From d980fd3098e617722acf56774ba3c11818154f68 Mon Sep 17 00:00:00 2001 From: "Corentin \"Koroh\" RICHARD" Date: Wed, 4 Oct 2023 02:06:24 +0200 Subject: [PATCH] Ajout des ressource pour le TP Osint --- ...dbd60b3bf6dfaa4806e80b7536a56b0681df37.png | Bin 0 -> 90797 bytes ...6dfaa4806e80b7536a56b0681df37.png_original | Bin 0 -> 90754 bytes ExifTool/Changes | 10628 + ExifTool/MANIFEST | 1164 + ExifTool/META.json | 54 + ExifTool/META.yml | 34 + ExifTool/Makefile.PL | 58 + ExifTool/README | 261 + ExifTool/arg_files/exif2iptc.args | 31 + ExifTool/arg_files/exif2xmp.args | 53 + ExifTool/arg_files/gps2xmp.args | 27 + ExifTool/arg_files/iptc2exif.args | 24 + ExifTool/arg_files/iptc2xmp.args | 57 + ExifTool/arg_files/iptcCore.args | 241 + ExifTool/arg_files/pdf2xmp.args | 27 + ExifTool/arg_files/xmp2exif.args | 59 + ExifTool/arg_files/xmp2gps.args | 31 + ExifTool/arg_files/xmp2iptc.args | 66 + ExifTool/arg_files/xmp2pdf.args | 27 + ExifTool/config_files/acdsee.config | 259 + ExifTool/config_files/age.config | 84 + ExifTool/config_files/bibble.config | 520 + ExifTool/config_files/convert_regions.config | 234 + ExifTool/config_files/cuepointlist.config | 70 + ExifTool/config_files/depthmap.config | 56 + ExifTool/config_files/example.config | 358 + ExifTool/config_files/fotoware.config | 114 + ExifTool/config_files/frameCount.config | 56 + ExifTool/config_files/gps2utm.config | 256 + ExifTool/config_files/guano.config | 161 + ExifTool/config_files/nksc.config | 146 + ExifTool/config_files/photoshop_paths.config | 221 + ExifTool/config_files/picasa_faces.config | 382 + ExifTool/config_files/pix4d.config | 99 + ExifTool/config_files/rotate_regions.config | 173 + ExifTool/config_files/tiff_version.config | 121 + ExifTool/config_files/time_zone.config | 96 + ExifTool/exiftool | 7460 + ExifTool/fmt_files/gpx.fmt | 39 + ExifTool/fmt_files/gpx_wpt.fmt | 43 + ExifTool/fmt_files/kml.fmt | 62 + ExifTool/fmt_files/kml_track.fmt | 49 + ExifTool/html/ExifTool.html | 2399 + ExifTool/html/MIE1.1-20070121.pdf | Bin 0 -> 128751 bytes ExifTool/html/Shift.html | 183 + ExifTool/html/TagNames/AFCP.html | 54 + ExifTool/html/TagNames/AIFF.html | 160 + ExifTool/html/TagNames/APE.html | 155 + ExifTool/html/TagNames/APP12.html | 189 + ExifTool/html/TagNames/ASF.html | 1067 + ExifTool/html/TagNames/Apple.html | 242 + ExifTool/html/TagNames/Audible.html | 178 + ExifTool/html/TagNames/BMP.html | 234 + ExifTool/html/TagNames/BPG.html | 119 + ExifTool/html/TagNames/CBOR.html | 62 + ExifTool/html/TagNames/Canon.html | 11695 + ExifTool/html/TagNames/CanonCustom.html | 2224 + ExifTool/html/TagNames/CanonRaw.html | 564 + ExifTool/html/TagNames/CanonVRD.html | 2042 + ExifTool/html/TagNames/Casio.html | 1607 + ExifTool/html/TagNames/Composite.html | 832 + ExifTool/html/TagNames/DICOM.html | 15763 + ExifTool/html/TagNames/DJI.html | 439 + ExifTool/html/TagNames/DNG.html | 312 + ExifTool/html/TagNames/DPX.html | 300 + ExifTool/html/TagNames/DV.html | 76 + ExifTool/html/TagNames/DarwinCore.html | 1891 + ExifTool/html/TagNames/DjVu.html | 314 + ExifTool/html/TagNames/EXE.html | 766 + ExifTool/html/TagNames/EXIF.html | 3892 + ExifTool/html/TagNames/Extra.html | 580 + ExifTool/html/TagNames/FITS.html | 96 + ExifTool/html/TagNames/FLAC.html | 190 + ExifTool/html/TagNames/FLIF.html | 85 + ExifTool/html/TagNames/FLIR.html | 1434 + ExifTool/html/TagNames/Flash.html | 420 + ExifTool/html/TagNames/FlashPix.html | 1727 + ExifTool/html/TagNames/Font.html | 486 + ExifTool/html/TagNames/FotoStation.html | 104 + ExifTool/html/TagNames/FujiFilm.html | 1280 + ExifTool/html/TagNames/GE.html | 41 + ExifTool/html/TagNames/GIF.html | 191 + ExifTool/html/TagNames/GIMP.html | 159 + ExifTool/html/TagNames/GPS.html | 231 + ExifTool/html/TagNames/GeoTiff.html | 2303 + ExifTool/html/TagNames/GoPro.html | 589 + ExifTool/html/TagNames/H264.html | 399 + ExifTool/html/TagNames/HP.html | 156 + ExifTool/html/TagNames/HTML.html | 667 + ExifTool/html/TagNames/ICC_Profile.html | 1412 + ExifTool/html/TagNames/ICO.html | 82 + ExifTool/html/TagNames/ID3.html | 1679 + ExifTool/html/TagNames/IPTC.html | 904 + ExifTool/html/TagNames/ISO.html | 160 + ExifTool/html/TagNames/ITC.html | 91 + ExifTool/html/TagNames/InfiRay.html | 485 + ExifTool/html/TagNames/JFIF.html | 89 + ExifTool/html/TagNames/JPEG.html | 696 + ExifTool/html/TagNames/JSON.html | 58 + ExifTool/html/TagNames/JVC.html | 57 + ExifTool/html/TagNames/Jpeg2000.html | 598 + ExifTool/html/TagNames/Kodak.html | 5735 + ExifTool/html/TagNames/KyoceraRaw.html | 93 + ExifTool/html/TagNames/LIF.html | 30 + ExifTool/html/TagNames/LNK.html | 479 + ExifTool/html/TagNames/Leaf.html | 872 + ExifTool/html/TagNames/Lytro.html | 123 + ExifTool/html/TagNames/M2TS.html | 154 + ExifTool/html/TagNames/MIE.html | 1035 + ExifTool/html/TagNames/MIFF.html | 197 + ExifTool/html/TagNames/MISB.html | 767 + ExifTool/html/TagNames/MNG.html | 848 + ExifTool/html/TagNames/MOI.html | 61 + ExifTool/html/TagNames/MPC.html | 98 + ExifTool/html/TagNames/MPEG.html | 250 + ExifTool/html/TagNames/MPF.html | 201 + ExifTool/html/TagNames/MRC.html | 731 + ExifTool/html/TagNames/MWG.html | 591 + ExifTool/html/TagNames/MXF.html | 6674 + ExifTool/html/TagNames/MacOS.html | 660 + ExifTool/html/TagNames/Matroska.html | 1651 + ExifTool/html/TagNames/Microsoft.html | 2149 + ExifTool/html/TagNames/Minolta.html | 2598 + ExifTool/html/TagNames/MinoltaRaw.html | 332 + ExifTool/html/TagNames/Motorola.html | 53 + ExifTool/html/TagNames/Nikon.html | 13326 + ExifTool/html/TagNames/NikonCapture.html | 800 + ExifTool/html/TagNames/NikonCustom.html | 11551 + ExifTool/html/TagNames/NikonSettings.html | 2587 + ExifTool/html/TagNames/Nintendo.html | 64 + ExifTool/html/TagNames/OOXML.html | 281 + ExifTool/html/TagNames/Ogg.html | 50 + ExifTool/html/TagNames/Olympus.html | 3800 + ExifTool/html/TagNames/OpenEXR.html | 246 + ExifTool/html/TagNames/Opus.html | 63 + ExifTool/html/TagNames/Other.html | 42 + ExifTool/html/TagNames/PCX.html | 105 + ExifTool/html/TagNames/PDF.html | 752 + ExifTool/html/TagNames/PGF.html | 81 + ExifTool/html/TagNames/PICT.html | 754 + ExifTool/html/TagNames/PLIST.html | 87 + ExifTool/html/TagNames/PLUS.html | 2629 + ExifTool/html/TagNames/PNG.html | 691 + ExifTool/html/TagNames/PSP.html | 161 + ExifTool/html/TagNames/Palm.html | 389 + ExifTool/html/TagNames/Panasonic.html | 2136 + ExifTool/html/TagNames/PanasonicRaw.html | 686 + ExifTool/html/TagNames/Parrot.html | 676 + ExifTool/html/TagNames/Pentax.html | 4926 + ExifTool/html/TagNames/PhaseOne.html | 267 + ExifTool/html/TagNames/PhotoCD.html | 403 + ExifTool/html/TagNames/PhotoMechanic.html | 199 + ExifTool/html/TagNames/Photoshop.html | 805 + ExifTool/html/TagNames/PostScript.html | 174 + ExifTool/html/TagNames/PrintIM.html | 32 + ExifTool/html/TagNames/Qualcomm.html | 4779 + ExifTool/html/TagNames/QuickTime.html | 9082 + ExifTool/html/TagNames/RIFF.html | 1748 + ExifTool/html/TagNames/RSRC.html | 75 + ExifTool/html/TagNames/RTF.html | 151 + ExifTool/html/TagNames/Radiance.html | 93 + ExifTool/html/TagNames/Rawzor.html | 46 + ExifTool/html/TagNames/Real.html | 781 + ExifTool/html/TagNames/Reconyx.html | 396 + ExifTool/html/TagNames/Red.html | 292 + ExifTool/html/TagNames/Ricoh.html | 812 + ExifTool/html/TagNames/Samsung.html | 1137 + ExifTool/html/TagNames/Sanyo.html | 367 + ExifTool/html/TagNames/Scalado.html | 46 + ExifTool/html/TagNames/Shortcuts.html | 307 + ExifTool/html/TagNames/Sigma.html | 736 + ExifTool/html/TagNames/SigmaRaw.html | 439 + ExifTool/html/TagNames/Sony.html | 12041 + ExifTool/html/TagNames/SonyIDC.html | 339 + ExifTool/html/TagNames/Stim.html | 201 + ExifTool/html/TagNames/Text.html | 76 + ExifTool/html/TagNames/Theora.html | 103 + ExifTool/html/TagNames/Torrent.html | 189 + ExifTool/html/TagNames/Unknown.html | 32 + ExifTool/html/TagNames/VCard.html | 558 + ExifTool/html/TagNames/Vorbis.html | 244 + ExifTool/html/TagNames/WPG.html | 132 + ExifTool/html/TagNames/WTV.html | 381 + ExifTool/html/TagNames/XMP.html | 20009 ++ ExifTool/html/TagNames/ZIP.html | 245 + ExifTool/html/TagNames/ZISRAW.html | 41 + ExifTool/html/TagNames/iWork.html | 50 + ExifTool/html/TagNames/index.html | 251 + ExifTool/html/TagNames/style.css | 31 + ExifTool/html/ancient_history.html | 11545 + ExifTool/html/canon_raw.html | 368 + ExifTool/html/commentary.html | 336 + ExifTool/html/config.html | 377 + ExifTool/html/data_members.html | 323 + ExifTool/html/exiftool_pod.html | 2071 + ExifTool/html/faq.html | 1825 + ExifTool/html/filename.html | 380 + ExifTool/html/geotag.html | 728 + ExifTool/html/history.html | 393 + ExifTool/html/htmldump.html | 769 + ExifTool/html/idiosyncracies.html | 293 + ExifTool/html/index.html | 1587 + ExifTool/html/install.html | 266 + ExifTool/html/metafiles.html | 323 + ExifTool/html/mistakes.html | 155 + ExifTool/html/overview.png | Bin 0 -> 27207 bytes ExifTool/html/standards.html | 306 + ExifTool/html/struct.html | 436 + ExifTool/html/style.css | 31 + ExifTool/html/under.html | 165 + ExifTool/html/verbose.html | 211 + ExifTool/html/writing.html | 239 + ExifTool/lib/File/RandomAccess.pm | 418 + ExifTool/lib/File/RandomAccess.pod | 250 + ExifTool/lib/Image/ExifTool.pm | 9370 + ExifTool/lib/Image/ExifTool.pod | 2897 + ExifTool/lib/Image/ExifTool/7Z.pm | 793 + ExifTool/lib/Image/ExifTool/AES.pm | 501 + ExifTool/lib/Image/ExifTool/AFCP.pm | 281 + ExifTool/lib/Image/ExifTool/AIFF.pm | 312 + ExifTool/lib/Image/ExifTool/APE.pm | 287 + ExifTool/lib/Image/ExifTool/APP12.pm | 322 + ExifTool/lib/Image/ExifTool/ASF.pm | 898 + ExifTool/lib/Image/ExifTool/Apple.pm | 356 + ExifTool/lib/Image/ExifTool/Audible.pm | 317 + ExifTool/lib/Image/ExifTool/BMP.pm | 360 + ExifTool/lib/Image/ExifTool/BPG.pm | 253 + ExifTool/lib/Image/ExifTool/BZZ.pm | 472 + ExifTool/lib/Image/ExifTool/BigTIFF.pm | 304 + ExifTool/lib/Image/ExifTool/BuildTagLookup.pm | 2793 + ExifTool/lib/Image/ExifTool/CBOR.pm | 331 + ExifTool/lib/Image/ExifTool/Canon.pm | 10275 + ExifTool/lib/Image/ExifTool/CanonCustom.pm | 2900 + ExifTool/lib/Image/ExifTool/CanonRaw.pm | 921 + ExifTool/lib/Image/ExifTool/CanonVRD.pm | 2284 + ExifTool/lib/Image/ExifTool/CaptureOne.pm | 235 + ExifTool/lib/Image/ExifTool/Casio.pm | 2060 + ExifTool/lib/Image/ExifTool/Charset.pm | 434 + ExifTool/lib/Image/ExifTool/Charset/Arabic.pm | 39 + ExifTool/lib/Image/ExifTool/Charset/Baltic.pm | 35 + .../lib/Image/ExifTool/Charset/Cyrillic.pm | 45 + .../lib/Image/ExifTool/Charset/DOSCyrillic.pm | 49 + .../lib/Image/ExifTool/Charset/DOSLatin1.pm | 49 + .../lib/Image/ExifTool/Charset/DOSLatinUS.pm | 49 + ExifTool/lib/Image/ExifTool/Charset/Greek.pm | 40 + ExifTool/lib/Image/ExifTool/Charset/Hebrew.pm | 36 + ExifTool/lib/Image/ExifTool/Charset/JIS.pm | 1735 + ExifTool/lib/Image/ExifTool/Charset/Latin.pm | 24 + ExifTool/lib/Image/ExifTool/Charset/Latin2.pm | 36 + .../lib/Image/ExifTool/Charset/MacArabic.pm | 47 + .../Image/ExifTool/Charset/MacChineseCN.pm | 2088 + .../Image/ExifTool/Charset/MacChineseTW.pm | 3623 + .../lib/Image/ExifTool/Charset/MacCroatian.pm | 43 + .../lib/Image/ExifTool/Charset/MacCyrillic.pm | 47 + .../lib/Image/ExifTool/Charset/MacGreek.pm | 45 + .../lib/Image/ExifTool/Charset/MacHebrew.pm | 47 + .../lib/Image/ExifTool/Charset/MacIceland.pm | 42 + .../lib/Image/ExifTool/Charset/MacJapanese.pm | 1933 + .../lib/Image/ExifTool/Charset/MacKorean.pm | 2720 + .../lib/Image/ExifTool/Charset/MacLatin2.pm | 44 + .../lib/Image/ExifTool/Charset/MacRSymbol.pm | 2087 + .../lib/Image/ExifTool/Charset/MacRoman.pm | 42 + .../lib/Image/ExifTool/Charset/MacRomanian.pm | 42 + .../lib/Image/ExifTool/Charset/MacThai.pm | 49 + .../lib/Image/ExifTool/Charset/MacTurkish.pm | 42 + ExifTool/lib/Image/ExifTool/Charset/PDFDoc.pm | 28 + .../lib/Image/ExifTool/Charset/ShiftJIS.pm | 1835 + ExifTool/lib/Image/ExifTool/Charset/Symbol.pm | 54 + ExifTool/lib/Image/ExifTool/Charset/Thai.pm | 41 + .../lib/Image/ExifTool/Charset/Turkish.pm | 25 + .../lib/Image/ExifTool/Charset/Vietnam.pm | 27 + ExifTool/lib/Image/ExifTool/DICOM.pm | 3881 + ExifTool/lib/Image/ExifTool/DJI.pm | 249 + ExifTool/lib/Image/ExifTool/DNG.pm | 848 + ExifTool/lib/Image/ExifTool/DPX.pm | 247 + ExifTool/lib/Image/ExifTool/DV.pm | 315 + ExifTool/lib/Image/ExifTool/DarwinCore.pm | 393 + ExifTool/lib/Image/ExifTool/DjVu.pm | 378 + ExifTool/lib/Image/ExifTool/EXE.pm | 1440 + ExifTool/lib/Image/ExifTool/Exif.pm | 6948 + ExifTool/lib/Image/ExifTool/FITS.pm | 159 + ExifTool/lib/Image/ExifTool/FLAC.pm | 321 + ExifTool/lib/Image/ExifTool/FLIF.pm | 353 + ExifTool/lib/Image/ExifTool/FLIR.pm | 1669 + ExifTool/lib/Image/ExifTool/Fixup.pm | 366 + ExifTool/lib/Image/ExifTool/Flash.pm | 755 + ExifTool/lib/Image/ExifTool/FlashPix.pm | 2537 + ExifTool/lib/Image/ExifTool/Font.pm | 654 + ExifTool/lib/Image/ExifTool/FotoStation.pm | 261 + ExifTool/lib/Image/ExifTool/FujiFilm.pm | 1765 + ExifTool/lib/Image/ExifTool/GE.pm | 81 + ExifTool/lib/Image/ExifTool/GIF.pm | 569 + ExifTool/lib/Image/ExifTool/GIMP.pm | 294 + ExifTool/lib/Image/ExifTool/GPS.pm | 635 + ExifTool/lib/Image/ExifTool/GeoTiff.pm | 2265 + ExifTool/lib/Image/ExifTool/Geotag.pm | 1518 + ExifTool/lib/Image/ExifTool/GoPro.pm | 757 + ExifTool/lib/Image/ExifTool/H264.pm | 1149 + ExifTool/lib/Image/ExifTool/HP.pm | 263 + ExifTool/lib/Image/ExifTool/HTML.pm | 583 + ExifTool/lib/Image/ExifTool/HtmlDump.pm | 930 + ExifTool/lib/Image/ExifTool/ICC_Profile.pm | 1382 + ExifTool/lib/Image/ExifTool/ICO.pm | 143 + ExifTool/lib/Image/ExifTool/ID3.pm | 1680 + ExifTool/lib/Image/ExifTool/IPTC.pm | 1305 + ExifTool/lib/Image/ExifTool/ISO.pm | 206 + ExifTool/lib/Image/ExifTool/ITC.pm | 215 + ExifTool/lib/Image/ExifTool/Import.pm | 360 + ExifTool/lib/Image/ExifTool/InDesign.pm | 279 + ExifTool/lib/Image/ExifTool/InfiRay.pm | 227 + ExifTool/lib/Image/ExifTool/JPEG.pm | 779 + ExifTool/lib/Image/ExifTool/JPEGDigest.pm | 2617 + ExifTool/lib/Image/ExifTool/JSON.pm | 190 + ExifTool/lib/Image/ExifTool/JVC.pm | 130 + ExifTool/lib/Image/ExifTool/Jpeg2000.pm | 1629 + ExifTool/lib/Image/ExifTool/Kodak.pm | 3279 + ExifTool/lib/Image/ExifTool/KyoceraRaw.pm | 173 + ExifTool/lib/Image/ExifTool/LIF.pm | 161 + ExifTool/lib/Image/ExifTool/LNK.pm | 727 + ExifTool/lib/Image/ExifTool/Lang/cs.pm | 1588 + ExifTool/lib/Image/ExifTool/Lang/de.pm | 8720 + ExifTool/lib/Image/ExifTool/Lang/en_ca.pm | 1002 + ExifTool/lib/Image/ExifTool/Lang/en_gb.pm | 1045 + ExifTool/lib/Image/ExifTool/Lang/es.pm | 4023 + ExifTool/lib/Image/ExifTool/Lang/fi.pm | 2896 + ExifTool/lib/Image/ExifTool/Lang/fr.pm | 11290 + ExifTool/lib/Image/ExifTool/Lang/it.pm | 7921 + ExifTool/lib/Image/ExifTool/Lang/ja.pm | 5858 + ExifTool/lib/Image/ExifTool/Lang/ko.pm | 2333 + ExifTool/lib/Image/ExifTool/Lang/nl.pm | 3226 + ExifTool/lib/Image/ExifTool/Lang/pl.pm | 1276 + ExifTool/lib/Image/ExifTool/Lang/ru.pm | 5736 + ExifTool/lib/Image/ExifTool/Lang/sk.pm | 1806 + ExifTool/lib/Image/ExifTool/Lang/sv.pm | 638 + ExifTool/lib/Image/ExifTool/Lang/tr.pm | 546 + ExifTool/lib/Image/ExifTool/Lang/zh_cn.pm | 1330 + ExifTool/lib/Image/ExifTool/Lang/zh_tw.pm | 801 + ExifTool/lib/Image/ExifTool/Leaf.pm | 517 + ExifTool/lib/Image/ExifTool/Lytro.pm | 214 + ExifTool/lib/Image/ExifTool/M2TS.pm | 972 + ExifTool/lib/Image/ExifTool/MIE.pm | 2574 + ExifTool/lib/Image/ExifTool/MIEUnits.pod | 377 + ExifTool/lib/Image/ExifTool/MIFF.pm | 279 + ExifTool/lib/Image/ExifTool/MISB.pm | 494 + ExifTool/lib/Image/ExifTool/MNG.pm | 684 + ExifTool/lib/Image/ExifTool/MOI.pm | 159 + ExifTool/lib/Image/ExifTool/MPC.pm | 156 + ExifTool/lib/Image/ExifTool/MPEG.pm | 735 + ExifTool/lib/Image/ExifTool/MPF.pm | 289 + ExifTool/lib/Image/ExifTool/MRC.pm | 341 + ExifTool/lib/Image/ExifTool/MWG.pm | 777 + ExifTool/lib/Image/ExifTool/MXF.pm | 3031 + ExifTool/lib/Image/ExifTool/MacOS.pm | 735 + ExifTool/lib/Image/ExifTool/MakerNotes.pm | 1844 + ExifTool/lib/Image/ExifTool/Matroska.pm | 1178 + ExifTool/lib/Image/ExifTool/Microsoft.pm | 1122 + ExifTool/lib/Image/ExifTool/Minolta.pm | 2958 + ExifTool/lib/Image/ExifTool/MinoltaRaw.pm | 537 + ExifTool/lib/Image/ExifTool/Motorola.pm | 178 + ExifTool/lib/Image/ExifTool/Nikon.pm | 13343 + ExifTool/lib/Image/ExifTool/NikonCapture.pm | 940 + ExifTool/lib/Image/ExifTool/NikonCustom.pm | 10918 + ExifTool/lib/Image/ExifTool/NikonSettings.pm | 2095 + ExifTool/lib/Image/ExifTool/Nintendo.pm | 128 + ExifTool/lib/Image/ExifTool/OOXML.pm | 410 + ExifTool/lib/Image/ExifTool/Ogg.pm | 240 + ExifTool/lib/Image/ExifTool/Olympus.pm | 4209 + ExifTool/lib/Image/ExifTool/OpenEXR.pm | 343 + ExifTool/lib/Image/ExifTool/Opus.pm | 98 + ExifTool/lib/Image/ExifTool/Other.pm | 93 + ExifTool/lib/Image/ExifTool/PCX.pm | 138 + ExifTool/lib/Image/ExifTool/PDF.pm | 2447 + ExifTool/lib/Image/ExifTool/PGF.pm | 143 + ExifTool/lib/Image/ExifTool/PICT.pm | 1260 + ExifTool/lib/Image/ExifTool/PLIST.pm | 474 + ExifTool/lib/Image/ExifTool/PLUS.pm | 2622 + ExifTool/lib/Image/ExifTool/PNG.pm | 1683 + ExifTool/lib/Image/ExifTool/PPM.pm | 169 + ExifTool/lib/Image/ExifTool/PSP.pm | 306 + ExifTool/lib/Image/ExifTool/Palm.pm | 403 + ExifTool/lib/Image/ExifTool/Panasonic.pm | 2899 + ExifTool/lib/Image/ExifTool/PanasonicRaw.pm | 955 + ExifTool/lib/Image/ExifTool/Parrot.pm | 846 + ExifTool/lib/Image/ExifTool/Pentax.pm | 6339 + ExifTool/lib/Image/ExifTool/PhaseOne.pm | 746 + ExifTool/lib/Image/ExifTool/PhotoCD.pm | 506 + ExifTool/lib/Image/ExifTool/PhotoMechanic.pm | 256 + ExifTool/lib/Image/ExifTool/Photoshop.pm | 1231 + ExifTool/lib/Image/ExifTool/PostScript.pm | 802 + ExifTool/lib/Image/ExifTool/PrintIM.pm | 125 + ExifTool/lib/Image/ExifTool/Qualcomm.pm | 1344 + ExifTool/lib/Image/ExifTool/QuickTime.pm | 9991 + .../lib/Image/ExifTool/QuickTimeStream.pl | 3206 + ExifTool/lib/Image/ExifTool/README | 1144 + ExifTool/lib/Image/ExifTool/RIFF.pm | 2185 + ExifTool/lib/Image/ExifTool/RSRC.pm | 247 + ExifTool/lib/Image/ExifTool/RTF.pm | 390 + ExifTool/lib/Image/ExifTool/Radiance.pm | 149 + ExifTool/lib/Image/ExifTool/Rawzor.pm | 190 + ExifTool/lib/Image/ExifTool/Real.pm | 739 + ExifTool/lib/Image/ExifTool/Reconyx.pm | 444 + ExifTool/lib/Image/ExifTool/Red.pm | 325 + ExifTool/lib/Image/ExifTool/Ricoh.pm | 1169 + ExifTool/lib/Image/ExifTool/Samsung.pm | 1749 + ExifTool/lib/Image/ExifTool/Sanyo.pm | 440 + ExifTool/lib/Image/ExifTool/Scalado.pm | 141 + ExifTool/lib/Image/ExifTool/Shift.pl | 644 + ExifTool/lib/Image/ExifTool/Shortcuts.pm | 356 + ExifTool/lib/Image/ExifTool/Sigma.pm | 885 + ExifTool/lib/Image/ExifTool/SigmaRaw.pm | 702 + ExifTool/lib/Image/ExifTool/Sony.pm | 11583 + ExifTool/lib/Image/ExifTool/SonyIDC.pm | 393 + ExifTool/lib/Image/ExifTool/Stim.pm | 196 + ExifTool/lib/Image/ExifTool/TagInfoXML.pm | 847 + ExifTool/lib/Image/ExifTool/TagLookup.pm | 12974 + ExifTool/lib/Image/ExifTool/TagNames.pod | 39741 +++ ExifTool/lib/Image/ExifTool/Text.pm | 244 + ExifTool/lib/Image/ExifTool/Theora.pm | 145 + ExifTool/lib/Image/ExifTool/Torrent.pm | 331 + ExifTool/lib/Image/ExifTool/Unknown.pm | 66 + ExifTool/lib/Image/ExifTool/VCard.pm | 457 + ExifTool/lib/Image/ExifTool/Validate.pm | 688 + ExifTool/lib/Image/ExifTool/Vorbis.pm | 255 + ExifTool/lib/Image/ExifTool/WPG.pm | 296 + ExifTool/lib/Image/ExifTool/WTV.pm | 319 + ExifTool/lib/Image/ExifTool/WriteCanonRaw.pl | 644 + ExifTool/lib/Image/ExifTool/WriteExif.pl | 2710 + ExifTool/lib/Image/ExifTool/WriteIPTC.pl | 724 + ExifTool/lib/Image/ExifTool/WritePDF.pl | 774 + ExifTool/lib/Image/ExifTool/WritePNG.pl | 414 + ExifTool/lib/Image/ExifTool/WritePhotoshop.pl | 270 + .../lib/Image/ExifTool/WritePostScript.pl | 700 + ExifTool/lib/Image/ExifTool/WriteQuickTime.pl | 1968 + ExifTool/lib/Image/ExifTool/WriteRIFF.pl | 359 + ExifTool/lib/Image/ExifTool/WriteXMP.pl | 1648 + ExifTool/lib/Image/ExifTool/Writer.pl | 7223 + ExifTool/lib/Image/ExifTool/XMP.pm | 4476 + ExifTool/lib/Image/ExifTool/XMP2.pl | 2226 + ExifTool/lib/Image/ExifTool/XMPStruct.pl | 950 + ExifTool/lib/Image/ExifTool/ZIP.pm | 865 + ExifTool/lib/Image/ExifTool/ZISRAW.pm | 242 + ExifTool/lib/Image/ExifTool/iWork.pm | 230 + ExifTool/perl-Image-ExifTool.spec | 120 + ExifTool/t/AFCP.t | 45 + ExifTool/t/AFCP_2.out | 39 + ExifTool/t/AFCP_3.out | 20 + ExifTool/t/AIFF.t | 28 + ExifTool/t/AIFF_2.out | 33 + ExifTool/t/APE.t | 38 + ExifTool/t/APE_2.out | 30 + ExifTool/t/APE_3.out | 58 + ExifTool/t/ASF.t | 28 + ExifTool/t/ASF_2.out | 56 + ExifTool/t/Apple.t | 29 + ExifTool/t/Apple_2.out | 79 + ExifTool/t/Audible.t | 28 + ExifTool/t/Audible_2.out | 40 + ExifTool/t/BMP.t | 28 + ExifTool/t/BMP_2.out | 24 + ExifTool/t/BPG.t | 28 + ExifTool/t/BPG_2.out | 92 + ExifTool/t/BigTIFF.t | 28 + ExifTool/t/BigTIFF_2.out | 21 + ExifTool/t/Canon.t | 41 + ExifTool/t/CanonRaw.t | 163 + ExifTool/t/CanonRaw_2.out | 169 + ExifTool/t/CanonRaw_4.out | 221 + ExifTool/t/CanonRaw_5.out | 209 + ExifTool/t/CanonRaw_6.out | 234 + ExifTool/t/CanonRaw_7.out | 251 + ExifTool/t/CanonRaw_8.out | 510 + ExifTool/t/CanonRaw_9.out | 634 + ExifTool/t/CanonVRD.t | 232 + ExifTool/t/CanonVRD_11.out | 121 + ExifTool/t/CanonVRD_12.out | 13 + ExifTool/t/CanonVRD_13.out | 102 + ExifTool/t/CanonVRD_14.out | 101 + ExifTool/t/CanonVRD_15.out | 5 + ExifTool/t/CanonVRD_16.out | 6 + ExifTool/t/CanonVRD_17.out | 5 + ExifTool/t/CanonVRD_18.out | 5 + ExifTool/t/CanonVRD_19.out | 5 + ExifTool/t/CanonVRD_2.out | 119 + ExifTool/t/CanonVRD_20.out | 5 + ExifTool/t/CanonVRD_21.out | 5 + ExifTool/t/CanonVRD_22.out | 1 + ExifTool/t/CanonVRD_24.out | 5 + ExifTool/t/CanonVRD_3.out | 119 + ExifTool/t/CanonVRD_4.out | 2 + ExifTool/t/CanonVRD_5.out | 3 + ExifTool/t/CanonVRD_6.out | 3 + ExifTool/t/CanonVRD_7.out | 2 + ExifTool/t/CanonVRD_8.out | 2 + ExifTool/t/CanonVRD_9.out | 2 + ExifTool/t/Canon_2.out | 358 + ExifTool/t/Canon_3.out | 3 + ExifTool/t/Casio.t | 54 + ExifTool/t/Casio_2.out | 72 + ExifTool/t/Casio_3.out | 102 + ExifTool/t/Casio_4.out | 28 + ExifTool/t/Casio_5.out | 80 + ExifTool/t/Casio_6.out | 117 + ExifTool/t/DICOM.t | 28 + ExifTool/t/DICOM_2.out | 109 + ExifTool/t/DNG.t | 40 + ExifTool/t/DNG_2.out | 329 + ExifTool/t/DNG_3.out | 4 + ExifTool/t/DPX.t | 28 + ExifTool/t/DPX_2.out | 42 + ExifTool/t/DV.t | 28 + ExifTool/t/DV_2.out | 26 + ExifTool/t/DjVu.t | 28 + ExifTool/t/DjVu_2.out | 46 + ExifTool/t/EXE.t | 31 + ExifTool/t/EXE_2.out | 35 + ExifTool/t/EXE_3.out | 10 + ExifTool/t/EXE_4.out | 8 + ExifTool/t/EXE_5.out | 9 + ExifTool/t/EXE_6.out | 8 + ExifTool/t/EXE_7.out | 10 + ExifTool/t/ExifTool.t | 403 + ExifTool/t/ExifTool_16.out | 185 + ExifTool/t/ExifTool_17.out | 599 + ExifTool/t/ExifTool_2.out | 442 + ExifTool/t/ExifTool_20.out | 1 + ExifTool/t/ExifTool_21.out | 1 + ExifTool/t/ExifTool_22.out | 1 + ExifTool/t/ExifTool_23.out | 6 + ExifTool/t/ExifTool_24.out | 3 + ExifTool/t/ExifTool_25.out | 14 + ExifTool/t/ExifTool_26.out | 13 + ExifTool/t/ExifTool_27.out | 4 + ExifTool/t/ExifTool_28.out | 11 + ExifTool/t/ExifTool_29.out | 441 + ExifTool/t/ExifTool_3.out | 89 + ExifTool/t/ExifTool_30.out | 2 + ExifTool/t/ExifTool_31.out | 1 + ExifTool/t/ExifTool_32.out | 5 + ExifTool/t/ExifTool_33.out | 5 + ExifTool/t/ExifTool_34.out | 1 + ExifTool/t/ExifTool_35.out | 1 + ExifTool/t/ExifTool_4.out | 41 + ExifTool/t/ExifTool_5.out | 6 + ExifTool/t/ExifTool_6.out | 71 + ExifTool/t/ExifTool_7.out | 126 + ExifTool/t/ExifTool_8.out | 126 + ExifTool/t/ExifTool_9.out | 33 + ExifTool/t/FITS.t | 28 + ExifTool/t/FITS_2.out | 47 + ExifTool/t/FLAC.t | 37 + ExifTool/t/FLAC_2.out | 27 + ExifTool/t/FLAC_3.out | 21 + ExifTool/t/FLIF.t | 133 + ExifTool/t/FLIF_2.out | 51 + ExifTool/t/FLIF_3.out | 5 + ExifTool/t/FLIF_4.out | 18 + ExifTool/t/FLIF_5.out | 4 + ExifTool/t/FLIF_6.out | 4 + ExifTool/t/FLIR.t | 31 + ExifTool/t/FLIR_2.out | 121 + ExifTool/t/FLIR_3.out | 49 + ExifTool/t/Flash.t | 37 + ExifTool/t/FlashPix.t | 30 + ExifTool/t/FlashPix_2.out | 49 + ExifTool/t/Flash_2.out | 22 + ExifTool/t/Flash_3.out | 55 + ExifTool/t/Font.t | 31 + ExifTool/t/Font_2.out | 24 + ExifTool/t/Font_3.out | 36 + ExifTool/t/Font_4.out | 27 + ExifTool/t/Font_5.out | 27 + ExifTool/t/Font_6.out | 37 + ExifTool/t/Font_7.out | 37 + ExifTool/t/FotoStation.t | 39 + ExifTool/t/FotoStation_2.out | 41 + ExifTool/t/FotoStation_3.out | 3 + ExifTool/t/FujiFilm.t | 78 + ExifTool/t/FujiFilm_2.out | 89 + ExifTool/t/FujiFilm_3.out | 90 + ExifTool/t/FujiFilm_4.out | 147 + ExifTool/t/FujiFilm_5.out | 1 + ExifTool/t/GE.t | 46 + ExifTool/t/GE_2.out | 86 + ExifTool/t/GE_3.out | 86 + ExifTool/t/GIF.t | 84 + ExifTool/t/GIF_2.out | 43 + ExifTool/t/GIF_3.out | 44 + ExifTool/t/GIF_4.out | 19 + ExifTool/t/GIMP.t | 28 + ExifTool/t/GIMP_2.out | 67 + ExifTool/t/GPS.t | 46 + ExifTool/t/GPS_2.out | 96 + ExifTool/t/GPS_3.out | 98 + ExifTool/t/GeoTiff.t | 53 + ExifTool/t/GeoTiff_2.out | 38 + ExifTool/t/GeoTiff_3.out | 38 + ExifTool/t/GeoTiff_4.out | 11 + ExifTool/t/Geotag.t | 223 + ExifTool/t/Geotag_10.out | 15 + ExifTool/t/Geotag_11.out | 3 + ExifTool/t/Geotag_12.out | 5 + ExifTool/t/Geotag_2.out | 9 + ExifTool/t/Geotag_3.out | 6 + ExifTool/t/Geotag_5.out | 1 + ExifTool/t/Geotag_6.out | 9 + ExifTool/t/Geotag_7.out | 54 + ExifTool/t/Geotag_8.out | 15 + ExifTool/t/Geotag_9.out | 7 + ExifTool/t/GoPro.t | 29 + ExifTool/t/GoPro_2.out | 30 + ExifTool/t/HTML.t | 28 + ExifTool/t/HTML_2.out | 69 + ExifTool/t/ICO.t | 28 + ExifTool/t/ICO_2.out | 20 + ExifTool/t/IPTC.t | 158 + ExifTool/t/IPTC_2.out | 69 + ExifTool/t/IPTC_4.out | 67 + ExifTool/t/IPTC_5.out | 1 + ExifTool/t/IPTC_6.out | 2 + ExifTool/t/IPTC_7.out | 1 + ExifTool/t/IPTC_8.out | 20 + ExifTool/t/ISO.t | 28 + ExifTool/t/ISO_2.out | 19 + ExifTool/t/ITC.t | 28 + ExifTool/t/ITC_2.out | 21 + ExifTool/t/InDesign.t | 62 + ExifTool/t/InDesign_2.out | 20 + ExifTool/t/InDesign_3.out | 22 + ExifTool/t/InDesign_4.out | 12 + ExifTool/t/InfiRay.t | 30 + ExifTool/t/InfiRay_2.out | 126 + ExifTool/t/JSON.t | 30 + ExifTool/t/JSON_2.out | 19 + ExifTool/t/JVC.t | 37 + ExifTool/t/JVC_2.out | 3 + ExifTool/t/JVC_3.out | 3 + ExifTool/t/JXL.t | 64 + ExifTool/t/JXL_2.out | 8 + ExifTool/t/JXL_3.out | 19 + ExifTool/t/JXL_4.out | 8 + ExifTool/t/Jpeg2000.t | 61 + ExifTool/t/Jpeg2000_2.out | 71 + ExifTool/t/Jpeg2000_3.out | 54 + ExifTool/t/Jpeg2000_4.out | 17 + ExifTool/t/Jpeg2000_5.out | 2 + ExifTool/t/Kodak.t | 40 + ExifTool/t/Kodak_2.out | 111 + ExifTool/t/Kodak_3.out | 121 + ExifTool/t/KyoceraRaw.t | 28 + ExifTool/t/KyoceraRaw_2.out | 28 + ExifTool/t/LNK.t | 28 + ExifTool/t/LNK_2.out | 47 + ExifTool/t/Lang.t | 44 + ExifTool/t/Lang_1.out | 1 + ExifTool/t/Lang_10.out | 47 + ExifTool/t/Lang_11.out | 47 + ExifTool/t/Lang_12.out | 47 + ExifTool/t/Lang_13.out | 47 + ExifTool/t/Lang_14.out | 47 + ExifTool/t/Lang_15.out | 47 + ExifTool/t/Lang_16.out | 47 + ExifTool/t/Lang_17.out | 47 + ExifTool/t/Lang_18.out | 47 + ExifTool/t/Lang_19.out | 47 + ExifTool/t/Lang_2.out | 47 + ExifTool/t/Lang_3.out | 47 + ExifTool/t/Lang_4.out | 47 + ExifTool/t/Lang_5.out | 47 + ExifTool/t/Lang_6.out | 47 + ExifTool/t/Lang_7.out | 47 + ExifTool/t/Lang_8.out | 47 + ExifTool/t/Lang_9.out | 47 + ExifTool/t/Lytro.t | 28 + ExifTool/t/Lytro_2.out | 109 + ExifTool/t/M2TS.t | 29 + ExifTool/t/M2TS_2.out | 31 + ExifTool/t/MIE.t | 84 + ExifTool/t/MIE_2.out | 69 + ExifTool/t/MIE_3.out | 137 + ExifTool/t/MIE_5.out | 1 + ExifTool/t/MIE_6.out | 1 + ExifTool/t/MIFF.t | 28 + ExifTool/t/MIFF_2.out | 113 + ExifTool/t/MOI.t | 28 + ExifTool/t/MOI_2.out | 11 + ExifTool/t/MP3.t | 29 + ExifTool/t/MP3_2.out | 46 + ExifTool/t/MRC.t | 29 + ExifTool/t/MRC_2.out | 157 + ExifTool/t/MWG.t | 100 + ExifTool/t/MWG_2.out | 13 + ExifTool/t/MWG_3.out | 11 + ExifTool/t/MWG_4.out | 7 + ExifTool/t/MWG_5.out | 23 + ExifTool/t/MWG_6.out | 2 + ExifTool/t/MWG_7.out | 11 + ExifTool/t/MXF.t | 28 + ExifTool/t/MXF_2.out | 79 + ExifTool/t/MacOS.t | 28 + ExifTool/t/MacOS_2.out | 19 + ExifTool/t/Matroska.t | 29 + ExifTool/t/Matroska_2.out | 66 + ExifTool/t/Minolta.t | 50 + ExifTool/t/Minolta_2.out | 135 + ExifTool/t/Minolta_3.out | 148 + ExifTool/t/Minolta_4.out | 156 + ExifTool/t/Motorola.t | 29 + ExifTool/t/Motorola_2.out | 97 + ExifTool/t/Nikon.t | 123 + ExifTool/t/Nikon_2.out | 84 + ExifTool/t/Nikon_3.out | 100 + ExifTool/t/Nikon_4.out | 176 + ExifTool/t/Nikon_5.out | 169 + ExifTool/t/Nikon_7.out | 261 + ExifTool/t/Nikon_8.out | 4 + ExifTool/t/Olympus.t | 94 + ExifTool/t/Olympus_2.out | 81 + ExifTool/t/Olympus_3.out | 83 + ExifTool/t/Olympus_4.out | 195 + ExifTool/t/Olympus_5.out | 266 + ExifTool/t/Olympus_6.out | 153 + ExifTool/t/Olympus_7.out | 484 + ExifTool/t/Olympus_8.out | 15 + ExifTool/t/OpenEXR.t | 28 + ExifTool/t/OpenEXR_2.out | 25 + ExifTool/t/Opus.t | 28 + ExifTool/t/Opus_2.out | 26 + ExifTool/t/PCX.t | 28 + ExifTool/t/PCX_2.out | 26 + ExifTool/t/PDF.t | 303 + ExifTool/t/PDF_10.out | 10 + ExifTool/t/PDF_11.out | 10 + ExifTool/t/PDF_12.out | 9 + ExifTool/t/PDF_14.out | 5 + ExifTool/t/PDF_15.out | 6 + ExifTool/t/PDF_16.out | 4 + ExifTool/t/PDF_17.out | 4 + ExifTool/t/PDF_18.out | 5 + ExifTool/t/PDF_19.out | 6 + ExifTool/t/PDF_2.out | 127 + ExifTool/t/PDF_20.out | 4 + ExifTool/t/PDF_22.out | 3 + ExifTool/t/PDF_4.out | 12 + ExifTool/t/PDF_5.out | 8 + ExifTool/t/PDF_6.out | 9 + ExifTool/t/PDF_7.out | 11 + ExifTool/t/PDF_8.out | 9 + ExifTool/t/PDF_9.out | 9 + ExifTool/t/PFM.t | 28 + ExifTool/t/PFM_2.out | 17 + ExifTool/t/PGF.t | 28 + ExifTool/t/PGF_2.out | 35 + ExifTool/t/PICT.t | 28 + ExifTool/t/PICT_2.out | 17 + ExifTool/t/PLIST.t | 31 + ExifTool/t/PLIST_2.out | 21 + ExifTool/t/PLIST_3.out | 21 + ExifTool/t/PLIST_4.out | 17 + ExifTool/t/PLUS.t | 45 + ExifTool/t/PLUS_2.out | 31 + ExifTool/t/PNG.t | 141 + ExifTool/t/PNG_2.out | 25 + ExifTool/t/PNG_3.out | 123 + ExifTool/t/PNG_4.out | 12 + ExifTool/t/PNG_5.out | 24 + ExifTool/t/PNG_6.out | 5 + ExifTool/t/PNG_7.out | 2 + ExifTool/t/PPM.t | 41 + ExifTool/t/PPM_2.out | 17 + ExifTool/t/PPM_3.out | 11 + ExifTool/t/PSP.t | 28 + ExifTool/t/PSP_2.out | 34 + ExifTool/t/Palm.t | 28 + ExifTool/t/Palm_2.out | 32 + ExifTool/t/Panasonic.t | 60 + ExifTool/t/Panasonic_2.out | 102 + ExifTool/t/Panasonic_3.out | 121 + ExifTool/t/Panasonic_4.out | 213 + ExifTool/t/Panasonic_5.out | 244 + ExifTool/t/Pentax.t | 50 + ExifTool/t/Pentax_2.out | 225 + ExifTool/t/Pentax_3.out | 266 + ExifTool/t/Pentax_4.out | 132 + ExifTool/t/PhaseOne.t | 38 + ExifTool/t/PhaseOne_2.out | 100 + ExifTool/t/PhaseOne_3.out | 171 + ExifTool/t/PhotoCD.t | 28 + ExifTool/t/PhotoCD_2.out | 37 + ExifTool/t/PhotoMechanic.t | 39 + ExifTool/t/PhotoMechanic_2.out | 83 + ExifTool/t/PhotoMechanic_3.out | 2 + ExifTool/t/Photoshop.t | 42 + ExifTool/t/Photoshop_2.out | 109 + ExifTool/t/Photoshop_3.out | 118 + ExifTool/t/PostScript.t | 49 + ExifTool/t/PostScript_2.out | 66 + ExifTool/t/PostScript_3.out | 135 + ExifTool/t/QuickTime.t | 240 + ExifTool/t/QuickTime_10.out | 23 + ExifTool/t/QuickTime_11.out | 46 + ExifTool/t/QuickTime_12.out | 49 + ExifTool/t/QuickTime_13.out | 55 + ExifTool/t/QuickTime_14.out | 22 + ExifTool/t/QuickTime_15.out | 20 + ExifTool/t/QuickTime_16.out | 2 + ExifTool/t/QuickTime_17.out | 2 + ExifTool/t/QuickTime_2.out | 133 + ExifTool/t/QuickTime_3.out | 73 + ExifTool/t/QuickTime_4.out | 16 + ExifTool/t/QuickTime_5.out | 12 + ExifTool/t/QuickTime_6.out | 4 + ExifTool/t/QuickTime_7.out | 27 + ExifTool/t/QuickTime_8.out | 96 + ExifTool/t/QuickTime_9.out | 54 + ExifTool/t/RIFF.t | 81 + ExifTool/t/RIFF_2.out | 25 + ExifTool/t/RIFF_3.out | 55 + ExifTool/t/RIFF_4.out | 32 + ExifTool/t/RIFF_5.out | 38 + ExifTool/t/RIFF_6.out | 24 + ExifTool/t/RIFF_7.out | 36 + ExifTool/t/RTF.t | 28 + ExifTool/t/RTF_2.out | 21 + ExifTool/t/Radiance.t | 28 + ExifTool/t/Radiance_2.out | 24 + ExifTool/t/Real.t | 46 + ExifTool/t/Real_2.out | 69 + ExifTool/t/Real_3.out | 19 + ExifTool/t/Real_4.out | 12 + ExifTool/t/Red.t | 28 + ExifTool/t/Red_2.out | 45 + ExifTool/t/Ricoh.t | 47 + ExifTool/t/Ricoh_2.out | 72 + ExifTool/t/Ricoh_3.out | 100 + ExifTool/t/Ricoh_4.out | 114 + ExifTool/t/Sanyo.t | 39 + ExifTool/t/Sanyo_2.out | 103 + ExifTool/t/Sanyo_3.out | 113 + ExifTool/t/Sigma.t | 60 + ExifTool/t/Sigma_2.out | 93 + ExifTool/t/Sigma_3.out | 96 + ExifTool/t/Sigma_4.out | 65 + ExifTool/t/Sigma_5.out | 194 + ExifTool/t/Sony.t | 63 + ExifTool/t/Sony_2.out | 86 + ExifTool/t/Sony_3.out | 87 + ExifTool/t/Sony_5.out | 34 + ExifTool/t/TestLib.pm | 469 + ExifTool/t/Text.t | 32 + ExifTool/t/Text_2.out | 8 + ExifTool/t/Text_3.out | 8 + ExifTool/t/Text_4.out | 9 + ExifTool/t/Text_5.out | 8 + ExifTool/t/Text_6.out | 7 + ExifTool/t/Text_7.out | 10 + ExifTool/t/Torrent.t | 28 + ExifTool/t/Torrent_2.out | 32 + ExifTool/t/Unknown.t | 36 + ExifTool/t/Unknown_2.out | 92 + ExifTool/t/Unknown_3.out | 92 + ExifTool/t/VCard.t | 31 + ExifTool/t/VCard_2.out | 54 + ExifTool/t/VCard_3.out | 88 + ExifTool/t/Vorbis.t | 28 + ExifTool/t/Vorbis_2.out | 29 + ExifTool/t/WPG.t | 28 + ExifTool/t/WPG_2.out | 15 + ExifTool/t/WTV.t | 28 + ExifTool/t/WTV_2.out | 80 + ExifTool/t/Writer.t | 1114 + ExifTool/t/Writer_10.out | 246 + ExifTool/t/Writer_11.out | 39 + ExifTool/t/Writer_13.out | 166 + ExifTool/t/Writer_14.out | 69 + ExifTool/t/Writer_15.out | 175 + ExifTool/t/Writer_16.out | 1 + ExifTool/t/Writer_17.out | 1 + ExifTool/t/Writer_18.out | 3 + ExifTool/t/Writer_19.out | 36 + ExifTool/t/Writer_2.out | 172 + ExifTool/t/Writer_22.out | 4 + ExifTool/t/Writer_24.out | 1 + ExifTool/t/Writer_25.out | 40 + ExifTool/t/Writer_26.out | 34 + ExifTool/t/Writer_27.out | 14 + ExifTool/t/Writer_28.out | 1 + ExifTool/t/Writer_29.out | 2 + ExifTool/t/Writer_30.out | 2 + ExifTool/t/Writer_31.out | 108 + ExifTool/t/Writer_32.out | 3 + ExifTool/t/Writer_33.out | 3 + ExifTool/t/Writer_34.out | 3 + ExifTool/t/Writer_35.out | 184 + ExifTool/t/Writer_36.out | 2 + ExifTool/t/Writer_37.out | 2 + ExifTool/t/Writer_38.out | 63 + ExifTool/t/Writer_39.out | 4 + ExifTool/t/Writer_4.out | 87 + ExifTool/t/Writer_40.out | 4 + ExifTool/t/Writer_41.out | 3 + ExifTool/t/Writer_42.out | 5 + ExifTool/t/Writer_43.out | 5 + ExifTool/t/Writer_44.out | 2 + ExifTool/t/Writer_45.out | 1 + ExifTool/t/Writer_46.out | 121 + ExifTool/t/Writer_47.out | 57 + ExifTool/t/Writer_48.out | 31 + ExifTool/t/Writer_50.out | 3 + ExifTool/t/Writer_51.out | 429 + ExifTool/t/Writer_52.out | 23 + ExifTool/t/Writer_53.out | 36 + ExifTool/t/Writer_54.out | 2 + ExifTool/t/Writer_55.out | 7 + ExifTool/t/Writer_56.out | 4 + ExifTool/t/Writer_58.out | 44 + ExifTool/t/Writer_59.out | 2 + ExifTool/t/Writer_6.out | 186 + ExifTool/t/Writer_60.out | 3 + ExifTool/t/Writer_7.out | 151 + ExifTool/t/Writer_9.out | 243 + ExifTool/t/XMP.t | 702 + ExifTool/t/XMP_10.out | 37 + ExifTool/t/XMP_11.out | 81 + ExifTool/t/XMP_12.out | 4 + ExifTool/t/XMP_13.out | 5 + ExifTool/t/XMP_14.out | 3 + ExifTool/t/XMP_15.out | 3 + ExifTool/t/XMP_16.out | 5 + ExifTool/t/XMP_17.out | 6 + ExifTool/t/XMP_18.out | 10 + ExifTool/t/XMP_19.out | 35 + ExifTool/t/XMP_2.out | 140 + ExifTool/t/XMP_20.out | 35 + ExifTool/t/XMP_21.out | 38 + ExifTool/t/XMP_22.out | 38 + ExifTool/t/XMP_23.out | 9 + ExifTool/t/XMP_24.out | 32 + ExifTool/t/XMP_25.out | 28 + ExifTool/t/XMP_26.out | 50 + ExifTool/t/XMP_27.out | 101 + ExifTool/t/XMP_28.out | 44 + ExifTool/t/XMP_29.out | 26 + ExifTool/t/XMP_3.out | 138 + ExifTool/t/XMP_30.out | 86 + ExifTool/t/XMP_31.out | 20 + ExifTool/t/XMP_32.out | 2 + ExifTool/t/XMP_34.out | 2 + ExifTool/t/XMP_36.out | 19 + ExifTool/t/XMP_37.out | 36 + ExifTool/t/XMP_39.out | 45 + ExifTool/t/XMP_40.out | 3 + ExifTool/t/XMP_41.out | 247 + ExifTool/t/XMP_42.out | 8 + ExifTool/t/XMP_43.out | 6 + ExifTool/t/XMP_44.out | 35 + ExifTool/t/XMP_45.out | 23 + ExifTool/t/XMP_46.out | 1 + ExifTool/t/XMP_47.out | 2 + ExifTool/t/XMP_48.out | 2 + ExifTool/t/XMP_49.out | 2 + ExifTool/t/XMP_5.out | 30 + ExifTool/t/XMP_50.out | 35 + ExifTool/t/XMP_52.out | 39 + ExifTool/t/XMP_53.out | 67 + ExifTool/t/XMP_54.out | 1 + ExifTool/t/XMP_6.out | 89 + ExifTool/t/XMP_7.out | 118 + ExifTool/t/XMP_8.out | 20 + ExifTool/t/XMP_9.out | 52 + ExifTool/t/ZIP.t | 79 + ExifTool/t/ZIP_2.out | 19 + ExifTool/t/ZIP_3.out | 18 + ExifTool/t/ZIP_4.out | 214 + ExifTool/t/ZIP_5.out | 136 + ExifTool/t/ZIP_6.out | 50 + ExifTool/t/ZIP_7.out | 29 + ExifTool/t/ZIP_8.out | 16 + ExifTool/t/ZISRAW.t | 28 + ExifTool/t/ZISRAW_2.out | 42 + ExifTool/t/images/AFCP.jpg | Bin 0 -> 1110 bytes ExifTool/t/images/AIFF.aif | Bin 0 -> 290 bytes ExifTool/t/images/APE.ape | Bin 0 -> 2166 bytes ExifTool/t/images/APE.mpc | Bin 0 -> 2513 bytes ExifTool/t/images/ASF.wmv | Bin 0 -> 12379 bytes ExifTool/t/images/Apple.jpg | Bin 0 -> 2355 bytes ExifTool/t/images/Audible.aa | Bin 0 -> 1322 bytes ExifTool/t/images/BMP.bmp | Bin 0 -> 1142 bytes ExifTool/t/images/BPG.bpg | Bin 0 -> 1863 bytes ExifTool/t/images/BigTIFF.btf | Bin 0 -> 384 bytes ExifTool/t/images/Canon.jpg | Bin 0 -> 2697 bytes ExifTool/t/images/Canon1DmkIII.jpg | Bin 0 -> 8337 bytes ExifTool/t/images/CanonRaw.cr2 | Bin 0 -> 8724 bytes ExifTool/t/images/CanonRaw.cr3 | Bin 0 -> 52502 bytes ExifTool/t/images/CanonRaw.crw | Bin 0 -> 6816 bytes ExifTool/t/images/CanonVRD.dr4 | Bin 0 -> 5472 bytes ExifTool/t/images/CanonVRD.vrd | Bin 0 -> 1768 bytes ExifTool/t/images/CaptureOne.eip | Bin 0 -> 5448 bytes ExifTool/t/images/Casio.jpg | Bin 0 -> 1203 bytes ExifTool/t/images/Casio2.jpg | Bin 0 -> 1699 bytes ExifTool/t/images/CasioQVCI.jpg | Bin 0 -> 407 bytes ExifTool/t/images/DICOM.dcm | Bin 0 -> 1860 bytes ExifTool/t/images/DNG.dng | Bin 0 -> 13662 bytes ExifTool/t/images/DPX.dpx | Bin 0 -> 2080 bytes ExifTool/t/images/DV.dv | Bin 0 -> 4400 bytes ExifTool/t/images/DjVu.djvu | Bin 0 -> 930 bytes ExifTool/t/images/EXE.a | Bin 0 -> 784 bytes ExifTool/t/images/EXE.dylib | Bin 0 -> 4144 bytes ExifTool/t/images/EXE.elf | Bin 0 -> 2860 bytes ExifTool/t/images/EXE.exe | Bin 0 -> 5120 bytes ExifTool/t/images/EXE.macho | Bin 0 -> 9484 bytes ExifTool/t/images/EXE.so | Bin 0 -> 5608 bytes ExifTool/t/images/ExifTool.jpg | Bin 0 -> 25766 bytes ExifTool/t/images/ExifTool.jps | Bin 0 -> 283 bytes ExifTool/t/images/ExifTool.tif | Bin 0 -> 4864 bytes ExifTool/t/images/ExtendedXMP.jpg | Bin 0 -> 1380 bytes ExifTool/t/images/FITS.fits | 1 + ExifTool/t/images/FLAC.flac | Bin 0 -> 282 bytes ExifTool/t/images/FLAC.ogg | Bin 0 -> 151 bytes ExifTool/t/images/FLIF.flif | Bin 0 -> 674 bytes ExifTool/t/images/FLIR.fpf | Bin 0 -> 914 bytes ExifTool/t/images/FLIR.jpg | Bin 0 -> 7338 bytes ExifTool/t/images/Flash.flv | Bin 0 -> 1358 bytes ExifTool/t/images/Flash.swf | Bin 0 -> 384 bytes ExifTool/t/images/FlashPix.ppt | Bin 0 -> 9728 bytes ExifTool/t/images/Font.afm | 28 + ExifTool/t/images/Font.dfont | Bin 0 -> 1744 bytes ExifTool/t/images/Font.pfa | 38 + ExifTool/t/images/Font.pfb | Bin 0 -> 1423 bytes ExifTool/t/images/Font.pfm | Bin 0 -> 240 bytes ExifTool/t/images/Font.ttf | Bin 0 -> 1252 bytes ExifTool/t/images/FotoStation.jpg | Bin 0 -> 4320 bytes ExifTool/t/images/FujiFilm.jpg | Bin 0 -> 1373 bytes ExifTool/t/images/FujiFilm.raf | Bin 0 -> 38452 bytes ExifTool/t/images/GE.jpg | Bin 0 -> 1467 bytes ExifTool/t/images/GIF.gif | Bin 0 -> 2321 bytes ExifTool/t/images/GIMP.xcf | Bin 0 -> 2405 bytes ExifTool/t/images/GPS.jpg | Bin 0 -> 2133 bytes ExifTool/t/images/GeoTiff.tif | Bin 0 -> 2660 bytes ExifTool/t/images/Geotag.gpx | 23 + ExifTool/t/images/Geotag.igc | 24 + ExifTool/t/images/Geotag.kml | 55 + ExifTool/t/images/Geotag.log | 13 + ExifTool/t/images/Geotag.xml | 23 + ExifTool/t/images/Geotag2.log | 7 + ExifTool/t/images/Geotag3.log | 7 + .../Geotag_DJI_2020-12-02_[07-50-31].csv | 94 + ExifTool/t/images/GoPro.jpg | Bin 0 -> 4601 bytes ExifTool/t/images/HTML.html | 77 + ExifTool/t/images/ICC_Profile.icc | Bin 0 -> 492 bytes ExifTool/t/images/ICO.ico | Bin 0 -> 78 bytes ExifTool/t/images/IPTC.jpg | Bin 0 -> 9851 bytes ExifTool/t/images/ISO.iso | Bin 0 -> 40960 bytes ExifTool/t/images/ITC.itc | Bin 0 -> 672 bytes ExifTool/t/images/InDesign.indd | Bin 0 -> 12288 bytes ExifTool/t/images/InfiRay.jpg | Bin 0 -> 2207 bytes ExifTool/t/images/JSON.json | 19 + ExifTool/t/images/JVC.jpg | Bin 0 -> 1253 bytes ExifTool/t/images/JVC2.jpg | Bin 0 -> 947 bytes ExifTool/t/images/JXL.jxl | 2 + ExifTool/t/images/JXL2.jxl | Bin 0 -> 395 bytes ExifTool/t/images/Jpeg2000.j2c | Bin 0 -> 618 bytes ExifTool/t/images/Jpeg2000.jp2 | Bin 0 -> 2028 bytes ExifTool/t/images/Kodak.jpg | Bin 0 -> 3375 bytes ExifTool/t/images/KyoceraRaw.raw | Bin 0 -> 166 bytes ExifTool/t/images/LNK.lnk | Bin 0 -> 876 bytes ExifTool/t/images/Lytro.lfp | Bin 0 -> 4400 bytes ExifTool/t/images/M2TS.mts | Bin 0 -> 1344 bytes ExifTool/t/images/MIE.mie | Bin 0 -> 2055 bytes ExifTool/t/images/MIFF.miff | Bin 0 -> 3750 bytes ExifTool/t/images/MOI.moi | Bin 0 -> 320 bytes ExifTool/t/images/MP3.mp3 | Bin 0 -> 395 bytes ExifTool/t/images/MRC.mrc | Bin 0 -> 2560 bytes ExifTool/t/images/MWG.jpg | Bin 0 -> 2255 bytes ExifTool/t/images/MXF.mxf | Bin 0 -> 7510 bytes ExifTool/t/images/MacOS.macos | Bin 0 -> 4096 bytes ExifTool/t/images/Matroska.mkv | Bin 0 -> 507 bytes ExifTool/t/images/Minolta.jpg | Bin 0 -> 13501 bytes ExifTool/t/images/Minolta.mrw | Bin 0 -> 2596 bytes ExifTool/t/images/Motorola.jpg | Bin 0 -> 2489 bytes ExifTool/t/images/Nikon.jpg | Bin 0 -> 1703 bytes ExifTool/t/images/Nikon.nef | Bin 0 -> 6188 bytes ExifTool/t/images/NikonD2Hs.jpg | Bin 0 -> 3505 bytes ExifTool/t/images/NikonD70.jpg | Bin 0 -> 3661 bytes ExifTool/t/images/OOXML.docx | Bin 0 -> 7521 bytes ExifTool/t/images/Olympus.dss | Bin 0 -> 80 bytes ExifTool/t/images/Olympus.jpg | Bin 0 -> 1573 bytes ExifTool/t/images/Olympus2.jpg | Bin 0 -> 7701 bytes ExifTool/t/images/OlympusE1.jpg | Bin 0 -> 7365 bytes ExifTool/t/images/OpenDoc.ods | Bin 0 -> 7037 bytes ExifTool/t/images/OpenEXR.exr | Bin 0 -> 395 bytes ExifTool/t/images/Opus.opus | Bin 0 -> 1128 bytes ExifTool/t/images/PCX.pcx | Bin 0 -> 160 bytes ExifTool/t/images/PDF.pdf | Bin 0 -> 8907 bytes ExifTool/t/images/PDF2.pdf | Bin 0 -> 2213 bytes ExifTool/t/images/PFM.pfm | 4 + ExifTool/t/images/PGF.pgf | Bin 0 -> 286 bytes ExifTool/t/images/PICT.pict | Bin 0 -> 150 bytes ExifTool/t/images/PLIST-bin.plist | Bin 0 -> 351 bytes ExifTool/t/images/PLIST-xml.plist | 35 + ExifTool/t/images/PLIST.aae | 24 + ExifTool/t/images/PLUS.xmp | 127 + ExifTool/t/images/PNG.png | Bin 0 -> 572 bytes ExifTool/t/images/PPM.ppm | 5 + ExifTool/t/images/PSP.psp | Bin 0 -> 1703 bytes ExifTool/t/images/Palm.mobi | Bin 0 -> 1382 bytes ExifTool/t/images/Panasonic.jpg | Bin 0 -> 7099 bytes ExifTool/t/images/Panasonic.rw2 | Bin 0 -> 12344 bytes ExifTool/t/images/Pentax.avi | Bin 0 -> 1672 bytes ExifTool/t/images/Pentax.jpg | Bin 0 -> 2721 bytes ExifTool/t/images/PhaseOne.iiq | Bin 0 -> 3364 bytes ExifTool/t/images/PhotoCD.pcd | Bin 0 -> 4116 bytes ExifTool/t/images/PhotoMechanic.jpg | Bin 0 -> 3417 bytes ExifTool/t/images/Photoshop.psd | Bin 0 -> 17369 bytes ExifTool/t/images/PostScript.eps | 108 + ExifTool/t/images/QuickTime.heic | Bin 0 -> 623 bytes ExifTool/t/images/QuickTime.m4a | Bin 0 -> 5237 bytes ExifTool/t/images/QuickTime.mov | Bin 0 -> 3871 bytes ExifTool/t/images/RIFF.avi | Bin 0 -> 1262 bytes ExifTool/t/images/RIFF.wav | Bin 0 -> 224 bytes ExifTool/t/images/RIFF.webp | Bin 0 -> 586 bytes ExifTool/t/images/RTF.rtf | 25 + ExifTool/t/images/Radiance.hdr | Bin 0 -> 587 bytes ExifTool/t/images/Real.ra | Bin 0 -> 130 bytes ExifTool/t/images/Real.ram | 1 + ExifTool/t/images/Real.rm | Bin 0 -> 1915 bytes ExifTool/t/images/Red.r3d | Bin 0 -> 1160 bytes ExifTool/t/images/Ricoh.jpg | Bin 0 -> 1903 bytes ExifTool/t/images/Ricoh2.jpg | Bin 0 -> 3187 bytes ExifTool/t/images/Sanyo.jpg | Bin 0 -> 2165 bytes ExifTool/t/images/Sigma.jpg | Bin 0 -> 1531 bytes ExifTool/t/images/Sigma.x3f | Bin 0 -> 1464 bytes ExifTool/t/images/SigmaDP2.x3f | Bin 0 -> 4364 bytes ExifTool/t/images/Sony.jpg | Bin 0 -> 2779 bytes ExifTool/t/images/Sony.pmp | Bin 0 -> 375 bytes ExifTool/t/images/Text.csv | 3 + ExifTool/t/images/Text1.txt | 1 + ExifTool/t/images/Text2.txt | 1 + ExifTool/t/images/Text3.txt | 1 + ExifTool/t/images/Text4.txt | 1 + ExifTool/t/images/Text5.txt | Bin 0 -> 36 bytes ExifTool/t/images/Torrent.torrent | 1 + ExifTool/t/images/Unknown.jpg | Bin 0 -> 7199 bytes ExifTool/t/images/VCard.ics | 94 + ExifTool/t/images/VCard.vcf | 45 + ExifTool/t/images/Vorbis.ogg | Bin 0 -> 5371 bytes ExifTool/t/images/WPG.wpg | Bin 0 -> 159 bytes ExifTool/t/images/WTV.wtv | Bin 0 -> 7680 bytes ExifTool/t/images/Writer.jpg | Bin 0 -> 251 bytes ExifTool/t/images/XMP.inx | 108 + ExifTool/t/images/XMP.jpg | Bin 0 -> 10314 bytes ExifTool/t/images/XMP.svg | 33 + ExifTool/t/images/XMP.xml | 101 + ExifTool/t/images/XMP.xmp | 99 + ExifTool/t/images/XMP2.xmp | 25 + ExifTool/t/images/XMP3.xmp | 69 + ExifTool/t/images/XMP4.xmp | 93 + ExifTool/t/images/XMP5.xmp | 157 + ExifTool/t/images/XMP6.xmp | 48 + ExifTool/t/images/XMP7.xmp | 26 + ExifTool/t/images/XMP8.xmp | 38 + ExifTool/t/images/XMP9.xmp | 34 + ExifTool/t/images/ZIP.gz | Bin 0 -> 71 bytes ExifTool/t/images/ZIP.rar | Bin 0 -> 74 bytes ExifTool/t/images/ZIP.zip | Bin 0 -> 167 bytes ExifTool/t/images/ZISRAW.czi | Bin 0 -> 1856 bytes ExifTool/t/images/iWork.numbers | Bin 0 -> 1359 bytes SQLITE/INSTALL | 370 + SQLITE/Makefile.am | 20 + SQLITE/Makefile.fallback | 19 + SQLITE/Makefile.in | 1028 + SQLITE/Makefile.msc | 1064 + SQLITE/README.txt | 113 + SQLITE/Replace.cs | 223 + SQLITE/aclocal.m4 | 10200 + SQLITE/compile | 347 + SQLITE/config.guess | 1441 + SQLITE/config.sub | 1813 + SQLITE/configure | 16112 + SQLITE/configure.ac | 270 + SQLITE/depcomp | 791 + SQLITE/install-sh | 508 + SQLITE/ltmain.sh | 11156 + SQLITE/missing | 215 + SQLITE/osint.sqlite | Bin 0 -> 20480 bytes SQLITE/shell.c | 28615 ++ SQLITE/sqlite3.1 | 161 + SQLITE/sqlite3.c | 251262 +++++++++++++++ SQLITE/sqlite3.h | 13181 + SQLITE/sqlite3.pc.in | 13 + SQLITE/sqlite3.rc | 83 + SQLITE/sqlite3ext.h | 713 + SQLITE/sqlite3rc.h | 3 + SQLITE/tea/Makefile.in | 475 + SQLITE/tea/README | 36 + SQLITE/tea/aclocal.m4 | 9 + SQLITE/tea/configure | 9447 + SQLITE/tea/configure.ac | 227 + SQLITE/tea/doc/sqlite3.n | 15 + SQLITE/tea/generic/tclsqlite3.c | 4080 + SQLITE/tea/license.terms | 6 + SQLITE/tea/pkgIndex.tcl.in | 10 + SQLITE/tea/tclconfig/install-sh | 528 + SQLITE/tea/tclconfig/tcl.m4 | 4067 + SQLITE/tea/win/makefile.vc | 430 + SQLITE/tea/win/nmakehlp.c | 815 + SQLITE/tea/win/rules.vc | 711 + photo_edf1.JPG | Bin 0 -> 177811 bytes photo_edf2.JPG | Bin 0 -> 1957142 bytes photo_edf3.JPG | Bin 0 -> 38339 bytes 1209 files changed, 1005582 insertions(+) create mode 100644 ExifTool/0027ea9ab3e2d95a68f3e5e738dbd60b3bf6dfaa4806e80b7536a56b0681df37.png create mode 100644 ExifTool/0027ea9ab3e2d95a68f3e5e738dbd60b3bf6dfaa4806e80b7536a56b0681df37.png_original create mode 100644 ExifTool/Changes create mode 100644 ExifTool/MANIFEST create mode 100644 ExifTool/META.json create mode 100644 ExifTool/META.yml create mode 100644 ExifTool/Makefile.PL create mode 100644 ExifTool/README create mode 100644 ExifTool/arg_files/exif2iptc.args create mode 100644 ExifTool/arg_files/exif2xmp.args create mode 100644 ExifTool/arg_files/gps2xmp.args create mode 100644 ExifTool/arg_files/iptc2exif.args create mode 100644 ExifTool/arg_files/iptc2xmp.args create mode 100644 ExifTool/arg_files/iptcCore.args create mode 100644 ExifTool/arg_files/pdf2xmp.args create mode 100644 ExifTool/arg_files/xmp2exif.args create mode 100644 ExifTool/arg_files/xmp2gps.args create mode 100644 ExifTool/arg_files/xmp2iptc.args create mode 100644 ExifTool/arg_files/xmp2pdf.args create mode 100644 ExifTool/config_files/acdsee.config create mode 100644 ExifTool/config_files/age.config create mode 100644 ExifTool/config_files/bibble.config create mode 100644 ExifTool/config_files/convert_regions.config create mode 100644 ExifTool/config_files/cuepointlist.config create mode 100644 ExifTool/config_files/depthmap.config create mode 100644 ExifTool/config_files/example.config create mode 100644 ExifTool/config_files/fotoware.config create mode 100755 ExifTool/config_files/frameCount.config create mode 100644 ExifTool/config_files/gps2utm.config create mode 100644 ExifTool/config_files/guano.config create mode 100644 ExifTool/config_files/nksc.config create mode 100644 ExifTool/config_files/photoshop_paths.config create mode 100644 ExifTool/config_files/picasa_faces.config create mode 100644 ExifTool/config_files/pix4d.config create mode 100644 ExifTool/config_files/rotate_regions.config create mode 100644 ExifTool/config_files/tiff_version.config create mode 100644 ExifTool/config_files/time_zone.config create mode 100755 ExifTool/exiftool create mode 100644 ExifTool/fmt_files/gpx.fmt create mode 100644 ExifTool/fmt_files/gpx_wpt.fmt create mode 100644 ExifTool/fmt_files/kml.fmt create mode 100644 ExifTool/fmt_files/kml_track.fmt create mode 100644 ExifTool/html/ExifTool.html create mode 100644 ExifTool/html/MIE1.1-20070121.pdf create mode 100644 ExifTool/html/Shift.html create mode 100644 ExifTool/html/TagNames/AFCP.html create mode 100644 ExifTool/html/TagNames/AIFF.html create mode 100644 ExifTool/html/TagNames/APE.html create mode 100644 ExifTool/html/TagNames/APP12.html create mode 100644 ExifTool/html/TagNames/ASF.html create mode 100644 ExifTool/html/TagNames/Apple.html create mode 100644 ExifTool/html/TagNames/Audible.html create mode 100644 ExifTool/html/TagNames/BMP.html create mode 100644 ExifTool/html/TagNames/BPG.html create mode 100644 ExifTool/html/TagNames/CBOR.html create mode 100644 ExifTool/html/TagNames/Canon.html create mode 100644 ExifTool/html/TagNames/CanonCustom.html create mode 100644 ExifTool/html/TagNames/CanonRaw.html create mode 100644 ExifTool/html/TagNames/CanonVRD.html create mode 100644 ExifTool/html/TagNames/Casio.html create mode 100644 ExifTool/html/TagNames/Composite.html create mode 100644 ExifTool/html/TagNames/DICOM.html create mode 100644 ExifTool/html/TagNames/DJI.html create mode 100644 ExifTool/html/TagNames/DNG.html create mode 100644 ExifTool/html/TagNames/DPX.html create mode 100644 ExifTool/html/TagNames/DV.html create mode 100644 ExifTool/html/TagNames/DarwinCore.html create mode 100644 ExifTool/html/TagNames/DjVu.html create mode 100644 ExifTool/html/TagNames/EXE.html create mode 100644 ExifTool/html/TagNames/EXIF.html create mode 100644 ExifTool/html/TagNames/Extra.html create mode 100644 ExifTool/html/TagNames/FITS.html create mode 100644 ExifTool/html/TagNames/FLAC.html create mode 100644 ExifTool/html/TagNames/FLIF.html create mode 100644 ExifTool/html/TagNames/FLIR.html create mode 100644 ExifTool/html/TagNames/Flash.html create mode 100644 ExifTool/html/TagNames/FlashPix.html create mode 100644 ExifTool/html/TagNames/Font.html create mode 100644 ExifTool/html/TagNames/FotoStation.html create mode 100644 ExifTool/html/TagNames/FujiFilm.html create mode 100644 ExifTool/html/TagNames/GE.html create mode 100644 ExifTool/html/TagNames/GIF.html create mode 100644 ExifTool/html/TagNames/GIMP.html create mode 100644 ExifTool/html/TagNames/GPS.html create mode 100644 ExifTool/html/TagNames/GeoTiff.html create mode 100644 ExifTool/html/TagNames/GoPro.html create mode 100644 ExifTool/html/TagNames/H264.html create mode 100644 ExifTool/html/TagNames/HP.html create mode 100644 ExifTool/html/TagNames/HTML.html create mode 100644 ExifTool/html/TagNames/ICC_Profile.html create mode 100644 ExifTool/html/TagNames/ICO.html create mode 100644 ExifTool/html/TagNames/ID3.html create mode 100644 ExifTool/html/TagNames/IPTC.html create mode 100644 ExifTool/html/TagNames/ISO.html create mode 100644 ExifTool/html/TagNames/ITC.html create mode 100644 ExifTool/html/TagNames/InfiRay.html create mode 100644 ExifTool/html/TagNames/JFIF.html create mode 100644 ExifTool/html/TagNames/JPEG.html create mode 100644 ExifTool/html/TagNames/JSON.html create mode 100644 ExifTool/html/TagNames/JVC.html create mode 100644 ExifTool/html/TagNames/Jpeg2000.html create mode 100644 ExifTool/html/TagNames/Kodak.html create mode 100644 ExifTool/html/TagNames/KyoceraRaw.html create mode 100644 ExifTool/html/TagNames/LIF.html create mode 100644 ExifTool/html/TagNames/LNK.html create mode 100644 ExifTool/html/TagNames/Leaf.html create mode 100644 ExifTool/html/TagNames/Lytro.html create mode 100644 ExifTool/html/TagNames/M2TS.html create mode 100644 ExifTool/html/TagNames/MIE.html create mode 100644 ExifTool/html/TagNames/MIFF.html create mode 100644 ExifTool/html/TagNames/MISB.html create mode 100644 ExifTool/html/TagNames/MNG.html create mode 100644 ExifTool/html/TagNames/MOI.html create mode 100644 ExifTool/html/TagNames/MPC.html create mode 100644 ExifTool/html/TagNames/MPEG.html create mode 100644 ExifTool/html/TagNames/MPF.html create mode 100644 ExifTool/html/TagNames/MRC.html create mode 100644 ExifTool/html/TagNames/MWG.html create mode 100644 ExifTool/html/TagNames/MXF.html create mode 100644 ExifTool/html/TagNames/MacOS.html create mode 100644 ExifTool/html/TagNames/Matroska.html create mode 100644 ExifTool/html/TagNames/Microsoft.html create mode 100644 ExifTool/html/TagNames/Minolta.html create mode 100644 ExifTool/html/TagNames/MinoltaRaw.html create mode 100644 ExifTool/html/TagNames/Motorola.html create mode 100644 ExifTool/html/TagNames/Nikon.html create mode 100644 ExifTool/html/TagNames/NikonCapture.html create mode 100644 ExifTool/html/TagNames/NikonCustom.html create mode 100644 ExifTool/html/TagNames/NikonSettings.html create mode 100644 ExifTool/html/TagNames/Nintendo.html create mode 100644 ExifTool/html/TagNames/OOXML.html create mode 100644 ExifTool/html/TagNames/Ogg.html create mode 100644 ExifTool/html/TagNames/Olympus.html create mode 100644 ExifTool/html/TagNames/OpenEXR.html create mode 100644 ExifTool/html/TagNames/Opus.html create mode 100644 ExifTool/html/TagNames/Other.html create mode 100644 ExifTool/html/TagNames/PCX.html create mode 100644 ExifTool/html/TagNames/PDF.html create mode 100644 ExifTool/html/TagNames/PGF.html create mode 100644 ExifTool/html/TagNames/PICT.html create mode 100644 ExifTool/html/TagNames/PLIST.html create mode 100644 ExifTool/html/TagNames/PLUS.html create mode 100644 ExifTool/html/TagNames/PNG.html create mode 100644 ExifTool/html/TagNames/PSP.html create mode 100644 ExifTool/html/TagNames/Palm.html create mode 100644 ExifTool/html/TagNames/Panasonic.html create mode 100644 ExifTool/html/TagNames/PanasonicRaw.html create mode 100644 ExifTool/html/TagNames/Parrot.html create mode 100644 ExifTool/html/TagNames/Pentax.html create mode 100644 ExifTool/html/TagNames/PhaseOne.html create mode 100644 ExifTool/html/TagNames/PhotoCD.html create mode 100644 ExifTool/html/TagNames/PhotoMechanic.html create mode 100644 ExifTool/html/TagNames/Photoshop.html create mode 100644 ExifTool/html/TagNames/PostScript.html create mode 100644 ExifTool/html/TagNames/PrintIM.html create mode 100644 ExifTool/html/TagNames/Qualcomm.html create mode 100644 ExifTool/html/TagNames/QuickTime.html create mode 100644 ExifTool/html/TagNames/RIFF.html create mode 100644 ExifTool/html/TagNames/RSRC.html create mode 100644 ExifTool/html/TagNames/RTF.html create mode 100644 ExifTool/html/TagNames/Radiance.html create mode 100644 ExifTool/html/TagNames/Rawzor.html create mode 100644 ExifTool/html/TagNames/Real.html create mode 100644 ExifTool/html/TagNames/Reconyx.html create mode 100644 ExifTool/html/TagNames/Red.html create mode 100644 ExifTool/html/TagNames/Ricoh.html create mode 100644 ExifTool/html/TagNames/Samsung.html create mode 100644 ExifTool/html/TagNames/Sanyo.html create mode 100644 ExifTool/html/TagNames/Scalado.html create mode 100644 ExifTool/html/TagNames/Shortcuts.html create mode 100644 ExifTool/html/TagNames/Sigma.html create mode 100644 ExifTool/html/TagNames/SigmaRaw.html create mode 100644 ExifTool/html/TagNames/Sony.html create mode 100644 ExifTool/html/TagNames/SonyIDC.html create mode 100644 ExifTool/html/TagNames/Stim.html create mode 100644 ExifTool/html/TagNames/Text.html create mode 100644 ExifTool/html/TagNames/Theora.html create mode 100644 ExifTool/html/TagNames/Torrent.html create mode 100644 ExifTool/html/TagNames/Unknown.html create mode 100644 ExifTool/html/TagNames/VCard.html create mode 100644 ExifTool/html/TagNames/Vorbis.html create mode 100644 ExifTool/html/TagNames/WPG.html create mode 100644 ExifTool/html/TagNames/WTV.html create mode 100644 ExifTool/html/TagNames/XMP.html create mode 100644 ExifTool/html/TagNames/ZIP.html create mode 100644 ExifTool/html/TagNames/ZISRAW.html create mode 100644 ExifTool/html/TagNames/iWork.html create mode 100644 ExifTool/html/TagNames/index.html create mode 100644 ExifTool/html/TagNames/style.css create mode 100644 ExifTool/html/ancient_history.html create mode 100644 ExifTool/html/canon_raw.html create mode 100644 ExifTool/html/commentary.html create mode 100644 ExifTool/html/config.html create mode 100644 ExifTool/html/data_members.html create mode 100644 ExifTool/html/exiftool_pod.html create mode 100644 ExifTool/html/faq.html create mode 100644 ExifTool/html/filename.html create mode 100644 ExifTool/html/geotag.html create mode 100644 ExifTool/html/history.html create mode 100644 ExifTool/html/htmldump.html create mode 100644 ExifTool/html/idiosyncracies.html create mode 100644 ExifTool/html/index.html create mode 100644 ExifTool/html/install.html create mode 100644 ExifTool/html/metafiles.html create mode 100644 ExifTool/html/mistakes.html create mode 100644 ExifTool/html/overview.png create mode 100644 ExifTool/html/standards.html create mode 100644 ExifTool/html/struct.html create mode 100644 ExifTool/html/style.css create mode 100644 ExifTool/html/under.html create mode 100644 ExifTool/html/verbose.html create mode 100644 ExifTool/html/writing.html create mode 100644 ExifTool/lib/File/RandomAccess.pm create mode 100644 ExifTool/lib/File/RandomAccess.pod create mode 100644 ExifTool/lib/Image/ExifTool.pm create mode 100644 ExifTool/lib/Image/ExifTool.pod create mode 100644 ExifTool/lib/Image/ExifTool/7Z.pm create mode 100644 ExifTool/lib/Image/ExifTool/AES.pm create mode 100644 ExifTool/lib/Image/ExifTool/AFCP.pm create mode 100644 ExifTool/lib/Image/ExifTool/AIFF.pm create mode 100644 ExifTool/lib/Image/ExifTool/APE.pm create mode 100644 ExifTool/lib/Image/ExifTool/APP12.pm create mode 100644 ExifTool/lib/Image/ExifTool/ASF.pm create mode 100644 ExifTool/lib/Image/ExifTool/Apple.pm create mode 100644 ExifTool/lib/Image/ExifTool/Audible.pm create mode 100644 ExifTool/lib/Image/ExifTool/BMP.pm create mode 100644 ExifTool/lib/Image/ExifTool/BPG.pm create mode 100644 ExifTool/lib/Image/ExifTool/BZZ.pm create mode 100644 ExifTool/lib/Image/ExifTool/BigTIFF.pm create mode 100644 ExifTool/lib/Image/ExifTool/BuildTagLookup.pm create mode 100644 ExifTool/lib/Image/ExifTool/CBOR.pm create mode 100644 ExifTool/lib/Image/ExifTool/Canon.pm create mode 100644 ExifTool/lib/Image/ExifTool/CanonCustom.pm create mode 100644 ExifTool/lib/Image/ExifTool/CanonRaw.pm create mode 100644 ExifTool/lib/Image/ExifTool/CanonVRD.pm create mode 100644 ExifTool/lib/Image/ExifTool/CaptureOne.pm create mode 100644 ExifTool/lib/Image/ExifTool/Casio.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Arabic.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Baltic.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Cyrillic.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/DOSCyrillic.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/DOSLatin1.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/DOSLatinUS.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Greek.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Hebrew.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/JIS.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Latin.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Latin2.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacArabic.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacChineseCN.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacChineseTW.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacCroatian.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacCyrillic.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacGreek.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacHebrew.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacIceland.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacJapanese.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacKorean.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacLatin2.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacRSymbol.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacRoman.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacRomanian.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacThai.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/MacTurkish.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/PDFDoc.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/ShiftJIS.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Symbol.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Thai.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Turkish.pm create mode 100644 ExifTool/lib/Image/ExifTool/Charset/Vietnam.pm create mode 100644 ExifTool/lib/Image/ExifTool/DICOM.pm create mode 100644 ExifTool/lib/Image/ExifTool/DJI.pm create mode 100644 ExifTool/lib/Image/ExifTool/DNG.pm create mode 100644 ExifTool/lib/Image/ExifTool/DPX.pm create mode 100644 ExifTool/lib/Image/ExifTool/DV.pm create mode 100644 ExifTool/lib/Image/ExifTool/DarwinCore.pm create mode 100644 ExifTool/lib/Image/ExifTool/DjVu.pm create mode 100644 ExifTool/lib/Image/ExifTool/EXE.pm create mode 100644 ExifTool/lib/Image/ExifTool/Exif.pm create mode 100644 ExifTool/lib/Image/ExifTool/FITS.pm create mode 100644 ExifTool/lib/Image/ExifTool/FLAC.pm create mode 100644 ExifTool/lib/Image/ExifTool/FLIF.pm create mode 100644 ExifTool/lib/Image/ExifTool/FLIR.pm create mode 100644 ExifTool/lib/Image/ExifTool/Fixup.pm create mode 100644 ExifTool/lib/Image/ExifTool/Flash.pm create mode 100644 ExifTool/lib/Image/ExifTool/FlashPix.pm create mode 100644 ExifTool/lib/Image/ExifTool/Font.pm create mode 100644 ExifTool/lib/Image/ExifTool/FotoStation.pm create mode 100644 ExifTool/lib/Image/ExifTool/FujiFilm.pm create mode 100644 ExifTool/lib/Image/ExifTool/GE.pm create mode 100644 ExifTool/lib/Image/ExifTool/GIF.pm create mode 100644 ExifTool/lib/Image/ExifTool/GIMP.pm create mode 100644 ExifTool/lib/Image/ExifTool/GPS.pm create mode 100644 ExifTool/lib/Image/ExifTool/GeoTiff.pm create mode 100644 ExifTool/lib/Image/ExifTool/Geotag.pm create mode 100644 ExifTool/lib/Image/ExifTool/GoPro.pm create mode 100644 ExifTool/lib/Image/ExifTool/H264.pm create mode 100644 ExifTool/lib/Image/ExifTool/HP.pm create mode 100644 ExifTool/lib/Image/ExifTool/HTML.pm create mode 100644 ExifTool/lib/Image/ExifTool/HtmlDump.pm create mode 100644 ExifTool/lib/Image/ExifTool/ICC_Profile.pm create mode 100644 ExifTool/lib/Image/ExifTool/ICO.pm create mode 100644 ExifTool/lib/Image/ExifTool/ID3.pm create mode 100644 ExifTool/lib/Image/ExifTool/IPTC.pm create mode 100644 ExifTool/lib/Image/ExifTool/ISO.pm create mode 100644 ExifTool/lib/Image/ExifTool/ITC.pm create mode 100644 ExifTool/lib/Image/ExifTool/Import.pm create mode 100644 ExifTool/lib/Image/ExifTool/InDesign.pm create mode 100644 ExifTool/lib/Image/ExifTool/InfiRay.pm create mode 100644 ExifTool/lib/Image/ExifTool/JPEG.pm create mode 100644 ExifTool/lib/Image/ExifTool/JPEGDigest.pm create mode 100644 ExifTool/lib/Image/ExifTool/JSON.pm create mode 100644 ExifTool/lib/Image/ExifTool/JVC.pm create mode 100644 ExifTool/lib/Image/ExifTool/Jpeg2000.pm create mode 100644 ExifTool/lib/Image/ExifTool/Kodak.pm create mode 100644 ExifTool/lib/Image/ExifTool/KyoceraRaw.pm create mode 100644 ExifTool/lib/Image/ExifTool/LIF.pm create mode 100644 ExifTool/lib/Image/ExifTool/LNK.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/cs.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/de.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/en_ca.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/en_gb.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/es.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/fi.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/fr.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/it.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/ja.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/ko.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/nl.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/pl.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/ru.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/sk.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/sv.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/tr.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/zh_cn.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lang/zh_tw.pm create mode 100644 ExifTool/lib/Image/ExifTool/Leaf.pm create mode 100644 ExifTool/lib/Image/ExifTool/Lytro.pm create mode 100644 ExifTool/lib/Image/ExifTool/M2TS.pm create mode 100644 ExifTool/lib/Image/ExifTool/MIE.pm create mode 100644 ExifTool/lib/Image/ExifTool/MIEUnits.pod create mode 100644 ExifTool/lib/Image/ExifTool/MIFF.pm create mode 100644 ExifTool/lib/Image/ExifTool/MISB.pm create mode 100644 ExifTool/lib/Image/ExifTool/MNG.pm create mode 100644 ExifTool/lib/Image/ExifTool/MOI.pm create mode 100644 ExifTool/lib/Image/ExifTool/MPC.pm create mode 100644 ExifTool/lib/Image/ExifTool/MPEG.pm create mode 100644 ExifTool/lib/Image/ExifTool/MPF.pm create mode 100644 ExifTool/lib/Image/ExifTool/MRC.pm create mode 100644 ExifTool/lib/Image/ExifTool/MWG.pm create mode 100644 ExifTool/lib/Image/ExifTool/MXF.pm create mode 100644 ExifTool/lib/Image/ExifTool/MacOS.pm create mode 100644 ExifTool/lib/Image/ExifTool/MakerNotes.pm create mode 100644 ExifTool/lib/Image/ExifTool/Matroska.pm create mode 100644 ExifTool/lib/Image/ExifTool/Microsoft.pm create mode 100644 ExifTool/lib/Image/ExifTool/Minolta.pm create mode 100644 ExifTool/lib/Image/ExifTool/MinoltaRaw.pm create mode 100644 ExifTool/lib/Image/ExifTool/Motorola.pm create mode 100644 ExifTool/lib/Image/ExifTool/Nikon.pm create mode 100644 ExifTool/lib/Image/ExifTool/NikonCapture.pm create mode 100644 ExifTool/lib/Image/ExifTool/NikonCustom.pm create mode 100644 ExifTool/lib/Image/ExifTool/NikonSettings.pm create mode 100644 ExifTool/lib/Image/ExifTool/Nintendo.pm create mode 100644 ExifTool/lib/Image/ExifTool/OOXML.pm create mode 100644 ExifTool/lib/Image/ExifTool/Ogg.pm create mode 100644 ExifTool/lib/Image/ExifTool/Olympus.pm create mode 100644 ExifTool/lib/Image/ExifTool/OpenEXR.pm create mode 100644 ExifTool/lib/Image/ExifTool/Opus.pm create mode 100644 ExifTool/lib/Image/ExifTool/Other.pm create mode 100644 ExifTool/lib/Image/ExifTool/PCX.pm create mode 100644 ExifTool/lib/Image/ExifTool/PDF.pm create mode 100644 ExifTool/lib/Image/ExifTool/PGF.pm create mode 100644 ExifTool/lib/Image/ExifTool/PICT.pm create mode 100644 ExifTool/lib/Image/ExifTool/PLIST.pm create mode 100644 ExifTool/lib/Image/ExifTool/PLUS.pm create mode 100644 ExifTool/lib/Image/ExifTool/PNG.pm create mode 100644 ExifTool/lib/Image/ExifTool/PPM.pm create mode 100644 ExifTool/lib/Image/ExifTool/PSP.pm create mode 100644 ExifTool/lib/Image/ExifTool/Palm.pm create mode 100644 ExifTool/lib/Image/ExifTool/Panasonic.pm create mode 100644 ExifTool/lib/Image/ExifTool/PanasonicRaw.pm create mode 100644 ExifTool/lib/Image/ExifTool/Parrot.pm create mode 100644 ExifTool/lib/Image/ExifTool/Pentax.pm create mode 100644 ExifTool/lib/Image/ExifTool/PhaseOne.pm create mode 100644 ExifTool/lib/Image/ExifTool/PhotoCD.pm create mode 100644 ExifTool/lib/Image/ExifTool/PhotoMechanic.pm create mode 100644 ExifTool/lib/Image/ExifTool/Photoshop.pm create mode 100644 ExifTool/lib/Image/ExifTool/PostScript.pm create mode 100644 ExifTool/lib/Image/ExifTool/PrintIM.pm create mode 100644 ExifTool/lib/Image/ExifTool/Qualcomm.pm create mode 100644 ExifTool/lib/Image/ExifTool/QuickTime.pm create mode 100644 ExifTool/lib/Image/ExifTool/QuickTimeStream.pl create mode 100644 ExifTool/lib/Image/ExifTool/README create mode 100644 ExifTool/lib/Image/ExifTool/RIFF.pm create mode 100644 ExifTool/lib/Image/ExifTool/RSRC.pm create mode 100644 ExifTool/lib/Image/ExifTool/RTF.pm create mode 100644 ExifTool/lib/Image/ExifTool/Radiance.pm create mode 100644 ExifTool/lib/Image/ExifTool/Rawzor.pm create mode 100644 ExifTool/lib/Image/ExifTool/Real.pm create mode 100644 ExifTool/lib/Image/ExifTool/Reconyx.pm create mode 100644 ExifTool/lib/Image/ExifTool/Red.pm create mode 100644 ExifTool/lib/Image/ExifTool/Ricoh.pm create mode 100644 ExifTool/lib/Image/ExifTool/Samsung.pm create mode 100644 ExifTool/lib/Image/ExifTool/Sanyo.pm create mode 100644 ExifTool/lib/Image/ExifTool/Scalado.pm create mode 100644 ExifTool/lib/Image/ExifTool/Shift.pl create mode 100644 ExifTool/lib/Image/ExifTool/Shortcuts.pm create mode 100644 ExifTool/lib/Image/ExifTool/Sigma.pm create mode 100644 ExifTool/lib/Image/ExifTool/SigmaRaw.pm create mode 100644 ExifTool/lib/Image/ExifTool/Sony.pm create mode 100644 ExifTool/lib/Image/ExifTool/SonyIDC.pm create mode 100644 ExifTool/lib/Image/ExifTool/Stim.pm create mode 100644 ExifTool/lib/Image/ExifTool/TagInfoXML.pm create mode 100644 ExifTool/lib/Image/ExifTool/TagLookup.pm create mode 100644 ExifTool/lib/Image/ExifTool/TagNames.pod create mode 100644 ExifTool/lib/Image/ExifTool/Text.pm create mode 100644 ExifTool/lib/Image/ExifTool/Theora.pm create mode 100644 ExifTool/lib/Image/ExifTool/Torrent.pm create mode 100644 ExifTool/lib/Image/ExifTool/Unknown.pm create mode 100644 ExifTool/lib/Image/ExifTool/VCard.pm create mode 100644 ExifTool/lib/Image/ExifTool/Validate.pm create mode 100644 ExifTool/lib/Image/ExifTool/Vorbis.pm create mode 100644 ExifTool/lib/Image/ExifTool/WPG.pm create mode 100644 ExifTool/lib/Image/ExifTool/WTV.pm create mode 100644 ExifTool/lib/Image/ExifTool/WriteCanonRaw.pl create mode 100644 ExifTool/lib/Image/ExifTool/WriteExif.pl create mode 100644 ExifTool/lib/Image/ExifTool/WriteIPTC.pl create mode 100644 ExifTool/lib/Image/ExifTool/WritePDF.pl create mode 100644 ExifTool/lib/Image/ExifTool/WritePNG.pl create mode 100644 ExifTool/lib/Image/ExifTool/WritePhotoshop.pl create mode 100644 ExifTool/lib/Image/ExifTool/WritePostScript.pl create mode 100644 ExifTool/lib/Image/ExifTool/WriteQuickTime.pl create mode 100644 ExifTool/lib/Image/ExifTool/WriteRIFF.pl create mode 100644 ExifTool/lib/Image/ExifTool/WriteXMP.pl create mode 100644 ExifTool/lib/Image/ExifTool/Writer.pl create mode 100644 ExifTool/lib/Image/ExifTool/XMP.pm create mode 100644 ExifTool/lib/Image/ExifTool/XMP2.pl create mode 100644 ExifTool/lib/Image/ExifTool/XMPStruct.pl create mode 100644 ExifTool/lib/Image/ExifTool/ZIP.pm create mode 100644 ExifTool/lib/Image/ExifTool/ZISRAW.pm create mode 100644 ExifTool/lib/Image/ExifTool/iWork.pm create mode 100644 ExifTool/perl-Image-ExifTool.spec create mode 100644 ExifTool/t/AFCP.t create mode 100644 ExifTool/t/AFCP_2.out create mode 100644 ExifTool/t/AFCP_3.out create mode 100644 ExifTool/t/AIFF.t create mode 100644 ExifTool/t/AIFF_2.out create mode 100644 ExifTool/t/APE.t create mode 100644 ExifTool/t/APE_2.out create mode 100644 ExifTool/t/APE_3.out create mode 100644 ExifTool/t/ASF.t create mode 100644 ExifTool/t/ASF_2.out create mode 100644 ExifTool/t/Apple.t create mode 100644 ExifTool/t/Apple_2.out create mode 100644 ExifTool/t/Audible.t create mode 100644 ExifTool/t/Audible_2.out create mode 100644 ExifTool/t/BMP.t create mode 100644 ExifTool/t/BMP_2.out create mode 100644 ExifTool/t/BPG.t create mode 100644 ExifTool/t/BPG_2.out create mode 100644 ExifTool/t/BigTIFF.t create mode 100644 ExifTool/t/BigTIFF_2.out create mode 100644 ExifTool/t/Canon.t create mode 100644 ExifTool/t/CanonRaw.t create mode 100644 ExifTool/t/CanonRaw_2.out create mode 100644 ExifTool/t/CanonRaw_4.out create mode 100644 ExifTool/t/CanonRaw_5.out create mode 100644 ExifTool/t/CanonRaw_6.out create mode 100644 ExifTool/t/CanonRaw_7.out create mode 100644 ExifTool/t/CanonRaw_8.out create mode 100644 ExifTool/t/CanonRaw_9.out create mode 100644 ExifTool/t/CanonVRD.t create mode 100644 ExifTool/t/CanonVRD_11.out create mode 100644 ExifTool/t/CanonVRD_12.out create mode 100644 ExifTool/t/CanonVRD_13.out create mode 100644 ExifTool/t/CanonVRD_14.out create mode 100644 ExifTool/t/CanonVRD_15.out create mode 100644 ExifTool/t/CanonVRD_16.out create mode 100644 ExifTool/t/CanonVRD_17.out create mode 100644 ExifTool/t/CanonVRD_18.out create mode 100644 ExifTool/t/CanonVRD_19.out create mode 100644 ExifTool/t/CanonVRD_2.out create mode 100644 ExifTool/t/CanonVRD_20.out create mode 100644 ExifTool/t/CanonVRD_21.out create mode 100644 ExifTool/t/CanonVRD_22.out create mode 100644 ExifTool/t/CanonVRD_24.out create mode 100644 ExifTool/t/CanonVRD_3.out create mode 100644 ExifTool/t/CanonVRD_4.out create mode 100644 ExifTool/t/CanonVRD_5.out create mode 100644 ExifTool/t/CanonVRD_6.out create mode 100644 ExifTool/t/CanonVRD_7.out create mode 100644 ExifTool/t/CanonVRD_8.out create mode 100644 ExifTool/t/CanonVRD_9.out create mode 100644 ExifTool/t/Canon_2.out create mode 100644 ExifTool/t/Canon_3.out create mode 100644 ExifTool/t/Casio.t create mode 100644 ExifTool/t/Casio_2.out create mode 100644 ExifTool/t/Casio_3.out create mode 100644 ExifTool/t/Casio_4.out create mode 100644 ExifTool/t/Casio_5.out create mode 100644 ExifTool/t/Casio_6.out create mode 100644 ExifTool/t/DICOM.t create mode 100644 ExifTool/t/DICOM_2.out create mode 100644 ExifTool/t/DNG.t create mode 100644 ExifTool/t/DNG_2.out create mode 100644 ExifTool/t/DNG_3.out create mode 100644 ExifTool/t/DPX.t create mode 100644 ExifTool/t/DPX_2.out create mode 100644 ExifTool/t/DV.t create mode 100644 ExifTool/t/DV_2.out create mode 100644 ExifTool/t/DjVu.t create mode 100644 ExifTool/t/DjVu_2.out create mode 100644 ExifTool/t/EXE.t create mode 100644 ExifTool/t/EXE_2.out create mode 100644 ExifTool/t/EXE_3.out create mode 100644 ExifTool/t/EXE_4.out create mode 100644 ExifTool/t/EXE_5.out create mode 100644 ExifTool/t/EXE_6.out create mode 100644 ExifTool/t/EXE_7.out create mode 100644 ExifTool/t/ExifTool.t create mode 100644 ExifTool/t/ExifTool_16.out create mode 100644 ExifTool/t/ExifTool_17.out create mode 100644 ExifTool/t/ExifTool_2.out create mode 100644 ExifTool/t/ExifTool_20.out create mode 100644 ExifTool/t/ExifTool_21.out create mode 100644 ExifTool/t/ExifTool_22.out create mode 100644 ExifTool/t/ExifTool_23.out create mode 100644 ExifTool/t/ExifTool_24.out create mode 100644 ExifTool/t/ExifTool_25.out create mode 100644 ExifTool/t/ExifTool_26.out create mode 100644 ExifTool/t/ExifTool_27.out create mode 100644 ExifTool/t/ExifTool_28.out create mode 100644 ExifTool/t/ExifTool_29.out create mode 100644 ExifTool/t/ExifTool_3.out create mode 100644 ExifTool/t/ExifTool_30.out create mode 100644 ExifTool/t/ExifTool_31.out create mode 100644 ExifTool/t/ExifTool_32.out create mode 100644 ExifTool/t/ExifTool_33.out create mode 100644 ExifTool/t/ExifTool_34.out create mode 100644 ExifTool/t/ExifTool_35.out create mode 100644 ExifTool/t/ExifTool_4.out create mode 100644 ExifTool/t/ExifTool_5.out create mode 100644 ExifTool/t/ExifTool_6.out create mode 100644 ExifTool/t/ExifTool_7.out create mode 100644 ExifTool/t/ExifTool_8.out create mode 100644 ExifTool/t/ExifTool_9.out create mode 100644 ExifTool/t/FITS.t create mode 100644 ExifTool/t/FITS_2.out create mode 100644 ExifTool/t/FLAC.t create mode 100644 ExifTool/t/FLAC_2.out create mode 100644 ExifTool/t/FLAC_3.out create mode 100644 ExifTool/t/FLIF.t create mode 100644 ExifTool/t/FLIF_2.out create mode 100644 ExifTool/t/FLIF_3.out create mode 100644 ExifTool/t/FLIF_4.out create mode 100644 ExifTool/t/FLIF_5.out create mode 100644 ExifTool/t/FLIF_6.out create mode 100644 ExifTool/t/FLIR.t create mode 100644 ExifTool/t/FLIR_2.out create mode 100644 ExifTool/t/FLIR_3.out create mode 100644 ExifTool/t/Flash.t create mode 100644 ExifTool/t/FlashPix.t create mode 100644 ExifTool/t/FlashPix_2.out create mode 100644 ExifTool/t/Flash_2.out create mode 100644 ExifTool/t/Flash_3.out create mode 100644 ExifTool/t/Font.t create mode 100644 ExifTool/t/Font_2.out create mode 100644 ExifTool/t/Font_3.out create mode 100644 ExifTool/t/Font_4.out create mode 100644 ExifTool/t/Font_5.out create mode 100644 ExifTool/t/Font_6.out create mode 100644 ExifTool/t/Font_7.out create mode 100644 ExifTool/t/FotoStation.t create mode 100644 ExifTool/t/FotoStation_2.out create mode 100644 ExifTool/t/FotoStation_3.out create mode 100644 ExifTool/t/FujiFilm.t create mode 100644 ExifTool/t/FujiFilm_2.out create mode 100644 ExifTool/t/FujiFilm_3.out create mode 100644 ExifTool/t/FujiFilm_4.out create mode 100644 ExifTool/t/FujiFilm_5.out create mode 100644 ExifTool/t/GE.t create mode 100644 ExifTool/t/GE_2.out create mode 100644 ExifTool/t/GE_3.out create mode 100644 ExifTool/t/GIF.t create mode 100644 ExifTool/t/GIF_2.out create mode 100644 ExifTool/t/GIF_3.out create mode 100644 ExifTool/t/GIF_4.out create mode 100644 ExifTool/t/GIMP.t create mode 100644 ExifTool/t/GIMP_2.out create mode 100644 ExifTool/t/GPS.t create mode 100644 ExifTool/t/GPS_2.out create mode 100644 ExifTool/t/GPS_3.out create mode 100644 ExifTool/t/GeoTiff.t create mode 100644 ExifTool/t/GeoTiff_2.out create mode 100644 ExifTool/t/GeoTiff_3.out create mode 100644 ExifTool/t/GeoTiff_4.out create mode 100644 ExifTool/t/Geotag.t create mode 100644 ExifTool/t/Geotag_10.out create mode 100644 ExifTool/t/Geotag_11.out create mode 100644 ExifTool/t/Geotag_12.out create mode 100644 ExifTool/t/Geotag_2.out create mode 100644 ExifTool/t/Geotag_3.out create mode 100644 ExifTool/t/Geotag_5.out create mode 100644 ExifTool/t/Geotag_6.out create mode 100644 ExifTool/t/Geotag_7.out create mode 100644 ExifTool/t/Geotag_8.out create mode 100644 ExifTool/t/Geotag_9.out create mode 100644 ExifTool/t/GoPro.t create mode 100644 ExifTool/t/GoPro_2.out create mode 100644 ExifTool/t/HTML.t create mode 100644 ExifTool/t/HTML_2.out create mode 100644 ExifTool/t/ICO.t create mode 100644 ExifTool/t/ICO_2.out create mode 100644 ExifTool/t/IPTC.t create mode 100644 ExifTool/t/IPTC_2.out create mode 100644 ExifTool/t/IPTC_4.out create mode 100644 ExifTool/t/IPTC_5.out create mode 100644 ExifTool/t/IPTC_6.out create mode 100644 ExifTool/t/IPTC_7.out create mode 100644 ExifTool/t/IPTC_8.out create mode 100644 ExifTool/t/ISO.t create mode 100644 ExifTool/t/ISO_2.out create mode 100644 ExifTool/t/ITC.t create mode 100644 ExifTool/t/ITC_2.out create mode 100644 ExifTool/t/InDesign.t create mode 100644 ExifTool/t/InDesign_2.out create mode 100644 ExifTool/t/InDesign_3.out create mode 100644 ExifTool/t/InDesign_4.out create mode 100644 ExifTool/t/InfiRay.t create mode 100644 ExifTool/t/InfiRay_2.out create mode 100644 ExifTool/t/JSON.t create mode 100644 ExifTool/t/JSON_2.out create mode 100644 ExifTool/t/JVC.t create mode 100644 ExifTool/t/JVC_2.out create mode 100644 ExifTool/t/JVC_3.out create mode 100644 ExifTool/t/JXL.t create mode 100644 ExifTool/t/JXL_2.out create mode 100644 ExifTool/t/JXL_3.out create mode 100644 ExifTool/t/JXL_4.out create mode 100644 ExifTool/t/Jpeg2000.t create mode 100644 ExifTool/t/Jpeg2000_2.out create mode 100644 ExifTool/t/Jpeg2000_3.out create mode 100644 ExifTool/t/Jpeg2000_4.out create mode 100644 ExifTool/t/Jpeg2000_5.out create mode 100644 ExifTool/t/Kodak.t create mode 100644 ExifTool/t/Kodak_2.out create mode 100644 ExifTool/t/Kodak_3.out create mode 100644 ExifTool/t/KyoceraRaw.t create mode 100644 ExifTool/t/KyoceraRaw_2.out create mode 100644 ExifTool/t/LNK.t create mode 100644 ExifTool/t/LNK_2.out create mode 100644 ExifTool/t/Lang.t create mode 100644 ExifTool/t/Lang_1.out create mode 100644 ExifTool/t/Lang_10.out create mode 100644 ExifTool/t/Lang_11.out create mode 100644 ExifTool/t/Lang_12.out create mode 100644 ExifTool/t/Lang_13.out create mode 100644 ExifTool/t/Lang_14.out create mode 100644 ExifTool/t/Lang_15.out create mode 100644 ExifTool/t/Lang_16.out create mode 100644 ExifTool/t/Lang_17.out create mode 100644 ExifTool/t/Lang_18.out create mode 100644 ExifTool/t/Lang_19.out create mode 100644 ExifTool/t/Lang_2.out create mode 100644 ExifTool/t/Lang_3.out create mode 100644 ExifTool/t/Lang_4.out create mode 100644 ExifTool/t/Lang_5.out create mode 100644 ExifTool/t/Lang_6.out create mode 100644 ExifTool/t/Lang_7.out create mode 100644 ExifTool/t/Lang_8.out create mode 100644 ExifTool/t/Lang_9.out create mode 100644 ExifTool/t/Lytro.t create mode 100644 ExifTool/t/Lytro_2.out create mode 100644 ExifTool/t/M2TS.t create mode 100644 ExifTool/t/M2TS_2.out create mode 100644 ExifTool/t/MIE.t create mode 100644 ExifTool/t/MIE_2.out create mode 100644 ExifTool/t/MIE_3.out create mode 100644 ExifTool/t/MIE_5.out create mode 100644 ExifTool/t/MIE_6.out create mode 100644 ExifTool/t/MIFF.t create mode 100644 ExifTool/t/MIFF_2.out create mode 100644 ExifTool/t/MOI.t create mode 100644 ExifTool/t/MOI_2.out create mode 100644 ExifTool/t/MP3.t create mode 100644 ExifTool/t/MP3_2.out create mode 100644 ExifTool/t/MRC.t create mode 100644 ExifTool/t/MRC_2.out create mode 100644 ExifTool/t/MWG.t create mode 100644 ExifTool/t/MWG_2.out create mode 100644 ExifTool/t/MWG_3.out create mode 100644 ExifTool/t/MWG_4.out create mode 100644 ExifTool/t/MWG_5.out create mode 100644 ExifTool/t/MWG_6.out create mode 100644 ExifTool/t/MWG_7.out create mode 100644 ExifTool/t/MXF.t create mode 100644 ExifTool/t/MXF_2.out create mode 100644 ExifTool/t/MacOS.t create mode 100644 ExifTool/t/MacOS_2.out create mode 100644 ExifTool/t/Matroska.t create mode 100644 ExifTool/t/Matroska_2.out create mode 100644 ExifTool/t/Minolta.t create mode 100644 ExifTool/t/Minolta_2.out create mode 100644 ExifTool/t/Minolta_3.out create mode 100644 ExifTool/t/Minolta_4.out create mode 100644 ExifTool/t/Motorola.t create mode 100644 ExifTool/t/Motorola_2.out create mode 100644 ExifTool/t/Nikon.t create mode 100644 ExifTool/t/Nikon_2.out create mode 100644 ExifTool/t/Nikon_3.out create mode 100644 ExifTool/t/Nikon_4.out create mode 100644 ExifTool/t/Nikon_5.out create mode 100644 ExifTool/t/Nikon_7.out create mode 100644 ExifTool/t/Nikon_8.out create mode 100644 ExifTool/t/Olympus.t create mode 100644 ExifTool/t/Olympus_2.out create mode 100644 ExifTool/t/Olympus_3.out create mode 100644 ExifTool/t/Olympus_4.out create mode 100644 ExifTool/t/Olympus_5.out create mode 100644 ExifTool/t/Olympus_6.out create mode 100644 ExifTool/t/Olympus_7.out create mode 100644 ExifTool/t/Olympus_8.out create mode 100644 ExifTool/t/OpenEXR.t create mode 100644 ExifTool/t/OpenEXR_2.out create mode 100644 ExifTool/t/Opus.t create mode 100644 ExifTool/t/Opus_2.out create mode 100644 ExifTool/t/PCX.t create mode 100644 ExifTool/t/PCX_2.out create mode 100644 ExifTool/t/PDF.t create mode 100644 ExifTool/t/PDF_10.out create mode 100644 ExifTool/t/PDF_11.out create mode 100644 ExifTool/t/PDF_12.out create mode 100644 ExifTool/t/PDF_14.out create mode 100644 ExifTool/t/PDF_15.out create mode 100644 ExifTool/t/PDF_16.out create mode 100644 ExifTool/t/PDF_17.out create mode 100644 ExifTool/t/PDF_18.out create mode 100644 ExifTool/t/PDF_19.out create mode 100644 ExifTool/t/PDF_2.out create mode 100644 ExifTool/t/PDF_20.out create mode 100644 ExifTool/t/PDF_22.out create mode 100644 ExifTool/t/PDF_4.out create mode 100644 ExifTool/t/PDF_5.out create mode 100644 ExifTool/t/PDF_6.out create mode 100644 ExifTool/t/PDF_7.out create mode 100644 ExifTool/t/PDF_8.out create mode 100644 ExifTool/t/PDF_9.out create mode 100644 ExifTool/t/PFM.t create mode 100644 ExifTool/t/PFM_2.out create mode 100644 ExifTool/t/PGF.t create mode 100644 ExifTool/t/PGF_2.out create mode 100644 ExifTool/t/PICT.t create mode 100644 ExifTool/t/PICT_2.out create mode 100644 ExifTool/t/PLIST.t create mode 100644 ExifTool/t/PLIST_2.out create mode 100644 ExifTool/t/PLIST_3.out create mode 100644 ExifTool/t/PLIST_4.out create mode 100644 ExifTool/t/PLUS.t create mode 100644 ExifTool/t/PLUS_2.out create mode 100644 ExifTool/t/PNG.t create mode 100644 ExifTool/t/PNG_2.out create mode 100644 ExifTool/t/PNG_3.out create mode 100644 ExifTool/t/PNG_4.out create mode 100644 ExifTool/t/PNG_5.out create mode 100644 ExifTool/t/PNG_6.out create mode 100644 ExifTool/t/PNG_7.out create mode 100644 ExifTool/t/PPM.t create mode 100644 ExifTool/t/PPM_2.out create mode 100644 ExifTool/t/PPM_3.out create mode 100644 ExifTool/t/PSP.t create mode 100644 ExifTool/t/PSP_2.out create mode 100644 ExifTool/t/Palm.t create mode 100644 ExifTool/t/Palm_2.out create mode 100644 ExifTool/t/Panasonic.t create mode 100644 ExifTool/t/Panasonic_2.out create mode 100644 ExifTool/t/Panasonic_3.out create mode 100644 ExifTool/t/Panasonic_4.out create mode 100644 ExifTool/t/Panasonic_5.out create mode 100644 ExifTool/t/Pentax.t create mode 100644 ExifTool/t/Pentax_2.out create mode 100644 ExifTool/t/Pentax_3.out create mode 100644 ExifTool/t/Pentax_4.out create mode 100644 ExifTool/t/PhaseOne.t create mode 100644 ExifTool/t/PhaseOne_2.out create mode 100644 ExifTool/t/PhaseOne_3.out create mode 100644 ExifTool/t/PhotoCD.t create mode 100644 ExifTool/t/PhotoCD_2.out create mode 100644 ExifTool/t/PhotoMechanic.t create mode 100644 ExifTool/t/PhotoMechanic_2.out create mode 100644 ExifTool/t/PhotoMechanic_3.out create mode 100644 ExifTool/t/Photoshop.t create mode 100644 ExifTool/t/Photoshop_2.out create mode 100644 ExifTool/t/Photoshop_3.out create mode 100644 ExifTool/t/PostScript.t create mode 100644 ExifTool/t/PostScript_2.out create mode 100644 ExifTool/t/PostScript_3.out create mode 100644 ExifTool/t/QuickTime.t create mode 100644 ExifTool/t/QuickTime_10.out create mode 100644 ExifTool/t/QuickTime_11.out create mode 100644 ExifTool/t/QuickTime_12.out create mode 100644 ExifTool/t/QuickTime_13.out create mode 100644 ExifTool/t/QuickTime_14.out create mode 100644 ExifTool/t/QuickTime_15.out create mode 100644 ExifTool/t/QuickTime_16.out create mode 100644 ExifTool/t/QuickTime_17.out create mode 100644 ExifTool/t/QuickTime_2.out create mode 100644 ExifTool/t/QuickTime_3.out create mode 100644 ExifTool/t/QuickTime_4.out create mode 100644 ExifTool/t/QuickTime_5.out create mode 100644 ExifTool/t/QuickTime_6.out create mode 100644 ExifTool/t/QuickTime_7.out create mode 100644 ExifTool/t/QuickTime_8.out create mode 100644 ExifTool/t/QuickTime_9.out create mode 100644 ExifTool/t/RIFF.t create mode 100644 ExifTool/t/RIFF_2.out create mode 100644 ExifTool/t/RIFF_3.out create mode 100644 ExifTool/t/RIFF_4.out create mode 100644 ExifTool/t/RIFF_5.out create mode 100644 ExifTool/t/RIFF_6.out create mode 100644 ExifTool/t/RIFF_7.out create mode 100644 ExifTool/t/RTF.t create mode 100644 ExifTool/t/RTF_2.out create mode 100644 ExifTool/t/Radiance.t create mode 100644 ExifTool/t/Radiance_2.out create mode 100644 ExifTool/t/Real.t create mode 100644 ExifTool/t/Real_2.out create mode 100644 ExifTool/t/Real_3.out create mode 100644 ExifTool/t/Real_4.out create mode 100644 ExifTool/t/Red.t create mode 100644 ExifTool/t/Red_2.out create mode 100644 ExifTool/t/Ricoh.t create mode 100644 ExifTool/t/Ricoh_2.out create mode 100644 ExifTool/t/Ricoh_3.out create mode 100644 ExifTool/t/Ricoh_4.out create mode 100644 ExifTool/t/Sanyo.t create mode 100644 ExifTool/t/Sanyo_2.out create mode 100644 ExifTool/t/Sanyo_3.out create mode 100644 ExifTool/t/Sigma.t create mode 100644 ExifTool/t/Sigma_2.out create mode 100644 ExifTool/t/Sigma_3.out create mode 100644 ExifTool/t/Sigma_4.out create mode 100644 ExifTool/t/Sigma_5.out create mode 100644 ExifTool/t/Sony.t create mode 100644 ExifTool/t/Sony_2.out create mode 100644 ExifTool/t/Sony_3.out create mode 100644 ExifTool/t/Sony_5.out create mode 100644 ExifTool/t/TestLib.pm create mode 100644 ExifTool/t/Text.t create mode 100644 ExifTool/t/Text_2.out create mode 100644 ExifTool/t/Text_3.out create mode 100644 ExifTool/t/Text_4.out create mode 100644 ExifTool/t/Text_5.out create mode 100644 ExifTool/t/Text_6.out create mode 100644 ExifTool/t/Text_7.out create mode 100644 ExifTool/t/Torrent.t create mode 100644 ExifTool/t/Torrent_2.out create mode 100644 ExifTool/t/Unknown.t create mode 100644 ExifTool/t/Unknown_2.out create mode 100644 ExifTool/t/Unknown_3.out create mode 100644 ExifTool/t/VCard.t create mode 100644 ExifTool/t/VCard_2.out create mode 100644 ExifTool/t/VCard_3.out create mode 100644 ExifTool/t/Vorbis.t create mode 100644 ExifTool/t/Vorbis_2.out create mode 100644 ExifTool/t/WPG.t create mode 100644 ExifTool/t/WPG_2.out create mode 100644 ExifTool/t/WTV.t create mode 100644 ExifTool/t/WTV_2.out create mode 100644 ExifTool/t/Writer.t create mode 100644 ExifTool/t/Writer_10.out create mode 100644 ExifTool/t/Writer_11.out create mode 100644 ExifTool/t/Writer_13.out create mode 100644 ExifTool/t/Writer_14.out create mode 100644 ExifTool/t/Writer_15.out create mode 100644 ExifTool/t/Writer_16.out create mode 100644 ExifTool/t/Writer_17.out create mode 100644 ExifTool/t/Writer_18.out create mode 100644 ExifTool/t/Writer_19.out create mode 100644 ExifTool/t/Writer_2.out create mode 100644 ExifTool/t/Writer_22.out create mode 100644 ExifTool/t/Writer_24.out create mode 100644 ExifTool/t/Writer_25.out create mode 100644 ExifTool/t/Writer_26.out create mode 100644 ExifTool/t/Writer_27.out create mode 100644 ExifTool/t/Writer_28.out create mode 100644 ExifTool/t/Writer_29.out create mode 100644 ExifTool/t/Writer_30.out create mode 100644 ExifTool/t/Writer_31.out create mode 100644 ExifTool/t/Writer_32.out create mode 100644 ExifTool/t/Writer_33.out create mode 100644 ExifTool/t/Writer_34.out create mode 100644 ExifTool/t/Writer_35.out create mode 100644 ExifTool/t/Writer_36.out create mode 100644 ExifTool/t/Writer_37.out create mode 100644 ExifTool/t/Writer_38.out create mode 100644 ExifTool/t/Writer_39.out create mode 100644 ExifTool/t/Writer_4.out create mode 100644 ExifTool/t/Writer_40.out create mode 100644 ExifTool/t/Writer_41.out create mode 100644 ExifTool/t/Writer_42.out create mode 100644 ExifTool/t/Writer_43.out create mode 100644 ExifTool/t/Writer_44.out create mode 100644 ExifTool/t/Writer_45.out create mode 100644 ExifTool/t/Writer_46.out create mode 100644 ExifTool/t/Writer_47.out create mode 100644 ExifTool/t/Writer_48.out create mode 100644 ExifTool/t/Writer_50.out create mode 100644 ExifTool/t/Writer_51.out create mode 100644 ExifTool/t/Writer_52.out create mode 100644 ExifTool/t/Writer_53.out create mode 100644 ExifTool/t/Writer_54.out create mode 100644 ExifTool/t/Writer_55.out create mode 100644 ExifTool/t/Writer_56.out create mode 100644 ExifTool/t/Writer_58.out create mode 100644 ExifTool/t/Writer_59.out create mode 100644 ExifTool/t/Writer_6.out create mode 100644 ExifTool/t/Writer_60.out create mode 100644 ExifTool/t/Writer_7.out create mode 100644 ExifTool/t/Writer_9.out create mode 100644 ExifTool/t/XMP.t create mode 100644 ExifTool/t/XMP_10.out create mode 100644 ExifTool/t/XMP_11.out create mode 100644 ExifTool/t/XMP_12.out create mode 100644 ExifTool/t/XMP_13.out create mode 100644 ExifTool/t/XMP_14.out create mode 100644 ExifTool/t/XMP_15.out create mode 100644 ExifTool/t/XMP_16.out create mode 100644 ExifTool/t/XMP_17.out create mode 100644 ExifTool/t/XMP_18.out create mode 100644 ExifTool/t/XMP_19.out create mode 100644 ExifTool/t/XMP_2.out create mode 100644 ExifTool/t/XMP_20.out create mode 100644 ExifTool/t/XMP_21.out create mode 100644 ExifTool/t/XMP_22.out create mode 100644 ExifTool/t/XMP_23.out create mode 100644 ExifTool/t/XMP_24.out create mode 100644 ExifTool/t/XMP_25.out create mode 100644 ExifTool/t/XMP_26.out create mode 100644 ExifTool/t/XMP_27.out create mode 100644 ExifTool/t/XMP_28.out create mode 100644 ExifTool/t/XMP_29.out create mode 100644 ExifTool/t/XMP_3.out create mode 100644 ExifTool/t/XMP_30.out create mode 100644 ExifTool/t/XMP_31.out create mode 100644 ExifTool/t/XMP_32.out create mode 100644 ExifTool/t/XMP_34.out create mode 100644 ExifTool/t/XMP_36.out create mode 100644 ExifTool/t/XMP_37.out create mode 100644 ExifTool/t/XMP_39.out create mode 100644 ExifTool/t/XMP_40.out create mode 100644 ExifTool/t/XMP_41.out create mode 100644 ExifTool/t/XMP_42.out create mode 100644 ExifTool/t/XMP_43.out create mode 100644 ExifTool/t/XMP_44.out create mode 100644 ExifTool/t/XMP_45.out create mode 100644 ExifTool/t/XMP_46.out create mode 100644 ExifTool/t/XMP_47.out create mode 100644 ExifTool/t/XMP_48.out create mode 100644 ExifTool/t/XMP_49.out create mode 100644 ExifTool/t/XMP_5.out create mode 100644 ExifTool/t/XMP_50.out create mode 100644 ExifTool/t/XMP_52.out create mode 100644 ExifTool/t/XMP_53.out create mode 100644 ExifTool/t/XMP_54.out create mode 100644 ExifTool/t/XMP_6.out create mode 100644 ExifTool/t/XMP_7.out create mode 100644 ExifTool/t/XMP_8.out create mode 100644 ExifTool/t/XMP_9.out create mode 100644 ExifTool/t/ZIP.t create mode 100644 ExifTool/t/ZIP_2.out create mode 100644 ExifTool/t/ZIP_3.out create mode 100644 ExifTool/t/ZIP_4.out create mode 100644 ExifTool/t/ZIP_5.out create mode 100644 ExifTool/t/ZIP_6.out create mode 100644 ExifTool/t/ZIP_7.out create mode 100644 ExifTool/t/ZIP_8.out create mode 100644 ExifTool/t/ZISRAW.t create mode 100644 ExifTool/t/ZISRAW_2.out create mode 100644 ExifTool/t/images/AFCP.jpg create mode 100644 ExifTool/t/images/AIFF.aif create mode 100644 ExifTool/t/images/APE.ape create mode 100644 ExifTool/t/images/APE.mpc create mode 100644 ExifTool/t/images/ASF.wmv create mode 100644 ExifTool/t/images/Apple.jpg create mode 100644 ExifTool/t/images/Audible.aa create mode 100644 ExifTool/t/images/BMP.bmp create mode 100644 ExifTool/t/images/BPG.bpg create mode 100644 ExifTool/t/images/BigTIFF.btf create mode 100644 ExifTool/t/images/Canon.jpg create mode 100644 ExifTool/t/images/Canon1DmkIII.jpg create mode 100644 ExifTool/t/images/CanonRaw.cr2 create mode 100644 ExifTool/t/images/CanonRaw.cr3 create mode 100644 ExifTool/t/images/CanonRaw.crw create mode 100644 ExifTool/t/images/CanonVRD.dr4 create mode 100644 ExifTool/t/images/CanonVRD.vrd create mode 100644 ExifTool/t/images/CaptureOne.eip create mode 100644 ExifTool/t/images/Casio.jpg create mode 100644 ExifTool/t/images/Casio2.jpg create mode 100644 ExifTool/t/images/CasioQVCI.jpg create mode 100644 ExifTool/t/images/DICOM.dcm create mode 100644 ExifTool/t/images/DNG.dng create mode 100644 ExifTool/t/images/DPX.dpx create mode 100644 ExifTool/t/images/DV.dv create mode 100644 ExifTool/t/images/DjVu.djvu create mode 100644 ExifTool/t/images/EXE.a create mode 100644 ExifTool/t/images/EXE.dylib create mode 100755 ExifTool/t/images/EXE.elf create mode 100755 ExifTool/t/images/EXE.exe create mode 100755 ExifTool/t/images/EXE.macho create mode 100644 ExifTool/t/images/EXE.so create mode 100644 ExifTool/t/images/ExifTool.jpg create mode 100644 ExifTool/t/images/ExifTool.jps create mode 100644 ExifTool/t/images/ExifTool.tif create mode 100644 ExifTool/t/images/ExtendedXMP.jpg create mode 100644 ExifTool/t/images/FITS.fits create mode 100644 ExifTool/t/images/FLAC.flac create mode 100644 ExifTool/t/images/FLAC.ogg create mode 100755 ExifTool/t/images/FLIF.flif create mode 100644 ExifTool/t/images/FLIR.fpf create mode 100644 ExifTool/t/images/FLIR.jpg create mode 100644 ExifTool/t/images/Flash.flv create mode 100644 ExifTool/t/images/Flash.swf create mode 100644 ExifTool/t/images/FlashPix.ppt create mode 100644 ExifTool/t/images/Font.afm create mode 100644 ExifTool/t/images/Font.dfont create mode 100644 ExifTool/t/images/Font.pfa create mode 100644 ExifTool/t/images/Font.pfb create mode 100644 ExifTool/t/images/Font.pfm create mode 100644 ExifTool/t/images/Font.ttf create mode 100644 ExifTool/t/images/FotoStation.jpg create mode 100644 ExifTool/t/images/FujiFilm.jpg create mode 100644 ExifTool/t/images/FujiFilm.raf create mode 100644 ExifTool/t/images/GE.jpg create mode 100644 ExifTool/t/images/GIF.gif create mode 100644 ExifTool/t/images/GIMP.xcf create mode 100644 ExifTool/t/images/GPS.jpg create mode 100644 ExifTool/t/images/GeoTiff.tif create mode 100644 ExifTool/t/images/Geotag.gpx create mode 100644 ExifTool/t/images/Geotag.igc create mode 100644 ExifTool/t/images/Geotag.kml create mode 100644 ExifTool/t/images/Geotag.log create mode 100644 ExifTool/t/images/Geotag.xml create mode 100644 ExifTool/t/images/Geotag2.log create mode 100644 ExifTool/t/images/Geotag3.log create mode 100644 ExifTool/t/images/Geotag_DJI_2020-12-02_[07-50-31].csv create mode 100644 ExifTool/t/images/GoPro.jpg create mode 100644 ExifTool/t/images/HTML.html create mode 100644 ExifTool/t/images/ICC_Profile.icc create mode 100644 ExifTool/t/images/ICO.ico create mode 100644 ExifTool/t/images/IPTC.jpg create mode 100644 ExifTool/t/images/ISO.iso create mode 100644 ExifTool/t/images/ITC.itc create mode 100644 ExifTool/t/images/InDesign.indd create mode 100644 ExifTool/t/images/InfiRay.jpg create mode 100644 ExifTool/t/images/JSON.json create mode 100644 ExifTool/t/images/JVC.jpg create mode 100644 ExifTool/t/images/JVC2.jpg create mode 100644 ExifTool/t/images/JXL.jxl create mode 100755 ExifTool/t/images/JXL2.jxl create mode 100644 ExifTool/t/images/Jpeg2000.j2c create mode 100644 ExifTool/t/images/Jpeg2000.jp2 create mode 100644 ExifTool/t/images/Kodak.jpg create mode 100644 ExifTool/t/images/KyoceraRaw.raw create mode 100755 ExifTool/t/images/LNK.lnk create mode 100644 ExifTool/t/images/Lytro.lfp create mode 100644 ExifTool/t/images/M2TS.mts create mode 100644 ExifTool/t/images/MIE.mie create mode 100644 ExifTool/t/images/MIFF.miff create mode 100644 ExifTool/t/images/MOI.moi create mode 100755 ExifTool/t/images/MP3.mp3 create mode 100644 ExifTool/t/images/MRC.mrc create mode 100644 ExifTool/t/images/MWG.jpg create mode 100644 ExifTool/t/images/MXF.mxf create mode 100755 ExifTool/t/images/MacOS.macos create mode 100644 ExifTool/t/images/Matroska.mkv create mode 100644 ExifTool/t/images/Minolta.jpg create mode 100644 ExifTool/t/images/Minolta.mrw create mode 100644 ExifTool/t/images/Motorola.jpg create mode 100644 ExifTool/t/images/Nikon.jpg create mode 100644 ExifTool/t/images/Nikon.nef create mode 100644 ExifTool/t/images/NikonD2Hs.jpg create mode 100644 ExifTool/t/images/NikonD70.jpg create mode 100644 ExifTool/t/images/OOXML.docx create mode 100644 ExifTool/t/images/Olympus.dss create mode 100644 ExifTool/t/images/Olympus.jpg create mode 100644 ExifTool/t/images/Olympus2.jpg create mode 100644 ExifTool/t/images/OlympusE1.jpg create mode 100644 ExifTool/t/images/OpenDoc.ods create mode 100644 ExifTool/t/images/OpenEXR.exr create mode 100644 ExifTool/t/images/Opus.opus create mode 100644 ExifTool/t/images/PCX.pcx create mode 100644 ExifTool/t/images/PDF.pdf create mode 100644 ExifTool/t/images/PDF2.pdf create mode 100644 ExifTool/t/images/PFM.pfm create mode 100644 ExifTool/t/images/PGF.pgf create mode 100644 ExifTool/t/images/PICT.pict create mode 100644 ExifTool/t/images/PLIST-bin.plist create mode 100644 ExifTool/t/images/PLIST-xml.plist create mode 100644 ExifTool/t/images/PLIST.aae create mode 100644 ExifTool/t/images/PLUS.xmp create mode 100644 ExifTool/t/images/PNG.png create mode 100644 ExifTool/t/images/PPM.ppm create mode 100644 ExifTool/t/images/PSP.psp create mode 100644 ExifTool/t/images/Palm.mobi create mode 100644 ExifTool/t/images/Panasonic.jpg create mode 100644 ExifTool/t/images/Panasonic.rw2 create mode 100644 ExifTool/t/images/Pentax.avi create mode 100644 ExifTool/t/images/Pentax.jpg create mode 100644 ExifTool/t/images/PhaseOne.iiq create mode 100644 ExifTool/t/images/PhotoCD.pcd create mode 100644 ExifTool/t/images/PhotoMechanic.jpg create mode 100644 ExifTool/t/images/Photoshop.psd create mode 100644 ExifTool/t/images/PostScript.eps create mode 100644 ExifTool/t/images/QuickTime.heic create mode 100644 ExifTool/t/images/QuickTime.m4a create mode 100644 ExifTool/t/images/QuickTime.mov create mode 100644 ExifTool/t/images/RIFF.avi create mode 100755 ExifTool/t/images/RIFF.wav create mode 100644 ExifTool/t/images/RIFF.webp create mode 100644 ExifTool/t/images/RTF.rtf create mode 100644 ExifTool/t/images/Radiance.hdr create mode 100644 ExifTool/t/images/Real.ra create mode 100644 ExifTool/t/images/Real.ram create mode 100644 ExifTool/t/images/Real.rm create mode 100644 ExifTool/t/images/Red.r3d create mode 100644 ExifTool/t/images/Ricoh.jpg create mode 100644 ExifTool/t/images/Ricoh2.jpg create mode 100644 ExifTool/t/images/Sanyo.jpg create mode 100644 ExifTool/t/images/Sigma.jpg create mode 100644 ExifTool/t/images/Sigma.x3f create mode 100644 ExifTool/t/images/SigmaDP2.x3f create mode 100644 ExifTool/t/images/Sony.jpg create mode 100644 ExifTool/t/images/Sony.pmp create mode 100644 ExifTool/t/images/Text.csv create mode 100644 ExifTool/t/images/Text1.txt create mode 100644 ExifTool/t/images/Text2.txt create mode 100644 ExifTool/t/images/Text3.txt create mode 100644 ExifTool/t/images/Text4.txt create mode 100644 ExifTool/t/images/Text5.txt create mode 100644 ExifTool/t/images/Torrent.torrent create mode 100644 ExifTool/t/images/Unknown.jpg create mode 100644 ExifTool/t/images/VCard.ics create mode 100644 ExifTool/t/images/VCard.vcf create mode 100644 ExifTool/t/images/Vorbis.ogg create mode 100755 ExifTool/t/images/WPG.wpg create mode 100644 ExifTool/t/images/WTV.wtv create mode 100644 ExifTool/t/images/Writer.jpg create mode 100644 ExifTool/t/images/XMP.inx create mode 100644 ExifTool/t/images/XMP.jpg create mode 100644 ExifTool/t/images/XMP.svg create mode 100644 ExifTool/t/images/XMP.xml create mode 100644 ExifTool/t/images/XMP.xmp create mode 100644 ExifTool/t/images/XMP2.xmp create mode 100644 ExifTool/t/images/XMP3.xmp create mode 100644 ExifTool/t/images/XMP4.xmp create mode 100644 ExifTool/t/images/XMP5.xmp create mode 100644 ExifTool/t/images/XMP6.xmp create mode 100644 ExifTool/t/images/XMP7.xmp create mode 100644 ExifTool/t/images/XMP8.xmp create mode 100644 ExifTool/t/images/XMP9.xmp create mode 100644 ExifTool/t/images/ZIP.gz create mode 100755 ExifTool/t/images/ZIP.rar create mode 100644 ExifTool/t/images/ZIP.zip create mode 100644 ExifTool/t/images/ZISRAW.czi create mode 100644 ExifTool/t/images/iWork.numbers create mode 100644 SQLITE/INSTALL create mode 100644 SQLITE/Makefile.am create mode 100644 SQLITE/Makefile.fallback create mode 100644 SQLITE/Makefile.in create mode 100644 SQLITE/Makefile.msc create mode 100644 SQLITE/README.txt create mode 100644 SQLITE/Replace.cs create mode 100644 SQLITE/aclocal.m4 create mode 100755 SQLITE/compile create mode 100755 SQLITE/config.guess create mode 100755 SQLITE/config.sub create mode 100755 SQLITE/configure create mode 100644 SQLITE/configure.ac create mode 100755 SQLITE/depcomp create mode 100755 SQLITE/install-sh create mode 100644 SQLITE/ltmain.sh create mode 100755 SQLITE/missing create mode 100644 SQLITE/osint.sqlite create mode 100644 SQLITE/shell.c create mode 100644 SQLITE/sqlite3.1 create mode 100644 SQLITE/sqlite3.c create mode 100644 SQLITE/sqlite3.h create mode 100644 SQLITE/sqlite3.pc.in create mode 100644 SQLITE/sqlite3.rc create mode 100644 SQLITE/sqlite3ext.h create mode 100644 SQLITE/sqlite3rc.h create mode 100644 SQLITE/tea/Makefile.in create mode 100644 SQLITE/tea/README create mode 100644 SQLITE/tea/aclocal.m4 create mode 100755 SQLITE/tea/configure create mode 100644 SQLITE/tea/configure.ac create mode 100644 SQLITE/tea/doc/sqlite3.n create mode 100644 SQLITE/tea/generic/tclsqlite3.c create mode 100644 SQLITE/tea/license.terms create mode 100644 SQLITE/tea/pkgIndex.tcl.in create mode 100644 SQLITE/tea/tclconfig/install-sh create mode 100644 SQLITE/tea/tclconfig/tcl.m4 create mode 100644 SQLITE/tea/win/makefile.vc create mode 100644 SQLITE/tea/win/nmakehlp.c create mode 100644 SQLITE/tea/win/rules.vc create mode 100644 photo_edf1.JPG create mode 100644 photo_edf2.JPG create mode 100644 photo_edf3.JPG diff --git a/ExifTool/0027ea9ab3e2d95a68f3e5e738dbd60b3bf6dfaa4806e80b7536a56b0681df37.png b/ExifTool/0027ea9ab3e2d95a68f3e5e738dbd60b3bf6dfaa4806e80b7536a56b0681df37.png new file mode 100644 index 0000000000000000000000000000000000000000..828026d0686cc2d89a6be03cc890ef43b676f73b GIT binary patch literal 90797 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

mmY&#>R z4kFKC<8Zwc5II*+P%Z~f5IWY9<@sE`EUC)KwzHCWycGki;sCGcd8b;vvxAEFH7^Kw ztnbY)|FN%KyJ9b|WBUIA+@|_=F@<3e5qas9!|dgl-3w=B_t*tfRamzc%gIcDNv?=1 z)|%>xw}c^XbjVP(5bkz>@IY9?YTF+le)S2B|m`IT<#baltq1aHI zfmc*&^iF9k>1(-M6iQFT)RV8z6YM%iz0#FJ1Chw)=0l%A+j(;snRCNTftaQLY}YemqJ<9M@aa38M|5 zCG3(L@Kcmjg4>{9UK1kz)th(k-u?5WB^d8YO9s{J;&u`;Pi8V-0K16458w$7!}EgV z+(hWRItPgmBtS>cKq5!V&0rf~qp`ogH&3`Xwj#LO2&WR=yV@8t5~@P_1mJ7j#*|LA zq_Ca(DAWS!e1hRtUbiG=NeV_>zBHM#rK-u!UAvDKC% zx+?iCVmJsNCQa8=Ar@36ddT*mMAryKgavP-YeX?~aeYyBUHmNSPM(ffX{kgi)r&db z4vna%6-CXn%^fQJ#BkyvhjL@&yQm9yvCNA7@N&|;@P)WX!1I&1PC1iICNrhL7ucs* z>vErr!gd==V_V;@?e5Ok>+|cZ{vr_@QSUd?5-l67b%kJ7Ys7VN6qj?S{)MaNdtFauwEFf)5m7qhZ+N1!}ENpBn_f)Gl?pm@XljtSk zOaFOMhk3!9d9G3%kf1Yr_9%rmXG#H{S0Z86wfh6g_hb|l`~>AY%|{V3Ns-d4qhmxy z%UsTQ0(b#Ek-(h|cL{qlGx|GciG8+*jPZhZX&Am!PEL&QvTGA~11=l-m~{-;ZRx;h z=GNYBqdr$JTp9c%{iElDvA#G!q{#L!&>s}CU{Gd0O7hrLJKMV*{7li`Lq>8u%PilN z(8EJzo1MKSk3@{UmYm3b9d3`hCi7hNJOE74hh;gFbgL8;5ay9kC&SN1Fi(R-j#jDT z-OhsY8%VO%#lATI+`a+aNF_7zqDle))N^u2SN1Y-LTExIQX*>aUDVJ00!3>1u04v> zwvkGqWMsxi*JQj-x!QsFIEw9ukm$now6ljKcV00g7PF+?4^>JMEIO zZ0imIf484w*?gX2@qzCK;xS$tkiW;ek9<=!;Eav`{V0l*`uwnqXs3H;VkG1De>K>} zvbfMQke5OMDTqOq63m(xIjRRln%XZ7`qLN>ePaaV<6(9t11GAR+?_BplXM39VqY6}OfeYfS0m-#7NbIJ>M*@@b>KoA zCYM|E2!2DeD*drSq`%!Bc)663$Z`oaasKK#i8U< zk%uYt9Jv+TK|XMS+lA;fK_9E8{@HMkkYCP@jfoe8O8iPsGPA@+!5{XtL%-H2hW>0o opVK*=(>a~fIi1rvozv&&|4E9)Ea2)4p#T5?07*qoM6N<$g5u`4UH||9 literal 0 HcmV?d00001 diff --git a/ExifTool/0027ea9ab3e2d95a68f3e5e738dbd60b3bf6dfaa4806e80b7536a56b0681df37.png_original b/ExifTool/0027ea9ab3e2d95a68f3e5e738dbd60b3bf6dfaa4806e80b7536a56b0681df37.png_original new file mode 100644 index 0000000000000000000000000000000000000000..18f2ed79350afb35872d0f28fe48bd8475cc9779 GIT binary patch literal 90754 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 literal 0 HcmV?d00001 diff --git a/ExifTool/Changes b/ExifTool/Changes new file mode 100644 index 0000000..ea4e6d7 --- /dev/null +++ b/ExifTool/Changes @@ -0,0 +1,10628 @@ +DO NOT EDIT THIS FILE -- it is generated from the html history files. + +ExifTool Version History + +RSS feed: https://exiftool.org/rss.xml + +Note: The most recent production release is Version 12.60. (Other versions are +considered development releases, and are not uploaded to MetaCPAN.) + +Sept. 19, 2023 - Version 12.67 + + - Added a new Pentax LensType (thanks dmont) + - Added a new FujiFilm FilmMode and FaceElementTypes values (thanks Greybeard) + - Fixed error writing new DataMining tag where URI prefix wasn't being + properly added to the value + +Sept. 19, 2023 - Version 12.66 + + - Added a few new Canon LensType values (thanks Norbert Wasser) + - Added conversions for a few Apple:ImageCaptureType values + - Added new XMP tag for PLUS version 2.0.1 + - Added a new CanonModelID (thanks Laurent Clevy) + - Decode another tag from Canon 1DS raw images (Hubert Figuiere, github #219) + - Decode JPGCompression for newer Nikon models (thanks Warren Hatch) + - Fixed bug introduced in 12.65 where duplicate tags were not returned even + when the groups where specified explicitly + - API Changes: + - Added WindowsWideFile option + +Aug. 10, 2023 - Version 12.65 + + - Added a new QuickTime Keys tag + - Added a new CanonModelID (thanks Laurent Clevy) + - Added a new Canon LensType (thanks Norbert Wasser) + - Added number in brackets to converted Samsung MCCData value + - Decode a number of new Sony tags (thanks Jos Roost) + - Decode a few new FlashPix tags (github #217) + - Improved decoding of Nikon Z9 firmware 4.0 tags (thanks Warren Hatch) + - Improved parsing of PDF:Keywords to support semicolon-separated lists + - Enhanced -api option to show list of available options if no argument is + provided + - Lowered priority of IFD1 tags in ARW images so IFD0/SubIFD take precedence + - Changed QuickTime tag names for atID (AlbumTitleID to ArtistID) and plID + (PlayListID to AlbumID) (github issue #216), and added cmID (ComposerID) + - Changed Apple:MediaGroupUUID tag name back to ContentIdentifier + - Patched the -d option to handle the %s format code internally when writing + (avoids problems due to inconsistent behaviour of this format code in the + strptime function on different systems) + - Patched patch of version 12.32 to restore ability to read from named pipes + - Fixed bug which could cause a hang when processing a corrupt BigTIFF image + - Fixed document number for auxiliary image metadata in HEIC files + - Fixed misspelt Apple tag name (thanks Neal Krawetz) + - API Changes: + - Added AvailableOptions method + +June 28, 2023 - Version 12.64 + + - Added a new Sony LensType (thanks Jos Roost) + - Added config_files/guano.config to the distribution (thanks StarGeek) + - Added support for Garmin Low-resolution Video (GLV) files + - Added JUMBF to the list of deletable groups + - Added (untested) read support for spherical video tags in Matroska videos + - Decode a number of new Nikon Z9 tags (thanks Warren Hatch) + - Decode AmbisonicAudio tags in spherical MP4 vidoes + - Decode another Apple tag + - Improved French translations (thanks Philippe Bonnaure of GraphicConverter) + - Patched to allow writing QuickTime-based videos where the audio/video sample + description comes after the sample pointers + - Fixed parsing of GPS from Insta360 videos to properly skip void fixes + - Fixed problem where Apple iPhone 14 images produced invalid XML in -X output + when using -struct option + - API Changes: + - Added StructFormat option to allow JSON-format serialized structures + - Added NoDups option to eliminate duplicate items from queued values when + writing List-type tags + +June 8, 2023 - Version 12.63 + + - Added ability to read/write/create Brotli-compressed metadata in JXL images + (requires IO::Compress::Brotli) + - Added partial support for Exif 3.0 specification: + - Added new EXIF tags + - Added MPF Original Preservation Image type + - Support for reading 'utf8' values (but still write only as 'string') + - Added support for Adobe XMP-hdrgm (HDR Gain Map) tags + - Added support for reading 7z files (thanks Amir Gooran, github #205) (but + currently this doesn't work for the Windows .exe version because I haven't + been able to install Compress::Raw::Lzma for ActivePerl) + - Added XMP-panorama tags + - Added warning if -csv is used with -p + - Added warning if trying to geotag from a UTF-16 track log + - Decode ImageWidth/Height from JXL images using partial codestreams + - Decode more Sony tags for some newer models (thanks Jos Roost) + - Extract GainMapImage (hrgm box) from JXL files + - Extract Guano information from WAV files + - Enhanced ImageDataMD5 feature and renamed to ImageDataHash (with + ImageDataMD5 alias for backward compatibility) + - Changed RARVersion tag name to FileVersion + - Fixed bug introduced in 12.46 which could cause a hang when reading a + corrupted RIFF-based file + - Fixed writing of Composition:GPSPosition when -n is used + - API Changes: + - Added ImageHashType option + +May 3, 2023 - Version 12.62 + + - Added basic read support for WPG images + - Added ImageDataMD5 support for HEIC images + - Added support for RAR version 5.0 files (thanks Amir Gooran, github #203) + - Added a few new XMP-aux tags (thanks John Ellis) + - Made Composite tags available for use in -fileNUM argument + - Better handling of FlashPix VT_EMPTY value + - Fixed "Can't write" error when specifying a .webp file for the -o option + - API Changes: + - Added NoWarning option + +Apr. 24, 2023 - Version 12.61 + + - Added ImageDataMD5 support for J2C and JXL images + - Added support for PDF 2.0 (specification is finally freely available) + - Added ability to extract timed Accelerometer data from Azdome GS63H MP4 + videos which don't contain GPS + - Added some new Sony lenses (thanks Jos Roost) + - Decode some new tags for the Sony ZV-E1 (thanks Jos Roost) + - Decode more tags for the Nikon Z30 (thanks Xavier) + - Enhanced -fileNUM option to allow tags from the main file to be used in the + file name string + - Validate sample offset and size when calculating ImageDataMD5 for MP4 videos + (note: may change ImageDataMD5 value for videos where audio data runs past + end of media data) + - Return error when attempting to write a fragmented JXL file + - Improved robustness for determining image size for corrupted JPEG + - Patched to allow Insta360 GPS records of unexpected length and tweaked + verification algorithm to determine validity of these records + - Fixed bug introduced in 12.57 where -progress:%f gave runtime warnings + - Fixed "--" option to ignore subsequent -common_args option + - Fixed incorrect ImageDataMD5 for Sony A100 ARW images + - Fixed problem reading new XMP-et:OriginalImageMD5 tag + +Apr. 5, 2023 - Version 12.60 (production release) + + - Added a new Sony FileFormat value + - Added Validate warning about duplicate EXIF + - Added ability to edit JPEG APP1 EXIF segment with incorrect header + - Decode a few new Sony ARW tags + - Improved -htmldump of non-EXIF-based maker notes + - Enhanced -geotag from CSV files support GPSSpeed (with variable units), + "bearing" for GPSTrack, and GPSDateTime in format "dd.mm.YYYY HH:MM:SS" + - Enhanced ImageDataMD5 to also support CRW, RAF, X3F and AVIF images + - Enhanced -efile option to also record updated and created file names + - Family 8 group names may now also be used in Composite Require/Desire tags + - Fixed handling of undefined tags in -if conditions to conform with + documentation and match -p and -tagsFromFile behaviour when -m or -f option + is used + - Fixed problem where setting the Geotime value didn't work when using an + advanced-formatting expression containing a greater-than symbol (>) + +Mar. 28, 2023 - Version 12.59 + + - COMPATIBILITY WARNING: Changed the calculated ImageDataMD5 for JPEG images + to include all data from the SOS to the EOI (including the SOS marker but + not the EOI marker) + - Added new -fileNUM option to load tags from alternate files + - Added family 8 groups for accessing tags from alternate files + - Added new XMP-et:OriginalImageMD5 tag for storing ImageDataMD5 value + - Added verbose ImageDataMD5 message for JPEG files + - Added a new Nikon LensID (thanks Warren Hatch) + - Decode a new Olympus tag and improved decoding of another (thanks Herb) + - Decode a couple of new PanasonicRaw tags + - Decode image coordinates for a couple more VNT object types + - Enhanced ImageDataMD5 to also support MRW, CR3, IIQ, PNG, MOV/MP4 and some + RIFF-based files + - Improved verbose messages when deleting NikonApp trailer + - Patched to avoid structure warnings when copying tags from Nikon files + containing NKSC metadata + - Fixed %-C filename format code to work properly with the -fileOrder and + -progress options + - Fixed potential ValueConv warning when reading LIF files + - API Changes: + - Added SetAlternateFile method + +Mar. 15, 2023 - Version 12.58 + + - Added Extra ImageDataMD5 tag to calculate MD5 of image data only + - Added support for reading DJI APP4 and APP7 JPEG segments + - Added a new SonyModelID value + - Decode a few new Nikon tags (thanks Warren Hatch) + - Downgraded "Windows file times" to a minor warning when Win32::API or + Win32API::File is not installed while reading metadata + - Patched possible runtime warning when API IgnoreTags option is used to + ignore FileType + - Fixed problem extracting NetName from Windows LNK files + - Fixed issue where the %C filename format code would increment the count on + an output filename collision, but it is supposed to count the input files + +Feb. 23, 2023 - Version 12.57 + + - Added two new Nikon Z lenses (thanks LibRaw) + - Added a new Sigma LensType (thanks LibRaw) + - Added a new Olympus LensType (thanks Herb) + - Decode more new Nikon tags (thanks Warren Hatch) + - Decode Photoshop LayerColors, LayerSections and LayerVisible tags + - Improved Verbose output for QuickTime-format files + - Set family 1 group name for Garmin GPS from uuid atom + - Enhanced -progress option to allow message to be displayed every NUM files + - Significant improvements to parsing of Nikon ShotInfo records for newer + models + - Removed hex dump of APP segments from -v3 output when writing + - Fixed bug writing negative MIE GPS coordinates + - Fixed bug where a duplicate XMP could be generated when writing XMP to a + JPEG XL image which already contained XMP + - Fixed problem where HEAD lines may be duplicated in an output file if the -p + option was combined with -w+ or -W+ + +Feb. 9, 2023 - Version 12.56 + + - Added support for VNT files (both Scene7 Vignette and V-Note document) + - Added read support for InfiRay IJPEG metadata (thanks Marcos Del Sol Vives) + - Added some new Sony LensType values (thanks Jos Roost and Francois Piette) + - Added a new FujiFilm VideoRecordingMode value (thanks Greybeard) + - Added two new Canon LensTypes and CanonModelIDs (thanks Norbert Wasser) + - Added ability to extract semantic images from Apple ProRaw DNG files + - Added read support for the PNG cICP chunk + - Decode more Nikon tags (thanks Warren Hatch) + - Extract PreviewImage from Insta360 trailer record 0x200 + - Extract EmbeddedImageRectangle and some other new tags from VNT files + - Minor improvement to arg_files/xmp2exif.args (thanks StarGeek) + - Enhanced -ee option to extract metadata from all frames of a multipart EXR + image + - Removed EXR Layout tag and incorporated into new Flags tag + - Patched possible hang problem when reading corrupted .rm audio files + +Jan. 17, 2023 - Version 12.55 + + - Added support for geotagging from FlightAware KML files + - Decode two more types of timed GPS from MOV/MP4 videos (66 types now) + - Decode a few new Nikon tags (thanks Warren Hatch) + - Decode a new Samsung HEIC tag + - Decode FujiFilm RollAngle + - Fixed bug where the FlatName property wasn't working properly for some + user-defined structure tags + +Jan. 6, 2023 - Version 12.54 + + - Decode a number of new Apple tags (thanks Frank Rupprecht) + - Increased precision of Sony FocusDistance2 conversion + - Fixed problem where GPSAltitude wasn't being set when geotagging from KML + files + - Fixed bug writing HEIC/AVIF files which have a zero-sized mdat (ie. media + data extends to end of file) which could cause an incorrect mdat size to be + written + +Jan. 4, 2023 - Version 12.53 + + - Added support for a number of new XMP tags written by ACR 15.1 + - Added a new Nikon LensID + - Decode timed GPS from Lamax S9 dual dashcam MOV videos + - Decode a number of new Nikon tags (thanks Warren Hatch) + - Decode a couple of new Canon tags (thanks John Moyer) + - Decode FujiFilm BWMagentaGreen tag + - Enable block-write of EXIF to JXL files + - Accept values of "now" and "Z" when writing EXIF OffsetTime tags + - Changed priority of XMP when reading/writing HEIC files so that it is no + longer preferred as with other QuickTime-based formats + - Changed family 1 group name of Canon DR4 tags from CanonVRD to CanonDR4 to + allow newer tags to be differentiated from older ones. The family 0 group + name for both remains CanonVRD + - Patched to recognize JXL EXIF box with non-zero header length + - Patched to avoid runtime error when writing a PDF with an Info dictionary + which was stored incorrectly as a direct object + - Fixed problem writing EXIF to JXL images where a new EXIF box was created + even if one previously existed + +Dec. 6, 2022 - Version 12.52 + + - Added a few new Nikon LensID's (thanks LibRaw and Chris) + - Added Slovak translations (thanks Peter Bagin) + - Made SphericalVideoXML readable/writable as a block + - Improved handling of Matroska metadata tags, including language support + - Improved French translations (thanks Philippe Bonnaure of GraphicConverter) + - Improved Composite:GPSAltitude conversion to honour -lang setting + - Improved -v2 messages to indicate files extracted from zip archives + +Nov. 21, 2022 - Version 12.51 + + - Added a new Olympus LensType (thanks Herb) + - Extract C2PA JUMBF metadata from PNG images and extract C2PA Salt values + - Decode NikonSettings for Z9 firmware 3.0 (thanks Warren Hatch) + - Decode additional camm metadata from Insta360 Pro2 MP4 videos + - Improved Verbose output when writing Composite tags to add a "+" sign to + indicate related tags that are being written + - Enhanced -geotag option CSV format to support GPSImgDirection column + - Fixed problem where -w+ option didn't work in Windows if there were Unicode + characters in the path name + - Fixed problem where only the last image of the sequence was extracted + (multiple times) when using -ee2 to extract embedded images from FLIR SEQ + files + - Fixed issue where GPS reference directions may be unknowingly written when + using ExifTool 12.44 or later to write GPSLatitude or GPSLongitude without + specifying a group name. The fix was to Avoid writing the Composite tags + unless the Composite group is specified explicitly + - Fixed -geotag to write orientation and track tags even if some tags in the + category were missing + - Fixed inconsistency in selecting which tag to output with the -json option + when multiple tags with the same JSON key exist and the -TAG# feature is + used to disable print conversion + - Fixed problem writing QuickTime:PlayListID + - Fixed problem writing QuickTime tags when specifying tag ID (ie. family 7 + group) as well as a language code + +Nov. 8, 2022 - Version 12.50 (production release) + + - Added a new XMP-GCreations tag + - Added a few new Sony lenses (thanks Jos Roost) + - Added new SonyModelID and Olympus CameraType values (thanks LibRaw and Herb) + - Added a couple of new XMP tags (thanks Jose Oliver-Didier) + - Added a new Nikon Z lens (thanks LibRaw) + - Added a new Canon LensType and CanonModelID (thanks Norbert Wasser and + LibRaw) + - Added some new Pentax lenses (thanks LibRaw) + - Added experimental support for timed GPS in TS videos from Jomise T860S-GM + dashcam (more samples are needed for this to be finalized) + - Decode information written in "skip" atom of 70mai Pro Plus+ MP4 videos + - Decode timed accelerometer data from Kenwood dashcam MP4 videos + - Decode a few new Nikon Z9 tags (thanks Stefan Grussen) + - Decode ColorData for some newer Canon models (thanks LibRaw) + - Decode a number of new tags for the Sony ILCE-7RM5 (thanks Jos Roost) + - Updated IPTC XMP tags to correspond with new Photo Metadata 2022.1 standard + - Extract JPEG previews from FujiFilm HIF images + - Changed -if option so multiple -if options are evaluated at the lowest + specified -fast level + - Changed MIMEType for ICO and CUR files + - Enhanced -fast2 so it stops processing QuickTime files at mdat atom + - Enhanced -listx output so -f also indicates the ID of the parent structure + for Flattened tags + - Improved conversion of IPTC date-only and time-only tags to allow formatting + with the -d option + - Improved Canon and Nikon TimeZone tags to accept a wider variety of input + formats when writing + - Disabled extraction of Nikon Z9 MenuSettings for firmware 3.0 until they can + be properly decoded (thanks Warren Hatch) + - Fixed decoding of AF points for some newer Nikon models + - Fixed inconsistent year and time zone for Kenwood dashcam timed GPS in MP4 + videos + +History of older versions (back to Nov. 19, 2003 - Version 1.00) --> + +Oct. 19, 2022 - Version 12.49 + + - Added read support for Windows ICO and CUR files + - Added ability to shift EXIF OffsetTime tags (eg. "-OffsetTime+=+02:00") + - Added a few new XMP tags and print conversions + - Added a print conversion for Photoshop:PrintFlags + - Added a new SonyModelID (thanks LibRaw) + - Added a few new Canon RF LensType values (thanks Norbert Wasser) + - Added a new Canon LensType + - Added a new Nikon LensID + - Decode 'riff' metadata blocks in FLAC audio files + - Decode RIFF 'acid' chunk written by Acidizer + - Enhanced the -d option %f sub-second date/time format code to allow the + decimal point to be dropped (eg. "%-3f") + - Patched another Sigma Photo Pro incompatibility when writing X3F images + (Sigma will also fix this at their end in the next SPP release) + +Oct. 13, 2022 - Version 12.48 + + - Added support for new XMP-photoshop:CameraProfiles structure + - Added a new SonyModelID and Sony LensType (thanks Jos Roost) + - Decode more tags for the Sony ILME-FX30 (thanks Jos Roost) + - Decode a couple of new Panasonic tags, and improved decoding of others + - Decode STANAG-4609 MISB timed metadata from M2TS videos + - Decode a new Nikon tag (thanks Warren Hatch) + - Decode a couple of new FujiFilm tags (thanks Honza Pokorny) + - Improved round-off errors when writing QuickTime:MatrixStructure via the + Composite:Rotation tag + - Increased Verbose level of "nothing changed" message added in 12.45 + - Removed "Z" (Zulu) designation from some of the MS-DOC date/time tags + because they most certainly are in local time as written by Word 2011 for + Mac (while some other MS-DOC and FlashPix date/time tags extracted without a + "Z" are actually in Zulu time -- a bit of a mess really) + - Prevent dynamically-generated Unknown tags from being extracted when the + -validate option is used without -u + - Patched to better handle irregular timestamps in streaming GPS of NextBase + dashcam videos + - Fixed incompatibility with Sigma Photo Pro which could result in Sigma Photo + Pro corrupting an ExifTool-edited X3F image (the section length in the + footer needed to include the padding to a 4-byte boundary, thanks Sigma + engineer Yuki Miyahara) + - Fixed problem which could prevent ExifTool from reading all GPS points from + some INNOV M2TS videos + +Oct. 3, 2022 - Version 12.47 + + - Added a new Nikon LensID (thanks David Puschel) + - Fixed bug introduced in 12.46 which resulted in a runtime error when -j was + combined with -b + +Oct. 1, 2022 - Version 12.46 - "Write WEBP" + + - Added WEBP write support + - Added the abilty to write Panasonic GH6 RW2 files + - Added a new Canon LensType + - Added a number of new Sigma LensType values (thanks LibRaw) + - Added support for BigTIFF format code 16 in Apple ProRaw maker notes + - Added config_files/frameCount.config to extract MP4 FrameCount + - Added a MIE OriginalImageSize tag + - Added some extra -validate checks for RIFF-based file formats + - Extract FrameRate from MP4 tmcd box + - Decode a new Apple tag (thanks Neal Krawetz) + - Decode more information from Nikon Z-camera videos + - Decode streaming GPS from Garmin DriveAssist 51 MP4 videos + - Changed the names of two FujiFilm FirmwareVersion tags + - Enhanced WEBP FileType identification to denote Extended WEBP + - Preserve fractional seconds when extracting Samsung TimeStamp times + - Patched to avoid reporting Photoshop:ProgressiveScans unless PhotoshopFormat + is Progressive + - Patched to test QuickTime UserData tags with default 0x0000 language code to + see if they contain UTF8 characters, and if so assume UTF8 encoding and + ignore the CharsetQuickTime setting + - Patched to avoid potential deep recursion when reading/writing malicious CRW + images + - Fixed "Invalid ID3 frame size" problem when reading ID3v2 with an extended + header (very uncommon, but Audacity uses this) + - Fixed typo in the name of a new DNG 1.6 tag + - Fixed some verbose warnings when reading Nikon Z-camera NEF files + - Fixed decoding of a couple of Nikon Z9 tags for newer firmware versions + (thanks Warren Hatch) + +Sept. 16, 2022 - Version 12.45 + + - Added new IPTC Video Metadata version 1.3 tags + - Added a couple of new Canon lenses (thanks Norbert Wasser) + - Added a new Sony LensType (thanks Jos Roost) + - Added known Unknown value for IPTC ChromaticityColorant (thanks Herb) + - Added new Nikon WhiteBalanceFineTune tag (thanks Milos Komarcevic) + - Extract the raw thermal data from all frames of a SEQ file when -ee2 is used + - Decode individual tags in QuickTime ColorRepresentation + - Decode a new Matroska tag + - Improved verbose "nothing changed" messages when writing + - Patched -ee option to extract metadata after the first Cluster in MKV videos + (previously only -U and -v did this) + - Patched to differentiate Java bytecode .class files from Mach-O fat binaries + - Patched to avoid "Use of uninitialized value" warning when deleting GPS + coordinates via the newly writable Composite tags + - Patched to avoid duplicating raw data when writing Sony ARW images where the + raw data is double-referenced as both strips and tiles (affects ARW images + from some newer models like the ILCE-1 when SonyRawFileType is "Lossless + Compressed RAW 2") + - Patched to avoid "fixing" the order of IFD entries in TIFF-based RAW files + to improve compatibility with some RAW viewers + - Minor change to Composite FileNumber to remove "-" when -n is used + - Fixed problem extracting some timed metadata when "-api ignoretags=all" was + used with "-api requesttags" to request the specific information + - Fixed -validate feature to avoid incorrectly warning about non-capitalized + boolean values in XMP + +July 21, 2022 - Version 12.44 + + - Added a few new Sony lenses (thanks Jos Roost) + - Decode Accelerometer and Gyroscope data from ARCore videos + - Decode a couple of new Motorola tags (thanks Neal Krawetz) + - Decode FujiFilm FirmwareVersion (thanks Justin Arkinson) + - Decode MetaType for timed metadata in videos + - Decode a number of new Nikon Z tags (thanks Warren Hatch) + - Extract more types of embedded images from FlashPix-format files + - Made Composite GPSLatitude and GPSLongitude writable for setting GPS + coordinates and reference directions with one assignment + - Fixed bug introduced in 12.39 which broke extraction of timed GPS from some + INNOVV videos + - Fixed bug introduced in 12.43 which broke extraction of timed GPSDateTime + from Insta360 videos + +July 6, 2022 - Version 12.43 + + - Added the ability to geotag from Google Takeout JSON files + - Added a few new Canon RF LensType values and a couple of new CanonModelID's + (thanks Norbert Wasser) + - Added new values to a couple of FujiFilm tags (thanks Greybeard) + - Added a new Nikon LensID (thanks BertJan Bakker) + - Recognize Autodesk Revit files (but don't yet support reading metadata) + - Decode DriveSerialNumber from LNK files (github #145) + - Decode Apple FocusDistanceRange (thanks Neal Krawetz) + - Made a number of Sony SR2SubIFD tags writable + - Tolerate dashes instead of colons as date separators in -geotag CSV files + - Patched to read new format accelerometer data from Insta360 files + - Patched to avoid outputting some Unknown tags when the -validate option is + used after a previously -execute'd command used the -u option + - Fixed names of Canon G9 WB levels tags (changed from GRGB to GRBG) (thanks + Christoph) + - Fixed typo in new Olympus AISubjectTrackingMode value + - Fixed "use of undefined value" warning when reading DJI metadata + - API Changes: + - Added IgnoreTags option + +June 1, 2022 - Version 12.42 (production release) + + - Added support for reading maker notes from Panasonic DC-GH6 videos + - Added conversion for Samsung MCCData + - Added a new Nikon LensID (thanks Chris) + - Added a few new Canon LensType values + - Added a couple of new Olympus StackedImage values (thanks Eberhard) + - Added a few new values for some Nikon Settings tags (thanks Warren Hatch) + - Added a "lang:" element to the -json output for alternate language tags when + -D, -H or -t is used + - Update DNG writer to not issue an error when writing DNG 1.6 files + - Decode information from DJI "ae_dbg_info" maker notes + - Decode Olympus AISubjectTrackingMode + - Changed ExifTool FileSize print conversion to use kB/MB/GB units instead of + KiB/MiB/GiB + - Changed "is not shiftable" warning to appear in -v (instead of just -v3) + output + - Patched to allow PDF Encrypt object to be "null" + - Fixed bug reading ICC_Profile 'meta' tags + +Apr. 7, 2022 - Version 12.41 + + - Added support for "OM SYSTEM" maker notes + - Added 2 new Sony LensType values (thanks Jos Roost) + - Added some new Canon lenses (thanks LibRaw) + - Added a new Nikon LensID (thanks Bert Ligtvoet) + - Added a new Canon ContinuousDrive value (thanks Wolfgang Gulcker) + - Enhanced -v0 option to also print new file name when renaming, moving or + copying a file + - Updated xmp2exif.args and exif2xmp.args helper files to reflect the IPTC + Photometadata Mapping Guidelines version 2022.1 + - Made "Invalid Xxx data" a minor warning for MakerNote data + - Patched to allow writing of MP4 videos which have other tracks with a + missing sample description entry + - Patched MacOS version to specify directory for external utilities (setfile, + xattr, stat, mdls and osascript from /usr/bin, and tag from /usr/local/bin) + - Fixed long-standing problem where Windows version could behave differently + for -if conditions containing undefined tags + - Fixed problem where -W+! combined with -j or -X produced invalid JSON or XML + when processing multiple files + - Fixed potential "uninitialized value $time in division" runtime warning when + reading MP4 videos + +Feb. 9, 2022 - Version 12.40 + + - Added PageCount tag to return the number of pages in a multi-page TIFF + - Added a new Nikon LensID (thanks Wolfgang Exler) + - Added a few more Sony LensTypes (thanks Jos Roost) + - Decode some new Canon tags (thanks Mark Reid) + - Decode another Nikon Z9 tag (thanks Warren Hatch) + - Decode Nikon NKSC GPSImgDirection (thanks Olaf) + - Improved handling of empty XMP structures in lists + - Tolerate leading UTF-8 BOM in -geotag log files + - Updated photoshop_paths.config to include WorkingPath + - Patched to allow writing of MP4 videos which have url tracks with a missing + sample description entry + - Fixed deep recursion error when reading multi-page TIFF images with more + than 100 pages + - Fixed potential deep recursion runtime error when writing nested XMP + structures + - Fixed warning which could be generated when writing new + Composite:GPSCoordinates tag + - Fixed description of GPR (General Purpose RAW) file type + - Fixed typo in the name of a new Nikon tag (thanks Herb) + +Jan. 13, 2022 - Version 12.39 + + - Added a new Pentax LensType (thanks Christian Shulz) + - Added a couple of new Nikon LensID's + - Added support for Nikon NKSC sidecar files + - Decode another type of timed GPS from MP4 videos + - Decode more tags for the Nikon Z7 and Z9 (thanks Warren Hatch) + - Decode a couple more FLIR tags + - Extract ZIP file comments + - Made PNG ProfileName, SRGBRendering and Gamma writable + - Patched to avoid possible problem running "more" to show documentation in + Windows version + - Fixed problem writing Composite:GPSPosition with coordinates in DMS format, + and made this tag protected when writing + - Fixed bug where invalid date/time tags could be written to PNG files when + attempting to shift a non-existent date/time tag + - Fixed spelling of a few Matroska tag names (thanks Martin Hoppenheit) + +Dec. 20, 2021 - Version 12.38 + + - Decode a number of new tags for the Nikon Z9 (thanks Warren Hatch) + - Patched incorrect decoding of AEBShotCount for the Canon EOS 90D + - Patched EXR reader to support long tag names + - Patched security issue (thanks Joe Lothan) + - Fixed an incorrect tag ID for a new Nikon MakerNote tag (github #108) + - Fixed XMP-exif:GPSMeasureMode conversions to match EXIF + - Fixed problem where some namespaces may be undeclared in the -X output when + using the -struct option + +Dec. 8, 2021 - Version 12.37 + + - Decode timed GPS from Vantrue S1 dashcam MP4 videos + - Decode ColorData tags for the Canon EOS R3 (thanks LibRaw) + - Decode more makernotes tags for Nikon Z cameras (thanks Warren Hatch) + - Extract TransparentColor from GIF images + - Improved parsing of input time values for GPSTimeStamp to properly handle a + "." separator + - Improved warning when incorrectly using " + +Nov. 16, 2021 - Version 12.36 + + - IMPORTANT: Fixed bug introduced in 12.35 which corrupted JPEG 2000 images + when removing all metadata with -all= + - Added feature to bypass processing of specified XMP namespaces and + properties (to improve performance in cases where the XMP suffers from + Adobe-editing bloat) + - Added a number of new XMP tags used by Lightroom 11.0 + - Decode a number of new Nikon tags (thanks Warren Hatch) + - Made the Composite GPSPosition tag writable + - Fixed erroneous "Skipped unknown bytes after JPEG SOS" warning + - Fixed group for new writable Jpeg2000 color tags in -listx output + - Fixed problem finding files in Windows when using wildcards in file name and + a drive letter with no slash + +Nov. 11, 2021 - Version 12.35 + + - Added ability to write ICC_Profile (and other color specifications) to + Jpeg2000 images + - Added %o code to -W option format string + - Added %f code to -d option for fractional seconds + - Added a couple of new Sony LensType values (thanks Jos Roost) + - Added a new CanonModelID (thanks Norbert Wasser) + - Added a new Nikon LensID + - Decode more Nikon MakerNotes tags for some new models (thanks Warren Hatch) + - Extract ThumbnailImage from some DJI drone videos + - Enhanced -ee option to extract metadata from all frames in a SEQ file + - Patched to avoid possible "Use of uninitialized value" runtime warning + - Fixed a couple of misspelt new ICC_Profile tag names (thanks Herb) + - Fixed problem generating the correct file extension when extracting + OriginalRawImage from a DNG file using the -W option with the %s format code + - Fixed bug introduced in 11.91 where exiftool couldn't find its libraries + when run via a soft link. Also changed to look for config file in the link + target directory instead of the directory of the link itself + +Oct. 27, 2021 - Version 12.34 + + - Added support for ICC.2:2019 (Profile version 5.0.0 - iccMAX) color profiles + - Added ability to detect/delete a Windows Zone.Identifier alternate data + stream (ADS) via the new ZoneIdentifier tag (thanks Alex Xu) + - Added support for the Sony ILCE-7M4 (thanks Jos Roost) + - Added a new Sony lens (thanks LibRaw and Jos Roost) + - Added a new SonyModelID (thanks LibRaw) + - Added a new Canon RF lens (thanks Norbert Wasser) + - Improved handling of some SVG files + - Patched -overwrite_original_in_place option to open the output file in + update mode rather than write mode (to allow some write optimizations on + certain filesystems) (thanks Joel Low) + - Fixed case of tag ID for new XMP-iptcExt:EventID (thanks Michael Steidl) + - Fixed problem extracting ICC_Profile information from some PDF files + - API Changes: + - Added QuickTimePad option + +Oct. 16, 2021 - Version 12.33 + + - Added support for DNG version 1.6.0.0 + - Added two new Sony LensType values (thanks Jos Roost and LibRaw) + - Added some new elements to the XMP-crs:Look structure (thanks Herb) + - Added a few new IPTC XMP tags (thanks Michael Steidl) + - Added a new Canon RF lens (thanks Norbert Wasser) + - Decode Canon ShutterMode (thanks John Moyer) + - Extract LensModel from some Olympus MOV videos + - Generate MediaDataOffset/Size for MOV videos with zero-sized mdat chunk + - Improvements to CBOR reader, including hex dump with -v3 option + - Recognize Final Cut Pro XML files + - Allow binary data of Protected tags to be extracted with the -X -j and -php + options with -b by setting the API RequestAll option to 3 + - Changed name of "Canon EF 80-200mm f/4.5-5.6" lens with LensType 38 to add + "II" to the name (Exiv2 issue 1906) + - Fixed runtime warning when processing files with a .DIR extension + +Sept. 30, 2021 - Version 12.32 + + - Added support for CBOR-format metadata in JUMBF (note that JUMBF support is + still experimental) + - Added a new Nikon LensID + - Added a new Pentax LensType + - Decode timed GPS for two more dashcam formats + - Support reference direction columns in -geotag CSV input + - Removed generation of GPSSpeedRef and GPSTrackRef tags in timed metadata for + most dashcam formats when speed is km/h and track is relative to true north + - Patched to allow writing of console output to named pipes + - Fixed formatting of InternalSerialNumber for some Panasonic cameras + - Fixed bug in arg_files/xmp2exif.args support file + +Sept. 22, 2021 - Version 12.31 + + - Added a new SonyModelID and a couple of new Sony lenses (thanks Jos Roost) + - Added a new Canon LensType (thanks Chris Skopec) + - Added Composite GPSLatitude/Longitude tags for Sony videos to combine the + reference hemispheres as with the Composite tags for EXIF GPS + - Decode DPX AspectRatio + - Decode more GoPro MP4 tags + - Extract ICC_Profile from CS0 object in PDF files + - Extract encrypted GPS from Akaso V1 dashcam videos (can't yet decrypt) + - Improved handling of QuickTime iTunesInfo tags, and created new "iTunes" + family 1 group for these + - Patched so NoPDFList option also applies when writing + - Patched to allow user-defined PNG TextualData tags to be written only as iTXt + - Patched PDF reader to avoid concatenating values of multiple List-type tags + into a single tag + +Aug. 12, 2021 - Version 12.30 (production release) + + - Added read support for Portable FloatMap (PFM) images (this was a bit of a + pain because they have the same file extension as Printer Font Metrix files) + - Added a few new Nikon LensID values (thanks LibRaw) + - Added a new Canon LensType + - Added a new Olympus CameraType (thanks LibRaw) + - Added minor warning about unknown data between JPEG segments + - Added a couple of new NikonSettings tags (thanks Warren Hatch) + - Added a new Sony LensType (thanks Jos Roost) + - Decode 'id3 ' chunk in WAV audio files + - Decode timed GPS from concatenated Garmin dashcam videos + - Decode SamsungTrailer information from sefd atom in HEIC images + - Decode more Sony MakerNote tags for the ZV-E10 (thanks Jos Roost) + - Decode DepthMapTiff from JPEG images of more Samsung models + - Decode timed GPS from M2TS videos of yet another type of dashcam + - Extract PreviewImage from Xaiomi MP4 videos + - Changed name of second EmbeddedImage in Samsung trailer to EmbeddedImage2 + - Improved Dutch translations for GPS tags (thanks Peter Dubbelman) + - Allow ICC_Profile to be "deleted" from AVIF files (actually, the profile + isn't really deleted. Instead, a zero-length profile is written to allow a + profile to be added back later since QuickTime item property containers + currently can't be created) + - Patched to remove 2 GB size limit when reading Photoshop ImageSourceData + +July 9, 2021 - Version 12.29 + + - Added a few new Nikon and Olympus lenses (thanks LibRaw) + - Improved a QuickTime "File format error" message to be more meaningful, and + made it a minor error + - Changed PNG writer to add EXIF before IDAT + - Some changes the way JUMBF metadata is handled + - Patched to read timed GPS from a different type of INSV videos + - Patched a security issue + - Fixed problem where ExifTool could hang when processing mebx timed metadata + +June 22, 2021 - Version 12.28 + + - Added read support for Leica Image File (LIF) images + - Added a new Olympus LensType (thanks LibRaw) + - Decode another Panasonic tag (thanks LibRaw) + - Decode more timed metadata from Sony MP4 videos + - Attempt to shorten tag names for metadata in CZI files + - Allow full QuickTime Keys tag ID's in UserDefined tags (fixes backward + incompatibility introduced in 12.02) + - Patched to handle special characters in Torrent tag values + +June 9, 2021 - Version 12.27 + + - Added a new SonyModelID value + - Added two new Nikon LensID values (thanks Daniel) + - Added a new Pentax RawDevelopmentProcess value + - Added a few new Sony LensType values (thanks Jos Roost) + - Added warning if IPTCDigest is not current + - Decode a couple more Pentax tags (thanks LibRaw) + - Decode streaming GPS from Novatek INNOVV MP4 and TS videos + - Improved tag names in config_files/covert_regions.config (thanks StarGeek) + - Changed MIME types for MS Office macro-enabled formats to add the .12 + - Patched Canon LensID logic to properly identify the Canon RF 24-105mm F4 L + IS USM lens + - Patched decoding of camm6 GPSDateTime to use a flexible epoch because other + apps don't seem to use a consistent time zero + - Fixed family 7 group names for QuickTime Keys tags + - Fixed problem reading BeatsPerMinute from some MP4 files + - Fixed hemisphere problem when extracting GPS from 70mai dashcam videos + +May 20, 2021 - Version 12.26 (production release) + + - Added support for JPEG Stereo (JPS) images + - Added a new Sony LensType (thanks LibRaw) + - Added a new PentaxModelID (thanks LibRaw) + - Changed ExifTool namespace URI to use exiftool.org instead of exiftool.ca in + the -X option output (exiftool.ca is still recognized when reading XML) + - Improved handling of large-array warnings in -htmldump output + - Changed handling of escaped characters in #[CSTR] lines of -@ argfile + - Patched security vulnerability in argument of -lang option + - Fixed problem which could cause a "Wide character" warning and generate a + corrupted output file when writing some illegal values + +Apr. 22, 2021 - Version 12.25 + + - JPEG XL support is now official + - Added read support for Medical Research Council (MRC) image files + - Added ability to write a number of 3gp tags in video files + - Added a new Sony PictureProfile value (thanks Jos Roost) + - Added a new Sony LensType (thanks LibRaw) + - Added a new Nikon LensID (thanks Niels Kristian Bech Jensen) + - Added a new Canon LensType + - Decode more GPS information from Blackvue dashcam videos + - Decode a couple of new NikonSettings tags (thanks Warren Hatch) + - Decode a few new RIFF tags + - Improved Validate option to add minor warning if standard XMP is missing + xpacket wrapper + - Avoid decoding some large arrays in DNG images to improve performance unless + the -m option is used + - Patched bug that could give runtime warning when trying to write an empty + XMP structure + - Fixed decoding of ImageWidth/Height for JPEG XL images + - Fixed problem were Microsoft Xtra tags couldn't be deleted + +Apr. 13, 2021 - Version 12.24 + + - Added a new PhaseOne RawFormat value (thanks LibRaw) + - Decode a new Sony tag (thanks Jos Roost) + - Decode a few new Panasonic and FujiFilm tags (thanks LibRaw and Greybeard) + - Updated acdsee.config in distribution (thanks StarGeek) + - Recognize AutoCAD DXF files + - More work on experimental JUMBF read support + - More work on experimental JPEG XL read/write support + - Patched security vulnerability in DjVu reader + +Apr. 1, 2021 - Version 12.23 + + - Added support for Olympus ORI files + - Added experimental read/write support for JPEG XL images + - Added experimental read support for JUMBF metadata in JPEG and Jpeg2000 + images + - Added built-in support for parsing GPS track from Denver ACG-8050 videos + with the -ee option + - Added a some new Sony lenses (thanks Jos Roost and LibRaw) + - Changed priority of Samsung trailer tags so the first DepthMapImage takes + precedence when -a is not used + - Improved identification of M4A audio files + - Patched to avoid escaping ',' in "Binary data" message when -struct is used + - Removed Unknown flag from MXF VideoCodingSchemeID tag + - Fixed -forcewrite=EXIF to apply to EXIF in binary header of EPS files + - API Changes: + - Added BlockExtract option + +Mar. 17, 2021 - Version 12.22 + + - Added a few new Sony LensTypes and a new SonyModelID (thanks Jos Roost and + LibRaw) + - Added Extra BaseName tag + - Added a new CanonModelID (thanks LibRaw) + - Decode timed GPS from unlisted programs in M2TS videos with the -ee3 option + - Decode more Sony rtmd tags + - Decode some tags for the Sony ILME-FX3 (thanks Jos Roost) + - Allow negative values to be written to XMP-aux:LensID + - Recognize HEVC video program in M2TS files + - Enhanced -b option so --b suppresses tags with binary data + - Improved flexibility when writing GPS coordinates: + - Now pulls latitude and longitude from a combined GPSCoordinates string + - Recognize full word "South" and "West" to write negative coordinates + - Improved warning when trying to write an integer QuickTime date/time tag and + Time::Local is not available + - Convert GPSSpeed from mph to km/h in timed GPS from Garmin MP4 videos + +Feb. 24, 2021 - Version 12.21 + + - Added a few new iOS QuickTime tags + - Decode a couple more Sony rtmd tags + - Patch to avoid possible "Use of uninitialized value" warning when attempting + to write QuickTime date/time tags with an invalid value + - Fixed problem writing Microsoft Xtra tags + - Fixed Windows daylight savings time patch for file times that was broken in + 12.19 (however directory times will not yet handle DST properly) + +Feb. 23, 2021 - Version 12.20 + + - Added ability to write some Microsoft Xtra tags in MOV/MP4 videos + - Added two new Canon LensType values (thanks Norbert Wasser) + - Added a new Nikon LensID + - Fixed problem reading FITS comments that start before column 11 + +Feb. 18, 2021 - Version 12.19 + + - Added -list_dir option + - Added the "ls-l" Shortcut tag + - Extract Comment and History from FITS files + - Enhanced FilePermissions to include device type (similar to "ls -l") + - Changed the name of Apple ContentIdentifier tag to MediaGroupUUID + (thanks Neal Krawetz) + - Fixed a potential "substr outside of string" runtime error when reading + corrupted EXIF + - Fixed edge case where NikonScanIFD may not be copied properly when copying + MakerNotes to another file + - API Changes: + - Added ability to read/write System tags of directories + - Enhanced GetAllGroups() to support family 7 and take optional ExifTool + reference + - Changed QuickTimeHandler option default to 1 + +Feb. 9, 2021 - Version 12.18 + + - Added a new SonyModelID + - Decode a number of Sony tags for the ILCE-1 (thanks Jos Roost) + - Decode a couple of new Canon tags (thanks LibRaw) + - Patched to read differently formatted UserData:Keywords as written by iPhone + - Patched to tolerate out-of-order Nikon MakerNote IFD entries when obtaining + tags necessary for decryption + - Fixed a few possible Condition warnings for some NikonSettings tags + +Feb. 3, 2021 - Version 12.17 + + - Added a new Canon FocusMode value + - Added a new FujiFilm FilmMode value + - Added a number of new XMP-crs tags (thanks Herb) + - Decode a new H264 MDPM tag + - Allow non-conforming lower-case XMP boolean "true" and "false" values to be + written, but only when print conversion is disabled + - Improved Validate option to warn about non-capitalized boolean XMP values + - Improved logic for setting GPSLatitude/LongitudeRef values when writing + - Changed -json and -php options so the -a option is implied even without the + -g option + - Avoid extracting audio/video data from AVI videos when -ee -u is used + - Patched decoding of Canon ContinuousShootingSpeed for newer firmware + versions of the EOS-1DXmkIII + - Re-worked LensID patch of version 12.00 (github issue #51) + - Fixed a few typos in newly-added NikonSettings tags (thanks Herb) + - Fixed problem where group could not be specified for PNG-pHYs tags when + writing + +Jan. 21, 2021 - Version 12.16 (production release) + + - Extract another form of video subtitle text + - Enhanced -ee option with -ee2 and -ee3 to allow parsing of the H264 video + stream in MP4 files + - Changed a Nikon FlashMode value + - Fixed problem that caused a failed DPX test on Strawberry Perl + - API Changes: + - Enhanced ExtractEmbedded option + +Jan. 18, 2021 - Version 12.15 (production release) + + - Added a couple of new Sony LensType values (thanks LibRaw and Jos Roost) + - Added a new Nikon FlashMode value (thanks Mike) + - Decode NikonSettings (thanks Warren Hatch) + - Decode thermal information from DJI RJPEG images + - Fixed extra newline in -echo3 and -echo4 outputs added in version 12.10 + - Fixed out-of-memory problem when writing some very large PNG files under + Windows + +Jan. 6, 2021 - Version 12.14 + + - Added support for 2 more types of timed GPS in video files (that makes 49 + different formats now supported) + - Added validity check for PDF trailer dictionary Size + - Added a new Pentax LensType + - Extract metadata from Jpeg2000 Association box + - Changed -g:XX:YY and -G:XX:YY options to show empty strings for non-existent + groups + - Patched to issue warning and avoid writing date/time values with a zero + month or day number + - Patched to avoid runtime warnings if trying to set FileName to an empty + string + - Fixed issue that could cause GPS test number 12 to fail on some systems + - Fixed problem extracting XML as a block from Jpeg2000 images, and extract + XML tags in the XML group instead of XMP + +Dec. 24, 2020 - Version 12.13 + + - Added -i HIDDEN option to ignore files with names that start with "." + - Added a few new Nikon ShutterMode values (thanks Jan Skoda) + - Added ability to write Google GCamera MicroVideo XMP tags + - Add time zone automatically to most string-based QuickTime date/time tags + when writing unless the PrintConv option is disabled + - Decode a new Sony tag (thanks LibRaw) + - Changed behaviour when writing only pseudo tags to return an error and avoid + writing any other tags if writing FileName fails + - Print "X image files read" message even if only 1 file is read when at least + one other file has failed the -if condition + +Dec. 4, 2020 - Version 12.12 + + - Added ability to geotag from DJI CSV log files + - Added a new CanonModelID + - Added a couple of new Sony LensType values (thanks LibRaw) + - Enhanced -csvDelim option to allow "\t", "\n", "\r" and "\\" + - Unescape "\b" and "\f" in imported JSON values + - Fixed bug introduced in 12.10 which generated a "Not an integer" warning + when attempting to shift some QuickTime date/time tags + - Fixed shared-write permission problem with -@ argfile when using -stay_open + and a filename containing special characters on Windows + +Nov. 27, 2020 - Version 12.11 + + - Added -csvDelim option + - Added new Canon and Olympus LensType values (thanks LibRaw) + - Added a warning if ICC_Profile is deleted from an image (github issue #63) + - EndDir() function for -if option now works when -fileOrder is used + - Changed FileSize conversion to use binary prefixes since that is how the + conversion is currently done (eg. MiB instead of MB) + - Patched -csv option so columns aren't resorted when using -G option and one + of the tags is missing from a file + - Fixed incompatiblity with Google Photos when writing UserData:GPSCoordinates + to MP4 videos + - Fixed problem where the tags available in a -p format string were limited to + the same as the -if[NUM] option when NUM was specified + - Fixed incorrect decoding of SourceFileIndex/SourceDirectoryIndex for Ricoh + models + +Nov. 12, 2020 - Version 12.10 + + - Added -validate test for proper TIFF magic number in JPEG EXIF header + - Added support for Nikon Z7 LensData version 0801 + - Added a new XMP-GPano tag + - Decode ColorData for the Canon EOS 1DXmkIII (thanks LibRaw) + - Decode more tags for the Sony ILCE-7SM3 (thanks Jos Roost) + - Automatically apply QuickTimeUTC option for CR3 files + - Improved decoding of XAttrMDLabel from MacOS files + - Ignore time zones when writing date/time values and using the -d option + - Enhanced -echo3 and -echo4 options to allow exit status to be returned + - Changed -execute so the -q option no longer suppresses the "{ready}" message + when a synchronization number is used (eg. -execute123) + +Oct. 29, 2020 - Version 12.09 + + - Added ability to copy CanonMakerNotes from CR3 images to other file types + - Added read support for ON1 presets file (.ONP) + - Added two new CanonModelID values + - Added trailing "/" when writing QuickTime:GPSCoordinates + - Added a number of new XMP-crs tags + - Added a new Sony LensType (thanks Jos Roost) + - Added a new Nikon Z lens (thanks LibRaw) + - Added a new Canon LensType + - Decode ColorData for Canon EOS R5/R6 + - Decode a couple of new HEIF tags + - Decode FirmwareVersion for Canon M50 + - Improved decoding of Sony CreativeStyle tags (thanks Jos Roost) + - Improved parsing of Radiance files to recognize comments + - Renamed GIF AspectRatio tag to PixelAspectRatio + - Patched EndDir() feature so subdirectories are always processed when -r is + used (previously, EndDir() would end processing of a directory completely) + - Yet another tweak to the EventTime formatting rules (also allow time-only + values with fractional seconds and a time zone) + - Avoid loading GoPro module unnecessarily when reading MP4 videos from some + other cameras + - Fixed problem with an incorrect naming of CodecID tags in some MKV videos + - Fixed verbose output to avoid "adding" messages for existing flattened XMP + tags + +Oct. 15, 2020 - Version 12.08 + + - Added read support for MacOS "._" sidecar files + - Added a new Sony LensType (thanks Jos Roost) + - Recognize Mac OS X xattr files + - Extract ThumbnailImage from MP4 videos of more dashcam models + - Improved decoding of a number of Sony tags (thanks Jos Roost) + - Fixed problem where the special -if EndDir() function didn't work properly + for directories after the one in which it was initially called + - Patched to read DLL files which don't have a .rsrc section (thanks Hank) + - Patched to support new IGC date format when geotagging + - Patched to read DLL files with an invalid size in the header + +Oct. 2, 2020 - Version 12.07 + + - Added support for GoPro .360 videos + - Added some new Canon RF and Nikkor Z lenses (thanks LibRaw) + - Added some new Sony LensType and CreativeStyle values and decode some + ILCE-7C tags (thanks Jos Roost) + - Added a number of new Olympus SceneMode values (thanks Herb) + - Added a new Nikon LensID + - Decode more timed metadata from Insta360 videos (thanks Thomas Allen) + - Decode timed GPS from videos of more Garmin dashcam models + - Decode a new GoPro video tag + - Reformat time-only EventTime values when writing and prevent arbitrary + strings from being written + - Patched to accept backslashes in SourceFile entries for -csv option + +Sept. 11, 2020 - Version 12.06 + + - Added read support for Lyrics3 metadata (and fixed problem where APE + metadata may be ignored if Lyrics3 exists) + - Added a new Panasonic VideoBurstMode value (thanks Klaus Homeister) + - Added a new Olympus MultipleExposureMode value + - Added a new Nikon LensID + - Added back conversions for XMP-dwc EventTime that were removed in 12.04 with + a patch to allow time-only values + - Decode GIF AspectRatio + - Decode Olympus FocusBracketStepSize (thanks Karsten) + - Extract PNG iDOT chunk in Binary format with the name AppleDataOffsets + - Process PNG images which do not start with mandatory IHDR chunk + +Aug. 24, 2020 - Version 12.05 + + - Added a new Panasonic SelfTimer value (thanks Herb) + - Decode a few more DPX tags (thanks Harry Mallon) + - Extract AIFF APPL tag as ApplicationData + - Fixed bug writing QuickTime ItemList 'gnre' Genre values + - Fixed an incorrect value for Panasonic VideoBurstResolution (thanks Herb) + - Fixed problem when applying a time shift to some invalid makernote date/time + values + +Aug. 10, 2020 - Version 12.04 + + - Added read support for Zeiss ZISRAW CZI files + - Added some new values for a couple of Olympus tags (thanks Sebastian) + - Decode a number of new tags for the Sony ILCE-7SM3 (thanks Jos Roost) + - Removed formatting restrictions on XMP-dwc:EventTime to allow a time-only + value to be written + - Moved new QuckTime ItemList tags added in version 12.02 to the proper group + (they were incorrectly added to the Keys group) + - Improved QuickTime -v3 output to show default language codes + - Patched -lang option to work for the values of some tags with coded + translations + - Patched the format of a number of QuickTime tags when writing for improved + compatibility with iTunes and AtomicParsley + - Patched to write a default QuickTime language code of 0x0000 (null) instead + of 0x55c4 ('und') + +July 29, 2020 - Version 12.03 + + - Added family 7 group names to allow tag ID's to be specified when reading + and writing + - Fixed a couple of typos in tag values (thanks Herb) + - API Changes: + - Added HexTagIDs option + - Enhanced GetNewValue() to allow family 7 groups names to be used + - Internal Changes: + - Changed Composite tag ID's to use "-" instead of "::" as a separator + +July 27, 2020 - Version 12.02 + + - Added support for a number of new QuickTime ItemList tags + - Added support for writing XMP-xmp:RatingPercent + - Added a new Sony LensType (thanks Jos Roost and LibRaw) + - Added a new Pentax LensType (thanks James O'Neill) + - Decode barcodes from Ricoh APP5 RMETA segment + - Decode a few new QuickTime tags written by Ricoh and Garmin cameras + - Decode timed GPS from Sony A7R IV MP4 videos + - Decode timed GPS from 70mai dashcam videos + - Decode a few new Panasonic tags (thanks Klaus Homeister) + - Decode altitude and more accurate latitude/longitude from Transcend Driver + Pro 230 MP4 videos + - Improved decoding of some Canon EOS 1DXmkIII custom functions + - Allow integer QuickTime TrackNumber and DiskNumber values + - Relax validity check of QuickTime:ContentCreateDate when writing with -n + - Removed "Com" from the start of some unknown QuickTime ItemList tag names + - Patched CanonCustom decoding for bug in Canon EOS-1DX firmware + - Changed QuickTime CleanAperture tags decode as signed rationals + +June 24, 2020 - Version 12.01 + + - Added a new NEFCompression value (thanks Warren Hatch) + - Added a new Sony LensType (thanks Jos Roost) + - Decode timed GPS from Rove Stealth 4K dashcam videos + - Fixed bug which would corrupt TIFF images with 16-bit image data offsets + when writing (these are very rare) + +June 11, 2020 - Version 12.00 (production release) + + - Added two new Olympus LensTypes (thanks Don Komarechka for one) + - Added two new Sony LensType values (thanks Jos Roost) + - Added a few new Nikon LensID's (thanks Mathieu Carbou) + - Added support for the Sony ZV-1 (thanks Jos Roost) + - Added a new CanonModelID (thanks Jos Roost) + - Added missing MimeType values for HEICS and HEIFS files + - Added definitions for a number of new XMP-crs tags + - Recognize WOFF and WOFF2 font files + - Decode streaming GPS from Roadhawk, EEEkit and 360Fly MP4 videos + - Decode a number of new tags for the Nikon D6 (thanks Warren Hatch) + - Decode a couple more AF tags for the D500/D850 + - Decode a number of new Panasonic tags + - Improved Composite LensID logic (thanks Matt Stancliff) + - Enhanced -v option to state when a directory has 0 entries + - Removed a couple of incorrect Validate warnings for bilevel TIFF images + - Drop ContrastCurve tag when copying from NEF to JPEG + - Changed -csv output to add "Unknown" group name to column headings for + missing tags when -f and -G options are used + - Patched to support new XMP LensID format for Nikon cameras as written by + Apple Photos (thanks Mattsta) + - Fixed problem extracting metadata from Sigma DP2 Quattro X3F files + - Fixed End() and EndDir() functions so they work when writing and when the -v + option is used + - Fixed problem recognizing some PGM files + - Fixed bug in offsets for some Photoshop information in -v3 output + - Fixed problem writing a list containing empty elements inside an XMP + structure + - API Changes: + - Added NoMultiExif option + - Changed FilterW option to not write tag if $_ is set to undef + +May 11, 2020 - Version 11.99 + + - Added a new Nikon LensID (thanks Mykyta Kozlov) + - Added a new Canon LensType + - Added a newn PentaxModelID + - Decode a few new QuickTime tags + - Decode new ID3 Grouping tag + - Decode a few more MinoltaRaw tags (thanks LibRaw) + - Fixed runtime warning which could occur when reading corrupted RTF files + - Fixed another potential pitfall in M2TS Duration calculation + - Fixed problem extracting some unknown QuickTime:Keys tags + - Fixed problem decoding Nikon D850 orientation tags + - Fixed bug where TIFF image data may not be padded to an even number of bytes + +May 1, 2020 - Version 11.98 + + - Added a new Nikon LensID (thanks Warren Hatch) + - Added a new Sony LensType (thanks LibRaw) + - Added a new Canon LensType + - Patched to extract EXIF with an "Exif\0\0" header from WebP images + - Enhanced -efile option and added to the documentation + - Minor tweak to -htmlDump output (disallow locking of empty selection) + - Fixed problem determining Duration of some M2TS videos + +Apr. 27, 2020 - Version 11.97 + + - Added experimental -efile option (undocumented) + - Decode NMEA GGA sentence from streaming GPS of some dashcam videos + +Apr. 24, 2020 - Version 11.96 + + - Decode streaming GPS from Lucas LK-7900 Ace AVI videos + - Changed new Exit/ExitDir function names to End/EndDir + - Fixed inconsistencies when using "-use mwg" together with the -wm option + +Apr. 23, 2020 - Version 11.95 + + - Added Exit() and ExitDir() functions for use in -if conditions (NOTE: these + function names changed to End() and EndDir() in ExifTool 11.96) + - Enhanced -geotag feature to support a more flexible input CSV file format + - Enhanced -if and API Filter options to allow access to ExifTool object via + $self + - Fixed problem reading HEIC Exif with a missing header + +Apr. 17, 2020 - Version 11.94 + + - Added support for QuickTime ItemList:GPSCoordinates + - Added additional Validate test for overlapping EXIF values + - Added a new Sony LensType (thanks Jos Roost) + - Added a new Nikon LensID + - Decode a few more Nikon tags (thanks Warren Hatch) + - Decode Pentax ShutterType + - Changed color of locked highlighted selection in -htmlDump output + - Fixed problem reading PDF files written by Microsoft Print-to-PDF + - Fixed problem where -X output would produce invalid XML for MP4 files + containing an HTCTrack + +Apr. 3, 2020 - Version 11.93 + + - Added new config file to the distribution for writing Pix4D XMP-Camera tags + (config_files/pix4d.config) + - Added support for the DOSCyrillic (cp866) character set + - Added IO::String to the Windows EXE version + - Improved identification of Canon RF lenses (thanks LibRaw) + - Enhanced -htmlDump output to add "File offset" entry for EXIF tags and + ability lock highlighted selection by clicking the mouse + - Enhanced -srcfile option to generate OriginalFileName and OriginalDirectory + UserParam tags + - Patched HEIC writer to add missing pitm box if necessary + - Fixed problem adding back EXIF after deleting it from HEIC file + - Fixed minor problem with incorrect number of bytes being reported for + invalid header in corrupt files + - API Changes: + - Enhanced UserParam option to allow parameters to be extracted as if + they were normal tags + +Mar. 19, 2020 - Version 11.92 + + - Added a new Nikon LensID (thanks Wolfgang Exler) + - Decode a few new Leica tags (thanks Tim Gray) + - Decode AccelerometerData from Samsung Gear 360 videos + - Fixed a couple of problems decoding timed GPS metadata from NextBase dashcam + videos + - Fixed problem where -X option could produce invalid XML when reading + corrupted XMP + +Mar. 5, 2020 - Version 11.91 + + - Added undocumented -xpath option for use by alternate Windows version + - Decode a couple of new Panasonic tags + - Documented -ec option (available since version 11.54) + - Reverted -htmlDump fix of 11.90 because it broke more than it fixed, and + instead applied a targeted patch to fix this problem for RW2 files + +Mar. 3, 2020 - Version 11.90 + + - Added a new Sony LensType (thanks LibRaw and Jos Roost) + - Added two new Olympus LensType values + - Added a new Canon LensType + - Added some new Canon RecordMode values + - Added some new QuickTime GeneralProfileIDC values + - Added new values for a couple of FujiFilm tags + - Added a number of new QuickTime GenreID values + - Decode Nikon Z6/Z7 phase-detect AF points (thanks Andy Dragon) + - Patched to avoid possible "Undefined subroutine" error in MacOS 10.15 + - Fixed incorrect offsets in -htmlDump output for some file types + +Feb. 25, 2020 - Version 11.89 + + - Added support for Exif 2.32 for XMP + - Recognize the HIF file extension + - Improved verbose output for QuickTime iref items + - Patched to create new GPS metadata in Canon CR3 images using a default byte + order that is the same as existing EXIF boxes + - Patched to add missing newline that could occur in XMP with the API Compact + Shorthand option + +Feb. 20, 2020 - Version 11.88 + + - Added write support for new Google depth-map XMP tags + - Added config_files/depthmap.config to the distribution + - Added minor error when attempting to write FFF images due to incompatibility + with Hasselblad Phocus software + - Patched to avoid "Invalid iloc offset size" error when writing some + QuickTime-based files + - Fixed incorrect ColumnCount for CSV files + - Fixed various spelling errors (thanks Jens Schleusener) + - Fixed bug writing QuickTime:Rotation in HEIC files + +Feb. 13, 2020 - Version 11.87 + + - Added read support for CSV files + - Added "--" option to indicate the end of options + - Added ability to read/write/copy/delete the JPEG trailer as a block + - Added new Olympus CameraType and LensType values (thanks LibRaw) + - Decode a few more FujiFilm tags + - Enhanced -fast option (API FastScan) to bypass PNG CRC validation when + writing + +Feb. 4, 2020 - Version 11.86 + + - Added support for DNG version 1.5 + - Added config_files/acdsee.config to the full distribution (thanks StarGeek) + - Added a new Sony LensType (thanks Jos Roost and LibRaw) + - Decode two more bits from Nikon LensType (thanks LibRaw) + - Decode QuickTime MovieFragmentSequence + - Patched HEIC writer to add missing iref box if necessary + - Fixed typo in a Canon LensType value + - API Changes: + - Patched ImageInfo() to recognize a stringified object as a file name + +Jan. 28, 2020 - Version 11.85 (production release) + + - Added a new Sony LensType (thanks Jos Roost) + - Added a new Olympus CameraType (thanks LibRaw) + - Added a two new Pentax LensType values + - Added a new FujiFilm FocusMode + - Decode timed GPS from Akaso dashcam MOV videos + - Decode Insta360 trailer from INSP images and made Insta360 a deletable group + - Patched kml.fmt file to limit maximum image size (thanks Fedor Kotov) + - Fixed problem decoding values from Leica M10 and S maker notes + +Jan. 10, 2020 - Version 11.84 + + - Decode accelerometer data from timed metadata of more dashcam videos + - Decode Canon G9 white balance tags (thanks LibRaw) + - Recognize INSP files + +Jan. 9, 2020 - Version 11.83 + + - Added a couple of new XMP-crs tags (thanks Herb) + - Fixed bug introduced in 11.82 with the -php -D output + - Fixed problem where some flattened XMP tags could be written when they + should be avoided + +Jan. 8, 2020 - Version 11.82 + + - Added a new Canon LensType + - Added a new CanonModelID (thanks LibRaw) + - Added ability to process SubDirectories in QuickTime Keys tags + - Removed minor error when writing PDF 2.0 files (github issue #30) + - Fixed problem where trailing null bytes were removed from binary values in + the -php output when the -b option was used + +Jan. 2, 2020 - Version 11.81 + + - Added a new Nikon LensID + - Added two new CanonModelID's (thanks LibRaw) + - Decode AVIF AV1 configuration record + - Changed names of QuickTime MovieData tags to "MediaData" + - Patched to use 4-digit years in Time::Local calls + - Patched Composite sub-second date/time tags to do additional validation of + source EXIF date/time tags before adding sub seconds + - Fixed problem where -json output could produce invalid JSON when -struct was + used and the structure field names contained special characters (github + issue #32) + - Fixed spelling in a Panasonic SceneMode value (thanks Hubert) + +Dec. 17, 2019 - Version 11.80 + + - Added a new Canon LensType + - Added a new Nikon Z LensID (thanks LibRaw) + - Added a few new Sony LensType values (thanks Jos Roost) + - Attempt to improve reliability of Samsung DepthMapWidth/Height decoding + - Updated a number of Canon-mount Tamron lens names to include the Tamron + model number + - Patched MOV/MP4 writer to allow a small amount of garbage at the end of a + file to be deleted when writing with the -m option + - Fixed bug where some Composite tags may not have taken priority over other + tags as they should have + +Dec. 12, 2019 - Version 11.79 + + - Added support for AVIF files + - Added new Canon, Sigma and Sony LensType values (thanks LibRaw) + - Made PDF 2.0 writable at your own risk with the -m option (github issue #30) + - Enhanced -validate feature to warn about duplicate languages in an XMP + lang-alt list + - Fixed inconsistency between documentation and ExifTool capabilities for + "Writable" status of some tags + +Dec. 5, 2019 - Version 11.78 + + - Added a new Nikon LensID (thanks Chris) + - Added two new FujiFilm SceneRecognition values + - Patched to avoid crash in Windows when writing a negative epoch time using + the "-d %s" option + - Fixed problem editing MIE tags when using the "-wm w" option + +Nov. 27, 2019 - Version 11.77 + + - Added a new Nikon LensID (thanks Joe Schonberg) + - Added a number of new Olympus LensType values (thanks LibRaw) + - Added a new Canon LensType + - Decode timed GPS from Ambarella A12 dash cam MP4 videos + - Decode a number of new Sigma tags (thanks LibRaw) + - Decode a couple of new PanasonicRaw tags (thanks LibRaw) + - Enhanced -fileOrder option to add -fast feature + +Nov. 12, 2019 - Version 11.76 + + - Added support for the Sony ILCE-9M2 (thanks Jos Roost) + - Added a couple of new XMP-GCamera tags + - Added MIMEType values for some formats that previously reported + "application/unknown" + - Enhanced -geotag feature to write pitch to CameraElevationAngle if available + - Improved determination of MIMEEncoding for TXT files + +Nov. 4, 2019 - Version 11.75 + + - Added ability to read some basic characteristics of TXT files + - Added kml_track.fmt to the fmt_files of the full distribution + - Added built-in support for decoding GPS from the four video subtitle text + formats that were previously handled by separate config files, and removed + these config files from the distribution + - Derive GPSDateTime from CreateDate and SampleTime if not already available + when extracting timed GPS metadata from QuickTime-format videos + - Changed family 2 groups of some Extra tags + +Oct. 29, 2019 - Version 11.74 + + - Added support for new XMP IPTC Extension version 1.5 tags + - Added a new Nikon LensID (thanks LibRaw) + - Decode GPS track from Auto-Vox dashcam MOV videos + - Improved Russian translations (thanks Andrei Korzhyts and Alexander) + - Enhanced convert_regions.config to support new IPTC Extension 1.5 ImageRegion + - Changed the way the FlatName element works when used in a structure element + (the structure name is now added as a prefix to the flattened tag name) + - Patched gpx.fmt and gpx_wpt.fmt to support sub-seconds in GPSDateTime value + +Oct. 23, 2019 - Version 11.73 + + - Decode timed metadata from Parrot drone videos + - Patched dji.config file to properly handle time zones + - Fixed bug which caused runtime error when reading timed metadata from Cobra + Dash Cam AVI videos + +Oct. 22, 2019 - Version 11.72 + + - Added warning messages for corrupted Photoshop document data + - Added a new Olympus CameraType + - Added a new Canon LensType + - Decode more Sigma tags + - Improved Russian translations (thanks Alexander) + - Updated decoding of some CanonCustom settings for recent models + - Documented DNG OpcodeList values + +Oct. 16, 2019 - Version 11.71 + + - Added a new Sony LensType (thanks Jos Roost) + - Added a few new Nikon Z LensID's + - Added a simple print conversion for DNG OpcodeList tags (note that due to + this, these tags must now be copied using the -n option) + - Fixed problems determining some video parameters for DV files + - Changed behaviour of -sep option when writing empty list items + - API Changes: + - Changed ListSplit option to preserve empty list items + +Oct. 10, 2019 - Version 11.70 (production release) + + - Added a new CanonModelID (thanks Laurent Clevy) + - Improved identification of Office Open XML files (github issue #27) + - Removed RAF version check when writing FujiFilm RAF files + - Limited the number of accelerometer records that ExifTool will read by + default with the -ee option from INSV files to avoid excessive processing + times and memory usage + - Patched Windows version to allow reading of shared files with Unicode names + (thanks Eriksson) + - Patched to avoid converting some bad GPS coordinates (thanks Csaba Toth) + - Fixed verbose output to include YCbCrSubSampling for JPEG files + - Fixed conversion and group names for the new FujiFilm tag added in 11.68 + - Fixed format of GeoTiffDirectory and GeoTiffDoubleParams when writing + +Oct. 2, 2019 - Version 11.69 + + - Fixed bug introduced in version 11.66 where the sign was lost when writing + coordinate values between 0 and -1 to QuickTime:GPSCoordinates + +Oct. 1, 2019 - Version 11.68 + + - Added read support for yet another type of streaming GPS in MP4 videos + - Added a number of new FujiFlashMode values + - Decode a new FujiFilm tag + - Made NikonCaptureOffsets and NikonCaptureVersion deletable + - Enhanced tag name documentation to indicate deletable MakerNotes tags + +Sept. 30, 2019 - Version 11.67 + + - Added config_files/thinkware.config to the distribution + - Fixed bug decoding negative GPS coordinates from INSV videos + +Sept. 30, 2019 - Version 11.66 + + - Added a new Nikon LensID (thanks LibRaw) + - Added a few new Canon LensType values (thanks LibRaw and Tom Lachecki) + - Decode a few more Hasselblad tags (thanks LibRaw) + - Decode a new Canon tag (thanks Laurent Clevy) + - Decode more Samsung trailer tags + - Extract BWF iXML, aXML and UMID from RIFF-format files + - Extract ICC_Profile from more types of PDF files + - Enhanced %s of the -W option to recognize the PICT format + - Recognize MacOS alias files + - Changed name of Ricoh CropMode35mm tag and added a new value (thanks LibRaw) + - Minor change to a Minolta lens name (thanks Jos Roost) + - Fixed problem where NikonCapture information couldn't be deleted from an NEF + - Fixed problem identifying some SVG files + - Fixed typo in a CanonModelID value (thanks Dmitry) + - Fixed bug which could result in "Internal error: no list index" warning when + creating nested XMP lang-alt lists + - Fixed the names of a few Tamron lenses for Nikon (thanks Tom Lachecki) + - Fixed problem extracting Layer information from some PSD files + - Fixed writing of QuickTime GPSCoordinates to use the correct number of + digits before the decimal point for latitude and longitude + +Aug. 29, 2019 - Version 11.65 + + - Added new SonyModelID and Sony LensType values (thanks LibRaw and Jos Roost) + - Added support for some new Sony models (thanks Jos Roost) + - Added a couple of new CanonModelID values (thanks LibRaw) + - Added a new Canon ColorDataVersion value + - Enhanced FastScan option so a setting of 2 stops processing PNG images at + the IDAT chunk when reading + - Preserve order of nested lang-alt list entries when -struct option is used + +Aug. 28, 2019 - Version 11.64 + + - Added a new Canon LensType (thanks LibRaw) + - Added a new Nikon LensID (thanks Bruno) + - Added config file for converting streaming GPS from BlueSkySea dashcam + - Decode FocusDistance for Nikon Z6/Z7 + - Documented groups in families 5 and 6 (available but undocumented since + Exiftool version 8.22 and 11.50 respectively) + - Fixed some ordering problems when writing/copying nested XMP lang-alt lists + - Fixed some minor quirks with QuickTime language codes (thanks Hayo Baan) + - Fixed a CanonModelID value (thanks Dmitry) + - API Changes: + - Documented SavePath and SaveFormat options + +Aug. 20, 2019 - Version 11.63 - "PNG Early Text" + + - Added a few new Sigma lenses (thanks LibRaw) + - Improved handling of Canon CNTH atom in MOV/MP4 videos + - Changed PNG writer to place all text chunks before IDAT (not just XMP) + (github issue #23) + - Issue minor warning for any text chunk after PNG IDAT (not just XMP) + - Enhanced ForceWrite feature to allow "PNG" to be specified (to move existing + text chunks to before IDAT without editing any metadata) + - Removed Windows "surrogate" warning for files that wouldn't be processed + anyway + - Fixed some entries in the Minolta LensType list (thanks Jos Roost) + - Fixed identification of a Sony lens (thanks Jos Roost) + +Aug. 15, 2019 - Version 11.62 + + - Added a number of new Canon, Pentax, Sony and Sigma lenses (thanks LibRaw) + - Removed some extraneous verbose warnings when geotagging + - Removed Minolta LensType value for a non-existent lens (thanks LibRaw) + - Patched problem writing some simple qualified XMP values + - Patched to avoid writing files in Windows with Unicode surrogate characters + in their name unless the -overwrite_original_in_place option is used + - Fixed an incorrect Pentax LensType (thanks LibRaw) + - Fixed family 2 group names of some XMP-exifEX and XMP Composite tags + +Aug. 7, 2019 - Version 11.61 + + - Added a new FujiFilm CropMode (thanks LibRaw) + - Added a few new proprietary CustomRendered values (thanks Jeffrey Friedl) + - Added a new Nikon LensID and fixed a Canon LensType (thanks LibRaw) + - Added a new CanonModelID + - Decode more Sony DSC-RX100M7 tags (thanks Jos Roost) + - Write standard EXIF to PNG even if non-standard EXIF already exists + - Changed a Minolta/Sony LensType (thanks LibRaw) + - Changed Composite GPS reference direction tags to be derived from only the + XMP-exif GPS coordinate tags (and not other XMP GPS coordinates) + - Reverted a PNG Validation check that was removed from 11.60 + - Patched to avoid problems overriding new values when writing thumbnail and + preview images + +July 30, 2019 - Version 11.60 + + - Added a few new Sigma LensType values (thanks LibRaw) + - Updated Sony makernote decoding for the DSC-RX100M7 (thanks Jos Roost) + - Various internal improvements to PNG reader/writer + - Fixed bug in RIFF decoder that could cause an "undefined subroutine" error + (thanks Hayo Baan) + - Fixed problem writing some QuickTime tags if the PREFERRED levels were + changed via the config file + - Install Changes: + - Properly erase all temporary files after validation tests + +July 25, 2019 - Version 11.59 + + - Added a new SonyModelID (thanks LibRaw) + - Changed block delete to allow subsequent writing of tags from the same group + (like a group delete) + - Minor changes to warnings and verbose output when writing PNG images + - Fixed potential runtime warning on an error rewriting XMP in a PNG image + +July 25, 2019 - Version 11.58 + + - Added a number of new Canon and Sony LensType values (thanks LibRaw) + - Decode NikonMeteringMode for the D500 + - Decode LensID for Nikon Z lenses + - Extract RawThermalImage from Parrot Bebop-Pro Thermal images + - Validate PNG CRC values when writing or using the Validate option + - Improved Russian translation (thanks Andrei Korzhyts) + - Improved identification of some Tamron lenses for Canon cameras + - Changed name of D810MeteringMode tag to NikonMeteringMode + - Patched writing of XMP in PNG images to always come before IDAT, and warn if + XMP comes after IDAT when reading + - Fixed problem replacing multiple lang-alt default-language structure + elements in lists of XMP structures (behaviour for other languages still not + ideal) + - API Changes: + - Removed PNGEarlyXMP option + - Fixed problem introduced in 11.54 which caused Options('UserParam') to + return undef + - Internal Changes: + - A block delete of EXIF, XMP, IPTC, etc now sets the group delete flag + +July 19, 2019 - Version 11.57 + + - Improved decoding of some tags for the Sony ILCE-7RM4 (thanks Jos Roost) + - Minor change to a Sony lens name + - Fixed format of a number of 8-bit integer QuickTime tags when writing + - Fixed problem replacing multiple structure elements in lists of XMP + structures + +July 18, 2019 - Version 11.56 + + - Added support for the Sony ILCE-7RM4 (thanks Jos Roost) + - Added a new SonyModelID (thanks LibRaw) + - Added a few new Sony/Minolta LensType values (thanks LibRaw and Jos Roost) + - Decode some new Nikon and Motorola tags (thanks Neal Krawetz) + - Decode a couple more ColorData tags for some Canon models + - Extract PreviewImage from DNG files which don't have a .DNG extension + - Extract Huawei APP7 maker notes with the Unknown (-u) option + - Internal change in LensID logic for Sony E-type lenses + +July 12, 2019 - Version 11.55 + + - Added write support for XMP-crs:Texture and XMP-drs tags + - Added a number of new Panasonic NoiseReduction values + - Added definition for a new Kodak tag (thanks LibRaw) + - Added a couple of new Panasonic AFAreaMode values (thanks Daniel Beichl) + - Added a couple of new Sony/Minolta LensTypes (thanks Jos Roost and LibRaw) + - Added a new CanonModelID + - Decode HEVCConfiguration record from HEIC images + - Decode a new Panasonic tag + - Decode a new QuickTime tag + - Changed internal handling of Composite tag ID's to include module name + - Removed "FE" designation from Samyang E-mount lenses + - Dropped Validate warning about missing GPSProcessingMethod tag + +July 2, 2019 - Version 11.54 + + - Added new Canon and Sony/Minolta LensType values (thanks LibRaw) + - Added a number of new Sony/Minolta LensType values (thanks Jos Roost) + - Added "Unknown" value for new EXIF CompositeImage tag + - Added ability to write GSpherical tags in video track of MOV/MP4 files + - Added support for geotagging from GPS/IMU CSV-format files + - Improved Russian translation (thanks Alexander) + - Improved Validate feature to check ExifVersion/GPSVersionID numbers + - Accept unsigned numbers when setting GPSAltitudeRef from a numerical value + - Fixed decoding of DepthMapWidth/Height for some Samsung live-focus images + - Fixed a couple of incorrect/incomplete CanonModelID values (thanks LibRaw) + - Fixed problem identifying some Canon lenses when used on a Sony camera with + a Metabones adapter + - API Changes: + - Added FilterW option + - Enhanced Compact option to improve flexibility and include features of + XMPShorthand option + - Removed XMPShorthand option from documentation + +June 24, 2019 - Version 11.53 - "Exif 2.32" + + - Added support for the new tags of the Exif 2.32 specification + - Added a new SamsungModelID (thanks LibRaw) + - Added warning if extracting ZIP file contents without the -a option + - Added ability to extract EmbeddedVideo from the trailer of Android JPEG + images with the ExtractEmbedded option + - Decode timed GPS from Cobra Dash Cam AVI videos + - Decode a new GoPro tag + - Enhanced -struct option to allow extraction of structured Torrent Info + - Improved error handling when an unexpected terminator is encountered while + writing a QuickTime-format file + - Renamed one of the Nikon Saturation tags to "SaturationAdj" + - Removed warning message when writing FujiFilm RAFVersion 0240 and 0261 files + - Fixed encoding problem when writing some QuickTime UserData tags with + strings containing special characters + - API Changes: + - Enhanced XMPShorthand option to add level 2 + +June 17, 2019 - Version 11.52 + + - Added a few new Nikon CropHiSpeed values (thanks Hayo Baan) + - Added a new Nikon LensID (thanks Yves) + - Fixed problem where reading a large, corrupt AIFF file may could take an + excessively long time + - API Changes: + - Enhanced Compact option to add levels 3, 4 and 5 (github issue #16) + +June 13, 2019 - Version 11.51 + + - Decode Canon DistortionCorrection tags + - Removed a minor EXIF warning when processing EPS files with a DOS header + - Fixed bug which caused an error when rewriting some EPS files multiple times + +June 11, 2019 - Version 11.50 (production release) + + - Added a new Canon LensType and two new Sony LensTypes (thanks LibRaw) + - Added tiff_version and rotate_regions config files to the distribution + - Added two new QuickTime Keys tags and made some existing Keys unwritable + - Improved Composite LensID logic to make better use of EXIF LensModel + - Improved logic when writing BinaryData tags to allow multiple interdependent + tags to be written in a single command + - Improved -htmldump output to show names of Unknown tags + - Allow advanced formatting expressions to access the current tag key ($tag) + - Remove escaped nulls from -json string values + - Reverted change in ExifTool 11.38 so that Composite GPS reference directions + are generated again even if the EXIF versions of these tags already exist + - Fixed an incorrect FlashPix CodePage conversion + +June 5, 2019 - Version 11.49 + + - Added inverse print conversion for one of the QuickTime ItemList Genre tags + - Avoid creating a few obscure QuickTime UserData tags when writing + - Fixed problem where some QuickTime groups were not being created when + writing QuickTime tags without specifying a group + - Fixed problem where QuickTime Keys tags could be duplicated when writing an + existing alternate-language tag + - Fixed problem were QuickTime Keys alternate-language tags would not be + written when deleting the corresponding default-language tag in the same + command + - Fixed some inconsistencies when writing QuickTime tags using the -wm + (WriteMode) option + - Fixed an incorrect Pentax Sigma LensType value + +June 1, 2019 - Version 11.48 + + - Added write support for Google GCamera and GCreation XMP tags + - Renamed XMP-GDepth "Data" tag to "DepthImage" + - Fixed bug where some QuickTime UserData tags could be duplicated when + writing + +May 31, 2019 - Version 11.47 + + - Fixed problem which resulted in a warning for one of the CanonVRD tests on + some platforms + +May 31, 2019 - Version 11.46 - "CR3 update" + + - Added ability to write CanonVRD tags in CR3 images + - Decode a couple more tags from Canon CR3 images + - Enhanced Validate option to check for duplicate QuickTime atoms + - Relaxed constraints when writing IPTC date tags to allow use of separators + other than a colon + - Fixed CR3 writing to update CTBO table with any changed offsets or sizes + (although this table doesn't seem to be used by any RAW viewer, it may be + used in-camera to improve response time when browsing images) + +May 29, 2019 - Version 11.45 + + - CORRUPTION WARNING: Patched problem where Canon DPP would destroy a CR3 + image if the file had previously been edited by DPP then Exiftool + (If you have edited any CR3 images with ExifTool that had been previously + edited by DPP, then re-edit with ExifTool 11.45 or later to restructure the + file so DPP doesn't destroy it if used later to edit the file again) + - Added ability to create and delete QuickTime Keys tags + - Added sample config file (mini0806.config) to generate GPS tags from + subtitle Text in Mini 0806 dashcam videos + - Added new Canon and Nikon lenses (thanks LibRaw) + - Added a new Olympus CameraType (thanks LibRaw) + - Decode CanonVRD tags from CR3 images + - Improved handling of QuickTime language tags when writing + - Fixed bug introduced in 11.38 which could cause "Use of uninitialized value" + runtime warning when reading XMP GPS tags + - Fixed bug where QuickTime tags could be written when another group was + specified + - API Changes: + - Added QuickTimeHandler option + +May 21, 2019 - Version 11.44 + + - Added ability to extract XMP as a block from XMP files + - Prevent ExifIFD from being deleted from any RAW file type + - Fixed problem where some Canon tags couldn't be written in CR3 files + - Fixed problem reading QuickTime Keys tags with a space in the tag ID + - Fixed incorrect family 1 group when reading some QuickTime Keys tags + +May 17, 2019 - Version 11.43 - "Write HEIC and CR3" + + - Added ability to write/create EXIF and write ICC_Profile in HEIC images + - Added ability to write/create EXIF and write MakerNotes in CR3 images + (one might hope/expect EXIF to be stored in the same location for HEIC and + CR3 since they are both based on the QuickTime file format, but in fact they + couldn't be more different, and both are much more complicated than + necessary, which of course follows the seemingly established practice of + intentional obfuscation and zero standardization in video metadata) + - Added support for QuickTime ItemList:Author and Keys:DisplayName tags + - Prevent MakerNotes from being deleted from any RAW file type + - Fixed writing of XMP in HEIC files to conform with the HEIC specification + (obviously, Apple couldn't put this XMP in the same place as any other + QuickTime-based file format, because Apple is, after all, king of "Let's + reinvent the wheel!") + - Fixed problem where API WriteMode option wouldn't always prevent groups from + being created when group creation was disabled + +May 13, 2019 - Version 11.42 + + - Added ability to edit ThumbnailImage in Canon MOV videos + - Improved verbose hex dump for HEIC files + - Fixed another "Chunk offset outside movie data" error when writing some HEIC + files + +May 9, 2019 - Version 11.41 + + - Added write support and improved language handling for 3GP QuickTime tags + - Fixed format problems writing some binary values to QuickTime tags + - Fixed some language translations (thanks Herbert Kauer) + +May 7, 2019 - Version 11.40 + + - Added a new Canon LensType + - Added a new value for EXIF:SceneCaptureType used by some Samsung cameras + - Fixed QuickTime writing to preserve existing same-named default-language + tags in other groups when writing a default language tag + +May 3, 2019 - Version 11.39 - "Create QuickTime tags" + + - Added ability to create new QuickTime tags in MOV/MP4 videos + - Added two new Canon LensTypes and a new CanonModelID (thanks LibRaw) + - Added a few new Sony/Minolta LensType values (thanks Jos Roost) + - Added a number of new QuickTime GenreID values + - Added range check on date/time values when writing + - Decode Canon EOS D60 black levels + - Split off some QuickTime tags into different family 1 groups + - Fixed "Chunk offset outside movie data" error when writing some HEIC files + - Fixed decoding of Pentax AutoBracketing for K-1 and K-5 (github issue #15) + - Fixed some QuickTime family 2 group names + - Fixed bug introduced in 11.38 that broke extraction of thumbnail images from + Canon MOV videos + +Apr. 24, 2019 - Version 11.38 + + - Added Extra JPEGImageLength tag + - Added nksc.config to the sample config files + - Added a couple more Sony/Minolta LensTypes (thanks Jos Roost) + - Added a couple of new Sigma LensType values + - Decode a couple more tags from Pittasoft dashcam videos + - Decode two new FLIR tags (thanks Corinne Berthier) + - Decode a new ERF tag, and fix wrong format for some others (thanks LibRaw) + - Improved decoding of Sigma maker notes for some models + - Enhanced Composite tag logic to allow a scalar Inhibit entry + - Enhanced XMP processing to support readable subdirectories embedded in a tag + - Updated some language translations + - Patched Composite GPS reference direction tags to prevent them from being + created if these tags already exist + - Fixed problem reading some odd PDF files + +Apr. 17, 2019 - Version 11.37 + + - Added a new Sony AFAreaMode (thanks Jos Roost) + - Decode GPS and other tags from Pittasoft Blackvue dashcam videos + - Improved decoding of FujiFilm FlickerReduction + - Ignore any garbage before an NMEA sentence when geotagging + - Fixed bug which could result in loss of timed GPS metadata when writing MP4 + videos + +Apr. 15, 2019 - Version 11.36 + + - Added a number of new MacOS tags + - Added a new CanonModelID (thanks Laurent Clevy) + - Added some new Canon EasyMode and AFAreaMode values + - Added two new Canon AspectRatio values (thanks LibRaw) + - Decode a new Nikon tag (thanks LibRaw) + - Decode some new FujiFilm tags + - Updated Sony maker notes for the DSC-RX0M2 (thanks Jos Roost) + - Hide the Nikon ShotInfo offset tags + - Fixed problem decoding NikonCustom settings for some D810 firmware versions + - Fixed typo in a warning message (thanks Hayo Baan) + +Apr. 9, 2019 - Version 11.35 + + - Added print conversion for MDItemFSLabel + - Added a new Sony LensType (thanks Jos Roost) + - Added an additional -validate check for PNG images + - Decode a few more FujiFilm RAF tags (thanks LibRaw) + - Decode a couple more QuickTime tags + - Allow "Copy0" to be specified as a group name for the copy number of the + primary tag when extracting information + - Improved the Composite ImageSize tag to report the RawImageCroppedSize for + FujiFilm RAF images + - Changed Composite ImageSize tag to use a space instead of "x" as a separator + when the -n option is used + - Fixed problem writing user-defined PhaseOne SensorCalibration tags + - Fixed problem where a List-type tag may not be split into individual items + with the -sep option when using the advanced-formatting "@" feature + - API Changes: + - Patched a potential pitfall if calling code used both the old List and + ListSep options at the same time as the new ListJoin option + +Apr. 4, 2019 - Version 11.34 + + - Added a couple of new Canon LensType values (thanks LibRaw for one) + - Added a new CanonExposureMode value (thanks Arnold van Oostrum) + - Added support for FujiFilm X-H1 Ver2.01 RAF images + - Decode a couple of new Sony tags (thanks LibRaw) + - Improved decoding of Sony Shutter tag (thanks Jos Roost) + - Improved identification of some Sony lenses (thanks Jos Roost) + - Improved parsing of streamed metadata from TomTom Bandit videos + - Improved warning for truncated QuickTime atom + - Accept wider range of formats when writing QuickTime:GPSCoordinates + - API Changes: + - Changed SetFileName() 'Link' option name to 'HardLink' (but still allow + 'Link' for backward compatibility) + +Mar. 28, 2019 - Version 11.33 + + - Added write support for HEIC/HEIF files + - Added new write-only SymLink tag for creating symbolic links + - Made EXIF GDALMetadata and GDALNoData writable + - Enhanced writing capabilities for MOV/MP4 videos + - Enhanced -validate option to add more IPTC checks + - Updated decoding of Sony ILCE-9 maker notes for firmware version 5.00 + (thanks Jos Roost) + - Fixed problem reading streamed metadata from some TomTom Bandit videos + - API Changes: + - Added SymLink option to SetFileName() + +Mar. 14, 2019 - Version 11.32 + + - Added a new Nikon LensID (thanks Kenneth Cochran) + - Added a couple of new QuickTime HandlerType values + - Decode streamed metadata from DuDuBell M1 and VSYS M6L dashcam videos + - Attempt to improve Nikon lens identification + - API Changes: + - Added new single-argument version of ShiftTime() + +Mar. 7, 2019 - Version 11.31 + + - Added read support for FITS images + - Another try at removing spaces from some DICOM values (github issues #10/12) + +Mar. 6, 2019 - Version 11.30 (production release) + + - Added a new Sony/Minolta LensType (thanks Jos Roost) + - Decode streaming metadata from TomTom Bandit Action Cam MP4 videos + - Decode Reconyx HF2 PRO maker notes + - Decode ColorData for some new Canon models (thanks LibRaw) + - Enhanced -geotag feature to set AmbientTemperature if available + - Remove non-significant spaces from some DICOM values (github issues #10/12) + - Fixed possible "'x' outside of string" error when reading corrupted EXIF + - Fixed incorrect write group for GeoTIFF tags added in version 11.24 + +Feb. 28, 2019 - Version 11.29 + + - Added support for Ricoh GR III maker notes + - Added a new Canon LensType (thanks Claude Jolicoeur) + - Added a new XMP-crs tag (github issue #8) + - Enhanced -csv option to output base64-encoded binary data when combined with + -b or when the -charset option is used and the text has invalid characters + (github issue #11) + - Remove trailing space from even-length DICOM values (github issue #9) + - Patched to avoid "Hexadecimal number > 0xffffffff non-portable" warning + (github issue #6) + - Fixed meta charset attribute in -htmlDump output + +Feb. 21, 2019 - Version 11.28 + + - Added support for reading INSV video and decode streaming GPS + - Added a new Pentax LensType (thanks Louis Granboulan) + - Added a new FujiFilm ImageStabilization value + - Allow exiftool to be run via a symbolic link on Mac/Linux + - Reverted INDD patch of version 11.27 (ie. raise error again on incorrectly + terminated INDD object list) + - Changed handling of temporary documentation file in Windows version + +Feb. 14, 2019 - Version 11.27 + + - Added support for more XMP-dji-drone tags + - Added new Olympus CameraType and LensType values (thanks LibRaw) + - Added a new Canon LensType (thanks LibRaw) + - Added a new CanonModelID + - Decode yet another type of GPS from DashCam videos + - Allow FileName to be written when only case is changed on case-insensitive + filesystems + - Improved identification of some iWork file types + - Recognize the LRV file extension + - Changed Windows version to use the parent folder of PAR_GLOBAL_TEMP for the + temporary documentation file + - Don't raise an error if an INDD object list is terminated by spaces instead + of nulls + - Fixed some problems with new -htmldump IFD highlighting feature + - Fixed bug introduced in 11.24 with "-o -.EXT" feature + +Jan. 21, 2019 - Version 11.26 + + - Added a new Nikon LensID (thanks LibRaw) + - Decode more tags for the Sony ICLE-6400 (thanks Jos Roost and LibRaw) + - Enhanced -htmldump feature to highlight IFD when mousing over IFD offset + +Jan. 15, 2019 - Version 11.25 + + - Added a new Sony/Minolta LensType (thanks LibRaw) + - Added a new Nikon LensID + - Decode Leica D-Lux7 maker notes + - Decode more Nikon AF tags for newer models + - Decode Samsung Type2 maker notes with lower case Make + - Decode another Sony tag (thanks Jos Roost) + - Improved decoding of Nikon LensType + - Improved time shift feature to fix some incorrectly formatted date/time + values + - Renamed some Sony ImageCount tags to ShutterCount (thanks Jos Roost) + - Fixed problem reading back metadata written to some odd PDF files + +Jan. 8, 2019 - Version 11.24 + + - Compatibility Notice: Changed the meaning of '-' and '+' modifiers for %C + formatting code (does not affect lower-case %c code) + - Decode a number of new Nikon tags (thanks Michael Tapes for samples) + - Added new Olympus FlashType and FlashModel values (thanks Per) + - Added a new Canon LensType + - Added a new Nikon LensID + - Made more GeoTIFF tags writable + - Handle XMP rdf:value when reading + - Improved warning when trying to read a file with a zero-length name + - Fixed decoding of PictureControl tags for Nikon Z-7 + - Fixed problem writing date/time values with " DST" designator at end of + date/time string + - Fixed problem in Windows which could cause ExifTool to abort due to a + Win32::FindFile error if a file name contained surrogate Unicode characters + +Dec. 21, 2018 - Version 11.23 + + - Recognize DWG and DWF files + - Minor improvement to some -validate warnings + - Tolerate leading UTF-8 byte order mark (BOM) at start of JSON files + - Fixed problem recognizing some streaming camm metadata in QuickTime videos + +Dec. 13, 2018 - Version 11.22 + + - Added read support for PC Paintbrush (PCX) files + - Added two new Sony/Minolta LensTypes (thanks Jos Roost and LibRaw) + - Decode LensData tags for some newer Nikon models + - Decode ColorData for the Canon EOS R (thanks LibRaw) + - Recognize DCX files + +Dec. 7, 2018 - Version 11.21 + + - Added a new Sony/Minolta LensType (thanks Jos Roost) + - Added a new Olympus FlashModel (thanks Michael Meissner) + - Improved decoding of FujiFilm InternalSerialNumber (thanks LibRaw) + - Minor improvements to decoding of GPS from some dashcam videos + - Made XMP-getty:Personality a List-type tag + - Made it an error to use the -o option or write FileName or Directory tags + when using the TestName dry-run feature + - Fixed problem using -E with other character sets when writing + +Nov. 20, 2018 - Version 11.20 + + - Added a new Panasonic WhiteBalance value + - Added a new Nikon LensID (thanks LibRaw) + - Decode streaming GPS from MOV videos for another dashcam model + - Improved -E option to support character sets other than UTF-8 + +Nov. 14, 2018 - Version 11.19 + + - Added -fast4 option + - Enhanced -if option to allow arbitrary Perl expressions instead of just + logic expressions + - API Changes: + - Enhanced FastScan option to add a setting of 4 + +Nov. 12, 2018 - Version 11.18 + + - Decode a new Nikon tag (thanks Richard Butler) + - Decode a new FujiFilm tag + - Updated decoding of Sony maker notes for newer models (thanks Jos Roost) + - Enhanced -if option to allow fast processing pass to evaluate the condition + - Improved warning for unknown JPEG APP segment + +Nov. 4, 2018 - Version 11.17 + + - Added a new Canon LensType (thanks Norbert Wasser) + - Added a new Sony/Minolta LensType and a new SonyModelID (thanks LibRaw) + - Decode GPS from Garmin Dashcam videos + - Changed type of J2C files from a JPEG 2000 image to a JPEG 2000 codestream + +Oct. 26, 2018 - Version 11.16 + + - Decode FLIR GPS information + - Decode 3D image from RED Hydrogen smartphone + - Minor improvements to decoding of new FujiFilm tags + - Fixed problem where writing Shortcut tags with the -E option would double + unescape the HTML entities + +Oct. 25, 2018 - Version 11.15 + + - Added a couple of new Canon LensType values (thanks LibRaw and Andrew Shieh) + - Added a new Nikon LensID + - Added definitions for a few more VCard tags + - Added a new FujiFilm ShutterType value + - Decode some new FujiFilm tags (thanks Richard Butler) + - Store XMP GPS coordinates with two extra digits of precision and trim + trailing zeros + - Improved technique for handling rounding errors in times and GPS seconds + - Removed "Undersized IFD0 StripByteCounts" minor warning when writing ORF + files since this is a "feature" of most Olympus models + - Warn about undefined EXIF values with -validate option + - Changed the way Mask-ed values are decoded (do bit shift automatically) + - Changed FujiFilm HighISONoiseReduction tag to just "NoiseReduction", and + avoid extracting historic NoiseReduction tag if value is "n/a" + - Fixed potential problem reading GeoTiff tags with multiple SHORT values + - API Changes: + - Added GeoSpeedRef option + +Oct. 16, 2018 - Version 11.14 + + - Added more TIFF Compression values + - Added more AIFF CompressionType values + - Added more Nikon NEFCompression values (thanks LibRaw) + - Added a new Canon RecordMode + - Decode some new Canon custom functions + - Patched "Invalid VignettingCorrUnknown2 data" warning for EOS R CR3 images + - Fixed bug were any argument beginning with "-progress" on the command line + was interpreted as the -progress option + +Oct. 9, 2018 - Version 11.13 + + - Decode GPS from NextBase 512G dashcam MOV videos (different than 512GW) + - Added a new Canon LensType (thanks LibRaw) + - Minor improvements to verbose dump of streaming GPS metadata + - Reverted change of version 10.71 which resulted in Windows not recognizing + PNG CreationTime as written by ExifTool (added this feature to the API + StrictDate option instead) + - Improved decoding of Nikon CropHiSpeed (thanks LibRaw) + - Improved -fast option to reduce memory usage when reading JPG, PNG, + QuickTime-based and RIFF-based files via a sequential stream + - Fixed DOF calculation to use ApproximateFocusDistance if available + - API Changes: + - Enhanced StrictDate option to reformat PNG CreateTime according to PNG + specification + +Oct. 2, 2018 - Version 11.12 + + - Added a new Sony/Minolta LensType (thanks LibRaw and Jos Roost) + - Added a new Nikon LensID + - Decode a few new Sony SRF2 tags (thanks LibRaw) + - Decode GPS from NextBase 512GW dashcam MOV videos + - Validate MS-DOC FIB before extracting contained tags + - Fixed bug extracting GPSSpeed for some dashcam models + +Sept. 27, 2018 - Version 11.11 (production release) + + - Added ARQ to the list of supported file types + - Added support for GIMP XCF version 4 and later + - Added a new QuickTime HandlerType value + - Added read support for Apple AAE files + - Added a new CanonModelID and some new Canon LensType values (thanks LibRaw) + - Added a number of new Nikon LensID values (thanks Robert Rottmerhusen) + - Added a new Sony/Minolta LensType (thanks LibRaw) + - Decode more Sony IDC tags (thanks Jos Roost) + - Decode some new Panasonic tags (thanks Klaus Homeister) + - Decode more tags from Nikon MOV videos + - Decode a new Nikon tag (thanks LibRaw) + - Decode a large number of new Kodak IFD tags (thanks Jim McGarvey) + - Decode streaming GPS from videos of more dashcam and drone models + - Decode more tags from Microsoft Word DOC files + - Updated arg_files/iptcCore.args for IPTC Extension version 1.4 + - Patched to read corrupted MakN data written by buggy Adobe Camera Raw + - Downgraded "Undersized StripByteCounts" error for some RAW file types + - Fixed incorrect decoding of embedded GPS in Rexing V1P dashcam videos + - Fixed incorrect format for DNGPrivateData + - Fixed potential error when deleting maker notes from some images + - Fixed problem decoding Apple PLIST information from some files + - Fixed bug in Windows with CR/LF sequences in list values of the -X output + - Fixed some inconsistencies in detecting file name conflicts when writing the + TestName tag + +Aug. 17, 2018 - Version 11.10 + + - Added support for Canon 1DX firmware 2.1.0 + - Added a new Canon LensType (thanks LibRaw) + - Added a new Nikon LensID (thanks LibRaw) + - Added a new CanonModelID + - Decode more tags for newer Sony DSC models (thanks Jos Roost) + - Decode some new SonyIDC tags (thanks Jos Roost) + - Decode a number of new Panasonic tags (thanks Klaus Homeister) + - Improved validation of XMP namespaces + - Changed "File not found" messages to "Error: File not found" + - Fixed problem editing tags in Canon DR4 directory + +Aug. 13, 2018 - Version 11.09 + + - Added new Pentax and Canon LensType values + - Decode Google Camera Motion metadata from MP4 videos + - Decode more PanasonicRaw tags (thanks Klaus Homeister) + - Removed warning when multiple Word document LastSavedBy tags exist and the + Duplicates option wasn't enabled (added Note in tag name docs instead) + +Aug. 1, 2018 - Version 11.08 + + - Decode more tags from Microsoft Word documents, including LastSavedBy + - Decode image file characteristics from Windows EXE files + - Decode more PanasonicRaw tags (thanks Klaus Homeister) + - Changed names of new Samsung trailer tags + - Fixed potential problems converting C-style escaped strings + - Fixed new "#[CSTR]" feature to work with -stay_open option + +July 27, 2018 - Version 11.07 + + - Added "#[CSTR]" feature to -@ argfile + - Added some new Sony LensType values (thanks Jos Roost) + - Decode more tags from Samsung trailer + - Decode an undocumented DNG tag + - Decode some new Panasonic tags (thanks Klaus Homeister) + - Improved/fixed a few Validate warnings + - Made MakerNote "Bad SubDirectory start" warnings minor + - Fixed NoDups() function to work with special characters as list separators + +July 6, 2018 - Version 11.06 + + - Fixed "undefined value" bug when reading ImageSourceData from a JPEG file + +July 5, 2018 - Version 11.05 + + - Added a number of new Nikon LensID's (thanks Robert Rottmerhusen) + - Fixed out-of-memory problem when writing some large TIFF images in Windows + +July 4, 2018 - Version 11.04 + + - Added a check on TIFF image data size when writing or using Validate option + - Added a few new Sony lenses (thanks LibRaw) + - Added a new Nikon LensType + - Improved validation of XMP with Validate option + - Drop PhaseOne tags larger than 8 kB when copying PhaseOne maker notes to + another file + - Fixed out-of-memory problem when reading some large TIFF images in Windows + +June 21, 2018 - Version 11.03 + + - Added support for new Exif 2.31 for XMP tags + - Added support for another FujiFilm X-T1 firmware version + - Decode more Panasonic tags (thanks Klaus Homeister) + +June 13, 2018 - Version 11.02 + + - Added support for a different format of Apple iWorks files + - Added undocumented API FixCorruptedMOV option to allow fixing MOV videos + with multiple 'mdat' atoms which were corrupted by ExifTool + - Decode more QuickTime tags + - Decode more PanasonicRaw tags (thanks Klaus Homeister) + - Improved decoding of makernotes in ARW images from Hasselblad cameras + (thanks LibRaw) + - Fixed some problems writing multi-segment EXIF in JPEG images + +June 11, 2018 - Version 11.01 (production release) + + - Added a new ProfileCMMType (thanks Neal Krawetz) + - Added a Validate warning about non-standard EXIF or XMP in PNG images + - Added a new Canon LensType + - Decode a couple more PanasonicRaw tags (thanks Klaus Homeister) + - Patched to avoid adding tags to QuickTime videos with multiple 'mdat' atoms + --> avoids potential corruption of these videos! + +June 7, 2018 - Version 11.00 (production release) + + - Added read support for WTV and DVR-MS videos + - Added print conversions for some ASF date/time tags + - Added a new SonyModelID (thanks LibRaw) + - Decode a new PanasonicRaw tag (thanks Klaus Homeister) + - Decode some new Sony RX100 VI tags (thanks LibRaw and Jos Roost) + - Made Padding and OffsetSchema tags "unsafe" so they aren't copied by default + +May 29, 2018 - Version 10.99 + + - Decode layer information from Photoshop ImageSourceData in TIFF images + - Updated to the IPTC video metadata 1.2 specification + - Patched DateFmt() utility function to apply GlobalTimeShift if used + - Improved error message when trying to write a file with a wrong extension + - Fixed unnecessary warning when setting FileCreateDate in Windows + +May 22, 2018 - Version 10.98 + + - Added additional Validate checks for JPEG thumbnail tags + - Added a new Canon LensType (thanks LibRaw) + - Decode a number of new Nikon ColorBalance tags (thanks LibRaw) + - Disable extraction of Nikon D850 PhotoShootingMenuBank from NEF images + (apparently not valid in this type of file) + - Fixed problem with writable user-defined Composite tags introduced in 10.16 + - Fixed unnecessary Validate warning about missing GPSVersionID + - Fixed incorrect "wrong IFD" Validate warnings in CR3 images + +May 17, 2018 - Version 10.97 - "Multi-segment EXIF" + + - Added read/write support for multi-segment EXIF in JPEG images + - Added a number of new Canon LensType values (thanks LibRaw) + - Added support for Panasonic DC-FT7 makernotes and metadata in MP4 videos + - Decode a number of new Nikon WB tags (thanks LibRaw) + - Improved warning message when attempting to write to an invalid tag name + - Enhanced Validate feature to perform more tests on TIFF and JPEG images + [The Validate feature is no longer considered experimental] + +May 9, 2018 - Version 10.96 + + - Added a new Sony LensType (thanks Jos Roost) + - Added a few new Panasonic lenses (thanks LibRaw) + - Added Composite tags for GPSDestLatitudeRef and GPSDestLongitudeRef + - Decode maker notes from Kodak PixPro AZ901 + - Extract Preview images from iWork files + - Improved identification of Apple iWork files + - Fixed arg_files to handle GPS destination reference directions + +May 4, 2018 - Version 10.95 + + - Added new Nikon LensID's (thanks Warren Hatch, LibRaw and Jami Bradley) + - Added a new Sony LensType (thanks Jos Roost) + - Decode a new Samsung tag (thanks LibRaw) + - Decode Photoshop Lr16 layer information + - Decode more Leica tags (thanks LibRaw) + - Updated DarwinCore tags to current specification + - Improved validation of JPEG files + - Disabled writing of buggy Samsung EK-GN120 SRW files + - Fixed conversion for Nikon D850 ExposureDelayMode (thanks Jami Bradley) + - Fixed "x outside string" error when reading a truncated zip file + - Fixed "uninitialized value" error when writing a corrupted JPEG image + +Apr. 19, 2018 - Version 10.94 + + - Added read/write support for Canon CRM files + - Added a new Sony LensType (thanks Jos Roost) + - Added a new CanonModelID + - Decode a new Samsung tag (thanks LibRaw) + - Fixed AIColorModel conversion + +Apr. 13, 2018 - Version 10.93 + + - Added a new Canon Quality value (thanks Norbert Wasser) + - Added a new Pentax Quality value (thanks LibRaw) + - Decode some new Sony ARW tags (thanks Jos Roost) + - Decode some AI-specific tags from PDF and PostScript files + - Decode a new QuickTime tag + - Enhanced -geotag option to support NMEA from GLONASS and other systems + - Fixed bug decoding seconds of ZIP file timestamps (thanks Lars Wallenborn) + +Apr. 19, 2018 - Version 10.92 + + - Decode GPS from videos of more camera models + - Tolerate white space before header in PDF files + +Apr. 9, 2018 - Version 10.91 + + - Added read/write support for MacOS FileCreateDate (writing this is the same + as MDItemFSCreationDate, but reading uses a different mechanism which + doesn't have the delayed-update issue of MDItemFSCreationDate) + - Added ability to write MacOS MDItemUserTags (requires "tag" utility) + - Decode a new Sony tag (thanks LibRaw) + - Properly un-escape quotes in extracted MacOS MDItem values + - Fixed another subtle order-of-operations anomaly + - API Changes: + - Enhanced GetValue() to accept a tag name with group prefix(es) + +Apr. 5, 2018 - Version 10.90 + + - Improved decoding of Sony PictureProfile (thanks Jos Roost) + - Fixed problem introduced in 10.61 with order of command-line operations when + mixing copied values with assigned values + +Apr. 2, 2018 - Version 10.89 + + - Added ability to rotate MP4/MOV videos by writing Rotation angle + - Added two new Sony PictureProfile values (thanks Albert Shan) + - Decode more Nikon tags + - Updated some values of the QuickTime MediaType tag + +Mar. 27, 2018 - Version 10.88 + + - Added example config file (dji.config) to generate Composite GPS and other + tags from Text metadata embedded in DJI drone videos + - Added a new PentaxModelID + - Documented new advanced-formatting "@" feature which has existed since + version 10.53 but not fully functional until 10.87 + - Decode a new Sony tag (thanks LibRaw) + - User-defined Composite tags now show up in the -list output + - Improved speed for generating Composite SubDoc tags with -ee option + - Fixed problem creating writable UserDefined Composite tags + - Fixed an incorrect PRISM tag name + +Mar. 20, 2018 - Version 10.87 + + - Added a new FujiFilm ShutterType value (thanks Albert Shan) + - Decode more timed metadata from CR3 images + - Decode Samsung DualShot depth map + - Decode a new Canon tag + - Improved decoding of some Canon color information (thanks LibRaw) + - Fixed print conversion of ID3v2 Genre values with multiple genres + +Mar. 15, 2018 - Version 10.86 + + - Decode timed GPS and accelerometer data from BikeBro AVI videos + - Decode a new Sony tag and add a new value to another tag (thanks Jos Roost) + - Decode GPSAltitude from some videos with the -ee option + - Decode some new tags from Kodak PixPro 4KVR360 JPEG images and MP4 videos + - Decode GPS information from CR3 images + - Fixed unnecessary warnings when Validate option used on CR3 images + +Mar. 14, 2018 - Version 10.85 + + - Decode GPSTrack from MOV videos of more dashcam models + - Decode a few new CanonVRD tags for DPP 4.8.20 + - Decode a new Sony tag (thanks Jos Roost) + - Decode a few more Canon tags (thanks LibRaw) + - Improved processing speed when using the -ee option on some video files + - Fixed incorrect names for a couple of CanonVRD HSL tags + - Fixed problem where UTF-8 validation missed some invalid sequences + - Fixed bug introduced in 10.84 with GPSPosition when -n option is used + +Mar. 12, 2018 - Version 10.84 + + - Decode GPS from MOV videos of more dashcam models with -ee option + - Decode a new Sony tag (thanks Jos Roost) + - Convert GPS speeds extracted from MOV videos with the -ee option to km/h + - Avoid converting empty GPS coordinates to 0.000000 + - Fixed some bugs extracting Novatek GPS from MP4 videos + +Mar. 7, 2018 - Version 10.83 + + - Added read support for Sketch design files + - Added Light LRI files to the list of recognized file types + - Added a new Canon LensType (thanks LibRaw) + - Decode a couple of new Sony tags (thanks Jos Roost) + - Extract JpgFromRaw image from CR3 images + - Improved warning message when attempting to write a tag in a specific group + that isn't writable + - Changed group name of JSON tags from "File" to "JSON" + - Fixed some incorrect offsets in -v3 output for CR3 images + +Mar. 1, 2018 - Version 10.82 + + - Added support for Canon's new CR3 raw file format + - Added a few new CanonModelID/SonyModelID values (thanks LibRaw) + - Added support for the Sony ILCE-7M3 (thanks Jos Roost) + - Decode timed GPS information from Insta360 MP4 videos with the -ee option + - Write XMP before mdat in MOV/MP4/CR3 files if possible + - Fixed "'x' outside of string" runtime error when reading some Sony images + - Fixed problem with some hex dumps going to the console when -v3 was combined + with the -w option + +Feb. 26, 2018 - Version 10.81 + + - Added new values for a few Panasonic tags (thanks Bernd-Michael Kemper) + - Added a new Canon, Olympus and Sony LensTypes (thanks LibRaw) + - Added a new PanasonicRaw Compression type (thanks LibRaw) + - Added definitions for a number of new MacOS tags + - Decode CameraInfo for Canon 5DmkIII firmware 1.3.5 + - Removed INX from list of writable files (-listwf option output) + - Fixed problem introduced in version 10.16 that could cause a "Can't create" + error when using the -o option to write certain types of files + - Fixed problem introduced in version 10.34 resulting in a "Can't delete all + meta information" error when writing .PS files + +Feb. 22, 2018 - Version 10.80 (production release) + + - Added read/write support for Reconyx UltraFire maker notes + - Added a new Sony/Minolta lens (thanks Jos Roost) + - Decode a new PanasonicRaw tag (thanks LibRaw) + - Extract ImageWidth/Height for main image of an HEIC file + - Internal changes: + - Changed TimeNow() make ExifTool object optional + +Feb. 11, 2018 - Version 10.79 + + - Added a new Olympus CameraType (thanks LibRaw) + - Added a new XMP-microsoft tag (thanks Jose Oliver-Didier) + - Decode a new GoPro QuickTime tag + - Convert nulls IPTC:DocumentHistory to newlines + - Removed all null terminators from JSON and PHP output + - Fixed writing of GPSDateStamp and GPSTimeStamp to be able to set to "now" + - Internal changes: + - Changed TimeNow() to require ExifTool object as first argument + +Jan. 31, 2018 - Version 10.78 + + - Added a few new values for some Olympus tags (thanks John) + - Decode GoPro APP6 metadata in JPEG images and more GoPro MP4 tags + - Decode more Red tags, and improved decoding of others + - Decode face detection information from timed metadata with the -ee option + - Fixed problem writing shorthand XMP containing CDATA sections + - Fixed problem copying XMP-acdsee:Snapshots + - Fixed decoding of a few Panasonic RAW tags (thanks Klaus Homeister) + +Jan. 26, 2018 - Version 10.77 + + - Added read support for Redcode R3D RAW videos + - Enhanced -sep option to specify separator and terminator for binary output + - Removed null terminator from JSON output of ICC_Profile:CharTarget + - Improved error messages to help diagnosing some types of corrupted files + - Return an exit status of 2 instead of 1 if all files fail the -if condition + - Fixed decoding of QuickTime chapter names + - Fixed incorrect MimeType for RMD files + - Fixed problem where exit status of 1 was returned when writing with a -if + condition if any of the files failed the condition + +Jan. 22, 2018 - Version 10.76 + + - Added ability to write shorthand XMP with the -z option + - Added write support for Google XMP GFocus tags + - Improved decoding of GoPro timed metadata + - Renamed ASF PlayDuration to Duration + - Fixed problem where fractional seconds were ignored when geotagging from an + NMEA track log with no date stamps + - Fixed runtime warning when reading XMP with an empty structure in a list + - API Changes: + - Added XMPShorthand option + - Enhanced Compact option so a setting of 2 avoids XMP indentation + +Jan. 12, 2018 - Version 10.75 + + - Added another Sony/Minolta lens (thanks Jos Roost) + - Decode more QuickTime tags + - Decode a number of new tags from GoPro Hero6 MP4 videos + - Enhanded "Unknown file type" error to indicate if "File is empty" or "File + header is all binary zeros" + - Improved decoding of some Sony tags (thanks Jos Roost) + - Improved decoding of QuickTime timed metadata + - Marked ArtworkCircaDateCreated as "unsafe" for writing to avoid it being + added when attempting to shift all date/time tags + - Fixed bug which could cause runtime error when reading some old Sony maker + notes (thanks Tamas Lovag) + +Jan. 8, 2018 - Version 10.74 + + - Added a new Sony/Minolta lens (thanks Jos Roost) + - Added print conversion for ICC_Profile DeviceManufacturer and ProfileCreator + - Added informational warning when the ExtractEmbedded option may be useful + - Improved experimental Validate feature for RAW files + - Fixed bug in experimental Validate feature that could cause out-of-memory + error when combined with "-use mwg" + +Jan. 5, 2018 - Version 10.73 + + - Added read/write support for GoPro RAW (GPR) files + - Added a new Sony/Minolta lens (thanks Sylvain) + - Improved conversions for GPS tags extracted from video streams + +Jan. 4, 2018 - Version 10.72 + + - Added IF feature to -p option + - Decode streamed GPS position and other streamed metadata from MP4 videos + when the -ee option is used + - Fixed problem geotagging GPSAltitude from some GPX files + +Jan. 2, 2018 - Version 10.71 + + - Decode some more ICC_Profile tags (thanks Eef Vreeland) + - Decode MechanicalShutterCount for Nikon D850 (thanks Xavier Jubier) + - Convert PNG:CreationTime values to/from standard date format + - Fixed problem loading default config file from application directory + - Fixed problem reading XMP where a namespace is defined after an attribute + which uses the namespace + +Dec. 27, 2017 - Version 10.70 + + - Search application directory for -config file + - Improved robustness of JSON import + - Enhancements to experimental Validate feature + - Fixed bug introduced in 10.69 which could result in hang when writing + multi-segment JPEG metadata + +Dec. 18, 2017 - Version 10.69 + + - Added "OK" UserParam for use in -if conditions + - Allow writing an empty JPEG Comment + - Check for proper location of Photoshop metadata with -validate or -use mwg + - Exit status now set to 1 if command was aborted due to invalid arguments + - Translate "UTF8" to appropriate escape sequence when writing + IPTC:CodedCharacterSet with the -n option + - Improved "Not a valid TIFF" error message to be more specific about the file + type for TIFF-based formats + - Fixed problem parsing Honeywell PTNTHPR NMEA sentences from some GPS devices + +Dec. 5, 2017 - Version 10.68 + + - Added ability to set tag values and API options to an empty string using + "^=" on the command line + - Added a new Sony LensType (thanks Jos Roost) + - Added a new Nikon LensID + - Decode more tags from some newer Leica models + - Decode a new Apple tag + - Fixed indeterminate order of extracted XMP structures + +Nov. 16, 2017 - Version 10.67 + + - Fixed problem introduced in 10.66 with -execute not returning the command + number in the "{ready}" message when -stay_open was used + - API Changes: + - Added TimeZone option + +Nov. 14, 2017 - Version 10.66 + + - Added a new Canon LensType (thanks Norbert Wasser) + - Updated en-ca and en-gb language translations + - Minor format change to experimental Validate feature return value + - Prevent JFIF from being added to a JPEG containing Adobe APP14 + - Changed a number of Canon LensType strings to add "USM" to L model names + - Patched for compatibility with Time::Piece version 1.29_04 and later, and + improved error handling when writing formatted date/time values + - Fixed bug in Composite MWG CreateDate and DateTimeOriginal tags which could + cause existing tags to be hidden when not using the -a option + - Fixed problem using '#' suffix not properly fixed in 10.65 + - Fixed problem decoding Nikon D810 MultiExposure tags + +Oct. 31, 2017 - Version 10.65 + + - Added support for DOSLatinUS (cp437) and DOSLatin1 (cp850) character sets + - Added Extra ForceWrite tag for forcing metadata in a file to be rewritten + - Added write support for RAF images from some newer FujiFilm models + - Added a new SonyModelID, Sony LensType and Olympus LensTypes (thanks LibRaw) + - Added a new Pentax LensType + - Added a new Nikon LensID + - Decode more Sony tags for new models (thanks Jos Roost) + - Decode Nikon D850 ShotInfo tags and custom settings (thanks Warren Hatch) + - Decode Nikon D850 ColorTemperatureAuto (thanks LibRaw) + - Decode Photoshop LayerModifyDates and LayerIDs + - Improved decoding of Nikon D5/D500/D810 ShotInfo tags + - Allow advanced formatting expressions to return a list reference + - Fixed problem in Composite:GPSAltitude when derived from an "undef" altitude + - Fixed bug which could result in runtime warning when excluding some tags + from being extracted + +Oct. 17, 2017 - Version 10.64 + + - Added a new Nikon LensID + - Added a new SonyModelID + - Added a new CanonModelID (thanks LibRaw) + - Added some new non-standard CustomRendered values + - Decode FrameRate from FLIR SEQ files (thanks Sebastian Hani) + - Enhanced shift feature to be able to shift some not-so-simple numerical + values (eg. GPSLatitude) with -TAG+=VALUE syntax + - Fixed problem with possible malformed UTF-8 when writing IPTC values that + require truncation + - Fixed incorrect Writable type for XMP-GSpherical:TimeStamp + - Fixed incorrect family 2 group name for Nikon HDRInfo tags + +Oct. 4, 2017 - Version 10.63 + + - Added a number of new Canon LensTypes (thanks Norbert Wasser for some) + - Added a new CanonModelID + - Added a new Olympus CameraType + - Decode MD5Signature in FLAC StreamInfo (thanks Tim Eliseo) + - Improved decoding of HEIC/HEIF metadata + - Removed useless write support for QuickTime date/time tags in HEIC/HEIF + images + - Fixed "Incorrect XMP stream length" problem when writing some INDD files + +Sept. 28, 2017 - Version 10.62 + + - Added preliminary support for HEIC/HEIF images + - Added support for Google depthmap metadata (XMP-GDepth) + - Added some new Sony/Minolta lenses (thanks LibRaw and Jos Roost) + - Added a new CanonModelID (thanks Norbert Wasser) + - Added a new Nikon LensID (thanks Michael Tapes) + - Decode a new Sony tag (thanks Jos Roost) + - Decode some new Nikon tags (thanks Warren Hatch) + - Decode maker notes from Leica TL2 + - Enhanced ID3 -v2 output to show frame flags + - Fixed problem decoding Nikon D810 camera tilt angles + - Fixed problem where SphericalVideoXML metadata was deleted when writing XMP + to a QuickTime-format file containing this information + +Aug. 18, 2017 - Version 10.61 + + - Added a new Canon LensType (thanks LibRaw) + - Added a number of new Sigma, Nikon and Sony lenses (thanks Jos Roost) + - Added a new Nikon LensID (thanks Yang You) + - Decode a number of new Sony tags (thanks Jos Roost) + - Decode Panasonic FocusDistance (thanks David Ellsworth) + - Updated to the IPTC video metadata 1.1 specification + - Restored the ability to delete JpgFromRaw from RAW images (broken in 10.38) + - Fixed problem decoding maker notes from Pentax K-70 AVI videos + - Fixed problem conditionally replacing a tag if the value to be deleted was + taken from another tag while the new value was assigned directly + +July 21, 2017 - Version 10.60 + + - Added two new Sony/Minolta LensTypes (thanks Jos Roost) + - Added a new Pentax LensType (thanks Dieter Pearcey) + - Added new Composite UniquePathPoints tag to photoshop_paths.config + - Extract raw-data JFIF/JFXX thumbnails as ThumbnailTIFF + - Improved Sony LensSpec conversion + - Updated German translations (thanks Herbert Kauer) + - Set family 1 group name of JFXX ThumbnailImage to JFXX instead of JFIF + - Fixed problem with %C no longer incrementing properly + +July 7, 2017 - Version 10.59 + + - Added a new Canon LensType (thanks LibRaw) + - Added a new Nikon LensID + - Added "wrong extension" warning to experimental Validate feature + - Decode Pentax maker notes in Q-S1 AVI videos + - Updated iptc2exif.args and exif2iptc.args to support new EXIF OffsetTime + tags (thanks Herb) + - Patched potential problem with "Use of uninitialized value $pos" error when + importing malformed JSON data + - Patched to avoid runtime warning due to invalid Nikon ShutterCount value + - Raise an error if -b is used with the -csv option + - Changed PNG exIf chunk name to eXIf + - Fixed bug introduced in 10.26 which could cause hang when %C is used in an + output file name + - Fixed MWG:DateTimeOriginal and MWG:CreateDate to return XMP when EXIF and + IPTC don't exist + +June 29, 2017 - Version 10.58 + + - Added read support for RIFF-format MBWF/RF64 files + - Added write support for dji-drone XMP tags + - Added a new Canon LensType (thanks Steve Bates) + - Added a few new Sony/Minolta LensType values (thanks Jos Roost) + - Added a couple of new CanonModelID values (thanks LibRaw and Norbert Wasser) + - Decode some new FujiFilm tags (thanks Chris Schucker) + - Enhanced FileSize print conversion to show in "GB" for large files + - Fixed "outside of string in unpack" errors when reading some corrupted + EXE/ICC files + - Fixed problem extracting GIF MIDISong metadata + +June 20, 2017 - Version 10.57 + + - Added a new Canon LensType (thanks Norbert Wasser) + - Added write support for PNG Collection tag + - Added a few new CanonModelID values + - Added some new Pentax ShakeReduction values + - Removed ability to create PNG zxIf chunks + - Documented -progress:%b feature (added in 10.26) + - No longer report FileType, FileTypeExtension or MIMEType for JPEG/TIFF + images with an unknown header + - Relaxed case requirement for "SourceFile" header in CSV and JSON import + - Fixed decoding of Pentax ExposureCompensation for newer Ricoh models + - Fixed some incorrect "Wrong IFD" messages with experimental Validate feature + - Fixed a couple of Sony/Minolta lens names (thanks Jos Roost) + - Fixed "Error reading PreviewImage" warning for some Sony models + +June 6, 2017 - Version 10.56 + + - Removed ordering constraints between Geotag/Geosync and Geotime assignments + on the command line + - Removed debugging print statement left in photoshop_paths.config + +June 5, 2017 - Version 10.55 (production release) + + - Added support for GIF multimedia extensions + - Added a couple of new Sony/Minolta lenses (thanks Chris) + - Added a new Nikon LensID (thanks Jakob Dettner) + - Added new Composite TotalPathPoints tag to photoshop_paths.config + - Decode a number of new Sony tags and updated some others (thanks Jos Roost) + - Decode a new Pentax tag and fixed decoding of another (thanks Andras + Salamon) + - Updated iptcCore.args for new IPTC specification + - Changed description of a couple of AVI Model tags to match EXIF + - Patched tests to avoid failures with Perl 5.25.11 due to missing "." in @INC + - Fixed an incorrect warning from the experimental Validate feature + +May 26, 2017 - Version 10.54 + + - Added support for Google XMP GImage and GAudio tags + - Added a new Olympus CameraType (thanks LibRaw) + - Added a two new Sony lenses and decode more ILCE-9 tags (thanks Jos Roost) + - Added new values to some Pentax tags (thanks Andras Salamon) + - Added a new Canon LensType + - Added an additional checks to the experimental Validate feature + - Improved user-defined FileTypes feature to provide more flexibility + - Enhanced -ext option to allow specific files extensions to be processed + along with supported files + - API Changes: + - Added ListJoin option to replace List and ListSep options + +May 17, 2017 - Version 10.53 + + - Added support for "MeSa" Photoshop IRB resource + - Made XMP-GSpherical tags writable + - Improved German translations (thanks Jobi) + +May 12, 2017 - Version 10.52 + + - Added some new values to a number of FujiFilm tags and changed some others + (thanks Albert Shan) + - Decode a number of new Sony tags for the ILCE-9 (thanks Jos Roost) + - Made SonyISO writable + - Changed behaviour of advanced formatting expression for Shortcut tags so it + now applies to the combined value rather than individual constituent values + - Minor changes to some Pentax print conversions + - Fixed problem using new NoDups utility with Shortcut tags + +May 2, 2017 - Version 10.51 + + - Added "NoDups" utility function for use in advanced formatting expressions + - Added a new Pentax LensType (thanks JohnK) + - Added some new Pentax DriveMode values (thanks Andras Salamon) + - Enhanced -ver option to report Perl include directories with -v2 + - Improved warning message when advanced formatting expression returns undef + - Minor change to a few FujiFilm print conversion strings (thanks Albert Shan) + - Changed behaviour when interpolating Shortcut tags in a string (the values + are now separated according to the -sep option setting instead of simply + being concatenated) + - Patched to allow file times to be set on systems where futimes is not + available + +Apr. 20, 2017 - Version 10.50 (production release) + + - Decode a new Pentax tag (thanks Andras Salamon) + - Improved decoding of Olympus DriveMode (thanks Herbert Kauer) + - Improved handling of errors from utime when setting file times + - Fixed potential hang problem when reading corrupted QuickTime metadata + - Fixed problem deleting duplicate EXIF tags when writing other tags at the + same time + +Apr. 10, 2017 - Version 10.49 + + - Added "DateFmt" utility function for use in advanced formatting expressions + - Added a new Sony/Minolta LensType (thanks LibRaw) + - Decode a new Panasonic tag + - Fixed problem decoding Sony VariableLowPassFilter values (thanks Jos Roost) + - Fixed problem setting XMP:About when creating new XMP in a file + - Fixed an incorrect Pentax DriveMode value (thanks Andras Salamon) + - API Changes + - Allow access to the advanced formatting expression via a new ExifTool + "FMT_EXPR" member variable + +Apr. 3, 2017 - Version 10.48 + + - Added some new FujiFlashMode values (thanks Albert Shan) + - Added a new Sony LensType (thanks Jos Roost) + - Added a new Canon LensType (thanks LibRaw) + - Added a new CanonModelID and minor changes to some others (thanks Dmitry) + - Decode two Pentax tags and added a number of new values for other Pentax + tags (thanks Andras Salamon) + - Decode a new Sony tag (thanks Jos Roost) + - Improvements to the experimental Validate feature + - Fixed problem which could cause hang when reading bad PPT documents + +Mar. 20, 2017 - Version 10.47 + + - Added read support for JSON-format files + - Added two new Sony/Minolta lenses (thanks Jos Roost) + - Added a number of new Pentax tag values + - Decode a new Canon CR2 tag (thanks Ed Hannon) + - Decode WB information for Canon 800D (thanks LibRaw) + - Improved config_files/photoshop_paths.config to indicate start of paths + - Attempt to validate new file names in Windows before renaming images + - Experimental Validate feature no longer warns about Windows XP tags + - Fixed problem extracting layer information from very large PSD/PSB files + +Mar. 8, 2017 - Version 10.46 + + - Moved Mac System tags from the Extra table into a new MacOS group and added + ability to extract them by requesting the MacOS group + - Updated QuickTime GenreID list (thanks Francois Bonzon) + - Fixed "Invalid xref" problem when reading some PDF files + - Fixed error in Minolta lens list (thanks Jos Roost) + - Fixed minor problem with -U option generating Unknown tags for some known + bytes in variable-sized strings + - API Changes: + - Enhanced RequestTags option to allow groups to be requested + +Mar. 2, 2017 - Version 10.45 + + - Added ability to write a number of Mac OS X system tags (including the file + creation date!) + - Added ability to extract OS X extended attributes ("XAttr" tags) + - API Changes: + - Added XAttrTags option + - Enhanced RequestAll option + +Feb. 24, 2017 - Version 10.44 + + - Added a few new CanonModelID values and a new Canon LensType + - Added two new Nikon lenses (thanks Rolf Probst) + - Added a few new Sony/Minolta lenses (thanks Jos Roost) + - Added two new Sony MeteringMode values (thanks Jos Roost) + - Improved verbose dump of Photoshop Layer information + - Patched to allow "FileName encoding not specified" warnings to be avoided by + setting -charset filename="" + - Fixed problem in photoshop_paths.config printing some paths + +Feb. 16, 2017 - Version 10.43 + + - Restrict writing of EXIF:FlashEnergy to a single value as per EXIF spec + - Reverted format change of Sony ImageCount tag + - Changed PNG new eXIF/zXIF chunk names to "exIf" and "zxIf" until the + proposed chunks are accepted (of course, while maintaining backward + compatibility for reading/updating the other chunks) + - Lowered priority of XMP-pdf:Keywords so it doesn't take precedence over + PDF:Keywords when the Duplicates option is not used + - Improved config_files/convert_regions.config to handle the case where the + RegionInfoMP is missing a Rectangle + +Feb. 10, 2017 - Version 10.42 + + - Added ability to read/write PNG eXIF and zXIF chunks, and made these the + place where new EXIF is created in PNG images (zXIF if the -z option is + used, or eXIF otherwise) + - Added ability to copy Photoshop OriginPathInfo with photoshop_paths.config + - Made FileUserID and FileGroupID writable + - Changed format for a Sony ImageCount tag + - Improvements to experimental Validate feature + - Fixed incorrect XMP swf namespace URI + - Fixed problem using new -p section feature when combined with -w or -ee + - Fixed formatting problem in -listx output when -lang option was used + - Fixed problem where UserComment wasn't removed if found in IFD0 when writing + it to the correct IFD + +Feb. 1, 2017 - Version 10.41 + + - Added an experimental metadata validation feature (invoked either by + requesting the new Extra Validate tag or by setting the API Validate option) + - Added support for PSDT file extension + - Added age.config to the distribution + - Added a new Sony lens (thanks Jos Roost) + - Added a new PentaxModelID (thanks Louis Granboulan) + - Enhanced -p option to allow files to be grouped in sections + - Made makernote offset warning minor + - Relaxed parsing of NMEA GGA sentence so comma after the geoid units is now + optional + - Patched problem extracting value of an unsafe binary tag with the -b option + when specified using -TAG# instead of -TAG with -n + - API Changes: + - Added experimental Validate option + +Jan. 14, 2017 - Version 10.40 (production release) + + - Fixed tests that were failing on some platforms + +Jan. 13, 2017 - Version 10.39 + + - Added Perl version and Unicode settings to -ver -v output + - Added a new Sony LensType2 value + - Added a new Canon LensType (thanks Norbert Wasser) + - More improvements to sample time_zone.config file (thanks Hayo Baan) + - Fixed problem with MWG date/time tags introduced in version 10.34 + - Fixed problem setting the value of a tag from a binary file when the + PERL_UNICODE environment or the perl -C option is used to force UTF-8 + treatment of @ARGV elements + +Jan. 5, 2017 - Version 10.38 + + - Added a couple of new XMP-ics tags + - Added a new Nikon LensID (thanks Ken Cochran) + - Decode a couple more PhaseOne tags + - Increased priority of Sony 0x0115 WhiteBalance when reading + - Range check QuickTime date/time values when writing + - Apply CharsetPhotoshop setting to decoding of Photoshop LayerNames + - Improved identification of Nikon NRW images + - Minor improvements to verbose dump of FLIR information + - Improvements to sample time_zone.config file (thanks Hayo Baan) + - Removed trailing null in -b output for GPSDateStamp + - Changed "TAG is not supported" warning when writing to "TAG is not defined" + - Changed groups of Composite Preview/Thumbnail/JpgFromRaw/etc images to match + the tags from which they are derived + - Changed description of Composite Nikon LensSpec tag to match the tag name + - Fixed problems reading/writing PreviewImage from some DNG files + +Dec. 19, 2016 - Version 10.37 + + - Decode more information from BMP V4 and V5 images + - Added a few new FujiFlashMode values (thanks Albert Shan) + - Changed -geotime default to use unconverted value of DateTimeOriginal + - Changed a couple of Sony Voigtlander LensType strings (thanks Jos Roost) + - Warn about invalid tag names used on the command line + - Generate default-language version of QuickTime tags even if the same-named + tag already exists in another group + - Fixed bug reading some Photoshop layer information + - Fixed problems in sample config file time_zone.config (thanks Hayo Baan) + +Nov. 24, 2016 - Version 10.36 (production release) + + - Added 3D Studio MAX files to the list of supported file types + - Decode more Sony tags (thanks Jos Roost) + - Decode a couple more FlashPix tags + - Minor changes to some of the new IPTC Extension tags + - Fixed problem reading some FlashPix (Windows Compound Binary Format) files + +Nov. 21, 2016 - Version 10.35 + + - Fixed bug in Windows version introduced in 10.32 which could cause ExifTool + to exit with an error if the -lang option was used + +Nov. 21, 2016 - Version 10.34 + + - Added support for new IPTC Extension version 1.3 + video metadata XMP tags + - Added missing print conversion for PreviewDateTime + - Decode a few new FujiFilm tags (thanks Zilvinas Brobliauskas) + - Enhanced MWG date/time tags to support new EXIF time offsets + - Patched loophole in WriteMode which would allow creation of new metadata + files when creation of new groups was disallowed + - Fixed problem where some EXIF date/time tags may not shifted when shifting + all date/time tags with "-time:all-=VAL" for ExifTool version 10.28-10.33 or + when the MWG feature was used + +Nov. 11, 2016 - Version 10.33 + + - Windows EXE version is 32-bit again, and packaged with Perl 5.24.0 + - Fixed encoding problem with EXIF:Copyright when writing MWG tags using an + alternate EXIF charset + +Nov. 9, 2016 - Version 10.32 + + - WARNING: The Windows EXE version for this release is 64-bit (and packaged + with Perl 5.22.2 instead of 5.24.0) + - Time::Piece may now be used as an alternative to POSIX::strptime for parsing + date/time values when writing, and is included in the Windows package + - Added a number of new XMP tags (thanks StarGeek) + - Added support for a few new Sony cameras (thanks Jos Roost) + - Added new Nikon LensID (thanks Tanel) + - Decode a new Nikon tag (thanks Warren Hatch) + - Decode FLIF encoding type + - Decode a new Samsung tag (thanks Klaus Homeister) + - Ignore -filter option for a tag if it returns an undefined value + +Oct. 19, 2016 - Version 10.31 + + - Added write support for FLIF images + - Added support for animated PNG images + - Added a few new SamsungModelID values + - Added a new Canon LensType + - Added a new Sony/Minolta LensType (thanks Jos Roost) + - Decode more Samsung tags (thanks Klaus Homeister and Sreerag Raghavan) + - Decode more Nikon tags (thanks Warren Hatch) + - Changed "TAG does not exist" warning when writing to "TAG is not supported" + - Fixed problem importing information from CSV or JSON databases for files + with special characters in their name + +Oct. 13, 2016 - Version 10.30 + + - Added read support for FLIF images + - Added a couple of new Minolta/Sony LensType values (thanks Jos Roost) + - Added a new SonyModelID (thanks LibRaw and Jos Roost) + - Added a new digiKam XMP tag + - Decode a new Apple tag (thanks Neal Krawetz) + - Decode a few new FujiFilm tags (thanks Chris Schucker) + - Decode more Nikon D5 custom settings (thanks Warren Hatch) + - Decode a couple more Samsung tags (thanks Klaus Homeister) + - Improved decoding of Nikon D500/D5 ShotInfo information + - Enhanced -ver option to output system information when -v is added + - Minor change to parsing of -@ argfile (comment lines may may no longer have + spaces before the "#") + - Patched Jpeg2000 reader to read bad UUID-EXIF boxes + - Lowered priority of unknown XMP tags when reading + - Fixed problem in new xmp2exif.args date/time arguments introduced in 10.28 + - Fixed potential "Use of uninitialized value" warning when decoding + compressed PNG iTXt chunk + +Oct. 5, 2016 - Version 10.29 + + - Added a couple of new Sony LensType values (thanks LibRaw) + - Decode a few new Sony tags + - Decode a few new FLIR tags + - Decode some new Nikon D5 tags (thanks Warren Hatch) + - Decode a new Apple tag + - Enhanced -geotag option to allow tagging from KML placemarks with a TimeSpan + - Enhanced -d option (and API DateFormat option) to perform inverse date/time + conversion when writing if the POSIX::strptime module is available. If + POSIX::strptime is not available then the behaviour is like older versions + (ie. the date/time is not converted) unless the API StrictDate option is set + in which case a warning is issued and the tag is not written + +Sept. 27, 2016 - Version 10.28 - "Exif 2.31" + + - Added support for new Exif 2.31 tags + - Added some new Canon LensType values (thanks Norbert Wasser for one) + - Added a new Olympus LensType (thanks LibRaw and Niels Kristian Bech Jensen) + - Added a new Sony LensType and SonyModelID (thanks Jos Roost) + - Added a new Pentax LensType + - Added fotoware.config and bibble.config files to the distribution + - Made Composite SubSecDateTimeOriginal, SubSecCreateDate and SubSecModifyDate + tags writable, and expanded to include new Exif 2.31 time zone tags + - Fixed problem writing user-defined structured tag elements with a dot (.) in + their tag ID + +Sept. 23, 2016 - Version 10.27 + + - Added a new CanonModelID (thanks LibRaw) + - Added a new Sony LensType (thanks Jos Roost) + - Added a few new NikonLensID's (thanks Yang You and Robert Rottmerhusen) + - Added a couple of new Olympus LensType values (thanks LibRaw and Niels + Kristian Bech Jensen) + - Added a new Olympus CameraType + - Decode some Canon 80D, 750D, 760D and 1200D CameraInfo tags + - Changed writing of some ExposureTime and ExposureCompensation tags to allow + the exact numerator and denominator of the stored rational value to be + specified + - Fixed Timecode printout in -v3 output for M2TS videos (thanks Ken Neighbors) + - Fixed some problems with the new "-progress:TITLE" feature + - Fixed problem where "_exiftool_tmp" file could be left around after a failed + write attempt + - Fixed potential "isn't numeric" runtime error when reading a PDF file with + the -ee option + +Sept. 15, 2016 - Version 10.26 + + - Added read support for GSpherical metadata in MP4 videos + - Added a few new XMP-xmpMM tags and a new XMP-crs tag + - Added some new Minolta/Sony lenses (thanks Jos Roost) + - Added two new CanonModelID's (thanks Norbert Wasser and Laurent Clevy) + - Added two new Canon LensType's (thanks Norbert Wasser) + - Decode a number of Nikon D610 custom settings (thanks Tor) + - Removed a questionable Samsung tag + - Marked TestName tag as "unsafe" for writing + - Enhanced -progress option with ability to set console window title + - Changed behaviour of %C to increment for each processed file as documented + (was incrementing for each output file created) + - Patched to recreate XMP in the standard location of PNG images when deleting + certain non-standard XMP as a group and recreating in one step + - Fixed runtime warning when writing 0 to MinoltaRaw ISOSetting + - Fixed problem writing SRW images from some newer Samsung models + +Aug. 3, 2016 - Version 10.25 + + - Added a new Pentax PictureMode (thanks Louis Granboulan) + - Added a new Nikon LensID (thanks LibRaw) + - Decode a new Samsung tag (thanks LibRaw) + - Decode a few more Canon tags (thanks Anton Reiser) + - Removed "Avoid" flag for XMP-crs:ColorTemperature + - Changed the format of a number of XMP-GPano tags from integer to real + - Fixed incorrect tag ID's for some obscure Island Graphics EXIF tags + - Fixed decoding of some UTF-8 DNG tags which may be stored in BYTE format + +July 27, 2016 - Version 10.24 + + - Added support for DJI Phantom maker notes + - Added a few more XMP-crs tags + - Added ability to write DNG OpcodeList tags + - Added a new Sony/Minolta LensType (thanks Jos Roost) + - Added a few new FujiFilm Saturation values + - Added a new FujiFlashMode value and fixed an incorrect Italian translation + (thanks Massimo Sanna, ApolloOne) + - Decode more Pentax tags (thanks Louis Granboulan) + - Changed -config option to search the current directory first for the config + file (patches problem introduced in ExifTool 10.21 for Windows where the + working directory might not be searched when using the -config option) + - Changed print conversion of ProcessingTime to show 3 significant digits + - Fixed bug decoding PanasonicRaw DistortionInfo in DNG images + +July 14, 2016 - Version 10.23 + + - Added read support for Ogg Opus audio files + - Added ability to geotag only GPS date/time if no position information is + available by setting Geotag to "DATETIMEONLY" (all caps) + - Added "-charset RIFF" option + - Added a new Sony LensType (thanks Jos Roost) + - Decode a number of new Canon tags (thanks Kai Harrekilde-Petersen) + - Changed handling of special characters in RIFF-format files (eg. AVI, WAV) + - Changed MIME type of OGG files to audio/ogg (was audio/x-ogg) + - Minor change to wording of new Nikon D80 Rotation tag for consistency + - API Changes: + - Added CharsetRIFF option + +July 7, 2016 - Version 10.22 + + - Added read support for BPG images + - Minor changes to a few of the new Nikon tags + - Fixed problem in Windows version where not all 10.21 updates were included + in the release + +June 29, 2016 - Version 10.21 + + - WARNING: The Windows EXE package for this release was built on Windows 10 + using Perl 5.24 instead of Windows XP and Perl 5.8 -- please watch for + problems and report anything that you find + - Added a new Minolta/Sony LensType (thanks LibRaw) + - Added a new element to the XMP Colorant structure + - Added a new Pentax lens (thanks Louis Granboulan) + - Decode Nikon D5/D500 AF information (thanks Michael Tapes for samples) + - Decode a number of new Olympus tags (thanks Daniel Pollock) + - Decode a number of new Nikon tags (thanks Warren Hatch) + - Decode Pentax K-1 AF points (thanks Louis Granboulan) + - Extract a new DPX tag + - Patched to avoid writing an empty structure field for an undefined value + +June 13, 2016 - Version 10.20 (production release) + + - Added a few new Sigma LensTypes (thanks LibRaw and Jos Roost) + - Added a new Sony LensType (thanks Jos Roost) + - Added two new Canon LensTypes (thanks Jos Roost and Norbert Wasser) + - Added a couple of new PentaxModelID's (thanks Louis Granboulan for one) + - Added a new Pentax LensType (thanks Louis Granboulan) + - Added a few new Olympus PictureMode values (thanks Daniel Pollock) + - Added a few more XMP tags + - Decode a new Nikon video tag (thanks Hayo Baan) + - Patched to allow protected binary data tags to be extracted when -b is + combined with -php or -X if the tag is specifically requested + - Fixed bug introduced in version 9.96 where extended XMP is ignored if the + MWG module is used + - Fixed problem where the MWG module wasn't loaded automatically if there were + MWG tags in the argument to the -p option + - Fixed column alignment of alternate-language output (requires + Unicode::LineBreak to be installed) + - Fixed problem writing Sigma:LensFocalRange + +May 31, 2016 - Version 10.19 + + - Added a few new Sony and Sigma LensType values (thanks Jos Roost) + - Decode more Nikon tags (thanks Warren Hatch) + - Fixed an incorrect Sigma LensType (thanks LibRaw) + - Fixed decoding of D500 custom settings for NEF images (thanks Warren Hatch) + +May 27, 2016 - Version 10.18 + + - Added a number of new Sigma LensTypes (thanks LibRaw) + - Added a few new Sony/Minolta lenses (thanks Jos Roost) + - Added ability to write FilePermissions + - Decode NikonCustom settings for the D500 (thanks Warren Hatch) + - Decode PLUS MediaSummaryCode values + - Use hexadecimal for Sigma LensType values + - Changed -fileOrder option to sort numbers in strings numerically + - Fixed typo in Samsung lens name + +May 16, 2016 - Version 10.17 + + - Added support for Leica X-U (Typ 113) maker notes + - Added a new Pentax LensType (thanks Louis Granboulan) + - Added a number of new Sony lenses (thanks Jos Roost) + - Added a new Canon LensType (thanks Mees Dekker) + - Extract TIFF-format thumbnails and previews + - Patched to ignore XML entities inside comments + - Fixed inconsistent conversion of PreviewColorSpace values + - Fixed writing of TargetPrinter tag + - Fixed bug introduced in 10.16 which which could cause a runtime warning when + using the -o option and not writing any "real" tags + +May 3, 2016 - Version 10.16 + + - Added %D, %F and %E filename format codes + - Added a new Minolta lens (thanks Jos Roost) + - Decode Photoshop Compression mode + - Decode Nikon MultiExposure information for the D5 + - Updated decoding of Sony tags for ILCA-68 (thanks Jos Roost) + - Fixed bug adding back XMP tags after deleting all XMP from MOV/MP4 files + - Fixed problem using -o option when reading from stdin (ie. FILE is "-") + - Fixed problem where user-defined Composite tags may not always override + pre-defined Composite tags with the same name, and added feature to allow + the user to specify whether they should override existing tags or not + +Apr. 20, 2016 - Version 10.15 (production release) + + - Added .a and .o to the list of supported file types + - Added a few new Sony/Minolta lenses (thanks Jos Roost and LibRaw) + - Decode more Photoshop tags (thanks Taylor Bangs for some) + - Decode more information from static library (.a) files + - Decode a few more tags from GoPro MP4 videos (thanks Calvin Hass) + - Decode ColorData for Canon EOS 1300D (thanks LibRaw) + - Updated Sony decoding for newer models (thanks Jos Roost) + - Fixed bug where ScaleFactor35efl could be calculated incorrectly for Canon + images from some models which have had their EXIF rebuilt + +Apr. 8, 2016 - Version 10.14 + + - Added read support for ISO 9660 disk images + - Added a few new Nikon ISOExpansion values (thanks LibRaw) + - Added a few new Olympus LensType values (thanks Niels Kristian Bech Jensen) + - Added a couple of new SonyModelID values (thanks LibRaw for one) + - Added a new Olympus CameraType + - Added config_files/gps2utm.config to the distribution + - Decode Canon ColorData for the EOS 80D (thanks LibRaw) + - Decode a few new Samsung tags (thanks Francois) + - Decode a new Fuji tag (thanks Frank Markesteijn) + - Calculate Duration for APE audio files + - Tightened constraints on M2TS file recognition + - Improved verbose dump of ID3 information + - Changed XMP-acdsee:Snapshots to a Binary data tag + - Fixed bug which prevented writing of various Sony FocalLength tags + - API Changes: + - Fixed bug where FileModifyDate wasn't set properly when WriteInfo() was + called without a destination file name and other "real" tags were + written at the same time + +Mar. 12, 2016 - Version 10.13 + + - Added a few new Canon LensType values (thanks Niels Kristian Bech Jensen and + LibRaw) + - Added a new CanonModelID + - Added a number of new Nikon RetouchHistory modes + - Decode a number of new Sony tags (thanks Jos Roost) + - Changed a couple of Sigma "| C" lens names for consistency + - API Changes: + - Fixed bug which could cause the API Filter option to be ignored for some + tags when copying tags with the Composite option set + +Mar. 4, 2016 - Version 10.12 + + - Added a new PentaxModelID and SonyModelID (thanks LibRaw) + - Added a number of new CanonModelID values (thanks Norbert Wasser for one) + - Added a new Olypus LensType (thanks Niels Kristian Bech Jensen) + - Added two new Pentax LensType values + - Added a few new Nikon LensID values and updated some others + - Added a new Canon LensType (thanks Norbert Wasser) + - Decode a new Nikon tag + - Decode a new CanonCustom tag for the EOS 80D + - Improved decoding of SonyRawFileType (thanks Jos Roost and LibRaw) + - Changed "Optimised" to "Optimized" in a Photoshop tag value + - Fixed warning that could be generated by the Canon FileNumber conversion + +Feb. 17, 2016 - Version 10.11 + + - Added a couple of new Olympus CameraType values (thanks LibRaw for one) + - Added some new ACDSee XMP tags (thanks Malus) + - Added a few more XMP-crs tags + - Added a new CanonModelID (thanks Norbert Wasser) + - Added a couple of new Sony LensType values (thanks Jos Roost and LibRaw) + - Added support for PDF ASCII85Decode filter + - Decode a number of new Sony tags (thanks Jos Roost) + - Decode a new Canon tag (thanks Juha Iso-Sipila) + - Decode a few more Photoshop tags + - Convert MDItem date/time values to local time + - Patched to read incorrectly written DJI GPSCoordinates in MOV videos + +Jan. 22, 2016 - Version 10.10 (production release) + + - Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) + - Added a couple of new Olympus FlashModel values + - Added a new Nikon LensID + - Added a new Pentax LensType + - Decode a number of new Sony tags (thanks Jos Roost) + - Decode H264:DateTimeOriginal DST flag, and add " DST" to time string if set + - Decode a few more CanonCustom settings + - Fixed problem creating user-defined XMP structure elements with names + containing characters which are illegal in tag names + - Improved mechanism for generating tags which must be specifically requested + when copying or used in -if or -p expressions + - Improved warning in Windows if help documentation file can't be created + - Patched Composite:FileNumber to handle case where Canon:FileNumber is 10000 + - Patched reading FujiFilm RawImageWidth/Height for new X-Pro2 RAF images + - Fixed problem reading PDF objects which begin with a comment line + - Fixed problem which could result in ExifTool corrupting a PDF file when + writing + - API Changes: + - Added RequestTags option + - No longer generate MDItem tags when RequestAll option is set + +Jan. 4, 2016 - Version 10.09 + + - Added ability to extract OS X system metadata ("MDItem" tags) + - Added a value conversion for GoogleTrackDuration + - Enhanced the -i option to allow full path names to be specified + - Fixed a potential runtime error when writing corrupted JPEG images + - API Changes: + - Added MDItemTags option + +Dec. 22, 2015 - Version 10.08 + + - Added ability to write empty XMP structures + - Added write support for PhaseOne MakerNotes tags in IIQ files + - Added a new Nikon LensID (thanks David Puschel) + - Decode a new Olympus tag and improved decoding of DriveMode + - Minor improvements to HtmlDump of PhaseOne IIQ and PDF files + - Patched to allow overwriting of empty XMP written by some PhaseOne cameras + - Fixed bug in HtmlDump feature that could cause a "substr outside of string" + error + - API Changes: + - Changed API QuickTimeUTC option to also enforce proper time zero + +Nov. 26, 2015 - Version 10.07 + + - Fixed problem with warnings on some systems about unimplemented functions + for FileGroupID and FileUserID when -p or -if were used + +Nov. 26, 2015 - Version 10.06 + + - Added a new Canon LensType (thanks LibRaw) + - Added a new Pentax LensType (thanks Louis Granboulan) + - Added a couple of new XMP-digiKam tags + - Added a new CanonModelID + - Added a new ACDSee XMP tag (thanks Malus) + - Decode a new Canon tag + - Improved a few lens names (thanks Jos Roost) + - Patched to remove trailing null when reading improperly written QuickTime + strings + - Fixed bug where SystemTags weren't available for use with the -p and -if + options + +Nov. 6, 2015 - Version 10.05 + + - Added new Canon and Sony LensType values + - Added some new Nikon LensID's (thanks Robert Rottmerhusen) + - Added a new PentaxModelID + - Added preliminary support for Motorola maker notes + - Changed format of XMP-photoshop:DocumentAncestors to write a simple Bag of + strings rather than structures (since this is what the Adobe software + writes, contrary to their own XMP specification) + - Fixed problem where HASH references may be exposed when copying a list of + structures to a non-list-type tag + - API Changes: + - Added Filter option + - Changed GetNewValues method name to GetNewValue (GetNewValues still + works for backward compatibility) + +Oct. 28, 2015 - Version 10.04 + + - Added a few new Microsoft XMP tags + - Enhanced -r option to allow processing of directory names beginning with "." + - Avoid writing XMP-microsoft:LensModel unless specified explicitly + - Limit precision of area size in picasa_faces config file (thanks StarGeek) + - Moved Nikon NCDT GPS tags into the GPS group so they work with the Composite + GPS tags + - Fixed problem reading Ricoh RMETA information from some cameras + +Oct. 21, 2015 - Version 10.03 + + - Added support for JPEG 2000 extended-length boxes + - Added a few new Canon LensType and CanonModelID values + - Added a new Nikon LensID (thanks LibRaw) + - Added ExifTool version number as a comment in -listx output + - Added support for Leica SL (Typ 601) maker notes + - Added a new Sony LensType (thanks Jos Roost) + - Decode more Sony tags for some new models (thanks Jos Roost) + - Decode a new Pentax tag + - Patched for invalid makernote entry written by Sony ILCE-7M2 v1.21 + - Patched problem reading EXE resources with a missing null terminator + - Updated Windows distribution package to use latest version of PAR + +Sept. 17, 2015 - Version 10.02 + + - Added ability to read PNG chunks after the normal PNG end of file (IEND) + - Added ability to delete a PNG trailer (with -trailer:all=) + - Added some new Nikon LensID's (thanks Robert Rottmerhusen and LibRaw) + - Added a few new Canon LensType's (thanks Jos Roost and LibRaw) + - Added a new Pentax LensType (thanks Niels Kristian Bech Jensen) + - Decode some new PanasonicRaw tags (thanks Andrew) + - Decode a new Pentax tag + - Enhanced -m option to allow IPTC values which are too short to be written + +Sept. 3, 2015 - Version 10.01 + + - Added a new Olympus CameraType + - Added a new Canon LensType (thanks Norbert Wasser) + - Added a few new QuickTime GenreID values (thanks Francois Bonzon) + - Decode a new Pentax tag + - Leica programmers should all be ashamed of the complete shambles of metadata + they have created + - Minor change to the name of a Sigma lens for Canon + - Improved recognition of Pentax lens adapter + - Implemented NoPDFList for -b option of Windows version too + - Renamed a few Sony tags and improved decoding of others (thanks Jos Roost) + - Renamed a few Pentax tags + - Fixed problem importing structured information from -X option output + - Fixed round-off errors in value and typo in name of CanonVRD:GammaWhitePoint + - Fixed test failures if Encode, POSIX or Time::Local modules are missing + +Aug. 18, 2015 - Version 10.00 (production release) + + - Added a few new CanonModelID's + - Added a few new Nikon LensID's (thanks Jaap Voets and Robert Rottmerhusen) + - Added a new Canon LensType (thanks Jos Roost) + - Decode a number of new Sony tags (thanks Jos Roost) + - Decode a couple more RIFF tags and extract Unknown RIFF tags + - Changed -b option to avoid splitting PDF List-type tag values + - API Changes: + - Added NoPDFList option + +July 23, 2015 - Version 9.99 + + - Added support for the Leica Q (Typ 116) maker notes + - Added two new SonyModelID's (thanks Jos Roost for one) + - Added two new Sony LensType2 values (thanks Jos Roost) + - Added a new Pentax LensType + - Extract a number of new File System tags when API SystemTags option is set + - Decode a new FujiFilm tag (thanks TonyB) + - Decode a number of new Sony tags (thanks Jos Roost) + - Disabled writing of XMP to PostScript-format Adobe Illustrator files + - SourceFile values in -csv and -json input/output are now converted to/from + filename character set (set by -charset filename option) when + reading/writing + - Renamed Flash:FileAttributes to FlashAttributes + - Renamed FujiFilm:AFPointSet to AFMode and improved decoding + - Fixed problem where a partial command could be executed if the -stay_open + option was used and the command is aborted due to an error in arguments + - Fixed problem with OS X installer on El Capitan (now installs in + /usr/local/bin instead of /usr/bin) + - API Changes: + - Added SystemTags option + +June 26, 2015 - Version 9.98 + + - Added read support for DSS and DS2 file formats + - Added write support for XMP-mwg-rs:Rotation (seen in XMP from LR6) + - Added a new Sigma LensType (thanks Niels Kristian Bech Jensen) + - Decode Pentax DiffractionCorrection + - Decode Olympus ID3 XOLY frame + - Decode a few more Sony tags + - Improved reliability of decoding Nikon D810 ShotInfo and custom settings + - Changed name of Pentax VignettingCorrection to PeripheralIlluminationCorr + - Fixed problem with user parameters in tag name expressions when copying + +June 2, 2015 - Version 9.97 + + - Added a new Pentax Quality value + - Added a new Panasonic AdvancedSceneMode (thanks Horst Wandres) + - Minor improvement to lens identification logic based on XMP information + - Changed a few DR4 tag names + - Fixed improper scoping of XMP namespace prefixes (so conflicting prefixes + are now properly resolved) + +May 20, 2015 - Version 9.96 + + - Added support for CanonVRD version 4 information and DR4 files + - Added two new Canon LensType values (thanks Norbert Wasser) + - Added two new Olympus LensType values (thanks Niels Kristian Bech Jensen) + - Added a new Minolta/Sony LensType and fixed another one (thanks Jos Roost) + - Added a new Nikon LensID (thanks John Helour) + - Added a few new QuickTime tags + - Added a new PentaxModelID and a new Pentax PictureMode + - Added a few new XMP-aux tags + - Decode a couple more DPX tags + - Decode NikonCustom tags for D810 firmware version 1.02 + - Improved -htmlDump output for extended XMP and some other JPEG segments + - Improved a Canon lens name + - Documented the -userParam command-line option (which has existed since 9.90) + - Changed default behaviour to ignore extended XMP with an incorrect GUID (as + per the XMP specification) + - Changed the case of a few tag names for consistency + - Patched problem with Sony cameras giving incorrect LensInfo for some third + party lenses, leading to an incorrect LensID by ExifTool (thanks Jos Roost) + - Patched problem where GPS minutes or seconds could round up to 60 + - Fixed picasa_faces.config to rotate regions if necessary for RAW file types + (thanks Stargeek) + - API Changes: + - Added ExtendedXMP option + +May 9, 2015 - Version 9.95 + + - Added a few new Minolta/Sony lenses (thanks Jos Roost) + - Added config_files/photoshop_paths.config to the full distribution + - Avoid rebuilding maker notes when using -tagsFromFile with -fast2 option + - Validate tag names when redirecting (ie. "-DSTTAG 1) + - Avoid writing a few XMP-crs tags which have XMP-exifEX equivalents unless + specified explicitly + - Patched POD documentation in MIE.pm to remove non-ASCII characters + - Fixed bug adding back XMP tags in PDF files after deleting all in the same + command (also made XMP the preferred group when writing PDF files) + - Fixed bug extracting some font information from QuickTime videos + - Fixed inconsistencies in behaviour when extracting XML as a block from + JPEG2000 images + - Fixed problem where FileName was changed when using -srcfile option and + writing only the Directory + - API Changes: + - Added the PNGEarlyXMP option + - Internal Changes: + - Changed all "$exifTool" variable names to "$et" throughout -- my + apologies to the diff engines + +Oct. 19, 2013 - Version 9.39 + + - Added a new PentaxCameraID and some new LensTypes (thanks Louis Granboulan) + - Added a new Nikon LensID + - Added a new Panasonic ShutterType + - Additions and improvements to Pentax makernote decoding for K-3 + - Decode a number of new tags including purchase information from MP4 videos + - Decode FLIR information acquired by Extech MeterLink meters (thanks Tomas) + - Decode more Sony tags (thanks Jos Roost) + - Patched to suppress the run-time "No such file or directory" error that has + been seen when using the -csv option on Windows systems + +Oct. 7, 2013 - Version 9.38 + + - Added read support for DPX images + - Added a new Pentax LensType + - Added a few new CanonModelID values + - Added a new XMP-apple-fi tag + - Decode a few more Canon tags (thanks Tomasz Kawecki) + - Decode a few more Sony tags (thanks Jos Roost) + - Improved the names of a few Apple tags (thanks Neal Krawetz) and added new + Composite tag + - Tolerate NMEA sentences with missing degrees written by some crappy software + - Changed Duration conversion to print number of days if more than 24 hours + +Sept. 14, 2013 - Version 9.37 + + - Added support for maker notes from Apple iPhone5 iOS 7 + - Added two more Torrent tags + - Added a new Pentax DigitalFilter + - Added new Olympus CameraType and LensType values + - Decode a couple more Olympus tags + - More improvements to Sony decoding (thanks Jos Roost) + - Improved decoding of Scalado JPEG APP4 information + - Fixed problem where a PreviewImage could be reported in either the File or + Composite group, depending on the details of the command + +Sept. 7, 2013 - Version 9.36 + + - Added read support for BitTorrent description files (bencode format) + - Added a couple of new Nikon LensID's (thanks Jurgen Sahlberg) + - Added support for PNG 8bim raw profile + - Added or fixed a few Pentax LensType values (thanks Louis Granboulan) + - Added ability to delete DNGAdobeData and DNGPrivateData + - Decode more Sony tags and improved decoding of others (thanks Jos Roost) + - Decode a number of new FujiFilm tags and fixed 2 incorrect Saturation values + - Decode a number of Canon CameraInfo tags for the 70D (thanks Tomasz Kawecki) + - Patched to issue minor warning and extract only the first 1000 values from + XMP list-type tags containing more than 1000 items (all values may be + extracted by ignoring this warning with the -m option) + - Patched decoding of PNG IPTC raw profile to allow either IIM or IRB data + - Flagged ImageSourceData as "unsafe" (avoids excessive memory usage when + copying all tags because this data may be larger than the image itself for + Photoshop TIFF images) + - Disabled feature introduced in version 9.14 which allowed multiple tags + (specified by wildcards) to be copied into a single list. This feature had + the unintended side-effect of generating duplicate list items when copying + list-type tags if there were multiple source tags with the same name. If + necessary, -addTagsFromFile may still be used to copy the values of multiple + tags into a single list. + +Aug. 17, 2013 - Version 9.35 + + - Added a new Canon LensType (thanks Oliver) + - Added two new Olympus CameraType values + - Added some new Pentax LensType values (thanks Louis Granboulan) + - Added a new RIFF StreamType value + - Decode a number of new Sony tags (thanks Jos Roost) + - Decode CameraTemperature from more Canon models + - Extract thumbnail information from Leica X VARIO MP4 videos + - Improved decoding of Pentax LensData (thanks Louis Granboulan) + - Patched to avoid a warning for the messed-up Leica M maker notes + - Changed a few Pentax Samsung/Schneider lens names for consistency + - Changed "Can't delete" message to indicate if the tag is Permanent + - Fixed the case of a few tag names (thanks Romain) + +July 27, 2013 - Version 9.34 + + - Added support for Ricoh GR maker notes (in MOV videos too) + - Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) + - Added a new Canon LensType (thanks Norbert Wasser) + - Added support for Sony DSC-TF1 maker notes (thanks Jos Roost) + - Added patch for messed up Leica M (Typ 240) MakerNote trailer + - Added a few new CanonModelID's and a few new PentaxModelID's + - Added some new XMP-crs tags written by LR5 + - Added a few new Nikon LensID's + - Decode a number of new Ricoh GR tags (thanks Tim Gray) + - Recognize the Nikon SB-700 external flash + - Updated MWG location tags to conform with the MWG 2.0 specification (but + continue writing legacy IPTC Core location tags) + - Removed "[Minor]" designation from "excessive count" warning if count is + greater than 2M + - Avoid processing multiple EXIF IFD's if only one should exist + +July 13, 2013 - Version 9.33 + + - Added support for EXIF UTF-16 Unicode text (previously treated as UCS-2) + - Added support for Leica X Vario maker notes + - Added a couple of new SonyModelID values (thanks Jos Roost) + - Added a new CanonModelID and a new Olympus CameraType + - Added a new Canon LensType + - Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) + - Decode some new Panasonic tags and added values for others (thanks Thomas) + - Improved decoding of Olympus RawDevArtFilter + - Improved decoding of some Sony tags for the RX100M2 + - Changed application to always return an error status when exiting if an + error was encountered when extracting information + +June 22, 2013 - Version 9.32 + + - Added support for "Exif 2.3 for XMP" tags + - Added a few new Olympus LensTypes (thanks Niels Kristian Bech Jensen) + - Added a few new PentaxModelID's + - Added two new Samsung LensType values (thanks Pascal de Bruijn) + - Decode a new Olympus tag + - Fixed problem extracting audio comments from Ricoh G700SE images + - Fixed a non-conforming CanonModelID string + +June 8, 2013 - Version 9.31 + + - Added a number of new Photoshop tags (but marked as Unknown) + - Added a few new values for some Olympus tags + - Added conversion for Olympus SensorTemperature + - Added two new CanonModelID's + - Added support for Reconyx firmware 4.0.0 + - Decode a number of new QuickTime and FLIR tags in MP4 videos + - Decode more Sony tags (thanks Jos Roost) + - Decode a new Olympus tag + - The API List option may now be used in the config file for the same effect + as -sep when combined with the -X, -j or -php option on the command line + - Fixed problem where some QuickTime string values could have terminating NULL + characters, which caused problems when renaming files using these tags + +May 25, 2013 - Version 9.30 + + - Added a new Canon LensType + - Decode many Canon 700D CameraInfo tags + - Also delete null characters with the default advanced formatting filter + - Tolerate leading whitespace in HTML files + - Fixed decoding of Canon 650D CameraInfo FocalLength + - Fixed bug in new advanced formatting feature which gave incorrect + output when used in the -p option for processing multiple files + +May 18, 2013 - Version 9.29 + + - Added another H264 Model value (thanks Rob Lewis) + - Added support for Canon 5DmkIII firmware version 1.2.1 + - Added recognition of IBM AVC video files + - Added a new CanonModelID + - Decode more FLIR tags (thanks Tomas) + - Decode H264 MDPM TimeCode + - More improvements to Sony LensType decoding (thanks Jos Roost) + - Extract information from the ASF Metadata Library in WMV files + - Extract ColorBalanceVersion for unknown Nikon ColorBalance information + - Updated some ID3 Genre names (thanks Mats Peterson) + - Fixed warning when using -p with a string containing a newline + - Fixed some incorrect Pentax Q LensType values + +Apr. 21, 2013 - Version 9.28 + + - Added the ability to delete unknown JPEG APP segments by segment name + - Added a bunch of new ID3 Genre values (thanks Mats Peterson) + - Decode a few more Sony tags (thanks Jos Roost) + - Decode a few more tricky FLIR tags (thanks Tomas) + - Improved Dutch language translation (thanks Peter van der Laan) + - Patched to avoid warning in images where the AFMicroAdj data has been + truncated by Canon DPP + - Fixed -tagsFromFile and -v so they may now be used when writing via pipes + - Fixed writing of Panasonic LensType tags that were broken in the 9.15 update + - Fixed incorrect case for list type of XMP DocumentAncestors and TextLayers + - API Changes: + - Allow a File::RandomAccess reference as an input to WriteInfo() + +Apr. 15, 2013 - Version 9.27 (production release) + + - Fixed "ARRAY ref" runtime error introduced in 9.25 that could occur when + using the -X option + - Fixed runtime warning which could occur when conditionally deleting XMP + structure + +Apr. 13, 2013 - Version 9.26 + + - Added read support for FLIR FFF and FPF images and decode more FLIR tags + - Added some new Pentax LensType's and Nikon LensID's + - Added a few new Panasonic ContrastMode values + - Decode a number of Canon 6D tags + - Allow CanonRaw tags to be written using "CIFF" as a group name + - Improved decoding of Canon ColorData information for newer EOS models + - Improved decoding of a number of Sony tags (thanks Jos Roost) + - Removed index number from duplicate Composite TagID's in XML output + - Fixed byte-order problem for a few Nikon D5200 and D7100 tags + - Fixed incompatibility with old-style (pre-8.46) XMP user-defined structure + definitions + +Apr. 6, 2013 - Version 9.25 (production release) + + - Added read support for FLIR thermal image metadata in JPEG images + - Added write support for DNG version 1.4 images + - Added a new Pentax DriveMode value and a new Pentax LensType + - Added two new Olympus CameraType values + - Added print conversion for XMP Flash tags to provide alternate language + support + - Decode a few more Nikon and Pentax tags + - Decode more Sony tags (thanks Jos Roost) + - Decode more Panasonic tags and changed decoding of others + - Enhanced -j and -php options to work with -D, -H and -l + - Improved German translations (thanks Herbert Kauer) + - Patched decoding of QuickTime date/time tags to accommodate Samsung and Sony + cameras that use an incorrect time zero of 1970 instead of 1904. This patch + will only work for videos produced before 2036, so hopefully Samsung and + Sony will fix this problem at their end before then (care to place a wager?) + - Fixed issues when using "-wm cg" and writing metadata as a block + - Fixed possible "division by zero" error when reading undefined XMP rational + +Mar. 23, 2013 - Version 9.24 + + - Added ability to overwrite plus append output files (-w+!) + - Added support for Sigma X3F version 3.0 images + - Added a few new values for some Pentax tags + - Added a few new CanonModelID's + - Decode Nikon D5100 and D5200 custom settings plus a few other Nikon tags + - Allow the value for missing tags extracted with the -f option to be + configured via the API MissingTagValue setting (default is still "-") + - Improved decoding of Sony LensSpec (again, thanks Jos Roost) + - Fixed bug reading QuickTime extended-size atoms + +Mar. 10, 2013 - Version 9.23 + + - Added -W (-tagOut) and -Wext (-tagOutExt) options to allow multiple tags + to be extracted to separate output files from a single source file + - Added append feature to -w (-w+) + - Added ability to extract SoundFile from Ricoh RMETA + - Added more SonyModelID and Sony LensType values and improved Sony LensType + decoding (thanks Jos Roost) + - Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) + - Added another Pentax LensType + - Decode more Nikon flash information (thanks Alyda Gilmore for the samples) + - Decode Pentax Kelvin white balance tags (thanks Klaus Homeister) + - Extract PDF embedded image color space + - Improved Spanish translations (thanks Emilio Sancha) + - More patches to avoid "APP1 segment too large" errors when copying all tags + from some RAW images + +Mar. 2, 2013 - Version 9.22 + + - Fixed problem extracting metadata from encrypted embedded JPEG images in PDF + files and added the ability to extract JPEG 2000 information too + +Mar. 2, 2013 - Version 9.21 + + - Added ability to extract embedded images and their metadata from PDF files + - Added read support for binary-format PLIST files + - Added support for Sigma DP3 Merrill maker notes + - Added a few new Sigma LensType values + - Added a new FujiFilm PictureMode value + - Decode a number of new Pentax tags (thanks Klaus Homeister) + - Decode more Sony tags (thanks Jos Roost) + - Decode some new Nikon D800 tags (thanks Alyda Gilmore for the samples) + - Decode a number of new tags in 3GP videos + - Decode Pentax CameraType + - Made a few more DNG tags writable (but protected) + - Fixed problem reading XREF table of some PDF files + - API Changes: + - The CombineInfo() routine is now deprecated because it is likely that + nobody ever used it. If anyone actually uses this, please let me know + +Feb. 20, 2013 - Version 9.20 + + - NOTICE: This release fixes a problem in the 9.19 Windows version that could + cause ExifTool to crash when writing metadata to some files (it seems that + one of the files in the 9.19 Windows package was corrupted) + - Added a new PentaxModelID + - Added write support for a few Getty Images XMP tags + - Decode Sony AFAreaModeSetting (thanks Jos Roost) + +Feb. 20, 2013 - Version 9.19 + + - Added read support for Phase One IIQ maker notes + - Added a couple of new Minolta Teleconverter values + - Patched problem which could result in runtime warning when extracting + information from a file with an incorrectly formatted PreviewImage pointer + - Improved handling of unknown maker notes when writing to reduce the chance + of corruption (fixes problem of corrupted SilverFast maker notes) + - Fixed bug in HtmlDump where unused bytes at end of MakerNotes were not shown + if they came at the end of a TIFF-format file + +Feb. 16, 2013 - Version 9.18 + + - Decode more AF information for Sony SLT models (thanks Andy Johnson for the + samples) + - Recognize CameraInfo and ColorData information from newer Canon 1DX firmware + - Organized support files in full Perl distribution into separate directories + - Improved German and Spanish translations (thanks Herbert Kauer and Emilio + Sancha) + - Fixed inconsistency where a priority tag could be hidden by a same-named tag + in the same group when using the -j or -X option combined with -g or -G + - Fixed problem in standard tests that could cause ExifTool test 25 to fail + +Feb. 9, 2013 - Version 9.17 + + - Added PLIST and MODD to the list of supported file extensions + - Added track name to UserData tags within QuickTime tracks + - Added a new Pentax LensType (thanks Pietu Pohjalainen) + - Added a new Canon LensType + - Decode binary data in PLIST and MODD files + - Decode new Canon 1DX CustomFunctions + - Issue a minor warning and ignore duplicate PDF Info dictionaries unless the + -m option is used + - Improved date/time parsing when writing to allow single-digit fields + - Improved decoding/naming of a few Sony tags (thanks Jos Roost) + - Improved German translations (thanks Herbert Kauer) + - Changed a few PLIST tag names + - Fixed decoding of Olympus CameraType for some models + - Fixed problem calculating AvgBitrate for some video files + - Fixed problem writing Canon:LensSerialNumber + +Feb. 2, 2013 - Version 9.16 + + - Added support for DarwinCore XMP tags + - Added support for CinemaDNG tags + - Added basic support for parsing XML PLIST information, and use this to + extract tags from QuickTime iTunesInfo Data + - Added a new Pentax lens (thanks Niels Kristian Bech Jensen) + - Added some new Sony E-mount lenses (thanks Jos Roost) + - Added a new NEFBitDepth value (thanks Jos Roost) + - Added a new CanonModelID + - Decode a few more Sony tags (thanks Jos Roost) + - Improved decoding of QuickTime iTunesInfo tags + - Improved Spanish translations (thanks Emilio Sancha) + - Improved handling of errors in Perl expression of new formatting feature + - Improved -p option to also handle structures + - Changed a number of Sigma lens names for Olympus to conform with official + Sigma model names (thanks Niels Kristian Bech Jensen) + - Moved the MWG XMP tags documentation to the MWG page + - Patched to allow reading GPX track logs with no version number + - Fixed problem reading an ID3 POPM frame with a missing counter + - Fixed bug which could cause "uninitialized value" runtime warning when + reading Nikon maker notes with an empty RetouchHistory + - API Changes: + - Compatibility Notice: The MWG Composite tags are no longer automatically + loaded just by using the MWG module. Image::ExifTool::MWG::Load() must + now be called explicitly to load these tags + +Jan. 27, 2013 - Version 9.15 + + - Added advanced formatting feature to -p and -tagsFromFile options + - Added -echo3 and -echo4 options + - Added a few more Olympus LensType values, removed one, changed some lens + names for consistency (all thanks Niels Kristian Bech Jensen), and use + hexadecimal instead of decimal for numerical LensType values + - Added a number of new Sony E-mount lenses + - Added a new Tamron lens for Sony (thanks Marcin Krol) + - Trim trailing spaces from Panasonic LensType strings + - Fixed bug which could cause "Can't call method GetMarkerPointers" runtime + warning when writing certain types of corrupted images + - Fixed problem copying PrevewImage from some corrupted files + - Fixed problem identifying a Sigma lens for Nikon at some focal lengths + - API Changes: + - Added AddUserDefinedTags() method + - Added formatting feature for tag values in SetNewValuesFromFile() + +Jan. 18, 2013 - Version 9.14 + + - Added -wm (-writeMode) option to provide control over tag write/create mode + - Added ability to use wildcards in target tag names when writing + - Added ability to read/write Jpeg2000 XML tag as a block + - Added ability to delete MPF segment (with -MPF:All=) + - Added a number of new Olympus lenses (thanks Niels Kristian Bech Jensen) + - Added a new Nikon LensID (thanks Robert Rottmerhusen) + - Added a number of new Pentax LensType's (thanks Alan Robinson for one) + - Added a few new CanonModelID's and Canon LensType's + - Decode ID3v2 POPM and OWNE frames + - Decode new Canon 6D CustomFunctions + - Improved calculation of ScaleFactor35efl for Canon cameras + - Changed priority of PDF Info tags so tags from most recent Info dictionary + take precedence (to partially accommodate the questionable Acrobat Pro + incremental update technique) + - Changed some verbose warnings when attempting to write "unsafe" tags + - Changed behaviour so that "unsafe" tags are not copied for any tag specified + using a wildcard (previously this was the behaviour for a tag name of 'all' + or '*', but not names like 'gps*') + - Fixed bug where a Composite tag could sometimes not be generated when the + -struct option was used if the tag was derived from an XMP List-type tag + - Fixed problem conditionally deleting GIF Comment and MIE tags + - Fixed decoding of RawImageWidth/Height from FujiFilm X-E1 RAF images + - API Changes: + - Added WriteMode option + +Jan. 10, 2013 - Version 9.13 (production release) + + - Added basic validation of ExifVersion and FlashpixVersion tags when writing + - Fixed problem where MPF PreviewImage was lost when editing metadata in JPEG + images from the Nikon D4, D600 or D800 + +Jan. 2, 2013 - Version 9.12 (production release) + + - Fixed problem introduced in 9.10 preserving file modification date/time when + some options are used + +Jan. 2, 2013 - Version 9.11 (production release) + + - Improved decoding of some Sony tags + - Changed 3 tag names to avoid a leading digit to fix XML validation problem + - Fixed bug introduced in 9.04 that could double-encipher some Sony MakerNote + information when writing (affected files are fixed by writing any tag with + ExifTool 9.11) + +Dec. 29, 2012 - Version 9.10 + + - Added write support for a few new XMP-crs and XMP-photomech tags + - Added a new Samsung LensType (thanks Jaroslav Stepanek) + - Added a new Pentax LensType (thanks Helmut Schutz) + - Added a new Canon LensType + - Decode Sony A99 FocusMode (thanks Michael Tapes for the samples) + - Tolerate (but warn about) up to 4 bytes of garbage at start of EXIF segment + - Changed -P option to also preserve FileCreateDate on Windows (requires + Win32API::File::Time) + - Changed "[minor]" warning messages to capitalize the "M" (ie. "[Minor]") if + processing is affected when the warning is ignored + - Patched to avoid problem of slow processing with some corrupted EXIF + +Dec. 15, 2012 - Version 9.09 + + - Added a few new Google XMP GPano tags + - Added a new Olympus CameraType + - Added a couple of new Minolta LensTypes + - Added two new Nikon LensID's (thanks David Puschel and Robert + Rottmerhusen) + - Decode Nikon D7000 AFPointsUsed and make this tag writable + - Decode a new Olympus tag (thanks Christoph Anton Mitterer) + - Renamed one of the FujiFilm RAF RawImageWidth/Height pairs to + RawImageFullWidth/Height + - Changed -stay_open when combined with -q to flush output after each command + (as already done without -q) (requires IO::Handle) + - Fixed problem shifting FileCreateDate when writing other "real" tags in the + same command + +Nov. 26, 2012 - Version 9.08 + + - Fixed bug introduced in 9.07 that broke writing of FileModifyDate + +Nov. 24, 2012 - Version 9.07 + + - Added ability to read/write FileCreateDate (Windows only) + - Added ability to read FileInodeChangeDate (non-Windows only) + - Added support for new tags in DNG 1.4 specification + - Added support for Google Photosphere GPano XMP tags + - Added a couple of new Olympus filter effects + - Changed a Panasonic LensType (thanks Olaf Ulrich) + - API Changes: + - Enhanced SetFileModifyDate() to write FileCreateDate (Windows only) + +Nov. 17, 2012 - Version 9.06 + + - Added support for Nikon maker notes in images from any camera make (as + written by Capture NX2) + - Added support for FujiFilm X-E1 RAF images + - Added a new Olympus CameraType + - Added a new PentaxModelID and a new Pentax LensType + - Extract FileCreateDate (Windows) and FileInodeChangeDate (other systems) + - Fixed bug decoding UTF-16 ID3 synchronized lyrics + +Nov. 10, 2012 - Version 9.05 + + - Added ability to read APE metadata from MP3 audio files + - Decode ID3 synchronized lyrics/text information + - Decode maker notes in Leica V-LUX40 MP4 videos + - Decode Sony A99 AFPointSelected (thanks Michael Tapes for the samples) + - Improved decoding of some Sony tags (thanks Jos Roost) + - API Changes: + - Removed GeoNoInterpolate option (just set GeoMaxIntSecs to 0 instead) + +Nov. 3, 2012 - Version 9.04 (production release) + + - Added two new Sony LensType values (thanks Matthias Paul) + - Added a few new Canon LensType values + - Added a couple of new PentaxModelID's and decode some new K-5 II values + - Added support for some new XMP tags written by the Apple iPhone 5 + - Added a new Olympus CameraType + - Decode more Sony tags/values (thanks Jos Roost) + - Decode Nikon HDRInfo (thanks Stefan) + - Decode some FlashInfo tags for new Nikon models + - Decode a few WM ID3 tags (some documentation on these would be nice) + - Fixed bug which could cause truncated/garbage ID3v2 strings to be returned + - Fixed -globalTimeShift option to also work when copying tags + - Fixed decoding of Nikon AFFineTuneAdj for FirmwareVersion 1.10B (thanks + Michael Tapes for the samples for this and the A77) + - Fixed problem where a few tags (FileSequence, NewGUID and Now) were not + available for use with the -p option + - API Changes: + - Added RequestAll and GeoNoInterpolate options + - Fixed problem in SetNewValue when setting the Raw value of some tags + +Oct. 13, 2012 - Version 9.03 + + - Added new feature to provide control over directory levels in %d strings + - Added ability to write OtherImage in NEF images + - Added a new Pentax LensType + - Added a few new CanonModelID's (thanks Laurent Clevy) + - Added a new Nikon LensID (thanks Geert De Soete) + - Added a few new Olympus CameraType values + - Decode some new CameraInfo tags for the Canon EOS 650D + - Decode a number of new Sony tags (thanks Jos Roost) + - Improved decoding of some Sigma tags for the DP1/DP2 Merrill + - Give priority to EXIF tags over SigmaRaw tags X3F images + - Changed Samsung lens names to include "NX" (thanks Jaroslav Stepanek) + - Fixed misleading verbose "TAG is not writable" messages when copying + list-type tags + - API Changes: + - Enhanced GetValue() to allow return of 'Rational' value + +Sept. 6, 2012 - Version 9.02 + + - Added a new Nikon LensID (thanks Joseph Heled) + - Added a new EXIF SubFileType value used in DNG images + - Added write support for Apple Adjustment Settings XMP tags (XMP-aas) + - Added a couple of new Samsung LensType values (thanks Jaroslav Stepanek) + - Added a couple of new Canon LensType values and a new CanonModelID + - Decode a number of new Sony tags (thanks Jos Roost) + - Enhanced "-o -" feature to allow output file type to be specified + - Extract last file access time as FileAccessDate + - Allow tags to be set from files which are zero bytes in size + - Made ProfileHueSatMap tags Binary if they are too long + - Changed names of some PanasonicRaw DistortionInfo tags + - Changed decoding for a Sony ExposureMode value + - Fixed hang/crash that could occur when writing to an image with corrupted + Sony MoreInfo data (eg. SLT-A55V JPEG corrupted by GIMP) + +Aug. 25, 2012 - Version 9.01 (production release) + + - Added a couple of new CanonModelID values + - Added a couple of new Canon LensType values (thanks Pascal de Bruijn) + - Added a new PentaxModelID and a few new Pentax PictureMode values + - Decode a new Pentax ISO tag + - Improved -listx output for XMP structure tags + - Fixed "unexpected end of file" problems with some compressed MIE files + +Aug. 18, 2012 - Version 9.00 + + - Added support for PDF encryption V5.6 (new in Adobe Reader X) + - Added a few new XMP-cc tags and changed a few others to rdf:resource type + - Added a new Sony LensType and values for other Sony tags (thanks Jos Roost) + - Added a new Nikon LensID + - Added a new Panasonic LensType (thanks Olaf Ulrich) + - Added patch to fix simple XMP tags written incorrectly as lang-alt type + - Decode some Panasonic RW2 lens distortion correction tags + - Decode some WEBP image characteristics from the VP8 bitstream + - Decode more Leica MakerNote information + - Calculate CurrentIPTCDigest for IPTC in PostScript files + - Changed the names of a couple of WBShift tags + - Improved parsing of -if expressions to interpret a dash after a tag name as + a minus sign instead of part of the tag name + - Patched problem with conditional deletion of an incorrectly null-terminated + JPEG Comment + - Fixed hang bug when reading unsupported Microsoft Xtra information in MOV + videos + +Aug. 3, 2012 - Version 8.99 + + - Added patch to avoid "Error renaming temporary file" errors in Windows + - Decode some new Sony tags and values (thanks Mike Reit and Jos Roost) + - Improved Italian translation (thanks Michele Locati) + - Improved decoding of H264 ImageStabilization + - Changed names of PanasonicRaw ImageWidth/Height tags, and added new + Composite tags to calculate actual size of RW2 images + - Fixed "Corrupted Ricoh RMETA data" warning for images from some Ricoh models + - Fixed problem writing information to some EPS images + +July 28, 2012 - Version 8.98 + + - Added a new Pentax LensType and two new PentaxModelID's + - Added a new CanonModelID and a new Olympus CameraType + - Added a new Composite Duration tag for Vorbis audio files + - Added more elements to Microsoft Regions XMP structure and fixed tag name + documentation for this + - Decode a number of new Sony tags (thanks Jos Roost) + - Changed name of Minolta BatteryLevel tag to BatteryState + - Patched problem with conditional deletion of IPTC string-type tags which are + incorrectly null terminated (eg. written by Picasa 2.0) + - Fixed problem copying Canon 5DmkIII MakerNotes from CR2 to JPEG images + - Fixed runtime error when writing some images with corrupted EXIF + +July 6, 2012 - Version 8.97 + + - Added a new Canon LensType + - Added support for GPX attitude information as written by Arduino + - Added write support for XMP-expressionmedia:CatalogSets + - Made CFARepeatPatternDim and CFAPattern2 writable but protected + - Minor improvement to decoding of Sony FaceInfo + - Fixed problem reading some GPX track logs + +June 30, 2012 - Version 8.96 + + - Added -globalTimeShift option + - Added new values for a couple of Nikon tags (thanks Michael Relt) + - Added a few new Sony PictureEffect values + - Added a new Olympus LensType + - Decode a new Sony A100 tag and improved/renamed some others (thanks Igal + Milchtaich) + - Changed -restore_original and -delete_original options to scan directories + only for writable file types + - Enhanced -srcfile option to allow multiple source files to be specified + - Patched possible round-off problem when extracting rational values + - Fixed bug which could cause runtime error when reading some HTML files and + improved reliability when extracting HTML "meta" tags + - API Changes: + - Added GlobalTimeShift option + +June 16, 2012 - Version 8.95 + + - Added a few new Sony PictureEffect values + - Added a new Olympus lens type (thanks Niels Kristian Bech Jensen) + - Improved decoding of Canon IntelligentContrast + - Improved user-defined lens logic to attempt to choose the best matching + user-defined lens if more than one is possible + +June 9, 2012 - Version 8.94 + + - Added ability to read/write IPTC as a block + - Added a few Nikon LensID's (thanks Mike Pollock and Robert Rottmerhusen) + - Added a new Olympus LensType (thanks Brad Grier) + - Added new values for a few Olympus tags + - Decode more Sony tags (thanks Jos Roost and Igal Milchtaich) + - Decode Canon IntelligentContrast and add a new CanonModelID + - Changed names of Canon Sort/LongFocal tags to Min/MaxFocalLength + +May 26, 2012 - Version 8.93 + + - Added some new Nikon RetouchHistory values + - Added a couple of new Pentax LensType values + - Added some new Olympus MagicFilter and LensType values + - Added a new CanonModelID + - Decode more Sony tags (thanks Jos Roost) + - Decode some MakerNote information in Olympus E-M5 MOV videos + - Decode a couple more Canon tags + - Patched to overcome formatting problems in Samsung NX200 JPEG maker notes + +May 12, 2012 - Version 8.92 + + - Added read support for PCD (Kodak Photo CD Image Pac) files + - Added Geotag support for Winplus Beacon text-format GPS log files + - Added support for Leica X2 MakeNotes + - Added NewGUID tag + - Decode Panasonic ManometerPressure tag (thanks Christoph Mitterer) + - Decode more Sony tags (thanks Jos Roost) + - Changed a few Canon-mount Tokina lens model names for consistency + +May 5, 2012 - Version 8.91 + + - Added -progress option + - Added support for XMP fpv namespace + - Added a new Canon EasyMode value and fixed an incorrect one + - Added a couple of new Canon LensTypes + - Decode a number of new tags for the Canon 1DX and 5DmkIII + - Improved the names of a few Sony tags (thanks Jos Roost) + - Fixed -sep option to apply to interpolated tag values in a string when + copying + +Apr. 28, 2012 - Version 8.90 (production release) + + - Added ability to fix double-UTF-encoded embedded XMP + - Added a warning for invalid XMP + - Added a new Minolta/Sony LensType (thanks Matthias) + - Added a new values for some Canon tags + - Decode ColorBalance information for a few more Nikon models + - Ignore trailing whitespace when writing converted values + - Enhanced the -z option to avoid writing the 2424 bytes of padding in XMP + - Improved decoding of some Sony MakerNotes tags (thanks Jos Roost) + - Improved "best guess" for fixing corrupted makernote offsets of some Sony + models + +Apr. 21, 2012 - Version 8.89 + + - Added new Nikon and Ricoh LensID's + - Added a new Olympus CameraType + - Added new Canon LensType, EasyMode and CanonModelID values + - Added new Pentax PictureMode and PentaxModelID values + - Added support for IDimager XMP tags + - Added a number of new XMP-crs tags used by LR4 + - Decode a few more QuickTime tags + - More improvements decoding Minolta/Sony CameraSettings (thanks Jos Roost) + - Enhanced -ext option to allow files with any extension to be processed + - Increased maximum number of SubIFD's to accommodate some DNG 1.4 images + - Lowered priority of JPEG APP12 PictureInfo tags when reading + - Created mechanism to allow self-referential XMP structures + +Apr. 15, 2012 - Version 8.88 + + - Added a new Canon LensType (thanks Gerald Erdmann) + - Decode a number of new Olympus tags and values + - Decode a few more QuickTime tags + - Many more improvements and additions to Sony decoding (thanks Jos Roost) + - Changed Ricoh InternalSerialNumber to also convert numerical value + - Removed the ability to create IFD1 in TIFF-format images (you shouldn't + really do this anyway) + - Fixed incorrect IFD number in some error messages when writing + +Apr. 9, 2012 - Version 8.87 + + - Added a new PentaxModelID + - Added new values for some Panasonic tags + - Added a couple of new Canon LensTypes + - Decode a few more Sony tags and values (thanks Jos Roost) + - Decode more CanonVRD tags + - Decode makernotes from Pentax WG-2 GPS MOV videos + - Changed Panasonic AdvancedSceneMode to a Composite tag + - Fixed problem introduced in 8.70 where excluding groups from deletion didn't + work when copying back tags in the same command + - Fixed problem repairing incorrect makernotes offsets in JPEG images from + Sony SLT and NEX cameras + +Apr. 3, 2012 - Version 8.86 + + - Added a few new values for some Panasonic tags + - Added a new CanonModelID and a new Canon LensType + - Added a new Nikon LensID + - Decode more Sony CameraSettings3 information (thanks Jos Roost) + - Decode another Canon 5D tag + - Decode some new CanonVRD DLO tags + - Changed decoding of CanonVRD VRDVersion tag + - Changed formatting of a Pentax LensType for consistency with other lenses + - Patched decoding of Reconyx:DateTimeOriginal to accommodate values written + with an incorrect byte order by some models + +Mar. 25, 2012 - Version 8.85 (production release) + + - Added a couple more Olympus CameraType values + - Added two new Pentax LensType's and a PentaxModelID + - Decode a number of new Sony CameraSettings3 tags (thanks Jos Roost) + - Decode a few new Pentax K-01 tags + - Decode new custom functions of the Canon 5D Mark III + - Recognize another non-standard APP1 XMP header + - Increased unrolled depth of XMP-mwg-kw:HierarchicalKeywords from 4 to 6 + - Extended "-charset exif=CHARSET" to also apply to EXIF UserComment when + stored as ASCII + - Changed name of Olympus MaxApertureAtCurrentFocal to to MaxAperture + - Patched to avoid possibility of unnecessary "references previous directory" + warning when the length of one directory is zero + +Mar. 17, 2012 - Version 8.84 + + - Added a few more SonyModelID's (thanks Jos Roost) + - Added a new CanonModelID and a number of new Canon LensType values + - Added a new Minolta/Sony LensType + - Decode CameraTemperature for a number of new Canon PowerShot models + - Decode information from PANA atom of Panasonic DMC-FT20 MP4 videos + - Decode a bit more of the Casio MakerNotes + - Improved Polish translations for EXIF information (thanks Kacper Perschke) + - Changed some warning messages for invalid IFD entries + - Patched to allow writing of Sony MakerNotes containing invalid IFD entries + +Mar. 13, 2012 - Version 8.83 + + - Added a new SonyModelID and a new Nikon LensID (thanks Gregg Lee and Jos + Roost) + - Added Finnish translations (thanks Jens Duttke and Jarkko Makineva) + - Fixed the Composite:LensID problem properly this time (with any luck) + +Mar. 13, 2012 - Version 8.82 + + - Added ability to extract information from PostScript-type DFONT files + - Added a new Minolta/Sony LensType (thanks Jos Roost) + - Improved geotagging of orientation information when extrapolating past end + of track + - Changed behaviour while copying information to allow flattened tags to be + specified without the need to use the --struct option + - Removed unnecessary warning when writing PreviewImage to Ricoh DNG file + - Fixed problem introduced in 8.81 which prevented generation of the Composite + LensID for Nikon images when duplicate tags were disabled + - API Changes: + - Added NoFlat option to SetNewValues() + - Changed Struct option to allow copying of both structured and flattened + tags at the same time + +Mar. 9, 2012 - Version 8.81 + + - Added some new Canon, Pentax and Sony/Minolta LensType's + - Added a few new FujiFilm PictureMode values (thanks Kai Lappalainen) + - Added some new FujiFilm FilmMode values + - Added a couple of new CanonModelID values + - Added local timezone message to -v2 geotagging output + - Made all Pentax LensType tags writable + - Improved Composite LensID logic to use Sony LensSpec value if available + - Fixed problem opening files with path names that begin with "&" + +Feb. 25, 2012 - Version 8.80 + + - Added a new Olympus CameraType + - Improved geotagging to tolerate out-of-sequence and missing NMEA sentences + - Increased the maximum XMP tag ID length to 250 characters to allow very deep + user-defined structure hierarchies + +Feb. 20, 2012 - Version 8.79 + + - Avoid deleting the JPEG APP14 Adobe segment when deleting all metadata + - Added ability to read/write/create JPEG APP14 Adobe segment as a block + - Added some new CanonModelID values + - Added another Panasonic WhiteBalance value (thanks PeterK) + - Decode Panasonic ColorTempKelvin tag + - Decode information from Qualcomm APP7 JPEG segment + - Extract PreviewImage for a few more uncommon camera models + - Strengthened MP3 file recognition to avoid mis-identification of some files + - Fixed problems reading "sfnt" resource in some DFONT files + - Fixed problems writing some LensType values for 3rd-party lenses + +Feb. 11, 2012 - Version 8.78 + + - Added basic read support for a few obscure audio formats (LA, OFR, PAC, WV) + - Added a couple more Canon LensType values + - Decode some new Kodak tags in MP4 videos + - Patched timezone problem on MirBSD due to leap-second "feature" of this OS + - Fixed problem converting Adobe XMP LensID's for Pentax lenses + - Fixed runtime warning due to conflict with some Vorbis tag ID's + - Fixed problem which could result in duplicate columns in -csv output when + used with -f and the "#" suffix on a tag name + - API Changes: + - Added XMPAutoConv option + +Jan. 27, 2012 - Version 8.77 (production release) + + - Added some new and updated some existing Sony/Minolta LensType values + - Added two missing Minolta Teleconverter values + - Added a new Canon LensType + - Decode Olympus ArtFilterEffect + - Enhanced -c (CoordFormat) option to allow signed coordinate output + - Changed -sort option to always sort -json and -X outputs by tag name + - Minor change to an Olympus LensType name (thanks Niels Kristian Bech Jensen) + - Fixed problem geotagging orientation information from PTNTHPR sentence + - Fixed decoding of negative Pentax EffectiveLV values + - Fixed typo in an Olympus LensType + +Jan. 18, 2012 - Version 8.76 + + - Added -sort option to sort output by tag name or description + - Added support for FujiFilm RAF version 1.03 images and downgraded RAF + version error to a warning + - Added a number of new Minolta/Sony LensType's + - Added a new CanonModelID + - Decode FocusPosition for Sony A850 and calculate Composite FocusDistance + - Decode IFD found in some Samsung Type1 maker notes + - Patched Olympus test to fix failure on some platforms + - Patched -json output to filter out invalid UTF-8 characters + - API Changes: + - Added Sort2 option and 'Descr' setting for Sort option + - Added secondary sort option to GetFoundTags() and GetTagList() + - Changed name of Sort 'Alpha' setting to 'Tag' (but 'Alpha' still works + for backward compatibility) + +Jan. 8, 2012 - Version 8.75 (production release) + + - Added -php output option (thanks Marcel) + - Decode another AIFF tag and handle character encoding in AIFF text values + - Recognize PHP files + - Enhanced Geotag feature to write speed/track from NMEA GPRMC sentence, and + orientation information from Honeywell NMEA PTNTHPR sentence + - Changed verbose XMP output to print raw values + - Lowered default priority of "avoided" tags so they don't override other + same-named tags when reading with duplicate tags disabled + - Patched tests to ignore MirBSD leap-second unconformity + - Patched ZIP module to avoid failed tests with Perl 5.6.2 on GNU/Linux 2.6 + - Fixed problem reading xref table of some PDF files created by PScript5.dll + - Fixed problem reading RicohSubdir from AVI videos of the GR Digital 4 + +Dec. 28, 2011 - Version 8.74 + + - Added read/write support for Hasselblad FFF images + - Added iptcCore.args convenience file to the distribution package + - Catch CONT signal to allow calling applications to trigger an immediate + response (avoiding a delay of up to 0.01 sec) after writing arguments to a + -stay_open ARGFILE + - Protect against some infinite loops that could be created when using some of + the advanced exiftool options + - Improved decoding of Samsung PictureWizard (thanks Pascal de Bruijn) + - Improved handling of bad IFD entries in -htmlDump output + - Changed print conversion of EXIF:FNumber and XMP:FNumber to use 2 decimal + digits for values less than 1.0, and disable conversion for invalid values + - Tightened up the -stay_open feature to fix a few potential problems + - Fixed bug using -csv+= or -json+= for non-list-type tags + - Fixed problem deleting unknown makernotes as a block + - API Changes: + - Enhanced SetNewValue() AddValue option to allow this option to be + ignored for non-list tags + +Dec. 16, 2011 - Version 8.73 + + - Added read support for OpenEXR and Radiance RGBE images + - Added a couple of new Nikon LensID's (thanks Robert Rottmerhusen) + - Added a new PentaxModelID + - Added a new Olympus CameraType + - Created new FileSequence tag for use in batch processing + - Decode maker notes from Pentax Optio RZ18 AVI videos + - Tolerate unrecognized IPTC records (but still issue warning) + - Changed ScaleFactor35efl calculation to also use Pentax SensorSize + - Minor changes to two Samsung lens names (thanks Pascal de Bruijn) + +Dec. 8, 2011 - Version 8.72 + + - Added support for reading XMP from INX files + - Added PDF HasXFA tag + - Added a new XMP Colorants field (not in 2010 XMP specification) + - Decode Casio BestShotMode for yet more cameras + - Decode a few more Casio ImageStabilization values + - Decode a few more Olympus tags and added conversion for CameraType + - Protect against reading insanely large XMP (> 300 MB) in INDD files + - Extract large (> 64 kB) unknown XMP tags as binary data + - Reduced memory requirements for XMP processing (by 1/10) + - Fixed another place where empty XMP structures could hide (in lists) + +Nov. 19, 2011 - Version 8.71 + + - Added two new Olympus LensType values (thanks Martin Hilbers) + - Avoid recreating duplicate groups when deleting whole groups and adding back + tags in the same step + - Fixed problem where the QuickTime -charset option didn't work for some tags + - Fixed bug introduced in 8.69 which could cause excessive memory usage when + reading QuickTime videos with the -u option + - Fixed problem where existing empty XMP structure couldn't be deleted or + overwritten as a structured tag + +Nov. 15, 2011 - Version 8.70 + + - Compatibility Notice: Changed order of operations when batch processing with + -tagsFromFile option to be consistent with non-batch mode + - Added -listItem option + - Added read support for IDML files + - Added a new Canon LensType (thanks Jon Charnas) + - Added a couple of new Samsung LensType's (thanks Tae-Sun Park) + - Added support for another DigiKam XMP tag + - Decode a couple more ID3 tags + - Decode Casio BestShotMode for more cameras + - Improved decoding of Casio AFMode + - Extract unknown FLAC blocks as binary data + - Changed ITC:ImageType to make "numerical" value more friendly + - Changed priority of two unreliable Samsung tags + - Fixed bug where ExifTool could produce improperly formatted XMP when writing + structure elements to a previously empty XMP structure (the empty XMP + structure was not being properly deleted). Affected XMP may be repaired by + re-writing any element of the structure with this version of ExifTool + - API Changes: + - Added ProtectSaved option to SetNewValue() and return save count from + SaveNewValues() + +Nov. 9, 2011 - Version 8.69 + + - IMPORTANT: Fixed bug which could corrupt GIF images when writing a Comment + to a GIF image containing XMP metadata + - Added ability to read/write ICC_Profile in GIF images + - Added ability to specify internal encoding of EXIF "ASCII" strings and + QuickTime strings + - Added a new DigiKam XMP tag + - Documented -echo option (has been an undocumented feature since 6.86) + - Decode a number of new Sony tags + - Decode a few new Pentax tags and added a few new values + - Decode a few new QuickTime and ID3 tags + - Decode Casio BestShotMode for a number of models + - Improved validity checking of ICC_Profile segments in JPEG image + - Tolerate UTF-8 byte order mark (BOM) in input CSV and JSON files + - No longer trim trailing spaces from arguments in -@ argfiles + - Upgraded Windows executable version to use PAR 1.002 + - Changed priority of the Sony DynamicRangeOptimizer tags + - Changed MWG feature to use UTF8 encoding for EXIF strings by default + - Changed the -b option to avoid loading large binary values for tags that + have been excluded with the -x option or --TAG + - Changed Canon AFMicroAdjActive to AFMicroAdjMode and improved decoding + - Fixed problem where the PreviewImage could be lost when writing to images + from some newer Sony cameras + - Fixed problem reporting duplicate information when -if used with -TAG# + - Fixed incorrectly written XMP-tiff:YCbCrSubSampling tag + - Fixed problem opening files with names beginning and/or ending with some + characters such as SPACE, '>', '<' and '|'; however file names ending + with '|' are still not allowed + - API Changes: + - Added CharsetEXIF and CharsetQuickTime options + +Oct. 21, 2011 - Version 8.68 + + - Added a new CanonModelID and a new SonyModelID + - Added new Canon and Pentax LensType's + - Decode more makernote information from Nikon MOV videos + - Improved decoding of Sony LensSpec and enabled writing of this tag + - Overhauled Minolta/Sony LensType list for consistency with official Sony + lens names and removed a couple of anomalous entries (thanks Jos Roost) + - Fixed problem with negative temperatures in Reconyx makernotes + - Fixed bug which could cause runtime warnings when -f used with -X and -l + - Fixed some minor problems when using -X with MWG option + - Fixed issue where some missing tags could be printed when -f option was used + in combination with wildcard tag names + +Oct. 13, 2011 - Version 8.67 + + - Added a new Canon LensType (thanks Norbert Wasser) + - Decode tags from FujiIFD in HS10 and X100 RAF images + - Decode LocationInfo tags from Nikon maker notes + - Decode GPS tags from Nikon MOV videos + - Decode information from Microsoft "Xtra" atom in QuickTime files + - Decode Sony LensSpec information (thanks Jos Roost) + - Use more specific MakerNotes names in warning messages and verbose output + - Updated Canon CustomFunctions for the EOS 600D and 1100D + - Improved handling of some corrupted RIFF files + - Improved decoding of Samsung manual lens types (thanks Pascal de Bruijn) + - Changed "No writable tags found" warning to "No writable tags set from" + - Fixed problem handling resource forks in newer versions of OS X + - Fixed problem writing XMP as a block to Jpeg2000 images + - Fixed problem which could cause XMP and IPTC to be ignored when using MWG + feature with TIFF images and performing multiple operations in a single + command + +Oct. 3, 2011 - Version 8.66 + + - Added the ability to use "$GROUP:all" in -if and -p expressions (evaluates + to "1" if any tag exists in the specified group, or "0" otherwise) + - Added a new Sony/Minolta LensType (thanks Florian Knorn) + - Added list of recommended modules to Perl installation + - Decode ColorBalance information for a few new Nikon models + - Updated Canon CustomFunctions for the EOS 600D and 1100D + - Fixed problem writing "now" to MWG date/time tags + +Sept. 24, 2011 - Version 8.65 (production release) + + - Added a few new CanonModelID's + - Added a new Sony/Minolta LensType + - Added a new Canon LensType (thanks Klaus Reinfeld) + - Added a number of new Olympus ArtFilter/MagicFilter values + - Included new .args files in distribution: exif2iptc.args and iptc2exif.args + - Enhanced writing of date/time tags to recognize "now" for the current time + - Improved decoding of H264 Gain + - Minor improvement to -htmlDump for some invalid IFD entries + - Allow PostScript date/time tags to be written without the -n option + - Allow NikonCapture:ExposureAdj2 to be written without the -n option + - Fixed problem introduced in version 8.62 where DateTimeOriginal in IFD0 of + NEF images was no longer updated when shifting times + - Fixed problem where keywords could be duplicated when exporting to XMP while + using the MWG module + - Fixed problem reading PDF images with extra whitespace before xref table + - Fixed format problem in CSV output for filenames containing a comma or quote + - Fixed problem reading concatenated AVI videos + +Sept. 10, 2011 - Version 8.64 + + - Added 2 new ACDSee XMP tags (thanks Hannes Leubbers) + - Added a new Sony FileFormat value + - Added a new CanonModelID + - Added a few new Pentax DigitalFilter and ImageTone values + - Enhanced -execute option to allow a command ID number to be added + - Enhanced -csv and -json import features to also key on canonical SourceFile + path (requires Cwd module) + - Improved Composite LensID logic for some Sony cameras + - Fixed misleading error message when using -if option on file that doesn't + exist + - Fixed problems decoding a number of inconsistent tags in the Sigma SD1 maker + notes + +Aug. 27, 2011 - Version 8.63 + + - Added support for a number of new Open Document file extensions + - Added a few new CanonModelID and SonyModelID values + - Added a new Ricoh GXR LensID + - Added a new Sony/Minolta LensType (thanks Mladen Sever) + - Added patch to read the improperly formatted DateTimeOriginal in AVI videos + written by the Kodak Easyshare Sport camera + - API Changes: + - Added QuickTimeUTC option + +Aug. 21, 2011 - Version 8.62 - "JPEG2000 Update" + + - Added read support for JPEG2000 codestream format (J2C) + - Added a few new Nikon LensID's (thanks Robert Rottmerhusen) + - Added a few new Pentax LensType's + - Added a few new Sony/Minolta LensType's (thanks Wolfram for 2 of these) + - Added two new Sony Teleconverter values (thanks Wolfram) + - Decode a few more JPEG2000 UUID's written by Adobe JPEG2000 plugin + - Decode additional JPEG2000 ColorSpecification information + - Recognize a few more JPEG2000 file extensions + - Updated some CanonModelID's + - Tolerate extra comma at end of line in imported -csv files + - Changed name of Kodak Type9 SerialNumber tag to UnknownNumber + - Fixed bug which in rare situations could result in an erroneous "IFD pointer + references previous IFD" warning + - Fixed another memory leak when writing and removed circular references from + ExifTool object to prevent future bugs like this + - Fixed problem in Windows where values in the -X (XML) output containing + CR+LF were converted to CR+CR+LF + - Fixed superfluous warning which could occur when using += to decrement a + numerical tag + - Fixed an incorrectly spelt Pentax city name (thanks John Francis) + +July 16, 2011 - Version 8.61 + + - Added the ability to increment/decrement tags with numerical values using += + - Added support for Extensis Portfolio XMP tags plus a number of non-standard + and/or undocumented XMP-xmp and XMP-xmpMM tags + - Added read support for Microsoft Compiled HTML (CHM) format + - Added read support for Ogg Video (OGV) files + - Added new LensType values for Pentax (thanks Heike Herrmann), Sony/Minolta + (thanks Fabio Suprani and Florian Knorn), Nikon (thanks Jens Kriese), + Olympus and Sigma cameras + - Added a new QuickTime VendorID + - Recognize DEX (Dalvik Executable) files + - Identify Windows 64-bit EXE/DLL files and relax EXE validation + - Validate date/time values when reading NMEA GPS log files + - Changed decoding of CFAPattern to return a string of numbers with -n option + - Extract all unknown makernote blocks as undef, regardless of actual format + - Improved print conversion of Pentax ShakeReduction + - Fixed problem processing some Ogg files with multiple streams + - Fixed incorrect namespace URI for stArea (used by MWG 2.0 regions) + - Fixed problem with spaces in -geotag path when using wildcards + - Fixed problem writing PDF:Keywords list items individually if they contain + special characters + - API Changes: + - Enhanced SetNewValue() to allow increment/decrement of numerical tags + +June 25, 2011 - Version 8.60 (production release) + + - Added Composite Flash tag to facilitate copying of flash information between + XMP and EXIF + - Added new Pentax and Canon LensType values and fixed a Pentax lens name + - Added a few new Leica LensType's (thanks Olaf Ulrich) + - Added a new PentaxModelID + - Enhanced GPSDateStamp conversion to tolerate null separators (Casio EX-H20G) + - Made DNG LinearizationCurve and Nikon ContrastCurve writable but protected + - Renamed Nikon LinearizationTable to NEFLinearizationTable and made writable + but protected + - Removed Leica M8 FrameSelector tag since it seems to have evolved into an + extension of the LensType tag for newer lenses + - Fixed problem with order of operations when using multiple -if options + +June 11, 2011 - Version 8.59 + + - Added new Composite:LensID derived from XMP-aux:LensID + - Added new PentaxModelID and CanonModelID values + - Added a new Pentax LensType (thanks Artur) + - Decode maker notes in Pentax Optio S1 AVI videos + - Extract PreviewWMF from DOCX files + - Recognize WMF images + - Fixed decoding of CanonVRD WBAdjRGBLevels and renamed to WBAdjRGGBLevels + +June 2, 2011 - Version 8.58 + + - Decode a number of CameraInfo tags for the Canon EOS 600D and 1100D + - Improved speed by a factor of 2 when reading M2TS videos + - Fixed memory leak with -stay_open feature when writing + +May 26, 2011 - Version 8.57 + + - Added a couple of new Canon LensType values + - Added a few new Nikon LensID's (thanks Robert Rottmerhusen) + - Added format string to -v2 output for IPTC tags + - Added extra logic to avoid misidentifying unknown IFD-style maker notes + - Decode custom settings for Nikon D700 and D7000 + - Fixed problem recognizing NikonCaptureData for ViewNX version 2.1.1 + +Apr. 16, 2011 - Version 8.56 + + - Added a new Canon LensType (thanks Rodolfo Borges) + - Decode EXIF information in FujiFilm HS20EXR MOV videos + - Decode NikonCaptureEditVersions when ExtractEmbedded option is used + (previously called NikonCaptureHistory) + - Decode another Samsung tag (thanks Tae-Sun Park) + - Recognize CaptureOne ".newer" COS files + - Reverted JSON output to pre-8.51 behaviour by removing '#' suffix from tag + names when print conversion is disabled on a per-tag basis + - Fixed bug introduced in 8.32 interpreting some expressions when copying tags + +Apr. 11, 2011 - Version 8.55 + + - Added write support for FujiFilm RAF version 0716 images + - Added support for a number of new LR3 XMP tags (thanks Wolfgang Guelcker) + - Decode some more Samsung tags (thanks Tae-Sun Park) + - Improved handling of incorrectly formatted XMP + - Recognize a few alternate PS and EPS file extensions (thanks Jeff Harmon) + - Reverted a few Pentax macro lens names (less consistent, but at least they + match the official Pentax names) + - Fixed problem reading some XMP custom properties + - Fixed minor problem in HtmlDump output for Canon MakerNotes footer + +Apr. 2, 2011 - Version 8.54 + + - Added a number of new values for various tags + - Added a new Nikon LensID + - Decode a number of encrypted Samsung SRW tags (thanks Tae-Sun Park) + - Enhanced -s option so allow a number to be specified + - Fixed problem reading some Casio EX-Z35 MakerNote values + +Mar. 27, 2011 - Version 8.53 + + - Added a new Olympus LensType + - Added a new Nikon LensID + - Added a new PentaxModelID value + - Decode new Pentax MakerNotes format of Optio WG-1 GPS + - Decode Casio, Ricoh and Sanyo face detection information (thanks Jeffrey + Friedl and Emilio for samples) + - Decode FujiFilm face recognition information (thanks Jeffrey Friedl) + - Decode a new FujiFilm tag for GE models + - Allow writing GPSLatitudeRef/GPSLongitudeRef with a signed number + - Return proper FileType for M4P audio files + - Combined Canon FaceDetectFrameWidth/FaceDetectFrameHeight tags into + FaceDetectFrameSize for consistency with other makes + - API Changes: + - Fixed problem when specifying family 1 group in call to SetNewValue() + when tags were previously extracted with ExtractInfo() + +Mar. 20, 2011 - Version 8.52 + + - Added -listr option and mechanism to recognize some unsupported file types + - Added read support for VSD (Microsoft Visio Drawing) files + - Added a new Pentax LensType and improved consistency of macro lens names + - Added another CanonModelID + - Calculate Duration for M2TS (AVCHD) videos + - Decode a new FujiFilm tag + - Recognize .TS extension + - Recognize FotoStation IPTC record 240 + - Attempt to better identify FPX-format MSOffice documents with incorrect file + extensions + - Fixed bug applying time shift to Nikon PowerUpTime + - API Changes: + - Enhanced GetNewValues() to allow group name to be specified + - Allow description flag to be set to '0' when calling GetFileType() to + return types of recognized-yet-unsupported files + +Mar. 12, 2011 - Version 8.51 + + - Added -csv option for import/export of CSV database files + - Added ability to import JSON files + - Added read support for APP1 "Ocad" segment + - Added a new Nikon LensID (thanks Robert Rottmerhusen) + - Decode more Reconyx MakerNotes tags (thanks Robert Hass of Reconyx!) + - Report the number of encryption bits in the PDF:Encryption tag value + - Allow empty group name when specifying a tag + - Improved decoding of Olympus ArtFilter and MagicFilter tags + - Improved exception handling to continue with next -execute command after + aborting a command due to a serious error + - Fixed problem reading indexed PGF images + +Mar. 1, 2011 - Version 8.50 (production release) + + - Added Composite tags to convert QuickTime GPS information + - Added a couple new Sony PMP Orientation values (thanks Mike Battilana) + - Added a couple of new Nikon LensID's (thanks Rolando Ruzic) + - Added a new Canon LensType (thanks Gerald Kapounek) + - Decode new Nikon, Olympus, Pentax and Sony face detection tags (thanks + Jeffrey Friedl) + - Decode Ricoh FirmwareRevision tags + - Allow GPSLatitudeRef and GPSLongitudeRef to be written with a GPS coordinate + containing a N/S/E/W designator + - Removed Canon20D shortcut and changed Canon shortcut + - Removed LEGRIA/VIXIA/iVIS from CanonModelID names + - Renumbered Canon FacePosition tags to start at Face1Position + +Feb. 12, 2011 - Version 8.49 + + - Added a number of new values for various Canon tags + - Added a new Pentax LensType + - Added ability to write Nikon PowerUpTime tag + - Added a number of MachO CPUSubtype's and improved handling of 64-bit flag + - Decode ColorData for the Canon EOS 600D and 1100D + - Decode a few new Sony tags + - Set document number for FlashPix tags extracted from embedded documents + - Attempted to patch OS X 10.6 quirk where FileModifyDate may not be preserved + for some files when -P is combined with -overwrite_original_in_place + +Feb. 3, 2011 - Version 8.48 + + - Added a new Canon LensType value + - Changed order of stored information when rewriting existing IPTC tags (to + make the order of items in list-type tags consistent with XMP when deleting + and adding back values in the same command) + - Fixed problems with format of binary data in lists for some output options + +Jan. 29, 2011 - Version 8.47 + + - Added -args option + - Added read support for PGF (Progressive Graphics File) images + - Added write support for Phase One IIQ images + - Added ability to write XMP-xmpMM:Pantry + - Added print conversions for a number of closed-choice XMP properties + - Added some new CanonModelID's + - Included new argument files in distribution: pdf2xmp.args and xmp2pdf.args + - Avoid copying TIFF trailers containing nothing but zeros when rewriting + - Handle binary data in serialized structure output + - Moved BMP tags to the File group + - Fixed bug reading/writing some IPTC binary data tags + - Fixed problem copying XMP:Thumbnails structure + - Fixed conversion of MXF:ByteOrder value + - Fixed potential "Undefined subroutine ConvertStruct" crash bug + - API Changes: + - Fixed bug introduced in 8.46 when calling GetValue(xxx,'Raw') + +Jan. 22, 2011 - Version 8.46 + + - Simplified definition of user-defined XMP structures: flattened tags are now + automatically generated, and UserDefined::xmpStruct is no longer needed (but + backward compatibility is maintained with the old-style definitions) + - Added ability to handle multi-dimensional arrays in structured output + - Added a new Canon LensType (thanks Jean-Michel Dubois) + - Added some new XMP-xmpMM tags + - Enabled writing of a number of XMP-crs tags + - Decode Reconyx TriggerMode tag + - Relaxed structure validation to allow a structure to be written even if + there were errors with some fields + - Patched problem with formatting of very large numbers in JSON (-j) output + - Fixed a few problems reading and writing structured information + - Fixed bug which could cause hang with some user-defined tag definitions + +Jan. 12, 2011 - Version 8.45 + + - Fixed a couple of minor bugs with the new -struct option + +Jan. 12, 2011 - Version 8.44 - "Structured XMP" + + - Added ability to specify XMP structures when writing (yet another Christmas + vacation spent adding a significant new feature to ExifTool) + - Added support for new XMP tags in the MWG 2.0 specification + - Added read support for DV video files + - Added support for Reconyx maker notes + - Added option to overwrite existing text output files (-w!) + - Added ability to ignore symbolic directory links with "-i SYMLINKS" + - Added support for Sony Ericsson XMP cell phone location tags + - Added a few new CanonModelID's + - Added a new Minolta/Sony LensType (thanks Jean-Michel Dubois) + - Added a new Olympus LensType + - Added print conversion for all Bitrate tags + - Decode a couple new RIFF tags + - Decode CameraTemperature for a few new Canon PowerShot models + - Improved -struct option to work with all text output formats + - Changed behaviour of XMP lang-alt lists to conform to the July 2010 + specification (x-default item is no longer mandatory) + - Renamed AudioSampleBits tags to AudioBitsPerSample + - Renamed XMP-crs:Temperature tag to ColorTemperature + - Minor change to behaviour when replacing values in XMP lists: new list + items are now all inserted in place of the first deleted item (previously + new items were inserted one-by-one into the holes left by deleted items) + - Fixed bug writing alternate languages for XMP-iptcExt:ArtworkTitle tag + - Fixed problem where console echo was disabled when using -k option from a + bash script + - Attempted to patch problem of -b option affecting newline sequence for + subsequent -execute commands in Windows + - API Changes: + - SetNewValue() now accepts structured values (as HASH references or + serialized strings) + - Struct option now has 3 settings (undef, 0 and 1) + +Dec. 21, 2010 - Version 8.43 + + - Added read support for MXF (Material Exchange Format) files + - Added support for GE (General Imaging) maker notes + - Added a couple of new Pentax LensType's + - Added a couple of new CanonModelID's + - Added a few more values to Casio UnknownMode + - Recognize 3GPP and 3GP2 file extensions + - Improved handling of character encoding errors + - Changed Duration format to always include hours for times > 1 minute + - Fixed minor quirk in HtmlDump output + - Fixed race condition with -stay_open when reading options requiring + additional arguments from the argfile + +Dec. 11, 2010 - Version 8.42 + + - Added a couple more Samsung LensType values + - Added a few new Canon EasyMode values and a Canon LensType value + - Added a new PentaxModelID + - Decode some new H264 tags (thanks Dave Nicholson) + - Decode JUNK chunk in Pentax RS1000 AVI videos + - Flush console output before "{ready}" message when using -stay_open + - Improved decoding of some Canon and Pentax tags (thanks Dave Nicholson) + - Fixed problem copying makernotes from Nikon NRW image to JPEG + - Fixed incorrect decoding of some AEInfo tags for newer Pentax DSLR's + +Dec. 3, 2010 - Version 8.41 + + - Added a new PentaxModelID + - Added a few new values for some Canon tags + - Added some non-standard values to a few XMP-exif tags + - Decode a new Ricoh tag and added a LensID + - Decode more Pentax K-5 tags and values + - Improved decoding of Battery tags for various Pentax DSLR models + - Fixed bug where time could be wrong by up to 2 seconds when shifting + multiple date/time values containing fractional seconds + +Nov. 21, 2010 - Version 8.40 (production release) + + - Added -restore_original and -delete_original options + - Added new Canon, Pentax and Sony LensType values + - Decode more Pentax K-5 tags + - Decode a number of new tags in Nikon D7000 MOV videos + - Decode FocusDistance tags for the Canon EOS 60D + - Decode a few new Panasonic tags + - Decode a few maker note tags from Flip Video MP4 files + - Extract PDF PageMode and PageLayout tags + - Changed family 2 group names for a number of PDF tags + - Changed Canon LensType strings for a few lenses with updated models + - Patched problem reading GPX files which contain no newlines + +Nov. 12, 2010 - Version 8.39 + + - Added read support for RAR archive files + - Added warning for non-standard XMP APP1 header in JPEG images + - Added a new Canon LensType (thanks Rolando Ruzic) + - Decode more Olympus WAV tags + - Decode a few more PDF document property tags + - Decode a new Canon tag + - Extract firmware revision letter with Nikon FirmwareVersion + - Improved decoding of some Pentax tags + - Changed names of a couple of Pentax tags + - Changed name of ASF:FileSize to FileLength to avoid conflict + - Fixed problem creating output files on network drives in Windows + - Fixed bug where MWG module wasn't loaded automatically when -execute was + used + +Nov. 7, 2010 - Version 8.38 + + - Added support for Nikon D3 firmware 2.02 + - Decode many new Pentax K-5 tags and improved decoding of others + - Decode a few more Nikon D3 and D3S settings (thanks Warren Hatch) + - Decode some new Olympus WAV tags (thanks Tomasz Kawecki) + - Decode a few new Canon DPP 3.9.2 tags + - Decode PDF digital signature permission information + - Improved recognition of Adobe Illustrator PS-format AI files + - Disable writing XMP to Adobe Illustrator version 8 and older EPS files + +Oct. 31, 2010 - Version 8.37 + + - Added ability to switch ARGFILE while -stay_open is active + - Fixed a couple of bugs with the new -stay_open option + - Fixed problem with -E option that caused double-escaping of Composite tags + +Oct. 30, 2010 - Version 8.36 + + - Added ability to read/write metadata in Sigma X3F images containing a + JpgFromRaw (eg. all Sigma models except the SD9 and SD10) + - Added -stay_open option to avoid startup delay when called from other + applications + - Added a new Pentax LensType (thanks Hubert Meier) + - Decode a couple of new tags written by Sigma Photo Pro + - Changed family 0 group name for SonyIDC tags to "MakerNotes" + - Improved Composite:LensID to use LensModel if available when LensType is + "Unknown" + - Fixed problem extracting ThumbnailImage from some FujiFilm RAF images + - Fixed problem calculating Red/BlueBalance for some newer Nikon models + +Oct. 23, 2010 - Version 8.35 - "PDF Encryption" + + - Added support for PDF AES-128 and AES-256 encryption (requires Digest::SHA + for AES-256 support) + - Added -password option for processing password-protected PDF documents + - Added write support for a couple more FujiFilm RAF versions + - Added a number of new Olympus SceneMode values + - Added a few new SonyModelID's + - Added a new Nikon LensID (thanks marten) + - Added a Canon LensType and fixed an incorrect one (thanks Andreas Huggel) + - Decode a number of new Canon tags + - Decode a few new Nikon D3S settings (thanks Warren Hatch) + - Extract PDF UserAccess + - Extract Olympus ZoomedPreviewImage + - Updated decoding of Olympus AFPoint for recent E-models + - Avoid writing mandatory IPTC tags unless another IPTC tag actually changes + (eg. trying to delete a non-existent IPTC tag will no longer have the side + effect of generating mandatory IPTC tags) + - Improved language translations + - Improved error message when trying to write a file with the wrong extension + - Renamed a couple of Olympus tags + - Fixed problem reading/writing PDF tags from some encrypted stream objects + - API Changes: + - Added Password option + +Oct. 7, 2010 - Version 8.34 + + - Added read support for XCF and WebP images and WebM videos + - Added a couple of new PentaxModelID's + - Decode a number of new Canon 60D MakerNotes tags (thanks Bogdan for + LensSerialNumber) + - Decode FrameCount from MakerNotes in Nikon MOV videos + - Decode Ambience and some video tags from Canon + - Decode more Canon EOS 1D Mark IV CameraInfo tags + - Updated decoding of Pentax HighISONoiseReduction for newer models + - Changed description of Canon SerialNumber tags + - Fixed problem with extra comma in JSON output when -w option was used + +Oct. 3, 2010 - Version 8.33 + + - Added ability to specify numerator and denominator of rational values + - Decode more Canon custom picture style settings (thanks Tom Kawecki) + - Decode Samsung MP4 "TAGS" information from WP10 videos + - Decode thumbnail image and maker notes from Canon S95 MOV videos + - Decode Microsoft Photo 1.1 EXIF and XMP information + - Fixed problem copying tags dynamically from files with read errors + - Fixed problem setting FileName with a Windows UNC path (leading "\\") + +Sept. 25, 2010 - Version 8.32 + + - Added the ability to use wildcards ('?' and '*') in tag names when + extracting or copying information + - Added a number of new CanonModelID's + - Decode a few more QuickTime tags and improved decoding of others + - Decode UserDefPictureStyle tags for more Canon cameras (thanks Tom Kawecki) + - Extract unknown text-based maker notes under new MakerNoteUnknownText tag + - Tested writing of PDF 1.7 files and removed warning for this version + - Identify Canon MakerNote footer in HtmlDump of DNG images + - Updated MimeType for PSD, AVI, AIFF plus a number of raw file formats + - Changed FileType for Adobe Illustrator (AI) files + - Fixed "Can't handle XMP attribute 'rdf:xmlns'" error when writing some XMP + +Sept. 17, 2010 - Version 8.31 - "CRW+XMP" + + - Added ability to read/write XMP inside CanonVRD, which finally provides a + technique to write XMP in CRW images! (thanks Mike Kobzar for help testing) + - Added a couple of new Canon LensType's and CanonModelID's + - Added a number of new Nikon LensID's (thanks Robert Rottmerhusen) + - Added a new Sony LensType (thanks Mladen Sever) + - Treat 'eng' as a default language in ID3v2 information + - Recognize AIT file extension (AI file) + - Fixed problem where ExifTool could refuse to write PDF files containing + XMP-pdf:PDFVersion information + +Sept. 11, 2010 - Version 8.30 + + - Added a couple of new Nikon LensID's (thanks Robert Rottmerhusen) + - Added a couple more Sigma LensType values + - Added a few more tag values for the new Sony SLT-A33, SLT-A55V and DSLR-A560 + - Added a few more values for various Casio tags + - Added a new Canon LensType (thanks Guido) + - Decode Panasonic ContrastMode for the TZ10/ZS7 + - Decode some Canon CameraInfo tags for the 60D + - Updated Canon custom functions for the 60D + - Updated Flash video to add some new values and decode some new tags + - Updated QuickTime decoding for new track and movie header formats + - Named a couple of unknown Canon tags + - Made Nikon PictureControl and NikonCaptureOutput directories block writable + - Fixed problem geotagging when any coordinate was exactly zero + - Fixed typo in Canon AFAssistBeam converted value + - Fixed problem displaying exiftool documentation on OS/2 (thanks Ilya + Zakharevich) + +Aug. 22, 2010 - Version 8.29 + + - Added a few new CanonModelID's + - Added verbose messages for "unsafe" and "protected" tags which are not + copied + - Decode CameraTemperature for a few new Canon models + - Decode a few new Panasonic tags (thanks Zdenek Mihula) + - Decode a number of new 3rd party RIFF tags + - Recognize Casio-type maker notes in Concord cameras + - Handle "CDATA" sections in XML/XMP + - Fixed problem that could cause value to be added twice when writing MWG + list-type tags without specifying a group + - Fixed bug extracting altitude from GPX files containing "rtept" nodes which + could result in an altitude being associated with the next GPS fix + - Fixed problem deleting PreviewImage from MIE files + +Aug. 14, 2010 - Version 8.28 + + - Added ability to specify Photoshop encoding (-charset Photoshop=CHARSET) + - Added support for maker notes of some Sony Ericsson phones + - Improved conversion for SigmaRaw:FocalLengthIn35mmFormat (thanks Niels + Kristian Bech Jensen) + - Fixed bug in calculation of AvgBitrate for QuickTime videos (thanks Mats + Peterson) + - Improved error handling when reading Matroska files + - Fixed -GROUP:geotag= to allow multiple geotag groups to be deleted + separately + +July 31, 2010 - Version 8.27 + + - Added support for QuickTime localized languages and character encodings + - Added support for alternate language ICC_Profile tags + - Added a new XMP-swf tag + - Added a new Sony LensType (thanks Mladen Sever) + - Added ability to specify any group (not only family 0 and 1) for source tag + when copying + - Decode a number of new QuickTime tags + - Decode MakerNoteKodak9 maker notes in a few non-Kodak cameras + - Extract NikonCaptureHistory and drop when copying Nikon MakerNotes + - Calculate AvgBitrate for QuickTime movies + - Fixed names of a few recently added ICC_Profile tags (thanks Jeff Harmon) + - Fixed bug calculating duration of AVI videos for which FrameCount is zero + - Fixed tag ID for XMP-iptcExt:AdditionalModelInformation + - Fixed decoding of ShiftJIS character set + +July 20, 2010 - Version 8.26 + + - Decode a number of new ICC_Profile tags added in approved revisions to the + specification + - Drop NikonCaptureData when copying Nikon MakerNotes (it may be too large for + a JPEG APP1 segment when copying from an NEF image) + - Made NikonCaptureData writable as a block and NikonCapture a deletable group + - Minor addition to tooltip for HtmlDump of offset values + - Fixed problem writing to an incorrectly-typed XMP list (patch for LR3 bug) + - Fixed problem setting file ownership on OS/2 systems when writing (thanks + Ilya Zakharevich) + - Fixed incorrect ICC_Profile tag name (thanks Jeff Harmon) + +July 13, 2010 - Version 8.25 (production release) + + - Added CommonIFD0 shortcut tag to help when deleting metata from TIFF images + - Added a new Pentax LensType and fixed an incorrect one + - Added a new Panasonic ColorMode + - Decode FLAC picture metadata + - Changed ASF Preview tags to be consistent with ID3 and FLAC Picture tags + - Patched problem with funny dash character in cut-n-paste from documentation + on some systems (by allowing the funny dash in command-line arguments) + - Fixed misleading warning message which could appear when writing MWG tags + - Fixed typo in an ID3 tag name (thanks Mats Peterson) + - Fixed an incorrect Sony lens name (thanks Stephen Bishop) + - Fixed problem misidentifying some other RAW files as Epson ERF + +June 30, 2010 - Version 8.24 + + - Added ability to write some Kodak APP3 Meta tags + - Added a few new Olympus LensType's and new values for a couple of other tags + - Added support for yet another Kodak MakerNote variation (M580) + - Added conversion for OOXML DocSecurity tag (thanks Jeff Harmon) + - Added another Nikon ExternalFlashFlags value (thanks Warren Hatch) + - Decode more Canon VRD tags (thanks Gert Kello) and changed some tag names + - Decode a couple of new Canon 7D tags (thanks Vesa Kivisto) + - Decode a few more Sigma tags + - Decode HTML tags written by Microsoft Office + - Decode some MakerNotes tags from Samsung MP4 videos + - Allow RFC 8601 date/time values to be written without seconds + - Fixed conversion for Kodak Meta:SerialNumber + - Changed conversion of Canon FocusDistanceUpper/Lower tags to add units (m) + - Changed the names of some Nikon FlashExposureComp tags + - Changed name of RTF CharactersNoWhiteSpace tag to CharactersWithSpaces to + conform with what Microsoft does with their software as opposed to what they + say in their RTF specification + - Changed a few FlashPix tags for better consistency with OOXML and RTF + - Properly convert OOXML Unicode character entities + - Fixed problem writing some Sigma MakerNote tags + - Fixed problem writing incorrect value for "Uncalibrated" XMP:ColorSpace + - Fixed bug where some unknown Canon values were extracted twice with -U + +June 20, 2010 - Version 8.23 + + - Added write support for FujiFilm RAF images from the HS10 and S100FS + - Added read support for RTF files + - Added read support for FPXR in JPEG APP4 as written by some HP cameras + - Added ability to copy files of any type (now does a straight copy instead of + processing the file if no new values are set for any "real" tag) + - Added new values for CanonModelID, PentaxModelID and SonyModelID + - Added a new Ricoh LensID + - Added conversion for "Off" and "On" values when writing EXIF:Flash + - Added a new Canon LensType and changed the name of one Sigma lens + - Decode more Canon VRD tags and update to DPP 3.8 (thanks Gert Kello) + - Decode FujiFilm AutoDynamicRange + - Changed some DNG tags to make them writable (but "unsafe") + +June 9, 2010 - Version 8.22 + + - Implemented PNG alternate language tags and special character translations + - Added print conversion for XMP-photoshop:ColorMode + - Decode some new Pentax 645D tags/values and added more PentaxModelID's + - Changed family 1 group names for Matroska Chapters + - Changed frame rate conversions to round to 3 decimal points + - Enable summary messages when -b is combined with -w + - Assume local system timezone on specified date (instead of current local + timezone) when writing an IPTC time tag with a date/time value which doesn't + include a timezone + - Fixed conversion of Matroska:ChapterTimeStart/End values + - Fixed an incorrect Panasonic Lens name (thanks Michael Byczkowski) + +June 2, 2010 - Version 8.21 + + - Added read support for Matroska multimedia files (MKA, MKV and MKS) + - Added a new PentaxModelID (Optio E80) + - Decode some information from Casio EX-7000SX APP1 "QVCI", HP Photosmart + R837 APP6 "TDHD" JPEG segments + - Extract more Samsung and HP PreviewImages hidden in other JPEG APP segments + - Extract unknown tags with numerical ID's by default when -v option is used + - Updated default GPSVersionID to 2.3.0.0 when writing + - Fixed bug geotagging from KML file (lat/long were swapped) + +May 26, 2010 - Version 8.20 - "Exif 2.3" + + - Added read support for Open Document files (ODP, ODS, ODT) + - Added Composite:AudioBitrate tag for VBR MPEG audio + - Added support for IPTC:CatalogSets written by iView MediaPro + - Decode Olympus MagicFilter tag and add a two new SceneMode values + - Decode a few new Sony tags written by NEX models + - Decode a number of new Sony A100 tags (thanks Igal Milchtaich) + - Decode some information from MPEG audio LAME header + - Updated to Exif 2.3 specification (!!) + - Allow date/time tags to be shifted by the values of other tags when using + the -tagsFromFile feature + - Fixed formatting of QuickTime:CreateDate as written by iPhone + - Fixed problem conditionally replacing some blank EXIF tags and alternate + language tags in XMP + +May 11, 2010 - Version 8.19 + + - Added ability to read/write Samsung PreviewImage trailer + - Added two new PentaxModelID's (Optio H90 and W90) + - Added a new Canon LensType + - Added a new CanonModelID + - Decode more Sony tags/values (thanks Michael Reitinger) + - Decode more Leica M9 tags (thanks Michael Byczkowski and Carl Bretteville) + - Updated to XMP April 2010 specification + - Avoid extracting Sony DSLR-A100 tags which have "n/a" values + - Improved German language translations (thanks Herbert Kauer) + - Improved efficiency of Composite tag calculations + - Made RSRC a deletable group + - Tolerate extra white space at the start of an XMP file + - Changed MWG logic to ignore blank EXIF tags + - Changed a few print conversion strings to improve interoperability + - Changed XMP namespace prefix 'prismusagerights' to 'pur' as per most recent + PRISM specification + - Patched memory problem in Windows when processing very large EPS files + - Fixed a couple of incorrectly named Sony Panorama tags + - Fixed bug which could prevent file from being updated when deleting + mandatory tags and adding back tags in other locations + +Apr. 16, 2010 - Version 8.18 + + - Added read support for Sony DSC-F1 PMP images + - Added a new Nikon LensID (thanks Jeffrey Friedl) + - Decode a number of new Sony tags (thanks Michael Reitinger) + - Decode a few more Leica M9 tags (thanks Michael Byczkowski) + - Preserve original file permissions and ownership when writing + - Made Canon DustRemovalData writable + - Changed some Pentax WhiteBalance strings for consistency + - Patched potential security problem when writing values + - Fixed bug extracting unsynchronized ID3v2.4 information + +Apr. 9, 2010 - Version 8.17 + + - Added a new Sony ExposureMode (thanks Michael Reitinger) + - Decode Casio DriveMode (thanks Robert Chi) + - Decode CameraTemperature for more Canon EOS models (thanks Vesa Kivisto) + - Updated to the DICOM 2009 specification (Note: Changed some DICOM tag names) + - Improved conversions for XMP:LensInfo, EXIF:DNGLensInfo and Nikon:Lens + - Changed case of some Canon DriveMode strings + - Fixed divide-by-zero error when Geotagging from a track with only one point + - Fixed incorrect ImageHeight reported for top-to-bottom BMP images + - API Changes: + - Fixed a problem passing options to Image::ExifTool::TagInfoXML::Write() + +Mar. 31, 2010 - Version 8.16 + + - Preserve Mac OS resource fork when writing (OS X only) + - Added a number of new Nikon LensID's (thanks Robert Rottmerhusen) + - Decode a couple more Mac OS resources + - Decode Olympus LensModel tag (thanks Martin Hilbers) + - Extract PrintIMVersion tag from PrintIM information + - Separate extraction of Leica FrameSelector information from LensType tag + - Recognize Bitstream PFA/PFB font files + - Patched ActivePerl 5.10 bug which could cause Perl crash during Geotag tests + - Fixed another Geotag test that fails due to round-off errors on some systems + +Mar. 18, 2010 - Version 8.15 (production release) + + - Added read support for Macintosh resource files: + - Generate ResourceForkSize tag if data exists in a file's resource fork + - Enhanced -ee option to process resource fork as a sub-document + - Added a new PentaxModelID (Optio I-10) + - Decode Panasonic DMC-ZS7 landmark tags + - Fixed decoding of Pentax Optio 555 PictureMode and added a number of new + values (thanks Ralf Medow) + +Mar. 16, 2010 - Version 8.14 + + - Added some new Canon AFMode values for the EOS 7D (thanks Dieter Steiner) + and renamed tag to AFAreaMode + - Decode ColorData and some new MOV tags for the production Canon EOS 550D + - Decode Panasonic IntelligentResolution tag + - Allow times with timezones in GPX track logs + - Improved handling of maker notes in Olympus MP4 videos + - Changed H264 GPS tags to the GPS group + - Fixed date/time format error in reverse geotagging GPX example + - Fixed problem introduced in version 8.09 where XMP:GPSLatitude/GPSLongitude + require the -a option to be extracted + - API Changes: + - Fixed bug where some options (Charset, Escape, Exclude and Lang) weren't + activated properly when set via options hash in calls to some functions + - Fixed some potential problems when used with mod_perl + +Mar. 5, 2010 - Version 8.13 + + - Added read/write support for Samsung SRW images and decode some NX10 maker + note tags (thanks Tae-Sun Park) + - Added new values for some Sony tags (thanks Michael Reitinger) + - Added a new Canon LensType + - Decode maker notes in Nikon Coolpix S8000 MOV videos + - Decode a number of obscure TIFF FX tags + - Implemented list-type behaviour for MWG:Creator tag + - More improvements to German translations (thanks Herbert Kauer) + - Changed name of NikonPreview group to PreviewIFD + - Fixed problem which prevented ThumbnailImage from being written to ARW, SR2 + and PEF images + +Feb. 26, 2010 - Version 8.12 + + - Added a number of missing ProgramMode values for the Sony DSLR-A330 + - Added XMP-iptcCore:DigitalSourceType (IPTC Extension version 1.1) + - Added a couple more Nikon LensID's (thanks Jens Kriese and Robert + Rottmerhusen) + - Improved German language tag descriptions (thanks Herbert Kauer) + - Improved identification of some RAW file types + - Moved MPF PreviewImage into the Composite group + - Fixed some problems in HtmlDump output + - Fixed problem copying makernotes as a block into DNGAdobeData + +Feb. 20, 2010 - Version 8.11 + + - Added support for Leica S2 maker notes + - Added a bunch of new CanonModelID's + - Decode MacroMagnification for more Canon models (MP-E 65mm only) + - Decode a number of Canon CameraInfo tags for the 1DmkIV and 550D + - Updated CanonCustom tags for the 550D + - Improved parsing of Canon OriginalDecisionData + - Improved decoding of Canon CameraInfo LensType + - Improved decoding of some Sigma tags + - Recognize a number of new Paint Shop Pro file extensions + - Prevent a directory from being recreated in the wrong location when deleting + a group and adding back information in the same step + - Changed -fileOrder option to sort numbers numerically + - Fixed bug in -fileOrder option when directory names are specified + - Fixed problem extracting information from some Panasonic AVCHD videos + - Fixed some minor compatibility problems with Perl 5.11 + - Fixed problem which could result in runtime error when using MWG feature + - Fixed an inconsistency in the way duplicate tags were handled in the grouped + JSON (-j -g) and short XML (-X -s) output formats + +Feb. 8, 2010 - Version 8.10 (production release) + + - Added read/write support for Photoshop PSB file format + - Added -fileOrder option to provide control over file processing order + - Added a few new Sony/Minolta LensTypes (thanks Marcin Krol) + - Added more Nikon LensID's (thanks Robert Rottmerhusen) + - Decode metadata from all frames in AVCHD H.264 video with -ee option + - Decode more H.264 tags and improved decoding of others + - Improved decoding of some Olympus E-P1 tags + - Improved handling of some types of unknown maker notes + - Enhanced -p option to support output file headers and footers, and to parse + embedded documents as separate input files when combined with -ee + - Relaxed validation of PFM files to accommodate incorrect device type string + written by FontForge software + - API Changes: + - Enhanced GetFileType() to return descriptions for more file types + +Jan. 29, 2010 - Version 8.09 + + - Added a number of new Nikon LensID's (thanks Robert Rottmerhusen) + - Decode GPS position and some camera settings from AVCHD (.M2TS) video + - Decode a few new PhotoMechanic tags + - Decode MacroMagnification for the Canon MP-E 65mm f/2.8 1-5x Macro Photo + lens in EOS 5DmkII and 40D images + - Delete multiple Photoshop segments in JPEG images when deleting all + Photoshop information and adding some back in one step + - Print warning message in Windows when there are no matching files to process + - Changed print conversion for PSP CreatorAppVersion + - Fixed problem rewriting NikonCapture information written by NX2 + +Jan. 25, 2010 - Version 8.08 + + - Added read support for Paint Shop Pro images (PSP and PSPIMAGE) + - Added ability to decode a number of new character sets including JIS, and + completely overhauled character encoding routines + - Fixed problem reading old OS/2-format BMP images + +Jan. 19, 2010 - Version 8.07 + + - Added read support for a number of font file formats (OTF, TTF, TTC, PFA, + PFB, PFM, DFONT, AFM, ACFM and AMFM) + - Added (experimental) read support for FLA files + - Added a few new Sony LensType's (thanks Sander Stols) + - Added a new Canon LensType (thanks Mark Berger) + - Set BigTIFF MIME type to "image/x-tiff-big" (unofficial) + - Fixed bug in GPS time drift correction when dates are specified for both GPS + and image times + - Fixed problem reading some IGC GPS logs + +Jan. 12, 2010 - Version 8.06 + + - Added a few new CanonModelID's + - Fixed a bug introduced in 8.05 which broke rewriting of XMP in MWG mode + +Jan. 10, 2010 - Version 8.05 - "Strict MWG" + + - Improved MWG conformance by ignoring non-standard EXIF, IPTC and XMP when + the MWG module is loaded + - CurrentIPTCDigest tag is now only generated for IPTC in the standard + location (as specified by the MWG recommendation) + - Added support for 3rd party trailers on ARW images + - Changed names of Sony IDC date/time tags and decode the last unknown IDC tag + - Fixed "-TAG-= -TAG=VALUE" syntax to work with shiftable (date/time) tags + and tags with conversions + - Fixed incorrect tag format when writing some PhotoMechanic tags + - Fixed problem where some tags couldn't be written in Olympus ORF images + +Jan. 7, 2010 - Version 8.04 - "Write ARW" + + - Added write support for Sony ARW and SR2 images (at long last!) + - WARNING: Some Adobe utilities (Photoshop Camera Raw 5.6, DNG Converter + 5.6, LightRoom 2.6) have a bug which causes the tone curve to be + incorrect for edited ARW images from some Sony cameras (A500, A550, + A700, A850, A900 and maybe others) + - Compatibility Notice: Embedded JPEG in ARW and SR2 images is now extracted + as PreviewImage instead of JpgFromRaw + - Added read/write support for Sony IDC tags + - Added support for Leica X1 maker notes and decode a few tags + - Added support for DigiKam XMP schema + - Added a new Minolta/Sony LensType (thanks Jean-Michel Dubois) + - Decode Nikon D90 AFAreaModeSetting + - Decode Nikon NEFBitDepth (thanks Warren Hatch) + - Decode a few new Sony SRF, Casio AVI and MSOffice TIFF tags + - Enhanced Geosync tag to allow GPS time-drift correction + - Fixed Nikon D3 FlashSyncSpeed values (thanks Warren Hatch) + +Dec. 19, 2009 - Version 8.03 + + - Added a new Nikon ExternalFlashFlags value (thanks Warren Hatch) + - Implemented -charset id3=CHARSET option in Windows version too (oops!) + - Improved heuristic for guessing EXIF "Unicode" string byte order + - Improved decoding of some obscure QuickTime tags + - Renamed Casio SelfTimer tag to ReleaseMode and added new values + - Fixed problem converting numerical M4P Genre values + +Dec. 15, 2009 - Version 8.02 + + - Added MIME types for Apple iWork file formats + - Added bitmask to -v2 output for applicable tags + - Added a new Canon LensType and fixed an incorrect one (thanks Hugh + Griffiths) + - Added a few new Ricoh Saturation values (written by GXR) + - Added ability to specify character set for ID3v1 information + - Added French translations for some Nikon tags (thanks Harry Nizard) + - Extract FilePermissions information + - Decode Nikon D90 custom settings + - Decode a few more Nikon tags and removed AutoBracketRelease (thanks Warren + Hatch) + - Decode a few more GIF tags (and changed groups of some others) + - Decode some information from JPEG APP4 "SCALADO" segment + - Updated DICOM decoding to latest (2008) specification + - Enhanced -fast option to allow MakerNote information to be skipped + - Changed -v0 to enable output autoflushing for STDERR as well as STDOUT + - Improved decoding of some QuickTime tags (fixes M4P Genre problem) + - API Changes: + - Added CharsetID3 option + - Changed name of IPTCCharset option to CharsetIPTC (but IPTCCharset may + still be used for backward compatibility) + +Dec. 1, 2009 - Version 8.01 + + - Compatibility Notice: Extract full-sized preview from X3F images as + JpgFromRaw instead of PreviewImage + - Added support for the new X3F version 2.3 files written by the Sigma DP2 + - Added support for a few more XMP-acdsee tags + - Decode Nikon D3 custom settings (thanks Warren Hatch) and extrapolate to + D3S, D3X and D300S + - Decode the few remaining Nikon D300 custom settings (thanks Stuart Solomon + for providing sample images) + - Decode Nikon D5000 custom settings + - Decode Nikon FlashColorFilter tag (thanks Warren Hatch) + - Decode a few more PNG tags + - Created a new family 1 group for Nikon custom settings + - Improved write conversions for EXIF Contrast, Saturation and Sharpness + - Fixed problem with %f and %e when the source file has no extension + - Fixed problem decoding Nikon D3 flash group B and C intensities + - Fixed missing MIME type for XLT files + +Nov. 20, 2009 - Version 8.00 (production release) + + - Added read support for Apple iWork '09 files (Keynote, Pages and Numbers) + - Added ability to write Nikon SerialNumber and ShutterCount tags + - Added a few new Nikon LensID's and changed Tamron lens names to include + model number (thanks Robert Rottmerhusen) + - Decode a number of new Nikon tags (thanks Warren Hatch for much of this) + - Decode a few new Sony tags and improved others (thanks Igal Milchtaich) + - Decode a few new Ricoh tags, renamed RicohDateTime1/2, Revision and + MakerNoteVersion tags, and added some print conversions + - Decode Parallax in FujiFilm MPO MPImage2 images (thanks John Goodman) + - Decode Canon EOS 1D Mark IV custom functions + - Decode a number of new tags in MPEG-4 videos + - Decode a large number of private GE DICOM tags + - Decode a few more tags in AVI videos and attempt to fix problem calculating + duration when multiple video streams exist + - Enhanced -ee option to extract information from embedded MPF images + - Improved Nikon LensID conversion to recognize user-defined lenses + - Improved decoding of a few Olympus tags (ArtFilter, FaceDetect and + FocusProcess) + - Improved handling of warnings when processing corrupted ZIP files + - Improved recognition of Canon teleconverters in Composite LensID tag + - Added patch for Leica M8 bug which writes incorrect format for EXIF + ExposureCompensation and ShutterSpeedValue + - Changed prefix of unknown Leica M9 tags from LeicaSubdir to Leica_Subdir + - Fixed problem writing encrypted Nikon WB Levels + - Fixed problems reading PDF tags written by OS X 10.6 utilities + - Fixed problem where the -charset option didn't work properly for some XML + character entities when reading XMP + +Nov. 6, 2009 - Version 7.99 + + - Added read support for Office Open XML files and improved recognition of + many MS Office file types + - Added read support for Phase One IIQ and Capture One COS and EIP files + - Added read support for GZIP information (first archived file only) + - Added a new Canon LensType (thanks Karsten Sote) + - Added a new Nikon LensID (thanks Geert De Soete) + - Decode a few new Sony tags + - Decode MakerNotes in Pentax AVI videos + - Decode SerialNumber for newer Pentax cameras + - Decode Canon FlashMeteringMode for most EOS models + - Disabled some Sony A230 CameraInfo tags which weren't valid for this model + - Give names to a number of unknown QuickTime atoms + - Recognize VOB file extension (but audio information in MPEG private stream + is not yet decoded) + - Tolerate extra white space in GPX attributes when geotagging (fixes problem + reading GlobalSat GPX files) + - Minor improvements to FlashPix decoding + - Changed names of all ZIP tags to avoid name conflicts with other tags + - Changed Composite ImageSize to use ExifImageWidth/Height for CR2 images + - Changed names of QuickTime image and video track description + ImageWidth/Height tags to SourceImageWidth/Height + - Fixed problems when -if option was combined with -v or -htmlDump + - Fixed problem parsing NMEA track logs where coordinates have the wrong + number of digits due to missing leading zeros (Holux M-241) + - Fixed an incorrect Pentax LensType + +Oct. 28, 2009 - Version 7.98 + + - Implemented MWG support via a plug-in module ("-use MWG") + - Added -config and -use options + - Added ability to read Sony Vegas tags in AVI videos + - Added a couple of new Canon LensType's + - Added a new Panasonic ShootingMode (thanks Joerg) + - Added a new PentaxModelID (Optio P80) + - Added a new CanonModelID + - Added a few new Canon 1D Mark IV custom functions values + - Added warning for superfluous tag names on the command line when writing + - Decode a few more tags for the Canon EOS 5D and 7D + - Decode a number of new tags in Quicktime-based files (including MP4 and JP2) + - Impose length limit on IPTC values when writing as per spec. (for backward + compatibility, the length check may be disabled with the -m option) + - Improved checks for invalid EXIF offsets and changed some warning messages + - Improved decoding for a few Canon tags (and renamed NoiseReduction tag) + - Improved date/time formatting to accept date-only values + - Implemented print conversion for ID3 date/time tags + - Enhanced writing of Photoshop:IPTCDigest to allow a special value of 'old' + to represent the digest of the IPTC from the original file + - Updated iptc2xmp.args and xmp2iptc.args to handle IPTC + DigitalCreationDate/Time + - Recognize a number of Sigma LensType's in X3F images + - Recognize a large number of additional audio/video file extensions + - Minor improvements to -htmldump output + - Minor changes to some application warning messages + - Fixed problem writing Canon CameraTemperature tags + - Fixed "Error reading Info object" warning when reading a PDF file after + deleting all PDF tags + - API Changes: + - Added ability to specify config file via $Image::ExifTool::configFile + - Added EditGroup option for SetNewValue() + +Oct. 13, 2009 - Version 7.97 + + - Added ability to disable print conversion on a per-tag basis by suffixing + the tag name with a '#' character + - Added a new PentaxModelID (Optio WS80) + - Decode a few more Sony tags + - Decode a number of new Casio tags and values + - Decode CameraTemperature for Canon PowerShot models (thanks Vesa Kivisto) + - Improved warning messages for the -ext option + - Improved DOF calculation to use ObjectDistance if SubjectDistance and + FocusDistance are not available + - Improved -X output to support more of the new -charset encodings + - Made Composite:FileNumber writable + - Use more detailed makernote directory names in EXIF warning messages + - Decreased priority of tags in IFD1 of JPEG images to avoid taking precedence + over tags from IFD0 or ExifIFD + - Changed print conversion strings for TIFF SampleFormat tag + - Renamed Casio ObjectDistance tag to FocusDistance + - Fixed invalid character in a Minolta/Sony LensType string + - Fixed bug decoding NITFVersion tag + - Fixed bug where binary data was returned without the -b option when using an + expression involving tag names for some tags such as ThumbnailImage + - Fixed two problems which could result in runtime warnings when: + - reading truncated ICC_Profile information + - using -htmldump on an image containing invalid EXIF offsets + - API Changes: + - Added ability to disable print conversion by suffixing tag name with '#' + - Changed name of BigTIFF 'ifd8' format to 'ifd64' for consistency + +Oct. 2, 2009 - Version 7.96 + + - Added new Geosync tag to allow geotagging of images with timestamps which + are not pre-synchronized to GPS time + - Added patch to avoid crash bug in Canon DPP software when OwnerName is set + to a value that is exactly 3 characters long (doh!) + - Added a few new Olympus LensType's (thanks Godfrey DiGiorgi) + - Added a couple more Nikon LensID's (thanks Robert Rottmerhusen) + - Added minor warning when fixing invalid counts in Kodak MakerNotes + - Decode a few new tags and values for the Panasonic GF1 + - Improved parsing of command-line arguments to remove order dependencies of + certain options + - Minor improvement to decoding of Olympus FaceDetect tag + - Changed "Error reading PreviewImage from file" to a minor warning + - Changed conversion of Canon MeasuredEV to correspond more closely to + LightValue (by adding 5 to the MeasuredEV value, which seems to be good for + all EOS models, but it may be high by up to 1 EV for some PowerShot models) + - Fixed problems decoding some CameraInfo tags for the Canon 7D with the new + production firmware (1.0.7) + - Fixed problems writing some CameraInfo tags for the Canon 50D and 5DmkII + +Sept. 24, 2009 - Version 7.95 + + - Added read support for LNK (Windows shortcut) file metadata + - Added patch to fix incorrect count written by a number of recent Kodak + cameras to some tags in SubIFD3 of the MakerNotes + - Added a few more Sony/Minolta LensType's + - Added a couple more Canon LensType's (thanks Norbert Wasser) + - Added a PentaxModelID for the new K-x + - Decode a couple more Canon VignettingCorr tags + - Improved Canon FocusDistance conversions to indicate "inf" for maximum value + - Improved DOF calculation to use SubjectDistance if FocusDistance is not + available + - Changed -fast, -scanForXMP and -unknown options to also apply when copying + tags with -tagsFromFile + +Sept. 11, 2009 - Version 7.94 + + - Added support for Leica M9 makernote format and decode a few new tags + - Added a few new Leica LensType's + - Added support for IGC GPS track logs (thanks Lionel Genet) + - Added a number of alternate Macintosh character sets and changed a couple of + character set names for -charset option + - Decode even more Sony A100 tags (thanks Igal Milchtaich!) + - Improved handling of FlashPix character translations + - Changed a couple of Sony and Minolta AF tag names to be more consistent + +Sept. 5, 2009 - Version 7.93 + + - Added a new CanonModelID + - Added a couple of new Nikon LensType's (thanks Robert Rottmerhusen) + - Added a few new Pentax LensType's + - Decode a number of new tags for the Canon EOS 7D + - Calculate Duration for WAV audio files + - Allow exponents when writing GPS coordinates (eg. "-gpslatitude=7.657e+01") + - Print available character sets if no CHARSET is given for -charset option + - Improved -v3 and -htmldump output to show MPF image data + - Fixed -E option to work with tag descriptions when -lang option used + - Fixed problem reading large FlashPix-format documents + - API Changes: + - Added LargeFileSupport option + +Aug. 29, 2009 - Version 7.92 + + - Fixed new "-charset iptc=CHARSET" feature to work with -tagsFromFile + +Aug. 29, 2009 - Version 7.91 + + - Added -charset option and support for additional Windows and Mac character + sets. Character sets now supported are: UTF-8, Latin1, Latin2, Cyrillic, + Greek, Turkish, Hebrew, Arabic, Baltic, Vietnam, Thai and MacRoman + - Fixed problem with some duplicate Nikon LensID's + - Fixed incorrect Duration calculation for multi-channel FLAC audio files + - Compatibility Notice: Removed "CreatorContactInfo" shortcuts which were + added to ease the transition when some Iptc4xmpCore tag names were changed + in version 7.45 + - API Changes: + - Added IPTCCharset option and support for additional character sets + +Aug. 24, 2009 - Version 7.90 + + - Added -ex (-escapeXML) option + - Added a few more Minolta M42-type lenses (thanks Lukasz Stelmach) + - Added a number of new CanonModelID's + - Decode more Sony A100 tags (thanks Igal Milchtaich) + - Decode a few more Kodak WhiteBalance tags + - Decode a couple more JPEG APP segments + - Internal changes to Composite tag calculation algorithm + - Patched problem with renaming files on OS/2 that caused failed tests + +Aug. 18, 2009 - Version 7.89 (production release) + + - IMPORTANT: Not quite done with NRW fixes -- fixed similar bug which could + corrupt NRW images when writing new values larger than 10 MB + +Aug. 17, 2009 - Version 7.88 (production release) + + - IMPORTANT: Fixed bug introduced in version 7.77 which causes Nikon NRW + images to be corrupted when writing + - Decode a number of Sony A100 Camera Settings tags (thanks Igal Milchtaich) + - Improved accuracy of some CameraInfo values for Canon PowerShot models + - Tolerate blank lines in PDF xref tables + - Fixed problem where -E didn't escape values when copying with -tagsFromFile + - Fixed bug identifying AF Micro-Nikkor 105mm f/2.8D lens + +Aug. 14, 2009 - Version 7.87 + + - Added a new Sony lens (thanks Lukasz Stelmach) + - Added a few new Pentax City and PictureMode values (thanks Niels Kristian + Bech Jensen) + - Added lookup for XMP-photoshop:Urgency + - Added a few new Nikon RetouchHistory values + - Decode a number of new Sony tags for the A700 (thanks Rudiger Lange) + - Decode Canon PeripheralLighting tags + - Decode Olympus AFFineTuneAdj (thanks Yrjo Rauste) + - Extract System tags from unknown file types + - Enhanced -E option to work when writing, and when used in combination with + other options such as -p + - Tolerate white space around "=" in XMP attributes (allowed by XML spec) + - Improved error handling when parsing bad EXIF IFD entries + - API Changes: + - Added Escape option + +July 25, 2009 - Version 7.86 + + - Added support for reading Garmin TCX track logs with the -geotag option + - Added a number of new Canon, Olympus and Pentax LensType's + - Enabled writing of .AI (Adobe Illustrator) files + - Minor changes to DICOM decoding + +July 21, 2009 - Version 7.85 + + - Added a new Sony LensType + - Added a new Pentax LensType (thanks Albert Bogner) + - Added a new PentaxModelID value (Optio W80) + - Added a few new JPEGDigest values (thanks Franz Buchinger) + - Added check for proper support of IFD-format value types + - Decode Nikon D300 firmware 1.10 camera settings (thanks Stuart Solomon) + - Improved handling of Olympus makernotes for recent models and fixed error + messages resulting from makernote format changes in Stylus 550WP images + - Improved geotagging by allowing different NMEA sentences with slightly + different timestamps (within 10 seconds) in the same fix + - Fixed decoding of some CameraSettings tags for the new Sony A330 and A380 + - API Changes: + - Added GeoMinSats option + +July 16, 2009 - Version 7.84 (Windows only) + + - Fixed bug in -geotag option of Windows version when using wildcards in the + GPS track filename + +July 13, 2009 - Version 7.83 + + - Added preliminary read support for M2TS/AVCHD video files (much pain for + little gain) + - Added family 4 group names (instance number) to provide a technique for + differentiating same-named tags extracted from the same location via the + command-line application + - Added a new family 1 group ("System") to differentiate tags obtained from + the file system + - Added a couple of new Canon LensType values + - Decode ID3 Picture attributes + - Decode ICC_Profile ColorantTableOut + - Changed application to return a value of 1 if all files fail condition + - Made the IPTC CodedCharacterSet tag "unsafe" to copy by default (since this + could result in incorrect encoding for existing IPTC in the destination + image) + - Fixed bug handing some non-standard offset formats when writing EXIF + - Fixed problem with MakerNote warnings for Samsung WB500 + - Fixed problem reading Leica M8 makernotes when copied between JPEG and DNG + images + - Fixed problem extracting ThumbnailImage from Sanyo VPC-FH1 MP4 videos + - Fixed problem extracting ThumbnailImage from some Sony DSLR-A100 ARW images + (due to a bug in some A100 firmware versions which results in incorrect + ThumbnailOffset values) + +July 2, 2009 - Version 7.82 (production release) + + - Added a new Canon LensType (thanks Norbert Wasser) + - Decode another Nikon AVI tag + - A number of improvements, bug fixes and additions to ID3 decoding + +June 28, 2009 - Version 7.81 + + - Added a few missing print conversions to Nikon, Kyocera and FlashPix + date/time tags + +June 26, 2009 - Version 7.80 + + - IMPORTANT: Fixed bug introduced in 7.77 which had the potential to corrupt + TIFF-format images when writing to an image containing a SubIFD tag larger + than 10 MB (not that I've ever seen one of these in the wild) + - Added support for DNG version 1.3 + - Decode makernotes in Nikon AVI videos + - Decode QuickTime MatrixStructure tag and added Composite Rotation tag to + calculate the rotation of the QuickTime video track + - Updated CanonCustom tags for the EOS 500D + - The -fast option now stops parsing of WAV and AVI files at audio/video data + - API Changes: + - Improved handling of $/ by localizing internally + +June 20, 2009 - Version 7.79 + + - Added read/write support for Adobe InDesign files (.IND, .INDD, .INDT) + - Added ability to geotag with KML files (Note: each Placemark must contain a + TimeStamp for this to work) + - Added undocumented XMP-xmp PagInfo tags written by Adobe InDesign + - Added conversion for MPF:PanOrientation + - Many improvements and additions to Olympus and Panasonic makernote decoding + - Improved logic of -scanForXMP option + - Recognize MPO file extension (Extended Multi-Picture format) + - Distinguish between infinite (inf) and undefined (undef) rational values + - Changed namespace prefixes for xapG and xapGImg to match current XMP spec + - Changed print conversion for Casio AFPointPosition + - Made "Error reading value" warning minor when reading makernotes values + - Allow all tags to be deleted from an XMP file + - Fixed group names for a few Panasonic and Sony makernote tags + +June 13, 2009 - Version 7.78 + + - Added read support for the new CIPA standards: Multi Picture Format (MPF) + and Stereo Still Image format (Stim) + - Added support for Kodak type 10 makernotes (Z980) + - Added a new Pentax LensType and a new Nikon LensID (thanks Jens Duttke) + - Added %C format code for output file names + - Decode a number of camera settings from Sony DSLR images + +June 7, 2009 - Version 7.77 + + - Added -struct option for JSON (-j) and XML (-X) outputs + - Added 2 new Pentax LensType's and a PentaxModelID (thanks Jens Duttke) + - Decode large preview in APP2 of images from newer Samsung models + - Extract FujiFilm PreviewImage from improperly written FPXR segment + - Improved decoding of Nikon WB levels for some models + - Reduced memory usage when writing DNG and some other RAW image files + - Changed format of Canon D30 SerialNumber to remove the hyphen and add + leading 0's if less than 9 characters (now same format as printed on camera) + - Changed writing of GPSTimeStamp and GPSDateStamp to adjust date/time to UTC + if it contains a timezone, and added timezone ("Z") to Composite:GPSDateTime + - Suppress "Unlisted FPXR segment (index 255)" warning from some Kodak images + - Suppress "Unrecognized MakerNotes" warning for Samsung STMN-type maker notes + - Made "Unrecognized MakerNotes" a minor warning + - Fixed problems reading/writing large PreviewImage in some Sony JPEG images + - Fixed problem decoding some base64 values in XML files + - API Changes: + - Added Struct option (considered experimental) + +May 20, 2009 - Version 7.76 + + - Added support for Leica RWL raw images (just RW2 with a different name -- + Panasonic is pulling the same dumb stunt as Nikon with NRW) + - Added ability to specify geotagging parameters via config file + - Added two new Canon LensType's (thanks Jose Oliver-Didier) + - Added a couple more Panasonic FilmMode values + - Added bitmapped value lookups to -listx output + - Decode Panasonic face recognition information (DMC-TZ7) + - Decode some new FujiFilm face detection tags + - Implemented language translations for bitmapped values + - Enhanced -geotag option to allow wildcards in track file name + - Minor changes to Nikon AF point decoding + - Allow empty string when writing unknown values (ie. "Unknown ()") + - Pad numerical IPTC values with zeros if necessary when writing + - Fixed problem with -geotag feature interpolating in some NMEA logs + - API Changes: + - Added GeoMaxHDOP, GeoMaxPDOP, GeoMaxIntSecs and GeoMaxExtSecs options + +May 9, 2009 - Version 7.75 + + - Added a few new translations (thanks Jens Duttke et al) + - Added warning when stream mode data is encountered in a ZIP file (this + is currently not supported) + - Added a couple of new Nikon ActiveD-Lighting values (thanks Werner Kober) + - Added and changed some Nikon LensID's (thanks Robert Rottmerhusen) + - Added ability to specify user-defined option defaults in config file + - Added write support for FujiFilm S5Pro firmware 1.11 RAF images + - Decode AF point information for more Nikon models (thanks Werner Kober) + - Improvements to new geotagging feature + - Changed language code for simplified Chinese from "zh_s" to "zh_cn" + - Changed user-defined shortcuts to Image::ExifTool::UserDefined::Shortcuts + - Limit PrintConv precision of Composite GPSAltitude to 1 decimal place + - API Changes: + - Changed WriteInfo() to use a temporary file instead of a memory buffer + when a source file name is given with no destination file + - Attempt (yet again) to fix problems when UTF-8 encoded strings are + passed to exiftool functions + +Apr. 10, 2009 - Version 7.74 + + - Added geotagging feature and new -geotag option (guess who finally bought a + hand-held GPS!) + - Added a few new Casio RecordMode values + - Decode FujiFilm EXRAuto and EXRMode tags (FinePix F200EXR) + - Decode Olympus ArtFilter tag + - Allow EXIF ISO to have multiple values as per EXIF spec + - Improved XMP-exif and XMP-tiff list-type tags to allow copying from EXIF + - Changed handling of ComponentsConfiguration to facilitate copying between + EXIF and XMP + - Changed name of EXIF tag 0x9214 from SubjectLocation to SubjectArea to match + EXIF specification + - Changed behaviour when writing pre-existing EXIF tags to use the standard + EXIF field type instead of preserving the existing type (fixes problem + rewriting some incorrectly typed EXIF tags) + - Fixed error if a shift value is not given when shifting a date/time tag + - Fixed makernote offsets error message when writing Pentax Optio WP images + - API Changes: + - Added EditOnly option to SetNewValue() + +Mar. 31, 2009 - Version 7.73 + + - Added write support for Panasonic RW2 images (including IPTC and XMP) + - Added ability to write IPTC and XMP to Panasonic/Leica RAW images and fixed + bug introduced in version 7.64 which disabled write support for these images + - Added a new Canon EasyMode value (thanks Irwin Poche) + - Added a number of new Nikon LensID's (thanks Robert Rottmerhusen) + - Added CanonModelID for the new 500D + - Decode many CameraInfo and ColorData tags for the Canon EOS 500D + - Decode track-level 'meta' atom in MOV videos + - Enhanced Canon Composite:ShootingMode logic to distinguish Bulb mode + - Improved decoding of Canon TargetExposureTime + - Changed name of Panasonic RW2 PreviewImage to JpgFromRaw + - Fixed bug where JPEGDigest wasn't generated for some images + - Fixed problem where -F didn't permanently fix makernote offsets for some + images when writing + - Fixed bug decoding Canon RawMeasuredRGGB and MeasuredRGGBData which resulted + in a failed test on 64-bit systems + +Mar. 20, 2009 - Version 7.72 + + - Added a new Minolta/Sony LensType (thanks Jens Duttke) + - Added support for localized language descriptions of "lang-alt" tags + - Added support for Nikon NRW files (please just kill me now) + - Added two new PentaxModelID's and a new PentaxImageSize + - Decode Pentax PEF HuffmanTable as Unknown Binary tag + - Decode Leaf and Kodak records in DNGAdobeData information + - Made "Empty PrintIM data" a minor warning + - Minor improvement to Canon lens recognition logic + - Changed Composite:LensID to also return a value for Olympus lenses + - Changed copying behaviour to preserve the specific location (family 1 group) + when source group is specified and destination group is "all" or "*" + (eg. "-exif:all>all:all" now preserves the IFD of each tag) + - Fixed a number of incorrect Minolta/Sony lens names (thanks Olaf Ulrich) + - Fixed bug rewriting MIE trailers on TIFF images + +Mar. 12, 2009 - Version 7.71 + + - Added a new Pentax LensType (thanks Akos Szalkai) + - Added a new Canon LensType (thanks Kurt Garloff) + - Added new PentaxModelID for the Optio P70 + - Added XMP list-type flag (Alt, Bag or Seq) to "-f -listx" output + - Decode a number of new Canon tags (thanks Vesa Kivisto) + - Removed unreliable Canon Composite FlashOn tag (use Flash instead) + - Removed Nikon FlashModel tag and replaced it with ExternalFlashFirmware + - Changed tags in Canon "ColorBalance" tables to signed integer and renamed + the tables to "ColorData" + - Changed formatting for Canon FocalUnits + - Changes to -X output: + - Now uses 'rdf:datatype' instead of 'et:encoding' (thanks Alexander Vonk) + - Improved long (-l) output to produce valid RDF/XML, and added 'et:val' + - Improved handling of unknown XMP lang-alt tags + - Fixed family 2 group names for a few tags + +Feb. 26, 2009 - Version 7.70 + + - Added a few new Nikon LensID's (thanks Robert Rottmerhusen) + - Added a number of new CanonModelID's + - Added ability to use -f before -listx to output 'flags' attribute + - Added xml:lang attribute to -X output (when used with -t, -H or -D) to + identify alternate language entries for XMP lang-alt tags + - Decode Canon ImageUniqueID and added a new EasyMode value + - Created "unsafe" shortcut used when rebuilding JPEG EXIF metadata from + scratch + - Changed Olympus lens "pre-release" designation to "release 1" + - Changed exiftool to continue after encountering "Error opening directory" + - Enhanced makernote-offset-fix logic to account for problems like those + caused by bugs in Picasa and ACDSee + - API Changes: + - Enhanced GetTagID() to also return language code in list context + +Feb. 17, 2009 - Version 7.69 + + - Added a new Nikon LensID (thanks Jens Kriese) + - Added a new Pentax LensType (thanks Jens Duttke) + - Added Extra JPEGDigest tag + - Recognize new Panasonic APP2 MPF information written by FX40 + - Improved -@ option to allow a UTF-8 BOM at the start of the input file + - Augmented -listx output to include indexed value conversions + - Changed Japanese and Chinese language codes to 'ja' and 'zh' (ISO 639-1) + - Fixed a few problems with some CanonCustom tags + +Feb. 13, 2009 - Version 7.68 + + - Added French translations for XMP and Composite tags (thanks Jean Piquemal) + - Decode Panasonic AdvancedSceneMode, added a few more SceneMode values, and + fixed incorrect format for TextStamp + - Decode a missing Canon 1DmkII custom function + - Changed Czech language code to 'cs' (as per ISO 639-1) + - Relaxed XMP date/time validation to allow writing year-only and year-month + values (YYYY and YYYY:MM) without requiring the -n option + - More work on language translations (this will be ongoing) + - Fixed problem shifting XMP date/time values with missing seconds + - Fixed some family 1 group names in -listx output + +Feb. 9, 2009 - Version 7.67 (production release) + + - IMPORTANT: Fixed bug introduced in version 7.01 which could cause corruption + of TIFF-format images in very rare situations when adding tags to an image + containing very large (> 10 MB) binary data blocks + +Feb. 7, 2009 - Version 7.66 + + - Improved language support + - Changed conversion for a couple of the EXIF Flash values + - Removed trailing white space from Make and Model values + - Removed null terminators that may be left on some string values + - Fixed problem with family 1 group names for QuickTime Date tags + - Fixed problem with invalid names being generated for some unknown tags + - Fixed decoding of ASF PreviewMimeType and PreviewDescription + - Fixed formatting problems with -j output when combined with some options + +Feb. 5, 2009 - Version 7.65 + + - Added -j option for JSON (JavaScript Object Notation) output format + - Improved French language translation for File group (thanks Jean Piquemal) + - Enhanced -listx option to give short output when used after -s + - Renamed "tagid" attribute to "id" in -X output to match -listx output + - Fixed bug introduced in 7.64 which resulted in runtime warning when + extracting non-existent tags with the -f option + - Fixed problem which could cause runtime error with -listx option on some + systems + +Feb. 3, 2009 - Version 7.64 - "Babel fish" + + - Added -listx and -lang options + - Added preliminary support for the following languages (thanks Jens!): + - en [default] + - ch_s (thanks Haibing Zhong) [renamed 'zh_cn' in 7.75] + - cz (thanks Petr Michalek) [renamed 'cs' in 7.68] + - de (thanks Jens Duttke) + - en_ca (for those of us who like to see "colour" spelled properly) + - en_gb (correct "colour" plus a few other quirks) + - es (thanks Santiago del Brio Gonzalez) + - fr (thanks Bernard Guillotin) + - it (thanks Emilio Dati) + - jp (thanks Kazunari Nishina) [renamed 'ja' in 7.69] + - nl (thanks Peter Moonen and Herman Beld) + - pl (thanks Przemyslaw Sulek) + - Added support for new XMP Windows Live Photo Gallery tags + - Decode two new Panasonic tags and improved decoding of some others + - Decode a few new 3rd party EXIF and IPTC tags + - Enhanced -X output by adding -t feature for tag table information + - Improved decoding of Photoshop ClippingPathName and remove Unknown flag + - Renamed Panasonic EXIF "Title" tag to "PanasonicTitle" and improved decoding + - Fixed problem which could cause crash if reading corrupted images on Windows + - Fixed inconsistencies rewriting XMP which uses extra rdf:Description + elements instead of rdf:parseType='Resource' attribute + - Fixed decoding of Nikon D40 RemoteOnDuration + - API Changes: + - Added Lang option + +Jan. 23, 2009 - Version 7.63 + + - Added new Composite tags: SubSecCreateDate and SubSecModifyDate + - Decode Sony DSLR WB_RGBLevels tags (thanks Andrey Tverdokhleb) + - Decode a few more NikonScan tags (thanks Brendt Wohlberg) + - Included new argument files in distribution: xmp2exif.args and exif2xmp.args + - Improved decoding of PentaxModelID for K-m and K2000 + - Minor change to decoding of Canon 1DmkIII ISOSpeedRange + - Downgrade "MRW format error" to a warning when reading ARW images containing + MRW information that has been corrupted by the Sony IDC utility + - Renamed Kodak SubSecTime tag to Time + - Changed Composite DateTimeCreated tag to use only IPTC tags + - Changed name of Sony/Minolta MRW WBLevels tag to reflect ordering of color + components + - Fixed problems recognizing some MP3 files + +Jan. 16, 2009 - Version 7.62 + + - Decode a number of new tags for recent Canon EOS models + - Decode ID3v2.3 Compilation tag (written by iTunes) + - Added a number of new ID3 genre's and improved ID3v2 genre conversion + - Avoid converting MIE ISO 8859-1 string values + - Enhanced XML output (-X) to work with binary data (-b) option and encode + values in base64 if necessary + - Fixed problem with invalid UTF-8 when writing XMP or using -X (XML) option + +Jan. 10, 2009 - Version 7.61 + + - Added a new Pentax LensType and a new PentaxModelID (thanks Denis Bourez) + - Added ability to copy makernotes from Pentax or Samsung native DNG image + - Decode makernotes in Samsung GX model DNG images + - Decode CameraTemperature for Canon EOS cameras with Live View (thanks + Karl-Heinz Klotz) + - Decode a number of Canon 5DmkII CameraInfo tags + - Included 2 new argument files in distribution: xmp2gps.args and gps2xmp.args + - Prevent writing of TIFF images containing the obsolete (and unsupported) + TIFF 6.0 JPEG extensions + - Fixed bug which could result in runtime warning when writing makernotes as a + block + +Jan. 6, 2009 - Version 7.60 (production release) + + - Decode a few more Nikon D700 FlashInfo tags (thanks Jens Duttke) + - Defined (empty) XMP-pdfx tag table, mainly for documentation purposes + - Fixed problem where the behaviour of -tagsFromFile changed to that of + -addTagsFromFile if the first specified tag was an exclusion + - Fixed XMP writer to allow a namespace to be deleted after a mass copy + - Fixed bug introduced in 7.58 which could cause hang when using -tagsFromFile + +Dec. 23, 2008 - Version 7.59 + + - Removed file size limit when setting tag value from contents of a file + +Dec. 22, 2008 - Version 7.58 + + - Added new Canon, Nikon and Olympus lenses (thanks Jan Boelsma and Geert De + Soete) + - Added write support for FujiFilm S5000 Ver3.00 and S9500 Ver1.01 RAF images + - Extract RAFVersion tag from FujiFilm RAF images + - Decode ColorBalance information for PowerShot G10 + - Decode Sharpness for Canon EOS 50D + - More improvements to Canon 50D and 5DmkII makernote decoding + - Attempt to identify unknown Nikon lenses which exist in LensID list with a + different LensIDNumber (to patch Sigma lens renumbering debacle) + - Removed limit of 1000 items in an XMP list-type tag when writing + - Increased maximum size of file from 16MB to 100MB when setting tag value + from the contents of a file + - Improved performance when extracting a large number of same-named tags + - Fixed bug which resulted in "segment too large" error message when rewriting + multi-segment XMP if XMP was edited but nothing was actually changed + +Dec. 11, 2008 - Version 7.57 + + - Added read support for Panasonic RW2 raw images (and extract meta + information from embedded PreviewImage as Doc1) + - Added new Pentax K-m PictureModes and new PentaxModelID for the Optio S12 + - Decode ColorBalance information for Canon 50D and 5DmkII + - Decode Panasonic RAW/RW2 information from DNG images + - Decode Canon SRAWQuality tag + - Recognize DCP (DNG Camera Profile) files + - Updated Canon CustomFunctions for the EOS 5D Mark II + - Changed name of "OtherImage" tags to "JpgFromRaw" in IFD0 of SR2 and ARW + images, and to "ThumbnailImage" in IFD0 of MRW images + - Changed EXIF DeviceSettingDescription and ProfileLookTableData to binary + data tags + - Fixed problem reading/writing ThumbnailImage in Minolta A200 MRW images + - Fixed ColorBalance2 tags for AsShot and Auto modes of Canon 1DmkII/1DSmkII + +Dec. 2, 2008 - Version 7.56 + + - Decode CompressorVersion from Canon 5D Mark II videos + - Fixed family 1 group classifications for tags in QuickTime video tracks + - Fixed problem with new -sep feature when separator contained spaces + +Dec. 2, 2008 - Version 7.55 + + - Added a number of new CanonVRD tags for DPP 3.4/3.5 (thanks Bogdan) + - Added a new FocusMode for the Pentax K-m + - Added a new Nikon LensID (thanks Niels Kristian) + - Decode some tags from Kodak C1013 maker notes (type 9) + - Enhanced -sep option to allow list-type tag values to be split when writing + - API Changes: + - Added ListSplit option + +Nov. 26, 2008 - Version 7.54 + + - Added a few old XMP-crs tags that were missed + - Show numerator and denominator for rational EXIF values in verbose mode + - Changed htmldump tooltip font + - Fixed bugs in HTML reader that could cause runtime error or hang + +Nov. 19, 2008 - Version 7.53 + + - Added read/write support for EXIF files + - Added ability to write EXIF as a block (finally!) + - Added ability to write CanonVRD information to MIE files + - Added timezone to "Now" tag value + - Added a new CanonModelID (FS100) + - Added write support for ACDSee XMP tags (XMP-acdsee:RPP) + - Added a few new XMP-cc tags + - Decode CameraOrientation for a number of Canon EOS models (thanks Bogdan) + - Allow XMP to be copied as a block with -tagsFromFile option + - Highlight odd value offsets in -htmldump output + - Improved htmldump tooltip display + - Minor improvements to MIE reader + - API Changes: + - The full XMP block is now extracted with the Binary option, so the XMP + block is marked as "unsafe" and the Protected flag must be set (as with + other writable blocks) when calling SetNewValue() + +Nov. 4, 2008 - Version 7.52 + + - Added ability to extract AI private data from PDF files + - Added extract embedded option (-ee, -extractEmbedded) + - Added new group family 3 and ability to specify multiple group names for a + single tag when extracting information + - Added a new Sony lens and decode two new Sony tags (thanks Jens Duttke) + - Added a few new Nikon LensID's (thanks Robert Rottmerhusen) + - Added a new Olympus LensType (thanks Michael Meissner) + - Decode a few new Nikon tags (thanks Jens Duttke) + - Enhanced command line parsing to allow long names for most options + - Improved verbose output when writing makernotes + - Allow writing of empty string values in EXIF information + - Fixed problem rewriting XMP lists that contained no entries + - Fixed bug writing JpgFromRaw and ThumbnailImage to CRW files that could make + the image unreadable by Canon utilities (affected images may be repaired by + rewriting the same tag with this version of exiftool) + - Fixed bug where some Canon MakerNote values could not be written + - Fixed bug introduced in version 7.49 that broke the use of wildcards in + filenames for the Windows version + - API Changes: + - Enhanced a number of functions to accept multiple group names separated + by colons + +Oct. 27, 2008 - Version 7.51 (production release) + + - Fixed problems which caused failed test or warning with Perl 5.6 or older + (does do not affect Mac or Windows versions) + - Fixed Windows application so help is displayed when run with no options + +Oct. 26, 2008 - Version 7.50 (production release) "XMP 2008" + + - Added a number of new XMP tags from new XMP specification released Oct. 17 + - Added support for extended XMP segment in JPEG images (as per new XMP spec) + - Added a number of new Minolta/Sony lenses (thanks Jens Duttke) + - Added a new Canon LensType (thanks Andreas Huggel and Pascal de Bruijn) + - Added new PRISM 2.1 XMP tags + - Added ability to read/write x:xmptk attribute (via XMP-x:XMPToolkit tag) + - Added ability to specify user-defined Lenses + - Decode XMP in ASF (WMA/WMV), FLV, SWF and MP4 audio and video files + - Preserve byte order of EXIF information when copying to MIE file + - Allow byte order for newly created MIE files to be set by ExifByteOrder tag + (and API ByteOrder option) + - Allow backslashes in filenames on non-Windows-like systems + - Removed 's' from XMP-xmp:Thumbnails tag names and set Avoid flag for + XMP-xmp:ThumbnailImage + - Fixed definitions of some XMP-xmpDM tags + - Fixed some PDF reader bugs (thanks Leonhard Zachl for one patch) + - API Changes: + - Added ExtractEmbedded option + +Oct. 16, 2008 - Version 7.49 + + - Added new PentaxModelID for K-m/K2000 plus a new LensID used by K-m + - Added --a option and made -a the default behaviour for the -X option + - Added ability to read/write XMP-rdf:about attribute + - Added new "Resource" flag which may be set in user-defined XMP tags to write + a value as an rdf:resource instead of a normal string + - Allow decimal (real) values to be written to XMP-xmp:Rating (contrary to + current XMP specification, but as per MWG recommendation) + - Fixed file renaming bug in Windows that caused the file to be moved into the + current directory instead of leaving it in the original directory when the + source file was specified using backslashes as directory separators + +Oct. 14, 2008 - Version 7.48 + + - Added support for XMP PRISM 2.0 schema tags + - Added two more ZIP compression types + - Added conversions for XMP-plus date tags + - Changed conversion of all Digest tags to make the -n value readable + - Changed some error handling to avoid generating console warnings + +Oct. 11, 2008 - Version 7.47 - "Jumbo" + + - Added -X option to output extracted information in XML format + - Added -listwf option to list extensions of writable files + - Added a number of new Nikon and Pentax LensTypes (thanks Robert + Rottmerhusen, Jens Duttke and Bozi) + - Decode Canon 1000D custom functions + - Decode a number of new tags written by Nikon Capture NX 2 + - Decode many FlashInfo tags for the Nikon D90 and D700 + - Implemented character set translation for MIE information (-L option) + - Improved speed when scanning unknown file to determine FileType + - Fixed bug where some writable EXIF tags gave a "not writable" message when + reading tag value from a dynamic file (eg. "-TAG<=%f.txt") + - Fixed problem double-escaping characters when -h and -S were used together + - Fixed decoding of Nikon FlashModel for SU-800 Remote Commander + - Fixed swapped Nikon FlashGroupBControlMode/FlashGroupCControlMode tags + - Fixed bug reading PDF files that could cause "Argument isn't numeric in + subtraction" warning (note that writing PDF files with this problem could + cause format errors which may be fixed by reverting with "-pdf-update:all=") + - API Changes: + - Fixed CanWrite() to be consistent with documentation + +Oct. 2, 2008 - Version 7.46 + + - Fixed bug which could cause a runtime warning when writing images in a + directory containing an unrecognized file type + - Fixed an IPTC-XMP test that failed in other time zones (this was a test + problem, not an exiftool bug) + +Oct. 1, 2008 - Version 7.45 + + - Added support for new XMP IPTC Extension 1.0 tags (rev 2) + - Added a few more TIFF Compression values (for MDI files) + - Decode a few new Nikon Flash tags + - Decode Canon 50D custom functions + - Calculate CurrentIPTCDigest tag (if Digest::MD5 is available) + - Renamed Photoshop CaptionDigest tag back to IPTCDigest again + - Avoid touching IPTC data block when only Photoshop information is changed + - Allow IPTCDigest to be set to the special value of 'new', representing the + new IPTC digest of the output file + - Updated iptc2xmp.args and xmp2iptc.args to write IPTCDigest as per MWG + recommendation + - Allow zone-less date/time values in XMP (as per MWG and upcoming XMP spec) + - Allow brackets in $$ and $/ expressions (eg. ${$} and ${/} now work) + - Changed decoding of EXIF:Copyright to allow two separate strings as per spec + - Changed a number of XMP Iptc4xmpCore tag names and added a corresponding set + of aliases (shortcuts) for backward compatibility + - Changed some XMP xmpTPg tag names + - Fixed problem extracting lists from other information types in MIE files + +Sept. 26, 2008 - Version 7.44 + + - Added read support for DjVu images + - Added two new Sony LensType's (thanks Mladen Sever) + - Added a new Pentax LensType (thanks Jens Duttke) + - Decode a few new Canon 450D and 1000D tags (thanks Bogdan) + +Sept. 17, 2008 - Version 7.43 + + - Added two new Pentax LensTypes (thanks Jens Duttke and Anton Bondar) + - Added PentaxModelID's for the Optio E60 and M60 + - Added a number of new CanonModelID's + - Extract XMP from MOV and AVI videos (as written by Adobe CS3 Bridge) + - Decode information from QuickTime HintInfo atoms (hinf and hnti) + - Decode Canon 50D/5DmkII AutoLightingOptimizer + - Enable writing of ThumbnailImage in CR2 images + - Avoid extracting invalid Canon FocusDistance tags + - Improved handling of timezones in date/time values (fixes failed EXE test) + +Sept. 11, 2008 - Version 7.42 + + - Added read support for Windows, MacOS and Unix executable and library files + - Added read support for ZIP and RWZ (Rawzor) compressed files + - Added a number of new XMP tags written by PS Elements 4.0 (thanks Drew + Holland) and LightRoom 2.0 + - Added new Sony, Canon and Nikon LensTypes (thanks Jens Duttke and Werner + Kober) + - Decode a few new Canon CameraInfo tags for the 40D, 50D, 450D and 1000D + (thanks D.J. Cristi) + - Decode Nikon D90 LensData + - Define version number etc. in properties of exiftool Windows executable + - Improved handling of corrupted makernote offsets when writing + - Fixed problem where FileType could be incorrect for a TIFF-based file with + the wrong extension + +Aug. 28, 2008 - Version 7.41 + + - Added new Composite LensID tag and changed a number of LensType values in + an attempt to disambiguate Canon, Pentax, Minolta and Sony 3rd party lenses + - Added -sep option to specify separator for values in list-type tags + - Added a new Nikon LensID (thanks Jens Duttke) + - Added CanonModelID values for new models (SX110, A1000, A2000, E1, 50D) + - Decode some CameraInfo tags of the Canon EOS 450D and 1000D (thanks Bogdan) + - Decode a few new tags in Kodak MOV videos + - Updated CanonVRD decoding for version 3.40 (DPP 3.4.1, thanks Bogdan) + - Allow writable EXIF properties to be overridden by user-defined tags + - Relaxed PDF parsing to allow xref tables with zero entries + - Renamed Sigma LensID tag to LensType + - Changed PDF update structure to better conform with PDF specification + - Changed conversion of Olympus ManometerReading values + - Reverted back to Perl 5.8 for Windows EXE version (fixes problem running + exiftool.exe using a non-standard TEMP directory) + - Patched DST problem in Windows when "Automatically adjust clock for daylight + savings time" is used in Windows Date and Time settings + - Fixed problems in the QuickTime parser that could cause exiftool to hang + - Fixed bug which could cause an error to be reported when writing a DNG image + containing ProfileIFD information + - API Changes: + - Added ListSep option + +Aug. 17, 2008 - Version 7.40 + + - Fixed -p option in Windows executable version (caused by packaging problem + with Perl 5.10 release) + +July 30, 2008 - Version 7.39 + + - Added a number of new Canon LensType values (thanks Rich Taylor) + - Added a new Pentax LensType (thanks Jens Duttke) + - Added a new Sony LensType (thanks Mladen Sever) + - Added support for writing invalid IFD entries used by some Kodak Z cameras + - Updated Canon CustomFunctions for EOS 450D + - Made a few more DNG tags writable + - Renamed CIFF TvValue and AvValue tags to ShutterSpeedValue and ApertureValue + and added conversions (to seconds and F-number) as with EXIF tags + +July 18, 2008 - Version 7.38 + + - Same as version 7.37 except that Windows executable is packaged with Perl + 5.10.0 instead of 5.8.7 -- this fixes a problem with FileModifyDate and DST + +July 16, 2008 - Version 7.37 + + - Added -addTagsFromFile option (variant of -tagsFromFile which allows copying + multiple tags into the values of a single list-type tag) + - Added a new Sony LensID (thanks Jens Duttke) + - Added PentaxModelID for the Optio W60 + - Added a couple of new YCbCrSubSampling values (thanks Jens Duttke) and made + values consistent across different types of meta information + - Decoded Canon Categories tag (thanks Darryl Zurn) + - Reduced priority of XMP-xmp date/time tags so the EXIF tags are preferred + - Fixed problem where time may be duplicated in Composite:DateTimeCreated + - API Changes: + - Added ability to pass options to SetNewValuesFromFile + +July 8, 2008 - Version 7.36 + + - Added a new Nikon LensID (thanks Jens Duttke) + - Fixed bug introduced in 7.33 where a SubIFD error was erroneously reported + when writing an already edited NEF image + +July 6, 2008 - Version 7.35 + + - Added two new Nikon LensIDs (thanks Geert De Soete and Jens Duttke) + - Added XMP-pdf:Trapped tag + - Added Composite:GPSAltitude tag (like Composite:GPSLatitude/GPSLongitude) + - Added a couple of new PentaxModelID values + - Decode Canon 450D Sharpness tag (thanks Bogdan) + - Decode Nikon D300 AFAreaMode and AutoFocus tags (thanks Jens Duttke) + - Extract Pentax SaturationInfo as an Unknown tag (thanks Dave Nicholson) + - Renamed Canon LensType string tag (ID 0x0095) to LensModel + - Changed JFIFVersion print conversion to match the formatting used in the + JFIF specification + - Fixed a Minolta LensID entry for Tamron lenses + - Fixed problem excluding XMP family 1 groups from deletion in some file types + +June 28, 2008 - Version 7.34 + + - Added names for a few more of the Unknown Photoshop tags + - Added support for XMP files with leading XML comments + - Added support for older XMP "x:xapmeta", and XMP without "x:xmpmeta" element + - Changed priority of XMP:Source tags when writing so XMP-photoshop:Source is + now preferred over XMP-dc:Source + - Renamed Photoshop IPTCDigest to CaptionDigest and removed Unknown status + - Improved parsing of IPTC time values when writing, and assume the local + timezone (if available) instead of UTC when a timezone is not specified + - Improved handling of lists that exist in multiple groups in the same file + - Disabled shifting of list-type date/time tags (allows += to add list items) + - Reduced priority of XMP-exif and XMP-tiff tags so these values don't + override more reliable EXIF and TIFF tags when extracting information + without specifying a group + - Fixed quirk where exiftool could add an extra padding byte to the makernotes + - Fixed incorrect tag ID that prevented ImageStabilization from being decoded + in Sony DSLR-A100 images (thanks Ger Vermeulen) + - Fixed problem where error/warning messages could be duplicated for + subsequent files when copying tags from multiple files + +June 21, 2008 - Version 7.33 + + - WARNING: Older ExifTool versions will not properly rewrite DNG 1.2 images + which contain multiple color profiles + - Added DNGVersion check to avoid future problems with major DNG revisions + - Added support for new DNG version 1.2.0.0 tags + - Added support for XMP PLUS License Data Format 1.2.0 tags + - Added a new Pentax LensType (thanks Peter) + - Added a new Canon LensType + - Added support for user-defined XMP structures + - Decode a few new Sony tags (thanks Marcus Holland-Moritz) + - Decode Nikon Capture NX 2 NikonICCProfile information (thanks Jens Duttke) + - Extract MP3 VBR and ID3Size tags + - Improved accuracy of MP3 Duration calculation (account for VBR and ID3Size) + +June 12, 2008 - Version 7.32 + + - Added a new Pentax LensType (thanks yeryry) + - Decode ColorBalance information for Canon 450D and 1000D + - Fixed names of a few NikonCapture D-LightingHQ tags (thanks Jens Duttke) + - Fixed bug where a list-type tag was not created when simultaneously adding + and deleting values from the list + +June 10, 2008 - Version 7.31 + + - Added proper support for special characters in PDF text strings + - Added support for a number of new XMP tags written by Adobe Lightroom 1.4 + - Added ability to write XMP-xmp:ThumbnailsImage + - Added Photoshop IPTCDigest tag + - Added two new Nikon LensID's (thanks Jens Duttke) + - Added a new Pentax LensType (thanks Bogdan) + - Added a new CanonModelID for the EOS 1000D + - Decode a few new Pentax tags (thanks Dave Nicholson) + - Increased precision of GPS coordinates when copying with -tagsFromFile + - Fixed problem which could result in "Argument isn't numeric" runtime warning + when attempting to write an Unknown value to a bitmapped tag + +May 31, 2008 - Version 7.30 (production release) + + - Adjusted MakerNote error checks to be a compromise between 7.28 and 7.29 + - Fixed various htmlDump problems + - Fixed bug which could cause runtime warnings when attempting to write + certain types of unsupported images + +May 28, 2008 - Version 7.29 + + - Renamed Pentax ModelRevision tag to ProductionCode and improved print + conversion to indicate if camera has been serviced + - Added check to prevent EXIF tags from being written to JPEG images if they + would obviously exceed the maximum JPEG segment size limit + - Relaxed error checks when writing JPEG images to allow MakerNotes to be + rebuilt if the MakerNote IFD is not contained within the MakerNotes data + - Fixed decoding of Pentax ExternalFlashGuideNumber when AF360 is used with + the wide angle panel + - Fixed unnecessary "Multiple new values for IFD0 tag 0x927c" warning which + could occur when copying MakerNotes from some images + +May 26, 2008 - Version 7.28 + + - Added new Canon CustomFunctions values from the EOS 1DmkIII firmware update, + and a new CanonExposureMode value (thanks David Pitcher) + - Added a new Olympus LensType (thanks Viktor Lushnikov) + - Decode Pentax ExternalFlashBounce tag (thanks Cvetan Ivanov) + - Renamed Pentax ExternalFlashZoom tag to ExternalFlashGuideNumber and + improved decoding (thanks Cvetan Ivanov) + - Fixed bug which could prevent maker notes from being copied when copying all + tags from a file containing a PreviewImage + - Fixed problems decoding some Sony ARW images + - Fixed problem writing some makernote values in sub-IFD's + - Fixed "APP1 segment too large" problem where PreviewImage was not dropped + as it should have been when copying all tags from some RAW images + +May 24, 2008 - Version 7.27 - "GIF+XMP" + + - Added ability to read/write XMP in GIF images + - Added ability to write to GIF87a images (by upgrading them to GIF89a) + - Added GIFVersion tag + - Improved decoding of Canon 1DmkIII/1DSmkIII TimeStamp tags + - Changed print conversion of EXIF/XMP GPSStatus tags to make more sense + - Fixed bug introduced in version 7.22 that could cause exiftool to abort with + an "'x' outside string" error when processing some DNG images + - API Changes: + - Extract FileSize information from images passed as a scalar reference + +May 21, 2008 - Version 7.26 + + - Added write support for FujiFilm FinePix S5 Pro V1.04 RAF images + - Added support for new Kodak TIFF-format maker notes used by the Z1085 + - Added new Pentax and Nikon LensType's (thanks Jens Duttke, Dave Nicholson + and Robert Rottmerhusen) + - Added some new Minolta LensID's (thanks Thomas Kassner) + - Added new CanonModelID's and a 1DmkIII TimeStamp (thanks Ger Vermeulen) + - Decode a number of new Pentax K10D tags (thanks Dave Nicholson) + - Decode Panasonic Title tag (thanks Jens Duttke) + - Recognize a few more uncommon top-level QuickTime atoms + - Changed decoding of some Olympus tags for new E-520 + - Changed warning when empty PrintIM data is encountered (eg. as written in + Sony A700 ARW files when Adobe RGB color mode is used) + - Dropped Canon PreviewFocalPlaneX/YResolution tags since they never really + existed (thanks Ger Vermeulen for pointing out the Canon bug which lead to + this false assumption) + - Fixed duplicate tag problem with Pentax LensData when -U option used + - Fixed bug which could cause a runtime warning when copying Nikon maker notes + - Fixed bug in exiftool application which could cause all tags to be copied + instead of just the specified tags when creating an output XMP or MIE file + and using the -tagsFromFile option + +Apr. 18, 2008 - Version 7.25 (production release) + + - Added read support for DIVX video files + - Added a new Nikon LensID (thanks Tanel Kuusk) + - Decode a number of new Pentax K10D tags and values (thanks Dave Nicholson) + - Decode a few new Nikon tags (thanks Jens Duttke) + - Decode Nikon VignetteControl tag found in D3 images with new 1.10 firmware + (thanks Alexandre Naaman) + - Improved formatting of video duration times + - Improved print conversion for video Compression values + - Apply print conversion for XMP:FocalLengthIn35mmFormat to add "mm" + - Fixed MIME type of JPEG 2000 images + - Fixed problem decoding new Nikon D300 AFPrioritySelection tags + - API Changes: + - Fixed CanWrite so it returns false for non-writable TIFF-based files + +Apr. 10, 2008 - Version 7.24 + + - Added read support for SVG (Scalable Vector Graphics) images + - Added support for non-standard Apple iPhone PNG images + - Added support for ISL maker note format + - Added a couple of new Olympus LensType's + - Added a few new Nikon LensID's (thanks Robert Rottmerhusen) + - Added values for various Sony tags (thanks Jens Duttke) + - Decode Nikon D300 custom settings (thanks Jens Duttke) + - Decode Nikon D300 AFFineTuneAdj (thanks Neil Nappe) + - Decode a number of new Pentax tags and values (thanks Jens Duttke) + - Decode a number of new QuickTime tags, including 'mdta' information + - Decode a missing Custom Function for Canon 450D + - Avoid extracting any unknown tag in binary data tables when -u option used + - Avoid writing Canon 1D/1DS RAW images masquerading as TIF (writing 1D + RAW images is not yet supported) + - Improved parsing of AFCP ThumbnailImage and PreviewImage + - Downgraded errors in the NikonScan and NikonPreview IFD's to allow writing + of images with these problems without requiring the -m option + +Mar. 27, 2008 - Version 7.23 + + - Decode a number of new Pentax K20D/K200D tags and values + - Fixed bug introduced in 7.18 which caused "Error parsing XMP" warning when + deleting all XMP and writing new XMP tags in the same step + +Mar. 25, 2008 - Version 7.22 + + - Added support for Olympus-style Sony makernotes (DSC-S45/500/650/700/750) + - Added %c 'n' modifier to number output files from 1 instead of 0 + - Added Extra "Now" tag used for setting a tag value to the current date/time + - Added a new Nikon LensID (thanks Jens Duttke) + - Added ability to specify byte order for EXIF Unicode text and fixed problem + where text wasn't always written in EXIF byte order by default + - Added a new Canon LensType (thanks Hal Williamson) + - Added a few new CanonModelID values + - Decode a new Pentax K20D tag and add a few new values to other tags (thanks + Jens Duttke) + - Recognize non-standard Nikon ICC Profile files + - Improved error checking when writing a JPEG image with a bad IFD + - Fixed bug where IFD0 could be deleted when writing JPEG with a bad IFD1 + - Fixed some Olympus LensType names for Leica lenses + - Fixed problem extracting some writable directories as a block + - Fixed bug which could cause "Not an ARRAY" error when reading PDF files + +Mar. 12, 2008 - Version 7.21 (production release) + + - Added support for Leica M8 maker notes (in both DNG and JPEG images) + - Added ability to write encrypted Nikon makernote information (!!) + - Added a new Olympus Leica lens (thanks Chris Shaw) + - Decode a couple of new Canon 40D and 1DmkIII tags (thanks Chris Huebsch) + - Decode Adobe RAF data in DNG images + - Decode a few new Nikon D3 and D300 tags (thanks Jens Duttke) + - Calculate VideoFrameRate for QuickTime MOV videos + - Marked DNG OriginalRawFileName and OriginalRawFileData as "unsafe" to copy + - Changed decoding of Casio BestShotMode + - Renamed Nikon NEFCurve tags (thanks Jens Duttke) + - Patched problem parsing OriginalDecisionData for the Canon EOS 5D + +Mar. 7, 2008 - Version 7.20 + + - Added a few new Minolta LensID's + - Added two more TIFF-IT tags to the EXIF table + - Added a number of new RIFF and ASF Audio Encoding values + - Added a new new values for some Canon tags (thanks Dave Nicholson) + - Decode a number of new Pentax K10D tags (thanks Dave Nicholson) + - Decode a number of new MP4/QuickTime tags + - Decode makernotes in Casio, Kodak, Minolta, Olympus and Ricoh AVI and MOV + videos + - Improved decoding of Casio maker notes and decode a few new tags (thanks + Jens Duttke) + - Removed incorrect CanonD30 ColorTemperature and ColorMatrix tags + - Fixed Location translation in iptc2xmp.args and xmp2iptc.args + - Fixed problem decoding some Nikon tags in images edited by Capture NX + - Fixed decoding of InternalSerialNumber for Canon 5D + - Fixed decoding of Nikon D3 color balance information + - Fixed decoding of Minolta 7D FocusMode (thanks Jens Duttke) + +Feb. 25, 2008 - Version 7.19 + + - Added a new Pentax LensType and some new Panasonic NoiseReduction values + (thanks Jens Duttke) + - Decode Nikon D40 and D40X custom settings plus a couple of other tags + - Decode a couple of new Pentax K10D tags (thanks Dave Nicholson) + - Improved reliability of Canon FocalPlaneXSize and FocalPlaneYSize tags + - Recognize HP Type2 maker notes in images from other makes + - Write TIFF ApplicationNotes in 'int8u' format as per XMP specification + - Made TIFF ApplicationNotes writable as a block + - Changed HtmlDump to show actual IFD format if different than read format + - Changed some MeteringMode strings to be more consistent + - Fixed problem adding back JFIF information after deleting JFIF group + +Feb. 21, 2008 - Version 7.18 + + - Added ability to exclude XMP family 1 groups from deletion + - Added patch to recognize new Ricoh R50 maker notes + - Added a new Minolta LensID (thanks Jens Duttke) + - Decode AFPointsUsed for Nikon D3 and D300 (thanks Jens Duttke) + - Decode a couple of new Pentax K10D tags (thanks Dave Nicholson) + - Improved decoding of Nikon FlashInfo tags (thanks Jens Duttke) + - Renamed Olympus FlashExposureCompensation tag to FlashExposureComp + - Patched problem with Perl 5.10.x which broke conversion of UTF8 strings + - Fixed problem where an ExposureTime of 1 second was ignored in CRW images + - Fixed problem where special characters were not handled properly when using + the -L option while copying IPTC tags + - Fixed bug which could cause a runtime error when attempting to write JFIF + information after deleting JFIF:all in the same step + +Feb. 16, 2008 - Version 7.17 + + - Extract duplicate tags when -p option is used + - Fixed bug introduced in 7.00 which broke the use of group family numbers and + groups ending with a digit in tag format strings (eg. "$IFD0:Model") + +Feb. 14, 2008 - Version 7.16 + + - Added a couple of new Pentax LensTypes (thanks Jens Duttke) + - Added a few more EXIF:Compression values + - Decode color balance levels in Leaf MOS images + - Decode a number of new tags from JPEG, TIFF, KDC and DCR images of older + Kodak models + - Improved decoding of TIFF SampleFormat tag + - Made a number of DNG tags "unsafe" so they aren't copied by default + - Allow JPEG EXIF segment to be deleted and a new EXIF segment to be created + with a different byte order in a single command + - Attempted to improve reliability of ScaleFactor35efl calculation for newer + Canon models + - Fixed a couple more places where we still needed a space before "mm" + - Fixed problem with LightValue calculation which caused failed tests for Perl + 5.6.2 on Darwin + +Feb. 5, 2008 - Version 7.15 (production release) + + - Added a few new CanonModelID's and PentaxModelID's + - Added support for new Pentax K20D/K200D values for some tags + - Added a few new Nikon LensID's (thanks Robert Rottmerhusen) + - Decode a few new Sigma tags, including PreviewImage + - Decode a few more tags in Canon CRW images (thanks Dave Nicholson) + - Improved Sony ARW parsing (fix some problems and extract more tags) + - Improved handling of timezone when writing EXIF and XMP information (the + timezone is now added to XMP date/time values and removed from EXIF + date/time values if necessary unless the -n option is used) + - Recognize a few more FLV AudioEncoding and VideoEncoding values + - Allow "pseudo" tags to be copied from unrecognized file types + - Made FileModifyDate an "unsafe" tag so it isn't copied unless specified + - Changed all "sec" units to "s" with a leading space for consistency + - Fixed bug introduced in version 6.91 that could prevent some XMP date/time + tags from being written when copying with "-all:all" + +Jan. 25, 2008 - Version 7.14 + + - Added read support for Kodak KDC raw images + - Added ability to read/write Canon OriginalDecisionData in JPEG, CR2 and DNG + images + - Added ValueConv translations for some of the new Nikon PictureControl tags + - Decode a number of new Nikon tags (thanks Jens Duttke and Gregor Dorlars) + - Decode Canon CR2Segmentation tag + - Decode a new Canon CustomFunction of the EOS 450D + - Improved handling of mandatory tags in EXIF information + - Changed all FocalLength print conversions to add a space before "mm" + - Renamed Canon Self-timer tags to SelfTimer for consistency + - Fixed some problem with -htmlDump for some types of trailer information + - Fixed problem which could give a runtime warning when attempting to delete a + permanent tag + +Jan. 17, 2008 - Version 7.13 + + - Decode a couple more Nikon and Sony tags + - Decode Windows HD Photo "Padding" tag + - Recognize HDP (Windows HD Photo) file extension + - Designated EXIF CompressedBitsPerPixel and ComponentsConfiguration as + "unsafe" tags so they aren't copied by -tagsFromFile by default + - Changed priority of new Nikon D3/D300 ISO tag + - Changed Canon LensType for a Tamron lens (thanks Monica Wallek) + - Fixed incorrect TagID for new Panasonic Sharpness tag + +Jan. 15, 2008 - Version 7.12 + + - Added read support for ITC (iTunes Cover Flow) files + - Added ability to deal with corrupted IPTC written by Nikon Capture NX + - Added a few new Canon LensType's (thanks Steve Balcombe) + - Decode a number of new Nikon D3/D300 tags (thanks Gregor Dorlars) + - Decode a number of new FujiFilm and Panasonic tags and values + - Decode ColorBalance information for the Canon 40D, 1DmkIII and 1DSmkIII + - Improved decoding of Nikon D80 VibrationReduction tag (thanks Jens Duttke) + - Renamed Pentax WBShiftBA and WBShiftGM tags to WBShiftAB and WBShiftMG (now + more consistent with Pentax software, but inconsistent with Canon naming) + - Fixed a CanonImageHeight tag which was incorrectly named CanonImageWidth + +Jan. 10, 2008 - Version 7.11 + + - Decode a number of new Canon tags and improved decoding of many old tags + - Renamed EXIF:RelatedImageLength to RelatedImageHeight (hopefully all + ImageWidth/Height tag names are now consistent) + +Jan. 7, 2008 - Version 7.10 + + - Added support for escape sequences and continuation comments in EPS files + - Added ability to read/write Sony A700 PreviewImage (tag 0x2001) + - Added a new Sony ColorMode value (thanks Philippe Devaux) + - Decode a number of new Minolta tags + - Improved handling of newlines when writing PDF information + - Improved decoding of Canon 40D and 1DmkIII FocusDistance tags (thanks + Wolfgang Hoffmann) + - Fixed problem creating multiple output meta files with some commands + - Fixed problem deleting XMP by value for strings with escaped characters + - Fixed bug when trying to write output image to console with "-o -" + - Fixed problem where %c (copy number) was changed when the new file name + should have been the same as the source file + +Jan. 3, 2008 - Version 7.09 + + - Decode Canon ThumbnailImageValidArea + - Improved decoding of some Olympus tags (thanks Frank Ledwon) + - Improved decoding of some Pentax tags (thanks Dave Nicholson) + - Improved error messages when writing PDF files + - Changed XMP-cc namespace URI (spec apparently changed for some reason) + - Changed Photoshop XMLData to a binary data tag + - Changed conversion strings for Canon ModifiedSharpnessFrequency values + - Changed Olympus NoiseReduction "ISO Boost" value back to "Noise Filter (ISO + Boost)" + - Fixed minor problem writing PDF cross-reference stream after multiple edits + - Fixed problem redirecting some verbose output to an output text file + +Dec. 21, 2007 - Version 7.08 + + - Added write support for PDF files which use only cross-reference streams + - Added a number of new Olympus tags, and changed names of some existing tags + - Fixed problem decoding some PDF cross-reference streams + - Fixed bug introduced in 7.07 which broke copying between two list-type tags + +Dec. 18, 2007 - Version 7.07 + + - Added ability to write XMP and PDF information to PDF files, with revert + capability! (use "-PDF-update:all=" to undo all exiftool edits) + - Added PDF:AppleKeywords tag (written by Apple Preview) + - Added Composite FOV (Field Of View) tag + - Added a few more Minolta/Sony LensID's + - Added new Canon and Pentax LensType's (thanks Magne Nilsen and Jens Duttke) + - Added "Nothing changed" message in verbose mode for files that weren't + changed when writing + - Added minor warning when invalid IFD entries are removed during writing (you + will get this, for instance, when ExifTool fixes the entry count problem in + Canon EOS 40D firmware 1.0.4 maker notes) + - Patched Canon 40D firmware 1.0.4 problem for JPEG images too + - Decode specified "unknown" zero values for four EXIF tags (ExposureProgram, + LightSource, MeteringMode and SubjectDistanceRange) instead of handling as a + truly unknown value (if this makes sense) + - Extract PreviewImage from newer Panasonic RAW images (thanks Jens Duttke) + - Recognize Pentax-type Kodak maker notes (eg. Easyshare 883) + - Made "Entries out of sequence" a minor warning since this problem is fixed + - Allow decimal seconds to be written in time values without needing to use -n + - Improved parsing of PDF files + - Improved behaviour when copying list-type tags to to non-List tags + - Improved exiftool summary message for files that were copied without changes + - Adjusted Pentax K10D battery percentage calibration + - Changed names of Pentax FirmwareID tags + - Fixed runtime warning that could occur with some invalid tag names + - Fixed problem decoding Pentax:LensCodes for some images (thanks Jens Duttke) + - API Changes: + - Also allow File::RandomAccess reference as argument to ImageInfo() + +Dec. 7, 2007 - Version 7.06 + + - Permanently fix MakerNote offsets with -F option when writing + - A few more Pentax tag improvements (thanks Dave and Jens) + +Dec. 6, 2007 - Version 7.05 + + - Patched problem rewriting Canon 40D CR2 images caused by bug in the 40D + firmware 1.0.4 which writes an improperly formatted MakerNote IFD + - More improvements in decoding Pentax K10D tags (thanks Dave Nicholson) + - Translate non-standard XMP namespace prefixes + - Changed a couple of Kodak Meta tags to Binary data type + - Renamed Pentax MeasuredLV to EffectiveLV (thanks Jens Duttke) + +Dec. 3, 2007 - Version 7.04 + + - COMPATIBILITY WARNING: Renamed EXIF:ExifImageLength to ExifImageHeight and + XMP:GPSTimeStamp to GPSDateTime + - Added write support Minolta A200 MRW images + - Added read support for Hasselblad 3FR raw images + - Added a few new Nikon LensID's (thanks Robert Rottmerhusen) + - Added a new Canon LensType (thanks Bogdan) + - Added ability to insert a newline using "$/" in a print format string + - Decode some new FujiFilm and Pentax tags (thanks Jens Duttke) + - Decode some new Pentax and Canon tags (thanks Dave Nicholson) + - Recognize a few new Olympus lenses (thanks Michael Meissner) + - Improved decoding of Sony ARW images and added support for A700 + - Improved warnings for HtmlDump option + - Improved string parsing when writing date/time tags + - Fixed problem extracting Canon CRW RGGB values from DNG images + +Nov. 17, 2007 - Version 7.03 + + - Fixed problem deleting XMP family 1 groups from JPEG images + +Nov. 16, 2007 - Version 7.02 + + - Added ability to delete XMP family 1 groups (eg. "-XMP-crss:all=") + - Fixed problem writing XMP as a block to XMP file + +Nov. 15, 2007 - Version 7.01 + + - Added ability to write FujiFilm RAF images (thanks Jens Duttke) + - Added -T option (equivalent to -t -S -q -f) + - Decode a number of new Pentax tags and values (thanks Dave Nicholson) + - Decode a new Canon LensType value (thanks Bogdan) + - Decode the not-so-accurate FocusDistanceUpper and FocusDistanceLower in + Canon EOS 1DmkIII and 40D images (thanks Heiko Hinrichs) + - Allow FileSource tag to be assigned values outside the EXIF standard + - Made ImageSourceData a protected tag + - Avoid loading huge binary data blocks into memory unless necessary (avoids + out-of-memory problem when processing huge, layered Photoshop TIFF images) + - Improved HtmlDump speed and memory usage by not loading "snipped" data + - Improved decoding of Nikon ShootingMode + - Various improvements and bug fixes when reading FujiFilm RAF information + - Fixed problem decoding CRW images where ImageWidth wasn't extracted with -U + +Oct. 23, 2007 - Version 7.00 (production release) + + - IMPORTANT: Fixed problem writing ORF images from newer Olympus cameras which + could lead to errors when the image is opened by another utility (affected + images may be repaired by rewriting with this version of ExifTool) + - Added -ScanForXMP option + - Added ability to extract ID3v2 PRIV tags (including XMP) and the ID3:MCDI + tag (plus unknown ID3v2 tags with the -u option) + - Added new PentaxModelID's for Optio V10 and A40 + - Added support for Casio-like and HP-like Pentax maker notes + - Added ICC_Profile WCSProfiles tag (thanks Jens Duttke) + - Added ability to write and create CanonVRD as a block + - Added ability to shift GPSTimeStamp tag + - Added ability to write DNG AsShotICCProfile and CurrentICCProfile tags + - Decode VRDOffset tag in Canon MakerNotes + - Shortcuts may now be used in redirections and expressions, and with group + names + - Improved decoding of CanonVRD information (also decode new DPP 3.0 tags and + fixed a problem which could give a "Possibly corrupt CanonVRD" warning) + - Improved decoding of FujiFilm RAF images, and extract JPEG Preview + - Improved handling of Pentax Casio-style maker notes + - Improved conversion for Pentax K10D AFPointsInFocus + - Enhanced Composite tag syntax to simplify user-defined tag definitions + - Changed decoding of Nikon VibrationReduction 0x0075 tag + - Changed a number of Pentax and Casio tags to improve consistency + - Dump unsupported files with -htmlDump only if -u option is used + - Fixed problem which could cause a virtual hang when writing large EPS files + - Fixed problem of misleading error messages when attempting to write + unsupported file formats + - Fixed problem outputting list-type tags with -b option + - Fixed bug where the "image files created" count could miss some files + - Fixed problem where "Error rebuilding maker notes" warning could be issued + in cases where the maker notes do not need rebuilding + +Oct. 6, 2007 - Version 6.99 + + - Added support for IView MediaPro XMP tags + - Added ability to read multiple comments from GIF89a images + - Added some new PentaxModelID's (Optio L20, T20, Z10) + - Added minor warning for unknown JPEG APP segments when -u option is used + - Extract information from JPEG APP13 "Adobe_CM" segment + - Improved -htmlDump output to show TIFF image data and trailer (the TIFF dump + is now complete) + - Improved decoding of Minolta WhiteBalance for some DiMAGE models + - Improved decoding of Panasonic FirmwareVersion when -n option is used + - Increased precision of 64-bit rational conversion from 7 to 10 digits + - Fixed problem which caused failed tests with Perl 5.005_05 + - Fixed problem where some groups could not easily be excluded when deleting + all other information (eg. "-all= --exif:all" now behaves as expected) + - Fixed problem decoding ICC Profile "dtim" format values + - Fixed typo in a Minolta FlashMetering value (thanks Jens Duttke) + - Fixed problem in API which could result in a UTF-8 encoded file not being + properly identified if it was passed as a scalar reference to WriteInfo() + +Sept. 23, 2007 - Version 6.98 + + - Added ExifByteOrder tag (writable to set byte order for new Exif segments) + - Added CanonModelID for new EOS-1Ds Mark III + - Added value conversions for Pentax AEFlashTv, AEXv and AEBXv tags + - Decode Pentax ShutterCount (with help from Jens Duttke) + - Decode Pentax AFPointsInFocus for newer DSLR models (thanks Jens Duttke) + - Improved decoding of a Pentax LensType (thanks Jens Duttke) + - Renamed Pentax AutoAFPoint to AFPointsInFocus and improved conversion + - Renamed Pentax AEDump to AEMeteringSegments and converted values to + approximate LV equivalent units + - Fixed problem where some warnings were not being properly handled when + attempting to write an invalid value to some tags + +Sept. 14, 2007 - Version 6.97 + + - Added support for Canon EOS 40D Custom Functions + - Added ability to decode new Nikon D3 and D300 LensData + - Added a few new Nikon LensID's (thanks Robert Rottmerhusen) + - Decode Olympus NoiseFilter tag (thanks Ioannis Panagiotopoulos) + - Decode a few new Nikon ShotInfo tags (thanks Jens Duttke) + - Improved decoding of Canon AF point information + - Improved decoding of Nikon HighISONoiseReduction + - Renamed Nikon VRState to VibrationReduction + - Fixed typo which prevented some Olympus MakerNote tags from being written + +Sept. 5, 2007 - Version 6.96 + + - Added ability to read/write XMP alternate languages + - Added ability to create new GPS information in Panasonic RAW images + - Added a few new PentaxModelID's (Optio E40, M40 and S10) + - Added a couple of new Pentax LensType's (thanks Jens Duttke) + - Added a new Olympus Sigma LensType (thanks Jens Duttke) + - Added EOS 40D CanonModelID and prepared for new 40D custom functions + - Decode a large number of new Canon tags + - Decode SerialNumber from previously unknown maker notes of some Kodak models + - Decode Olympus ImageStabilization tag (thanks Jens Birch) + - Improved decoding of Canon Self-timer and AFPoint values + - Improved decoding of some tags for high end Canon EOS models + - Renamed Pentax LensCoefficients to LensCodes and print 16 values + - Renamed Panasonic ImageStabilizer to ImageStabilization + - Renamed all AFPointsUsed tags to AFPointsInFocus + - Fixed decoding of ICC_Profile DeviceAttributes + +Aug. 21, 2007 - Version 6.95 + + - Added support for new Kodak IFD-format makernotes used by the P712, P850, + P880, Z612 and Z712 + - Added a few new Nikon LensID's (thanks Robert Rottmerhusen) + - Added LensType's for 2 new Pentax DA* lenses (thanks Jens Duttke) + - Added 2 new FujiFilm S5 WhiteBalance values (thanks Paul Samuelson) + - Added a number of new CanonModelID's + - Extract TIFFPreview from DOS EPS images + - Decode a number of new Panasonic tags, and added a number of new SceneMode's + - Decode FujiFilm S5 ColorTemperature tag (thanks Paul Samuelson) + - Improved handling of unknown XMP list-type tags + - Suppress EPS 'unterminated Document data' warning + - Fixed decoding of ASCII-type Panasonic FirmwareVersion + - Fixed bug calculating leap years for years outside the range 1601-2399 + - API Changes: + - Changed WriteInfo() behaviour to be more consistent when editing file in + place and a new FileName is specified (original file is now deleted) + - Improved warning message when trying to write an 'unsafe' tag + +July 26, 2007 - Version 6.94 + + - Added a few new XMP-crs tags + - Added ability to create a new Photoshop IRB record in TIFF-format images + - Added a few new EXIF:Compression values (thanks Jens Duttke) + - Added a number of new Panasonic/Leica tags, and changed the names of some + Panasonic tags, including reverting FirmwareVersion (thanks Jens Duttke) + - Added test for Unknown (Bulb) Pentax ExposureTime value (thanks Jens Duttke) + - Added a new Nikon LensID (thanks Vladimir Sauta) + - Avoid extracting information from documents embedded in EPS images + (this is temporary; eventually I want to figure out a way to allow this + information to be extracted separately) + - Decode Red/BlueBalance from Leica Digilux 2 RAW images (thanks Jens Duttke) + - Changed conversion for Sony A100 Rotation tag to conform to EXIF:Rotation + - Changed decoding of one of the Pentax ExternalFlashBounce tags (thanks + Michael Meissner) + - Extract EncodingProcess, BitsPerSample, ColorComponents and YCbCrSubSampling + from JPEG SOF segment + - Show raw horizontal/vertical widths in the converted YCbCrSubSampling value + - Improved conversion of some Pentax tags (thanks Jens Duttke) + - Avoid loading data blocks larger than 16MB from QuickTime images + - Allow PDF:Keywords to be comma-delimited + - Fixed problem where a tag would be removed from both IFD0 and ExifIFD even + if only IFD0 or ExifIFD was specified + - Fixed problem with byte order mark showing up in output when decoding + hex-encoded Unicode values from PDF images + - Fixed problem where ExifTool could hang when reading corrupted ASF files + - Fixed possible problem with infinite recursion in FlashPix-format files + +July 6, 2007 - Version 6.93 + + - Added read support for BigTIFF images (with extensions BTF, TIF and TIFF) + - Added a number of new Olympus tags and fixed decoding of a few others + (thanks Jens Duttke) + - Added a number of new SigmaRaw tags (found in SD14 X3F images) + - Changed conversion for Canon LensType 152 (used by various Sigma models) + - Fixed problem editing XMP containing new "Camera Raw Saved Settings" + properties (written by Adobe Lightroom) + +June 29, 2007 - Version 6.92 + + - Added read support for FLV (Flash Video) files + - Added read support for EXIF and IPTC and write support for EXIF, IPTC and + XMP in JPEG 2000 images + - Added read/write support for Sinar CS1 raw images + - Added read support for Kodak DCR and K25 raw images + - Added ability to read/write improperly byte-swapped IPTC information + - Added check for infinity value of Casio ObjectDistance + - Added a new Nikon LensID (thanks Bruce Stevens) + - Improved decoding of APP12 "Ducky" segment (thanks Heinrich Giesen) and + added write/create support + - Improved handling of warning messages when setting new values + - Changed print conversion for Olympus PictureModeSaturation, + PictureModeContrast and PictureModeSharpness to label min and max values + - Fixed problem introduced in 6.91 when writing some EPS images + - Fixed group names for Pentax CameraInfo tags + - Fixed bug which could result in negative Canon SerialNumber values + - Fixed decoding of some Canon EOS 1DmkIII custom function values + - Fixed problem copying subdirectories in new-style Olympus maker notes + - Fixed problem of missing last character when decoding ID3 Unicode strings + - Fixed problems decoding some ID3 URL values + - Fixed inconsistency where the -if option may have used a different tag than + the one normally extracted when a group name was specified and multiple + matching tags existed in the group + +June 5, 2007 - Version 6.91 + + - Added support for new XMP-lr, XMP-photoshop and XMP-DICOM tags of PS CS3 + - Added new Sigma lens to Pentax LensID list + - Added a few new Nikon and Canon LensID's (thanks Jens Duttke) + - Added Canon IXY Digital 810 IS to CanonModelID + - Recognize Photoshop "AgHg" resource type + - Removed "warnings" dependency in exiftool application + - Updated XMP:FileSource values to match EXIF:FileSource + - Greatly improved processing speed for some large EPS images + - Improved handling of XMP date/time formatting + - Officially support writing of MEF images + - Applied patch to convert Pentax LensType for changes in K10D firmware 1.2 + - Fixed decoding of Pentax BatteryBodyGripStates (thanks Jens Duttke) + +May 10, 2007 - Version 6.90 (production release) + + - Added CanonModelID values for new PowerShot S5 IS and SD850 IS + - Encode IPTC values in default CodedCharacterSet when writing new values at + the same time as deleting the existing CodedCharacterSet + - Renamed Nikon FirmwareVersion to MakerNoteVersion and Panasonic + FirmwareVersion to ProductionVersion (thanks Jens Duttke) + - Allow EXIF GPS coordinates to be negative when writing (take absolute value) + - Revert "$evalWarning" fix (false alarm) + +May 7, 2007 - Version 6.89 + + - Added support for maker notes of some Hewlett-Packard models + - Decode Pentax ImageProcessing tag + - Fixed problem which gave "$evalWarning" errors on some systems + +May 2, 2007 - Version 6.88 + + - Added read support for Mamiya MEF images + - Implement long overdue change to standardize FocalPlaneResolutionUnit values + - Decode Panasonic BabyAge + some new ShootingMode values (thanks Jens Duttke) + - Improved recognition of maker notes for some camera models + - Fixed bug that could cause an incorrect "tag is not writable" warning + - Fixed problems converting WDP PixelFormat values + - Fixed decoding of Canon 350D AFPointsUsed (thanks Bogdan) + - API Changes: + - Added option to allow makernote block to be extracted without rebuilding + +Apr. 26, 2007 - Version 6.87 + + - Added read/write/delete support for recognized trailers in PSD images + - Added PhotoMechanic IPTC:Prefs tag + - Added ability to decode double-UTF-encoded XMP files + - Added a few more Canon, Pentax and Nikon lens types (thanks Hayo Baan and + Robert Rottmerhusen for Nikon entries) + - Added ability to create new user-defined MIE groups + - Decode a new Nikon lens tag: ExitPupilPosition (thanks Robert Rottmerhusen) + - Increased precision (from 20m to 2mm) when writing XMP GPS coordinates + - Renamed Panasonic SpotMode tag to AFMode and improved decoding + - The -e (Composite) option now also applies when copying tags + - Minor changes to IPTC verbose output and error handling + - Minor changes to a few warning messages + - Avoid converting XMP values as rational or date if tag is known and not + specified with these formats + - Identify CR2 header and Canon MakerNote footer in -htmlDump output + - Reverted change from version 6.85 to once again allow JPEG thumbnails to be + written to TIFF-type images (perfectly valid for many TIFF-based RAW formats + even though it isn't technically correct in a proper TIFF) + - Added test to check for invalid encoding when Image::ExifTool is loaded + - Fixed problem shifting Canon:TimeStamp tag + - Fixed failed FlashPix test on Cygwin Perl 5.8.2 (roundoff errors again) + - Fixed problem where some types of write errors could result in exiftool + reporting that a file was updated when it wasn't + +Apr. 10, 2007 - Version 6.86 + + - Added -execute, -srcfile and -common_args options to allow complex + processing with multiple commands in a single invocation + - Added ability to write Panasonic RAW files + - Added Panasonic ConversionLens tag + - Improved decoding of Panasonic/Leica Contrast and SpotMode tags + - Changed -@ to insert arguments at the current position in the command line + (rather than at the end) + - Once again automatically fix Canon maker note offsets (this feature was + removed in 6.84 due to a bug bug report that turned out to be a false alarm) + - Fixed bug in -if option which could incorrectly cause a failed condition + when using expressions containing multiple tags with proper-case names + - Fixed problem extracting binary data when -if option was used + - Fixed bug which caused error when setting CodedCharacterSet to "UTF8" + - Fixed decoding of InternalSerialNumber for FujiFilm FinePix F40fd + - Fixed problem using "-TAG+<=FMT" or "-TAG-<=FMT" on command line + +Apr. 3, 2007 - Version 6.85 + + - Prevent JPEG thumbnail image from being written to TIFF-type images + - Fixed a couple of problems decoding Canon EOS 1D Mark III tags + - Fixed bug which generated an error message when rewriting maker notes in + Adobe-edited Pentax K10D native DNG images + +Mar. 30, 2007 - Version 6.84 + + - Added a number of new XMP-crs tags, plus new XMP-lr (Adobe Lightroom) group + - No longer automatically fix Canon makernote offsets (but still use makernote + footer if present to calculate recommended fix) + - Fixed problem where some errors were not properly counted in the summary + statistics with the -overwrite_original_in_place option + - Fixed problem parsing XMP shorthand format for values containing '=' symbol + +Mar. 24, 2007 - Version 6.83 + + - Automatically fix corrupted makernote offsets when reading images from Canon + models which include a makernote offset footer + - Added CanonModelID and CameraType values for 2 new Canon DV cameras + - Renamed SPIFF ResolutionUnits tag to ResolutionUnit + - Fixed formatting of GPSTimeStamp value + +Mar. 20, 2007 - Version 6.82 + + - Added read/write support for new Canon EOS-1D Mark III custom functions + - Made a few makernotes warnings minor when writing + - Append "mm" to FocalLengthIn25mmFormat value + - Fixed problem which could cause "uninitialized value" warning when writing + - Fixed problem writing Canon EOS D60 custom functions + +Mar. 17, 2007 - Version 6.81 + + - Added l/u modifiers for lower/uppercase in filename format codes (eg. "%le") + - Added equivalent IXY names to CanonModelID for PowerShot SD750 and SD1000 + - Added a few new Pentax ModelID's (Optio E30, T30, W30, A30) + - Allow non-encrypted Nikon ColorBalance values to be written + - Fixed problem where some encrypted Nikon information was not properly + protected against writing + +Mar. 14, 2007 - Version 6.80 + + - Added Olympus ManometerReading tag + - Added ability to edit private IPTC and XMP information found inside + PhotoshopSettings record of TIFF images + - Renamed NikonShotInfoVers tag to ShotInfoVersion and added + MultiExposureVersion tag + - Search further in MPEG file to look for first audio/video frame headers + - Use default resolution information from JPEG JFIF segment for mandatory EXIF + resolution tags when creating new EXIF segment + - Enhanced %c format code so %+c adds an underline before the copy number + +Mar. 7, 2007 - Version 6.79 + + - Translate special characters in ID3 information when reading + - Improved conversions for GPSTimeStamp and GPSDateStamp when writing so they + can be set from a normal date/time tag (eg. "-gpstimestampDSTTAG") + - Added a few more Canon EasyMode values (thanks Samson Tai) + - Added CanonModelID values for new A450, A460 and A550 + - Changed the -if option so the condition automatically fails if the + expression generates a warning (use -v to show the warning) + - Specified LF character (0x0a) for MIE text newline sequence + - Catch warnings if perldoc doesn't exist when running with no arguments + - Minor tweaks/fixes to htmldump output + +Jan. 19, 2007 - Version 6.70 - "IPTC Character Coding" + + - Translate coded characters in IPTC string values (UTF8 and Latin only), and + assume Latin encoding if no CodedCharacterSet (see FAQ #10 for details) + - Enhanced IPTC:CodedCharacterSet print conversion so "ESC % G" is now printed + as "UTF8" (either may be used when writing) + - Specified ISO 8859-1 character set for MIE ASCII string values + - Added warnings for UTF-8 conversion errors + - Decode a few new Pentax tags + - Decode maker notes in Pentax DNG images + +Jan. 8, 2007 - Version 6.69 + + - Decode information in NikonScanIFD + - Enhanced -p option to allow expressions to be used + - The -p option no longer suppresses error and warning messages + - Made ImageSourceData writable + - Reduced font size of htmldump output + - Fixed "Argument isn't numeric" error when reading an image with a missing + IFD offset + +Jan. 3, 2007 - Version 6.68 + + - Added mechanism to allow Composite tags to be writable + - Recognize XMP sidecar files that begin with a UTF BOM (byte order mark) + - Changed TIFF ImageSourceData tag to a Binary data type + - Fixed problem which could cause warning when writing XMP in PNG images + - Fixed bug when shifting times in an XMP sidecar file that caused an invalid + date/time to be written if the tag didn't previously exist + - Fixed problem where writing to a JPEG image containing a PreviewImage could + report that the file was updated even if nothing was changed + +Dec. 30, 2006 - Version 6.67 - "Adobe DNGPrivateData" + + - Added ability to write MakerNote information written by Adobe DNG Converter + - Added ability to copy Adobe MakerNote and CRW information from DNG images + - Added ability to read/write Adobe CRW and MRW information in DNG images + - Added ability to read Adobe SR2 information in DNG images + - Added a few more Nikon LensID's (thanks Robert Rottmerhusen) + - Added ability to delete a specific MIE document in multi-document files + - Improved handling of tags in multi-document MIE files + - Improved verbose and htmlDump output for unknown JPEG trailers + - Improved handling of ignored minor errors when writing MakerNotes + - Decode Panasonic LensType tag + - Changed description for Canon:OwnerName tag + - Minor changes to HtmlDump output + - Fixed parsing of XMP date/time values with no seconds + +Dec. 20, 2006 - Version 6.66 (production release) + + - Added a few more Pentax K10D PictureMode's (thanks Axel Kellner) + - Added a few new Nikon LensID's and Olympus LensType's + - Added Canon 1D PictureStyle's + - Updated CanonModelID strings for a few new models + - Changed tagID for MIE:GPSDifferential + - Minor change to MIE specification for unknown data formats (MIE 1.1) + +Dec. 15, 2006 - Version 6.65 - "MIE 1.0" + + - Added ability to read/write MIE trailers in JPEG and TIFF images + - Added a number of new MIE tags and changed some existing tags + - Added support for units in MIE values + - Added new Pentax K10D PictureMode's (thanks Axel Kellner) + - Avoid creating non-native groups in MIE, PNG and EPS images unless necessary + - Fixed problem with -P option so it now works when -o option is used + - Fixed bug where 'all' was replaced with '*' in redirection expressions + - Fixed "APP1 segment too large" error when copying all tags from some Canon + CR2 images to a JPEG (fixed initially in 6.08, but broken again in 6.47) + +Dec. 8, 2006 - Version 6.64 + + - Added Nikon ImageAuthentication tag (thanks Jeffrey Friedl) + - Added Canon RecordMode and OpticalZoomCode and Composite DigitalZoom tag + - Applied FocalUnits scaling to Canon ShortFocal, LongFocal and + ScaledFocalLength tags, and renamed ScaledFocalLength to FocalLength + - Allow (but ignore) leading family number on tag group when writing + - Fixed calculation of 35mm scaling factor when Canon digital zoom is applied + - Fixed bug which could cause "'x' outside of string" error when reading Nikon + images with the -U option + +Dec. 6, 2006 - Version 6.63 + + - Changed the sense of the '-' modifier for the new '%c' format code + +Dec. 6, 2006 - Version 6.62 + + - Added '%c' format code to add copy number if output file exists + - Added a couple of new Nikon LensID's (Werner Kober, Robert Rottmerhusen) + - Made -htmlDump tag names purple if actual offset differs from stored offset + +Dec. 4, 2006 - Version 6.61 + + - MakerNotes offsets are now permanently fixed when the makernotes are copied + using -tagsFromFile with the -F option + - Fixed typo in MakerNoteSanyoC4 tag name of MakerNotes shortcut + - Minor improvements to htmldump style + +Dec. 2, 2006 - Version 6.60 + + - Added -k option of stand-alone version to regular distribution + - Fixed bug adding/deleting XMP tags in a list (introduced in 6.50) + - Fixed decoding of Canon 5D LongExposureNoiseReduction + - Fixed problem writing AFCP where incorrect offset could be written + - Fixed bug in -p option which caused it to abort if all tag names were + contained in braces (thanks Joel Becker) + - Stand-alone Windows executable: + - Print application documentation after "No file specified" warning + +Nov. 30, 2006 - Version 6.59 + + - Do not delete IFD1 when deleting all meta information from a TIFF image + - Added a couple of new CanonImageSize values: "Postcard" and "Widescreen" + - Added a few new Olympus LensType's (thanks Lilo Huang for one) + - Improved handling of invalid date values + - Fixed "divide by zero" warning if FocalPlaneXYResolution is "inf" + - Fixed incorrect "unknown trailer" verbose message when writing JPEG images + - Stand-alone Windows executable: + - Allow quoting of options embedded in executable name + +Nov. 25, 2006 - Version 6.58 + + - Added a few more Nikon LensID's (thanks Robert Rottmerhusen) + - Added missing print conversion for RIFF DateTimeOriginal + - Improved HTML 4.01 compliance of -htmlDump output + - Lowered priority of ID3v1 tags so ID3v2 takes precedence if both exist + - Minor change to names of some Vorbis and APE tags + - Made Ogg file type all capitals + - Patched problem which could cause ExifTool to die if input file is corrupt + - Fixed GPSDOP description (GPS Dilution of Precision, thanks Greg Troxel) + - Fixed problem which could generate a run-time error when attempting to write + to a corrupted JPEG image + - API Changes: + - GetFileType() may now also be used to return a file description + +Nov. 19, 2006 - Version 6.57 (production release) + + - Missing tags in -p and redirection expressions are now set to an empty + string ('') by default, or a dash ('-') if the -f option is used + - Added ability to use %f,%d,%e tokens in "-TAG<=FILE" argument + - Added new Nikon LensID (thanks Werner Kober) + - Set missing tags to '' instead of '-' in redirected expressions if -m used + - Renamed LV tag to LightValue + - Improved decoding of Sony DSLR-A100 maker notes + - Attempted to clarify date/time shift documentation in Shift.pl + - Fixed bug which could result in CanonVRD information not being recognized + - Fixed bug in new SetResourceName feature of user-defined Photoshop tags + - First release of stand-alone Windows executable + - API Changes: + - Added MissingTagValue option + +Nov. 15, 2006 - Version 6.56 - "Audio Update" + + - Added read support for a number of audio file formats: Ogg Vorbis, + Ogg FLAC, FLAC, APE (Monkey's Audio) and MPC (Musepack) + - Improved parsing of ID3 v2.3 and v2.4 information + - Added a number of new Pentax *istD tags (thanks Douglas O'Brien) + - Added ability to print processed file names when writing (-v0 option) + - Patched problem with makernotes offsets in Sanyo C4 images + - Fixed problem that prevented some Olympus RAW files from being written + - Fixed bug where XMP values could be improperly converted as a rational + +Nov. 8, 2006 - Version 6.55 + + - Added read/write support for Canon VRD (Recipe Data) files and trailers + - Changed name of CanonDPP module and group to CanonVRD + +Nov. 3, 2006 - Version 6.54 + + - Added write support for ORF (Olympus RAW) images + - Added Panasonic TravelDay tag (thanks Marcel Coenen) + - Show Photoshop resource block names in verbose output, and preserve these + names when copying tags from file + - Changed write format of Nikon WhiteBalanceFineTune from int16u to int16s + (thanks Giridhar Appaji Nag) + - Allow Flags to be used in UserDefined tags + - Added trailer signature to MIE format specification + - Fixed problem with the -list and -listw options (dynamically loaded tags + weren't appearing in the list) + +Nov. 1, 2006 - Version 6.53 + + - IMPORTANT: Fixed bug introduced in 6.51 which could result in a corrupted + image (!!) when rewriting TIFF-format files containing an unknown trailer + (this includes all TIFF-based RAW formats except CR2). The good news is + that unknown trailers should be very uncommon, and nobody has reported any + problems yet, so with any luck I caught this before it affected anyone. But + please update immediately to 6.53 if you downloaded 6.51 or 6.52. + +Nov. 1, 2006 - Version 6.52 + + - Added read/write support for trailers in CRW images + - Dropped historic support for obsolete -group# option + +Oct. 31, 2006 - Version 6.51 - "Trailer Update" + + - Improved handling of trailers in JPEG and TIFF-format images: + - Added read/write support for PhotoMechanic and FotoStation trailers + - Recognize and handle Canon DPP trailers + - Added AFCP trailer read/write support for TIFF (previously JPEG only) + - Added ability to read/write multiple trailers in the same image + - Trailers are now dumped with verbose and htmlDump options + - Trailers are now deleted when deleting all tags + - Added ability to delete trailers individually by group or altogether + with "-Trailer:all=" + - Changed reading/writing XMP in PNG images to conform with XMP specification + (but continue to support the XMP profile format used previously) + - Avoid writing duplicate XMP tags in less common namespaces + - More consistent handling of unknown IPTC tags + - Added -listd option to list deletable groups + - IPTC time-only tags may now be set from date/time values (this already + worked for date-only tags) + - Fixed problem rewriting international text (iTXt) chunks in PNG images + - API Changes: + - Added GetDeleteGroups() routine + +Oct. 26, 2006 - Version 6.50 + + - Changed name of new "-eval" option to "-if" + - Added read support for PhotoStudio Unicode comment (thanks Dec Anisimov) + - Recognize the "PHUT" Photoshop IRB resource type (thanks Dec Anisimov) + - Extract PhotoshopBGRThumbnail image from Photoshop information + - Write PNG compressed text for new tags when -z option is used + - Added ability to write PNG:ModifyDate + - Don't print Olympus LensType "release" if used to differentiate lenses + - Changed TagName documentation to show actual format written instead of + format used to interpret the data (which differs only for a few odd tags) + - Fixed bug in PNG writer which could cause duplicate tags to be written + - Fixed minor problem in HtmlDump output + - Fixed logic bug when writing XMP using += or -= + +Oct. 21, 2006 - Version 6.49 + + - Added -eval option for conditional batch processing [changed to -if in 6.50] + - Allow .ExifTool_config file to be placed in application directory + - Decode copyright information from JPEG APP12 "Ducky" segment + - Decode Casio FirmwareDate + - Added IFD0 ProcessingSoftware tag (0x000b, written by ACD Systems) + - Added print conversion for InteropIndex + - Write InteropVersion automatically when creating a new InteropIFD + - Made RelatedImageFileFormat writable + - Protect all InteropIFD tags from being copied by default with -TagsFromFile + - Renamed XMP ExifImageHeight to ExifImageLength (to correspond with EXIF tag) + +Oct. 19, 2006 - Version 6.48 + + - Decode Minolta 7D FlashExposureComp (thanks Jeffery Small) + - Decode InternalSerialNumber from newer FujiFilm models + - Improved decoding of new Pentax PictureMode tag (thanks Doug O'Brien) + - Updated CustomFunctions in Canon CRW images and recognize CIFF extension + - Added a couple new Pentax LensType's (thanks Barney Garrett) + - Changed "AdobeRGB" to "Adobe RGB" in all ColorSpace values for consistency + - Fixed bug in recent update to extract large preview from Epson JPEG images + - Fixed problem in -htmldump output introduced in 6.46 + - Various documentation improvements and updates + +Oct. 15, 2006 - Version 6.47 + + - Decode JPEG APP6 "EPPIM" segment used in Toshiba images + - Process PICT images to extract JPEG preview when -u option is used + - Added OtherImage composite tag + - Added PentaxModelID for K110D and a new K110D PictureMode tag + - Fixed problem extracting CoverArt from some MP4 audio files + - Fixed problem decoding Canon BulbDuration (affects Composite ShutterSpeed) + - Fixed problem reading/writing large Epson preview image in R-D1 JPEG images + and allow large (>64kB) preview images for all make/models + +Oct. 11, 2006 - Version 6.46 + + - The "-ext" option now overrides internal file selection rules + - Expand filename wildcards on Windows command line (thanks Marjolein Katsma) + - Enhanced warnings when copying information to a specific tag + - Changed family 0 group name: GPS->EXIF + - Changed family 1 group names: APP12->PictureInfo,GraphicConverter->GraphConv + - Added a couple of new Pentax LensType's + - Added JPEG.pm module (mainly for documentation purposes) + - Fixed bug when re-writing NEF files which caused new preview image written + by Nikon Capture 4.4.0 to be lost + - Fixed bug which could cause problems if a user-defined composite tag is + created with the same name as an existing tag + +Oct. 6, 2006 - Version 6.45 + + - Added ability to create JFIF segment + - Decode information in JPEG APP8 "SPIFF", APP12 "Ducky", and APP15 + GraphicConverter segments + - Improved html dump feature to dump all JPEG APP segments + - Decode maker notes in FujiFilm AVI videos + - Renamed Nikon AFMode tag to AFAreaMode (thanks Tobias Briseno) + - Changed "Image Quality" description to "Quality" + - Added option to allow the htmlDump base offset to be specified + - Changed EV tag name to LV since this is technically more correct + - Print warnings if syntax problems are found in .ExifTool_config file + - Use HOMEDRIVE and HOMEPATH (Windows cmd shell environment variables) for + .ExifTool_config path if neither EXIFTOOL_HOME nor HOME are available + - Fixed some problems which were causing failed tests when using ActivePerl + - User-defined Composite tags now override composite tags of the same name + - Added a few more PentaxModelID's (K10D, A20, M20, W20) + +Oct. 2, 2006 - Version 6.44 + + - Now deletes all JPEG APP segments when deleting all information + - Decode Ricoh APP5 RMETA information (custom fields in Caplio Pro G3 images) + - Decode AVI Audio/Video stream headers + - Recognize and preserve PhotoMechanic trailer when editing TIFF-based images + - Added ability to delete JFIF, CIFF, Meta and FlashPix groups + - Added ability to exclude groups when deleting all information + - Added a number of new Canon, Nikon, Pentax, Sony and Minolta tags + - Added description for GPSDOP tag (GPS Degree Of Precision) + +Sept. 26, 2006 - Version 6.43 + + - Added read support for M4A audio files + - Simplified and documented technique for adding user-defined Composite tags + - Issue minor warning when a tag used in an expression doesn't exist, instead + of silently inserting a '-' (use -m option for previous behaviour) + +Sept. 21, 2006 - Version 6.42 (production release) + + - Re-worked Sony and Minolta LensID lists and added a number of new lenses + - Extract maker note information from Sanyo MOV and MP4 videos + - Recognize ARW extension of Sony Alpha-100 RAW images + - Improved extraction of PreviewImage from damaged Minolta images + +Sept. 18, 2006 - Version 6.41 + + - Fixed calculation of Canon ISO in some images and renamed ShotISO to BaseISO + - Minor improvement to order of operations when deleting multiple groups and + adding back information in batch mode + +Sept. 14, 2006 - Version 6.40 + + - Added ability to delete a group and write back information in one step + - Compatibility Warning: This changes previous behaviour when adding and + deleting information in the same operation if new tag values are set + after a group has been flagged for deletion + - Fixed problem writing to specific MIE groups + - Minor improvements to verbose output while writing + - Added a few new CanonModelID's (PowerShot G7, SD900, SD800IS, SD40) + +Sept. 12, 2006 - Version 6.37 + + - Decode Sony LensID's (thanks Thomas Bodenmann) + - Added another Canon LensType + - Added shortcut MakerNotes tag to represent the maker notes tags from all + manufacturers (useful when copying tags between files) + - Improved MPEG decoding and calculate approx. Duration based on avg. bitrate + - Issue a minor error when rewriting an empty IFD (previously this was fatal) + - Print 2 decimal points of MeasuredEV (avoids round-off errors resulting in + failed tests on some systems) + +Sept. 6, 2006 - Version 6.36 (production release) + + - Added a few more Canon LensType's + - Improved decoding of Canon 400D ExposureTime and FileNumber + - Decode AFPointsUsed for PowerShot models with 9 AF points + - Fixed decoding of Canon 5D PictureStyle + +Sept. 5, 2006 - Version 6.35 + + - Added Canon NumAFPoints tag + - Added support for Canon 400D custom functions + - Renamed Canon AFPointsUsed20D to AFPointsUsed and decode for 30D and 400D + - Changed phrasing in a text string to bypass bug in rpm build causing it to + obtain incorrect dependencies + +Sept. 3, 2006 - Version 6.34 + + - Removed empirical offset from Canon:MeasuredEV + +Sept. 1, 2006 - Version 6.33 + + - Added Composite:EV and Canon:MeasuredEV tags [comments welcome] + +Sept. 1, 2006 - Version 6.32 + + - Decode a new value of "Auto High" for Canon CameraISO + - Added new Canon AutoISO tag, renamed Canon:ISO tag to ShotISO, and added a + new composite ISO tag to give the ISO that was actually used + - Decode CanonModelID's for recently announced Canon cameras (400D, etc) + - Decode PentaxModelID for Optio S7 + - XMP Changes: + - Added support for rdf:nodeID attribute in XMP information + - Changed XMP file MIME type from application/xmp to application/rdf+xml + to correspond with XMP specification + - Write 'rdf:about' instead of 'about' (unqualified use now deprecated) + - Don't write blank-line padding (as per XMP spec) for .XMP files + - Fixed problem extracting XMP information from some EPS files + - Fixed typos in some (not commonly used) XMP namespace URI's + - Fixed FocalLength conversion for some Pentax-built BenQ and Samsung models + +Aug. 23, 2006 - Version 6.31 + + - Decode a number of new values for FujiFilm PictureMode (thanks Michael + Meissner) + - Properly parse AVI DateTimeOriginal tag when month name is all capitals + - Improved compatibility when running "exiftool" with no arguments (thanks + Jesse Zhang) + - Added support for Nikon D80 lens information and recognize a new lens + (thanks Robert Rottmerhusen) + - Improvements to Pentax maker note decoding (thanks Ger Vermeulen) + - Fixed problem when extracting information from image in memory when the + UTF-8 flag is set for the image data (fixes install on RHEL 3) + +July 28, 2006 - Version 6.30 + + - Added ability to read/write APP0 CIFF segment (found in Canon PowerShot A5 + and PowerShot Pro 70 images) + - Improved decoding of Canon 30D FileNumber (was ShutterCount) + - Made EXIF tags ImageNumber and ImageHistory writable + - Fixed decoding of TargetExposureTime for Canon 20D/250D and ExposureTime + for Kiss Digital N + - Fixed problem processing GIF images which don't contain a color table + - Fixed bug in EXIF tag name documentation introduced in 6.12 where ExifIFD + group was not properly shown + - Fixed typo in exiftool pod documentation ("GROUP:TAG" was reversed) + +July 24, 2006 - Version 6.29 (production release) + + - Added XMP-xmpMM:PreservedFileName tag (used by Photoshop CS) + - Fixed problem reading TIFF images which don't start at the beginning of the + file + +July 12, 2006 - Version 6.28 + + - Fixed bug introduced in 6.04 which prevented PNG tags from being deleted + - Improved decoding of Canon PictureStyle information + +July 7, 2006 - Version 6.27 + + - Decode a number of new tags in Canon, Casio, FujiFilm, Minolta, Nikon, + Panasonic, Pentax, Ricoh and Sony and maker notes + - Improved recognition of various Minolta maker note formats + - Added a number of new Nikon Capture tags + - Added support for XML-formatted XMP files + - Properly handle mixed linefeed characters in PostScript images + - Improved formatting of DICOM date/time values + - Added "Actual Offset" entry to HtmlDump tooltip information + +June 27, 2006 - Version 6.26 + + - Avoid creating new SubIFD when copying all tags with "-all:all" from a RAW + or TIFF image (this gave problems if image was subsequently edited by PSCS2) + - Fixed decoding of a few Nikon LensID strings + - Minor fixes and changes to htmlDump and verbose output + - Added a new Pentax LensType (thanks Kazumichi Kawabata) + +June 19, 2006 - Version 6.25 + + - Added read/write support for WDP (Windows Media Photo) images + - Improved algorithm to recognize maker notes offsets which need fixing + - Properly handle maker notes which have value offsets relative to the + individual IFD entries (Kyocera, Rollei and some Konica and Toshiba models) + - Decode a couple of new Sigma lens values in Canon LensType + - Decreased block size for buffered files to improve performance over slow + pipes + +June 9, 2006 - Version 6.24 + + - Added -fast option to avoid scanning to the end of JPEG images to check for + an AFCP or PreviewImage trailer + - Recognize PS files which start with %!Adobe-PS instead of %!PS + - Improved FlashPix verbose output + - API Changes: + - Added FastScan option + +June 7, 2006 - Version 6.23 + + - Added new feature allowing tag-name expressions to be used with the + -TagsFromFile option + +June 5, 2006 - Version 6.22 + + - Added read support for FPX (FlashPix) images and FPXR (FlashPix Ready) + JPEG APP2 meta information + - Added AllDates shortcut tag to allow DateTimeOriginal, CreateDate and + ModifyDate to all be written via a single tag + - Added shortcuts to tag name documentation + - Return "0000:00:00 00:00:00" instead of "1970:01:01 00:00:00" as the string + representation of numerical times with a value of zero + +May 26, 2006 - Version 6.21 + + - Changed CR2 identification logic to properly identify CR2 images which have + been edited by PhotoMechanic + +May 24, 2006 - Version 6.20 + + - Added read support for Real audio/video (RA, RM, RV, RMVB, RAM, RPM) files + - Downgraded "Error reading value..." message from an error to a warning + - Fixed bug where IgnoreMinorErrors option could get set when writing images + with NikonCapture information + - Fixed two ID3 tag names which contained spaces + - Fixed problem parsing DateTimeOriginal in Casio EX-Z30 AVI files (thanks + Joachim Loehr) + - Fixed problem with apostrophes in HTML documentation for some browsers + - API Changes: + - Can now call Options() with undefined value to set option value to undef + +May 16, 2006 - Version 6.19 + + - Added read support for SWF (Shockwave Flash) files + +May 15, 2006 - Version 6.18 + + - Added read support for MPEG audio/video files + - Decode audio information in MP3 files + - Print Nikon:LensPosition in hex + +May 12, 2006 - Version 6.17 (production release) + + - Fixed problem with rpmbuild on Mandriva 2006.0 (thanks Niels Kristian) + - Fixed typo in iptc2xmp.args and xmp2iptc.args which prevented the XMP + Instructions from being copied properly (thanks Mark Tate) + - Handle byte order mark in unicode EXIF strings + +May 8, 2006 - Version 6.16 + + - Write %ADO_ContainsXMP comment when adding XMP to EPS images + - Don't issue DSC warning when writing Adobe version 3.1 EPS images + - Added separate table for decoding tags in IFD0 of Panasonic RAW images + - Improvements to Nikon AF point decoding (thanks Roger Larsson) + - Allow .ExifTool_config directory to be specified by setting the + EXIFTOOL_HOME environment variable + - Made all maker note write errors minor so they can be ignored if necessary, + allowing information to be written to images with corrupted maker notes + - Minor change to perl-Image-ExifTool.spec to fix problem with rpmbuild + (thanks Volker Kuhlmann) + - Fixed bug which could cause incorrect date to be calculated when shifting + date/time values + +Apr. 20, 2006 - Version 6.15 + + - Changes to MIE specification involving string lists and alternate languages + +Apr. 18, 2006 - Version 6.14 + + - Fixed some problems with EPS writer and removed beta testing status (thanks + to Tim Kordick for help with testing) + - Created new MIE meta information format [Note: The MIE module is fully + functional but the MIE format specification is still in development] + - Added print conversion for SpatialFrequencyResponse + - Extended meaning of -z option when writing to allow compressed information + to be written to MIE files + - Added Minolta FlashMetering tag + - API Changes: + - Added 'Compress' option + +Apr. 9, 2006 - Version 6.13 + + - Fixed problem with writing FileName that caused format codes not to be + properly expanded if the specified filename already existed + - Standardized reported FileType for ACR, AIFC, CRW, JP2, PS and PSD files + - Allow 2 values to be written for EXIF TimeZoneOffset and make EXIF + SecurityClassification writable + +Apr. 5, 2006 - Version 6.12 + + - Avoid printing garbage for DNG maker note information that was not copied + properly by the Adobe DNG converter (affects converted ORF images) + - Disabled "Possibly incorrect maker notes offsets" warning for a number of + Olympus models + - Fixed bug introduced in 6.04 which could cause endless loop (eeek!) when + writing tags with PostScript equivalents + - Fixed error reading some DICOM images + +Apr. 3, 2006 - Version 6.11 + + - Added a few new Pentax LensType's + - Fixed bug rewriting MOS images (this bug introduced in version 5.95 caused + an error message and prevented the file from being rewritten) + +Mar. 31, 2006 - Version 6.10 + + - Added ability to use filename format codes %d, %f and %e in values written + to FileName and Directory tags + - Fixed problem of odd filenames being generated when setting FileName from an + invalid date/time tag + - Removed debugging print statement forgotten in Olympus code of 6.07 (oops) + - API Changes: + - Added StrictDate option + +Mar. 30, 2006 - Version 6.09 + + - Made FileName and Directory writable (enabling a whole new functionality!) + - Added ability to write DOS-style EPS images [Note: still in beta testing] + - Increased precision of Composite Red/BlueBalance print conversion + - When combining the -o and -overwrite_original options, the original file is + now erased if the new file is written successfully + - Added a new Nikon lens (thanks Werner Kober) + - API Changes: + - Added SetFileName() routine + - In list context, CountNewValues() now also returns a "pseudo" tag count + +Mar. 25, 2006 - Version 6.08 + + - Made YCbCrCoefficients and YCbCrPositioning protected when writing + - Decode some new Nikon-specific tags in QuickTime videos from Nikon cameras + - Calculate Red/BlueBalance for Olympus images + - Fixed "APP1 segment too large" problem when copying all tags from Canon + EOS-5D or EOS-30D CR2 image to JPEG image + - Fixed problem running "exiftool" with no arguments in Windows cmd shell + +Mar. 22, 2006 - Version 6.07 + + - Added a number of new Olympus tags (thanks Frank Ledwon) + - Decode Adobe JPEG APP14 segment (thanks Didier Giet) + - Made Rotation writable in CRW images + - Changed some FujiFilm WhiteBalance strings + - No longer return multiple tags when a group is specified unless the + duplicates option is enabled or the group name is 'all' or '*' + +Mar. 20, 2006 - Version 6.06 + + - Added validity check for Canon FocalPlaneX/YSize which resulted in incorrect + values of FocalLength35efl being calculated for some PowerShot models + - Made Opto-ElectricConvFactor value binary + +Mar. 18, 2006 - Version 6.05 + + - Improved JPEG writer to tolerate any segment ordering + - Fixed Olympus ExtenderStatus to work with E-330 (thanks Mark Dapoz) + +Mar. 15, 2006 - Version 6.04 + + - Added write support for EPS and PS images [Note: still in beta testing -- + must currently use the -m option to enable writing to EPS images] + - Added ability to write ICC_Profile data as a block + - Added read/write support for ICC and ICM color profile files + - Added read/write support for ERF (Epson Raw Format) images + - Added a couple of new Olympus tags and LensType's (thanks Mark Dapoz) + - Added ability to scan past unknown header to find JPEG or TIFF image + - Added Canon EOS 30D custom functions + - Renamed Panasonic SerialNumber tag to InternalSerialNumber + - Renamed Canon 5D PictureNumber tag to ImageNumber + - Improved MRW reading and writing + - Decode a number of new Minolta tags and changed names of some existing tags + - Decode some type-specific data in ASF StreamProperties, including video + ImageWidth and ImageHeight + - Extract a few more PostScript tags and derive ImageWidth and ImageHeight for + PostScript documents + - Some improvements to Panasonic decoding (thanks Tels) + - API Changes: + - 'Unsafe' tags are now copied by SetNewValuesFromFile() if specified + explicitly + - Internal Changes: + - SubDirectory tags are no longer Writable by default in WRITABLE tables + +Mar. 2, 2006 - Version 6.03 + + - Added print conversion for CFAPlaneColor + - Decode CFAPattern as written incorrectly in ASCII by some Panasonic cameras + - Added recently announced Canon cameras to CanonModelID list + - API Changes: + - Added ability to prefix tag name with group in arguments to ImageInfo() + (read/write symmetry is now improved since this feature already existed + in the write routines, and now group names can be used in shortcuts) + - Changed order of filtering for Group# option and tag exclusions to be + applied after extracting tags specified in calls to ImageInfo() + +Feb. 26, 2006 - Version 6.02 + + - Fixed problem rewriting Photoshop IRB resources as written by some + applications (eg. PixVue) + - Improved decoding of AVI files to increase speed and extract more tags + - Added -overwrite_original_in_place option + - Added a number of new XMP tags and bring XMP support up to new + specification, plus a few undocumented XMP-aux tags (thanks Lou Salkind) + - Added support for large DNG preview image (with JpgFromRaw tag) + - Added ability to decode DNG Adobe MakerNotes + - Added SEMInfo tag (thanks Robert Mucke) + - Decode (but don't rewrite) old PS APP13 "Adobe_Photoshop2.5:" segment + +Feb. 20, 2006 - Version 6.01 + + - Added back RedBalance and BlueBalance as composite tags + - Fixed potential problem in File::RandomAccess which could cause a "substr + outside of string" warning + +Feb. 19, 2006 - Version 6.00 (production release) + + - Added read support for Sony SR2 raw images (but most tags still unknown) + - Added read support for Kyocera Contax N Digital RAW images + - Added ability to write or delete shortcuts which reference multiple tags + (previously only shortcuts referencing a single tag were writable) + - Changed descriptions of FNumber, ExposureTime, ISO, DateTimeOriginal, + CreateDate and ModifyDate to more closely match their tag names + - Separated Canon and Nikon Red/BlueBalance information into individual + components with tag names like WB_RGGBLevels + - Decoded a number of new Canon tags for EOS models, including ColorBalance + tables, 20D AF points and SensorInfo (thanks Rainer Honle) + - Fixed incorrect decoding of EOS 10D/300D color balance modes + - More additions and minor fixes to Canon decoding + - Made EOS-1D personal functions writable + - Added ability to write bitmasks at the PrintConv level + - Set MIME type for all RAW image formats to "image/x-raw" + - The -f option is no longer implied when -S and -s are combined + - Fixed bug introduced in 5.99 which broke the "-tagsFromFile @" feature + - Fixed problem with offsets in verbose dump of CRW images + - Fixed problem with some tags in Canon images not printing without -a option + - Fixed problem with validation of Canon PictureInfo for images rotated by + Canon ZoomBrowser EX (thanks Joshua Bixby) + +Feb. 1, 2006 - Version 5.99 + + - Major additions to Canon maker note decoding, including EOS-1D personal + functions (thanks Rainer Honle for decoding many 5D tags) + - Added Canon maker note footer when rewriting Canon maker notes + - Attempt to fix problem where ScaleFactor35efl was calculated incorrectly for + some Canon images + - Reduce memory usage and speed up writing of large TIFF images + - Fixed problem with binary data offsets in verbose dump + - Fixed problem writing Comment if 'File' group specified + - Fixed bug which could cause formatting error in htmlDump output + +Jan. 22, 2006 - Version 5.98 + + - Enhanced FMT syntax for -o, -w and -tagsFromFile options + - Decode maker notes of Samsung DX-1S + - Added ability to list tags in a specific group + - Recognize maker notes of a few more Kodak models + - Added a few more Canon LensType's + - Added missing semicolons in HtmlDump JavaScript output + +Jan. 16, 2006 - Version 5.97 + + - Added support for Canon 5D custom functions (thanks Rainer Honle) + - Added support for Canon 1DmkII and 350D custom functions + - General fixes and improvements to Canon custom functions + - Renamed ICC_Profile Copyright to ProfileCopyright + - Report all extraction errors when copying only specified tags from file + - Avoid issuing "Error rebuilding maker notes" warning when copying maker + notes that don't require rebuilding + +Jan. 14, 2006 - Version 5.96 + + - Fixed problem where XMP information could be lost when writing PSD images + +Jan. 12, 2006 - Version 5.95 + + - Decode AIFF SampleRate + - Fixed problem where FileType was being set twice for AIFF files + - Patched problem reading some file types through Windows cmd shell pipeline + - Properly identify CR2 images read via pipes (previously identified as TIFF) + - Improved formatting of printed values for some DNG tags + - Fixed problem with EXIF format of some tags when writing + - Changed 'rational' format names to match full bit size of value + +Jan. 10, 2006 - Version 5.94 + + - Fixed problem extracting OriginalRawImage from little-endian DNG images + - Fixed problem where "unreferenced bytes" error could be incorrectly issued + when deleting all EXIF from a TIFF image + +Jan. 9, 2006 - Version 5.93 + + - Added ability to write JFIF information + +Jan. 9, 2006 - Version 5.92 + + - Added ability to extract and decompress original raw image from DNG + - Fixed problem extracting information from some image types in pipelines + - Decode more information in PSD images + +Jan. 7, 2006 - Version 5.91 + + - Added write support for PSD images + - Made a couple more Photoshop tags writable + +Jan. 6, 2006 - Version 5.90 + + - Added read support for AIFF audio files + - Made Photoshop:XResolution and Photoshop:YResolution writable + - Fixed problem with processing some RIFF files + - Added a new Canon LensType + - API Changes: + - SetNewValue() now accepts an ARRAY reference for setting list-type tags + such as Keywords, or a SCALAR reference for binary data, so it may now + be called directly with any value returned by GetValue(). + +Jan. 3, 2006 - Version 5.89 + + - Recognize Panasonic Type 2 maker notes + - Changed Nikon LensID to a composite tag to allow better decoding of + non-Nikon lenses, and added a bunch of new lenses to the list + +Jan. 1, 2006 - Version 5.88 + + - Added ability to read and write AFCP information in JPEG images + - Added read support for WMV video and WMA audio files (ASF format files) + - Added EXIF tags 0x82a5-0x82ac + - Fixed TagID of IntergraphPacketData tag + - Fixed problem in rewriting some types of JVC maker notes + - Renamed WAV module to RIFF + +Dec. 22, 2005 - Version 5.87 (production release) + + - Added support for JVC maker notes + - Extract a number of new DNG tags plus DNG JPEG preview image + - Renamed DNGCameraSerialNumber tag to CameraSerialNumber + +Dec. 20, 2005 - Version 5.86 + + - Added support for AVI and MP4 videos + - Improved decoding of Olympus maker notes + - Improved APP12 decoding + - Improved CanonPictureInfo validation to work with more PowerShot models + - Display Canon 1D serial numbers with 6 digits + - Decode maker notes of Nikon D1 + - Combining -t with -S now gives a single-line tab-delimited list of values + - Extract preview image for Samsung Digimax i5 + +Dec. 13, 2005 - Version 5.85 + + - Added ability to read and write XMP files which don't have an xpacket header + - Fixed problem deleting entire XMP data block using '-xmp=' syntax + - More minor HtmlDump improvements + +Dec. 12, 2005 - Version 5.84 + + - Minor improvements to HtmlDump output + +Dec. 12, 2005 - Version 5.83 + + - Added -F option to allow maker notes offsets to be fixed + - Added -htmlDump option to generate a verbose HTML-based hex dump of EXIF + and/or TIFF information (cool new diagnostic tool) + - Attempt to validate maker notes offsets and issue warning if they look wrong + - Fixed problem rewriting PreviewImage in some Olympus and Pentax images + - Increased speed for extracting large preview images + - Improved synthetic maker notes when coping tags from CRW file + - Display absolute offsets for EXIF values in very very verbose mode + - Verbose option output is now written to file if -w option used + - Speed up rewriting of some TIFF images when using ActivePerl 5.8.x for + Windows (image strips are now copied in a single block if they are + contiguous in the file to avoid ActivePerl bug which causes extremely poor + performance when concatenating a large number of memory blocks) + - Added a couple of new Nikon and Pentax lens ID's (thanks Robert Rottmerhusen + and David Buret) + - Decode PrintIM information in Casio QV-4000 + - Fixed Decoding of Canon EOS D60 serial numbers to agree with Canon utilities + - API Changes: + - Added HtmlDump and TextOut options + +Nov. 26, 2005 - Version 5.82 + + - Fixed bug which caused error rewriting Minolta MRW images + - Added MRW write test + - Improved MRW verbose output + +Nov. 24, 2005 - Version 5.81 + + - Changed writing of TIFF so that existing IPTC will be rewritten as int32u + whenever IPTC is edited, regardless of original format type. This allows + files to be 'fixed' even if IPTC was previously another format (now we get + to see if there is any software out there that barfs on int32u's...) + - Changed the -s option so tag names are displayed instead of descriptions + (now similar to the -S option, but values are aligned in a column) + - Remove padding at the end of IPTC record when writing + - Fixed problem which was generating a warning with ActivePerl 5.6.1 + +Nov. 22, 2005 - Version 5.80 + + - Changed writing of new TIFF IPTC information to make it visible in Nikon + Capture (for some reason requires int32u format) + - Installed patch for building of ExifTool RPMS on Mandriva Linux (thanks + Niels Kristian) + +Nov. 22, 2005 - Version 5.79 + + - Fixed problem which could render XMP information unreadable by Photoshop + when editing some XMP written by Photoshop CS2 + +Nov. 21, 2005 - Version 5.78 + + - Fixed problem which could generate an error when adding IFD1 to an image + +Nov. 18, 2005 - Version 5.77 (production release) + + - Allow integer tag values to be specified in hex (with leading '0x') + - Fixed problem which generated warnings about symbol "@indent" in Nikon.pm + when using older versions of Perl (observed with 5.6.1) + +Nov. 16, 2005 - Version 5.76 + + - Tolerate extra null padding at end of TIFF images (as written by Photoshop + CS) when rewriting TIFF images + - Minor improvements to DICOM image processing + - Updated FAQ + +Nov. 14, 2005 - Version 5.75 + + - Fixed problem decompressing deflated DICOM images + +Nov. 14, 2005 - Version 5.74 + + - Added read support for DICOM (DCM, DC3, DIC, DICM) and ACR-NEMA (ACR) + medical image files + - Decode a lot more Nikon Capture information and add write ability + - Updated Nikon makernote decoding for D200 and new AF-S Nikkor 18-200 lens + (thanks Werner Kober) + - Added a number of new Canon LensType's (thanks Volker Gering) + - Recognize file types even if they have the wrong extension + +Nov. 7, 2005 - Version 5.73 + + - Added ability to shift date/time tag values + - Extract Red/BlueBalance tags for Nikon D2Hs, D50 and D2X + - Decode Nikon Capture Data to extract IPTC information and Rotation + - Added a new Olympus LensType (thanks Michael Meissner) + +Oct. 28, 2005 - Version 5.72 + + - Added ability to create XMP data files. This is more significant than it + sounds: The -o option may now be used to generate XMP files from + information in any other format, or even to create an XMP file from nothing + more than tags defined on the command line. + - Added printout of number of directories created with -w and -o options + - Improved error handling + - Effectively set preferred group to 'XMP' when writing XMP data files + - Fixed problem rewriting maker notes of some Pentax cameras + - API Changes: + - Added CanWrite() and CanCreate() functions + - Allow WriteInfo() source file to be undefined to create new file + - Allow WriteInfo() output file to be undefined to edit file in place + - Added extra argument to WriteInfo() to specify output file type + +Oct. 24, 2005 - Version 5.71 + + - Added ability to read/write .XMP data files + - Added -listf option to print list of recognized file types + - Changed "-group#" option to "-listg#" (but still support old -group#) + - Moved Kodak APP3 "Meta" tags from EXIF to a new Kodak "Meta" group + +Oct. 23, 2005 - Version 5.70 + + - Significant internal changes to improve speed and reduce memory usage + - Fixed a bug introduced in version 5.63 which caused incorrect XMP GPS + coordinates to be returned + - Changed handling of Kodak date records + - API Changes: + - Added ability to access original 'Raw' values via GetValue() + - GetValue() now returns empty array in list context if value is undefined + - Values are now converted as they are requested, so the PrintConv option + now applies to GetInfo() and GetValue() instead of ExtractInfo() + +Oct. 19, 2005 - Version 5.69 + + - Changed UTF-8 bug fix introduced in 5.67 to improve portability and allow it + to work with Perl versions back to 5.6 + - Changed some offsets in verbose output from relative to absolute addressing + - Improved APP12 decoding + - Changed technique for rounding off extracted rational values + - API Changes: + - Changed handling of floating point numbers to tolerate locales where a + comma is used instead of a decimal point + +Oct. 17, 2005 - Version 5.68 + + - Added support for reading Sigma RAW (X3F) images + +Oct. 13, 2005 - Version 5.67 (production release) + + - Added support for reading PICT images + - Fixed a problem when writing information via the ExifTool API if using Perl + 5.8 or later and passing a UTF-8 encoded string to SetNewValue(). The + problem generated an error which prevented the file from being written + - Fixed timezone problem in timestamps of QuickTime images which was causing + a failed test + +Oct. 10, 2005 - Version 5.66 + + - Enhanced -tagsFromFile option to allow %d, %f and %e in filenames + - Extract a few more tags from Canon EOS 5D images + - Allow multiple ICC_Profiles to be extracted from same image and add a number + to the group1 name for subsequent profiles to make the tag locations unique + - Changed Photoshop PixelsPerInchX/Y and QuickTime DotsPerInchX/Y tag names to + X/YResolution. Neither has a corresponding ResolutionUnit tag, so inches + should be assumed if no resolution unit is present + - Added tests of Nikon, Sony and PDF decryption algorithms + +Oct. 7, 2005 - Version 5.65 + + - Added read support for QuickTime MOV videos (and QTIF images if anyone + cares) + - Extract maker note information from Sony SRF raw images + - Improved Jpeg2000 decoding + - Decode a few more Photoshop tags + - Issue an error if there is extra data after the normal end of file when + rewriting TIFF images (avoids possible data loss if attempting to write an + unsupported RAW image with a TIFF-like data structure) + - Added ability to replace existing tags with user defined tags + - Denote minor errors/warnings by adding '[minor]' to the message (these are + the errors which can be ignored with the -m option) + - Fixed problem of missing LeafSubIFD when rewriting MOS images + - Removed hack to write Leaf maker note information at start of image + +Sept. 30, 2005 - Version 5.64 + + - Improved writing of Canon CR2 images to preserve CR2 header and editing + information written by Canon Digital Photo Professional software + - Extract information from JPEG APP0 JFIF segments + - Added support for extracting Creo Leaf meta information from MOS images + - Added ability to define new tags in .ExifTool_config file and added a sample + ExifTool_config file to the distribution + - Extended the -w option to allow an expression to be specified + - Allow tag aliases to be used when writing + - Changed print conversion of FileSize tag + - Internal changes to tag lookup to improve speed when writing information + - Decode Photoshop resolution information + +Sept. 21, 2005 - Version 5.63 + + - Added read support for MP3 and WAV audio files (Oops... ExifTool has + expanded beyond its "Image" roots!) + - Added write support for PNG and MRW (Minolta RAW) images + - Improved decoding of PNG profile information and added a few new PNG tags + - Changes to handling of GPS coordinates: + - Added -c (CoordFormat) option to format output of GPS coordinates + - Added GPSPosition composite tag + - GPS coordinates now show as decimal degrees with the -n option + - Much more flexible about the input coordinate format when writing + - Enforce proper formatting of XMP GPS coordinates + - Added XMP-xmp Rating and Label tags, and a few missing XMP-exif GPS tags + - Added new XMP-dex group + - Added two new lenses to the Minolta LensID list (thanks Pedro Corte-Real) + - Added a new lens to the Olympus list (thanks Shingo Noguchi) + +Sept. 7, 2005 - Version 5.62 + + - Fixed problem reading FujiFilm maker notes from RAF images + - Extract comments from PPM/PGM/PBM images and add write support + - Extract maker notes from Nikon Coolscan scanner images + +Sept. 3, 2005 - Version 5.61 + + - Added read support for PBM, PGM and PPM file formats + - Added read support for RAF (FujiFilm RAW) file format + +Sept. 2, 2005 - Version 5.60 + + - Fixed bug where tag was deleted if TAG+=VALUE used for a non-list type tag + - Fixed problem where reading some CRW files could generate a "Use of + uninitialized value in concatenation" warning + - Restructured XMP to separate tags by namespace + - Added XMP-xmpTPg, XMP-cc, XMP-xmpPLUS and XMP-PixelLive groups + - Improved logic for editing XMP list-type tags + - Removed SubDirectory tags from -list option output + - More updates to Pentax LensType list + - Changed Nikon FileSystemVersion tag name to FirmwareVersion + - Added NikonCaptureData and NikonCaptureVersion tags + +Aug. 24, 2005 - Version 5.55 (production release) + + - Added patch to fix word ordering when unpacking doubles on ARM systems with + little-endian byte order but big-endian word order (thanks Riku Voipio) + - Added another lens to the Pentax LensType list + +Aug. 22, 2005 - Version 5.54 + + - Fixed problem introduced in version 5.50 which broke ability to delete + groups of information + - Added a couple of new Pentax LensType's + - Renamed Olympus Lens tag to LensType + +July 29, 2005 - Version 5.53 + + - Added -ext option to allow files to be processed or excluded from processing + based on their extension + - Added MimeType tag + - Convert PDF UTF-16 character strings to UTF-8 (or Windows Latin1 if '-L' + option used) + +July 28, 2005 - Version 5.52 + + - Removed warning message when writing CR2 files that was intended only for + Canon 1D TIFF files + +July 27, 2005 - Version 5.51 + + - Assume '-TagsFromFile @' for any redirected tags (eg. '-SRCTAG>DSTTAG' or + '-DSTTAGTAG' + - Recognize some more Nikon lenses + - API Changes: + - Added ByteOrder option to specify byte ordering when creating new EXIF + segment in a JPEG file + +June 3, 2005 - Version 5.25 (production release) + + - Fixed problem with writing IPTC Time tags + - Changed Composite ShutterSpeed to ignore bulb duration if it is negative + - API Changes: + - Allow tag name to be prefixed by group in calls to SetNewValue() + +June 1, 2005 - Version 5.24 + + - Added new "XMP" tag to allow read/write of XMP data as a block + - Added numbers to subsequent SubIFD group names to allow tags in various + SubIFD's to be accessed individually + - Give priority to tags in full resolution image (whichever TIFF directory + this is in) + - Renamed ExifData tag to EXIF (but didn't make it writable as a block like + XMP) + - Recognize maker notes from more Konica Minolta cameras + - Extract PreviewImage for Samsung Digimax V700, Kenox V10 and Digimax V10 + - Changed validation of CanonPictureInfo to work with more PowerShot cameras + (Note: for these cameras, CanonImageHeightAsShot may not be meaningful) + - Added a number of new IPTC ApplicationRecord tags + - Added Nikon ExposureDifference tag + - Removed trailing white space in values printed by exiftool + +May 27, 2005 - Version 5.23 + + - Changed behaviour of -tagsfromfile slightly so that '-GROUP:TAG>DSTTAG' now + commutes information between different groups unless a destination group is + specified + - Improved reliability of calculating offsets in Pentax maker notes + +May 26, 2005 - Version 5.22 + + - Fixed problem with new '-tagsFromFile @' feature which occurred when + simultaneously copying tags and writing new values to multiple target files + (the new values were only getting written to the first file) + +May 25, 2005 - Version 5.21 + + - Allow target file to be specified by '@' with -TagsFromFile option + - Fixed bug which caused internal error when using -TagsFromFile option to + copy PrintIM information to a file that already contained PrintIM data + - Fixed problem which broke the (now deprecated) -allTagsFromFile=FILE syntax + - Fixed problem decoding Pentax Date for some Optio cameras + - Fixed problem in GeoTiff decoding which could cause some tags to be missed + - Decode a number of new Pentax tags (using my new Optio WP!) + - Made Photoshop URL writable + - Limit length of JPEG segment dump at Verbose=4, and add Verbose=5 level + - API Changes: + - Added SaveNewValues() and RestoreNewValues() + +May 20, 2005 - Version 5.20 + + - Give names to many Photoshop tags, but leave them marked as 'Unknown' so + they aren't extracted under normal circumstances (must use the -u option) + - Read/write Kyocera maker notes properly (although Kyocera information + remains unknown) + - Changed installation tests to tolerate rounding-off errors or format + differences in floating point numbers + +May 17, 2005 - Version 5.19 + + - Added -overwrite_original option + +May 16, 2005 - Version 5.18 (production release) + + - Added -@ option and two utility files (iptc2xmp.args and xmp2iptc.args) to + use with this option for translating between IPTC and XMP tag names + - Disable normal console output if -v option used and no tags specified + - Repair incorrect first byte of MRW preview images when extracting + - More tweaking of -TagsFromFile order of operations + +May 14, 2005 - Version 5.17 + + - Allow 'All' to be used as a group name with '-TagsFromFile' option to + preserve original tag groups (eg. '-all:all') + - PrintIM information is now copied with -TagsFromFile + - Decode EXIF:Gamma tag + - Decode Canon 350D FileNumber + - Made a few more tags writable + - Don't rewrite TIFF files which could be Canon 1D RAW files since this + format currently isn't supported (can use the -m option to write anyway, + which will remove the RAW image data if this is a 1D file) + - Don't add null terminator to UserComment, GPSProcessingMethod or + GPSAreaInformation + - Improved logic for handling command line tag names and exclusions, + especially when associated with the -TagsFromFile option + +May 10, 2005 - Version 5.16 + + - Decode a number of new Nikon lens-related tags (thanks again Robert + Rottmerhusen) + - Various other improvements + +May 7, 2005 - Version 5.15 + + - Added powerful new information redirection feature to -TagsFromFile option + - Added writable File:FileModifyDate tag which represents the filesystem + date/time of last modification + - Allow '*' to also be used as well as 'all' to represent all tags, although + this feature is not documented for the command-line options because 'all' is + more convenient since '*' must be quoted to prevent shell globbing + +May 5, 2005 - Version 5.11 + + - Fixed problem where the proper tags weren't excluded from being extracted if + -GROUP:All and --TAG options are used together on the command line + +May 5, 2005 - Version 5.10 + + - Changed -AllTagsFromFile option to -TagsFromFile and allow copied tags to be + specified on the command line. (-AllTagsFromFile is preserved as an alias + to -TagsFromFile for backward compatibility.) + - Allow -GROUP:All and --GROUP:All on command line to extract or exclude all + tags in specified group + - Allow family 1 group names to be used when deleting groups with -GROUP:All= + - Added composite CFAPattern derived from CFARepeatPatternDim and CFAPattern2 + - Fixed problem where tags which can exist in both IFD0 and ExifIFD weren't + being properly removed from one IFD when written to the other + - Added FAQ + +May 2, 2005 - Version 5.06 + + - Made a few more EXIF tags writable + - No longer add null-terminator to JPEG comment (was confusing xv) + +Apr. 20, 2005 - Version 5.05 (production release) + + - Added Nikon LensFStops tag (thanks to Robert Rottmerhusen) + - Reliability improvements for writing maker notes information + - exiftool now returns error status if there were errors reading/writing files + +Apr. 18, 2005 - Version 5.04 + + - Fixed problem where maker notes of Olympus C2500L could get corrupted when + writing + +Apr. 18, 2005 - Version 5.03 + + - ExifTool now requires Perl version 5.004 or higher (previously 5.002 was OK) + - Restrict the size of preview images where data is referenced directly as + the value data of an IFD entry (only affects Casio images) + - Fixed problems rewriting some Casio maker notes + - Change priority of orientation (and a number of other tags which may appear + in IFD1) so value in IFD0 takes precedence of value in IFD1 if it exists + - API Changes: + - Allow any file reference, not only GLOB references, to be used in + function calls + +Apr. 16, 2005 - Version 5.02 + + - Fixed problem rewriting Pentax *istD preview image + +Apr. 15, 2005 - Version 5.01 + + - Major speed improvements for writing large JPEG files with preview images + - Fixed problem rewriting preview in Olympus E-1 and E-300 images + - Old large preview is now properly removed when writing new small preview + - Allow PreviewImage to be deleted (ie. set length to zero) + - Don't extract images that have zero length + - Deleting MakerNotes group now works in conjunction with -allTagsFromFile + - Change image validation again to only validate images for tags that were + specifically requested + - Separate lookups by manufacturer for Olympus lens information + +Apr. 14, 2005 - Version 5.00 + + - ALL MAJOR PLANNED WRITING FEATURES NOW IMPLEMENTED! + - Finally solved problem of writing large preview images in JPEG files + - -AllTagsFromFile now sets PreviewImage to 'dummy' if it exists in the maker + notes to avoid writing a large preview to the destination file (now you have + to do this manually afterwards if this is what you want) + - Fixed problem rewriting Olympus E1 maker note subdirectories + - Only validate extracted images when Binary (-b) option is used + - Rename Olympus PreviewImageAvailable to PreviewImageValid, and check/set + this tag when reading/writing the preview image + - Change priority of X/YResolution tags so IFD0 value takes precedence + - Changes to Olympus Lens decoding + +Apr. 11, 2005 - Version 4.95 + + - Added ability to delete all meta information, or all information in a group + - Create some mandatory IPTC tags automatically when writing IPTC information + - Decoded a bunch more Olympus tags (thanks to Frank Ledwon) + - Decoded a couple more Canon 1D MkII tags (thanks to Denny Priebe) + - Fixed problem where Sony maker notes could be corrupted when rewriting file + - Fixed problem that could cause wrong tag description to be printed for + missing tags when the -f option is used + - Account for different encoding of Canon ExposureTime in 20D and 350D, and + lower priority of Canon ExposureTime and FNumber so regular EXIF values take + precedence because it appears these values may be model dependent (I hate it + when that happens) + +Apr. 6, 2005 - Version 4.94 + + - Added support for Kodak DX3215 and DX3700 + - Improved Kodak decoding and changed some Kodak tag names + - Improved logic to guard against cyclical recursion in EXIF directories + - Allow tags to be edited in IFD2, IFD3, etc... + - Patched problem when writing Canon 350D images due to probable bug in 350D + firmware (version 1.0.1) that writes an incorrect ThumbnailLength in IFD1 + +Apr. 2, 2005 - Version 4.93 (production release) + + - Added IPTC XMP Core support + - Added support for Kodak CX4200 plus other minor Kodak changes + - Made Kodak maker notes writable + - Minor changes to Olympus tag names and decoding + - Split HTML TagNames documentation into separate files + +Mar. 31, 2005 - Version 4.92 + + - Added support for Kodak and Ricoh cameras + - Decode still more Olympus E-1/E-300 tags (thanks Markku Hanninen) + - Added 'Directory' tag + - Decode a few more Pentax tags (thanks to John Francis) + - Allow newlines in tag values on command line when writing + - Fixed problem rewriting makernotes with sub directories (eg. Olympus) + +Mar. 28, 2005 - Version 4.91 + + - Decode yet more Olympus E-1/E-300 tags (thanks Markku Hanninen) + - Changed decoding of Olympus E-300 Quality tag + - Patched bug in Olympus maker notes that was causing ExifTool to report an + error when reading ORF files + - Fixed problem where strings weren't being properly truncated at the null + terminator if there was a newline after the terminator + - Improved decoding for some Nikon tags (thanks Tom Christiansen) + - Added Nikon shortcut + - Added composite SubSecDateTimeOriginal tag + - Fixed problem where CRW file without file extension wasn't being identified + properly + - Fixed problem extracting thumbnail from some (specifically Olympus) images + - Changed verbose output to always show original EXIF format + - Skip over EXIF entries with unknown format instead of aborting (while + reading only) + - Recognize TIFF field type 13 + +Mar. 24, 2005 - Version 4.90 + + - Extract Olympus PreviewImage, and decode a bunch more Olympus tags + - Improvements to documentation + +Mar. 23, 2005 - Version 4.89 + + - Decode subdirectories in Olympus maker notes (now much more information is + extracted for E-1 and E-300 cameras, although most is still unknown) + +Mar. 22, 2005 - Version 4.88 + + - Convert exiftool help to POD format + +Mar. 15, 2005 - Version 4.87 (production release) + + - Added notes to TagNames documentation + +Mar. 11, 2005 - Version 4.86 + + - Extract PreviewImage from CR2 files + - Create mandatory GPS tags when adding new GPS directory + - Bring IPTC newsphoto support up to spec (as if anyone uses this crap) + - Fixed problem when setting 8-bit integer IPTC values + +Mar. 10, 2005 - Version 4.85 + + - Create most mandatory EXIF entries automatically when a new EXIF directory + is created + - Fixed problem which caused an error when adding XMP information to a TIFF + file which didn't previously contain XMP + - Made '=' optional with -AllTagsFromFile option + - Fixed problem with verbose dump of zero-length directory (eg. Sony F717 + maker notes) + +Mar. 9, 2005 - Version 4.84 + + - Interpret Olympus ImageQuality of 6 as RAW + - Remove validation of TIFF identifier to allow forward compatibility with + untested RAW file formats (ORF files in particular seem to fiddle with this + identifier) + +Mar. 8, 2005 - Version 4.83 + + - Extract ThumbnailImage from Canon CRW files written by some cameras + - Recognize ORF files from Olympus C5060WZ (and hopefully some others too!) + +Mar. 7, 2005 - Version 4.82 + + - Made a number of new EXIF tags writable, but classify them as 'unsafe' so + they aren't copied over with the -AllTagsFromFile option + - Recognize a number of new and very uncommon EXIF tags + - Remove copy number from tag name when using the -S option + - Interpret Photoshop XMP:ColorSpace value of 4294967295 as 'Uncalibrated' + +Mar. 4, 2005 - Version 4.81 + + - Added user-definable shortcuts + - Fixed problem with XMP:Identifier (should have existed in both XMP-dc and + XMP-xmp) + +Mar. 2, 2005 - Version 4.80 + + - The -n option now prints binary data values as "Binary data #### bytes" + - API Changes: (NOTE: Change in API behaviour for binary data values) + - Changed returned ValueConv values so that binary data is now returned as + a SCALAR reference, the same as with PrintConv values + +Mar. 1, 2005 - Version 4.73 (production release) + + - Minor changes to XMP parsing to increase speed and improve validation + +Feb. 28, 2005 - Version 4.72 + + - Extract info from UTF-16 and UTF-32 encoded XMP + - Convert EXIF text fields if encoded in Unicode + - Fixed a few incorrect XP character translation codes + - Fixed name of Nikon ColorBalanceD2H tag + +Feb. 25, 2005 - Version 4.71 + + - Fixed bug introduced in 4.70 which caused error when transferring + information using -AllTagsFromFile from a RAW file to a JPEG file + +Feb. 24, 2005 - Version 4.70 + + - Allow family 1 group name to be specified for any tag while writing + - Fixed problem with writing Nikon PreviewImage to NEF files + +Feb. 23, 2005 - Version 4.67 + + - Added -L option to allow XP characters to be converted to Latin character + set instead of UTF-8. (Now XP characters can be displayed properly in + terminal windows which use either the UTF-8 or WinLatin1 character set.) + - Make JpgFromRaw image writable in Nikon NEF files + +Feb. 21, 2005 - Version 4.66 + + - Recognize JPEG 2000 XMP UUID information + - Extract Meta information from JPEG APP3 + - Yet more playing with XP characters (this has been a learning process for + me). Now special characters show up properly in my OSX terminal window, and + the reverse translation works so now they get written properly as well (for + Perl 5.6.1 or greater anyway... Earlier versions don't have the required + UTF-8 support to handle these special characters) + - Improvements to TagNames documentation (including changing format names to + make them more consistent across different types of meta information) + +Feb. 18, 2005 - Version 4.65 + + - Fixed problem in translating XP characters + +Feb. 17, 2005 - Version 4.64 (production release) + + - Added new tag name documentation (replaces old tag lists) + - Made a few more DNG tags writable + +Feb. 15, 2005 - Version 4.63 + + - Remove null terminators in ICC_Profile 'desc' strings + - Treat Olympus CameraID as a string (why wasn't it written this way?) + - Added print conversion for EXIF:CFAPattern + +Feb. 14, 2005 - Version 4.62 + + - Convert XPTitle, XPComment, XPKeywords etc from XP character codes and add + write support for these tags + - Decode JPEG 2000 Resolution, Label and URL information + - Another try at patching 3 digit exponent situation which causes failed tests + on MSWin32-x86 + - Removed .J2K from recognized extensions (since apparently this is a raw JP2 + codestream, and doesn't contain any metadata that ExifTool can extract) + +Feb. 14, 2005 - Version 4.61 + + - Don't print filename line when -p option used + - JPEG 2000 improvements + - Also recognize .JPX and .J2K extensions + +Feb. 11, 2005 - Version 4.60 + + - Added support for reading the JPEG 2000 (.JP2) files + - Improved warnings on errors while setting tag values + +Feb. 10, 2005 - Version 4.54 + + - Added ColorTemperature tag for many Canon models + - Added AutoRotate for Canon 10D and 300D + - Lowered priority of Nikon ISO so that EXIF ISO is used instead if both exist + - Changed names of PentaxISO and Casio ISOSetting to ISO, and lowered priority + as with Nikon ISO + - Made Photoshop EXIF Camera RAW tags writable + +Feb. 7, 2005 - Version 4.53 (production release) + + - Added FileNumber for Canon 20D (decoded by Juha Eskelinen) + - Removed CanonA0Tag + +Feb. 4, 2005 - Version 4.52 + + - Added another CanonRaw test + - Changes to Canon CRW documentation + +Feb. 4, 2005 - Version 4.51 + + - Finally found documentation for Canon CRW files (CIFF format)!! + - Changed CanonRaw to bring code up to CIFF specification + - Added a bunch more CanonRaw tags + - Updated Canon CRW documentation + +Feb. 2, 2005 - Version 4.50 + + - Allow writing to specific IFD + - Allow permanent tags (eg. MakerNotes tags) 'deleted' by setting them to an + empty string if '' is a valid value for the tag + - Added test for rewriting Nikon D70 information + - Added missing inverse conversion routines for GPS tags (now they are all + writable) + - Decoded a few more Canon and CanonRaw tags + - Added -z option to extract information from images in compressed files + - Improved CanonRaw verbose output + - Remove garbage after null terminator in CanonRaw string-type tags + +Jan. 30, 2005 - Version 4.45 + + - Added a few more Canon tags + - Fixed bug with divide by zero error (in Perl, '0.0' is a true value -- doh!) + +Jan. 30, 2005 - Version 4.44 + + - Sort entries in synthesized Canon MakerNotes directory + - Interpret Canon custom functions for models other than 10D in CRW files + +Jan. 29, 2005 - Version 4.43 + + - Synthesize Canon MakerNotes information when using -allTagsFromFile for a + CRW file + - Decode WhiteBalance table in Canon maker notes + - Rename CanonRaw CanonFileType tag to CanonImageType + +Jan. 28, 2005 - Version 4.42 + + - Fixed problem where multiple IPTC tags could be created if replacing + specific IPTC tag values with 'TAG-=VALUE' + - Made EXIF SceneType writable + - Renamed Nikon ISOUsed tag to ISO + - Added documentation of Canon RAW (CRW) file format + +Jan. 27, 2005 - Version 4.41 + + - Added write support for Canon exposure parameters + - Change validation of CanonPictureInfo to get it working for Canon 20D + +Jan. 26, 2005 - Version 4.40 + + - Added ability to write Canon RAW (CRW) files. With this format you aren't + allowed to add or delete any new tags (just as with the MakerNotes), except + for JpgFromRaw, which I like to be able to delete to save disk space + - Added validation of JpgFromRaw images + - Relax filtering on non-ASCII characters by exiftool script to allow + high-ASCII characters to be printed + - Changed the tense of Orientation values to try to make the meaning more + clear. This tag can be a bit confusing. It gives the rotation that must be + applied to the image to view it properly (hence the rotation of the camera + when the picture was taken). + - Patched problem which was causing failed tests on some platforms (floating + point format has 3 digits in exponent on Perl 5.8.5 MSWin32-x86, grrrr...) + - API Changes: + - Added 'Compact' option to not write blank padding as per XMP and IPTC + specs + +Jan. 24, 2005 - Version 4.36 (production release) + + - Added support for reading Olympus Raw Format (ORF) + +Jan. 23, 2005 - Version 4.35 + + - Moved a couple of the informational warnings to verbose mode + - Suppress warnings an non-critical errors with -m option + - Made a few more of the EXIF tags writable + - Made model-dependent tags Pentax FocalLength and Olympus Quality writable + - Added ability to write CanonCustom tags + - Added range check for integer values + +Jan. 21, 2005 - Version 4.34 + + - Fixed problem when writing Canon maker notes with -allTagsFromFile + - Added -o option to write to different file or directory + - Added handler to clean up temporary file on Ctrl-C + - Re-wrote routine to rationalize floating point values (it is slower now, but + produces much prettier fractions) + - Other minor improvements to writer code + +Jan. 19, 2005 - Version 4.33 + + - Added check at higher level and return warning if trying to delete + information from maker notes + - Make GPS latitude and longitude a bit more flexible about the format they + accept when writing + - Updates to documentation + +Jan. 19, 2005 - Version 4.32 + + - Now rewrites Casio EX-Z3 maker notes properly (well, not actually + 'properly', but the way they were written in the first place, which is + wrong) + - Added warning when writing information if original IFD entries were not in + the proper sequence, which is a violation of EXIF specs. (And surprise, + you'll never guess who does this too... Yup, the EX-Z3.) + - Fixed parsing problem with GPSProcessingMethod and GPSAreaInformation + - No longer truncates 'undef' values at first null character + - Changed all DataDump tags to binary data types + - Changed some warning messages + - Documented the -m option (it's now official, even though it's been there + since version 4.10) + - Added some more writer tests + +Jan. 18, 2005 - Version 4.31 + + - Now also copies over preview image in Nikon NEF files + +Jan. 18, 2005 - Version 4.30 + + - Now copies over preview images in EXIF data (large, external previews still + not copied) + - Account for funny offsets in Casio EX-Z3 maker notes while extracting data + (but haven't figured out how to handle them when writing) + - Fixed bug introduced in 4.20 that broke extraction of Canon PreviewImage + +Jan. 17, 2005 - Version 4.23 + + - Improve handling of unrecognized maker notes when writing + +Jan. 17, 2005 - Version 4.22 + + - Added check in -AllTagsFromFile to test for pointers in the maker notes + directory running outside the maker notes data. If they do, a warning is + issued and the maker notes are rebuilt properly before copying. + - Fixed problem which could corrupt some values when editing maker notes + +Jan. 17, 2005 - Version 4.21 + + - Added Olympus Red/BlueBalance + +Jan. 17, 2005 - Version 4.20 + + - Added ability to edit MakerNotes! + - Added more validation when writing IPTC information + - Fixed display of Nikon FlashExposureComp for negative values + - Fixed problem where the large JPEG image in Nikon and Pentax raw files was + misidentified as the ThumbnailImage. It is now extracted as JpgFromRaw. + This allows all 3 JPEG images contained in Pentax PEF files to be extracted: + ThumbnailImage, PreviewImage and JpgFromRaw. + - Fixed problem on systems that use backslashes in directory names that + prevented exiftool from finding its libraries if not installed + - Changed many Pentax tag names to remove "Pentax" prefix and conform more to + the other tag names (moving information between files of different formats + is much easier if tags have standardized names): + - PentaxPictureMode => PictureMode + - PentaxFocusMode => FocusMode + - PentaxWhiteBalance => WhiteBalance + - PentaxAEMetering => MeteringMode + - PentaxFocalLength => FocalLength + - PentaxZoom => DigitalZoom + - PentaxSaturation => Saturation + - PentaxContrast => Contrast + - PentaxSharpness => Sharpness + - Fixed FocalLength conversion for Pentax Optio S + - Fixed printout of Nikon FileSystemVersion for older Nikon models + - More improvements to reliabilty of preview image extraction + - Fixed Quality for Olympus E-1 + +Jan. 12, 2005 - Version 4.15 + + - Added Pentax LensType and RawImageSize tags + - Change printing of some unknown values to hexadecimal + - Now recognizes Nikon PEF files + - More reliable extraction of preview and thumbnail images, particularly for + the various models of Pentax cameras + - Added decoding of the Canon 20D custom functions and a new Canon20D shortcut + (thanks to Christian Koller) + - Improved write logic for EXIF information + - Improved logic in determining byte ordering of maker notes + +Jan. 10, 2005 - Version 4.14 + + - Fixed problem introduced in 4.13 that messed up new 4.12 features. doh. + (and added test to keep this from happening again!). + - No longer store bad directory data as a tag (dump in verbose output instead) + +Jan. 9, 2005 - Version 4.13 + + - Added check on size of new ThumbnailImage so ExifTool doesn't try to write + an image that is too large (>60k) into the JPEG EXIF APP1 segment + +Jan. 9, 2005 - Version 4.12 + + - -AllTagsFromFile option now copies over the maker notes + - Changed some misleading warning messages + +Jan. 8, 2005 - Version 4.11 + + - Improved validation of tag values with -AllTagsFromFile option + +Jan. 7, 2005 - Version 4.10 + + - Added ability to write EXIF, IPTC and XMP tags in JPEG and TIFF files! + - Allow Photoshop APP13 data to span multiple segments (read and write) + - Added -TAG+=VALUE, -TAG-=VALUE and -TAG<=VALUE syntaxes + - Added -GROUP:TAG syntax to allow tag group to be specified + - Added powerful -AllTagsFromFile=SRCFILE option to copy all tags from file + - Added -listw option to list all writable tags + - Added -E option to escape output values for HTML + - Fixed -w option to only replace extension after last '.' in filename if more + than one '.' + - Unescape XMP character codes when extracting values (and escape again when + writing) + - Now processes all IFD's of TIFF imags (not just IFD0) + - Added data length check in hex dump of verbose option + - Allow group name to be specified as prefix to tag name on command line + - Renamed a few Nikon tags: FlashExposureComp to FlashExposureBracketValue, + FEC to FlashExposureComp, and ShutterReleaseMode to ShootingMode + - Extract Nikon preview image + - Changed descriptions for Aperture and Shutter Speed to drop the Av/Tv + Canonism + - Improved logic to recognize more types of unknown maker notes + - Recognize a couple more values of the Canon WhiteBalance tag + - Renamed IPTC 'SupplementalCategory' to 'SupplementalCategories' + - Handle timezone in times + - API Changes: + - Fixed problem where first tag name passed to GetInfo() was ignored + - The values returned by ImageInfo() and GetInfo() may contain array + references to indicate lists of values if PrintConv is disabled + - Added a bunch of new stuff... + +Dec. 15, 2004 - Version 4.05 + + - Added a couple of Nikon tags (thanks Brian Ristuccia) + - Now preserves original file by renaming to "NAME_original" when writing + information + - Don't preserve file time by default when writing. Added -P option to do + this. + - Changes to spec file + +Dec. 11, 2004 - Version 4.04 + + - Fixed problem which could corrupt JPEG images when adding comments (Note: if + done, the damage can reversed by removing the comments with the same version + of ExifTool that added them.) + +Dec. 6, 2004 - Version 4.03 + + - Major overhaul of verbose message output + - Change -v option to allow verbose level to be specified (eg. -v3 = very very + verbose) + - Added a new Nikon tag (thanks Thomas Walter) + - Count images which were unchanged when writing tags + - Changed FileType 'JPG' to 'JPEG' + +Dec. 2, 2004 - Version 4.02 + + - Fixed problem with rewriting some JPEG images + - Preserve original file modification time when updating tags in a file + - Report of number of files updated + - API Changes: + - Changed arguments of WriteInfo() and allow scalar and file references to + be used + +Dec. 1, 2004 - Version 4.01 + + - Changed -o option to -w to avoid confusion since we now write image files + too + - Added warning if specified image file doesn't exist + +Dec. 1, 2004 - Version 4.00 + + - Started down the road of adding write support: + - Allow writing of Comment tag to JPEG and GIF files + - API for write functions still under development and is likely to change + - Clean up formatting of Nikon string tags (fix case and remove trailing + spaces) + +Nov. 30, 2004 - Version 3.96 + + - Changed JPEG read routine to speed things up a bit + - Added a few more ICC_Profile tags + +Nov. 25, 2004 - Version 3.95 + + - Improved compatibility with old Perl versions (now runs, albeit with + warnings, on 5.003) + +Nov. 25, 2004 - Version 3.94 (production release) + + - Patched problem with reading XMP data using Perl 5.6.x (Perl bug) + - Put lib directory first in exiftool include list to take precedence over + installed versions + - Continue trying to parse JPEG image after an unrecognized APP1 segment + +Nov. 24, 2004 - Version 3.93 (production release) + + - Final round of ICC_Profile updates + - Increase precision of extracted rational values + - Internal Changes: + - Build in better support for all data formats + - Standardize data format names + - Clean up and streamline data read routine + +Nov. 22, 2004 - Version 3.92 + + - Fixed problem with -p option when multiple files are specified + - Enhancements to ICC_Profile information, including extracting information + from profile header + - Subdivide ICC_Profile group in family 1 + - Added Minolta ImageStabilization tag + +Nov. 20, 2004 - Version 3.91 + + - Fixed problem where some tags were not extracted properly from Canon CR2 + files + - Internal Changes: + - Cleaned up and simplified pointer calculations and dirInfo members + +Nov. 20, 2004 - Version 3.90 + + - Extract information from ICC Profiles + - Extract undocumented IFD0 Photoshop tags + - Added support for Minolta RAW (MRW) file format + - Added support for Konica-Minolta cameras + - Improved decoding for Minolta maker notes + - Extract (the sometimes misleading) EXIF WhiteBalance tag even if + WhiteBalance was extracted from the maker notes if the Duplicates option is + set. (Previously it was only extracted as an Unknown tag in this case.) + - API Changes: + - Return list of all tags in image if GetFoundTags() or GetTagList() are + called before ImageInfo() or GetInfo() + +Nov. 15, 2004 - Version 3.85 + + - Extract a couple more Photoshop tags (including PhotoshopQuality) + - All XMP lists now comma separated (previously, 'alt' lists were separated by + '|') + - API Changes: + - GetValue() now returns reference to array if values form a list and + ValueConv is specified + +Nov. 12, 2004 - Version 3.84 + + - Added test of GetTagID() + - Fixed bug in GetTagID() which was causing special tags to get overwritten + +Nov. 12, 2004 - Version 3.83 + + - Added -D and -H command line options + - API Changes: + - Added GetTagID() + +Nov. 11, 2004 - Version 3.82 (production release) + + - Improved diagnostic output for failed tests in installation + +Nov. 11, 2004 - Version 3.81 + + - Updated Olympus module to also support Epson cameras + - Moved MakerNotes code into separate module + - Added tests for Sony and Unknown maker notes + +Nov. 10, 2004 - Version 3.80 + + - Added support for Panasonic/Leica cameras + - Updated Pentax module to also support Asahi cameras + - Decode a couple more Minolta camera model types + +Nov. 4, 2004 - Version 3.74 (production release) + + - Properly localize $_ in public Image::ExifTool subroutines + +Nov. 3, 2004 - Version 3.73 + + - Changes to tests to avoid false failures on MSWin32-x86-multi-thread 4.0 + +Nov. 1, 2004 - Version 3.72 (production release) + + - Fixed minor bug in generation of family 1 XMP group names + - Changes to Photoshop family 2 groups + +Oct. 30, 2004 - Version 3.71 + + - Switched group families 0 and 1 so the general location is now the default + - Fixed bug when sorting by order of group for any family other than 0 + - Added test 17 to ExifTool.t + +Oct. 29, 2004 - Version 3.70 + + - Major improvements to XMP parsing + - Divided XMP group in family 0 based on the XMP namespace prefix + - Changed a few long tables to binary type + - Recognize some new YCbCrSubSampling values + - Display DNG LocalizedCameraModel in plain text + - Patched problem in FileSource reported by Sigma cameras + - Added information about tag format to verbose hex dump + +Oct. 22, 2004 - Version 3.61 + + - Added support for DNG file format + - Added and updated a number of EXIF tags for FAX and other uncommon images + - Added Photoshop URL tag + - Attempt to extract image from files with unrecognized extensions assuming + TIFF format + - Added "Image format error" if the image type is recognized but the format is + bad + - Changed "Unknown file type" error to "Unknown image type" + - Moved POD documentation into separate .pod files + - Started referencing sources for tag definitions in the source code + +Oct. 1, 2004 - Version 3.60 (production release) - initial CPAN release! + + - Changed group family 0 to divide EXIF group into individual IFD groups + - Fixed typos in some Casio tag names + - API Changes: + - Changed name of File::RandomAccessFile to File::RandomAccess + - Changed default setting of Duplicates to 1 + +Sept. 21, 2004 - Version 3.51 + + - Improvements to interpretation of Nikon D70 ISO settings + +Sept. 16, 2004 - Version 3.50 + + - Fixed problem with duplicate tags showing up without the -a option + - Changed Nikon DataDump to a binary type + - Added D70Boring shortcut + +Sept. 14, 2004 - Version 3.49 + + - Changed installation to also install the 'exiftool' script + +Sept. 13, 2004 - Version 3.48 + + - Changed UserComment to skip first 8 bytes since the comments come after an 8 + byte character code + +Sept. 10, 2004 - Version 3.47 + + - Added support for second type of Casio maker notes (MakerNoteCasio2) + +Sept. 1, 2004 - Version 3.46 + + - Fixed minor bug in PrintConv of FileNumber for CanonRaw files + +June 3, 2004 - Version 3.45 + + - Recognize Canon 1D Mk II raw files (.CR2) + (Note: Not properly decoding maker notes from these files yet) + +May 28, 2004 - Version 3.44 + + - Improved validity check of Sony maker notes + +May 18, 2004 - Version 3.43 + + - A couple more changes to the Nikon maker notes + +May 17, 2004 - Version 3.42 + + - Additions to Nikon maker notes for values derived from D70 + +Apr. 28, 2004 - Version 3.41 + + - Fixed some errors when running on older Perl versions + +Apr. 7, 2004 - Version 3.40 + + - Try to extract data from unrecognized maker notes (assuming standard EXIF + format) + - Added tests for different maker notes + +Apr. 6, 2004 - Version 3.37 + + - Added support for Sigma maker notes + - Remember to add new files to MANIFEST so they get included in release. Doh + +Apr. 6, 2004 - Version 3.36 + + - Added support for Sanyo and Minolta maker notes + - Added skeleton for interpreting Sony maker notes + - Interpret Pentax PrintIM + +Apr. 6, 2004 - Version 3.35 + + - Added support for Nikon PrintIM + - Changed names of duplicate EXIF tags + +Apr. 5, 2004 - Version 3.34 + + - Added all missing tag definitions from TIFF 6 standard + - Added a few more EXIF tag definitions + - Interpret PrintIM IFD + - Fixed interpretation of Interoperability IFD + - Fixed potential endless loop bug introduced in version 3.33 + +Apr. 5, 2004 - Version 3.33 + + - Parse SubIFD of Nikon NEF file (now extracts raw image size and thumbnail + image) + +Apr. 2, 2004 - Version 3.32 + + - Changes to some Nikon tag names + - Added Nikon Saturation + - Documentation improvements + +Mar. 31, 2004 - Version 3.31 + + - Now recognizes NEF (Nikon Electronic image Format) files + +Mar. 29, 2004 - Version 3.30 + + - Removed -w option + - Fixed problem with some XMP tags being put in the EXIF group + - More minor speed improvements + - API Changes: + - GetDescription() now requires an ExifTool object reference + - Removed WarnDuplicateDescriptions() + +Mar. 26, 2004 - Version 3.27 + + - Optimized a few routines to speed things up a bit + - API Changes: + - Changed GetDescription() documentation to indicate it is called with an + ExifTool object (this is still optional, but will be mandatory with the + next version) + +Mar. 25, 2004 - Version 3.26 + + - Don't generate warning if end of IPTC block is padded with nulls + +Mar. 19, 2004 - Version 3.25 + + - Fixed problem with 'Input' sort order + +Mar. 19, 2004 - Version 3.24 + + - Only return PreviewImage if it is a valid JPG (otherwise set 'Warning') + +Mar. 16, 2004 - Version 3.23 + + - API Changes: + - Added GetGroups() + - GetGroup() now returns group names for all families if used in list + context and family not specified + +Mar. 12, 2004 - Version 3.22 + + - API Changes: + - Changed GetInfo() to return list of tags like ImageInfo() if list + reference provided + - Fixed bug that caused GetInfo() to ignore specified tags + +Mar. 11, 2004 - Version 3.21 + + - Fixed problem with Composite group in family 1 + - Changed case of Exif to EXIF in family 1 + - -group option now lists Composite group as it should + - Internal Changes: + - Cleaned up handling of function arguments + +Mar. 10, 2004 - Version 3.20 + + - Added -group option + - Added group families 1 and 2 + - Can now specify excluded tags with leading '-' (replaces -x option) + - API Changes: + - Added ClearOptions(), ExtractInfo(), GetInfo(), CombineInfo(), + GetTagList() and GetAllGroups() + - Removed IsVerbose() function (use Options('Verbose') instead) + - Allow groups to be excluded by specifying leading '-' on group name + - ImageInfo() and GetInfo() now use specified group order to set tag + precedence if Duplicates option is not set + - Change default value of Duplicates option back to 0 + +Mar. 1, 2004 - Version 3.15 + + - Changed format of all date and time tags to EXIF standard + - Added some composite date/time tags + - Fixed date formatting so -d option should now work with all combined + date/time tags + - Other minor changes to GPS information + - Improvements to TIFF processing + - Set value to "Undefined" if PrintConv evaluates to undefined value + - Added -G option + - API Changes: + - Changed all option names: shortened and changed to mixed case (sorry!) + - Internal changes: + - Standardized arguments to all processing procedures + - Made call to processing procedure more automatic + - Removed TABLE_TYPE tag and added PROCESS_PROC + - Added ProcessTagTable() member function + +Feb. 27, 2004 - Version 3.14 + + - Added GPS tag conversions and GPS test + - Values that can't be converted now show up simply as "Unknown (X)" + +Feb. 26, 2004 - Version 3.13 + + - Print out errors from exiftool script (since Image::ExifTool no longer + prints them) + - Added more tests + - Failed tests now leave ".failed" file in "t" directory for post mortem + +Feb. 25, 2004 - Version 3.12 + + - Moved all image-related warnings to new Warning tag + +Feb. 25, 2004 - Version 3.11 + + - Added GeoTiff support + - Added -x option + - Improvements to documentation + - Improve XMP parsing for 'Bag' elements + - Capitalize first letter of XMP tag descriptions + - Patch problem with APP13 resource written by older Photoshop versions + - API Changes: + - Added EXCLUDE and GROUP# options + - Change default value of SAVE_DUPLICATES option to 1 + +Feb. 20, 2004 - Version 3.10 + + - Restructuring only -- the behaviour of the exiftool script was not changed + - Moved html documentation to new html directory + - API Changes: + - Conform to standard Perl module mechanics: + - Changed ExifTool package name to Image::ExifTool + - Added Makefile.PL and other standard files + - Added Perl pod documentation + - Added standard test files + - Moved modules into lib directory + - Changed "TagTables" directory name to "ExifTool" + - Added extra parameter in new RandomAccessFile + +Feb. 20, 2004 - Version 3.05 + + - Fixed problem where output files (-o) weren't written if -p option used + +Feb. 19, 2004 - Version 3.04 + + - Added -U option to allow display of unknown values in Canon binary data + blocks + - Made unknown tag names more specific when -u or -U option used + - Added RawData and DecoderTable tags (for Canon RAW file) + +Feb. 17, 2004 - Version 3.03 + + - Fixed RandomAccessFile package name (should have been + File::RandomAccessFile) + - Added IxusAFPoint tag to Canon maker notes (thanks Michael Rommel) + - Avoid scanning past end of Canon binary data blocks + - API Changes: + - GetFoundTags() and GetRequestedTags() now return list instead of list + reference + +Feb. 16, 2004 - Version 3.02 + + - Improved handling of Pentax maker notes + +Feb. 15, 2004 - Version 3.01 + + - API Changes: + - Added GetValue() function + - Completed API documentation + +Feb. 13, 2004 - Version 3.00 + + - Removed -all option (it is now the default -- specify -common for previous + default behaviour) + - Added -a option to allow printout of duplicate tag values + - API Changes: + - I am finally happy with the API, so future major changes are less likely + (hence the major version number) + - No longer return ARRAY reference for list of tags (Instead, tag values + are joined in a comma separated list if tag 'List' flag is set) + - Added SAVE_DUPLICATES option + - Added BuildCompositeTags() to EXPORT_OK list + - GetFoundTags() now sorts tags in specified order + - GetDescriptions() longer returns undef if the description doesn't exist + +Feb. 12, 2004 - Version 2.71 + + - Still more playing with Pentax maker notes + - More API Changes: + - Added RandomAccessFile.pm + - All image file i/o now done through a RandomAccessFile object + --> allows proper piping and use of string i/o + - Allow scalar reference to be passed to ImageInfo() (for string i/o) + +Feb. 11, 2004 - Version 2.70 + + - More tweaking of Pentax maker notes + - Changed API to be more object oriented: + - Removed SetVerbose(), ExtractUnknown(), SetDateFormat(), + EnablePrintConversion(), EnableCompositeTags() + - Added Options() to replace above functions + - Changed WarnDuplicateTags() to WarnDuplicateDescriptions() + - Added GetFoundTags() and GetRequestedTags() + - Many functions now take ExifTool object reference as first argument + - ImageInfo() no longer returns reference to ExifTool object when used in + list context (you have to use "new ExifTool" and the OO form of + ImageInfo() if you want the object) + +Feb. 10, 2004 - Version 2.62 + + - Added -u option to allow display of unknown tags + - Major changes to Pentax maker notes (still needs work) (thanks Wayne Smith) + +Feb. 09, 2004 - Version 2.61 + + - Allow file reference to be passed to ImageInfo() + - Allow file to be read from standard input by specifying "-" as file name + - Added FileType tag + +Feb. 07, 2004 - Version 2.60 + + - Improve IPTC parsing and add support for more IPTC data types + - Read Photoshop APP13 records properly + - Added -g option + - Move shortcuts into separate module + - Changes to API: + - Removed LoadAllTables() and added GetAllTags() + - Removed GetDescriptions() and added GetDescription() + - Changed GetShortcuts() to return a list + - Added tag groups and GetGroup() function + - Return object data from ImageInfo() for use in GetGroup() + +Jan. 30, 2004 - Version 2.51 + + - Speed up JPG reading code + - API no longer returns references to image-specific static data + - Added ExifToolVersion tag + +Jan. 29, 2004 - Version 2.50 + + - Changed API to return binary data as SCALAR reference and + list of values as ARRAY reference (thanks Dan Heller for the suggestions) + - Attempt to make case of tag descriptions more consistent + +Jan. 28, 2004 - Version 2.41 + + - Scan photoshop JPG 0xe1 garbage for possible XMP information + +Jan. 27, 2004 - Version 2.40 + + - Improved handling of XMP data + - Changed output format and added -l option + +Jan. 21, 2004 - Version 2.36 + + - Don't output trailing linefeed when -b option used + +Jan. 19, 2004 - Version 2.35 + + - Changes to verbose output + - Added TagTables::CanonRaw::CleanRaw() as an API utility function + +Jan. 16, 2004 - Version 2.34 + + - Added 'Validate' check for Canon data fields + - Changed ScaleFactor35efl to use FocalLengthIn35mmFormat if available + +Jan. 15, 2004 - Version 2.33 + + - Added ScaleFactor35efl, FocalLength35efl, Lens35efl + - Allow Composite tags to Require/Desire each other + - Changed FlashType to use FlashBits instead of CanonFlashMode + +Jan. 13, 2004 - Version 2.32 + + - Added -d (date format) option + - Added -p (print format file) option + +Jan. 9, 2004 - Version 2.31 + + - Exif WhiteBalance no longer overrides maker-specific WhiteBalance + +Jan. 8, 2004 - Version 2.30 + + - Added support for IPTC format information + +Jan. 6, 2004 - Version 2.25 + + - Fixed problem with ImageInfo() function prototype + - Fixed printout of JpgFromRaw message (doesn't affect JPG extraction) + - Set output files to binmode (including STDOUT) if -b option used (thanks + David Anson) + +Jan. 1, 2004 - Version 2.24 + + - Fixed -list option to show all available tag names + +Dec. 18, 2003 - Version 2.23 + + - Changed "Disable" routines to "Enable" + +Dec. 17, 2003 - Version 2.22 + + - Fixed make/model tags which I broke with a recent change + - Removed null terminator from returned strings + +Dec. 16, 2003 - Version 2.21 + + - Fixed problem with decoding some Nikon maker notes + - General improvements and tweaks to the code + +Dec. 14, 2003 - Version 2.20 + + - Now extracts preview image from 300D JPG files (PreviewImage) + - Changed ThumbnailData tag name to ThumbnailImage + +Dec. 12, 2003 - Version 2.10 + + - ExifTool::ImageInfo now returns reference to hash instead of hash + +Dec. 10, 2003 - Version 2.01 + + - Minor fixes for reading of RAW files + +Dec. 09, 2003 - Version 2.00 + + - Added support for Olympus, Casio and Nikon cameras + - Now recognizes GPS information + - Moved config information to TagTables modules + - Restructured API + +Dec. 05, 2003 - Version 1.72 + + - Changes to composite Aperture and ShutterSpeed decisions + +Dec. 05, 2003 - Version 1.71 + + - Read 10D Custom functions from CRW file too (thanks dpophyte) + +Dec. 05, 2003 - Version 1.70 + + - Added custom functions for 10D and 1D + +Dec. 04, 2003 - Version 1.62 + + - Decode known flash bits + +Dec. 04, 2003 - Version 1.61 + + - Override ShutterSpeed with BulbDuration if available + - Change -s option to add tab-separated list + +Dec. 03, 2003 - Version 1.60 + + - Big improvements in reading Canon RAW files + +Nov. 29, 2003 - Version 1.50 + + - Added ability to extract JPG from RAW + - Added ExifData tag to allow entire EXIF block to be dumped + +Nov. 26, 2003 - Version 1.40 + + - Split up config files to speed things up + - Added ability to extract binary data + - Added ThumbnailData tag (to allow extracting JPG thumbnails) + +Nov. 25, 2003 - Version 1.30 + + - Added experimental Canon RAW (CRW) file support + +Nov. 22, 2003 - Version 1.20 + + - Now reads TIFF files too + +Nov. 20, 2003 - Version 1.12 + + - Don't translate Photoshop Brightness, etc + +Nov. 20, 2003 - Version 1.11 + + - Attempt to fix problem on hp + - Clean up code a bit + - Added '-ver' command-line option + +Nov. 20, 2003 - Version 1.10 + + - Added support for XMP format + +Nov. 19, 2003 - Version 1.00 + + - Initial release (extracts information from JPEG and GIF images, with Canon, + FujiFilm and Pentax makernote support) + diff --git a/ExifTool/MANIFEST b/ExifTool/MANIFEST new file mode 100644 index 0000000..47f1aa9 --- /dev/null +++ b/ExifTool/MANIFEST @@ -0,0 +1,1164 @@ +Changes +MANIFEST +META.json +META.yml +Makefile.PL +README +arg_files/exif2iptc.args +arg_files/exif2xmp.args +arg_files/gps2xmp.args +arg_files/iptc2exif.args +arg_files/iptc2xmp.args +arg_files/iptcCore.args +arg_files/pdf2xmp.args +arg_files/xmp2exif.args +arg_files/xmp2gps.args +arg_files/xmp2iptc.args +arg_files/xmp2pdf.args +config_files/acdsee.config +config_files/age.config +config_files/bibble.config +config_files/convert_regions.config +config_files/cuepointlist.config +config_files/depthmap.config +config_files/example.config +config_files/fotoware.config +config_files/frameCount.config +config_files/gps2utm.config +config_files/guano.config +config_files/nksc.config +config_files/photoshop_paths.config +config_files/picasa_faces.config +config_files/pix4d.config +config_files/rotate_regions.config +config_files/tiff_version.config +config_files/time_zone.config +exiftool +fmt_files/gpx.fmt +fmt_files/gpx_wpt.fmt +fmt_files/kml.fmt +fmt_files/kml_track.fmt +html/ExifTool.html +html/MIE1.1-20070121.pdf +html/Shift.html +html/TagNames/AFCP.html +html/TagNames/AIFF.html +html/TagNames/APE.html +html/TagNames/APP12.html +html/TagNames/ASF.html +html/TagNames/Apple.html +html/TagNames/Audible.html +html/TagNames/BMP.html +html/TagNames/BPG.html +html/TagNames/CBOR.html +html/TagNames/Canon.html +html/TagNames/CanonCustom.html +html/TagNames/CanonRaw.html +html/TagNames/CanonVRD.html +html/TagNames/Casio.html +html/TagNames/Composite.html +html/TagNames/DICOM.html +html/TagNames/DJI.html +html/TagNames/DNG.html +html/TagNames/DPX.html +html/TagNames/DV.html +html/TagNames/DarwinCore.html +html/TagNames/DjVu.html +html/TagNames/EXE.html +html/TagNames/EXIF.html +html/TagNames/Extra.html +html/TagNames/FITS.html +html/TagNames/FLAC.html +html/TagNames/FLIF.html +html/TagNames/FLIR.html +html/TagNames/Flash.html +html/TagNames/FlashPix.html +html/TagNames/Font.html +html/TagNames/FotoStation.html +html/TagNames/FujiFilm.html +html/TagNames/GE.html +html/TagNames/GIF.html +html/TagNames/GIMP.html +html/TagNames/GPS.html +html/TagNames/GeoTiff.html +html/TagNames/GoPro.html +html/TagNames/H264.html +html/TagNames/HP.html +html/TagNames/HTML.html +html/TagNames/ICC_Profile.html +html/TagNames/ICO.html +html/TagNames/ID3.html +html/TagNames/IPTC.html +html/TagNames/ISO.html +html/TagNames/ITC.html +html/TagNames/InfiRay.html +html/TagNames/JFIF.html +html/TagNames/JPEG.html +html/TagNames/JSON.html +html/TagNames/JVC.html +html/TagNames/Jpeg2000.html +html/TagNames/Kodak.html +html/TagNames/KyoceraRaw.html +html/TagNames/LIF.html +html/TagNames/LNK.html +html/TagNames/Leaf.html +html/TagNames/Lytro.html +html/TagNames/M2TS.html +html/TagNames/MIE.html +html/TagNames/MIFF.html +html/TagNames/MISB.html +html/TagNames/MNG.html +html/TagNames/MOI.html +html/TagNames/MPC.html +html/TagNames/MPEG.html +html/TagNames/MPF.html +html/TagNames/MRC.html +html/TagNames/MWG.html +html/TagNames/MXF.html +html/TagNames/MacOS.html +html/TagNames/Matroska.html +html/TagNames/Microsoft.html +html/TagNames/Minolta.html +html/TagNames/MinoltaRaw.html +html/TagNames/Motorola.html +html/TagNames/Nikon.html +html/TagNames/NikonCapture.html +html/TagNames/NikonCustom.html +html/TagNames/NikonSettings.html +html/TagNames/Nintendo.html +html/TagNames/OOXML.html +html/TagNames/Ogg.html +html/TagNames/Olympus.html +html/TagNames/OpenEXR.html +html/TagNames/Opus.html +html/TagNames/Other.html +html/TagNames/PCX.html +html/TagNames/PDF.html +html/TagNames/PGF.html +html/TagNames/PICT.html +html/TagNames/PLIST.html +html/TagNames/PLUS.html +html/TagNames/PNG.html +html/TagNames/PSP.html +html/TagNames/Palm.html +html/TagNames/Panasonic.html +html/TagNames/PanasonicRaw.html +html/TagNames/Parrot.html +html/TagNames/Pentax.html +html/TagNames/PhaseOne.html +html/TagNames/PhotoCD.html +html/TagNames/PhotoMechanic.html +html/TagNames/Photoshop.html +html/TagNames/PostScript.html +html/TagNames/PrintIM.html +html/TagNames/Qualcomm.html +html/TagNames/QuickTime.html +html/TagNames/RIFF.html +html/TagNames/RSRC.html +html/TagNames/RTF.html +html/TagNames/Radiance.html +html/TagNames/Rawzor.html +html/TagNames/Real.html +html/TagNames/Reconyx.html +html/TagNames/Red.html +html/TagNames/Ricoh.html +html/TagNames/Samsung.html +html/TagNames/Sanyo.html +html/TagNames/Scalado.html +html/TagNames/Shortcuts.html +html/TagNames/Sigma.html +html/TagNames/SigmaRaw.html +html/TagNames/Sony.html +html/TagNames/SonyIDC.html +html/TagNames/Stim.html +html/TagNames/Text.html +html/TagNames/Theora.html +html/TagNames/Torrent.html +html/TagNames/Unknown.html +html/TagNames/VCard.html +html/TagNames/Vorbis.html +html/TagNames/WPG.html +html/TagNames/WTV.html +html/TagNames/XMP.html +html/TagNames/ZIP.html +html/TagNames/ZISRAW.html +html/TagNames/iWork.html +html/TagNames/index.html +html/TagNames/style.css +html/ancient_history.html +html/canon_raw.html +html/commentary.html +html/config.html +html/data_members.html +html/exiftool_pod.html +html/faq.html +html/filename.html +html/geotag.html +html/history.html +html/htmldump.html +html/idiosyncracies.html +html/index.html +html/install.html +html/metafiles.html +html/mistakes.html +html/overview.png +html/standards.html +html/struct.html +html/style.css +html/under.html +html/verbose.html +html/writing.html +lib/File/RandomAccess.pm +lib/File/RandomAccess.pod +lib/Image/ExifTool.pm +lib/Image/ExifTool.pod +lib/Image/ExifTool/7Z.pm +lib/Image/ExifTool/AES.pm +lib/Image/ExifTool/AFCP.pm +lib/Image/ExifTool/AIFF.pm +lib/Image/ExifTool/APE.pm +lib/Image/ExifTool/APP12.pm +lib/Image/ExifTool/ASF.pm +lib/Image/ExifTool/Apple.pm +lib/Image/ExifTool/Audible.pm +lib/Image/ExifTool/BMP.pm +lib/Image/ExifTool/BPG.pm +lib/Image/ExifTool/BZZ.pm +lib/Image/ExifTool/BigTIFF.pm +lib/Image/ExifTool/BuildTagLookup.pm +lib/Image/ExifTool/CBOR.pm +lib/Image/ExifTool/Canon.pm +lib/Image/ExifTool/CanonCustom.pm +lib/Image/ExifTool/CanonRaw.pm +lib/Image/ExifTool/CanonVRD.pm +lib/Image/ExifTool/CaptureOne.pm +lib/Image/ExifTool/Casio.pm +lib/Image/ExifTool/Charset.pm +lib/Image/ExifTool/Charset/Arabic.pm +lib/Image/ExifTool/Charset/Baltic.pm +lib/Image/ExifTool/Charset/Cyrillic.pm +lib/Image/ExifTool/Charset/DOSCyrillic.pm +lib/Image/ExifTool/Charset/DOSLatin1.pm +lib/Image/ExifTool/Charset/DOSLatinUS.pm +lib/Image/ExifTool/Charset/Greek.pm +lib/Image/ExifTool/Charset/Hebrew.pm +lib/Image/ExifTool/Charset/JIS.pm +lib/Image/ExifTool/Charset/Latin.pm +lib/Image/ExifTool/Charset/Latin2.pm +lib/Image/ExifTool/Charset/MacArabic.pm +lib/Image/ExifTool/Charset/MacChineseCN.pm +lib/Image/ExifTool/Charset/MacChineseTW.pm +lib/Image/ExifTool/Charset/MacCroatian.pm +lib/Image/ExifTool/Charset/MacCyrillic.pm +lib/Image/ExifTool/Charset/MacGreek.pm +lib/Image/ExifTool/Charset/MacHebrew.pm +lib/Image/ExifTool/Charset/MacIceland.pm +lib/Image/ExifTool/Charset/MacJapanese.pm +lib/Image/ExifTool/Charset/MacKorean.pm +lib/Image/ExifTool/Charset/MacLatin2.pm +lib/Image/ExifTool/Charset/MacRSymbol.pm +lib/Image/ExifTool/Charset/MacRoman.pm +lib/Image/ExifTool/Charset/MacRomanian.pm +lib/Image/ExifTool/Charset/MacThai.pm +lib/Image/ExifTool/Charset/MacTurkish.pm +lib/Image/ExifTool/Charset/PDFDoc.pm +lib/Image/ExifTool/Charset/ShiftJIS.pm +lib/Image/ExifTool/Charset/Symbol.pm +lib/Image/ExifTool/Charset/Thai.pm +lib/Image/ExifTool/Charset/Turkish.pm +lib/Image/ExifTool/Charset/Vietnam.pm +lib/Image/ExifTool/DICOM.pm +lib/Image/ExifTool/DJI.pm +lib/Image/ExifTool/DNG.pm +lib/Image/ExifTool/DPX.pm +lib/Image/ExifTool/DV.pm +lib/Image/ExifTool/DarwinCore.pm +lib/Image/ExifTool/DjVu.pm +lib/Image/ExifTool/EXE.pm +lib/Image/ExifTool/Exif.pm +lib/Image/ExifTool/FITS.pm +lib/Image/ExifTool/FLAC.pm +lib/Image/ExifTool/FLIF.pm +lib/Image/ExifTool/FLIR.pm +lib/Image/ExifTool/Fixup.pm +lib/Image/ExifTool/Flash.pm +lib/Image/ExifTool/FlashPix.pm +lib/Image/ExifTool/Font.pm +lib/Image/ExifTool/FotoStation.pm +lib/Image/ExifTool/FujiFilm.pm +lib/Image/ExifTool/GE.pm +lib/Image/ExifTool/GIF.pm +lib/Image/ExifTool/GIMP.pm +lib/Image/ExifTool/GPS.pm +lib/Image/ExifTool/GeoTiff.pm +lib/Image/ExifTool/Geotag.pm +lib/Image/ExifTool/GoPro.pm +lib/Image/ExifTool/H264.pm +lib/Image/ExifTool/HP.pm +lib/Image/ExifTool/HTML.pm +lib/Image/ExifTool/HtmlDump.pm +lib/Image/ExifTool/ICC_Profile.pm +lib/Image/ExifTool/ICO.pm +lib/Image/ExifTool/ID3.pm +lib/Image/ExifTool/IPTC.pm +lib/Image/ExifTool/ISO.pm +lib/Image/ExifTool/ITC.pm +lib/Image/ExifTool/Import.pm +lib/Image/ExifTool/InDesign.pm +lib/Image/ExifTool/InfiRay.pm +lib/Image/ExifTool/JPEG.pm +lib/Image/ExifTool/JPEGDigest.pm +lib/Image/ExifTool/JSON.pm +lib/Image/ExifTool/JVC.pm +lib/Image/ExifTool/Jpeg2000.pm +lib/Image/ExifTool/Kodak.pm +lib/Image/ExifTool/KyoceraRaw.pm +lib/Image/ExifTool/LIF.pm +lib/Image/ExifTool/LNK.pm +lib/Image/ExifTool/Lang/cs.pm +lib/Image/ExifTool/Lang/de.pm +lib/Image/ExifTool/Lang/en_ca.pm +lib/Image/ExifTool/Lang/en_gb.pm +lib/Image/ExifTool/Lang/es.pm +lib/Image/ExifTool/Lang/fi.pm +lib/Image/ExifTool/Lang/fr.pm +lib/Image/ExifTool/Lang/it.pm +lib/Image/ExifTool/Lang/ja.pm +lib/Image/ExifTool/Lang/ko.pm +lib/Image/ExifTool/Lang/nl.pm +lib/Image/ExifTool/Lang/pl.pm +lib/Image/ExifTool/Lang/ru.pm +lib/Image/ExifTool/Lang/sk.pm +lib/Image/ExifTool/Lang/sv.pm +lib/Image/ExifTool/Lang/tr.pm +lib/Image/ExifTool/Lang/zh_cn.pm +lib/Image/ExifTool/Lang/zh_tw.pm +lib/Image/ExifTool/Leaf.pm +lib/Image/ExifTool/Lytro.pm +lib/Image/ExifTool/M2TS.pm +lib/Image/ExifTool/MIE.pm +lib/Image/ExifTool/MIEUnits.pod +lib/Image/ExifTool/MIFF.pm +lib/Image/ExifTool/MISB.pm +lib/Image/ExifTool/MNG.pm +lib/Image/ExifTool/MOI.pm +lib/Image/ExifTool/MPC.pm +lib/Image/ExifTool/MPEG.pm +lib/Image/ExifTool/MPF.pm +lib/Image/ExifTool/MRC.pm +lib/Image/ExifTool/MWG.pm +lib/Image/ExifTool/MXF.pm +lib/Image/ExifTool/MacOS.pm +lib/Image/ExifTool/MakerNotes.pm +lib/Image/ExifTool/Matroska.pm +lib/Image/ExifTool/Microsoft.pm +lib/Image/ExifTool/Minolta.pm +lib/Image/ExifTool/MinoltaRaw.pm +lib/Image/ExifTool/Motorola.pm +lib/Image/ExifTool/Nikon.pm +lib/Image/ExifTool/NikonCapture.pm +lib/Image/ExifTool/NikonCustom.pm +lib/Image/ExifTool/NikonSettings.pm +lib/Image/ExifTool/Nintendo.pm +lib/Image/ExifTool/OOXML.pm +lib/Image/ExifTool/Ogg.pm +lib/Image/ExifTool/Olympus.pm +lib/Image/ExifTool/OpenEXR.pm +lib/Image/ExifTool/Opus.pm +lib/Image/ExifTool/Other.pm +lib/Image/ExifTool/PCX.pm +lib/Image/ExifTool/PDF.pm +lib/Image/ExifTool/PGF.pm +lib/Image/ExifTool/PICT.pm +lib/Image/ExifTool/PLIST.pm +lib/Image/ExifTool/PLUS.pm +lib/Image/ExifTool/PNG.pm +lib/Image/ExifTool/PPM.pm +lib/Image/ExifTool/PSP.pm +lib/Image/ExifTool/Palm.pm +lib/Image/ExifTool/Panasonic.pm +lib/Image/ExifTool/PanasonicRaw.pm +lib/Image/ExifTool/Parrot.pm +lib/Image/ExifTool/Pentax.pm +lib/Image/ExifTool/PhaseOne.pm +lib/Image/ExifTool/PhotoCD.pm +lib/Image/ExifTool/PhotoMechanic.pm +lib/Image/ExifTool/Photoshop.pm +lib/Image/ExifTool/PostScript.pm +lib/Image/ExifTool/PrintIM.pm +lib/Image/ExifTool/Qualcomm.pm +lib/Image/ExifTool/QuickTime.pm +lib/Image/ExifTool/QuickTimeStream.pl +lib/Image/ExifTool/README +lib/Image/ExifTool/RIFF.pm +lib/Image/ExifTool/RSRC.pm +lib/Image/ExifTool/RTF.pm +lib/Image/ExifTool/Radiance.pm +lib/Image/ExifTool/Rawzor.pm +lib/Image/ExifTool/Real.pm +lib/Image/ExifTool/Reconyx.pm +lib/Image/ExifTool/Red.pm +lib/Image/ExifTool/Ricoh.pm +lib/Image/ExifTool/Samsung.pm +lib/Image/ExifTool/Sanyo.pm +lib/Image/ExifTool/Scalado.pm +lib/Image/ExifTool/Shift.pl +lib/Image/ExifTool/Shortcuts.pm +lib/Image/ExifTool/Sigma.pm +lib/Image/ExifTool/SigmaRaw.pm +lib/Image/ExifTool/Sony.pm +lib/Image/ExifTool/SonyIDC.pm +lib/Image/ExifTool/Stim.pm +lib/Image/ExifTool/TagInfoXML.pm +lib/Image/ExifTool/TagLookup.pm +lib/Image/ExifTool/TagNames.pod +lib/Image/ExifTool/Text.pm +lib/Image/ExifTool/Theora.pm +lib/Image/ExifTool/Torrent.pm +lib/Image/ExifTool/Unknown.pm +lib/Image/ExifTool/VCard.pm +lib/Image/ExifTool/Validate.pm +lib/Image/ExifTool/Vorbis.pm +lib/Image/ExifTool/WPG.pm +lib/Image/ExifTool/WTV.pm +lib/Image/ExifTool/WriteCanonRaw.pl +lib/Image/ExifTool/WriteExif.pl +lib/Image/ExifTool/WriteIPTC.pl +lib/Image/ExifTool/WritePDF.pl +lib/Image/ExifTool/WritePNG.pl +lib/Image/ExifTool/WritePhotoshop.pl +lib/Image/ExifTool/WritePostScript.pl +lib/Image/ExifTool/WriteQuickTime.pl +lib/Image/ExifTool/WriteRIFF.pl +lib/Image/ExifTool/WriteXMP.pl +lib/Image/ExifTool/Writer.pl +lib/Image/ExifTool/XMP.pm +lib/Image/ExifTool/XMP2.pl +lib/Image/ExifTool/XMPStruct.pl +lib/Image/ExifTool/ZIP.pm +lib/Image/ExifTool/ZISRAW.pm +lib/Image/ExifTool/iWork.pm +perl-Image-ExifTool.spec +t/AFCP.t +t/AFCP_2.out +t/AFCP_3.out +t/AIFF.t +t/AIFF_2.out +t/APE.t +t/APE_2.out +t/APE_3.out +t/ASF.t +t/ASF_2.out +t/Apple.t +t/Apple_2.out +t/Audible.t +t/Audible_2.out +t/BMP.t +t/BMP_2.out +t/BPG.t +t/BPG_2.out +t/BigTIFF.t +t/BigTIFF_2.out +t/Canon.t +t/CanonRaw.t +t/CanonRaw_2.out +t/CanonRaw_4.out +t/CanonRaw_5.out +t/CanonRaw_6.out +t/CanonRaw_7.out +t/CanonRaw_8.out +t/CanonRaw_9.out +t/CanonVRD.t +t/CanonVRD_11.out +t/CanonVRD_12.out +t/CanonVRD_13.out +t/CanonVRD_14.out +t/CanonVRD_15.out +t/CanonVRD_16.out +t/CanonVRD_17.out +t/CanonVRD_18.out +t/CanonVRD_19.out +t/CanonVRD_2.out +t/CanonVRD_20.out +t/CanonVRD_21.out +t/CanonVRD_22.out +t/CanonVRD_24.out +t/CanonVRD_3.out +t/CanonVRD_4.out +t/CanonVRD_5.out +t/CanonVRD_6.out +t/CanonVRD_7.out +t/CanonVRD_8.out +t/CanonVRD_9.out +t/Canon_2.out +t/Canon_3.out +t/Casio.t +t/Casio_2.out +t/Casio_3.out +t/Casio_4.out +t/Casio_5.out +t/Casio_6.out +t/DICOM.t +t/DICOM_2.out +t/DNG.t +t/DNG_2.out +t/DNG_3.out +t/DPX.t +t/DPX_2.out +t/DV.t +t/DV_2.out +t/DjVu.t +t/DjVu_2.out +t/EXE.t +t/EXE_2.out +t/EXE_3.out +t/EXE_4.out +t/EXE_5.out +t/EXE_6.out +t/EXE_7.out +t/ExifTool.t +t/ExifTool_16.out +t/ExifTool_17.out +t/ExifTool_2.out +t/ExifTool_20.out +t/ExifTool_21.out +t/ExifTool_22.out +t/ExifTool_23.out +t/ExifTool_24.out +t/ExifTool_25.out +t/ExifTool_26.out +t/ExifTool_27.out +t/ExifTool_28.out +t/ExifTool_29.out +t/ExifTool_3.out +t/ExifTool_30.out +t/ExifTool_31.out +t/ExifTool_32.out +t/ExifTool_33.out +t/ExifTool_34.out +t/ExifTool_35.out +t/ExifTool_4.out +t/ExifTool_5.out +t/ExifTool_6.out +t/ExifTool_7.out +t/ExifTool_8.out +t/ExifTool_9.out +t/FITS.t +t/FITS_2.out +t/FLAC.t +t/FLAC_2.out +t/FLAC_3.out +t/FLIF.t +t/FLIF_2.out +t/FLIF_3.out +t/FLIF_4.out +t/FLIF_5.out +t/FLIF_6.out +t/FLIR.t +t/FLIR_2.out +t/FLIR_3.out +t/Flash.t +t/FlashPix.t +t/FlashPix_2.out +t/Flash_2.out +t/Flash_3.out +t/Font.t +t/Font_2.out +t/Font_3.out +t/Font_4.out +t/Font_5.out +t/Font_6.out +t/Font_7.out +t/FotoStation.t +t/FotoStation_2.out +t/FotoStation_3.out +t/FujiFilm.t +t/FujiFilm_2.out +t/FujiFilm_3.out +t/FujiFilm_4.out +t/FujiFilm_5.out +t/GE.t +t/GE_2.out +t/GE_3.out +t/GIF.t +t/GIF_2.out +t/GIF_3.out +t/GIF_4.out +t/GIMP.t +t/GIMP_2.out +t/GPS.t +t/GPS_2.out +t/GPS_3.out +t/GeoTiff.t +t/GeoTiff_2.out +t/GeoTiff_3.out +t/GeoTiff_4.out +t/Geotag.t +t/Geotag_10.out +t/Geotag_11.out +t/Geotag_12.out +t/Geotag_2.out +t/Geotag_3.out +t/Geotag_5.out +t/Geotag_6.out +t/Geotag_7.out +t/Geotag_8.out +t/Geotag_9.out +t/GoPro.t +t/GoPro_2.out +t/HTML.t +t/HTML_2.out +t/ICO.t +t/ICO_2.out +t/IPTC.t +t/IPTC_2.out +t/IPTC_4.out +t/IPTC_5.out +t/IPTC_6.out +t/IPTC_7.out +t/IPTC_8.out +t/ISO.t +t/ISO_2.out +t/ITC.t +t/ITC_2.out +t/InDesign.t +t/InDesign_2.out +t/InDesign_3.out +t/InDesign_4.out +t/InfiRay.t +t/InfiRay_2.out +t/JSON.t +t/JSON_2.out +t/JVC.t +t/JVC_2.out +t/JVC_3.out +t/JXL.t +t/JXL_2.out +t/JXL_3.out +t/JXL_4.out +t/Jpeg2000.t +t/Jpeg2000_2.out +t/Jpeg2000_3.out +t/Jpeg2000_4.out +t/Jpeg2000_5.out +t/Kodak.t +t/Kodak_2.out +t/Kodak_3.out +t/KyoceraRaw.t +t/KyoceraRaw_2.out +t/LNK.t +t/LNK_2.out +t/Lang.t +t/Lang_1.out +t/Lang_10.out +t/Lang_11.out +t/Lang_12.out +t/Lang_13.out +t/Lang_14.out +t/Lang_15.out +t/Lang_16.out +t/Lang_17.out +t/Lang_18.out +t/Lang_19.out +t/Lang_2.out +t/Lang_3.out +t/Lang_4.out +t/Lang_5.out +t/Lang_6.out +t/Lang_7.out +t/Lang_8.out +t/Lang_9.out +t/Lytro.t +t/Lytro_2.out +t/M2TS.t +t/M2TS_2.out +t/MIE.t +t/MIE_2.out +t/MIE_3.out +t/MIE_5.out +t/MIE_6.out +t/MIFF.t +t/MIFF_2.out +t/MOI.t +t/MOI_2.out +t/MP3.t +t/MP3_2.out +t/MRC.t +t/MRC_2.out +t/MWG.t +t/MWG_2.out +t/MWG_3.out +t/MWG_4.out +t/MWG_5.out +t/MWG_6.out +t/MWG_7.out +t/MXF.t +t/MXF_2.out +t/MacOS.t +t/MacOS_2.out +t/Matroska.t +t/Matroska_2.out +t/Minolta.t +t/Minolta_2.out +t/Minolta_3.out +t/Minolta_4.out +t/Motorola.t +t/Motorola_2.out +t/Nikon.t +t/Nikon_2.out +t/Nikon_3.out +t/Nikon_4.out +t/Nikon_5.out +t/Nikon_7.out +t/Nikon_8.out +t/Olympus.t +t/Olympus_2.out +t/Olympus_3.out +t/Olympus_4.out +t/Olympus_5.out +t/Olympus_6.out +t/Olympus_7.out +t/Olympus_8.out +t/OpenEXR.t +t/OpenEXR_2.out +t/Opus.t +t/Opus_2.out +t/PCX.t +t/PCX_2.out +t/PDF.t +t/PDF_10.out +t/PDF_11.out +t/PDF_12.out +t/PDF_14.out +t/PDF_15.out +t/PDF_16.out +t/PDF_17.out +t/PDF_18.out +t/PDF_19.out +t/PDF_2.out +t/PDF_20.out +t/PDF_22.out +t/PDF_4.out +t/PDF_5.out +t/PDF_6.out +t/PDF_7.out +t/PDF_8.out +t/PDF_9.out +t/PFM.t +t/PFM_2.out +t/PGF.t +t/PGF_2.out +t/PICT.t +t/PICT_2.out +t/PLIST.t +t/PLIST_2.out +t/PLIST_3.out +t/PLIST_4.out +t/PLUS.t +t/PLUS_2.out +t/PNG.t +t/PNG_2.out +t/PNG_3.out +t/PNG_4.out +t/PNG_5.out +t/PNG_6.out +t/PNG_7.out +t/PPM.t +t/PPM_2.out +t/PPM_3.out +t/PSP.t +t/PSP_2.out +t/Palm.t +t/Palm_2.out +t/Panasonic.t +t/Panasonic_2.out +t/Panasonic_3.out +t/Panasonic_4.out +t/Panasonic_5.out +t/Pentax.t +t/Pentax_2.out +t/Pentax_3.out +t/Pentax_4.out +t/PhaseOne.t +t/PhaseOne_2.out +t/PhaseOne_3.out +t/PhotoCD.t +t/PhotoCD_2.out +t/PhotoMechanic.t +t/PhotoMechanic_2.out +t/PhotoMechanic_3.out +t/Photoshop.t +t/Photoshop_2.out +t/Photoshop_3.out +t/PostScript.t +t/PostScript_2.out +t/PostScript_3.out +t/QuickTime.t +t/QuickTime_10.out +t/QuickTime_11.out +t/QuickTime_12.out +t/QuickTime_13.out +t/QuickTime_14.out +t/QuickTime_15.out +t/QuickTime_16.out +t/QuickTime_17.out +t/QuickTime_2.out +t/QuickTime_3.out +t/QuickTime_4.out +t/QuickTime_5.out +t/QuickTime_6.out +t/QuickTime_7.out +t/QuickTime_8.out +t/QuickTime_9.out +t/RIFF.t +t/RIFF_2.out +t/RIFF_3.out +t/RIFF_4.out +t/RIFF_5.out +t/RIFF_6.out +t/RIFF_7.out +t/RTF.t +t/RTF_2.out +t/Radiance.t +t/Radiance_2.out +t/Real.t +t/Real_2.out +t/Real_3.out +t/Real_4.out +t/Red.t +t/Red_2.out +t/Ricoh.t +t/Ricoh_2.out +t/Ricoh_3.out +t/Ricoh_4.out +t/Sanyo.t +t/Sanyo_2.out +t/Sanyo_3.out +t/Sigma.t +t/Sigma_2.out +t/Sigma_3.out +t/Sigma_4.out +t/Sigma_5.out +t/Sony.t +t/Sony_2.out +t/Sony_3.out +t/Sony_5.out +t/TestLib.pm +t/Text.t +t/Text_2.out +t/Text_3.out +t/Text_4.out +t/Text_5.out +t/Text_6.out +t/Text_7.out +t/Torrent.t +t/Torrent_2.out +t/Unknown.t +t/Unknown_2.out +t/Unknown_3.out +t/VCard.t +t/VCard_2.out +t/VCard_3.out +t/Vorbis.t +t/Vorbis_2.out +t/WPG.t +t/WPG_2.out +t/WTV.t +t/WTV_2.out +t/Writer.t +t/Writer_10.out +t/Writer_11.out +t/Writer_13.out +t/Writer_14.out +t/Writer_15.out +t/Writer_16.out +t/Writer_17.out +t/Writer_18.out +t/Writer_19.out +t/Writer_2.out +t/Writer_22.out +t/Writer_24.out +t/Writer_25.out +t/Writer_26.out +t/Writer_27.out +t/Writer_28.out +t/Writer_29.out +t/Writer_30.out +t/Writer_31.out +t/Writer_32.out +t/Writer_33.out +t/Writer_34.out +t/Writer_35.out +t/Writer_36.out +t/Writer_37.out +t/Writer_38.out +t/Writer_39.out +t/Writer_4.out +t/Writer_40.out +t/Writer_41.out +t/Writer_42.out +t/Writer_43.out +t/Writer_44.out +t/Writer_45.out +t/Writer_46.out +t/Writer_47.out +t/Writer_48.out +t/Writer_50.out +t/Writer_51.out +t/Writer_52.out +t/Writer_53.out +t/Writer_54.out +t/Writer_55.out +t/Writer_56.out +t/Writer_58.out +t/Writer_59.out +t/Writer_6.out +t/Writer_60.out +t/Writer_7.out +t/Writer_9.out +t/XMP.t +t/XMP_10.out +t/XMP_11.out +t/XMP_12.out +t/XMP_13.out +t/XMP_14.out +t/XMP_15.out +t/XMP_16.out +t/XMP_17.out +t/XMP_18.out +t/XMP_19.out +t/XMP_2.out +t/XMP_20.out +t/XMP_21.out +t/XMP_22.out +t/XMP_23.out +t/XMP_24.out +t/XMP_25.out +t/XMP_26.out +t/XMP_27.out +t/XMP_28.out +t/XMP_29.out +t/XMP_3.out +t/XMP_30.out +t/XMP_31.out +t/XMP_32.out +t/XMP_34.out +t/XMP_36.out +t/XMP_37.out +t/XMP_39.out +t/XMP_40.out +t/XMP_41.out +t/XMP_42.out +t/XMP_43.out +t/XMP_44.out +t/XMP_45.out +t/XMP_46.out +t/XMP_47.out +t/XMP_48.out +t/XMP_49.out +t/XMP_5.out +t/XMP_50.out +t/XMP_52.out +t/XMP_53.out +t/XMP_54.out +t/XMP_6.out +t/XMP_7.out +t/XMP_8.out +t/XMP_9.out +t/ZIP.t +t/ZIP_2.out +t/ZIP_3.out +t/ZIP_4.out +t/ZIP_5.out +t/ZIP_6.out +t/ZIP_7.out +t/ZIP_8.out +t/ZISRAW.t +t/ZISRAW_2.out +t/images/AFCP.jpg +t/images/AIFF.aif +t/images/APE.ape +t/images/APE.mpc +t/images/ASF.wmv +t/images/Apple.jpg +t/images/Audible.aa +t/images/BMP.bmp +t/images/BPG.bpg +t/images/BigTIFF.btf +t/images/Canon.jpg +t/images/Canon1DmkIII.jpg +t/images/CanonRaw.cr2 +t/images/CanonRaw.cr3 +t/images/CanonRaw.crw +t/images/CanonVRD.dr4 +t/images/CanonVRD.vrd +t/images/CaptureOne.eip +t/images/Casio.jpg +t/images/Casio2.jpg +t/images/CasioQVCI.jpg +t/images/DICOM.dcm +t/images/DNG.dng +t/images/DPX.dpx +t/images/DV.dv +t/images/DjVu.djvu +t/images/EXE.a +t/images/EXE.dylib +t/images/EXE.elf +t/images/EXE.exe +t/images/EXE.macho +t/images/EXE.so +t/images/ExifTool.jpg +t/images/ExifTool.jps +t/images/ExifTool.tif +t/images/ExtendedXMP.jpg +t/images/FITS.fits +t/images/FLAC.flac +t/images/FLAC.ogg +t/images/FLIF.flif +t/images/FLIR.fpf +t/images/FLIR.jpg +t/images/Flash.flv +t/images/Flash.swf +t/images/FlashPix.ppt +t/images/Font.afm +t/images/Font.dfont +t/images/Font.pfa +t/images/Font.pfb +t/images/Font.pfm +t/images/Font.ttf +t/images/FotoStation.jpg +t/images/FujiFilm.jpg +t/images/FujiFilm.raf +t/images/GE.jpg +t/images/GIF.gif +t/images/GIMP.xcf +t/images/GPS.jpg +t/images/GeoTiff.tif +t/images/Geotag.gpx +t/images/Geotag.igc +t/images/Geotag.kml +t/images/Geotag.log +t/images/Geotag.xml +t/images/Geotag2.log +t/images/Geotag3.log +t/images/Geotag_DJI_2020-12-02_[07-50-31].csv +t/images/GoPro.jpg +t/images/HTML.html +t/images/ICC_Profile.icc +t/images/ICO.ico +t/images/IPTC.jpg +t/images/ISO.iso +t/images/ITC.itc +t/images/InDesign.indd +t/images/InfiRay.jpg +t/images/JSON.json +t/images/JVC.jpg +t/images/JVC2.jpg +t/images/JXL.jxl +t/images/JXL2.jxl +t/images/Jpeg2000.j2c +t/images/Jpeg2000.jp2 +t/images/Kodak.jpg +t/images/KyoceraRaw.raw +t/images/LNK.lnk +t/images/Lytro.lfp +t/images/M2TS.mts +t/images/MIE.mie +t/images/MIFF.miff +t/images/MOI.moi +t/images/MP3.mp3 +t/images/MRC.mrc +t/images/MWG.jpg +t/images/MXF.mxf +t/images/MacOS.macos +t/images/Matroska.mkv +t/images/Minolta.jpg +t/images/Minolta.mrw +t/images/Motorola.jpg +t/images/Nikon.jpg +t/images/Nikon.nef +t/images/NikonD2Hs.jpg +t/images/NikonD70.jpg +t/images/OOXML.docx +t/images/Olympus.dss +t/images/Olympus.jpg +t/images/Olympus2.jpg +t/images/OlympusE1.jpg +t/images/OpenDoc.ods +t/images/OpenEXR.exr +t/images/Opus.opus +t/images/PCX.pcx +t/images/PDF.pdf +t/images/PDF2.pdf +t/images/PFM.pfm +t/images/PGF.pgf +t/images/PICT.pict +t/images/PLIST-bin.plist +t/images/PLIST-xml.plist +t/images/PLIST.aae +t/images/PLUS.xmp +t/images/PNG.png +t/images/PPM.ppm +t/images/PSP.psp +t/images/Palm.mobi +t/images/Panasonic.jpg +t/images/Panasonic.rw2 +t/images/Pentax.avi +t/images/Pentax.jpg +t/images/PhaseOne.iiq +t/images/PhotoCD.pcd +t/images/PhotoMechanic.jpg +t/images/Photoshop.psd +t/images/PostScript.eps +t/images/QuickTime.heic +t/images/QuickTime.m4a +t/images/QuickTime.mov +t/images/RIFF.avi +t/images/RIFF.wav +t/images/RIFF.webp +t/images/RTF.rtf +t/images/Radiance.hdr +t/images/Real.ra +t/images/Real.ram +t/images/Real.rm +t/images/Red.r3d +t/images/Ricoh.jpg +t/images/Ricoh2.jpg +t/images/Sanyo.jpg +t/images/Sigma.jpg +t/images/Sigma.x3f +t/images/SigmaDP2.x3f +t/images/Sony.jpg +t/images/Sony.pmp +t/images/Text.csv +t/images/Text1.txt +t/images/Text2.txt +t/images/Text3.txt +t/images/Text4.txt +t/images/Text5.txt +t/images/Torrent.torrent +t/images/Unknown.jpg +t/images/VCard.ics +t/images/VCard.vcf +t/images/Vorbis.ogg +t/images/WPG.wpg +t/images/WTV.wtv +t/images/Writer.jpg +t/images/XMP.inx +t/images/XMP.jpg +t/images/XMP.svg +t/images/XMP.xml +t/images/XMP.xmp +t/images/XMP2.xmp +t/images/XMP3.xmp +t/images/XMP4.xmp +t/images/XMP5.xmp +t/images/XMP6.xmp +t/images/XMP7.xmp +t/images/XMP8.xmp +t/images/XMP9.xmp +t/images/ZIP.gz +t/images/ZIP.rar +t/images/ZIP.zip +t/images/ZISRAW.czi +t/images/iWork.numbers diff --git a/ExifTool/META.json b/ExifTool/META.json new file mode 100644 index 0000000..8ffcf72 --- /dev/null +++ b/ExifTool/META.json @@ -0,0 +1,54 @@ +{ + "abstract" : "Read and write meta information", + "author" : [ + "Phil Harvey (philharvey66 at gmail.com)" + ], + "dynamic_config" : 1, + "generated_by" : "ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.133380", + "license" : [ + "perl_5" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : "2" + }, + "name" : "Image-ExifTool", + "no_index" : { + "directory" : [ + "t", + "inc" + ] + }, + "prereqs" : { + "build" : { + "requires" : { + "ExtUtils::MakeMaker" : "0" + } + }, + "configure" : { + "requires" : { + "ExtUtils::MakeMaker" : "0" + } + }, + "runtime" : { + "recommends" : { + "Archive::Zip" : "0", + "Compress::Raw::Lzma" : "0", + "Compress::Zlib" : "0", + "Digest::MD5" : "0", + "Digest::SHA" : "0", + "IO::Compress::Brotli" : "0", + "IO::Compress::RawDeflate" : "0", + "IO::Uncompress::Brotli" : "0", + "IO::Uncompress::RawInflate" : "0", + "POSIX::strptime" : "0", + "Time::HiRes" : "0" + }, + "requires" : { + "perl" : "5.004" + } + } + }, + "release_status" : "stable", + "version" : "12.67" +} diff --git a/ExifTool/META.yml b/ExifTool/META.yml new file mode 100644 index 0000000..ff8f0c2 --- /dev/null +++ b/ExifTool/META.yml @@ -0,0 +1,34 @@ +--- +abstract: 'Read and write meta information' +author: + - 'Phil Harvey (philharvey66 at gmail.com)' +build_requires: + ExtUtils::MakeMaker: 0 +configure_requires: + ExtUtils::MakeMaker: 0 +dynamic_config: 1 +generated_by: 'ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.133380' +license: perl +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: 1.4 +name: Image-ExifTool +no_index: + directory: + - t + - inc +recommends: + Archive::Zip: 0 + Compress::Raw::Lzma: 0 + Compress::Zlib: 0 + Digest::MD5: 0 + Digest::SHA: 0 + IO::Compress::Brotli: 0 + IO::Compress::RawDeflate: 0 + IO::Uncompress::Brotli: 0 + IO::Uncompress::RawInflate: 0 + POSIX::strptime: 0 + Time::HiRes: 0 +requires: + perl: 5.004 +version: 12.67 diff --git a/ExifTool/Makefile.PL b/ExifTool/Makefile.PL new file mode 100644 index 0000000..219674a --- /dev/null +++ b/ExifTool/Makefile.PL @@ -0,0 +1,58 @@ +use ExtUtils::MakeMaker; +use File::Spec; + +my $ExifTool_pm = File::Spec->catfile('lib', 'Image', 'ExifTool.pm'); +my $ExifTool_pod = File::Spec->catfile('lib', 'Image', 'ExifTool.pod'); + +WriteMakefile( + NAME => 'Image::ExifTool', + VERSION_FROM => $ExifTool_pm, + PREREQ_PM => { }, + (($ExtUtils::MakeMaker::VERSION gt '6.31' and + $ExtUtils::MakeMaker::VERSION lt '6.46') ? + (EXTRA_META => "recommends:\n" . + " Archive::Zip: 0\n" . + " Compress::Zlib: 0\n" . + " Digest::MD5: 0\n" . + " Digest::SHA: 0\n" . + " Time::HiRes: 0\n" . + " POSIX::strptime: 0\n" . + " Compress::Raw::Lzma: 0\n" . + " IO::Compress::RawDeflate: 0\n" . + " IO::Uncompress::RawInflate: 0\n" . + " IO::Compress::Brotli: 0\n" . + " IO::Uncompress::Brotli: 0\n", + # (not worth recommending -- only for column alignment of some languages) + # " Unicode::LineBreak: 0\n", + # (not worth recommending -- only for Rawzor files) + # " IO::Compress::Bzip2: 0\n", + ) : ()), + ($ExtUtils::MakeMaker::VERSION ge '6.46' ? + (META_MERGE => { + recommends => { + 'Archive::Zip' => 0, + 'Compress::Zlib' => 0, + 'Digest::MD5' => 0, + 'Digest::SHA' => 0, + 'Time::HiRes' => 0, + 'POSIX::strptime' => 0, + 'Compress::Raw::Lzma' => 0, + 'IO::Compress::RawDeflate' => 0, + 'IO::Uncompress::RawInflate' => 0, + 'IO::Compress::Brotli' => 0, + 'IO::Uncompress::Brotli' => 0, + # 'Unicode::LineBreak' => 0, + # 'IO::Compress::Bzip2' => 0, + }, + } ) : ()), + clean => { FILES => 't/*.tmp' }, + EXE_FILES => [ 'exiftool' ], + ($] >= 5.005 ? + (ABSTRACT_FROM => $ExifTool_pod, + AUTHOR => 'Phil Harvey (philharvey66 at gmail.com)', + ) : ()), + (($ExtUtils::MakeMaker::VERSION gt '6.30') ? + (LICENSE => 'perl') : ()), + (($ExtUtils::MakeMaker::VERSION ge '6.47') ? + (MIN_PERL_VERSION => '5.004') : ()), +); diff --git a/ExifTool/README b/ExifTool/README new file mode 100644 index 0000000..4567882 --- /dev/null +++ b/ExifTool/README @@ -0,0 +1,261 @@ +ExifTool by Phil Harvey (philharvey66 at gmail.com) +---------------------------------------------------------------------------- + +ExifTool is a customizable set of Perl modules plus a full-featured +command-line application for reading and writing meta information in a wide +variety of files, including the maker note information of many digital +cameras by various manufacturers such as Canon, Casio, DJI, FLIR, FujiFilm, +GE, HP, JVC/Victor, Kodak, Leaf, Minolta/Konica-Minolta, Nikon, Nintendo, +Olympus/Epson, Panasonic/Leica, Pentax/Asahi, Phase One, Reconyx, Ricoh, +Samsung, Sanyo, Sigma/Foveon and Sony. + +Below is a list of file types and meta information formats currently +supported by ExifTool (r = read, w = write, c = create): + + File Types + ------------+-------------+-------------+-------------+------------ + 360 r/w | DPX r | ITC r | NUMBERS r | RAW r/w + 3FR r | DR4 r/w/c | J2C r | O r | RIFF r + 3G2 r/w | DSS r | JNG r/w | ODP r | RSRC r + 3GP r/w | DV r | JP2 r/w | ODS r | RTF r + 7Z r | DVB r/w | JPEG r/w | ODT r | RW2 r/w + A r | DVR-MS r | JSON r | OFR r | RWL r/w + AA r | DYLIB r | JXL r | OGG r | RWZ r + AAE r | EIP r | K25 r | OGV r | RM r + AAX r/w | EPS r/w | KDC r | ONP r | SEQ r + ACR r | EPUB r | KEY r | OPUS r | SKETCH r + AFM r | ERF r/w | LA r | ORF r/w | SO r + AI r/w | EXE r | LFP r | ORI r/w | SR2 r/w + AIFF r | EXIF r/w/c | LIF r | OTF r | SRF r + APE r | EXR r | LNK r | PAC r | SRW r/w + ARQ r/w | EXV r/w/c | LRV r/w | PAGES r | SVG r + ARW r/w | F4A/V r/w | M2TS r | PBM r/w | SWF r + ASF r | FFF r/w | M4A/V r/w | PCD r | THM r/w + AVI r | FITS r | MACOS r | PCX r | TIFF r/w + AVIF r/w | FLA r | MAX r | PDB r | TORRENT r + AZW r | FLAC r | MEF r/w | PDF r/w | TTC r + BMP r | FLIF r/w | MIE r/w/c | PEF r/w | TTF r + BPG r | FLV r | MIFF r | PFA r | TXT r + BTF r | FPF r | MKA r | PFB r | VCF r + CHM r | FPX r | MKS r | PFM r | VNT r + COS r | GIF r/w | MKV r | PGF r | VRD r/w/c + CR2 r/w | GLV r/w | MNG r/w | PGM r/w | VSD r + CR3 r/w | GPR r/w | MOBI r | PLIST r | WAV r + CRM r/w | GZ r | MODD r | PICT r | WDP r/w + CRW r/w | HDP r/w | MOI r | PMP r | WEBP r/w + CS1 r/w | HDR r | MOS r/w | PNG r/w | WEBM r + CSV r | HEIC r/w | MOV r/w | PPM r/w | WMA r + CUR r | HEIF r/w | MP3 r | PPT r | WMV r + CZI r | HTML r | MP4 r/w | PPTX r | WPG r + DCM r | ICC r/w/c | MPC r | PS r/w | WTV r + DCP r/w | ICO r | MPG r | PSB r/w | WV r + DCR r | ICS r | MPO r/w | PSD r/w | X3F r/w + DFONT r | IDML r | MQV r/w | PSP r | XCF r + DIVX r | IIQ r/w | MRC r | QTIF r/w | XLS r + DJVU r | IND r/w | MRW r/w | R3D r | XLSX r + DLL r | INSP r/w | MXF r | RA r | XMP r/w/c + DNG r/w | INSV r | NEF r/w | RAF r/w | ZIP r + DOC r | INX r | NKSC r/w | RAM r | + DOCX r | ISO r | NRW r/w | RAR r | + + Meta Information + ----------------------+----------------------+--------------------- + EXIF r/w/c | CIFF r/w | Ricoh RMETA r + GPS r/w/c | AFCP r/w | Picture Info r + IPTC r/w/c | Kodak Meta r/w | Adobe APP14 r + XMP r/w/c | FotoStation r/w | MPF r + MakerNotes r/w/c | PhotoMechanic r/w | Stim r + Photoshop IRB r/w/c | JPEG 2000 r | DPX r + ICC Profile r/w/c | DICOM r | APE r + MIE r/w/c | Flash r | Vorbis r + JFIF r/w/c | FlashPix r | SPIFF r + Ducky APP12 r/w/c | QuickTime r | DjVu r + PDF r/w/c | Matroska r | M2TS r + PNG r/w/c | MXF r | PE/COFF r + Canon VRD r/w/c | PrintIM r | AVCHD r + Nikon Capture r/w/c | FLAC r | ZIP r + GeoTIFF r/w/c | ID3 r | (and more) + +See html/index.html for more details about ExifTool features. + +ExifTool can be downloaded from + + https://exiftool.org/ + +RUNNING + +The exiftool script can be run right away without the need to install +Image::ExifTool. For example, from within the exiftool directory you can +extract the information from one of the included test files by typing: + + ./exiftool t/images/ExifTool.jpg + +If you move the exiftool script to a different directory, you must also +either move the contents of the lib directory or install the Image::ExifTool +package so the script can find the necessary libraries. + +Note: If you are using the Windows cmd shell, you may need to rename +'exiftool' to 'exiftool.pl' to run it directly from the command line. +Alternatively, you can run exiftool with the command 'perl exiftool'. + +IF YOU ARE STILL CONFUSED + +The exiftool script is a command line application. You run it by typing +commands in a terminal window. The first step is to determine the name of +the directory where you downloaded the ExifTool distribution package. +Assuming, for example, you downloaded it to a folder called "Desktop" in +your home directory, then you would type the following commands in a +terminal window to extract and run ExifTool: + + cd ~/Desktop + gzip -dc Image-ExifTool-12.67.tar.gz | tar -xf - + cd Image-ExifTool-12.67 + ./exiftool t/images/ExifTool.jpg + +Note: These commands extract meta information from one of the test images. +To use one of your images instead, enter the full path name of your file in +place of "t/images/ExifTool.jpg". + +INSTALLATION + +You can install the Image::ExifTool package to make it available for use by +other Perl scripts by typing the following: + + perl Makefile.PL + make + make test + make install + +Notes: + i) You need root access for the last step above. + + ii) Some Perl installations (like the standard OSX installation) may not + contain the necessary files to complete the first step above. But no + worries: You can install ExifTool manually by moving 'exiftool' and the + 'lib' directory to any directory in your current PATH (ie. /usr/bin). + + iii) In Windows, "dmake" or "nmake" may be used if "make" is not + available. + +(Also see html/install.html for more help with installation.) + +DEPENDENCIES + +Requires Perl version 5.004 or later. No other special libraries are +required, however the following modules are recommended for decoding +compressed and/or encrypted information from the indicated file types, and +for calculating digest values and providing other features listed below: + + Archive::Zip (ZIP, DOCX, PPTX, XLSX, ODP, ODS, ODT, EIP, iWork) + Compress::Zlib (DNG, PNG, PDF, DCM, MIE and SWF files) + Digest::MD5 (PDF files, IPTC information, and JPG Extended XMP) + Digest::SHA (PDF with AES-256 encryption) + IO::Compress::Bzip2 (RWZ files) + Time::HiRes (for generating ProcessingTime tag) + POSIX::strptime (for inverse date/time conversion) + Time::Piece (alternative to POSIX::strptime) + Unicode::LineBreak (for column alignment of alternate-language output) + Win32::API (for proper handling of Windows file times) + Win32::FindFile (for Windows Unicode directory support, app only) + Win32API::File (for Windows Unicode file names and file times) + Compress::Raw::Lzma (for reading encoded 7z files) + IO::Compress::RawDeflate (for writing FLIF images) + IO::Uncompress::RawInflate (for reading FLIF images) + IO::Compress::Brotli (for writing compressed JXL metadata) + IO::Uncompress::Brotli (for reading compressed JXL metadata) + +COPYRIGHT AND LICENSE + +Copyright 2003-2023, Phil Harvey + +This is free software; you can redistribute it and/or modify it under the +same terms as Perl itself. + +DISTRIBUTION FILES + +Below is a list of the files/directories included in the full ExifTool +distribution package: + + Changes - Revision history + MANIFEST - Full list of distribution files + META.json - Standard CPAN dependency file (JSON format) + META.yml - Standard CPAN dependency file (YAML format) + Makefile.PL - Makefile for installation + README - This file + arg_files/ - Argument files to convert metadata formats: + exif2iptc.args - Arguments for converting EXIF to IPTC + exif2xmp.args - Arguments for converting EXIF to XMP + gps2xmp.args - Arguments for converting GPS to XMP + iptc2exif.args - Arguments for converting IPTC to EXIF + iptc2xmp.args - Arguments for converting IPTC to XMP + iptcCore.args - Complete list of IPTC Core XMP tags + pdf2xmp.args - Arguments for converting PDF to XMP + xmp2exif.args - Arguments for converting XMP to EXIF + xmp2gps.args - Arguments for converting XMP to GPS + xmp2iptc.args - Arguments for converting XMP to IPTC + xmp2pdf.args - Arguments for converting XMP to PDF + config_files/ - Sample ExifTool configuration files: + acdsee.config - Definitions for writing ACDSee XMP regions + age.config - Calculate Age of person in photo + bibble.config - Definitions for writing Bibble XMP tags + convert_regions.config - Convert between MWG, MP and IPTC regions + cuepointlist.config - Extract cue points and labels as a list + depthmap.config - Extract Google DepthMap images + example.config - General example showing config features + fotoware.config - Definitions for writing Fotoware XMP tags + frameCount.config - Extract FrameCount from MP4 videos + gps2utm.config - Generate UTM coordinate tags from GPS info + guano.config - Decode individual tags from Guano metadata + nksc.config - Decode tags in Nikon ViewNX NKSC files + photoshop_paths.config - For extracting or copying Photoshop paths + picasa_faces.config - Convert from Picasa to MWG/MP face regions + pix4d.config - Definitions for writing Pix4D XMP tags + rotate_regions.config - Rotate MWG and MP region tags + tiff_version.config - Determine the version of a TIFF file + time_zone.config - Calculate time zone from EXIF tags + exiftool - The exiftool application (Perl script) + fmt_files/ - Output formatting example files: + gpx.fmt - Format file for creating GPX track + gpx_wpt.fmt - Format file for creating GPX waypoints + kml.fmt - Format file for creating KML placemarks + kml_track.fmt - Format file for creating KML track + html/ - HTML documentation + html/TagNames/ - HTML tag name documentation + lib/ - ExifTool Perl library modules + perl-Image-ExifTool.spec - Red Hat Packaging Manager specification file + t/ - Verification test code + t/images/ - Verification test images + +ADDITIONAL INFORMATION + +Read the following files included in the full distribution for more +information: + + html/index.html - Main ExifTool documentation + html/install.html - Installation instructions + html/history.html - Revision history + html/ExifTool.html - API documentation + html/TagNames/index.html - Tag name documentation + html/geotag.html - Geotag feature + html/faq.html - Frequently asked questions + html/filename.html - Renaming/moving files + html/metafiles.html - Working with metadata sidecar files + html/struct.html - Working with structured XMP information + lib/Image/ExifTool/README - ExifTool library modules documentation + +and if you have installed Image::ExifTool, you can also consult perldoc or +the man pages: + + perldoc exiftool + perldoc Image::ExifTool + perldoc Image::ExifTool::TagNames + + man exiftool + man Image::ExifTool + man Image::ExifTool::TagNames + +Note: If the man pages don't work, it is probably because your man path is +not set to include the installed documentation. See "man man" for +information about how to set the man path. + +---------------------------------------------------------------------------- diff --git a/ExifTool/arg_files/exif2iptc.args b/ExifTool/arg_files/exif2iptc.args new file mode 100644 index 0000000..408d8e4 --- /dev/null +++ b/ExifTool/arg_files/exif2iptc.args @@ -0,0 +1,31 @@ +#------------------------------------------------------------------------------ +# File: exif2iptc.args +# +# Description: Tag name translations for converting EXIF to IPTC +# +# Usage: exiftool -tagsFromFile SRCFILE -@ exif2iptc.args DSTFILE +# +# Requires: ExifTool version 6.51 or later +# +# Revisions: 2011/09/13 - P. Harvey Created +# 2017/07/06 - PH Set time zone from new EXIF OffsetTime tags +# +# Notes: 1) IPTC requires a timezone but EXIF does not support one, so +# the local timezone is assumed when writing the IPTC times +# +# 2) These arguments will not delete existing IPTC tags which are +# missing from the EXIF. The IPTC tags should be deleted +# beforehand if required. +#------------------------------------------------------------------------------ +-IPTC:By-line < EXIF:Artist +-IPTC:CopyrightNotice < EXIF:Copyright +-IPTC:Caption-Abstract < EXIF:ImageDescription +# the inverse IPTC date and time conversions automagically pull the +# appropriate date or time part out of the EXIF date/time value +-IPTC:DateCreated < EXIF:DateTimeOriginal +-IPTC:TimeCreated < EXIF:DateTimeOriginal +-IPTC:TimeCreated < Composite:SubSecDateTimeOriginal +-IPTC:DigitalCreationDate < EXIF:CreateDate +-IPTC:DigitalCreationTime < EXIF:CreateDate +-IPTC:DigitalCreationTime < Composite:SubSecCreateDate +# end diff --git a/ExifTool/arg_files/exif2xmp.args b/ExifTool/arg_files/exif2xmp.args new file mode 100644 index 0000000..234a0cb --- /dev/null +++ b/ExifTool/arg_files/exif2xmp.args @@ -0,0 +1,53 @@ +#------------------------------------------------------------------------------ +# File: exif2xmp.args +# +# Description: Tag name translations for converting EXIF to XMP +# +# Usage: exiftool -tagsFromFile SRCFILE -@ exif2xmp.args DSTFILE +# +# Requires: ExifTool version 10.96 or later +# +# Revisions: 2009/01/20 - P. Harvey Created +# 2011/06/22 - PH Copy flash information via Composite:Flash +# 2013/06/12 - PH Additions for the Exif 2.3 for XMP spec +# 2015/01/12 - PH Avoid copying to non-standard namespaces +# 2018/05/07 - PH Added support for GPSDestXxxRef tags +# 2022/03/31 - PH IPTC Photometadata Mapping Guidelines 2202.1 update +# +# References: http://www.metadataworkinggroup.org/specs/ +# https://iptc.org/std/photometadata/documentation/mappingguidelines/ +# +# Notes: 1) The first three arguments copy most of the EXIF and GPS tags to +# XMP-exif/XMP-tiff, and the remaining arguments handle tags +# which have different names and/or formats in EXIF and XMP. +# +# 2) These arguments will not delete existing XMP tags which are +# missing from the EXIF. The XMP tags should be deleted +# beforehand if required. +# +# 3) EXIF:SubjectArea is not currently mapped into XMP-iptcExt:ImageRegion +#------------------------------------------------------------------------------ +-XMP-exif:all < EXIF:all +-XMP-exifEX:all < EXIF:all +-XMP-tiff:all < EXIF:all +-XMP-dc:Description < EXIF:ImageDescription +# overwrite date/time values to include sub-second information if available +-XMP-photoshop:DateCreated < EXIF:DateTimeOriginal +-XMP-photoshop:DateCreated < Composite:SubSecDateTimeOriginal +-XMP-xmp:CreateDate < EXIF:CreateDate +-XMP-xmp:CreateDate < Composite:SubSecCreateDate +-XMP-xmp:ModifyDate < EXIF:ModifyDate +-XMP-xmp:ModifyDate < Composite:SubSecModifyDate +-XMP-xmp:CreatorTool < EXIF:Software +-XMP-dc:Rights < EXIF:Copyright +-XMP-dc:Creator < EXIF:Artist +-XMP-iptcExt:DigitalImageGUID < EXIF:DigImageGUID +# XMP flash information is translated by the Composite Flash tag +-Composite:Flash < EXIF:Flash +# overwrite GPS tags which have different formats in XMP +-XMP:GPSLatitude < Composite:GPSLatitude +-XMP:GPSLongitude < Composite:GPSLongitude +-XMP:GPSDestLatitude < Composite:GPSDestLatitude +-XMP:GPSDestLongitude < Composite:GPSDestLongitude +-XMP:GPSDateTime < Composite:GPSDateTime +# end diff --git a/ExifTool/arg_files/gps2xmp.args b/ExifTool/arg_files/gps2xmp.args new file mode 100644 index 0000000..96482ba --- /dev/null +++ b/ExifTool/arg_files/gps2xmp.args @@ -0,0 +1,27 @@ +#------------------------------------------------------------------------------ +# File: gps2xmp.args +# +# Description: Argument file for copying GPS information from EXIF to XMP +# +# Usage: exiftool -tagsFromFile SRCFILE -@ gps2xmp.args DSTFILE +# +# Requires: ExifTool version 10.96 or later +# +# Revisions: 2009/01/09 - P. Harvey Created +# 2018/05/07 - PH Handle GPSDestLatitude/LongitudeRef tags +# +# Notes: 1) Most of the GPS tags are copied by the first argument, but +# the XMP GPS coordinate and date/time tags are composites of +# more than one EXIF GPS tag, so they are handled separately. +# +# 2) These arguments will not delete existing XMP tags which are +# missing from the GPS. The XMP GPS tags should be deleted +# first with "-xmp:gps*=" if required. +#------------------------------------------------------------------------------ +-XMP:all < GPS:all +-XMP:GPSLatitude < Composite:GPSLatitude +-XMP:GPSLongitude < Composite:GPSLongitude +-XMP:GPSDestLatitude < Composite:GPSDestLatitude +-XMP:GPSDestLongitude < Composite:GPSDestLongitude +-XMP:GPSDateTime < Composite:GPSDateTime +# end diff --git a/ExifTool/arg_files/iptc2exif.args b/ExifTool/arg_files/iptc2exif.args new file mode 100644 index 0000000..4cafcc8 --- /dev/null +++ b/ExifTool/arg_files/iptc2exif.args @@ -0,0 +1,24 @@ +#------------------------------------------------------------------------------ +# File: iptc2exif.args +# +# Description: Tag name translations for converting IPTC to EXIF +# +# Usage: exiftool -tagsFromFile SRCFILE -@ iptc2exif.args DSTFILE +# +# Requires: ExifTool version 7.98 or later +# +# Revisions: 2011/09/13 - P. Harvey Created +# 2017/07/06 - PH Set new EXIF OffsetTime tags +# +# Notes: These arguments will not delete existing EXIF tags which are +# missing from the IPTC. The EXIF tags should be deleted +# beforehand if required. +#------------------------------------------------------------------------------ +-EXIF:Artist < IPTC:By-line +-EXIF:Copyright < IPTC:CopyrightNotice +-EXIF:ImageDescription < IPTC:Caption-Abstract +-EXIF:DateTimeOriginal < Composite:DateTimeCreated +-EXIF:CreateDate < Composite:DigitalCreationDateTime +-EXIF:OffsetTimeOriginal < IPTC:TimeCreated +-EXIF:OffsetTimeDigitized < IPTC:DigitalCreationTime +# end diff --git a/ExifTool/arg_files/iptc2xmp.args b/ExifTool/arg_files/iptc2xmp.args new file mode 100644 index 0000000..1e387f6 --- /dev/null +++ b/ExifTool/arg_files/iptc2xmp.args @@ -0,0 +1,57 @@ +#------------------------------------------------------------------------------ +# File: iptc2xmp.args +# +# Description: Tag name translations for converting from IPTC to XMP +# +# Usage: exiftool -tagsFromFile SRCFILE -@ iptc2xmp.args DSTFILE +# +# Requires: ExifTool version 7.45 or later +# +# Revisions: 2005/05/14 - P. Harvey Created +# 2008/03/04 - PH Changed Location translation and added +# IntellectualGenre and SubjectCode +# 2008/09/30 - PH Added writing of Photoshop:IPTCDigest +# 2009/01/20 - PH Updated to conform with MWG spec +# 2009/10/21 - PH Write XMP-xmp:CreateDate as per MWG 1.01 spec +# +# References: http://www.iptc.org/IPTC4XMP/ +# http://www.iptc.org/IIM/ +# http://www.adobe.com/products/xmp/pdfs/xmpspec.pdf +# http://www.metadataworkinggroup.org/specs/ +# +# Notes: 1) Also updates Photoshop:IPTCDigest as per MWG recommendation. +# +# 2) These arguments will not delete existing XMP tags which are +# missing from the IPTC. The XMP tags should be deleted +# beforehand if required. +#------------------------------------------------------------------------------ +-XMP-dc:Creator < IPTC:By-line +-XMP-dc:Description < IPTC:Caption-Abstract +-XMP-dc:Rights < IPTC:CopyrightNotice +-XMP-dc:Subject < IPTC:Keywords +-XMP-dc:Title < IPTC:ObjectName +-XMP-photoshop:AuthorsPosition < IPTC:By-lineTitle +-XMP-photoshop:CaptionWriter < IPTC:Writer-Editor +-XMP-photoshop:Category < IPTC:Category +-XMP-photoshop:City < IPTC:City +-XMP-photoshop:Country < IPTC:Country-PrimaryLocationName +-XMP-photoshop:Credit < IPTC:Credit +-XMP-photoshop:DateCreated < IPTC:DateCreated +# overwrite XMP DateCreated to include date and time if available +-XMP-photoshop:DateCreated < Composite:DateTimeCreated +-XMP-photoshop:Headline < IPTC:Headline +-XMP-photoshop:Instructions < IPTC:SpecialInstructions +-XMP-photoshop:Source < IPTC:Source +-XMP-photoshop:State < IPTC:Province-State +-XMP-photoshop:SupplementalCategories < IPTC:SupplementalCategories +-XMP-photoshop:TransmissionReference < IPTC:OriginalTransmissionReference +-XMP-photoshop:Urgency < IPTC:Urgency +-XMP-iptcCore:CountryCode < IPTC:Country-PrimaryLocationCode +-XMP-iptcCore:Location < IPTC:Sub-location +-XMP-xmp:CreateDate < IPTC:DigitalCreationDate +-XMP-xmp:CreateDate < Composite:DigitalCreationDateTime +# these tags are not free-form text, and may need special handling: +#-XMP-iptcCore:IntellectualGenre < IPTC:ObjectAttributeReference +#-XMP-iptcCore:SubjectCode < IPTC:SubjectReference +-Photoshop:IPTCDigest=new +# end diff --git a/ExifTool/arg_files/iptcCore.args b/ExifTool/arg_files/iptcCore.args new file mode 100644 index 0000000..1f3de09 --- /dev/null +++ b/ExifTool/arg_files/iptcCore.args @@ -0,0 +1,241 @@ +#------------------------------------------------------------------------------ +# File: iptcCore.args +# +# Description: ExifTool arguments for IPTC Core and Extension tags +# +# Usage: exiftool -@ iptcCore.args FILE +# +# Requires: ExifTool version 8.44 or later +# +# Revisions: 2011/12/28 - P. Harvey Created (IPTC Core version 1.1) +# 2015/04/21 - PH Updated to IPTC Extension version 1.2 +# 2017/06/02 - PH Updated to IPTC Extension version 1.3 +# 2018/08/20 - PH Updated to IPTC Extension version 1.4 +# +# References: http://www.iptc.org/IPTC4XMP/ +# +# Notes: Both flattened and structured tags are included +#------------------------------------------------------------------------------ + +# +# dc schema +# + +-XMP-dc:Creator +-XMP-dc:Description +-XMP-dc:Rights +-XMP-dc:Subject +-XMP-dc:Title + +# +# photoshop schema +# + +-XMP-photoshop:AuthorsPosition +-XMP-photoshop:CaptionWriter +-XMP-photoshop:City +-XMP-photoshop:Country +-XMP-photoshop:Credit +-XMP-photoshop:DateCreated +-XMP-photoshop:Headline +-XMP-photoshop:Instructions +-XMP-photoshop:Source +-XMP-photoshop:State +-XMP-photoshop:TransmissionReference + +# +# xmp schema +# + +-XMP-xmp:Rating + +# +# xmpRights schema +# + +-XMP-xmpRights:UsageTerms +-XMP-xmpRights:WebStatement + +# +# Iptc4xmpCore schema +# + +-XMP-iptcCore:CountryCode + +# ContactInfo struct +-XMP-iptcCore:CreatorContactInfo +-XMP-iptcCore:CreatorAddress +-XMP-iptcCore:CreatorCity +-XMP-iptcCore:CreatorCountry +-XMP-iptcCore:CreatorPostalCode +-XMP-iptcCore:CreatorRegion +-XMP-iptcCore:CreatorWorkEmail +-XMP-iptcCore:CreatorWorkTelephone +-XMP-iptcCore:CreatorWorkURL + +-XMP-iptcCore:IntellectualGenre +-XMP-iptcCore:Location +-XMP-iptcCore:Scene +-XMP-iptcCore:SubjectCode + +# +# Iptc4xmpExt schema +# + +-XMP-iptcExt:AdditionalModelInformation + +# CVTermDetails struct +-XMP-iptcExt:AboutCvTerm +-XMP-iptcExt:AboutCvTermCvId +-XMP-iptcExt:AboutCvTermId +-XMP-iptcExt:AboutCvTermName +-XMP-iptcExt:AboutCvTermRefinedAbout + +# ArtworkOrObjectDetails struct +-XMP-iptcExt:ArtworkOrObject +-XMP-iptcExt:ArtworkCopyrightNotice +-XMP-iptcExt:ArtworkCreator +-XMP-iptcExt:ArtworkDateCreated +-XMP-iptcExt:ArtworkSource +-XMP-iptcExt:ArtworkSourceInventoryNo +-XMP-iptcExt:ArtworkTitle +-XMP-iptcExt:ArtworkCopyrightOwnerName +-XMP-iptcExt:ArtworkCopyrightOwnerID +-XMP-iptcExt:ArtworkLicensorName +-XMP-iptcExt:ArtworkLicensorID +-XMP-iptcExt:ArtworkCreatorID +-XMP-iptcExt:ArtworkCircaDateCreated +-XMP-iptcExt:ArtworkStylePeriod +-XMP-iptcExt:ArtworkSourceInvURL +-XMP-iptcExt:ArtworkContentDescription +-XMP-iptcExt:ArtworkContributionDescription +-XMP-iptcExt:ArtworkPhysicalDescription + +-XMP-iptcExt:ControlledVocabularyTerm +-XMP-iptcExt:DigitalImageGUID +-XMP-iptcExt:DigitalSourcefileType +-XMP-iptcExt:DigitalSourceType + +# EEREDetails struct +-XMP-iptcExt:EmbdEncRightsExpr +-XMP-iptcExt:EmbeddedEncodedRightsExpr +-XMP-iptcExt:EmbeddedEncodedRightsExprType +-XMP-iptcExt:EmbeddedEncodedRightsExprLangID + +-XMP-iptcExt:Event +-XMP-iptcExt:IPTCLastEdited + +# LEREDetails struct +-XMP-iptcExt:LinkedEncRightsExpr +-XMP-iptcExt:LinkedEncodedRightsExpr +-XMP-iptcExt:LinkedEncodedRightsExprType +-XMP-iptcExt:LinkedEncodedRightsExprLangID + +# LocationDetails struct +-XMP-iptcExt:LocationCreated +-XMP-iptcExt:LocationCreatedCity +-XMP-iptcExt:LocationCreatedCountryCode +-XMP-iptcExt:LocationCreatedCountryName +-XMP-iptcExt:LocationCreatedProvinceState +-XMP-iptcExt:LocationCreatedSublocation +-XMP-iptcExt:LocationCreatedWorldRegion +-XMP-iptcExt:LocationCreatedLocationId + +# LocationDetails struct +-XMP-iptcExt:LocationShown +-XMP-iptcExt:LocationShownCity +-XMP-iptcExt:LocationShownCountryCode +-XMP-iptcExt:LocationShownCountryName +-XMP-iptcExt:LocationShownProvinceState +-XMP-iptcExt:LocationShownSublocation +-XMP-iptcExt:LocationShownWorldRegion +-XMP-iptcExt:LocationShownLocationId + +-XMP-iptcExt:MaxAvailHeight +-XMP-iptcExt:MaxAvailWidth +-XMP-iptcExt:ModelAge +-XMP-iptcExt:OrganisationInImageCode +-XMP-iptcExt:OrganisationInImageName +-XMP-iptcExt:PersonInImage + +# PersonDetails struct +-XMP-iptcExt:PersonInImageWDetails +-XMP-iptcExt:PersonInImageId +-XMP-iptcExt:PersonInImageName +-XMP-iptcExt:PersonInImageDescription +# CVTermDetails struct +#-XMP-iptcExt:PersonInImageCharacteristic +-XMP-iptcExt:PersonInImageCvTermCvId +-XMP-iptcExt:PersonInImageCvTermId +-XMP-iptcExt:PersonInImageCvTermName +-XMP-iptcExt:PersonInImageCvTermRefinedAbout + +# ProductDetails struct +-XMP-iptcExt:ProductInImage +-XMP-iptcExt:ProductInImageName +-XMP-iptcExt:ProductInImageGTIN +-XMP-iptcExt:ProductInImageDescription + +# RegistryEntryDetails struct +-XMP-iptcExt:RegistryID +-XMP-iptcExt:RegistryItemID +-XMP-iptcExt:RegistryOrganisationID +-XMP-iptcExt:RegistryEntryRole + +# Genre struct +-XMP-iptcExt:Genre +-XMP-iptcExt:GenreCvId +-XMP-iptcExt:GenreCvTermId +-XMP-iptcExt:GenreCvTermName +-XMP-iptcExt:GenreCvTermRefinedAbout + +# +# plus schema +# + +# CopyrightOwnerDetail struct +-XMP-plus:CopyrightOwner +-XMP-plus:CopyrightOwnerID +-XMP-plus:CopyrightOwnerImageID +-XMP-plus:CopyrightOwnerName + +# ImageCreatorDetail struct +-XMP-plus:ImageCreator +-XMP-plus:ImageCreatorID +-XMP-plus:ImageCreatorImageID +-XMP-plus:ImageCreatorName + +# ImageSupplierDetail struct +-XMP-plus:ImageSupplier +-XMP-plus:ImageSupplierID +-XMP-plus:ImageSupplierImageID +-XMP-plus:ImageSupplierName + +# LicensorDetail struct +-XMP-plus:Licensor +-XMP-plus:LicensorCity +-XMP-plus:LicensorCountry +-XMP-plus:LicensorEmail +-XMP-plus:LicensorExtendedAddress +-XMP-plus:LicensorID +-XMP-plus:LicensorImageID +-XMP-plus:LicensorName +-XMP-plus:LicensorNotes +-XMP-plus:LicensorPostalCode +-XMP-plus:LicensorRegion +-XMP-plus:LicensorStreetAddress +-XMP-plus:LicensorTelephone1 +-XMP-plus:LicensorTelephone2 +-XMP-plus:LicensorTelephoneType1 +-XMP-plus:LicensorTelephoneType2 +-XMP-plus:LicensorTransactionID +-XMP-plus:LicensorURL + +-XMP-plus:MinorModelAgeDisclosure +-XMP-plus:ModelReleaseID +-XMP-plus:ModelReleaseStatus +-XMP-plus:PLUSVersion +-XMP-plus:PropertyReleaseID +-XMP-plus:PropertyReleaseStatus + +# end diff --git a/ExifTool/arg_files/pdf2xmp.args b/ExifTool/arg_files/pdf2xmp.args new file mode 100644 index 0000000..58eb288 --- /dev/null +++ b/ExifTool/arg_files/pdf2xmp.args @@ -0,0 +1,27 @@ +#------------------------------------------------------------------------------ +# File: pdf2xmp.args +# +# Description: Tag name translations for converting from PDF DocInfo to XMP +# +# Usage: exiftool -tagsFromFile SRCFILE -@ pdf2xmp.args DSTFILE +# +# Requires: ExifTool version 7.07 or later +# +# Revisions: 2011/01/23 - P. Harvey Created +# +# References: http://www.adobe.com/devnet/xmp/ +# +# Notes: These arguments will not delete existing XMP tags which are +# missing from the PDF. The XMP tags should be deleted +# beforehand if required. +#------------------------------------------------------------------------------ +-XMP-dc:Title < PDF:Title +-XMP-dc:Creator < PDF:Author +-XMP-dc:Description < PDF:Subject +-XMP-pdf:Keywords < PDF:Keywords +-XMP-xmp:CreatorTool < PDF:Creator +-XMP-pdf:Producer < PDF:Producer +-XMP-xmp:CreateDate < PDF:CreateDate +-XMP-xmp:ModifyDate < PDF:ModifyDate +-XMP-pdf:Trapped < PDF:Trapped +# end diff --git a/ExifTool/arg_files/xmp2exif.args b/ExifTool/arg_files/xmp2exif.args new file mode 100644 index 0000000..b421a94 --- /dev/null +++ b/ExifTool/arg_files/xmp2exif.args @@ -0,0 +1,59 @@ +#------------------------------------------------------------------------------ +# File: xmp2exif.args +# +# Description: Tag name translations for converting XMP to EXIF +# +# Usage: exiftool -tagsFromFile SRCFILE -@ xmp2exif.args DSTFILE +# +# Requires: ExifTool version 10.96 or later +# +# Revisions: 2009/01/20 - P. Harvey Created +# 2011/06/22 - PH Copy flash information via Composite:Flash +# 2013/06/12 - PH Additions for the Exif 2.3 for XMP spec +# 2015/01/12 - PH Avoid copying from non-standard namespaces +# 2016/09/26 - PH Write Composite SubSec tags +# 2018/05/07 - PH Added support for GPSDestXxxRef tags +# 2021/09/30 - PH Removed erroneous "-" when copying CreatorTool +# 2022/03/31 - PH IPTC Photometadata Mapping Guidelines 2202.1 update +# 2023/01/30 - PH Also write Composite:SubSecDateTimeOriginal from +# XMP-exif:DateTimeOriginal +# +# References: http://www.metadataworkinggroup.org/specs/ +# https://iptc.org/std/photometadata/documentation/mappingguidelines/ +# +# Notes: 1) The first three arguments copy the bulk of the EXIF and GPS +# information, and the remaining arguments handle the tags +# which have different names and/or formats in XMP and EXIF. +# +# 2) These arguments will not delete existing EXIF tags which are +# missing from the XMP. The EXIF tags should be deleted +# beforehand if required. +# +# 3) XMP-iptcExt:ImageRegion is not currently mapped into EXIF:SubjectArea +#------------------------------------------------------------------------------ +-EXIF:all < XMP-exif:all +-EXIF:all < XMP-exifEX:all +-EXIF:all < XMP-tiff:all +-EXIF:ImageDescription < XMP-dc:Description +-EXIF:DateTimeOriginal < XMP-photoshop:DateCreated +# the following SubSec tags also write/delete the corresponding EXIF +# SubSecTime and OffsetTime tags as appropriate +-Composite:SubSecDateTimeOriginal < XMP-exif:DateTimeOriginal +-Composite:SubSecDateTimeOriginal < XMP-photoshop:DateCreated +-Composite:SubSecCreateDate < XMP-xmp:CreateDate +-Composite:SubSecModifyDate < XMP-xmp:ModifyDate +-EXIF:Software < XMP-xmp:CreatorTool +-EXIF:Copyright < XMP-dc:Rights +-EXIF:Artist < XMP-plus:ImageCreatorName +-EXIF:Artist < XMP-dc:Creator +-EXIF:DigImageGUID < XMP-iptcExt:DigitalImageGUID +# XMP flash information is translated by the Composite Flash tag +-EXIF:Flash < Composite:Flash +# generate GPS tags which have been combined into other XMP tags +-GPS:GPSLatitudeRef < Composite:GPSLatitudeRef +-GPS:GPSLongitudeRef < Composite:GPSLongitudeRef +-GPS:GPSDestLatitudeRef < Composite:GPSDestLatitudeRef +-GPS:GPSDestLongitudeRef < Composite:GPSDestLongitudeRef +-GPS:GPSDatestamp < XMP-exif:GPSDateTime +-GPS:GPSTimestamp < XMP-exif:GPSDateTime +# end diff --git a/ExifTool/arg_files/xmp2gps.args b/ExifTool/arg_files/xmp2gps.args new file mode 100644 index 0000000..0e978f3 --- /dev/null +++ b/ExifTool/arg_files/xmp2gps.args @@ -0,0 +1,31 @@ +#------------------------------------------------------------------------------ +# File: xmp2gps.args +# +# Description: Argument file for copying GPS information from XMP to EXIF +# +# Usage: exiftool -tagsFromFile SRCFILE -@ xmp2gps.args DSTFILE +# +# Requires: ExifTool version 10.96 or later +# +# Revisions: 2009/01/09 - P. Harvey Created +# 2018/05/07 - PH Handle GPSDestLatitude/LongitudeRef tags +# +# Notes: 1) Most of the GPS tags are copied by the first argument, but +# the coordinate references and date/time values are stored +# separately in EXIF, so they must be handled separately. +# A bit of magic is employed by ExifTool to extract the date +# and time parts respectively when writing date-only and +# time-only tags with a date/time value. +# +# 2) These arguments will not delete existing GPS tags which are +# missing from the XMP. The GPS tags should be deleted with +# "-gps:all=" first if required. +#------------------------------------------------------------------------------ +-GPS:all < XMP-exif:all +-GPS:GPSLatitudeRef < Composite:GPSLatitudeRef +-GPS:GPSLongitudeRef < Composite:GPSLongitudeRef +-GPS:GPSDestLatitudeRef < Composite:GPSDestLatitudeRef +-GPS:GPSDestLongitudeRef < Composite:GPSDestLongitudeRef +-GPS:GPSDateStamp < XMP-exif:GPSDateTime +-GPS:GPSTimeStamp < XMP-exif:GPSDateTime +# end diff --git a/ExifTool/arg_files/xmp2iptc.args b/ExifTool/arg_files/xmp2iptc.args new file mode 100644 index 0000000..3d87317 --- /dev/null +++ b/ExifTool/arg_files/xmp2iptc.args @@ -0,0 +1,66 @@ +#------------------------------------------------------------------------------ +# File: xmp2iptc.args +# +# Description: Tag name translations for converting from XMP to IPTC +# +# Usage: exiftool -tagsFromFile SRCFILE -@ xmp2iptc.args DSTFILE +# +# Requires: ExifTool version 7.45 or later +# +# Revisions: 2005/05/14 - P. Harvey Created +# 2008/03/04 - PH Changed Location translation and added +# IntellectualGenre and SubjectCode +# 2008/09/30 - PH Added writing of Photoshop:IPTCDigest +# 2009/01/20 - PH Updated to conform with MWG spec +# 2009/10/21 - PH Write IPTC:DigitalCreationDate/Time tags +# +# References: http://www.iptc.org/IPTC4XMP/ +# http://www.iptc.org/IIM/ +# http://www.adobe.com/products/xmp/pdfs/xmpspec.pdf +# http://www.metadataworkinggroup.org/specs/ +# +# Notes: 1) Also updates Photoshop:IPTCDigest as per MWG recommendation. +# +# 2) For special characters in the XMP to be preserved, the IPTC +# encoding must be UTF-8: Either IPTC:CodedCharacterSet must +# already be "UTF8", or it must be set to "UTF8" when copying the +# XMP. This is not done automatically by this argfile because it +# could invalidate the encoding of existing IPTC if there were +# any values containing special characters. See FAQ number 10 +# for more information about converting the IPTC encoding: +# https://exiftool.org/faq.html#Q10 +# +# 3) These arguments will not delete existing IPTC tags which are +# missing from the XMP. The IPTC tags should be deleted +# beforehand if required. +#------------------------------------------------------------------------------ +-IPTC:By-line < XMP-dc:Creator +-IPTC:Caption-Abstract < XMP-dc:Description +-IPTC:CopyrightNotice < XMP-dc:Rights +-IPTC:Keywords < XMP-dc:Subject +-IPTC:ObjectName < XMP-dc:Title +-IPTC:By-lineTitle < XMP-photoshop:AuthorsPosition +-IPTC:Writer-Editor < XMP-photoshop:CaptionWriter +-IPTC:Category < XMP-photoshop:Category +-IPTC:City < XMP-photoshop:City +-IPTC:Country-PrimaryLocationName < XMP-photoshop:Country +-IPTC:Credit < XMP-photoshop:Credit +-IPTC:DateCreated < XMP-photoshop:DateCreated +# magically extracts time from a date/time value +-IPTC:TimeCreated < XMP-photoshop:DateCreated +-IPTC:Headline < XMP-photoshop:Headline +-IPTC:SpecialInstructions < XMP-photoshop:Instructions +-IPTC:Source < XMP-photoshop:Source +-IPTC:Province-State < XMP-photoshop:State +-IPTC:SupplementalCategories < XMP-photoshop:SupplementalCategories +-IPTC:OriginalTransmissionReference < XMP-photoshop:TransmissionReference +-IPTC:Urgency < XMP-photoshop:Urgency +-IPTC:Country-PrimaryLocationCode < XMP-iptcCore:CountryCode +-IPTC:Sub-location < XMP-iptcCore:Location +-IPTC:DigitalCreationDate < XMP-xmp:CreateDate +-IPTC:DigitalCreationTime < XMP-xmp:CreateDate +# these tags are not free-form text, and may need special handling: +#-IPTC:ObjectAttributeReference < XMP-iptcCore:IntellectualGenre +#-IPTC:SubjectReference < XMP-iptcCore:SubjectCode +-Photoshop:IPTCDigest=new +# end diff --git a/ExifTool/arg_files/xmp2pdf.args b/ExifTool/arg_files/xmp2pdf.args new file mode 100644 index 0000000..b7f8ab8 --- /dev/null +++ b/ExifTool/arg_files/xmp2pdf.args @@ -0,0 +1,27 @@ +#------------------------------------------------------------------------------ +# File: xmp2pdf.args +# +# Description: Tag name translations for converting from XMP to PDF DocInfo +# +# Usage: exiftool -tagsFromFile SRCFILE -@ xmp2pdf.args DSTFILE +# +# Requires: ExifTool version 7.07 or later +# +# Revisions: 2011/01/23 - P. Harvey Created +# +# References: http://www.adobe.com/devnet/xmp/ +# +# Notes: These arguments will not delete existing PDF tags which are +# missing from the XMP. The PDF tags should be deleted +# beforehand if required. +#------------------------------------------------------------------------------ +-PDF:Title < XMP-dc:Title +-PDF:Author < XMP-dc:Creator +-PDF:Subject < XMP-dc:Description +-PDF:Keywords < XMP-pdf:Keywords +-PDF:Creator < XMP-xmp:CreatorTool +-PDF:Producer < XMP-pdf:Producer +-PDF:CreateDate < XMP-xmp:CreateDate +-PDF:ModifyDate < XMP-xmp:ModifyDate +-PDF:Trapped < XMP-pdf:Trapped +# end diff --git a/ExifTool/config_files/acdsee.config b/ExifTool/config_files/acdsee.config new file mode 100644 index 0000000..265ee41 --- /dev/null +++ b/ExifTool/config_files/acdsee.config @@ -0,0 +1,259 @@ +#------------------------------------------------------------------------------ +# File: acdsee.config +# +# Description: This config file defines ACDSee XMP region tags for writing. +# The following tags are created in the XMP-acdsee-rs group +# +# RegionInfoACDSee : The structured tag for the ACDSee regions +# (similar to XMP-mwg-rs:RegionInfo) +# +# The following tags are the width, height, and unit of the +# image at the time of processing when storing image region +# metadata. They are similar to the AppliedToDimensions tags +# of the MWG regions. +# ACDSeeRegionAppliedToDimensionsH : Height of the image +# ACDSeeRegionAppliedToDimensionsUnit : Unit of the image +# ACDSeeRegionAppliedToDimensionsW : Width of the image +# +# Actual region data, stored in an array. These flattened tags +# are treated as List Type tags. There are two region types, +# the ALYArea and the DLYArea. The ALYArea tags tags assigned +# by ACDSee and are usually square in dimensions. The DLYArea +# tags are both the tags assigned by ACDSee (but possibly +# rectangular instead of square) as well as any manual +# assigned tags. They are similar to the area tags of the MWG +# regions. +# ACDSeeRegionDLYAreaH : Height of DLY region +# ACDSeeRegionDLYAreaW : Width of DLY region +# ACDSeeRegionDLYAreaX : X centerpoint of DLY region +# ACDSeeRegionDLYAreaY : Y centerpoint of DLY region +# ACDSeeRegionALYAreaH : Height of ALY region +# ACDSeeRegionALYAreaW : Width of ALY region +# ACDSeeRegionALYAreaX : X centerpoint of ALY region +# ACDSeeRegionALYAreaY : Y centerpoint of ALY region +# ACDSeeRegionName : Name of region +# ACDSeeRegionType : Type of region +# ACDSeeRegionNameAssignType : How the type was assigned. +# "Manual" is the only known +# entry at this time +# +# Conversion tags. These tags can be used to convert other region +# type tags to ACDSee regions. +# MPRegion2ACDSeeRegion : Converts a Microsoft RegionInfoMP +# IPTCRegion2ACDSeeRegion : Converts an IPTC ImageRegion +# MWGRegion2ACDSeeRegion : Converts a MWG RegionInfo +# +# Usage: To set individual tags +# exiftool -config acdsee.config -ACDSEETAG=VALUE FILE ... +# +# To convert Microsoft Regions to ACDSee regions +# exiftool -config acdsee.config "-RegionInfoACDSee "ACDSeeRegionAppliedToDimensions*" +# "RegionInfoACDSeeRegionList* -> "ACDSeeRegion*" +# 2022/10/03 - PH Set group name properly to XMP-acdsee-rs +#------------------------------------------------------------------------------ +use Data::Dumper; + +my %sACDSeeDimensions = ( + STRUCT_NAME => 'ACDSee Dimensions', + NAMESPACE => {'acdsee-stDim' => 'http://ns.acdsee.com/sType/Dimensions#'}, + 'w' => { Writable => 'real' }, + 'h' => { Writable => 'real' }, + 'unit' => { }, +); + +my %sACDSeeArea = ( + STRUCT_NAME => 'ACDSee Area', + NAMESPACE => { 'acdsee-stArea' => 'http://ns.acdsee.com/sType/Area#' }, + 'x' => { Writable => 'real' }, + 'y' => { Writable => 'real' }, + w => { Writable => 'real' }, + h => { Writable => 'real' }, +); + +my %sACDSeeRegionStruct = ( + STRUCT_NAME => 'ACDSee Regions', + NAMESPACE => { 'acdsee-rs' => 'http://ns.acdsee.com/regions/' }, + ALGArea => { Struct => \%sACDSeeArea }, + DLYArea => { Struct => \%sACDSeeArea }, + Name => { }, + NameAssignType => { }, + Type => { }, +); + +%Image::ExifTool::UserDefined = ( + # new XMP namespaces for ACDSee regions + 'Image::ExifTool::XMP::Main' => { + 'acdsee-rs' => { # <-- must be the same as the NAMESPACE prefix + SubDirectory => { + TagTable => 'Image::ExifTool::UserDefined::ACDSeeRegions' + }, + }, + }, + 'Image::ExifTool::Composite' => { + # create an ACDSee RegionInfo structure from a Microsoft RegionInfoMP structure + MPRegion2ACDSeeRegion => { + Require => { + 0 => 'RegionInfoMP', + 1 => 'ImageWidth', + 2 => 'ImageHeight', + }, + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{Regions}}) { + my $name = $$rgn{PersonDisplayName}; + next unless $$rgn{Rectangle} or defined $name; + my %newRgn = ( Type => 'Face' ); + if (defined $name) { + # don't add ignored faces + next if $name eq 'ffffffffffffffff'; + $newRgn{Name} = $name; + } + if ($$rgn{Rectangle}) { + my @rect = split /\s*,\s*/, $$rgn{Rectangle}; + $newRgn{DLYArea} = { + X => $rect[0] + $rect[2]/2, + Y => $rect[1] + $rect[3]/2, + W => $rect[2], + H => $rect[3], + } if @rect == 4; + } + push @newRgns, \%newRgn; + } + return { + AppliedToDimensions => { W => $val[1], H => $val[2], Unit => 'pixel' }, + RegionList => \@newRgns, + }; + }, + }, + # create an ACDSee RegionInfo structure from an IPTC ImageRegion list + IPTCRegion2ACDSeeRegion => { + Require => { + 0 => 'ImageRegion', + 1 => 'ImageWidth', + 2 => 'ImageHeight', + }, + ValueConv => q{ + my ($rgn, @newRgns); + my $rgns = ref $val[0] eq 'ARRAY' ? $val[0] : [ $val[0] ]; + foreach $rgn (@$rgns) { + my %newRgn = ( Type => 'Face' ); + if ($$rgn{RegionBoundary} and $$rgn{RegionBoundary}{RbShape} eq 'rectangle') { + my @rect = @{$$rgn{RegionBoundary}}{'RbX','RbY','RbW','RbH'}; + if ($$rgn{RegionBoundary}{RbUnit} eq 'pixel') { + $rect[0] /= $val[1], $rect[2] /= $val[1]; + $rect[1] /= $val[2]; $rect[3] /= $val[2]; + } + $newRgn{'DLYArea'} = { + X => $rect[0] + $rect[2]/2, + Y => $rect[1] + $rect[3]/2, + W => $rect[2], + H => $rect[3], + }; + } else { + next unless defined $$rgn{Name}; + } + $newRgn{Name} = $$rgn{Name} if defined $$rgn{Name}; + push @newRgns, \%newRgn; + } + return { + AppliedToDimensions => { 'W' => $val[1], 'H' => $val[2], 'Unit' => 'pixel' }, + RegionList => \@newRgns, + }; + }, + }, + + # create an MWG RegionInfo structure from an IPTC ImageRegion list + MWGRegion2ACDSeeRegion => { + Require => { + 0 => 'RegionInfo', + 1 => 'ImageWidth', + 2 => 'ImageHeight', + }, + ValueConv => q{ + my ($rgn, @newRgns); + my %newRgn; + foreach $rgn (@{$val[0]{RegionList}}) { + next unless $$rgn{Area} or defined $$rgn{Name}; + my %newRgn; + if ($$rgn{Area}) { + $newRgn{'DLYArea'} = { + 'X' => $$rgn{Area}{'X'}, + 'Y' => $$rgn{Area}{'Y'}, + 'W' => $$rgn{Area}{'W'}, + 'H' => $$rgn{Area}{'H'}, + }; + }; + $newRgn{Name} = $$rgn{Name} if defined $$rgn{Name}; + $newRgn{'Type'} = $$rgn{'Type'} if defined $$rgn{'Type'}; + push @newRgns, \%newRgn; + } + return { + 'AppliedToDimensions' => $val[0]{'AppliedToDimensions'}, + RegionList => \@newRgns, + } + }, + }, + #### + }, +); + +%Image::ExifTool::UserDefined::ACDSeeRegions = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-acdsee-rs', 2 => 'Image' }, + NAMESPACE => { 'acdsee-rs' => 'http://ns.acdsee.com/regions/' }, + WRITABLE => 'string', # (default to string-type tags) + Regions => { + Name => 'RegionInfoACDSee', + FlatName => 'ACDSee', + # the "Struct" entry defines the structure fields + Struct => { + # optional structure name (used for warning messages only) + STRUCT_NAME => 'ACDSee RegionInfo', + RegionList => { + FlatName => 'Region', + Struct => \%sACDSeeRegionStruct, + List => 'Bag', + }, + AppliedToDimensions => { + FlatName => 'RegionAppliedToDimensions',Struct => \%sACDSeeDimensions }, + }, + }, +); + +# Shortcuts to old names added so as not to break previously used commands +%Image::ExifTool::UserDefined::Shortcuts = ( + RegionInfoACDSeeAppliedToDimensionsH => 'ACDSeeRegionAppliedToDimensionsH', + RegionInfoACDSeeAppliedToDimensionsUnit => 'ACDSeeRegionAppliedToDimensionsUnit', + RegionInfoACDSeeAppliedToDimensionsW => 'ACDSeeRegionAppliedToDimensionsW', + RegionInfoACDSeeRegionListDLYAreaH => 'ACDSeeRegionDLYAreaH', + RegionInfoACDSeeRegionListDLYAreaW => 'ACDSeeRegionDLYAreaW', + RegionInfoACDSeeRegionListDLYAreaX => 'ACDSeeRegionDLYAreaX', + RegionInfoACDSeeRegionListDLYAreaY => 'ACDSeeRegionDLYAreaY', + RegionInfoACDSeeRegionListALGAreaH => 'ACDSeeRegionALGAreaH', + RegionInfoACDSeeRegionListALGAreaW => 'ACDSeeRegionALGAreaW', + RegionInfoACDSeeRegionListALGAreaX => 'ACDSeeRegionALGAreaX', + RegionInfoACDSeeRegionListALGAreaY => 'ACDSeeRegionALGAreaY', + RegionInfoACDSeeRegionListName => 'ACDSeeRegionName', + RegionInfoACDSeeRegionListType => 'ACDSeeRegionType', + RegionInfoACDSeeRegionListNameAssignType => 'ACDSeeRegionNameAssignType', +); + +# Forced -struct option during debugging +#%Image::ExifTool::UserDefined::Options = ( +# Struct => 1, +#); +#------------------------------------------------------------------------------ +1; #end \ No newline at end of file diff --git a/ExifTool/config_files/age.config b/ExifTool/config_files/age.config new file mode 100644 index 0000000..879f894 --- /dev/null +++ b/ExifTool/config_files/age.config @@ -0,0 +1,84 @@ +#------------------------------------------------------------------------------ +# File: age.config +# +# Description: Definition for a Composite "Age" tag to calculate age of person +# in image based on their birthday and the DateTimeOriginal or +# CreateDate of the image. +# +# Notes: The birthday date is set via the -userParam option, and is a +# string containing year, month, day, hour, minute and second. +# Only the year is required. By default the format of the +# returned Age value is exactly the same as the input birthday, +# with 2 digits for each parameter, however an AgeFormat user +# parameter may be used to define a printf-style format string +# for any desired output format (see fourth example below). +# +# If the specified birthday comes after the image date/time +# then the returned result is enclosed in brackets to indicate +# a negative time difference (see last example below). +# +# If birthday is not given, then the current date/time is assumed +# (in which case Age returns the age of the photo). +# +# Examples: (based on a DateTimeOriginal of "2005:08:27 18:21:00") +# +# > exiftool -config age.config -userparam birthday=1990:09:31 -age a.jpg +# Age : 14:10:26 +# +# > exiftool -config age.config -userparam birthday=1990Y09M31D -age a.jpg +# Age : 14Y10M26D +# +# > exiftool -config age.config -userparam birthday="2000:01:02 03:45" -age a.jpg +# Age : 05:07:25 14:36 +# +# > exiftool -config age.config -userparam birthday=2000:01:02 \ +# -userparam ageformat="%d years" -age a.jpg +# Age : 5 years +# +# > exiftool -config age.config -userparam birthday=2005:09:26 -age a.jpg +# Age : (00:00:30) +# +# Requires: ExifTool version 9.90 or later +# +# Revisions: 2017/01/24 - P. Harvey Created +#------------------------------------------------------------------------------ + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::Composite' => { + Age => { + Desire => { + 0 => 'DateTimeOriginal', + 1 => 'CreateDate', + }, + ValueConv => q{ + my $bday = $self->Options(UserParam => 'Birthday'); + $bday or ($bday = TimeNow()) =~ s/[-+].*//; + my $date = $val[0] || $val[1] or return undef; + my @t0 = ($bday =~ /\d+/g); + my @t1 = ($date =~ /\d+/g); + return '' unless $t0[0]; + my ($i, @diff, $sign); + for ($i=0; $i<6; ++$i) { + last unless defined $t0[$i]; + my $dt = $t1[$i] - $t0[$i]; + $sign or $sign = ($dt <=> 0); + push @diff, $dt * $sign; + } + require 'Image/ExifTool/Shift.pl'; + my $base = $sign < 0 ? \@t1 : \@t0; + my @wrap = (0, 12, DaysInMonth($$base[1]||1,$$base[0]), 24, 60, 60); + for ($i=$#diff; $i>0; --$i) { + $diff[$i] < 0 and $diff[$i] += $wrap[$i], --$diff[$i-1]; + } + $bday =~ s/\d+/sprintf '%.2d', shift @diff/ge; + return $sign < 0 ? "($bday)" : $bday; + }, + PrintConv => q{ + my $fmt = $self->Options(UserParam => 'AgeFormat') or return $val; + sprintf($fmt, $val =~ /\d+/g); + }, + }, + }, +); + +1; #end diff --git a/ExifTool/config_files/bibble.config b/ExifTool/config_files/bibble.config new file mode 100644 index 0000000..063f5bc --- /dev/null +++ b/ExifTool/config_files/bibble.config @@ -0,0 +1,520 @@ +#------------------------------------------------------------------------------ +# File: bibble.config +# +# Description: This config file defines Bibble XMP tags for writing. +# +# Usage: exiftool -config bibble.config -BIBBLETAG=VALUE FILE ... +# +# Requires: ExifTool version 10.28 or later +# +# Revisions: 2016/09/25 - P. Harvey Created +#------------------------------------------------------------------------------ + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::XMP::Main' => { + dmf => { + SubDirectory => { TagTable => 'Image::ExifTool::UserDefined::dmf' }, + }, + }, +); + +%Image::ExifTool::UserDefined::dmf = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-dmf', 2 => 'Image' }, + NAMESPACE => { 'dmf' => 'http://www.bibblelabs.com/DigitalMasterFile/1.0/' }, + WRITABLE => 'string', + versionCount => { + Name => 'DMFCount', + Writable => 'integer', + }, + versions => { + Name => 'DMF', + List => 'Seq', + Struct => { + NAMESPACE => { 'dmfversion' => 'http://www.bibblelabs.com/DigitalMasterFileVersion/1.0/' }, + software => { }, + softwareVersion => { }, + versionName => { }, + settings => { + Struct => { + NAMESPACE => { 'bset' => 'http://www.bibblelabs.com/BibbleSettings/5.0/' }, + settingsVersion => { }, + respectsTransform => { }, + curLayer => { }, + layers => { + List => 'Seq', + Struct => { + NAMESPACE => { 'blay' => 'http://www.bibblelabs.com/BibbleLayers/5.0/' }, + layerId => { }, + layerPos => { }, + name => { }, + enabled => { }, + options => { + Struct => { + NAMESPACE => { 'bopt' => 'http://www.bibblelabs.com/BibbleOpt/5.0/' }, + 'optionchanged' => { }, + 'hasSettings' => { }, + 'metaDirty' => { }, + 'version' => { }, + 'colormode' => { }, + 'colormode' => { }, + 'workingspace' => { }, + 'whiteauto' => { }, + 'whitetype' => { }, + 'lwhitetype' => { }, + 'lmr' => { }, + 'lmb' => { }, + 'whitered' => { }, + 'whitegreen' => { }, + 'whiteblue' => { }, + 'whiteowbr' => { }, + 'whiteowbb' => { }, + 'autolevel' => { }, + 'shadowval' => { }, + 'highlightval' => { }, + 'exposureval' => { }, + 'highlightrecval' => { }, + 'highlightrange' => { }, + 'blackPoint' => { }, + 'fillamount' => { }, + 'fillrange' => { }, + 'autorotate' => { }, + 'rotateangle' => { }, + 'sharpensense' => { }, + 'newsharpen' => { }, + 'sharpenon' => { }, + 'pcenabled' => { }, + 'pctintmeta' => { }, + 'PCNOn' => { }, + 'PCNPreset' => { }, + 'PCNStrength' => { }, + 'PCNDetail' => { }, + 'red' => { }, + 'green' => { }, + 'blue' => { }, + 'sat' => { }, + 'cont' => { }, + 'hue' => { }, + 'vibe' => { }, + 'useHSL' => { }, + 'useHSV' => { }, + 'invertRegions' => { }, + 'layerOpacity' => { }, + 'selective_color' => { }, + 'curveson' => { }, + 'curves_m_cn' => { }, + 'curves_m_cy' => { }, + 'curves_m_cx' => { }, + 'curves_m_imid' => { }, + 'curves_m_olo' => { }, + 'curves_m_ilo' => { }, + 'curves_m_ihi' => { }, + 'curves_m_ohi' => { }, + 'kelvin' => { }, + 'tint' => { }, + 'okelvin' => { }, + 'otint' => { }, + 'lkelvin' => { }, + 'ltint' => { }, + 'ckelvin' => { }, + 'ctint' => { }, + 'cropon' => { }, + 'croplocked' => { }, + 'cropstyle' => { }, + 'cropleft' => { }, + 'croptop' => { }, + 'cropheight' => { }, + 'cropwidth' => { }, + 'cropdpi' => { }, + 'cropstickydpi' => { }, + 'cropstickyx' => { }, + 'cropstickyy' => { }, + 'cropmenuitem' => { }, + 'croppercent' => { }, + 'Date' => { }, + 'DigitizedDateTime' => { }, + 'title' => { }, + 'Priority' => { }, + 'Category' => { }, + 'SupplementalCategory' => { }, + 'creator' => { }, + 'AuthorsPosition' => { }, + 'rights' => { }, + 'Credit' => { }, + 'description' => { }, + 'CaptionWriter' => { }, + 'City' => { }, + 'State' => { }, + 'Country' => { }, + 'CountryCode' => { }, + 'Headline' => { }, + 'Instructions' => { }, + 'TransmissionReference' => { }, + 'Source' => { }, + 'subject' => { }, + 'IntellectualGenre' => { }, + 'CiAdrCity' => { }, + 'CiAdrCtry' => { }, + 'CiAdrExtadr' => { }, + 'CiAdrPcode' => { }, + 'CiAdrRegion' => { }, + 'CiEmailWork' => { }, + 'CiTelWork' => { }, + 'CiUrlWork' => { }, + 'UsageTerms' => { }, + 'Location' => { }, + 'SubjectCode' => { }, + 'Scene' => { }, + 'tag' => { }, + 'keywordlist' => { }, + 'rating' => { }, + 'label' => { }, + 'warpon' => { }, + 'warpa' => { }, + 'warpb' => { }, + 'warpc' => { }, + 'warpresize' => { }, + 'warpfocal' => { }, + 'warpcaon' => { }, + 'warpcarc' => { }, + 'warpcaby' => { }, + 'vignetteon' => { }, + 'vignetterad' => { }, + 'vignetteamt' => { }, + 'profilemake' => { }, + 'profilemodel' => { }, + 'profilelens' => { }, + 'lens' => { }, + 'GPSVersionID' => { }, + 'GPSLatitude' => { }, + 'GPSLongitude' => { }, + 'GPSAltitudeRef' => { }, + 'GPSAltitude' => { }, + 'GPSTimeStamp' => { }, + 'GPSSatellites' => { }, + 'GPSStatus' => { }, + 'GPSMeasureMode' => { }, + 'GPSDateStamp' => { }, + 'GPSMapDatum' => { }, + 'GPSAreaInformation' => { }, + 'GPSProcessingMethod' => { }, + 'bMirrorOn' => { }, + 'bInvertOn' => { }, + 'rn_enabled' => { }, + 'rn_smooth_enabled' => { }, + 'rn_threshold' => { }, + 'rn_limit' => { }, + 'lc_enabled' => { }, + 'lc_radius' => { }, + 'lc_strength' => { }, + 'lc_debug' => { }, + 'enableInputProfile' => { }, + 'inputProfile' => { }, + 'redeyeSize' => { }, + 'redeyeTrack' => { }, + 'redeyeTrackStra' => { }, + 'redeyeTrackCrop' => { }, + 'redeyeEnable' => { }, + 'watermark_mode' => { }, + 'watermark_imageFilename' => { }, + 'watermark_text' => { }, + 'watermark_fontName' => { }, + 'watermark_fontColor' => { }, + 'watermark_fontItalic' => { }, + 'watermark_fontUnderline' => { }, + 'watermark_fontBold' => { }, + 'watermark_fontStrikeout' => { }, + 'watermark_alignment' => { }, + 'watermark_basesize' => { }, + 'watermark_rotation' => { }, + 'watermark_size' => { }, + 'watermark_hpos' => { }, + 'watermark_vpos' => { }, + 'watermark_opacity' => { }, + 'watermark_hskew' => { }, + 'watermark_vskew' => { }, + 'watermark_hproject' => { }, + 'watermark_vproject' => { }, + 'watermark_hstretch' => { }, + 'watermark_vstretch' => { }, + 'watermark_enabled' => { }, + 'ferd.bez_enabled' => { }, + 'ferd.bez_Early' => { }, + 'ferd.bez_HCL' => { }, + 'ferd.bez_opacity' => { }, + 'ferd.bez_saturation' => { }, + 'ferd.bez_lighten' => { }, + 'ferd.bez_lightengamma' => { }, + 'ferd.bez_midi' => { }, + 'ferd.bez_sigcontrast' => { }, + 'ferd.bez_sigmiddle' => { }, + 'ferd.bez_shadows' => { }, + 'ferd.bez_lowmids' => { }, + 'ferd.bez_greys' => { }, + 'ferd.bez_highmids' => { }, + 'ferd.bez_highlights' => { }, + 'ferd.bez_shadows4' => { }, + 'ferd.bez_lowmids4' => { }, + 'ferd.bez_highmids4' => { }, + 'ferd.bez_highlights4' => { }, + 'ferd.ferd_bez_enabled' => { }, + 'ferd.ferd_bez_RGB' => { }, + 'ferd.ferd_bez_BW' => { }, + 'ferd.ferd_bez_lighten' => { }, + 'ferd.ferd_bez_lightengamma' => { }, + 'ferd.ferd_bez_midi' => { }, + 'ferd.ferd_bez_blak' => { }, + 'ferd.ferd_bez_sigcontrast' => { }, + 'ferd.ferd_bez_sigmiddle' => { }, + 'ferd.ferd_bez_contrast' => { }, + 'ferd.ferd_bez_centre' => { }, + 'ferd.ferd_bez_shadows' => { }, + 'ferd.ferd_bez_lowmids' => { }, + 'ferd.ferd_bez_greys' => { }, + 'ferd.ferd_bez_highmids' => { }, + 'ferd.ferd_bez_highlights' => { }, + 'ferd.ferd_bez_shadows4' => { }, + 'ferd.ferd_bez_lowmids4' => { }, + 'ferd.ferd_bez_highmids4' => { }, + 'ferd.ferd_bez_highlights4' => { }, + 'Grid_kb.thirds' => { }, + 'Grid_kb.goldenrat' => { }, + 'Grid_kb.goldenspir' => { }, + 'Grid_kb.squaregrid' => { }, + 'Grid_kb.spiralcenter' => { }, + 'Grid_kb.gridsize' => { }, + 'Grid_kb.gridxcenter' => { }, + 'Grid_kb.gridycenter' => { }, + 'Mix3.mix3_enabled' => { }, + 'Mix3.mix3_early' => { }, + 'Mix3.mix3_exposure' => { }, + 'Mix3.mix3_red' => { }, + 'Mix3.mix3_green' => { }, + 'Mix3.mix3_blue' => { }, + 'SiliconBonk.SiliconBonkOn' => { }, + 'SiliconBonk.SiliconBonkHighlights' => { }, + 'SiliconBonk.SiliconBonkH' => { }, + 'SiliconBonk.SiliconBonkL' => { }, + 'SiliconBonk.SiliconBonkContrast' => { }, + 'SiliconBonk.SiliconBonkMid' => { }, + 'SiliconBonk.SiliconBonkSat' => { }, + 'SiliconBonk.SiliconBonkC0L' => { }, + 'SiliconBonk.SiliconBonkC1L' => { }, + 'SiliconBonk.SiliconBonkC2L' => { }, + 'SiliconBonk.SiliconBonkC3L' => { }, + 'SiliconBonk.SiliconBonkC4L' => { }, + 'SiliconBonk.SiliconBonkC5L' => { }, + 'Vigne_kb.kbv_enabled' => { }, + 'Vigne_kb.kbv_size' => { }, + 'Vigne_kb.kbv_strength' => { }, + 'Vigne_kb.kbv_ccol' => { }, + 'Vigne_kb.kbv_falloff' => { }, + 'Vigne_kb.kbv_shape' => { }, + 'Vigne_kb.kbv_horzcenter' => { }, + 'Vigne_kb.kbv_vertcenter' => { }, + 'Vigne_kb.kbv_factor' => { }, + 'Vigne_kb.kbv_trans' => { }, + 'Vigne_kb.kbv_adjustcrop' => { }, + 'Vigne_kb.kbv_colenabled' => { }, + 'Vigne_kb.kbv_col' => { }, + 'Vigne_kb.kbv_preview' => { }, + 'Vigne_kb.kbv_desat' => { }, + 'Vigne_kb.kbv_desaturate' => { }, + 'WaveletDenoise2.bSphWaveletDenoiseon' => { }, + 'WaveletDenoise2.bSphWaveletDenoiseThresL' => { }, + 'WaveletDenoise2.bSphWaveletDenoiseLowL' => { }, + 'WaveletDenoise2.bSphWaveletDenoiseThresC' => { }, + 'WaveletDenoise2.bSphWaveletDenoiseLowC' => { }, + 'WaveletDenoise2.bSphWaveletDenoiseDePepper' => { }, + 'WaveletDenoise2.bSphWaveletDenoiseShowEdge' => { }, + 'WaveletSharpen2.bSphWaveletUsmon' => { }, + 'WaveletSharpen2.bSphWaveletUsmRadius' => { }, + 'WaveletSharpen2.bSphWaveletUsmAmount' => { }, + 'WaveletSharpen2.bSphWaveletUsmThreshold' => { }, + 'WaveletSharpen2.bSphWaveletUsmClarity' => { }, + 'WaveletSharpen2.bSphWaveletLLenable' => { }, + 'WaveletSharpen2.bSphWaveletLLStrength' => { }, + 'WaveletSharpen2.bSphWaveletLLiter' => { }, + 'WaveletSharpen2.bSphWaveletMCStrength' => { }, + 'WaveletSharpen2.bSphWaveleton' => { }, + 'WaveletSharpen2.bSphWaveletAmount' => { }, + 'WaveletSharpen2.bSphWaveletRadius' => { }, + 'WaveletSharpen2.bSphWaveletSharpenDePepper' => { }, + 'WaveletSharpen2.bSphWaveletEdge' => { }, + 'WaveletSharpen2.bSphWaveletSharpenClarity' => { }, + 'WaveletSharpen2.bSphWaveleton2' => { }, + 'WaveletSharpen2.bSphWaveletAmount2' => { }, + 'WaveletSharpen2.bSphWaveletRadius2' => { }, + 'WaveletSharpen2.bSphWaveletSharpenDePepper2' => { }, + 'WaveletSharpen2.bSphWaveletEdge2' => { }, + 'WaveletSharpen2.bSphWaveletSharpenClarity2' => { }, + 'zChannelMixerGroup.zcmEnable' => { }, + 'zChannelMixerGroup.zcmbw' => { }, + 'zChannelMixerGroup.zcmbwa' => { }, + 'zChannelMixerGroup.zcmlockbw' => { }, + 'zChannelMixerGroup.zcmlockcolor' => { }, + 'zChannelMixerGroup.zcmrr' => { }, + 'zChannelMixerGroup.zcmrg' => { }, + 'zChannelMixerGroup.zcmrb' => { }, + 'zChannelMixerGroup.zcmgr' => { }, + 'zChannelMixerGroup.zcmgg' => { }, + 'zChannelMixerGroup.zcmgb' => { }, + 'zChannelMixerGroup.zcmbr' => { }, + 'zChannelMixerGroup.zcmbg' => { }, + 'zChannelMixerGroup.zcmbb' => { }, + 'zFramePlug.zframeSize1' => { }, + 'zFramePlug.zframeColor1' => { }, + 'zFramePlug.zframeSize2' => { }, + 'zFramePlug.zframeColor2' => { }, + 'zFramePlug.zframeSize3' => { }, + 'zFramePlug.zframeColor3' => { }, + 'zFramePlug.zframeSize4' => { }, + 'zFramePlug.zframeColor4' => { }, + 'zFramePlug.zframeSize5' => { }, + 'zFramePlug.zframeColor5' => { }, + 'zFramePlug.zframeDummy' => { }, + 'zFramePlug.zframeEnable' => { }, + 'zFramePlug.zframeEnable1' => { }, + 'zFramePlug.zframeEnable2' => { }, + 'zFramePlug.zframeEnable3' => { }, + 'zFramePlug.zframeEnable4' => { }, + 'zFramePlug.zframeEnable5' => { }, + 'zFramePlug.zframeCalibration' => { }, + 'zFramePlug.zframeRatio1' => { }, + 'zFramePlug.zframeRatio2' => { }, + 'zFramePlug.zframeRatio3' => { }, + 'zFramePlug.zframeRatio4' => { }, + 'zFramePlug.zframeRatio5' => { }, + 'zFramePlug.xPos1' => { }, + 'zFramePlug.yPos1' => { }, + 'zFramePlug.xPos2' => { }, + 'zFramePlug.yPos2' => { }, + 'zFramePlug.xPos3' => { }, + 'zFramePlug.yPos3' => { }, + 'zFramePlug.xPos4' => { }, + 'zFramePlug.yPos4' => { }, + 'zFramePlug.xPos5' => { }, + 'zFramePlug.yPos5' => { }, + 'zFramePlug.relativeTo' => { }, + 'zFramePlug.place' => { }, + 'Zone_kb.kbz_enabled' => { }, + 'Zone_kb.kbz_highlight' => { }, + 'zPerspectorPlug.zpersEnable' => { }, + 'zPerspectorPlug.zpersRotate' => { }, + 'zPerspectorPlug.zpersGridEnable' => { }, + 'zPerspectorPlug.zpersGrid' => { }, + 'zPerspectorPlug.zpersTopLeft' => { }, + 'zPerspectorPlug.zpersTopRight' => { }, + 'zPerspectorPlug.zpersLeftTop' => { }, + 'zPerspectorPlug.zpersRightTop' => { }, + 'zPerspectorPlug.zpersBottomLeft' => { }, + 'zPerspectorPlug.zpersBottomRight' => { }, + 'zPerspectorPlug.zpersLeftBottom' => { }, + 'zPerspectorPlug.zpersRightBottom' => { }, + 'zPerspectorPlug.zpersmirror' => { }, + 'zPerspectorPlug.zpersMirrorOffset' => { }, + 'zPerspectorPlug.zpersCanvasResize' => { }, + 'zShadowPlug.direction' => { }, + 'zShadowPlug.zshadowEnable' => { }, + 'zShadowPlug.dummy' => { }, + 'zShadowPlug.size' => { }, + 'zShadowPlug.scale' => { }, + 'zShadowPlug.edge' => { }, + 'zShadowPlug.sizeBg' => { }, + 'zShadowPlug.color' => { }, + 'zShadowPlug.colorBg' => { }, + 'zShadowPlug.relativeTo' => { }, + 'zShadowPlug.xPos' => { }, + 'zShadowPlug.yPos' => { }, + 'zSoftenPlug.softenEnable' => { }, + 'zSoftenPlug.softenradius' => { }, + 'zSoftenPlug.softenorton' => { }, + 'zSoftenPlug.softencombo' => { }, + 'zSoftenPlug.softenVignette' => { }, + 'zSoftenPlug.softenProof' => { }, + 'zSoftenPlug.softenvigSize' => { }, + 'zSoftenPlug.softenvigShape' => { }, + 'zSoftenPlug.softenvigFeather' => { }, + 'zSoftenPlug.softenvigXpos' => { }, + 'zSoftenPlug.softenvigYpos' => { }, + 'zSoftenPlug.softenvigRotate' => { }, + 'zTextPlug.zTextOn' => { }, + 'zTextPlug.italic' => { }, + 'zTextPlug.bold' => { }, + 'zTextPlug.underline' => { }, + 'zTextPlug.strikeout' => { }, + 'zTextPlug.overline' => { }, + 'zTextPlug.size' => { }, + 'zTextPlug.xpos' => { }, + 'zTextPlug.ypos' => { }, + 'zTextPlug.rotate' => { }, + 'zTextPlug.font' => { }, + 'zTextPlug.text' => { }, + 'zTextPlug.opacity' => { }, + 'zTextPlug.stretch' => { }, + 'zTextPlug.color' => { }, + 'zTextPlug.shearv' => { }, + 'zTextPlug.shearh' => { }, + 'zTextPlug.scalev' => { }, + 'zTextPlug.scaleh' => { }, + 'zTextPlug.projectv' => { }, + 'zTextPlug.projecth' => { }, + 'zTextPlug.basesize' => { }, + 'zTextPlug.textalign' => { }, + 'zTextPlug.textposh' => { }, + 'zTextPlug.textposv' => { }, + 'zTextPlug.shadowEnable' => { }, + 'zTextPlug.shadowDirection' => { }, + 'zTextPlug.shadowShift' => { }, + 'zTextPlug.shadowIntensity' => { }, + 'zTextPlug.shadowColor' => { }, + 'blplug.bbwenabled' => { }, + 'blplug.bbwspotA' => { }, + 'blplug.bbwspotB' => { }, + 'blplug.bbwhueA' => { }, + 'blplug.bbwfuzzyA' => { }, + 'blplug.bbwcolorA' => { }, + 'blplug.bbwhueB' => { }, + 'blplug.bbwcolorB' => { }, + 'blplug.bbwfuzzyB' => { }, + 'blplug.bbwmode' => { }, + 'Equalizer_kb.kbs_enabled' => { }, + 'Equalizer_kb.kbs_bw' => { }, + 'Equalizer_kb.kbs_redlum' => { }, + 'Equalizer_kb.kbs_yellowlum' => { }, + 'Equalizer_kb.kbs_orangelum' => { }, + 'Equalizer_kb.kbs_greenlum' => { }, + 'Equalizer_kb.kbs_cyanlum' => { }, + 'Equalizer_kb.kbs_bluelum' => { }, + 'Equalizer_kb.kbs_magentalum' => { }, + 'Equalizer_kb.kbs_redhue' => { }, + 'Equalizer_kb.kbs_yellowhue' => { }, + 'Equalizer_kb.kbs_orangehue' => { }, + 'Equalizer_kb.kbs_greenhue' => { }, + 'Equalizer_kb.kbs_cyanhue' => { }, + 'Equalizer_kb.kbs_bluehue' => { }, + 'Equalizer_kb.kbs_magentahue' => { }, + 'Equalizer_kb.kbs_redsat' => { }, + 'Equalizer_kb.kbs_yellowsat' => { }, + 'Equalizer_kb.kbs_orangesat' => { }, + 'Equalizer_kb.kbs_greensat' => { }, + 'Equalizer_kb.kbs_cyansat' => { }, + 'Equalizer_kb.kbs_bluesat' => { }, + 'Equalizer_kb.kbs_magentasat' => { }, + 'Equalizer_kb.kbs_redvib' => { }, + 'Equalizer_kb.kbs_yellowvib' => { }, + 'Equalizer_kb.kbs_orangevib' => { }, + 'Equalizer_kb.kbs_greenvib' => { }, + 'Equalizer_kb.kbs_cyanvib' => { }, + 'Equalizer_kb.kbs_bluevib' => { }, + 'Equalizer_kb.kbs_magentavib' => { }, + }, + }, + }, + }, + }, + }, + }, + }, +); + +1; #end diff --git a/ExifTool/config_files/convert_regions.config b/ExifTool/config_files/convert_regions.config new file mode 100644 index 0000000..481b96f --- /dev/null +++ b/ExifTool/config_files/convert_regions.config @@ -0,0 +1,234 @@ +#------------------------------------------------------------------------------ +# File: convert_regions.config +# +# Description: User-defined Composite tag definitions to allow conversion of +# face regions between Microsoft Windows Live Photo Gallery (WLPG), +# Metadata Working Group (MWG), and IPTC Extension formats +# +# Usage: 1) Convert from MP WLPG or IPTC regions to MWG regions: +# +# exiftool -config convert_regions.config "-RegionInfo { + + # create an MWG RegionInfo structure from a Microsoft RegionInfoMP structure + MPRegion2MWGRegion => { + Require => { + 0 => 'RegionInfoMP', + 1 => 'ImageWidth', + 2 => 'ImageHeight', + }, + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{Regions}}) { + my $name = $$rgn{PersonDisplayName}; + next unless $$rgn{Rectangle} or defined $name; + my %newRgn = ( Type => 'Face' ); + if (defined $name) { + # don't add ignored faces + next if $name eq 'ffffffffffffffff'; + $newRgn{Name} = $name; + } + if ($$rgn{Rectangle}) { + my @rect = split /\s*,\s*/, $$rgn{Rectangle}; + $newRgn{Area} = { + X => $rect[0] + $rect[2]/2, + Y => $rect[1] + $rect[3]/2, + W => $rect[2], + H => $rect[3], + Unit => 'normalized', + } if @rect == 4; + } + push @newRgns, \%newRgn; + } + return { + AppliedToDimensions => { W => $val[1], H => $val[2], Unit => 'pixel' }, + RegionList => \@newRgns, + }; + }, + }, + + # create an MWG RegionInfo structure from an IPTC ImageRegion list + IPTCRegion2MWGRegion => { + Name => 'MPRegion2MWGRegion', + Require => { + 0 => 'ImageRegion', + 1 => 'ImageWidth', + 2 => 'ImageHeight', + }, + ValueConv => q{ + my ($rgn, @newRgns); + my $rgns = ref $val[0] eq 'ARRAY' ? $val[0] : [ $val[0] ]; + foreach $rgn (@$rgns) { + my %newRgn = ( Type => 'Face' ); + if ($$rgn{RegionBoundary} and $$rgn{RegionBoundary}{RbShape} eq 'rectangle') { + my @rect = @{$$rgn{RegionBoundary}}{'RbX','RbY','RbW','RbH'}; + if ($$rgn{RegionBoundary}{RbUnit} eq 'pixel') { + $rect[0] /= $val[1], $rect[2] /= $val[1]; + $rect[1] /= $val[2]; $rect[3] /= $val[2]; + } + $newRgn{Area} = { + X => $rect[0] + $rect[2]/2, + Y => $rect[1] + $rect[3]/2, + W => $rect[2], + H => $rect[3], + Unit => 'normalized', + }; + } else { + next unless defined $$rgn{Name}; + } + $newRgn{Name} = $$rgn{Name} if defined $$rgn{Name}; + push @newRgns, \%newRgn; + } + return { + AppliedToDimensions => { W => $val[1], H => $val[2], Unit => 'pixel' }, + RegionList => \@newRgns, + }; + }, + }, + + # create a Microsoft RegionInfoMP structure from an MWG RegionInfo structure + MWGRegion2MPRegion => { + Require => 'RegionInfo', + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{RegionList}}) { + next unless $$rgn{Area} or defined $$rgn{Name}; + my %newRgn; + if ($$rgn{Area}) { + my @rect = @{$$rgn{Area}}{'X','Y','W','H'}; + $rect[0] -= $rect[2]/2; + $rect[1] -= $rect[3]/2; + $newRgn{Rectangle} = join(', ', @rect); + } + $newRgn{PersonDisplayName} = $$rgn{Name} if defined $$rgn{Name}; + push @newRgns, \%newRgn; + } + return { Regions => \@newRgns }; + }, + }, + + # create a Microsoft RegionInfoMP structure from an IPTC ImageRegion list + IPTCRegion2MPRegion => { + Name => 'MWGRegion2MPRegion', + Require => { + 0 => 'ImageRegion', + 1 => 'ImageWidth', + 2 => 'ImageHeight', + }, + ValueConv => q{ + my ($rgn, @newRgns); + my $rgns = ref $val[0] eq 'ARRAY' ? $val[0] : [ $val[0] ]; + foreach $rgn (@$rgns) { + my %newRgn; + if ($$rgn{RegionBoundary} and $$rgn{RegionBoundary}{RbShape} eq 'rectangle') { + my @rect = @{$$rgn{RegionBoundary}}{'RbX','RbY','RbW','RbH'}; + if ($$rgn{RegionBoundary}{RbUnit} eq 'pixel') { + $rect[0] /= $val[1], $rect[2] /= $val[1]; + $rect[1] /= $val[2]; $rect[3] /= $val[2]; + } + $newRgn{Rectangle} = join(', ', @rect); + } else { + next unless defined $$rgn{Name}; + } + $newRgn{PersonDisplayName} = $$rgn{Name} if defined $$rgn{Name}; + push @newRgns, \%newRgn; + } + return { Regions => \@newRgns }; + }, + }, + + # create an IPTC ImageRegion list from an MWG RegionInfo structure + MWGRegion2IPTCRegion => { + Require => 'RegionInfo', + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{RegionList}}) { + next unless $$rgn{Area} or defined $$rgn{Name}; + my %newRgn; + if ($$rgn{Area}) { + my @rect = @{$$rgn{Area}}{'X','Y','W','H'}; + $rect[0] -= $rect[2]/2; + $rect[1] -= $rect[3]/2; + $newRgn{RegionBoundary} = { + RbShape => 'rectangle', + RbUnit => 'relative', + RbX => $rect[0], + RbY => $rect[1], + RbW => $rect[2], + RbH => $rect[3], + }; + } + $newRgn{Name} = $$rgn{Name} if defined $$rgn{Name}; + push @newRgns, \%newRgn; + } + return \@newRgns; + }, + }, + + # create an IPTC ImageRegion list from a Microsoft RegionInfoMP structure + MPRegion2IPTCRegion => { + Name => 'MWGRegion2IPTCRegion', + Require => 'RegionInfoMP', + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{Regions}}) { + my $name = $$rgn{PersonDisplayName}; + next unless $$rgn{Rectangle} or defined $name; + my %newRgn; + if (defined $name) { + # don't add ignored faces + next if $name eq 'ffffffffffffffff'; + $newRgn{Name} = $name; + } + if ($$rgn{Rectangle}) { + my @rect = split /\s*,\s*/, $$rgn{Rectangle}; + $newRgn{RegionBoundary} = { + RbShape => 'rectangle', + RbUnit => 'relative', + RbX => $rect[0], + RbY => $rect[1], + RbW => $rect[2], + RbH => $rect[3], + } if @rect == 4; + } + push @newRgns, \%newRgn; + } + return \@newRgns; + }, + }, + }, +); + +%Image::ExifTool::UserDefined::Shortcuts = ( + MyRegion => 'MPRegion2MWGRegion', + MyRegion2 => 'IPTCRegion2MWGRegion', + MyRegionMP => 'MWGRegion2MPRegion', + MyRegionMP2 => 'IPTCRegion2MPRegion', + MyRegionIPTC => 'MWGRegion2IPTCRegion', + MyRegionIPTC2 => 'MPRegion2IPTCRegion', +); + +1; #end diff --git a/ExifTool/config_files/cuepointlist.config b/ExifTool/config_files/cuepointlist.config new file mode 100644 index 0000000..21bc75c --- /dev/null +++ b/ExifTool/config_files/cuepointlist.config @@ -0,0 +1,70 @@ +#------------------------------------------------------------------------------ +# File: cuepointlist.config +# +# Description: ExifTool config file to generate list of cue points and labels +# in WAV audio files +# +# Usage: exiftool -config cuepointlist.config -cuepointlist -b FILE +# +# Requires: ExifTool version 12.25 or later +# +# Revisions: 2021-04-20 - P. Harvey Created +#------------------------------------------------------------------------------ + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::Composite' => { + CuePointList => { + Require => { + 0 => 'CuePoints', + 1 => 'SampleRate', + }, + Desire => { + 2 => 'CuePointLabel', + 3 => 'CuePointNotes', + 4 => 'LabeledText', + }, + ValueConv => q{ + SetByteOrder('II'); + my (%lbl, %rgn, %rtyp, %rlbl, %note, $pos, $i); + # get labels if available + for ($i=1; defined $val[2]; ++$i) { + $lbl{$1} = $2 if $val[2] =~ /^(\d+) (.*)/ and length $2; + $val[2] = $self->GetValue("CuePointLabel ($i)"); + } + # get notes if available + for ($i=1; defined $val[3]; ++$i) { + $note{$1} = $2 if $val[3] =~ /^(\d+) (.*)/ and length $2; + $val[3] = $self->GetValue("CuePointNotes ($i)"); + } + # get regions if available + for ($i=1; defined $val[4]; ++$i) { + if ($val[4] =~ /^(\d+) (\d+) '(.*)' \d+ \d+ \d+ \d+ (.*)/) { + $rgn{$1} = $2; + $rtyp{$1} = $3; + $rlbl{$1} = $4; + } + $val[4] = $self->GetValue("LabeledText ($i)"); + } + my $buff = "Cue\tStart\tEnd\tLabel\tPurpose\tText\tNotes\n"; + for ($pos=4; $pos+24<=length(${$val[0]}); $pos+=24) { + my $n = Get32u($val[0], $pos); + my $start = Get32u($val[0], $pos + 4); + my (@lbl, $lbl); + push @lbl, $lbl{$n} if defined $lbl{$n}; + push @lbl, $rlbl{$n} if defined $rlbl{$n}; + push @lbl, '-', $note{$n} if defined $note{$n}; + my $lbl = join ' ', @lbl; + $buff .= sprintf("%d\t%.3f\t%.3f\t%s\t%s\t%s\t%s\n", + $n, $start/$val[1], ($start+($rgn{$n}||0))/$val[1], + defined($lbl{$n}) ? $lbl{$n} : '', + defined($rtyp{$n}) ? $rtyp{$n} : '', + defined($rlbl{$n}) ? $rlbl{$n} : '', + defined($note{$n}) ? $note{$n} : ''); + } + return $buff; + }, + }, + }, +); + +1; # end \ No newline at end of file diff --git a/ExifTool/config_files/depthmap.config b/ExifTool/config_files/depthmap.config new file mode 100644 index 0000000..ec56fef --- /dev/null +++ b/ExifTool/config_files/depthmap.config @@ -0,0 +1,56 @@ +#------------------------------------------------------------------------------ +# File: depthmap.config +# +# Description: Composite tag definitions to extract DepthMap and ConfidenceMap +# images from Google Dynamic-depth images +# +# Usage: exiftool -config depthmap.config -W %d%f_%t.%s -depthmap -confidencemap -b DIR +# +# Requires: ExifTool version 11.88 or later +# +# Revisions: 2020/02/20 - P. Harvey Created +#------------------------------------------------------------------------------ + +sub GetTrailerImage($$) +{ + my ($val, $type) = @_; + my @uri = ref $$val[1] ? @{$$val[1]} : $$val[1]; + my @len = ref $$val[2] ? @{$$val[2]} : $$val[2]; + my $start = 0; + for (my $i=0; $i<@uri; ++$i) { + if ($uri[$i] =~ /$type/) { + my $img = substr(${$$val[0]}, $start, $len[$i]); + return \$img; + } + $start += $len[$i]; + } + return undef; +} + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::Composite' => { + DepthMap => { + Require => { + 0 => 'Trailer', + 1 => 'ContainerDirectoryItemDataURI', + 2 => 'ContainerDirectoryItemLength', + }, + ValueConv => 'GetTrailerImage(\@val, "depthmap")', + }, + ConfidenceMap => { + Require => { + 0 => 'Trailer', + 1 => 'ContainerDirectoryItemDataURI', + 2 => 'ContainerDirectoryItemLength', + }, + ValueConv => 'GetTrailerImage(\@val, "confidencemap")', + }, + }, +); + +%Image::ExifTool::UserDefined::Options = ( + RequestTags => 'Trailer', +); + +#------------------------------------------------------------------------------ +1; #end \ No newline at end of file diff --git a/ExifTool/config_files/example.config b/ExifTool/config_files/example.config new file mode 100644 index 0000000..006f87e --- /dev/null +++ b/ExifTool/config_files/example.config @@ -0,0 +1,358 @@ +#------------------------------------------------------------------------------ +# File: example.config --> ~/.ExifTool_config +# +# Description: Sample user configuration file for Image::ExifTool +# +# Notes: This example file shows how to define your own shortcuts and +# add new EXIF, IPTC, XMP, PNG, MIE and Composite tags, as well +# as how to specify preferred lenses for the LensID tag, and +# define new file types and default ExifTool option values. +# +# Note that unknown tags may be extracted even if they aren't +# defined, but tags must be defined to be written. Also note +# that it is possible to override an existing tag definition +# with a user-defined tag. +# +# To activate this file, rename it to ".ExifTool_config" and +# place it in your home directory or the exiftool application +# directory. (On Mac and some Windows systems this must be done +# via the command line since the GUI's may not allow filenames to +# begin with a dot. Use the "rename" command in Windows or "mv" +# on the Mac.) This causes ExifTool to automatically load the +# file when run. Your home directory is determined by the first +# defined of the following environment variables: +# +# 1. EXIFTOOL_HOME +# 2. HOME +# 3. HOMEDRIVE + HOMEPATH +# 4. (the current directory) +# +# Alternatively, the -config option of the exiftool application +# may be used to load a specific configuration file (note that +# this must be the first option on the command line): +# +# exiftool -config example.config ... +# +# This example file defines the following 16 new tags as well as +# a number of Shortcut and Composite tags: +# +# 1. EXIF:NewEXIFTag +# 2. GPS:GPSPitch +# 3. GPS:GPSRoll +# 4. IPTC:NewIPTCTag +# 5. XMP-xmp:NewXMPxmpTag +# 6. XMP-exif:GPSPitch +# 7. XMP-exif:GPSRoll +# 8. XMP-xxx:NewXMPxxxTag1 +# 9. XMP-xxx:NewXMPxxxTag2 +# 10. XMP-xxx:NewXMPxxxTag3 +# 11. XMP-xxx:NewXMPxxxStruct +# 12. PNG:NewPngTag1 +# 13. PNG:NewPngTag2 +# 14. PNG:NewPngTag3 +# 15. MIE-Meta:NewMieTag1 +# 16. MIE-Test:NewMieTag2 +# +# For detailed information on the definition of tag tables and +# tag information hashes, see lib/Image/ExifTool/README. +#------------------------------------------------------------------------------ + +# Shortcut tags are used when extracting information to simplify +# commonly used commands. They can be used to represent groups +# of tags, or to provide an alias for a tag name. +%Image::ExifTool::UserDefined::Shortcuts = ( + MyShortcut => ['exif:createdate','exposuretime','aperture'], + MyAlias => 'FocalLengthIn35mmFormat', +); + +# NOTE: All tag names used in the following tables are case sensitive. + +# The %Image::ExifTool::UserDefined hash defines new tags to be added +# to existing tables. +%Image::ExifTool::UserDefined = ( + # All EXIF tags are added to the Main table, and WriteGroup is used to + # specify where the tag is written (default is ExifIFD if not specified): + 'Image::ExifTool::Exif::Main' => { + # Example 1. EXIF:NewEXIFTag + 0xd000 => { + Name => 'NewEXIFTag', + Writable => 'int16u', + WriteGroup => 'IFD0', + }, + # add more user-defined EXIF tags here... + }, + # the Geotag feature writes these additional GPS tags if available: + 'Image::ExifTool::GPS::Main' => { + # Example 2. GPS:GPSPitch + 0xd000 => { + Name => 'GPSPitch', + Writable => 'rational64s', + }, + # Example 3. GPS:GPSRoll + 0xd001 => { + Name => 'GPSRoll', + Writable => 'rational64s', + }, + }, + # IPTC tags are added to a specific record type (eg. application record): + # (Note: IPTC tag ID's are limited to the range 0-255) + 'Image::ExifTool::IPTC::ApplicationRecord' => { + # Example 4. IPTC:NewIPTCTag + 160 => { + Name => 'NewIPTCTag', + Format => 'string[0,16]', + }, + # add more user-defined IPTC ApplicationRecord tags here... + }, + # XMP tags may be added to existing namespaces: + 'Image::ExifTool::XMP::xmp' => { + # Example 5. XMP-xmp:NewXMPxmpTag + NewXMPxmpTag => { Groups => { 2 => 'Author' } }, + # add more user-defined XMP-xmp tags here... + }, + # special Geotag tags for XMP-exif: + 'Image::ExifTool::XMP::exif' => { + # Example 6. XMP-exif:GPSPitch + GPSPitch => { Writable => 'rational', Groups => { 2 => 'Location' } }, + # Example 7. XMP-exif:GPSRoll + GPSRoll => { Writable => 'rational', Groups => { 2 => 'Location' } }, + }, + # new XMP namespaces (eg. xxx) must be added to the Main XMP table: + 'Image::ExifTool::XMP::Main' => { + # namespace definition for examples 8 to 11 + xxx => { # <-- must be the same as the NAMESPACE prefix + SubDirectory => { + TagTable => 'Image::ExifTool::UserDefined::xxx', + # (see the definition of this table below) + }, + }, + # add more user-defined XMP namespaces here... + }, + # new PNG tags are added to the PNG::TextualData table: + 'Image::ExifTool::PNG::TextualData' => { + # Example 12. PNG:NewPngTag1 + NewPngTag1 => { }, + # Example 13. PNG:NewPngTag2 + NewPngTag2 => { iTXt => 1 }, # (force this tag to be written as iTXt) + # Example 14. PNG:NewPngTag3 + NewPngTag3 => { }, + }, + # add a new MIE tag (NewMieTag1) and group (MIE-Test) to MIE-Meta + # (Note: MIE group names must NOT end with a number) + 'Image::ExifTool::MIE::Meta' => { + # Example 15. MIE-Meta:NewMieTag1 + NewMieTag1 => { + Writable => 'rational64u', + Units => [ 'cm', 'in' ], + }, + # new MIE "Test" group for example 16 + Test => { + SubDirectory => { + TagTable => 'Image::ExifTool::UserDefined::MIETest', + DirName => 'MIE-Test', + }, + }, + }, + # Composite tags are added to the Composite table: + 'Image::ExifTool::Composite' => { + # Composite tags have values that are derived from the values of + # other tags. The Require/Desire elements below specify constituent + # tags that must/may exist, and the keys of these hashes are used as + # indices in the @val array of the ValueConv expression to access the + # numerical (-n) values of these tags. Print-converted values are + # accessed via the @prt array. All Require'd tags must exist for + # the Composite tag to be evaluated. If no Require'd tags are + # specified, then at least one of the Desire'd tags must exist. See + # the Composite table in Image::ExifTool::Exif for more examples, + # and lib/Image/ExifTool/README for all of the details. + # The first few examples demonstrate simplifications which may be + # used if only one tag is Require'd or Desire'd: + # 1) the Require lookup may be replaced with a simple tag name + # 2) "$val" may be used to represent "$val[0]" in the expression + FileExtension => { + Require => 'FileName', + ValueConv => '$val=~/\.([^.]*)$/; $1', + }, + # override CircleOfConfusion tag to use D/1750 instead of D/1440 + CircleOfConfusion => { + Require => 'ScaleFactor35efl', + Groups => { 2 => 'Camera' }, + ValueConv => 'sqrt(24*24+36*36) / ($val * 1750)', + # an optional PrintConv may be used to format the value + PrintConv => 'sprintf("%.3f mm",$val)', + }, + # generate a description for this file type + FileTypeDescription => { + Require => 'FileType', + ValueConv => 'GetFileType($val,1) || $val', + }, + # calculate physical image size based on resolution + PhysicalImageSize => { + Require => { + 0 => 'ImageWidth', + 1 => 'ImageHeight', + 2 => 'XResolution', + 3 => 'YResolution', + 4 => 'ResolutionUnit', + }, + ValueConv => '$val[0]/$val[2] . " " . $val[1]/$val[3]', + # (the @prt array contains print-formatted values) + PrintConv => 'sprintf("%.1fx%.1f $prt[4]", split(" ",$val))', + }, + # [advanced] select largest JPEG preview image + BigImage => { + Groups => { 2 => 'Preview' }, + Desire => { + 0 => 'JpgFromRaw', + 1 => 'PreviewImage', + 2 => 'OtherImage', + # (DNG and A100 ARW may be have 2 PreviewImage's) + 3 => 'PreviewImage (1)', + # (if the MPF has 2 previews, MPImage3 could be the larger one) + 4 => 'MPImage3', + 5 => 'JpgFromRaw2', # in RW2 from newer Panasonic models + }, + # ValueConv may also be a code reference + # Inputs: 0) reference to list of values, 1) ExifTool object + ValueConv => sub { + my $val = shift; + my ($image, $bigImage, $len, $bigLen); + foreach $image (@$val) { + next unless ref $image eq 'SCALAR'; + # check for JPEG image (or "Binary data" if -b not used) + next unless $$image =~ /^(\xff\xd8\xff|Binary data (\d+))/; + $len = $2 || length $$image; # get image length + # save largest image + next if defined $bigLen and $bigLen >= $len; + $bigLen = $len; + $bigImage = $image; + } + return $bigImage; + }, + }, + # **** ADD ADDITIONAL COMPOSITE TAG DEFINITIONS HERE **** + }, +); + +# This is a basic example of the definition for a new XMP namespace. +# This table is referenced through a SubDirectory tag definition +# in the %Image::ExifTool::UserDefined definition above. +# The namespace prefix for these tags is 'xxx', which corresponds to +# an ExifTool family 1 group name of 'XMP-xxx'. +%Image::ExifTool::UserDefined::xxx = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-xxx', 2 => 'Image' }, + NAMESPACE => { 'xxx' => 'http://ns.myname.com/xxx/1.0/' }, + WRITABLE => 'string', # (default to string-type tags) + # Example 8. XMP-xxx:NewXMPxxxTag1 (an alternate-language tag) + # - replace "NewXMPxxxTag1" with your own tag name (eg. "MyTag") + NewXMPxxxTag1 => { Writable => 'lang-alt' }, + # Example 9. XMP-xxx:NewXMPxxxTag2 (a string tag in the Author category) + NewXMPxxxTag2 => { Groups => { 2 => 'Author' } }, + # Example 10. XMP-xxx:NewXMPxxxTag3 (an unordered List-type tag) + NewXMPxxxTag3 => { List => 'Bag' }, + # Example 11. XMP-xxx:NewXMPxxxStruct (a structured tag) + # - example structured XMP tag + NewXMPxxxStruct => { + # the "Struct" entry defines the structure fields + Struct => { + # optional namespace prefix and URI for structure fields + # (required only if different than NAMESPACE above) + # --> multiple entries may exist in this namespace lookup, + # with the last one alphabetically being the default for + # the fields, but each field may have a "Namespace" + # element to specify which prefix to use + NAMESPACE => { 'test' => 'http://x.y.z/test/' }, + # optional structure name (used for warning messages only) + STRUCT_NAME => 'MyStruct', + # optional rdf:type property for the structure + TYPE => 'http://x.y.z/test/xystruct', + # structure field definitions (very similar to tag definitions) + X => { Writable => 'integer' }, + Y => { Writable => 'integer' }, + # a nested structure... + Things => { + List => 'Bag', + Struct => { + NAMESPACE => { thing => 'http://x.y.z/thing/' }, + What => { }, + Where => { }, + }, + }, + }, + List => 'Seq', # structures may also be elements of a list + }, + # Each field in the structure has a corresponding flattened tag that is + # generated automatically with an ID made from a concatenation of the + # original structure tag ID and the field name (after capitalizing the + # first letter of the field name if necessary). The Name and/or + # Description of these flattened tags may be changed if desired, but all + # other tag properties are taken from the structure field definition. + # The "Flat" flag must be used when setting the Name or Description of a + # flattened tag. For example: + NewXMPxxxStructX => { Name => 'SomeOtherName', Flat => 1 }, +); + +# Adding a new MIE group requires a few extra definitions +use Image::ExifTool::MIE; +%Image::ExifTool::UserDefined::MIETest = ( + %Image::ExifTool::MIE::tableDefaults, # default MIE table entries + GROUPS => { 0 => 'MIE', 1 => 'MIE-Test', 2 => 'Document' }, + WRITE_GROUP => 'MIE-Test', + # Example 16. MIE-Test:NewMieTag2 + NewMieTag2 => { }, # new user-defined tag in MIE-Test group +); + +# A special 'Lenses' list can be defined to give priority to specific lenses +# in the logic to determine a lens model for the Composite:LensID tag +@Image::ExifTool::UserDefined::Lenses = ( + 'Sigma AF 10-20mm F4-5.6 EX DC', + 'Tokina AF193-2 19-35mm f/3.5-4.5', +); + +# User-defined file types to recognize +%Image::ExifTool::UserDefined::FileTypes = ( + XXX => { # <-- the extension of the new file type (case insensitive) + # BaseType specifies the format upon which this file is based (case + # sensitive). If BaseType is defined, then the file will be fully + # supported, and in this case the Magic pattern should not be defined + BaseType => 'TIFF', + MIMEType => 'image/x-xxx', + Description => 'My XXX file type', + # if the BaseType is writable by ExifTool, then the new file type + # will also be writable unless otherwise specified, like this: + Writable => 0, + }, + YYY => { + # without BaseType, the file will be recognized but not supported + Magic => '0123abcd', # regular expression to match at start of file + MIMEType => 'application/test', + Description => 'My YYY file type', + }, + ZZZ => { + # if neither BaseType nor Magic are defined, the file will be + # recognized by extension only. MIMEType will be application/unknown + # unless otherwise specified + Description => 'My ZZZ file type', + }, + # if only BaseType is specified, then the following simplified syntax + # may be used. In this example, files with extension "TTT" will be + # processed as JPEG files + TTT => 'JPEG', +); + +# Change default location for writing QuickTime tags so Keys is preferred +# (by default, the PREFERRED levels are: ItemList=2, UserData=1, Keys=0) +use Image::ExifTool::QuickTime; +$Image::ExifTool::QuickTime::Keys{PREFERRED} = 3; + +# Specify default ExifTool option values +# (see the Options function documentation for available options) +%Image::ExifTool::UserDefined::Options = ( + CoordFormat => '%.6f', # change default GPS coordinate format + Duplicates => 1, # make -a default for the exiftool app + GeoMaxHDOP => 4, # ignore GPS fixes with HDOP > 4 + RequestAll => 3, # request additional tags not normally generated +); + +#------------------------------------------------------------------------------ +1; #end diff --git a/ExifTool/config_files/fotoware.config b/ExifTool/config_files/fotoware.config new file mode 100644 index 0000000..137f832 --- /dev/null +++ b/ExifTool/config_files/fotoware.config @@ -0,0 +1,114 @@ +#------------------------------------------------------------------------------ +# File: fotoware.config +# +# Description: This config file defines Fotoware XMP tags for writing. +# +# Notes: Length limits are imposed according to Fotoware limitations when +# writing string values, but these may be disabled with the -n +# option or by adding a "#" to the tag name. However, beware that +# longer strings may cause unpredictable results in Fotoware +# software. +# +# Use of this file will not bring full Fotoware 'experience'. +# Fotostation writes these tags not only into XMP but also in IIM +# and even Exif spaces. Communication with older Fotoware systems +# may be broken when using only XMP. +# +# Also note that there are potential problems with character +# encoding on systems with mixed environments, depending on +# Fotoware software settings. +# +# Usage: exiftool -config fotoware.config -FOTOWARETAG=VALUE FILE ... +# +# Requires: ExifTool version 7.00 or later +# +# Revisions: 2012/09/19 - P. Harvey Created +# 2016/09/24 - Mikolaj Machowski updated with new Fotostation tags +#------------------------------------------------------------------------------ + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::XMP::Main' => { + fwl => { + SubDirectory => { TagTable => 'Image::ExifTool::UserDefined::fwl' }, + }, + fwr => { + SubDirectory => { TagTable => 'Image::ExifTool::UserDefined::fwr' }, + }, + fwc => { + SubDirectory => { TagTable => 'Image::ExifTool::UserDefined::fwc' }, + }, + fwu => { + SubDirectory => { TagTable => 'Image::ExifTool::UserDefined::fwu' }, + }, + }, +); + +%Image::ExifTool::UserDefined::fwl = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-fwl', 2 => 'Image' }, + NAMESPACE => { 'fwl' => 'http://ns.fotoware.com/iptcxmp-legacy/1.0/' }, + WRITABLE => 'string', + # limit maximum lengths of string values + CreatedTime => { PrintConvInv => 'substr($val,0,16)' }, + EditStatus => { PrintConvInv => 'substr($val,0,64)' }, + FixtureIdentifier=> { PrintConvInv => 'substr($val,0,1024)' }, + LocalCaption => { PrintConvInv => 'substr($val,0,2000)' }, + ObjectCycle => { PrintConvInv => 'substr($val,0,32)' }, + ProgramVersion => { PrintConvInv => 'substr($val,0,10)' }, + ReferenceNumber => { PrintConvInv => 'substr($val,0,256)' }, + ReferenceService => { PrintConvInv => 'substr($val,0,256)' }, + ReferenceDate => { + Groups => { 2 => 'Time' }, + Writable => 'date', + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + }, + ReleaseDate => { + Groups => { 2 => 'Time' }, + Writable => 'date', + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + }, + ReleaseTime => { PrintConvInv => 'substr($val,0,16)' }, +); + +%Image::ExifTool::UserDefined::fwr = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-fwr', 2 => 'Image' }, + NAMESPACE => { 'fwr' => 'http://ns.fotoware.com/iptcxmp-reserved/1.0/' }, + WRITABLE => 'string', + Classify => { PrintConvInv => 'substr($val,0,256)' }, + DocumentText => { PrintConvInv => 'substr($val,0,2000)' }, + Exif => { PrintConvInv => 'substr($val,0,1024)' }, + History => { PrintConvInv => 'substr($val,0,256)' }, + ImageNotes => { PrintConvInv => 'substr($val,0,256)' }, + JobId => { PrintConvInv => 'substr($val,0,256)' }, + OwnerId => { PrintConvInv => 'substr($val,0,256)' }, + ShortUniqeId => { PrintConvInv => 'substr($val,0,256)' }, + ContentValue => { PrintConvInv => 'substr($val,0,256)' }, + UniqueId => { PrintConvInv => 'substr($val,0,256)' }, + MasterDocumentId => { PrintConvInv => 'substr($val,0,256)' }, +); + + +%Image::ExifTool::UserDefined::fwc = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-fwc', 2 => 'Image' }, + NAMESPACE => { 'fwc' => 'http://ns.fotoware.com/iptcxmp-custom/1.0/' }, + WRITABLE => 'string', + # generate CustomField1 through CustomField20 tags + map { 'CustomField' . $_ => { + PrintConvInv => 'substr($val,0,256)', + } } (1 .. 20), +); + +%Image::ExifTool::UserDefined::fwu = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-fwu', 2 => 'Image' }, + NAMESPACE => { 'fwu' => 'http://ns.fotoware.com/iptcxmp-user/1.0/' }, + WRITABLE => 'string', + # generate UserDefined1 through UserDefined255 tags + map { 'UserDefined' . $_ => { + PrintConvInv => 'substr($val,0,256)', + } } (1 .. 255), +); + +1; #end diff --git a/ExifTool/config_files/frameCount.config b/ExifTool/config_files/frameCount.config new file mode 100755 index 0000000..95de926 --- /dev/null +++ b/ExifTool/config_files/frameCount.config @@ -0,0 +1,56 @@ +#------------------------------------------------------------------------------ +# File: frameCount.config +# +# Description: ExifTool config file to extract MP4 video FrameCount +# +# Usage: exiftool -config frameCount.config -frameCount FILE +# +# Requires: ExifTool version 7.99 or later +# +# Revisions: 2022-09-22 - P. Harvey Created +# +# Notes: Enables Unknown option to extract the required SampleSizes atom +#------------------------------------------------------------------------------ + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::Composite' => { + 'FrameCount' => { + Require => { + 0 => 'HandlerType', + }, + Desire => { + 1 => 'SampleSizes', + 2 => 'CompactSampleSizes', + }, + Groups => { 2 => 'Video' }, + ValueConv => q{ + my ($i, $tag, $ptr); + my $key = 'HandlerType'; + # find video track number + for ($i=1; defined $val[0]; ++$i) { + last if $val[0] eq 'Video Track'; + $key = "HandlerType ($i)"; + $val[0] = $self->GetValue($key); + } + my $trk = $self->GetGroup($key, 1); + # search for SampleSizes or CompactSampleSizes for this track + foreach $tag ('SampleSizes', 'CompactSampleSizes') { + $key = $tag; + for ($i=1; ; ++$i) { + $ptr = $self->GetValue($key); + last unless $ptr; + last if $self->GetGroup($key, 1) eq $trk; + $key = "$tag ($i)"; + } + last if $ptr; + } + return undef unless $ptr; + return unpack('x8N', $$ptr); + }, + }, + }, +); + +%Image::ExifTool::UserDefined::Options = ( + Unknown => 1, # (otherwise SampleSizes won't be extracted) +); diff --git a/ExifTool/config_files/gps2utm.config b/ExifTool/config_files/gps2utm.config new file mode 100644 index 0000000..1f929e6 --- /dev/null +++ b/ExifTool/config_files/gps2utm.config @@ -0,0 +1,256 @@ +#------------------------------------------------------------------------------ +# File: gps2utm.config +# +# Description: Generate UTM tags from GPS information +# +# Requires: ExifTool version 7.00 or later +# +# Notes: Uses GPSMapDatum, GPSLatitude and GPSLongitude to generate +# UTMCoordinates, UTMZone, UTMEasting and UTMNorthing. If +# GPSMapDatum is not available then "WGS84" is assumed. +# +# Example: > exiftool -config gps2utm.config "-utm*" t/images/GPS.jpg +# UTM Coordinates : 30U 569475.596m E 6094180.754m N +# UTM Easting : 569475.595558165 +# UTM Northing : 6094180.75443061 +# UTM Zone : 30U +# +# Caveats: When used to convert EXIF GPS coordinates, the reference +# direction tags (GPSLatitudeRef/GPSLongitudeRef) must exist or +# the calculated UTM coordinates may be in the wrong hemisphere +# +# Revisions: 2016/03/08 - Bryan K. Williams (aka StarGeek) Created +# 2016/03/09 - PH removed library dependency and re-organized +#------------------------------------------------------------------------------ + +my $deg2rad = 3.14159265358979 / 180; + +sub tan($) +{ + return sin($_[0]) / cos($_[0]); +} + +#=============================================================================== +# the following code is by Graham Crookham: +# http://search.cpan.org/~grahamc/Geo-Coordinates-UTM/ (version 0.11) + +# remove all markup from an ellipsoid name, to increase the chance +# that a match is found. +sub _cleanup_name($) +{ my $copy = lc(shift); + for($copy) + { s/\([^)]+\)//g; # remove text between parentheses + s/[\s-]//g; # no blanks or dashes + } + $copy; +} + +# Ellipsoid array (name,equatorial radius,square of eccentricity) +# Same data also as hash with key eq name (in variations) + +my (@Ellipsoid, %Ellipsoid); + +BEGIN { # Initialize this before other modules get a chance + @Ellipsoid = + ( [ "Airy", 6377563, 0.00667054] + , [ "Australian National", 6378160, 0.006694542] + , [ "Bessel 1841", 6377397, 0.006674372] + , [ "Bessel 1841 Nambia", 6377484, 0.006674372] + , [ "Clarke 1866", 6378206, 0.006768658] + , [ "Clarke 1880", 6378249, 0.006803511] + , [ "Everest 1830 India", 6377276, 0.006637847] + , [ "Fischer 1960 Mercury", 6378166, 0.006693422] + , [ "Fischer 1968", 6378150, 0.006693422] + , [ "GRS 1967", 6378160, 0.006694605] + , [ "GRS 1980", 6378137, 0.00669438] + , [ "Helmert 1906", 6378200, 0.006693422] + , [ "Hough", 6378270, 0.00672267] + , [ "International", 6378388, 0.00672267] + , [ "Krassovsky", 6378245, 0.006693422] + , [ "Modified Airy", 6377340, 0.00667054] + , [ "Modified Everest", 6377304, 0.006637847] + , [ "Modified Fischer 1960", 6378155, 0.006693422] + , [ "South American 1969", 6378160, 0.006694542] + , [ "WGS 60", 6378165, 0.006693422] + , [ "WGS 66", 6378145, 0.006694542] + , [ "WGS-72", 6378135, 0.006694318] + , [ "WGS-84", 6378137, 0.00669438 ] + , [ "Everest 1830 Malaysia", 6377299, 0.006637847] + , [ "Everest 1956 India", 6377301, 0.006637847] + , [ "Everest 1964 Malaysia and Singapore", 6377304, 0.006637847] + , [ "Everest 1969 Malaysia", 6377296, 0.006637847] + , [ "Everest Pakistan", 6377296, 0.006637534] + , [ "Indonesian 1974", 6378160, 0.006694609] + , [ "Arc 1950", 6378249.145,0.006803481] + , [ "NAD 27",6378206.4,0.006768658] + , [ "NAD 83",6378137,0.006694384] + ); + +# calc ecc as +# a = semi major axis +# b = semi minor axis +# e^2 = (a^2-b^2)/a^2 +# For clarke 1880 (Arc1950) a=6378249.145 b=6356514.966398753 +# e^2 (40682062155693.23 - 40405282518051.34) / 40682062155693.23 +# e^2 = 0.0068034810178165 + + foreach my $el (@Ellipsoid) + { my ($name, $eqrad, $eccsq) = @$el; + $Ellipsoid{$name} = $el; + $Ellipsoid{_cleanup_name $name} = $el; + } +} + +# Returns "official" name, equator radius and square eccentricity +# The specified name can be numeric (for compatibility reasons) or +# a more-or-less exact name +# Examples: my($name, $r, $sqecc) = ellipsoid_info 'wgs84'; +# my($name, $r, $sqecc) = ellipsoid_info 'WGS 84'; +# my($name, $r, $sqecc) = ellipsoid_info 'WGS-84'; +# my($name, $r, $sqecc) = ellipsoid_info 'WGS-84 (new specs)'; +# my($name, $r, $sqecc) = ellipsoid_info 22; + +sub ellipsoid_info($) +{ my $id = shift; + + my $el = $id !~ m/\D/ + ? $Ellipsoid[$id-1] # old system counted from 1 + : $Ellipsoid{$id} || $Ellipsoid{_cleanup_name $id}; + + defined $el ? @$el : (); +} + +# Expects Ellipsoid Number or name, Latitude, Longitude +# (Latitude and Longitude in decimal degrees) +# Returns UTM Zone, UTM Easting, UTM Northing + +sub latlon_to_utm(@) +{ my ($ellips, $latitude, $longitude) = @_; + + die("Longitude value ($longitude) invalid\n") + if $longitude < -180 || $longitude > 180; + + my $long2 = $longitude - int(($longitude + 180)/360) * 360; + my $zone = _latlon_zone_number($latitude, $long2); + + _latlon_to_utm($ellips || 'WGS84', $zone, $latitude, $long2); +} + +sub _latlon_zone_number +{ my ($latitude, $long2) = @_; + + my $zone = int( ($long2 + 180)/6) + 1; + if($latitude >= 56.0 && $latitude < 64.0 && $long2 >= 3.0 && $long2 < 12.0) + { $zone = 32; + } + if($latitude >= 72.0 && $latitude < 84.0) { + $zone = ($long2 >= 0.0 && $long2 < 9.0) ? 31 + : ($long2 >= 9.0 && $long2 < 21.0) ? 33 + : ($long2 >= 21.0 && $long2 < 33.0) ? 35 + : ($long2 >= 33.0 && $long2 < 42.0) ? 37 + : $zone; + } + return $zone; +} + +sub _latlon_to_utm +{ my ($ellips, $zone, $latitude, $long2) = @_; + + my ($name, $radius, $eccentricity) = ellipsoid_info $ellips + or die("Ellipsoid value ($ellips) invalid\n"); + + my $lat_radian = $deg2rad * $latitude; + my $long_radian = $deg2rad * $long2; + + my $k0 = 0.9996; # scale + + my $longorigin = ($zone - 1)*6 - 180 + 3; + my $longoriginradian = $deg2rad * $longorigin; + my $eccentprime = $eccentricity/(1-$eccentricity); + + my $N = $radius / sqrt(1-$eccentricity * sin($lat_radian)*sin($lat_radian)); + my $T = tan($lat_radian) * tan($lat_radian); + my $C = $eccentprime * cos($lat_radian)*cos($lat_radian); + my $A = cos($lat_radian) * ($long_radian - $longoriginradian); + my $M = $radius + * ( ( 1 - $eccentricity/4 - 3 * $eccentricity * $eccentricity/64 + - 5 * $eccentricity * $eccentricity * $eccentricity/256 + ) * $lat_radian + - ( 3 * $eccentricity/8 + 3 * $eccentricity * $eccentricity/32 + + 45 * $eccentricity * $eccentricity * $eccentricity/1024 + ) * sin(2 * $lat_radian) + + ( 15 * $eccentricity * $eccentricity/256 + + 45 * $eccentricity * $eccentricity * $eccentricity/1024 + ) * sin(4 * $lat_radian) + - ( 35 * $eccentricity * $eccentricity * $eccentricity/3072 + ) * sin(6 * $lat_radian) + ); + + my $utm_easting = $k0*$N*($A+(1-$T+$C)*$A*$A*$A/6 + + (5-18*$T+$T*$T+72*$C-58*$eccentprime)*$A*$A*$A*$A*$A/120) + + 500000.0; + + my $utm_northing= $k0 * ( $M + $N*tan($lat_radian) * ( $A*$A/2+(5-$T+9*$C+4*$C*$C)*$A*$A*$A*$A/24 + (61-58*$T+$T*$T+600*$C-330*$eccentprime) * $A*$A*$A*$A*$A*$A/720)); + + $utm_northing += 10000000.0 if $latitude < 0; + + my $utm_letter + = ( 84 >= $latitude && $latitude >= 72) ? 'X' + : ( 72 > $latitude && $latitude >= 64) ? 'W' + : ( 64 > $latitude && $latitude >= 56) ? 'V' + : ( 56 > $latitude && $latitude >= 48) ? 'U' + : ( 48 > $latitude && $latitude >= 40) ? 'T' + : ( 40 > $latitude && $latitude >= 32) ? 'S' + : ( 32 > $latitude && $latitude >= 24) ? 'R' + : ( 24 > $latitude && $latitude >= 16) ? 'Q' + : ( 16 > $latitude && $latitude >= 8) ? 'P' + : ( 8 > $latitude && $latitude >= 0) ? 'N' + : ( 0 > $latitude && $latitude >= -8) ? 'M' + : ( -8 > $latitude && $latitude >= -16) ? 'L' + : (-16 > $latitude && $latitude >= -24) ? 'K' + : (-24 > $latitude && $latitude >= -32) ? 'J' + : (-32 > $latitude && $latitude >= -40) ? 'H' + : (-40 > $latitude && $latitude >= -48) ? 'G' + : (-48 > $latitude && $latitude >= -56) ? 'F' + : (-56 > $latitude && $latitude >= -64) ? 'E' + : (-64 > $latitude && $latitude >= -72) ? 'D' + : (-72 > $latitude && $latitude >= -80) ? 'C' + : die("Latitude ($latitude) out of UTM range\n"); + + $zone .= $utm_letter; + + ($zone, $utm_easting, $utm_northing); +} +# End Graham Crookham code +#=============================================================================== + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::Composite' => { + UTMCoordinates => { + Desire => { + 0 => 'GPSMapDatum', + }, + Require => { + 1 => 'GPSLatitude', + 2 => 'GPSLongitude', + }, + ValueConv => 'join " ", latlon_to_utm(@val)', + PrintConv => 'sprintf("%s %.3fm E %.3fm N", split(" ", $val))', + }, + UTMZone => { + Require => 'UTMCoordinates', + ValueConv => 'my @a=split(" ",$val); $a[0]', + }, + UTMEasting => { + Require => 'UTMCoordinates', + ValueConv => 'my @a=split(" ",$val); $a[1]', + }, + UTMNorthing => { + Require => 'UTMCoordinates', + ValueConv => 'my @a=split(" ",$val); $a[2]', + }, + }, +); + +#------------------------------------------------------------------------------ +1; #end diff --git a/ExifTool/config_files/guano.config b/ExifTool/config_files/guano.config new file mode 100644 index 0000000..14edfba --- /dev/null +++ b/ExifTool/config_files/guano.config @@ -0,0 +1,161 @@ +#------------------------------------------------------------------------------ +# File: Guano.config +# +# Description: User-defined Composite tag definitions to extract individual entries +# from the RIFF:Guano text block. +# +# Requires exiftool version 12.63+ +# +# See GUANO - Grand Unified Acoustic Notation Ontology +# https://github.com/riggsd/guano-spec/blob/master/guano_specification.md +# +# The code itself is simply a regex match. If other entries are needed, +# then all that needs to be done is to copy/paste one of the blocks, change +# the name and edit the regex +# +# Examples: +# This will list the Make and Model from the Guano text block, if they exist +# exiftool -config Guano.config -GuanoModel -GuanoMake /path/to/files/ +# +# Lists all the Guano entries in the Guano text block +# exiftool -config Guano.config -Guano* /path/to/files/ +# +# Revisions: 2023/06/09 - Bryan K. Williams (aka StarGeek) Created +#------------------------------------------------------------------------------ + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::Composite' => { + GuanoVersion => { + Require => 'Guano', + ValueConv => '$val =~ /GUANO\|Version:\s+(.*)/m ? $1 : undef', + }, + GuanoMake => { + Require => 'Guano', + ValueConv => '$val =~ /Make:\s+(.*)/m ? $1 : undef', + }, + GuanoModel => { + Require => 'Guano', + ValueConv => '$val =~ /Model:\s+(.*)/m ? $1 : undef', + }, + GuanoFirmwareVersion => { + Require => 'Guano', + ValueConv => '$val =~ /Firmware Version:\s+(.*)/m ? $1 : undef', + }, + GuanoSerial => { + Require => 'Guano', + ValueConv => '$val =~ /Serial:\s+(.*)/m ? $1 : undef', + }, + GuanoTimestamp => { + Require => 'Guano', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ /Timestamp:\s+(.*)/m ? $1 : undef', + PrintConv => '$self->ConvertDateTime($val)', + }, + GuanoLocPosition => { + Require => 'Guano', + Groups => { 2 => 'Location' }, + ValueConv => '$val =~ /Loc Position:\s+(.*)/m ? $1 : undef', + PrintConv => q{ + require Image::ExifTool::GPS; + my @v = split ' ', $val; + return Image::ExifTool::GPS::ToDMS($self, $v[0], 1, "N") . ', ' . + Image::ExifTool::GPS::ToDMS($self, $v[1], 1, "E"); + }, + }, + GuanoLocElevation => { + Require => 'Guano', + Groups => { 2 => 'Location' }, + ValueConv => '$val =~ /Loc Elevation:\s+(.*)/m ? $1 : undef', + }, + GuanoTemperatureInt => { + Require => 'Guano', + ValueConv => '$val =~ /Temperature Int:\s+(.*)/m ? $1 : undef', + }, + GuanoFilterHP => { + Require => 'Guano', + ValueConv => '$val =~ /^Filter HP:\s+(.*)/m ? $1 : undef', + }, + GuanoAnabatBatteryVoltage => { + Require => 'Guano', + ValueConv => '$val =~ /Anabat\|Battery voltage:\s+(.*)/m ? $1 : undef', + }, + GuanoAnabatMicrophone => { + Require => 'Guano', + ValueConv => '$val =~ /Anabat\|Microphone:\s+(.*)/m ? $1 : undef', + }, + GuanoAnabatActivation => { + Require => 'Guano', + ValueConv => '$val =~ /Anabat\|Activation:\s+(.*)/m ? $1 : undef', + }, + GuanoAnabatZcSensitivity => { + Require => 'Guano', + ValueConv => '$val =~ /Anabat\|Zc Sensitivity:\s+(.*)/m ? $1 : undef', + }, + GuanoAnabatTriggerMinFreq => { + Require => 'Guano', + ValueConv => '$val =~ /Anabat\|Trigger min freq:\s+(.*)/m ? $1 : undef', + }, + GuanoAnabatTriggerMaxFreq => { + Require => 'Guano', + ValueConv => '$val =~ /Anabat\|Trigger max freq:\s+(.*)/m ? $1 : undef', + }, + GuanoAnabatMinEvent => { + Require => 'Guano', + ValueConv => '$val =~ /Anabat\|Min event:\s+(.*)/m ? $1 : undef', + }, + GuanoAnabatTriggerWindow => { + Require => 'Guano', + ValueConv => '$val =~ /Anabat\|Trigger Window:\s+(.*)/m ? $1 : undef', + }, + GuanoAnabatMaximumFileDuration => { + Require => 'Guano', + ValueConv => '$val =~ /Anabat\|Maximum File Duration:\s+(.*)/m ? $1 : undef', + }, + GuanoSBVersion => { + Require => 'Guano', + ValueConv => '$val =~ /SB\|Version:\s+(.*)/m ? $1 : undef', + }, + GuanoSBSpeciesAutoID => { + Require => 'Guano', + ValueConv => '$val =~ /SB\|Species Auto ID:\s+(.*)/m ? $1 : undef', + }, + GuanoSpeciesAutoID => { + Require => 'Guano', + ValueConv => '$val =~ /Species Auto ID:\s+(.*)/m ? $1 : undef', + }, + GuanoSpeciesManualID => { + Require => 'Guano', + ValueConv => '$val =~ /Species Manual ID:\s+(.*)/m ? $1 : undef', + }, + GuanoSampleRate => { + Require => 'Guano', + ValueConv => '$val =~ /Samplerate:\s+(.*)/m ? $1 : undef', + }, + GuanoLength => { + Require => 'Guano', + ValueConv => '$val =~ /Length:\s+(.*)/m ? $1 : undef', + }, + GuanoTE => { + Require => 'Guano', + ValueConv => '$val =~ /TE:\s+(.*)/m ? $1 : undef', + }, + GuanoSBFilterHP => { + Require => 'Guano', + ValueConv => '$val =~ /SB\|Filter HP:\s+(.*)/m ? $1 : undef', + }, + GuanoNote => { + Require => 'Guano', + ValueConv => '$val =~ /Note:\s+(.*)/m ? $1 : undef', + }, + GuanoSBRegion => { + Require => 'Guano', + ValueConv => '$val =~ /SB\|Region:\s+(.*)/m ? $1 : undef', + }, + GuanoSBClassifier => { + Require => 'Guano', + ValueConv => '$val =~ /SB\|Classifier:\s+(.*)/m ? $1 : undef', + }, + }, +); +#------------------------------------------------------------------------------ +1; #end diff --git a/ExifTool/config_files/nksc.config b/ExifTool/config_files/nksc.config new file mode 100644 index 0000000..c0244b9 --- /dev/null +++ b/ExifTool/config_files/nksc.config @@ -0,0 +1,146 @@ +#------------------------------------------------------------------------------ +# File: nksc.config +# +# Description: This config file contains tag definitions to extract metadata +# from RDF/XML-based Nikon ViewNX NKSC sidecar files +# +# Usage: exiftool -config nksc.config -ext nksc DIR +# +# Requires: ExifTool version 11.38 or later +# +# Revisions: 2019/04/24 - P. Harvey Created +#------------------------------------------------------------------------------ + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::XMP::Main' => { + ast => { + SubDirectory => { TagTable => 'Image::ExifTool::UserDefined::ast' }, + }, + sdc => { + SubDirectory => { TagTable => 'Image::ExifTool::UserDefined::sdc' }, + }, + nine => { + SubDirectory => { TagTable => 'Image::ExifTool::UserDefined::nine' }, + }, + }, + 'Image::ExifTool::Composite' => { + GPSLatitude => { + Require => { + 0 => 'XMP-ast:GPSLatitude', + 1 => 'XMP-ast:GPSLatitudeRef', + }, + ValueConv => '$prt[1] =~ /^S/i ? -$val[0] : $val[0]', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + GPSLongitude => { + Require => { + 0 => 'XMP-ast:GPSLongitude', + 1 => 'XMP-ast:GPSLongitudeRef', + }, + ValueConv => '$prt[1] =~ /^W/i ? -$val[0] : $val[0]', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + }, +); + +%Image::ExifTool::UserDefined::ast = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-ast', 2 => 'Location' }, + NAMESPACE => { 'ast' => 'http://ns.nikon.com/asteroid/1.0/' }, + about => { Groups => { 2 => 'Image' } }, + version => { Groups => { 2 => 'Image' } }, + GPSVersionID => { + ValueConv => q{ + my $valPt = Image::ExifTool::XMP::DecodeBase64($val); + SetByteOrder('II'); + ReadValue($valPt, 0, 'int8u', 4, length($$valPt)); + }, + PrintConv => '$val =~ tr/ /./; $val', + }, + GPSLatitude => { + ValueConv => q{ + my $valPt = Image::ExifTool::XMP::DecodeBase64($val); + SetByteOrder('II'); + $val = ReadValue($valPt, 0, 'double', 3, length($$valPt)); + Image::ExifTool::GPS::ToDegrees($val); + }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val)', + }, + GPSLatitudeRef => { + ValueConv => q{ + my $valPt = Image::ExifTool::XMP::DecodeBase64($val); + SetByteOrder('II'); + ReadValue($valPt, 0, 'int32u', 3, length($$valPt)); + }, + PrintConv => { 0 => 'North', 1 => 'South' }, + }, + GPSLongitude => { + ValueConv => q{ + my $valPt = Image::ExifTool::XMP::DecodeBase64($val); + SetByteOrder('II'); + $val = ReadValue($valPt, 0, 'double', 3, length($$valPt)); + Image::ExifTool::GPS::ToDegrees($val); + }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val)', + }, + GPSLongitudeRef => { + ValueConv => q{ + my $valPt = Image::ExifTool::XMP::DecodeBase64($val); + SetByteOrder('II'); + ReadValue($valPt, 0, 'int32u', 3, length($$valPt)); + }, + PrintConv => { 2 => 'East', 3 => 'West' }, + }, + GPSAltitude => { + ValueConv => q{ + my $valPt = Image::ExifTool::XMP::DecodeBase64($val); + SetByteOrder('II'); + ReadValue($valPt, 0, 'double'); + }, + PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"', + }, + GPSAltitudeRef => { + ValueConv => q{ + my $valPt = Image::ExifTool::XMP::DecodeBase64($val); + ReadValue($valPt, 0, 'int8u'); + }, + PrintConv => { + 0 => 'Above Sea Level', + 1 => 'Below Sea Level', + }, + }, + GPSMapDatum => { }, + XMLPackets => { + Name => 'XMP', + Groups => { 2 => 'Image' }, + RawConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, + IPTC => { + Name => 'IPTC', + Groups => { 2 => 'Image' }, + RawConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + SubDirectory => { TagTable => 'Image::ExifTool::IPTC::Main' }, + }, +); + +%Image::ExifTool::UserDefined::sdc = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-sdc', 2 => 'Image' }, + NAMESPACE => { 'sdc' => 'http://ns.nikon.com/sdc/1.0/' }, + about => { }, + version => { }, + appversion => { }, + appname => { }, +); + +%Image::ExifTool::UserDefined::nine = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-nine', 2 => 'Image' }, + NAMESPACE => { 'nine' => 'http://ns.nikon.com/nine/1.0/' }, + about => { }, + version => { }, + NineEdits => { Binary => 1 }, + Label => { }, + Rating => { }, + Trim => { ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)' }, +); + +1; #end diff --git a/ExifTool/config_files/photoshop_paths.config b/ExifTool/config_files/photoshop_paths.config new file mode 100644 index 0000000..a2e0ab6 --- /dev/null +++ b/ExifTool/config_files/photoshop_paths.config @@ -0,0 +1,221 @@ +#------------------------------------------------------------------------------ +# File: photoshop_paths.config +# +# Description: This config file generates user-defined tags for Photoshop +# paths, and may be used to extract path names and/or Bezier knot +# anchor points, or copy path information from one file to +# another. +# +# The anchor points may be extracted along with the path names by +# setting the "Anchor" user parameter (ie. "-userparam anchor"), +# or by themselves with "AnchorOnly". +# +# An "AllPaths" shortcut tag is also provided represent all +# Photoshop path tags. This shortcut must be used when copying +# because these tags are marked as "Protected" so they won't be +# copied by default (also see the notes below). +# +# Path anchor points are converted to pixel coordinates by the +# Composite PathPixXXX tags, and an "AllPathPix" shortcut is +# provided to represent these tags. +# +# Finally, a Composite PathCount tag is provided to return the +# number of paths in an image, and a TotalPathPoints tag counts +# the total number of path anchor points. +# +# Notes: 1) Print conversion must be disabled to be able to copy the paths +# (via either the -n option, or by adding a "#" to the tag name, +# eg. "-tagsfromfile SRC -allpaths#"). +# +# 2) When copying the paths, OriginPathInfo must also be copied +# (otherwise Photoshop may give a "program error" and refuse to +# load the image). +# +# Usage: +# +# 1) Extract Photoshop path names: +# +# exiftool -config photoshop_paths.config -allpaths FILE +# +# 2) Extract Photoshop path names and anchor points: +# +# exiftool -config photoshop_paths.config -userparam anchor -allpaths FILE +# +# 3) Extract Photoshop path anchor points only: +# +# exiftool -config photoshop_paths.config -userparam anchoronly -allpaths FILE +# +# 4) Copy all Photoshop paths from one file (SRC) to another (DST): +# (note that OriginPathInfo must also be copied when copying all paths) +# +# exiftool -config photoshop_paths.config -tagsfromfile SRC -allpaths# -originpathinfo DST +# +# 5) Extract path names and anchor points in pixel coordinates: +# +# exiftool -config photoshop_paths.config -allpathpix FILE +# +# Requires: ExifTool version 9.95 or later +# +# Notes: A "-" before a set of Bezier path points indicates a closed subpath, +# and a "+ indicates the start of an open subpath. +# +# Revisions: 2015/05/07 - P. Harvey Created +# 2016/09/14 - PH Added feature to allow extracting anchor points +# 2017/01/24 - PH Added PathCount and PathPix Composite tags +# 2017/02/02 - PH Added support for copying OriginPathInfo +# 2017/02/22 - PH Fixed problem printing some paths +# 2017/03/19 - PH Added "-" or "+" at the start of closed or open +# subpath respectively +# 2017/06/03 - PH Added TotalPathPoints +# 2017/07/17 - PH Added UniquePathPoints +# 2022/02/03 - PH Added WorkingPath and WorkingPathPix +# +# References: https://exiftool.org/forum/index.php/topic,1621.0.html +# https://exiftool.org/forum/index.php/topic,3910.0.html +# https://exiftool.org/forum/index.php/topic,6647.0.html +#------------------------------------------------------------------------------ + +# Print Photoshop path name and/or anchor points +# Inputs: 0) reference to Photoshop path data, 1) ExifTool object reference +# 2-3) optional image width/height to convert anchor points to pixels +# 4) optional path name +# Returns: String with name and/or Bezier knot anchor points +sub PrintPath($$;$$$) +{ + my ($val, $et, $w, $h, $nm) = @_; + my ($pos, $name, @rtn); + my $len = length($$val) - 26; + + # recover exiftool-added path name if it exists + if ($$val =~ m{.*/#(.{0,255})#/$}s) { + $name = $1; + $len -= length($1) + 4; + $name = $nm if defined $nm and not length $name; + } else { + $name = defined $nm ? $nm : ''; + } + my $anchorOnly = $et->Options(UserParam => 'AnchorOnly'); + push @rtn, $name unless $anchorOnly; + + # loop through path points and extract anchor points if specified + if ($anchorOnly or $et->Options(UserParam => 'Anchor') or defined $w) { + SetByteOrder('MM'); + for ($pos=0; $pos<=$len; $pos+=26) { + my $type = Get16u($val, $pos); + $type == 0 and push(@rtn, '-'), next; + $type == 3 and push(@rtn, '+'), next; + # Bezier knot records are types 1, 2, 4 and 5 + next unless {1=>1,2=>1,4=>1,5=>1}->{$type}; + # the anchor point is at offset 10 in the Bezier knot record + # (fixed-point values with 24-bits after the decimal point) + my $y = Get32s($val, $pos+10) / 0x1000000; # (vertical component first) + my $x = Get32s($val, $pos+14) / 0x1000000; + if (defined $w and defined $h) { + push @rtn, sprintf('(%g,%g)', $x * $w, $y * $h); + } else { + push @rtn, sprintf('(%g,%g)', $x, $y); + } + } + } + return join ' ', @rtn; +} + +%Image::ExifTool::Shortcuts::UserDefined = ( + # create "AllPaths" shortcut for all Photoshop path tags (except WorkingPath) + AllPaths => [ + map { sprintf "Path%x", $_ } (0x7d0 .. 0xbb5), + ], + AllPathPix => [ + map { sprintf "PathPix%x", $_ } (0x7d0 .. 0xbb5), + ], +); + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::Photoshop::Main' => { + 0xbb8 => { + Name => 'OriginPathInfo', + Flags => [ qw(Writable Protected Binary SetResourceName) ], + }, + 0x401 => { + Name => 'WorkingPath', + Flags => [ qw(Writable Protected Binary ConvertBinary SetResourceName) ], + PrintConv => sub { + my ($val, $et) = @_; + PrintPath($val, $et, undef, undef, 'Work Path'); + }, + }, + # generate tags for each of the 998 possible Photoshop paths + map { $_ => { + Name => sprintf('Path%x', $_), + Description => sprintf('Path %x', $_), + Flags => [ qw(Writable Protected Binary ConvertBinary SetResourceName) ], + PrintConv => \&PrintPath, + } } (0x7d0 .. 0xbb5), + }, + 'Image::ExifTool::Composite' => { + PathCount => { + # (PathCount statistics do not include WorkingPath) + Desire => { + map { $_-0x7d0 => sprintf('Path%x', $_) } (0x7d0 .. 0xbb5), + }, + ValueConv => sub { + my ($val, $self) = @_; + my $count = 0; + my $pts = 0; + my $uniq = 0; + my %uniq; + foreach (@$val) { + next unless defined $_; + ++$count; + # determine the total number of path anchor points + my $len = length($$_) - 26; + for ($pos=0; $pos<=$len; $pos+=26) { + SetByteOrder('MM'); + my $type = Get16u($_, $pos); + last if $type == 0x2f23; # (start of path name added by ExifTool) + next unless {1=>1,2=>1,4=>1,5=>1}->{$type}; + my $pt = substr($$_, $pos+10, 8); + $uniq{$pt} or ++$uniq, $uniq{$pt} = 1; + ++$pts; + } + } + $$self{TotalPathPoints} = $pts; + $$self{UniquePathPoints} = $uniq; + return $count; + }, + }, + UniquePathPoints => { + Require => 'PathCount', + ValueConv => '$$self{UniquePathPoints}', + }, + TotalPathPoints => { + Require => 'PathCount', + ValueConv => '$$self{TotalPathPoints}', + }, + WorkingPathPix => { + Require => { + 0 => 'ImageWidth', + 1 => 'ImageHeight', + 2 => 'WorkingPath', + }, + ValueConv => sub { + my ($val, $et) = @_; + PrintPath($$val[2], $et, $$val[0], $$val[1], 'Work Path'); + }, + }, + map { sprintf('PathPix%x', $_) => { + Require => { + 0 => 'ImageWidth', + 1 => 'ImageHeight', + 2 => sprintf('Path%x', $_), + }, + Description => sprintf('Path Pix %x', $_), + ValueConv => sub { + my ($val, $et) = @_; + PrintPath($$val[2], $et, $$val[0], $$val[1]); + }, + } } (0x7d0 .. 0xbb5), + }, +); + +1; #end diff --git a/ExifTool/config_files/picasa_faces.config b/ExifTool/config_files/picasa_faces.config new file mode 100644 index 0000000..4db3c85 --- /dev/null +++ b/ExifTool/config_files/picasa_faces.config @@ -0,0 +1,382 @@ +#------------------------------------------------------------------------------ +# File: picasa_faces.config +# +# Description: User-defined Composite tag definitions to convert face regions +# in .picasa.ini files to MWG region tags (Metadata Working Group +# region, used by Picasa) and MP region tags (used by Microsoft +# Photo Library). +# +# Tag definitions and examples: +# +# PicasaToMWGRegion +# This will create the MWG region tag but will filter out the regions +# that are still unnamed in Picasa. Picasa defaults to naming these +# regions 'ffffffffffffffff' but normally will not save these to file. +# Example: +# exiftool -config picasa_faces.config "-RegionInfo 1 } qw( + 3FR ARW CR2 CRW CS1 DCR DNG EIP ERF IIQ K25 KDC MEF MOS MRW NEF NRW + ORF PEF RAF RAW RW2 RWL SR2 SRF SRW X3F), 'Canon 1D RAW'; + +my %contactHash; # lookup for loaded contacts.xml entries +my %fileHash; # lookup for loaded .picasa.ini entries + +#------------------------------------------------------------------------------ +# Load Picasa's contacts.xml and .picasa.ini files. +# Inputs: 0) ExifTool object reference, 1) .picasa.ini directory +# Returns: 1 if files were loaded and parsed, undef on error +# Notes: If file has already been loaded, it isn't reloaded +sub LoadPicasaFiles($$) +{ + local (*CONTACTS, *INI); + my ($et, $iniDir) = @_; + + # check ExifTool version to see if there might be + # a command line setting for the contact file + my $contactFile = ($Image::ExifTool::VERSION >= 9.89 and + defined($et->Options(UserParam => 'PicasaContactsFile'))) ? + $et->Options(UserParam => 'PicasaContactsFile') : $contactXML; + + # load Picasa contacts.xml file unless done already + unless ($contactFile eq $lastContactFile) { + $lastContactFile = $contactFile; + undef %contactHash; + # Picasa's default setting for unnamed faces. + $contactHash{'ffffffffffffffff'} = 'unnamed'; + if (open(CONTACTS, $contactFile)) { + require Image::ExifTool::HTML; + while () { + /name="(.*?)"/ or next; + my $name = $1; + /id="([a-f0-9]+)"/ or next; + my $id = $1; + $contactHash{$id} = Image::ExifTool::HTML::UnescapeHTML($name); + } + close(CONTACTS); + } else { + local $SIG{'__WARN__'} = undef; # stop ExifTool from catching the warning + warn "Error reading contacts file $contactFile\n"; + } + } + + # load .picasa.ini file from the specified directory + my $iniFile = "$iniDir/.picasa.ini"; + + if ($iniFile eq $lastIniFile) { + return %fileHash ? 1 : undef; + } + $lastIniFile = $iniFile; + open(INI, $iniFile) or return undef; + my $section = ''; + while () { + # Process New Section + /^\s*\[(.+)\][\n\r]*$/ and $section = $1, next; + # process entry (all we care about are the "faces" lines) + /^faces=(.*)$/ or next; + my @temp = split /;/, $1; + foreach (@temp) { + /rect64\(([\da-f]{1,16})\),([\da-f]{1,16})/ or next; + # the string in parens after "rect64" is a 64 bit number in hex, + # but Picasa doesn't add leading zeroes, so the length of the string + # cannot be assumed to be 16 bytes. Handle this as two 32-bit numbers + # for compatibility with 32-bit systems. + my $hi = hex(substr($1, 0, -8)); + my $lo = hex(substr($1, -8)); + my $x0 = ($hi >> 16) /65535; + my $y0 = ($hi & 0xffff)/65535; + my $x1 = ($lo >> 16) /65535; + my $y1 = ($lo & 0xffff)/65535; + push @{ $fileHash{$section} }, { + ContactID => $2, + X => $x0, + Y => $y0, + W => $x1 - $x0, + H => $y1 - $y0, + }; + } + } + close(INI); + return %fileHash ? 1 : undef; +} + +#------------------------------------------------------------------------------ +# Rotate region to specified orientation (for RAW file types only) +# Input: 0) rectangle array ref (x,y,w,h), 1) EXIF orientation value, 2) file type +sub RotateRegion($$$) +{ + my ($rect, $orientation, $fileType) = @_; + if ($orientation and $fileType and $isRawFile{$fileType}) { + my ($x,$y,$w,$h) = @$rect; + if ($orientation == 8) { # CW 90 + @$rect = (1-$h-$y, $x, $h, $w); + } elsif ($orientation == 3) { # CW 180 + @$rect = (1-$x-$w, 1-$y-$h, $w, $h); + } elsif ($orientation == 6) { # CW 270 + @$rect = ($y, 1-$x-$w, $h, $w); + } + } +} + +#------------------------------------------------------------------------------ +# Rounds number to 9 decimal places, which is the limit to the number of decimal places that Picasa can read. +sub Rounded +{ + my $DecAcc = 10**9; + return(int($_[0]*$DecAcc+.5)/$DecAcc); +} + +#------------------------------------------------------------------------------ +# User-defined tag definitions +# +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::Composite' => { + # + # Versions that filter out unnamed regions (ContactID=ffffffffffffffff) + # + PicasaToMWGRegion => { + Require => { + 0 => 'Directory', + 1 => 'FileName', + 2 => 'ImageWidth', + 3 => 'ImageHeight', + }, + Desire => { + 4 => 'Orientation', + 5 => 'FileType', + }, + ValueConv => sub { + my ($val, $et) = @_; + LoadPicasaFiles($et, $$val[0]) or return undef; # load contacts.xml and Picasa.ini + my $filename = $$val[1]; + my @regList; + # convert to local variables for readability, and make + # sure there is a region associated with the current file + my $contactHashRef = \%contactHash; + my $tempArrayRef = $fileHash{$filename} or return undef; + foreach my $tempHash (@$tempArrayRef) { + next if $$tempHash{ContactID} eq 'ffffffffffffffff'; + my $name = $$contactHashRef{$$tempHash{ContactID}}; + next unless defined $name; + my @rect = @$tempHash{'X','Y','W','H'}; + RotateRegion(\@rect, $$val[4], $$val[5]); + push @regList, { + Area => { + X => Rounded($rect[0] + $rect[2] / 2), + Y => Rounded($rect[1] + $rect[3] / 2), + W => Rounded($rect[2]), + H => Rounded($rect[3]), + Unit => 'normalized', + }, + Name => $name, + Type => 'Face', + }; + } + # make sure a region exists, otherwise return undef + return @regList ? { + AppliedToDimensions => { W => $$val[2], H => $$val[3], Unit => 'pixel' }, + RegionList => \@regList, + } : undef; + }, + }, + PicasaToMPRegion => { + Require => { + 0 => 'Directory', + 1 => 'FileName', + }, + Desire => { + 2 => 'Orientation', + 3 => 'FileType', + }, + ValueConv => sub { + my ($val, $et) = @_; + LoadPicasaFiles($et, $$val[0]) or return undef; # load contacts.xml and Picasa.ini + my $filename = $$val[1]; + my @regList; + # convert to local variables for readability, and make + # sure there is a region associated with the current file + my $contactHashRef = \%contactHash; + my $tempArrayRef = $fileHash{$filename} or return undef; + foreach my $tempHash (@$tempArrayRef) { + next if $$tempHash{ContactID} eq 'ffffffffffffffff'; + my $name = $$contactHashRef{$$tempHash{ContactID}}; + next unless defined $name; + my @rect = @$tempHash{'X','Y','W','H'}; + RotateRegion(\@rect, $$val[2], $$val[3]); + @rect = map {Rounded($_)} @rect; + push @regList, { + PersonDisplayName => $name, + Rectangle => join(', ', @rect), + }; + } + # make sure a region exists, otherwise return undef + return @regList ? { Regions => \@regList } : undef; + }, + }, + PicasaRegionNames => { + Require => { + 0 => 'Directory', + 1 => 'FileName', + }, + ValueConv => sub { + my ($val, $et) = @_; + LoadPicasaFiles($et, $$val[0]) or return undef; # load contacts.xml and Picasa.ini + my $filename = $$val[1]; + my @regList; + # convert to local variables for readability, and make + # sure there is a region associated with the current file + my $contactHashRef = \%contactHash; + my $tempArrayRef = $fileHash{$filename} or return undef; + foreach my $tempHash (@$tempArrayRef) { + next if $$tempHash{ContactID} eq 'ffffffffffffffff'; + my $name = $$contactHashRef{$$tempHash{ContactID}}; + push @regList, $name if defined $name; + } + # make sure a region exists, otherwise return undef + return @regList ? \@regList : undef; + }, + }, + # + # Versions that do not filter out unnamed regions (ContactID=ffffffffffffffff) + # Picasa normally does not add these regions when it saves names to the file. + # + PicasaToMWGRegionUnfiltered => { + Require => { + 0 => 'Directory', + 1 => 'FileName', + 2 => 'ImageWidth', + 3 => 'ImageHeight', + }, + Desire => { + 4 => 'Orientation', + 5 => 'FileType', + }, + ValueConv => sub { + my ($val, $et) = @_; + LoadPicasaFiles($et, $$val[0]) or return undef; # load contacts.xml and Picasa.ini + my $filename = $$val[1]; + my @regList; + # convert to local variables for readability, and make + # sure there is a region associated with the current file + my $contactHashRef = \%contactHash; + my $tempArrayRef = $fileHash{$filename} or return undef; + foreach my $tempHash (@$tempArrayRef) { + my @rect = @$tempHash{'X','Y','W','H'}; + RotateRegion(\@rect, $$val[4], $$val[5]); + push @regList, { + Area => { + X => Rounded($rect[0] + $rect[2] / 2), + Y => Rounded($rect[1] + $rect[3] / 2), + W => Rounded($rect[2]), + H => Rounded($rect[3]), + Unit => 'normalized', + }, + Name => $$contactHashRef{$$tempHash{ContactID}} || 'unnamed', + Type => 'Face', + }; + } + # make sure a region exists, otherwise return undef + return @regList ? { + AppliedToDimensions => { W => $$val[2], H => $$val[3], Unit => 'pixel' }, + RegionList => \@regList, + } : undef; + }, + }, + PicasaToMPRegionUnfiltered => { + Require => { + 0 => 'Directory', + 1 => 'FileName', + }, + Desire => { + 2 => 'Orientation', + 3 => 'FileType', + }, + ValueConv => sub { + my ($val, $et) = @_; + LoadPicasaFiles($et, $$val[0]) or return undef; # load contacts.xml and Picasa.ini + my $filename = $$val[1]; + my @regList; + # convert to local variables for readability, and make + # sure there is a region associated with the current file + my $contactHashRef = \%contactHash; + my $tempArrayRef = $fileHash{$filename} or return undef; + foreach my $tempHash (@$tempArrayRef) { + my @rect = @$tempHash{'X','Y','W','H'}; + RotateRegion(\@rect, $$val[2], $$val[3]); + @rect = map {Rounded($_)} @rect; + push @regList, { + PersonDisplayName => $$contactHashRef{$$tempHash{ContactID}} || 'unnamed', + Rectangle => join(', ', @rect), + } + } + # make sure a region exists, otherwise return undef + return @regList ? { Regions => \@regList } : undef; + }, + }, + }, +); + +#------------------------------------------------------------------------------ +1; #end diff --git a/ExifTool/config_files/pix4d.config b/ExifTool/config_files/pix4d.config new file mode 100644 index 0000000..b11bc92 --- /dev/null +++ b/ExifTool/config_files/pix4d.config @@ -0,0 +1,99 @@ +#------------------------------------------------------------------------------ +# File: pix4d.config +# +# Description: This config file contains tag definitions needed to be able +# to write Pix4D XMP-Camera tags +# +# Usage: exiftool -config pix4d.config -XMP-camera:TAG=VAL ... +# +# Requires: ExifTool version 7.00 or later +# +# References: 1) https://support.pix4d.com/hc/en-us/articles/360016450032-Specifications-of-xmpcamera-tags +# +# Revisions: 2017/12/08 - P. Harvey Created +# 2020/04/02 - PH Updated to current specification +#------------------------------------------------------------------------------ + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::XMP::Main' => { + Camera => { + SubDirectory => { + TagTable => 'Image::ExifTool::UserDefined::Camera', + }, + }, + }, +); + +%Image::ExifTool::UserDefined::Camera = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-Camera', 2 => 'Camera' }, + NAMESPACE => { 'Camera' => 'http://pix4d.com/camera/1.0/' }, + WRITABLE => 'string', + Yaw => { Writable => 'real' }, + Pitch => { Writable => 'real' }, + Roll => { Writable => 'real' }, + IMUSampleSize => { Writable => 'integer' }, + IMUTimeOffset => { Writable => 'integer' }, + LineReadoutTime => { Writable => 'integer' }, + IMUFrequency => { Writable => 'real' }, + PrincipalPoint => { }, + ModelType => { }, + PerspectiveFocalLength => { Writable => 'real' }, + PerspectiveDistortion => { }, + IMULinearVelocity => { }, + GPSXYAccuracy => { Writable => 'real' }, + GPSZAccuracy => { Writable => 'real' }, + FlightUUID => { }, + CentralWaveLength => { }, + BandName => { List => 'Seq' }, + RigName => { }, + RigCameraIndex => { Writable => 'integer' }, + BandName => { List => 'Seq' }, + IMUAngularVelocity => { + Binary => 1, + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, + # added 2020/04/02 (ref 1) + FisheyeAffineMatrix => { }, + FisheyeAffineSymmetric => { }, + FisheyePolynomial => { }, + RigRelatives => { }, + PerspectiveFocalLengthUnits => { }, + CaptureUUID => { }, + CentralWavelength => { List => 'Seq' }, + WavelengthFWHM => { List => 'Seq' }, + BlackCurrent => { List => 'Seq' }, + BandSensitivity => { List => 'Seq' }, + SunSensor => { List => 'Seq' }, + SunSensorExposureTime => { List => 'Seq' }, + SunSensorSensitivity => { List => 'Seq' }, + InvalidPixel => { List => 'Seq' }, + VignettingPolynomial => { List => 'Seq' }, + VignettingCenter => { List => 'Seq' }, + VignettingPolynomial2DName => { List => 'Seq' }, + VignettingPolynomial2D => { List => 'Seq' }, + ColorTransform => { List => 'Seq' }, + IsNormalized => { }, + Albedo => { List => 'Seq' }, + ReflectArea => { List => 'Seq' }, + CalibrationPicture => { Writable => 'integer' }, + GyroRate => { Writable => 'real' }, + IMUPitchAccuracy => { Writable => 'real' }, + IMURollAccuracy => { Writable => 'real' }, + IMUYawAccuracy => { Writable => 'real' }, + NominalCameraDistance => { Writable => 'real' }, + AboveGroundAltitude => { Writable => 'real' }, + SunSensorYaw => { Writable => 'real' }, + SunSensorPitch => { Writable => 'real' }, + SunSensorRoll => { Writable => 'real' }, + SunSensorRelativeRotation => { Writable => 'real', List => 'Seq' }, + TransformAlpha => { List => 'Seq' }, + TransformBeta => { List => 'Seq' }, + TransformGamma => { List => 'Seq' }, + SensorBitDepth => { Writable => 'integer' }, + SensorTemperature => { Writable => 'real' }, + # (ref https://community.pix4d.com/t/camera-sun-irradiance-and-sun-angle-in-red-text/3290) + IrradianceRelativeRotation => { }, +); + +1; #end diff --git a/ExifTool/config_files/rotate_regions.config b/ExifTool/config_files/rotate_regions.config new file mode 100644 index 0000000..7e4ee42 --- /dev/null +++ b/ExifTool/config_files/rotate_regions.config @@ -0,0 +1,173 @@ +#------------------------------------------------------------------------------ +# File: rotate_regions.config +# +# Description: User-defined Composite tag definitions to rotate MWG region tags +# (Metadata Working Group region, used by Picasa) and MP region tags +# (used by Microsoft Photo Library). +# +# Tag definitions and examples: +# +# RotateMWGRegionCW90 +# RotateMWGRegionCW180 +# RotateMWGRegionCW270 +# These tags will rotate a MWG Region clockwise 90, 180, or 270 degrees. +# Example: +# exiftool -config rotate_regions.config "-RegionInfo { + RotateMWGRegionCW90 =>{ + Require => 'RegionInfo', + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{RegionList}}) { + my @rect = @{$$rgn{Area}}{'X','Y','W','H'}; + my %newRgn = ( + Area => { + X => 1-$rect[1], + Y => $rect[0], + W => $rect[3], + H => $rect[2], + Unit => 'normalized', + }, + Name => $$rgn{Name}, + Type => 'Face', + ); + push @newRgns, \%newRgn; + } + return { + AppliedToDimensions => { + W => $val[0]{AppliedToDimensions}{W}, + H => $val[0]{AppliedToDimensions}{H}, + Unit => $val[0]{AppliedToDimensions}{Unit}, + }, + RegionList => \@newRgns, + }; + }, + }, #End RotateMWGRegionCW90 + RotateMWGRegionCW180 =>{ + Require => 'RegionInfo', + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{RegionList}}) { + my @rect = @{$$rgn{Area}}{'X','Y','W','H'}; + my %newRgn = ( + Area => { + X => 1-$rect[0], + Y => 1-$rect[1], + W => $rect[2], + H => $rect[3], + Unit => 'normalized', + }, + Name => $$rgn{Name}, + Type => 'Face', + ); + push @newRgns, \%newRgn; + } + return { + AppliedToDimensions => { + W => $val[0]{AppliedToDimensions}{W}, + H => $val[0]{AppliedToDimensions}{H}, + Unit => $val[0]{AppliedToDimensions}{Unit}, + }, + RegionList => \@newRgns, + }; + }, + }, #End RotateMWGRegionCW180 + RotateMWGRegionCW270 =>{ + Require => 'RegionInfo', + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{RegionList}}) { + my @rect = @{$$rgn{Area}}{'X','Y','W','H'}; + my %newRgn = ( + Area => { + X => $rect[1], + Y => 1-$rect[0], + W => $rect[3], + H => $rect[2], + Unit => 'normalized', + }, + Name => $$rgn{Name}, + Type => 'Face', + ); + push @newRgns, \%newRgn; + } + return { + AppliedToDimensions => { + W => $val[0]{AppliedToDimensions}{W}, + H => $val[0]{AppliedToDimensions}{H}, + Unit => $val[0]{AppliedToDimensions}{Unit}, + }, + RegionList => \@newRgns, + }; + }, + }, #End RotateMWGRegionCW270 + RotateMPRegionCW90=>{ + Require => 'RegionInfoMP', + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{Regions}}) { + my @rect = split /\s*,\s*/, $$rgn{Rectangle}; + my $temp = $rect[0]; + $rect[0] = 1-$rect[1]-$rect[3]; + $rect[1] = $temp; + ($rect[2], $rect[3]) = ($rect[3],$rect[2]); #Swap W and H + push @newRgns, { + PersonDisplayName => $$rgn{PersonDisplayName}, + Rectangle => join(', ', @rect), + }; + } + return { Regions => \@newRgns }; + } + }, #end RotateMPRegionCW90 + RotateMPRegionCW180=>{ + Require => 'RegionInfoMP', + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{Regions}}) { + my @rect = split /\s*,\s*/, $$rgn{Rectangle}; + my $tempX = $rect[0]; + my $tempY = $rect[1]; + $rect[0] = 1-$tempX-$rect[2]; + $rect[1] = 1-$tempY-$rect[3]; + push @newRgns, { + PersonDisplayName => $$rgn{PersonDisplayName}, + Rectangle => join(', ', @rect), + }; + } + return { Regions => \@newRgns }; + } + }, #end RotateMPRegionCW180 + RotateMPRegionCW270=>{ + Require => 'RegionInfoMP', + ValueConv => q{ + my ($rgn, @newRgns); + foreach $rgn (@{$val[0]{Regions}}) { + my @rect = split /\s*,\s*/, $$rgn{Rectangle}; + my $temp = $rect[1]; + $rect[1] = 1-$rect[0]-$rect[2]; + $rect[0] = $temp; + ($rect[2], $rect[3]) = ($rect[3],$rect[2]); #Swap W and H + push @newRgns, { + PersonDisplayName => $$rgn{PersonDisplayName}, + Rectangle => join(', ', @rect), + }; + } + return { Regions => \@newRgns }; + } + }, #end RotateMPRegionCW270 + }, +); + +1; #end diff --git a/ExifTool/config_files/tiff_version.config b/ExifTool/config_files/tiff_version.config new file mode 100644 index 0000000..b59158d --- /dev/null +++ b/ExifTool/config_files/tiff_version.config @@ -0,0 +1,121 @@ +#------------------------------------------------------------------------------ +# File: tiff_version.config +# +# Description: This config file contains the definition for a Composite +# TIFFVersion tag used to determine the version of a TIFF file +# +# Usage: exiftool -config tiff_version.config -tiffversion DIR +# +# Requires: ExifTool version 11.50 or later +# +# Revisions: 2019/06/06 - Bryan K. Williams (aka StarGeek) Created +# 2019/06/07 - P. Harvey added test of TIFF format types +# +# References: http://jhove.openpreservation.org/modules/tiff/#profiles +#------------------------------------------------------------------------------ + +# Checks if at least one the values passed are defined. +sub has_defined { + for my $i ( @_ ) { + return 1 if defined $i; + } + return 0; +} + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::Composite' => { + TIFFVersion => { + Require => { + 0 => 'FileType' + }, + Desire => { + # Version 5 + 1 => 'EXIF:Artist', # Artist (315/0x013b) + 2 => 'EXIF:ColorMap', # ColorMap (320/0x0140) + 3 => 'EXIF:ModifyDate', # DateTime (306/0x0132) Called DateTime by EXIF spec, ModifyDate by exiftool + 4 => 'EXIF:HostComputer', # HostComputer (316/0x013c) + 5 => 'EXIF:SubfileType', # NewSubfileType (254/0x00fe) Called NewSubfileType by TIFF spec, SubfileType by exiftool + 6 => 'EXIF:Predictor', # Predictor (317/0x013d) + 7 => 'EXIF:PrimaryChromaticities', # PrimaryChromaticities (319/0x013f) + 8 => 'EXIF:Software', # Software (305/0x0131) + 9 => 'EXIF:WhitePoint', # WhitePoint (318/0x013e) + # Version 6 + 10 => 'EXIF:Copyright', # Copyright (33432/0x8298) + 11 => 'EXIF:DotRange', # DotRange (336/0x0150) + 12 => 'EXIF:ExtraSamples', # ExtraSamples (338/0x0152) + 13 => 'EXIF:HalftoneHints', # HalftoneHints (321/0x0141) + 14 => 'EXIF:InkNames', # InkNames (333/0x014d) + 15 => 'EXIF:InkSet', # InkSet (332/0x014c) + 16 => 'EXIF:JPEGACTables', # JPEGACTables (521/0x0209) + 17 => 'EXIF:JPEGDCTables', # JPEGDCTables (520/0x0208) + 18 => 'EXIF:ThumbnailOffset', # JPEGInterchangeFormat (513/0x0201) + 19 => 'EXIF:ThumbnailLength ', # JPEGInterchangeFormatLength (514/0x0202) + 20 => 'EXIF:JPEGLosslessPredictors', # JPEGLosslessPredictors (517/0x0205) + 21 => 'EXIF:JPEGPointTransforms', # JPEGPointTransforms (518/0x0206) + 22 => 'EXIF:JPEGProc', # JPEGProc (512/0x0200) + 23 => 'EXIF:JPEGRestartInterval', # JPEGRestartInterval (515/0x0203) + 24 => 'EXIF:JPEGQTables', # JPEGQTables (519/0x0207) + 25 => 'EXIF:NumberofInks', # NumberOfInks (334/0x014e) + 26 => 'EXIF:ReferenceBlackWhite', # ReferenceBlackWhite (532/0x0214) + 27 => 'EXIF:SampleFormat', # SampleFormat (339/0x0153) + 28 => 'EXIF:SMinSampleValue', # SMinSampleValue (340/0x0154) + 29 => 'EXIF:SMaxSampleValue', # SMaxSampleValue (341/0x0155) + 30 => 'EXIF:TargetPrinter', # TargetPrinter (337/0x0151) + 31 => 'EXIF:TileLength', # TileLength (323/0x0143) + 32 => 'EXIF:TileOffsets', # TileOffsets (324/0x0144) + 33 => 'EXIF:TileWidth', # TileWidth (322/0x0142) + 34 => 'EXIF:TileByteCounts', # TileByteCounts (325/0x0145) + 35 => 'EXIF:TransferRange', # TransferRange (342/0x0156) + 36 => 'EXIF:YCbCrCoefficients', # YCbCrCoefficients (529/0x0211) + 37 => 'EXIF:YCbCrPositioning', # YCbCrPositioning (531/0x0213) + 38 => 'EXIF:YCbCrSubSampling', # YCbCrSubSampling (530/0x0212) + # Other tags to check + 39 => 'EXIF:PhotometricInterpretation', # PhotometricInterpretation (262/0x0106) + 40 => 'EXIF:Compression', # Compression (259/0x0103) + }, + ValueConv => q{ + if ($val[0] ne 'TIFF') { + return undef; + } + unless ($$self{SaveFormat}) { + return ''; + } + foreach (qw(int8s undef int16s int32s rational64s float double)) { + return '6.0' if $$self{SaveFormat}{$_}; + } + if ( $val[39]=~/^(?:C(?:IELab|MYK)|YCbCr)$/ or $val[40] eq 'JPEG (old-style)' or has_defined(@val[10..38]) ) { + return '6.0'; + } + if ($val[39]=~/^(?:Transparency Mask|RGB Palette)$/ or $val[40] eq 'LZW' or has_defined(@val[1..9]) ) { + return '5.0'; + } + return '4.0'; + }, + }, + }, +); + +# A couple of shortcuts to test for the existence of certain tags +%Image::ExifTool::UserDefined::Shortcuts = ( + TiffVersion5Tags => [ + 'EXIF:Artist', 'EXIF:ColorMap', 'EXIF:ModifyDate', 'EXIF:HostComputer', 'EXIF:SubfileType', + 'EXIF:Predictor', 'EXIF:PrimaryChromaticities', 'EXIF:Software', 'EXIF:WhitePoint', + ], + TiffVersion6Tags => [ + 'EXIF:Copyright', 'EXIF:DotRange', 'EXIF:ExtraSamples', 'EXIF:HalftoneHints', + 'EXIF:InkNames', 'EXIF:InkSet', 'EXIF:JPEGACTables', 'EXIF:JPEGDCTables', + 'EXIF:ThumbnailOffset', 'EXIF:ThumbnailLength ', 'EXIF:JPEGLosslessPredictors', + 'EXIF:JPEGPointTransforms', 'EXIF:JPEGProc', 'EXIF:JPEGRestartInterval', + 'EXIF:JPEGQTables', 'EXIF:NumberofInks', 'EXIF:ReferenceBlackWhite', 'EXIF:SampleFormat', + 'EXIF:SMinSampleValue', 'EXIF:SMaxSampleValue', 'EXIF:TargetPrinter', 'EXIF:TileLength', + 'EXIF:TileOffsets', 'EXIF:TileWidth', 'EXIF:TileByteCounts', 'EXIF:TransferRange', + 'EXIF:YCbCrCoefficients', 'EXIF:YCbCrPositioning', 'EXIF:YCbCrSubSampling', + ], +); + +# Must set SaveFormat option to provide access to the tag format types +%Image::ExifTool::UserDefined::Options = ( + SaveFormat => 1, +); + +1; #end diff --git a/ExifTool/config_files/time_zone.config b/ExifTool/config_files/time_zone.config new file mode 100644 index 0000000..bfe61b8 --- /dev/null +++ b/ExifTool/config_files/time_zone.config @@ -0,0 +1,96 @@ +#------------------------------------------------------------------------------ +# File: time_zone.config +# +# Description: ExifTool config file to return time zone from an image +# +# Notes: The Composite:TimeZone tag defined here attempts to determine +# the time zone for an image. If possible, the camera time zone +# is used, if that does not exist, the time zone for +# DateTimeOriginal is returned, but if this can't be determined +# then the time zone for CreateDate and then ModifyDate are used. +# If this all does not result in a time zone, the timezone of the +# TimeCreated tag is used +# +# Usage: exiftool -config time_zone.config -timezone FILE +# +# Requires: ExifTool version 7.74 or later +# +# Revisions: 2016/10/03 - P. Harvey Created +# 2016/12/12 - H. Baan Corrected tag name, added support for +# camera time zone/daylight savings info in +# MakerNotes +# 2017/01/05 - H. Baan Handle case were GPSDateStamp is missing +# 2017/01/06 - H. Baan Use QuickTime:CreationDate if available +# 2017/03/12 - H. Baan Added QuickTime:TimeZone, reordered +# Desired tags according to precedence, added +# comments +#------------------------------------------------------------------------------ + +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::Composite' => { + # Calculate the TimeZone of the picture taken + TimeZone => { + Desire => { + # TimeZone information tags ordered by precedence + 0 => 'MakerNotes:TimeZone', + 1 => 'MakerNotes:DaylightSavings', + 2 => 'QuickTime:TimeZone', + 3 => 'OffsetTimeOriginal', + 4 => 'OffsetTimeDigitized', + 5 => 'OffsetTime', + 6 => 'TimeZoneOffset', + 7 => 'GPSDateStamp', + 8 => 'GPSTimeStamp', + 9 => 'DateTimeOriginal', + 10 => 'DateTimeDigitized', + 11 => 'CreateDate', + 12 => 'ModifyDate', + 13 => 'QuickTime:CreationDate', + 14 => 'TimeCreated', + }, + RawConv => q{ + # TimeZone from MakeNotes (camera setting) + return TimeZoneString($val[0] + ($val[1] ? 60 : 0)) if defined $val[0]; + + # TimeZone from QuickTime (camera setting) + return TimeZoneString($val[2]) if $val[2]; + + # TimeZone from offset fields + return $val[3] if $val[3]; # DateTimeOriginal + return $val[4] if $val[4]; # DateTimeDigitized + return $val[5] if $val[5]; # (ModifyDate) + if (defined $val[6]) { # (ModifyDate) + my $tzh = $val[6]; + $tzh =~ s/ .*//; + return TimeZoneString($tzh * 60); + } + + # Difference between GPS and local time as TimeZone + if (defined $val[8]) { + my $loc = $val[9] || $val[10] || $val[11] || $val[12]; + if ($loc) { + my @loc = split /[: ]/, $loc; + my @gmt = split /[: ]/, ($val[7]||"$loc[0]:$loc[1]:$loc[2]") . " $val[8]"; + my $tzm = 15 * sprintf("%.0f", (GetTimeZone([@loc[5,4,3,2]], [@gmt[5,4,3,2]])) / 15); + $tzm -= 1440 if $tzm > 840; + $tzm += 1440 if $tzm < -720; + return TimeZoneString($tzm); + } + } + + # TimeZone from QuickTime Creation Date + if ($val[13] && $val[13] =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}([+-]\d{2})(\d{2})/) { + return TimeZoneString($1 * 60 + $2); + } + + # Time Created + if ($val[14] && $val[14] =~ /\d{6}([+-]\d{2})(\d{2})/) { + return TimeZoneString($1 * 60 + $2); + } + return undef; + }, + }, + }, +); + +1; #end diff --git a/ExifTool/exiftool b/ExifTool/exiftool new file mode 100755 index 0000000..84ae7bb --- /dev/null +++ b/ExifTool/exiftool @@ -0,0 +1,7460 @@ +#!/usr/bin/perl -w +#------------------------------------------------------------------------------ +# File: exiftool +# +# Description: Read/write meta information +# +# Revisions: Nov. 12/03 - P. Harvey Created +# (See html/history.html for revision history) +#------------------------------------------------------------------------------ +use strict; +use warnings; +require 5.004; + +my $version = '12.67'; + +# add our 'lib' directory to the include list BEFORE 'use Image::ExifTool' +my $exePath; +BEGIN { + # (undocumented -xpath option added in 11.91, must come before other options) + $exePath = @ARGV && lc($ARGV[0]) eq '-xpath' && shift() ? $^X : $0; + # get exe directory + my $exeDir = ($exePath =~ /(.*)[\\\/]/) ? $1 : '.'; + my $incDir = ($0 =~ /(.*)[\\\/]/) ? "$1/lib" : './lib'; + if (-l $0) { + my $lnk = eval { readlink $0 }; + if (defined $lnk) { + my $lnkDir = ($lnk =~ /(.*)[\\\/]/) ? $1 : '.'; + $exeDir = (($lnk =~ m(^/)) ? '' : $exeDir . '/') . $lnkDir; + $incDir = "$exeDir/lib"; + } + } + $Image::ExifTool::exeDir = $exeDir; # use our exeDir for loading config file + # add lib directory at start of include path + unshift @INC, $incDir; + # load or disable config file if specified + if (@ARGV and lc($ARGV[0]) eq '-config') { + shift; + $Image::ExifTool::configFile = shift; + } +} +use Image::ExifTool qw{:Public}; + +# function prototypes +sub SigInt(); +sub SigCont(); +sub Cleanup(); +sub GetImageInfo($$); +sub SetImageInfo($$$); +sub DoHardLink($$$$$); +sub CleanXML($); +sub EncodeXML($); +sub FormatXML($$$); +sub EscapeJSON($;$); +sub FormatJSON($$$); +sub PrintCSV(); +sub AddGroups($$$$); +sub ConvertBinary($); +sub IsEqual($$); +sub Infile($;$); +sub AddSetTagsFile($;$); +sub DoSetFromFile($$$); +sub CleanFilename($); +sub SetWindowTitle($); +sub ProcessFiles($;$); +sub ScanDir($$;$); +sub FindFileWindows($$); +sub FileNotFound($); +sub PreserveTime(); +sub AbsPath($); +sub MyConvertFileName($$); +sub SuggestedExtension($$$); +sub LoadPrintFormat($); +sub FilenameSPrintf($;$@); +sub NextUnusedFilename($;$); +sub CreateDirectory($); +sub OpenOutputFile($;@); +sub AcceptFile($); +sub SlurpFile($$); +sub FilterArgfileLine($); +sub ReadStayOpen($); +sub Progress($$); +sub PrintTagList($@); +sub PrintErrors($$$); + +$SIG{INT} = 'SigInt'; # do cleanup on Ctrl-C +$SIG{CONT} = 'SigCont'; # (allows break-out of delays) +END { + Cleanup(); +} + +# declare all static file-scope variables +my @commonArgs; # arguments common to all commands +my @condition; # conditional processing of files +my @csvFiles; # list of files when reading with CSV option (in ExifTool Charset) +my @csvTags; # order of tags for first file with CSV option (lower case) +my @delFiles; # list of files to delete +my @dynamicFiles; # list of -tagsFromFile files with dynamic names and -TAG<=FMT pairs +my @efile; # files for writing list of error/fail/same file names +my @exclude; # list of excluded tags +my (@echo3, @echo4);# stdout and stderr echo after processing is complete +my @files; # list of files and directories to scan +my @moreArgs; # more arguments to process after -stay_open -@ +my @newValues; # list of new tag values to set +my @requestTags; # tags to request (for -p or -if option arguments) +my @srcFmt; # source file name format strings +my @tags; # list of tags to extract +my %altFile; # alternate files to extract information (keyed by lower-case family 8 group) +my %appended; # list of files appended to +my %countLink; # count hard and symbolic links made +my %created; # list of files we created +my %csvTags; # lookup for all found tags with CSV option (lower case keys) +my %database; # lookup for database information based on file name (in ExifTool Charset) +my %filterExt; # lookup for filtered extensions +my %ignore; # directory names to ignore +my $ignoreHidden; # flag to ignore hidden files +my %outComma; # flag that output text file needs a comma +my %outTrailer; # trailer for output text file +my %preserveTime; # preserved timestamps for files +my %printFmt; # the contents of the print format file +my %seqFileDir; # file sequence number in each directory +my %setTags; # hash of list references for tags to set from files +my %setTagsList; # list of other tag lists for multiple -tagsFromFile from the same file +my %usedFileName; # lookup for file names we already used in TestName feature +my %utf8FileName; # lookup for file names that are UTF-8 encoded +my %warnedOnce; # lookup for once-only warnings +my %wext; # -W extensions to write +my $allGroup; # show group name for all tags +my $altEnc; # alternate character encoding if not UTF-8 +my $argFormat; # use exiftool argument-format output +my $binaryOutput; # flag for binary output (undef or 1, or 0 for binary XML/PHP) +my $binaryStdout; # flag set if we output binary to stdout +my $binSep; # separator used for list items in binary output +my $binTerm; # terminator used for binary output +my $comma; # flag set if we need a comma in JSON output +my $count; # count of files scanned when reading or deleting originals +my $countBad; # count of files with errors +my $countBadCr; # count files not created due to errors +my $countBadWr; # count write errors +my $countCopyWr; # count of files copied without being changed +my $countDir; # count of directories scanned +my $countFailed; # count files that failed condition +my $countGoodCr; # count files created OK +my $countGoodWr; # count files written OK +my $countNewDir; # count of directories created +my $countSameWr; # count files written OK but not changed +my $critical; # flag for critical operations (disable CTRL-C) +my $csv; # flag for CSV option (set to "CSV", or maybe "JSON" when writing) +my $csvAdd; # flag to add CSV information to existing lists +my $csvDelim; # delimiter for CSV files +my $csvSaveCount; # save counter for last CSV file loaded +my $deleteOrig; # 0=restore original files, 1=delete originals, 2=delete w/o asking +my $disableOutput; # flag to disable normal output +my $doSetFileName; # flag set if FileName may be written +my $doUnzip; # flag to extract info from .gz and .bz2 files +my ($end,$endDir,%endDir); # flags to end processing +my $escapeC; # C-style escape +my $escapeHTML; # flag to escape printed values for html +my $evalWarning; # warning from eval +my $executeID; # -execute ID number +my $failCondition; # flag to fail -if condition +my $fastCondition; # flag for fast -if condition +my $fileHeader; # header to print to output file (or console, once) +my $fileTrailer; # trailer for output file +my $filtered; # flag indicating file was filtered by name +my $filterFlag; # file filter flag (0x01=deny extensions, 0x02=allow extensions, 0x04=add ext) +my $fixLen; # flag to fix description lengths when writing alternate languages +my $forcePrint; # string to use for missing tag values (undef to not print them) +my $helped; # flag to avoid printing help if no tags specified +my $html; # flag for html-formatted output (2=html dump) +my $interrupted; # flag set if CTRL-C is pressed during a critical process +my $isBinary; # true if value is a SCALAR ref +my $isWriting; # flag set if we are writing tags +my $joinLists; # flag set to join list values into a single string +my $json; # flag for JSON/PHP output format (1=JSON, 2=PHP) +my $langOpt; # language option +my $listDir; # treat a directory as a regular file +my $listItem; # item number for extracting single item from a list +my $listSep; # list item separator (', ' by default) +my $mt; # main ExifTool object +my $multiFile; # non-zero if we are scanning multiple files +my $noBinary; # flag set to ignore binary tags +my $outFormat; # -1=Canon format, 0=same-line, 1=tag names, 2=values only +my $outOpt; # output file or directory name +my $overwriteOrig; # flag to overwrite original file (1=overwrite, 2=in place) +my $pause; # pause before returning +my $preserveTime; # flag to preserve times of updated files (2=preserve FileCreateDate only) +my $progress; # flag to calculate total files to process (0=calculate but don't display) +my $progressCount; # count of files processed +my $progressIncr; # increment for progress counter +my $progressMax; # total number of files to process +my $progressNext; # next progress count to output +my $progStr; # progress message string +my $quiet; # flag to disable printing of informational messages / warnings +my $rafStdin; # File::RandomAccess for stdin (if necessary to rewind) +my $recurse; # recurse into subdirectories (2=also hidden directories) +my $rtnVal; # command return value (0=success) +my $rtnValPrev; # previous command return value (0=success) +my $saveCount; # count the number of times we will/did call SaveNewValues() +my $scanWritable; # flag to process only writable file types +my $sectHeader; # current section header for -p option +my $sectTrailer; # section trailer for -p option +my $seqFileDir; # sequential file number used for %-C +my $seqFileNum; # sequential file number used for %C +my $setCharset; # character set setting ('default' if not set and -csv -b used) +my $showGroup; # number of group to show (may be zero or '') +my $showTagID; # non-zero to show tag ID's +my $stayOpenBuff='';# buffer for -stay_open file +my $stayOpenFile; # name of the current -stay_open argfile +my $structOpt; # output structured XMP information (JSON and XML output only) +my $tabFormat; # non-zero for tab output format +my $tagOut; # flag for separate text output file for each tag +my $textOut; # extension for text output file (or undef for no output) +my $textOverwrite; # flag to overwrite existing text output file (2=append, 3=over+append) +my $tmpFile; # temporary file to delete on exit +my $tmpText; # temporary text file +my $validFile; # flag indicating we processed a valid file +my $verbose; # verbose setting +my $vout; # verbose output file reference (\*STDOUT or \*STDERR) +my $windowTitle; # title for console window +my %wroteHEAD; # list of output txt files to which we wrote HEAD +my $xml; # flag for XML-formatted output + +# flag to keep the input -@ argfile open: +# 0 = normal behaviour +# 1 = received "-stay_open true" and waiting for argfile to keep open +# 2 = currently reading from STAYOPEN argfile +# 3 = waiting for -@ to switch to a new STAYOPEN argfile +my $stayOpen = 0; + +my $rtnValApp = 0; # app return value (0=success) +my $curTitle = ''; # current window title + +# lookup for O/S names which may use a backslash as a directory separator +# (ref File::Spec of PathTools-3.2701) +my %hasBackslash = ( MSWin32 => 1, os2 => 1, dos => 1, NetWare => 1, symbian => 1, cygwin => 1 ); + +# lookup for O/S names which use CR/LF newlines +my $isCRLF = { MSWin32 => 1, os2 => 1, dos => 1 }->{$^O}; + +# lookup for JSON characters that we escape specially +my %jsonChar = ( '"'=>'"', '\\'=>'\\', "\t"=>'t', "\n"=>'n', "\r"=>'r' ); + +# lookup for C-style escape sequences +my %escC = ( "\n" => '\n', "\r" => '\r', "\t" => '\t', '\\' => '\\\\'); +my %unescC = ( a => "\a", b => "\b", f => "\f", n => "\n", r => "\r", + t => "\t", 0 => "\0", '\\' => '\\' ); + +# options requiring additional arguments +# (used only to skip over these arguments when reading -stay_open ARGFILE) +# (arg is converted to lower case then tested again unless an entry was found with the same case) +my %optArgs = ( + '-tagsfromfile' => 1, '-addtagsfromfile' => 1, '-alltagsfromfile' => 1, + '-@' => 1, + '-api' => 1, + '-c' => 1, '-coordformat' => 1, + '-charset' => 0, # (optional arg; OK because arg cannot begin with "-") + '-config' => 1, + '-csvdelim' => 1, + '-d' => 1, '-dateformat' => 1, + '-D' => 0, # necessary to avoid matching lower-case equivalent + '-echo' => 1, '-echo#' => 1, + '-efile' => 1, '-efile#' => 1, '-efile!' => 1, '-efile#!' => 1, + '-ext' => 1, '--ext' => 1, '-ext+' => 1, '--ext+' => 1, + '-extension' => 1, '--extension' => 1, '-extension+' => 1, '--extension+' => 1, + '-fileorder' => 1, '-fileorder#' => 1, + '-geotag' => 1, + '-globaltimeshift' => 1, + '-i' => 1, '-ignore' => 1, + '-if' => 1, '-if#' => 1, + '-lang' => 0, # (optional arg; cannot begin with "-") + '-listitem' => 1, + '-o' => 1, '-out' => 1, + '-p' => 1, '-printformat' => 1, + '-P' => 0, + '-password' => 1, + '-require' => 1, + '-sep' => 1, '-separator' => 1, + '-srcfile' => 1, + '-stay_open' => 1, + '-use' => 1, + '-userparam' => 1, + '-w' => 1, '-w!' => 1, '-w+' => 1, '-w+!' => 1, '-w!+' => 1, + '-textout' => 1, '-textout!' => 1, '-textout+' => 1, '-textout+!' => 1, '-textout!+' => 1, + '-tagout' => 1, '-tagout!' => 1, '-tagout+' => 1, '-tagout+!' => 1, '-tagout!+' => 1, + '-wext' => 1, + '-wm' => 1, '-writemode' => 1, + '-x' => 1, '-exclude' => 1, + '-X' => 0, +); + +# recommended packages and alternatives +my @recommends = qw( + Archive::Zip + Compress::Zlib + Digest::MD5 + Digest::SHA + IO::Compress::Bzip2 + POSIX::strptime + Time::Local + Unicode::LineBreak + Compress::Raw::Lzma + IO::Compress::RawDeflate + IO::Uncompress::RawInflate + IO::Compress::Brotli + IO::Uncompress::Brotli + Win32::API + Win32::FindFile + Win32API::File +); +my %altRecommends = ( + 'POSIX::strptime' => 'Time::Piece', # (can use Time::Piece instead of POSIX::strptime) +); + +my %unescapeChar = ( 't'=>"\t", 'n'=>"\n", 'r'=>"\r" ); + +# special subroutines used in -if condition +sub Image::ExifTool::EndDir() { return $endDir = 1 } +sub Image::ExifTool::End() { return $end = 1 } + +# exit routine +sub Exit { + if ($pause) { + if (eval { require Term::ReadKey }) { + print STDERR "-- press any key --"; + Term::ReadKey::ReadMode('cbreak'); + Term::ReadKey::ReadKey(0); + Term::ReadKey::ReadMode(0); + print STDERR "\b \b" x 20; + } else { + print STDERR "-- press RETURN --\n"; + ; + } + } + exit shift; +} +# my warning and error routines (NEVER say "die"!) +sub Warn { + if ($quiet < 2 or $_[0] =~ /^Error/) { + my $oldWarn = $SIG{'__WARN__'}; + delete $SIG{'__WARN__'}; + warn(@_); + $SIG{'__WARN__'} = $oldWarn if defined $oldWarn; + } +} +sub Error { Warn @_; $rtnVal = 1; } +sub WarnOnce($) { + Warn(@_) and $warnedOnce{$_[0]} = 1 unless $warnedOnce{$_[0]}; +} + +# define signal handlers and cleanup routine +sub SigInt() { + $critical and $interrupted = 1, return; + Cleanup(); + exit 1; +} +sub SigCont() { } +sub Cleanup() { + $mt->Unlink($tmpFile) if defined $tmpFile; + $mt->Unlink($tmpText) if defined $tmpText; + undef $tmpFile; + undef $tmpText; + PreserveTime() if %preserveTime; + SetWindowTitle(''); +} + +#------------------------------------------------------------------------------ +# main script +# + +# isolate arguments common to all commands +if (grep /^-common_args$/i, @ARGV) { + my (@newArgs, $common, $end); + foreach (@ARGV) { + if (/^-common_args$/i and not $end) { + $common = 1; + } elsif ($common) { + push @commonArgs, $_; + } else { + $end = 1 if $_ eq '--'; + push @newArgs, $_; + } + } + @ARGV = @newArgs if $common; +} + +#.............................................................................. +# loop over sets of command-line arguments separated by "-execute" +Command: for (;;) { + +if (@echo3) { + my $str = join("\n", @echo3) . "\n"; + $str =~ s/\$\{status\}/$rtnVal/ig; + print STDOUT $str; +} +if (@echo4) { + my $str = join("\n", @echo4) . "\n"; + $str =~ s/\$\{status\}/$rtnVal/ig; + print STDERR $str; +} + +$rafStdin->Close() if $rafStdin; +undef $rafStdin; + +# save our previous return codes +$rtnValPrev = $rtnVal; +$rtnValApp = $rtnVal if $rtnVal; + +# exit Command loop now if we are all done processing commands +last unless @ARGV or not defined $rtnVal or $stayOpen >= 2 or @commonArgs; + +# attempt to restore text mode for STDOUT if necessary +if ($binaryStdout) { + binmode(STDOUT,':crlf') if $] >= 5.006 and $isCRLF; + $binaryStdout = 0; +} + +# flush console and print "{ready}" message if -stay_open is in effect +if ($stayOpen >= 2) { + if ($quiet and not defined $executeID) { + # flush output if possible + eval { require IO::Handle } and STDERR->flush(), STDOUT->flush(); + } else { + eval { require IO::Handle } and STDERR->flush(); + my $id = defined $executeID ? $executeID : ''; + my $save = $|; + $| = 1; # turn on output autoflush for stdout + print "{ready$id}\n"; + $| = $save; # restore original autoflush setting + } +} + +# initialize necessary static file-scope variables +# (not done: @commonArgs, @moreArgs, $critical, $binaryStdout, $helped, +# $interrupted, $mt, $pause, $rtnValApp, $rtnValPrev, $stayOpen, $stayOpenBuff, $stayOpenFile) +undef @condition; +undef @csvFiles; +undef @csvTags; +undef @delFiles; +undef @dynamicFiles; +undef @echo3; +undef @echo4; +undef @efile; +undef @exclude; +undef @files; +undef @newValues; +undef @srcFmt; +undef @tags; +undef %appended; +undef %countLink; +undef %created; +undef %csvTags; +undef %database; +undef %endDir; +undef %filterExt; +undef %ignore; +undef %outComma; +undef %outTrailer; +undef %printFmt; +undef %preserveTime; +undef %seqFileDir; +undef %setTags; +undef %setTagsList; +undef %usedFileName; +undef %utf8FileName; +undef %warnedOnce; +undef %wext; +undef $allGroup; +undef $altEnc; +undef $argFormat; +undef $binaryOutput; +undef $binSep; +undef $binTerm; +undef $comma; +undef $csv; +undef $csvAdd; +undef $deleteOrig; +undef $disableOutput; +undef $doSetFileName; +undef $doUnzip; +undef $end; +undef $endDir; +undef $escapeHTML; +undef $escapeC; +undef $evalWarning; +undef $executeID; +undef $failCondition; +undef $fastCondition; +undef $fileHeader; +undef $filtered; +undef $fixLen; +undef $forcePrint; +undef $ignoreHidden; +undef $joinLists; +undef $langOpt; +undef $listItem; +undef $multiFile; +undef $noBinary; +undef $outOpt; +undef $preserveTime; +undef $progress; +undef $progressCount; +undef $progressIncr; +undef $progressMax; +undef $progressNext; +undef $recurse; +undef $scanWritable; +undef $sectHeader; +undef $setCharset; +undef $showGroup; +undef $showTagID; +undef $structOpt; +undef $tagOut; +undef $textOut; +undef $textOverwrite; +undef $tmpFile; +undef $tmpText; +undef $validFile; +undef $verbose; +undef $windowTitle; + +$count = 0; +$countBad = 0; +$countBadCr = 0; +$countBadWr = 0; +$countCopyWr = 0; +$countDir = 0; +$countFailed = 0; +$countGoodCr = 0; +$countGoodWr = 0; +$countNewDir = 0; +$countSameWr = 0; +$csvDelim = ','; +$csvSaveCount = 0; +$fileTrailer = ''; +$filterFlag = 0; +$html = 0; +$isWriting = 0; +$json = 0; +$listSep = ', '; +$outFormat = 0; +$overwriteOrig = 0; +$progStr = ''; +$quiet = 0; +$rtnVal = 0; +$saveCount = 0; +$sectTrailer = ''; +$seqFileDir = 0; +$seqFileNum = 0; +$tabFormat = 0; +$vout = \*STDOUT; +$xml = 0; + +# define local variables used only in this command loop +my @fileOrder; # tags to use for ordering of input files +my $fileOrderFast; # -fast level for -fileOrder option +my $addGeotime; # automatically added geotime argument +my $doGlob; # flag set to do filename wildcard expansion +my $endOfOpts; # flag set if "--" option encountered +my $escapeXML; # flag to escape printed values for xml +my $setTagsFile; # filename for last TagsFromFile option +my $sortOpt; # sort option is used +my $srcStdin; # one of the source files is STDIN +my $useMWG; # flag set if we are using any MWG tag + +my ($argsLeft, @nextPass, $badCmd); +my $pass = 0; + +# for Windows, use globbing for wildcard expansion if available - MK/20061010 +if ($^O eq 'MSWin32' and eval { require File::Glob }) { + # override the core glob forcing case insensitivity + import File::Glob qw(:globally :nocase); + $doGlob = 1; +} + +$mt = new Image::ExifTool; # create ExifTool object + +# don't extract duplicates by default unless set by UserDefined::Options +$mt->Options(Duplicates => 0) unless %Image::ExifTool::UserDefined::Options + and defined $Image::ExifTool::UserDefined::Options{Duplicates}; + +# default is to join lists if the List option was set to zero in the config file +$joinLists = 1 if defined $mt->Options('List') and not $mt->Options('List'); + +# preserve FileCreateDate if possible +if (not $preserveTime and $^O eq 'MSWin32') { + $preserveTime = 2 if eval { require Win32::API } and eval { require Win32API::File }; +} + +# parse command-line options in 2 passes... +# pass 1: set all of our ExifTool options +# pass 2: print all of our help and informational output (-list, -ver, etc) +for (;;) { + + # execute the command now if no more arguments or -execute is used + if (not @ARGV or ($ARGV[0] =~ /^(-|\xe2\x88\x92)execute(\d+)?$/i and not $endOfOpts)) { + if (@ARGV) { + $executeID = $2; # save -execute number for "{ready}" response + $helped = 1; # don't show help if we used -execute + $badCmd and shift, $rtnVal=1, next Command; + } elsif ($stayOpen >= 2) { + ReadStayOpen(\@ARGV); # read more arguments from -stay_open file + next; + } elsif ($badCmd) { + undef @commonArgs; # all done. Flush common arguments + $rtnVal = 1; + next Command; + } + if ($pass == 0) { + # insert common arguments now if not done already + if (@commonArgs and not defined $argsLeft) { + # count the number of arguments remaining for subsequent commands + $argsLeft = scalar(@ARGV) + scalar(@moreArgs); + unshift @ARGV, @commonArgs; + # all done with commonArgs if this is the end of the command + undef @commonArgs unless $argsLeft; + next; + } + # check if we have more arguments now than we did before we processed + # the common arguments. If so, then we have an infinite processing loop + if (defined $argsLeft and $argsLeft < scalar(@ARGV) + scalar(@moreArgs)) { + Warn "Ignoring -common_args from $ARGV[0] onwards to avoid infinite recursion\n"; + while ($argsLeft < scalar(@ARGV) + scalar(@moreArgs)) { + @ARGV and shift(@ARGV), next; + shift @moreArgs; + } + } + # require MWG module if used in any argument + # (note: doesn't cover the -p option because these tags will be parsed on the 2nd pass) + $useMWG = 1 if not $useMWG and grep /^mwg:/i, @tags, @requestTags; + if ($useMWG) { + require Image::ExifTool::MWG; + Image::ExifTool::MWG::Load(); + } + # update necessary variables for 2nd pass + if (defined $forcePrint) { + unless (defined $mt->Options('MissingTagValue')) { + $mt->Options(MissingTagValue => '-'); + } + $forcePrint = $mt->Options('MissingTagValue'); + } + } + if (@nextPass) { + # process arguments which were deferred to the next pass + unshift @ARGV, @nextPass; + undef @nextPass; + undef $endOfOpts; + ++$pass; + next; + } + @ARGV and shift; # remove -execute from argument list + last; # process the command now + } + $_ = shift; + next if $badCmd; # flush remaining arguments if aborting this command + + # allow funny dashes (nroff dash bug for cut-n-paste from pod) + if (not $endOfOpts and s/^(-|\xe2\x88\x92)//) { + s/^\xe2\x88\x92/-/; # translate double-dash too + if ($_ eq '-') { + $pass or push @nextPass, '--'; + $endOfOpts = 1; + next; + } + my $a = lc $_; + if (/^list([wfrdx]|wf|g(\d*))?$/i) { + $pass or push @nextPass, "-$_"; + my $type = lc($1 || ''); + if (not $type or $type eq 'w' or $type eq 'x') { + my $group; + if ($ARGV[0] and $ARGV[0] =~ /^(-|\xe2\x88\x92)(.+):(all|\*)$/i) { + if ($pass == 0) { + $useMWG = 1 if lc($2) eq 'mwg'; + push @nextPass, shift; + next; + } + $group = $2; + shift; + $group =~ /IFD/i and Warn("Can't list tags for specific IFD\n"), $helped=1, next; + $group =~ /^(all|\*)$/ and undef $group; + } else { + $pass or next; + } + $helped = 1; + if ($type eq 'x') { + require Image::ExifTool::TagInfoXML; + my %opts; + $opts{Flags} = 1 if defined $forcePrint; + $opts{NoDesc} = 1 if $outFormat > 0; + $opts{Lang} = $langOpt; + Image::ExifTool::TagInfoXML::Write(undef, $group, %opts); + next; + } + my $wr = ($type eq 'w'); + my $msg = ($wr ? 'Writable' : 'Available') . ($group ? " $group" : '') . ' tags'; + PrintTagList($msg, $wr ? GetWritableTags($group) : GetAllTags($group)); + # also print shortcuts if listing all tags + next if $group or $wr; + my @tagList = GetShortcuts(); + PrintTagList('Command-line shortcuts', @tagList) if @tagList; + next; + } + $pass or next; + $helped = 1; + if ($type eq 'wf') { + my @wf; + CanWrite($_) and push @wf, $_ foreach GetFileType(); + PrintTagList('Writable file extensions', @wf); + } elsif ($type eq 'f') { + PrintTagList('Supported file extensions', GetFileType()); + } elsif ($type eq 'r') { + PrintTagList('Recognized file extensions', GetFileType(undef, 0)); + } elsif ($type eq 'd') { + PrintTagList('Deletable groups', GetDeleteGroups()); + } else { # 'g(\d*)' + # list all groups in specified family + my $family = $2 || 0; + PrintTagList("Groups in family $family", $mt->GetAllGroups($family)); + } + next; + } + if ($a eq 'ver') { + $pass or push(@nextPass,'-ver'), next; + my $libVer = $Image::ExifTool::VERSION; + my $str = $libVer eq $version ? '' : " [Warning: Library version is $libVer]"; + if ($verbose) { + print "ExifTool version $version$str$Image::ExifTool::RELEASE\n"; + printf "Perl version %s%s\n", $], (defined ${^UNICODE} ? " (-C${^UNICODE})" : ''); + print "Platform: $^O\n"; + if ($verbose > 8) { + print "Current Dir: " . Cwd::getcwd() . "\n" if (eval { require Cwd }); + print "Script Name: $0\n"; + print "Exe Name: $^X\n"; + print "Exe Dir: $Image::ExifTool::exeDir\n"; + print "Exe Path: $exePath\n"; + } + print "Optional libraries:\n"; + foreach (@recommends) { + next if /^Win32/ and $^O ne 'MSWin32'; + my $ver = eval "require $_ and \$${_}::VERSION"; + my $alt = $altRecommends{$_}; + # check for alternative if primary not available + $ver = eval "require $alt and \$${alt}::VERSION" and $_ = $alt if not $ver and $alt; + printf " %-28s %s\n", $_, $ver || '(not installed)'; + } + if ($verbose > 1) { + print "Include directories:\n"; + ref $_ or print " $_\n" foreach @INC; + } + } else { + print "$version$str$Image::ExifTool::RELEASE\n"; + } + $helped = 1; + next; + } + if (/^(all|add)?tagsfromfile(=.*)?$/i) { + $setTagsFile = $2 ? substr($2,1) : (@ARGV ? shift : ''); + if ($setTagsFile eq '') { + Error("File must be specified for -tagsFromFile option\n"); + $badCmd = 1; + next; + } + # create necessary lists, etc for this new -tagsFromFile file + AddSetTagsFile($setTagsFile, { Replace => ($1 and lc($1) eq 'add') ? 0 : 1 } ); + next; + } + if ($a eq '@') { + my $argFile = shift or Error("Expecting filename for -\@ option\n"), $badCmd=1, next; + # switch to new ARGFILE if using chained -stay_open options + if ($stayOpen == 1) { + # defer remaining arguments until we close this argfile + @moreArgs = @ARGV; + undef @ARGV; + } elsif ($stayOpen == 3) { + if ($stayOpenFile and $stayOpenFile ne '-' and $argFile eq $stayOpenFile) { + # don't allow user to switch to the same -stay_open argfile + # because it will result in endless recursion + $stayOpen = 2; + Warn "Ignoring request to switch to the same -stay_open ARGFILE ($argFile)\n"; + next; + } + close STAYOPEN; + $stayOpen = 1; # switch to this -stay_open file + } + my $fp = ($stayOpen == 1 ? \*STAYOPEN : \*ARGFILE); + unless ($mt->Open($fp, $argFile)) { + unless ($argFile !~ /^\// and $mt->Open($fp, "$Image::ExifTool::exeDir/$argFile")) { + Error "Error opening arg file $argFile\n"; + $badCmd = 1; + next + } + } + if ($stayOpen == 1) { + $stayOpenFile = $argFile; # remember the name of the file we have open + $stayOpenBuff = ''; # initialize buffer for reading this file + $stayOpen = 2; + $helped = 1; + ReadStayOpen(\@ARGV); + next; + } + my (@newArgs, $didBOM); + foreach () { + # filter Byte Order Mark if it exists from start of UTF-8 text file + unless ($didBOM) { + s/^\xef\xbb\xbf//; + $didBOM = 1; + } + $_ = FilterArgfileLine($_); + push @newArgs, $_ if defined $_; + } + close ARGFILE; + unshift @ARGV, @newArgs; + next; + } + /^(-?)(a|duplicates)$/i and $mt->Options(Duplicates => ($1 ? 0 : 1)), next; + if ($a eq 'api') { + my $opt = shift; + if (defined $opt and length $opt) { + my $val = ($opt =~ s/=(.*)//s) ? $1 : 1; + # empty string means an undefined value unless ^= is used + $val = undef unless $opt =~ s/\^$// or length $val; + $mt->Options($opt => $val); + } else { + print "Available API Options:\n"; + my $availableOptions = Image::ExifTool::AvailableOptions(); + printf(" %-17s - %s\n", $$_[0], $$_[2]) foreach @$availableOptions; + $helped = 1; + } + next; + } + /^arg(s|format)$/i and $argFormat = 1, next; + if (/^(-?)b(inary)?$/i) { + ($binaryOutput, $noBinary) = $1 ? (undef, 1) : (1, undef); + $mt->Options(Binary => $binaryOutput, NoPDFList => $binaryOutput); + next; + } + if (/^c(oordFormat)?$/i) { + my $fmt = shift; + $fmt or Error("Expecting coordinate format for -c option\n"), $badCmd=1, next; + $mt->Options('CoordFormat', $fmt); + next; + } + if ($a eq 'charset') { + my $charset = (@ARGV and $ARGV[0] !~ /^(-|\xe2\x88\x92)/) ? shift : undef; + if (not $charset) { + $pass or push(@nextPass, '-charset'), next; + my %charsets; + $charsets{$_} = 1 foreach values %Image::ExifTool::charsetName; + PrintTagList('Available character sets', sort keys %charsets); + $helped = 1; + } elsif ($charset !~ s/^(\w+)=// or lc($1) eq 'exiftool') { + { + local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] }; + undef $evalWarning; + $mt->Options(Charset => $charset); + } + if ($evalWarning) { + Warn $evalWarning; + } else { + $setCharset = $mt->Options('Charset'); + } + } else { + # set internal encoding of specified metadata type + my $type = { id3 => 'ID3', iptc => 'IPTC', exif => 'EXIF', filename => 'FileName', + photoshop => 'Photoshop', quicktime => 'QuickTime', riff=>'RIFF' }->{lc $1}; + $type or Warn("Unknown type for -charset option: $1\n"), next; + $mt->Options("Charset$type" => $charset); + } + next; + } + /^config$/i and Warn("Ignored -config option (not first on command line)\n"), shift, next; + if (/^csv(\+?=.*)?$/i) { + my $csvFile = $1; + # must process on 2nd pass so -f and -charset options are available + unless ($pass) { + push @nextPass, "-$_"; + if ($csvFile) { + push @newValues, { SaveCount => ++$saveCount }; # marker to save new values now + $csvSaveCount = $saveCount; + } + next; + } + if ($csvFile) { + $csvFile =~ s/^(\+?=)//; + $csvAdd = 2 if $1 eq '+='; + $vout = \*STDERR if $srcStdin; + $verbose and print $vout "Reading CSV file $csvFile\n"; + my $msg; + if ($mt->Open(\*CSVFILE, $csvFile)) { + binmode CSVFILE; + require Image::ExifTool::Import; + $msg = Image::ExifTool::Import::ReadCSV(\*CSVFILE, \%database, $forcePrint, $csvDelim); + close(CSVFILE); + } else { + $msg = "Error opening CSV file '${csvFile}'"; + } + $msg and Warn("$msg\n"); + $isWriting = 1; + } + $csv = 'CSV'; + next; + } + if (/^csvdelim$/i) { + $csvDelim = shift; + defined $csvDelim or Error("Expecting argument for -csvDelim option\n"), $badCmd=1, next; + $csvDelim =~ /"/ and Error("CSV delimiter can not contain a double quote\n"), $badCmd=1, next; + my %unescape = ( 't'=>"\t", 'n'=>"\n", 'r'=>"\r", '\\' => '\\' ); + $csvDelim =~ s/\\(.)/$unescape{$1}||"\\$1"/sge; + $mt->Options(CSVDelim => $csvDelim); + next; + } + if (/^d$/ or $a eq 'dateformat') { + my $fmt = shift; + $fmt or Error("Expecting date format for -d option\n"), $badCmd=1, next; + $mt->Options('DateFormat', $fmt); + next; + } + (/^D$/ or $a eq 'decimal') and $showTagID = 'D', next; + /^delete_original(!?)$/i and $deleteOrig = ($1 ? 2 : 1), next; + /^list_dir$/i and $listDir = 1, next; + (/^e$/ or $a eq '-composite') and $mt->Options(Composite => 0), next; + (/^-e$/ or $a eq 'composite') and $mt->Options(Composite => 1), next; + (/^E$/ or $a eq 'escapehtml') and require Image::ExifTool::HTML and $escapeHTML = 1, next; + ($a eq 'ec' or $a eq 'escapec') and $escapeC = 1, next; + ($a eq 'ex' or $a eq 'escapexml') and $escapeXML = 1, next; + if (/^echo(\d)?$/i) { + my $n = $1 || 1; + my $arg = shift; + next unless defined $arg; + $n > 4 and Warn("Invalid -echo number\n"), next; + if ($n > 2) { + $n == 3 ? push(@echo3, $arg) : push(@echo4, $arg); + } else { + print {$n==2 ? \*STDERR : \*STDOUT} $arg, "\n"; + } + $helped = 1; + next; + } + if (/^(ee|extractembedded)(\d*)$/i) { + $mt->Options(ExtractEmbedded => $2 || 1); + $mt->Options(Duplicates => 1); + next; + } + if (/^efile(\d+)?(!)?$/i) { + my $arg = shift; + defined $arg or Error("Expecting file name for -$_ option\n"), $badCmd=1, next; + $efile[0] = $arg if not $1 or $1 & 0x01;# error + $efile[1] = $arg if $1 and $1 & 0x02; # unchanged + $efile[2] = $arg if $1 and $1 & 0x04; # failed -if condition + $efile[3] = $arg if $1 and $1 & 0x08; # updated + $efile[4] = $arg if $1 and $1 & 0x016; # created + unlink $arg if $2; + next; + } + # (-execute handled at top of loop) + if (/^-?ext(ension)?(\+)?$/i) { + my $ext = shift; + defined $ext or Error("Expecting extension for -ext option\n"), $badCmd=1, next; + my $flag = /^-/ ? 0 : ($2 ? 2 : 1); + $filterFlag |= (0x01 << $flag); + $ext =~ s/^\.//; # remove leading '.' if it exists + $filterExt{uc($ext)} = $flag ? 1 : 0; + next; + } + if (/^f$/ or $a eq 'forceprint') { + $forcePrint = 1; + next; + } + if (/^F([-+]?\d*)$/ or /^fixbase([-+]?\d*)$/i) { + $mt->Options(FixBase => $1); + next; + } + if (/^fast(\d*)$/i) { + $mt->Options(FastScan => (length $1 ? $1 : 1)); + next; + } + if (/^(file\d+)$/i) { + $altFile{lc $1} = shift or Error("Expecting file name for -file option\n"), $badCmd=1, next; + next; + } + if (/^fileorder(\d*)$/i) { + push @fileOrder, shift if @ARGV; + my $num = $1 || 0; + $fileOrderFast = $num if not defined $fileOrderFast or $fileOrderFast > $num; + next; + } + $a eq 'globaltimeshift' and $mt->Options(GlobalTimeShift => shift), next; + if (/^(g)(roupHeadings|roupNames)?([\d:]*)$/i) { + $showGroup = $3 || 0; + $allGroup = ($2 ? lc($2) eq 'roupnames' : $1 eq 'G'); + $mt->Options(SavePath => 1) if $showGroup =~ /\b5\b/; + $mt->Options(SaveFormat => 1) if $showGroup =~ /\b6\b/; + next; + } + if ($a eq 'geotag') { + my $trkfile = shift; + unless ($pass) { + # defer to next pass so the filename charset is available + push @nextPass, '-geotag', $trkfile; + next; + } + $trkfile or Error("Expecting file name for -geotag option\n"), $badCmd=1, next; + # allow wildcards in filename + if ($trkfile =~ /[*?]/) { + # CORE::glob() splits on white space, so use File::Glob if possible + my @trks; + if ($^O eq 'MSWin32' and eval { require Win32::FindFile }) { + # ("-charset filename=UTF8" must be set for this to work with Unicode file names) + @trks = FindFileWindows($mt, $trkfile); + } elsif (eval { require File::Glob }) { + @trks = File::Glob::bsd_glob($trkfile); + } else { + @trks = glob($trkfile); + } + @trks or Error("No matching file found for -geotag option\n"), $badCmd=1, next; + push @newValues, 'geotag='.shift(@trks) while @trks > 1; + $trkfile = pop(@trks); + } + $_ = "geotag=$trkfile"; + # (fall through!) + } + if (/^h$/ or $a eq 'htmlformat') { + require Image::ExifTool::HTML; + $html = $escapeHTML = 1; + $json = $xml = 0; + next; + } + (/^H$/ or $a eq 'hex') and $showTagID = 'H', next; + if (/^htmldump([-+]?\d+)?$/i) { + $verbose = ($verbose || 0) + 1; + $html = 2; + $mt->Options(HtmlDumpBase => $1) if defined $1; + next; + } + if (/^i(gnore)?$/i) { + my $dir = shift; + defined $dir or Error("Expecting directory name for -i option\n"), $badCmd=1, next; + $ignore{$dir} = 1; + $dir eq 'HIDDEN' and $ignoreHidden = 1; + next; + } + if (/^if(\d*)$/i) { + my $cond = shift; + my $fast = length($1) ? $1 : undef; + defined $cond or Error("Expecting expression for -if option\n"), $badCmd=1, next; + # use lowest -fast setting if multiple conditions + if (not @condition or not defined $fast or (defined $fastCondition and $fastCondition > $fast)) { + $fastCondition = $fast; + } + # prevent processing file unnecessarily for simple case of failed '$ok' or 'not $ok' + $cond =~ /^\s*(not\s*)\$ok\s*$/i and ($1 xor $rtnValPrev) and $failCondition=1; + # add to list of requested tags + push @requestTags, $cond =~ /\$\{?((?:[-\w]+:)*[-\w?*]+)/g; + push @condition, $cond; + next; + } + if (/^j(son)?(\+?=.*)?$/i) { + if ($2) { + # must process on 2nd pass because we need -f and -charset options + unless ($pass) { + push @nextPass, "-$_"; + push @newValues, { SaveCount => ++$saveCount }; # marker to save new values now + $csvSaveCount = $saveCount; + next; + } + my $jsonFile = $2; + $jsonFile =~ s/^(\+?=)//; + $csvAdd = 2 if $1 eq '+='; + $vout = \*STDERR if $srcStdin; + $verbose and print $vout "Reading JSON file $jsonFile\n"; + my $chset = $mt->Options('Charset'); + my $msg; + if ($mt->Open(\*JSONFILE, $jsonFile)) { + binmode JSONFILE; + require Image::ExifTool::Import; + $msg = Image::ExifTool::Import::ReadJSON(\*JSONFILE, \%database, $forcePrint, $chset); + close(JSONFILE); + } else { + $msg = "Error opening JSON file '${jsonFile}'"; + } + $msg and Warn("$msg\n"); + $isWriting = 1; + $csv = 'JSON'; + } else { + $json = 1; + $html = $xml = 0; + $mt->Options(Duplicates => 1); + require Image::ExifTool::XMP; # for FixUTF8() + } + next; + } + /^(k|pause)$/i and $pause = 1, next; + (/^l$/ or $a eq 'long') and --$outFormat, next; + (/^L$/ or $a eq 'latin') and $mt->Options(Charset => 'Latin'), next; + if ($a eq 'lang') { + $langOpt = (@ARGV and $ARGV[0] !~ /^(-|\xe2\x88\x92)/) ? shift : undef; + if ($langOpt) { + # make lower case and use underline as a separator (eg. 'en_ca') + $langOpt =~ tr/-A-Z/_a-z/; + $mt->Options(Lang => $langOpt); + next if $langOpt eq $mt->Options('Lang'); + } else { + $pass or push(@nextPass, '-lang'), next; + } + my $langs = "Available languages:\n"; + $langs .= " $_ - $Image::ExifTool::langName{$_}\n" foreach @Image::ExifTool::langs; + $langs =~ tr/_/-/; # display dashes instead of underlines in language codes + $langs = Image::ExifTool::HTML::EscapeHTML($langs) if $escapeHTML; + $langs = $mt->Decode($langs, 'UTF8'); + $langOpt and Error("Invalid or unsupported language '${langOpt}'.\n$langs"), $badCmd=1, next; + print $langs; + $helped = 1; + next; + } + if ($a eq 'listitem') { + my $li = shift; + defined $li and Image::ExifTool::IsInt($li) or Warn("Expecting integer for -listItem option\n"), next; + $mt->Options(ListItem => $li); + $listItem = $li; + next; + } + /^(m|ignoreminorerrors)$/i and $mt->Options(IgnoreMinorErrors => 1), next; + /^(n|-printconv)$/i and $mt->Options(PrintConv => 0), next; + /^(-n|printconv)$/i and $mt->Options(PrintConv => 1), next; + $a eq 'nop' and $helped=1, next; # (undocumented) no operation, added in 11.25 + if (/^o(ut)?$/i) { + $outOpt = shift; + defined $outOpt or Error("Expected output file or directory name for -o option\n"), $badCmd=1, next; + CleanFilename($outOpt); + # verbose messages go to STDERR of output is to console + $vout = \*STDERR if $vout =~ /^-(\.\w+)?$/; + next; + } + /^overwrite_original$/i and $overwriteOrig = 1, next; + /^overwrite_original_in_place$/i and $overwriteOrig = 2, next; + if (/^p$/ or $a eq 'printformat') { + my $fmt = shift; + if ($pass) { + LoadPrintFormat($fmt); + # load MWG module now if necessary + if (not $useMWG and grep /^mwg:/i, @requestTags) { + $useMWG = 1; + require Image::ExifTool::MWG; + Image::ExifTool::MWG::Load(); + } + } else { + # defer to next pass so the filename charset is available + push @nextPass, '-p', $fmt; + } + next; + } + (/^P$/ or $a eq 'preserve') and $preserveTime = 1, next; + /^password$/i and $mt->Options(Password => shift), next; + if (/^progress(\d*)(:.*)?$/i) { + $progressIncr = $1 || 1; + $progressNext = 0; # start showing progress at the first file + if ($2) { + $windowTitle = substr $2, 1; + $windowTitle = 'ExifTool %p%%' unless length $windowTitle; + $windowTitle =~ /%\d*[bpr]/ and $progress = 0 unless defined $progress; + } else { + $progress = 1; + $verbose = 0 unless defined $verbose; + } + $progressCount = 0; + next; + } + /^q(uiet)?$/i and ++$quiet, next; + /^r(ecurse)?(\.?)$/i and $recurse = ($2 ? 2 : 1), next; + if ($a eq 'require') { # (undocumented) added in version 8.65 + my $ver = shift; + unless (defined $ver and Image::ExifTool::IsFloat($ver)) { + Error("Expecting version number for -require option\n"); + $badCmd = 1; + next; + } + unless ($Image::ExifTool::VERSION >= $ver) { + Error("Requires ExifTool version $ver or later\n"); + $badCmd = 1; + } + next; + } + /^restore_original$/i and $deleteOrig = 0, next; + (/^S$/ or $a eq 'veryshort') and $outFormat+=2, next; + /^s(hort)?(\d*)$/i and $outFormat = $2 eq '' ? $outFormat + 1 : $2, next; + /^scanforxmp$/i and $mt->Options(ScanForXMP => 1), next; + if (/^sep(arator)?$/i) { + my $sep = $listSep = shift; + defined $listSep or Error("Expecting list item separator for -sep option\n"), $badCmd=1, next; + $sep =~ s/\\(.)/$unescapeChar{$1}||$1/sge; # translate escape sequences + (defined $binSep ? $binTerm : $binSep) = $sep; + $mt->Options(ListSep => $listSep); + $joinLists = 1; + # also split when writing values + my $listSplit = quotemeta $listSep; + # a space in the string matches zero or more whitespace characters + $listSplit =~ s/(\\ )+/\\s\*/g; + # but a single space alone matches one or more whitespace characters + $listSplit = '\\s+' if $listSplit eq '\\s*'; + $mt->Options(ListSplit => $listSplit); + next; + } + /^(-)?sort$/i and $sortOpt = $1 ? 0 : 1, next; + if ($a eq 'srcfile') { + @ARGV or Warn("Expecting FMT for -srcfile option\n"), next; + push @srcFmt, shift; + next; + } + if ($a eq 'stay_open') { + my $arg = shift; + defined $arg or Warn("Expecting argument for -stay_open option\n"), next; + if ($arg =~ /^(1|true)$/i) { + if (not $stayOpen) { + $stayOpen = 1; + } elsif ($stayOpen == 2) { + $stayOpen = 3; # chained -stay_open options + } else { + Warn "-stay_open already active\n"; + } + } elsif ($arg =~ /^(0|false)$/i) { + if ($stayOpen >= 2) { + # close -stay_open argfile and process arguments up to this point + close STAYOPEN; + push @ARGV, @moreArgs; + undef @moreArgs; + } elsif (not $stayOpen) { + Warn("-stay_open wasn't active\n"); + } + $stayOpen = 0; + } else { + Warn "Invalid argument for -stay_open\n"; + } + next; + } + if (/^(-)?struct$/i) { + $mt->Options(Struct => $1 ? 0 : 1); + next; + } + /^t(ab)?$/ and $tabFormat = 1, next; + if (/^T$/ or $a eq 'table') { + $tabFormat = $forcePrint = 1; $outFormat+=2; ++$quiet; + next; + } + if (/^(u)(nknown(2)?)?$/i) { + my $inc = ($3 or (not $2 and $1 eq 'U')) ? 2 : 1; + $mt->Options(Unknown => $mt->Options('Unknown') + $inc); + next; + } + if ($a eq 'use') { + my $module = shift; + $module or Error("Expecting module name for -use option\n"), $badCmd=1, next; + lc $module eq 'mwg' and $useMWG = 1, next; + $module =~ /[^\w:]/ and Error("Invalid module name: $module\n"), $badCmd=1, next; + local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] }; + unless (eval "require Image::ExifTool::$module" or + eval "require $module" or + eval "require '${module}'") + { + Error("Error using module $module\n"); + $badCmd = 1; + } + next; + } + if ($a eq 'userparam') { + my $opt = shift; + defined $opt or Error("Expected parameter for -userParam option\n"), $badCmd=1, next; + $opt =~ /=/ or $opt .= '=1'; + $mt->Options(UserParam => $opt); + next; + } + if (/^v(erbose)?(\d*)$/i) { + $verbose = ($2 eq '') ? ($verbose || 0) + 1 : $2; + next; + } + if (/^(w|textout|tagout)([!+]*)$/i) { + # (note: all logic ignores $textOut of 0 or '') + $textOut = shift || Warn("Expecting argument for -$_ option\n"); + my ($t1, $t2) = ($1, $2); + $textOverwrite = 0; + $textOverwrite += 1 if $t2 =~ /!/; # overwrite + $textOverwrite += 2 if $t2 =~ /\+/; # append + if ($t1 ne 'W' and lc($t1) ne 'tagout') { + undef $tagOut; + } elsif ($textOverwrite >= 2 and $textOut !~ /%[-+]?\d*[.:]?\d*[lu]?[tgso]/) { + $tagOut = 0; # append tags to one file + } else { + $tagOut = 1; # separate file for each tag + } + next; + } + if (/^(-?)(wext|tagoutext)$/i) { + my $ext = shift; + defined $ext or Error("Expecting extension for -wext option\n"), $badCmd=1, next; + my $flag = 1; + $1 and $wext{'*'} = 1, $flag = -1; + $ext =~ s/^\.//; + $wext{lc $ext} = $flag; + next; + } + if ($a eq 'wm' or $a eq 'writemode') { + my $wm = shift; + defined $wm or Error("Expecting argument for -$_ option\n"), $badCmd=1, next; + $wm =~ /^[wcg]*$/i or Error("Invalid argument for -$_ option\n"), $badCmd=1, next; + $mt->Options(WriteMode => $wm); + next; + } + if (/^x$/ or $a eq 'exclude') { + my $tag = shift; + defined $tag or Error("Expecting tag name for -x option\n"), $badCmd=1, next; + $tag =~ s/\ball\b/\*/ig; # replace 'all' with '*' in tag names + if ($setTagsFile) { + push @{$setTags{$setTagsFile}}, "-$tag"; + } else { + push @exclude, $tag; + } + next; + } + (/^X$/ or $a eq 'xmlformat') and $xml = 1, $html = $json = 0, $mt->Options(Duplicates => 1), next; + if (/^php$/i) { + $json = 2; + $html = $xml = 0; + $mt->Options(Duplicates => 1); + next; + } + if (/^z(ip)?$/i) { + $doUnzip = 1; + $mt->Options(Compress => 1, XMPShorthand => 1); + $mt->Options(Compact => 1) unless $mt->Options('Compact'); + next; + } + $_ eq '' and push(@files, '-'), $srcStdin = 1, next; # read STDIN + length $_ eq 1 and $_ ne '*' and Error("Unknown option -$_\n"), $badCmd=1, next; + if (/^[^<]+( ++$saveCount }; + } + push @newValues, $_; + if (/^mwg:/i) { + $useMWG = 1; + } elsif (/^([-\w]+:)*(filename|directory|testname)\b/i) { + $doSetFileName = 1; + } elsif (/^([-\w]+:)*(geotag|geotime|geosync)\b/i) { + if (lc $2 eq 'geotime') { + $addGeotime = ''; + } else { + # add geotag/geosync commands first + unshift @newValues, pop @newValues; + if (lc $2 eq 'geotag' and (not defined $addGeotime or $addGeotime) and length $val) { + $addGeotime = ($1 || '') . 'Geotime)/; + if ($setTagsFile) { + push @{$setTags{$setTagsFile}}, $_; + if ($1 eq '>') { + $useMWG = 1 if /^(.*>\s*)?mwg:/si; + if (/\b(filename|directory|testname)#?$/i) { + $doSetFileName = 1; + } elsif (/\bgeotime#?$/i) { + $addGeotime = ''; + } + } else { + $useMWG = 1 if /^([^<]+<\s*(.*\$\{?)?)?mwg:/si; + if (/^([-\w]+:)*(filename|directory|testname)\b/i) { + $doSetFileName = 1; + } elsif (/^([-\w]+:)*geotime\b/i) { + $addGeotime = ''; + } + } + } else { + my $lst = s/^-// ? \@exclude : \@tags; + unless (/^([-\w*]+:)*([-\w*?]+)#?$/) { + Warn(qq(Invalid TAG name: "$_"\n)); + } + push @$lst, $_; # (push everything for backward compatibility) + } + } + } else { + unless ($pass) { + # defer to next pass so the filename charset is available + push @nextPass, $_; + next; + } + if ($doGlob and /[*?]/) { + if ($^O eq 'MSWin32' and eval { require Win32::FindFile }) { + push @files, FindFileWindows($mt, $_); + } else { + # glob each filespec if necessary - MK/20061010 + push @files, File::Glob::bsd_glob($_); + } + $doGlob = 2; + } else { + push @files, $_; + $srcStdin = 1 if $_ eq '-'; + } + } +} + +# set "OK" UserParam based on result of last command +$mt->Options(UserParam => 'OK=' . (not $rtnValPrev)); + +# set verbose output to STDERR if output could be to console +$vout = \*STDERR if $srcStdin and ($isWriting or @newValues); +$mt->Options(TextOut => $vout) if $vout eq \*STDERR; + +# change default EXIF string encoding if MWG used +if ($useMWG and not defined $mt->Options('CharsetEXIF')) { + $mt->Options(CharsetEXIF => 'UTF8'); +} + +# print help +unless ((@tags and not $outOpt) or @files or @newValues) { + if ($doGlob and $doGlob == 2) { + Warn "No matching files\n"; + $rtnVal = 1; + next; + } + if ($outOpt) { + Warn "Nothing to write\n"; + $rtnVal = 1; + next; + } + unless ($helped) { + # catch warnings if we have problems running perldoc + local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] }; + my $dummy = \*SAVEERR; # avoid "used only once" warning + unless ($^O eq 'os2') { + open SAVEERR, ">&STDERR"; + open STDERR, '>/dev/null'; + } + if (system('perldoc',$0)) { + print "Syntax: exiftool [OPTIONS] FILE\n\n"; + print "Consult the exiftool documentation for a full list of options.\n"; + } + unless ($^O eq 'os2') { + close STDERR; + open STDERR, '>&SAVEERR'; + } + } + next; +} + +# do sanity check on -delete_original and -restore_original +if (defined $deleteOrig and (@newValues or @tags)) { + if (not @newValues) { + my $verb = $deleteOrig ? 'deleting' : 'restoring from'; + Warn "Can't specify tags when $verb originals\n"; + } elsif ($deleteOrig) { + Warn "Can't use -delete_original when writing.\n"; + Warn "Maybe you meant -overwrite_original ?\n"; + } else { + Warn "It makes no sense to use -restore_original when writing\n"; + } + $rtnVal = 1; + next; +} + +if ($overwriteOrig > 1 and $outOpt) { + Warn "Can't overwrite in place when -o option is used\n"; + $rtnVal = 1; + next; +} + +if ($tagOut and ($csv or %printFmt or $tabFormat or $xml or ($verbose and $html))) { + Warn "Sorry, -W may not be combined with -csv, -htmlDump, -j, -p, -t or -X\n"; + $rtnVal = 1; + next; +} + +if ($csv and $csv eq 'CSV' and not $isWriting) { + if ($textOut) { + Warn "Sorry, -w may not be combined with -csv\n"; + $rtnVal = 1; + next; + } + if ($binaryOutput) { + $binaryOutput = 0; + $setCharset = 'default' unless defined $setCharset; + } + if (%printFmt) { + Warn "The -csv option has no effect when -p is used\n"; + undef $csv; + } + require Image::ExifTool::XMP if $setCharset; +} + +if ($escapeHTML or $json) { + # must be UTF8 for HTML conversion and JSON output + $mt->Options(Charset => 'UTF8') if $json; + # use Escape option to do our HTML escaping unless XML output + $mt->Options(Escape => 'HTML') if $escapeHTML and not $xml; +} elsif ($escapeXML and not $xml) { + $mt->Options(Escape => 'XML'); +} + +# set sort option +if ($sortOpt) { + # (note that -csv sorts alphabetically by default anyway if more than 1 file) + my $sort = ($outFormat > 0 or $xml or $json or $csv) ? 'Tag' : 'Descr'; + $mt->Options(Sort => $sort, Sort2 => $sort); +} + +# set $structOpt in case set by API option +if ($mt->Options('Struct') and not $structOpt) { + $structOpt = $mt->Options('Struct'); + require 'Image/ExifTool/XMPStruct.pl'; +} + +# set up for RDF/XML, JSON and PHP output formats +if ($xml) { + require Image::ExifTool::XMP; # for EscapeXML() + my $charset = $mt->Options('Charset'); + # standard XML encoding names for supported Charset settings + # (ref http://www.iana.org/assignments/character-sets) + my %encoding = ( + UTF8 => 'UTF-8', + Latin => 'windows-1252', + Latin2 => 'windows-1250', + Cyrillic => 'windows-1251', + Greek => 'windows-1253', + Turkish => 'windows-1254', + Hebrew => 'windows-1255', + Arabic => 'windows-1256', + Baltic => 'windows-1257', + Vietnam => 'windows-1258', + MacRoman => 'macintosh', + ); + # switch to UTF-8 if we don't have a standard encoding name + unless ($encoding{$charset}) { + $charset = 'UTF8'; + $mt->Options(Charset => $charset); + } + # set file header/trailer for XML output + $fileHeader = "\n" . + "\n"; + $fileTrailer = "\n"; + # extract as a list unless short output format + $joinLists = 1 if $outFormat > 0; + $mt->Options(List => 1) unless $joinLists; + $showGroup = $allGroup = 1; # always show group 1 + # set binaryOutput flag to 0 or undef (0 = output encoded binary in XML) + $binaryOutput = ($outFormat > 0 ? undef : 0) if $binaryOutput; + $showTagID = 'D' if $tabFormat and not $showTagID; +} elsif ($json) { + if ($json == 1) { # JSON + $fileHeader = '['; + $fileTrailer = "]\n"; + } else { # PHP + $fileHeader = 'Array('; + $fileTrailer = ");\n"; + } + # allow binary output in a text-mode file when -php/-json and -b used together + # (this works because PHP strings are simple arrays of bytes, and CR/LF + # won't be messed up in the text mode output because they are converted + # to escape sequences in the strings) + if ($binaryOutput) { + $binaryOutput = 0; + require Image::ExifTool::XMP if $json == 1; # (for EncodeBase64) + } + $mt->Options(List => 1) unless $joinLists; + $showTagID = 'D' if $tabFormat and not $showTagID; +} elsif ($structOpt) { + $mt->Options(List => 1); +} else { + $joinLists = 1; # join lists for all other unstructured output formats +} + +if ($argFormat) { + $outFormat = 3; + $allGroup = 1 if defined $showGroup; +} + +# change to forward slashes if necessary in all filenames (like CleanFilename) +if ($hasBackslash{$^O}) { + tr/\\/\// foreach @files; +} + +# can't do anything if no file specified +unless (@files) { + unless ($outOpt) { + if ($doGlob and $doGlob == 2) { + Warn "No matching files\n"; + } else { + Warn "No file specified\n"; + } + $rtnVal = 1; + next; + } + push @files, ''; # create file from nothing +} + +# set Verbose and HtmlDump options +if ($verbose) { + $disableOutput = 1 unless @tags or @exclude or $tagOut; + undef $binaryOutput unless $tagOut; # disable conflicting option + if ($html) { + $html = 2; # flag for html dump + $mt->Options(HtmlDump => $verbose); + } else { + $mt->Options(Verbose => $verbose) unless $tagOut; + } +} elsif (defined $verbose) { + # auto-flush output when -v0 is used + require FileHandle; + STDOUT->autoflush(1); + STDERR->autoflush(1); +} + +# validate all tags we're writing +my $needSave = 1; +if (@newValues) { + # assume -geotime value if -geotag specified without -geotime + if ($addGeotime) { + AddSetTagsFile($setTagsFile = '@') unless $setTagsFile and $setTagsFile eq '@'; + push @{$setTags{$setTagsFile}}, $addGeotime; + $verbose and print $vout qq{Argument "-$addGeotime" is assumed\n}; + } + my %setTagsIndex; + # add/delete option lookup + my %addDelOpt = ( '+' => 'AddValue', '-' => 'DelValue', "\xe2\x88\x92" => 'DelValue' ); + $saveCount = 0; + foreach (@newValues) { + if (ref $_ eq 'HASH') { + # save new values now if we stored a "SaveCount" marker + if ($$_{SaveCount}) { + $saveCount = $mt->SaveNewValues(); + $needSave = 0; + # insert marker to load values from CSV file now if this was the CSV file + push @dynamicFiles, \$csv if $$_{SaveCount} == $csvSaveCount; + } + next; + } + /(.*?)=(.*)/s or next; + my ($tag, $newVal) = ($1, $2); + $tag =~ s/\ball\b/\*/ig; # replace 'all' with '*' in tag names + $newVal eq '' and undef $newVal unless $tag =~ s/\^([-+]*)$/$1/; # undefined to delete tag + if ($tag =~ /^(All)?TagsFromFile$/i) { + defined $newVal or Error("Need file name for -tagsFromFile\n"), next Command; + ++$isWriting; + if ($newVal eq '@' or not defined FilenameSPrintf($newVal)) { + push @dynamicFiles, $newVal; + next; # set tags from dynamic file later + } + unless ($mt->Exists($newVal) or $newVal eq '-') { + Warn "File '${newVal}' does not exist for -tagsFromFile option\n"; + $rtnVal = 1; + next Command; + } + my $setTags = $setTags{$newVal}; + # do we have multiple -tagsFromFile options with this file? + if ($setTagsList{$newVal}) { + # use the tags set in the i-th occurrence + my $i = $setTagsIndex{$newVal} || 0; + $setTagsIndex{$newVal} = $i + 1; + $setTags = $setTagsList{$newVal}[$i] if $setTagsList{$newVal}[$i]; + } + # set specified tags from this file + unless (DoSetFromFile($mt, $newVal, $setTags)) { + $rtnVal = 1; + next Command; + } + $needSave = 1; + next; + } + my %opts = ( Shift => 0 ); # shift values if possible instead of adding/deleting + # allow writing of 'Unsafe' tags unless specified by wildcard + $opts{Protected} = 1 unless $tag =~ /[?*]/; + + if ($tag =~ s/SetNewValue($tag, $newVal, %opts); + $needSave = 1; + ++$isWriting if $rtn; + $wrn and Warn "Warning: $wrn\n"; + } + # exclude specified tags + foreach (@exclude) { + $mt->SetNewValue($_, undef, Replace => 2); + $needSave = 1; + } + unless ($isWriting or $outOpt or @tags) { + Warn "Nothing to do.\n"; + $rtnVal = 1; + next; + } +} elsif (grep /^(\*:)?\*$/, @exclude) { + Warn "All tags excluded -- nothing to do.\n"; + $rtnVal = 1; + next; +} +if ($isWriting and @tags and not $outOpt) { + my ($tg, $s) = @tags > 1 ? ("$tags[0] ...", 's') : ($tags[0], ''); + Warn "Ignored superfluous tag name$s or invalid option$s: -$tg\n"; +} +# save current state of new values if setting values from target file +# or if we may be translating to a different format +$mt->SaveNewValues() if $outOpt or (@dynamicFiles and $needSave); + +$multiFile = 1 if @files > 1; +@exclude and $mt->Options(Exclude => \@exclude); + +undef $binaryOutput if $html; + +if ($binaryOutput) { + $outFormat = 99; # shortest possible output format + $mt->Options(PrintConv => 0); + unless ($textOut or $binaryStdout) { + binmode(STDOUT); + $binaryStdout = 1; + $mt->Options(TextOut => ($vout = \*STDERR)); + } + # disable conflicting options + undef $showGroup; +} + +# sort by groups to look nicer depending on options +if (defined $showGroup and not (@tags and $allGroup) and ($sortOpt or not defined $sortOpt)) { + $mt->Options(Sort => "Group$showGroup"); +} + +if ($textOut) { + CleanFilename($textOut); # make all forward slashes + # add '.' before output extension if necessary + $textOut = ".$textOut" unless $textOut =~ /[.%]/ or defined $tagOut; +} + +# determine if we should scan for only writable files +if ($outOpt) { + my $type = GetFileType($outOpt); + if ($type) { + # (must test original file name because we can write .webp but not other RIFF types) + unless (CanWrite($outOpt)) { + Warn "Can't write $type files\n"; + $rtnVal = 1; + next; + } + $scanWritable = $type unless CanCreate($type); + } else { + $scanWritable = 1; + } + $isWriting = 1; # set writing flag +} elsif ($isWriting or defined $deleteOrig) { + $scanWritable = 1; +} + +# initialize alternate encoding flag +$altEnc = $mt->Options('Charset'); +undef $altEnc if $altEnc eq 'UTF8'; + +# set flag to fix description lengths if necessary +if (not $altEnc and $mt->Options('Lang') ne 'en' and eval { require Encode }) { + # (note that Unicode::GCString is part of the Unicode::LineBreak package) + $fixLen = eval { require Unicode::GCString } ? 2 : 1; +} + +# sort input files if specified +if (@fileOrder) { + my @allFiles; + ProcessFiles($mt, \@allFiles); + my $sortTool = new Image::ExifTool; + $sortTool->Options(FastScan => $fileOrderFast) if $fileOrderFast; + $sortTool->Options(PrintConv => $mt->Options('PrintConv')); + $sortTool->Options(Duplicates => 0); + my (%sortBy, %isFloat, @rev, $file); + # save reverse sort flags + push @rev, (s/^-// ? 1 : 0) foreach @fileOrder; + foreach $file (@allFiles) { + my @tags; + my $info = $sortTool->ImageInfo(Infile($file,1), @fileOrder, \@tags); + # get values of all tags (or '~' to sort last if not defined) + foreach (@tags) { + $_ = $$info{$_}; # put tag value into @tag list + defined $_ or $_ = '~', next; + $isFloat{$_} = Image::ExifTool::IsFloat($_); + # pad numbers to 12 digits to keep them sequential + s/(\d+)/(length($1) < 12 ? '0'x(12-length($1)) : '') . $1/eg unless $isFloat{$_}; + } + $sortBy{$file} = \@tags; # save tag values for each file + } + # sort in specified order + @files = sort { + my ($i, $cmp); + for ($i=0; $i<@rev; ++$i) { + my $u = $sortBy{$a}[$i]; + my $v = $sortBy{$b}[$i]; + if (not $isFloat{$u} and not $isFloat{$v}) { + $cmp = $u cmp $v; # alphabetically + } elsif ($isFloat{$u} and $isFloat{$v}) { + $cmp = $u <=> $v; # numerically + } else { + $cmp = $isFloat{$u} ? -1 : 1; # numbers first + } + return $rev[$i] ? -$cmp : $cmp if $cmp; + } + return $a cmp $b; # default to sort by name + } @allFiles; +} elsif (defined $progress) { + # expand FILE argument to count the number of files to process + my @allFiles; + ProcessFiles($mt, \@allFiles); + @files = @allFiles; +} +# set file count for progress message +$progressMax = scalar @files if defined $progress; + +# store duplicate database information under absolute path +my @dbKeys = keys %database; +if (@dbKeys) { + if (eval { require Cwd }) { + undef $evalWarning; + local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] }; + foreach (@dbKeys) { + my $db = $database{$_}; + tr/\\/\// and $database{$_} = $db; # allow for backslashes in SourceFile + # (punt on using ConvertFileName here, so $absPath may be a mix of encodings) + my $absPath = AbsPath($_); + if (defined $absPath) { + $database{$absPath} = $db unless $database{$absPath}; + if ($verbose and $verbose > 1) { + print $vout "Imported entry for '${_}' (full path: '${absPath}')\n"; + } + } elsif ($verbose and $verbose > 1) { + print $vout "Imported entry for '${_}' (non-existent file)\n"; + } + } + } +} + +# process all specified files +ProcessFiles($mt); + +if ($filtered and not $validFile) { + Warn "No file with specified extension\n"; + $rtnVal = 1; +} + +# print CSV information if necessary +PrintCSV() if $csv and not $isWriting; + +# print folder/file trailer if necessary +if ($textOut) { + foreach (keys %outTrailer) { + next unless $outTrailer{$_}; + if ($mt->Open(\*OUTTRAIL, $_, '>>')) { + my $fp = \*OUTTRAIL; + print $fp $outTrailer{$_}; + close $fp; + } else { + Error("Error appending to $_\n"); + } + } +} else { + print $sectTrailer if $sectTrailer; + print $fileTrailer if $fileTrailer and not $fileHeader; +} + +my $totWr = $countGoodWr + $countBadWr + $countSameWr + $countCopyWr + + $countGoodCr + $countBadCr; + +if (defined $deleteOrig) { + + # print summary and delete requested files + unless ($quiet) { + printf "%5d directories scanned\n", $countDir if $countDir; + printf "%5d directories created\n", $countNewDir if $countNewDir; + printf "%5d files failed condition\n", $countFailed if $countFailed; + printf "%5d image files found\n", $count; + } + if (@delFiles) { + # verify deletion unless "-delete_original!" was specified + if ($deleteOrig == 1) { + printf '%5d originals will be deleted! Are you sure [y/n]? ', scalar(@delFiles); + my $response = ; + unless ($response =~ /^(y|yes)\s*$/i) { + Warn "Originals not deleted.\n"; + next; + } + } + $countGoodWr = $mt->Unlink(@delFiles); + $countBad = scalar(@delFiles) - $countGoodWr; + } + if ($quiet) { + # no more messages + } elsif ($count and not $countGoodWr and not $countBad) { + printf "%5d original files found\n", $countGoodWr; # (this will be 0) + } elsif ($deleteOrig) { + printf "%5d original files deleted\n", $countGoodWr if $count; + printf "%5d originals not deleted due to errors\n", $countBad if $countBad; + } else { + printf "%5d image files restored from original\n", $countGoodWr if $count; + printf "%5d files not restored due to errors\n", $countBad if $countBad; + } + +} elsif ((not $binaryStdout or $verbose) and not $quiet) { + + # print summary + my $tot = $count + $countBad; + if ($countDir or $totWr or $countFailed or $tot > 1 or $textOut or %countLink) { + my $o = (($html or $json or $xml or %printFmt or $csv) and not $textOut) ? \*STDERR : $vout; + printf($o "%5d directories scanned\n", $countDir) if $countDir; + printf($o "%5d directories created\n", $countNewDir) if $countNewDir; + printf($o "%5d files failed condition\n", $countFailed) if $countFailed; + printf($o "%5d image files created\n", $countGoodCr) if $countGoodCr; + printf($o "%5d image files updated\n", $countGoodWr) if $totWr - $countGoodCr - $countBadCr - $countCopyWr; + printf($o "%5d image files unchanged\n", $countSameWr) if $countSameWr; + printf($o "%5d image files %s\n", $countCopyWr, $overwriteOrig ? 'moved' : 'copied') if $countCopyWr; + printf($o "%5d files weren't updated due to errors\n", $countBadWr) if $countBadWr; + printf($o "%5d files weren't created due to errors\n", $countBadCr) if $countBadCr; + printf($o "%5d image files read\n", $count) if ($tot+$countFailed)>1 or ($countDir and not $totWr); + printf($o "%5d files could not be read\n", $countBad) if $countBad; + printf($o "%5d output files created\n", scalar(keys %created)) if $textOut; + printf($o "%5d output files appended\n", scalar(keys %appended)) if %appended; + printf($o "%5d hard links created\n", $countLink{Hard} || 0) if $countLink{Hard} or $countLink{BadHard}; + printf($o "%5d hard links could not be created\n", $countLink{BadHard}) if $countLink{BadHard}; + printf($o "%5d symbolic links created\n", $countLink{Sym} || 0) if $countLink{Sym} or $countLink{BadSym}; + printf($o "%5d symbolic links could not be created\n", $countLink{BadSym}) if $countLink{BadSym}; + } +} + +# set error status if we had any errors or if all files failed the "-if" condition +if ($countBadWr or $countBadCr or $countBad) { + $rtnVal = 1; +} elsif ($countFailed and not ($count or $totWr) and not $rtnVal) { + $rtnVal = 2; +} + +# clean up after each command +Cleanup(); + +} # end "Command" loop ........................................................ + +close STAYOPEN if $stayOpen >= 2; + +Exit $rtnValApp; # all done + + +#------------------------------------------------------------------------------ +# Get image information from EXIF data in file (or write file if writing) +# Inputs: 0) ExifTool object reference, 1) file name +sub GetImageInfo($$) +{ + my ($et, $orig) = @_; + my (@foundTags, $info, $file, $ind, $g8); + + # set window title for this file if necessary + if (defined $windowTitle) { + if ($progressCount >= $progressNext) { + my $prog = $progressMax ? "$progressCount/$progressMax" : '0/0'; + my $title = $windowTitle; + my ($num, $denom) = split '/', $prog; + my $frac = $num / ($denom || 1); + my $n = $title =~ s/%(\d+)b/%b/ ? $1 : 20; # length of bar + my $bar = int($frac * $n + 0.5); + my %lkup = ( + b => ('I' x $bar) . ('.' x ($n - $bar)), + f => $orig, + p => int(100 * $frac + 0.5), + r => $prog, + '%'=> '%', + ); + $title =~ s/%([%bfpr])/$lkup{$1}/eg; + SetWindowTitle($title); + if (defined $progressMax) { + undef $progressNext; + } else { + $progressNext += $progressIncr; + } + } + # ($progressMax is not defined for "-progress:%f") + ++$progressCount unless defined $progressMax; + } + unless (length $orig or $outOpt) { + Warn qq(Error: Zero-length file name - ""\n); + ++$countBad; + return; + } + # determine the name of the source file based on the original input file name + if (@srcFmt) { + my ($fmt, $first); + foreach $fmt (@srcFmt) { + $file = $fmt eq '@' ? $orig : FilenameSPrintf($fmt, $orig); + # use this file if it exists + $et->Exists($file) and undef($first), last; + $verbose and print $vout "Source file $file does not exist\n"; + $first = $file unless defined $first; + } + $file = $first if defined $first; + my ($d, $f) = Image::ExifTool::SplitFileName($orig); + $et->Options(UserParam => "OriginalDirectory#=$d"); + $et->Options(UserParam => "OriginalFileName#=$f"); + } else { + $file = $orig; + } + # set alternate file names + foreach $g8 (sort keys %altFile) { + my $altName = $orig; + # must double any '$' symbols in the original file name because + # they are used for tag names in a -fileNUM argument + $altName =~ s/\$/\$\$/g; + $altName = FilenameSPrintf($altFile{$g8}, $altName); + $et->SetAlternateFile($g8, $altName); + } + + my $pipe = $file; + if ($doUnzip) { + # pipe through gzip or bzip2 if necessary + if ($file =~ /\.(gz|bz2)$/i) { + my $type = lc $1; + if ($file =~ /[^-_.'A-Za-z0-9\/\\]/) { + Warn "Error: Insecure zip file name. Skipped\n"; + EFile($file); + ++$countBad; + return; + } + if ($type eq 'gz') { + $pipe = qq{gzip -dc "$file" |}; + } else { + $pipe = qq{bzip2 -dc "$file" |}; + } + $$et{TRUST_PIPE} = 1; + } + } + # evaluate -if expression for conditional processing + if (@condition) { + unless ($file eq '-' or $et->Exists($file)) { + Warn "Error: File not found - $file\n"; + EFile($file); + FileNotFound($file); + ++$countBad; + return; + } + my $result; + + unless ($failCondition) { + # catch run time errors as well as compile errors + undef $evalWarning; + local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] }; + + my (%info, $condition); + # extract information and build expression for evaluation + my $opts = { Duplicates => 1, RequestTags => \@requestTags, Verbose => 0, HtmlDump => 0 }; + $$opts{FastScan} = $fastCondition if defined $fastCondition; + # return all tags but explicitly mention tags on command line so + # requested images will generate the appropriate warnings + @foundTags = ('*', @tags) if @tags; + $info = $et->ImageInfo(Infile($pipe,$isWriting), \@foundTags, $opts); + foreach $condition (@condition) { + my $cond = $et->InsertTagValues(\@foundTags, $condition, \%info); + { + # set package so eval'd functions are in Image::ExifTool namespace + package Image::ExifTool; + + my $self = $et; + #### eval "-if" condition (%info, $self) + $result = eval $cond; + + $@ and $evalWarning = $@; + } + if ($evalWarning) { + # fail condition if warning is issued + undef $result; + if ($verbose) { + chomp $evalWarning; + $evalWarning =~ s/ at \(eval .*//s; + Warn "Condition: $evalWarning - $file\n"; + } + } + last unless $result; + } + undef @foundTags if $fastCondition; # ignore if we didn't get all tags + } + unless ($result) { + Progress($vout, "-------- $file (failed condition)") if $verbose; + EFile($file, 2); + ++$countFailed; + return; + } + # can't make use of $info if verbose because we must reprocess + # the file anyway to generate the verbose output + undef $info if $verbose or defined $fastCondition; + } + if (defined $deleteOrig) { + Progress($vout, "======== $file") if defined $verbose; + ++$count; + my $original = "${file}_original"; + $et->Exists($original) or return; + if ($deleteOrig) { + $verbose and print $vout "Scheduled for deletion: $original\n"; + push @delFiles, $original; + } elsif ($et->Rename($original, $file)) { + $verbose and print $vout "Restored from $original\n"; + EFile($file, 3); + ++$countGoodWr; + } else { + Warn "Error renaming $original\n"; + EFile($file); + ++$countBad; + } + return; + } + ++$seqFileNum; # increment our file counter + my ($dir) = Image::ExifTool::SplitFileName($orig); + $seqFileDir = $seqFileDir{$dir} = ($seqFileDir{$dir} || 0) + 1; + + my $lineCount = 0; + my ($fp, $outfile, $append); + if ($textOut and $verbose and not $tagOut) { + ($fp, $outfile, $append) = OpenOutputFile($orig); + $fp or EFile($file), ++$countBad, return; + # delete file if we exit prematurely (unless appending) + $tmpText = $outfile unless $append; + $et->Options(TextOut => $fp); + } + + if ($isWriting) { + Progress($vout, "======== $file") if defined $verbose; + SetImageInfo($et, $file, $orig); + $info = $et->GetInfo('Warning', 'Error'); + PrintErrors($et, $info, $file); + # close output text file if necessary + if (defined $outfile) { + undef $tmpText; + close($fp); + $et->Options(TextOut => $vout); + if ($info->{Error}) { + $et->Unlink($outfile); # erase bad file + } elsif ($append) { + $appended{$outfile} = 1 unless $created{$outfile}; + } else { + $created{$outfile} = 1; + } + } + return; + } + + # extract information from this file + unless ($file eq '-' or $et->Exists($file)) { + Warn "Error: File not found - $file\n"; + FileNotFound($file); + defined $outfile and close($fp), undef($tmpText), $et->Unlink($outfile); + EFile($file); + ++$countBad; + return; + } + # print file/progress message + my $o; + unless ($binaryOutput or $textOut or %printFmt or $html > 1 or $csv) { + if ($html) { + require Image::ExifTool::HTML; + my $f = Image::ExifTool::HTML::EscapeHTML($file); + print "\n"; + } elsif (not ($json or $xml)) { + $o = \*STDOUT if ($multiFile and not $quiet) or $progress; + } + } + $o = \*STDERR if $progress and not $o; + Progress($o, "======== $file") if $o; + if ($info) { + # get the information we wanted + if (@tags and not %printFmt) { + @foundTags = @tags; + $info = $et->GetInfo(\@foundTags); + } + } else { + # request specified tags unless using print format option + my $oldDups = $et->Options('Duplicates'); + if (%printFmt) { + $et->Options(Duplicates => 1); + $et->Options(RequestTags => \@requestTags); + } else { + @foundTags = @tags; + } + # extract the information + $info = $et->ImageInfo(Infile($pipe), \@foundTags); + $et->Options(Duplicates => $oldDups); + } + # all done now if we already wrote output text file (eg. verbose option) + if ($fp) { + if (defined $outfile) { + $et->Options(TextOut => \*STDOUT); + undef $tmpText; + if ($info->{Error}) { + close($fp); + $et->Unlink($outfile); # erase bad file + } else { + ++$lineCount; # output text file (likely) is not empty + } + } + if ($info->{Error}) { + Warn "Error: $info->{Error} - $file\n"; + EFile($file); + ++$countBad; + return; + } + } + + # print warnings to stderr if using binary output + # (because we are likely ignoring them and piping stdout to file) + # or if there is none of the requested information available + if ($binaryOutput or not %$info) { + my $errs = $et->GetInfo('Warning', 'Error'); + PrintErrors($et, $errs, $file) and EFile($file), $rtnVal = 1; + } elsif ($et->GetValue('Error') or ($$et{Validate} and $et->GetValue('Warning'))) { + $rtnVal = 1; + } + + # open output file (or stdout if no output file) if not done already + unless (defined $outfile or $tagOut) { + ($fp, $outfile, $append) = OpenOutputFile($orig); + $fp or EFile($file), ++$countBad, return; + $tmpText = $outfile unless $append; + } + + # restore state of comma flag for this file if appending + $comma = $outComma{$outfile} if $append and ($textOverwrite & 0x02); + + # print the results for this file + if (%printFmt) { + # output using print format file (-p) option + my ($type, $doc, $grp, $lastDoc, $cache); + $fileTrailer = ''; + # repeat for each embedded document if necessary + if ($et->Options('ExtractEmbedded')) { + # (cache tag keys if there are sub-documents) + $lastDoc = $$et{DOC_COUNT} and $cache = { }; + } else { + $lastDoc = 0; + } + for ($doc=0; $doc<=$lastDoc; ++$doc) { + my $skipBody; + foreach $type (qw(HEAD SECT IF BODY ENDS TAIL)) { + my $prf = $printFmt{$type} or next; + if ($type eq 'HEAD' and defined $outfile) { + next if $wroteHEAD{$outfile}; + $wroteHEAD{$outfile} = 1; + } + next if $type eq 'BODY' and $skipBody; + if ($lastDoc) { + if ($doc) { + next if $type eq 'HEAD' or $type eq 'TAIL'; # only repeat SECT/IF/BODY/ENDS + $grp = "Doc$doc"; + } else { + $grp = 'Main'; + } + } + my @lines; + my $opt = $type eq 'IF' ? 'Silent' : 'Warn'; # silence "IF" warnings + foreach (@$prf) { + my $line = $et->InsertTagValues(\@foundTags, $_, $opt, $grp, $cache); + if ($type eq 'IF') { + $skipBody = 1 unless defined $line; + } elsif (defined $line) { + push @lines, $line; + } + } + $lineCount += scalar @lines; + if ($type eq 'SECT') { + my $thisHeader = join '', @lines; + if ($sectHeader and $sectHeader ne $thisHeader) { + print $fp $sectTrailer if $sectTrailer; + undef $sectHeader; + } + $sectTrailer = ''; + print $fp $sectHeader = $thisHeader unless $sectHeader; + } elsif ($type eq 'ENDS') { + $sectTrailer .= join '', @lines if defined $sectHeader; + } elsif ($type eq 'TAIL') { + $fileTrailer .= join '', @lines; + } elsif (@lines) { + print $fp @lines; + } + } + } + delete $printFmt{HEAD} unless defined $outfile; # print header only once per output file + my $errs = $et->GetInfo('Warning', 'Error'); + PrintErrors($et, $errs, $file) and EFile($file); + } elsif (not $disableOutput) { + my ($tag, $line, %noDups, %csvInfo, $bra, $ket, $sep); + if ($fp) { + # print file header (only once) + if ($fileHeader) { + print $fp $fileHeader unless defined $outfile and ($created{$outfile} or $appended{$outfile}); + undef $fileHeader unless $textOut; + } + if ($html) { + print $fp "\n"; + } elsif ($xml) { + my $f = $file; + CleanXML(\$f); + print $fp "\nGetGroup($tag); + unless ($grp1) { + next unless defined $forcePrint; + $grp0 = $grp1 = 'Unknown'; + } + # add groups from structure fields + AddGroups($$info{$tag}, $grp0, \%groups, \@groups) if ref $$info{$tag}; + next if $groups{$grp1}; + # include family 0 and 1 groups in URI except for internal tags + # (this will put internal tags in the "XML" group on readback) + $groups{$grp1} = $grp0; + push @groups, $grp1; + } + foreach $grp1 (@groups) { + my $grp = $groups{$grp1}; + unless ($grp eq $grp1 and $grp =~ /^(ExifTool|File|Composite|Unknown)$/) { + $grp .= "/$grp1"; + } + print $fp "\n xmlns:$grp1='http://ns.exiftool.org/$grp/1.0/'"; + } + print $fp '>' if $outFormat < 1; # finish rdf:Description token unless short format + $ind = $outFormat >= 0 ? ' ' : ' '; + } elsif ($json) { + # set delimiters for JSON or PHP output + ($bra, $ket, $sep) = $json == 1 ? ('{','}',':') : ('Array(',')',' =>'); + print $fp ",\n" if $comma; + print $fp qq($bra\n "SourceFile"$sep ), EscapeJSON(MyConvertFileName($et,$file)); + $comma = 1; + $ind = (defined $showGroup and not $allGroup) ? ' ' : ' '; + } elsif ($csv) { + my $file2 = MyConvertFileName($et, $file); + $database{$file2} = \%csvInfo; + push @csvFiles, $file2; + } + } + # suppress duplicates manually in JSON and short XML output + my $noDups = ($json or ($xml and $outFormat > 0)); + my $printConv = $et->Options('PrintConv'); + my $lastGroup = ''; + my $i = -1; +TAG: foreach $tag (@foundTags) { + ++$i; # keep track on index in @foundTags + my $tagName = GetTagName($tag); + my ($group, $valList); + # get the value for this tag + my $val = $$info{$tag}; + # set flag if this is binary data + $isBinary = (ref $val eq 'SCALAR' and defined $binaryOutput); + if (ref $val) { + # happens with -X, -j or -php when combined with -b: + if (defined $binaryOutput and not $binaryOutput and $$et{TAG_INFO}{$tag}{Protected}) { + # avoid extracting Unsafe binary tags (eg. data blocks) [insider information] + my $lcTag = lc $tag; + $lcTag =~ s/ .*//; + next unless $$et{REQ_TAG_LOOKUP}{$lcTag} or ($$et{OPTIONS}{RequestAll} || 0) > 2; + } + $val = ConvertBinary($val); # convert SCALAR references + next unless defined $val; + if ($structOpt and ref $val) { + # serialize structure if necessary + $val = Image::ExifTool::XMP::SerializeStruct($et, $val) unless $xml or $json; + } elsif (ref $val eq 'ARRAY') { + if (defined $listItem) { + # take only the specified item + $val = $$val[$listItem]; + # join arrays of simple values (with newlines for binary output) + } elsif ($binaryOutput) { + if ($tagOut) { + $valList = $val; + $val = shift @$valList; + } else { + $val = join defined $binSep ? $binSep : "\n", @$val; + } + } elsif ($joinLists) { + $val = join $listSep, @$val; + } + } + } + if (not defined $val) { + # ignore tags that weren't found unless necessary + next if $binaryOutput; + if (defined $forcePrint) { + $val = $forcePrint; # forced to print all tag values + } elsif (not $csv) { + next; + } + } + if (defined $showGroup) { + $group = $et->GetGroup($tag, $showGroup); + # look ahead to see if this tag may suppress a priority tag in + # the same group, and if so suppress this tag instead + # (note that the tag key may look like "TAG #(1)" when the "#" feature is used) + next if $noDups and $tag =~ /^(.*?) ?\(/ and defined $$info{$1} and + $group eq $et->GetGroup($1, $showGroup); + $group = 'Unknown' if not $group and ($xml or $json or $csv); + if ($fp and not ($allGroup or $csv)) { + if ($lastGroup ne $group) { + if ($html) { + my $cols = 1; + ++$cols if $outFormat==0 or $outFormat==1; + ++$cols if $showTagID; + print $fp "\n"; + } elsif ($json) { + print $fp "\n $ket" if $lastGroup; + print $fp ',' if $lastGroup or $comma; + print $fp qq(\n "$group"$sep $bra); + undef $comma; + undef %noDups; # allow duplicate names in different groups + } else { + print $fp "---- $group ----\n"; + } + $lastGroup = $group; + } + undef $group; # undefine so we don't print it below + } + } elsif ($noDups) { + # don't allow duplicates, but avoid suppressing the priority tag + next if $tag =~ /^(.*?) ?\(/ and defined $$info{$1}; + } + + ++$lineCount; # we are printing something meaningful + + # loop through list values when -b -W used + for (;;) { + if ($tagOut) { + # determine suggested extension for output file + my $ext = SuggestedExtension($et, \$val, $tagName); + if (%wext and ($wext{$ext} || $wext{'*'} || -1) < 0) { + if ($verbose and $verbose > 1) { + print $vout "Not writing $ext output file for $tagName\n"; + } + next TAG; + } + my @groups = $et->GetGroup($tag); + defined $outfile and close($fp), undef($tmpText); # (shouldn't happen) + my $org = $et->GetValue('OriginalRawFileName') || $et->GetValue('OriginalFileName'); + ($fp, $outfile, $append) = OpenOutputFile($orig, $tagName, \@groups, $ext, $org); + $fp or ++$countBad, next TAG; + $tmpText = $outfile unless $append; + } + # write binary output + if ($binaryOutput) { + print $fp $val; + print $fp $binTerm if defined $binTerm; + if ($tagOut) { + if ($append) { + $appended{$outfile} = 1 unless $created{$outfile}; + } else { + $created{$outfile} = 1; + } + close($fp); + undef $tmpText; + $verbose and print $vout "Wrote $tagName to $outfile\n"; + undef $outfile; + undef $fp; + next TAG unless $valList and @$valList; + $val = shift @$valList; + next; # loop over values of List tag + } + next TAG; + } + last; + } + # save information for CSV output + if ($csv) { + my $tn = $tagName; + $tn .= '#' if $tag =~ /#/; # add ValueConv "#" suffix if used + my $gt = $group ? "$group:$tn" : $tn; + # (tag-name case may be different if some tags don't exist + # in a file, so all logic must use lower-case tag names) + my $lcTag = lc $gt; + # override existing entry only if top priority + next if defined $csvInfo{$lcTag} and $tag =~ /\(/; + $csvInfo{$lcTag} = $val; + if (defined $csvTags{$lcTag}) { + # overwrite with actual extracted tag name + # (note: can't check "if defined $val" here because -f may be used) + $csvTags{$lcTag} = $gt if defined $$info{$tag}; + next; + } + # must check for "Unknown" group (for tags that don't exist) + if ($group and defined $csvTags[$i] and $csvTags[$i] =~ /^(.*):$tn$/i) { + next if $group eq 'Unknown'; # nothing more to do if we don't know tag group + if ($1 eq 'unknown') { + # replace unknown entry in CSV tag lookup and list + delete $csvTags{$csvTags[$i]}; + $csvTags{$lcTag} = defined($val) ? $gt : ''; + $csvTags[$i] = $lcTag; + next; + } + } + # (don't save unextracted tag name unless -f was used) + $csvTags{$lcTag} = defined($val) ? $gt : ''; + if (@csvFiles == 1) { + push @csvTags, $lcTag; # save order of tags for first file + } elsif (@csvTags) { + undef @csvTags; + } + next; + } + + # get description if we need it (use tag name if $outFormat > 0) + my $desc = $outFormat > 0 ? $tagName : $et->GetDescription($tag); + + if ($xml) { + # RDF/XML output format + my $tok = "$group:$tagName"; + if ($outFormat > 0) { + if ($structOpt and ref $val) { + $val = Image::ExifTool::XMP::SerializeStruct($et, $val); + } + if ($escapeHTML) { + $val =~ tr/\0-\x08\x0b\x0c\x0e-\x1f/./; + Image::ExifTool::XMP::FixUTF8(\$val) unless $altEnc; + $val = Image::ExifTool::HTML::EscapeHTML($val, $altEnc); + } else { + CleanXML(\$val); + } + unless ($noDups{$tok}) { + # manually un-do CR/LF conversion in Windows because output + # is in text mode, which will re-convert newlines to CR/LF + $isCRLF and $val =~ s/\x0d\x0a/\x0a/g; + print $fp "\n $tok='${val}'"; + # XML does not allow duplicate attributes + $noDups{$tok} = 1; + } + next; + } + my ($xtra, $valNum, $descClose); + if ($showTagID) { + my ($id, $lang) = $et->GetTagID($tag); + if ($id =~ /^\d+$/) { + $id = sprintf("0x%.4x", $id) if $showTagID eq 'H'; + } else { + $id = Image::ExifTool::XMP::FullEscapeXML($id); + } + $xtra = " et:id='${id}'"; + $xtra .= " xml:lang='${lang}'" if $lang; + } else { + $xtra = ''; + } + if ($tabFormat) { + my $table = $et->GetTableName($tag); + my $index = $et->GetTagIndex($tag); + $xtra .= " et:table='${table}'"; + $xtra .= " et:index='${index}'" if defined $index; + } + # Note: New $xtra attributes must be added to %ignoreEtProp in XMP.pm! + my $lastVal = $val; + for ($valNum=0; $valNum<2; ++$valNum) { + $val = FormatXML($val, $ind, $group); + # manually un-do CR/LF conversion in Windows because output + # is in text mode, which will re-convert newlines to CR/LF + $isCRLF and $val =~ s/\x0d\x0a/\x0a/g; + if ($outFormat >= 0) { + # normal output format (note: this will give + # non-standard RDF/XML if there are any attributes) + print $fp "\n <$tok$xtra$val"; + last; + } elsif ($valNum == 0) { + CleanXML(\$desc); + if ($xtra) { + print $fp "\n <$tok>"; + print $fp "\n "; + $descClose = "\n "; + } else { + print $fp "\n <$tok rdf:parseType='Resource'>"; + $descClose = ''; + } + # print tag Description + print $fp "\n $desc"; + if ($printConv) { + # print PrintConv value + print $fp "\n "; + $val = $et->GetValue($tag, 'ValueConv'); + $val = '' unless defined $val; + # go back to print ValueConv value only if different + next unless IsEqual($val, $lastVal); + print $fp "$descClose\n "; + last; + } + } + # print ValueConv value + print $fp "\n "; + print $fp "$descClose\n "; + last; + } + next; + } elsif ($json) { + # JSON or PHP output format + my $tok = $allGroup ? "$group:$tagName" : $tagName; + # (removed due to backward incompatibility) + # $tok .= '#' if $tag =~ /#/; # add back '#' suffix if used + next if $noDups{$tok}; + $noDups{$tok} = 1; + print $fp ',' if $comma; + print $fp qq(\n$ind"$tok"$sep ); + if ($showTagID or $outFormat < 0) { + $val = { val => $val }; + if ($showTagID) { + my ($id, $lang) = $et->GetTagID($tag); + $id = sprintf('0x%.4x', $id) if $showTagID eq 'H' and $id =~ /^\d+$/; + $$val{lang} = $lang if $lang; + $$val{id} = $id; + } + if ($tabFormat) { + $$val{table} = $et->GetTableName($tag); + my $index = $et->GetTagIndex($tag); + $$val{index} = $index if defined $index; + } + if ($outFormat < 0) { + $$val{desc} = $desc; + if ($printConv) { + my $num = $et->GetValue($tag, 'ValueConv'); + $$val{num} = $num if defined $num and not IsEqual($num, $$val{val}); + } + } + } + FormatJSON($fp, $val, $ind); + $comma = 1; + next; + } + my $id; + if ($showTagID) { + $id = $et->GetTagID($tag); + if ($id =~ /^(\d+)(\.\d+)?$/) { # only print numeric ID's + $id = sprintf("0x%.4x", $1) if $showTagID eq 'H'; + } else { + $id = '-'; + } + } + + if ($escapeC) { + $val =~ s/([\0-\x1f\\\x7f])/$escC{$1} || sprintf('\x%.2x', ord $1)/eg; + } else { + # translate unprintable chars in value and remove trailing spaces + $val =~ tr/\x01-\x1f\x7f/./; + $val =~ s/\x00//g; + $val =~ s/\s+$//; + } + + if ($html) { + print $fp ""; + print $fp "" if defined $group; + print $fp "" if $showTagID; + print $fp "" if $outFormat <= 1; + print $fp "\n"; + } else { + my $buff = ''; + if ($tabFormat) { + $buff = "$group\t" if defined $group; + $buff .= "$id\t" if $showTagID; + if ($outFormat <= 1) { + $buff .= "$desc\t$val\n"; + } elsif (defined $line) { + $line .= "\t$val"; + } else { + $line = $val; + } + } elsif ($outFormat < 0) { # long format + $buff = "[$group] " if defined $group; + $buff .= "$id " if $showTagID; + $buff .= "$desc\n $val\n"; + } elsif ($outFormat == 0 or $outFormat == 1) { + my $wid; + my $len = 0; + if (defined $group) { + $buff = sprintf("%-15s ", "[$group]"); + $len = 16; + } + if ($showTagID) { + $wid = ($showTagID eq 'D') ? 5 : 6; + $len += $wid + 1; + ($wid = $len - length($buff) - 1) < 1 and $wid = 1; + $buff .= sprintf "%${wid}s ", $id; + } + $wid = 32 - (length($buff) - $len); + # pad description to a constant length + # (get actual character length when using alternate languages + # because these descriptions may contain UTF8-encoded characters) + my $padLen = $wid; + if (not $fixLen) { + $padLen -= length $desc; + } elsif ($fixLen == 1) { + $padLen -= length Encode::decode_utf8($desc); + } else { + my $gcstr = eval { new Unicode::GCString(Encode::decode_utf8($desc)) }; + if ($gcstr) { + $padLen -= $gcstr->columns; + } else { + $padLen -= length Encode::decode_utf8($desc); + Warn "Warning: Unicode::GCString problem. Columns may be misaligned\n"; + $fixLen = 1; + } + } + $padLen = 0 if $padLen < 0; + $buff .= $desc . (' ' x $padLen) . ": $val\n"; + } elsif ($outFormat == 2) { + $buff = "[$group] " if defined $group; + $buff .= "$id " if $showTagID; + $buff .= "$tagName: $val\n"; + } elsif ($argFormat) { + $buff = '-'; + $buff .= "$group:" if defined $group; + $tagName .= '#' if $tag =~ /#/; # add '#' suffix if used + $buff .= "$tagName=$val\n"; + } else { + $buff = "$group " if defined $group; + $buff .= "$id " if $showTagID; + $buff .= "$val\n"; + } + print $fp $buff; + } + if ($tagOut) { + if ($append) { + $appended{$outfile} = 1 unless $created{$outfile}; + } else { + $created{$outfile} = 1; + } + close($fp); + undef $tmpText; + $verbose and print $vout "Wrote $tagName to $outfile\n"; + undef $outfile; + undef $fp; + } + } + if ($fp) { + if ($html) { + print $fp "
$group
$group$id$desc$val
\n"; + } elsif ($xml) { + # close rdf:Description element + print $fp $outFormat < 1 ? "\n\n" : "/>\n"; + } elsif ($json) { + print $fp "\n $ket" if $lastGroup; + print $fp "\n$ket"; + $comma = 1; + } elsif ($tabFormat and $outFormat > 1) { + print $fp "$line\n" if defined $line; + } + } + } + if (defined $outfile) { + if ($textOverwrite & 0x02) { + # save state of this file if we may be appending + $outComma{$outfile} = $comma; + $outTrailer{$outfile} = ''; + $outTrailer{$outfile} .= $sectTrailer and $sectTrailer = '' if $sectTrailer; + $outTrailer{$outfile} .= $fileTrailer if $fileTrailer; + } else { + # write section and file trailers before closing the file + print $fp $sectTrailer and $sectTrailer = '' if $sectTrailer; + print $fp $fileTrailer if $fileTrailer; + } + close($fp); + undef $tmpText; + if ($lineCount) { + if ($append) { + $appended{$outfile} = 1 unless $created{$outfile}; + } else { + $created{$outfile} = 1; + } + } else { + $et->Unlink($outfile) unless $append; # don't keep empty output files + } + undef $comma; + } + ++$count; +} + +#------------------------------------------------------------------------------ +# Set information in file +# Inputs: 0) ExifTool object reference, 1) source file name +# 2) original source file name ('' to create from scratch) +# Returns: true on success +sub SetImageInfo($$$) +{ + my ($et, $file, $orig) = @_; + my ($outfile, $restored, $isTemporary, $isStdout, $outType, $tagsFromSrc); + my ($hardLink, $symLink, $testName, $sameFile); + my $infile = $file; # save infile in case we change it again + + # clean up old temporary file if necessary + if (defined $tmpFile) { + $et->Unlink($tmpFile); + undef $tmpFile; + } + # clear any existing errors or warnings since we check these on return + delete $$et{VALUE}{Error}; + delete $$et{VALUE}{Warning}; + + # first, try to determine our output file name so we can return quickly + # if it already exists (note: this test must be delayed until after we + # set tags from dynamic files if writing FileName or Directory) + if (defined $outOpt) { + if ($outOpt =~ /^-(\.\w+)?$/) { + # allow output file type to be specified with "-o -.EXT" + $outType = GetFileType($outOpt) if $1; + $outfile = '-'; + $isStdout = 1; + } else { + $outfile = FilenameSPrintf($outOpt, $orig); + if ($outfile eq '') { + Warn "Error: Can't create file with zero-length name from $orig\n"; + EFile($infile); + ++$countBadCr; + return 0; + } + } + if (not $isStdout and (($et->IsDirectory($outfile) and not $listDir) or $outfile =~ /\/$/)) { + $outfile .= '/' unless $outfile =~ /\/$/; + my $name = $file; + $name =~ s/^.*\///s; # remove directory name + $outfile .= $name; + } else { + my $srcType = GetFileType($file) || ''; + $outType or $outType = GetFileType($outfile); + if ($outType and ($srcType ne $outType or $outType eq 'ICC') and $file ne '-') { + unless (CanCreate($outType)) { + my $what = $srcType ? 'other types' : 'scratch'; + WarnOnce "Error: Can't create $outType files from $what\n"; + EFile($infile); + ++$countBadCr; + return 0; + } + if ($file ne '') { + # restore previous new values unless done already + $et->RestoreNewValues() unless $restored; + $restored = 1; + # translate to this type by setting specified tags from file + my @setTags = @tags; + foreach (@exclude) { + push @setTags, "-$_"; + } + # force some tags to be copied for certain file types + my %forceCopy = ( + ICC => 'ICC_Profile', + VRD => 'CanonVRD', + DR4 => 'CanonDR4', + ); + push @setTags, $forceCopy{$outType} if $forceCopy{$outType}; + # assume "-tagsFromFile @" unless -tagsFromFile already specified + # (%setTags won't be empty if -tagsFromFile used) + if (not %setTags or (@setTags and not $setTags{'@'})) { + return 0 unless DoSetFromFile($et, $file, \@setTags); + } elsif (@setTags) { + # add orphaned tags to existing "-tagsFromFile @" for this file only + push @setTags, @{$setTags{'@'}}; + $tagsFromSrc = \@setTags; + } + # all done with source file -- create from meta information alone + $file = ''; + } + } + } + unless ($isStdout) { + $outfile = NextUnusedFilename($outfile); + if ($et->Exists($outfile, 1) and not $doSetFileName) { + Warn "Error: '${outfile}' already exists - $infile\n"; + EFile($infile); + ++$countBadWr; + return 0; + } + } + } elsif ($file eq '-') { + $isStdout = 1; + } + # set tags from destination file if required + if (@dynamicFiles) { + # restore previous values if necessary + $et->RestoreNewValues() unless $restored; + my ($dyFile, %setTagsIndex); + foreach $dyFile (@dynamicFiles) { + if (not ref $dyFile) { + my ($fromFile, $setTags); + if ($dyFile eq '@') { + $fromFile = $orig; + $setTags = $tagsFromSrc || $setTags{$dyFile}; + } else { + $fromFile = FilenameSPrintf($dyFile, $orig); + defined $fromFile or EFile($infile), ++$countBadWr, return 0; + $setTags = $setTags{$dyFile}; + } + # do we have multiple -tagsFromFile options with this file? + if ($setTagsList{$dyFile}) { + # use the tags set in the i-th occurrence + my $i = $setTagsIndex{$dyFile} || 0; + $setTagsIndex{$dyFile} = $i + 1; + $setTags = $setTagsList{$dyFile}[$i] if $setTagsList{$dyFile}[$i]; + } + # set new values values from file + return 0 unless DoSetFromFile($et, $fromFile, $setTags); + } elsif (ref $dyFile eq 'ARRAY') { + # a dynamic file containing a simple tag value + my $fname = FilenameSPrintf($$dyFile[1], $orig); + my ($buff, $rtn, $wrn); + my $opts = $$dyFile[2]; + if (defined $fname and SlurpFile($fname, \$buff)) { + $verbose and print $vout "Reading $$dyFile[0] from $fname\n"; + ($rtn, $wrn) = $et->SetNewValue($$dyFile[0], $buff, %$opts); + $wrn and Warn "$wrn\n"; + } + # remove this tag if we couldn't set it properly + $rtn or $et->SetNewValue($$dyFile[0], undef, Replace => 2, + ProtectSaved => $$opts{ProtectSaved}); + next; + } elsif (ref $dyFile eq 'SCALAR') { + # set new values from CSV or JSON database + my ($f, $found, $tag); + undef $evalWarning; + local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] }; + # force UTF-8 if the database was JSON + my $old = $et->Options('Charset'); + $et->Options(Charset => 'UTF8') if $csv eq 'JSON'; + # read tags for SourceFile '*' plus the specific file + foreach $f ('*', MyConvertFileName($et, $file)) { + my $csvInfo = $database{$f}; + unless ($csvInfo) { + next if $f eq '*'; + # check absolute path + # (punt on using ConvertFileName here, so $absPath may be a mix of encodings) + my $absPath = AbsPath($f); + next unless defined $absPath and $csvInfo = $database{$absPath}; + } + $found = 1; + $verbose and print $vout "Setting new values from $csv database\n"; + foreach $tag (sort keys %$csvInfo) { + next if $tag =~ /\b(SourceFile|Directory|FileName)$/i; # don't write these + my ($rtn, $wrn) = $et->SetNewValue($tag, $$csvInfo{$tag}, + Protected => 1, AddValue => $csvAdd, + ProtectSaved => $csvSaveCount); + $wrn and Warn "$wrn\n" if $verbose; + } + } + $et->Options(Charset => $old) if $csv eq 'JSON'; + unless ($found) { + Warn("No SourceFile '${file}' in imported $csv database\n"); + my $absPath = AbsPath($file); + Warn("(full path: '${absPath}')\n") if defined $absPath and $absPath ne $file; + return 0; + } + } + } + } + if ($isStdout) { + # write to STDOUT + $outfile = \*STDOUT; + unless ($binaryStdout) { + binmode(STDOUT); + $binaryStdout = 1; + } + } else { + # get name of hard link if we are creating one + $hardLink = $et->GetNewValues('HardLink'); + $symLink = $et->GetNewValues('SymLink'); + $testName = $et->GetNewValues('TestName'); + $hardLink = FilenameSPrintf($hardLink, $orig) if defined $hardLink; + $symLink = FilenameSPrintf($symLink, $orig) if defined $symLink; + # determine what our output file name should be + my $newFileName = $et->GetNewValues('FileName'); + my $newDir = $et->GetNewValues('Directory'); + if (defined $newFileName and not length $newFileName) { + Warn "Warning: New file name is empty - $ infile\n"; + undef $newFileName; + } + if (defined $testName) { + my $err; + $err = "You shouldn't write FileName or Directory with TestFile" if defined $newFileName or defined $newDir; + $err = "The -o option shouldn't be used with TestFile" if defined $outfile; + $err and Warn("Error: $err - $infile\n"), EFile($infile), ++$countBadWr, return 0; + $testName = FilenameSPrintf($testName, $orig); + $testName = Image::ExifTool::GetNewFileName($file, $testName) if $file ne ''; + } + if (defined $newFileName or defined $newDir or ($doSetFileName and defined $outfile)) { + if ($newFileName) { + $newFileName = FilenameSPrintf($newFileName, $orig); + if (defined $outfile) { + $outfile = Image::ExifTool::GetNewFileName($file, $outfile) if $file ne ''; + $outfile = Image::ExifTool::GetNewFileName($outfile, $newFileName); + } elsif ($file ne '') { + $outfile = Image::ExifTool::GetNewFileName($file, $newFileName); + } + } + if ($newDir) { + $newDir = FilenameSPrintf($newDir, $orig); + $outfile = Image::ExifTool::GetNewFileName(defined $outfile ? $outfile : $file, $newDir); + } + $outfile = NextUnusedFilename($outfile, $infile); + if ($et->Exists($outfile, 1)) { + if ($infile eq $outfile) { + undef $outfile; # not changing the file name after all + # (allow for case-insensitive filesystems) + } elsif ($et->IsSameFile($infile, $outfile)) { + $sameFile = $outfile; # same file, but the name has a different case + } else { + Warn "Error: '${outfile}' already exists - $infile\n"; + EFile($infile); + ++$countBadWr; + return 0; + } + } + } + if (defined $outfile) { + defined $verbose and print $vout "'${infile}' --> '${outfile}'\n"; + # create output directory if necessary + CreateDirectory($outfile); + # set temporary file (automatically erased on abnormal exit) + $tmpFile = $outfile if defined $outOpt; + } + unless (defined $tmpFile) { + # count the number of tags and pseudo-tags we are writing + my ($numSet, $numPseudo) = $et->CountNewValues(); + if ($numSet != $numPseudo and $et->IsDirectory($file)) { + print $vout "Can't write real tags to a directory - $infile\n" if defined $verbose; + $numSet = $numPseudo; + } + if ($et->Exists($file)) { + unless ($numSet) { + # no need to write if no tags set + print $vout "Nothing changed in $file\n" if defined $verbose; + EFile($infile, 1); + ++$countSameWr; + return 1; + } + } elsif (CanCreate($file)) { + if ($numSet == $numPseudo) { + # no need to write if no real tags + Warn("Error: Nothing to write - $file\n"); + EFile($infile, 1); + ++$countBadWr; + return 0; + } + unless (defined $outfile) { + # create file from scratch + $outfile = $file; + $file = ''; + } + } else { + # file doesn't exist, and we can't create it + Warn "Error: File not found - $file\n"; + EFile($infile); + FileNotFound($file); + ++$countBadWr; + return 0; + } + # quickly rename file and/or set file date if this is all we are doing + if ($numSet == $numPseudo) { + my ($r0, $r1, $r2, $r3) = (0, 0, 0, 0); + if (defined $outfile) { + $r0 = $et->SetFileName($file, $outfile); + $file = $$et{NewName} if $r0 > 0; # continue with new name if changed + } + unless ($r0 < 0) { + $r1 = $et->SetFileModifyDate($file,undef,'FileCreateDate'); + $r2 = $et->SetFileModifyDate($file); + $r3 = $et->SetSystemTags($file); + } + if ($r0 > 0 or $r1 > 0 or $r2 > 0 or $r3 > 0) { + EFile($infile, 3); + ++$countGoodWr; + } elsif ($r0 < 0 or $r1 < 0 or $r2 < 0 or $r3 < 0) { + EFile($infile); + ++$countBadWr; + return 0; + } else { + EFile($infile, 1); + ++$countSameWr; + } + if (defined $hardLink or defined $symLink or defined $testName) { + DoHardLink($et, $file, $hardLink, $symLink, $testName); + } + return 1; + } + if (not defined $outfile or defined $sameFile) { + # write to a truly temporary file + $outfile = "${file}_exiftool_tmp"; + if ($et->Exists($outfile)) { + Warn("Error: Temporary file already exists: $outfile\n"); + EFile($infile); + ++$countBadWr; + return 0; + } + $isTemporary = 1; + } + # new output file is temporary until we know it has been written properly + $tmpFile = $outfile; + } + } + # rewrite the file + my $success = $et->WriteInfo(Infile($file), $outfile, $outType); + + # create hard link if specified + if ($success and (defined $hardLink or defined $symLink or defined $testName)) { + my $src = defined $outfile ? $outfile : $file; + DoHardLink($et, $src, $hardLink, $symLink, $testName); + } + + # get file time if preserving it + my ($aTime, $mTime, $cTime, $doPreserve); + $doPreserve = $preserveTime unless $file eq ''; + if ($doPreserve and $success) { + ($aTime, $mTime, $cTime) = $et->GetFileTime($file); + # don't override date/time values written by the user + undef $cTime if $$et{WRITTEN}{FileCreateDate}; + if ($$et{WRITTEN}{FileModifyDate} or $doPreserve == 2) { + if (defined $cTime) { + undef $aTime; # only preserve FileCreateDate + undef $mTime; + } else { + undef $doPreserve; # (nothing to preserve) + } + } + } + + if ($success == 1) { + # preserve the original file times + if (defined $tmpFile) { + if ($et->Exists($file)) { + $et->SetFileTime($tmpFile, $aTime, $mTime, $cTime) if $doPreserve; + if ($isTemporary) { + # preserve original file attributes if possible + $et->CopyFileAttrs($file, $outfile); + # move original out of the way + my $original = "${file}_original"; + if (not $overwriteOrig and not $et->Exists($original)) { + # rename the file and check again to be sure the file doesn't exist + # (in case, say, the filesystem truncated the file extension) + if (not $et->Rename($file, $original) or $et->Exists($file)) { + Error "Error renaming $file\n"; + return 0; + } + } + my $dstFile = defined $sameFile ? $sameFile : $file; + if ($overwriteOrig > 1) { + # copy temporary file over top of original to preserve attributes + my ($err, $buff); + my $newFile = $tmpFile; + $et->Open(\*NEW_FILE, $newFile) or Error("Error opening $newFile\n"), return 0; + binmode(NEW_FILE); + + #.......................................................... + # temporarily disable CTRL-C during this critical operation + $critical = 1; + undef $tmpFile; # handle deletion of temporary file ourself + if ($et->Open(\*ORIG_FILE, $file, '+<')) { + binmode(ORIG_FILE); + while (read(NEW_FILE, $buff, 65536)) { + print ORIG_FILE $buff or $err = 1; + } + close(NEW_FILE); + # Handle files being shorter than the original + eval { truncate(ORIG_FILE, tell(ORIG_FILE)) } or $err = 1; + close(ORIG_FILE) or $err = 1; + if ($err) { + Warn "Couldn't overwrite in place - $file\n"; + unless ($et->Rename($newFile, $file) or + ($et->Unlink($file) and $et->Rename($newFile, $file))) + { + Error("Error renaming $newFile to $file\n"); + undef $critical; + SigInt() if $interrupted; + return 0; + } + } else { + $et->SetFileModifyDate($file, $cTime, 'FileCreateDate', 1); + $et->SetFileModifyDate($file, $mTime, 'FileModifyDate', 1); + $et->Unlink($newFile); + if ($doPreserve) { + $et->SetFileTime($file, $aTime, $mTime, $cTime); + # save time to set it later again to patch OS X 10.6 bug + $preserveTime{$file} = [ $aTime, $mTime, $cTime ]; + } + } + EFile($infile, 3); + ++$countGoodWr; + } else { + close(NEW_FILE); + Warn "Error opening $file for writing\n"; + EFile($infile); + $et->Unlink($newFile); + ++$countBadWr; + } + undef $critical; # end critical section + SigInt() if $interrupted; # issue delayed SIGINT if necessary + #.......................................................... + + # simply rename temporary file to replace original + # (if we didn't already rename it to add "_original") + } elsif ($et->Rename($tmpFile, $dstFile)) { + EFile($infile, 3); + ++$countGoodWr; + } else { + my $newFile = $tmpFile; + undef $tmpFile; # (avoid deleting file if we get interrupted) + # unlink may fail if already renamed or no permission + if (not $et->Unlink($file)) { + Warn "Error renaming temporary file to $dstFile\n"; + EFile($infile); + $et->Unlink($newFile); + ++$countBadWr; + # try renaming again now that the target has been deleted + } elsif (not $et->Rename($newFile, $dstFile)) { + Warn "Error renaming temporary file to $dstFile\n"; + EFile($infile); + # (don't delete tmp file now because it is all we have left) + ++$countBadWr; + } else { + EFile($infile, 3); + ++$countGoodWr; + } + } + } elsif ($overwriteOrig) { + # erase original file + EFile($infile, 3); + $et->Unlink($file) or Warn "Error erasing original $file\n"; + ++$countGoodWr; + } else { + EFile($infile, 4); + ++$countGoodCr; + } + } else { + # this file was created from scratch, not edited + EFile($infile, 4); + ++$countGoodCr; + } + } else { + EFile($infile, 3); + ++$countGoodWr; + } + } elsif ($success) { + EFile($infile, 1); + if ($isTemporary) { + # just erase the temporary file since no changes were made + $et->Unlink($tmpFile); + ++$countSameWr; + } else { + $et->SetFileTime($outfile, $aTime, $mTime, $cTime) if $doPreserve; + if ($overwriteOrig) { + $et->Unlink($file) or Warn "Error erasing original $file\n"; + } + ++$countCopyWr; + } + print $vout "Nothing changed in $file\n" if defined $verbose; + } else { + EFile($infile); + $et->Unlink($tmpFile) if defined $tmpFile; + ++$countBadWr; + } + undef $tmpFile; + return $success; +} + +#------------------------------------------------------------------------------ +# Make hard link and handle TestName if specified +# Inputs: 0) ExifTool ref, 1) source file name, 2) HardLink name, +# 3) SymLink name, 4) TestFile name +sub DoHardLink($$$$$) +{ + my ($et, $src, $hardLink, $symLink, $testName) = @_; + if (defined $hardLink) { + $hardLink = NextUnusedFilename($hardLink); + if ($et->SetFileName($src, $hardLink, 'Link') > 0) { + $countLink{Hard} = ($countLink{Hard} || 0) + 1; + } else { + $countLink{BadHard} = ($countLink{BadHard} || 0) + 1; + } + } + if (defined $symLink) { + $symLink = NextUnusedFilename($symLink); + if ($et->SetFileName($src, $symLink, 'SymLink') > 0) { + $countLink{Sym} = ($countLink{Sym} || 0) + 1; + } else { + $countLink{BadSym} = ($countLink{BadSym} || 0) + 1; + } + } + if (defined $testName) { + $testName = NextUnusedFilename($testName, $src); + if ($usedFileName{$testName}) { + $et->Warn("File '${testName}' would exist"); + } elsif ($et->SetFileName($src, $testName, 'Test', $usedFileName{$testName}) == 1) { + $usedFileName{$testName} = 1; + $usedFileName{$src} = 0; + } + } +} + +#------------------------------------------------------------------------------ +# Clean string for XML (also removes invalid control chars and malformed UTF-8) +# Inputs: 0) string ref +# Returns: nothing, but input string is escaped +sub CleanXML($) +{ + my $strPt = shift; + # translate control characters that are invalid in XML + $$strPt =~ tr/\0-\x08\x0b\x0c\x0e-\x1f/./; + # fix malformed UTF-8 characters + Image::ExifTool::XMP::FixUTF8($strPt) unless $altEnc; + # escape necessary characters for XML + $$strPt = Image::ExifTool::XMP::EscapeXML($$strPt); +} + +#------------------------------------------------------------------------------ +# Encode string for XML +# Inputs: 0) string ref +# Returns: encoding used (and input string is translated) +sub EncodeXML($) +{ + my $strPt = shift; + if ($$strPt =~ /[\0-\x08\x0b\x0c\x0e-\x1f]/ or + (not $altEnc and Image::ExifTool::IsUTF8($strPt) < 0)) + { + # encode binary data and non-UTF8 with special characters as base64 + $$strPt = Image::ExifTool::XMP::EncodeBase64($$strPt); + # #ATV = Alexander Vonk, private communication + return 'http://www.w3.org/2001/XMLSchema#base64Binary'; #ATV + } elsif ($escapeHTML) { + $$strPt = Image::ExifTool::HTML::EscapeHTML($$strPt, $altEnc); + } else { + $$strPt = Image::ExifTool::XMP::EscapeXML($$strPt); + } + return ''; # not encoded +} + +#------------------------------------------------------------------------------ +# Format value for XML output +# Inputs: 0) value, 1) indentation, 2) group +# Returns: formatted value +sub FormatXML($$$) +{ + local $_; + my ($val, $ind, $grp) = @_; + my $gt = '>'; + if (ref $val eq 'ARRAY') { + # convert ARRAY into an rdf:Bag + my $val2 = "\n$ind "; + foreach (@$val) { + $val2 .= "\n$ind "; + } + $val = "$val2\n$ind \n$ind"; + } elsif (ref $val eq 'HASH') { + $gt = " rdf:parseType='Resource'>"; + my $val2 = ''; + my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val; + foreach (@keys) { + # (some variable-namespace XML structure fields may have a different group) + my $tok = /:/ ? $_ : ($grp . ':' . $_); + $val2 .= "\n$ind <$tok" . FormatXML($$val{$_}, "$ind ", $grp) . ""; + } + $val = "$val2\n$ind"; + } else { + # (note: SCALAR reference should have already been converted) + my $enc = EncodeXML(\$val); + $gt = " rdf:datatype='${enc}'>\n" if $enc; #ATV + } + return $gt . $val; +} + +#------------------------------------------------------------------------------ +# Escape string for JSON or PHP +# Inputs: 0) string, 1) flag to force numbers to be quoted too +# Returns: Escaped string (quoted if necessary) +sub EscapeJSON($;$) +{ + my ($str, $quote) = @_; + unless ($quote) { + # JSON boolean (true or false) + return lc($str) if $str =~ /^(true|false)$/i and $json < 2; + # JSON/PHP number (see json.org for numerical format) + # return $str if $str =~ /^-?(\d|[1-9]\d+)(\.\d+)?(e[-+]?\d+)?$/i; + # (these big numbers caused problems for some JSON parsers, so be more conservative) + return $str if $str =~ /^-?(\d|[1-9]\d{1,14})(\.\d{1,16})?(e[-+]?\d{1,3})?$/i; + } + # encode JSON string in base64 if necessary + if ($json < 2 and defined $binaryOutput and Image::ExifTool::IsUTF8(\$str) < 0) { + return '"base64:' . Image::ExifTool::XMP::EncodeBase64($str, 1) . '"'; + } + # escape special characters + $str =~ s/(["\t\n\r\\])/\\$jsonChar{$1}/sg; + if ($json < 2) { # JSON + $str =~ tr/\0//d; # remove all nulls + # escape other control characters with \u + $str =~ s/([\0-\x1f])/sprintf("\\u%.4X",ord $1)/sge; + # JSON strings must be valid UTF8 + Image::ExifTool::XMP::FixUTF8(\$str) unless $altEnc; + } else { # PHP + $str =~ s/\0+$// unless $isBinary; # remove trailing nulls unless binary + # must escape "$" too for PHP + $str =~ s/\$/\\\$/sg; + # escape other control characters with \x + $str =~ s/([\0-\x1f])/sprintf("\\x%.2X",ord $1)/sge; + } + return '"' . $str . '"'; # return the quoted string +} + +#------------------------------------------------------------------------------ +# Print JSON or PHP value +# Inputs: 0) file reference, 1) value, 2) indentation +sub FormatJSON($$$) +{ + local $_; + my ($fp, $val, $ind) = @_; + my $comma; + if (not ref $val) { + print $fp EscapeJSON($val); + } elsif (ref $val eq 'ARRAY') { + if ($joinLists and not ref $$val[0]) { + print $fp EscapeJSON(join $listSep, @$val); + } else { + my ($bra, $ket) = $json == 1 ? ('[',']') : ('Array(',')'); + print $fp $bra; + foreach (@$val) { + print $fp ',' if $comma; + FormatJSON($fp, $_, $ind); + $comma = 1, + } + print $fp $ket, + } + } elsif (ref $val eq 'HASH') { + my ($bra, $ket, $sep) = $json == 1 ? ('{','}',':') : ('Array(',')',' =>'); + print $fp $bra; + my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val; + foreach (@keys) { + print $fp ',' if $comma; + my $key = EscapeJSON($_, 1); + print $fp qq(\n$ind $key$sep ); + # hack to force decimal id's to be printed as strings with -H + if ($showTagID and $_ eq 'id' and $showTagID eq 'H' and $$val{$_} =~ /^\d+\.\d+$/) { + print $fp qq{"$$val{$_}"}; + } else { + FormatJSON($fp, $$val{$_}, "$ind "); + } + $comma = 1, + } + print $fp "\n$ind$ket", + } else { + # (note: SCALAR reference should have already been converted) + print $fp '""'; + } +} + +#------------------------------------------------------------------------------ +# Format value for CSV file +# Inputs: value +# Returns: value quoted if necessary +sub FormatCSV($) +{ + my $val = shift; + # check for valid encoding if the Charset option was used + if ($setCharset and ($val =~ /[^\x09\x0a\x0d\x20-\x7e\x80-\xff]/ or + ($setCharset eq 'UTF8' and Image::ExifTool::IsUTF8(\$val) < 0))) + { + $val = 'base64:' . Image::ExifTool::XMP::EncodeBase64($val, 1); + } + # currently, there is a chance that the value may contain NULL characters unless + # the -b option is used to encode as Base64. It is unclear whether or not this + # is valid CSV, but some readers may not like it. (If this becomes a problem, + # in the future values may need to be truncated at the first NULL character.) + $val = qq{"$val"} if $val =~ s/"/""/g or $val =~ /(^\s+|\s+$)/ or $val =~ /[\n\r]|\Q$csvDelim/; + return $val; +} + +#------------------------------------------------------------------------------ +# Print accumulated CSV information +sub PrintCSV() +{ + my ($file, $lcTag, @tags); + + @csvTags or @csvTags = sort keys %csvTags; + # make a list of tags actually found + foreach $lcTag (@csvTags) { + push @tags, FormatCSV($csvTags{$lcTag}) if $csvTags{$lcTag}; + } + print join($csvDelim, 'SourceFile', @tags), "\n"; + my $empty = defined($forcePrint) ? $forcePrint : ''; + foreach $file (@csvFiles) { + my @vals = (FormatCSV($file)); # start with full file name + my $csvInfo = $database{$file}; + foreach $lcTag (@csvTags) { + next unless $csvTags{$lcTag}; + my $val = $$csvInfo{$lcTag}; + defined $val or push(@vals,$empty), next; + push @vals, FormatCSV($val); + } + print join($csvDelim, @vals), "\n"; + } +} + +#------------------------------------------------------------------------------ +# Add tag groups from structure fields to a list +# Inputs: 0) tag value, 1) parent group, 2) group hash ref, 3) group list ref +sub AddGroups($$$$) +{ + my ($val, $grp, $groupHash, $groupList) = @_; + my ($key, $val2); + if (ref $val eq 'HASH') { + foreach $key (sort keys %$val) { + if ($key =~ /(.*?):/ and not $$groupHash{$1}) { + $$groupHash{$1} = $grp; + push @$groupList, $1; + } + AddGroups($$val{$key}, $grp, $groupHash, $groupList) if ref $$val{$key}; + } + } elsif (ref $val eq 'ARRAY') { + foreach $val2 (@$val) { + AddGroups($val2, $grp, $groupHash, $groupList) if ref $val2; + } + } +} + +#------------------------------------------------------------------------------ +# Convert binary data (SCALAR references) for printing +# Inputs: 0) object reference +# Returns: converted object, or undef if we don't want binary objects +sub ConvertBinary($) +{ + my $obj = shift; + my ($key, $val); + if (ref $obj eq 'HASH') { + foreach $key (keys %$obj) { + next unless ref $$obj{$key}; + $$obj{$key} = ConvertBinary($$obj{$key}); + return undef unless defined $$obj{$key}; + } + } elsif (ref $obj eq 'ARRAY') { + foreach $val (@$obj) { + next unless ref $val; + $val = ConvertBinary($val); + return undef unless defined $val; + } + } elsif (ref $obj eq 'SCALAR') { + return undef if $noBinary; + # (binaryOutput flag is set to 0 for binary mode of XML/PHP/JSON output formats) + if (defined $binaryOutput) { + $obj = $$obj; + # encode in base64 if necessary (0xf7 allows for up to 21-bit UTF-8 code space) + if ($json == 1 and ($obj =~ /[^\x09\x0a\x0d\x20-\x7e\x80-\xf7]/ or + Image::ExifTool::IsUTF8(\$obj) < 0)) + { + $obj = 'base64:' . Image::ExifTool::XMP::EncodeBase64($obj, 1); + } + } else { + # (-b is not valid for HTML output) + my $bOpt = $html ? '' : ', use -b option to extract'; + if ($$obj =~ /^Binary data \d+ bytes$/) { + $obj = "($$obj$bOpt)"; + } else { + $obj = '(Binary data ' . length($$obj) . " bytes$bOpt)"; + } + } + } + return $obj; +} + +#------------------------------------------------------------------------------ +# Compare ValueConv and PrintConv values of a tag to see if they are equal +# Inputs: 0) value1, 1) value2 +# Returns: true if they are equal +sub IsEqual($$) +{ + my ($a, $b) = @_; + # (scalar values are not print-converted) + return 1 if $a eq $b or ref $a eq 'SCALAR'; + if (ref $a eq 'HASH' and ref $b eq 'HASH') { + return 0 if scalar(keys %$a) != scalar(keys %$b); + my $key; + foreach $key (keys %$a) { + return 0 unless IsEqual($$a{$key}, $$b{$key}); + } + } else { + return 0 if ref $a ne 'ARRAY' or ref $b ne 'ARRAY' or @$a != @$b; + my $i; + for ($i=0; $i ++$saveCount }, "TagsFromFile=$setFile"; + # add option to protect the tags which are assigned after this + # (this is the mechanism by which the command-line order-of-operations is preserved) + $opts or $opts = { }; + $$opts{ProtectSaved} = $saveCount; + push @{$setTags{$setFile}}, $opts; +} + +#------------------------------------------------------------------------------ +# Get input file name or reference for calls to the ExifTool API +# Inputs: 0) file name ('-' for STDIN), 1) flag to buffer STDIN +# Returns: file name, or RAF reference for buffering STDIN +sub Infile($;$) +{ + my ($file, $bufferStdin) = @_; + if ($file eq '-' and ($bufferStdin or $rafStdin)) { + if ($rafStdin) { + $rafStdin->Seek(0); # rewind + } elsif (open RAF_STDIN, '-') { + $rafStdin = new File::RandomAccess(\*RAF_STDIN); + $rafStdin->BinMode(); + } + return $rafStdin if $rafStdin; + } + return $file; +} + +#------------------------------------------------------------------------------ +# Set new values from file +# Inputs: 0) exiftool ref, 1) filename, 2) reference to list of values to set +# Returns: 0 on error (and increments $countBadWr) +sub DoSetFromFile($$$) +{ + local $_; + my ($et, $file, $setTags) = @_; + $verbose and print $vout "Setting new values from $file\n"; + my $info = $et->SetNewValuesFromFile(Infile($file,1), @$setTags); + my $numSet = scalar(keys %$info); + if ($$info{Error}) { + # delete all error and warning tags + my @warns = grep /^(Error|Warning)\b/, keys %$info; + $numSet -= scalar(@warns); + # issue a warning for the main error only if we were able to set some tags + if (keys(%$info) > @warns) { + my $err = $$info{Error}; + delete $$info{$_} foreach @warns; + $$info{Warning} = $err; + } + } elsif ($$info{Warning}) { + my $warns = 1; + ++$warns while $$info{"Warning ($warns)"}; + $numSet -= $warns; + } + PrintErrors($et, $info, $file) and EFile($file), ++$countBadWr, return 0; + Warn "Warning: No writable tags set from $file\n" unless $numSet; + return 1; +} + +#------------------------------------------------------------------------------ +# Translate backslashes to forward slashes in filename if necessary +# Inputs: 0) Filename +# Returns: nothing, but changes filename if necessary +sub CleanFilename($) +{ + $_[0] =~ tr/\\/\// if $hasBackslash{$^O}; +} + +#------------------------------------------------------------------------------ +# Check for valid UTF-8 of a file name +# Inputs: 0) string, 1) original encoding +# Returns: 0=plain ASCII, 1=valid UTF-8, -1=invalid UTF-8 (and print warning) +sub CheckUTF8($$) +{ + my ($file, $enc) = @_; + my $isUTF8 = 0; + if ($file =~ /[\x80-\xff]/) { + $isUTF8 = Image::ExifTool::IsUTF8(\$file); + if ($isUTF8 < 0) { + if ($enc) { + Warn("Invalid filename encoding for $file\n"); + } elsif (not defined $enc) { + WarnOnce(qq{FileName encoding not specified. Use "-charset FileName=CHARSET"\n}); + } + } + } + return $isUTF8; +} + +#------------------------------------------------------------------------------ +# Set window title +# Inputs: title string or '' to reset title +sub SetWindowTitle($) +{ + my $title = shift; + if ($curTitle ne $title) { + $curTitle = $title; + if ($^O eq 'MSWin32') { + $title =~ s/([&\/\?:|"<>])/^$1/g; # escape special chars + eval { system qq{title $title} }; + } else { + # (this only works for XTerm terminals, and STDERR must go to the console) + printf STDERR "\033]0;%s\007", $title; + } + } +} + +#------------------------------------------------------------------------------ +# Process files in our @files list +# Inputs: 0) ExifTool ref, 1) list ref to just return full file names +sub ProcessFiles($;$) +{ + my ($et, $list) = @_; + my $enc = $et->Options('CharsetFileName'); + my $file; + foreach $file (@files) { + $et->Options(CharsetFileName => 'UTF8') if $utf8FileName{$file}; + if (defined $progressMax) { + unless (defined $progressNext) { + $progressNext = $progressCount + $progressIncr; + $progressNext -= $progressNext % $progressIncr; # (show even multiples) + $progressNext = $progressMax if $progressNext > $progressMax; + } + ++$progressCount; + if ($progress) { + if ($progressCount >= $progressNext) { + $progStr = " [$progressCount/$progressMax]"; + } else { + undef $progStr; # don't update progress yet + } + } + } + if ($et->IsDirectory($file) and not $listDir) { + $multiFile = $validFile = 1; + ScanDir($et, $file, $list); + } elsif ($filterFlag and not AcceptFile($file)) { + if ($et->Exists($file)) { + $filtered = 1; + Progress($vout, "-------- $file (wrong extension)") if $verbose; + } else { + Warn "Error: File not found - $file\n"; + FileNotFound($file); + $rtnVal = 1; + } + } else { + $validFile = 1; + if ($list) { + push(@$list, $file); + } else { + if (%endDir) { + my ($d, $f) = Image::ExifTool::SplitFileName($file); + next if $endDir{$d}; + } + GetImageInfo($et, $file); + $end and Warn("End called - $file\n"); + if ($endDir) { + Warn("EndDir called - $file\n"); + my ($d, $f) = Image::ExifTool::SplitFileName($file); + $endDir{$d} = 1; + undef $endDir; + } + } + } + $et->Options(CharsetFileName => $enc) if $utf8FileName{$file}; + last if $end; + } +} + +#------------------------------------------------------------------------------ +# Scan directory for image files +# Inputs: 0) ExifTool ref, 1) directory name, 2) list ref to return file names +sub ScanDir($$;$) +{ + local $_; + my ($et, $dir, $list) = @_; + my (@fileList, $done, $file, $utf8Name, $winSurrogate, $endThisDir); + my $enc = $et->Options('CharsetFileName'); + # recode as UTF-8 if necessary + if ($enc) { + unless ($enc eq 'UTF8') { + $dir = $et->Decode($dir, $enc, undef, 'UTF8'); + $et->Options(CharsetFileName => 'UTF8'); # now using UTF8 + } + $utf8Name = 1; + } + return if $ignore{$dir}; + # use Win32::FindFile on Windows if available + # (ReadDir will croak if there is a wildcard, so check for this) + if ($^O eq 'MSWin32' and $dir !~ /[*?]/) { + undef $evalWarning; + local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] };; + if (CheckUTF8($dir, $enc) >= 0) { + if (eval { require Win32::FindFile }) { + eval { + @fileList = Win32::FindFile::ReadDir($dir); + $_ = $_->cFileName foreach @fileList; + }; + $@ and $evalWarning = $@; + if ($evalWarning) { + chomp $evalWarning; + $evalWarning =~ s/ at .*//s; + Warn "Warning: [Win32::FindFile] $evalWarning - $dir\n"; + $winSurrogate = 1 if $evalWarning =~ /surrogate/; + } else { + $et->Options(CharsetFileName => 'UTF8'); # now using UTF8 + $utf8Name = 1; # ReadDir returns UTF-8 file names + $done = 1; + } + } else { + $done = 0; + } + } + } + unless ($done) { + # use standard perl library routines to read directory + unless (opendir(DIR_HANDLE, $dir)) { + Warn("Error opening directory $dir\n"); + return; + } + @fileList = readdir(DIR_HANDLE); + closedir(DIR_HANDLE); + if (defined $done) { + # issue warning if some names would have required Win32::FindFile + foreach $file ($dir, @fileList) { + next unless $file =~ /[\?\x80-\xff]/; + WarnOnce("Install Win32::FindFile to support Windows Unicode file names in directories\n"); + last; + } + } + } + $dir =~ /\/$/ or $dir .= '/'; # make sure directory name ends with '/' + foreach $file (@fileList) { + my $path = "$dir$file"; + if ($et->IsDirectory($path)) { + next unless $recurse; + # ignore directories starting with "." by default + next if $file =~ /^\./ and ($recurse == 1 or $file eq '.' or $file eq '..'); + next if $ignore{$file} or ($ignore{SYMLINKS} and -l $path); + ScanDir($et, $path, $list); + last if $end; + next; + } + next if $endThisDir; + next if $ignoreHidden and $file =~ /^\./; # ignore hidden files if specified + # apply rules from -ext options + my $accepted; + if ($filterFlag) { + $accepted = AcceptFile($file) or next; + # must be specifically accepted to bypass selection logic + $accepted &= 0x01; + } + unless ($accepted) { + # read/write this file if it is a supported type + if ($scanWritable) { + if ($scanWritable eq '1') { + next unless CanWrite($file); + } else { + my $type = GetFileType($file); + next unless defined $type and $type eq $scanWritable; + } + } elsif (not GetFileType($file)) { + next unless $doUnzip; + next unless $file =~ /\.(gz|bz2)$/i; + } + } + # Windows patch to avoid replacing filename containing Unicode surrogate with 8.3 name + if ($winSurrogate and $isWriting and + (not $overwriteOrig or $overwriteOrig != 2) and + not $doSetFileName and $file =~ /~/) # (8.3 name will contain a tilde) + { + Warn("Not writing $path\n"); + WarnOnce("Use -overwrite_original_in_place to write files with Unicode surrogate characters\n"); + EFile($file); + ++$countBad; + next; + } + $utf8FileName{$path} = 1 if $utf8Name; + if ($list) { + push(@$list, $path); + } else { + GetImageInfo($et, $path); + if ($end) { + Warn("End called - $file\n"); + last; + } + if ($endDir) { + $path =~ s(/$)(); + Warn("EndDir called - $path\n"); + $endDir{$path} = 1; + $endThisDir = 1; + undef $endDir; + } + } + } + ++$countDir; + $et->Options(CharsetFileName => $enc); # restore original setting +} + +#------------------------------------------------------------------------------ +# Find files with wildcard expression on Windows +# Inputs: 0) ExifTool ref, 1) file name with wildcards +# Returns: list of matching file names +# Notes: +# 1) Win32::FindFile must already be loaded +# 2) Sets flag in %utf8FileName for each file found +sub FindFileWindows($$) +{ + my ($et, $wildfile) = @_; + + # recode file name as UTF-8 if necessary + my $enc = $et->Options('CharsetFileName'); + $wildfile = $et->Decode($wildfile, $enc, undef, 'UTF8') if $enc and $enc ne 'UTF8'; + $wildfile =~ tr/\\/\//; # use forward slashes + my ($dir, $wildname) = ($wildfile =~ m{(.*[:/])(.*)}) ? ($1, $2) : ('', $wildfile); + if ($dir =~ /[*?]/) { + Warn "Wildcards don't work in the directory specification\n"; + return (); + } + CheckUTF8($wildfile, $enc) >= 0 or return (); + undef $evalWarning; + local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] }; + my @files; + eval { + my @names = Win32::FindFile::FindFile($wildfile) or return; + # (apparently this isn't always sorted, so do a case-insensitive sort here) + @names = sort { uc($a) cmp uc($b) } @names; + my ($rname, $nm); + # replace "\?" with ".", and "\*" with ".*" for regular expression + ($rname = quotemeta $wildname) =~ s/\\\?/./g; + $rname =~ s/\\\*/.*/g; + foreach $nm (@names) { + $nm = $nm->cFileName; + # make sure that FindFile behaves + # (otherwise "*.jpg" matches things like "a.jpg_original"!) + next unless $nm =~ /^$rname$/i; + next if $nm eq '.' or $nm eq '..'; # don't match "." and ".." + my $file = "$dir$nm"; # add back directory name + push @files, $file; + $utf8FileName{$file} = 1; # flag this file name as UTF-8 encoded + } + }; + $@ and $evalWarning = $@; + if ($evalWarning) { + chomp $evalWarning; + $evalWarning =~ s/ at .*//s; + Warn "Error: [Win32::FindFile] $evalWarning - $wildfile\n"; + undef @files; + EFile($wildfile); + ++$countBad; + } + return @files; +} + +#------------------------------------------------------------------------------ +# Handle missing file on the command line +# Inputs: 0) file name +sub FileNotFound($) +{ + my $file = shift; + if ($file =~ /^(DIR|FILE)$/) { + my $type = { DIR => 'directory', FILE => 'file' }->{$file}; + Warn qq{You were meant to enter any valid $type name, not "$file" literally.\n}; + } +} + +#------------------------------------------------------------------------------ +# Patch for OS X 10.6 to preserve file modify date +# (this probably isn't a 100% fix, but it may solve a majority of the cases) +sub PreserveTime() +{ + local $_; + $mt->SetFileTime($_, @{$preserveTime{$_}}) foreach keys %preserveTime; + undef %preserveTime; +} + +#------------------------------------------------------------------------------ +# Return absolute path for a file +# Inputs: 0) file name +# Returns: absolute path string, or undef if path could not be determined +# Note: Warnings should be suppressed when calling this routine +sub AbsPath($) +{ + my $file = shift; + my $path; + if (defined $file and eval { require Cwd }) { + $path = eval { Cwd::abs_path($file) }; + # make the delimiters and case consistent + # (abs_path is very inconsistent about what it returns in Windows) + if (defined $path and $hasBackslash{$^O}) { + $path =~ tr/\\/\//; + $path = lc $path; + } + } + return $path; +} + +#------------------------------------------------------------------------------ +# Convert file name to ExifTool Charset +# Inputs: 0) ExifTool ref, 1) file name in CharsetFileName +# Returns: file name in ExifTool Charset +sub MyConvertFileName($$) +{ + my ($et, $file) = @_; + my $enc = $et->Options('CharsetFileName'); + $et->Options(CharsetFileName => 'UTF8') if $utf8FileName{$file}; + my $convFile = $et->ConvertFileName($file); + $et->Options(CharsetFileName => $enc) if $utf8FileName{$file}; + return $convFile; +} + +#------------------------------------------------------------------------------ +# Add print format entry +# Inputs: 0) expression string +sub AddPrintFormat($) +{ + my $expr = shift; + my $type; + if ($expr =~ /^#/) { + $expr =~ s/^#\[(HEAD|SECT|IF|BODY|ENDS|TAIL)\]// or return; # ignore comments + $type = $1; + } else { + $type = 'BODY'; + } + $printFmt{$type} or $printFmt{$type} = [ ]; + push @{$printFmt{$type}}, $expr; + # add to list of requested tags + push @requestTags, $expr =~ /\$\{?((?:[-\w]+:)*[-\w?*]+)/g; +} + +#------------------------------------------------------------------------------ +# Get suggested file extension based on tag value for binary output +# Inputs: 0) ExifTool ref, 1) data ref, 2) tag name +# Returns: file extension (lower case), or 'dat' if unknown +sub SuggestedExtension($$$) +{ + my ($et, $valPt, $tag) = @_; + my $ext; + if (not $binaryOutput) { + $ext = 'txt'; + } elsif ($$valPt =~ /^\xff\xd8\xff/) { + $ext = 'jpg'; + } elsif ($$valPt =~ /^(\0\0\0\x0cjP( |\x1a\x1a)\x0d\x0a\x87\x0a|\xff\x4f\xff\x51\0)/) { + $ext = 'jp2'; + } elsif ($$valPt =~ /^(\x89P|\x8aM|\x8bJ)NG\r\n\x1a\n/) { + $ext = 'png'; + } elsif ($$valPt =~ /^GIF8[79]a/) { + $ext = 'gif'; + } elsif ($$valPt =~ /^<\?xpacket/ or $tag eq 'XMP') { + $ext = 'xmp'; + } elsif ($$valPt =~ /^<\?xml/ or $tag eq 'XML') { + $ext = 'xml'; + } elsif ($$valPt =~ /^RIFF....WAVE/s) { + $ext = 'wav'; + } elsif ($tag eq 'OriginalRawImage' and defined($ext = $et->GetValue('OriginalRawFileName'))) { + $ext =~ s/^.*\.//s; + $ext = $ext ? lc($ext) : 'raw'; + } elsif ($tag eq 'EXIF') { + $ext = 'exif'; + } elsif ($tag eq 'ICC_Profile') { + $ext = 'icc'; + } elsif ($$valPt =~ /^(MM\0\x2a|II\x2a\0)/) { + $ext = 'tiff'; + } elsif ($$valPt =~ /^.{4}ftyp(3gp|mp4|f4v|qt )/s) { + my %movType = ( 'qt ' => 'mov' ); + $ext = $movType{$1} || $1; + } elsif ($$valPt !~ /^.{0,4096}\0/s) { + $ext = 'txt'; + } elsif ($$valPt =~ /^BM.{15}\0/s) { + $ext = 'bmp'; + } elsif ($$valPt =~ /^CANON OPTIONAL DATA\0/) { + $ext = 'vrd'; + } elsif ($$valPt =~ /^IIII\x04\0\x04\0/) { + $ext = 'dr4'; + } elsif ($$valPt =~ /^(.{10}|.{522})(\x11\x01|\x00\x11)/s) { + $ext = 'pict'; + } elsif ($$valPt =~ /^\xff\x0a|\0\0\0\x0cJXL \x0d\x0a......ftypjxl/s) { + $ext = 'jxl'; + } else { + $ext = 'dat'; + } + return $ext; +} + +#------------------------------------------------------------------------------ +# Load print format file +# Inputs: 0) file name +# - saves lines of file to %printFmt list +# - adds tag names to @tags list +sub LoadPrintFormat($) +{ + my $arg = shift; + if (not defined $arg) { + Error "Must specify file or expression for -p option\n"; + } elsif ($arg !~ /\n/ and -f $arg and $mt->Open(\*FMT_FILE, $arg)) { + foreach () { + AddPrintFormat($_); + } + close(FMT_FILE); + } else { + AddPrintFormat($arg . "\n"); + } +} + +#------------------------------------------------------------------------------ +# A sort of sprintf for filenames +# Inputs: 0) format string (%d=dir, %f=file name, %e=ext), +# 1) source filename or undef to test format string +# 2-4) [%t %g %s %o only] tag name, ref to array of group names, +# suggested extension, original raw file name +# Returns: new filename or undef on error (or if no file and fmt contains token) +sub FilenameSPrintf($;$@) +{ + my ($fmt, $file, @extra) = @_; + local $_; + # return format string straight away if no tokens + return $fmt unless $fmt =~ /%[-+]?\d*[.:]?\d*[lu]?[dDfFeEtgso]/; + return undef unless defined $file; + CleanFilename($file); # make sure we are using forward slashes + # split filename into directory, file, extension + my %part; + @part{qw(d f E)} = ($file =~ /^(.*?)([^\/]*?)(\.[^.\/]*)?$/); + defined $part{f} or Warn("Error: Bad pattern match for file $file\n"), return undef; + if ($part{E}) { + $part{e} = substr($part{E}, 1); + } else { + @part{qw(e E)} = ('', ''); + } + $part{F} = $part{f} . $part{E}; + ($part{D} = $part{d}) =~ s{/+$}{}; + @part{qw(t g s o)} = @extra; + my ($filename, $pos) = ('', 0); + while ($fmt =~ /(%([-+]?)(\d*)([.:]?)(\d*)([lu]?)([dDfFeEtgso]))/g) { + $filename .= substr($fmt, $pos, pos($fmt) - $pos - length($1)); + $pos = pos($fmt); + my ($sign, $wid, $dot, $skip, $mod, $code) = ($2, $3, $4, $5 || 0, $6, $7); + my (@path, $part, $len, $groups); + if (lc $code eq 'd' and $dot and $dot eq ':') { + # field width applies to directory levels instead of characters + @path = split '/', $part{$code}; + $len = scalar @path; + } else { + if ($code eq 'g') { + $groups = $part{g} || [ ] unless defined $groups; + $fmt =~ /\G(\d?)/g; # look for %g1, %g2, etc + $part{g} = $$groups[$1 || 0]; + $pos = pos($fmt); + } + $part{$code} = '' unless defined $part{$code}; + $len = length $part{$code}; + } + next unless $skip < $len; + $wid = $len - $skip if $wid eq '' or $wid + $skip > $len; + $skip = $len - $wid - $skip if $sign eq '-'; + if (@path) { + $part = join('/', @path[$skip..($skip+$wid-1)]); + $part .= '/' unless $code eq 'D'; + } else { + $part = substr($part{$code}, $skip, $wid); + } + $part = ($mod eq 'u') ? uc($part) : lc($part) if $mod; + $filename .= $part; + } + $filename .= substr($fmt, $pos); # add rest of file name + # remove double slashes (except at beginning to allow Windows UNC paths) + $filename =~ s{(?!^)//}{/}g; + return $filename; +} + +#------------------------------------------------------------------------------ +# Convert number to alphabetical index: a, b, c, ... z, aa, ab ... +# Inputs: 0) number +# Returns: alphabetical index string +sub Num2Alpha($) +{ + my $num = shift; + my $alpha = chr(97 + ($num % 26)); + while ($num >= 26) { + $num = int($num / 26) - 1; + $alpha = chr(97 + ($num % 26)) . $alpha; + } + return $alpha; +} + +#------------------------------------------------------------------------------ +# Expand '%c' and '%C' codes if filename to get next unused file name +# Inputs: 0) file name format string, 1) filename ok to use even if it exists +# Returns: new file name +sub NextUnusedFilename($;$) +{ + my ($fmt, $okfile) = @_; + return $fmt unless $fmt =~ /%[-+]?\d*\.?\d*[lun]?[cC]/; + my %sep = ( '-' => '-', '+' => '_' ); + my ($copy, $alpha) = (0, 'a'); + for (;;) { + my ($filename, $pos) = ('', 0); + while ($fmt =~ /(%([-+]?)(\d*)(\.?)(\d*)([lun]?)([cC]))/g) { + $filename .= substr($fmt, $pos, pos($fmt) - $pos - length($1)); + $pos = pos($fmt); + my ($sign, $wid, $dec, $wid2, $mod, $tok) = ($2, $3 || 0, $4, $5 || 0, $6, $7); + my $seq; + if ($tok eq 'C') { + $seq = $wid + ($sign eq '-' ? $seqFileDir : $seqFileNum) - 1; + $wid = $wid2; + } else { + next unless $dec or $copy; + $wid = $wid2 if $wid < $wid2; + # add dash or underline separator if '-' or '+' specified + $filename .= $sep{$sign} if $sign; + } + if ($mod and $mod ne 'n') { + my $a = $tok eq 'C' ? Num2Alpha($seq) : $alpha; + my $str = ($wid and $wid > length $a) ? 'a' x ($wid - length($a)) : ''; + $str .= $a; + $str = uc $str if $mod eq 'u'; + $filename .= $str; + } else { + my $c = $tok eq 'C' ? $seq : $copy; + my $num = $c + ($mod ? 1 : 0); + $filename .= $wid ? sprintf("%.${wid}d",$num) : $num; + } + } + $filename .= substr($fmt, $pos); # add rest of file name + # return now with filename unless file exists + return $filename unless ($mt->Exists($filename, 1) and not defined $usedFileName{$filename}) or $usedFileName{$filename}; + if (defined $okfile) { + return $filename if $filename eq $okfile; + my ($fn, $ok) = (AbsPath($filename), AbsPath($okfile)); + return $okfile if defined $fn and defined $ok and $fn eq $ok; + } + ++$copy; + ++$alpha; + } +} + +#------------------------------------------------------------------------------ +# Create directory for specified file +# Inputs: 0) complete file name including path +# Returns: true if a directory was created +my $k32CreateDir; +sub CreateDirectory($) +{ + my $file = shift; + my ($dir, $created); + ($dir = $file) =~ s/[^\/]*$//; # remove filename from path specification + if ($dir and not $mt->IsDirectory($dir)) { + my @parts = split /\//, $dir; + $dir = ''; + foreach (@parts) { + $dir .= $_; + if (length $dir and not $mt->IsDirectory($dir) and + # don't try to create a network drive root directory + not ($hasBackslash{$^O} and $dir =~ m{^//[^/]*$})) + { + my $success; + # create directory since it doesn't exist + my $d2 = $dir; # (must make a copy in case EncodeFileName recodes it) + if ($mt->EncodeFileName($d2)) { + # handle Windows Unicode directory names + unless (eval { require Win32::API }) { + Error('Install Win32::API to create directories with Unicode names'); + return 0; + } + unless ($k32CreateDir) { + $k32CreateDir = new Win32::API('KERNEL32', 'CreateDirectoryW', 'PP', 'I'); + } + $success = $k32CreateDir->Call($d2, 0) if $k32CreateDir; + } else { + $success = mkdir($d2, 0777); + } + $success or Error("Error creating directory $dir\n"), return 0; + $verbose and print $vout "Created directory $dir\n"; + $created = 1; + } + $dir .= '/'; + } + ++$countNewDir if $created; + } + return $created; +} + +#------------------------------------------------------------------------------ +# Open output text file +# Inputs: 0) file name format string, 1-N) extra arguments for FilenameSPrintf +# Returns: 0) file reference (or undef on error), 1) file name if opened, 2) append flag +# Notes: returns reference to STDOUT and no file name if no textOut file needed +sub OpenOutputFile($;@) +{ + my ($file, @args) = @_; + my ($fp, $outfile, $append); + if ($textOut) { + $outfile = $file; + CleanFilename($outfile); + if ($textOut =~ /%[-+]?\d*[.:]?\d*[lun]?[dDfFeEtgsocC]/ or defined $tagOut) { + # make filename from printf-like $textOut + $outfile = FilenameSPrintf($textOut, $file, @args); + return () unless defined $outfile; + $outfile = NextUnusedFilename($outfile); + CreateDirectory($outfile); # create directory if necessary + } else { + $outfile =~ s/\.[^.\/]*$//; # remove extension if it exists + $outfile .= $textOut; + } + my $mode = '>'; + if ($mt->Exists($outfile, 1)) { + unless ($textOverwrite) { + Warn "Output file $outfile already exists for $file\n"; + return (); + } + if ($textOverwrite == 2 or ($textOverwrite == 3 and $created{$outfile})) { + $mode = '>>'; + $append = 1; + } + } + unless ($mt->Open(\*OUTFILE, $outfile, $mode)) { + my $what = $mode eq '>' ? 'creating' : 'appending to'; + Error("Error $what $outfile\n"); + return (); + } + binmode(OUTFILE) if $binaryOutput; + $fp = \*OUTFILE; + } else { + $fp = \*STDOUT; + } + return($fp, $outfile, $append); +} + +#------------------------------------------------------------------------------ +# Filter files based on extension +# Inputs: 0) file name +# Returns: 0 = rejected, 1 = specifically accepted, 2 = accepted by default +# Notes: This routine should only be called if $filterFlag is set +sub AcceptFile($) +{ + my $file = shift; + my $ext = ($file =~ /^.*\.(.+)$/s) ? uc($1) : ''; + return $filterExt{$ext} if defined $filterExt{$ext}; + return $filterExt{'*'} if defined $filterExt{'*'}; + return 0 if $filterFlag & 0x02; # reject if accepting specific extensions + return 2; # accept by default +} + +#------------------------------------------------------------------------------ +# Slurp file into buffer +# Inputs: 0) file name, 1) buffer reference +# Returns: 1 on success +sub SlurpFile($$) +{ + my ($file, $buffPt) = @_; + $mt->Open(\*INFILE, $file) or Warn("Error opening file $file\n"), return 0; + binmode(INFILE); + # (CAREFUL!: must clear buffer first to reset possible utf8 flag because the data + # would be corrupted if it was read into a buffer which had the utf8 flag set!) + undef $$buffPt; + my $bsize = 1024 * 1024; + my $num = read(INFILE, $$buffPt, $bsize); + unless (defined $num) { + close(INFILE); + Warn("Error reading $file\n"); + return 0; + } + my $bmax = 64 * $bsize; + while ($num == $bsize) { + $bsize *= 2 if $bsize < $bmax; + my $buff; + $num = read(INFILE, $buff, $bsize); + last unless $num; + $$buffPt .= $buff; + } + close(INFILE); + return 1; +} + + +#------------------------------------------------------------------------------ +# Filter argfile line +# Inputs: 0) line of argfile +# Returns: filtered line or undef to ignore +sub FilterArgfileLine($) +{ + my $arg = shift; + if ($arg =~ /^#/) { # comment lines begin with '#' + return undef unless $arg =~ s/^#\[CSTR\]//; + $arg =~ s/[\x0d\x0a]+$//s; # remove trailing newline + # escape double quotes, dollar signs and ampersands if they aren't already + # escaped by an odd number of backslashes, and escape a single backslash + # if it occurs at the end of the string + $arg =~ s{\\(.)|(["\$\@]|\\$)}{'\\'.($2 || $1)}sge; + # un-escape characters in C string + my %esc = ( a => "\a", b => "\b", f => "\f", n => "\n", + r => "\r", t => "\t", '"' => '"', '\\' => '\\' ); + $arg =~ s/\\(.)/$esc{$1}||'\\'.$1/egs; + } else { + $arg =~ s/^\s+//; # remove leading white space + $arg =~ s/[\x0d\x0a]+$//s; # remove trailing newline + # remove white space before, and single space after '=', '+=', '-=' or '<=' + $arg =~ s/^(-[-:\w]+#?)\s*([-+<]?=) ?/$1$2/; + return undef if $arg eq ''; + } + return $arg; +} + +#------------------------------------------------------------------------------ +# Read arguments from -stay_open argfile +# Inputs: 0) argument list ref +# Notes: blocks until -execute, -stay_open or -@ option is available +# (or until there was an error reading from the file) +sub ReadStayOpen($) +{ + my $args = shift; + my (@newArgs, $processArgs, $result, $optArgs); + my $lastOpt = ''; + my $unparsed = length $stayOpenBuff; + for (;;) { + if ($unparsed) { + # parse data already read from argfile + $result = $unparsed; + undef $unparsed; + } else { + # read more data from argfile + # - this read may block (which is good) if reading from a pipe + $result = sysread(STAYOPEN, $stayOpenBuff, 65536, length($stayOpenBuff)); + } + if ($result) { + my $pos = 0; + while ($stayOpenBuff =~ /\n/g) { + my $len = pos($stayOpenBuff) - $pos; + my $arg = substr($stayOpenBuff, $pos, $len); + $pos += $len; + $arg = FilterArgfileLine($arg); + next unless defined $arg; + push @newArgs, $arg; + if ($optArgs) { + # this is an argument for the last option + undef $optArgs; + next unless $lastOpt eq '-stay_open' or $lastOpt eq '-@'; + } else { + $lastOpt = lc $arg; + $optArgs = $optArgs{$arg}; + unless (defined $optArgs) { + $optArgs = $optArgs{$lastOpt}; + # handle options with trailing numbers + $optArgs = $optArgs{"$1#$2"} if not defined $optArgs and $lastOpt =~ /^(.*?)\d+(!?)$/; + } + next unless $lastOpt =~ /^-execute\d*$/; + } + $processArgs = 1; + last; # process arguments up to this point + } + next unless $pos; # nothing to do if we didn't read any arguments + # keep unprocessed data in buffer + $stayOpenBuff = substr($stayOpenBuff, $pos); + if ($processArgs) { + # process new arguments after -execute or -stay_open option + unshift @$args, @newArgs; + last; + } + } elsif ($result == 0) { + # sysread() didn't block (eg. when reading from a file), + # so wait for a short time (1/100 sec) then try again + # Note: may break out of this early if SIGCONT is received + select(undef,undef,undef,0.01); + } else { + Warn "Error reading from ARGFILE\n"; + close STAYOPEN; + $stayOpen = 0; + last; + } + } +} + +#------------------------------------------------------------------------------ +# Add new entry to -efile output file +# Inputs: 0) file name, 1) -efile option number (0=error, 1=same, 2=failed, 3=updated, 4=created) +sub EFile($$) +{ + my $entry = shift; + my $efile = $efile[shift || 0]; + if (defined $efile and length $entry and $entry ne '-') { + my $err; + CreateDirectory($efile); + if ($mt->Open(\*EFILE_FILE, $efile, '>>')) { + print EFILE_FILE $entry, "\n" or Warn("Error writing to $efile\n"), $err = 1; + close EFILE_FILE; + } else { + Warn("Error opening '${efile}' for append\n"); + $err = 1; + } + if ($err) { + defined $_ and $_ eq $efile and undef $_ foreach @efile; + } + } +} + +#------------------------------------------------------------------------------ +# Print progress message if it is time for it +# Inputs: 0) file ref, 1) message +sub Progress($$) +{ + my ($file, $msg) = @_; + if (defined $progStr) { + print $file $msg, $progStr, "\n"; + undef $progressNext if defined $progressMax; + } +} + +#------------------------------------------------------------------------------ +# Print list of tags +# Inputs: 0) message, 1-N) list of tag names +sub PrintTagList($@) +{ + my $msg = shift; + print $msg, ":\n" unless $quiet; + my $tag; + if ($outFormat < 0 and $msg =~ /file extensions$/ and @_) { + foreach $tag (@_) { + printf(" %-11s %s\n", $tag, GetFileType($tag, 1)); + } + return; + } + my ($len, $pad) = (0, $quiet ? '' : ' '); + foreach $tag (@_) { + my $taglen = length($tag); + if ($len + $taglen > 77) { + print "\n"; + ($len, $pad) = (0, $quiet ? '' : ' '); + } + print $pad, $tag; + $len += $taglen + 1; + $pad = ' '; + } + @_ or print $pad, '[empty list]'; + print "\n"; +} + +#------------------------------------------------------------------------------ +# Print warnings and errors from info hash +# Inputs: 0) ExifTool object ref, 1) info hash, 2) file name +# Returns: true if there was an Error +sub PrintErrors($$$) +{ + my ($et, $info, $file) = @_; + my ($tag, $key); + foreach $tag (qw(Warning Error)) { + next unless $$info{$tag}; + my @keys = ( $tag ); + push @keys, sort(grep /^$tag /, keys %$info) if $et->Options('Duplicates'); + foreach $key (@keys) { + Warn "$tag: $info->{$key} - $file\n"; + } + } + return $$info{Error}; +} + +__END__ + +=head1 NAME + +exiftool - Read and write meta information in files + +=head1 SYNOPSIS + +=head2 Reading + +B [I] [-I...] [--I...] I... + +=head2 Writing + +B [I] -I[+-E]=[I]... I... + +=head2 Copying + +B [I] B<-tagsFromFile> I +[-[IE]I...] I... + +=head2 Other + +B [ B<-ver> | +B<-list>[B|B|B|B|B[I]|B|B] ] + +For specific examples, see the L sections below. + +This documentation is displayed if exiftool is run without an input I +when one is expected. + +=head1 DESCRIPTION + +A command-line interface to L, used for +reading and writing meta information in a variety of file types. I is +one or more source file names, directory names, or C<-> for the standard +input. Metadata is read from source files and printed in readable form to +the console (or written to output text files with B<-w>). + +To write or delete metadata, tag values are assigned using +-I=[I], and/or the B<-geotag>, B<-csv=> or B<-json=> options. +To copy or move metadata, the B<-tagsFromFile> feature is used. By default +the original files are preserved with C<_original> appended to their names +-- be sure to verify that the new files are OK before erasing the originals. +Once in write mode, exiftool will ignore any read-specific options. + +Note: If I is a directory name then only supported file types in the +directory are processed (in write mode only writable types are processed). +However, files may be specified by name, or the B<-ext> option may be used +to force processing of files with any extension. Hidden files in the +directory are also processed. Adding the B<-r> option causes subdirectories +to be processed recursively, but subdirectories with names beginning with +"." are skipped unless B<-r.> is used. + +Below is a list of file types and meta information formats currently +supported by ExifTool (r = read, w = write, c = create): + + File Types + ------------+-------------+-------------+-------------+------------ + 360 r/w | DPX r | ITC r | NUMBERS r | RAW r/w + 3FR r | DR4 r/w/c | J2C r | O r | RIFF r + 3G2 r/w | DSS r | JNG r/w | ODP r | RSRC r + 3GP r/w | DV r | JP2 r/w | ODS r | RTF r + 7Z r | DVB r/w | JPEG r/w | ODT r | RW2 r/w + A r | DVR-MS r | JSON r | OFR r | RWL r/w + AA r | DYLIB r | JXL r | OGG r | RWZ r + AAE r | EIP r | K25 r | OGV r | RM r + AAX r/w | EPS r/w | KDC r | ONP r | SEQ r + ACR r | EPUB r | KEY r | OPUS r | SKETCH r + AFM r | ERF r/w | LA r | ORF r/w | SO r + AI r/w | EXE r | LFP r | ORI r/w | SR2 r/w + AIFF r | EXIF r/w/c | LIF r | OTF r | SRF r + APE r | EXR r | LNK r | PAC r | SRW r/w + ARQ r/w | EXV r/w/c | LRV r/w | PAGES r | SVG r + ARW r/w | F4A/V r/w | M2TS r | PBM r/w | SWF r + ASF r | FFF r/w | M4A/V r/w | PCD r | THM r/w + AVI r | FITS r | MACOS r | PCX r | TIFF r/w + AVIF r/w | FLA r | MAX r | PDB r | TORRENT r + AZW r | FLAC r | MEF r/w | PDF r/w | TTC r + BMP r | FLIF r/w | MIE r/w/c | PEF r/w | TTF r + BPG r | FLV r | MIFF r | PFA r | TXT r + BTF r | FPF r | MKA r | PFB r | VCF r + CHM r | FPX r | MKS r | PFM r | VNT r + COS r | GIF r/w | MKV r | PGF r | VRD r/w/c + CR2 r/w | GLV r/w | MNG r/w | PGM r/w | VSD r + CR3 r/w | GPR r/w | MOBI r | PLIST r | WAV r + CRM r/w | GZ r | MODD r | PICT r | WDP r/w + CRW r/w | HDP r/w | MOI r | PMP r | WEBP r/w + CS1 r/w | HDR r | MOS r/w | PNG r/w | WEBM r + CSV r | HEIC r/w | MOV r/w | PPM r/w | WMA r + CUR r | HEIF r/w | MP3 r | PPT r | WMV r + CZI r | HTML r | MP4 r/w | PPTX r | WPG r + DCM r | ICC r/w/c | MPC r | PS r/w | WTV r + DCP r/w | ICO r | MPG r | PSB r/w | WV r + DCR r | ICS r | MPO r/w | PSD r/w | X3F r/w + DFONT r | IDML r | MQV r/w | PSP r | XCF r + DIVX r | IIQ r/w | MRC r | QTIF r/w | XLS r + DJVU r | IND r/w | MRW r/w | R3D r | XLSX r + DLL r | INSP r/w | MXF r | RA r | XMP r/w/c + DNG r/w | INSV r | NEF r/w | RAF r/w | ZIP r + DOC r | INX r | NKSC r/w | RAM r | + DOCX r | ISO r | NRW r/w | RAR r | + + Meta Information + ----------------------+----------------------+--------------------- + EXIF r/w/c | CIFF r/w | Ricoh RMETA r + GPS r/w/c | AFCP r/w | Picture Info r + IPTC r/w/c | Kodak Meta r/w | Adobe APP14 r + XMP r/w/c | FotoStation r/w | MPF r + MakerNotes r/w/c | PhotoMechanic r/w | Stim r + Photoshop IRB r/w/c | JPEG 2000 r | DPX r + ICC Profile r/w/c | DICOM r | APE r + MIE r/w/c | Flash r | Vorbis r + JFIF r/w/c | FlashPix r | SPIFF r + Ducky APP12 r/w/c | QuickTime r | DjVu r + PDF r/w/c | Matroska r | M2TS r + PNG r/w/c | MXF r | PE/COFF r + Canon VRD r/w/c | PrintIM r | AVCHD r + Nikon Capture r/w/c | FLAC r | ZIP r + GeoTIFF r/w/c | ID3 r | (and more) + +=head1 OPTIONS + +Case is not significant for any command-line option (including tag and group +names), except for single-character options when the corresponding +upper-case option exists. Many single-character options have equivalent +long-name versions (shown in brackets), and some options have inverses which +are invoked with a leading double-dash. Unrecognized options are +interpreted as tag names (for this reason, multiple single-character options +may NOT be combined into one argument). Contrary to standard practice, +options may appear after source file names on the exiftool command line. + +=head2 Option Overview + +L + + -TAG or --TAG Extract or exclude specified tag + -TAG[+-^]=[VALUE] Write new value for tag + -TAG[+-]<=DATFILE Write tag value from contents of file + -[+]TAG[+-] + + -args (-argFormat) Format metadata as exiftool arguments + -b (-binary) Output metadata in binary format + -c FMT (-coordFormat) Set format for GPS coordinates + -charset [[TYPE=]CHARSET] Specify encoding for special characters + -csv[[+]=CSVFILE] Export/import tags in CSV format + -csvDelim STR Set delimiter for CSV file + -d FMT (-dateFormat) Set format for date/time values + -D (-decimal) Show tag ID numbers in decimal + -E,-ex,-ec (-escape(HTML|XML|C))Escape tag values for HTML, XML or C + -f (-forcePrint) Force printing of all specified tags + -g[NUM...] (-groupHeadings) Organize output by tag group + -G[NUM...] (-groupNames) Print group name for each tag + -h (-htmlFormat) Use HTML formatting for output + -H (-hex) Show tag ID numbers in hexadecimal + -htmlDump[OFFSET] Generate HTML-format binary dump + -j[[+]=JSONFILE] (-json) Export/import tags in JSON format + -l (-long) Use long 2-line output format + -L (-latin) Use Windows Latin1 encoding + -lang [LANG] Set current language + -listItem INDEX Extract specific item from a list + -n (--printConv) No print conversion + -p FMTFILE (-printFormat) Print output in specified format + -php Export tags as a PHP Array + -s[NUM] (-short) Short output format + -S (-veryShort) Very short output format + -sep STR (-separator) Set separator string for list items + -sort Sort output alphabetically + -struct Enable output of structured information + -t (-tab) Output in tab-delimited list format + -T (-table) Output in tabular format + -v[NUM] (-verbose) Print verbose messages + -w[+|!] EXT (-textOut) Write (or overwrite!) output text files + -W[+|!] FMT (-tagOut) Write output text file for each tag + -Wext EXT (-tagOutExt) Write only specified file types with -W + -X (-xmlFormat) Use RDF/XML output format + +L + + -a (-duplicates) Allow duplicate tags to be extracted + -e (--composite) Do not generate composite tags + -ee[NUM] (-extractEmbedded) Extract information from embedded files + -ext[+] EXT (-extension) Process files with specified extension + -F[OFFSET] (-fixBase) Fix the base for maker notes offsets + -fast[NUM] Increase speed when extracting metadata + -fileOrder[NUM] [-]TAG Set file processing order + -i DIR (-ignore) Ignore specified directory name + -if[NUM] EXPR Conditionally process files + -m (-ignoreMinorErrors) Ignore minor errors and warnings + -o OUTFILE (-out) Set output file or directory name + -overwrite_original Overwrite original by renaming tmp file + -overwrite_original_in_place Overwrite original by copying tmp file + -P (-preserve) Preserve file modification date/time + -password PASSWD Password for processing protected files + -progress[NUM][:[TITLE]] Show file progress count + -q (-quiet) Quiet processing + -r[.] (-recurse) Recursively process subdirectories + -scanForXMP Brute force XMP scan + -u (-unknown) Extract unknown tags + -U (-unknown2) Extract unknown binary tags too + -wm MODE (-writeMode) Set mode for writing/creating tags + -z (-zip) Read/write compressed information + +L + + -@ ARGFILE Read command-line arguments from file + -k (-pause) Pause before terminating + -list[w|f|wf|g[NUM]|d|x] List various exiftool capabilities + -ver Print exiftool version number + -- End of options + +L + + -geotag TRKFILE Geotag images from specified GPS log + -globalTimeShift SHIFT Shift all formatted date/time values + -use MODULE Add features from plug-in module + +L + + -delete_original[!] Delete "_original" backups + -restore_original Restore from "_original" backups + +L + + -api OPT[[^]=[VAL]] Set ExifTool API option + -common_args Define common arguments + -config CFGFILE Specify configuration file name + -echo[NUM] TEXT Echo text to stdout or stderr + -efile[NUM][!] ERRFILE Save names of files with errors + -execute[NUM] Execute multiple commands on one line + -fileNUM ALTFILE Load tags from alternate file + -list_dir List directories, not their contents + -srcfile FMT Process a different source file + -stay_open FLAG Keep reading -@ argfile even after EOF + -userParam PARAM[[^]=[VAL]] Set user parameter (API UserParam opt) + +=head2 Option Details + +=head3 Tag operations + +=over 5 + +=item B<->I + +Extract information for the specified tag (eg. C<-CreateDate>). Multiple +tags may be specified in a single command. A tag name is the handle by +which a piece of information is referenced. See +L for documentation on +available tag names. A tag name may include leading group names separated +by colons (eg. C<-EXIF:CreateDate>, or C<-Doc1:XMP:Creator>), and each group +name may be prefixed by a digit to specify family number (eg. +C<-1IPTC:City>). (Note that the API SavePath and SaveFormat options must be +used for the family 5 and 6 groups respectively to be available.) Use the +B<-listg> option to list available group names by family. + +A special tag name of C may be used to indicate all meta information +(ie. B<-All>). This is particularly useful when a group name is specified +to extract all information in a group (but beware that unless the B<-a> +option is also used, some tags in the group may be suppressed by same-named +tags in other groups). The wildcard characters C and C<*> may be used in +a tag name to match any single character and zero or more characters +respectively. These may not be used in a group name, with the exception +that a group name of C<*> (or C) may be used to extract all instances +of a tag (as if B<-a> was used). Note that arguments containing wildcards +must be quoted on the command line of most systems to prevent shell +globbing. + +A C<#> may be appended to the tag name to disable the print conversion on a +per-tag basis (see the B<-n> option). This may also be used when writing or +copying tags. + +If no tags are specified, all available information is extracted (as if +C<-All> had been specified). + +Note: Descriptions, not tag names, are shown by default when extracting +information. Use the B<-s> option to see the tag names instead. + +=item B<-->I + +Exclude specified tag from extracted information. Same as the B<-x> option. +Group names and wildcards are permitted as described above for B<-TAG>. +Once excluded from the output, a tag may not be re-included by a subsequent +option. May also be used following a B<-tagsFromFile> option to exclude +tags from being copied (when redirecting to another tag, it is the source +tag that should be excluded), or to exclude groups from being deleted when +deleting all information (eg. C<-all= --exif:all> deletes all but EXIF +information). But note that this will not exclude individual tags from a +group delete (unless a family 2 group is specified, see note 4 below). +Instead, individual tags may be recovered using the B<-tagsFromFile> option +(eg. C<-all= -tagsfromfile @ -artist>). + +To speed processing when reading XMP, exclusions in XMP groups also bypass +processing of the corresponding XMP property and any contained properties. +For example, C<--xmp-crs:all> may speed processing significantly in cases +where a large number of XMP-crs tags exist. To use this feature to bypass +processing of a specific XMP property, the property name must be used +instead of the ExifTool tag name (eg. C<--xmp-crs:dabs>). Also, C +may be used to to indicate any XMP namespace (eg. C<--xmp-all:dabs>). + +=item B<->I[+-^]B<=>[I] + +Write a new value for the specified tag (eg. C<-comment=wow>), or delete the +tag if no I is given (eg. C<-comment=>). C<+=> and C<-=> are used to +add or remove existing entries from a list, or to shift date/time values +(see L and note 6 below +for more details). C<+=> may also be used to increment numerical values (or +decrement if I is negative), and C<-=> may be used to conditionally +delete or replace a tag (see L for examples). C<^=> is +used to write an empty string instead of deleting the tag when no I +is given, but otherwise it is equivalent to C<=>. (Note that the caret must +be quoted on the Windows command line.) + +I may contain one or more leading family 0, 1, 2 or 7 group names, +prefixed by optional family numbers, and separated colons. If no group name +is specified, the tag is created in the preferred group, and updated in any +other location where a same-named tag already exists. The preferred group +in JPEG and TIFF-format images is the first group in the following list +where I is valid: 1) EXIF, 2) IPTC, 3) XMP. + +The wildcards C<*> and C may be used in tag names to assign the same +value to multiple tags. When specified with wildcards, "Unsafe" tags are +not written. A tag name of C is equivalent to C<*> (except that it +doesn't require quoting, while arguments with wildcards do on systems with +shell globbing), and is often used when deleting all metadata (ie. C<-All=>) +or an entire group (eg. C<-XMP-dc:All=>, see note 4 below). Note that not +all groups are deletable, and that the JPEG APP14 "Adobe" group is not +removed by default with C<-All=> because it may affect the appearance of the +image. However, color space information is removed, so the colors may be +affected (but this may be avoided by copying back the tags defined by the +ColorSpaceTags shortcut). Use the B<-listd> option for a complete list of +deletable groups, and see note 5 below regarding the "APP" groups. Also, +within an image some groups may be contained within others, and these groups +are removed if the containing group is deleted: + + JPEG Image: + - Deleting EXIF or IFD0 also deletes ExifIFD, GlobParamIFD, + GPS, IFD1, InteropIFD, MakerNotes, PrintIM and SubIFD. + - Deleting ExifIFD also deletes InteropIFD and MakerNotes. + - Deleting Photoshop also deletes IPTC. + + TIFF Image: + - Deleting EXIF only removes ExifIFD which also deletes + InteropIFD and MakerNotes. + + MOV/MP4 Video: + - Deleting ItemList also deletes Keys tags. + +Notes: + +1) B. If two +assignments affect the same tag, the latter takes precedence (except for +list-type tags, for which both values are written). + +2) In general, MakerNotes tags are considered "Permanent", and may be edited +but not created or deleted individually. This avoids many potential +problems, including the inevitable compatibility problems with OEM software +which may be very inflexible about the information it expects to find in the +maker notes. + +3) Changes to PDF files by ExifTool are reversible (by deleting the update +with C<-PDF-update:all=>) because the original information is never actually +deleted from the file. So ExifTool alone may not be used to securely edit +metadata in PDF files. + +4) Specifying C<-GROUP:all=> deletes the entire group as a block only if a +single family 0 or 1 group is specified. Otherwise all deletable tags in +the specified group(s) are removed individually, and in this case is it +possible to exclude individual tags from a mass delete. For example, +C<-time:all --Exif:Time:All> removes all deletable Time tags except those in +the EXIF. This difference also applies if family 2 is specified when +deleting all groups. For example, C<-2all:all=> deletes tags individually, +while C<-all:all=> deletes entire blocks. + +5) The "APP" group names ("APP0" through "APP15") are used to delete JPEG +application segments which are not associated with another deletable group. +For example, specifying C<-APP14:All=> will NOT delete the APP14 "Adobe" +segment because this is accomplished with C<-Adobe:All>. But note that +these unnamed APP segments may not be excluded with C<--APPxx:all>) when +deleting all information. + +6) When shifting a value, the shift is applied to the original value of the +tag, overriding any other values previously assigned to the tag on the same +command line. To shift a date/time value and copy it to another tag in the +same operation, use the B<-globalTimeShift> option. + +Special feature: Integer values may be specified in hexadecimal with a +leading C<0x>, and simple rational values may be specified as fractions. + +=item B<->IE=I or B<->IE=I + +Set the value of a tag from the contents of file I. The file name +may also be given by a I string where %d, %f and %e represent the +directory, file name and extension of the original I (see the B<-w> +option for more details). Note that quotes are required around this +argument to prevent shell redirection since it contains a C> symbol. +If I/I is not provided, the effect is the same as C<-TAG=>, +and the tag is simply deleted. C<+E=> or C<-E=> may also be used to +add or delete specific list entries, or to shift date/time values. + +=item B<-tagsFromFile> I or I + +Copy tag values from I to I. Tag names on the command line +after this option specify the tags to be copied, or excluded from the copy. +Wildcards are permitted in these tag names. If no tags are specified, then +all possible tags (see note 1 below) from the source file are copied to +same-named tags in the preferred location of the output file (the same as +specifying C<-all>). More than one B<-tagsFromFile> option may be used to +copy tags from multiple files. + +By default, this option will update any existing and writable same-named +tags in the output I, but will create new tags only in their preferred +groups. This allows some information to be automatically transferred to the +appropriate group when copying between images of different formats. However, +if a group name is specified for a tag then the information is written only +to this group (unless redirected to another group, see below). If C is +used as a group name, then the specified tag(s) are written to the same +family 1 group they had in the source file (ie. the same specific location, +like ExifIFD or XMP-dc). For example, the common operation of copying all +writable tags to the same specific locations in the output I is +achieved by adding C<-all:all>. A different family may be specified by +adding a leading family number to the group name (eg. C<-0all:all> preserves +the same general location, like EXIF or XMP). + +I may be the same as I to move information around within a +single file. In this case, C<@> may be used to represent the source file +(ie. C<-tagsFromFile @>), permitting this feature to be used for batch +processing multiple files. Specified tags are then copied from each file in +turn as it is rewritten. For advanced batch use, the source file name may +also be specified using a I string in which %d, %f and %e represent the +directory, file name and extension of I. (eg. the current I +would be represented by C<%d%f.%e>, with the same effect as C<@>). See the +B<-w> option for I string examples. + +A powerful redirection feature allows a destination tag to be specified for +each copied tag. With this feature, information may be written to a tag +with a different name or group. This is done using +E'-IEI'E or +E'-IEI'E on the command line after +B<-tagsFromFile>, and causes the value of I to be copied from +I and written to I in I. Has no effect unless +I exists in I. Note that this argument must be quoted to +prevent shell redirection, and there is no C<=> sign as when assigning new +values. Source and/or destination tags may be prefixed by a group name +and/or suffixed by C<#>. Wildcards are allowed in both the source and +destination tag names. A destination group and/or tag name of C or +C<*> writes to the same family 1 group and/or tag name as the source (but +the family may be specified by adding a leading number to the group name, +eg. C<0All> writes to the same family 0 group as the source). If no +destination group is specified, the information is written to the preferred +group. Whitespace around the C> or C> is ignored. As a +convenience, C<-tagsFromFile @> is assumed for any redirected tags which are +specified without a prior B<-tagsFromFile> option. Copied tags may also be +added or deleted from a list with arguments of the form +E'-I+EI'E or +E'-I-EI'E (but see Note 5 below). + +An extension of the redirection feature allows strings involving tag names +to be used on the right hand side of the C> symbol with the syntax +E'-IEI'E, where tag names in I are +prefixed with a C<$> symbol. See the B<-p> option and the +L section for more details about this syntax. +Strings starting with a C<=> sign must insert a single space after the +C> to avoid confusion with the C=> operator which sets the tag +value from the contents of a file. A single space at the start of the +string is removed if it exists, but all other whitespace in the string is +preserved. See note 8 below about using the redirection feature with +list-type stags, shortcuts or when using wildcards in tag names. + +See L for examples using B<-tagsFromFile>. + +Notes: + +1) Some tags (generally tags which may affect the appearance of the image) +are considered "Unsafe" to write, and are only copied if specified +explicitly (ie. no wildcards). See the +L for more details about +"Unsafe" tags. + +2) Be aware of the difference between excluding a tag from being copied +(--I), and deleting a tag (-I=). Excluding a tag prevents it from +being copied to the destination image, but deleting will remove a +pre-existing tag from the image. + +3) The maker note information is copied as a block, so it isn't affected +like other information by subsequent tag assignments on the command line, +and individual makernote tags may not be excluded from a block copy. Also, +since the PreviewImage referenced from the maker notes may be rather large, +it is not copied, and must be transferred separately if desired. + +4) The order of operations is to copy all specified tags at the point of the +B<-tagsFromFile> option in the command line. Any tag assignment to the +right of the B<-tagsFromFile> option is made after all tags are copied. For +example, new tag values are set in the order One, Two, Three then Four with +this command: + + exiftool -One=1 -tagsFromFile s.jpg -Two -Four=4 -Three d.jpg + +This is significant in the case where an overlap exists between the copied +and assigned tags because later operations may override earlier ones. + +5) The normal behaviour of copied tags differs from that of assigned tags +for list-type tags and conditional replacements because each copy operation +on a tag overrides any previous operations. While this avoids duplicate +list items when copying groups of tags from a file containing redundant +information, it also prevents values of different tags from being copied +into the same list when this is the intent. To accumulate values +from different operations into the same list, add a C<+> after the initial +C<-> of the argument. For example: + + exiftool -tagsfromfile @ '-subject must be used when conditionally replacing a tag to +prevent overriding earlier conditions. + +6) The B<-a> option (allow duplicate tags) is always in effect when copying +tags from I, but the highest priority tag is always copied last so +it takes precedence. + +7) Structured tags are copied by default when copying tags. See the +B<-struct> option for details. + +8) With the redirection feature, copying a tag directly (ie. +E'-IEI'E) is not the same as interpolating +its value inside a string (ie. E'-IE$I'E) +for source tags which are list-type tags, +L, tag names containing wildcards, +or UserParam variables. When copying directly, the values of each matching +source tag are copied individually to the destination tag (as if they were +separate assignments). However, when interpolated inside a string, list +items and the values of shortcut tags are concatenated (with a separator set +by the B<-sep> option), and wildcards are not allowed. Also, UserParam +variables are available only when interpolated in a string. Another +difference is that a minor warning is generated if a tag doesn't exist when +interpolating its value in a string (with C<$>), but isn't when copying the +tag directly. + +Finally, the behaviour is different when a destination tag or group of +C is used. When copying directly, a destination group and/or tag name +of C writes to the same family 1 group and/or tag name as the source. +But when interpolated in a string, the identity of the source tags are lost +and the value is written to all possible groups/tags. For example, the +string form must be used in the following command since the intent is to set +the value of all existing date/time tags from C: + + exiftool '-time:all<$createdate' -wm w FILE + +=item B<-x> I (B<-exclude>) + +Exclude the specified tag. There may be multiple B<-x> options. This has +the same effect as --I on the command line. See the --I +documentation above for a complete description. + +=back + +=head3 Input-output text formatting + +Note that trailing spaces are removed from extracted values for most output +text formats. The exceptions are B<-b>, B<-csv>, B<-j> and B<-X>. + +=over 5 + +=item B<-args> (B<-argFormat>) + +Output information in the form of exiftool arguments, suitable for use with +the B<-@> option when writing. May be combined with the B<-G> option to +include group names. This feature may be used to effectively copy tags +between images, but allows the metadata to be altered by editing the +intermediate file (C in this example): + + exiftool -args -G1 --filename --directory src.jpg > out.args + exiftool -@ out.args -sep ', ' dst.jpg + +Note: Be careful when copying information with this technique since it is +easy to write tags which are normally considered "Unsafe". For instance, +the FileName and Directory tags are excluded in the example above to avoid +renaming and moving the destination file. Also note that the second command +above will produce warning messages for any tags which are not writable. + +As well, the B<-sep> option should be used as in the second command above to +maintain separate list items when writing metadata back to image files, and +the B<-struct> option may be used when extracting to preserve structured XMP +information. + +=item B<-b>, B<--b> (B<-binary>, B<--binary>) + +Output requested metadata in binary format without tag names or descriptions +(B<-b> or B<-binary>). This option is mainly used for extracting embedded +images or other binary data, but it may also be useful for some text strings +since control characters (such as newlines) are not replaced by '.' as they +are in the default output. By default, list items are separated by a +newline when extracted with the B<-b> option, but this may be changed (see +the B<-sep> option for details). May be combined with B<-j>, B<-php> or +B<-X> to extract binary data in JSON, PHP or XML format, but note that +"Unsafe" tags are not extracted as binary unless they are specified +explicitly or the API RequestAll option is set to 3 or higher. + +With a leading double dash (B<--b> or B<--binary>), tags which contain +binary data are suppressed in the output when reading. + +=item B<-c> I (B<-coordFormat>) + +Set the print format for GPS coordinates. I uses the same syntax as +a C format string. The specifiers correspond to degrees, minutes +and seconds in that order, but minutes and seconds are optional. For +example, the following table gives the output for the same coordinate using +various formats: + + FMT Output + ------------------- ------------------ + "%d deg %d' %.2f"\" 54 deg 59' 22.80" (default for reading) + "%d %d %.8f" 54 59 22.80000000 (default for copying) + "%d deg %.4f min" 54 deg 59.3800 min + "%.6f degrees" 54.989667 degrees + +Notes: + +1) To avoid loss of precision, the default coordinate format is different +when copying tags using the B<-tagsFromFile> option. + +2) If the hemisphere is known, a reference direction (N, S, E or W) is +appended to each printed coordinate, but adding a C<+> to the format +specifier (eg. C<%+.6f>) prints a signed coordinate instead. + +3) This print formatting may be disabled with the B<-n> option to extract +coordinates as signed decimal degrees. + +=item B<-charset> [[I=]I] + +If I is C or not specified, this option sets the ExifTool +character encoding for output tag values when reading and input values when +writing, with a default of C. If no I is given, a list of +available character sets is returned. Valid I values are: + + CHARSET Alias(es) Description + ---------- --------------- ---------------------------------- + UTF8 cp65001, UTF-8 UTF-8 characters (default) + Latin cp1252, Latin1 Windows Latin1 (West European) + Latin2 cp1250 Windows Latin2 (Central European) + Cyrillic cp1251, Russian Windows Cyrillic + Greek cp1253 Windows Greek + Turkish cp1254 Windows Turkish + Hebrew cp1255 Windows Hebrew + Arabic cp1256 Windows Arabic + Baltic cp1257 Windows Baltic + Vietnam cp1258 Windows Vietnamese + Thai cp874 Windows Thai + DOSLatinUS cp437 DOS Latin US + DOSLatin1 cp850 DOS Latin1 + DOSCyrillic cp866 DOS Cyrillic + MacRoman cp10000, Roman Macintosh Roman + MacLatin2 cp10029 Macintosh Latin2 (Central Europe) + MacCyrillic cp10007 Macintosh Cyrillic + MacGreek cp10006 Macintosh Greek + MacTurkish cp10081 Macintosh Turkish + MacRomanian cp10010 Macintosh Romanian + MacIceland cp10079 Macintosh Icelandic + MacCroatian cp10082 Macintosh Croatian + +I may be C to specify the encoding of file names on the +command line (ie. I arguments). In Windows, this triggers use of +wide-character i/o routines, thus providing support for Unicode file names. +See the L section below for details. + +Other values of I listed below are used to specify the internal +encoding of various meta information formats. + + TYPE Description Default + --------- ------------------------------------------- ------- + EXIF Internal encoding of EXIF "ASCII" strings (none) + ID3 Internal encoding of ID3v1 information Latin + IPTC Internal IPTC encoding to assume when Latin + IPTC:CodedCharacterSet is not defined + Photoshop Internal encoding of Photoshop IRB strings Latin + QuickTime Internal encoding of QuickTime strings MacRoman + RIFF Internal encoding of RIFF strings 0 + +See L for more information about coded +character sets, and the L +for more details about the B<-charset> settings. + +=item B<-csv>[[+]=I] + +Export information in CSV format, or import information if I is +specified. When importing, the CSV file must be in exactly the same format +as the exported file. The first row of the I must be the ExifTool +tag names (with optional group names) for each column of the file, and +values must be separated by commas. A special "SourceFile" column specifies +the files associated with each row of information (and a SourceFile of "*" +may be used to define default tags to be imported for all files which are +combined with any tags specified for the specific SourceFile processed). The +B<-csvDelim> option may be used to change the input/output field delimiter +if something other than a comma is required. + +The following examples demonstrate basic use of the B<-csv> option: + + # generate CSV file with common tags from all images in a directory + exiftool -common -csv dir > out.csv + + # update metadata for all images in a directory from CSV file + exiftool -csv=a.csv dir + +When importing, empty values are ignored unless the B<-f> option is used and +the API MissingTagValue is set to an empty string (in which case the tag is +deleted). Also, FileName and Directory columns are ignored if they exist +(ie. ExifTool will not attempt to write these tags with a CSV import), but +all other columns are imported. To force a tag to be deleted, use the B<-f> +option and set the value to "-" in the CSV file (or to the MissingTagValue +if this API option was used). Multiple databases may be imported in a +single command. + +When exporting a CSV file, the B<-g> or B<-G> option adds group names to the +tag headings. If the B<-a> option is used to allow duplicate tag names, the +duplicate tags are only included in the CSV output if the column headings +are unique. Adding the B<-G4> option ensures a unique column heading for +each tag. The B<-b> option may be added to output binary data, encoded in +base64 if necessary (indicated by ASCII "base64:" as the first 7 bytes of +the value). Values may also be encoded in base64 if the B<-charset> option +is used and the value contains invalid characters. + +When exporting specific tags, the CSV columns are arranged in the same order +as the specified tags provided the column headings exactly match the +specified tag names, otherwise the columns are sorted in alphabetical order. + +When importing from a CSV file, only files specified on the command line are +processed. Any extra entries in the CSV file are ignored. + +List-type tags are stored as simple strings in a CSV file, but the B<-sep> +option may be used to split them back into separate items when importing. + +Special feature: B<-csv>+=I may be used to add items to existing +lists. This affects only list-type tags. Also applies to the B<-j> option. + +Note that this option is fundamentally different than all other output +format options because it requires information from all input files to be +buffered in memory before the output is written. This may result in +excessive memory usage when processing a very large number of files with a +single command. Also, it makes this option incompatible with the B<-w> and +B<-W> options. When processing a large number of files, it is recommended +to either use the JSON (B<-j>) or XML (B<-X>) output format, or use B<-p> to +generate a fixed-column CSV file instead of using the B<-csv> option. + +=item B<-csvDelim> I + +Set the delimiter for separating CSV entries for CSV file input/output via +the B<-csv> option. I may contain "\t", "\n", "\r" and "\\" to +represent TAB, LF, CR and '\' respectively. A double quote is not allowed +in the delimiter. Default is ','. + +=item B<-d> I (B<-dateFormat>) + +Set the format for date/time tag values. The I string may contain +formatting codes beginning with a percent character (C<%>) to represent the +various components of a date/time value. The specifics of the I syntax +are system dependent -- consult the C man page on your system for +details. The default format is equivalent to "%Y:%m:%d %H:%M:%S". This +option has no effect on date-only or time-only tags and ignores timezone +information if present. ExifTool adds a C<%f> format code to represent +fractional seconds, and supports an optional width to specify the number of +digits after the decimal point (eg. C<%3f> would give something like +C<.437>), and a minus sign to drop the decimal point (eg. C<%-3f> would give +C<437>). Only one B<-d> option may be used per command. Requires +POSIX::strptime or Time::Piece for the inversion conversion when writing. + +=item B<-D> (B<-decimal>) + +Show tag ID number in decimal when extracting information. + +=item B<-E>, B<-ex>, B<-ec> (B<-escapeHTML>, B<-escapeXML>, B<-escapeC>) + +Escape characters in output tag values for HTML (B<-E>), XML (B<-ex>) or C +(B<-ec>). For HTML, all characters with Unicode code points above U+007F +are escaped as well as the following 5 characters: & (&) E<39> (') +E (") E (>) and E (<). For XML, only these 5 +characters are escaped. The B<-E> option is implied with B<-h>, and B<-ex> +is implied with B<-X>. For C, all control characters and the backslash are +escaped. The inverse conversion is applied when writing tags. + +=item B<-f> (B<-forcePrint>) + +Force printing of tags even if they don't exist. This option applies to +tags specified on the command line, or with the B<-p>, B<-if> or +B<-tagsFromFile> options. When B<-f> is used, the value of any missing tag +is set to a dash (C<->) by default, but this may be configured via the API +MissingTagValue option. B<-f> is also used to add a 'flags' attribute to +the B<-listx> output, or to allow tags to be deleted when writing with the +B<-csv>=I feature. + +=item B<-g>[I][:I...] (B<-groupHeadings>) + +Organize output by tag group. I specifies a group family number, and +may be 0 (general location), 1 (specific location), 2 (category), 3 +(document number), 4 (instance number), 5 (metadata path), 6 (EXIF/TIFF +format), 7 (tag ID) or 8 (file number). B<-g0> is assumed if a family +number is not specified. May be combined with other options to add group +names to the output. Multiple families may be specified by separating them +with colons. By default the resulting group name is simplified by removing +any leading C and collapsing adjacent identical group names, but this +can be avoided by placing a colon before the first family number (eg. +B<-g:3:1>). Use the B<-listg> option to list group names for a specified +family. The API SavePath and SaveFormat options are automatically enabled +if the respective family 5 or 6 group names are requested. See the +L for more information. + +=item B<-G>[I][:I...] (B<-groupNames>) + +Same as B<-g> but print group name for each tag. B<-G0> is assumed if +I is not specified. May be combined with a number of other options to +add group names to the output. Note that I may be added wherever B<-G> +is mentioned in the documentation. See the B<-g> option above for details. + +=item B<-h> (B<-htmlFormat>) + +Use HTML table formatting for output. Implies the B<-E> option. The +formatting options B<-D>, B<-H>, B<-g>, B<-G>, B<-l> and B<-s> may be used +in combination with B<-h> to influence the HTML format. + +=item B<-H> (B<-hex>) + +Show tag ID number in hexadecimal when extracting information. + +=item B<-htmlDump>[I] + +Generate a dynamic web page containing a hex dump of the EXIF information. +This can be a very powerful tool for low-level analysis of EXIF information. +The B<-htmlDump> option is also invoked if the B<-v> and B<-h> options are +used together. The verbose level controls the maximum length of the blocks +dumped. An I may be given to specify the base for displayed +offsets. If not provided, the EXIF/TIFF base offset is used. Use +B<-htmlDump0> for absolute offsets. Currently only EXIF/TIFF and JPEG +information is dumped, but the -u option can be used to give a raw hex dump +of other file formats. + +=item B<-j>[[+]=I] (B<-json>) + +Use JSON (JavaScript Object Notation) formatting for console output, or +import JSON file if I is specified. This option may be combined +with B<-g> to organize the output into objects by group, or B<-G> to add +group names to each tag. List-type tags with multiple items are output as +JSON arrays unless B<-sep> is used. By default XMP structures are flattened +into individual tags in the JSON output, but the original structure may be +preserved with the B<-struct> option (this also causes all list-type XMP +tags to be output as JSON arrays, otherwise single-item lists would be +output as simple strings). The B<-a> option is implied when B<-json> is +used, but entries with identical JSON names are suppressed in the output. +(B<-G4> may be used to ensure that all tags have unique JSON names.) Adding +the B<-D> or B<-H> option changes tag values to JSON objects with "val" and +"id" fields, and adding B<-l> adds a "desc" field, and a "num" field if the +numerical value is different from the converted "val". The B<-b> option may +be added to output binary data, encoded in base64 if necessary (indicated by +ASCII "base64:" as the first 7 bytes of the value), and B<-t> may be added +to include tag table information (see B<-t> for details). The JSON output +is UTF-8 regardless of any B<-L> or B<-charset> option setting, but the +UTF-8 validation is disabled if a character set other than UTF-8 is +specified. Note that ExifTool quotes JSON values only if they don't look +like numbers (regardless of the original storage format or the relevant +metadata specification). + +If I is specified, the file is imported and the tag definitions +from the file are used to set tag values on a per-file basis. The special +"SourceFile" entry in each JSON object associates the information with a +specific target file. An object with a missing SourceFile or a SourceFile +of "*" defines default tags for all target files which are combined with any +tags specified for the specific SourceFile processed. The imported JSON +file must have the same format as the exported JSON files with the exception +that options exporting JSON objects instead of simple values are not +compatible with the import file format (ie. export with B<-D>, B<-H>, B<-l>, +or B<-T> is not compatable, and use B<-G> instead of B<-g>). Additionally, +tag names in the input JSON file may be suffixed with a C<#> to disable +print conversion. + +Unlike CSV import, empty values are not ignored, and will cause an empty +value to be written if supported by the specific metadata type. Tags are +deleted by using the B<-f> option and setting the tag value to "-" (or to +the MissingTagValue setting if this API option was used). Importing with +B<-j>+=I causes new values to be added to existing lists. + +=item B<-l> (B<-long>) + +Use long 2-line Canon-style output format. Adds a description and +unconverted value (if it is different from the converted value) to the XML, +JSON or PHP output when B<-X>, B<-j> or B<-php> is used. May also be +combined with B<-listf>, B<-listr> or B<-listwf> to add descriptions of the +file types. + +=item B<-L> (B<-latin>) + +Use Windows Latin1 encoding (cp1252) for output tag values instead of the +default UTF-8. When writing, B<-L> specifies that input text values are +Latin1 instead of UTF-8. Equivalent to C<-charset latin>. + +=item B<-lang> [I] + +Set current language for tag descriptions and converted values. I is +C, C, C, etc. Use B<-lang> with no other arguments to get a +list of available languages. The default language is C if B<-lang> is +not specified. Note that tag/group names are always English, independent of +the B<-lang> setting, and translation of warning/error messages has not yet +been implemented. May also be combined with B<-listx> to output +descriptions in one language only. + +By default, ExifTool uses UTF-8 encoding for special characters, but the +B<-L> or B<-charset> option may be used to invoke other encodings. Note +that ExifTool uses Unicode::LineBreak if available to help preserve the +column alignment of the plain text output for languages with a +variable-width character set. + +Currently, the language support is not complete, but users are welcome to +help improve this by submitting their own translations. To submit a +translation, follow these steps (you must have Perl installed for this): + +1. Download and unpack the latest Image-ExifTool full distribution. + +2. 'cd' into the Image-ExifTool directory. + +3. Run this command to make an XML file of the desired tags (eg. EXIF): + + ./exiftool -listx -exif:all > out.xml + +4. Copy this text into a file called 'import.pl' in the exiftool directory: + + push @INC, 'lib'; + require Image::ExifTool::TagInfoXML; + my $file = shift or die "Expected XML file name\n"; + $Image::ExifTool::TagInfoXML::makeMissing = shift; + Image::ExifTool::TagInfoXML::BuildLangModules($file,8); + +5. Run the 'import.pl' script to Import the XML file, generating the +'MISSING' entries for your language (eg. Russian): + + perl import.pl out.xml ru + +6. Edit the generated language module lib/Image/ExifTool/Lang/ru.pm, and +search and replace all 'MISSING' strings in the file with your translations. + +7. Email the module ('ru.pm' in this example) to philharvey66 at gmail.com + +8. Thank you!! + +=item B<-listItem> I + +For list-type tags, this causes only the item with the specified index to be +extracted. I is 0 for the first item in the list. Negative indices +may also be used to reference items from the end of the list. Has no effect +on single-valued tags. Also applies to tag values when copying from a tag, +and in B<-if> conditions. + +=item B<-n> (B<--printConv>) + +Disable print conversion for all tags. By default, extracted values are +converted to a more human-readable format, but the B<-n> option disables +this conversion, revealing the machine-readable values. For example: + + > exiftool -Orientation -S a.jpg + Orientation: Rotate 90 CW + > exiftool -Orientation -S -n a.jpg + Orientation: 6 + +The print conversion may also be disabled on a per-tag basis by suffixing +the tag name with a C<#> character: + + > exiftool -Orientation# -Orientation -S a.jpg + Orientation: 6 + Orientation: Rotate 90 CW + +These techniques may also be used to disable the inverse print conversion +when writing. For example, the following commands all have the same effect: + + > exiftool -Orientation='Rotate 90 CW' a.jpg + > exiftool -Orientation=6 -n a.jpg + > exiftool -Orientation#=6 a.jpg + +=item B<-p> I or I (B<-printFormat>) + +Print output in the format specified by the given file or string. The +argument is interpreted as a string unless a file of that name exists, in +which case the string is loaded from the contents of the file. Tag names in +the format file or string begin with a C<$> symbol and may contain leading +group names and/or a trailing C<#> (to disable print conversion). Case is +not significant. Braces C<{}> may be used around the tag name to separate +it from subsequent text (and must be used if subsequent text begins with an +alphanumeric character, hyphen, underline, colon or number sign). Use C<$$> +to represent a C<$> symbol, and C<$/> for a newline. + +Multiple B<-p> options may be used, each contributing a line (or more) of +text to the output. Lines beginning with C<#[HEAD]> and C<#[TAIL]> are +output before the first processed file and after the last processed file +respectively. Lines beginning with C<#[SECT]> and C<#[ENDS]> are output +before and after each section of files. A section is defined as a group of +consecutive files with the same section header (eg. files are grouped by +directory if C<#[SECT]> contains C<$directory>). Lines beginning with +C<#[BODY]> and lines not beginning with C<#> are output for each processed +file. Lines beginning with C<#[IF]> are not output, but all BODY lines are +skipped if any tag on an IF line doesn't exist. Other lines beginning with +C<#> are ignored. (To output a line beginning with C<#>, use C<#[BODY]#>.) +For example, this format file: + + # this is a comment line + #[HEAD]-- Generated by ExifTool $exifToolVersion -- + File: $FileName - $DateTimeOriginal + (f/$Aperture, ${ShutterSpeed}s, ISO $EXIF:ISO) + #[TAIL]-- end -- + +with this command: + + exiftool -p test.fmt a.jpg b.jpg + +produces output like this: + + -- Generated by ExifTool 12.67 -- + File: a.jpg - 2003:10:31 15:44:19 + (f/5.6, 1/60s, ISO 100) + File: b.jpg - 2006:05:23 11:57:38 + (f/8.0, 1/13s, ISO 100) + -- end -- + +The values of List-type tags with multiple items and Shortcut tags +representing multiple tags are joined according the B<-sep> option setting +when interpolated in the string. + +When B<-ee> (B<-extractEmbedded>) is combined with B<-p>, embedded documents +are effectively processed as separate input files. + +If a specified tag does not exist, a minor warning is issued and the line +with the missing tag is not printed. However, the B<-f> option may be used +to set the value of missing tags to '-' (but this may be configured via the +API MissingTagValue option), or the B<-m> option may be used to ignore minor +warnings and leave the missing values empty. Alternatively, B<-q -q> may be +used to simply suppress the warning messages. + +The L may be used to modify the values of +individual tags within the B<-p> option string. + +Note that the API RequestTags option is automatically set for all tags used +in the I or I. This allows all other tags to be ignored using +B<-API IgnoreTags=all>, resulting in reduced memory usage and increased +speed. + +=item B<-php> + +Format output as a PHP Array. The B<-g>, B<-G>, B<-D>, B<-H>, B<-l>, +B<-sep> and B<-struct> options combine with B<-php>, and duplicate tags are +handled in the same way as with the B<-json> option. As well, the B<-b> +option may be added to output binary data, and B<-t> may be added to include +tag table information (see B<-t> for details). Here is a simple example +showing how this could be used in a PHP script: + + + +=item B<-s>[I] (B<-short>) + +Short output format. Prints tag names instead of descriptions. Add I +or up to 3 B<-s> options for even shorter formats: + + -s1 or -s - print tag names instead of descriptions + -s2 or -s -s - no extra spaces to column-align values + -s3 or -s -s -s - print values only (no tag names) + +Also effective when combined with B<-t>, B<-h>, B<-X> or B<-listx> options. + +=item B<-S> (B<-veryShort>) + +Very short format. The same as B<-s2> or two B<-s> options. Tag names are +printed instead of descriptions, and no extra spaces are added to +column-align values. + +=item B<-sep> I (B<-separator>) + +Specify separator string for items in list-type tags. When reading, the +default is to join list items with ", ". When writing, this option causes +values assigned to list-type tags to be split into individual items at each +substring matching I (otherwise they are not split by default). Space +characters in I match zero or more whitespace characters in the value. + +Note that an empty separator ("") is allowed, and will join items with no +separator when reading, or split the value into individual characters when +writing. + +For pure binary output (B<-b> used without B<-j>, B<-php> or B<-X>), the +first B<-sep> option specifies a list-item separator, and a second B<-sep> +option specifies a terminator for the end of the list (or after each value +if not a list). In these strings, C<\n>, C<\r> and C<\t> may be used to +represent a newline, carriage return and tab respectively. By default, +binary list items are separated by a newline, and no terminator is added. + +=item B<-sort>, B<--sort> + +Sort output by tag description, or by tag name if the B<-s> option is used. +When sorting by description, the sort order will depend on the B<-lang> +option setting. Without the B<-sort> option, tags appear in the order they +were specified on the command line, or if not specified, the order they were +extracted from the file. By default, tags are organized by groups when +combined with the B<-g> or B<-G> option, but this grouping may be disabled +with B<--sort>. + +=item B<-struct>, B<--struct> + +Output structured XMP information instead of flattening to individual tags. +This option works well when combined with the XML (B<-X>) and JSON (B<-j>) +output formats. For other output formats, XMP structures and lists are +serialized into the same format as when writing structured information (see +L for details). When copying, structured +tags are copied by default unless B<--struct> is used to disable this +feature (although flattened tags may still be copied by specifying them +individually unless B<-struct> is used). These options have no effect when +assigning new values since both flattened and structured tags may always be +used when writing. + +=item B<-t> (B<-tab>) + +Output a tab-delimited list of description/values (useful for database +import). May be combined with B<-s> to print tag names instead of +descriptions, or B<-S> to print tag values only, tab-delimited on a single +line. The B<-t> option may be combined with B<-j>, B<-php> or B<-X> to add +tag table information (C, tag C, and C for cases where +multiple conditional tags exist with the same ID). + +=item B<-T> (B<-table>) + +Output tag values in table form. Equivalent to B<-t -S -q -f>. + +=item B<-v>[I] (B<-verbose>) + +Print verbose messages. I specifies the level of verbosity in the +range 0-5, with higher numbers being more verbose. If I is not given, +then each B<-v> option increases the level of verbosity by 1. With any +level greater than 0, most other options are ignored and normal console +output is suppressed unless specific tags are extracted. Using B<-v0> +causes the console output buffer to be flushed after each line (which may be +useful to avoid delays when piping exiftool output), and prints the name of +each processed file when writing and the new file name when renaming, +moving or copying. Verbose levels above B<-v0> do not flush after each +line. Also see the B<-progress> option. + +=item B<-w>[+|!] I or I (B<-textOut>) + +Write console output to files with names ending in I, one for each +source file. The output file name is obtained by replacing the source file +extension (including the '.') with the specified extension (and a '.' is +added to the start of I if it doesn't already contain one). +Alternatively, a I string may be used to give more control over the +output file name and directory. In the format string, %d, %f and %e +represent the directory, filename and extension of the source file, and %c +represents a copy number which is automatically incremented if the file +already exists. %d includes the trailing '/' if necessary, but %e does not +include the leading '.'. For example: + + -w %d%f.txt # same effect as "-w txt" + -w dir/%f_%e.out # write files to "dir" as "FILE_EXT.out" + -w dir2/%d%f.txt # write to "dir2", keeping dir structure + -w a%c.txt # write to "a.txt" or "a1.txt" or "a2.txt"... + +Existing files will not be changed unless an exclamation point is added to +the option name (ie. B<-w!> or B<-textOut!>) to overwrite the file, or a +plus sign (ie. B<-w+> or B<-textOut+>) to append to the existing file. Both +may be used (ie. B<-w+!> or B<-textOut+!>) to overwrite output files that +didn't exist before the command was run, and append the output from multiple +source files. For example, to write one output file for all source files in +each directory: + + exiftool -filename -createdate -T -w+! %d/out.txt -r DIR + +Capitalized format codes %D, %F, %E and %C provide slightly different +alternatives to the lower case versions. %D does not include the trailing +'/', %F is the full filename including extension, %E includes the leading +'.', and %C increments the count for each processed file (see below). + +Notes: + +1) In a Windows BAT file the C<%> character is represented by C<%%>, so an +argument like C<%d%f.txt> is written as C<%%d%%f.txt>. + +2) If the argument for B<-w> does not contain a valid format code (eg. %f), +then it is interpreted as a file extension, but there are three different +ways to create a single output file from multiple source files: + + # 1. Shell redirection + exiftool FILE1 FILE2 ... > out.txt + + # 2. With the -w option and a zero-width format code + exiftool -w+! %0fout.txt FILE1 FILE2 ... + + # 3. With the -W option (see the -W option below) + exiftool -W+! out.txt FILE1 FILE2 ... + +Advanced features: + +A substring of the original file name, directory or extension may be taken +by specifying a field width immediately following the '%' character. If the +width is negative, the substring is taken from the end. The substring +position (characters to ignore at the start or end of the string) may be +given by a second optional value after a decimal point. For example: + + Input File Name Format Specifier Output File Name + ---------------- ---------------- ---------------- + Picture-123.jpg %7f.txt Picture.txt + Picture-123.jpg %-.4f.out Picture.out + Picture-123.jpg %7f.%-3f Picture.123 + Picture-123a.jpg Meta%-3.1f.txt Meta123.txt + +(Note that special characters may have a width of greater than one.) + +For %d and %D, the field width/position specifiers may be applied to the +directory levels instead of substring position by using a colon instead of a +decimal point in the format specifier. For example: + + Source Dir Format Result Notes + ------------ ------ ---------- ------------------ + pics/2012/02 %2:d pics/2012/ take top 2 levels + pics/2012/02 %-:1d pics/2012/ up one directory level + pics/2012/02 %:1d 2012/02/ ignore top level + pics/2012/02 %1:1d 2012/ take 1 level after top + pics/2012/02 %-1:D 02 bottom level folder name + /Users/phil %:2d phil/ ignore top 2 levels + +(Note that the root directory counts as one level when an absolute path is +used as in the last example above.) + +For %c, these modifiers have a different effects. If a field width is +given, the copy number is padded with zeros to the specified width. A +leading '-' adds a dash before the copy number, and a '+' adds an underline. +By default, the copy number is omitted from the first file of a given name, +but this can be changed by adding a decimal point to the modifier. For +example: + + -w A%-cZ.txt # AZ.txt, A-1Z.txt, A-2Z.txt ... + -w B%5c.txt # B.txt, B00001.txt, B00002.txt ... + -w C%.c.txt # C0.txt, C1.txt, C2.txt ... + -w D%-.c.txt # D-0.txt, D-1.txt, D-2.txt ... + -w E%-.4c.txt # E-0000.txt, E-0001.txt, E-0002.txt ... + -w F%-.4nc.txt # F-0001.txt, F-0002.txt, F-0003.txt ... + -w G%+c.txt # G.txt, G_1.txt G_2.txt ... + -w H%-lc.txt # H.txt, H-b.txt, H-c.txt ... + -w I.%.3uc.txt # I.AAA.txt, I.AAB.txt, I.AAC.txt ... + +A special feature allows the copy number to be incremented for each +processed file by using %C (upper case) instead of %c. This allows a +sequential number to be added to output file names, even if the names are +different. For %C, a copy number of zero is not omitted as it is with %c. +A leading '-' causes the number to be reset at the start of each new +directory, and '+' has no effect. The number before the decimal place gives +the starting index, the number after the decimal place gives the field +width. The following examples show the output filenames when used with the +command C: + + -w %C%f.txt # 0rose.txt, 1star.txt, 2jet.txt + -w %f-%10C.txt # rose-10.txt, star-11.txt, jet-12.txt + -w %.3C-%f.txt # 000-rose.txt, 001-star.txt, 002-jet.txt + -w %57.4C%f.txt # 0057rose.txt, 0058star.txt, 0059jet.txt + +All format codes may be modified by 'l' or 'u' to specify lower or upper +case respectively (ie. C<%le> for a lower case file extension). When used +to modify %c or %C, the numbers are changed to an alphabetical base (see +example H above). Also, %c and %C may be modified by 'n' to count using +natural numbers starting from 1, instead of 0 (see example F above). + +This same I syntax is used with the B<-o> and B<-tagsFromFile> options, +although %c and %C are only valid for output file names. + +=item B<-W>[+|!] I (B<-tagOut>) + +This enhanced version of the B<-w> option allows a separate output file to +be created for each extracted tag. See the B<-w> option documentation above +for details of the basic functionality. Listed here are the differences +between B<-W> and B<-w>: + +1) With B<-W>, a new output file is created for each extracted tag. + +2) B<-W> supports four additional format codes: %t, %g and %s represent the +tag name, group name, and suggested extension for the output file (based on +the format of the data), and %o represents the value of the +OriginalRawFileName or OriginalFileName tag from the input file (including +extension). The %g code may be followed by a single digit to specify the +group family number (eg. %g1), otherwise family 0 is assumed. The substring +width/position/case specifiers may be used with these format codes in +exactly the same way as with %f and %e. + +3) The argument for B<-W> is interpreted as a file name if it contains no +format codes. (For B<-w>, this would be a file extension.) This change +allows a simple file name to be specified, which, when combined with the +append feature, provides a method to write metadata from multiple source +files to a single output file without the need for shell redirection. For +example, the following pairs of commands give the same result: + + # overwriting existing text file + exiftool test.jpg > out.txt # shell redirection + exiftool test.jpg -W+! out.txt # equivalent -W option + + # append to existing text file + exiftool test.jpg >> out.txt # shell redirection + exiftool test.jpg -W+ out.txt # equivalent -W option + +4) Adding the B<-v> option to B<-W> sends a list of the tags and output file +names to the console instead of giving a verbose dump of the entire file. +(Unless appending all output to one file for each source file by using +B<-W+> with an output file I that does not contain %t, %g, %s or %o.) + +5) Individual list items are stored in separate files when B<-W> is combined +with B<-b>, but note that for separate files to be created %c or %C must be +used in I to give the files unique names. + +=item B<-Wext> I, B<--Wext> I (B<-tagOutExt>) + +This option is used to specify the type of output file(s) written by the +B<-W> option. An output file is written only if the suggested extension +matches I. Multiple B<-Wext> options may be used to write more than +one type of file. Use B<--Wext> to write all but the specified type(s). + +=item B<-X> (B<-xmlFormat>) + +Use ExifTool-specific RDF/XML formatting for console output. Implies the +B<-a> option, so duplicate tags are extracted. The formatting options +B<-b>, B<-D>, B<-H>, B<-l>, B<-s>, B<-sep>, B<-struct> and B<-t> may be used +in combination with B<-X> to affect the output, but note that the tag ID +(B<-D>, B<-H> and B<-t>), binary data (B<-b>) and structured output +(B<-struct>) options are not effective for the short output (B<-s>). Another +restriction of B<-s> is that only one tag with a given group and name may +appear in the output. Note that the tag ID options (B<-D>, B<-H> and B<-t>) +will produce non-standard RDF/XML unless the B<-l> option is also used. + +By default, B<-X> outputs flattened tags, so B<-struct> should be added if +required to preserve XMP structures. List-type tags with multiple values +are formatted as an RDF Bag, but they are combined into a single string when +B<-s> or B<-sep> is used. Using B<-L> changes the XML encoding from "UTF-8" +to "windows-1252". Other B<-charset> settings change the encoding only if +there is a corresponding standard XML character set. The B<-b> option +causes binary data values to be written, encoded in base64 if necessary. +The B<-t> option adds tag table information to the output (see B<-t> for +details). + +Note: This output is NOT the same as XMP because it uses +dynamically-generated property names corresponding to the ExifTool tag names +with ExifTool family 1 group names as namespaces, and not the standard XMP +properties and namespaces. To write XMP instead, use the B<-o> option with +an XMP extension for the output file. + +=back + +=head3 Processing control + +=over 5 + +=item B<-a>, B<--a> (B<-duplicates>, B<--duplicates>) + +Allow (B<-a>) or suppress (B<--a>) duplicate tag names to be extracted. By +default, duplicate tags are suppressed when reading unless the B<-ee> or B<-X> +options are used or the Duplicates option is enabled in the configuration file. +When writing, this option allows multiple Warning messages to be shown. +Duplicate tags are always extracted when copying. + +=item B<-e> (B<--composite>) + +Extract existing tags only -- don't generate composite tags. + +=item B<-ee>[I] (B<-extractEmbedded>) + +Extract information from embedded documents in EPS files, embedded EPS +information and JPEG and Jpeg2000 images in PDF files, embedded MPF images +in JPEG and MPO files, streaming metadata in AVCHD videos, and the resource +fork of Mac OS files. Implies the B<-a> option. Use B<-g3> or B<-G3> to +identify the originating document for extracted information. Embedded +documents containing sub-documents are indicated with dashes in the family 3 +group name. (eg. C is the 3rd sub-document of the 2nd embedded +document.) Note that this option may increase processing time substantially, +especially for PDF files with many embedded images or videos with streaming +metadata. + +When used with B<-ee>, the B<-p> option is evaluated for each embedded +document as if it were a separate input file. This allows, for example, +generation of GPS track logs from timed metadata in videos. See +L for examples. + +Setting I to 2 causes the H264 video stream in MP4 videos to be parsed +until the first Supplemental Enhancement Information (SEI) message is +decoded, or 3 to parse the entire H624 stream and decode all SEI +information. For M2TS videos, a setting of 3 causes the entire file to be +parsed in search of unlisted programs which may contain timed GPS. + +=item B<-ext>[+] I, B<--ext> I (B<-extension>) + +Process only files with (B<-ext>) or without (B<--ext>) a specified +extension. There may be multiple B<-ext> and B<--ext> options. A plus sign +may be added (ie. B<-ext+>) to add the specified extension to the normally +processed files. EXT may begin with a leading '.', which is ignored. Case +is not significant. C<"*"> may be used to process files with any extension +(or none at all), as in the last three examples: + + exiftool -ext JPG DIR # process only JPG files + exiftool --ext cr2 --ext dng DIR # supported files but CR2/DNG + exiftool -ext+ txt DIR # supported files plus TXT + exiftool -ext "*" DIR # process all files + exiftool -ext "*" --ext xml DIR # process all but XML files + exiftool -ext "*" --ext . DIR # all but those with no ext + +Using this option has two main advantages over specifying C<*.I> on the +command line: 1) It applies to files in subdirectories when combined with +the B<-r> option. 2) The B<-ext> option is case-insensitive, which is +useful when processing files on case-sensitive filesystems. + +Note that all files specified on the command line will be processed +regardless of extension unless the B<-ext> option is used. + +=item B<-F>[I] (B<-fixBase>) + +Fix the base for maker notes offsets. A common problem with some image +editors is that offsets in the maker notes are not adjusted properly when +the file is modified. This may cause the wrong values to be extracted for +some maker note entries when reading the edited file. This option allows an +integer I to be specified for adjusting the maker notes base offset. +If no I is given, ExifTool takes its best guess at the correct base. +Note that exiftool will automatically fix the offsets for images which store +original offset information (eg. newer Canon models). Offsets are fixed +permanently if B<-F> is used when writing EXIF to an image. eg) + + exiftool -F -exif:resolutionunit=inches image.jpg + +=item B<-fast>[I] + +Increase speed of extracting information. With B<-fast> (or B<-fast1>), +ExifTool will not scan to the end of a JPEG image to check for an AFCP or +PreviewImage trailer, or past the first comment in GIF images or the +audio/video data in WAV/AVI files to search for additional metadata. These +speed benefits are small when reading images directly from disk, but can be +substantial if piping images through a network connection. For more +substantial speed benefits, B<-fast2> also causes exiftool to avoid +extracting any EXIF MakerNote information, and to stop processing at the +IDAT chunk of PNG images and the mdat atom of QuickTime-format files (but +note that some files may store metadata after this). B<-fast3> avoids +extracting metadata from the file, and returns only pseudo System tags, but +still reads the file header to obtain an educated guess at FileType. +B<-fast4> doesn't even read the file header, and returns only System tags +and a FileType based on the file extension. B<-fast5> also disables +generation of the Composite tags (like B<-e>). Has no effect when writing. + +Note that a separate B<-fast> setting may be used for evaluation of a B<-if> +condition, or when ordering files with the B<-fileOrder> option. See the +B<-if> and B<-fileOrder> options for details. + +=item B<-fileOrder>[I] [-]I + +Set file processing order according to the sorted value of the specified +I. For example, to process files in order of date: + + exiftool -fileOrder DateTimeOriginal DIR + +Additional B<-fileOrder> options may be added for secondary sort keys. +Numbers are sorted numerically, and all other values are sorted +alphabetically. Files missing the specified tag are sorted last. The sort +order may be reversed by prefixing the tag name with a C<-> (eg. +C<-fileOrder -createdate>). Print conversion of the sorted values is +disabled with the B<-n> option, or a C<#> appended to the tag name. Other +formatting options (eg. B<-d>) have no effect on the sorted values. Note +that the B<-fileOrder> option can incur large performance penalty since it +involves an additional initial processing pass of all files, but this impact +may be reduced by specifying a I to effectively set the B<-fast> level +for the initial pass. For example, B<-fileOrder4> may be used if I is +a pseudo System tag. If multiple B<-fileOrder> options are used, the +extraction is done at the lowest B<-fast> level. Note that files are sorted +across directory boundaries if multiple input directories are specified. + +=item B<-i> I (B<-ignore>) + +Ignore specified directory name. I may be either an individual folder +name, or a full path. If a full path is specified, it must match the +Directory tag exactly to be ignored. Use multiple B<-i> options to ignore +more than one directory name. A special I value of C (case +sensitive) may be specified to avoid recursing into directories which are +symbolic links when the B<-r> option is used. As well, a value of C +(case sensitive) may be used to ignore files with names that start with a +"." (ie. hidden files on Unix systems) when scanning a directory. + +=item B<-if>[I] I + +Specify a condition to be evaluated before processing each I. I +is a Perl-like logic expression containing tag names prefixed by C<$> +symbols. It is evaluated with the tags from each I in turn, and the +file is processed only if the expression returns true. Unlike Perl variable +names, tag names are not case sensitive and may contain a hyphen. As well, +tag names may have a leading group names separated by colons, and/or a +trailing C<#> character to disable print conversion. The expression +C<$GROUP:all> evaluates to 1 if any tag exists in the specified C, or +0 otherwise (see note 2 below). When multiple B<-if> options are used, all +conditions must be satisfied to process the file. Returns an exit status of +2 if all files fail the condition. Below are a few examples: + + # extract shutterspeed from all Canon images in a directory + exiftool -shutterspeed -if '$make eq "Canon"' dir + + # add one hour to all images created on or after Apr. 2, 2006 + exiftool -alldates+=1 -if '$CreateDate ge "2006:04:02"' dir + + # set EXIF ISO value if possible, unless it is set already + exiftool '-exif:iso to the B<-if> option causes a separate processing pass to be +executed for evaluating I at a B<-fast> level given by I (see the +B<-fast> option documentation for details). Without I, only one +processing pass is done at the level specified by the B<-fast> option. For +example, using B<-if5> is possible if I uses only pseudo System tags, +and may significantly speed processing if enough files fail the condition. + +The expression has access to the current ExifTool object through C<$self>, +and the following special functions are available to allow short-circuiting +of the file processing. Both functions have a return value of 1. Case is +significant for function names. + + End() - end processing after this file + EndDir() - end processing of files in the current directory + after this file (not compatible with -fileOrder) + +Notes: + +1) The B<-n> and B<-b> options also apply to tags used in I. + +2) Some binary data blocks are not extracted unless specified explicitly. +These tags are not available for use in the B<-if> condition unless they are +also specified on the command line. The alternative is to use the +C<$GROUP:all> syntax. (eg. Use C<$exif:all> instead of C<$exif> in I +to test for the existence of EXIF tags.) + +3) Tags in the string are interpolated in a similar way to B<-p> before the +expression is evaluated. In this interpolation, C<$/> is converted to a +newline and C<$$> represents a single C<$> symbol. So Perl variables, if +used, require a double C<$>, and regular expressions ending in C<$/> must +use C<$$/> instead. + +4) The condition accesses only tags from the file being processed unless the +B<-fileNUM> option is used to read an alternate file and the corresponding +family 8 group name is specified for the tag. See the B<-fileNUM> option +details for more information. + +5) The B<-a> option has no effect on the evaluation of the expression, and +the values of duplicate tags are accessible only by specifying a group name +(such as a family 4 instance number, eg. C<$Copy1:TAG>, C<$Copy2:TAG>, etc). + +6) A special "OK" UserParam is available to test the success of the previous +command when B<-execute> was used, and may be used like any other tag in the +condition (ie. "$OK"). + +7) The API RequestTags option is automatically set for all tags used in the +B<-if> condition. + +=item B<-m> (B<-ignoreMinorErrors>) + +Ignore minor errors and warnings. This enables writing to files with minor +errors and disables some validation checks which could result in minor +warnings. Generally, minor errors/warnings indicate a problem which usually +won't result in loss of metadata if ignored. However, there are exceptions, +so ExifTool leaves it up to you to make the final decision. Minor errors +and warnings are indicated by "[minor]" at the start of the message. +Warnings which affect processing when ignored are indicated by "[Minor]" +(with a capital "M"). Note that this causes missing values in +B<-tagsFromFile>, B<-p> and B<-if> strings to be set to an empty string +rather than an undefined value. + +=item B<-o> I or I (B<-out>) + +Set the output file or directory name when writing information. Without +this option, when any "real" tags are written the original file is renamed +to C and output is written to I. When writing only +FileName and/or Directory "pseudo" tags, B<-o> causes the file to be copied +instead of moved, but directories specified for either of these tags take +precedence over that specified by the B<-o> option. + +I may be C<-> to write to stdout. The output file name may also be +specified using a I string in which %d, %f and %e represent the +directory, file name and extension of I. Also, %c may be used to add +a copy number. See the B<-w> option for I string examples. + +The output file is taken to be a directory name if it already exists as a +directory or if the name ends with '/'. Output directories are created if +necessary. Existing files will not be overwritten. Combining the +B<-overwrite_original> option with B<-o> causes the original source file to +be erased after the output file is successfully written. + +A special feature of this option allows the creation of certain types of +files from scratch, or with the metadata from another type of file. The +following file types may be created using this technique: + + XMP, EXIF, EXV, MIE, ICC/ICM, VRD, DR4 + +The output file type is determined by the extension of I (specified +as C<-.EXT> when writing to stdout). The output file is then created from a +combination of information in I (as if the B<-tagsFromFile> option was +used), and tag values assigned on the command line. If no I is +specified, the output file may be created from scratch using only tags +assigned on the command line. + +=item B<-overwrite_original> + +Overwrite the original I (instead of preserving it by adding +C<_original> to the file name) when writing information to an image. +Caution: This option should only be used if you already have separate backup +copies of your image files. The overwrite is implemented by renaming a +temporary file to replace the original. This deletes the original file and +replaces it with the edited version in a single operation. When combined +with B<-o>, this option causes the original file to be deleted if the output +file was successfully written (ie. the file is moved instead of copied). + +=item B<-overwrite_original_in_place> + +Similar to B<-overwrite_original> except that an extra step is added to +allow the original file attributes to be preserved. For example, on a Mac +this causes the original file creation date, type, creator, label color, +icon, Finder tags, other extended attributes and hard links to the file to +be preserved (but note that the Mac OS resource fork is always preserved +unless specifically deleted with C<-rsrc:all=>). This is implemented by +opening the original file in update mode and replacing its data with a copy +of a temporary file before deleting the temporary. The extra step results +in slower performance, so the B<-overwrite_original> option should be used +instead unless necessary. + +Note that this option reverts to the behaviour of the B<-overwrite_original> +option when also writing the FileName and/or Directory tags. + +=item B<-P> (B<-preserve>) + +Preserve the filesystem modification date/time (C) of the +original file when writing. Note that some filesystems store a creation +date (ie. C on Windows and Mac systems) which is not +affected by this option. This creation date is preserved on Windows systems +where Win32API::File and Win32::API are available regardless of this +setting. For other systems, the B<-overwrite_original_in_place> option may +be used if necessary to preserve the creation date. The B<-P> option is +superseded by any value written to the FileModifyDate tag. + +=item B<-password> I + +Specify password to allow processing of password-protected PDF documents. +If a password is required but not given, a warning is issued and the +document is not processed. This option is ignored if a password is not +required. + +=item B<-progress>[NUM][:[I]] + +Show the progress when processing files. Without a colon, the B<-progress> +option adds a progress count in brackets after the name of each processed +file, giving the current file number and the total number of files to be +processed. Implies the B<-v0> option, causing the names of processed files +to also be printed when writing. When combined with the B<-if> option, the +total count includes all files before the condition is applied, but files +that fail the condition will not have their names printed. If NUM is +specified, the progress is shown every NUM input files. + +If followed by a colon (ie. B<-progress:>), the console window title is set +according to the specified I<TITLE> string. If no I<TITLE> is given, a +default I<TITLE> string of "ExifTool %p%%" is assumed. In the string, %f +represents the file name, %p is the progress as a percent, %r is the +progress as a ratio, %##b is a progress bar of width "##" (where "##" is an +integer specifying the bar width in characters, or 20 characters by default +if "##" is omitted), and %% is a % character. May be combined with the +normal B<-progress> option to also show the progress count in console +messages. (Note: For this feature to function correctly on Mac/Linux, stderr +must go to the console.) + +=item B<-q> (B<-quiet>) + +Quiet processing. One B<-q> suppresses normal informational messages, and a +second B<-q> suppresses warnings as well. Error messages can not be +suppressed, although minor errors may be downgraded to warnings with the +B<-m> option, which may then be suppressed with C<-q -q>. + +=item B<-r>[.] (B<-recurse>) + +Recursively process files in subdirectories. Only meaningful if I<FILE> is +a directory name. Subdirectories with names beginning with "." are not +processed unless "." is added to the option name (ie. B<-r.> or +B<-recurse.>). By default, exiftool will also follow symbolic links to +directories if supported by the system, but this may be disabled with +C<-i SYMLINKS> (see the B<-i> option for details). Combine this with +B<-ext> options to control the types of files processed. + +=item B<-scanForXMP> + +Scan all files (even unsupported formats) for XMP information unless found +already. When combined with the B<-fast> option, only unsupported file +types are scanned. Warning: It can be time consuming to scan large files. + +=item B<-u> (B<-unknown>) + +Extract values of unknown tags. Add another B<-u> to also extract unknown +information from binary data blocks. This option applies to tags with +numerical tag ID's, and causes tag names like "Exif_0xc5d9" to be generated +for unknown information. It has no effect on information types which have +human-readable tag ID's (such as XMP), since unknown tags are extracted +automatically from these formats. + +=item B<-U> (B<-unknown2>) + +Extract values of unknown tags as well as unknown information from some +binary data blocks. This is the same as two B<-u> options. + +=item B<-wm> I<MODE> (B<-writeMode>) + +Set mode for writing/creating tags. I<MODE> is a string of one or more +characters from the list below. The default write mode is C<wcg>. + + w - Write existing tags + c - Create new tags + g - create new Groups as necessary + +For example, use C<-wm cg> to only create new tags (and avoid editing +existing ones). + +The level of the group is the SubDirectory level in the metadata structure. +For XMP or IPTC this is the full XMP/IPTC block (the family 0 group), but +for EXIF this is the individual IFD (the family 1 group). + +=item B<-z> (B<-zip>) + +When reading, causes information to be extracted from .gz and .bz2 +compressed images (only one image per archive; requires gzip and bzip2 to be +available). When writing, causes compressed information to be written if +supported by the metadata format (eg. PNG supports compressed textual +metadata, JXL supports compressed EXIF and XML, and MIE supports any +compressed metadata), disables the recommended padding in embedded XMP +(saving 2424 bytes when writing XMP in a file), and writes XMP in shorthand +format -- the equivalent of setting the API Compress=1 and +Compact="NoPadding,Shorthand". + +=back + +=head3 Other options + +=over 5 + +=item B<-@> I<ARGFILE> + +Read command-line arguments from the specified file. The file contains one +argument per line (NOT one option per line -- some options require +additional arguments, and all arguments must be placed on separate lines). +Blank lines and lines beginning with C<#> are ignored (unless they start +with C<#[CSTR]>, in which case the rest of the line is treated as a C +string, allowing standard C escape sequences such as "\n" for a newline). +White space at the start of a line is removed. Normal shell processing of +arguments is not performed, which among other things means that arguments +should not be quoted and spaces are treated as any other character. +I<ARGFILE> may exist relative to either the current directory or the +exiftool directory unless an absolute pathname is given. + +For example, the following I<ARGFILE> will set the value of Copyright to +"Copyright YYYY, Phil Harvey", where "YYYY" is the year of CreateDate: + + -d + %Y + -copyright<Copyright $createdate, Phil Harvey + +Arguments in I<ARGFILE> behave exactly the same as if they were entered at +the location of the B<-@> option on the command line, with the exception +that the B<-config> and B<-common_args> options may not be used in an +I<ARGFILE>. + +=item B<-k> (B<-pause>) + +Pause with the message C<-- press any key --> or C<-- press RETURN --> +(depending on your system) before terminating. This option is used to +prevent the command window from closing when run as a Windows drag and drop +application. + +=item B<-list>, B<-listw>, B<-listf>, B<-listr>, B<-listwf>, +B<-listg>[I<NUM>], B<-listd>, B<-listx> + +Print a list of all valid tag names (B<-list>), all writable tag names +(B<-listw>), all supported file extensions (B<-listf>), all recognized file +extensions (B<-listr>), all writable file extensions (B<-listwf>), all tag +groups [in a specified family] (B<-listg>[I<NUM>]), all deletable tag groups +(B<-listd>), or an XML database of tag details including language +translations (B<-listx>). The B<-list>, B<-listw> and B<-listx> options may +be followed by an additional argument of the form C<-GROUP:All> to list only +tags in a specific group, where C<GROUP> is one or more family 0-2 group +names (excepting EXIF IFD groups) separated by colons. With B<-listg>, +I<NUM> may be given to specify the group family, otherwise family 0 is +assumed. The B<-l> option may be combined with B<-listf>, B<-listr> or +B<-listwf> to add file descriptions to the list. The B<-lang> option may be +combined with B<-listx> to output descriptions in a single language. Here +are some examples: + + -list # list all tag names + -list -EXIF:All # list all EXIF tags + -list -xmp:time:all # list all XMP tags relating to time + -listw -XMP-dc:All # list all writable XMP-dc tags + -listf # list all supported file extensions + -listr # list all recognized file extensions + -listwf # list all writable file extensions + -listg1 # list all groups in family 1 + -listd # list all deletable groups + -listx -EXIF:All # list database of EXIF tags in XML format + -listx -XMP:All -s # list short XML database of XMP tags + +When combined with B<-listx>, the B<-s> option shortens the output by +omitting the descriptions and values (as in the last example above), and +B<-f> adds 'flags' and 'struct' attributes if applicable. The flags are +formatted as a comma-separated list of the following possible values: +Avoid, Binary, List, Mandatory, Permanent, Protected, Unknown and Unsafe +(see the L<Tag Name documentation|Image::ExifTool::TagNames>). For XMP List +tags, the list type (Alt, Bag or Seq) is added to the flags, and flattened +structure tags are indicated by a Flattened flag with 'struct' giving the ID +of the parent structure. + +Note that none of the B<-list> options require an input I<FILE>. + +=item B<-ver> + +Print exiftool version number. The B<-v> option may be added to print +addition system information (see the README file of the full distribution +for more details about optional libraries), or B<-v2> to also list the Perl +include directories. + +=item B<--> + +Indicates the end of options. Any remaining arguments are treated as file +names, even if they begin with a dash (C<->). + +=back + +=head3 Special features + +=over 5 + +=item B<-geotag> I<TRKFILE> + +Geotag images from the specified GPS track log file. Using the B<-geotag> +option is equivalent to writing a value to the C<Geotag> tag. The GPS +position is interpolated from the track at a time specified by the value +written to the C<Geotime> tag. If C<Geotime> is not specified, the value is +copied from C<DateTimeOriginal#> (the C<#> is added to copy the unformatted +value, avoiding potential conflicts with the B<-d> option). For example, +the following two commands are equivalent: + + exiftool -geotag trk.log image.jpg + exiftool -geotag trk.log "-Geotime<DateTimeOriginal#" image.jpg + +When the C<Geotime> value is converted to UTC, the local system timezone is +assumed unless the date/time value contains a timezone. Writing C<Geotime> +causes the following tags to be written (provided they can be calculated +from the track log, and they are supported by the destination metadata +format): GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef, +GPSAltitude, GPSAltitudeRef, GPSDateStamp, GPSTimeStamp, GPSDateTime, +GPSTrack, GPSTrackRef, GPSSpeed, GPSSpeedRef, GPSImgDirection, +GPSImgDirectionRef, GPSPitch, GPSRoll, AmbientTemperature and +CameraElevationAngle. By default, tags are created in EXIF, and updated in +XMP only if they already exist. However, C<EXIF:Geotime> or C<XMP:Geotime> +may be specified to write only EXIF or XMP tags respectively. Note that +GPSPitch and GPSRoll are non-standard, and require user-defined tags in +order to be written. + +The C<Geosync> tag may be used to specify a time correction which is applied +to each C<Geotime> value for synchronization with GPS time. For example, +the following command compensates for image times which are 1 minute and 20 +seconds behind GPS: + + exiftool -geosync=+1:20 -geotag a.log DIR + +Advanced C<Geosync> features allow a piecewise linear time drift correction +and synchronization from previously geotagged images. See "geotag.html" in +the full ExifTool distribution for more information. + +Multiple B<-geotag> options may be used to concatenate GPS track log data. +Also, a single B<-geotag> option may be used to load multiple track log +files by using wildcards in the I<TRKFILE> name, but note that in this case +I<TRKFILE> must be quoted on most systems (with the notable exception of +Windows) to prevent filename expansion. For example: + + exiftool -geotag "TRACKDIR/*.log" IMAGEDIR + +Currently supported track file formats are GPX, NMEA RMC/GGA/GLL, KML, IGC, +Garmin XML and TCX, Magellan PMGNTRK, Honeywell PTNTHPR, Bramor gEO, Winplus +Beacon TXT, and GPS/IMU CSV files. See L</GEOTAGGING EXAMPLES> for +examples. Also see "geotag.html" in the full ExifTool distribution and the +L<Image::ExifTool Options|Image::ExifTool/Options> for more details and for +information about geotag configuration options. + +=item B<-globalTimeShift> I<SHIFT> + +Shift all formatted date/time values by the specified amount when reading. +Does not apply to unformatted (B<-n>) output. I<SHIFT> takes the same form +as the date/time shift when writing (see +L<Image::ExifTool::Shift.pl|Image::ExifTool::Shift.pl> for details), with a +negative shift being indicated with a minus sign (C<->) at the start of the +I<SHIFT> string. For example: + + # return all date/times, shifted back by 1 hour + exiftool -globalTimeShift -1 -time:all a.jpg + + # set the file name from the shifted CreateDate (-1 day) for + # all images in a directory + exiftool "-filename<createdate" -globaltimeshift "-0:0:1 0:0:0" \ + -d %Y%m%d-%H%M%S.%%e dir + +=item B<-use> I<MODULE> + +Add features from specified plug-in I<MODULE>. Currently, the MWG module is +the only plug-in module distributed with exiftool. This module adds +read/write support for tags as recommended by the Metadata Working Group. As +a convenience, C<-use MWG> is assumed if the group name prefix starts with +C<MWG:> exactly for any requested tag. See the +L<MWG Tags documentation|Image::ExifTool::TagNames/MWG Tags> for more +details. Note that this option is not reversible, and remains in effect +until the application terminates, even across the B<-execute> option. + +=back + +=head3 Utilities + +=over 5 + +=item B<-restore_original> + +=item B<-delete_original>[!] + +These utility options automate the maintenance of the C<_original> files +created by exiftool. They have no effect on files without an C<_original> +copy. The B<-restore_original> option restores the specified files from +their original copies by renaming the C<_original> files to replace the +edited versions. For example, the following command restores the originals +of all JPG images in directory C<DIR>: + + exiftool -restore_original -ext jpg DIR + +The B<-delete_original> option deletes the C<_original> copies of all files +specified on the command line. Without a trailing C<!> this option prompts +for confirmation before continuing. For example, the following command +deletes C<a.jpg_original> if it exists, after asking "Are you sure?": + + exiftool -delete_original a.jpg + +These options may not be used with other options to read or write tag values +in the same command, but may be combined with options such B<-ext>, B<-if>, +B<-r>, B<-q> and B<-v>. + +=back + +=head3 Advanced options + +Among other things, the advanced options allow complex processing to be +performed from a single command without the need for additional scripting. +This may be particularly useful for implementations such as Windows +drag-and-drop applications. These options may also be used to improve +performance in multi-pass processing by reducing the overhead required to +load exiftool for each invocation. + +=over 5 + +=item B<-api> [I<OPT[[^]=[VAL]]>] + +Set ExifTool API option. I<OPT> is an API option name. The option value is +set to 1 if I<=VAL> is omitted. If I<VAL> is omitted, the option value is +set to undef if C<=> is used, or an empty string with C<^=>. If I<OPT> is +not specified a list of available options is returned. The option name is +not case senstive, but the option values are. See +L<Image::ExifTool Options|Image::ExifTool/Options> for option details. This +overrides API options set via the config file. + +=item B<-common_args> + +Specifies that all arguments following this option are common to all +executed commands when B<-execute> is used. This and the B<-config> option +are the only options that may not be used inside a B<-@> I<ARGFILE>. Note +that by definition this option and its arguments MUST come after all other +options on the command line. + +=item B<-config> I<CFGFILE> + +Load specified configuration file instead of the default ".ExifTool_config". +If used, this option must come before all other arguments on the command +line and applies to all B<-execute>'d commands. The I<CFGFILE> must exist +relative to the current working directory or the exiftool application +directory unless an absolute path is specified. Loading of the default +config file may be disabled by setting I<CFGFILE> to an empty string (ie. +""). See L<https://exiftool.org/config.html> and +config_files/example.config in the full ExifTool distribution for details +about the configuration file syntax. + +=item B<-echo>[I<NUM>] I<TEXT> + +Echo I<TEXT> to stdout (B<-echo> or B<-echo1>) or stderr (B<-echo2>). Text +is output as the command line is parsed, before the processing of any input +files. I<NUM> may also be 3 or 4 to output text (to stdout or stderr +respectively) after processing is complete. For B<-echo3> and B<-echo4>, +"${status}" may be used in the I<TEXT> string to represent the numerical +exit status of the command (see L</EXIT STATUS>). + +=item B<-efile>[I<NUM>][!] I<ERRFILE> + +Save the names of files giving errors (I<NUM> missing or 1), files that were +unchanged (I<NUM> is 2), files that fail the B<-if> condition (I<NUM> is 4), +files that were updated (I<NUM> is 8), files that were created (I<NUM> is +16), or any combination thereof by summing I<NUM> (eg. B<-efile3> is the +same has having both B<-efile> and B<-efile2> options with the same +I<ERRFILE>). By default, file names are appended to any existing I<ERRFILE>, +but I<ERRFILE> is overwritten if an exclamation point is added to the option +(eg. B<-efile!>). Saves the name of the file specified by the B<-srcfile> +option if applicable. + +=item B<-execute>[I<NUM>] + +Execute command for all arguments up to this point on the command line (plus +any arguments specified by B<-common_args>). The result is as if the +commands were executed as separate command lines (with the exception of the +B<-config> and B<-use> options which remain in effect for subsequent +commands). Allows multiple commands to be executed from a single command +line. I<NUM> is an optional number that is echoed in the "{ready}" message +when using the B<-stay_open> feature. If a I<NUM> is specified, the B<-q> +option no longer suppresses the output "{readyNUM}" message. + +=item B<-file>I<NUM> I<ALTFILE> + +Read tags from an alternate source file. Among other things, this allows +tags from different files to be compared and combined using the B<-if> and +B<-p> options. Tags from alternate files are accessed via the corresponding +family 8 group name (eg. C<File1:TAG> for the B<-file1> option, C<File2:TAG> +for B<-file2>, etc). I<ALTFILE> may contain filename formatting codes like +the B<-w> option (%d, %f, etc), and/or tag names with a leading C<$> symbol +to access tags from the source file in the same way as the B<-p> option (so +any other dollar symbol in the file name must be doubled, eg. +C<money$$.jpg>). For example, assuming that the OriginalFileName tag has +been set in the edited file, a command to copy Rights from the original file +could look like this: + + exiftool -file1 '$originalfilename' '-rights<file1:rights' edited.jpg + +Composite tags may access tags from alternate files using the appropriate +(case-sensitive) family 8 group name. + +=item B<-list_dir> + +List directories themselves instead of their contents. This option +effectively causes directories to be treated as normal files when reading +and writing. For example, with this option the output of the C<ls -la> +command on Mac/Linux may be approximated by this exiftool command: + + exiftool -list_dir -T -ls-l -api systemtags -fast5 .* * + +(The B<-T> option formats the output in tab-separated columns, B<-ls-l> is a +L<shortcut tag|Image::ExifTool::Shortcuts>, the API SystemTags option is +required to extract some necessary tags, and the B<-fast5> option is added +for speed since only system tags are being extracted.) + +=item B<-srcfile> I<FMT> + +Specify a different source file to be processed based on the name of the +original I<FILE>. This may be useful in some special situations for +processing related preview images or sidecar files. See the B<-w> option +for a description of the I<FMT> syntax. Note that file name I<FMT> strings +for all options are based on the original I<FILE> specified from the command +line, not the name of the source file specified by B<-srcfile>. + +For example, to copy metadata from NEF files to the corresponding JPG +previews in a directory where other JPG images may exist: + + exiftool -ext nef -tagsfromfile @ -srcfile %d%f.jpg dir + +If more than one B<-srcfile> option is specified, the files are tested in +order and the first existing source file is processed. If none of the +source files already exist, then exiftool uses the first B<-srcfile> +specified. + +A I<FMT> of C<@> may be used to represent the original I<FILE>, which may be +useful when specifying multiple B<-srcfile> options (eg. to fall back to +processing the original I<FILE> if no sidecar exists). + +When this option is used, two special UserParam tags (OriginalFileName and +OriginalDirectory) are generated to allow access to the original I<FILE> +name and directory. + +=item B<-stay_open> I<FLAG> + +If I<FLAG> is C<1> or C<True> (case insensitive), causes exiftool keep +reading from the B<-@> I<ARGFILE> even after reaching the end of file. This +feature allows calling applications to pre-load exiftool, thus avoiding the +overhead of loading exiftool for each command. The procedure is as follows: + +1) Execute C<exiftool -stay_open True -@ I<ARGFILE>>, where I<ARGFILE> is the +name of an existing (possibly empty) argument file or C<-> to pipe arguments +from the standard input. + +2) Write exiftool command-line arguments to I<ARGFILE>, one argument per +line (see the B<-@> option for details). + +3) Write C<-execute\n> to I<ARGFILE>, where C<\n> represents a newline +sequence. (Note: You may need to flush your write buffers here if using +buffered output.) ExifTool will then execute the command with the arguments +received up to this point, send a "{ready}" message to stdout when done +(unless the B<-q> or B<-T> option is used), and continue trying to read +arguments for the next command from I<ARGFILE>. To aid in command/response +synchronization, any number appended to the B<-execute> option is echoed in +the "{ready}" message. For example, C<-execute613> results in "{ready613}". +When this number is added, B<-q> no longer suppresses the "{ready}" message. +(Also, see the B<-echo3> and B<-echo4> options for additional ways to pass +signals back to your application.) + +4) Repeat steps 2 and 3 for each command. + +5) Write C<-stay_open\nFalse\n> (or C<-stay_open\n0\n>) to I<ARGFILE> when +done. This will cause exiftool to process any remaining command-line +arguments then exit normally. + +The input I<ARGFILE> may be changed at any time before step 5 above by +writing the following lines to the currently open I<ARGFILE>: + + -stay_open + True + -@ + NEWARGFILE + +This causes I<ARGFILE> to be closed, and I<NEWARGFILE> to be kept open. +(Without the B<-stay_open> here, exiftool would have returned to reading +arguments from I<ARGFILE> after reaching the end of I<NEWARGFILE>.) + +Note: When writing arguments to a disk file there is a delay of up to 0.01 +seconds after writing C<-execute\n> before exiftool starts processing the +command. This delay may be avoided by sending a CONT signal to the exiftool +process immediately after writing C<-execute\n>. (There is no associated +delay when writing arguments via a pipe with C<-@ ->, so the signal is not +necessary when using this technique.) + +=item B<-userParam> I<PARAM[[^]=[VAL]]> + +Set user parameter. I<PARAM> is an arbitrary user parameter name. This is +an interface to the API UserParam option (see the +L<Image::ExifTool Options|Image::ExifTool/Options> documentation), and +provides a method to access user-defined parameters in arguments to the +B<-if> and B<-p> options as if they were any other tag. Appending a hash +tag (C<#>) to I<PARAM> (eg. C<-userParam MyTag#=yes>) also causes the +parameter to be extracted as a normal tag in the UserParam group. Similar +to the B<-api> option, the parameter value is set to 1 if I<=VAL> is +omitted, undef if just I<VAL> is omitted with C<=>, or an empty string if +I<VAL> is omitted with C<^=>. + + exiftool -p '$test from $filename' -userparam test=Hello FILE + +=back + +=head3 Advanced formatting feature + +An advanced formatting feature allows modification of the value of any tag +interpolated within a B<-if> or B<-p> option argument, or a B<-tagsFromFile> +redirection string. Tag names within these strings are prefixed by a C<$> +symbol, and an arbitrary Perl expression may be applied to the tag value by +placing braces around the tag name and inserting the expression after the +name, separated by a semicolon (ie. C<${TAG;EXPR}>). The expression acts on +the value of the tag through the default input variable (C<$_>), and has +access to the full ExifTool API through the current ExifTool object +(C<$self>) and the tag key (C<$tag>). It may contain any valid Perl code, +including translation (C<tr///>) and substitution (C<s///>) operations, but +note that braces within the expression must be balanced. The example below +prints the camera Make with spaces translated to underlines, and multiple +consecutive underlines replaced by a single underline: + + exiftool -p '${make;tr/ /_/;s/__+/_/g}' image.jpg + +An C<@> may be added after the tag name to make the expression act on +individual list items for list-type tags, simplifying list processing. Set +C<$_> to undef to remove an item from the list. As an example, the +following command returns all subjects not containing the string "xxx": + + exiftool -p '${subject@;$_=undef if /xxx/}' image.jpg + +A default expression of C<tr(/\\?*:|"E<lt>E<gt>\0)()d> is assumed if the +expression is empty (ie. C<${TAG;}>). This removes the characters / \ ? * : +| E<lt> E<gt> and null from the printed value. (These characters are +illegal in Windows file names, so this feature is useful if tag values are +used in file names.) + +=head4 Helper functions + +C<DateFmt> + +Simplifies reformatting of individual date/time values. This function acts +on a standard EXIF-formatted date/time value in C<$_> and formats it +according to the specified format string (see the B<-d> option). To avoid +trying to reformat an already-formatted date/time value, a C<#> must be +added to the tag name (as in the example below) if the B<-d> option is also +used. For example: + + exiftool -p '${createdate#;DateFmt("%Y-%m-%d_%H%M%S")}' a.jpg + +C<ShiftTime> + +Shifts EXIF-formatted date/time string by a specified amount. Start with a +leading minus sign to shift backwards in time. See +L<Image::ExifTool::Shift.pl|Image::ExifTool::Shift.pl> for details about +shift syntax. For example, to shift a date/time value back by one year: + + exiftool -p '${createdate;ShiftTime("-1:0:0 0")}' a.jpg + +C<NoDups> + +Removes duplicate items from a list with a separator specified by the +B<-sep> option. This function is most useful when copying list-type tags. +For example, the following command may be used to remove duplicate Keywords: + + exiftool -sep '##' '-keywords<${keywords;NoDups}' a.jpg + +The B<-sep> option is necessary to split the string back into individual +list items when writing to a list-type tag. + +An optional flag argument may be set to 1 to cause C<NoDups> to set C<$_> to +undef if no duplicates existed, thus preventing the file from being +rewritten unnecessarily: + + exiftool -sep '##' '-keywords<${keywords;NoDups(1)}' a.jpg + +Note that function names are case sensitive. + +ExifTool 12.64 adds an API NoDups option which makes the NoDups helper +function largely redundant, with all the functionality except the ability to +avoid rewriting the file if there are no duplicates, but with the advantage +the duplicates may be removed when accumulating list items from multiple +sources. An equivalent to the above commands using this feature would be: + + exiftool -tagsfromfile @ -keywords -api nodups a.jpg + +=head1 WINDOWS UNICODE FILE NAMES + +In Windows, command-line arguments are specified using the current code page +and are recoded automatically to the system code page. This recoding is not +done for arguments in ExifTool arg files, so by default filenames in arg +files use the system code page. Unfortunately, these code pages are not +complete character sets, so not all file names may be represented. + +ExifTool 9.79 and later allow the file name encoding to be specified with +C<-charset filename=CHARSET>, where C<CHARSET> is the name of a valid +ExifTool character set, preferably C<UTF8> (see the B<-charset> option for a +complete list). Setting this triggers the use of Windows wide-character i/o +routines, thus providing support for most Unicode file names (see note 4). +But note that it is not trivial to pass properly encoded file names on the +Windows command line (see L<https://exiftool.org/faq.html#Q18> for details), +so placing them in a UTF-8 encoded B<-@> argfile and using +C<-charset filename=utf8> is recommended if possible. + +A warning is issued if a specified filename contains special characters and +the filename character set was not provided. However, the warning may be +disabled by setting C<-charset filename="">, and ExifTool may still function +correctly if the system code page matches the character set used for the +file names. + +When a directory name is provided, the file name encoding need not be +specified (unless the directory name contains special characters), and +ExifTool will automatically use wide-character routines to scan the +directory. + +The filename character set applies to the I<FILE> arguments as well as +filename arguments of B<-@>, B<-geotag>, B<-o>, B<-p>, B<-srcfile>, +B<-tagsFromFile>, B<-csv>=, B<-j>= and B<->I<TAG>E<lt>=. However, it does +not apply to the B<-config> filename, which always uses the system character +set. The C<-charset filename=> option must come before the B<-@> option to +be effective, but the order doesn't matter with respect to other options. + +Notes: + +1) FileName and Directory tag values still use the same encoding as other +tag values, and are converted to/from the filename character set when +writing/reading if specified. + +2) Unicode support is not yet implemented for other Windows-based systems +like Cygwin. + +3) See L</WRITING READ-ONLY FILES> below for a note about editing read-only +files with Unicode names. + +4) Unicode file names with surrogate pairs (code points over U+FFFF) still +cause problems. + +=head1 WRITING READ-ONLY FILES + +In general, ExifTool may be used to write metadata to read-only files +provided that the user has write permission in the directory. However, +there are three cases where file write permission is also required: + +1) When using the B<-overwrite_original_in_place> option. + +2) When writing only pseudo System tags (eg. FileModifyDate). + +3) On Windows if the file has Unicode characters in its name, and a) the +B<-overwrite_original> option is used, or b) the C<_original> backup already +exists. + +Hidden files in Windows behave as read-only files when attempting to write +any real tags to the file -- an error is generated when using the +B<-overwrite_original_in_place>, otherwise writing should be successful and +the hidden attribute will be removed. But the B<-if> option may be used to +avoid processing hidden files (provided Win32API::File is available): + + exiftool -if "$fileattributes !~ /Hidden/" ... + +=head1 READING EXAMPLES + +B<Note>: Beware when cutting and pasting these examples into your terminal! +Some characters such as single and double quotes and hyphens may have been +changed into similar-looking yet functionally-different characters by the +text formatter used to display this documentation. Also note that Windows +users must use double quotes instead of single quotes as below around +arguments containing special characters. + +=over 5 + +=item exiftool -a -u -g1 a.jpg + +Print all meta information in an image, including duplicate and unknown +tags, sorted by group (for family 1). For performance reasons, this command +may not extract all available metadata. (Metadata in embedded documents, +metadata extracted by external utilities, and metadata requiring excessive +processing time may not be extracted). Add C<-ee3> and C<-api RequestAll=3> +to the command to extract absolutely everything available. + +=item exiftool -common dir + +Print common meta information for all images in C<dir>. C<-common> is a +L<shortcut tag|Image::ExifTool::Shortcuts> representing common EXIF meta +information. + +=item exiftool -T -createdate -aperture -shutterspeed -iso dir > out.txt + +List specified meta information in tab-delimited column form for all images +in C<dir> to an output text file named "out.txt". + +=item exiftool -s -ImageSize -ExposureTime b.jpg + +Print ImageSize and ExposureTime tag names and values. + +=item exiftool -l -canon c.jpg d.jpg + +Print standard Canon information from two image files. + +=item exiftool -r -w .txt -common pictures + +Recursively extract common meta information from files in C<pictures> +directory, writing text output to C<.txt> files with the same names. + +=item exiftool -b -ThumbnailImage image.jpg > thumbnail.jpg + +Save thumbnail image from C<image.jpg> to a file called C<thumbnail.jpg>. + +=item exiftool -b -JpgFromRaw -w _JFR.JPG -ext NEF -r . + +Recursively extract JPG image from all Nikon NEF files in the current +directory, adding C<_JFR.JPG> for the name of the output JPG files. + +=item exiftool -a -b -W %d%f_%t%-c.%s -preview:all dir + +Extract all types of preview images (ThumbnailImage, PreviewImage, +JpgFromRaw, etc.) from files in directory "dir", adding the tag name to the +output preview image file names. + +=item exiftool -d '%r %a, %B %e, %Y' -DateTimeOriginal -S -s -ext jpg . + +Print formatted date/time for all JPG files in the current directory. + +=item exiftool -IFD1:XResolution -IFD1:YResolution image.jpg + +Extract image resolution from EXIF IFD1 information (thumbnail image IFD). + +=item exiftool '-*resolution*' image.jpg + +Extract all tags with names containing the word "Resolution" from an image. + +=item exiftool -xmp:author:all -a image.jpg + +Extract all author-related XMP information from an image. + +=item exiftool -xmp -b a.jpg > out.xmp + +Extract complete XMP data record intact from C<a.jpg> and write it to +C<out.xmp> using the special C<XMP> tag (see the Extra tags in +L<Image::ExifTool::TagNames|Image::ExifTool::TagNames>). + +=item exiftool -p '$filename has date $dateTimeOriginal' -q -f dir + +Print one line of output containing the file name and DateTimeOriginal for +each image in directory C<dir>. + +=item exiftool -ee3 -p '$gpslatitude, $gpslongitude, $gpstimestamp' a.m2ts + +Extract all GPS positions from an AVCHD video. + +=item exiftool -icc_profile -b -w icc image.jpg + +Save complete ICC_Profile from an image to an output file with the same name +and an extension of C<.icc>. + +=item exiftool -htmldump -w tmp/%f_%e.html t/images + +Generate HTML pages from a hex dump of EXIF information in all images from +the C<t/images> directory. The output HTML files are written to the C<tmp> +directory (which is created if it didn't exist), with names of the form +'FILENAME_EXT.html'. + +=item exiftool -a -b -ee -embeddedimage -W Image_%.3g3.%s file.pdf + +Extract embedded JPG and JP2 images from a PDF file. The output images will +have file names like "Image_#.jpg" or "Image_#.jp2", where "#" is the +ExifTool family 3 embedded document number for the image. + +=back + +=head1 WRITING EXAMPLES + +Note that quotes are necessary around arguments which contain certain +special characters such as C<E<gt>>, C<E<lt>> or any white space. These +quoting techniques are shell dependent, but the examples below will work for +most Unix shells. With the Windows cmd shell however, double quotes should +be used (eg. -Comment=E<34>This is a new commentE<34>). + +=over 5 + +=item exiftool -Comment='This is a new comment' dst.jpg + +Write new comment to a JPG image (replaces any existing comment). + +=item exiftool -comment= -o newdir -ext jpg . + +Remove comment from all JPG images in the current directory, writing the +modified images to a new directory. + +=item exiftool -keywords=EXIF -keywords=editor dst.jpg + +Replace existing keyword list with two new keywords (C<EXIF> and C<editor>). + +=item exiftool -Keywords+=word -o newfile.jpg src.jpg + +Copy a source image to a new file, and add a keyword (C<word>) to the +current list of keywords. + +=item exiftool -exposurecompensation+=-0.5 a.jpg + +Decrement the value of ExposureCompensation by 0.5 EV. Note that += with a +negative value is used for decrementing because the -= operator is used for +conditional deletion (see next example). + +=item exiftool -credit-=xxx dir + +Delete Credit information from all files in a directory where the Credit +value was C<xxx>. + +=item exiftool -xmp:description-de='kühl' -E dst.jpg + +Write alternate language for XMP:Description, using HTML character escaping +to input special characters. + +=item exiftool -all= dst.jpg + +Delete all meta information from an image. Note: You should NOT do this to +RAW images (except DNG) since proprietary RAW image formats often contain +information in the makernotes that is necessary for converting the image. + +=item exiftool -all= -comment='lonely' dst.jpg + +Delete all meta information from an image and add a comment back in. (Note +that the order is important: C<-comment='lonely' -all=> would also delete +the new comment.) + +=item exiftool -all= --jfif:all dst.jpg + +Delete all meta information except JFIF group from an image. + +=item exiftool -Photoshop:All= dst.jpg + +Delete Photoshop meta information from an image (note that the Photoshop +information also includes IPTC). + +=item exiftool -r -XMP-crss:all= DIR + +Recursively delete all XMP-crss information from images in a directory. + +=item exiftool '-ThumbnailImageE<lt>=thumb.jpg' dst.jpg + +Set the thumbnail image from specified file (Note: The quotes are necessary +to prevent shell redirection). + +=item exiftool '-JpgFromRawE<lt>=%d%f_JFR.JPG' -ext NEF -r . + +Recursively write JPEG images with filenames ending in C<_JFR.JPG> to the +JpgFromRaw tag of like-named files with extension C<.NEF> in the current +directory. (This is the inverse of the C<-JpgFromRaw> command of the +L</READING EXAMPLES> section above.) + +=item exiftool -DateTimeOriginal-='0:0:0 1:30:0' dir + +Adjust original date/time of all images in directory C<dir> by subtracting +one hour and 30 minutes. (This is equivalent to C<-DateTimeOriginal-=1.5>. +See L<Image::ExifTool::Shift.pl|Image::ExifTool::Shift.pl> for details.) + +=item exiftool -createdate+=3 -modifydate+=3 a.jpg b.jpg + +Add 3 hours to the CreateDate and ModifyDate timestamps of two images. + +=item exiftool -AllDates+=1:30 -if '$make eq E<34>CanonE<34>' dir + +Shift the values of DateTimeOriginal, CreateDate and ModifyDate forward by 1 +hour and 30 minutes for all Canon images in a directory. (The AllDates tag +is provided as a shortcut for these three tags, allowing them to be accessed +via a single tag.) + +=item exiftool -xmp:city=Kingston image1.jpg image2.nef + +Write a tag to the XMP group of two images. (Without the C<xmp:> this tag +would get written to the IPTC group since C<City> exists in both, and IPTC +is preferred by default.) + +=item exiftool -LightSource-='Unknown (0)' dst.tiff + +Delete C<LightSource> tag only if it is unknown with a value of 0. + +=item exiftool -whitebalance-=auto -WhiteBalance=tung dst.jpg + +Set C<WhiteBalance> to C<Tungsten> only if it was previously C<Auto>. + +=item exiftool -comment-= -comment='new comment' a.jpg + +Write a new comment only if the image doesn't have one already. + +=item exiftool -o %d%f.xmp dir + +Create XMP meta information data files for all images in C<dir>. + +=item exiftool -o test.xmp -owner=Phil -title='XMP File' + +Create an XMP data file only from tags defined on the command line. + +=item exiftool '-ICC_Profile<=%d%f.icc' image.jpg + +Write ICC_Profile to an image from a C<.icc> file of the same name. + +=item exiftool -hierarchicalkeywords='{keyword=one,children={keyword=B}}' + +Write structured XMP information. See L<https://exiftool.org/struct.html> +for more details. + +=item exiftool -trailer:all= image.jpg + +Delete any trailer found after the end of image (EOI) in a JPEG file. A +number of digital cameras store a large PreviewImage after the JPEG EOI, and +the file size may be reduced significantly by deleting this trailer. See +the L<JPEG Tags documentation|Image::ExifTool::TagNames/JPEG Tags> for a +list of recognized JPEG trailers. + +=back + +=head1 COPYING EXAMPLES + +These examples demonstrate the ability to copy tag values between files. + +=over 5 + +=item exiftool -tagsFromFile src.cr2 dst.jpg + +Copy the values of all writable tags from C<src.cr2> to C<dst.jpg>, writing +the information to same-named tags in the preferred groups. + +=item exiftool -TagsFromFile src.jpg -all:all dst.jpg + +Copy the values of all writable tags from C<src.jpg> to C<dst.jpg>, +preserving the original tag groups. + +=item exiftool -all= -tagsfromfile src.jpg -exif:all dst.jpg + +Erase all meta information from C<dst.jpg> image, then copy EXIF tags from +C<src.jpg>. + +=item exiftool -exif:all= -tagsfromfile @ -all:all -unsafe bad.jpg + +Rebuild all EXIF meta information from scratch in an image. This technique +can be used in JPEG images to repair corrupted EXIF information which +otherwise could not be written due to errors. The C<Unsafe> tag is a +shortcut for unsafe EXIF tags in JPEG images which are not normally copied. +See the L<tag name documentation|Image::ExifTool::TagNames> for more details +about unsafe tags. + +=item exiftool -Tagsfromfile a.jpg out.xmp + +Copy meta information from C<a.jpg> to an XMP data file. If the XMP data +file C<out.xmp> already exists, it will be updated with the new information. +Otherwise the XMP data file will be created. Only metadata-only files may +be created like this (files containing images may be edited but not +created). See L</WRITING EXAMPLES> above for another technique to generate +XMP files. + +=item exiftool -tagsFromFile a.jpg -XMP:All= -ThumbnailImage= -m b.jpg + +Copy all meta information from C<a.jpg> to C<b.jpg>, deleting all XMP +information and the thumbnail image from the destination. + +=item exiftool -TagsFromFile src.jpg -title -author=Phil dst.jpg + +Copy title from one image to another and set a new author name. + +=item exiftool -TagsFromFile a.jpg -ISO -TagsFromFile b.jpg -comment +dst.jpg + +Copy ISO from one image and Comment from another image to a destination +image. + +=item exiftool -tagsfromfile src.jpg -exif:all --subifd:all dst.jpg + +Copy only the EXIF information from one image to another, excluding SubIFD +tags. + +=item exiftool '-FileModifyDateE<lt>DateTimeOriginal' dir + +Use the original date from the meta information to set the same file's +filesystem modification date for all images in a directory. (Note that +C<-TagsFromFile @> is assumed if no other B<-TagsFromFile> is specified when +redirecting information as in this example.) + +=item exiftool -TagsFromFile src.jpg '-xmp:allE<lt>all' dst.jpg + +Copy all possible information from C<src.jpg> and write in XMP format to +C<dst.jpg>. + +=item exiftool '-Description<${FileName;s/\.[^.]*$//}' dir + +Set the image Description from the file name after removing the extension. +This example uses the L</Advanced formatting feature> to perform a +substitution operation to remove the last dot and subsequent characters from +the file name. + +=item exiftool -@ iptc2xmp.args -iptc:all= a.jpg + +Translate IPTC information to XMP with appropriate tag name conversions, and +delete the original IPTC information from an image. This example uses +iptc2xmp.args, which is a file included with the ExifTool distribution that +contains the required arguments to convert IPTC information to XMP format. +Also included with the distribution are xmp2iptc.args (which performs the +inverse conversion) and a few more .args files for other conversions between +EXIF, IPTC and XMP. + +=item exiftool -tagsfromfile %d%f.CR2 -r -ext JPG dir + +Recursively rewrite all C<JPG> images in C<dir> with information copied from +the corresponding C<CR2> images in the same directories. + +=item exiftool '-keywords+E<lt>make' image.jpg + +Add camera make to list of keywords. + +=item exiftool '-commentE<lt>ISO=$exif:iso Exposure=${shutterspeed}' dir + +Set the Comment tag of all images in C<dir> from the values of the EXIF:ISO +and ShutterSpeed tags. The resulting comment will be in the form "ISO=100 +Exposure=1/60". + +=item exiftool -TagsFromFile src.jpg -icc_profile dst.jpg + +Copy ICC_Profile from one image to another. + +=item exiftool -TagsFromFile src.jpg -all:all dst.mie + +Copy all meta information in its original form from a JPEG image to a MIE +file. The MIE file will be created if it doesn't exist. This technique can +be used to store the metadata of an image so it can be inserted back into +the image (with the inverse command) later in a workflow. + +=item exiftool -o dst.mie -all:all src.jpg + +This command performs exactly the same task as the command above, except +that the B<-o> option will not write to an output file that already exists. + +=item exiftool -b -jpgfromraw -w %d%f_%ue.jpg -execute -b -previewimage -w +%d%f_%ue.jpg -execute -tagsfromfile @ -srcfile %d%f_%ue.jpg +-overwrite_original -common_args --ext jpg DIR + +[Advanced] Extract JpgFromRaw or PreviewImage from all but JPG files in DIR, +saving them with file names like C<image_EXT.jpg>, then add all meta +information from the original files to the extracted images. Here, the +command line is broken into three sections (separated by B<-execute> +options), and each is executed as if it were a separate command. The +B<-common_args> option causes the C<--ext jpg DIR> arguments to be applied +to all three commands, and the B<-srcfile> option allows the extracted JPG +image to be the source file for the third command (whereas the RAW files are +the source files for the other two commands). + +=back + +=head1 RENAMING EXAMPLES + +By writing the C<FileName> and C<Directory> tags, files are renamed and/or +moved to new directories. This can be particularly useful and powerful for +organizing files by date when combined with the B<-d> option. New +directories are created as necessary, but existing files will not be +overwritten. The format codes %d, %f and %e may be used in the new file +name to represent the directory, name and extension of the original file, +and %c may be used to add a copy number if the file already exists (see the +B<-w> option for details). Note that if used within a date format string, +an extra '%' must be added to pass these codes through the date/time parser. +(And further note that in a Windows batch file, all '%' characters must also +be escaped, so in this extreme case '%%%%f' is necessary to pass a simple +'%f' through the two levels of parsing.) See +L<https://exiftool.org/filename.html> for additional documentation and +examples. + +=over 5 + +=item exiftool -filename=new.jpg dir/old.jpg + +Rename C<old.jpg> to C<new.jpg> in directory C<dir>. + +=item exiftool -directory=%e dir + +Move all files from directory C<dir> into directories named by the original +file extensions. + +=item exiftool '-Directory<DateTimeOriginal' -d %Y/%m/%d dir + +Move all files in C<dir> into a directory hierarchy based on year, month and +day of C<DateTimeOriginal>. eg) This command would move the file +C<dir/image.jpg> with a C<DateTimeOriginal> of C<2005:10:12 16:05:56> to +C<2005/10/12/image.jpg>. + +=item exiftool -o . '-Directory<DateTimeOriginal' -d %Y/%m/%d dir + +Same effect as above except files are copied instead of moved. + +=item exiftool '-filename<%f_${model;}.%e' dir + +Rename all files in C<dir> by adding the camera model name to the file name. +The semicolon after the tag name inside the braces causes characters which +are invalid in Windows file names to be deleted from the tag value (see the +L</Advanced formatting feature> for an explanation). + +=item exiftool '-FileName<CreateDate' -d %Y%m%d_%H%M%S%%-c.%%e dir + +Rename all images in C<dir> according to the C<CreateDate> date and time, +adding a copy number with leading '-' if the file already exists (C<%-c>), +and preserving the original file extension (C<%e>). Note the extra '%' +necessary to escape the filename codes (C<%c> and C<%e>) in the date format +string. + +=item exiftool -r '-FileName<CreateDate' -d %Y-%m-%d/%H%M_%%f.%%e dir + +Both the directory and the filename may be changed together via the +C<FileName> tag if the new C<FileName> contains a '/'. The example above +recursively renames all images in a directory by adding a C<CreateDate> +timestamp to the start of the filename, then moves them into new directories +named by date. + +=item exiftool '-FileName<${CreateDate}_$filenumber.jpg' -d %Y%m%d -ext jpg . + +Set the filename of all JPG images in the current directory from the +CreateDate and FileNumber tags, in the form "20060507_118-1861.jpg". + +=back + +=head1 GEOTAGGING EXAMPLES + +ExifTool implements geotagging from GPS log files via 3 special tags: Geotag +(which for convenience is also implemented as an exiftool option), Geosync +and Geotime. The examples below highlight some geotagging features. See +L<https://exiftool.org/geotag.html> for additional documentation. (Note +that geotagging from known GPS coordinates is done by writing the +L<GPS tags|Image::ExifTool::TagNames/GPS Tags> directly rather than using +the B<-geotag> option.) + +=over 5 + +=item exiftool -geotag track.log a.jpg + +Geotag an image (C<a.jpg>) from position information in a GPS track log +(C<track.log>). Since the C<Geotime> tag is not specified, the value of +DateTimeOriginal is used for geotagging. Local system time is assumed +unless DateTimeOriginal contains a timezone. + +=item exiftool -geotag t.log -geotime='2009:04:02 13:41:12-05:00' a.jpg + +Geotag an image with the GPS position for a specific time. + +=item exiftool -geotag log.gpx '-xmp:geotimeE<lt>createdate' dir + +Geotag all images in directory C<dir> with XMP tags instead of EXIF tags, +based on the image CreateDate. + +=item exiftool -geotag a.log -geosync=-20 dir + +Geotag images in directory C<dir>, accounting for image timestamps which +were 20 seconds ahead of GPS. + +=item exiftool -geotag a.log -geosync=1.jpg -geosync=2.jpg dir + +Geotag images using time synchronization from two previously geotagged images +(1.jpg and 2.jpg), synchronizing the image and GPS times using a linear time +drift correction. + +=item exiftool -geotag a.log '-geotimeE<lt>${createdate}+01:00' dir + +Geotag images in C<dir> using CreateDate with the specified timezone. If +CreateDate already contained a timezone, then the timezone specified on the +command line is ignored. + +=item exiftool -geotag= a.jpg + +Delete GPS tags which may have been added by the geotag feature. Note that +this does not remove all GPS tags -- to do this instead use C<-gps:all=>. + +=item exiftool -xmp:geotag= a.jpg + +Delete XMP GPS tags which were added by the geotag feature. + +=item exiftool -xmp:geotag=track.log a.jpg + +Geotag an image with XMP tags, using the time from DateTimeOriginal. + +=item exiftool -geotag a.log -geotag b.log -r dir + +Combine multiple track logs and geotag an entire directory tree of images. + +=item exiftool -geotag 'tracks/*.log' -r dir + +Read all track logs from the C<tracks> directory. + +=item exiftool -p gpx.fmt dir > out.gpx + +Generate a GPX track log from all images in directory C<dir>. This example +uses the C<gpx.fmt> file included in the full ExifTool distribution package +and assumes that the images in C<dir> have all been previously geotagged. + +=back + +=head1 PIPING EXAMPLES + +=over 5 + +=item cat a.jpg | exiftool - + +Extract information from stdin. + +=item exiftool image.jpg -thumbnailimage -b | exiftool - + +Extract information from an embedded thumbnail image. + +=item cat a.jpg | exiftool -iptc:keywords+=fantastic - > b.jpg + +Add an IPTC keyword in a pipeline, saving output to a new file. + +=item curl -s http://a.domain.com/bigfile.jpg | exiftool -fast - + +Extract information from an image over the internet using the cURL utility. +The B<-fast> option prevents exiftool from scanning for trailer information, +so only the meta information header is transferred. + +=item exiftool a.jpg -thumbnailimage -b | exiftool -comment=wow - | +exiftool a.jpg -thumbnailimage'<=-' + +Add a comment to an embedded thumbnail image. (Why anyone would want to do +this I don't know, but I've included this as an example to illustrate the +flexibility of ExifTool.) + +=back + +=head1 INTERRUPTING EXIFTOOL + +Interrupting exiftool with a CTRL-C or SIGINT will not result in partially +written files or temporary files remaining on the hard disk. The exiftool +application traps SIGINT and defers it until the end of critical processes +if necessary, then does a proper cleanup before exiting. + +=head1 EXIT STATUS + +The exiftool application exits with a status of 0 on success, or 1 if an +error occurred, or 2 if all files failed the B<-if> condition (for any of +the commands if B<-execute> was used). + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey + +This is free software; you can redistribute it and/or modify it under the +same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagNames(3pm)|Image::ExifTool::TagNames>, +L<Image::ExifTool::Shortcuts(3pm)|Image::ExifTool::Shortcuts>, +L<Image::ExifTool::Shift.pl|Image::ExifTool::Shift.pl> + +=cut + +#------------------------------------------------------------------------------ +# end diff --git a/ExifTool/fmt_files/gpx.fmt b/ExifTool/fmt_files/gpx.fmt new file mode 100644 index 0000000..fb2a5db --- /dev/null +++ b/ExifTool/fmt_files/gpx.fmt @@ -0,0 +1,39 @@ +#------------------------------------------------------------------------------ +# File: gpx.fmt +# +# Description: Example ExifTool print format file to generate a GPX track log +# +# Usage: exiftool -p gpx.fmt -ee3 FILE [...] > out.gpx +# +# Requires: ExifTool version 10.49 or later +# +# Revisions: 2010/02/05 - P. Harvey created +# 2018/01/04 - PH Added IF to be sure position exists +# 2018/01/06 - PH Use DateFmt function instead of -d option +# 2019/10/24 - PH Preserve sub-seconds in GPSDateTime value +# +# Notes: 1) Input file(s) must contain GPSLatitude and GPSLongitude. +# 2) The -ee3 option is to extract the full track from video files. +# 3) The -fileOrder option may be used to control the order of the +# generated track points when processing multiple files. +# 4) Coordinates are written at full resolution. To change this, +# remove the "#" from the GPSLatitude/Longitude tag names below +# and use the -c option to set the desired precision. +#------------------------------------------------------------------------------ +#[HEAD]<?xml version="1.0" encoding="utf-8"?> +#[HEAD]<gpx version="1.0" +#[HEAD] creator="ExifTool $ExifToolVersion" +#[HEAD] xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +#[HEAD] xmlns="http://www.topografix.com/GPX/1/0" +#[HEAD] xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd"> +#[HEAD]<trk> +#[HEAD]<number>1</number> +#[HEAD]<trkseg> +#[IF] $gpslatitude $gpslongitude +#[BODY]<trkpt lat="$gpslatitude#" lon="$gpslongitude#"> +#[BODY] <ele>$gpsaltitude#</ele> +#[BODY] <time>${gpsdatetime#;my ($ss)=/\.\d+/g;DateFmt("%Y-%m-%dT%H:%M:%SZ");s/Z/${ss}Z/ if $ss}</time> +#[BODY]</trkpt> +#[TAIL]</trkseg> +#[TAIL]</trk> +#[TAIL]</gpx> diff --git a/ExifTool/fmt_files/gpx_wpt.fmt b/ExifTool/fmt_files/gpx_wpt.fmt new file mode 100644 index 0000000..e05ef6c --- /dev/null +++ b/ExifTool/fmt_files/gpx_wpt.fmt @@ -0,0 +1,43 @@ +#------------------------------------------------------------------------------ +# File: gpx_wpt.fmt +# +# Description: Example ExifTool print format file to generate GPX waypoints +# with pictures +# +# Usage: exiftool -p gpx_wpt.fmt -ee3 FILE [...] > out.gpx +# +# Requires: ExifTool version 10.49 or later +# +# Revisions: 2010/03/13 - Peter Grimm created +# 2018/01/04 - PH Added IF to be sure position exists +# 2018/01/06 - PH Use DateFmt function instead of -d option +# 2019/10/24 - PH Preserve sub-seconds in GPSDateTime value +# +# Notes: 1) Input file(s) must contain GPSLatitude and GPSLongitude. +# 2) The -ee3 option is to extract the full track from video files. +# 3) The -fileOrder option may be used to control the order of the +# generated track points when processing multiple files. +# 4) Coordinates are written at full resolution. To change this, +# remove the "#" from the GPSLatitude/Longitude tag names below +# and use the -c option to set the desired precision. +#------------------------------------------------------------------------------ +#[HEAD]<?xml version="1.0" encoding="UTF-8" standalone="no" ?> +#[HEAD]<gpx version="1.1" +#[HEAD] creator="ExifTool $ExifToolVersion" +#[HEAD] xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +#[HEAD] xmlns="http://www.topografix.com/GPX/1/1" +#[HEAD] xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> +#[IF] $gpslatitude $gpslongitude +#[BODY]<wpt lat="$gpslatitude#" lon="$gpslongitude#"> +#[BODY] <ele>$gpsaltitude#</ele> +#[BODY] <time>${gpsdatetime#;my ($ss)=/\.\d+/g;DateFmt("%Y-%m-%dT%H:%M:%SZ");s/Z/${ss}Z/ if $ss}</time> +#[BODY] <name>$filename</name> +#[BODY] <link href="$directory/$filename"/> +#[BODY] <sym>Scenic Area</sym> +#[BODY] <extensions> +#[BODY] <gpxx:WaypointExtension xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"> +#[BODY] <gpxx:DisplayMode>SymbolAndName</gpxx:DisplayMode> +#[BODY] </gpxx:WaypointExtension> +#[BODY] </extensions> +#[BODY]</wpt> +#[TAIL]</gpx> \ No newline at end of file diff --git a/ExifTool/fmt_files/kml.fmt b/ExifTool/fmt_files/kml.fmt new file mode 100644 index 0000000..cbff751 --- /dev/null +++ b/ExifTool/fmt_files/kml.fmt @@ -0,0 +1,62 @@ +#------------------------------------------------------------------------------ +# File: kml.fmt +# +# Description: Example ExifTool print format file for generating a +# Google Earth KML file from a collection of geotagged images +# +# Usage: exiftool -p kml.fmt -r DIR [...] > out.kml +# +# Requires: ExifTool version 10.41 or later +# +# Revisions: 2010/02/05 - P. Harvey created +# 2013/02/05 - PH Fixed camera icon to work with new Google Earth +# 2017/02/02 - PH Organize into folders based on file directory +# 2018/01/04 - PH Added IF to be sure position exists +# 2020/01/11 - F. Kotov Limited image preview size to 500px +# +# Notes: 1) Input files must contain GPSLatitude and GPSLongitude. +# 2) Add the -ee3 option to extract the full track from video files. +# 3) For Google Earth to be able to find the images, the input +# images must be specified using relative paths, and "out.kml" +# must stay in the same directory as where the command was run. +# 4) Google Earth is picky about the case of the image file extension, +# and may not be able to display the image if an upper-case +# extension is used. +# 5) The -fileOrder option may be used to control the order of the +# generated placemarks when processing multiple files. +# 6) The "0" in the coordinates line may be changed to "$gpsaltitude#" +# and the altitudeMode may be changed to "absolute" to store +# altitude information if it exists in the track log. +#------------------------------------------------------------------------------ +#[HEAD]<?xml version="1.0" encoding="UTF-8"?> +#[HEAD]<kml xmlns="http://earth.google.com/kml/2.0"> +#[HEAD] <Document> +#[HEAD] <name>My Photos</name> +#[HEAD] <open>1</open> +#[HEAD] <Style id="Photo"> +#[HEAD] <IconStyle> +#[HEAD] <Icon> +#[HEAD] <href>http://maps.google.com/mapfiles/kml/pal4/icon38.png</href> +#[HEAD] <scale>1.0</scale> +#[HEAD] </Icon> +#[HEAD] </IconStyle> +#[HEAD] </Style> +#[SECT] <Folder> +#[SECT] <name>$main:directory</name> +#[SECT] <open>0</open> +#[IF] $gpslatitude $gpslongitude +#[BODY] <Placemark> +#[BODY] <description><![CDATA[<img src='$main:directory/$main:filename' +#[BODY] style='max-width:500px;max-height:500px;'> ]]> +#[BODY] </description> +#[BODY] <Snippet/> +#[BODY] <name>$filename</name> +#[BODY] <styleUrl>#Photo</styleUrl> +#[BODY] <Point> +#[BODY] <altitudeMode>clampedToGround</altitudeMode> +#[BODY] <coordinates>$gpslongitude#,$gpslatitude#,0</coordinates> +#[BODY] </Point> +#[BODY] </Placemark> +#[ENDS] </Folder> +#[TAIL] </Document> +#[TAIL]</kml> diff --git a/ExifTool/fmt_files/kml_track.fmt b/ExifTool/fmt_files/kml_track.fmt new file mode 100644 index 0000000..9d15241 --- /dev/null +++ b/ExifTool/fmt_files/kml_track.fmt @@ -0,0 +1,49 @@ +#------------------------------------------------------------------------------ +# File: kml_track.fmt +# +# Description: Example ExifTool print format file for generating a +# track in Google Earth KML format from a collection of +# geotagged images or timed GPS from video files +# +# Usage: From a collection of images: +# +# exiftool -p kml_track.fmt -r DIR [...] > out.kml +# +# From video files: +# +# exiftool -p kml_track.fmt -ee3 FILEorDIR [...] > out.kml +# +# Requires: ExifTool version 10.41 or later +# +# Revisions: 2019/10/29 - P. Harvey created +# +# Notes: 1) Input files must contain GPSLatitude and GPSLongitude. +# 2) The -fileOrder option may be used to control the order of the +# waypoints when processing multiple still-image files, or the +# order of the tracks when processing multiple video files. +# 3) The "0" in the BODY line below may be changed to "$gpsaltitude#" +# and the altitudeMode may be changed to "absolute" to store +# altitude information if it exists in the track log. +#------------------------------------------------------------------------------ +#[HEAD]<?xml version="1.0" encoding="UTF-8"?> +#[HEAD]<kml xmlns="http://earth.google.com/kml/2.0"> +#[HEAD] <Document> +#[HEAD] <name>My Track</name> +#[HEAD] <open>1</open> +#[SECT] <Placemark> +#[SECT] <name>${main:directory;$_=$self->GetValue('FileName') if $self->Options('ExtractEmbedded')}</name> +#[SECT] <Style> +#[SECT] <LineStyle> +#[SECT] <color>#ff4499ff</color> +#[SECT] <width>3</width> +#[SECT] </LineStyle> +#[SECT] </Style> +#[SECT] <LineString> +#[SECT] <altitudeMode>clampToGround</altitudeMode> +#[SECT] <coordinates> +#[BODY]$gpslongitude#,$gpslatitude#,0 +#[ENDS] </coordinates> +#[ENDS] </LineString> +#[ENDS] </Placemark> +#[TAIL] </Document> +#[TAIL]</kml> diff --git a/ExifTool/html/ExifTool.html b/ExifTool/html/ExifTool.html new file mode 100644 index 0000000..e7e2b27 --- /dev/null +++ b/ExifTool/html/ExifTool.html @@ -0,0 +1,2399 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd"> +<html> +<head> +<title>Image::ExifTool + + + + +

The Image::ExifTool Perl Library Module

+ +

Description

+ +

The Image::ExifTool library provides a set of Perl modules to read and +write meta information in a wide variety of image, audio, video and document +files. This document details the public methods of the ExifTool API.

+ +

Methods

+ +

All ExifTool features are accessed through the methods of the public +interface listed below. Other Image::ExifTool methods and modules should not be +accessed directly because their interface may change with future versions.

+ +

The ExifTool methods should never die or issue a warning to STDERR if called +with the proper arguments (with the exception of +SetNewValue which may send an error message to +STDERR, but only when called in scalar context). Error and warning messages +that occur during processing are stored in the values of the Error and Warning +tags, and are accessible via the GetValue method to +retrieve a single Error or Warning message, or GetInfo to +retrieve any number of them.

+ +

The ExifTool methods are not thread safe.

+ +
+ + + + + +
+ +


Using ExifTool

+ +

The ExifTool module may be used by simply calling the +ImageInfo function:

+ +
+use Image::ExifTool qw(:Public);
+my $info = ImageInfo('image.jpg');
+
+ +

or in a more object-oriented fashion, by creating an ExifTool object:

+ +
+use Image::ExifTool;
+my $exifTool = Image::ExifTool->new;
+my $info = $exifTool->ImageInfo('image.jpg');
+
+ +

The object-oriented method allows more flexibility, but is slightly more +complicated. You choose the method that you prefer.

+ +

The $info value returned by ImageInfo in the above +examples is a reference to a hash containing the tag/value pairs. Here is a +simplified example which prints out this information:

+ +
+foreach (keys %$info) {
+    print "$_ => $$info{$_}\n";
+}
+
+ +

See ImageInfo for a more detailed description of the +info hash entries.

+ +

And the technique for writing meta information is equally simple:

+ +
+use Image::ExifTool;
+my $exifTool = Image::ExifTool->new;
+$exifTool->SetNewValue(Author => 'Phil Harvey');
+$exifTool->WriteInfo('image.jpg','modified_image.jpg');
+
+ +

Configuration

+ +

User-defined tags can be added via the ExifTool configuration file, or by +defining the %Image::ExifTool::UserDefined hash before calling any ExifTool +functions. See "ExifTool_config" in the ExifTool +distribution for more details.

+ +

By default ExifTool looks for a configuration file named ".ExifTool_config" +first in your home directory, then in the directory of the application script, +but a different directory may be specified by setting the EXIFTOOL_HOME +environment variable, or a different file may be specified by setting the +ExifTool "configFile" variable before using Image::ExifTool. For +example:

+ +
+BEGIN { $Image::ExifTool::configFile = '/Users/phil/myconfig.cfg' }
+use Image::ExifTool;
+
+ +

The configuration feature may also be disabled by setting +"configFile" to an empty string:

+ +
+BEGIN { $Image::ExifTool::configFile = '' }
+use Image::ExifTool;
+
+ +

ImageInfo

+ +

Read image file and return meta information. This is the one-step function for +retrieving meta information from an image. Internally, +ImageInfo calls ExtractInfo +to extract data from the image, GetInfo to generate the +information hash, and GetTagList for the returned tag +list.

+ +
+ + + +
PrototypeImageInfo($;@)
Inputs0) [optional] ExifTool object reference +
1) File name, file reference or scalar reference +
2-N) [optional] list of tag names to find (or tag list reference or + options hash reference, see below) +
ReturnsReference to hash of tag-key/value pairs
+ +

Examples:

+ +
Non object-oriented example showing use of options and returning tag list: +
+use Image::ExifTool qw(ImageInfo);
+my @ioTagList;
+my $info;
+
+$info = ImageInfo('image.jpg', \@ioTagList, {Sort => 'Group0'});
+
+ +
Object-oriented example to read from a file that is already open: +
+my $exifTool = Image::ExifTool->new;
+
+$info = $exifTool->ImageInfo(\*FILE_PT, 'Aperture', 'ShutterSpeed', 'ISO');
+
+ +
Extract information from an image in memory: +
+$info = $exifTool->ImageInfo(\$imageData);
+
+ +
Extract information from an embedded thumbnail image: +
+$info = ImageInfo('image.jpg', 'thumbnailimage');
+my $thumbInfo = ImageInfo($$info{ThumbnailImage});
+
+ +
Using an ExifTool object to set the options before calling +ImageInfo: +
+my $filename = shift || die "Please specify filename\n";
+my @ioTagList = qw(filename imagesize xmp:creator exif:* -ifd1:*);
+
+$exifTool->Options(Unknown => 1, DateFormat => '%H:%M:%S %a. %b. %e, %Y');
+$info = $exifTool->ImageInfo($filename, \@ioTagList);
+
+ +

Function Arguments:

+ +

ImageInfo is very flexible about the arguments +passed to it, and interprets them based on their type. It may be called with +one or more arguments. The one required argument is either a SCALAR (the image +file name), a file reference (a reference to the image file) or a SCALAR +reference (a reference to the image in memory). Other arguments are optional. +The order of the arguments is not significant, except that the first SCALAR is +taken to be the file name unless a file reference or scalar reference comes +earlier in the argument list.

+ +

Below is a more detailed explanation of how the ImageInfo +function arguments are interpreted.

+ +
+
ExifTool ref +ImageInfo may be called with an ExifTool object if +desired. Advantages of using the object-oriented form are that options may be +set before calling ImageInfo, and the object may be +used afterward to access member functions. Must be the first argument if used. + +
SCALAR +The first scalar argument is taken to be the file name unless an earlier +argument specified the image data via a file reference (file ref) or data +reference (SCALAR ref). The remaining scalar arguments are names of tags for +requested information. All tags are returned if no tags are specified. +
 
+Tag names are case-insensitive and may be prefixed by optional group names +separated by colons. A group name may begin with a family number (eg. +'1IPTC:Keywords'), to restrict matches to a specific family. In the +tag name, a '?' matches any single character and a '*' +matches zero or more characters. Thus 'GROUP:*' represents all +tags in a specific group. Wildcards may not be used in group names, with the +exception that a group name of '*' may be used to extract all +available instances of a tag regardless of the +Duplicates setting (eg. '*:WhiteBalance'). +Multiple groups may be specified (eg. 'EXIF:Time:*' extracts all +EXIF Time tags). And finally, a leading '-' indicates a tag to be +excluded (eg. '-IFD1:*'), or a trailing '#' causes the +ValueConv value to be returned for this tag. +
 
+Note that keys in the returned information hash and elements of the returned tag +list are not necessarily the same as these tag names because group names are +removed, the case may be changed, and an instance number may be added. For this +reason it is best to use either the keys of the returned hash or the elements of +the returned tag list when accessing the tag values. +
 
+See the TagNames documentation for a +complete list of ExifTool tag names. + +
File ref +A reference to an open image file. If you use this method (or a SCALAR +reference) to access information in an image, the FileName and Directory tags +will not be returned. (Also, the FileSize, FileModifyDate, FilePermissions and +FileAttributes tags will not be returned unless it is a plain file.) Image +processing begins at the current file position, and on return the file position +is unspecified. May be either a standard filehandle or a reference to a +File::RandomAccess object. +
 
+[Advanced: To allow a non-rewindable stream (eg. a network socket) to be +re-read after processing with ExifTool, first wrap the file reference in a +File::RandomAccess object, then pass this object to +ImageInfo. The File::RandomAccess object will buffer +the file if necessary, and may be used to re-read the file after +ImageInfo returns.] + +
SCALAR ref +A reference to image data in memory. + +
ARRAY ref +Reference to a list of tag names. On entry, any elements in the list are added +to the list of requested tags. On return, this list is updated to contain an +ordered list of tag keys for the returned information. +
 
+There will be 1:1 correspondence between the requested tags and the returned +tag keys only if the Duplicates option is 0 and +Sort is 'Input'. (With +Duplicates enabled, there may be more entries in the +returned list of tag keys, and with other Sort settings the +entries may not be in the same order as requested.) If a requested tag doesn't +exist, a tag key is still generated, but the tag value is undefined. +
 
+Note: Do not reuse this list in subsequent calls to +ImageInfo because it returns tag keys, not names, and +the list will grow for each call resulting in increasingly slower +performance. + +
HASH ref +Reference to a hash containing the options settings valid for this call only. +See Options documentation below for a list of available +options. Options specified as arguments to ImageInfo +take precedence over Options settings. +
+ +

Return Value:

+ +

ImageInfo returns a reference to a hash of +tag-key/value pairs. The tag keys are identifiers -- essentially case-sensitive +tag names with an appended instance number if multiple tags with the same name +were extracted from the image. Many of the ExifTool functions require a tag key +as an argument. Use GetTagName to get the tag name +for a given tag key. Note that the case of the tag names may not be the same as +requested.

+ +

Values of the returned hash are usually simple scalars, but a scalar +reference is used to indicate binary data and an array reference may be used to +indicate a list. Also, a hash reference may be returned if the +Struct option is used. Lists of values are joined by +commas into a single string only if the PrintConv option is enabled and the +ListJoin option is enabled (which are the defaults). Note that binary values +are not necessarily extracted unless specifically requested, or the Binary +option is enabled and the tag is not specifically excluded. If not extracted the +value is a reference to a string of the form "Binary data ##### +bytes".

+ +

Here is a simple example to print out the information returned by +ImageInfo:

+ +
+foreach (keys %$info) {
+    my $val = $$info{$_};
+    if (ref $val eq 'ARRAY') {
+        $val = join(', ', @$val);
+    } elsif (ref $val eq 'SCALAR') {
+        $val = '(Binary data)';
+    }
+    printf("%-24s : %s\n", $_, $val);
+}
+
+ +

which gives output like this (PrintConv enabled):

+ +
+WhiteBalance             : Auto
+FNumber                  : 3.5
+InteroperabilityOffset   : 936
+XResolution              : 72
+ISO                      : 100
+ThumbnailImage           : (Binary data)
+FlashOn                  : On
+Make                     : FUJIFILM
+ShutterSpeedValue        : 1/64
+ExposureCompensation     : 0
+Sharpness                : Soft
+ResolutionUnit           : inches
+
+ +

Notes:

+ +

ExifTool returns all values as byte strings of encoded characters. Perl wide +characters are not used. See FAQ number 10 for +details about the encodings. By default, most returned strings are encoded in +UTF-8. For these, Encode::decode_utf8() may be used to convert to a sequence of +logical Perl characters.

+ +

As well as tags representing information extracted from the image, the +following Extra tags generated by ExifTool may +be returned:

+ +
+ + + +
ExifToolVersionThe ExifTool version number
ErrorAn error message if the image could not be processed
WarningA warning message if problems were encountered +while processing the image
+ +

new

+

Create a new ExifTool object.

+

Example:

+
+my $exifTool = Image::ExifTool->new;
+
+ +

One ExifTool object may be used to process many files, so creating multiple +ExifTool objects usually is not necessary.

+

Note that ExifTool uses AUTOLOAD to load non-member methods, so any class +using Image::ExifTool as a base class must define an AUTOLOAD which calls +Image::ExifTool::DoAutoLoad(). ie)

+ +
+sub AUTOLOAD
+{
+    Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
+}
+
+ +
+The following functions require an ExifTool object as the first argument +
+ +

Options

+

Get/set ExifTool options. This function can be called to set the default +options for an ExifTool object. Options set this way are in effect for all +function calls but may be overridden by options passed as arguments to some +functions. Option names are not case sensitive, but option values are.

+

The default option values may be changed by defining a +%Image::ExifTool::UserDefined::Options hash. See the +ExifTool_config file in the full ExifTool distribution +for examples. Unless otherwise noted, a default of undef has the same +effect as a value of 0 for options with numerical values.

+
+ + + +
PrototypeOptions($$;@)
Inputs0) ExifTool object reference +
1) Parameter name (case-insensitive, see table below) +
2) [optional] Option value if specified (may be undef to clear option) +
3-N) [optional] Additional parameter/value pairs +
ReturnsPrevious value of last specified parameter
+ +

Available options:

+

Note that these API options may be also be used in the +exiftool application via the command-line +-api option.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExifTool Options
OptionDescriptionValuesDefault
BinaryFlag to extract the value data for all binary tags. + Tag values representing large binary data blocks (eg. ThumbnailImage) + are not necessarily extracted unless this option is set or the tag is + specifically requested by name.0 or 1undef
BlockExtractFlag to extract some directories + (mentioned in the Tag Name documentation) as a block. + + + +
0 =Extract as block only if tag specified by name
1 =Extract as block, and extract contained tags
2 =Extract as block without contained tags
undef
ByteOrderThe byte order for newly created EXIF segments when + writing. Note that if EXIF information already exists, the existing order is + maintained. If ByteOrder is not defined, then the order of the maker notes is + used (if they are being copied), otherwise big-endian ('MM') order is assumed. + This can also be set via the ExifByteOrder tag, + but the ByteOrder option takes precedence if both are set.'MM','II' or undefundef
CharsetCharacter set for encoding character + strings passed to/from ExifTool containing code points above U+007F. Note + that this option affects some types of information when reading/writing the + file and other types when getting/setting tag values, so it must be defined + for both types of access. Charset values listed to the right have aliases + which are given in brackets. Case is not significant. See + FAQ #10 for more information about character sets. + + + + + + + + + + + + + + + + + + + + + + +
UTF8(cp65001, UTF-8)
Latin(cp1252, Latin1)
Latin2(cp1250)
Cyrillic(cp1251, Russian)
Greek(cp1253)
Turkish(cp1254)
Hebrew(cp1255)
Arabic(cp1256)
Baltic(cp1257)
Vietnam(cp1258)
Thai(cp874)
DOSLatinUS(cp437)
DOSLatin1(cp850)
DOSCyrillic(cp866)
MacRoman(cp10000, Mac, Roman)
MacLatin2(cp10029)
MacCyrillic(cp10007)
MacGreek(cp10006)
MacTurkish(cp10081)
MacRomanian(cp10010)
MacIceland(cp10079)
MacCroatian(cp10082)
'UTF8'
CharsetEXIFInternal encoding to use for stored + EXIF "ASCII" string values. May also be set to undef to pass through EXIF + "ASCII" values without recoding. Set to "UTF8" to conform with the MWG + recommendation.(see Charset option)
or undef
undef
CharsetFileNameExternal character set + used when specifying file names. When set in Windows, this triggers use of + Windows Unicode file library routines (requires Win32API::File). May also + be set to an empty string to avoid "encoding not specified" warnings on Windows.(see Charset option)
or undef
undef
CharsetID3Internal encoding to assume for ID3v1 strings. By + the specification ID3v1 strings should be encoded in ISO 8859-1 (essentially + 'Latin'), but some applications may use local encoding instead. This option + allows different encodings to be specified.(see Charset option)'Latin'
CharsetIPTCFallback internal IPTC character set to assume if IPTC information + contains no CodedCharacterSet tag.(see Charset option)'Latin'
CharsetPhotoshopInternal encoding to assume for Photoshop IRB resource names.(see Charset option)'Latin'
CharsetQuickTimeInternal encoding to assume for QuickTime + strings stored with an unspecified encoding.(see Charset option)'MacRoman'
CharsetRIFFInternal encoding to assume for strings in RIFF + metadata (eg. AVI and WAV files). The default value of 0 assumes 'Latin' encoding unless otherwise + specified by the RIFF CSET chunk. Set to undef to pass through strings without recoding.(see Charset option)
or 0 or undef
0
CompactComma-delimited list of settings for writing compact XMP. + Note that 'NoPadding' effects only embedded XMP since padding is never written for + stand-alone XMP files. Also note that 'OneDesc' is not recommended when writing + XMP larger than 64 kB to a JPG file because it interferes with ExifTool's technique + of splitting off large rdf:Description elements into the extended XMP. Case is not + significant for any of these options. Some options have aliases (shown in brackets). + + + + + + + + +
NoPadding =Avoid 2 kB of recommended padding at end of XMP (NoPad)
NoIndent =Avoid spaces to indent lines for readability (NoSpace, NoSpaces)
NoNewline =Avoid unnecessary newlines (NoNewlines)
Shorthand =Use XMP Shorthand format
OneDesc =Combine XMP properties into a single rdf:Description (OneDescr)
AllSpace ='NoPadding,NoIndent,NoNewline'
AllFormat ='Shorthand,OneDesc'
All ='AllSpace,AllFormat'
undef
CompositeFlag to generate Composite tags when extracting information.0 or 11
CompressFlag to write new values in compressed + format if possible. Has no effect unless the relevant compression library is + available. Valid when writing metadata to PNG, JXL or MIE images. Setting this + to zero causes JXL metadata to be rewritten as uncompressed when edited.0, 1 or undefundef
CoordFormatSpecify output format for GPS coordinates.A printf-style format string with specifiers + for degrees, minutes and seconds in that order, however minutes and seconds + may be omitted. If the hemisphere is known, a reference direction (N, S, E + or W) is appended to each printed coordinate, but adding a '+' + to the first format specifier (eg. '%+.6f') prints a signed + coordinate instead. The default for reading is equivalent to a format string + of q{%d deg %d' %.2f"}, but to avoid a loss + of precision the default for copying tags with + SetNewValuesFromFile is + q{%d %d %.8f}. + undef
DateFormatOutput format for date/time values. If date can not + be converted, value is left unchanged unless the StrictDate + option is set. Timezones are ignored. The inversion conversion (ie. when calling + SetNewValue) is performed only if POSIX::strptime + or Time::Piece is installed.See strftime manpage for details. + The default setting of undef causes date/time values to remain in standard EXIF + format (similar to a DateFormat of "%Y:%m:%d %H:%M:%S").undef
DuplicatesFlag to return values from + tags with duplicate names when extracting information.0 or 11
EscapeEscape special characters in extracted values for + HTML or XML. Also unescapes HTML or XML character entities in input values + passed to SetNewValue. HTML, XML or undefundef
ExcludeExclude specified tags when extracting information. Note that this + option is applied after all of the tags have already been loaded into + memory (so different tags may be excluded in subsequent calls to + GetInfo). See the IgnoreTags + option to save memory by not loading the tags in the first place.Tag name or reference to a list of tag names to + exclude. Case is not significant. Tags may also be excluded by preceding + their name with a '-' in the arguments to ImageInfo.undef
ExtendedXMPThis setting affects the + reading and editing of extended XMP in JPEG images. According to the XMP + specification, extended XMP is only valid if it has the GUID specified by + the HasExtendedXMP tag. ExifTool + 9.95 and earlier would read extended XMP regardless of GUID, but with the + addition of this option in version 9.96 the default behaviour was changed to + conform with the XMP specification (to read only extended XMP with the + proper GUID). This option should be set to 2 to emulate pre-9.96 behaviour + and read all extended XMP. It may also be set to a GUID to read a specific + extended XMP, or 0 to ignore extended XMP entirely. + + + + +
0 =Ignore extended XMP
1 =Valid GUID only
2 =Any GUID
guid =Specific GUID
1
ExtractEmbeddedFlag to extract information from embedded documents in EPS files, + embedded EPS information and JPEG and Jpeg2000 images in PDF files, embedded + MPF images in JPEG and MPO files, metadata after the first Cluster in MKV + files, timed metadata in videos, all frames of a multipart EXR image, and + the resource fork of Mac OS files. A setting of 2 also causes the H264 + video stream in MP4 files to be parsed until the first SEI message is + decoded, or 3 to parse the entire H264 stream in MP4 videos and the entire + M2TS file to look for any unlisted program containing GPS metadata.0, 1, 2 or 3undef
FastScanFlag to increase speed when reading files by avoiding extraction of some + types of metadata. With this option set to 1, ExifTool will not scan to the + end of a JPEG image to check for an AFCP, CanonVRD, FotoStation, + PhotoMechanic, MIE or PreviewImage trailer. This also stops the parsing + after the first comment in GIF images, and at the audio/video data of + RIFF-format files (AVI, WAV, etc), so any trailing metadata (eg. XMP written + by some utilities) may be missed. Also disables input buffering for some + types of files to reduce memory usage when reading from a non-seekable + stream, and bypasses CRC validation for speed when writing PNG files. When + combined with the ScanForXMP option, prevents scanning for XMP in recognized + file types. With a value of 2, ExifTool will also avoid extracting any EXIF + MakerNote information, and will stop processing at the IDAT chunk of PNG + images and the mdat atom in QuickTime-format files. (By the PNG + specification, metadata is allowed after IDAT, but ExifTool always writes it + before because some utilities will ignore it otherwise.) When set to 3 or + higher, only pseudo system tags and FileType are generated. For 3, the file + header is read to provide an educated guess at FileType. For 4, the file is + not read at all and FileType is determined based on the file's extension. + For 5, generation of Composite tags is also disabled (like setting + Composite to 0).0, 1, 2, 3, 4 or 5undef
FilterPerl expression used to filter all returned tag values. Applies to + PrintConv values only. List items are filtered individually.Expression to act on the value of the Perl default variable ($_), + changing the value of this variable as required. The current ExifTool + object may be accessed through $self. The value is not changed if $_ is + set to undef.undef
FilterWPerl expression used to filter PrintConv values when writing.Expression to act on the value of the Perl default variable ($_), + changing the value of this variable as required. The current ExifTool + object may be accessed through $self. The tag is not written if $_ is + set to undef.undef
FixBaseFix maker notes base offset. Allows values to be extracted from maker notes + which have been corrupted by editing with 3rd party software.An integer specifying a value to be added to the + maker notes base offset, or the empty string ('') for ExifTool to take its + best guess at the correct base.undef
GeoMaxIntSecsMaximum interpolation time in seconds for + geotagging. Geotagging is treated as an extrapolation if the Geotime value + lies between two fixes in the same track which are separated by a number of + seconds greater than this. Otherwise, the coordinates are calculated as a + linear interpolation between the nearest fixes on either side of the Geotime + value. Set to 0 to disable interpolation and use the coordinates of the + nearest fix instead (provided it is within GeoMaxExtSecs, otherwise + geotagging fails).A floating point number1800
GeoMaxExtSecsMaximum extrapolation time in seconds for + geotagging. Geotagging fails if the Geotime value lies outside a GPS track + by a number of seconds greater than this. Otherwise, for an extrapolation + the coordinates of the nearest fix are taken (ie. it is assumed that + you weren't moving during this period).A floating point number1800
GeoMaxHDOPMaximum Horizontal (2D) Dilution Of Precision + for geotagging. GPS fixes are ignored if the HDOP is greater than this.A floating point number, or undefundef
GeoMaxPDOPMaximum Position (3D) Dilution Of Precision for + geotagging. GPS fixes are ignored if the PDOP is greater than this.A floating point number, or undefundef
GeoMinSatsMinimum number of satellites for geotagging. + GPS fixes are ignored if the number of acquired satellites is less than this.A positive integer, or undefundef
GeoSpeedRefReference units for writing GPSSpeed when geotagging. + + + +
Kk or km/h= km/h
Mm or mph= mph
(anything else)= knots
undef
GlobalTimeShiftTime shift to apply to all extracted + date/time PrintConv values. Does not affect ValueConv values.Date/time shift string with leading '-' for negative shifts
(see Image::ExifTool::Shift.pl)
undef
Group#Extract tags only for specified groups in family # + (Group0 assumed if # not given).Group name or reference to list of group names. + Group name may begin with '-' to exclude a group. Case IS significant. + See GetGroup for a description of group families, + and GetAllGroups for a list of available groups.undef
HexTagIDsUse hexadecimal instead of decimal for the family 7 + group names of tags with numerical ID's.0 or 1undef
HtmlDumpDump information in hex to a dynamic HTML web page. + Option value sets a limit on the maximum block size. Output file is + specified by the TextOut option. + + + + +
0 =No HTML dump
1 =1 KB size limit
2 =16 KB size limit
3 =Full dump
0
HtmlDumpBaseBase for HTML dump + offsets. If not defined, the EXIF/TIFF base offset is used. + + + +
0 =Absolute offsets
non‑zero =Relative offsets
undef =EXIF/TIFF offsets
undef
IgnoreMinorErrorsFlag to ignore minor errors. Causes minor + errors to be downgraded to warnings, and minor warnings to be ignored. This + option is provided mainly to allow writing of files when minor errors occur, + but by ignoring some minor warnings the behaviour of ExifTool may be changed + to allow some questionable operations to proceed (such as extracting + thumbnail and preview images even if they don't have a recognizable header). + Minor errors/warnings are denoted by "[minor]" at the start of the message, + or "[Minor]" (with a capital "M") for warnings that affect processing when + ignored.0 or 1undef
IgnoreTagsList of tag names to ignore when reading. + This may help in situations where memory is limited because the ignored tag values + are not stored in memory. The tag names are case insensitive and group names + and wildcards are not allowed. A special tag name of "All" may be used to ignore + all tags except those specified by the RequestTags option.List reference, delimited string of names (any delimiter is allowed), or undef to + clear the previous IgnoreTags list.undef
ImageHashTypeSets type of hash + algorithm used for the ImageDataHash tag calculation.'MD5', 'SHA256' or 'SHA512''MD5'
LangLocalized language for ExifTool tag descriptions, etc. If the + specified language isn't available, the option is not changed. May be set to + undef to select the built-in default language.Image::ExifTool::Lang module name (eg. 'fr', 'zh_cn'), or 'en' or undef for the default language.'en'
LargeFileSupportFlag to indicate that 64-bit file offsets are supported on this system.0 or 1undef
ListItemReturn only a specific item from + list-type values. A value of 0 returns the first item in each list, 1 returns + the second item, etc. Negative indices may also be used, with -1 representing the + last item in the list. Applies only to the top-level list of nested lists.An integer, or undefundef
ListJoinSeparator used to join the PrintConv value of + multi-item List-type tags into a single string. If not defined, multi-item lists + are returned as a list reference. Does not affect ValueConv values.Any string, or undef', '
ListSplitRegular expression used to split values of list-type tags + into individual items when writing. (eg. Use ',\\s*' to split a comma-separated + list.) Split when writing either PrintConv or ValueConv values.A regular expression pattern, or undefundef
MakerNotesOption to extract MakerNotes and other writable + subdirectories (such as PrintIM) as a data block. Normally when the MakerNotes + are extracted they are rebuilt to include data outside the boundaries of the + original maker note data block, but a value of 2 disables this feature. + + + +
0 =Don't extract writable subdirectories
1 =Extract and rebuild makernotes into self-contained block
2 =Extract without rebuilding makernotes
undef
MDItemTagsFlag to extract the OS X + metadata item tags (see the "mdls" man page and the + MacOS MDItem Tags documentation for more information).0 or 1undef
MissingTagValueValue for missing tags + interpolated in tag name expressions (or tags where the advanced formatting + expression returns undef). If not set, a minor error is issued for missing + values, or the value is set to '' if IgnoreMinorErrors is set.Any string, or undefundef
NoDupsFlag to remove duplicate items from queued values for + List-type tags when writing. This applies only to queued values, and doesn't resolve + duplicates with existing values in the file when adding to an existing list.0 or 1undef
NoMultiExifRaise error when attempting to write multi-segment + EXIF in a JPEG image.0 or 1undef
NoPDFListFlag to avoid splitting PDF list-type tag + values into separate items.0 or 1undef
NoWarning[+]Regular expression to suppress matching + warning messages. For example, a value of "^Ignored" suppresses all + warnings that begin with the word "Ignored". Has no other effect on + processing, unlike IgnoreMinorErrors for + some warnings. Start the expression with "(?i)" for case-insensitive + matching. Use NoWarning+ to add to existing expressions.A regular expression pattern, or undefundef
PasswordPassword for reading/writing + password-protected PDF documents. Ignored if a password is not required. Character encoding of + the password is determined by the value of the Charset option at processing time.Any stringundef
PrintConvFlag to enable print conversion. Also enables inverse print + conversion for writing.0 or 11
QuickTimeHandlerFlag set to add an 'mdir' + Handler to a newly created + Meta box when adding QuickTime ItemList tags. Adobe Bridge does not add this + Handler, but it is commonly found in samples from other software, and it has been + reported that Apple QuickTime Player and Photos.apps will ignore ItemList tags + if this is missing.0 or 11
QuickTimePadFlag to preserve the padding of + some QuickTime atoms when writing. QuickTime-based Canon CR3 files pad + the values of container atoms with null bytes. This padding is removed by + default when the file is rewritten, but setting this option to 1 adds + padding to preserve the original atom size if the new atom would be smaller + than the original.0 or 1undef
QuickTimeUTCFlag set to assume that QuickTime + date/time values are stored as UTC, causing conversion to local time when they are + extracted and from local time when written. According to the QuickTime + specification date/time values should be UTC, but many digital cameras store + local time instead (presumably because they don't know the time zone), so + the default is to not convert these times (except for Canon CR3 files, which + always use UTC times). This option also disables the autodetection of + incorrect time-zero offsets in QuickTime date/time values, and enforces a + time zero of 1904 as per the QuickTime specification.0 or 1undef
RequestAllFlag to request all tags to be extracted. + This causes some tags to be generated which normally would not be unless specifically + requested (by passing the tag name to ImageInfo or + ExtractInfo). May be set to 2 or 3 to enable generation + of some additional tags as mentioned in the Tag Name documentation.0, 1, 2 or 3undef
RequestTagsList of additional tag and/or group names + to request in the next call to ExtractInfo. This option is + useful only for tags/groups which aren't extracted unless specifically requested. Groups + are requested by adding a colon after the name (eg. "MacOS:"). Names are converted to lower + case as they are added to the list.List reference, delimited string of names + (any delimiter is allowed), or undef to clear the previous RequestTags list.undef
SaveFormatFlag to save EXIF/TIFF format type as the + family 6 group name when extracting information. Without this option set, the + family 6 group names are not generated. See GetGroup + for more details. + 0 or 1undef
SavePathFlag to save the metadata path as the family 5 group + name when extracting information. Without this option set, the family 5 group names + are not generated. See GetGroup for more details. + 0 or 1undef
ScanForXMPFlag to scan all files (even unrecognized + formats) for XMP information unless XMP was already found in the file. When combined with + the FastScan option, only unrecognized file types are scanned for XMP. + 0 or 1undef
SortSpecifies order to sort tags in the returned tag list. + + + + + +
Input =Sort in same order as input tag arguments
File =Sort in order that tags were found in the file
Tag =Sort alphabetically by tag name
Descr =Sort by tag description (with current Lang setting)
Group# =Sort by tag group, + where # is zero or more family numbers separated by colons. If # is not specified, + Group0 is assumed. See GetGroup for a description of group + families.
'Input'
Sort2Secondary sort order used for tags within each group when Sort is 'Group'. + + + +
File =Sort in order that tags were found in the file
Tag =Sort alphabetically by tag name
Descr =Sort by tag description (with current Lang setting)
'File'
StrictDateFlag to return undefined value for + any date which can't be converted when the DateFormat option is used. When + set to 1 while writing a PrintConv date/time value with the DateFormat + option set, the value is written only if POSIX::strptime or Time::Piece is + available and can successfully convert the value. For PNG CreationTime, a + setting of 1 has the additional effect of causing the date/time to be + reformatted according to PNG 1.2 recommendation (RFC-1123) when writing, and + a warning to be issued for any non-standard value when reading (but note + that Windows may not recognize PNG date/time values in standard format). + + + +
undef =Same as 0 for reading/writing or 1 for copying
0 =Return bad date/time values unchanged
1 =Return undef if date/time value can't be converted
undef
StructFlag to return XMP structures as HASH references + instead of flattening into individual tags. This setting has no effect + when writing since both flattened and structured tags may always be written. + See the Structured Information documentation for + more details about structured information. + + + + +
undef =Same as 0 for reading and 2 for copying
0 =Read/copy flattened tags
1 =Read/copy structures
2 =Read/copy both flattened and structured tags, + but flag flattened tags as "unsafe" for copying
undef
StructFormatFormat for serialized structures when reading/writing. + Read here for more details about structured information. + + + +
undef =ExifTool format
JSON =JSON format
JSONQ =JSON with quoted numbers
undef
SystemTagsFlag to extract the + following additional File System tags: FileAttributes, FileDeviceNumber, + FileInodeNumber, FileHardLinks, FileUserID, FileGroupID, FileDeviceID, + FileBlockSize and FileBlockCount.0 or 1undef
TextOutOutput file for Verbose and HtmlDump options.File reference\*STDOUT
TimeZoneTime zone for local date/time values. (Requires POSIX::tzset, + which may not be available in Windows. A work-around in Windows is to + set TZ=<zone> before running ExifTool.)Any valid TZ string, or undef to use the system time zoneundef
UnknownControl extraction of unknown tags. + + + +
0 =Unknown tags not extracted
1 =Unknown tags are extracted from EXIF + (and other tagged-format) directories
2 =Unknown tags also extracted from binary data blocks
0
UserParamSpecial option to set/get user-defined parameters. + Useful to allow external input into tag name expressions and ValueConv logic. + PARAM is the user-defined parameter name (case insensitive). These parameters + may be accessed in tag name expressions by prefixing the parameter name with a dollar + sign just like normal tags, or via the API by calling Options('UserParam','PARAM'). + Appending a hash tag (#) to the parameter name also causes the parameter + to be extracted as a normal tag (in the UserParam group). If called without + additional arguments, Options('UserParam') returns a reference to the + hash of all user parameters (with lower-case names). + + + +
PARAM-Get parameter
PARAM=-Clear parameter
PARAM^=-Set parameter to empty string
+ + +
PARAM=VALUE-Set parameter
+ + +
hash ref-Set UserParam hash
undef-Clear UserParam hash
+
{ }
ValidateFlag to perform extra metadata validation checks + when reading, causing extra warnings to be generated if problems are found.0 or 1undef
VerbosePrint verbose messages to file specified by TextOut option. + Click here for example outputs. + + + + + + +
0 =No verbose messages
1 =Print tag names and raw values
2 =Add additional tag details
3 =Add hex dump of tag data (with length limits)
4 =Remove length limit on dump of tag values
5 =Remove length limit on dump of JPEG segments
0
WindowsWideFileForce the use of wide-character + Windows I/O functions when the CharsetFileName option + is used. This may be necessary when files are on a network drive and the current + directory name contains Unicode characters. By default, the wide-character functions + are used only if the specified file path contains Unicode characters.0 or 1undef
WriteModeSet tag write/create mode. The level of the group + differs for different types of metadata. For XMP or IPTC this is the full XMP/IPTC block + (the family 0 group), but for EXIF this is the individual IFD (the family 1 group). + The 'w' and 'c' modes are tested only when SetNewValue + is called, but the 'g' mode is also tested in WriteInfo.A string with one or more of these characters: + + + +
w =Write existing tags
c =Create new tags
g =Create new groups
'wcg'
XAttrTagsFlag to extract the OS X + extended attribute tags (see the "xattr" man page and the + MacOS XAttr Tags documentation for more information).0 or 1undef
XMPAutoConvFlag to enable automatic conversion + when reading unknown XMP tags with values that look like rational numbers or dates.0 or 11
+ +
The level of +the group differs for different types of metadata. For XMP or IPTC this is the +full XMP/IPTC block (the family 0 group), but for EXIF this is the individual +IFD (the family 1 group).
+ +

Examples:

+
+# exclude the 'OwnerName' tag from returned information
+$exifTool->Options(Exclude => 'OwnerName');
+
+ +
+# only get information in EXIF or MakerNotes groups
+$exifTool->Options(Group0 => ['EXIF', 'MakerNotes']);
+
+ +
+# ignore information from IFD1
+$exifTool->Options(Group1 => '-IFD1');
+
+ +
+# sort by groups in family 2, and extract unknown tags
+$exifTool->Options(Sort => 'Group2', Unknown => 1);
+
+ +
+# reset DateFormat option
+$exifTool->Options(DateFormat => undef);
+
+ +
+# do not extract duplicate tag names
+$oldSetting = $exifTool->Options(Duplicates => 0);
+
+ +
+# get current Verbose setting
+$isVerbose = $exifTool->Options('Verbose');
+
+ +
+# set a user parameter
+$exifTool->Options(UserParam => 'MyParam=some value');
+
+ +

ClearOptions

+

Reset all options to their default values. Loads user-defined default +option values from the %Image::ExifTool::UserDefined::Options hash in +the .ExifTool_config file if it exists.

+
+ + +
PrototypeClearOptions()
Inputs0) ExifTool object reference +
+ +

ExtractInfo

+

Extract all meta information from an image.

+
+ + + +
PrototypeExtractInfo($;@)
Inputs0) ExifTool object reference +
1-N) Same as ImageInfo except that a list of tag + keys is not returned if an ARRAY reference is given. +
Returns1 if this was a recognized file format, 0 otherwise
+

Example:

+
+$success = $exifTool->ExtractInfo('image.jpg', \%options);
+
+

The following options are effective in the call to ExtractInfo:

+
+Binary, Charset, CharsetEXIF, CharsetFileName, CharsetID3, CharsetIPTC, +CharsetPhotoshop, CharsetQuickTime, CharsetRIFF, Composite, ExtendedXMP, +ExtractEmbedded, FastScan, FixBase, HtmlDump, HtmlDumpBase, +IgnoreMinorErrors, Lang, LargeFileSupport, MakerNotes, MDItemTags, +NoPDFList, Password, QuickTimeUTC (enforced 1904 time zero), RequestAll, +RequestTags, SaveFormat, SavePath, ScanForXMP, Struct, TextOut, Unknown, +Verbose, WindowsWideFile, XAttrTags and XMPAutoConv. +
+ +

GetInfo

+

GetInfo is called to return meta information +after it has been extracted from the image by a previous call to +ExtractInfo or ImageInfo. +This function may be called repeatedly after a single call to +ExtractInfo or ImageInfo.

+
+ + + +
PrototypeGetInfo($;@)
Inputs0) ExifTool object reference +
1-N) Same as ImageInfo except that an image + can not be specified +
ReturnsReference to information hash, the same as with + ImageInfo
+

Examples:

+
+# get image width and height only
+$info = $exifTool->GetInfo('ImageWidth', 'ImageHeight');
+
+ +
+# get all Error and Warning messages
+$info = $exifTool->GetInfo('Error', 'Warning');
+
+ +
+# get information for all tags in list (list updated with tags found)
+$info = $exifTool->GetInfo(\@ioTagList);
+
+ +
+# get all information in Author or Location groups
+$info = $exifTool->GetInfo({Group2 => ['Author', 'Location']});
+
+

The following options are effective in the call to GetInfo:

+
+Charset, CoordFormat, DateFormat, Duplicates, Escape, Exclude, Filter, Group#, +GlobalTimeShift, Lang, ListItem, ListJoin, PrintConv, QuickTimeUTC (conversion +to local time), Sort (if a tag list reference is given) and StrictDate. +
+ +

WriteInfo

+

Write meta information to a file. The specified source file is rewritten to +the same-type destination file with new information as specified by previous +calls to SetNewValue. The necessary segments and/or +directories are created in the destination file as required to store the +specified information. May be called repeatedly to write the same information +to additional files without the need to call SetNewValue +again.

+ +

ExifTool queues all new values that are assigned via calls to +SetNewValue, then applies them to any number of files +through one or more calls to WriteInfo. These queued +values may be accessed through GetNewValue, and are +completely separate from metadata extracted from files via +ExtractInfo or ImageInfo +and accessed through GetInfo or +GetValue.

+ +

To be clear, it is NOT necessary to call ExtractInfo +or ImageInfo before WriteInfo. +WriteInfo changes only metadata specified by previous +calls to SetNewValue.

+ +
+ + + +
PrototypeWriteInfo($$;$$)
Inputs0) ExifTool object reference +
1) Source file name, file reference, scalar reference, or undef to + create a file from scratch. A reference to a File::RandomAccess object is + also allowed as a source, but in this case the destination is not optional. +
2) [optional] Destination file name, file reference, scalar + reference to write to memory, or undef to overwrite the original file. May + be '-' to write to stdout. +
3) [optional] Destination file type. Ignored if a source + is defined. +
Returns1 if file was written OK, 2 if file was written +but no changes made, 0 on file write error. +
+

The source file name may be undefined to create a file from scratch +(currently only XMP, MIE, ICC, VRD, DR4, EXV and EXIF files can be created +in this way -- see CanCreate for details). +If undefined, the destination file type is required unless the type can be +determined from the extension of the destination file name.

+

If a destination file name is given, the specified file must not exist +because an existing destination file will not be overwritten. Any new +values for FileName, Directory or HardLink are ignored when a destination +file name is specified.

+

The destination file name may be undefined to overwrite the original file +(make sure you have backups!). In this case, if a source file name is +provided, a temporary file is created and renamed to replace the source file +if no errors occurred while writing. Otherwise, if a source file reference +or scalar reference is used, the image is first written to memory then +copied back to replace the original if there were no errors.

+

On Mac OS systems, the file resource fork is preserved if this routine +is called with a source file name.

+

Examples:

+
+# add information to a source file, writing output to new file
+my $result = $exifTool->WriteInfo($srcfile, $dstfile);
+
+ +
+# create XMP data file from scratch
+$exifTool->WriteInfo(undef, $dstfile, 'XMP');
+
+ +
+# overwrite file (you do have backups, right?)
+$exifTool->WriteInfo($srcfile);
+
+ +
+# retrieve error and warning messages
+$errorMessage = $exifTool->GetValue('Error');
+$warningMessage = $exifTool->GetValue('Warning');
+
+

If an error code is returned, an Error tag is set and GetValue('Error') can +be called to obtain the error description. A Warning tag may be set even if +this routine is successful. Calling WriteInfo clears any pre-existing Error +and Warning tags.

+

The following ExifTool options are effective in the call to +WriteInfo:

+
+ByteOrder, Charset, CharsetEXIF, CharsetFileName, CharsetIPTC, Compact, +Compress, FixBase, IgnoreMinorErrors, NoMultiExif, NoPDFList, Password, +QuickTimeHandler, QuickTimePad, Verbose, WindowsWideFile and WriteMode. +
+ +

GetTagList

+

Get a sorted list of tags from the specified information hash or tag list.

+
+ + + +
PrototypeGetTagList($;$$$)
Inputs0) ExifTool object reference +
1) [optional] Reference to info hash or tag list +
2) [optional] Sort order ('Input', 'File', 'Tag', 'Descr' or 'Group#') +
3) [optional] Secondary sort order ('File', 'Tag' or 'Descr') +
ReturnsList of tag keys in specified order
+

Example:

+
+@tags = $exifTool->GetTagList($info, 'Group0');
+
+

If the information hash or tag list reference is not provided, then the list +of found tags from the last call to ImageInfo, +ExtractInfo or GetInfo +is used instead, and the result is the same as if +GetFoundTags was called. If sort order is not +specified, the sort order is taken from the current options settings.

+ +

GetFoundTags

+

Get list of found tags in specified sort order. The found tags are the +tags for the information obtained from the most recent call to +ImageInfo, ExtractInfo +or GetInfo for this object.

+
+ + + +
PrototypeGetFoundTags($;$$)
Inputs0) ExifTool object reference +
1) [optional] Sort order ('Input', 'File', 'Tag', 'Descr' or 'Group#') +
2) [optional] Secondary sort order ('File', 'Tag' or 'Descr') +
ReturnsList of tag keys in the specified order
+

Example:

+
+my @tags = $exifTool->GetFoundTags('File');
+
+ +

GetRequestedTags

+

Get list of requested tags. These are the tags that were specified +in the arguments of the most recent call to ImageInfo, +ExtractInfo or GetInfo, +including tags specified via a tag list reference. They are returned +in the same order that they were specified. Shortcut tags are expanded +in the list.

+
+ + + +
PrototypeGetRequestedTags($)
Inputs0) ExifTool object reference +
ReturnsList of tag keys for requested tags +(empty if no tags specifically requested)
+

Example:

+
+my @requestedTags = $exifTool->GetRequestedTags();
+
+ +

GetValue

+

Get the value of a specified tag. The returned value is either the +human-readable (PrintConv) value, the converted machine-readable (ValueConv) +value, the original raw (Raw) value, or the original rational (Rational) value +for rational formats. If the value type is not specified, the PrintConv value +is returned if the PrintConv option is set, otherwise the ValueConv value is +returned. The PrintConv values are the same as the values returned by +ImageInfo and GetInfo in the +tag/value hash unless the PrintConv option is disabled.

+

Tags which represent lists of multiple values (as may happen with +'Keywords' for example) are handled specially. In scalar context, +the returned PrintConv value for these tags is either a string of values or +a list reference (depending on the ListJoin option setting), and the ValueConv +value is always a list reference. But in list context, +GetValue always returns the list itself.

+

Note that GetValue requires a case-sensitive tag key +as an argument. To retrieve tag information based on a case-insensitive tag name +(with an optional group specifier), use GetInfo +instead.

+
+ + + +
PrototypeGetValue($$;$)
Inputs0) ExifTool object reference +
1) Tag key, or case-sensitive tag name with optional group prefix(es) +
2) [optional] Value type, 'PrintConv', 'ValueConv', 'Both', + 'Raw' or 'Rational' +
 
The default value type is 'PrintConv' if the PrintConv option + is set, otherwise the default is 'ValueConv'. A value type of 'Both' + returns both ValueConv and PrintConv values as a list. 'Rational' returns + the raw rational value as a string fraction for rational types, or undef for + other types. +
Returns +The value of the specified tag. If the tag represents a list of multiple values +and the ListJoin option is enabled then PrintConv returns a string of values, +otherwise a reference to the list is returned in scalar context. The list +itself is returned in list context. (Unless 'Both' values are requested, in +which case two list references are returned, regardless of context.) Values may +also be scalar references to binary data, or hash references if the +Struct option is set.
 
Note: It is possible +for GetValue to return an undefined ValueConv or +PrintConv value (or an empty list in list context) even if the tag exists, since +it is possible for these conversions to yield undefined values. And the +Rational value will be undefined for any non-rational tag. The Raw value should +always exist if the tag exists. +
+

Examples:

+
+# PrintConv example
+my $val = $exifTool->GetValue($tag);
+if (ref $val eq 'SCALAR') {
+    print "$tag = (unprintable value)\n";
+} else {
+    print "$tag = $val\n";
+}
+
+ +
+# ValueConv example
+my $val = $exifTool->GetValue($tag, 'ValueConv');
+if (ref $val eq 'ARRAY') {
+    print "$tag is a list of values\n";
+} elsif (ref $val eq 'SCALAR') {
+    print "$tag represents binary data\n";
+} else {
+    print "$tag is a simple scalar\n";
+}
+
+ +
+# list example
+my @keywords = $exifTool->GetValue('Keywords', 'ValueConv');
+
+

The following options are in effect when GetValue is +called:

+
+Charset, CoordFormat, DateFormat, Escape, Filter, GlobalTimeShift, Lang, +ListItem, ListJoin, PrintConv, QuickTimeUTC (conversion to local time), +StrictDate and TimeZone. +
+ +

SetNewValue

+

Set the new value for a tag. The routine may be called multiple times to set +the values of many tags before using WriteInfo to write +the new values to an image. These values remain queued for writing to subsequent +files until SetNewValue is called without arguments +to reset the queued values.

+

For list-type tags (like Keywords), either call repeatedly with +the same tag name for each value, or call with a reference to the list of values.

+
+ + + +
PrototypeSetNewValue($;$$%)
Inputs0) ExifTool object reference +
1) [optional] Tag key or tag name, or undef to clear all new + values. The tag name may be prefixed one or more family 0, 1 or 2 group + names with optional leading family numbers, separated by colons (eg. + 'EXIF:Artist', 'XMP:Time:*'), which is equivalent + to using a Group option argument. Also, a '#' may be appended + to the tag name (eg. 'EXIF:Orientation#'), with the same effect + as setting Type to 'ValueConv'. Wildcards ('*' and + '?') may be used in the tag name to assign or delete multiple + tags simultaneously. A tag name of '*' is special when deleting + information, and will delete an entire group even if some individual tags in + the group are not writable, but only if a single family 0 or 1 group name is + specified (otherwise, the tags are deleted individually). Use + GetDeleteGroups to get a list of deletable group + names, and see the TagNames documentation + for a complete list of ExifTool tag names. +
2) [optional] New value for tag. Undefined to delete tag + from file. May be a scalar, scalar reference, list reference to set a list + of values, or hash reference for a structure. Integer values may be + specified as a hexadecimal string (with a leading '0x'), and simple + rational values may be specified in fractional form (eg. '4/10'). Structure + tags may be specified either as a hash reference or a serialized string + (see the last two examples below). +
3-N) [optional] SetNewValue option/value pairs (see below). +
ReturnsScalar context: The number of tags set, and +errors are printed to STDERR. +
List context: The number of tags set, and the error string (undefined if no error). +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
SetNewValue Options
OptionDescriptionValuesDefault
AddValueAdd value to existing list in a file rather than + overwriting the existing values + + + +
0 =Overwrite existing value(s)
1 =Add to existing list, or warn for non-list tags
2 =Add to existing list, or overwrite non-list tags
0
DelValueDelete existing tag from a file if it has the specified + value. For list-type tags this deletes a specified item from the list. For + non-list tags this may be used to conditionally replace a tag by providing a + new value in a separate call to SetNewValue (see + examples below). For structured tags, the entire structure is deleted/replaced + only if all of the specified fields match the existing structure.0 or 10
EditGroupCreate tags in existing groups only. Don't create new group. + Effectively removes the 'g' from the ExifTool WriteMode option for this tag only.0 or 10
EditOnlyEdit tag only if it already exists. Don't create new tag. + Effectively removes the 'c' from the ExifTool WriteMode option for this tag only.0 or 10
GroupSpecifies group name where tag should be written. This + option is superseded by any group specified in the tag name. If not + specified, tag is written to highest priority group as specified by + SetNewGroups. Case is not significantOne or more family 0, 1 or 2 groups with optional leading + family number, separated by colonsundef
NoFlatTreat flattened tags as 'unsafe'0 or 10
NoShortcutDisables default behaviour of looking up tag in + shortcuts if not found otherwise.0 or 10
ProtectedAllow unsafe and protected tags to be writtenBitmask of tag protection levels to write: + + + + + +
0x01 =Write 'unsafe' tags (ie. tags not copied + automatically via SetNewValuesFromFile)
0x02 =Write 'protected' tags (internal use only)
0
ProtectSavedAvoid setting new values which were saved after the Nth + call to SaveNewValues. Has no effect on unsaved values, + or values saved before the Nth call.Nundef
ReplaceReplace previous new values for this tag (ie. replace the values + set in previous calls to SetNewValue). This option + is most commonly used to replace previously-set new values for list-type tags. + + + +
0 =Set new value normally (adds to new values for list-type tags)
1 =Reset any previous new values before setting new value
2 =Reset previous new values only (new value argument is ignored)
0
ShiftShift the tag by the specified value. Currently only date/time tags + and tags with numerical values may be shifted. Value is added if Shift is 1, or subtracted + if Shift is -1. See Image::ExifTool::Shift.pl for details time shift formats. + + + + +
undef = No shift
0 =Shift if shiftable:
+ Positive if AddValue set, or
+ Negative if DelValue set and
+ tag is date/time
1 =Positive shift
-1 =Negative shift
undef
TypeThe type of value being setPrintConv, ValueConv or Raw (default depends on PrintConv Option)PrintConv or ValueConv
+

Examples:

+
+# set a new value for a tag (errors go to STDERR)
+$success = $exifTool->SetNewValue($tag, $value);
+
+ +
+# set a new value and capture any error message
+($success, $errStr) = $exifTool->SetNewValue($tag, $value);
+
+ +
+# delete information for specified tag if it exists in image
+# (also resets AddValue and DelValue options for this tag)
+$exifTool->SetNewValue($tag);
+
+ +
+# reset all values from previous calls to SetNewValue()
+$exifTool->SetNewValue();
+
+ +
+# delete a specific keyword
+$exifTool->SetNewValue('Keywords', $word, DelValue => 1);
+
+ +
+# set keywords (a list-type tag) with two new values
+$exifTool->SetNewValue(Keywords => 'word1');
+$exifTool->SetNewValue(Keywords => 'word2');
+# equivalent, but set both in one call using an array reference
+$exifTool->SetNewValue(Keywords => ['word1','word2']);
+
+ +
+# add a keyword without replacing existing keywords in the file
+$exifTool->SetNewValue(Keywords => $word, AddValue => 1);
+
+ +
+# conditionally add or replace a tag if it didn't exist before
+# or had a specified value ("old value")
+$exifTool->SetNewValue(Description => '', DelValue => 1);
+$exifTool->SetNewValue(Description => 'old value', DelValue => 1);
+$exifTool->SetNewValue(Description => 'new value');
+
+ +
+# set a tag in a specific group
+$exifTool->SetNewValue(Headline => $val, Group => 'XMP');
+$exifTool->SetNewValue('XMP:Headline' => $val);  # (equivalent)
+
+ +
+# shift original date/time back by 2.5 hours
+$exifTool->SetNewValue(DateTimeOriginal => '2:30', Shift => -1);
+
+ +
+# write a tag only if it had a specific value
+# (the order of the following calls is not significant)
+$exifTool->SetNewValue(Title => $oldVal, DelValue => 1);
+$exifTool->SetNewValue(Title => $newVal);
+
+ +
+# write tag by numerical value
+$exifTool->SetNewValue(Orientation => 6, Type => 'ValueConv');
+$exifTool->SetNewValue('Orientation#' => 6);  # (equivalent)
+
+ +
+# delete all but EXIF tags
+$exifTool->SetNewValue('*');  # delete all...
+$exifTool->SetNewValue('EXIF:*', undef, Replace => 2); # ...but EXIF
+
+ +
+
# write structured information as a HASH reference
+$exifTool->SetNewValue('XMP:Flash' => { mode=>'on', fired=>'true', return=>'not' });
+
+ +
+
# write structured information as a serialized string
+$exifTool->SetNewValue('XMP:Flash' => '{mode=on,fired=true,return=not}');
+
(see struct.html for a +description of the structure serialization technique)
+ +

Notes:

+

When deleting groups of tags, the Replace option may be used to exclude +specific groups from a mass delete. However, this technique may not be used to +exclude individual tags from a group delete (unless a family 2 group was +specified in the delete). Instead, use +SetNewValuesFromFile to recover the values +of individual tags after deleting a group.

+

When deleting all tags from a JPEG image, the APP14 "Adobe" information is +not deleted by default because doing so may affect the appearance of the image. +However, this information may be deleted by specifying it explicitly, either by +group (with 'Adobe:*') or as a block (with 'Adobe').

+

The following ExifTool options are effective in the call to +SetNewValue:

+
+Charset, DateFormat, Escape, IgnoreMinorErrors, Lang, ListJoin, ListSplit, +PrintConv, QuickTimeUTC, StrictDate, TimeZone, Verbose and WriteMode. +
+ +

GetNewValue

+

Get the new Raw value for a tag. This is the value set by +SetNewValue that is queued to be written to +file. List-type tags may return multiple values in list context.

+
+ + + +
PrototypeGetNewValue($$)
Inputs0) ExifTool object reference +
1) Tag name (case sensitive, may be prefixed by family 0, 1 or 7 + group names, separated by colons) +
ReturnsList of new Raw tag values, or first value in +list when called in scalar context. The list may be empty either if the tag +isn't being written, or if it is being deleted (ie. if +SetNewValue was called without a value). +
+

Examples:

+
+my $rawVal = $exifTool->GetNewValue($tag);
+
+ +
+my @rawVals = $exifTool->GetNewValue($tag);
+
+ +

Notes:

+

The API NoDups option applies when this routine is called, and removes +duplicate items from values returned for List-type tags.

+ +

SetNewValuesFromFile

+

A very powerful routine that sets new values for tags from information found +in a specified file.

+
+ + + +
PrototypeSetNewValuesFromFile($$;@)
Inputs0) ExifTool object reference +
1) File name, file reference, or scalar reference +
2-N) [optional] List of tag names to set or options hash + references. All writable tags are set if none are specified. The tag names + are not case sensitive, and may be prefixed by one or more family 0, 1, 2 + or 7 group names with optional leading family numbers, separated by colons (eg. + 'exif:iso'). A leading '-' indicates tags to be + excluded (eg. '-comment'), or a trailing '#' causes + the ValueConv value to be copied (same as setting the Type option to + 'ValueConv' for this tag only). A leading '+' sets the Replace + option to 0 on a per-tag basis (see Options below). Wildcards ('*' + and '?') may be used in the tag name. A tag name of + '*' is commonly used when a group is specified to copy all tags in + the group (eg. 'XMP:*').
 
+ A special feature allows tag names of the form 'DSTTAG<SRCTAG' + (or 'SRCTAG>DSTTAG') to be specified to copy information to a + tag with a different name or a specified group. Both 'SRCTAG' and + 'DSTTAG' may contain wildcards and/or be prefixed by a group name + (eg. 'fileModifyDate<modifyDate' or 'xmp:*<*'), + and/or suffixed by a '#' to disable print conversion. Copied tags + may also be added or deleted from a list with arguments of the form + 'DSTTAG+<SRCTAG' or 'DSTTAG-<SRCTAG'. Tags are + evaluated in order, so exclusions apply only to tags included earlier in the + list. An extension of this feature allows the tag value to be set from a + string containing tag names with leading '$' symbols (eg. + 'Comment<the file is $filename'). Braces '{}' may + be used around the tag name to separate it from subsequent text, and a + '$$' is used to to represent a '$' symbol. The + behaviour for missing tags in expressions is defined by the + MissingTagValue option. The tag value may be + modified via changes to the default input variable ($_) in a Perl + expression placed inside the braces and after a semicolon following the tag + name (see the last example below). A @ may be added after the tag + name (before the semicolon) to make the expression act on individual list items + instead of the concatenated string for list-type tags. Braces within the + expression must be balanced.
 
+ Multiple options hash references may be passed to set different options for + different tags. Options apply to subsequent tags in the argument list. +
ReturnsA hash of information that was set +successfully. May include Warning or Error entries if there were problems +reading the input file. +
+

By default, this routine will commute information between same-named tags in +different groups, allowing information to be translated between images with +different formats. This behaviour may be modified by specifying a group name +for extracted tags (even if '*' is used as a group name), in which +case the information is written to the original group, unless redirected to a +different group. When '*' is used for a group name, by default the +family 1 group of the original tag is preserved, but a different family may be +specified with a leading family number. (For example, specifying +'*:*' copies all information while preserving the original family 1 +groups, while '0*:*' preserves the family 0 group.)

+

SetNewValuesFromFile Options:

+

The options are the same was for SetNewValue, and +are passed directly to SetNewValue internally, +with a few exceptions:

+
    +
  • The Replace option defaults to 1 instead of 0 as with +SetNewValue, however the tag name argument may be +prefixed with '+' to set the Replace option to 0 for this argument only.
  • +
  • The AddValue or DelValue option is set for individual tags if '+>' or +'->' (or '+<' or '-<') are used.
  • +
  • The Group option is set for tags where a group name is given.
  • +
  • The Protected flag is set to 1 for individually specified tags.
  • +
  • The Type option also applies to extracted tags.
  • +
+

Examples:

+
+# set new values from all information in a file...
+my $info = $exifTool->SetNewValuesFromFile($srcFile);
+# ...then write these values to another image
+my $result = $exifTool->WriteInfo($file2, $outFile);
+
+ +
+# set all new values, preserving original groups
+$exifTool->SetNewValuesFromFile($srcFile, '*:*');
+
+ +
+# set specific information
+$exifTool->SetNewValuesFromFile($srcFile, $tag1, $tag2...);
+
+ +
+# set new value from a different tag in specific group
+$exifTool->SetNewValuesFromFile($src, 'XMP-dc:Subject<IPTC:Keywords');
+
+ +
+# add all IPTC keywords to XMP subject list
+$exifTool->SetNewValuesFromFile($src, 'XMP-dc:Subject+<IPTC:Keywords');
+
+ +
+# set new value from a string involving other tags
+$exifTool->SetNewValuesFromFile($file,
+    'Comment<ISO=$ISO Aperture=$aperture Exposure=$shutterSpeed');
+
+ +
+# set keywords list from the values of multiple tags
+$exifTool->SetNewValuesFromFile($file, { Replace => 0 },
+    'keywords<xmp:subject', 'keywords<filename');
+
+ +
+# copy all EXIF information, preserving the original IFD
+# (without '*.*<' tags would be copied to the preferred EXIF IFD)
+$exifTool->SetNewValuesFromFile($file, '*:*<EXIF:*');
+
+ +
+# copy all tags with names starting with "gps" (note: this is
+# different than "gps:*" because it will also copy XMP GPS tags)
+$exifTool->SetNewValuesFromFile($file, 'gps*');
+
+ +
+# set FileName from Model, translating questionable characters to underlines
+$exifTool->SetNewValuesFromFile($file, 'filename<${model;tr(/\\\\?*:|"<>)(_)}.jpg');
+
+ +

Notes:

+

The PrintConv option applies to this routine, but it normally should be left +on to provide more reliable transfer of information between groups.

+

If a preview image exists, it is not copied. The preview image must be +transferred separately if desired, in a separate call to +WriteInfo

+

When simply copying all information between files of the same type, it is +usually desirable to preserve the original groups by specifying +'*:*' for the tags to set.

+

The Duplicates option is always in effect for tags +extracted from the source file using this routine.

+

The Struct option is enabled by default for tags +extracted by this routine. This allows the hierarchy of complex structures to +be preserved when copying, but the Struct option may be set to 0 to override +this behaviour and copy as flattened tags instead.

+ +

CountNewValues

+

Return the total number of new values set.

+
+ + + +
PrototypeCountNewValues($)
Inputs0) ExifTool object reference +
ReturnsIn scalar context, returns the total number +of tags with new values set. In list context, also returns the number of +"pseudo" tag values which have been set. "Pseudo" tags are tags like FileName +and FileModifyDate which are not contained within the file and can be changed +without rewriting the file.
+

Examples:

+
+my $numSet = $exifTool->CountNewValues();
+
+ +
+my ($numSet, $numPseudo) = $exifTool->CountNewValues();
+
+ +

SaveNewValues

+

Save state of new values to be later restored by RestoreNewValues.

+
+ + + +
PrototypeSaveNewValues($)
Inputs0) ExifTool object reference +
ReturnsCount of the number of times this routine has +been called (N) since the last time the new values were reset.
+

Example:

+
+$exifTool->SaveNewValues();         # save state of new values
+$exifTool->SetNewValue(ISO => 100); # set new value for ISO
+$exifTool->WriteInfo($src, $dst1);  # write ISO plus any previous new values
+$exifTool->RestoreNewValues();      # restore previous new values
+$exifTool->WriteInfo($src, $dst2);  # write previous new values only
+
+ +

RestoreNewValues

+

Restore new values to the settings that existed when +SaveNewValues was last called. May be called +repeatedly after a single call to SaveNewValues. +See SaveNewValues above for an example.

+
+ + +
PrototypeRestoreNewValues($)
Inputs0) ExifTool object reference +
+ +

SetAlternateFile

+

Specify alternate file from which to read metadata. Tags from the alternate +file are available after ExtractInfo is called or +during a call to SetNewValuesFromFile by +using a family 8 group name (eg. 'File1' in the example below).

+
+ + + +
PrototypeSetAlternateFile($$$)
Inputs0) ExifTool object reference +
1) Case insensitive family 8 group name ('File1', 'File2' or 'File3', etc) +
2) Name of alternate input file, or undef to reset +
Returns1 on success, or 0 if the group name is invalid. +
+

Example:

+
+$exifTool->SetAlternateFile(File1 => 'images/test1.jpg');
+
+ +

SetFileModifyDate

+

Write the filesystem modification or creation time from the new value of the +FileModifyDate or FileCreateDate tag.

+
+ + + +
PrototypeSetFileModifyDate($$;$$)
Inputs0) ExifTool object reference +
1) File name +
2) [optional] Base time if applying shift (in days before $^T) +
3) [optional] Tag to write: 'FileModifyDate' (default), or 'FileCreateDate' +
Returns1 if the time was changed, 0 if nothing was +done, or -1 if there was an error setting the time. +
+

Example:

+
+$exifTool->SetNewValue(FileModifyDate => '2000:01:02 03:04:05', Protected => 1);
+my $result = $exifTool->SetFileModifyDate($file);
+
+

Notes:

+

Equivalent to, but more efficient than calling WriteInfo +when only the FileModifyDate or FileCreateDate tag has been set. If a timezone is not +specified, local time is assumed. When shifting, the time of the original +file is used unless the optional base time is specified.

+

The ability to write FileCreateDate is currently restricted to Windows systems only.

+ +

SetFileName

+

Set the file name and directory, or create a hard link to the file. If not +specified, the new file name is derived from the new values of the FileName and +Directory tags, or from the HardLink or SymLink tag if creating a link. If the +FileName tag contains a '/', then the file is renamed into a new +directory. If FileName ends with '/', then it is taken as a +directory name and the file is moved into the new directory. The new value for +the Directory tag takes precedence over any directory specified in FileName.

+
+ + + +
PrototypeSetFileName($$;$$)
Inputs0) ExifTool object reference +
1) Current file name +
2) [optional] New file name +
3) [optional] 'HardLink' or 'SymLink' to create a hard or + symbolic link instead of renaming the file, or 'Test' to test renaming feature + by printing the old and new names instead of changing anything. +
Returns1 on success, 0 if nothing was done, +or -1 if there was an error renaming the file or creating the link. +
+

Examples:

+
+my $result = $exifTool->SetFileName($file);
+
+ +
+my $result = $exifTool->SetFileName($file, $newName);
+
+

Notes:

+

Will not overwrite existing files. New directories are created as +necessary. If the file is successfully renamed, the new file name may be +accessed via $$exifTool{NewName}.

+ +

SetNewGroups

+

Set the order of the preferred groups when adding new information. In +subsequent calls to SetNewValue, new information +will be created in the first valid group of this list. This has an impact +only if the group is not specified when calling +SetNewValue, and if the tag name exists in more than +one group. The default order is EXIF, IPTC, XMP, MakerNotes, QuickTime, +Photoshop, ICC_Profile, CanonVRD, Adobe. Any family 0 group name may be used. +Case is not significant.

+
+ + +
PrototypeSetNewGroups($;@)
Inputs0) ExifTool object reference +
1-N) Groups in order of priority. If no groups are specified, the +priorities are reset to the defaults. +
+

Example:

+
+$exifTool->SetNewGroups('XMP','EXIF','IPTC');
+
+ +

GetNewGroups

+

Get current group priority list.

+
+ + + +
PrototypeGetNewGroups($)
Inputs0) ExifTool object reference +
ReturnsList of group names in order of write +priority. Highest priority first. +
+

Example:

+
+@groups = $exifTool->GetNewGroups();
+
+ +

GetTagID

+

Get the ID for the specified tag. The ID is the IFD tag number in EXIF +information, the property name in XMP information, or the data offset in a +binary data block. For some tags, such as Composite tags where there is no ID, +an empty string is returned. In list context, also returns a language code for +the tag if available and different from the default language (eg. with +alternate language entries for XMP "lang-alt" tags).

+
+ + + +
PrototypeGetTagID($$)
Inputs0) ExifTool object reference +
1) Tag key +
ReturnsIn scalar context, returns the tag ID or '' if +there is no ID for this tag.
In list context, returns the tag ID (or '') and the +language code (or undef).
+

Examples:

+
+my $id = $exifTool->GetTagID($tag);
+
+
+my ($id, $lang) = $exifTool->GetTagID($tag);
+
+ +

GetDescription

+

Get description for specified tag. This function will always return a defined +value. In the case where the description doesn't exist, one is generated from +the tag name.

+
+ + + +
PrototypeGetDescription($$)
Inputs0) ExifTool object reference +
1) Tag key +
ReturnsTag description
+ +

GetGroup

+

Get group name(s) for a specified tag.

+
+ + + +
PrototypeGetGroup($$;$)
Inputs0) ExifTool object reference +
1) Tag key +
2) [optional] Group family number, or string of numbers + separated by colons +
ReturnsGroup name (or '' if tag has no +group). If no group family is specified, returns the name of group in family 0 +when called in scalar context, or the names of groups for all families in list +context. Returns a string of group names separated by colons if the input group +family contains a colon. The string is simplified to remove a leading 'Main:' +and adjacent identical group names unless the family string begins with a colon. +
+

The following families of groups are available:

+
+ + + + + + + + + + +
FamilyDescriptionExamples
0Information Type EXIF, XMP, IPTC
1Specific LocationIFD0, XMP-dc
2Category Author, Time
3Document Number Main, Doc1, Doc3-2
4Instance Number Copy1, Copy2, Copy3...
5Metadata Path eg. JPEG-APP1-IFD0-ExifIFD
6EXIF/TIFF Format int8u, int32u, undef, string
7Tag ID ID-271, ID-rights, ID-a9aut
8Alternate File File1, File2, File3...
+ +

Families 0 and 1 are based on the file structure, and are similar except that +family 1 is more specific and sub-divides some groups to give more detail about +the specific location where the information was found. For example, the EXIF +group is split up based on the specific IFD (Image File Directory), the +MakerNotes group is divided into groups for each manufacturer, and the XMP group +is separated based on the XMP namespace prefix. Note that only common XMP +namespaces are listed in the GetAllGroups +documentation, but additional namespaces may be present in some XMP data. Also +note that the 'XMP-xmp...' group names may appear in the older form +'XMP-xap...' since these names evolved as the XMP standard was +developed. The ICC_Profile group is broken down to give information about the +specific ICC_Profile tag from which multiple values were extracted. As well, +information extracted from the ICC_Profile header is separated into the +ICC-header group.

+

Family 2 classifies information based on the logical category to which the +information refers.

+

Family 3 gives the document number for tags extracted from embedded documents, +or 'Main' for tags from the main document. (See the +ExtractEmbedded option for extracting tags from embedded +documents.) Nested sub-documents (if they exist) are indicated by numbers +separated with dashes in the group name, to an arbitrary depth. (eg. +'Doc2-3-1' is the 1st sub-sub-document of the +3rd sub-document of the 2nd embedded document of the main +file.) Document numbers are also used to differentiate samples for +timed metadata in videos.

+

Family 4 provides a method for differentiating tags when multiple tags exist +with the same name in the same location. The primary instance of a tag (the tag +extracted when the Duplicates option is disabled and no group is specified) has +no family 4 group name, but additional instances have family 4 group names +of 'Copy1', 'Copy2', 'Copy3', etc. For +convenience, the primary tag may also be accessed using a group name of +'Copy0'.

+

Family 5 and gives the complete path for the metadata in the file. Generated +only if the SavePath option is used when extracting.

+

Family 6 is currently used only for EXIF/TIFF metadata, and gives the format +type of the extracted value. Generated only if the +SaveFormat option is used when extracting.

+

Family 7 is used for tag ID's. The group names are the actual tag ID's with +a leading "ID-" string. Non-numerical tag ID's have characters other than +[-_A-Za-z0-9] converted to hex. Numerical tag ID's are returned in hex if the +HexTagIDs option is set, otherwise decimal is used. +When specifying a family 7 group name, numerical ID's may be in hex (with +leading "0x") or decimal, and non-numerical ID's may or may not have characters +other than [-_A-Za-z0-9] converted to hex. Note that unlike other group names, +the tag ID's in family 7 group names are case sensitive (but the leading "ID-" +is not).

+

Family 8 is used to specify tags loaded from alternate input files defined +in calls to SetAlternateFile.

+

See GetAllGroups for lists of group names.

+ +

Examples:

+
+# return family 0 group name (eg. 'EXIF')
+$group = $exifTool->GetGroup($tag, 0);
+
+ +
+# return all groups (eg. qw{EXIF IFD0 Author Main})
+@groups = $exifTool->GetGroup($tag);
+
+ +
+# return groups as a string (eg. 'Main:IFD0:Author')
+$group = $exifTool->GetGroup($tag, ':3:1:2');
+
+ +
+# return groups as a simplified string (eg. 'IFD0:Author')
+$group = $exifTool->GetGroup($tag, '3:1:2');
+
+ +

GetGroups

+

Get list of group names for all tags in specified information hash.

+
+ + + +
PrototypeGetGroups($;$$)
Inputs0) ExifTool object reference +
1) [optional] Information hash reference (default is all extracted info) +
2) [optional] Group family number (default 0) +
Returns +List of group names in alphabetical order. +If information hash is not specified, the group names are returned for +all extracted information. See GetAllGroups for +a list of groups in each family. +
+

Examples:

+
+my @groups = $exifTool->GetGroups($info, $family);
+
+
Example of one way to print information organized by group +
+my $exifTool = Image::ExifTool->new;
+$exifTool->ExtractInfo('t/images/ExifTool.jpg');
+
+my $family = 1;
+my @groups = $exifTool->GetGroups($family);
+my $group;
+foreach $group (@groups) {
+    print "---- $group ----\n";
+    my $info = $exifTool->GetInfo({"Group$family" => $group});
+    foreach ($exifTool->GetTagList($info)) {
+        print "$_ : $$info{$_}\n";
+    }
+}
+
+ +

BuildCompositeTags

+

Builds composite tags from required tags. The composite tags are convenience +tags which are derived from the values of other tags. This routine is called +automatically by ImageInfo if the Composite option is set.

+
+ + + +
PrototypeBuildCompositeTags($)
Inputs0) ExifTool object reference +
Returns(none)
+

Notes:

+
    +
  1. Tag values are calculated in alphabetical order unless a tag Require's +or Desire's another composite tag, in which case the calculation is +deferred until after the other tag is calculated.
  2. +
  3. Composite tags may need to read data from the image for their value to be +determined, and for these BuildCompositeTags +must be called while the image is available. This is only a problem if +ImageInfo is called with a filename (as opposed to a +file reference or scalar reference) since in this case the file is closed before +ImageInfo returns. Here the Composite option may be +used so that BuildCompositeTags is called from +within ImageInfo, before the file is closed.
  4. +
+
+The following functions access only static data and are not called with an +ExifTool object +
+ +

The names of all the following functions, plus +ImageInfo, may be imported into the current namespace +with the "Public" tag. When this is done, the functions can be accessed without +the need to prefix the function name with "Image::ExifTool::". For +example:

+
+use Image::ExifTool ':Public';
+$tagName = GetTagName($tag);
+
+ +

AvailableOptions

+

Get a list of available API options. (See Options method +for option details.)

+
+ + + +
PrototypeAvailableOptions()
Inputs(none)
ReturnsReference to list of available options. +Each entry in the list is a list reference with 3 items: 0=Option name, 1=Default value, +2=Description
+

Example:

+
+my $opts = Image::ExifTool::AvailableOptions();
+foreach (@$opts) {
+    my ($optionName, $defaultValue, $description) = @$_;
+    ...
+}
+
+ +

GetTagName

+

Get name of tag from tag identifier. This is a convenience function that +strips the embedded instance number, if it exists, from the tag key.

+
+ + + +
PrototypeGetTagName($)
Inputs0) Tag key +
ReturnsTag name
+

Example:

+
+$tagName = Image::ExifTool::GetTagName($tag);
+
+ +

GetShortcuts

+

Get list of tag shortcut names.

+
+ + + +
PrototypeGetShortcuts()
Inputs(none) +
ReturnsList of shortcuts
+ +

GetAllTags

+

Get list of all available tag names.

+
+ + + +
PrototypeGetAllTags(;$)
Inputs0) [optional] Group name, +or string of group names separated by colons +
ReturnsA list of all available tags in alphabetical +order, or all tags in a specified group or intersection of groups. The +group name is case insensitive, and any group in families 0-2 may be used +except for EXIF family 1 groups (ie. the specific IFD). +
+ +

GetWritableTags

+

Get list of all writable tag names.

+
+ + + +
PrototypeGetWritableTags(;$)
Inputs0) [optional] Group name, +or string of group names separated by colons +
ReturnsA list of all writable tags in alphabetical +order. These are the tags for which values may be set through +SetNewValue. If a group name is given, returns +only writable tags in specified group(s). The group name is case insensitive, +and any group in families 0-2 may be used except for EXIF family 1 groups (ie. +the specific IFD). +
+ +

GetAllGroups

+

Get list of all group names in specified family.

+
+ + + + +
PrototypeGetAllGroups($)
Inputs0) Group family number (0-7) +
ReturnsA list of all groups in the specified family in alphabetical order
+

Here is a complete list of groups for each family:

+
+ + + + + + + + + + + + + + + + + + + +
FamilyGroup Names
0 (Information Type)AFCP, AIFF, APE, APP0, APP1, APP11, APP12, APP13, APP14, APP15, APP2, +APP3, APP4, APP5, APP6, APP8, ASF, Audible, CanonVRD, Composite, DICOM, DNG, +DV, DjVu, Ducky, EXE, EXIF, ExifTool, FITS, FLAC, FLIR, File, Flash, +FlashPix, Font, FotoStation, GIF, GIMP, GeoTiff, GoPro, H264, HTML, +ICC_Profile, ID3, IPTC, ISO, ITC, JFIF, JPEG, JSON, JUMBF, Jpeg2000, LNK, +Leaf, Lytro, M2TS, MIE, MIFF, MISB, MNG, MOI, MPC, MPEG, MPF, MXF, +MakerNotes, Matroska, Meta, Ogg, OpenEXR, Opus, PDF, PICT, PLIST, PNG, PSP, +Palm, Parrot, PanasonicRaw, PhotoCD, PhotoMechanic, Photoshop, PostScript, +PrintIM, QuickTime, RAF, RIFF, RSRC, RTF, Radiance, Rawzor, Real, Red, SVG, +SigmaRaw, Stim, Theora, Torrent, Trailer, UserParam, VCard, Vorbis, WTV, +XML, XMP, ZIP +
1 (Specific Location)AC3, AFCP, AIFF, APE, ASF, AVI1, Adobe, AdobeCM, AdobeDNG, Apple, Audible, +CBOR, CIFF, CameraIFD, Canon, CanonCustom, CanonDR4, CanonRaw, CanonVRD, +Casio, Chapter#, Composite, DICOM, DJI, DNG, DV, DjVu, DjVu-Meta, Ducky, +EPPIM, EXE, EXIF, ExifIFD, ExifTool, FITS, FLAC, FLIR, File, Flash, +FlashPix, Font, FotoStation, FujiFilm, FujiIFD, GE, GIF, GIMP, GPS, +GSpherical, Garmin, GeoTiff, GlobParamIFD, GoPro, GraphConv, H264, HP, HTC, +HTML, HTML-dc, HTML-ncc, HTML-office, HTML-prod, HTML-vw96, HTTP-equiv, +ICC-chrm, ICC-clrt, ICC-header, ICC-meas, ICC-meta, ICC-view, ICC_Profile, +ICC_Profile#, ID3, ID3v1, ID3v1_Enh, ID3v2_2, ID3v2_3, ID3v2_4, IFD0, IFD1, +IPTC, IPTC#, ISO, ITC, InfiRay, Insta360, InteropIFD, ItemList, JFIF, JFXX, +JPEG, JPEG-HDR, JPS, JSON, JUMBF, JVC, Jpeg2000, KDC_IFD, Keys, Kodak, +KodakBordersIFD, KodakEffectsIFD, KodakIFD, KyoceraRaw, LNK, Leaf, +LeafSubIFD, Leica, Lyrics3, Lytro, M2TS, MAC, MIE-Audio, MIE-Camera, +MIE-Canon, MIE-Doc, MIE-Extender, MIE-Flash, MIE-GPS, MIE-Geo, MIE-Image, +MIE-Lens, MIE-Main, MIE-MakerNotes, MIE-Meta, MIE-Orient, MIE-Preview, +MIE-Thumbnail, MIE-UTM, MIE-Unknown, MIE-Video, MIFF, MISB, MNG, MOBI, MOI, +MPC, MPEG, MPF0, MPImage, MS-DOC, MXF, MacOS, MakerNotes, MakerUnknown, +Matroska, MediaJukebox, Meta, MetaIFD, Microsoft, Minolta, MinoltaRaw, +Motorola, NITF, Nikon, NikonCapture, NikonCustom, NikonScan, NikonSettings, +NineEdits, Nintendo, Ocad, Ogg, Olympus, OpenEXR, Opus, PDF, PICT, PNG, +PNG-cICP, PNG-pHYs, PSP, Palm, Panasonic, PanasonicRaw, Parrot, Pentax, +PhaseOne, PhotoCD, PhotoMechanic, Photoshop, PictureInfo, PostScript, +PreviewIFD, PrintIM, ProfileIFD, Qualcomm, QuickTime, RAF, RAF2, RIFF, +RMETA, RSRC, RTF, Radiance, Rawzor, Real, Real-CONT, Real-MDPR, Real-PROP, +Real-RA3, Real-RA4, Real-RA5, Real-RJMD, Reconyx, Red, Ricoh, SPIFF, SR2, +SR2DataIFD, SR2SubIFD, SRF#, SVG, Samsung, Sanyo, Scalado, Sigma, SigmaRaw, +Sony, SonyIDC, Stim, SubIFD, System, Theora, Torrent, Track#, UserData, +VCalendar, VCard, VNote, Version0, Vorbis, WTV, XML, XMP, XMP-DICOM, +XMP-Device, XMP-GAudio, XMP-GCamera, XMP-GCreations, XMP-GDepth, XMP-GFocus, +XMP-GImage, XMP-GPano, XMP-GSpherical, XMP-LImage, XMP-MP, XMP-MP1, +XMP-PixelLive, XMP-aas, XMP-acdsee, XMP-album, XMP-apple-fi, XMP-ast, +XMP-aux, XMP-cc, XMP-cell, XMP-crd, XMP-creatorAtom, XMP-crs, XMP-dc, +XMP-dex, XMP-digiKam, XMP-drone-dji, XMP-dwc, XMP-et, XMP-exif, XMP-exifEX, +XMP-expressionmedia, XMP-extensis, XMP-fpv, XMP-getty, XMP-hdr, XMP-hdrgm, +XMP-ics, XMP-iptcCore, XMP-iptcExt, XMP-lr, XMP-mediapro, XMP-microsoft, +XMP-mwg-coll, XMP-mwg-kw, XMP-mwg-rs, XMP-nine, XMP-panorama, XMP-pdf, +XMP-pdfx, XMP-photomech, XMP-photoshop, XMP-plus, XMP-pmi, XMP-prism, +XMP-prl, XMP-prm, XMP-pur, XMP-rdf, XMP-sdc, XMP-swf, XMP-tiff, XMP-x, +XMP-xmp, XMP-xmpBJ, XMP-xmpDM, XMP-xmpMM, XMP-xmpNote, XMP-xmpPLUS, +XMP-xmpRights, XMP-xmpTPg, ZIP, iTunes +
2 (Category)Audio, Author, Camera, Device, Document, ExifTool, Image, Location, Other, +Preview, Printing, Time, Unknown, Video +
3 (Document Number)Doc#, Main +
4 (Instance Number)Copy# +
5 (Metadata Path)eg. JPEG-APP1-IFD0-ExifIFD +
6 (EXIF/TIFF Format)int8u, string, int16u, int32u, rational64u, int8s, undef, int16s, int32s, +rational64s, float, double, ifd, unicode, complex, int64u, int64s, ifd64
7 (Tag ID)ID-xxx (Where xxx is the tag ID. Numerical ID's are returned in hex with a +leading "0x" if the HexTagIDs option is set, or decimal otherwise. Characters +in non-numerical ID's which are not valid in a group name are returned as 2 +hex digits.)
8 (Alternate File)File# +
+

Note: This function may also be called as an ExifTool member function to +allow the HexTagIDs option to be set when retrieving family 7 group names.

+

Example:

+
+@groupList = Image::ExifTool::GetAllGroups($family);
+
+ +

GetDeleteGroups

+

Get list of all deletable group names.

+
+ + + + +
PrototypeGetDelGroups()
InputsNone +
ReturnsA list of deletable group names in +alphabetical order. +
+

Below is a current list of deletable group names.

+
Adobe, AFCP, APP0, APP1, APP10, APP11, APP12, APP13, APP14, APP15, APP2, +APP3, APP4, APP5, APP6, APP7, APP8, APP9, Audio, Author, Camera, CanonVRD, +CIFF, Document, Ducky, EXIF, ExifIFD, ExifTool, File, FlashPix, FotoStation, +GlobParamIFD, GPS, ICC_Profile, IFD0, IFD1, Image, Insta360, InteropIFD, +IPTC, ItemList, JFIF, Jpeg2000, Keys, Location, MakerNotes, Meta, MetaIFD, +Microsoft, MIE, MPF, NikonCapture, Other, PDF, PDF-update, PhotoMechanic, +Photoshop, PNG, PNG-pHYs, Preview, PrintIM, Printing, QuickTime, RMETA, +RSRC, SubIFD, Time, Trailer, UserData, Video, XML, XML-*, XMP, XMP-*
+

To schedule a group for deletion, call SetNewValue +with a tag name like 'EXIF:*' and an undefined tag value.

+

Deleting a family 0 or 1 group will delete the entire corresponding block of +metadata, but deleting a family 2 group (eg. Audio, Author, Camera, etc.) +deletes the individual tags belonging to that category.

+

The 'Trailer' group allows all trailers in JPEG and TIFF-format +images to be deleted at once, including unknown trailers. Note that the JPEG +"APP" groups are special, and are used only to delete application segments which +are not associated with another deletable group. For example, deleting +'APP14:*' will delete other APP14 segments, but not the APP14 +"Adobe" segment.

+

Example:

+
+my @delGroups = Image::ExifTool::GetDelGroups();
+
+ +

GetFileType

+

Get type of file given file name.

+
+ + + + +
PrototypeGetFileType(;$$)
Inputs0) [optional] File name or extension +
1) [optional] Flag to return a description instead of a type. +Default is undef. Set to 0 to also return return types of recognized but +unsupported files (otherwise undef is returned for unsupported files), or 1 to +return file descriptions. +
ReturnsA string, based on the file extension, which +indicates the basic format of the file. Note that some files may be based on +other formats (like many RAW image formats are based on TIFF). In list context, +may return more than one file type if the file may be based on different +formats. Returns undef if files with this extension are not yet supported by +ExifTool. Returns a list of extensions for all supported file types if no input +extension is specified (or all recognized file types if the description flag is +set to 0). Returns a more detailed description of the specific file format when +the description flag is set.
+

Examples:

+
+my $type = Image::ExifTool::GetFileType($filename);
+my $desc = Image::ExifTool::GetFileType($filename, 1);
+
+ +

CanWrite

+

Can the specified file be written?

+
+ + + + +
PrototypeCanWrite($)
Inputs0) File name or extension
ReturnsTrue if ExifTool supports writing files of +this type (based on the file extension).
+

Example:

+
+my $writable = Image::ExifTool::CanWrite($filename);
+
+ +

CanCreate

+

Can the specified file be created?

+
+ + + + +
PrototypeCanCreate($)
Inputs0) File name or extension
ReturnsTrue if ExifTool can create files with this +extension from scratch.
Currently, this can only be done with XMP, MIE, ICC, +VRD, DR4, EXV and EXIF files.
+

Example:

+
+my $creatable = Image::ExifTool::CanCreate($filename);
+
+ +

AddUserDefinedTags

+

Add user-defined tags to an existing tag table at run time. This differs from +the usual technique of creating user-defined tags via the +%Image::ExifTool::UserDefined hash (see the +sample config file), because it allows tags to be added +after a tag table has been initialized.

+
+ + + +
PrototypeAddUserDefinedTags($%)
Inputs0) Destination tag table name +
1-N) Pairs of tag ID / tag information hash references for the new tags +
ReturnsThe number of tags added
+

Example:

+
+use Image::ExifTool ':Public';
+my %tags = (
+    TestTagID1 => { Name => 'TestTagName1' },
+    TestTagID2 => { Name => 'TestTagName2' },
+);
+my $num = AddUserDefinedTags('Image::ExifTool::PDF::Info', %tags);
+
+

Notes:

+

Pre-existing tags with the same ID will be replaced in the destination table. +See lib/Image/ExifTool/README in the full distribution for full details on the +elements of the tag information hash.

+ +
+

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/MIE1.1-20070121.pdf b/ExifTool/html/MIE1.1-20070121.pdf new file mode 100644 index 0000000000000000000000000000000000000000..57b64b0bbf18f1ba6d4cfc1d3dda02fca5e99826 GIT binary patch literal 128751 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℞p7RN<%OvDd>ug64DV9OBt7d-FbLE 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 OU?>g)!)-Y+oc{xuwCkk+ literal 0 HcmV?d00001 diff --git a/ExifTool/html/Shift.html b/ExifTool/html/Shift.html new file mode 100644 index 0000000..3226ef2 --- /dev/null +++ b/ExifTool/html/Shift.html @@ -0,0 +1,183 @@ + + + + +ExifTool Date/Time Shift Module + + + + + + + +
+ ExifTool Date/Time Shift Module +
+ + + + + +

NAME

+ +

Image::ExifTool::Shift.pl - ExifTool time shifting routines

+ +

DESCRIPTION

+ +

This module contains routines used by ExifTool to shift date and time values.

+ +

METHODS

+ +

ShiftTime

+ +

Shift date/time value

+ +
    use Image::ExifTool;
+    $err = Image::ExifTool::ShiftTime($dateTime, $shift);
+ +
+ +
Inputs:
+
+ +

0) Date/time string in EXIF format (eg. 2016:01:30 11:45:00).

+ +

1) Shift string (see below) with optional leading sign for shift direction.

+ +

2) [optional] Direction of shift (-1 or +1), or 0 or undef to use the sign from the shift string.

+ +

3) [optional] Reference to time-shift hash -- filled in by first call to ShiftTime, and used in subsequent calls to shift date/time values by the same relative amount (see "TRICKY" section below).

+ +

or

+ +

0) Shift string (and $_ contains the input date/time string).

+ +
+
Return value:
+
+ +

Error string, or undef on success and the input date/time string is shifted by the specified amount.

+ +
+
+ +

SHIFT STRING

+ +

Time shifts are applied to standard EXIF-formatted date/time values (eg. 2005:03:14 18:55:00). Date-only and time-only values may also be shifted, and an optional timezone (eg. -05:00) is also supported. Here are some general rules and examples to explain how shift strings are interpreted:

+ +

Date-only values are shifted using the following formats:

+ +
    'Y:M:D'     - shift date by 'Y' years, 'M' months and 'D' days
+    'M:D'       - shift months and days only
+    'D'         - shift specified number of days
+ +

Time-only values are shifted using the following formats:

+ +
    'h:m:s'     - shift time by 'h' hours, 'm' minutes and 's' seconds
+    'h:m'       - shift hours and minutes only
+    'h'         - shift specified number of hours
+ +

Timezone shifts are specified in the following formats:

+ +
    '+h:m'      - shift timezone by 'h' hours and 'm' minutes
+    '-h:m'      - negative shift of timezone hours and minutes
+    '+h'        - shift timezone hours only
+    '-h'        - negative shift of timezone hours only
+ +

A valid shift value consists of one or two arguments, separated by a space. If only one is provided, it is assumed to be a time shift when applied to a time-only or a date/time value, or a date shift when applied to a date-only value. For example:

+ +
    '1'         - shift by 1 hour if applied to a time or date/time
+                  value, or by one day if applied to a date value
+    '2:0'       - shift 2 hours (time, date/time), or 2 months (date)
+    '5:0:0'     - shift 5 hours (time, date/time), or 5 years (date)
+    '0:0:1'     - shift 1 s (time, date/time), or 1 day (date)
+ +

If two arguments are given, the date shift is first, followed by the time shift:

+ +
    '3:0:0 0'         - shift date by 3 years
+    '0 15:30'         - shift time by 15 hours and 30 minutes
+    '1:0:0 0:0:0+5:0' - shift date by 1 year and timezone by 5 hours
+ +

A date shift is simply ignored if applied to a time value or visa versa.

+ +

Numbers specified in shift fields may contain a decimal point:

+ +
    '1.5'       - 1 hour 30 minutes (time, date/time), or 1 day (date)
+    '2.5 0'     - 2 days 12 hours (date/time), 12 hours (time) or
+                  2 days (date)
+ +

And to save typing, a zero is assumed for any missing numbers:

+ +
    '1::'       - shift by 1 hour (time, date/time) or 1 year (date)
+    '26:: 0'    - shift date by 26 years
+    '+:30'      - shift timezone by 30 minutes
+ +

Below are some specific examples applied to real date and/or time values ('Dir' is the applied shift direction: '+' is positive, '-' is negative):

+ +
     Original Value         Shift   Dir    Shifted Value
+    ---------------------  -------  ---  ---------------------
+    '20:30:00'             '5'       +   '01:30:00'
+    '2005:01:27'           '5'       +   '2005:02:01'
+    '2005:01:27 20:30:00'  '5'       +   '2005:01:28 01:30:00'
+    '11:54:00'             '2.5 0'   -   '23:54:00'
+    '2005:11:02'           '2.5 0'   -   '2005:10:31'
+    '2005:11:02 11:54:00'  '2.5 0'   -   '2005:10:30 23:54:00'
+    '2004:02:28 08:00:00'  '1 1.3'   +   '2004:02:29 09:18:00'
+    '07:00:00'             '-5'      +   '07:00:00'
+    '07:00:00+01:00'       '-5'      +   '07:00:00-04:00'
+    '07:00:00Z'            '+2:30'   -   '07:00:00-02:30'
+    '1970:01:01'           '35::'    +   '2005:01:01'
+    '2005:01:01'           '400'     +   '2006:02:05'
+    '10:00:00.00'          '::1.33'  -   '09:59:58.67'
+ +

NOTES

+ +

The format of the original date/time value is not changed when the time shift is applied. This means that the length of the date/time string will not change, and only the numbers in the string will be modified. The only exception to this rule is that a 'Z' timezone is changed to '+00:00' notation if a timezone shift is applied. A timezone will not be added to the date/time string.

+ +

TRICKY

+ +

This module is perhaps more complicated than it needs to be because it is designed to be very flexible in the way time shifts are specified and applied...

+ +

The ability to shift dates by Y years, M months, etc, conflicts with the design goal of maintaining a constant shift for all time values when applying a batch shift. This is because shifting by 1 month can be equivalent to anything from 28 to 31 days, and 1 year can be 365 or 366 days, depending on the starting date.

+ +

The inconsistency is handled by shifting the first tag found with the actual specified shift, then calculating the equivalent time difference in seconds for this shift and applying this difference to subsequent tags in a batch conversion. So if it works as designed, the behaviour should be both intuitive and mathematically correct, and the user shouldn't have to worry about details such as this (in keeping with Perl's "do the right thing" philosophy).

+ +

BUGS

+ +

Due to the use of the standard time library functions, dates are typically limited to the range 1970 to 2038 on 32-bit systems.

+ +

AUTHOR

+ +

Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com)

+ +

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

+ +

SEE ALSO

+ +

Image::ExifTool(3pm)

+ + + +
+ ExifTool Date/Time Shift Module +
+ + + + + + diff --git a/ExifTool/html/TagNames/AFCP.html b/ExifTool/html/TagNames/AFCP.html new file mode 100644 index 0000000..ae89e73 --- /dev/null +++ b/ExifTool/html/TagNames/AFCP.html @@ -0,0 +1,54 @@ + + + + +AFCP Tags + + + +

AFCP Tags

+

AFCP stands for AXS File Concatenation Protocol, and is a poorly designed +protocol for appending information to the end of files. This can be used as +an auxiliary technique to store IPTC information in images, but is +incompatible with some file formats.

+ +

ExifTool will read and write (but not create) AFCP IPTC information in JPEG +and TIFF images.

+ +

See +http://web.archive.org/web/20080828211305/http://www.tocarte.com/media/axs_afcp_spec.pdf +for the AFCP specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'IPTC'IPTC---> IPTC Tags
'Nail'ThumbnailImageno 
'PrVw'PreviewImageno 
'TEXT'Textno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 3, 2013 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/AIFF.html b/ExifTool/html/TagNames/AIFF.html new file mode 100644 index 0000000..c236171 --- /dev/null +++ b/ExifTool/html/TagNames/AIFF.html @@ -0,0 +1,160 @@ + + + + +AIFF Tags + + + +

AIFF Tags

+

Tags extracted from Audio Interchange File Format (AIFF) files. See +http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/AIFF.html for +the AIFF specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'(c) 'Copyrightno 
'ANNO'Annotationno 
'APPL'ApplicationDatano 
'AUTH'Authorno 
'COMM'Common---> AIFF Common Tags
'COMT'Comment---> AIFF Comment Tags
'FVER'FormatVersion---> AIFF FormatVers Tags
'ID3 'ID3---> ID3 Tags
'NAME'Nameno 
+ +

AIFF Common Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0NumChannelsno 
1NumSampleFramesno 
3SampleSizeno 
4SampleRateno 
9CompressionTypeno +
'ACE2' = ACE 2-to-1 +
'ACE8' = ACE 8-to-3 +
'ALAW' = A-law +
'G722' = G722 +
'G726' = G726 +
'G728' = G728 +
'GSM ' = GSM +
'MAC3' = MAC 3-to-1 +
'MAC6' = MAC 6-to-1 +
'NONE' = None +
'ULAW' = Mu-law +
'alaw' = a-law +
'sowt' = Little-endian, no compression +
'ulaw' = mu-law
+
11CompressorNameno 
+ +

AIFF Comment Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000CommentTimeno 
0x0001MarkerIDno 
0x0002Commentno 
+ +

AIFF FormatVers Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
0FormatVersionTimeno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 24, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/APE.html b/ExifTool/html/TagNames/APE.html new file mode 100644 index 0000000..26845a2 --- /dev/null +++ b/ExifTool/html/TagNames/APE.html @@ -0,0 +1,155 @@ + + + + +APE Tags + + + +

APE Tags

+

Tags found in Monkey's Audio (APE) information. Only a few common tags are +listed below, but ExifTool will extract any tag found. ExifTool supports +APEv1 and APEv2 tags, as well as ID3 information in APE files, and will also +read APE metadata from MP3 and MPC files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Album'Albumno 
'Artist'Artistno 
'DURATION'Durationno 
'Genre'Genreno 
'Title'Titleno 
'Tool Name'ToolNameno 
'Tool Version'ToolVersionno 
'Track'Trackno 
'Year'Yearno 
+ +

APE NewHeader Tags

+

APE MAC audio header for version 3.98 or later.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0CompressionLevelno 
2BlocksPerFrameno 
4FinalFrameBlocksno 
6TotalFramesno 
8BitsPerSampleno 
9Channelsno 
10SampleRateno 
+ +

APE OldHeader Tags

+

APE MAC audio header for version 3.97 or earlier.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0APEVersionno 
1CompressionLevelno 
3Channelsno 
4SampleRateno 
10TotalFramesno 
12FinalFrameBlocksno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 7, 2017 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/APP12.html b/ExifTool/html/TagNames/APP12.html new file mode 100644 index 0000000..04ce574 --- /dev/null +++ b/ExifTool/html/TagNames/APP12.html @@ -0,0 +1,189 @@ + + + + +APP12 Tags + + + +

APP12 PictureInfo Tags

+

The JPEG APP12 "Picture Info" segment was used by some older cameras, and +contains ASCII-based meta information. Below are some tags which have been +observed Agfa and Polaroid images, however ExifTool will extract information +from any tags found in this segment.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Aperture'Apertureno 
'ColorMode'ColorModeno 
'ConTake'ConTakeno 
'ExpBias'ExposureCompensationno 
'FNumber'FNumberno 
'FWare'FirmwareVersionno 
'Flash'Flashno0 = Off +
1 = On
'FocusMode'FocusModeno 
'FocusPos'FocusPosno 
'ID'IDno 
'ImageSize'ImageSizeno 
'LightS'LightSno 
'Macro'Macrono0 = Off +
1 = On
'Protect'Protectno 
'Quality'Qualityno 
'Resolution'Resolutionno 
'Serial#'SerialNumberno 
'Shutter'ExposureTimeno 
'StrobeTime'StrobeTimeno 
'TimeDate'DateTimeOriginalno 
'Type'CameraTypeno 
'Version'Versionno 
'Ytarget'YTargetno 
'Zoom'Zoomno 
'ZoomPos'ZoomPosno 
'shtr'ExposureTimeno 
'ylevel'YLevelno 
+ +

APP12 Ducky Tags

+

Photoshop uses the JPEG APP12 "Ducky" segment to store some information in +"Save for Web" images.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001Qualityint32u/ 
0x0002Commentstring/ 
0x0003Copyrightstring/ 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jan 22, 2011 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/ASF.html b/ExifTool/html/TagNames/ASF.html new file mode 100644 index 0000000..7b1c1a7 --- /dev/null +++ b/ExifTool/html/TagNames/ASF.html @@ -0,0 +1,1067 @@ + + + + +ASF Tags + + + +

ASF Tags

+

The ASF format is used by Windows WMA and WMV files, and DIVX videos. Tag +ID's aren't listed because they are huge 128-bit GUID's that would ruin the +formatting of this table.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
SimpleIndexno 
TimecodeIndexno 
Header---> ASF Header Tags
Datano 
XMP---> XMP Tags
Indexno 
MediaIndexno 
+ +

ASF Header Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Paddingno 
ScriptCommandno 
ContentBranding---> ASF ContentBranding Tags
ContentEncryptionno 
DigitalSignatureno 
ExtendedContentEncryptionno 
HeaderExtension---> ASF HeaderExtension Tags
ContentDescription---> ASF ContentDescr Tags
ErrorCorrectionno 
StreamBitratePropsno 
CodecList---> ASF CodecList Tags
FileProperties---> ASF FileProperties Tags
StreamProperties---> ASF StreamProperties Tags
ExtendedContentDescr---> ASF ExtendedDescr Tags
BitrateMutualExclusionno 
Markerno 
+ +

ASF ContentBranding Tags

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
BannerImageTypeno0 = None +
1 = Bitmap +
2 = JPEG +
3 = GIF
BannerImageno 
BannerImageURLno 
CopyrightURLno 
+ +

ASF HeaderExtension Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ExtendedStreamPropsno 
AdvancedContentEncryptionno 
MetadataLibrary---> ASF ExtendedDescr Tags
TimecodeIndexParmsno 
Compatibilityno 
LanguageListno 
AdvancedMutualExclno 
BandwidthSharingno 
Reserved1no 
Metadata---> ASF ExtendedDescr Tags
GroupMutualExclusionno 
StreamPrioritizationno 
IndexParametersno 
+ +

ASF ExtendedDescr Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ASFLeakyBucketPairsno 
ASFPacketCountno 
ASFSecurityObjectsSizeno 
AlbumArtistno 
AlbumCoverURLno 
AlbumTitleno 
AspectRatioXno 
AspectRatioYno 
AudioFileURLno 
AudioSourceURLno 
Authorno 
AuthorURLno 
AverageLevelno 
BannerImageDatano 
BannerImageTypeno 
BannerImageURLno 
BeatsPerMinuteno 
Bitrateno 
Broadcastno 
BufferAverageno 
Can_Skip_Backwardno 
Can_Skip_Forwardno 
Categoryno 
Codecno 
Composerno 
Conductorno 
ContainerFormatno 
ContentDistributorno 
ContentGroupDescriptionno 
Copyrightno 
CopyrightURLno 
CurrentBitrateno 
DRMno 
DRM_ContentIDno 
DRM_DRMHeaderno 
DRM_DRMHeader_ContentDistributorno 
DRM_DRMHeader_ContentIDno 
DRM_DRMHeader_IndividualizedVersionno 
DRM_DRMHeader_KeyIDno 
DRM_DRMHeader_LicenseAcqURLno 
DRM_DRMHeader_SubscriptionContentIDno 
DRM_IndividualizedVersionno 
DRM_KeyIDno 
DRM_LASignatureCertno 
DRM_LASignatureLicSrvCertno 
DRM_LASignaturePrivKeyno 
DRM_LASignatureRootCertno 
DRM_LicenseAcqURLno 
DRM_V1LicenseAcqURLno 
DVDIDno 
Descriptionno 
Directorno 
Durationno 
EncodedByno 
EncodingSettingsno 
EncodingTimeno 
FileSizeno 
Genreno 
GenreIDno 
HasArbitraryDataStreamno 
HasAttachedImagesno 
HasAudiono 
HasFileTransferStreamno 
HasImageno 
HasScriptno 
HasVideono 
ISRCno 
InitialKeyno 
IsVBRno 
Is_Protectedno 
Is_Trustedno 
Languageno 
Lyricsno 
Lyrics_Synchronisedno 
MCDIno 
MediaClassPrimaryIDno 
MediaClassSecondaryIDno 
MediaCreditsno 
MediaIsDelayno 
MediaIsFinaleno 
MediaIsLiveno 
MediaIsPremiereno 
MediaIsRepeatno 
MediaIsSAPno 
MediaIsStereono 
MediaIsSubtitledno 
MediaIsTapeno 
MediaNetworkAffiliationno 
MediaOriginalBroadcastDateTimeno 
MediaOriginalChannelno 
MediaStationCallSignno 
MediaStationNameno 
ModifiedByno 
Moodno 
NSC_Addressno 
NSC_Descriptionno 
NSC_Emailno 
NSC_Nameno 
NSC_Phoneno 
NumberOfFramesno 
OptimalBitrateno 
OriginalAlbumTitleno 
OriginalArtistno 
OriginalFileNameno 
OriginalLyricistno 
OriginalReleaseTimeno 
OriginalReleaseYearno 
ParentalRatingno 
ParentalRatingReasonno 
PartOfSetno 
PeakBitrateno 
PeakValueno 
Periodno 
Picture---> ASF Picture Tags
PlaylistDelayno 
Producerno 
PromotionURLno 
ProtectionTypeno 
Providerno 
ProviderCopyrightno 
ProviderRatingno 
ProviderStyleno 
Publisherno 
RadioStationNameno 
RadioStationOwnerno 
Ratingno 
Seekableno 
SharedUserRatingno 
Signature_Nameno 
StreamTypeInfono 
Stridableno 
Subtitleno 
SubtitleDescriptionno 
SubscriptionContentIDno 
Textno 
Titleno 
ToolNameno 
ToolVersionno 
Trackno 
TrackNumberno 
UniqueFileIdentifierno 
UserWebURLno 
VBRPeakno 
VideoClosedCaptioningno 
VideoFrameRateno 
VideoHeightno 
VideoWidthno 
WMADRCAverageReferenceno 
WMADRCAverageTargetno 
WMADRCPeakReferenceno 
WMADRCPeakTargetno 
WMCollectionGroupIDno 
WMCollectionIDno 
WMContentIDno 
Writerno 
Yearno 
+ +

ASF Picture Tags

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
PictureTypeno +
0 = Other +
1 = 32x32 PNG Icon +
2 = Other Icon +
3 = Front Cover +
4 = Back Cover +
5 = Leaflet +
6 = Media +
7 = Lead Artist +
8 = Artist +
9 = Conductor +
10 = Band +
11 = Composer +
12 = Lyricist +
13 = Recording Studio or Location +
14 = Recording Session +
15 = Performance +
16 = Capture from Movie or Video +
17 = Bright(ly) Colored Fish +
18 = Illustration +
19 = Band Logo +
20 = Publisher Logo
+
PictureMIMETypeno 
PictureDescriptionno 
Pictureno 
+ +

ASF ContentDescr Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Titleno 
Authorno 
Copyrightno 
Descriptionno 
Ratingno 
+ +

ASF CodecList Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AudioCodecDescriptionno 
AudioCodecNameno 
OtherCodecDescriptionno 
OtherCodecNameno 
VideoCodecDescriptionno 
VideoCodecNameno 
+ +

ASF FileProperties Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FileIDno 
16FileLengthno 
24CreationDateno 
32DataPacketsno 
40Durationno(called PlayDuration by the ASF spec)
48SendDurationno 
56Prerollno 
64Flagsno 
68MinPacketSizeno 
72MaxPacketSizeno 
76MaxBitrateno 
+ +

ASF StreamProperties Tags

+

Tags with index 54 and greater are conditional based on the StreamType.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0StreamTypeno'35907DE0-E415-11CF-A917-00805F5C442B' = Degradable JPEG +
'3AFB65E2-47EF-40F2-AC2C-70A90D71D343' = Binary +
'59DACFC0-59E6-11D0-A3AC-00A0C90348F6' = Command +
'91BD222C-F21C-497A-8B6D-5AA86BFC0185' = File Transfer +
'B61BE100-5B4E-11CF-A8FD-00805F5C442B' = JFIF +
'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B' = Video +
'F8699E40-5B4D-11CF-A8FD-00805F5C442B' = Audio
16ErrorCorrectionTypeno'20FB5700-5B55-11CF-A8FD-00805F5C442B' = No Error Correction +
'BFC3CD50-618F-11CF-8BB2-00AA00B4E220' = Audio Spread
32TimeOffsetno 
48StreamNumberno 
54AudioCodecID +
ImageWidth
no
no
--> RIFF AudioEncoding Values
56AudioChannelsno 
58AudioSampleRate +
ImageHeight
no
no
 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jan 15, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Apple.html b/ExifTool/html/TagNames/Apple.html new file mode 100644 index 0000000..5462eec --- /dev/null +++ b/ExifTool/html/TagNames/Apple.html @@ -0,0 +1,242 @@ + + + + +Apple Tags + + + +

Apple Tags

+

Tags extracted from the maker notes of iPhone images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001MakerNoteVersionint32s 
0x0002AEMatrix?no 
0x0003RunTime---> Apple RunTime Tags
0x0004AEStableint32s0 = No +
1 = Yes
0x0005AETargetint32s 
0x0006AEAverageint32s 
0x0007AFStableint32s0 = No +
1 = Yes
0x0008AccelerationVectorrational64s[3](XYZ coordinates of the acceleration vector in units of g. As viewed from +the front of the phone, positive X is toward the left side, positive Y is +toward the bottom, and positive Z points into the face of the phone)
0x000aHDRImageTypeint32s3 = HDR Image +
4 = Original Image
0x000bBurstUUIDstring(unique ID for all images in a burst)
0x000cFocusDistanceRangerational64s[2] 
0x000fOISModeint32s 
0x0011ContentIdentifierstring(called MediaGroupUUID when it appears as an XAttr)
0x0014ImageCaptureTypeint32s1 = ProRAW +
2 = Portrait +
10 = Photo
0x0015ImageUniqueIDstring 
0x0017LivePhotoVideoIndexyes(divide by RunTimeScale to get time in seconds)
0x0019ImageProcessingFlags?int32s 
0x001aQualityHint?string 
0x001dLuminanceNoiseAmplituderational64s 
0x0020ImageCaptureRequestID?string 
0x0021HDRHeadroomrational64s 
0x0025SceneFlags?int32s 
0x0026SignalToNoiseRatioType?int32s 
0x0027SignalToNoiseRatiorational64s 
0x002bPhotoIdentifierstring 
0x002fFocusPositionint32s 
0x0030HDRGainrational64s 
0x0038AFMeasuredDepthint32s(from the time-of-flight-assisted auto-focus estimator)
0x003dAFConfidenceint32s 
0x003eColorCorrectionMatrix?no 
0x003fGreenGhostMitigationStatus?int32s 
0x0040SemanticStyleno 
0x0041SemanticStyleRenderingVerno 
0x0042SemanticStylePresetno 
0x0045FrontFacingCameraint32s0 = No +
1 = Yes
+ +

Apple RunTime Tags

+

This PLIST-format information contains the elements of a CMTime structure +representing the amount of time the phone has been running since the last +boot, not including standby time.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'epoch'RunTimeEpochno 
'flags'RunTimeFlagsnoBit 0 = Valid +
Bit 1 = Has been rounded +
Bit 2 = Positive infinity +
Bit 3 = Negative infinity +
Bit 4 = Indefinite
'timescale'RunTimeScaleno 
'value'RunTimeValueno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 19, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Audible.html b/ExifTool/html/TagNames/Audible.html new file mode 100644 index 0000000..dd1cca4 --- /dev/null +++ b/ExifTool/html/TagNames/Audible.html @@ -0,0 +1,178 @@ + + + + +Audible Tags + + + +

Audible Tags

+

ExifTool will extract any information found in the metadata dictionary of +Audible .AA files, even if not listed in the table below.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'_chapter_count'ChapterCountno 
'_cover_art'CoverArtno 
'author'Authorno 
'copyright'Copyrightno 
'pub_date_start'PublishDateStartno 
'pubdate'PublishDateno 
+ +

Audible tags Tags

+

Information found in "tags" atom of Audible M4B audio books.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'cvrx'Audible_cvrx---> Audible cvrx Tags
'meta'Audible_meta---> Audible meta Tags
'tseg'Audible_tseg---> Audible tseg Tags
+ +

Audible cvrx Tags

+

Audible cover art information in M4B audio books.

+
+
+ + + + + + + + + + + +
Tag NameWritableValues / Notes
CoverArtno 
CoverArtTypeno 
+ +

Audible meta Tags

+

Information found in Audible M4B "meta" atom.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ALBUMARTIST'AlbumArtistno 
'Album'Albumno 
'Artist'Artistno 
'Comment'Commentno 
'Genre'Genreno 
'SUBTITLE'Subtitleno 
'TOOL'CreatorToolno 
'Title'Titleno 
'Year'Yearno 
'itunesmediatype'iTunesMediaTypeno 
'track'ChapterNameno 
+ +

Audible tseg Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'meta'Audible_meta2---> Audible meta Tags
'tshd'ChapterNumberno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Apr 30, 2019 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/BMP.html b/ExifTool/html/TagNames/BMP.html new file mode 100644 index 0000000..34dc544 --- /dev/null +++ b/ExifTool/html/TagNames/BMP.html @@ -0,0 +1,234 @@ + + + + +BMP Tags + + + +

BMP Tags

+

There really isn't much meta information in a BMP file as such, just a bit +of image related information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0BMPVersionno(this is actually the size of the BMP header, but used to determine the BMP +version) +
40 = Windows V3 +
68 = AVI BMP structure? +
108 = Windows V4 +
124 = Windows V5
4ImageWidthno 
8ImageHeightno 
12Planesno 
14BitDepthno 
16Compressionno + +
0 = None +
1 = 8-Bit RLE +
2 = 4-Bit RLE
  3 = Bitfields +
4 = JPEG +
5 = PNG
+
20ImageLengthno 
24PixelsPerMeterXno 
28PixelsPerMeterYno 
32NumColorsno 
36NumImportantColorsno 
40RedMaskno 
44GreenMaskno 
48BlueMaskno 
52AlphaMaskno 
56ColorSpaceno +
0 = Calibrated RGB +
1 = Device RGB +
2 = Device CMYK +
'LINK' = Linked Color Profile +
'MBED' = Embedded Color Profile +
'Win ' = Windows Color Space +
'sRGB' = sRGB
+
60RedEndpointno 
72GreenEndpointno 
84BlueEndpointno 
96GammaRedno 
100GammaGreenno 
104GammaBlueno 
108RenderingIntentno1 = Graphic (LCS_GM_BUSINESS) +
2 = Proof (LCS_GM_GRAPHICS) +
4 = Picture (LCS_GM_IMAGES) +
8 = Absolute Colorimetric (LCS_GM_ABS_COLORIMETRIC)
112ProfileDataOffsetno 
116ProfileSizeno 
+ +

BMP OS2 Tags

+

Information extracted from OS/2-format BMP images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0BMPVersionno(again, the header size is used to determine the BMP version) +
12 = OS/2 V1 +
64 = OS/2 V2
4ImageWidthno 
6ImageHeightno 
8Planesno 
10BitDepthno 
+ +

BMP Extra Tags

+

Extra information extracted from some BMP images.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
EmbeddedJPGno 
EmbeddedPNGno 
ICC_Profile---> ICC_Profile Tags
LinkedProfileNameno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Dec 7, 2016 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/BPG.html b/ExifTool/html/TagNames/BPG.html new file mode 100644 index 0000000..bc75d43 --- /dev/null +++ b/ExifTool/html/TagNames/BPG.html @@ -0,0 +1,119 @@ + + + + +BPG Tags + + + +

BPG Tags

+

The information listed below is extracted from BPG (Better Portable +Graphics) images. See http://bellard.org/bpg/ for the specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4PixelFormatno[val >> 13 & 0x7] +
0 = Grayscale +
1 = 4:2:0 (chroma at 0.5, 0.5) +
2 = 4:2:2 (chroma at 0.5, 0) +
3 = 4:4:4 +
4 = 4:2:0 (chroma at 0, 0.5) +
5 = 4:2:2 (chroma at 0, 0)
+
4.1Alphano[val & 0x1004] +
0x0 = No Alpha Plane +
0x4 = Alpha Exists (W color component) +
0x1000 = Alpha Exists (color not premultiplied) +
0x1004 = Alpha Exists (color premultiplied)
4.2BitDepthno[val >> 8 & 0xf]
4.3ColorSpaceno[val >> 4 & 0xf] +
0 = YCbCr (BT 601) +
1 = RGB +
2 = YCgCo +
3 = YCbCr (BT 709) +
4 = YCbCr (BT 2020) +
5 = BT 2020 Constant Luminance
+
4.4Flagsno[val & 0xb] +
Bit 0 = Animation +
Bit 1 = Limited Range +
Bit 3 = Extension Present
6ImageWidthno 
7ImageHeightno 
8ImageLengthno 
+ +

BPG Extensions Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001EXIF---> EXIF Tags
0x0002ICC_Profile---> ICC_Profile Tags
0x0003XMP---> XMP Tags
0x0004ThumbnailBPGno 
0x0005AnimationControl?no 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 24, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/CBOR.html b/ExifTool/html/TagNames/CBOR.html new file mode 100644 index 0000000..9df9fae --- /dev/null +++ b/ExifTool/html/TagNames/CBOR.html @@ -0,0 +1,62 @@ + + + + +CBOR Tags + + + +

CBOR Tags

+

The tags below are extracted from CBOR (Concise Binary Object +Representation) metadata. The C2PA specification uses this format for some +metadata. As well as these tags, ExifTool will read any existing tags.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AuthorIdentifierno 
AuthorNameno 
DocumentIDno 
Formatno 
InstanceIDno 
Relationshipno 
ThumbnailHashno+ 
ThumbnailURLno 
Titleno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 30, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Canon.html b/ExifTool/html/TagNames/Canon.html new file mode 100644 index 0000000..6f714d7 --- /dev/null +++ b/ExifTool/html/TagNames/Canon.html @@ -0,0 +1,11695 @@ + + + + +Canon Tags + + + +

Canon Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001CanonCameraSettings---> Canon CameraSettings Tags
0x0002CanonFocalLength---> Canon FocalLength Tags
0x0003CanonFlashInfo?no 
0x0004CanonShotInfo---> Canon ShotInfo Tags
0x0005CanonPanorama---> Canon Panorama Tags
0x0006CanonImageTypestring 
0x0007CanonFirmwareVersionstring 
0x0008FileNumberint32u 
0x0009OwnerNamestring 
0x000aUnknownD30---> Canon UnknownD30 Tags
0x000cSerialNumberint32u 
0x000dCanonCameraInfo1D +
CanonCameraInfo1DmkII +
CanonCameraInfo1DmkIIN +
CanonCameraInfo1DmkIII +
CanonCameraInfo1DmkIV +
CanonCameraInfo1DX +
CanonCameraInfo5D +
CanonCameraInfo5DmkII +
CanonCameraInfo5DmkIII +
CanonCameraInfo6D +
CanonCameraInfo7D +
CanonCameraInfo40D +
CanonCameraInfo50D +
CanonCameraInfo60D +
CanonCameraInfo70D +
CanonCameraInfo80D +
CanonCameraInfo450D +
CanonCameraInfo500D +
CanonCameraInfo550D +
CanonCameraInfo600D +
CanonCameraInfo650D +
CanonCameraInfo700D +
CanonCameraInfo750D +
CanonCameraInfo760D +
CanonCameraInfo1000D +
CanonCameraInfo1100D +
CanonCameraInfo1200D +
CanonCameraInfoPowerShot +
CanonCameraInfoPowerShot2 +
CanonCameraInfoUnknown32 +
CanonCameraInfoUnknown16 +
CanonCameraInfoUnknown
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--> Canon CameraInfo1D Tags +
--> Canon CameraInfo1DmkII Tags +
--> Canon CameraInfo1DmkIIN Tags +
--> Canon CameraInfo1DmkIII Tags +
--> Canon CameraInfo1DmkIV Tags +
--> Canon CameraInfo1DX Tags +
--> Canon CameraInfo5D Tags +
--> Canon CameraInfo5DmkII Tags +
--> Canon CameraInfo5DmkIII Tags +
--> Canon CameraInfo6D Tags +
--> Canon CameraInfo7D Tags +
--> Canon CameraInfo40D Tags +
--> Canon CameraInfo50D Tags +
--> Canon CameraInfo60D Tags +
--> Canon CameraInfo70D Tags +
--> Canon CameraInfo80D Tags +
--> Canon CameraInfo450D Tags +
--> Canon CameraInfo500D Tags +
--> Canon CameraInfo550D Tags +
--> Canon CameraInfo600D Tags +
--> Canon CameraInfo650D Tags +
--> Canon CameraInfo650D Tags +
--> Canon CameraInfo750D Tags +
--> Canon CameraInfo750D Tags +
--> Canon CameraInfo1000D Tags +
--> Canon CameraInfo600D Tags +
--> Canon CameraInfo60D Tags +
--> Canon CameraInfoPowerShot Tags +
--> Canon CameraInfoPowerShot2 Tags +
--> Canon CameraInfoUnknown32 Tags +
--> Canon CameraInfoUnknown16 Tags +
--> Canon CameraInfoUnknown Tags
0x000eCanonFileLengthint32u 
0x000fCustomFunctions1D +
CustomFunctions5D +
CustomFunctions10D +
CustomFunctions20D +
CustomFunctions30D +
CustomFunctions350D +
CustomFunctions400D +
CustomFunctionsD30 +
CustomFunctionsD60 +
CustomFunctionsUnknown
-
-
-
-
-
-
-
-
-
-
--> CanonCustom Functions1D Tags +
--> CanonCustom Functions5D Tags +
--> CanonCustom Functions10D Tags +
--> CanonCustom Functions20D Tags +
--> CanonCustom Functions30D Tags +
--> CanonCustom Functions350D Tags +
--> CanonCustom Functions400D Tags +
--> CanonCustom FunctionsD30 Tags +
--> CanonCustom FunctionsD30 Tags +
--> CanonCustom FuncsUnknown Tags
0x0010CanonModelIDint32u--> Canon CanonModelID Values
0x0011MovieInfo---> Canon MovieInfo Tags
0x0012CanonAFInfo---> Canon AFInfo Tags
0x0013ThumbnailImageValidAreaint16u[4](all zeros for full frame)
0x0015SerialNumberFormatint32u0x90000000 = Format 1 +
0xa0000000 = Format 2
0x001aSuperMacroint16u0 = Off +
1 = On (1) +
2 = On (2)
0x001cDateStampModeint16u(used only in postcard mode) +
0 = Off +
1 = Date +
2 = Date & Time
0x001dMyColors---> Canon MyColors Tags
0x001eFirmwareRevisionint32u 
0x0023Categoriesint32u[2](2 values: 1. always 8, 2. Categories) + +
0x0 = (none) +
Bit 0 = People +
Bit 1 = Scenery +
Bit 2 = Events
  Bit 3 = User 1 +
Bit 4 = User 2 +
Bit 5 = User 3 +
Bit 6 = To Do
+
0x0024FaceDetect1---> Canon FaceDetect1 Tags
0x0025FaceDetect2---> Canon FaceDetect2 Tags
0x0026CanonAFInfo2---> Canon AFInfo2 Tags
0x0027ContrastInfo---> Canon ContrastInfo Tags
0x0028ImageUniqueIDint8u 
0x0029WBInfo---> Canon WBInfo Tags
0x002fFaceDetect3---> Canon FaceDetect3 Tags
0x0035TimeInfo---> Canon TimeInfo Tags
0x0038BatteryTypeundef 
0x003cAFInfo3---> Canon AFInfo2 Tags
0x0081RawDataOffsetno 
0x0082RawDataLengthno 
0x0083OriginalDecisionDataOffsetint32u* 
0x0090CustomFunctions1D---> CanonCustom Functions1D Tags
0x0091PersonalFunctions---> CanonCustom PersonalFuncs Tags
0x0092PersonalFunctionValues---> CanonCustom PersonalFuncValues Tags
0x0093CanonFileInfo---> Canon FileInfo Tags
0x0094AFPointsInFocus1Dno(EOS 1D -- 5 rows: A1-7, B1-10, C1-11, D1-10, E1-7, center point is C6)
0x0095LensModelstring 
0x0096SerialInfo +
InternalSerialNumber
-
string
--> Canon SerialInfo Tags
0x0097DustRemovalDataundef! 
0x0098CropInfo---> Canon CropInfo Tags
0x0099CustomFunctions2---> CanonCustom Functions2 Tags
0x009aAspectInfo---> Canon AspectInfo Tags
0x00a0ProcessingInfo---> Canon Processing Tags
0x00a1ToneCurveTableno 
0x00a2SharpnessTableno 
0x00a3SharpnessFreqTableno 
0x00a4WhiteBalanceTableno 
0x00a9ColorBalance---> Canon ColorBalance Tags
0x00aaMeasuredColor---> Canon MeasuredColor Tags
0x00aeColorTemperatureint16u 
0x00b0CanonFlags---> Canon Flags Tags
0x00b1ModifiedInfo---> Canon ModifiedInfo Tags
0x00b2ToneCurveMatchingno 
0x00b3WhiteBalanceMatchingno 
0x00b4ColorSpaceint16u1 = sRGB +
2 = Adobe RGB +
65535 = n/a
0x00b6PreviewImageInfo---> Canon PreviewImageInfo Tags
0x00d0VRDOffsetint32u*(offset of VRD "recipe data" if it exists)
0x00e0SensorInfo---> Canon SensorInfo Tags
0x4001ColorData1 +
ColorData2 +
ColorData3 +
ColorData4 +
ColorData5 +
ColorData6 +
ColorData7 +
ColorData8 +
ColorData9 +
ColorData10 +
ColorData11 +
ColorDataUnknown
-
-
-
-
-
-
-
-
-
-
-
-
--> Canon ColorData1 Tags +
--> Canon ColorData2 Tags +
--> Canon ColorData3 Tags +
--> Canon ColorData4 Tags +
--> Canon ColorData5 Tags +
--> Canon ColorData6 Tags +
--> Canon ColorData7 Tags +
--> Canon ColorData8 Tags +
--> Canon ColorData9 Tags +
--> Canon ColorData10 Tags +
--> Canon ColorData11 Tags +
--> Canon ColorDataUnknown Tags
0x4002CRWParam?no 
0x4003ColorInfo---> Canon ColorInfo Tags
0x4005Flavor?no(unknown 49kB block, not copied to JPEG images)
0x4008PictureStyleUserDefint16u[3][Values 0-2] +
--> Canon PictureStyle Values
0x4009PictureStylePCint16u[3][Values 0-2] +
--> Canon PictureStyle Values
0x4010CustomPictureStyleFileNamestring 
0x4013AFMicroAdj---> Canon AFMicroAdj Tags
0x4015VignettingCorr +
VignettingCorrUnknown1 +
VignettingCorrUnknown2
-
-
-
--> Canon VignettingCorr Tags +
--> Canon VignettingCorrUnknown Tags +
--> Canon VignettingCorrUnknown Tags
0x4016VignettingCorr2---> Canon VignettingCorr2 Tags
0x4018LightingOpt---> Canon LightingOpt Tags
0x4019LensInfo---> Canon LensInfo Tags
0x4020AmbienceInfo---> Canon Ambience Tags
0x4021MultiExp---> Canon MultiExp Tags
0x4024FilterInfo---> Canon FilterInfo Tags
0x4025HDRInfo---> Canon HDRInfo Tags
0x4026LogInfo---> Canon LogInfo Tags
0x4028AFConfig---> Canon AFConfig Tags
0x403fRawBurstModeRoll---> Canon RawBurstInfo Tags
+ +

Canon CanonModelID Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueCanonModelID
0x412= EOS M50 / Kiss M
0x801= PowerShot SX740 HS
0x804= PowerShot G5 X Mark II
0x805= PowerShot SX70 HS
0x808= PowerShot G7 X Mark III
0x811= EOS M6 Mark II
0x812= EOS M200
0x1010000= PowerShot A30
0x1040000= PowerShot S300 / Digital IXUS 300 / IXY Digital 300
0x1060000= PowerShot A20
0x1080000= PowerShot A10
0x1090000= PowerShot S110 / Digital IXUS v / IXY Digital 200
0x1100000= PowerShot G2
0x1110000= PowerShot S40
0x1120000= PowerShot S30
0x1130000= PowerShot A40
0x1140000= EOS D30
0x1150000= PowerShot A100
0x1160000= PowerShot S200 / Digital IXUS v2 / IXY Digital 200a
0x1170000= PowerShot A200
0x1180000= PowerShot S330 / Digital IXUS 330 / IXY Digital 300a
0x1190000= PowerShot G3
0x1210000= PowerShot S45
0x1230000= PowerShot SD100 / Digital IXUS II / IXY Digital 30
0x1240000= PowerShot S230 / Digital IXUS v3 / IXY Digital 320
0x1250000= PowerShot A70
0x1260000= PowerShot A60
0x1270000= PowerShot S400 / Digital IXUS 400 / IXY Digital 400
0x1290000= PowerShot G5
0x1300000= PowerShot A300
0x1310000= PowerShot S50
0x1340000= PowerShot A80
0x1350000= PowerShot SD10 / Digital IXUS i / IXY Digital L
0x1360000= PowerShot S1 IS
0x1370000= PowerShot Pro1
0x1380000= PowerShot S70
0x1390000= PowerShot S60
0x1400000= PowerShot G6
0x1410000= PowerShot S500 / Digital IXUS 500 / IXY Digital 500
0x1420000= PowerShot A75
0x1440000= PowerShot SD110 / Digital IXUS IIs / IXY Digital 30a
0x1450000= PowerShot A400
0x1470000= PowerShot A310
0x1490000= PowerShot A85
0x1520000= PowerShot S410 / Digital IXUS 430 / IXY Digital 450
0x1530000= PowerShot A95
0x1540000= PowerShot SD300 / Digital IXUS 40 / IXY Digital 50
0x1550000= PowerShot SD200 / Digital IXUS 30 / IXY Digital 40
0x1560000= PowerShot A520
0x1570000= PowerShot A510
0x1590000= PowerShot SD20 / Digital IXUS i5 / IXY Digital L2
0x1640000= PowerShot S2 IS
0x1650000= PowerShot SD430 / Digital IXUS Wireless / IXY Digital Wireless
0x1660000= PowerShot SD500 / Digital IXUS 700 / IXY Digital 600
0x1668000= EOS D60
0x1700000= PowerShot SD30 / Digital IXUS i Zoom / IXY Digital L3
0x1740000= PowerShot A430
0x1750000= PowerShot A410
0x1760000= PowerShot S80
0x1780000= PowerShot A620
0x1790000= PowerShot A610
0x1800000= PowerShot SD630 / Digital IXUS 65 / IXY Digital 80
0x1810000= PowerShot SD450 / Digital IXUS 55 / IXY Digital 60
0x1820000= PowerShot TX1
0x1870000= PowerShot SD400 / Digital IXUS 50 / IXY Digital 55
0x1880000= PowerShot A420
0x1890000= PowerShot SD900 / Digital IXUS 900 Ti / IXY Digital 1000
0x1900000= PowerShot SD550 / Digital IXUS 750 / IXY Digital 700
0x1920000= PowerShot A700
0x1940000= PowerShot SD700 IS / Digital IXUS 800 IS / IXY Digital 800 IS
0x1950000= PowerShot S3 IS
0x1960000= PowerShot A540
0x1970000= PowerShot SD600 / Digital IXUS 60 / IXY Digital 70
0x1980000= PowerShot G7
0x1990000= PowerShot A530
0x2000000= PowerShot SD800 IS / Digital IXUS 850 IS / IXY Digital 900 IS
0x2010000= PowerShot SD40 / Digital IXUS i7 / IXY Digital L4
0x2020000= PowerShot A710 IS
0x2030000= PowerShot A640
0x2040000= PowerShot A630
0x2090000= PowerShot S5 IS
0x2100000= PowerShot A460
0x2120000= PowerShot SD850 IS / Digital IXUS 950 IS / IXY Digital 810 IS
0x2130000= PowerShot A570 IS
0x2140000= PowerShot A560
0x2150000= PowerShot SD750 / Digital IXUS 75 / IXY Digital 90
0x2160000= PowerShot SD1000 / Digital IXUS 70 / IXY Digital 10
0x2180000= PowerShot A550
0x2190000= PowerShot A450
0x2230000= PowerShot G9
0x2240000= PowerShot A650 IS
0x2260000= PowerShot A720 IS
0x2290000= PowerShot SX100 IS
0x2300000= PowerShot SD950 IS / Digital IXUS 960 IS / IXY Digital 2000 IS
0x2310000= PowerShot SD870 IS / Digital IXUS 860 IS / IXY Digital 910 IS
0x2320000= PowerShot SD890 IS / Digital IXUS 970 IS / IXY Digital 820 IS
0x2360000= PowerShot SD790 IS / Digital IXUS 90 IS / IXY Digital 95 IS
0x2370000= PowerShot SD770 IS / Digital IXUS 85 IS / IXY Digital 25 IS
0x2380000= PowerShot A590 IS
0x2390000= PowerShot A580
0x2420000= PowerShot A470
0x2430000= PowerShot SD1100 IS / Digital IXUS 80 IS / IXY Digital 20 IS
0x2460000= PowerShot SX1 IS
0x2470000= PowerShot SX10 IS
0x2480000= PowerShot A1000 IS
0x2490000= PowerShot G10
0x2510000= PowerShot A2000 IS
0x2520000= PowerShot SX110 IS
0x2530000= PowerShot SD990 IS / Digital IXUS 980 IS / IXY Digital 3000 IS
0x2540000= PowerShot SD880 IS / Digital IXUS 870 IS / IXY Digital 920 IS
0x2550000= PowerShot E1
0x2560000= PowerShot D10
0x2570000= PowerShot SD960 IS / Digital IXUS 110 IS / IXY Digital 510 IS
0x2580000= PowerShot A2100 IS
0x2590000= PowerShot A480
0x2600000= PowerShot SX200 IS
0x2610000= PowerShot SD970 IS / Digital IXUS 990 IS / IXY Digital 830 IS
0x2620000= PowerShot SD780 IS / Digital IXUS 100 IS / IXY Digital 210 IS
0x2630000= PowerShot A1100 IS
0x2640000= PowerShot SD1200 IS / Digital IXUS 95 IS / IXY Digital 110 IS
0x2700000= PowerShot G11
0x2710000= PowerShot SX120 IS
0x2720000= PowerShot S90
0x2750000= PowerShot SX20 IS
0x2760000= PowerShot SD980 IS / Digital IXUS 200 IS / IXY Digital 930 IS
0x2770000= PowerShot SD940 IS / Digital IXUS 120 IS / IXY Digital 220 IS
0x2800000= PowerShot A495
0x2810000= PowerShot A490
0x2820000= PowerShot A3100/A3150 IS
0x2830000= PowerShot A3000 IS
0x2840000= PowerShot SD1400 IS / IXUS 130 / IXY 400F
0x2850000= PowerShot SD1300 IS / IXUS 105 / IXY 200F
0x2860000= PowerShot SD3500 IS / IXUS 210 / IXY 10S
0x2870000= PowerShot SX210 IS
0x2880000= PowerShot SD4000 IS / IXUS 300 HS / IXY 30S
0x2890000= PowerShot SD4500 IS / IXUS 1000 HS / IXY 50S
0x2920000= PowerShot G12
0x2930000= PowerShot SX30 IS
0x2940000= PowerShot SX130 IS
0x2950000= PowerShot S95
0x2980000= PowerShot A3300 IS
0x2990000= PowerShot A3200 IS
0x3000000= PowerShot ELPH 500 HS / IXUS 310 HS / IXY 31S
0x3010000= PowerShot Pro90 IS
0x3010001= PowerShot A800
0x3020000= PowerShot ELPH 100 HS / IXUS 115 HS / IXY 210F
0x3030000= PowerShot SX230 HS
0x3040000= PowerShot ELPH 300 HS / IXUS 220 HS / IXY 410F
0x3050000= PowerShot A2200
0x3060000= PowerShot A1200
0x3070000= PowerShot SX220 HS
0x3080000= PowerShot G1 X
0x3090000= PowerShot SX150 IS
0x3100000= PowerShot ELPH 510 HS / IXUS 1100 HS / IXY 51S
0x3110000= PowerShot S100 (new)
0x3120000= PowerShot ELPH 310 HS / IXUS 230 HS / IXY 600F
0x3130000= PowerShot SX40 HS
0x3140000= IXY 32S
0x3160000= PowerShot A1300
0x3170000= PowerShot A810
0x3180000= PowerShot ELPH 320 HS / IXUS 240 HS / IXY 420F
0x3190000= PowerShot ELPH 110 HS / IXUS 125 HS / IXY 220F
0x3200000= PowerShot D20
0x3210000= PowerShot A4000 IS
0x3220000= PowerShot SX260 HS
0x3230000= PowerShot SX240 HS
0x3240000= PowerShot ELPH 530 HS / IXUS 510 HS / IXY 1
0x3250000= PowerShot ELPH 520 HS / IXUS 500 HS / IXY 3
0x3260000= PowerShot A3400 IS
0x3270000= PowerShot A2400 IS
0x3280000= PowerShot A2300
0x3320000= PowerShot S100V
0x3330000= PowerShot G15
0x3340000= PowerShot SX50 HS
0x3350000= PowerShot SX160 IS
0x3360000= PowerShot S110 (new)
0x3370000= PowerShot SX500 IS
0x3380000= PowerShot N
0x3390000= IXUS 245 HS / IXY 430F
0x3400000= PowerShot SX280 HS
0x3410000= PowerShot SX270 HS
0x3420000= PowerShot A3500 IS
0x3430000= PowerShot A2600
0x3440000= PowerShot SX275 HS
0x3450000= PowerShot A1400
0x3460000= PowerShot ELPH 130 IS / IXUS 140 / IXY 110F
0x3470000= PowerShot ELPH 115/120 IS / IXUS 132/135 / IXY 90F/100F
0x3490000= PowerShot ELPH 330 HS / IXUS 255 HS / IXY 610F
0x3510000= PowerShot A2500
0x3540000= PowerShot G16
0x3550000= PowerShot S120
0x3560000= PowerShot SX170 IS
0x3580000= PowerShot SX510 HS
0x3590000= PowerShot S200 (new)
0x3600000= IXY 620F
0x3610000= PowerShot N100
0x3640000= PowerShot G1 X Mark II
0x3650000= PowerShot D30
0x3660000= PowerShot SX700 HS
0x3670000= PowerShot SX600 HS
0x3680000= PowerShot ELPH 140 IS / IXUS 150 / IXY 130
0x3690000= PowerShot ELPH 135 / IXUS 145 / IXY 120
0x3700000= PowerShot ELPH 340 HS / IXUS 265 HS / IXY 630
0x3710000= PowerShot ELPH 150 IS / IXUS 155 / IXY 140
0x3740000= EOS M3
0x3750000= PowerShot SX60 HS
0x3760000= PowerShot SX520 HS
0x3770000= PowerShot SX400 IS
0x3780000= PowerShot G7 X
0x3790000= PowerShot N2
0x3800000= PowerShot SX530 HS
0x3820000= PowerShot SX710 HS
0x3830000= PowerShot SX610 HS
0x3840000= EOS M10
0x3850000= PowerShot G3 X
0x3860000= PowerShot ELPH 165 HS / IXUS 165 / IXY 160
0x3870000= PowerShot ELPH 160 / IXUS 160
0x3880000= PowerShot ELPH 350 HS / IXUS 275 HS / IXY 640
0x3890000= PowerShot ELPH 170 IS / IXUS 170
0x3910000= PowerShot SX410 IS
0x3930000= PowerShot G9 X
0x3940000= EOS M5
0x3950000= PowerShot G5 X
0x3970000= PowerShot G7 X Mark II
0x3980000= EOS M100
0x3990000= PowerShot ELPH 360 HS / IXUS 285 HS / IXY 650
0x4010000= PowerShot SX540 HS
0x4020000= PowerShot SX420 IS
0x4030000= PowerShot ELPH 190 IS / IXUS 180 / IXY 190
0x4040000= PowerShot G1
0x4040001= PowerShot ELPH 180 IS / IXUS 175 / IXY 180
0x4050000= PowerShot SX720 HS
0x4060000= PowerShot SX620 HS
0x4070000= EOS M6
0x4100000= PowerShot G9 X Mark II
0x4150000= PowerShot ELPH 185 / IXUS 185 / IXY 200
0x4160000= PowerShot SX430 IS
0x4170000= PowerShot SX730 HS
0x4180000= PowerShot G1 X Mark III
0x6040000= PowerShot S100 / Digital IXUS / IXY Digital
0x4007d673= DC19/DC21/DC22
0x4007d674= XH A1
0x4007d675= HV10
0x4007d676= MD130/MD140/MD150/MD160/ZR850
0x4007d777= DC50
0x4007d778= HV20
0x4007d779= DC211
0x4007d77a= HG10
0x4007d77b= HR10
0x4007d77d= MD255/ZR950
0x4007d81c= HF11
0x4007d878= HV30
0x4007d87c= XH A1S
0x4007d87e= DC301/DC310/DC311/DC320/DC330
0x4007d87f= FS100
0x4007d880= HF10
0x4007d882= HG20/HG21
0x4007d925= HF21
0x4007d926= HF S11
0x4007d978= HV40
0x4007d987= DC410/DC411/DC420
0x4007d988= FS19/FS20/FS21/FS22/FS200
0x4007d989= HF20/HF200
0x4007d98a= HF S10/S100
0x4007da8e= HF R10/R16/R17/R18/R100/R106
0x4007da8f= HF M30/M31/M36/M300/M306
0x4007da90= HF S20/S21/S200
0x4007da92= FS31/FS36/FS37/FS300/FS305/FS306/FS307
0x4007dca0= EOS C300
0x4007dda9= HF G25
0x4007dfb4= XC10
0x4007e1c3= EOS C200
0x80000001= EOS-1D
0x80000167= EOS-1DS
0x80000168= EOS 10D
0x80000169= EOS-1D Mark III
0x80000170= EOS Digital Rebel / 300D / Kiss Digital
0x80000174= EOS-1D Mark II
0x80000175= EOS 20D
0x80000176= EOS Digital Rebel XSi / 450D / Kiss X2
0x80000188= EOS-1Ds Mark II
0x80000189= EOS Digital Rebel XT / 350D / Kiss Digital N
0x80000190= EOS 40D
0x80000213= EOS 5D
0x80000215= EOS-1Ds Mark III
0x80000218= EOS 5D Mark II
0x80000219= WFT-E1
0x80000232= EOS-1D Mark II N
0x80000234= EOS 30D
0x80000236= EOS Digital Rebel XTi / 400D / Kiss Digital X
0x80000241= WFT-E2
0x80000246= WFT-E3
0x80000250= EOS 7D
0x80000252= EOS Rebel T1i / 500D / Kiss X3
0x80000254= EOS Rebel XS / 1000D / Kiss F
0x80000261= EOS 50D
0x80000269= EOS-1D X
0x80000270= EOS Rebel T2i / 550D / Kiss X4
0x80000271= WFT-E4
0x80000273= WFT-E5
0x80000281= EOS-1D Mark IV
0x80000285= EOS 5D Mark III
0x80000286= EOS Rebel T3i / 600D / Kiss X5
0x80000287= EOS 60D
0x80000288= EOS Rebel T3 / 1100D / Kiss X50
0x80000289= EOS 7D Mark II
0x80000297= WFT-E2 II
0x80000298= WFT-E4 II
0x80000301= EOS Rebel T4i / 650D / Kiss X6i
0x80000302= EOS 6D
0x80000324= EOS-1D C
0x80000325= EOS 70D
0x80000326= EOS Rebel T5i / 700D / Kiss X7i
0x80000327= EOS Rebel T5 / 1200D / Kiss X70 / Hi
0x80000328= EOS-1D X Mark II
0x80000331= EOS M
0x80000346= EOS Rebel SL1 / 100D / Kiss X7
0x80000347= EOS Rebel T6s / 760D / 8000D
0x80000349= EOS 5D Mark IV
0x80000350= EOS 80D
0x80000355= EOS M2
0x80000382= EOS 5DS
0x80000393= EOS Rebel T6i / 750D / Kiss X8i
0x80000401= EOS 5DS R
0x80000404= EOS Rebel T6 / 1300D / Kiss X80
0x80000405= EOS Rebel T7i / 800D / Kiss X9i
0x80000406= EOS 6D Mark II
0x80000408= EOS 77D / 9000D
0x80000417= EOS Rebel SL2 / 200D / Kiss X9
0x80000421= EOS R5
0x80000422= EOS Rebel T100 / 4000D / 3000D
0x80000424= EOS R
0x80000428= EOS-1D X Mark III
0x80000432= EOS Rebel T7 / 2000D / 1500D / Kiss X90
0x80000433= EOS RP
0x80000435= EOS Rebel T8i / 850D / X10i
0x80000436= EOS SL3 / 250D / Kiss X10
0x80000437= EOS 90D
0x80000450= EOS R3
0x80000453= EOS R6
0x80000464= EOS R7
0x80000465= EOS R10
0x80000467= PowerShot ZOOM
0x80000468= EOS M50 Mark II / Kiss M2
0x80000480= EOS R50
0x80000481= EOS R6 Mark II
0x80000487= EOS R8
0x80000491= PowerShot V10
0x80000498= EOS R100
0x80000520= EOS D2000C
0x80000560= EOS D6000C
+ +

Canon PictureStyle Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ValuePictureStyleValuePictureStyleValuePictureStyle
0x0= None0x21= User Def. 10x83= Landscape
0x1= Standard0x22= User Def. 20x84= Neutral
0x2= Portrait0x23= User Def. 30x85= Faithful
0x3= High Saturation0x41= PC 10x86= Monochrome
0x4= Adobe RGB0x42= PC 20x87= Auto
0x5= Low Saturation0x43= PC 30x88= Fine Detail
0x6= CM Set 10x81= Standard0xff= n/a
0x7= CM Set 20x82= Portrait0xffff= n/a
+ +

Canon CameraSettings Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1MacroModeint16s1 = Macro +
2 = Normal
2SelfTimerint16s 
3Qualityint16s + +
-1 = n/a +
1 = Economy +
2 = Normal +
3 = Fine +
4 = RAW
  5 = Superfine +
7 = CRAW +
130 = Light (RAW) +
131 = Standard (RAW)
+
4CanonFlashModeint16s +
-1 = n/a +
0 = Off +
1 = Auto +
2 = On +
3 = Red-eye reduction +
4 = Slow-sync +
5 = Red-eye reduction (Auto) +
6 = Red-eye reduction (On) +
16 = External flash
+
5ContinuousDriveint16s +
0 = Single +
1 = Continuous +
2 = Movie +
3 = Continuous, Speed Priority +
4 = Continuous, Low +
5 = Continuous, High +
6 = Silent Single +
8 = Continuous, High+ +
9 = Single, Silent +
10 = Continuous, Silent
+
7FocusModeint16s + +
0 = One-shot AF +
1 = AI Servo AF +
2 = AI Focus AF +
3 = Manual Focus (3) +
4 = Single +
5 = Continuous
  6 = Manual Focus (6) +
16 = Pan Focus +
256 = AF + MF +
257 = Live View +
512 = Movie Snap Focus +
519 = Movie Servo AF
+
9RecordModeint16s + + +
1 = JPEG +
2 = CRW+THM +
3 = AVI+THM +
4 = TIF +
5 = TIF+JPEG
  6 = CR2 +
7 = CR2+JPEG +
9 = MOV +
10 = MP4 +
11 = CRM
  12 = CR3 +
13 = CR3+JPEG +
14 = HIF +
15 = CR3+HIF
+
10CanonImageSizeint16s + +
-1 = n/a +
0 = Large +
1 = Medium +
2 = Small +
5 = Medium 1 +
6 = Medium 2 +
7 = Medium 3 +
8 = Postcard +
9 = Widescreen +
10 = Medium Widescreen
  14 = Small 1 +
15 = Small 2 +
16 = Small 3 +
128 = 640x480 Movie +
129 = Medium Movie +
130 = Small Movie +
137 = 1280x720 Movie +
142 = 1920x1080 Movie +
143 = 4096x2160 Movie
+
11EasyModeint16s + + +
0 = Full auto +
1 = Manual +
2 = Landscape +
3 = Fast shutter +
4 = Slow shutter +
5 = Night +
6 = Gray Scale +
7 = Sepia +
8 = Portrait +
9 = Sports +
10 = Macro +
11 = Black & White +
12 = Pan focus +
13 = Vivid +
14 = Neutral +
15 = Flash Off +
16 = Long Shutter +
17 = Super Macro +
18 = Foliage +
19 = Indoor +
20 = Fireworks +
21 = Beach +
22 = Underwater +
23 = Snow +
24 = Kids & Pets
  25 = Night Snapshot +
26 = Digital Macro +
27 = My Colors +
28 = Movie Snap +
29 = Super Macro 2 +
30 = Color Accent +
31 = Color Swap +
32 = Aquarium +
33 = ISO 3200 +
34 = ISO 6400 +
35 = Creative Light Effect +
36 = Easy +
37 = Quick Shot +
38 = Creative Auto +
39 = Zoom Blur +
40 = Low Light +
41 = Nostalgic +
42 = Super Vivid +
43 = Poster Effect +
44 = Face Self-timer +
45 = Smile +
46 = Wink Self-timer +
47 = Fisheye Effect +
48 = Miniature Effect +
49 = High-speed Burst
  50 = Best Image Selection +
51 = High Dynamic Range +
52 = Handheld Night Scene +
53 = Movie Digest +
54 = Live View Control +
55 = Discreet +
56 = Blur Reduction +
57 = Monochrome +
58 = Toy Camera Effect +
59 = Scene Intelligent Auto +
60 = High-speed Burst HQ +
61 = Smooth Skin +
62 = Soft Focus +
68 = Food +
84 = HDR Art Standard +
85 = HDR Art Vivid +
93 = HDR Art Bold +
257 = Spotlight +
258 = Night 2 +
259 = Night+ +
260 = Super Night +
261 = Sunset +
263 = Night Scene +
264 = Surface +
265 = Low Light 2
+
12DigitalZoomint16s0 = None +
1 = 2x +
2 = 4x +
3 = Other
13Contrastint16s0 = Normal
14Saturationint16s0 = Normal
15Sharpnessint16s(some models use a range of -2 to +2 where 0 is normal sharpening, and +others use a range of 0 to 7 where 0 is no sharpening)
16CameraISOint16s 
17MeteringModeint16s +
0 = Default +
1 = Spot +
2 = Average +
3 = Evaluative +
4 = Partial +
5 = Center-weighted average
+
18FocusRangeint16s + +
0 = Manual +
1 = Auto +
2 = Not Known +
3 = Macro +
4 = Very Close +
5 = Close
  6 = Middle Range +
7 = Far Range +
8 = Pan Focus +
9 = Super Macro +
10 = Infinity
+
19AFPointint16s +
0x2005 = Manual AF point selection +
0x3000 = None (MF) +
0x3001 = Auto AF point selection +
0x3002 = Right +
0x3003 = Center +
0x3004 = Left +
0x4001 = Auto AF point selection +
0x4006 = Face Detect
+
20CanonExposureModeint16s +
0 = Easy +
1 = Program AE +
2 = Shutter speed priority AE +
3 = Aperture-priority AE +
4 = Manual +
5 = Depth-of-field AE +
6 = M-Dep +
7 = Bulb +
8 = Flexible-priority AE
+
22LensTypeint16u--> Canon LensType Values +
(this value is incorrect for EOS 7D images with lenses of type 256 or greater)
23MaxFocalLengthint16u 
24MinFocalLengthint16u 
25FocalUnitsint16s 
26MaxApertureint16s 
27MinApertureint16s 
28FlashActivityint16s 
29FlashBitsint16s + +
0x0 = (none) +
Bit 0 = Manual +
Bit 1 = TTL +
Bit 2 = A-TTL +
Bit 3 = E-TTL
  Bit 4 = FP sync enabled +
Bit 7 = 2nd-curtain sync used +
Bit 11 = FP sync used +
Bit 13 = Built-in +
Bit 14 = External
+
32FocusContinuousint16s0 = Single +
1 = Continuous +
8 = Manual
33AESettingint16s0 = Normal AE +
1 = Exposure Compensation +
2 = AE Lock +
3 = AE Lock + Exposure Comp. +
4 = No AE
34ImageStabilizationint16s + +
0 = Off +
1 = On +
2 = Shoot Only +
3 = Panning +
4 = Dynamic
  256 = Off (2) +
257 = On (2) +
258 = Shoot Only (2) +
259 = Panning (2) +
260 = Dynamic (2)
+
35DisplayApertureint16s 
36ZoomSourceWidthint16s 
37ZoomTargetWidthint16s 
39SpotMeteringModeint16s0 = Center +
1 = AF Point
40PhotoEffectint16s + +
0 = Off +
1 = Vivid +
2 = Neutral +
3 = Smooth
  4 = Sepia +
5 = B&W +
6 = Custom +
100 = My Color Data
+
41ManualFlashOutputint16s0x0 = n/a +
0x500 = Full +
0x502 = Medium +
0x504 = Low +
0x7fff = n/a
42ColorToneint16s0 = Normal
46SRAWQualityint16s0 = n/a +
1 = sRAW1 (mRAW) +
2 = sRAW2 (sRAW)
+ +

Canon LensType Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLensType
-1= n/a
1= Canon EF 50mm f/1.8
2= Canon EF 28mm f/2.8 or Sigma Lens
2= Sigma 24mm f/2.8 Super Wide II
3= Canon EF 135mm f/2.8 Soft
4= Canon EF 35-105mm f/3.5-4.5 or Sigma Lens
4= Sigma UC Zoom 35-135mm f/4-5.6
5= Canon EF 35-70mm f/3.5-4.5
6= Canon EF 28-70mm f/3.5-4.5 or Sigma or Tokina Lens
6= Sigma 18-50mm f/3.5-5.6 DC
6= Sigma 18-125mm f/3.5-5.6 DC IF ASP
6= Tokina AF 193-2 19-35mm f/3.5-4.5
6= Sigma 28-80mm f/3.5-5.6 II Macro
6= Sigma 28-300mm f/3.5-6.3 DG Macro
7= Canon EF 100-300mm f/5.6L
8= Canon EF 100-300mm f/5.6 or Sigma or Tokina Lens
8= Sigma 70-300mm f/4-5.6 [APO] DG Macro
8= Tokina AT-X 242 AF 24-200mm f/3.5-5.6
9= Canon EF 70-210mm f/4
9= Sigma 55-200mm f/4-5.6 DC
10= Canon EF 50mm f/2.5 Macro or Sigma Lens
10= Sigma 50mm f/2.8 EX
10= Sigma 28mm f/1.8
10= Sigma 105mm f/2.8 Macro EX
10= Sigma 70mm f/2.8 EX DG Macro EF
11= Canon EF 35mm f/2
13= Canon EF 15mm f/2.8 Fisheye
14= Canon EF 50-200mm f/3.5-4.5L
15= Canon EF 50-200mm f/3.5-4.5
16= Canon EF 35-135mm f/3.5-4.5
17= Canon EF 35-70mm f/3.5-4.5A
18= Canon EF 28-70mm f/3.5-4.5
20= Canon EF 100-200mm f/4.5A
21= Canon EF 80-200mm f/2.8L
22= Canon EF 20-35mm f/2.8L or Tokina Lens
22= Tokina AT-X 280 AF Pro 28-80mm f/2.8 Aspherical
23= Canon EF 35-105mm f/3.5-4.5
24= Canon EF 35-80mm f/4-5.6 Power Zoom
25= Canon EF 35-80mm f/4-5.6 Power Zoom
26= Canon EF 100mm f/2.8 Macro or Other Lens
26= Cosina 100mm f/3.5 Macro AF
26= Tamron SP AF 90mm f/2.8 Di Macro
26= Tamron SP AF 180mm f/3.5 Di Macro
26= Carl Zeiss Planar T* 50mm f/1.4
26= Voigtlander APO Lanthar 125mm F2.5 SL Macro
26= Carl Zeiss Planar T 85mm f/1.4 ZE
27= Canon EF 35-80mm f/4-5.6
28= Canon EF 80-200mm f/4.5-5.6 or Tamron Lens
28= Tamron SP AF 28-105mm f/2.8 LD Aspherical IF
28= Tamron SP AF 28-75mm f/2.8 XR Di LD Aspherical [IF] Macro
28= Tamron AF 70-300mm f/4-5.6 Di LD 1:2 Macro
28= Tamron AF Aspherical 28-200mm f/3.8-5.6
29= Canon EF 50mm f/1.8 II
30= Canon EF 35-105mm f/4.5-5.6
31= Canon EF 75-300mm f/4-5.6 or Tamron Lens
31= Tamron SP AF 300mm f/2.8 LD IF
32= Canon EF 24mm f/2.8 or Sigma Lens
32= Sigma 15mm f/2.8 EX Fisheye
33= Voigtlander or Carl Zeiss Lens
33= Voigtlander Ultron 40mm f/2 SLII Aspherical
33= Voigtlander Color Skopar 20mm f/3.5 SLII Aspherical
33= Voigtlander APO-Lanthar 90mm f/3.5 SLII Close Focus
33= Carl Zeiss Distagon T* 15mm f/2.8 ZE
33= Carl Zeiss Distagon T* 18mm f/3.5 ZE
33= Carl Zeiss Distagon T* 21mm f/2.8 ZE
33= Carl Zeiss Distagon T* 25mm f/2 ZE
33= Carl Zeiss Distagon T* 28mm f/2 ZE
33= Carl Zeiss Distagon T* 35mm f/2 ZE
33= Carl Zeiss Distagon T* 35mm f/1.4 ZE
33= Carl Zeiss Planar T* 50mm f/1.4 ZE
33= Carl Zeiss Makro-Planar T* 50mm f/2 ZE
33= Carl Zeiss Makro-Planar T* 100mm f/2 ZE
33= Carl Zeiss Apo-Sonnar T* 135mm f/2 ZE
35= Canon EF 35-80mm f/4-5.6
36= Canon EF 38-76mm f/4.5-5.6
37= Canon EF 35-80mm f/4-5.6 or Tamron Lens
37= Tamron 70-200mm f/2.8 Di LD IF Macro
37= Tamron AF 28-300mm f/3.5-6.3 XR Di VC LD Aspherical [IF] Macro (A20)
37= Tamron SP AF 17-50mm f/2.8 XR Di II VC LD Aspherical [IF]
37= Tamron AF 18-270mm f/3.5-6.3 Di II VC LD Aspherical [IF] Macro
38= Canon EF 80-200mm f/4.5-5.6 II
39= Canon EF 75-300mm f/4-5.6
40= Canon EF 28-80mm f/3.5-5.6
41= Canon EF 28-90mm f/4-5.6
42= Canon EF 28-200mm f/3.5-5.6 or Tamron Lens
42= Tamron AF 28-300mm f/3.5-6.3 XR Di VC LD Aspherical [IF] Macro (A20)
43= Canon EF 28-105mm f/4-5.6
44= Canon EF 90-300mm f/4.5-5.6
45= Canon EF-S 18-55mm f/3.5-5.6 [II]
46= Canon EF 28-90mm f/4-5.6
47= Zeiss Milvus 35mm f/2 or 50mm f/2
47= Zeiss Milvus 50mm f/2 Makro
47= Zeiss Milvus 135mm f/2 ZE
48= Canon EF-S 18-55mm f/3.5-5.6 IS
49= Canon EF-S 55-250mm f/4-5.6 IS
50= Canon EF-S 18-200mm f/3.5-5.6 IS
51= Canon EF-S 18-135mm f/3.5-5.6 IS
52= Canon EF-S 18-55mm f/3.5-5.6 IS II
53= Canon EF-S 18-55mm f/3.5-5.6 III
54= Canon EF-S 55-250mm f/4-5.6 IS II
60= Irix 11mm f/4
63= Irix 30mm F1.4 Dragonfly
80= Canon TS-E 50mm f/2.8L Macro
81= Canon TS-E 90mm f/2.8L Macro
82= Canon TS-E 135mm f/4L Macro
94= Canon TS-E 17mm f/4L
95= Canon TS-E 24mm f/3.5L II
103= Samyang AF 14mm f/2.8 EF or Rokinon Lens
103= Rokinon SP 14mm f/2.4
103= Rokinon AF 14mm f/2.8 EF
106= Rokinon SP / Samyang XP 35mm f/1.2
112= Sigma 28mm f/1.5 FF High-speed Prime or other Sigma Lens
112= Sigma 40mm f/1.5 FF High-speed Prime
112= Sigma 105mm f/1.5 FF High-speed Prime
117= Tamron 35-150mm f/2.8-4.0 Di VC OSD (A043) or other Tamron Lens
117= Tamron SP 35mm f/1.4 Di USD (F045)
124= Canon MP-E 65mm f/2.8 1-5x Macro Photo
125= Canon TS-E 24mm f/3.5L
126= Canon TS-E 45mm f/2.8
127= Canon TS-E 90mm f/2.8 or Tamron Lens
127= Tamron 18-200mm f/3.5-6.3 Di II VC (B018)
129= Canon EF 300mm f/2.8L USM
130= Canon EF 50mm f/1.0L USM
131= Canon EF 28-80mm f/2.8-4L USM or Sigma Lens
131= Sigma 8mm f/3.5 EX DG Circular Fisheye
131= Sigma 17-35mm f/2.8-4 EX DG Aspherical HSM
131= Sigma 17-70mm f/2.8-4.5 DC Macro
131= Sigma APO 50-150mm f/2.8 [II] EX DC HSM
131= Sigma APO 120-300mm f/2.8 EX DG HSM
131= Sigma 4.5mm f/2.8 EX DC HSM Circular Fisheye
131= Sigma 70-200mm f/2.8 APO EX HSM
131= Sigma 28-70mm f/2.8-4 DG
132= Canon EF 1200mm f/5.6L USM
134= Canon EF 600mm f/4L IS USM
135= Canon EF 200mm f/1.8L USM
136= Canon EF 300mm f/2.8L USM
136= Tamron SP 15-30mm f/2.8 Di VC USD (A012)
137= Canon EF 85mm f/1.2L USM or Sigma or Tamron Lens
137= Sigma 18-50mm f/2.8-4.5 DC OS HSM
137= Sigma 50-200mm f/4-5.6 DC OS HSM
137= Sigma 18-250mm f/3.5-6.3 DC OS HSM
137= Sigma 24-70mm f/2.8 IF EX DG HSM
137= Sigma 18-125mm f/3.8-5.6 DC OS HSM
137= Sigma 17-70mm f/2.8-4 DC Macro OS HSM | C
137= Sigma 17-50mm f/2.8 OS HSM
137= Sigma 18-200mm f/3.5-6.3 DC OS HSM [II]
137= Tamron AF 18-270mm f/3.5-6.3 Di II VC PZD (B008)
137= Sigma 8-16mm f/4.5-5.6 DC HSM
137= Tamron SP 17-50mm f/2.8 XR Di II VC (B005)
137= Tamron SP 60mm f/2 Macro Di II (G005)
137= Sigma 10-20mm f/3.5 EX DC HSM
137= Tamron SP 24-70mm f/2.8 Di VC USD
137= Sigma 18-35mm f/1.8 DC HSM
137= Sigma 12-24mm f/4.5-5.6 DG HSM II
137= Sigma 70-300mm f/4-5.6 DG OS
138= Canon EF 28-80mm f/2.8-4L
139= Canon EF 400mm f/2.8L USM
140= Canon EF 500mm f/4.5L USM
141= Canon EF 500mm f/4.5L USM
142= Canon EF 300mm f/2.8L IS USM
143= Canon EF 500mm f/4L IS USM or Sigma Lens
143= Sigma 17-70mm f/2.8-4 DC Macro OS HSM
144= Canon EF 35-135mm f/4-5.6 USM
145= Canon EF 100-300mm f/4.5-5.6 USM
146= Canon EF 70-210mm f/3.5-4.5 USM
147= Canon EF 35-135mm f/4-5.6 USM
148= Canon EF 28-80mm f/3.5-5.6 USM
149= Canon EF 100mm f/2 USM
150= Canon EF 14mm f/2.8L USM or Sigma Lens
150= Sigma 20mm EX f/1.8
150= Sigma 30mm f/1.4 DC HSM
150= Sigma 24mm f/1.8 DG Macro EX
150= Sigma 28mm f/1.8 DG Macro EX
150= Sigma 18-35mm f/1.8 DC HSM | A
151= Canon EF 200mm f/2.8L USM
152= Canon EF 300mm f/4L IS USM or Sigma Lens
152= Sigma 12-24mm f/4.5-5.6 EX DG ASPHERICAL HSM
152= Sigma 14mm f/2.8 EX Aspherical HSM
152= Sigma 10-20mm f/4-5.6
152= Sigma 100-300mm f/4
152= Sigma 300-800mm f/5.6 APO EX DG HSM
153= Canon EF 35-350mm f/3.5-5.6L USM or Sigma or Tamron Lens
153= Sigma 50-500mm f/4-6.3 APO HSM EX
153= Tamron AF 28-300mm f/3.5-6.3 XR LD Aspherical [IF] Macro
153= Tamron AF 18-200mm f/3.5-6.3 XR Di II LD Aspherical [IF] Macro (A14)
153= Tamron 18-250mm f/3.5-6.3 Di II LD Aspherical [IF] Macro
154= Canon EF 20mm f/2.8 USM or Zeiss Lens
154= Zeiss Milvus 21mm f/2.8
154= Zeiss Milvus 15mm f/2.8 ZE
154= Zeiss Milvus 18mm f/2.8 ZE
155= Canon EF 85mm f/1.8 USM or Sigma Lens
155= Sigma 14mm f/1.8 DG HSM | A
156= Canon EF 28-105mm f/3.5-4.5 USM or Tamron Lens
156= Tamron SP 70-300mm f/4-5.6 Di VC USD (A005)
156= Tamron SP AF 28-105mm f/2.8 LD Aspherical IF (176D)
160= Canon EF 20-35mm f/3.5-4.5 USM or Tamron or Tokina Lens
160= Tamron AF 19-35mm f/3.5-4.5
160= Tokina AT-X 124 AF Pro DX 12-24mm f/4
160= Tokina AT-X 107 AF DX 10-17mm f/3.5-4.5 Fisheye
160= Tokina AT-X 116 AF Pro DX 11-16mm f/2.8
160= Tokina AT-X 11-20 F2.8 PRO DX Aspherical 11-20mm f/2.8
161= Canon EF 28-70mm f/2.8L USM or Other Lens
161= Sigma 24-70mm f/2.8 EX
161= Sigma 28-70mm f/2.8 EX
161= Sigma 24-60mm f/2.8 EX DG
161= Tamron AF 17-50mm f/2.8 Di-II LD Aspherical
161= Tamron 90mm f/2.8
161= Tamron SP AF 17-35mm f/2.8-4 Di LD Aspherical IF (A05)
161= Tamron SP AF 28-75mm f/2.8 XR Di LD Aspherical [IF] Macro
161= Tokina AT-X 24-70mm f/2.8 PRO FX (IF)
162= Canon EF 200mm f/2.8L USM
163= Canon EF 300mm f/4L
164= Canon EF 400mm f/5.6L
165= Canon EF 70-200mm f/2.8L USM
166= Canon EF 70-200mm f/2.8L USM + 1.4x
167= Canon EF 70-200mm f/2.8L USM + 2x
168= Canon EF 28mm f/1.8 USM or Sigma Lens
168= Sigma 50-100mm f/1.8 DC HSM | A
169= Canon EF 17-35mm f/2.8L USM or Sigma Lens
169= Sigma 18-200mm f/3.5-6.3 DC OS
169= Sigma 15-30mm f/3.5-4.5 EX DG Aspherical
169= Sigma 18-50mm f/2.8 Macro
169= Sigma 50mm f/1.4 EX DG HSM
169= Sigma 85mm f/1.4 EX DG HSM
169= Sigma 30mm f/1.4 EX DC HSM
169= Sigma 35mm f/1.4 DG HSM
169= Sigma 35mm f/1.5 FF High-Speed Prime | 017
169= Sigma 70mm f/2.8 Macro EX DG
170= Canon EF 200mm f/2.8L II USM or Sigma Lens
170= Sigma 300mm f/2.8 APO EX DG HSM
170= Sigma 800mm f/5.6 APO EX DG HSM
171= Canon EF 300mm f/4L USM
172= Canon EF 400mm f/5.6L USM or Sigma Lens
172= Sigma 150-600mm f/5-6.3 DG OS HSM | S
172= Sigma 500mm f/4.5 APO EX DG HSM
173= Canon EF 180mm Macro f/3.5L USM or Sigma Lens
173= Sigma 180mm EX HSM Macro f/3.5
173= Sigma APO Macro 150mm f/2.8 EX DG HSM
173= Sigma 10mm f/2.8 EX DC Fisheye
173= Sigma 15mm f/2.8 EX DG Diagonal Fisheye
173= Venus Laowa 100mm F2.8 2X Ultra Macro APO
174= Canon EF 135mm f/2L USM or Other Lens
174= Sigma 70-200mm f/2.8 EX DG APO OS HSM
174= Sigma 50-500mm f/4.5-6.3 APO DG OS HSM
174= Sigma 150-500mm f/5-6.3 APO DG OS HSM
174= Zeiss Milvus 100mm f/2 Makro
174= Sigma APO 50-150mm f/2.8 EX DC OS HSM
174= Sigma APO 120-300mm f/2.8 EX DG OS HSM
174= Sigma 120-300mm f/2.8 DG OS HSM S013
174= Sigma 120-400mm f/4.5-5.6 APO DG OS HSM
174= Sigma 200-500mm f/2.8 APO EX DG
175= Canon EF 400mm f/2.8L USM
176= Canon EF 24-85mm f/3.5-4.5 USM
177= Canon EF 300mm f/4L IS USM
178= Canon EF 28-135mm f/3.5-5.6 IS
179= Canon EF 24mm f/1.4L USM
180= Canon EF 35mm f/1.4L USM or Other Lens
180= Sigma 50mm f/1.4 DG HSM | A
180= Sigma 24mm f/1.4 DG HSM | A
180= Zeiss Milvus 50mm f/1.4
180= Zeiss Milvus 85mm f/1.4
180= Zeiss Otus 28mm f/1.4 ZE
180= Sigma 24mm f/1.5 FF High-Speed Prime | 017
180= Sigma 50mm f/1.5 FF High-Speed Prime | 017
180= Sigma 85mm f/1.5 FF High-Speed Prime | 017
180= Tokina Opera 50mm f/1.4 FF
180= Sigma 20mm f/1.4 DG HSM | A
181= Canon EF 100-400mm f/4.5-5.6L IS USM + 1.4x or Sigma Lens
181= Sigma 150-600mm f/5-6.3 DG OS HSM | S + 1.4x
182= Canon EF 100-400mm f/4.5-5.6L IS USM + 2x or Sigma Lens
182= Sigma 150-600mm f/5-6.3 DG OS HSM | S + 2x
183= Canon EF 100-400mm f/4.5-5.6L IS USM or Sigma Lens
183= Sigma 150mm f/2.8 EX DG OS HSM APO Macro
183= Sigma 105mm f/2.8 EX DG OS HSM Macro
183= Sigma 180mm f/2.8 EX DG OS HSM APO Macro
183= Sigma 150-600mm f/5-6.3 DG OS HSM | C
183= Sigma 150-600mm f/5-6.3 DG OS HSM | S
183= Sigma 100-400mm f/5-6.3 DG OS HSM
183= Sigma 180mm f/3.5 APO Macro EX DG IF HSM
184= Canon EF 400mm f/2.8L USM + 2x
185= Canon EF 600mm f/4L IS USM
186= Canon EF 70-200mm f/4L USM
187= Canon EF 70-200mm f/4L USM + 1.4x
188= Canon EF 70-200mm f/4L USM + 2x
189= Canon EF 70-200mm f/4L USM + 2.8x
190= Canon EF 100mm f/2.8 Macro USM
191= Canon EF 400mm f/4 DO IS or Sigma Lens
191= Sigma 500mm f/4 DG OS HSM
193= Canon EF 35-80mm f/4-5.6 USM
194= Canon EF 80-200mm f/4.5-5.6 USM
195= Canon EF 35-105mm f/4.5-5.6 USM
196= Canon EF 75-300mm f/4-5.6 USM
197= Canon EF 75-300mm f/4-5.6 IS USM or Sigma Lens
197= Sigma 18-300mm f/3.5-6.3 DC Macro OS HSM
198= Canon EF 50mm f/1.4 USM or Other Lens
198= Zeiss Otus 55mm f/1.4 ZE
198= Zeiss Otus 85mm f/1.4 ZE
198= Zeiss Milvus 25mm f/1.4
198= Zeiss Otus 100mm f/1.4
198= Zeiss Milvus 35mm f/1.4 ZE
198= Yongnuo YN 35mm f/2
199= Canon EF 28-80mm f/3.5-5.6 USM
200= Canon EF 75-300mm f/4-5.6 USM
201= Canon EF 28-80mm f/3.5-5.6 USM
202= Canon EF 28-80mm f/3.5-5.6 USM IV
208= Canon EF 22-55mm f/4-5.6 USM
209= Canon EF 55-200mm f/4.5-5.6
210= Canon EF 28-90mm f/4-5.6 USM
211= Canon EF 28-200mm f/3.5-5.6 USM
212= Canon EF 28-105mm f/4-5.6 USM
213= Canon EF 90-300mm f/4.5-5.6 USM or Tamron Lens
213= Tamron SP 150-600mm f/5-6.3 Di VC USD (A011)
213= Tamron 16-300mm f/3.5-6.3 Di II VC PZD Macro (B016)
213= Tamron SP 35mm f/1.8 Di VC USD (F012)
213= Tamron SP 45mm f/1.8 Di VC USD (F013)
214= Canon EF-S 18-55mm f/3.5-5.6 USM
215= Canon EF 55-200mm f/4.5-5.6 II USM
217= Tamron AF 18-270mm f/3.5-6.3 Di II VC PZD
220= Yongnuo YN 50mm f/1.8
224= Canon EF 70-200mm f/2.8L IS USM
225= Canon EF 70-200mm f/2.8L IS USM + 1.4x
226= Canon EF 70-200mm f/2.8L IS USM + 2x
227= Canon EF 70-200mm f/2.8L IS USM + 2.8x
228= Canon EF 28-105mm f/3.5-4.5 USM
229= Canon EF 16-35mm f/2.8L USM
230= Canon EF 24-70mm f/2.8L USM
231= Canon EF 17-40mm f/4L USM or Sigma Lens
231= Sigma 12-24mm f/4 DG HSM A016
232= Canon EF 70-300mm f/4.5-5.6 DO IS USM
233= Canon EF 28-300mm f/3.5-5.6L IS USM
234= Canon EF-S 17-85mm f/4-5.6 IS USM or Tokina Lens
234= Tokina AT-X 12-28 PRO DX 12-28mm f/4
235= Canon EF-S 10-22mm f/3.5-4.5 USM
236= Canon EF-S 60mm f/2.8 Macro USM
237= Canon EF 24-105mm f/4L IS USM
238= Canon EF 70-300mm f/4-5.6 IS USM
239= Canon EF 85mm f/1.2L II USM or Rokinon Lens
239= Rokinon SP 85mm f/1.2
240= Canon EF-S 17-55mm f/2.8 IS USM or Sigma Lens
240= Sigma 17-50mm f/2.8 EX DC OS HSM
241= Canon EF 50mm f/1.2L USM
242= Canon EF 70-200mm f/4L IS USM
243= Canon EF 70-200mm f/4L IS USM + 1.4x
244= Canon EF 70-200mm f/4L IS USM + 2x
245= Canon EF 70-200mm f/4L IS USM + 2.8x
246= Canon EF 16-35mm f/2.8L II USM
247= Canon EF 14mm f/2.8L II USM
248= Canon EF 200mm f/2L IS USM or Sigma Lens
248= Sigma 24-35mm f/2 DG HSM | A
248= Sigma 135mm f/2 FF High-Speed Prime | 017
248= Sigma 24-35mm f/2.2 FF Zoom | 017
248= Sigma 135mm f/1.8 DG HSM A017
249= Canon EF 800mm f/5.6L IS USM
250= Canon EF 24mm f/1.4L II USM or Sigma Lens
250= Sigma 20mm f/1.4 DG HSM | A
250= Sigma 20mm f/1.5 FF High-Speed Prime | 017
250= Tokina Opera 16-28mm f/2.8 FF
250= Sigma 85mm f/1.4 DG HSM A016
251= Canon EF 70-200mm f/2.8L IS II USM
251= Canon EF 70-200mm f/2.8L IS III USM
252= Canon EF 70-200mm f/2.8L IS II USM + 1.4x
252= Canon EF 70-200mm f/2.8L IS III USM + 1.4x
253= Canon EF 70-200mm f/2.8L IS II USM + 2x
253= Canon EF 70-200mm f/2.8L IS III USM + 2x
254= Canon EF 100mm f/2.8L Macro IS USM or Tamron Lens
254= Tamron SP 90mm f/2.8 Di VC USD 1:1 Macro (F017)
255= Sigma 24-105mm f/4 DG OS HSM | A or Other Lens
255= Sigma 180mm f/2.8 EX DG OS HSM APO Macro
255= Tamron SP 70-200mm f/2.8 Di VC USD
255= Yongnuo YN 50mm f/1.8
368= Sigma 14-24mm f/2.8 DG HSM | A or other Sigma Lens
368= Sigma 20mm f/1.4 DG HSM | A
368= Sigma 50mm f/1.4 DG HSM | A
368= Sigma 40mm f/1.4 DG HSM | A
368= Sigma 60-600mm f/4.5-6.3 DG OS HSM | S
368= Sigma 28mm f/1.4 DG HSM | A
368= Sigma 150-600mm f/5-6.3 DG OS HSM | S
368= Sigma 85mm f/1.4 DG HSM | A
368= Sigma 105mm f/1.4 DG HSM
368= Sigma 14-24mm f/2.8 DG HSM
368= Sigma 35mm f/1.4 DG HSM | A
368= Sigma 70mm f/2.8 DG Macro
368= Sigma 18-35mm f/1.8 DC HSM | A
368= Sigma 24-105mm f/4 DG OS HSM | A
488= Canon EF-S 15-85mm f/3.5-5.6 IS USM
489= Canon EF 70-300mm f/4-5.6L IS USM
490= Canon EF 8-15mm f/4L Fisheye USM
491= Canon EF 300mm f/2.8L IS II USM or Tamron Lens
491= Tamron SP 70-200mm f/2.8 Di VC USD G2 (A025)
491= Tamron 18-400mm f/3.5-6.3 Di II VC HLD (B028)
491= Tamron 100-400mm f/4.5-6.3 Di VC USD (A035)
491= Tamron 70-210mm f/4 Di VC USD (A034)
491= Tamron 70-210mm f/4 Di VC USD (A034) + 1.4x
491= Tamron SP 24-70mm f/2.8 Di VC USD G2 (A032)
492= Canon EF 400mm f/2.8L IS II USM
493= Canon EF 500mm f/4L IS II USM or EF 24-105mm f4L IS USM
493= Canon EF 24-105mm f/4L IS USM
494= Canon EF 600mm f/4L IS II USM
495= Canon EF 24-70mm f/2.8L II USM or Sigma Lens
495= Sigma 24-70mm f/2.8 DG OS HSM | A
496= Canon EF 200-400mm f/4L IS USM
499= Canon EF 200-400mm f/4L IS USM + 1.4x
502= Canon EF 28mm f/2.8 IS USM or Tamron Lens
502= Tamron 35mm f/1.8 Di VC USD (F012)
503= Canon EF 24mm f/2.8 IS USM
504= Canon EF 24-70mm f/4L IS USM
505= Canon EF 35mm f/2 IS USM
506= Canon EF 400mm f/4 DO IS II USM
507= Canon EF 16-35mm f/4L IS USM
508= Canon EF 11-24mm f/4L USM or Tamron Lens
508= Tamron 10-24mm f/3.5-4.5 Di II VC HLD (B023)
624= Sigma 70-200mm f/2.8 DG OS HSM | S or other Sigma Lens
624= Sigma 150-600mm f/5-6.3 | C
747= Canon EF 100-400mm f/4.5-5.6L IS II USM or Tamron Lens
747= Tamron SP 150-600mm f/5-6.3 Di VC USD G2
748= Canon EF 100-400mm f/4.5-5.6L IS II USM + 1.4x or Tamron Lens
748= Tamron 100-400mm f/4.5-6.3 Di VC USD A035E + 1.4x
748= Tamron 70-210mm f/4 Di VC USD (A034) + 2x
749= Tamron 100-400mm f/4.5-6.3 Di VC USD A035E + 2x
750= Canon EF 35mm f/1.4L II USM or Tamron Lens
750= Tamron SP 85mm f/1.8 Di VC USD (F016)
750= Tamron SP 45mm f/1.8 Di VC USD (F013)
751= Canon EF 16-35mm f/2.8L III USM
752= Canon EF 24-105mm f/4L IS II USM
753= Canon EF 85mm f/1.4L IS USM
754= Canon EF 70-200mm f/4L IS II USM
757= Canon EF 400mm f/2.8L IS III USM
758= Canon EF 600mm f/4L IS III USM
1136= Sigma 24-70mm f/2.8 DG OS HSM | A
4142= Canon EF-S 18-135mm f/3.5-5.6 IS STM
4143= Canon EF-M 18-55mm f/3.5-5.6 IS STM or Tamron Lens
4143= Tamron 18-200mm f/3.5-6.3 Di III VC
4144= Canon EF 40mm f/2.8 STM
4145= Canon EF-M 22mm f/2 STM
4146= Canon EF-S 18-55mm f/3.5-5.6 IS STM
4147= Canon EF-M 11-22mm f/4-5.6 IS STM
4148= Canon EF-S 55-250mm f/4-5.6 IS STM
4149= Canon EF-M 55-200mm f/4.5-6.3 IS STM
4150= Canon EF-S 10-18mm f/4.5-5.6 IS STM
4152= Canon EF 24-105mm f/3.5-5.6 IS STM
4153= Canon EF-M 15-45mm f/3.5-6.3 IS STM
4154= Canon EF-S 24mm f/2.8 STM
4155= Canon EF-M 28mm f/3.5 Macro IS STM
4156= Canon EF 50mm f/1.8 STM
4157= Canon EF-M 18-150mm f/3.5-6.3 IS STM
4158= Canon EF-S 18-55mm f/4-5.6 IS STM
4159= Canon EF-M 32mm f/1.4 STM
4160= Canon EF-S 35mm f/2.8 Macro IS STM
4208= Sigma 56mm f/1.4 DC DN | C or other Sigma Lens
4208= Sigma 30mm F1.4 DC DN | C
36910= Canon EF 70-300mm f/4-5.6 IS II USM
36912= Canon EF-S 18-135mm f/3.5-5.6 IS USM
61182= Canon RF 50mm F1.2L USM or other Canon RF Lens
61182= Canon RF 24-105mm F4L IS USM
61182= Canon RF 28-70mm F2L USM
61182= Canon RF 35mm F1.8 MACRO IS STM
61182= Canon RF 85mm F1.2L USM
61182= Canon RF 85mm F1.2L USM DS
61182= Canon RF 24-70mm F2.8L IS USM
61182= Canon RF 15-35mm F2.8L IS USM
61182= Canon RF 24-240mm F4-6.3 IS USM
61182= Canon RF 70-200mm F2.8L IS USM
61182= Canon RF 85mm F2 MACRO IS STM
61182= Canon RF 600mm F11 IS STM
61182= Canon RF 600mm F11 IS STM + RF1.4x
61182= Canon RF 600mm F11 IS STM + RF2x
61182= Canon RF 800mm F11 IS STM
61182= Canon RF 800mm F11 IS STM + RF1.4x
61182= Canon RF 800mm F11 IS STM + RF2x
61182= Canon RF 24-105mm F4-7.1 IS STM
61182= Canon RF 100-500mm F4.5-7.1L IS USM
61182= Canon RF 100-500mm F4.5-7.1L IS USM + RF1.4x
61182= Canon RF 100-500mm F4.5-7.1L IS USM + RF2x
61182= Canon RF 70-200mm F4L IS USM
61182= Canon RF 100mm F2.8L MACRO IS USM
61182= Canon RF 50mm F1.8 STM
61182= Canon RF 14-35mm F4L IS USM
61182= Canon RF-S 18-45mm F4.5-6.3 IS STM
61182= Canon RF 100-400mm F5.6-8 IS USM
61182= Canon RF 100-400mm F5.6-8 IS USM + RF1.4x
61182= Canon RF 100-400mm F5.6-8 IS USM + RF2x
61182= Canon RF-S 18-150mm F3.5-6.3 IS STM
61182= Canon RF 24mm F1.8 MACRO IS STM
61182= Canon RF 16mm F2.8 STM
61182= Canon RF 400mm F2.8L IS USM
61182= Canon RF 400mm F2.8L IS USM + RF1.4x
61182= Canon RF 400mm F2.8L IS USM + RF2x
61182= Canon RF 600mm F4L IS USM
61182= Canon RF 600mm F4L IS USM + RF1.4x
61182= Canon RF 600mm F4L IS USM + RF2x
61182= Canon RF 15-30mm F4.5-6.3 IS STM
61182= Canon RF 800mm F5.6L IS USM
61182= Canon RF 800mm F5.6L IS USM + RF1.4x
61182= Canon RF 800mm F5.6L IS USM + RF2x
61182= Canon RF 1200mm F8L IS USM
61182= Canon RF 1200mm F8L IS USM + RF1.4x
61182= Canon RF 1200mm F8L IS USM + RF2x
61182= Canon RF 135mm F1.8 L IS USM
61182= Canon RF 24-50mm F4.5-6.3 IS STM
61182= Canon RF-S 55-210mm F5-7.1 IS STM
61182= Canon RF 100-300mm F2.8L IS USM
61182= Canon RF 100-300mm F2.8L IS USM + RF1.4x
61182= Canon RF 100-300mm F2.8L IS USM + RF2x
61182= Canon RF 28mm F2.8 STM
61182= Canon RF 5.2mm F2.8L Dual Fisheye 3D VR
61491= Canon CN-E 14mm T3.1 L F
61492= Canon CN-E 24mm T1.5 L F
61494= Canon CN-E 85mm T1.3 L F
61495= Canon CN-E 135mm T2.2 L F
61496= Canon CN-E 35mm T1.5 L F
65535= n/a
+ +

Canon FocalLength Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0FocalTypeint16u1 = Fixed +
2 = Zoom
1FocalLengthint16u 
2FocalPlaneXSize +
FocalPlaneXUnknown?
int16u
int16u
(these focal plane sizes are only valid for some models, and are affected by +digital zoom if applied)
3FocalPlaneYSize +
FocalPlaneYUnknown?
int16u
int16u
 
+ +

Canon ShotInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1AutoISOint16s(actual ISO used = BaseISO * AutoISO / 100)
2BaseISOint16s 
3MeasuredEVint16s(this is the Canon name for what could better be called MeasuredLV, and +should be close to the calculated LightValue for a proper exposure with most +models)
4TargetApertureint16s 
5TargetExposureTimeint16s 
6ExposureCompensationint16s 
7WhiteBalanceint16s--> Canon WhiteBalance Values
8SlowShutterint16s-1 = n/a +
0 = Off +
1 = Night Scene +
2 = On +
3 = None
9SequenceNumberint16s(valid only for some models)
10OpticalZoomCodeint16s(for many PowerShot models, a this is 0-6 for wide-tele zoom)
12CameraTemperatureint16s(newer EOS models only)
13FlashGuideNumberint16s 
14AFPointsInFocusint16s(used by D30, D60 and some PowerShot/Ixus models) + +
0x3000 = None (MF) +
0x3001 = Right +
0x3002 = Center +
0x3003 = Center+Right
  0x3004 = Left +
0x3005 = Left+Right +
0x3006 = Left+Center +
0x3007 = All
+
15FlashExposureCompint16s 
16AutoExposureBracketingint16s-1 = On +
0 = Off +
1 = On (shot 1) +
2 = On (shot 2) +
3 = On (shot 3)
17AEBBracketValueint16s 
18ControlModeint16s0 = n/a +
1 = Camera Local Control +
3 = Computer Remote Control
19FocusDistanceUpperint16u(FocusDistance tags are only extracted if FocusDistanceUpper is non-zero)
20FocusDistanceLowerint16u 
21FNumberint16s 
22ExposureTimeint16s 
23MeasuredEV2int16s 
24BulbDurationint16s 
26CameraTypeint16s0 = n/a +
248 = EOS High-end +
250 = Compact +
252 = EOS Mid-range +
255 = DV Camera
27AutoRotateint16s-1 = n/a +
0 = None +
1 = Rotate 90 CW +
2 = Rotate 180 +
3 = Rotate 270 CW
28NDFilterint16s-1 = n/a +
0 = Off +
1 = On
29SelfTimer2int16s 
33FlashOutputint16s(used only for PowerShot models, this has a maximum value of 500 for models +like the A570IS)
+ +

Canon WhiteBalance Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueWhiteBalanceValueWhiteBalanceValueWhiteBalance
0= Auto8= Shade17= Underwater
1= Daylight9= Manual Temperature (Kelvin)18= Custom 3
2= Cloudy10= PC Set119= Custom 4
3= Tungsten11= PC Set220= PC Set4
4= Fluorescent12= PC Set321= PC Set5
5= Flash14= Daylight Fluorescent23= Auto (ambience priority)
6= Custom15= Custom 1  
7= Black & White16= Custom 2  
+ +

Canon Panorama Tags

+
+
+ + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
2PanoramaFrameNumberint16s 
5PanoramaDirectionint16s0 = Left to Right +
1 = Right to Left +
2 = Bottom to Top +
3 = Top to Bottom +
4 = 2x2 Matrix (Clockwise)
+ +

Canon UnknownD30 Tags

+
+
+ + + + +
Index2Tag NameWritableValues / Notes
[no tags known]
+ +

Canon CameraInfo1D Tags

+

Information in the "CameraInfo" records is tricky to decode because the +encodings are very different than in other Canon records (even sometimes +switching endianness between values within a single camera), plus there is +considerable variation in format from model to model. The first table below +lists CameraInfo tags for the 1D and 1DS.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ExposureTimeint8u 
10FocalLengthint16u 
13LensTypeint16uRev--> Canon LensType Values
14MinFocalLengthint16u 
16MaxFocalLengthint16u 
65SharpnessFrequencyint8u(1D only) + +
0 = n/a +
1 = Lowest +
2 = Low
  3 = Standard +
4 = High +
5 = Highest
+
66Sharpnessint8s(1D only)
68WhiteBalanceint8u--> Canon WhiteBalance Values +
(1D only)
71SharpnessFrequencyint8u(1DS only) + +
0 = n/a +
1 = Lowest +
2 = Low
  3 = Standard +
4 = High +
5 = Highest
+
72ColorTemperature +
Sharpness
int16u
int8s
(1D only) +
(1DS only)
74WhiteBalanceint8u--> Canon WhiteBalance Values +
(1DS only)
75PictureStyleint8u--> Canon PictureStyle Values +
(1D only, called 'Color Matrix' in owner's manual)
78ColorTemperatureint16u(1DS only)
81PictureStyleint8u--> Canon PictureStyle Values +
(1DS only)
+ +

Canon CameraInfo1DmkII Tags

+

CameraInfo tags for the 1DmkII and 1DSmkII.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ExposureTimeint8u 
9FocalLengthint16uRev 
12LensTypeint16uRev--> Canon LensType Values
17MinFocalLengthint16uRev 
19MaxFocalLengthint16uRev 
45FocalTypeint8u0 = Fixed +
2 = Zoom
54WhiteBalanceint8u--> Canon WhiteBalance Values
55ColorTemperatureint16uRev 
57CanonImageSizeint16u + +
-1 = n/a +
0 = Large +
1 = Medium +
2 = Small +
5 = Medium 1 +
6 = Medium 2 +
7 = Medium 3 +
8 = Postcard +
9 = Widescreen +
10 = Medium Widescreen
  14 = Small 1 +
15 = Small 2 +
16 = Small 3 +
128 = 640x480 Movie +
129 = Medium Movie +
130 = Small Movie +
137 = 1280x720 Movie +
142 = 1920x1080 Movie +
143 = 4096x2160 Movie
+
102JPEGQualityint8u(a number from 1 to 10)
108PictureStyleint8u--> Canon PictureStyle Values
110Saturationint8s0 = Normal
111ColorToneint8s0 = Normal
114Sharpnessint8s 
115Contrastint8s0 = Normal
117ISOstring[5] 
+ +

Canon CameraInfo1DmkIIN Tags

+

CameraInfo tags for the 1DmkIIN.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ExposureTimeint8u 
9FocalLengthint16uRev 
12LensTypeint16uRev--> Canon LensType Values
17MinFocalLengthint16uRev 
19MaxFocalLengthint16uRev 
54WhiteBalanceint8u--> Canon WhiteBalance Values
55ColorTemperatureint16uRev 
115PictureStyleint8u--> Canon PictureStyle Values
116Sharpnessint8s 
117Contrastint8s0 = Normal
118Saturationint8s0 = Normal
119ColorToneint8s0 = Normal
121ISOstring[5] 
+ +

Canon CameraInfo1DmkIII Tags

+

CameraInfo tags for the 1DmkIII and 1DSmkIII.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
24CameraTemperatureint8u 
27MacroMagnificationint8u(currently decoded only for the MP-E 65mm f/2.8 1-5x Macro Photo)
29FocalLengthint16uRev 
48CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
67FocusDistanceUpperint16uRev 
69FocusDistanceLowerint16uRev 
94WhiteBalanceint16u--> Canon WhiteBalance Values
98ColorTemperatureint16u 
134PictureStyleint8u--> Canon PictureStyle Values
273LensTypeint16uRev--> Canon LensType Values
275MinFocalLengthint16uRev 
277MaxFocalLengthint16uRev 
310FirmwareVersionstring[6] 
370FileIndexint32u 
374ShutterCountint32u(may be valid only for some 1DmkIII copies, even running the same firmware)
382DirectoryIndexint32u 
682PictureStyleInfo---> Canon PSInfo Tags
1114TimeStamp1int32u(only valid for some versions of the 1DmkIII firmware)
1118TimeStampint32u(valid for the 1DSmkIII and some versions of the 1DmkIII firmware)
+ +

Canon PSInfo Tags

+

Custom picture style information for various models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ContrastStandardint32s0xdeadbeef = n/a
4SharpnessStandardint32s0xdeadbeef = n/a
8SaturationStandardint32s0xdeadbeef = n/a
12ColorToneStandardint32s0xdeadbeef = n/a
16FilterEffectStandard?int32s0xdeadbeef = n/a
20ToningEffectStandard?int32s0xdeadbeef = n/a
24ContrastPortraitint32s0xdeadbeef = n/a
28SharpnessPortraitint32s0xdeadbeef = n/a
32SaturationPortraitint32s0xdeadbeef = n/a
36ColorTonePortraitint32s0xdeadbeef = n/a
40FilterEffectPortrait?int32s0xdeadbeef = n/a
44ToningEffectPortrait?int32s0xdeadbeef = n/a
48ContrastLandscapeint32s0xdeadbeef = n/a
52SharpnessLandscapeint32s0xdeadbeef = n/a
56SaturationLandscapeint32s0xdeadbeef = n/a
60ColorToneLandscapeint32s0xdeadbeef = n/a
64FilterEffectLandscape?int32s0xdeadbeef = n/a
68ToningEffectLandscape?int32s0xdeadbeef = n/a
72ContrastNeutralint32s0xdeadbeef = n/a
76SharpnessNeutralint32s0xdeadbeef = n/a
80SaturationNeutralint32s0xdeadbeef = n/a
84ColorToneNeutralint32s0xdeadbeef = n/a
88FilterEffectNeutral?int32s0xdeadbeef = n/a
92ToningEffectNeutral?int32s0xdeadbeef = n/a
96ContrastFaithfulint32s0xdeadbeef = n/a
100SharpnessFaithfulint32s0xdeadbeef = n/a
104SaturationFaithfulint32s0xdeadbeef = n/a
108ColorToneFaithfulint32s0xdeadbeef = n/a
112FilterEffectFaithful?int32s0xdeadbeef = n/a
116ToningEffectFaithful?int32s0xdeadbeef = n/a
120ContrastMonochromeint32s0xdeadbeef = n/a
124SharpnessMonochromeint32s0xdeadbeef = n/a
128SaturationMonochrome?int32s0xdeadbeef = n/a
132ColorToneMonochrome?int32s0xdeadbeef = n/a
136FilterEffectMonochromeint32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Yellow
  0x2 = Orange +
0x3 = Red +
0x4 = Green
+
140ToningEffectMonochromeint32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Sepia
  0x2 = Blue +
0x3 = Purple +
0x4 = Green
+
144ContrastUserDef1int32s0xdeadbeef = n/a
148SharpnessUserDef1int32s0xdeadbeef = n/a
152SaturationUserDef1int32s0xdeadbeef = n/a
156ColorToneUserDef1int32s0xdeadbeef = n/a
160FilterEffectUserDef1int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Yellow
  0x2 = Orange +
0x3 = Red +
0x4 = Green
+
164ToningEffectUserDef1int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Sepia
  0x2 = Blue +
0x3 = Purple +
0x4 = Green
+
168ContrastUserDef2int32s0xdeadbeef = n/a
172SharpnessUserDef2int32s0xdeadbeef = n/a
176SaturationUserDef2int32s0xdeadbeef = n/a
180ColorToneUserDef2int32s0xdeadbeef = n/a
184FilterEffectUserDef2int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Yellow
  0x2 = Orange +
0x3 = Red +
0x4 = Green
+
188ToningEffectUserDef2int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Sepia
  0x2 = Blue +
0x3 = Purple +
0x4 = Green
+
192ContrastUserDef3int32s0xdeadbeef = n/a
196SharpnessUserDef3int32s0xdeadbeef = n/a
200SaturationUserDef3int32s0xdeadbeef = n/a
204ColorToneUserDef3int32s0xdeadbeef = n/a
208FilterEffectUserDef3int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Yellow
  0x2 = Orange +
0x3 = Red +
0x4 = Green
+
212ToningEffectUserDef3int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Sepia
  0x2 = Blue +
0x3 = Purple +
0x4 = Green
+
216UserDef1PictureStyleint16u--> Canon UserDefStyle Values
218UserDef2PictureStyleint16u--> Canon UserDefStyle Values
220UserDef3PictureStyleint16u--> Canon UserDefStyle Values
+ +

Canon UserDefStyle Values

+

Base style for user-defined picture styles. PC values represent external +picture styles which may be downloaded from Canon and installed in the +camera.

+
+
+ + + + + + + + + + + + + + +
ValueUserDefStyleValueUserDefStyleValueUserDefStyle
0x41= PC 10x82= Portrait0x86= Monochrome
0x42= PC 20x83= Landscape0x87= Auto
0x43= PC 30x84= Neutral  
0x81= Standard0x85= Faithful  
+ +

Canon CameraInfo1DmkIV Tags

+

CameraInfo tags for the EOS 1D Mark IV. Indices shown are for firmware +versions 1.0.x, but they may be different for other firmware versions.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
7HighlightTonePriorityint8u0 = Off +
1 = On
8MeasuredEV2int8u 
9MeasuredEV3int8u 
21FlashMeteringModeint8u0 = E-TTL +
3 = TTL +
4 = External Auto +
5 = External Manual +
6 = Off
25CameraTemperatureint8u 
30FocalLengthint16uRev 
53CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
84FocusDistanceUpperint16uRev 
86FocusDistanceLowerint16uRev 
120WhiteBalanceint16u--> Canon WhiteBalance Values
124ColorTemperatureint16u 
335LensTypeint16uRev--> Canon LensType Values
337MinFocalLengthint16uRev 
339MaxFocalLengthint16uRev 
493FirmwareVersionno 
556FileIndexint32u 
568DirectoryIndexint32u 
872PictureStyleInfo---> Canon PSInfo Tags
+ +

Canon CameraInfo1DX Tags

+

CameraInfo tags for the EOS 1D X. Indices shown are for firmware version +1.0.2, but they may be different for other firmware versions.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
27CameraTemperatureint8u 
35FocalLengthint16uRev 
125CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
140FocusDistanceUpperint16uRev 
142FocusDistanceLowerint16uRev 
188WhiteBalanceint16u--> Canon WhiteBalance Values
192ColorTemperatureint16u 
244PictureStyleint8u--> Canon PictureStyle Values
423LensTypeint16uRev--> Canon LensType Values
425MinFocalLengthint16uRev 
427MaxFocalLengthint16uRev 
640FirmwareVersionno 
720FileIndexint32u 
732DirectoryIndexint32u 
1012PictureStyleInfo---> Canon PSInfo2 Tags
+ +

Canon PSInfo2 Tags

+

Custom picture style information for the EOS 5DmkIII, 60D, 600D and 1100D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ContrastStandardint32s0xdeadbeef = n/a
4SharpnessStandardint32s0xdeadbeef = n/a
8SaturationStandardint32s0xdeadbeef = n/a
12ColorToneStandardint32s0xdeadbeef = n/a
16FilterEffectStandard?int32s0xdeadbeef = n/a
20ToningEffectStandard?int32s0xdeadbeef = n/a
24ContrastPortraitint32s0xdeadbeef = n/a
28SharpnessPortraitint32s0xdeadbeef = n/a
32SaturationPortraitint32s0xdeadbeef = n/a
36ColorTonePortraitint32s0xdeadbeef = n/a
40FilterEffectPortrait?int32s0xdeadbeef = n/a
44ToningEffectPortrait?int32s0xdeadbeef = n/a
48ContrastLandscapeint32s0xdeadbeef = n/a
52SharpnessLandscapeint32s0xdeadbeef = n/a
56SaturationLandscapeint32s0xdeadbeef = n/a
60ColorToneLandscapeint32s0xdeadbeef = n/a
64FilterEffectLandscape?int32s0xdeadbeef = n/a
68ToningEffectLandscape?int32s0xdeadbeef = n/a
72ContrastNeutralint32s0xdeadbeef = n/a
76SharpnessNeutralint32s0xdeadbeef = n/a
80SaturationNeutralint32s0xdeadbeef = n/a
84ColorToneNeutralint32s0xdeadbeef = n/a
88FilterEffectNeutral?int32s0xdeadbeef = n/a
92ToningEffectNeutral?int32s0xdeadbeef = n/a
96ContrastFaithfulint32s0xdeadbeef = n/a
100SharpnessFaithfulint32s0xdeadbeef = n/a
104SaturationFaithfulint32s0xdeadbeef = n/a
108ColorToneFaithfulint32s0xdeadbeef = n/a
112FilterEffectFaithful?int32s0xdeadbeef = n/a
116ToningEffectFaithful?int32s0xdeadbeef = n/a
120ContrastMonochromeint32s0xdeadbeef = n/a
124SharpnessMonochromeint32s0xdeadbeef = n/a
128SaturationMonochrome?int32s0xdeadbeef = n/a
132ColorToneMonochrome?int32s0xdeadbeef = n/a
136FilterEffectMonochromeint32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Yellow
  0x2 = Orange +
0x3 = Red +
0x4 = Green
+
140ToningEffectMonochromeint32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Sepia
  0x2 = Blue +
0x3 = Purple +
0x4 = Green
+
144ContrastAutoint32s0xdeadbeef = n/a
148SharpnessAutoint32s0xdeadbeef = n/a
152SaturationAutoint32s0xdeadbeef = n/a
156ColorToneAutoint32s0xdeadbeef = n/a
160FilterEffectAutoint32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Yellow
  0x2 = Orange +
0x3 = Red +
0x4 = Green
+
164ToningEffectAutoint32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Sepia
  0x2 = Blue +
0x3 = Purple +
0x4 = Green
+
168ContrastUserDef1int32s0xdeadbeef = n/a
172SharpnessUserDef1int32s0xdeadbeef = n/a
176SaturationUserDef1int32s0xdeadbeef = n/a
180ColorToneUserDef1int32s0xdeadbeef = n/a
184FilterEffectUserDef1int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Yellow
  0x2 = Orange +
0x3 = Red +
0x4 = Green
+
188ToningEffectUserDef1int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Sepia
  0x2 = Blue +
0x3 = Purple +
0x4 = Green
+
192ContrastUserDef2int32s0xdeadbeef = n/a
196SharpnessUserDef2int32s0xdeadbeef = n/a
200SaturationUserDef2int32s0xdeadbeef = n/a
204ColorToneUserDef2int32s0xdeadbeef = n/a
208FilterEffectUserDef2int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Yellow
  0x2 = Orange +
0x3 = Red +
0x4 = Green
+
212ToningEffectUserDef2int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Sepia
  0x2 = Blue +
0x3 = Purple +
0x4 = Green
+
216ContrastUserDef3int32s0xdeadbeef = n/a
220SharpnessUserDef3int32s0xdeadbeef = n/a
224SaturationUserDef3int32s0xdeadbeef = n/a
228ColorToneUserDef3int32s0xdeadbeef = n/a
232FilterEffectUserDef3int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Yellow
  0x2 = Orange +
0x3 = Red +
0x4 = Green
+
236ToningEffectUserDef3int32s + +
0xdeadbeef = n/a +
0x0 = None +
0x1 = Sepia
  0x2 = Blue +
0x3 = Purple +
0x4 = Green
+
240UserDef1PictureStyleint16u--> Canon UserDefStyle Values
242UserDef2PictureStyleint16u--> Canon UserDefStyle Values
244UserDef3PictureStyleint16u--> Canon UserDefStyle Values
+ +

Canon CameraInfo5D Tags

+

CameraInfo tags for the EOS 5D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
12LensTypeint16uRev--> Canon LensType Values
23CameraTemperatureint8u 
27MacroMagnificationint8s(currently decoded only for the MP-E 65mm f/2.8 1-5x Macro Photo)
39CameraOrientationint8s0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
40FocalLengthint16uRev 
56AFPointsInFocus5Dint16uRev + +
0x0 = (none) +
Bit 0 = Center +
Bit 1 = Top +
Bit 2 = Bottom +
Bit 3 = Upper-left +
Bit 4 = Upper-right +
Bit 5 = Lower-left +
Bit 6 = Lower-right
  Bit 7 = Left +
Bit 8 = Right +
Bit 9 = AI Servo1 +
Bit 10 = AI Servo2 +
Bit 11 = AI Servo3 +
Bit 12 = AI Servo4 +
Bit 13 = AI Servo5 +
Bit 14 = AI Servo6
+
84WhiteBalanceint16u--> Canon WhiteBalance Values
88ColorTemperatureint16u 
108PictureStyleint8u--> Canon PictureStyle Values
147MinFocalLengthint16uRev 
149MaxFocalLengthint16uRev 
151LensTypeint16uRev--> Canon LensType Values
164FirmwareRevisionstring[8] 
172ShortOwnerNamestring[16] 
204DirectoryIndexint32u 
208FileIndexint16u 
232ContrastStandardint8s 
233ContrastPortraitint8s 
234ContrastLandscapeint8s 
235ContrastNeutralint8s 
236ContrastFaithfulint8s 
237ContrastMonochromeint8s 
238ContrastUserDef1int8s 
239ContrastUserDef2int8s 
240ContrastUserDef3int8s 
241SharpnessStandardint8s 
242SharpnessPortraitint8s 
243SharpnessLandscapeint8s 
244SharpnessNeutralint8s 
245SharpnessFaithfulint8s 
246SharpnessMonochromeint8s 
247SharpnessUserDef1int8s 
248SharpnessUserDef2int8s 
249SharpnessUserDef3int8s 
250SaturationStandardint8s 
251SaturationPortraitint8s 
252SaturationLandscapeint8s 
253SaturationNeutralint8s 
254SaturationFaithfulint8s 
255FilterEffectMonochromeint8s + +
-559038737 = n/a +
0 = None +
1 = Yellow
  2 = Orange +
3 = Red +
4 = Green
+
256SaturationUserDef1int8s 
257SaturationUserDef2int8s 
258SaturationUserDef3int8s 
259ColorToneStandardint8s 
260ColorTonePortraitint8s 
261ColorToneLandscapeint8s 
262ColorToneNeutralint8s 
263ColorToneFaithfulint8s 
264ToningEffectMonochromeint8s + +
-559038737 = n/a +
0 = None +
1 = Sepia
  2 = Blue +
3 = Purple +
4 = Green
+
265ColorToneUserDef1int8s 
266ColorToneUserDef2int8s 
267ColorToneUserDef3int8s 
268UserDef1PictureStyleint16u--> Canon UserDefStyle Values
270UserDef2PictureStyleint16u--> Canon UserDefStyle Values
272UserDef3PictureStyleint16u--> Canon UserDefStyle Values
284TimeStampint32u 
+ +

Canon CameraInfo5DmkII Tags

+

CameraInfo tags for the EOS 5D Mark II. Indices shown are for firmware +version 1.0.6, but they may be different for other firmware versions.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
7HighlightTonePriorityint8u0 = Off +
1 = On
21FlashMeteringModeint8u0 = E-TTL +
3 = TTL +
4 = External Auto +
5 = External Manual +
6 = Off
25CameraTemperatureint8u 
27MacroMagnificationint8u(currently decoded only for the MP-E 65mm f/2.8 1-5x Macro Photo)
30FocalLengthint16uRev 
49CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
80FocusDistanceUpperint16uRev 
82FocusDistanceLowerint16uRev 
111WhiteBalanceint16u--> Canon WhiteBalance Values
115ColorTemperatureint16u 
167PictureStyleint8u--> Canon PictureStyle Values
189HighISONoiseReductionint8u0 = Standard +
1 = Low +
2 = Strong +
3 = Off
191AutoLightingOptimizerint8u0 = Standard +
1 = Low +
2 = Strong +
3 = Off
230LensTypeint16uRev--> Canon LensType Values
232MinFocalLengthint16uRev 
234MaxFocalLengthint16uRev 
382FirmwareVersionno 
443FileIndexint32u 
455DirectoryIndexint32u 
759PictureStyleInfo---> Canon PSInfo Tags
+ +

Canon CameraInfo5DmkIII Tags

+

CameraInfo tags for the EOS 5D Mark III. Indices shown are for firmware +versions 1.0.x, but they may be different for other firmware versions.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
27CameraTemperatureint8u 
35FocalLengthint16uRev 
125CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
140FocusDistanceUpperint16uRev 
142FocusDistanceLowerint16uRev 
188WhiteBalanceint16u--> Canon WhiteBalance Values
192ColorTemperatureint16u 
244PictureStyleint8u--> Canon PictureStyle Values
339LensTypeint16uRev--> Canon LensType Values
341MinFocalLengthint16uRev 
343MaxFocalLengthint16uRev 
356LensSerialNumberundef[5] 
572FirmwareVersionno 
652FileIndexint32u 
656FileIndex2int32u 
664DirectoryIndexint32u 
668DirectoryIndex2int32u 
944PictureStyleInfo---> Canon PSInfo2 Tags
+ +

Canon CameraInfo6D Tags

+

CameraInfo tags for the EOS 6D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
27CameraTemperatureint8u 
35FocalLengthint16uRev 
131CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
146FocusDistanceUpperint16uRev 
148FocusDistanceLowerint16uRev 
194WhiteBalanceint16u--> Canon WhiteBalance Values
198ColorTemperatureint16u 
250PictureStyleint8u--> Canon PictureStyle Values
353LensTypeint16uRev--> Canon LensType Values
355MinFocalLengthint16uRev 
357MaxFocalLengthint16uRev 
598FirmwareVersionno 
682FileIndexint32u 
694DirectoryIndexint32u 
966PictureStyleInfo---> Canon PSInfo2 Tags
+ +

Canon CameraInfo7D Tags

+

CameraInfo tags for the EOS 7D. Indices shown are for firmware versions +1.0.x, but they may be different for other firmware versions.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
7HighlightTonePriorityint8u0 = Off +
1 = On
8MeasuredEV2int8u 
9MeasuredEVint8u 
21FlashMeteringModeint8u0 = E-TTL +
3 = TTL +
4 = External Auto +
5 = External Manual +
6 = Off
25CameraTemperatureint8u 
30FocalLengthint16uRev 
53CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
84FocusDistanceUpperint16uRev 
86FocusDistanceLowerint16uRev 
119WhiteBalanceint16u--> Canon WhiteBalance Values
123ColorTemperatureint16u 
175CameraPictureStyleint8u + +
0x21 = User Defined 1 +
0x22 = User Defined 2 +
0x23 = User Defined 3 +
0x81 = Standard +
0x82 = Portrait
  0x83 = Landscape +
0x84 = Neutral +
0x85 = Faithful +
0x86 = Monochrome
+
201HighISONoiseReductionint8u0 = Standard +
1 = Low +
2 = Strong +
3 = Off
274LensTypeint16uRev--> Canon LensType Values
276MinFocalLengthint16uRev 
278MaxFocalLengthint16uRev 
428FirmwareVersionno 
491FileIndexint32u 
503DirectoryIndexint32u 
807PictureStyleInfo---> Canon PSInfo Tags
+ +

Canon CameraInfo40D Tags

+

CameraInfo tags for the EOS 40D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
21FlashMeteringModeint8u0 = E-TTL +
3 = TTL +
4 = External Auto +
5 = External Manual +
6 = Off
24CameraTemperatureint8u 
27MacroMagnificationint8u(currently decoded only for the MP-E 65mm f/2.8 1-5x Macro Photo)
29FocalLengthint16uRev 
48CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
67FocusDistanceUpperint16uRev 
69FocusDistanceLowerint16uRev 
111WhiteBalanceint16u--> Canon WhiteBalance Values
115ColorTemperatureint16u 
214LensTypeint16uRev--> Canon LensType Values
216MinFocalLengthint16uRev 
218MaxFocalLengthint16uRev 
255FirmwareVersionstring[6] 
307FileIndexint32u(combined with DirectoryIndex to give the Composite FileNumber tag)
319DirectoryIndexint32u 
603PictureStyleInfo---> Canon PSInfo Tags
2347LensModelstring[64] 
+ +

Canon CameraInfo50D Tags

+

CameraInfo tags for the EOS 50D. Indices shown are for firmware versions +1.0.x, but they may be different for other firmware versions.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
7HighlightTonePriorityint8u0 = Off +
1 = On
21FlashMeteringModeint8u0 = E-TTL +
3 = TTL +
4 = External Auto +
5 = External Manual +
6 = Off
25CameraTemperatureint8u 
30FocalLengthint16uRev 
49CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
80FocusDistanceUpperint16uRev 
82FocusDistanceLowerint16uRev 
111WhiteBalanceint16u--> Canon WhiteBalance Values
115ColorTemperatureint16u 
167PictureStyleint8u--> Canon PictureStyle Values
189HighISONoiseReductionint8u0 = Standard +
1 = Low +
2 = Strong +
3 = Off
191AutoLightingOptimizerint8u0 = Standard +
1 = Low +
2 = Strong +
3 = Off
234LensTypeint16uRev--> Canon LensType Values
236MinFocalLengthint16uRev 
238MaxFocalLengthint16uRev 
350FirmwareVersionno 
411FileIndexint32u 
423DirectoryIndexint32u 
727PictureStyleInfo---> Canon PSInfo Tags
+ +

Canon CameraInfo60D Tags

+

CameraInfo tags for the EOS 60D and 1200D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
25CameraTemperatureint8u 
30FocalLengthint16uRev 
54CameraOrientationint8u(60D only) +
0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
58CameraOrientationint8u(1200D only) +
0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
85FocusDistanceUpperint16uRev(60D only)
87FocusDistanceLowerint16uRev(60D only)
125ColorTemperatureint16u(60D only)
232LensTypeint16uRev--> Canon LensType Values
234MinFocalLengthint16uRev 
236MaxFocalLengthint16uRev 
409FirmwareVersionno 
473FileIndexint32u(60D only)
485DirectoryIndexint32u(60D only)
761PictureStyleInfo---> Canon PSInfo2 Tags +
(1200D)
801PictureStyleInfo---> Canon PSInfo2 Tags +
(60D)
+ +

Canon CameraInfo70D Tags

+

CameraInfo tags for the EOS 70D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
27CameraTemperatureint8u 
35FocalLengthint16uRev 
132CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
147FocusDistanceUpperint16uRev 
149FocusDistanceLowerint16uRev 
199ColorTemperatureint16u 
358LensTypeint16uRev--> Canon LensType Values
360MinFocalLengthint16uRev 
362MaxFocalLengthint16uRev 
606FirmwareVersionno 
691FileIndexint32u 
703DirectoryIndexint32u 
975PictureStyleInfo---> Canon PSInfo2 Tags
+ +

Canon CameraInfo80D Tags

+

CameraInfo tags for the EOS 80D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
27CameraTemperatureint8u 
35FocalLengthint16uRev 
150CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
165FocusDistanceUpperint16uRev 
167FocusDistanceLowerint16uRev 
314ColorTemperatureint16u 
393LensTypeint16uRev--> Canon LensType Values
395MinFocalLengthint16uRev 
397MaxFocalLengthint16uRev 
1114FirmwareVersionno 
1198FileIndexint32u 
1210DirectoryIndexint32u 
+ +

Canon CameraInfo450D Tags

+

CameraInfo tags for the EOS 450D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
21FlashMeteringModeint8u0 = E-TTL +
3 = TTL +
4 = External Auto +
5 = External Manual +
6 = Off
24CameraTemperatureint8u 
27MacroMagnificationint8u(currently decoded only for the MP-E 65mm f/2.8 1-5x Macro Photo)
29FocalLengthint16uRev 
48CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
67FocusDistanceUpperint16uRev 
69FocusDistanceLowerint16uRev 
111WhiteBalanceint16u--> Canon WhiteBalance Values
115ColorTemperatureint16u 
222LensTypeint16uRev--> Canon LensType Values
263FirmwareVersionstring[6] 
271OwnerNamestring[32] 
307DirectoryIndexint32u 
319FileIndexint32u 
611PictureStyleInfo---> Canon PSInfo Tags
2355LensModelstring[64] 
+ +

Canon CameraInfo500D Tags

+

CameraInfo tags for the EOS 500D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
7HighlightTonePriorityint8u0 = Off +
1 = On
21FlashMeteringModeint8u0 = E-TTL +
3 = TTL +
4 = External Auto +
5 = External Manual +
6 = Off
25CameraTemperatureint8u 
30FocalLengthint16uRev 
49CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
80FocusDistanceUpperint16uRev 
82FocusDistanceLowerint16uRev 
115WhiteBalanceint16u--> Canon WhiteBalance Values
119ColorTemperatureint16u 
171PictureStyleint8u--> Canon PictureStyle Values
188HighISONoiseReductionint8u0 = Standard +
1 = Low +
2 = Strong +
3 = Off
190AutoLightingOptimizerint8u0 = Standard +
1 = Low +
2 = Strong +
3 = Off
246LensTypeint16uRev--> Canon LensType Values
248MinFocalLengthint16uRev 
250MaxFocalLengthint16uRev 
400FirmwareVersionno 
467FileIndexint32u 
479DirectoryIndexint32u 
779PictureStyleInfo---> Canon PSInfo Tags
+ +

Canon CameraInfo550D Tags

+

CameraInfo tags for the EOS 550D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
7HighlightTonePriorityint8u0 = Off +
1 = On
21FlashMeteringModeint8u0 = E-TTL +
3 = TTL +
4 = External Auto +
5 = External Manual +
6 = Off
25CameraTemperatureint8u 
30FocalLengthint16uRev 
53CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
84FocusDistanceUpperint16uRev 
86FocusDistanceLowerint16uRev 
120WhiteBalanceint16u--> Canon WhiteBalance Values
124ColorTemperatureint16u 
176PictureStyleint8u--> Canon PictureStyle Values
255LensTypeint16uRev--> Canon LensType Values
257MinFocalLengthint16uRev 
259MaxFocalLengthint16uRev 
420FirmwareVersionno 
484FileIndexint32u 
496DirectoryIndexint32u 
796PictureStyleInfo---> Canon PSInfo Tags
+ +

Canon CameraInfo600D Tags

+

CameraInfo tags for the EOS 600D and 1100D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
7HighlightTonePriorityint8u0 = Off +
1 = On
21FlashMeteringModeint8u0 = E-TTL +
3 = TTL +
4 = External Auto +
5 = External Manual +
6 = Off
25CameraTemperatureint8u 
30FocalLengthint16uRev 
56CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
87FocusDistanceUpperint16uRev 
89FocusDistanceLowerint16uRev 
123WhiteBalanceint16u--> Canon WhiteBalance Values
127ColorTemperatureint16u 
179PictureStyleint8u--> Canon PictureStyle Values
234LensTypeint16uRev--> Canon LensType Values
236MinFocalLengthint16uRev 
238MaxFocalLengthint16uRev 
411FirmwareVersionno 
475FileIndexint32u 
487DirectoryIndexint32u 
763PictureStyleInfo---> Canon PSInfo2 Tags
+ +

Canon CameraInfo650D Tags

+

CameraInfo tags for the EOS 650D and 700D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
27CameraTemperatureint8u 
35FocalLengthint16uRev 
125CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
140FocusDistanceUpperint16uRev 
142FocusDistanceLowerint16uRev 
188WhiteBalanceint16u--> Canon WhiteBalance Values
192ColorTemperatureint16u 
244PictureStyleint8u--> Canon PictureStyle Values
295LensTypeint16uRev--> Canon LensType Values
297MinFocalLengthint16uRev 
299MaxFocalLengthint16uRev 
539FirmwareVersionno(650D)
544FirmwareVersionno(700D)
624FileIndexint32u(650D)
628FileIndexint32u(700D)
636DirectoryIndexint32u(650D)
640DirectoryIndexint32u(700D)
912PictureStyleInfo---> Canon PSInfo2 Tags
+ +

Canon CameraInfo750D Tags

+

CameraInfo tags for the EOS 750D and 760D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
27CameraTemperatureint8u 
35FocalLengthint16uRev 
150CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
165FocusDistanceUpperint16uRev 
167FocusDistanceLowerint16uRev 
305WhiteBalanceint16u--> Canon WhiteBalance Values
309ColorTemperatureint16u 
361PictureStyleint8u--> Canon PictureStyle Values
388LensTypeint16uRev--> Canon LensType Values
390MinFocalLengthint16uRev 
392MaxFocalLengthint16uRev 
1085FirmwareVersionno 
1097FirmwareVersionno 
+ +

Canon CameraInfo1000D Tags

+

CameraInfo tags for the EOS 1000D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3FNumberint8u 
4ExposureTimeint8u 
6ISOint8u 
21FlashMeteringModeint8u0 = E-TTL +
3 = TTL +
4 = External Auto +
5 = External Manual +
6 = Off
24CameraTemperatureint8u 
27MacroMagnificationint8u(currently decoded only for the MP-E 65mm f/2.8 1-5x Macro Photo)
29FocalLengthint16uRev 
48CameraOrientationint8u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
67FocusDistanceUpperint16uRev 
69FocusDistanceLowerint16uRev 
111WhiteBalanceint16u--> Canon WhiteBalance Values
115ColorTemperatureint16u 
226LensTypeint16uRev--> Canon LensType Values
228MinFocalLengthint16uRev 
230MaxFocalLengthint16uRev 
267FirmwareVersionstring[6] 
311DirectoryIndexint32u 
323FileIndexint32u 
615PictureStyleInfo---> Canon PSInfo Tags
2359LensModelstring[64] 
+ +

Canon CameraInfoPowerShot Tags

+

CameraInfo tags for PowerShot models such as the A450, A460, A550, A560, +A570, A630, A640, A650, A710, A720, G7, G9, S5, SD40, SD750, SD800, SD850, +SD870, SD900, SD950, SD1000, SX100 and TX1.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0ISOint32s 
5FNumberint32s 
6ExposureTimeint32s 
23Rotationint32s 
135CameraTemperatureint32s(A450, A460, A550, A630, A640 and A710)
145CameraTemperatureint32s(A560, A570, A650, A720, G7, G9, S5, SD40, SD750, SD800, SD850, SD870, SD900, +SD950, SD1000, SX100 and TX1)
+ +

Canon CameraInfoPowerShot2 Tags

+

CameraInfo tags for PowerShot models such as the A470, A480, A490, A495, +A580, A590, A1000, A1100, A2000, A2100, A3000, A3100, D10, E1, G10, G11, +S90, S95, SD770, SD780, SD790, SD880, SD890, SD940, SD960, SD970, SD980, +SD990, SD1100, SD1200, SD1300, SD1400, SD3500, SD4000, SD4500, SX1, SX10, +SX20, SX110, SX120, SX130, SX200 and SX210.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1ISOint32s 
6FNumberint32s 
7ExposureTimeint32s 
24Rotationint32s 
153CameraTemperatureint32s(A470, A580, A590, SD770, SD790, SD890 and SD1100)
159CameraTemperatureint32s(A1000, A2000, E1, G10, SD880, SD990, SX1, SX10 and SX110)
164CameraTemperatureint32s(A480, A1100, A2100, D10, SD780, SD960, SD970, SD1200 and SX200)
168CameraTemperatureint32s(A490, A495, A3000, A3100, G11, S90, SD940, SD980, SD1300, SD1400, SD3500, +SD4000, SX20, SX120 and SX210)
261CameraTemperatureint32s(S95, SD4500 and SX130)
+ +

Canon CameraInfoUnknown32 Tags

+

Unknown CameraInfo tags are divided into 3 tables based on format size.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
71CameraTemperatureint32s(S1)
83CameraTemperatureint32s(S2)
91CameraTemperatureint32s(A410, A610, A620, S80, SD30, SD400, SD430, SD450, SD500 and SD550)
92CameraTemperatureint32s(S3)
100CameraTemperatureint32s(A420, A430, A530, A540, A700, SD600, SD630 and SD700)
-3CameraTemperatureint32s(3 entries from end of record for most newer camera models)
+ +

Canon CameraInfoUnknown16 Tags

+
+
+ + + + +
Index2Tag NameWritableValues / Notes
[no tags known]
+ +

Canon CameraInfoUnknown Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
363LensSerialNumberundef[5] 
1473FirmwareVersionno(M50)
+ +

Canon MovieInfo Tags

+

Tags written by some Canon cameras when recording video.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1FrameRateint16u 
2FrameCountint16u 
4FrameCountint32u 
6FrameRaterational32u 
106Durationint32u 
108AudioBitrateint32u 
110AudioSampleRateint32u 
112AudioChannelsint32u 
116VideoCodecundef[4] 
+ +

Canon AFInfo Tags

+

Auto-focus information used by many older Canon models. The values in this +record are sequential, and some have variable sizes based on the value of +NumAFPoints (which may be 1,5,7,9,15,45 or 53). The AFArea coordinates are +given in a system where the image has dimensions given by AFImageWidth and +AFImageHeight, and 0,0 is the image center. The direction of the Y axis +depends on the camera model, with positive Y upwards for EOS models, but +apparently downwards for PowerShot models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceTag NameWritableValues / Notes
0NumAFPointsno 
1ValidAFPointsno(number of AF points valid in the following information)
2CanonImageWidthno 
3CanonImageHeightno 
4AFImageWidthno(size of image in AF coordinates)
5AFImageHeightno 
6AFAreaWidthno 
7AFAreaHeightno 
8AFAreaXPositionsno 
9AFAreaYPositionsno 
10AFPointsInFocusno 
11PrimaryAFPoint +
Canon_AFInfo_0x000b?
no
no
 
12PrimaryAFPointno 
+ +

Canon MyColors Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
2MyColorModeint16u + +
0 = Off +
1 = Positive Film +
2 = Light Skin Tone +
3 = Dark Skin Tone +
4 = Vivid Blue +
5 = Vivid Green +
6 = Vivid Red
  7 = Color Accent +
8 = Color Swap +
9 = Custom +
12 = Vivid +
13 = Neutral +
14 = Sepia +
15 = B&W
+
+ +

Canon FaceDetect1 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
2FacesDetectedint16u 
3FaceDetectFrameSizeint16u[2] 
8Face1Positionint16s[2](X-Y coordinates for the center of each face in the Face Detect frame at the +time of focus lock. "0 0" is the center, and positive X and Y are to the +right and downwards respectively)
10Face2Positionint16s[2] 
12Face3Positionint16s[2] 
14Face4Positionint16s[2] 
16Face5Positionint16s[2] 
18Face6Positionint16s[2] 
20Face7Positionint16s[2] 
22Face8Positionint16s[2] 
24Face9Positionint16s[2] 
+ +

Canon FaceDetect2 Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1FaceWidthint8u 
2FacesDetectedint8u 
+ +

Canon AFInfo2 Tags

+

Newer version of the AFInfo record containing much of the same information +(and coordinate confusion) as the older version. In this record, NumAFPoints +may be 7, 9, 11, 19, 31, 45 or 61, depending on the camera model.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceTag NameWritableValues / Notes
0AFInfoSize?no 
1AFAreaModeno +
0 = Off (Manual Focus) +
1 = AF Point Expansion (surround) +
2 = Single-point AF +
4 = Auto +
5 = Face Detect AF +
6 = Face + Tracking +
7 = Zone AF +
8 = AF Point Expansion (4 point) +
9 = Spot AF +
10 = AF Point Expansion (8 point) +
11 = Flexizone Multi (49 point) +
12 = Flexizone Multi (9 point) +
13 = Flexizone Single +
14 = Large Zone AF
+
2NumAFPointsno 
3ValidAFPointsno(number of AF points valid in the following information)
4CanonImageWidthno 
5CanonImageHeightno 
6AFImageWidthno(size of image in AF coordinates)
7AFImageHeightno 
8AFAreaWidthsno 
9AFAreaHeightsno 
10AFAreaXPositionsno 
11AFAreaYPositionsno 
12AFPointsInFocusno 
13AFPointsSelected +
Canon_AFInfo2_0x000d?
no
no
 
14PrimaryAFPointno 
+ +

Canon ContrastInfo Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
4IntelligentContrastint16u0x0 = Off +
0x8 = On +
0xffff = n/a
+ +

Canon WBInfo Tags

+

WB tags for the Canon G9.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
2WB_GRBGLevelsAutoint32s[4] 
10WB_GRBGLevelsDaylightint32s[4] 
18WB_GRBGLevelsCloudyint32s[4] 
26WB_GRBGLevelsTungstenint32s[4] 
34WB_GRBGLevelsFluorescentint32s[4] 
42WB_GRBGLevelsFluorHighint32s[4] 
50WB_GRBGLevelsFlashint32s[4] 
58WB_GRBGLevelsUnderwaterint32s[4] 
66WB_GRBGLevelsCustom1int32s[4] 
74WB_GRBGLevelsCustom2int32s[4] 
+ +

Canon FaceDetect3 Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
3FacesDetectedint16u 
+ +

Canon TimeInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1TimeZoneint32s 
2TimeZoneCityint32s + + +
0 = n/a +
1 = Chatham Islands +
2 = Wellington +
3 = Solomon Islands +
4 = Sydney +
5 = Adelaide +
6 = Tokyo +
7 = Hong Kong +
8 = Bangkok +
9 = Yangon +
10 = Dhaka +
11 = Kathmandu
  12 = Delhi +
13 = Karachi +
14 = Kabul +
15 = Dubai +
16 = Tehran +
17 = Moscow +
18 = Cairo +
19 = Paris +
20 = London +
21 = Azores +
22 = Fernando de Noronha +
23 = Sao Paulo
  24 = Newfoundland +
25 = Santiago +
26 = Caracas +
27 = New York +
28 = Chicago +
29 = Denver +
30 = Los Angeles +
31 = Anchorage +
32 = Honolulu +
33 = Samoa +
32766 = (not set)
+
3DaylightSavingsint32s0 = Off +
60 = On
+ +

Canon FileInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1FileNumber +
ShutterCount
int32u
int32u
(the location of the upper 4 bits of the directory number is a mystery for +the EOS 30D, so the reported directory number will be incorrect for original +images with a directory number of 164 or greater) +
(there are reports that the ShutterCount changed when loading a settings file +on the 1DSmkII)
3BracketModeint16s0 = Off +
1 = AEB +
2 = FEB +
3 = ISO +
4 = WB
4BracketValueint16s 
5BracketShotNumberint16s 
6RawJpgQualityint16s + +
-1 = n/a +
1 = Economy +
2 = Normal +
3 = Fine +
4 = RAW
  5 = Superfine +
7 = CRAW +
130 = Light (RAW) +
131 = Standard (RAW)
+
7RawJpgSizeint16s + +
-1 = n/a +
0 = Large +
1 = Medium +
2 = Small +
5 = Medium 1 +
6 = Medium 2 +
7 = Medium 3 +
8 = Postcard +
9 = Widescreen +
10 = Medium Widescreen
  14 = Small 1 +
15 = Small 2 +
16 = Small 3 +
128 = 640x480 Movie +
129 = Medium Movie +
130 = Small Movie +
137 = 1280x720 Movie +
142 = 1920x1080 Movie +
143 = 4096x2160 Movie
+
8LongExposureNoiseReduction2int16s(for some modules this gives the long exposure noise reduction applied to the +image, but for other models this just reflects the setting independent of +whether or not it was applied) +
0 = Off +
1 = On (1D) +
3 = On +
4 = Auto
9WBBracketModeint16s0 = Off +
1 = On (shift AB) +
2 = On (shift GM)
12WBBracketValueABint16s 
13WBBracketValueGMint16s 
14FilterEffectint16s0 = None +
1 = Yellow +
2 = Orange +
3 = Red +
4 = Green
15ToningEffectint16s0 = None +
1 = Sepia +
2 = Blue +
3 = Purple +
4 = Green
16MacroMagnificationint16s(currently decoded only for the MP-E 65mm f/2.8 1-5x Macro Photo, and not +valid for all camera models)
19LiveViewShootingint16s0 = Off +
1 = On
20FocusDistanceUpperint16u 
21FocusDistanceLowerint16u 
23ShutterModeint16s0 = Mechanical +
1 = Electronic First Curtain +
2 = Electronic
25FlashExposureLockint16s0 = Off +
1 = On
61RFLensTypeint16u0 = n/a +
257 = Canon RF 50mm F1.2L USM +
258 = Canon RF 24-105mm F4L IS USM +
259 = Canon RF 28-70mm F2L USM +
260 = Canon RF 35mm F1.8 MACRO IS STM +
261 = Canon RF 85mm F1.2L USM +
262 = Canon RF 85mm F1.2L USM DS +
263 = Canon RF 24-70mm F2.8L IS USM +
264 = Canon RF 15-35mm F2.8L IS USM +
265 = Canon RF 24-240mm F4-6.3 IS USM +
266 = Canon RF 70-200mm F2.8L IS USM +
267 = Canon RF 85mm F2 MACRO IS STM +
268 = Canon RF 600mm F11 IS STM +
269 = Canon RF 600mm F11 IS STM + RF1.4x +
270 = Canon RF 600mm F11 IS STM + RF2x +
271 = Canon RF 800mm F11 IS STM +
272 = Canon RF 800mm F11 IS STM + RF1.4x +
273 = Canon RF 800mm F11 IS STM + RF2x +
274 = Canon RF 24-105mm F4-7.1 IS STM +
275 = Canon RF 100-500mm F4.5-7.1L IS USM +
276 = Canon RF 100-500mm F4.5-7.1L IS USM + RF1.4x +
277 = Canon RF 100-500mm F4.5-7.1L IS USM + RF2x +
278 = Canon RF 70-200mm F4L IS USM +
279 = Canon RF 100mm F2.8L MACRO IS USM +
280 = Canon RF 50mm F1.8 STM +
281 = Canon RF 14-35mm F4L IS USM +
282 = Canon RF-S 18-45mm F4.5-6.3 IS STM +
283 = Canon RF 100-400mm F5.6-8 IS USM +
284 = Canon RF 100-400mm F5.6-8 IS USM + RF1.4x +
285 = Canon RF 100-400mm F5.6-8 IS USM + RF2x +
286 = Canon RF-S 18-150mm F3.5-6.3 IS STM +
287 = Canon RF 24mm F1.8 MACRO IS STM +
288 = Canon RF 16mm F2.8 STM +
289 = Canon RF 400mm F2.8L IS USM +
290 = Canon RF 400mm F2.8L IS USM + RF1.4x +
291 = Canon RF 400mm F2.8L IS USM + RF2x +
292 = Canon RF 600mm F4L IS USM +
293 = Canon RF 600mm F4L IS USM + RF1.4x +
294 = Canon RF 600mm F4L IS USM + RF2x +
295 = Canon RF 800mm F5.6L IS USM +
296 = Canon RF 800mm F5.6L IS USM + RF1.4x +
297 = Canon RF 800mm F5.6L IS USM + RF2x +
298 = Canon RF 1200mm F8L IS USM +
299 = Canon RF 1200mm F8L IS USM + RF1.4x +
300 = Canon RF 1200mm F8L IS USM + RF2x +
302 = Canon RF 15-30mm F4.5-6.3 IS STM +
303 = Canon RF 135mm F1.8 L IS USM +
304 = Canon RF 24-50mm F4.5-6.3 IS STM +
305 = Canon RF-S 55-210mm F5-7.1 IS STM +
306 = Canon RF 100-300mm F2.8L IS USM +
307 = Canon RF 100-300mm F2.8L IS USM + RF1.4x +
308 = Canon RF 100-300mm F2.8L IS USM + RF2x +
313 = Canon RF 28mm F2.8 STM
+ +

Canon SerialInfo Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
9InternalSerialNumberstring 
+ +

Canon CropInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0CropLeftMarginint16u 
1CropRightMarginint16u 
2CropTopMarginint16u 
3CropBottomMarginint16u 
+ +

Canon AspectInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0AspectRatioint32u + +
0 = 3:2 +
1 = 1:1 +
2 = 4:3 +
7 = 16:9
  8 = 4:5 +
12 = 3:2 (APS-H crop) +
13 = 3:2 (APS-C crop) +
258 = 4:3 crop
+
1CroppedImageWidthint32u 
2CroppedImageHeightint32u 
3CroppedImageLeftint32u 
4CroppedImageTopint32u 
+ +

Canon Processing Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1ToneCurveint16s0 = Standard +
1 = Manual +
2 = Custom
2Sharpnessint16s(all models except the 20D and 350D)
3SharpnessFrequencyint16s + +
0 = n/a +
1 = Lowest +
2 = Low
  3 = Standard +
4 = High +
5 = Highest
+
4SensorRedLevelint16s 
5SensorBlueLevelint16s 
6WhiteBalanceRedint16s 
7WhiteBalanceBlueint16s 
8WhiteBalanceint16s--> Canon WhiteBalance Values
9ColorTemperatureint16s 
10PictureStyleint16s--> Canon PictureStyle Values
11DigitalGainint16s 
12WBShiftABint16s(positive is a shift toward amber)
13WBShiftGMint16s(positive is a shift toward green)
+ +

Canon ColorBalance Tags

+

These tags are used by the 10D and 300D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1WB_RGGBLevelsAutoint16s[4] 
5WB_RGGBLevelsDaylightint16s[4] 
9WB_RGGBLevelsShadeint16s[4] 
13WB_RGGBLevelsCloudyint16s[4] 
17WB_RGGBLevelsTungstenint16s[4] 
21WB_RGGBLevelsFluorescentint16s[4] 
25WB_RGGBLevelsFlashint16s[4] 
29WB_RGGBLevelsCustom +
BlackLevels
int16s[4]
int16s[4]
(black levels for the D60)
33WB_RGGBLevelsKelvinint16s[4] 
37WB_RGGBBlackLevelsint16s[4] 
+ +

Canon MeasuredColor Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
1MeasuredRGGBint16u[4] 
+ +

Canon Flags Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
1ModifiedParamFlagint16s 
+ +

Canon ModifiedInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1ModifiedToneCurveint16s0 = Standard +
1 = Manual +
2 = Custom
2ModifiedSharpnessint16s(1D and 5D only)
3ModifiedSharpnessFreqint16s + +
0 = n/a +
1 = Lowest +
2 = Low
  3 = Standard +
4 = High +
5 = Highest
+
4ModifiedSensorRedLevelint16s 
5ModifiedSensorBlueLevelint16s 
6ModifiedWhiteBalanceRedint16s 
7ModifiedWhiteBalanceBlueint16s 
8ModifiedWhiteBalanceint16s--> Canon WhiteBalance Values
9ModifiedColorTempint16s 
10ModifiedPictureStyleint16s--> Canon PictureStyle Values
11ModifiedDigitalGainint16s 
+ +

Canon PreviewImageInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1PreviewQualityint32u + +
-1 = n/a +
1 = Economy +
2 = Normal +
3 = Fine +
4 = RAW
  5 = Superfine +
7 = CRAW +
130 = Light (RAW) +
131 = Standard (RAW)
+
2PreviewImageLengthint32u* 
3PreviewImageWidthint32u 
4PreviewImageHeightint32u 
5PreviewImageStartint32u* 
+ +

Canon SensorInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1SensorWidthno 
2SensorHeightno 
5SensorLeftBorderno 
6SensorTopBorderno 
7SensorRightBorderno 
8SensorBottomBorderno 
9BlackMaskLeftBorderno(coordinates for the area to the left or right of the image used to calculate +the average black level)
10BlackMaskTopBorderno 
11BlackMaskRightBorderno 
12BlackMaskBottomBorderno 
+ +

Canon ColorData1 Tags

+

These tags are used by the 20D and 350D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
25WB_RGGBLevelsAsShotint16s[4] 
29ColorTempAsShotint16s 
30WB_RGGBLevelsAutoint16s[4] 
34ColorTempAutoint16s 
35WB_RGGBLevelsDaylightint16s[4] 
39ColorTempDaylightint16s 
40WB_RGGBLevelsShadeint16s[4] 
44ColorTempShadeint16s 
45WB_RGGBLevelsCloudyint16s[4] 
49ColorTempCloudyint16s 
50WB_RGGBLevelsTungstenint16s[4] 
54ColorTempTungstenint16s 
55WB_RGGBLevelsFluorescentint16s[4] 
59ColorTempFluorescentint16s 
60WB_RGGBLevelsFlashint16s[4] 
64ColorTempFlashint16s 
65WB_RGGBLevelsCustom1int16s[4] 
69ColorTempCustom1int16s 
70WB_RGGBLevelsCustom2int16s[4] 
74ColorTempCustom2int16s 
75ColorCalib?---> Canon ColorCalib Tags +
(A, B, C, Temperature)
+ +

Canon ColorCalib Tags

+

Camera color calibration data. For the 20D, 350D, 1DmkII and 1DSmkII the +order of the coefficients is A, B, C, Temperature, but for newer models it +is B, C, A, Temperature. These tags are extracted only when the Unknown +option is used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0CameraColorCalibration01?int16s[4] 
4CameraColorCalibration02?int16s[4] 
8CameraColorCalibration03?int16s[4] 
12CameraColorCalibration04?int16s[4] 
16CameraColorCalibration05?int16s[4] 
20CameraColorCalibration06?int16s[4] 
24CameraColorCalibration07?int16s[4] 
28CameraColorCalibration08?int16s[4] 
32CameraColorCalibration09?int16s[4] 
36CameraColorCalibration10?int16s[4] 
40CameraColorCalibration11?int16s[4] 
44CameraColorCalibration12?int16s[4] 
48CameraColorCalibration13?int16s[4] 
52CameraColorCalibration14?int16s[4] 
56CameraColorCalibration15?int16s[4] 
+ +

Canon ColorData2 Tags

+

These tags are used by the 1DmkII and 1DSmkII.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
24WB_RGGBLevelsAutoint16s[4] 
28ColorTempAutoint16s 
29WB_RGGBLevelsUnknown?int16s[4] 
33ColorTempUnknown?int16s 
34WB_RGGBLevelsAsShotint16s[4] 
38ColorTempAsShotint16s 
39WB_RGGBLevelsDaylightint16s[4] 
43ColorTempDaylightint16s 
44WB_RGGBLevelsShadeint16s[4] 
48ColorTempShadeint16s 
49WB_RGGBLevelsCloudyint16s[4] 
53ColorTempCloudyint16s 
54WB_RGGBLevelsTungstenint16s[4] 
58ColorTempTungstenint16s 
59WB_RGGBLevelsFluorescentint16s[4] 
63ColorTempFluorescentint16s 
64WB_RGGBLevelsKelvinint16s[4] 
68ColorTempKelvinint16s 
69WB_RGGBLevelsFlashint16s[4] 
73ColorTempFlashint16s 
74WB_RGGBLevelsUnknown2?int16s[4] 
78ColorTempUnknown2?int16s 
79WB_RGGBLevelsUnknown3?int16s[4] 
83ColorTempUnknown3?int16s 
84WB_RGGBLevelsUnknown4?int16s[4] 
88ColorTempUnknown4?int16s 
89WB_RGGBLevelsUnknown5?int16s[4] 
93ColorTempUnknown5?int16s 
94WB_RGGBLevelsUnknown6?int16s[4] 
98ColorTempUnknown6?int16s 
99WB_RGGBLevelsUnknown7?int16s[4] 
103ColorTempUnknown7?int16s 
104WB_RGGBLevelsUnknown8?int16s[4] 
108ColorTempUnknown8?int16s 
109WB_RGGBLevelsUnknown9?int16s[4] 
113ColorTempUnknown9?int16s 
114WB_RGGBLevelsUnknown10?int16s[4] 
118ColorTempUnknown10?int16s 
119WB_RGGBLevelsUnknown11?int16s[4] 
123ColorTempUnknown11?int16s 
124WB_RGGBLevelsUnknown12?int16s[4] 
128ColorTempUnknown12?int16s 
129WB_RGGBLevelsUnknown13?int16s[4] 
133ColorTempUnknown13?int16s 
134WB_RGGBLevelsUnknown14?int16s[4] 
138ColorTempUnknown14?int16s 
139WB_RGGBLevelsUnknown15?int16s[4] 
143ColorTempUnknown15?int16s 
144WB_RGGBLevelsPC1int16s[4] 
148ColorTempPC1int16s 
149WB_RGGBLevelsPC2int16s[4] 
153ColorTempPC2int16s 
154WB_RGGBLevelsPC3int16s[4] 
158ColorTempPC3int16s 
159WB_RGGBLevelsUnknown16?int16s[4] 
163ColorTempUnknown16?int16s 
164ColorCalib?---> Canon ColorCalib Tags +
(A, B, C, Temperature)
618RawMeasuredRGGBint32u[4](raw MeasuredRGGB values, before normalization)
+ +

Canon ColorData3 Tags

+

These tags are used by the 1DmkIIN, 5D, 30D and 400D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ColorDataVersionint16s1 = 1 (1DmkIIN/5D/30D/400D)
63WB_RGGBLevelsAsShotint16s[4] 
67ColorTempAsShotint16s 
68WB_RGGBLevelsAutoint16s[4] 
72ColorTempAutoint16s 
73WB_RGGBLevelsMeasuredint16s[4] 
77ColorTempMeasuredint16s 
78WB_RGGBLevelsDaylightint16s[4] 
82ColorTempDaylightint16s 
83WB_RGGBLevelsShadeint16s[4] 
87ColorTempShadeint16s 
88WB_RGGBLevelsCloudyint16s[4] 
92ColorTempCloudyint16s 
93WB_RGGBLevelsTungstenint16s[4] 
97ColorTempTungstenint16s 
98WB_RGGBLevelsFluorescentint16s[4] 
102ColorTempFluorescentint16s 
103WB_RGGBLevelsKelvinint16s[4] 
107ColorTempKelvinint16s 
108WB_RGGBLevelsFlashint16s[4] 
112ColorTempFlashint16s 
113WB_RGGBLevelsPC1int16s[4] 
117ColorTempPC1int16s 
118WB_RGGBLevelsPC2int16s[4] 
122ColorTempPC2int16s 
123WB_RGGBLevelsPC3int16s[4] 
127ColorTempPC3int16s 
128WB_RGGBLevelsCustomint16s[4] 
132ColorTempCustomint16s 
133ColorCalib?---> Canon ColorCalib Tags +
(B, C, A, Temperature)
196PerChannelBlackLevelint16u[4] 
584FlashOutputint16s 
585FlashBatteryLevelint16s 
586ColorTempFlashDataint16s 
647MeasuredRGGBDataint32u[4](MeasuredRGGB may be derived from these data values)
+ +

Canon ColorData4 Tags

+

These tags are used by the 1DmkIII, 1DSmkIII, 1DmkIV, 5DmkII, 7D, 40D, 50D, +60D, 450D, 500D, 550D, 1000D and 1100D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ColorDataVersionint16s +
2 = 2 (1DmkIII) +
3 = 3 (40D) +
4 = 4 (1DSmkIII) +
5 = 5 (450D/1000D) +
6 = 6 (50D/5DmkII) +
7 = 7 (500D/550D/7D/1DmkIV) +
9 = 9 (60D/1100D)
+
63ColorCoefs---> Canon ColorCoefs Tags
168ColorCalib?---> Canon ColorCalib Tags +
(B, C, A, Temperature)
231AverageBlackLevelint16u[4] 
640RawMeasuredRGGBint32u[4](raw MeasuredRGGB values, before normalization)
692PerChannelBlackLevelint16u[4] 
696NormalWhiteLevelint16u 
697SpecularWhiteLevelint16u 
698LinearityUpperMarginint16u 
715PerChannelBlackLevelint16u[4] 
719NormalWhiteLevel +
PerChannelBlackLevel
int16u
int16u[4]
 
720SpecularWhiteLevelint16u 
721LinearityUpperMarginint16u 
723NormalWhiteLevelint16u 
724SpecularWhiteLevelint16u 
725LinearityUpperMarginint16u 
+ +

Canon ColorCoefs Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0WB_RGGBLevelsAsShotint16s[4] 
4ColorTempAsShotint16s 
5WB_RGGBLevelsAutoint16s[4] 
9ColorTempAutoint16s 
10WB_RGGBLevelsMeasuredint16s[4] 
14ColorTempMeasuredint16s 
15WB_RGGBLevelsUnknown?int16s[4] 
19ColorTempUnknown?int16s 
20WB_RGGBLevelsDaylightint16s[4] 
24ColorTempDaylightint16s 
25WB_RGGBLevelsShadeint16s[4] 
29ColorTempShadeint16s 
30WB_RGGBLevelsCloudyint16s[4] 
34ColorTempCloudyint16s 
35WB_RGGBLevelsTungstenint16s[4] 
39ColorTempTungstenint16s 
40WB_RGGBLevelsFluorescentint16s[4] 
44ColorTempFluorescentint16s 
45WB_RGGBLevelsKelvinint16s[4] 
49ColorTempKelvinint16s 
50WB_RGGBLevelsFlashint16s[4] 
54ColorTempFlashint16s 
55WB_RGGBLevelsUnknown2?int16s[4] 
59ColorTempUnknown2?int16s 
60WB_RGGBLevelsUnknown3?int16s[4] 
64ColorTempUnknown3?int16s 
65WB_RGGBLevelsUnknown4?int16s[4] 
69ColorTempUnknown4?int16s 
70WB_RGGBLevelsUnknown5?int16s[4] 
74ColorTempUnknown5?int16s 
75WB_RGGBLevelsUnknown6?int16s[4] 
79ColorTempUnknown6?int16s 
80WB_RGGBLevelsUnknown7?int16s[4] 
84ColorTempUnknown7?int16s 
85WB_RGGBLevelsUnknown8?int16s[4] 
89ColorTempUnknown8?int16s 
90WB_RGGBLevelsUnknown9?int16s[4] 
94ColorTempUnknown9?int16s 
95WB_RGGBLevelsUnknown10?int16s[4] 
99ColorTempUnknown10?int16s 
100WB_RGGBLevelsUnknown11?int16s[4] 
104ColorTempUnknown11?int16s 
105WB_RGGBLevelsUnknown12?int16s[4] 
109ColorTempUnknown12?int16s 
110WB_RGGBLevelsUnknown13?int16s[4] 
114ColorTempUnknown13?int16s 
+ +

Canon ColorData5 Tags

+

These tags are used by many EOS M and PowerShot models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ColorDataVersionint16s-4 = -4 (M100/M5/M6) +
-3 = -3 (M10/M3)
71ColorCoefs +
ColorCoefs2
-
-
--> Canon ColorCoefs Tags +
--> Canon ColorCoefs2 Tags
186ColorCalib2?---> Canon ColorCalib2 Tags
255ColorCalib2?---> Canon ColorCalib2 Tags
264PerChannelBlackLevelint16s[4] 
333PerChannelBlackLevelint16s[4] 
1385NormalWhiteLevelint16u 
1386SpecularWhiteLevelint16u 
+ +

Canon ColorCoefs2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0WB_RGGBLevelsAsShotint16s[4] 
7ColorTempAsShotint16s 
8WB_RGGBLevelsAutoint16s[4] 
15ColorTempAutoint16s 
16WB_RGGBLevelsMeasuredint16s[4] 
23ColorTempMeasuredint16s 
24WB_RGGBLevelsUnknown?int16s[4] 
31ColorTempUnknown?int16s 
32WB_RGGBLevelsDaylightint16s[4] 
39ColorTempDaylightint16s 
40WB_RGGBLevelsShadeint16s[4] 
47ColorTempShadeint16s 
48WB_RGGBLevelsCloudyint16s[4] 
55ColorTempCloudyint16s 
56WB_RGGBLevelsTungstenint16s[4] 
63ColorTempTungstenint16s 
64WB_RGGBLevelsFluorescentint16s[4] 
71ColorTempFluorescentint16s 
72WB_RGGBLevelsKelvinint16s[4] 
79ColorTempKelvinint16s 
80WB_RGGBLevelsFlashint16s[4] 
87ColorTempFlashint16s 
88WB_RGGBLevelsUnknown2?int16s[4] 
95ColorTempUnknown2?int16s 
96WB_RGGBLevelsUnknown3?int16s[4] 
103ColorTempUnknown3?int16s 
104WB_RGGBLevelsUnknown4?int16s[4] 
111ColorTempUnknown4?int16s 
112WB_RGGBLevelsUnknown5?int16s[4] 
119ColorTempUnknown5?int16s 
120WB_RGGBLevelsUnknown6?int16s[4] 
127ColorTempUnknown6?int16s 
128WB_RGGBLevelsUnknown7?int16s[4] 
135ColorTempUnknown7?int16s 
136WB_RGGBLevelsUnknown8?int16s[4] 
143ColorTempUnknown8?int16s 
144WB_RGGBLevelsUnknown9?int16s[4] 
151ColorTempUnknown9?int16s 
152WB_RGGBLevelsUnknown10?int16s[4] 
159ColorTempUnknown10?int16s 
160WB_RGGBLevelsUnknown11?int16s[4] 
167ColorTempUnknown11?int16s 
168WB_RGGBLevelsUnknown12?int16s[4] 
175ColorTempUnknown12?int16s 
176WB_RGGBLevelsUnknown13?int16s[4] 
183ColorTempUnknown13?int16s 
+ +

Canon ColorCalib2 Tags

+

B, C, A, D, Temperature.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0CameraColorCalibration01?int16s[5] 
5CameraColorCalibration02?int16s[5] 
10CameraColorCalibration03?int16s[5] 
15CameraColorCalibration04?int16s[5] 
20CameraColorCalibration05?int16s[5] 
25CameraColorCalibration06?int16s[5] 
30CameraColorCalibration07?int16s[5] 
35CameraColorCalibration08?int16s[5] 
40CameraColorCalibration09?int16s[5] 
45CameraColorCalibration10?int16s[5] 
50CameraColorCalibration11?int16s[5] 
55CameraColorCalibration12?int16s[5] 
60CameraColorCalibration13?int16s[5] 
65CameraColorCalibration14?int16s[5] 
70CameraColorCalibration15?int16s[5] 
+ +

Canon ColorData6 Tags

+

These tags are used by the EOS 600D and 1200D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ColorDataVersionint16s10 = 10 (600D/1200D)
63WB_RGGBLevelsAsShotint16s[4] 
67ColorTempAsShotint16s 
68WB_RGGBLevelsAutoint16s[4] 
72ColorTempAutoint16s 
73WB_RGGBLevelsMeasuredint16s[4] 
77ColorTempMeasuredint16s 
78WB_RGGBLevelsUnknown?int16s[4] 
82ColorTempUnknown?int16s 
83WB_RGGBLevelsUnknown2?int16s[4] 
87ColorTempUnknown2?int16s 
88WB_RGGBLevelsUnknown3?int16s[4] 
92ColorTempUnknown3?int16s 
93WB_RGGBLevelsUnknown4?int16s[4] 
97ColorTempUnknown4?int16s 
98WB_RGGBLevelsUnknown5?int16s[4] 
102ColorTempUnknown5?int16s 
103WB_RGGBLevelsDaylightint16s[4] 
107ColorTempDaylightint16s 
108WB_RGGBLevelsShadeint16s[4] 
112ColorTempShadeint16s 
113WB_RGGBLevelsCloudyint16s[4] 
117ColorTempCloudyint16s 
118WB_RGGBLevelsTungstenint16s[4] 
122ColorTempTungstenint16s 
123WB_RGGBLevelsFluorescentint16s[4] 
127ColorTempFluorescentint16s 
128WB_RGGBLevelsKelvinint16s[4] 
132ColorTempKelvinint16s 
133WB_RGGBLevelsFlashint16s[4] 
137ColorTempFlashint16s 
138WB_RGGBLevelsUnknown6?int16s[4] 
142ColorTempUnknown6?int16s 
143WB_RGGBLevelsUnknown7?int16s[4] 
147ColorTempUnknown7?int16s 
148WB_RGGBLevelsUnknown8?int16s[4] 
152ColorTempUnknown8?int16s 
153WB_RGGBLevelsUnknown9?int16s[4] 
157ColorTempUnknown9?int16s 
158WB_RGGBLevelsUnknown10?int16s[4] 
162ColorTempUnknown10?int16s 
163WB_RGGBLevelsUnknown11?int16s[4] 
167ColorTempUnknown11?int16s 
168WB_RGGBLevelsUnknown12?int16s[4] 
172ColorTempUnknown12?int16s 
173WB_RGGBLevelsUnknown13?int16s[4] 
177ColorTempUnknown13?int16s 
178WB_RGGBLevelsUnknown14?int16s[4] 
182ColorTempUnknown14?int16s 
183WB_RGGBLevelsUnknown15?int16s[4] 
187ColorTempUnknown15?int16s 
188ColorCalib?---> Canon ColorCalib Tags +
(B, C, A, Temperature)
251AverageBlackLevelint16u[4] 
404RawMeasuredRGGBint32u[4](raw MeasuredRGGB values, before normalization)
479PerChannelBlackLevelint16u[4] 
483NormalWhiteLevelint16u 
484SpecularWhiteLevelint16u 
485LinearityUpperMarginint16u 
+ +

Canon ColorData7 Tags

+

These tags are used by the EOS 1DX, 5DmkIII, 6D, 7DmkII, 100D, 650D, 700D, +8000D, M and M2.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ColorDataVersionint16s10 = 10 (1DX/5DmkIII/6D/70D/100D/650D/700D/M/M2) +
11 = 11 (7DmkII/750D/760D/8000D)
63WB_RGGBLevelsAsShotint16s[4] 
67ColorTempAsShotint16s 
68WB_RGGBLevelsAutoint16s[4] 
72ColorTempAutoint16s 
73WB_RGGBLevelsMeasuredint16s[4] 
77ColorTempMeasuredint16s 
78WB_RGGBLevelsUnknown?int16s[4] 
82ColorTempUnknown?int16s 
83WB_RGGBLevelsUnknown2?int16s[4] 
87ColorTempUnknown2?int16s 
88WB_RGGBLevelsUnknown3?int16s[4] 
92ColorTempUnknown3?int16s 
93WB_RGGBLevelsUnknown4?int16s[4] 
97ColorTempUnknown4?int16s 
98WB_RGGBLevelsUnknown5?int16s[4] 
102ColorTempUnknown5?int16s 
103WB_RGGBLevelsUnknown6?int16s[4] 
107ColorTempUnknown6?int16s 
108WB_RGGBLevelsUnknown7?int16s[4] 
112ColorTempUnknown7?int16s 
113WB_RGGBLevelsUnknown8?int16s[4] 
117ColorTempUnknown8?int16s 
118WB_RGGBLevelsUnknown9?int16s[4] 
122ColorTempUnknown9?int16s 
123WB_RGGBLevelsUnknown10?int16s[4] 
127ColorTempUnknown10?int16s 
128WB_RGGBLevelsDaylightint16s[4] 
132ColorTempDaylightint16s 
133WB_RGGBLevelsShadeint16s[4] 
137ColorTempShadeint16s 
138WB_RGGBLevelsCloudyint16s[4] 
142ColorTempCloudyint16s 
143WB_RGGBLevelsTungstenint16s[4] 
147ColorTempTungstenint16s 
148WB_RGGBLevelsFluorescentint16s[4] 
152ColorTempFluorescentint16s 
153WB_RGGBLevelsKelvinint16s[4] 
157ColorTempKelvinint16s 
158WB_RGGBLevelsFlashint16s[4] 
162ColorTempFlashint16s 
163WB_RGGBLevelsUnknown11?int16s[4] 
167ColorTempUnknown11?int16s 
168WB_RGGBLevelsUnknown12?int16s[4] 
172ColorTempUnknown12?int16s 
173WB_RGGBLevelsUnknown13?int16s[4] 
177ColorTempUnknown13?int16s 
178WB_RGGBLevelsUnknown14?int16s[4] 
182ColorTempUnknown14?int16s 
183WB_RGGBLevelsUnknown15?int16s[4] 
187ColorTempUnknown15?int16s 
188WB_RGGBLevelsUnknown16?int16s[4] 
192ColorTempUnknown16?int16s 
193WB_RGGBLevelsUnknown17?int16s[4] 
197ColorTempUnknown17?int16s 
198WB_RGGBLevelsUnknown18?int16s[4] 
202ColorTempUnknown18?int16s 
203WB_RGGBLevelsUnknown19?int16s[4] 
207ColorTempUnknown19?int16s 
208WB_RGGBLevelsUnknown20?int16s[4] 
212ColorTempUnknown20?int16s 
213ColorCalib?---> Canon ColorCalib Tags +
(B, C, A, Temperature)
276AverageBlackLevelint16u[4] 
429RawMeasuredRGGBint32u[4](raw MeasuredRGGB values, before normalization)
504PerChannelBlackLevelint16u[4] 
508NormalWhiteLevelint16u 
509SpecularWhiteLevelint16u 
510LinearityUpperMarginint16u 
619RawMeasuredRGGBint32u[4] 
728PerChannelBlackLevelint16u[4] 
732NormalWhiteLevelint16u 
733SpecularWhiteLevelint16u 
734LinearityUpperMarginint16u 
+ +

Canon ColorData8 Tags

+

These tags are used by the EOS 1DXmkII, 5DS, 5DSR, 5DmkIV, 6DmkII, 77D, 80D, +200D, 800D, 1300D, 2000D, 4000D and 9000D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ColorDataVersionint16s12 = 12 (1DXmkII/5DS/5DSR) +
13 = 13 (80D/5DmkIV) +
14 = 14 (1300D/2000D/4000D) +
15 = 15 (6DmkII/77D/200D/800D,9000D)
63WB_RGGBLevelsAsShotint16s[4] 
67ColorTempAsShotint16s 
68WB_RGGBLevelsAutoint16s[4] 
72ColorTempAutoint16s 
73WB_RGGBLevelsMeasuredint16s[4] 
77ColorTempMeasuredint16s 
78WB_RGGBLevelsUnknown?int16s[4] 
82ColorTempUnknown?int16s 
83WB_RGGBLevelsUnknown2?int16s[4] 
87ColorTempUnknown2?int16s 
88WB_RGGBLevelsUnknown3?int16s[4] 
92ColorTempUnknown3?int16s 
93WB_RGGBLevelsUnknown4?int16s[4] 
97ColorTempUnknown4?int16s 
98WB_RGGBLevelsUnknown5?int16s[4] 
102ColorTempUnknown5?int16s 
103WB_RGGBLevelsUnknown6?int16s[4] 
107ColorTempUnknown6?int16s 
108WB_RGGBLevelsUnknown7?int16s[4] 
112ColorTempUnknown7?int16s 
113WB_RGGBLevelsUnknown8?int16s[4] 
117ColorTempUnknown8?int16s 
118WB_RGGBLevelsUnknown9?int16s[4] 
122ColorTempUnknown9?int16s 
123WB_RGGBLevelsUnknown10?int16s[4] 
127ColorTempUnknown10?int16s 
128WB_RGGBLevelsUnknown11?int16s[4] 
132ColorTempUnknown11?int16s 
133WB_RGGBLevelsDaylightint16s[4] 
137ColorTempDaylightint16s 
138WB_RGGBLevelsShadeint16s[4] 
142ColorTempShadeint16s 
143WB_RGGBLevelsCloudyint16s[4] 
147ColorTempCloudyint16s 
148WB_RGGBLevelsTungstenint16s[4] 
152ColorTempTungstenint16s 
153WB_RGGBLevelsFluorescentint16s[4] 
157ColorTempFluorescentint16s 
158WB_RGGBLevelsKelvinint16s[4] 
162ColorTempKelvinint16s 
163WB_RGGBLevelsFlashint16s[4] 
167ColorTempFlashint16s 
168WB_RGGBLevelsUnknown12?int16s[4] 
172ColorTempUnknown12?int16s 
173WB_RGGBLevelsUnknown13?int16s[4] 
177ColorTempUnknown13?int16s 
178WB_RGGBLevelsUnknown14?int16s[4] 
182ColorTempUnknown14?int16s 
183WB_RGGBLevelsUnknown15?int16s[4] 
187ColorTempUnknown15?int16s 
188WB_RGGBLevelsUnknown16?int16s[4] 
192ColorTempUnknown16?int16s 
193WB_RGGBLevelsUnknown17?int16s[4] 
197ColorTempUnknown17?int16s 
198WB_RGGBLevelsUnknown18?int16s[4] 
202ColorTempUnknown18?int16s 
203WB_RGGBLevelsUnknown19?int16s[4] 
207ColorTempUnknown19?int16s 
208WB_RGGBLevelsUnknown20?int16s[4] 
212ColorTempUnknown20?int16s 
213WB_RGGBLevelsUnknown21?int16s[4] 
217ColorTempUnknown21?int16s 
218WB_RGGBLevelsUnknown22?int16s[4] 
222ColorTempUnknown22?int16s 
223WB_RGGBLevelsUnknown23?int16s[4] 
227ColorTempUnknown23?int16s 
228WB_RGGBLevelsUnknown24?int16s[4] 
232ColorTempUnknown24?int16s 
233WB_RGGBLevelsUnknown25?int16s[4] 
237ColorTempUnknown25?int16s 
238WB_RGGBLevelsUnknown26?int16s[4] 
242ColorTempUnknown26?int16s 
243WB_RGGBLevelsUnknown27?int16s[4] 
247ColorTempUnknown27?int16s 
248WB_RGGBLevelsUnknown28?int16s[4] 
252ColorTempUnknown28?int16s 
253WB_RGGBLevelsUnknown29?int16s[4] 
257ColorTempUnknown29?int16s 
258WB_RGGBLevelsUnknown30?int16s[4] 
262ColorTempUnknown30?int16s 
263ColorCalib?---> Canon ColorCalib Tags +
(B, C, A, Temperature)
326AverageBlackLevelint16u[4] 
556PerChannelBlackLevelint16u[4](1300D)
560NormalWhiteLevelint16u(1300D)
561SpecularWhiteLevelint16u(1300D)
562LinearityUpperMarginint16u(1300D)
778PerChannelBlackLevelint16u[4](5DS, 5DS R, 77D, 80D and 800D)
782NormalWhiteLevelint16u(5DS, 5DS R, 77D, 80D and 800D)
783SpecularWhiteLevelint16u(5DS, 5DS R, 77D, 80D and 800D)
784LinearityUpperMarginint16u(5DS, 5DS R, 77D, 80D and 800D)
+ +

Canon ColorData9 Tags

+

These tags are used by the M6mkII, M50, M200, EOS R, RP, 90D, 250D and 850D

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ColorDataVersionint16s16 = 16 (M50) +
17 = 17 (EOS R) +
18 = 18 (EOS RP/250D) +
19 = 19 (90D/850D/M6mkII/M200)
71WB_RGGBLevelsAsShotint16s[4] 
75ColorTempAsShotint16s 
76WB_RGGBLevelsAutoint16s[4] 
80ColorTempAutoint16s 
81WB_RGGBLevelsMeasuredint16s[4] 
85ColorTempMeasuredint16s 
86WB_RGGBLevelsUnknown?int16s[4] 
90ColorTempUnknown?int16s 
91WB_RGGBLevelsUnknown2?int16s[4] 
95ColorTempUnknown2?int16s 
96WB_RGGBLevelsUnknown3?int16s[4] 
100ColorTempUnknown3?int16s 
101WB_RGGBLevelsUnknown4?int16s[4] 
105ColorTempUnknown4?int16s 
106WB_RGGBLevelsUnknown5?int16s[4] 
110ColorTempUnknown5?int16s 
111WB_RGGBLevelsUnknown6?int16s[4] 
115ColorTempUnknown6?int16s 
116WB_RGGBLevelsUnknown7?int16s[4] 
120ColorTempUnknown7?int16s 
121WB_RGGBLevelsUnknown8?int16s[4] 
125ColorTempUnknown8?int16s 
126WB_RGGBLevelsUnknown9?int16s[4] 
130ColorTempUnknown9?int16s 
131WB_RGGBLevelsUnknown10?int16s[4] 
135ColorTempUnknown10?int16s 
136WB_RGGBLevelsDaylightint16s[4] 
140ColorTempDaylightint16s 
141WB_RGGBLevelsShadeint16s[4] 
145ColorTempShadeint16s 
146WB_RGGBLevelsCloudyint16s[4] 
150ColorTempCloudyint16s 
151WB_RGGBLevelsTungstenint16s[4] 
155ColorTempTungstenint16s 
156WB_RGGBLevelsFluorescentint16s[4] 
160ColorTempFluorescentint16s 
161WB_RGGBLevelsKelvinint16s[4] 
165ColorTempKelvinint16s 
166WB_RGGBLevelsFlashint16s[4] 
170ColorTempFlashint16s 
171WB_RGGBLevelsUnknown11?int16s[4] 
175ColorTempUnknown11?int16s 
176WB_RGGBLevelsUnknown12?int16s[4] 
180ColorTempUnknown12?int16s 
181WB_RGGBLevelsUnknown13?int16s[4] 
185ColorTempUnknown13?int16s 
186WB_RGGBLevelsUnknown14?int16s[4] 
190ColorTempUnknown14?int16s 
191WB_RGGBLevelsUnknown15?int16s[4] 
195ColorTempUnknown15?int16s 
196WB_RGGBLevelsUnknown16?int16s[4] 
200ColorTempUnknown16?int16s 
201WB_RGGBLevelsUnknown17?int16s[4] 
205ColorTempUnknown17?int16s 
206WB_RGGBLevelsUnknown18?int16s[4] 
210ColorTempUnknown18?int16s 
211WB_RGGBLevelsUnknown19?int16s[4] 
215ColorTempUnknown19?int16s 
216WB_RGGBLevelsUnknown20?int16s[4] 
220ColorTempUnknown20?int16s 
221WB_RGGBLevelsUnknown21?int16s[4] 
225ColorTempUnknown21?int16s 
226WB_RGGBLevelsUnknown22?int16s[4] 
230ColorTempUnknown22?int16s 
231WB_RGGBLevelsUnknown23?int16s[4] 
235ColorTempUnknown23?int16s 
236WB_RGGBLevelsUnknown24?int16s[4] 
240ColorTempUnknown24?int16s 
241WB_RGGBLevelsUnknown25?int16s[4] 
245ColorTempUnknown25?int16s 
246WB_RGGBLevelsUnknown26?int16s[4] 
250ColorTempUnknown26?int16s 
251WB_RGGBLevelsUnknown27?int16s[4] 
255ColorTempUnknown27?int16s 
256WB_RGGBLevelsUnknown28?int16s[4] 
260ColorTempUnknown28?int16s 
261WB_RGGBLevelsUnknown29?int16s[4] 
265ColorTempUnknown29?int16s 
266ColorCalib?---> Canon ColorCalib Tags
329PerChannelBlackLevelint16u[4] 
796NormalWhiteLevelint16u 
797SpecularWhiteLevelint16u 
798LinearityUpperMarginint16u 
+ +

Canon ColorData10 Tags

+

These tags are used by the R5, R5 and EOS 1DXmkIII.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ColorDataVersionint16s32 = 32 (1DXmkIII) +
33 = 33 (R5/R6)
85WB_RGGBLevelsAsShotint16s[4] 
89ColorTempAsShotint16s 
90WB_RGGBLevelsAutoint16s[4] 
94ColorTempAutoint16s 
95WB_RGGBLevelsMeasuredint16s[4] 
99ColorTempMeasuredint16s 
100WB_RGGBLevelsUnknown?int16s[4] 
104ColorTempUnknown?int16s 
105WB_RGGBLevelsUnknown2?int16s[4] 
109ColorTempUnknown2?int16s 
110WB_RGGBLevelsUnknown3?int16s[4] 
114ColorTempUnknown3?int16s 
115WB_RGGBLevelsUnknown4?int16s[4] 
119ColorTempUnknown4?int16s 
120WB_RGGBLevelsUnknown5?int16s[4] 
124ColorTempUnknown5?int16s 
125WB_RGGBLevelsUnknown6?int16s[4] 
129ColorTempUnknown6?int16s 
130WB_RGGBLevelsUnknown7?int16s[4] 
134ColorTempUnknown7?int16s 
135WB_RGGBLevelsUnknown8?int16s[4] 
139ColorTempUnknown8?int16s 
140WB_RGGBLevelsUnknown9?int16s[4] 
144ColorTempUnknown9?int16s 
145WB_RGGBLevelsUnknown10?int16s[4] 
149ColorTempUnknown10?int16s 
150WB_RGGBLevelsDaylightint16s[4] 
154ColorTempDaylightint16s 
155WB_RGGBLevelsShadeint16s[4] 
159ColorTempShadeint16s 
160WB_RGGBLevelsCloudyint16s[4] 
164ColorTempCloudyint16s 
165WB_RGGBLevelsTungstenint16s[4] 
169ColorTempTungstenint16s 
170WB_RGGBLevelsFluorescentint16s[4] 
174ColorTempFluorescentint16s 
175WB_RGGBLevelsKelvinint16s[4] 
179ColorTempKelvinint16s 
180WB_RGGBLevelsFlashint16s[4] 
184ColorTempFlashint16s 
185WB_RGGBLevelsUnknown11?int16s[4] 
189ColorTempUnknown11?int16s 
190WB_RGGBLevelsUnknown12?int16s[4] 
194ColorTempUnknown12?int16s 
195WB_RGGBLevelsUnknown13?int16s[4] 
199ColorTempUnknown13?int16s 
200WB_RGGBLevelsUnknown14?int16s[4] 
204ColorTempUnknown14?int16s 
205WB_RGGBLevelsUnknown15?int16s[4] 
209ColorTempUnknown15?int16s 
210WB_RGGBLevelsUnknown16?int16s[4] 
214ColorTempUnknown16?int16s 
215WB_RGGBLevelsUnknown17?int16s[4] 
219ColorTempUnknown17?int16s 
220WB_RGGBLevelsUnknown18?int16s[4] 
224ColorTempUnknown18?int16s 
225WB_RGGBLevelsUnknown19?int16s[4] 
229ColorTempUnknown19?int16s 
230WB_RGGBLevelsUnknown20?int16s[4] 
234ColorTempUnknown20?int16s 
235WB_RGGBLevelsUnknown21?int16s[4] 
239ColorTempUnknown21?int16s 
240WB_RGGBLevelsUnknown22?int16s[4] 
244ColorTempUnknown22?int16s 
245WB_RGGBLevelsUnknown23?int16s[4] 
249ColorTempUnknown23?int16s 
250WB_RGGBLevelsUnknown24?int16s[4] 
254ColorTempUnknown24?int16s 
255WB_RGGBLevelsUnknown25?int16s[4] 
259ColorTempUnknown25?int16s 
260WB_RGGBLevelsUnknown26?int16s[4] 
264ColorTempUnknown26?int16s 
265WB_RGGBLevelsUnknown27?int16s[4] 
269ColorTempUnknown27?int16s 
270WB_RGGBLevelsUnknown28?int16s[4] 
274ColorTempUnknown28?int16s 
275WB_RGGBLevelsUnknown29?int16s[4] 
279ColorTempUnknown29?int16s 
280ColorCalib?---> Canon ColorCalib Tags
343PerChannelBlackLevelint16u[4] 
810NormalWhiteLevelint16u 
811SpecularWhiteLevelint16u 
812LinearityUpperMarginint16u 
+ +

Canon ColorData11 Tags

+

These tags are used by the EOS R3, R7 and R6mkII

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ColorDataVersionint16s34 = 34 (R3) +
48 = 48 (R7, R10, R6 Mark II)
105WB_RGGBLevelsAsShotint16s[4] 
109ColorTempAsShotint16s 
110WB_RGGBLevelsAutoint16s[4] 
114ColorTempAutoint16s 
115WB_RGGBLevelsMeasuredint16s[4] 
119ColorTempMeasuredint16s 
120WB_RGGBLevelsUnknown?int16s[4] 
124ColorTempUnknown?int16s 
125WB_RGGBLevelsUnknown2?int16s[4] 
129ColorTempUnknown2?int16s 
130WB_RGGBLevelsUnknown3?int16s[4] 
134ColorTempUnknown3?int16s 
135WB_RGGBLevelsUnknown4?int16s[4] 
139ColorTempUnknown4?int16s 
140WB_RGGBLevelsUnknown5?int16s[4] 
144ColorTempUnknown5?int16s 
145WB_RGGBLevelsUnknown6?int16s[4] 
149ColorTempUnknown6?int16s 
150WB_RGGBLevelsUnknown7?int16s[4] 
154ColorTempUnknown7?int16s 
155WB_RGGBLevelsUnknown8?int16s[4] 
159ColorTempUnknown8?int16s 
160WB_RGGBLevelsUnknown9?int16s[4] 
164ColorTempUnknown9?int16s 
165WB_RGGBLevelsUnknown10?int16s[4] 
169ColorTempUnknown10?int16s 
170WB_RGGBLevelsUnknown11?int16s[4] 
174ColorTempUnknown11?int16s 
175WB_RGGBLevelsUnknown11?int16s[4] 
179ColorTempUnknown11?int16s 
180WB_RGGBLevelsUnknown12?int16s[4] 
184ColorTempUnknown12?int16s 
185WB_RGGBLevelsUnknown13?int16s[4] 
189ColorTempUnknown13?int16s 
190WB_RGGBLevelsUnknown14?int16s[4] 
194ColorTempUnknown14?int16s 
195WB_RGGBLevelsUnknown15?int16s[4] 
199ColorTempUnknown15?int16s 
200WB_RGGBLevelsUnknown16?int16s[4] 
204ColorTempUnknown16?int16s 
205WB_RGGBLevelsDaylightint16s[4] 
209ColorTempDaylightint16s 
210WB_RGGBLevelsShadeint16s[4] 
214ColorTempShadeint16s 
215WB_RGGBLevelsCloudyint16s[4] 
219ColorTempCloudyint16s 
220WB_RGGBLevelsTungstenint16s[4] 
224ColorTempTungstenint16s 
225WB_RGGBLevelsFluorescentint16s[4] 
229ColorTempFluorescentint16s 
230WB_RGGBLevelsKelvinint16s[4] 
234ColorTempKelvinint16s 
235WB_RGGBLevelsFlashint16s[4] 
239ColorTempFlashint16s 
240WB_RGGBLevelsUnknown17?int16s[4] 
244ColorTempUnknown17?int16s 
245WB_RGGBLevelsUnknown18?int16s[4] 
249ColorTempUnknown18?int16s 
250WB_RGGBLevelsUnknown19?int16s[4] 
254ColorTempUnknown19?int16s 
255WB_RGGBLevelsUnknown20?int16s[4] 
259ColorTempUnknown20?int16s 
260WB_RGGBLevelsUnknown21?int16s[4] 
264ColorTempUnknown21?int16s 
265WB_RGGBLevelsUnknown22?int16s[4] 
269ColorTempUnknown22?int16s 
270WB_RGGBLevelsUnknown23?int16s[4] 
274ColorTempUnknown23?int16s 
275WB_RGGBLevelsUnknown24?int16s[4] 
279ColorTempUnknown24?int16s 
280WB_RGGBLevelsUnknown25?int16s[4] 
284ColorTempUnknown25?int16s 
285WB_RGGBLevelsUnknown26?int16s[4] 
289ColorTempUnknown26?int16s 
290WB_RGGBLevelsUnknown27?int16s[4] 
294ColorTempUnknown27?int16s 
300ColorCalib?---> Canon ColorCalib Tags
363PerChannelBlackLevelint16u[4] 
640NormalWhiteLevelint16u 
641SpecularWhiteLevelint16u 
642LinearityUpperMarginint16u 
+ +

Canon ColorDataUnknown Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
0ColorDataVersionno 
+ +

Canon ColorInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1Saturationint16s0 = Normal
2ColorToneint16s0 = Normal
3ColorSpaceint16s1 = sRGB +
2 = Adobe RGB
+ +

Canon AFMicroAdj Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1AFMicroAdjModeint32s0 = Disable +
1 = Adjust all by the same amount +
2 = Adjust by lens
2AFMicroAdjValuerational64s 
+ +

Canon VignettingCorr Tags

+

This information is found in images from newer EOS models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0VignettingCorrVersionno 
2PeripheralLightingint16s0 = Off +
1 = On
3DistortionCorrectionint16s0 = Off +
1 = On
4ChromaticAberrationCorrint16s0 = Off +
1 = On
5ChromaticAberrationCorrint16s0 = Off +
1 = On
6PeripheralLightingValueint16s 
9DistortionCorrectionValueint16s 
11OriginalImageWidthint16s(full size of original image before being rotated or scaled in camera)
12OriginalImageHeightint16s 
+ +

Canon VignettingCorrUnknown Tags

+

Vignetting correction from PowerShot models.

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
0VignettingCorrVersionno 
+ +

Canon VignettingCorr2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
5PeripheralLightingSettingint32s0 = Off +
1 = On
6ChromaticAberrationSettingint32s0 = Off +
1 = On
7DistortionCorrectionSettingint32s0 = Off +
1 = On
9DigitalLensOptimizerSettingint32s0 = Off +
1 = On
+ +

Canon LightingOpt Tags

+

This information is new in images from the EOS 7D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1PeripheralIlluminationCorrint32s0 = Off +
1 = On
2AutoLightingOptimizerint32s0 = Standard +
1 = Low +
2 = Strong +
3 = Off
3HighlightTonePriorityint32s0 = Off +
1 = On
4LongExposureNoiseReductionint32s0 = Off +
1 = Auto +
2 = On
5HighISONoiseReductionint32s0 = Standard +
1 = Low +
2 = Strong +
3 = Off
10DigitalLensOptimizerint32s0 = Off +
1 = Stanard +
2 = High
+ +

Canon LensInfo Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensSerialNumberundef[5](apparently this is an internal serial number because it doesn't correspond +to the one printed on the lens)
+ +

Canon Ambience Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
1AmbienceSelectionint32s + + +
0 = Standard +
1 = Vivid +
2 = Warm
  3 = Soft +
4 = Cool +
5 = Intense
  6 = Brighter +
7 = Darker +
8 = Monochrome
+
+ +

Canon MultiExp Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1MultiExposureint32s0 = Off +
1 = On +
2 = On (RAW)
2MultiExposureControlint32s0 = Additive +
1 = Average +
2 = Bright (comparative) +
3 = Dark (comparative)
3MultiExposureShotsint32s 
+ +

Canon FilterInfo Tags

+

Information about creative filter settings.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0101GrainyBWFilterno-1 = Off
0x0201SoftFocusFilterno-1 = Off
0x0301ToyCameraFilterno-1 = Off
0x0401MiniatureFilterno-1 = Off
0x0402MiniatureFilterOrientationno0 = Horizontal +
1 = Vertical
0x0403MiniatureFilterPositionno 
0x0404MiniatureFilterParameterno 
0x0501FisheyeFilterno-1 = Off
0x0601PaintingFilterno-1 = Off
0x0701WatercolorFilterno-1 = Off
+ +

Canon HDRInfo Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1HDRint32s0 = Off +
1 = Auto +
2 = On
2HDREffectint32s0 = Natural +
1 = Art (standard) +
2 = Art (vivid) +
3 = Art (bold) +
4 = Art (embossed)
+ +

Canon LogInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
4CompressionFormatint32s0 = Editing (ALL-I) +
1 = Standard (IPB) +
2 = Light (IPB) +
3 = Motion JPEG +
4 = RAW
6Sharpnessint32s 
7Saturationint32s0 = Normal
8ColorToneint32s0 = Normal
9ColorSpace2int32s0 = BT.709 +
1 = BT.2020 +
2 = CinemaGamut
10ColorMatrixint32s0 = EOS Original +
1 = Neutral
11CanonLogVersionint32s0 = OFF +
1 = CLogV1 +
2 = CLogV2 +
3 = CLogV3
+ +

Canon AFConfig Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1AFConfigToolint32s 
2AFTrackingSensitivityint32s 
3AFAccelDecelTrackingint32s 
4AFPointSwitchingint32s 
5AIServoFirstImageint32s0 = Equal Priority +
1 = Release Priority +
2 = Focus Priority
6AIServoSecondImageint32s0 = Equal Priority +
1 = Release Priority +
2 = Focus Priority +
3 = Release High Priority +
4 = Focus High Priority
7USMLensElectronicMFint32s0 = Enable After AF +
1 = Disable After AF +
2 = Disable in AF Mode
8AFAssistBeamint32s0 = Enable +
1 = Disable +
2 = IR AF Assist Beam Only
9OneShotAFReleaseint32s0 = Focus Priority +
1 = Release Priority
10AutoAFPointSelEOSiTRAFint32s(only valid for some models) +
0 = Enable +
1 = Disable
11LensDriveWhenAFImpossibleint32s0 = Continue Focus Search +
1 = Stop Focus Search
12SelectAFAreaSelectionModeint32s +
Bit 0 = Single-point AF +
Bit 1 = Auto +
Bit 2 = Zone AF +
Bit 3 = AF Point Expansion (4 point) +
Bit 4 = Spot AF +
Bit 5 = AF Point Expansion (8 point)
+
13AFAreaSelectionMethodint32s0 = M-Fn Button +
1 = Main Dial
14OrientationLinkedAFint32s0 = Same for Vert/Horiz Points +
1 = Separate Vert/Horiz Points +
2 = Separate Area+Points
15ManualAFPointSelPatternint32s0 = Stops at AF Area Edges +
1 = Continuous
16AFPointDisplayDuringFocusint32s0 = Selected (constant) +
1 = All (constant) +
2 = Selected (pre-AF, focused) +
3 = Selected (focused) +
4 = Disabled
17VFDisplayIlluminationint32s0 = Auto +
1 = Enable +
2 = Disable
18AFStatusViewfinderint32s(1D X only) +
0 = Show in Field of View +
1 = Show Outside View
19InitialAFPointInServoint32s(1D X only) +
0 = Initial AF Point Selected +
1 = Manual AF Point +
2 = Auto
+ +

Canon RawBurstInfo Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1RawBurstImageNumint32u 
2RawBurstImageCountint32u 
+ +

Canon CTMD Tags

+

Canon Timed MetaData tags found in CR3 images. The ExtractEmbedded option +is automatically applied when reading CR3 files to be able to extract this +information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001TimeStampno 
0x0004FocalInfo---> Canon FocalInfo Tags
0x0005ExposureInfo---> Canon ExposureInfo Tags
0x0007ExifInfo7---> Canon ExifInfo Tags
0x0008ExifInfo8---> Canon ExifInfo Tags
0x0009ExifInfo9---> Canon ExifInfo Tags
+ +

Canon FocalInfo Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
0FocalLengthno 
+ +

Canon ExposureInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0FNumberno 
1ExposureTimeno 
2ISOno 
+ +

Canon ExifInfo Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x8769ExifIFD---> EXIF Tags
0x927cMakerNoteCanon---> Canon Tags
+ +

Canon CDI1 Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'IAD1'IAD1---> Canon IAD1 Tags
+ +

Canon IAD1 Tags

+
+
+ + + + +
Index2Tag NameWritableValues / Notes
[no tags known]
+ +

Canon CMP1 Tags

+
+
+ + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
8ImageWidthno 
10ImageHeightno 
+ +

Canon CNOP Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

Canon CNTH Tags

+

Canon-specific QuickTime tags found in the CNTH atom of MOV/MP4 videos from +some cameras.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CNDA'ThumbnailImageundef(the full THM image, embedded metadata is extracted as the first sub-document)
+ +

Canon uuid Tags

+

Tags extracted from the uuid atom of MP4 videos from cameras such as the +SX280, and CR3 images from cameras such as the EOS M50.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CCTP'CanonCCTP---> Canon CCTP Tags
'CMT1'IFD0---> EXIF Tags
'CMT2'ExifIFD---> EXIF Tags
'CMT3'MakerNoteCanon---> Canon Tags
'CMT4'GPSInfo---> GPS Tags
'CNCV'CompressorVersionno 
'CNOP'CanonCNOP---> Canon CNOP Tags
'CNTH'CanonCNTH---> Canon CNTH Tags
'THMB'ThumbnailImageno 
+ +

Canon CCTP Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

Canon Skip Tags

+

Information found in the "skip" atom of Canon MOV videos.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CNDB'Unknown_CNDB?no 
+ +

Canon uuid2 Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CNOP'CanonVRD---> CanonVRD Tags
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 19, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/CanonCustom.html b/ExifTool/html/TagNames/CanonCustom.html new file mode 100644 index 0000000..0742d7b --- /dev/null +++ b/ExifTool/html/TagNames/CanonCustom.html @@ -0,0 +1,2224 @@ + + + + +CanonCustom Tags + + + +

CanonCustom Functions1D Tags

+

These custom functions are used by all 1D models up to but not including the +Mark III.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0FocusingScreenint8u0 = Ec-N, R +
1 = Ec-A,B,C,CII,CIII,D,H,I,L
1FinderDisplayDuringExposureint8u0 = Off +
1 = On
2ShutterReleaseNoCFCardint8u0 = Yes +
1 = No
3ISOSpeedExpansionint8u0 = No +
1 = Yes
4ShutterAELButtonint8u0 = AF/AE lock stop +
1 = AE lock/AF +
2 = AF/AF lock, No AE lock +
3 = AE/AF, No AE lock
5ManualTvint8u0 = Tv=Main/Av=Control +
1 = Tv=Control/Av=Main +
2 = Tv=Main/Av=Main w/o lens +
3 = Tv=Control/Av=Main w/o lens
6ExposureLevelIncrementsint8u0 = 1/3-stop set, 1/3-stop comp. +
1 = 1-stop set, 1/3-stop comp. +
2 = 1/2-stop set, 1/2-stop comp.
7USMLensElectronicMFint8u0 = Turns on after one-shot AF +
1 = Turns off after one-shot AF +
2 = Always turned off
8LCDPanelsint8u0 = Remain. shots/File no. +
1 = ISO/Remain. shots +
2 = ISO/File no. +
3 = Shots in folder/Remain. shots
9AEBSequenceAutoCancelint8u0 = 0,-,+/Enabled +
1 = 0,-,+/Disabled +
2 = -,0,+/Enabled +
3 = -,0,+/Disabled
10AFPointIlluminationint8u0 = On +
1 = Off +
2 = On without dimming +
3 = Brighter
11AFPointSelectionint8u0 = H=AF+Main/V=AF+Command +
1 = H=Comp+Main/V=Comp+Command +
2 = H=Command only/V=Assist+Main +
3 = H=FEL+Main/V=FEL+Command
12MirrorLockupint8u0 = Disable +
1 = Enable
13AFPointSpotMeteringint8u0 = 45/Center AF point +
1 = 11/Active AF point +
2 = 11/Center AF point +
3 = 9/Active AF point
14FillFlashAutoReductionint8u0 = Enable +
1 = Disable
15ShutterCurtainSyncint8u0 = 1st-curtain sync +
1 = 2nd-curtain sync
16SafetyShiftInAvOrTvint8u0 = Disable +
1 = Enable
17AFPointActivationAreaint8u0 = Single AF point +
1 = Expanded (TTL. of 7 AF points) +
2 = Automatic expanded (max. 13)
18SwitchToRegisteredAFPointint8u0 = Assist + AF +
1 = Assist +
2 = Only while pressing assist
19LensAFStopButtonint8u +
0 = AF stop +
1 = AF start +
2 = AE lock while metering +
3 = AF point: M -> Auto / Auto -> Ctr. +
4 = AF mode: ONE SHOT <-> AI SERVO +
5 = IS start
+
20AIServoTrackingSensitivityint8u0 = Standard +
1 = Slow +
2 = Moderately slow +
3 = Moderately fast +
4 = Fast
21AIServoContinuousShootingint8u0 = Shooting not possible without focus +
1 = Shooting possible without focus
+ +

CanonCustom Functions5D Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0FocusingScreenint8u0 = Ee-A +
1 = Ee-D +
2 = Ee-S
1SetFunctionWhenShootingint8u0 = Default (no function) +
1 = Change quality +
2 = Change Parameters +
3 = Menu display +
4 = Image replay
2LongExposureNoiseReductionint8u0 = Off +
1 = Auto +
2 = On
3FlashSyncSpeedAvint8u0 = Auto +
1 = 1/200 Fixed
4Shutter-AELockint8u0 = AF/AE lock +
1 = AE lock/AF +
2 = AF/AF lock, No AE lock +
3 = AE/AF, No AE lock
5AFAssistBeamint8u0 = Emits +
1 = Does not emit
6ExposureLevelIncrementsint8u0 = 1/3 Stop +
1 = 1/2 Stop
7FlashFiringint8u0 = Fires +
1 = Does not fire
8ISOExpansionint8u0 = Off +
1 = On
9AEBSequenceAutoCancelint8u0 = 0,-,+/Enabled +
1 = 0,-,+/Disabled +
2 = -,0,+/Enabled +
3 = -,0,+/Disabled
10SuperimposedDisplayint8u0 = On +
1 = Off
11MenuButtonDisplayPositionint8u0 = Previous (top if power off) +
1 = Previous +
2 = Top
12MirrorLockupint8u0 = Disable +
1 = Enable
13AFPointSelectionMethodint8u0 = Normal +
1 = Multi-controller direct +
2 = Quick Control Dial direct
14ETTLIIint8u0 = Evaluative +
1 = Average
15ShutterCurtainSyncint8u0 = 1st-curtain sync +
1 = 2nd-curtain sync
16SafetyShiftInAvOrTvint8u0 = Disable +
1 = Enable
17AFPointActivationAreaint8u0 = Standard +
1 = Expanded
18LCDDisplayReturnToShootint8u0 = With Shutter Button only +
1 = Also with * etc.
19LensAFStopButtonint8u +
0 = AF stop +
1 = AF start +
2 = AE lock while metering +
3 = AF point: M -> Auto / Auto -> Ctr. +
4 = ONE SHOT <-> AI SERVO +
5 = IS start
+
20AddOriginalDecisionDataint8u0 = Off +
1 = On
+ +

CanonCustom Functions10D Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
1SetButtonWhenShootingint8u0 = Normal (disabled) +
1 = Image quality +
2 = Change parameters +
3 = Menu display +
4 = Image playback
2ShutterReleaseNoCFCardint8u0 = Yes +
1 = No
3FlashSyncSpeedAvint8u0 = Auto +
1 = 1/200 Fixed
4Shutter-AELockint8u0 = AF/AE lock +
1 = AE lock/AF +
2 = AF/AF lock, No AE lock +
3 = AE/AF, No AE lock
5AFAssistint8u0 = Emits/Fires +
1 = Does not emit/Fires +
2 = Only ext. flash emits/Fires +
3 = Emits/Does not fire
6ExposureLevelIncrementsint8u0 = 1/2 Stop +
1 = 1/3 Stop
7AFPointRegistrationint8u + +
0 = Center +
1 = Bottom +
2 = Right +
3 = Extreme Right
  4 = Automatic +
5 = Extreme Left +
6 = Left +
7 = Top
+
8RawAndJpgRecordingint8u + +
0 = RAW+Small/Normal +
1 = RAW+Small/Fine +
2 = RAW+Medium/Normal
  3 = RAW+Medium/Fine +
4 = RAW+Large/Normal +
5 = RAW+Large/Fine
+
9AEBSequenceAutoCancelint8u0 = 0,-,+/Enabled +
1 = 0,-,+/Disabled +
2 = -,0,+/Enabled +
3 = -,0,+/Disabled
10SuperimposedDisplayint8u0 = On +
1 = Off
11MenuButtonDisplayPositionint8u0 = Previous (top if power off) +
1 = Previous +
2 = Top
12MirrorLockupint8u0 = Disable +
1 = Enable
13AssistButtonFunctionint8u0 = Normal +
1 = Select Home Position +
2 = Select HP (while pressing) +
3 = Av+/- (AF point by QCD) +
4 = FE lock
14FillFlashAutoReductionint8u0 = Enable +
1 = Disable
15ShutterCurtainSyncint8u0 = 1st-curtain sync +
1 = 2nd-curtain sync
16SafetyShiftInAvOrTvint8u0 = Disable +
1 = Enable
17LensAFStopButtonint8u +
0 = AF stop +
1 = AF start +
2 = AE lock while metering +
3 = AF point: M->Auto/Auto->ctr +
4 = One Shot <-> AI servo +
5 = IS start
+
+ +

CanonCustom Functions20D Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0SetFunctionWhenShootingint8u0 = Default (no function) +
1 = Change quality +
2 = Change Parameters +
3 = Menu display +
4 = Image replay
1LongExposureNoiseReductionint8u0 = Off +
1 = On
2FlashSyncSpeedAvint8u0 = Auto +
1 = 1/250 Fixed
3Shutter-AELockint8u0 = AF/AE lock +
1 = AE lock/AF +
2 = AF/AF lock, No AE lock +
3 = AE/AF, No AE lock
4AFAssistBeamint8u0 = Emits +
1 = Does not emit +
2 = Only ext. flash emits
5ExposureLevelIncrementsint8u0 = 1/3 Stop +
1 = 1/2 Stop
6FlashFiringint8u0 = Fires +
1 = Does not fire
7ISOExpansionint8u0 = Off +
1 = On
8AEBSequenceAutoCancelint8u0 = 0,-,+/Enabled +
1 = 0,-,+/Disabled +
2 = -,0,+/Enabled +
3 = -,0,+/Disabled
9SuperimposedDisplayint8u0 = On +
1 = Off
10MenuButtonDisplayPositionint8u0 = Previous (top if power off) +
1 = Previous +
2 = Top
11MirrorLockupint8u0 = Disable +
1 = Enable
12AFPointSelectionMethodint8u0 = Normal +
1 = Multi-controller direct +
2 = Quick Control Dial direct
13ETTLIIint8u0 = Evaluative +
1 = Average
14ShutterCurtainSyncint8u0 = 1st-curtain sync +
1 = 2nd-curtain sync
15SafetyShiftInAvOrTvint8u0 = Disable +
1 = Enable
16LensAFStopButtonint8u +
0 = AF stop +
1 = AF start +
2 = AE lock while metering +
3 = AF point: M -> Auto / Auto -> Ctr. +
4 = ONE SHOT <-> AI SERVO +
5 = IS start
+
17AddOriginalDecisionDataint8u0 = Off +
1 = On
+ +

CanonCustom Functions30D Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
1SetFunctionWhenShootingint8u0 = Default (no function) +
1 = Change quality +
2 = Change Picture Style +
3 = Menu display +
4 = Image replay
2LongExposureNoiseReductionint8u0 = Off +
1 = Auto +
2 = On
3FlashSyncSpeedAvint8u0 = Auto +
1 = 1/250 Fixed
4Shutter-AELockint8u0 = AF/AE lock +
1 = AE lock/AF +
2 = AF/AF lock, No AE lock +
3 = AE/AF, No AE lock
5AFAssistBeamint8u0 = Emits +
1 = Does not emit +
2 = Only ext. flash emits
6ExposureLevelIncrementsint8u0 = 1/3 Stop +
1 = 1/2 Stop
7FlashFiringint8u0 = Fires +
1 = Does not fire
8ISOExpansionint8u0 = Off +
1 = On
9AEBSequenceAutoCancelint8u0 = 0,-,+/Enabled +
1 = 0,-,+/Disabled +
2 = -,0,+/Enabled +
3 = -,0,+/Disabled
10SuperimposedDisplayint8u0 = On +
1 = Off
11MenuButtonDisplayPositionint8u0 = Previous (top if power off) +
1 = Previous +
2 = Top
12MirrorLockupint8u0 = Disable +
1 = Enable
13AFPointSelectionMethodint8u0 = Normal +
1 = Multi-controller direct +
2 = Quick Control Dial direct
14ETTLIIint8u0 = Evaluative +
1 = Average
15ShutterCurtainSyncint8u0 = 1st-curtain sync +
1 = 2nd-curtain sync
16SafetyShiftInAvOrTvint8u0 = Disable +
1 = Enable
17MagnifiedViewint8u0 = Image playback only +
1 = Image review and playback
18LensAFStopButtonint8u +
0 = AF stop +
1 = AF start +
2 = AE lock while metering +
3 = AF point: M -> Auto / Auto -> Ctr. +
4 = ONE SHOT <-> AI SERVO +
5 = IS start
+
19AddOriginalDecisionDataint8u0 = Off +
1 = On
+ +

CanonCustom Functions350D Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0SetButtonCrossKeysFuncint8u0 = Normal +
1 = Set: Quality +
2 = Set: Parameter +
3 = Set: Playback +
4 = Cross keys: AF point select
1LongExposureNoiseReductionint8u0 = Off +
1 = On
2FlashSyncSpeedAvint8u0 = Auto +
1 = 1/200 Fixed
3Shutter-AELockint8u0 = AF/AE lock +
1 = AE lock/AF +
2 = AF/AF lock, No AE lock +
3 = AE/AF, No AE lock
4AFAssistBeamint8u0 = Emits +
1 = Does not emit +
2 = Only ext. flash emits
5ExposureLevelIncrementsint8u0 = 1/3 Stop +
1 = 1/2 Stop
6MirrorLockupint8u0 = Disable +
1 = Enable
7ETTLIIint8u0 = Evaluative +
1 = Average
8ShutterCurtainSyncint8u0 = 1st-curtain sync +
1 = 2nd-curtain sync
+ +

CanonCustom Functions400D Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0SetButtonCrossKeysFuncint8u0 = Set: Picture Style +
1 = Set: Quality +
2 = Set: Flash Exposure Comp +
3 = Set: Playback +
4 = Cross keys: AF point select
1LongExposureNoiseReductionint8u0 = Off +
1 = Auto +
2 = On
2FlashSyncSpeedAvint8u0 = Auto +
1 = 1/200 Fixed
3Shutter-AELockint8u0 = AF/AE lock +
1 = AE lock/AF +
2 = AF/AF lock, No AE lock +
3 = AE/AF, No AE lock
4AFAssistBeamint8u0 = Emits +
1 = Does not emit +
2 = Only ext. flash emits
5ExposureLevelIncrementsint8u0 = 1/3 Stop +
1 = 1/2 Stop
6MirrorLockupint8u0 = Disable +
1 = Enable
7ETTLIIint8u0 = Evaluative +
1 = Average
8ShutterCurtainSyncint8u0 = 1st-curtain sync +
1 = 2nd-curtain sync
9MagnifiedViewint8u0 = Image playback only +
1 = Image review and playback
10LCDDisplayAtPowerOnint8u0 = Display +
1 = Retain power off status
+ +

CanonCustom FunctionsD30 Tags

+

Custom functions for the EOS D30 and D60.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
1LongExposureNoiseReductionint8u0 = Off +
1 = On
2Shutter-AELockint8u0 = AF/AE lock +
1 = AE lock/AF +
2 = AF/AF lock +
3 = AE+release/AE+AF
3MirrorLockupint8u0 = Disable +
1 = Enable
4ExposureLevelIncrementsint8u0 = 1/2 Stop +
1 = 1/3 Stop
5AFAssistint8u0 = Emits/Fires +
1 = Does not emit/Fires +
2 = Only ext. flash emits/Fires +
3 = Emits/Does not fire
6FlashSyncSpeedAvint8u0 = Auto +
1 = 1/200 Fixed
7AEBSequenceAutoCancelint8u0 = 0,-,+/Enabled +
1 = 0,-,+/Disabled +
2 = -,0,+/Enabled +
3 = -,0,+/Disabled
8ShutterCurtainSyncint8u0 = 1st-curtain sync +
1 = 2nd-curtain sync
9LensAFStopButtonint8u0 = AF Stop +
1 = Operate AF +
2 = Lock AE and start timer
10FillFlashAutoReductionint8u0 = Enable +
1 = Disable
11MenuButtonReturnint8u0 = Top +
1 = Previous (volatile) +
2 = Previous
12SetButtonWhenShootingint8u0 = Default (no function) +
1 = Image quality +
2 = Change ISO speed +
3 = Change parameters
13SensorCleaningint8u0 = Disable +
1 = Enable
14SuperimposedDisplayint8u0 = On +
1 = Off
15ShutterReleaseNoCFCardint8u0 = Yes +
1 = No
+ +

CanonCustom FuncsUnknown Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

CanonCustom PersonalFuncs Tags

+

Personal function settings for the EOS-1D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1PF0CustomFuncRegistrationint16u 
2PF1DisableShootingModesint16u 
3PF2DisableMeteringModesint16u 
4PF3ManualExposureMeteringint16u 
5PF4ExposureTimeLimitsint16u 
6PF5ApertureLimitsint16u 
7PF6PresetShootingModesint16u 
8PF7BracketContinuousShootint16u 
9PF8SetBracketShotsint16u 
10PF9ChangeBracketSequenceint16u 
11PF10RetainProgramShiftint16u 
14PF13DrivePriorityint16u 
15PF14DisableFocusSearchint16u 
16PF15DisableAFAssistBeamint16u 
17PF16AutoFocusPointShootint16u 
18PF17DisableAFPointSelint16u 
19PF18EnableAutoAFPointSelint16u 
20PF19ContinuousShootSpeedint16u 
21PF20LimitContinousShotsint16u 
22PF21EnableQuietOperationint16u 
24PF23SetTimerLengthsint16u 
25PF24LightLCDDuringBulbint16u 
26PF25DefaultClearSettingsint16u 
27PF26ShortenReleaseLagint16u 
28PF27ReverseDialRotationint16u 
29PF28NoQuickDialExpCompint16u 
30PF29QuickDialSwitchOffint16u 
31PF30EnlargementModeint16u 
32PF31OriginalDecisionDataint16u 
+ +

CanonCustom PersonalFuncValues Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1PF1Valueint16u 
2PF2Valueint16u 
3PF3Valueint16u 
4PF4ExposureTimeMinint16u 
5PF4ExposureTimeMaxint16u 
6PF5ApertureMinint16u 
7PF5ApertureMaxint16u 
8PF8BracketShotsint16u 
9PF19ShootingSpeedLowint16u 
10PF19ShootingSpeedHighint16u 
11PF20MaxContinousShotsint16u 
12PF23ShutterButtonTimeint16u 
13PF23FELockTimeint16u 
14PF23PostReleaseTimeint16u 
15PF25AEModeint16u 
16PF25MeteringModeint16u 
17PF25DriveModeint16u 
18PF25AFModeint16u 
19PF25AFPointSelint16u 
20PF25ImageSizeint16u 
21PF25WBModeint16u 
22PF25Parametersint16u 
23PF25ColorMatrixint16u 
24PF27Valueint16u 
+ +

CanonCustom Functions2 Tags

+

Beginning with the EOS 1D Mark III, Canon finally created a set of custom +function tags which are (reasonably) consistent across models. The EOS 1D +Mark III has 57 custom function tags divided into four main groups: 1. +Exposure (0x0101-0x010f), 2. Image (0x0201-0x0203), Flash Exposure +(0x0304-0x0306) and Display (0x0407-0x0409), 3. Auto Focus (0x0501-0x050e) +and Drive (0x060f-0x0611), and 4. Operation (0x0701-0x070a) and Others +(0x080b-0x0810). The table below lists tags used by the EOS 1D Mark III, as +well as newer tags and values added by later models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0101ExposureLevelIncrementsint32s(1DmkIII and 1DmkIV) +
0 = 1/3-stop set, 1/3-stop comp. +
1 = 1-stop set, 1/3-stop comp. +
2 = 1/2-stop set, 1/2-stop comp. +
(other models) +
0 = 1/3 Stop +
1 = 1/2 Stop
0x0102ISOSpeedIncrementsint32s0 = 1/3 Stop +
1 = 1 Stop
0x0103ISOSpeedRange +
ISOExpansion
int32s[3]
int32s
(1DmkIII and 1DmkIV) +
[Value 0] +
0 = Disable +
1 = Enable +
(other models) +
0 = Off +
1 = On
0x0104AEBAutoCancelint32s0 = On +
1 = Off
0x0105AEBSequenceint32s(value of 2 not used by 40D, 50D, 60D, 5DmkII and 7D) +
0 = 0,-,+ +
1 = -,0,+ +
2 = +,0,-
0x0106AEBShotCount +
AEBShotCount
int32s
int32s[2]
(EOS 90D) +
2 = 2 shots +
3 = 3 shots +
5 = 5 shots +
7 = 7 shots +
(other models storing a single value) +
0 = 3 shots +
1 = 2 shots +
2 = 5 shots +
3 = 7 shots +
(models storing two values) +
'2 1' = 2 shots +
'3 0' = 3 shots +
'5 2' = 5 shots +
'7 3' = 7 shots
0x0107SpotMeterLinkToAFPointint32s0 = Disable (use center AF point) +
1 = Enable (use active AF point)
0x0108SafetyShiftint32s(value of 2 not used by some models) +
0 = Disable +
1 = Enable (Tv/Av) +
2 = Enable (ISO speed)
0x0109UsableShootingModes +
UsableShootingModes
int32s
int32s[2]
[Value 0] +
0 = Disable +
1 = Enable
0x010aUsableMeteringModes +
UsableMeteringModes
int32s
int32s[2]
[Value 0] +
0 = Disable +
1 = Enable
0x010bExposureModeInManualint32s0 = Specified metering mode +
1 = Evaluative metering +
2 = Partial metering +
3 = Spot metering +
4 = Center-weighted average
0x010cShutterSpeedRange +
ShutterSpeedRange
int32s[3]
int32s[4]
[Value 0] +
0 = Disable +
1 = Enable
0x010dApertureRange +
ApertureRange
int32s[3]
int32s[4]
[Value 0] +
0 = Disable +
1 = Enable
0x010eApplyShootingMeteringModeint32s[8][Value 0] +
0 = Disable +
1 = Enable
0x010fFlashSyncSpeedAvint32s(40D and 1Ds Mark III) +
0 = Auto +
1 = 1/250 Fixed +
(50D, 60D and 7D) +
0 = Auto +
1 = 1/250-1/60 Auto +
2 = 1/250 Fixed +
(450D and 1000D) +
0 = Auto +
1 = 1/200 Fixed +
(1D Mark III and 1Ds Mark III) +
0 = Auto +
1 = 1/300 Fixed +
(1D Mark IV) +
0 = Auto +
1 = 1/300-1/60 Auto +
2 = 1/300 Fixed +
(5D Mark II, 5D Mark III, 500D, 550D, 600D and 1100D) +
0 = Auto +
1 = 1/200-1/60 Auto +
2 = 1/200 Fixed
0x0110AEMicroadjustmentint32s[3][Value 0] +
0 = Disable +
1 = Enable
0x0111FEMicroadjustmentint32s[3][Value 0] +
0 = Disable +
1 = Enable
0x0112SameExposureForNewApertureint32s0 = Disable +
1 = ISO Speed +
2 = Shutter Speed +
(EOS R) +
0 = Disable +
1 = ISO Speed +
2 = ISO Speed/Shutter Speed +
3 = Shutter Speed
0x0113ExposureCompAutoCancelint32s0 = Enable +
1 = Disable
0x0114AELockMeterModeAfterFocusint32sBit 0 = Evaluative +
Bit 1 = Partial +
Bit 2 = Spot +
Bit 3 = Center-weighted
0x0201LongExposureNoiseReductionint32s0 = Off +
1 = Auto +
2 = On
0x0202HighISONoiseReductionint32s(50D, 60D, 500D, 550D, 600D, 1100D, 5DmkII and 7D) +
0 = Standard +
1 = Low +
2 = Strong +
3 = Off +
(other models) +
0 = Off +
1 = On
0x0203HighlightTonePriorityint32s0 = Disable +
1 = Enable
0x0204AutoLightingOptimizerint32s(50D, 500D, 5DmkII and 1DmkIV) +
0 = Standard +
1 = Low +
2 = Strong +
3 = Disable +
(other models) +
0 = Enable +
1 = Disable
0x0304ETTLIIint32s0 = Evaluative +
1 = Average
0x0305ShutterCurtainSyncint32s0 = 1st-curtain sync +
1 = 2nd-curtain sync
0x0306FlashFiringint32s0 = Fires +
1 = Does not fire
0x0407ViewInfoDuringExposureint32s0 = Disable +
1 = Enable
0x0408LCDIlluminationDuringBulbint32s0 = Off +
1 = On
0x0409InfoButtonWhenShootingint32s(1D Mark III) +
0 = Displays camera settings +
1 = Displays shooting functions +
(1D Mark IV) +
0 = Displays shooting functions +
1 = Displays camera settings
0x040aViewfinderWarningsint32s +
Bit 0 = Monochrome +
Bit 1 = WB corrected +
Bit 2 = One-touch image quality +
Bit 3 = ISO expansion +
Bit 4 = Spot metering +
Bit 6 = Noise reduction +
Bit 7 = HDR
+
0x040bLVShootingAreaDisplayint32s0 = Masked +
1 = Outlined
0x040cLVShootingAreaDisplayint32s0 = Masked +
1 = Outlined
0x0501USMLensElectronicMFint32s0 = Enable after one-shot AF +
1 = Disable after one-shot AF +
2 = Disable in AF mode
0x0502AIServoTrackingSensitivityint32s-2 = Slow +
-1 = Medium Slow +
0 = Standard +
1 = Medium Fast +
2 = Fast
0x0503AIServoImagePriorityint32s0 = 1: AF, 2: Tracking +
1 = 1: AF, 2: Drive speed +
2 = 1: Release, 2: Drive speed +
3 = 1: Release, 2: Tracking
0x0504AIServoTrackingMethodint32s0 = Main focus point priority +
1 = Continuous AF track priority
0x0505LensDriveNoAFint32s0 = Focus search on +
1 = Focus search off
0x0506LensAFStopButtonint32s(value of 6 not used by 40D, 50D and 5DmkII) +
0 = AF stop +
1 = AF start +
2 = AE lock +
3 = AF point: M->Auto/Auto->ctr +
4 = One Shot <-> AI servo +
5 = IS start +
6 = Switch to registered AF point +
7 = Spot AF
+
0x0507AFMicroadjustmentint32s[5][Value 0] +
0 = Disable +
1 = Adjust all by same amount +
2 = Adjust by lens
0x0508AFPointAreaExpansionint32s(5D Mark II) +
0 = Disable +
1 = Enable +
(1Ds Mark III) +
0 = Disable +
1 = Enable (left/right Assist AF points) +
2 = Enable (surrounding Assist AF points) +
(other models) +
0 = Disable +
1 = Left/right AF points +
2 = Surrounding AF points +
3 = All 45 points area
0x0509SelectableAFPointint32s(1D Mark IV) +
0 = 45 points +
1 = 19 points +
2 = 11 points +
3 = Inner 9 points +
4 = Outer 9 points +
(other models)
+
0 = 19 points +
1 = Inner 9 points +
2 = Outer 9 points +
3 = 19 Points, Multi-controller selectable +
4 = Inner 9 Points, Multi-controller selectable +
5 = Outer 9 Points, Multi-controller selectable
+
0x050aSwitchToRegisteredAFPointint32s(1D Mark IV) +
0 = Disable +
1 = Switch with multi-controller +
2 = Only while AEL is pressed +
(other models) +
0 = Disable +
1 = Enable
0x050bAFPointAutoSelectionint32s0 = Control-direct:disable/Main:enable +
1 = Control-direct:disable/Main:disable +
2 = Control-direct:enable/Main:enable
0x050cAFPointDisplayDuringFocusint32s(1D models) +
0 = On +
1 = Off +
2 = On (when focus achieved) +
(other models) +
0 = Selected (constant) +
1 = All (constant) +
2 = Selected (pre-AF, focused) +
3 = Selected (focused) +
4 = Disable display
0x050dAFPointBrightnessint32s0 = Normal +
1 = Brighter
0x050eAFAssistBeamint32s(1D Mark IV and 6D) +
0 = Emits +
1 = Does not emit +
2 = IR AF assist beam only +
(other models; values 2-3 not used by 1DmkIII or 5DmkII, value 3 new for 7D) +
0 = Emits +
1 = Does not emit +
2 = Only ext. flash emits +
3 = IR AF assist beam only
0x050fAFPointSelectionMethodint32s(40D, 50D and 5DmkII) +
0 = Normal +
1 = Multi-controller direct +
2 = Quick Control Dial direct +
(60D) +
0 = AF point button: Activate AF Sel; Rear dial: Select AF points +
1 = AF point button: Auto selection; Rear dial: Manual selection
0x0510VFDisplayIllumination +
SuperimposedDisplay
int32s
int32s
(7D) +
0 = Auto +
1 = Enable +
2 = Disable +
(other models) +
0 = On +
1 = Off
0x0511AFDuringLiveViewint32s(40D) +
0 = Disable +
1 = Enable +
(450D and 1000D) +
0 = Disable +
1 = Quick mode +
2 = Live mode
0x0512SelectAFAreaSelectModeint32s[Value 0] +
0 = Disable +
1 = Enable +
2 = Register +
3 = Select AF-modes
0x0513ManualAFPointSelectPatternint32s0 = Stops at AF area edges +
1 = Continuous
0x0514DisplayAllAFPointsint32s0 = Enable +
1 = Disable
0x0515FocusDisplayAIServoAndMFint32s0 = Enable +
1 = Disable
0x0516OrientationLinkedAFPointint32s0 = Same for vertical and horizontal +
1 = Select different AF points
0x0517MultiControllerWhileMeteringint32s0 = Off +
1 = AF point selection
0x0518AccelerationTrackingint32s 
0x0519AIServoFirstImagePriorityint32s-1 = Release priority +
0 = Equal priority +
1 = Focus priority
0x051aAIServoSecondImagePriorityint32s-1 = Shooting speed priority +
0 = Equal priority +
1 = Focus priority
0x051bAFAreaSelectMethodint32s0 = AF area selection button +
1 = Main dial
0x051cAutoAFPointColorTrackingint32s0 = On-Shot AF only +
1 = Disable
0x051dVFDisplayIlluminationint32s[Value 0] +
0 = Auto +
1 = Enable +
2 = Disable +
[Value 1] +
0 = Non-illuminated +
1 = Illuminated
0x051eInitialAFPointAIServoAFint32s0 = Auto +
1 = Initial AF point selected +
2 = Manual AF point
0x060fMirrorLockupint32s(value of 2 not used by some models) +
0 = Disable +
1 = Enable +
2 = Enable: Down with Set
0x0610ContinuousShootingSpeed +
ContinuousShootingSpeed +
ContinuousShootingSpeed
int32s[6]
int32s[5]
int32s[3]
[Value 0] +
0 = Disable +
1 = Enable +
[Value 0] +
0 = Disable +
1 = Enable
0x0611ContinuousShotLimitint32s[2][Value 0] +
0 = Disable +
1 = Enable
0x0612RestrictDriveModes +
RestrictDriveModes
int32s
int32s[2]
[Value 0] +
0 = Disable +
1 = Enable
0x0701Shutter-AELock +
AFAndMeteringButtons +
ShutterButtonAFOnButton
int32s
int32s
int32s
(500D, 550D, 600D, 1000D and 1100D) +
0 = AF/AE lock +
1 = AE lock/AF +
2 = AF/AF lock, No AE lock +
3 = AE/AF, No AE lock +
(250D) +
'0 0' = AF/AE lock +
'1 0' = AE lock/AF +
'2 0' = AF/AF lock, No AE lock +
'3 0' = AE/AF, No AE lock +
(60D) +
0 = Metering start +
1 = Metering + AF start +
2 = AE lock +
3 = AF stop +
4 = No function +
(other models) +
0 = Metering + AF start +
1 = Metering + AF start/AF stop +
2 = Metering start/Meter + AF start +
3 = AE lock/Metering + AF start +
4 = Metering + AF start/disable
0x0702AFOnAELockButtonSwitchint32s0 = Disable +
1 = Enable
0x0703QuickControlDialInMeterint32s0 = Exposure comp/Aperture +
1 = AF point selection +
2 = ISO speed +
3 = AF point selection swapped with Exposure comp +
4 = ISO speed swapped with Exposure comp
0x0704SetButtonWhenShootingint32s(40D, 50D and 5DmkII; value of 5 is new for 50D, and 6 is new for 5DmkII) +
0 = Normal (disabled) +
1 = Image quality +
2 = Picture style +
3 = Menu display +
4 = Image playback +
5 = Quick control screen +
6 = Record movie (Live View)
+(60D) +
0 = Normal (disabled) +
1 = Image quality +
2 = Picture style +
3 = White balance +
4 = Flash exposure compensation +
5 = Viewfinder leveling gauge
+(450D, 550D and 600D; value of 5 is new for 550D) +
0 = Normal (disabled) +
1 = Image quality +
2 = Flash exposure compensation +
3 = LCD monitor On/Off +
4 = Menu display +
5 = ISO speed
+(1100D) +
0 = Normal (disabled) +
1 = Image quality +
2 = Flash exposure compensation +
3 = LCD monitor On/Off +
4 = Menu display +
5 = Depth-of-field preview
+(1000D) +
0 = LCD monitor On/Off +
1 = Image quality +
2 = Flash exposure compensation +
3 = Menu display +
4 = Disabled +
(500D)
+
0 = Quick control screen +
1 = Image quality +
2 = Flash exposure compensation +
3 = LCD monitor On/Off +
4 = Menu display +
5 = Disabled
+(250D) +
(1DmkIII and 1DmkIV)
+
0 = Normal (disabled) +
1 = White balance +
2 = Image size +
3 = ISO speed +
4 = Picture style +
5 = Record func. + media/folder +
6 = Menu display +
7 = Image playback
+
0x0705ManualTvint32s0 = Tv=Main/Av=Control +
1 = Tv=Control/Av=Main
0x0706DialDirectionTvAvint32s0 = Normal +
1 = Reversed
0x0707AvSettingWithoutLensint32s0 = Disable +
1 = Enable
0x0708WBMediaImageSizeSettingint32s0 = Rear LCD panel +
1 = LCD monitor +
2 = Off (disable button)
0x0709LockMicrophoneButtonint32s[Value 0] +
0 = Protect (hold:record memo) +
1 = Record memo (protect:disable) +
2 = Play memo (hold:record memo) +
3 = Rating (protect/memo:disable)
0x070aButtonFunctionControlOffint32s0 = Normal (enable) +
1 = Disable main, Control, Multi-control
0x070bAssignFuncButtonint32s0 = LCD brightness +
1 = Image quality +
2 = Exposure comp./AEB setting +
3 = Image jump with main dial +
4 = Live view function settings
0x070cCustomControlsint32s 
0x070dStartMovieShootingint32s0 = Default (from LV) +
1 = Quick start (FEL button)
0x070eFlashButtonFunctionint32s0 = Raise built-in flash +
1 = ISO speed
0x070fMultiFunctionLockint32s[Value 0] +
0 = Off +
1 = On +
2 = On (quick control dial) +
3 = On (main dial and quick control dial) +
[Value 1] +
Bit 0 = Main dial +
Bit 1 = Quick control dial +
Bit 2 = Multi-controller
0x0710TrashButtonFunctionint32s0 = Normal (set center AF point) +
1 = Depth-of-field preview
0x0711ShutterReleaseWithoutLensint32s0 = Disable +
1 = Enable
0x0712ControlRingRotationint32s0 = Normal +
1 = Reversed
0x0713FocusRingRotationint32s0 = Normal +
1 = Reversed
0x0714RFLensMFFocusRingSensitivityint32s0 = Varies With Rotation Speed +
1 = Linked To Rotation Angle
0x0715CustomizeDialsint32s 
0x080bFocusingScreenint32s(40D, 50D and 60D) +
0 = Ef-A +
1 = Ef-D +
2 = Ef-S +
(5D Mark II) +
0 = Eg-A +
1 = Eg-D +
2 = Eg-S +
(6D) +
0 = Eg-A II +
1 = Eg-D +
2 = Eg-S +
(7D Mark II) +
0 = Eh-A +
1 = Eh-S +
(1DX) +
0 = Ec-CV +
1 = Ec-A,B,D,H,I,L +
(1DmkIII, 1DSmkIII and 1DmkIV) +
0 = Ec-CIV +
1 = Ec-A,B,C,CII,CIII,D,H,I,L +
2 = Ec-S +
3 = Ec-N,R
0x080cTimerLength +
TimerLength
int32s[3]
int32s[4]
[Value 0] +
0 = Disable +
1 = Enable
0x080dShortReleaseTimeLagint32s0 = Disable +
1 = Enable
0x080eAddAspectRatioInfoint32s + +
0 = Off +
1 = 6:6 +
2 = 3:4 +
3 = 4:5
  4 = 6:7 +
5 = 10:12 +
6 = 5:7
+
0x080fAddOriginalDecisionDataint32s0 = Off +
1 = On
0x0810LiveViewExposureSimulationint32s0 = Disable (LCD auto adjust) +
1 = Enable (simulates exposure)
0x0811LCDDisplayAtPowerOnint32s0 = Display +
1 = Retain power off status
0x0812MemoAudioQualityint32s0 = High (48 kHz) +
1 = Low (8 kHz)
0x0813DefaultEraseOptionint32s0 = Cancel selected +
1 = Erase selected +
2 = Erase RAW selected +
3 = Erase non-RAW selected
0x0814RetractLensOnPowerOffint32s0 = Enable +
1 = Disable
0x0815AddIPTCInformationint32s0 = Disable +
1 = Enable
0x0816AudioCompressionint32s0 = Enable +
1 = Disable
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Dec 20, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/CanonRaw.html b/ExifTool/html/TagNames/CanonRaw.html new file mode 100644 index 0000000..021252f --- /dev/null +++ b/ExifTool/html/TagNames/CanonRaw.html @@ -0,0 +1,564 @@ + + + + +CanonRaw Tags + + + +

CanonRaw Tags

+

+These tags apply to CRW-format Canon RAW files and information in the APP0 +"CIFF" segment of JPEG images. When writing CanonRaw/CIFF information, the +length of the information is preserved (and the new information is truncated +or padded as required) unless Writable is resize. Currently, only +JpgFromRaw and ThumbnailImage are allowed to change size. See +canon_raw.html for a description of the Canon CRW +format.

+ +

CRW images also support the addition of a CanonVRD trailer, which in turn +supports XMP. This trailer is created automatically if necessary when +ExifTool is used to write XMP to a CRW image. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000NullRecordundef 
0x0001FreeBytesundef 
0x0032CanonColorInfo1no 
0x0805CanonFileDescription +
UserComment
string[32]
string[256]
 
0x080aCanonRawMakeModel---> CanonRaw MakeModel Tags
0x080bCanonFirmwareVersionstring[32] 
0x080cComponentVersionstring 
0x080dROMOperationModestring[8] 
0x0810OwnerNamestring[32] 
0x0815CanonImageTypestring[32] 
0x0816OriginalFileNamestring[32] 
0x0817ThumbnailFileNamestring[32] 
0x100aTargetImageTypeint16u0 = Real-world Subject +
1 = Written Document
0x1010ShutterReleaseMethodint16u0 = Single Shot +
2 = Continuous Shooting
0x1011ShutterReleaseTimingint16u0 = Priority on shutter +
1 = Priority on focus
0x1016ReleaseSettingint16u 
0x101cBaseISOint16u 
0x1028CanonFlashInfo?int16u[4] 
0x1029CanonFocalLength---> Canon FocalLength Tags
0x102aCanonShotInfo---> Canon ShotInfo Tags
0x102cCanonColorInfo2no 
0x102dCanonCameraSettings---> Canon CameraSettings Tags
0x1030WhiteSample---> CanonRaw WhiteSample Tags
0x1031SensorInfo---> Canon SensorInfo Tags
0x1033CustomFunctions10D +
CustomFunctionsD30 +
CustomFunctionsD60 +
CustomFunctionsUnknown
-
-
-
-
--> CanonCustom Functions10D Tags +
--> CanonCustom FunctionsD30 Tags +
--> CanonCustom FunctionsD30 Tags +
--> CanonCustom FuncsUnknown Tags
0x1038CanonAFInfo---> Canon AFInfo Tags
0x1093CanonFileInfo---> Canon FileInfo Tags
0x10a9ColorBalance---> Canon ColorBalance Tags
0x10aeColorTemperatureint16u 
0x10b4ColorSpaceint16u1 = sRGB +
2 = Adobe RGB +
65535 = Uncalibrated
0x10b5RawJpgInfo---> CanonRaw RawJpgInfo Tags
0x1803ImageFormat---> CanonRaw ImageFormat Tags
0x1804RecordIDint32u 
0x1806SelfTimerTimeint32u 
0x1807TargetDistanceSettingfloat 
0x180bSerialNumber +
UnknownNumber?
int32u
yes
 
0x180eTimeStamp---> CanonRaw TimeStamp Tags
0x1810ImageInfo---> CanonRaw ImageInfo Tags
0x1813FlashInfo---> CanonRaw FlashInfo Tags
0x1814MeasuredEVfloat(this is the Canon name for what could better be called MeasuredLV, and +should be close to the calculated LightValue for a proper exposure with most +models)
0x1817FileNumberint32u 
0x1818ExposureInfo---> CanonRaw ExposureInfo Tags
0x1834CanonModelIDint32u--> Canon CanonModelID Values +
(this is the complete list of model ID numbers, but note that many of these +models do not produce CRW images)
0x1835DecoderTable---> CanonRaw DecoderTable Tags
0x183bSerialNumberFormatint32u0x90000000 = Format 1 +
0xa0000000 = Format 2
0x2005RawDatano 
0x2007JpgFromRawresize^ 
0x2008ThumbnailImageresize^ 
0x2804ImageDescription---> CanonRaw Tags
0x2807CameraObject---> CanonRaw Tags
0x3002ShootingRecord---> CanonRaw Tags
0x3003MeasuredInfo---> CanonRaw Tags
0x3004CameraSpecification---> CanonRaw Tags
0x300aImageProps---> CanonRaw Tags
0x300bExifInformation---> CanonRaw Tags
+ +

CanonRaw MakeModel Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makestring[6] 
6Modelstring 
+ +

CanonRaw WhiteSample Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1WhiteSampleWidthno 
2WhiteSampleHeightno 
3WhiteSampleLeftBorderno 
4WhiteSampleTopBorderno 
5WhiteSampleBitsno 
+ +

CanonRaw RawJpgInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1RawJpgQualityint16u1 = Economy +
2 = Normal +
3 = Fine +
5 = Superfine
2RawJpgSizeint16u0 = Large +
1 = Medium +
2 = Small
3RawJpgWidthint16u 
4RawJpgHeightint16u 
+ +

CanonRaw ImageFormat Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0FileFormatint32u0x10000 = JPEG (lossy) +
0x10002 = JPEG (non-quantization) +
0x10003 = JPEG (lossy/non-quantization toggled) +
0x20001 = CRW
1TargetCompressionRatiofloat 
+ +

CanonRaw TimeStamp Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0DateTimeOriginalint32u 
1TimeZoneCodeint32s 
2TimeZoneInfoint32u(set to 0x80000000 if TimeZoneCode is valid)
+ +

CanonRaw ImageInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0ImageWidthno 
1ImageHeightno 
2PixelAspectRationo 
3Rotationint32s 
4ComponentBitDepthno 
5ColorBitDepthno 
6ColorBWno 
+ +

CanonRaw FlashInfo Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0FlashGuideNumberfloat 
1FlashThresholdfloat 
+ +

CanonRaw ExposureInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0ExposureCompensationfloat 
1ShutterSpeedValuefloat 
2ApertureValuefloat 
+ +

CanonRaw DecoderTable Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0DecoderTableNumberno 
2CompressedDataOffsetno 
3CompressedDataLengthno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Nov 28, 2019 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/CanonVRD.html b/ExifTool/html/TagNames/CanonVRD.html new file mode 100644 index 0000000..da26a3d --- /dev/null +++ b/ExifTool/html/TagNames/CanonVRD.html @@ -0,0 +1,2042 @@ + + + + +CanonVRD Tags + + + +

CanonVRD Tags

+

Canon Digital Photo Professional writes VRD (Recipe Data) information as a +trailer record to JPEG, TIFF, CRW and CR2 images, or as stand-alone VRD or +DR4 files. The tags listed below represent information found in these +records. The complete VRD/DR4 data record may be accessed as a block using +the Extra 'CanonVRD' or 'CanonDR4' tag, but this tag is not extracted or +copied unless specified explicitly.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0xffff00f4EditData---> CanonVRD Edit Tags
0xffff00f5IHLData---> CanonVRD IHL Tags
0xffff00f6XMPundef!--> XMP Tags
0xffff00f7Edit4Data---> CanonVRD Edit4 Tags
+ +

CanonVRD Edit Tags

+

Canon VRD edit information.

+
+
+ + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0VRD1---> CanonVRD Ver1 Tags
1VRDStampTool---> CanonVRD StampTool Tags
2VRD2---> CanonVRD Ver2 Tags
+ +

CanonVRD Ver1 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
2VRDVersionno 
6WBAdjRGGBLevelsint16u[4] 
24WhiteBalanceAdjint16u + +
0 = Auto +
1 = Daylight +
2 = Cloudy +
3 = Tungsten +
4 = Fluorescent
  5 = Flash +
8 = Shade +
9 = Kelvin +
30 = Manual (Click) +
31 = Shot Settings
+
26WBAdjColorTempint16u 
36WBFineTuneActiveint16u + +
0 = No  1 = Yes
+
40WBFineTuneSaturationint16u 
44WBFineTuneToneint16u 
46RawColorAdjint16u0 = Shot Settings +
1 = Faithful +
2 = Custom
48RawCustomSaturationint32s 
52RawCustomToneint32s 
56RawBrightnessAdjint32s 
60ToneCurvePropertyint16u + +
0 = Shot Settings +
1 = Linear +
2 = Custom 1 +
3 = Custom 2
  4 = Custom 3 +
5 = Custom 4 +
6 = Custom 5
+
122DynamicRangeMinint16u 
124DynamicRangeMaxint16u 
272ToneCurveActiveint16u + +
0 = No  1 = Yes
+
275ToneCurveModeint8u0 = RGB +
1 = Luminance
276BrightnessAdjint8s 
277ContrastAdjint8s 
278SaturationAdjint16s 
286ColorToneAdjint32s(in degrees, so -1 is the same as 359)
294LuminanceCurvePointsint16u[21] 
336LuminanceCurveLimitsint16u[4](4 numbers: input and output highlight and shadow points)
345ToneCurveInterpolationint8u0 = Curve +
1 = Straight
352RedCurvePointsint16u[21] 
394RedCurveLimitsint16u[4] 
410GreenCurvePointsint16u[21] 
452GreenCurveLimitsint16u[4] 
468BlueCurvePointsint16u[21] 
510BlueCurveLimitsint16u[4] 
526RGBCurvePointsint16u[21] 
568RGBCurveLimitsint16u[4] 
580CropActiveint16u + +
0 = No  1 = Yes
+
582CropLeftint16u(crop coordinates in original unrotated image)
584CropTopint16u 
586CropWidthint16u 
588CropHeightint16u 
602SharpnessAdjint16u 
608CropAspectRatioint16u +
0 = Free +
1 = 3:2 +
2 = 2:3 +
3 = 4:3 +
4 = 3:4 +
5 = A-size Landscape +
6 = A-size Portrait +
7 = Letter-size Landscape +
8 = Letter-size Portrait +
9 = 4:5 +
10 = 5:4 +
11 = 1:1 +
12 = Circle +
65535 = Custom
+
610ConstrainedCropWidthfloat 
614ConstrainedCropHeightfloat 
618CheckMarkint16u0 = Clear +
1 = 1 +
2 = 2 +
3 = 3
622Rotationint16u0 = 0 +
1 = 90 +
2 = 180 +
3 = 270
624WorkColorSpaceint16u0 = sRGB +
1 = Adobe RGB +
2 = Wide Gamut RGB +
3 = Apple RGB +
4 = ColorMatch RGB
+ +

CanonVRD StampTool Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0StampToolCountno 
+ +

CanonVRD Ver2 Tags

+

Tags added in DPP version 2.0 and later.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
2PictureStyleint16s + +
0 = Standard +
1 = Portrait +
2 = Landscape +
3 = Neutral
  4 = Faithful +
5 = Monochrome +
6 = Unknown? +
7 = Custom
+
3IsCustomPictureStyleint16s + +
0 = No  1 = Yes
+
13StandardRawColorToneint16s 
14StandardRawSaturationint16s 
15StandardRawContrastint16s 
16StandardRawLinearint16s + +
0 = No  1 = Yes
+
17StandardRawSharpnessint16s 
18StandardRawHighlightPointint16s 
19StandardRawShadowPointint16s 
20StandardOutputHighlightPointint16s 
21StandardOutputShadowPointint16s 
22PortraitRawColorToneint16s 
23PortraitRawSaturationint16s 
24PortraitRawContrastint16s 
25PortraitRawLinearint16s + +
0 = No  1 = Yes
+
26PortraitRawSharpnessint16s 
27PortraitRawHighlightPointint16s 
28PortraitRawShadowPointint16s 
29PortraitOutputHighlightPointint16s 
30PortraitOutputShadowPointint16s 
31LandscapeRawColorToneint16s 
32LandscapeRawSaturationint16s 
33LandscapeRawContrastint16s 
34LandscapeRawLinearint16s + +
0 = No  1 = Yes
+
35LandscapeRawSharpnessint16s 
36LandscapeRawHighlightPointint16s 
37LandscapeRawShadowPointint16s 
38LandscapeOutputHighlightPointint16s 
39LandscapeOutputShadowPointint16s 
40NeutralRawColorToneint16s 
41NeutralRawSaturationint16s 
42NeutralRawContrastint16s 
43NeutralRawLinearint16s + +
0 = No  1 = Yes
+
44NeutralRawSharpnessint16s 
45NeutralRawHighlightPointint16s 
46NeutralRawShadowPointint16s 
47NeutralOutputHighlightPointint16s 
48NeutralOutputShadowPointint16s 
49FaithfulRawColorToneint16s 
50FaithfulRawSaturationint16s 
51FaithfulRawContrastint16s 
52FaithfulRawLinearint16s + +
0 = No  1 = Yes
+
53FaithfulRawSharpnessint16s 
54FaithfulRawHighlightPointint16s 
55FaithfulRawShadowPointint16s 
56FaithfulOutputHighlightPointint16s 
57FaithfulOutputShadowPointint16s 
58MonochromeFilterEffectint16s-2 = None +
-1 = Yellow +
0 = Orange +
1 = Red +
2 = Green
59MonochromeToningEffectint16s-2 = None +
-1 = Sepia +
0 = Blue +
1 = Purple +
2 = Green
60MonochromeContrastint16s 
61MonochromeLinearint16s + +
0 = No  1 = Yes
+
62MonochromeSharpnessint16s 
63MonochromeRawHighlightPointint16s 
64MonochromeRawShadowPointint16s 
65MonochromeOutputHighlightPointint16s 
66MonochromeOutputShadowPointint16s 
69UnknownContrast?int16s 
70UnknownLinear?int16s + +
0 = No  1 = Yes
+
71UnknownSharpness?int16s 
72UnknownRawHighlightPoint?int16s 
73UnknownRawShadowPoint?int16s 
74UnknownOutputHighlightPoint?int16s 
75UnknownOutputShadowPoint?int16s 
76CustomColorToneint16s 
77CustomSaturationint16s 
78CustomContrastint16s 
79CustomLinearint16s + +
0 = No  1 = Yes
+
80CustomSharpnessint16s 
81CustomRawHighlightPointint16s 
82CustomRawShadowPointint16s 
83CustomOutputHighlightPointint16s 
84CustomOutputShadowPointint16s 
88CustomPictureStyleDatano(variable-length data structure)
94ChrominanceNoiseReductionint16s(VRDVersion prior to 3.3.0) +
0 = Off +
58 = Low +
100 = High +
(VRDVersion 3.3.0 or later)
+ + + +
0x0 = 0 +
0x10 = 1 +
0x21 = 2 +
0x32 = 3 +
0x42 = 4 +
0x53 = 5
  0x64 = 6 +
0x74 = 7 +
0x85 = 8 +
0x96 = 9 +
0xa6 = 10 +
0xa7 = 11
  0xa8 = 12 +
0xa9 = 13 +
0xaa = 14 +
0xab = 15 +
0xac = 16 +
0xad = 17
  0xae = 18 +
0xaf = 19 +
0xb0 = 20
+
95LuminanceNoiseReductionint16s(VRDVersion prior to 3.3.0) +
0 = Off +
65 = Low +
100 = High +
(VRDVersion 3.3.0 or later)
+ + + +
0x0 = 0 +
0x41 = 1 +
0x64 = 2 +
0x6e = 3 +
0x78 = 4 +
0x82 = 5
  0x8c = 6 +
0x96 = 7 +
0xa0 = 8 +
0xaa = 9 +
0xb4 = 10 +
0xb5 = 11
  0xb6 = 12 +
0xb7 = 13 +
0xb8 = 14 +
0xb9 = 15 +
0xba = 16 +
0xbb = 17
  0xbc = 18 +
0xbd = 19 +
0xbe = 20
+
96ChrominanceNR_TIFF_JPEGint16s(VRDVersion prior to 3.3.0) +
0 = Off +
33 = Low +
100 = High +
(VRDVersion 3.3.0 or later)
+ + + +
0x0 = 0 +
0x10 = 1 +
0x21 = 2 +
0x32 = 3 +
0x42 = 4 +
0x53 = 5
  0x64 = 6 +
0x74 = 7 +
0x85 = 8 +
0x96 = 9 +
0xa6 = 10 +
0xa7 = 11
  0xa8 = 12 +
0xa9 = 13 +
0xaa = 14 +
0xab = 15 +
0xac = 16 +
0xad = 17
  0xae = 18 +
0xaf = 19 +
0xb0 = 20
+
98ChromaticAberrationOnint16s + +
0 = No  1 = Yes
+
99DistortionCorrectionOnint16s + +
0 = No  1 = Yes
+
100PeripheralIlluminationOnint16s + +
0 = No  1 = Yes
+
101ColorBlurint16s + +
0 = No  1 = Yes
+
102ChromaticAberrationint16s 
103DistortionCorrectionint16s 
104PeripheralIlluminationint16s 
105AberrationCorrectionDistanceint16s(100% = infinity)
106ChromaticAberrationRedint16s 
107ChromaticAberrationBlueint16s 
109LuminanceNR_TIFF_JPEGint16s(val = raw / 10)
110AutoLightingOptimizerOnint16s + +
0 = No  1 = Yes
+
111AutoLightingOptimizerint16s100 = Low +
200 = Standard +
300 = Strong +
32767 = n/a
117StandardRawHighlightint16s 
118PortraitRawHighlightint16s 
119LandscapeRawHighlightint16s 
120NeutralRawHighlightint16s 
121FaithfulRawHighlightint16s 
122MonochromeRawHighlightint16s 
123UnknownRawHighlight?int16s 
124CustomRawHighlightint16s 
126StandardRawShadowint16s 
127PortraitRawShadowint16s 
128LandscapeRawShadowint16s 
129NeutralRawShadowint16s 
130FaithfulRawShadowint16s 
131MonochromeRawShadowint16s 
132UnknownRawShadow?int16s 
133CustomRawShadowint16s 
139AngleAdjint32s 
142CheckMark2int16u + +
0 = Clear +
1 = 1 +
2 = 2
  3 = 3 +
4 = 4 +
5 = 5
+
144UnsharpMaskint16s0 = Off +
1 = On
146StandardUnsharpMaskStrengthint16s 
148StandardUnsharpMaskFinenessint16s 
150StandardUnsharpMaskThresholdint16s 
152PortraitUnsharpMaskStrengthint16s 
154PortraitUnsharpMaskFinenessint16s 
156PortraitUnsharpMaskThresholdint16s 
158LandscapeUnsharpMaskStrengthint16s 
160LandscapeUnsharpMaskFinenessint16s 
162LandscapeUnsharpMaskThresholdint16s 
164NeutraUnsharpMaskStrengthint16s 
166NeutralUnsharpMaskFinenessint16s 
168NeutralUnsharpMaskThresholdint16s 
170FaithfulUnsharpMaskStrengthint16s 
172FaithfulUnsharpMaskFinenessint16s 
174FaithfulUnsharpMaskThresholdint16s 
176MonochromeUnsharpMaskStrengthint16s 
178MonochromeUnsharpMaskFinenessint16s 
180MonochromeUnsharpMaskThresholdint16s 
182CustomUnsharpMaskStrengthint16s 
184CustomUnsharpMaskFinenessint16s 
186CustomUnsharpMaskThresholdint16s 
188CustomDefaultUnsharpStrengthint16s 
190CustomDefaultUnsharpFinenessint16s 
192CustomDefaultUnsharpThresholdint16s 
214CropCircleActiveint16s + +
0 = No  1 = Yes
+
215CropCircleXint16s 
216CropCircleYint16s 
217CropCircleRadiusint16s 
220DLOOnint16s + +
0 = No  1 = Yes
+
221DLOSettingint16s 
222DLOShootingDistanceint16s(100% = infinity)
223DLODataLengthno 
224DLOInfo---> CanonVRD DLOInfo Tags
225CameraRawColorToneint16s 
226CameraRawSaturationint16s 
227CameraRawContrastint16s 
228CameraRawLinearint16s + +
0 = No  1 = Yes
+
229CameraRawSharpnessint16s 
230CameraRawHighlightPointint16s 
231CameraRawShadowPointint16s 
232CameraRawOutputHighlightPointint16s 
233CameraRawOutputShadowPointint16s 
+ +

CanonVRD DLOInfo Tags

+

Tags added when DLO (Digital Lens Optimizer) is on.

+
+
+ + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
4DLOSettingAppliedint16s 
5DLOVersionstring[10] 
10DLODatano(variable-length Digital Lens Optimizer data, stored in JPEG-like format)
+ +

CanonVRD IHL Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001IHL_EXIF +
IHL_EXIF?
-
no
--> EXIF Tags +
(extracted as a block if the Unknown option is used, or processed as the +first sub-document with the ExtractEmbedded option)
0x0003ThumbnailImageno 
0x0004PreviewImageno 
0x0005RawCodecVersionno 
0x0006CRCDevelParams?no 
+ +

CanonVRD Edit4 Tags

+

Canon DPP version 4 edit information.

+
+
+ + + + + + + + +
IndexTag NameWritableValues / Notes
0DR4---> CanonVRD DR4 Tags
+ +

CanonVRD DR4 Tags

+

Tags written by Canon DPP version 4 in CanonVRD trailers and DR4 files. Each +tag has three associated flag words which are stored with the directory +entry, some of which are extracted as a separate tag, indicated in the table +below by a decimal appended to the tag ID (.0, .1 or .2).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'header'DR4Header---> CanonVRD DR4Header Tags
0x10002Rotationyes 
0x10003AngleAdjyes 
0x10021CustomPictureStyleyes 
0x10101CheckMarkyes + +
0 = Clear +
1 = 1 +
2 = 2
  3 = 3 +
4 = 4 +
5 = 5
+
0x10200WorkColorSpaceyes1 = sRGB +
2 = Adobe RGB +
3 = Wide Gamut RGB +
4 = Apple RGB +
5 = ColorMatch RGB
0x20001RawBrightnessAdjyes 
0x20101WhiteBalanceAdjyes + +
-1 = Manual (Click) +
0 = Auto +
1 = Daylight +
2 = Cloudy +
3 = Tungsten
  4 = Fluorescent +
5 = Flash +
8 = Shade +
9 = Kelvin +
255 = Shot Settings
+
0x20102WBAdjColorTempyes 
0x20105WBAdjMagentaGreenyes 
0x20106WBAdjBlueAmberyes 
0x20125WBAdjRGGBLevelsyes 
0x20200GammaLinearyes + +
0 = No  1 = Yes
+
0x20301PictureStyleyes + +
0x81 = Standard +
0x82 = Portrait +
0x83 = Landscape +
0x84 = Neutral +
0x85 = Faithful
  0x86 = Monochrome +
0x87 = Auto +
0x88 = Fine Detail +
0xf0 = Shot Settings +
0xff = Custom
+
0x20303ContrastAdjyes 
0x20304ColorToneAdjyes 
0x20305ColorSaturationAdjyes 
0x20306MonochromeToningEffectyes0 = None +
1 = Sepia +
2 = Blue +
3 = Purple +
4 = Green
0x20307MonochromeFilterEffectyes0 = None +
1 = Yellow +
2 = Orange +
3 = Red +
4 = Green
0x20308UnsharpMaskStrengthyes 
0x20309UnsharpMaskFinenessyes 
0x2030aUnsharpMaskThresholdyes 
0x2030bShadowAdjyes 
0x2030cHighlightAdjyes 
0x20310SharpnessAdjyes0 = Sharpness +
1 = Unsharp Mask
0x20310.0SharpnessAdjOnyes + +
0 = No  1 = Yes
+
0x20311SharpnessStrengthyes 
0x20400ToneCurve---> CanonVRD ToneCurve Tags
0x20400.1ToneCurveOriginalyes + +
0 = No  1 = Yes
+
0x20410ToneCurveBrightnessyes 
0x20411ToneCurveContrastyes 
0x20500AutoLightingOptimizeryes0 = Low +
1 = Standard +
2 = Strong
0x20500.0AutoLightingOptimizerOnyes(ignored if gamma is linear) + +
0 = No  1 = Yes
+
0x20600LuminanceNoiseReductionyes 
0x20601ChrominanceNoiseReductionyes 
0x20670ColorMoireReductionyes 
0x20670.0ColorMoireReductionOnyes + +
0 = No  1 = Yes
+
0x20701ShootingDistanceyes(100% = infinity)
0x20702PeripheralIlluminationyes 
0x20702.0PeripheralIlluminationOnyes + +
0 = No  1 = Yes
+
0x20703ChromaticAberrationyes 
0x20703.0ChromaticAberrationOnyes + +
0 = No  1 = Yes
+
0x20704ColorBlurOnyes + +
0 = No  1 = Yes
+
0x20705DistortionCorrectionyes 
0x20705.0DistortionCorrectionOnyes + +
0 = No  1 = Yes
+
0x20706DLOSettingyes 
0x20706.0DLOOnyes + +
0 = No  1 = Yes
+
0x20707ChromaticAberrationRedyes 
0x20708ChromaticAberrationBlueyes 
0x20709DistortionEffectyes0 = Shot Settings +
1 = Emphasize Linearity +
2 = Emphasize Distance +
3 = Emphasize Periphery +
4 = Emphasize Center
0x2070bDiffractionCorrectionOnyes + +
0 = No  1 = Yes
+
0x20900ColorHueyes 
0x20901SaturationAdjyes 
0x20910RedHSLyes 
0x20911OrangeHSLyes 
0x20912YellowHSLyes 
0x20913GreenHSLyes 
0x20914AquaHSLyes 
0x20915BlueHSLyes 
0x20916PurpleHSLyes 
0x20917MagentaHSLyes 
0x20a00GammaInfo---> CanonVRD GammaInfo Tags
0x30101CropAspectRatioyes + + +
0 = Free +
1 = Custom +
2 = 1:1 +
3 = 3:2
  4 = 2:3 +
5 = 4:3 +
6 = 3:4 +
7 = 5:4
  8 = 4:5 +
9 = 16:9 +
10 = 9:16
+
0x30102CropAspectRatioCustomyes 
0xf0100CropInfo---> CanonVRD CropInfo Tags
0xf0500CustomPictureStyleDatayes 
0xf0510StampInfo---> CanonVRD StampInfo Tags
0xf0511DustInfo---> CanonVRD DustInfo Tags
0xf0512LensFocalLengthyes 
+ +

CanonVRD DR4Header Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
3DR4CameraModelint32u--> Canon CanonModelID Values
+ +

CanonVRD ToneCurve Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0ToneCurveColorSpaceint32u0 = RGB +
1 = Luminance
1ToneCurveShapeint32u0 = Curve +
1 = Straight
3ToneCurveInputRangeint32u[2](255 max)
5ToneCurveOutputRangeint32u[2](255 max)
7RGBCurvePointsint32u[21] 
10ToneCurveXint32u 
11ToneCurveYint32u 
45RedCurvePointsint32u[21] 
83GreenCurvePointsint32u[21] 
121BlueCurvePointsint32u[21] 
+ +

CanonVRD GammaInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index8Tag NameWritableValues / Notes
2GammaContrastdouble 
3GammaColorTonedouble 
4GammaSaturationdouble 
5GammaUnsharpMaskStrengthdouble 
6GammaUnsharpMaskFinenessdouble 
7GammaUnsharpMaskThresholddouble 
8GammaSharpnessStrengthdouble 
9GammaShadowdouble 
10GammaHighlightdouble 
12GammaBlackPointdouble 
13GammaWhitePointdouble 
14GammaMidPointdouble 
15GammaCurveOutputRangedouble[2](16383 max)
+ +

CanonVRD CropInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0CropActiveint32s + +
0 = No  1 = Yes
+
1CropRotatedOriginalWidthint32s 
2CropRotatedOriginalHeightint32s 
3CropXint32s 
4CropYint32s 
5CropWidthint32s 
6CropHeightint32s 
8CropRotationdouble 
10CropOriginalWidthint32s 
11CropOriginalHeightint32s 
+ +

CanonVRD StampInfo Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
2StampToolCountno 
+ +

CanonVRD DustInfo Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
2DustDeleteAppliedno + +
0 = No  1 = Yes
+
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 14, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Casio.html b/ExifTool/html/TagNames/Casio.html new file mode 100644 index 0000000..515da2a --- /dev/null +++ b/ExifTool/html/TagNames/Casio.html @@ -0,0 +1,1607 @@ + + + + +Casio Tags + + + +

Casio Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001RecordingModeint16u + +
1 = Single Shutter +
2 = Panorama +
3 = Night Scene +
4 = Portrait +
5 = Landscape
  7 = Panorama +
10 = Night Scene +
15 = Portrait +
16 = Landscape
+
0x0002Qualityint16u1 = Economy +
2 = Normal +
3 = Fine
0x0003FocusModeint16u2 = Macro +
3 = Auto +
4 = Manual +
5 = Infinity +
7 = Spot AF
0x0004FlashModeint16u1 = Auto +
2 = On +
3 = Off +
4 = Off +
5 = Red-eye Reduction +
1 = Auto +
2 = On +
3 = Off +
4 = Red-eye Reduction
0x0005FlashIntensityint16u11 = Weak +
12 = Low +
13 = Normal +
14 = High +
15 = Strong
0x0006ObjectDistanceint32u 
0x0007WhiteBalanceint16u + +
1 = Auto +
2 = Tungsten +
3 = Daylight
  4 = Fluorescent +
5 = Shade +
129 = Manual
+
0x000aDigitalZoomint32u + +
0x10000 = Off +
0x10001 = 2x +
0x19999 = 1.6x
  0x20000 = 2x +
0x33333 = 3.2x +
0x40000 = 4x
+
0x000bSharpnessint16u + +
0 = Normal +
1 = Soft +
2 = Hard
  16 = Normal +
17 = +1 +
18 = -1
+
0x000cContrastint16u + +
0 = Normal +
1 = Low +
2 = High
  16 = Normal +
17 = +1 +
18 = -1
+
0x000dSaturationint16u + +
0 = Normal +
1 = Low +
2 = High
  16 = Normal +
17 = +1 +
18 = -1
+
0x0014ISOint16u 
0x0015FirmwareDatestring[18] 
0x0016Enhancementint16u1 = Off +
2 = Red +
3 = Green +
4 = Blue +
5 = Flesh Tones
0x0017ColorFilterint16u + +
1 = Off +
2 = Black & White +
3 = Sepia +
4 = Red +
5 = Green
  6 = Blue +
7 = Yellow +
8 = Pink +
9 = Purple
+
0x0018AFPointint16u(may not be valid for all models) +
1 = Center +
2 = Upper Left +
3 = Upper Right +
4 = Near Left/Right of Center +
5 = Far Left/Right of Center +
6 = Far Left/Right of Center/Bottom +
7 = Top Near-left +
8 = Near Upper/Left +
9 = Top Near-right +
10 = Top Left +
11 = Top Center +
12 = Top Right +
13 = Center Left +
14 = Center Right +
15 = Bottom Left +
16 = Bottom Center +
17 = Bottom Right
+
0x0019FlashIntensityint16u1 = Normal +
2 = Weak +
3 = Strong
0x0e00PrintIM---> PrintIM Tags
+ +

Casio Type2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0002PreviewImageSizeint16u[2] 
0x0003PreviewImageLengthint32u* 
0x0004PreviewImageStartint32u* 
0x0008QualityModeint16u0 = Economy +
1 = Normal +
2 = Fine
0x0009CasioImageSizeint16u + +
0 = 640x480 +
4 = 1600x1200 +
5 = 2048x1536 +
20 = 2288x1712
  21 = 2592x1944 +
22 = 2304x1728 +
36 = 3008x2008
+
0x000dFocusModeint16u0 = Normal +
1 = Macro
0x0014ISOint16u3 = 50 +
4 = 64 +
6 = 100 +
9 = 200
0x0019WhiteBalanceint16u + +
0 = Auto +
1 = Daylight +
2 = Shade
  3 = Tungsten +
4 = Fluorescent +
5 = Manual
+
0x001dFocalLengthrational64u 
0x001fSaturationint16u0 = Low +
1 = Normal +
2 = High
0x0020Contrastint16u0 = Low +
1 = Normal +
2 = High
0x0021Sharpnessint16u0 = Soft +
1 = Normal +
2 = Hard
0x0e00PrintIM---> PrintIM Tags
0x2000PreviewImageundef 
0x2001FirmwareDatestring[18] 
0x2011WhiteBalanceBiasint16u[2] 
0x2012WhiteBalanceint16u + +
0 = Manual +
1 = Daylight +
2 = Cloudy +
3 = Shade +
4 = Flash?
  6 = Fluorescent +
9 = Tungsten? +
10 = Tungsten +
12 = Flash
+
0x2021AFPointPositionint16u[4]~ 
0x2022ObjectDistanceint32u 
0x2034FlashDistanceint16u 
0x2076SpecialEffectModeint8u[3]'0 0 0' = Off +
'1 0 0' = Makeup +
'2 0 0' = Mist Removal +
'3 0 0' = Vivid Landscape
0x2089FaceInfo1 +
FaceInfo2 +
FaceInfoUnknown?
-
-
yes
--> Casio FaceInfo1 Tags +
--> Casio FaceInfo2 Tags
0x211cFacesDetectedint8u 
0x3000RecordModeint16u +
2 = Program AE +
3 = Shutter Priority +
4 = Aperture Priority +
5 = Manual +
6 = Best Shot +
17 = Movie +
19 = Movie (19) +
20 = YouTube Movie +
'2 0' = Program AE +
'3 0' = Shutter Priority +
'4 0' = Aperture Priority +
'5 0' = Manual +
'6 0' = Best Shot
+
0x3001ReleaseModeint16u1 = Normal +
3 = AE Bracketing +
11 = WB Bracketing +
13 = Contrast Bracketing +
19 = High Speed Burst
0x3002Qualityint16u1 = Economy +
2 = Normal +
3 = Fine
0x3003FocusModeint16u +
0 = Manual +
1 = Focus Lock +
2 = Macro +
3 = Single-Area Auto Focus +
5 = Infinity +
6 = Multi-Area Auto Focus +
8 = Super Macro
+
0x3006HometownCitystring 
0x3007BestShotModeint16u(EX-FC100) + +
0 = Off +
1 = Auto +
2 = Portrait +
3 = Scenery +
4 = Portrait with Scenery +
5 = Children +
6 = Sports +
7 = Pet +
8 = Flower +
9 = Natural Green +
10 = Autumn Leaves
  11 = Sundown +
12 = High Speed Night Scene +
13 = Night Scene Portrait +
14 = Fireworks +
15 = High Speed Anti Shake +
16 = Multi-motion Image +
17 = High Speed Best Selection +
18 = Move Out CS +
19 = Move In CS +
20 = Pre-record Movie +
21 = For YouTube
+(EX-FC150) + +
0 = Off +
1 = Auto +
2 = Expression CS +
3 = Baby CS +
4 = Child CS +
5 = Pet CS +
6 = Sports CS +
7 = Child High Speed Movie +
8 = Pet High Speed Movie +
9 = Sports High Speed Movie +
10 = Lag Correction +
11 = High Speed Lighting +
12 = High Speed Night Scene +
13 = High Speed Night Scene and Portrait +
14 = High Speed Anti Shake
  15 = High Speed Best Selection +
16 = Portrait +
17 = Scenery +
18 = Portrait with Scenery +
19 = Flower +
20 = Natural Green +
21 = Autumn Leaves +
22 = Sundown +
23 = Fireworks +
24 = Multi-motion Image +
25 = Move Out CS +
26 = Move In CS +
27 = Pre-record Movie +
28 = For YouTube
+(EX-FC200S) + +
0 = Off +
1 = Slow Motion Swing (behind) +
2 = Slow Motion Swing (front) +
3 = Self Slow Motion (behind) +
4 = Self Slow Motion (front) +
5 = Swing Burst +
6 = HDR +
7 = HDR Art +
8 = High Speed Night Scene +
9 = High Speed Night Scene and Portrait +
10 = High Speed Anti Shake +
11 = Multi SR Zoom +
12 = Blurred Background +
13 = Wide Shot +
14 = Slide Panorama +
15 = High Speed Best Selection +
16 = Lag Correction +
17 = High Speed CS +
18 = Child CS +
19 = Pet CS +
20 = Sports CS +
21 = Child High Speed Movie +
22 = Pet High Speed Movie
  23 = Sports High Speed Movie +
24 = Portrait +
25 = Scenery +
26 = Portrait with Scenery +
27 = Children +
28 = Sports +
29 = Candlelight Portrait +
30 = Party +
31 = Pet +
32 = Flower +
33 = Natural Green +
34 = Autumn Leaves +
35 = Soft Flowing Water +
36 = Splashing Water +
37 = Sundown +
38 = Fireworks +
39 = Food +
40 = Text +
41 = Collection +
42 = Auction +
43 = Pre-record Movie +
44 = For YouTube
+(EX-FH100) + +
0 = Off +
1 = Expression CS +
2 = Baby CS +
3 = Child CS +
4 = Pet CS +
5 = Sports CS +
6 = Child High Speed Movie +
7 = Pet High Speed Movie +
8 = Sports High Speed Movie +
9 = Lag Correction +
10 = High Speed Lighting +
11 = High Speed Night Scene +
12 = High Speed Night Scene and Portrait +
13 = High Speed Anti Shake
  14 = High Speed Best Selection +
15 = Portrait +
16 = Scenery +
17 = Portrait with Scenery +
18 = Flower +
19 = Natural Green +
20 = Autumn Leaves +
21 = Sundown +
22 = Fireworks +
23 = Multi-motion Image +
24 = Move Out CS +
25 = Move In CS +
26 = Pre-record Movie +
27 = For YouTube
+(EX-G1) + + +
0 = Off +
1 = Auto +
2 = Auto Best Shot +
3 = Dynamic Photo +
4 = Interval Snapshot +
5 = Interval Movie +
6 = Portrait +
7 = Scenery +
8 = Portrait with Scenery
  9 = Underwater +
10 = Beach +
11 = Snow +
12 = Children +
13 = Sports +
14 = Pet +
15 = Flower +
16 = Sundown +
17 = Night Scene
  18 = Night Scene Portrait +
19 = Fireworks +
20 = Food +
21 = For eBay +
22 = Multi-motion Image +
23 = Pre-record Movie +
24 = For YouTube +
25 = Voice Recording
+(EX-S10) + + +
0 = Off +
1 = Auto +
2 = Portrait +
3 = Scenery +
4 = Portrait with Scenery +
5 = Self-portrait (1 person) +
6 = Self-portrait (2 people) +
7 = Children +
8 = Sports +
9 = Candlelight Portrait +
10 = Party +
11 = Pet +
12 = Flower
  13 = Natural Green +
14 = Autumn Leaves +
15 = Soft Flowing Water +
16 = Splashing Water +
17 = Sundown +
18 = Night Scene +
19 = Night Scene Portrait +
20 = Fireworks +
21 = Food +
22 = Text +
23 = Collection +
24 = Auction +
25 = Backlight
  26 = Anti Shake +
27 = High Sensitivity +
28 = Underwater +
29 = Monochrome +
30 = Retro +
31 = Business Cards +
32 = White Board +
33 = Silent +
34 = Pre-record Movie +
35 = For YouTube +
36 = Voice Recording
+(EX-S880) + + +
0 = Off +
1 = Auto +
2 = Portrait +
3 = Scenery +
4 = Portrait with Scenery +
5 = Children +
6 = Sports +
7 = Candlelight Portrait +
8 = Party +
9 = Pet +
10 = Flower +
11 = Natural Green +
12 = Autumn Leaves +
13 = Soft Flowing Water
  14 = Splashing Water +
15 = Sundown +
16 = Night Scene +
17 = Night Scene Portrait +
18 = Fireworks +
19 = Food +
20 = Text +
21 = Collection +
22 = Auction +
23 = Backlight +
24 = Anti Shake +
25 = High Sensitivity +
26 = Monochrome +
27 = Retro
  28 = Twilight +
29 = Layout (2 images) +
30 = Layout (3 images) +
31 = Auto Framing +
32 = Old Photo +
33 = Business Cards +
34 = White Board +
35 = Silent +
36 = Short Movie +
37 = Past Movie +
38 = For YouTube +
39 = Voice Recording
+(EX-Z16) + + +
0 = Off +
1 = Auto +
2 = Portrait +
3 = Scenery +
4 = Portrait with Scenery +
5 = Children +
6 = Sports +
7 = Candlelight Portrait
  8 = Party +
9 = Pet +
10 = Flower +
11 = Soft Flowing Water +
12 = Sundown +
13 = Night Scene +
14 = Night Scene Portrait +
15 = Fireworks
  16 = Food +
17 = Text +
18 = For eBay +
19 = Backlight +
20 = Anti Shake +
21 = High Sensitivity +
22 = For YouTube +
23 = Voice Recording
+(EX-Z9) + + +
0 = Off +
1 = Auto +
2 = Movie +
3 = Portrait +
4 = Scenery +
5 = Children +
6 = Sports +
7 = Candlelight Portrait
  8 = Party +
9 = Pet +
10 = Flower +
11 = Soft Flowing Water +
12 = Sundown +
13 = Night Scene +
14 = Night Scene Portrait +
15 = Fireworks
  16 = Food +
17 = Text +
18 = Auction +
19 = Backlight +
20 = Anti Shake +
21 = High Sensitivity +
22 = For YouTube +
23 = Voice Recording
+(EX-Z80) + + +
0 = Off +
1 = Auto +
2 = Portrait +
3 = Scenery +
4 = Portrait with Scenery +
5 = Pet +
6 = Self-portrait (1 person) +
7 = Self-portrait (2 people) +
8 = Flower +
9 = Food +
10 = Fashion Accessories
  11 = Magazine +
12 = Monochrome +
13 = Retro +
14 = Cross Filter +
15 = Pastel +
16 = Night Scene +
17 = Night Scene Portrait +
18 = Party +
19 = Sports +
20 = Children +
21 = Sundown
  22 = Fireworks +
23 = Underwater +
24 = Backlight +
25 = High Sensitivity +
26 = Auction +
27 = White Board +
28 = Pre-record Movie +
29 = For YouTube +
30 = Voice Recording
+(EX-Z100 and EX-Z200) + + +
0 = Off +
1 = Auto +
2 = Auto Best Shot +
3 = Portrait +
4 = Scenery +
5 = Portrait with Scenery +
6 = Self-portrait (1 person) +
7 = Self-portrait (2 people) +
8 = Children +
9 = Sports +
10 = Candlelight Portrait +
11 = Party +
12 = Pet +
13 = Flower
  14 = Natural Green +
15 = Autumn Leaves +
16 = Soft Flowing Water +
17 = Splashing Water +
18 = Sundown +
19 = Night Scene +
20 = Night Scene Portrait +
21 = Fireworks +
22 = Food +
23 = Text +
24 = Collection +
25 = Auction +
26 = Backlight +
27 = Anti Shake
  28 = High Sensitivity +
29 = Underwater +
30 = Monochrome +
31 = Retro +
32 = Twilight +
33 = ID Photo +
34 = Business Cards +
35 = White Board +
36 = Silent +
37 = Pre-record Movie +
38 = For YouTube +
39 = Voice Recording
+(EX-Z750 JPEG images) + + +
0 = Off +
1 = Portrait +
2 = Scenery +
3 = Portrait with Scenery +
4 = Children +
5 = Sports +
6 = Candlelight Portrait +
7 = Party +
8 = Pet +
9 = Flower +
10 = Natural Green
  11 = Soft Flowing Water +
12 = Splashing Water +
13 = Sundown +
14 = Night Scene +
15 = Night Scene Portrait +
16 = Fireworks +
17 = Food +
18 = Text +
19 = Collection +
20 = Backlight +
21 = Anti Shake
  22 = Pastel +
23 = Illustration +
24 = Cross Filter +
25 = Monochrome +
26 = Retro +
27 = Twilight +
28 = Old Photo +
29 = ID Photo +
30 = Business Cards +
31 = White Board
+(EX-Z750 movies) + + +
0 = Off +
1 = Portrait +
2 = Scenery
  3 = Night Scene +
4 = Fireworks +
5 = Backlight
  6 = Silent
+(EX-Z850 JPEG images) + + +
0 = Off +
1 = Portrait +
2 = Scenery +
3 = Portrait with Scenery +
4 = Children +
5 = Sports +
6 = Candlelight Portrait +
7 = Party +
8 = Pet +
9 = Flower +
10 = Natural Green +
11 = Autumn Leaves
  12 = Soft Flowing Water +
13 = Splashing Water +
14 = Sundown +
15 = Night Scene +
16 = Night Scene Portrait +
17 = Fireworks +
18 = Food +
19 = Text +
20 = Collection +
21 = For eBay +
22 = Backlight +
23 = Anti Shake
  24 = High Sensitivity +
25 = Pastel +
26 = Illustration +
27 = Cross Filter +
28 = Monochrome +
29 = Retro +
30 = Twilight +
31 = ID Photo +
32 = Old Photo +
33 = Business Cards +
34 = White Board
+(EX-Z850 movies) + + +
0 = Off +
1 = Portrait +
2 = Scenery +
3 = Night Scene
  4 = Fireworks +
5 = Backlight +
6 = High Sensitivity +
7 = Silent
  8 = Short Movie +
9 = Past Movie
+(EX-Z1050) + + +
0 = Off +
1 = Auto +
2 = Movie +
3 = Portrait +
4 = Scenery +
5 = Portrait with Scenery +
6 = Children +
7 = Sports +
8 = Candlelight Portrait +
9 = Party +
10 = Pet +
11 = Flower +
12 = Natural Green
  13 = Autumn Leaves +
14 = Soft Flowing Water +
15 = Splashing Water +
16 = Sundown +
17 = Night Scene +
18 = Night Scene Portrait +
19 = Fireworks +
20 = Food +
21 = Text +
22 = Collection +
23 = For eBay +
24 = Backlight +
25 = Anti Shake
  26 = High Sensitivity +
27 = Underwater +
28 = Monochrome +
29 = Retro +
30 = Twilight +
31 = Layout (2 images) +
32 = Layout (3 images) +
33 = Auto Framing +
34 = ID Photo +
35 = Old Photo +
36 = Business Cards +
37 = White Board +
38 = Voice Recording
+(EX-Z1080) + + +
0 = Off +
1 = Auto +
2 = Movie +
3 = Portrait +
4 = Scenery +
5 = Portrait with Scenery +
6 = Children +
7 = Sports +
8 = Candlelight Portrait +
9 = Party +
10 = Pet +
11 = Flower +
12 = Natural Green +
13 = Autumn Leaves
  14 = Soft Flowing Water +
15 = Splashing Water +
16 = Sundown +
17 = Night Scene +
18 = Night Scene Portrait +
19 = Fireworks +
20 = Food +
21 = Text +
22 = Collection +
23 = For eBay +
24 = Backlight +
25 = Anti Shake +
26 = High Sensitivity +
27 = Underwater
  28 = Monochrome +
29 = Retro +
30 = Twilight +
31 = Layout (2 images) +
32 = Layout (3 images) +
33 = Auto Framing +
34 = ID Photo +
35 = Old Photo +
36 = Business Cards +
37 = White Board +
38 = Short Movie +
39 = Past Movie +
40 = For YouTube +
41 = Voice Recording
+(EX-Z1200 JPEG images) + + +
0 = Off +
1 = Portrait +
2 = Scenery +
3 = Portrait with Scenery +
4 = Children +
5 = Sports +
6 = Candlelight Portrait +
7 = Party +
8 = Pet +
9 = Flower +
10 = Natural Green +
11 = Autumn Leaves
  12 = Soft Flowing Water +
13 = Splashing Water +
14 = Sundown +
15 = Night Scene +
16 = Night Scene Portrait +
17 = Fireworks +
18 = Food +
19 = Text +
20 = Collection +
21 = Auction +
22 = Backlight +
23 = High Sensitivity
  24 = Underwater +
25 = Monochrome +
26 = Retro +
27 = Twilight +
28 = Layout (2 images) +
29 = Layout (3 images) +
30 = Auto Framing +
31 = ID Photo +
32 = Old Photo +
33 = Business Cards +
34 = White Board
+(EX-Z1200 movies) + + +
0 = Off +
1 = Portrait +
2 = Scenery +
3 = Night Scene
  4 = Fireworks +
5 = Backlight +
6 = High Sensitivity +
7 = Silent
  8 = Short Movie +
9 = Past Movie
+(EX-Z2000) + + +
0 = Off +
1 = Auto +
2 = Premium Auto +
3 = Dynamic Photo +
4 = Portrait +
5 = Scenery +
6 = Portrait with Scenery +
7 = Children +
8 = Sports +
9 = Candlelight Portrait +
10 = Party +
11 = Pet +
12 = Flower +
13 = Natural Green
  14 = Autumn Leaves +
15 = Soft Flowing Water +
16 = Splashing Water +
17 = Sundown +
18 = Night Scene +
19 = Night Scene Portrait +
20 = Fireworks +
21 = Food +
22 = Text +
23 = Collection +
24 = For eBay +
25 = Backlight +
26 = High Sensitivity +
27 = Oil Painting
  28 = Crayon +
29 = Water Color +
30 = Monochrome +
31 = Retro +
32 = Twilight +
33 = Multi-motion Image +
34 = ID Photo +
35 = Business Cards +
36 = White Board +
37 = Silent +
38 = Pre-record Movie +
39 = For YouTube +
40 = Voice Recording
+(EX-Z2300) + + +
0 = Off +
1 = Auto +
2 = Premium Auto +
3 = Dynamic Photo +
4 = Portrait +
5 = Scenery +
6 = Portrait with Scenery +
7 = Children +
8 = Sports +
9 = Candlelight Portrait +
10 = Party +
11 = Pet +
12 = Flower +
13 = Natural Green
  14 = Autumn Leaves +
15 = Soft Flowing Water +
16 = Splashing Water +
17 = Sundown +
18 = Night Scene +
19 = Night Scene Portrait +
20 = Fireworks +
21 = Food +
22 = Text +
23 = Collection +
24 = Auction +
25 = Backlight +
26 = High Sensitivity +
27 = Oil Painting
  28 = Crayon +
29 = Water Color +
30 = Monochrome +
31 = Retro +
32 = Twilight +
33 = Multi-motion Image +
34 = ID Photo +
35 = Business Cards +
36 = White Board +
37 = Silent +
38 = Pre-record Movie +
39 = For YouTube +
40 = Voice Recording
+(EX-Z3000) + + +
0 = Off +
1 = Portrait +
2 = Scenery
  3 = Portrait with Scenery +
4 = Children +
5 = Sports
  6 = Night Scene
+(EX-ZR100) + +
0 = Off +
1 = Child CS +
2 = Pet CS +
3 = Sports CS +
4 = Child High Speed Movie +
5 = Pet High Speed Movie +
6 = Sports High Speed Movie +
7 = Multi SR Zoom +
8 = Lag Correction +
9 = High Speed Night Scene +
10 = High Speed Night Scene and Portrait +
11 = High Speed Anti Shake +
12 = Portrait +
13 = Scenery +
14 = Portrait with Scenery +
15 = Children +
16 = Sports
  17 = Candlelight Portrait +
18 = Party +
19 = Pet +
20 = Flower +
21 = Natural Green +
22 = Autumn Leaves +
23 = Soft Flowing Water +
24 = Splashing Water +
25 = Sundown +
26 = Fireworks +
27 = Food +
28 = Text +
29 = Collection +
30 = For eBay +
31 = Pre-record Movie +
32 = For YouTube
+(EX-ZR200) + +
0 = Off +
1 = High Speed Night Scene +
2 = High Speed Night Scene and Portrait +
3 = High Speed Anti Shake +
4 = Blurred Background +
5 = Wide Shot +
6 = High Speed Best Selection +
7 = Lag Correction +
8 = Child CS +
9 = Pet CS +
10 = Sports CS +
11 = Child High Speed Movie +
12 = Pet High Speed Movie +
13 = Sports High Speed Movie +
14 = Portrait +
15 = Scenery +
16 = Portrait with Scenery +
17 = Children
  18 = Sports +
19 = Candlelight Portrait +
20 = Party +
21 = Pet +
22 = Flower +
23 = Natural Green +
24 = Autumn Leaves +
25 = Soft Flowing Water +
26 = Splashing Water +
27 = Sundown +
28 = Fireworks +
29 = Food +
30 = Text +
31 = Collection +
32 = Auction +
33 = Pre-record Movie +
34 = For YouTube
+(QV-4000) + + +
0 = Off +
1 = People
  2 = Scenery +
3 = Flower
  4 = Night Scene +
5 = Soft Focus
+(EX-ZR300) + +
1 = High Speed Night Shot +
2 = Blurred Background +
3 = Toy Camera +
4 = Soft Focus +
5 = Light Tone +
6 = Pop +
7 = Sepia +
8 = Monochrome +
9 = Miniature +
10 = Wide Shot +
11 = High Speed Best Selection +
12 = Lag Correction +
13 = High Speed Night Scene +
14 = High Speed Night Scene and Portrait +
15 = High Speed Anti Shake +
16 = Portrait +
17 = Scenery +
18 = Portrait with Scenery
  19 = Children +
20 = Sports +
21 = Candlelight Portrait +
22 = Party +
23 = Pet +
24 = Flower +
25 = Natural Green +
26 = Autumn Leaves +
27 = Soft Flowing Water +
28 = Splashing Water +
29 = Sundown +
30 = Fireworks +
31 = Food +
32 = Text +
33 = Collection +
34 = Auction +
35 = Prerecord (Movie) +
36 = For YouTube
+(other models not yet decoded)
0x3008AutoISOint16u1 = On +
2 = Off +
7 = On (high sensitivity) +
8 = On (anti-shake) +
10 = High Speed
0x3009AFModeint16u + +
0 = Off +
1 = Spot +
2 = Multi
  3 = Face Detection +
4 = Tracking +
5 = Intelligent
+
0x3011Sharpnessundef[2] 
0x3012Contrastundef[2] 
0x3013Saturationundef[2] 
0x3014ISOint16u 
0x3015ColorModeint16u0 = Off +
2 = Black & White +
3 = Sepia
0x3016Enhancementint16u0 = Off +
1 = Scenery +
3 = Green +
5 = Underwater +
9 = Flesh Tones
0x3017ColorFilterint16u + +
0 = Off +
1 = Blue +
3 = Green +
4 = Yellow
  5 = Red +
6 = Purple +
7 = Pink
+
0x301bArtModeint16u +
0 = Normal +
8 = Silent Movie +
39 = HDR +
45 = Premium Auto +
47 = Painting +
49 = Crayon Drawing +
51 = Panorama +
52 = Art HDR +
62 = High Speed Night Shot +
64 = Monochrome +
67 = Toy Camera +
68 = Pop Art +
69 = Light Tone
+
0x301cSequenceNumberint16u 
0x301dBracketSequenceint16u[2] 
0x3020ImageStabilizationint16u +
0 = Off +
1 = On +
2 = Best Shot +
3 = Movie Anti-Shake +
'0 0' = Off +
'0 1' = Off (1) +
'0 3' = CCD Shift +
'2 1' = High Sensitivity +
'2 3' = CCD Shift + High Sensitivity +
'16 0' = Slow Shutter +
'18 0' = Anti-Shake +
'20 0' = High Sensitivity
+
0x302aLightingModeint16u0 = Off +
1 = High Dynamic Range +
5 = Shadow Enhance Low +
6 = Shadow Enhance High
0x302bPortraitRefinerint16u0 = Off +
1 = +1 +
2 = +2
0x3030SpecialEffectLevelint16u 
0x3031SpecialEffectSettingint16u0 = Off +
1 = Makeup +
2 = Mist Removal +
3 = Vivid Landscape +
16 = Art Shot
0x3103DriveModeint16u + +
0 = Single Shot +
1 = Continuous Shooting +
2 = Continuous (2 fps) +
3 = Continuous (3 fps) +
4 = Continuous (4 fps) +
5 = Continuous (5 fps) +
6 = Continuous (6 fps) +
7 = Continuous (7 fps)
  10 = Continuous (10 fps) +
12 = Continuous (12 fps) +
15 = Continuous (15 fps) +
20 = Continuous (20 fps) +
30 = Continuous (30 fps) +
40 = Continuous (40 fps) +
60 = Continuous (60 fps) +
240 = Auto-N
+
0x310bArtModeParametersint8u[3] 
0x4001CaptureFrameRateint16u[n] 
0x4003VideoQualityint16u1 = Standard +
3 = HD (720p) +
4 = Full HD (1080p) +
5 = Low
+ +

Casio FaceInfo1 Tags

+

Face-detect tags extracted from models such as the EX-H5.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FacesDetectedint8u 
1FaceDetectFrameSizeint16u[2] 
13Face1Positionint16u[4](left, top, right and bottom of detected face in coordinates of +FaceDetectFrameSize, with increasing Y downwards)
124Face2Positionint16u[4] 
235Face3Positionint16u[4] 
346Face4Positionint16u[4] 
457Face5Positionint16u[4] 
568Face6Positionint16u[4] 
679Face7Positionint16u[4] 
790Face8Positionint16u[4] 
901Face9Positionint16u[4] 
1012Face10Positionint16u[4] 
+ +

Casio FaceInfo2 Tags

+

Face-detect tags extracted from models such as the EX-H20G and EX-ZR100.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
2FacesDetectedint8u 
4FaceDetectFrameSizeint16u[2] 
8FaceOrientationint8u(orientation of face relative to unrotated image) +
0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW +
3 = Rotate 180
24Face1Positionint16u[4](left, top, right and bottom of detected face in coordinates of +FaceDetectFrameSize, with increasing Y downwards)
76Face2Positionint16u[4] 
128Face3Positionint16u[4] 
180Face4Positionint16u[4] 
232Face5Positionint16u[4] 
284Face6Positionint16u[4] 
336Face7Positionint16u[4] 
388Face8Positionint16u[4] 
440Face9Positionint16u[4] 
492Face10Positionint16u[4] 
+ +

Casio QVCI Tags

+

This information is found in the APP1 QVCI segment of JPEG images from the +Casio QV-7000SX.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
44CasioQualityno1 = Economy +
2 = Normal +
3 = Fine +
4 = Super Fine
55FocalRange?no 
77DateTimeOriginalno 
98ModelTypeno 
114ManufactureIndexno 
124ManufactureCodeno 
+ +

Casio AVI Tags

+

This information is found in Casio GV-10 AVI videos.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0Softwareno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Dec 6, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Composite.html b/ExifTool/html/TagNames/Composite.html new file mode 100644 index 0000000..ee115dc --- /dev/null +++ b/ExifTool/html/TagNames/Composite.html @@ -0,0 +1,832 @@ + + + + +Composite Tags + + + +

Composite Tags

+

+The values of the composite tags are Derived From the values of other +tags. These are convenience tags which are calculated after all other +information is extracted. Only a few of these tags are writable directly, +the others are changed by writing the corresponding Derived From tags. +User-defined Composite tags, also useful for custom-formatting of tag +values, may created via the ExifTool configuration file. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableDerived FromValues / Notes
AdvancedSceneModenoModel +
SceneMode +
AdvancedSceneType
--> Panasonic AdvancedSceneMode Values
AperturenoFNumber +
ApertureValue
 
AudioBitratenoMPEG:MPEGAudioVersion +
MPEG:SampleRate +
MPEG:VBRBytes +
MPEG:VBRFrames
(calculated for variable-bitrate MPEG audio)
AutoFocusnoNikon:PhaseDetectAF +
Nikon:ContrastDetectAF
0 = Off +
1 = On
AvgBitratenoQuickTime::MediaDataSize +
QuickTime::Duration
 
BlueBalancenoWB_RGGBLevels +
WB_RGBGLevels +
WB_RBGGLevels +
WB_GRBGLevels +
WB_GRGBLevels +
WB_GBRGLevels +
WB_RGBLevels +
WB_GRBLevels +
WB_RBLevels +
WBBlueLevel +
WBGreenLevel
 
CDDBDiscPlayTimenoCDDB1Info 
CDDBDiscTracksnoCDDB1Info 
CFAPatternnoCFARepeatPatternDim +
CFAPattern2
 
CircleOfConfusionnoScaleFactor35efl(calculated as D/1440, where D is the focal plane diagonal in mm. This value +may be incorrect if the image has been resized)
ConditionalFECnoFlashExposureComp +
FlashBits
 
DOFnoFocalLength +
Aperture +
CircleOfConfusion +
FocusDistance +
SubjectDistance +
ObjectDistance +
ApproximateFocusDistance +
FocusDistanceLower +
FocusDistanceUpper
(this value may be incorrect if the image has been resized)
DateCreatednoKodak:YearCreated +
Kodak:MonthDayCreated
 
DateTimeCreatednoIPTC:DateCreated +
IPTC:TimeCreated
 
DateTimeOriginalnoDateTimeCreated +
DateCreated +
TimeCreated
 
DateTimeOriginalnoID3:RecordingTime +
ID3:Year +
ID3:Date +
ID3:Time
 
DepthMapTiffnoDepthMapData +
DepthMapWidth +
DepthMapHeight
 
DigitalCreationDateTimenoIPTC:DigitalCreationDate +
IPTC:DigitalCreationTime
 
DigitalZoomnoCanon:ZoomSourceWidth +
Canon:ZoomTargetWidth +
Canon:DigitalZoom
 
DriveModenoContinuousDrive +
SelfTimer
0 = Continuous Shooting +
1 = Self-timer Operation +
2 = Single-frame Shooting
DurationnoAIFF:SampleRate +
AIFF:NumSampleFrames
 
DurationnoAPE:SampleRate +
APE:TotalFrames +
APE:BlocksPerFrame +
APE:FinalFrameBlocks
 
DurationnoFLAC:SampleRate +
FLAC:TotalSamples
 
DurationnoFileSize +
ID3Size +
MPEG:AudioBitrate +
MPEG:VideoBitrate +
MPEG:VBRFrames +
MPEG:SampleRate +
MPEG:MPEGAudioVersion
 
DurationnoRIFF:FrameRate +
RIFF:FrameCount +
VideoFrameRate +
VideoFrameCount
 
DurationnoRIFF:AvgBytesPerSec +
FileSize +
FrameCount +
VideoFrameCount
 
DurationnoVorbis:NominalBitrate +
FileSize
 
ExtenderStatusnoOlympus:Extender +
Olympus:LensType +
MaxApertureValue
(Olympus cameras have the quirk that they may retain the extender settings +after the extender is removed until the camera is powered off. This tag is +an attempt to represent the actual status of the extender.) +
0 = Not attached +
1 = Attached +
2 = Removed
FOVnoFocalLength +
ScaleFactor35efl +
FocusDistance
(calculated for the long image dimension. This value may be incorrect for +fisheye lenses, or if the image has been resized)
FileNumberyesDirectoryIndex +
FileIndex
 
FlashyesXMP:FlashFired +
XMP:FlashReturn +
XMP:FlashMode +
XMP:FlashFunction +
XMP:FlashRedEyeMode +
XMP:Flash
--> EXIF Flash Values +
(facilitates copying camera flash information between XMP and EXIF)
FlashTypenoFlashBits(may report "Built-in Flash" for some Canon cameras with external flash in +manual mode) +
0 = Built-In Flash +
1 = External
FocalLength35eflnoFocalLength +
ScaleFactor35efl
(this value may be incorrect if the image has been resized)
FocusDistancenoSony:FocusPosition +
FocalLength
(distance in metres = FocusPosition * FocalLength / 1000)
FocusDistance2noSony:FocusPosition2 +
FocalLengthIn35mmFormat
 
GPSAltitudenoGPS:GPSAltitude +
GPS:GPSAltitudeRef +
XMP:GPSAltitude +
XMP:GPSAltitudeRef
 
GPSAltitudenoQuickTime:GPSCoordinates 
GPSAltitudenoQuickTime:LocationInformation 
GPSAltitudeRefnoQuickTime:GPSCoordinates0 = Above Sea Level +
1 = Below Sea Level
GPSAltitudeRefnoQuickTime:LocationInformation0 = Above Sea Level +
1 = Below Sea Level
GPSDateTimenoGPS:GPSDateStamp +
GPS:GPSTimeStamp
 
GPSDateTimenoParrot:GPSLatitude +
Main:CreateDate +
SampleTime
 
GPSDateTimenoSony:GPSDateStamp +
Sony:GPSTimeStamp
 
GPSDestLatitudenoGPS:GPSDestLatitude +
GPS:GPSDestLatitudeRef
 
GPSDestLatitudeRefnoXMP-exif:GPSDestLatitude'N' = North +
'S' = South
GPSDestLongitudenoGPS:GPSDestLongitude +
GPS:GPSDestLongitudeRef
 
GPSDestLongitudeRefnoXMP-exif:GPSDestLongitude'E' = East +
'W' = West
GPSLatitudeyes/GPS:GPSLatitude +
GPS:GPSLatitudeRef
 
GPSLatitudenoQuickTime:GPSCoordinates 
GPSLatitudenoQuickTime:LocationInformation 
GPSLatitudenoSony:GPSLatitude +
Sony:GPSLatitudeRef
 
GPSLatitudeRefnoXMP-exif:GPSLatitude'N' = North +
'S' = South
GPSLongitudeyes/GPS:GPSLongitude +
GPS:GPSLongitudeRef
 
GPSLongitudenoQuickTime:GPSCoordinates 
GPSLongitudenoQuickTime:LocationInformation 
GPSLongitudenoSony:GPSLongitude +
Sony:GPSLongitudeRef
 
GPSLongitudeRefnoXMP-exif:GPSLongitude'E' = East +
'W' = West
GPSPositionyes!GPSLatitude +
GPSLongitude
(when written, writes GPSLatitude, GPSLatitudeRef, GPSLongitude and +GPSLongitudeRef. This tag may be written using the same coordinate +format as provided by Google Maps when right-clicking on a location)
HyperfocalDistancenoFocalLength +
Aperture +
CircleOfConfusion
(this value may be incorrect if the image has been resized)
IDCPreviewImagenoIDCPreviewStart +
IDCPreviewLength
 
ISOnoCanon:CameraISO +
Canon:BaseISO +
Canon:AutoISO
(use CameraISO if numerical, otherwise calculate as BaseISO * AutoISO / 100)
ImageHeightnoIFD0:SensorTopBorder +
IFD0:SensorBottomBorder
 
ImageHeightnoMain:PostScript:ImageData +
PostScript:BoundingBox
 
ImageSizenoImageWidth +
ImageHeight +
ExifImageWidth +
ExifImageHeight +
RawImageCroppedSize
 
ImageWidthnoIFD0:SensorLeftBorder +
IFD0:SensorRightBorder
 
ImageWidthnoMain:PostScript:ImageData +
PostScript:BoundingBox
 
JpgFromRawyesJpgFromRawStart +
JpgFromRawLength
(this tag is writable, and may be used to update existing embedded images, +but not create or delete them)
LensnoCanon:MinFocalLength +
Canon:MaxFocalLength
 
Lens35eflnoCanon:MinFocalLength +
Canon:MaxFocalLength +
Lens +
ScaleFactor35efl
 
LensIDnoLensType +
FocalLength +
MaxAperture +
MaxApertureValue +
MinFocalLength +
MaxFocalLength +
LensModel +
LensFocalRange +
LensSpec +
LensType2 +
LensType3 +
LensFocalLength +
RFLensType
(attempt to identify the actual lens from all lenses with a given LensType. +Applies only to LensType values with a lookup table. May be configured +by adding user-defined lenses)
LensIDnoLensModel +
Lens +
XMP-aux:LensID +
Make
 
LensIDnoNikon:LensIDNumber +
LensFStops +
MinFocalLength +
MaxFocalLength +
MaxApertureAtMinFocal +
MaxApertureAtMaxFocal +
MCUVersion +
Nikon:LensType
--> Nikon LensID Values
LensIDnoRicoh:LensFirmware--> Ricoh LensID Values
LensIDnoXMP-aux:LensID +
Make +
LensInfo +
FocalLength +
LensModel +
MaxApertureValue
(attempt to convert numerical XMP-aux:LensID stored by Adobe applications)
LensSpecnoNikon:Lens +
Nikon:LensType
 
LensTypenoLensTypeMake +
LensTypeModel
--> Olympus LensType Values +
(based on tags found in some Panasonic RW2 images)
LightValuenoAperture +
ShutterSpeed +
ISO
(calculated LV = 2 * log2(Aperture) - log2(ShutterSpeed) - log2(ISO/100); +similar to exposure value but normalized to ISO 100)
MPImagenoMPImageStart +
MPImageLength +
MPImageType
(the first MPF "Large Thumbnail" is extracted as PreviewImage, and the rest +of the embedded MPF images are extracted as MPImage#. The ExtractEmbedded +option may be used to extract information from these embedded images.)
MegapixelsnoImageSize 
OriginalDecisionDatayes!OriginalDecisionDataOffset 
OtherImageyesOtherImageStart +
OtherImageLength +
OtherImageStart (1) +
OtherImageLength (1)
(this tag is writable, and may be used to update existing embedded images, +but not create or delete them)
PeakSpectralSensitivitynoFLIR:PlanckB 
PreviewImageyesPreviewImageStart +
PreviewImageLength +
PreviewImageValid +
PreviewImageStart (1) +
PreviewImageLength (1)
(this tag is writable, and may be used to update existing embedded images, +but not create or delete them)
PreviewImagenoScreenNail 
PreviewImageSizenoPreviewImageWidth +
PreviewImageHeight
 
RedBalancenoWB_RGGBLevels +
WB_RGBGLevels +
WB_RBGGLevels +
WB_GRBGLevels +
WB_GRGBLevels +
WB_GBRGLevels +
WB_RGBLevels +
WB_GRBLevels +
WB_RBLevels +
WBRedLevel +
WBGreenLevel
 
RedEyeReductionnoCanonFlashMode +
FlashBits
0 = Off +
1 = On
RicohPitchnoRicoh:Accelerometer 
RicohRollnoRicoh:Accelerometer 
Rotationyes!QuickTime:MatrixStructure +
QuickTime:HandlerType
(writing this tag updates QuickTime MatrixStructure for all tracks with a +non-zero image size)
RunTimeSincePowerUpnoApple:RunTimeValue +
Apple:RunTimeScale
 
ScaleFactor35eflnoFocalLength +
FocalLengthIn35mmFormat +
Composite:DigitalZoom +
FocalPlaneDiagonal +
SensorSize +
FocalPlaneXSize +
FocalPlaneYSize +
FocalPlaneResolutionUnit +
FocalPlaneXResolution +
FocalPlaneYResolution +
ExifImageWidth +
ExifImageHeight +
CanonImageWidth +
CanonImageHeight +
ImageWidth +
ImageHeight
(this value and any derived values may be incorrect if the image has been +resized)
ShootingModenoCanonExposureMode +
EasyMode +
BulbDuration
 
ShutterCurtainHacknoFlashBits +
ShutterCurtainSync
0 = 1st-curtain sync +
1 = 2nd-curtain sync
ShutterSpeednoExposureTime +
ShutterSpeedValue +
BulbDuration
 
SingleShotDepthMapTiffnoSingleShotDepthMap +
SegWidth +
SegHeight
 
SubSecCreateDateyesEXIF:CreateDate +
SubSecTimeDigitized +
OffsetTimeDigitized
 
SubSecDateTimeOriginalyesEXIF:DateTimeOriginal +
SubSecTimeOriginal +
OffsetTimeOriginal
 
SubSecModifyDateyesEXIF:ModifyDate +
SubSecTime +
OffsetTime
 
ThumbnailImageyesThumbnailOffset +
ThumbnailLength
(this tag is writable, and may be used to update existing thumbnails, but may +only create a thumbnail in IFD1 of certain types of files. Note that for +this and other Composite embedded-image tags the family 0 and 1 groups match +those of the originating tags)
ThumbnailTIFFnoSubfileType +
Compression +
ImageWidth +
ImageHeight +
BitsPerSample +
PhotometricInterpretation +
StripOffsets +
SamplesPerPixel +
RowsPerStrip +
StripByteCounts +
PlanarConfiguration +
Orientation
 
VolumeSizenoISO:VolumeBlockCount +
ISO:VolumeBlockSize
 
WB_RGBLevelsnoKDC_IFD:WhiteBalance +
WB_RGBLevelsAuto +
WB_RGBLevelsFluorescent +
WB_RGBLevelsTungsten +
WB_RGBLevelsDaylight +
WB_RGBLevels4 +
WB_RGBLevels5 +
WB_RGBLevelsShade
 
WB_RGBLevelsnoKodakIFD:WhiteBalance +
WB_RGBMul0 +
WB_RGBMul1 +
WB_RGBMul2 +
WB_RGBMul3 +
WB_RGBCoeffs0 +
WB_RGBCoeffs1 +
WB_RGBCoeffs2 +
WB_RGBCoeffs3 +
KodakIFD:ColorTemperature +
Kodak:WB_RGBLevels
 
WB_RGGBLevelsnoCanon:WhiteBalance +
WB_RGGBLevelsAsShot +
WB_RGGBLevelsAuto +
WB_RGGBLevelsDaylight +
WB_RGGBLevelsCloudy +
WB_RGGBLevelsTungsten +
WB_RGGBLevelsFluorescent +
WB_RGGBLevelsFlash +
WB_RGGBLevelsCustom +
WB_RGGBLevelsShade +
WB_RGGBLevelsKelvin
 
WB_RGGBLevelsnoWB_RGGBLevelsUncorrected +
WB_RGGBLevelsBlack
 
ZoomedPreviewImagenoZoomedPreviewStart +
ZoomedPreviewLength
 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 9, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/DICOM.html b/ExifTool/html/TagNames/DICOM.html new file mode 100644 index 0000000..939c26b --- /dev/null +++ b/ExifTool/html/TagNames/DICOM.html @@ -0,0 +1,15763 @@ + + + + +DICOM Tags + + + +

DICOM Tags

+

The DICOM format is based on the ACR-NEMA specification, but adds a file +header and a number of new tags. ExifTool will extract information from +either type of file. The Tag ID's in the following table are the tag group +and element numbers in hexadecimal, as given in the DICOM specification (see +http://medical.nema.org/). The table below contains tags from the DICOM +2009 and earlier specifications plus some vendor-specific private tags.

+ +

Note that DICOM information may be saved in other file formats using the +XMP DICOM Tags.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0002,0000FileMetaInfoGroupLengthno 
0002,0001FileMetaInfoVersionno 
0002,0002MediaStorageSOPClassUIDno 
0002,0003MediaStorageSOPInstanceUIDno 
0002,0010TransferSyntaxUIDno 
0002,0012ImplementationClassUIDno 
0002,0013ImplementationVersionNameno 
0002,0016SourceApplicationEntityTitleno 
0002,0100PrivateInformationCreatorUIDno 
0002,0102PrivateInformationno 
0004,1130FileSetIDno 
0004,1141FileSetDescriptorFileIDno 
0004,1142SpecificCharacterSetOfFileno 
0004,1200FirstDirectoryRecordOffsetno 
0004,1202LastDirectoryRecordOffsetno 
0004,1212FileSetConsistencyFlagno 
0004,1220DirectoryRecordSequenceno 
0004,1400OffsetOfNextDirectoryRecordno 
0004,1410RecordInUseFlagno 
0004,1420LowerLevelDirectoryEntityOffsetno 
0004,1430DirectoryRecordTypeno 
0004,1432PrivateRecordUIDno 
0004,1500ReferencedFileIDno 
0004,1504MRDRDirectoryRecordOffsetno 
0004,1510ReferencedSOPClassUIDInFileno 
0004,1511ReferencedSOPInstanceUIDInFileno 
0004,1512ReferencedTransferSyntaxUIDInFileno 
0004,151AReferencedRelatedSOPClassUIDInFileno 
0004,1600NumberOfReferencesno 
0008,0000IdentifyingGroupLengthno 
0008,0001LengthToEndno 
0008,0005SpecificCharacterSetno 
0008,0006LanguageCodeSequenceno 
0008,0008ImageTypeno 
0008,0010RecognitionCodeno 
0008,0012InstanceCreationDateno 
0008,0013InstanceCreationTimeno 
0008,0014InstanceCreatorUIDno 
0008,0016SOPClassUIDno 
0008,0018SOPInstanceUIDno 
0008,001ARelatedGeneralSOPClassUIDno 
0008,001BOriginalSpecializedSOPClassUIDno 
0008,0020StudyDateno 
0008,0021SeriesDateno 
0008,0022AcquisitionDateno 
0008,0023ContentDateno 
0008,0024OverlayDateno 
0008,0025CurveDateno 
0008,002AAcquisitionDateTimeno 
0008,0030StudyTimeno 
0008,0031SeriesTimeno 
0008,0032AcquisitionTimeno 
0008,0033ContentTimeno 
0008,0034OverlayTimeno 
0008,0035CurveTimeno 
0008,0040DataSetTypeno 
0008,0041DataSetSubtypeno 
0008,0042NuclearMedicineSeriesTypeno 
0008,0050AccessionNumberno 
0008,0052QueryRetrieveLevelno 
0008,0054RetrieveAETitleno 
0008,0056InstanceAvailabilityno 
0008,0058FailedSOPInstanceUIDListno 
0008,0060Modalityno 
0008,0061ModalitiesInStudyno 
0008,0062SOPClassesInStudyno 
0008,0064ConversionTypeno 
0008,0068PresentationIntentTypeno 
0008,0070Manufacturerno 
0008,0080InstitutionNameno 
0008,0081InstitutionAddressno 
0008,0082InstitutionCodeSequenceno 
0008,0090ReferringPhysicianNameno 
0008,0092ReferringPhysicianAddressno 
0008,0094ReferringPhysicianTelephoneNumberno 
0008,0096ReferringPhysicianIDSequenceno 
0008,0100CodeValueno 
0008,0102CodingSchemeDesignatorno 
0008,0103CodingSchemeVersionno 
0008,0104CodeMeaningno 
0008,0105MappingResourceno 
0008,0106ContextGroupVersionno 
0008,0107ContextGroupLocalVersionno 
0008,010BContextGroupExtensionFlagno 
0008,010CCodingSchemeUIDno 
0008,010DContextGroupExtensionCreatorUIDno 
0008,010FContextIdentifierno 
0008,0110CodingSchemeIDSequenceno 
0008,0112CodingSchemeRegistryno 
0008,0114CodingSchemeExternalIDno 
0008,0115CodingSchemeNameno 
0008,0116ResponsibleOrganizationno 
0008,0117ContextUIDno 
0008,0201TimezoneOffsetFromUTCno 
0008,1000NetworkIDno 
0008,1010StationNameno 
0008,1030StudyDescriptionno 
0008,1032ProcedureCodeSequenceno 
0008,103ESeriesDescriptionno 
0008,1040InstitutionalDepartmentNameno 
0008,1048PhysiciansOfRecordno 
0008,1049PhysiciansOfRecordIDSequenceno 
0008,1050PerformingPhysicianNameno 
0008,1052PerformingPhysicianIDSequenceno 
0008,1060NameOfPhysicianReadingStudyno 
0008,1062PhysicianReadingStudyIDSequenceno 
0008,1070OperatorsNameno 
0008,1072OperatorIDSequenceno 
0008,1080AdmittingDiagnosesDescriptionno 
0008,1084AdmittingDiagnosesCodeSequenceno 
0008,1090ManufacturersModelNameno 
0008,1100ReferencedResultsSequenceno 
0008,1110ReferencedStudySequenceno 
0008,1111ReferencedProcedureStepSequenceno 
0008,1115ReferencedSeriesSequenceno 
0008,1120ReferencedPatientSequenceno 
0008,1125ReferencedVisitSequenceno 
0008,1130ReferencedOverlaySequenceno 
0008,113AReferencedWaveformSequenceno 
0008,1140ReferencedImageSequenceno 
0008,1145ReferencedCurveSequenceno 
0008,114AReferencedInstanceSequenceno 
0008,1150ReferencedSOPClassUIDno 
0008,1155ReferencedSOPInstanceUIDno 
0008,115ASOPClassesSupportedno 
0008,1160ReferencedFrameNumberno 
0008,1161SimpleFrameListno 
0008,1162CalculatedFrameListno 
0008,1163TimeRangeno 
0008,1164FrameExtractionSequenceno 
0008,1195TransactionUIDno 
0008,1197FailureReasonno 
0008,1198FailedSOPSequenceno 
0008,1199ReferencedSOPSequenceno 
0008,1200OtherReferencedStudiesSequenceno 
0008,1250RelatedSeriesSequenceno 
0008,2110LossyImageCompressionno 
0008,2111DerivationDescriptionno 
0008,2112SourceImageSequenceno 
0008,2120StageNameno 
0008,2122StageNumberno 
0008,2124NumberOfStagesno 
0008,2127ViewNameno 
0008,2128ViewNumberno 
0008,2129NumberOfEventTimersno 
0008,212ANumberOfViewsInStageno 
0008,2130EventElapsedTimesno 
0008,2132EventTimerNamesno 
0008,2133EventTimerSequenceno 
0008,2134EventTimeOffsetno 
0008,2135EventCodeSequenceno 
0008,2142StartTrimno 
0008,2143StopTrimno 
0008,2144RecommendedDisplayFrameRateno 
0008,2200TransducerPositionno 
0008,2204TransducerOrientationno 
0008,2208AnatomicStructureno 
0008,2218AnatomicRegionSequenceno 
0008,2220AnatomicRegionModifierSequenceno 
0008,2228PrimaryAnatomicStructureSequenceno 
0008,2229AnatomicStructureOrRegionSequenceno 
0008,2230AnatomicStructureModifierSequenceno 
0008,2240TransducerPositionSequenceno 
0008,2242TransducerPositionModifierSequenceno 
0008,2244TransducerOrientationSequenceno 
0008,2246TransducerOrientationModifierSeqno 
0008,2253AnatomicEntrancePortalCodeSeqTrialno 
0008,2255AnatomicApproachDirCodeSeqTrialno 
0008,2256AnatomicPerspectiveDescrTrialno 
0008,2257AnatomicPerspectiveCodeSeqTrialno 
0008,3001AlternateRepresentationSequenceno 
0008,3010IrradiationEventUIDno 
0008,4000IdentifyingCommentsno 
0008,9007FrameTypeno 
0008,9092ReferencedImageEvidenceSequenceno 
0008,9121ReferencedRawDataSequenceno 
0008,9123CreatorVersionUIDno 
0008,9124DerivationImageSequenceno 
0008,9154SourceImageEvidenceSequenceno 
0008,9205PixelPresentationno 
0008,9206VolumetricPropertiesno 
0008,9207VolumeBasedCalculationTechniqueno 
0008,9208ComplexImageComponentno 
0008,9209AcquisitionContrastno 
0008,9215DerivationCodeSequenceno 
0008,9237GrayscalePresentationStateSequenceno 
0008,9410ReferencedOtherPlaneSequenceno 
0008,9458FrameDisplaySequenceno 
0008,9459RecommendedDisplayFrameRateInFloatno 
0008,9460SkipFrameRangeFlagno 
0009,1001FullFidelityno 
0009,1002SuiteIDno 
0009,1004ProductIDno 
0009,1027ImageActualDateno 
0009,1030ServiceIDno 
0009,1031MobileLocationNumberno 
0009,10E3EquipmentUIDno 
0009,10E6GenesisVersionNowno 
0009,10E7ExamRecordChecksumno 
0009,10E9ActualSeriesDataTimeStampno 
0010,0000PatientGroupLengthno 
0010,0010PatientNameno 
0010,0020PatientIDno 
0010,0021IssuerOfPatientIDno 
0010,0022TypeOfPatientIDno 
0010,0030PatientBirthDateno 
0010,0032PatientBirthTimeno 
0010,0040PatientSexno 
0010,0050PatientInsurancePlanCodeSequenceno 
0010,0101PatientPrimaryLanguageCodeSeqno 
0010,0102PatientPrimaryLanguageCodeModSeqno 
0010,1000OtherPatientIDsno 
0010,1001OtherPatientNamesno 
0010,1002OtherPatientIDsSequenceno 
0010,1005PatientBirthNameno 
0010,1010PatientAgeno 
0010,1020PatientSizeno 
0010,1030PatientWeightno 
0010,1040PatientAddressno 
0010,1050InsurancePlanIdentificationno 
0010,1060PatientMotherBirthNameno 
0010,1080MilitaryRankno 
0010,1081BranchOfServiceno 
0010,1090MedicalRecordLocatorno 
0010,2000MedicalAlertsno 
0010,2110Allergiesno 
0010,2150CountryOfResidenceno 
0010,2152RegionOfResidenceno 
0010,2154PatientTelephoneNumbersno 
0010,2160EthnicGroupno 
0010,2180Occupationno 
0010,21A0SmokingStatusno 
0010,21B0AdditionalPatientHistoryno 
0010,21C0PregnancyStatusno 
0010,21D0LastMenstrualDateno 
0010,21F0PatientReligiousPreferenceno 
0010,2201PatientSpeciesDescriptionno 
0010,2202PatientSpeciesCodeSequenceno 
0010,2203PatientSexNeuteredno 
0010,2210AnatomicalOrientationTypeno 
0010,2292PatientBreedDescriptionno 
0010,2293PatientBreedCodeSequenceno 
0010,2294BreedRegistrationSequenceno 
0010,2295BreedRegistrationNumberno 
0010,2296BreedRegistryCodeSequenceno 
0010,2297ResponsiblePersonno 
0010,2298ResponsiblePersonRoleno 
0010,2299ResponsibleOrganizationno 
0010,4000PatientCommentsno 
0010,9431ExaminedBodyThicknessno 
0011,1010PatientStatusno 
0012,0010ClinicalTrialSponsorNameno 
0012,0020ClinicalTrialProtocolIDno 
0012,0021ClinicalTrialProtocolNameno 
0012,0030ClinicalTrialSiteIDno 
0012,0031ClinicalTrialSiteNameno 
0012,0040ClinicalTrialSubjectIDno 
0012,0042ClinicalTrialSubjectReadingIDno 
0012,0050ClinicalTrialTimePointIDno 
0012,0051ClinicalTrialTimePointDescriptionno 
0012,0060ClinicalTrialCoordinatingCenterno 
0012,0062PatientIdentityRemovedno 
0012,0063DeidentificationMethodno 
0012,0064DeidentificationMethodCodeSequenceno 
0012,0071ClinicalTrialSeriesIDno 
0012,0072ClinicalTrialSeriesDescriptionno 
0012,0084DistributionTypeno 
0012,0085ConsentForDistributionFlagno 
0018,0000AcquisitionGroupLengthno 
0018,0010ContrastBolusAgentno 
0018,0012ContrastBolusAgentSequenceno 
0018,0014ContrastBolusAdministrationRouteno 
0018,0015BodyPartExaminedno 
0018,0020ScanningSequenceno 
0018,0021SequenceVariantno 
0018,0022ScanOptionsno 
0018,0023MRAcquisitionTypeno 
0018,0024SequenceNameno 
0018,0025AngioFlagno 
0018,0026InterventionDrugInformationSeqno 
0018,0027InterventionDrugStopTimeno 
0018,0028InterventionDrugDoseno 
0018,0029InterventionDrugSequenceno 
0018,002AAdditionalDrugSequenceno 
0018,0030Radionuclideno 
0018,0031Radiopharmaceuticalno 
0018,0032EnergyWindowCenterlineno 
0018,0033EnergyWindowTotalWidthno 
0018,0034InterventionDrugNameno 
0018,0035InterventionDrugStartTimeno 
0018,0036InterventionSequenceno 
0018,0037TherapyTypeno 
0018,0038InterventionStatusno 
0018,0039TherapyDescriptionno 
0018,003AInterventionDescriptionno 
0018,0040CineRateno 
0018,0042InitialCineRunStateno 
0018,0050SliceThicknessno 
0018,0060KVPno 
0018,0070CountsAccumulatedno 
0018,0071AcquisitionTerminationConditionno 
0018,0072EffectiveDurationno 
0018,0073AcquisitionStartConditionno 
0018,0074AcquisitionStartConditionDatano 
0018,0075AcquisitionEndConditionDatano 
0018,0080RepetitionTimeno 
0018,0081EchoTimeno 
0018,0082InversionTimeno 
0018,0083NumberOfAveragesno 
0018,0084ImagingFrequencyno 
0018,0085ImagedNucleusno 
0018,0086EchoNumberno 
0018,0087MagneticFieldStrengthno 
0018,0088SpacingBetweenSlicesno 
0018,0089NumberOfPhaseEncodingStepsno 
0018,0090DataCollectionDiameterno 
0018,0091EchoTrainLengthno 
0018,0093PercentSamplingno 
0018,0094PercentPhaseFieldOfViewno 
0018,0095PixelBandwidthno 
0018,1000DeviceSerialNumberno 
0018,1002DeviceUIDno 
0018,1003DeviceIDno 
0018,1004PlateIDno 
0018,1005GeneratorIDno 
0018,1006GridIDno 
0018,1007CassetteIDno 
0018,1008GantryIDno 
0018,1010SecondaryCaptureDeviceIDno 
0018,1011HardcopyCreationDeviceIDno 
0018,1012DateOfSecondaryCaptureno 
0018,1014TimeOfSecondaryCaptureno 
0018,1016SecondaryCaptureDeviceManufacturerno 
0018,1017HardcopyDeviceManufacturerno 
0018,1018SecondaryCaptureDeviceModelNameno 
0018,1019SecondaryCaptureDeviceSoftwareVersno 
0018,101AHardcopyDeviceSoftwareVersionno 
0018,101BHardcopyDeviceModelNameno 
0018,1020SoftwareVersionno 
0018,1022VideoImageFormatAcquiredno 
0018,1023DigitalImageFormatAcquiredno 
0018,1030ProtocolNameno 
0018,1040ContrastBolusRouteno 
0018,1041ContrastBolusVolumeno 
0018,1042ContrastBolusStartTimeno 
0018,1043ContrastBolusStopTimeno 
0018,1044ContrastBolusTotalDoseno 
0018,1045SyringeCountsno 
0018,1046ContrastFlowRateno 
0018,1047ContrastFlowDurationno 
0018,1048ContrastBolusIngredientno 
0018,1049ContrastBolusConcentrationno 
0018,1050SpatialResolutionno 
0018,1060TriggerTimeno 
0018,1061TriggerSourceOrTypeno 
0018,1062NominalIntervalno 
0018,1063FrameTimeno 
0018,1064CardiacFramingTypeno 
0018,1065FrameTimeVectorno 
0018,1066FrameDelayno 
0018,1067ImageTriggerDelayno 
0018,1068MultiplexGroupTimeOffsetno 
0018,1069TriggerTimeOffsetno 
0018,106ASynchronizationTriggerno 
0018,106CSynchronizationChannelno 
0018,106ETriggerSamplePositionno 
0018,1070RadiopharmaceuticalRouteno 
0018,1071RadiopharmaceuticalVolumeno 
0018,1072RadiopharmaceuticalStartTimeno 
0018,1073RadiopharmaceuticalStopTimeno 
0018,1074RadionuclideTotalDoseno 
0018,1075RadionuclideHalfLifeno 
0018,1076RadionuclidePositronFractionno 
0018,1077RadiopharmaceuticalSpecActivityno 
0018,1078RadiopharmaceuticalStartDateTimeno 
0018,1079RadiopharmaceuticalStopDateTimeno 
0018,1080BeatRejectionFlagno 
0018,1081LowRRValueno 
0018,1082HighRRValueno 
0018,1083IntervalsAcquiredno 
0018,1084IntervalsRejectedno 
0018,1085PVCRejectionno 
0018,1086SkipBeatsno 
0018,1088HeartRateno 
0018,1090CardiacNumberOfImagesno 
0018,1094TriggerWindowno 
0018,1100ReconstructionDiameterno 
0018,1110DistanceSourceToDetectorno 
0018,1111DistanceSourceToPatientno 
0018,1114EstimatedRadiographicMagnificationno 
0018,1120GantryDetectorTiltno 
0018,1121GantryDetectorSlewno 
0018,1130TableHeightno 
0018,1131TableTraverseno 
0018,1134TableMotionno 
0018,1135TableVerticalIncrementno 
0018,1136TableLateralIncrementno 
0018,1137TableLongitudinalIncrementno 
0018,1138TableAngleno 
0018,113ATableTypeno 
0018,1140RotationDirectionno 
0018,1141AngularPositionno 
0018,1142RadialPositionno 
0018,1143ScanArcno 
0018,1144AngularStepno 
0018,1145CenterOfRotationOffsetno 
0018,1146RotationOffsetno 
0018,1147FieldOfViewShapeno 
0018,1149FieldOfViewDimensionsno 
0018,1150ExposureTimeno 
0018,1151XRayTubeCurrentno 
0018,1152Exposureno 
0018,1153ExposureInMicroAmpSecno 
0018,1154AveragePulseWidthno 
0018,1155RadiationSettingno 
0018,1156RectificationTypeno 
0018,115ARadiationModeno 
0018,115EImageAreaDoseProductno 
0018,1160FilterTypeno 
0018,1161TypeOfFiltersno 
0018,1162IntensifierSizeno 
0018,1164ImagerPixelSpacingno 
0018,1166Gridno 
0018,1170GeneratorPowerno 
0018,1180CollimatorGridNameno 
0018,1181CollimatorTypeno 
0018,1182FocalDistanceno 
0018,1183XFocusCenterno 
0018,1184YFocusCenterno 
0018,1190FocalSpotsno 
0018,1191AnodeTargetMaterialno 
0018,11A0BodyPartThicknessno 
0018,11A2CompressionForceno 
0018,1200DateOfLastCalibrationno 
0018,1201TimeOfLastCalibrationno 
0018,1210ConvolutionKernelno 
0018,1240UpperLowerPixelValuesno 
0018,1242ActualFrameDurationno 
0018,1243CountRateno 
0018,1244PreferredPlaybackSequencingno 
0018,1250ReceiveCoilNameno 
0018,1251TransmitCoilNameno 
0018,1260PlateTypeno 
0018,1261PhosphorTypeno 
0018,1300ScanVelocityno 
0018,1301WholeBodyTechniqueno 
0018,1302ScanLengthno 
0018,1310AcquisitionMatrixno 
0018,1312InPlanePhaseEncodingDirectionno 
0018,1314FlipAngleno 
0018,1315VariableFlipAngleFlagno 
0018,1316SARno 
0018,1318DB-Dtno 
0018,1400AcquisitionDeviceProcessingDescrno 
0018,1401AcquisitionDeviceProcessingCodeno 
0018,1402CassetteOrientationno 
0018,1403CassetteSizeno 
0018,1404ExposuresOnPlateno 
0018,1405RelativeXRayExposureno 
0018,1450ColumnAngulationno 
0018,1460TomoLayerHeightno 
0018,1470TomoAngleno 
0018,1480TomoTimeno 
0018,1490TomoTypeno 
0018,1491TomoClassno 
0018,1495NumberOfTomosynthesisSourceImagesno 
0018,1500PositionerMotionno 
0018,1508PositionerTypeno 
0018,1510PositionerPrimaryAngleno 
0018,1511PositionerSecondaryAngleno 
0018,1520PositionerPrimaryAngleIncrementno 
0018,1521PositionerSecondaryAngleIncrementno 
0018,1530DetectorPrimaryAngleno 
0018,1531DetectorSecondaryAngleno 
0018,1600ShutterShapeno 
0018,1602ShutterLeftVerticalEdgeno 
0018,1604ShutterRightVerticalEdgeno 
0018,1606ShutterUpperHorizontalEdgeno 
0018,1608ShutterLowerHorizontalEdgeno 
0018,1610CenterOfCircularShutterno 
0018,1612RadiusOfCircularShutterno 
0018,1620VerticesOfPolygonalShutterno 
0018,1622ShutterPresentationValueno 
0018,1623ShutterOverlayGroupno 
0018,1624ShutterPresentationColorCIELabValno 
0018,1700CollimatorShapeno 
0018,1702CollimatorLeftVerticalEdgeno 
0018,1704CollimatorRightVerticalEdgeno 
0018,1706CollimatorUpperHorizontalEdgeno 
0018,1708CollimatorLowerHorizontalEdgeno 
0018,1710CenterOfCircularCollimatorno 
0018,1712RadiusOfCircularCollimatorno 
0018,1720VerticesOfPolygonalCollimatorno 
0018,1800AcquisitionTimeSynchronizedno 
0018,1801TimeSourceno 
0018,1802TimeDistributionProtocolno 
0018,1803NTPSourceAddressno 
0018,2001PageNumberVectorno 
0018,2002FrameLabelVectorno 
0018,2003FramePrimaryAngleVectorno 
0018,2004FrameSecondaryAngleVectorno 
0018,2005SliceLocationVectorno 
0018,2006DisplayWindowLabelVectorno 
0018,2010NominalScannedPixelSpacingno 
0018,2020DigitizingDeviceTransportDirectionno 
0018,2030RotationOfScannedFilmno 
0018,3100IVUSAcquisitionno 
0018,3101IVUSPullbackRateno 
0018,3102IVUSGatedRateno 
0018,3103IVUSPullbackStartFrameNumberno 
0018,3104IVUSPullbackStopFrameNumberno 
0018,3105LesionNumberno 
0018,4000AcquisitionCommentsno 
0018,5000OutputPowerno 
0018,5010TransducerDatano 
0018,5012FocusDepthno 
0018,5020ProcessingFunctionno 
0018,5021PostprocessingFunctionno 
0018,5022MechanicalIndexno 
0018,5024BoneThermalIndexno 
0018,5026CranialThermalIndexno 
0018,5027SoftTissueThermalIndexno 
0018,5028SoftTissueFocusThermalIndexno 
0018,5029SoftTissueSurfaceThermalIndexno 
0018,5030DynamicRangeno 
0018,5040TotalGainno 
0018,5050DepthOfScanFieldno 
0018,5100PatientPositionno 
0018,5101ViewPositionno 
0018,5104ProjectionEponymousNameCodeSeqno 
0018,5210ImageTransformationMatrixno 
0018,5212ImageTranslationVectorno 
0018,6000Sensitivityno 
0018,6011SequenceOfUltrasoundRegionsno 
0018,6012RegionSpatialFormatno 
0018,6014RegionDataTypeno 
0018,6016RegionFlagsno 
0018,6018RegionLocationMinX0no 
0018,601ARegionLocationMinY0no 
0018,601CRegionLocationMaxX1no 
0018,601ERegionLocationMaxY1no 
0018,6020ReferencePixelX0no 
0018,6022ReferencePixelY0no 
0018,6024PhysicalUnitsXDirectionno 
0018,6026PhysicalUnitsYDirectionno 
0018,6028ReferencePixelPhysicalValueXno 
0018,602AReferencePixelPhysicalValueYno 
0018,602CPhysicalDeltaXno 
0018,602EPhysicalDeltaYno 
0018,6030TransducerFrequencyno 
0018,6031TransducerTypeno 
0018,6032PulseRepetitionFrequencyno 
0018,6034DopplerCorrectionAngleno 
0018,6036SteeringAngleno 
0018,6038DopplerSampleVolumeXPosRetiredno 
0018,6039DopplerSampleVolumeXPositionno 
0018,603ADopplerSampleVolumeYPosRetiredno 
0018,603BDopplerSampleVolumeYPositionno 
0018,603CTMLinePositionX0Retiredno 
0018,603DTMLinePositionX0no 
0018,603ETMLinePositionY0Retiredno 
0018,603FTMLinePositionY0no 
0018,6040TMLinePositionX1Retiredno 
0018,6041TMLinePositionX1no 
0018,6042TMLinePositionY1Retiredno 
0018,6043TMLinePositionY1no 
0018,6044PixelComponentOrganizationno 
0018,6046PixelComponentMaskno 
0018,6048PixelComponentRangeStartno 
0018,604APixelComponentRangeStopno 
0018,604CPixelComponentPhysicalUnitsno 
0018,604EPixelComponentDataTypeno 
0018,6050NumberOfTableBreakPointsno 
0018,6052TableOfXBreakPointsno 
0018,6054TableOfYBreakPointsno 
0018,6056NumberOfTableEntriesno 
0018,6058TableOfPixelValuesno 
0018,605ATableOfParameterValuesno 
0018,6060RWaveTimeVectorno 
0018,7000DetectorConditionsNominalFlagno 
0018,7001DetectorTemperatureno 
0018,7004DetectorTypeno 
0018,7005DetectorConfigurationno 
0018,7006DetectorDescriptionno 
0018,7008DetectorModeno 
0018,700ADetectorIDno 
0018,700CDateOfLastDetectorCalibrationno 
0018,700ETimeOfLastDetectorCalibrationno 
0018,7010DetectorExposuresSinceCalibrationno 
0018,7011DetectorExposuresSinceManufacturedno 
0018,7012DetectorTimeSinceLastExposureno 
0018,7014DetectorActiveTimeno 
0018,7016DetectorActiveOffsetFromExposureno 
0018,701ADetectorBinningno 
0018,7020DetectorElementPhysicalSizeno 
0018,7022DetectorElementSpacingno 
0018,7024DetectorActiveShapeno 
0018,7026DetectorActiveDimensionsno 
0018,7028DetectorActiveOriginno 
0018,702ADetectorManufacturerNameno 
0018,702BDetectorManufacturersModelNameno 
0018,7030FieldOfViewOriginno 
0018,7032FieldOfViewRotationno 
0018,7034FieldOfViewHorizontalFlipno 
0018,7040GridAbsorbingMaterialno 
0018,7041GridSpacingMaterialno 
0018,7042GridThicknessno 
0018,7044GridPitchno 
0018,7046GridAspectRationo 
0018,7048GridPeriodno 
0018,704CGridFocalDistanceno 
0018,7050FilterMaterialno 
0018,7052FilterThicknessMinimumno 
0018,7054FilterThicknessMaximumno 
0018,7060ExposureControlModeno 
0018,7062ExposureControlModeDescriptionno 
0018,7064ExposureStatusno 
0018,7065PhototimerSettingno 
0018,8150ExposureTimeInMicroSecno 
0018,8151XRayTubeCurrentInMicroAmpsno 
0018,9004ContentQualificationno 
0018,9005PulseSequenceNameno 
0018,9006MRImagingModifierSequenceno 
0018,9008EchoPulseSequenceno 
0018,9009InversionRecoveryno 
0018,9010FlowCompensationno 
0018,9011MultipleSpinEchono 
0018,9012MultiPlanarExcitationno 
0018,9014PhaseContrastno 
0018,9015TimeOfFlightContrastno 
0018,9016Spoilingno 
0018,9017SteadyStatePulseSequenceno 
0018,9018EchoPlanarPulseSequenceno 
0018,9019TagAngleFirstAxisno 
0018,9020MagnetizationTransferno 
0018,9021T2Preparationno 
0018,9022BloodSignalNullingno 
0018,9024SaturationRecoveryno 
0018,9025SpectrallySelectedSuppressionno 
0018,9026SpectrallySelectedExcitationno 
0018,9027SpatialPresaturationno 
0018,9028Taggingno 
0018,9029OversamplingPhaseno 
0018,9030TagSpacingFirstDimensionno 
0018,9032GeometryOfKSpaceTraversalno 
0018,9033SegmentedKSpaceTraversalno 
0018,9034RectilinearPhaseEncodeReorderingno 
0018,9035TagThicknessno 
0018,9036PartialFourierDirectionno 
0018,9037CardiacSynchronizationTechniqueno 
0018,9041ReceiveCoilManufacturerNameno 
0018,9042MRReceiveCoilSequenceno 
0018,9043ReceiveCoilTypeno 
0018,9044QuadratureReceiveCoilno 
0018,9045MultiCoilDefinitionSequenceno 
0018,9046MultiCoilConfigurationno 
0018,9047MultiCoilElementNameno 
0018,9048MultiCoilElementUsedno 
0018,9049MRTransmitCoilSequenceno 
0018,9050TransmitCoilManufacturerNameno 
0018,9051TransmitCoilTypeno 
0018,9052SpectralWidthno 
0018,9053ChemicalShiftReferenceno 
0018,9054VolumeLocalizationTechniqueno 
0018,9058MRAcquisitionFrequencyEncodeStepsno 
0018,9059Decouplingno 
0018,9060DecoupledNucleusno 
0018,9061DecouplingFrequencyno 
0018,9062DecouplingMethodno 
0018,9063DecouplingChemicalShiftReferenceno 
0018,9064KSpaceFilteringno 
0018,9065TimeDomainFilteringno 
0018,9066NumberOfZeroFillsno 
0018,9067BaselineCorrectionno 
0018,9069ParallelReductionFactorInPlaneno 
0018,9070CardiacRRIntervalSpecifiedno 
0018,9073AcquisitionDurationno 
0018,9074FrameAcquisitionDateTimeno 
0018,9075DiffusionDirectionalityno 
0018,9076DiffusionGradientDirectionSequenceno 
0018,9077ParallelAcquisitionno 
0018,9078ParallelAcquisitionTechniqueno 
0018,9079InversionTimesno 
0018,9080MetaboliteMapDescriptionno 
0018,9081PartialFourierno 
0018,9082EffectiveEchoTimeno 
0018,9083MetaboliteMapCodeSequenceno 
0018,9084ChemicalShiftSequenceno 
0018,9085CardiacSignalSourceno 
0018,9087DiffusionBValueno 
0018,9089DiffusionGradientOrientationno 
0018,9090VelocityEncodingDirectionno 
0018,9091VelocityEncodingMinimumValueno 
0018,9093NumberOfKSpaceTrajectoriesno 
0018,9094CoverageOfKSpaceno 
0018,9095SpectroscopyAcquisitionPhaseRowsno 
0018,9096ParallelReductFactorInPlaneRetiredno 
0018,9098TransmitterFrequencyno 
0018,9100ResonantNucleusno 
0018,9101FrequencyCorrectionno 
0018,9103MRSpectroscopyFOV-GeometrySequenceno 
0018,9104SlabThicknessno 
0018,9105SlabOrientationno 
0018,9106MidSlabPositionno 
0018,9107MRSpatialSaturationSequenceno 
0018,9112MRTimingAndRelatedParametersSeqno 
0018,9114MREchoSequenceno 
0018,9115MRModifierSequenceno 
0018,9117MRDiffusionSequenceno 
0018,9118CardiacTriggerSequenceno 
0018,9119MRAveragesSequenceno 
0018,9125MRFOV-GeometrySequenceno 
0018,9126VolumeLocalizationSequenceno 
0018,9127SpectroscopyAcquisitionDataColumnsno 
0018,9147DiffusionAnisotropyTypeno 
0018,9151FrameReferenceDateTimeno 
0018,9152MRMetaboliteMapSequenceno 
0018,9155ParallelReductionFactorOutOfPlaneno 
0018,9159SpectroscopyOutOfPlanePhaseStepsno 
0018,9166BulkMotionStatusno 
0018,9168ParallelReductionFactSecondInPlaneno 
0018,9169CardiacBeatRejectionTechniqueno 
0018,9170RespiratoryMotionCompTechniqueno 
0018,9171RespiratorySignalSourceno 
0018,9172BulkMotionCompensationTechniqueno 
0018,9173BulkMotionSignalSourceno 
0018,9174ApplicableSafetyStandardAgencyno 
0018,9175ApplicableSafetyStandardDescrno 
0018,9176OperatingModeSequenceno 
0018,9177OperatingModeTypeno 
0018,9178OperatingModeno 
0018,9179SpecificAbsorptionRateDefinitionno 
0018,9180GradientOutputTypeno 
0018,9181SpecificAbsorptionRateValueno 
0018,9182GradientOutputno 
0018,9183FlowCompensationDirectionno 
0018,9184TaggingDelayno 
0018,9185RespiratoryMotionCompTechDescrno 
0018,9186RespiratorySignalSourceIDno 
0018,9195ChemicalShiftsMinIntegrateLimitHzno 
0018,9196ChemicalShiftsMaxIntegrateLimitHzno 
0018,9197MRVelocityEncodingSequenceno 
0018,9198FirstOrderPhaseCorrectionno 
0018,9199WaterReferencedPhaseCorrectionno 
0018,9200MRSpectroscopyAcquisitionTypeno 
0018,9214RespiratoryCyclePositionno 
0018,9217VelocityEncodingMaximumValueno 
0018,9218TagSpacingSecondDimensionno 
0018,9219TagAngleSecondAxisno 
0018,9220FrameAcquisitionDurationno 
0018,9226MRImageFrameTypeSequenceno 
0018,9227MRSpectroscopyFrameTypeSequenceno 
0018,9231MRAcqPhaseEncodingStepsInPlaneno 
0018,9232MRAcqPhaseEncodingStepsOutOfPlaneno 
0018,9234SpectroscopyAcqPhaseColumnsno 
0018,9236CardiacCyclePositionno 
0018,9239SpecificAbsorptionRateSequenceno 
0018,9240RFEchoTrainLengthno 
0018,9241GradientEchoTrainLengthno 
0018,9295ChemicalShiftsMinIntegrateLimitPPMno 
0018,9296ChemicalShiftsMaxIntegrateLimitPPMno 
0018,9301CTAcquisitionTypeSequenceno 
0018,9302AcquisitionTypeno 
0018,9303TubeAngleno 
0018,9304CTAcquisitionDetailsSequenceno 
0018,9305RevolutionTimeno 
0018,9306SingleCollimationWidthno 
0018,9307TotalCollimationWidthno 
0018,9308CTTableDynamicsSequenceno 
0018,9309TableSpeedno 
0018,9310TableFeedPerRotationno 
0018,9311SpiralPitchFactorno 
0018,9312CTGeometrySequenceno 
0018,9313DataCollectionCenterPatientno 
0018,9314CTReconstructionSequenceno 
0018,9315ReconstructionAlgorithmno 
0018,9316ConvolutionKernelGroupno 
0018,9317ReconstructionFieldOfViewno 
0018,9318ReconstructionTargetCenterPatientno 
0018,9319ReconstructionAngleno 
0018,9320ImageFilterno 
0018,9321CTExposureSequenceno 
0018,9322ReconstructionPixelSpacingno 
0018,9323ExposureModulationTypeno 
0018,9324EstimatedDoseSavingno 
0018,9325CTXRayDetailsSequenceno 
0018,9326CTPositionSequenceno 
0018,9327TablePositionno 
0018,9328ExposureTimeInMilliSecno 
0018,9329CTImageFrameTypeSequenceno 
0018,9330XRayTubeCurrentInMilliAmpsno 
0018,9332ExposureInMilliAmpSecno 
0018,9333ConstantVolumeFlagno 
0018,9334FluoroscopyFlagno 
0018,9335SourceToDataCollectionCenterDistno 
0018,9337ContrastBolusAgentNumberno 
0018,9338ContrastBolusIngredientCodeSeqno 
0018,9340ContrastAdministrationProfileSeqno 
0018,9341ContrastBolusUsageSequenceno 
0018,9342ContrastBolusAgentAdministeredno 
0018,9343ContrastBolusAgentDetectedno 
0018,9344ContrastBolusAgentPhaseno 
0018,9345CTDIvolno 
0018,9346CTDIPhantomTypeCodeSequenceno 
0018,9351CalciumScoringMassFactorPatientno 
0018,9352CalciumScoringMassFactorDeviceno 
0018,9353EnergyWeightingFactorno 
0018,9360CTAdditionalXRaySourceSequenceno 
0018,9401ProjectionPixelCalibrationSequenceno 
0018,9402DistanceSourceToIsocenterno 
0018,9403DistanceObjectToTableTopno 
0018,9404ObjectPixelSpacingInCenterOfBeamno 
0018,9405PositionerPositionSequenceno 
0018,9406TablePositionSequenceno 
0018,9407CollimatorShapeSequenceno 
0018,9412XA-XRFFrameCharacteristicsSequenceno 
0018,9417FrameAcquisitionSequenceno 
0018,9420XRayReceptorTypeno 
0018,9423AcquisitionProtocolNameno 
0018,9424AcquisitionProtocolDescriptionno 
0018,9425ContrastBolusIngredientOpaqueno 
0018,9426DistanceReceptorPlaneToDetHousingno 
0018,9427IntensifierActiveShapeno 
0018,9428IntensifierActiveDimensionsno 
0018,9429PhysicalDetectorSizeno 
0018,9430PositionOfIsocenterProjectionno 
0018,9432FieldOfViewSequenceno 
0018,9433FieldOfViewDescriptionno 
0018,9434ExposureControlSensingRegionsSeqno 
0018,9435ExposureControlSensingRegionShapeno 
0018,9436ExposureControlSensRegionLeftEdgeno 
0018,9437ExposureControlSensRegionRightEdgeno 
0018,9440CenterOfCircExposControlSensRegionno 
0018,9441RadiusOfCircExposControlSensRegionno 
0018,9447ColumnAngulationPatientno 
0018,9449BeamAngleno 
0018,9451FrameDetectorParametersSequenceno 
0018,9452CalculatedAnatomyThicknessno 
0018,9455CalibrationSequenceno 
0018,9456ObjectThicknessSequenceno 
0018,9457PlaneIdentificationno 
0018,9461FieldOfViewDimensionsInFloatno 
0018,9462IsocenterReferenceSystemSequenceno 
0018,9463PositionerIsocenterPrimaryAngleno 
0018,9464PositionerIsocenterSecondaryAngleno 
0018,9465PositionerIsocenterDetRotAngleno 
0018,9466TableXPositionToIsocenterno 
0018,9467TableYPositionToIsocenterno 
0018,9468TableZPositionToIsocenterno 
0018,9469TableHorizontalRotationAngleno 
0018,9470TableHeadTiltAngleno 
0018,9471TableCradleTiltAngleno 
0018,9472FrameDisplayShutterSequenceno 
0018,9473AcquiredImageAreaDoseProductno 
0018,9474CArmPositionerTabletopRelationshipno 
0018,9476XRayGeometrySequenceno 
0018,9477IrradiationEventIDSequenceno 
0018,9504XRay3DFrameTypeSequenceno 
0018,9506ContributingSourcesSequenceno 
0018,9507XRay3DAcquisitionSequenceno 
0018,9508PrimaryPositionerScanArcno 
0018,9509SecondaryPositionerScanArcno 
0018,9510PrimaryPositionerScanStartAngleno 
0018,9511SecondaryPositionerScanStartAngleno 
0018,9514PrimaryPositionerIncrementno 
0018,9515SecondaryPositionerIncrementno 
0018,9516StartAcquisitionDateTimeno 
0018,9517EndAcquisitionDateTimeno 
0018,9524ApplicationNameno 
0018,9525ApplicationVersionno 
0018,9526ApplicationManufacturerno 
0018,9527AlgorithmTypeno 
0018,9528AlgorithmDescriptionno 
0018,9530XRay3DReconstructionSequenceno 
0018,9531ReconstructionDescriptionno 
0018,9538PerProjectionAcquisitionSequenceno 
0018,9601DiffusionBMatrixSequenceno 
0018,9602DiffusionBValueXXno 
0018,9603DiffusionBValueXYno 
0018,9604DiffusionBValueXZno 
0018,9605DiffusionBValueYYno 
0018,9606DiffusionBValueYZno 
0018,9607DiffusionBValueZZno 
0018,9701DecayCorrectionDateTimeno 
0018,9715StartDensityThresholdno 
0018,9722TerminationTimeThresholdno 
0018,9725DetectorGeometryno 
0018,9727AxialDetectorDimensionno 
0018,9735PETPositionSequenceno 
0018,9739NumberOfIterationsno 
0018,9740NumberOfSubsetsno 
0018,9751PETFrameTypeSequenceno 
0018,9756ReconstructionTypeno 
0018,9758DecayCorrectedno 
0018,9759AttenuationCorrectedno 
0018,9760ScatterCorrectedno 
0018,9761DeadTimeCorrectedno 
0018,9762GantryMotionCorrectedno 
0018,9763PatientMotionCorrectedno 
0018,9765RandomsCorrectedno 
0018,9767SensitivityCalibratedno 
0018,9801DepthsOfFocusno 
0018,9804ExclusionStartDatetimeno 
0018,9805ExclusionDurationno 
0018,9807ImageDataTypeSequenceno 
0018,9808DataTypeno 
0018,980BAliasedDataTypeno 
0018,A001ContributingEquipmentSequenceno 
0018,A002ContributionDateTimeno 
0018,A003ContributionDescriptionno 
0019,1002NumberOfCellsIInDetectorno 
0019,1003CellNumberAtThetano 
0019,1004CellSpacingno 
0019,100FHorizFrameOfRefno 
0019,1011SeriesContrastno 
0019,1012LastPseqno 
0019,1013StartNumberForBaselineno 
0019,1014EndNumberForBaselineno 
0019,1015StartNumberForEnhancedScansno 
0019,1016EndNumberForEnhancedScansno 
0019,1017SeriesPlaneno 
0019,1018FirstScanRasno 
0019,1019FirstScanLocationno 
0019,101ALastScanRasno 
0019,101BLastScanLocno 
0019,101EDisplayFieldOfViewno 
0019,1023TableSpeedno 
0019,1024MidScanTimeno 
0019,1025MidScanFlagno 
0019,1026DegreesOfAzimuthno 
0019,1027GantryPeriodno 
0019,102AXRayOnPositionno 
0019,102BXRayOffPositionno 
0019,102CNumberOfTriggersno 
0019,102EAngleOfFirstViewno 
0019,102FTriggerFrequencyno 
0019,1039ScanFOVTypeno 
0019,1040StatReconFlagno 
0019,1041ComputeTypeno 
0019,1042SegmentNumberno 
0019,1043TotalSegmentsRequestedno 
0019,1044InterscanDelayno 
0019,1047ViewCompressionFactorno 
0019,104ATotalNoOfRefChannelsno 
0019,104BDataSizeForScanDatano 
0019,1052ReconPostProcflagno 
0019,1057CTWaterNumberno 
0019,1058CTBoneNumberno 
0019,105AAcquisitionDurationno 
0019,105ENumberOfChannelsno 
0019,105FIncrementBetweenChannelsno 
0019,1060StartingViewno 
0019,1061NumberOfViewsno 
0019,1062IncrementBetweenViewsno 
0019,106ADependantOnNoViewsProcessedno 
0019,106BFieldOfViewInDetectorCellsno 
0019,1070ValueOfBackProjectionButtonno 
0019,1071SetIfFatqEstimatesWereUsedno 
0019,1072ZChanAvgOverViewsno 
0019,1073AvgOfLeftRefChansOverViewsno 
0019,1074MaxLeftChanOverViewsno 
0019,1075AvgOfRightRefChansOverViewsno 
0019,1076MaxRightChanOverViewsno 
0019,107DSecondEchono 
0019,107ENumberOfEchoesno 
0019,107FTableDeltano 
0019,1081Contiguousno 
0019,1084PeakSARno 
0019,1085MonitorSARno 
0019,1087CardiacRepetitionTimeno 
0019,1088ImagesPerCardiacCycleno 
0019,108AActualReceiveGainAnalogno 
0019,108BActualReceiveGainDigitalno 
0019,108DDelayAfterTriggerno 
0019,108FSwappfno 
0019,1090PauseIntervalno 
0019,1091PulseTimeno 
0019,1092SliceOffsetOnFreqAxisno 
0019,1093CenterFrequencyno 
0019,1094TransmitGainno 
0019,1095AnalogReceiverGainno 
0019,1096DigitalReceiverGainno 
0019,1097BitmapDefiningCVsno 
0019,1098CenterFreqMethodno 
0019,109BPulseSeqModeno 
0019,109CPulseSeqNameno 
0019,109DPulseSeqDateno 
0019,109EInternalPulseSeqNameno 
0019,109FTransmittingCoilno 
0019,10A0SurfaceCoilTypeno 
0019,10A1ExtremityCoilFlagno 
0019,10A2RawDataRunNumberno 
0019,10A3CalibratedFieldStrengthno 
0019,10A4SATFatWaterBoneno 
0019,10A5ReceiveBandwidthno 
0019,10A7UserData01no 
0019,10A8UserData02no 
0019,10A9UserData03no 
0019,10AAUserData04no 
0019,10ABUserData05no 
0019,10ACUserData06no 
0019,10ADUserData07no 
0019,10AEUserData08no 
0019,10AFUserData09no 
0019,10B0UserData10no 
0019,10B1UserData11no 
0019,10B2UserData12no 
0019,10B3UserData13no 
0019,10B4UserData14no 
0019,10B5UserData15no 
0019,10B6UserData16no 
0019,10B7UserData17no 
0019,10B8UserData18no 
0019,10B9UserData19no 
0019,10BAUserData20no 
0019,10BBUserData21no 
0019,10BCUserData22no 
0019,10BDUserData23no 
0019,10BEProjectionAngleno 
0019,10C0SaturationPlanesno 
0019,10C1SurfaceCoilIntensityno 
0019,10C2SATLocationRno 
0019,10C3SATLocationLno 
0019,10C4SATLocationAno 
0019,10C5SATLocationPno 
0019,10C6SATLocationHno 
0019,10C7SATLocationFno 
0019,10C8SATThicknessR-Lno 
0019,10C9SATThicknessA-Pno 
0019,10CASATThicknessH-Fno 
0019,10CBPrescribedFlowAxisno 
0019,10CCVelocityEncodingno 
0019,10CDThicknessDisclaimerno 
0019,10CEPrescanTypeno 
0019,10CFPrescanStatusno 
0019,10D0RawDataTypeno 
0019,10D2ProjectionAlgorithmno 
0019,10D3ProjectionAlgorithmno 
0019,10D5FractionalEchono 
0019,10D6PrepPulseno 
0019,10D7CardiacPhasesno 
0019,10D8VariableEchoflagno 
0019,10D9ConcatenatedSATno 
0019,10DAReferenceChannelUsedno 
0019,10DBBackProjectorCoefficientno 
0019,10DCPrimarySpeedCorrectionUsedno 
0019,10DDOverrangeCorrectionUsedno 
0019,10DEDynamicZAlphaValueno 
0019,10DFUserDatano 
0019,10E0UserDatano 
0019,10E2VelocityEncodeScaleno 
0019,10F2FastPhasesno 
0019,10F9TransmissionGainno 
0020,0000RelationshipGroupLengthno 
0020,000DStudyInstanceUIDno 
0020,000ESeriesInstanceUIDno 
0020,0010StudyIDno 
0020,0011SeriesNumberno 
0020,0012AcquisitionNumberno 
0020,0013InstanceNumberno 
0020,0014IsotopeNumberno 
0020,0015PhaseNumberno 
0020,0016IntervalNumberno 
0020,0017TimeSlotNumberno 
0020,0018AngleNumberno 
0020,0019ItemNumberno 
0020,0020PatientOrientationno 
0020,0022OverlayNumberno 
0020,0024CurveNumberno 
0020,0026LookupTableNumberno 
0020,0030ImagePositionno 
0020,0032ImagePositionPatientno 
0020,0035ImageOrientationno 
0020,0037ImageOrientationPatientno 
0020,0050Locationno 
0020,0052FrameOfReferenceUIDno 
0020,0060Lateralityno 
0020,0062ImageLateralityno 
0020,0070ImageGeometryTypeno 
0020,0080MaskingImageno 
0020,0100TemporalPositionIdentifierno 
0020,0105NumberOfTemporalPositionsno 
0020,0110TemporalResolutionno 
0020,0200SynchronizationFrameOfReferenceUIDno 
0020,1000SeriesInStudyno 
0020,1001AcquisitionsInSeriesno 
0020,1002ImagesInAcquisitionno 
0020,1003ImagesInSeriesno 
0020,1004AcquisitionsInStudyno 
0020,1005ImagesInStudyno 
0020,1020Referenceno 
0020,1040PositionReferenceIndicatorno 
0020,1041SliceLocationno 
0020,1070OtherStudyNumbersno 
0020,1200NumberOfPatientRelatedStudiesno 
0020,1202NumberOfPatientRelatedSeriesno 
0020,1204NumberOfPatientRelatedInstancesno 
0020,1206NumberOfStudyRelatedSeriesno 
0020,1208NumberOfStudyRelatedInstancesno 
0020,1209NumberOfSeriesRelatedInstancesno 
0020,31xxSourceImageIDsno 
0020,3401ModifyingDeviceIDno 
0020,3402ModifiedImageIDno 
0020,3403ModifiedImageDateno 
0020,3404ModifyingDeviceManufacturerno 
0020,3405ModifiedImageTimeno 
0020,3406ModifiedImageDescriptionno 
0020,4000ImageCommentsno 
0020,5000OriginalImageIdentificationno 
0020,5002OriginalImageIdentNomenclatureno 
0020,9056StackIDno 
0020,9057InStackPositionNumberno 
0020,9071FrameAnatomySequenceno 
0020,9072FrameLateralityno 
0020,9111FrameContentSequenceno 
0020,9113PlanePositionSequenceno 
0020,9116PlaneOrientationSequenceno 
0020,9128TemporalPositionIndexno 
0020,9153TriggerDelayTimeno 
0020,9156FrameAcquisitionNumberno 
0020,9157DimensionIndexValuesno 
0020,9158FrameCommentsno 
0020,9161ConcatenationUIDno 
0020,9162InConcatenationNumberno 
0020,9163InConcatenationTotalNumberno 
0020,9164DimensionOrganizationUIDno 
0020,9165DimensionIndexPointerno 
0020,9167FunctionalGroupPointerno 
0020,9213DimensionIndexPrivateCreatorno 
0020,9221DimensionOrganizationSequenceno 
0020,9222DimensionIndexSequenceno 
0020,9228ConcatenationFrameOffsetNumberno 
0020,9238FunctionalGroupPrivateCreatorno 
0020,9241NominalPercentageOfCardiacPhaseno 
0020,9245NominalPercentOfRespiratoryPhaseno 
0020,9246StartingRespiratoryAmplitudeno 
0020,9247StartingRespiratoryPhaseno 
0020,9248EndingRespiratoryAmplitudeno 
0020,9249EndingRespiratoryPhaseno 
0020,9250RespiratoryTriggerTypeno 
0020,9251RRIntervalTimeNominalno 
0020,9252ActualCardiacTriggerDelayTimeno 
0020,9253RespiratorySynchronizationSequenceno 
0020,9254RespiratoryIntervalTimeno 
0020,9255NominalRespiratoryTriggerDelayTimeno 
0020,9256RespiratoryTriggerDelayThresholdno 
0020,9257ActualRespiratoryTriggerDelayTimeno 
0020,9301ImagePositionVolumeno 
0020,9302ImageOrientationVolumeno 
0020,9308ApexPositionno 
0020,9421DimensionDescriptionLabelno 
0020,9450PatientOrientationInFrameSequenceno 
0020,9453FrameLabelno 
0020,9518AcquisitionIndexno 
0020,9529ContributingSOPInstancesRefSeqno 
0020,9536ReconstructionIndexno 
0021,1003SeriesFromWhichPrescribedno 
0021,1005GenesisVersionNowno 
0021,1007SeriesRecordChecksumno 
0021,1018GenesisVersionNowno 
0021,1019AcqreconRecordChecksumno 
0021,1020TableStartLocationno 
0021,1035SeriesFromWhichPrescribedno 
0021,1036ImageFromWhichPrescribedno 
0021,1037ScreenFormatno 
0021,104AAnatomicalReferenceForScoutno 
0021,104FLocationsInAcquisitionno 
0021,1050GraphicallyPrescribedno 
0021,1051RotationFromSourceXRotno 
0021,1052RotationFromSourceYRotno 
0021,1053RotationFromSourceZRotno 
0021,1054ImagePositionno 
0021,1055ImageOrientationno 
0021,1056IntegerSlopno 
0021,1057IntegerSlopno 
0021,1058IntegerSlopno 
0021,1059IntegerSlopno 
0021,105AIntegerSlopno 
0021,105BFloatSlopno 
0021,105CFloatSlopno 
0021,105DFloatSlopno 
0021,105EFloatSlopno 
0021,105FFloatSlopno 
0021,1081AutoWindowLevelAlphano 
0021,1082AutoWindowLevelBetano 
0021,1083AutoWindowLevelWindowno 
0021,1084ToWindowLevelLevelno 
0021,1090TubeFocalSpotPositionno 
0021,1091BiopsyPositionno 
0021,1092BiopsyTLocationno 
0021,1093BiopsyRefLocationno 
0022,0001LightPathFilterPassThroughWavelenno 
0022,0002LightPathFilterPassBandno 
0022,0003ImagePathFilterPassThroughWavelenno 
0022,0004ImagePathFilterPassBandno 
0022,0005PatientEyeMovementCommandedno 
0022,0006PatientEyeMovementCommandCodeSeqno 
0022,0007SphericalLensPowerno 
0022,0008CylinderLensPowerno 
0022,0009CylinderAxisno 
0022,000AEmmetropicMagnificationno 
0022,000BIntraOcularPressureno 
0022,000CHorizontalFieldOfViewno 
0022,000DPupilDilatedno 
0022,000EDegreeOfDilationno 
0022,0010StereoBaselineAngleno 
0022,0011StereoBaselineDisplacementno 
0022,0012StereoHorizontalPixelOffsetno 
0022,0013StereoVerticalPixelOffsetno 
0022,0014StereoRotationno 
0022,0015AcquisitionDeviceTypeCodeSequenceno 
0022,0016IlluminationTypeCodeSequenceno 
0022,0017LightPathFilterTypeStackCodeSeqno 
0022,0018ImagePathFilterTypeStackCodeSeqno 
0022,0019LensesCodeSequenceno 
0022,001AChannelDescriptionCodeSequenceno 
0022,001BRefractiveStateSequenceno 
0022,001CMydriaticAgentCodeSequenceno 
0022,001DRelativeImagePositionCodeSequenceno 
0022,0020StereoPairsSequenceno 
0022,0021LeftImageSequenceno 
0022,0022RightImageSequenceno 
0022,0030AxialLengthOfTheEyeno 
0022,0031OphthalmicFrameLocationSequenceno 
0022,0032ReferenceCoordinatesno 
0022,0035DepthSpatialResolutionno 
0022,0036MaximumDepthDistortionno 
0022,0037AlongScanSpatialResolutionno 
0022,0038MaximumAlongScanDistortionno 
0022,0039OphthalmicImageOrientationno 
0022,0041DepthOfTransverseImageno 
0022,0042MydriaticAgentConcUnitsSeqno 
0022,0048AcrossScanSpatialResolutionno 
0022,0049MaximumAcrossScanDistortionno 
0022,004EMydriaticAgentConcentrationno 
0022,0055IlluminationWaveLengthno 
0022,0056IlluminationPowerno 
0022,0057IlluminationBandwidthno 
0022,0058MydriaticAgentSequenceno 
0023,1001NumberOfSeriesInStudyno 
0023,1002NumberOfUnarchivedSeriesno 
0023,1010ReferenceImageFieldno 
0023,1050SummaryImageno 
0023,1070StartTimeSecsInFirstAxialno 
0023,1074NoofUpdatesToHeaderno 
0023,107DIndicatesIfTheStudyHasCompleteInfono 
0025,1006LastPulseSequenceUsedno 
0025,1007ImagesInSeriesno 
0025,1010LandmarkCounterno 
0025,1011NumberOfAcquisitionsno 
0025,1014IndicatesNoofUpdatesToHeaderno 
0025,1017SeriesCompleteFlagno 
0025,1018NumberOfImagesArchivedno 
0025,1019LastImageNumberUsedno 
0025,101APrimaryReceiverSuiteAndHostno 
0027,1006ImageArchiveFlagno 
0027,1010ScoutTypeno 
0027,101CVmaMampno 
0027,101DVmaPhaseno 
0027,101EVmaModno 
0027,101FVmaClipno 
0027,1020SmartScanOnOffFlagno 
0027,1030ForeignImageRevisionno 
0027,1031ImagingModeno 
0027,1032PulseSequenceno 
0027,1033ImagingOptionsno 
0027,1035PlaneTypeno 
0027,1036ObliquePlaneno 
0027,1040RASLetterOfImageLocationno 
0027,1041ImageLocationno 
0027,1042CenterRCoordOfPlaneImageno 
0027,1043CenterACoordOfPlaneImageno 
0027,1044CenterSCoordOfPlaneImageno 
0027,1045NormalRCoordno 
0027,1046NormalACoordno 
0027,1047NormalSCoordno 
0027,1048RCoordOfTopRightCornerno 
0027,1049ACoordOfTopRightCornerno 
0027,104ASCoordOfTopRightCornerno 
0027,104BRCoordOfBottomRightCornerno 
0027,104CACoordOfBottomRightCornerno 
0027,104DSCoordOfBottomRightCornerno 
0027,1050TableStartLocationno 
0027,1051TableEndLocationno 
0027,1052RASLetterForSideOfImageno 
0027,1053RASLetterForAnteriorPosteriorno 
0027,1054RASLetterForScoutStartLocno 
0027,1055RASLetterForScoutEndLocno 
0027,1060ImageDimensionXno 
0027,1061ImageDimensionYno 
0027,1062NumberOfExcitationsno 
0028,0000ImagePresentationGroupLengthno 
0028,0002SamplesPerPixelno 
0028,0003SamplesPerPixelUsedno 
0028,0004PhotometricInterpretationno 
0028,0005ImageDimensionsno 
0028,0006PlanarConfigurationno 
0028,0008NumberOfFramesno 
0028,0009FrameIncrementPointerno 
0028,000AFrameDimensionPointerno 
0028,0010Rowsno 
0028,0011Columnsno 
0028,0012Planesno 
0028,0014UltrasoundColorDataPresentno 
0028,0030PixelSpacingno 
0028,0031ZoomFactorno 
0028,0032ZoomCenterno 
0028,0034PixelAspectRationo 
0028,0040ImageFormatno 
0028,0050ManipulatedImageno 
0028,0051CorrectedImageno 
0028,005FCompressionRecognitionCodeno 
0028,0060CompressionCodeno 
0028,0061CompressionOriginatorno 
0028,0062CompressionLabelno 
0028,0063CompressionDescriptionno 
0028,0065CompressionSequenceno 
0028,0066CompressionStepPointersno 
0028,0068RepeatIntervalno 
0028,0069BitsGroupedno 
0028,0070PerimeterTableno 
0028,0071PerimeterValueno 
0028,0080PredictorRowsno 
0028,0081PredictorColumnsno 
0028,0082PredictorConstantsno 
0028,0090BlockedPixelsno 
0028,0091BlockRowsno 
0028,0092BlockColumnsno 
0028,0093RowOverlapno 
0028,0094ColumnOverlapno 
0028,0100BitsAllocatedno 
0028,0101BitsStoredno 
0028,0102HighBitno 
0028,0103PixelRepresentationno0 = Unsigned +
1 = Signed
0028,0104SmallestValidPixelValueno 
0028,0105LargestValidPixelValueno 
0028,0106SmallestImagePixelValueno 
0028,0107LargestImagePixelValueno 
0028,0108SmallestPixelValueInSeriesno 
0028,0109LargestPixelValueInSeriesno 
0028,0110SmallestImagePixelValueInPlaneno 
0028,0111LargestImagePixelValueInPlaneno 
0028,0120PixelPaddingValueno 
0028,0121PixelPaddingRangeLimitno 
0028,0200ImageLocationno 
0028,0300QualityControlImageno 
0028,0301BurnedInAnnotationno 
0028,0400TransformLabelno 
0028,0401TransformVersionNumberno 
0028,0402NumberOfTransformStepsno 
0028,0403SequenceOfCompressedDatano 
0028,0404DetailsOfCoefficientsno 
0028,04x2CoefficientCodingno 
0028,04x3CoefficientCodingPointersno 
0028,0700DCTLabelno 
0028,0701DataBlockDescriptionno 
0028,0702DataBlockno 
0028,0710NormalizationFactorFormatno 
0028,0720ZonalMapNumberFormatno 
0028,0721ZonalMapLocationno 
0028,0722ZonalMapFormatno 
0028,0730AdaptiveMapFormatno 
0028,0740CodeNumberFormatno 
0028,08x0CodeLabelno 
0028,08x2NumberOfTablesno 
0028,08x3CodeTableLocationno 
0028,08x4BitsForCodeWordno 
0028,08x8ImageDataLocationno 
0028,0A02PixelSpacingCalibrationTypeno 
0028,0A04PixelSpacingCalibrationDescriptionno 
0028,1040PixelIntensityRelationshipno 
0028,1041PixelIntensityRelationshipSignno 
0028,1050WindowCenterno 
0028,1051WindowWidthno 
0028,1052RescaleInterceptno 
0028,1053RescaleSlopeno 
0028,1054RescaleTypeno 
0028,1055WindowCenterAndWidthExplanationno 
0028,1056VOI_LUTFunctionno 
0028,1080GrayScaleno 
0028,1090RecommendedViewingModeno 
0028,1100GrayLookupTableDescriptorno 
0028,1101RedPaletteColorTableDescriptorno 
0028,1102GreenPaletteColorTableDescriptorno 
0028,1103BluePaletteColorTableDescriptorno 
0028,1111LargeRedPaletteColorTableDescrno 
0028,1112LargeGreenPaletteColorTableDescrno 
0028,1113LargeBluePaletteColorTableDescrno 
0028,1199PaletteColorTableUIDno 
0028,1200GrayLookupTableDatano 
0028,1201RedPaletteColorTableDatano 
0028,1202GreenPaletteColorTableDatano 
0028,1203BluePaletteColorTableDatano 
0028,1211LargeRedPaletteColorTableDatano 
0028,1212LargeGreenPaletteColorTableDatano 
0028,1213LargeBluePaletteColorTableDatano 
0028,1214LargePaletteColorLookupTableUIDno 
0028,1221SegmentedRedColorTableDatano 
0028,1222SegmentedGreenColorTableDatano 
0028,1223SegmentedBlueColorTableDatano 
0028,1300BreastImplantPresentno 
0028,1350PartialViewno 
0028,1351PartialViewDescriptionno 
0028,1352PartialViewCodeSequenceno 
0028,135ASpatialLocationsPreservedno 
0028,1402DataPathAssignmentno 
0028,1404BlendingLUT1Sequenceno 
0028,1406BlendingWeightConstantno 
0028,1408BlendingLookupTableDatano 
0028,140CBlendingLUT2Sequenceno 
0028,140EDataPathIDno 
0028,140FRGBLUTTransferFunctionno 
0028,1410AlphaLUTTransferFunctionno 
0028,2000ICCProfileno 
0028,2110LossyImageCompressionno 
0028,2112LossyImageCompressionRationo 
0028,2114LossyImageCompressionMethodno 
0028,3000ModalityLUTSequenceno 
0028,3002LUTDescriptorno 
0028,3003LUTExplanationno 
0028,3004ModalityLUTTypeno 
0028,3006LUTDatano 
0028,3010VOILUTSequenceno 
0028,3110SoftcopyVOILUTSequenceno 
0028,4000ImagePresentationCommentsno 
0028,5000BiPlaneAcquisitionSequenceno 
0028,6010RepresentativeFrameNumberno 
0028,6020FrameNumbersOfInterestno 
0028,6022FrameOfInterestDescriptionno 
0028,6023FrameOfInterestTypeno 
0028,6030MaskPointersno 
0028,6040RWavePointerno 
0028,6100MaskSubtractionSequenceno 
0028,6101MaskOperationno 
0028,6102ApplicableFrameRangeno 
0028,6110MaskFrameNumbersno 
0028,6112ContrastFrameAveragingno 
0028,6114MaskSubPixelShiftno 
0028,6120TIDOffsetno 
0028,6190MaskOperationExplanationno 
0028,7FE0PixelDataProviderURLno 
0028,9001DataPointRowsno 
0028,9002DataPointColumnsno 
0028,9003SignalDomainColumnsno 
0028,9099LargestMonochromePixelValueno 
0028,9108DataRepresentationno 
0028,9110PixelMeasuresSequenceno 
0028,9132FrameVOILUTSequenceno 
0028,9145PixelValueTransformationSequenceno 
0028,9235SignalDomainRowsno 
0028,9411DisplayFilterPercentageno 
0028,9415FramePixelShiftSequenceno 
0028,9416SubtractionItemIDno 
0028,9422PixelIntensityRelationshipLUTSeqno 
0028,9443FramePixelDataPropertiesSequenceno 
0028,9444GeometricalPropertiesno 
0028,9445GeometricMaximumDistortionno 
0028,9446ImageProcessingAppliedno 
0028,9454MaskSelectionModeno 
0028,9474LUTFunctionno 
0028,9478MaskVisibilityPercentageno 
0028,9501PixelShiftSequenceno 
0028,9502RegionPixelShiftSequenceno 
0028,9503VerticesOfTheRegionno 
0028,9506PixelShiftFrameRangeno 
0028,9507LUTFrameRangeno 
0028,9520ImageToEquipmentMappingMatrixno 
0028,9537EquipmentCoordinateSystemIDno 
0029,1004LowerRangeOfPixels1ano 
0029,1005LowerRangeOfPixels1bno 
0029,1006LowerRangeOfPixels1cno 
0029,1007LowerRangeOfPixels1dno 
0029,1008LowerRangeOfPixels1eno 
0029,1009LowerRangeOfPixels1fno 
0029,100ALowerRangeOfPixels1gno 
0029,1015LowerRangeOfPixels1hno 
0029,1016LowerRangeOfPixels1ino 
0029,1017LowerRangeOfPixels2no 
0029,1018UpperRangeOfPixels2no 
0029,101ALenOfTotHdrInBytesno 
0029,1026VersionOfTheHdrStructno 
0029,1034AdvantageCompOverflowno 
0029,1035AdvantageCompUnderflowno 
0032,0000StudyGroupLengthno 
0032,000AStudyStatusIDno 
0032,000CStudyPriorityIDno 
0032,0012StudyIDIssuerno 
0032,0032StudyVerifiedDateno 
0032,0033StudyVerifiedTimeno 
0032,0034StudyReadDateno 
0032,0035StudyReadTimeno 
0032,1000ScheduledStudyStartDateno 
0032,1001ScheduledStudyStartTimeno 
0032,1010ScheduledStudyStopDateno 
0032,1011ScheduledStudyStopTimeno 
0032,1020ScheduledStudyLocationno 
0032,1021ScheduledStudyLocationAETitleno 
0032,1030ReasonForStudyno 
0032,1031RequestingPhysicianIDSequenceno 
0032,1032RequestingPhysicianno 
0032,1033RequestingServiceno 
0032,1040StudyArrivalDateno 
0032,1041StudyArrivalTimeno 
0032,1050StudyCompletionDateno 
0032,1051StudyCompletionTimeno 
0032,1055StudyComponentStatusIDno 
0032,1060RequestedProcedureDescriptionno 
0032,1064RequestedProcedureCodeSequenceno 
0032,1070RequestedContrastAgentno 
0032,4000StudyCommentsno 
0038,0004ReferencedPatientAliasSequenceno 
0038,0008VisitStatusIDno 
0038,0010AdmissionIDno 
0038,0011IssuerOfAdmissionIDno 
0038,0016RouteOfAdmissionsno 
0038,001AScheduledAdmissionDateno 
0038,001BScheduledAdmissionTimeno 
0038,001CScheduledDischargeDateno 
0038,001DScheduledDischargeTimeno 
0038,001EScheduledPatientInstitResidenceno 
0038,0020AdmittingDateno 
0038,0021AdmittingTimeno 
0038,0030DischargeDateno 
0038,0032DischargeTimeno 
0038,0040DischargeDiagnosisDescriptionno 
0038,0044DischargeDiagnosisCodeSequenceno 
0038,0050SpecialNeedsno 
0038,0060ServiceEpisodeIDno 
0038,0061IssuerOfServiceEpisodeIDno 
0038,0062ServiceEpisodeDescriptionno 
0038,0100PertinentDocumentsSequenceno 
0038,0300CurrentPatientLocationno 
0038,0400PatientInstitutionResidenceno 
0038,0500PatientStateno 
0038,0502PatientClinicalTrialParticipSeqno 
0038,4000VisitCommentsno 
003A,0004WaveformOriginalityno 
003A,0005NumberOfWaveformChannelsno 
003A,0010NumberOfWaveformSamplesno 
003A,001ASamplingFrequencyno 
003A,0020MultiplexGroupLabelno 
003A,0200ChannelDefinitionSequenceno 
003A,0202WaveformChannelNumberno 
003A,0203ChannelLabelno 
003A,0205ChannelStatusno 
003A,0208ChannelSourceSequenceno 
003A,0209ChannelSourceModifiersSequenceno 
003A,020ASourceWaveformSequenceno 
003A,020CChannelDerivationDescriptionno 
003A,0210ChannelSensitivityno 
003A,0211ChannelSensitivityUnitsSequenceno 
003A,0212ChannelSensitivityCorrectionFactorno 
003A,0213ChannelBaselineno 
003A,0214ChannelTimeSkewno 
003A,0215ChannelSampleSkewno 
003A,0218ChannelOffsetno 
003A,021AWaveformBitsStoredno 
003A,0220FilterLowFrequencyno 
003A,0221FilterHighFrequencyno 
003A,0222NotchFilterFrequencyno 
003A,0223NotchFilterBandwidthno 
003A,0230WaveformDataDisplayScaleno 
003A,0231WaveformDisplayBkgCIELabValueno 
003A,0240WaveformPresentationGroupSequenceno 
003A,0241PresentationGroupNumberno 
003A,0242ChannelDisplaySequenceno 
003A,0244ChannelRecommendDisplayCIELabValueno 
003A,0245ChannelPositionno 
003A,0246DisplayShadingFlagno 
003A,0247FractionalChannelDisplayScaleno 
003A,0248AbsoluteChannelDisplayScaleno 
003A,0300MultiplexAudioChannelsDescrCodeSeqno 
003A,0301ChannelIdentificationCodeno 
003A,0302ChannelModeno 
0040,0001ScheduledStationAETitleno 
0040,0002ScheduledProcedureStepStartDateno 
0040,0003ScheduledProcedureStepStartTimeno 
0040,0004ScheduledProcedureStepEndDateno 
0040,0005ScheduledProcedureStepEndTimeno 
0040,0006ScheduledPerformingPhysiciansNameno 
0040,0007ScheduledProcedureStepDescriptionno 
0040,0008ScheduledProtocolCodeSequenceno 
0040,0009ScheduledProcedureStepIDno 
0040,000AStageCodeSequenceno 
0040,000BScheduledPerformingPhysicianIDSeqno 
0040,0010ScheduledStationNameno 
0040,0011ScheduledProcedureStepLocationno 
0040,0012PreMedicationno 
0040,0020ScheduledProcedureStepStatusno 
0040,0031LocalNamespaceEntityIDno 
0040,0032UniversalEntityIDno 
0040,0033UniversalEntityIDTypeno 
0040,0035IdentifierTypeCodeno 
0040,0036AssigningFacilitySequenceno 
0040,0100ScheduledProcedureStepSequenceno 
0040,0220ReferencedNonImageCompositeSOPSeqno 
0040,0241PerformedStationAETitleno 
0040,0242PerformedStationNameno 
0040,0243PerformedLocationno 
0040,0244PerformedProcedureStepStartDateno 
0040,0245PerformedProcedureStepStartTimeno 
0040,0250PerformedProcedureStepEndDateno 
0040,0251PerformedProcedureStepEndTimeno 
0040,0252PerformedProcedureStepStatusno 
0040,0253PerformedProcedureStepIDno 
0040,0254PerformedProcedureStepDescriptionno 
0040,0255PerformedProcedureTypeDescriptionno 
0040,0260PerformedProtocolCodeSequenceno 
0040,0261PerformedProtocolTypeno 
0040,0270ScheduledStepAttributesSequenceno 
0040,0275RequestAttributesSequenceno 
0040,0280CommentsOnPerformedProcedureStepno 
0040,0281ProcStepDiscontinueReasonCodeSeqno 
0040,0293QuantitySequenceno 
0040,0294Quantityno 
0040,0295MeasuringUnitsSequenceno 
0040,0296BillingItemSequenceno 
0040,0300TotalTimeOfFluoroscopyno 
0040,0301TotalNumberOfExposuresno 
0040,0302EntranceDoseno 
0040,0303ExposedAreano 
0040,0306DistanceSourceToEntranceno 
0040,0307DistanceSourceToSupportno 
0040,030EExposureDoseSequenceno 
0040,0310CommentsOnRadiationDoseno 
0040,0312XRayOutputno 
0040,0314HalfValueLayerno 
0040,0316OrganDoseno 
0040,0318OrganExposedno 
0040,0320BillingProcedureStepSequenceno 
0040,0321FilmConsumptionSequenceno 
0040,0324BillingSuppliesAndDevicesSequenceno 
0040,0330ReferencedProcedureStepSequenceno 
0040,0340PerformedSeriesSequenceno 
0040,0400CommentsOnScheduledProcedureStepno 
0040,0440ProtocolContextSequenceno 
0040,0441ContentItemModifierSequenceno 
0040,050ASpecimenAccessionNumberno 
0040,0512ContainerIdentifierno 
0040,051AContainerDescriptionno 
0040,0550SpecimenSequenceno 
0040,0551SpecimenIdentifierno 
0040,0552SpecimenDescriptionSequenceTrialno 
0040,0553SpecimenDescriptionTrialno 
0040,0554SpecimenUIDno 
0040,0555AcquisitionContextSequenceno 
0040,0556AcquisitionContextDescriptionno 
0040,059ASpecimenTypeCodeSequenceno 
0040,0600SpecimenShortDescriptionno 
0040,06FASlideIdentifierno 
0040,071AImageCenterPointCoordinatesSeqno 
0040,072AXOffsetInSlideCoordinateSystemno 
0040,073AYOffsetInSlideCoordinateSystemno 
0040,074AZOffsetInSlideCoordinateSystemno 
0040,08D8PixelSpacingSequenceno 
0040,08DACoordinateSystemAxisCodeSequenceno 
0040,08EAMeasurementUnitsCodeSequenceno 
0040,09F8VitalStainCodeSequenceTrialno 
0040,1001RequestedProcedureIDno 
0040,1002ReasonForRequestedProcedureno 
0040,1003RequestedProcedurePriorityno 
0040,1004PatientTransportArrangementsno 
0040,1005RequestedProcedureLocationno 
0040,1006PlacerOrderNumber-Procedureno 
0040,1007FillerOrderNumber-Procedureno 
0040,1008ConfidentialityCodeno 
0040,1009ReportingPriorityno 
0040,100AReasonForRequestedProcedureCodeSeqno 
0040,1010NamesOfIntendedRecipientsOfResultsno 
0040,1011IntendedRecipientsOfResultsIDSeqno 
0040,1101PersonIdentificationCodeSequenceno 
0040,1102PersonAddressno 
0040,1103PersonTelephoneNumbersno 
0040,1400RequestedProcedureCommentsno 
0040,2001ReasonForImagingServiceRequestno 
0040,2004IssueDateOfImagingServiceRequestno 
0040,2005IssueTimeOfImagingServiceRequestno 
0040,2006PlacerOrderNum-ImagingServiceReqno 
0040,2007FillerOrderNum-ImagingServiceReqno 
0040,2008OrderEnteredByno 
0040,2009OrderEntererLocationno 
0040,2010OrderCallbackPhoneNumberno 
0040,2016PlacerOrderNum-ImagingServiceReqno 
0040,2017FillerOrderNum-ImagingServiceReqno 
0040,2400ImagingServiceRequestCommentsno 
0040,3001ConfidentialityOnPatientDataDescrno 
0040,4001GenPurposeScheduledProcStepStatusno 
0040,4002GenPurposePerformedProcStepStatusno 
0040,4003GenPurposeSchedProcStepPriorityno 
0040,4004SchedProcessingApplicationsCodeSeqno 
0040,4005SchedProcedureStepStartDateAndTimeno 
0040,4006MultipleCopiesFlagno 
0040,4007PerformedProcessingAppsCodeSeqno 
0040,4009HumanPerformerCodeSequenceno 
0040,4010SchedProcStepModificationDateTimeno 
0040,4011ExpectedCompletionDateAndTimeno 
0040,4015ResultingGenPurposePerfProcStepSeqno 
0040,4016RefGenPurposeSchedProcStepSeqno 
0040,4018ScheduledWorkitemCodeSequenceno 
0040,4019PerformedWorkitemCodeSequenceno 
0040,4020InputAvailabilityFlagno 
0040,4021InputInformationSequenceno 
0040,4022RelevantInformationSequenceno 
0040,4023RefGenPurSchedProcStepTransUIDno 
0040,4025ScheduledStationNameCodeSequenceno 
0040,4026ScheduledStationClassCodeSequenceno 
0040,4027SchedStationGeographicLocCodeSeqno 
0040,4028PerformedStationNameCodeSequenceno 
0040,4029PerformedStationClassCodeSequenceno 
0040,4030PerformedStationGeogLocCodeSeqno 
0040,4031RequestedSubsequentWorkItemCodeSeqno 
0040,4032NonDICOMOutputCodeSequenceno 
0040,4033OutputInformationSequenceno 
0040,4034ScheduledHumanPerformersSequenceno 
0040,4035ActualHumanPerformersSequenceno 
0040,4036HumanPerformersOrganizationno 
0040,4037HumanPerformerNameno 
0040,4040RawDataHandlingno 
0040,8302EntranceDoseInMilliGyno 
0040,9094RefImageRealWorldValueMappingSeqno 
0040,9096RealWorldValueMappingSequenceno 
0040,9098PixelValueMappingCodeSequenceno 
0040,9210LUTLabelno 
0040,9211RealWorldValueLastValueMappedno 
0040,9212RealWorldValueLUTDatano 
0040,9216RealWorldValueFirstValueMappedno 
0040,9224RealWorldValueInterceptno 
0040,9225RealWorldValueSlopeno 
0040,A010RelationshipTypeno 
0040,A027VerifyingOrganizationno 
0040,A030VerificationDateTimeno 
0040,A032ObservationDateTimeno 
0040,A040ValueTypeno 
0040,A043ConceptNameCodeSequenceno 
0040,A050ContinuityOfContentno 
0040,A073VerifyingObserverSequenceno 
0040,A075VerifyingObserverNameno 
0040,A078AuthorObserverSequenceno 
0040,A07AParticipantSequenceno 
0040,A07CCustodialOrganizationSequenceno 
0040,A080ParticipationTypeno 
0040,A082ParticipationDateTimeno 
0040,A084ObserverTypeno 
0040,A088VerifyingObserverIdentCodeSequenceno 
0040,A090EquivalentCDADocumentSequenceno 
0040,A0B0ReferencedWaveformChannelsno 
0040,A120DateTimeno 
0040,A121Dateno 
0040,A122Timeno 
0040,A123PersonNameno 
0040,A124UIDno 
0040,A130TemporalRangeTypeno 
0040,A132ReferencedSamplePositionsno 
0040,A136ReferencedFrameNumbersno 
0040,A138ReferencedTimeOffsetsno 
0040,A13AReferencedDateTimeno 
0040,A160TextValueno 
0040,A168ConceptCodeSequenceno 
0040,A170PurposeOfReferenceCodeSequenceno 
0040,A180AnnotationGroupNumberno 
0040,A195ModifierCodeSequenceno 
0040,A300MeasuredValueSequenceno 
0040,A301NumericValueQualifierCodeSequenceno 
0040,A30ANumericValueno 
0040,A353AddressTrialno 
0040,A354TelephoneNumberTrialno 
0040,A360PredecessorDocumentsSequenceno 
0040,A370ReferencedRequestSequenceno 
0040,A372PerformedProcedureCodeSequenceno 
0040,A375CurrentRequestedProcEvidenceSeqno 
0040,A385PertinentOtherEvidenceSequenceno 
0040,A390HL7StructuredDocumentRefSeqno 
0040,A491CompletionFlagno 
0040,A492CompletionFlagDescriptionno 
0040,A493VerificationFlagno 
0040,A494ArchiveRequestedno 
0040,A496PreliminaryFlagno 
0040,A504ContentTemplateSequenceno 
0040,A525IdenticalDocumentsSequenceno 
0040,A730ContentSequenceno 
0040,B020AnnotationSequenceno 
0040,DB00TemplateIdentifierno 
0040,DB06TemplateVersionno 
0040,DB07TemplateLocalVersionno 
0040,DB0BTemplateExtensionFlagno 
0040,DB0CTemplateExtensionOrganizationUIDno 
0040,DB0DTemplateExtensionCreatorUIDno 
0040,DB73ReferencedContentItemIdentifierno 
0040,E001HL7InstanceIdentifierno 
0040,E004HL7DocumentEffectiveTimeno 
0040,E006HL7DocumentTypeCodeSequenceno 
0040,E010RetrieveURIno 
0040,E011RetrieveLocationUIDno 
0042,0010DocumentTitleno 
0042,0011EncapsulatedDocumentno 
0042,0012MIMETypeOfEncapsulatedDocumentno 
0042,0013SourceInstanceSequenceno 
0042,0014ListOfMIMETypesno 
0043,1001BitmapOfPrescanOptionsno 
0043,1002GradientOffsetInXno 
0043,1003GradientOffsetInYno 
0043,1004GradientOffsetInZno 
0043,1005ImgIsOriginalOrUnoriginalno 
0043,1006NumberOfEPIShotsno 
0043,1007ViewsPerSegmentno 
0043,1008RespiratoryRateBpmno 
0043,1009RespiratoryTriggerPointno 
0043,100ATypeOfReceiverUsedno 
0043,100BPeakRateOfChangeOfGradientFieldno 
0043,100CLimitsInUnitsOfPercentno 
0043,100DPSDEstimatedLimitno 
0043,100EPSDEstimatedLimitInTeslaPerSecondno 
0043,100FSaravgheadno 
0043,1010WindowValueno 
0043,1011TotalInputViewsno 
0043,1012X-RayChainno 
0043,1013DeconKernelParametersno 
0043,1014CalibrationParametersno 
0043,1015TotalOutputViewsno 
0043,1016NumberOfOverrangesno 
0043,1017IBHImageScaleFactorsno 
0043,1018BBHCoefficientsno 
0043,1019NumberOfBBHChainsToBlendno 
0043,101AStartingChannelNumberno 
0043,101BPpscanParametersno 
0043,101CGEImageIntegrityno 
0043,101DLevelValueno 
0043,101EDeltaStartTimeno 
0043,101FMaxOverrangesInAViewno 
0043,1020AvgOverrangesAllViewsno 
0043,1021CorrectedAfterGlowTermsno 
0043,1025ReferenceChannelsno 
0043,1026NoViewsRefChansBlockedno 
0043,1027ScanPitchRationo 
0043,1028UniqueImageIdenno 
0043,1029HistogramTablesno 
0043,102AUserDefinedDatano 
0043,102BPrivateScanOptionsno 
0043,102CEffectiveEchoSpacingno 
0043,102DStringSlopField1no 
0043,102EStringSlopField2no 
0043,102FRawDataTypeno 
0043,1030RawDataTypeno 
0043,1031RACordOfTargetReconCenterno 
0043,1032RawDataTypeno 
0043,1033NegScanspacingno 
0043,1034OffsetFrequencyno 
0043,1035UserUsageTagno 
0043,1036UserFillMapMSWno 
0043,1037UserFillMapLSWno 
0043,1038User25-48no 
0043,1039SlopInt6-9no 
0043,1040TriggerOnPositionno 
0043,1041DegreeOfRotationno 
0043,1042DASTriggerSourceno 
0043,1043DASFpaGainno 
0043,1044DASOutputSourceno 
0043,1045DASAdInputno 
0043,1046DASCalModeno 
0043,1047DASCalFrequencyno 
0043,1048DASRegXmno 
0043,1049DASAutoZerono 
0043,104AStartingChannelOfViewno 
0043,104BDASXmPatternno 
0043,104CTGGCTriggerModeno 
0043,104DStartScanToXrayOnDelayno 
0043,104EDurationOfXrayOnno 
0043,1060SlopInt10-17no 
0043,1061ScannerStudyEntityUIDno 
0043,1062ScannerStudyIDno 
0043,106fScannerTableEntryno 
0044,0001ProductPackageIdentifierno 
0044,0002SubstanceAdministrationApprovalno 
0044,0003ApprovalStatusFurtherDescriptionno 
0044,0004ApprovalStatusDateTimeno 
0044,0007ProductTypeCodeSequenceno 
0044,0008ProductNameno 
0044,0009ProductDescriptionno 
0044,000AProductLotIdentifierno 
0044,000BProductExpirationDateTimeno 
0044,0010SubstanceAdministrationDateTimeno 
0044,0011SubstanceAdministrationNotesno 
0044,0012SubstanceAdministrationDeviceIDno 
0044,0013ProductParameterSequenceno 
0044,0019SubstanceAdminParameterSeqno 
0045,1001NumberOfMacroRowsInDetectorno 
0045,1002MacroWidthAtISOCenterno 
0045,1003DASTypeno 
0045,1004DASGainno 
0045,1005DASTemperatureno 
0045,1006TableDirectionInOrOutno 
0045,1007ZSmoothingFactorno 
0045,1008ViewWeightingModeno 
0045,1009SigmaRowNumberWhichRowsWereUsedno 
0045,100AMinimumDasValueFoundInTheScanDatano 
0045,100BMaximumOffsetShiftValueUsedno 
0045,100CNumberOfViewsShiftedno 
0045,100DZTrackingFlagno 
0045,100EMeanZErrorno 
0045,100FZTrackingMaximumErrorno 
0045,1010StartingViewForRow2ano 
0045,1011NumberOfViewsInRow2ano 
0045,1012StartingViewForRow1ano 
0045,1013SigmaModeno 
0045,1014NumberOfViewsInRow1ano 
0045,1015StartingViewForRow2bno 
0045,1016NumberOfViewsInRow2bno 
0045,1017StartingViewForRow1bno 
0045,1018NumberOfViewsInRow1bno 
0045,1019AirFilterCalibrationDateno 
0045,101AAirFilterCalibrationTimeno 
0045,101BPhantomCalibrationDateno 
0045,101CPhantomCalibrationTimeno 
0045,101DZSlopeCalibrationDateno 
0045,101EZSlopeCalibrationTimeno 
0045,101FCrosstalkCalibrationDateno 
0045,1020CrosstalkCalibrationTimeno 
0045,1021IterboneOptionFlagno 
0045,1022PeristalticFlagOptionno 
0046,0012LensDescriptionno 
0046,0014RightLensSequenceno 
0046,0015LeftLensSequenceno 
0046,0018CylinderSequenceno 
0046,0028PrismSequenceno 
0046,0030HorizontalPrismPowerno 
0046,0032HorizontalPrismBaseno 
0046,0034VerticalPrismPowerno 
0046,0036VerticalPrismBaseno 
0046,0038LensSegmentTypeno 
0046,0040OpticalTransmittanceno 
0046,0042ChannelWidthno 
0046,0044PupilSizeno 
0046,0046CornealSizeno 
0046,0060DistancePupillaryDistanceno 
0046,0062NearPupillaryDistanceno 
0046,0064OtherPupillaryDistanceno 
0046,0075RadiusOfCurvatureno 
0046,0076KeratometricPowerno 
0046,0077KeratometricAxisno 
0046,0092BackgroundColorno 
0046,0094Optotypeno 
0046,0095OptotypePresentationno 
0046,0100AddNearSequenceno 
0046,0101AddIntermediateSequenceno 
0046,0102AddOtherSequenceno 
0046,0104AddPowerno 
0046,0106ViewingDistanceno 
0046,0125ViewingDistanceTypeno 
0046,0135VisualAcuityModifiersno 
0046,0137DecimalVisualAcuityno 
0046,0139OptotypeDetailedDefinitionno 
0046,0146SpherePowerno 
0046,0147CylinderPowerno 
0050,0004CalibrationImageno 
0050,0010DeviceSequenceno 
0050,0014DeviceLengthno 
0050,0015ContainerComponentWidthno 
0050,0016DeviceDiameterno 
0050,0017DeviceDiameterUnitsno 
0050,0018DeviceVolumeno 
0050,0019InterMarkerDistanceno 
0050,001BContainerComponentIDno 
0050,0020DeviceDescriptionno 
0054,0010EnergyWindowVectorno 
0054,0011NumberOfEnergyWindowsno 
0054,0012EnergyWindowInformationSequenceno 
0054,0013EnergyWindowRangeSequenceno 
0054,0014EnergyWindowLowerLimitno 
0054,0015EnergyWindowUpperLimitno 
0054,0016RadiopharmaceuticalInformationSeqno 
0054,0017ResidualSyringeCountsno 
0054,0018EnergyWindowNameno 
0054,0020DetectorVectorno 
0054,0021NumberOfDetectorsno 
0054,0022DetectorInformationSequenceno 
0054,0030PhaseVectorno 
0054,0031NumberOfPhasesno 
0054,0032PhaseInformationSequenceno 
0054,0033NumberOfFramesInPhaseno 
0054,0036PhaseDelayno 
0054,0038PauseBetweenFramesno 
0054,0039PhaseDescriptionno 
0054,0050RotationVectorno 
0054,0051NumberOfRotationsno 
0054,0052RotationInformationSequenceno 
0054,0053NumberOfFramesInRotationno 
0054,0060RRIntervalVectorno 
0054,0061NumberOfRRIntervalsno 
0054,0062GatedInformationSequenceno 
0054,0063DataInformationSequenceno 
0054,0070TimeSlotVectorno 
0054,0071NumberOfTimeSlotsno 
0054,0072TimeSlotInformationSequenceno 
0054,0073TimeSlotTimeno 
0054,0080SliceVectorno 
0054,0081NumberOfSlicesno 
0054,0090AngularViewVectorno 
0054,0100TimeSliceVectorno 
0054,0101NumberOfTimeSlicesno 
0054,0200StartAngleno 
0054,0202TypeOfDetectorMotionno 
0054,0210TriggerVectorno 
0054,0211NumberOfTriggersInPhaseno 
0054,0220ViewCodeSequenceno 
0054,0222ViewModifierCodeSequenceno 
0054,0300RadionuclideCodeSequenceno 
0054,0302AdministrationRouteCodeSequenceno 
0054,0304RadiopharmaceuticalCodeSequenceno 
0054,0306CalibrationDataSequenceno 
0054,0308EnergyWindowNumberno 
0054,0400ImageIDno 
0054,0410PatientOrientationCodeSequenceno 
0054,0412PatientOrientationModifierCodeSeqno 
0054,0414PatientGantryRelationshipCodeSeqno 
0054,0500SliceProgressionDirectionno 
0054,1000SeriesTypeno 
0054,1001Unitsno 
0054,1002CountsSourceno 
0054,1004ReprojectionMethodno 
0054,1100RandomsCorrectionMethodno 
0054,1101AttenuationCorrectionMethodno 
0054,1102DecayCorrectionno 
0054,1103ReconstructionMethodno 
0054,1104DetectorLinesOfResponseUsedno 
0054,1105ScatterCorrectionMethodno 
0054,1200AxialAcceptanceno 
0054,1201AxialMashno 
0054,1202TransverseMashno 
0054,1203DetectorElementSizeno 
0054,1210CoincidenceWindowWidthno 
0054,1220SecondaryCountsTypeno 
0054,1300FrameReferenceTimeno 
0054,1310PrimaryCountsAccumulatedno 
0054,1311SecondaryCountsAccumulatedno 
0054,1320SliceSensitivityFactorno 
0054,1321DecayFactorno 
0054,1322DoseCalibrationFactorno 
0054,1323ScatterFractionFactorno 
0054,1324DeadTimeFactorno 
0054,1330ImageIndexno 
0054,1400CountsIncludedno 
0054,1401DeadTimeCorrectionFlagno 
0060,3000HistogramSequenceno 
0060,3002HistogramNumberOfBinsno 
0060,3004HistogramFirstBinValueno 
0060,3006HistogramLastBinValueno 
0060,3008HistogramBinWidthno 
0060,3010HistogramExplanationno 
0060,3020HistogramDatano 
0062,0001SegmentationTypeno 
0062,0002SegmentSequenceno 
0062,0003SegmentedPropertyCategoryCodeSeqno 
0062,0004SegmentNumberno 
0062,0005SegmentLabelno 
0062,0006SegmentDescriptionno 
0062,0008SegmentAlgorithmTypeno 
0062,0009SegmentAlgorithmNameno 
0062,000ASegmentIdentificationSequenceno 
0062,000BReferencedSegmentNumberno 
0062,000CRecommendedDisplayGrayscaleValueno 
0062,000DRecommendedDisplayCIELabValueno 
0062,000EMaximumFractionalValueno 
0062,000FSegmentedPropertyTypeCodeSequenceno 
0062,0010SegmentationFractionalTypeno 
0064,0002DeformableRegistrationSequenceno 
0064,0003SourceFrameOfReferenceUIDno 
0064,0005DeformableRegistrationGridSequenceno 
0064,0007GridDimensionsno 
0064,0008GridResolutionno 
0064,0009VectorGridDatano 
0064,000FPreDeformationMatrixRegistSeqno 
0064,0010PostDeformationMatrixRegistSeqno 
0066,0001NumberOfSurfacesno 
0066,0002SurfaceSequenceno 
0066,0003SurfaceNumberno 
0066,0004SurfaceCommentsno 
0066,0009SurfaceProcessingno 
0066,000ASurfaceProcessingRationo 
0066,000EFiniteVolumeno 
0066,0010Manifoldno 
0066,0011SurfacePointsSequenceno 
0066,0015NumberOfSurfacePointsno 
0066,0016PointCoordinatesDatano 
0066,0017PointPositionAccuracyno 
0066,0018MeanPointDistanceno 
0066,0019MaximumPointDistanceno 
0066,001BAxisOfRotationno 
0066,001CCenterOfRotationno 
0066,001ENumberOfVectorsno 
0066,001FVectorDimensionalityno 
0066,0020VectorAccuracyno 
0066,0021VectorCoordinateDatano 
0066,0023TrianglePointIndexListno 
0066,0024EdgePointIndexListno 
0066,0025VertexPointIndexListno 
0066,0026TriangleStripSequenceno 
0066,0027TriangleFanSequenceno 
0066,0028LineSequenceno 
0066,0029PrimitivePointIndexListno 
0066,002ASurfaceCountno 
0066,002FAlgorithmFamilyCodeSequno 
0066,0031AlgorithmVersionno 
0066,0032AlgorithmParametersno 
0066,0034FacetSequenceno 
0066,0036AlgorithmNameno 
0070,0001GraphicAnnotationSequenceno 
0070,0002GraphicLayerno 
0070,0003BoundingBoxAnnotationUnitsno 
0070,0004AnchorPointAnnotationUnitsno 
0070,0005GraphicAnnotationUnitsno 
0070,0006UnformattedTextValueno 
0070,0008TextObjectSequenceno 
0070,0009GraphicObjectSequenceno 
0070,0010BoundingBoxTopLeftHandCornerno 
0070,0011BoundingBoxBottomRightHandCornerno 
0070,0012BoundingBoxTextHorizJustificationno 
0070,0014AnchorPointno 
0070,0015AnchorPointVisibilityno 
0070,0020GraphicDimensionsno 
0070,0021NumberOfGraphicPointsno 
0070,0022GraphicDatano 
0070,0023GraphicTypeno 
0070,0024GraphicFilledno 
0070,0040ImageRotationRetiredno 
0070,0041ImageHorizontalFlipno 
0070,0042ImageRotationno 
0070,0050DisplayedAreaTopLeftTrialno 
0070,0051DisplayedAreaBottomRightTrialno 
0070,0052DisplayedAreaTopLeftno 
0070,0053DisplayedAreaBottomRightno 
0070,005ADisplayedAreaSelectionSequenceno 
0070,0060GraphicLayerSequenceno 
0070,0062GraphicLayerOrderno 
0070,0066GraphicLayerRecDisplayGraysclValueno 
0070,0067GraphicLayerRecDisplayRGBValueno 
0070,0068GraphicLayerDescriptionno 
0070,0080ContentLabelno 
0070,0081ContentDescriptionno 
0070,0082PresentationCreationDateno 
0070,0083PresentationCreationTimeno 
0070,0084ContentCreatorNameno 
0070,0086ContentCreatorIDCodeSequenceno 
0070,0100PresentationSizeModeno 
0070,0101PresentationPixelSpacingno 
0070,0102PresentationPixelAspectRationo 
0070,0103PresentationPixelMagRationo 
0070,0306ShapeTypeno 
0070,0308RegistrationSequenceno 
0070,0309MatrixRegistrationSequenceno 
0070,030AMatrixSequenceno 
0070,030CFrameOfRefTransformationMatrixTypeno 
0070,030DRegistrationTypeCodeSequenceno 
0070,030FFiducialDescriptionno 
0070,0310FiducialIdentifierno 
0070,0311FiducialIdentifierCodeSequenceno 
0070,0312ContourUncertaintyRadiusno 
0070,0314UsedFiducialsSequenceno 
0070,0318GraphicCoordinatesDataSequenceno 
0070,031AFiducialUIDno 
0070,031CFiducialSetSequenceno 
0070,031EFiducialSequenceno 
0070,0401GraphicLayerRecomDisplayCIELabValno 
0070,0402BlendingSequenceno 
0070,0403RelativeOpacityno 
0070,0404ReferencedSpatialRegistrationSeqno 
0070,0405BlendingPositionno 
0072,0002HangingProtocolNameno 
0072,0004HangingProtocolDescriptionno 
0072,0006HangingProtocolLevelno 
0072,0008HangingProtocolCreatorno 
0072,000AHangingProtocolCreationDateTimeno 
0072,000CHangingProtocolDefinitionSequenceno 
0072,000EHangingProtocolUserIDCodeSequenceno 
0072,0010HangingProtocolUserGroupNameno 
0072,0012SourceHangingProtocolSequenceno 
0072,0014NumberOfPriorsReferencedno 
0072,0020ImageSetsSequenceno 
0072,0022ImageSetSelectorSequenceno 
0072,0024ImageSetSelectorUsageFlagno 
0072,0026SelectorAttributeno 
0072,0028SelectorValueNumberno 
0072,0030TimeBasedImageSetsSequenceno 
0072,0032ImageSetNumberno 
0072,0034ImageSetSelectorCategoryno 
0072,0038RelativeTimeno 
0072,003ARelativeTimeUnitsno 
0072,003CAbstractPriorValueno 
0072,003EAbstractPriorCodeSequenceno 
0072,0040ImageSetLabelno 
0072,0050SelectorAttributeVRno 
0072,0052SelectorSequencePointerno 
0072,0054SelectorSeqPointerPrivateCreatorno 
0072,0056SelectorAttributePrivateCreatorno 
0072,0060SelectorATValueno 
0072,0062SelectorCSValueno 
0072,0064SelectorISValueno 
0072,0066SelectorLOValueno 
0072,0068SelectorLTValueno 
0072,006ASelectorPNValueno 
0072,006CSelectorSHValueno 
0072,006ESelectorSTValueno 
0072,0070SelectorUTValueno 
0072,0072SelectorDSValueno 
0072,0074SelectorFDValueno 
0072,0076SelectorFLValueno 
0072,0078SelectorULValueno 
0072,007ASelectorUSValueno 
0072,007CSelectorSLValueno 
0072,007ESelectorSSValueno 
0072,0080SelectorCodeSequenceValueno 
0072,0100NumberOfScreensno 
0072,0102NominalScreenDefinitionSequenceno 
0072,0104NumberOfVerticalPixelsno 
0072,0106NumberOfHorizontalPixelsno 
0072,0108DisplayEnvironmentSpatialPositionno 
0072,010AScreenMinimumGrayscaleBitDepthno 
0072,010CScreenMinimumColorBitDepthno 
0072,010EApplicationMaximumRepaintTimeno 
0072,0200DisplaySetsSequenceno 
0072,0202DisplaySetNumberno 
0072,0203DisplaySetLabelno 
0072,0204DisplaySetPresentationGroupno 
0072,0206DisplaySetPresentationGroupDescrno 
0072,0208PartialDataDisplayHandlingno 
0072,0210SynchronizedScrollingSequenceno 
0072,0212DisplaySetScrollingGroupno 
0072,0214NavigationIndicatorSequenceno 
0072,0216NavigationDisplaySetno 
0072,0218ReferenceDisplaySetsno 
0072,0300ImageBoxesSequenceno 
0072,0302ImageBoxNumberno 
0072,0304ImageBoxLayoutTypeno 
0072,0306ImageBoxTileHorizontalDimensionno 
0072,0308ImageBoxTileVerticalDimensionno 
0072,0310ImageBoxScrollDirectionno 
0072,0312ImageBoxSmallScrollTypeno 
0072,0314ImageBoxSmallScrollAmountno 
0072,0316ImageBoxLargeScrollTypeno 
0072,0318ImageBoxLargeScrollAmountno 
0072,0320ImageBoxOverlapPriorityno 
0072,0330CineRelativeToRealTimeno 
0072,0400FilterOperationsSequenceno 
0072,0402FilterByCategoryno 
0072,0404FilterByAttributePresenceno 
0072,0406FilterByOperatorno 
0072,0432SynchronizedImageBoxListno 
0072,0434TypeOfSynchronizationno 
0072,0500BlendingOperationTypeno 
0072,0510ReformattingOperationTypeno 
0072,0512ReformattingThicknessno 
0072,0514ReformattingIntervalno 
0072,0516ReformattingOpInitialViewDirno 
0072,0520RenderingType3Dno 
0072,0600SortingOperationsSequenceno 
0072,0602SortByCategoryno 
0072,0604SortingDirectionno 
0072,0700DisplaySetPatientOrientationno 
0072,0702VOITypeno 
0072,0704PseudoColorTypeno 
0072,0706ShowGrayscaleInvertedno 
0072,0710ShowImageTrueSizeFlagno 
0072,0712ShowGraphicAnnotationFlagno 
0072,0714ShowPatientDemographicsFlagno 
0072,0716ShowAcquisitionTechniquesFlagno 
0072,0717DisplaySetHorizontalJustificationno 
0072,0718DisplaySetVerticalJustificationno 
0074,1000UnifiedProcedureStepStateno 
0074,1002UPSProgressInformationSequenceno 
0074,1004UnifiedProcedureStepProgressno 
0074,1006UnifiedProcedureStepProgressDescrno 
0074,1008UnifiedProcedureStepComURISeqno 
0074,100aContactURIno 
0074,100cContactDisplayNameno 
0074,1020BeamTaskSequenceno 
0074,1022BeamTaskTypeno 
0074,1024BeamOrderIndexno 
0074,1030DeliveryVerificationImageSequenceno 
0074,1032VerificationImageTimingno 
0074,1034DoubleExposureFlagno 
0074,1036DoubleExposureOrderingno 
0074,1038DoubleExposureMetersetno 
0074,103ADoubleExposureFieldDeltano 
0074,1040RelatedReferenceRTImageSequenceno 
0074,1042GeneralMachineVerificationSequenceno 
0074,1044ConventionalMachineVerificationSeqno 
0074,1046IonMachineVerificationSequenceno 
0074,1048FailedAttributesSequenceno 
0074,104AOverriddenAttributesSequenceno 
0074,104CConventionalControlPointVerifySeqno 
0074,104EIonControlPointVerificationSeqno 
0074,1050AttributeOccurrenceSequenceno 
0074,1052AttributeOccurrencePointerno 
0074,1054AttributeItemSelectorno 
0074,1056AttributeOccurrencePrivateCreatorno 
0074,1200ScheduledProcedureStepPriorityno 
0074,1202WorklistLabelno 
0074,1204ProcedureStepLabelno 
0074,1210ScheduledProcessingParametersSeqno 
0074,1212PerformedProcessingParametersSeqno 
0074,1216UPSPerformedProcedureSequenceno 
0074,1220RelatedProcedureStepSequenceno 
0074,1222ProcedureStepRelationshipTypeno 
0074,1230DeletionLockno 
0074,1234ReceivingAEno 
0074,1236RequestingAEno 
0074,1238ReasonForCancellationno 
0074,1242SCPStatusno 
0074,1244SubscriptionListStatusno 
0074,1246UPSListStatusno 
0088,0130StorageMediaFileSetIDno 
0088,0140StorageMediaFileSetUIDno 
0088,0200IconImageSequenceno 
0088,0904TopicTitleno 
0088,0906TopicSubjectno 
0088,0910TopicAuthorno 
0088,0912TopicKeywordsno 
0100,0410SOPInstanceStatusno 
0100,0420SOPAuthorizationDateAndTimeno 
0100,0424SOPAuthorizationCommentno 
0100,0426AuthorizationEquipmentCertNumberno 
0400,0005MACIDNumberno 
0400,0010MACCalculationTransferSyntaxUIDno 
0400,0015MACAlgorithmno 
0400,0020DataElementsSignedno 
0400,0100DigitalSignatureUIDno 
0400,0105DigitalSignatureDateTimeno 
0400,0110CertificateTypeno 
0400,0115CertificateOfSignerno 
0400,0120Signatureno 
0400,0305CertifiedTimestampTypeno 
0400,0310CertifiedTimestampno 
0400,0401DigitalSignaturePurposeCodeSeqno 
0400,0402ReferencedDigitalSignatureSeqno 
0400,0403ReferencedSOPInstanceMACSeqno 
0400,0404MACno 
0400,0500EncryptedAttributesSequenceno 
0400,0510EncryptedContentTransferSyntaxUIDno 
0400,0520EncryptedContentno 
0400,0550ModifiedAttributesSequenceno 
0400,0561OriginalAttributesSequenceno 
0400,0562AttributeModificationDateTimeno 
0400,0563ModifyingSystemno 
0400,0564SourceOfPreviousValuesno 
0400,0565ReasonForTheAttributeModificationno 
1000,xxx0EscapeTripletno 
1000,xxx1RunLengthTripletno 
1000,xxx2HuffmanTableSizeno 
1000,xxx3HuffmanTableTripletno 
1000,xxx4ShiftTableSizeno 
1000,xxx5ShiftTableTripletno 
1010,xxxxZonalMapno 
2000,0010NumberOfCopiesno 
2000,001EPrinterConfigurationSequenceno 
2000,0020PrintPriorityno 
2000,0030MediumTypeno 
2000,0040FilmDestinationno 
2000,0050FilmSessionLabelno 
2000,0060MemoryAllocationno 
2000,0061MaximumMemoryAllocationno 
2000,0062ColorImagePrintingFlagno 
2000,0063CollationFlagno 
2000,0065AnnotationFlagno 
2000,0067ImageOverlayFlagno 
2000,0069PresentationLUTFlagno 
2000,006AImageBoxPresentationLUTFlagno 
2000,00A0MemoryBitDepthno 
2000,00A1PrintingBitDepthno 
2000,00A2MediaInstalledSequenceno 
2000,00A4OtherMediaAvailableSequenceno 
2000,00A8SupportedImageDisplayFormatSeqno 
2000,0500ReferencedFilmBoxSequenceno 
2000,0510ReferencedStoredPrintSequenceno 
2010,0010ImageDisplayFormatno 
2010,0030AnnotationDisplayFormatIDno 
2010,0040FilmOrientationno 
2010,0050FilmSizeIDno 
2010,0052PrinterResolutionIDno 
2010,0054DefaultPrinterResolutionIDno 
2010,0060MagnificationTypeno 
2010,0080SmoothingTypeno 
2010,00A6DefaultMagnificationTypeno 
2010,00A7OtherMagnificationTypesAvailableno 
2010,00A8DefaultSmoothingTypeno 
2010,00A9OtherSmoothingTypesAvailableno 
2010,0100BorderDensityno 
2010,0110EmptyImageDensityno 
2010,0120MinDensityno 
2010,0130MaxDensityno 
2010,0140Trimno 
2010,0150ConfigurationInformationno 
2010,0152ConfigurationInformationDescrno 
2010,0154MaximumCollatedFilmsno 
2010,015EIlluminationno 
2010,0160ReflectedAmbientLightno 
2010,0376PrinterPixelSpacingno 
2010,0500ReferencedFilmSessionSequenceno 
2010,0510ReferencedImageBoxSequenceno 
2010,0520ReferencedBasicAnnotationBoxSeqno 
2020,0010ImageBoxPositionno 
2020,0020Polarityno 
2020,0030RequestedImageSizeno 
2020,0040RequestedDecimate-CropBehaviorno 
2020,0050RequestedResolutionIDno 
2020,00A0RequestedImageSizeFlagno 
2020,00A2DecimateCropResultno 
2020,0110BasicGrayscaleImageSequenceno 
2020,0111BasicColorImageSequenceno 
2020,0130ReferencedImageOverlayBoxSequenceno 
2020,0140ReferencedVOILUTBoxSequenceno 
2030,0010AnnotationPositionno 
2030,0020TextStringno 
2040,0010ReferencedOverlayPlaneSequenceno 
2040,0011ReferencedOverlayPlaneGroupsno 
2040,0020OverlayPixelDataSequenceno 
2040,0060OverlayMagnificationTypeno 
2040,0070OverlaySmoothingTypeno 
2040,0072OverlayOrImageMagnificationno 
2040,0074MagnifyToNumberOfColumnsno 
2040,0080OverlayForegroundDensityno 
2040,0082OverlayBackgroundDensityno 
2040,0090OverlayModeno 
2040,0100ThresholdDensityno 
2040,0500ReferencedImageBoxSequenceno 
2050,0010PresentationLUTSequenceno 
2050,0020PresentationLUTShapeno 
2050,0500ReferencedPresentationLUTSequenceno 
2100,0010PrintJobIDno 
2100,0020ExecutionStatusno 
2100,0030ExecutionStatusInfono 
2100,0040CreationDateno 
2100,0050CreationTimeno 
2100,0070Originatorno 
2100,0140DestinationAEno 
2100,0160OwnerIDno 
2100,0170NumberOfFilmsno 
2100,0500ReferencedPrintJobSequenceno 
2110,0010PrinterStatusno 
2110,0020PrinterStatusInfono 
2110,0030PrinterNameno 
2110,0099PrintQueueIDno 
2120,0010QueueStatusno 
2120,0050PrintJobDescriptionSequenceno 
2120,0070ReferencedPrintJobSequenceno 
2130,0010PrintManagementCapabilitiesSeqno 
2130,0015PrinterCharacteristicsSequenceno 
2130,0030FilmBoxContentSequenceno 
2130,0040ImageBoxContentSequenceno 
2130,0050AnnotationContentSequenceno 
2130,0060ImageOverlayBoxContentSequenceno 
2130,0080PresentationLUTContentSequenceno 
2130,00A0ProposedStudySequenceno 
2130,00C0OriginalImageSequenceno 
2200,0001LabelFromInfoExtractedFromInstanceno 
2200,0002LabelTextno 
2200,0003LabelStyleSelectionno 
2200,0004MediaDispositionno 
2200,0005BarcodeValueno 
2200,0006BarcodeSymbologyno 
2200,0007AllowMediaSplittingno 
2200,0008IncludeNonDICOMObjectsno 
2200,0009IncludeDisplayApplicationno 
2200,000ASaveCompInstancesAfterMediaCreateno 
2200,000BTotalNumberMediaPiecesCreatedno 
2200,000CRequestedMediaApplicationProfileno 
2200,000DReferencedStorageMediaSequenceno 
2200,000EFailureAttributesno 
2200,000FAllowLossyCompressionno 
2200,0020RequestPriorityno 
3002,0002RTImageLabelno 
3002,0003RTImageNameno 
3002,0004RTImageDescriptionno 
3002,000AReportedValuesOriginno 
3002,000CRTImagePlaneno 
3002,000DXRayImageReceptorTranslationno 
3002,000EXRayImageReceptorAngleno 
3002,0010RTImageOrientationno 
3002,0011ImagePlanePixelSpacingno 
3002,0012RTImagePositionno 
3002,0020RadiationMachineNameno 
3002,0022RadiationMachineSADno 
3002,0024RadiationMachineSSDno 
3002,0026RTImageSIDno 
3002,0028SourceToReferenceObjectDistanceno 
3002,0029FractionNumberno 
3002,0030ExposureSequenceno 
3002,0032MetersetExposureno 
3002,0034DiaphragmPositionno 
3002,0040FluenceMapSequenceno 
3002,0041FluenceDataSourceno 
3002,0042FluenceDataScaleno 
3002,0051FluenceModeno 
3002,0052FluenceModeIDno 
3004,0001DVHTypeno 
3004,0002DoseUnitsno 
3004,0004DoseTypeno 
3004,0006DoseCommentno 
3004,0008NormalizationPointno 
3004,000ADoseSummationTypeno 
3004,000CGridFrameOffsetVectorno 
3004,000EDoseGridScalingno 
3004,0010RTDoseROISequenceno 
3004,0012DoseValueno 
3004,0014TissueHeterogeneityCorrectionno 
3004,0040DVHNormalizationPointno 
3004,0042DVHNormalizationDoseValueno 
3004,0050DVHSequenceno 
3004,0052DVHDoseScalingno 
3004,0054DVHVolumeUnitsno 
3004,0056DVHNumberOfBinsno 
3004,0058DVHDatano 
3004,0060DVHReferencedROISequenceno 
3004,0062DVHROIContributionTypeno 
3004,0070DVHMinimumDoseno 
3004,0072DVHMaximumDoseno 
3004,0074DVHMeanDoseno 
3006,0002StructureSetLabelno 
3006,0004StructureSetNameno 
3006,0006StructureSetDescriptionno 
3006,0008StructureSetDateno 
3006,0009StructureSetTimeno 
3006,0010ReferencedFrameOfReferenceSequenceno 
3006,0012RTReferencedStudySequenceno 
3006,0014RTReferencedSeriesSequenceno 
3006,0016ContourImageSequenceno 
3006,0020StructureSetROISequenceno 
3006,0022ROINumberno 
3006,0024ReferencedFrameOfReferenceUIDno 
3006,0026ROINameno 
3006,0028ROIDescriptionno 
3006,002AROIDisplayColorno 
3006,002CROIVolumeno 
3006,0030RTRelatedROISequenceno 
3006,0033RTROIRelationshipno 
3006,0036ROIGenerationAlgorithmno 
3006,0038ROIGenerationDescriptionno 
3006,0039ROIContourSequenceno 
3006,0040ContourSequenceno 
3006,0042ContourGeometricTypeno 
3006,0044ContourSlabThicknessno 
3006,0045ContourOffsetVectorno 
3006,0046NumberOfContourPointsno 
3006,0048ContourNumberno 
3006,0049AttachedContoursno 
3006,0050ContourDatano 
3006,0080RTROIObservationsSequenceno 
3006,0082ObservationNumberno 
3006,0084ReferencedROINumberno 
3006,0085ROIObservationLabelno 
3006,0086RTROIIdentificationCodeSequenceno 
3006,0088ROIObservationDescriptionno 
3006,00A0RelatedRTROIObservationsSequenceno 
3006,00A4RTROIInterpretedTypeno 
3006,00A6ROIInterpreterno 
3006,00B0ROIPhysicalPropertiesSequenceno 
3006,00B2ROIPhysicalPropertyno 
3006,00B4ROIPhysicalPropertyValueno 
3006,00B6ROIElementalCompositionSequenceno 
3006,00B7ROIElementalCompAtomicNumberno 
3006,00B8ROIElementalCompAtomicMassFractionno 
3006,00C0FrameOfReferenceRelationshipSeqno 
3006,00C2RelatedFrameOfReferenceUIDno 
3006,00C4FrameOfReferenceTransformTypeno 
3006,00C6FrameOfReferenceTransformMatrixno 
3006,00C8FrameOfReferenceTransformCommentno 
3008,0010MeasuredDoseReferenceSequenceno 
3008,0012MeasuredDoseDescriptionno 
3008,0014MeasuredDoseTypeno 
3008,0016MeasuredDoseValueno 
3008,0020TreatmentSessionBeamSequenceno 
3008,0021TreatmentSessionIonBeamSequenceno 
3008,0022CurrentFractionNumberno 
3008,0024TreatmentControlPointDateno 
3008,0025TreatmentControlPointTimeno 
3008,002ATreatmentTerminationStatusno 
3008,002BTreatmentTerminationCodeno 
3008,002CTreatmentVerificationStatusno 
3008,0030ReferencedTreatmentRecordSequenceno 
3008,0032SpecifiedPrimaryMetersetno 
3008,0033SpecifiedSecondaryMetersetno 
3008,0036DeliveredPrimaryMetersetno 
3008,0037DeliveredSecondaryMetersetno 
3008,003ASpecifiedTreatmentTimeno 
3008,003BDeliveredTreatmentTimeno 
3008,0040ControlPointDeliverySequenceno 
3008,0041IonControlPointDeliverySequenceno 
3008,0042SpecifiedMetersetno 
3008,0044DeliveredMetersetno 
3008,0045MetersetRateSetno 
3008,0046MetersetRateDeliveredno 
3008,0047ScanSpotMetersetsDeliveredno 
3008,0048DoseRateDeliveredno 
3008,0050TreatmentSummaryCalcDoseRefSeqno 
3008,0052CumulativeDoseToDoseReferenceno 
3008,0054FirstTreatmentDateno 
3008,0056MostRecentTreatmentDateno 
3008,005ANumberOfFractionsDeliveredno 
3008,0060OverrideSequenceno 
3008,0061ParameterSequencePointerno 
3008,0062OverrideParameterPointerno 
3008,0063ParameterItemIndexno 
3008,0064MeasuredDoseReferenceNumberno 
3008,0065ParameterPointerno 
3008,0066OverrideReasonno 
3008,0068CorrectedParameterSequenceno 
3008,006ACorrectionValueno 
3008,0070CalculatedDoseReferenceSequenceno 
3008,0072CalculatedDoseReferenceNumberno 
3008,0074CalculatedDoseReferenceDescriptionno 
3008,0076CalculatedDoseReferenceDoseValueno 
3008,0078StartMetersetno 
3008,007AEndMetersetno 
3008,0080ReferencedMeasuredDoseReferenceSeqno 
3008,0082ReferencedMeasuredDoseReferenceNumno 
3008,0090ReferencedCalculatedDoseRefSeqno 
3008,0092ReferencedCalculatedDoseRefNumberno 
3008,00A0BeamLimitingDeviceLeafPairsSeqno 
3008,00B0RecordedWedgeSequenceno 
3008,00C0RecordedCompensatorSequenceno 
3008,00D0RecordedBlockSequenceno 
3008,00E0TreatmentSummaryMeasuredDoseRefSeqno 
3008,00F0RecordedSnoutSequenceno 
3008,00F2RecordedRangeShifterSequenceno 
3008,00F4RecordedLateralSpreadingDeviceSeqno 
3008,00F6RecordedRangeModulatorSequenceno 
3008,0100RecordedSourceSequenceno 
3008,0105SourceSerialNumberno 
3008,0110TreatmentSessionAppSetupSeqno 
3008,0116ApplicationSetupCheckno 
3008,0120RecordedBrachyAccessoryDeviceSeqno 
3008,0122ReferencedBrachyAccessoryDeviceNumno 
3008,0130RecordedChannelSequenceno 
3008,0132SpecifiedChannelTotalTimeno 
3008,0134DeliveredChannelTotalTimeno 
3008,0136SpecifiedNumberOfPulsesno 
3008,0138DeliveredNumberOfPulsesno 
3008,013ASpecifiedPulseRepetitionIntervalno 
3008,013CDeliveredPulseRepetitionIntervalno 
3008,0140RecordedSourceApplicatorSequenceno 
3008,0142ReferencedSourceApplicatorNumberno 
3008,0150RecordedChannelShieldSequenceno 
3008,0152ReferencedChannelShieldNumberno 
3008,0160BrachyControlPointDeliveredSeqno 
3008,0162SafePositionExitDateno 
3008,0164SafePositionExitTimeno 
3008,0166SafePositionReturnDateno 
3008,0168SafePositionReturnTimeno 
3008,0200CurrentTreatmentStatusno 
3008,0202TreatmentStatusCommentno 
3008,0220FractionGroupSummarySequenceno 
3008,0223ReferencedFractionNumberno 
3008,0224FractionGroupTypeno 
3008,0230BeamStopperPositionno 
3008,0240FractionStatusSummarySequenceno 
3008,0250TreatmentDateno 
3008,0251TreatmentTimeno 
300A,0002RTPlanLabelno 
300A,0003RTPlanNameno 
300A,0004RTPlanDescriptionno 
300A,0006RTPlanDateno 
300A,0007RTPlanTimeno 
300A,0009TreatmentProtocolsno 
300A,000APlanIntentno 
300A,000BTreatmentSitesno 
300A,000CRTPlanGeometryno 
300A,000EPrescriptionDescriptionno 
300A,0010DoseReferenceSequenceno 
300A,0012DoseReferenceNumberno 
300A,0013DoseReferenceUIDno 
300A,0014DoseReferenceStructureTypeno 
300A,0015NominalBeamEnergyUnitno 
300A,0016DoseReferenceDescriptionno 
300A,0018DoseReferencePointCoordinatesno 
300A,001ANominalPriorDoseno 
300A,0020DoseReferenceTypeno 
300A,0021ConstraintWeightno 
300A,0022DeliveryWarningDoseno 
300A,0023DeliveryMaximumDoseno 
300A,0025TargetMinimumDoseno 
300A,0026TargetPrescriptionDoseno 
300A,0027TargetMaximumDoseno 
300A,0028TargetUnderdoseVolumeFractionno 
300A,002AOrganAtRiskFullVolumeDoseno 
300A,002BOrganAtRiskLimitDoseno 
300A,002COrganAtRiskMaximumDoseno 
300A,002DOrganAtRiskOverdoseVolumeFractionno 
300A,0040ToleranceTableSequenceno 
300A,0042ToleranceTableNumberno 
300A,0043ToleranceTableLabelno 
300A,0044GantryAngleToleranceno 
300A,0046BeamLimitingDeviceAngleToleranceno 
300A,0048BeamLimitingDeviceToleranceSeqno 
300A,004ABeamLimitingDevicePositionTolno 
300A,004BSnoutPositionToleranceno 
300A,004CPatientSupportAngleToleranceno 
300A,004ETableTopEccentricAngleToleranceno 
300A,004FTableTopPitchAngleToleranceno 
300A,0050TableTopRollAngleToleranceno 
300A,0051TableTopVerticalPositionToleranceno 
300A,0052TableTopLongitudinalPositionTolno 
300A,0053TableTopLateralPositionToleranceno 
300A,0055RTPlanRelationshipno 
300A,0070FractionGroupSequenceno 
300A,0071FractionGroupNumberno 
300A,0072FractionGroupDescriptionno 
300A,0078NumberOfFractionsPlannedno 
300A,0079NumberFractionPatternDigitsPerDayno 
300A,007ARepeatFractionCycleLengthno 
300A,007BFractionPatternno 
300A,0080NumberOfBeamsno 
300A,0082BeamDoseSpecificationPointno 
300A,0084BeamDoseno 
300A,0086BeamMetersetno 
300A,0088BeamDosePointDepthno 
300A,0089BeamDosePointEquivalentDepthno 
300A,008ABeamDosePointSSDno 
300A,00A0NumberOfBrachyApplicationSetupsno 
300A,00A2BrachyAppSetupDoseSpecPointno 
300A,00A4BrachyApplicationSetupDoseno 
300A,00B0BeamSequenceno 
300A,00B2TreatmentMachineNameno 
300A,00B3PrimaryDosimeterUnitno 
300A,00B4SourceAxisDistanceno 
300A,00B6BeamLimitingDeviceSequenceno 
300A,00B8RTBeamLimitingDeviceTypeno 
300A,00BASourceToBeamLimitingDeviceDistanceno 
300A,00BBIsocenterToBeamLimitingDeviceDistno 
300A,00BCNumberOfLeafJawPairsno 
300A,00BELeafPositionBoundariesno 
300A,00C0BeamNumberno 
300A,00C2BeamNameno 
300A,00C3BeamDescriptionno 
300A,00C4BeamTypeno 
300A,00C6RadiationTypeno 
300A,00C7HighDoseTechniqueTypeno 
300A,00C8ReferenceImageNumberno 
300A,00CAPlannedVerificationImageSequenceno 
300A,00CCImagingDeviceSpecificAcqParamsno 
300A,00CETreatmentDeliveryTypeno 
300A,00D0NumberOfWedgesno 
300A,00D1WedgeSequenceno 
300A,00D2WedgeNumberno 
300A,00D3WedgeTypeno 
300A,00D4WedgeIDno 
300A,00D5WedgeAngleno 
300A,00D6WedgeFactorno 
300A,00D7TotalWedgeTrayWaterEquivThicknessno 
300A,00D8WedgeOrientationno 
300A,00D9IsocenterToWedgeTrayDistanceno 
300A,00DASourceToWedgeTrayDistanceno 
300A,00DBWedgeThinEdgePositionno 
300A,00DCBolusIDno 
300A,00DDBolusDescriptionno 
300A,00E0NumberOfCompensatorsno 
300A,00E1MaterialIDno 
300A,00E2TotalCompensatorTrayFactorno 
300A,00E3CompensatorSequenceno 
300A,00E4CompensatorNumberno 
300A,00E5CompensatorIDno 
300A,00E6SourceToCompensatorTrayDistanceno 
300A,00E7CompensatorRowsno 
300A,00E8CompensatorColumnsno 
300A,00E9CompensatorPixelSpacingno 
300A,00EACompensatorPositionno 
300A,00EBCompensatorTransmissionDatano 
300A,00ECCompensatorThicknessDatano 
300A,00EDNumberOfBolino 
300A,00EECompensatorTypeno 
300A,00F0NumberOfBlocksno 
300A,00F2TotalBlockTrayFactorno 
300A,00F3TotalBlockTrayWaterEquivThicknessno 
300A,00F4BlockSequenceno 
300A,00F5BlockTrayIDno 
300A,00F6SourceToBlockTrayDistanceno 
300A,00F7IsocenterToBlockTrayDistanceno 
300A,00F8BlockTypeno 
300A,00F9AccessoryCodeno 
300A,00FABlockDivergenceno 
300A,00FBBlockMountingPositionno 
300A,00FCBlockNumberno 
300A,00FEBlockNameno 
300A,0100BlockThicknessno 
300A,0102BlockTransmissionno 
300A,0104BlockNumberOfPointsno 
300A,0106BlockDatano 
300A,0107ApplicatorSequenceno 
300A,0108ApplicatorIDno 
300A,0109ApplicatorTypeno 
300A,010AApplicatorDescriptionno 
300A,010CCumulativeDoseReferenceCoefficientno 
300A,010EFinalCumulativeMetersetWeightno 
300A,0110NumberOfControlPointsno 
300A,0111ControlPointSequenceno 
300A,0112ControlPointIndexno 
300A,0114NominalBeamEnergyno 
300A,0115DoseRateSetno 
300A,0116WedgePositionSequenceno 
300A,0118WedgePositionno 
300A,011ABeamLimitingDevicePositionSequenceno 
300A,011CLeafJawPositionsno 
300A,011EGantryAngleno 
300A,011FGantryRotationDirectionno 
300A,0120BeamLimitingDeviceAngleno 
300A,0121BeamLimitingDeviceRotateDirectionno 
300A,0122PatientSupportAngleno 
300A,0123PatientSupportRotationDirectionno 
300A,0124TableTopEccentricAxisDistanceno 
300A,0125TableTopEccentricAngleno 
300A,0126TableTopEccentricRotateDirectionno 
300A,0128TableTopVerticalPositionno 
300A,0129TableTopLongitudinalPositionno 
300A,012ATableTopLateralPositionno 
300A,012CIsocenterPositionno 
300A,012ESurfaceEntryPointno 
300A,0130SourceToSurfaceDistanceno 
300A,0134CumulativeMetersetWeightno 
300A,0140TableTopPitchAngleno 
300A,0142TableTopPitchRotationDirectionno 
300A,0144TableTopRollAngleno 
300A,0146TableTopRollRotationDirectionno 
300A,0148HeadFixationAngleno 
300A,014AGantryPitchAngleno 
300A,014CGantryPitchRotationDirectionno 
300A,014EGantryPitchAngleToleranceno 
300A,0180PatientSetupSequenceno 
300A,0182PatientSetupNumberno 
300A,0183PatientSetupLabelno 
300A,0184PatientAdditionalPositionno 
300A,0190FixationDeviceSequenceno 
300A,0192FixationDeviceTypeno 
300A,0194FixationDeviceLabelno 
300A,0196FixationDeviceDescriptionno 
300A,0198FixationDevicePositionno 
300A,0199FixationDevicePitchAngleno 
300A,019AFixationDeviceRollAngleno 
300A,01A0ShieldingDeviceSequenceno 
300A,01A2ShieldingDeviceTypeno 
300A,01A4ShieldingDeviceLabelno 
300A,01A6ShieldingDeviceDescriptionno 
300A,01A8ShieldingDevicePositionno 
300A,01B0SetupTechniqueno 
300A,01B2SetupTechniqueDescriptionno 
300A,01B4SetupDeviceSequenceno 
300A,01B6SetupDeviceTypeno 
300A,01B8SetupDeviceLabelno 
300A,01BASetupDeviceDescriptionno 
300A,01BCSetupDeviceParameterno 
300A,01D0SetupReferenceDescriptionno 
300A,01D2TableTopVerticalSetupDisplacementno 
300A,01D4TableTopLongitudinalSetupDisplaceno 
300A,01D6TableTopLateralSetupDisplacementno 
300A,0200BrachyTreatmentTechniqueno 
300A,0202BrachyTreatmentTypeno 
300A,0206TreatmentMachineSequenceno 
300A,0210SourceSequenceno 
300A,0212SourceNumberno 
300A,0214SourceTypeno 
300A,0216SourceManufacturerno 
300A,0218ActiveSourceDiameterno 
300A,021AActiveSourceLengthno 
300A,0222SourceEncapsulationNomThicknessno 
300A,0224SourceEncapsulationNomTransmissionno 
300A,0226SourceIsotopeNameno 
300A,0228SourceIsotopeHalfLifeno 
300A,0229SourceStrengthUnitsno 
300A,022AReferenceAirKermaRateno 
300A,022BSourceStrengthno 
300A,022CSourceStrengthReferenceDateno 
300A,022ESourceStrengthReferenceTimeno 
300A,0230ApplicationSetupSequenceno 
300A,0232ApplicationSetupTypeno 
300A,0234ApplicationSetupNumberno 
300A,0236ApplicationSetupNameno 
300A,0238ApplicationSetupManufacturerno 
300A,0240TemplateNumberno 
300A,0242TemplateTypeno 
300A,0244TemplateNameno 
300A,0250TotalReferenceAirKermano 
300A,0260BrachyAccessoryDeviceSequenceno 
300A,0262BrachyAccessoryDeviceNumberno 
300A,0263BrachyAccessoryDeviceIDno 
300A,0264BrachyAccessoryDeviceTypeno 
300A,0266BrachyAccessoryDeviceNameno 
300A,026ABrachyAccessoryDeviceNomThicknessno 
300A,026CBrachyAccessoryDevNomTransmissionno 
300A,0280ChannelSequenceno 
300A,0282ChannelNumberno 
300A,0284ChannelLengthno 
300A,0286ChannelTotalTimeno 
300A,0288SourceMovementTypeno 
300A,028ANumberOfPulsesno 
300A,028CPulseRepetitionIntervalno 
300A,0290SourceApplicatorNumberno 
300A,0291SourceApplicatorIDno 
300A,0292SourceApplicatorTypeno 
300A,0294SourceApplicatorNameno 
300A,0296SourceApplicatorLengthno 
300A,0298SourceApplicatorManufacturerno 
300A,029CSourceApplicatorWallNomThicknessno 
300A,029ESourceApplicatorWallNomTransno 
300A,02A0SourceApplicatorStepSizeno 
300A,02A2TransferTubeNumberno 
300A,02A4TransferTubeLengthno 
300A,02B0ChannelShieldSequenceno 
300A,02B2ChannelShieldNumberno 
300A,02B3ChannelShieldIDno 
300A,02B4ChannelShieldNameno 
300A,02B8ChannelShieldNominalThicknessno 
300A,02BAChannelShieldNominalTransmissionno 
300A,02C8FinalCumulativeTimeWeightno 
300A,02D0BrachyControlPointSequenceno 
300A,02D2ControlPointRelativePositionno 
300A,02D4ControlPoint3DPositionno 
300A,02D6CumulativeTimeWeightno 
300A,02E0CompensatorDivergenceno 
300A,02E1CompensatorMountingPositionno 
300A,02E2SourceToCompensatorDistanceno 
300A,02E3TotalCompTrayWaterEquivThicknessno 
300A,02E4IsocenterToCompensatorTrayDistanceno 
300A,02E5CompensatorColumnOffsetno 
300A,02E6IsocenterToCompensatorDistancesno 
300A,02E7CompensatorRelStoppingPowerRationo 
300A,02E8CompensatorMillingToolDiameterno 
300A,02EAIonRangeCompensatorSequenceno 
300A,02EBCompensatorDescriptionno 
300A,0302RadiationMassNumberno 
300A,0304RadiationAtomicNumberno 
300A,0306RadiationChargeStateno 
300A,0308ScanModeno 
300A,030AVirtualSourceAxisDistancesno 
300A,030CSnoutSequenceno 
300A,030DSnoutPositionno 
300A,030FSnoutIDno 
300A,0312NumberOfRangeShiftersno 
300A,0314RangeShifterSequenceno 
300A,0316RangeShifterNumberno 
300A,0318RangeShifterIDno 
300A,0320RangeShifterTypeno 
300A,0322RangeShifterDescriptionno 
300A,0330NumberOfLateralSpreadingDevicesno 
300A,0332LateralSpreadingDeviceSequenceno 
300A,0334LateralSpreadingDeviceNumberno 
300A,0336LateralSpreadingDeviceIDno 
300A,0338LateralSpreadingDeviceTypeno 
300A,033ALateralSpreadingDeviceDescriptionno 
300A,033CLateralSpreadingDevWaterEquivThickno 
300A,0340NumberOfRangeModulatorsno 
300A,0342RangeModulatorSequenceno 
300A,0344RangeModulatorNumberno 
300A,0346RangeModulatorIDno 
300A,0348RangeModulatorTypeno 
300A,034ARangeModulatorDescriptionno 
300A,034CBeamCurrentModulationIDno 
300A,0350PatientSupportTypeno 
300A,0352PatientSupportIDno 
300A,0354PatientSupportAccessoryCodeno 
300A,0356FixationLightAzimuthalAngleno 
300A,0358FixationLightPolarAngleno 
300A,035AMetersetRateno 
300A,0360RangeShifterSettingsSequenceno 
300A,0362RangeShifterSettingno 
300A,0364IsocenterToRangeShifterDistanceno 
300A,0366RangeShifterWaterEquivThicknessno 
300A,0370LateralSpreadingDeviceSettingsSeqno 
300A,0372LateralSpreadingDeviceSettingno 
300A,0374IsocenterToLateralSpreadingDevDistno 
300A,0380RangeModulatorSettingsSequenceno 
300A,0382RangeModulatorGatingStartValueno 
300A,0384RangeModulatorGatingStopValueno 
300A,038AIsocenterToRangeModulatorDistanceno 
300A,0390ScanSpotTuneIDno 
300A,0392NumberOfScanSpotPositionsno 
300A,0394ScanSpotPositionMapno 
300A,0396ScanSpotMetersetWeightsno 
300A,0398ScanningSpotSizeno 
300A,039ANumberOfPaintingsno 
300A,03A0IonToleranceTableSequenceno 
300A,03A2IonBeamSequenceno 
300A,03A4IonBeamLimitingDeviceSequenceno 
300A,03A6IonBlockSequenceno 
300A,03A8IonControlPointSequenceno 
300A,03AAIonWedgeSequenceno 
300A,03ACIonWedgePositionSequenceno 
300A,0401ReferencedSetupImageSequenceno 
300A,0402SetupImageCommentno 
300A,0410MotionSynchronizationSequenceno 
300A,0412ControlPointOrientationno 
300A,0420GeneralAccessorySequenceno 
300A,0421GeneralAccessoryIDno 
300A,0422GeneralAccessoryDescriptionno 
300A,0423GeneralAccessoryTypeno 
300A,0424GeneralAccessoryNumberno 
300C,0002ReferencedRTPlanSequenceno 
300C,0004ReferencedBeamSequenceno 
300C,0006ReferencedBeamNumberno 
300C,0007ReferencedReferenceImageNumberno 
300C,0008StartCumulativeMetersetWeightno 
300C,0009EndCumulativeMetersetWeightno 
300C,000AReferencedBrachyAppSetupSeqno 
300C,000CReferencedBrachyAppSetupNumberno 
300C,000EReferencedSourceNumberno 
300C,0020ReferencedFractionGroupSequenceno 
300C,0022ReferencedFractionGroupNumberno 
300C,0040ReferencedVerificationImageSeqno 
300C,0042ReferencedReferenceImageSequenceno 
300C,0050ReferencedDoseReferenceSequenceno 
300C,0051ReferencedDoseReferenceNumberno 
300C,0055BrachyReferencedDoseReferenceSeqno 
300C,0060ReferencedStructureSetSequenceno 
300C,006AReferencedPatientSetupNumberno 
300C,0080ReferencedDoseSequenceno 
300C,00A0ReferencedToleranceTableNumberno 
300C,00B0ReferencedBolusSequenceno 
300C,00C0ReferencedWedgeNumberno 
300C,00D0ReferencedCompensatorNumberno 
300C,00E0ReferencedBlockNumberno 
300C,00F0ReferencedControlPointIndexno 
300C,00F2ReferencedControlPointSequenceno 
300C,00F4ReferencedStartControlPointIndexno 
300C,00F6ReferencedStopControlPointIndexno 
300C,0100ReferencedRangeShifterNumberno 
300C,0102ReferencedLateralSpreadingDevNumno 
300C,0104ReferencedRangeModulatorNumberno 
300E,0002ApprovalStatusno 
300E,0004ReviewDateno 
300E,0005ReviewTimeno 
300E,0008ReviewerNameno 
4000,0000TextGroupLengthno 
4000,0010Arbitraryno 
4000,4000TextCommentsno 
4008,0040ResultsIDno 
4008,0042ResultsIDIssuerno 
4008,0050ReferencedInterpretationSequenceno 
4008,0100InterpretationRecordedDateno 
4008,0101InterpretationRecordedTimeno 
4008,0102InterpretationRecorderno 
4008,0103ReferenceToRecordedSoundno 
4008,0108InterpretationTranscriptionDateno 
4008,0109InterpretationTranscriptionTimeno 
4008,010AInterpretationTranscriberno 
4008,010BInterpretationTextno 
4008,010CInterpretationAuthorno 
4008,0111InterpretationApproverSequenceno 
4008,0112InterpretationApprovalDateno 
4008,0113InterpretationApprovalTimeno 
4008,0114PhysicianApprovingInterpretationno 
4008,0115InterpretationDiagnosisDescriptionno 
4008,0117InterpretationDiagnosisCodeSeqno 
4008,0118ResultsDistributionListSequenceno 
4008,0119DistributionNameno 
4008,011ADistributionAddressno 
4008,0200InterpretationIDno 
4008,0202InterpretationIDIssuerno 
4008,0210InterpretationTypeIDno 
4008,0212InterpretationStatusIDno 
4008,0300Impressionsno 
4008,4000ResultsCommentsno 
4FFE,0001MACParametersSequenceno 
50xx,0005CurveDimensionsno 
50xx,0010NumberOfPointsno 
50xx,0020TypeOfDatano 
50xx,0022CurveDescriptionno 
50xx,0030AxisUnitsno 
50xx,0040AxisLabelsno 
50xx,0103DataValueRepresentationno 
50xx,0104MinimumCoordinateValueno 
50xx,0105MaximumCoordinateValueno 
50xx,0106CurveRangeno 
50xx,0110CurveDataDescriptorno 
50xx,0112CoordinateStartValueno 
50xx,0114CoordinateStepValueno 
50xx,1001CurveActivationLayerno 
50xx,2000AudioTypeno 
50xx,2002AudioSampleFormatno 
50xx,2004NumberOfChannelsno 
50xx,2006NumberOfSamplesno 
50xx,2008SampleRateno 
50xx,200ATotalTimeno 
50xx,200CAudioSampleDatano 
50xx,200EAudioCommentsno 
50xx,2500CurveLabelno 
50xx,2600ReferencedOverlaySequenceno 
50xx,2610ReferencedOverlayGroupno 
50xx,3000CurveDatano 
5200,9229SharedFunctionalGroupsSequenceno 
5200,9230PerFrameFunctionalGroupsSequenceno 
5400,0100WaveformSequenceno 
5400,0110ChannelMinimumValueno 
5400,0112ChannelMaximumValueno 
5400,1004WaveformBitsAllocatedno 
5400,1006WaveformSampleInterpretationno 
5400,100AWaveformPaddingValueno 
5400,1010WaveformDatano 
5600,0010FirstOrderPhaseCorrectionAngleno 
5600,0020SpectroscopyDatano 
6000,0000OverlayGroupLengthno 
60xx,0010OverlayRowsno 
60xx,0011OverlayColumnsno 
60xx,0012OverlayPlanesno 
60xx,0015NumberOfFramesInOverlayno 
60xx,0022OverlayDescriptionno 
60xx,0040OverlayTypeno 
60xx,0045OverlaySubtypeno 
60xx,0050OverlayOriginno 
60xx,0051ImageFrameOriginno 
60xx,0052OverlayPlaneOriginno 
60xx,0060OverlayCompressionCodeno 
60xx,0061OverlayCompressionOriginatorno 
60xx,0062OverlayCompressionLabelno 
60xx,0063OverlayCompressionDescriptionno 
60xx,0066OverlayCompressionStepPointersno 
60xx,0068OverlayRepeatIntervalno 
60xx,0069OverlayBitsGroupedno 
60xx,0100OverlayBitsAllocatedno 
60xx,0102OverlayBitPositionno 
60xx,0110OverlayFormatno 
60xx,0200OverlayLocationno 
60xx,0800OverlayCodeLabelno 
60xx,0802OverlayNumberOfTablesno 
60xx,0803OverlayCodeTableLocationno 
60xx,0804OverlayBitsForCodeWordno 
60xx,1001OverlayActivationLayerno 
60xx,1100OverlayDescriptorGrayno 
60xx,1101OverlayDescriptorRedno 
60xx,1102OverlayDescriptorGreenno 
60xx,1103OverlayDescriptorBlueno 
60xx,1200OverlaysGrayno 
60xx,1201OverlaysRedno 
60xx,1202OverlaysGreenno 
60xx,1203OverlaysBlueno 
60xx,1301ROIAreano 
60xx,1302ROIMeanno 
60xx,1303ROIStandardDeviationno 
60xx,1500OverlayLabelno 
60xx,3000OverlayDatano 
60xx,4000OverlayCommentsno 
7Fxx,0000PixelDataGroupLengthno 
7Fxx,0010PixelDatano 
7Fxx,0011VariableNextDataGroupno 
7Fxx,0020VariableCoefficientsSDVNno 
7Fxx,0030VariableCoefficientsSDHNno 
7Fxx,0040VariableCoefficientsSDDNno 
FFFA,FFFADigitalSignaturesSequenceno 
FFFC,FFFCDataSetTrailingPaddingno 
FFFE,E000StartOfItemno 
FFFE,E00DEndOfItemsno 
FFFE,E0DDEndOfSequenceno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jan 2, 2013 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/DJI.html b/ExifTool/html/TagNames/DJI.html new file mode 100644 index 0000000..5e879e2 --- /dev/null +++ b/ExifTool/html/TagNames/DJI.html @@ -0,0 +1,439 @@ + + + + +DJI Tags + + + +

DJI Tags

+

This table lists tags found in the maker notes of images from some DJI +Phantom drones.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001Makestring 
0x0003SpeedXfloat 
0x0004SpeedYfloat 
0x0005SpeedZfloat 
0x0006Pitchfloat 
0x0007Yawfloat 
0x0008Rollfloat 
0x0009CameraPitchfloat 
0x000aCameraYawfloat 
0x000bCameraRollfloat 
+ +

DJI XMP Tags

+

XMP tags used by DJI for images from drones.

+ +

These tags belong to the ExifTool XMP-drone-dji family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AbsoluteAltitudereal 
CalibratedFocalLengthreal 
CalibratedOpticalCenterXreal 
CalibratedOpticalCenterYreal 
CamReversestring 
DewarpDatastring 
DewarpFlagstring 
FlightPitchDegreereal 
FlightRollDegreereal 
FlightXSpeedreal 
FlightYSpeedreal 
FlightYawDegreereal 
FlightZSpeedreal 
GPSLatitudereal/ 
GPSLongitudereal/ 
GPSLongtitudereal 
GimbalPitchDegreereal 
GimbalReversestring 
GimbalRollDegreereal 
GimbalYawDegreereal 
Latitudereal 
Longitudereal 
RelativeAltitudereal 
RtkFlagstring 
RtkStdHgtreal 
RtkStdLatreal 
RtkStdLonreal 
SelfDatastring 
+ +

DJI Info Tags

+

Tags written by some DJI drones.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'FlightDegree(Y,P,R)'FlightDegreeno 
'FlightSpeed(X,Y,Z)'FlightSpeedno 
'GimbalDegree(Y,P,R)'GimbalDegreeno 
'adj_dbg_info'ADJDebugInfono 
'ae_dbg_info'AEDebugInfono 
'ae_histogram_info'AEHistogramInfono 
'ae_liveview_histogram_info'AELiveViewHistogramInfono 
'ae_liveview_local_histogram'AELiveViewLocalHistogramno 
'ae_local_histogram'AELocalHistogramno 
'af_dbg_info'AFDebugInfono 
'awb_dbg_info'AWBDebugInfono 
'hiso'Histogramno 
'hyperlapse_dbg_info'HyperlapsDebugInfono 
'sensor_id'SensorIDno 
'xidiri'Xidirino 
+ +

DJI ThermalParams Tags

+

Thermal parameters extracted from APP4 of DJI RJPEG files from the ZH20T.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
36K1no 
40K2no 
44K3no 
48K4no 
52KFno 
56B1no 
60B2no 
68ObjectDistanceno 
70RelativeHumidityno 
72Emissivityno 
74Reflectionno 
76AmbientTemperatureno 
80D2no 
84KJno 
86DBno 
88KKno 
+ +

DJI ThermalParams2 Tags

+

Thermal parameters extracted from APP4 of DJI M3T RJPEG files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AmbientTemperatureno 
4ObjectDistanceno 
8Emissivityno 
12RelativeHumidityno 
16ReflectedTemperatureno 
101IDStringno 
+ +

DJI ThermalParams3 Tags

+

Thermal parameters extracted from APP4 of some DJI RJPEG files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4RelativeHumidityno 
6ObjectDistanceno 
8Emissivityno 
10ReflectedTemperatureno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 15, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/DNG.html b/ExifTool/html/TagNames/DNG.html new file mode 100644 index 0000000..8542fa5 --- /dev/null +++ b/ExifTool/html/TagNames/DNG.html @@ -0,0 +1,312 @@ + + + + +DNG Tags + + + +

DNG Tags

+

+The main DNG tags are found in the EXIF table. The tables below define only +information found within structures of these main DNG tag values. See +http://www.adobe.com/products/dng/ for the official DNG specification. +

+

DNG AdobeData Tags

+

This information is found in the "Adobe" DNGPrivateData.

+ +

The maker notes ('MakN') are processed by ExifTool, but some information may +have been lost by the Adobe DNG Converter. This is because the Adobe DNG +Converter (as of version 6.3) doesn't properly handle information referenced +from inside the maker notes that lies outside the original maker notes +block. This information is lost when only the maker note block is copied to +the DNG image. While this doesn't effect all makes of cameras, it is a +problem for some major brands such as Olympus and Sony.

+ +

Other entries in this table represent proprietary information that is +extracted from the original RAW image and restructured to a different (but +still proprietary) Adobe format.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CRW 'AdobeCRW---> CanonRaw Tags
'Koda'AdobeKoda---> Kodak IFD Tags
'Leaf'AdobeLeaf---> Leaf SubIFD Tags
'MRW 'AdobeMRW---> MinoltaRaw Tags
'MakN'MakerNoteApple +
MakerNoteNikon +
MakerNoteCanon +
MakerNoteCasio +
MakerNoteCasio2 +
MakerNoteDJIInfo +
MakerNoteDJI +
MakerNoteFLIR +
MakerNoteFujiFilm +
MakerNoteGE +
MakerNoteGE2 +
MakerNoteHasselblad +
MakerNoteHP +
MakerNoteHP2 +
MakerNoteHP4 +
MakerNoteHP6 +
MakerNoteISL +
MakerNoteJVC +
MakerNoteJVCText +
MakerNoteKodak1a +
MakerNoteKodak1b +
MakerNoteKodak2 +
MakerNoteKodak3 +
MakerNoteKodak4 +
MakerNoteKodak5 +
MakerNoteKodak6a +
MakerNoteKodak6b +
MakerNoteKodak7 +
MakerNoteKodak8a +
MakerNoteKodak8b +
MakerNoteKodak8c +
MakerNoteKodak9 +
MakerNoteKodak10 +
MakerNoteKodak11 +
MakerNoteKodak12 +
MakerNoteKodakUnknown +
MakerNoteKyocera +
MakerNoteMinolta +
MakerNoteMinolta2 +
MakerNoteMinolta3 +
MakerNoteMotorola +
MakerNoteNikon2 +
MakerNoteNikon3 +
MakerNoteNintendo +
MakerNoteOlympus +
MakerNoteOlympus2 +
MakerNoteOlympus3 +
MakerNoteLeica +
MakerNoteLeica2 +
MakerNoteLeica3 +
MakerNoteLeica4 +
MakerNoteLeica5 +
MakerNoteLeica6 +
MakerNoteLeica7 +
MakerNoteLeica8 +
MakerNoteLeica9 +
MakerNoteLeica10 +
MakerNotePanasonic +
MakerNotePanasonic2 +
MakerNotePanasonic3 +
MakerNotePentax +
MakerNotePentax2 +
MakerNotePentax3 +
MakerNotePentax4 +
MakerNotePentax5 +
MakerNotePentax6 +
MakerNotePhaseOne +
MakerNoteReconyx +
MakerNoteReconyx2 +
MakerNoteReconyx3 +
MakerNoteRicohPentax +
MakerNoteRicoh +
MakerNoteRicoh2 +
MakerNoteRicohText +
MakerNoteSamsung1a +
MakerNoteSamsung1b +
MakerNoteSamsung2 +
MakerNoteSanyo +
MakerNoteSanyoC4 +
MakerNoteSanyoPatch +
MakerNoteSigma +
MakerNoteSony +
MakerNoteSony2 +
MakerNoteSony3 +
MakerNoteSony4 +
MakerNoteSony5 +
MakerNoteSonyEricsson +
MakerNoteSonySRF +
MakerNoteUnknownText +
MakerNoteUnknownBinary +
MakerNoteUnknown
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
--> Apple Tags +
--> Nikon Tags +
--> Canon Tags +
--> Casio Tags +
--> Casio Type2 Tags +
--> DJI Info Tags +
--> DJI Tags +
--> FLIR Tags +
--> FujiFilm Tags +
--> GE Tags +
--> FujiFilm Tags +
--> Unknown Tags +
--> HP Tags +
--> HP Type2 Tags +
--> HP Type4 Tags +
--> HP Type6 Tags +
--> Unknown Tags +
--> JVC Tags +
--> JVC Text Tags +
--> Kodak Tags +
--> Kodak Tags +
--> Kodak Type2 Tags +
--> Kodak Type3 Tags +
--> Kodak Type4 Tags +
--> Kodak Type5 Tags +
--> Kodak Type6 Tags +
--> Kodak Type6 Tags +
--> Kodak Type7 Tags +
--> Kodak Type8 Tags +
--> Kodak Type8 Tags +
--> Kodak Type8 Tags +
--> Kodak Type9 Tags +
--> Kodak Type10 Tags +
--> Kodak Type11 Tags +
--> Kodak Type11 Tags +
--> Kodak Unknown Tags +
--> Unknown Tags +
--> Minolta Tags +
--> Olympus Tags +
(not EXIF-based) +
--> Motorola Tags +
--> Nikon Type2 Tags +
--> Nikon Tags +
--> Nintendo Tags +
--> Olympus Tags +
--> Olympus Tags +
--> Olympus Tags +
--> Panasonic Tags +
--> Panasonic Leica2 Tags +
--> Panasonic Leica3 Tags +
--> Panasonic Leica4 Tags +
--> Panasonic Leica5 Tags +
--> Panasonic Leica6 Tags +
--> Panasonic Leica6 Tags +
--> Panasonic Leica5 Tags +
--> Panasonic Leica9 Tags +
--> Panasonic Tags +
--> Panasonic Tags +
--> Panasonic Type2 Tags +
--> Panasonic Tags +
--> Pentax Tags +
--> Pentax Type2 Tags +
--> Casio Type2 Tags +
--> Pentax Type4 Tags +
--> Pentax Tags +
--> Pentax S1 Tags +
--> PhaseOne Tags +
--> Reconyx Tags +
--> Reconyx Type2 Tags +
--> Reconyx Type3 Tags +
--> Pentax Tags +
--> Ricoh Tags +
--> Ricoh Type2 Tags +
--> Ricoh Text Tags +
(Samsung "STMN" maker notes without PreviewImage) +
--> Samsung Tags +
--> Samsung Type2 Tags +
--> Sanyo Tags +
--> Sanyo Tags +
--> Sanyo Tags +
--> Sigma Tags +
--> Sony Tags +
--> Olympus Tags +
--> Olympus Tags +
--> Sony PIC Tags +
--> Sony Tags +
--> Sony Ericsson Tags +
--> Sony SRF Tags +
(unknown text-based maker notes) +
(unknown binary maker notes) +
--> Unknown Tags
'Pano'AdobePano---> PanasonicRaw Tags
'RAF 'AdobeRAF---> FujiFilm RAF Tags
'SR2 'AdobeSR2---> Sony SR2Private Tags
+ +

DNG OriginalRaw Tags

+

This table defines tags extracted from the DNG OriginalRawFileData +information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000OriginalRawImageno 
0x0001OriginalRawResourceno 
0x0002OriginalRawFileTypeno 
0x0003OriginalRawCreatorno 
0x0004OriginalTHMImageno 
0x0005OriginalTHMResourceno 
0x0006OriginalTHMFileTypeno 
0x0007OriginalTHMCreatorno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 1, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/DPX.html b/ExifTool/html/TagNames/DPX.html new file mode 100644 index 0000000..246213c --- /dev/null +++ b/ExifTool/html/TagNames/DPX.html @@ -0,0 +1,300 @@ + + + + +DPX Tags + + + +

DPX Tags

+

Tags extracted from DPX (Digital Picture Exchange) images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ByteOrderno'SDPX' = Big-endian +
'XPDS' = Little-endian
8HeaderVersionno 
16DPXFileSizeno 
20DittoKeyno0 = Same +
1 = New
36ImageFileNameno 
136CreateDateno 
160Creatorno 
260Projectno 
460Copyrightno 
660EncryptionKeyno 
768Orientationno +
0 = Horizontal (normal) +
1 = Mirror vertical +
2 = Mirror horizontal +
3 = Rotate 180 +
4 = Mirror horizontal and rotate 270 CW +
5 = Rotate 90 CW +
6 = Rotate 270 CW +
7 = Mirror horizontal and rotate 90 CW
+
770ImageElementsno 
772ImageWidthno 
776ImageHeightno 
780DataSignno0 = Unsigned +
1 = Signed
800ComponentsConfigurationno +
0 = User-defined single component +
1 = Red (R) +
2 = Green (G) +
3 = Blue (B) +
4 = Alpha (matte) +
6 = Luminance (Y) +
7 = Chrominance (Cb, Cr, subsampled by two) +
8 = Depth (Z) +
9 = Composite video +
50 = R, G, B +
51 = R, G, B, Alpha +
52 = Alpha, B, G, R +
100 = Cb, Y, Cr, Y (4:2:2) +
101 = Cb, Y, A, Cr, Y, A (4:2:2:4) +
102 = Cb, Y, Cr (4:4:4) +
103 = Cb, Y, Cr, A (4:4:4:4) +
150 = User-defined 2 component element +
151 = User-defined 3 component element +
152 = User-defined 4 component element +
153 = User-defined 5 component element +
154 = User-defined 6 component element +
155 = User-defined 7 component element +
156 = User-defined 8 component element
+
801TransferCharacteristicno +
0 = User-defined +
1 = Printing density +
2 = Linear +
3 = Logarithmic +
4 = Unspecified video +
5 = SMPTE 274M +
6 = ITU-R 704-4 +
7 = ITU-R 601-5 system B or G (625) +
8 = ITU-R 601-5 system M (525) +
9 = Composite video (NTSC) +
10 = Composite video (PAL) +
11 = Z (depth) - linear +
12 = Z (depth) - homogeneous +
13 = SMPTE ADX +
14 = ITU-R 2020 NCL +
15 = ITU-R 2020 CL +
16 = IEC 61966-2-4 xvYCC +
17 = ITU-R 2100 NCL/PQ +
18 = ITU-R 2100 ICtCp/PQ +
19 = ITU-R 2100 NCL/HLG +
20 = ITU-R 2100 ICtCp/HLG +
21 = RP 431-2:2011 Gama 2.6 +
22 = IEC 61966-2-1 sRGB
+
802ColorimetricSpecificationno +
0 = User-defined +
1 = Printing density +
4 = Unspecified video +
5 = SMPTE 274M +
6 = ITU-R 704-4 +
7 = ITU-R 601-5 system B or G (625) +
8 = ITU-R 601-5 system M (525) +
9 = Composite video (NTSC) +
10 = Composite video (PAL) +
13 = SMPTE ADX +
14 = ITU-R 2020 +
15 = P3D65 +
16 = P3DCI +
17 = P3D60 +
18 = ACES
+
803BitDepthno 
820ImageDescriptionno 
892Image2Descriptionno 
964Image3Descriptionno 
1036Image4Descriptionno 
1108Image5Descriptionno 
1180Image6Descriptionno 
1252Image7Descriptionno 
1324Image8Descriptionno 
1432SourceFileNameno 
1532SourceCreateDateno 
1556InputDeviceNameno 
1588InputDeviceSerialNumberno 
1628AspectRationo 
1724OriginalFrameRateno 
1728ShutterAngleno 
1732FrameIDno 
1764SlateInformationno 
1920TimeCodeno 
1940FrameRateno 
1972Reserved5?no 
2048UserIDno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 22, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/DV.html b/ExifTool/html/TagNames/DV.html new file mode 100644 index 0000000..bddb51b --- /dev/null +++ b/ExifTool/html/TagNames/DV.html @@ -0,0 +1,76 @@ + + + + +DV Tags + + + +

DV Tags

+

The following tags are extracted from DV videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AspectRationo 
AudioBitsPerSampleno 
AudioChannelsno 
AudioSampleRateno 
Colorimetryno 
DateTimeOriginalno 
Durationno 
FrameRateno 
ImageHeightno 
ImageWidthno 
TotalBitrateno 
VideoFormatno 
VideoScanTypeno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jan 3, 2011 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/DarwinCore.html b/ExifTool/html/TagNames/DarwinCore.html new file mode 100644 index 0000000..2b0c4c8 --- /dev/null +++ b/ExifTool/html/TagNames/DarwinCore.html @@ -0,0 +1,1891 @@ + + + + +DarwinCore Tags + + + +

DarwinCore Tags

+

Tags defined in the Darwin Core (dwc) XMP namespace. See +http://rs.tdwg.org/dwc/index.htm for the official specification.

+ +

These tags belong to the ExifTool XMP-dwc family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
DCTermsLocationstruct--> DarwinCore DCTermsLocation Struct
DCContinentstring_(dctermsLocationContinent)
DCCoordinatePrecisionstring_(dctermsLocationCoordinatePrecision)
DCCoordinateUncertaintyInMetersstring_(dctermsLocationCoordinateUncertaintyInMeters)
DCCountrystring_(dctermsLocationCountry)
DCCountryCodestring_(dctermsLocationCountryCode)
DCCountystring_(dctermsLocationCounty)
DCDecimalLatitudestring_(dctermsLocationDecimalLatitude)
DCDecimalLongitudestring_(dctermsLocationDecimalLongitude)
DCFootprintSpatialFitstring_(dctermsLocationFootprintSpatialFit)
DCFootprintSRSstring_(dctermsLocationFootprintSRS)
DCFootprintWKTstring_(dctermsLocationFootprintWKT)
DCGeodeticDatumstring_(dctermsLocationGeodeticDatum)
DCGeoreferencedBystring_(dctermsLocationGeoreferencedBy)
DCGeoreferencedDatestring_(dctermsLocationGeoreferencedDate)
DCGeoreferenceProtocolstring_(dctermsLocationGeoreferenceProtocol)
DCGeoreferenceRemarksstring_(dctermsLocationGeoreferenceRemarks)
DCGeoreferenceSourcesstring_(dctermsLocationGeoreferenceSources)
DCGeoreferenceVerificationStatusstring_(dctermsLocationGeoreferenceVerificationStatus)
DCHigherGeographystring_(dctermsLocationHigherGeography)
DCHigherGeographyIDstring_(dctermsLocationHigherGeographyID)
DCIslandstring_(dctermsLocationIsland)
DCIslandGroupstring_(dctermsLocationIslandGroup)
DCLocalitystring_(dctermsLocationLocality)
DCLocationAccordingTostring_(dctermsLocationLocationAccordingTo)
DCLocationIDstring_(dctermsLocationLocationID)
DCLocationRemarksstring_(dctermsLocationLocationRemarks)
DCMaximumDepthInMetersstring_(dctermsLocationMaximumDepthInMeters)
DCMaximumDistanceAboveSurfaceInMetersstring_(dctermsLocationMaximumDistanceAboveSurfaceInMeters)
DCMaximumElevationInMetersstring_(dctermsLocationMaximumElevationInMeters)
DCMinimumDepthInMetersstring_(dctermsLocationMinimumDepthInMeters)
DCMinimumDistanceAboveSurfaceInMetersstring_(dctermsLocationMinimumDistanceAboveSurfaceInMeters)
DCMinimumElevationInMetersstring_(dctermsLocationMinimumElevationInMeters)
DCMunicipalitystring_(dctermsLocationMunicipality)
DCPointRadiusSpatialFitstring_(dctermsLocationPointRadiusSpatialFit)
DCStateProvincestring_(dctermsLocationStateProvince)
DCVerbatimCoordinatesstring_(dctermsLocationVerbatimCoordinates)
DCVerbatimCoordinateSystemstring_(dctermsLocationVerbatimCoordinateSystem)
DCVerbatimDepthstring_(dctermsLocationVerbatimDepth)
DCVerbatimElevationstring_(dctermsLocationVerbatimElevation)
DCVerbatimLatitudestring_(dctermsLocationVerbatimLatitude)
DCVerbatimLocalitystring_(dctermsLocationVerbatimLocality)
DCVerbatimLongitudestring_(dctermsLocationVerbatimLongitude)
DCVerbatimSRSstring_(dctermsLocationVerbatimSRS)
DCVerticalDatumstring_(dctermsLocationVerticalDatum)
DCWaterBodystring_(dctermsLocationWaterBody)
DCEventstruct--> DarwinCore Event Struct +
(called Event by the spec)
EventDayinteger_ 
EventEarliestDatedate_ 
EventEndDayOfYearinteger_ 
EventDatedate_(EventEventDate)
EventIDstring/_(EventEventID; avoided in favor of XMP-iptcExt:EventID)
EventRemarkslang-alt_(EventEventRemarks)
EventTimestring_(EventEventTime)
EventFieldNotesstring_ 
EventFieldNumberstring_ 
EventHabitatstring_ 
EventLatestDatedate_ 
EventMonthinteger_ 
EventParentEventIDstring_ 
EventSampleSizeUnitstring_ 
EventSampleSizeValuestring_ 
EventSamplingEffortstring_ 
EventSamplingProtocolstring_ 
EventStartDayOfYearinteger_ 
EventVerbatimEventDatestring_ 
EventYearinteger_ 
FossilSpecimenstruct--> DarwinCore MaterialSample Struct
FossilSpecimenMaterialSampleIDstring_ 
GeologicalContextstruct--> DarwinCore GeologicalContext Struct
GeologicalContextBedstring_ 
EarliestAgeOrLowestStagestring_(GeologicalContextEarliestAgeOrLowestStage)
EarliestEonOrLowestEonothemstring_(GeologicalContextEarliestEonOrLowestEonothem)
EarliestEpochOrLowestSeriesstring_(GeologicalContextEarliestEpochOrLowestSeries)
EarliestEraOrLowestErathemstring_(GeologicalContextEarliestEraOrLowestErathem)
EarliestPeriodOrLowestSystemstring_(GeologicalContextEarliestPeriodOrLowestSystem)
GeologicalContextFormationstring_ 
GeologicalContextIDstring_(GeologicalContextGeologicalContextID)
GeologicalContextGroupstring_ 
HighestBiostratigraphicZonestring_(GeologicalContextHighestBiostratigraphicZone)
LatestAgeOrHighestStagestring_(GeologicalContextLatestAgeOrHighestStage)
LatestEonOrHighestEonothemstring_(GeologicalContextLatestEonOrHighestEonothem)
LatestEpochOrHighestSeriesstring_(GeologicalContextLatestEpochOrHighestSeries)
LatestEraOrHighestErathemstring_(GeologicalContextLatestEraOrHighestErathem)
LatestPeriodOrHighestSystemstring_(GeologicalContextLatestPeriodOrHighestSystem)
LithostratigraphicTermsstring_(GeologicalContextLithostratigraphicTerms)
LowestBiostratigraphicZonestring_(GeologicalContextLowestBiostratigraphicZone)
GeologicalContextMemberstring_ 
HumanObservationstruct--> DarwinCore Event Struct
HumanObservationDayinteger_ 
HumanObservationEarliestDatedate_ 
HumanObservationEndDayOfYearinteger_ 
HumanObservationEventDatedate_ 
HumanObservationEventIDstring/_(avoided in favor of XMP-iptcExt:EventID)
HumanObservationEventRemarkslang-alt_ 
HumanObservationEventTimestring_ 
HumanObservationFieldNotesstring_ 
HumanObservationFieldNumberstring_ 
HumanObservationHabitatstring_ 
HumanObservationLatestDatedate_ 
HumanObservationMonthinteger_ 
HumanObservationParentEventIDstring_ 
HumanObservationSampleSizeUnitstring_ 
HumanObservationSampleSizeValuestring_ 
HumanObservationSamplingEffortstring_ 
HumanObservationSamplingProtocolstring_ 
HumanObservationStartDayOfYearinteger_ 
HumanObservationVerbatimEventDatestring_ 
HumanObservationYearinteger_ 
Identificationstruct--> DarwinCore Identification Struct
DateIdentifieddate_(IdentificationDateIdentified)
IdentificationIDstring_(IdentificationIdentificationID)
IdentificationQualifierstring_(IdentificationIdentificationQualifier)
IdentificationReferencesstring_(IdentificationIdentificationReferences)
IdentificationRemarksstring_(IdentificationIdentificationRemarks)
IdentificationVerificationStatusstring_(IdentificationIdentificationVerificationStatus)
IdentifiedBystring_(IdentificationIdentifiedBy)
IdentifiedByIDstring_(IdentificationIdentifiedByID)
TypeStatusstring_(IdentificationTypeStatus)
VerbatimIdentificationstring_(IdentificationVerbatimIdentification)
LivingSpecimenstruct--> DarwinCore MaterialSample Struct
LivingSpecimenMaterialSampleIDstring_ 
MachineObservationstruct--> DarwinCore Event Struct
MachineObservationDayinteger_ 
MachineObservationEarliestDatedate_ 
MachineObservationEndDayOfYearinteger_ 
MachineObservationEventDatedate_ 
MachineObservationEventIDstring/_(avoided in favor of XMP-iptcExt:EventID)
MachineObservationEventRemarkslang-alt_ 
MachineObservationEventTimestring_ 
MachineObservationFieldNotesstring_ 
MachineObservationFieldNumberstring_ 
MachineObservationHabitatstring_ 
MachineObservationLatestDatedate_ 
MachineObservationMonthinteger_ 
MachineObservationParentEventIDstring_ 
MachineObservationSampleSizeUnitstring_ 
MachineObservationSampleSizeValuestring_ 
MachineObservationSamplingEffortstring_ 
MachineObservationSamplingProtocolstring_ 
MachineObservationStartDayOfYearinteger_ 
MachineObservationVerbatimEventDatestring_ 
MachineObservationYearinteger_ 
MaterialSamplestruct--> DarwinCore MaterialSample Struct
MaterialSampleIDstring_(MaterialSampleMaterialSampleID)
MeasurementOrFactstruct--> DarwinCore MeasurementOrFact Struct
MeasurementAccuracystring_(MeasurementOrFactMeasurementAccuracy)
MeasurementDeterminedBystring_(MeasurementOrFactMeasurementDeterminedBy)
MeasurementDeterminedDatedate_(MeasurementOrFactMeasurementDeterminedDate)
MeasurementIDstring_(MeasurementOrFactMeasurementID)
MeasurementMethodstring_(MeasurementOrFactMeasurementMethod)
MeasurementRemarksstring_(MeasurementOrFactMeasurementRemarks)
MeasurementTypestring_(MeasurementOrFactMeasurementType)
MeasurementUnitstring_(MeasurementOrFactMeasurementUnit)
MeasurementValuestring_(MeasurementOrFactMeasurementValue)
Occurrencestruct--> DarwinCore Occurrence Struct
OccurrenceAssociatedMediastring_ 
OccurrenceAssociatedOccurrencesstring_ 
OccurrenceAssociatedReferencesstring_ 
OccurrenceAssociatedSequencesstring_ 
OccurrenceAssociatedTaxastring_ 
OccurrenceBehaviorstring_ 
OccurrenceCatalogNumberstring_ 
OccurrenceDegreeOfEstablishmentstring_ 
OccurrenceDispositionstring_ 
OccurrenceEstablishmentMeansstring_ 
OccurrenceGeoreferenceVerificationStatusstring_ 
OccurrenceIndividualCountstring_ 
OccurrenceIndividualIDstring_ 
OccurrenceLifeStagestring_ 
OccurrenceDetailsstring_(OccurrenceOccurrenceDetails)
OccurrenceIDstring_(OccurrenceOccurrenceID)
OccurrenceRemarksstring_(OccurrenceOccurrenceRemarks)
OccurrenceStatusstring_(OccurrenceOccurrenceStatus)
OccurrenceOrganismQuantitystring_ 
OccurrenceOrganismQuantityTypestring_ 
OccurrenceOtherCatalogNumbersstring_ 
OccurrencePathwaystring_ 
OccurrencePreparationsstring_ 
OccurrencePreviousIdentificationsstring_ 
OccurrenceRecordedBystring_ 
OccurrenceRecordedByIDstring_ 
OccurrenceRecordNumberstring_ 
OccurrenceReproductiveConditionstring_ 
OccurrenceSexstring_ 
Organismstruct--> DarwinCore Organism Struct
OrganismAssociatedOccurrencesstring_ 
OrganismAssociatedOrganismsstring_ 
OrganismIDstring_(OrganismOrganismID)
OrganismNamestring_(OrganismOrganismName)
OrganismRemarksstring_(OrganismOrganismRemarks)
OrganismScopestring_(OrganismOrganismScope)
OrganismPreviousIdentificationsstring_ 
PreservedSpecimenstruct--> DarwinCore MaterialSample Struct
PreservedSpecimenMaterialSampleIDstring_ 
Recordstruct--> DarwinCore Record Struct
RecordBasisOfRecordstring_ 
RecordCollectionCodestring_ 
RecordCollectionIDstring_ 
RecordDataGeneralizationsstring_ 
RecordDatasetIDstring_ 
RecordDatasetNamestring_ 
RecordDynamicPropertiesstring_ 
RecordInformationWithheldstring_ 
RecordInstitutionCodestring_ 
RecordInstitutionIDstring_ 
RecordOwnerInstitutionCodestring_ 
ResourceRelationshipstruct--> DarwinCore ResourceRelationship Struct
RelatedResourceIDstring_(ResourceRelationshipRelatedResourceID)
RelationshipAccordingTostring_(ResourceRelationshipRelationshipAccordingTo)
RelationshipEstablishedDatedate_(ResourceRelationshipRelationshipEstablishedDate)
RelationshipOfResourcestring_(ResourceRelationshipRelationshipOfResource)
RelationshipOfResourceIDstring_(ResourceRelationshipRelationshipOfResourceID)
RelationshipRemarksstring_(ResourceRelationshipRelationshipRemarks)
ResourceIDstring_(ResourceRelationshipResourceID)
ResourceRelationshipIDstring_(ResourceRelationshipResourceRelationshipID)
Taxonstruct--> DarwinCore Taxon Struct
TaxonAcceptedNameUsagestring_ 
TaxonAcceptedNameUsageIDstring_ 
TaxonClassstring_ 
TaxonCultivarEpithetstring_ 
TaxonFamilystring_ 
TaxonGenusstring_ 
TaxonHigherClassificationstring_ 
TaxonInfraspecificEpithetstring_ 
TaxonKingdomstring_ 
TaxonNameAccordingTostring_ 
TaxonNameAccordingToIDstring_ 
TaxonNamePublishedInstring_ 
TaxonNamePublishedInIDstring_ 
TaxonNamePublishedInYearstring_ 
TaxonNomenclaturalCodestring_ 
TaxonNomenclaturalStatusstring_ 
TaxonOrderstring_ 
TaxonOriginalNameUsagestring_ 
TaxonOriginalNameUsageIDstring_ 
TaxonParentNameUsagestring_ 
TaxonParentNameUsageIDstring_ 
TaxonPhylumstring_ 
TaxonScientificNamestring_ 
TaxonScientificNameAuthorshipstring_ 
TaxonScientificNameIDstring_ 
TaxonSpecificEpithetstring_ 
TaxonSubgenusstring_ 
TaxonConceptIDstring_(TaxonTaxonConceptID)
TaxonIDstring_(TaxonTaxonID)
TaxonTaxonomicStatusstring_ 
TaxonRankstring_(TaxonTaxonRank)
TaxonRemarksstring_(TaxonTaxonRemarks)
TaxonVerbatimTaxonRankstring_ 
TaxonVernacularNamelang-alt_ 
+ +

DarwinCore DCTermsLocation Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Continentstring 
CoordinatePrecisionstring 
CoordinateUncertaintyInMetersstring 
Countrystring 
CountryCodestring 
Countystring 
DecimalLatitudestring 
DecimalLongitudestring 
FootprintSRSstring 
FootprintSpatialFitstring 
FootprintWKTstring 
GeodeticDatumstring 
GeoreferenceProtocolstring 
GeoreferenceRemarksstring 
GeoreferenceSourcesstring 
GeoreferenceVerificationStatusstring 
GeoreferencedBystring 
GeoreferencedDatestring 
HigherGeographystring 
HigherGeographyIDstring 
Islandstring 
IslandGroupstring 
Localitystring 
LocationAccordingTostring 
LocationIDstring 
LocationRemarksstring 
MaximumDepthInMetersstring 
MaximumDistanceAboveSurfaceInMetersstring 
MaximumElevationInMetersstring 
MinimumDepthInMetersstring 
MinimumDistanceAboveSurfaceInMetersstring 
MinimumElevationInMetersstring 
Municipalitystring 
PointRadiusSpatialFitstring 
StateProvincestring 
VerbatimCoordinateSystemstring 
VerbatimCoordinatesstring 
VerbatimDepthstring 
VerbatimElevationstring 
VerbatimLatitudestring 
VerbatimLocalitystring 
VerbatimLongitudestring 
VerbatimSRSstring 
VerticalDatumstring 
WaterBodystring 
+ +

DarwinCore Event Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Dayinteger 
EarliestDatedate 
EndDayOfYearinteger 
EventDatedate 
EventIDstring(avoided in favor of XMP-iptcExt:EventID)
EventRemarkslang-alt 
EventTimestring 
FieldNotesstring 
FieldNumberstring 
Habitatstring 
LatestDatedate 
Monthinteger 
ParentEventIDstring 
SampleSizeUnitstring 
SampleSizeValuestring 
SamplingEffortstring 
SamplingProtocolstring 
StartDayOfYearinteger 
VerbatimEventDatestring 
Yearinteger 
+ +

DarwinCore MaterialSample Struct

+
+
+ + + + + + + +
Field NameWritableValues / Notes
MaterialSampleIDstring 
+ +

DarwinCore GeologicalContext Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Bedstring 
EarliestAgeOrLowestStagestring 
EarliestEonOrLowestEonothemstring 
EarliestEpochOrLowestSeriesstring 
EarliestEraOrLowestErathemstring 
EarliestPeriodOrLowestSystemstring 
Formationstring 
GeologicalContextIDstring 
Groupstring 
HighestBiostratigraphicZonestring 
LatestAgeOrHighestStagestring 
LatestEonOrHighestEonothemstring 
LatestEpochOrHighestSeriesstring 
LatestEraOrHighestErathemstring 
LatestPeriodOrHighestSystemstring 
LithostratigraphicTermsstring 
LowestBiostratigraphicZonestring 
Memberstring 
+ +

DarwinCore Identification Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
DateIdentifieddate 
IdentificationIDstring 
IdentificationQualifierstring 
IdentificationReferencesstring 
IdentificationRemarksstring 
IdentificationVerificationStatusstring 
IdentifiedBystring 
IdentifiedByIDstring 
TypeStatusstring 
VerbatimIdentificationstring 
+ +

DarwinCore MeasurementOrFact Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
MeasurementAccuracystring 
MeasurementDeterminedBystring 
MeasurementDeterminedDatedate 
MeasurementIDstring 
MeasurementMethodstring 
MeasurementRemarksstring 
MeasurementTypestring 
MeasurementUnitstring 
MeasurementValuestring 
+ +

DarwinCore Occurrence Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
AssociatedMediastring 
AssociatedOccurrencesstring 
AssociatedReferencesstring 
AssociatedSequencesstring 
AssociatedTaxastring 
Behaviorstring 
CatalogNumberstring 
DegreeOfEstablishmentstring 
Dispositionstring 
EstablishmentMeansstring 
GeoreferenceVerificationStatusstring 
IndividualCountstring 
IndividualIDstring 
LifeStagestring 
OccurrenceDetailsstring 
OccurrenceIDstring 
OccurrenceRemarksstring 
OccurrenceStatusstring 
OrganismQuantitystring 
OrganismQuantityTypestring 
OtherCatalogNumbersstring 
Pathwaystring 
Preparationsstring 
PreviousIdentificationsstring 
RecordNumberstring 
RecordedBystring 
RecordedByIDstring 
ReproductiveConditionstring 
Sexstring 
+ +

DarwinCore Organism Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
AssociatedOccurrencesstring 
AssociatedOrganismsstring 
OrganismIDstring 
OrganismNamestring 
OrganismRemarksstring 
OrganismScopestring 
PreviousIdentificationsstring 
+ +

DarwinCore Record Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
BasisOfRecordstring 
CollectionCodestring 
CollectionIDstring 
DataGeneralizationsstring 
DatasetIDstring 
DatasetNamestring 
DynamicPropertiesstring 
InformationWithheldstring 
InstitutionCodestring 
InstitutionIDstring 
OwnerInstitutionCodestring 
+ +

DarwinCore ResourceRelationship Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
RelatedResourceIDstring 
RelationshipAccordingTostring 
RelationshipEstablishedDatedate 
RelationshipOfResourcestring 
RelationshipOfResourceIDstring 
RelationshipRemarksstring 
ResourceIDstring 
ResourceRelationshipIDstring 
+ +

DarwinCore Taxon Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
AcceptedNameUsagestring 
AcceptedNameUsageIDstring 
Classstring 
CultivarEpithetstring 
Familystring 
Genusstring 
HigherClassificationstring 
InfraspecificEpithetstring 
Kingdomstring 
NameAccordingTostring 
NameAccordingToIDstring 
NamePublishedInstring 
NamePublishedInIDstring 
NamePublishedInYearstring 
NomenclaturalCodestring 
NomenclaturalStatusstring 
Orderstring 
OriginalNameUsagestring 
OriginalNameUsageIDstring 
ParentNameUsagestring 
ParentNameUsageIDstring 
Phylumstring 
ScientificNamestring 
ScientificNameAuthorshipstring 
ScientificNameIDstring 
SpecificEpithetstring 
Subgenusstring 
TaxonConceptIDstring 
TaxonIDstring 
TaxonRankstring 
TaxonRemarksstring 
TaxonomicStatusstring 
VerbatimTaxonRankstring 
VernacularNamelang-alt 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 21, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/DjVu.html b/ExifTool/html/TagNames/DjVu.html new file mode 100644 index 0000000..e90122f --- /dev/null +++ b/ExifTool/html/TagNames/DjVu.html @@ -0,0 +1,314 @@ + + + + +DjVu Tags + + + +

DjVu Tags

+

Information is extracted from the following chunks in DjVu images. See +http://www.djvu.org/ for the DjVu specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ANTa'ANTa---> DjVu Ant Tags
'ANTz'CompressedAnnotation---> DjVu Ant Tags
'FORM'FORM---> DjVu Form Tags
'INCL'IncludedFileIDno 
'INFO'INFO---> DjVu Info Tags
+ +

DjVu Ant Tags

+

Information extracted from annotation chunks.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'metadata'Metadata---> DjVu Meta Tags
'xmp'XMP---> XMP Tags
+ +

DjVu Meta Tags

+

This table lists the standard DjVu metadata tags, but ExifTool will extract +any tags that exist even if they don't appear here. The DjVu v3 +documentation endorses tags borrowed from two standards: 1) BibTeX +bibliography system tags (all lowercase Tag ID's in the table below), and 2) +PDF DocInfo tags (capitalized Tag ID's).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Author'Authorno 
'CreationDate'CreateDateno 
'Creator'Creatorno 
'Keywords'Keywordsno 
'ModDate'ModifyDateno 
'Producer'Producerno 
'Subject'Subjectno 
'Title'Titleno 
'Trapped'Trappedno 
'address'Addressno 
'annote'Annotationno 
'author'Authorno 
'booktitle'BookTitleno 
'chapter'Chapterno 
'crossref'CrossRefno 
'edition'Editionno 
'eprint'EPrintno 
'howpublished'HowPublishedno 
'institution'Institutionno 
'journal'Journalno 
'key'Keyno 
'month'Monthno 
'note'Noteno 
'number'Numberno 
'organization'Organizationno 
'pages'Pagesno 
'publisher'Publisherno 
'school'Schoolno 
'series'Seriesno 
'title'Titleno 
'type'Typeno 
'url'URLno 
'volume'Volumeno 
'year'Yearno 
+ +

DjVu Form Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0SubfileTypeno +
'BM44' = Grayscale IW44 +
'DJVI' = Shared component +
'DJVM' = Multi-page document +
'DJVU' = Single-page image +
'PM44' = Color IW44 +
'THUM' = Thumbnail image
+
+ +

DjVu Info Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ImageWidthno 
2ImageHeightno 
4DjVuVersionno 
6SpatialResolutionno 
8Gammano 
9Orientationno[val & 0x7] +
1 = Horizontal (normal) +
2 = Rotate 180 +
5 = Rotate 90 CW +
6 = Rotate 270 CW
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 24, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/EXE.html b/ExifTool/html/TagNames/EXE.html new file mode 100644 index 0000000..ee557d4 --- /dev/null +++ b/ExifTool/html/TagNames/EXE.html @@ -0,0 +1,766 @@ + + + + +EXE Tags + + + +

EXE Tags

+

This module extracts information from various types of Windows, MacOS and +Unix executable and library files. The first table below lists information +extracted from the header of Windows PE (Portable Executable) EXE files and +DLL libraries.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0MachineTypeno +
0x14c = Intel 386 or later, and compatibles +
0x14d = Intel i860 +
0x162 = MIPS R3000 +
0x166 = MIPS little endian (R4000) +
0x168 = MIPS R10000 +
0x169 = MIPS little endian WCI v2 +
0x183 = Alpha AXP (old) +
0x184 = Alpha AXP +
0x1a2 = Hitachi SH3 +
0x1a3 = Hitachi SH3 DSP +
0x1a6 = Hitachi SH4 +
0x1a8 = Hitachi SH5 +
0x1c0 = ARM little endian +
0x1c2 = Thumb +
0x1d3 = Matsushita AM33 +
0x1f0 = PowerPC little endian +
0x1f1 = PowerPC with floating point support +
0x200 = Intel IA64 +
0x266 = MIPS16 +
0x268 = Motorola 68000 series +
0x284 = Alpha AXP 64-bit +
0x366 = MIPS with FPU +
0x466 = MIPS16 with FPU +
0xebc = EFI Byte Code +
0x8664 = AMD AMD64 +
0x9041 = Mitsubishi M32R little endian +
0xc0ee = clr pure MSIL
+
2TimeStampno 
9ImageFileCharacteristicsno +
Bit 0 = No relocs +
Bit 1 = Executable +
Bit 2 = No line numbers +
Bit 3 = No symbols +
Bit 4 = Aggressive working-set trim +
Bit 5 = Large address aware +
Bit 7 = Bytes reversed lo +
Bit 8 = 32-bit +
Bit 9 = No debug +
Bit 10 = Removable run from swap +
Bit 11 = Net run from swap +
Bit 12 = System file +
Bit 13 = DLL +
Bit 14 = Uniprocessor only +
Bit 15 = Bytes reversed hi
+
10PETypeno0x107 = ROM Image +
0x10b = PE32 +
0x20b = PE32+
11LinkerVersionno 
12CodeSizeno 
14InitializedDataSizeno 
16UninitializedDataSizeno 
18EntryPointno 
30OSVersionno 
32ImageVersionno 
34SubsystemVersionno 
44Subsystemno +
0 = Unknown +
1 = Native +
2 = Windows GUI +
3 = Windows command line +
5 = OS/2 command line +
7 = POSIX command line +
9 = Windows CE GUI +
10 = EFI application +
11 = EFI boot service +
12 = EFI runtime driver +
13 = EFI ROM +
14 = XBOX
+
+ +

EXE PEVersion Tags

+

Information extracted from the VS_VERSION_INFO structure of Windows PE +files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
2FileVersionNumberno 
4ProductVersionNumberno 
6FileFlagsMaskno 
7FileFlagsno + +
Bit 0 = Debug +
Bit 1 = Pre-release +
Bit 2 = Patched
  Bit 3 = Private build +
Bit 4 = Info inferred +
Bit 5 = Special build
+
8FileOSno +
0x1 = Win16 +
0x2 = PM-16 +
0x3 = PM-32 +
0x4 = Win32 +
0x10000 = DOS +
0x10001 = Windows 16-bit +
0x10004 = Windows 32-bit +
0x20000 = OS/2 16-bit +
0x20002 = OS/2 16-bit PM-16 +
0x30000 = OS/2 32-bit +
0x30003 = OS/2 32-bit PM-32 +
0x40000 = Windows NT +
0x40004 = Windows NT 32-bit
+
9ObjectFileTypeno +
0 = Unknown +
1 = Executable application +
2 = Dynamic link library +
3 = Driver +
4 = Font +
5 = VxD +
7 = Static library
+
10FileSubtypeno 
+ +

EXE PEString Tags

+

Resource strings found in Windows PE files. The TagID's are not shown +because they are the same as the Tag Name. ExifTool will extract any +existing StringFileInfo tags even if not listed in this table.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
BuildDateno 
BuildVersionno 
CharacterSetno(extracted from the StringFileInfo value) +
'0000' = ASCII +
'03A4' = Windows, Japan (Shift - JIS X-0208) +
'03A8' = Windows, Chinese (Simplified) +
'03B5' = Windows, Korea (Shift - KSC 5601) +
'03B6' = Windows, Taiwan (Big5) +
'04B0' = Unicode +
'04E2' = Windows, Latin2 (Eastern European) +
'04E3' = Windows, Cyrillic +
'04E4' = Windows, Latin1 +
'04E5' = Windows, Greek +
'04E6' = Windows, Turkish +
'04E7' = Windows, Hebrew +
'04E8' = Windows, Arabic
+
Commentsno 
CompanyNameno 
Copyrightno 
FileDescriptionno 
FileVersionno 
InternalNameno 
LanguageCodeno--> EXE LanguageCode Values +
(Windows code page; extracted from the StringFileInfo value)
LegalCopyrightno 
LegalTrademarksno 
OriginalFileNameno 
PrivateBuildno 
ProductNameno 
ProductVersionno 
SpecialBuildno 
+ +

EXE LanguageCode Values

+

See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid +for the full list of Microsoft language codes.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLanguageCodeValueLanguageCodeValueLanguageCode
'0000'= Neutral'0437'= Georgian'040E'= Hungarian
'0400'= Process default'0438'= Faeroese'040F'= Icelandic
'0401'= Arabic'0439'= Hindi'041A'= Croato-Serbian (Latin)
'0402'= Bulgarian'0440'= Kyrgyz'041B'= Slovak
'0403'= Catalan'0441'= Swahili'041C'= Albanian
'0404'= Chinese (Traditional)'0443'= Uzbek'041D'= Swedish
'0405'= Czech'0444'= Tatar'041E'= Thai
'0406'= Danish'0445'= Bengali'041F'= Turkish
'0407'= German'0446'= Punjabi'042a'= Vietnamese
'0408'= Greek'0447'= Gujarati'042b'= Armenian
'0409'= English (U.S.)'0448'= Oriya'042c'= Azeri
'0410'= Italian'0449'= Tamil'042d'= Basque
'0411'= Japanese'0450'= Mongolian'042e'= Sorbian
'0412'= Korean'0456'= Galician'042f'= Macedonian
'0413'= Dutch'0457'= Konkani'043a'= Maltese
'0414'= Norwegian (Bokml)'0458'= Manipuri'043b'= Saami
'0415'= Polish'0459'= Sindhi'043c'= Gaelic
'0416'= Portuguese (Brazilian)'0460'= Kashmiri'043e'= Malay
'0417'= Rhaeto-Romanic'0461'= Nepali'043f'= Kazak
'0418'= Romanian'0465'= Divehi'044a'= Telugu
'0419'= Russian'0490'= Walon'044b'= Kannada
'0420'= Urdu'0491'= Cornish'044c'= Malayalam
'0421'= Indonesian'0492'= Welsh'044d'= Assamese
'0422'= Ukrainian'0493'= Breton'044e'= Marathi
'0423'= Belarusian'0800'= Neutral 2'044f'= Sanskrit
'0424'= Slovenian'0804'= Chinese (Simplified)'045a'= Syriac
'0425'= Estonian'0807'= German (Swiss)'047f'= Invariant
'0426'= Latvian'0809'= English (British)'048f'= Esperanto
'0427'= Lithuanian'0810'= Italian (Swiss)'080A'= Spanish (Mexican)
'0428'= Maori'0813'= Dutch (Belgian)'080C'= French (Belgian)
'0429'= Farsi'0814'= Norwegian (Nynorsk)'081A'= Serbo-Croatian (Cyrillic)
'0430'= Sutu'0816'= Portuguese'0C07'= German (Austrian)
'0431'= Tsonga'1009'= English (Canadian)'0C09'= English (Australian)
'0432'= Tswana'007F'= Invariant'0C0A'= Spanish (Modern)
'0433'= Venda'040A'= Spanish (Castilian)'0C0C'= French (Canadian)
'0434'= Xhosa'040B'= Finnish'100C'= French (Swiss)
'0435'= Zulu'040C'= French  
'0436'= Afrikaans'040D'= Hebrew  
+ +

EXE MachO Tags

+

Information extracted from Mach-O (Mac OS X) executable files and DYLIB +libraries.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0CPUArchitectureno 
1CPUByteOrderno 
2CPUCountno 
3CPUTypeno+ + +
-1 = Any +
1 = VAX +
2 = ROMP +
4 = NS32032 +
5 = NS32332 +
6 = MC680x0 +
7 = x86 +
8 = MIPS +
9 = NS32532 +
10 = MC98000
  11 = HPPA +
12 = ARM +
13 = MC88000 +
14 = SPARC +
15 = i860 big endian +
16 = i860 little endian +
17 = RS6000 +
18 = PowerPC +
255 = VEO
+
4CPUSubtypeno+ +
'1 0' = VAX (all) +
'1 1' = VAX780 +
'1 2' = VAX785 +
'1 3' = VAX750 +
'1 4' = VAX730 +
'1 5' = UVAXI +
'1 6' = UVAXII +
'1 7' = VAX8200 +
'1 8' = VAX8500 +
'1 9' = VAX8600 +
'1 10' = VAX8650 +
'1 11' = VAX8800 +
'1 12' = UVAXIII +
'2 0' = RT (all) +
'2 1' = RT PC +
'2 2' = RT APC +
'2 3' = RT 135 +
'4 0' = NS32032 (all) +
'4 1' = NS32032 DPC (032 CPU) +
'4 2' = NS32032 SQT +
'4 3' = NS32032 APC FPU (32081) +
'4 4' = NS32032 APC FPA (Weitek) +
'4 5' = NS32032 XPC (532) +
'5 0' = NS32332 (all) +
'5 1' = NS32332 DPC (032 CPU) +
'5 2' = NS32332 SQT +
'5 3' = NS32332 APC FPU (32081) +
'5 4' = NS32332 APC FPA (Weitek) +
'5 5' = NS32332 XPC (532) +
'6 1' = MC680x0 (all) +
'6 2' = MC68040 +
'6 3' = MC68030 +
'7 3' = i386 (all) +
'7 4' = i486 +
'7 5' = i586 +
'7 8' = Pentium III +
'7 9' = Pentium M +
'7 10' = Pentium 4 +
'7 11' = Itanium +
'7 12' = Xeon +
'7 22' = Pentium Pro +
'7 24' = Pentium III M +
'7 26' = Pentium 4 M +
'7 27' = Itanium 2 +
'7 28' = Xeon MP +
'7 40' = Pentium III Xeon +
'7 54' = Pentium II M3 +
'7 86' = Pentium II M5 +
'7 103' = Celeron +
'7 119' = Celeron Mobile +
'7 132' = i486SX +
'8 0' = MIPS (all) +
'8 1' = MIPS R2300 +
'8 2' = MIPS R2600 +
'8 3' = MIPS R2800 +
'8 4' = MIPS R2000a +
'8 5' = MIPS R2000 +
'8 6' = MIPS R3000a +
'8 7' = MIPS R3000 +
'10 0' = MC98000 (all) +
'10 1' = MC98601 +
'11 0' = HPPA (all) +
'11 1' = HPPA 7100LC +
'12 0' = ARM (all) +
'12 1' = ARM A500 ARCH +
'12 2' = ARM A500 +
'12 3' = ARM A440 +
'12 4' = ARM M4 +
'12 5' = ARM A680/V4T +
'12 6' = ARM V6 +
'12 7' = ARM V5TEJ +
'12 8' = ARM XSCALE +
'12 9' = ARM V7 +
'13 0' = MC88000 (all) +
'13 1' = MC88100 +
'13 2' = MC88110 +
'14 0' = SPARC (all) +
'14 1' = SUN 4/260 +
'14 2' = SUN 4/110 +
'15 0' = i860 (all) +
'15 1' = i860 860 +
'16 0' = i860 little (all) +
'16 1' = i860 little +
'17 0' = RS6000 (all) +
'17 1' = RS6000 +
'18 0' = PowerPC (all) +
'18 1' = PowerPC 601 +
'18 2' = PowerPC 602 +
'18 3' = PowerPC 603 +
'18 4' = PowerPC 603e +
'18 5' = PowerPC 603ev +
'18 6' = PowerPC 604 +
'18 7' = PowerPC 604e +
'18 8' = PowerPC 620 +
'18 9' = PowerPC 750 +
'18 10' = PowerPC 7400 +
'18 11' = PowerPC 7450 +
'18 100' = PowerPC 970 +
'255 1' = VEO 1 +
'255 2' = VEO 2
+
5ObjectFileTypeno +
-1 = Static library +
0x1 = Relocatable object +
0x2 = Demand paged executable +
0x3 = Fixed VM shared library +
0x4 = Core +
0x5 = Preloaded executable +
0x6 = Dynamically bound shared library +
0x7 = Dynamic link editor +
0x8 = Dynamically bound bundle +
0x9 = Shared library stub for static linking +
0xa = Debug information +
0xb = x86_64 kexts
+
6ObjectFlagsno +
Bit 0 = No undefs +
Bit 1 = Incrementa link +
Bit 2 = Dyld link +
Bit 3 = Bind at load +
Bit 4 = Prebound +
Bit 5 = Split segs +
Bit 6 = Lazy init +
Bit 7 = Two level +
Bit 8 = Force flat +
Bit 9 = No multi defs +
Bit 10 = No fix prebinding +
Bit 11 = Prebindable +
Bit 12 = All mods bound +
Bit 13 = Subsections via symbols +
Bit 14 = Canonical +
Bit 15 = Weak defines +
Bit 16 = Binds to weak +
Bit 17 = Allow stack execution +
Bit 18 = Dead strippable dylib +
Bit 19 = Root safe +
Bit 20 = No reexported dylibs +
Bit 21 = Random address
+
+ +

EXE PEF Tags

+

Information extracted from PEF (Classic MacOS) executable files and +libraries.

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
2CPUArchitectureno'm68k' = 68000 +
'pwpc' = PowerPC
3PEFVersionno 
4TimeStampno 
+ +

EXE ELF Tags

+

Information extracted from ELF (Unix) executable files and SO libraries.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4CPUArchitectureno1 = 32 bit +
2 = 64 bit
5CPUByteOrderno1 = Little endian +
2 = Big endian
16ObjectFileTypeno0 = None +
1 = Relocatable file +
2 = Executable file +
3 = Shared object file +
4 = Core file
18CPUTypeno0 = None +
1 = AT&T WE 32100 +
2 = SPARC +
3 = i386 +
4 = Motorola 68000 +
5 = Motorola 88000 +
6 = i486 +
7 = i860 +
8 = MIPS R3000 +
10 = MIPS R4000 +
15 = HPPA +
18 = Sun v8plus +
20 = PowerPC +
21 = PowerPC 64-bit +
22 = IBM S/390 +
23 = Cell BE SPU +
42 = SuperH +
43 = SPARC v9 64-bit +
46 = Renesas H8/300,300H,H8S +
50 = HP/Intel IA-64 +
62 = AMD x86-64 +
76 = Axis Communications 32-bit embedded processor +
87 = NEC v850 +
88 = Renesas M32R +
21569 = Fujitsu FR-V +
36902 = Alpha +
36929 = m32r (old) +
36992 = v850 (old) +
41872 = S/390 (old)
+ +

EXE AR Tags

+

Information extracted from static libraries.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
16CreateDateno 
+ +

EXE CHM Tags

+

Tags extracted from Microsoft Compiled HTML files.

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1CHMVersionno 
5LanguageCodeno--> EXE LanguageCode Values
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 19, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/EXIF.html b/ExifTool/html/TagNames/EXIF.html new file mode 100644 index 0000000..bead3e1 --- /dev/null +++ b/ExifTool/html/TagNames/EXIF.html @@ -0,0 +1,3892 @@ + + + + +EXIF Tags + + + +

EXIF Tags

+

+EXIF stands for "Exchangeable Image File Format". This type of information +is formatted according to the TIFF specification, and may be found in JPG, +TIFF, PNG, JP2, PGF, MIFF, HDP, PSP and XCF images, as well as many +TIFF-based RAW images, and even some AVI and MOV videos.

+ +

The EXIF meta information is organized into different Image File Directories +(IFD's) within an image. The names of these IFD's correspond to the +ExifTool family 1 group names. When writing EXIF information, the default +Group listed below is used unless another group is specified.

+ +

Mandatory tags (indicated by a colon after the Writable type) may be +added automatically with default values when creating a new IFD, and the IFD +is removed automatically when deleting tags if only default-valued mandatory +tags remain.

+ +

The table below lists all EXIF tags. Also listed are TIFF, DNG, HDP and +other tags which are not part of the EXIF specification, but may co-exist +with EXIF tags in some images. Tags which are part of the EXIF 2.32 +specification have an underlined Tag Name in the HTML version of this +documentation. See +https://web.archive.org/web/20190624045241if_/http://www.cipa.jp:80/std/documents/e/DC-008-Translation-2019-E.pdf +for the official EXIF 2.32 specification. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableGroupValues / Notes
0x0001InteropIndexstring!InteropIFD'R03' = R03 - DCF option file (Adobe RGB) +
'R98' = R98 - DCF basic file (sRGB) +
'THM' = THM - DCF thumbnail file
0x0002InteropVersionundef!:InteropIFD 
0x000bProcessingSoftwarestringIFD0(used by ACD Systems Digital Imaging)
0x00feSubfileTypeint32u!IFD0(called NewSubfileType by the TIFF specification) +
0x0 = Full-resolution image +
0x1 = Reduced-resolution image +
0x2 = Single page of multi-page image +
0x3 = Single page of multi-page reduced-resolution image +
0x4 = Transparency mask +
0x5 = Transparency mask of reduced-resolution image +
0x6 = Transparency mask of multi-page image +
0x7 = Transparency mask of reduced-resolution multi-page image +
0x8 = Depth map +
0x9 = Depth map of reduced-resolution image +
0x10 = Enhanced image data +
0x10001 = Alternate reduced-resolution image +
0x10004 = Semantic Mask +
0xffffffff = invalid +
Bit 0 = Reduced resolution +
Bit 1 = Single page +
Bit 2 = Transparency mask +
Bit 3 = TIFF/IT final page +
Bit 4 = TIFF-FX mixed raster content
0x00ffOldSubfileTypeint16u!IFD0(called SubfileType by the TIFF specification) +
1 = Full-resolution image +
2 = Reduced-resolution image +
3 = Single page of multi-page image
0x0100ImageWidthint32u!IFD0 
0x0101ImageHeightint32u!IFD0(called ImageLength by the EXIF spec.)
0x0102BitsPerSampleint16u[n]!IFD0 
0x0103Compressionint16u!:IFD0--> EXIF Compression Values
0x0106PhotometricInterpretationint16u!IFD0 +
0 = WhiteIsZero +
1 = BlackIsZero +
2 = RGB +
3 = RGB Palette +
4 = Transparency Mask +
5 = CMYK +
6 = YCbCr +
8 = CIELab +
9 = ICCLab +
10 = ITULab +
32803 = Color Filter Array +
32844 = Pixar LogL +
32845 = Pixar LogLuv +
32892 = Sequential Color Filter +
34892 = Linear Raw +
51177 = Depth Map +
52527 = Semantic Mask
+
0x0107Thresholdingint16u!IFD01 = No dithering or halftoning +
2 = Ordered dither or halftone +
3 = Randomized dither
0x0108CellWidthint16u!IFD0 
0x0109CellLengthint16u!IFD0 
0x010aFillOrderint16u!IFD01 = Normal +
2 = Reversed
0x010dDocumentNamestringIFD0 
0x010eImageDescriptionstringIFD0 
0x010fMakestringIFD0 
0x0110ModelstringIFD0 
0x0111StripOffsets +
OtherImageStart +
StripOffsets +
PreviewImageStart +
PreviewImageStart +
JpgFromRawStart
no
no
no
int32u*
int32u*
int32u*
-
-
-
IFD0
All
SubIFD2
(called StripOffsets in most locations, but it is PreviewImageStart in IFD0 +of CR2 images and various IFD's of DNG images except for SubIFD2 where it is +JpgFromRawStart)
0x0112Orientationint16uIFD0 +
1 = Horizontal (normal) +
2 = Mirror horizontal +
3 = Rotate 180 +
4 = Mirror vertical +
5 = Mirror horizontal and rotate 270 CW +
6 = Rotate 90 CW +
7 = Mirror horizontal and rotate 90 CW +
8 = Rotate 270 CW
+
0x0115SamplesPerPixelint16u!IFD0 
0x0116RowsPerStripint32u!IFD0 
0x0117StripByteCounts +
OtherImageLength +
StripByteCounts +
PreviewImageLength +
PreviewImageLength +
JpgFromRawLength
no
no
no
int32u*
int32u*
int32u*
-
-
-
IFD0
All
SubIFD2
(called StripByteCounts in most locations, but it is PreviewImageLength in +IFD0 of CR2 images and various IFD's of DNG images except for SubIFD2 where +it is JpgFromRawLength)
0x0118MinSampleValueint16uIFD0 
0x0119MaxSampleValueint16uIFD0 
0x011aXResolutionrational64u:IFD0 
0x011bYResolutionrational64u:IFD0 
0x011cPlanarConfigurationint16u!IFD01 = Chunky +
2 = Planar
0x011dPageNamestringIFD0 
0x011eXPositionrational64uIFD0 
0x011fYPositionrational64uIFD0 
0x0120FreeOffsetsno- 
0x0121FreeByteCountsno- 
0x0122GrayResponseUnitint16uIFD01 = 0.1 +
2 = 0.001 +
3 = 0.0001 +
4 = 1e-05 +
5 = 1e-06
0x0123GrayResponseCurveno- 
0x0124T4Optionsno-Bit 0 = 2-Dimensional encoding +
Bit 1 = Uncompressed +
Bit 2 = Fill bits added
0x0125T6Optionsno-Bit 1 = Uncompressed
0x0128ResolutionUnitint16u:IFD0(the value 1 is not standard EXIF) +
1 = None +
2 = inches +
3 = cm
0x0129PageNumberint16u[2]IFD0 
0x012cColorResponseUnitno- 
0x012dTransferFunctionint16u[768]!IFD0 
0x0131SoftwarestringIFD0 
0x0132ModifyDatestringIFD0(called DateTime by the EXIF spec.)
0x013bArtiststringIFD0(becomes a list-type tag when the MWG module is loaded)
0x013cHostComputerstringIFD0 
0x013dPredictorint16u!IFD0 +
1 = None +
2 = Horizontal differencing +
3 = Floating point +
34892 = Horizontal difference X2 +
34893 = Horizontal difference X4 +
34894 = Floating point X2 +
34895 = Floating point X4
+
0x013eWhitePointrational64u[2]IFD0 
0x013fPrimaryChromaticitiesrational64u[6]IFD0 
0x0140ColorMapno- 
0x0141HalftoneHintsint16u[2]IFD0 
0x0142TileWidthint32u!IFD0 
0x0143TileLengthint32u!IFD0 
0x0144TileOffsetsno- 
0x0145TileByteCountsno- 
0x0146BadFaxLinesno- 
0x0147CleanFaxDatano-0 = Clean +
1 = Regenerated +
2 = Unclean
0x0148ConsecutiveBadFaxLinesno- 
0x014aSubIFD +
A100DataOffset
-
no
-
IFD0
--> EXIF Tags +
(the data offset in original Sony DSLR-A100 ARW images)
0x014cInkSetint16uIFD01 = CMYK +
2 = Not CMYK
0x014dInkNamesno- 
0x014eNumberofInksno- 
0x0150DotRangeno- 
0x0151TargetPrinterstringIFD0 
0x0152ExtraSamplesno-0 = Unspecified +
1 = Associated Alpha +
2 = Unassociated Alpha
0x0153SampleFormatnoSubIFD(SamplesPerPixel values) +
[Values 0-3]
+ +
1 = Unsigned +
2 = Signed +
3 = Float
  4 = Undefined +
5 = Complex int +
6 = Complex float
+
0x0154SMinSampleValueno- 
0x0155SMaxSampleValueno- 
0x0156TransferRangeno- 
0x0157ClipPathno- 
0x0158XClipPathUnitsno- 
0x0159YClipPathUnitsno- 
0x015aIndexedno-0 = Not indexed +
1 = Indexed
0x015bJPEGTablesno- 
0x015fOPIProxyno-0 = Higher resolution image does not exist +
1 = Higher resolution image exists
0x0190GlobalParametersIFD----> EXIF Tags
0x0191ProfileTypeno-0 = Unspecified +
1 = Group 3 FAX
0x0192FaxProfileno- +
0 = Unknown +
1 = Minimal B&W lossless, S +
2 = Extended B&W lossless, F +
3 = Lossless JBIG B&W, J +
4 = Lossy color and grayscale, C +
5 = Lossless color and grayscale, L +
6 = Mixed raster content, M +
7 = Profile T +
255 = Multi Profiles
+
0x0193CodingMethodsno- +
Bit 0 = Unspecified compression +
Bit 1 = Modified Huffman +
Bit 2 = Modified Read +
Bit 3 = Modified MR +
Bit 4 = JBIG +
Bit 5 = Baseline JPEG +
Bit 6 = JBIG color
+
0x0194VersionYearno- 
0x0195ModeNumberno- 
0x01b1Decodeno- 
0x01b2DefaultImageColorno- 
0x01b3T82Optionsno- 
0x01b5JPEGTablesno- 
0x0200JPEGProcno-1 = Baseline +
14 = Lossless
0x0201ThumbnailOffset +
ThumbnailOffset +
ThumbnailOffset +
PreviewImageStart +
PreviewImageStart +
JpgFromRawStart +
JpgFromRawStart +
OtherImageStart +
OtherImageStart +
OtherImageStart
int32u*
int32u*
int32u*
int32u*
int32u*
int32u*
int32u*
int32u*
int32u*
no
IFD1
IFD0
SubIFD
MakerNotes
IFD0
SubIFD
IFD2
SubIFD1
SubIFD2
-
(called JPEGInterchangeFormat in the specification, this is ThumbnailOffset +in IFD1 of JPEG and some TIFF-based images, IFD0 of MRW images and AVI and +MOV videos, and the SubIFD in IFD1 of SRW images; PreviewImageStart in +MakerNotes and IFD0 of ARW and SR2 images; JpgFromRawStart in SubIFD of NEF +images and IFD2 of PEF images; and OtherImageStart in everything else)
0x0202ThumbnailLength +
ThumbnailLength +
ThumbnailLength +
PreviewImageLength +
PreviewImageLength +
JpgFromRawLength +
JpgFromRawLength +
OtherImageLength +
OtherImageLength +
OtherImageLength
int32u*
int32u*
int32u*
int32u*
int32u*
int32u*
int32u*
int32u*
int32u*
no
IFD1
IFD0
SubIFD
MakerNotes
IFD0
SubIFD
IFD2
SubIFD1
SubIFD2
-
(called JPEGInterchangeFormatLength in the specification, this is +ThumbnailLength in IFD1 of JPEG and some TIFF-based images, IFD0 of MRW +images and AVI and MOV videos, and the SubIFD in IFD1 of SRW images; +PreviewImageLength in MakerNotes and IFD0 of ARW and SR2 images; +JpgFromRawLength in SubIFD of NEF images, and IFD2 of PEF images; and +OtherImageLength in everything else)
0x0203JPEGRestartIntervalno- 
0x0205JPEGLosslessPredictorsno- 
0x0206JPEGPointTransformsno- 
0x0207JPEGQTablesno- 
0x0208JPEGDCTablesno- 
0x0209JPEGACTablesno- 
0x0211YCbCrCoefficientsrational64u[3]!IFD0 
0x0212YCbCrSubSamplingint16u[2]!IFD0 + +
'1 1' = YCbCr4:4:4 (1 1) +
'1 2' = YCbCr4:4:0 (1 2) +
'1 4' = YCbCr4:4:1 (1 4) +
'2 1' = YCbCr4:2:2 (2 1)
  '2 2' = YCbCr4:2:0 (2 2) +
'2 4' = YCbCr4:2:1 (2 4) +
'4 1' = YCbCr4:1:1 (4 1) +
'4 2' = YCbCr4:1:0 (4 2)
+
0x0213YCbCrPositioningint16u!:IFD01 = Centered +
2 = Co-sited
0x0214ReferenceBlackWhiterational64u[6]IFD0 
0x022fStripRowCountsno- 
0x02bcApplicationNotesint8u!IFD0--> XMP Tags
0x03e7USPTOMiscellaneousno- 
0x1000RelatedImageFileFormatstring!InteropIFD 
0x1001RelatedImageWidthint16u!InteropIFD 
0x1002RelatedImageHeightint16u!InteropIFD(called RelatedImageLength by the DCF spec.)
0x4746Ratingint16u/IFD0 
0x4747XP_DIP_XMLno- 
0x4748StitchInfo----> Microsoft Stitch Tags
0x4749RatingPercentint16u/IFD0 
0x7000SonyRawFileTypeno-0 = Sony Uncompressed 14-bit RAW +
1 = Sony Uncompressed 12-bit RAW +
2 = Sony Compressed RAW +
3 = Sony Lossless Compressed RAW +
4 = Sony Lossless Compressed RAW 2
0x7010SonyToneCurveno- 
0x7031VignettingCorrectionint16s!SubIFD(found in Sony ARW images) +
256 = Off +
257 = Auto +
272 = Auto (ILCE-1) +
511 = No correction params available
0x7032VignettingCorrParamsint16s[17]!SubIFD(found in Sony ARW images)
0x7034ChromaticAberrationCorrectionint16s!SubIFD(found in Sony ARW images) +
0 = Off +
1 = Auto +
255 = No correction params available
0x7035ChromaticAberrationCorrParamsint16s[33]!SubIFD(found in Sony ARW images)
0x7036DistortionCorrectionint16s!SubIFD(found in Sony ARW images) +
0 = Off +
1 = Auto +
17 = Auto fixed by lens +
255 = No correction params available
0x7037DistortionCorrParamsint16s[17]!SubIFD(found in Sony ARW images)
0x7038SonyRawImageSizeint32u[2]!SubIFD(size of actual image in Sony ARW files)
0x7310BlackLevelint16u[4]!SubIFD(found in Sony ARW images)
0x7313WB_RGGBLevelsint16s[4]!SubIFD(found in Sony ARW images)
0x74c7SonyCropTopLeftint32u[2]!SubIFD 
0x74c8SonyCropSizeint32u[2]!SubIFD 
0x800dImageIDno- 
0x80a3WangTag1no- 
0x80a4WangAnnotationno- 
0x80a5WangTag3no- 
0x80a6WangTag4no- 
0x80b9ImageReferencePointsno- 
0x80baRegionXformTackPointno- 
0x80bbWarpQuadrilateralno- 
0x80bcAffineTransformMatno- 
0x80e3Matteingno- 
0x80e4DataTypeno- 
0x80e5ImageDepthno- 
0x80e6TileDepthno- 
0x8214ImageFullWidthno- 
0x8215ImageFullHeightno- 
0x8216TextureFormatno- 
0x8217WrapModesno- 
0x8218FovCotno- 
0x8219MatrixWorldToScreenno- 
0x821aMatrixWorldToCamerano- 
0x827dModel2no- 
0x828dCFARepeatPatternDimint16u[2]!SubIFD 
0x828eCFAPattern2int8u[n]!SubIFD 
0x828fBatteryLevelno- 
0x8290KodakIFD----> Kodak IFD Tags +
(used in various types of Kodak images)
0x8298CopyrightstringIFD0(may contain copyright notices for photographer and editor, separated by a +newline. As per the EXIF specification, the newline is replaced by a null +byte when writing to file, but this may be avoided by disabling the print +conversion)
0x829aExposureTimerational64uExifIFD 
0x829dFNumberrational64uExifIFD 
0x82a5MDFileTagno-(tags 0x82a5-0x82ac are used in Molecular Dynamics GEL files)
0x82a6MDScalePixelno- 
0x82a7MDColorTableno- 
0x82a8MDLabNameno- 
0x82a9MDSampleInfono- 
0x82aaMDPrepDateno- 
0x82abMDPrepTimeno- 
0x82acMDFileUnitsno- 
0x830ePixelScaledouble[3]IFD0 
0x8335AdventScaleno- 
0x8336AdventRevisionno- 
0x835cUIC1Tagno- 
0x835dUIC2Tagno- 
0x835eUIC3Tagno- 
0x835fUIC4Tagno- 
0x83bbIPTC-NAAint32u!IFD0--> IPTC Tags
0x847eIntergraphPacketDatano- 
0x847fIntergraphFlagRegistersno- 
0x8480IntergraphMatrixdouble[n]IFD0 
0x8481INGRReservedno- 
0x8482ModelTiePointdouble[n]IFD0 
0x84e0Siteno- 
0x84e1ColorSequenceno- 
0x84e2IT8Headerno- 
0x84e3RasterPaddingno-0 = Byte +
1 = Word +
2 = Long Word +
9 = Sector +
10 = Long Sector
0x84e4BitsPerRunLengthno- 
0x84e5BitsPerExtendedRunLengthno- 
0x84e6ColorTableno- 
0x84e7ImageColorIndicatorno-0 = Unspecified Image Color +
1 = Specified Image Color
0x84e8BackgroundColorIndicatorno-0 = Unspecified Background Color +
1 = Specified Background Color
0x84e9ImageColorValueno- 
0x84eaBackgroundColorValueno- 
0x84ebPixelIntensityRangeno- 
0x84ecTransparencyIndicatorno- 
0x84edColorCharacterizationno- 
0x84eeHCUsageno-0 = CT +
1 = Line Art +
2 = Trap
0x84efTrapIndicatorno- 
0x84f0CMYKEquivalentno- 
0x8546SEMInfostringIFD0(found in some scanning electron microscope images)
0x8568AFCP_IPTC----> IPTC Tags
0x85b8PixelMagicJBIGOptionsno- 
0x85d7JPLCartoIFDno- 
0x85d8ModelTransformdouble[16]IFD0 
0x8602WB_GRGBLevelsno-(found in IFD0 of Leaf MOS images)
0x8606LeafData----> Leaf Tags
0x8649PhotoshopSettings-IFD0--> Photoshop Tags
0x8769ExifOffset-IFD0--> EXIF Tags
0x8773ICC_Profile-IFD0--> ICC_Profile Tags
0x877fTIFF_FXExtensionsno-Bit 0 = Resolution/Image Width +
Bit 1 = N Layer Profile M +
Bit 2 = Shared Data +
Bit 3 = B&W JBIG2 +
Bit 4 = JBIG2 Profile M
0x8780MultiProfilesno- +
Bit 0 = Profile S +
Bit 1 = Profile F +
Bit 2 = Profile J +
Bit 3 = Profile C +
Bit 4 = Profile L +
Bit 5 = Profile M +
Bit 6 = Profile T +
Bit 7 = Resolution/Image Width +
Bit 8 = N Layer Profile M +
Bit 9 = Shared Data +
Bit 10 = JBIG2 Profile M
+
0x8781SharedDatano- 
0x8782T88Optionsno- 
0x87acImageLayerno- 
0x87afGeoTiffDirectoryint16u[0.5]IFD0(these "GeoTiff" tags may read and written as a block, but they aren't +extracted unless specifically requested. Byte order changes are handled +automatically when copying between TIFF images with different byte order)
0x87b0GeoTiffDoubleParamsdouble[0.125]IFD0 
0x87b1GeoTiffAsciiParamsstringIFD0 
0x87beJBIGOptionsno- 
0x8822ExposureProgramint16uExifIFD(the value of 9 is not standard EXIF, but is used by the Canon EOS 7D) +
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 +
9 = Bulb
+
0x8824SpectralSensitivitystringExifIFD 
0x8825GPSInfo-IFD0--> GPS Tags
0x8827ISOint16u[n]ExifIFD(called ISOSpeedRatings by EXIF 2.2, then PhotographicSensitivity by the EXIF +2.3 spec.)
0x8828Opto-ElectricConvFactorno-(called OECF by the EXIF spec.)
0x8829Interlaceno- 
0x882aTimeZoneOffsetint16s[n]ExifIFD(1 or 2 values: 1. The time zone offset of DateTimeOriginal from GMT in +hours, 2. If present, the time zone offset of ModifyDate)
0x882bSelfTimerModeint16uExifIFD 
0x8830SensitivityTypeint16uExifIFD(applies to EXIF:ISO tag) +
0 = Unknown +
1 = Standard Output Sensitivity +
2 = Recommended Exposure Index +
3 = ISO Speed +
4 = Standard Output Sensitivity and Recommended Exposure Index +
5 = Standard Output Sensitivity and ISO Speed +
6 = Recommended Exposure Index and ISO Speed +
7 = Standard Output Sensitivity, Recommended Exposure Index and ISO Speed
0x8831StandardOutputSensitivityint32uExifIFD 
0x8832RecommendedExposureIndexint32uExifIFD 
0x8833ISOSpeedint32uExifIFD 
0x8834ISOSpeedLatitudeyyyint32uExifIFD 
0x8835ISOSpeedLatitudezzzint32uExifIFD 
0x885cFaxRecvParamsno- 
0x885dFaxSubAddressno- 
0x885eFaxRecvTimeno- 
0x8871FedexEDRno- 
0x888aLeafSubIFD----> Leaf SubIFD Tags
0x9000ExifVersionundef:ExifIFD 
0x9003DateTimeOriginalstringExifIFD(date/time when original image was taken)
0x9004CreateDatestringExifIFD(called DateTimeDigitized by the EXIF spec.)
0x9009GooglePlusUploadCodeundef[n]ExifIFD 
0x9010OffsetTimestringExifIFD(time zone for ModifyDate)
0x9011OffsetTimeOriginalstringExifIFD(time zone for DateTimeOriginal)
0x9012OffsetTimeDigitizedstringExifIFD(time zone for CreateDate)
0x9101ComponentsConfigurationundef[4]!:ExifIFD + +
0 = - +
1 = Y +
2 = Cb +
3 = Cr
  4 = R +
5 = G +
6 = B
+
0x9102CompressedBitsPerPixelrational64u!ExifIFD 
0x9201ShutterSpeedValuerational64sExifIFD(displayed in seconds, but stored as an APEX value)
0x9202ApertureValuerational64uExifIFD(displayed as an F number, but stored as an APEX value)
0x9203BrightnessValuerational64sExifIFD 
0x9204ExposureCompensationrational64sExifIFD(called ExposureBiasValue by the EXIF spec.)
0x9205MaxApertureValuerational64uExifIFD(displayed as an F number, but stored as an APEX value)
0x9206SubjectDistancerational64uExifIFD 
0x9207MeteringModeint16uExifIFD +
0 = Unknown +
1 = Average +
2 = Center-weighted average +
3 = Spot +
4 = Multi-spot +
5 = Multi-segment +
6 = Partial +
255 = Other
+
0x9208LightSourceint16uExifIFD--> EXIF LightSource Values
0x9209Flashint16uExifIFD--> EXIF Flash Values
0x920aFocalLengthrational64uExifIFD 
0x920bFlashEnergyno- 
0x920cSpatialFrequencyResponseno- 
0x920dNoiseno- 
0x920eFocalPlaneXResolutionno- 
0x920fFocalPlaneYResolutionno- 
0x9210FocalPlaneResolutionUnitno-1 = None +
2 = inches +
3 = cm +
4 = mm +
5 = um
0x9211ImageNumberint32uExifIFD 
0x9212SecurityClassificationstringExifIFD'C' = Confidential +
'R' = Restricted +
'S' = Secret +
'T' = Top Secret +
'U' = Unclassified
0x9213ImageHistorystringExifIFD 
0x9214SubjectAreaint16u[n]ExifIFD 
0x9215ExposureIndexno- 
0x9216TIFF-EPStandardIDno- 
0x9217SensingMethodno- +
1 = Monochrome area +
2 = One-chip color area +
3 = Two-chip color area +
4 = Three-chip color area +
5 = Color sequential area +
6 = Monochrome linear +
7 = Trilinear +
8 = Color sequential linear
+
0x923aCIP3DataFileno- 
0x923bCIP3Sheetno- 
0x923cCIP3Sideno- 
0x923fStoNitsno- 
0x927cMakerNoteApple +
MakerNoteNikon +
MakerNoteCanon +
MakerNoteCasio +
MakerNoteCasio2 +
MakerNoteDJIInfo +
MakerNoteDJI +
MakerNoteFLIR +
MakerNoteFujiFilm +
MakerNoteGE +
MakerNoteGE2 +
MakerNoteHasselblad +
MakerNoteHP +
MakerNoteHP2 +
MakerNoteHP4 +
MakerNoteHP6 +
MakerNoteISL +
MakerNoteJVC +
MakerNoteJVCText +
MakerNoteKodak1a +
MakerNoteKodak1b +
MakerNoteKodak2 +
MakerNoteKodak3 +
MakerNoteKodak4 +
MakerNoteKodak5 +
MakerNoteKodak6a +
MakerNoteKodak6b +
MakerNoteKodak7 +
MakerNoteKodak8a +
MakerNoteKodak8b +
MakerNoteKodak8c +
MakerNoteKodak9 +
MakerNoteKodak10 +
MakerNoteKodak11 +
MakerNoteKodak12 +
MakerNoteKodakUnknown +
MakerNoteKyocera +
MakerNoteMinolta +
MakerNoteMinolta2 +
MakerNoteMinolta3 +
MakerNoteMotorola +
MakerNoteNikon2 +
MakerNoteNikon3 +
MakerNoteNintendo +
MakerNoteOlympus +
MakerNoteOlympus2 +
MakerNoteOlympus3 +
MakerNoteLeica +
MakerNoteLeica2 +
MakerNoteLeica3 +
MakerNoteLeica4 +
MakerNoteLeica5 +
MakerNoteLeica6 +
MakerNoteLeica7 +
MakerNoteLeica8 +
MakerNoteLeica9 +
MakerNoteLeica10 +
MakerNotePanasonic +
MakerNotePanasonic2 +
MakerNotePanasonic3 +
MakerNotePentax +
MakerNotePentax2 +
MakerNotePentax3 +
MakerNotePentax4 +
MakerNotePentax5 +
MakerNotePentax6 +
MakerNotePhaseOne +
MakerNoteReconyx +
MakerNoteReconyx2 +
MakerNoteReconyx3 +
MakerNoteRicohPentax +
MakerNoteRicoh +
MakerNoteRicoh2 +
MakerNoteRicohText +
MakerNoteSamsung1a +
MakerNoteSamsung1b +
MakerNoteSamsung2 +
MakerNoteSanyo +
MakerNoteSanyoC4 +
MakerNoteSanyoPatch +
MakerNoteSigma +
MakerNoteSony +
MakerNoteSony2 +
MakerNoteSony3 +
MakerNoteSony4 +
MakerNoteSony5 +
MakerNoteSonyEricsson +
MakerNoteSonySRF +
MakerNoteUnknownText +
MakerNoteUnknownBinary +
MakerNoteUnknown
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
undef
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
ExifIFD
--> Apple Tags +
--> Nikon Tags +
--> Canon Tags +
--> Casio Tags +
--> Casio Type2 Tags +
--> DJI Info Tags +
--> DJI Tags +
--> FLIR Tags +
--> FujiFilm Tags +
--> GE Tags +
--> FujiFilm Tags +
--> Unknown Tags +
--> HP Tags +
--> HP Type2 Tags +
--> HP Type4 Tags +
--> HP Type6 Tags +
--> Unknown Tags +
--> JVC Tags +
--> JVC Text Tags +
--> Kodak Tags +
--> Kodak Tags +
--> Kodak Type2 Tags +
--> Kodak Type3 Tags +
--> Kodak Type4 Tags +
--> Kodak Type5 Tags +
--> Kodak Type6 Tags +
--> Kodak Type6 Tags +
--> Kodak Type7 Tags +
--> Kodak Type8 Tags +
--> Kodak Type8 Tags +
--> Kodak Type8 Tags +
--> Kodak Type9 Tags +
--> Kodak Type10 Tags +
--> Kodak Type11 Tags +
--> Kodak Type11 Tags +
--> Kodak Unknown Tags +
--> Unknown Tags +
--> Minolta Tags +
--> Olympus Tags +
(not EXIF-based) +
--> Motorola Tags +
--> Nikon Type2 Tags +
--> Nikon Tags +
--> Nintendo Tags +
--> Olympus Tags +
--> Olympus Tags +
--> Olympus Tags +
--> Panasonic Tags +
--> Panasonic Leica2 Tags +
--> Panasonic Leica3 Tags +
--> Panasonic Leica4 Tags +
--> Panasonic Leica5 Tags +
--> Panasonic Leica6 Tags +
--> Panasonic Leica6 Tags +
--> Panasonic Leica5 Tags +
--> Panasonic Leica9 Tags +
--> Panasonic Tags +
--> Panasonic Tags +
--> Panasonic Type2 Tags +
--> Panasonic Tags +
--> Pentax Tags +
--> Pentax Type2 Tags +
--> Casio Type2 Tags +
--> Pentax Type4 Tags +
--> Pentax Tags +
--> Pentax S1 Tags +
--> PhaseOne Tags +
--> Reconyx Tags +
--> Reconyx Type2 Tags +
--> Reconyx Type3 Tags +
--> Pentax Tags +
--> Ricoh Tags +
--> Ricoh Type2 Tags +
--> Ricoh Text Tags +
(Samsung "STMN" maker notes without PreviewImage) +
--> Samsung Tags +
--> Samsung Type2 Tags +
--> Sanyo Tags +
--> Sanyo Tags +
--> Sanyo Tags +
--> Sigma Tags +
--> Sony Tags +
--> Olympus Tags +
--> Olympus Tags +
--> Sony PIC Tags +
--> Sony Tags +
--> Sony Ericsson Tags +
--> Sony SRF Tags +
(unknown text-based maker notes) +
(unknown binary maker notes) +
--> Unknown Tags
0x9286UserCommentundefExifIFD 
0x9290SubSecTimestringExifIFD(fractional seconds for ModifyDate)
0x9291SubSecTimeOriginalstringExifIFD(fractional seconds for DateTimeOriginal)
0x9292SubSecTimeDigitizedstringExifIFD(fractional seconds for CreateDate)
0x932fMSDocumentTextno- 
0x9330MSPropertySetStorageno- 
0x9331MSDocumentTextPositionno- 
0x935cImageSourceDataundef!IFD0--> Photoshop DocumentData Tags
0x9400AmbientTemperaturerational64sExifIFD(ambient temperature in degrees C, called Temperature by the EXIF spec.)
0x9401Humidityrational64uExifIFD(ambient relative humidity in percent)
0x9402Pressurerational64uExifIFD(air pressure in hPa or mbar)
0x9403WaterDepthrational64sExifIFD(depth under water in metres, negative for above water)
0x9404Accelerationrational64uExifIFD(directionless camera acceleration in units of mGal, or 10-5 m/s2)
0x9405CameraElevationAnglerational64sExifIFD 
0x9c9bXPTitleint8uIFD0(tags 0x9c9b-0x9c9f are used by Windows Explorer; special characters +in these values are converted to UTF-8 by default, or Windows Latin1 +with the -L option. XPTitle is ignored by Windows Explorer if +ImageDescription exists)
0x9c9cXPCommentint8uIFD0 
0x9c9dXPAuthorint8uIFD0(ignored by Windows Explorer if Artist exists)
0x9c9eXPKeywordsint8uIFD0 
0x9c9fXPSubjectint8uIFD0 
0xa000FlashpixVersionundef:ExifIFD 
0xa001ColorSpaceint16u:ExifIFD(the value of 0x2 is not standard EXIF. Instead, an Adobe RGB image is +indicated by "Uncalibrated" with an InteropIndex of "R03". The values +0xfffd and 0xfffe are also non-standard, and are used by some Sony cameras) +
0x1 = sRGB +
0x2 = Adobe RGB +
0xfffd = Wide Gamut RGB +
0xfffe = ICC Profile +
0xffff = Uncalibrated
0xa002ExifImageWidthint16u:ExifIFD(called PixelXDimension by the EXIF spec.)
0xa003ExifImageHeightint16u:ExifIFD(called PixelYDimension by the EXIF spec.)
0xa004RelatedSoundFilestringExifIFD 
0xa005InteropOffset----> EXIF Tags
0xa010SamsungRawPointersOffsetno- 
0xa011SamsungRawPointersLengthno- 
0xa101SamsungRawByteOrderno- 
0xa102SamsungRawUnknown?no- 
0xa20bFlashEnergyrational64uExifIFD 
0xa20cSpatialFrequencyResponseno- 
0xa20dNoiseno- 
0xa20eFocalPlaneXResolutionrational64uExifIFD 
0xa20fFocalPlaneYResolutionrational64uExifIFD 
0xa210FocalPlaneResolutionUnitint16uExifIFD(values 1, 4 and 5 are not standard EXIF) +
1 = None +
2 = inches +
3 = cm +
4 = mm +
5 = um
0xa211ImageNumberno- 
0xa212SecurityClassificationno- 
0xa213ImageHistoryno- 
0xa214SubjectLocationint16u[2]ExifIFD 
0xa215ExposureIndexrational64uExifIFD 
0xa216TIFF-EPStandardIDno- 
0xa217SensingMethodint16uExifIFD +
1 = Not defined +
2 = One-chip color area +
3 = Two-chip color area +
4 = Three-chip color area +
5 = Color sequential area +
7 = Trilinear +
8 = Color sequential linear
+
0xa300FileSourceundefExifIFD1 = Film Scanner +
2 = Reflection Print Scanner +
3 = Digital Camera +
"\x03\x00\x00\x00" = Sigma Digital Camera
0xa301SceneTypeundefExifIFD1 = Directly photographed
0xa302CFAPatternundefExifIFD 
0xa401CustomRenderedint16uExifIFD(only 0 and 1 are standard EXIF, but other values are used by Apple iOS +devices) +
0 = Normal +
1 = Custom +
2 = HDR (no original saved) +
3 = HDR (original saved) +
4 = Original (for HDR) +
6 = Panorama +
7 = Portrait HDR +
8 = Portrait
+
0xa402ExposureModeint16uExifIFD0 = Auto +
1 = Manual +
2 = Auto bracket
0xa403WhiteBalanceint16uExifIFD0 = Auto +
1 = Manual
0xa404DigitalZoomRatiorational64uExifIFD 
0xa405FocalLengthIn35mmFormatint16uExifIFD(called FocalLengthIn35mmFilm by the EXIF spec.)
0xa406SceneCaptureTypeint16uExifIFD(the value of 4 is non-standard, and used by some Samsung models) +
0 = Standard +
1 = Landscape +
2 = Portrait +
3 = Night +
4 = Other
0xa407GainControlint16uExifIFD0 = None +
1 = Low gain up +
2 = High gain up +
3 = Low gain down +
4 = High gain down
0xa408Contrastint16uExifIFD0 = Normal +
1 = Low +
2 = High
0xa409Saturationint16uExifIFD0 = Normal +
1 = Low +
2 = High
0xa40aSharpnessint16uExifIFD0 = Normal +
1 = Soft +
2 = Hard
0xa40bDeviceSettingDescriptionno- 
0xa40cSubjectDistanceRangeint16uExifIFD0 = Unknown +
1 = Macro +
2 = Close +
3 = Distant
0xa420ImageUniqueIDstringExifIFD 
0xa430OwnerNamestringExifIFD(called CameraOwnerName by the EXIF spec.)
0xa431SerialNumberstringExifIFD(called BodySerialNumber by the EXIF spec.)
0xa432LensInforational64u[4]ExifIFD(4 rational values giving focal and aperture ranges, called LensSpecification +by the EXIF spec.)
0xa433LensMakestringExifIFD 
0xa434LensModelstringExifIFD 
0xa435LensSerialNumberstringExifIFD 
0xa436Titlestring/ExifIFD 
0xa437PhotographerstringExifIFD 
0xa438ImageEditorstringExifIFD 
0xa439CameraFirmwarestringExifIFD 
0xa43aRAWDevelopingSoftwarestringExifIFD 
0xa43bImageEditingSoftwarestringExifIFD 
0xa43cMetadataEditingSoftwarestringExifIFD 
0xa460CompositeImageint16uExifIFD0 = Unknown +
1 = Not a Composite Image +
2 = General Composite Image +
3 = Composite Image Captured While Shooting
0xa461CompositeImageCountint16u[2]ExifIFD(2 values: 1. Number of source images, 2. Number of images used. Called +SourceImageNumberOfCompositeImage by the EXIF spec.)
0xa462CompositeImageExposureTimesundefExifIFD(11 or more values: 1. Total exposure time period, 2. Total exposure of all +source images, 3. Total exposure of all used images, 4. Max exposure time of +source images, 5. Max exposure time of used images, 6. Min exposure time of +source images, 7. Min exposure of used images, 8. Number of sequences, 9. +Number of source images in sequence. 10-N. Exposure times of each source +image. Called SourceExposureTimesOfCompositeImage by the EXIF spec.)
0xa480GDALMetadatastringIFD0 
0xa481GDALNoDatastringIFD0 
0xa500Gammarational64uExifIFD 
0xafc0ExpandSoftwareno- 
0xafc1ExpandLensno- 
0xafc2ExpandFilmno- 
0xafc3ExpandFilterLensno- 
0xafc4ExpandScannerno- 
0xafc5ExpandFlashLampno- 
0xb4c3HasselbladRawImageno- 
0xbc01PixelFormatno-(tags 0xbc** are used in Windows HD Photo (HDP and WDP) images. The actual +PixelFormat values are 16-byte GUID's but the leading 15 bytes, +'6fddc324-4e03-4bfe-b1853-d77768dc9', have been removed below to avoid +unnecessary clutter) +
0x5 = Black & White +
0x8 = 8-bit Gray +
0x9 = 16-bit BGR555 +
0xa = 16-bit BGR565 +
0xb = 16-bit Gray +
0xc = 24-bit BGR +
0xd = 24-bit RGB +
0xe = 32-bit BGR +
0xf = 32-bit BGRA +
0x10 = 32-bit PBGRA +
0x11 = 32-bit Gray Float +
0x12 = 48-bit RGB Fixed Point +
0x13 = 32-bit BGR101010 +
0x15 = 48-bit RGB +
0x16 = 64-bit RGBA +
0x17 = 64-bit PRGBA +
0x18 = 96-bit RGB Fixed Point +
0x19 = 128-bit RGBA Float +
0x1a = 128-bit PRGBA Float +
0x1b = 128-bit RGB Float +
0x1c = 32-bit CMYK +
0x1d = 64-bit RGBA Fixed Point +
0x1e = 128-bit RGBA Fixed Point +
0x1f = 64-bit CMYK +
0x20 = 24-bit 3 Channels +
0x21 = 32-bit 4 Channels +
0x22 = 40-bit 5 Channels +
0x23 = 48-bit 6 Channels +
0x24 = 56-bit 7 Channels +
0x25 = 64-bit 8 Channels +
0x26 = 48-bit 3 Channels +
0x27 = 64-bit 4 Channels +
0x28 = 80-bit 5 Channels +
0x29 = 96-bit 6 Channels +
0x2a = 112-bit 7 Channels +
0x2b = 128-bit 8 Channels +
0x2c = 40-bit CMYK Alpha +
0x2d = 80-bit CMYK Alpha +
0x2e = 32-bit 3 Channels Alpha +
0x2f = 40-bit 4 Channels Alpha +
0x30 = 48-bit 5 Channels Alpha +
0x31 = 56-bit 6 Channels Alpha +
0x32 = 64-bit 7 Channels Alpha +
0x33 = 72-bit 8 Channels Alpha +
0x34 = 64-bit 3 Channels Alpha +
0x35 = 80-bit 4 Channels Alpha +
0x36 = 96-bit 5 Channels Alpha +
0x37 = 112-bit 6 Channels Alpha +
0x38 = 128-bit 7 Channels Alpha +
0x39 = 144-bit 8 Channels Alpha +
0x3a = 64-bit RGBA Half +
0x3b = 48-bit RGB Half +
0x3d = 32-bit RGBE +
0x3e = 16-bit Gray Half +
0x3f = 32-bit Gray Fixed Point
+
0xbc02Transformationno- +
0 = Horizontal (normal) +
1 = Mirror vertical +
2 = Mirror horizontal +
3 = Rotate 180 +
4 = Rotate 90 CW +
5 = Mirror horizontal and rotate 90 CW +
6 = Mirror horizontal and rotate 270 CW +
7 = Rotate 270 CW
+
0xbc03Uncompressedno-0 = No +
1 = Yes
0xbc04ImageTypeno-Bit 0 = Preview +
Bit 1 = Page
0xbc80ImageWidthno- 
0xbc81ImageHeightno- 
0xbc82WidthResolutionno- 
0xbc83HeightResolutionno- 
0xbcc0ImageOffsetno- 
0xbcc1ImageByteCountno- 
0xbcc2AlphaOffsetno- 
0xbcc3AlphaByteCountno- 
0xbcc4ImageDataDiscardno-0 = Full Resolution +
1 = Flexbits Discarded +
2 = HighPass Frequency Data Discarded +
3 = Highpass and LowPass Frequency Data Discarded
0xbcc5AlphaDataDiscardno-0 = Full Resolution +
1 = Flexbits Discarded +
2 = HighPass Frequency Data Discarded +
3 = Highpass and LowPass Frequency Data Discarded
0xc427OceScanjobDescno- 
0xc428OceApplicationSelectorno- 
0xc429OceIDNumberno- 
0xc42aOceImageLogicno- 
0xc44fAnnotationsno- 
0xc4a5PrintIMundefIFD0--> PrintIM Tags
0xc51bHasselbladExifno- 
0xc573OriginalFileNameno-(used by some obscure software)
0xc580USPTOOriginalContentTypeno-0 = Text or Drawing +
1 = Grayscale +
2 = Color
0xc5e0CR2CFAPatternno-1 => '0 1 1 2' = [Red,Green][Green,Blue] +
4 => '1 0 2 1' = [Green,Red][Blue,Green] +
3 => '1 2 0 1' = [Green,Blue][Red,Green] +
2 => '2 1 1 0' = [Blue,Green][Green,Red]
0xc612DNGVersionint8u[4]!IFD0(tags 0xc612-0xcd3b are defined by the DNG specification unless otherwise +noted. See https://helpx.adobe.com/photoshop/digital-negative.html for +the specification)
0xc613DNGBackwardVersionint8u[4]!IFD0 
0xc614UniqueCameraModelstringIFD0 
0xc615LocalizedCameraModelstringIFD0 
0xc616CFAPlaneColornoSubIFD 
0xc617CFALayoutnoSubIFD1 = Rectangular +
2 = Even columns offset down 1/2 row +
3 = Even columns offset up 1/2 row +
4 = Even rows offset right 1/2 column +
5 = Even rows offset left 1/2 column +
6 = Even rows offset up by 1/2 row, even columns offset left by 1/2 column +
7 = Even rows offset up by 1/2 row, even columns offset right by 1/2 column +
8 = Even rows offset down by 1/2 row, even columns offset left by 1/2 column +
9 = Even rows offset down by 1/2 row, even columns offset right by 1/2 column
0xc618LinearizationTableint16u[n]!SubIFD 
0xc619BlackLevelRepeatDimint16u[2]!SubIFD 
0xc61aBlackLevelrational64u[n]!SubIFD 
0xc61bBlackLevelDeltaHrational64s[n]!SubIFD 
0xc61cBlackLevelDeltaVrational64s[n]!SubIFD 
0xc61dWhiteLevelint32u[n]!SubIFD 
0xc61eDefaultScalerational64u[2]!SubIFD 
0xc61fDefaultCropOriginint32u[2]!SubIFD 
0xc620DefaultCropSizeint32u[2]!SubIFD 
0xc621ColorMatrix1rational64s[n]!IFD0 
0xc622ColorMatrix2rational64s[n]!IFD0 
0xc623CameraCalibration1rational64s[n]!IFD0 
0xc624CameraCalibration2rational64s[n]!IFD0 
0xc625ReductionMatrix1rational64s[n]!IFD0 
0xc626ReductionMatrix2rational64s[n]!IFD0 
0xc627AnalogBalancerational64u[n]!IFD0 
0xc628AsShotNeutralrational64u[n]!IFD0 
0xc629AsShotWhiteXYrational64u[2]!IFD0 
0xc62aBaselineExposurerational64s!IFD0 
0xc62bBaselineNoiserational64u!IFD0 
0xc62cBaselineSharpnessrational64u!IFD0 
0xc62dBayerGreenSplitint32u!SubIFD 
0xc62eLinearResponseLimitrational64u!IFD0 
0xc62fCameraSerialNumberstringIFD0 
0xc630DNGLensInforational64u[4]IFD0 
0xc631ChromaBlurRadiusrational64u!SubIFD 
0xc632AntiAliasStrengthrational64u!SubIFD 
0xc633ShadowScalerational64u!IFD0 
0xc634SR2Private +
DNGAdobeData +
MakerNotePentax +
MakerNotePentax5 +
MakerNoteRicohPentax +
MakerNoteDJIInfo +
DNGPrivateData
-
undef!
-
-
-
-
int8u!
IFD0
IFD0
IFD0
IFD0
IFD0
IFD0
IFD0
--> Sony SR2Private Tags +
--> DNG AdobeData Tags +
--> Pentax Tags +
--> Pentax Tags +
--> Pentax Tags +
--> DJI Info Tags
0xc635MakerNoteSafetyint16uIFD00 = Unsafe +
1 = Safe
0xc640RawImageSegmentationno-(used in segmented Canon CR2 images. 3 numbers: 1. Number of segments minus +one; 2. Pixel width of segments except last; 3. Pixel width of last segment)
0xc65aCalibrationIlluminant1int16u!IFD0--> EXIF LightSource Values
0xc65bCalibrationIlluminant2int16u!IFD0--> EXIF LightSource Values
0xc65cBestQualityScalerational64u!SubIFD 
0xc65dRawDataUniqueIDint8u[16]!IFD0 
0xc660AliasLayerMetadatano-(used by Alias Sketchbook Pro)
0xc68bOriginalRawFileNamestring!IFD0 
0xc68cOriginalRawFileDataundef!IFD0--> DNG OriginalRaw Tags
0xc68dActiveAreaint32u[4]!SubIFD 
0xc68eMaskedAreasint32u[n]!SubIFD 
0xc68fAsShotICCProfileundef!IFD0--> ICC_Profile Tags
0xc690AsShotPreProfileMatrixrational64s[n]!IFD0 
0xc691CurrentICCProfileundef!IFD0--> ICC_Profile Tags
0xc692CurrentPreProfileMatrixrational64s[n]!IFD0 
0xc6bfColorimetricReferenceint16u!IFD0 
0xc6c5SRawTypenoIFD0 
0xc6d2PanasonicTitleundefIFD0(proprietary Panasonic tag used for baby/pet name, etc)
0xc6d3PanasonicTitle2undefIFD0(proprietary Panasonic tag used for baby/pet name with age)
0xc6f3CameraCalibrationSigstring!IFD0 
0xc6f4ProfileCalibrationSigstring!IFD0 
0xc6f5ProfileIFD-IFD0--> EXIF Tags
0xc6f6AsShotProfileNamestring!IFD0 
0xc6f7NoiseReductionAppliedrational64u!SubIFD 
0xc6f8ProfileNamestring!IFD0 
0xc6f9ProfileHueSatMapDimsint32u[3]!IFD0 
0xc6faProfileHueSatMapData1float[n]!IFD0 
0xc6fbProfileHueSatMapData2float[n]!IFD0 
0xc6fcProfileToneCurvefloat[n]!IFD0 
0xc6fdProfileEmbedPolicyint32u!IFD00 = Allow Copying +
1 = Embed if Used +
2 = Never Embed +
3 = No Restrictions
0xc6feProfileCopyrightstring!IFD0 
0xc714ForwardMatrix1rational64s[n]!IFD0 
0xc715ForwardMatrix2rational64s[n]!IFD0 
0xc716PreviewApplicationNamestring!IFD0 
0xc717PreviewApplicationVersionstring!IFD0 
0xc718PreviewSettingsNamestring!IFD0 
0xc719PreviewSettingsDigestint8u!IFD0 
0xc71aPreviewColorSpaceint32u!IFD00 = Unknown +
1 = Gray Gamma 2.2 +
2 = sRGB +
3 = Adobe RGB +
4 = ProPhoto RGB
0xc71bPreviewDateTimestring!IFD0 
0xc71cRawImageDigestint8u[16]!IFD0 
0xc71dOriginalRawFileDigestint8u[16]!IFD0 
0xc71eSubTileBlockSizeno- 
0xc71fRowInterleaveFactorno- 
0xc725ProfileLookTableDimsint32u[3]!IFD0 
0xc726ProfileLookTableDatafloat[n]!IFD0 
0xc740OpcodeList1undef~!SubIFD + +
1 = WarpRectilinear +
2 = WarpFisheye +
3 = FixVignetteRadial +
4 = FixBadPixelsConstant +
5 = FixBadPixelsList +
6 = TrimBounds +
7 = MapTable
  8 = MapPolynomial +
9 = GainMap +
10 = DeltaPerRow +
11 = DeltaPerColumn +
12 = ScalePerRow +
13 = ScalePerColumn +
14 = WarpRectilinear2
+
0xc741OpcodeList2undef~!SubIFD + +
1 = WarpRectilinear +
2 = WarpFisheye +
3 = FixVignetteRadial +
4 = FixBadPixelsConstant +
5 = FixBadPixelsList +
6 = TrimBounds +
7 = MapTable
  8 = MapPolynomial +
9 = GainMap +
10 = DeltaPerRow +
11 = DeltaPerColumn +
12 = ScalePerRow +
13 = ScalePerColumn +
14 = WarpRectilinear2
+
0xc74eOpcodeList3undef~!SubIFD + +
1 = WarpRectilinear +
2 = WarpFisheye +
3 = FixVignetteRadial +
4 = FixBadPixelsConstant +
5 = FixBadPixelsList +
6 = TrimBounds +
7 = MapTable
  8 = MapPolynomial +
9 = GainMap +
10 = DeltaPerRow +
11 = DeltaPerColumn +
12 = ScalePerRow +
13 = ScalePerColumn +
14 = WarpRectilinear2
+
0xc761NoiseProfiledouble[n]!SubIFD 
0xc763TimeCodesint8u[n]IFD0 
0xc764FrameRaterational64sIFD0 
0xc772TStoprational64u[n]IFD0 
0xc789ReelNamestringIFD0 
0xc791OriginalDefaultFinalSizeint32u[2]!IFD0 
0xc792OriginalBestQualitySizeint32u[2]!IFD0(called OriginalBestQualityFinalSize by the DNG spec)
0xc793OriginalDefaultCropSizerational64u[2]!IFD0 
0xc7a1CameraLabelstringIFD0 
0xc7a3ProfileHueSatMapEncodingint32u!IFD00 = Linear +
1 = sRGB
0xc7a4ProfileLookTableEncodingint32u!IFD00 = Linear +
1 = sRGB
0xc7a5BaselineExposureOffsetrational64s!IFD0 
0xc7a6DefaultBlackRenderint32u!IFD00 = Auto +
1 = None
0xc7a7NewRawImageDigestint8u[16]!IFD0 
0xc7a8RawToPreviewGaindouble!IFD0 
0xc7aaCacheVersionint32u!SubIFD2 
0xc7b5DefaultUserCroprational64u[4]!SubIFD 
0xc7d5NikonNEFInfo----> Nikon NEFInfo Tags
0xc7e9DepthFormatint16u!IFD0(tags 0xc7e9-0xc7ee added by DNG 1.5.0.0) +
0 = Unknown +
1 = Linear +
2 = Inverse
0xc7eaDepthNearrational64u!IFD0 
0xc7ebDepthFarrational64u!IFD0 
0xc7ecDepthUnitsint16u!IFD00 = Unknown +
1 = Meters
0xc7edDepthMeasureTypeint16u!IFD00 = Unknown +
1 = Optical Axis +
2 = Optical Ray
0xc7eeEnhanceParamsstring!IFD0 
0xcd2dProfileGainTableMapundef!SubIFD 
0xcd2eSemanticNamenoSubIFD 
0xcd30SemanticInstanceIDnoSubIFD 
0xcd31CalibrationIlluminant3int16u!IFD0--> EXIF LightSource Values
0xcd32CameraCalibration3rational64s[n]!IFD0 
0xcd33ColorMatrix3rational64s[n]!IFD0 
0xcd34ForwardMatrix3rational64s[n]!IFD0 
0xcd35IlluminantData1undef!IFD0 
0xcd36IlluminantData2undef!IFD0 
0xcd37IlluminantData3undef!IFD0 
0xcd38MaskSubAreanoSubIFD 
0xcd39ProfileHueSatMapData3float[n]!IFD0 
0xcd3aReductionMatrix3rational64s[n]!IFD0 
0xcd3bRGBTablesundef!IFD0 
0xea1cPaddingundef!ExifIFD 
0xea1dOffsetSchemaint32s!ExifIFD(Microsoft's ill-conceived maker note offset difference)
0xfde8OwnerNamestring/ExifIFD(tags 0xfde8-0xfdea and 0xfe4c-0xfe58 are generated by Photoshop Camera RAW. +Some names are the same as other EXIF tags, but ExifTool will avoid writing +these unless they already exist in the file)
0xfde9SerialNumberstring/ExifIFD 
0xfdeaLensstring/ExifIFD 
0xfe00KDC_IFD----> Kodak KDC_IFD Tags +
(used in some Kodak KDC images)
0xfe4cRawFilestring/ExifIFD 
0xfe4dConverterstring/ExifIFD 
0xfe4eWhiteBalancestring/ExifIFD 
0xfe51Exposurestring/ExifIFD 
0xfe52Shadowsstring/ExifIFD 
0xfe53Brightnessstring/ExifIFD 
0xfe54Contraststring/ExifIFD 
0xfe55Saturationstring/ExifIFD 
0xfe56Sharpnessstring/ExifIFD 
0xfe57Smoothnessstring/ExifIFD 
0xfe58MoireFilterstring/ExifIFD 
+ +

EXIF Compression Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueCompression
1= Uncompressed
2= CCITT 1D
3= T4/Group 3 Fax
4= T6/Group 4 Fax
5= LZW
6= JPEG (old-style)
7= JPEG
8= Adobe Deflate
9= JBIG B&W
10= JBIG Color
99= JPEG
262= Kodak 262
32766= Next
32767= Sony ARW Compressed
32769= Packed RAW
32770= Samsung SRW Compressed
32771= CCIRLEW
32772= Samsung SRW Compressed 2
32773= PackBits
32809= Thunderscan
32867= Kodak KDC Compressed
32895= IT8CTPAD
32896= IT8LW
32897= IT8MP
32898= IT8BL
32908= PixarFilm
32909= PixarLog
32946= Deflate
32947= DCS
33003= Aperio JPEG 2000 YCbCr
33005= Aperio JPEG 2000 RGB
34661= JBIG
34676= SGILog
34677= SGILog24
34712= JPEG 2000
34713= Nikon NEF Compressed
34715= JBIG2 TIFF FX
34718= Microsoft Document Imaging (MDI) Binary Level Codec
34719= Microsoft Document Imaging (MDI) Progressive Transform Codec
34720= Microsoft Document Imaging (MDI) Vector
34887= ESRI Lerc
34892= Lossy JPEG
34925= LZMA2
34926= Zstd
34927= WebP
34933= PNG
34934= JPEG XR
65000= Kodak DCR Compressed
65535= Pentax PEF Compressed
+ +

EXIF LightSource Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLightSourceValueLightSourceValueLightSource
0= Unknown12= Daylight Fluorescent20= D55
1= Daylight13= Day White Fluorescent21= D65
2= Fluorescent14= Cool White Fluorescent22= D75
3= Tungsten (Incandescent)15= White Fluorescent23= D50
4= Flash16= Warm White Fluorescent24= ISO Studio Tungsten
9= Fine Weather17= Standard Light A255= Other
10= Cloudy18= Standard Light B  
11= Shade19= Standard Light C  
+ +

EXIF Flash Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueFlash
0x0= No Flash
0x1= Fired
0x5= Fired, Return not detected
0x7= Fired, Return detected
0x8= On, Did not fire
0x9= On, Fired
0xd= On, Return not detected
0xf= On, Return detected
0x10= Off, Did not fire
0x14= Off, Did not fire, Return not detected
0x18= Auto, Did not fire
0x19= Auto, Fired
0x1d= Auto, Fired, Return not detected
0x1f= Auto, Fired, Return detected
0x20= No flash function
0x30= Off, No flash function
0x41= Fired, Red-eye reduction
0x45= Fired, Red-eye reduction, Return not detected
0x47= Fired, Red-eye reduction, Return detected
0x49= On, Red-eye reduction
0x4d= On, Red-eye reduction, Return not detected
0x4f= On, Red-eye reduction, Return detected
0x50= Off, Red-eye reduction
0x58= Auto, Did not fire, Red-eye reduction
0x59= Auto, Fired, Red-eye reduction
0x5d= Auto, Fired, Red-eye reduction, Return not detected
0x5f= Auto, Fired, Red-eye reduction, Return detected
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 8, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Extra.html b/ExifTool/html/TagNames/Extra.html new file mode 100644 index 0000000..7182906 --- /dev/null +++ b/ExifTool/html/TagNames/Extra.html @@ -0,0 +1,580 @@ + + + + +Extra Tags + + + +

Extra Tags

+

+The extra tags provide extra features or extra information extracted or +generated by ExifTool that is not directly associated with another tag +group. The Group column lists the family 1 group name when reading. +Tags with a "-" in this column are write-only.

+ +

Tags in the family 1 "System" group are referred to as "pseudo" tags because +they don't represent real metadata in the file. Instead, this information +is stored in the directory structure of the filesystem. The Writable +System "pseudo" tags in this table may be written without modifying the file +itself. The TestName tag is used for dry-run testing before writing +FileName. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableGroupValues / Notes
Adobeyes!Adobe(the JPEG APP14 Adobe segment. Extracted only if specified. See the +JPEG Adobe Tags for more information)
BaseNamenoSystem(file name without extension. Not generated unless specifically requested or +the API RequestAll option is set)
CanonDR4yes!^CanonVRD(the full Canon DPP version 4 DR4 block. This tag is generated only if +specifically requested)
CanonVRDyes!^CanonVRD(the full Canon DPP VRD trailer block. This tag is generated only if +specifically requested)
CommentyesFile(comment embedded in JPEG, GIF89a or PPM/PGM/PBM image)
CurrentIPTCDigestnoFile(MD5 digest of existing IPTC data. All zeros if IPTC exists but Digest::MD5 +is not installed. Only calculated for IPTC in the standard location as +specified by the MWG. ExifTool +automates the handling of this tag in the MWG module -- see the +MWG Composite Tags for details)
Directoryyes!System(the directory of the file as specified in the call to ExifTool, or "." if no +directory was specified. May be written to move the file to another +directory that will be created if doesn't already exist)
EXIFyes!EXIF(the full EXIF data block from JPEG, PNG, JP2, MIE and MIFF images. This tag +is generated only if specifically requested)
EmbeddedVideonoFile 
ErrornoExifTool(returns errors that may have occurred while reading or writing a file. Any +Error will prevent the file from being processed. Minor errors may be +downgraded to warnings with the -m or IgnoreMinorErrors option)
ExifByteOrderyesFile(represents the byte order of EXIF information. May be written to set the +byte order only for newly created EXIF segments) +
'II' = Little-endian (Intel, II) +
'MM' = Big-endian (Motorola, MM)
ExifToolVersionnoExifTool(the version of ExifTool currently running)
ExifUnicodeByteOrderyes-(specifies the byte order to use when writing EXIF Unicode text. The EXIF +specification is particularly vague about this byte ordering, and different +applications use different conventions. By default ExifTool writes Unicode +text in EXIF byte order, but this write-only tag may be used to force a +specific order. Applies to the EXIF UserComment tag when writing special +characters) +
'II' = Little-endian (Intel, II) +
'MM' = Big-endian (Motorola, MM)
FileAccessDatenoSystem(the date/time of last access of the file. Note that this access time is +updated whenever any software, including ExifTool, reads the file)
FileAttributesnoSystem(extracted only if specifically requested or the SystemTags or RequestAll API +option is set. 2 or 3 values: 0. File type, 1. Attribute bits, 2. Windows +attribute bits if Win32API::File is available) +
[Value 0]
+ +
0x0 = Unknown +
0x1000 = FIFO +
0x2000 = Character +
0x3000 = Mux Character +
0x4000 = Directory +
0x5000 = XENIX Named +
0x6000 = Block +
0x7000 = Mux Block
  0x8000 = Regular +
0x9000 = VxFS Compressed +
0xa000 = Symbolic Link +
0xb000 = Solaris Shadow Inode +
0xc000 = Socket +
0xd000 = Solaris Door +
0xe000 = BSD Whiteout
+[Value 1] + +
Bit 9 = Sticky +
Bit 10 = Set Group ID
  Bit 11 = Set User ID
+[Value 2] + +
Bit 0 = Read Only +
Bit 1 = Hidden +
Bit 2 = System +
Bit 3 = Volume Label +
Bit 4 = Directory +
Bit 5 = Archive +
Bit 6 = Device +
Bit 7 = Normal
  Bit 8 = Temporary +
Bit 9 = Sparse File +
Bit 10 = Reparse Point +
Bit 11 = Compressed +
Bit 12 = Offline +
Bit 13 = Not Content Indexed +
Bit 14 = Encrypted
+
FileBlockCountnoSystem(extracted only if specifically requested or the SystemTags or RequestAll API +option is set)
FileBlockSizenoSystem(extracted only if specifically requested or the SystemTags or RequestAll API +option is set)
FileCreateDateyes!System(the filesystem creation date/time. Windows/Mac only. In Windows, the file +creation date/time is preserved by default when writing if Win32API::File +and Win32::API are available. On Mac, this tag is extracted only if it or +the MacOS group is specifically requested or the API RequestAll option is +set to 2 or higher. Requires "setfile" for writing on Mac, which may be +installed by typing xcode-select --install in the Terminal)
FileDeviceIDnoSystem(extracted only if specifically requested or the SystemTags or RequestAll API +option is set)
FileDeviceNumbernoSystem(extracted only if specifically requested or the SystemTags or RequestAll API +option is set)
FileGroupIDyes!System(extracted only if specifically requested or the SystemTags or RequestAll API +option is set. Returns group ID number with the -n option, or name +otherwise. May be written with either group name or number)
FileHardLinksnoSystem(extracted only if specifically requested or the SystemTags or RequestAll API +option is set)
FileInodeChangeDatenoSystem(the date/time when the file's directory information was last changed. +Non-Windows systems only)
FileInodeNumbernoSystem(extracted only if specifically requested or the SystemTags or RequestAll API +option is set)
FileModifyDateyes!System(the filesystem modification date/time. Note that ExifTool may not be able +to handle filesystem dates before 1970 depending on the limitations of the +system's standard libraries)
FileNameyes!System(may be written with a full path name to set FileName and Directory in one +operation. This is such a powerful feature that a TestName tag is provided +to allow dry-run tests before actually writing the file name. See +filename.html for more information on writing the +FileName, Directory and TestName tags)
FilePathnoSystem(absolute path of source file. Not generated unless specifically requested or +the API RequestAll option is set. Does not support Windows Unicode file +names)
FilePermissionsyes!System(r=read, w=write and x=execute permissions for the file owner, group and +others. The ValueConv value is an octal number so bit test operations on +this value should be done in octal, eg. 'oct($filePermissions#) & 0200')
FileSequencenoExifTool(sequence number for each source file when extracting or copying information, +including files that fail the -if condition of the command-line application, +beginning at 0 for the first file. Not generated unless specifically +requested or the API RequestAll option is set)
FileSizenoSystem(note that the print conversion for this tag uses historic prefixes: 1 kB = +1024 bytes, etc.)
FileTypenoFile(a short description of the file type. For many file types this is the just +the uppercase file extension)
FileTypeExtensionnoFile(a common lowercase extension for this file type, or uppercase with the -n +option)
FileUserIDyes!System(extracted only if specifically requested or the SystemTags or RequestAll API +option is set. Returns user ID number with the -n option, or name +otherwise. May be written with either user name or number)
ForceWriteyes-(write-only tag used to force metadata in a file to be rewritten even if no +tag values are changed. May be set to "EXIF", "IPTC", "XMP" or "PNG" to +force the corresponding metadata type to be rewritten, "FixBase" to cause +EXIF to be rewritten only if the MakerNotes offset base was fixed, or "All" +to rewrite all of these metadata types. Values are case insensitive, and +multiple values may be separated with commas, eg. -ForceWrite=exif,xmp)
Geosyncyes-(this write-only tag specifies a time difference to add to Geotime for +synchronization with the GPS clock. For example, set this to "-12" if the +camera clock is 12 seconds faster than GPS time. Input format is +"[+-][[[DD ]HH:]MM:]SS[.ss]". Additional features allow calculation of time +differences and time drifts, and extraction of synchronization times from +image files. See the geotagging documentation for details)
Geotagyes-(this write-only tag is used to define the GPS track log data or track log +file name. Currently supported track log formats are GPX, NMEA RMC/GGA/GLL, +KML, IGC, Garmin XML and TCX, Magellan PMGNTRK, Honeywell PTNTHPR, Winplus +Beacon text, and Bramor gEO log files. May be set to the special value of +"DATETIMEONLY" (all caps) to set GPS date/time tags if no input track points +are available. See geotag.html for details)
Geotimeyes-(this write-only tag is used to define a date/time for interpolating a +position in the GPS track specified by the Geotag tag. Writing this tag +causes GPS information to be written into the EXIF or XMP of the target +files. The local system timezone is assumed if the date/time value does not +contain a timezone. May be deleted to delete associated GPS tags. A group +name of "EXIF" or "XMP" may be specified to write or delete only EXIF or XMP +GPS tags)
HardLinkyes!-(this write-only tag is used to create a hard link with the specified name to +the source file. If the source file is edited, copied, renamed or moved in +the same operation as writing HardLink, then the link is made to the updated +file. Note that subsequent editing of either hard-linked file by exiftool +will break the link unless the -overwrite_original_in_place option is used)
ICC_Profileyes!ICC_Profile(the full ICC_Profile data block. This tag is generated only if specifically +requested)
ID3SizenoFile(size of the ID3 data block)
IPTCyes!IPTC(the full IPTC data block. This tag is generated only if specifically +requested)
ImageDataHashnoFile(Hash of image data. Generated only if specifically requested for JPEG, TIFF, +PNG, CRW, CR3, MRW, RAF, X3F, IIQ, JP2, JXL, HEIC and AVIF images, MOV/MP4 +videos, and some RIFF-based files such as AVI, WAV and WEBP. The hash +algorithm is set by the API ImageHashType option, and is 'MD5' by default. +The hash includes the main image data, plus JpgFromRaw/OtherImage for some +formats, but does not include ThumbnailImage or PreviewImage. Includes +video and audio data for MOV/MP4. The XMP-et:OriginalImageHash and +XMP-et:OriginalImageHashType tags provide a way to store +the this hash value and the hash type in the file.)
ImageHeightnoFile(the height of the image in number of pixels)
ImageWidthnoFile(the width of the image in number of pixels)
JPEGDigestnoFile(an MD5 digest of the JPEG quantization tables is combined with the component +sub-sampling values to generate the value of this tag. The result is +compared to known values in an attempt to deduce the originating software +based only on the JPEG image data. For performance reasons, this tag is +generated only if specifically requested or the API RequestAll option is set +to 3 or higher)
JPEGImageLengthnoFile(byte length of JPEG image without metadata. For performance reasons, this +tag is generated only if specifically requested or the API RequestAll option +is set to 3 or higher)
JPEGQualityEstimatenoFile(an estimate of the IJG JPEG quality setting for the image, calculated from +the quantization tables. For performance reasons, this tag is generated +only if specifically requested or the API RequestAll option is set to 3 or +higher)
MIMETypenoFile(the MIME type of the source file)
MaxValnoFile(maximum pixel value in PPM or PGM image)
NewGUIDnoExifTool(generates a new, random GUID with format +YYYYmmdd-HHMM-SSNN-PPPP-RRRRRRRRRRRR, where Y=year, m=month, d=day, H=hour, +M=minute, S=second, N=file sequence number in hex, P=process ID in hex, and +R=random hex number; without dashes with the -n option. Not generated +unless specifically requested or the API RequestAll option is set)
NownoExifTool(the current date/time. Useful when setting the tag values, eg. +"-modifydate<now". Not generated unless specifically requested or the +API RequestAll option is set)
OtherImagenoFile(other JPEG-format embedded image)
PageCountnoFile(the number of pages in a multi-page TIFF document)
PreviewImageyesFile(JPEG-format embedded preview image)
PreviewPDFnoFile(PDF-format embedded preview image)
PreviewPNGnoFile(PNG-format embedded preview image)
PreviewTIFFnoFile(TIFF-format embedded preview image)
PreviewWMFnoFile(WMF-format embedded preview image)
ProcessingTimenoExifTool(the clock time in seconds taken by ExifTool to extract information from this +file. Not generated unless specifically requested or the RequestAll API +option is set. Requires Time::HiRes)
RAFVersionnoFile(RAF file version number)
ResourceForkSizenoSystem(size of the file's resource fork if it contains data. Mac OS only. If this +tag is generated the ExtractEmbedded option may be used to extract +resource-fork information as a sub-document. When writing, the resource +fork is preserved by default, but it may be deleted with -rsrc:all= on +the command line)
SphericalVideoXMLyes!GSpherical(the SphericalVideoXML block from MP4/MOV videos. This tag is generated only +if specifically requested)
SymLinkyes!-(this write-only tag is used to create a symbolic link with the specified +name to the source file. If the source file is edited, copied, renamed or +moved in the same operation as writing SymLink, then the link is made to the +updated file. The link uses an absolute path unless it is created in the +current working directory. Valid only for file systems that support +symbolic links. Note that subsequent editing of the file via the symbolic +link by exiftool will cause the link to be replaced by the edited file +without changing the original unless the -overwrite_original_in_place option +is used)
TestNameyes!-(this write-only tag may be used instead of FileName for dry-run tests of the +file renaming feature. Writing this tag prints the old and new file names +to the console, but does not affect the file itself)
ThumbnailImagenoFile(JPEG-format embedded thumbnail image)
Traileryes!File(the full JPEG trailer data block. Extracted only if specifically requested +or the API RequestAll option is set to 3 or higher)
ValidatenoExifTool(generated only if specifically requested. Requesting this tag automatically +enables the API Validate option, imposing +additional validation checks when extracting metadata. Returns the number +of errors, warnings and minor warnings encountered. Note that the Validate +feature focuses mainly on validation of EXIF/TIFF metadata) +
'0 0 0' = OK
WarningnoExifTool(returns warnings that may have occurred while reading or writing a file. +Use the -a or Duplicates option to see all warnings if more than one +occurred. Minor warnings may be ignored with the -m or IgnoreMinorErrors +option. Minor warnings with a capital "M" in the "[Minor]" designation +indicate that the processing is affected by ignoring the warning)
XMLnoXML(the XML data block, extracted for some file types)
XMPyes!XMP(the XMP data block, but note that extended XMP in JPEG images may be split +into multiple blocks. This tag is generated only if specifically requested)
XResolutionnoFile(the horizontal pixel resolution)
YResolutionnoFile(the vertical pixel resolution)
ZoneIdentifieryes!System(Windows only. Existence indicates that the file has a Zone.Identifier +alternate data stream, which is used by some Windows browsers to mark +downloaded files as possibly unsafe to run. May be deleted to remove this +stream. Requires Win32API::File)
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 28, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/FITS.html b/ExifTool/html/TagNames/FITS.html new file mode 100644 index 0000000..1bec6f9 --- /dev/null +++ b/ExifTool/html/TagNames/FITS.html @@ -0,0 +1,96 @@ + + + + +FITS Tags + + + +

FITS Tags

+

This table lists some standard Flexible Image Transport System (FITS) tags, +but ExifTool will extract any other tags found. See +https://fits.gsfc.nasa.gov/fits_standard.html for the specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AUTHOR'Authorno 
'BACKGRND'Backgroundno 
'COMMENT'Commentno(leading spaces are removed if PrintConv is enabled)
'DATE'CreateDateno 
'DATE-END'ObservationDateEndno 
'DATE-OBS'ObservationDateno 
'HISTORY'Historyno(leading spaces are removed if PrintConv is enabled)
'INSTRUME'Instrumentno 
'OBJECT'Objectno 
'OBSERVER'Observerno 
'REFERENC'Referenceno 
'TELESCOP'Telescopeno 
'TIME-END'ObservationTimeEndno 
'TIME-OBS'ObservationTimeno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 23, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/FLAC.html b/ExifTool/html/TagNames/FLAC.html new file mode 100644 index 0000000..dd0b605 --- /dev/null +++ b/ExifTool/html/TagNames/FLAC.html @@ -0,0 +1,190 @@ + + + + +FLAC Tags + + + +

FLAC Tags

+

Free Lossless Audio Codec (FLAC) meta information. ExifTool also extracts +ID3 information from these files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000StreamInfo---> FLAC StreamInfo Tags
0x0001Padding?no 
0x0002Application_riff +
ApplicationUnknown?
-
no
--> RIFF Tags
0x0003SeekTable?no 
0x0004VorbisComment---> Vorbis Comments Tags
0x0005CueSheet?no 
0x0006Picture---> FLAC Picture Tags
+ +

FLAC StreamInfo Tags

+

FLAC is big-endian, so bit 0 is the high-order bit in this table.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Bit000-015'BlockSizeMinno 
'Bit016-031'BlockSizeMaxno 
'Bit032-055'FrameSizeMinno 
'Bit056-079'FrameSizeMaxno 
'Bit080-099'SampleRateno 
'Bit100-102'Channelsno 
'Bit103-107'BitsPerSampleno 
'Bit108-143'TotalSamplesno 
'Bit144-271'MD5Signatureno 
+ +

FLAC Picture Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0PictureTypeno +
0 = Other +
1 = 32x32 PNG Icon +
2 = Other Icon +
3 = Front Cover +
4 = Back Cover +
5 = Leaflet +
6 = Media +
7 = Lead Artist +
8 = Artist +
9 = Conductor +
10 = Band +
11 = Composer +
12 = Lyricist +
13 = Recording Studio or Location +
14 = Recording Session +
15 = Performance +
16 = Capture from Movie or Video +
17 = Bright(ly) Colored Fish +
18 = Illustration +
19 = Band Logo +
20 = Publisher Logo
+
1PictureMIMETypeno 
2PictureDescriptionno 
3PictureWidthno 
4PictureHeightno 
5PictureBitsPerPixelno 
6PictureIndexedColorsno 
7PictureLengthno 
8Pictureno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 19, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/FLIF.html b/ExifTool/html/TagNames/FLIF.html new file mode 100644 index 0000000..9d314d5 --- /dev/null +++ b/ExifTool/html/TagNames/FLIF.html @@ -0,0 +1,85 @@ + + + + +FLIF Tags + + + +

FLIF Tags

+

Information extracted from Free Lossless Image Format files. See +http://flif.info/ for more information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0ImageTypeno +
1 = Grayscale (non-interlaced) +
3 = RGB (non-interlaced) +
4 = RGBA (non-interlaced) +
'A' = Grayscale (interlaced) +
'C' = RGB (interlaced) +
'D' = RGBA (interlaced) +
'Q' = Grayscale Animation (non-interlaced) +
'S' = RGB Animation (non-interlaced) +
'T' = RGBA Animation (non-interlaced) +
'a' = Grayscale Animation (interlaced) +
'c' = RGB Animation (interlaced) +
'd' = RGBA Animation (interlaced)
+
1BitDepthno0 = Custom +
1 = 8 +
2 = 16
2ImageWidthno 
3ImageHeightno 
4AnimationFramesno 
5Encodingno0 = FLIF16
'eXif'EXIF---> EXIF Tags
'eXmp'XMP---> XMP Tags
'iCCP'ICC_Profile---> ICC_Profile Tags
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Nov 2, 2016 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/FLIR.html b/ExifTool/html/TagNames/FLIR.html new file mode 100644 index 0000000..3c820e8 --- /dev/null +++ b/ExifTool/html/TagNames/FLIR.html @@ -0,0 +1,1434 @@ + + + + +FLIR Tags + + + +

FLIR Tags

+

Information extracted from the maker notes of JPEG images from thermal +imaging cameras by FLIR Systems Inc.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001ImageTemperatureMaxrational64u(these temperatures may be in Celsius, Kelvin or Fahrenheit, but there is no +way to tell which)
0x0002ImageTemperatureMinrational64u 
0x0003Emissivityrational64u 
0x0004UnknownTemperature?rational64u 
0x0005CameraTemperatureRangeMax?rational64u 
0x0006CameraTemperatureRangeMin?rational64u 
+ +

FLIR FFF Tags

+

Information extracted from FLIR FFF images and the APP1 FLIR segment of JPEG +images. These tags may also be extracted from the first frame of an FLIR +SEQ file, or all frames if the ExtractEmbedded option is used. Setting +ExtractEmbedded to 2 also the raw thermal data from all frames.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'_header'FFFHeader---> FLIR Header Tags
0x0001RawData---> FLIR RawData Tags
0x0005GainDeadData---> FLIR GainDeadData Tags
0x0006CoarseData---> FLIR CoarseData Tags
0x000eEmbeddedImage---> FLIR EmbeddedImage Tags
0x0020CameraInfo---> FLIR CameraInfo Tags
0x0021MeasurementInfo---> FLIR MeasInfo Tags
0x0022PaletteInfo---> FLIR PaletteInfo Tags
0x0023TextInfo---> FLIR TextInfo Tags
0x0024EmbeddedAudioFileno 
0x0028PaintData---> FLIR PaintData Tags
0x002aPiP---> FLIR PiP Tags
0x002bGPSInfo---> FLIR GPSInfo Tags
0x002cMeterLink---> FLIR MeterLink Tags
0x002eParameterInfo---> FLIR ParamInfo Tags
+ +

FLIR Header Tags

+

Tags extracted from the FLIR FFF/AFF header.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
4CreatorSoftwareno 
+ +

FLIR RawData Tags

+

The thermal image data may be stored either as raw data, or in PNG format. +If stored as raw data, ExifTool adds a TIFF header to allow the data to be +viewed as a TIFF image. If stored in PNG format, the PNG image is extracted +as-is. Note that most FLIR cameras using the PNG format seem to write the +16-bit raw image data in the wrong byte order.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1RawThermalImageWidthno 
2RawThermalImageHeightno 
16RawThermalImageTypeno 
16.1RawThermalImageno 
+ +

FLIR GainDeadData Tags

+

Information found in FFF-format .GAN calibration image files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1GainDeadMapImageWidthno 
2GainDeadMapImageHeightno 
16GainDeadMapImageTypeno 
16.1GainDeadMapImageno 
+ +

FLIR CoarseData Tags

+

Information found in FFF-format .CRS correction image files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1CoarseMapImageWidthno 
2CoarseMapImageHeightno 
16CoarseMapImageTypeno 
16.1CoarseMapImageno 
+ +

FLIR EmbeddedImage Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1EmbeddedImageWidthno 
2EmbeddedImageHeightno 
16EmbeddedImageTypeno("PNG" for PNG image in Y Cb Cr colors, "JPG" for a JPEG image, or "DAT" for +other image data)
16.1EmbeddedImageno 
+ +

FLIR CameraInfo Tags

+

FLIR camera information. The Planck tags are variables used in the +temperature calculation. See +forum/index.php?topic=4898.msg23972#msg23972 +for details.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
32Emissivityno 
36ObjectDistanceno 
40ReflectedApparentTemperatureno 
44AtmosphericTemperatureno 
48IRWindowTemperatureno 
52IRWindowTransmissionno 
60RelativeHumidityno 
88PlanckR1no 
92PlanckBno 
96PlanckFno 
112AtmosphericTransAlpha1no 
116AtmosphericTransAlpha2no 
120AtmosphericTransBeta1no 
124AtmosphericTransBeta2no 
128AtmosphericTransXno 
144CameraTemperatureRangeMaxno 
148CameraTemperatureRangeMinno 
152CameraTemperatureMaxClipno 
156CameraTemperatureMinClipno 
160CameraTemperatureMaxWarnno 
164CameraTemperatureMinWarnno 
168CameraTemperatureMaxSaturatedno 
172CameraTemperatureMinSaturatedno 
212CameraModelno 
244CameraPartNumberno 
260CameraSerialNumberno 
276CameraSoftwareno 
368LensModelno 
400LensPartNumberno 
416LensSerialNumberno 
436FieldOfViewno 
492FilterModelno 
508FilterPartNumberno 
540FilterSerialNumberno 
776PlanckOno 
780PlanckR2no 
784RawValueRangeMinno 
786RawValueRangeMaxno 
824RawValueMedianno 
828RawValueRangeno 
900DateTimeOriginalno 
912FocusStepCountno 
1116FocusDistanceno 
1124FrameRateno 
+ +

FLIR MeasInfo Tags

+

Tags listed below are only for the first measurement tool, however multiple +measurements may be added, and information is extracted for all of them. +Tags for subsequent measurements are generated as required with the prefixes +"Meas2", "Meas3", etc.

+
+
+ + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Meas1Labelno 
Meas1Paramsno(Spot=X,Y; Area=X1,Y1,W,H; Ellipse=XC,YC,X1,Y1,X2,Y2; Line=X1,Y1,X2,Y2)
Meas1Typeno + +
1 = Spot +
2 = Area +
3 = Ellipse +
4 = Line
  5 = Endpoint +
6 = Alarm +
7 = Unused +
8 = Difference
+
+ +

FLIR PaletteInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PaletteColorsno 
6AboveColorno(Y Cr Cb color components)
9BelowColorno 
12OverflowColorno 
15UnderflowColorno 
18Isotherm1Colorno 
21Isotherm2Colorno 
26PaletteMethodno 
27PaletteStretchno 
48PaletteFileNameno 
80PaletteNameno 
112Paletteno(Y Cr Cb byte values for each palette color)
+ +

FLIR TextInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Label0no 
Label1no 
Label2no 
Label3no 
Value0no 
Value1no 
Value2no 
Value3no 
+ +

FLIR PaintData Tags

+

Information generated by FLIR Tools "Paint colors" tool.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
5PaintImageWidthno 
6PaintImageHeightno 
20PaintImageTypeno 
20.1PaintImageno 
+ +

FLIR PiP Tags

+

FLIR Picture in Picture tags.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0Real2IRno 
2OffsetXno(offset from of insertion point from center)
3OffsetYno 
4PiPX1no(crop size for radiometric image)
5PiPX2no 
6PiPY1no 
7PiPY2no 
+ +

FLIR GPSInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0GPSValidno0 = No +
1 = Yes
4GPSVersionIDno 
8GPSLatitudeRefno'N' = North +
'S' = South
10GPSLongitudeRefno'E' = East +
'W' = West
16GPSLatitudeno 
24GPSLongitudeno 
32GPSAltitudeno 
64GPSDOPno 
68GPSSpeedRefno'K' = km/h +
'M' = mph +
'N' = knots
70GPSTrackRefno'M' = Magnetic North +
'T' = True North
72GPSImgDirectionRefno'M' = Magnetic North +
'T' = True North
76GPSSpeedno 
80GPSTrackno 
84GPSImgDirectionno 
88GPSMapDatumno 
+ +

FLIR MeterLink Tags

+

Tags containing Meterlink humidity meter information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
26Reading1Unitsno0xd = C +
0x1b = % +
0x1d = Relative +
0x24 = g/kg
28Reading1Descriptionno +
0 = Humidity +
3 = Moisture +
7 = Dew Point +
8 = Air Temperature +
9 = IR Temperature +
11 = Difference Temperature
+
32Reading1Deviceno 
96Reading1Valueno 
126Reading2Unitsno0xd = C +
0x1b = % +
0x1d = rel +
0x24 = g/kg
128Reading2Descriptionno +
0 = Humidity +
3 = Moisture +
7 = Dew Point +
8 = Air Temperature +
9 = IR Temperature +
11 = Difference Temperature
+
132Reading2Deviceno 
196Reading2Valueno 
226Reading3Unitsno0xd = C +
0x1b = % +
0x1d = rel +
0x24 = g/kg
228Reading3Descriptionno +
0 = Humidity +
3 = Moisture +
7 = Dew Point +
8 = Air Temperature +
9 = IR Temperature +
11 = Difference Temperature
+
232Reading3Deviceno 
296Reading3Valueno 
326Reading4Unitsno0xd = C +
0x1b = % +
0x1d = rel +
0x24 = g/kg
328Reading4Descriptionno +
0 = Humidity +
3 = Moisture +
7 = Dew Point +
8 = Air Temperature +
9 = IR Temperature +
11 = Difference Temperature
+
332Reading4Deviceno 
396Reading4Valueno 
+ +

FLIR ParamInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
DateTimeGeneratedno 
Param0no 
Param1no 
Param2no 
Param3no 
+ +

FLIR UserData Tags

+

Tags written by some FLIR cameras in a top-level (!) "udta" atom of MP4 +videos.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'uuid'FLIR_Parts +
FLIR_Serial +
FLIR_Params +
FLIR_UnknownUUID +
FLIR_GPS +
FLIR_MoreInfo +
SoftwareComponents? +
FLIR_Unknown? +
Units +
ThumbnailImage
-
-
-
-
-
-
no
no
no+
no
--> FLIR Parts Tags +
--> FLIR SerialNums Tags +
--> FLIR Params Tags +
--> FLIR UnknownUUID Tags +
--> FLIR GPS_UUID Tags +
--> FLIR MoreInfo Tags
+ +

FLIR Parts Tags

+

Tags extracted from the "uuid" box with ID 43c3993b0f94424b82056b66513f485d +in FLIR MP4 videos.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
4BAHPVer +
BALPVer +
Battery +
BAVPVer +
CamCore +
DetectorBoard +
Detector +
GIDCVer +
GIDPVer +
GIPC_CPLD +
GIPCVer +
GIXIVer +
MainBoard +
Optics +
PartNumber
no
no
no
no
no
no
no
no
no
no
no
no
no
no
no
 
+ +

FLIR SerialNums Tags

+

Tags extracted from the "uuid" box with ID 57f5b93e51e448afa0d9c3ef1b37f712 +in FLIR MP4 videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12UnknownSerial1?no 
45UnknownSerial2?no 
78UnknownSerial3?no 
111UnknownSerial4?no 
123UnknownNumber?no 
126CameraSerialNumberno 
+ +

FLIR Params Tags

+

Tags extracted from the "uuid" box with ID 41e5dcf9e80a41ceadfe7f0c58082c19 +in FLIR MP4 videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1ReflectedApparentTemperatureno 
2AtmosphericTemperatureno 
3Emissivityno 
4ObjectDistanceno 
5RelativeHumidityno 
6EstimatedAtmosphericTransno 
7IRWindowTemperatureno 
8IRWindowTransmissionno 
+ +

FLIR UnknownUUID Tags

+

Tags extracted from the "uuid" box with ID 574520502cbb44adae5415e9b839d903 +in FLIR MP4 videos.

+
+
+ + + + +
Index4Tag NameWritableValues / Notes
[no tags known]
+ +

FLIR GPS_UUID Tags

+

Tags extracted from the "uuid" box with ID 7f2e21008b464918afb1de709a74f6f5 +in FLIR MP4 videos.

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1GPSLatitudeno 
2GPSLongitudeno 
3GPSAltitudeno 
+ +

FLIR MoreInfo Tags

+

Tags extracted from the "uuid" box with ID 2b452fdc74354094baee22a6b23a7cf8 +in FLIR MP4 videos.

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
5LensModelno 
11UnknownTemperature1?no 
15UnknownTemperature2?no 
+ +

FLIR AFF Tags

+

Tags extracted from FLIR "AFF" SEQ images.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'_header'AFFHeader---> FLIR Header Tags
0x0001AFF1---> FLIR AFF1 Tags
0x0005AFF5---> FLIR AFF5 Tags
+ +

FLIR AFF1 Tags

+
+
+ + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1SensorWidthno 
2SensorHeightno 
+ +

FLIR AFF5 Tags

+
+
+ + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
19SensorWidthno 
20SensorHeightno 
+ +

FLIR FPF Tags

+

Tags extracted from FLIR Public image Format (FPF) files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
32FPFVersionno 
36ImageDataOffsetno 
40ImageTypeno0 = Temperature +
1 = Temperature Difference +
2 = Object Signal +
3 = Object Signal Difference
42ImagePixelFormatno0 = 2-byte short integer +
1 = 4-byte long integer +
2 = 4-byte float +
3 = 8-byte double
44ImageWidthno 
46ImageHeightno 
48ExternalTriggerCountno 
52SequenceFrameNumberno 
120CameraModelno 
152CameraPartNumberno 
184CameraSerialNumberno 
216CameraTemperatureRangeMinno 
220CameraTemperatureRangeMaxno 
224LensModelno 
256LensPartNumberno 
288LensSerialNumberno 
320FilterModelno 
336FilterPartNumberno 
384FilterSerialNumberno 
480Emissivityno 
484ObjectDistanceno 
488ReflectedApparentTemperatureno 
492AtmosphericTemperatureno 
496RelativeHumidityno 
500ComputedAtmosphericTransno 
504EstimatedAtmosphericTransno 
508ReferenceTemperatureno 
512IRWindowTemperatureno 
516IRWindowTransmissionno 
584DateTimeOriginalno 
676CameraScaleMinno 
680CameraScaleMaxno 
684CalculatedScaleMinno 
688CalculatedScaleMaxno 
692ActualScaleMinno 
696ActualScaleMaxno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 16, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Flash.html b/ExifTool/html/TagNames/Flash.html new file mode 100644 index 0000000..9b18411 --- /dev/null +++ b/ExifTool/html/TagNames/Flash.html @@ -0,0 +1,420 @@ + + + + +Flash Tags + + + +

Flash Tags

+

The information below is extracted from SWF (Shockwave Flash) files. Tags +with string ID's represent information extracted from the file header.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Compressed'Compressedno0 = False +
1 = True
'Duration'Durationno(calculated from FrameRate and FrameCount)
'FlashVersion'FlashVersionno 
'FrameCount'FrameCountno 
'FrameRate'FrameRateno 
'ImageHeight'ImageHeightno 
'ImageWidth'ImageWidthno 
0x0045FlashAttributesnoBit 0 = UseNetwork +
Bit 3 = ActionScript3 +
Bit 4 = HasMetadata
0x004dXMP---> XMP Tags
+ +

Flash FLV Tags

+

Information is extracted from the following packets in FLV (Flash Video) +files.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0008Audio---> Flash Audio Tags
0x0009Video---> Flash Video Tags
0x0012Meta---> Flash Meta Tags
+ +

Flash Audio Tags

+

Information extracted from the Flash Audio header.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Bit0-3'AudioEncodingno +
0 = PCM-BE (uncompressed) +
1 = ADPCM +
2 = MP3 +
3 = PCM-LE (uncompressed) +
4 = Nellymoser 16kHz Mono +
5 = Nellymoser 8kHz Mono +
6 = Nellymoser +
7 = G.711 A-law logarithmic PCM +
8 = G.711 mu-law logarithmic PCM +
10 = AAC +
11 = Speex +
13 = MP3 8-Khz +
15 = Device-specific sound
+
'Bit4-5'AudioSampleRateno 
'Bit6'AudioBitsPerSampleno 
'Bit7'AudioChannelsno1 = 1 (mono) +
2 = 2 (stereo)
+ +

Flash Video Tags

+

Information extracted from the Flash Video header.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Bit4-7'VideoEncodingno + +
1 = JPEG +
2 = Sorensen H.263 +
3 = Screen Video +
4 = On2 VP6
  5 = On2 VP6 Alpha +
6 = Screen Video 2 +
7 = H.264
+
+ +

Flash Meta Tags

+

Below are a few observed FLV Meta tags, but ExifTool will attempt to extract +information from any tag found.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'audiocodecid'AudioCodecIDno 
'audiodatarate'AudioBitrateno 
'audiodelay'AudioDelayno 
'audiosamplerate'AudioSampleRateno 
'audiosamplesize'AudioSampleSizeno 
'audiosize'AudioSizeno 
'bytelength'ByteLengthno 
'canSeekToEnd'CanSeekToEndno 
'canseekontime'CanSeekOnTimeno 
'createdby'CreatedByno 
'creationdate'CreateDateno 
'cuePoints'CuePoint---> Flash CuePoint Tags
'datasize'DataSizeno 
'duration'Durationno 
'filesize'FileSizeBytesno 
'framerate'FrameRateno 
'hasAudio'HasAudiono 
'hasCuePoints'HasCuePointsno 
'hasKeyframes'HasKeyFramesno 
'hasMetadata'HasMetadatano 
'hasVideo'HasVideono 
'height'ImageHeightno 
'httphostheader'HTTPHostHeaderno 
'keyframesFilepositions'KeyFramePositionsno 
'keyframesTimes'KeyFramesTimesno 
'lastkeyframetimestamp'LastKeyFrameTimeno 
'lasttimestamp'LastTimeStampno 
'liveXML'XMP---> XMP Tags
'metadatacreator'MetadataCreatorno 
'metadatadate'MetadataDateno 
'pmsg'Messageno 
'purl'URLno 
'sourcedata'SourceDatano 
'starttime'StartTimeno 
'stereo'Stereono 
'totaldatarate'TotalDataRateno 
'totalduration'TotalDurationno 
'videocodecid'VideoCodecIDno 
'videodatarate'VideoBitrateno 
'videosize'VideoSizeno 
'width'ImageWidthno 
+ +

Flash CuePoint Tags

+

These tag names are added to the CuePoint name to generate complete tag +names like "CuePoint0Name".

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'name'Nameno 
'parameters'Parameter---> Flash Parameter Tags
'time'Timeno 
'type'Typeno 
+ +

Flash Parameter Tags

+

There are no pre-defined parameter tags, but ExifTool will extract any +existing parameters, with tag names like "CuePoint0ParameterXxx".

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 9, 2015 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/FlashPix.html b/ExifTool/html/TagNames/FlashPix.html new file mode 100644 index 0000000..f92d18b --- /dev/null +++ b/ExifTool/html/TagNames/FlashPix.html @@ -0,0 +1,1727 @@ + + + + +FlashPix Tags + + + +

FlashPix Tags

+

The FlashPix file format, introduced in 1996, was developed by Kodak, +Hewlett-Packard and Microsoft. Internally the FPX file structure mimics +that of an old DOS disk with fixed-sized "sectors" (usually 512 bytes) and a +"file allocation table" (FAT). No wonder this image format never became +popular. However, some of the structures used in FlashPix streams are part +of the EXIF specification, and are still being used in the APP2 FPXR segment +of JPEG images by some digital cameras from manufacturers such as FujiFilm, +Hewlett-Packard, Kodak and Sanyo.

+ +

ExifTool extracts FlashPix information from both FPX images and the APP2 +FPXR segment of JPEG images. As well, FlashPix information is extracted +from DOC, PPT, XLS (Microsoft Word, PowerPoint and Excel) documents, VSD +(Microsoft Visio) drawings, and FLA (Macromedia/Adobe Flash project) files +since these are based on the same file format as FlashPix (the Windows +Compound Binary File format). Note that ExifTool identifies any +unrecognized Windows Compound Binary file as a FlashPix (FPX) file. See +http://graphcomp.com/info/specs/livepicture/fpx.pdf for the FlashPix +specification.

+ +

Note that Microsoft is not consistent with the time zone used for some +date/time tags, and it may be either UTC or local time depending on the +software used to create the file.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
"\x01CompObj"CompObj---> FlashPix CompObj Tags
"\x05Audio Info"AudioInfo---> FlashPix AudioInfo Tags
"\x05Data Object"DataObject---> FlashPix DataObject Tags
"\x05DocumentSummaryInformation"DocumentInfo---> FlashPix DocumentInfo Tags
"\x05Extension List"Extensions---> FlashPix Extensions Tags
"\x05Global Info"GlobalInfo---> FlashPix GlobalInfo Tags
"\x05Image Contents"Image---> FlashPix Image Tags
"\x05Image Info"ImageInfo---> FlashPix ImageInfo Tags
"\x05Operation"Operation---> FlashPix Operation Tags
"\x05Screen Nail"ScreenNailno 
"\x05SummaryInformation"SummaryInfo---> FlashPix SummaryInfo Tags
"\x05Transform"Transform---> FlashPix Transform Tags
'Audio Stream'AudioStreamno 
'BasicFileInfo'BasicFileInfono 
'Contents'Contents---> XMP Tags +
(found in FLA files; may contain XMP)
'Current User'CurrentUserno 
'ICC Profile 0001'ICC_Profile---> ICC_Profile Tags
'IeImg'EmbeddedImageno(embedded images in Scene7 vignette VNT files. The EmbeddedImage Class and +Rectangle are also extracted for applicable images, and may be associated +with the corresponding EmbeddedImage via the family 3 group name)
'IeImg_class'EmbeddedImageClassno(not a real tag. This information is extracted if available for the +corresponding EmbeddedImage from the Contents of a VNT file)
'IeImg_rect'EmbeddedImageRectangleno(not a real tag. This information is extracted if available for the +corresponding EmbeddedImage from the Contents of a VNT file)
'Preview'PreviewImageno(written by some FujiFilm models)
'Property'PreviewInfo---> FlashPix PreviewInfo Tags
'Subimage 0000 Header'SubimageHdr---> FlashPix SubimageHdr Tags
'WordDocument'WordDocument---> FlashPix WordDocument Tags
+ +

FlashPix CompObj Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0CompObjUserTypeLenno 
1CompObjUserTypeno 
+ +

FlashPix AudioInfo Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

FlashPix DataObject Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x10000DataObjectIDno 
0x10002LockedPropertyListno 
0x10003DataObjectTitleno 
0x10004LastModifierno 
0x10005RevisionNumberno 
0x10006DataCreateDateno 
0x10007DataModifyDateno 
0x10008CreatingApplicationno 
0x10100DataObjectStatusno 
0x10101CreatingTransformno 
0x10102UsingTransformsno 
0x10000000CachedImageHeightno 
0x10000001CachedImageWidthno 
+ +

FlashPix DocumentInfo Tags

+

The DocumentSummaryInformation property set includes a UserDefined property +set for which only the Hyperlinks and HyperlinkBase tags are pre-defined. +However, ExifTool will also extract any other information found in the +UserDefined properties.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0002Categoryno 
0x0003PresentationTargetno 
0x0004Bytesno 
0x0005Linesno 
0x0006Paragraphsno 
0x0007Slidesno 
0x0008Notesno 
0x0009HiddenSlidesno 
0x000aMMClipsno 
0x000bScaleCropno0 = No +
1 = Yes
0x000cHeadingPairsno 
0x000dTitleOfPartsno 
0x000eManagerno 
0x000fCompanyno 
0x0010LinksUpToDateno0 = No +
1 = Yes
0x0011CharCountWithSpacesno 
0x0013SharedDocno0 = No +
1 = Yes
0x0016HyperlinksChangedno0 = No +
1 = Yes
0x0017AppVersionno 
0x001aContentTypeno 
0x001bContentStatusno 
0x001cLanguageno 
0x001dDocVersionno 
'_PID_HLINKS'Hyperlinksno 
'_PID_LINKBASE'HyperlinkBaseno 
+ +

FlashPix Extensions Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001ExtensionNameno 
0x0002ExtensionClassIDno 
0x0003ExtensionPersistenceno0 = Always Valid +
1 = Invalidated By Modification +
2 = Potentially Invalidated By Modification
0x0004ExtensionCreateDateno 
0x0005ExtensionModifyDateno 
0x0006CreatingApplicationno 
0x0007ExtensionDescriptionno 
0x1000Storage-StreamPathnameno 
0x2000FlashPixStreamPathnameno 
0x2001FlashPixStreamFieldOffsetno 
0x3000PropertySetPathnameno 
0x3001PropertySetIDCodesno 
0x3002PropertyVectorElementsno 
0x4000SubimageResolutionsno 
0x10000000UsedExtensionNumbersno 
+ +

FlashPix GlobalInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x10002LockedPropertyListno 
0x10003TransformedImageTitleno 
0x10004LastModifierno 
0x10100VisibleOutputsno 
0x10101MaximumImageIndexno 
0x10102MaximumTransformIndexno 
0x10103MaximumOperationIndexno 
+ +

FlashPix Image Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x1000000NumberOfResolutionsno 
0x1000002ImageWidthno 
0x1000003ImageHeightno 
0x1000004DefaultDisplayHeightno 
0x1000005DefaultDisplayWidthno 
0x1000006DisplayUnitsno0 = inches +
1 = meters +
2 = cm +
3 = mm
0x2000000SubimageWidthno 
0x2000001SubimageHeightno 
0x2000002SubimageColorno +
'01 0000' = Opacity Only +
'01 0001' = Monochrome +
'01 8000' = Opacity Only (uncalibrated) +
'01 8001' = Monochrome (uncalibrated) +
'03 0002' = YCbCr +
'03 0003' = RGB +
'03 8002' = YCbCr (uncalibrated) +
'03 8003' = RGB (uncalibrated) +
'04 0002' = YCbCr with Opacity +
'04 0003' = RGB with Opacity +
'04 8002' = YCbCr with Opacity (uncalibrated) +
'04 8003' = RGB with Opacity (uncalibrated)
+
0x2000003SubimageNumericalFormatno17 = 8-bit, Unsigned +
18 = 16-bit, Unsigned +
19 = 32-bit, Unsigned
0x2000004DecimationMethodno0 = None (Full-sized Image) +
8 = 8-point Prefilter
0x2000005DecimationPrefilterWidthno 
0x2000007SubimageICC_Profileno 
0x3000001JPEGTablesno 
0x3000002MaxJPEGTableIndexno 
+ +

FlashPix ImageInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x21000000FileSourceno1 = Film Scanner +
2 = Reflection Print Scanner +
3 = Digital Camera +
4 = Video Capture +
5 = Computer Graphics
0x21000001SceneTypeno1 = Original Scene +
2 = Second Generation Scene +
3 = Digital Scene Generation
0x21000002CreationPathVectorno 
0x21000003SoftwareReleaseno 
0x21000004UserDefinedIDno 
0x21000005SharpnessApproximationno 
0x22000000Copyrightno 
0x22000001OriginalImageBrokerno 
0x22000002DigitalImageBrokerno 
0x22000003Authorshipno 
0x22000004IntellectualPropertyNotesno 
0x23000000TestTargetno + +
1 = Color Chart +
2 = Gray Card +
3 = Grayscale +
4 = Resolution Chart
  5 = Inch Scale +
6 = Centimeter Scale +
7 = Millimeter Scale +
8 = Micrometer Scale
+
0x23000002GroupCaptionno 
0x23000003CaptionTextno 
0x23000004Peopleno 
0x23000007Thingsno 
0x2300000aDateTimeOriginalno 
0x2300000bEventsno 
0x2300000cPlacesno 
0x2300000fContentDescriptionNotesno 
0x24000000Makeno 
0x24000001Modelno 
0x24000002SerialNumberno 
0x25000000CreateDateno 
0x25000001ExposureTimeno 
0x25000002FNumberno 
0x25000003ExposureProgramno +
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 +
9 = Bulb
+
0x25000004BrightnessValueno 
0x25000005ExposureCompensationno 
0x25000006SubjectDistanceno 
0x25000007MeteringModeno +
0 = Unknown +
1 = Average +
2 = Center-weighted average +
3 = Spot +
4 = Multi-spot +
5 = Multi-segment +
6 = Partial +
255 = Other
+
0x25000008LightSourceno +
0 = Unknown +
1 = Daylight +
2 = Fluorescent +
3 = Tungsten (Incandescent) +
4 = Flash +
9 = Fine Weather +
10 = Cloudy +
11 = Shade +
12 = Daylight Fluorescent +
13 = Day White Fluorescent +
14 = Cool White Fluorescent +
15 = White Fluorescent +
16 = Warm White Fluorescent +
17 = Standard Light A +
18 = Standard Light B +
19 = Standard Light C +
20 = D55 +
21 = D65 +
22 = D75 +
23 = D50 +
24 = ISO Studio Tungsten +
255 = Other
+
0x25000009FocalLengthno 
0x2500000aMaxApertureValueno 
0x2500000bFlashno1 = No Flash +
2 = Flash Fired
0x2500000cFlashEnergyno 
0x2500000dFlashReturnno1 = Subject Outside Flash Range +
2 = Subject Inside Flash Range
0x2500000eBackLightno1 = Front Lit +
2 = Back Lit 1 +
3 = Back Lit 2
0x2500000fSubjectLocationno 
0x25000010ExposureIndexno 
0x25000011SpecialEffectsOpticalFilterno + +
1 = None +
2 = Colored +
3 = Diffusion +
4 = Multi-image
  5 = Polarizing +
6 = Split-field +
7 = Star
+
0x25000012PerPictureNotesno 
0x26000000SensingMethodno +
1 = Monochrome area +
2 = One-chip color area +
3 = Two-chip color area +
4 = Three-chip color area +
5 = Color sequential area +
6 = Monochrome linear +
7 = Trilinear +
8 = Color sequential linear
+
0x26000001FocalPlaneXResolutionno 
0x26000002FocalPlaneYResolutionno 
0x26000003FocalPlaneResolutionUnitno1 = None +
2 = inches +
3 = cm +
4 = mm +
5 = um
0x26000004SpatialFrequencyResponseno 
0x26000005CFAPatternno 
0x26000007ISOno 
0x26000008Opto-ElectricConvFactorno 
0x27000000FilmBrandno 
0x27000001FilmCategoryno 
0x27000002FilmSizeno 
0x27000003FilmRollNumberno 
0x27000004FilmFrameNumberno 
0x28000000ScannerMakeno 
0x28000001ScannerModelno 
0x28000002ScannerSerialNumberno 
0x28000003ScanSoftwareno 
0x28000004ScanSoftwareRevisionDateno 
0x28000005ServiceOrganizationNameno 
0x28000006ScanOperatorIDno 
0x28000008ScanDateno 
0x28000009ModifyDateno 
0x2800000aScannerPixelSizeno 
0x29000000OriginalScannedImageSizeno 
0x29000001OriginalDocumentSizeno 
0x29000002OriginalMediumno1 = Continuous Tone Image +
2 = Halftone Image +
3 = Line Art
0x29000003TypeOfOriginalno1 = B&W Print +
2 = Color Print +
3 = B&W Document +
4 = Color Document
+ +

FlashPix Operation Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x10000OperationIDno 
+ +

FlashPix SummaryInfo Tags

+

The Dictionary, CodePage and LocalIndicator tags are common to all FlashPix +property tables, even though they are only listed in the SummaryInfo table.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000Dictionaryno 
0x0001CodePageno37 = IBM EBCDIC US-Canada +
437 = DOS United States +
500 = IBM EBCDIC International +
708 = Arabic (ASMO 708) +
709 = Arabic (ASMO-449+, BCON V4) +
710 = Arabic - Transparent Arabic +
720 = DOS Arabic (Transparent ASMO) +
737 = DOS Greek (formerly 437G) +
775 = DOS Baltic +
850 = DOS Latin 1 (Western European) +
852 = DOS Latin 2 (Central European) +
855 = DOS Cyrillic (primarily Russian) +
857 = DOS Turkish +
858 = DOS Multilingual Latin 1 with Euro +
860 = DOS Portuguese +
861 = DOS Icelandic +
862 = DOS Hebrew +
863 = DOS French Canadian +
864 = DOS Arabic +
865 = DOS Nordic +
866 = DOS Russian (Cyrillic) +
869 = DOS Modern Greek +
870 = IBM EBCDIC Multilingual/ROECE (Latin 2) +
874 = Windows Thai (same as 28605, ISO 8859-15) +
875 = IBM EBCDIC Greek Modern +
932 = Windows Japanese (Shift-JIS) +
936 = Windows Simplified Chinese (PRC, Singapore) +
949 = Windows Korean (Unified Hangul Code) +
950 = Windows Traditional Chinese (Taiwan) +
1026 = IBM EBCDIC Turkish (Latin 5) +
1047 = IBM EBCDIC Latin 1/Open System +
1140 = IBM EBCDIC US-Canada with Euro +
1141 = IBM EBCDIC Germany with Euro +
1142 = IBM EBCDIC Denmark-Norway with Euro +
1143 = IBM EBCDIC Finland-Sweden with Euro +
1144 = IBM EBCDIC Italy with Euro +
1145 = IBM EBCDIC Latin America-Spain with Euro +
1146 = IBM EBCDIC United Kingdom with Euro +
1147 = IBM EBCDIC France with Euro +
1148 = IBM EBCDIC International with Euro +
1149 = IBM EBCDIC Icelandic with Euro +
1200 = Unicode UTF-16, little endian +
1201 = Unicode UTF-16, big endian +
1250 = Windows Latin 2 (Central European) +
1251 = Windows Cyrillic +
1252 = Windows Latin 1 (Western European) +
1253 = Windows Greek +
1254 = Windows Turkish +
1255 = Windows Hebrew +
1256 = Windows Arabic +
1257 = Windows Baltic +
1258 = Windows Vietnamese +
1361 = Korean (Johab) +
10000 = Mac Roman (Western European) +
10001 = Mac Japanese +
10002 = Mac Traditional Chinese +
10003 = Mac Korean +
10004 = Mac Arabic +
10005 = Mac Hebrew +
10006 = Mac Greek +
10007 = Mac Cyrillic +
10008 = Mac Simplified Chinese +
10010 = Mac Romanian +
10017 = Mac Ukrainian +
10021 = Mac Thai +
10029 = Mac Latin 2 (Central European) +
10079 = Mac Icelandic +
10081 = Mac Turkish +
10082 = Mac Croatian +
12000 = Unicode UTF-32, little endian +
12001 = Unicode UTF-32, big endian +
20000 = CNS Taiwan +
20001 = TCA Taiwan +
20002 = Eten Taiwan +
20003 = IBM5550 Taiwan +
20004 = TeleText Taiwan +
20005 = Wang Taiwan +
20105 = IA5 (IRV International Alphabet No. 5, 7-bit) +
20106 = IA5 German (7-bit) +
20107 = IA5 Swedish (7-bit) +
20108 = IA5 Norwegian (7-bit) +
20127 = US-ASCII (7-bit) +
20261 = T.61 +
20269 = ISO 6937 Non-Spacing Accent +
20273 = IBM EBCDIC Germany +
20277 = IBM EBCDIC Denmark-Norway +
20278 = IBM EBCDIC Finland-Sweden +
20280 = IBM EBCDIC Italy +
20284 = IBM EBCDIC Latin America-Spain +
20285 = IBM EBCDIC United Kingdom +
20290 = IBM EBCDIC Japanese Katakana Extended +
20297 = IBM EBCDIC France +
20420 = IBM EBCDIC Arabic +
20423 = IBM EBCDIC Greek +
20424 = IBM EBCDIC Hebrew +
20833 = IBM EBCDIC Korean Extended +
20838 = IBM EBCDIC Thai +
20866 = Russian/Cyrillic (KOI8-R) +
20871 = IBM EBCDIC Icelandic +
20880 = IBM EBCDIC Cyrillic Russian +
20905 = IBM EBCDIC Turkish +
20924 = IBM EBCDIC Latin 1/Open System with Euro +
20932 = Japanese (JIS 0208-1990 and 0121-1990) +
20936 = Simplified Chinese (GB2312) +
20949 = Korean Wansung +
21025 = IBM EBCDIC Cyrillic Serbian-Bulgarian +
21027 = Extended Alpha Lowercase (deprecated) +
21866 = Ukrainian/Cyrillic (KOI8-U) +
28591 = ISO 8859-1 Latin 1 (Western European) +
28592 = ISO 8859-2 (Central European) +
28593 = ISO 8859-3 Latin 3 +
28594 = ISO 8859-4 Baltic +
28595 = ISO 8859-5 Cyrillic +
28596 = ISO 8859-6 Arabic +
28597 = ISO 8859-7 Greek +
28598 = ISO 8859-8 Hebrew (Visual) +
28599 = ISO 8859-9 Turkish +
28603 = ISO 8859-13 Estonian +
28605 = ISO 8859-15 Latin 9 +
29001 = Europa 3 +
38598 = ISO 8859-8 Hebrew (Logical) +
50220 = ISO 2022 Japanese with no halfwidth Katakana (JIS) +
50221 = ISO 2022 Japanese with halfwidth Katakana (JIS-Allow 1 byte Kana) +
50222 = ISO 2022 Japanese JIS X 0201-1989 (JIS-Allow 1 byte Kana - SO/SI) +
50225 = ISO 2022 Korean +
50227 = ISO 2022 Simplified Chinese +
50229 = ISO 2022 Traditional Chinese +
50930 = EBCDIC Japanese (Katakana) Extended +
50931 = EBCDIC US-Canada and Japanese +
50933 = EBCDIC Korean Extended and Korean +
50935 = EBCDIC Simplified Chinese Extended and Simplified Chinese +
50936 = EBCDIC Simplified Chinese +
50937 = EBCDIC US-Canada and Traditional Chinese +
50939 = EBCDIC Japanese (Latin) Extended and Japanese +
51932 = EUC Japanese +
51936 = EUC Simplified Chinese +
51949 = EUC Korean +
51950 = EUC Traditional Chinese +
52936 = HZ-GB2312 Simplified Chinese +
54936 = Windows XP and later: GB18030 Simplified Chinese (4 byte) +
57002 = ISCII Devanagari +
57003 = ISCII Bengali +
57004 = ISCII Tamil +
57005 = ISCII Telugu +
57006 = ISCII Assamese +
57007 = ISCII Oriya +
57008 = ISCII Kannada +
57009 = ISCII Malayalam +
57010 = ISCII Gujarati +
57011 = ISCII Punjabi +
65000 = Unicode (UTF-7) +
65001 = Unicode (UTF-8)
0x0002Titleno 
0x0003Subjectno 
0x0004Authorno 
0x0005Keywordsno 
0x0006Commentsno 
0x0007Templateno 
0x0008LastModifiedByno 
0x0009RevisionNumberno 
0x000aTotalEditTimeno 
0x000bLastPrintedno 
0x000cCreateDateno 
0x000dModifyDateno 
0x000ePagesno 
0x000fWordsno 
0x0010Charactersno 
0x0011ThumbnailClipno 
0x0012Softwareno 
0x0013Securityno0x0 = None +
Bit 0 = Password protected +
Bit 1 = Read-only recommended +
Bit 2 = Read-only enforced +
Bit 3 = Locked for annotations
0x0022CreatedByno 
0x0023DocumentIDno 
0x80000000LocaleIndicatorno 
+ +

FlashPix Transform Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x10000TransformNodeIDno 
0x10001OperationClassIDno 
0x10002LockedPropertyListno 
0x10003TransformTitleno 
0x10004LastModifierno 
0x10005RevisionNumberno 
0x10006TransformCreateDateno 
0x10007TransformModifyDateno 
0x10008CreatingApplicationno 
0x10100InputDataObjectListno 
0x10101OutputDataObjectListno 
0x10102OperationNumberno 
0x10000000ResultAspectRationo 
0x10000001RectangleOfInterestno 
0x10000002Filteringno 
0x10000003SpatialOrientationno 
0x10000004ColorTwistMatrixno 
0x10000005ContrastAdjustmentno 
+ +

FlashPix PreviewInfo Tags

+

Preview information written by some FujiFilm models.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
13PreviewImageWidthno 
23PreviewImageHeightno 
+ +

FlashPix SubimageHdr Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1SubimageWidthno 
2SubimageHeightno 
3SubimageTileCountno 
4SubimageTileWidthno 
5SubimageTileHeightno 
6NumChannelsno 
+ +

FlashPix WordDocument Tags

+

Tags extracted from the Microsoft Word document stream.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000Identificationno0x626a = Word 98 Mac +
0x6a62 = MS Word 97 +
0xa5dc = Word 6.0/7.0 +
0xa5ec = Word 8.0
0x0003LanguageCodeno +
0x400 = None +
0x401 = Arabic +
0x402 = Bulgarian +
0x403 = Catalan +
0x404 = Traditional Chinese +
0x405 = Czech +
0x406 = Danish +
0x407 = German +
0x408 = Greek +
0x409 = English (US) +
0x40a = Spanish (Castilian) +
0x40b = Finnish +
0x40c = French +
0x40d = Hebrew +
0x40e = Hungarian +
0x40f = Icelandic +
0x410 = Italian +
0x411 = Japanese +
0x412 = Korean +
0x413 = Dutch +
0x414 = Norwegian (Bokmal) +
0x415 = Polish +
0x416 = Portuguese (Brazilian) +
0x417 = Rhaeto-Romanic +
0x418 = Romanian +
0x419 = Russian +
0x41a = Croato-Serbian (Latin) +
0x41b = Slovak +
0x41c = Albanian +
0x41d = Swedish +
0x41e = Thai +
0x41f = Turkish +
0x420 = Urdu +
0x421 = Bahasa +
0x422 = Ukrainian +
0x423 = Byelorussian +
0x424 = Slovenian +
0x425 = Estonian +
0x426 = Latvian +
0x427 = Lithuanian +
0x429 = Farsi +
0x42d = Basque +
0x42f = Macedonian +
0x436 = Afrikaans +
0x43e = Malaysian +
0x804 = Simplified Chinese +
0x807 = German (Swiss) +
0x809 = English (British) +
0x80a = Spanish (Mexican) +
0x80c = French (Belgian) +
0x810 = Italian (Swiss) +
0x813 = Dutch (Belgian) +
0x814 = Norwegian (Nynorsk) +
0x816 = Portuguese +
0x81a = Serbo-Croatian (Cyrillic) +
0xc09 = English (Australian) +
0xc0c = French (Canadian) +
0x100c = French (Swiss)
+
0x0005DocFlagsno[val & 0xff0f] + +
Bit 0 = Template +
Bit 1 = AutoText only +
Bit 2 = Complex +
Bit 3 = Has picture +
Bit 8 = Encrypted +
Bit 9 = 1Table
  Bit 10 = Read only +
Bit 11 = Passworded +
Bit 12 = ExtChar +
Bit 13 = Load override +
Bit 14 = Far east +
Bit 15 = Obfuscated
+
9.1Systemno[val & 0x1] +
0 = Windows +
1 = Macintosh
9.2Word97no[val >> 4 & 0x1] +
0 = No +
1 = Yes
+ +

FlashPix DocTable Tags

+

Tags extracted from the Microsoft Word document table.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CommentByno(enable Duplicates option to extract all entries)
DOP---> FlashPix DOP Tags
LastSavedByno(enable Duplicates option to extract history of up to 10 entries)
ModifyDateno 
+ +

FlashPix DOP Tags

+

Microsoft office document properties.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
20CreateDateno 
24ModifyDateno 
28LastPrintedno 
32RevisionNumberno 
34TotalEditTimeno 
38Wordsno 
42Charactersno 
46Pagesno 
48Paragraphsno 
56Linesno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 10, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Font.html b/ExifTool/html/TagNames/Font.html new file mode 100644 index 0000000..72538e5 --- /dev/null +++ b/ExifTool/html/TagNames/Font.html @@ -0,0 +1,486 @@ + + + + +Font Tags + + + +

Font Tags

+

This table contains a collection of tags found in font files of various +formats. ExifTool current recognizes OTF, TTF, TTC, DFONT, PFA, PFB, PFM, +AFM, ACFM and AMFM font files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AFM'AFM---> Font AFM Tags
'PFM'PFMHeader---> Font PFM Tags
'PSInfo'PSFontInfo---> Font PSInfo Tags
'fontname'FontNameno 
'name'Name---> Font Name Tags
'numfonts'NumFontsno 
'postfont'PostScriptFontNameno 
+ +

Font AFM Tags

+

Tags extracted from Adobe Font Metrics files (AFM, ACFM and AMFM).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Ascender'Ascenderno 
'CapHeight'CapHeightno 
'CharacterSet'CharacterSetno 
'Characters'Charactersno 
'Creation Date'CreateDateno 
'Descender'Descenderno 
'EncodingScheme'EncodingSchemeno 
'EscChar'EscCharno 
'FamilyName'FontFamilyno 
'FontName'FontNameno 
'FullName'FullNameno 
'IsBaseFont'IsBaseFontno 
'IsFixedV'IsFixedVno 
'MappingScheme'MappingSchemeno 
'Notice'Noticeno 
'Version'Versionno 
'Weight'Weightno 
'XHeight'XHeightno 
+ +

Font PFM Tags

+

Tags extracted from the PFM file header.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PFMVersionno 
6Copyrightno 
66FontTypeno 
68PointSizeno 
70YResolutionno 
72XResolutionno 
74Ascentno 
76InternalLeadingno 
78ExternalLeadingno 
80Italicno 
81Underlineno 
82Strikeoutno 
83Weightno 
85CharacterSetno 
86PixWidthno 
88PixHeightno 
90PitchAndFamilyno 
91AvgWidthno 
93MaxWidthno 
95FirstCharno 
96LastCharno 
97DefaultCharno 
98BreakCharno 
99WidthBytesno 
+ +

Font PSInfo Tags

+

Tags extracted from PostScript font files (PFA and PFB).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Copyright'Copyrightno 
'FSType'FSTypeno 
'FamilyName'FontFamilyno 
'FontName'FontNameno 
'FontType'FontTypeno 
'FullName'FullNameno 
'ItalicAngle'ItalicAngleno 
'Notice'Noticeno 
'UnderlinePosition'UnderlinePositionno 
'UnderlineThickness'UnderlineThicknessno 
'Weight'Weightno 
'isFixedPitch'IsFixedPitchno 
'version'Versionno 
+ +

Font Name Tags

+

The following tags are extracted from the TrueType font "name" table found +in OTF, TTF, TTC and DFONT files. These tags support localized languages by +adding a hyphen followed by a language code to the end of the tag name (eg. +"Copyright-fr" or "License-en-US"). Tags with no language code use the +default language of "en".

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000Copyrightno 
0x0001FontFamilyno 
0x0002FontSubfamilyno 
0x0003FontSubfamilyIDno 
0x0004FontNameno 
0x0005NameTableVersionno 
0x0006PostScriptFontNameno 
0x0007Trademarkno 
0x0008Manufacturerno 
0x0009Designerno 
0x000aDescriptionno 
0x000bVendorURLno 
0x000cDesignerURLno 
0x000dLicenseno 
0x000eLicenseInfoURLno 
0x0010PreferredFamilyno 
0x0011PreferredSubfamilyno 
0x0012CompatibleFontNameno 
0x0013SampleTextno 
0x0014PostScriptFontNameno 
0x0015WWSFamilyNameno 
0x0016WWSSubfamilyNameno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jan 31, 2012 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/FotoStation.html b/ExifTool/html/TagNames/FotoStation.html new file mode 100644 index 0000000..4149e33 --- /dev/null +++ b/ExifTool/html/TagNames/FotoStation.html @@ -0,0 +1,104 @@ + + + + +FotoStation Tags + + + +

FotoStation Tags

+

The following tables define information found in the FotoWare FotoStation +trailer.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001IPTC---> IPTC Tags
0x0002SoftEdit---> FotoStation SoftEdit Tags
0x0003ThumbnailImageyes 
0x0004PreviewImageyes 
+ +

FotoStation SoftEdit Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0OriginalImageWidthint32s 
1OriginalImageHeightint32s 
2ColorPlanesint32s 
3XYResolutionint32s 
4Rotationint32s(rotations are stored as degrees CCW * 100, but converted to degrees CW by +ExifTool)
6CropLeftint32s 
7CropTopint32s 
8CropRightint32s 
9CropBottomint32s 
11CropRotationint32s 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 20, 2006 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/FujiFilm.html b/ExifTool/html/TagNames/FujiFilm.html new file mode 100644 index 0000000..6f04d89 --- /dev/null +++ b/ExifTool/html/TagNames/FujiFilm.html @@ -0,0 +1,1280 @@ + + + + +FujiFilm Tags + + + +

FujiFilm Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000Versionundef 
0x0010InternalSerialNumberstring(this number is unique for most models, and contains the camera model ID and +the date of manufacture)
0x1000Qualitystring 
0x1001Sharpnessint16u +
0x0 = -4 (softest) +
0x1 = -3 (very soft) +
0x2 = -2 (soft) +
0x3 = 0 (normal) +
0x4 = +2 (hard) +
0x5 = +3 (very hard) +
0x6 = +4 (hardest) +
0x82 = -1 (medium soft) +
0x84 = +1 (medium hard) +
0x8000 = Film Simulation +
0xffff = n/a
+
0x1002WhiteBalanceint16u +
0x0 = Auto +
0x1 = Auto (white priority) +
0x2 = Auto (ambiance priority) +
0x100 = Daylight +
0x200 = Cloudy +
0x300 = Daylight Fluorescent +
0x301 = Day White Fluorescent +
0x302 = White Fluorescent +
0x303 = Warm White Fluorescent +
0x304 = Living Room Warm White Fluorescent +
0x400 = Incandescent +
0x500 = Flash +
0x600 = Underwater +
0xf00 = Custom +
0xf01 = Custom2 +
0xf02 = Custom3 +
0xf03 = Custom4 +
0xf04 = Custom5 +
0xff0 = Kelvin
+
0x1003Saturationint16u +
0x0 = 0 (normal) +
0x80 = +1 (medium high) +
0xc0 = +3 (very high) +
0xe0 = +4 (highest) +
0x100 = +2 (high) +
0x180 = -1 (medium low) +
0x200 = Low +
0x300 = None (B&W) +
0x301 = B&W Red Filter +
0x302 = B&W Yellow Filter +
0x303 = B&W Green Filter +
0x310 = B&W Sepia +
0x400 = -2 (low) +
0x4c0 = -3 (very low) +
0x4e0 = -4 (lowest) +
0x500 = Acros +
0x501 = Acros Red Filter +
0x502 = Acros Yellow Filter +
0x503 = Acros Green Filter +
0x8000 = Film Simulation
+
0x1004Contrastint16u +
0x0 = Normal +
0x80 = Medium High +
0x100 = High +
0x180 = Medium Low +
0x200 = Low +
0x8000 = Film Simulation
+
0x1005ColorTemperatureint16u 
0x1006Contrastint16u0x0 = Normal +
0x100 = High +
0x300 = Low
0x100aWhiteBalanceFineTuneint32s[2](newer cameras should divide these values by 20)
0x100bNoiseReductionint16u0x40 = Low +
0x80 = Normal +
0x100 = n/a
0x100eNoiseReductionint16u +
0x0 = 0 (normal) +
0x100 = +2 (strong) +
0x180 = +1 (medium strong) +
0x1c0 = +3 (very strong) +
0x1e0 = +4 (strongest) +
0x200 = -2 (weak) +
0x280 = -1 (medium weak) +
0x2c0 = -3 (very weak) +
0x2e0 = -4 (weakest)
+
0x100fClarityint32s + + +
-5000 = -5 +
-4000 = -4 +
-3000 = -3 +
-2000 = -2
  -1000 = -1 +
0 = 0 +
1000 = 1 +
2000 = 2
  3000 = 3 +
4000 = 4 +
5000 = 5
+
0x1010FujiFlashModeint16u0x0 = Auto +
0x1 = On +
0x2 = Off +
0x3 = Red-eye reduction +
0x4 = External +
0x10 = Commander +
0x8000 = Not Attached +
0x8120 = TTL +
0x8320 = TTL Auto - Did not fire +
0x9840 = Manual +
0x9860 = Flash Commander +
0x9880 = Multi-flash +
0xa920 = 1st Curtain (front) +
0xaa20 = TTL Slow - 1st Curtain (front) +
0xab20 = TTL Auto - 1st Curtain (front) +
0xad20 = TTL - Red-eye Flash - 1st Curtain (front) +
0xae20 = TTL Slow - Red-eye Flash - 1st Curtain (front) +
0xaf20 = TTL Auto - Red-eye Flash - 1st Curtain (front) +
0xc920 = 2nd Curtain (rear) +
0xca20 = TTL Slow - 2nd Curtain (rear) +
0xcb20 = TTL Auto - 2nd Curtain (rear) +
0xcd20 = TTL - Red-eye Flash - 2nd Curtain (rear) +
0xce20 = TTL Slow - Red-eye Flash - 2nd Curtain (rear) +
0xcf20 = TTL Auto - Red-eye Flash - 2nd Curtain (rear) +
0xe920 = High Speed Sync (HSS)
0x1011FlashExposureComprational64s 
0x1020Macroint16u0 = Off +
1 = On
0x1021FocusModeint16u0 = Auto +
1 = Manual +
65535 = Movie
0x1022AFModeint16u("No" for manual and some AF-multi focus modes) +
0 = No +
1 = Single Point +
256 = Zone +
512 = Wide/Tracking
0x1023FocusPixelint16u[2] 
0x102bPrioritySettings---> FujiFilm PrioritySettings Tags
0x102dFocusSettings---> FujiFilm FocusSettings Tags
0x102eAFCSettings---> FujiFilm AFCSettings Tags
0x1030SlowSyncint16u0 = Off +
1 = On
0x1031PictureModeint16u +
0x0 = Auto +
0x1 = Portrait +
0x2 = Landscape +
0x3 = Macro +
0x4 = Sports +
0x5 = Night Scene +
0x6 = Program AE +
0x7 = Natural Light +
0x8 = Anti-blur +
0x9 = Beach & Snow +
0xa = Sunset +
0xb = Museum +
0xc = Party +
0xd = Flower +
0xe = Text +
0xf = Natural Light & Flash +
0x10 = Beach +
0x11 = Snow +
0x12 = Fireworks +
0x13 = Underwater +
0x14 = Portrait with Skin Correction +
0x16 = Panorama +
0x17 = Night (tripod) +
0x18 = Pro Low-light +
0x19 = Pro Focus +
0x1a = Portrait 2 +
0x1b = Dog Face Detection +
0x1c = Cat Face Detection +
0x30 = HDR +
0x40 = Advanced Filter +
0x100 = Aperture-priority AE +
0x200 = Shutter speed priority AE +
0x300 = Manual
+
0x1032ExposureCountint16u(number of exposures used for this image)
0x1033EXRAutoint16u0 = Auto +
1 = Manual
0x1034EXRModeint16u0x100 = HR (High Resolution) +
0x200 = SN (Signal to Noise priority) +
0x300 = DR (Dynamic Range priority)
0x1040ShadowToneint32s + +
-64 = +4 (hardest) +
-48 = +3 (very hard) +
-32 = +2 (hard) +
-16 = +1 (medium hard)
  0 = 0 (normal) +
16 = -1 (medium soft) +
32 = -2 (soft)
+
0x1041HighlightToneint32s + +
-64 = +4 (hardest) +
-48 = +3 (very hard) +
-32 = +2 (hard) +
-16 = +1 (medium hard)
  0 = 0 (normal) +
16 = -1 (medium soft) +
32 = -2 (soft)
+
0x1044DigitalZoomint32u 
0x1045LensModulationOptimizerint32u0 = Off +
1 = On
0x1047GrainEffectRoughnessint32s0 = Off +
32 = Weak +
64 = Strong
0x1048ColorChromeEffectint32s0 = Off +
32 = Weak +
64 = Strong
0x1049BWAdjustmentint8s(positive values are warm, negative values are cool)
0x104bBWMagentaGreenint8s(positive values are green, negative values are magenta)
0x104cGrainEffectSizeint16u0 = Off +
16 = Small +
32 = Large
0x104dCropModeint16u0 = n/a +
1 = Full-frame on GFX +
2 = Sports Finder Mode +
4 = Electronic Shutter 1.25x Crop
0x104eColorChromeFXBlueint32s0 = Off +
32 = Weak +
64 = Strong
0x1050ShutterTypeint16u0 = Mechanical +
1 = Electronic +
2 = Electronic (long shutter speed) +
3 = Electronic Front Curtain
0x1100AutoBracketingint16u(X-T3 only) +
0 = Off +
1 = On +
2 = Pre-shot +
(other models) +
0 = Off +
1 = On +
2 = No flash & flash +
6 = Pixel Shift
0x1101SequenceNumberint16u 
0x1103DriveSettings---> FujiFilm DriveSettings Tags
0x1105PixelShiftShotsint16u 
0x1106PixelShiftOffsetrational64s[2] 
0x1153PanoramaAngleint16u 
0x1154PanoramaDirectionint16u1 = Right +
2 = Up +
3 = Left +
4 = Down
0x1201AdvancedFilterint32u +
0x10000 = Pop Color +
0x20000 = Hi Key +
0x30000 = Toy Camera +
0x40000 = Miniature +
0x50000 = Dynamic Tone +
0x60001 = Partial Color Red +
0x60002 = Partial Color Yellow +
0x60003 = Partial Color Green +
0x60004 = Partial Color Blue +
0x60005 = Partial Color Orange +
0x60006 = Partial Color Purple +
0x70000 = Soft Focus +
0x90000 = Low Key
+
0x1210ColorModeint16u0x0 = Standard +
0x10 = Chrome +
0x30 = B & W
0x1300BlurWarningint16u0 = None +
1 = Blur Warning
0x1301FocusWarningint16u0 = Good +
1 = Out of focus
0x1302ExposureWarningint16u0 = Good +
1 = Bad exposure
0x1304GEImageSizestring(GE models only)
0x1400DynamicRangeint16u1 = Standard +
3 = Wide
0x1401FilmModeint16u0x0 = F0/Standard (Provia) +
0x100 = F1/Studio Portrait +
0x110 = F1a/Studio Portrait Enhanced Saturation +
0x120 = F1b/Studio Portrait Smooth Skin Tone (Astia) +
0x130 = F1c/Studio Portrait Increased Sharpness +
0x200 = F2/Fujichrome (Velvia) +
0x300 = F3/Studio Portrait Ex +
0x400 = F4/Velvia +
0x500 = Pro Neg. Std +
0x501 = Pro Neg. Hi +
0x600 = Classic Chrome +
0x700 = Eterna +
0x800 = Classic Negative +
0x900 = Bleach Bypass +
0xa00 = Nostalgic Neg +
0xb00 = Reala ACE
0x1402DynamicRangeSettingint16u +
0x0 = Auto +
0x1 = Manual +
0x100 = Standard (100%) +
0x200 = Wide1 (230%) +
0x201 = Wide2 (400%) +
0x8000 = Film Simulation
+
0x1403DevelopmentDynamicRangeint16u 
0x1404MinFocalLengthrational64s 
0x1405MaxFocalLengthrational64s 
0x1406MaxApertureAtMinFocalrational64s 
0x1407MaxApertureAtMaxFocalrational64s 
0x140bAutoDynamicRangeint16u 
0x1422ImageStabilizationint16u[3][Value 0] + +
0 = None +
1 = Optical +
2 = Sensor-shift
  3 = OIS Lens +
258 = IBIS/OIS + DIS +
512 = Digital
+[Value 1] +
0 = Off +
1 = On (mode 1, continuous) +
2 = On (mode 2, shooting only)
0x1425SceneRecognitionint16u +
0x0 = Unrecognized +
0x100 = Portrait Image +
0x103 = Night Portrait +
0x105 = Backlit Portrait +
0x200 = Landscape Image +
0x300 = Night Scene +
0x400 = Macro
+
0x1431Ratingint32u 
0x1436ImageGenerationint16u0 = Original Image +
1 = Re-developed from RAW
0x1438ImageCountint16u(may reset to 0 when new firmware is installed)
0x1443DRangePriorityint16u0 = Auto +
1 = Fixed
0x1444DRangePriorityAutoint16u1 = Weak +
2 = Strong +
3 = Plus
0x1445DRangePriorityFixedint16u1 = Weak +
2 = Strong
0x1446FlickerReductionint32u 
0x1447FujiModelstring 
0x1448FujiModel2string 
0x144dRollAnglerational64s 
0x3803VideoRecordingModeint32u0x0 = Normal +
0x10 = F-log +
0x20 = HLG +
0x30 = F-log2
0x3804PeripheralLightingint16u0 = Off +
1 = On
0x3806VideoCompressionint16u1 = Log GOP +
2 = All Intra
0x3820FrameRateint16u 
0x3821FrameWidthint16u 
0x3822FrameHeightint16u 
0x3824FullHDHighSpeedRecint32u1 = Off +
2 = On
0x4005FaceElementSelectedint16u[4] 
0x4100FacesDetectedint16u 
0x4103FacePositionsint16u[n](left, top, right and bottom coordinates in full-sized image for each face +detected)
0x4200NumFaceElementsint16u 
0x4201FaceElementTypesint8u[n][Values 0-N] + +
1 = Face +
2 = Left Eye +
3 = Right Eye +
7 = Body +
8 = Head +
11 = Bike +
12 = Body of Car +
13 = Front of Car +
14 = Animal Body +
15 = Animal Head +
16 = Animal Face +
17 = Animal Left Eye
  18 = Animal Right Eye +
19 = Bird Body +
20 = Bird Head +
21 = Bird Left Eye +
22 = Bird Right Eye +
23 = Aircraft Body +
25 = Aircraft Cockpit +
26 = Train Front +
27 = Train Cockpit +
28 = Animal Head (28) +
29 = Animal Body (29)
+
0x4203FaceElementPositionsint16u[n](left, top, right and bottom coordinates in full-sized image for each face +element)
0x4282FaceRecInfo---> FujiFilm FaceRecInfo Tags
0x8000FileSourcestring 
0x8002OrderNumberint32u 
0x8003FrameNumberint16u 
0xb211Parallaxrational64s(only found in MPImage2 of .MPO images)
+ +

FujiFilm PrioritySettings Tags

+
+
+ + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0.1AF-SPriorityint16u[val & 0xf] +
1 = Release +
2 = Focus
0.2AF-CPriorityint16u[val >> 4 & 0xf] +
1 = Release +
2 = Focus
+ +

FujiFilm FocusSettings Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0.1FocusMode2int32u[val & 0xf] +
0 = AF-M +
1 = AF-S +
2 = AF-C
0.2PreAFint32u[val >> 4 & 0xf] +
0 = Off +
1 = On
0.3AFAreaModeint32u[val >> 8 & 0xf] +
0 = Single Point +
1 = Zone +
2 = Wide/Tracking
0.4AFAreaPointSizeint32u[val >> 12 & 0xf] +
0 = n/a
0.5AFAreaZoneSizeint32u[val >> 16 & 0xf] +
0 = n/a
+ +

FujiFilm AFCSettings Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0AF-CSettingint32u0x102 = Set 1 (multi-purpose) +
0x203 = Set 2 (ignore obstacles) +
0x122 = Set 3 (accelerating subject) +
0x010 = Set 4 (suddenly appearing subject) +
0x123 = Set 5 (erratic motion)
0.1AF-CTrackingSensitivityint32u[val & 0xf]
0.2AF-CSpeedTrackingSensitivityint32u[val >> 4 & 0xf]
0.3AF-CZoneAreaSwitchingint32u[val >> 8 & 0xf] +
0 = Front +
1 = Auto +
2 = Center
+ +

FujiFilm DriveSettings Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0.1DriveModeint32u[val & 0xff] +
0 = Single +
1 = Continuous Low +
2 = Continuous High
0.2DriveSpeedint32u[val >> 24 & 0xff] +
0 = n/a
+ +

FujiFilm FaceRecInfo Tags

+

Face recognition information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Face1Birthdayno 
Face1CategorynoBit 1 = Partner +
Bit 2 = Family +
Bit 3 = Friend
Face1Nameno 
Face2Birthdayno 
Face2CategorynoBit 1 = Partner +
Bit 2 = Family +
Bit 3 = Friend
Face2Nameno 
Face3Birthdayno 
Face3CategorynoBit 1 = Partner +
Bit 2 = Family +
Bit 3 = Friend
Face3Nameno 
Face4Birthdayno 
Face4CategorynoBit 1 = Partner +
Bit 2 = Family +
Bit 3 = Friend
Face4Nameno 
Face5Birthdayno 
Face5CategorynoBit 1 = Partner +
Bit 2 = Family +
Bit 3 = Friend
Face5Nameno 
Face6Birthdayno 
Face6CategorynoBit 1 = Partner +
Bit 2 = Family +
Bit 3 = Friend
Face6Nameno 
Face7Birthdayno 
Face7CategorynoBit 1 = Partner +
Bit 2 = Family +
Bit 3 = Friend
Face7Nameno 
Face8Birthdayno 
Face8CategorynoBit 1 = Partner +
Bit 2 = Family +
Bit 3 = Friend
Face8Nameno 
+ +

FujiFilm RAF Tags

+

FujiFilm RAF images contain meta information stored in a proprietary +FujiFilm RAF format, as well as EXIF information stored inside an embedded +JPEG preview image. The table below lists tags currently decoded from the +RAF-format information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0100RawImageFullSizeno(including borders)
0x0110RawImageCropTopLeftno(top margin first, then left margin)
0x0111RawImageCroppedSizeno(including borders)
0x0115RawImageAspectRationo 
0x0121RawImageSizeno 
0x0130FujiLayoutno 
0x0131XTransLayoutno 
0x2000WB_GRGBLevelsAutono 
0x2100WB_GRGBLevelsDaylightno 
0x2200WB_GRGBLevelsCloudyno 
0x2300WB_GRGBLevelsDaylightFluorno 
0x2301WB_GRGBLevelsDayWhiteFluorno 
0x2302WB_GRGBLevelsWhiteFluorescentno 
0x2310WB_GRGBLevelsWarmWhiteFluorno 
0x2311WB_GRGBLevelsLivingRoomWarmWhiteFluorno 
0x2400WB_GRGBLevelsTungstenno 
0x2ff0WB_GRGBLevelsno 
0x9200RelativeExposureno 
0x9650RawExposureBiasno 
0xc000RAFData---> FujiFilm RAFData Tags
+ +

FujiFilm RAFData Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0RawImageWidthno 
4RawImageWidth +
RawImageHeight
no
no
 
8RawImageWidth +
RawImageHeight
no
no
 
12RawImageHeightno 
+ +

FujiFilm IFD Tags

+

Tags found in the FujiIFD information of RAF images from some models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0xf000FujiIFD---> FujiFilm IFD Tags
0xf001RawImageFullWidthno 
0xf002RawImageFullHeightno 
0xf003BitsPerSampleno 
0xf007StripOffsetsno 
0xf008StripByteCountsno 
0xf00aBlackLevelno 
0xf00bGeometricDistortionParamsno 
0xf00cWB_GRBLevelsStandardno 
0xf00dWB_GRBLevelsAutono 
0xf00eWB_GRBLevelsno 
0xf00fChromaticAberrationParamsno 
0xf010VignettingParamsno 
+ +

FujiFilm FFMV Tags

+

Information found in the FFMV atom of MOV videos.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0MovieStreamNameno 
+ +

FujiFilm MOV Tags

+

This information is found in MOV videos from some FujiFilm cameras.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
24Modelno 
46ExposureTimeno 
50FNumberno 
58ExposureCompensationno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 19, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/GE.html b/ExifTool/html/TagNames/GE.html new file mode 100644 index 0000000..bef8ea7 --- /dev/null +++ b/ExifTool/html/TagNames/GE.html @@ -0,0 +1,41 @@ + + + + +GE Tags + + + +

GE Tags

+

This table lists tags found in the maker notes of some General Imaging +camera models.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0202Macroint16u0 = Off +
1 = On
0x0207GEModelstring 
0x0300GEMakestring 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Dec 15, 2010 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/GIF.html b/ExifTool/html/TagNames/GIF.html new file mode 100644 index 0000000..b207b35 --- /dev/null +++ b/ExifTool/html/TagNames/GIF.html @@ -0,0 +1,191 @@ + + + + +GIF Tags + + + +

GIF Tags

+

This table lists information extracted from GIF images. See +http://www.w3.org/Graphics/GIF/spec-gif89a.txt for the official GIF89a +specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Commentyes 
Durationno(duration of a single animation iteration)
Extensions---> GIF Extensions Tags
FrameCountno(number of animated images)
GIFVersionno 
ScreenDescriptor---> GIF Screen Tags
Textno(text displayed in image)
TransparentColorno 
+ +

GIF Extensions Tags

+

Tags extracted from GIF89a application extensions.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ICCRGBG1/012'ICC_Profileyes--> ICC_Profile Tags
'MIDICTRL/Jon'MIDIControl---> GIF MIDIControl Tags
'MIDISONG/Dm7'MIDISongno 
'NETSCAPE/2.0'Animation---> GIF Animation Tags
'XMP Data/XMP'XMPyes--> XMP Tags
+ +

GIF MIDIControl Tags

+

Information extracted from the MIDI control block extension.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0MIDIControlVersionno 
1SequenceNumberno 
2MelodicPolyphonyno 
3PercussivePolyphonyno 
4ChannelUsageno 
6DelayTimeno 
+ +

GIF Animation Tags

+

Information extracted from the "NETSCAPE2.0" animation extension.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
1AnimationIterationsno 
+ +

GIF Screen Tags

+

Information extracted from the GIF logical screen descriptor.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ImageWidthno 
2ImageHeightno 
4.1HasColorMapno[val >> 7 & 0x1] +
0 = No +
1 = Yes
4.2ColorResolutionDepthno[val >> 4 & 0x7]
4.3BitsPerPixelno[val & 0x7]
5BackgroundColorno 
6PixelAspectRationo 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Dec 8, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/GIMP.html b/ExifTool/html/TagNames/GIMP.html new file mode 100644 index 0000000..a822987 --- /dev/null +++ b/ExifTool/html/TagNames/GIMP.html @@ -0,0 +1,159 @@ + + + + +GIMP Tags + + + +

GIMP Tags

+

The GNU Image Manipulation Program (GIMP) writes these tags in its native +XCF (eXperimental Computing Facilty) images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'header'Header---> GIMP Header Tags
0x0011Compressionno0 = None +
1 = RLE Encoding +
2 = Zlib +
3 = Fractal
0x0013Resolution---> GIMP Resolution Tags
0x0014Tattoono 
0x0015Parasites---> GIMP Parasite Tags
0x0016Unitsno1 = Inches +
2 = mm +
3 = Points +
4 = Picas
+ +

GIMP Header Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
9XCFVersionno'file' = 0 +
'v001' = 1 +
'v002' = 2
14ImageWidthno 
18ImageHeightno 
22ColorModeno0 = RGB Color +
1 = Grayscale +
2 = Indexed Color
+ +

GIMP Resolution Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0XResolutionno 
1YResolutionno 
+ +

GIMP Parasite Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'exif-data'ExifData---> EXIF Tags
'gimp-comment'Commentno 
'gimp-image-metadata'XML---> XMP XML Tags
'gimp-metadata'XMP---> XMP Tags
'icc-profile'ICC_Profile---> ICC_Profile Tags
'icc-profile-name'ICCProfileNameno 
'iptc-data'IPTCData---> IPTC Tags
'jpeg-exif-data'JPEGExifData---> EXIF Tags
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 21, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/GPS.html b/ExifTool/html/TagNames/GPS.html new file mode 100644 index 0000000..9e048d3 --- /dev/null +++ b/ExifTool/html/TagNames/GPS.html @@ -0,0 +1,231 @@ + + + + +GPS Tags + + + +

GPS Tags

+

+These GPS tags are part of the EXIF standard, and are stored in a separate +IFD within the EXIF information.

+ +

ExifTool is very flexible about the input format when writing lat/long +coordinates, and will accept from 1 to 3 floating point numbers (for decimal +degrees, degrees and minutes, or degrees, minutes and seconds) separated by +just about anything, and will format them properly according to the EXIF +specification.

+ +

Some GPS tags have values which are fixed-length strings. For these, the +indicated string lengths include a null terminator which is added +automatically by ExifTool. Remember that the descriptive values are used +when writing (eg. 'Above Sea Level', not '0') unless the print conversion is +disabled (with '-n' on the command line or the PrintConv option in the API, +or by suffixing the tag name with a # character).

+ +

When adding GPS information to an image, it is important to set all of the +following tags: GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef, +and GPSAltitude and GPSAltitudeRef if the altitude is known. ExifTool will +write the required GPSVersionID tag automatically if new a GPS IFD is added +to an image. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000GPSVersionIDint8u[4]: 
0x0001GPSLatitudeRefstring[2](tags 0x0001-0x0006 used for camera location according to MWG 2.0. ExifTool +will also accept a number when writing GPSLatitudeRef, positive for north +latitudes or negative for south, or a string containing N, North, S or South) +
'N' = North +
'S' = South
0x0002GPSLatituderational64u[3] 
0x0003GPSLongitudeRefstring[2](ExifTool will also accept a number when writing this tag, positive for east +longitudes or negative for west, or a string containing E, East, W or West) +
'E' = East +
'W' = West
0x0004GPSLongituderational64u[3] 
0x0005GPSAltitudeRefint8u(ExifTool will also accept number when writing this tag, with negative +numbers indicating below sea level) +
0 = Above Sea Level +
1 = Below Sea Level
0x0006GPSAltituderational64u 
0x0007GPSTimeStamprational64u[3](UTC time of GPS fix. When writing, date is stripped off if present, and +time is adjusted to UTC if it includes a timezone)
0x0008GPSSatellitesstring 
0x0009GPSStatusstring[2]'A' = Measurement Active +
'V' = Measurement Void
0x000aGPSMeasureModestring[2]2 = 2-Dimensional Measurement +
3 = 3-Dimensional Measurement
0x000bGPSDOPrational64u 
0x000cGPSSpeedRefstring[2]'K' = km/h +
'M' = mph +
'N' = knots
0x000dGPSSpeedrational64u 
0x000eGPSTrackRefstring[2]'M' = Magnetic North +
'T' = True North
0x000fGPSTrackrational64u 
0x0010GPSImgDirectionRefstring[2]'M' = Magnetic North +
'T' = True North
0x0011GPSImgDirectionrational64u 
0x0012GPSMapDatumstring 
0x0013GPSDestLatitudeRefstring[2](tags 0x0013-0x001a used for subject location according to MWG 2.0) +
'N' = North +
'S' = South
0x0014GPSDestLatituderational64u[3] 
0x0015GPSDestLongitudeRefstring[2]'E' = East +
'W' = West
0x0016GPSDestLongituderational64u[3] 
0x0017GPSDestBearingRefstring[2]'M' = Magnetic North +
'T' = True North
0x0018GPSDestBearingrational64u 
0x0019GPSDestDistanceRefstring[2]'K' = Kilometers +
'M' = Miles +
'N' = Nautical Miles
0x001aGPSDestDistancerational64u 
0x001bGPSProcessingMethodundef(values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec.)
0x001cGPSAreaInformationundef 
0x001dGPSDateStampstring[11](when writing, time is stripped off if present, after adjusting date/time to +UTC if time includes a timezone. Format is YYYY:mm:dd)
0x001eGPSDifferentialint16u0 = No Correction +
1 = Differential Corrected
0x001fGPSHPositioningErrorrational64u 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 17, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/GeoTiff.html b/ExifTool/html/TagNames/GeoTiff.html new file mode 100644 index 0000000..0bc3b21 --- /dev/null +++ b/ExifTool/html/TagNames/GeoTiff.html @@ -0,0 +1,2303 @@ + + + + +GeoTiff Tags + + + +

GeoTiff Tags

+

+ExifTool extracts the following tags from GeoTIFF images. See +https://web.archive.org/web/20070820121549/http://www.remotesensing.org/geotiff/spec/geotiffhome.html +for the complete GeoTIFF specification. Also included in the table below +are ChartTIFF tags (see +https://web.archive.org/web/20020828193928/http://www.charttiff.com/whitepapers.shtml). +GeoTIFF tags are not writable individually, but they may be copied en mass +via the block tags GeoTiffDirectory, GeoTiffDoubleParams and +GeoTiffAsciiParams. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001GeoTiffVersionno 
0x0400GTModelTypeno1 = Projected +
2 = Geographic +
3 = Geocentric +
32767 = User Defined
0x0401GTRasterTypeno1 = Pixel Is Area +
2 = Pixel Is Point +
32767 = User Defined
0x0402GTCitationno 
0x0800GeographicTypeno +
4001 = Airy 1830 +
4002 = Airy Modified 1849 +
4003 = Australian National Spheroid +
4004 = Bessel 1841 +
4005 = Bessel Modified +
4006 = Bessel Namibia +
4007 = Clarke 1858 +
4008 = Clarke 1866 +
4009 = Clarke 1866 Michigan +
4010 = Clarke 1880 Benoit +
4011 = Clarke 1880 IGN +
4012 = Clarke 1880 RGS +
4013 = Clarke 1880 Arc +
4014 = Clarke 1880 SGA 1922 +
4015 = Everest 1830 1937 Adjustment +
4016 = Everest 1830 1967 Definition +
4017 = Everest 1830 1975 Definition +
4018 = Everest 1830 Modified +
4019 = GRS 1980 +
4020 = Helmert 1906 +
4021 = Indonesian National Spheroid +
4022 = International 1924 +
4023 = International 1967 +
4024 = Krassowsky 1940 +
4025 = NWL9D +
4026 = NWL10D +
4027 = Plessis 1817 +
4028 = Struve 1860 +
4029 = War Office +
4030 = WGS84 +
4031 = GEM10C +
4032 = OSU86F +
4033 = OSU91A +
4034 = Clarke 1880 +
4035 = Sphere +
4120 = Greek +
4121 = GGRS87 +
4123 = KKJ +
4124 = RT90 +
4133 = EST92 +
4201 = Adindan +
4202 = AGD66 +
4203 = AGD84 +
4204 = Ain el Abd +
4205 = Afgooye +
4206 = Agadez +
4207 = Lisbon +
4208 = Aratu +
4209 = Arc 1950 +
4210 = Arc 1960 +
4211 = Batavia +
4212 = Barbados +
4213 = Beduaram +
4214 = Beijing 1954 +
4215 = Belge 1950 +
4216 = Bermuda 1957 +
4217 = Bern 1898 +
4218 = Bogota +
4219 = Bukit Rimpah +
4220 = Camacupa +
4221 = Campo Inchauspe +
4222 = Cape +
4223 = Carthage +
4224 = Chua +
4225 = Corrego Alegre +
4226 = Cote d Ivoire +
4227 = Deir ez Zor +
4228 = Douala +
4229 = Egypt 1907 +
4230 = ED50 +
4231 = ED87 +
4232 = Fahud +
4233 = Gandajika 1970 +
4234 = Garoua +
4235 = Guyane Francaise +
4236 = Hu Tzu Shan +
4237 = HD72 +
4238 = ID74 +
4239 = Indian 1954 +
4240 = Indian 1975 +
4241 = Jamaica 1875 +
4242 = JAD69 +
4243 = Kalianpur +
4244 = Kandawala +
4245 = Kertau +
4246 = KOC +
4247 = La Canoa +
4248 = PSAD56 +
4249 = Lake +
4250 = Leigon +
4251 = Liberia 1964 +
4252 = Lome +
4253 = Luzon 1911 +
4254 = Hito XVIII 1963 +
4255 = Herat North +
4256 = Mahe 1971 +
4257 = Makassar +
4258 = EUREF89 +
4259 = Malongo 1987 +
4260 = Manoca +
4261 = Merchich +
4262 = Massawa +
4263 = Minna +
4264 = Mhast +
4265 = Monte Mario +
4266 = M poraloko +
4267 = NAD27 +
4268 = NAD Michigan +
4269 = NAD83 +
4270 = Nahrwan 1967 +
4271 = Naparima 1972 +
4272 = GD49 +
4273 = NGO 1948 +
4274 = 73 +
4275 = NTF +
4276 = NSWC 9Z 2 +
4277 = OSGB 1936 +
4278 = OSGB70 +
4279 = OS SN80 +
4280 = Padang +
4281 = Palestine 1923 +
4282 = Pointe Noire +
4283 = GDA94 +
4284 = Pulkovo 1942 +
4285 = Qatar +
4286 = Qatar 1948 +
4287 = Qornoq +
4288 = Loma Quintana +
4289 = Amersfoort +
4290 = RT38 +
4291 = SAD69 +
4292 = Sapper Hill 1943 +
4293 = Schwarzeck +
4294 = Segora +
4295 = Serindung +
4296 = Sudan +
4297 = Tananarive +
4298 = Timbalai 1948 +
4299 = TM65 +
4300 = TM75 +
4301 = Tokyo +
4302 = Trinidad 1903 +
4303 = TC 1948 +
4304 = Voirol 1875 +
4305 = Voirol Unifie +
4306 = Bern 1938 +
4307 = Nord Sahara 1959 +
4308 = Stockholm 1938 +
4309 = Yacare +
4310 = Yoff +
4311 = Zanderij +
4312 = MGI +
4313 = Belge 1972 +
4314 = DHDN +
4315 = Conakry 1905 +
4317 = Dealul Piscului 1970 +
4322 = WGS 72 +
4324 = WGS 72BE +
4326 = WGS 84 +
4801 = Bern 1898 Bern +
4802 = Bogota Bogota +
4803 = Lisbon Lisbon +
4804 = Makassar Jakarta +
4805 = MGI Ferro +
4806 = Monte Mario Rome +
4807 = NTF Paris +
4808 = Padang Jakarta +
4809 = Belge 1950 Brussels +
4810 = Tananarive Paris +
4811 = Voirol 1875 Paris +
4812 = Voirol Unifie Paris +
4813 = Batavia Jakarta +
4815 = Greek Athens +
4901 = ATF Paris +
4902 = NDG Paris +
32767 = User Defined
+
0x0801GeogCitationno 
0x0802GeogGeodeticDatumno +
6001 = Airy 1830 +
6002 = Airy Modified 1849 +
6003 = Australian National Spheroid +
6004 = Bessel 1841 +
6005 = Bessel Modified +
6006 = Bessel Namibia +
6007 = Clarke 1858 +
6008 = Clarke 1866 +
6009 = Clarke 1866 Michigan +
6010 = Clarke 1880 Benoit +
6011 = Clarke 1880 IGN +
6012 = Clarke 1880 RGS +
6013 = Clarke 1880 Arc +
6014 = Clarke 1880 SGA 1922 +
6015 = Everest 1830 1937 Adjustment +
6016 = Everest 1830 1967 Definition +
6017 = Everest 1830 1975 Definition +
6018 = Everest 1830 Modified +
6019 = GRS 1980 +
6020 = Helmert 1906 +
6021 = Indonesian National Spheroid +
6022 = International 1924 +
6023 = International 1967 +
6024 = Krassowsky 1960 +
6025 = NWL9D +
6026 = NWL10D +
6027 = Plessis 1817 +
6028 = Struve 1860 +
6029 = War Office +
6030 = WGS84 +
6031 = GEM10C +
6032 = OSU86F +
6033 = OSU91A +
6034 = Clarke 1880 +
6035 = Sphere +
6201 = Adindan +
6202 = Australian Geodetic Datum 1966 +
6203 = Australian Geodetic Datum 1984 +
6204 = Ain el Abd 1970 +
6205 = Afgooye +
6206 = Agadez +
6207 = Lisbon +
6208 = Aratu +
6209 = Arc 1950 +
6210 = Arc 1960 +
6211 = Batavia +
6212 = Barbados +
6213 = Beduaram +
6214 = Beijing 1954 +
6215 = Reseau National Belge 1950 +
6216 = Bermuda 1957 +
6217 = Bern 1898 +
6218 = Bogota +
6219 = Bukit Rimpah +
6220 = Camacupa +
6221 = Campo Inchauspe +
6222 = Cape +
6223 = Carthage +
6224 = Chua +
6225 = Corrego Alegre +
6226 = Cote d Ivoire +
6227 = Deir ez Zor +
6228 = Douala +
6229 = Egypt 1907 +
6230 = European Datum 1950 +
6231 = European Datum 1987 +
6232 = Fahud +
6233 = Gandajika 1970 +
6234 = Garoua +
6235 = Guyane Francaise +
6236 = Hu Tzu Shan +
6237 = Hungarian Datum 1972 +
6238 = Indonesian Datum 1974 +
6239 = Indian 1954 +
6240 = Indian 1975 +
6241 = Jamaica 1875 +
6242 = Jamaica 1969 +
6243 = Kalianpur +
6244 = Kandawala +
6245 = Kertau +
6246 = Kuwait Oil Company +
6247 = La Canoa +
6248 = Provisional S American Datum 1956 +
6249 = Lake +
6250 = Leigon +
6251 = Liberia 1964 +
6252 = Lome +
6253 = Luzon 1911 +
6254 = Hito XVIII 1963 +
6255 = Herat North +
6256 = Mahe 1971 +
6257 = Makassar +
6258 = European Reference System 1989 +
6259 = Malongo 1987 +
6260 = Manoca +
6261 = Merchich +
6262 = Massawa +
6263 = Minna +
6264 = Mhast +
6265 = Monte Mario +
6266 = M poraloko +
6267 = North American Datum 1927 +
6268 = NAD Michigan +
6269 = North American Datum 1983 +
6270 = Nahrwan 1967 +
6271 = Naparima 1972 +
6272 = New Zealand Geodetic Datum 1949 +
6273 = NGO 1948 +
6274 = Datum 73 +
6275 = Nouvelle Triangulation Francaise +
6276 = NSWC 9Z 2 +
6277 = OSGB 1936 +
6278 = OSGB 1970 SN +
6279 = OS SN 1980 +
6280 = Padang 1884 +
6281 = Palestine 1923 +
6282 = Pointe Noire +
6283 = Geocentric Datum of Australia 1994 +
6284 = Pulkovo 1942 +
6285 = Qatar +
6286 = Qatar 1948 +
6287 = Qornoq +
6288 = Loma Quintana +
6289 = Amersfoort +
6290 = RT38 +
6291 = South American Datum 1969 +
6292 = Sapper Hill 1943 +
6293 = Schwarzeck +
6294 = Segora +
6295 = Serindung +
6296 = Sudan +
6297 = Tananarive 1925 +
6298 = Timbalai 1948 +
6299 = TM65 +
6300 = TM75 +
6301 = Tokyo +
6302 = Trinidad 1903 +
6303 = Trucial Coast 1948 +
6304 = Voirol 1875 +
6305 = Voirol Unifie 1960 +
6306 = Bern 1938 +
6307 = Nord Sahara 1959 +
6308 = Stockholm 1938 +
6309 = Yacare +
6310 = Yoff +
6311 = Zanderij +
6312 = Militar Geographische Institut +
6313 = Reseau National Belge 1972 +
6314 = Deutsche Hauptdreiecksnetz +
6315 = Conakry 1905 +
6317 = Dealul Piscului 1970 +
6322 = WGS72 +
6324 = WGS72 Transit Broadcast Ephemeris +
6326 = WGS84 +
6901 = Ancienne Triangulation Francaise +
6902 = Nord de Guerre +
32767 = User Defined
+
0x0803GeogPrimeMeridianno + +
8901 = Greenwich +
8902 = Lisbon +
8903 = Paris +
8904 = Bogota +
8905 = Madrid +
8906 = Rome
  8907 = Bern +
8908 = Jakarta +
8909 = Ferro +
8910 = Brussels +
8911 = Stockholm +
32767 = User Defined
+
0x0804GeogLinearUnitsno--> GeoTiff Units Values
0x0805GeogLinearUnitSizeno 
0x0806GeogAngularUnitsno--> GeoTiff Units Values
0x0807GeogAngularUnitSizeno 
0x0808GeogEllipsoidno +
7001 = Airy 1830 +
7002 = Airy Modified 1849 +
7003 = Australian National Spheroid +
7004 = Bessel 1841 +
7005 = Bessel Modified +
7006 = Bessel Namibia +
7007 = Clarke 1858 +
7008 = Clarke 1866 +
7009 = Clarke 1866 Michigan +
7010 = Clarke 1880 Benoit +
7011 = Clarke 1880 IGN +
7012 = Clarke 1880 RGS +
7013 = Clarke 1880 Arc +
7014 = Clarke 1880 SGA 1922 +
7015 = Everest 1830 1937 Adjustment +
7016 = Everest 1830 1967 Definition +
7017 = Everest 1830 1975 Definition +
7018 = Everest 1830 Modified +
7019 = GRS 1980 +
7020 = Helmert 1906 +
7021 = Indonesian National Spheroid +
7022 = International 1924 +
7023 = International 1967 +
7024 = Krassowsky 1940 +
7025 = NWL 9D +
7026 = NWL 10D +
7027 = Plessis 1817 +
7028 = Struve 1860 +
7029 = War Office +
7030 = WGS 84 +
7031 = GEM 10C +
7032 = OSU86F +
7033 = OSU91A +
7034 = Clarke 1880 +
7035 = Sphere +
32767 = User Defined
+
0x0809GeogSemiMajorAxisno 
0x080aGeogSemiMinorAxisno 
0x080bGeogInvFlatteningno 
0x080cGeogAzimuthUnitsno--> GeoTiff Units Values
0x080dGeogPrimeMeridianLongno 
0x080eGeogToWGS84no 
0x0c00ProjectedCSTypeno +
2100 = GGRS87 Greek Grid +
2176 = ETRS89 Poland CS2000 zone 5 +
2177 = ETRS89 Poland CS2000 zone 7 +
2178 = ETRS89 Poland CS2000 zone 8 +
2180 = ETRS89 Poland CS92 +
2204 = NAD27 Tennessee +
2205 = NAD83 Kentucky North +
2391 = KKJ Finland zone 1 +
2392 = KKJ Finland zone 2 +
2393 = KKJ Finland zone 3 +
2394 = KKJ Finland zone 4 +
2400 = RT90 2 5 gon W +
2600 = Lietuvos Koordinoei Sistema 1994 +
3053 = Hjorsey 1955 Lambert +
3057 = ISN93 Lambert 1993 +
3300 = Estonian Coordinate System of 1992 +
3786 = Popular Visualisation CRS / Mercator +
3857 = WGS 84 / Pseudo-Mercator +
20137 = Adindan UTM zone 37N +
20138 = Adindan UTM zone 38N +
20248 = AGD66 AMG zone 48 +
20249 = AGD66 AMG zone 49 +
20250 = AGD66 AMG zone 50 +
20251 = AGD66 AMG zone 51 +
20252 = AGD66 AMG zone 52 +
20253 = AGD66 AMG zone 53 +
20254 = AGD66 AMG zone 54 +
20255 = AGD66 AMG zone 55 +
20256 = AGD66 AMG zone 56 +
20257 = AGD66 AMG zone 57 +
20258 = AGD66 AMG zone 58 +
20348 = AGD84 AMG zone 48 +
20349 = AGD84 AMG zone 49 +
20350 = AGD84 AMG zone 50 +
20351 = AGD84 AMG zone 51 +
20352 = AGD84 AMG zone 52 +
20353 = AGD84 AMG zone 53 +
20354 = AGD84 AMG zone 54 +
20355 = AGD84 AMG zone 55 +
20356 = AGD84 AMG zone 56 +
20357 = AGD84 AMG zone 57 +
20358 = AGD84 AMG zone 58 +
20437 = Ain el Abd UTM zone 37N +
20438 = Ain el Abd UTM zone 38N +
20439 = Ain el Abd UTM zone 39N +
20499 = Ain el Abd Bahrain Grid +
20538 = Afgooye UTM zone 38N +
20539 = Afgooye UTM zone 39N +
20700 = Lisbon Portuguese Grid +
20822 = Aratu UTM zone 22S +
20823 = Aratu UTM zone 23S +
20824 = Aratu UTM zone 24S +
20973 = Arc 1950 Lo13 +
20975 = Arc 1950 Lo15 +
20977 = Arc 1950 Lo17 +
20979 = Arc 1950 Lo19 +
20981 = Arc 1950 Lo21 +
20983 = Arc 1950 Lo23 +
20985 = Arc 1950 Lo25 +
20987 = Arc 1950 Lo27 +
20989 = Arc 1950 Lo29 +
20991 = Arc 1950 Lo31 +
20993 = Arc 1950 Lo33 +
20995 = Arc 1950 Lo35 +
21100 = Batavia NEIEZ +
21148 = Batavia UTM zone 48S +
21149 = Batavia UTM zone 49S +
21150 = Batavia UTM zone 50S +
21413 = Beijing Gauss zone 13 +
21414 = Beijing Gauss zone 14 +
21415 = Beijing Gauss zone 15 +
21416 = Beijing Gauss zone 16 +
21417 = Beijing Gauss zone 17 +
21418 = Beijing Gauss zone 18 +
21419 = Beijing Gauss zone 19 +
21420 = Beijing Gauss zone 20 +
21421 = Beijing Gauss zone 21 +
21422 = Beijing Gauss zone 22 +
21423 = Beijing Gauss zone 23 +
21473 = Beijing Gauss 13N +
21474 = Beijing Gauss 14N +
21475 = Beijing Gauss 15N +
21476 = Beijing Gauss 16N +
21477 = Beijing Gauss 17N +
21478 = Beijing Gauss 18N +
21479 = Beijing Gauss 19N +
21480 = Beijing Gauss 20N +
21481 = Beijing Gauss 21N +
21482 = Beijing Gauss 22N +
21483 = Beijing Gauss 23N +
21500 = Belge Lambert 50 +
21790 = Bern 1898 Swiss Old +
21817 = Bogota UTM zone 17N +
21818 = Bogota UTM zone 18N +
21891 = Bogota Colombia 3W +
21892 = Bogota Colombia Bogota +
21893 = Bogota Colombia 3E +
21894 = Bogota Colombia 6E +
22032 = Camacupa UTM 32S +
22033 = Camacupa UTM 33S +
22191 = C Inchauspe Argentina 1 +
22192 = C Inchauspe Argentina 2 +
22193 = C Inchauspe Argentina 3 +
22194 = C Inchauspe Argentina 4 +
22195 = C Inchauspe Argentina 5 +
22196 = C Inchauspe Argentina 6 +
22197 = C Inchauspe Argentina 7 +
22332 = Carthage UTM zone 32N +
22391 = Carthage Nord Tunisie +
22392 = Carthage Sud Tunisie +
22523 = Corrego Alegre UTM 23S +
22524 = Corrego Alegre UTM 24S +
22832 = Douala UTM zone 32N +
22992 = Egypt 1907 Red Belt +
22993 = Egypt 1907 Purple Belt +
22994 = Egypt 1907 Ext Purple +
23028 = ED50 UTM zone 28N +
23029 = ED50 UTM zone 29N +
23030 = ED50 UTM zone 30N +
23031 = ED50 UTM zone 31N +
23032 = ED50 UTM zone 32N +
23033 = ED50 UTM zone 33N +
23034 = ED50 UTM zone 34N +
23035 = ED50 UTM zone 35N +
23036 = ED50 UTM zone 36N +
23037 = ED50 UTM zone 37N +
23038 = ED50 UTM zone 38N +
23239 = Fahud UTM zone 39N +
23240 = Fahud UTM zone 40N +
23433 = Garoua UTM zone 33N +
23700 = HD72 EOV +
23846 = ID74 UTM zone 46N +
23847 = ID74 UTM zone 47N +
23848 = ID74 UTM zone 48N +
23849 = ID74 UTM zone 49N +
23850 = ID74 UTM zone 50N +
23851 = ID74 UTM zone 51N +
23852 = ID74 UTM zone 52N +
23853 = ID74 UTM zone 53N +
23886 = ID74 UTM zone 46S +
23887 = ID74 UTM zone 47S +
23888 = ID74 UTM zone 48S +
23889 = ID74 UTM zone 49S +
23890 = ID74 UTM zone 50S +
23891 = ID74 UTM zone 51S +
23892 = ID74 UTM zone 52S +
23893 = ID74 UTM zone 53S +
23894 = ID74 UTM zone 54S +
23947 = Indian 1954 UTM 47N +
23948 = Indian 1954 UTM 48N +
24047 = Indian 1975 UTM 47N +
24048 = Indian 1975 UTM 48N +
24100 = Jamaica 1875 Old Grid +
24200 = JAD69 Jamaica Grid +
24370 = Kalianpur India 0 +
24371 = Kalianpur India I +
24372 = Kalianpur India IIa +
24373 = Kalianpur India IIIa +
24374 = Kalianpur India IVa +
24382 = Kalianpur India IIb +
24383 = Kalianpur India IIIb +
24384 = Kalianpur India IVb +
24500 = Kertau Singapore Grid +
24547 = Kertau UTM zone 47N +
24548 = Kertau UTM zone 48N +
24720 = La Canoa UTM zone 20N +
24721 = La Canoa UTM zone 21N +
24818 = PSAD56 UTM zone 18N +
24819 = PSAD56 UTM zone 19N +
24820 = PSAD56 UTM zone 20N +
24821 = PSAD56 UTM zone 21N +
24877 = PSAD56 UTM zone 17S +
24878 = PSAD56 UTM zone 18S +
24879 = PSAD56 UTM zone 19S +
24880 = PSAD56 UTM zone 20S +
24891 = PSAD56 Peru west zone +
24892 = PSAD56 Peru central +
24893 = PSAD56 Peru east zone +
25000 = Leigon Ghana Grid +
25231 = Lome UTM zone 31N +
25391 = Luzon Philippines I +
25392 = Luzon Philippines II +
25393 = Luzon Philippines III +
25394 = Luzon Philippines IV +
25395 = Luzon Philippines V +
25700 = Makassar NEIEZ +
25932 = Malongo 1987 UTM 32S +
26191 = Merchich Nord Maroc +
26192 = Merchich Sud Maroc +
26193 = Merchich Sahara +
26237 = Massawa UTM zone 37N +
26331 = Minna UTM zone 31N +
26332 = Minna UTM zone 32N +
26391 = Minna Nigeria West +
26392 = Minna Nigeria Mid Belt +
26393 = Minna Nigeria East +
26432 = Mhast UTM zone 32S +
26591 = Monte Mario Italy 1 +
26592 = Monte Mario Italy 2 +
26632 = M poraloko UTM 32N +
26692 = M poraloko UTM 32S +
26703 = NAD27 UTM zone 3N +
26704 = NAD27 UTM zone 4N +
26705 = NAD27 UTM zone 5N +
26706 = NAD27 UTM zone 6N +
26707 = NAD27 UTM zone 7N +
26708 = NAD27 UTM zone 8N +
26709 = NAD27 UTM zone 9N +
26710 = NAD27 UTM zone 10N +
26711 = NAD27 UTM zone 11N +
26712 = NAD27 UTM zone 12N +
26713 = NAD27 UTM zone 13N +
26714 = NAD27 UTM zone 14N +
26715 = NAD27 UTM zone 15N +
26716 = NAD27 UTM zone 16N +
26717 = NAD27 UTM zone 17N +
26718 = NAD27 UTM zone 18N +
26719 = NAD27 UTM zone 19N +
26720 = NAD27 UTM zone 20N +
26721 = NAD27 UTM zone 21N +
26722 = NAD27 UTM zone 22N +
26729 = NAD27 Alabama East +
26730 = NAD27 Alabama West +
26731 = NAD27 Alaska zone 1 +
26732 = NAD27 Alaska zone 2 +
26733 = NAD27 Alaska zone 3 +
26734 = NAD27 Alaska zone 4 +
26735 = NAD27 Alaska zone 5 +
26736 = NAD27 Alaska zone 6 +
26737 = NAD27 Alaska zone 7 +
26738 = NAD27 Alaska zone 8 +
26739 = NAD27 Alaska zone 9 +
26740 = NAD27 Alaska zone 10 +
26741 = NAD27 California I +
26742 = NAD27 California II +
26743 = NAD27 California III +
26744 = NAD27 California IV +
26745 = NAD27 California V +
26746 = NAD27 California VI +
26747 = NAD27 California VII +
26748 = NAD27 Arizona East +
26749 = NAD27 Arizona Central +
26750 = NAD27 Arizona West +
26751 = NAD27 Arkansas North +
26752 = NAD27 Arkansas South +
26753 = NAD27 Colorado North +
26754 = NAD27 Colorado Central +
26755 = NAD27 Colorado South +
26756 = NAD27 Connecticut +
26757 = NAD27 Delaware +
26758 = NAD27 Florida East +
26759 = NAD27 Florida West +
26760 = NAD27 Florida North +
26761 = NAD27 Hawaii zone 1 +
26762 = NAD27 Hawaii zone 2 +
26763 = NAD27 Hawaii zone 3 +
26764 = NAD27 Hawaii zone 4 +
26765 = NAD27 Hawaii zone 5 +
26766 = NAD27 Georgia East +
26767 = NAD27 Georgia West +
26768 = NAD27 Idaho East +
26769 = NAD27 Idaho Central +
26770 = NAD27 Idaho West +
26771 = NAD27 Illinois East +
26772 = NAD27 Illinois West +
26773 = NAD27 Indiana East +
26774 = NAD27 Indiana West +
26775 = NAD27 Iowa North +
26776 = NAD27 Iowa South +
26777 = NAD27 Kansas North +
26778 = NAD27 Kansas South +
26779 = NAD27 Kentucky North +
26780 = NAD27 Kentucky South +
26781 = NAD27 Louisiana North +
26782 = NAD27 Louisiana South +
26783 = NAD27 Maine East +
26784 = NAD27 Maine West +
26785 = NAD27 Maryland +
26786 = NAD27 Massachusetts +
26787 = NAD27 Massachusetts Is +
26788 = NAD27 Michigan North +
26789 = NAD27 Michigan Central +
26790 = NAD27 Michigan South +
26791 = NAD27 Minnesota North +
26792 = NAD27 Minnesota Cent +
26793 = NAD27 Minnesota South +
26794 = NAD27 Mississippi East +
26795 = NAD27 Mississippi West +
26796 = NAD27 Missouri East +
26797 = NAD27 Missouri Central +
26798 = NAD27 Missouri West +
26801 = NAD Michigan Michigan East +
26802 = NAD Michigan Michigan Old Central +
26803 = NAD Michigan Michigan West +
26903 = NAD83 UTM zone 3N +
26904 = NAD83 UTM zone 4N +
26905 = NAD83 UTM zone 5N +
26906 = NAD83 UTM zone 6N +
26907 = NAD83 UTM zone 7N +
26908 = NAD83 UTM zone 8N +
26909 = NAD83 UTM zone 9N +
26910 = NAD83 UTM zone 10N +
26911 = NAD83 UTM zone 11N +
26912 = NAD83 UTM zone 12N +
26913 = NAD83 UTM zone 13N +
26914 = NAD83 UTM zone 14N +
26915 = NAD83 UTM zone 15N +
26916 = NAD83 UTM zone 16N +
26917 = NAD83 UTM zone 17N +
26918 = NAD83 UTM zone 18N +
26919 = NAD83 UTM zone 19N +
26920 = NAD83 UTM zone 20N +
26921 = NAD83 UTM zone 21N +
26922 = NAD83 UTM zone 22N +
26923 = NAD83 UTM zone 23N +
26929 = NAD83 Alabama East +
26930 = NAD83 Alabama West +
26931 = NAD83 Alaska zone 1 +
26932 = NAD83 Alaska zone 2 +
26933 = NAD83 Alaska zone 3 +
26934 = NAD83 Alaska zone 4 +
26935 = NAD83 Alaska zone 5 +
26936 = NAD83 Alaska zone 6 +
26937 = NAD83 Alaska zone 7 +
26938 = NAD83 Alaska zone 8 +
26939 = NAD83 Alaska zone 9 +
26940 = NAD83 Alaska zone 10 +
26941 = NAD83 California 1 +
26942 = NAD83 California 2 +
26943 = NAD83 California 3 +
26944 = NAD83 California 4 +
26945 = NAD83 California 5 +
26946 = NAD83 California 6 +
26948 = NAD83 Arizona East +
26949 = NAD83 Arizona Central +
26950 = NAD83 Arizona West +
26951 = NAD83 Arkansas North +
26952 = NAD83 Arkansas South +
26953 = NAD83 Colorado North +
26954 = NAD83 Colorado Central +
26955 = NAD83 Colorado South +
26956 = NAD83 Connecticut +
26957 = NAD83 Delaware +
26958 = NAD83 Florida East +
26959 = NAD83 Florida West +
26960 = NAD83 Florida North +
26961 = NAD83 Hawaii zone 1 +
26962 = NAD83 Hawaii zone 2 +
26963 = NAD83 Hawaii zone 3 +
26964 = NAD83 Hawaii zone 4 +
26965 = NAD83 Hawaii zone 5 +
26966 = NAD83 Georgia East +
26967 = NAD83 Georgia West +
26968 = NAD83 Idaho East +
26969 = NAD83 Idaho Central +
26970 = NAD83 Idaho West +
26971 = NAD83 Illinois East +
26972 = NAD83 Illinois West +
26973 = NAD83 Indiana East +
26974 = NAD83 Indiana West +
26975 = NAD83 Iowa North +
26976 = NAD83 Iowa South +
26977 = NAD83 Kansas North +
26978 = NAD83 Kansas South +
26979 = NAD83 Kentucky North +
26980 = NAD83 Kentucky South +
26981 = NAD83 Louisiana North +
26982 = NAD83 Louisiana South +
26983 = NAD83 Maine East +
26984 = NAD83 Maine West +
26985 = NAD83 Maryland +
26986 = NAD83 Massachusetts +
26987 = NAD83 Massachusetts Is +
26988 = NAD83 Michigan North +
26989 = NAD83 Michigan Central +
26990 = NAD83 Michigan South +
26991 = NAD83 Minnesota North +
26992 = NAD83 Minnesota Cent +
26993 = NAD83 Minnesota South +
26994 = NAD83 Mississippi East +
26995 = NAD83 Mississippi West +
26996 = NAD83 Missouri East +
26997 = NAD83 Missouri Central +
26998 = NAD83 Missouri West +
27038 = Nahrwan 1967 UTM 38N +
27039 = Nahrwan 1967 UTM 39N +
27040 = Nahrwan 1967 UTM 40N +
27120 = Naparima UTM 20N +
27200 = GD49 NZ Map Grid +
27291 = GD49 North Island Grid +
27292 = GD49 South Island Grid +
27429 = Datum 73 UTM zone 29N +
27500 = ATF Nord de Guerre +
27581 = NTF France I +
27582 = NTF France II +
27583 = NTF France III +
27591 = NTF Nord France +
27592 = NTF Centre France +
27593 = NTF Sud France +
27700 = British National Grid +
28232 = Point Noire UTM 32S +
28348 = GDA94 MGA zone 48 +
28349 = GDA94 MGA zone 49 +
28350 = GDA94 MGA zone 50 +
28351 = GDA94 MGA zone 51 +
28352 = GDA94 MGA zone 52 +
28353 = GDA94 MGA zone 53 +
28354 = GDA94 MGA zone 54 +
28355 = GDA94 MGA zone 55 +
28356 = GDA94 MGA zone 56 +
28357 = GDA94 MGA zone 57 +
28358 = GDA94 MGA zone 58 +
28404 = Pulkovo Gauss zone 4 +
28405 = Pulkovo Gauss zone 5 +
28406 = Pulkovo Gauss zone 6 +
28407 = Pulkovo Gauss zone 7 +
28408 = Pulkovo Gauss zone 8 +
28409 = Pulkovo Gauss zone 9 +
28410 = Pulkovo Gauss zone 10 +
28411 = Pulkovo Gauss zone 11 +
28412 = Pulkovo Gauss zone 12 +
28413 = Pulkovo Gauss zone 13 +
28414 = Pulkovo Gauss zone 14 +
28415 = Pulkovo Gauss zone 15 +
28416 = Pulkovo Gauss zone 16 +
28417 = Pulkovo Gauss zone 17 +
28418 = Pulkovo Gauss zone 18 +
28419 = Pulkovo Gauss zone 19 +
28420 = Pulkovo Gauss zone 20 +
28421 = Pulkovo Gauss zone 21 +
28422 = Pulkovo Gauss zone 22 +
28423 = Pulkovo Gauss zone 23 +
28424 = Pulkovo Gauss zone 24 +
28425 = Pulkovo Gauss zone 25 +
28426 = Pulkovo Gauss zone 26 +
28427 = Pulkovo Gauss zone 27 +
28428 = Pulkovo Gauss zone 28 +
28429 = Pulkovo Gauss zone 29 +
28430 = Pulkovo Gauss zone 30 +
28431 = Pulkovo Gauss zone 31 +
28432 = Pulkovo Gauss zone 32 +
28464 = Pulkovo Gauss 4N +
28465 = Pulkovo Gauss 5N +
28466 = Pulkovo Gauss 6N +
28467 = Pulkovo Gauss 7N +
28468 = Pulkovo Gauss 8N +
28469 = Pulkovo Gauss 9N +
28470 = Pulkovo Gauss 10N +
28471 = Pulkovo Gauss 11N +
28472 = Pulkovo Gauss 12N +
28473 = Pulkovo Gauss 13N +
28474 = Pulkovo Gauss 14N +
28475 = Pulkovo Gauss 15N +
28476 = Pulkovo Gauss 16N +
28477 = Pulkovo Gauss 17N +
28478 = Pulkovo Gauss 18N +
28479 = Pulkovo Gauss 19N +
28480 = Pulkovo Gauss 20N +
28481 = Pulkovo Gauss 21N +
28482 = Pulkovo Gauss 22N +
28483 = Pulkovo Gauss 23N +
28484 = Pulkovo Gauss 24N +
28485 = Pulkovo Gauss 25N +
28486 = Pulkovo Gauss 26N +
28487 = Pulkovo Gauss 27N +
28488 = Pulkovo Gauss 28N +
28489 = Pulkovo Gauss 29N +
28490 = Pulkovo Gauss 30N +
28491 = Pulkovo Gauss 31N +
28492 = Pulkovo Gauss 32N +
28600 = Qatar National Grid +
28991 = RD Netherlands Old +
28992 = RD Netherlands New +
29118 = SAD69 UTM zone 18N +
29119 = SAD69 UTM zone 19N +
29120 = SAD69 UTM zone 20N +
29121 = SAD69 UTM zone 21N +
29122 = SAD69 UTM zone 22N +
29177 = SAD69 UTM zone 17S +
29178 = SAD69 UTM zone 18S +
29179 = SAD69 UTM zone 19S +
29180 = SAD69 UTM zone 20S +
29181 = SAD69 UTM zone 21S +
29182 = SAD69 UTM zone 22S +
29183 = SAD69 UTM zone 23S +
29184 = SAD69 UTM zone 24S +
29185 = SAD69 UTM zone 25S +
29220 = Sapper Hill UTM 20S +
29221 = Sapper Hill UTM 21S +
29333 = Schwarzeck UTM 33S +
29635 = Sudan UTM zone 35N +
29636 = Sudan UTM zone 36N +
29700 = Tananarive Laborde +
29738 = Tananarive UTM 38S +
29739 = Tananarive UTM 39S +
29800 = Timbalai 1948 Borneo +
29849 = Timbalai 1948 UTM 49N +
29850 = Timbalai 1948 UTM 50N +
29900 = TM65 Irish Nat Grid +
30200 = Trinidad 1903 Trinidad +
30339 = TC 1948 UTM zone 39N +
30340 = TC 1948 UTM zone 40N +
30491 = Voirol N Algerie ancien +
30492 = Voirol S Algerie ancien +
30591 = Voirol Unifie N Algerie +
30592 = Voirol Unifie S Algerie +
30600 = Bern 1938 Swiss New +
30729 = Nord Sahara UTM 29N +
30730 = Nord Sahara UTM 30N +
30731 = Nord Sahara UTM 31N +
30732 = Nord Sahara UTM 32N +
31028 = Yoff UTM zone 28N +
31121 = Zanderij UTM zone 21N +
31291 = MGI Austria West +
31292 = MGI Austria Central +
31293 = MGI Austria East +
31300 = Belge Lambert 72 +
31491 = DHDN Germany zone 1 +
31492 = DHDN Germany zone 2 +
31493 = DHDN Germany zone 3 +
31494 = DHDN Germany zone 4 +
31495 = DHDN Germany zone 5 +
31700 = Dealul Piscului 1970 Stereo 70 +
32001 = NAD27 Montana North +
32002 = NAD27 Montana Central +
32003 = NAD27 Montana South +
32005 = NAD27 Nebraska North +
32006 = NAD27 Nebraska South +
32007 = NAD27 Nevada East +
32008 = NAD27 Nevada Central +
32009 = NAD27 Nevada West +
32010 = NAD27 New Hampshire +
32011 = NAD27 New Jersey +
32012 = NAD27 New Mexico East +
32013 = NAD27 New Mexico Cent +
32014 = NAD27 New Mexico West +
32015 = NAD27 New York East +
32016 = NAD27 New York Central +
32017 = NAD27 New York West +
32018 = NAD27 New York Long Is +
32019 = NAD27 North Carolina +
32020 = NAD27 North Dakota N +
32021 = NAD27 North Dakota S +
32022 = NAD27 Ohio North +
32023 = NAD27 Ohio South +
32024 = NAD27 Oklahoma North +
32025 = NAD27 Oklahoma South +
32026 = NAD27 Oregon North +
32027 = NAD27 Oregon South +
32028 = NAD27 Pennsylvania N +
32029 = NAD27 Pennsylvania S +
32030 = NAD27 Rhode Island +
32031 = NAD27 South Carolina N +
32033 = NAD27 South Carolina S +
32034 = NAD27 South Dakota N +
32035 = NAD27 South Dakota S +
32036 = NAD27 Tennessee +
32037 = NAD27 Texas North +
32038 = NAD27 Texas North Cen +
32039 = NAD27 Texas Central +
32040 = NAD27 Texas South Cen +
32041 = NAD27 Texas South +
32042 = NAD27 Utah North +
32043 = NAD27 Utah Central +
32044 = NAD27 Utah South +
32045 = NAD27 Vermont +
32046 = NAD27 Virginia North +
32047 = NAD27 Virginia South +
32048 = NAD27 Washington North +
32049 = NAD27 Washington South +
32050 = NAD27 West Virginia N +
32051 = NAD27 West Virginia S +
32052 = NAD27 Wisconsin North +
32053 = NAD27 Wisconsin Cen +
32054 = NAD27 Wisconsin South +
32055 = NAD27 Wyoming East +
32056 = NAD27 Wyoming E Cen +
32057 = NAD27 Wyoming W Cen +
32058 = NAD27 Wyoming West +
32059 = NAD27 Puerto Rico +
32060 = NAD27 St Croix +
32100 = NAD83 Montana +
32104 = NAD83 Nebraska +
32107 = NAD83 Nevada East +
32108 = NAD83 Nevada Central +
32109 = NAD83 Nevada West +
32110 = NAD83 New Hampshire +
32111 = NAD83 New Jersey +
32112 = NAD83 New Mexico East +
32113 = NAD83 New Mexico Cent +
32114 = NAD83 New Mexico West +
32115 = NAD83 New York East +
32116 = NAD83 New York Central +
32117 = NAD83 New York West +
32118 = NAD83 New York Long Is +
32119 = NAD83 North Carolina +
32120 = NAD83 North Dakota N +
32121 = NAD83 North Dakota S +
32122 = NAD83 Ohio North +
32123 = NAD83 Ohio South +
32124 = NAD83 Oklahoma North +
32125 = NAD83 Oklahoma South +
32126 = NAD83 Oregon North +
32127 = NAD83 Oregon South +
32128 = NAD83 Pennsylvania N +
32129 = NAD83 Pennsylvania S +
32130 = NAD83 Rhode Island +
32133 = NAD83 South Carolina +
32134 = NAD83 South Dakota N +
32135 = NAD83 South Dakota S +
32136 = NAD83 Tennessee +
32137 = NAD83 Texas North +
32138 = NAD83 Texas North Cen +
32139 = NAD83 Texas Central +
32140 = NAD83 Texas South Cen +
32141 = NAD83 Texas South +
32142 = NAD83 Utah North +
32143 = NAD83 Utah Central +
32144 = NAD83 Utah South +
32145 = NAD83 Vermont +
32146 = NAD83 Virginia North +
32147 = NAD83 Virginia South +
32148 = NAD83 Washington North +
32149 = NAD83 Washington South +
32150 = NAD83 West Virginia N +
32151 = NAD83 West Virginia S +
32152 = NAD83 Wisconsin North +
32153 = NAD83 Wisconsin Cen +
32154 = NAD83 Wisconsin South +
32155 = NAD83 Wyoming East +
32156 = NAD83 Wyoming E Cen +
32157 = NAD83 Wyoming W Cen +
32158 = NAD83 Wyoming West +
32161 = NAD83 Puerto Rico Virgin Is +
32201 = WGS72 UTM zone 1N +
32202 = WGS72 UTM zone 2N +
32203 = WGS72 UTM zone 3N +
32204 = WGS72 UTM zone 4N +
32205 = WGS72 UTM zone 5N +
32206 = WGS72 UTM zone 6N +
32207 = WGS72 UTM zone 7N +
32208 = WGS72 UTM zone 8N +
32209 = WGS72 UTM zone 9N +
32210 = WGS72 UTM zone 10N +
32211 = WGS72 UTM zone 11N +
32212 = WGS72 UTM zone 12N +
32213 = WGS72 UTM zone 13N +
32214 = WGS72 UTM zone 14N +
32215 = WGS72 UTM zone 15N +
32216 = WGS72 UTM zone 16N +
32217 = WGS72 UTM zone 17N +
32218 = WGS72 UTM zone 18N +
32219 = WGS72 UTM zone 19N +
32220 = WGS72 UTM zone 20N +
32221 = WGS72 UTM zone 21N +
32222 = WGS72 UTM zone 22N +
32223 = WGS72 UTM zone 23N +
32224 = WGS72 UTM zone 24N +
32225 = WGS72 UTM zone 25N +
32226 = WGS72 UTM zone 26N +
32227 = WGS72 UTM zone 27N +
32228 = WGS72 UTM zone 28N +
32229 = WGS72 UTM zone 29N +
32230 = WGS72 UTM zone 30N +
32231 = WGS72 UTM zone 31N +
32232 = WGS72 UTM zone 32N +
32233 = WGS72 UTM zone 33N +
32234 = WGS72 UTM zone 34N +
32235 = WGS72 UTM zone 35N +
32236 = WGS72 UTM zone 36N +
32237 = WGS72 UTM zone 37N +
32238 = WGS72 UTM zone 38N +
32239 = WGS72 UTM zone 39N +
32240 = WGS72 UTM zone 40N +
32241 = WGS72 UTM zone 41N +
32242 = WGS72 UTM zone 42N +
32243 = WGS72 UTM zone 43N +
32244 = WGS72 UTM zone 44N +
32245 = WGS72 UTM zone 45N +
32246 = WGS72 UTM zone 46N +
32247 = WGS72 UTM zone 47N +
32248 = WGS72 UTM zone 48N +
32249 = WGS72 UTM zone 49N +
32250 = WGS72 UTM zone 50N +
32251 = WGS72 UTM zone 51N +
32252 = WGS72 UTM zone 52N +
32253 = WGS72 UTM zone 53N +
32254 = WGS72 UTM zone 54N +
32255 = WGS72 UTM zone 55N +
32256 = WGS72 UTM zone 56N +
32257 = WGS72 UTM zone 57N +
32258 = WGS72 UTM zone 58N +
32259 = WGS72 UTM zone 59N +
32260 = WGS72 UTM zone 60N +
32301 = WGS72 UTM zone 1S +
32302 = WGS72 UTM zone 2S +
32303 = WGS72 UTM zone 3S +
32304 = WGS72 UTM zone 4S +
32305 = WGS72 UTM zone 5S +
32306 = WGS72 UTM zone 6S +
32307 = WGS72 UTM zone 7S +
32308 = WGS72 UTM zone 8S +
32309 = WGS72 UTM zone 9S +
32310 = WGS72 UTM zone 10S +
32311 = WGS72 UTM zone 11S +
32312 = WGS72 UTM zone 12S +
32313 = WGS72 UTM zone 13S +
32314 = WGS72 UTM zone 14S +
32315 = WGS72 UTM zone 15S +
32316 = WGS72 UTM zone 16S +
32317 = WGS72 UTM zone 17S +
32318 = WGS72 UTM zone 18S +
32319 = WGS72 UTM zone 19S +
32320 = WGS72 UTM zone 20S +
32321 = WGS72 UTM zone 21S +
32322 = WGS72 UTM zone 22S +
32323 = WGS72 UTM zone 23S +
32324 = WGS72 UTM zone 24S +
32325 = WGS72 UTM zone 25S +
32326 = WGS72 UTM zone 26S +
32327 = WGS72 UTM zone 27S +
32328 = WGS72 UTM zone 28S +
32329 = WGS72 UTM zone 29S +
32330 = WGS72 UTM zone 30S +
32331 = WGS72 UTM zone 31S +
32332 = WGS72 UTM zone 32S +
32333 = WGS72 UTM zone 33S +
32334 = WGS72 UTM zone 34S +
32335 = WGS72 UTM zone 35S +
32336 = WGS72 UTM zone 36S +
32337 = WGS72 UTM zone 37S +
32338 = WGS72 UTM zone 38S +
32339 = WGS72 UTM zone 39S +
32340 = WGS72 UTM zone 40S +
32341 = WGS72 UTM zone 41S +
32342 = WGS72 UTM zone 42S +
32343 = WGS72 UTM zone 43S +
32344 = WGS72 UTM zone 44S +
32345 = WGS72 UTM zone 45S +
32346 = WGS72 UTM zone 46S +
32347 = WGS72 UTM zone 47S +
32348 = WGS72 UTM zone 48S +
32349 = WGS72 UTM zone 49S +
32350 = WGS72 UTM zone 50S +
32351 = WGS72 UTM zone 51S +
32352 = WGS72 UTM zone 52S +
32353 = WGS72 UTM zone 53S +
32354 = WGS72 UTM zone 54S +
32355 = WGS72 UTM zone 55S +
32356 = WGS72 UTM zone 56S +
32357 = WGS72 UTM zone 57S +
32358 = WGS72 UTM zone 58S +
32359 = WGS72 UTM zone 59S +
32360 = WGS72 UTM zone 60S +
32401 = WGS72BE UTM zone 1N +
32402 = WGS72BE UTM zone 2N +
32403 = WGS72BE UTM zone 3N +
32404 = WGS72BE UTM zone 4N +
32405 = WGS72BE UTM zone 5N +
32406 = WGS72BE UTM zone 6N +
32407 = WGS72BE UTM zone 7N +
32408 = WGS72BE UTM zone 8N +
32409 = WGS72BE UTM zone 9N +
32410 = WGS72BE UTM zone 10N +
32411 = WGS72BE UTM zone 11N +
32412 = WGS72BE UTM zone 12N +
32413 = WGS72BE UTM zone 13N +
32414 = WGS72BE UTM zone 14N +
32415 = WGS72BE UTM zone 15N +
32416 = WGS72BE UTM zone 16N +
32417 = WGS72BE UTM zone 17N +
32418 = WGS72BE UTM zone 18N +
32419 = WGS72BE UTM zone 19N +
32420 = WGS72BE UTM zone 20N +
32421 = WGS72BE UTM zone 21N +
32422 = WGS72BE UTM zone 22N +
32423 = WGS72BE UTM zone 23N +
32424 = WGS72BE UTM zone 24N +
32425 = WGS72BE UTM zone 25N +
32426 = WGS72BE UTM zone 26N +
32427 = WGS72BE UTM zone 27N +
32428 = WGS72BE UTM zone 28N +
32429 = WGS72BE UTM zone 29N +
32430 = WGS72BE UTM zone 30N +
32431 = WGS72BE UTM zone 31N +
32432 = WGS72BE UTM zone 32N +
32433 = WGS72BE UTM zone 33N +
32434 = WGS72BE UTM zone 34N +
32435 = WGS72BE UTM zone 35N +
32436 = WGS72BE UTM zone 36N +
32437 = WGS72BE UTM zone 37N +
32438 = WGS72BE UTM zone 38N +
32439 = WGS72BE UTM zone 39N +
32440 = WGS72BE UTM zone 40N +
32441 = WGS72BE UTM zone 41N +
32442 = WGS72BE UTM zone 42N +
32443 = WGS72BE UTM zone 43N +
32444 = WGS72BE UTM zone 44N +
32445 = WGS72BE UTM zone 45N +
32446 = WGS72BE UTM zone 46N +
32447 = WGS72BE UTM zone 47N +
32448 = WGS72BE UTM zone 48N +
32449 = WGS72BE UTM zone 49N +
32450 = WGS72BE UTM zone 50N +
32451 = WGS72BE UTM zone 51N +
32452 = WGS72BE UTM zone 52N +
32453 = WGS72BE UTM zone 53N +
32454 = WGS72BE UTM zone 54N +
32455 = WGS72BE UTM zone 55N +
32456 = WGS72BE UTM zone 56N +
32457 = WGS72BE UTM zone 57N +
32458 = WGS72BE UTM zone 58N +
32459 = WGS72BE UTM zone 59N +
32460 = WGS72BE UTM zone 60N +
32501 = WGS72BE UTM zone 1S +
32502 = WGS72BE UTM zone 2S +
32503 = WGS72BE UTM zone 3S +
32504 = WGS72BE UTM zone 4S +
32505 = WGS72BE UTM zone 5S +
32506 = WGS72BE UTM zone 6S +
32507 = WGS72BE UTM zone 7S +
32508 = WGS72BE UTM zone 8S +
32509 = WGS72BE UTM zone 9S +
32510 = WGS72BE UTM zone 10S +
32511 = WGS72BE UTM zone 11S +
32512 = WGS72BE UTM zone 12S +
32513 = WGS72BE UTM zone 13S +
32514 = WGS72BE UTM zone 14S +
32515 = WGS72BE UTM zone 15S +
32516 = WGS72BE UTM zone 16S +
32517 = WGS72BE UTM zone 17S +
32518 = WGS72BE UTM zone 18S +
32519 = WGS72BE UTM zone 19S +
32520 = WGS72BE UTM zone 20S +
32521 = WGS72BE UTM zone 21S +
32522 = WGS72BE UTM zone 22S +
32523 = WGS72BE UTM zone 23S +
32524 = WGS72BE UTM zone 24S +
32525 = WGS72BE UTM zone 25S +
32526 = WGS72BE UTM zone 26S +
32527 = WGS72BE UTM zone 27S +
32528 = WGS72BE UTM zone 28S +
32529 = WGS72BE UTM zone 29S +
32530 = WGS72BE UTM zone 30S +
32531 = WGS72BE UTM zone 31S +
32532 = WGS72BE UTM zone 32S +
32533 = WGS72BE UTM zone 33S +
32534 = WGS72BE UTM zone 34S +
32535 = WGS72BE UTM zone 35S +
32536 = WGS72BE UTM zone 36S +
32537 = WGS72BE UTM zone 37S +
32538 = WGS72BE UTM zone 38S +
32539 = WGS72BE UTM zone 39S +
32540 = WGS72BE UTM zone 40S +
32541 = WGS72BE UTM zone 41S +
32542 = WGS72BE UTM zone 42S +
32543 = WGS72BE UTM zone 43S +
32544 = WGS72BE UTM zone 44S +
32545 = WGS72BE UTM zone 45S +
32546 = WGS72BE UTM zone 46S +
32547 = WGS72BE UTM zone 47S +
32548 = WGS72BE UTM zone 48S +
32549 = WGS72BE UTM zone 49S +
32550 = WGS72BE UTM zone 50S +
32551 = WGS72BE UTM zone 51S +
32552 = WGS72BE UTM zone 52S +
32553 = WGS72BE UTM zone 53S +
32554 = WGS72BE UTM zone 54S +
32555 = WGS72BE UTM zone 55S +
32556 = WGS72BE UTM zone 56S +
32557 = WGS72BE UTM zone 57S +
32558 = WGS72BE UTM zone 58S +
32559 = WGS72BE UTM zone 59S +
32560 = WGS72BE UTM zone 60S +
32601 = WGS84 UTM zone 1N +
32602 = WGS84 UTM zone 2N +
32603 = WGS84 UTM zone 3N +
32604 = WGS84 UTM zone 4N +
32605 = WGS84 UTM zone 5N +
32606 = WGS84 UTM zone 6N +
32607 = WGS84 UTM zone 7N +
32608 = WGS84 UTM zone 8N +
32609 = WGS84 UTM zone 9N +
32610 = WGS84 UTM zone 10N +
32611 = WGS84 UTM zone 11N +
32612 = WGS84 UTM zone 12N +
32613 = WGS84 UTM zone 13N +
32614 = WGS84 UTM zone 14N +
32615 = WGS84 UTM zone 15N +
32616 = WGS84 UTM zone 16N +
32617 = WGS84 UTM zone 17N +
32618 = WGS84 UTM zone 18N +
32619 = WGS84 UTM zone 19N +
32620 = WGS84 UTM zone 20N +
32621 = WGS84 UTM zone 21N +
32622 = WGS84 UTM zone 22N +
32623 = WGS84 UTM zone 23N +
32624 = WGS84 UTM zone 24N +
32625 = WGS84 UTM zone 25N +
32626 = WGS84 UTM zone 26N +
32627 = WGS84 UTM zone 27N +
32628 = WGS84 UTM zone 28N +
32629 = WGS84 UTM zone 29N +
32630 = WGS84 UTM zone 30N +
32631 = WGS84 UTM zone 31N +
32632 = WGS84 UTM zone 32N +
32633 = WGS84 UTM zone 33N +
32634 = WGS84 UTM zone 34N +
32635 = WGS84 UTM zone 35N +
32636 = WGS84 UTM zone 36N +
32637 = WGS84 UTM zone 37N +
32638 = WGS84 UTM zone 38N +
32639 = WGS84 UTM zone 39N +
32640 = WGS84 UTM zone 40N +
32641 = WGS84 UTM zone 41N +
32642 = WGS84 UTM zone 42N +
32643 = WGS84 UTM zone 43N +
32644 = WGS84 UTM zone 44N +
32645 = WGS84 UTM zone 45N +
32646 = WGS84 UTM zone 46N +
32647 = WGS84 UTM zone 47N +
32648 = WGS84 UTM zone 48N +
32649 = WGS84 UTM zone 49N +
32650 = WGS84 UTM zone 50N +
32651 = WGS84 UTM zone 51N +
32652 = WGS84 UTM zone 52N +
32653 = WGS84 UTM zone 53N +
32654 = WGS84 UTM zone 54N +
32655 = WGS84 UTM zone 55N +
32656 = WGS84 UTM zone 56N +
32657 = WGS84 UTM zone 57N +
32658 = WGS84 UTM zone 58N +
32659 = WGS84 UTM zone 59N +
32660 = WGS84 UTM zone 60N +
32701 = WGS84 UTM zone 1S +
32702 = WGS84 UTM zone 2S +
32703 = WGS84 UTM zone 3S +
32704 = WGS84 UTM zone 4S +
32705 = WGS84 UTM zone 5S +
32706 = WGS84 UTM zone 6S +
32707 = WGS84 UTM zone 7S +
32708 = WGS84 UTM zone 8S +
32709 = WGS84 UTM zone 9S +
32710 = WGS84 UTM zone 10S +
32711 = WGS84 UTM zone 11S +
32712 = WGS84 UTM zone 12S +
32713 = WGS84 UTM zone 13S +
32714 = WGS84 UTM zone 14S +
32715 = WGS84 UTM zone 15S +
32716 = WGS84 UTM zone 16S +
32717 = WGS84 UTM zone 17S +
32718 = WGS84 UTM zone 18S +
32719 = WGS84 UTM zone 19S +
32720 = WGS84 UTM zone 20S +
32721 = WGS84 UTM zone 21S +
32722 = WGS84 UTM zone 22S +
32723 = WGS84 UTM zone 23S +
32724 = WGS84 UTM zone 24S +
32725 = WGS84 UTM zone 25S +
32726 = WGS84 UTM zone 26S +
32727 = WGS84 UTM zone 27S +
32728 = WGS84 UTM zone 28S +
32729 = WGS84 UTM zone 29S +
32730 = WGS84 UTM zone 30S +
32731 = WGS84 UTM zone 31S +
32732 = WGS84 UTM zone 32S +
32733 = WGS84 UTM zone 33S +
32734 = WGS84 UTM zone 34S +
32735 = WGS84 UTM zone 35S +
32736 = WGS84 UTM zone 36S +
32737 = WGS84 UTM zone 37S +
32738 = WGS84 UTM zone 38S +
32739 = WGS84 UTM zone 39S +
32740 = WGS84 UTM zone 40S +
32741 = WGS84 UTM zone 41S +
32742 = WGS84 UTM zone 42S +
32743 = WGS84 UTM zone 43S +
32744 = WGS84 UTM zone 44S +
32745 = WGS84 UTM zone 45S +
32746 = WGS84 UTM zone 46S +
32747 = WGS84 UTM zone 47S +
32748 = WGS84 UTM zone 48S +
32749 = WGS84 UTM zone 49S +
32750 = WGS84 UTM zone 50S +
32751 = WGS84 UTM zone 51S +
32752 = WGS84 UTM zone 52S +
32753 = WGS84 UTM zone 53S +
32754 = WGS84 UTM zone 54S +
32755 = WGS84 UTM zone 55S +
32756 = WGS84 UTM zone 56S +
32757 = WGS84 UTM zone 57S +
32758 = WGS84 UTM zone 58S +
32759 = WGS84 UTM zone 59S +
32760 = WGS84 UTM zone 60S +
32767 = User Defined
+
0x0c01PCSCitationno 
0x0c02Projectionno +
10101 = Alabama CS27 East +
10102 = Alabama CS27 West +
10131 = Alabama CS83 East +
10132 = Alabama CS83 West +
10201 = Arizona Coordinate System east +
10202 = Arizona Coordinate System Central +
10203 = Arizona Coordinate System west +
10231 = Arizona CS83 east +
10232 = Arizona CS83 Central +
10233 = Arizona CS83 west +
10301 = Arkansas CS27 North +
10302 = Arkansas CS27 South +
10331 = Arkansas CS83 North +
10332 = Arkansas CS83 South +
10401 = California CS27 I +
10402 = California CS27 II +
10403 = California CS27 III +
10404 = California CS27 IV +
10405 = California CS27 V +
10406 = California CS27 VI +
10407 = California CS27 VII +
10431 = California CS83 1 +
10432 = California CS83 2 +
10433 = California CS83 3 +
10434 = California CS83 4 +
10435 = California CS83 5 +
10436 = California CS83 6 +
10501 = Colorado CS27 North +
10502 = Colorado CS27 Central +
10503 = Colorado CS27 South +
10531 = Colorado CS83 North +
10532 = Colorado CS83 Central +
10533 = Colorado CS83 South +
10600 = Connecticut CS27 +
10630 = Connecticut CS83 +
10700 = Delaware CS27 +
10730 = Delaware CS83 +
10901 = Florida CS27 East +
10902 = Florida CS27 West +
10903 = Florida CS27 North +
10931 = Florida CS83 East +
10932 = Florida CS83 West +
10933 = Florida CS83 North +
11001 = Georgia CS27 East +
11002 = Georgia CS27 West +
11031 = Georgia CS83 East +
11032 = Georgia CS83 West +
11101 = Idaho CS27 East +
11102 = Idaho CS27 Central +
11103 = Idaho CS27 West +
11131 = Idaho CS83 East +
11132 = Idaho CS83 Central +
11133 = Idaho CS83 West +
11201 = Illinois CS27 East +
11202 = Illinois CS27 West +
11231 = Illinois CS83 East +
11232 = Illinois CS83 West +
11301 = Indiana CS27 East +
11302 = Indiana CS27 West +
11331 = Indiana CS83 East +
11332 = Indiana CS83 West +
11401 = Iowa CS27 North +
11402 = Iowa CS27 South +
11431 = Iowa CS83 North +
11432 = Iowa CS83 South +
11501 = Kansas CS27 North +
11502 = Kansas CS27 South +
11531 = Kansas CS83 North +
11532 = Kansas CS83 South +
11601 = Kentucky CS27 North +
11602 = Kentucky CS27 South +
11631 = Kentucky CS83 North +
11632 = Kentucky CS83 South +
11701 = Louisiana CS27 North +
11702 = Louisiana CS27 South +
11731 = Louisiana CS83 North +
11732 = Louisiana CS83 South +
11801 = Maine CS27 East +
11802 = Maine CS27 West +
11831 = Maine CS83 East +
11832 = Maine CS83 West +
11900 = Maryland CS27 +
11930 = Maryland CS83 +
12001 = Massachusetts CS27 Mainland +
12002 = Massachusetts CS27 Island +
12031 = Massachusetts CS83 Mainland +
12032 = Massachusetts CS83 Island +
12101 = Michigan State Plane East +
12102 = Michigan State Plane Old Central +
12103 = Michigan State Plane West +
12111 = Michigan CS27 North +
12112 = Michigan CS27 Central +
12113 = Michigan CS27 South +
12141 = Michigan CS83 North +
12142 = Michigan CS83 Central +
12143 = Michigan CS83 South +
12201 = Minnesota CS27 North +
12202 = Minnesota CS27 Central +
12203 = Minnesota CS27 South +
12231 = Minnesota CS83 North +
12232 = Minnesota CS83 Central +
12233 = Minnesota CS83 South +
12301 = Mississippi CS27 East +
12302 = Mississippi CS27 West +
12331 = Mississippi CS83 East +
12332 = Mississippi CS83 West +
12401 = Missouri CS27 East +
12402 = Missouri CS27 Central +
12403 = Missouri CS27 West +
12431 = Missouri CS83 East +
12432 = Missouri CS83 Central +
12433 = Missouri CS83 West +
12501 = Montana CS27 North +
12502 = Montana CS27 Central +
12503 = Montana CS27 South +
12530 = Montana CS83 +
12601 = Nebraska CS27 North +
12602 = Nebraska CS27 South +
12630 = Nebraska CS83 +
12701 = Nevada CS27 East +
12702 = Nevada CS27 Central +
12703 = Nevada CS27 West +
12731 = Nevada CS83 East +
12732 = Nevada CS83 Central +
12733 = Nevada CS83 West +
12800 = New Hampshire CS27 +
12830 = New Hampshire CS83 +
12900 = New Jersey CS27 +
12930 = New Jersey CS83 +
13001 = New Mexico CS27 East +
13002 = New Mexico CS27 Central +
13003 = New Mexico CS27 West +
13031 = New Mexico CS83 East +
13032 = New Mexico CS83 Central +
13033 = New Mexico CS83 West +
13101 = New York CS27 East +
13102 = New York CS27 Central +
13103 = New York CS27 West +
13104 = New York CS27 Long Island +
13131 = New York CS83 East +
13132 = New York CS83 Central +
13133 = New York CS83 West +
13134 = New York CS83 Long Island +
13200 = North Carolina CS27 +
13230 = North Carolina CS83 +
13301 = North Dakota CS27 North +
13302 = North Dakota CS27 South +
13331 = North Dakota CS83 North +
13332 = North Dakota CS83 South +
13401 = Ohio CS27 North +
13402 = Ohio CS27 South +
13431 = Ohio CS83 North +
13432 = Ohio CS83 South +
13501 = Oklahoma CS27 North +
13502 = Oklahoma CS27 South +
13531 = Oklahoma CS83 North +
13532 = Oklahoma CS83 South +
13601 = Oregon CS27 North +
13602 = Oregon CS27 South +
13631 = Oregon CS83 North +
13632 = Oregon CS83 South +
13701 = Pennsylvania CS27 North +
13702 = Pennsylvania CS27 South +
13731 = Pennsylvania CS83 North +
13732 = Pennsylvania CS83 South +
13800 = Rhode Island CS27 +
13830 = Rhode Island CS83 +
13901 = South Carolina CS27 North +
13902 = South Carolina CS27 South +
13930 = South Carolina CS83 +
14001 = South Dakota CS27 North +
14002 = South Dakota CS27 South +
14031 = South Dakota CS83 North +
14032 = South Dakota CS83 South +
14100 = Tennessee CS27 +
14130 = Tennessee CS83 +
14201 = Texas CS27 North +
14202 = Texas CS27 North Central +
14203 = Texas CS27 Central +
14204 = Texas CS27 South Central +
14205 = Texas CS27 South +
14231 = Texas CS83 North +
14232 = Texas CS83 North Central +
14233 = Texas CS83 Central +
14234 = Texas CS83 South Central +
14235 = Texas CS83 South +
14301 = Utah CS27 North +
14302 = Utah CS27 Central +
14303 = Utah CS27 South +
14331 = Utah CS83 North +
14332 = Utah CS83 Central +
14333 = Utah CS83 South +
14400 = Vermont CS27 +
14430 = Vermont CS83 +
14501 = Virginia CS27 North +
14502 = Virginia CS27 South +
14531 = Virginia CS83 North +
14532 = Virginia CS83 South +
14601 = Washington CS27 North +
14602 = Washington CS27 South +
14631 = Washington CS83 North +
14632 = Washington CS83 South +
14701 = West Virginia CS27 North +
14702 = West Virginia CS27 South +
14731 = West Virginia CS83 North +
14732 = West Virginia CS83 South +
14801 = Wisconsin CS27 North +
14802 = Wisconsin CS27 Central +
14803 = Wisconsin CS27 South +
14831 = Wisconsin CS83 North +
14832 = Wisconsin CS83 Central +
14833 = Wisconsin CS83 South +
14901 = Wyoming CS27 East +
14902 = Wyoming CS27 East Central +
14903 = Wyoming CS27 West Central +
14904 = Wyoming CS27 West +
14931 = Wyoming CS83 East +
14932 = Wyoming CS83 East Central +
14933 = Wyoming CS83 West Central +
14934 = Wyoming CS83 West +
15001 = Alaska CS27 1 +
15002 = Alaska CS27 2 +
15003 = Alaska CS27 3 +
15004 = Alaska CS27 4 +
15005 = Alaska CS27 5 +
15006 = Alaska CS27 6 +
15007 = Alaska CS27 7 +
15008 = Alaska CS27 8 +
15009 = Alaska CS27 9 +
15010 = Alaska CS27 10 +
15031 = Alaska CS83 1 +
15032 = Alaska CS83 2 +
15033 = Alaska CS83 3 +
15034 = Alaska CS83 4 +
15035 = Alaska CS83 5 +
15036 = Alaska CS83 6 +
15037 = Alaska CS83 7 +
15038 = Alaska CS83 8 +
15039 = Alaska CS83 9 +
15040 = Alaska CS83 10 +
15101 = Hawaii CS27 1 +
15102 = Hawaii CS27 2 +
15103 = Hawaii CS27 3 +
15104 = Hawaii CS27 4 +
15105 = Hawaii CS27 5 +
15131 = Hawaii CS83 1 +
15132 = Hawaii CS83 2 +
15133 = Hawaii CS83 3 +
15134 = Hawaii CS83 4 +
15135 = Hawaii CS83 5 +
15201 = Puerto Rico CS27 +
15202 = St Croix +
15230 = Puerto Rico Virgin Is +
15302 = Kentucky CS27 +
15303 = Kentucky CS83 North +
15914 = BLM 14N feet +
15915 = BLM 15N feet +
15916 = BLM 16N feet +
15917 = BLM 17N feet +
16001 = UTM zone 1N +
16002 = UTM zone 2N +
16003 = UTM zone 3N +
16004 = UTM zone 4N +
16005 = UTM zone 5N +
16006 = UTM zone 6N +
16007 = UTM zone 7N +
16008 = UTM zone 8N +
16009 = UTM zone 9N +
16010 = UTM zone 10N +
16011 = UTM zone 11N +
16012 = UTM zone 12N +
16013 = UTM zone 13N +
16014 = UTM zone 14N +
16015 = UTM zone 15N +
16016 = UTM zone 16N +
16017 = UTM zone 17N +
16018 = UTM zone 18N +
16019 = UTM zone 19N +
16020 = UTM zone 20N +
16021 = UTM zone 21N +
16022 = UTM zone 22N +
16023 = UTM zone 23N +
16024 = UTM zone 24N +
16025 = UTM zone 25N +
16026 = UTM zone 26N +
16027 = UTM zone 27N +
16028 = UTM zone 28N +
16029 = UTM zone 29N +
16030 = UTM zone 30N +
16031 = UTM zone 31N +
16032 = UTM zone 32N +
16033 = UTM zone 33N +
16034 = UTM zone 34N +
16035 = UTM zone 35N +
16036 = UTM zone 36N +
16037 = UTM zone 37N +
16038 = UTM zone 38N +
16039 = UTM zone 39N +
16040 = UTM zone 40N +
16041 = UTM zone 41N +
16042 = UTM zone 42N +
16043 = UTM zone 43N +
16044 = UTM zone 44N +
16045 = UTM zone 45N +
16046 = UTM zone 46N +
16047 = UTM zone 47N +
16048 = UTM zone 48N +
16049 = UTM zone 49N +
16050 = UTM zone 50N +
16051 = UTM zone 51N +
16052 = UTM zone 52N +
16053 = UTM zone 53N +
16054 = UTM zone 54N +
16055 = UTM zone 55N +
16056 = UTM zone 56N +
16057 = UTM zone 57N +
16058 = UTM zone 58N +
16059 = UTM zone 59N +
16060 = UTM zone 60N +
16101 = UTM zone 1S +
16102 = UTM zone 2S +
16103 = UTM zone 3S +
16104 = UTM zone 4S +
16105 = UTM zone 5S +
16106 = UTM zone 6S +
16107 = UTM zone 7S +
16108 = UTM zone 8S +
16109 = UTM zone 9S +
16110 = UTM zone 10S +
16111 = UTM zone 11S +
16112 = UTM zone 12S +
16113 = UTM zone 13S +
16114 = UTM zone 14S +
16115 = UTM zone 15S +
16116 = UTM zone 16S +
16117 = UTM zone 17S +
16118 = UTM zone 18S +
16119 = UTM zone 19S +
16120 = UTM zone 20S +
16121 = UTM zone 21S +
16122 = UTM zone 22S +
16123 = UTM zone 23S +
16124 = UTM zone 24S +
16125 = UTM zone 25S +
16126 = UTM zone 26S +
16127 = UTM zone 27S +
16128 = UTM zone 28S +
16129 = UTM zone 29S +
16130 = UTM zone 30S +
16131 = UTM zone 31S +
16132 = UTM zone 32S +
16133 = UTM zone 33S +
16134 = UTM zone 34S +
16135 = UTM zone 35S +
16136 = UTM zone 36S +
16137 = UTM zone 37S +
16138 = UTM zone 38S +
16139 = UTM zone 39S +
16140 = UTM zone 40S +
16141 = UTM zone 41S +
16142 = UTM zone 42S +
16143 = UTM zone 43S +
16144 = UTM zone 44S +
16145 = UTM zone 45S +
16146 = UTM zone 46S +
16147 = UTM zone 47S +
16148 = UTM zone 48S +
16149 = UTM zone 49S +
16150 = UTM zone 50S +
16151 = UTM zone 51S +
16152 = UTM zone 52S +
16153 = UTM zone 53S +
16154 = UTM zone 54S +
16155 = UTM zone 55S +
16156 = UTM zone 56S +
16157 = UTM zone 57S +
16158 = UTM zone 58S +
16159 = UTM zone 59S +
16160 = UTM zone 60S +
16200 = Gauss Kruger zone 0 +
16201 = Gauss Kruger zone 1 +
16202 = Gauss Kruger zone 2 +
16203 = Gauss Kruger zone 3 +
16204 = Gauss Kruger zone 4 +
16205 = Gauss Kruger zone 5 +
17348 = Map Grid of Australia 48 +
17349 = Map Grid of Australia 49 +
17350 = Map Grid of Australia 50 +
17351 = Map Grid of Australia 51 +
17352 = Map Grid of Australia 52 +
17353 = Map Grid of Australia 53 +
17354 = Map Grid of Australia 54 +
17355 = Map Grid of Australia 55 +
17356 = Map Grid of Australia 56 +
17357 = Map Grid of Australia 57 +
17358 = Map Grid of Australia 58 +
17448 = Australian Map Grid 48 +
17449 = Australian Map Grid 49 +
17450 = Australian Map Grid 50 +
17451 = Australian Map Grid 51 +
17452 = Australian Map Grid 52 +
17453 = Australian Map Grid 53 +
17454 = Australian Map Grid 54 +
17455 = Australian Map Grid 55 +
17456 = Australian Map Grid 56 +
17457 = Australian Map Grid 57 +
17458 = Australian Map Grid 58 +
18031 = Argentina 1 +
18032 = Argentina 2 +
18033 = Argentina 3 +
18034 = Argentina 4 +
18035 = Argentina 5 +
18036 = Argentina 6 +
18037 = Argentina 7 +
18051 = Colombia 3W +
18052 = Colombia Bogota +
18053 = Colombia 3E +
18054 = Colombia 6E +
18072 = Egypt Red Belt +
18073 = Egypt Purple Belt +
18074 = Extended Purple Belt +
18141 = New Zealand North Island Nat Grid +
18142 = New Zealand South Island Nat Grid +
19900 = Bahrain Grid +
19905 = Netherlands E Indies Equatorial +
19912 = RSO Borneo +
19926 = Stereo 70 +
32767 = User Defined
+
0x0c03ProjCoordTransno +
1 = Transverse Mercator +
2 = Transverse Mercator Modified Alaska +
3 = Oblique Mercator +
4 = Oblique Mercator Laborde +
5 = Oblique Mercator Rosenmund +
6 = Oblique Mercator Spherical +
7 = Mercator +
8 = Lambert Conf Conic 2SP +
9 = Lambert Conf Conic 1SP +
10 = Lambert Azim Equal Area +
11 = Albers Equal Area +
12 = Azimuthal Equidistant +
13 = Equidistant Conic +
14 = Stereographic +
15 = Polar Stereographic +
16 = Oblique Stereographic +
17 = Equirectangular +
18 = Cassini Soldner +
19 = Gnomonic +
20 = Miller Cylindrical +
21 = Orthographic +
22 = Polyconic +
23 = Robinson +
24 = Sinusoidal +
25 = VanDerGrinten +
26 = New Zealand Map Grid +
27 = Transverse Mercator South Orientated +
28 = Cylindrical Equal Area +
32767 = User Defined
+
0x0c04ProjLinearUnitsno--> GeoTiff Units Values
0x0c05ProjLinearUnitSizeno 
0x0c06ProjStdParallel1no 
0x0c07ProjStdParallel2no 
0x0c08ProjNatOriginLongno 
0x0c09ProjNatOriginLatno 
0x0c0aProjFalseEastingno 
0x0c0bProjFalseNorthingno 
0x0c0cProjFalseOriginLongno 
0x0c0dProjFalseOriginLatno 
0x0c0eProjFalseOriginEastingno 
0x0c0fProjFalseOriginNorthingno 
0x0c10ProjCenterLongno 
0x0c11ProjCenterLatno 
0x0c12ProjCenterEastingno 
0x0c13ProjCenterNorthingno 
0x0c14ProjScaleAtNatOriginno 
0x0c15ProjScaleAtCenterno 
0x0c16ProjAzimuthAngleno 
0x0c17ProjStraightVertPoleLongno 
0x0c18ProjRectifiedGridAngleno 
0x1000VerticalCSTypeno--> GeoTiff VerticalCS Values
0x1001VerticalCitationno 
0x1002VerticalDatumno--> GeoTiff VerticalCS Values
0x1003VerticalUnitsno--> GeoTiff Units Values
0xb799ChartFormatno +
47500 = General +
47501 = Coastal +
47502 = Harbor +
47503 = SailingInternational +
47504 = SmallCraft Route +
47505 = SmallCraftArea +
47506 = SmallCraftFolio +
47507 = Topographic +
47508 = Recreation +
47509 = Index +
47510 = Inset
+
0xb79aChartSourceno 
0xb79bChartSourceEditionno 
0xb79cChartSourceDateno 
0xb79dChartCorrDateno 
0xb79eChartCountryOriginno 
0xb79fChartRasterEditionno 
0xb7a0ChartSoundingDatumno +
47600 = Equatorial Spring Low Water +
47601 = Indian Spring Low Water +
47602 = Lowest Astronomical Tide +
47603 = Lowest Low Water +
47604 = Lowest Normal Low Water +
47605 = Mean Higher High Water +
47606 = Mean High Water +
47607 = Mean High Water Springs +
47608 = Mean Lower Low Water +
47609 = Mean Lower Low Water Springs +
47610 = Mean Low Water +
47611 = Mean Sea Level +
47612 = Tropic Higher High Water +
47613 = Tropic Lower Low Water
+
0xb7a1ChartDepthUnitsno--> GeoTiff Units Values
0xb7a2ChartMagVarno 
0xb7a3ChartMagVarYearno 
0xb7a4ChartMagVarAnnChangeno 
0xb7a5ChartWGSNSShiftno 
0xb7a7InsetNWPixelXno 
0xb7a8InsetNWPixelYno 
0xb7a9ChartContourIntervalno 
+ +

GeoTiff Units Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueUnitsValueUnits
9001= Linear Meter9013= Linear Yard Indian
9002= Linear Foot9014= Linear Fathom
9003= Linear Foot US Survey9015= Linear Mile International Nautical
9004= Linear Foot Modified American9101= Angular Radian
9005= Linear Foot Clarke9102= Angular Degree
9006= Linear Foot Indian9103= Angular Arc Minute
9007= Linear Link9104= Angular Arc Second
9008= Linear Link Benoit9105= Angular Grad
9009= Linear Link Sears9106= Angular Gon
9010= Linear Chain Benoit9107= Angular DMS
9011= Linear Chain Sears9108= Angular DMS Hemisphere
9012= Linear Yard Sears32767= User Defined
+ +

GeoTiff VerticalCS Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueVerticalCSValueVerticalCS
0= Undefined5021= INS ellipsoid
5001= Airy 1830 ellipsoid5022= International 1924 ellipsoid
5002= Airy Modified 1849 ellipsoid5023= International 1967 ellipsoid
5003= ANS ellipsoid5024= Krassowsky 1940 ellipsoid
5004= Bessel 1841 ellipsoid5025= NWL 9D ellipsoid
5005= Bessel Modified ellipsoid5026= NWL 10D ellipsoid
5006= Bessel Namibia ellipsoid5027= Plessis 1817 ellipsoid
5007= Clarke 1858 ellipsoid5028= Struve 1860 ellipsoid
5008= Clarke 1866 ellipsoid5029= War Office ellipsoid
5010= Clarke 1880 Benoit ellipsoid5030= WGS 84 ellipsoid
5011= Clarke 1880 IGN ellipsoid5031= GEM 10C ellipsoid
5012= Clarke 1880 RGS ellipsoid5032= OSU86F ellipsoid
5013= Clarke 1880 Arc ellipsoid5033= OSU91A ellipsoid
5014= Clarke 1880 SGA 1922 ellipsoid5101= Newlyn
5015= Everest 1830 1937 Adjustment ellipsoid5102= North American Vertical Datum 1929
5016= Everest 1830 1967 Definition ellipsoid5103= North American Vertical Datum 1988
5017= Everest 1830 1975 Definition ellipsoid5104= Yellow Sea 1956
5018= Everest 1830 Modified ellipsoid5105= Baltic Sea
5019= GRS 1980 ellipsoid5106= Caspian Sea
5020= Helmert 1906 ellipsoid32767= User Defined
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 1, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/GoPro.html b/ExifTool/html/TagNames/GoPro.html new file mode 100644 index 0000000..b944c70 --- /dev/null +++ b/ExifTool/html/TagNames/GoPro.html @@ -0,0 +1,589 @@ + + + + +GoPro Tags + + + +

GoPro GPMF Tags

+

Tags extracted from the GPMF box of GoPro MP4 videos, the APP6 "GoPro" +segment of JPEG files, and from the "gpmd" timed metadata if the +ExtractEmbedded (-ee) option is enabled. Many more tags exist, but are +currently unknown and extracted only with the Unknown (-u) option. Please +let me know if you discover the meaning of any of these unknown tags. See +https://github.com/gopro/gpmf-parser for details about this format.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ACCL'Accelerometerno(accelerator readings in m/s2)
'ALLD'AutoLowLightDurationno 
'ATTD'Attitudeno 
'ATTR'AttitudeTargetno 
'AUDO'AudioSettingno 
'BPOS'Controller?no 
'CASN'CameraSerialNumberno 
'CSEN'CoyoteSenseno 
'CYTS'CoyoteStatusno 
'DEVC'DeviceContainer---> GoPro GPMF Tags
'DVID'DeviceID?no 
'DVNM'DeviceNameno 
'DZOM'DigitalZoomno'N' = No +
'Y' = Yes
'EISA'ElectronicImageStabilizationno 
'EMPT'Empty?no 
'ESCS'EscapeStatus?no 
'EXPT'MaximumShutterAngleno 
'FACE'FaceDetectedno 
'FCNM'FaceNumbersno 
'FMWR'FirmwareVersionno 
'FWVS'OtherFirmwareno 
'GLPI'GPSPos---> GoPro GLPI Tags
'GPRI'GPSRaw?---> GoPro GPRI Tags
'GPS5'GPSInfo---> GoPro GPS5 Tags
'GPSF'GPSMeasureModeno2 = 2-Dimensional Measurement +
3 = 3-Dimensional Measurement
'GPSP'GPSHPositioningErrorno 
'GPSU'GPSDateTimeno 
'GYRO'Gyroscopeno(gyroscope readings in rad/s)
'ISOE'ISOSpeedsno 
'ISOG'ImageSensorGainno 
'KBAT'BatteryStatus---> GoPro KBAT Tags
'LNED'LocalPositionNEDno 
'MAGN'Magnetometerno 
'MINF'Modelno 
'MTRX'AccelerometerMatrixno 
'MUID'MediaUniqueIDno 
'OREN'AutoRotationno'A' = Auto +
'D' = Down +
'U' = Up
'ORIN'InputOrientationno 
'ORIO'OutputOrientationno 
'PHDR'HDRSettingno 
'PIMN'AutoISOMinno 
'PIMX'AutoISOMaxno 
'PRES'PhotoResolutionno 
'PRTN'ProTuneno'N' = Off +
'Y' = On
'PTCL'ColorModeno 
'PTEV'ExposureCompensationno 
'PTSH'Sharpnessno 
'PTWB'WhiteBalanceno 
'RATE'Rateno 
'RMRK'Commentsno 
'SCAL'ScaleFactor?no 
'SCPR'ScaledPressureno 
'SHUT'ExposureTimesno 
'SIMU'ScaledIMUno 
'SIUN'SIUnits?no 
'SROT'SensorReadoutTimeno 
'STMP'TimeStampno 
'STNM'StreamName?no 
'STRM'NestedSignalStream---> GoPro GPMF Tags
'SYST'SystemTimeno 
'TMPC'CameraTemperatureno 
'TSMP'TotalSamples?no 
'TYPE'StructureType?no 
'UNIF'InputUniformityno 
'UNIT'Units?no 
'VERS'MetadataVersionno 
'VFOV'FieldOfViewno'L' = Linear +
'S' = Super View +
'W' = Wide
'VFRH'VisualFlightRulesHUDno 
'WBAL'ColorTemperaturesno 
'WRGB'WhiteBalanceRGBno 
+ +

GoPro GLPI Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0GPSDateTimeno 
1GPSLatitudeno 
2GPSLongitudeno 
3GPSAltitudeno 
5GPSSpeedXno 
6GPSSpeedYno 
7GPSSpeedZno 
8GPSTrackno 
+ +

GoPro GPRI Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0GPSDateTimeRawno 
1GPSLatitudeRawno 
2GPSLongitudeRawno 
3GPSAltitudeRawno 
6GPSSpeedRawno 
7GPSTrackRawno 
+ +

GoPro GPS5 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0GPSLatitudeno 
1GPSLongitudeno 
2GPSAltitudeno 
3GPSSpeedno 
4GPSSpeed3Dno 
+ +

GoPro KBAT Tags

+

Battery status information found in GoPro Karma videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0BatteryCurrentno 
1BatteryCapacityno 
3BatteryTemperatureno 
4BatteryVoltage1no 
5BatteryVoltage2no 
6BatteryVoltage3no 
7BatteryVoltage4no 
8BatteryTimeno 
14BatteryLevelno 
+ +

GoPro fdsc Tags

+

Tags extracted from the MP4 "fdsc" timed metadata when the ExtractEmbedded +(-ee) option is used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8FirmwareVersionno 
23SerialNumberno 
87OtherSerialNumberno 
102Modelno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 22, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/H264.html b/ExifTool/html/TagNames/H264.html new file mode 100644 index 0000000..03567c7 --- /dev/null +++ b/ExifTool/html/TagNames/H264.html @@ -0,0 +1,399 @@ + + + + +H264 Tags + + + +

H264 Tags

+

Tags extracted from H.264 video streams. The metadata for AVCHD videos is +stored in this stream.

+
+
+ + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ImageHeightno 
ImageWidthno 
MDPM---> H264 MDPM Tags
+ +

H264 MDPM Tags

+

The following tags are decoded from the Modified Digital Video Pack Metadata +(MDPM) of the unregistered user data with UUID +17ee8c60f84d11d98cd60800200c9a66 in the H.264 Supplemental Enhancement +Information (SEI). [Yes, this description is confusing, but nothing +compared to the challenge of actually decoding the data!] This information +may exist at regular intervals through the entire video, but only the first +occurrence is extracted unless the ExtractEmbedded (-ee) option is used (in +which case subsequent occurrences are extracted as sub-documents).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0013TimeCodeno(hours:minutes:seconds:frames)
0x0018DateTimeOriginalno(combined with tag 0x19)
0x0070Camera1---> H264 Camera1 Tags
0x0071Camera2---> H264 Camera2 Tags
0x007fShutter---> H264 Shutter Tags
0x00a0ExposureTimeno 
0x00a1FNumberno 
0x00a2ExposureProgramno +
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
+
0x00a3BrightnessValueno 
0x00a4ExposureCompensationno 
0x00a5MaxApertureValueno 
0x00a6Flashno--> EXIF Flash Values
0x00a7CustomRenderedno0 = Normal +
1 = Custom
0x00a8WhiteBalanceno0 = Auto +
1 = Manual
0x00a9FocalLengthIn35mmFormatno 
0x00aaSceneCaptureTypeno0 = Standard +
1 = Landscape +
2 = Portrait +
3 = Night
0x00b0GPSVersionIDno 
0x00b1GPSLatitudeRefno'N' = North +
'S' = South
0x00b2GPSLatitudeno(combined with tags 0xb3 and 0xb4)
0x00b5GPSLongitudeRefno'E' = East +
'W' = West
0x00b6GPSLongitudeno(combined with tags 0xb7 and 0xb8)
0x00b9GPSAltitudeRefno0 = Above Sea Level +
1 = Below Sea Level
0x00baGPSAltitudeno 
0x00bbGPSTimeStampno(combined with tags 0xbc and 0xbd)
0x00beGPSStatusno'A' = Measurement Active +
'V' = Measurement Void
0x00bfGPSMeasureModeno2 = 2-Dimensional Measurement +
3 = 3-Dimensional Measurement
0x00c0GPSDOPno 
0x00c1GPSSpeedRefno'K' = km/h +
'M' = mph +
'N' = knots
0x00c2GPSSpeedno 
0x00c3GPSTrackRefno'M' = Magnetic North +
'T' = True North
0x00c4GPSTrackno 
0x00c5GPSImgDirectionRefno'M' = Magnetic North +
'T' = True North
0x00c6GPSImgDirectionno 
0x00c7GPSMapDatumno(combined with tag 0xc8)
0x00caGPSDateStampno(combined with tags 0xcb and 0xcc)
0x00e0MakeModel---> H264 MakeModel Tags
0x00e1RecInfo---> H264 RecInfo Tags +
(Canon only)
0x00e4Modelno(Sony only, combined with tags 0xe5 and 0xe6)
0x00eeFrameInfo---> H264 FrameInfo Tags +
(Canon only)
+ +

H264 Camera1 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ApertureSettingno0xfe = Closed +
0xff = Auto
1Gainno[val & 0xf]
1.1ExposureProgramno[val >> 4 & 0xf] +
0 = Program AE +
1 = Gain +
2 = Shutter speed priority AE +
3 = Aperture-priority AE +
4 = Manual
2.1WhiteBalanceno[val >> 5 & 0x7] +
0 = Auto +
1 = Hold +
2 = 1-Push +
3 = Daylight
3Focusno 
+ +

H264 Camera2 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
1ImageStabilizationno0x0 = Off +
0x3f = On (0x3f) +
0xbf = Off (0xbf) +
0xff = n/a
+ +

H264 Shutter Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
1.1ExposureTimeno[val & 0x7fff]
+ +

H264 MakeModel Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
0Makeno0x103 = Panasonic +
0x108 = Sony +
0x1011 = Canon +
0x1104 = JVC
+ +

H264 RecInfo Tags

+

Recording information stored by some Canon video cameras.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0RecordingModeno2 = XP+ +
4 = SP +
5 = LP +
6 = FXP +
7 = MXP
+ +

H264 FrameInfo Tags

+

Frame rate information stored by some Canon video cameras.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0CaptureFrameRateno 
1VideoFrameRateno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 3, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/HP.html b/ExifTool/html/TagNames/HP.html new file mode 100644 index 0000000..2e14d74 --- /dev/null +++ b/ExifTool/html/TagNames/HP.html @@ -0,0 +1,156 @@ + + + + +HP Tags + + + +

HP Tags

+

These tables list tags found in the maker notes of some Hewlett-Packard +camera models.

+ +

The first table lists tags found in the EXIF-format maker notes of the +PhotoSmart 720 (also used by the Vivitar ViviCam 3705, 3705B and 3715).

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0e00PrintIM---> PrintIM Tags
+ +

HP Type2 Tags

+

These tags are used by the PhotoSmart E427.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Lens Shading'LensShadingno 
'PreviewImage'PreviewImageno 
'Serial Number'SerialNumberno 
+ +

HP Type4 Tags

+

These tags are used by the PhotoSmart M627.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12MaxApertureno 
16ExposureTimeno 
20CameraDateTimeno 
52ISOno 
92SerialNumberno 
+ +

HP Type6 Tags

+

These tags are used by the PhotoSmart M425, M525 and M527.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12FNumberno 
16ExposureTimeno 
20CameraDateTimeno 
52ISOno 
88SerialNumberno 
+ +

HP TDHD Tags

+

These tags are extracted from the APP6 "TDHD" segment of Photosmart R837 +JPEG images. Many other unknown tags exist in is data, and can be seen with +the Unknown (-u) option.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CMSN'SerialNumberno 
'FWRV'FirmwareVersionno 
'LSLV'LSLV---> HP TDHD Tags
'TDHD'TDHD---> HP TDHD Tags
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised May 31, 2010 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/HTML.html b/ExifTool/html/TagNames/HTML.html new file mode 100644 index 0000000..310e4ef --- /dev/null +++ b/ExifTool/html/TagNames/HTML.html @@ -0,0 +1,667 @@ + + + + +HTML Tags + + + +

HTML Tags

+

Meta information extracted from the header of HTML and XHTML files. This is +a mix of information found in the META elements, XML element, and the +TITLE element.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'abstract'Abstractno 
'author'Authorno 
'classification'Classificationno 
'content-language'ContentLanguageno 
'copyright'Copyrightno 
'dc'DC---> HTML dc Tags
'description'Descriptionno 
'distribution'Distributionno 
'doc-class'DocClassno 
'doc-rights'DocRightsno 
'doc-type'DocTypeno 
'formatter'Formatterno 
'generator'Generatorno 
'generatorversion'GeneratorVersionno 
'googlebot'GoogleBotno 
'http-equiv'HTTP-equiv---> HTML equiv Tags
'keywords'Keywordsno+ 
'mssmarttagspreventparsing'NoMSSmartTagsno 
'ncc'NCC---> HTML ncc Tags
'o'Office---> HTML Office Tags
'originator'Originatorno 
'owner'Ownerno 
'prod'Prod---> HTML prod Tags
'progid'ProgIDno 
'rating'Ratingno 
'refresh'Refreshno 
'resource-type'ResourceTypeno 
'revisit-after'RevisitAfterno 
'robots'Robotsno+ 
'title'Titleno(the only extracted tag which isn't from an HTML META element)
'vw96'VW96---> HTML vw96 Tags
+ +

HTML dc Tags

+

Dublin Core schema tags (also used in XMP).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'contributor'Contributorno+ 
'coverage'Coverageno 
'creator'Creatorno+ 
'date'Dateno+ 
'description'Descriptionno 
'format'Formatno 
'identifier'Identifierno 
'language'Languageno+ 
'publisher'Publisherno+ 
'relation'Relationno+ 
'rights'Rightsno 
'source'Sourceno 
'subject'Subjectno+ 
'title'Titleno 
'type'Typeno+ 
+ +

HTML equiv Tags

+

These tags have a family 1 group name of "HTTP-equiv".

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'cache-control'CacheControlno 
'content-disposition'ContentDispositionno 
'content-language'ContentLanguageno 
'content-script-type'ContentScriptTypeno 
'content-style-type'ContentStyleTypeno 
'content-type'ContentTypeno 
'default-style'DefaultStyleno 
'expires'Expiresno 
'ext-cache'ExtCacheno 
'imagetoolbar'ImageToolbarno 
'lotus'Lotusno 
'page-enter'PageEnterno 
'page-exit'PageExitno 
'pics-label'PicsLabelno 
'pragma'Pragmano 
'refresh'Refreshno 
'reply-to'ReplyTono 
'set-cookie'SetCookieno 
'site-enter'SiteEnterno 
'site-exit'SiteExitno 
'vary'Varyno 
'window-target'WindowTargetno 
+ +

HTML ncc Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'charset'CharacterSetno 
'depth'Depthno 
'files'Filesno 
'footnotes'Footnotesno 
'generator'Generatorno 
'kbytesize'KByteSizeno 
'maxpagenormal'MaxPageNormalno 
'multimediatype'MultimediaTypeno 
'narrator'Narratorno 
'pagefront'PageFrontno 
'pagenormal'PageNormalno 
'pagespecial'PageSpecialno 
'prodnotes'ProdNotesno 
'produceddate'ProducedDateno 
'producer'Producerno 
'revision'Revisionno 
'revisiondate'RevisionDateno 
'setinfo'SetInfono 
'sidebars'Sidebarsno 
'sourcedate'SourceDateno 
'sourceedition'SourceEditionno 
'sourcepublisher'SourcePublisherno 
'sourcerights'SourceRightsno 
'sourcetitle'SourceTitleno 
'tocitems'TOCItemsno 
'totaltime'Durationno 
+ +

HTML Office Tags

+

Tags written by Microsoft Office applications.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Author'Authorno 
'Category'Categoryno 
'Characters'Charactersno 
'CharactersWithSpaces'CharactersWithSpacesno 
'Company'Companyno 
'Created'CreateDateno 
'Description'Descriptionno 
'Keywords'Keywordsno 
'LastAuthor'LastAuthorno 
'LastPrinted'LastPrintedno 
'LastSaved'ModifyDateno 
'Lines'Linesno 
'Manager'Managerno 
'Pages'Pagesno 
'Paragraphs'Paragraphsno 
'Revision'RevisionNumberno 
'Subject'Subjectno 
'Template'Templateno 
'TotalTime'TotalEditTimeno 
'Version'RevisionNumberno 
'Words'Wordsno 
+ +

HTML prod Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'recengineer'RecEngineerno 
'reclocation'RecLocationno 
+ +

HTML vw96 Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'objecttype'ObjectTypeno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 30, 2012 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/ICC_Profile.html b/ExifTool/html/TagNames/ICC_Profile.html new file mode 100644 index 0000000..377a334 --- /dev/null +++ b/ExifTool/html/TagNames/ICC_Profile.html @@ -0,0 +1,1412 @@ + + + + +ICC_Profile Tags + + + +

ICC_Profile Tags

+

ICC profile information is used in many different file types including JPEG, +TIFF, PDF, PostScript, Photoshop, PNG, MIFF, PICT, QuickTime, XCF and some +RAW formats. While the tags listed below are not individually writable, the +entire profile itself can be accessed via the extra 'ICC_Profile' tag, but +this tag is neither extracted nor written unless specified explicitly. See +http://www.color.org/icc_specs2.xalter for the official ICC +specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'A2B0'AToB0no 
'A2B1'AToB1no 
'A2B2'AToB2no 
'A2B3'AToB3no 
'A2M0'AToM0no 
'B2A0'BToA0no 
'B2A1'BToA1no 
'B2A2'BToA2no 
'B2A3'BToA3no 
'B2D0'BToD0no 
'B2D1'BToD1no 
'B2D2'BToD2no 
'B2D3'BToD3no 
'CxF 'CXFno 
'D2B0'DToB0no 
'D2B1'DToB1no 
'D2B2'DToB2no 
'D2B3'DToB3no 
'Header'ProfileHeader---> ICC_Profile Header Tags
'M2A0'MToA0no 
'M2B0'MToB0no 
'M2B1'MToB1no 
'M2B2'MToB2no 
'M2B3'MToB3no 
'M2S0'MToS0no 
'M2S1'MToS1no 
'M2S2'MToS2no 
'M2S3'MToS3no 
'MS00'WCSProfilesno 
'bAB0'BRDFAToB0no 
'bAB1'BRDFAToB1no 
'bAB2'BRDFAToB2no 
'bAB3'BRDFAToB3no 
'bBA0'BRDFBToA0no 
'bBA1'BRDFBToA1no 
'bBA2'BRDFBToA2no 
'bBA3'BRDFBToA3no 
'bBD0'BRDFBToD0no 
'bBD1'BRDFBToD1no 
'bBD2'BRDFBToD2no 
'bBD3'BRDFBToD3no 
'bDB0'BRDFDToB0no 
'bDB1'BRDFDToB1no 
'bDB2'BRDFDToB2no 
'bDB3'BRDFDToB3no 
'bMB0'BRDFMToB0no 
'bMB1'BRDFMToB1no 
'bMB2'BRDFMToB2no 
'bMB3'BRDFMToB3no 
'bMS0'BRDFMToS0no 
'bMS1'BRDFMToS1no 
'bMS2'BRDFMToS2no 
'bMS3'BRDFMToS3no 
'bTRC'BlueTRCno 
'bXYZ'BlueMatrixColumnno 
'bcp0'BRDFColorimetricParam0no 
'bcp1'BRDFColorimetricParam1no 
'bcp2'BRDFColorimetricParam2no 
'bcp3'BRDFColorimetricParam3no 
'bfd 'UCRBGno 
'bkpt'MediaBlackPointno 
'bsp0'BRDFSpectralParam0no 
'bsp1'BRDFSpectralParam1no 
'bsp2'BRDFSpectralParam2no 
'bsp3'BRDFSpectralParam3no 
'c2sp'CustomToStandardPccno 
'calt'CalibrationDateTimeno 
'cept'ColorEncodingParamsno 
'chad'ChromaticAdaptationno 
'chrm'Chromaticity---> ICC_Profile Chromaticity Tags
'ciis'ColorimetricIntentImageStateno 
'clio'ColorantInfoOutno 
'cloo'ColorantOrderOutno 
'clot'ColorantTableOutno 
'clro'ColorantOrderno 
'clrt'ColorantTable---> ICC_Profile ColorantTable Tags
'cprt'ProfileCopyrightno 
'crdi'CRDInfono 
'csnm'ColorSpaceNameno 
'dAB0'DirectionalAToB0no 
'dAB1'DirectionalAToB1no 
'dAB2'DirectionalAToB2no 
'dAB3'DirectionalAToB3no 
'dBA0'DirectionalBToA0no 
'dBA1'DirectionalBToA1no 
'dBA2'DirectionalBToA2no 
'dBA3'DirectionalBToA3no 
'dBD0'DirectionalBToD0no 
'dBD1'DirectionalBToD1no 
'dBD2'DirectionalBToD2no 
'dBD3'DirectionalBToD3no 
'dDB0'DirectionalDToB0no 
'dDB1'DirectionalDToB1no 
'dDB2'DirectionalDToB2no 
'dDB3'DirectionalDToB3no 
'desc'ProfileDescriptionno 
'devs'DeviceSettingsno 
'dmdd'DeviceModelDescno 
'dmnd'DeviceMfgDescno 
'dscm'ProfileDescriptionMLno 
'fpce'FocalPlaneColorimetryEstimatesno 
'gTRC'GreenTRCno 
'gXYZ'GreenMatrixColumnno 
'gamt'Gamutno 
'gdb0'GamutBoundaryDescription0no 
'gdb1'GamutBoundaryDescription1no 
'gdb2'GamutBoundaryDescription2no 
'gdb3'GamutBoundaryDescription3no 
'kTRC'GrayTRCno 
'lumi'Luminanceno 
'mcta'MultiplexTypeArrayno 
'mdv 'MultiplexDefaultValuesno 
'meas'Measurement---> ICC_Profile Measurement Tags
'meta'Metadata---> ICC_Profile Metadata Tags
'miin'MeasurementInputInfono 
'minf'MeasurementInfono 
'mmod'MakeAndModelno 
'ncl2'NamedColor2no 
'ncol'NamedColorno 
'ndin'NativeDisplayInfono 
'nmcl'NamedColorno 
'pre0'Preview0no 
'pre1'Preview1no 
'pre2'Preview2no 
'ps2i'PS2RenderingIntentno 
'ps2s'PostScript2CSAno 
'psd0'PostScript2CRD0no 
'psd1'PostScript2CRD1no 
'psd2'PostScript2CRD2no 
'psd3'PostScript2CRD3no 
'pseq'ProfileSequenceDescno 
'psid'ProfileSequenceIdentifierno 
'psin'ProfileSequenceInfono 
'psvm'PS2CRDVMSizeno 
'rTRC'RedTRCno 
'rXYZ'RedMatrixColumnno 
'resp'OutputResponseno 
'rfnm'ReferenceNameno 
'rhoc'ReflectionHardcopyOrigColorimetryno 
'rig0'PerceptualRenderingIntentGamutno'prmg' = Perceptual Reference Medium Gamut
'rig2'SaturationRenderingIntentGamutno'prmg' = Perceptual Reference Medium Gamut
'rpoc'ReflectionPrintOutputColorimetryno 
's2cp'StandardToCustomPccno 
'sape'SceneAppearanceEstimatesno 
'scoe'SceneColorimetryEstimatesno 
'scrd'ScreeningDescno 
'scrn'Screeningno 
'smap'SurfaceMapno 
'svcn'SpectralViewingConditionsno 
'swpt'SpectralWhitePointno 
'targ'CharTargetno 
'tech'Technologyno +
'AMD ' = Active Matrix Display +
'CRT ' = Cathode Ray Tube Display +
'KPCD' = Photo CD +
'PMD ' = Passive Matrix Display +
'dcam' = Digital Camera +
'dcpj' = Digital Cinema Projector +
'dmpc' = Digital Motion Picture Camera +
'dsub' = Dye Sublimation Printer +
'epho' = Electrophotographic Printer +
'esta' = Electrostatic Printer +
'flex' = Flexography +
'fprn' = Film Writer +
'fscn' = Film Scanner +
'grav' = Gravure +
'ijet' = Ink Jet Printer +
'imgs' = Photo Image Setter +
'mpfr' = Motion Picture Film Recorder +
'mpfs' = Motion Picture Film Scanner +
'offs' = Offset Lithography +
'pjtv' = Projection Television +
'rpho' = Photographic Paper Printer +
'rscn' = Reflective Scanner +
'silk' = Silkscreen +
'twax' = Thermal Wax Printer +
'vidc' = Video Camera +
'vidm' = Video Monitor
+
'vcgt'VideoCardGammano 
'view'ViewingConditions---> ICC_Profile ViewingConditions Tags
'vued'ViewingCondDescno 
'wtpt'MediaWhitePointno 
+ +

ICC_Profile Header Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ProfileCMMTypeno--> ICC_Profile ManuSig Values
8ProfileVersionno 
12ProfileClassno'abst' = Abstract Profile +
'cenc' = ColorEncodingSpace Profile +
'link' = DeviceLink Profile +
'mid ' = MultiplexIdentification Profile +
'mlnk' = MultiplexLink Profile +
'mntr' = Display Device Profile +
'mvis' = MultiplexVisualization Profile +
'nkpf' = Nikon Input Device Profile (NON-STANDARD!) +
'nmcl' = NamedColor Profile +
'prtr' = Output Device Profile +
'scnr' = Input Device Profile +
'spac' = ColorSpace Conversion Profile
16ColorSpaceDatano 
20ProfileConnectionSpaceno 
24ProfileDateTimeno 
36ProfileFileSignatureno 
40PrimaryPlatformno'APPL' = Apple Computer Inc. +
'MSFT' = Microsoft Corporation +
'SGI ' = Silicon Graphics Inc. +
'SUNW' = Sun Microsystems Inc. +
'TGNT' = Taligent Inc.
44CMMFlagsno 
48DeviceManufacturerno--> ICC_Profile ManuSig Values
52DeviceModelno 
56DeviceAttributesno 
64RenderingIntentno0 = Perceptual +
1 = Media-Relative Colorimetric +
2 = Saturation +
3 = ICC-Absolute Colorimetric
68ConnectionSpaceIlluminantno 
80ProfileCreatorno--> ICC_Profile ManuSig Values
84ProfileIDno 
+ +

ICC_Profile ManuSig Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueManuSig
''=
'4d2p'= Erdt Systems GmbH & Co KG
'AAMA'= Aamazing Technologies, Inc.
'ACER'= Acer Peripherals
'ACLT'= Acolyte Color Research
'ACTI'= Actix Systems, Inc.
'ADAR'= Adara Technology, Inc.
'ADBE'= Adobe Systems Inc.
'ADI '= ADI Systems, Inc.
'AGFA'= Agfa Graphics N.V.
'ALMD'= Alps Electric USA, Inc.
'ALPS'= Alps Electric USA, Inc.
'ALWN'= Alwan Color Expertise
'AMTI'= Amiable Technologies, Inc.
'AOC '= AOC International (U.S.A), Ltd.
'APAG'= Apago
'APPL'= Apple Computer Inc.
'AST '= AST
'AT&T'= AT&T Computer Systems
'BAEL'= BARBIERI electronic
'BRCO'= Barco NV
'BRKP'= Breakpoint Pty Limited
'BROT'= Brother Industries, LTD
'BULL'= Bull
'BUS '= Bus Computer Systems
'C-IT'= C-Itoh
'CAMR'= Intel Corporation
'CANO'= Canon, Inc. (Canon Development Americas, Inc.)
'CARR'= Carroll Touch
'CASI'= Casio Computer Co., Ltd.
'CBUS'= Colorbus PL
'CEL '= Crossfield
'CELx'= Crossfield
'CGS '= CGS Publishing Technologies International GmbH
'CHM '= Rochester Robotics
'CIGL'= Colour Imaging Group, London
'CITI'= Citizen
'CL00'= Candela, Ltd.
'CLIQ'= Color IQ
'CMCO'= Chromaco, Inc.
'CMiX'= CHROMiX
'COLO'= Colorgraphic Communications Corporation
'COMP'= COMPAQ Computer Corporation
'COMp'= Compeq USA/Focus Technology
'CONR'= Conrac Display Products
'CORD'= Cordata Technologies, Inc.
'CPQ '= Compaq Computer Corporation
'CPRO'= ColorPro
'CRN '= Cornerstone
'CTX '= CTX International, Inc.
'CVIS'= ColorVision
'CWC '= Fujitsu Laboratories, Ltd.
'DARI'= Darius Technology, Ltd.
'DATA'= Dataproducts
'DCP '= Dry Creek Photo
'DCRC'= Digital Contents Resource Center, Chung-Ang University
'DELL'= Dell Computer Corporation
'DIC '= Dainippon Ink and Chemicals
'DICO'= Diconix
'DIGI'= Digital
'DL&C'= Digital Light & Color
'DPLG'= Doppelganger, LLC
'DS '= Dainippon Screen
'DSOL'= DOOSOL
'DUPN'= DuPont
'EPSO'= Epson
'ESKO'= Esko-Graphics
'ETRI'= Electronics and Telecommunications Research Institute
'EVER'= Everex Systems, Inc.
'EXAC'= ExactCODE GmbH
'Eizo'= EIZO NANAO CORPORATION
'FALC'= Falco Data Products, Inc.
'FF '= Fuji Photo Film Co.,LTD
'FFEI'= FujiFilm Electronic Imaging, Ltd.
'FNRD'= fnord software
'FORA'= Fora, Inc.
'FORE'= Forefront Technology Corporation
'FP '= Fujitsu
'FPA '= WayTech Development, Inc.
'FUJI'= Fujitsu
'FX '= Fuji Xerox Co., Ltd.
'GCC '= GCC Technologies, Inc.
'GGSL'= Global Graphics Software Limited
'GMB '= Gretagmacbeth
'GMG '= GMG GmbH & Co. KG
'GOLD'= GoldStar Technology, Inc.
'GOOG'= Google
'GPRT'= Giantprint Pty Ltd
'GTMB'= Gretagmacbeth
'GVC '= WayTech Development, Inc.
'GW2K'= Sony Corporation
'HCI '= HCI
'HDM '= Heidelberger Druckmaschinen AG
'HERM'= Hermes
'HITA'= Hitachi America, Ltd.
'HP '= Hewlett-Packard
'HTC '= Hitachi, Ltd.
'HiTi'= HiTi Digital, Inc.
'IBM '= IBM Corporation
'IDNT'= Scitex Corporation, Ltd.
'IEC '= Hewlett-Packard
'IIYA'= Iiyama North America, Inc.
'IKEG'= Ikegami Electronics, Inc.
'IMAG'= Image Systems Corporation
'IMI '= Ingram Micro, Inc.
'INTC'= Intel Corporation
'INTL'= N/A (INTL)
'INTR'= Intra Electronics USA, Inc.
'IOCO'= Iocomm International Technology Corporation
'IPS '= InfoPrint Solutions Company
'IRIS'= Scitex Corporation, Ltd.
'ISL '= Ichikawa Soft Laboratory
'ITNL'= N/A (ITNL)
'IVM '= IVM
'IWAT'= Iwatsu Electric Co., Ltd.
'Idnt'= Scitex Corporation, Ltd.
'Inca'= Inca Digital Printers Ltd.
'Iris'= Scitex Corporation, Ltd.
'JPEG'= Joint Photographic Experts Group
'JSFT'= Jetsoft Development
'JVC '= JVC Information Products Co.
'KART'= Scitex Corporation, Ltd.
'KFC '= KFC Computek Components Corporation
'KLH '= KLH Computers
'KMHD'= Konica Minolta Holdings, Inc.
'KNCA'= Konica Corporation
'KODA'= Kodak
'KYOC'= Kyocera
'Kart'= Scitex Corporation, Ltd.
'LCAG'= Leica Camera AG
'LCCD'= Leeds Colour
'LDAK'= Left Dakota
'LEAD'= Leading Technology, Inc.
'LEXM'= Lexmark International, Inc.
'LINK'= Link Computer, Inc.
'LINO'= Linotronic
'LITE'= Lite-On, Inc.
'Leaf'= Leaf
'Lino'= Linotronic
'MAGC'= Mag Computronic (USA) Inc.
'MAGI'= MAG Innovision, Inc.
'MANN'= Mannesmann
'MICN'= Micron Technology, Inc.
'MICR'= Microtek
'MICV'= Microvitec, Inc.
'MINO'= Minolta
'MITS'= Mitsubishi Electronics America, Inc.
'MITs'= Mitsuba Corporation
'MNLT'= Minolta
'MODG'= Modgraph, Inc.
'MONI'= Monitronix, Inc.
'MONS'= Monaco Systems Inc.
'MORS'= Morse Technology, Inc.
'MOTI'= Motive Systems
'MSFT'= Microsoft Corporation
'MUTO'= MUTOH INDUSTRIES LTD.
'Mits'= Mitsubishi Electric Corporation Kyoto Works
'NANA'= NANAO USA Corporation
'NEC '= NEC Corporation
'NEXP'= NexPress Solutions LLC
'NISS'= Nissei Sangyo America, Ltd.
'NKON'= Nikon Corporation
'NONE'= none
'OCE '= Oce Technologies B.V.
'OCEC'= OceColor
'OKI '= Oki
'OKID'= Okidata
'OKIP'= Okidata
'OLIV'= Olivetti
'OLYM'= OLYMPUS OPTICAL CO., LTD
'ONYX'= Onyx Graphics
'OPTI'= Optiquest
'PACK'= Packard Bell
'PANA'= Matsushita Electric Industrial Co., Ltd.
'PANT'= Pantone, Inc.
'PBN '= Packard Bell
'PFU '= PFU Limited
'PHIL'= Philips Consumer Electronics Co.
'PNTX'= HOYA Corporation PENTAX Imaging Systems Division
'POne'= Phase One A/S
'PREM'= Premier Computer Innovations
'PRIN'= Princeton Graphic Systems
'PRIP'= Princeton Publishing Labs
'QLUX'= Hong Kong
'QMS '= QMS, Inc.
'QPCD'= QPcard AB
'QUAD'= QuadLaser
'QUME'= Qume Corporation
'RADI'= Radius, Inc.
'RDDx'= Integrated Color Solutions, Inc.
'RDG '= Roland DG Corporation
'REDM'= REDMS Group, Inc.
'RELI'= Relisys
'RGMS'= Rolf Gierling Multitools
'RICO'= Ricoh Corporation
'RNLD'= Edmund Ronald
'ROYA'= Royal
'RPC '= Ricoh Printing Systems,Ltd.
'RTL '= Royal Information Electronics Co., Ltd.
'SAMP'= Sampo Corporation of America
'SAMS'= Samsung, Inc.
'SANT'= Jaime Santana Pomares
'SCIT'= Scitex Corporation, Ltd.
'SCRN'= Dainippon Screen
'SDP '= Scitex Corporation, Ltd.
'SEC '= SAMSUNG ELECTRONICS CO.,LTD
'SEIK'= Seiko Instruments U.S.A., Inc.
'SEIk'= Seikosha
'SGUY'= ScanGuy.com
'SHAR'= Sharp Laboratories
'SICC'= International Color Consortium
'SONY'= SONY Corporation
'SPCL'= SpectraCal
'STAR'= Star
'STC '= Sampo Technology Corporation
'Scit'= Scitex Corporation, Ltd.
'Sdp '= Scitex Corporation, Ltd.
'Sony'= Sony Corporation
'TALO'= Talon Technology Corporation
'TAND'= Tandy
'TATU'= Tatung Co. of America, Inc.
'TAXA'= TAXAN America, Inc.
'TDS '= Tokyo Denshi Sekei K.K.
'TECO'= TECO Information Systems, Inc.
'TEGR'= Tegra
'TEKT'= Tektronix, Inc.
'TI '= Texas Instruments
'TMKR'= TypeMaker Ltd.
'TOSB'= TOSHIBA corp.
'TOSH'= Toshiba, Inc.
'TOTK'= TOTOKU ELECTRIC Co., LTD
'TRIU'= Triumph
'TSBT'= TOSHIBA TEC CORPORATION
'TTX '= TTX Computer Products, Inc.
'TVM '= TVM Professional Monitor Corporation
'TW '= TW Casper Corporation
'ULSX'= Ulead Systems
'UNIS'= Unisys
'UTZF'= Utz Fehlau & Sohn
'VARI'= Varityper
'VIEW'= Viewsonic
'VISL'= Visual communication
'VIVO'= Vivo Mobile Communication Co., Ltd
'WANG'= Wang
'WLBR'= Wilbur Imaging
'WTG2'= Ware To Go
'WYSE'= WYSE Technology
'XERX'= Xerox Corporation
'XRIT'= X-Rite
'Z123'= Lavanya's test Company
'ZRAN'= Zoran Corporation
'Zebr'= Zebra Technologies Inc
'appl'= Apple Computer Inc.
'bICC'= basICColor GmbH
'berg'= bergdesign incorporated
'ceyd'= Integrated Color Solutions, Inc.
'clsp'= MacDermid ColorSpan, Inc.
'ds '= Dainippon Screen
'dupn'= DuPont
'ffei'= FujiFilm Electronic Imaging, Ltd.
'flux'= FluxData Corporation
'iris'= Scitex Corporation, Ltd.
'kart'= Scitex Corporation, Ltd.
'lcms'= Little CMS
'lino'= Linotronic
'none'= none
'ob4d'= Erdt Systems GmbH & Co KG
'obic'= Medigraph GmbH
'quby'= Qubyx Sarl
'scit'= Scitex Corporation, Ltd.
'scrn'= Dainippon Screen
'sdp '= Scitex Corporation, Ltd.
'siwi'= SIWI GRAFIKA CORPORATION
'yxym'= YxyMaster GmbH
+ +

ICC_Profile Chromaticity Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8ChromaticityChannelsno 
10ChromaticityColorantno0 = Unknown +
1 = ITU-R BT.709 +
2 = SMPTE RP145-1994 +
3 = EBU Tech.3213-E +
4 = P22
12ChromaticityChannel1no 
20ChromaticityChannel2no 
28ChromaticityChannel3no 
36ChromaticityChannel4no 
+ +

ICC_Profile ColorantTable Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8ColorantCountno 
12Colorant1Nameno 
44Colorant1Coordinatesno 
50Colorant2Nameno 
82Colorant2Coordinatesno 
88Colorant3Nameno 
120Colorant3Coordinatesno 
+ +

ICC_Profile Measurement Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8MeasurementObserverno1 = CIE 1931 +
2 = CIE 1964
12MeasurementBackingno 
24MeasurementGeometryno0 = Unknown +
1 = 0/45 or 45/0 +
2 = 0/d or d/0
28MeasurementFlareno 
32MeasurementIlluminantno + +
1 = D50 +
2 = D65 +
3 = D93 +
4 = F2
  5 = D55 +
6 = A +
7 = Equi-Power (E) +
8 = F8
+
+ +

ICC_Profile Metadata Tags

+

Only these few tags have been pre-defined, but ExifTool will extract any +Metadata tags that exist.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CreatorAppno 
ManufacturerNameno 
MediaColorno 
MediaWeightno 
+ +

ICC_Profile ViewingConditions Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8ViewingCondIlluminantno 
20ViewingCondSurroundno 
32ViewingCondIlluminantTypeno + +
1 = D50 +
2 = D65 +
3 = D93 +
4 = F2
  5 = D55 +
6 = A +
7 = Equi-Power (E) +
8 = F8
+
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 16, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/ICO.html b/ExifTool/html/TagNames/ICO.html new file mode 100644 index 0000000..583d412 --- /dev/null +++ b/ExifTool/html/TagNames/ICO.html @@ -0,0 +1,82 @@ + + + + +ICO Tags + + + +

ICO Tags

+

Information extracted from Windows ICO (icon) and CUR (cursor) files.

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
2ImageTypeno1 = Icon +
2 = Cursor
4ImageCountno 
6IconDir---> ICO IconDir Tags
+ +

ICO IconDir Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ImageWidthno 
1ImageHeightno 
2NumColorsno 
4ColorPlanes +
HotspotX
no
no
(ICO files) +
(CUR files)
6BitsPerPixel +
HotspotY
no
no
(ICO files) +
(CUR files)
8ImageLengthno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 19, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/ID3.html b/ExifTool/html/TagNames/ID3.html new file mode 100644 index 0000000..607234d --- /dev/null +++ b/ExifTool/html/TagNames/ID3.html @@ -0,0 +1,1679 @@ + + + + +ID3 Tags + + + +

ID3 Tags

+

ExifTool extracts ID3 and Lyrics3 information from MP3, MPEG, WAV, AIFF, +OGG, FLAC, APE, MPC and RealAudio files. ID3v2 tags which support multiple +languages (eg. Comment and Lyrics) are extracted by specifying the tag name, +followed by a dash ('-'), then a 3-character ISO 639-2 language code (eg. +"Comment-spa"). See https://id3.org/ for the official ID3 specification +and http://www.loc.gov/standards/iso639-2/php/code_list.php for a list of +ISO 639-2 language codes.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ID3v1---> ID3 v1 Tags
ID3v1_Enh---> ID3 v1_Enh Tags
ID3v2_2---> ID3 v2_2 Tags
ID3v2_3---> ID3 v2_3 Tags
ID3v2_4---> ID3 v2_4 Tags
+ +

ID3 v1 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3Titleno 
33Artistno 
63Albumno 
93Yearno 
97Commentno 
125Trackno(v1.1 addition -- last 2 bytes of v1.0 Comment field)
127Genreno(CR and RX are ID3v2 only) + + +
0 = Blues +
1 = Classic Rock +
2 = Country +
3 = Dance +
4 = Disco +
5 = Funk +
6 = Grunge +
7 = Hip-Hop +
8 = Jazz +
9 = Metal +
10 = New Age +
11 = Oldies +
12 = Other +
13 = Pop +
14 = R&B +
15 = Rap +
16 = Reggae +
17 = Rock +
18 = Techno +
19 = Industrial +
20 = Alternative +
21 = Ska +
22 = Death Metal +
23 = Pranks +
24 = Soundtrack +
25 = Euro-Techno +
26 = Ambient +
27 = Trip-Hop +
28 = Vocal +
29 = Jazz+Funk +
30 = Fusion +
31 = Trance +
32 = Classical +
33 = Instrumental +
34 = Acid +
35 = House +
36 = Game +
37 = Sound Clip +
38 = Gospel +
39 = Noise +
40 = Alt. Rock +
41 = Bass +
42 = Soul +
43 = Punk +
44 = Space +
45 = Meditative +
46 = Instrumental Pop +
47 = Instrumental Rock +
48 = Ethnic +
49 = Gothic +
50 = Darkwave +
51 = Techno-Industrial +
52 = Electronic +
53 = Pop-Folk +
54 = Eurodance +
55 = Dream +
56 = Southern Rock +
57 = Comedy +
58 = Cult +
59 = Gangsta Rap +
60 = Top 40 +
61 = Christian Rap +
62 = Pop/Funk +
63 = Jungle +
64 = Native American
  65 = Cabaret +
66 = New Wave +
67 = Psychedelic +
68 = Rave +
69 = Showtunes +
70 = Trailer +
71 = Lo-Fi +
72 = Tribal +
73 = Acid Punk +
74 = Acid Jazz +
75 = Polka +
76 = Retro +
77 = Musical +
78 = Rock & Roll +
79 = Hard Rock +
80 = Folk +
81 = Folk-Rock +
82 = National Folk +
83 = Swing +
84 = Fast-Fusion +
85 = Bebop +
86 = Latin +
87 = Revival +
88 = Celtic +
89 = Bluegrass +
90 = Avantgarde +
91 = Gothic Rock +
92 = Progressive Rock +
93 = Psychedelic Rock +
94 = Symphonic Rock +
95 = Slow Rock +
96 = Big Band +
97 = Chorus +
98 = Easy Listening +
99 = Acoustic +
100 = Humour +
101 = Speech +
102 = Chanson +
103 = Opera +
104 = Chamber Music +
105 = Sonata +
106 = Symphony +
107 = Booty Bass +
108 = Primus +
109 = Porn Groove +
110 = Satire +
111 = Slow Jam +
112 = Club +
113 = Tango +
114 = Samba +
115 = Folklore +
116 = Ballad +
117 = Power Ballad +
118 = Rhythmic Soul +
119 = Freestyle +
120 = Duet +
121 = Punk Rock +
122 = Drum Solo +
123 = A Cappella +
124 = Euro-House +
125 = Dance Hall +
126 = Goa +
127 = Drum & Bass +
128 = Club-House +
129 = Hardcore
  130 = Terror +
131 = Indie +
132 = BritPop +
133 = Afro-Punk +
134 = Polsk Punk +
135 = Beat +
136 = Christian Gangsta Rap +
137 = Heavy Metal +
138 = Black Metal +
139 = Crossover +
140 = Contemporary Christian +
141 = Christian Rock +
142 = Merengue +
143 = Salsa +
144 = Thrash Metal +
145 = Anime +
146 = JPop +
147 = Synthpop +
148 = Abstract +
149 = Art Rock +
150 = Baroque +
151 = Bhangra +
152 = Big Beat +
153 = Breakbeat +
154 = Chillout +
155 = Downtempo +
156 = Dub +
157 = EBM +
158 = Eclectic +
159 = Electro +
160 = Electroclash +
161 = Emo +
162 = Experimental +
163 = Garage +
164 = Global +
165 = IDM +
166 = Illbient +
167 = Industro-Goth +
168 = Jam Band +
169 = Krautrock +
170 = Leftfield +
171 = Lounge +
172 = Math Rock +
173 = New Romantic +
174 = Nu-Breakz +
175 = Post-Punk +
176 = Post-Rock +
177 = Psytrance +
178 = Shoegaze +
179 = Space Rock +
180 = Trop Rock +
181 = World Music +
182 = Neoclassical +
183 = Audiobook +
184 = Audio Theatre +
185 = Neue Deutsche Welle +
186 = Podcast +
187 = Indie Rock +
188 = G-Funk +
189 = Dubstep +
190 = Garage Rock +
191 = Psybient +
255 = None +
'CR' = Cover +
'RX' = Remix
+
+ +

ID3 v1_Enh Tags

+

ID3 version 1 "Enhanced TAG" information (not part of the official spec).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4Title2no 
64Artist2no 
124Album2no 
184Speedno1 = Slow +
2 = Medium +
3 = Fast +
4 = Hardcore
185Genreno 
215StartTimeno 
221EndTimeno 
+ +

ID3 v2_2 Tags

+

ExifTool extracts mainly text-based tags from ID3v2 information. The tags +in the tables below are those extracted by ExifTool, and don't represent a +complete list of available ID3v2 tags.

+ +

ID3 version 2.2 tags. (These are the tags written by iTunes 5.0.)

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CNT'PlayCounterno 
'COM'Commentno 
'IPL'InvolvedPeopleno 
'ITU'iTunesU?no 
'PCS'Podcast?no 
'PIC'Pictureno(the 3 tags below are also extracted from this PIC frame)
'PIC-1'PictureFormatno 
'PIC-2'PictureTypeno--> ID3 PictureType Values
'PIC-3'PictureDescriptionno 
'POP'Popularimeterno 
'RVA'RelativeVolumeAdjustmentno 
'SLT'SynLyrics---> ID3 SynLyrics Tags
'TAL'Albumno 
'TBP'BeatsPerMinuteno 
'TCM'Composerno 
'TCO'Genreno(uses same lookup table as ID3v1 Genre)
'TCP'Compilationno0 = No +
1 = Yes
'TCR'Copyrightno 
'TDA'Dateno 
'TDY'PlaylistDelayno 
'TEN'EncodedByno 
'TFT'FileTypeno 
'TIM'Timeno 
'TKE'InitialKeyno 
'TLA'Languageno 
'TLE'Lengthno 
'TMT'Mediano 
'TOA'OriginalArtistno 
'TOF'OriginalFileNameno 
'TOL'OriginalLyricistno 
'TOR'OriginalReleaseYearno 
'TOT'OriginalAlbumno 
'TP1'Artistno 
'TP2'Bandno 
'TP3'Conductorno 
'TP4'InterpretedByno 
'TPA'PartOfSetno 
'TPB'Publisherno 
'TRC'ISRCno 
'TRD'RecordingDatesno 
'TRK'Trackno 
'TS2'AlbumArtistSortOrderno 
'TSA'AlbumSortOrderno 
'TSC'ComposerSortOrderno 
'TSI'Sizeno 
'TSP'PerformerSortOrderno 
'TSS'EncoderSettingsno 
'TST'TitleSortOrderno 
'TT1'Groupingno 
'TT2'Titleno 
'TT3'Subtitleno 
'TXT'Lyricistno 
'TXX'UserDefinedTextno 
'TYE'Yearno 
'ULT'Lyricsno 
'WAF'FileURLno 
'WAR'ArtistURLno 
'WAS'SourceURLno 
'WCM'CommercialURLno 
'WCP'CopyrightURLno 
'WPB'PublisherURLno 
'WXX'UserDefinedURLno 
+ +

ID3 PictureType Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
ValuePictureTypeValuePictureTypeValuePictureType
0= Other7= Lead Artist14= Recording Session
1= 32x32 PNG Icon8= Artist15= Performance
2= Other Icon9= Conductor16= Capture from Movie or Video
3= Front Cover10= Band17= Bright(ly) Colored Fish
4= Back Cover11= Composer18= Illustration
5= Leaflet12= Lyricist19= Band Logo
6= Media13= Recording Studio or Location20= Publisher Logo
+ +

ID3 SynLyrics Tags

+

The following tags are extracted from synchronized lyrics/text frames.

+
+
+ + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
SynchronizedLyricsDescriptionno 
SynchronizedLyricsTextno+(each list item has a leading time stamp in square brackets. Time stamps may +be in seconds with format [MM:SS.ss], or MPEG frames with format [FFFF], +depending on how this information was stored)
SynchronizedLyricsTypeno +
0 = Other +
1 = Lyrics +
2 = Text Transcription +
3 = Movement/part Name +
4 = Events +
5 = Chord +
6 = Trivia/"pop-up" Information +
7 = Web Page URL +
8 = Image URL
+
+ +

ID3 v2_3 Tags

+

ID3 version 2.3 tags. Includes some non-standard tags written by other +software.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'APIC'Pictureno(the 3 tags below are also extracted from this APIC frame)
'APIC-1'PictureMIMETypeno 
'APIC-2'PictureTypeno--> ID3 PictureType Values
'APIC-3'PictureDescriptionno 
'COMM'Commentno 
'GRP1'Groupingno 
'IPLS'InvolvedPeopleno 
'ITNU'iTunesU?no 
'MCDI'MusicCDIdentifierno 
'MVIN'MovementNumberno 
'MVNM'MovementNameno 
'OWNE'Ownershipno 
'PCNT'PlayCounterno 
'PCST'Podcast?no 
'POPM'Popularimeterno 
'PRIV'Private---> ID3 Private Tags
'SYLT'SynLyrics---> ID3 SynLyrics Tags
'TALB'Albumno 
'TBPM'BeatsPerMinuteno 
'TCAT'PodcastCategoryno 
'TCMP'Compilationno0 = No +
1 = Yes
'TCOM'Composerno 
'TCON'Genreno(uses same lookup table as ID3v1 Genre)
'TCOP'Copyrightno 
'TDAT'Dateno 
'TDES'PodcastDescriptionno 
'TDLY'PlaylistDelayno 
'TENC'EncodedByno 
'TEXT'Lyricistno 
'TFLT'FileTypeno 
'TGID'PodcastIDno 
'TIME'Timeno 
'TIT1'Groupingno 
'TIT2'Titleno 
'TIT3'Subtitleno 
'TKEY'InitialKeyno 
'TKWD'PodcastKeywordsno 
'TLAN'Languageno 
'TLEN'Lengthno 
'TMED'Mediano 
'TOAL'OriginalAlbumno 
'TOFN'OriginalFileNameno 
'TOLY'OriginalLyricistno 
'TOPE'OriginalArtistno 
'TORY'OriginalReleaseYearno 
'TOWN'FileOwnerno 
'TPE1'Artistno 
'TPE2'Bandno 
'TPE3'Conductorno 
'TPE4'InterpretedByno 
'TPOS'PartOfSetno 
'TPUB'Publisherno 
'TRCK'Trackno 
'TRDA'RecordingDatesno 
'TRSN'InternetRadioStationNameno 
'TRSO'InternetRadioStationOwnerno 
'TSIZ'Sizeno 
'TSO2'AlbumArtistSortOrderno 
'TSOC'ComposerSortOrderno 
'TSRC'ISRCno 
'TSSE'EncoderSettingsno 
'TXXX'UserDefinedTextno 
'TYER'Yearno 
'USER'TermsOfUseno 
'USLT'Lyricsno 
'WCOM'CommercialURLno 
'WCOP'CopyrightURLno 
'WFED'PodcastURLno 
'WOAF'FileURLno 
'WOAR'ArtistURLno 
'WOAS'SourceURLno 
'WORS'InternetRadioStationURLno 
'WPAY'PaymentURLno 
'WPUB'PublisherURLno 
'WXXX'UserDefinedURLno 
'XDOR'OriginalReleaseTimeno 
'XOLY'OlympusDSS---> Olympus DSS Tags
'XSOA'AlbumSortOrderno 
'XSOP'PerformerSortOrderno 
'XSOT'TitleSortOrderno 
+ +

ID3 Private Tags

+

ID3 private (PRIV) tags. ExifTool will decode any private tags found, even +if they do not appear in this table.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AverageLevelno 
PeakValueno 
WM_CollectionGroupIDno 
WM_CollectionIDno 
WM_ContentIDno 
WM_MediaClassPrimaryIDno 
WM_MediaClassSecondaryIDno 
WM_Providerno 
XMP---> XMP Tags
+ +

ID3 v2_4 Tags

+

ID3 version 2.4 tags. Includes some non-standard tags written by other +software.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'APIC'Pictureno(the 3 tags below are also extracted from this APIC frame)
'APIC-1'PictureMIMETypeno 
'APIC-2'PictureTypeno--> ID3 PictureType Values
'APIC-3'PictureDescriptionno 
'COMM'Commentno 
'GRP1'Groupingno 
'ITNU'iTunesU?no 
'MCDI'MusicCDIdentifierno 
'MVIN'MovementNumberno 
'MVNM'MovementNameno 
'OWNE'Ownershipno 
'PCNT'PlayCounterno 
'PCST'Podcast?no 
'POPM'Popularimeterno 
'PRIV'Private---> ID3 Private Tags
'RVA2'RelativeVolumeAdjustmentno 
'SYLT'SynLyrics---> ID3 SynLyrics Tags
'TALB'Albumno 
'TBPM'BeatsPerMinuteno 
'TCAT'PodcastCategoryno 
'TCMP'Compilationno0 = No +
1 = Yes
'TCOM'Composerno 
'TCON'Genreno(uses same lookup table as ID3v1 Genre)
'TCOP'Copyrightno 
'TDEN'EncodingTimeno 
'TDES'PodcastDescriptionno 
'TDLY'PlaylistDelayno 
'TDOR'OriginalReleaseTimeno 
'TDRC'RecordingTimeno 
'TDRL'ReleaseTimeno 
'TDTG'TaggingTimeno 
'TENC'EncodedByno 
'TEXT'Lyricistno 
'TFLT'FileTypeno 
'TGID'PodcastIDno 
'TIPL'InvolvedPeopleno 
'TIT1'Groupingno 
'TIT2'Titleno 
'TIT3'Subtitleno 
'TKEY'InitialKeyno 
'TKWD'PodcastKeywordsno 
'TLAN'Languageno 
'TLEN'Lengthno 
'TMCL'MusicianCreditsno 
'TMED'Mediano 
'TMOO'Moodno 
'TOAL'OriginalAlbumno 
'TOFN'OriginalFileNameno 
'TOLY'OriginalLyricistno 
'TOPE'OriginalArtistno 
'TOWN'FileOwnerno 
'TPE1'Artistno 
'TPE2'Bandno 
'TPE3'Conductorno 
'TPE4'InterpretedByno 
'TPOS'PartOfSetno 
'TPRO'ProducedNoticeno 
'TPUB'Publisherno 
'TRCK'Trackno 
'TRSN'InternetRadioStationNameno 
'TRSO'InternetRadioStationOwnerno 
'TSO2'AlbumArtistSortOrderno 
'TSOA'AlbumSortOrderno 
'TSOC'ComposerSortOrderno 
'TSOP'PerformerSortOrderno 
'TSOT'TitleSortOrderno 
'TSRC'ISRCno 
'TSSE'EncoderSettingsno 
'TSST'SetSubtitleno 
'TXXX'UserDefinedTextno 
'USER'TermsOfUseno 
'USLT'Lyricsno 
'WCOM'CommercialURLno 
'WCOP'CopyrightURLno 
'WFED'PodcastURLno 
'WOAF'FileURLno 
'WOAR'ArtistURLno 
'WOAS'SourceURLno 
'WORS'InternetRadioStationURLno 
'WPAY'PaymentURLno 
'WPUB'PublisherURLno 
'WXXX'UserDefinedURLno 
'XDOR'OriginalReleaseTimeno 
'XOLY'OlympusDSS---> Olympus DSS Tags
'XSOA'AlbumSortOrderno 
'XSOP'PerformerSortOrderno 
'XSOT'TitleSortOrderno 
+ +

ID3 Lyrics3 Tags

+

ExifTool extracts Lyrics3 version 1.00 and 2.00 tags from any file that +supports ID3. See https://id3.org/Lyrics3 for the specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AUT'Authorno 
'CRC'CRCno 
'EAL'ExtendedAlbumNameno 
'EAR'ExtendedArtistNameno 
'ETT'ExtendedTrackTitleno 
'IMG'AssociatedImageFileno 
'IND'Indicationsno 
'INF'AdditionalInfono 
'LYR'Lyricsno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 1, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/IPTC.html b/ExifTool/html/TagNames/IPTC.html new file mode 100644 index 0000000..be3885c --- /dev/null +++ b/ExifTool/html/TagNames/IPTC.html @@ -0,0 +1,904 @@ + + + + +IPTC Tags + + + +

IPTC Tags

+

+The tags listed below are part of the International Press Telecommunications +Council (IPTC) and the Newspaper Association of America (NAA) Information +Interchange Model (IIM). This is an older meta information format, slowly +being phased out in favor of XMP -- the newer IPTCCore specification uses +XMP format. IPTC information may be found in JPG, TIFF, PNG, MIFF, PS, PDF, +PSD, XCF and DNG images.

+ +

IPTC information is separated into different records, each of which has its +own set of tags. See +http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf for the +official IPTC IIM specification.

+ +

This specification dictates a length for ASCII (string or digits) and +binary (undef) values. These lengths are given in square brackets after +the Writable format name. For tags where a range of lengths is allowed, +the minimum and maximum lengths are separated by a comma within the +brackets. When writing, ExifTool issues a minor warning and truncates the +value if it is longer than allowed by the IPTC specification. Minor errors +may be ignored with the IgnoreMinorErrors (-m) option, allowing longer +values to be written, but beware that values like this may cause problems +for some other IPTC readers. ExifTool will happily read IPTC values of any +length.

+ +

Separate IPTC date and time tags may be written with a combined date/time +value and ExifTool automagically takes the appropriate part of the date/time +string depending on whether a date or time tag is being written. This is +very useful when copying date/time values to IPTC from other metadata +formats.

+ +

IPTC time values include a timezone offset. If written with a value which +doesn't include a timezone then the current local timezone offset is used +(unless written with a combined date/time, in which case the local timezone +offset at the specified date/time is used, which may be different due to +changes in daylight savings time).

+ +

Note that it is not uncommon for IPTC to be found in non-standard locations +in JPEG and TIFF-based images. When reading, the family 1 group name has a +number added for non-standard IPTC ("IPTC2", "IPTC3", etc), but when writing +only "IPTC" may be specified as the group. To keep the IPTC consistent, +ExifTool updates tags in all existing IPTC locations, but will create a new +IPTC group only in the standard location. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RecordTag NameWritableValues / Notes
1IPTCEnvelope---> IPTC EnvelopeRecord Tags
2IPTCApplication---> IPTC ApplicationRecord Tags
3IPTCNewsPhoto---> IPTC NewsPhoto Tags
7IPTCPreObjectData---> IPTC PreObjectData Tags
8IPTCObjectData---> IPTC ObjectData Tags
9IPTCPostObjectData---> IPTC PostObjectData Tags
240IPTCFotoStation---> IPTC FotoStation Tags
+ +

IPTC EnvelopeRecord Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0EnvelopeRecordVersionint16u: 
5Destinationstring[0,1024]+ 
20FileFormatint16u0 = No ObjectData +
1 = IPTC-NAA Digital Newsphoto Parameter Record +
2 = IPTC7901 Recommended Message Format +
3 = Tagged Image File Format (Adobe/Aldus Image data) +
4 = Illustrator (Adobe Graphics data) +
5 = AppleSingle (Apple Computer Inc) +
6 = NAA 89-3 (ANPA 1312) +
7 = MacBinary II +
8 = IPTC Unstructured Character Oriented File Format (UCOFF) +
9 = United Press International ANPA 1312 variant +
10 = United Press International Down-Load Message +
11 = JPEG File Interchange (JFIF) +
12 = Photo-CD Image-Pac (Eastman Kodak) +
13 = Bit Mapped Graphics File [.BMP] (Microsoft) +
14 = Digital Audio File [.WAV] (Microsoft & Creative Labs) +
15 = Audio plus Moving Video [.AVI] (Microsoft) +
16 = PC DOS/Windows Executable Files [.COM][.EXE] +
17 = Compressed Binary File [.ZIP] (PKWare Inc) +
18 = Audio Interchange File Format AIFF (Apple Computer Inc) +
19 = RIFF Wave (Microsoft Corporation) +
20 = Freehand (Macromedia/Aldus) +
21 = Hypertext Markup Language [.HTML] (The Internet Society) +
22 = MPEG 2 Audio Layer 2 (Musicom), ISO/IEC +
23 = MPEG 2 Audio Layer 3, ISO/IEC +
24 = Portable Document File [.PDF] Adobe +
25 = News Industry Text Format (NITF) +
26 = Tape Archive [.TAR] +
27 = Tidningarnas Telegrambyra NITF version (TTNITF DTD) +
28 = Ritzaus Bureau NITF version (RBNITF DTD) +
29 = Corel Draw [.CDR]
22FileVersionint16u 
30ServiceIdentifierstring[0,10] 
40EnvelopeNumberdigits[8] 
50ProductIDstring[0,32]+ 
60EnvelopePrioritydigits[1] +
0 = 0 (reserved) +
1 = 1 (most urgent) +
2 = 2 +
3 = 3 +
4 = 4 +
5 = 5 (normal urgency) +
6 = 6 +
7 = 7 +
8 = 8 (least urgent) +
9 = 9 (user-defined priority)
+
70DateSentdigits[8] 
80TimeSentstring[11] 
90CodedCharacterSetstring[0,32]!(values are entered in the form "ESC X Y[, ...]". The escape sequence for +UTF-8 character coding is "ESC % G", but this is displayed as "UTF8" for +convenience. Either string may be used when writing. The value of this tag +affects the decoding of string values in the Application and NewsPhoto +records. This tag is marked as "unsafe" to prevent it from being copied by +default in a group operation because existing tags in the destination image +may use a different encoding. When creating a new IPTC record from scratch, +it is suggested that this be set to "UTF8" if special characters are a +possibility)
100UniqueObjectNamestring[14,80] 
120ARMIdentifierint16u 
122ARMVersionint16u 
+ +

IPTC ApplicationRecord Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0ApplicationRecordVersionint16u: 
3ObjectTypeReferencestring[3,67] 
4ObjectAttributeReferencestring[4,68]+ 
5ObjectNamestring[0,64] 
7EditStatusstring[0,64] 
8EditorialUpdatedigits[2]01 = Additional language
10Urgencydigits[1] +
0 = 0 (reserved) +
1 = 1 (most urgent) +
2 = 2 +
3 = 3 +
4 = 4 +
5 = 5 (normal urgency) +
6 = 6 +
7 = 7 +
8 = 8 (least urgent) +
9 = 9 (user-defined priority)
+
12SubjectReferencestring[13,236]+ 
15Categorystring[0,3] 
20SupplementalCategoriesstring[0,32]+ 
22FixtureIdentifierstring[0,32] 
25Keywordsstring[0,64]+ 
26ContentLocationCodestring[3]+ 
27ContentLocationNamestring[0,64]+ 
30ReleaseDatedigits[8] 
35ReleaseTimestring[11] 
37ExpirationDatedigits[8] 
38ExpirationTimestring[11] 
40SpecialInstructionsstring[0,256] 
42ActionAdviseddigits[2]01 = Object Kill +
02 = Object Replace +
03 = Object Append +
04 = Object Reference
45ReferenceServicestring[0,10]+ 
47ReferenceDatedigits[8]+ 
50ReferenceNumberdigits[8]+ 
55DateCreateddigits[8] 
60TimeCreatedstring[11] 
62DigitalCreationDatedigits[8] 
63DigitalCreationTimestring[11] 
65OriginatingProgramstring[0,32] 
70ProgramVersionstring[0,10] 
75ObjectCyclestring[1]'a' = Morning +
'b' = Both Morning and Evening +
'p' = Evening
80By-linestring[0,32]+ 
85By-lineTitlestring[0,32]+ 
90Citystring[0,32] 
92Sub-locationstring[0,32] 
95Province-Statestring[0,32] 
100Country-PrimaryLocationCodestring[3] 
101Country-PrimaryLocationNamestring[0,64] 
103OriginalTransmissionReferencestring[0,32](now used as a job identifier)
105Headlinestring[0,256] 
110Creditstring[0,32] 
115Sourcestring[0,32] 
116CopyrightNoticestring[0,128] 
118Contactstring[0,128]+ 
120Caption-Abstractstring[0,2000] 
121LocalCaptionstring[0,256](I haven't found a reference for the format of tags 121, 184-188 and +225-232, so I have just make them writable as strings with +reasonable length. Beware that if this is wrong, other utilities +may not be able to read these tags as written by ExifTool)
122Writer-Editorstring[0,32]+ 
125RasterizedCaptionundef[7360] 
130ImageTypestring[2] 
131ImageOrientationstring[1]'L' = Landscape +
'P' = Portrait +
'S' = Square
135LanguageIdentifierstring[2,3] 
150AudioTypestring[2] +
'0T' = Text Only +
'1A' = Mono Actuality +
'1C' = Mono Question and Answer Session +
'1M' = Mono Music +
'1Q' = Mono Response to a Question +
'1R' = Mono Raw Sound +
'1S' = Mono Scener +
'1V' = Mono Voicer +
'1W' = Mono Wrap +
'2A' = Stereo Actuality +
'2C' = Stereo Question and Answer Session +
'2M' = Stereo Music +
'2Q' = Stereo Response to a Question +
'2R' = Stereo Raw Sound +
'2S' = Stereo Scener +
'2V' = Stereo Voicer +
'2W' = Stereo Wrap
+
151AudioSamplingRatedigits[6] 
152AudioSamplingResolutiondigits[2] 
153AudioDurationdigits[6] 
154AudioOutcuestring[0,64] 
184JobIDstring[0,64] 
185MasterDocumentIDstring[0,256] 
186ShortDocumentIDstring[0,64] 
187UniqueDocumentIDstring[0,128] 
188OwnerIDstring[0,128] 
200ObjectPreviewFileFormatint16u0 = No ObjectData +
1 = IPTC-NAA Digital Newsphoto Parameter Record +
2 = IPTC7901 Recommended Message Format +
3 = Tagged Image File Format (Adobe/Aldus Image data) +
4 = Illustrator (Adobe Graphics data) +
5 = AppleSingle (Apple Computer Inc) +
6 = NAA 89-3 (ANPA 1312) +
7 = MacBinary II +
8 = IPTC Unstructured Character Oriented File Format (UCOFF) +
9 = United Press International ANPA 1312 variant +
10 = United Press International Down-Load Message +
11 = JPEG File Interchange (JFIF) +
12 = Photo-CD Image-Pac (Eastman Kodak) +
13 = Bit Mapped Graphics File [.BMP] (Microsoft) +
14 = Digital Audio File [.WAV] (Microsoft & Creative Labs) +
15 = Audio plus Moving Video [.AVI] (Microsoft) +
16 = PC DOS/Windows Executable Files [.COM][.EXE] +
17 = Compressed Binary File [.ZIP] (PKWare Inc) +
18 = Audio Interchange File Format AIFF (Apple Computer Inc) +
19 = RIFF Wave (Microsoft Corporation) +
20 = Freehand (Macromedia/Aldus) +
21 = Hypertext Markup Language [.HTML] (The Internet Society) +
22 = MPEG 2 Audio Layer 2 (Musicom), ISO/IEC +
23 = MPEG 2 Audio Layer 3, ISO/IEC +
24 = Portable Document File [.PDF] Adobe +
25 = News Industry Text Format (NITF) +
26 = Tape Archive [.TAR] +
27 = Tidningarnas Telegrambyra NITF version (TTNITF DTD) +
28 = Ritzaus Bureau NITF version (RBNITF DTD) +
29 = Corel Draw [.CDR]
201ObjectPreviewFileVersionint16u 
202ObjectPreviewDataundef[0,256000] 
221Prefsstring[0,64](PhotoMechanic preferences)
225ClassifyStatestring[0,64] 
228SimilarityIndexstring[0,32] 
230DocumentNotesstring[0,1024] 
231DocumentHistorystring[0,256] 
232ExifCameraInfostring[0,4096] 
255CatalogSetsstring[0,256]+(written by iView MediaPro)
+ +

IPTC NewsPhoto Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0NewsPhotoVersionint16u: 
10IPTCPictureNumberstring[16](4 numbers: 1-Manufacturer ID, 2-Equipment ID, 3-Date, 4-Sequence)
20IPTCImageWidthint16u 
30IPTCImageHeightint16u 
40IPTCPixelWidthint16u 
50IPTCPixelHeightint16u 
55SupplementalTypeint8u0 = Main Image +
1 = Reduced Resolution Image +
2 = Logo +
3 = Rasterized Caption
60ColorRepresentationint16u0x0 = No Image, Single Frame +
0x100 = Monochrome, Single Frame +
0x300 = 3 Components, Single Frame +
0x301 = 3 Components, Frame Sequential in Multiple Objects +
0x302 = 3 Components, Frame Sequential in One Object +
0x303 = 3 Components, Line Sequential +
0x304 = 3 Components, Pixel Sequential +
0x305 = 3 Components, Special Interleaving +
0x400 = 4 Components, Single Frame +
0x401 = 4 Components, Frame Sequential in Multiple Objects +
0x402 = 4 Components, Frame Sequential in One Object +
0x403 = 4 Components, Line Sequential +
0x404 = 4 Components, Pixel Sequential +
0x405 = 4 Components, Special Interleaving
64InterchangeColorSpaceint8u +
1 = X,Y,Z CIE +
2 = RGB SMPTE +
3 = Y,U,V (K) (D65) +
4 = RGB Device Dependent +
5 = CMY (K) Device Dependent +
6 = Lab (K) CIE +
7 = YCbCr +
8 = sRGB
+
65ColorSequenceint8u 
66ICC_Profileno 
70ColorCalibrationMatrixno 
80LookupTableno 
84NumIndexEntriesint16u 
85ColorPaletteno 
86IPTCBitsPerSampleint8u 
90SampleStructureint8u0 = OrthogonalConstangSampling +
1 = Orthogonal4-2-2Sampling +
2 = CompressionDependent
100ScanningDirectionint8u + +
0 = L-R, Top-Bottom +
1 = R-L, Top-Bottom +
2 = L-R, Bottom-Top +
3 = R-L, Bottom-Top
  4 = Top-Bottom, L-R +
5 = Bottom-Top, L-R +
6 = Top-Bottom, R-L +
7 = Bottom-Top, R-L
+
102IPTCImageRotationint8u0 = 0 +
1 = 90 +
2 = 180 +
3 = 270
110DataCompressionMethodint32u 
120QuantizationMethodint8u +
0 = Linear Reflectance/Transmittance +
1 = Linear Density +
2 = IPTC Ref B +
3 = Linear Dot Percent +
4 = AP Domestic Analogue +
5 = Compression Method Specific +
6 = Color Space Specific +
7 = Gamma Compensated
+
125EndPointsno 
130ExcursionToleranceint8u0 = Not Allowed +
1 = Allowed
135BitsPerComponentint8u 
140MaximumDensityRangeint16u 
145GammaCompensatedValueint16u 
+ +

IPTC PreObjectData Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
10SizeModeno0 = Size Not Known +
1 = Size Known
20MaxSubfileSizeno 
90ObjectSizeAnnouncedno 
95MaximumObjectSizeno 
+ +

IPTC ObjectData Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
10SubFileno+ 
+ +

IPTC PostObjectData Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
10ConfirmedObjectSizeno 
+ +

IPTC FotoStation Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 2, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/ISO.html b/ExifTool/html/TagNames/ISO.html new file mode 100644 index 0000000..1ec6ca6 --- /dev/null +++ b/ExifTool/html/TagNames/ISO.html @@ -0,0 +1,160 @@ + + + + +ISO Tags + + + +

ISO Tags

+

Tags extracted from ISO 9660 disk images.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000BootRecord---> ISO BootRecord Tags
0x0001PrimaryVolume---> ISO PrimaryVolume Tags
+ +

ISO BootRecord Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
7BootSystemno 
39BootIdentifierno 
+ +

ISO PrimaryVolume Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8Systemno 
40VolumeNameno 
80VolumeBlockCountno 
120VolumeSetDiskCount?no 
124VolumeSetDiskNumber?no 
128VolumeBlockSizeno 
132PathTableSize?no 
140PathTableLocation?no 
174RootDirectoryCreateDateno 
190VolumeSetNameno 
318Publisherno 
446DataPreparerno 
574Softwareno 
702CopyrightFileNameno 
740AbstractFileNameno 
776BibligraphicFileNameno 
813VolumeCreateDateno 
830VolumeModifyDateno 
847VolumeExpirationDateno 
864VolumeEffectiveDateno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Apr 12, 2016 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/ITC.html b/ExifTool/html/TagNames/ITC.html new file mode 100644 index 0000000..00da4e2 --- /dev/null +++ b/ExifTool/html/TagNames/ITC.html @@ -0,0 +1,91 @@ + + + + +ITC Tags + + + +

ITC Tags

+

This information is found in iTunes Cover Flow data files.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'data'ImageDatano(embedded JPEG or PNG image, depending on ImageType)
'itch'Itch---> ITC Header Tags
'item'Item---> ITC Item Tags
+ +

ITC Header Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
16DataTypeno'artw' = Artwork
+ +

ITC Item Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0LibraryIDno 
2TrackIDno 
4DataLocationno'down' = Downloaded Separately +
'locl' = Local Music File
5ImageTypeno 
7ImageWidthno 
8ImageHeightno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Nov 9, 2011 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/InfiRay.html b/ExifTool/html/TagNames/InfiRay.html new file mode 100644 index 0000000..da622b8 --- /dev/null +++ b/ExifTool/html/TagNames/InfiRay.html @@ -0,0 +1,485 @@ + + + + +InfiRay Tags + + + +

InfiRay Version Tags

+

This table lists tags found in the InfiRay APP2 IJPEG version header, found +in JPEGs taken with the P2 Pro camera app.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0IJPEGVersionno 
12IJPEGOrgTypeno 
13IJPEGDispTypeno 
14IJPEGRotateno 
15IJPEGMirrorFlipno 
16ImageColorSwitchableno 
17ThermalColorPaletteno 
32IRDataSizeno 
40IRDataFormatno 
42IRImageWidthno 
44IRImageHeightno 
46IRImageBppno 
48TempDataSizeno 
56TempDataFormatno 
58TempImageWidthno 
60TempImageHeightno 
62TempImageBppno 
64VisibleDataSizeno 
72VisibleDataFormatno 
74VisibleImageWidthno 
76VisibleImageHeightno 
78VisibleImageBppno 
+ +

InfiRay Factory Tags

+

This table lists tags found in the InfiRay APP4 IJPEG camera factory +defaults and calibration data.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0IJPEGTempVersionno 
4FactDefEmissivityno 
5FactDefTauno 
6FactDefTano 
8FactDefTuno 
10FactDefDistno 
12FactDefA0no 
16FactDefB0no 
20FactDefA1no 
24FactDefB1no 
28FactDefP0no 
32FactDefP1no 
36FactDefP2no 
68FactRelSensorTempno 
70FactRelShutterTempno 
72FactRelLensTempno 
100FactStatusGainno 
101FactStatusEnvOKno 
102FactStatusDistOKno 
103FactStatusTempMapno 
+ +

InfiRay Picture Tags

+

This table lists tags found in the InfiRay APP5 IJPEG picture temperature +information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0EnvironmentTempno 
4Distanceno 
8Emissivityno 
12Humidityno 
16ReferenceTempno 
32TempUnitno 
33ShowCenterTempno 
34ShowMaxTempno 
35ShowMinTempno 
36TempMeasureCountno 
+ +

InfiRay MixMode Tags

+

This table lists tags found in the InfiRay APP6 IJPEG visual-infrared mixing +mode section.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0MixModeno 
1FusionIntensityno 
5OffsetAdjustmentno 
9CorrectionAsixno 
+ +

InfiRay OpMode Tags

+

This table lists tags found in the InfiRay APP7 IJPEG camera operation mode +section.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0WorkingModeno 
1IntegralTimeno 
5IntegratTimeHdrno 
9GainStableno 
10TempControlEnableno 
11DeviceTempno 
+ +

InfiRay Isothermal Tags

+

This table lists tags found in the InfiRay APP8 IJPEG picture isothermal +information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0IsothermalMaxno 
4IsothermalMinno 
8ChromaBarMaxno 
12ChromaBarMinno 
+ +

InfiRay Sensor Tags

+

This table lists tags found in the InfiRay APP9 IJPEG sensor information +chunk.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0IRSensorManufacturerno 
64IRSensorNameno 
128IRSensorPartNumberno 
192IRSensorSerialNumberno 
256IRSensorFirmwareno 
320IRSensorApertureno 
324IRFocalLengthno 
384VisibleSensorManufacturerno 
448VisibleSensorNameno 
512VisibleSensorPartNumberno 
576VisibleSensorSerialNumberno 
640VisibleSensorFirmwareno 
704VisibleSensorApertureno 
708VisibleFocalLengthno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 9, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/JFIF.html b/ExifTool/html/TagNames/JFIF.html new file mode 100644 index 0000000..33a6274 --- /dev/null +++ b/ExifTool/html/TagNames/JFIF.html @@ -0,0 +1,89 @@ + + + + +JFIF Tags + + + +

JFIF Tags

+

+The following information is extracted from the JPEG JFIF header. See +https://www.w3.org/Graphics/JPEG/jfif3.pdf for the JFIF 1.02 +specification. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0JFIFVersionno: 
2ResolutionUnitint8u:0 = None +
1 = inches +
2 = cm
3XResolutionint16u: 
5YResolutionint16u: 
7ThumbnailWidthno 
8ThumbnailHeightno 
9ThumbnailTIFFno(raw RGB thumbnail data, extracted as a TIFF image)
+ +

JFIF Extension Tags

+

Thumbnail images extracted from the JFXX segment.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0010ThumbnailImageno(JPEG-format thumbnail image)
0x0011ThumbnailTIFFno(raw palette-color thumbnail data, extracted as a TIFF image)
0x0013ThumbnailTIFFno(raw RGB thumbnail data, extracted as a TIFF image)
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 21, 2017 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/JPEG.html b/ExifTool/html/TagNames/JPEG.html new file mode 100644 index 0000000..7ad546f --- /dev/null +++ b/ExifTool/html/TagNames/JPEG.html @@ -0,0 +1,696 @@ + + + + +JPEG Tags + + + +

JPEG Tags

+

This table lists information extracted by ExifTool from JPEG images. See +https://www.w3.org/Graphics/JPEG/jfif3.pdf for the JPEG specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'APP0'JFIF +
JFXX +
CIFF +
AVI1 +
Ocad
-
-
-
-
-
--> JFIF Tags +
--> JFIF Extension Tags +
--> CanonRaw Tags +
--> JPEG AVI1 Tags +
--> JPEG Ocad Tags
'APP1'EXIF +
ExtendedXMP +
XMP +
QVCI +
FLIR +
RawThermalImage
-
-
-
-
-
no
--> EXIF Tags +
--> XMP Tags +
--> XMP Tags +
--> Casio QVCI Tags +
--> FLIR FFF Tags +
(thermal image from Parrot Bebop-Pro Thermal drone)
'APP2'ICC_Profile +
FPXR +
MPF +
InfiRayVersion +
PreviewImage
-
-
-
-
no
--> ICC_Profile Tags +
--> FlashPix Tags +
--> MPF Tags +
--> InfiRay Version Tags +
(Samsung APP2 preview image)
'APP3'Meta +
Stim +
JPS +
ThermalData +
ImagingData +
PreviewImage
-
-
-
no
no
no
--> Kodak Meta Tags +
--> Stim Tags +
--> JPEG JPS Tags +
(DJI raw thermal data) +
(InfiRay IR+thermal+visible data) +
(Samsung/HP preview image)
'APP4'Scalado +
FPXR +
InfiRayFactory +
ThermalParams +
ThermalParams2 +
ThermalParams3 +
PreviewImage
-
-
-
-
-
-
no
--> Scalado Tags +
--> FlashPix Tags +
--> InfiRay Factory Tags +
--> DJI ThermalParams Tags +
--> DJI ThermalParams2 Tags +
--> DJI ThermalParams3 Tags +
(continued from APP3)
'APP5'RMETA +
SamsungUniqueID +
InfiRayPicture +
ThermalCalibration +
PreviewImage
-
-
-
no
no
--> Ricoh RMETA Tags +
--> Samsung APP5 Tags +
--> InfiRay Picture Tags +
(DJI thermal calibration data) +
(continued from APP4)
'APP6'EPPIM +
NITF +
HP_TDHD +
GoPro +
InfiRayMixMode +
DJI_DTAT
-
-
-
-
-
no
--> JPEG EPPIM Tags +
--> JPEG NITF Tags +
--> HP TDHD Tags +
--> GoPro GPMF Tags +
--> InfiRay MixMode Tags +
(DJI Thermal Analysis Tool record)
'APP7'Pentax +
Huawei +
Qualcomm +
InfiRayOpMode +
DJI-DBG
-
-
-
-
-
--> Pentax Tags +
--> Unknown Tags +
--> Qualcomm Tags +
--> InfiRay OpMode Tags +
--> DJI Info Tags
'APP8'SPIFF +
InfiRayIsothermal
-
-
--> JPEG SPIFF Tags +
--> InfiRay Isothermal Tags
'APP9'MediaJukebox +
InfiRaySensor
-
-
--> JPEG MediaJukebox Tags +
--> InfiRay Sensor Tags
'APP10'Commentno(PhotoStudio Unicode comment)
'APP11'JPEG-HDR +
JUMBF
-
-
--> JPEG HDR Tags +
--> Jpeg2000 Tags
'APP12'PictureInfo +
Ducky
-
-
--> APP12 PictureInfo Tags +
--> APP12 Ducky Tags
'APP13'Photoshop +
Adobe_CM
-
-
--> Photoshop Tags +
--> JPEG AdobeCM Tags
'APP14'Adobeyes--> JPEG Adobe Tags
'APP15'GraphicConverter---> JPEG GraphConv Tags
'COM'Commentyes 
'DQT'DefineQuantizationTableno(used to calculate the Extra JPEGDigest tag value)
'SOF'StartOfFrame---> JPEG SOF Tags
'Trailer'AFCP +
CanonVRD +
FotoStation +
PhotoMechanic +
MIE +
Samsung +
EmbeddedVideo +
Insta360 +
NikonApp +
PreviewImage
-
-
-
-
-
-
no
no
no
yes
--> AFCP Tags +
--> CanonVRD Tags +
--> FotoStation Tags +
--> PhotoMechanic Tags +
--> MIE Tags +
--> Samsung Trailer Tags +
(extracted only when ExtractEmbedded option is used) +
(contains editing information in XMP format)
+ +

JPEG AVI1 Tags

+

This information may be found in APP0 of JPEG image data from AVI videos.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0InterleavedFieldno0 = Not Interleaved +
1 = Odd +
2 = Even
+ +

JPEG Ocad Tags

+

Tags extracted from the JPEG APP0 "Ocad" segment (found in Photobucket +images).

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Rev'OcadRevisionno 
+ +

JPEG JPS Tags

+

Tags found in JPEG Stereo (JPS) images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
10JPSSeparationno(stereo only)
11JPSFlagsnoBit 0 = Half height +
Bit 1 = Half width +
Bit 2 = Left field first
12JPSLayoutno(mono) +
0 = Both Eyes +
1 = Left Eye +
2 = Right Eye +
(stereo) +
1 = Interleaved +
2 = Side By Side +
3 = Over Under +
4 = Anaglyph
13JPSTypeno0 = Mono +
1 = Stereo
16JPSCommentno 
+ +

JPEG EPPIM Tags

+

APP6 is used in by the Toshiba PDR-M700 to store a TIFF structure containing +PrintIM information.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0xc4a5PrintIM---> PrintIM Tags
+ +

JPEG NITF Tags

+

Information in APP6 used by the National Imagery Transmission Format. See +http://www.gwg.nga.mil/ntb/baseline/docs/n010697/bwcguide25aug98.pdf for +the official specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0NITFVersionno 
2ImageFormatno'B' = IMode B
3BlocksPerRowno 
5BlocksPerColumnno 
7ImageColorno0 = Monochrome
8BitDepthno 
9ImageClassno0 = General Purpose +
4 = Tactical Imagery
10JPEGProcessno1 = Baseline sequential DCT, Huffman coding, 8-bit samples +
4 = Extended sequential DCT, Huffman coding, 12-bit samples
11Qualityno 
12StreamColorno0 = Monochrome
13StreamBitDepthno 
14Flagsno 
+ +

JPEG SPIFF Tags

+

This information is found in APP8 of SPIFF-style JPEG images (the "official" +yet rarely used JPEG file format standard: Still Picture Interchange File +Format). See http://www.jpeg.org/public/spiff.pdf for the official +specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SPIFFVersionno 
2ProfileIDno0 = Not Specified +
1 = Continuous-tone Base +
2 = Continuous-tone Progressive +
3 = Bi-level Facsimile +
4 = Continuous-tone Facsimile
3ColorComponentsno 
6ImageHeightno(at index 4 in specification, but there are 2 extra bytes here in my only +SPIFF sample, version 1.2)
10ImageWidthno 
14ColorSpaceno +
0 = Bi-level +
1 = YCbCr, ITU-R BT 709, video +
2 = No color space specified +
3 = YCbCr, ITU-R BT 601-1, RGB +
4 = YCbCr, ITU-R BT 601-1, video +
8 = Gray-scale +
9 = PhotoYCC +
10 = RGB +
11 = CMY +
12 = CMYK +
13 = YCCK +
14 = CIELab
+
15BitsPerSampleno 
16Compressionno +
0 = Uncompressed, interleaved, 8 bits per sample +
1 = Modified Huffman +
2 = Modified READ +
3 = Modified Modified READ +
4 = JBIG +
5 = JPEG
+
17ResolutionUnitno0 = None +
1 = inches +
2 = cm
18YResolutionno 
22XResolutionno 
+ +

JPEG MediaJukebox Tags

+

Tags found in the XML metadata of the APP9 "Media Jukebox" segment.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Albumno 
Captionno 
Dateno 
Keywordsno 
Nameno 
Peopleno 
Placesno 
Tool_Nameno 
Tool_Versionno 
+ +

JPEG HDR Tags

+

Information extracted from APP11 of a JPEG-HDR image.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'RatioImage'RatioImageno(the embedded JPEG-compressed ratio image)
'alp'Alphano 
'bet'Betano 
'cor'CorrectionMethodno 
'ln0'Ln0no 
'ln1'Ln1no 
's2n'S2nno 
'ver'JPEG-HDRVersionno 
+ +

JPEG AdobeCM Tags

+

The APP13 "Adobe_CM" segment presumably contains color management +information, but the meaning of the data is currently unknown. If anyone +has an idea about what this means, please let me know.

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
0AdobeCMTypeno 
+ +

JPEG Adobe Tags

+

The APP14 "Adobe" segment stores image encoding information for DCT filters. +This segment may be copied or deleted as a block using the Extra "Adobe" +tag, but note that it is not deleted by default when deleting all metadata +because it may affect the appearance of the image.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0DCTEncodeVersionno 
1APP14Flags0no0x0 = (none) +
Bit 15 = Encoded with Blend=1 downsampling
2APP14Flags1no0x0 = (none)
3ColorTransformno0 = Unknown (RGB or CMYK) +
1 = YCbCr +
2 = YCCK
+ +

JPEG GraphConv Tags

+

APP15 is used by GraphicConverter to store JPEG quality.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Q'Qualityno 
+ +

JPEG SOF Tags

+

This information is extracted from the JPEG Start Of Frame segment.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
BitsPerSampleno 
ColorComponentsno 
EncodingProcessno0x0 = Baseline DCT, Huffman coding +
0x1 = Extended sequential DCT, Huffman coding +
0x2 = Progressive DCT, Huffman coding +
0x3 = Lossless, Huffman coding +
0x5 = Sequential DCT, differential Huffman coding +
0x6 = Progressive DCT, differential Huffman coding +
0x7 = Lossless, Differential Huffman coding +
0x9 = Extended sequential DCT, arithmetic coding +
0xa = Progressive DCT, arithmetic coding +
0xb = Lossless, arithmetic coding +
0xd = Sequential DCT, differential arithmetic coding +
0xe = Progressive DCT, differential arithmetic coding +
0xf = Lossless, differential arithmetic coding
ImageHeightno 
ImageWidthno 
YCbCrSubSamplingno(calculated from components table) +
'1 1' = YCbCr4:4:4 (1 1) +
'1 2' = YCbCr4:4:0 (1 2) +
'1 4' = YCbCr4:4:1 (1 4) +
'2 1' = YCbCr4:2:2 (2 1) +
'2 2' = YCbCr4:2:0 (2 2) +
'2 4' = YCbCr4:2:1 (2 4) +
'4 1' = YCbCr4:1:1 (4 1) +
'4 2' = YCbCr4:1:0 (4 2)
+
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 15, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/JSON.html b/ExifTool/html/TagNames/JSON.html new file mode 100644 index 0000000..79b9433 --- /dev/null +++ b/ExifTool/html/TagNames/JSON.html @@ -0,0 +1,58 @@ + + + + +JSON Tags + + + +

JSON Tags

+

Other than a few tags in the table below, JSON tags have not been +pre-defined. However, ExifTool will read any existing tags from basic +JSON-formatted files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ON1_SettingsData---> PLIST Tags
ON1_SettingsMetadataCreatedno 
ON1_SettingsMetadataModifiedno 
ON1_SettingsMetadataNameno 
ON1_SettingsMetadataPluginIDno 
ON1_SettingsMetadataTimestampno 
ON1_SettingsMetadataUsageno 
ON1_SettingsMetadataVisibleToUserno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 29, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/JVC.html b/ExifTool/html/TagNames/JVC.html new file mode 100644 index 0000000..95a7a6c --- /dev/null +++ b/ExifTool/html/TagNames/JVC.html @@ -0,0 +1,57 @@ + + + + +JVC Tags + + + +

JVC Tags

+

JVC EXIF maker note tags.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0002CPUVersionsno 
0x0003Qualityno0 = Low +
1 = Normal +
2 = Fine
+ +

JVC Text Tags

+

JVC/Victor text-based maker note tags.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'QTY'Qualityno'FINE' = Fine +
'STD' = Normal +
'STND' = Normal
'VER'MakerNoteVersionno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Dec 22, 2005 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Jpeg2000.html b/ExifTool/html/TagNames/Jpeg2000.html new file mode 100644 index 0000000..19d5fde --- /dev/null +++ b/ExifTool/html/TagNames/Jpeg2000.html @@ -0,0 +1,598 @@ + + + + +Jpeg2000 Tags + + + +

Jpeg2000 Tags

+

The tags below are found in JPEG 2000 images and the JUMBF metadata in JPEG +images, but not all of these are extracted. Note that ExifTool currently +writes only EXIF, IPTC and XMP tags in Jpeg2000 images, and EXIF and XMP in +JXL images. ExifTool will read/write Brotli-compressed EXIF and XMP in JXL +images, but the API Compress option must be set to create new EXIF and XMP +in compressed format.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Exif'EXIF---> EXIF Tags
'asoc'Association---> Jpeg2000 Tags
'bfdb'BinaryDataTypeno(JUMBF, MIME type and optional file name)
'bfil'BinaryFilterno 
'bidb'BinaryDatano(JUMBF)
'bpcc'BitsPerComponentno 
'brob'BrotliXMP +
BrotliEXIF +
BrotliJUMB
-
-
-
--> XMP Tags +
--> EXIF Tags +
--> Jpeg2000 Tags
'c2sh'C2PASaltHashno 
'cbor'CBORData---> CBOR Tags
'cdef'ComponentDefinitionno 
'cgrp'ColorGroupno 
'chck'DigitalSignatureno 
'cmap'ComponentMappingno 
'colr'ColorSpecification---> Jpeg2000 ColorSpec Tags
'comp'Compositionno 
'copt'CompositionOptionsno 
'cref'Cross-Referenceno 
'creg'CodestreamRegistrationno 
'drep'DesiredReproductionsno 
'dtbl'DataReferenceno 
'flst'FragmentListno 
'free'Freeno 
'ftbl'FragmentTableno 
'ftyp'FileType---> Jpeg2000 FileType Tags
'gtso'GraphicsTechnologyStandardOutputno 
'hrgm'GainMapImageno 
'ihdr'ImageHeader---> Jpeg2000 ImageHeader Tags
'inst'InstructionSetno 
'jP 'JP2Signatureno 
'jp2c'ContiguousCodestream +
PreviewImage
no
no
 
'jp2h'JP2Header---> Jpeg2000 Tags
'jp2i'IntellectualProperty---> XMP Tags
'jpch'CodestreamHeader---> Jpeg2000 Tags
'jplh'CompositingLayerHeader---> Jpeg2000 Tags
'json'JSONData---> JSON Tags +
(by default, data in this tag is parsed using the ExifTool JSON module to to +allow individual tags to be accessed when reading, but it may also be +extracted as a block via the "JSONData" tag or by setting the API +BlockExtract option)
'jumb'JUMBFBox---> Jpeg2000 Tags
'jumd'JUMBFDescr---> Jpeg2000 JUMD Tags
'jxlc'JXLCodestreamno(Codestream in JPEG XL image. Currently processed only to determine +ImageSize)
'jxlp'PartialJXLCodestreamno(Partial codestreams in JPEG XL image. Currently processed only to determine +ImageSize)
'lbl 'Labelno 
'mdat'MediaDatano 
'mp7b'MPEG7Binaryno 
'nlst'NumberListno 
'opct'Opacityno 
'pclr'Paletteno 
'prfl'Profileno 
'res 'Resolution---> Jpeg2000 Tags
'resc'CaptureResolution---> Jpeg2000 CaptureResolution Tags
'resd'DisplayResolution---> Jpeg2000 DisplayResolution Tags
'roid'ROIDescriptionno 
'rreq'ReaderRequirementsno 
'uinf'UUIDInfo---> Jpeg2000 Tags
'ulst'UUIDListno 
'url 'URLno 
'uuid'UUID-EXIF +
UUID-EXIF2 +
UUID-EXIF_bad +
UUID-IPTC +
UUID-IPTC2 +
UUID-XMP +
UUID-GeoJP2 +
UUID-Photoshop +
UUID-Signature +
UUID-C2PAClaimSignature +
UUID-Unknown
-
-
-
-
-
-
-
-
no
-
no
--> EXIF Tags +
--> EXIF Tags +
--> EXIF Tags +
--> IPTC Tags +
--> IPTC Tags +
--> XMP Tags +
--> EXIF Tags +
--> Photoshop Tags +
--> CBOR Tags
'xml 'XML +
XMP
undef!+
-
(by default, the XML data in this tag is parsed using the ExifTool XMP module +to to allow individual tags to be accessed when reading, but it may also be +extracted as a block via the "XML" tag, which is also how this tag is +written and copied. It may also be extracted as a block by setting the API +BlockExtract option. This is a List-type tag because multiple XML blocks +may exist) +
--> XMP XML Tags +
(used for XMP in JPEG XL files) +
--> XMP Tags
+ +

Jpeg2000 ColorSpec Tags

+

The table below contains tags in the color specification (colr) box. This +box may be rewritten by writing either ICC_Profile, ColorSpace or +ColorSpecData. When writing, any existing colr boxes are replaced with the +newly created colr box.

+ +

NOTE: Care must be taken when writing this color specification because +writing a specification that is incompatible with the image data may make +the image undisplayable.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ColorSpecMethodint8s!(default for writing is 2 when writing ICC_Profile, 1 when writing +ColorSpace, or 4 when writing ColorSpecData) +
1 = Enumerated +
2 = Restricted ICC +
3 = Any ICC +
4 = Vendor Color
1ColorSpecPrecedenceint8s!(default for writing is 0)
2ColorSpecApproximationint8s!(default for writing is 0) +
0 = Not Specified +
1 = Accurate +
2 = Exceptional Quality +
3 = Reasonable Quality +
4 = Poor Quality
3ICC_Profile +
ColorSpace +
ColorSpecData
-
int32u!
undef!
--> ICC_Profile Tags +
+ +
0 = Bi-level +
1 = YCbCr(1) +
3 = YCbCr(2) +
4 = YCbCr(3) +
9 = PhotoYCC +
11 = CMY +
12 = CMYK +
13 = YCCK +
14 = CIELab +
15 = Bi-level(2)
  16 = sRGB +
17 = Grayscale +
18 = sYCC +
19 = CIEJab +
20 = e-sRGB +
21 = ROMM-RGB +
22 = YPbPr(1125/60) +
23 = YPbPr(1250/50) +
24 = e-sYCC
+
+ +

Jpeg2000 FileType Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0MajorBrandno'jp2 ' = JPEG 2000 Image (.JP2) +
'jpm ' = JPEG 2000 Compound Image (.JPM) +
'jpx ' = JPEG 2000 with extensions (.JPX) +
'jxl ' = JPEG XL Image (.JXL)
1MinorVersionno 
2CompatibleBrandsno 
+ +

Jpeg2000 ImageHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ImageHeightno 
4ImageWidthno 
8NumberOfComponentsno 
10BitsPerComponentno 
11Compressionno +
0 = Uncompressed +
1 = Modified Huffman +
2 = Modified READ +
3 = Modified Modified READ +
4 = JBIG +
5 = JPEG +
6 = JPEG-LS +
7 = JPEG 2000 +
8 = JBIG2
+
+ +

Jpeg2000 JUMD Tags

+

Information extracted from the JUMBF description box.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'id'JUMDIDno 
'label'JUMDLabelno 
'sig'JUMDSignatureno 
'toggles'JUMDToggles?noBit 0 = Requestable +
Bit 1 = Label +
Bit 2 = ID +
Bit 3 = Signature
'type'JUMDTypeno 
+ +

Jpeg2000 CaptureResolution Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0CaptureYResolutionno 
4CaptureXResolutionno 
8CaptureYResolutionUnitno--> Jpeg2000 ResolutionUnit Values
9CaptureXResolutionUnitno--> Jpeg2000 ResolutionUnit Values
+ +

Jpeg2000 ResolutionUnit Values

+
+
+ + + + + + + + + + + + + + +
ValueResolutionUnitValueResolutionUnitValueResolutionUnit
-3= km1= 10 cm5= 0.01 mm
-2= 100 m2= cm6= um
-1= 10 m3= mm  
0= m4= 0.1 mm  
+ +

Jpeg2000 DisplayResolution Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0DisplayYResolutionno 
4DisplayXResolutionno 
8DisplayYResolutionUnitno--> Jpeg2000 ResolutionUnit Values
9DisplayXResolutionUnitno--> Jpeg2000 ResolutionUnit Values
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 28, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Kodak.html b/ExifTool/html/TagNames/Kodak.html new file mode 100644 index 0000000..787f1fb --- /dev/null +++ b/ExifTool/html/TagNames/Kodak.html @@ -0,0 +1,5735 @@ + + + + +Kodak Tags + + + +

Kodak Tags

+

+Many Kodak models don't store the maker notes in standard IFD format, and +these formats vary with different models. Some information has been +decoded, but much of the Kodak information remains unknown. +

+

The table below contains the most common set of Kodak tags. The following +Kodak camera models have been tested and found to use these tags: C360, +C663, C875, CX6330, CX6445, CX7330, CX7430, CX7525, CX7530, DC4800, DC4900, +DX3500, DX3600, DX3900, DX4330, DX4530, DX4900, DX6340, DX6440, DX6490, +DX7440, DX7590, DX7630, EasyShare-One, LS420, LS443, LS633, LS743, LS753, +V530, V550, V570, V603, V610, V705, Z650, Z700, Z710, Z730, Z740, Z760 and +Z7590.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0KodakModelstring[8] 
9Qualityint8u1 = Fine +
2 = Normal
10BurstModeint8u0 = Off +
1 = On
12KodakImageWidthint16u 
14KodakImageHeightint16u 
16YearCreatedint16u 
18MonthDayCreatedint8u[2] 
20TimeCreatedint8u[4] 
24BurstMode2?int16u 
27ShutterModeint8u0 = Auto +
8 = Aperture Priority +
32 = Manual?
28MeteringModeint8u0 = Multi-segment +
1 = Center-weighted average +
2 = Spot
29SequenceNumberint8u 
30FNumberint16u 
32ExposureTimeint32u 
36ExposureCompensationint16s 
38VariousModes?int16u 
40Distance1?int32u 
44Distance2?int32u 
48Distance3?int32u 
52Distance4?int32u 
56FocusModeint8u0 = Normal +
2 = Macro
58VariousModes2?int16u 
60PanoramaMode?int16u 
62SubjectDistance?int16u 
64WhiteBalanceint8u0 = Auto +
1 = Flash? +
2 = Tungsten +
3 = Daylight
92FlashModeint8u + +
0x0 = Auto +
0x1 = Fill Flash +
0x2 = Off +
0x3 = Red-Eye
  0x10 = Fill Flash +
0x20 = Off +
0x40 = Red-Eye?
+
93FlashFiredint8u0 = No +
1 = Yes
94ISOSettingint16u 
96ISOint16u 
98TotalZoomint16u 
100DateTimeStampint16u 
102ColorModeint16u + +
0x1 = B&W +
0x2 = Sepia +
0x3 = B&W Yellow Filter +
0x4 = B&W Red Filter +
0x20 = Saturated Color
  0x40 = Neutral Color +
0x100 = Saturated Color +
0x200 = Neutral Color +
0x2000 = B&W +
0x4000 = Sepia
+
104DigitalZoomint16u 
107Sharpnessint8s0 = Normal
+ +

Kodak IFD Tags

+

These tags are found in a separate IFD of JPEG, TIFF, DCR and KDC images +from some older Kodak models such as the DC50, DC120, DCS760C, DCS Pro 14N, +14nx, SLR/n, Pro Back and Canon EOS D2000.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000KodakVersionint8u[4] 
0x0001UnknownEV?rational64u 
0x0003ExposureValuerational64u 
0x03e9OriginalFileNamestring 
0x03eaKodakTagint32u 
0x03ebSensorLeftBorderint16u 
0x03ecSensorTopBorderint16u 
0x03edSensorImageWidthint16u 
0x03eeSensorImageHeightint16u 
0x03efBlackLevelTopint16u 
0x03f0BlackLevelBottomint16u 
0x03f1TextualInfo---> Kodak TextualInfo Tags
0x03f2FlashMode?int16u 
0x03f3FlashCompensationrational64s 
0x03f4WindMode?int16u 
0x03f5FocusMode?int16u 
0x03f8MinAperturerational64u 
0x03f9MaxAperturerational64u 
0x03faWhiteBalanceMode?int16u 
0x03fbWhiteBalanceDetected?int16u 
0x03fcWhiteBalanceint16u 
0x03fdProcessing +
ProcessingParameters
-
no
--> Kodak Processing Tags
0x03feImageAbsoluteXint16s 
0x03ffImageAbsoluteYint16s 
0x0400ApplicationKeyStringstring 
0x0401Timestring 
0x0402GPSStringstring 
0x0403EventLogCapture?no 
0x0404ComponentTable?no 
0x0405CustomIlluminant?int16u 
0x0406CameraTemperature +
CameraTemperature
rational64s
no
 
0x0407AdapterVoltagerational64u 
0x0408BatteryVoltagerational64u 
0x0409DacVoltagesno 
0x040aIlluminantDetectorData?no 
0x040bPixelClockFrequencyint32u 
0x040cCenterPixelint16u[3] 
0x040dBurstCountint16u 
0x040eBlackLevelRoughint16u 
0x040fOffsetMapHorizontal?no 
0x0410OffsetMapVertical?no 
0x0411Histogram?no 
0x0412VerticalClockOverlapsint16u[2] 
0x0413SensorTemperatureno 
0x0414XilinxVersionstring 
0x0415FirmwareVersionint32u 
0x0416BlackLevelRoughAfterint16u 
0x0417BrightRowsTopno 
0x0418EventLogProcessno 
0x0419DacVoltagesFlushno 
0x041aFlashUsedno 
0x041bFlashTypeno 
0x041cSelfTimerno 
0x041dAFModeno 
0x041eLensTypeno 
0x041fImageCropXint16s 
0x0420ImageCropYint16s 
0x0421AdjustedTbnImageWidthno 
0x0422AdjustedTbnImageHeightno 
0x0423IntegrationTimeint32u 
0x0424BracketingModeno 
0x0425BracketingStepno 
0x0426BracketingCounterno 
0x042eHuffmanTableLengthno 
0x042fHuffmanTableValueno 
0x0438MainBoardVersionint32u 
0x0439ImagerBoardVersionint32u 
0x044cFocusEdgeMapno 
0x05e6IdleTimingno 
0x05e7FlushTimingno 
0x05e8IntegrateTimingno 
0x05e9RegisterReadTimingno 
0x05eaFirstLineTransferTimingno 
0x05ebShiftTimingno 
0x05ecNormalLineTransferTimingno 
0x05edTestTransferTimingno 
0x05faMinimumFlushRowsno 
0x05fdImagerPowerOnDelayMsecint32u 
0x05feImagerInitialTimingCodeno 
0x05ffImagerLogicProgramno 
0x0600ImagerBiasSettlingDelayMsecint32u 
0x0604IdleSequenceno 
0x0605FirstFlushSequenceno 
0x0606FinalFlushSequenceno 
0x0607SampleBlackSequenceno 
0x0608TransferSequenceno 
0x060eDacCountsPerVoltno 
0x060fBlackDacChannelno 
0x0610BlackAdCountsPerDacVoltno 
0x0611BlackTargetno 
0x0612BlackDacSettlingMsecno 
0x07d0StandardMatrixDaylightrational64s[9] 
0x07d1StandardMatrixTungstenrational64s[9] 
0x07d2StandardMatrixFluorescentrational64s[9] 
0x07d3StandardMatrixFlashrational64s[9] 
0x07d4StandardMatrixCustomrational64s[9] 
0x07daDeviantMatrixDaylightrational64s[9] 
0x07dbDeviantMatrixTungstenrational64s[9] 
0x07dcDeviantMatrixFluorescentrational64s[9] 
0x07ddDeviantMatrixFlashrational64s[9] 
0x07deDeviantMatrixCustomrational64s[9] 
0x07e4UniqueMatrixDaylightrational64s[9] 
0x07e5UniqueMatrixTungstenrational64s[9] 
0x07e6UniqueMatrixFluorescentrational64s[9] 
0x07e7UniqueMatrixFlashrational64s[9] 
0x07e8UniqueMatrixCustomrational64s[9] 
0x07e9UniqueMatrixAutorational64s[9] 
0x0834StandardWhiteDaylightrational64s[3] 
0x0835StandardWhiteTungstenrational64s[3] 
0x0836StandardWhiteFluorescentrational64s[3] 
0x0837StandardWhiteFlashrational64s[3] 
0x0838StandardWhiteCustomrational64s[3] 
0x083eDeviantWhiteDaylightrational64s[3] 
0x083fDeviantWhiteTungstenrational64s[3] 
0x0840DeviantWhiteFluorescentrational64s[3] 
0x0841DeviantWhiteFlashrational64s[3] 
0x0842DeviantWhiteCustomrational64s[3] 
0x0846ColorTemperatureint16u 
0x0847WB_RGBLevelsAsShotno 
0x0848WB_RGBLevelsDaylightno 
0x0849WB_RGBLevelsTungstenno 
0x084aWB_RGBLevelsFluorescentno 
0x084bWB_RGBLevelsFlashno 
0x084cWB_RGBLevelsCustomno 
0x084dWB_RGBLevelsAutono 
0x0852WB_RGBMulDaylightrational64u[3] 
0x0853WB_RGBMulTungstenrational64u[3] 
0x0854WB_RGBMulFluorescentrational64u[3] 
0x0855WB_RGBMulFlashrational64u[3] 
0x085cWB_RGBCoeffsDaylightno 
0x085dWB_RGBCoeffsTungstenno 
0x085eWB_RGBCoeffsFluorescentno 
0x085fWB_RGBCoeffsFlashno 
0x0898ExposureGainDaylightrational64s 
0x0899ExposureGainTungstenrational64s 
0x089aExposureGainFluorescentrational64s 
0x089bExposureGainFlashrational64s 
0x089cExposureGainCustomrational64s 
0x089dAnalogISOTablerational64u[3] 
0x089eAnalogCaptureISOint32u 
0x089fISOCalibrationGainrational64u 
0x08a0ISOCalibrationGainTableno 
0x08a1ExposureHeadroomFactorno 
0x08abLinearitySplineTagsno 
0x08fcMonitorMatrixrational64s[9] 
0x08fdTonScaleTableno 
0x08feGammarational64u 
0x08ffLogLinTableno 
0x0900LinLogTableno 
0x0901GammaTableno 
0x0902LogScalerational64u 
0x0903BaseISOrational64u 
0x0904LinLogCoringint16u 
0x0905PatternGainConversionTableno 
0x0906DefectCountno 
0x0907DefectListno 
0x0908DefectListPackedno 
0x0909ImageSpaceint16u 
0x090aThumbnailCompressionTableno 
0x090bThumbnailExpansionTableno 
0x090cImageCompressionTableno 
0x090dImageExpansionTableno 
0x090eEighteenPercentPointno 
0x090fDefectIsoCodeint16u 
0x0910BaseISODaylightrational64u 
0x0911BaseISOTungstenrational64u 
0x0912BaseISOFluorescentrational64u 
0x0913BaseISOFlashrational64u 
0x091aMatrixSelectThresholdint16s 
0x091bMatrixSelectKrational64u 
0x091cIlluminantDetectTableno 
0x091dRGTableno 
0x091eMatrixSelectThreshold1int16s 
0x091fMatrixSelectThreshold2int16s 
0x0924PortraitMatrixno 
0x0925PortraitToneScaleTableno 
0x092eEnableSharpeningint16u 
0x092fSharpeningKernelint16s[25] 
0x0930EdgeMapSlopeint16u 
0x0931EdgeMapX1int16u 
0x0932EdgeMapX2int16u 
0x0933KernelDenominatorsint16u[3] 
0x0934EdgeMapX3int16u 
0x0935EdgeMapX4int16u 
0x0936SharpenForThumbnailno 
0x0937EdgeSplineno 
0x0938DownSampleBy2Horno 
0x0939DownSampleBy2Verno 
0x093aDownSampleBy4Horno 
0x093bDownSampleBy4Verno 
0x093cDownSampleBy3Horno 
0x093dDownSampleBy3Verno 
0x093eDownSampleBy6Horno 
0x093fDownSampleBy6Verno 
0x0940DownSampleBy2Hor3MPdcrno 
0x0941DownSampleBy2Ver3MPdcrno 
0x0942ThumbnailResizeRationo 
0x0943AtCaptureUserCropint32u[4] 
0x0944ImageResolutionint32u 
0x0945ImageResolutionJpgint32u 
0x094cUSMParametersLowno 
0x094dUSMParametersMedno 
0x094eUSMParametersHighno 
0x094fUSMParametersHostno 
0x0950EdgeSplineLowno 
0x0951EdgeSplineMedno 
0x0952EdgeSplineHighno 
0x0953USMParametersHost6MPno 
0x0954USMParametersHost3MPno 
0x0960PatternImagerWidthint16u 
0x0961PatternImagerHeightint16u 
0x0962PatternAreaWidthint16u 
0x0963PatternAreaHeightint16u 
0x0964PatternCorrectionGainsno 
0x0965PatternCorrectionOffsetsno 
0x0966PatternXint16u 
0x0967PatternYint16u 
0x0968PatternCorrectionFactorsno 
0x0969PatternCorrectionFactorScaleint16u 
0x096aPatternCropRows1int16u 
0x096bPatternCropRows2int16u 
0x096cPatternCropCols1int16u 
0x096dPatternCropCols2int16u 
0x096ePixelCorrectionGainsno 
0x096fStitchRowsno 
0x0970StitchColumnsno 
0x0971PixelCorrectionScaleint16u 
0x0972PixelCorrectionOffsetint16u 
0x0988LensTableIndexno 
0x0992DiffTileGains602832no 
0x0993DiffTileGains24t852822no 
0x099cTileGainDeterminationTableno 
0x099dNemoBlurKernelno 
0x099eNemoTileSizeno 
0x099fNemoGainFactorsno 
0x09a0NemoDarkLimitno 
0x09a1NemoHighlight12Limitno 
0x09c4ImagerFileProductionLevelint16u 
0x09c5ImagerFileDateCreatedint32u 
0x09c6CalibrationVersionstring 
0x09c7ImagerFileTagsVersionStandardint16u 
0x09c8IFCameraModelstring 
0x09c9CalibrationHistorystring 
0x09caCalibrationLogno 
0x09ceSensorSerialNumberstring 
0x09f6DefectConcealArtCorrectThresno 
0x09f7SglColDCACThres1no 
0x09f8SglColDCACThres2no 
0x09f9SglColDCACTHres3no 
0x0a01DblColDCACThres1no 
0x0a02DblColDCACThres2no 
0x0a0aDefectConcealThresTableno 
0x0a28MonoUniqueMatrixno 
0x0a29MonoMonitorMatrixno 
0x0a2aMonoToneScaleTableno 
0x0a5aOmenInitialScalingno 
0x0a5bOmenInitialRowsno 
0x0a5cOmenInitialColumnsno 
0x0a5dOmenInitialIPFStrengthint32s[4] 
0x0a5eOmenEarlyStrengthint32s[4] 
0x0a5fOmenAutoStrengthint32s[4] 
0x0a60OmenAtCaptureStrengthint32s[4] 
0x0a61OmenAtCaptureModeno 
0x0a62OmenFocalLengthLimitint16s 
0x0a64OmenSurfaceIndexint16s 
0x0a65OmenPercentToRationalLimitsRedno 
0x0a66OmenPercentToRationalLimitsGoRno 
0x0a67OmenPercentToRationalLimitsBlueno 
0x0a68OmenPercentToRationalLimitsGoBno 
0x0a6eOmenEarlyGoBSurfaceno 
0x0a6fOmenEarlyGoBRowsno 
0x0a70OmenEarlyGoBColumnsno 
0x0a73OmenSmoothingKernelno 
0x0a74OmenGradientOffsetno 
0x0a75OmenGradientKernelno 
0x0a76OmenGradientKernelTapsno 
0x0a77OmenRatioClipFactorsno 
0x0a78OmenRatioExclusionFactorsno 
0x0a79OmenGradientExclusionLimitsno 
0x0a7aOmenROICoordinatesno 
0x0a7bOmenROICoefficientsno 
0x0a7cOmenRangeWeightingno 
0x0a7dOmenMeanToStrengthno 
0x0bb8FactoryWhiteGainsDaylightno 
0x0bb9FactoryWhiteOffsetsDaylightno 
0x0bbaDacGainsCoarseno 
0x0bbbDacGainsFineno 
0x0bbcDigitalExposureGainsno 
0x0bbdDigitalExposureBiasesno 
0x0bbeBlackClampno 
0x0bbfChannelCoarseGainAdjustno 
0x0bc0BlackClampOffsetno 
0x0bf4DMPixelThresholdFactorno 
0x0bf5DMWindowThresholdFactorno 
0x0bf6DMTrimFractionno 
0x0bf7DMSmoothRejThreshno 
0x0bf8DMFillRejThreshno 
0x0bf9VMWsizeno 
0x0bfaDMErodeRadiusno 
0x0bfbDMNumPatchesno 
0x0bfcDMNoiseScaleno 
0x0bfeBrightDefectThresholdno 
0x0bffBrightDefectIntegrationMSno 
0x0c00BrightDefectIsoCodeno 
0x0c03TopDarkRow1no 
0x0c04TopDarkRow2no 
0x0c05BottomDarkRow1no 
0x0c06BottomDarkRow2no 
0x0c07LeftDarkCol1no 
0x0c08LeftDarkCol2no 
0x0c09RightDarkCol1no 
0x0c0aRightDarkCol2no 
0x0c0bHMPixThreshno 
0x0c0cHMColThreshno 
0x0c0dHMWsizeno 
0x0c0eHMColRejThreshno 
0x0c0fVMPixThreshno 
0x0c10VMColThreshno 
0x0c11VMNbandsno 
0x0c12VMColDropThreshno 
0x0c13VMPatchResLimitno 
0x0c14MapScaleno 
0x0c1cKlutno 
0x0c1dRimNonlinearityno 
0x0c1eInverseRimNonlinearityno 
0x0c1fRembrandtToneScaleno 
0x0c20RimToNifColorTransformno 
0x0c21RimToNifScaleFactorno 
0x0c22NifNonlinearityno 
0x0c23SBALogTransformno 
0x0c24InverseSBALogTransformno 
0x0c25SBABlackint16u 
0x0c26SBAGrayint16u 
0x0c27SBAWhiteint16u 
0x0c28GaussianWeightsno 
0x0c29SfsBoundaryno 
0x0c2aCoringTableBestno 
0x0c2bCoringTableBetterno 
0x0c2cCoringTableGoodno 
0x0c2dExposureReferenceGainno 
0x0c2eExposureReferenceOffsetno 
0x0c2fSBARedBalanceLutno 
0x0c30SBAGreenBalanceLutno 
0x0c31SBABlueBalanceLutno 
0x0c32SBANeutralBALint32s 
0x0c33SBAGreenMagentaBALint32s 
0x0c34SBAIlluminantBALint32s 
0x0c35SBAAnalysisCompleteint8u 
0x0c36JPEGQTableBestno 
0x0c37JPEGQTableBetterno 
0x0c38JPEGQTableGoodno 
0x0c39RembrandtPortraitToneScaleno 
0x0c3aRembrandtConsumerToneScaleno 
0x0c3bCFAGreenThreshold1no 
0x0c3cCFAGreenThreshold2no 
0x0c3dQTableLarge50Pctno 
0x0c3eQTableLarge67Pctno 
0x0c3fQTableLarge100Pctno 
0x0c40QTableMedium50Pctno 
0x0c41QTableMedium67Pctno 
0x0c42QTableMedium100Pctno 
0x0c43QTableSmall50Pctno 
0x0c44QTableSmall67Pctno 
0x0c45QTableSmall100Pctno 
0x0c46SBAHighGrayint16u 
0x0c47SBALowGrayint16u 
0x0c48CaptureLookint16u 
0x0c49SBAIllOffsetint16s 
0x0c4aSBAGmOffsetint16s 
0x0c4bNifNonlinearity12Bitno 
0x0c4cSharpeningOnno 
0x0c4dNifNonlinearity16Bitno 
0x0c4eRawHistogramno 
0x0c4fRawCFAComponentAveragesno 
0x0c50DisableFlagsPresentno 
0x0c51DelayColsno 
0x0c52DummyColsLeftno 
0x0c53TrashColsRightno 
0x0c54BlackColsRightno 
0x0c55DummyColsRightno 
0x0c56OverClockColsRightno 
0x0c57UnusedBlackRowsTopOutno 
0x0c58TrashRowsBottomno 
0x0c59BlackRowsBottomno 
0x0c5aOverClockRowsBottomno 
0x0c5bBlackColsLeftno 
0x0c5cBlackRowsTopno 
0x0c5dPartialActiveColsLeftno 
0x0c5ePartialActiveColsRightno 
0x0c5fPartialActiveRowsTopno 
0x0c60PartialActiveRowsBottomno 
0x0c61ProcessBorderColsLeftint16u 
0x0c62ProcessBorderColsRightint16u 
0x0c63ProcessBorderRowsTopint16u 
0x0c64ProcessBorderRowsBottomint16u 
0x0c65ActiveColsno 
0x0c66ActiveRowsno 
0x0c67FirstLinesno 
0x0c68UnusedBlackRowsTopInno 
0x0c69UnusedBlackRowsBottomInno 
0x0c6aUnusedBlackRowsBottomOutno 
0x0c6bUnusedBlackColsLeftOutno 
0x0c6cUnusedBlackColsLeftInno 
0x0c6dUnusedBlackColsRightInno 
0x0c6eUnusedBlackColsRightOutno 
0x0c6fCFAOffsetRowsint32u 
0x0c70ShiftColsint16s 
0x0c71CFAOffsetColsint32u 
0x0c76DarkMapScaleno 
0x0c77HMapHandlingno 
0x0c78VMapHandlingno 
0x0c79DarkThresholdno 
0x0c7aDMDitherMatrixint16u 
0x0c7bDMDitherMatrixWidthint16u 
0x0c7cDMDitherMatrixHeightint16u 
0x0c7dMaxPixelValueThresholdint16u 
0x0c7eHoleFillDeltaThresholdint16u 
0x0c7fDarkPedestalint16u 
0x0c80ImageProcessingFileTagsVersionNumberint16u 
0x0c81ImageProcessingFileDateCreatedstring 
0x0c82DoublingMicrovoltsint32s 
0x0c83DarkFrameShortExposureint32u 
0x0c84DarkFrameLongExposureint32u 
0x0c85DarkFrameCountFactorrational64u 
0x0c88HoleFillDarkDeltaThresholdint16u 
0x0c89FarkleWhiteThresholdno 
0x0c8aColumnResetOffsetsno 
0x0c8bColumnGainFactorsno 
0x0c8cChannel0LagKernelno 
0x0c8dChannel1LagKernelno 
0x0c8eChannel2LagKernelno 
0x0c8fChannel3LagKernelno 
0x0c90BluegrassTableno 
0x0c91BluegrassScale1no 
0x0c92BluegrassScale2no 
0x0ce4FinishedFileProcessingRequestno 
0x0ce5FirmwareVersionstring 
0x0ce6HostSoftwareExportVersionno 
0x0ce7HostSoftwareRenderingint32u0 = Normal (sRGB) +
1 = Linear (camera RGB) +
2 = Pro Photo RGB +
3 = Unknown +
4 = Other Profile
0x0dacDCS3XXProcessingInfoIFDno 
0x0dadDCS3XXProcessingInfono 
0x0daeIPAVersionint32u 
0x0db6FinishIPAVersionno 
0x0db7FinishIPFVersionno 
0x0db8FinishFileTypeint32u0 = JPEG Best +
1 = JPEG Better +
2 = JPEG Good +
3 = TIFF RGB
0x0db9FinishResolutionint32u0 = 100% +
1 = 67% +
2 = 50% +
3 = 25%
0x0dbaFinishNoiseint32u0 = Normal +
1 = Strong +
2 = Low
0x0dbbFinishSharpeningint32u0 = None +
1 = High +
2 = Medium +
3 = Low
0x0dbcFinishLookint32u +
0 = Product +
1 = Portrait +
2 = Product Reduced +
3 = Portrait Reduced +
4 = Monochrome Product +
5 = Monochrome Portrait +
6 = Wedding +
7 = Event +
8 = Product Hi Color +
9 = Portrait Hi Color +
10 = Product Hi Color Hold +
11 = Portrait Hi Color Hold +
13 = DCS BW Normal +
14 = DCS BW Wratten 8 +
15 = DCS BW Wratten 25 +
16 = DCS Sepia 1 +
17 = DCS Sepia 2
+
0x0dbdFinishExposureint32u0 = Yes +
1 = No
0x0e0bSigmaScalingFactorLowResrational64u 
0x0e0cSigmaScalingFactorCamerarational64u 
0x0e0dSigmaImpulseParametersint16u[n] 
0x0e0eSigmaNoiseThreshTableV2no 
0x0e0fSigmaSizeTableint16u[n] 
0x0e10DacGainsCoarseAdjPreIF41no 
0x0e11SigmaNoiseFilterCalTableV1no 
0x0e12SigmaNoiseFilterTableV1no 
0x0e13Lin12ToKlut8no 
0x0e14SigmaNoiseFilterTableV1Versionno 
0x0e15Lin12ToKlut12no 
0x0e16Klut12ToLin12no 
0x0e17NifNonlinearity12To16no 
0x0e18SBALog12Transformno 
0x0e19InverseSBALog12Transformno 
0x0e1aToneScale0no 
0x0e1bToneScale1no 
0x0e1cToneScale2no 
0x0e1dToneScale3no 
0x0e1eToneScale4no 
0x0e1fToneScale5no 
0x0e20ToneScale6no 
0x0e21ToneScale7no 
0x0e22ToneScale8no 
0x0e23ToneScale9no 
0x0e24DayMat0no 
0x0e25DayMat1no 
0x0e26DayMat2no 
0x0e27DayMat3no 
0x0e28DayMat4no 
0x0e29DayMat5no 
0x0e2aDayMat6no 
0x0e2bDayMat7no 
0x0e2cDayMat8no 
0x0e2dDayMat9no 
0x0e2eTungMat0no 
0x0e2fTungMat1no 
0x0e30TungMat2no 
0x0e31TungMat3no 
0x0e32TungMat4no 
0x0e33TungMat5no 
0x0e34TungMat6no 
0x0e35TungMat7no 
0x0e36TungMat8no 
0x0e37TungMat9no 
0x0e38FluorMat0no 
0x0e39FluorMat1no 
0x0e3aFluorMat2no 
0x0e3bFluorMat3no 
0x0e3cFluorMat4no 
0x0e3dFluorMat5no 
0x0e3eFluorMat6no 
0x0e3fFluorMat7no 
0x0e40FluorMat8no 
0x0e41FluorMat9no 
0x0e42FlashMat0no 
0x0e43FlashMat1no 
0x0e44FlashMat2no 
0x0e45FlashMat3no 
0x0e46FlashMat4no 
0x0e47FlashMat5no 
0x0e48FlashMat6no 
0x0e49FlashMat7no 
0x0e4aFlashMat8no 
0x0e4bFlashMat9no 
0x0e4cKodakLookstring 
0x0e4dIPFCameraModelstring 
0x0e4eAH2GreenInterpolationThresholdint16u 
0x0e4fResamplingKernelDenominators067int16u[3] 
0x0e50ResamplingKernelDenominators050int16u[3] 
0x0e51ResamplingKernelDenominators100int16u[3] 
0x0e56LookMat0no 
0x0e57LookMat1no 
0x0e58LookMat2no 
0x0e59LookMat3no 
0x0e5aLookMat4no 
0x0e5bLookMat5no 
0x0e5cLookMat6no 
0x0e5dLookMat7no 
0x0e5eLookMat8no 
0x0e5fLookMat9no 
0x0e60CFAInterpolationAlgorithmint16u0 = AH2 +
1 = Karnak
0x0e61CFAInterpolationMetricint16u0 = Linear12 +
1 = KLUT12
0x0e62CFAZipperFixThresholdint16u 
0x0e63NoiseReductionParametersKhufuRGBint16u[9] 
0x0e64NoiseReductionParametersKhufu6MPint16u[9] 
0x0e65NoiseReductionParametersKhufu3MPint16u[9] 
0x0e6aChromaNoiseHighFThreshint32u[2] 
0x0e6bChromaNoiseLowFThreshint32u[2] 
0x0e6cChromaNoiseEdgeMapThreshint32u 
0x0e6dChromaNoiseColorSpaceint32u 
0x0e6eEnableChromaNoiseReductionint16u 
0x0e6fNoiseReductionParametersHostRGBint16u[9] 
0x0e70NoiseReductionParametersHost6MPint16u[9] 
0x0e71NoiseReductionParametersHost3MPint16u[9] 
0x0e72NoiseReductionParametersCameraint16u[6] 
0x0e73NoiseReductionParametersAtCaptureint16u[6] 
0x0e74LCDMatrixrational64s[9] 
0x0e75LCDMatrixChickFixrational64s[9] 
0x0e76LCDMatrixMarvinrational64s[9] 
0x0e7cLCDGammaTableChickFixno 
0x0e7dLCDGammaTableMarvinno 
0x0e7eLCDGammaTableno 
0x0e7fLCDSharpeningF1no 
0x0e80LCDSharpeningF2no 
0x0e81LCDSharpeningF3no 
0x0e82LCDSharpeningF4no 
0x0e83LCDEdgeMapX1no 
0x0e84LCDEdgeMapX2no 
0x0e85LCDEdgeMapX3no 
0x0e86LCDEdgeMapX4no 
0x0e87LCDEdgeMapSlopeno 
0x0e88YCrCbMatrixno 
0x0e89LCDEdgeSplineno 
0x0e92Fac18Perint16u 
0x0e93Fac170Perint16u 
0x0e94Fac100Perint16u 
0x0e9bExtraTickLocationsno 
0x0e9cRGBtoeV0no 
0x0e9dRGBtoeV1no 
0x0e9eRGBtoeV2no 
0x0e9fRGBtoeV3no 
0x0ea0RGBtoeV4no 
0x0ea1RGBtoeV5no 
0x0ea2RGBtoeV6no 
0x0ea3RGBtoeV7no 
0x0ea4RGBtoeV8no 
0x0ea5RGBtoeV9no 
0x0ea6LCDHistLUT0no 
0x0ea7LCDHistLUT1no 
0x0ea8LCDHistLUT2no 
0x0ea9LCDHistLUT3no 
0x0eaaLCDHistLUT4no 
0x0eabLCDHistLUT5no 
0x0eacLCDHistLUT6no 
0x0eadLCDHistLUT7no 
0x0eaeLCDHistLUT8no 
0x0eafLCDHistLUT9no 
0x0eb0LCDLinearClipValueno 
0x0eceLCDStepYvaluesno 
0x0ecfLCDStepYvaluesChickFixno 
0x0ed0LCDStepYvaluesMarvinno 
0x0ed8InterpolationCoefficientsno 
0x0ed9InterpolationCoefficients6MPno 
0x0edaInterpolationCoefficients3MPno 
0x0f00NoiseReductionParametersHostNormalno 
0x0f01NoiseReductionParametersHostStrongno 
0x0f02NoiseReductionParametersHostLowno 
0x0f0aMariahTextureThresholdint16u 
0x0f0bMariahMapLoThresholdint16u 
0x0f0cMariahMapHiThresholdint16u 
0x0f0dMariahChromaBlurSizeint16u 
0x0f0eMariahSigmaThresholdint16u 
0x0f0fMariahThresholdsno 
0x0f10MariahThresholdsNormalno 
0x0f11MariahThresholdsStrongno 
0x0f12MariahThresholdsLowno 
0x0f14KhufuLinearRedMixingCoefficientno 
0x0f15KhufuLinearGreenMixingCoefficientno 
0x0f16KhufuLinearBlueMixingCoefficientno 
0x0f17KhufuUSpaceC2MixingCoefficientno 
0x0f18KhufuSigmaGaussianWeightsno 
0x0f19KhufuSigmaScalingFactors6MPno 
0x0f1aKhufuSigmaScalingFactors3MPno 
0x0f1bKhufuSigmaScalingFactors14MPno 
0x0f32KhufuI0Thresholdsno 
0x0f33KhufuI1Thresholdsno 
0x0f34KhufuI2Thresholdsno 
0x0f35KhufuI3Thresholdsno 
0x0f36KhufuI4Thresholdsno 
0x0f37KhufuI5Thresholdsno 
0x0f3cCondadoDayBVThreshint16u 
0x0f3dCondadoNeuRangeint16u 
0x0f3eCondadoBVFactorint16s 
0x0f3fCondadoIllFactorint16s 
0x0f40CondadoTunThreshint16s 
0x0f41CondadoFluThreshint16s 
0x0f42CondadoDayOffsetsint16s[2] 
0x0f43CondadoTunOffsetsint16s[2] 
0x0f44CondadoFluOffsetsint16s[2] 
0x0f5aERIMMToCRGB0Splineno 
0x0f5bERIMMToCRGB1Splineno 
0x0f5cERIMMToCRGB2Splineno 
0x0f5dERIMMToCRGB3Splineno 
0x0f5eERIMMToCRGB4Splineno 
0x0f5fERIMMToCRGB5Splineno 
0x0f60ERIMMToCRGB6Splineno 
0x0f61ERIMMToCRGB7Splineno 
0x0f62ERIMMToCRGB8Splineno 
0x0f63ERIMMToCRGB9Splineno 
0x0f64CRGBToERIMM0Splineno 
0x0f65CRGBToERIMM1Splineno 
0x0f66CRGBToERIMM2Splineno 
0x0f67CRGBToERIMM3Splineno 
0x0f68CRGBToERIMM4Splineno 
0x0f69CRGBToERIMM5Splineno 
0x0f6aCRGBToERIMM6Splineno 
0x0f6bCRGBToERIMM7Splineno 
0x0f6cCRGBToERIMM8Splineno 
0x0f6dCRGBToERIMM9Splineno 
0x0f6eERIMMNonLinearitySplineno 
0x0f6fDelta12To8Splineno 
0x0f70Delta8To12Splineno 
0x0f71InverseMonitorMatrixno 
0x0f72NifNonlinearityExtno 
0x0f73InvNifNonLinearityno 
0x0f74RIMM13ToERIMM12Splineno 
0x0f78ToneScale0Splineno 
0x0f79ToneScale1Splineno 
0x0f7aToneScale2Splineno 
0x0f7bToneScale3Splineno 
0x0f7cToneScale4Splineno 
0x0f7dToneScale5Splineno 
0x0f7eToneScale6Splineno 
0x0f7fToneScale7Splineno 
0x0f80ToneScale8Splineno 
0x0f81ToneScale9Splineno 
0x0f82ERIMMToneScale0Splineno 
0x0f83ERIMMToneScale1Splineno 
0x0f84ERIMMToneScale2Splineno 
0x0f85ERIMMToneScale3Splineno 
0x0f86ERIMMToneScale4Splineno 
0x0f87ERIMMToneScale5Splineno 
0x0f88ERIMMToneScale6Splineno 
0x0f89ERIMMToneScale7Splineno 
0x0f8aERIMMToneScale8Splineno 
0x0f8bERIMMToneScale9Splineno 
0x0f8cRIMMToCRGB0Splineno 
0x0f8dRIMMToCRGB1Splineno 
0x0f8eRIMMToCRGB2Splineno 
0x0f8fRIMMToCRGB3Splineno 
0x0f90RIMMToCRGB4Splineno 
0x0f91RIMMToCRGB5Splineno 
0x0f92RIMMToCRGB6Splineno 
0x0f93RIMMToCRGB7Splineno 
0x0f94RIMMToCRGB8Splineno 
0x0f95RIMMToCRGB9Splineno 
0x0fa0QTableLarge25Pctno 
0x0fa1QTableMedium25Pctno 
0x0fa2QTableSmall25Pctno 
0x1130NoiseReductionKernelno 
0x1388UserMetaDatano 
0x1389InputProfileundef 
0x138aKodakLookProfileundef 
0x138bOutputProfileundef 
0x1390SourceProfilePrefixstring 
0x1391ToneCurveProfileNamestring 
0x1392InputProfile---> ICC_Profile Tags
0x1393ProcessParametersV2no 
0x1394ReservedBlob2no 
0x1395ReservedBlob3no 
0x1396ReservedBlob4no 
0x1397ReservedBlob5no 
0x1398ReservedBlob6no 
0x1399ReservedBlob7no 
0x139aReservedBlob8no 
0x139bReservedBlob9no 
0x1770ScriptVersionint32u 
0x177aImagerTimingDatano 
0x1784ISOint32u 
0x17a2Scav11Colsno 
0x17a3Scav12Colsno 
0x17a4Scav21Colsno 
0x17a5Scav22Colsno 
0x17a6ActiveCTEMonitor1Colsno 
0x17a7ActiveCTEMonitor2Colsno 
0x17a8ActiveCTEMonitorRowsno 
0x17a9ActiveBuf1Colsno 
0x17aaActiveBuf2Colsno 
0x17abActiveBuf1Rowsno 
0x17acActiveBuf2Rowsno 
0x17c0HRNoiseLinesno 
0x17c1RNoiseLinesno 
0x17c2ANoiseLinesno 
0x17d4ImagerColsint16u 
0x17deImagerRowsint16u 
0x17e8PartialActiveCols1int32u 
0x17f2PartialActiveCols2int32u 
0x17fcPartialActiveRows1int32u 
0x1806PartialActiveRows2int32u 
0x1810ElectricalBlackColumnsint32u 
0x181aResetBlackSegRowsint32u 
0x1838CaptureWidthNormalint32u 
0x1839CaptureHeightNormalint32u 
0x183aCaptureWidthResetBlackSegNormalno 
0x183bCaptureHeightResetBlackSegNormalno 
0x183cDarkRefOffsetNormalno 
0x1842CaptureWidthTestint32u 
0x1843CaptureHeightTestno 
0x1844CaptureWidthResetBlackSegTestno 
0x1845CaptureHeightResetBlackSegTestno 
0x1846DarkRefOffsetTestno 
0x184cImageSegmentStartLineint32u 
0x184dImageSegmentLinesint32u 
0x184eSkipLineTimeint32u 
0x1860FastResetLineTimeint32u 
0x186aNormalLineTimeint32u 
0x1874MinIntegrationRowsint32u 
0x187ePreReadFastResetCountint32u 
0x1888TransferTimeNormalint32u 
0x1889TransferTimeTestint32u 
0x188aQuietTimeint32u 
0x189cOverClockColsint16u 
0x18a6H2ResetBlackPixelsint32u 
0x18b0H3ResetBlackPixelsint32u 
0x18baBlackAcquireRowsint32u 
0x18c4OverClockRowsint16u 
0x18ceH3ResetBlackColumnsint32u 
0x18d8DarkBlackSegRowsint32u 
0x1900CrossbarEnableno 
0x1901FifoenOnePixelDelayint32u 
0x1902ReadoutTypeRequestedint32u 
0x1903ReadoutTypeActualint32u 
0x190aOffsetDacValueint32u 
0x1914TempAmpGainX100int32u 
0x191eVarrayDacNominalValuesint32u[3] 
0x1928VddimDacNominalValuesno 
0x1964C14Configurationint32u 
0x196eTDA1Offsetint32u[3] 
0x196fTDA1Bandwidthint32u 
0x1970TDA1Gainint32u[3] 
0x1971TDA1EdgePolarityint32u 
0x1978TDA2Offsetint32u[3] 
0x1979TDA2Bandwidthint32u 
0x197aTDA2Gainint32u[3] 
0x197bTDA2EdgePolarityint32u 
0x1982TDA3Offsetint32u[3] 
0x1983TDA3Bandwidthint32u 
0x1984TDA3Gainint32u[3] 
0x1985TDA3EdgePolarityint32u 
0x198cTDA4Offsetint32u[3] 
0x198dTDA4Bandwidthint32u 
0x198eTDA4Gainint32u[3] 
0x198fTDA4EdgePolarityint32u 
0xfde8ComLenBlkSizeint16u 
+ +

Kodak TextualInfo Tags

+

Below is a list of tags which have been observed in the Kodak TextualInfo +data, however ExifTool will extract information from any tags found here.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AF Function'AFModeno 
'Actual Compensation'ActualCompensationno 
'Aperture'Apertureno 
'Auto Bracket'AutoBracketno 
'Brightness Value'BrightnessValueno 
'Camera'CameraModelno 
'Camera body'CameraBodyno 
'Compensation'ExposureCompensationno 
'Date'Dateno 
'Exposure Bias'ExposureBiasno 
'Exposure Mode'ExposureModeno'A' = Aperture Priority +
'B' = Bulb +
'M' = Manual +
'P' = Program +
'S' = Shutter Priority
'Firmware Version'FirmwareVersionno 
'Flash Compensation'FlashExposureCompno 
'Flash Fired'FlashFiredno 
'Flash Sync Mode'FlashSyncModeno 
'Focal Length'FocalLengthno 
'Height'KodakImageHeightno 
'ISO'ISOno 
'ISO Speed'ISOno 
'Image Number'ImageNumberno 
'Lens'Lensno 
'Max Aperture'MaxApertureno 
'Meter Mode'MeterModeno 
'Min Aperture'MinApertureno 
'Popup Flash'PopupFlashno 
'Serial Number'SerialNumberno 
'Shooting Mode'ShootingModeno 
'Shutter'ShutterSpeedno 
'Temperature'Temperatureno 
'Time'Timeno 
'White balance'WhiteBalanceno 
'Width'KodakImageWidthno 
'_other_info'OtherInfono(any other information without a tag name)
+ +

Kodak Processing Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
20WB_RGBLevelsno 
+ +

Kodak Type2 Tags

+

These tags are used by the Kodak DC220, DC260, DC265 and DC290, +Hewlett-Packard PhotoSmart 618, C500 and C912, Pentax EI-200 and EI-2000, +and Minolta EX1500Z.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8KodakMakerstring[32] 
40KodakModelstring[32] 
108KodakImageWidthint32u 
112KodakImageHeightint32u 
+ +

Kodak Type3 Tags

+

These tags are used by the DC240, DC280, DC3400 and DC5000.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12YearCreatedint16u 
14MonthDayCreatedint8u[2] 
16TimeCreatedint8u[4] 
30OpticalZoomint16u 
55Sharpnessint8s0 = Normal
56ExposureTimeint32u 
60FNumberint16u 
78ISOint16u 
+ +

Kodak Type4 Tags

+

These tags are used by the DC200 and DC215.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
32OriginalFileNamestring[12] 
+ +

Kodak Type5 Tags

+

These tags are used by the CX4200, CX4210, CX4230, CX4300, CX4310, CX6200 +and CX6230.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
20ExposureTimeint32u 
26WhiteBalanceint8u1 = Daylight +
2 = Flash +
3 = Tungsten
28FNumberint16u 
30ISOint16u 
32OpticalZoomint16u 
34DigitalZoomint16u 
39FlashModeint8u0 = Auto +
1 = On +
2 = Off +
3 = Red-Eye
42ImageRotatedint8u0 = No +
1 = Yes
43Macroint8u0 = On +
1 = Off
+ +

Kodak Type6 Tags

+

These tags are used by the DX3215 and DX3700.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
16ExposureTimeint32u 
20ISOSetting?int32u 
24FNumberint16u 
26ISOint16u 
28OpticalZoomint16u 
30DigitalZoomint16u 
34Flashint16u0 = No Flash +
1 = Fired
+ +

Kodak Type7 Tags

+

The maker notes of models such as the C340, C433, CC533, LS755, V803 and +V1003 seem to start with the camera serial number. The C310, C315, C330, +C643, C743, CD33, CD43, CX7220 and CX7300 maker notes are also decoded using +this table, although the strings for these cameras don't conform to the +usual Kodak serial number format, and instead have the model name followed +by 8 digits.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0SerialNumberstring[16] 
+ +

Kodak Type8 Tags

+

Kodak models such as the ZD710, P712, P850, P880, V1233, V1253, V1275, +V1285, Z612, Z712, Z812, Z885 use standard TIFF IFD format for the maker +notes. In keeping with Kodak's strategy of inconsistent makernotes, models +such as the M380, M1033, M1093, V1073, V1273, Z1012, Z1085 and Z8612 +also use these tags, but these makernotes begin with a TIFF header instead +of an IFD entry count and use relative instead of absolute offsets. There +is a large amount of information stored in these maker notes (apparently +with much duplication), but relatively few tags have so far been decoded.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0xfc00SubIFD0---> Kodak SubIFD0 Tags +
--> Kodak SubIFD0 Tags
0xfc01SubIFD1---> Kodak SubIFD1 Tags +
--> Kodak SubIFD1 Tags
0xfc02SubIFD2---> Kodak SubIFD2 Tags +
--> Kodak SubIFD2 Tags
0xfc03SubIFD3---> Kodak SubIFD3 Tags +
--> Kodak SubIFD3 Tags
0xfc04SubIFD4---> Kodak SubIFD4 Tags +
--> Kodak SubIFD4 Tags
0xfc05SubIFD5---> Kodak SubIFD5 Tags +
--> Kodak SubIFD5 Tags
0xfc06SubIFD6---> Kodak SubIFD6 Tags +
--> Kodak SubIFD6 Tags
0xfcffSubIFD255---> Kodak SubIFD0 Tags
0xff00CameraInfo---> Kodak CameraInfo Tags
+ +

Kodak SubIFD0 Tags

+

SubIFD0 through SubIFD5 tags are written a number of newer Kodak models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0xfa02SceneModeint16u(may not be valid for some models) + +
1 = Sport +
3 = Portrait +
4 = Landscape +
6 = Beach +
7 = Night Portrait +
8 = Night Landscape +
9 = Snow +
10 = Text +
11 = Fireworks +
12 = Macro +
13 = Museum +
16 = Children
  17 = Program +
18 = Aperture Priority +
19 = Shutter Priority +
20 = Manual +
25 = Back Light +
28 = Candlelight +
29 = Sunset +
31 = Panorama Left-right +
32 = Panorama Right-left +
33 = Smart Scene +
34 = High ISO
+
0xfa19SerialNumberstring 
0xfa1dKodakImageWidthint16u 
0xfa1eKodakImageHeightint16u 
0xfa20SensorWidthint16u 
0xfa21SensorHeightint16u 
0xfa23FNumberint16u 
0xfa24ExposureTimeint32u 
0xfa2eISOint16u 
0xfa3dOpticalZoomint16u 
0xfa46ISOint16u 
0xfa51KodakImageWidthint16u 
0xfa52KodakImageHeightint16u 
0xfa54ThumbnailWidthint16u 
0xfa55ThumbnailHeightint16u 
0xfa57PreviewImageWidthint16u 
0xfa58PreviewImageHeightint16u 
+ +

Kodak SubIFD1 Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0027ISOint16u 
0x0028ISOint16u 
+ +

Kodak SubIFD2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x6002SceneModeUsedint32u + +
0 = Program +
2 = Aperture Priority +
3 = Shutter Priority +
4 = Manual +
5 = Portrait +
6 = Sport +
7 = Children +
8 = Museum +
10 = High ISO +
11 = Text +
12 = Macro
  13 = Back Light +
16 = Landscape +
17 = Night Landscape +
18 = Night Portrait +
19 = Snow +
20 = Beach +
21 = Fireworks +
22 = Sunset +
23 = Candlelight +
28 = Panorama
+
0x6006OpticalZoomint32u 
0x6103MaxApertureint32u 
0xf002SceneModeUsedint32u + +
0 = Program +
2 = Aperture Priority +
3 = Shutter Priority +
4 = Manual +
5 = Portrait +
6 = Sport +
7 = Children +
8 = Museum +
10 = High ISO +
11 = Text +
12 = Macro
  13 = Back Light +
16 = Landscape +
17 = Night Landscape +
18 = Night Portrait +
19 = Snow +
20 = Beach +
21 = Fireworks +
22 = Sunset +
23 = Candlelight +
28 = Panorama
+
0xf006OpticalZoomint32u 
0xf103FNumberint32u 
0xf104ExposureTimeint32u 
0xf105ISOint32u 
+ +

Kodak SubIFD3 Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x1000OpticalZoomint16u 
+ +

Kodak SubIFD4 Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

Kodak SubIFD5 Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x000fOpticalZoomint16u 
+ +

Kodak SubIFD6 Tags

+

SubIFD6 is written by the M580.

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

Kodak CameraInfo Tags

+

These tags are used by the P712, P850 and P880.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0xf900SensorWidthint16u(effective sensor size)
0xf901SensorHeightint16u 
0xf902BayerPatternstring 
0xf903SensorFullWidthint16u(includes black border?)
0xf904SensorFullHeightint16u 
0xf907KodakImageWidthint16u 
0xf908KodakImageHeightint16u 
0xfa00KodakInfoTypestring 
0xfa04SerialNumberstring 
0xfd04FNumberint16u 
0xfd05ExposureTimeint32u 
0xfd06ISOint16u 
+ +

Kodak Type9 Tags

+

These tags are used by the Kodak C140, C180, C913, C1013, M320, M340 and +M550, as well as various cameras marketed by other manufacturers.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12FNumberint16u 
16ExposureTimeint32u 
20DateTimeOriginalstring[20] 
52ISOint16u 
87FirmwareVersionstring[16](Kodak only)
168UnknownNumberno(Kodak only)
196UnknownNumberno(Kodak only)
+ +

Kodak Type10 Tags

+

Another variation of the IFD-format type, this time with just a byte order +indicator instead of a full TIFF header. These tags are used by the Z980.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0002PreviewImageSizeint16u[2] 
0x0012ExposureTimeint32u 
0x0013FNumberint16u 
0x0014ISOint16u 
0x001dFocalLengthint32u 
+ +

Kodak Type11 Tags

+

These tags are found in models such as the PixPro S-1. They are not +writable because the inconsistency of Kodak maker notes is beginning to get +on my nerves.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0104FirmwareVersionno 
0x0203PictureEffectno0 = None +
3 = Monochrome +
9 = Kodachrome
0x0207KodakModelno 
0x0300KodakMakeno 
0x0308LensSerialNumberno 
0x0309LensModelno 
0x030dLevelMeter?no 
0x0311Pitchno 
0x0312Yawno 
0x0313Rollno 
0x0314CX?no 
0x0315CY?no 
0x0316Rads?no 
+ +

Kodak Unknown Tags

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +

Kodak Meta Tags

+

These tags are found in the APP3 "Meta" segment of JPEG images from Kodak +cameras such as the DC280, DC3400, DC5000, MC3, M580, Z950 and Z981. The +structure of this segment is similar to the APP1 "Exif" segment, but a +different set of tags is used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0xc350FilmProductCodeno 
0xc351ImageSourceEKno 
0xc352CaptureConditionsPARno 
0xc353CameraOwnerundef 
0xc354SerialNumberundef 
0xc355UserSelectGroupTitleno 
0xc356DealerIDNumberno 
0xc357CaptureDeviceFIDno 
0xc358EnvelopeNumberno 
0xc359FrameNumberno 
0xc35aFilmCategoryno 
0xc35bFilmGencodeno 
0xc35cModelAndVersionno 
0xc35dFilmSizeno 
0xc35eSBA_RGBShiftsno 
0xc35fSBAInputImageColorspaceno 
0xc360SBAInputImageBitDepthno 
0xc361SBAExposureRecordno 
0xc362UserAdjSBA_RGBShiftsno 
0xc363ImageRotationStatusno 
0xc364RollGuidElementsno 
0xc365MetadataNumberno 
0xc366EditTagArrayno 
0xc367Magnificationno 
0xc36cNativeXResolutionno 
0xc36dNativeYResolutionno 
0xc36eKodakEffectsIFD---> Kodak SpecialEffects Tags
0xc36fKodakBordersIFD---> Kodak Borders Tags
0xc37aNativeResolutionUnitno 
0xc418SourceImageDirectoryno 
0xc419SourceImageFileNameno 
0xc41aSourceImageVolumeNameno 
0xc46cPrintQualityno 
0xc46eImagePrintStatusno 
+ +

Kodak SpecialEffects Tags

+

+The Kodak SpecialEffects and Borders tags are found in sub-IFD's within the +Kodak JPEG APP3 "Meta" segment. +

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000DigitalEffectsVersionno 
0x0001DigitalEffectsNameno 
0x0002DigitalEffectsTypeno 
+ +

Kodak Borders Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000BordersVersionno 
0x0001BorderNameno 
0x0002BorderIDno 
0x0003BorderLocationno 
0x0004BorderTypeno 
0x0008WatermarkTypeno 
+ +

Kodak KDC_IFD Tags

+

These tags are found in a separate IFD of KDC images from some newer Kodak +models such as the P880 and Z1015IS.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0xfa00SerialNumberstring 
0xfa0dWhiteBalanceint8u0 = Auto +
1 = Fluorescent +
2 = Tungsten +
3 = Daylight +
6 = Shade
0xfa25WB_RGBLevelsAutono 
0xfa27WB_RGBLevelsTungstenno 
0xfa28WB_RGBLevelsFluorescentno 
0xfa29WB_RGBLevelsDaylightno 
0xfa2aWB_RGBLevelsShadeno 
+ +

Kodak frea Tags

+

Information stored in the "frea" atom of Kodak PixPro SP360 MP4 videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'scra'PreviewImageno 
'thma'ThumbnailImageno 
'tima'Durationno 
'ver 'KodakVersionno 
+ +

Kodak Free Tags

+

Information stored in the "free" atom of Kodak MP4 videos. (VERY bad form +for Kodak to store useful information in an atom intended for unused space!)

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AprV'ApertureValueno 
'BrtV'BrightnessValueno 
'Expc'ExposureCompensationno 
'Expo'ExposureTimeno 
'FL35'FocalLengthIn35mmFormatno 
'FNum'FNumberno 
'FoLn'FocalLengthno 
'ISOS'ISOno 
'SVer'FirmwareVersionno 
'Scrn'PreviewInfo---> Kodak Scrn Tags
'Seri'SerialNumberno 
'StSV'ShutterSpeedValueno 
+ +

Kodak Scrn Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0PreviewImageWidthno 
1PreviewImageHeightno 
2PreviewImageLengthno 
4PreviewImageno 
+ +

Kodak DcMD Tags

+

Metadata directory found in MOV and MP4 videos from some Kodak cameras.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CMbo'CameraByteOrderno'II' = Little-endian (Intel, II) +
'MM' = Big-endian (Motorola, MM)
'Cmbo'CameraByteOrderno'II' = Little-endian (Intel, II) +
'MM' = Big-endian (Motorola, MM)
'DcEM'DcEM---> Kodak DcEM Tags
'DcME'DcME---> Kodak DcME Tags
+ +

Kodak DcEM Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

Kodak DcME Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

Kodak MOV Tags

+

This information is found in the TAGS atom of MOV videos from Kodak models +such as the P880.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
22Modelno 
64ModelTypeno 
78ExposureTimeno 
82FNumberno 
90ExposureCompensationno 
112FocalLengthno 
+ +

Kodak pose Tags

+

Streamed orientation information from the PixPro 4KVR360, extracted as +sub-documents when the Duplicates option is used.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Accelerometer'Accelerometerno 
'AngularVelocity'AngularVelocityno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 8, 2019 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/KyoceraRaw.html b/ExifTool/html/TagNames/KyoceraRaw.html new file mode 100644 index 0000000..326b758 --- /dev/null +++ b/ExifTool/html/TagNames/KyoceraRaw.html @@ -0,0 +1,93 @@ + + + + +KyoceraRaw Tags + + + +

KyoceraRaw Tags

+

Tags for Kyocera Contax N Digital RAW images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1FirmwareVersionno 
12Modelno 
25Makeno 
33DateTimeOriginalno 
52ISOno + + + +
7 = 25 +
8 = 32 +
9 = 40 +
10 = 50
  11 = 64 +
12 = 80 +
13 = 100 +
14 = 125
  15 = 160 +
16 = 200 +
17 = 250 +
18 = 320
  19 = 400
+
56ExposureTimeno 
60WB_RGGBLevelsno 
88FNumberno 
104MaxApertureno 
112FocalLengthno 
124Lensno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 17, 2006 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/LIF.html b/ExifTool/html/TagNames/LIF.html new file mode 100644 index 0000000..ecaa368 --- /dev/null +++ b/ExifTool/html/TagNames/LIF.html @@ -0,0 +1,30 @@ + + + + +LIF Tags + + + +

LIF Tags

+

Tags extracted from Leica Image Format (LIF) imaging files. As well as the +tags listed below, all available information is extracted from the +XML-format metadata in the LIF header.

+
+
+ + + + + + + +
Tag NameWritableValues / Notes
TimeStampListno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 22, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/LNK.html b/ExifTool/html/TagNames/LNK.html new file mode 100644 index 0000000..1800fc8 --- /dev/null +++ b/ExifTool/html/TagNames/LNK.html @@ -0,0 +1,479 @@ + + + + +LNK Tags + + + +

LNK Tags

+

Information extracted from MS Shell Link (Windows shortcut) files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0x0014Flagsno +
Bit 0 = IDList +
Bit 1 = LinkInfo +
Bit 2 = Description +
Bit 3 = RelativePath +
Bit 4 = WorkingDir +
Bit 5 = CommandArgs +
Bit 6 = IconFile +
Bit 7 = Unicode +
Bit 8 = NoLinkInfo +
Bit 9 = ExpString +
Bit 10 = SeparateProc +
Bit 12 = DarwinID +
Bit 13 = RunAsUser +
Bit 14 = ExpIcon +
Bit 15 = NoPidAlias +
Bit 17 = RunWithShim +
Bit 18 = NoLinkTrack +
Bit 19 = TargetMetadata +
Bit 20 = NoLinkPathTracking +
Bit 21 = NoKnownFolderTracking +
Bit 22 = NoKnownFolderAlias +
Bit 23 = LinkToLink +
Bit 24 = UnaliasOnSave +
Bit 25 = PreferEnvPath +
Bit 26 = KeepLocalIDList
+
0x0018FileAttributesno + +
Bit 0 = Read-only +
Bit 1 = Hidden +
Bit 2 = System +
Bit 3 = Volume +
Bit 4 = Directory +
Bit 5 = Archive +
Bit 6 = Encrypted? +
Bit 7 = Normal
  Bit 8 = Temporary +
Bit 9 = Sparse +
Bit 10 = Reparse point +
Bit 11 = Compressed +
Bit 12 = Offline +
Bit 13 = Not indexed +
Bit 14 = Encrypted
+
0x001cCreateDateno 
0x0024AccessDateno 
0x002cModifyDateno 
0x0034TargetFileSizeno 
0x0038IconIndexno 
0x003cRunWindowno +
0 = Hide +
1 = Normal +
2 = Show Minimized +
3 = Show Maximized +
4 = Show No Activate +
5 = Show +
6 = Minimized +
7 = Show Minimized No Activate +
8 = Show NA +
9 = Restore +
10 = Show Default
+
0x0040HotKeyno + +
0x0 = (none) +
0x90 = Num Lock +
0x91 = Scroll Lock +
0x100 = Shift +
0x200 = Control
  0x400 = Alt +
'0x30'-'0x39' = 0-9 +
'0x41'-'0x5a' = A-Z +
'0x70'-'0x87' = F1-F24
+
0x10000ItemID---> LNK ItemID Tags
0x20000LinkInfo---> LNK LinkInfo Tags
0x30004Descriptionno 
0x30008RelativePathno 
0x30010WorkingDirectoryno 
0x30020CommandLineArgumentsno 
0x30040IconFileNameno 
0xa0000000UnknownData---> LNK UnknownData Tags
0xa0000001EnvVarData---> LNK UnknownData Tags
0xa0000002ConsoleData---> LNK ConsoleData Tags
0xa0000003TrackerData---> LNK TrackerData Tags
0xa0000004ConsoleFEData---> LNK ConsoleFEData Tags
0xa0000005SpecialFolderData---> LNK UnknownData Tags
0xa0000006DarwinData---> LNK UnknownData Tags
0xa0000007IconEnvData---> LNK UnknownData Tags
0xa0000008ShimData---> LNK UnknownData Tags
0xa0000009PropertyStoreData---> LNK UnknownData Tags
0xa000000bKnownFolderData---> LNK UnknownData Tags
0xa000000cVistaIDListData---> LNK UnknownData Tags
+ +

LNK ItemID Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0032Item0032---> LNK Item0032 Tags
+ +

LNK Item0032 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
14TargetFileDOSNameno 
+ +

LNK LinkInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CommonNetworkRelLinkno 
CommonPathSuffixno 
DeviceNameno 
DriveSerialNumberno 
DriveTypeno + +
0 = Unknown +
1 = Invalid Root Path +
2 = Removable Media +
3 = Fixed Disk
  4 = Remote Drive +
5 = CD-ROM +
6 = Ram Disk
+
LocalBasePathno 
NetNameno 
NetProviderTypeno + +
0x1a0000 = AVID +
0x1b0000 = DOCUSPACE +
0x1c0000 = MANGOSOFT +
0x1d0000 = SERNET +
0x1e0000 = RIVERFRONT1 +
0x1f0000 = RIVERFRONT2 +
0x200000 = DECORB +
0x210000 = PROTSTOR +
0x220000 = FJ_REDIR +
0x230000 = DISTINCT +
0x240000 = TWINS +
0x250000 = RDR2SAMPLE +
0x260000 = CSC +
0x270000 = 3IN1 +
0x290000 = EXTENDNET +
0x2a0000 = STAC +
0x2b0000 = FOXBAT
  0x2c0000 = YAHOO +
0x2d0000 = EXIFS +
0x2e0000 = DAV +
0x2f0000 = KNOWARE +
0x300000 = OBJECT_DIRE +
0x310000 = MASFAX +
0x320000 = HOB_NFS +
0x330000 = SHIVA +
0x340000 = IBMAL +
0x350000 = LOCK +
0x360000 = TERMSRV +
0x370000 = SRT +
0x380000 = QUINCY +
0x390000 = OPENAFS +
0x3a0000 = AVID1 +
0x3b0000 = DFS
+
VolumeIDno 
VolumeLabelno 
+ +

LNK UnknownData Tags

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +

LNK ConsoleData Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8FillAttributesno 
10PopupFillAttributesno 
12ScreenBufferSizeno 
16WindowSizeno 
20WindowOriginno 
32FontSizeno 
36FontFamilyno + +
0x0 = Don't Care +
0x10 = Roman +
0x20 = Swiss
  0x30 = Modern +
0x40 = Script +
0x50 = Decorative
+
40FontWeightno 
44FontNameno 
108CursorSizeno 
112FullScreenno 
116QuickEditno 
120InsertModeno 
124WindowOriginAutono 
128HistoryBufferSizeno 
132NumHistoryBuffersno 
136RemoveHistoryDuplicatesno 
+ +

LNK TrackerData Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
16MachineIDno 
+ +

LNK ConsoleFEData Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
8CodePageno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 17, 2012 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Leaf.html b/ExifTool/html/TagNames/Leaf.html new file mode 100644 index 0000000..6decf56 --- /dev/null +++ b/ExifTool/html/TagNames/Leaf.html @@ -0,0 +1,872 @@ + + + + +Leaf Tags + + + +

Leaf Tags

+

These tags are found in .MOS images from Leaf digital camera backs as +written by Creo Leaf Capture. They exist within the Leaf-specific directory +structure of EXIF tag 0x8606. The tables below list observed Leaf tags, +however ExifTool will extract any tags found in the Leaf directories even if +they don't appear in these tables.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'JPEG_preview_data'PreviewImageno 
'JPEG_preview_info'PreviewInfono 
'PDA_histogram_data'PDAHistogramno 
'back_serial_number'BackSerialno 
'camera_profile'CameraProfile---> Leaf CameraProfile Tags
'icc_camera_profile'ICC_Profile---> ICC_Profile Tags
'icc_camera_to_tone_matrix'ToneMatrixno 
'icc_camera_to_tone_space_flow'ToneSpaceFlowno 
'icc_rgb_ws_profile'RGB_Profile---> ICC_Profile Tags
'image_offset'ImageOffsetno 
'pattern_ratation_angle'PatternAngleno("ratation" is not a typo)
+ +

Leaf CameraProfile Tags

+

All Tag ID's in the following table have a leading 'CamProf_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'back_type'CameraBackTypeno 
'capture_profile'CaptureProfile---> Leaf CaptureProfile Tags
'image_profile'ImageProfile---> Leaf ImageProfile Tags
'name'CameraNameno 
'type'CameraTypeno 
'version'CameraProfileVersionno 
+ +

Leaf CaptureProfile Tags

+

All Tag ID's in the following table have a leading 'CaptProf_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CCD_rect'CCDRectno 
'CCD_valid_rect'CCDValidRectno 
'CCD_video_rect'CCDVideoRectno 
'back_type'CaptProfBackTypeno 
'center_dark_rect'CenterDarkRectno 
'color_averages'ColorAveragesno 
'color_matrix'ColorMatrixno 
'dark_correction_type'DarkCorrectionTypeno 
'image_bounds'ImageBoundsno 
'image_fields'ImageFieldsno 
'image_offset'ImageOffsetno 
'left_dark_rect'LeftDarkRectno 
'luminance_consts'LuminanceConstsno 
'mosaic_pattern'MosaicPatternno 
'name'CaptProfNameno 
'number_of_planes'NumberOfPlanesno 
'raw_data_rotation'RawDataRotationno 
'reconstruction_type'ReconstructionTypeno 
'right_dark_rect'RightDarkRectno 
'serial_number'CaptureSerialno 
'type'CaptProfTypeno 
'version'CaptProfVersionno 
'xy_offset_info'XYOffsetInfono 
+ +

Leaf ImageProfile Tags

+

All Tag ID's in the following table have a leading 'ImgProf_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'back_type'ImgProfBackTypeno 
'image_status'ImageStatusno 
'name'ImgProfNameno 
'rotation_angle'RotationAngleno 
'shoot_setup'ShootSetup---> Leaf ShootSetup Tags
'type'ImgProfTypeno 
'version'ImgProfVersionno 
+ +

Leaf ShootSetup Tags

+

All Tag ID's in the following table have a leading 'ShootObj_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'back_type'ShootObjBackTypeno 
'camera_setup'CameraSetup---> Leaf CameraSetup Tags
'capture_setup'CaptureSetup---> Leaf CaptureSetup Tags
'color_setup'ColorSetup---> Leaf ColorSetup Tags
'look_header'LookHeader---> Leaf LookHeader Tags
'name'ShootObjNameno 
'save_setup'SaveSetup---> Leaf SaveSetup Tags
'type'ShootObjTypeno 
'version'ShootObjVersionno 
+ +

Leaf CameraSetup Tags

+

All Tag ID's in the following table have a leading 'CameraObj_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ISO_speed'ISOSpeedno 
'back_type'CameraObjBackTypeno 
'camera_type'CameraTypeno 
'lens_ID'LensIDno 
'lens_type'LensTypeno 
'name'CameraObjNameno 
'strobe'Strobeno 
'type'CameraObjTypeno 
'version'CameraObjVersionno 
+ +

Leaf CaptureSetup Tags

+

All Tag ID's in the following table have a leading 'CaptureObj_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Multi_quality'MultiQualityno 
'back_type'CaptureObjBackTypeno 
'name'CaptureObjNameno 
'neutals'Neutals---> Leaf Neutrals Tags
'selection'Selection---> Leaf Selection Tags
'sharpness'Sharpness---> Leaf Sharpness Tags
'single_quality'SingleQualityno 
'tone_curve'ToneCurve---> Leaf ToneCurve Tags
'type'CaptureObjTypeno 
'version'CaptureObjVersionno 
+ +

Leaf Neutrals Tags

+

All Tag ID's in the following table have a leading 'NeutObj_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'back_type'NeutObjBackTypeno 
'color_casts'ColorCastsno 
'highlight_end_points'HighlightEndPointsno 
'name'NeutObjNameno 
'neutrals'Neutralsno 
'shadow_end_points'ShadowEndPointsno 
'type'NeutObjTypeno 
'version'NeutObjVersionno 
+ +

Leaf Selection Tags

+

All Tag ID's in the following table have a leading 'SelObj_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'back_type'SelObjBackTypeno 
'locks'Locksno 
'name'SelObjNameno 
'orientation'Orientationno 
'rect'Rectno 
'resolution'Resolutionno 
'scale'Scaleno 
'type'SelObjTypeno 
'version'SelObjVersionno 
+ +

Leaf Sharpness Tags

+

All Tag ID's in the following table have a leading 'SharpObj_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'back_type'SharpObjBackTypeno 
'data_len'DataLenno 
'name'SharpObjNameno 
'sharp_info'SharpInfono 
'sharp_method'SharpMethodno 
'type'SharpObjTypeno 
'version'SharpObjVersionno 
+ +

Leaf ToneCurve Tags

+

All Tag ID's in the following table have a leading 'ToneObj_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'back_type'ToneObjBackTypeno 
'gamma'Gammano 
'name'ToneObjNameno 
'npts'Nptsno 
'tones'Tonesno 
'type'ToneObjTypeno 
'version'ToneObjVersionno 
+ +

Leaf ColorSetup Tags

+

All Tag ID's in the following table have a leading 'ColorObj_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'back_type'ColorObjBackTypeno 
'color_mode'ColorModeno 
'color_type'ColorTypeno 
'has_ICC'HasICCno 
'input_profile'InputProfileno 
'name'ColorObjNameno 
'output_profile'OutputProfileno 
'type'ColorObjTypeno 
'version'ColorObjVersionno 
+ +

Leaf LookHeader Tags

+

All Tag ID's in the following table have a leading 'LookHead_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'back_type'LookHeadBackTypeno 
'name'LookHeadNameno 
'type'LookHeadTypeno 
'version'LookHeadVersionno 
+ +

Leaf SaveSetup Tags

+

All Tag ID's in the following table have a leading 'SaveObj_' which +has been removed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'back_type'SaveObjBackTypeno 
'leaf_auto_active'LeafAutoActiveno 
'leaf_auto_base_name'LeafAutoBaseNameno 
'leaf_hot_folder'LeafHotFolderno 
'leaf_open_proc_HDR'LeafOpenProcHDRno 
'leaf_output_file_type'LeafOutputFileTypeno 
'leaf_save_selection'LeafSaveSelectionno 
'name'SaveObjNameno 
'std_auto_active'StdAutoActiveno 
'std_base_name'StdBaseNameno 
'std_hot_folder'StdHotFolderno 
'std_open_in_photoshop'StdOpenInPhotoshopno 
'std_output_bit_depth'StdOutputBitDepthno 
'std_output_color_mode'StdOutputColorModeno 
'std_output_file_type'StdOutputFileTypeno 
'std_oxygen'StdOxygenno 
'std_save_selection'StdSaveSelectionno 
'std_scaled_output'StdScaledOutputno 
'std_sharpen_output'StdSharpenOutputno 
'type'SaveObjTypeno 
'version'SaveObjVersionno 
+ +

Leaf SubIFD Tags

+

Leaf writes a TIFF-format sub-IFD inside IFD0 of a MOS image. No tags in +this sub-IFD are currently known, except for tags 0x8602 and 0x8606 which +really shouldn't be here anyway (so they don't appear in the table below) +because they duplicate references to the same data from tags with the same +ID in IFD0.

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Dec 11, 2019 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Lytro.html b/ExifTool/html/TagNames/Lytro.html new file mode 100644 index 0000000..52b89e4 --- /dev/null +++ b/ExifTool/html/TagNames/Lytro.html @@ -0,0 +1,123 @@ + + + + +Lytro Tags + + + +

Lytro Tags

+

Tag definitions for Lytro Light Field Picture (LFP) files. ExifTool +extracts the full JSON metadata blocks, as well as breaking them down into +individual tags. All available tags are extracted from the JSON metadata, +even if they don't appear in the table below.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AccelerometerTimeno 
AccelerometerXno 
AccelerometerYno 
AccelerometerZno 
CameraTypeno 
DateTimeOriginalno 
EmbeddedImageno(JPEG image embedded in LFP files written by Lytro Desktop)
ExposureTimeno 
FNumberno 
FirmwareVersionno 
FocalLengthno 
FocalPlaneXResolutionno(Y resolution is the same as X resolution)
FrameExposureTimeno 
ISOno 
ImageLimitExposureBiasno 
ImageModulationExposureBiasno 
JSONMetadatano+(the full JSON-format metadata blocks)
LensTemperatureno 
Makeno 
Modelno 
Orientationno1 = Horizontal (normal)
SensorSerialNumberno 
SerialNumberno 
SocTemperatureno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 18, 2014 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/M2TS.html b/ExifTool/html/TagNames/M2TS.html new file mode 100644 index 0000000..1ec4068 --- /dev/null +++ b/ExifTool/html/TagNames/M2TS.html @@ -0,0 +1,154 @@ + + + + +M2TS Tags + + + +

M2TS Tags

+

The MPEG-2 transport stream is used as a container for many different +audio/video formats (including AVCHD). This table lists information +extracted from M2TS files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AudioStreamTypeno--> M2TS StreamType Values
Durationno(the -fast option may be used to avoid scanning to the end of file to +calculate the Duration)
VideoStreamTypeno--> M2TS StreamType Values
_AC3---> M2TS AC3 Tags
_H264---> H264 Tags
_MISB---> MISB Tags
+ +

M2TS StreamType Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueStreamType
0x0= Reserved
0x1= MPEG-1 Video
0x2= MPEG-2 Video
0x3= MPEG-1 Audio
0x4= MPEG-2 Audio
0x5= ISO 13818-1 private sections
0x6= ISO 13818-1 PES private data
0x7= ISO 13522 MHEG
0x8= ISO 13818-1 DSM-CC
0x9= ISO 13818-1 auxiliary
0xa= ISO 13818-6 multi-protocol encap
0xb= ISO 13818-6 DSM-CC U-N msgs
0xc= ISO 13818-6 stream descriptors
0xd= ISO 13818-6 sections
0xe= ISO 13818-1 auxiliary
0xf= MPEG-2 AAC Audio
0x10= MPEG-4 Video
0x11= MPEG-4 LATM AAC Audio
0x12= MPEG-4 generic
0x13= ISO 14496-1 SL-packetized
0x14= ISO 13818-6 Synchronized Download Protocol
0x15= Packetized metadata
0x16= Sectioned metadata
0x17= ISO/IEC 13818-6 DSM CC Data Carousel metadata
0x18= ISO/IEC 13818-6 DSM CC Object Carousel metadata
0x19= ISO/IEC 13818-6 Synchronized Download Protocol metadata
0x1a= ISO/IEC 13818-11 IPMP
0x1b= H.264 (AVC) Video
0x1c= ISO/IEC 14496-3 (MPEG-4 raw audio)
0x1d= ISO/IEC 14496-17 (MPEG-4 text)
0x1e= ISO/IEC 23002-3 (MPEG-4 auxiliary video)
0x1f= ISO/IEC 14496-10 SVC (MPEG-4 AVC sub-bitstream)
0x20= ISO/IEC 14496-10 MVC (MPEG-4 AVC sub-bitstream)
0x21= ITU-T Rec. T.800 and ISO/IEC 15444 (JPEG 2000 video)
0x24= H.265 (HEVC) Video
0x42= Chinese Video Standard
0x7f= ISO/IEC 13818-11 IPMP (DRM)
0x80= DigiCipher II Video
0x81= A52/AC-3 Audio
0x82= HDMV DTS Audio
0x83= LPCM Audio
0x84= SDDS Audio
0x85= ATSC Program ID
0x86= DTS-HD Audio
0x87= E-AC-3 Audio
0x8a= DTS Audio
0x90= PGS Audio
0x91= A52b/AC-3 Audio
0x92= DVD_SPU vls Subtitle
0x94= SDDS Audio
0xa0= MSCODEC Video
0xea= Private ES (VC-1)
+ +

M2TS AC3 Tags

+

Tags extracted from AC-3 audio streams.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AudioBitrateno 
AudioChannelsno + +
0 = 1 + 1 +
1 = 1 +
2 = 2 +
3 = 3 +
4 = 2/1 +
5 = 3/1 +
6 = 2/2
  7 = 3/2 +
8 = 1 +
9 = 2 max +
10 = 3 max +
11 = 4 max +
12 = 5 max +
13 = 6 max
+
AudioSampleRateno0 = 48000 +
1 = 44100 +
2 = 32000
SurroundModeno0 = Not indicated +
1 = Not Dolby surround +
2 = Dolby surround
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 13, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MIE.html b/ExifTool/html/TagNames/MIE.html new file mode 100644 index 0000000..0b942e6 --- /dev/null +++ b/ExifTool/html/TagNames/MIE.html @@ -0,0 +1,1035 @@ + + + + +MIE Tags + + + +

MIE Tags

+

MIE is a flexible format which may be used as a stand-alone meta information +format, for encapsulation of other files and information, or as a trailer +appended to other file formats. The tables below represent currently +defined MIE tags, however ExifTool will also extract any other information +present in a MIE file.

+ +

When writing MIE information, some special features are supported:

+ +

1) String values may be written as ASCII (ISO 8859-1) or UTF-8. ExifTool +automatically detects the presence of wide characters and treats the string +appropriately. Internally, UTF-8 text may be converted to UTF-16 or UTF-32 +and stored in this format in the file if it is more compact.

+ +

2) All MIE string-value tags support localized text. Localized values are +written by adding a language/country code to the tag name in the form +TAG-xx_YY, where TAG is the tag name, xx is a 2-character lower +case ISO 639-1 language code, and YY is a 2-character upper case ISO +3166-1 alpha 2 country code (eg. Title-en_US). But as usual, the user +interface is case-insensitive, and ExifTool will write the correct case to +the file.

+ +

3) Some numerical MIE tags allow units of measurement to be specified. For +these tags, units may be added in brackets immediately following the value +(eg. 55(mi/h)). If no units are specified, the default units are +written.

+ +

4) ExifTool writes compressed metadata to MIE files if the Compress (-z) +option is used and Compress::Zlib is available.

+ +

See MIE1.1-20070121.pdf for the official MIE +specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'0Type'SubfileTypestring(the capitalized common extension for this type of file. If the extension +has a dot-3 abbreviation, then the longer version is used here. For +instance, JPEG and TIFF are used, not JPG and TIF)
'0Vers'MIEVersionstring(version 1.1 is assumed if not specified)
'1Directory'SubfileDirectorystring(original directory for the file)
'1Name'SubfileNamestring(the file name, including extension if it exists)
'2MIME'SubfileMIMETypestring 
'Meta'Meta---> MIE Meta Tags
'data'SubfileDataundef(the subfile data)
'rsrc'SubfileResourceundef(subfile resource fork if it exists)
'zmd5'MD5Digeststring(16-byte MD5 digest written in binary form or as a 32-character hex-encoded +ASCII string. Value is an MD5 digest of the entire 0MIE group as it would be +with the digest value itself set to all null bytes)
'zmie'TrailerSignatureundef(used as the last element in the main "0MIE" group to identify a MIE trailer +when appended to another type of file. ExifTool will create this tag if set +to any value, but always with an empty data block)
+ +

MIE Meta Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Audio'Audio---> MIE Audio Tags
'Camera'Camera---> MIE Camera Tags
'Document'Document---> MIE Doc Tags
'EXIF'EXIF---> EXIF Tags
'Geo'Geo---> MIE Geo Tags
'ICCProfile'ICC_Profile---> ICC_Profile Tags
'ID3'ID3---> ID3 Tags
'IPTC'IPTC---> IPTC Tags
'Image'Image---> MIE Image Tags
'MakerNotes'MakerNotes---> MIE MakerNotes Tags
'Preview'Preview---> MIE Preview Tags
'Thumbnail'Thumbnail---> MIE Thumbnail Tags
'Video'Video---> MIE Video Tags
'XMP'XMP---> XMP Tags
+ +

MIE Audio Tags

+

For the Audio group (and any other group containing a 'data' element), tags +refer to the contained data if present, otherwise they refer to the main +SubfileData. The 0Type and 1Name elements should exist only if data +is present.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'0Type'RelatedAudioFileTypestring(MP3 if not specified)
'1Name'RelatedAudioFileNamestring 
'Channels'Channelsint8u 
'Compression'AudioCompressionstring 
'Duration'Durationrational64u~ 
'SampleBits'SampleBitsint16u 
'SampleRate'SampleRateint32u 
'data'RelatedAudioFileundef 
+ +

MIE Camera Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Brightness'Brightnessint8s 
'ColorBalance'ColorBalancerational64u[3](RGB scaling factors)
'ColorTemperature'ColorTemperatureint32u 
'Contrast'Contrastint8s 
'DigitalZoom'DigitalZoomrational64u 
'ExposureComp'ExposureCompensationrational64s 
'ExposureMode'ExposureModestring 
'ExposureTime'ExposureTimerational64u 
'FirmwareVersion'FirmwareVersionstring 
'Flash'Flash---> MIE Flash Tags
'FocusMode'FocusModestring 
'ISO'ISOint16u 
'ISOSetting'ISOSettingint16u(0 = Auto, otherwise manual ISO speed setting)
'ImageNumber'ImageNumberint32u 
'ImageQuality'ImageQualitystring(Economy, Normal, Fine, Super Fine or Raw)
'ImageStabilization'ImageStabilizationint8u 
'Lens'Lens---> MIE Lens Tags
'Make'Makestring 
'MeasuredEV'MeasuredEVrational64s 
'Model'Modelstring 
'Orientation'Orientation---> MIE Orient Tags
'OwnerName'OwnerNamestring 
'Saturation'Saturationint8s 
'SensorSize'SensorSizerational64u[2](width and height of active sensor area in mm)
'SerialNumber'SerialNumberstring 
'Sharpness'Sharpnessint8s 
'ShootingMode'ShootingModestring 
+ +

MIE Flash Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ExposureComp'FlashExposureComprational64s 
'Fired'FlashFiredint8u0 = No +
1 = Yes
'GuideNumber'FlashGuideNumberstring 
'Make'FlashMakestring 
'Mode'FlashModestring 
'Model'FlashModelstring 
'SerialNumber'FlashSerialNumberstring 
'Type'FlashTypestring("Internal" or "External")
+ +

MIE Lens Tags

+

All recorded lens parameters (focal length, aperture, etc) include the +effects of the extender if present.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Extender'Extender---> MIE Extender Tags
'FNumber'FNumberrational64u 
'FocalLength'FocalLengthrational64u(all focal lengths in mm)
'FocusDistance'FocusDistancerational64u('m' unless 'ft' specified)
'Make'LensMakestring 
'MaxAperture'MaxAperturerational64u 
'MaxApertureAtMaxFocal'MaxApertureAtMaxFocalrational64u 
'MaxFocalLength'MaxFocalLengthrational64u 
'MinAperture'MinAperturerational64u 
'MinFocalLength'MinFocalLengthrational64u 
'Model'LensModelstring 
'OpticalZoom'OpticalZoomrational64u 
'SerialNumber'LensSerialNumberstring 
+ +

MIE Extender Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Magnification'ExtenderMagnificationrational64s 
'Make'ExtenderMakestring 
'Model'ExtenderModelstring 
'SerialNumber'ExtenderSerialNumberstring 
+ +

MIE Orient Tags

+

These tags describe the camera orientation.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Azimuth'Azimuthrational64s('deg' CW from true north unless 'deg{mag}' specified)
'Declination'Declinationrational64s 
'Elevation'Elevationrational64s 
'RightAscension'RightAscensionrational64s 
'Rotation'Rotationrational64s(CW rotation angle of camera about lens axis)
+ +

MIE Doc Tags

+

Information describing the main document, image or file.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Author'Authorstring 
'Comment'Commentstring 
'Contributors'Contributorsstring+ 
'Copyright'Copyrightstring 
'CreateDate'CreateDatestring 
'EMail'Emailstring 
'Keywords'Keywordsstring+ 
'ModifyDate'ModifyDatestring 
'OriginalDate'DateTimeOriginalstring 
'Phone'PhoneNumberstring 
'References'Referencesstring+ 
'Software'Softwarestring 
'Title'Titlestring 
'URL'URLstring 
+ +

MIE Geo Tags

+

Information related to geographic location.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Address'Addressstring 
'City'Citystring 
'Country'Countrystring 
'GPS'GPS---> MIE GPS Tags
'PostalCode'PostalCodestring 
'State'Statestring(state or province)
'UTM'UTM---> MIE UTM Tags
+ +

MIE GPS Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Altitude'GPSAltituderational64s('m' above sea level unless 'ft' specified)
'Bearing'GPSDestBearingrational64s('deg' CW from true north unless 'deg{mag}' specified)
'DateTime'GPSDateTimestring 
'Datum'GPSMapDatumstring(WGS-84 assumed if not specified)
'Differential'GPSDifferentialint8u0 = No Correction +
1 = Differential Corrected
'Distance'GPSDestDistancerational64s('km' unless 'mi' or 'nmi' specified)
'Heading'GPSTrackrational64s('deg' CW from true north unless 'deg{mag}' specified)
'Latitude'GPSLatituderational64s[n](1 to 3 numbers: degrees, minutes then seconds. South latitudes are stored +as all negative numbers, but may be entered as positive numbers with a +trailing 'S' for convenience. For example, these are all equivalent: "-40 +-30", "-40.5", "40 30 0.00 S")
'Longitude'GPSLongituderational64s[n](1 to 3 numbers: degrees, minutes then seconds. West longitudes are +negative, but may be entered as positive numbers with a trailing 'W')
'MeasureMode'GPSMeasureModeint8u2 = 2-D +
3 = 3-D
'Satellites'GPSSatellitesstring 
'Speed'GPSSpeedrational64s('km/h' unless 'mi/h', 'm/s' or 'kn' specified)
+ +

MIE UTM Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Datum'UTMMapDatumstring(WGS-84 assumed if not specified)
'Easting'UTMEastingstring 
'Northing'UTMNorthingstring 
'Zone'UTMZoneint8s 
+ +

MIE Image Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'0Type'FullSizeImageTypestring(JPEG if not specified)
'1Name'FullSizeImageNamestring 
'BitDepth'BitDepthint16u 
'ColorSpace'ColorSpacestring(standard ColorSpace values are "sRGB" and "Adobe RGB")
'Components'ComponentsConfigurationstring(string composed of R, G, B, Y, Cb and Cr)
'Compression'CompressionRatiorational32u 
'ImageSize'ImageSizeint16u[n](2 or 3 values, for number of XY or XYZ pixels)
'OriginalImageSize'OriginalImageSizeint16u[n](size of original image before cropping)
'Resolution'Resolutionrational64u[n](1 to 3 values. A single value for equal resolution in all directions, or +separate X, Y and Z values if necessary. Units are '/in' unless '/cm', +'/deg', '/arcmin', '/arcsec' or '' specified)
'data'FullSizeImageundef 
+ +

MIE MakerNotes Tags

+

MIE maker notes are contained within separate groups for each manufacturer +to avoid name conflicts.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Canon'Canon---> MIE Canon Tags
'Casio'Casio---> MIE Unknown Tags
'FujiFilm'FujiFilm---> MIE Unknown Tags
'Kodak'Kodak---> MIE Unknown Tags
'KonicaMinolta'KonicaMinolta---> MIE Unknown Tags
'Nikon'Nikon---> MIE Unknown Tags
'Olympus'Olympus---> MIE Unknown Tags
'Panasonic'Panasonic---> MIE Unknown Tags
'Pentax'Pentax---> MIE Unknown Tags
'Ricoh'Ricoh---> MIE Unknown Tags
'Sigma'Sigma---> MIE Unknown Tags
'Sony'Sony---> MIE Unknown Tags
+ +

MIE Canon Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'VRD'CanonVRD---> CanonVRD Tags
+ +

MIE Unknown Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

MIE Preview Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'0Type'PreviewImageTypestring(JPEG if not specified)
'1Name'PreviewImageNamestring 
'ImageSize'PreviewImageSizeint16u[n](2 or 3 values, for number of XY or XYZ pixels)
'data'PreviewImageundef 
+ +

MIE Thumbnail Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'0Type'ThumbnailImageTypestring(JPEG if not specified)
'1Name'ThumbnailImageNamestring 
'ImageSize'ThumbnailImageSizeint16u[n](2 or 3 values, for number of XY or XYZ pixels)
'data'ThumbnailImageundef 
+ +

MIE Video Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'0Type'RelatedVideoFileTypestring(MOV if not specified)
'1Name'RelatedVideoFileNamestring 
'Codec'Codecstring 
'Duration'Durationrational64u~ 
'data'RelatedVideoFileundef 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 1, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MIFF.html b/ExifTool/html/TagNames/MIFF.html new file mode 100644 index 0000000..b8d1e96 --- /dev/null +++ b/ExifTool/html/TagNames/MIFF.html @@ -0,0 +1,197 @@ + + + + +MIFF Tags + + + +

MIFF Tags

+

The MIFF (Magick Image File Format) format allows aribrary tag names to be +used. Only the standard tag names are listed below, however ExifTool will +decode any tags found in the image.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'background-color'BackgroundColorno 
'blue-primary'BluePrimaryno 
'border-color'BorderColorno 
'class'Classno 
'colors'Colorsno 
'colorspace'ColorSpaceno 
'columns'ImageWidthno 
'compression'Compressionno 
'delay'Delayno 
'depth'Depthno 
'dispose'Disposeno 
'gamma'Gammano 
'green-primary'GreenPrimaryno 
'id'IDno 
'iterations'Iterationsno 
'label'Labelno 
'matt-color'MattColorno 
'matte'Matteno 
'montage'Montageno 
'packets'Packetsno 
'page'Pageno 
'profile-APP1'APP1_Profile---> EXIF Tags +
--> XMP Tags
'profile-exif'EXIF_Profile---> EXIF Tags
'profile-icc'ICC_Profile---> ICC_Profile Tags
'profile-iptc'IPTC_Profile---> Photoshop Tags
'profile-xmp'XMP_Profile---> XMP Tags
'red-primary'RedPrimaryno 
'rendering-intent'RenderingIntentno 
'resolution'Resolutionno 
'rows'ImageHeightno 
'scene'Sceneno 
'signature'Signatureno 
'units'Unitsno 
'white-point'WhitePointno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 21, 2011 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MISB.html b/ExifTool/html/TagNames/MISB.html new file mode 100644 index 0000000..dcbc3eb --- /dev/null +++ b/ExifTool/html/TagNames/MISB.html @@ -0,0 +1,767 @@ + + + + +MISB Tags + + + +

MISB Tags

+

These tags are extracted from STANAG-4609 MISB (Motion Industry Standards +Board) KLV-format metadata in M2TS videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'060E2B34030101010E01030302000000'Security---> MISB Security Tags
'060e2b3402030101434e415644494147'ChurchillNav---> MISB ChurchillNav Tags
'060e2b34020b01010e01030101000000'UASDataLink---> MISB UASDatalink Tags
'<other>'Unknown---> MISB Unknown Tags
+ +

MISB Security Tags

+

Tags extracted from the MISB ST 0102.11 Security Metadata local set.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001SecurityClassificationno1 = Unclassified +
2 = Restricted +
3 = Confidential +
4 = Secret +
5 = Top Secret
0x0002ClassifyingCountryCodeMethodno +
1 = ISO-3166 Two Letter +
2 = ISO-3166 Three Letter +
3 = FIPS 10-4 Two Letter +
4 = FIPS 10-4 Four Letter +
5 = ISO-3166 Numeric +
6 = 1059 Two Letter +
7 = 1059 Three Letter +
10 = FIPS 10-4 Mixed +
11 = ISO 3166 Mixed +
12 = STANAG 1059 Mixed +
13 = GENC Two Letter +
14 = GENC Three Letter +
15 = GENC Numeric +
16 = GENC Mixed
+
0x0003ClassifyingCountryno 
0x0004SecuritySCI-SHIInformationno 
0x0005Caveatsno 
0x0006ReleasingInstructionsno 
0x0007ClassifiedByno 
0x0008DerivedFromno 
0x0009ClassificationReasonno 
0x000aDeclassificationDateno 
0x000bClassificationAndMarkingSystemno 
0x000cObjectCountryCodingMethodno +
1 = ISO-3166 Two Letter +
2 = ISO-3166 Three Letter +
3 = ISO-3166 Numeric +
4 = FIPS 10-4 Two Letter +
5 = FIPS 10-4 Four Letter +
6 = 1059 Two Letter +
7 = 1059 Three Letter +
13 = GENC Two Letter +
14 = GENC Three Letter +
15 = GENC Numeric +
64 = GENC AdminSub
+
0x000dObjectCountryCodesno 
0x000eClassificationCommentsno 
0x000fUMIDno 
0x0010StreamIDno 
0x0011TransportStreamIDno 
0x0015ItemDesignatorIDno 
0x0016SecurityVersionno 
0x0017ClassifyingCountryCodingMethodDateno 
0x0018ObjectCountryCodingMethodDateno 
+ +

MISB ChurchillNav Tags

+

Proprietary tags used by Churchill Navigation units. These tags are all +currently unknown, but extracted with the Unknown option.

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

MISB UASDatalink Tags

+

Tags extracted from the MISB ST 0601.11 UAS Datalink local set.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001Checksumno 
0x0002GPSDateTimeno 
0x0003MissionIDno 
0x0004TailNumberno 
0x0005GPSTrackno 
0x0006PitchAngleno 
0x0007RollAngleno 
0x0008TrueAirspeedno 
0x0009IndicatedAirspeedno 
0x000aProjectIDCodeno 
0x000bSensorNameno 
0x000cImageCoordinateSystemno 
0x000dGPSLatitudeno 
0x000eGPSLongitudeno 
0x000fGPSAltitudeno 
0x0010HorizontalFieldOfViewno 
0x0011VerticalFieldOfViewno 
0x0012SensorRelativeAzimuthAngleno 
0x0013SensorRelativeElevationAngleno 
0x0014SensorRelativeRollAngleno 
0x0015SlantRangeno 
0x0016TargetWidthno 
0x0017FrameCenterLatitudeno 
0x0018FrameCenterLongitudeno 
0x0019FrameCenterElevationno 
0x001aOffsetCornerLatitude1no 
0x001bOffsetCornerLongitude1no 
0x001cOffsetCornerLatitude2no 
0x001dOffsetCornerLongitude2no 
0x001eOffsetCornerLatitude3no 
0x001fOffsetCornerLongitude3no 
0x0020OffsetCornerLatitude4no 
0x0021OffsetCornerLongitude4no 
0x0022IcingDetectedno0 = n/a +
1 = No +
2 = Yes
0x0023WindDirectionno 
0x0024WindSpeedno(m/s)
0x0025StaticPressureno(mbar)
0x0026DensityAltitudeno 
0x0027AirTemperatureno 
0x0028TargetLocationLatitudeno 
0x0029TargetLocationLongitudeno 
0x002aTargetLocationElevationno 
0x002bTargetTrackGateWidthno 
0x002cTargetTrackGateHeightno 
0x002dTargetErrorEstimateCE90no 
0x002eTargetErrorEstimateLE90no 
0x002fGenericFlagData01no +
Bit 0 = Laser range +
Bit 1 = Auto-track +
Bit 2 = IR polarity black +
Bit 3 = Icing detected +
Bit 4 = Slant range measured +
Bit 5 = Image invalid
+
0x0030SecurityLocalMetadataSet---> MISB Security Tags
0x0031DifferentialPressureno 
0x0032AngleOfAttackno 
0x0033VerticalSpeedno(m/s)
0x0034SideslipAngleno 
0x0035AirfieldBarometricPressureno 
0x0036AirfieldElevationno 
0x0037RelativeHumidityno 
0x0038GPSSpeedno(m/s)
0x0039GroundRangeno 
0x003aFuelRemainingno(kg)
0x003bCallSignno 
0x003cWeaponLoadno 
0x003dWeaponFiredno 
0x003eLaserPRFCodeno 
0x003fSensorFieldOfViewNameno + +
0 = Ultranarrow +
1 = Narrow +
2 = Medium +
3 = Wide
  4 = Ultrawide +
5 = Narrow Medium +
6 = 2x Ultranarrow +
7 = 4x Ultranarrow
+
0x0040MagneticHeadingno 
0x0041UAS_LSVersionNumberno 
0x0042TargetLocationCovarianceMatrixno 
0x0043AlternateLatitudeno 
0x0044AlternateLongitudeno 
0x0045AlternateAltitudeno 
0x0046AlternateNameno 
0x0047AlternateHeadingno 
0x0048EventStartTimeno 
0x0049RVTLocalSet---> MISB Unknown Tags
0x004aVMTIDataSet---> MISB Unknown Tags
0x004bSensorEllipsoidHeightno 
0x004cAlternateEllipsoidHeightno 
0x004dOperationalModeno0 = Other +
1 = Operational +
2 = Training +
3 = Exercise +
4 = Maintenance
0x004eFrameCenterHeightAboveEllipsoidno 
0x004fSensorVelocityNorthno 
0x0050SensorVelocityEastno 
0x0051ImageHorizonPixelPackno 
0x0052CornerLatitude1no 
0x0053CornerLongitude1no 
0x0054CornerLatitude2no 
0x0055CornerLongitude2no 
0x0056CornerLatitude3no 
0x0057CornerLongitude3no 
0x0058CornerLatitude4no 
0x0059CornerLongitude4no 
0x005aFullPitchAngleno 
0x005bFullRollAngleno 
0x005cFullAngleOfAttackno 
0x005dFullSideslipAngleno 
0x005eMIISCoreIdentifierno 
0x005fSARMotionImageryData---> MISB Unknown Tags
0x0060TargetWidthExtendedno 
0x0061RangeImageLocalSet---> MISB Unknown Tags
0x0062GeoregistrationLocalSet---> MISB Unknown Tags
0x0063CompositeImagingLocalSet---> MISB Unknown Tags
0x0064SegmentLocalSet---> MISB Unknown Tags
0x0065AmendLocalSet---> MISB Unknown Tags
0x0066SDCC-FLPno 
0x0067DensityAltitudeExtendedno 
0x0068SensorEllipsoidHeightExtendedno 
0x0069AlternateEllipsoidHeightExtendedno 
+ +

MISB Unknown Tags

+

Other tags are extracted with the Unknown option.

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 13, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MNG.html b/ExifTool/html/TagNames/MNG.html new file mode 100644 index 0000000..71cf64d --- /dev/null +++ b/ExifTool/html/TagNames/MNG.html @@ -0,0 +1,848 @@ + + + + +MNG Tags + + + +

MNG Tags

+

This table contains definitions for tags found in MNG and JNG images. MNG +is a superset of PNG and JNG, so a MNG image may contain any of these tags +as well as any PNG tags. Conversely, only some of these tags are valid for +JNG images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'BACK'Background---> MNG Background Tags
'BASI'BasisObject---> MNG BasisObject Tags
'CLIP'ClipObjects---> MNG ClipObjects Tags
'CLON'CloneObject---> MNG CloneObject Tags
'DBYK'DropByKeywordno 
'DEFI'DefineObject---> MNG DefineObject Tags
'DHDR'DeltaPNGHeader---> MNG DeltaPNGHeader Tags
'DISC'DiscardObjectsno 
'DROP'DropChunksno 
'FRAM'Frameno 
'JHDR'JNGHeader---> MNG JNGHeader Tags
'LOOP'Loop---> MNG Loop Tags
'MAGN'MagnifyObject---> MNG MagnifyObject Tags
'MHDR'MNGHeader---> MNG MNGHeader Tags
'MOVE'MoveObjects---> MNG MoveObjects Tags
'ORDR'OrderingRestrictionsno 
'PAST'PasteImage---> MNG PasteImage Tags
'PPLT'PartialPaletteno 
'PROM'PromoteParent---> MNG PromoteParent Tags
'SAVE'SaveObjectsno 
'SEEK'SeekPointno 
'SHOW'ShowObjects---> MNG ShowObjects Tags
'TERM'TerminationAction---> MNG TerminationAction Tags
'eXPi'ExportImage---> MNG ExportImage Tags
'fPRI'FramePriority---> MNG FramePriority Tags
'nEED'ResourcesNeededno 
'pHYg'GlobalPixelSize---> PNG PhysicalPixel Tags
+ +

MNG Background Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0BackgroundColorno 
6MandatoryBackgroundno0 = Color and Image Advisory +
1 = Color Mandatory, Image Advisory +
2 = Color Advisory, Image Mandatory +
3 = Color and Image Mandatory
7BackgroundImageIDno 
9BackgroundTilingno0 = No +
1 = Yes
+ +

MNG BasisObject Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ImageWidthno 
4ImageHeightno 
8BitDepthno 
9ColorTypeno0 = Grayscale +
2 = RGB +
3 = Palette +
4 = Grayscale with Alpha +
6 = RGB with Alpha
10Compressionno0 = Deflate/Inflate
11Filterno0 = Adaptive
12Interlaceno0 = Noninterlaced +
1 = Adam7 Interlace
13RedSampleno 
17GreenSampleno 
21BlueSampleno 
25AlphaSampleno 
26Viewableno 
+ +

MNG ClipObjects Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FirstObjectno 
2LastObjectno 
4DeltaTypeno0 = Absolute +
1 = Relative
5ClipBoundaryno 
+ +

MNG CloneObject Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SourceIDno 
2CloneIDno 
4CloneTypeno0 = Full +
1 = Parital +
2 = Renumber object
5DoNotShowno 
6ConcreteFlagno 
7LocalDeltaTypeno0 = Absolute +
1 = Relative
8DeltaXYno 
+ +

MNG DefineObject Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ObjectIDno 
2DoNotShowno 
3ConcreteFlagno 
4XYLocationno 
12ClippingBoundaryno 
+ +

MNG DeltaPNGHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ObjectIDno 
2ImageTypeno0 = Unspecified +
1 = PNG +
2 = JNG
3DeltaTypeno + +
0 = Full Replacement +
1 = Pixel Addition +
2 = Alpha Addition +
3 = Color Addition
  4 = Pixel Replacement +
5 = Alpha Replacement +
6 = Color Replacement +
7 = No Change
+
4BlockSizeno 
12BlockLocationno 
+ +

MNG JNGHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ImageWidthno 
4ImageHeightno 
8ColorTypeno8 = Gray +
10 = Color +
12 = Gray Alpha +
14 = Color Alpha
9BitDepthno 
10Compressionno8 = Huffman-coded baseline JPEG
11Interlaceno0 = Sequential +
8 = Progressive
12AlphaBitDepthno 
13AlphaCompressionno0 = MNG Grayscale IDAT +
8 = JNG 8-bit Grayscale JDAA
14AlphaFilterno0 = Adaptive MNG (N/A for JPEG)
15AlphaInterlaceno0 = Noninterlaced
+ +

MNG Loop Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0NestLevelno 
1IterationCountno 
5TerminationConditionno +
0 = Deterministic, not cacheable +
1 = Decoder discretion, not cacheable +
2 = User discretion, not cacheable +
3 = External signal, not cacheable +
4 = Deterministic, cacheable +
5 = Decoder discretion, cacheable +
6 = User discretion, cacheable +
7 = External signal, cacheable
+
6IterationMinMaxno 
14SignalNumberno 
+ +

MNG MagnifyObject Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FirstObjectIDno 
2LastObjectIDno 
4XMethodno0 = No Magnification +
1 = Pixel Replication +
2 = Linear Interpolation +
3 = Closest Pixel +
4 = Color Linear Interpolation and Alpha Closest Pixel +
5 = Color Closest Pixel and Alpha Linear Interpolation
5XMagno 
7YMagno 
9LeftMagno 
11RightMagno 
13TopMagno 
15BottomMagno 
17YMethodno0 = No Magnification +
1 = Pixel Replication +
2 = Linear Interpolation +
3 = Closest Pixel +
4 = Color Linear Interpolation and Alpha Closest Pixel +
5 = Color Closest Pixel and Alpha Linear Interpolation
+ +

MNG MNGHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0ImageWidthno 
1ImageHeightno 
2TicksPerSecondno 
3NominalLayerCountno 
4NominalFrameCountno 
5NominalPlayTimeno 
6SimplicityProfileno 
+ +

MNG MoveObjects Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FirstObjectno 
2LastObjectno 
4DeltaTypeno0 = Absolute +
1 = Relative
5DeltaXYno 
+ +

MNG PasteImage Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0DestinationIDno 
2TargetDeltaTypeno0 = Absolute +
1 = Relative
3TargetXYno 
11SourceIDno 
13CompositionModeno0 = Over +
1 = Replace +
2 = Under
14Orientationno0 = Same as source +
2 = Flipped left-right, then up-down +
4 = Flipped left-right +
6 = Flipped up-down +
8 = Tiled
15OffsetOriginno0 = Desination Origin +
1 = Target Origin
16OffsetXYno 
24BoundaryOriginno0 = Desination Origin +
1 = Target Origin
25PastClippingBoundaryno 
+ +

MNG PromoteParent Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0NewColorTypeno 
1NewBitDepthno 
2FillMethodno0 = Bit Replication +
1 = Zero Fill
+ +

MNG ShowObjects Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FirstObjectno 
2LastObjectno 
4ShowModeno 
+ +

MNG TerminationAction Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0TerminationActionno0 = Show Last Frame +
1 = Display Nothing +
2 = Show First Frame +
3 = Repeat Sequence
1IterationEndActionno0 = Show Last Frame +
1 = Display Nothing +
2 = Show First Frame
2Delayno 
6IterationMaxno 
+ +

MNG ExportImage Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SnapshotIDno 
2SnapshotNameno 
+ +

MNG FramePriority Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0DeltaTypeno0 = Absolute +
1 = Relative
2Priorityno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 25, 2006 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MOI.html b/ExifTool/html/TagNames/MOI.html new file mode 100644 index 0000000..539d3fd --- /dev/null +++ b/ExifTool/html/TagNames/MOI.html @@ -0,0 +1,61 @@ + + + + +MOI Tags + + + +

MOI Tags

+

MOI files store information about associated MOD or TOD files, and are +written by some JVC, Canon and Panasonic camcorders.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0MOIVersionno 
6DateTimeOriginalno 
14Durationno 
128AspectRationo 
132AudioCodecno0xc1 = AC3 +
0x4001 = MPEG
134AudioBitrateno 
218VideoBitrateno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Dec 15, 2014 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MPC.html b/ExifTool/html/TagNames/MPC.html new file mode 100644 index 0000000..5bda5eb --- /dev/null +++ b/ExifTool/html/TagNames/MPC.html @@ -0,0 +1,98 @@ + + + + +MPC Tags + + + +

MPC Tags

+

Tags used in Musepack (MPC) audio files. ExifTool also extracts ID3 and APE +information from these files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Bit032-063'TotalFramesno 
'Bit080-081'SampleRateno0 = 44100 +
1 = 48000 +
2 = 37800 +
3 = 32000
'Bit084-087'Qualityno +
1 = Unstable/Experimental +
5 = 0 +
6 = 1 +
7 = 2 (Telephone) +
8 = 3 (Thumb) +
9 = 4 (Radio) +
10 = 5 (Standard) +
11 = 6 (Xtreme) +
12 = 7 (Insane) +
13 = 8 (BrainDead) +
14 = 9 +
15 = 10
+
'Bit088-093'MaxBandno 
'Bit096-111'ReplayGainTrackPeakno 
'Bit112-127'ReplayGainTrackGainno 
'Bit128-143'ReplayGainAlbumPeakno 
'Bit144-159'ReplayGainAlbumGainno 
'Bit179'FastSeekno0 = No +
1 = Yes
'Bit191'Gaplessno0 = No +
1 = Yes
'Bit216-223'EncoderVersionno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Nov 20, 2006 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MPEG.html b/ExifTool/html/TagNames/MPEG.html new file mode 100644 index 0000000..2523c76 --- /dev/null +++ b/ExifTool/html/TagNames/MPEG.html @@ -0,0 +1,250 @@ + + + + +MPEG Tags + + + +

MPEG Tags

+

+The MPEG format doesn't specify any file-level meta information. In lieu of +this, information is extracted from the first audio and video frame headers +in the file. +

+

MPEG Audio Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Bit11-12'MPEGAudioVersionno0 = 2.5 +
2 = 2 +
3 = 1
'Bit13-14'AudioLayerno1 = 3 +
2 = 2 +
3 = 1
'Bit16-19'AudioBitrateno(version 1, layer 1) +
(version 1, layer 2) +
(version 1, layer 3) +
(version 2 or 2.5, layer 1) +
(version 2 or 2.5, layer 2 or 3)
'Bit20-21'SampleRateno(version 1) +
0 = 44100 +
1 = 48000 +
2 = 32000 +
(version 2) +
0 = 22050 +
1 = 24000 +
2 = 16000 +
(version 2.5) +
0 = 11025 +
1 = 12000 +
2 = 8000
'Bit24-25'ChannelModeno0 = Stereo +
1 = Joint Stereo +
2 = Dual Channel +
3 = Single Channel
'Bit26'MSStereono(layer 3) +
0 = Off +
1 = On
'Bit26-27'ModeExtensionno(layer 1 or 2) +
0 = Bands 4-31 +
1 = Bands 8-31 +
2 = Bands 12-31 +
3 = Bands 16-31
'Bit27'IntensityStereono(layer 3) +
0 = Off +
1 = On
'Bit28'CopyrightFlagno0 = False +
1 = True
'Bit29'OriginalMediano0 = False +
1 = True
'Bit30-31'Emphasisno0 = None +
1 = 50/15 ms +
2 = reserved +
3 = CCIT J.17
+ +

MPEG Video Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Bit00-11'ImageWidthno 
'Bit12-23'ImageHeightno 
'Bit24-27'AspectRationo +
2 => 0.6735 = 0.6735 +
3 => 0.7031 = 16:9, 625 line, PAL +
4 => 0.7615 = 0.7615 +
5 => 0.8055 = 0.8055 +
6 => 0.8437 = 16:9, 525 line, NTSC +
7 => 0.8935 = 0.8935 +
8 => 0.9157 = 4:3, 625 line, PAL, CCIR601 +
9 => 0.9815 = 0.9815 +
1 => 1 = 1:1 +
12 => 1.095 = 4:3, 525 line, NTSC, CCIR601 +
10 => 1.0255 = 1.0255 +
11 => 1.0695 = 1.0695 +
13 => 1.1575 = 1.1575 +
14 => 1.2015 = 1.2015
+
'Bit28-31'FrameRateno 
'Bit32-49'VideoBitrateno 
+ +

MPEG Xing Tags

+

These tags are extracted from the Xing/Info frame.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Encoderno 
LameHeader---> MPEG Lame Tags
LameQualityno 
LameVBRQualityno 
VBRBytesno 
VBRFramesno 
VBRScaleno 
+ +

MPEG Lame Tags

+

Tags extracted from Lame 3.90 or later header.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
9LameMethodno[val & 0xf] + +
1 = CBR +
2 = ABR +
3 = VBR (old/rh) +
4 = VBR (new/mtrh)
  5 = VBR (old/rh) +
6 = VBR +
8 = CBR (2-pass) +
9 = ABR (2-pass)
+
10LameLowPassFilterno 
20LameBitrateno 
24LameStereoModeno[val >> 2 & 0x7] + +
0 = Mono +
1 = Stereo +
2 = Dual Channels +
3 = Joint Stereo
  4 = Forced Joint Stereo +
6 = Auto +
7 = Intensity Stereo
+
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 24, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MPF.html b/ExifTool/html/TagNames/MPF.html new file mode 100644 index 0000000..d1bfd58 --- /dev/null +++ b/ExifTool/html/TagNames/MPF.html @@ -0,0 +1,201 @@ + + + + +MPF Tags + + + +

MPF Tags

+

These tags are part of the CIPA Multi-Picture Format specification, and are +found in the APP2 "MPF" segment of JPEG images. MPImage data referenced +from this segment is stored as a JPEG trailer. The MPF tags are not +writable, however the MPF segment may be deleted as a group (with "MPF:All") +but then the JPEG trailer should also be deleted (with "Trailer:All"). See +https://web.archive.org/web/20190713230858/http://www.cipa.jp/std/documents/e/DC-007_E.pdf +for the official specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0xb000MPFVersionno 
0xb001NumberOfImagesno 
0xb002MPImageList---> MPF MPImage Tags
0xb003ImageUIDListno 
0xb004TotalFramesno 
0xb101MPIndividualNumno 
0xb201PanOrientationno(long integer is split into 4 bytes) +
[Value 2] +
0x0 = [unused] +
0x1 = Start at top right +
0x2 = Start at top left +
0x3 = Start at bottom left +
0x4 = Start at bottom right +
[Value 3]
+
0x1 = Left to right +
0x2 = Right to left +
0x3 = Top to bottom +
0x4 = Bottom to top +
0x10 = Clockwise +
0x20 = Counter clockwise +
0x30 = Zigzag (row start) +
0x40 = Zigzag (column start)
+
0xb202PanOverlapHno 
0xb203PanOverlapVno 
0xb204BaseViewpointNumno 
0xb205ConvergenceAngleno 
0xb206BaselineLengthno 
0xb207VerticalDivergenceno 
0xb208AxisDistanceXno 
0xb209AxisDistanceYno 
0xb20aAxisDistanceZno 
0xb20bYawAngleno 
0xb20cPitchAngleno 
0xb20dRollAngleno 
+ +

MPF MPImage Tags

+

The first MPF "Large Thumbnail" image is extracted as PreviewImage, and the +rest of the embedded MPF images are extracted as MPImage#. The +ExtractEmbedded (-ee) option may be used to extract information from these +embedded images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1MPImageFlagsno[val >> 27 & 0x1f] +
Bit 2 = Representative image +
Bit 3 = Dependent child image +
Bit 4 = Dependent parent image
0.2MPImageFormatno[val >> 24 & 0x7] +
0 = JPEG
0.3MPImageTypeno[val & 0xffffff] +
0x0 = Undefined +
0x10001 = Large Thumbnail (VGA equivalent) +
0x10002 = Large Thumbnail (full HD equivalent) +
0x20001 = Multi-frame Panorama +
0x20002 = Multi-frame Disparity +
0x20003 = Multi-angle +
0x30000 = Baseline MP Primary Image +
0x40000 = Original Preservation Image
+
4MPImageLengthno 
8MPImageStartno 
12DependentImage1EntryNumberno 
14DependentImage2EntryNumberno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 8, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MRC.html b/ExifTool/html/TagNames/MRC.html new file mode 100644 index 0000000..b888513 --- /dev/null +++ b/ExifTool/html/TagNames/MRC.html @@ -0,0 +1,731 @@ + + + + +MRC Tags + + + +

MRC Tags

+

Tags extracted from Medical Research Council (MRC) format imaging files. +See https://www.ccpem.ac.uk/mrc_format/mrc2014.php for the specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0ImageWidthno 
1ImageHeightno 
2ImageDepthno(number of sections. Use ExtractEmbedded option to extract metadata for all +sections)
3ImageModeno +
0 = 8-bit signed integer +
1 = 16-bit signed integer +
2 = 32-bit signed real +
3 = complex 16-bit integer +
4 = complex 32-bit real +
6 = 16-bit unsigned integer
+
4StartPointno 
7GridSizeno 
10CellWidthno(cell size in angstroms)
11CellHeightno 
12CellDepthno 
13CellAlphano 
14CellBetano 
15CellGammano 
16ImageWidthAxisno1 = X +
2 = Y +
3 = Z
17ImageHeightAxisno1 = X +
2 = Y +
3 = Z
18ImageDepthAxisno1 = X +
2 = Y +
3 = Z
19DensityMinno 
20DensityMaxno 
21DensityMeanno 
22SpaceGroupNumberno 
23ExtendedHeaderSizeno 
26ExtendedHeaderTypeno 
27MRCVersionno 
49Originno 
53MachineStampno 
54RMSDeviationno 
55NumberOfLabelsno 
56Label0no 
76Label1no 
96Label2no 
116Label3no 
136Label4no 
156Label5no 
176Label6no 
196Label7no 
216Label8no 
236Label9no 
+ +

MRC FEI12 Tags

+

Tags extracted from FEI1 and FEI2 extended headers.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0MetadataSizeno 
4MetadataVersionno 
8Bitmask1no 
12TimeStampno 
20MicroscopeTypeno 
36MicroscopeIDno 
52Applicationno 
68AppVersionno 
84HighTensionno(volts)
92Doseno(electrons/m2)
100AlphaTiltno 
108BetaTiltno 
116XStageno 
124YStageno 
132ZStageno 
140TiltAxisAngleno 
148DualAxisRotno 
156PixelSizeXno 
164PixelSizeYno 
220Defocusno 
228STEMDefocusno 
236AppliedDefocusno 
244InstrumentModeno1 = TEM +
2 = STEM
248ProjectionModeno1 = Diffraction +
2 = Imaging
252ObjectiveLensno 
268HighMagnificationModeno 
284ProbeModeno1 = Nano +
2 = Micro
288EFTEMOnno0 = No +
1 = Yes
289Magnificationno 
297Bitmask2no 
301CameraLengthno 
309SpotIndexno 
313IlluminationAreano 
321Intensityno 
329ConvergenceAngleno 
337IlluminationModeno 
353WideConvergenceAngleRangeno0 = No +
1 = Yes
354SlitInsertedno0 = No +
1 = Yes
355SlitWidthno 
363AccelVoltOffsetno 
371DriftTubeVoltno 
379EnergyShiftno 
387ShiftOffsetXno 
395ShiftOffsetYno 
403ShiftXno 
411ShiftYno 
419IntegrationTimeno 
427BinningWidthno 
431BinningHeightno 
435CameraNameno 
451ReadoutAreaLeftno 
455ReadoutAreaTopno 
459ReadoutAreaRightno 
463ReadoutAreaBottomno 
467CetaNoiseReductno0 = No +
1 = Yes
468CetaFramesSummedno 
472DirectDetElectronCountingno0 = No +
1 = Yes
473DirectDetAlignFramesno0 = No +
1 = Yes
490Bitmask3no 
518PhasePlateno0 = No +
1 = Yes
519STEMDetectorNameno 
535Gainno 
543Offsetno 
571DwellTimeno 
579FrameTimeno 
587ScanSizeLeftno 
591ScanSizeTopno 
595ScanSizeRightno 
599ScanSizeBottomno 
603FullScanFOV_Xno 
611FullScanFOV_Yno 
619Elementno 
635EnergyIntervalLowerno 
643EnergyIntervalHigherno 
651Methodno 
655IsDoseFractionno0 = No +
1 = Yes
656FractionNumberno 
660StartFrameno 
664EndFrameno 
668InputStackFilenameno 
748Bitmask4no 
752AlphaTiltMinno 
760AlphaTiltMaxno 
768ScanRotationno 
776DiffractionPatternRotationno 
784ImageRotationno 
792ScanModeEnumerationno0 = Other +
1 = Raster +
2 = Serpentine
796AcquisitionTimeStampno 
804DetectorCommercialNameno 
820StartTiltAngleno 
828EndTiltAngleno 
836TiltPerImageno 
844TitlSpeedno 
852BeamCenterXno 
856BeamCenterYno 
860CFEGFlashTimeStampno 
868PhasePlatePositionno 
872ObjectiveApertureno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Apr 22, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MWG.html b/ExifTool/html/TagNames/MWG.html new file mode 100644 index 0000000..7376f9b --- /dev/null +++ b/ExifTool/html/TagNames/MWG.html @@ -0,0 +1,591 @@ + + + + +MWG Tags + + + +

MWG Tags

+

+The Metadata Working Group (MWG) recommends techniques to allow certain +overlapping EXIF, IPTC and XMP tags to be reconciled when reading, and +synchronized when writing. The MWG Composite tags below are designed to aid +in the implementation of these recommendations. As well, the MWG defines +new XMP tags which are listed in the subsequent tables below. See +https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf +for the official MWG specification. +

+

MWG Composite Tags

+

The table below lists special Composite tags which are used to access other +tags based on the MWG 2.0 recommendations. These tags are only accessible +when explicitly loaded, but this is done automatically by the exiftool +application if MWG is specified as a group for any tag on the command line, +or manually with the -use MWG option. Via the API, the MWG Composite +tags are loaded by calling "Image::ExifTool::MWG::Load()".

+ +

When reading, the value of each MWG tag is Derived From the specified +tags based on the MWG guidelines. When writing, the appropriate associated +tags are written. The value of the IPTCDigest tag is updated automatically +when the IPTC is changed if either the IPTCDigest tag didn't exist +beforehand or its value agreed with the original IPTC digest (indicating +that the XMP is synchronized with the IPTC). IPTC information is written +only if the original file contained IPTC.

+ +

Loading the MWG module activates "strict MWG conformance mode", which has +the effect of causing EXIF, IPTC and XMP in non-standard locations to be +ignored when reading, as per the MWG recommendations. Instead, a "Warning" +tag is generated when non-standard metadata is encountered. This feature +may be disabled by setting $Image::ExifTool::MWG::strict = 0 in the +ExifTool config file (or from your Perl script when using the API). Note +that the behaviour when writing is not changed: ExifTool always creates new +records only in the standard location, but writes new tags to any +EXIF/IPTC/XMP records that exist.

+ +

Contrary to the EXIF specification, the MWG recommends that EXIF "ASCII" +string values be stored as UTF-8. To honour this, the exiftool application +sets the default internal EXIF string encoding to "UTF8" when the MWG module +is loaded, but via the API this must be done manually by setting the +CharsetEXIF option.

+ +

A complication of the MWG specification is that although the MWG:Creator +property may consist of multiple values, the associated EXIF tag +(EXIF:Artist) is only a simple string. To resolve this discrepancy the MWG +recommends a technique which allows a list of values to be stored in a +string by using a semicolon-space separator (with quotes around values if +necessary). When the MWG module is loaded, ExifTool automatically +implements this policy and changes EXIF:Artist to a list-type tag.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableDerived FromValues / Notes
CityyesIPTC:City +
XMP-photoshop:City +
XMP-iptcExt:LocationShownCity +
CurrentIPTCDigest +
IPTCDigest
 
CopyrightyesEXIF:Copyright +
IPTC:CopyrightNotice +
XMP-dc:Rights +
CurrentIPTCDigest +
IPTCDigest
 
CountryyesIPTC:Country-PrimaryLocationName +
XMP-photoshop:Country +
XMP-iptcExt:LocationShownCountryName +
CurrentIPTCDigest +
IPTCDigest
 
CreateDateyesComposite:SubSecCreateDate +
EXIF:CreateDate +
IPTC:DigitalCreationDate +
IPTC:DigitalCreationTime +
XMP-xmp:CreateDate +
CurrentIPTCDigest +
IPTCDigest
("specifies when an image was digitized" - MWG)
Creatoryes+EXIF:Artist +
IPTC:By-line +
XMP-dc:Creator +
CurrentIPTCDigest +
IPTCDigest
 
DateTimeOriginalyesComposite:SubSecDateTimeOriginal +
EXIF:DateTimeOriginal +
IPTC:DateCreated +
IPTC:TimeCreated +
XMP-photoshop:DateCreated +
CurrentIPTCDigest +
IPTCDigest
("specifies when a photo was taken" - MWG)
DescriptionyesEXIF:ImageDescription +
IPTC:Caption-Abstract +
XMP-dc:Description +
CurrentIPTCDigest +
IPTCDigest
 
Keywordsyes+IPTC:Keywords +
XMP-dc:Subject +
CurrentIPTCDigest +
IPTCDigest
 
LocationyesIPTC:Sub-location +
XMP-iptcCore:Location +
XMP-iptcExt:LocationShownSublocation +
CurrentIPTCDigest +
IPTCDigest
 
ModifyDateyesComposite:SubSecModifyDate +
EXIF:ModifyDate +
XMP-xmp:ModifyDate +
CurrentIPTCDigest +
IPTCDigest
("specifies when a file was modified by the user" - MWG)
OrientationyesEXIF:Orientation +
1 = Horizontal (normal) +
2 = Mirror horizontal +
3 = Rotate 180 +
4 = Mirror vertical +
5 = Mirror horizontal and rotate 270 CW +
6 = Rotate 90 CW +
7 = Mirror horizontal and rotate 90 CW +
8 = Rotate 270 CW
+
RatingyesXMP-xmp:Rating 
StateyesIPTC:Province-State +
XMP-photoshop:State +
XMP-iptcExt:LocationShownProvinceState +
CurrentIPTCDigest +
IPTCDigest
 
+ +

MWG Regions Tags

+

Image region metadata defined by the MWG 2.0 specification. These tags +may be accessed without the need to load the MWG Composite tags above. See +https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf +for the official specification.

+ +

These tags belong to the ExifTool XMP-mwg-rs family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
RegionInfostruct--> MWG RegionInfo Struct +
(called Regions by the spec)
RegionAppliedToDimensionsstruct_--> Dimensions Struct +
(RegionsAppliedToDimensions)
RegionAppliedToDimensionsHreal_(RegionsAppliedToDimensionsH)
RegionAppliedToDimensionsUnitstring_(RegionsAppliedToDimensionsUnit)
RegionAppliedToDimensionsWreal_(RegionsAppliedToDimensionsW)
RegionListstruct_+--> MWG RegionStruct Struct +
(RegionsRegionList)
RegionAreastruct_+--> Area Struct +
(RegionsRegionListArea)
RegionAreaDreal_+(RegionsRegionListAreaD)
RegionAreaHreal_+(RegionsRegionListAreaH)
RegionAreaUnitstring_+(RegionsRegionListAreaUnit)
RegionAreaWreal_+(RegionsRegionListAreaW)
RegionAreaXreal_+(RegionsRegionListAreaX)
RegionAreaYreal_+(RegionsRegionListAreaY)
RegionBarCodeValuestring_+(RegionsRegionListBarCodeValue)
RegionDescriptionstring_+(RegionsRegionListDescription)
RegionExtensionsstruct_+--> MWG Extensions Struct +
(RegionsRegionListExtensions)
RegionFocusUsagestring_+(RegionsRegionListFocusUsage) +
'EvaluatedNotUsed' = Evaluated, Not Used +
'EvaluatedUsed' = Evaluated, Used +
'NotEvaluatedNotUsed' = Not Evaluated, Not Used
RegionNamestring_+(RegionsRegionListName)
RegionRotationreal_+(RegionsRegionListRotation; not part of MWG 2.0 spec)
RegionSeeAlsostring_+(RegionsRegionListSeeAlso)
RegionTypestring_+(RegionsRegionListType) +
'BarCode' = BarCode +
'Face' = Face +
'Focus' = Focus +
'Pet' = Pet
+ +

MWG RegionInfo Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
AppliedToDimensionsDimensions--> Dimensions Struct
RegionListMWG RegionStruct+--> MWG RegionStruct Struct
+ +

MWG RegionStruct Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
AreaArea--> Area Struct
BarCodeValuestring 
Descriptionstring 
ExtensionsMWG Extensions--> MWG Extensions Struct
FocusUsagestringEvaluatedNotUsed = Evaluated, Not Used +
EvaluatedUsed = Evaluated, Used +
NotEvaluatedNotUsed = Not Evaluated, Not Used
Namestring 
Rotationreal(not part of MWG 2.0 spec)
TypestringBarCode = BarCode +
Face = Face +
Focus = Focus +
Pet = Pet
SeeAlsostring 
+ +

MWG Extensions Struct

+

This structure may contain any top-level XMP tags, but none have been +pre-defined in ExifTool. Since no flattened tags have been pre-defined, +RegionExtensions is writable only as a structure (eg. +{xmp-dc:creator=me,rating=5}). Fields for this structure are identified +using the standard ExifTool tag name (with optional leading group name, +and/or trailing language code, and/or trailing # symbol to disable print +conversion).

+
+
+ + + + +
Field NameWritableValues / Notes
[no tags known]
+ +

MWG Keywords Tags

+

Hierarchical keywords metadata defined by the MWG 2.0 specification. +ExifTool unrolls keyword structures to an arbitrary depth of 6 to allow +individual levels to be accessed with different tag names, and to avoid +infinite recursion. See +https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf +for the official specification.

+ +

These tags belong to the ExifTool XMP-mwg-kw family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
KeywordInfostruct--> MWG KeywordInfo Struct +
(called Keywords by the spec)
HierarchicalKeywordsstruct_+--> MWG KeywordStruct Struct +
(KeywordsHierarchy)
HierarchicalKeywords1Appliedboolean_+(KeywordsHierarchyApplied)
HierarchicalKeywords1Childrenstruct_+--> MWG KeywordStruct Struct +
(KeywordsHierarchyChildren)
HierarchicalKeywords2Appliedboolean_+(KeywordsHierarchyChildrenApplied)
HierarchicalKeywords2Childrenstruct_+--> MWG KeywordStruct Struct +
(KeywordsHierarchyChildrenChildren)
HierarchicalKeywords3Appliedboolean_+(KeywordsHierarchyChildrenChildrenApplied)
HierarchicalKeywords3Childrenstruct_+--> MWG KeywordStruct Struct +
(KeywordsHierarchyChildrenChildrenChildren)
HierarchicalKeywords4Appliedboolean_+(KeywordsHierarchyChildrenChildrenChildrenApplied)
HierarchicalKeywords4Childrenstruct_+--> MWG KeywordStruct Struct +
(KeywordsHierarchyChildrenChildrenChildrenChildren)
HierarchicalKeywords5Appliedboolean_+(KeywordsHierarchyChildrenChildrenChildrenChildrenApplied)
HierarchicalKeywords5Childrenstruct_+--> MWG KeywordStruct Struct +
(KeywordsHierarchyChildrenChildrenChildrenChildrenChildren)
HierarchicalKeywords6Appliedboolean_+(KeywordsHierarchyChildrenChildrenChildrenChildrenChildrenApplied)
HierarchicalKeywords6string_+(KeywordsHierarchyChildrenChildrenChildrenChildrenChildrenKeyword)
HierarchicalKeywords5string_+(KeywordsHierarchyChildrenChildrenChildrenChildrenKeyword)
HierarchicalKeywords4string_+(KeywordsHierarchyChildrenChildrenChildrenKeyword)
HierarchicalKeywords3string_+(KeywordsHierarchyChildrenChildrenKeyword)
HierarchicalKeywords2string_+(KeywordsHierarchyChildrenKeyword)
HierarchicalKeywords1string_+(KeywordsHierarchyKeyword)
+ +

MWG KeywordInfo Struct

+
+
+ + + + + + + +
Field NameWritableValues / Notes
HierarchyMWG KeywordStruct+--> MWG KeywordStruct Struct
+ +

MWG KeywordStruct Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Appliedboolean 
ChildrenMWG KeywordStruct+--> MWG KeywordStruct Struct
Keywordstring 
+ +

MWG Collections Tags

+

Collections metadata defined by the MWG 2.0 specification. See +https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf +for the official specification.

+ +

These tags belong to the ExifTool XMP-mwg-coll family 1 group.

+
+
+ + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Collectionsstruct+--> MWG CollectionInfo Struct
CollectionNamestring_+(CollectionsCollectionName)
CollectionURIstring_+(CollectionsCollectionURI)
+ +

MWG CollectionInfo Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
CollectionNamestring 
CollectionURIstring 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Apr 1, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MXF.html b/ExifTool/html/TagNames/MXF.html new file mode 100644 index 0000000..98162f1 --- /dev/null +++ b/ExifTool/html/TagNames/MXF.html @@ -0,0 +1,6674 @@ + + + + +MXF Tags + + + +

MXF Tags

+

Tags extracted from Material Exchange Format files. Tag ID's are not listed +because they are bulky 16-byte binary values.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AAFManufacturerID?no 
AES3PCMDescriptor---> MXF Tags
AFDAndBarData?no 
AGICOAID?no 
AICI?no 
AIFCSummary?no 
ANCPacketCountno 
ANCPayloadByteArrayno 
ANCPayloadSampleCodingno 
ANCPayloadSampleCountno 
ANCWrappingTypeno 
Abstractno 
Abstractno 
AccountingReferenceNumberno 
ActiveFormatDescriptorno 
ActiveLinesperFrameno 
ActiveSamplesperLineno 
ActiveStateno 
Ad-ID?no 
Address---> MXF Tags
AddressLineno 
AddressLineno 
AddressNameValueSets?no 
AddressSets?no 
AdvertisingMaterialReferenceno 
AestheticValueno 
AlphaMaximumRefno 
AlphaMinimumRefno 
AlphaSampleDepthno 
AlphaTransparencyno0 = Not Inverted +
1 = Inverted
AlternateNameno 
AlternateNameno 
Alternates?no 
AnalogDataCodingKindno 
AnalogMetadataCarrierno 
AnalogMonitoringAndControlCodingKindno 
AnalogSystemno 
AnalogVideoSystemNameno 
AnalogVideoSystemName?no 
AnamorphicLensCharacteristicno 
AnchorOffsetno 
AncillaryResourceID?no 
AngleToNorthno 
AngularUnitKindno 
Annotation?no 
Annotation---> MXF Tags
AnnotationCueWordsSet?no 
AnnotationDescriptionno 
AnnotationDescriptionno 
AnnotationKindno 
AnnotationKindno 
AnnotationParticipantSets?no 
AnnotationSets?no 
AnnotationSynopsisno 
AnnotationSynopsisno 
ApplicationEnvironmentIDno 
ApplicationInformationArray?no 
ApplicationNameno 
ApplicationNameno 
ApplicationPlatformno 
ApplicationPlatformno 
ApplicationPlug-InBatch?no 
ApplicationPlug-InInstanceID?no 
ApplicationProductID?no 
ApplicationScheme?no 
ApplicationSchemeBatch?no 
ApplicationSupplierNameno 
ApplicationSupplierNameno 
ApplicationVersionNumberno 
ApplicationVersionStringno 
ApplicationVersionStringno 
ApproximateImageContainerSizeno 
ArchiveIDno 
AssetValueno 
AssignedCategoryNameno 
AssignedCategoryNameno 
AssignedCategoryValueno 
AssignedCategoryValueno 
AssociatedMetadataDefinition?no 
AstronomicalBodyNameno 
AstronomicalBodyNameno 
AudienceAppreciationno 
AudienceRatingno 
AudienceReachno 
AudienceShareno 
AudioAverageBitrateno 
AudioCodingSchemeCodeno 
AudioCodingSchemeID?no 
AudioCodingSchemeNameno 
AudioCompressionAlgorithmno 
AudioDeviceKindno 
AudioDeviceParameterno 
AudioDeviceParameterSettingno 
AudioEnhancementOrModificationDescriptionno 
AudioFirstMix-DownProcessno 
AudioFixedBitrateFlagno 
AudioMonoChannelCountno 
AudioNoiseReductionAlgorithmno 
AudioReferenceLevelno 
AudioReferenceLevelno 
AudioSampleRateno 
AudioSampleRateno 
AudioStereoChannelCountno 
AudioWatermarkKindno 
AuthenticationFlagno 
AuxiliaryBitsModeno 
AvailableRepresentations?no 
AverageBytesPerSecondno 
Award---> MXF Tags
AwardCategoryno 
AwardCategoryno 
AwardNameno 
AwardNameno 
AwardParticipantSets?no 
AwardSets?no 
BICI?no 
BackgroundMusicFlagno 
BankDetailsSet?no 
BeginAnchorno 
BeginAnchorno 
BextCodingHistoryno 
BextCodingHistoryno 
Bitrateno 
BitsPerAudioSampleno 
BitsPerPixelno 
BitsPerPixelno 
BitsPerSampleno 
BlackReferenceLevelno 
BlockAlignno 
BlockContinuityCountno 
BlockStartOffsetno 
BoundingRectangle?no 
BrandMainTitleno 
BrandMainTitleno 
BrandOriginalTitleno 
BrandOriginalTitleno 
Branding---> MXF Tags
BrandingSets?no 
BroadcastChannelno 
BroadcastDateno 
BroadcastMediumCodeno 
BroadcastMediumKindno 
BroadcastOrganizationNameno 
BroadcastOrganizationNameno 
BroadcastRegionno 
BroadcastRegionno 
BroadcastServiceNameno 
BroadcastTimeno 
BroadcasterRightsToCopyno 
BufferDelay?no 
BuildingNameno 
BuildingNameno 
Bypassno 
BypassOverrideno 
ByteOffsetno 
ByteOrderno'II' = Little-endian (Intel, II) +
'MM' = Big-endian (Motorola, MM)
CBEStartOffsetno 
CDCIEssenceDescriptor---> MXF Tags
CISACLegalEntityID?no 
CaptionDescription---> MXF Tags
CaptionDescriptionSets?no 
CaptionKindno 
CaptionKindno 
CaptionTitlesno 
CaptionsDescriptionParticipantSets?no 
CaptionsViaTeletextno 
CaptureAspectRationo 
CaptureFilmFrameRateno 
CaptureGammaEquationno 
CaptureGammaEquationno 
CaptureGammaEquation?no 
CatalogDataStatusno 
CatalogDataStatusno 
CatalogPrefixNumberno 
CatalogingSystemNameno 
CentralTelephoneNumberno 
ChannelAssignment?no 
ChannelCountno 
ChannelHandleno 
ChannelIDno 
ChannelIDsno 
ChannelStatusModeno[Value 0] +
0 = No Channel Status Data +
1 = AES3 Minimum +
2 = AES3 Standard +
3 = Fixed 24 Bytes in FixedChannelStatusData +
4 = Stream of Data in MXF Header Metadata +
5 = Stream of Data Multiplexed within MXF Body
+
CipherAlgorithm?no 
CipherAlgorithmAES128CBC?no 
Citizenshipno 
Citizenshipno 
CityNameno 
CityNameno 
ClassDefinitions?no 
Classification---> MXF Tags
ClassificationAndMarkingSystemno 
ClassificationCommentno 
ClassificationCommentno 
ClassificationNameValueSets?no 
ClassificationReasonno 
ClassificationSets?no 
ClassifiedByno 
ClassifyingCountryCodeno 
ClipCreationDateTimeno 
ClipFramework---> MXF Tags
ClipIDno 
ClipIDArray?no 
ClipKindno 
ClipNumberno 
ClipNumberno 
ClipShotSets?no 
CloneCountno 
ClosedBodyPartition?no 
ClosedCaptionSubtitlesFlagno 
ClosedCompleteBodyPartition?no 
ClosedCompleteHeader---> MXF Header Tags
ClosedGOPIndicatorno 
ClosedHeader---> MXF Header Tags
CodecDefinition?no 
CodecDefinition?no 
CodecDefinitions?no 
CodecEssenceDescriptor?no 
CodecEssenceKinds?no 
CodedContentScanningKindno0 = Unknown +
1 = Progressive +
2 = Interlaced +
3 = Mixed
CodingLawKindno 
CodingLawNameno 
CodingStyleDefault?no 
CollectionNameno 
CollectionNameno 
ColorDescriptorno 
ColorDescriptorno 
ColorFieldCodeno 
ColorPrimariesno 
ColorPrimaries?no 
ColorRangeLevelsno 
ColorSitingno 
ColorimetryCodeno 
ColorimetryCode?no 
CommunicationSets?no 
Communications---> MXF Tags
CompleteFooter?no 
ComponentAttributes?no 
ComponentDataDefinitionno'060e2b34.0401.0101.01030201.01000000' = SMPTE 12M Timecode Track +
'060e2b34.0401.0101.01030201.02000000' = SMPTE 12M Timecode Track with active user bits +
'060e2b34.0401.0101.01030201.03000000' = SMPTE 309M Timecode Track +
'060e2b34.0401.0101.01030201.10000000' = Descriptive Metadata Track +
'060e2b34.0401.0101.01030202.01000000' = Picture Essence Track +
'060e2b34.0401.0101.01030202.02000000' = Sound Essence Track +
'060e2b34.0401.0101.01030202.03000000' = Data Essence Track
ComponentDepthno 
ComponentKLVData?no 
ComponentMaximumRefno 
ComponentMinimumRefno 
ComponentUserComments?no 
ComponentsInSequence?no 
CompositionRendering?no 
ComputedKeyDatano 
ComputedKeyFramesno 
ComputedKeySoundsno 
ComputedKeywordsno 
ComputedKeywordsno 
ComputedObjectKindno 
ComputedObjectKindno 
ComputedStratumKindno 
ConstantBPictureFlagno 
ConsumerRightsToCopyno 
ContactDepartmentNameno 
ContactDepartmentNameno 
ContactID?no 
ContactKindno 
ContactKindno 
ContactNameValueSets?no 
ContactsList---> MXF Tags
ContactsListSet?no 
ContainerDefinitions?no 
ContainerLastModifyDateno 
ContainerVersionno 
ContentClassificationno 
ContentClassificationno 
ContentCodingSystemno 
ContentMaturityDescriptionno 
ContentMaturityGraphicno 
ContentMaturityRatingno 
ContentPackageIndexArray?no 
ContentPackageMetadataLinkno 
ContentStorage?no 
ContentStorageSet---> MXF Tags
ContentValueno 
ContextDescriptionno 
ContiguousDataFlagno 
Contract---> MXF Tags
ContractClauseDescriptionno 
ContractClauseDescriptionno 
ContractDateTimeno 
ContractEntityno 
ContractInstallmentPercentageno 
ContractLineCodeno 
ContractLineNameno 
ContractLineNameno 
ContractParticipantSets?no 
ContractSets?no 
ContractTermsOfBusinessno 
ContractTermsOfBusinessno 
ContractTypeno 
ContractTypeno 
ContractTypeCodeno 
ContractTypeLinkno 
ContributionStatusno 
ContributionStatusno 
ControlPointList?no 
ControlPointTimeno 
CopyCountno 
CopyrightLicenseCountryCodeno 
CopyrightLicenseRegionCodeno 
CopyrightLicenseRegionNameno 
CopyrightOwnerNameno 
CopyrightOwnerNameno 
CopyrightStatusno 
CopyrightStatusno 
CornerLatitudePoint1no 
CornerLatitudePoint1no 
CornerLatitudePoint2no 
CornerLatitudePoint2no 
CornerLatitudePoint3no 
CornerLatitudePoint3no 
CornerLatitudePoint4no 
CornerLatitudePoint4no 
CornerLongitudePoint1no 
CornerLongitudePoint1no 
CornerLongitudePoint2no 
CornerLongitudePoint2no 
CornerLongitudePoint3no 
CornerLongitudePoint3no 
CornerLongitudePoint4no 
CornerLongitudePoint4no 
CountryCodeMethodno 
CountryCodeMethodno 
CountryNameno 
CountryNameno 
CountryNameno 
CountryNameno 
CreateDateno 
CryptographicContext---> MXF Tags
CryptographicContextID?no 
CryptographicContextLink?no 
CryptographicContextObject?no 
CryptographicFramework---> MXF Tags
CryptographicFrameworkLabel?no 
CryptographicKeyID?no 
Csizno 
Cue-InWordsno 
Cue-InWordsno 
Cue-OutWordsno 
Cue-OutWordsno 
CueWords---> MXF Tags
CulturalValueno 
CurrencyCodeno 
CurrencyNameno 
CurrentNumberInSequenceno 
CurrentNumberInSequenceno 
CurrentRepeatNumberno 
CutPointno 
DMFramework---> MXF Tags
DMSegment---> MXF Tags
DMSet---> MXF Tags
DMSourceClip---> MXF Tags
DOI?no 
DVBParentalRatingno 
DataDefinition?no 
DataDefinitions?no 
DataDeviceKindno 
DataDeviceParameterNameno 
DataDeviceParameterSettingno 
DataEnhancementOrModificationDescriptionno 
DataEssenceCodingno 
DataEssenceCodingID?no 
DateTimeDropFrameFlagno 
DateTimeEmbeddedFlagno 
DateTimeKind?no 
DateTimeRateno 
DeclassificationDateno 
DefaultDataValue?no 
DefaultFadeDurationno 
DefaultFadeEditRateno 
DefaultFadeType?no 
DefaultNamespaceURIno 
DefaultNamespaceURIno 
DefaultObject?no 
DefinedNameno 
DefinedNameno 
DefinitionObjectID?no 
DefinitionObjectNameno 
DefinitionObjectNameno 
DegradedEffects?no 
DeltaEntryArray?no 
DerivedFromno 
Descriptionno 
Descriptionno 
DescriptionKindno 
DescriptionKindno 
DescriptiveCommentno 
DescriptiveCommentno 
DescriptiveMetadataApplicationEnvironmentIDno 
DescriptiveMetadataFramework?no 
DescriptiveMetadataPlug-InID?no 
DescriptiveMetadataScheme?no 
DescriptiveMetadataSchemes?no 
DescriptiveMetadataSetReferences?no 
DescriptiveMetadataSets?no 
DescriptiveMetadataTrackIDsno 
DeviceAbsoluteHeadingno 
DeviceAbsolutePositionalAccuracyno 
DeviceAbsoluteSpeedno 
DeviceAltitudeno 
DeviceAltitudeno 
DeviceAssetNumberno 
DeviceDesignationno 
DeviceIDKindno 
DeviceKindno 
DeviceKindno 
DeviceKindCodeno 
DeviceLatitudeno 
DeviceLatitudeno 
DeviceLatitudeno 
DeviceLongitudeno 
DeviceLongitudeno 
DeviceLongitudeno 
DeviceManufacturerNameno 
DeviceManufacturerNameno 
DeviceModelno 
DeviceParameterNameValueSets?no 
DeviceParameters---> MXF Tags
DeviceParametersSets?no 
DeviceRelativeHeadingno 
DeviceRelativePositionXno 
DeviceRelativePositionYno 
DeviceRelativePositionZno 
DeviceRelativePositionalAccuracyno 
DeviceRelativeSpeedno 
DeviceSerialNumberno 
DeviceUsageDescriptionno 
DeviceUsageDescriptionno 
DeviceXDimensionno 
DeviceYDimensionno 
DialNormno 
Dictionary?no 
DictionaryDescriptionno 
DictionaryDescriptionno 
DictionaryIdentifier?no 
DigitalEncodingBitrateno 
DigitalMetadataCarrierno 
DigitalOrAnalogOriginationno 
DigitalVideoFileFormatno 
DirectorNameno 
DiscPartitionCapacityno 
DisplayF2Offsetno 
DisplayHeightno 
DisplayUnitsno 
DisplayUnitsno 
DisplayWidthno 
DisplayXOffsetno 
DisplayYOffsetno 
Ditherno 
DropFrameno 
Durationno 
DynamicSourcePackageID?no 
DynamicSourceTrackIDsno 
E-mailAddressno 
E-mailAddressno 
EPGProgramSynopsisno 
EdgeCodeno 
EdgeCodeFilmGauge?no 
EdgeCodeFormat?no 
EdgeCodeHeader?no 
EdgeCodeStartno 
EditHint?no 
EditRateno 
EditUnitFlagsno 
EditUnitLengthno 
EditingEventCommentno 
EditingEventCommentno 
EffectRendering?no 
ElectrospatialFormulationno 
ElementCountno 
ElementDeltano 
ElementLengthno 
ElementNameListno 
Emphasisno 
EncryptedContainerLabel?no 
EncryptedSourceValue?no 
EncryptedTrackFileID?no 
EndAnchorno 
EndAnchorno 
EnhancementOrModificationDescriptionno 
EnumerationUnderlyingIntegerType?no 
EpisodeEndNumberno 
EpisodeNumberno 
EpisodeNumberno 
EpisodeStartNumberno 
EpisodicItemSets?no 
EssenceContainerArray?no 
EssenceContainerDataSet---> MXF Tags
EssenceContainerFormat?no 
EssenceContainerFormat?no 
EssenceContainers?no 
EssenceData?no 
EssenceDescription?no 
EssenceIsIdentifiedno 
EssenceLengthno 
EssenceLocators?no 
EssenceStreamIDno 
Event---> MXF Tags
EventAbsoluteDurationno 
EventAbsoluteDuration?no 
EventAbsoluteDurationFrameCountno 
EventAnnotationSets?no 
EventElapsedTimeToEndno 
EventElapsedTimeToStartno 
EventEndTimeOffsetno 
EventEndTimecodeOffset?no 
EventIndicationno 
EventIndicationno 
EventOriginno 
EventSets?no 
EventStartno 
EventStartTimeOffsetno 
EventStartTimecodeOffset?no 
EventTrack---> MXF Tags
EventTrackEditRateno 
ExCCIData?no 
ExposedAspectRationo 
ExtendedCaptionsLanguageCodeno 
ExtendedClipIDno 
ExtendedClipIDArray?no 
ExtendedTextLanguageCodeno 
ExtendibleElementNameListno 
FNumberno 
Fade-InDurationno 
Fade-InType?no 
Fade-OutDurationno 
Fade-OutType?no 
FamilyNameno 
FamilyNameno 
FaxNumberno 
FaxNumberno 
FemaleLeadActressNameno 
FestivalNameno 
FestivalNameno 
FieldDominanceno 
FieldFrameTypeCodeno 
FieldOfViewHorizontalno 
FieldOfViewHorizontalno 
FieldOfViewVerticalno 
FieldOfViewVerticalFPno 
FieldRateno 
FileDescriptor---> MXF Tags
FileDescriptors?no 
FileSecurityReportno 
FileSecurityWaveno 
FillerData?no 
FilmBatchNumberno 
FilmBatchNumberno 
FilmCaptureApertureno 
FilmColorProcessno 
FilmFormatNameno 
FilmFormatNameno 
FilmFormatName?no 
FilmGauge?no 
FilmStockKindno 
FilmStockKindno 
FilmStockManufacturerNameno 
FilmStockManufacturerNameno 
FilmTestParameterno 
FilmTestResultno 
FilmTestResult?no 
FilmToVideoTransferDirection?no 
FilmToVideoTransferKind?no 
FilmToVideoTransferPhase?no 
FilteringAppliedno 
FilteringCodeno 
FirstBroadcastFlagno 
FirstGivenNameno 
FirstGivenNameno 
FirstNumberInSequenceno 
FirstNumberInSequenceno 
FirstTransmissionInfono 
FixedArrayElementType?no 
FixedChannelStatusData?no 
FixedUserData?no 
FocalLengthno 
FocalLengthno 
Footer?no 
FormatDescriptorno 
FormatDescriptorno 
FormerFamilyNameno 
FormerFamilyNameno 
FrameCenterElevationno 
FrameCenterLatitudeno 
FrameCenterLatitudeno 
FrameCenterLatitudeno 
FrameCenterLatitudeLongitudeno 
FrameCenterLongitudeno 
FrameCenterLongitudeno 
FrameCenterLongitudeno 
FrameCodeno 
FrameCountno 
FrameCountOffsetno 
FrameLayoutno 
FramePositionalAccuracyno 
FrameRateno 
FrameworkExtendedTextLanguageCodeno 
FrameworkTextLanguageCodeno 
FrameworkThesaurusNameno 
FrameworkThesaurusNameno 
FrameworkTitleno 
FrameworkTitleno 
GenerationCloneNumberno 
GenerationCopyNumberno 
GenerationID?no 
GenericDataEssenceDescriptor---> MXF Tags
GenericPackage---> MXF Tags
GenericPayloads?no 
GenericPictureEssenceDescriptor---> MXF Tags
GenericSoundEssenceDescriptor---> MXF Tags
GenericTrack---> MXF Tags
Genreno 
Genreno 
GeographicAreaNorthwest?no 
GeographicAreaSourceDatumno 
GeographicAreaSoutheast?no 
GeographicLocation?no 
GeographicPolygonCoordinates?no 
GeographicPolygonSourceDatumno 
GeographicalCoordinates?no 
GlobalNumberno 
GraphicKindno 
GraphicUsageKindno 
GraphicUsageKindno 
GroupRelationship---> MXF Tags
GroupSet?no 
GroupSets?no 
GroupSynopsisno 
GroupSynopsisno 
HMACAlgorithmSHA1128?no 
HTMLDOCTYPEno 
HTMLDOCTYPEno 
HTMLMetaDescriptionno 
HTMLMetaDescriptionno 
HardwareAcceleratorFlagno 
HasAudioWatermarkno 
HasVideoWatermarkno 
HeaderByteCountno 
HistoricalValueno 
HonorsAndQualificationsno 
HonorsAndQualificationsno 
HorizontalActionSafePercentageno 
HorizontalDatumno 
HorizontalGraphicsSafePercentageno 
HorizontalSubsamplingno 
IBTN?no 
IEEEDeviceIDno 
IEEEManufacturerIDno 
ISAN?no 
ISBD?no 
ISBN?no 
ISCI?no 
ISMN?no 
ISO3166CountryCodeno 
ISO639-1LanguageCodeno 
ISO639-1LanguageCodeno 
ISO639CaptionsLanguageCodeno 
ISO639TextLanguageCodeno 
ISRC?no 
ISRN?no 
ISSN?no 
ISTC?no 
ISWC?no 
IdenticalGOPIndicatorno 
Identification---> MXF Tags
Identification---> MXF Tags
IdentificationList?no 
IdentificationSets?no 
IdentificationUL?no 
IdentifierIssuingAuthorityno 
IdentifierIssuingAuthorityno 
IdentifierKindno 
IdentifierValueno 
ImageAlignmentOffsetno 
ImageCategoryno 
ImageCoordinateSystemno 
ImageEndOffsetno 
ImageFormatSet?no 
ImageHeightno 
ImageSourceDeviceKindno 
ImageSourceDeviceKindno 
ImageStartOffsetno 
ImageWidthno 
IncludeSyncno 
IndexByteCountno 
IndexDurationno 
IndexEditRateno 
IndexEntryArray?no 
IndexStreamIDno 
IndexTableSegment?no 
IndexingStartPositionno 
IndividualAwardNameno 
InkNumberno 
InputSegment?no 
InputSegmentCountno 
InputSegments?no 
InsertMusicFlagno 
InstallmentNumberno 
InstanceUID?no 
IntegrationIndicationno 
IntegrationIndicationno 
IntellectualPropertyDescriptionno 
IntellectualPropertyDescriptionno 
IntellectualPropertyLicenseCountryCodeno 
IntellectualPropertyLicenseRegionCodeno 
IntellectualPropertyLicenseRegionNameno 
IntellectualPropertyRightsno 
IntellectualPropertyRightsno 
IntendedAFDno 
IntentDescriptorno 
IntentDescriptorno 
InterestedPartyNameno 
Interpolation?no 
InterpolationDefinitions?no 
IsConcreteno 
IsDubbedno 
IsLiveProductionno 
IsLiveTransmissionno 
IsOptionalno 
IsRecordingno 
IsRepeatno 
IsSearchableno 
IsSignedno 
IsUniqueIdentifierno 
IsVoiceoverno 
ItemDesignatorID?no 
ItemIDno 
ItemNameno 
ItemNameno 
ItemValueno 
ItemValueno 
JFIFMarkerDescriptionno 
JFIFMarkerDescriptionno 
JPEG2000PictureSubDescriptor---> MXF Tags
JPEGTableID?no 
JobFunctionCodeno 
JobFunctionNameno 
JobFunctionNameno 
JobTitleno 
JobTitleno 
Jurisdictionno 
Jurisdictionno 
KAGSizeno 
KLVDataDefinitions?no 
KLVDataParentProperties?no 
KLVDataType?no 
KLVDataValue?no 
KLVMetadataSequence?no 
KeyCode?no 
KeyDatano 
KeyDataOrProgramno 
KeyFrameno 
KeyFrameSampleCountno 
KeyFramesno 
KeyPoint---> MXF Tags
KeyPointSets?no 
KeySoundno 
KeySoundsno 
KeyTextno 
KeyTimePointno 
KeypointKindno 
KeypointKindno 
KeypointValueno 
KeypointValueno 
Keywordsno 
Keywordsno 
LUIDno 
LanguageNameno 
LanguageNameno 
LastNumberInSequenceno 
LastNumberInSequenceno 
LayerNumberno 
LeadingLinesno 
LengthSystemNameno 
LengthUnitKindno 
LicenseOptionsDescriptionno 
LineNumberno 
LinkedApplicationPlug-InInstanceID?no 
LinkedDescriptiveFrameworkPlug-InID?no 
LinkedDescriptiveObjectPlug-InID?no 
LinkedGenerationID?no 
LinkedPackageID?no 
LinkedTimecodeTrackID?no 
LinkedTrackIDno 
LinkingNameno 
LinkingNameno 
LocalCreationDateTimeno 
LocalDatumAbsolutePositionAccuracyno 
LocalDatumRelativePositionAccuracyno 
LocalEndDateTimeno 
LocalEventEndDateTimeno 
LocalEventEndDateTimeno 
LocalEventStartDateTimeno 
LocalEventStartDateTimeno 
LocalFestivalDateTimeno 
LocalFilePathno 
LocalFilePathno 
LocalIDno 
LocalLastModifyDateno 
LocalModifyDateno 
LocalStartDateTimeno 
LocalTagEntries?no 
LocalTagUniqueID?no 
LocalTagValueno 
LocalTapeNumberno 
LocalTargetIDno 
LocalTargetIDno 
LocalUserDateTimeno 
Location---> MXF Tags
LocationDescriptionno 
LocationDescriptionno 
LocationKindno 
LocationKindno 
LocationSets?no 
LockedIndicatorno 
LogoFlagno 
LowDelayIndicatorno 
LumaEquationno 
LuminanceSampleRateno 
MIC?no 
MICAlgorithm?no 
MIMECharSetno 
MIMECharSetno 
MIMEEncodingno 
MIMEEncodingno 
MIMEMediaTypeno 
MIMEMediaTypeno 
MPEG2VideoDescriptor---> MXF Tags
MPEG7BiMAccessUnitFrame1?no 
MPEG7BiMAccessUnitFrame2?no 
MPEG7BiMAccessUnitFrame3?no 
MPEG7BiMAccessUnitFrame4?no 
MPEG7BiMAccessUnitFrame5?no 
MPEG7BiMAccessUnitFrame6?no 
MPEG7BiMAccessUnitFrame7?no 
MPEG7BiMAccessUnitFrame8?no 
MPEG7BiMDecoderInitFrame1?no 
MPEG7BiMDecoderInitFrame2?no 
MPEG7BiMDecoderInitFrame3?no 
MPEG7BiMDecoderInitFrame4?no 
MPEG7BiMDecoderInitFrame5?no 
MPEG7BiMDecoderInitFrame6?no 
MPEG7BiMDecoderInitFrame7?no 
MPEG7BiMDecoderInitFrame8?no 
MPEGAudioBitrateno 
MPEGAudioRecodingDataset?no 
MPEGVideoRecodingDataset?no 
MagneticDiskNumberno 
MagneticTrackno 
MainCatalogNumberno 
MainNameno 
MainNameno 
MainSponsorNameno 
MainTitleno 
MainTitleno 
MajorVersionno 
MaleLeadActorNameno 
ManufacturerID?no 
ManufacturerInformationObject?no 
MapDatumUsedno 
MarkInno 
MarkOutno 
MaterialAbsoluteDurationno 
MaterialAbsoluteDuration?no 
MaterialEndTimeOffsetno 
MaterialEndTimecodeOffset?no 
MaterialPackage---> MXF Tags
MaximumAPIVersionno 
MaximumBPictureCountno 
MaximumGOPSizeno 
MaximumSupportedEngineVersionno 
MaximumSupportedPlatformVersionno 
MaximumUseCountno 
MediaLocationno 
MemberNameListno 
MemberTypes?no 
MetadataEncodingSchemeCodeno 
MetadataItemNameno 
MetadataItemNameno 
MetadataServerLocators?no 
MicrophonePlacementTechniquesno 
MinimumAPIVersionno 
MinimumSupportedEngineVersionno 
MinimumSupportedPlatformVersionno 
MinorVersionno 
MissionIDno 
MissionIDno 
MobileTelephoneNumberno 
ModifyDateno 
MonoSourceTrackIDsno 
MultipleDescriptor---> MXF Tags
NITFLayerTargetIDno 
NITFLayerTargetIDno 
NMEADocumentTextno 
NOLACodeno 
NameSuffixno 
NameSuffixno 
NameValue---> MXF Tags
NamespacePrefixno 
NamespacePrefixno 
NamespacePrefixesno 
NamespacePrefixesno 
NamespaceURIno 
NamespaceURIno 
NamespaceURIsno 
NamespaceURIsno 
Nationalityno 
Nationalityno 
NatureOfPersonalityno 
NatureOfPersonalityno 
NetworkLocator---> MXF Tags
NextNumberInSequenceno 
NextNumberInSequenceno 
NielsenStreamIdentifierno 
NominationCategoryno 
NominationCategoryno 
Non-USClassifyingCountryCodeno 
ObjectAreaDimensionno 
ObjectClass?no 
ObjectClassDefinition?no 
ObjectCountryCodeno 
ObjectCountryCodeno 
ObjectCountryCodeMethodno 
ObjectDescriptionno 
ObjectDescriptionno 
ObjectDescriptionCodeno 
ObjectHorizontalAverageDimensionno 
ObjectIdentificationConfidenceno 
ObjectKindno 
ObjectKindno 
ObjectModelVersionno 
ObjectNameno 
ObjectRegionCodeno 
ObjectRegionNameno 
ObjectVerticalAverageDimensionno 
ObliquityAngleno 
OffsetToIndexTableno 
OffsetToIndexTableno 
OffsetToMetadatano 
OffsetToMetadatano 
OpenBodyPartition?no 
OpenCompleteBodyPartition?no 
OpenCompleteHeader---> MXF Header Tags
OpenHeader---> MXF Header Tags
OperatingSystemInterpretationsno 
OperationCategory?no 
OperationDataDefinition?no 
OperationDefinitionID?no 
OperationDefinitions?no 
OperationParameters?no 
OperationalPatternUL?no 
OpticalDiscNumberno 
OpticalTestParameterNameno 
OpticalTestResultno 
OpticalTestResultno 
OpticalTrackno 
Organisation---> MXF Tags
OrganizationCodeno 
OrganizationCodeno 
OrganizationIDno 
OrganizationIDno 
OrganizationIDKindno 
OrganizationIDKindno 
OrganizationKindno 
OrganizationKindno 
OrganizationMainNameno 
OrganizationMainNameno 
OrganizationSets?no 
OrganizationalProgramNumberno 
OrganizationalProgramNumberno 
Originno 
OriginCodeno 
OriginalExtendedSpokenPrimaryLanguageCodeno 
OriginalProducerNameno 
OriginalProducerNameno 
OriginalTitleno 
OriginalTitleno 
OtherGivenNamesno 
OtherGivenNamesno 
OtherValuesno 
PII?no 
POSIXMicrosecondsno 
PURLno 
PackLengthno 
PackageAttributes?no 
PackageID?no 
PackageKLVData?no 
PackageLastModifyDateno 
PackageMarkInPositionno 
PackageMarkOutPositionno 
PackageMarker?no 
PackageNameno 
PackageNameno 
PackageTimelineMarkerRef?no 
PackageTracks?no 
PackageUsageKind?no 
PackageUserComments?no 
Packages?no 
PaddingBitsno 
Palette?no 
PaletteLayout?no 
PanScanInformation?no 
ParameterDataType?no 
ParameterDefinition?no 
ParameterDefinitions?no 
Parameters?no 
ParentClass?no 
Participant---> MXF Tags
ParticipantID?no 
ParticipantOrganizationSets?no 
PartitionMetadata?no 
Passwordno 
Passwordno 
PayeeAccountNameno 
PayeeAccountNumberno 
PayeeAccountSortCodeno 
PayerAccountNameno 
PayerAccountNumberno 
PayerAccountSortCodeno 
PaymentDueDateTimeno 
PaymentsSets?no 
PeakChannelCountno 
PeakEnvelopeno 
PeakEnvelopeBlockSizeno 
PeakEnvelopeData?no 
PeakEnvelopeData?no 
PeakEnvelopeFormatno 
PeakEnvelopeTimestampno 
PeakEnvelopeVersionno 
PeakFrameCountno 
PeakOfPeaksPositionno 
PerceivedDisplayFormatCodeno 
PerceivedDisplayFormatNameno 
PerforationsPerFrameno 
PerforationsPerFrameno 
Person---> MXF Tags
PersonDescriptionno 
PersonDescriptionno 
PersonOrganizationSets?no 
PersonSets?no 
PhysicalInstanceKindno 
PhysicalMediaLength?no 
PhysicalMediaLocationno 
PictureComponentSizing?no 
PictureDisplayRateno 
PictureFormat---> MXF Tags
PixelLayout?no 
PlaceKeywordno 
PlaceKeywordno 
PlaceNameno 
PlaceNameno 
PlaintextOffsetno 
PlatformDesignationno 
PlatformDesignationno 
PlatformHeadingAngleno 
PlatformModelno 
PlatformPitchAngleno 
PlatformRollAngleno 
PlatformSerialNumberno 
Plug-InAPIID?no 
Plug-InCategoryID?no 
Plug-InDefinitions?no 
Plug-InEngineID?no 
Plug-InLocatorSet?no 
Plug-InPlatformID?no 
PointsPerPeakValueno 
PolarCharacteristicno 
PosTableArray?no 
PositionInSequenceno 
PositionTable?no 
PositionTableCountno 
PositionTableIndexingno 
PositionWithinViewportImageXCoordinateno 
PositionWithinViewportImageYCoordinateno 
PostCodeForPostboxno 
PostalCodeno 
PostalCodeno 
PostalTownno 
PostalTownno 
PostboxNumberno 
Preface---> MXF Tags
PresentationAspectRationo 
PresentationGammaEquationno 
PresentationGammaEquation?no 
PresenterNameno 
PreviousNumberInSequenceno 
PreviousNumberInSequenceno 
PreviousRepeatNumberno 
PrimaryExtendedSpokenLanguageCodeno 
PrimaryOriginalLanguageCodeno 
PrimaryPackage?no 
PrimarySpokenLanguageCodeno 
Primer---> MXF Tags
Processing---> MXF Tags
ProcessingSet?no 
ProducerNameno 
ProductFormatno 
ProductFormatno 
ProductionFramework---> MXF Tags
ProductionOrganizationRoleno 
ProductionOrganizationRoleno 
ProductionScriptReferenceno 
ProductionScriptReferenceno 
ProductionSettingPeriodSets?no 
ProfileAndLevelno 
ProgramAwardNameno 
ProgramCommercialMaterialReferenceno 
ProgramIdentifierno 
ProgramIdentifierStringno 
ProgramKindno 
ProgramMaterialClassificationCodeno 
ProgramNumberno 
ProgramSupportMaterialReferenceno 
ProgrammingGroupKindno 
ProgrammingGroupKindno 
ProgrammingGroupTitleno 
ProgrammingGroupTitleno 
ProjectNameno 
ProjectNameno 
ProjectNumberno 
ProjectSet?no 
Projects---> MXF Tags
Properties?no 
PropertyType?no 
Publication---> MXF Tags
PublicationSets?no 
PublishingMediumNameno 
PublishingMediumNameno 
PublishingOrganizationNameno 
PublishingOrganizationNameno 
PublishingRegionNameno 
PublishingRegionNameno 
PublishingServiceNameno 
PublishingServiceNameno 
PulldownFieldDominanceno 
PulldownSequence?no 
PurchaserAccountNameno 
PurchaserAccountNameno 
PurchaserAccountNumberno 
PurchaserIdentificationKindno 
PurchaserIdentificationValueno 
PurchasingDepartmentno 
PurchasingOrganizationNameno 
Purposeno 
Purposeno 
QltyBasicDatano 
QltyBasicDatano 
QltyCueSheetno 
QltyCueSheetno 
QltyEndOfModulationno 
QltyEndOfModulationno 
QltyOperatorCommentno 
QltyOperatorCommentno 
QltyQualityEventno 
QltyQualityEventno 
QltyQualityParameterno 
QltyQualityParameterno 
QltyStartOfModulationno 
QltyStartOfModulationno 
QualityFlagno 
QuantizationDefault?no 
RGBAEssenceDescriptor---> MXF Tags
RIFFChunkData?no 
RIFFChunkIDno 
RIFFChunkLengthno 
RP217DataStreamPIDno 
RP217VideoStreamPIDno 
RandomIndexMetadata?no 
RandomIndexMetadataV10?no 
Ratingno 
RecordedFormatno 
RecordedFormatno 
RecordedTrackNumberno 
RecordingLabelNameno 
RecordingLabelNameno 
ReelOrRollNumberno 
RegionCodeno 
RegionNameno 
RegionNameno 
RegisterActionno 
RegisterAdministrationArray?no 
RegisterAdministrationNotesno 
RegisterAdministrationObject?no 
RegisterApproverNameno 
RegisterChildEntryArray?no 
RegisterCreationTimeno 
RegisterEditorNameno 
RegisterEntryAdministrationObject?no 
RegisterEntryArray?no 
RegisterEntryStatus?no 
RegisterItemDefiningDocumentNameno 
RegisterItemDefinitionno 
RegisterItemHierarchyLevelno 
RegisterItemIntroductionVersionno 
RegisterItemNameno 
RegisterItemNotesno 
RegisterItemOriginatorNameno 
RegisterItemStatusChangeDateTimeno 
RegisterItemSymbol?no 
RegisterItemUL?no 
RegisterKind?no 
RegisterReleaseDateTimeno 
RegisterStatusKind?no 
RegisterUserNameno 
RegisterUserTimeno 
RegisterVersionno 
RegistrantNameno 
RelatedMaterialDescriptionno 
RelatedMaterialDescriptionno 
RelatedMaterialLocators?no 
RelativePositionInSequenceNameno 
RelativePositionInSequenceOffsetno 
RelativeScopeno 
RelativeTrackno 
ReleasableCountryCodeno 
ReleasableCountryCodeno 
RenamedType?no 
ResourceID?no 
RestrictionsonUseno 
ReversePlayno 
ReversedByteOrderno 
Rights---> MXF Tags
RightsCommentno 
RightsCommentno 
RightsConditionDescriptionno 
RightsConditionDescriptionno 
RightsManagementAuthorityno 
RightsManagementAuthorityno 
RightsSets?no 
RightsStartDateTimeno 
RightsStopDateTimeno 
Rightsholderno 
Rightsholderno 
RoleNameno 
RoleNameno 
RoomNumberno 
RoomNumberno 
RoomOrSuiteNameno 
RoomOrSuiteNameno 
RootFormatVersionno 
RootMetaDictionary?no 
RootObjectDirectory?no 
RootPreface?no 
RoundedCaptureFilmFrameRateno 
RoundedTimecodeTimebaseno 
RoundingLawno 
RoundingMethodCodeno 
RoyaltyIncomeInformationno 
RoyaltyPaymentInformationno 
Rsizno 
SDKVersionno 
SICI?no 
SMPTE12MUserDateTime?no 
SMPTE309MUserDateTime?no 
SMPTE337MDataStreamNumberno 
SMPTEUL?no 
SalesContractNumberno 
Salutationno 
Salutationno 
SampleIndex?no 
SampleRateno 
SampledHeightno 
SampledWidthno 
SampledXOffsetno 
SampledYOffsetno 
SamplingHierarchyCodeno 
SamplingStructureCodeno 
ScanningDirectionno 
SceneFramework---> MXF Tags
SceneNumberno 
SceneNumberno 
SceneSettingPeriodSets?no 
SceneShotSets?no 
ScramblingKeyKindno 
ScramblingKeyValueno 
Scripting---> MXF Tags
ScriptingKindno 
ScriptingKindno 
ScriptingLocators?no 
ScriptingSets?no 
ScriptingTextno 
ScriptingTextno 
SeasonEpisodeNumberno 
SeasonEpisodeTitleno 
SecondGivenNameno 
SecondGivenNameno 
SecondaryExtendedSpokenLanguageCodeno 
SecondaryOriginalExtendedSpokenLanguageCodeno 
SecondaryOriginalLanguageCodeno 
SecondarySpokenLanguageCodeno 
SecondaryTitleno 
SecondaryTitleno 
SectorSizeno 
SecurityClassificationno 
SecurityClassificationno 
SecurityClassificationCaveatsno 
SecurityClassificationCaveatsno 
Selected?no 
SensorModeno 
SensorRollAngleno 
SensorSizeno 
SensorTypeno 
SensorTypeCodeno 
Sequence?no 
SequenceOffsetno 
SequenceSet---> MXF Tags
SeriesNumberno 
SeriesNumberno 
SeriesinaSeriesGroupCountno 
SetElementType?no 
SettingCityNameno 
SettingCityNameno 
SettingCountryCodeno 
SettingCountryNameno 
SettingCountryNameno 
SettingDateTimeno 
SettingDescriptionno 
SettingDescriptionno 
SettingPeriod---> MXF Tags
SettingPeriodDescriptionno 
SettingPeriodDescriptionno 
SettingPostalCodeno 
SettingPostalCodeno 
SettingRegionCodeno 
SettingRegionNameno 
SettingRoomNumberno 
SettingRoomNumberno 
SettingStateOrProvinceOrCountyNameno 
SettingStateOrProvinceOrCountyNameno 
SettingStreetNameno 
SettingStreetNameno 
SettingStreetNumberOrBuildingNameno 
SettingStreetNumberOrBuildingNameno 
SettingTownNameno 
SettingTownNameno 
ShimNameno 
ShootingCountryCodeno 
ShootingRegionCodeno 
ShootingRegionNameno 
Shot---> MXF Tags
ShotCommentno 
ShotCommentno 
ShotCommentKindno 
ShotCommentKindno 
ShotCueWordsSet?no 
ShotDescriptionno 
ShotDescriptionno 
ShotDurationno 
ShotListno 
ShotLocationSets?no 
ShotParticipantRoleSets?no 
ShotPersonSets?no 
ShotStartPositionno 
ShotTrackIDsno 
SideNumberno 
Signal-to-NoiseRationo 
SignalFormCodeno 
SignalStandardno 
SignalStandardno 
SignatureTuneFlagno 
SimpleFlaggingCountno 
SingleSequenceFlagno 
Sizeno 
SlantRangeno 
SlateInformationno 
SlateTitleno 
SliceCountno 
SliceNumberno 
SliceOffsetList?no 
Software-OnlySupportFlagno 
SourceClip?no 
SourceContainerFormat?no 
SourceImageCenterXCoordinateno 
SourceImageCenterYCoordinateno 
SourceIndex?no 
SourceKey?no 
SourceLengthno 
SourceOrganizationno 
SourceOrganizationno 
SourcePackage---> MXF Tags
SourcePackageID?no 
SourceSpecies?no 
SourceTrackIDno 
SourceTrackIDsno 
SourceValue?no 
SpeedChangeEffectFlagno 
SplicingMetadata?no 
StartTimeRelativeToReferenceno 
StartTimeRelativeToReferenceno 
StartTimecodeno 
StartTimecodeRelativeToReference?no 
StateOrProvinceOrCountyNameno 
StateOrProvinceOrCountyNameno 
StaticTrack---> MXF Tags
StillFrame?no 
StorageDeviceKindno 
StorageKindno 
StorageKindno 
StorageKindCodeno 
StorageMediaIDno 
StorageMediaKindno 
StoredANCLineNumberno 
StoredF2Offsetno 
StoredVBILineNumberno 
StratumKindno 
StreamData?no 
StreamElementType?no 
StreamIDno 
StreamOffsetno 
StreamPositionIndicatorno 
StreamPositionIndicatorno 
StreamPositionIndicatorno 
StreamPositionIndicatorno 
StreetNameno 
StreetNameno 
StreetNumberno 
StreetNumberno 
StringElementType?no 
StructuralComponent---> MXF Tags
Sub-descriptors?no 
SubDescriptor?no 
SubDescriptors?no 
SubjectAbsoluteHeadingno 
SubjectAbsoluteSpeedno 
SubjectDistanceno 
SubjectNameno 
SubjectNameno 
SubjectRelativeHeadingno 
SubjectRelativePositionalAccuracyno 
SubjectRelativeSpeedno 
SubtitleDatafileFlagno 
SubtitlesPresentno 
SupplementaryNameno 
SupplementaryNameno 
SupplementaryOrganizationNameno 
SupplementaryOrganizationNameno 
SupplierAccountNameno 
SupplierAccountNameno 
SupplierAccountNumberno 
SupplierIdentificationKindno 
SupplierIdentificationValueno 
SupplyContractNumberno 
SupplyingDepartmentNameno 
SupportOrAdministrationStatusno 
SupportOrAdministrationStatusno 
SupportOrganizationRoleno 
SupportOrganizationRoleno 
SystemNameOrNumberno 
TIFFSummary?no 
TaggedValueDefinitions?no 
TaggedValueParentProperties?no 
TakeNumberno 
TapeBatchNumberno 
TapeBatchNumberno 
TapeCapacityno 
TapeFormat?no 
TapeFormulationno 
TapeFormulationno 
TapeManufacturerno 
TapeManufacturerno 
TapePartitionCapacityno 
TapeShellKindno 
TapeShellKindno 
TapeStockno 
TapeStockno 
TargetAudienceno 
TargetAudienceno 
TargetClassOfStrongReference?no 
TargetClassOfWeakReference?no 
TargetSet?no 
TargetWidthno 
TechnicalValueno 
TelephoneNumberno 
TelephoneNumberno 
TeletextSubtitlesAvailableno 
TeletextSubtitlesFlagno 
TemporalOffsetno 
TerminatingFillerDatano 
TextLocator---> MXF Tags
TextlessBlackDurationno 
TextlessMaterialno 
TextualDescriptionKindno 
TextualDescriptionKindno 
Themeno 
Themeno 
ThemeMusicFlagno 
ThesaurusNameno 
ThesaurusNameno 
ThirdGivenNameno 
ThirdGivenNameno 
TimePeriodNameno 
TimePeriodNameno 
TimeSystemOffsetno 
TimeUnitKindno 
TimebaseReferenceTrackIDno 
TimecodeArray?no 
TimecodeComponent---> MXF Tags
TimecodeCreationDateTime?no 
TimecodeEndDateTime?no 
TimecodeEventEndDateTime?no 
TimecodeEventStartDateTime?no 
TimecodeKindno 
TimecodeLastModifyDate?no 
TimecodeModifyDate?no 
TimecodeSourceKindno 
TimecodeStartDateTime?no 
TimecodeStreamData?no 
TimecodeTimebaseno 
TimecodeTimebaseno 
TimecodeUserBitsFlagno 
TimepointValue?no 
TimingBiasCorrectionno 
TimingBiasCorrectionDescriptionno 
TitleKindno 
TitleKindno 
Titles---> MXF Tags
TitlesSets?no 
ToleranceInterpolationMethod?no 
ToleranceMode?no 
ToleranceWindow?no 
ToolkitVersionno 
TotalCurrencyAmountno 
TotalEpisodeCountno 
TotalIncomeno 
TotalLinesperFrameno 
TotalNumberInSequenceno 
TotalPaymentno 
TotalSamplesperLineno 
Track---> MXF Tags
TrackIDno 
TrackNameno 
TrackNameno 
TrackNumberno 
TrackNumberBatchno 
Tracks?no 
TrafficIDno 
TrailingLinesno 
TranscriptReferenceno 
TranscriptReferenceno 
TransferFilmFrameRateno 
TransitionEffect?no 
TransmissionIDno 
TransportStreamIDno 
TripletSequenceNumberno 
TypeDefinitionElementValueListno 
TypeDefinitionExtendibleElementValues?no 
TypeDefinitions?no 
UCSEncodingno 
UPID?no 
UPN?no 
URLno 
URLno 
URLno 
URLno 
URNno 
UTCEndDateTimeno 
UTCEventEndDateTimeno 
UTCEventEndDateTimeno 
UTCEventStartDateTimeno 
UTCEventStartDateTimeno 
UTCInstantDateTimeno 
UTCInstantDateTimeno 
UTCLastModifyDateno 
UTCLastModifyDateno 
UTCStartDateTimeno 
UTCStartDateTimeno 
UTCUserDateTimeno 
UniformDataFlagno 
UnknownBWFChunks?no 
UpstreamAudioCompressionAlgorithmno 
UseDefaultValueno 
UserDataMode?no 
UserNameno 
UserNameno 
UserPositionno 
V10IndexTableSegment?no 
VBEEndOffsetno 
VBIDataDescriptor---> MXF Tags
VBILineCountno 
VBIPayloadByteArrayno 
VBIPayloadSampleCodingno 
VBIPayloadSampleCountno 
VBIWrappingTypeno 
VC-1AverageBitrateno 
VC-1BPictureCountno 
VC-1CodedContentTypeno 
VC-1IdenticalGOPno 
VC-1InitializationMetadata?no 
VC-1Levelno 
VC-1MaximumBitrateno 
VC-1MaximumGOPno 
VC-1Profileno 
VC-1SingleSequenceno 
Value?no 
VariableArrayElementType?no 
VersionNumberno 
VersionNumberStringno 
VersionNumberStringno 
VersionTitleno 
VersionTitleno 
VerticalActionSafePercentageno 
VerticalDatumno 
VerticalGraphicsSafePercentageno 
VerticalSub-samplingno 
VideoAndFilmFrameRelationshipno 
VideoAverageBitrateno 
VideoClipDurationno 
VideoCodingSchemeIDno 
VideoColorKindno 
VideoCompressionAlgorithmno 
VideoDeviceKindno 
VideoDeviceParameterNameno 
VideoDeviceParameterSettingno 
VideoFixedBitrateno 
VideoIndexArray?no 
VideoLineMapno 
VideoNoiseReductionAlgorithmno 
VideoOrImageCompressionAlgorithmno 
VideoPayloadIdentifierno 
VideoPayloadIdentifier2002no 
VideoTestParameterno 
VideoTestResultno 
VideoTestResultno 
VideoWatermarkKindno 
ViewportAspectRationo 
ViewportHeightno 
ViewportImageCenterCCoordinateno 
ViewportImageCenterYCoordinateno 
ViewportWidthno 
VoiceTalentNameno 
WAVESummary?no 
WaveAudioDescriptor---> MXF Tags
Weightingno 
WhiteReferenceLevelno 
Work-in-ProgressFlagno 
WorkingTitleno 
WorkingTitleno 
XMLDocumentText?no 
XMLDocumentTextno 
XMLDocumentTextno 
XMLDocumentText?no 
XOsizno 
XTOsizno 
XTsizno 
Xsizno 
YOsizno 
YTOsizno 
YTsizno 
Ysizno 
+ +

MXF Header Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0MXFVersionno 
24FooterPositionno 
32HeaderSizeno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Apr 1, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MacOS.html b/ExifTool/html/TagNames/MacOS.html new file mode 100644 index 0000000..55ef51e --- /dev/null +++ b/ExifTool/html/TagNames/MacOS.html @@ -0,0 +1,660 @@ + + + + +MacOS Tags + + + +

MacOS Tags

+

+On MacOS systems, the there are additional MDItem and XAttr Finder tags that +may be extracted. These tags are not extracted by default -- they must be +specifically requested or enabled via an API option. (Except when reading +MacOS "._" files directly, see below.)

+ +

The tables below list some of the tags that may be extracted, but ExifTool +will extract all available information even for tags not listed.

+ +

Tags in these tables are referred to as "pseudo" tags because their +information is not stored in the file itself. As such, Writable tags in +these tables may be changed without having to rewrite the file. +

+

Note that on some filesystems, MacOS creates sidecar files with names that +begin with "._". ExifTool will read these files if specified, and extract +the information listed in the following table without the need for extra +options, but these files are not writable directly.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0002RSRC---> RSRC Tags
0x0009ATTR---> MacOS XAttr Tags
+ +

MacOS XAttr Tags

+

XAttr tags are extracted using the "xattr" utility. They are extracted if +any "XAttr*" tag or the MacOS group is specifically requested, or by setting +the API XAttrTags option to 1 or the API RequestAll option to 2 or higher. +And they are extracted by default from MacOS "._" files when reading +these files directly.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
XAttrAppleMailDateReceivedno 
XAttrAppleMailDateSentno 
XAttrAppleMailIsRemoteAttachmentno 
XAttrFinderInfono 
XAttrLastUsedDateno 
XAttrMDItemDownloadedDateno 
XAttrMDItemFinderCommentno 
XAttrMDItemWhereFromsno 
XAttrMDLabelno 
XAttrQuarantineyes!(quarantine information for files downloaded from the internet. May only be +deleted when writing)
XAttrResourceForkno 
+ +

MacOS MDItem Tags

+

MDItem tags are extracted using the "mdls" utility. They are extracted if +any "MDItem*" tag or the MacOS group is specifically requested, or by +setting the API MDItemTags option to 1 or the API RequestAll option to 2 or +higher. Note that these tags do not necessarily reflect the current +metadata of a file -- it may take some time for the MacOS mdworker daemon to +index the file after a metadata change.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AppleMailDateReceivedno 
AppleMailDateSentno 
AppleMailFlaggedno 
AppleMailIsRemoteAttachmentno 
AppleMailMessageIDno 
AppleMailPriorityno 
AppleMailReadno 
AppleMailRepliedTono 
MDItemAccountHandlesno 
MDItemAccountIdentifierno 
MDItemAcquisitionMakeno 
MDItemAcquisitionModelno 
MDItemAltitudeno 
MDItemApertureno 
MDItemAudioBitRateno 
MDItemAudioChannelCountno 
MDItemAuthorEmailAddressesno 
MDItemAuthorsno 
MDItemBitsPerSampleno 
MDItemBundleIdentifierno 
MDItemCityno 
MDItemCodecsno 
MDItemColorSpaceno 
MDItemCommentno 
MDItemContentCreationDateno 
MDItemContentCreationDateRankingno 
MDItemContentCreationDate_Rankingno 
MDItemContentModificationDateno 
MDItemContentTypeno 
MDItemContentTypeTreeno 
MDItemContributorsno 
MDItemCopyrightno 
MDItemCountryno 
MDItemCreatorno 
MDItemDateAddedno 
MDItemDateAdded_Rankingno 
MDItemDescriptionno 
MDItemDisplayNameno 
MDItemDownloadedDateno 
MDItemDurationSecondsno 
MDItemEXIFGPSVersionno 
MDItemEXIFVersionno 
MDItemEmailConversationIDno 
MDItemEncodingApplicationsno 
MDItemExposureModeno 
MDItemExposureProgramno 
MDItemExposureTimeSecondsno 
MDItemFNumberno 
MDItemFSContentChangeDateno 
MDItemFSCreationDateyes!(file creation date. Requires "setfile" for writing. Note that when +reading, it may take a few seconds after writing a file before this value +reflects the change. However, FileCreateDate is updated immediately)
MDItemFSCreatorCodeno 
MDItemFSFinderFlagsno 
MDItemFSHasCustomIconno 
MDItemFSInvisibleno 
MDItemFSIsExtensionHiddenno 
MDItemFSIsStationeryno 
MDItemFSLabelyes! + +
0 = 0 (none) +
1 = 1 (Gray) +
2 = 2 (Green) +
3 = 3 (Purple)
  4 = 4 (Blue) +
5 = 5 (Yellow) +
6 = 6 (Red) +
7 = 7 (Orange)
+
MDItemFSNameno 
MDItemFSNodeCountno 
MDItemFSOwnerGroupIDno 
MDItemFSOwnerUserIDno 
MDItemFSSizeno 
MDItemFSTypeCodeno 
MDItemFinderCommentyes! 
MDItemFlashOnOffno 
MDItemFocalLengthno 
MDItemGPSDateStampno 
MDItemGPSStatusno 
MDItemGPSTrackno 
MDItemHasAlphaChannelno 
MDItemISOSpeedno 
MDItemIdentifierno 
MDItemImageDirectionno 
MDItemInterestingDateRankingno 
MDItemInterestingDate_Rankingno 
MDItemIsApplicationManagedno 
MDItemIsExistingThreadno 
MDItemIsLikelyJunkno 
MDItemKeywordsno 
MDItemKindno 
MDItemLastUsedDateno 
MDItemLastUsedDate_Rankingno 
MDItemLatitudeno 
MDItemLensModelno 
MDItemLogicalSizeno 
MDItemLongitudeno 
MDItemMailDateReceived_Rankingno 
MDItemMailboxesno 
MDItemMediaTypesno 
MDItemNumberOfPagesno 
MDItemOrientationno 
MDItemOriginApplicationIdentifierno 
MDItemOriginMessageIDno 
MDItemOriginSenderDisplayNameno 
MDItemOriginSenderHandleno 
MDItemOriginSubjectno 
MDItemPageHeightno 
MDItemPageWidthno 
MDItemPhysicalSizeno 
MDItemPixelCountno 
MDItemPixelHeightno 
MDItemPixelWidthno 
MDItemPrimaryRecipientEmailAddressesno 
MDItemProfileNameno 
MDItemRecipientsno 
MDItemRedEyeOnOffno 
MDItemResolutionHeightDPIno 
MDItemResolutionWidthDPIno 
MDItemSecurityMethodno 
MDItemSpeedno 
MDItemStateOrProvinceno 
MDItemStreamableno 
MDItemSubjectno 
MDItemTimestampno 
MDItemTitleno 
MDItemTotalBitRateno 
MDItemUseCountno 
MDItemUsedDatesno 
MDItemUserDownloadedDateno 
MDItemUserDownloadedUserHandleno 
MDItemUserSharedReceivedDateno 
MDItemUserSharedReceivedRecipientno 
MDItemUserSharedReceivedRecipientHandleno 
MDItemUserSharedReceivedSenderno 
MDItemUserSharedReceivedSenderHandleno 
MDItemUserSharedReceivedTransportno 
MDItemUserTagsyes!+(requires "tag" utility for writing -- install with "brew install tag". Note +that user tags may not contain a comma, and that duplicate user tags will +not be written)
MDItemVersionno 
MDItemVideoBitRateno 
MDItemWhereFromsno 
MDItemWhiteBalanceno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 16, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Matroska.html b/ExifTool/html/TagNames/Matroska.html new file mode 100644 index 0000000..cf4aa1a --- /dev/null +++ b/ExifTool/html/TagNames/Matroska.html @@ -0,0 +1,1651 @@ + + + + +Matroska Tags + + + +

Matroska Tags

+

The following tags are extracted from Matroska multimedia container files. +This container format is used by file types such as MKA, MKV, MKS and WEBM. +For speed, by default ExifTool extracts tags only up to the first Cluster. +However, the Verbose (-v) and Unknown = 2 (-U) options force processing of +Cluster data, and the ExtractEmbedded (-ee) option skips over Clusters to +read subsequent tags. See +http://www.matroska.org/technical/specs/index.html for the official +Matroska specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000ChapterDisplay---> Matroska Tags
0x0003TrackTypeno + +
0x1 = Video +
0x2 = Audio +
0x3 = Complex +
0x10 = Logo
  0x11 = Subtitle +
0x12 = Buttons +
0x20 = Control
+
0x0005ChapterStringno 
0x0006VideoCodecID +
AudioCodecID +
CodecID
no
no
no
 
0x0008TrackDefaultno0 = No +
1 = Yes
0x0009ChapterTrackNumber?no 
0x000eSlices---> Matroska Tags
0x000fChapterTrack---> Matroska Tags
0x0011ChapterTimeStartno 
0x0012ChapterTimeEndno 
0x0016CueRefTime?no 
0x0017CueRefCluster?no 
0x0018ChapterFlagHidden?no 
0x001aVideoScanTypeno0 = Progressive +
1 = Interlaced
0x001bBlockDuration?no 
0x001cTrackLacing?no0 = No +
1 = Yes
0x001fAudioChannelsno 
0x0020BlockGroup---> Matroska Tags
0x0021Block?no 
0x0022BlockVirtual?no 
0x0023SimpleBlock?no 
0x0024CodecState?no 
0x0025BlockAdditional?no 
0x0026BlockMore---> Matroska Tags
0x0027Positionno 
0x002aCodecDecodeAllno0 = No +
1 = Yes
0x002bPrevSizeno 
0x002eTrackEntry---> Matroska Tags
0x002fEncryptedBlock?no 
0x0030ImageWidthno 
0x0033CueTime?no 
0x0035AudioSampleRateno 
0x0036ChapterAtom---> Matroska Tags
0x0037CueTrackPositions---> Matroska Tags
0x0039TrackUsedno0 = No +
1 = Yes
0x003aImageHeightno 
0x003bCuePoint---> Matroska Tags
0x003fCRC-32?no 
0x004bBlockAdditionalID?no 
0x004cLaceNumber?no 
0x004dFrameNumber?no 
0x004eDelay?no 
0x004fClusterDuration?no 
0x0057TrackNumberno 
0x005bCueReference---> Matroska Tags
0x0060Video---> Matroska Tags
0x0061Audio---> Matroska Tags
0x0067TimeCode?no 
0x0068TimeSlice---> Matroska Tags
0x006aCueCodecState?no 
0x006bCueRefCodecState?no 
0x006cVoid?no 
0x006eBlockAddID?no 
0x0071CueClusterPosition?no 
0x0077CueTrack?no 
0x007aReferencePriority?no 
0x007bReferenceBlock?no 
0x007dReferenceVirtual?no 
0x0254ContentCompressionAlgorithmno0 = zlib +
1 = bzlib +
2 = lzo1x +
3 = Header Stripping
0x0255ContentCompressionSettings?no 
0x0282DocTypeno 
0x0285DocTypeReadVersionno 
0x0286EBMLVersionno 
0x0287DocTypeVersionno 
0x02f2EBMLMaxIDLength?no 
0x02f3EBMLMaxSizeLength?no 
0x02f7EBMLReadVersionno 
0x037cChapterLanguageno 
0x037eChapterCountryno 
0x0444SegmentFamily?no 
0x0461DateTimeOriginalno 
0x047aTagLanguageBCP47no 
0x0484TagDefaultno0 = No +
1 = Yes
0x0485TagBinaryno 
0x0487TagStringno 
0x0489Durationno 
0x050dChapterProcessPrivate?no 
0x0598ChapterFlagEnabled?no 
0x05a3TagNameno 
0x05b9EditionEntry---> Matroska Tags
0x05bcEditionUID?no 
0x05bdEditionFlagHidden?no 
0x05dbEditionFlagDefault?no 
0x05ddEditionFlagOrdered?no 
0x065cAttachedFileDatano 
0x0660AttachedFileMIMETypeno 
0x066eAttachedFileNameno 
0x0675AttachedFileReferral?no 
0x067eAttachedFileDescriptionno 
0x06aeAttachedFileUIDno 
0x07e1ContentEncryptionAlgorithmno + +
0 = Not Encrypted +
1 = DES +
2 = 3DES
  3 = Twofish +
4 = Blowfish +
5 = AES
+
0x07e2ContentEncryptionKeyID?no 
0x07e3ContentSignature?no 
0x07e4ContentSignatureKeyID?no 
0x07e5ContentSignatureAlgorithmno0 = Not Signed +
1 = RSA
0x07e6ContentSignatureHashAlgorithmno0 = Not Signed +
1 = SHA1-160 +
2 = MD5
0x0d80MuxingAppno 
0x0dbbSeek---> Matroska Tags
0x1031ContentEncodingOrder?no 
0x1032ContentEncodingScope?no 
0x1033ContentEncodingTypeno0 = Compression +
1 = Encryption
0x1034ContentCompression---> Matroska Tags
0x1035ContentEncryption---> Matroska Tags
0x135fCueRefNumber?no 
0x136eTrackNameno 
0x1378CueBlockNumber?no 
0x137fTrackOffset?no 
0x13abSeekID?no 
0x13acSeekPosition?no 
0x13b8Stereo3DModeno 
0x14aaCropBottomno 
0x14b0DisplayWidthno 
0x14b2DisplayUnitno0 = Pixels +
1 = cm +
2 = inches +
3 = Display Aspect Ratio +
4 = Unknown
0x14b3AspectRatioTypeno0 = Free Resizing +
1 = Keep Aspect Ratio +
2 = Fixed
0x14baDisplayHeightno 
0x14bbCropTopno 
0x14ccCropLeftno 
0x14ddCropRightno 
0x15aaTrackForcedno0 = No +
1 = Yes
0x15eeMaxBlockAdditionID?no 
0x1741WritingAppno 
0x1854SilentTracks---> Matroska Tags
0x18d7SilentTrackNumberno 
0x21a7AttachedFile---> Matroska Tags
0x2240ContentEncoding---> Matroska Tags
0x2264AudioBitsPerSampleno 
0x23a2CodecPrivate?no 
0x23c0Targets---> Matroska Tags
0x23c3ChapterPhysicalEquivalentno + +
10 = Index +
20 = Track +
30 = Session +
40 = Layer
  50 = Side +
60 = CD / DVD +
70 = Set / Package
+
0x23c4TagChapterUIDno 
0x23c5TagTrackUIDno 
0x23c6TagAttachmentUIDno 
0x23c9TagEditionUIDno 
0x23caTargetTypeno 
0x2532SignedElement?no 
0x2624TrackTranslate---> Matroska Tags
0x26a5TrackTranslateTrackID?no 
0x26bfTrackTranslateCodecno0 = Matroska Script +
1 = DVD Menu
0x26fcTrackTranslateEditionUID?no 
0x27c8SimpleTag---> Matroska Tags
0x28caTargetTypeValueno 
0x2911ChapterProcessCommand---> Matroska Tags
0x2922ChapterProcessTime?no0 = For Duration of Chapter +
1 = Before Chapter +
2 = After Chapter
0x2924ChapterTranslate---> Matroska Tags
0x2933ChapterProcessData?no 
0x2944ChapterProcess---> Matroska Tags
0x2955ChapterProcessCodecID?no0 = Matroska +
1 = DVD
0x29a5ChapterTranslateID?no 
0x29bfChapterTranslateCodecno0 = Matroska Script +
1 = DVD Menu
0x29fcChapterTranslateEditionUID?no 
0x2d80ContentEncodings---> Matroska Tags
0x2de7MinCache?no 
0x2df8MaxCache?no 
0x2e67ChapterSegmentUID?no 
0x2ebcChapterSegmentEditionUID?no 
0x2fabTrackOverlay?no 
0x3373Tag---> Matroska Tags
0x3384SegmentFileNameno 
0x33a4SegmentUID?no 
0x33c4ChapterUID?no 
0x33c5TrackUIDno 
0x3446TrackAttachmentUIDno 
0x35a1BlockAdditions---> Matroska Tags
0x38b5OutputAudioSampleRateno 
0x3ba9Titleno 
0x3d7bChannelPositions?no 
0x3e5bSignatureElements---> Matroska Tags
0x3e7bSignatureElementList---> Matroska Tags
0x3e8aSignatureAlgono 
0x3e9aSignatureHashno 
0x3ea5SignaturePublicKey?no 
0x3eb5Signature?no 
0x7670Projection---> Matroska Projection Tags
0x2b59cTrackLanguageno 
0x2b59dTrackLanguageIETFno 
0x3314fTrackTimecodeScaleno 
0x383e3FrameRateno 
0x3e383VideoFrameRate +
DefaultDuration
no
no
 
0x58688VideoCodecName +
AudioCodecName +
CodecName
no
no
no
 
0x6b240CodecDownloadURLno 
0xad7b1TimecodeScaleno 
0xeb524ColorSpace?no 
0xfb523Gammano 
0x1a9697CodecSettingsno 
0x1b4040CodecInfoURLno 
0x1c83abPrevFileNameno 
0x1cb923PrevUID?no 
0x1e83bbNextFileNameno 
0x1eb923NextUID?no 
0x43a770Chapters---> Matroska Tags
0x14d9b74SeekHead---> Matroska Tags
0x254c367Tags---> Matroska Tags
0x549a966Info---> Matroska Tags
0x654ae6bTracks---> Matroska Tags
0x8538067SegmentHeader---> Matroska Tags
0x941a469Attachments---> Matroska Tags
0xa45dfa3EBMLHeader---> Matroska Tags
0xb538667SignatureSlot---> Matroska Tags
0xc53bb6bCues---> Matroska Tags
0xf43b675Cluster---> Matroska Tags
+ +

Matroska Projection Tags

+

Projection tags defined by the Spherical Video V2 specification. See +https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md +for the specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x7671ProjectionTypeno0 = Rectangular +
1 = Equirectangular +
2 = Cubemap +
3 = Mesh
0x7672EquirectangularProj +
CubemapProj +
ProjectionPrivate
-
-
no
--> QuickTime equi Tags +
--> QuickTime cbmp Tags
0x7673ProjectionPoseYawno 
0x7674ProjectionPosePitchno 
0x7675ProjectionPoseRollno 
+ +

Matroska StdTag Tags

+

Standardized Matroska tags, stored in a SimpleTag structure (see +https://www.matroska.org/technical/tagging.html).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ACCOMPANIMENT'Accompanimentno 
'ACTOR'Actorno 
'ADDRESS'Addressno 
'ARRANGER'Arrangerno 
'ARTIST'Artistno 
'ART_DIRECTOR'ArtDirectorno 
'ASSISTANT_DIRECTOR'AssistantDirectorno 
'BARCODE'Barcodeno 
'BPM'BPMno 
'BPS'BPSno 
'CATALOG_NUMBER'CatalogNumberno 
'CHARACTER'Characterno 
'CHOREGRAPHER'Choregrapherno 
'COMMENT'Commentno 
'COMPOSER'Composerno 
'COMPOSER_NATIONALITY'ComposerNationalityno 
'COMPOSITION_LOCATION'CompositionLocationno 
'CONDUCTOR'Conductorno 
'CONTENT_TYPE'ContentTypeno 
'COPRODUCER'Coproducerno 
'COPYRIGHT'Copyrightno 
'COSTUME_DESIGNER'CostumeDesignerno 
'COUNTRY'Countryno 
'DATE_DIGITIZED'CreateDateno 
'DATE_ENCODED'DateEncodedno 
'DATE_PURCHASED'DatePurchasedno 
'DATE_RECORDED'DateTimeOriginalno 
'DATE_RELEASED'DateReleasedno 
'DATE_TAGGED'DateTaggedno 
'DATE_WRITTEN'DateWrittenno 
'DESCRIPTION'Descriptionno 
'DIRECTOR'Directorno 
'DIRECTOR_OF_PHOTOGRAPHY'DirectorOfPhotographyno 
'DISTRIBUTED_BY'DistributedByno 
'EDITED_BY'EditedByno 
'EMAIL'Emailno 
'ENCODED_BY'EncodedByno 
'ENCODER'Encoderno 
'ENCODER_SETTINGS'EncoderSettingsno 
'EXECUTIVE_PRODUCER'ExecutiveProducerno 
'FAX'FAXno 
'FPS'FPSno 
'GENRE'Genreno 
'IMDB'IMDBno 
'INITIAL_KEY'InitialKeyno 
'INSTRUMENTS'Instrumentsno 
'ISBN'ISBNno 
'ISRC'ISRCno 
'KEYWORDS'Keywordsno 
'LABEL'Labelno 
'LABEL_CODE'LabelCodeno 
'LAW_RATING'LawRatingno 
'LCCN'Lccnno 
'LEAD_PERFORMER'LeadPerformerno 
'LICENSE'Licenseno 
'LYRICIST'Lyricistno 
'LYRICS'Lyricsno 
'MASTERED_BY'MasteredByno 
'MCDI'MCDIno 
'MEASURE'Measureno 
'MIXED_BY'MixedByno 
'MOOD'Moodno 
'ORIGINAL'Originalno 
'ORIGINAL_MEDIA_TYPE'OriginalMediaTypeno 
'PART_NUMBER'PartNumberno 
'PART_OFFSET'PartOffsetno 
'PERIOD'Periodno 
'PHONE'Phoneno 
'PLAY_COUNTER'PlayCounterno 
'PRODUCER'Producerno 
'PRODUCTION_COPYRIGHT'ProductionCopyrightno 
'PRODUCTION_DESIGNER'ProductionDesignerno 
'PRODUCTION_STUDIO'ProductionStudiono 
'PUBLISHER'Publisherno 
'PURCHASE_CURRENCY'PurchaseCurrencyno 
'PURCHASE_INFO'PurchaseInfono 
'PURCHASE_ITEM'PurchaseItemno 
'PURCHASE_OWNER'PurchaseOwnerno 
'PURCHASE_PRICE'PurchasePriceno 
'RATING'Ratingno 
'RECORDING_LOCATION'RecordingLocationno 
'REMIXED_BY'RemixedByno 
'REPLAYGAIN_GAIN'ReplaygainGainno 
'REPLAYGAIN_PEAK'ReplaygainPeakno 
'SAMPLE'Sampleno 
'SCREENPLAY_BY'ScreenplayByno 
'SORT_WITH'SortWithno 
'SOUND_ENGINEER'SoundEngineerno 
'SPHERICAL-VIDEO'SphericalVideoXML---> XMP Tags
'SUBJECT'Subjectno 
'SUBTITLE'Subtitleno 
'SUMMARY'Summaryno 
'SYNOPSIS'Synopsisno 
'TERMS_OF_USE'TermsOfUseno 
'THANKS_TO'ThanksTono 
'TITLE'Titleno 
'TMDB'TMDBno 
'TOTAL_PARTS'TotalPartsno 
'TUNING'Tuningno 
'TVDB'TVDBno 
'URL'URLno 
'WRITTEN_BY'WrittenByno 
'spherical-video'SphericalVideoXML---> XMP Tags
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 28, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Microsoft.html b/ExifTool/html/TagNames/Microsoft.html new file mode 100644 index 0000000..ab3cc6d --- /dev/null +++ b/ExifTool/html/TagNames/Microsoft.html @@ -0,0 +1,2149 @@ + + + + +Microsoft Tags + + + +

Microsoft XMP Tags

+

Microsoft Photo 1.0 schema XMP tags. This is likely not a complete list, +but represents tags which have been observed in sample images. The actual +namespace prefix is "MicrosoftPhoto", but ExifTool shortens this in the +family 1 group name.

+ +

These tags belong to the ExifTool XMP-microsoft family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CameraSerialNumberstring 
CreatorAppIDstring 
CreatorOpenWithUIOptionsstring 
DateAcquireddate 
FlashManufacturerstring 
FlashModelstring 
ItemSubTypestring 
LastKeywordIPTCstring+ 
LastKeywordXMPstring+ 
LensManufacturerstring 
LensModelstring/ 
RatingPercentstring(called Rating by the spec; XMP-xmp:Rating values of 1,2,3,4 and 5 stars correspond to RatingPercent +values of 1,25,50,75 and 99 respectively)
+ +

Microsoft MP1 Tags

+

Microsoft Photo 1.1 schema XMP tags which have been observed.

+ +

These tags belong to the ExifTool XMP-MP1 family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Brightnessstring/ 
CameraModelIDstring/ 
Contraststring/ 
ExposureCompensationstring/ 
PanoramicStitchCameraMotionstring'3DRotation' = 3D Rotation +
'Affine' = Affine +
'Homography' = Homography +
'RigidScale' = Rigid Scale
PanoramicStitchMapTypestring'Horizontal-Cylindrical' = Horizontal Cylindrical +
'Horizontal-Spherical' = Horizontal Spherical +
'Perspective' = Perspective +
'Vertical-Cylindrical' = Vertical Cylindrical +
'Vertical-Spherical' = Vertical Spherical
PanoramicStitchPhi0real 
PanoramicStitchPhi1real 
PanoramicStitchTheta0real 
PanoramicStitchTheta1real 
PipelineVersionstring 
StreamTypestring 
WhiteBalance0real 
WhiteBalance1real 
WhiteBalance2real 
+ +

Microsoft MP Tags

+

Microsoft Photo 1.2 schema XMP tags which have been observed.

+ +

These tags belong to the ExifTool XMP-MP family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
RegionInfoDateRegionsValiddate_ 
RegionInfoMPstruct--> Microsoft RegionInfo Struct +
(called RegionInfo by the spec)
RegionInfoRegionsstruct_+--> Microsoft Regions Struct
RegionPersonDisplayNamestring_+(RegionInfoRegionsPersonDisplayName)
RegionPersonEmailDigeststring_+(RegionInfoRegionsPersonEmailDigest)
RegionPersonLiveIdCIDstring_+(RegionInfoRegionsPersonLiveIdCID)
RegionPersonSourceIDstring_+(RegionInfoRegionsPersonSourceID)
RegionRectanglestring_+(RegionInfoRegionsRectangle)
+ +

Microsoft RegionInfo Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
DateRegionsValiddate 
RegionsMicrosoft Regions+--> Microsoft Regions Struct
+ +

Microsoft Regions Struct

+

Note that PersonLiveIdCID element is called PersonLiveCID according to the +Microsoft specification, but in practice their software actually writes +PersonLiveIdCID, so ExifTool uses this too.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
PersonDisplayNamestring 
PersonEmailDigeststring 
PersonLiveIdCIDstring 
PersonSourceIDstring 
Rectanglestring 
+ +

Microsoft Stitch Tags

+

Information found in the Microsoft custom EXIF tag 0x4748, as written by +Windows Live Photo Gallery.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0PanoramicStitchVersionint32u 
1PanoramicStitchCameraMotionint32u2 = Rigid Scale +
3 = Affine +
4 = 3D Rotation +
5 = Homography
2PanoramicStitchMapTypeint32u0 = Perspective +
1 = Horizontal Cylindrical +
2 = Horizontal Spherical +
257 = Vertical Cylindrical +
258 = Vertical Spherical
3PanoramicStitchTheta0float 
4PanoramicStitchTheta1float 
5PanoramicStitchPhi0float 
6PanoramicStitchPhi1float 
+ +

Microsoft Xtra Tags

+

Tags found in the Microsoft "Xtra" atom of QuickTime videos. Tag ID's are +not shown because some are unruly GUID's. Currently most of these tags are +not writable because the Microsoft documentation is poor and samples were +not available, but more tags may be made writable in the future if samples +are provided. Note that writable tags in this table are are flagged to +"Avoid", which means that other more common tags will be written instead if +possible unless the Microsoft group is specified explicitly.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Abstractno 
AccountNameno 
AcquisitionTimeno 
AcquisitionTimeDayno 
AcquisitionTimeMonthno 
AcquisitionTimeYearno 
AcquisitionTimeYearMonthno 
AcquisitionTimeYearMonthDayno 
AlbumArtistUnicode/ 
AlbumArtistno 
AlbumArtistSortOrderno 
AlbumCoverURLUnicode/ 
AlbumIDno 
AlbumIDAlbumArtistno 
AlbumTitleUnicode/ 
AlbumTitleno 
AlbumTitleSortOrderno 
AlternateSourceURLno 
Anniversaryno 
Artistno 
AssistantsNameno 
AssistantsPhoneno 
Attachmentsno 
Attributesno 
AudioBitrateno 
AudioFormatno 
AudioSampleRateno 
AudioSampleSizeno 
Authorno 
Authorno 
AuthorSortOrderno 
AuthorURLUnicode/ 
AutoSummaryno 
AverageLevelno 
BccAddressesno 
BccNamesno 
Beats-per-minuteno 
BeatsPerMinuteno 
BillingInformationno 
Birthdayno 
BitDepthno 
Bitrateno 
Bitrateno 
BroadcastDateno 
BusinessAddressno 
BusinessCityno 
BusinessCountry-Regionno 
BusinessFaxno 
BusinessHomePageno 
BusinessPOBoxno 
BusinessPhoneno 
BusinessPostalCodeno 
BusinessStateOrProvinceno 
BusinessStreetno 
BuyNowno 
BuyTicketsno 
CDTrackEnabledno 
CallLettersno 
CallbackNumberno 
CameraMakerno 
CameraManufacturerno 
CameraModelno 
CameraModelno 
CarPhoneno 
CategoryUnicode/+ 
Categoryno 
CcAddressesno 
CcNamesno 
CellPhoneno 
ChannelNumberno 
Channelsno 
Channelsno 
ChapterNumno 
Childrenno 
Cityno 
ClientIDno 
ClosedCaptioningno 
Colorno 
Commentno 
Commentsno 
Companyno 
CompanyMainPhoneno 
Completeno 
ComposerUnicode/ 
Composersno 
Computerno 
ConductorUnicode/+ 
Conductorsno 
ContactNamesno 
ContentDistributorUnicode/ 
ContentDistributorDurationno 
ContentDistributorTypeno 
ContentGroupDescriptionno 
ContentTypeno 
Contributorsno 
ConversationIDno 
Copyrightno 
Copyrightno 
Countno 
Country-Regionno 
Creatorno 
CurrentBitrateno 
DLNAServerUDNno 
DLNASourceURIno 
DRMIndividualizedVersionno 
DRMKeyIDno 
DTCPIPHostno 
DTCPIPPortno 
DVDIDno 
DataRateno 
Dateno 
DateAccessedno 
DateAcquiredvt_filetime/ 
DateArchivedno 
DateCompletedno 
DateCreatedno 
DateImportedno 
DateLastSavedno 
DateModifiedno 
DatePictureTakenno 
DateReceivedno 
DateReleasedno 
DateSentno 
DateVisitedno 
Departmentno 
Descriptionno 
Descriptionno 
Descriptionno 
Dimensionsno 
DirectorUnicode/+ 
Directorsno 
DisplayArtistno 
Divisionno 
DocumentIDno 
DueDateno 
Durationno 
Durationno 
Durationno 
E-mail2no 
E-mail3no 
E-mailAddressno 
E-mailDisplayNameno 
E-mailListno 
EncodedByUnicode/ 
EncodedByno 
EncodingTimedate/ 
EndDateno 
EntryTypeno 
EpisodeNameno 
Eventno 
Eventno 
ExifVersionno 
ExposureBiasno 
ExposureProgramno 
ExposureTimeno 
F-stopno 
FileAsno 
FileCountno 
FileSizeno 
FileTypeno 
FileVersionno 
FirstNameno 
FlagColorno 
FlagStatusno 
FlashModeno 
FocalLengthno 
FocalLength35mmno 
Folderno 
FolderNameno 
FolderPathno 
FormatTagno 
FourCCno 
FrameHeightno 
FrameRateno 
FrameRateno 
FrameWidthno 
Free-busyStatusno 
Frequencyno 
FromAddressesno 
FromNamesno 
FullNameno 
Genderno 
Genreno 
Genreno 
GenreIDno 
GivenNameno 
HasAttachmentsno 
HasFlagno 
Hobbiesno 
HomeAddressno 
HomeCityno 
HomeCountry-Regionno 
HomeFaxno 
HomePOBoxno 
HomePhoneno 
HomePostalCodeno 
HomeStateOrProvinceno 
HomeStreetno 
HorizontalResolutionno 
IMAddressesno 
ISOSpeedno 
Importanceno 
Incompleteno 
InitialKeyUnicode/ 
InitialKeyno 
Initialsno 
IsAttachmentno 
IsCompletedno 
IsDeletedno 
IsNetworkFeedno 
IsOnlineno 
IsProtectedno 
IsRecurringno 
IsVBRno 
JobTitleno 
Keywordsno 
Kindsno 
Labelno 
Languageno 
Languageno 
LastNameno 
LastPrintedno 
LeadPerformerno 
LegalTrademarksno 
LensMakerno 
LensModelno 
LibraryIDno 
LibraryNameno 
LightSourceno 
LinkStatusno 
LinkTargetno 
Locationno 
Locationno 
Lyricsno 
MCDIno 
MailingAddressno 
MaxApertureno 
MediaClassPrimaryIDGUID/'01CD0F29-DA4E-4157-897B-6275D50C4F11' = Audio (not music) +
'D1607DBC-E323-4BE2-86A1-48A42A28441E' = Music +
'DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B' = Video +
'FCF24A76-9A57-4036-990D-E35DD8B244E1' = Other (not audio or video)
MediaClassSecondaryIDGUID/'00000000-0000-0000-0000-000000000000' = Unknown Content +
'00033368-5009-4AC3-A820-5D2D09A4E7C1' = Sound Clip from Game +
'0B710218-8C0C-475E-AF73-4C41C0C8F8CE' = Home Video from Pictures +
'1B824A67-3F80-4E3E-9CDE-F7361B0F5F1B' = Talk Show +
'1FE2E091-4E1E-40CE-B22D-348C732E0B10' = Video News +
'3A172A13-2BD9-4831-835B-114F6A95943F' = Spoken Word +
'44051B5B-B103-4B5C-92AB-93060A9463F0' = Corporate Video +
'6677DB9B-E5A0-4063-A1AD-ACEB52840CF1' = Audio News +
'A9B87FC9-BD47-4BF0-AC4F-655B89F7D868' = Feature Film +
'B76628F4-300D-443D-9CB5-01C285109DAF' = Home Movie +
'BA7F258A-62F7-47A9-B21F-4651C42A000E' = TV Show +
'D6DE1D88-C77C-4593-BFBC-9C61E8C373E3' = Web-based Video +
'E0236BEB-C281-4EDE-A36D-7AF76A3D45B5' = Audio Book +
'E3E689E2-BA8C-4330-96DF-A0EEEFFA6876' = Music Video +
'F24FF731-96FC-4D0F-A2F5-5A3483682B1A' = Song from Game
MediaContentTypesno 
MediaCreatedno 
MediaOriginalBroadcastDateTimeno 
MediaOriginalChannelno 
MediaStationNameno 
MediaTypeno 
MeteringModeno 
MiddleNameno 
Mileageno 
ModifiedByno 
MoodUnicode/ 
Moodno 
MoreInfono 
Nameno 
Nicknameno 
OfficeLocationno 
OfflineAvailabilityno 
OfflineStatusno 
OptionalAttendeeAddressesno 
OptionalAttendeesno 
OrganizerAddressno 
OrganizerNameno 
Orientationno 
OriginalAlbumTitleUnicode/ 
OriginalArtistUnicode/ 
OriginalLyricistUnicode/ 
OtherAddressno 
OtherCityno 
OtherCountry-Regionno 
OtherPOBoxno 
OtherPostalCodeno 
OtherStateOrProvinceno 
OtherStreetno 
Ownerno 
POBoxno 
Pagerno 
Pagesno 
ParentalRatingUnicode/ 
ParentalRatingno 
ParentalRatingReasonno 
PartOfSetno 
PartOfSetno 
PartOfSetno 
Participantsno 
Pathno 
PeakValueno 
PerceivedTypeno 
PeriodUnicode/ 
Periodno 
PersonalTitleno 
PixelAspectRatioXno 
PixelAspectRatioYno 
PlaylistIndexno 
PostalCodeno 
PrimaryE-mailno 
PrimaryPhoneno 
Priorityno 
ProducerUnicode/+ 
Producersno 
ProductNameno 
ProductVersionno 
Professionno 
ProgramDescriptionno 
ProgramModeno 
ProgramNameno 
Projectno 
PromotionURLUnicode/ 
Protectedno 
ProtectionTypeno 
Providerno 
ProviderUnicode/ 
ProviderLogoURLno 
ProviderRatingno 
ProviderStyleno 
ProviderURLno 
PublisherUnicode/ 
Publisherno 
RadioBandno 
RadioFormatno 
Ratingno 
RatingOrgno 
ReadStatusno 
RecordingTimeno 
RecordingTimeno 
RecordingTimeDayno 
RecordingTimeMonthno 
RecordingTimeYearno 
RecordingTimeYearMonthno 
RecordingTimeYearMonthDayno 
ReleaseDateno 
ReleaseDateDayno 
ReleaseDateMonthno 
ReleaseDateYearno 
ReleaseDateYearMonthno 
ReleaseDateYearMonthDayno 
ReminderTimeno 
RequestStateno 
RequiredAttendeeAddressesno 
RequiredAttendeesno 
Rerunno 
Resourcesno 
SAPno 
Saturationno 
SearchRankingno 
SenderAddressno 
SenderNameno 
Sensitivityno 
ShadowFilePathno 
Sharedno 
SharedUserRatingint64u/ 
SharedWithno 
Sizeno 
Slidesno 
Sourceno 
SourceURLno 
SpaceFreeno 
SpaceUsedno 
Spouseno 
StartDateno 
StateOrProvinceno 
StationCallSignno 
StationNameno 
Statusno 
Statusno 
Storeno 
Streetno 
Subjectno 
Subjectno 
SubjectDistanceno 
SubscriptionContentIDno 
SubtitleUnicode/ 
Subtitleno 
SubtitleDescriptionno 
Suffixno 
Summaryno 
Sync01no 
Sync02no 
Sync03no 
Sync04no 
Sync05no 
Sync06no 
Sync07no 
Sync08no 
Sync09no 
Sync10no 
Sync11no 
Sync12no 
Sync13no 
Sync14no 
Sync15no 
Sync16no 
SyncOnlyno 
SyncStateno 
TTY-TTDPhoneno 
TaskOwnerno 
Telexno 
Temporaryno 
Titleno 
Titleno 
TitleNumno 
TitleSortOrderno 
ToAddressesno 
ToDoTitleno 
ToNamesno 
TotalBitrateno 
TotalDurationno 
TotalEditingTimeno 
TotalFileSizeno 
TotalSizeno 
TrackNumberno 
TrackNumberno 
TrackingIDno 
Typeno 
Typeno 
URLno 
UniqueFileIdentifierno 
Untitled0no 
Untitled1no 
Untitled2no 
UserCustom1no 
UserCustom2no 
UserEffectiveRatingno 
UserLastPlayedTimeno 
UserPlayCountno 
UserPlaycountAfternoonno 
UserPlaycountEveningno 
UserPlaycountMorningno 
UserPlaycountNightno 
UserPlaycountWeekdayno 
UserPlaycountWeekendno 
UserRatingno 
UserServiceRatingno 
UserWebURLno 
VerticalResolutionno 
VideoBitrateno 
VideoCompressionno 
VideoFormatno 
VideoFrameRateno 
VideoHeightno 
VideoWidthno 
WMCollectionGroupIDno 
WMCollectionIDno 
WMContentIDno 
WMShadowFileSourceDRMTypeno 
WMShadowFileSourceFileTypeno 
Webpageno 
WhiteBalanceno 
WindowsFileNameno 
WordCountno 
WriterUnicode/ 
Writersno 
Yearno 
Yearno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 17, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Minolta.html b/ExifTool/html/TagNames/Minolta.html new file mode 100644 index 0000000..fa23d0f --- /dev/null +++ b/ExifTool/html/TagNames/Minolta.html @@ -0,0 +1,2598 @@ + + + + +Minolta Tags + + + +

Minolta Tags

+

+These tags are used by Minolta, Konica/Minolta as well as some Sony cameras. +Minolta doesn't make things easy for decoders because the meaning of some +tags and the location where some information is stored is different for +different camera models. (Take MinoltaQuality for example, which may be +located in 5 different places.) +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000MakerNoteVersionundef[4] 
0x0001MinoltaCameraSettingsOld---> Minolta CameraSettings Tags
0x0003MinoltaCameraSettings---> Minolta CameraSettings Tags
0x0004MinoltaCameraSettings7D---> Minolta CameraSettings7D Tags
0x0010CameraInfoA100---> Minolta CameraInfoA100 Tags
0x0018ISInfoA100 +
ImageStabilization
-
no
--> Minolta ISInfoA100 Tags +
(a block of binary data which exists in DiMAGE A2 (and A1/X1?) images only if +image stabilization is enabled)
0x0020WBInfoA100---> Minolta WBInfoA100 Tags +
(currently decoded only for the Sony A100)
0x0040CompressedImageSizeint32u 
0x0081PreviewImageundef 
0x0088PreviewImageStartint32u* 
0x0089PreviewImageLengthint32u* 
0x0100SceneModeint32u +
0 = Standard +
1 = Portrait +
2 = Text +
3 = Night Scene +
4 = Sunset +
5 = Sports +
6 = Landscape +
7 = Night Portrait +
8 = Macro +
9 = Super Macro +
16 = Auto +
17 = Night View/Portrait +
18 = Sweep Panorama +
19 = Handheld Night Shot +
20 = Anti Motion Blur +
21 = Cont. Priority AE +
22 = Auto+ +
23 = 3D Sweep Panorama +
24 = Superior Auto +
25 = High Sensitivity +
26 = Fireworks +
27 = Food +
28 = Pet +
33 = HDR +
65535 = n/a
+
0x0101ColorModeint32u + +
0 = Natural color +
1 = Black & White +
2 = Vivid color +
3 = Solarization +
4 = Adobe RGB +
5 = Sepia +
9 = Natural +
12 = Portrait
  13 = Natural sRGB +
14 = Natural+ sRGB +
15 = Landscape +
16 = Evening +
17 = Night Scene +
18 = Night Portrait +
132 = Embed Adobe RGB
+(Sony models) + +
0 = Standard +
1 = Vivid +
2 = Portrait +
3 = Landscape +
4 = Sunset +
5 = Night View/Portrait +
6 = B&W +
7 = Adobe RGB +
12 = Neutral +
13 = Clear +
14 = Deep +
15 = Light +
16 = Autumn Leaves
  17 = Sepia +
18 = FL +
19 = Vivid 2 +
20 = IN +
21 = SH +
100 = Neutral +
101 = Clear +
102 = Deep +
103 = Light +
104 = Night View +
105 = Autumn Leaves +
255 = Off +
4294967295 = n/a
+
0x0102MinoltaQualityint32u + +
0 = Raw +
1 = Super Fine +
2 = Fine
  3 = Standard +
4 = Economy +
5 = Extra fine
+
0x0103MinoltaQuality +
MinoltaImageSize
int32u
int32u
(quality for DiMAGE A2/7Hi) + +
0 = Raw +
1 = Super Fine +
2 = Fine
  3 = Standard +
4 = Economy +
5 = Extra fine
+(image size for other models except A200) + +
1 = 1600x1200 +
2 = 1280x960 +
3 = 640x480
  5 = 2560x1920 +
6 = 2272x1704 +
7 = 2048x1536
+
0x0104FlashExposureComprational64s 
0x0105Teleconverterint32u +
0x0 = None +
0x4 = Minolta/Sony AF 1.4x APO (D) (0x04) +
0x5 = Minolta/Sony AF 2x APO (D) (0x05) +
0x48 = Minolta/Sony AF 2x APO (D) +
0x50 = Minolta AF 2x APO II +
0x60 = Minolta AF 2x APO +
0x88 = Minolta/Sony AF 1.4x APO (D) +
0x90 = Minolta AF 1.4x APO II +
0xa0 = Minolta AF 1.4x APO
+
0x0107ImageStabilizationint32u1 = Off +
5 = On
0x0109RawAndJpgRecordingint32u0 = Off +
1 = On
0x010aZoneMatchingint32u0 = ISO Setting Used +
1 = High Key +
2 = Low Key
0x010bColorTemperatureint32u 
0x010cLensTypeint32u--> Minolta LensType Values
0x0111ColorCompensationFilterint32s(ranges from -2 for green to +2 for magenta)
0x0112WhiteBalanceFineTuneint32u 
0x0113ImageStabilizationint32u(valid for Sony A100 only) +
0 = Off +
1 = On
0x0114MinoltaCameraSettings5D +
CameraSettingsA100
-
-
--> Minolta CameraSettings5D Tags +
--> Minolta CameraSettingsA100 Tags
0x0115WhiteBalanceint32u +
0x0 = Auto +
0x1 = Color Temperature/Color Filter +
0x10 = Daylight +
0x20 = Cloudy +
0x30 = Shade +
0x40 = Tungsten +
0x50 = Flash +
0x60 = Fluorescent +
0x70 = Custom
+
0x0e00PrintIM---> PrintIM Tags
0x0f00MinoltaCameraSettings2no 
+ +

Minolta LensType Values

+

"New" or "II" appear in brackets if the original version of the lens has the +same LensType. Special logic is employed to identify the attached lens when +a Metabones Canon EF adapter is used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLensType
0= Minolta AF 28-85mm F3.5-4.5 New
1= Minolta AF 80-200mm F2.8 HS-APO G
2= Minolta AF 28-70mm F2.8 G
3= Minolta AF 28-80mm F4-5.6
4= Minolta AF 85mm F1.4G
5= Minolta AF 35-70mm F3.5-4.5 [II]
6= Minolta AF 24-85mm F3.5-4.5 [New]
7= Minolta AF 100-300mm F4.5-5.6 APO [New] or 100-400mm or Sigma Lens
7= Minolta AF 100-400mm F4.5-6.7 APO
7= Sigma AF 100-300mm F4 EX DG IF
8= Minolta AF 70-210mm F4.5-5.6 [II]
9= Minolta AF 50mm F3.5 Macro
10= Minolta AF 28-105mm F3.5-4.5 [New]
11= Minolta AF 300mm F4 HS-APO G
12= Minolta AF 100mm F2.8 Soft Focus
13= Minolta AF 75-300mm F4.5-5.6 (New or II)
14= Minolta AF 100-400mm F4.5-6.7 APO
15= Minolta AF 400mm F4.5 HS-APO G
16= Minolta AF 17-35mm F3.5 G
17= Minolta AF 20-35mm F3.5-4.5
18= Minolta AF 28-80mm F3.5-5.6 II
19= Minolta AF 35mm F1.4 G
20= Minolta/Sony 135mm F2.8 [T4.5] STF
22= Minolta AF 35-80mm F4-5.6 II
23= Minolta AF 200mm F4 Macro APO G
24= Minolta/Sony AF 24-105mm F3.5-4.5 (D) or Sigma or Tamron Lens
24= Sigma 18-50mm F2.8
24= Sigma 17-70mm F2.8-4.5 DC Macro
24= Sigma 20-40mm F2.8 EX DG Aspherical IF
24= Sigma 18-200mm F3.5-6.3 DC
24= Sigma DC 18-125mm F4-5,6 D
24= Tamron SP AF 28-75mm F2.8 XR Di LD Aspherical [IF] Macro
24= Sigma 15-30mm F3.5-4.5 EX DG Aspherical
25= Minolta AF 100-300mm F4.5-5.6 APO (D) or Sigma Lens
25= Sigma 100-300mm F4 EX (APO (D) or D IF)
25= Sigma 70mm F2.8 EX DG Macro
25= Sigma 20mm F1.8 EX DG Aspherical RF
25= Sigma 30mm F1.4 EX DC
25= Sigma 24mm F1.8 EX DG ASP Macro
27= Minolta AF 85mm F1.4 G (D)
28= Minolta/Sony AF 100mm F2.8 Macro (D) or Tamron Lens
28= Tamron SP AF 90mm F2.8 Di Macro
28= Tamron SP AF 180mm F3.5 Di LD [IF] Macro
29= Minolta/Sony AF 75-300mm F4.5-5.6 (D)
30= Minolta AF 28-80mm F3.5-5.6 (D) or Sigma Lens
30= Sigma AF 10-20mm F4-5.6 EX DC
30= Sigma AF 12-24mm F4.5-5.6 EX DG
30= Sigma 28-70mm EX DG F2.8
30= Sigma 55-200mm F4-5.6 DC
31= Minolta/Sony AF 50mm F2.8 Macro (D) or F3.5
31= Minolta/Sony AF 50mm F3.5 Macro
32= Minolta/Sony AF 300mm F2.8 G or 1.5x Teleconverter
33= Minolta/Sony AF 70-200mm F2.8 G
35= Minolta AF 85mm F1.4 G (D) Limited
36= Minolta AF 28-100mm F3.5-5.6 (D)
38= Minolta AF 17-35mm F2.8-4 (D)
39= Minolta AF 28-75mm F2.8 (D)
40= Minolta/Sony AF DT 18-70mm F3.5-5.6 (D)
41= Minolta/Sony AF DT 11-18mm F4.5-5.6 (D) or Tamron Lens
41= Tamron SP AF 11-18mm F4.5-5.6 Di II LD Aspherical IF
42= Minolta/Sony AF DT 18-200mm F3.5-6.3 (D)
43= Sony 35mm F1.4 G (SAL35F14G)
44= Sony 50mm F1.4 (SAL50F14)
45= Carl Zeiss Planar T* 85mm F1.4 ZA (SAL85F14Z)
46= Carl Zeiss Vario-Sonnar T* DT 16-80mm F3.5-4.5 ZA (SAL1680Z)
47= Carl Zeiss Sonnar T* 135mm F1.8 ZA (SAL135F18Z)
48= Carl Zeiss Vario-Sonnar T* 24-70mm F2.8 ZA SSM (SAL2470Z) or Other Lens
48= Carl Zeiss Vario-Sonnar T* 24-70mm F2.8 ZA SSM II (SAL2470Z2)
48= Tamron SP 24-70mm F2.8 Di USD
49= Sony DT 55-200mm F4-5.6 (SAL55200)
50= Sony DT 18-250mm F3.5-6.3 (SAL18250)
51= Sony DT 16-105mm F3.5-5.6 (SAL16105)
52= Sony 70-300mm F4.5-5.6 G SSM (SAL70300G) or G SSM II or Tamron Lens
52= Sony 70-300mm F4.5-5.6 G SSM II (SAL70300G2)
52= Tamron SP 70-300mm F4-5.6 Di USD
53= Sony 70-400mm F4-5.6 G SSM (SAL70400G)
54= Carl Zeiss Vario-Sonnar T* 16-35mm F2.8 ZA SSM (SAL1635Z) or ZA SSM II
54= Carl Zeiss Vario-Sonnar T* 16-35mm F2.8 ZA SSM II (SAL1635Z2)
55= Sony DT 18-55mm F3.5-5.6 SAM (SAL1855) or SAM II
55= Sony DT 18-55mm F3.5-5.6 SAM II (SAL18552)
56= Sony DT 55-200mm F4-5.6 SAM (SAL55200-2)
57= Sony DT 50mm F1.8 SAM (SAL50F18) or Tamron Lens or Commlite CM-EF-NEX adapter
57= Tamron SP AF 60mm F2 Di II LD [IF] Macro 1:1
57= Tamron 18-270mm F3.5-6.3 Di II PZD
58= Sony DT 30mm F2.8 Macro SAM (SAL30M28)
59= Sony 28-75mm F2.8 SAM (SAL2875)
60= Carl Zeiss Distagon T* 24mm F2 ZA SSM (SAL24F20Z)
61= Sony 85mm F2.8 SAM (SAL85F28)
62= Sony DT 35mm F1.8 SAM (SAL35F18)
63= Sony DT 16-50mm F2.8 SSM (SAL1650)
64= Sony 500mm F4 G SSM (SAL500F40G)
65= Sony DT 18-135mm F3.5-5.6 SAM (SAL18135)
66= Sony 300mm F2.8 G SSM II (SAL300F28G2)
67= Sony 70-200mm F2.8 G SSM II (SAL70200G2)
68= Sony DT 55-300mm F4.5-5.6 SAM (SAL55300)
69= Sony 70-400mm F4-5.6 G SSM II (SAL70400G2)
70= Carl Zeiss Planar T* 50mm F1.4 ZA SSM (SAL50F14Z)
128= Tamron or Sigma Lens (128)
128= Tamron AF 18-200mm F3.5-6.3 XR Di II LD Aspherical [IF] Macro
128= Tamron AF 28-300mm F3.5-6.3 XR Di LD Aspherical [IF] Macro
128= Tamron AF 28-200mm F3.8-5.6 XR Di Aspherical [IF] Macro
128= Tamron SP AF 17-35mm F2.8-4 Di LD Aspherical IF
128= Sigma AF 50-150mm F2.8 EX DC APO HSM II
128= Sigma 10-20mm F3.5 EX DC HSM
128= Sigma 70-200mm F2.8 II EX DG APO MACRO HSM
128= Sigma 10mm F2.8 EX DC HSM Fisheye
128= Sigma 50mm F1.4 EX DG HSM
128= Sigma 85mm F1.4 EX DG HSM
128= Sigma 24-70mm F2.8 IF EX DG HSM
128= Sigma 18-250mm F3.5-6.3 DC OS HSM
128= Sigma 17-50mm F2.8 EX DC HSM
128= Sigma 17-70mm F2.8-4 DC Macro HSM
128= Sigma 150mm F2.8 EX DG OS HSM APO Macro
128= Sigma 150-500mm F5-6.3 APO DG OS HSM
128= Tamron AF 28-105mm F4-5.6 [IF]
128= Sigma 35mm F1.4 DG HSM
128= Sigma 18-35mm F1.8 DC HSM
128= Sigma 50-500mm F4.5-6.3 APO DG OS HSM
128= Sigma 24-105mm F4 DG HSM | A
128= Sigma 30mm F1.4
128= Sigma 35mm F1.4 DG HSM | A
128= Sigma 105mm F2.8 EX DG OS HSM Macro
128= Sigma 180mm F2.8 EX DG OS HSM APO Macro
128= Sigma 18-300mm F3.5-6.3 DC Macro HSM | C
128= Sigma 18-50mm F2.8-4.5 DC HSM
129= Tamron Lens (129)
129= Tamron 200-400mm F5.6 LD
129= Tamron 70-300mm F4-5.6 LD
131= Tamron 20-40mm F2.7-3.5 SP Aspherical IF
135= Vivitar 28-210mm F3.5-5.6
136= Tokina EMZ M100 AF 100mm F3.5
137= Cosina 70-210mm F2.8-4 AF
138= Soligor 19-35mm F3.5-4.5
139= Tokina AF 28-300mm F4-6.3
142= Cosina AF 70-300mm F4.5-5.6 MC
146= Voigtlander Macro APO-Lanthar 125mm F2.5 SL
194= Tamron SP AF 17-50mm F2.8 XR Di II LD Aspherical [IF]
202= Tamron SP AF 70-200mm F2.8 Di LD [IF] Macro
203= Tamron SP 70-200mm F2.8 Di USD
204= Tamron SP 24-70mm F2.8 Di USD
212= Tamron 28-300mm F3.5-6.3 Di PZD
213= Tamron 16-300mm F3.5-6.3 Di II PZD Macro
214= Tamron SP 150-600mm F5-6.3 Di USD
215= Tamron SP 15-30mm F2.8 Di USD
216= Tamron SP 45mm F1.8 Di USD
217= Tamron SP 35mm F1.8 Di USD
218= Tamron SP 90mm F2.8 Di Macro 1:1 USD (F017)
220= Tamron SP 150-600mm F5-6.3 Di USD G2
224= Tamron SP 90mm F2.8 Di Macro 1:1 USD (F004)
255= Tamron Lens (255)
255= Tamron SP AF 17-50mm F2.8 XR Di II LD Aspherical
255= Tamron AF 18-250mm F3.5-6.3 XR Di II LD
255= Tamron AF 55-200mm F4-5.6 Di II LD Macro
255= Tamron AF 70-300mm F4-5.6 Di LD Macro 1:2
255= Tamron SP AF 200-500mm F5.0-6.3 Di LD IF
255= Tamron SP AF 10-24mm F3.5-4.5 Di II LD Aspherical IF
255= Tamron SP AF 70-200mm F2.8 Di LD IF Macro
255= Tamron SP AF 28-75mm F2.8 XR Di LD Aspherical IF
255= Tamron AF 90-300mm F4.5-5.6 Telemacro
18688= Sigma MC-11 SA-E Mount Converter with not-supported Sigma lens
25501= Minolta AF 50mm F1.7
25511= Minolta AF 35-70mm F4 or Other Lens
25511= Sigma UC AF 28-70mm F3.5-4.5
25511= Sigma AF 28-70mm F2.8
25511= Sigma M-AF 70-200mm F2.8 EX Aspherical
25511= Quantaray M-AF 35-80mm F4-5.6
25511= Tokina 28-70mm F2.8-4.5 AF
25521= Minolta AF 28-85mm F3.5-4.5 or Other Lens
25521= Tokina 19-35mm F3.5-4.5
25521= Tokina 28-70mm F2.8 AT-X
25521= Tokina 80-400mm F4.5-5.6 AT-X AF II 840
25521= Tokina AF PRO 28-80mm F2.8 AT-X 280
25521= Tokina AT-X PRO [II] AF 28-70mm F2.6-2.8 270
25521= Tamron AF 19-35mm F3.5-4.5
25521= Angenieux AF 28-70mm F2.6
25521= Tokina AT-X 17 AF 17mm F3.5
25521= Tokina 20-35mm F3.5-4.5 II AF
25531= Minolta AF 28-135mm F4-4.5 or Other Lens
25531= Sigma ZOOM-alpha 35-135mm F3.5-4.5
25531= Sigma 28-105mm F2.8-4 Aspherical
25531= Sigma 28-105mm F4-5.6 UC
25531= Tokina AT-X 242 AF 24-200mm F3.5-5.6
25541= Minolta AF 35-105mm F3.5-4.5
25551= Minolta AF 70-210mm F4 Macro or Sigma Lens
25551= Sigma 70-210mm F4-5.6 APO
25551= Sigma M-AF 70-200mm F2.8 EX APO
25551= Sigma 75-200mm F2.8-3.5
25561= Minolta AF 135mm F2.8
25571= Minolta/Sony AF 28mm F2.8
25581= Minolta AF 24-50mm F4
25601= Minolta AF 100-200mm F4.5
25611= Minolta AF 75-300mm F4.5-5.6 or Sigma Lens
25611= Sigma 70-300mm F4-5.6 DL Macro
25611= Sigma 300mm F4 APO Macro
25611= Sigma AF 500mm F4.5 APO
25611= Sigma AF 170-500mm F5-6.3 APO Aspherical
25611= Tokina AT-X AF 300mm F4
25611= Tokina AT-X AF 400mm F5.6 SD
25611= Tokina AF 730 II 75-300mm F4.5-5.6
25611= Sigma 800mm F5.6 APO
25611= Sigma AF 400mm F5.6 APO Macro
25611= Sigma 1000mm F8 APO
25621= Minolta AF 50mm F1.4 [New]
25631= Minolta AF 300mm F2.8 APO or Sigma Lens
25631= Sigma AF 50-500mm F4-6.3 EX DG APO
25631= Sigma AF 170-500mm F5-6.3 APO Aspherical
25631= Sigma AF 500mm F4.5 EX DG APO
25631= Sigma 400mm F5.6 APO
25641= Minolta AF 50mm F2.8 Macro or Sigma Lens
25641= Sigma 50mm F2.8 EX Macro
25651= Minolta AF 600mm F4 APO
25661= Minolta AF 24mm F2.8 or Sigma Lens
25661= Sigma 17-35mm F2.8-4 EX Aspherical
25721= Minolta/Sony AF 500mm F8 Reflex
25781= Minolta/Sony AF 16mm F2.8 Fisheye or Sigma Lens
25781= Sigma 8mm F4 EX [DG] Fisheye
25781= Sigma 14mm F3.5
25781= Sigma 15mm F2.8 Fisheye
25791= Minolta/Sony AF 20mm F2.8 or Tokina Lens
25791= Tokina AT-X Pro DX 11-16mm F2.8
25811= Minolta AF 100mm F2.8 Macro [New] or Sigma or Tamron Lens
25811= Sigma AF 90mm F2.8 Macro
25811= Sigma AF 105mm F2.8 EX [DG] Macro
25811= Sigma 180mm F5.6 Macro
25811= Sigma 180mm F3.5 EX DG Macro
25811= Tamron 90mm F2.8 Macro
25851= Beroflex 35-135mm F3.5-4.5
25858= Minolta AF 35-105mm F3.5-4.5 New or Tamron Lens
25858= Tamron 24-135mm F3.5-5.6
25881= Minolta AF 70-210mm F3.5-4.5
25891= Minolta AF 80-200mm F2.8 APO or Tokina Lens
25891= Tokina 80-200mm F2.8
25901= Minolta AF 200mm F2.8 G APO + Minolta AF 1.4x APO or Other Lens + 1.4x
25901= Minolta AF 600mm F4 HS-APO G + Minolta AF 1.4x APO
25911= Minolta AF 35mm F1.4
25921= Minolta AF 85mm F1.4 G (D)
25931= Minolta AF 200mm F2.8 APO
25941= Minolta AF 3x-1x F1.7-2.8 Macro
25961= Minolta AF 28mm F2
25971= Minolta AF 35mm F2 [New]
25981= Minolta AF 100mm F2
26011= Minolta AF 200mm F2.8 G APO + Minolta AF 2x APO or Other Lens + 2x
26011= Minolta AF 600mm F4 HS-APO G + Minolta AF 2x APO
26041= Minolta AF 80-200mm F4.5-5.6
26051= Minolta AF 35-80mm F4-5.6
26061= Minolta AF 100-300mm F4.5-5.6
26071= Minolta AF 35-80mm F4-5.6
26081= Minolta AF 300mm F2.8 HS-APO G
26091= Minolta AF 600mm F4 HS-APO G
26121= Minolta AF 200mm F2.8 HS-APO G
26131= Minolta AF 50mm F1.7 New
26151= Minolta AF 28-105mm F3.5-4.5 xi
26161= Minolta AF 35-200mm F4.5-5.6 xi
26181= Minolta AF 28-80mm F4-5.6 xi
26191= Minolta AF 80-200mm F4.5-5.6 xi
26201= Minolta AF 28-70mm F2.8 G
26211= Minolta AF 100-300mm F4.5-5.6 xi
26241= Minolta AF 35-80mm F4-5.6 Power Zoom
26281= Minolta AF 80-200mm F2.8 HS-APO G
26291= Minolta AF 85mm F1.4 New
26311= Minolta AF 100-300mm F4.5-5.6 APO
26321= Minolta AF 24-50mm F4 New
26381= Minolta AF 50mm F2.8 Macro New
26391= Minolta AF 100mm F2.8 Macro
26411= Minolta/Sony AF 20mm F2.8 New
26421= Minolta AF 24mm F2.8 New
26441= Minolta AF 100-400mm F4.5-6.7 APO
26621= Minolta AF 50mm F1.4 New
26671= Minolta AF 35mm F2 New
26681= Minolta AF 28mm F2 New
26721= Minolta AF 24-105mm F3.5-4.5 (D)
30464= Metabones Canon EF Speed Booster
45671= Tokina 70-210mm F4-5.6
45681= Tokina AF 35-200mm F4-5.6 Zoom SD
45701= Tamron AF 35-135mm F3.5-4.5
45711= Vivitar 70-210mm F4.5-5.6
45741= 2x Teleconverter or Tamron or Tokina Lens
45741= Tamron SP AF 90mm F2.5
45741= Tokina RF 500mm F8.0 x2
45741= Tokina 300mm F2.8 x2
45751= 1.4x Teleconverter
45851= Tamron SP AF 300mm F2.8 LD IF
45861= Tamron SP AF 35-105mm F2.8 LD Aspherical IF
45871= Tamron AF 70-210mm F2.8 SP LD
48128= Metabones Canon EF Speed Booster Ultra
61184= Canon EF Adapter
65280= Sigma 16mm F2.8 Filtermatic Fisheye
65535= E-Mount, T-Mount, Other Lens or no lens
65535= Arax MC 35mm F2.8 Tilt+Shift
65535= Arax MC 80mm F2.8 Tilt+Shift
65535= Zenitar MF 16mm F2.8 Fisheye M42
65535= Samyang 500mm Mirror F8.0
65535= Pentacon Auto 135mm F2.8
65535= Pentacon Auto 29mm F2.8
65535= Helios 44-2 58mm F2.0
+ +

Minolta CameraSettings Tags

+

There is some variability in CameraSettings information between different +models (and sometimes even between different firmware versions), so this +information may not be as reliable as it should be. Because of this, tags +in the following tables are set to lower priority to prevent them from +superseding the values of same-named tags in other locations when duplicate +tags are disabled.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1ExposureModeint32u0 = Program +
1 = Aperture Priority +
2 = Shutter Priority +
3 = Manual
2FlashModeint32u0 = Fill flash +
1 = Red-eye reduction +
2 = Rear flash sync +
3 = Wireless +
4 = Off?
3WhiteBalanceint32u~ 
4MinoltaImageSizeint32u + +
0 = Full +
1 = 1600x1200 +
2 = 1280x960 +
3 = 640x480
  6 = 2080x1560 +
7 = 2560x1920 +
8 = 3264x2176
+
5MinoltaQualityint32u + +
0 = Raw +
1 = Super Fine +
2 = Fine
  3 = Standard +
4 = Economy +
5 = Extra Fine
+
6DriveModeint32u + +
0 = Single +
1 = Continuous +
2 = Self-timer +
4 = Bracketing
  5 = Interval +
6 = UHS continuous +
7 = HS continuous
+
7MeteringModeint32u0 = Multi-segment +
1 = Center-weighted average +
2 = Spot
8ISOint32u 
9ExposureTimeint32u 
10FNumberint32u 
11MacroModeint32u0 = Off +
1 = On
12DigitalZoomint32u0 = Off +
1 = Electronic magnification +
2 = 2x
13ExposureCompensationint32u 
14BracketStepint32u0 = 1/3 EV +
1 = 2/3 EV +
2 = 1 EV
16IntervalLengthint32u 
17IntervalNumberint32u 
18FocalLengthint32u 
19FocusDistanceint32u 
20FlashFiredint32u0 = No +
1 = Yes
21MinoltaDateint32u 
22MinoltaTimeint32u 
23MaxApertureint32u 
26FileNumberMemoryint32u0 = Off +
1 = On
27LastFileNumberint32u 
28ColorBalanceRedint32u 
29ColorBalanceGreenint32u 
30ColorBalanceBlueint32u 
31Saturationint32u0 = Normal
32Contrastint32u0 = Normal
33Sharpnessint32u0 = Hard +
1 = Normal +
2 = Soft
34SubjectProgramint32u + +
0 = None +
1 = Portrait +
2 = Text
  3 = Night portrait +
4 = Sunset +
5 = Sports action
+
35FlashExposureCompint32u 
36ISOSettingint32u + +
0 = 100 +
1 = 200 +
2 = 400
  3 = 800 +
4 = Auto +
5 = 64
+
37MinoltaModelIDint32u +
0 = DiMAGE 7, X1, X21 or X31 +
1 = DiMAGE 5 +
2 = DiMAGE S304 +
3 = DiMAGE S404 +
4 = DiMAGE 7i +
5 = DiMAGE 7Hi +
6 = DiMAGE A1 +
7 = DiMAGE A2 or S414
+
38IntervalModeint32u0 = Still Image +
1 = Time-lapse Movie
39FolderNameint32u0 = Standard Form +
1 = Data Form
40ColorModeint32u0 = Natural color +
1 = Black & White +
2 = Vivid color +
3 = Solarization +
4 = Adobe RGB
41ColorFilterint32u 
42BWFilterint32u 
43InternalFlashint32u0 = No +
1 = Fired
44Brightnessint32u 
45SpotFocusPointXint32u 
46SpotFocusPointYint32u 
47WideFocusZoneint32u0 = No zone +
1 = Center zone (horizontal orientation) +
2 = Center zone (vertical orientation) +
3 = Left zone +
4 = Right zone
48FocusModeint32u0 = AF +
1 = MF
49FocusAreaint32u0 = Wide Focus (normal) +
1 = Spot Focus
50DECPositionint32u0 = Exposure +
1 = Contrast +
2 = Saturation +
3 = Filter
51ColorProfileint32u(DiMAGE 7Hi only) +
0 = Not Embedded +
1 = Embedded
52DataImprintint32u(DiMAGE 7Hi only) +
0 = None +
1 = YYYY/MM/DD +
2 = MM/DD/HH:MM +
3 = Text +
4 = Text + ID#
63FlashMeteringint32u0 = ADI (Advanced Distance Integration) +
1 = Pre-flash TTL +
2 = Manual flash control
+ +

Minolta CameraSettings7D Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ExposureModeint16u + +
0 = Program +
1 = Aperture Priority +
2 = Shutter Priority +
3 = Manual
  4 = Auto +
5 = Program-shift A +
6 = Program-shift S
+
2MinoltaImageSizeint16u0 = Large +
1 = Medium +
2 = Small
3MinoltaQualityint16u0 = RAW +
16 = Fine +
32 = Normal +
34 = RAW+JPEG +
48 = Economy
4WhiteBalanceint16u + +
0 = Auto +
1 = Daylight +
2 = Shade +
3 = Cloudy
  4 = Tungsten +
5 = Fluorescent +
256 = Kelvin +
512 = Manual
+
14FocusModeint16u0 = AF-S +
1 = AF-C +
3 = Manual +
4 = AF-A
16AFPointsint16u + +
0x0 = (none) +
Bit 0 = Center +
Bit 1 = Top +
Bit 2 = Top-right +
Bit 3 = Right
  Bit 4 = Bottom-right +
Bit 5 = Bottom +
Bit 6 = Bottom-left +
Bit 7 = Left +
Bit 8 = Top-left
+
21Flashint16u0 = Off +
1 = On
22FlashModeint16u0 = Normal +
1 = Red-eye reduction +
2 = Rear flash sync
28ISOSettingint16u + +
0 = Auto +
1 = 100 +
3 = 200 +
4 = 400
  5 = 800 +
6 = 1600 +
7 = 3200
+
30ExposureCompensationint16s 
37ColorSpaceint16u0 = Natural sRGB +
1 = Natural+ sRGB +
4 = Adobe RGB
38Sharpnessint16u 
39Contrastint16u 
40Saturationint16u 
45FreeMemoryCardImagesint16u 
63ColorTemperatureint16s 
64HueAdjustmentint16u 
70Rotationint16u72 = Horizontal (normal) +
76 = Rotate 90 CW +
82 = Rotate 270 CW
71FNumberint16u 
72ExposureTimeint16u 
74FreeMemoryCardImagesint16u 
94ImageNumberint16u(this information may appear at index 98 (0x62), depending on firmware +version)
96NoiseReductionint16u0 = Off +
1 = On
98ImageNumber2int16u 
113ImageStabilizationint16u0 = Off +
1 = On
117ZoneMatchingOnint16u0 = Off +
1 = On
+ +

Minolta CameraInfoA100 Tags

+

Camera information for the Sony DSLR-A100.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1AFSensorActiveint8u + +
0 = Top-right +
1 = Bottom-right +
2 = Bottom +
3 = Middle Horizontal
  4 = Center Vertical +
5 = Top +
6 = Top-left +
7 = Bottom-left
+
2AFStatusActiveSensorint16s(the focus status at shutter release. May not reflect the status after +focusing if the image is focused then recomposed) + +
-32768 = Out of Focus  0 = In Focus
+
4AFStatusTop-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
6AFStatusBottom-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
8AFStatusBottomint16s + +
-32768 = Out of Focus  0 = In Focus
+
10AFStatusMiddleHorizontalint16s(any of the three horizontal sensors at the middle of the focus frame: Left, +Center or Right) + +
-32768 = Out of Focus  0 = In Focus
+
12AFStatusCenterVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
14AFStatusTopint16s + +
-32768 = Out of Focus  0 = In Focus
+
16AFStatusTop-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
18AFStatusBottom-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
20FocusLockedint8u0 = Manual Focus +
4 = No +
16 = Continuous Focus +
64 = Yes
21AFPointint8u + +
0 = Auto +
1 = Center +
2 = Top +
3 = Top-right +
4 = Right
  5 = Bottom-right +
6 = Bottom +
7 = Bottom-left +
8 = Left +
9 = Top-left
+
22AFModeint8u0 = DMF +
1 = AF-S +
2 = AF-C +
3 = AF-A
45AFStatusLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
47AFStatusCenterHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
49AFStatusRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
51AFAreaModeint8u0 = Wide +
1 = Local +
2 = Spot
+ +

Minolta ISInfoA100 Tags

+

Image stabilization information for the Sony DSLR-A100.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0ImageStabilizationint16u0x0 = Off +
0x2784 = On
+ +

Minolta WBInfoA100 Tags

+

White balance information for the Sony DSLR-A100.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
14DriveModeint8u +
0 = Self-timer 10 sec +
1 = Continuous +
2 = Single-frame Exposure Bracketing +
3 = Continuous Exposure Bracketing +
4 = Self-Timer 2 sec +
5 = Single Frame +
8 = White Balance Bracketing Low +
9 = White Balance Bracketing High
+
16Rotationint8u0 = Horizontal (normal) +
1 = Rotate 270 CW +
2 = Rotate 90 CW
20ImageStabilizationSettingint8u0 = Off +
1 = On
21DynamicRangeOptimizerModeint8u0 = Off +
1 = Standard +
2 = Advanced
42ExposureCompensationModeint8u0 = Ambient and Flash +
1 = Ambient Only
43WBBracketShotNumberint8u 
44WhiteBalanceBracketingint8u0 = Off +
1 = Low +
2 = High
45ExposureBracketShotNumberint8u 
49FlashFunctionint16u +
0x0 = No flash +
0x300 = Built-in flash +
0x1205 = Manual +
0x120e = Strobe +
0x128e = Fill flash, Pre-flash TTL +
0x12ae = Bounce flash +
0x140e = Rear sync, ADI +
0x148e = Fill flash, ADI +
0x1580 = Wireless +
0x178e = HSS
+
52ExposureModeint16u + +
0x0 = Program +
0x1 = Aperture Priority +
0x2 = Shutter Priority +
0x3 = Manual +
0x4 = Auto +
0x5 = Program Shift A +
0x6 = Program Shift S
  0x1013 = Portrait +
0x1023 = Sports +
0x1033 = Sunset +
0x1043 = Night View/Portrait +
0x1053 = Landscape +
0x1083 = Macro
+
54ColorModeint16u + +
0 = Standard +
1 = Vivid +
2 = Portrait +
3 = Landscape
  4 = Sunset +
5 = Night View +
7 = B&W +
8 = Adobe RGB
+
56AverageLVint16u(arithmetic mean of the readings from the 40 honeycomb segments)
60FrameNumberint8u 
150WB_RGBLevelsint16u[3] 
174WB_GBRGLevelsint16u[4] 
192WB_RedLevelsTungstenint16u[7](7 values for adjustments of -3 through +3)
206WB_BlueLevelsTungstenint16u[7] 
220WB_RedLevelsDaylightint16u[7] 
234WB_BlueLevelsDaylightint16u[7] 
248WB_RedLevelsCloudyint16u[7] 
262WB_BlueLevelsCloudyint16u[7] 
276WB_RedLevelsFlashint16u[7] 
290WB_BlueLevelsFlashint16u[7] 
332WB_RedLevelsFluorescentint16u[7](white balance red presets for fluorescent -2 through +4: -2=Fluorescent, +-1=WhiteFluorescent, 0=CoolWhiteFluorescent, +1=DayWhiteFluorescent and ++3=DaylightFluorescent)
346WB_BlueLevelsFluorescentint16u[7] 
360WB_RedLevelsShadeint16u[7] 
374WB_BlueLevelsShadeint16u[7] 
392WB_RedLevel6500Kint16u 
394WB_BlueLevel6500Kint16u 
396WB_RedLevelCustomint16u 
398WB_BlueLevelCustomint16u 
408WB_RedLevel3500Kint16u 
410WB_BlueLevel3500Kint16u 
446WB_RedLevelsKelvinint16u[75](values for 2500-9900 K, in increments of 100 K)
596WB_BlueLevelsKelvinint16u[75] 
772WB_RBLevelsFlashint16u[2] 
776WB_RBLevelsCoolWhiteFint16u[2] 
1000WB_RBLevelsTungstenint16u[2] 
1004WB_RBLevelsDaylightint16u[2] 
1008WB_RBLevelsCloudyint16u[2] 
1012WB_RBLevelsFlashint16u[2] 
1020WB_RedLevelsFluorescentint16u[7] 
1034WB_BlueLevelsFluorescentint16u[7] 
1048WB_RBLevelsShadeint16u[2] 
1056WB_RBLevels6500Kint16u[2] 
1060WB_RBLevelsCustomint16u[2] 
1072WB_RBLevels3500Kint16u[2] 
1320WB_RBLevelsDaylightint16u[2] 
1350WB_RGBLevelsint16u[3] 
1576AEMeteringSegmentsint8u[40](metering values from the 40 honeycomb segments, converted to LV. The first +value is for the outer cell, then the values are given row by row, from top +to bottom, with each row scanned left-to-right. The 21st value is the +middle cell, which gives the spot metering)
1680MeasuredLVint8u(measured light value based on MeteringMode)
1681BrightnessValueint8u 
4172TiffMeteringImageno(13-bit RBGG (?) 40x30 pixels, presumably metering info, converted to a 16-bit +TIFF image;)
18872ExposureTimeint8u 
18874ISOint8u 
18875FocusDistanceint8u 
18877LensTypeint16uRev--> Minolta LensType Values
18880ExposureCompensationint8s 
18881FlashExposureCompint8s 
18882ImageStabilizationint8u0 = Off +
1 = On
18883BrightnessValueint8u 
18885MaxApertureint8u 
18887FNumberint8u 
18908InternalSerialNumberstring[12] 
+ +

Minolta CameraSettings5D Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
10ExposureModeint16u +
0 = Program +
1 = Aperture Priority +
2 = Shutter Priority +
3 = Manual +
4 = Auto? +
4131 = Connected Copying?
+
12MinoltaImageSizeint16u0 = Large +
1 = Medium +
2 = Small
13MinoltaQualityint16u0 = RAW +
16 = Fine +
32 = Normal +
34 = RAW+JPEG +
48 = Economy
14WhiteBalanceint16u + +
0 = Auto +
1 = Daylight +
2 = Cloudy +
3 = Shade +
4 = Tungsten
  5 = Fluorescent +
6 = Flash +
256 = Kelvin +
512 = Manual
+
31Flashint16u0 = Did not fire +
1 = Fired
32FlashModeint16u0 = Normal +
1 = Red-eye reduction +
2 = Rear flash sync
37MeteringModeint16u0 = Multi-segment +
1 = Center-weighted average +
2 = Spot
38ISOSettingint16u +
0 = Auto +
1 = 100 +
3 = 200 +
4 = 400 +
5 = 800 +
6 = 1600 +
7 = 3200 +
8 = 200 (Zone Matching High) +
10 = 80 (Zone Matching Low)
+
47ColorSpaceint16u0 = Natural sRGB +
1 = Natural+ sRGB +
2 = Monochrome +
4 = Adobe RGB (ICC) +
5 = Adobe RGB
48Sharpnessint16u 
49Contrastint16u 
50Saturationint16u 
53ExposureTimeint16u 
54FNumberint16u 
55FreeMemoryCardImagesint16u 
73ColorTemperatureint16s 
74HueAdjustmentint16u 
80Rotationint16u72 = Horizontal (normal) +
76 = Rotate 90 CW +
82 = Rotate 270 CW
83ExposureCompensationint16u 
84FreeMemoryCardImagesint16u 
101Rotationint16u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
110ColorTemperatureint16s 
113PictureFinishint16u + +
0 = Natural +
1 = Natural+ +
2 = Portrait +
3 = Wind Scene +
4 = Evening Scene
  5 = Night Scene +
6 = Night Portrait +
7 = Monochrome +
8 = Adobe RGB +
9 = Adobe RGB (ICC)
+
174ImageNumberint16u 
176NoiseReductionint16u0 = Off +
1 = On
189ImageStabilizationint16u0 = Off +
1 = On
+ +

Minolta CameraSettingsA100 Tags

+

Camera settings information for the Sony DSLR-A100.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ExposureModeint16u +
0x0 = Program +
0x1 = Aperture Priority +
0x2 = Shutter Priority +
0x3 = Manual +
0x4 = Auto +
0x5 = Program Shift A +
0x6 = Program Shift S +
0x1013 = Portrait +
0x1023 = Sports +
0x1033 = Sunset +
0x1043 = Night View/Portrait +
0x1053 = Landscape +
0x1083 = Macro
+
1ExposureCompensationSettingint16u 
5HighSpeedSyncint16u0 = Off +
1 = On
6ShutterSpeedSettingint16u(used only in M and S exposure modes)
7ApertureSettingint16u(used only in M and A exposure modes)
8ExposureTimeint16u 
9FNumberint16u 
10DriveMode2int16u +
0x0 = Self-timer 10 sec +
0x1 = Continuous +
0x4 = Self-timer 2 sec +
0x5 = Single Frame +
0x8 = White Balance Bracketing Low +
0x9 = White Balance Bracketing High +
0x302 = Single-frame Bracketing Low +
0x303 = Continous Bracketing Low +
0x702 = Single-frame Bracketing High +
0x703 = Continuous Bracketing High
+
11WhiteBalanceint16u + +
0x0 = Auto +
0x1 = Daylight +
0x2 = Cloudy +
0x3 = Shade +
0x4 = Tungsten
  0x5 = Fluorescent +
0x6 = Flash +
0x100 = Kelvin +
0x200 = Manual
+
12FocusModeint16u0 = AF-S +
1 = AF-C +
4 = AF-A +
5 = Manual +
6 = DMF
13AFPointSelectedint16u + +
1 = Center +
2 = Top +
3 = Top-right +
4 = Right +
5 = Bottom-right
  6 = Bottom +
7 = Bottom-left +
8 = Left +
9 = Top-left
+
14AFAreaModeint16u0 = Wide +
1 = Local +
2 = Spot
15FlashModeint16u0 = Auto +
2 = Rear Sync +
3 = Wireless +
4 = Fill Flash
16FlashExposureCompSetint16u 
18MeteringModeint16u0 = Multi-segment +
1 = Center-weighted average +
2 = Spot
19ISOSettingint16u +
0 = Auto +
48 = 100 +
56 = 200 +
64 = 400 +
72 = 800 +
80 = 1600 +
174 = 80 (Zone Matching Low) +
184 = 200 (Zone Matching High)
+
20ZoneMatchingModeint16u0 = Off +
1 = Standard +
2 = Advanced
21DynamicRangeOptimizerint16u(as applied to image) +
0 = Off +
1 = Standard +
2 = Advanced
22ColorModeint16u + +
0 = Standard +
1 = Vivid +
2 = Portrait +
3 = Landscape
  4 = Sunset +
5 = Night Scene +
7 = B&W +
8 = Adobe RGB
+
23ColorSpaceint16u0 = sRGB +
2 = B&W +
5 = Adobe RGB
24Sharpnessint16u0 = Normal
25Contrastint16u0 = Normal
26Saturationint16u0 = Normal
28FlashMeteringint16u0 = ADI (Advanced Distance Integration) +
1 = Pre-flash TTL
29PrioritySetupShutterReleaseint16u0 = AF +
1 = Release
30DriveModeint16u +
0 = Single Frame +
1 = Continuous +
2 = Self-timer +
3 = Continuous Bracketing +
4 = Single-Frame Bracketing +
5 = White Balance Bracketing
+
31SelfTimerTimeint16u0 = 10 s +
4 = 2 s
32ContinuousBracketingint16u0x303 = Low +
0x703 = High
33SingleFrameBracketingint16u0x302 = Low +
0x702 = High
34WhiteBalanceBracketingint16u0x8 = Low +
0x9 = High
35WhiteBalanceSettingint16u +
0x0 = Auto +
0x1 = Preset +
0x2 = Custom +
0x3 = Color Temperature/Color Filter +
0x8001 = Preset +
0x8002 = Custom +
0x8003 = Color Temperature/Color Filter
+
36PresetWhiteBalanceint16u + +
1 = Daylight +
2 = Cloudy +
3 = Shade
  4 = Tungsten +
5 = Fluorescent +
6 = Flash
+
37ColorTemperatureSettingint16u0 = Temperature +
2 = Color Filter
38CustomWBSettingint16u0 = Setup +
1 = Recall
39DynamicRangeOptimizerSettingint16u(as set in camera) +
0 = Off +
1 = Standard +
2 = Advanced
50FreeMemoryCardImagesint16u 
52CustomWBRedLevelint16u 
53CustomWBGreenLevelint16u 
54CustomWBBlueLevelint16u 
55CustomWBErrorint16u0 = OK +
1 = Error
56WhiteBalanceFineTuneint16s 
57ColorTemperatureint16u 
58ColorCompensationFilterint16s(ranges from -2 for green to +2 for magenta)
59SonyImageSizeint16u0 = Standard +
1 = Medium +
2 = Small
60SonyQualityint16u0 = RAW +
32 = Fine +
34 = RAW + JPEG +
48 = Standard
61InstantPlaybackTimeint16u 
62InstantPlaybackSetupint16u0 = Image and Information +
1 = Image Only +
3 = Image and Histogram
63NoiseReductionint16u0 = Off +
1 = On
64EyeStartAFint16u0 = On +
1 = Off
65RedEyeReductionint16u0 = Off +
1 = On
66FlashDefaultint16u0 = Auto +
1 = Fill Flash
67AutoBracketOrderint16u0 = 0 - + +
1 = - 0 +
68FocusHoldButtonint16u0 = Focus Hold +
1 = DOF Preview
69AELButtonint16u0 = Hold +
1 = Toggle +
2 = Spot Hold +
3 = Spot Toggle
70ControlDialSetint16u0 = Shutter Speed +
1 = Aperture
71ExposureCompensationModeint16u0 = Ambient and Flash +
1 = Ambient Only
72AFAssistint16u0 = On +
1 = Off
73CardShutterLockint16u0 = On +
1 = Off
74LensShutterLockint16u0 = On +
1 = Off
75AFAreaIlluminationint16u0 = 0.3 s +
1 = 0.6 s +
2 = Off
76MonitorDisplayOffint16u0 = Automatic +
1 = Manual
77RecordDisplayint16u0 = Auto Rotate +
1 = Horizontal
78PlayDisplayint16u0 = Auto Rotate +
1 = Manual Rotate
80ExposureIndicatorint16u--> Minolta ExposureIndicator Values
81AELExposureIndicatorint16u--> Minolta ExposureIndicator Values +
(also indicates exposure for next shot when bracketing)
82ExposureBracketingIndicatorLastint16u--> Minolta ExposureIndicator Values +
(indicator for last shot when bracketing)
83MeteringOffScaleIndicatorint16u(two flashing triangles when under or over metering scale) +
0 = Within Range +
1 = Under/Over Range +
255 = Out of Range
84FlashExposureIndicatorint16u--> Minolta ExposureIndicator Values
85FlashExposureIndicatorNextint16u--> Minolta ExposureIndicator Values +
(indicator for next shot when bracketing)
86FlashExposureIndicatorLastint16u--> Minolta ExposureIndicator Values +
(indicator for last shot when bracketing)
87ImageStabilizationint16u0 = Off +
1 = On
88FocusModeSwitchint16u0 = AF +
1 = MF
89FlashTypeint16u0 = Off +
1 = Built-in +
2 = External
90Rotationint16u0 = Horizontal (Normal) +
1 = Rotate 270 CW +
2 = Rotate 90 CW
91AELockint16u0 = Off +
1 = On
94ColorTemperatureint16u 
95ColorCompensationFilterint16s(ranges from -2 for green to +2 for magenta)
96BatteryStateint16u3 = Very Low +
4 = Low +
5 = Half Full +
6 = Sufficient Power Remaining
+ +

Minolta ExposureIndicator Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueExposureIndicatorValueExposureIndicatorValueExposureIndicator
0= Not Indicated125= -0.7133= +1.3
1= Under Scale126= -0.5134= +1.5
119= Bottom of Scale127= -0.3135= +1.7
120= -2.0128= 0136= +2.0
121= -1.7129= +0.3253= Top of Scale
122= -1.5130= +0.5254= Over Scale
123= -1.3131= +0.7  
124= -1.0132= +1.0  
+ +

Minolta MMA Tags

+

This information is found in MOV videos from Minolta models such as the +DiMAGE A2, S414 and 7Hi.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
20SoftwareVersionno 
+ +

Minolta MOV1 Tags

+

This information is found in MOV videos from some Konica Minolta models such +as the DiMage Z10 and X50.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
32ModelTypeno 
46ExposureTimeno 
50FNumberno 
58ExposureCompensationno 
80FocalLengthno 
+ +

Minolta MOV2 Tags

+

This information is found in MOV videos from some Minolta models such as the +DiMAGE X and Xt.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
24ModelTypeno 
38ExposureTimeno 
42FNumberno 
50ExposureCompensationno 
72FocalLengthno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 15, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/MinoltaRaw.html b/ExifTool/html/TagNames/MinoltaRaw.html new file mode 100644 index 0000000..f6d325a --- /dev/null +++ b/ExifTool/html/TagNames/MinoltaRaw.html @@ -0,0 +1,332 @@ + + + + +MinoltaRaw Tags + + + +

MinoltaRaw Tags

+

These tags are used in Minolta RAW format (MRW) images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
"\0PRD"MinoltaPRD---> MinoltaRaw PRD Tags
"\0RIF"MinoltaRIF---> MinoltaRaw RIF Tags
"\0TTW"MinoltaTTW---> EXIF Tags
"\0WBG"MinoltaWBG---> MinoltaRaw WBG Tags
+ +

MinoltaRaw PRD Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FirmwareIDstring[8] 
8SensorHeightint16u 
10SensorWidthint16u 
12ImageHeightint16u 
14ImageWidthint16u 
16RawDepthint8u 
17BitDepthint8u 
18StorageMethodint8u82 = Padded +
89 = Linear
23BayerPatternint8u1 = RGGB +
4 = GBRG
+ +

MinoltaRaw RIF Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1Saturationint8s 
2Contrastint8s 
3Sharpnessint8s 
4WBModeint8u~ 
5ProgramModeint8u + +
0 = None +
1 = Portrait +
2 = Text
  3 = Night Portrait +
4 = Sunset +
5 = Sports
+
6ISOSettingint8u +
0 = Auto +
48 = 100 +
56 = 200 +
64 = 400 +
72 = 800 +
80 = 1600 +
174 = 80 (Zone Matching Low) +
184 = 200 (Zone Matching High)
+
7ColorModeint32u[0.25] + +
0 = Natural color +
1 = Black & White +
2 = Vivid color +
3 = Solarization +
4 = Adobe RGB +
5 = Sepia +
9 = Natural +
12 = Portrait
  13 = Natural sRGB +
14 = Natural+ sRGB +
15 = Landscape +
16 = Evening +
17 = Night Scene +
18 = Night Portrait +
132 = Embed Adobe RGB
+(Sony A100) +
0x0 = Standard +
0x1 = Vivid +
0x2 = Portrait +
0x3 = Landscape +
0x4 = Sunset +
0x5 = Night View/Portrait +
0x6 = B&W +
0x7 = Adobe RGB +
0xc = Neutral +
0xd = Clear +
0xe = Deep +
0xf = Light +
0x10 = Autumn Leaves +
0x11 = Sepia +
0x12 = FL +
0x13 = Vivid 2 +
0x14 = IN +
0x15 = SH +
0x64 = Neutral +
0x65 = Clear +
0x66 = Deep +
0x67 = Light +
0x68 = Night View +
0x69 = Autumn Leaves +
0xff = Off +
0xffffffff = n/a
+
8WB_RBLevelsTungstenint16u[2](these WB_RBLevels currently decoded only for the Sony A100)
12WB_RBLevelsDaylightint16u[2] 
16WB_RBLevelsCloudyint16u[2] 
20WB_RBLevelsCoolWhiteFint16u[2] 
24WB_RBLevelsFlashint16u[2] 
28WB_RBLevelsCustomint16u[2] 
32WB_RBLevelsShadeint16u[2] 
36WB_RBLevelsDaylightFint16u[2] 
40WB_RBLevelsDayWhiteFint16u[2] 
44WB_RBLevelsWhiteFint16u[2] 
56ColorFilterint8s(Minolta models)
57BWFilterint8u 
58ZoneMatchingint8u(Minolta models) +
0 = ISO Setting Used +
1 = High Key +
2 = Low Key
59Hueint8s 
60ColorTemperatureint8u(Minolta models)
74ZoneMatchingint8u(Sony models) +
0 = ISO Setting Used +
1 = High Key +
2 = Low Key
76ColorTemperatureint8u(A100)
77ColorFilterint8u(A100)
78ColorTemperatureint8u(A200 and A700)
79ColorFilterint8u(A200 and A700)
80RawDataLengthno(A100)
+ +

MinoltaRaw WBG Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0WBScaleint8u[4] 
4WB_GBRGLevels +
WB_RGGBLevels
int16u[4]
int16u[4]
(DiMAGE A200) +
(other models)
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 29, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Motorola.html b/ExifTool/html/TagNames/Motorola.html new file mode 100644 index 0000000..84a0065 --- /dev/null +++ b/ExifTool/html/TagNames/Motorola.html @@ -0,0 +1,53 @@ + + + + +Motorola Tags + + + +

Motorola Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x5500BuildNumberstring 
0x5501SerialNumberstring 
0x6420CustomRenderedstring 
0x64d0DriveModestring 
0x665eSensorstring 
0x6705ManufactureDatestring 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 21, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Nikon.html b/ExifTool/html/TagNames/Nikon.html new file mode 100644 index 0000000..5b63c97 --- /dev/null +++ b/ExifTool/html/TagNames/Nikon.html @@ -0,0 +1,13326 @@ + + + + +Nikon Tags + + + +

Nikon Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001MakerNoteVersionundef[4] 
0x0002ISOint16u[2] 
0x0003ColorModestring 
0x0004Qualitystring 
0x0005WhiteBalancestring 
0x0006Sharpnessstring 
0x0007FocusModestring 
0x0008FlashSettingstring 
0x0009FlashTypestring 
0x000bWhiteBalanceFineTuneint16s[n] 
0x000cWB_RBLevelsrational64u[4] 
0x000dProgramShiftundef[4] 
0x000eExposureDifferenceundef[4] 
0x000fISOSelectionstring 
0x0010DataDumpno 
0x0011PreviewIFD---> Nikon PreviewIFD Tags
0x0012FlashExposureCompundef[4](may be set even if flash does not fire. Does not include the effect of +flash bracketing.)
0x0013ISOSettingint16u[2] 
0x0014ColorBalanceA +
NRWData
-
-
--> Nikon ColorBalanceA Tags +
--> Nikon ColorBalanceB Tags +
--> Nikon ColorBalanceC Tags
0x0016ImageBoundaryint16u[4] 
0x0017ExternalFlashExposureCompundef[4] 
0x0018FlashExposureBracketValueundef[4] 
0x0019ExposureBracketValuerational64s 
0x001aImageProcessingstring 
0x001bCropHiSpeedint16u[7] + +
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 +
13 = 2.8x Movie Crop +
14 = 1.4x Movie Crop +
15 = 1.5x Movie Crop +
17 = 1:1 Crop
+
0x001cExposureTuningundef[3] 
0x001dSerialNumberstring!(this value is used as a key to decrypt other information -- writing this tag +causes the other information to be re-encrypted with the new key)
0x001eColorSpaceint16u1 = sRGB +
2 = Adobe RGB +
4 = BT.2100
0x001fVRInfo---> Nikon VRInfo Tags
0x0020ImageAuthenticationint8u0 = Off +
1 = On
0x0021FaceDetect---> Nikon FaceDetect Tags
0x0022ActiveD-Lightingint16u + +
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
+
0x0023PictureControlDataundef!^--> Nikon PictureControl Tags +
--> Nikon PictureControl2 Tags +
--> Nikon PictureControl3 Tags +
--> Nikon PictureControlUnknown Tags
0x0024WorldTime---> Nikon WorldTime Tags
0x0025ISOInfo---> Nikon ISOInfo Tags
0x002aVignetteControlint16u0 = Off +
1 = Low +
3 = Normal +
5 = High
0x002bDistortInfo---> Nikon DistortInfo Tags
0x002cUnknownInfo---> Nikon UnknownInfo Tags
0x0032UnknownInfo2---> Nikon UnknownInfo2 Tags
0x0034ShutterModeint16u +
0 = Mechanical +
16 = Electronic +
48 = Electronic Front Curtain +
64 = Electronic (Movie) +
80 = Auto (Mechanical) +
81 = Auto (Electronic Front Curtain) +
96 = Electronic (High Speed)
+
0x0035HDRInfo +
HDRInfo2
-
-
--> Nikon HDRInfo Tags +
--> Nikon HDRInfo2 Tags
0x0037MechanicalShutterCountint32u 
0x0039LocationInfo---> Nikon LocationInfo Tags
0x003dBlackLevelint16u[4] 
0x003eImageSizeRAWyes1 = Large +
2 = Medium +
3 = Small
0x003fWhiteBalanceFineTunerational64s[2] 
0x0044JPGCompressionyes1 = Size Priority +
3 = Optimal Quality
0x0045CropAreaint16u[4](left, top, width, height)
0x004eNikonSettingsundef!^--> NikonSettings Tags
0x004fColorTemperatureAutoint16u 
0x0080ImageAdjustmentstring 
0x0081ToneCompstring 
0x0082AuxiliaryLensstring 
0x0083LensTypeint8uBit 0 = MF +
Bit 1 = D +
Bit 2 = G +
Bit 3 = VR +
Bit 4 = 1 +
Bit 5 = FT-1 +
Bit 6 = E +
Bit 7 = AF-P
0x0084Lensrational64u[4] 
0x0085ManualFocusDistancerational64u 
0x0086DigitalZoomrational64u 
0x0087FlashModeint8u +
0 = Did Not Fire +
1 = Fired, Manual +
3 = Not Ready +
7 = Fired, External +
8 = Fired, Commander Mode +
9 = Fired, TTL Mode +
18 = LED Light
+
0x0088AFInfo---> Nikon AFInfo Tags +
--> Nikon AFInfo Tags
0x0089ShootingModeint16u~(for the D70, Bit 5 = Unused LE-NR Slowdown) +
Bit 0 = Continuous +
Bit 1 = Delay +
Bit 2 = PC Control +
Bit 3 = Self-timer +
Bit 4 = Exposure Bracketing +
Bit 5 = Auto ISO +
Bit 6 = White-Balance Bracketing +
Bit 7 = IR Control +
Bit 8 = D-Lighting Bracketing +
Bit 11 = Pre-capture
0x008bLensFStopsundef[4] 
0x008cContrastCurveundef! 
0x008dColorHuestring 
0x008fSceneModestring 
0x0090LightSourcestring 
0x0091ShotInfoD40 +
ShotInfoD80 +
ShotInfoD90 +
ShotInfoD3a +
ShotInfoD3b +
ShotInfoD3X +
ShotInfoD3S +
ShotInfoD300a +
ShotInfoD300b +
ShotInfoD300S +
ShotInfoD700 +
ShotInfoD780 +
ShotInfoD7500 +
ShotInfoD800 +
ShotInfoD810 +
ShotInfoD850 +
ShotInfoD5000 +
ShotInfoD5100 +
ShotInfoD5200 +
ShotInfoD7000 +
ShotInfoD4 +
ShotInfoD4S +
ShotInfoD500 +
ShotInfoD6 +
ShotInfoD610 +
ShotInfoZ7II +
ShotInfoZ8 +
ShotInfoZ9 +
ShotInfo02xx +
ShotInfoUnknown
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--> Nikon ShotInfoD40 Tags +
--> Nikon ShotInfoD80 Tags +
--> Nikon ShotInfoD90 Tags +
--> Nikon ShotInfoD3a Tags +
--> Nikon ShotInfoD3b Tags +
--> Nikon ShotInfoD3X Tags +
--> Nikon ShotInfoD3S Tags +
--> Nikon ShotInfoD300a Tags +
--> Nikon ShotInfoD300b Tags +
--> Nikon ShotInfoD300S Tags +
--> Nikon ShotInfoD700 Tags +
--> Nikon ShotInfoD780 Tags +
--> Nikon ShotInfoD7500 Tags +
--> Nikon ShotInfoD800 Tags +
--> Nikon ShotInfoD810 Tags +
--> Nikon ShotInfoD850 Tags +
--> Nikon ShotInfoD5000 Tags +
--> Nikon ShotInfoD5100 Tags +
--> Nikon ShotInfoD5200 Tags +
--> Nikon ShotInfoD7000 Tags +
--> Nikon ShotInfoD4 Tags +
--> Nikon ShotInfoD4S Tags +
--> Nikon ShotInfoD500 Tags +
--> Nikon ShotInfoD6 Tags +
--> Nikon ShotInfoD610 Tags +
--> Nikon ShotInfoZ7II Tags +
--> Nikon ShotInfoZ8 Tags +
--> Nikon ShotInfoZ9 Tags +
--> Nikon ShotInfo Tags +
--> Nikon ShotInfo Tags
0x0092HueAdjustmentint16s 
0x0093NEFCompressionint16u--> Nikon NEFCompression Values
0x0094SaturationAdjint16s 
0x0095NoiseReductionstring 
0x0096NEFLinearizationTableundef! 
0x0097ColorBalance0100 +
ColorBalance0102 +
ColorBalance0103 +
ColorBalance0205 +
ColorBalance0209 +
ColorBalance02 +
ColorBalance0211 +
ColorBalance0213 +
ColorBalance0215 +
ColorBalanceUnknown02 +
ColorBalanceUnknown04 +
ColorBalanceUnknown
-
-
-
-
-
-
-
-
-
-
-
-
--> Nikon ColorBalance1 Tags +
--> Nikon ColorBalance2 Tags +
--> Nikon ColorBalance3 Tags +
--> Nikon ColorBalance2 Tags +
--> Nikon ColorBalance4 Tags +
--> Nikon ColorBalance2 Tags +
--> Nikon ColorBalance4 Tags +
--> Nikon ColorBalance2 Tags +
--> Nikon ColorBalance4 Tags +
--> Nikon ColorBalanceUnknown Tags +
--> Nikon ColorBalanceUnknown Tags +
--> Nikon ColorBalanceUnknown Tags
0x0098LensData0100 +
LensData0101 +
LensData0201 +
LensData0204 +
LensData0400 +
LensData0402 +
LensData0403 +
LensData0800 +
LensDataUnknown
-
-
-
-
-
-
-
-
-
--> Nikon LensData00 Tags +
--> Nikon LensData01 Tags +
--> Nikon LensData01 Tags +
--> Nikon LensData0204 Tags +
--> Nikon LensData0400 Tags +
--> Nikon LensData0402 Tags +
--> Nikon LensData0403 Tags +
--> Nikon LensData0800 Tags +
--> Nikon LensDataUnknown Tags
0x0099RawImageCenterint16u[2] 
0x009aSensorPixelSizerational64u[2] 
0x009cSceneAssiststring 
0x009dDateStampModeint16u(feature to imprint date/time on image) +
0 = Off +
1 = Date & Time +
2 = Date +
3 = Date Counter
0x009eRetouchHistoryint16u[10][Values 0-9] + +
0 = None +
3 = B & W +
4 = Sepia +
5 = Trim +
6 = Small Picture +
7 = D-Lighting +
8 = Red Eye +
9 = Cyanotype +
10 = Sky Light +
11 = Warm Tone +
12 = Color Custom +
13 = Image Overlay +
14 = Red Intensifier +
15 = Green Intensifier +
16 = Blue Intensifier +
17 = Cross Screen +
18 = Quick Retouch +
19 = NEF Processing +
23 = Distortion Control +
25 = Fisheye +
26 = Straighten +
29 = Perspective Control
  30 = Color Outline +
31 = Soft Filter +
32 = Resize +
33 = Miniature Effect +
34 = Skin Softening +
35 = Selected Frame +
37 = Color Sketch +
38 = Selective Color +
39 = Glamour +
40 = Drawing +
44 = Pop +
45 = Toy Camera Effect 1 +
46 = Toy Camera Effect 2 +
47 = Cross Process (red) +
48 = Cross Process (blue) +
49 = Cross Process (green) +
50 = Cross Process (yellow) +
51 = Super Vivid +
52 = High-contrast Monochrome +
53 = High Key +
54 = Low Key
+
0x00a0SerialNumberstring 
0x00a2ImageDataSizeint32u 
0x00a5ImageCountint32u 
0x00a6DeletedImageCountint32u 
0x00a7ShutterCountint32u!(includes both mechanical and electronic shutter activations for models with +this feature. This value is used as a key to decrypt other information, and +writing this tag causes the other information to be re-encrypted with the +new key)
0x00a8FlashInfo0100 +
FlashInfo0102 +
FlashInfo0103 +
FlashInfo0106 +
FlashInfo0107 +
FlashInfo0300 +
FlashInfoUnknown
-
-
-
-
-
-
-
--> Nikon FlashInfo0100 Tags +
--> Nikon FlashInfo0102 Tags +
--> Nikon FlashInfo0103 Tags +
--> Nikon FlashInfo0106 Tags +
--> Nikon FlashInfo0107 Tags +
--> Nikon FlashInfo0300 Tags +
--> Nikon FlashInfoUnknown Tags
0x00a9ImageOptimizationstring 
0x00aaSaturationstring 
0x00abVariProgramstring 
0x00acImageStabilizationstring 
0x00adAFResponsestring 
0x00b0MultiExposure +
MultiExposure2
-
-
--> Nikon MultiExposure Tags +
--> Nikon MultiExposure Tags +
--> Nikon MultiExposure2 Tags
0x00b1HighISONoiseReductionint16u + +
0 = Off +
1 = Minimal +
2 = Low +
3 = Medium Low
  4 = Normal +
5 = Medium High +
6 = High
+
0x00b3ToningEffectstring 
0x00b6PowerUpTimeundef(date/time when camera was last powered up)
0x00b7AFInfo2---> Nikon AFInfo2V0400 Tags +
--> Nikon AFInfo2 Tags
0x00b8FileInfo---> Nikon FileInfo Tags +
--> Nikon FileInfo Tags
0x00b9AFTune---> Nikon AFTune Tags
0x00bbRetouchInfo---> Nikon RetouchInfo Tags
0x00bdPictureControlDataundef!^--> Nikon PictureControl Tags
0x00bfSilentPhotographyyes0 = Off +
1 = On
0x00c3BarometerInfo---> Nikon BarometerInfo Tags
0x0e00PrintIM---> PrintIM Tags
0x0e01NikonCaptureDataundef!^--> NikonCapture Tags +
(this data is dropped when copying Nikon MakerNotes since it may be too large +to fit in the EXIF segment of a JPEG image, but it may be copied as a block +into existing Nikon MakerNotes later if desired)
0x0e09NikonCaptureVersionstring^ 
0x0e0eNikonCaptureOffsetsundef^--> Nikon CaptureOffsets Tags
0x0e10NikonScanIFD---> Nikon Scan Tags
0x0e13NikonCaptureEditVersions +
NikonCaptureEditVersions
undef^
undef!^
(the ExtractEmbedded option may be used to decode settings from the stored +edit versions, otherwise this is extracted as a binary data block) +
--> NikonCapture Tags
0x0e1dNikonICCProfileundef!--> ICC_Profile Tags
0x0e1eNikonCaptureOutputundef!^--> Nikon CaptureOutput Tags
0x0e22NEFBitDepthint16u[4]!'0 0 0 0' = n/a (JPEG) +
'8 8 8 0' = 8 x 3 +
'12 0 0 0' = 12 +
'14 0 0 0' = 14 +
'16 16 16 0' = 16 x 3
+ +

Nikon NEFCompression Values

+
+
+ + + + + + + + + + + + + + +
ValueNEFCompressionValueNEFCompression
1= Lossy (type 1)7= Unpacked 12 bits
2= Uncompressed8= Small
3= Lossless9= Packed 12 bits
4= Lossy (type 2)10= Packed 14 bits
5= Striped packed 12 bits13= High Efficiency
6= Uncompressed (reduced to 12 bit)14= High Efficiency*
+ +

Nikon ast Tags

+

Tags used by Nikon NX Studio in Nikon NKSC sidecar files and trailers.

+ +

These tags belong to the ExifTool XMP-ast family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Aboutno 
GPSAltitudeno 
GPSAltitudeRefno0 = Above Sea Level +
1 = Below Sea Level
GPSImgDirectionno 
GPSImgDirectionRefno'M' = Magnetic North +
'T' = True North
GPSLatitudeno 
GPSLatitudeRefno0 = North +
1 = South
GPSLongitudeno 
GPSLongitudeRefno2 = East +
3 = West
GPSMapDatumno 
GPSVersionIDno 
IPTC---> IPTC Tags
Versionno 
XMLPackets---> XMP Tags
+ +

Nikon nine Tags

+

These tags belong to the ExifTool XMP-nine family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Aboutno 
Labelno 
NineEdits---> Nikon NineEdits Tags
Ratingno 
Trimno 
Versionno 
+ +

Nikon NineEdits Tags

+

XML-based tags used to store editing information.

+
+
+ + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
FilterParametersBinaryno 
FilterParametersCustomCustomDatano 
FilterParametersExportExportDatano 
+ +

Nikon sdc Tags

+

These tags belong to the ExifTool XMP-sdc family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Aboutno 
AppNameno 
AppVersionno 
Versionno 
+ +

Nikon PreviewIFD Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x00feSubfileTypeno0x0 = Full-resolution image +
0x1 = Reduced-resolution image +
0x2 = Single page of multi-page image +
0x3 = Single page of multi-page reduced-resolution image +
0x4 = Transparency mask +
0x5 = Transparency mask of reduced-resolution image +
0x6 = Transparency mask of multi-page image +
0x7 = Transparency mask of reduced-resolution multi-page image +
0x8 = Depth map +
0x9 = Depth map of reduced-resolution image +
0x10 = Enhanced image data +
0x10001 = Alternate reduced-resolution image +
0x10004 = Semantic Mask +
0xffffffff = invalid +
Bit 0 = Reduced resolution +
Bit 1 = Single page +
Bit 2 = Transparency mask +
Bit 3 = TIFF/IT final page +
Bit 4 = TIFF-FX mixed raster content
0x0103Compressionno--> EXIF Compression Values
0x011aXResolutionno 
0x011bYResolutionno 
0x0128ResolutionUnitno1 = None +
2 = inches +
3 = cm
0x0201PreviewImageStartint32u* 
0x0202PreviewImageLengthint32u* 
0x0213YCbCrPositioningno1 = Centered +
2 = Co-sited
+ +

Nikon ColorBalanceA Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
624WB_RBLevelsint16u[2]!(as shot)
626WB_RBLevelsAutoint16u[2]! 
628WB_RBLevelsDaylightint16u[14]!(red/blue levels for 0,+3,+2,+1,-1,-2,-3)
642WB_RBLevelsIncandescentint16u[14]! 
656WB_RBLevelsFluorescentint16u[6]!(red/blue levels for fluorescent W,N,D)
662WB_RBLevelsCloudyint16u[14]! 
676WB_RBLevelsFlashint16u[14]! 
690WB_RBLevelsShadeint16u[14]!(not valid for E8700)
+ +

Nikon ColorBalanceB Tags

+

Color balance tags used by the P6000.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ColorBalanceVersionundef[4] 
5096WB_RGGBLevelsint32u[4]! 
5112WB_RGGBLevelsDaylightint32u[4]! 
5128WB_RGGBLevelsCloudyint32u[4]! 
5160WB_RGGBLevelsTungstenint32u[4]! 
5176WB_RGGBLevelsFluorescentWint32u[4]! 
5192WB_RGGBLevelsFlashint32u[4]! 
5224WB_RGGBLevelsCustomint32u[4]!(all zero if preset WB not used)
5240WB_RGGBLevelsAutoint32u[4]! 
+ +

Nikon ColorBalanceC Tags

+

Color balance tags used by the P1000, P7000, P7100 and B700.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ColorBalanceVersionundef[4] 
32BlackLevelint16u 
56WB_RGGBLevelsint32u[4]! 
76WB_RGGBLevelsDaylightint32u[4]! 
96WB_RGGBLevelsCloudyint32u[4]! 
116WB_RGGBLevelsShadeint32u[4]!(valid only for some models)
136WB_RGGBLevelsTungstenint32u[4]! 
156WB_RGGBLevelsFluorescentWint32u[4]! 
176WB_RGGBLevelsFluorescentNint32u[4]! 
196WB_RGGBLevelsFluorescentDint32u[4]! 
216WB_RGGBLevelsHTMercuryint32u[4]! 
256WB_RGGBLevelsCustomint32u[4]!(all zero if preset WB not used)
276WB_RGGBLevelsAutoint32u[4]! 
+ +

Nikon VRInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0VRInfoVersionno 
4VibrationReductionint8u0 = n/a +
1 = On +
2 = Off
6VRModeint8u(Z Series cameras thru July 2023) +
0 = Off +
1 = Normal +
3 = Sport +
0 = Normal +
1 = On (1) +
2 = Active +
3 = Sport
8VRTypeint8u2 = In-body +
3 = In-body + Lens
+ +

Nikon FaceDetect Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1FaceDetectFrameSizeint16u[2] 
3FacesDetectedint16u 
4Face1Positionint16u[4](top, left, width and height of face detect area in coordinates of +FaceDetectFrameSize)
8Face2Positionint16u[4] 
12Face3Positionint16u[4] 
16Face4Positionint16u[4] 
20Face5Positionint16u[4] 
24Face6Positionint16u[4] 
28Face7Positionint16u[4] 
32Face8Positionint16u[4] 
36Face9Positionint16u[4] 
40Face10Positionint16u[4] 
44Face11Positionint16u[4] 
48Face12Positionint16u[4] 
+ +

Nikon PictureControl Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PictureControlVersionno 
4PictureControlNamestring[20] 
24PictureControlBasestring[20] 
48PictureControlAdjustint8u0 = Default Settings +
1 = Quick Adjust +
2 = Full Control
49PictureControlQuickAdjustint8u 
50Sharpnessint8u 
51Contrastint8u 
52Brightnessint8u 
53Saturationint8u 
54HueAdjustmentint8u 
55FilterEffectint8u + +
0x80 = Off +
0x81 = Yellow +
0x82 = Orange
  0x83 = Red +
0x84 = Green +
0xff = n/a
+
56ToningEffectint8u + +
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
+
57ToningSaturationint8u 
+ +

Nikon PictureControl2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PictureControlVersionno 
4PictureControlNamestring[20] 
24PictureControlBasestring[20] 
48PictureControlAdjustint8u0 = Default Settings +
1 = Quick Adjust +
2 = Full Control
49PictureControlQuickAdjustint8u 
51Sharpnessint8u 
53Clarityint8u 
55Contrastint8u 
57Brightnessint8u 
59Saturationint8u 
61Hueint8u 
63FilterEffectint8u + +
0x80 = Off +
0x81 = Yellow +
0x82 = Orange
  0x83 = Red +
0x84 = Green +
0xff = n/a
+
64ToningEffectint8u + +
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
+
65ToningSaturationint8u 
+ +

Nikon PictureControl3 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PictureControlVersionno 
8PictureControlNamestring[20] 
28PictureControlBasestring[20] 
54PictureControlAdjustint8u0 = Default Settings +
1 = Quick Adjust +
2 = Full Control
55PictureControlQuickAdjustint8u 
57Sharpnessint8u 
59MidRangeSharpnessint8u 
61Clarityint8u 
63Contrastint8u 
65Brightnessint8u 
67Saturationint8u 
69Hueint8u 
71FilterEffectint8u + +
0x80 = Off +
0x81 = Yellow +
0x82 = Orange
  0x83 = Red +
0x84 = Green +
0xff = n/a
+
72ToningEffectint8u + +
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
+
73ToningSaturationint8u 
+ +

Nikon PictureControlUnknown Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0PictureControlVersionno 
+ +

Nikon WorldTime Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0TimeZoneint16s 
2DaylightSavingsint8u0 = No +
1 = Yes
3DateDisplayFormatint8u0 = Y/M/D +
1 = M/D/Y +
2 = D/M/Y
+ +

Nikon ISOInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ISOint8u(val = 100 * 2**(raw/12-5))
4ISOExpansionint16u + +
0x0 = Off +
0x101 = Hi 0.3 +
0x102 = Hi 0.5 +
0x103 = Hi 0.7 +
0x104 = Hi 1.0 +
0x105 = Hi 1.3 +
0x106 = Hi 1.5 +
0x107 = Hi 1.7 +
0x108 = Hi 2.0 +
0x109 = Hi 2.3 +
0x10a = Hi 2.5 +
0x10b = Hi 2.7 +
0x10c = Hi 3.0
  0x10d = Hi 3.3 +
0x10e = Hi 3.5 +
0x10f = Hi 3.7 +
0x110 = Hi 4.0 +
0x111 = Hi 4.3 +
0x112 = Hi 4.5 +
0x113 = Hi 4.7 +
0x114 = Hi 5.0 +
0x201 = Lo 0.3 +
0x202 = Lo 0.5 +
0x203 = Lo 0.7 +
0x204 = Lo 1.0
+
6ISO2int8u(val = 100 * 2**(raw/12-5))
10ISOExpansion2int16u + +
0x0 = Off +
0x101 = Hi 0.3 +
0x102 = Hi 0.5 +
0x103 = Hi 0.7 +
0x104 = Hi 1.0 +
0x105 = Hi 1.3 +
0x106 = Hi 1.5
  0x107 = Hi 1.7 +
0x108 = Hi 2.0 +
0x201 = Lo 0.3 +
0x202 = Lo 0.5 +
0x203 = Lo 0.7 +
0x204 = Lo 1.0
+
+ +

Nikon DistortInfo Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0DistortionVersion?no 
4AutoDistortionControlint8u0 = Off +
1 = On +
2 = On (underwater)
+ +

Nikon UnknownInfo Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
0UnknownInfoVersion?no 
+ +

Nikon UnknownInfo2 Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
0UnknownInfo2Version?no 
+ +

Nikon HDRInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0HDRInfoVersionno 
4HDRint8u0 = Off +
1 = On (normal) +
48 = Auto
5HDRLevelint8u0 = Auto +
1 = 1 EV +
2 = 2 EV +
3 = 3 EV +
255 = n/a
6HDRSmoothingint8u + +
0 = Off +
1 = Normal +
2 = Low
  3 = High +
48 = Auto +
255 = n/a
+
7HDRLevel2int8u0 = Auto +
1 = 1 EV +
2 = 2 EV +
3 = 3 EV +
255 = n/a
+ +

Nikon HDRInfo2 Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0HDRInfoVersionno 
4HDRint8u0 = Off +
1 = On (normal)
5HDRLevelint8u + +
0 = n/a +
1 = Normal +
2 = Low
  3 = High +
4 = High+ +
5 = Auto
+
+ +

Nikon LocationInfo Tags

+

Tags written by some Nikon GPS-equipped cameras like the AW100.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LocationInfoVersionundef[4] 
4TextEncodingint8u0 = n/a +
1 = UTF8 +
2 = UTF16
5CountryCodeundef[3] 
8POILevelint8u 
9Locationundef[70] 
+ +

Nikon MakerNotes0x51 Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FirmwareVersion51no 
10NEFCompressionint16u[0.5]--> Nikon NEFCompression Values
+ +

Nikon MakerNotes0x56 Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FirmwareVersionno 
4BurstGroupIDint16u 
+ +

Nikon AFInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AFAreaModeint8u +
0 = Single Area +
1 = Dynamic Area +
2 = Dynamic Area (closest subject) +
3 = Group Dynamic +
4 = Single Area (wide) +
5 = Dynamic Area (wide)
+
1AFPointint8u(in some focus modes this value is not meaningful) + +
0 = Center +
1 = Top +
2 = Bottom +
3 = Mid-left +
4 = Mid-right +
5 = Upper-left
  6 = Upper-right +
7 = Lower-left +
8 = Lower-right +
9 = Far Left +
10 = Far Right
+
2AFPointsInFocusint16u + +
0x0 = (none) +
0x7ff = All 11 Points +
Bit 0 = Center +
Bit 1 = Top +
Bit 2 = Bottom +
Bit 3 = Mid-left +
Bit 4 = Mid-right
  Bit 5 = Upper-left +
Bit 6 = Upper-right +
Bit 7 = Lower-left +
Bit 8 = Lower-right +
Bit 9 = Far Left +
Bit 10 = Far Right
+
+ +

Nikon ShotInfoD40 Tags

+

These tags are extracted from encrypted data in D40 and D40X images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
582ShutterCountint32u 
586.1VibrationReductionint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
729CustomSettingsD40---> NikonCustom SettingsD40 Tags
+ +

Nikon ShotInfoD80 Tags

+

These tags are extracted from encrypted data in D80 images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
586ShutterCountint32u 
590.1Rotationint8u[val & 0x7] +
0 = Horizontal +
1 = Rotate 270 CW +
2 = Rotate 90 CW +
3 = Rotate 180
590.2VibrationReductionint8u[val >> 3 & 0x3] +
0 = Off +
3 = On
590.3FlashFiredint8u[val >> 5 & 0x7] +
Bit 1 = Internal +
Bit 2 = External
708NikonImageSizeint8u[val >> 4 & 0xf] +
0 = Large (10.0 M) +
1 = Medium (5.6 M) +
2 = Small (2.5 M)
708.1ImageQualityint8u[val & 0xf] +
0 = NEF (RAW) +
1 = JPEG Fine +
2 = JPEG Normal +
3 = JPEG Basic +
4 = NEF (RAW) + JPEG Fine +
5 = NEF (RAW) + JPEG Normal +
6 = NEF (RAW) + JPEG Basic
+
748CustomSettingsD80---> NikonCustom SettingsD80 Tags
+ +

Nikon ShotInfoD90 Tags

+

These tags are extracted from encrypted data in images from the D90 with +firmware 1.00.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
693ISO2int8u 
725ShutterCountint32u 
884CustomSettingsD90---> NikonCustom SettingsD90 Tags
+ +

Nikon ShotInfoD3a Tags

+

These tags are extracted from encrypted data in images from the D3 with +firmware 1.00 and earlier.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
598ISO2int8u 
630ShutterCountint32u 
723.1NikonImageSizeint8u[val >> 3 & 0x3] +
0 = Large +
1 = Medium +
2 = Small
723.2ImageQualityint8u[val & 0x7] +
0 = NEF (RAW) + JPEG Fine +
1 = NEF (RAW) + JPEG Norm +
2 = NEF (RAW) + JPEG Basic +
3 = NEF (RAW) +
4 = TIF (RGB) +
5 = JPEG Fine +
6 = JPEG Normal +
7 = JPEG Basic
+
769CustomSettingsD3---> NikonCustom SettingsD3 Tags
+ +

Nikon ShotInfoD3b Tags

+

These tags are extracted from encrypted data in images from the D3 with +firmware 1.10, 2.00, 2.01 and 2.02.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
16ImageAreaint8u0 = FX (36.0 x 23.9 mm) +
1 = DX (23.5 x 15.6 mm) +
2 = 5:4 (30.0 x 23.9 mm)
605ISO2int8u 
637ShutterCountint32u(firmware 1.10)
639ShutterCountint32u(firmware 2.00, 2.01 and 2.02)
650PreFlashReturnStrengthint8u(valid in TTL and TTL-BL flash control modes)
732.1NikonImageSizeint8u[val >> 3 & 0x3] +
0 = Large +
1 = Medium +
2 = Small
732.2ImageQualityint8u[val & 0x7] +
0 = NEF (RAW) + JPEG Fine +
1 = NEF (RAW) + JPEG Norm +
2 = NEF (RAW) + JPEG Basic +
3 = NEF (RAW) +
4 = TIF (RGB) +
5 = JPEG Fine +
6 = JPEG Normal +
7 = JPEG Basic
+
778CustomSettingsD3---> NikonCustom SettingsD3 Tags
+ +

Nikon ShotInfoD3X Tags

+

These tags are extracted from encrypted data in images from the D3X with +firmware 1.00.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
605ISO2int8u 
640ShutterCountint32u 
779CustomSettingsD3X---> NikonCustom SettingsD3 Tags
+ +

Nikon ShotInfoD3S Tags

+

These tags are extracted from encrypted data in images from the D3S with +firmware 1.00 and earlier.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
16ImageAreaint8u0 = FX (36x24) +
1 = DX (24x16) +
2 = 5:4 (30x24) +
3 = 1.2x (30x20)
545ISO2int8u 
578ShutterCountint32u 
718CustomSettingsD3S---> NikonCustom SettingsD3 Tags
+ +

Nikon ShotInfoD300a Tags

+

These tags are extracted from encrypted data in images from the D300 with +firmware 1.00 and earlier.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
604ISO2int8u 
633ShutterCountint32u 
721AFFineTuneAdjint16u + + +
0x0 = 0 +
0x3a = +1 +
0x3b = +2 +
0x3c = +4 +
0x3d = +8 +
0x3e = +16 +
0xc2 = -16 +
0xc3 = -8 +
0xc4 = -4 +
0xc5 = -2 +
0xc6 = -1 +
0x103e = +17 +
0x10c2 = -17 +
0x203d = +9
  0x203e = +18 +
0x20c2 = -18 +
0x20c3 = -9 +
0x303e = +19 +
0x30c2 = -19 +
0x403c = +5 +
0x403d = +10 +
0x403e = +20 +
0x40c2 = -20 +
0x40c3 = -10 +
0x40c4 = -5 +
0x603d = +11 +
0x60c3 = -11 +
0x803b = +3
  0x803c = +6 +
0x803d = +12 +
0x80c3 = -12 +
0x80c4 = -6 +
0x80c5 = -3 +
0xa03d = +13 +
0xa0c3 = -13 +
0xc03c = +7 +
0xc03d = +14 +
0xc0c3 = -14 +
0xc0c4 = -7 +
0xe03d = +15 +
0xe0c3 = -15
+
790CustomSettingsD300---> NikonCustom SettingsD3 Tags
+ +

Nikon ShotInfoD300b Tags

+

These tags are extracted from encrypted data in images from the D300 with +firmware 1.10.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
613ISO2int8u 
644ShutterCountint32u 
732AFFineTuneAdjint16u(firmware version 1.10B) + + +
0x0 = 0 +
0x83e = +11 +
0x8c2 = -11 +
0x203c = +3 +
0x203d = +6 +
0x203e = +12 +
0x20c2 = -12 +
0x20c3 = -6 +
0x20c4 = -3 +
0x383e = +13 +
0x38c2 = -13 +
0x503d = +7 +
0x503e = +14 +
0x50c2 = -14
  0x50c3 = -7 +
0x683e = +15 +
0x68c2 = -15 +
0x803a = +1 +
0x803b = +2 +
0x803c = +4 +
0x803d = +8 +
0x803e = +16 +
0x80c2 = -16 +
0x80c3 = -8 +
0x80c4 = -4 +
0x80c5 = -2 +
0x80c6 = -1 +
0x983e = +17
  0x98c2 = -17 +
0xb03d = +9 +
0xb03e = +18 +
0xb0c2 = -18 +
0xb0c3 = -9 +
0xc83e = +19 +
0xc8c2 = -19 +
0xe03c = +5 +
0xe03d = +10 +
0xe03e = +20 +
0xe0c2 = -20 +
0xe0c3 = -10 +
0xe0c4 = -5
+(other versions) + + +
0x0 = 0 +
0x43e = +13 +
0x4c2 = -13 +
0x183d = +7 +
0x183e = +14 +
0x18c2 = -14 +
0x18c3 = -7 +
0x2c3e = +15 +
0x2cc2 = -15 +
0x403a = +1 +
0x403b = +2 +
0x403c = +4 +
0x403d = +8 +
0x403e = +16
  0x40c2 = -16 +
0x40c3 = -8 +
0x40c4 = -4 +
0x40c5 = -2 +
0x40c6 = -1 +
0x543e = +17 +
0x54c2 = -17 +
0x683d = +9 +
0x683e = +18 +
0x68c2 = -18 +
0x68c3 = -9 +
0x7c3e = +19 +
0x7cc2 = -19 +
0x903c = +5
  0x903d = +10 +
0x903e = +20 +
0x90c2 = -20 +
0x90c3 = -10 +
0x90c4 = -5 +
0xb83d = +11 +
0xb8c3 = -11 +
0xe03b = +3 +
0xe03c = +6 +
0xe03d = +12 +
0xe0c3 = -12 +
0xe0c4 = -6 +
0xe0c5 = -3
+
802CustomSettingsD300---> NikonCustom SettingsD3 Tags
+ +

Nikon ShotInfoD300S Tags

+

These tags are extracted from encrypted data in images from the D300S with +firmware 1.00.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
613ISO2int8u 
646ShutterCountint32u 
804CustomSettingsD300S---> NikonCustom SettingsD3 Tags
+ +

Nikon ShotInfoD700 Tags

+

These tags are extracted from encrypted data in images from the D700 with +firmware 1.02f.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
613ISO2int8u 
647ShutterCountint32u 
804CustomSettingsD700---> NikonCustom SettingsD700 Tags
+ +

Nikon ShotInfoD780 Tags

+

These tags are extracted from encrypted data in images from the D780.

+
+
+ + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
156OrientOffset---> Nikon OrientationInfo Tags
+ +

Nikon OrientationInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0RollAnglefixed32u(converted to degrees of clockwise camera roll)
4PitchAnglefixed32u(converted to degrees of upward camera tilt)
8YawAnglefixed32u(the camera yaw angle when shooting in portrait orientation)
+ +

Nikon ShotInfoD7500 Tags

+

These tags are extracted from encrypted data in images from the D7500.

+
+
+ + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
160OrientOffset---> Nikon OrientationInfo Tags
+ +

Nikon ShotInfoD800 Tags

+

These tags are extracted from encrypted data in images from the D800.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
1216RepeatingFlashOutputExternalint8u 
1218RepeatingFlashRateExternalint8u 
1219RepeatingFlashCountExternalint8u 
1234FlashExposureComp2int8s(includes the effect of flash bracketing)
1242RepeatingFlashRateBuilt-inint8u 
1243RepeatingFlashCountBuilt-inint8u 
1308SequenceNumberint8u 
1531ShutterCountint32u 
1772CustomSettingsD800---> NikonCustom SettingsD800 Tags
+ +

Nikon ShotInfoD810 Tags

+

These tags are extracted from encrypted data in images from the D810.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
16SettingsOffset---> Nikon SettingsInfoD810 Tags
36BracketingOffset---> Nikon BracketingInfoD810 Tags
56ISOAutoOffset---> Nikon ISOAutoInfoD810 Tags
64CustomSettingsOffset---> NikonCustom SettingsD810 Tags
132OrientationOffset---> Nikon OrientationInfo Tags
+ +

Nikon SettingsInfoD810 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
316SecondarySlotFunctionint8u[val & 0x3] +
0 = Overflow +
2 = Backup +
3 = NEF Primary + JPG Secondary
+ +

Nikon BracketingInfoD810 Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
15AEBracketingStepsint8u[val & 0xff] + +
0x0 = AE Bracketing Disabled +
0x20 = AE Bracketing Disabled +
0x30 = AE Bracketing Disabled +
0x40 = AE Bracketing Disabled +
0x50 = AE Bracketing Disabled +
0x81 = +3F0.3 +
0x82 = -3F0.3 +
0x83 = +2F0.3 +
0x84 = -2F0.3 +
0x85 = 3F0.3 +
0x86 = 5F0.3 +
0x87 = 7F0.3 +
0x88 = 9F0.3 +
0x91 = +3F0.5 +
0x92 = -3F0.5 +
0x93 = +2F0.5 +
0x94 = -2F0.5 +
0x95 = 3F0.5 +
0x96 = 5F0.5 +
0x97 = 7F0.5 +
0x98 = 9F0.5 +
0xa1 = +3F0.7 +
0xa2 = -3F0.7 +
0xa3 = +2F0.7 +
0xa4 = -2F0.7
  0xa5 = 3F0.7 +
0xa6 = 5F0.7 +
0xa7 = 7F0.7 +
0xa8 = 9F0.7 +
0xb1 = +3F1 +
0xb2 = -3F1 +
0xb3 = +2F1 +
0xb4 = -2F1 +
0xb5 = 3F1 +
0xb6 = 5F1 +
0xb7 = 7F1 +
0xb8 = 9F1 +
0xc1 = +3F2 +
0xc2 = -3F2 +
0xc3 = +2F2 +
0xc4 = -2F2 +
0xc5 = 3F2 +
0xc6 = 5F2 +
0xd1 = +3F3 +
0xd2 = -3F3 +
0xd3 = +2F3 +
0xd4 = -2F3 +
0xd5 = 3F3 +
0xd6 = 5F3
+
16WBBracketingStepsint8u[val & 0xff] + +
0x0 = WB Bracketing Disabled +
0x1 = b3F 1 +
0x2 = A3F 1 +
0x3 = b2F 1 +
0x4 = A2F 1 +
0x5 = 3F 1 +
0x6 = 5F 1 +
0x7 = 7F 1 +
0x8 = 9F 1 +
0x10 = 0F 2 +
0x11 = b3F 2 +
0x12 = A3F 2 +
0x13 = b2F 2 +
0x14 = A2F 2
  0x15 = 3F 2 +
0x16 = 5F 2 +
0x17 = 7F 2 +
0x18 = 9F 2 +
0x20 = 0F 3 +
0x21 = b3F 3 +
0x22 = A3F 3 +
0x23 = b2F 3 +
0x24 = A2F 3 +
0x25 = 3F 3 +
0x26 = 5F 3 +
0x27 = 7F 3 +
0x28 = 9F 3
+
23NikonMeteringModeint8u[val & 0x3] +
0 = Matrix +
1 = Center +
2 = Spot +
3 = Highlight
+ +

Nikon ISOAutoInfoD810 Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ISOAutoShutterTimeint8u[val & 0x3f] + +
0 = 1/4000 s +
1 = 1/3200 s +
2 = 1/2500 s +
3 = 1/2000 s +
4 = 1/1600 s +
5 = 1/1250 s +
6 = 1/1000 s +
7 = 1/800 s +
8 = 1/640 s +
9 = 1/500 s +
10 = 1/400 s +
11 = 1/320 s +
12 = 1/250 s +
13 = 1/200 s +
14 = 1/160 s +
15 = 1/125 s +
16 = 1/100 s +
17 = 1/80 s +
18 = 1/60 s
  19 = 1/50 s +
20 = 1/40 s +
21 = 1/30 s +
22 = 1/15 s +
23 = 1/8 s +
24 = 1/4 s +
25 = 1/2 s +
26 = 1 s +
27 = 2 s +
28 = 4 s +
29 = 8 s +
30 = 15 s +
31 = 30 s +
32 = Auto (Slowest) +
33 = Auto (Slower) +
34 = Auto +
35 = Auto (Faster) +
36 = Auto (Fastest)
+
5ISOAutoHiLimitint8u[val & 0xff] + +
0x24 = ISO 200 +
0x26 = ISO 250 +
0x27 = ISO 280 +
0x28 = ISO 320 +
0x2a = ISO 400 +
0x2c = ISO 500 +
0x2d = ISO 560 +
0x2e = ISO 640 +
0x30 = ISO 800 +
0x32 = ISO 1000 +
0x33 = ISO 1100 +
0x34 = ISO 1250 +
0x36 = ISO 1600 +
0x38 = ISO 2000 +
0x39 = ISO 2200 +
0x3a = ISO 2500 +
0x3c = ISO 3200 +
0x3e = ISO 4000 +
0x3f = ISO 4500 +
0x40 = ISO 5000 +
0x42 = ISO 6400
  0x44 = ISO 8000 +
0x45 = ISO 9000 +
0x46 = ISO 10000 +
0x48 = ISO 12800 +
0x4a = ISO 16000 +
0x4b = ISO 18000 +
0x4c = ISO 20000 +
0x4e = ISO 25600 +
0x50 = ISO 32000 +
0x51 = ISO 36000 +
0x52 = ISO 40000 +
0x54 = ISO 51200 +
0x56 = ISO Hi 0.3 +
0x57 = ISO Hi 0.5 +
0x58 = ISO Hi 0.7 +
0x5a = ISO Hi 1.0 +
0x60 = ISO Hi 2.0 +
0x66 = ISO Hi 3.0 +
0x6c = ISO Hi 4.0 +
0x72 = ISO Hi 5.0
+
+ +

Nikon ShotInfoD850 Tags

+

These tags are extracted from encrypted data in images from the D850.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
16MenuSettingsOffset---> Nikon MenuSettingsD850 Tags
76MoreSettingsOffset---> Nikon MoreSettingsD850 Tags
88CustomSettingsOffset---> NikonCustom SettingsD850 Tags
160OrientationOffset---> Nikon OrientationInfo Tags
+ +

Nikon MenuSettingsD850 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
1757PhotoShootingMenuBankImageAreaint8u[val & 0x7] +
0 = FX (36x24) +
1 = DX (24x16) +
2 = 5:4 (30x24) +
3 = 1.2x (30x20) +
4 = 1:1 (24x24)
+ +

Nikon MoreSettingsD850 Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
36PhotoShootingMenuBankint8u(valid for JPEG images only) +
[val & 0x3] +
0 = A +
1 = B +
2 = C +
3 = D
37PrimarySlotint8u[val >> 7 & 0x1] +
0 = XQD Card +
1 = SD Card
+ +

Nikon ShotInfoD5000 Tags

+

These tags are extracted from encrypted data in images from the D5000 with +firmware 1.00.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
693ISO2int8u 
726ShutterCountint32u 
888CustomSettingsD5000---> NikonCustom SettingsD5000 Tags
+ +

Nikon ShotInfoD5100 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
801ShutterCountint32u 
1031CustomSettingsD5100---> NikonCustom SettingsD5100 Tags
+ +

Nikon ShotInfoD5200 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
3032ShutterCountint32u 
3285CustomSettingsD5200---> NikonCustom SettingsD5200 Tags
+ +

Nikon ShotInfoD7000 Tags

+

These tags are extracted from encrypted data in images from the D7000 with +firmware 1.01b.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
800ShutterCountint32u 
1028CustomSettingsD7000---> NikonCustom SettingsD7000 Tags
+ +

Nikon ShotInfoD4 Tags

+

These tags are extracted from encrypted data in images from the D4.

+
+
+ + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
1873CustomSettingsD4---> NikonCustom SettingsD4 Tags
+ +

Nikon ShotInfoD4S Tags

+

These tags are extracted from encrypted data in images from the D4S.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
464SecondarySlotFunctionint8u[val & 0x3] +
0 = Overflow +
2 = Backup +
3 = NEF Primary + JPG Secondary
5964AEBracketingStepsint8u[val & 0xff] + +
0x0 = AE Bracketing Disabled +
0x20 = AE Bracketing Disabled +
0x30 = AE Bracketing Disabled +
0x40 = AE Bracketing Disabled +
0x50 = AE Bracketing Disabled +
0x81 = +3F0.3 +
0x82 = -3F0.3 +
0x83 = +2F0.3 +
0x84 = -2F0.3 +
0x85 = 3F0.3 +
0x86 = 5F0.3 +
0x87 = 7F0.3 +
0x88 = 9F0.3 +
0x91 = +3F0.5 +
0x92 = -3F0.5 +
0x93 = +2F0.5 +
0x94 = -2F0.5 +
0x95 = 3F0.5 +
0x96 = 5F0.5 +
0x97 = 7F0.5 +
0x98 = 9F0.5 +
0xa1 = +3F0.7 +
0xa2 = -3F0.7 +
0xa3 = +2F0.7 +
0xa4 = -2F0.7
  0xa5 = 3F0.7 +
0xa6 = 5F0.7 +
0xa7 = 7F0.7 +
0xa8 = 9F0.7 +
0xb1 = +3F1 +
0xb2 = -3F1 +
0xb3 = +2F1 +
0xb4 = -2F1 +
0xb5 = 3F1 +
0xb6 = 5F1 +
0xb7 = 7F1 +
0xb8 = 9F1 +
0xc1 = +3F2 +
0xc2 = -3F2 +
0xc3 = +2F2 +
0xc4 = -2F2 +
0xc5 = 3F2 +
0xc6 = 5F2 +
0xd1 = +3F3 +
0xd2 = -3F3 +
0xd3 = +2F3 +
0xd4 = -2F3 +
0xd5 = 3F3 +
0xd6 = 5F3
+
5965WBBracketingStepsint8u[val & 0xff] + +
0x0 = WB Bracketing Disabled +
0x1 = b3F 1 +
0x2 = A3F 1 +
0x3 = b2F 1 +
0x4 = A2F 1 +
0x5 = 3F 1 +
0x6 = 5F 1 +
0x7 = 7F 1 +
0x8 = 9F 1 +
0x10 = 0F 2 +
0x11 = b3F 2 +
0x12 = A3F 2 +
0x13 = b2F 2 +
0x14 = A2F 2
  0x15 = 3F 2 +
0x16 = 5F 2 +
0x17 = 7F 2 +
0x18 = 9F 2 +
0x20 = 0F 3 +
0x21 = b3F 3 +
0x22 = A3F 3 +
0x23 = b2F 3 +
0x24 = A2F 3 +
0x25 = 3F 3 +
0x26 = 5F 3 +
0x27 = 7F 3 +
0x28 = 9F 3
+
6221ReleaseModeint8u[val & 0xff] +
0 = Single Frame +
1 = Continuous High Speed +
3 = Continuous Low Speed +
4 = Timer +
32 = Mirror-Up +
64 = Quiet
+
6301CustomSettingsD4S---> NikonCustom SettingsD4 Tags +
(firmware version 1.00)
6338MultiSelectorLiveViewModeint8u[val >> 6 & 0x3] +
0 = Reset +
1 = Zoom +
3 = None
6378ISOAutoShutterTimeint8u[val & 0x3f] + +
0 = 1/4000 s +
1 = 1/3200 s +
2 = 1/2500 s +
3 = 1/2000 s +
4 = 1/1600 s +
5 = 1/1250 s +
6 = 1/1000 s +
7 = 1/800 s +
8 = 1/640 s +
9 = 1/500 s +
10 = 1/400 s +
11 = 1/320 s +
12 = 1/250 s +
13 = 1/200 s +
14 = 1/160 s +
15 = 1/125 s +
16 = 1/100 s +
17 = 1/80 s +
18 = 1/60 s
  19 = 1/50 s +
20 = 1/40 s +
21 = 1/30 s +
22 = 1/15 s +
23 = 1/8 s +
24 = 1/4 s +
25 = 1/2 s +
26 = 1 s +
27 = 2 s +
28 = 4 s +
29 = 8 s +
30 = 15 s +
31 = 30 s +
32 = Auto (Slowest) +
33 = Auto (Slower) +
34 = Auto +
35 = Auto (Faster) +
36 = Auto (Fastest)
+
6379ISOAutoHiLimitint8u[val & 0xff] + +
0x24 = ISO 200 +
0x26 = ISO 250 +
0x27 = ISO 280 +
0x28 = ISO 320 +
0x2a = ISO 400 +
0x2c = ISO 500 +
0x2d = ISO 560 +
0x2e = ISO 640 +
0x30 = ISO 800 +
0x32 = ISO 1000 +
0x33 = ISO 1100 +
0x34 = ISO 1250 +
0x36 = ISO 1600 +
0x38 = ISO 2000 +
0x39 = ISO 2200 +
0x3a = ISO 2500 +
0x3c = ISO 3200 +
0x3e = ISO 4000 +
0x3f = ISO 4500 +
0x40 = ISO 5000 +
0x42 = ISO 6400
  0x44 = ISO 8000 +
0x45 = ISO 9000 +
0x46 = ISO 10000 +
0x48 = ISO 12800 +
0x4a = ISO 16000 +
0x4b = ISO 18000 +
0x4c = ISO 20000 +
0x4e = ISO 25600 +
0x50 = ISO 32000 +
0x51 = ISO 36000 +
0x52 = ISO 40000 +
0x54 = ISO 51200 +
0x56 = ISO Hi 0.3 +
0x57 = ISO Hi 0.5 +
0x58 = ISO Hi 0.7 +
0x5a = ISO Hi 1.0 +
0x60 = ISO Hi 2.0 +
0x66 = ISO Hi 3.0 +
0x6c = ISO Hi 4.0 +
0x72 = ISO Hi 5.0
+
6461CustomSettingsD4S---> NikonCustom SettingsD4 Tags +
(firmware version 1.01)
13579OrientationInfo---> Nikon OrientationInfo Tags
13971Rotationint8u[val >> 4 & 0x3] +
0 = Horizontal +
1 = Rotate 270 CW +
2 = Rotate 90 CW +
3 = Rotate 180
+ +

Nikon ShotInfoD500 Tags

+

These tags are extracted from encrypted data in images from the D5 and D500.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
16RotationInfoOffset---> Nikon RotationInfoD500 Tags
20JPGInfoOffset---> Nikon JPGInfoD500 Tags
44BracketingOffset---> Nikon BracketingInfoD500 Tags
80ShootingMenuOffset---> Nikon ShootingMenuD500 Tags
88CustomSettingsOffset---> Nikon CustomSettingsD500 Tags
160OrientationOffset---> Nikon OrientationInfo Tags
168OtherOffset---> Nikon OtherInfoD500 Tags
+ +

Nikon RotationInfoD500 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
26Rotationint8u[val & 0x3] +
0 = Horizontal +
1 = Rotate 270 CW +
2 = Rotate 90 CW +
3 = Rotate 180
32Intervalint8u~ 
36IntervalFrameint8u~ 
1330FlickerReductionIndicatorint8u[val & 0x1] +
0 = On +
1 = Off
+ +

Nikon JPGInfoD500 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
36JPGCompressionint8u[val & 0x1] +
0 = Size Priority +
1 = Optimal Quality
+ +

Nikon BracketingInfoD500 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
15AEBracketingStepsint8u[val & 0xff] + +
0x0 = AE Bracketing Disabled +
0x20 = AE Bracketing Disabled +
0x30 = AE Bracketing Disabled +
0x40 = AE Bracketing Disabled +
0x50 = AE Bracketing Disabled +
0x81 = +3F0.3 +
0x82 = -3F0.3 +
0x83 = +2F0.3 +
0x84 = -2F0.3 +
0x85 = 3F0.3 +
0x86 = 5F0.3 +
0x87 = 7F0.3 +
0x88 = 9F0.3 +
0x91 = +3F0.5 +
0x92 = -3F0.5 +
0x93 = +2F0.5 +
0x94 = -2F0.5 +
0x95 = 3F0.5 +
0x96 = 5F0.5 +
0x97 = 7F0.5 +
0x98 = 9F0.5 +
0xa1 = +3F0.7 +
0xa2 = -3F0.7 +
0xa3 = +2F0.7 +
0xa4 = -2F0.7
  0xa5 = 3F0.7 +
0xa6 = 5F0.7 +
0xa7 = 7F0.7 +
0xa8 = 9F0.7 +
0xb1 = +3F1 +
0xb2 = -3F1 +
0xb3 = +2F1 +
0xb4 = -2F1 +
0xb5 = 3F1 +
0xb6 = 5F1 +
0xb7 = 7F1 +
0xb8 = 9F1 +
0xc1 = +3F2 +
0xc2 = -3F2 +
0xc3 = +2F2 +
0xc4 = -2F2 +
0xc5 = 3F2 +
0xc6 = 5F2 +
0xd1 = +3F3 +
0xd2 = -3F3 +
0xd3 = +2F3 +
0xd4 = -2F3 +
0xd5 = 3F3 +
0xd6 = 5F3
+
16WBBracketingStepsint8u[val & 0xff] + +
0x0 = WB Bracketing Disabled +
0x1 = b3F 1 +
0x2 = A3F 1 +
0x3 = b2F 1 +
0x4 = A2F 1 +
0x5 = 3F 1 +
0x6 = 5F 1 +
0x7 = 7F 1 +
0x8 = 9F 1 +
0x10 = 0F 2 +
0x11 = b3F 2 +
0x12 = A3F 2 +
0x13 = b2F 2 +
0x14 = A2F 2
  0x15 = 3F 2 +
0x16 = 5F 2 +
0x17 = 7F 2 +
0x18 = 9F 2 +
0x20 = 0F 3 +
0x21 = b3F 3 +
0x22 = A3F 3 +
0x23 = b2F 3 +
0x24 = A2F 3 +
0x25 = 3F 3 +
0x26 = 5F 3 +
0x27 = 7F 3 +
0x28 = 9F 3
+
23ADLBracketingStepint8u[val >> 4 & 0xf] + +
0 = Off +
1 = Low +
2 = Normal
  3 = High +
4 = Extra High +
8 = Auto
+
24ADLBracketingTypeint8u[val & 0xf] +
0 = Off +
1 = 2 Shots +
2 = 3 Shots +
3 = 4 Shots +
4 = 5 Shots
+ +

Nikon ShootingMenuD500 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PhotoShootingMenuBankint8u[val & 0x3] +
0 = A +
1 = B +
2 = C +
3 = D
2PrimarySlotint8u(D500 only) +
[val >> 7 & 0x1] +
0 = XQD Card +
1 = SD Card
4ISOAutoShutterTimeint8u[val & 0x3f] + +
0 = 1/4000 s +
1 = 1/3200 s +
2 = 1/2500 s +
3 = 1/2000 s +
4 = 1/1600 s +
5 = 1/1250 s +
6 = 1/1000 s +
7 = 1/800 s +
8 = 1/640 s +
9 = 1/500 s +
10 = 1/400 s +
11 = 1/320 s +
12 = 1/250 s +
13 = 1/200 s +
14 = 1/160 s +
15 = 1/125 s +
16 = 1/100 s +
17 = 1/80 s +
18 = 1/60 s
  19 = 1/50 s +
20 = 1/40 s +
21 = 1/30 s +
22 = 1/15 s +
23 = 1/8 s +
24 = 1/4 s +
25 = 1/2 s +
26 = 1 s +
27 = 2 s +
28 = 4 s +
29 = 8 s +
30 = 15 s +
31 = 30 s +
32 = Auto (Slowest) +
33 = Auto (Slower) +
34 = Auto +
35 = Auto (Faster) +
36 = Auto (Fastest)
+
5ISOAutoHiLimitint8u[val & 0xff] + +
0x24 = ISO 200 +
0x26 = ISO 250 +
0x27 = ISO 280 +
0x28 = ISO 320 +
0x2a = ISO 400 +
0x2c = ISO 500 +
0x2d = ISO 560 +
0x2e = ISO 640 +
0x30 = ISO 800 +
0x32 = ISO 1000 +
0x33 = ISO 1100 +
0x34 = ISO 1250 +
0x36 = ISO 1600 +
0x38 = ISO 2000 +
0x39 = ISO 2200 +
0x3a = ISO 2500 +
0x3c = ISO 3200 +
0x3e = ISO 4000 +
0x3f = ISO 4500 +
0x40 = ISO 5000 +
0x42 = ISO 6400
  0x44 = ISO 8000 +
0x45 = ISO 9000 +
0x46 = ISO 10000 +
0x48 = ISO 12800 +
0x4a = ISO 16000 +
0x4b = ISO 18000 +
0x4c = ISO 20000 +
0x4e = ISO 25600 +
0x50 = ISO 32000 +
0x51 = ISO 36000 +
0x52 = ISO 40000 +
0x54 = ISO 51200 +
0x56 = ISO Hi 0.3 +
0x57 = ISO Hi 0.5 +
0x58 = ISO Hi 0.7 +
0x5a = ISO Hi 1.0 +
0x60 = ISO Hi 2.0 +
0x66 = ISO Hi 3.0 +
0x6c = ISO Hi 4.0 +
0x72 = ISO Hi 5.0
+
7FlickerReductionint8u[val >> 5 & 0x1] +
0 = Enable +
1 = Disable
7.1PhotoShootingMenuBankImageAreaint8u[val & 0x7] +
0 = FX (36x24) +
1 = DX (24x16) +
2 = 5:4 (30x24) +
3 = 1.2x (30x20) +
4 = 1.3x (18x12)
+ +

Nikon CustomSettingsD500 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0CustomSettingsD5 +
CustomSettingsD500
-
-
--> NikonCustom SettingsD5 Tags +
--> NikonCustom SettingsD500 Tags
+ +

Nikon OtherInfoD500 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
532NikonMeteringModeint8u(D500 only) +
[val & 0x3] +
0 = Matrix +
1 = Center +
2 = Spot +
3 = Highlight
+ +

Nikon ShotInfoD6 Tags

+

These tags are extracted from encrypted data in images from the D6.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
48SequenceOffset---> Nikon SeqInfoD6 Tags
156OrientationOffset---> Nikon OrientationInfo Tags
164IntervalOffset---> Nikon IntervalInfoD6 Tags
+ +

Nikon SeqInfoD6 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
36IntervalShootingint16u~ 
+ +

Nikon IntervalInfoD6 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
380Intervalsint32u 
384ShotsPerIntervalint32u 
388IntervalExposureSmoothingint8u0 = Off +
1 = On
390IntervalPriorityint8u0 = Off +
1 = On
424FocusShiftNumberShotsint8u 
428FocusShiftStepWidthint8u 
432FocusShiftIntervalint8u~ 
436FocusShiftExposureLockint8u0 = Off +
1 = On
526DiffractionCompensationint8u0 = Off +
1 = On
532FlashControlModeint8u0 = TTL +
1 = Auto External Flash +
2 = GN (distance priority) +
3 = Manual +
4 = Repeating Flash
538FlashGNDistance?no + + +
0 = 0 +
1 = 0.1 m +
2 = 0.2 m +
3 = 0.3 m +
4 = 0.4 m +
5 = 0.5 m +
6 = 0.6 m +
7 = 0.7 m +
8 = 0.8 m +
9 = 0.9 m +
10 = 1.0 m +
11 = 1.1 m +
12 = 1.3 m
  13 = 1.4 m +
14 = 1.6 m +
15 = 1.8 m +
16 = 2.0 m +
17 = 2.2 m +
18 = 2.5 m +
19 = 2.8 m +
20 = 3.2 m +
21 = 3.6 m +
22 = 4.0 m +
23 = 4.5 m +
24 = 5.0 m +
25 = 5.6 m
  26 = 6.3 m +
27 = 7.1 m +
28 = 8.0 m +
29 = 9.0 m +
30 = 10.0 m +
31 = 11.0 m +
32 = 13.0 m +
33 = 14.0 m +
34 = 16.0 m +
35 = 18.0 m +
36 = 20.0 m +
255 = n/a
+
542FlashOutput?int8u 
552FlashRemoteControl?int8u0 = Group +
1 = Quick Wireless +
2 = Remote Repeating
556FlashMasterControlModeint8u0 = TTL +
1 = Manual +
2 = Auto +
3 = Off
558FlashMasterCompensation?int8s 
562FlashMasterOutput?int8u 
564FlashWirelessOption?int8u0 = Optical AWL +
1 = Off
714MovieType?int8u0 = MOV +
1 = MP4
+ +

Nikon ShotInfoD610 Tags

+

These tags are extracted from encrypted data in images from the D610.

+
+
+ + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
1999CustomSettingsD610---> NikonCustom SettingsD610 Tags
+ +

Nikon ShotInfoZ7II Tags

+

These tags are extracted from encrypted data in images from the Z7II.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
48IntervalOffset---> Nikon IntervalInfoZ7II Tags
56PortraitOffset---> Nikon PortraitInfoZ7II Tags
152OrientationOffset---> Nikon OrientationInfo Tags
160MenuOffset---> Nikon MenuInfoZ7II Tags
+ +

Nikon IntervalInfoZ7II Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
36IntervalShootingint16u~ 
+ +

Nikon PortraitInfoZ7II Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
160PortraitImpressionBalanceint8u[2]~ 
+ +

Nikon MenuInfoZ7II Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
16MenuSettingsOffsetZ7II---> Nikon MenuSettingsZ7II Tags
+ +

Nikon MenuSettingsZ7II Tags

+

These tags are used by the Z5, Z6, Z7, Z6II, Z7II, Z50 and Zfc.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
92ReleaseModeno0 = Continuous Low +
1 = Continuous High +
2 = Continuous High (Extended) +
4 = Timer +
5 = Single Frame
160IntervalDurationHoursint32u 
164IntervalDurationMinutesint32u 
168IntervalDurationSecondsint32u 
176Intervalsint32u 
180ShotsPerIntervalint32u 
184IntervalExposureSmoothingint8u0 = Off +
1 = On
186IntervalPriorityint8u0 = Off +
1 = On
220FocusShiftNumberShotsint8u 
224FocusShiftStepWidthint8u 
228FocusShiftIntervalint8u~ 
232FocusShiftExposureLockint8u0 = Off +
1 = On
322DiffractionCompensationint8u0 = Off +
1 = On
323AutoDistortionControlint8u0 = Off +
1 = On
326NikonMeteringModeint8u0 = Matrix +
1 = Center +
2 = Spot +
3 = Highlight
328FlashControlModeint8u0 = TTL +
1 = Auto External Flash +
2 = GN (distance priority) +
3 = Manual +
4 = Repeating Flash
334FlashGNDistance?no + + +
0 = 0 +
1 = 0.1 m +
2 = 0.2 m +
3 = 0.3 m +
4 = 0.4 m +
5 = 0.5 m +
6 = 0.6 m +
7 = 0.7 m +
8 = 0.8 m +
9 = 0.9 m +
10 = 1.0 m +
11 = 1.1 m +
12 = 1.3 m
  13 = 1.4 m +
14 = 1.6 m +
15 = 1.8 m +
16 = 2.0 m +
17 = 2.2 m +
18 = 2.5 m +
19 = 2.8 m +
20 = 3.2 m +
21 = 3.6 m +
22 = 4.0 m +
23 = 4.5 m +
24 = 5.0 m +
25 = 5.6 m
  26 = 6.3 m +
27 = 7.1 m +
28 = 8.0 m +
29 = 9.0 m +
30 = 10.0 m +
31 = 11.0 m +
32 = 13.0 m +
33 = 14.0 m +
34 = 16.0 m +
35 = 18.0 m +
36 = 20.0 m +
255 = n/a
+
338FlashOutput?int8u 
346FlashWirelessOption?int8u0 = Off +
1 = Optical AWL +
2 = Optical/Radio AWL +
3 = Radio AWL
348FlashRemoteControl?int8u0 = Group +
1 = Quick Wireless +
2 = Remote Repeating
352FlashMasterControlModeint8u0 = TTL +
1 = Manual +
2 = Auto +
3 = Off
354FlashMasterCompensation?int8s 
358FlashMasterOutput?int8u 
502MovieFrameSize?int8u1 = 1920x1080 +
2 = 3840x2160 +
3 = 7680x4320
504MovieFrameRate?int8u + +
0 = 120p +
1 = 100p +
2 = 60p +
3 = 50p
  4 = 30p +
5 = 25p +
6 = 24p
+
506MovieSlowMotion?int8u0 = Off +
1 = On (4x) +
2 = On (5x)
510MovieType?int8u0 = MOV +
1 = MP4
516MovieISOAutoManualMode?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
568MovieActiveD-Lighting?int8u0 = Off +
2 = Low +
3 = Normal +
4 = High +
5 = Extra High
572MovieHighISONoiseReduction?int8u0 = Off +
1 = Low +
2 = Normal +
3 = High
574MovieVignetteControl?int8u0 = Off +
1 = Low +
2 = Normal +
3 = High
576MovieVignetteControlSameAsPhoto?int8u0 = No +
1 = Yes
577MovieDiffractionCompensation?int8u0 = Off +
1 = On
578MovieAutoDistortionControl?int8u0 = Off +
1 = On
584MovieFocusMode?int8u0 = Manual +
1 = AF-S +
2 = AF-C +
4 = AF-F
590MovieVibrationReduction?int8u0 = Off +
1 = On (Normal) +
2 = On (Sport)
591MovieVibrationReductionSameAsPhoto?int8u0 = No +
1 = Yes
858HDMIOutputN-Log?int8u0 = Off +
1 = On
+ +

Nikon ShotInfoZ8 Tags

+

These tags are extracted from encrypted data in images from the Z8.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
48SequenceOffset---> Nikon SeqInfoZ9 Tags
132OrientOffset---> Nikon OrientationInfo Tags
140MenuOffset---> Nikon MenuInfoZ8 Tags
+ +

Nikon SeqInfoZ9 Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
32FocusShiftShootingint8u~ 
40IntervalShootingint16u~ 
+ +

Nikon MenuInfoZ8 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
16MenuSettingsOffsetZ8---> Nikon MenuSettingsZ8 Tags +
(Firmware versions 1.0.0)
+ +

Nikon MenuSettingsZ8 Tags

+

These tags are used by the Z8 firmware 1.00.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
72HighFrameRateint8u0 = Off +
1 = CH +
3 = C30 +
4 = C120 +
5 = C60
152MultipleExposureModeint8u0 = Off +
1 = On +
2 = On (Series)
154MultiExposureShotsint8u 
184IntervalDurationHoursint32u 
188IntervalDurationMinutesint32u 
192IntervalDurationSecondsint32u 
200Intervalsint32u 
204ShotsPerIntervalint32u 
208IntervalExposureSmoothingint8u0 = Off +
1 = On
210IntervalPriorityint8u0 = Off +
1 = On
244FocusShiftNumberShotsint8u 
248FocusShiftStepWidthint8u 
252FocusShiftIntervalint8u~ 
256FocusShiftExposureLock?int8u0 = Off +
1 = On
286PhotoShootingMenuBankint8u0 = A +
1 = B +
2 = C +
3 = D
288ExtendedMenuBanksint8u0 = Off +
1 = On
324PhotoShootingMenuBankImageAreaint8u0 = FX +
1 = DX +
4 = 16:9 +
8 = 1:1
338AutoISOint8u0 = Off +
1 = On
340ISOAutoHiLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
342ISOAutoFlashLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
350ISOAutoShutterTimeno + +
-15 = Auto +
-12 = 15 s +
-9 = 8 s +
-6 = 4 s +
-3 = 2 s +
0 = 1 s +
1 = 1/1.3 s +
2 = 1/1.6 s +
3 = 1/2 s +
4 = 1/2.5 s +
5 = 1/3 s +
6 = 1/4 s +
7 = 1/5 s +
8 = 1/6 s +
9 = 1/8 s +
10 = 1/10 s +
11 = 1/13 s +
12 = 1/15 s +
13 = 1/20 s +
14 = 1/25 s +
15 = 1/30 s +
16 = 1/40 s +
17 = 1/50 s +
18 = 1/60 s +
19 = 1/80 s
  20 = 1/100 s +
21 = 1/120 s +
22 = 1/160 s +
23 = 1/200 s +
24 = 1/250 s +
25 = 1/320 s +
26 = 1/400 s +
27 = 1/500 s +
28 = 1/640 s +
29 = 1/800 s +
30 = 1/1000 s +
31 = 1/1250 s +
32 = 1/1600 s +
33 = 1/2000 s +
34 = 1/2500 s +
35 = 1/3200 s +
36 = 1/4000 s +
37 = 1/5000 s +
37.5 = 1/6000 s +
38 = 1/6400 s +
39 = 1/8000 s +
40 = 1/10000 s +
40.5 = 1/12000 s +
41 = 1/13000 s +
42 = 1/16000 s
+
432MovieVignetteControl?int8u0 = Off +
1 = Low +
2 = Normal +
3 = High
434DiffractionCompensationint8u0 = Off +
1 = On
436FlickerReductionShootingint8u0 = Off +
1 = On
440FlashControlModeint8u0 = TTL +
1 = Auto External Flash +
2 = GN (distance priority) +
3 = Manual +
4 = Repeating Flash
548AFAreaModeint8u + +
0 = Pinpoint +
1 = Single +
2 = Dynamic +
3 = Wide (S) +
4 = Wide (L)
  5 = 3D +
6 = Auto +
11 = Subject Tracking +
12 = Wide (C1) +
13 = Wide (C2)
+
550VRModeint8u0 = Off +
1 = Normal +
2 = Sport
554BracketSetint8u0 = AE/Flash +
1 = AE +
2 = Flash +
3 = White Balance +
4 = Active-D Lighting
556BracketProgramint8u(AE and/or Flash Bracketing) + +
0 = Disabled +
2 = 2F +
3 = 3F +
4 = 4F
  5 = 5F +
7 = 7F +
9 = 9F
+
558BracketIncrementint8u(AE and/or Flash Bracketing) + + + +
0 = 0.3 +
1 = 0.5 +
2 = 0.7
  3 = 1.0 +
4 = 2.0 +
5 = 3.0
  6 = 1.3 +
7 = 1.5 +
8 = 1.7
  9 = 2.3 +
10 = 2.5 +
11 = 2.7
+
570HDRint8u0 = Off +
1 = On +
2 = On (Series)
576SecondarySlotFunctionint8u0 = Overflow +
1 = Backup +
2 = NEF Primary + JPG Secondary +
3 = JPG Primary + JPG Secondary
582HDRLevelint8u0 = Auto +
1 = Extra High +
2 = High +
3 = Normal +
4 = Low
586Slot2JpgSize?int8u0 = Large (8256x5504) +
1 = Medium (6192x4128) +
2 = Small (4128x2752)
592DXCropAlertint8u0 = Off +
1 = On
594SubjectDetectionint8u + +
0 = Off +
1 = Auto +
2 = People
  3 = Animals +
4 = Vehicles +
6 = Airplanes
+
596DynamicAFAreaSizeint8u0 = Small +
1 = Medium +
2 = Large
618ToneMap?int8u0 = SDR +
1 = HLG
622PortraitImpressionBalanceint8u0 = Off +
1 = Mode 1 +
2 = Mode 2 +
3 = Mode 3
636HighFrequencyFlickerReductionShooting?int8u0 = Off +
1 = On
730MovieImageArea?int8u[val & 0x1] +
0 = FX +
1 = DX
740MovieType?int8u +
1 = H.265 8-bit (MP4) +
2 = H.265 8-bit (MOV) +
3 = H.265 10-bit (MOV) +
4 = ProRes 422 HQ 10-bit (MOV) +
5 = ProRes RAW HQ 12-bit (MOV) +
6 = NRAW 12-bit (NEV)
+
742MovieISOAutoHiLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
744MovieISOAutoControlManualMode?int8u0 = Off +
1 = On
746MovieISOAutoManualMode?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
820MovieActiveD-Lighting?int8u0 = Off +
2 = Low +
3 = Normal +
4 = High +
5 = Extra High
822MovieHighISONoiseReduction?int8u0 = Off +
1 = Low +
2 = Normal +
3 = High
828MovieFlickerReductionint8u0 = Auto +
1 = 50Hz +
2 = 60Hz
830MovieMeteringMode?int8u0 = Matrix +
1 = Center +
2 = Spot +
3 = Highlight
832MovieFocusMode?int8u0 = Manual +
1 = AF-S +
2 = AF-C +
4 = AF-F
834MovieAFAreaModeint8u + +
0 = Pinpoint +
1 = Single +
2 = Dynamic +
3 = Wide (S) +
4 = Wide (L)
  5 = 3D +
6 = Auto +
11 = Subject Tracking +
12 = Wide (C1) +
13 = Wide (C2)
+
836MovieVRMode?int8u0 = Off +
1 = Normal +
2 = Sport
840MovieElectronicVR?int8u0 = Off +
1 = On
842MovieSoundRecording?int8u0 = Off +
1 = Auto +
2 = Manual
844MicrophoneSensitivity?int8u 
846MicrophoneAttenuator?int8u0 = Off +
1 = On
848MicrophoneFrequencyResponse?int8u0 = Wide Range +
1 = Vocal Range
850WindNoiseReduction?int8u0 = Off +
1 = On
882MovieFrameSize?int8u1 = 1920x1080 +
2 = 3840x2160 +
3 = 7680x4320
884MovieFrameRate?int8u + +
0 = 120p +
1 = 100p +
2 = 60p +
3 = 50p
  4 = 30p +
5 = 25p +
6 = 24p
+
886MicrophoneJackPower?int8u0 = Off +
1 = On
887MovieDXCropAlert?int8u0 = Off +
1 = On
888MovieSubjectDetection?int8u + +
0 = Off +
1 = Auto +
2 = People
  3 = Animals +
4 = Vehicles +
6 = Airplanes
+
896MovieHighResZoom?int8u0 = Off +
1 = On
943CustomSettingsZ8---> NikonCustom SettingsZ8 Tags
1682Language?int8u4 = English +
5 = Spanish +
7 = French +
15 = Portuguese
1684TimeZoneint8u +
3 = +10:00 (Sydney) +
5 = +09:00 (Tokyo) +
6 = +08:00 (Beijing, Honk Kong, Sinapore) +
10 = +05:45 (Kathmandu) +
11 = +05:30 (New Dehli) +
12 = +05:00 (Islamabad) +
13 = +04:30 (Kabul) +
14 = +04:00 (Abu Dhabi) +
15 = +03:30 (Tehran) +
16 = +03:00 (Moscow, Nairobi) +
17 = +02:00 (Athens, Helsinki) +
18 = +01:00 (Madrid, Paris, Berlin) +
19 = +00:00 (London) +
20 = -01:00 (Azores) +
21 = -02:00 (Fernando de Noronha) +
22 = -03:00 (Buenos Aires, Sao Paulo) +
23 = -03:30 (Newfoundland) +
24 = -04:00 (Manaus, Caracas) +
25 = -05:00 (New York, Toronto, Lima) +
26 = -06:00 (Chicago, Mexico City) +
27 = -07:00 (Denver) +
28 = -08:00 (Los Angeles, Vancouver) +
29 = -09:00 (Anchorage) +
30 = -10:00 (Hawaii)
+
1690MonitorBrightness?int8u + + + + +
0 = -5 +
1 = -4 +
2 = -3
  3 = -2 +
4 = -1 +
5 = 0
  6 = 1 +
7 = 2 +
8 = 3
  9 = 4 +
10 = 5 +
14 = Hi1
  15 = Hi2 +
16 = Lo2 +
17 = Lo1
+
1712AFFineTune?int8u0 = Off +
1 = On
1716NonCPULens1FocalLength?int16u~ 
1718NonCPULens2FocalLength?int16u~ 
1720NonCPULens3FocalLength?int16u~ 
1722NonCPULens4FocalLength?int16u~ 
1724NonCPULens5FocalLength?int16u~ 
1726NonCPULens6FocalLength?int16u~ 
1728NonCPULens7FocalLength?int16u~ 
1730NonCPULens8FocalLength?int16u~ 
1732NonCPULens9FocalLength?int16u~ 
1734NonCPULens10FocalLength?int16u~ 
1736NonCPULens11FocalLength?int16u~ 
1738NonCPULens12FocalLength?int16u~ 
1740NonCPULens13FocalLength?int16u~ 
1742NonCPULens14FocalLength?int16u~ 
1744NonCPULens15FocalLength?int16u~ 
1746NonCPULens16FocalLength?int16u~ 
1748NonCPULens17FocalLength?int16u~ 
1750NonCPULens18FocalLength?int16u~ 
1752NonCPULens19FocalLength?int16u~ 
1754NonCPULens20FocalLength?int16u~ 
1756NonCPULens1MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1758NonCPULens2MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1760NonCPULens3MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1762NonCPULens4MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1764NonCPULens5MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1766NonCPULens6MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1768NonCPULens7MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1770NonCPULens8MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1772NonCPULens9MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1774NonCPULens10MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1776NonCPULens11MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1778NonCPULens12MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1780NonCPULens13MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1782NonCPULens14MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1784NonCPULens15MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1786NonCPULens16MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1788NonCPULens17MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1790NonCPULens18MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1792NonCPULens19MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1794NonCPULens20MaxAperture?int16u + + +
12 = f/1.2 +
24 = f/1.4 +
40 = f/1.8 +
48 = f/2.0 +
64 = f/2.5 +
72 = f/2.8 +
84 = f/3.3 +
88 = f/3.5
  96 = f/4.0 +
104 = f/4.5 +
112 = f/5.0 +
120 = f/5.6 +
128 = f/6.3 +
136 = f/7.1 +
144 = f/8 +
156 = f/9.5
  168 = f/11 +
180 = f/13 +
188 = f/15 +
192 = f/16 +
204 = f/19 +
216 = f/22 +
313 = N/A
+
1808HDMIOutputResolutionint8u0 = Auto +
1 = 4320p +
2 = 2160p +
3 = 1080p +
5 = 720p
1826AirplaneMode?int8u0 = Off +
1 = On
1827EmptySlotRelease?int8u0 = Disable Release +
1 = Enable Release
1862EnergySavingMode?int8u0 = Off +
1 = On
1890USBPowerDelivery?int8u0 = Off +
1 = On
1899SensorShield?int8u0 = Stays Open +
1 = Closes
+ +

Nikon ShotInfoZ9 Tags

+

These tags are extracted from encrypted data in images from the Z9.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IndexTag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
48SequenceOffset---> Nikon SeqInfoZ9 Tags
88Offset13---> Nikon Offset13InfoZ9 Tags
128AutoCaptureOffset---> Nikon AutoCaptureInfo Tags
132OrientOffset---> Nikon OrientationInfo Tags
140MenuOffset---> Nikon MenuInfoZ9 Tags
+ +

Nikon Offset13InfoZ9 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3048AFAreaInitialXPositionint8s~ 
3049AFAreaInitialYPositionint8s~ 
3050AFAreaInitialWidthno 
3051AFAreaInitialHeightno 
+ +

Nikon AutoCaptureInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AutoCapturedFrameint8u0 = No +
5 = Yes
1AutoCaptureCriteriaint8u~Bit 0 = Distance +
Bit 1 = Motion +
Bit 2 = Subject Detection
55AutoCaptureRecordingTimeint8u + + +
0 = 1 Sec +
1 = 3 Sec +
2 = 5 Sec +
4 = 30 Sec +
5 = No Limit
  6 = 2 Sec +
7 = 10 Sec +
8 = 20 Sec +
9 = 1 Min +
10 = 3 Min
  11 = 5 Min +
12 = 10 Min +
13 = 30 Min
+
56AutoCaptureWaitTimeint8u + + +
0 = No Wait +
1 = 10 Sec +
2 = 30 Sec +
3 = 1 Min +
4 = 5 Min
  5 = 10 Min +
6 = 30 Min +
7 = 1 Sec +
8 = 2 Sec +
9 = 3 Sec
  10 = 5 Sec +
11 = 20 Sec +
12 = 3 Min
+
74AutoCaptureDistanceFarint8u~ 
78AutoCaptureDistanceNearint8u~ 
95AutoCaptureCriteriaMotionDirectionint8u~Bit 0 = Top Left +
Bit 1 = Top Right +
Bit 2 = Bottom Left +
Bit 3 = Bottom Right +
Bit 4 = Left +
Bit 5 = Right +
Bit 6 = Top Center +
Bit 7 = Bottom Center
99AutoCaptureCriteriaMotionSpeedint8u 
100AutoCaptureCriteriaMotionSizeint8u 
105AutoCaptureCriteriaSubjectSizeint8u 
106AutoCaptureCriteriaSubjectTypeint8u0 = Auto (all) +
1 = People +
2 = Animals +
3 = Vehicle
+ +

Nikon MenuInfoZ9 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
16MenuSettingsOffsetZ9 +
MenuSettingsOffsetZ9v3 +
MenuSettingsOffsetZ9v4
-
-
-
(Firmware versions 2.11 and earlier) +
--> Nikon MenuSettingsZ9 Tags +
(Firmware versions 3.0 and v3.10) +
--> Nikon MenuSettingsZ9v3 Tags +
(Firmware versions 4.0 and later) +
--> Nikon MenuSettingsZ9v4 Tags
+ +

Nikon MenuSettingsZ9 Tags

+

These tags are used by the Z9.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
140MultipleExposureModeint8u0 = Off +
1 = On +
2 = On (Series)
142MultiExposureShotsint8u 
188Intervalsint32u 
192ShotsPerIntervalint32u 
232FocusShiftNumberShotsint8u 
236FocusShiftStepWidthint8u 
240FocusShiftIntervalint8u~ 
244FocusShiftExposureLock?int8u0 = Off +
1 = On
274PhotoShootingMenuBankint8u0 = A +
1 = B +
2 = C +
3 = D
276ExtendedMenuBanksint8u0 = Off +
1 = On
308PhotoShootingMenuBankImageAreaint8u0 = FX +
1 = DX +
4 = 16:9 +
8 = 1:1
322AutoISOint8u0 = Off +
1 = On
324ISOAutoHiLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
326ISOAutoFlashLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
334ISOAutoShutterTimeno + +
-15 = Auto +
-12 = 15 s +
-9 = 8 s +
-6 = 4 s +
-3 = 2 s +
0 = 1 s +
1 = 1/1.3 s +
2 = 1/1.6 s +
3 = 1/2 s +
4 = 1/2.5 s +
5 = 1/3 s +
6 = 1/4 s +
7 = 1/5 s +
8 = 1/6 s +
9 = 1/8 s +
10 = 1/10 s +
11 = 1/13 s +
12 = 1/15 s +
13 = 1/20 s +
14 = 1/25 s +
15 = 1/30 s +
16 = 1/40 s +
17 = 1/50 s +
18 = 1/60 s +
19 = 1/80 s
  20 = 1/100 s +
21 = 1/120 s +
22 = 1/160 s +
23 = 1/200 s +
24 = 1/250 s +
25 = 1/320 s +
26 = 1/400 s +
27 = 1/500 s +
28 = 1/640 s +
29 = 1/800 s +
30 = 1/1000 s +
31 = 1/1250 s +
32 = 1/1600 s +
33 = 1/2000 s +
34 = 1/2500 s +
35 = 1/3200 s +
36 = 1/4000 s +
37 = 1/5000 s +
37.5 = 1/6000 s +
38 = 1/6400 s +
39 = 1/8000 s +
40 = 1/10000 s +
40.5 = 1/12000 s +
41 = 1/13000 s +
42 = 1/16000 s
+
416MovieVignetteControl?int8u0 = Off +
1 = Low +
2 = Normal +
3 = High
418DiffractionCompensationint8u0 = Off +
1 = On
420FlickerReductionShootingint8u0 = Off +
1 = On
424FlashControlModeint8u0 = TTL +
1 = Auto External Flash +
2 = GN (distance priority) +
3 = Manual +
4 = Repeating Flash
426FlashMasterCompensation?int8s 
430FlashGNDistance?no + + +
0 = 0 +
1 = 0.1 m +
2 = 0.2 m +
3 = 0.3 m +
4 = 0.4 m +
5 = 0.5 m +
6 = 0.6 m +
7 = 0.7 m +
8 = 0.8 m +
9 = 0.9 m +
10 = 1.0 m +
11 = 1.1 m +
12 = 1.3 m
  13 = 1.4 m +
14 = 1.6 m +
15 = 1.8 m +
16 = 2.0 m +
17 = 2.2 m +
18 = 2.5 m +
19 = 2.8 m +
20 = 3.2 m +
21 = 3.6 m +
22 = 4.0 m +
23 = 4.5 m +
24 = 5.0 m +
25 = 5.6 m
  26 = 6.3 m +
27 = 7.1 m +
28 = 8.0 m +
29 = 9.0 m +
30 = 10.0 m +
31 = 11.0 m +
32 = 13.0 m +
33 = 14.0 m +
34 = 16.0 m +
35 = 18.0 m +
36 = 20.0 m +
255 = n/a
+
434FlashOutput?int8u 
444FlashRemoteControl?int8u0 = Group +
1 = Quick Wireless +
2 = Remote Repeating
456FlashWirelessOption?int8u0 = Off +
1 = Optical AWL +
2 = Optical/Radio AWL +
3 = Radio AWL
528AFAreaModeint8u + +
0 = Pinpoint +
1 = Single +
2 = Dynamic +
3 = Wide (S) +
4 = Wide (L)
  5 = 3D +
6 = Auto +
11 = Subject Tracking +
12 = Wide (C1) +
13 = Wide (C2)
+
530VRModeint8u0 = Off +
1 = Normal +
2 = Sport
534BracketSetint8u0 = AE/Flash +
1 = AE +
2 = Flash +
3 = White Balance +
4 = Active-D Lighting
536BracketProgramint8u(AE and/or Flash Bracketing) + +
0 = Disabled +
2 = 2F +
3 = 3F +
4 = 4F
  5 = 5F +
7 = 7F +
9 = 9F
+
538BracketIncrementint8u(AE and/or Flash Bracketing) + + + +
0 = 0.3 +
1 = 0.5 +
2 = 0.7
  3 = 1.0 +
4 = 2.0 +
5 = 3.0
  6 = 1.3 +
7 = 1.5 +
8 = 1.7
  9 = 2.3 +
10 = 2.5 +
11 = 2.7
+
556SecondarySlotFunctionint8u0 = Overflow +
1 = Backup +
2 = NEF Primary + JPG Secondary +
3 = JPG Primary + JPG Secondary
572DXCropAlertint8u0 = Off +
1 = On
574SubjectDetectionint8u + +
0 = Off +
1 = Auto +
2 = People
  3 = Animals +
4 = Vehicles +
6 = Airplanes
+
576DynamicAFAreaSizeint8u0 = Small +
1 = Medium +
2 = Large
604MovieImageArea?int8u[val & 0x1] +
0 = FX +
1 = DX
614MovieType?int8u +
1 = H.265 8-bit (MP4) +
2 = H.265 8-bit (MOV) +
3 = H.265 10-bit (MOV) +
4 = ProRes 422 HQ 10-bit (MOV) +
5 = ProRes RAW HQ 12-bit (MOV) +
6 = NRAW 12-bit (NEV)
+
616MovieISOAutoHiLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
618MovieISOAutoControlManualMode?int8u0 = Off +
1 = On
620MovieISOAutoManualMode?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
696MovieActiveD-Lighting?int8u0 = Off +
2 = Low +
3 = Normal +
4 = High +
5 = Extra High
698MovieHighISONoiseReduction?int8u0 = Off +
1 = Low +
2 = Normal +
3 = High
704MovieFlickerReductionint8u0 = Auto +
1 = 50Hz +
2 = 60Hz
706MovieMeteringMode?int8u0 = Matrix +
1 = Center +
2 = Spot +
3 = Highlight
708MovieFocusMode?int8u0 = Manual +
1 = AF-S +
2 = AF-C +
4 = AF-F
710MovieAFAreaModeint8u + +
0 = Pinpoint +
1 = Single +
2 = Dynamic +
3 = Wide (S) +
4 = Wide (L)
  5 = 3D +
6 = Auto +
11 = Subject Tracking +
12 = Wide (C1) +
13 = Wide (C2)
+
712MovieVRMode?int8u0 = Off +
1 = Normal +
2 = Sport
716MovieElectronicVR?int8u0 = Off +
1 = On
718MovieSoundRecording?int8u0 = Off +
1 = Auto +
2 = Manual
720MicrophoneSensitivity?int8u 
722MicrophoneAttenuator?int8u0 = Off +
1 = On
724MicrophoneFrequencyResponse?int8u0 = Wide Range +
1 = Vocal Range
726WindNoiseReduction?int8u0 = Off +
1 = On
748MovieToneMap?int8u0 = SDR +
1 = HLG +
2 = N-Log
754MovieFrameSize?int8u1 = 1920x1080 +
2 = 3840x2160 +
3 = 7680x4320
756MovieFrameRate?int8u + +
0 = 120p +
1 = 100p +
2 = 60p +
3 = 50p
  4 = 30p +
5 = 25p +
6 = 24p
+
762MicrophoneJackPower?int8u0 = Off +
1 = On
763MovieDXCropAlert?int8u0 = Off +
1 = On
764MovieSubjectDetection?int8u + +
0 = Off +
1 = Auto +
2 = People
  3 = Animals +
4 = Vehicles +
6 = Airplanes
+
799CustomSettingsZ9---> NikonCustom SettingsZ9 Tags
1426Language?int8u4 = English +
5 = Spanish +
7 = French +
15 = Portuguese
1428TimeZoneint8u +
3 = +10:00 (Sydney) +
5 = +09:00 (Tokyo) +
6 = +08:00 (Beijing, Honk Kong, Sinapore) +
10 = +05:45 (Kathmandu) +
11 = +05:30 (New Dehli) +
12 = +05:00 (Islamabad) +
13 = +04:30 (Kabul) +
14 = +04:00 (Abu Dhabi) +
15 = +03:30 (Tehran) +
16 = +03:00 (Moscow, Nairobi) +
17 = +02:00 (Athens, Helsinki) +
18 = +01:00 (Madrid, Paris, Berlin) +
19 = +00:00 (London) +
20 = -01:00 (Azores) +
21 = -02:00 (Fernando de Noronha) +
22 = -03:00 (Buenos Aires, Sao Paulo) +
23 = -03:30 (Newfoundland) +
24 = -04:00 (Manaus, Caracas) +
25 = -05:00 (New York, Toronto, Lima) +
26 = -06:00 (Chicago, Mexico City) +
27 = -07:00 (Denver) +
28 = -08:00 (Los Angeles, Vancouver) +
29 = -09:00 (Anchorage) +
30 = -10:00 (Hawaii)
+
1434MonitorBrightness?no 
1456AFFineTune?int8u0 = Off +
1 = On
1552HDMIOutputResolutionint8u0 = Auto +
1 = 4320p +
2 = 2160p +
3 = 1080p +
5 = 720p
1565SetClockFromLocationData?int8u0 = Off +
1 = On
1572AirplaneMode?int8u0 = Off +
1 = On
1573EmptySlotRelease?int8u0 = Disable Release +
1 = Enable Release
1608EnergySavingMode?int8u0 = Off +
1 = On
1632RecordLocationData?int8u0 = Off +
1 = On
1636USBPowerDelivery?int8u0 = Off +
1 = On
1645SensorShield?int8u0 = Stays Open +
1 = Closes
+ +

Nikon MenuSettingsZ9v3 Tags

+

These tags are used by the Z9 firmware 3.00.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
72HighFrameRateint8u0 = Off +
1 = CH +
3 = C30 +
4 = C120 +
5 = C60
154MultipleExposureModeint8u0 = Off +
1 = On +
2 = On (Series)
156MultiExposureShotsint8u 
204Intervalsint32u 
208ShotsPerIntervalint32u 
248FocusShiftNumberShotsint8u 
252FocusShiftStepWidthint8u 
256FocusShiftIntervalint8u~ 
260FocusShiftExposureLock?int8u0 = Off +
1 = On
290PhotoShootingMenuBankint8u0 = A +
1 = B +
2 = C +
3 = D
292ExtendedMenuBanksint8u0 = Off +
1 = On
328PhotoShootingMenuBankImageAreaint8u0 = FX +
1 = DX +
4 = 16:9 +
8 = 1:1
342AutoISOint8u0 = Off +
1 = On
344ISOAutoHiLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
346ISOAutoFlashLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
354ISOAutoShutterTimeno + +
-15 = Auto +
-12 = 15 s +
-9 = 8 s +
-6 = 4 s +
-3 = 2 s +
0 = 1 s +
1 = 1/1.3 s +
2 = 1/1.6 s +
3 = 1/2 s +
4 = 1/2.5 s +
5 = 1/3 s +
6 = 1/4 s +
7 = 1/5 s +
8 = 1/6 s +
9 = 1/8 s +
10 = 1/10 s +
11 = 1/13 s +
12 = 1/15 s +
13 = 1/20 s +
14 = 1/25 s +
15 = 1/30 s +
16 = 1/40 s +
17 = 1/50 s +
18 = 1/60 s +
19 = 1/80 s
  20 = 1/100 s +
21 = 1/120 s +
22 = 1/160 s +
23 = 1/200 s +
24 = 1/250 s +
25 = 1/320 s +
26 = 1/400 s +
27 = 1/500 s +
28 = 1/640 s +
29 = 1/800 s +
30 = 1/1000 s +
31 = 1/1250 s +
32 = 1/1600 s +
33 = 1/2000 s +
34 = 1/2500 s +
35 = 1/3200 s +
36 = 1/4000 s +
37 = 1/5000 s +
37.5 = 1/6000 s +
38 = 1/6400 s +
39 = 1/8000 s +
40 = 1/10000 s +
40.5 = 1/12000 s +
41 = 1/13000 s +
42 = 1/16000 s
+
436MovieVignetteControl?int8u0 = Off +
1 = Low +
2 = Normal +
3 = High
438DiffractionCompensationint8u0 = Off +
1 = On
440FlickerReductionShootingint8u0 = Off +
1 = On
444FlashControlModeint8u0 = TTL +
1 = Auto External Flash +
2 = GN (distance priority) +
3 = Manual +
4 = Repeating Flash
446FlashMasterCompensation?int8s 
450FlashGNDistance?no + + +
0 = 0 +
1 = 0.1 m +
2 = 0.2 m +
3 = 0.3 m +
4 = 0.4 m +
5 = 0.5 m +
6 = 0.6 m +
7 = 0.7 m +
8 = 0.8 m +
9 = 0.9 m +
10 = 1.0 m +
11 = 1.1 m +
12 = 1.3 m
  13 = 1.4 m +
14 = 1.6 m +
15 = 1.8 m +
16 = 2.0 m +
17 = 2.2 m +
18 = 2.5 m +
19 = 2.8 m +
20 = 3.2 m +
21 = 3.6 m +
22 = 4.0 m +
23 = 4.5 m +
24 = 5.0 m +
25 = 5.6 m
  26 = 6.3 m +
27 = 7.1 m +
28 = 8.0 m +
29 = 9.0 m +
30 = 10.0 m +
31 = 11.0 m +
32 = 13.0 m +
33 = 14.0 m +
34 = 16.0 m +
35 = 18.0 m +
36 = 20.0 m +
255 = n/a
+
454FlashOutput?int8u 
548AFAreaModeint8u + +
0 = Pinpoint +
1 = Single +
2 = Dynamic +
3 = Wide (S) +
4 = Wide (L)
  5 = 3D +
6 = Auto +
11 = Subject Tracking +
12 = Wide (C1) +
13 = Wide (C2)
+
550VRModeint8u0 = Off +
1 = Normal +
2 = Sport
554BracketSetint8u0 = AE/Flash +
1 = AE +
2 = Flash +
3 = White Balance +
4 = Active-D Lighting
556BracketProgramint8u(AE and/or Flash Bracketing) + +
0 = Disabled +
2 = 2F +
3 = 3F +
4 = 4F
  5 = 5F +
7 = 7F +
9 = 9F
+
558BracketIncrementint8u(AE and/or Flash Bracketing) + + + +
0 = 0.3 +
1 = 0.5 +
2 = 0.7
  3 = 1.0 +
4 = 2.0 +
5 = 3.0
  6 = 1.3 +
7 = 1.5 +
8 = 1.7
  9 = 2.3 +
10 = 2.5 +
11 = 2.7
+
576SecondarySlotFunctionint8u0 = Overflow +
1 = Backup +
2 = NEF Primary + JPG Secondary +
3 = JPG Primary + JPG Secondary
592DXCropAlertint8u0 = Off +
1 = On
594SubjectDetectionint8u + +
0 = Off +
1 = Auto +
2 = People
  3 = Animals +
4 = Vehicles +
6 = Airplanes
+
596DynamicAFAreaSizeint8u0 = Small +
1 = Medium +
2 = Large
636HighFrequencyFlickerReductionShooting?int8u0 = Off +
1 = On
646MovieImageArea?int8u[val & 0x1] +
0 = FX +
1 = DX
656MovieType?int8u +
1 = H.265 8-bit (MP4) +
2 = H.265 8-bit (MOV) +
3 = H.265 10-bit (MOV) +
4 = ProRes 422 HQ 10-bit (MOV) +
5 = ProRes RAW HQ 12-bit (MOV) +
6 = NRAW 12-bit (NEV)
+
658MovieISOAutoHiLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
660MovieISOAutoControlManualMode?int8u0 = Off +
1 = On
662MovieISOAutoManualMode?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
736MovieActiveD-Lighting?int8u0 = Off +
2 = Low +
3 = Normal +
4 = High +
5 = Extra High
738MovieHighISONoiseReduction?int8u0 = Off +
1 = Low +
2 = Normal +
3 = High
744MovieFlickerReductionint8u0 = Auto +
1 = 50Hz +
2 = 60Hz
746MovieMeteringMode?int8u0 = Matrix +
1 = Center +
2 = Spot +
3 = Highlight
748MovieFocusMode?int8u0 = Manual +
1 = AF-S +
2 = AF-C +
4 = AF-F
750MovieAFAreaModeint8u + +
0 = Pinpoint +
1 = Single +
2 = Dynamic +
3 = Wide (S) +
4 = Wide (L)
  5 = 3D +
6 = Auto +
11 = Subject Tracking +
12 = Wide (C1) +
13 = Wide (C2)
+
752MovieVRMode?int8u0 = Off +
1 = Normal +
2 = Sport
756MovieElectronicVR?int8u0 = Off +
1 = On
758MovieSoundRecording?int8u0 = Off +
1 = Auto +
2 = Manual
760MicrophoneSensitivity?int8u 
762MicrophoneAttenuator?int8u0 = Off +
1 = On
764MicrophoneFrequencyResponse?int8u0 = Wide Range +
1 = Vocal Range
766WindNoiseReduction?int8u0 = Off +
1 = On
788MovieToneMap?int8u0 = SDR +
1 = HLG +
2 = N-Log
794MovieFrameSize?int8u1 = 1920x1080 +
2 = 3840x2160 +
3 = 7680x4320
796MovieFrameRate?int8u + +
0 = 120p +
1 = 100p +
2 = 60p +
3 = 50p
  4 = 30p +
5 = 25p +
6 = 24p
+
802MicrophoneJackPower?int8u0 = Off +
1 = On
803MovieDXCropAlert?int8u0 = Off +
1 = On
804MovieSubjectDetection?int8u + +
0 = Off +
1 = Auto +
2 = People
  3 = Animals +
4 = Vehicles +
6 = Airplanes
+
812MovieHighResZoom?int8u0 = Off +
1 = On
847CustomSettingsZ9---> NikonCustom SettingsZ9 Tags
1474Language?int8u4 = English +
5 = Spanish +
7 = French +
15 = Portuguese
1476TimeZoneint8u +
3 = +10:00 (Sydney) +
5 = +09:00 (Tokyo) +
6 = +08:00 (Beijing, Honk Kong, Sinapore) +
10 = +05:45 (Kathmandu) +
11 = +05:30 (New Dehli) +
12 = +05:00 (Islamabad) +
13 = +04:30 (Kabul) +
14 = +04:00 (Abu Dhabi) +
15 = +03:30 (Tehran) +
16 = +03:00 (Moscow, Nairobi) +
17 = +02:00 (Athens, Helsinki) +
18 = +01:00 (Madrid, Paris, Berlin) +
19 = +00:00 (London) +
20 = -01:00 (Azores) +
21 = -02:00 (Fernando de Noronha) +
22 = -03:00 (Buenos Aires, Sao Paulo) +
23 = -03:30 (Newfoundland) +
24 = -04:00 (Manaus, Caracas) +
25 = -05:00 (New York, Toronto, Lima) +
26 = -06:00 (Chicago, Mexico City) +
27 = -07:00 (Denver) +
28 = -08:00 (Los Angeles, Vancouver) +
29 = -09:00 (Anchorage) +
30 = -10:00 (Hawaii)
+
1482MonitorBrightness?int8u + + + + +
0 = -5 +
1 = -4 +
2 = -3
  3 = -2 +
4 = -1 +
5 = 0
  6 = 1 +
7 = 2 +
8 = 3
  9 = 4 +
10 = 5 +
14 = Hi1
  15 = Hi2 +
16 = Lo2 +
17 = Lo1
+
1504AFFineTune?int8u0 = Off +
1 = On
1600HDMIOutputResolutionint8u0 = Auto +
1 = 4320p +
2 = 2160p +
3 = 1080p +
5 = 720p
1613SetClockFromLocationData?int8u0 = Off +
1 = On
1620AirplaneMode?int8u0 = Off +
1 = On
1621EmptySlotRelease?int8u0 = Disable Release +
1 = Enable Release
1656EnergySavingMode?int8u0 = Off +
1 = On
1680RecordLocationData?int8u0 = Off +
1 = On
1684USBPowerDelivery?int8u0 = Off +
1 = On
1693SensorShield?int8u0 = Stays Open +
1 = Closes
1754FocusShiftAutoReset?int8u0 = Off +
1 = On
1810PreReleaseBurstLengthint8u0 = None +
1 = 0.3 Sec +
2 = 0.5 Sec +
3 = 1 Sec
1812PostReleaseBurstLengthint8u0 = 1 Sec +
1 = 2 Sec +
2 = 3 Sec +
3 = Max
+ +

Nikon MenuSettingsZ9v4 Tags

+

These tags are used by the Z9 firmware 3.00.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
72HighFrameRateint8u0 = Off +
1 = CH +
3 = C30 +
4 = C120 +
5 = C60
154MultipleExposureModeint8u0 = Off +
1 = On +
2 = On (Series)
156MultiExposureShotsint8u 
204Intervalsint32u 
208ShotsPerIntervalint32u 
248FocusShiftNumberShotsint8u 
252FocusShiftStepWidthint8u 
256FocusShiftIntervalint8u~ 
260FocusShiftExposureLock?int8u0 = Off +
1 = On
290PhotoShootingMenuBankint8u0 = A +
1 = B +
2 = C +
3 = D
292ExtendedMenuBanksint8u0 = Off +
1 = On
328PhotoShootingMenuBankImageAreaint8u0 = FX +
1 = DX +
4 = 16:9 +
8 = 1:1
342AutoISOint8u0 = Off +
1 = On
344ISOAutoHiLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
346ISOAutoFlashLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
354ISOAutoShutterTimeno + +
-15 = Auto +
-12 = 15 s +
-9 = 8 s +
-6 = 4 s +
-3 = 2 s +
0 = 1 s +
1 = 1/1.3 s +
2 = 1/1.6 s +
3 = 1/2 s +
4 = 1/2.5 s +
5 = 1/3 s +
6 = 1/4 s +
7 = 1/5 s +
8 = 1/6 s +
9 = 1/8 s +
10 = 1/10 s +
11 = 1/13 s +
12 = 1/15 s +
13 = 1/20 s +
14 = 1/25 s +
15 = 1/30 s +
16 = 1/40 s +
17 = 1/50 s +
18 = 1/60 s +
19 = 1/80 s
  20 = 1/100 s +
21 = 1/120 s +
22 = 1/160 s +
23 = 1/200 s +
24 = 1/250 s +
25 = 1/320 s +
26 = 1/400 s +
27 = 1/500 s +
28 = 1/640 s +
29 = 1/800 s +
30 = 1/1000 s +
31 = 1/1250 s +
32 = 1/1600 s +
33 = 1/2000 s +
34 = 1/2500 s +
35 = 1/3200 s +
36 = 1/4000 s +
37 = 1/5000 s +
37.5 = 1/6000 s +
38 = 1/6400 s +
39 = 1/8000 s +
40 = 1/10000 s +
40.5 = 1/12000 s +
41 = 1/13000 s +
42 = 1/16000 s
+
436MovieVignetteControl?int8u0 = Off +
1 = Low +
2 = Normal +
3 = High
438DiffractionCompensationint8u0 = Off +
1 = On
440FlickerReductionShootingint8u0 = Off +
1 = On
444FlashControlModeint8u0 = TTL +
1 = Auto External Flash +
2 = GN (distance priority) +
3 = Manual +
4 = Repeating Flash
446FlashMasterCompensation?int8s 
450FlashGNDistance?no + + +
0 = 0 +
1 = 0.1 m +
2 = 0.2 m +
3 = 0.3 m +
4 = 0.4 m +
5 = 0.5 m +
6 = 0.6 m +
7 = 0.7 m +
8 = 0.8 m +
9 = 0.9 m +
10 = 1.0 m +
11 = 1.1 m +
12 = 1.3 m
  13 = 1.4 m +
14 = 1.6 m +
15 = 1.8 m +
16 = 2.0 m +
17 = 2.2 m +
18 = 2.5 m +
19 = 2.8 m +
20 = 3.2 m +
21 = 3.6 m +
22 = 4.0 m +
23 = 4.5 m +
24 = 5.0 m +
25 = 5.6 m
  26 = 6.3 m +
27 = 7.1 m +
28 = 8.0 m +
29 = 9.0 m +
30 = 10.0 m +
31 = 11.0 m +
32 = 13.0 m +
33 = 14.0 m +
34 = 16.0 m +
35 = 18.0 m +
36 = 20.0 m +
255 = n/a
+
454FlashOutput?int8u 
548AFAreaModeint8u + +
0 = Pinpoint +
1 = Single +
2 = Dynamic +
3 = Wide (S) +
4 = Wide (L)
  5 = 3D +
6 = Auto +
11 = Subject Tracking +
12 = Wide (C1) +
13 = Wide (C2)
+
550VRModeint8u0 = Off +
1 = Normal +
2 = Sport
554BracketSetint8u0 = AE/Flash +
1 = AE +
2 = Flash +
3 = White Balance +
4 = Active-D Lighting
556BracketProgramint8u(AE and/or Flash Bracketing) + +
0 = Disabled +
2 = 2F +
3 = 3F +
4 = 4F
  5 = 5F +
7 = 7F +
9 = 9F
+
558BracketIncrementint8u(AE and/or Flash Bracketing) + + + +
0 = 0.3 +
1 = 0.5 +
2 = 0.7
  3 = 1.0 +
4 = 2.0 +
5 = 3.0
  6 = 1.3 +
7 = 1.5 +
8 = 1.7
  9 = 2.3 +
10 = 2.5 +
11 = 2.7
+
570HDRint8u0 = Off +
1 = On +
2 = On (Series)
576SecondarySlotFunctionint8u0 = Overflow +
1 = Backup +
2 = NEF Primary + JPG Secondary +
3 = JPG Primary + JPG Secondary
582HDRLevelint8u0 = Auto +
1 = Extra High +
2 = High +
3 = Normal +
4 = Low
586Slot2JpgSize?int8u0 = Large (8256x5504) +
1 = Medium (6192x4128) +
2 = Small (4128x2752)
592DXCropAlertint8u0 = Off +
1 = On
594SubjectDetectionint8u + +
0 = Off +
1 = Auto +
2 = People
  3 = Animals +
4 = Vehicles +
6 = Airplanes
+
596DynamicAFAreaSizeint8u0 = Small +
1 = Medium +
2 = Large
636HighFrequencyFlickerReductionShooting?int8u0 = Off +
1 = On
646MovieImageArea?int8u[val & 0x1] +
0 = FX +
1 = DX
656MovieType?int8u +
1 = H.265 8-bit (MP4) +
2 = H.265 8-bit (MOV) +
3 = H.265 10-bit (MOV) +
4 = ProRes 422 HQ 10-bit (MOV) +
5 = ProRes RAW HQ 12-bit (MOV) +
6 = NRAW 12-bit (NEV)
+
658MovieISOAutoHiLimit?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
660MovieISOAutoControlManualMode?int8u0 = Off +
1 = On
662MovieISOAutoManualMode?int16u + +
0 = ISO 64 +
1 = ISO 80 +
2 = ISO 100 +
3 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
7 = ISO 320 +
8 = ISO 400 +
9 = ISO 500 +
10 = ISO 640 +
11 = ISO 800 +
12 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000
  16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 5000 +
20 = ISO 6400 +
21 = ISO 8000 +
22 = ISO 10000 +
23 = ISO 12800 +
24 = ISO 16000 +
25 = ISO 20000 +
26 = ISO 25600 +
27 = ISO Hi 0.3 +
28 = ISO Hi 0.7 +
29 = ISO Hi 1.0 +
32 = ISO Hi 2.0
+
736MovieActiveD-Lighting?int8u0 = Off +
2 = Low +
3 = Normal +
4 = High +
5 = Extra High
738MovieHighISONoiseReduction?int8u0 = Off +
1 = Low +
2 = Normal +
3 = High
744MovieFlickerReductionint8u0 = Auto +
1 = 50Hz +
2 = 60Hz
746MovieMeteringMode?int8u0 = Matrix +
1 = Center +
2 = Spot +
3 = Highlight
748MovieFocusMode?int8u0 = Manual +
1 = AF-S +
2 = AF-C +
4 = AF-F
750MovieAFAreaModeint8u + +
0 = Pinpoint +
1 = Single +
2 = Dynamic +
3 = Wide (S) +
4 = Wide (L)
  5 = 3D +
6 = Auto +
11 = Subject Tracking +
12 = Wide (C1) +
13 = Wide (C2)
+
752MovieVRMode?int8u0 = Off +
1 = Normal +
2 = Sport
756MovieElectronicVR?int8u0 = Off +
1 = On
758MovieSoundRecording?int8u0 = Off +
1 = Auto +
2 = Manual
760MicrophoneSensitivity?int8u 
762MicrophoneAttenuator?int8u0 = Off +
1 = On
764MicrophoneFrequencyResponse?int8u0 = Wide Range +
1 = Vocal Range
766WindNoiseReduction?int8u0 = Off +
1 = On
788MovieToneMap?int8u0 = SDR +
1 = HLG +
2 = N-Log
794MovieFrameSize?int8u1 = 1920x1080 +
2 = 3840x2160 +
3 = 7680x4320
796MovieFrameRate?int8u + +
0 = 120p +
1 = 100p +
2 = 60p +
3 = 50p
  4 = 30p +
5 = 25p +
6 = 24p
+
802MicrophoneJackPower?int8u0 = Off +
1 = On
803MovieDXCropAlert?int8u0 = Off +
1 = On
804MovieSubjectDetection?int8u + +
0 = Off +
1 = Auto +
2 = People
  3 = Animals +
4 = Vehicles +
6 = Airplanes
+
812MovieHighResZoom?int8u0 = Off +
1 = On
847CustomSettingsZ9v4---> NikonCustom SettingsZ9v4 Tags
1498Language?int8u4 = English +
5 = Spanish +
7 = French +
15 = Portuguese
1500TimeZoneint8u +
3 = +10:00 (Sydney) +
5 = +09:00 (Tokyo) +
6 = +08:00 (Beijing, Honk Kong, Sinapore) +
10 = +05:45 (Kathmandu) +
11 = +05:30 (New Dehli) +
12 = +05:00 (Islamabad) +
13 = +04:30 (Kabul) +
14 = +04:00 (Abu Dhabi) +
15 = +03:30 (Tehran) +
16 = +03:00 (Moscow, Nairobi) +
17 = +02:00 (Athens, Helsinki) +
18 = +01:00 (Madrid, Paris, Berlin) +
19 = +00:00 (London) +
20 = -01:00 (Azores) +
21 = -02:00 (Fernando de Noronha) +
22 = -03:00 (Buenos Aires, Sao Paulo) +
23 = -03:30 (Newfoundland) +
24 = -04:00 (Manaus, Caracas) +
25 = -05:00 (New York, Toronto, Lima) +
26 = -06:00 (Chicago, Mexico City) +
27 = -07:00 (Denver) +
28 = -08:00 (Los Angeles, Vancouver) +
29 = -09:00 (Anchorage) +
30 = -10:00 (Hawaii)
+
1506MonitorBrightness?int8u + + + + +
0 = -5 +
1 = -4 +
2 = -3
  3 = -2 +
4 = -1 +
5 = 0
  6 = 1 +
7 = 2 +
8 = 3
  9 = 4 +
10 = 5 +
14 = Hi1
  15 = Hi2 +
16 = Lo2 +
17 = Lo1
+
1528AFFineTune?int8u0 = Off +
1 = On
1532NonCPULens1FocalLength?int16s~ 
1536NonCPULens2FocalLength?int16s~ 
1540NonCPULens3FocalLength?int16s~ 
1544NonCPULens4FocalLength?int16s~ 
1548NonCPULens5FocalLength?int16s~ 
1552NonCPULens6FocalLength?int16s~ 
1556NonCPULens7FocalLength?int16s~ 
1560NonCPULens8FocalLength?int16s~ 
1564NonCPULens9FocalLength?int16s~ 
1568NonCPULens10FocalLength?int16s~ 
1572NonCPULens11FocalLength?int16s~ 
1576NonCPULens12FocalLength?int16s~ 
1580NonCPULens13FocalLength?int16s~ 
1584NonCPULens14FocalLength?int16s~ 
1588NonCPULens15FocalLength?int16s~ 
1592NonCPULens16FocalLength?int16s~ 
1596NonCPULens17FocalLength?int16s~ 
1600NonCPULens18FocalLength?int16s~ 
1604NonCPULens19FocalLength?int16s~ 
1608NonCPULens20FocalLength?int16s~ 
1612NonCPULens1MaxAperture?int16s~ 
1616NonCPULens2MaxAperture?int16s~ 
1620NonCPULens3MaxAperture?int16s~ 
1624NonCPULens4MaxAperture?int16s~ 
1628NonCPULens5MaxAperture?int16s~ 
1632NonCPULens6MaxAperture?int16s~ 
1636NonCPULens7MaxAperture?int16s~ 
1640NonCPULens8MaxAperture?int16s~ 
1644NonCPULens9MaxAperture?int16s~ 
1648NonCPULens10MaxAperture?int16s~ 
1652NonCPULens11MaxAperture?int16s~ 
1656NonCPULens12MaxAperture?int16s~ 
1660NonCPULens13MaxAperture?int16s~ 
1664NonCPULens14MaxAperture?int16s~ 
1668NonCPULens15MaxAperture?int16s~ 
1672NonCPULens16MaxAperture?int16s~ 
1676NonCPULens17MaxAperture?int16s~ 
1680NonCPULens18MaxAperture?int16s~ 
1684NonCPULens19MaxAperture?int16s~ 
1688NonCPULens20MaxAperture?int16s~ 
1704HDMIOutputResolutionint8u0 = Auto +
1 = 4320p +
2 = 2160p +
3 = 1080p +
5 = 720p
1717SetClockFromLocationData?int8u0 = Off +
1 = On
1724AirplaneMode?int8u0 = Off +
1 = On
1725EmptySlotRelease?int8u0 = Disable Release +
1 = Enable Release
1760EnergySavingMode?int8u0 = Off +
1 = On
1784RecordLocationData?int8u0 = Off +
1 = On
1788USBPowerDelivery?int8u0 = Off +
1 = On
1797SensorShield?int8u0 = Stays Open +
1 = Closes
1862AutoCapturePresetint8u0 = 1 +
1 = 2 +
2 = 3 +
3 = 4 +
4 = 5
1864FocusShiftAutoReset?int8u0 = Off +
1 = On
1922PreReleaseBurstLengthint8u0 = None +
1 = 0.3 Sec +
2 = 0.5 Sec +
3 = 1 Sec
1924PostReleaseBurstLengthint8u0 = 1 Sec +
1 = 2 Sec +
2 = 3 Sec +
3 = Max
1938VerticalISOButtonint8u--> Nikon ButtonsZ9 Values
1940ExposureCompensationButtonint8u--> Nikon ButtonsZ9 Values
1942ISOButtonint8u--> Nikon ButtonsZ9 Values
2002ViewModeShowEffectsOfSettings?int8u0 = Always +
1 = Only When Flash Not Used
2004DispButtonint8u--> Nikon ButtonsZ9 Values
2048ExposureDelayfixed32u~ 
2056PlaybackButtonint8u--> Nikon ButtonsZ9 Values
2058WBButtonint8u--> Nikon ButtonsZ9 Values
2060BracketButtonint8u--> Nikon ButtonsZ9 Values
2062FlashModeButtonint8u--> Nikon ButtonsZ9 Values
2064LensFunc1ButtonPlaybackModeint8u--> Nikon ButtonsZ9 Values
2066LensFunc2ButtonPlaybackModeint8u--> Nikon ButtonsZ9 Values
2068PlaybackButtonPlaybackModeint8u--> Nikon ButtonsZ9 Values
2070BracketButtonPlaybackModeint8u--> Nikon ButtonsZ9 Values
2072FlashModeButtonPlaybackModeint8u--> Nikon ButtonsZ9 Values
+ +

Nikon ButtonsZ9 Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueButtonsZ9ValueButtonsZ9
0= None49= Power Aperture (Close)
1= Preview52= Microphone Sensitivity
3= FV Lock53= Release Mode
4= AE/AF Lock57= Preset Focus Point
5= AE Lock Only58= AE/AWB Lock (hold)
6= AE Lock (reset on release)59= AF-AreaMode
7= AE Lock (hold)60= AF-AreaMode + AF-On
8= AF Lock Only61= Recall Shooting Functions
9= AF-On64= Filtered Playback
10= Flash Disable/Enable65= Same as AF-On
11= Bracketing Burst66= Voice Memo
12= +NEF(RAW)70= Photo Shooting Bank
18= Virtual Horizon71= ISO
19= Synchronized Release73= Exposure Compensation
20= My Menu76= Silent Mode
21= My Menu Top Item78= LiveView Information
22= Playback79= AWB Lock (hold)
23= Rating80= Grid Display
24= Protect81= Starlight View
25= Zoom82= Select To Send (PC)
26= Focus Peaking83= Select To Send (FTP)
27= Flash Mode/Compensation84= Pattern Tone Range
28= Image Area85= Control Lock
30= Non-CPU Lens86= Save Focus Position
31= Active-D Lighting87= Recall Focus Position
32= Exposure Delay Mode88= Recall Shooting Functions (Hold)
33= 1 Stop Speed/Aperture97= High Frequency Flicker Reduction
34= White Balance98= Switch FX/DX
35= Metering99= View Mode (Photo LV)
36= Auto Bracketing100= Photo Flicker Reduction
37= Multiple Exposure101= Filtered Playback (Select Criteria)
38= HDR Overlay103= Start Series Playback
39= Picture Control104= View Assist
40= Quality105= Hi-Res Zoom+
41= Focus Mode/AF AreaMode106= Hi-Res Zoom-
42= Select Center Focus Point108= Override Other Cameras
44= Record Movie109= DISP - Cycle Information Display (shooting)
45= Thumbnail On/Off110= DISP - Cycle Information Display (playback)
46= View Histograms111= Resume Shooting
47= Choose Folder112= Switch Eyes
48= Power Aperture (Open)115= Delete
+ +

Nikon ShotInfo Tags

+

This information is encrypted for ShotInfoVersion 02xx, and some tags are +only valid for specific models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ShotInfoVersionno 
4FirmwareVersionno 
16DistortionControlint8u(P6000) +
0 = Off +
1 = On
102VR_0x66?int8u(D2X, D2Xs (unverified)) +
0 = Off +
1 = On (normal) +
2 = On (active)
106ShutterCountint32u(D2X, D2Xs)
110DeletedImageCountint32u(D2X, D2Xs)
117VibrationReductionint8u(D200) +
0 = Off +
1 = On (1) +
2 = On (2) +
3 = On (3)
130VibrationReductionint8u(D2X, D2Xs) +
0 = Off +
1 = On
343ShutterCountundef[2](D50)
430VibrationReductionint8u(D50) +
0x0 = n/a +
0xc = Off +
0xf = On
589ShutterCountint32u(D60)
+ +

Nikon ColorBalance1 Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
0WB_RBGGLevelsint16u[4]! 
+ +

Nikon ColorBalance2 Tags

+

This information is encrypted for most camera models.

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
0WB_RGGBLevelsint16u[4]! 
+ +

Nikon ColorBalance3 Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
0WB_RGBGLevelsint16u[4]! 
+ +

Nikon ColorBalance4 Tags

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
0WB_GRBGLevelsint16u[4]! 
+ +

Nikon ColorBalanceUnknown Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0ColorBalanceVersionundef[4] 
+ +

Nikon LensData00 Tags

+

This structure is used by the D100, and D1X with firmware version 1.1.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensDataVersionno 
6LensIDNumberint8u(see LensID values below)
7LensFStopsint8u 
8MinFocalLengthint8u 
9MaxFocalLengthint8u 
10MaxApertureAtMinFocalint8u 
11MaxApertureAtMaxFocalint8u 
12MCUVersionint8u 
+ +

Nikon LensData01 Tags

+

Nikon encrypts the LensData information below if LensDataVersion is 0201 or +higher, but the decryption algorithm is known so the information can be +extracted.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensDataVersionno 
4ExitPupilPositionint8u 
5AFApertureint8u 
8FocusPositionint8u 
9FocusDistanceint8u(this focus distance is approximate, and not very accurate for some lenses)
10FocalLengthint8u 
11LensIDNumberint8u(see LensID values below)
12LensFStopsint8u 
13MinFocalLengthint8u 
14MaxFocalLengthint8u 
15MaxApertureAtMinFocalint8u 
16MaxApertureAtMaxFocalint8u 
17MCUVersionint8u 
18EffectiveMaxApertureint8u 
+ +

Nikon LensData0204 Tags

+

Nikon encrypts the LensData information below if LensDataVersion is 0201 or +higher, but the decryption algorithm is known so the information can be +extracted.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensDataVersionno 
4ExitPupilPositionint8u 
5AFApertureint8u 
8FocusPositionint8u 
10FocusDistanceint8u(this focus distance is approximate, and not very accurate for some lenses)
11FocalLengthint8u 
12LensIDNumberint8u(see LensID values below)
13LensFStopsint8u 
14MinFocalLengthint8u 
15MaxFocalLengthint8u 
16MaxApertureAtMinFocalint8u 
17MaxApertureAtMaxFocalint8u 
18MCUVersionint8u 
19EffectiveMaxApertureint8u 
+ +

Nikon LensData0400 Tags

+

Tags extracted from the encrypted lens data of the Nikon 1J1/1V1/1J2.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensDataVersionno 
394LensModelstring[64] 
+ +

Nikon LensData0402 Tags

+

Tags extracted from the encrypted lens data of the Nikon 1J3/1S1/1V2.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensDataVersionno 
395LensModelstring[64] 
+ +

Nikon LensData0403 Tags

+

Tags extracted from the encrypted lens data of the Nikon 1J4/1J5.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensDataVersionno 
684LensModelstring[64] 
+ +

Nikon LensData0800 Tags

+

Tags found in the encrypted LensData from cameras such as the Z6 and Z7.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensDataVersionno 
4ExitPupilPositionint8u 
5AFApertureint8u 
11FocusDistanceint8u(this focus distance is approximate, and not very accurate for some lenses)
12FocalLengthint8u 
13LensIDNumberint8u(see LensID values below)
14LensFStopsint8u 
15MinFocalLengthint8u 
16MaxFocalLengthint8u 
17MaxApertureAtMinFocalint8u 
18MaxApertureAtMaxFocalint8u 
19MCUVersionint8u 
20EffectiveMaxApertureint8u 
47NewLensDataundef[17] 
48LensIDint16u(tags from here onward used for Nikkor Z lenses only) +
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 +
24 = Nikkor Z MC 105mm f/2.8 VR S +
25 = Nikkor Z 40mm f/2 +
26 = Nikkor Z DX 18-140mm f/3.5-6.3 VR +
27 = Nikkor Z MC 50mm f/2.8 +
28 = Nikkor Z 100-400mm f/4.5-5.6 VR S +
29 = Nikkor Z 28mm f/2.8 +
30 = Nikkor Z 400mm f/2.8 TC VR S +
31 = Nikkor Z 24-120 f/4 +
32 = Nikkor Z 800mm f/6.3 VR S +
35 = Nikkor Z 28-75mm f/2.8 +
36 = Nikkor Z 400mm f/4.5 VR S +
37 = Nikkor Z 600mm f/4 TC VR S +
38 = Nikkor Z 85mm f/1.2 S +
39 = Nikkor Z 17-28mm f/2.8 +
32768 = Nikkor Z 400mm f/2.8 TC VR S TC-1.4x +
32769 = Nikkor Z 600mm f/4 TC VR S TC-1.4x
+
53LensMountTypeint8u0 = F-mount Lens +
1 = Z-mount Lens
54MaxApertureint16u 
56FNumberint16u 
60FocalLengthint16u 
76FocusDistanceRangeWidth?int8u 
78FocusDistanceint16u~ 
86LensDriveEnd?int8u 
88FocusStepsFromInfinity?int8u 
90LensPositionAbsoluteint32s 
+ +

Nikon LensDataUnknown Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensDataVersionno 
+ +

Nikon FlashInfo0100 Tags

+

These tags are used by the D2H, D2Hs, D2X, D2Xs, D50, D70, D70s, D80 and +D200.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FlashInfoVersionno 
4FlashSourceint8u0 = None +
1 = External +
2 = Internal
6ExternalFlashFirmwareint8u[2]--> Nikon FlashFirmware Values
8ExternalFlashFlagsint8u0x0 = (none) +
Bit 0 = Fired +
Bit 2 = Bounce Flash +
Bit 4 = Wide Flash Adapter +
Bit 5 = Dome Diffuser
9.1FlashCommanderModeint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
9.2FlashControlModeint8u[val & 0x7f] +
--> Nikon FlashControlMode Values
10FlashOutput +
FlashCompensation
int8u
int8s
 
11FlashFocalLengthint8u 
12RepeatingFlashRateint8u 
13RepeatingFlashCountint8u 
14FlashGNDistanceint8u--> Nikon FlashGNDistance Values
15FlashGroupAControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values
16FlashGroupBControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values
17FlashGroupAOutput +
FlashGroupACompensation
int8u
int8s
 
18FlashGroupBOutput +
FlashGroupBCompensation
int8u
int8s
 
+ +

Nikon FlashFirmware Values

+
+
+ + + + + + + + + + + + + + + + + + +
ValueFlashFirmwareValueFlashFirmware
'0 0'= n/a'4 2'= 4.02 (SB-400)
'1 1'= 1.01 (SB-800 or Metz 58 AF-1)'4 4'= 4.04 (SB-400)
'1 3'= 1.03 (SB-800)'5 1'= 5.01 (SB-900)
'2 1'= 2.01 (SB-800)'5 2'= 5.02 (SB-900)
'2 4'= 2.04 (SB-600)'6 1'= 6.01 (SB-700)
'2 5'= 2.05 (SB-600)'7 1'= 7.01 (SB-910)
'3 1'= 3.01 (SU-800 Remote Commander)'14 3'= 14.03 (SB-5000)
'4 1'= 4.01 (SB-400)  
+ +

Nikon FlashControlMode Values

+
+
+ + + + + + + + + + + +
ValueFlashControlModeValueFlashControlModeValueFlashControlMode
0= Off3= Auto Aperture6= Manual
1= iTTL-BL4= Automatic7= Repeating Flash
2= iTTL5= GN (distance priority)  
+ +

Nikon FlashGNDistance Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueFlashGNDistanceValueFlashGNDistanceValueFlashGNDistance
0= 013= 1.4 m26= 6.3 m
1= 0.1 m14= 1.6 m27= 7.1 m
2= 0.2 m15= 1.8 m28= 8.0 m
3= 0.3 m16= 2.0 m29= 9.0 m
4= 0.4 m17= 2.2 m30= 10.0 m
5= 0.5 m18= 2.5 m31= 11.0 m
6= 0.6 m19= 2.8 m32= 13.0 m
7= 0.7 m20= 3.2 m33= 14.0 m
8= 0.8 m21= 3.6 m34= 16.0 m
9= 0.9 m22= 4.0 m35= 18.0 m
10= 1.0 m23= 4.5 m36= 20.0 m
11= 1.1 m24= 5.0 m255= n/a
12= 1.3 m25= 5.6 m  
+ +

Nikon FlashInfo0102 Tags

+

These tags are used by the D3 (firmware 1.x), D40, D40X, D60 and D300 +(firmware 1.00).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FlashInfoVersionno 
4FlashSourceint8u0 = None +
1 = External +
2 = Internal
6ExternalFlashFirmwareint8u[2]--> Nikon FlashFirmware Values
8ExternalFlashFlagsint8uBit 0 = Fired +
Bit 2 = Bounce Flash +
Bit 4 = Wide Flash Adapter +
Bit 5 = Dome Diffuser
9.1FlashCommanderModeint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
9.2FlashControlModeint8u[val & 0x7f] +
--> Nikon FlashControlMode Values
10FlashOutput +
FlashCompensation
int8u
int8s
 
12FlashFocalLengthint8u 
13RepeatingFlashRateint8u 
14RepeatingFlashCountint8u 
15FlashGNDistanceint8u--> Nikon FlashGNDistance Values
16.1FlashGroupAControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values +
(note: group A tags may apply to the built-in flash settings for some models)
17.1FlashGroupBControlModeint8u[val >> 4 & 0xf] +
--> Nikon FlashControlMode Values +
(note: group B tags may apply to group A settings for some models)
17.2FlashGroupCControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values +
(note: group C tags may apply to group B settings for some models)
18FlashGroupAOutput +
FlashGroupACompensation
int8u
int8s
 
19FlashGroupBOutput +
FlashGroupBCompensation
int8u
int8s
 
20FlashGroupCOutput +
FlashGroupCCompensation
int8u
int8s
 
+ +

Nikon FlashInfo0103 Tags

+

These tags are used by the D3 (firmware 2.x), D3X, D3S, D4, D90, D300 +(firmware 1.10), D300S, D600, D700, D800, D3000, D3100, D3200, D5000, D5100, +D5200, D7000.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FlashInfoVersionno 
4FlashSourceint8u0 = None +
1 = External +
2 = Internal
6ExternalFlashFirmwareint8u[2]--> Nikon FlashFirmware Values
8ExternalFlashFlagsint8uBit 0 = Fired +
Bit 2 = Bounce Flash +
Bit 4 = Wide Flash Adapter +
Bit 5 = Dome Diffuser
9.1FlashCommanderModeint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
9.2FlashControlModeint8u[val & 0x7f] +
--> Nikon FlashControlMode Values
10FlashOutput +
FlashCompensation
int8u
int8s
 
12FlashFocalLengthint8u 
13RepeatingFlashRateint8u 
14RepeatingFlashCountint8u 
15FlashGNDistanceint8u--> Nikon FlashGNDistance Values
16FlashColorFilterint8u--> Nikon FlashColorFilter Values
17.1FlashGroupAControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values +
(note: group A tags may apply to the built-in flash settings for some models)
18.1FlashGroupBControlModeint8u[val >> 4 & 0xf] +
--> Nikon FlashControlMode Values +
(note: group B tags may apply to group A settings for some models)
18.2FlashGroupCControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values +
(note: group C tags may apply to group B settings for some models)
19FlashGroupAOutput +
FlashGroupACompensation
int8u
int8s
 
20FlashGroupBOutput +
FlashGroupBCompensation
int8u
int8s
 
21FlashGroupCOutput +
FlashGroupCCompensation
int8u
int8s
 
27ExternalFlashCompensationint8s 
29FlashExposureComp3int8s(does not include the effect of flash bracketing)
39FlashExposureComp4int8s(includes the effect of flash bracketing. Valid for repeating flash)
+ +

Nikon FlashColorFilter Values

+
+
+ + + + + + + + + + + + + + +
ValueFlashColorFilterValueFlashColorFilterValueFlashColorFilter
0= None10= TN-A268= Amber
1= FL-GL1 or SZ-2FL Fluorescent65= Red128= Incandescent
2= FL-GL266= Blue  
9= TN-A1 or SZ-2TN Incandescent67= Yellow  
+ +

Nikon FlashInfo0106 Tags

+

These tags are used by the Df, D610, D3300, D5300, D7100 and Coolpix A.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FlashInfoVersionno 
4FlashSourceint8u0 = None +
1 = External +
2 = Internal
6ExternalFlashFirmwareint8u[2]--> Nikon FlashFirmware Values
8ExternalFlashFlagsint8uBit 0 = Fired +
Bit 2 = Bounce Flash +
Bit 4 = Wide Flash Adapter +
Bit 5 = Dome Diffuser
9.1FlashCommanderModeint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
9.2FlashControlModeint8u[val & 0x7f] +
--> Nikon FlashControlMode Values
12FlashFocalLengthint8u(only valid if flash pattern is "Standard Illumination")
13RepeatingFlashRateint8u 
14RepeatingFlashCountint8u 
15FlashGNDistanceint8u--> Nikon FlashGNDistance Values
16FlashColorFilterint8u--> Nikon FlashColorFilter Values
17.1FlashGroupAControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values
18.1FlashGroupBControlModeint8u[val >> 4 & 0xf] +
--> Nikon FlashControlMode Values
18.2FlashGroupCControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values
39FlashOutput +
FlashCompensation
int8u
int8s
 
40FlashGroupAOutput +
FlashGroupACompensation
int8u
int8s
 
41FlashGroupBOutput +
FlashGroupBCompensation
int8u
int8s
 
42FlashGroupCOutput +
FlashGroupCCompensation
int8u
int8s
 
+ +

Nikon FlashInfo0107 Tags

+

These tags are used by the D4S, D750, D810, D5500, D7200 (FlashInfoVersion +0107) and the D5, D500, D850 and D3400 (FlashInfoVersion 0108).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FlashInfoVersionno 
4FlashSourceint8u0 = None +
1 = External +
2 = Internal
6ExternalFlashFirmwareint8u[2]--> Nikon FlashFirmware Values
8.1ExternalFlashZoomOverrideint8u(indicates that the user has overridden the flash zoom distance) +
[val >> 7 & 0x1] +
0 = No +
1 = Yes
8.2ExternalFlashStatusint8u[val & 0x1] +
0 = Flash Not Attached +
1 = Flash Attached
9.1ExternalFlashReadyStateint8u[val & 0x7] +
0 = n/a +
1 = Ready +
6 = Not Ready
10FlashCompensationint8s 
12FlashFocalLengthint8u(only valid if flash pattern is "Standard Illumination")
13RepeatingFlashRateint8u 
14RepeatingFlashCountint8u 
15FlashGNDistanceint8u--> Nikon FlashGNDistance Values
17.1FlashGroupAControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values +
(note: group A tags may apply to the built-in flash settings for some models)
18.1FlashGroupBControlModeint8u[val >> 4 & 0xf] +
--> Nikon FlashControlMode Values +
(note: group B tags may apply to group A settings for some models)
18.2FlashGroupCControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values +
(note: group C tags may apply to group B settings for some models)
40FlashGroupAOutput +
FlashGroupACompensation
int8u
int8s
 
41FlashGroupBOutput +
FlashGroupBCompensation
int8u
int8s
 
42FlashGroupCOutput +
FlashGroupCCompensation
int8u
int8s
 
+ +

Nikon FlashInfo0300 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FlashInfoVersionno 
4FlashSourceint8u0 = None +
1 = External +
2 = Internal
6ExternalFlashFirmwareint8u[2]--> Nikon FlashFirmware Values
8ExternalFlashFlagsint8uBit 0 = Flash Ready +
Bit 2 = Bounce Flash +
Bit 4 = Wide Flash Adapter +
Bit 7 = Zoom Override
9.1FlashCommanderModeint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
9.2FlashControlModeint8u[val & 0x7f] +
--> Nikon FlashControlMode Values
10FlashCompensationint8s 
13RepeatingFlashRateint8u 
14RepeatingFlashCountint8u 
15FlashGNDistanceint8u--> Nikon FlashGNDistance Values
16FlashColorFilterint8u--> Nikon FlashColorFilter Values
17.1FlashGroupAControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values +
(note: group A tags may apply to the built-in flash settings for some models)
18.1FlashGroupBControlModeint8u[val >> 4 & 0xf] +
--> Nikon FlashControlMode Values +
(note: group B tags may apply to group A settings for some models)
18.2FlashGroupCControlModeint8u[val & 0xf] +
--> Nikon FlashControlMode Values +
(note: group C tags may apply to group B settings for some models)
33FlashOutputint8u 
37FlashIlluminationPatternint8u0 = Standard +
1 = Center-weighted +
2 = Even
38FlashFocalLengthint8u(only valid if flash pattern is "Standard Illumination")
40FlashGroupAOutput +
FlashGroupACompensation
int8u
int8s
 
41FlashGroupBOutput +
FlashGroupBCompensation
int8u
int8s
 
42FlashGroupCOutput +
FlashGroupCCompensation
int8u
int8s
 
+ +

Nikon FlashInfoUnknown Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0FlashInfoVersionno 
+ +

Nikon MultiExposure Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0MultiExposureVersionno 
1MultiExposureModeint32u0 = Off +
1 = Multiple Exposure +
2 = Image Overlay +
3 = HDR
2MultiExposureShotsint32u 
3MultiExposureAutoGainint32u0 = Off +
1 = On
+ +

Nikon MultiExposure2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0MultiExposureVersionno 
1MultiExposureModeint32u0 = Off +
1 = Multiple Exposure +
3 = HDR
2MultiExposureShotsint32u 
3MultiExposureOverlayModeint32u0 = Add +
1 = Average +
2 = Light +
3 = Dark
+ +

Nikon AFInfo2V0400 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AFInfo2Versionno 
5AFAreaModeint8u + +
192 = Pinpoint +
193 = Single +
195 = Wide (S) +
196 = Wide (L) +
197 = Auto
  204 = Dynamic Area (S) +
205 = Dynamic Area (M) +
206 = Dynamic Area (L) +
207 = 3D-tracking +
208 = Wide (C1/C2)
+
10AFPointsUsedundef[51] 
62AFImageWidthint16u 
64AFImageHeightint16u 
66AFAreaXPositionint16u 
67FocusPositionHorizontalint8u~(the focus points form a 29x17 grid, but the X,Y coordinate values run from 1,1 +to 30,19. The horizontal coordinate 11R (5) and the vertical coordinates 6U +(4) and 2D (12) are not used for some reason)
68AFAreaYPositionint16u 
69FocusPositionVerticalint8u~ 
70AFAreaWidthint16u(size of AF area in AFImage pixels)
72AFAreaHeightint16u 
74FocusResultint8u0 = Out of Focus +
1 = Focus
+ +

Nikon AFInfo2 Tags

+

These tags are written by Nikon DSLR's which have the live view feature.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AFInfo2Versionno 
4ContrastDetectAFint8u(this is Off for the hybrid AF used in Nikon 1 models) +
0 = Off +
1 = On +
2 = On (2)
5AFAreaModeint8u(ContrastDetectAF Off) +
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
+(ContrastDetectAF On) +
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 +
204 = Dynamic Area (S) +
205 = Dynamic Area (M) +
206 = Dynamic Area (L) +
207 = 3D-tracking +
208 = Wide-Area (C1/C2)
+
6PhaseDetectAFint8u(PrimaryAFPoint and AFPointsUsed below are only valid when this is On) + +
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)
+
7PrimaryAFPointint8u(models with 51-point AF -- 5 rows (A-E) and 11 columns (1-11): D3, D3S, D3X, +D4, D4S, D300, D300S, D700, D800, D800e and D810) + + + + +
0 = (none) +
1 = C6 (Center) +
2 = B6 +
3 = A5 +
4 = D6 +
5 = E5 +
6 = C7 +
7 = B7 +
8 = A6 +
9 = D7 +
10 = E6
  11 = C5 +
12 = B5 +
13 = A4 +
14 = D5 +
15 = E4 +
16 = C8 +
17 = B8 +
18 = A7 +
19 = D8 +
20 = E7 +
21 = C9
  22 = B9 +
23 = A8 +
24 = D9 +
25 = E8 +
26 = C10 +
27 = B10 +
28 = A9 +
29 = D10 +
30 = E9 +
31 = C11 +
32 = B11
  33 = D11 +
34 = C4 +
35 = B4 +
36 = A3 +
37 = D4 +
38 = E3 +
39 = C3 +
40 = B3 +
41 = A2 +
42 = D3 +
43 = E2
  44 = C2 +
45 = B2 +
46 = A1 +
47 = D2 +
48 = E1 +
49 = C1 +
50 = B1 +
51 = D1
+(models with 11-point AF: D90, D3000, D3100, D5000 and D5100) + +
0 = (none) +
1 = Center +
2 = Top +
3 = Bottom +
4 = Mid-left +
5 = Upper-left
  6 = Lower-left +
7 = Far Left +
8 = Mid-right +
9 = Upper-right +
10 = Lower-right +
11 = Far Right
+(models with 39-point AF: D600 and D7000) + + + + +
0 = (none) +
1 = C6 (Center) +
2 = B6 +
3 = A2 +
4 = D6 +
5 = E2 +
6 = C7 +
7 = B7
  8 = A3 +
9 = D7 +
10 = E3 +
11 = C5 +
12 = B5 +
13 = A1 +
14 = D5 +
15 = E1
  16 = C8 +
17 = B8 +
18 = D8 +
19 = C9 +
20 = B9 +
21 = D9 +
22 = C10 +
23 = B10
  24 = D10 +
25 = C11 +
26 = B11 +
27 = D11 +
28 = C4 +
29 = B4 +
30 = D4 +
31 = C3
  32 = B3 +
33 = D3 +
34 = C2 +
35 = B2 +
36 = D2 +
37 = C1 +
38 = B1 +
39 = D1
+(Nikon 1 models with older 135-point AF and 73-point phase-detect AF) + + + + +
0 = (none) +
1 = E8 (Center) +
2 = D8 +
3 = C8 +
4 = B8 +
5 = A8 +
6 = F8 +
7 = G8 +
8 = H8 +
9 = I8 +
10 = E9 +
11 = D9 +
12 = C9 +
13 = B9 +
14 = A9 +
15 = F9 +
16 = G9 +
17 = H9 +
18 = I9 +
19 = E7 +
20 = D7 +
21 = C7 +
22 = B7 +
23 = A7 +
24 = F7 +
25 = G7 +
26 = H7 +
27 = I7
  28 = E10 +
29 = D10 +
30 = C10 +
31 = B10 +
32 = A10 +
33 = F10 +
34 = G10 +
35 = H10 +
36 = I10 +
37 = E11 +
38 = D11 +
39 = C11 +
40 = B11 +
41 = A11 +
42 = F11 +
43 = G11 +
44 = H11 +
45 = I11 +
46 = E12 +
47 = D12 +
48 = C12 +
49 = B12 +
50 = A12 +
51 = F12 +
52 = G12 +
53 = H12 +
54 = I12 +
55 = E13
  56 = D13 +
57 = C13 +
58 = B13 +
59 = A13 +
60 = F13 +
61 = G13 +
62 = H13 +
63 = I13 +
64 = E14 +
65 = D14 +
66 = C14 +
67 = B14 +
68 = A14 +
69 = F14 +
70 = G14 +
71 = H14 +
72 = I14 +
73 = E15 +
74 = D15 +
75 = C15 +
76 = B15 +
77 = A15 +
78 = F15 +
79 = G15 +
80 = H15 +
81 = I15 +
82 = E6 +
83 = D6
  84 = C6 +
85 = B6 +
86 = A6 +
87 = F6 +
88 = G6 +
89 = H6 +
90 = I6 +
91 = E5 +
92 = D5 +
93 = C5 +
94 = B5 +
95 = A5 +
96 = F5 +
97 = G5 +
98 = H5 +
99 = I5 +
100 = E4 +
101 = D4 +
102 = C4 +
103 = B4 +
104 = A4 +
105 = F4 +
106 = G4 +
107 = H4 +
108 = I4 +
109 = E3 +
110 = D3 +
111 = C3
  112 = B3 +
113 = A3 +
114 = F3 +
115 = G3 +
116 = H3 +
117 = I3 +
118 = E2 +
119 = D2 +
120 = C2 +
121 = B2 +
122 = A2 +
123 = F2 +
124 = G2 +
125 = H2 +
126 = I2 +
127 = E1 +
128 = D1 +
129 = C1 +
130 = B1 +
131 = A1 +
132 = F1 +
133 = G1 +
134 = H1 +
135 = I1
+(Nikon 1 models with newer 135-point AF and 73-point phase-detect AF -- 9 +rows (B-J) and 15 columns (1-15), inside a grid of 11 rows by 15 columns. +The points are numbered sequentially, with F8 at the center) +
0 = (none) +
82 = F8 (Center) +
(Nikon 1 models with 171-point AF and 105-point phase-detect AF -- 9 rows +(B-J) and 19 columns (2-20), inside a grid of 11 rows by 21 columns. The +points are numbered sequentially, with F11 at the center) +
0 = (none) +
115 = F11 (Center) +
(Nikon models with 153-point AF -- 9 rows (A-I) and 17 columns (1-17): D5, +D500 and D850)
+ + + + +
0 = (none) +
1 = E9 (Center) +
2 = D9 +
3 = C9 +
4 = B9 +
5 = A9 +
6 = F9 +
7 = G9 +
8 = H9 +
9 = I9 +
10 = E10 +
11 = D10 +
12 = C10 +
13 = B10 +
14 = A10 +
15 = F10 +
16 = G10 +
17 = H10 +
18 = I10 +
19 = E11 +
20 = D11 +
21 = C11 +
22 = B11 +
23 = A11 +
24 = F11 +
25 = G11 +
26 = H11 +
27 = I11 +
28 = E8 +
29 = D8 +
30 = C8
  31 = B8 +
32 = A8 +
33 = F8 +
34 = G8 +
35 = H8 +
36 = I8 +
37 = E7 +
38 = D7 +
39 = C7 +
40 = B7 +
41 = A7 +
42 = F7 +
43 = G7 +
44 = H7 +
45 = I7 +
46 = E12 +
47 = D12 +
48 = C12 +
49 = B12 +
50 = A12 +
51 = F12 +
52 = G12 +
53 = H12 +
54 = I12 +
55 = E13 +
56 = D13 +
57 = C13 +
58 = B13 +
59 = A13 +
60 = F13 +
61 = G13
  62 = H13 +
63 = I13 +
64 = E14 +
65 = D14 +
66 = C14 +
67 = B14 +
68 = A14 +
69 = F14 +
70 = G14 +
71 = H14 +
72 = I14 +
73 = E15 +
74 = D15 +
75 = C15 +
76 = B15 +
77 = A15 +
78 = F15 +
79 = G15 +
80 = H15 +
81 = I15 +
82 = E16 +
83 = D16 +
84 = C16 +
85 = B16 +
86 = A16 +
87 = F16 +
88 = G16 +
89 = H16 +
90 = I16 +
91 = E17 +
92 = D17
  93 = C17 +
94 = B17 +
95 = A17 +
96 = F17 +
97 = G17 +
98 = H17 +
99 = I17 +
100 = E6 +
101 = D6 +
102 = C6 +
103 = B6 +
104 = A6 +
105 = F6 +
106 = G6 +
107 = H6 +
108 = I6 +
109 = E5 +
110 = D5 +
111 = C5 +
112 = B5 +
113 = A5 +
114 = F5 +
115 = G5 +
116 = H5 +
117 = I5 +
118 = E4 +
119 = D4 +
120 = C4 +
121 = B4 +
122 = A4 +
123 = F4
  124 = G4 +
125 = H4 +
126 = I4 +
127 = E3 +
128 = D3 +
129 = C3 +
130 = B3 +
131 = A3 +
132 = F3 +
133 = G3 +
134 = H3 +
135 = I3 +
136 = E2 +
137 = D2 +
138 = C2 +
139 = B2 +
140 = A2 +
141 = F2 +
142 = G2 +
143 = H2 +
144 = I2 +
145 = E1 +
146 = D1 +
147 = C1 +
148 = B1 +
149 = A1 +
150 = F1 +
151 = G1 +
152 = H1 +
153 = I1
+(future models?...) +
0 = (none) +
1 = Center
8AFPointsUsed +
AFPointsUsed +
AFPointsUsed +
AFPointsUsed +
AFPointsUsed +
AFPointsUsed +
AFPointsUsed +
AFPointsUsed +
PrimaryAFPoint
undef[7]
undef[2]
undef[5]
undef[17]
undef[21]
undef[29]
undef[20]
undef[7]
int8u
(models with 51-point AF -- 5 rows: A1-9, B1-11, C1-11, D1-11, E1-9. Center +point is C6) +
(models with 11-point AF)
+ +
0x0 = (none) +
0x7ff = All 11 Points +
Bit 0 = Center +
Bit 1 = Top +
Bit 2 = Bottom +
Bit 3 = Mid-left +
Bit 4 = Upper-left
  Bit 5 = Lower-left +
Bit 6 = Far Left +
Bit 7 = Mid-right +
Bit 8 = Upper-right +
Bit 9 = Lower-right +
Bit 10 = Far Right
+(models with 39-point AF -- 5 rows: A1-3, B1-11, C1-11, D1-11, E1-3. Center +point is C6) +
(older models with 135-point AF -- 9 rows (A-I) and 15 columns (1-15). +Center point is E8. The odd-numbered columns, columns 2 and 14, and the +remaining corner points are not used for 41-point AF mode) +
(newer models with 135-point AF -- 9 rows (B-J) and 15 columns (1-15). Center +point is F8) +
(models with 171-point AF -- 9 rows (B-J) and 19 columns (2-20). Center +point is F10) +
(models with 153-point AF -- 9 rows (A-I) and 17 columns (1-17). Center +point is E9) +
(newer models with 51-point AF)
+ + + + +
0 = (none) +
1 = C6 (Center) +
2 = B6 +
3 = A5 +
4 = D6 +
5 = E5 +
6 = C7 +
7 = B7 +
8 = A6 +
9 = D7 +
10 = E6
  11 = C5 +
12 = B5 +
13 = A4 +
14 = D5 +
15 = E4 +
16 = C8 +
17 = B8 +
18 = A7 +
19 = D8 +
20 = E7 +
21 = C9
  22 = B9 +
23 = A8 +
24 = D9 +
25 = E8 +
26 = C10 +
27 = B10 +
28 = A9 +
29 = D10 +
30 = E9 +
31 = C11 +
32 = B11
  33 = D11 +
34 = C4 +
35 = B4 +
36 = A3 +
37 = D4 +
38 = E3 +
39 = C3 +
40 = B3 +
41 = A2 +
42 = D3 +
43 = E2
  44 = C2 +
45 = B2 +
46 = A1 +
47 = D2 +
48 = E1 +
49 = C1 +
50 = B1 +
51 = D1
+ + +
0 = (none) +
1 = E5 (Center) +
2 = D5 +
3 = C5 +
4 = B5 +
5 = A5 +
6 = F5 +
7 = G5 +
8 = H5 +
9 = I5 +
10 = E6 +
11 = D6 +
12 = C6 +
13 = B6 +
14 = A6 +
15 = F6 +
16 = G6 +
17 = H6 +
18 = I6 +
19 = E4 +
20 = D4 +
21 = C4 +
22 = B4 +
23 = A4 +
24 = F4 +
25 = G4 +
26 = H4 +
27 = I4 +
28 = E7 +
29 = D7 +
30 = C7 +
31 = B7 +
32 = A7 +
33 = F7 +
34 = G7 +
35 = H7 +
36 = I7 +
37 = E3 +
38 = D3 +
39 = C3 +
40 = B3
  41 = A3 +
42 = F3 +
43 = G3 +
44 = H3 +
45 = I3 +
46 = E8 +
47 = D8 +
48 = C8 +
49 = B8 +
50 = A8 +
51 = F8 +
52 = G8 +
53 = H8 +
54 = I8 +
55 = E2 +
56 = D2 +
57 = C2 +
58 = B2 +
59 = A2 +
60 = F2 +
61 = G2 +
62 = H2 +
63 = I2 +
64 = E9 +
65 = D9 +
66 = C9 +
67 = B9 +
68 = A9 +
69 = F9 +
70 = G9 +
71 = H9 +
72 = I9 +
73 = E1 +
74 = D1 +
75 = C1 +
76 = B1 +
77 = A1 +
78 = F1 +
79 = G1 +
80 = H1 +
81 = I1
+
10AFPointsUsed +
AFPointsUsed +
AFPointsUsed
undef[7]
undef[11]
undef[14]
(newer models with 51-point AF) +
(models with 81-selectable point AF -- 9 rows (A-I) and 9 columns (1-9) for +phase detect AF points. Center point is E5) +
(models with 105-point AF -- 7 rows (A-G) and 15 columns (1-15). Center +point is D8)
16AFImageWidthint16u(this and the following tags are valid only for contrast-detect AF)
18AFImageHeightint16u 
20AFAreaXPositionint16u(center of AF area in AFImage coordinates)
22AFAreaYPositionint16u 
24AFAreaWidthint16u(size of AF area in AFImage coordinates)
26AFAreaHeightint16u 
28ContrastDetectAFInFocus +
AFPointsSelected
int8u
undef[20]
0 = No +
1 = Yes
42AFImageWidthint16u 
44AFImageHeightint16u 
46AFAreaXPositionint16u 
47FocusPositionHorizontalint8u~ 
48AFAreaYPosition +
AFPointsInFocus
int16u
undef[20]
(AF points in focus at the time time image was captured)
49FocusPositionVerticalint8u~ 
50AFAreaWidthint16u 
52AFAreaHeightint16u 
56PrimaryAFPointint8u(Nikon models with 105-point AF -- 7 rows (A-G) and 15 columns (1-15): D6) + + + + +
0 = (none) +
1 = D8 (Center) +
2 = C8 +
3 = B8 +
4 = A8 +
5 = E8 +
6 = F8 +
7 = G8 +
8 = D9 +
9 = C9 +
10 = B9 +
11 = A9 +
12 = E9 +
13 = F9 +
14 = G9 +
15 = D10 +
16 = C10 +
17 = B10 +
18 = A10 +
19 = E10 +
20 = F10 +
21 = G10
  22 = D7 +
23 = C7 +
24 = B7 +
25 = A7 +
26 = E7 +
27 = F7 +
28 = G7 +
29 = D6 +
30 = C6 +
31 = B6 +
32 = A6 +
33 = E6 +
34 = F6 +
35 = G6 +
36 = D11 +
37 = C11 +
38 = B11 +
39 = A11 +
40 = E11 +
41 = F11 +
42 = G11 +
43 = D12
  44 = C12 +
45 = B12 +
46 = A12 +
47 = E12 +
48 = F12 +
49 = G12 +
50 = D13 +
51 = C13 +
52 = B13 +
53 = A13 +
54 = E13 +
55 = F13 +
56 = G13 +
57 = D14 +
58 = C14 +
59 = B14 +
60 = A14 +
61 = E14 +
62 = F14 +
63 = G14 +
64 = D15 +
65 = C15
  66 = B15 +
67 = A15 +
68 = E15 +
69 = F15 +
70 = G15 +
71 = D5 +
72 = C5 +
73 = B5 +
74 = A5 +
75 = E5 +
76 = F5 +
77 = G5 +
78 = D4 +
79 = C4 +
80 = B4 +
81 = A4 +
82 = E4 +
83 = F4 +
84 = G4 +
85 = D3 +
86 = C3 +
87 = B3
  88 = A3 +
89 = E3 +
90 = F3 +
91 = G3 +
92 = D2 +
93 = C2 +
94 = B2 +
95 = A2 +
96 = E2 +
97 = F2 +
98 = G2 +
99 = D1 +
100 = C1 +
101 = B1 +
102 = A1 +
103 = E1 +
104 = F1 +
105 = G1
+
68PrimaryAFPointint8u + + + + +
0 = (none) +
1 = E9 (Center) +
2 = D9 +
3 = C9 +
4 = B9 +
5 = A9 +
6 = F9 +
7 = G9 +
8 = H9 +
9 = I9 +
10 = E10 +
11 = D10 +
12 = C10 +
13 = B10 +
14 = A10 +
15 = F10 +
16 = G10 +
17 = H10 +
18 = I10 +
19 = E11 +
20 = D11 +
21 = C11 +
22 = B11 +
23 = A11 +
24 = F11 +
25 = G11 +
26 = H11 +
27 = I11 +
28 = E8 +
29 = D8 +
30 = C8
  31 = B8 +
32 = A8 +
33 = F8 +
34 = G8 +
35 = H8 +
36 = I8 +
37 = E7 +
38 = D7 +
39 = C7 +
40 = B7 +
41 = A7 +
42 = F7 +
43 = G7 +
44 = H7 +
45 = I7 +
46 = E12 +
47 = D12 +
48 = C12 +
49 = B12 +
50 = A12 +
51 = F12 +
52 = G12 +
53 = H12 +
54 = I12 +
55 = E13 +
56 = D13 +
57 = C13 +
58 = B13 +
59 = A13 +
60 = F13 +
61 = G13
  62 = H13 +
63 = I13 +
64 = E14 +
65 = D14 +
66 = C14 +
67 = B14 +
68 = A14 +
69 = F14 +
70 = G14 +
71 = H14 +
72 = I14 +
73 = E15 +
74 = D15 +
75 = C15 +
76 = B15 +
77 = A15 +
78 = F15 +
79 = G15 +
80 = H15 +
81 = I15 +
82 = E16 +
83 = D16 +
84 = C16 +
85 = B16 +
86 = A16 +
87 = F16 +
88 = G16 +
89 = H16 +
90 = I16 +
91 = E17 +
92 = D17
  93 = C17 +
94 = B17 +
95 = A17 +
96 = F17 +
97 = G17 +
98 = H17 +
99 = I17 +
100 = E6 +
101 = D6 +
102 = C6 +
103 = B6 +
104 = A6 +
105 = F6 +
106 = G6 +
107 = H6 +
108 = I6 +
109 = E5 +
110 = D5 +
111 = C5 +
112 = B5 +
113 = A5 +
114 = F5 +
115 = G5 +
116 = H5 +
117 = I5 +
118 = E4 +
119 = D4 +
120 = C4 +
121 = B4 +
122 = A4 +
123 = F4
  124 = G4 +
125 = H4 +
126 = I4 +
127 = E3 +
128 = D3 +
129 = C3 +
130 = B3 +
131 = A3 +
132 = F3 +
133 = G3 +
134 = H3 +
135 = I3 +
136 = E2 +
137 = D2 +
138 = C2 +
139 = B2 +
140 = A2 +
141 = F2 +
142 = G2 +
143 = H2 +
144 = I2 +
145 = E1 +
146 = D1 +
147 = C1 +
148 = B1 +
149 = A1 +
150 = F1 +
151 = G1 +
152 = H1 +
153 = I1
+(D3500) + +
0 = (none) +
1 = Center +
2 = Top +
3 = Bottom +
4 = Mid-left +
5 = Upper-left
  6 = Lower-left +
7 = Far Left +
8 = Mid-right +
9 = Upper-right +
10 = Lower-right +
11 = Far Right
+(future models?...) +
0 = (none) +
1 = Center
70AFImageWidthint16u(this and the following tags are valid only for contrast-detect AF)
72AFImageHeightint16u 
74AFAreaXPositionint16u(center of AF area in AFImage coordinates)
76AFAreaYPositionint16u 
78AFAreaWidthint16u(size of AF area in AFImage coordinates)
80AFAreaHeightint16u 
82ContrastDetectAFInFocusint8u0 = No +
1 = Yes
+ +

Nikon FileInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0FileInfoVersionno 
2MemoryCardNumberint16u 
3DirectoryNumberint16u 
4FileNumberint16u 
+ +

Nikon AFTune Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AFFineTuneint8u0 = Off +
1 = On (1) +
2 = On (2) +
3 = On (Zoom)
1AFFineTuneIndexint8u(index of saved lens)
2AFFineTuneAdjint8s(may only be valid for saved lenses)
3AFFineTuneAdjTeleint8s(only valid for zoom lenses (ie, AFTune=3))
+ +

Nikon RetouchInfo Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0RetouchInfoVersionno 
5RetouchNEFProcessingint8s-1 = Off +
1 = On
+ +

Nikon BarometerInfo Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0BarometerInfoVersionno 
6Altitudeint32s 
+ +

Nikon CaptureOffsets Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001IFD0_Offsetno 
0x0002PreviewIFD_Offsetno 
0x0003SubIFD_Offsetno 
+ +

Nikon Scan Tags

+

This information is written by the Nikon Scan software.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0002FilmTypestring 
0x0040MultiSamplestring 
0x0041BitDepthint16u 
0x0050MasterGainrational64s 
0x0051ColorGainrational64s[3] 
0x0060ScanImageEnhancerint32u0 = Off +
1 = On
0x0100DigitalICEstring 
0x0110ROCInfo---> Nikon ROC Tags
0x0120GEMInfo---> Nikon GEM Tags
0x0200DigitalDEEShadowAdjint32u 
0x0201DigitalDEEThresholdint32u 
0x0202DigitalDEEHighlightAdjint32u 
+ +

Nikon ROC Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
0DigitalROCint32u 
+ +

Nikon GEM Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
0DigitalGEMint32u 
+ +

Nikon CaptureOutput Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
2OutputImageWidthint32u 
3OutputImageHeightint32u 
4OutputResolutionint32u 
+ +

Nikon Type2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0003Qualityyes 
0x0004ColorModeyes 
0x0005ImageAdjustmentyes 
0x0006CCDSensitivityyes 
0x0007WhiteBalanceyes 
0x0008Focusyes 
0x000aDigitalZoomyes 
0x000bConverteryes 
+ +

Nikon NEFInfo Tags

+

As-yet unknown information found in SubIFD1 tag 0xc7d5 of NEF images from +cameras such as the Z6 and Z7, and NRW images from some Coolpix cameras.

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

Nikon AVI Tags

+

Nikon-specific RIFF tags found in AVI videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'nctg'NikonTags---> Nikon AVITags Tags
'ncth'ThumbnailImageno 
'ncvr'NikonVers---> Nikon AVIVers Tags
'ncvw'PreviewImageno 
+ +

Nikon AVITags Tags

+

These tags and the AVIVer tags below are found in proprietary-format records +of Nikon AVI videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0003Makeno 
0x0004Modelno 
0x0005Softwareno 
0x0006Equipmentno 
0x0007Orientationno +
1 = Horizontal (normal) +
2 = Mirror horizontal +
3 = Rotate 180 +
4 = Mirror vertical +
5 = Mirror horizontal and rotate 270 CW +
6 = Rotate 90 CW +
7 = Mirror horizontal and rotate 90 CW +
8 = Rotate 270 CW
+
0x0008ExposureTimeno 
0x0009FNumberno 
0x000aExposureCompensationno 
0x000bMaxApertureValueno 
0x000cMeteringModeno +
0 = Unknown +
1 = Average +
2 = Center-weighted average +
3 = Spot +
4 = Multi-spot +
5 = Multi-segment +
6 = Partial +
255 = Other
+
0x000fFocalLengthno 
0x0010XResolutionno 
0x0011YResolutionno 
0x0012ResolutionUnitno1 = None +
2 = inches +
3 = cm
0x0013DateTimeOriginalno 
0x0014CreateDateno 
0x0016Durationno 
0x0018FocusModeno 
0x001bDigitalZoomno 
0x001dColorModeno 
0x001eSharpnessno 
0x001fWhiteBalanceno 
0x0020NoiseReductionno 
+ +

Nikon AVIVers Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001MakerNoteTypeno 
0x0002MakerNoteVersionno 
+ +

Nikon NCDT Tags

+

Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from +various Nikon models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'NCDB'NikonNCDB---> Nikon NCDB Tags
'NCHD'MakerNoteVersionno 
'NCM1'PreviewImage1no 
'NCM2'PreviewImage2no 
'NCTG'NikonTags---> Nikon NCTG Tags
'NCTH'ThumbnailImageno 
'NCVW'PreviewImageno 
+ +

Nikon NCDB Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

Nikon NCTG Tags

+

These tags are found in proprietary-format records of the NCTG atom in MOV +videos from some Nikon cameras.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001Makeno 
0x0002Modelno 
0x0003Softwareno 
0x0011CreateDateno 
0x0012DateTimeOriginalno 
0x0013FrameCountno 
0x0016FrameRateno 
0x0019TimeZoneno 
0x0022FrameWidthno 
0x0023FrameHeightno 
0x0032AudioChannelsno 
0x0033AudioBitsPerSampleno 
0x0034AudioSampleRateno 
0x1002NikonDateTimeno 
0x1013ElectronicVRno0 = Off +
1 = On
0x110829aExposureTimeno 
0x110829dFNumberno 
0x1108822ExposureProgramno +
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
+
0x1109204ExposureCompensationno 
0x1109207MeteringModeno +
0 = Unknown +
1 = Average +
2 = Center-weighted average +
3 = Spot +
4 = Multi-spot +
5 = Multi-segment +
6 = Partial +
255 = Other
+
0x110920aFocalLengthno 
0x110a431SerialNumberno 
0x110a432LensInfono 
0x110a433LensMakeno 
0x110a434LensModelno 
0x110a435LensSerialNumberno 
0x1200000GPSVersionIDno 
0x1200001GPSLatitudeRefno'N' = North +
'S' = South
0x1200002GPSLatitudeno 
0x1200003GPSLongitudeRefno'E' = East +
'W' = West
0x1200004GPSLongitudeno 
0x1200005GPSAltitudeRefno0 = Above Sea Level +
1 = Below Sea Level
0x1200006GPSAltitudeno 
0x1200007GPSTimeStampno 
0x1200008GPSSatellitesno 
0x1200010GPSImgDirectionRefno'M' = Magnetic North +
'T' = True North
0x1200011GPSImgDirectionno 
0x1200012GPSMapDatumno 
0x120001dGPSDateStampno 
0x2000001MakerNoteVersionno 
0x2000005WhiteBalanceno 
0x2000007FocusModeno 
0x200000bWhiteBalanceFineTuneno 
0x200001bCropHiSpeedno + +
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 +
13 = 2.8x Movie Crop +
14 = 1.4x Movie Crop +
15 = 1.5x Movie Crop +
17 = 1:1 Crop
+
0x200001eColorSpaceno1 = sRGB +
2 = Adobe RGB
0x200001fVRInfo---> Nikon VRInfo Tags
0x2000022ActiveD-Lightingno + +
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
+
0x2000023PictureControlData---> Nikon PictureControl Tags +
--> Nikon PictureControl2 Tags +
--> Nikon PictureControl3 Tags +
--> Nikon PictureControlUnknown Tags
0x2000024WorldTime---> Nikon WorldTime Tags
0x2000025ISOInfo---> Nikon ISOInfo Tags
0x200002aVignetteControlno0 = Off +
1 = Low +
3 = Normal +
5 = High
0x200002cUnknownInfo---> Nikon UnknownInfo Tags
0x2000032UnknownInfo2---> Nikon UnknownInfo2 Tags
0x2000039LocationInfo---> Nikon LocationInfo Tags
0x200003fWhiteBalanceFineTuneno 
0x200004eNikonSettings---> NikonSettings Tags
0x2000083LensTypenoBit 0 = MF +
Bit 1 = D +
Bit 2 = G +
Bit 3 = VR +
Bit 4 = 1 +
Bit 6 = E
0x2000084Lensno 
0x2000087FlashModeno +
0 = Did Not Fire +
1 = Fired, Manual +
3 = Not Ready +
7 = Fired, External +
8 = Fired, Commander Mode +
9 = Fired, TTL Mode +
18 = LED Light
+
0x2000098LensData0100 +
LensData0101 +
LensData0201 +
LensData0204 +
LensData0400 +
LensData0402 +
LensData0403 +
LensData0800 +
LensDataUnknown
-
-
-
-
-
-
-
-
-
--> Nikon LensData00 Tags +
--> Nikon LensData01 Tags +
--> Nikon LensData01 Tags +
--> Nikon LensData0204 Tags +
--> Nikon LensData0400 Tags +
--> Nikon LensData0402 Tags +
--> Nikon LensData0403 Tags +
--> Nikon LensData0800 Tags +
--> Nikon LensDataUnknown Tags
0x20000a7ShutterCountno 
0x20000a8FlashInfo0100 +
FlashInfo0102 +
FlashInfo0103 +
FlashInfo0106 +
FlashInfo0107 +
FlashInfoUnknown
-
-
-
-
-
-
--> Nikon FlashInfo0100 Tags +
--> Nikon FlashInfo0102 Tags +
--> Nikon FlashInfo0103 Tags +
--> Nikon FlashInfo0106 Tags +
--> Nikon FlashInfo0107 Tags +
--> Nikon FlashInfoUnknown Tags
0x20000abVariProgramno 
0x20000b1HighISONoiseReductionno + +
0 = Off +
1 = Minimal +
2 = Low +
3 = Medium Low
  4 = Normal +
5 = Medium High +
6 = High
+
0x20000b7AFInfo2---> Nikon AFInfo2 Tags
0x20000c3BarometerInfo---> Nikon BarometerInfo Tags
+ +

Nikon MOV Tags

+

This information is found in MOV and QT videos from some Nikon cameras.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
24Modelno 
38ExposureTimeno 
42FNumberno 
50ExposureCompensationno 
68WhiteBalanceno + +
0 = Auto +
1 = Daylight +
2 = Shade
  3 = Fluorescent +
4 = Tungsten +
5 = Manual
+
72FocalLengthno 
175Softwareno 
223ISOno 
+ +

Nikon LensID Values

+

The Nikon LensID is constructed as a Composite tag from the raw hex values +of 8 other tags: LensIDNumber, LensFStops, MinFocalLength, MaxFocalLength, +MaxApertureAtMinFocal, MaxApertureAtMaxFocal, MCUVersion and LensType, in +that order. The user-defined "Lenses" list may be used to specify the lens +for ExifTool to choose in these cases (see the +sample config file for details).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLensID
'00 00 00 00 00 00 00 01'= Manual Lens No CPU
'00 00 00 00 00 00 E1 12'= TC-17E II
'00 00 00 00 00 00 F1 0C'= TC-14E [II] or Sigma APO Tele Converter 1.4x EX DG or Kenko Teleplus PRO 300 DG 1.4x
'00 00 00 00 00 00 F2 18'= TC-20E [II] or Sigma APO Tele Converter 2x EX DG or Kenko Teleplus PRO 300 DG 2.0x
'00 00 48 48 53 53 00 01'= Loreo 40mm F11-22 3D Lens in a Cap 9005
'00 36 1C 2D 34 3C 00 06'= Tamron SP AF 11-18mm f/4.5-5.6 Di II LD Aspherical (IF) (A13)
'00 3C 1F 37 30 30 00 06'= Tokina AT-X 124 AF PRO DX (AF 12-24mm f/4)
'00 3C 2B 44 30 30 00 06'= Tokina AT-X 17-35 F4 PRO FX (AF 17-35mm f/4)
'00 3C 5C 80 30 30 00 0E'= Tokina AT-X 70-200 F4 FX VCM-S (AF 70-200mm f/4)
'00 3E 80 A0 38 3F 00 02'= Tamron SP AF 200-500mm f/5-6.3 Di LD (IF) (A08)
'00 3F 2D 80 2B 40 00 06'= Tamron AF 18-200mm f/3.5-6.3 XR Di II LD Aspherical (IF) (A14)
'00 3F 2D 80 2C 40 00 06'= Tamron AF 18-200mm f/3.5-6.3 XR Di II LD Aspherical (IF) Macro (A14)
'00 3F 80 A0 38 3F 00 02'= Tamron SP AF 200-500mm f/5-6.3 Di (A08)
'00 40 11 11 2C 2C 00 00'= Samyang 8mm f/3.5 Fish-Eye
'00 40 18 2B 2C 34 00 06'= Tokina AT-X 107 AF DX Fisheye (AF 10-17mm f/3.5-4.5)
'00 40 2A 72 2C 3C 00 06'= Tokina AT-X 16.5-135 DX (AF 16.5-135mm F3.5-5.6)
'00 40 2B 2B 2C 2C 00 02'= Tokina AT-X 17 AF PRO (AF 17mm f/3.5)
'00 40 2D 2D 2C 2C 00 00'= Carl Zeiss Distagon T* 3.5/18 ZF.2
'00 40 2D 80 2C 40 00 06'= Tamron AF 18-200mm f/3.5-6.3 XR Di II LD Aspherical (IF) Macro (A14NII)
'00 40 2D 88 2C 40 00 06'= Tamron AF 18-250mm f/3.5-6.3 Di II LD Aspherical (IF) Macro (A18NII)
'00 40 2D 88 2C 40 62 06'= Tamron AF 18-250mm f/3.5-6.3 Di II LD Aspherical (IF) Macro (A18)
'00 40 31 31 2C 2C 00 00'= Voigtlander Color Skopar 20mm F3.5 SLII Aspherical
'00 40 37 80 2C 3C 00 02'= Tokina AT-X 242 AF (AF 24-200mm f/3.5-5.6)
'00 40 64 64 2C 2C 00 00'= Voigtlander APO-Lanthar 90mm F3.5 SLII Close Focus
'00 44 60 98 34 3C 00 02'= Tokina AT-X 840 D (AF 80-400mm f/4.5-5.6)
'00 47 10 10 24 24 00 00'= Fisheye Nikkor 8mm f/2.8 AiS
'00 47 25 25 24 24 00 02'= Tamron SP AF 14mm f/2.8 Aspherical (IF) (69E)
'00 47 3C 3C 24 24 00 00'= Nikkor 28mm f/2.8 AiS
'00 47 44 44 24 24 00 06'= Tokina AT-X M35 PRO DX (AF 35mm f/2.8 Macro)
'00 47 53 80 30 3C 00 06'= Tamron AF 55-200mm f/4-5.6 Di II LD (A15)
'00 48 1C 29 24 24 00 06'= Tokina AT-X 116 PRO DX (AF 11-16mm f/2.8)
'00 48 27 27 24 24 00 00'= Carl Zeiss Distagon T* 2.8/15 ZF.2
'00 48 29 3C 24 24 00 06'= Tokina AT-X 16-28 AF PRO FX (AF 16-28mm f/2.8)
'00 48 29 50 24 24 00 06'= Tokina AT-X 165 PRO DX (AF 16-50mm f/2.8)
'00 48 32 32 24 24 00 00'= Carl Zeiss Distagon T* 2.8/21 ZF.2
'00 48 37 5C 24 24 00 06'= Tokina AT-X 24-70 F2.8 PRO FX (AF 24-70mm f/2.8)
'00 48 3C 3C 24 24 00 00'= Voigtlander Color Skopar 28mm F2.8 SL II
'00 48 3C 60 24 24 00 02'= Tokina AT-X 280 AF PRO (AF 28-80mm f/2.8)
'00 48 3C 6A 24 24 00 02'= Tamron SP AF 28-105mm f/2.8 LD Aspherical IF (176D)
'00 48 50 50 18 18 00 00'= Nikkor H 50mm f/2
'00 48 50 72 24 24 00 06'= Tokina AT-X 535 PRO DX (AF 50-135mm f/2.8)
'00 48 5C 80 30 30 00 0E'= Tokina AT-X 70-200 F4 FX VCM-S (AF 70-200mm f/4)
'00 48 5C 8E 30 3C 00 06'= Tamron AF 70-300mm f/4-5.6 Di LD Macro 1:2 (A17NII)
'00 48 68 68 24 24 00 00'= Series E 100mm f/2.8
'00 48 80 80 30 30 00 00'= Nikkor 200mm f/4 AiS
'00 49 30 48 22 2B 00 02'= Tamron SP AF 20-40mm f/2.7-3.5 (166D)
'00 4C 6A 6A 20 20 00 00'= Nikkor 105mm f/2.5 AiS
'00 4C 7C 7C 2C 2C 00 02'= Tamron SP AF 180mm f/3.5 Di Model (B01)
'00 53 2B 50 24 24 00 06'= Tamron SP AF 17-50mm f/2.8 XR Di II LD Aspherical (IF) (A16)
'00 54 2B 50 24 24 00 06'= Tamron SP AF 17-50mm f/2.8 XR Di II LD Aspherical (IF) (A16NII)
'00 54 38 38 18 18 00 00'= Carl Zeiss Distagon T* 2/25 ZF.2
'00 54 3C 3C 18 18 00 00'= Carl Zeiss Distagon T* 2/28 ZF.2
'00 54 44 44 0C 0C 00 00'= Carl Zeiss Distagon T* 1.4/35 ZF.2
'00 54 44 44 18 18 00 00'= Carl Zeiss Distagon T* 2/35 ZF.2
'00 54 48 48 18 18 00 00'= Voigtlander Ultron 40mm F2 SLII Aspherical
'00 54 50 50 0C 0C 00 00'= Carl Zeiss Planar T* 1.4/50 ZF.2
'00 54 50 50 18 18 00 00'= Carl Zeiss Makro-Planar T* 2/50 ZF.2
'00 54 53 53 0C 0C 00 00'= Zeiss Otus 1.4/55
'00 54 55 55 0C 0C 00 00'= Voigtlander Nokton 58mm F1.4 SLII
'00 54 56 56 30 30 00 00'= Coastal Optical Systems 60mm 1:4 UV-VIS-IR Macro Apo
'00 54 62 62 0C 0C 00 00'= Carl Zeiss Planar T* 1.4/85 ZF.2
'00 54 68 68 18 18 00 00'= Carl Zeiss Makro-Planar T* 2/100 ZF.2
'00 54 68 68 24 24 00 02'= Tokina AT-X M100 AF PRO D (AF 100mm f/2.8 Macro)
'00 54 72 72 18 18 00 00'= Carl Zeiss Apo Sonnar T* 2/135 ZF.2
'00 54 8E 8E 24 24 00 02'= Tokina AT-X 300 AF PRO (AF 300mm f/2.8)
'00 57 50 50 14 14 00 00'= Nikkor 50mm f/1.8 AI
'00 58 64 64 20 20 00 00'= Soligor C/D Macro MC 90mm f/2.5
'01 00 00 00 00 00 02 00'= TC-16A
'01 00 00 00 00 00 08 00'= TC-16A
'01 54 62 62 0C 0C 00 00'= Zeiss Otus 1.4/85
'01 58 50 50 14 14 02 00'= AF Nikkor 50mm f/1.8
'01 58 50 50 14 14 05 00'= AF Nikkor 50mm f/1.8
'02 2F 98 98 3D 3D 02 00'= Sigma APO 400mm F5.6
'02 34 A0 A0 44 44 02 00'= Sigma APO 500mm F7.2
'02 37 5E 8E 35 3D 02 00'= Sigma 75-300mm F4.5-5.6 APO
'02 37 A0 A0 34 34 02 00'= Sigma APO 500mm F4.5
'02 3A 37 50 31 3D 02 00'= Sigma 24-50mm F4-5.6 UC
'02 3A 5E 8E 32 3D 02 00'= Sigma 75-300mm F4.0-5.6
'02 3B 44 61 30 3D 02 00'= Sigma 35-80mm F4-5.6
'02 3B 5C 82 30 3C 02 00'= Sigma Zoom-K 70-210mm F4-5.6
'02 3C B0 B0 3C 3C 02 00'= Sigma APO 800mm F5.6
'02 3F 24 24 2C 2C 02 00'= Sigma 14mm F3.5
'02 3F 3C 5C 2D 35 02 00'= Sigma 28-70mm F3.5-4.5 UC
'02 40 44 5C 2C 34 02 00'= Exakta AF 35-70mm 1:3.5-4.5 MC
'02 40 44 73 2B 36 02 00'= Sigma 35-135mm F3.5-4.5 a
'02 40 5C 82 2C 35 02 00'= Sigma APO 70-210mm F3.5-4.5
'02 42 44 5C 2A 34 02 00'= AF Zoom-Nikkor 35-70mm f/3.3-4.5
'02 42 44 5C 2A 34 08 00'= AF Zoom-Nikkor 35-70mm f/3.3-4.5
'02 46 37 37 25 25 02 00'= Sigma 24mm F2.8 Super Wide II Macro
'02 46 3C 5C 25 25 02 00'= Sigma 28-70mm F2.8
'02 46 5C 82 25 25 02 00'= Sigma 70-210mm F2.8 APO
'02 48 50 50 24 24 02 00'= Sigma Macro 50mm F2.8
'02 48 65 65 24 24 02 00'= Sigma Macro 90mm F2.8
'03 43 5C 81 35 35 02 00'= Soligor AF C/D Zoom UMCS 70-210mm 1:4.5
'03 48 5C 81 30 30 02 00'= AF Zoom-Nikkor 70-210mm f/4
'03 54 68 68 0C 0C 00 00'= Zeiss Otus 1.4/100
'04 48 3C 3C 24 24 03 00'= AF Nikkor 28mm f/2.8
'05 54 50 50 0C 0C 04 00'= AF Nikkor 50mm f/1.4
'06 3F 68 68 2C 2C 06 00'= Cosina AF 100mm F3.5 Macro
'06 54 53 53 24 24 06 00'= AF Micro-Nikkor 55mm f/2.8
'07 36 3D 5F 2C 3C 03 00'= Cosina AF Zoom 28-80mm F3.5-5.6 MC Macro
'07 3E 30 43 2D 35 03 00'= Soligor AF Zoom 19-35mm 1:3.5-4.5 MC
'07 40 2F 44 2C 34 03 02'= Tamron AF 19-35mm f/3.5-4.5 (A10)
'07 40 30 45 2D 35 03 02'= Tamron AF 19-35mm f/3.5-4.5 (A10)
'07 40 30 45 2D 35 03 02'= Voigtlander Ultragon 19-35mm F3.5-4.5 VMV
'07 40 3C 5C 2C 35 03 00'= Tokina AF 270 II (AF 28-70mm f/3.5-4.5)
'07 40 3C 62 2C 34 03 00'= AF Zoom-Nikkor 28-85mm f/3.5-4.5
'07 46 2B 44 24 30 03 02'= Tamron SP AF 17-35mm f/2.8-4 Di LD Aspherical (IF) (A05)
'07 46 3D 6A 25 2F 03 00'= Cosina AF Zoom 28-105mm F2.8-3.8 MC
'07 47 3C 5C 25 35 03 00'= Tokina AF 287 SD (AF 28-70mm f/2.8-4.5)
'07 48 3C 5C 24 24 03 00'= Tokina AT-X 287 AF (AF 28-70mm f/2.8)
'08 40 44 6A 2C 34 04 00'= AF Zoom-Nikkor 35-105mm f/3.5-4.5
'09 48 37 37 24 24 04 00'= AF Nikkor 24mm f/2.8
'0A 48 8E 8E 24 24 03 00'= AF Nikkor 300mm f/2.8 IF-ED
'0A 48 8E 8E 24 24 05 00'= AF Nikkor 300mm f/2.8 IF-ED N
'0B 3E 3D 7F 2F 3D 0E 00'= Tamron AF 28-200mm f/3.8-5.6 (71D)
'0B 3E 3D 7F 2F 3D 0E 02'= Tamron AF 28-200mm f/3.8-5.6D (171D)
'0B 48 7C 7C 24 24 05 00'= AF Nikkor 180mm f/2.8 IF-ED
'0D 40 44 72 2C 34 07 00'= AF Zoom-Nikkor 35-135mm f/3.5-4.5
'0E 48 5C 81 30 30 05 00'= AF Zoom-Nikkor 70-210mm f/4
'0E 4A 31 48 23 2D 0E 02'= Tamron SP AF 20-40mm f/2.7-3.5 (166D)
'0F 58 50 50 14 14 05 00'= AF Nikkor 50mm f/1.8 N
'10 3D 3C 60 2C 3C D2 02'= Tamron AF 28-80mm f/3.5-5.6 Aspherical (177D)
'10 48 8E 8E 30 30 08 00'= AF Nikkor 300mm f/4 IF-ED
'11 48 44 5C 24 24 08 00'= AF Zoom-Nikkor 35-70mm f/2.8
'11 48 44 5C 24 24 15 00'= AF Zoom-Nikkor 35-70mm f/2.8
'12 36 5C 81 35 3D 09 00'= Cosina AF Zoom 70-210mm F4.5-5.6 MC Macro
'12 36 69 97 35 42 09 00'= Soligor AF Zoom 100-400mm 1:4.5-6.7 MC
'12 38 69 97 35 42 09 02'= Promaster Spectrum 7 100-400mm F4.5-6.7
'12 39 5C 8E 34 3D 08 02'= Cosina AF Zoom 70-300mm F4.5-5.6 MC Macro
'12 3B 68 8D 3D 43 09 02'= Cosina AF Zoom 100-300mm F5.6-6.7 MC Macro
'12 3B 98 98 3D 3D 09 00'= Tokina AT-X 400 AF SD (AF 400mm f/5.6)
'12 3D 3C 80 2E 3C DF 02'= Tamron AF 28-200mm f/3.8-5.6 AF Aspherical LD (IF) (271D)
'12 44 5E 8E 34 3C 09 00'= Tokina AF 730 (AF 75-300mm F4.5-5.6)
'12 48 5C 81 30 3C 09 00'= AF Nikkor 70-210mm f/4-5.6
'12 4A 5C 81 31 3D 09 00'= Soligor AF C/D Auto Zoom+Macro 70-210mm 1:4-5.6 UMCS
'13 42 37 50 2A 34 0B 00'= AF Zoom-Nikkor 24-50mm f/3.3-4.5
'14 48 60 80 24 24 0B 00'= AF Zoom-Nikkor 80-200mm f/2.8 ED
'14 48 68 8E 30 30 0B 00'= Tokina AT-X 340 AF (AF 100-300mm f/4)
'14 54 60 80 24 24 0B 00'= Tokina AT-X 828 AF (AF 80-200mm f/2.8)
'15 4C 62 62 14 14 0C 00'= AF Nikkor 85mm f/1.8
'17 3C A0 A0 30 30 0F 00'= Nikkor 500mm f/4 P ED IF
'17 3C A0 A0 30 30 11 00'= Nikkor 500mm f/4 P ED IF
'18 40 44 72 2C 34 0E 00'= AF Zoom-Nikkor 35-135mm f/3.5-4.5 N
'1A 54 44 44 18 18 11 00'= AF Nikkor 35mm f/2
'1B 44 5E 8E 34 3C 10 00'= AF Zoom-Nikkor 75-300mm f/4.5-5.6
'1C 48 30 30 24 24 12 00'= AF Nikkor 20mm f/2.8
'1D 42 44 5C 2A 34 12 00'= AF Zoom-Nikkor 35-70mm f/3.3-4.5 N
'1E 54 56 56 24 24 13 00'= AF Micro-Nikkor 60mm f/2.8
'1E 5D 64 64 20 20 13 00'= Tamron SP AF 90mm f/2.5 (52E)
'1F 54 6A 6A 24 24 14 00'= AF Micro-Nikkor 105mm f/2.8
'20 3C 80 98 3D 3D 1E 02'= Tamron AF 200-400mm f/5.6 LD IF (75D)
'20 48 60 80 24 24 15 00'= AF Zoom-Nikkor 80-200mm f/2.8 ED
'20 5A 64 64 20 20 14 00'= Tamron SP AF 90mm f/2.5 Macro (152E)
'21 40 3C 5C 2C 34 16 00'= AF Zoom-Nikkor 28-70mm f/3.5-4.5
'21 56 8E 8E 24 24 14 00'= Tamron SP AF 300mm f/2.8 LD-IF (60E)
'22 48 72 72 18 18 16 00'= AF DC-Nikkor 135mm f/2
'22 53 64 64 24 24 E0 02'= Tamron SP AF 90mm f/2.8 Macro 1:1 (72E)
'23 30 BE CA 3C 48 17 00'= Zoom-Nikkor 1200-1700mm f/5.6-8 P ED IF
'24 44 60 98 34 3C 1A 02'= Tokina AT-X 840 AF-II (AF 80-400mm f/4.5-5.6)
'24 48 60 80 24 24 1A 02'= AF Zoom-Nikkor 80-200mm f/2.8D ED
'24 54 60 80 24 24 1A 02'= Tokina AT-X 828 AF PRO (AF 80-200mm f/2.8)
'25 44 44 8E 34 42 1B 02'= Tokina AF 353 (AF 35-300mm f/4.5-6.7)
'25 48 3C 5C 24 24 1B 02'= Tokina AT-X 270 AF PRO II (AF 28-70mm f/2.6-2.8)
'25 48 3C 5C 24 24 1B 02'= Tokina AT-X 287 AF PRO SV (AF 28-70mm f/2.8)
'25 48 44 5C 24 24 1B 02'= AF Zoom-Nikkor 35-70mm f/2.8D
'25 48 44 5C 24 24 3A 02'= AF Zoom-Nikkor 35-70mm f/2.8D
'25 48 44 5C 24 24 52 02'= AF Zoom-Nikkor 35-70mm f/2.8D
'26 3C 54 80 30 3C 1C 06'= Sigma 55-200mm F4-5.6 DC
'26 3C 5C 82 30 3C 1C 02'= Sigma 70-210mm F4-5.6 UC-II
'26 3C 5C 8E 30 3C 1C 02'= Sigma 70-300mm F4-5.6 DG Macro
'26 3C 98 98 3C 3C 1C 02'= Sigma APO Tele Macro 400mm F5.6
'26 3D 3C 80 2F 3D 1C 02'= Sigma 28-300mm F3.8-5.6 Aspherical
'26 3E 3C 6A 2E 3C 1C 02'= Sigma 28-105mm F3.8-5.6 UC-III Aspherical IF
'26 40 27 3F 2C 34 1C 02'= Sigma 15-30mm F3.5-4.5 EX DG Aspherical DF
'26 40 2D 44 2B 34 1C 02'= Sigma 18-35mm F3.5-4.5 Aspherical
'26 40 2D 50 2C 3C 1C 06'= Sigma 18-50mm F3.5-5.6 DC
'26 40 2D 70 2B 3C 1C 06'= Sigma 18-125mm F3.5-5.6 DC
'26 40 2D 80 2C 40 1C 06'= Sigma 18-200mm F3.5-6.3 DC
'26 40 37 5C 2C 3C 1C 02'= Sigma 24-70mm F3.5-5.6 Aspherical HF
'26 40 3C 5C 2C 34 1C 02'= AF Zoom-Nikkor 28-70mm f/3.5-4.5D
'26 40 3C 60 2C 3C 1C 02'= Sigma 28-80mm F3.5-5.6 Mini Zoom Macro II Aspherical
'26 40 3C 65 2C 3C 1C 02'= Sigma 28-90mm F3.5-5.6 Macro
'26 40 3C 80 2B 3C 1C 02'= Sigma 28-200mm F3.5-5.6 Compact Aspherical Hyperzoom Macro
'26 40 3C 80 2C 3C 1C 02'= Sigma 28-200mm F3.5-5.6 Compact Aspherical Hyperzoom Macro
'26 40 3C 8E 2C 40 1C 02'= Sigma 28-300mm F3.5-6.3 Macro
'26 40 7B A0 34 40 1C 02'= Sigma APO 170-500mm F5-6.3 Aspherical RF
'26 41 3C 8E 2C 40 1C 02'= Sigma 28-300mm F3.5-6.3 DG Macro
'26 44 73 98 34 3C 1C 02'= Sigma 135-400mm F4.5-5.6 APO Aspherical
'26 45 68 8E 34 42 1C 02'= Sigma 100-300mm F4.5-6.7 DL
'26 48 11 11 30 30 1C 02'= Sigma 8mm F4 EX Circular Fisheye
'26 48 27 27 24 24 1C 02'= Sigma 15mm F2.8 EX Diagonal Fisheye
'26 48 2D 50 24 24 1C 06'= Sigma 18-50mm F2.8 EX DC
'26 48 31 49 24 24 1C 02'= Sigma 20-40mm F2.8
'26 48 37 56 24 24 1C 02'= Sigma 24-60mm F2.8 EX DG
'26 48 3C 5C 24 24 1C 06'= Sigma 28-70mm F2.8 EX DG
'26 48 3C 5C 24 30 1C 02'= Sigma 28-70mm F2.8-4 DG
'26 48 3C 6A 24 30 1C 02'= Sigma 28-105mm F2.8-4 Aspherical
'26 48 8E 8E 30 30 1C 02'= Sigma APO Tele Macro 300mm F4
'26 54 2B 44 24 30 1C 02'= Sigma 17-35mm F2.8-4 EX Aspherical
'26 54 37 5C 24 24 1C 02'= Sigma 24-70mm F2.8 EX DG Macro
'26 54 37 73 24 34 1C 02'= Sigma 24-135mm F2.8-4.5
'26 54 3C 5C 24 24 1C 02'= Sigma 28-70mm F2.8 EX
'26 58 31 31 14 14 1C 02'= Sigma 20mm F1.8 EX DG Aspherical RF
'26 58 37 37 14 14 1C 02'= Sigma 24mm F1.8 EX DG Aspherical Macro
'26 58 3C 3C 14 14 1C 02'= Sigma 28mm F1.8 EX DG Aspherical Macro
'27 48 8E 8E 24 24 1D 02'= AF-I Nikkor 300mm f/2.8D IF-ED
'27 48 8E 8E 24 24 E1 02'= AF-I Nikkor 300mm f/2.8D IF-ED + TC-17E
'27 48 8E 8E 24 24 F1 02'= AF-I Nikkor 300mm f/2.8D IF-ED + TC-14E
'27 48 8E 8E 24 24 F2 02'= AF-I Nikkor 300mm f/2.8D IF-ED + TC-20E
'27 48 8E 8E 30 30 1D 02'= Tokina AT-X 304 AF (AF 300mm f/4.0)
'27 54 8E 8E 24 24 1D 02'= Tamron SP AF 300mm f/2.8 LD-IF (360E)
'28 3C A6 A6 30 30 1D 02'= AF-I Nikkor 600mm f/4D IF-ED
'28 3C A6 A6 30 30 E1 02'= AF-I Nikkor 600mm f/4D IF-ED + TC-17E
'28 3C A6 A6 30 30 F1 02'= AF-I Nikkor 600mm f/4D IF-ED + TC-14E
'28 3C A6 A6 30 30 F2 02'= AF-I Nikkor 600mm f/4D IF-ED + TC-20E
'2A 54 3C 3C 0C 0C 26 02'= AF Nikkor 28mm f/1.4D
'2B 3C 44 60 30 3C 1F 02'= AF Zoom-Nikkor 35-80mm f/4-5.6D
'2C 48 6A 6A 18 18 27 02'= AF DC-Nikkor 105mm f/2D
'2D 48 80 80 30 30 21 02'= AF Micro-Nikkor 200mm f/4D IF-ED
'2E 48 5C 82 30 3C 22 02'= AF Nikkor 70-210mm f/4-5.6D
'2E 48 5C 82 30 3C 28 02'= AF Nikkor 70-210mm f/4-5.6D
'2F 40 30 44 2C 34 29 02'= Tokina AF 235 II (AF 20-35mm f/3.5-4.5)
'2F 40 30 44 2C 34 29 02'= Tokina AF 193 (AF 19-35mm f/3.5-4.5)
'2F 48 30 44 24 24 29 02'= AF Zoom-Nikkor 20-35mm f/2.8D IF
'2F 48 30 44 24 24 29 02'= Tokina AT-X 235 AF PRO (AF 20-35mm f/2.8)
'30 48 98 98 24 24 24 02'= AF-I Nikkor 400mm f/2.8D IF-ED
'30 48 98 98 24 24 E1 02'= AF-I Nikkor 400mm f/2.8D IF-ED + TC-17E
'30 48 98 98 24 24 F1 02'= AF-I Nikkor 400mm f/2.8D IF-ED + TC-14E
'30 48 98 98 24 24 F2 02'= AF-I Nikkor 400mm f/2.8D IF-ED + TC-20E
'31 54 56 56 24 24 25 02'= AF Micro-Nikkor 60mm f/2.8D
'32 53 64 64 24 24 35 02'= Tamron SP AF 90mm f/2.8 [Di] Macro 1:1 (172E/272E)
'32 54 50 50 24 24 35 02'= Sigma Macro 50mm F2.8 EX DG
'32 54 6A 6A 24 24 35 02'= AF Micro-Nikkor 105mm f/2.8D
'32 54 6A 6A 24 24 35 02'= Sigma Macro 105mm F2.8 EX DG
'33 48 2D 2D 24 24 31 02'= AF Nikkor 18mm f/2.8D
'33 54 3C 5E 24 24 62 02'= Tamron SP AF 28-75mm f/2.8 XR Di LD Aspherical (IF) Macro (A09)
'34 48 29 29 24 24 32 02'= AF Fisheye Nikkor 16mm f/2.8D
'35 3C A0 A0 30 30 33 02'= AF-I Nikkor 500mm f/4D IF-ED
'35 3C A0 A0 30 30 E1 02'= AF-I Nikkor 500mm f/4D IF-ED + TC-17E
'35 3C A0 A0 30 30 F1 02'= AF-I Nikkor 500mm f/4D IF-ED + TC-14E
'35 3C A0 A0 30 30 F2 02'= AF-I Nikkor 500mm f/4D IF-ED + TC-20E
'36 48 37 37 24 24 34 02'= AF Nikkor 24mm f/2.8D
'37 48 30 30 24 24 36 02'= AF Nikkor 20mm f/2.8D
'38 4C 62 62 14 14 37 02'= AF Nikkor 85mm f/1.8D
'3A 40 3C 5C 2C 34 39 02'= AF Zoom-Nikkor 28-70mm f/3.5-4.5D
'3B 48 44 5C 24 24 3A 02'= AF Zoom-Nikkor 35-70mm f/2.8D N
'3C 48 60 80 24 24 3B 02'= AF Zoom-Nikkor 80-200mm f/2.8D ED
'3D 3C 44 60 30 3C 3E 02'= AF Zoom-Nikkor 35-80mm f/4-5.6D
'3E 48 3C 3C 24 24 3D 02'= AF Nikkor 28mm f/2.8D
'3F 40 44 6A 2C 34 45 02'= AF Zoom-Nikkor 35-105mm f/3.5-4.5D
'41 48 7C 7C 24 24 43 02'= AF Nikkor 180mm f/2.8D IF-ED
'42 54 44 44 18 18 44 02'= AF Nikkor 35mm f/2D
'43 54 50 50 0C 0C 46 02'= AF Nikkor 50mm f/1.4D
'44 44 60 80 34 3C 47 02'= AF Zoom-Nikkor 80-200mm f/4.5-5.6D
'45 3D 3C 60 2C 3C 48 02'= Tamron AF 28-80mm f/3.5-5.6 Aspherical (177D)
'45 40 3C 60 2C 3C 48 02'= AF Zoom-Nikkor 28-80mm f/3.5-5.6D
'45 41 37 72 2C 3C 48 02'= Tamron SP AF 24-135mm f/3.5-5.6 AD Aspherical (IF) Macro (190D)
'46 3C 44 60 30 3C 49 02'= AF Zoom-Nikkor 35-80mm f/4-5.6D N
'47 42 37 50 2A 34 4A 02'= AF Zoom-Nikkor 24-50mm f/3.3-4.5D
'48 38 1F 37 34 3C 4B 06'= Sigma 12-24mm F4.5-5.6 EX DG Aspherical HSM
'48 3C 19 31 30 3C 4B 06'= Sigma 10-20mm F4-5.6 EX DC HSM
'48 3C 50 A0 30 40 4B 02'= Sigma 50-500mm F4-6.3 EX APO RF HSM
'48 3C 8E B0 3C 3C 4B 02'= Sigma APO 300-800mm F5.6 EX DG HSM
'48 3C B0 B0 3C 3C 4B 02'= Sigma APO 800mm F5.6 EX HSM
'48 44 A0 A0 34 34 4B 02'= Sigma APO 500mm F4.5 EX HSM
'48 48 24 24 24 24 4B 02'= Sigma 14mm F2.8 EX Aspherical HSM
'48 48 2B 44 24 30 4B 06'= Sigma 17-35mm F2.8-4 EX DG Aspherical HSM
'48 48 68 8E 30 30 4B 02'= Sigma APO 100-300mm F4 EX IF HSM
'48 48 76 76 24 24 4B 06'= Sigma APO Macro 150mm F2.8 EX DG HSM
'48 48 8E 8E 24 24 4B 02'= AF-S Nikkor 300mm f/2.8D IF-ED
'48 48 8E 8E 24 24 E1 02'= AF-S Nikkor 300mm f/2.8D IF-ED + TC-17E
'48 48 8E 8E 24 24 F1 02'= AF-S Nikkor 300mm f/2.8D IF-ED + TC-14E
'48 48 8E 8E 24 24 F2 02'= AF-S Nikkor 300mm f/2.8D IF-ED + TC-20E
'48 4C 7C 7C 2C 2C 4B 02'= Sigma APO Macro 180mm F3.5 EX DG HSM
'48 4C 7D 7D 2C 2C 4B 02'= Sigma APO Macro 180mm F3.5 EX DG HSM
'48 54 3E 3E 0C 0C 4B 06'= Sigma 30mm F1.4 EX DC HSM
'48 54 5C 80 24 24 4B 02'= Sigma 70-200mm F2.8 EX APO IF HSM
'48 54 6F 8E 24 24 4B 02'= Sigma APO 120-300mm F2.8 EX DG HSM
'48 54 8E 8E 24 24 4B 02'= Sigma APO 300mm F2.8 EX DG HSM
'49 3C A6 A6 30 30 4C 02'= AF-S Nikkor 600mm f/4D IF-ED
'49 3C A6 A6 30 30 E1 02'= AF-S Nikkor 600mm f/4D IF-ED + TC-17E
'49 3C A6 A6 30 30 F1 02'= AF-S Nikkor 600mm f/4D IF-ED + TC-14E
'49 3C A6 A6 30 30 F2 02'= AF-S Nikkor 600mm f/4D IF-ED + TC-20E
'4A 40 11 11 2C 0C 4D 02'= Samyang 8mm f/3.5 Fish-Eye CS
'4A 48 1E 1E 24 0C 4D 02'= Samyang 12mm f/2.8 ED AS NCS Fish-Eye
'4A 48 24 24 24 0C 4D 02'= Samyang 10mm f/2.8 ED AS NCS CS
'4A 48 24 24 24 0C 4D 02'= Samyang AE 14mm f/2.8 ED AS IF UMC
'4A 4C 24 24 1E 6C 4D 06'= Samyang 14mm f/2.4 Premium
'4A 54 29 29 18 0C 4D 02'= Samyang 16mm f/2.0 ED AS UMC CS
'4A 54 62 62 0C 0C 4D 02'= AF Nikkor 85mm f/1.4D IF
'4A 58 30 30 14 0C 4D 02'= Rokinon 20mm f/1.8 ED AS UMC
'4A 60 36 36 0C 0C 4D 02'= Samyang 24mm f/1.4 ED AS UMC
'4A 60 44 44 0C 0C 4D 02'= Samyang 35mm f/1.4 AS UMC
'4A 60 62 62 0C 0C 4D 02'= Samyang AE 85mm f/1.4 AS IF UMC
'4B 3C A0 A0 30 30 4E 02'= AF-S Nikkor 500mm f/4D IF-ED
'4B 3C A0 A0 30 30 E1 02'= AF-S Nikkor 500mm f/4D IF-ED + TC-17E
'4B 3C A0 A0 30 30 F1 02'= AF-S Nikkor 500mm f/4D IF-ED + TC-14E
'4B 3C A0 A0 30 30 F2 02'= AF-S Nikkor 500mm f/4D IF-ED + TC-20E
'4C 40 37 6E 2C 3C 4F 02'= AF Zoom-Nikkor 24-120mm f/3.5-5.6D IF
'4D 3E 3C 80 2E 3C 62 02'= Tamron AF 28-200mm f/3.8-5.6 XR Aspherical (IF) Macro (A03N)
'4D 40 3C 80 2C 3C 62 02'= AF Zoom-Nikkor 28-200mm f/3.5-5.6D IF
'4D 41 3C 8E 2B 40 62 02'= Tamron AF 28-300mm f/3.5-6.3 XR Di LD Aspherical (IF) (A061)
'4D 41 3C 8E 2C 40 62 02'= Tamron AF 28-300mm f/3.5-6.3 XR LD Aspherical (IF) (185D)
'4E 48 72 72 18 18 51 02'= AF DC-Nikkor 135mm f/2D
'4F 40 37 5C 2C 3C 53 06'= IX-Nikkor 24-70mm f/3.5-5.6
'50 48 56 7C 30 3C 54 06'= IX-Nikkor 60-180mm f/4-5.6
'52 54 44 44 18 18 00 00'= Zeiss Milvus 35mm f/2
'53 48 60 80 24 24 57 02'= AF Zoom-Nikkor 80-200mm f/2.8D ED
'53 48 60 80 24 24 60 02'= AF Zoom-Nikkor 80-200mm f/2.8D ED
'53 54 50 50 0C 0C 00 00'= Zeiss Milvus 50mm f/1.4
'54 44 5C 7C 34 3C 58 02'= AF Zoom-Micro Nikkor 70-180mm f/4.5-5.6D ED
'54 44 5C 7C 34 3C 61 02'= AF Zoom-Micro Nikkor 70-180mm f/4.5-5.6D ED
'54 54 50 50 18 18 00 00'= Zeiss Milvus 50mm f/2 Macro
'55 54 62 62 0C 0C 00 00'= Zeiss Milvus 85mm f/1.4
'56 3C 5C 8E 30 3C 1C 02'= Sigma 70-300mm F4-5.6 APO Macro Super II
'56 48 5C 8E 30 3C 5A 02'= AF Zoom-Nikkor 70-300mm f/4-5.6D ED
'56 54 68 68 18 18 00 00'= Zeiss Milvus 100mm f/2 Macro
'59 48 98 98 24 24 5D 02'= AF-S Nikkor 400mm f/2.8D IF-ED
'59 48 98 98 24 24 E1 02'= AF-S Nikkor 400mm f/2.8D IF-ED + TC-17E
'59 48 98 98 24 24 F1 02'= AF-S Nikkor 400mm f/2.8D IF-ED + TC-14E
'59 48 98 98 24 24 F2 02'= AF-S Nikkor 400mm f/2.8D IF-ED + TC-20E
'5A 3C 3E 56 30 3C 5E 06'= IX-Nikkor 30-60mm f/4-5.6
'5B 44 56 7C 34 3C 5F 06'= IX-Nikkor 60-180mm f/4.5-5.6
'5D 48 3C 5C 24 24 63 02'= AF-S Zoom-Nikkor 28-70mm f/2.8D IF-ED
'5E 48 60 80 24 24 64 02'= AF-S Zoom-Nikkor 80-200mm f/2.8D IF-ED
'5F 40 3C 6A 2C 34 65 02'= AF Zoom-Nikkor 28-105mm f/3.5-4.5D IF
'60 40 3C 60 2C 3C 66 02'= AF Zoom-Nikkor 28-80mm f/3.5-5.6D
'61 44 5E 86 34 3C 67 02'= AF Zoom-Nikkor 75-240mm f/4.5-5.6D
'63 48 2B 44 24 24 68 02'= AF-S Nikkor 17-35mm f/2.8D IF-ED
'64 00 62 62 24 24 6A 02'= PC Micro-Nikkor 85mm f/2.8D
'65 44 60 98 34 3C 6B 0A'= AF VR Zoom-Nikkor 80-400mm f/4.5-5.6D ED
'66 40 2D 44 2C 34 6C 02'= AF Zoom-Nikkor 18-35mm f/3.5-4.5D IF-ED
'67 48 37 62 24 30 6D 02'= AF Zoom-Nikkor 24-85mm f/2.8-4D IF
'67 54 37 5C 24 24 1C 02'= Sigma 24-70mm F2.8 EX DG Macro
'68 42 3C 60 2A 3C 6E 06'= AF Zoom-Nikkor 28-80mm f/3.3-5.6G
'69 47 5C 8E 30 3C 00 02'= Tamron AF 70-300mm f/4-5.6 Di LD Macro 1:2 (A17N)
'69 48 5C 8E 30 3C 6F 02'= Tamron AF 70-300mm f/4-5.6 LD Macro 1:2 (572D/772D)
'69 48 5C 8E 30 3C 6F 06'= AF Zoom-Nikkor 70-300mm f/4-5.6G
'6A 48 8E 8E 30 30 70 02'= AF-S Nikkor 300mm f/4D IF-ED
'6B 48 24 24 24 24 71 02'= AF Nikkor ED 14mm f/2.8D
'6D 48 8E 8E 24 24 73 02'= AF-S Nikkor 300mm f/2.8D IF-ED II
'6E 48 98 98 24 24 74 02'= AF-S Nikkor 400mm f/2.8D IF-ED II
'6F 3C A0 A0 30 30 75 02'= AF-S Nikkor 500mm f/4D IF-ED II
'70 3C A6 A6 30 30 76 02'= AF-S Nikkor 600mm f/4D IF-ED II
'71 48 64 64 24 24 00 00'= Voigtlander APO-Skopar 90mm F2.8 SL IIs
'72 48 4C 4C 24 24 77 00'= Nikkor 45mm f/2.8 P
'74 40 37 62 2C 34 78 06'= AF-S Zoom-Nikkor 24-85mm f/3.5-4.5G IF-ED
'75 40 3C 68 2C 3C 79 06'= AF Zoom-Nikkor 28-100mm f/3.5-5.6G
'76 58 50 50 14 14 7A 02'= AF Nikkor 50mm f/1.8D
'77 44 60 98 34 3C 7B 0E'= Sigma 80-400mm F4.5-5.6 APO DG D OS
'77 44 61 98 34 3C 7B 0E'= Sigma 80-400mm F4.5-5.6 EX OS
'77 48 5C 80 24 24 7B 0E'= AF-S VR Zoom-Nikkor 70-200mm f/2.8G IF-ED
'78 40 37 6E 2C 3C 7C 0E'= AF-S VR Zoom-Nikkor 24-120mm f/3.5-5.6G IF-ED
'79 40 11 11 2C 2C 1C 06'= Sigma 8mm F3.5 EX Circular Fisheye
'79 40 3C 80 2C 3C 7F 06'= AF Zoom-Nikkor 28-200mm f/3.5-5.6G IF-ED
'79 48 3C 5C 24 24 1C 06'= Sigma 28-70mm F2.8 EX DG
'79 48 5C 5C 24 24 1C 06'= Sigma Macro 70mm F2.8 EX DG
'79 54 31 31 0C 0C 4B 06'= Sigma 20mm F1.4 DG HSM | A
'7A 3B 53 80 30 3C 4B 06'= Sigma 55-200mm F4-5.6 DC HSM
'7A 3C 1F 37 30 30 7E 06'= AF-S DX Zoom-Nikkor 12-24mm f/4G IF-ED
'7A 3C 1F 37 30 30 7E 06'= Tokina AT-X 124 AF PRO DX II (AF 12-24mm f/4)
'7A 3C 1F 3C 30 30 7E 06'= Tokina AT-X 12-28 PRO DX (AF 12-28mm f/4)
'7A 40 2D 50 2C 3C 4B 06'= Sigma 18-50mm F3.5-5.6 DC HSM
'7A 40 2D 80 2C 40 4B 0E'= Sigma 18-200mm F3.5-6.3 DC OS HSM
'7A 47 2B 5C 24 34 4B 06'= Sigma 17-70mm F2.8-4.5 DC Macro Asp. IF HSM
'7A 47 50 76 24 24 4B 06'= Sigma 50-150mm F2.8 EX APO DC HSM
'7A 48 1C 29 24 24 7E 06'= Tokina AT-X 116 PRO DX II (AF 11-16mm f/2.8)
'7A 48 1C 30 24 24 7E 06'= Tokina AT-X 11-20 F2.8 PRO DX (AF 11-20mm f/2.8)
'7A 48 2B 5C 24 34 4B 06'= Sigma 17-70mm F2.8-4.5 DC Macro Asp. IF HSM
'7A 48 2D 50 24 24 4B 06'= Sigma 18-50mm F2.8 EX DC Macro
'7A 48 5C 80 24 24 4B 06'= Sigma 70-200mm F2.8 EX APO DG Macro HSM II
'7A 54 6E 8E 24 24 4B 02'= Sigma APO 120-300mm F2.8 EX DG HSM
'7B 48 37 44 18 18 4B 06'= Sigma 24-35mm F2.0 DG HSM | A
'7B 48 80 98 30 30 80 0E'= AF-S VR Zoom-Nikkor 200-400mm f/4G IF-ED
'7C 54 2B 50 24 24 00 06'= Tamron SP AF 17-50mm f/2.8 XR Di II LD Aspherical (IF) (A16)
'7D 48 2B 53 24 24 82 06'= AF-S DX Zoom-Nikkor 17-55mm f/2.8G IF-ED
'7E 54 37 37 0C 0C 4B 06'= Sigma 24mm F1.4 DG HSM | A
'7F 40 2D 5C 2C 34 84 06'= AF-S DX Zoom-Nikkor 18-70mm f/3.5-4.5G IF-ED
'7F 48 2B 5C 24 34 1C 06'= Sigma 17-70mm F2.8-4.5 DC Macro Asp. IF
'7F 48 2D 50 24 24 1C 06'= Sigma 18-50mm F2.8 EX DC Macro
'80 48 1A 1A 24 24 85 06'= AF DX Fisheye-Nikkor 10.5mm f/2.8G ED
'80 48 1C 29 24 24 7A 06'= Tokina atx-i 11-16mm F2.8 CF
'81 34 76 A6 38 40 4B 0E'= Sigma 150-600mm F5-6.3 DG OS HSM | S
'81 54 80 80 18 18 86 0E'= AF-S VR Nikkor 200mm f/2G IF-ED
'82 34 76 A6 38 40 4B 0E'= Sigma 150-600mm F5-6.3 DG OS HSM | C
'82 48 8E 8E 24 24 87 0E'= AF-S VR Nikkor 300mm f/2.8G IF-ED
'83 00 B0 B0 5A 5A 88 04'= FSA-L2, EDG 65, 800mm F13 G
'87 2C 2D 8E 2C 40 4B 0E'= Sigma 18-300mm F3.5-6.3 DC Macro HSM
'88 54 50 50 0C 0C 4B 06'= Sigma 50mm F1.4 DG HSM | A
'89 30 2D 80 2C 40 4B 0E'= Sigma 18-200mm F3.5-6.3 DC Macro OS HS | C
'89 3C 53 80 30 3C 8B 06'= AF-S DX Zoom-Nikkor 55-200mm f/4-5.6G ED
'8A 3C 37 6A 30 30 4B 0E'= Sigma 24-105mm F4 DG OS HSM
'8A 54 6A 6A 24 24 8C 0E'= AF-S VR Micro-Nikkor 105mm f/2.8G IF-ED
'8B 40 2D 80 2C 3C 8D 0E'= AF-S DX VR Zoom-Nikkor 18-200mm f/3.5-5.6G IF-ED
'8B 40 2D 80 2C 3C FD 0E'= AF-S DX VR Zoom-Nikkor 18-200mm f/3.5-5.6G IF-ED [II]
'8B 48 1C 30 24 24 85 06'= Tokina AT-X 11-20 F2.8 PRO DX (AF 11-20mm f/2.8)
'8B 4C 2D 44 14 14 4B 06'= Sigma 18-35mm F1.8 DC HSM
'8C 40 2D 53 2C 3C 8E 06'= AF-S DX Zoom-Nikkor 18-55mm f/3.5-5.6G ED
'8C 48 29 3C 24 24 86 06'= Tokina opera 16-28mm F2.8 FF
'8D 44 5C 8E 34 3C 8F 0E'= AF-S VR Zoom-Nikkor 70-300mm f/4.5-5.6G IF-ED
'8D 48 6E 8E 24 24 4B 0E'= Sigma 120-300mm F2.8 DG OS HSM Sports
'8D 54 68 68 24 24 87 02'= Tokina AT-X PRO 100mm F2.8 D Macro
'8E 3C 2B 5C 24 30 4B 0E'= Sigma 17-70mm F2.8-4 DC Macro OS HSM | C
'8F 40 2D 72 2C 3C 91 06'= AF-S DX Zoom-Nikkor 18-135mm f/3.5-5.6G IF-ED
'8F 48 2B 50 24 24 4B 0E'= Sigma 17-50mm F2.8 EX DC OS HSM
'90 3B 53 80 30 3C 92 0E'= AF-S DX VR Zoom-Nikkor 55-200mm f/4-5.6G IF-ED
'90 40 2D 80 2C 40 4B 0E'= Sigma 18-200mm F3.5-6.3 II DC OS HSM
'91 54 44 44 0C 0C 4B 06'= Sigma 35mm F1.4 DG HSM
'92 2C 2D 88 2C 40 4B 0E'= Sigma 18-250mm F3.5-6.3 DC Macro OS HSM
'92 48 24 37 24 24 94 06'= AF-S Zoom-Nikkor 14-24mm f/2.8G ED
'93 48 37 5C 24 24 95 06'= AF-S Zoom-Nikkor 24-70mm f/2.8G ED
'94 40 2D 53 2C 3C 96 06'= AF-S DX Zoom-Nikkor 18-55mm f/3.5-5.6G ED II
'94 48 7C 7C 24 24 4B 0E'= Sigma APO Macro 180mm F2.8 EX DG OS HSM
'95 00 37 37 2C 2C 97 06'= PC-E Nikkor 24mm f/3.5D ED
'95 4C 37 37 2C 2C 97 02'= PC-E Nikkor 24mm f/3.5D ED
'96 38 1F 37 34 3C 4B 06'= Sigma 12-24mm F4.5-5.6 II DG HSM
'96 48 98 98 24 24 98 0E'= AF-S VR Nikkor 400mm f/2.8G ED
'97 3C A0 A0 30 30 99 0E'= AF-S VR Nikkor 500mm f/4G ED
'97 48 6A 6A 24 24 4B 0E'= Sigma Macro 105mm F2.8 EX DG OS HSM
'98 3C A6 A6 30 30 9A 0E'= AF-S VR Nikkor 600mm f/4G ED
'98 48 50 76 24 24 4B 0E'= Sigma 50-150mm F2.8 EX APO DC OS HSM
'99 40 29 62 2C 3C 9B 0E'= AF-S DX VR Zoom-Nikkor 16-85mm f/3.5-5.6G ED
'99 48 76 76 24 24 4B 0E'= Sigma APO Macro 150mm F2.8 EX DG OS HSM
'9A 40 2D 53 2C 3C 9C 0E'= AF-S DX VR Zoom-Nikkor 18-55mm f/3.5-5.6G
'9A 4C 50 50 14 14 9C 06'= Yongnuo YN50mm F1.8N
'9B 00 4C 4C 24 24 9D 06'= PC-E Micro Nikkor 45mm f/2.8D ED
'9B 54 4C 4C 24 24 9D 02'= PC-E Micro Nikkor 45mm f/2.8D ED
'9B 54 62 62 0C 0C 4B 06'= Sigma 85mm F1.4 EX DG HSM
'9C 48 5C 80 24 24 4B 0E'= Sigma 70-200mm F2.8 EX DG OS HSM
'9C 54 56 56 24 24 9E 06'= AF-S Micro Nikkor 60mm f/2.8G ED
'9D 00 62 62 24 24 9F 06'= PC-E Micro Nikkor 85mm f/2.8D
'9D 48 2B 50 24 24 4B 0E'= Sigma 17-50mm F2.8 EX DC OS HSM
'9D 54 62 62 24 24 9F 02'= PC-E Micro Nikkor 85mm f/2.8D
'9E 38 11 29 34 3C 4B 06'= Sigma 8-16mm F4.5-5.6 DC HSM
'9E 40 2D 6A 2C 3C A0 0E'= AF-S DX VR Zoom-Nikkor 18-105mm f/3.5-5.6G ED
'9F 37 50 A0 34 40 4B 0E'= Sigma 50-500mm F4.5-6.3 DG OS HSM
'9F 48 48 48 24 24 A1 06'= Yongnuo YN40mm F2.8N
'9F 54 68 68 18 18 A2 06'= Yongnuo YN100mm F2N
'9F 58 44 44 14 14 A1 06'= AF-S DX Nikkor 35mm f/1.8G
'A0 37 5C 8E 34 3C A2 06'= Sony FE 70-300mm F4.5-5.6 G OSS
'A0 40 2D 53 2C 3C CA 0E'= AF-P DX Nikkor 18-55mm f/3.5-5.6G VR
'A0 40 2D 53 2C 3C CA 8E'= AF-P DX Nikkor 18-55mm f/3.5-5.6G
'A0 40 2D 74 2C 3C BB 0E'= AF-S DX Nikkor 18-140mm f/3.5-5.6G ED VR
'A0 48 2A 5C 24 30 4B 0E'= Sigma 17-70mm F2.8-4 DC Macro OS HSM
'A0 54 50 50 0C 0C A2 06'= AF-S Nikkor 50mm f/1.4G
'A0 56 44 44 14 14 A2 06'= Sony FE 35mm F1.8
'A1 40 18 37 2C 34 A3 06'= AF-S DX Nikkor 10-24mm f/3.5-4.5G ED
'A1 40 2D 53 2C 3C CB 86'= AF-P DX Nikkor 18-55mm f/3.5-5.6G
'A1 41 19 31 2C 2C 4B 06'= Sigma 10-20mm F3.5 EX DC HSM
'A1 48 6E 8E 24 24 DB 4E'= AF-S Nikkor 120-300mm f/2.8E FL ED SR VR
'A1 54 55 55 0C 0C BC 06'= AF-S Nikkor 58mm f/1.4G
'A2 38 5C 8E 34 40 CD 86'= AF-P DX Nikkor 70-300mm f/4.5-6.3G VR
'A2 40 2D 53 2C 3C BD 0E'= AF-S DX Nikkor 18-55mm f/3.5-5.6G VR II
'A2 48 5C 80 24 24 A4 0E'= AF-S Nikkor 70-200mm f/2.8G ED VR II
'A3 38 5C 8E 34 40 CE 0E'= AF-P DX Nikkor 70-300mm f/4.5-6.3G ED
'A3 38 5C 8E 34 40 CE 8E'= AF-P DX Nikkor 70-300mm f/4.5-6.3G ED VR
'A3 3C 29 44 30 30 A5 0E'= AF-S Nikkor 16-35mm f/4G ED VR
'A3 3C 5C 8E 30 3C 4B 0E'= Sigma 70-300mm F4-5.6 DG OS
'A4 40 2D 8E 2C 40 BF 0E'= AF-S DX Nikkor 18-300mm f/3.5-6.3G ED VR
'A4 47 2D 50 24 34 4B 0E'= Sigma 18-50mm F2.8-4.5 DC OS HSM
'A4 48 5C 80 24 24 CF 0E'= AF-S Nikkor 70-200mm f/2.8E FL ED VR
'A4 48 5C 80 24 24 CF 4E'= AF-S Nikkor 70-200mm f/2.8E FL ED VR
'A4 54 37 37 0C 0C A6 06'= AF-S Nikkor 24mm f/1.4G ED
'A5 40 2D 88 2C 40 4B 0E'= Sigma 18-250mm F3.5-6.3 DC OS HSM
'A5 40 3C 8E 2C 3C A7 0E'= AF-S Nikkor 28-300mm f/3.5-5.6G ED VR
'A5 4C 44 44 14 14 C0 06'= AF-S Nikkor 35mm f/1.8G ED
'A5 54 6A 6A 0C 0C D0 06'= AF-S Nikkor 105mm f/1.4E ED
'A5 54 6A 6A 0C 0C D0 46'= AF-S Nikkor 105mm f/1.4E ED
'A6 48 2F 2F 30 30 D1 06'= PC Nikkor 19mm f/4E ED
'A6 48 2F 2F 30 30 D1 46'= PC Nikkor 19mm f/4E ED
'A6 48 37 5C 24 24 4B 06'= Sigma 24-70mm F2.8 IF EX DG HSM
'A6 48 8E 8E 24 24 A8 0E'= AF-S Nikkor 300mm f/2.8G IF-ED VR II
'A6 48 98 98 24 24 C1 0E'= AF-S Nikkor 400mm f/2.8E FL ED VR
'A7 3C 53 80 30 3C C2 0E'= AF-S DX Nikkor 55-200mm f/4-5.6G ED VR II
'A7 40 11 26 2C 34 D2 06'= AF-S Fisheye Nikkor 8-15mm f/3.5-4.5E ED
'A7 40 11 26 2C 34 D2 46'= AF-S Fisheye Nikkor 8-15mm f/3.5-4.5E ED
'A7 49 80 A0 24 24 4B 06'= Sigma APO 200-500mm F2.8 EX DG
'A7 4B 62 62 2C 2C A9 0E'= AF-S DX Micro Nikkor 85mm f/3.5G ED VR
'A8 38 18 30 34 3C D3 0E'= AF-P DX Nikkor 10-20mm f/4.5-5.6G VR
'A8 38 18 30 34 3C D3 8E'= AF-P DX Nikkor 10-20mm f/4.5-5.6G VR
'A8 48 80 98 30 30 AA 0E'= AF-S Zoom-Nikkor 200-400mm f/4G IF-ED VR II
'A8 48 8E 8E 30 30 C3 0E'= AF-S Nikkor 300mm f/4E PF ED VR
'A8 48 8E 8E 30 30 C3 4E'= AF-S Nikkor 300mm f/4E PF ED VR
'A9 48 7C 98 30 30 D4 0E'= AF-S Nikkor 180-400mm f/4E TC1.4 FL ED VR
'A9 48 7C 98 30 30 D4 4E'= AF-S Nikkor 180-400mm f/4E TC1.4 FL ED VR
'A9 4C 31 31 14 14 C4 06'= AF-S Nikkor 20mm f/1.8G ED
'A9 54 80 80 18 18 AB 0E'= AF-S Nikkor 200mm f/2G ED VR II
'AA 3C 37 6E 30 30 AC 0E'= AF-S Nikkor 24-120mm f/4G ED VR
'AA 48 37 5C 24 24 C5 0E'= AF-S Nikkor 24-70mm f/2.8E ED VR
'AA 48 37 5C 24 24 C5 4E'= AF-S Nikkor 24-70mm f/2.8E ED VR
'AA 48 88 A4 3C 3C D5 0E'= AF-S Nikkor 180-400mm f/4E TC1.4 FL ED VR + 1.4x TC
'AA 48 88 A4 3C 3C D5 4E'= AF-S Nikkor 180-400mm f/4E TC1.4 FL ED VR + 1.4x TC
'AB 3C A0 A0 30 30 C6 4E'= AF-S Nikkor 500mm f/4E FL ED VR
'AB 44 5C 8E 34 3C D6 0E'= AF-P Nikkor 70-300mm f/4.5-5.6E ED VR
'AB 44 5C 8E 34 3C D6 4E'= AF-P Nikkor 70-300mm f/4.5-5.6E ED VR
'AB 44 5C 8E 34 3C D6 CE'= AF-P Nikkor 70-300mm f/4.5-5.6E ED VR
'AC 38 53 8E 34 3C AE 0E'= AF-S DX Nikkor 55-300mm f/4.5-5.6G ED VR
'AC 3C A6 A6 30 30 C7 4E'= AF-S Nikkor 600mm f/4E FL ED VR
'AC 54 3C 3C 0C 0C D7 06'= AF-S Nikkor 28mm f/1.4E ED
'AC 54 3C 3C 0C 0C D7 46'= AF-S Nikkor 28mm f/1.4E ED
'AD 3C 2D 8E 2C 3C AF 0E'= AF-S DX Nikkor 18-300mm f/3.5-5.6G ED VR
'AD 3C A0 A0 3C 3C D8 0E'= AF-S Nikkor 500mm f/5.6E PF ED VR
'AD 3C A0 A0 3C 3C D8 4E'= AF-S Nikkor 500mm f/5.6E PF ED VR
'AD 48 28 60 24 30 C8 0E'= AF-S DX Nikkor 16-80mm f/2.8-4E ED VR
'AD 48 28 60 24 30 C8 4E'= AF-S DX Nikkor 16-80mm f/2.8-4E ED VR
'AE 3C 80 A0 3C 3C C9 0E'= AF-S Nikkor 200-500mm f/5.6E ED VR
'AE 3C 80 A0 3C 3C C9 4E'= AF-S Nikkor 200-500mm f/5.6E ED VR
'AE 54 62 62 0C 0C B0 06'= AF-S Nikkor 85mm f/1.4G
'AF 4C 37 37 14 14 CC 06'= AF-S Nikkor 24mm f/1.8G ED
'AF 54 44 44 0C 0C B1 06'= AF-S Nikkor 35mm f/1.4G
'B0 4C 50 50 14 14 B2 06'= AF-S Nikkor 50mm f/1.8G
'B1 48 48 48 24 24 B3 06'= AF-S DX Micro Nikkor 40mm f/2.8G
'B2 48 5C 80 30 30 B4 0E'= AF-S Nikkor 70-200mm f/4G ED VR
'B3 4C 62 62 14 14 B5 06'= AF-S Nikkor 85mm f/1.8G
'B4 40 37 62 2C 34 B6 0E'= AF-S Zoom-Nikkor 24-85mm f/3.5-4.5G IF-ED VR
'B5 4C 3C 3C 14 14 B7 06'= AF-S Nikkor 28mm f/1.8G
'B6 3C B0 B0 3C 3C B8 0E'= AF-S VR Nikkor 800mm f/5.6E FL ED
'B6 3C B0 B0 3C 3C B8 4E'= AF-S VR Nikkor 800mm f/5.6E FL ED
'B6 48 37 56 24 24 1C 02'= Sigma 24-60mm F2.8 EX DG
'B7 44 60 98 34 3C B9 0E'= AF-S Nikkor 80-400mm f/4.5-5.6G ED VR
'B8 40 2D 44 2C 34 BA 06'= AF-S Nikkor 18-35mm f/3.5-4.5G ED
'BB 48 5C 80 24 24 4B 4E'= Sigma 70-200mm F2.8 DG OS HSM | S
'BD 54 48 48 0C 0C 4B 46'= Sigma 40mm F1.4 DG HSM | A
'BE 54 6A 6A 0C 0C 4B 46'= Sigma 105mm F1.4 DG HSM | A
'BF 3C 1B 1B 30 30 01 04'= Irix 11mm f/4 Firefly
'BF 4E 26 26 1E 1E 01 04'= Irix 15mm f/2.4 Firefly
'C1 48 24 37 24 24 4B 46'= Sigma 14-24mm F2.8 DG HSM | A
'C2 4C 24 24 14 14 4B 06'= Sigma 14mm F1.8 DG HSM | A
'C3 34 68 98 38 40 4B 4E'= Sigma 100-400mm F5-6.3 DG OS HSM | C
'C4 4C 73 73 14 14 4B 46'= Sigma 135mm F1.8 DG HSM | A
'C8 54 44 44 0D 0D DF 46'= Tamron SP 35mm f/1.4 Di USD (F045)
'C8 54 62 62 0C 0C 4B 06'= Sigma 85mm F1.4 DG HSM | A
'C8 54 62 62 0C 0C 4B 46'= Sigma 85mm F1.4 DG HSM | A
'C9 3C 44 76 25 31 DF 4E'= Tamron 35-150mm f/2.8-4 Di VC OSD (A043)
'C9 48 37 5C 24 24 4B 4E'= Sigma 24-70mm F2.8 DG OS HSM | A
'CA 3C 1F 37 30 30 4B 46'= Sigma 12-24mm F4 DG HSM | A
'CA 48 27 3E 24 24 DF 4E'= Tamron SP 15-30mm f/2.8 Di VC USD G2 (A041)
'CB 3C 2B 44 24 31 DF 46'= Tamron 17-35mm f/2.8-4 Di OSD (A037)
'CC 44 68 98 34 41 DF 0E'= Tamron 100-400mm f/4.5-6.3 Di VC USD
'CC 4C 50 68 14 14 4B 06'= Sigma 50-100mm F1.8 DC HSM | A
'CD 3D 2D 70 2E 3C 4B 0E'= Sigma 18-125mm F3.8-5.6 DC OS HSM
'CE 34 76 A0 38 40 4B 0E'= Sigma 150-500mm F5-6.3 DG OS APO HSM
'CE 47 37 5C 25 25 DF 4E'= Tamron SP 24-70mm f/2.8 Di VC USD G2 (A032)
'CF 38 6E 98 34 3C 4B 0E'= Sigma APO 120-400mm F4.5-5.6 DG OS HSM
'CF 47 5C 8E 31 3D DF 0E'= Tamron SP 70-300mm f/4-5.6 Di VC USD (A030)
'D2 3C 8E B0 3C 3C 4B 02'= Sigma APO 300-800mm F5.6 EX DG HSM
'DB 40 11 11 2C 2C 1C 06'= Sigma 8mm F3.5 EX DG Circular Fisheye
'DC 48 19 19 24 24 4B 06'= Sigma 10mm F2.8 EX DC HSM Fisheye
'DE 54 50 50 0C 0C 4B 06'= Sigma 50mm F1.4 EX DG HSM
'E0 3C 5C 8E 30 3C 4B 06'= Sigma 70-300mm F4-5.6 APO DG Macro HSM
'E0 40 2D 98 2C 41 DF 4E'= Tamron 18-400mm f/3.5-6.3 Di II VC HLD (B028)
'E1 40 19 36 2C 35 DF 4E'= Tamron 10-24mm f/3.5-4.5 Di II VC HLD (B023)
'E1 58 37 37 14 14 1C 02'= Sigma 24mm F1.8 EX DG Aspherical Macro
'E2 47 5C 80 24 24 DF 4E'= Tamron SP 70-200mm f/2.8 Di VC USD G2 (A025)
'E3 40 76 A6 38 40 DF 0E'= Tamron SP 150-600mm f/5-6.3 Di VC USD G2 (A022)
'E3 40 76 A6 38 40 DF 4E'= Tamron SP 150-600mm f/5-6.3 Di VC USD G2
'E3 54 50 50 24 24 35 02'= Sigma Macro 50mm F2.8 EX DG
'E4 54 64 64 24 24 DF 0E'= Tamron SP 90mm f/2.8 Di VC USD Macro 1:1 (F017)
'E5 4C 62 62 14 14 C9 4E'= Tamron SP 85mm f/1.8 Di VC USD (F016)
'E5 54 6A 6A 24 24 35 02'= Sigma Macro 105mm F2.8 EX DG
'E6 40 2D 80 2C 40 DF 0E'= Tamron 18-200mm f/3.5-6.3 Di II VC (B018)
'E6 41 3C 8E 2C 40 1C 02'= Sigma 28-300mm F3.5-6.3 DG Macro
'E7 4C 4C 4C 14 14 DF 0E'= Tamron SP 45mm f/1.8 Di VC USD (F013)
'E8 4C 44 44 14 14 DF 0E'= Tamron SP 35mm f/1.8 Di VC USD (F012)
'E9 48 27 3E 24 24 DF 0E'= Tamron SP 15-30mm f/2.8 Di VC USD (A012)
'E9 54 37 5C 24 24 1C 02'= Sigma 24-70mm F2.8 EX DG Macro
'EA 40 29 8E 2C 40 DF 0E'= Tamron 16-300mm f/3.5-6.3 Di II VC PZD (B016)
'EA 48 27 27 24 24 1C 02'= Sigma 15mm F2.8 EX Diagonal Fisheye
'EB 40 76 A6 38 40 DF 0E'= Tamron SP AF 150-600mm f/5-6.3 VC USD (A011)
'EC 3E 3C 8E 2C 40 DF 0E'= Tamron 28-300mm f/3.5-6.3 Di VC PZD A010
'ED 40 2D 80 2C 40 4B 0E'= Sigma 18-200mm F3.5-6.3 DC OS HSM
'EE 48 5C 80 24 24 4B 06'= Sigma 70-200mm F2.8 EX APO DG Macro HSM II
'F0 38 1F 37 34 3C 4B 06'= Sigma 12-24mm F4.5-5.6 EX DG Aspherical HSM
'F0 3F 2D 8A 2C 40 DF 0E'= Tamron AF 18-270mm f/3.5-6.3 Di II VC PZD (B008)
'F1 44 A0 A0 34 34 4B 02'= Sigma APO 500mm F4.5 EX DG HSM
'F1 47 5C 8E 30 3C DF 0E'= Tamron SP 70-300mm f/4-5.6 Di VC USD (A005)
'F3 48 68 8E 30 30 4B 02'= Sigma APO 100-300mm F4 EX IF HSM
'F3 54 2B 50 24 24 84 0E'= Tamron SP AF 17-50mm f/2.8 XR Di II VC LD Aspherical (IF) (B005)
'F4 4C 7C 7C 2C 2C 4B 02'= Sigma APO Macro 180mm F3.5 EX DG HSM
'F4 54 56 56 18 18 84 06'= Tamron SP AF 60mm f/2.0 Di II Macro 1:1 (G005)
'F5 40 2C 8A 2C 40 40 0E'= Tamron AF 18-270mm f/3.5-6.3 Di II VC LD Aspherical (IF) Macro (B003)
'F5 48 76 76 24 24 4B 06'= Sigma APO Macro 150mm F2.8 EX DG HSM
'F6 3F 18 37 2C 34 84 06'= Tamron SP AF 10-24mm f/3.5-4.5 Di II LD Aspherical (IF) (B001)
'F6 3F 18 37 2C 34 DF 06'= Tamron SP AF 10-24mm f/3.5-4.5 Di II LD Aspherical (IF) (B001)
'F6 48 2D 50 24 24 4B 06'= Sigma 18-50mm F2.8 EX DC Macro
'F7 53 5C 80 24 24 40 06'= Tamron SP AF 70-200mm f/2.8 Di LD (IF) Macro (A001)
'F7 53 5C 80 24 24 84 06'= Tamron SP AF 70-200mm f/2.8 Di LD (IF) Macro (A001)
'F8 54 3E 3E 0C 0C 4B 06'= Sigma 30mm F1.4 EX DC HSM
'F8 54 64 64 24 24 DF 06'= Tamron SP AF 90mm f/2.8 Di Macro 1:1 (272NII)
'F8 55 64 64 24 24 84 06'= Tamron SP AF 90mm f/2.8 Di Macro 1:1 (272NII)
'F9 3C 19 31 30 3C 4B 06'= Sigma 10-20mm F4-5.6 EX DC HSM
'F9 40 3C 8E 2C 40 40 0E'= Tamron AF 28-300mm f/3.5-6.3 XR Di VC LD Aspherical (IF) Macro (A20)
'FA 54 3C 5E 24 24 84 06'= Tamron SP AF 28-75mm f/2.8 XR Di LD Aspherical (IF) Macro (A09NII)
'FA 54 3C 5E 24 24 DF 06'= Tamron SP AF 28-75mm f/2.8 XR Di LD Aspherical (IF) Macro (A09NII)
'FA 54 6E 8E 24 24 4B 02'= Sigma APO 120-300mm F2.8 EX DG HSM
'FB 54 2B 50 24 24 84 06'= Tamron SP AF 17-50mm f/2.8 XR Di II LD Aspherical (IF) (A16NII)
'FB 54 8E 8E 24 24 4B 02'= Sigma APO 300mm F2.8 EX DG HSM
'FC 40 2D 80 2C 40 DF 06'= Tamron AF 18-200mm f/3.5-6.3 XR Di II LD Aspherical (IF) Macro (A14NII)
'FD 00 50 50 18 18 DF 00'= Voigtlander APO-Lanthar 50mm F2 Aspherical
'FD 47 50 76 24 24 4B 06'= Sigma 50-150mm F2.8 EX APO DC HSM II
'FE 47 00 00 24 24 4B 06'= Sigma 4.5mm F2.8 EX DC HSM Circular Fisheye
'FE 48 37 5C 24 24 DF 0E'= Tamron SP 24-70mm f/2.8 Di VC USD (A007)
'FE 53 5C 80 24 24 84 06'= Tamron SP AF 70-200mm f/2.8 Di LD (IF) Macro (A001)
'FE 54 5C 80 24 24 DF 0E'= Tamron SP 70-200mm f/2.8 Di VC USD (A009)
'FE 54 64 64 24 24 DF 0E'= Tamron SP 90mm f/2.8 Di VC USD Macro 1:1 (F004)
'FF 40 2D 80 2C 40 4B 06'= Sigma 18-200mm F3.5-6.3 DC
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 19, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/NikonCapture.html b/ExifTool/html/TagNames/NikonCapture.html new file mode 100644 index 0000000..4debade --- /dev/null +++ b/ExifTool/html/TagNames/NikonCapture.html @@ -0,0 +1,800 @@ + + + + +NikonCapture Tags + + + +

NikonCapture Tags

+

This information is written by the Nikon Capture software in tag 0x0e01 of +the maker notes of NEF images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x8ae85eLCHEditorint8u0 = Off +
1 = On
0x83a1a25HistogramXMLundef 
0xc89224bColorAberrationControlint8u0 = Off +
1 = On
0x116fea21HighlightData---> NikonCapture HighlightData Tags
0x2175eb78D-LightingHQint8u0 = Off +
1 = On
0x2fc08431StraightenAngledouble 
0x374233e0CropData---> NikonCapture CropData Tags
0x39c456acPictureCtrl---> NikonCapture PictureCtrl Tags
0x3cfc73c6RedEyeData---> NikonCapture RedEyeData Tags
0x3d136244EditVersionNamestring 
0x416391c6QuickFixint8u0 = Off +
1 = On
0x56a54260Exposure---> NikonCapture Exposure Tags
0x5f0e7d23ColorBoosterint8u0 = Off +
1 = On
0x6a6e36b6D-LightingHQSelectedint8u0 = No +
1 = Yes
0x753dcbc0NoiseReductionint8u0 = Off +
1 = On
0x76a43200UnsharpMaskint8u0 = Off +
1 = On
0x76a43201Curvesint8u0 = Off +
1 = On
0x76a43202ColorBalanceAdjint8u0 = Off +
1 = On
0x76a43203AdvancedRawint8u0 = Off +
1 = On
0x76a43204WhiteBalanceAdjint8u0 = Off +
1 = On
0x76a43205VignetteControlint8u0 = Off +
1 = On
0x76a43206FlipHorizontalint8u0 = No +
1 = Yes
0x76a43207Rotationint16u 
0x84589434BrightnessData---> NikonCapture Brightness Tags
0x890ff591D-LightingHQData---> NikonCapture DLightingHQ Tags
0x926f13e0NoiseReductionData---> NikonCapture NoiseReduction Tags
0x9ef5f6e0IPTCData---> IPTC Tags
0xab5eca5ePhotoEffectsint8u0 = Off +
1 = On
0xac6bd5c0VignetteControlIntensityint16s 
0xb0384e1ePhotoEffectsData---> NikonCapture PhotoEffects Tags
0xb999a36fColorBoostData---> NikonCapture ColorBoost Tags
0xbf3c6c20WBAdjData---> NikonCapture WBAdjData Tags
0xce5554aaD-LightingHSint8u0 = Off +
1 = On
0xe2173c47PictureControlint8u0 = Off +
1 = On
0xe37b4337D-LightingHSData---> NikonCapture DLightingHS Tags
0xe42b5161UnsharpData---> NikonCapture UnsharpData Tags
0xe9651831PhotoEffectHistoryXMLundef 
0xfe28a44fAutoRedEyeint8u0 = Off +
1 = On
0xfe443a45ImageDustOffint8u0 = Off +
1 = On
+ +

NikonCapture HighlightData Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ShadowProtectionint8s 
1SaturationAdjint8s 
6HighlightProtectionint8s 
+ +

NikonCapture CropData Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
30CropLeftdouble 
38CropTopdouble 
46CropRightdouble 
54CropBottomdouble 
142CropOutputWidthInchesdouble 
150CropOutputHeightInchesdouble 
158CropScaledResolutiondouble 
174CropSourceResolutiondouble 
182CropOutputResolutiondouble 
190CropOutputScaledouble 
198CropOutputWidthdouble 
206CropOutputHeightdouble 
214CropOutputPixelsdouble 
+ +

NikonCapture PictureCtrl Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PictureControlActiveint8u0 = Off +
1 = On
19PictureControlModestring[16] 
42QuickAdjustint8u 
43SharpeningAdjint8u 
44ContrastAdjint8u 
45BrightnessAdjint8u 
46SaturationAdjint8u 
47HueAdjint8u 
+ +

NikonCapture RedEyeData Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0RedEyeCorrectionint8u0 = Off +
1 = Automatic +
2 = Click on Eyes
+ +

NikonCapture Exposure Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ExposureAdjint16s 
18ExposureAdj2double 
36ActiveD-Lightingint8u0 = Off +
1 = On
37ActiveD-LightingModeint8u + +
0 = Unchanged +
1 = Off +
2 = Low +
3 = Normal
  4 = High +
6 = Extra High +
7 = Extra High 1 +
8 = Extra High 2
+
+ +

NikonCapture Brightness Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0BrightnessAdjdouble 
8EnhanceDarkTonesint8u0 = Off +
1 = On
+ +

NikonCapture DLightingHQ Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0D-LightingHQShadowint32u 
1D-LightingHQHighlightint32u 
2D-LightingHQColorBoostint32u 
+ +

NikonCapture NoiseReduction Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4EdgeNoiseReductionint8u0 = Off +
1 = On
5ColorMoireReductionModeint8u0 = Off +
1 = Low +
2 = Medium +
3 = High
9NoiseReductionIntensityint32u 
13NoiseReductionSharpnessint32u 
17NoiseReductionMethodint16u0 = Faster +
1 = Better Quality +
2 = Better Quality 2013
21ColorMoireReductionint8u0 = Off +
1 = On
23NoiseReductionint8u0 = Off +
1 = On
24ColorNoiseReductionIntensityint32u 
28ColorNoiseReductionSharpnessint32u 
+ +

NikonCapture PhotoEffects Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PhotoEffectsTypeint8u0 = None +
1 = B&W +
2 = Sepia +
3 = Tinted
4PhotoEffectsRedint16s 
6PhotoEffectsGreenint16s 
8PhotoEffectsBlueint16s 
+ +

NikonCapture ColorBoost Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ColorBoostTypeint8u0 = Nature +
1 = People
1ColorBoostLevelint32u 
+ +

NikonCapture WBAdjData Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0WBAdjRedBalancedouble 
8WBAdjBlueBalancedouble 
16WBAdjModeint8u +
1 = Use Gray Point +
2 = Recorded Value +
3 = Use Temperature +
4 = Calculate Automatically +
5 = Auto2 +
6 = Underwater +
7 = Auto1
+
20WBAdjLightingint16u0x0 = None +
0x100 = Incandescent +
0x200 = Daylight (direct sunlight) +
0x201 = Daylight (shade) +
0x202 = Daylight (cloudy) +
0x300 = Standard Fluorescent (warm white) +
0x301 = Standard Fluorescent (3700K) +
0x302 = Standard Fluorescent (cool white) +
0x303 = Standard Fluorescent (5000K) +
0x304 = Standard Fluorescent (daylight) +
0x305 = Standard Fluorescent (high temperature mercury vapor) +
0x400 = High Color Rendering Fluorescent (warm white) +
0x401 = High Color Rendering Fluorescent (3700K) +
0x402 = High Color Rendering Fluorescent (cool white) +
0x403 = High Color Rendering Fluorescent (5000K) +
0x404 = High Color Rendering Fluorescent (daylight) +
0x500 = Flash +
0x501 = Flash (FL-G1 filter) +
0x502 = Flash (FL-G2 filter) +
0x503 = Flash (TN-A1 filter) +
0x504 = Flash (TN-A2 filter) +
0x600 = Sodium Vapor Lamps
24WBAdjTemperatureint16u 
37WBAdjTintint32s 
+ +

NikonCapture DLightingHS Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0D-LightingHSAdjustmentint32u 
1D-LightingHSColorBoostint32u 
+ +

NikonCapture UnsharpData Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0UnsharpCountint8u 
19Unsharp1Colorint16u + +
0 = RGB +
1 = Red +
2 = Green +
3 = Blue
  4 = Yellow +
5 = Magenta +
6 = Cyan
+
23Unsharp1Intensityint16u 
25Unsharp1HaloWidthint16u 
27Unsharp1Thresholdint8u 
46Unsharp2Colorint16u + +
0 = RGB +
1 = Red +
2 = Green +
3 = Blue
  4 = Yellow +
5 = Magenta +
6 = Cyan
+
50Unsharp2Intensityint16u 
52Unsharp2HaloWidthint16u 
54Unsharp2Thresholdint8u 
73Unsharp3Colorint16u + +
0 = RGB +
1 = Red +
2 = Green +
3 = Blue
  4 = Yellow +
5 = Magenta +
6 = Cyan
+
77Unsharp3Intensityint16u 
79Unsharp3HaloWidthint16u 
81Unsharp3Thresholdint8u 
100Unsharp4Colorint16u + +
0 = RGB +
1 = Red +
2 = Green +
3 = Blue
  4 = Yellow +
5 = Magenta +
6 = Cyan
+
104Unsharp4Intensityint16u 
106Unsharp4HaloWidthint16u 
108Unsharp4Thresholdint8u 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised May 2, 2014 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/NikonCustom.html b/ExifTool/html/TagNames/NikonCustom.html new file mode 100644 index 0000000..f38ea4c --- /dev/null +++ b/ExifTool/html/TagNames/NikonCustom.html @@ -0,0 +1,11551 @@ + + + + +NikonCustom Tags + + + +

NikonCustom Tags

+

+Unfortunately, the NikonCustom settings are stored in a binary data block +which changes from model to model. This means that significant effort must +be spent in decoding these for each model, usually requiring hundreds of +test images from a dedicated Nikon owner. For this reason, the NikonCustom +settings have not been decoded for all models. The tables below list the +custom settings for the currently supported models. +

+

NikonCustom SettingsD40 Tags

+

Custom settings for the Nikon D40.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1Beepint8u[val >> 7 & 0x1] +
0 = On +
1 = Off
0.2AFAssistint8u[val >> 6 & 0x1] +
0 = On +
1 = Off
0.3NoMemoryCardint8u[val >> 5 & 0x1] +
0 = Release Locked +
1 = Enable Release
0.4ImageReviewint8u[val >> 4 & 0x1] +
0 = On +
1 = Off
1.1AutoISOint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
1.2AutoISOMaxint8u[val >> 4 & 0x3] +
1 = 400 +
2 = 800 +
3 = 1600
1.3AutoISOMinShutterSpeedint8u[val & 0x7] + +
0 = 1/125 s +
1 = 1/60 s +
2 = 1/30 s +
3 = 1/15 s
  4 = 1/8 s +
5 = 1/4 s +
6 = 1/2 s +
7 = 1 s
+
2.1ImageReviewTimeint8u[val & 0x7] +
0 = 4 s +
1 = 8 s +
2 = 20 s +
3 = 1 min +
4 = 10 min
3.1MonitorOffTimeint8u[val >> 5 & 0x7] +
0 = 4 s +
1 = 8 s +
2 = 20 s +
3 = 1 min +
4 = 10 min
3.2MeteringTimeint8u[val >> 2 & 0x7] +
0 = 4 s +
1 = 8 s +
2 = 20 s +
3 = 1 min +
4 = 30 min
3.3SelfTimerTimeint8u[val & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
3.4RemoteOnDurationint8u[val >> 6 & 0x3] +
0 = 1 min +
1 = 5 min +
2 = 10 min +
3 = 15 min
4.1AELockButtonint8u[val >> 1 & 0x7] +
0 = AE/AF Lock +
1 = AE Lock Only +
2 = AF Lock Only +
3 = AE Lock (hold) +
4 = AF-ON
4.2AELockint8u[val & 0x1] +
0 = Off +
1 = On
5.1ShootingModeSettingint8u[val >> 4 & 0x7] +
0 = Single Frame +
1 = Continuous +
2 = Self-timer +
3 = Delayed Remote +
4 = Quick-response Remote
5.2TimerFunctionButtonint8u[val & 0x7] +
0 = Shooting Mode +
1 = Image Quality/Size +
2 = ISO +
3 = White Balance +
4 = Self-timer
6.1Meteringint8u[val & 0x3] +
0 = Matrix +
1 = Center-weighted +
2 = Spot
8.1InternalFlashint8u[val >> 4 & 0x1] +
0 = TTL +
1 = Manual
8.2ManualFlashOutputint8u[val & 0x7]
9FlashLevelint8s 
10.1FocusModeSettingint8u[val >> 6 & 0x3] +
0 = Manual +
1 = AF-S +
2 = AF-C +
3 = AF-A
11.1AFAreaModeSettingint8u[val >> 4 & 0x3] +
0 = Single Area +
1 = Dynamic Area +
2 = Closest Subject
+ +

NikonCustom SettingsD80 Tags

+

Custom settings for the Nikon D80.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1Beepint8u[val >> 7 & 0x1] +
0 = On +
1 = Off
0.2AFAssistint8u[val >> 6 & 0x1] +
0 = On +
1 = Off
0.3NoMemoryCardint8u[val >> 5 & 0x1] +
0 = Release Locked +
1 = Enable Release
0.4ImageReviewint8u[val >> 4 & 0x1] +
0 = On +
1 = Off
0.5Illuminationint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
0.6MainDialExposureCompint8u[val >> 2 & 0x1] +
0 = Off +
1 = On
0.7EVStepSizeint8u[val & 0x1] +
0 = 1/3 EV +
1 = 1/2 EV
1.1AutoISOint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
1.2AutoISOMaxint8u[val >> 4 & 0x3] +
0 = 200 +
1 = 400 +
2 = 800 +
3 = 1600
1.3AutoISOMinShutterSpeedint8u[val & 0xf] + +
0 = 1/125 s +
1 = 1/100 s +
2 = 1/80 s +
3 = 1/60 s +
4 = 1/40 s +
5 = 1/30 s
  6 = 1/15 s +
7 = 1/8 s +
8 = 1/4 s +
9 = 1/2 s +
10 = 1 s
+
2.1AutoBracketSetint8u[val >> 6 & 0x3] +
0 = AE & Flash +
1 = AE Only +
2 = Flash Only +
3 = WB Bracketing
2.2AutoBracketOrderint8u[val >> 5 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
3.1MonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 5 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
3.2MeteringTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
1 = 6 s +
2 = 8 s
  3 = 16 s +
4 = 30 s +
5 = 30 min
+
3.3SelfTimerTimeint8u[val & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
4.1AELockButtonint8u[val >> 1 & 0xf] +
0 = AE/AF Lock +
1 = AE Lock Only +
2 = AF Lock Only +
3 = AE Lock (hold) +
4 = AF-ON +
5 = FV Lock +
6 = Focus Area Selection +
7 = AE-L/AF-L/AF Area +
8 = AE-L/AF Area +
9 = AF-L/AF Area +
10 = AF-ON/AF Area
+
4.2AELockint8u[val & 0x1] +
0 = Off +
1 = On
4.3RemoteOnDurationint8u[val >> 6 & 0x3] +
0 = 1 min +
1 = 5 min +
2 = 10 min +
3 = 15 min
5.1CommandDialsint8u[val >> 7 & 0x1] +
0 = Standard (Main Shutter, Sub Aperture) +
1 = Reversed (Main Aperture, Sub Shutter)
5.2FunctionButtonint8u[val >> 3 & 0xf] + +
0 = ISO Display +
1 = Framing Grid +
2 = AF-area Mode +
3 = Center AF Area +
4 = FV Lock
  5 = Flash Off +
6 = Matrix Metering +
7 = Center-weighted +
8 = Spot Metering
+
6.1GridDisplayint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
6.2ViewfinderWarningint8u[val >> 6 & 0x1] +
0 = On +
1 = Off
6.3CenterWeightedAreaSizeint8u[val >> 2 & 0x3] +
0 = 6 mm +
1 = 8 mm +
2 = 10 mm
6.4ExposureDelayModeint8u[val >> 5 & 0x1] +
0 = Off +
1 = On
6.5MB-D80Batteriesint8u[val & 0x3] +
0 = LR6 (AA Alkaline) +
1 = HR6 (AA Ni-MH) +
2 = FR6 (AA Lithium) +
3 = ZR6 (AA Ni-Mg)
7.1FlashWarningint8u[val >> 7 & 0x1] +
0 = On +
1 = Off
7.2FlashShutterSpeedint8u[val >> 3 & 0xf]
7.3AutoFPint8u[val >> 2 & 0x1] +
0 = Off +
1 = On
7.4ModelingFlashint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
8.1InternalFlashint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual +
2 = Repeating Flash +
3 = Commander Mode
8.2ManualFlashOutputint8u[val & 0x7]
9.1RepeatingFlashOutputint8u[val >> 4 & 0x7]
9.2RepeatingFlashCountint8u[val & 0xf]
10.1RepeatingFlashRateint8u[val >> 4 & 0xf]
10.2CommanderChannelint8u[val & 0x3]
11.1CommanderInternalFlashint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual +
2 = Off
11.2CommanderGroupAModeint8u[val >> 4 & 0x3] +
0 = TTL +
1 = Auto Aperture +
2 = Manual +
3 = Off
11.3CommanderGroupBModeint8u[val >> 2 & 0x3] +
0 = TTL +
1 = Auto Aperture +
2 = Manual +
3 = Off
12.1CommanderInternalTTLCompint8u[val & 0x1f]
12.2CommanderInternalManualOutputint8u[val >> 5 & 0x7]
13.1CommanderGroupA_TTL-AACompint8u[val & 0x1f]
13.2CommanderGroupAManualOutputint8u[val >> 5 & 0x7]
14.1CommanderGroupB_TTL-AACompint8u[val & 0x1f]
14.2CommanderGroupBManualOutputint8u[val >> 5 & 0x7]
15.1CenterAFAreaint8u[val >> 7 & 0x1] +
0 = Normal Zone +
1 = Wide Zone
15.2FocusAreaSelectionint8u[val >> 2 & 0x1] +
0 = No Wrap +
1 = Wrap
15.3AFAreaIlluminationint8u[val & 0x3] +
0 = Auto +
1 = Off +
2 = On
16.1AFAreaModeSettingint8u[val >> 6 & 0x3] +
0 = Single Area +
1 = Dynamic Area +
2 = Auto-area
+ +

NikonCustom SettingsD90 Tags

+

Custom settings for the D90.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1LightSwitchint8u[val >> 3 & 0x1] +
0 = LCD Backlight +
1 = LCD Backlight and Shooting Information
2.1AFAreaModeSettingint8u[val >> 5 & 0x3] +
0 = Single Area +
1 = Dynamic Area +
2 = Auto-area +
3 = 3D-tracking (11 points)
2.2CenterFocusPointint8u[val >> 4 & 0x1] +
0 = Normal Zone +
1 = Wide Zone
2.3AFAssistint8u[val & 0x1] +
0 = On +
1 = Off
2.4AFPointIlluminationint8u[val >> 1 & 0x3] +
0 = Auto +
1 = On +
2 = Off
2.5FocusPointWrapint8u[val >> 3 & 0x1] +
0 = No Wrap +
1 = Wrap
3.1AELockForMB-D80int8u[val >> 2 & 0x7] +
0 = AE Lock Only +
1 = AF Lock Only +
2 = AE Lock (hold) +
3 = AF-On +
4 = FV Lock +
5 = Focus Point Selection +
7 = AE/AF Lock
+
3.2MB-D80BatteryTypeint8u[val & 0x3] +
0 = LR6 (AA alkaline) +
1 = HR6 (AA Ni-MH) +
2 = FR6 (AA lithium) +
3 = ZR6 (AA Ni-Mn)
4.1Beepint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
4.2GridDisplayint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
4.3ISODisplayint8u[val >> 2 & 0x3] +
0 = Show ISO/Easy ISO +
1 = Show ISO Sensitivity +
3 = Show Frame Count
4.4ViewfinderWarningint8u[val & 0x1] +
0 = On +
1 = Off
4.5NoMemoryCardint8u[val >> 5 & 0x1] +
0 = Release Locked +
1 = Enable Release
5.1ScreenTipsint8u[val >> 2 & 0x1] +
0 = Off +
1 = On
5.2FileNumberSequenceint8u[val >> 3 & 0x1] +
0 = On +
1 = Off
5.3ShootingInfoDisplayint8u[val >> 6 & 0x3] +
0 = Auto +
2 = Manual (dark on light) +
3 = Manual (light on dark)
5.4LCDIlluminationint8u[val >> 5 & 0x1] +
0 = Off +
1 = On
6.1EasyExposureCompint8u[val & 0x1] +
0 = Off +
1 = On
6.2ReverseIndicatorsint8u[val >> 7 & 0x1] +
0 = + 0 - +
1 = - 0 +
7.1ExposureControlStepSizeint8u[val >> 6 & 0x1] +
0 = 1/3 EV +
1 = 1/2 EV
8.1CenterWeightedAreaSizeint8u[val >> 5 & 0x3] +
0 = 6 mm +
1 = 8 mm +
2 = 10 mm
8.2FineTuneOptMatrixMeteringint8u[val & 0xf]
9.1FineTuneOptCenterWeightedint8u[val >> 4 & 0xf]
9.2FineTuneOptSpotMeteringint8u[val & 0xf]
11.1CLModeShootingSpeedint8u[val & 0x7]
11.2ExposureDelayModeint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
13.1AutoBracketSetint8u[val >> 5 & 0x7] +
0 = AE & Flash +
1 = AE Only +
2 = Flash Only +
3 = WB Bracketing +
4 = Active D-Lighting
13.2AutoBracketOrderint8u[val >> 4 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
14.1FuncButtonint8u[val >> 3 & 0xf] +
1 = Framing Grid +
2 = AF-area Mode +
3 = Center Focus Point +
4 = FV Lock +
5 = Flash Off +
6 = Matrix Metering +
7 = Center-weighted Metering +
8 = Spot Metering +
9 = My Menu Top +
10 = + NEF (RAW)
+
16.1OKButtonint8u[val >> 3 & 0x3] +
0 = Not Used +
1 = Select Center Focus Point +
2 = Highlight Active Focus Point +
3 = Not Used
17.1AELockButtonint8u[val >> 3 & 0x7] + +
0 = AE/AF Lock +
1 = AE Lock Only +
2 = AF Lock Only
  3 = AE Lock (hold) +
4 = AF-ON +
5 = FV Lock
+
18.1CommandDialsReverseRotationint8u[val >> 7 & 0x1] +
0 = No +
1 = Yes
18.2ShutterReleaseButtonAE-Lint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
19.1MeteringTimeint8u[val >> 4 & 0xf] + +
0 = 4 s +
1 = 6 s +
2 = 8 s +
3 = 16 s +
4 = 30 s
  5 = 1 min +
6 = 5 min +
7 = 10 min +
8 = 30 min
+
19.2RemoteOnDurationint8u[val & 0x3] +
0 = 1 min +
1 = 5 min +
2 = 10 min +
3 = 15 min
20.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
20.2SelfTimerShotCountint8u[val >> 1 & 0xf]
21.1PlaybackMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
21.2ImageReviewTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
22.1MenuMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
22.2ShootingInfoMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
23.1FlashShutterSpeedint8u[val & 0xf] + +
0 = 1/60 s +
1 = 1/30 s +
2 = 1/15 s +
3 = 1/8 s +
4 = 1/4 s +
5 = 1/2 s
  6 = 1 s +
7 = 2 s +
8 = 4 s +
9 = 8 s +
10 = 15 s +
11 = 30 s
+
24.1InternalFlashint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual +
2 = Repeating Flash +
3 = Commander Mode
24.2ManualFlashOutputint8u[val & 0x1f]
25.1RepeatingFlashOutputint8u[val >> 4 & 0x7]
25.2RepeatingFlashCountint8u[val & 0xf]
26.1RepeatingFlashRateint8u[val >> 4 & 0xf]
31.1FlashWarningint8u[val >> 7 & 0x1] +
0 = On +
1 = Off
31.2CommanderInternalTTLCompint8u[val & 0x1f]
31.3ModelingFlashint8u[val >> 5 & 0x1] +
0 = On +
1 = Off
31.4AutoFPint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
32.1CommanderGroupA_TTLCompint8u[val & 0x1f]
33.1CommanderGroupB_TTLCompint8u[val & 0x1f]
34.1LiveViewAFint8u[val >> 6 & 0x3] +
0 = Face Priority +
1 = Wide Area +
2 = Normal Area
+ +

NikonCustom SettingsD3 Tags

+

Custom settings for the D3, D3S, D3X, D300 and D300S.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1CustomSettingsBankint8u[val & 0x3] +
0 = A +
1 = B +
2 = C +
3 = D
0.2CustomSettingsAllDefaultint8u("No" if any custom setting for this bank was changed from the default) +
[val >> 7 & 0x1] +
0 = Yes +
1 = No
1.1AF-CPrioritySelectionint8u[val >> 6 & 0x3] +
0 = Release +
1 = Release + Focus +
2 = Focus
1.2AF-SPrioritySelectionint8u[val >> 5 & 0x1] +
0 = Focus +
1 = Release
1.3AFPointSelectionint8u[val >> 4 & 0x1] +
0 = 51 Points +
1 = 11 Points
1.4DynamicAFAreaint8u[val >> 2 & 0x3] +
0 = 9 Points +
1 = 21 Points +
2 = 51 Points +
3 = 51 Points (3D-tracking)
1.5FocusTrackingLockOnint8u(not D3S) +
[val & 0x3] +
0 = Long +
1 = Normal +
2 = Short +
3 = Off
2.1AFActivationint8u[val >> 7 & 0x1] +
0 = Shutter/AF-On +
1 = AF-On Only
2.2FocusPointWrapint8u[val >> 3 & 0x1] +
0 = No Wrap +
1 = Wrap
2.3AFPointIlluminationint8u(D3) +
[val >> 5 & 0x3] +
0 = On in Continuous Shooting and Manual Focusing +
1 = On During Manual Focusing +
2 = On in Continuous Shooting Modes +
3 = Off +
(D300) +
[val >> 1 & 0x3] +
0 = Auto +
1 = Off +
2 = On
2.4AFPointBrightnessint8u(D3 only) +
[val >> 1 & 0x3] +
0 = Low +
1 = Normal +
2 = High +
3 = Extra High
2.5AFAssistint8u(D300 only) +
[val & 0x1] +
0 = On +
1 = Off
3.1AFOnButtonint8u(D3 only) +
[val & 0x7]
+
0 = AF On +
1 = AE/AF Lock +
2 = AE Lock Only +
3 = AE Lock (reset on release) +
4 = AE Lock (hold) +
5 = AF Lock Only
+
3.2VerticalAFOnButtonint8u(D3 only) +
[val >> 4 & 0x7]
+
0 = AF On +
1 = AE/AF Lock +
2 = AE Lock Only +
3 = AE Lock (reset on release) +
4 = AE Lock (hold) +
5 = AF Lock Only +
7 = Same as AF On
+
3.3AF-OnForMB-D10int8u(D300 only) +
[val >> 4 & 0x7]
+
0 = AF-On +
1 = AE/AF Lock +
2 = AE Lock Only +
3 = AE Lock (reset on release) +
4 = AE Lock (hold) +
5 = AF Lock Only +
6 = Same as FUNC Button
+
4.1FocusTrackingLockOnint8u(D3S only) +
[val & 0x7]
+ +
0 = 5 (Long) +
1 = 4 +
2 = 3 (Normal)
  3 = 2 +
4 = 1 (Short) +
5 = Off
+
4.2AssignBktButtonint8u(D3S only) +
[val >> 3 & 0x1] +
0 = Auto Bracketing +
1 = Multiple Exposure
4.3MultiSelectorLiveViewint8u(D3S only) +
[val >> 6 & 0x3] +
0 = Reset +
1 = Zoom On/Off +
2 = Start Movie Recording +
3 = Not Used
4.4InitialZoomLiveViewint8u(D3S only) +
[val >> 4 & 0x3] +
0 = Low Magnification +
1 = Medium Magnification +
2 = High Magnification
6.1ISOStepSizeint8u[val >> 6 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
6.2ExposureControlStepSizeint8u[val >> 4 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
6.3ExposureCompStepSizeint8u[val >> 2 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
6.4EasyExposureCompensationint8u[val & 0x3] +
0 = Off +
1 = On +
2 = On (auto reset)
7.1CenterWeightedAreaSizeint8u(D3) +
[val >> 5 & 0x7] +
0 = 8 mm +
1 = 12 mm +
2 = 15 mm +
3 = 20 mm +
4 = Average +
(D300) +
[val >> 5 & 0x7] +
0 = 6 mm +
1 = 8 mm +
2 = 10 mm +
3 = 13 mm +
4 = Average
7.2FineTuneOptCenterWeightedint8u[val & 0xf]
8.1FineTuneOptMatrixMeteringint8u[val >> 4 & 0xf]
8.2FineTuneOptSpotMeteringint8u[val & 0xf]
9.1MultiSelectorShootModeint8u[val >> 6 & 0x3] +
0 = Select Center Focus Point +
1 = Highlight Active Focus Point +
2 = Not Used
9.2MultiSelectorPlaybackModeint8u(all models except D3S) +
[val >> 4 & 0x3] +
0 = Thumbnail On/Off +
1 = View Histograms +
2 = Zoom On/Off +
3 = Choose Folder
9.3InitialZoomSettingint8u(D3) +
[val >> 2 & 0x3] +
0 = High Magnification +
1 = Medium Magnification +
2 = Low Magnification +
(D300) +
[val >> 2 & 0x3] +
0 = Low Magnification +
1 = Medium Magnification +
2 = High Magnification
9.4MultiSelectorint8u[val & 0x1] +
0 = Do Nothing +
1 = Reset Meter-off Delay
10.1ExposureDelayModeint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
10.2CLModeShootingSpeedint8u[val & 0x7]
10.3CHModeShootingSpeedint8u(D3 only) +
[val >> 4 & 0x3] +
0 = 9 fps +
1 = 10 fps +
2 = 11 fps
11MaxContinuousReleaseint8u 
12.1ReverseIndicatorsint8u[val >> 5 & 0x1] +
0 = + 0 - +
1 = - 0 +
12.2FileNumberSequenceint8u(D3) +
[val >> 1 & 0x1] +
0 = On +
1 = Off +
(D300) +
[val >> 3 & 0x1] +
0 = On +
1 = Off
12.3RearDisplayint8u(D3 only) +
[val >> 7 & 0x1] +
0 = ISO +
1 = Exposures Remaining
12.4ViewfinderDisplayint8u(D3 only) +
[val >> 6 & 0x1] +
0 = Frame Count +
1 = Exposures Remaining
12.5BatteryOrderint8u(D300 only) +
[val >> 2 & 0x1] +
0 = MB-D10 First +
1 = Camera Battery First
12.6MB-D10Batteriesint8u(D300 only) +
[val & 0x3] +
0 = LR6 (AA alkaline) +
1 = HR6 (AA Ni-MH) +
2 = FR6 (AA lithium) +
3 = ZR6 (AA Ni-Mn)
12.7ScreenTipsint8u[val >> 4 & 0x1] +
0 = On +
1 = Off
13.1Beepint8u[val >> 6 & 0x3] +
0 = High +
1 = Low +
2 = Off
13.2ShootingInfoDisplayint8u[val >> 4 & 0x3] +
0 = Auto +
1 = Auto +
2 = Manual (dark on light) +
3 = Manual (light on dark)
13.3GridDisplayint8u(D300 only) +
[val >> 1 & 0x1] +
0 = Off +
1 = On
13.4ViewfinderWarningint8u(D300 only) +
[val & 0x1] +
0 = On +
1 = Off
13.5MultiSelectorPlaybackModeint8u(D3S only) +
[val & 0x3] +
0 = Thumbnail On/Off +
1 = View Histograms +
2 = Zoom On/Off
14.1PreviewButton +
FuncButton
int8u
int8u
(D3) +
[val >> 3 & 0x1f]
+
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = Flash Off +
9 = Bracketing Burst +
10 = Matrix Metering +
11 = Center-weighted Metering +
12 = Spot Metering +
13 = Virtual Horizon +
15 = Playback +
16 = My Menu Top
+(D300) +
[val >> 3 & 0x1f]
+
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
9 = Flash Off +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top +
16 = + NEF (RAW)
+
14.2PreviewButtonPlusDials +
FuncButtonPlusDials
int8u
int8u
(D3) +
[val & 0x7]
+
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = One Step Speed/Aperture +
3 = Choose Non-CPU Lens Number +
5 = Choose Image Area (FX/DX) +
6 = Shooting Bank Menu +
7 = Dynamic AF Area
+(D300) +
[val & 0x7] +
0 = None +
2 = One Step Speed/Aperture +
3 = Choose Non-CPU Lens Number +
5 = Auto Bracketing +
6 = Dynamic AF Area
15.1FuncButton +
PreviewButton
int8u
int8u
(D3) +
[val >> 3 & 0x1f]
+
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = Flash Off +
9 = Bracketing Burst +
10 = Matrix Metering +
11 = Center-weighted Metering +
12 = Spot Metering +
13 = Virtual Horizon +
15 = Playback +
16 = My Menu Top
+(D300) +
[val >> 3 & 0x1f]
+
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
9 = Flash Off +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top +
16 = + NEF (RAW)
+
15.2FuncButtonPlusDials +
PreviewButtonPlusDials
int8u
int8u
(D3) +
[val & 0x7]
+
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = One Step Speed/Aperture +
3 = Choose Non-CPU Lens Number +
4 = Focus Point Selection +
5 = Choose Image Area (FX/DX) +
6 = Shooting Bank Menu +
7 = Dynamic AF Area
+(D300) +
[val & 0x7] +
0 = None +
2 = One Step Speed/Aperture +
3 = Choose Non-CPU Lens Number +
5 = Auto Bracketing +
6 = Dynamic AF Area
16.1AELockButtonint8u(D3) +
[val >> 3 & 0x1f]
+
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = Flash Off +
9 = Bracketing Burst +
10 = Matrix Metering +
11 = Center-weighted Metering +
12 = Spot Metering +
13 = Virtual Horizon +
14 = AF On +
15 = Playback +
16 = My Menu Top
+(D300) +
[val >> 3 & 0x1f]
+
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF On +
9 = Flash Off +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top +
16 = + NEF (RAW)
+
16.2AELockButtonPlusDialsint8u(D3) +
[val & 0x7]
+
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = One Step Speed/Aperture +
3 = Choose Non-CPU Lens Number +
5 = Choose Image Area (FX/DX) +
6 = Shooting Bank Menu +
7 = Dynamic AF Area
+(D300) +
[val & 0x7] +
0 = None +
3 = Choose Non-CPU Lens Number +
5 = Auto Bracketing +
6 = Dynamic AF Area
17.1CommandDialsReverseRotationint8u[val >> 7 & 0x1] +
0 = No +
1 = Yes
17.2CommandDialsChangeMainSubint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
17.3CommandDialsApertureSettingint8u[val >> 5 & 0x1] +
0 = Sub-command Dial +
1 = Aperture Ring
17.4CommandDialsMenuAndPlaybackint8u[val >> 4 & 0x1] +
0 = Off +
1 = On
17.5LCDIlluminationint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
17.6PhotoInfoPlaybackint8u[val >> 2 & 0x1] +
0 = Info Up-down, Playback Left-right +
1 = Info Left-right, Playback Up-down
17.7ShutterReleaseButtonAE-Lint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
17.8ReleaseButtonToUseDialint8u[val & 0x1] +
0 = No +
1 = Yes
18.1SelfTimerTimeint8u[val >> 3 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
18.2MonitorOffTimeint8u[val & 0x7] +
0 = 10 s +
1 = 20 s +
2 = 1 min +
3 = 5 min +
4 = 10 min
20.1FlashSyncSpeedint8u(D3) +
[val >> 5 & 0x7]
+ +
0 = 1/250 s (auto FP) +
1 = 1/250 s +
2 = 1/200 s +
3 = 1/160 s
  4 = 1/125 s +
5 = 1/100 s +
6 = 1/80 s +
7 = 1/60 s
+(D300) +
[val >> 4 & 0xf]
+ +
0 = 1/320 s (auto FP) +
1 = 1/250 s (auto FP) +
2 = 1/250 s +
3 = 1/200 s +
4 = 1/160 s
  5 = 1/125 s +
6 = 1/100 s +
7 = 1/80 s +
8 = 1/60 s
+
20.2FlashShutterSpeedint8u[val & 0xf] + +
0 = 1/60 s +
1 = 1/30 s +
2 = 1/15 s +
3 = 1/8 s +
4 = 1/4 s +
5 = 1/2 s
  6 = 1 s +
7 = 2 s +
8 = 4 s +
9 = 8 s +
10 = 15 s +
11 = 30 s
+
21.1AutoBracketSetint8u(D3 and D300) +
[val >> 6 & 0x3] +
0 = AE & Flash +
1 = AE Only +
2 = Flash Only +
3 = WB Bracketing +
(D3S and D300S) +
[val >> 5 & 0x7] +
0 = AE & Flash +
1 = AE Only +
2 = Flash Only +
3 = WB Bracketing +
4 = ADL Bracketing
21.2AutoBracketModeMint8u(D3 and D300) +
[val >> 4 & 0x3] +
0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only +
(D3S and D300S) +
[val >> 3 & 0x3] +
0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only
21.3AutoBracketOrderint8u(D3 and D300) +
[val >> 3 & 0x1] +
0 = 0,-,+ +
1 = -,0,+ +
(D3S and D300S) +
[val >> 2 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
21.4ModelingFlashint8u[val & 0x1] +
0 = On +
1 = Off
22.1NoMemoryCardint8u[val >> 7 & 0x1] +
0 = Release Locked +
1 = Enable Release
22.2MeteringTimeint8u[val & 0xf] + +
0 = 4 s +
1 = 6 s +
2 = 8 s +
3 = 16 s +
4 = 30 s
  5 = 1 min +
6 = 5 min +
7 = 10 min +
8 = 30 min +
9 = No Limit
+
23.1InternalFlashint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual +
2 = Repeating Flash +
3 = Commander Mode
25.1ImageReviewTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
25.2PlaybackMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
26.1MenuMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
26.2ShootingInfoMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
+ +

NikonCustom SettingsD700 Tags

+

Custom settings for the D700.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1CustomSettingsBankint8u[val & 0x3] +
0 = A +
1 = B +
2 = C +
3 = D
0.2CustomSettingsAllDefaultint8u("No" if any custom setting for this bank was changed from the default) +
[val >> 7 & 0x1] +
0 = Yes +
1 = No
1.1AF-CPrioritySelectionint8u[val >> 6 & 0x3] +
0 = Release +
1 = Release + Focus +
2 = Focus
1.2AF-SPrioritySelectionint8u[val >> 5 & 0x1] +
0 = Focus +
1 = Release
1.3AFPointSelectionint8u[val >> 4 & 0x1] +
0 = 51 Points +
1 = 11 Points
1.4DynamicAFAreaint8u[val >> 2 & 0x3] +
0 = 9 Points +
1 = 21 Points +
2 = 51 Points +
3 = 51 Points (3D-tracking)
2.1AFActivationint8u[val >> 7 & 0x1] +
0 = Shutter/AF-On +
1 = AF-On Only
2.2FocusPointWrapint8u[val >> 3 & 0x1] +
0 = No Wrap +
1 = Wrap
2.3AFPointIlluminationint8u[val >> 1 & 0x3] +
0 = Auto +
1 = Off +
2 = On
2.4AFAssistint8u[val & 0x1] +
0 = On +
1 = Off
3.1FocusTrackingLockOnint8u[val & 0x7] + +
0 = 3 Normal +
1 = 4 +
2 = 5 Long
  3 = 2 +
4 = 1 Short +
5 = Off
+
3.2AF-OnForMB-D10int8u[val >> 4 & 0x7] +
0 = AF-On +
1 = AE/AF Lock +
2 = AE Lock Only +
3 = AE Lock (reset on release) +
4 = AE Lock (hold) +
5 = AF Lock Only +
6 = Same as FUNC Button
+
4.1ISOStepSizeint8u[val >> 6 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
4.2ExposureControlStepSizeint8u[val >> 4 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
4.3ExposureCompStepSizeint8u[val >> 2 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
4.4EasyExposureCompensationint8u[val & 0x3] +
0 = Off +
1 = On +
2 = On (auto reset)
5.1CenterWeightedAreaSizeint8u[val >> 4 & 0x7] +
0 = 8 mm +
1 = 12 mm +
2 = 15 mm +
3 = 20 mm +
4 = Average
6.1FineTuneOptMatrixMeteringint8u[val >> 4 & 0xf]
6.2FineTuneOptSpotMeteringint8u[val & 0xf]
7.1ShutterReleaseButtonAE-Lint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
7.2SelfTimerTimeint8u[val >> 4 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
7.3MeteringTimeint8u[val & 0xf] + +
0 = 4 s +
1 = 6 s +
2 = 8 s +
3 = 16 s +
4 = 30 s
  5 = 1 min +
6 = 5 min +
7 = 10 min +
8 = 30 min +
9 = No Limit
+
8.1PlaybackMonitorOffTimeint8u[val >> 3 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
8.2MenuMonitorOffTimeint8u[val & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
9.1ShootingInfoMonitorOffTimeint8u[val >> 3 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
9.2ImageReviewTimeint8u[val & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
10.1Beepint8u[val >> 6 & 0x3] +
0 = High +
1 = Low +
2 = Off
10.2ShootingInfoDisplayint8u[val >> 4 & 0x3] +
0 = Auto +
1 = Auto +
2 = Manual (dark on light) +
3 = Manual (light on dark)
10.3LCDIlluminationint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
10.4ExposureDelayModeint8u[val >> 2 & 0x1] +
0 = Off +
1 = On
10.5GridDisplayint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
11.1FileNumberSequenceint8u[val >> 6 & 0x1] +
0 = On +
1 = Off
11.2CLModeShootingSpeedint8u[val & 0x7]
12MaxContinuousReleaseint8u 
13.1ScreenTipsint8u[val >> 3 & 0x1] +
0 = On +
1 = Off
13.2BatteryOrderint8u[val >> 2 & 0x1] +
0 = MB-D10 First +
1 = Camera Battery First
13.3MB-D10BatteryTypeint8u[val & 0x3] +
0 = LR6 (AA alkaline) +
1 = HR6 (AA Ni-MH) +
2 = FR6 (AA lithium) +
3 = ZR6 (AA Ni-Mn)
15.1FlashSyncSpeedint8u[val >> 4 & 0xf] + +
0 = 1/320 s (auto FP) +
1 = 1/250 s (auto FP) +
2 = 1/250 s +
3 = 1/200 s +
4 = 1/160 s
  5 = 1/125 s +
6 = 1/100 s +
7 = 1/80 s +
8 = 1/60 s
+
15.2FlashShutterSpeedint8u[val & 0xf] + +
0 = 1/60 s +
1 = 1/30 s +
2 = 1/15 s +
3 = 1/8 s +
4 = 1/4 s +
5 = 1/2 s
  6 = 1 s +
7 = 2 s +
8 = 4 s +
9 = 8 s +
10 = 15 s +
11 = 30 s
+
16.1FlashControlBuilt-inint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual +
2 = Repeating Flash +
3 = Commander Mode
16.2ManualFlashOutputint8u[val & 0x1f]
17.1RepeatingFlashOutputint8u[val >> 4 & 0x7]
17.2RepeatingFlashCountint8u[val & 0xf]
18.1RepeatingFlashRateint8u[val >> 4 & 0xf]
18.2CommanderInternalTTLChannelint8u[val & 0x3] +
0 = 1 ch +
1 = 2 ch +
2 = 3 ch +
3 = 4 ch
20.1CommanderInternalTTLCompBuiltinint8u[val & 0x1f]
21.1CommanderInternalTTLCompGroupAint8u[val & 0x1f]
22.1CommanderInternalTTLCompGroupBint8u[val & 0x1f]
26.1AutoBracketSetint8u[val >> 6 & 0x3] +
0 = AE & Flash +
1 = AE Only +
2 = Flash Only +
3 = WB Bracketing
26.2AutoBracketModeMint8u[val >> 4 & 0x3] +
0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only
26.3AutoBracketOrderint8u[val >> 3 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
26.4ModelingFlashint8u[val & 0x1] +
0 = On +
1 = Off
27.1MultiSelectorShootModeint8u[val >> 6 & 0x3] +
0 = Select Center Focus Point +
1 = Highlight Active Focus Point +
2 = Not Used
27.2MultiSelectorPlaybackModeint8u[val >> 4 & 0x3] +
0 = Thumbnail On/Off +
1 = View Histograms +
2 = Zoom On/Off +
3 = Choose Folder
27.3InitialZoomSettingint8u[val >> 2 & 0x3] +
0 = Low Magnification +
1 = Medium Magnification +
2 = High Magnification
27.4MultiSelectorint8u[val & 0x1] +
0 = Do Nothing +
1 = Reset Meter-off Delay
28.1FuncButtonint8u[val >> 3 & 0x1f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
9 = Flash Off +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = My Menu Top +
15 = Live View +
16 = + NEF (RAW) +
17 = Virtual Horizon
+
29.1PreviewButtonint8u[val >> 3 & 0x1f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-ON +
9 = Flash Off +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = My Menu Top +
15 = Live View +
16 = + NEF (RAW) +
17 = Virtual Horizon
+
30.1AELockButtonint8u(D300) +
[val >> 3 & 0x1f]
+
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-ON +
9 = Flash Off +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = My Menu Top +
15 = Live View +
16 = + NEF (RAW) +
17 = Virtual Horizon
+
31.1FuncButtonPlusDialsint8u[val >> 4 & 0x7] +
0 = None +
1 = Choose Image Area +
2 = One Step Speed/Aperture +
3 = Choose Non-CPU Lens Number +
5 = Auto bracketing +
6 = Dynamic AF Area +
7 = Shutter speed & Aperture lock
+
31.2PreviewButtonPlusDialsint8u[val & 0x7] +
0 = None +
1 = Choose Image Area +
2 = One Step Speed/Aperture +
3 = Choose Non-CPU Lens Number +
5 = Auto bracketing +
6 = Dynamic AF Area +
7 = Shutter speed & Aperture lock
+
32.1AELockButtonPlusDialsint8u[val >> 4 & 0x7]
33.1CommandDialsReverseRotationint8u[val >> 7 & 0x1] +
0 = No +
1 = Yes
33.2CommandDialsChangeMainSubint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
33.3CommandDialsApertureSettingint8u[val >> 5 & 0x1] +
0 = Sub-command Dial +
1 = Aperture Ring
33.4CommandDialsMenuAndPlaybackint8u[val >> 4 & 0x1] +
0 = Off +
1 = On
33.5ReverseIndicatorsint8u[val >> 3 & 0x1] +
0 = + 0 - +
1 = - 0 +
33.6PhotoInfoPlaybackint8u[val >> 2 & 0x1] +
0 = Off +
1 = On
33.7NoMemoryCardint8u[val >> 1 & 0x1] +
0 = Release Locked +
1 = Enable Release
33.8ReleaseButtonToUseDialint8u[val & 0x1] +
0 = No +
1 = Yes
+ +

NikonCustom SettingsD800 Tags

+

Custom settings for the D800 and D800E.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12.1AutoBracketingSetint8u[val >> 5 & 0x7] +
0 = AE & Flash +
1 = AE Only +
2 = Flash Only +
3 = WB Bracketing +
4 = Active D-Lighting
12.2AutoBracketOrderint8u[val >> 4 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
12.3AutoBracketingModeint8u[val >> 2 & 0x3] +
0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only
22.1FlashSyncSpeedint8u[val >> 4 & 0xf] + +
0 = 1/320 s (auto FP) +
1 = 1/250 s (auto FP) +
2 = 1/250 s +
3 = 1/200 s +
4 = 1/160 s
  5 = 1/125 s +
6 = 1/100 s +
7 = 1/80 s +
8 = 1/60 s
+
22.2FlashShutterSpeedint8u[val & 0xf] + +
0 = 1/60 s +
1 = 1/30 s +
2 = 1/15 s +
3 = 1/8 s +
4 = 1/4 s +
5 = 1/2 s
  6 = 1 s +
7 = 2 s +
8 = 4 s +
9 = 8 s +
10 = 15 s +
11 = 30 s
+
23.1FlashControlBuilt-inint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual +
2 = Repeating Flash +
3 = Commander Mode
23.2ManualFlashOutputint8u[val & 0x1f]
24.1RepeatingFlashOutputint8u[val >> 4 & 0x7]
24.2RepeatingFlashCountint8u[val & 0xf]
25.1RepeatingFlashRateint8u[val >> 4 & 0xf]
25.2CommanderChannelint8u[val & 0x3]
27.1CommanderInternalFlashint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual +
2 = Off
27.2CommanderInternalManualOutputint8u[val & 0x1f]
28.1CommanderGroupAModeint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Auto Aperture +
2 = Manual +
3 = Off
28.2CommanderGroupAManualOutputint8u[val & 0x1f]
29.1CommanderGroupBModeint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Auto Aperture +
2 = Manual +
3 = Off
29.2CommanderGroupBManualOutputint8u[val & 0x1f]
30.1ModelingFlashint8u[val >> 5 & 0x1] +
0 = On +
1 = Off
30.2CommanderInternalTTLCompint8u[val & 0x1f]
31.1CommanderGroupA_TTL-AACompint8u[val & 0x1f]
32.1CommanderGroupB_TTL-AACompint8u[val & 0x1f]
+ +

NikonCustom SettingsD810 Tags

+

Custom settings for the D810.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1LightSwitchint8u[val >> 3 & 0x1] +
0 = LCD Backlight +
1 = LCD Backlight and Shooting Information
0.2CustomSettingsBankint8u[val & 0x3] +
0 = A +
1 = B +
2 = C +
3 = D
1.1AF-CPrioritySelectionint8u[val >> 6 & 0x3] +
0 = Release +
1 = Release + Focus +
2 = Focus
1.2AF-SPrioritySelectionint8u[val >> 5 & 0x1] +
0 = Focus +
1 = Release
1.3AFPointSelectionint8u[val >> 4 & 0x1] +
0 = 51 Points +
1 = 11 Points
1.4FocusTrackingLockOnint8u[val & 0x7] + +
0 = Off +
1 = 1 (Short) +
2 = 2
  3 = 3 (Normal) +
4 = 4 +
5 = 5 (Long)
+
2.1AFActivationint8u[val >> 7 & 0x1] +
0 = Shutter/AF-On +
1 = AF-On Only
2.2FocusPointWrapint8u[val >> 3 & 0x1] +
0 = No Wrap +
1 = Wrap
2.3AFPointBrightnessint8u[val >> 1 & 0x3] +
0 = Auto +
1 = On +
2 = Off
2.4AFAssistint8u[val & 0x1] +
0 = On +
1 = Off
3.1BatteryOrderint8u[val >> 6 & 0x1] +
0 = MB-D12 First +
1 = Camera Battery First
3.2MB-D12BatteryTypeint8u[val & 0x3] +
0 = LR6 (AA alkaline) +
1 = HR6 (AA Ni-MH) +
2 = FR6 (AA lithium)
4.1Pitchint8u[val >> 6 & 0x1] +
0 = High +
1 = Low
4.2NoMemoryCardint8u[val >> 5 & 0x1] +
0 = Release Locked +
1 = Enable Release
4.3ISODisplayint8u[val >> 2 & 0x3] +
0 = Show ISO/Easy ISO +
1 = Show ISO Sensitivity +
3 = Show Frame Count
4.4GridDisplayint8u[val >> 1 & 0x1] +
0 = On +
1 = Off
5.1ShootingInfoDisplayint8u[val >> 6 & 0x3] +
0 = Not Set +
1 = Auto +
2 = Manual (dark on light) +
3 = Manual (light on dark)
5.2LCDIlluminationint8u[val >> 5 & 0x1] +
0 = Off +
1 = On
5.3ElectronicFront-CurtainShutterint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
5.4ScreenTipsint8u[val >> 2 & 0x1] +
0 = Off +
1 = On
5.5Beepint8u[val & 0x3] +
0 = Off +
1 = Low +
2 = Medium +
3 = High
6.1ReverseIndicatorsint8u[val >> 7 & 0x1] +
0 = + 0 - +
1 = - 0 +
6.2CommandDialsReverseRotationint8u[val >> 3 & 0x3] +
0 = No +
1 = Shutter Speed & Aperture +
2 = Exposure Compensation +
3 = Exposure Compensation, Shutter Speed & Aperture
6.3EasyExposureCompensationint8u[val & 0x3] +
0 = Off +
1 = On +
2 = On (auto reset)
7.1ExposureControlStepSizeint8u[val >> 6 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
7.2ISOStepSizeint8u[val >> 4 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
7.3ExposureCompStepSizeint8u[val >> 2 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
8.1CenterWeightedAreaSizeint8u[val >> 5 & 0x7] +
0 = 8 mm +
1 = 12 mm +
2 = 15 mm +
3 = 20 mm +
4 = Average
8.2FineTuneOptMatrixMeteringint8u[val & 0xf]
9.1FineTuneOptCenterWeightedint8u[val >> 4 & 0xf]
9.2FineTuneOptSpotMeteringint8u[val & 0xf]
10.1MultiSelectorShootModeint8u[val >> 6 & 0x3] +
0 = Select Center Focus Point (Reset) +
1 = Highlight Active Focus Point +
2 = Preset Focus Point (Pre) +
3 = Not Used (None)
10.2MultiSelectorPlaybackModeint8u[val >> 4 & 0x3] +
0 = Thumbnail On/Off +
1 = View Histograms +
2 = Zoom On/Off +
3 = Choose Folder
10.3MultiSelectorint8u[val & 0x1] +
0 = Do Nothing +
1 = Reset Meter-off Delay
11.1ExposureDelayModeint8u[val >> 6 & 0x3] +
0 = Off +
1 = 1 s +
2 = 2 s +
3 = 3 s
11.2CLModeShootingSpeedint8u[val & 0xf]
12.1MaxContinuousReleaseint8u 
13.1AutoBracketSetint8u[val >> 5 & 0x7] +
0 = AE & Flash +
1 = AE Only +
2 = Flash Only +
3 = WB Bracketing +
4 = Active D-Lighting
13.2AutoBracketOrderint8u[val >> 4 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
13.3AutoBracketModeMint8u[val >> 2 & 0x3] +
0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only
14.1FuncButtonint8u[val & 0x1f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
19 = Grid Display +
20 = My Menu +
21 = Disable Synchronized Release +
22 = Remote Release Only +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering
+
15.1PreviewButtonint8u[val & 0x1f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
19 = Grid Display +
20 = My Menu +
21 = Disable Synchronized Release +
22 = Remote Release Only +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering
+
16.1AssignBktButtonint8u[val & 0x7] +
0 = Auto Bracketing +
1 = Multiple Exposure +
2 = HDR (high dynamic range) +
3 = None
17.1AELockButtonint8u[val & 0x1f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
19 = Grid Display +
20 = My Menu +
21 = Disable Synchronized Release +
22 = Remote Release Only +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering
+
18.1CommandDialsChangeMainSubint8u[val >> 5 & 0x7] +
0 = Autofocus Off, Exposure Off +
1 = Autofocus Off, Exposure On +
2 = Autofocus Off, Exposure On (Mode A) +
4 = Autofocus On, Exposure Off +
5 = Autofocus On, Exposure On +
6 = Autofocus On, Exposure On (Mode A)
+
18.2CommandDialsMenuAndPlaybackint8u[val >> 3 & 0x3] +
0 = On +
1 = Off +
2 = On (Image Review Excluded)
18.3CommandDialsApertureSettingint8u[val >> 2 & 0x1] +
0 = Sub-command Dial +
1 = Aperture Ring
18.4ShutterReleaseButtonAE-Lint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
18.5ReleaseButtonToUseDialint8u[val & 0x1] +
0 = No +
1 = Yes
19.1StandbyTimerint8u[val >> 4 & 0xf] + + +
0 = 4 s +
1 = 6 s +
3 = 10 s
  5 = 30 s +
6 = 1 min +
7 = 5 min
  8 = 10 min +
9 = 30 min +
10 = No Limit
+
20.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
20.2SelfTimerShotIntervalint8u[val >> 4 & 0x3] +
0 = 0.5 s +
1 = 1 s +
2 = 2 s +
3 = 3 s
20.3SelfTimerShotCountint8u[val & 0xf]
21.1ImageReviewMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 2 s +
1 = 4 s +
3 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
21.2LiveViewMonitorOffTimeint8u[val >> 2 & 0x7] + +
1 = 5 min +
2 = 10 min +
3 = 15 min
  4 = 20 min +
5 = 30 min +
6 = No Limit
+
22.1MenuMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
2 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
22.2ShootingInfoMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
2 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
23.1FlashSyncSpeedint8u[val >> 4 & 0xf] + +
0 = 1/320 s (auto FP) +
2 = 1/250 s (auto FP) +
3 = 1/250 s +
5 = 1/200 s +
6 = 1/160 s
  7 = 1/125 s +
8 = 1/100 s +
9 = 1/80 s +
10 = 1/60 s
+
23.2FlashShutterSpeedint8u[val & 0xf] + +
0 = 1/60 s +
1 = 1/30 s +
2 = 1/15 s +
3 = 1/8 s +
4 = 1/4 s +
5 = 1/2 s
  6 = 1 s +
7 = 2 s +
8 = 4 s +
9 = 8 s +
10 = 15 s +
11 = 30 s
+
24.1FlashControlBuilt-inint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual +
2 = Repeating Flash +
3 = Commander Mode
31.1ModelingFlashint8u[val >> 5 & 0x1] +
0 = On +
1 = Off
36.1PlaybackMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
37.1MultiSelectorLiveViewint8u[val >> 6 & 0x3] +
0 = Reset +
1 = Zoom +
3 = Not Used
38.1ShutterSpeedLockint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
38.2ApertureLockint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
38.3MovieShutterButtonint8u[val >> 5 & 0x1] +
0 = Take Photo +
1 = Record Movies
38.4FlashExposureCompAreaint8u[val >> 2 & 0x1] +
0 = Entire frame +
1 = Background only
40.1MovieAELockButtonAssignmentint8u[val & 0xf] +
0 = None +
3 = Index Marking +
4 = View Photo Shooting Info +
5 = AE/AF Lock +
6 = AE Lock Only +
7 = AE Lock (hold) +
8 = AF Lock Only
+
41.1MovieFunctionButtonint8u[val >> 4 & 0x7] +
0 = None +
1 = Power Aperture (open) +
3 = Index Marking +
4 = View Photo Shooting Info
41.2MoviePreviewButtonint8u[val & 0x7] +
0 = None +
2 = Power Aperture (open) +
3 = Index Marking +
4 = View Photo Shooting Info
42.1FuncButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
8 = Exposure Delay Mode
+
43.1PreviewButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
8 = Exposure Delay Mode
+
44.1AELockButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = Shutter Speed & Aperture Lock +
4 = Choose Non-CPU Lens Number +
8 = Exposure Delay Mode
45.1AssignMovieRecordButtonint8u[val & 0xf] +
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = Shutter Speed & Aperture Lock +
9 = White Balance +
10 = ISO Sensitivity
46.1FineTuneOptHighlightWeightedint8u[val & 0xf]
47.1DynamicAreaAFDisplayint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
47.2AFPointIlluminationint8u[val >> 6 & 0x1] +
0 = Off +
1 = On During Manual Focusing
47.3StoreByOrientationint8u[val >> 3 & 0x3] +
0 = Off +
1 = Focus Point +
2 = Focus Point and AF-area mode
47.4GroupAreaAFIlluminationint8u[val >> 2 & 0x1] +
0 = Squares +
1 = Dots
48.1MatrixMeteringint8u[val >> 7 & 0x1] +
0 = Face Detection On +
1 = Face Detection Off
48.2LiveViewButtonOptionsint8u[val >> 4 & 0x3] +
0 = Enable +
2 = Disable
48.3AFModeRestrictionsint8u[val & 0x3] +
0 = No Restrictions +
1 = AF-C +
2 = AF-S
49.1LimitAFAreaModeSelectionint8u[val >> 1 & 0x3f] +
0x0 = No Restrictions +
Bit 0 = Auto-area +
Bit 1 = Group-area +
Bit 2 = 3D-tracking +
Bit 3 = Dynamic area (51 points) +
Bit 4 = Dynamic area (21 points) +
Bit 5 = Dynamic area (9 points)
+
50.1AF-OnForMB-D12int8u[val & 0x7] + +
0 = AE/AF Lock +
1 = AE Lock Only +
2 = AF Lock Only +
3 = AE Lock (hold)
  4 = AE Lock (reset) +
5 = AF-On +
6 = FV Lock +
7 = Same As Fn Button
+
51.1AssignRemoteFnButtonint8u[val & 0x1f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
7 = AF Lock Only +
8 = AF-On +
16 = +NEF(RAW) +
25 = Live View +
26 = Flash Disable/Enable
+
52.1LensFocusFunctionButtonsint8u[val & 0x3f] +
3 = AE/AF Lock +
4 = AE Lock Only +
7 = AF Lock Only +
21 = Disable Synchronized Release +
22 = Remote Release Only +
24 = Preset focus Point +
26 = Flash Disable/Enable +
32 = AF-Area Mode: Single-point AF +
33 = AF-Area Mode: Dynamic-area AF (9 points) +
34 = AF-Area Mode: Dynamic-area AF (21 points) +
35 = AF-Area Mode: Dynamic-area AF (51 points) +
36 = AF-Area Mode: Group-area AF +
37 = AF-Area Mode: Auto area AF
+
+ +

NikonCustom SettingsD850 Tags

+

Custom settings for the D850.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.2CustomSettingsBankint8u[val & 0x3] +
0 = A +
1 = B +
2 = C +
3 = D
1.1AF-CPrioritySelectionint8u[val >> 6 & 0x3] +
0 = Release +
1 = Release + Focus +
2 = Focus +
3 = Focus + Release
1.2AF-SPrioritySelectionint8u[val >> 5 & 0x1] +
0 = Focus +
1 = Release
1.3AFPointSelectionint8u[val >> 4 & 0x1] +
0 = 55 Points +
1 = 15 Points
1.4Three-DTrackingFaceDetectionint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
1.5BlockShotAFResponseint8u[val & 0x7] +
1 = 1 (Quick) +
2 = 2 +
3 = 3 (Normal) +
4 = 4 +
5 = 5 (Delay)
2.1FocusPointWrapint8u[val >> 3 & 0x1] +
0 = No Wrap +
1 = Wrap
2.2AFPointBrightnessint8u[val >> 1 & 0x3] +
0 = Auto +
1 = On +
2 = Off
4.1ISODisplayint8u[val >> 3 & 0x1] +
0 = Show ISO Sensitivity +
1 = Show Frame Count
4.2GridDisplayint8u[val >> 1 & 0x1] +
0 = On +
1 = Off
5.1LCDIlluminationint8u[val >> 5 & 0x1] +
0 = Off +
1 = On
5.2ElectronicFront-CurtainShutterint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
6.1ReverseIndicatorsint8u[val >> 7 & 0x1] +
0 = + 0 - +
1 = - 0 +
6.2CommandDialsReverseRotationint8u[val >> 3 & 0x3] +
0 = No +
1 = Shutter Speed & Aperture +
2 = Exposure Compensation +
3 = Exposure Compensation, Shutter Speed & Aperture
6.3EasyExposureCompensationint8u[val & 0x3] +
0 = Off +
1 = On +
2 = On (Auto Reset)
7.1ExposureControlStepSizeint8u[val >> 6 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
7.2ISOStepSizeint8u[val >> 4 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
7.3ExposureCompStepSizeint8u[val >> 2 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
8.1CenterWeightedAreaSizeint8u[val >> 5 & 0x7] +
0 = 8 mm +
1 = 12 mm +
2 = 15 mm +
3 = 20 mm +
4 = Average
8.2FineTuneOptMatrixMeteringint8u[val & 0xf]
9.1FineTuneOptCenterWeightedint8u[val >> 4 & 0xf]
9.2FineTuneOptSpotMeteringint8u[val & 0xf]
10.1MultiSelectorShootModeint8u[val >> 5 & 0x7] +
0 = Select Center Focus Point (Reset) +
2 = Preset Focus Point (Pre) +
3 = Highlight Active Focus Point +
4 = Not Used (None)
10.2MultiSelectorPlaybackModeint8u[val >> 2 & 0x3] +
0 = Thumbnail On/Off +
1 = View Histograms +
2 = Zoom On/Off +
3 = Choose Folder
10.3MultiSelectorint8u[val & 0x1] +
0 = Do Nothing +
1 = Reset Meter-off Delay
11.1ExposureDelayModeint8u[val >> 5 & 0x7] + +
0 = Off +
1 = 0.2 s +
2 = 0.5 s
  3 = 1 s +
4 = 2 s +
5 = 3 s
+
11.2CLModeShootingSpeedint8u[val & 0xf]
12.1MaxContinuousReleaseint8u 
13.1AutoBracketOrderint8u[val >> 4 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
13.2AutoBracketModeMint8u[val >> 2 & 0x3] +
0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only
14.1Func1Buttonint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
19 = Grid Display +
20 = My Menu +
22 = Remote Release Only +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 153 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 153 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
56 = AF-Area Mode (Dynamic Area 9 Points) +
57 = AF-Area Mode + AF-On (Dynamic Area 9 Points)
15.1PreviewButtonint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
19 = Grid Display +
20 = My Menu +
22 = Remote Release Only +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 153 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 153 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
56 = AF-Area Mode (Dynamic Area 9 Points) +
57 = AF-Area Mode + AF-On (Dynamic Area 9 Points)
16.1AssignBktButtonint8u[val & 0x7] +
0 = Auto Bracketing +
1 = Multiple Exposure +
2 = HDR (high dynamic range) +
3 = None
18.1CommandDialsChangeMainSubint8u[val >> 5 & 0x7] +
0 = Autofocus Off, Exposure Off +
1 = Autofocus Off, Exposure On +
2 = Autofocus Off, Exposure On (Mode A) +
4 = Autofocus On, Exposure Off +
5 = Autofocus On, Exposure On +
6 = Autofocus On, Exposure On (Mode A)
+
18.2CommandDialsMenuAndPlaybackint8u[val >> 3 & 0x3] +
0 = On +
1 = Off +
2 = On (Image Review Excluded)
18.3CommandDialsApertureSettingint8u[val >> 2 & 0x1] +
0 = Sub-command Dial +
1 = Aperture Ring
18.4ReleaseButtonToUseDialint8u[val & 0x1] +
0 = No +
1 = Yes
19.1StandbyTimerint8u[val >> 4 & 0xf] + + +
0 = 4 s +
1 = 6 s +
3 = 10 s
  5 = 30 s +
6 = 1 min +
7 = 5 min
  8 = 10 min +
9 = 30 min +
10 = No Limit
+
20.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
20.2SelfTimerShotIntervalint8u[val >> 4 & 0x3] +
0 = 0.5 s +
1 = 1 s +
2 = 2 s +
3 = 3 s
20.3SelfTimerShotCountint8u[val & 0xf]
21.1ImageReviewMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 2 s +
1 = 4 s +
3 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
21.2LiveViewMonitorOffTimeint8u[val >> 2 & 0x7] + +
1 = 5 min +
2 = 10 min +
3 = 15 min
  4 = 20 min +
5 = 30 min +
6 = No Limit
+
22.1MenuMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
2 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
22.2ShootingInfoMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
2 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
23.1FlashSyncSpeedint8u[val >> 4 & 0xf] + +
2 = 1/250 s (auto FP) +
3 = 1/250 s +
5 = 1/200 s +
6 = 1/160 s
  7 = 1/125 s +
8 = 1/100 s +
9 = 1/80 s +
10 = 1/60 s
+
23.2FlashShutterSpeedint8u[val & 0xf] + +
0 = 1/60 s +
1 = 1/30 s +
2 = 1/15 s +
3 = 1/8 s +
4 = 1/4 s +
5 = 1/2 s
  6 = 1 s +
7 = 2 s +
8 = 4 s +
9 = 8 s +
10 = 15 s +
11 = 30 s
+
31.1ModelingFlashint8u[val >> 5 & 0x1] +
0 = On +
1 = Off
36.1PlaybackMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
37.1MultiSelectorLiveViewint8u[val >> 6 & 0x3] +
0 = Reset +
1 = Zoom +
3 = Not Used
38.1ShutterSpeedLockint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
38.2ApertureLockint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
38.3MovieShutterButtonint8u[val >> 4 & 0x1] +
0 = Take Photo +
1 = Record Movies
38.4FlashExposureCompAreaint8u[val >> 2 & 0x1] +
0 = Entire Frame +
1 = Background Only
38.5AutoFlashISOSensitivityint8u[val >> 1 & 0x1] +
0 = Subject and Background +
1 = Subject Only
41.1MovieFunc1Buttonint8u[val >> 4 & 0xf] +
0 = None +
2 = Power Aperture (close) +
3 = Index Marking +
4 = View Photo Shooting Info +
11 = Exposure Compensation -
41.2MoviePreviewButtonint8u[val & 0xf] +
0 = None +
1 = Power Aperture (open) +
3 = Index Marking +
4 = View Photo Shooting Info +
10 = Exposure Compensation +
42.1Func1ButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
7 = Photo Shooting Menu Bank +
8 = Exposure Delay Mode
+
43.1PreviewButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
7 = Photo Shooting Menu Bank +
8 = Exposure Delay Mode
+
45.1AssignMovieRecordButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
7 = Photo Shooting Menu Bank +
11 = Exposure Mode
46.1FineTuneOptHighlightWeightedint8u[val & 0xf]
47.1DynamicAreaAFDisplayint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
47.2AFPointIlluminationint8u[val >> 6 & 0x1] +
0 = Off +
1 = On During Manual Focusing
47.3StoreByOrientationint8u[val >> 3 & 0x3] +
0 = Off +
1 = Focus Point +
2 = Focus Point and AF-area Mode
48.1MatrixMeteringint8u[val >> 7 & 0x1] +
0 = Face Detection On +
1 = Face Detection Off
48.2LiveViewButtonOptionsint8u[val >> 4 & 0x3] +
0 = Enable +
1 = Enable (Standby Timer Active) +
2 = Disable
48.3AFModeRestrictionsint8u[val & 0x3] +
0 = No Restrictions +
1 = AF-C +
2 = AF-S
49.1LimitAFAreaModeSelectionint8u[val >> 1 & 0x3f] +
0x0 = No Restrictions +
Bit 0 = Auto-area +
Bit 1 = Group-area +
Bit 2 = 3D-tracking +
Bit 3 = Dynamic area (153 points) +
Bit 4 = Dynamic area (72 points) +
Bit 5 = Dynamic area (25 points)
+
52.1LensFocusFunctionButtonsint8u[val & 0x3f] +
3 = AE/AF Lock +
4 = AE Lock Only +
7 = AF Lock Only +
8 = AF-On +
24 = Preset Focus Point +
26 = Flash Disable/Enable +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 153 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 153 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
56 = AF-Area Mode (Dynamic Area 9 Points) +
57 = AF-Area Mode + AF-On (Dynamic Area 9 Points)
66.1VerticalMultiSelectorint8u[val & 0xff] +
0x0 = Same as Multi-Selector with Info(U/D) & Playback(R/L) +
0x8 = Same as Multi-Selector with Info(R/L) & Playback(U/D) +
0x80 = Focus Point Selection
67.1AssignMB-D18FuncButtonint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
18 = Reset Focus Point +
19 = Grid Display +
20 = My Menu +
22 = Remote Release Only +
23 = Preset Focus Point +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 153 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 153 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
54 = Highlight Active Focus Point +
56 = AF-Area Mode (Dynamic Area 9 Points) +
57 = AF-Area Mode + AF-On (Dynamic Area 9 Points)
68.1AssignMB-D18FuncButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
7 = Photo Shooting Menu Bank +
8 = Exposure Delay Mode +
10 = ISO Sensitivity +
11 = Exposure Mode +
12 = Exposure Compensation +
13 = Metering Mode
+
70.1AF-OnButtonint8u[val & 0x3f] +
0 = None +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 153 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 153 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
56 = AF-Area Mode (Dynamic Area 9 Points) +
57 = AF-Area Mode + AF-On (Dynamic Area 9 Points)
71.1SubSelectorint8u[val >> 7 & 0x1] +
0 = Focus Point Selection +
1 = Same as MultiSelector
72.1SubSelectorCenterint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
18 = Reset Focus Point +
19 = Grid Display +
20 = My Menu +
22 = Remote Release Only +
23 = Preset Focus Point +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 153 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 153 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
54 = Highlight Active Focus Point +
56 = AF-Area Mode (Dynamic Area 9 Points) +
57 = AF-Area Mode + AF-On (Dynamic Area 9 Points)
73.1SubSelectorPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
4 = Choose Non-CPU Lens Number +
7 = Photo Shooting Menu Bank
74.1AssignMovieSubselectorint8u[val >> 4 & 0xf] +
0 = None +
3 = Index Marking +
4 = View Photo Shooting Info +
5 = AE/AF Lock +
6 = AE Lock (Only) +
7 = AE Lock (Hold) +
8 = AF Lock (Only)
+
75.1AssignMovieFunc1ButtonPlusDialsint8u[val >> 4 & 0x1] +
0 = None +
1 = Choose Image Area
75.2AssignMoviePreviewButtonPlusDialsint8u[val & 0x1] +
0 = None +
1 = Choose Image Area
76.1AssignMovieSubselectorPlusDialsint8u[val >> 4 & 0x1] +
0 = None +
1 = Choose Image Area
77.1SyncReleaseModeint8u[val >> 7 & 0x1] +
0 = No Sync +
1 = Sync
77.2ContinuousModeLiveViewint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
78.1Three-DTrackingWatchAreaint8u[val >> 7 & 0x1] +
0 = Wide +
1 = Normal
78.2SubjectMotionint8u[val >> 5 & 0x3] +
0 = Steady +
1 = Middle +
2 = Erratic
78.3AFActivationint8u[val >> 3 & 0x1] +
0 = Shutter/AF-On +
1 = AF-On Only
78.4ShutterReleaseButtonAE-Lint8u[val & 0x3] +
0 = Off +
1 = On (Half Press) +
2 = On (Burst Mode)
79.1AssignMB-D18AF-OnButtonint8u[val & 0x7f] +
0 = None +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 153 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 153 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
56 = AF-Area Mode (Dynamic Area 9 Points) +
57 = AF-Area Mode + AF-On (Dynamic Area 9 Points) +
100 = Same as Camera AF-On Button
80.1Func2Buttonint8u[val & 0x3f] +
0 = None +
15 = My Menu Top Item +
20 = My Menu +
55 = Rating
82.1AssignMovieFunc2Buttonint8u[val >> 4 & 0x7] +
0 = None +
3 = Index Marking +
4 = View Photo Shooting Info
+ +

NikonCustom SettingsD5000 Tags

+

Custom settings for the D5000.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1AFAreaModeSettingint8u[val >> 5 & 0x3] +
0 = Single Area +
1 = Dynamic Area +
2 = Auto-area +
3 = 3D-tracking (11 points)
0.2AFAssistint8u[val & 0x1] +
0 = On +
1 = Off
2.1Beepint8u[val >> 6 & 0x3] +
0 = Off +
1 = Low +
2 = High
2.2GridDisplayint8u[val >> 1 & 0x1] +
0 = On +
1 = Off
2.3ISODisplayint8u[val >> 3 & 0x1] +
0 = On +
1 = Off
2.4NoMemoryCardint8u[val >> 5 & 0x1] +
0 = Release Locked +
1 = Enable Release
3.1FileNumberSequenceint8u[val >> 3 & 0x1] +
0 = On +
1 = Off
4.1RangeFinderint8u[val >> 4 & 0x1] +
0 = Off +
1 = On
4.2DateImprintint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
4.3ReverseIndicatorsint8u[val >> 7 & 0x1] +
0 = + 0 - +
1 = - 0 +
5.1EVStepSizeint8u[val >> 6 & 0x1] +
0 = 1/3 EV +
1 = 1/2 EV
9.1ExposureDelayModeint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
11.1AutoBracketSetint8u[val >> 6 & 0x3] +
0 = Exposure +
1 = Active D-Lighting +
2 = WB Bracketing
12.1TimerFunctionButtonint8u[val >> 3 & 0x7] + +
0 = Self-timer +
1 = Release Mode +
2 = Image Quality/Size +
3 = ISO
  4 = White Balance +
5 = Active D-Lighting +
6 = + NEF (RAW) +
7 = Auto Bracketing
+
15.1AELockButtonint8u[val >> 3 & 0x7] +
0 = AE/AF Lock +
1 = AE Lock Only +
2 = AF Lock Only +
3 = AE Lock (hold) +
4 = AF-ON
16.1ShutterReleaseButtonAE-Lint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
16.2CommandDialsReverseRotationint8u[val >> 7 & 0x1] +
0 = No +
1 = Yes
17.1MeteringTimeint8u[val >> 4 & 0x7] +
0 = 4 s +
1 = 8 s +
2 = 20 s +
3 = 1 min +
4 = 30 min
17.2RemoteOnDurationint8u[val & 0x3] +
0 = 1 min +
1 = 5 min +
2 = 10 min +
3 = 15 min
18.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
18.2SelfTimerShotCountint8u[val >> 1 & 0xf]
19.1ImageReviewTimeint8u[val >> 5 & 0x7] +
0 = 4 s +
1 = 8 s +
2 = 20 s +
3 = 1 min +
4 = 10 min
20.1PlaybackMenusTimeint8u[val >> 5 & 0x7] +
0 = 8 s +
1 = 12 s +
2 = 20 s +
3 = 1 min +
4 = 10 min
22.1InternalFlashint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual
22.2ManualFlashOutputint8u[val & 0x1f]
32.1LiveViewAFint8u[val >> 5 & 0x3] +
0 = Face Priority +
1 = Wide Area +
2 = Normal Area +
3 = Subject Tracking
+ +

NikonCustom SettingsD5100 Tags

+

Custom settings for the D5100.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1AF-CPrioritySelectionint8u[val >> 7 & 0x1] +
0 = Release +
1 = Focus
1.1AFAssistint8u[val & 0x1] +
0 = On +
1 = Off
3.1Beepint8u[val >> 6 & 0x3] +
0 = Off +
1 = Low +
2 = High
3.2NoMemoryCardint8u[val >> 5 & 0x1] +
0 = Release Locked +
1 = Enable Release
3.3ISODisplayint8u[val >> 3 & 0x1] +
0 = On +
1 = Off
4.1FileNumberSequenceint8u[val >> 3 & 0x1] +
0 = On +
1 = Off
5.1RangeFinderint8u[val >> 4 & 0x1] +
0 = Off +
1 = On
5.2ReverseIndicatorsint8u[val >> 7 & 0x1] +
0 = + 0 - +
1 = - 0 +
6.1EVStepSizeint8u[val >> 6 & 0x1] +
0 = 1/3 EV +
1 = 1/2 EV
10.1ExposureDelayModeint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
12.1AutoBracketSetint8u[val >> 6 & 0x3] +
0 = Exposure +
1 = WB Bracketing +
2 = Active D-Lighting
13.1TimerFunctionButtonint8u[val >> 3 & 0x7] + +
0 = Self-timer +
1 = Release Mode +
2 = Image Quality/Size +
3 = ISO
  4 = White Balance +
5 = Active D-Lighting +
6 = + NEF (RAW) +
7 = Auto Bracketing
+
16.1AELockButtonint8u[val >> 3 & 0x7] +
0 = AE/AF Lock +
1 = AE Lock Only +
2 = AF Lock Only +
3 = AE Lock (hold) +
4 = AF-ON
17.1ShutterReleaseButtonAE-Lint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
17.2CommandDialsReverseRotationint8u[val >> 7 & 0x1] +
0 = No +
1 = Yes
18.1MeteringTimeint8u[val >> 4 & 0x7] +
0 = 4 s +
1 = 8 s +
2 = 20 s +
3 = 1 min +
4 = 30 min
18.2RemoteOnDurationint8u[val & 0x3] +
0 = 1 min +
1 = 5 min +
2 = 10 min +
3 = 20 min
19.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
19.2SelfTimerShotCountint8u[val & 0xf]
20.1ImageReviewTimeint8u[val >> 5 & 0x7] +
0 = 4 s +
1 = 8 s +
2 = 20 s +
3 = 1 min +
4 = 10 min
20.2LiveViewMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 3 min +
1 = 5 min +
2 = 10 min
  3 = 15 min +
4 = 20 min +
5 = 30 min
+
21.1PlaybackMenusTimeint8u[val >> 5 & 0x7] +
0 = 8 s +
1 = 12 s +
2 = 20 s +
3 = 1 min +
4 = 10 min
23.1ManualFlashOutputint8u[val & 0x1f]
+ +

NikonCustom SettingsD5200 Tags

+

Custom settings for the D5200.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1AF-CPrioritySelectionint8u[val >> 7 & 0x1] +
0 = Release +
1 = Focus
0.2NumberOfFocusPointsint8u[val >> 4 & 0x1] +
0 = 39 Points +
1 = 11 Points
1.1AFAssistint8u[val & 0x1] +
0 = On +
1 = Off
3.1Beepint8u[val >> 6 & 0x3] +
0 = Off +
1 = Low +
2 = High
3.2NoMemoryCardint8u[val >> 5 & 0x1] +
0 = Release Locked +
1 = Enable Release
3.3ISODisplayint8u[val >> 3 & 0x1] +
0 = On +
1 = Off
4.1FileNumberSequenceint8u[val >> 3 & 0x1] +
0 = On +
1 = Off
5.1RangeFinderint8u[val >> 2 & 0x1] +
0 = Off +
1 = On
5.2ReverseExposureCompDialint8u[val >> 4 & 0x1] +
0 = No +
1 = Yes
5.3ReverseShutterSpeedApertureint8u[val >> 3 & 0x1] +
0 = No +
1 = Yes
5.4ReverseIndicatorsint8u[val >> 7 & 0x1] +
0 = + 0 - +
1 = - 0 +
6.1EVStepSizeint8u[val >> 6 & 0x1] +
0 = 1/3 EV +
1 = 1/2 EV
10.1ExposureDelayModeint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
12.1AutoBracketSetint8u[val >> 6 & 0x3] +
0 = Exposure +
1 = WB Bracketing +
2 = Active D-Lighting
13.1FunctionButtonint8u[val & 0x1f] + +
3 = AE/AF Lock +
4 = AE Lock Only +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-ON +
16 = + NEF (RAW) +
18 = Active D-Lighting
  25 = Live View +
26 = Image Quality +
27 = ISO +
28 = White Balance +
29 = HDR +
30 = Auto Bracketing +
31 = AF-area Mode
+
16.1AELockButtonint8u[val & 0xf] +
3 = AE/AF Lock +
4 = AE Lock Only +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-ON
17.1ShutterReleaseButtonAE-Lint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
18.1StandbyTimerint8u[val >> 5 & 0x7] +
0 = 4 s +
1 = 8 s +
2 = 20 s +
3 = 1 min +
4 = 30 min
18.2RemoteOnDurationint8u[val & 0x3] +
0 = 1 min +
1 = 5 min +
2 = 10 min +
3 = 15 min
19.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
19.2SelfTimerShotCountint8u[val & 0xf]
20.1ImageReviewTimeint8u[val >> 5 & 0x7] +
1 = 4 s +
2 = 8 s +
4 = 20 s +
5 = 1 min +
7 = 10 min
20.2LiveViewMonitorOffTimeint8u[val >> 2 & 0x7] +
1 = 5 min +
2 = 10 min +
3 = 15 min +
4 = 20 min +
5 = 30 min
21.1PlaybackMenusTimeint8u[val >> 5 & 0x7] +
1 = 8 s +
4 = 20 s +
5 = 1 min +
6 = 5 min +
7 = 10 min
23.1InternalFlashint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual
23.2ManualFlashOutputint8u[val & 0x1f]
+ +

NikonCustom SettingsD7000 Tags

+

Custom settings for the D7000.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1AF-CPrioritySelectionint8u[val >> 7 & 0x1] +
0 = Release +
1 = Focus
0.2AF-SPrioritySelectionint8u[val >> 5 & 0x1] +
0 = Focus +
1 = Release
0.3NumberOfFocusPointsint8u[val >> 4 & 0x1] +
0 = 39 Points +
1 = 11 Points
0.4FocusTrackingLockOnint8u[val & 0x7] + +
0 = Off +
1 = 1 Short +
2 = 2
  3 = 3 Normal +
4 = 4 +
5 = 5 Long
+
1.1FocusPointWrapint8u[val >> 3 & 0x1] +
0 = No Wrap +
1 = Wrap
1.2AFPointIlluminationint8u[val >> 1 & 0x3] +
0 = Auto +
1 = On +
2 = Off
1.3AFAssistint8u[val & 0x1] +
0 = On +
1 = Off
2.1BatteryOrderint8u[val >> 6 & 0x1] +
0 = MB-D11 First +
1 = Camera Battery First
2.2AF-OnForMB-D11int8u[val >> 2 & 0x7] + +
0 = AE/AF Lock +
1 = AE Lock Only +
2 = AF Lock Only +
3 = AE Lock (hold)
  4 = AF-ON +
5 = FV Lock +
6 = Same as FUNC Button
+
2.3MB-D11BatteryTypeint8u[val & 0x3] +
0 = LR6 (AA alkaline) +
1 = Ni-MH (AA Ni-MH) +
2 = FR6 (AA lithium)
3.1BeepPitchint8u[val >> 6 & 0x3] +
0 = Off +
1 = Low +
2 = High
3.2NoMemoryCardint8u[val >> 5 & 0x1] +
0 = Release Locked +
1 = Enable Release
3.3ISODisplayint8u[val >> 2 & 0x3] +
0 = Show ISO/Easy ISO +
1 = Show ISO Sensitivity +
3 = Show Frame Count
3.4GridDisplayint8u[val >> 1 & 0x1] +
0 = On +
1 = Off
3.5ViewfinderWarningint8u[val & 0x1] +
0 = On +
1 = Off
4.1ShootingInfoDisplayint8u[val >> 6 & 0x3] +
0 = Auto +
2 = Manual (dark on light) +
3 = Manual (light on dark)
4.2LCDIlluminationint8u[val >> 5 & 0x1] +
0 = Off +
1 = On
4.3FileNumberSequenceint8u[val >> 3 & 0x1] +
0 = On +
1 = Off
4.4ScreenTipsint8u[val >> 2 & 0x1] +
0 = Off +
1 = On
4.5BeepVolumeint8u[val & 0x3] +
0 = Off +
1 = 1 +
2 = 2 +
3 = 3
5.1ReverseIndicatorsint8u[val >> 7 & 0x1] +
0 = + 0 - +
1 = - 0 +
5.2EasyExposureCompensationint8u[val & 0x3] +
0 = Off +
1 = On +
2 = On Auto Reset
6.1ExposureControlStepint8u[val >> 6 & 0x1] +
0 = 1/3 EV +
1 = 1/2 EV
6.2ISOSensitivityStepint8u[val >> 4 & 0x1] +
0 = 1/3 EV +
1 = 1/2 EV
7.1CenterWeightedAreaSizeint8u[val >> 5 & 0x7] +
0 = 6 mm +
1 = 8 mm +
2 = 10 mm +
3 = 13 mm +
4 = Average
10.1ExposureDelayModeint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
10.2CLModeShootingSpeedint8u[val & 0x7]
11MaxContinuousReleaseint8u 
12.1AutoBracketSetint8u[val >> 5 & 0x7] +
0 = AE & Flash +
1 = AE Only +
2 = Flash Only +
3 = WB Bracketing +
4 = Active D-Lighting
12.2AutoBracketOrderint8u[val >> 4 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
13.1FuncButtonint8u[val >> 3 & 0x1f] +
0 = Grid Display +
1 = FV Lock +
2 = Flash Off +
3 = Matrix Metering +
4 = Center-weighted Metering +
5 = Spot Metering +
6 = My Menu Top +
7 = + NEF (RAW) +
8 = Active D-Lighting +
9 = Preview +
10 = AE/AF Lock +
11 = AE Lock Only +
12 = AF Lock Only +
13 = AE Lock (hold) +
14 = Bracketing Burst +
15 = Playback +
16 = 1EV Step Speed/Aperture +
17 = Choose Non-CPU Lens +
18 = Virtual Horizon +
19 = Start Movie Recording
+
14.1PreviewButtonint8u[val >> 3 & 0x1f] +
0 = Grid Display +
1 = FV Lock +
2 = Flash Off +
3 = Matrix Metering +
4 = Center-weighted Metering +
5 = Spot Metering +
6 = My Menu Top +
7 = + NEF (RAW) +
8 = Active D-Lighting +
9 = Preview +
10 = AE/AF Lock +
11 = AE Lock Only +
12 = AF Lock Only +
13 = AE Lock (hold) +
14 = Bracketing Burst +
15 = Playback +
16 = 1EV Step Speed/Aperture +
17 = Choose Non-CPU Lens +
18 = Virtual Horizon +
19 = Start Movie Recording
+
15.1OKButtonint8u[val >> 3 & 0x3] +
0 = Off +
1 = Select Center Focus Point +
2 = Highlight Active Focus Point +
3 = Not Used
16.1AELockButtonint8u[val >> 3 & 0x7] + +
0 = AE/AF Lock +
1 = AE Lock Only +
2 = AF Lock Only
  3 = AE Lock (hold) +
4 = AF-ON +
5 = FV Lock
+
17.1CommandDialsReverseRotationint8u[val >> 7 & 0x1] +
0 = No +
1 = Yes
17.2CommandDialsChangeMainSubint8u[val >> 5 & 0x3] +
0 = Off +
1 = On +
2 = On (A mode only)
17.3CommandDialsApertureSettingint8u[val >> 2 & 0x1] +
0 = Sub-command Dial +
1 = Aperture Ring
17.4CommandDialsMenuAndPlaybackint8u[val >> 3 & 0x3] +
0 = On +
1 = Off +
2 = On (Image Review Exclude)
17.5ShutterReleaseButtonAE-Lint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
17.6ReleaseButtonToUseDialint8u[val & 0x1] +
0 = No +
1 = Yes
18.1MeteringTimeint8u[val >> 4 & 0xf] + +
0 = 4 s +
1 = 6 s +
2 = 8 s +
3 = 16 s +
4 = 30 s
  5 = 1 min +
6 = 5 min +
7 = 10 min +
8 = 30 min +
9 = No Limit
+
18.2RemoteOnDurationint8u[val & 0x3] +
0 = 1 min +
1 = 5 min +
2 = 10 min +
3 = 15 min
19.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
19.2SelfTimerIntervalint8u[val >> 4 & 0x3] +
0 = 0.5 s +
1 = 1 s +
2 = 2 s +
3 = 3 s
19.3SelfTimerShotCountint8u[val & 0xf]
20.1ImageReviewTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
20.2LiveViewMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
21.1MenuMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
21.2ShootingInfoMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
22.1FlashSyncSpeedint8u[val >> 4 & 0xf] + +
0 = 1/320 s (auto FP) +
1 = 1/250 s (auto FP) +
2 = 1/250 s +
3 = 1/200 s +
4 = 1/160 s
  5 = 1/125 s +
6 = 1/100 s +
7 = 1/80 s +
8 = 1/60 s
+
22.2FlashShutterSpeedint8u[val & 0xf] + +
0 = 1/60 s +
1 = 1/30 s +
2 = 1/15 s +
3 = 1/8 s +
4 = 1/4 s +
5 = 1/2 s
  6 = 1 s +
7 = 2 s +
8 = 4 s +
9 = 8 s +
10 = 15 s +
11 = 30 s
+
23.1FlashControlBuilt-inint8u[val >> 6 & 0x3] +
0 = TTL +
1 = Manual +
2 = Repeating Flash +
3 = Commander Mode
23.2ManualFlashOutputint8u[val & 0x1f]
24.1RepeatingFlashOutputint8u[val >> 4 & 0x7]
24.2RepeatingFlashCountint8u[val & 0xf]
25.1RepeatingFlashRateint8u[val >> 4 & 0xf]
26.1CommanderInternalTTLCompBuiltinint8u[val & 0x1f]
27.1CommanderInternalTTLCompGroupAint8u[val & 0x1f]
28.1CommanderInternalTTLCompGroupBint8u[val & 0x1f]
30.1FlashWarningint8u[val >> 7 & 0x1] +
0 = On +
1 = Off
30.2ModelingFlashint8u[val >> 5 & 0x1] +
0 = On +
1 = Off
34.1LiveViewAFAreaModeint8u[val >> 5 & 0x3] +
0 = Face-Priority +
1 = NormalArea +
2 = WideArea +
3 = SubjectTracking
34.2LiveViewAFModeint8u[val >> 1 & 0x1] +
0 = AF-C +
1 = AF-F
35.1PlaybackMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
+ +

NikonCustom SettingsD4 Tags

+

Custom settings for the D4 and D4S.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1CustomSettingsBankint8u[val & 0x3] +
0 = A +
1 = B +
2 = C +
3 = D
1.1AF-CPrioritySelectionint8u[val >> 6 & 0x3] +
0 = Release +
1 = Release + Focus +
2 = Focus +
3 = Focus + Release
1.2AF-SPrioritySelectionint8u[val >> 5 & 0x1] +
0 = Focus +
1 = Release
1.3AFPointSelectionint8u[val >> 4 & 0x1] +
0 = 51 Points +
1 = 11 Points
1.4FocusTrackingLockOnint8u[val & 0x7] + +
0 = Off +
1 = 1 (Short) +
2 = 2
  3 = 3 (Normal) +
4 = 4 +
5 = 5 (Long)
+
2.1AFActivationint8u[val >> 7 & 0x1] +
0 = Shutter/AF-On +
1 = AF-On Only
2.2FocusPointWrapint8u[val >> 3 & 0x1] +
0 = No Wrap +
1 = Wrap
4.1Pitchint8u[val >> 6 & 0x1] +
0 = High +
1 = Low
4.2NoMemoryCardint8u[val >> 5 & 0x1] +
0 = Release Locked +
1 = Enable Release
4.3GridDisplayint8u[val >> 1 & 0x1] +
0 = On +
1 = Off
5.1ShootingInfoDisplayint8u[val >> 6 & 0x3] +
1 = Auto +
2 = Manual (dark on light) +
3 = Manual (light on dark)
5.2LCDIlluminationint8u[val >> 5 & 0x1] +
0 = Off +
1 = On
5.3ScreenTipsint8u[val >> 2 & 0x1] +
0 = Off +
1 = On
5.4Beepint8u[val & 0x3] +
0 = Off +
1 = Low +
2 = Medium +
3 = High
6.1ReverseIndicatorsint8u[val >> 7 & 0x1] +
0 = + 0 - +
1 = - 0 +
6.2RearDisplayint8u[val >> 6 & 0x1] +
0 = ISO +
1 = Exposures Remaining
6.3ViewfinderDisplayint8u[val >> 5 & 0x1] +
0 = Frame Count +
1 = Exposures Remaining
6.4CommandDialsReverseRotationint8u[val >> 3 & 0x3] +
0 = No +
1 = Shutter Speed & Aperture +
2 = Exposure Compensation +
3 = Exposure Compensation, Shutter Speed & Aperture
6.5EasyExposureCompensationint8u[val & 0x3] +
0 = Off +
1 = On +
2 = On (auto reset)
7.1ExposureControlStepSizeint8u[val >> 6 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
7.2ISOStepSizeint8u[val >> 4 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
7.3ExposureCompStepSizeint8u[val >> 2 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
8.1CenterWeightedAreaSizeint8u[val >> 5 & 0x7] +
0 = 8 mm +
1 = 12 mm +
2 = 15 mm +
3 = 20 mm +
4 = Average
8.2FineTuneOptMatrixMeteringint8u[val & 0xf]
9.1FineTuneOptCenterWeightedint8u[val >> 4 & 0xf]
9.2FineTuneOptSpotMeteringint8u[val & 0xf]
10.1MultiSelectorShootModeint8u[val >> 6 & 0x3] +
0 = Select Center Focus Point (Reset) +
2 = Preset Focus Point (Pre) +
3 = Not Used (None)
10.2MultiSelectorPlaybackModeint8u[val >> 4 & 0x3] +
0 = Thumbnail On/Off +
1 = View Histograms +
2 = Zoom On/Off +
3 = Choose Folder
10.3MultiSelectorint8u[val & 0x1] +
0 = Do Nothing +
1 = Reset Meter-off Delay
11.1ExposureDelayModeint8u[val >> 6 & 0x3] +
0 = Off +
1 = 1 s +
2 = 2 s +
3 = 3 s
11.2CHModeShootingSpeedint8u[val >> 4 & 0x1] +
0 = 10 fps +
1 = 11 fps
11.3CLModeShootingSpeedint8u[val & 0xf]
12MaxContinuousReleaseint8u 
13.1AutoBracketSetint8u[val >> 5 & 0x7] +
0 = AE & Flash +
1 = AE Only +
2 = Flash Only +
3 = WB Bracketing +
4 = Active D-Lighting
13.2AutoBracketOrderint8u[val >> 4 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
13.3AutoBracketModeMint8u[val >> 2 & 0x3] +
0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only
14.1FuncButtonint8u[val >> 3 & 0x1f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
18 = My Menu +
20 = Grid Display +
21 = Disable Synchronized Release +
22 = Remote Release Only +
26 = Flash Disable/Enable
+
14.2FuncButtonPlusDialsint8u[val & 0x7] +
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
6 = Shooting Bank Menu
+
15.1PreviewButtonint8u[val >> 3 & 0x1f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
18 = My Menu +
20 = Grid Display +
21 = Disable Synchronized Release +
22 = Remote Release Only +
26 = Flash Disable/Enable
+
15.2PreviewButtonPlusDialsint8u[val & 0x7] +
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
6 = Shooting Bank Menu
+
16.1AssignBktButtonint8u[val & 0x7] +
0 = Auto Bracketing +
1 = Multiple Exposure +
2 = HDR (high dynamic range) +
3 = None
18.1CommandDialsChangeMainSubint8u[val >> 5 & 0x7] +
0 = Autofocus Off, Exposure Off +
1 = Autofocus Off, Exposure On +
2 = Autofocus Off, Exposure On (Mode A) +
4 = Autofocus On, Exposure Off +
5 = Autofocus On, Exposure On +
6 = Autofocus On, Exposure On (Mode A)
+
18.2CommandDialsMenuAndPlaybackint8u[val >> 3 & 0x3] +
0 = On +
1 = Off +
2 = On (Image Review Excluded)
18.3CommandDialsApertureSettingint8u[val >> 2 & 0x1] +
0 = Sub-command Dial +
1 = Aperture Ring
18.4ShutterReleaseButtonAE-Lint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
18.5ReleaseButtonToUseDialint8u[val & 0x1] +
0 = No +
1 = Yes
19.1StandbyTimerint8u[val >> 4 & 0xf] + +
0 = 4 s +
1 = 6 s +
3 = 10 s +
5 = 30 s
  6 = 1 min +
7 = 5 min +
8 = 10 min +
9 = 30 min
+
20.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
20.2SelfTimerShotCountint8u[val & 0xf]
20.3SelfTimerShotIntervalint8u[val >> 4 & 0x3] +
0 = 0.5 s +
1 = 1 s +
2 = 2 s +
3 = 3 s
21.1ImageReviewMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 2 s +
1 = 4 s +
2 = 10 s +
3 = 20 s
  4 = 1 min +
5 = 5 min +
6 = 10 min
+
21.2LiveViewMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 5 min +
1 = 10 min +
2 = 15 min
  3 = 20 min +
4 = 30 min +
5 = No Limit
+
22.1MenuMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
22.2ShootingInfoMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
23.1FlashSyncSpeedint8u[val >> 4 & 0xf] + +
1 = 1/250 s (auto FP) +
2 = 1/250 s +
3 = 1/200 s +
4 = 1/160 s
  5 = 1/125 s +
6 = 1/100 s +
7 = 1/80 s +
8 = 1/60 s
+
23.2FlashShutterSpeedint8u[val & 0xf] + +
0 = 1/60 s +
1 = 1/30 s +
2 = 1/15 s +
3 = 1/8 s +
4 = 1/4 s +
5 = 1/2 s
  6 = 1 s +
7 = 2 s +
8 = 4 s +
9 = 8 s +
10 = 15 s +
11 = 30 s
+
31.1ModelingFlashint8u[val >> 5 & 0x1] +
0 = On +
1 = Off
36.1PlaybackMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
37.1PlaybackZoomint8u[val & 0x1] +
0 = Use Separate Zoom Buttons +
1 = Use Either Zoom Button with Command Dial
38.1ShutterSpeedLockint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
38.2ApertureLockint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
38.3MovieShutterButtonint8u[val >> 4 & 0x3] +
0 = Take Photo +
1 = Record Movies +
2 = Live Frame Grab
38.4FlashExposureCompAreaint8u[val >> 2 & 0x1] +
0 = Entire frame +
1 = Background only
41.1MovieFunctionButtonint8u[val >> 4 & 0x7] +
0 = None +
1 = Power Aperture (open) +
3 = Index Marking +
4 = View Photo Shooting Info
41.2MoviePreviewButtonint8u[val & 0x7] +
0 = None +
2 = Power Aperture (open) +
3 = Index Marking +
4 = View Photo Shooting Info
42.1VerticalMultiSelectorint8u[val >> 5 & 0x3] +
0 = Same as Multi-Selector with Info(U/D) & Playback(R/L) +
1 = Same as Multi-Selector with Info(R/L) & Playback(U/D) +
2 = Focus Point Selection
42.2VerticalFuncButtonint8u[val & 0x1f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
18 = My Menu +
20 = Grid Display +
26 = Flash Disable/Enable
+
43.1VerticalFuncButtonPlusDialsint8u[val >> 4 & 0xf] +
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
6 = Shooting Bank Menu +
7 = ISO Sensitivity +
8 = Exposure Mode +
9 = Exposure Compensation +
10 = Metering
+
43.2AssignMovieRecordButtonint8u[val & 0x7] +
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = Shutter Speed & Aperture Lock +
3 = ISO Sensitivity +
4 = Shooting Bank Menu
46.1DynamicAreaAFDisplayint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
46.2AFPointIlluminationint8u[val >> 5 & 0x3] +
0 = Off +
1 = On in Continuous Shooting Modes +
2 = On During Manual Focusing +
3 = On in Continuous Shooting and Manual Focusing
46.3StoreByOrientationint8u[val >> 3 & 0x3] +
0 = Off +
1 = Focus Point +
2 = Focus Point and AF-area mode
46.4GroupAreaAFIlluminationint8u[val >> 2 & 0x1] +
0 = Squares +
1 = Dots
46.5AFPointBrightnessint8u[val & 0x3] +
0 = Low +
1 = Normal +
2 = High +
3 = Extra High
47.1AFOnButtonint8u[val >> 4 & 0x7] +
0 = AF On +
1 = AE/AF Lock +
2 = AE Lock Only +
3 = AE Lock (reset on release) +
4 = AE Lock (hold) +
5 = AF Lock Only +
6 = None
+
47.2VerticalAFOnButtonint8u[val & 0x7] +
0 = Same as AF On +
1 = AF On +
2 = AE/AF Lock +
3 = AE Lock Only +
4 = AE Lock (reset on release) +
5 = AE Lock (hold) +
6 = AF Lock Only +
7 = None
+
48.1SubSelectorAssignmentint8u[val >> 7 & 0x1] +
0 = Focus Point Selection +
1 = Same As Multi-selector
48.2MovieSubSelectorAssignmentint8u[val & 0x7] +
0 = None +
1 = Index Marking +
2 = AE/AF Lock +
3 = AE Lock Only +
4 = AE Lock (hold) +
5 = AF Lock Only +
6 = View Photo Shooting Info
+
49.1SubSelectorint8u[val >> 3 & 0x1f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
18 = My Menu +
19 = Reset +
20 = Grid Display +
21 = Disable Synchronized Release +
22 = Remote Release Only +
23 = Preview +
26 = Flash Disable/Enable
+
49.2SubSelectorPlusDialsint8u[val & 0x7] +
0 = None +
1 = Choose Image Area (FX/DX/5:4) +
2 = Shutter Speed & Aperture Lock +
4 = Choose Non-CPU Lens Number +
6 = Shooting Bank Menu
50.1MatrixMeteringint8u(D4S only) +
[val >> 7 & 0x1] +
0 = Face Detection On +
1 = Face Detection Off
50.2LiveViewButtonOptionsint8u(D4S only) +
[val >> 4 & 0x3] +
0 = Enable +
1 = Enable (standby time active) +
2 = Disable
50.3AFModeRestrictionsint8u(D4S only) +
[val & 0x3] +
0 = Off +
1 = AF-C +
2 = AF-S
51.1LimitAFAreaModeSelectionint8u(D4S only) +
[val >> 1 & 0x3f]
+
0x0 = No Restrictions +
Bit 0 = Auto-area +
Bit 1 = Group-area +
Bit 2 = 3D-tracking +
Bit 3 = Dynamic area (51 points) +
Bit 4 = Dynamic area (21 points) +
Bit 5 = Dynamic area (9 points)
+
52.1MovieFunctionButtonPlusDialsint8u[val >> 4 & 0x1] +
0 = None +
1 = Choose Image Area
52.2MoviePreviewButtonPlusDialsint8u[val & 0x1] +
0 = None +
1 = Choose Image Area
53.1MovieSubSelectorAssignmentPlusDialsint8u[val >> 4 & 0x1] +
0 = None +
1 = Choose Image Area
54.1AssignRemoteFnButtonint8u(D4S only) +
[val & 0x1f]
+
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
7 = AF Lock Only +
8 = AF-On +
16 = +NEF(RAW) +
25 = Live View +
26 = Flash Disable/Enable
+
55.1LensFocusFunctionButtonsint8u(D4S only) +
[val & 0x3f]
+
3 = AE/AF Lock +
4 = AE Lock Only +
7 = AF Lock Only +
21 = Disable Synchronized Release +
22 = Remote Release Only +
24 = Preset focus Point +
26 = Flash Disable/Enable +
32 = AF-Area Mode: Single-point AF +
33 = AF-Area Mode: Dynamic-area AF (9 points) +
34 = AF-Area Mode: Dynamic-area AF (21 points) +
35 = AF-Area Mode: Dynamic-area AF (51 points) +
36 = AF-Area Mode: Group-area AF +
37 = AF-Area Mode: Auto area AF
+
+ +

NikonCustom SettingsD5 Tags

+

Custom settings for the D5.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1CustomSettingsBankint8u[val & 0x3] +
0 = A +
1 = B +
2 = C +
3 = D
1.1AF-CPrioritySelectionint8u[val >> 6 & 0x3] +
0 = Release +
1 = Release + Focus +
2 = Focus +
3 = Focus + Release
1.2AF-SPrioritySelectionint8u[val >> 5 & 0x1] +
0 = Focus +
1 = Release
1.3NumberOfFocusPointsint8u[val >> 4 & 0x1] +
0 = 55 Points +
1 = 15 Points
1.4Three-DTrackingFaceDetectionint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
1.5BlockShotAFResponseint8u[val & 0x7]
2.1FocusPointWrapint8u[val >> 3 & 0x1] +
0 = No Wrap +
1 = Wrap
2.2AFPointBrightnessint8u[val >> 1 & 0x3] +
0 = Auto +
1 = On +
2 = Off
4.1ISODisplayint8u[val >> 3 & 0x1] +
0 = Show ISO Sensitivity +
1 = Show Frame Count
4.2GridDisplayint8u[val >> 1 & 0x1] +
0 = On +
1 = Off
5.1LCDIlluminationint8u[val >> 5 & 0x1] +
0 = Off +
1 = On
5.2ElectronicFront-CurtainShutterint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
6.1ReverseIndicatorsint8u[val >> 7 & 0x1] +
0 = + 0 - +
1 = - 0 +
6.2CommandDialsReverseRotationint8u[val >> 3 & 0x3] +
0 = No +
1 = Shutter Speed & Aperture +
2 = Exposure Compensation +
3 = Exposure Compensation, Shutter Speed & Aperture
6.3EasyExposureCompensationint8u[val & 0x3] +
0 = Off +
1 = On +
2 = On (auto reset)
7.1ExposureControlStepSizeint8u[val >> 6 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
7.2ISOStepSizeint8u[val >> 4 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
7.3ExposureCompStepSizeint8u[val >> 2 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
8.1CenterWeightedAreaSizeint8u[val >> 5 & 0x7] +
0 = 8 mm +
1 = 12 mm +
2 = 15 mm +
3 = 20 mm +
4 = Average
8.2FineTuneOptMatrixMeteringint8u[val & 0xf]
9.1FineTuneOptCenterWeightedint8u[val >> 4 & 0xf]
9.2FineTuneOptSpotMeteringint8u[val & 0xf]
10.1MultiSelectorShootModeint8u[val >> 5 & 0x7] +
0 = Select Center Focus Point (Reset) +
1 = Zoom On/Off +
2 = Preset Focus Point (Pre) +
4 = Not Used (None)
10.3MultiSelectorint8u[val & 0x1] +
0 = Do Nothing +
1 = Reset Meter-off Delay
11.1ExposureDelayModeint8u[val >> 6 & 0x3] +
0 = Off +
1 = 1 s +
2 = 2 s +
3 = 3 s
11.2CLModeShootingSpeedint8u[val & 0xf]
12.1MaxContinuousReleaseint8u 
13.1AutoBracketOrderint8u[val >> 4 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
13.2AutoBracketModeMint8u[val >> 2 & 0x3] +
0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only
14.1Func1Buttonint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
19 = Grid Display +
20 = My Menu +
21 = Disable Synchronized Release +
22 = Remote Release Only +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
58 = AF-Area Mode + AF-On (Group Area AF - HL) +
59 = AF-Area Mode + AF-On (Group Area AF - VL)
15.1PreviewButtonint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
19 = Grid Display +
20 = My Menu +
21 = Disable Synchronized Release +
22 = Remote Release Only +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
58 = AF-Area Mode + AF-On (Group Area AF - HL) +
59 = AF-Area Mode + AF-On (Group Area AF - VL)
16.1AssignBktButtonint8u[val & 0x7] +
0 = Auto Bracketing +
1 = Multiple Exposure +
2 = HDR (high dynamic range) +
3 = None
18.1CommandDialsChangeMainSubint8u[val >> 5 & 0x7] +
0 = Autofocus Off, Exposure Off +
1 = Autofocus Off, Exposure On +
2 = Autofocus Off, Exposure On (Mode A) +
4 = Autofocus On, Exposure Off +
5 = Autofocus On, Exposure On +
6 = Autofocus On, Exposure On (Mode A)
+
18.2CommandDialsMenuAndPlaybackint8u[val >> 3 & 0x3] +
0 = On +
1 = Off +
2 = On (Image Review Excluded)
18.3CommandDialsApertureSettingint8u[val >> 2 & 0x1] +
0 = Sub-command Dial +
1 = Aperture Ring
18.4ReleaseButtonToUseDialint8u[val & 0x1] +
0 = No +
1 = Yes
19.1StandbyTimerint8u[val >> 4 & 0xf] + + +
0 = 4 s +
1 = 6 s +
3 = 10 s
  5 = 30 s +
6 = 1 min +
7 = 5 min
  8 = 10 min +
9 = 30 min +
10 = No Limit
+
20.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
20.2SelfTimerShotIntervalint8u[val >> 4 & 0x3] +
0 = 0.5 s +
1 = 1 s +
2 = 2 s +
3 = 3 s
20.3SelfTimerShotCountint8u[val & 0xf]
21.1ImageReviewMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 2 s +
1 = 4 s +
3 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
21.2LiveViewMonitorOffTimeint8u[val >> 2 & 0x7] + +
1 = 5 min +
2 = 10 min +
3 = 15 min
  4 = 20 min +
5 = 30 min +
6 = No Limit
+
22.1MenuMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
2 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
22.2ShootingInfoMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
2 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
23.1FlashSyncSpeedint8u[val >> 4 & 0xf] + +
2 = 1/250 s (auto FP) +
3 = 1/250 s +
5 = 1/200 s +
6 = 1/160 s
  7 = 1/125 s +
8 = 1/100 s +
9 = 1/80 s +
10 = 1/60 s
+
23.2FlashShutterSpeedint8u[val & 0xf] + +
0 = 1/60 s +
1 = 1/30 s +
2 = 1/15 s +
3 = 1/8 s +
4 = 1/4 s +
5 = 1/2 s
  6 = 1 s +
7 = 2 s +
8 = 4 s +
9 = 8 s +
10 = 15 s +
11 = 30 s
+
31.1ModelingFlashint8u[val >> 5 & 0x1] +
0 = On +
1 = Off
36.1PlaybackMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
37.1MultiSelectorLiveViewint8u[val >> 6 & 0x3] +
0 = Reset +
1 = Zoom +
3 = Not Used
38.1ShutterSpeedLockint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
38.2ApertureLockint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
38.3MovieShutterButtonint8u[val >> 4 & 0x1] +
0 = Take Photo +
1 = Record Movies
38.4FlashExposureCompAreaint8u[val >> 2 & 0x1] +
0 = Entire Frame +
1 = Background Only
38.5AutoFlashISOSensitivityint8u[val >> 1 & 0x1] +
0 = Subject and Background +
1 = Subject Only
41.1MovieFunc1Buttonint8u[val >> 4 & 0xf] +
0 = None +
2 = Power Aperture (close) +
3 = Index Marking +
4 = View Photo Shooting Info +
11 = Exposure Compensation -
41.2MoviePreviewButtonint8u[val & 0xf] +
0 = None +
1 = Power Aperture (open) +
3 = Index Marking +
4 = View Photo Shooting Info +
10 = Exposure Compensation +
42.1Func1ButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
7 = Photo Shooting Menu Bank +
8 = Exposure Delay Mode
+
43.1PreviewButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
7 = Photo Shooting Menu Bank +
8 = Exposure Delay Mode
+
45.1AssignMovieRecordButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
7 = Photo Shooting Menu Bank +
11 = Exposure Mode
46.1FineTuneOptHighlightWeightedint8u[val & 0xf]
47.1DynamicAreaAFDisplayint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
47.2AFPointIlluminationint8u[val >> 6 & 0x1] +
0 = Off +
1 = On During Manual Focusing
47.3StoreByOrientationint8u[val >> 3 & 0x3] +
0 = Off +
1 = Focus Point +
2 = Focus Point and AF-area Mode
48.1MatrixMeteringint8u[val >> 7 & 0x1] +
0 = Face Detection On +
1 = Face Detection Off
48.2LiveViewButtonOptionsint8u[val >> 4 & 0x3] +
0 = Enable +
1 = Enable (Standby Timer Active) +
2 = Disable
48.3AFModeRestrictionsint8u[val & 0x3] +
0 = No Restrictions +
1 = AF-C +
2 = AF-S
49.1LimitAFAreaModeSelectionint8u[val >> 1 & 0x3f] +
0x0 = No Restrictions +
Bit 0 = Auto-area +
Bit 1 = Group-area +
Bit 2 = 3D-tracking +
Bit 3 = Dynamic area (153 points) +
Bit 4 = Dynamic area (72 points) +
Bit 5 = Dynamic area (25 points)
+
52.1LensFocusFunctionButtonsint8u[val & 0x3f] +
3 = AE/AF Lock +
4 = AE Lock Only +
7 = AF Lock Only +
8 = AF-On +
24 = Preset Focus Point +
26 = Flash Disable/Enable +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only)
66.1VerticalMultiSelectorint8u[val & 0xff] +
0x0 = Same as Multi-Selector with Info(U/D) & Playback(R/L) +
0x8 = Same as Multi-Selector with Info(R/L) & Playback(U/D) +
0x80 = Focus Point Selection
67.1VerticalFuncButtonint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
18 = Reset Focus Point +
19 = Grid Display +
20 = My Menu +
22 = Remote Release Only +
23 = Preset Focus Point +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
54 = Highlight Active Focus Point
68.1VerticalFuncPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
7 = Photo Shooting Menu Bank +
8 = Exposure Delay Mode +
10 = ISO Sensitivity +
11 = Exposure Mode +
12 = Exposure Compensation +
13 = Metering
+
70.1AF-OnButtonint8u[val & 0x3f] +
0 = None +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF)
71.1SubSelectorint8u[val >> 7 & 0x1] +
0 = Focus Point Selection +
1 = Same as MultiSelector
72.1SubSelectorCenterint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
18 = Reset Focus Point +
19 = Grid Display +
20 = My Menu +
22 = Remote Release Only +
23 = Preset Focus Point +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
54 = Highlight Active Focus Point
73.1SubSelectorPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
4 = Choose Non-CPU Lens Number +
7 = Photo Shooting Menu Bank
74.1AssignMovieSubselectorint8u[val >> 4 & 0xf] +
0 = None +
3 = Index Marking +
4 = View Photo Shooting Info +
5 = AE/AF Lock +
6 = AE Lock (Only) +
7 = AE Lock (Hold) +
8 = AF Lock (Only)
+
75.1AssignMovieFunc1ButtonPlusDialsint8u[val >> 4 & 0x1] +
0 = None +
1 = Choose Image Area
75.2AssignMoviePreviewButtonPlusDialsint8u[val & 0x1] +
0 = None +
1 = Choose Image Area (DX/1.3x)
76.1AssignMovieSubselectorPlusDialsint8u[val >> 4 & 0x1] +
0 = None +
1 = Choose Image Area
77.1SyncReleaseModeint8u[val >> 7 & 0x1] +
0 = No Sync +
1 = Sync
78.1Three-DTrackingWatchAreaint8u[val >> 7 & 0x1] +
0 = Wide +
1 = Normal
78.2SubjectMotionint8u[val >> 5 & 0x3] +
0 = Steady +
1 = Middle +
2 = Erratic
78.3AFActivationint8u[val >> 3 & 0x1] +
0 = Shutter/AF-On +
1 = AF-On Only
78.4ShutterReleaseButtonAE-Lint8u[val & 0x3] +
0 = Off +
1 = On (Half Press) +
2 = On (Burst Mode)
79.1VerticalAFOnButtonint8u[val & 0x7f] +
0 = None +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
100 = Same as AF-On
80.1Func2Buttonint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
19 = Grid Display +
20 = My Menu +
21 = Disable Synchronized Release +
22 = Remote Release Only +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only)
81.1Func2ButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
7 = Photo Shooting Menu Bank +
8 = Exposure Delay Mode
+
82.1AssignMovieFunc2Buttonint8u[val >> 4 & 0x7] +
0 = None +
3 = Index Marking +
4 = View Photo Shooting Info
83.1Func3Buttonint8u[val & 0x3] +
0 = None +
1 = Voice Memo +
2 = Rating +
3 = Connect To Network
+ +

NikonCustom SettingsD500 Tags

+

Custom settings for the D500.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1CustomSettingsBankint8u[val & 0x3] +
0 = A +
1 = B +
2 = C +
3 = D
1.1AF-CPrioritySelectionint8u[val >> 6 & 0x3] +
0 = Release +
1 = Release + Focus +
2 = Focus +
3 = Focus + Release
1.2AF-SPrioritySelectionint8u[val >> 5 & 0x1] +
0 = Focus +
1 = Release
1.3NumberOfFocusPointsint8u[val >> 4 & 0x1] +
0 = 55 Points +
1 = 15 Points
1.4Three-DTrackingFaceDetectionint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
1.5BlockShotAFResponseint8u[val & 0x7]
2.1FocusPointWrapint8u[val >> 3 & 0x1] +
0 = No Wrap +
1 = Wrap
2.2AFPointBrightnessint8u[val >> 1 & 0x3] +
0 = Auto +
1 = On +
2 = Off
4.1ISODisplayint8u[val >> 3 & 0x1] +
0 = Show ISO Sensitivity +
1 = Show Frame Count
4.2GridDisplayint8u[val >> 1 & 0x1] +
0 = On +
1 = Off
5.1LCDIlluminationint8u[val >> 5 & 0x1] +
0 = Off +
1 = On
5.2ElectronicFront-CurtainShutterint8u[val >> 3 & 0x1] +
0 = Off +
1 = On
6.1ReverseIndicatorsint8u[val >> 7 & 0x1] +
0 = + 0 - +
1 = - 0 +
6.2CommandDialsReverseRotationint8u[val >> 3 & 0x3] +
0 = No +
1 = Shutter Speed & Aperture +
2 = Exposure Compensation +
3 = Exposure Compensation, Shutter Speed & Aperture
6.3EasyExposureCompensationint8u[val & 0x3] +
0 = Off +
1 = On +
2 = On (auto reset)
7.1ExposureControlStepSizeint8u[val >> 6 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
7.2ISOStepSizeint8u[val >> 4 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
7.3ExposureCompStepSizeint8u[val >> 2 & 0x3] +
0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
8.1CenterWeightedAreaSizeint8u[val >> 5 & 0x7] +
0 = 6 mm +
1 = 8 mm +
2 = 10 mm +
3 = 13 mm +
4 = Average
8.2FineTuneOptMatrixMeteringint8u[val & 0xf]
9.1FineTuneOptCenterWeightedint8u[val >> 4 & 0xf]
9.2FineTuneOptSpotMeteringint8u[val & 0xf]
10.1MultiSelectorShootModeint8u[val >> 5 & 0x7] +
0 = Select Center Focus Point (Reset) +
2 = Preset Focus Point (Pre) +
3 = Highlight Active Focus Point +
4 = Not Used (None)
10.2MultiSelectorPlaybackModeint8u[val >> 2 & 0x3] +
0 = Thumbnail On/Off +
1 = View Histograms +
2 = Zoom On/Off +
3 = Choose Folder
10.3MultiSelectorint8u[val & 0x1] +
0 = Do Nothing +
1 = Reset Meter-off Delay
11.1ExposureDelayModeint8u[val >> 6 & 0x3] +
0 = Off +
1 = 1 s +
2 = 2 s +
3 = 3 s
11.2CLModeShootingSpeedint8u[val & 0xf]
12.1MaxContinuousReleaseint8u 
13.1AutoBracketOrderint8u[val >> 4 & 0x1] +
0 = 0,-,+ +
1 = -,0,+
13.2AutoBracketModeMint8u[val >> 2 & 0x3] +
0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only
14.1Func1Buttonint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
19 = Grid Display +
20 = My Menu +
22 = Remote Release Only +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only)
15.1PreviewButtonint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
19 = Grid Display +
20 = My Menu +
22 = Remote Release Only +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only)
16.1AssignBktButtonint8u[val & 0x7] +
0 = Auto Bracketing +
1 = Multiple Exposure +
2 = HDR (high dynamic range) +
3 = None
18.1CommandDialsChangeMainSubint8u[val >> 5 & 0x7] +
0 = Autofocus Off, Exposure Off +
1 = Autofocus Off, Exposure On +
2 = Autofocus Off, Exposure On (Mode A) +
4 = Autofocus On, Exposure Off +
5 = Autofocus On, Exposure On +
6 = Autofocus On, Exposure On (Mode A)
+
18.2CommandDialsMenuAndPlaybackint8u[val >> 3 & 0x3] +
0 = On +
1 = Off +
2 = On (Image Review Excluded)
18.3CommandDialsApertureSettingint8u[val >> 2 & 0x1] +
0 = Sub-command Dial +
1 = Aperture Ring
18.4ReleaseButtonToUseDialint8u[val & 0x1] +
0 = No +
1 = Yes
19.1StandbyTimerint8u[val >> 4 & 0xf] + + +
0 = 4 s +
1 = 6 s +
3 = 10 s
  5 = 30 s +
6 = 1 min +
7 = 5 min
  8 = 10 min +
9 = 30 min +
10 = No Limit
+
20.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
20.2SelfTimerShotIntervalint8u[val >> 4 & 0x3] +
0 = 0.5 s +
1 = 1 s +
2 = 2 s +
3 = 3 s
20.3SelfTimerShotCountint8u[val & 0xf]
21.1ImageReviewMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 2 s +
1 = 4 s +
3 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
21.2LiveViewMonitorOffTimeint8u[val >> 2 & 0x7] + +
1 = 5 min +
2 = 10 min +
3 = 15 min
  4 = 20 min +
5 = 30 min +
6 = No Limit
+
22.1MenuMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
2 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
22.2ShootingInfoMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
2 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
23.1FlashSyncSpeedint8u[val >> 4 & 0xf] + +
2 = 1/250 s (auto FP) +
3 = 1/250 s +
5 = 1/200 s +
6 = 1/160 s
  7 = 1/125 s +
8 = 1/100 s +
9 = 1/80 s +
10 = 1/60 s
+
23.2FlashShutterSpeedint8u[val & 0xf] + +
0 = 1/60 s +
1 = 1/30 s +
2 = 1/15 s +
3 = 1/8 s +
4 = 1/4 s +
5 = 1/2 s
  6 = 1 s +
7 = 2 s +
8 = 4 s +
9 = 8 s +
10 = 15 s +
11 = 30 s
+
31.1ModelingFlashint8u[val >> 5 & 0x1] +
0 = On +
1 = Off
36.1PlaybackMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
37.1MultiSelectorLiveViewint8u[val >> 6 & 0x3] +
0 = Reset +
1 = Zoom +
3 = Not Used
38.1ShutterSpeedLockint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
38.2ApertureLockint8u[val >> 6 & 0x1] +
0 = Off +
1 = On
38.3MovieShutterButtonint8u[val >> 4 & 0x1] +
0 = Take Photo +
1 = Record Movies
38.4FlashExposureCompAreaint8u[val >> 2 & 0x1] +
0 = Entire Frame +
1 = Background Only
38.5AutoFlashISOSensitivityint8u[val >> 1 & 0x1] +
0 = Subject and Background +
1 = Subject Only
41.1MovieFunc1Buttonint8u[val >> 4 & 0xf] +
0 = None +
2 = Power Aperture (close) +
3 = Index Marking +
4 = View Photo Shooting Info +
11 = Exposure Compensation -
41.2MoviePreviewButtonint8u[val & 0xf] +
0 = None +
1 = Power Aperture (open) +
3 = Index Marking +
4 = View Photo Shooting Info +
10 = Exposure Compensation +
42.1Func1ButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area (DX/1.3x) +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
7 = Photo Shooting Menu Bank +
8 = Exposure Delay Mode
+
43.1PreviewButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area (DX/1.3x) +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
7 = Photo Shooting Menu Bank +
8 = Exposure Delay Mode
+
45.1AssignMovieRecordButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area (DX/1.3x) +
2 = Shutter Speed & Aperture Lock +
7 = Photo Shooting Menu Bank +
11 = Exposure Mode
46.1FineTuneOptHighlightWeightedint8u[val & 0xf]
47.1DynamicAreaAFDisplayint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
47.2AFPointIlluminationint8u[val >> 6 & 0x1] +
0 = Off +
1 = On During Manual Focusing
47.3StoreByOrientationint8u[val >> 3 & 0x3] +
0 = Off +
1 = Focus Point +
2 = Focus Point and AF-area Mode
47.4GroupAreaAFIlluminationint8u[val >> 2 & 0x1] +
0 = Squares +
1 = Dots
48.1MatrixMeteringint8u[val >> 7 & 0x1] +
0 = Face Detection On +
1 = Face Detection Off
48.2LiveViewButtonOptionsint8u[val >> 4 & 0x3] +
0 = Enable +
1 = Enable (Standby Timer Active) +
2 = Disable
48.3AFModeRestrictionsint8u[val & 0x3] +
0 = No Restrictions +
1 = AF-C +
2 = AF-S
49.1LimitAFAreaModeSelectionint8u[val >> 1 & 0x3f] +
0x0 = No Restrictions +
Bit 0 = Auto-area +
Bit 1 = Group-area +
Bit 2 = 3D-tracking +
Bit 3 = Dynamic area (153 points) +
Bit 4 = Dynamic area (72 points) +
Bit 5 = Dynamic area (25 points)
+
52.1LensFocusFunctionButtonsint8u[val & 0x3f] +
3 = AE/AF Lock +
4 = AE Lock Only +
7 = AF Lock Only +
8 = AF-On +
24 = Preset Focus Point +
26 = Flash Disable/Enable +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only)
66.1VerticalMultiSelectorint8u[val & 0xff] +
0x0 = Same as Multi-Selector with Info(U/D) & Playback(R/L) +
0x8 = Same as Multi-Selector with Info(R/L) & Playback(U/D) +
0x80 = Focus Point Selection
67.1AssignMB-D17FuncButtonint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
18 = Reset Focus Point +
19 = Grid Display +
20 = My Menu +
22 = Remote Release Only +
23 = Preset Focus Point +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
54 = Highlight Active Focus Point
68.1AssignMB-D17FuncButtonPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area (DX/1.3x) +
2 = Shutter Speed & Aperture Lock +
3 = One Step Speed / Aperture +
4 = Choose Non-CPU Lens Number +
5 = Active D-Lighting +
7 = Photo Shooting Menu Bank +
8 = Exposure Delay Mode +
10 = ISO Sensitivity +
11 = Exposure Mode +
12 = Exposure Compensation +
13 = Metering Mode
+
70.1AF-OnButtonint8u[val & 0x3f] +
0 = None +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF)
71.1SubSelectorint8u[val >> 7 & 0x1] +
0 = Focus Point Selection +
1 = Same as MultiSelector
72.1SubSelectorCenterint8u[val & 0x3f] +
0 = None +
1 = Preview +
2 = FV Lock +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
10 = Bracketing Burst +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Playback +
15 = My Menu Top Item +
16 = +NEF(RAW) +
17 = Virtual Horizon +
18 = Reset Focus Point +
19 = Grid Display +
20 = My Menu +
22 = Remote Release Only +
23 = Preset Focus Point +
26 = Flash Disable/Enable +
27 = Highlight-weighted Metering +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
49 = Sync Release (Master Only) +
50 = Sync Release (Remote Only) +
54 = Highlight Active Focus Point
73.1SubSelectorPlusDialsint8u[val & 0xf] +
0 = None +
1 = Choose Image Area (DX/1.3x) +
2 = Shutter Speed & Aperture Lock +
4 = Choose Non-CPU Lens Number +
7 = Photo Shooting Menu Bank
74.1AssignMovieSubselectorint8u[val >> 4 & 0xf] +
0 = None +
3 = Index Marking +
4 = View Photo Shooting Info +
5 = AE/AF Lock +
6 = AE Lock (Only) +
7 = AE Lock (Hold) +
8 = AF Lock (Only)
+
75.1AssignMovieFunc1ButtonPlusDialsint8u[val >> 4 & 0x1] +
0 = None +
1 = Choose Image Area (DX/1.3x)
75.2AssignMoviePreviewButtonPlusDialsint8u[val & 0x1] +
0 = None +
1 = Choose Image Area (DX/1.3x)
76.1AssignMovieSubselectorPlusDialsint8u[val >> 4 & 0x1] +
0 = None +
1 = Choose Image Area (DX/1.3x)
77.1SyncReleaseModeint8u[val >> 7 & 0x1] +
0 = No Sync +
1 = Sync
78.1Three-DTrackingWatchAreaint8u[val >> 7 & 0x1] +
0 = Wide +
1 = Normal
78.2SubjectMotionint8u[val >> 5 & 0x3] +
0 = Steady +
1 = Middle +
2 = Erratic
78.3AFActivationint8u[val >> 3 & 0x1] +
0 = Shutter/AF-On +
1 = AF-On Only
78.4ShutterReleaseButtonAE-Lint8u[val & 0x3] +
0 = Off +
1 = On (Half Press) +
2 = On (Burst Mode)
79.1AssignMB-D17AF-OnButtonint8u[val & 0x7f] +
0 = None +
3 = AE/AF Lock +
4 = AE Lock Only +
5 = AE Lock (reset on release) +
6 = AE Lock (hold) +
7 = AF Lock Only +
8 = AF-On +
36 = AF-Area Mode (Single) +
37 = AF-Area Mode (Dynamic Area 25 Points) +
38 = AF-Area Mode (Dynamic Area 72 Points) +
39 = AF-Area Mode (Dynamic Area 152 Points) +
40 = AF-Area Mode (Group Area AF) +
41 = AF-Area Mode (Auto Area AF) +
42 = AF-Area Mode + AF-On (Single) +
43 = AF-Area Mode + AF-On (Dynamic Area 25 Points) +
44 = AF-Area Mode + AF-On (Dynamic Area 72 Points) +
45 = AF-Area Mode + AF-On (Dynamic Area 152 Points) +
46 = AF-Area Mode + AF-On (Group Area AF) +
47 = AF-Area Mode + AF-On (Auto Area AF) +
100 = Same as Camera AF-On Button
80.1Func2Buttonint8u[val & 0x3f] +
0 = None +
15 = My Menu Top Item +
20 = My Menu +
55 = Rating
82.1AssignMovieFunc2Buttonint8u[val >> 4 & 0x7] +
0 = None +
3 = Index Marking +
4 = View Photo Shooting Info
+ +

NikonCustom SettingsD610 Tags

+

Custom settings for the D610.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1AF-CPrioritySelectionint8u[val >> 7 & 0x1] +
0 = Release +
1 = Focus
0.2AF-SPrioritySelectionint8u[val >> 5 & 0x1] +
0 = Release +
1 = Focus
0.3NumberOfFocusPointsint8u[val >> 4 & 0x1] +
0 = 39 Points +
1 = 11 Points
0.4FocusTrackingLockOnint8u[val & 0x7] + +
0 = Off +
1 = 1 Short +
2 = 2
  3 = 3 Normal +
4 = 4 +
5 = 5 Long
+
1.1FocusPointWrapint8u[val >> 3 & 0x1] +
0 = No Wrap +
1 = Wrap
1.2AFPointIlluminationint8u[val >> 1 & 0x3] +
0 = Auto +
1 = On +
2 = Off
1.3AFAssistint8u[val & 0x1] +
0 = On +
1 = Off
5.1EasyExposureCompensationint8u[val & 0x3] +
0 = Off +
1 = On +
2 = On Auto Reset
6.1ExposureControlStepint8u[val >> 6 & 0x1] +
0 = 1/3 EV +
1 = 1/2 EV
6.2ISOSensitivityStepint8u[val >> 4 & 0x1] +
0 = 1/3 EV +
1 = 1/2 EV
7.1CenterWeightedAreaSizeint8u[val >> 5 & 0x7] +
0 = 8 mm +
1 = 12 mm +
2 = 15 mm +
3 = 20 mm +
4 = Average
7.2FineTuneOptMatrixMeteringint8u[val & 0xf]
8.1FineTuneOptCenterWeightedint8u[val >> 4 & 0xf]
8.2FineTuneOptSpotMeteringint8u[val & 0xf]
17.1ShutterReleaseButtonAE-Lint8u[val >> 1 & 0x1] +
0 = Off +
1 = On
18.1StandbyTimerint8u[val >> 4 & 0xf] + + +
0 = 4 s +
1 = 6 s +
2 = 10 s
  3 = 30 s +
4 = 1 min +
5 = 5 min
  6 = 10 min +
7 = 30 min +
8 = No Limit
+
18.2RemoteOnDurationint8u[val & 0x3] +
0 = 1 min +
1 = 5 min +
2 = 10 min +
3 = 20 min
19.1SelfTimerTimeint8u[val >> 6 & 0x3] +
0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
19.2SelfTimerShotIntervalint8u[val >> 4 & 0x3] +
0 = 0.5 s +
1 = 1 s +
2 = 2 s +
3 = 3 s
19.3SelfTimerShotCountint8u[val & 0xf]
20.1ImageReviewMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 2 s +
1 = 4 s +
2 = 10 s +
3 = 20 s
  4 = 1 min +
5 = 5 min +
6 = 10 min
+
20.2LiveViewMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 5 min +
1 = 10 min +
2 = 15 min
  3 = 20 min +
4 = 30 min +
5 = No Limit
+
21.1MenuMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
21.2ShootingInfoMonitorOffTimeint8u[val >> 2 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
35.1PlaybackMonitorOffTimeint8u[val >> 5 & 0x7] + +
0 = 4 s +
1 = 10 s +
2 = 20 s
  3 = 1 min +
4 = 5 min +
5 = 10 min
+
+ +

NikonCustom SettingsZ8 Tags

+

Custom settings for the Z8.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1CustomSettingsBankint8u0 = A +
1 = B +
2 = C +
3 = D
3AF-CPrioritySelectionint8u0 = Release +
1 = Release + Focus +
3 = Focus
5AF-SPrioritySelectionint8u0 = Release +
1 = Focus
7BlockShotAFResponseint8u1 = 1 (Quick) +
2 = 2 +
3 = 3 (Normal) +
4 = 4 +
5 = 5 (Delayed)
11AFPointSelint8u0 = Use All +
1 = Use Half
13StoreByOrientationint8u0 = Off +
1 = Focus Point +
2 = Focus Point and AF-area mode
15AFActivationint8u0 = AF-On Only +
1 = Shutter/AF-On
16AF-OnOutOfFocusRelease?int8u0 = Disable +
1 = Enable
17LimitAF-AreaModeSelPinpoint?int8u0 = Limit +
1 = No Limit
19LimitAF-AreaModeSelWideAF_S?int8u0 = Limit +
1 = No Limit
20LimitAF-AreaModeSelWideAF_L?int8u0 = Limit +
1 = No Limit
21LimitAFAreaModeSelAuto?int8u0 = Limit +
1 = No Limit
22FocusPointWrap?int8u0 = No Wrap +
1 = Wrap
23ManualFocusPointIllumination?int8u0 = On During Focus Point Selection Only +
1 = On
24DynamicAreaAFAssist?int8u0 = Focus Point Only +
1 = Focus and Surrounding Points
25AF-AssistIlluminatorint8u0 = Off +
1 = On
26ManualFocusRingInAFModeint8u0 = Off +
1 = On
27ExposureControlStepSizeint8u0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
29EasyExposureCompensationint8u0 = Off +
1 = On +
2 = On (auto reset)
31CenterWeightedAreaSizeint8u0 = 8 mm +
1 = 12 mm +
4 = Average
33FineTuneOptMatrixMeteringint8s 
35FineTuneOptCenterWeightedint8s 
37FineTuneOptSpotMeteringint8s 
39FineTuneOptHighlightWeightedint8s 
41ShutterReleaseButtonAE-Lint8u0 = Off +
1 = On (Half Press) +
2 = On (Burst Mode)
43SelfTimerTimeint8u0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
45SelfTimerShotCountint8u 
49SelfTimerShotIntervalint8u0 = 0.5 s +
1 = 1 s +
2 = 2 s +
3 = 3 s
51PlaybackMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
53MenuMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
55ShootingInfoMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
57ImageReviewMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
59CLModeShootingSpeedint8u 
61MaxContinuousReleaseno 
65SyncReleaseMode?int8u0 = No Sync +
1 = Sync
69LimitSelectableImageAreaDX?int8u0 = Limit +
1 = No Limit
70LimitSelectableImageArea1To1?int8u0 = Limit +
1 = No Limit
71LimitSelectableImageArea16To9?int8u0 = Limit +
1 = No Limit
72FileNumberSequenceint8u0 = Off +
1 = On
73FocusPeakingLevel?int8u0 = High Sensitivity +
1 = Standard Sensitivity +
2 = Low Sensitivity
75FocusPeakingHighlightColor?int8u0 = Red +
1 = Yellow +
2 = Blue +
3 = White
81ContinuousModeDisplayint8u0 = Off +
1 = On
83FlashSyncSpeedno + +
0 = 1/60 s +
1 = 1/80 s +
2 = 1/100 s +
3 = 1/125 s
  4 = 1/160 s +
5 = 1/200 s +
6 = 1/250 s
+
85HighSpeedSyncint8u0 = Off +
1 = On
87FlashShutterSpeedno + + + +
0 = 1 s +
1 = 1/2 s +
2 = 1/4 s
  3 = 1/8 s +
4 = 1/15 s +
5 = 1/30 s
  6 = 1/60 s +
7 = 30 s +
8 = 15 s
  9 = 8 s +
10 = 4 s +
11 = 2 s
+
89FlashExposureCompAreaint8u0 = Entire Frame +
1 = Background Only
91AutoFlashISOSensitivityint8u0 = Subject and Background +
1 = Subject Only
93ModelingFlashint8u0 = Off +
1 = On
95AutoBracketModeMint8u0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only +
4 = Flash/ISO
97AutoBracketOrderint8u0 = 0,-,+ +
1 = -,0,+
99Func1Buttonint8u--> NikonCustom ButtonsZ9 Values
115Func2Buttonint8u--> NikonCustom ButtonsZ9 Values
131AFOnButtonint8u--> NikonCustom ButtonsZ9 Values
143SubSelector?int8u--> NikonCustom ButtonsZ9 Values
155AssignMovieRecordButton?int8u--> NikonCustom ButtonsZ9 Values
159LensFunc1Buttonint8u--> NikonCustom ButtonsZ9 Values
167LensFunc2Buttonint8u--> NikonCustom ButtonsZ9 Values
173LensControlRingint8u0 = None (Disabled) +
1 = Focus (M/A) +
2 = ISO Sensitivity +
3 = Exposure Compensation +
4 = Aperture
175MultiSelectorShootModeint8u--> NikonCustom ButtonsZ9 Values
179MultiSelectorPlaybackModeint8u--> NikonCustom ButtonsZ9 Values
183ShutterSpeedLockint8u0 = Off +
1 = On
184ApertureLockint8u0 = Off +
1 = On
186CmdDialsReverseRotationint8u0 = No +
1 = Shutter Speed & Aperture +
0 = Exposure Compensation +
1 = Exposure Compensation, Shutter Speed & Aperture
191UseDialWithoutHold?int8u0 = Off +
1 = On
193ReverseIndicators?int8u0 = + 0 - +
1 = - 0 +
195MovieFunc1Buttonint8u--> NikonCustom ButtonsZ9 Values
199MovieFunc2Buttonint8u--> NikonCustom ButtonsZ9 Values
203MovieAF-OnButtonint8u--> NikonCustom ButtonsZ9 Values
215MovieLensControlRingint8u0 = None (Disabled) +
2 = ISO Sensitivity +
3 = Exposure Compensation +
4 = Power Aperture +
5 = Hi-Res Zoom
217MovieMultiSelector?int8u--> NikonCustom ButtonsZ9 Values
221MovieAFSpeedint8u 
223MovieAFSpeedApplyint8u0 = Always +
1 = Only During Recording
225MovieAFTrackingSensitivityint8u + +
0 = 1 (High) +
1 = 2 +
2 = 3 +
3 = 4 (Normal)
  4 = 5 +
5 = 6 +
6 = 7 (Low)
+
257LCDIllumination?int8u0 = Off +
1 = On
258ExtendedShutterSpeedsint8u0 = Off +
1 = On
259SubjectMotionint8u0 = Erratic +
1 = Steady
261FocusPointPersistenceint8u0 = Auto +
1 = Off
263AutoFocusModeRestrictions?int8u0 = AF-S +
1 = AF-C +
2 = Full-time AF +
3 = Manual +
4 = No Restrictions
267CHModeShootingSpeedint8u 
273FlashBurstPriority?int8u0 = Frame Rate +
1 = Exposure
335LimitAF-AreaModeSelDynamic_S?int8u0 = Limit +
1 = No Limit
336LimitAF-AreaModeSelDynamic_M?int8u0 = Limit +
1 = No Limit
337LimitAF-AreaModeSelDynamic_L?int8u0 = Limit +
1 = No Limit
339LimitAF-AreaModeSel3DTracking?int8u0 = Limit +
1 = No Limit
341PlaybackFlickUp?int8u +
0 = Rating +
1 = Select To Send (PC) +
2 = Select To Send (FTP) +
3 = Protect +
4 = Voice Memo +
5 = None
+
345PlaybackFlickDown?int8u +
0 = Rating +
1 = Select To Send (PC) +
2 = Select To Send (FTP) +
3 = Protect +
4 = Voice Memo +
5 = None
+
349ISOStepSizeint8u0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
355ReverseFocusRingint8u0 = Not Reversed +
1 = Reversed
356EVFImageFrame?int8u0 = Off +
1 = On
357EVFGrid?int8u + + +
0 = 3x3 +
1 = 4x4 +
2 = 2.35:1
  3 = 1.85:1 +
4 = 5:4 +
5 = 4:3
  6 = 1:1 +
7 = 16:9 +
8 = 90%
+
359VirtualHorizonStyle?int8u0 = Type A (Cockpit) +
1 = Type B (Sides)
421Func1ButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
423Func2ButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
437MovieRecordButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
459CommandDialPlaybackMode?int8u +
0 = 1 Frame +
1 = 10 Frames +
2 = 50 Frames +
3 = Folder +
4 = Protect +
5 = Photos Only +
6 = Videos Only +
7 = Rating +
8 = Page +
9 = Skip To First Shot In Series
+
463SubCommandDialPlaybackMode?int8u +
0 = 1 Frame +
1 = 10 Frames +
2 = 50 Frames +
3 = Folder +
4 = Protect +
5 = Photos Only +
6 = Videos Only +
7 = Rating +
8 = Page +
9 = Skip To First Shot In Series
+
467FocusPointLock?int8u0 = Off +
1 = On
469ControlRingResponseint8u0 = High +
1 = Low
515MovieAFAreaMode?int8u--> NikonCustom ButtonsZ9 Values
529ZebraPatternToneRange?int8u0 = Off +
1 = Highlights +
2 = Midtones
531MovieZebraPattern?int8u0 = Pattern 1 +
1 = Pattern 2
533MovieHighlightDisplayThreshold?int8u 
535MovieMidtoneDisplayValue?int8u 
537MovieMidtoneDisplayRange?int8u~ 
541MovieEVFGrid?int8u + + +
0 = 3x3 +
1 = 4x4 +
2 = 2.35:1
  3 = 1.85:1 +
4 = 5:4 +
5 = 4:3
  6 = 1:1 +
7 = 16:9 +
8 = 90%
+
549MovieShutterSpeedLock?int8u0 = Off +
1 = On
550MovieFocusPointLock?int8u0 = Off +
1 = On
563MatrixMetering?int8u0 = Face Detection Off +
1 = Face Detection On
564AF-CFocusDisplayint8u0 = Off +
1 = On
565FocusPeakingDisplay?int8u0 = Off +
1 = On
567KeepExposureint8u0 = Off +
1 = Shutter Speed +
2 = ISO
585StarlightView?int8u0 = Off +
1 = On
587EVFWarmDisplayMode?int8u0 = Off +
1 = Mode 1 +
2 = Mode 2
589EVFWarmDisplayBrightness?int8s 
591EVFReleaseIndicator?int8u0 = Off +
1 = Type A (Dark) +
2 = Type B (Border) +
3 = Type C (Sides)
601MovieApertureLock?int8u0 = Off +
1 = On
607FlickAdvanceDirection?int8u0 = Left to Right +
1 = Right to Left
647PreReleaseBurstLengthint8u0 = None +
1 = 0.3 Sec +
2 = 0.5 Sec +
3 = 1 Sec
649PostReleaseBurstLengthint8u0 = 1 Sec +
1 = 2 Sec +
2 = 3 Sec +
3 = Max
681ViewModeShowEffectsOfSettings?int8u0 = Always +
1 = Only When Flash Not Used
683DispButtonint8u--> NikonCustom ButtonsZ9 Values
+ +

NikonCustom DelaysZ9 Values

+
+
+ + + + + + + + + + + + + + +
ValueDelaysZ9ValueDelaysZ9ValueDelaysZ9
0= 2 s5= 30 s11= 30 min
1= 4 s6= 1 min12= No Limit
3= 10 s7= 5 min  
4= 20 s8= 10 min  
+ +

NikonCustom ButtonsZ9 Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueButtonsZ9ValueButtonsZ9
0= None49= Power Aperture (Close)
1= Preview52= Microphone Sensitivity
3= FV Lock53= Release Mode
4= AE/AF Lock57= Preset Focus Point
5= AE Lock Only58= AE/AWB Lock (hold)
6= AE Lock (reset on release)59= AF-AreaMode
7= AE Lock (hold)60= AF-AreaMode + AF-On
8= AF Lock Only61= Recall Shooting Functions
9= AF-On64= Filtered Playback
10= Flash Disable/Enable65= Same as AF-On
11= Bracketing Burst66= Voice Memo
12= +NEF(RAW)70= Photo Shooting Bank
18= Virtual Horizon71= ISO
19= Synchronized Release73= Exposure Compensation
20= My Menu76= Silent Mode
21= My Menu Top Item78= LiveView Information
22= Playback79= AWB Lock (hold)
23= Rating80= Grid Display
24= Protect81= Starlight View
25= Zoom82= Select To Send (PC)
26= Focus Peaking83= Select To Send (FTP)
27= Flash Mode/Compensation84= Pattern Tone Range
28= Image Area85= Control Lock
30= Non-CPU Lens86= Save Focus Position
31= Active-D Lighting87= Recall Focus Position
32= Exposure Delay Mode88= Recall Shooting Functions (Hold)
33= 1 Stop Speed/Aperture97= High Frequency Flicker Reduction
34= White Balance98= Switch FX/DX
35= Metering99= View Mode (Photo LV)
36= Auto Bracketing100= Photo Flicker Reduction
37= Multiple Exposure101= Filtered Playback (Select Criteria)
38= HDR Overlay103= Start Series Playback
39= Picture Control104= View Assist
40= Quality105= Hi-Res Zoom+
41= Focus Mode/AF AreaMode106= Hi-Res Zoom-
42= Select Center Focus Point108= Override Other Cameras
44= Record Movie109= DISP - Cycle Information Display (shooting)
45= Thumbnail On/Off110= DISP - Cycle Information Display (playback)
46= View Histograms111= Resume Shooting
47= Choose Folder112= Switch Eyes
48= Power Aperture (Open)115= Delete
+ +

NikonCustom SettingsZ9 Tags

+

Custom settings for the Z9.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1CustomSettingsBankint8u0 = A +
1 = B +
2 = C +
3 = D
3AF-CPrioritySelectionint8u0 = Release +
1 = Release + Focus +
3 = Focus
5AF-SPrioritySelectionint8u0 = Release +
1 = Focus
7BlockShotAFResponseint8u1 = 1 (Quick) +
2 = 2 +
3 = 3 (Normal) +
4 = 4 +
5 = 5 (Delayed)
11AFPointSelint8u0 = Use All +
1 = Use Half
13StoreByOrientationint8u0 = Off +
1 = Focus Point +
2 = Focus Point and AF-area mode
15AFActivationint8u0 = AF-On Only +
1 = Shutter/AF-On
16AF-OnOutOfFocusRelease?int8u0 = Disable +
1 = Enable
17LimitAF-AreaModeSelPinpoint?int8u0 = Limit +
1 = No Limit
19LimitAF-AreaModeSelWideAF_S?int8u0 = Limit +
1 = No Limit
20LimitAF-AreaModeSelWideAF_L?int8u0 = Limit +
1 = No Limit
21LimitAFAreaModeSelAuto?int8u0 = Limit +
1 = No Limit
22FocusPointWrap?int8u0 = No Wrap +
1 = Wrap
23ManualFocusPointIllumination?int8u0 = On During Focus Point Selection Only +
1 = On
24DynamicAreaAFAssist?int8u0 = Focus Point Only +
1 = Focus and Surrounding Points
25AF-AssistIlluminatorint8u0 = Off +
1 = On
26ManualFocusRingInAFModeint8u0 = Off +
1 = On
27ExposureControlStepSizeint8u0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
29EasyExposureCompensationint8u0 = Off +
1 = On +
2 = On (auto reset)
31CenterWeightedAreaSizeint8u0 = 8 mm +
1 = 12 mm +
4 = Average
33FineTuneOptMatrixMeteringint8s 
35FineTuneOptCenterWeightedint8s 
37FineTuneOptSpotMeteringint8s 
39FineTuneOptHighlightWeightedint8s 
41ShutterReleaseButtonAE-Lint8u0 = Off +
1 = On (Half Press) +
2 = On (Burst Mode)
43SelfTimerTimeint8u0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
45SelfTimerShotCountint8u 
49SelfTimerShotIntervalint8u0 = 0.5 s +
1 = 1 s +
2 = 2 s +
3 = 3 s
51PlaybackMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
53MenuMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
55ShootingInfoMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
57ImageReviewMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
59CLModeShootingSpeedint8u 
61MaxContinuousReleaseno 
65SyncReleaseMode?int8u0 = No Sync +
1 = Sync
69LimitSelectableImageAreaDX?int8u0 = Limit +
1 = No Limit
70LimitSelectableImageArea1To1?int8u0 = Limit +
1 = No Limit
71LimitSelectableImageArea16To9?int8u0 = Limit +
1 = No Limit
72FileNumberSequenceint8u0 = Off +
1 = On
73FocusPeakingLevel?int8u0 = High Sensitivity +
1 = Standard Sensitivity +
2 = Low Sensitivity
75FocusPeakingHighlightColor?int8u0 = Red +
1 = Yellow +
2 = Blue +
3 = White
81ContinuousModeDisplayint8u0 = Off +
1 = On
83FlashSyncSpeedno + +
0 = 1/60 s +
1 = 1/80 s +
2 = 1/100 s +
3 = 1/125 s
  4 = 1/160 s +
5 = 1/200 s +
6 = 1/250 s
+
85HighSpeedSyncint8u0 = Off +
1 = On
87FlashShutterSpeedno + + + +
0 = 1 s +
1 = 1/2 s +
2 = 1/4 s
  3 = 1/8 s +
4 = 1/15 s +
5 = 1/30 s
  6 = 1/60 s +
7 = 30 s +
8 = 15 s
  9 = 8 s +
10 = 4 s +
11 = 2 s
+
89FlashExposureCompAreaint8u0 = Entire Frame +
1 = Background Only
91AutoFlashISOSensitivityint8u0 = Subject and Background +
1 = Subject Only
93ModelingFlashint8u0 = Off +
1 = On
95AutoBracketModeMint8u0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only +
4 = Flash/ISO
97AutoBracketOrderint8u0 = 0,-,+ +
1 = -,0,+
99Func1Buttonint8u--> NikonCustom ButtonsZ9 Values
115Func2Buttonint8u--> NikonCustom ButtonsZ9 Values
131AFOnButtonint8u--> NikonCustom ButtonsZ9 Values
143SubSelector?int8u--> NikonCustom ButtonsZ9 Values
155AssignMovieRecordButton?int8u--> NikonCustom ButtonsZ9 Values
159LensFunc1Buttonint8u--> NikonCustom ButtonsZ9 Values
167LensFunc2Buttonint8u--> NikonCustom ButtonsZ9 Values
173LensControlRingint8u0 = None (Disabled) +
1 = Focus (M/A) +
2 = ISO Sensitivity +
3 = Exposure Compensation +
4 = Aperture
175MultiSelectorShootModeint8u--> NikonCustom ButtonsZ9 Values
179MultiSelectorPlaybackModeint8u--> NikonCustom ButtonsZ9 Values
183ShutterSpeedLockint8u0 = Off +
1 = On
184ApertureLockint8u0 = Off +
1 = On
186CmdDialsReverseRotationint8u0 = No +
1 = Shutter Speed & Aperture +
0 = Exposure Compensation +
1 = Exposure Compensation, Shutter Speed & Aperture
191UseDialWithoutHold?int8u0 = Off +
1 = On
193ReverseIndicators?int8u0 = + 0 - +
1 = - 0 +
195MovieFunc1Buttonint8u--> NikonCustom ButtonsZ9 Values
199MovieFunc2Buttonint8u--> NikonCustom ButtonsZ9 Values
203MovieAF-OnButtonint8u--> NikonCustom ButtonsZ9 Values
207MovieMultiSelector?int8u--> NikonCustom ButtonsZ9 Values
215MovieLensControlRingint8u0 = None (Disabled) +
2 = ISO Sensitivity +
3 = Exposure Compensation +
4 = Power Aperture +
5 = Hi-Res Zoom
221MovieAFSpeedint8u 
223MovieAFSpeedApplyint8u0 = Always +
1 = Only During Recording
225MovieAFTrackingSensitivityint8u + +
0 = 1 (High) +
1 = 2 +
2 = 3 +
3 = 4 (Normal)
  4 = 5 +
5 = 6 +
6 = 7 (Low)
+
257LCDIllumination?int8u0 = Off +
1 = On
258ExtendedShutterSpeedsint8u0 = Off +
1 = On
259SubjectMotionint8u0 = Erratic +
1 = Steady
261FocusPointPersistenceint8u0 = Auto +
1 = Off
263AutoFocusModeRestrictions?int8u0 = AF-S +
1 = AF-C +
2 = Full-time AF +
3 = Manual +
4 = No Restrictions
267CHModeShootingSpeedint8u 
269.1LimitReleaseModeSelCL?int8u[val >> 1 & 0x1] +
0 = Limit +
1 = No Limit
269.2LimitReleaseModeSelCH?int8u[val >> 2 & 0x1] +
0 = Limit +
1 = No Limit
269.3LimitReleaseModeSelC30?int8u[val >> 4 & 0x1] +
0 = Limit +
1 = No Limit
269.4LimitReleaseModeSelC120?int8u[val >> 6 & 0x1] +
0 = Limit +
1 = No Limit
269.5LimitReleaseModeSelSelf?int8u[val >> 7 & 0x1] +
0 = Limit +
1 = No Limit
273FlashBurstPriority?int8u0 = Frame Rate +
1 = Exposure
277VerticalFuncButtonint8u--> NikonCustom ButtonsZ9 Values
281Func3Buttonint8u--> NikonCustom ButtonsZ9 Values
285VerticalAFOnButtonint8u--> NikonCustom ButtonsZ9 Values
293VerticalMultiSelectorPlaybackMode?int8u0 = Image Scroll L/R +
1 = Image Scroll Up/Down
295MovieFunc3Buttonint8u--> NikonCustom ButtonsZ9 Values
335LimitAF-AreaModeSelDynamic_S?int8u0 = Limit +
1 = No Limit
336LimitAF-AreaModeSelDynamic_M?int8u0 = Limit +
1 = No Limit
337LimitAF-AreaModeSelDynamic_L?int8u0 = Limit +
1 = No Limit
339LimitAF-AreaModeSel3DTracking?int8u0 = Limit +
1 = No Limit
341PlaybackFlickUp?int8u +
0 = Rating +
1 = Select To Send (PC) +
2 = Select To Send (FTP) +
3 = Protect +
4 = Voice Memo +
5 = None
+
345PlaybackFlickDown?int8u +
0 = Rating +
1 = Select To Send (PC) +
2 = Select To Send (FTP) +
3 = Protect +
4 = Voice Memo +
5 = None
+
349ISOStepSizeint8u0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
355ReverseFocusRingint8u0 = Not Reversed +
1 = Reversed
356EVFImageFrame?int8u0 = Off +
1 = On
357EVFGrid?int8u + + +
0 = 3x3 +
1 = 4x4 +
2 = 2.35:1
  3 = 1.85:1 +
4 = 5:4 +
5 = 4:3
  6 = 1:1 +
7 = 16:9 +
8 = 90%
+
359VirtualHorizonStyle?int8u0 = Type A (Cockpit) +
1 = Type B (Sides)
373Func4Button?int8u--> NikonCustom ButtonsZ9 Values
379AudioButton?int8u--> NikonCustom ButtonsZ9 Values
381QualityButton?int8u--> NikonCustom ButtonsZ9 Values
399VerticalMultiSelector?int8u--> NikonCustom ButtonsZ9 Values
421Func1ButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
423Func2ButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
425Func3ButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
431Func4ButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
437MovieRecordButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
439VerticalFuncButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
441AudioButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
447QualityButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
453WhiteBalanceButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
459CommandDialPlaybackMode?int8u +
0 = 1 Frame +
1 = 10 Frames +
2 = 50 Frames +
3 = Folder +
4 = Protect +
5 = Photos Only +
6 = Videos Only +
7 = Rating +
8 = Page +
9 = Skip To First Shot In Series
+
463SubCommandDialPlaybackMode?int8u +
0 = 1 Frame +
1 = 10 Frames +
2 = 50 Frames +
3 = Folder +
4 = Protect +
5 = Photos Only +
6 = Videos Only +
7 = Rating +
8 = Page +
9 = Skip To First Shot In Series
+
467FocusPointLock?int8u0 = Off +
1 = On
469ControlRingResponseint8u0 = High +
1 = Low
481VerticalMovieFuncButton?int8u--> NikonCustom ButtonsZ9 Values
505VerticalMovieAFOnButton?int8u--> NikonCustom ButtonsZ9 Values
515MovieAFAreaMode?int8u--> NikonCustom ButtonsZ9 Values
527HDMIViewAssist?int8u0 = Off +
1 = On
529ZebraPatternToneRange?int8u0 = Off +
1 = Highlights +
2 = Midtones
531MovieZebraPattern?int8u0 = Pattern 1 +
1 = Pattern 2
533MovieHighlightDisplayThreshold?int8u 
535MovieMidtoneDisplayValue?int8u 
537MovieMidtoneDisplayRange?int8u~ 
541MovieEVFGrid?int8u + + +
0 = 3x3 +
1 = 4x4 +
2 = 2.35:1
  3 = 1.85:1 +
4 = 5:4 +
5 = 4:3
  6 = 1:1 +
7 = 16:9 +
8 = 90%
+
549MovieShutterSpeedLock?int8u0 = Off +
1 = On
550MovieFocusPointLock?int8u0 = Off +
1 = On
563MatrixMetering?int8u0 = Face Detection Off +
1 = Face Detection On
564AF-CFocusDisplayint8u0 = Off +
1 = On
565FocusPeakingDisplay?int8u0 = Off +
1 = On
567KeepExposureint8u0 = Off +
1 = Shutter Speed +
2 = ISO
585StarlightView?int8u0 = Off +
1 = On
587EVFWarmDisplayMode?int8u0 = Off +
1 = Mode 1 +
2 = Mode 2
589EVFWarmDisplayBrightness?int8s 
591EVFReleaseIndicator?int8u0 = Off +
1 = Type A (Dark) +
2 = Type B (Border) +
3 = Type C (Sides)
601MovieApertureLock?int8u0 = Off +
1 = On
607FlickAdvanceDirection?int8u0 = Left to Right +
1 = Right to Left
+ +

NikonCustom SettingsZ9v4 Tags

+

Custom settings for the Z9.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1CustomSettingsBankint8u0 = A +
1 = B +
2 = C +
3 = D
3AF-CPrioritySelectionint8u0 = Release +
1 = Release + Focus +
3 = Focus
5AF-SPrioritySelectionint8u0 = Release +
1 = Focus
7BlockShotAFResponseint8u1 = 1 (Quick) +
2 = 2 +
3 = 3 (Normal) +
4 = 4 +
5 = 5 (Delayed)
11AFPointSelint8u0 = Use All +
1 = Use Half
13StoreByOrientationint8u0 = Off +
1 = Focus Point +
2 = Focus Point and AF-area mode
15AFActivationint8u0 = AF-On Only +
1 = Shutter/AF-On
16AF-OnOutOfFocusRelease?int8u0 = Disable +
1 = Enable
17LimitAF-AreaModeSelPinpoint?int8u0 = Limit +
1 = No Limit
19LimitAF-AreaModeSelWideAF_S?int8u0 = Limit +
1 = No Limit
20LimitAF-AreaModeSelWideAF_L?int8u0 = Limit +
1 = No Limit
21LimitAFAreaModeSelAuto?int8u0 = Limit +
1 = No Limit
22FocusPointWrap?int8u0 = No Wrap +
1 = Wrap
23ManualFocusPointIllumination?int8u0 = On During Focus Point Selection Only +
1 = On
24DynamicAreaAFAssist?int8u0 = Focus Point Only +
1 = Focus and Surrounding Points
25AF-AssistIlluminatorint8u0 = Off +
1 = On
26ManualFocusRingInAFModeint8u0 = Off +
1 = On
27ExposureControlStepSizeint8u0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
29EasyExposureCompensationint8u0 = Off +
1 = On +
2 = On (auto reset)
31CenterWeightedAreaSizeint8u0 = 8 mm +
1 = 12 mm +
4 = Average
33FineTuneOptMatrixMeteringint8s 
35FineTuneOptCenterWeightedint8s 
37FineTuneOptSpotMeteringint8s 
39FineTuneOptHighlightWeightedint8s 
41ShutterReleaseButtonAE-Lint8u0 = Off +
1 = On (Half Press) +
2 = On (Burst Mode)
43SelfTimerTimeint8u0 = 2 s +
1 = 5 s +
2 = 10 s +
3 = 20 s
45SelfTimerShotCountint8u 
49SelfTimerShotIntervalint8u0 = 0.5 s +
1 = 1 s +
2 = 2 s +
3 = 3 s
51PlaybackMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
53MenuMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
55ShootingInfoMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
57ImageReviewMonitorOffTimeint8u--> NikonCustom DelaysZ9 Values
59CLModeShootingSpeedint8u 
61MaxContinuousReleaseno 
65SyncReleaseMode?int8u0 = No Sync +
1 = Sync
69LimitSelectableImageAreaDX?int8u0 = Limit +
1 = No Limit
70LimitSelectableImageArea1To1?int8u0 = Limit +
1 = No Limit
71LimitSelectableImageArea16To9?int8u0 = Limit +
1 = No Limit
72FileNumberSequenceint8u0 = Off +
1 = On
73FocusPeakingLevel?int8u0 = High Sensitivity +
1 = Standard Sensitivity +
2 = Low Sensitivity
75FocusPeakingHighlightColor?int8u0 = Red +
1 = Yellow +
2 = Blue +
3 = White
81ContinuousModeDisplayint8u0 = Off +
1 = On
83FlashSyncSpeedno + +
0 = 1/60 s +
1 = 1/80 s +
2 = 1/100 s +
3 = 1/125 s
  4 = 1/160 s +
5 = 1/200 s +
6 = 1/250 s
+
85HighSpeedSyncint8u0 = Off +
1 = On
87FlashShutterSpeedno + + + +
0 = 1 s +
1 = 1/2 s +
2 = 1/4 s
  3 = 1/8 s +
4 = 1/15 s +
5 = 1/30 s
  6 = 1/60 s +
7 = 30 s +
8 = 15 s
  9 = 8 s +
10 = 4 s +
11 = 2 s
+
89FlashExposureCompAreaint8u0 = Entire Frame +
1 = Background Only
91AutoFlashISOSensitivityint8u0 = Subject and Background +
1 = Subject Only
93ModelingFlashint8u0 = Off +
1 = On
95AutoBracketModeMint8u0 = Flash/Speed +
1 = Flash/Speed/Aperture +
2 = Flash/Aperture +
3 = Flash Only +
4 = Flash/ISO
97AutoBracketOrderint8u0 = 0,-,+ +
1 = -,0,+
99Func1Buttonint8u--> NikonCustom ButtonsZ9 Values
115Func2Buttonint8u--> NikonCustom ButtonsZ9 Values
131AFOnButtonint8u--> NikonCustom ButtonsZ9 Values
143SubSelector?int8u--> NikonCustom ButtonsZ9 Values
155AssignMovieRecordButton?int8u--> NikonCustom ButtonsZ9 Values
159LensFunc1Buttonint8u--> NikonCustom ButtonsZ9 Values
167LensFunc2Buttonint8u--> NikonCustom ButtonsZ9 Values
173LensControlRingint8u0 = None (Disabled) +
1 = Focus (M/A) +
2 = ISO Sensitivity +
3 = Exposure Compensation +
4 = Aperture
175MultiSelectorShootModeint8u--> NikonCustom ButtonsZ9 Values
179MultiSelectorPlaybackModeint8u--> NikonCustom ButtonsZ9 Values
183ShutterSpeedLockint8u0 = Off +
1 = On
184ApertureLockint8u0 = Off +
1 = On
186CmdDialsReverseRotationint8u0 = No +
1 = Shutter Speed & Aperture +
0 = Exposure Compensation +
1 = Exposure Compensation, Shutter Speed & Aperture
191UseDialWithoutHold?int8u0 = Off +
1 = On
193ReverseIndicators?int8u0 = + 0 - +
1 = - 0 +
195MovieFunc1Buttonint8u--> NikonCustom ButtonsZ9 Values
199MovieFunc2Buttonint8u--> NikonCustom ButtonsZ9 Values
203MovieAF-OnButtonint8u--> NikonCustom ButtonsZ9 Values
207MovieMultiSelector?int8u--> NikonCustom ButtonsZ9 Values
215MovieLensControlRingint8u0 = None (Disabled) +
2 = ISO Sensitivity +
3 = Exposure Compensation +
4 = Power Aperture +
5 = Hi-Res Zoom
221MovieAFSpeedint8u 
223MovieAFSpeedApplyint8u0 = Always +
1 = Only During Recording
225MovieAFTrackingSensitivityint8u + +
0 = 1 (High) +
1 = 2 +
2 = 3 +
3 = 4 (Normal)
  4 = 5 +
5 = 6 +
6 = 7 (Low)
+
279LCDIllumination?int8u0 = Off +
1 = On
280ExtendedShutterSpeedsint8u0 = Off +
1 = On
281SubjectMotionint8u0 = Erratic +
1 = Steady
283FocusPointPersistenceint8u0 = Auto +
1 = Off
285AutoFocusModeRestrictions?int8u0 = AF-S +
1 = AF-C +
2 = Full-time AF +
3 = Manual +
4 = No Restrictions
289CHModeShootingSpeedint8u 
293.1LimitReleaseModeSelCL?int8u[val >> 1 & 0x1] +
0 = Limit +
1 = No Limit
293.2LimitReleaseModeSelCH?int8u[val >> 2 & 0x1] +
0 = Limit +
1 = No Limit
293.3LimitReleaseModeSelC30?int8u[val >> 4 & 0x1] +
0 = Limit +
1 = No Limit
293.4LimitReleaseModeSelC120?int8u[val >> 6 & 0x1] +
0 = Limit +
1 = No Limit
293.5LimitReleaseModeSelSelf?int8u[val >> 7 & 0x1] +
0 = Limit +
1 = No Limit
297FlashBurstPriority?int8u0 = Frame Rate +
1 = Exposure
301VerticalFuncButtonint8u--> NikonCustom ButtonsZ9 Values
305Func3Buttonint8u--> NikonCustom ButtonsZ9 Values
309VerticalAFOnButtonint8u--> NikonCustom ButtonsZ9 Values
317VerticalMultiSelectorPlaybackMode?int8u0 = Image Scroll L/R +
1 = Image Scroll Up/Down
319MovieFunc3Buttonint8u--> NikonCustom ButtonsZ9 Values
359LimitAF-AreaModeSelDynamic_S?int8u0 = Limit +
1 = No Limit
360LimitAF-AreaModeSelDynamic_M?int8u0 = Limit +
1 = No Limit
361LimitAF-AreaModeSelDynamic_L?int8u0 = Limit +
1 = No Limit
363LimitAF-AreaModeSel3DTracking?int8u0 = Limit +
1 = No Limit
365PlaybackFlickUp?int8u +
0 = Rating +
1 = Select To Send (PC) +
2 = Select To Send (FTP) +
3 = Protect +
4 = Voice Memo +
5 = None
+
369PlaybackFlickDown?int8u +
0 = Rating +
1 = Select To Send (PC) +
2 = Select To Send (FTP) +
3 = Protect +
4 = Voice Memo +
5 = None
+
373ISOStepSizeint8u0 = 1/3 EV +
1 = 1/2 EV +
2 = 1 EV
379ReverseFocusRingint8u0 = Not Reversed +
1 = Reversed
380EVFImageFrame?int8u0 = Off +
1 = On
381EVFGrid?int8u + + +
0 = 3x3 +
1 = 4x4 +
2 = 2.35:1
  3 = 1.85:1 +
4 = 5:4 +
5 = 4:3
  6 = 1:1 +
7 = 16:9 +
8 = 90%
+
383VirtualHorizonStyle?int8u0 = Type A (Cockpit) +
1 = Type B (Sides)
397Func4Button?int8u--> NikonCustom ButtonsZ9 Values
403AudioButton?int8u--> NikonCustom ButtonsZ9 Values
405QualityButton?int8u--> NikonCustom ButtonsZ9 Values
423VerticalMultiSelector?int8u--> NikonCustom ButtonsZ9 Values
445Func1ButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
447Func2ButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
449Func3ButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
455Func4ButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
461MovieRecordButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
463VerticalFuncButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
465AudioButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
471QualityButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
477WhiteBalanceButtonPlaybackMode?int8u--> NikonCustom ButtonsZ9 Values
483CommandDialPlaybackMode?int8u +
0 = 1 Frame +
1 = 10 Frames +
2 = 50 Frames +
3 = Folder +
4 = Protect +
5 = Photos Only +
6 = Videos Only +
7 = Rating +
8 = Page +
9 = Skip To First Shot In Series
+
487SubCommandDialPlaybackMode?int8u +
0 = 1 Frame +
1 = 10 Frames +
2 = 50 Frames +
3 = Folder +
4 = Protect +
5 = Photos Only +
6 = Videos Only +
7 = Rating +
8 = Page +
9 = Skip To First Shot In Series
+
491FocusPointLock?int8u0 = Off +
1 = On
493ControlRingResponseint8u0 = High +
1 = Low
505VerticalMovieFuncButton?int8u--> NikonCustom ButtonsZ9 Values
529VerticalMovieAFOnButton?int8u--> NikonCustom ButtonsZ9 Values
539MovieAFAreaMode?int8u--> NikonCustom ButtonsZ9 Values
551HDMIViewAssist?int8u0 = Off +
1 = On
553ZebraPatternToneRange?int8u0 = Off +
1 = Highlights +
2 = Midtones
555MovieZebraPattern?int8u0 = Pattern 1 +
1 = Pattern 2
557MovieHighlightDisplayThreshold?int8u 
559MovieMidtoneDisplayValue?int8u 
561MovieMidtoneDisplayRange?int8u~ 
565MovieEVFGrid?int8u + + +
0 = 3x3 +
1 = 4x4 +
2 = 2.35:1
  3 = 1.85:1 +
4 = 5:4 +
5 = 4:3
  6 = 1:1 +
7 = 16:9 +
8 = 90%
+
573MovieShutterSpeedLock?int8u0 = Off +
1 = On
574MovieFocusPointLock?int8u0 = Off +
1 = On
587MatrixMetering?int8u0 = Face Detection Off +
1 = Face Detection On
588AF-CFocusDisplayint8u0 = Off +
1 = On
589FocusPeakingDisplay?int8u0 = Off +
1 = On
591KeepExposureint8u0 = Off +
1 = Shutter Speed +
2 = ISO
609StarlightView?int8u0 = Off +
1 = On
611EVFWarmDisplayMode?int8u0 = Off +
1 = Mode 1 +
2 = Mode 2
613EVFWarmDisplayBrightness?int8s 
615EVFReleaseIndicator?int8u0 = Off +
1 = Type A (Dark) +
2 = Type B (Border) +
3 = Type C (Sides)
625MovieApertureLock?int8u0 = Off +
1 = On
631FlickAdvanceDirection?int8u0 = Left to Right +
1 = Right to Left
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 10, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/NikonSettings.html b/ExifTool/html/TagNames/NikonSettings.html new file mode 100644 index 0000000..ce2a0ea --- /dev/null +++ b/ExifTool/html/TagNames/NikonSettings.html @@ -0,0 +1,2587 @@ + + + + +NikonSettings Tags + + + +

NikonSettings Tags

+

User settings for newer Nikon models. A number of the tags are marked as +Unknown only to reduce the volume of the normal output.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001ISOAutoHiLimitno(D6) + +
1 = ISO 200 +
2 = ISO 250 +
3 = ISO 280 +
4 = ISO 320 +
5 = ISO 400 +
6 = ISO 500 +
7 = ISO 560 +
8 = ISO 640 +
9 = ISO 800 +
10 = ISO 1000 +
11 = ISO 1100 +
12 = ISO 1250 +
13 = ISO 1600 +
14 = ISO 2000 +
15 = ISO 2200 +
16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 4500 +
20 = ISO 5000 +
21 = ISO 6400 +
22 = ISO 8000 +
23 = ISO 9000
  24 = ISO 10000 +
25 = ISO 12800 +
26 = ISO 16000 +
27 = ISO 18000 +
28 = ISO 20000 +
29 = ISO 25600 +
30 = ISO 32000 +
31 = ISO 36000 +
32 = ISO 40000 +
33 = ISO 51200 +
34 = ISO 64000 +
35 = ISO 72000 +
36 = ISO 81200 +
37 = ISO 102400 +
38 = ISO Hi 0.3 +
39 = ISO Hi 0.5 +
40 = ISO Hi 0.7 +
41 = ISO Hi 1.0 +
42 = ISO Hi 2.0 +
43 = ISO Hi 3.0 +
44 = ISO Hi 4.0 +
45 = ISO Hi 5.0
+(Z7 and Z7_2) + +
1 = ISO 100 +
2 = ISO 125 +
4 = ISO 160 +
5 = ISO 200 +
6 = ISO 250 +
8 = ISO 320 +
9 = ISO 400 +
10 = ISO 500 +
12 = ISO 640 +
13 = ISO 800 +
14 = ISO 1000 +
16 = ISO 1250 +
17 = ISO 1600 +
18 = ISO 2000 +
20 = ISO 2500
  21 = ISO 3200 +
22 = ISO 4000 +
24 = ISO 5000 +
25 = ISO 6400 +
26 = ISO 8000 +
28 = ISO 10000 +
29 = ISO 12800 +
30 = ISO 16000 +
32 = ISO 20000 +
33 = ISO 25600 +
38 = ISO Hi 0.3 +
39 = ISO Hi 0.5 +
40 = ISO Hi 0.7 +
41 = ISO Hi 1.0 +
42 = ISO Hi 2.0
+
0x0002ISOAutoFlashLimitno(D6) +
1 = Same As Without Flash +
2 = ISO 200 +
3 = ISO 250 +
5 = ISO 320 +
6 = ISO 400 +
7 = ISO 500 +
9 = ISO 640 +
10 = ISO 800 +
11 = ISO 1000 +
13 = ISO 1250 +
14 = ISO 1600 +
15 = ISO 2000 +
17 = ISO 2500 +
18 = ISO 3200 +
19 = ISO 4000 +
21 = ISO 5000 +
22 = ISO 6400 +
23 = ISO 8000 +
25 = ISO 10000 +
26 = ISO 12800 +
27 = ISO 16000 +
29 = ISO 20000 +
30 = ISO 25600 +
31 = ISO 32000 +
33 = ISO 40000 +
34 = ISO 51200 +
35 = ISO 64000 +
36 = ISO 72000 +
37 = ISO 81200 +
38 = ISO 102400 +
39 = ISO Hi 0.3 +
40 = ISO Hi 0.5 +
41 = ISO Hi 0.7 +
42 = ISO Hi 1.0 +
43 = ISO Hi 2.0 +
44 = ISO Hi 3.0 +
45 = ISO Hi 4.0 +
46 = ISO Hi 5.0
+(Z7 and Z7_2) +
1 = Same As Without Flash +
2 = ISO 100 +
3 = ISO 125 +
5 = ISO 160 +
6 = ISO 200 +
7 = ISO 250 +
9 = ISO 320 +
10 = ISO 400 +
11 = ISO 500 +
13 = ISO 640 +
14 = ISO 800 +
15 = ISO 1000 +
17 = ISO 1250 +
18 = ISO 1600 +
19 = ISO 2000 +
21 = ISO 2500 +
22 = ISO 3200 +
23 = ISO 4000 +
25 = ISO 5000 +
26 = ISO 6400 +
27 = ISO 8000 +
29 = ISO 10000 +
30 = ISO 12800 +
31 = ISO 16000 +
33 = ISO 20000 +
34 = ISO 25600 +
39 = ISO Hi 0.3 +
40 = ISO Hi 0.5 +
41 = ISO Hi 0.7 +
42 = ISO Hi 1.0 +
43 = ISO Hi 2.0
+
0x0003ISOAutoShutterTimeno + +
1 = Auto (Slowest) +
2 = Auto (Slower) +
3 = Auto +
4 = Auto (Faster) +
5 = Auto (Fastest) +
6 = 1/4000 s +
7 = 1/3200 s +
8 = 1/2500 s +
9 = 1/2000 s +
10 = 1/1600 s +
11 = 1/1250 s +
12 = 1/1000 s +
13 = 1/800 s +
14 = 1/640 s +
15 = 1/500 s +
16 = 1/400 s +
17 = 1/320 s +
18 = 1/250 s +
19 = 1/200 s +
20 = 1/160 s +
21 = 1/125 s +
22 = 1/100 s +
23 = 1/80 s +
24 = 1/60 s +
25 = 1/50 s +
26 = 1/40 s +
27 = 1/30 s +
28 = 1/25 s +
29 = 1/20 s
  30 = 1/15 s +
31 = 1/13 s +
32 = 1/10 s +
33 = 1/8 s +
34 = 1/6 s +
35 = 1/5 s +
36 = 1/4 s +
37 = 1/3 s +
38 = 1/2.5 s +
39 = 1/2 s +
40 = 1/1.6 s +
41 = 1/1.3 s +
42 = 1 s +
43 = 1.3 s +
44 = 1.6 s +
45 = 2 s +
46 = 2.5 s +
47 = 3 s +
48 = 4 s +
49 = 5 s +
50 = 6 s +
51 = 8 s +
52 = 10 s +
53 = 13 s +
54 = 15 s +
55 = 20 s +
56 = 25 s +
57 = 30 s
+
0x000bFlickerReductionShootingno1 = Enable +
2 = Disable
0x000cFlickerReductionIndicatorno1 = Enable +
2 = Disable
0x000dMovieISOAutoHiLimitno(D6) + +
1 = ISO 200 +
2 = ISO 250 +
3 = ISO 280 +
4 = ISO 320 +
5 = ISO 400 +
6 = ISO 500 +
7 = ISO 560 +
8 = ISO 640 +
9 = ISO 800 +
10 = ISO 1000 +
11 = ISO 1100 +
12 = ISO 1250 +
13 = ISO 1600 +
14 = ISO 2000 +
15 = ISO 2200 +
16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000 +
19 = ISO 4500 +
20 = ISO 5000 +
21 = ISO 6400 +
22 = ISO 8000 +
23 = ISO 9000
  24 = ISO 10000 +
25 = ISO 12800 +
26 = ISO 16000 +
27 = ISO 18000 +
28 = ISO 20000 +
29 = ISO 25600 +
30 = ISO 32000 +
31 = ISO 36000 +
32 = ISO 40000 +
33 = ISO 51200 +
34 = ISO 64000 +
35 = ISO 72000 +
36 = ISO 81200 +
37 = ISO 102400 +
38 = ISO Hi 0.3 +
39 = ISO Hi 0.5 +
40 = ISO Hi 0.7 +
41 = ISO Hi 1.0 +
42 = ISO Hi 2.0 +
43 = ISO Hi 3.0 +
44 = ISO Hi 4.0 +
45 = ISO Hi 5.0
+(Z7 and Z7_2) + +
1 = ISO 200 +
2 = ISO 250 +
4 = ISO 320 +
5 = ISO 400 +
6 = ISO 500 +
8 = ISO 640 +
9 = ISO 800 +
10 = ISO 1000 +
12 = ISO 1250 +
13 = ISO 1600 +
14 = ISO 2000 +
16 = ISO 2500 +
17 = ISO 3200 +
18 = ISO 4000
  20 = ISO 5000 +
21 = ISO 6400 +
22 = ISO 8000 +
24 = ISO 10000 +
25 = ISO 12800 +
26 = ISO 16000 +
28 = ISO 20000 +
29 = ISO 25600 +
34 = ISO Hi 0.3 +
35 = ISO Hi 0.5 +
36 = ISO Hi 0.7 +
37 = ISO Hi 1.0 +
38 = ISO Hi 2.0
+
0x000eMovieISOAutoControlManualModeno1 = On +
2 = Off
0x000fMovieWhiteBalanceSameAsPhotono1 = Yes +
2 = No
0x001dAF-CPrioritySelno(D6) +
1 = Release +
2 = Release + Focus +
3 = Focus + Release +
4 = Focus +
(Z Series cameras thru November 2021) +
1 = Release +
2 = Focus
0x001eAF-SPrioritySelno1 = Release +
2 = Focus
0x0020AFPointSelno(D6) +
1 = 105 Points +
2 = 27 Points +
3 = 15 Points +
(Z Series cameras thru November 2021) +
1 = Use All +
2 = Use Half
0x0022AFActivationno1 = Shutter/AF-On +
2 = AF-On Only
0x0023FocusPointWrapno1 = Wrap +
2 = No Wrap
0x0025ManualFocusPointIlluminationno1 = On +
2 = On During Focus Point Selection Only
0x0026AF-AssistIlluminatorno1 = On +
2 = Off
0x0027ManualFocusRingInAFModeno1 = On +
2 = Off
0x0029ISOStepSizeno1 = 1/3 EV +
2 = 1/2 EV +
3 = 1 EV
0x002aExposureControlStepSizeno1 = 1/3 EV +
2 = 1/2 EV +
3 = 1 EV
0x002bEasyExposureCompensationno1 = On (auto reset) +
2 = On +
3 = Off
0x002cMatrixMeteringno1 = Face Detection On +
2 = Face Detection Off
0x002dCenterWeightedAreaSizeno(D6) +
1 = 8 mm +
2 = 12 mm +
3 = 15 mm +
4 = 20 mm +
5 = Average +
(Z Series cameras thru November 2021) +
1 = 12 mm +
2 = Average
0x002fFineTuneOptMatrixMeteringno 
0x0030FineTuneOptCenterWeightedno 
0x0031FineTuneOptSpotMeteringno 
0x0032FineTuneOptHighlightWeightedno 
0x0033ShutterReleaseButtonAE-Lno1 = On (Half Press) +
2 = On (Burst Mode) +
3 = Off
0x0034StandbyMonitorOffTimeno(D6) + + +
1 = 4 s +
2 = 6 s +
3 = 10 s
  4 = 30 s +
5 = 1 min +
6 = 5 min
  7 = 10 min +
8 = 30 min +
9 = No Limit
+(Z Series cameras thru November 2021) + +
1 = 10 s +
2 = 20 s +
3 = 30 s +
4 = 1 min
  5 = 5 min +
6 = 10 min +
7 = 30 min +
8 = No Limit
+
0x0035SelfTimerTimeno1 = 2 s +
2 = 5 s +
3 = 10 s +
4 = 20 s
0x0036SelfTimerShotCountno 
0x0037SelfTimerShotIntervalno1 = 0.5 s +
2 = 1 s +
3 = 2 s +
4 = 3 s
0x0038PlaybackMonitorOffTimeno + +
1 = 4 s +
2 = 10 s +
3 = 20 s
  4 = 1 min +
5 = 5 min +
6 = 10 min
+
0x0039MenuMonitorOffTimeno + +
1 = 4 s +
2 = 10 s +
3 = 20 s
  4 = 1 min +
5 = 5 min +
6 = 10 min
+
0x003aShootingInfoMonitorOffTimeno + +
1 = 4 s +
2 = 10 s +
3 = 20 s
  4 = 1 min +
5 = 5 min +
6 = 10 min
+
0x003bImageReviewMonitorOffTimeno + +
1 = 2 s +
2 = 4 s +
3 = 10 s +
4 = 20 s
  5 = 1 min +
6 = 5 min +
7 = 10 min
+
0x003cLiveViewMonitorOffTimeno + +
1 = 5 min +
2 = 10 min +
3 = 15 min
  4 = 20 min +
5 = 30 min +
6 = No Limit
+
0x003eCLModeShootingSpeedno 
0x003fMaxContinuousReleaseno 
0x0040ExposureDelayModeno + +
1 = 3 s +
2 = 2 s +
3 = 1 s
  4 = 0.5 s +
5 = 0.2 s +
6 = Off
+
0x0041ElectronicFront-CurtainShutterno1 = On +
2 = Off
0x0042FileNumberSequenceno1 = On +
2 = Off
0x0043FramingGridDisplayno1 = On +
2 = Off
0x0045LCDIlluminationno1 = On +
2 = Off
0x0046OpticalVRno1 = On +
2 = Off
0x0047FlashSyncSpeedno(D6) + +
1 = 1/250 s (auto FP) +
2 = 1/250 s +
3 = 1/200 s +
4 = 1/160 s
  5 = 1/125 s +
6 = 1/100 s +
7 = 1/80 s +
8 = 1/60 s
+(Z Series cameras thru November 2021) + +
1 = 1/200 s (auto FP) +
2 = 1/200 s +
3 = 1/160 s +
4 = 1/125 s
  5 = 1/100 s +
6 = 1/80 s +
7 = 1/60 s
+
0x0048FlashShutterSpeedno + +
1 = 1/60 s +
2 = 1/30 s +
3 = 1/15 s +
4 = 1/8 s
  5 = 1/4 s +
6 = 1/2 s +
7 = 1 s +
8 = 2 s
+
0x0049FlashExposureCompAreano1 = Entire Frame +
2 = Background Only
0x004aAutoFlashISOSensitivityno1 = Subject and Background +
2 = Subject Only
0x0051AssignBktButtonno1 = Auto Bracketing +
2 = Multiple Exposure +
3 = HDR (high dynamic range) +
4 = None
0x0052AssignMovieRecordButtonno(D6) +
1 = Voice Memo +
2 = Photo Shooting Bank +
3 = Exposure Mode +
4 = AF Mode/AF Area Mode +
5 = Image Area +
6 = Shutter/Aperture Lock +
7 = None
+(Z Series cameras thru November 2021) +
1 = AE Lock (hold) +
2 = AE Lock (reset on release) +
3 = Preview +
4 = +NEF(RAW) +
5 = LiveView Info Display On/Off +
6 = Grid Display +
7 = Zoom (Low) +
8 = Zoom (1:1) +
9 = Zoom (High) +
10 = My Menu +
11 = My Menu Top Item +
12 = Image Area +
13 = Image Quality +
14 = White Balance +
15 = Picture Control +
16 = Active-D Lighting +
17 = Metering +
18 = Flash Mode +
19 = Focus Mode +
20 = Auto Bracketing +
21 = Multiple Exposure +
22 = HDR +
23 = Exposure Delay Mode +
24 = Shutter/Aperture Lock +
25 = Non-CPU Lens +
26 = None
+
0x0053MultiSelectorShootModeno(D6) +
1 = Select Center Focus Point +
2 = Preset Focus Point - Press To Recall +
3 = Preset Focus Point - Hold To Recall +
4 = None +
(Z Series cameras thru November 2021) +
1 = Select Center Focus Point +
2 = Zoom (Low) +
3 = Zoom (1:1) +
4 = Zoom (High) +
5 = None
0x0054MultiSelectorPlaybackModeno(D6) + +
1 = Filtered Playback +
2 = View Histograms +
3 = Zoom (Low)
  4 = Zoom (1:1) +
5 = Zoom (High) +
6 = Choose Folder
+(Z Series cameras thru November 2021) + +
1 = Thumbnail On/Off +
2 = View Histograms +
3 = Zoom (Low)
  4 = Zoom (1:1) +
5 = Zoom (High) +
6 = Choose Folder
+
0x0056MultiSelectorLiveViewno1 = Select Center Focus Point +
2 = Zoom (Low) +
3 = Zoom (1:1) +
4 = Zoom (High) +
5 = None
0x0058CmdDialsReverseRotExposureComp?no 
0x0059CmdDialsChangeMainSubExposure?no 
0x005aCmdDialsChangeMainSubno1 = Autofocus On, Exposure On +
2 = Autofocus Off, Exposure On +
1 = Autofocus On, Exposure On (Mode A) +
2 = Autofocus Off, Exposure On (Mode A) +
1 = Autofocus On, Exposure Off +
2 = Autofocus Off, Exposure Off
0x005bCmdDialsMenuAndPlaybackno1 = On +
2 = On (Image Review Excluded) +
3 = Off
0x005cSubDialFrameAdvanceno + +
1 = 10 Frames +
2 = 50 Frames +
3 = Rating +
4 = Protect
  5 = Stills Only +
6 = Movies Only +
7 = Folder
+
0x005dReleaseButtonToUseDialno1 = Yes +
2 = No
0x005eReverseIndicatorsno1 = + 0 - +
2 = - 0 +
0x0062MovieShutterButtonno1 = Take Photo +
2 = Record Movie
0x0063Languageno5 = English +
6 = Spanish +
8 = French +
15 = Portuguese (Br)
0x006cShootingInfoDisplayno(D6) +
1 = Auto +
2 = Manual (dark on light) +
3 = Manual (light on dark) +
(Z Series cameras thru November 2021) +
1 = Manual (dark on light) +
2 = Manual (light on dark)
0x0074FlickAdvanceDirectionno1 = Right to Left +
2 = Left to Right
0x0075HDMIOutputResolutionno + +
1 = Auto +
2 = 2160p +
3 = 1080p +
4 = 1080i
  5 = 720p +
6 = 576p +
7 = 480p
+
0x0077HDMIOutputRangeno1 = Auto +
2 = Limit +
3 = Full
0x0080RemoteFuncButtonno(D6) +
1 = AF-On +
2 = AF Lock Only +
3 = AE Lock (reset on release) +
4 = AE Lock Only +
5 = AE/AF Lock +
6 = FV Lock +
7 = Flash Disable/Enable +
8 = Preview +
9 = +NEF(RAW) +
10 = LiveView Info Display On/Off +
11 = Recall Shooting Functions +
12 = None
+(Z Series cameras thru November 2021) +
1 = AF-On +
2 = AF Lock Only +
3 = AE Lock (reset on release) +
4 = AE Lock Only +
5 = AE/AF Lock +
6 = FV Lock +
7 = Flash Disable/Enable +
8 = Preview +
9 = +NEF(RAW) +
10 = None +
11 = LiveView Info Display On/Off
+
0x008bCmdDialsReverseRotationno1 = No +
2 = Shutter Speed & Aperture +
1 = Exposure Compensation +
2 = Exposure Compensation, Shutter Speed & Aperture
0x008dFocusPeakingHighlightColorno1 = Red +
2 = Yellow +
3 = Blue +
4 = White
0x008eContinuousModeDisplayno1 = On +
2 = Off
0x008fShutterSpeedLockno1 = On +
2 = Off
0x0090ApertureLockno1 = On +
2 = Off
0x0091MovieHighlightDisplayThresholdno + +
1 = 255 +
2 = 248 +
3 = 235 +
4 = 224
  5 = 213 +
6 = 202 +
7 = 191 +
8 = 180
+
0x0092HDMIExternalRecorderno1 = On +
2 = Off
0x0093BlockShotAFResponseno1 = 1 (Quick) +
2 = 2 +
3 = 3 (Normal) +
4 = 4 +
5 = 5 (Delay)
0x0094SubjectMotionno1 = Erratic +
2 = Steady
0x0095Three-DTrackingFaceDetectionno1 = On +
2 = Off
0x0097StoreByOrientationno(D6) +
1 = Focus Point +
2 = Focus Point and AF-area mode +
3 = Off +
(Z Series cameras thru November 2021) +
1 = Focus Point +
2 = Off
0x0099DynamicAreaAFAssistno1 = On +
2 = Off
0x009aExposureCompStepSizeno1 = 1/3 EV +
2 = 1/2 EV +
3 = 1 EV
0x009bSyncReleaseModeno1 = Sync +
2 = No Sync
0x009cModelingFlashno1 = On +
2 = Off
0x009dAutoBracketModeMno1 = Flash/Speed +
2 = Flash/Speed/Aperture +
3 = Flash/Aperture +
4 = Flash Only
0x009ePreviewButtonno +
1 = Preset Focus Point - Press To Recall +
2 = Preset Focus Point - Hold To Recall +
3 = AF-AreaMode S +
4 = AF-AreaMode D9 +
5 = AF-AreaMode D25 +
6 = AF-AreaMode D49 +
7 = AF-AreaMode D105 +
8 = AF-AreaMode 3D +
9 = AF-AreaMode Group +
10 = AF-AreaMode Group C1 +
11 = AF-AreaMode Group C2 +
12 = AF-AreaMode Auto Area +
13 = AF-AreaMode + AF-On S +
14 = AF-AreaMode + AF-On D9 +
15 = AF-AreaMode + AF-On D25 +
16 = AF-AreaMode + AF-On D49 +
17 = AF-AreaMode + AF-On D105 +
18 = AF-AreaMode + AF-On 3D +
19 = AF-AreaMode + AF-On Group +
20 = AF-AreaMode + AF-On Group C1 +
21 = AF-AreaMode + AF-On Group C2 +
22 = AF-AreaMode + AF-On Auto Area +
23 = AF-On +
24 = AF Lock Only +
25 = AE Lock (hold) +
26 = AE/WB Lock (hold) +
27 = AE Lock (reset on release) +
28 = AE Lock Only +
29 = AE/AF Lock +
30 = FV Lock +
31 = Flash Disable/Enable +
32 = Preview +
33 = Recall Shooting Functions +
34 = Bracketing Burst +
35 = Synchronized Release (Master) +
36 = Synchronized Release (Remote) +
39 = +NEF(RAW) +
40 = Grid Display +
41 = Virtual Horizon +
42 = Voice Memo +
43 = Wired LAN +
44 = My Menu +
45 = My Menu Top Item +
46 = Playback +
47 = Filtered Playback +
48 = Photo Shooting Bank +
49 = AF Mode/AF Area Mode +
50 = Image Area +
51 = Active-D Lighting +
52 = Exposure Delay Mode +
53 = Shutter/Aperture Lock +
54 = 1 Stop Speed/Aperture +
55 = Non-CPU Lens +
56 = None
+
0x00a0Func1Buttonno(D6) +
1 = Preset Focus Point - Press To Recall +
2 = Preset Focus Point - Hold To Recall +
3 = AF-AreaMode S +
4 = AF-AreaMode D9 +
5 = AF-AreaMode D25 +
6 = AF-AreaMode D49 +
7 = AF-AreaMode D105 +
8 = AF-AreaMode 3D +
9 = AF-AreaMode Group +
10 = AF-AreaMode Group C1 +
11 = AF-AreaMode Group C2 +
12 = AF-AreaMode Auto Area +
13 = AF-AreaMode + AF-On S +
14 = AF-AreaMode + AF-On D9 +
15 = AF-AreaMode + AF-On D25 +
16 = AF-AreaMode + AF-On D49 +
17 = AF-AreaMode + AF-On D105 +
18 = AF-AreaMode + AF-On 3D +
19 = AF-AreaMode + AF-On Group +
20 = AF-AreaMode + AF-On Group C1 +
21 = AF-AreaMode + AF-On Group C2 +
22 = AF-AreaMode + AF-On Auto Area +
23 = AF-On +
24 = AF Lock Only +
25 = AE Lock (hold) +
26 = AE/WB Lock (hold) +
27 = AE Lock (reset on release) +
28 = AE Lock Only +
29 = AE/AF Lock +
30 = FV Lock +
31 = Flash Disable/Enable +
32 = Preview +
33 = Recall Shooting Functions +
34 = Bracketing Burst +
35 = Synchronized Release (Master) +
36 = Synchronized Release (Remote) +
39 = +NEF(RAW) +
40 = Grid Display +
41 = Virtual Horizon +
42 = Voice Memo +
43 = Wired LAN +
44 = My Menu +
45 = My Menu Top Item +
46 = Playback +
47 = Filtered Playback +
48 = Photo Shooting Bank +
49 = AF Mode/AF Area Mode +
50 = Image Area +
51 = Active-D Lighting +
52 = Exposure Delay Mode +
53 = Shutter/Aperture Lock +
54 = 1 Stop Speed/Aperture +
55 = Non-CPU Lens +
56 = None
+(Z Series cameras thru November 2021) +
1 = AF-On +
2 = AF Lock Only +
3 = AE Lock (hold) +
4 = AE Lock (reset on release) +
5 = AE Lock Only +
6 = AE/AF Lock +
7 = FV Lock +
8 = Flash Disable/Enable +
9 = Preview +
10 = Matrix Metering +
11 = Center-weighted Metering +
12 = Spot Metering +
13 = Highlight-weighted Metering +
14 = Bracketing Burst +
15 = Synchronized Release (Master) +
16 = Synchronized Release (Remote) +
19 = +NEF(RAW) +
20 = Subject Tracking +
21 = Silent Photography +
22 = LiveView Info Display On/Off +
23 = Grid Display +
24 = Zoom (Low) +
25 = Zoom (1:1) +
26 = Zoom (High) +
27 = My Menu +
28 = My Menu Top Item +
29 = Playback +
30 = Protect +
31 = Image Area +
32 = Image Quality +
33 = White Balance +
34 = Picture Control +
35 = Active-D Lighting +
36 = Metering +
37 = Flash Mode +
38 = Focus Mode +
39 = Auto Bracketing +
40 = Multiple Exposure +
41 = HDR +
42 = Exposure Delay Mode +
43 = Shutter/Aperture Lock +
44 = Focus Peaking +
45 = Rating 0 +
46 = Rating 5 +
47 = Rating 4 +
48 = Rating 3 +
49 = Rating 2 +
50 = Rating 1 +
52 = None
+
0x00a2Func2Buttonno(D6) +
1 = Preset Focus Point - Press To Recall +
2 = Preset Focus Point - Hold To Recall +
3 = AF-AreaMode S +
4 = AF-AreaMode D9 +
5 = AF-AreaMode D25 +
6 = AF-AreaMode D49 +
7 = AF-AreaMode D105 +
8 = AF-AreaMode 3D +
9 = AF-AreaMode Group +
10 = AF-AreaMode Group C1 +
11 = AF-AreaMode Group C2 +
12 = AF-AreaMode Auto Area +
13 = AF-AreaMode + AF-On S +
14 = AF-AreaMode + AF-On D9 +
15 = AF-AreaMode + AF-On D25 +
16 = AF-AreaMode + AF-On D49 +
17 = AF-AreaMode + AF-On D105 +
18 = AF-AreaMode + AF-On 3D +
19 = AF-AreaMode + AF-On Group +
20 = AF-AreaMode + AF-On Group C1 +
21 = AF-AreaMode + AF-On Group C2 +
22 = AF-AreaMode + AF-On Auto Area +
23 = AF-On +
24 = AF Lock Only +
25 = AE Lock (hold) +
26 = AE/WB Lock (hold) +
27 = AE Lock (reset on release) +
28 = AE Lock Only +
29 = AE/AF Lock +
30 = FV Lock +
31 = Flash Disable/Enable +
32 = Preview +
33 = Recall Shooting Functions +
34 = Bracketing Burst +
35 = Synchronized Release (Master) +
36 = Synchronized Release (Remote) +
39 = +NEF(RAW) +
40 = Grid Display +
41 = Virtual Horizon +
42 = Voice Memo +
43 = Wired LAN +
44 = My Menu +
45 = My Menu Top Item +
46 = Playback +
47 = Filtered Playback +
48 = Photo Shooting Bank +
49 = AF Mode/AF Area Mode +
50 = Image Area +
51 = Active-D Lighting +
52 = Exposure Delay Mode +
53 = Shutter/Aperture Lock +
54 = 1 Stop Speed/Aperture +
55 = Non-CPU Lens +
56 = None
+(Z Series cameras thru November 2021) +
1 = AF-On +
2 = AF Lock Only +
3 = AE Lock (hold) +
4 = AE Lock (reset on release) +
5 = AE Lock Only +
6 = AE/AF Lock +
7 = FV Lock +
8 = Flash Disable/Enable +
9 = Preview +
10 = Matrix Metering +
11 = Center-weighted Metering +
12 = Spot Metering +
13 = Highlight-weighted Metering +
14 = Bracketing Burst +
15 = Synchronized Release (Master) +
16 = Synchronized Release (Remote) +
19 = +NEF(RAW) +
20 = Subject Tracking +
21 = Silent Photography +
22 = LiveView Info Display On/Off +
23 = Grid Display +
24 = Zoom (Low) +
25 = Zoom (1:1) +
26 = Zoom (High) +
27 = My Menu +
28 = My Menu Top Item +
29 = Playback +
30 = Protect +
31 = Image Area +
32 = Image Quality +
33 = White Balance +
34 = Picture Control +
35 = Active-D Lighting +
36 = Metering +
37 = Flash Mode +
38 = Focus Mode +
39 = Auto Bracketing +
40 = Multiple Exposure +
41 = HDR +
42 = Exposure Delay Mode +
43 = Shutter/Aperture Lock +
44 = Focus Peaking +
45 = Rating 0 +
46 = Rating 5 +
47 = Rating 4 +
48 = Rating 3 +
49 = Rating 2 +
50 = Rating 1 +
52 = None
+
0x00a3AF-OnButtonno(D6) +
1 = AF-AreaMode S +
2 = AF-AreaMode D9 +
3 = AF-AreaMode D25 +
4 = AF-AreaMode D49 +
5 = AF-AreaMode D105 +
6 = AF-AreaMode 3D +
7 = AF-AreaMode Group +
8 = AF-AreaMode Group C1 +
9 = AF-AreaMode Group C2 +
10 = AF-AreaMode Auto Area +
11 = AF-AreaMode + AF-On S +
12 = AF-AreaMode + AF-On D9 +
13 = AF-AreaMode + AF-On D25 +
14 = AF-AreaMode + AF-On D49 +
15 = AF-AreaMode + AF-On D105 +
16 = AF-AreaMode + AF-On 3D +
17 = AF-AreaMode + AF-On Group +
18 = AF-AreaMode + AF-On Group C1 +
19 = AF-AreaMode + AF-On Group C2 +
20 = AF-AreaMode + AF-On Auto Area +
21 = AF-On +
22 = AF Lock Only +
23 = AE Lock (hold) +
24 = AE/WB Lock (hold) +
25 = AE Lock (reset on release) +
26 = AE Lock Only +
27 = AE/AF Lock +
28 = Recall Shooting Functions +
29 = None
+(Z Series cameras thru November 2021) +
1 = Center Focus Point +
2 = AF-On +
3 = AF Lock Only +
4 = AE Lock (hold) +
5 = AE Lock (reset on release) +
6 = AE Lock Only +
7 = AE/AF Lock +
8 = LiveView Info Display On/Off +
9 = Zoom (Low) +
10 = Zoom (1:1) +
11 = Zoom (High) +
12 = None
+
0x00a4SubSelectorno1 = Same as MultiSelector +
2 = Focus Point Selection
0x00a5SubSelectorCenterno(D6) +
1 = Preset Focus Point - Press To Recall +
2 = Preset Focus Point - Hold To Recall +
3 = Center Focus Point +
4 = AF-AreaMode S +
5 = AF-AreaMode D9 +
6 = AF-AreaMode D25 +
7 = AF-AreaMode D49 +
8 = AF-AreaMode D105 +
9 = AF-AreaMode 3D +
10 = AF-AreaMode Group +
11 = AF-AreaMode Group C1 +
12 = AF-AreaMode Group C2 +
13 = AF-AreaMode Auto Area +
14 = AF-AreaMode + AF-On S +
15 = AF-AreaMode + AF-On D9 +
16 = AF-AreaMode + AF-On D25 +
17 = AF-AreaMode + AF-On D49 +
18 = AF-AreaMode + AF-On D105 +
19 = AF-AreaMode + AF-On 3D +
20 = AF-AreaMode + AF-On Group +
21 = AF-AreaMode + AF-On Group C1 +
22 = AF-AreaMode + AF-On Group C2 +
23 = AF-AreaMode + AF-On Auto Area +
24 = AF-On +
25 = AF Lock Only +
26 = AE Lock (hold) +
27 = AE/WB Lock (hold) +
28 = AE Lock (reset on release) +
29 = AE Lock Only +
30 = AE/AF Lock +
31 = FV Lock +
32 = Flash Disable/Enable +
33 = Preview +
34 = Recall Shooting Functions +
35 = Bracketing Burst +
36 = Synchronized Release (Master) +
37 = Synchronized Release (Remote) +
38 = None
+(Z Series cameras thru November 2021) +
1 = Center Focus Point +
2 = AF-On +
3 = AF Lock Only +
4 = AE Lock (hold) +
5 = AE Lock (reset on release) +
6 = AE Lock Only +
7 = AE/AF Lock +
8 = FV Lock +
9 = Flash Disable/Enable +
10 = Preview +
11 = Matrix Metering +
12 = Center-weighted Metering +
13 = Spot Metering +
14 = Highlight-weighted Metering +
15 = Bracketing Burst +
16 = Synchronized Release (Master) +
17 = Synchronized Release (Remote) +
20 = +NEF(RAW) +
21 = LiveView Info Display On/Off +
22 = Grid Display +
23 = Image Area +
24 = Non-CPU Lens +
25 = None
+
0x00a7LensFunc1Buttonno(D6) +
1 = Preset Focus Point - Press To Recall +
2 = Preset Focus Point - Hold To Recall +
3 = AF-AreaMode S +
4 = AF-AreaMode D9 +
5 = AF-AreaMode D25 +
6 = AF-AreaMode D49 +
7 = AF-AreaMode D105 +
8 = AF-AreaMode 3D +
9 = AF-AreaMode Group +
10 = AF-AreaMode Group C1 +
11 = AF-AreaMode Group C2 +
12 = AF-AreaMode Auto Area +
13 = AF-AreaMode + AF-On S +
14 = AF-AreaMode + AF-On D9 +
15 = AF-AreaMode + AF-On D25 +
16 = AF-AreaMode + AF-On D49 +
17 = AF-AreaMode + AF-On D105 +
18 = AF-AreaMode + AF-On 3D +
19 = AF-AreaMode + AF-On Group +
20 = AF-AreaMode + AF-On Group C1 +
21 = AF-AreaMode + AF-On Group C2 +
22 = AF-AreaMode + AF-On Auto Area +
23 = AF-On +
24 = AF Lock Only +
25 = AE Lock Only +
26 = AE/AF Lock +
27 = Flash Disable/Enable +
28 = Recall Shooting Functions +
29 = Synchronized Release (Master) +
30 = Synchronized Release (Remote)
+(Z Series cameras thru November 2021) +
1 = AF-On +
2 = AF Lock Only +
3 = AE Lock (hold) +
4 = AE Lock (reset on release) +
5 = AE Lock Only +
6 = AE/AF Lock +
7 = FV Lock +
8 = Flash Disable/Enable +
9 = Preview +
10 = Matrix Metering +
11 = Center-weighted Metering +
12 = Spot Metering +
13 = Highlight-weighted Metering +
14 = Bracketing Burst +
15 = Synchronized Release (Master) +
16 = Synchronized Release (Remote) +
19 = +NEF(RAW) +
20 = Subject Tracking +
21 = Grid Display +
22 = Zoom (Low) +
23 = Zoom (1:1) +
24 = Zoom (High) +
25 = My Menu +
26 = My Menu Top Item +
27 = Playback +
28 = None
+
0x00a8CmdDialsApertureSettingno1 = Sub-command Dial +
2 = Aperture Ring
0x00a9MultiSelectorno1 = Restart Standby Timer +
2 = Do Nothing
0x00aaLiveViewButtonOptionsno1 = Enable +
2 = Enable (Standby Timer Active) +
3 = Disable
0x00abLightSwitchno1 = LCD Backlight +
2 = LCD Backlight and Shooting Information
0x00b1MoviePreviewButton +
MovieFunc1Button
no
no
(D6) +
1 = Power Aperture (Open) +
2 = Exposure Compensation +
3 = Grid Display +
4 = Zoom (Low) +
5 = Zoom (1:1) +
6 = Zoom (High) +
7 = Image Area +
8 = Microphone Sensitivity +
9 = None
+(Z Series cameras thru November 2021) +
1 = Power Aperture (Open) +
2 = Exposure Compensation +
3 = Subject Tracking +
4 = LiveView Info Display On/Off +
5 = Grid Display +
6 = Zoom (Low) +
7 = Zoom (1:1) +
8 = Zoom (High) +
9 = Protect +
10 = Image Area +
11 = White Balance +
12 = Picture Control +
13 = Active-D Lighting +
14 = Metering +
15 = Focus Mode +
16 = Microphone Sensitivity +
17 = Focus Peaking +
18 = Rating (None) +
19 = Rating (5) +
20 = Rating (4) +
21 = Rating (3) +
22 = Rating (2) +
23 = Rating (1) +
25 = None
+
0x00b3MovieFunc1Button +
MovieFunc2Button
no
no
(D6) +
1 = Power Aperture (Close) +
2 = Exposure Compensation +
3 = Grid Display +
4 = Zoom (Low) +
5 = Zoom (1:1) +
6 = Zoom (High) +
7 = Image Area +
8 = Microphone Sensitivity +
9 = None
+(Z Series cameras thru November 2021) +
1 = Power Aperture (Close) +
2 = Exposure Compensation +
3 = Subject Tracking +
4 = LiveView Info Display On/Off +
5 = Grid Display +
6 = Zoom (Low) +
7 = Zoom (1:1) +
8 = Zoom (High) +
9 = Protect +
10 = Image Area +
11 = White Balance +
12 = Picture Control +
13 = Active-D Lighting +
14 = Metering +
15 = Focus Mode +
16 = Microphone Sensitivity +
17 = Focus Peaking +
18 = Rating (None) +
19 = Rating (5) +
20 = Rating (4) +
21 = Rating (3) +
22 = Rating (2) +
23 = Rating (1) +
25 = None
+
0x00b5MovieFunc2Buttonno +
1 = Grid Display +
2 = Zoom (Low) +
3 = Zoom (1:1) +
4 = Zoom (High) +
5 = Image Area +
6 = Microphone Sensitivity +
7 = None
+
0x00b6AssignMovieSubselectorno(D6) + +
1 = Center Focus Point +
2 = AF Lock Only +
3 = AE Lock (hold) +
4 = AE/WB Lock (hold) +
5 = AE Lock Only +
6 = AE/AF Lock
  7 = Zoom (Low) +
8 = Zoom (1:1) +
9 = Zoom (High) +
10 = Record Movie +
11 = None
+(Z Series cameras thru November 2021) +
1 = Center Focus Point +
2 = AF Lock Only +
3 = AE Lock (hold) +
4 = AE Lock Only +
5 = AE/AF Lock +
6 = LiveView Info Display On/Off +
7 = Grid Display +
8 = Zoom (Low) +
9 = Zoom (1:1) +
10 = Zoom (High) +
11 = Record Movie +
12 = Image Area +
13 = None
+
0x00b8LimitAFAreaModeSelD9?no1 = Limit +
2 = No Limit
0x00b9LimitAFAreaModeSelD25?no1 = Limit +
2 = No Limit
0x00bcLimitAFAreaModeSel3D?no1 = Limit +
2 = No Limit
0x00bdLimitAFAreaModeSelGroup?no1 = Limit +
2 = No Limit
0x00beLimitAFAreaModeSelAuto?no1 = Limit +
2 = No Limit
0x00c1LimitSelectableImageArea5To4?no1 = Limit +
2 = No Limit
0x00c2LimitSelectableImageArea1To1?no1 = Limit +
2 = No Limit
0x00d4PhotoShootingMenuBankno1 = A +
2 = B +
3 = C +
4 = D
0x00d5CustomSettingsBankno1 = A +
2 = B +
3 = C +
4 = D
0x00d6LimitAF-AreaModeSelPinpoint?no1 = Limit +
2 = No Limit
0x00d7LimitAF-AreaModeSelDynamic?no1 = Limit +
2 = No Limit
0x00d8LimitAF-AreaModeSelWideAF_S?no1 = Limit +
2 = No Limit
0x00d9LimitAF-AreaModeSelWideAF_L?no1 = Limit +
2 = No Limit
0x00daLowLightAFno1 = On +
2 = Off
0x00dbLimitSelectableImageAreaDX?no1 = Limit +
2 = No Limit
0x00dcLimitSelectableImageArea5To4?no1 = Limit +
2 = No Limit
0x00ddLimitSelectableImageArea1To1?no1 = Limit +
2 = No Limit
0x00deLimitSelectableImageArea16To9?no1 = Limit +
2 = No Limit
0x00dfApplySettingsToLiveViewno1 = On +
2 = Off
0x00e0FocusPeakingLevelno1 = High Sensitivity +
2 = Standard Sensitivity +
3 = Low Sensitivity +
4 = Off
0x00eaLensControlRingno1 = Aperture +
2 = Exposure Compensation +
3 = ISO Sensitivity +
4 = None (Disabled)
0x00edMovieMultiSelectorno(D6) + +
1 = Center Focus Point +
2 = Zoom (Low) +
3 = Zoom (1:1)
  4 = Zoom (High) +
5 = Record Movie +
6 = None
+ + +
1 = Center Focus Point +
2 = Zoom (Low) +
3 = Zoom (1:1)
  4 = Zoom (High) +
5 = Record Movie +
6 = None
+
0x00eeMovieAFSpeedno 
0x00efMovieAFSpeedApplyno1 = Always +
2 = Only During Recording
0x00f0MovieAFTrackingSensitivityno + +
1 = 1 (High) +
2 = 2 +
3 = 3 +
4 = 4 (Normal)
  5 = 5 +
6 = 6 +
7 = 7 (Low)
+
0x00f1MovieHighlightDisplayPatternno1 = Pattern 1 +
2 = Pattern 2 +
3 = Off
0x00f2SubDialFrameAdvanceRating5?no1 = No +
2 = Yes
0x00f3SubDialFrameAdvanceRating4?no1 = No +
2 = Yes
0x00f4SubDialFrameAdvanceRating3?no1 = No +
2 = Yes
0x00f5SubDialFrameAdvanceRating2?no1 = No +
2 = Yes
0x00f6SubDialFrameAdvanceRating1?no1 = No +
2 = Yes
0x00f7SubDialFrameAdvanceRating0?no1 = No +
2 = Yes
0x00f9MovieAF-OnButtonno +
1 = Center Focus Point +
2 = AF-On +
3 = AF Lock Only +
4 = AE Lock (hold) +
5 = AE Lock Only +
6 = AE/AF Lock +
7 = LiveView Info Display On/Off +
8 = Zoom (Low) +
9 = Zoom (1:1) +
10 = Zoom (High) +
11 = Record Movie +
12 = None
+
0x00fbSecondarySlotFunctionno1 = Overflow +
2 = Backup +
3 = NEF Primary + JPG Secondary +
4 = JPG Primary + JPG Secondary
0x00fcSilentPhotographyno1 = On +
2 = Off
0x00fdExtendedShutterSpeedsno1 = On +
2 = Off
0x0102HDMIBitDepthno1 = 8 Bit +
2 = 10 Bit
0x0103HDMIOutputHDRno2 = On +
3 = Off
0x0104HDMIViewAssistno1 = On +
2 = Off
0x0109BracketSetno1 = AE/Flash +
2 = AE +
3 = Flash +
4 = White Balance +
5 = Active-D Lighting
0x010aBracketProgramno(AE and/or Flash Bracketing) + + +
15 = +3F +
16 = -3F +
17 = +2F
  18 = -2F +
19 = Disabled +
20 = 3F
  21 = 5F +
22 = 7F +
23 = 9F
+(White Balance Bracketing) + + +
1 = B3F +
2 = A3F +
3 = B2F +
4 = A2F
  5 = Disabled +
6 = 3F +
7 = 5F +
8 = 7F
  9 = 9F +
19 = N/A
+(Active-D Bracketing) +
[val & 0xf] +
10 = Disabled +
11 = 2 Exposures +
12 = 3 Exposures +
13 = 4 Exposures +
14 = 5 Exposures
0x010bBracketIncrementno(AE and/or Flash Bracketing enabled) +
1 = 0.3 +
3 = 0.5 +
4 = 1.0 +
5 = 2.0 +
6 = 3.0 +
(White Balance Bracketing enabled)
0x010cBracketIncrementno(Active-D Bracketing enabled) +
0 = Off +
1 = Off, Low +
2 = Off, Normal +
3 = Off, High +
4 = Off, Extra High +
5 = Off, Auto +
6 = Off, Low, Normal +
7 = Off, Low, Normal, High +
8 = Off, Low, Normal, High, Extra High
+
0x010eMonitorBrightnessno 
0x0116GroupAreaC1no + + + +
1 = 1x7 +
2 = 1x5 +
3 = 3x7 +
4 = 3x5 +
5 = 3x3
  6 = 5x7 +
7 = 5x5 +
8 = 5x3 +
9 = 5x1 +
10 = 7x7
  11 = 7x5 +
12 = 7x3 +
13 = 7x1 +
14 = 11x3 +
15 = 11x1
  16 = 15x3 +
17 = 15x1
+
0x0117AutoAreaAFStartingPointno1 = Enable +
2 = Disable
0x0118FocusPointPersistenceno1 = Auto +
2 = Off
0x0119LimitAFAreaModeSelD49?no1 = Limit +
2 = No Limit
0x011aLimitAFAreaModeSelD105?no1 = Limit +
2 = No Limit
0x011bLimitAFAreaModeSelGroupC1?no1 = Limit +
2 = No Limit
0x011cLimitAFAreaModeSelGroupC2?no1 = Limit +
2 = No Limit
0x011dAutoFocusModeRestrictionsno1 = AF-S +
2 = AF-C +
3 = No Limit
0x011eFocusPointBrightnessno1 = Extra High +
2 = High +
3 = Normal +
4 = Low
0x011fCHModeShootingSpeedno 
0x0120CLModeShootingSpeedno 
0x0121QuietShutterShootingSpeedno + +
1 = Single +
2 = 5 fps +
3 = 4 fps
  4 = 3 fps +
5 = 2 fps +
6 = 1 fps
+
0x0122LimitReleaseModeSelCL?no0 = No Limit +
1 = Limit +
2 = No Limit
0x0123LimitReleaseModeSelCH?no0 = No Limit +
1 = Limit +
2 = No Limit
0x0124LimitReleaseModeSelQ?no0 = No Limit +
1 = Limit +
2 = No Limit
0x0125LimitReleaseModeSelTimer?no0 = No Limit +
1 = Limit +
2 = No Limit
0x0126LimitReleaseModeSelMirror-Up?no0 = No Limit +
1 = Limit +
2 = No Limit
0x0127LimitSelectableImageArea16To9?no1 = Limit +
2 = No Limit
0x0128RearControPanelDisplayno1 = Release Mode +
2 = Frame Count
0x0129FlashBurstPriorityno1 = Frame Rate +
2 = Exposure
0x012aRecallShootFuncExposureModeno1 = Off +
2 = On
0x012bRecallShootFuncShutterSpeedno1 = Off +
2 = On
0x012cRecallShootFuncApertureno1 = Off +
2 = On
0x012dRecallShootFuncExposureCompno1 = Off +
2 = On
0x012eRecallShootFuncISOno1 = Off +
2 = On
0x012fRecallShootFuncMeteringModeno1 = Off +
2 = On
0x0130RecallShootFuncWhiteBalanceno1 = Off +
2 = On
0x0131RecallShootFuncAFAreaModeno1 = Off +
2 = On
0x0132RecallShootFuncFocusTrackingno1 = Off +
2 = On
0x0133RecallShootFuncAF-Onno1 = Off +
2 = On
0x0134VerticalFuncButtonno +
1 = Preset Focus Point +
2 = AE Lock (hold) +
3 = AE/WB Lock (hold) +
4 = AE Lock (reset on release) +
5 = FV Lock +
6 = Preview +
7 = +NEF(RAW) +
8 = Grid Display +
9 = Virtual Horizon +
10 = Voice Memo +
11 = Playback +
12 = Filtered Playback +
13 = Photo Shooting Bank +
14 = Exposure Mode +
15 = Exposure Comp +
16 = AF Mode/AF Area Mode +
17 = Image Area +
18 = ISO +
19 = Active-D Lighting +
20 = Metering +
21 = Exposure Delay Mode +
22 = Shutter/Aperture Lock +
23 = 1 Stop Speed/Aperture +
24 = Rating 0 +
25 = Rating 5 +
26 = Rating 4 +
27 = Rating 3 +
28 = Rating 2 +
29 = Rating 1 +
30 = Candidate For Deletion +
31 = Non-CPU Lens +
32 = None
+
0x0135Func3Buttonno +
1 = Voice Memo +
2 = Select To Send +
3 = Wired LAN +
4 = My Menu +
5 = My Menu Top Item +
6 = Filtered Playback +
7 = Rating 0 +
8 = Rating 5 +
9 = Rating 4 +
10 = Rating 3 +
11 = Rating 2 +
12 = Rating 1 +
13 = Candidate For Deletion +
14 = None
+
0x0136VerticalAF-OnButtonno +
1 = AF-AreaMode S +
2 = AF-AreaMode D9 +
3 = AF-AreaMode D25 +
4 = AF-AreaMode D49 +
5 = AF-AreaMode D105 +
6 = AF-AreaMode 3D +
7 = AF-AreaMode Group +
8 = AF-AreaMode Group C1 +
9 = AF-AreaMode Group C2 +
10 = AF-AreaMode Auto Area +
11 = AF-AreaMode + AF-On S +
12 = AF-AreaMode + AF-On D9 +
13 = AF-AreaMode + AF-On D25 +
14 = AF-AreaMode + AF-On D49 +
15 = AF-AreaMode + AF-On D105 +
16 = AF-AreaMode + AF-On 3D +
17 = AF-AreaMode + AF-On Group +
18 = AF-AreaMode + AF-On Group C1 +
19 = AF-AreaMode + AF-On Group C2 +
20 = AF-AreaMode + AF-On Auto Area +
21 = Same as AF-On +
22 = AF-On +
23 = AF Lock Only +
24 = AE Lock (hold) +
25 = AE/WB Lock (hold) +
26 = AE Lock (reset on release) +
27 = AE Lock Only +
28 = AE/AF Lock +
29 = Recall Shooting Functions +
30 = None
+
0x0137VerticalMultiSelectorno1 = Same as MultiSelector +
2 = Focus Point Selection
0x0138MeteringButtonno +
1 = Photo Shooting Bank +
2 = Image Area +
3 = Active-D Lighting +
4 = Metering +
5 = Exposure Delay Mode +
6 = Shutter/Aperture Lock +
7 = 1 Stop Speed/Aperture +
8 = Non-CPU Lens +
9 = None
+
0x0139PlaybackFlickUpno1 = Rating +
2 = Select To Send +
3 = Protect +
4 = Voice Memo +
5 = None
0x013aPlaybackFlickUpRatingno(Meaningful only when PlaybackFlickUp is Rating) +
1 = Rating 5 +
2 = Rating 4 +
3 = Rating 3 +
4 = Rating 2 +
5 = Rating 1 +
6 = Candidate for Deletion
+
0x013bPlaybackFlickDownno1 = Rating +
2 = Select To Send +
3 = Protect +
4 = Voice Memo +
5 = None
0x013cPlaybackFlickDownRatingno(Meaningful only when PlaybackFlickDown is Rating) +
1 = Rating 5 +
2 = Rating 4 +
3 = Rating 3 +
4 = Rating 2 +
5 = Rating 1 +
6 = Candidate for Deletion
+
0x013dMovieFunc3Buttonno1 = Record Movie +
2 = My Menu +
3 = My Menu Top Item +
4 = None
0x0150ShutterTypeno1 = Auto +
2 = Mechanical +
3 = Electronic
0x0151LensFunc2Buttonno +
1 = AF-On +
2 = AF Lock Only +
3 = AE Lock (hold) +
4 = AE Lock (reset on release) +
5 = AE Lock Only +
6 = AE/AF Lock +
7 = FV Lock +
8 = Flash Disable/Enable +
9 = Preview +
10 = Matrix Metering +
11 = Center-weighted Metering +
12 = Spot Metering +
13 = Highlight-weighted Metering +
14 = Bracketing Burst +
15 = Synchronized Release (Master) +
16 = Synchronized Release (Remote) +
19 = +NEF(RAW) +
20 = Subject Tracking +
21 = Grid Display +
22 = Zoom (Low) +
23 = Zoom (1:1) +
24 = Zoom (High) +
25 = My Menu +
26 = My Menu Top Item +
27 = Playback +
28 = None
+
0x0158USBPowerDeliveryno1 = Enable +
2 = Disable
0x0159EnergySavingModeno1 = On +
2 = Off
0x015cBracketingBurstOptionsno1 = Enable +
2 = Disable
0x015ePrimarySlotno1 = CFexpress/XQD Card +
2 = SD Card
0x015fReverseFocusRingno1 = Not Reversed +
2 = Reversed
0x0160VerticalFuncButtonno +
1 = AE Lock (hold) +
2 = AE Lock (reset on release) +
3 = FV Lock +
4 = Preview +
5 = +NEF(RAW) +
6 = Subject Tracking +
7 = Silent Photography +
8 = LiveView Info Display On/Off +
9 = Playback +
10 = Image Area +
11 = Metering +
12 = Flash Mode +
13 = Focus Mode +
14 = Exposure Delay Mode +
15 = Shutter/Aperture Lock +
16 = Exposure Compensation +
17 = ISO Sensitivity +
18 = None
+
0x0161VerticalAFOnButtonno +
1 = Same as AF-On Button +
2 = Select Center Focus Point +
3 = AF-On +
4 = AF Lock Only +
5 = AE Lock (hold) +
6 = AE Lock (reset on release) +
7 = AE Lock Only +
8 = AE/AF Lock +
9 = LiveView Info Display On/Off +
10 = Zoom (Low) +
11 = Zoom (1:1) +
12 = Zoom (High) +
13 = None
+
0x0162VerticalMultiSelectorno1 = Same as MultiSelector +
2 = Focus Point Selection
0x0164VerticalMovieFuncButtonno1 = LiveView Info Display On/Off +
2 = Record Movie +
3 = Exposure Compensation +
4 = ISO +
5 = None
0x0165VerticalMovieAFOnButtonno +
1 = Same as AF-On +
2 = Center Focus Point +
3 = AF-On +
4 = AF Lock Only +
5 = AE Lock (hold) +
6 = AE Lock Only +
7 = AE/AF Lock +
8 = LiveView Info Display On/Off +
9 = Zoom (Low) +
10 = Zoom (1:1) +
11 = Zoom (High) +
12 = Record Movie +
13 = None
+
0x0169LimitAF-AreaModeSelAutoPeople?no1 = Limit +
2 = No Limit
0x016aLimitAF-AreaModeSelAutoAnimals?no1 = Limit +
2 = No Limit
0x016bLimitAF-AreaModeSelWideLPeople?no1 = Limit +
2 = No Limit
0x016cLimitAF-AreaModeSelWideLAnimals?no1 = Limit +
2 = No Limit
0x016dSaveFocusno1 = On +
2 = Off
0x016eAFAreaModeno + +
2 = Single-point +
3 = Dynamic-area +
4 = Wide (S) +
5 = Wide (L) +
6 = Wide (L-people)
  7 = Wide (L-animals) +
8 = Auto +
9 = Auto (People) +
10 = Auto (Animals)
+
0x016fMovieAFAreaModeno + +
1 = Single-point +
2 = Wide (S) +
3 = Wide (L) +
4 = Wide (L-people)
  5 = Wide (L-animals) +
6 = Auto +
7 = Auto (People) +
8 = Auto (Animals)
+
0x0170PreferSubSelectorCenterno1 = Off +
2 = On
0x0171KeepExposureWithTeleconverterno1 = Off +
2 = Shutter Speed +
3 = ISO
0x0174FocusPointSelectionSpeedno1 = Normal +
2 = High +
3 = Very High
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jan 12, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Nintendo.html b/ExifTool/html/TagNames/Nintendo.html new file mode 100644 index 0000000..d91a226 --- /dev/null +++ b/ExifTool/html/TagNames/Nintendo.html @@ -0,0 +1,64 @@ + + + + +Nintendo Tags + + + +

Nintendo Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x1101CameraInfo---> Nintendo CameraInfo Tags
+ +

Nintendo CameraInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ModelIDundef[4] 
8TimeStampint32u 
24InternalSerialNumberundef[4] 
40Parallaxfloat 
48Categoryint16u0x0 = (none) +
0x1000 = Mii +
0x2000 = Man +
0x4000 = Woman
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 25, 2014 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/OOXML.html b/ExifTool/html/TagNames/OOXML.html new file mode 100644 index 0000000..c82bc7f --- /dev/null +++ b/ExifTool/html/TagNames/OOXML.html @@ -0,0 +1,281 @@ + + + + +OOXML Tags + + + +

OOXML Tags

+

The Office Open XML (OOXML) format was introduced with Microsoft Office 2007 +and is used by file types such as DOCX, PPTX and XLSX. These are +essentially ZIP archives containing XML files. The table below lists some +tags which have been observed in OOXML documents, but ExifTool will extract +any tags found from XML files of the OOXML document properties ("docProps") +directory.

+ +

Tips:

+ +

1) Structural ZIP tags may be ignored (if desired) with --ZIP:all on the +command line.

+ +

2) Tags may be grouped by their document number in the ZIP archive with the +-g3 or -G3 option.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AppVersionno 
Applicationno 
Categoryno 
Charactersno 
CharactersWithSpacesno 
CheckedByno 
Clientno 
Companyno 
CreateDateno 
DateCompletedno 
Departmentno 
Destinationno 
Dispositionno 
Divisionno 
DocSecurityno0 = None +
1 = Password protected +
2 = Read-only recommended +
4 = Read-only enforced +
8 = Locked for annotations
DocumentNumberno 
Editorno 
ForwardTono 
Groupno 
HeadingPairsno 
HiddenSlidesno 
HyperlinkBaseno 
HyperlinksChangedno'false' = No +
'true' = Yes
Keywordsno 
Languageno 
LastModifiedByno 
LastPrintedno 
Linesno 
LinksUpToDateno'false' = No +
'true' = Yes
MMClipsno 
Mailstopno 
Managerno 
Matterno 
ModifyDateno 
Notesno 
Officeno 
Ownerno 
Pagesno 
Paragraphsno 
PresentationFormatno 
Projectno 
Publisherno 
Purposeno 
ReceivedFromno 
RecordedByno 
RecordedDateno 
Referenceno 
RevisionNumberno 
ScaleCropno'false' = No +
'true' = Yes
SharedDocno'false' = No +
'true' = Yes
Slidesno 
Sourceno 
Statusno 
TelephoneNumberno 
Templateno 
TitlesOfPartsno 
TotalEditTimeno 
Typistno 
Wordsno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 29, 2010 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Ogg.html b/ExifTool/html/TagNames/Ogg.html new file mode 100644 index 0000000..853c95b --- /dev/null +++ b/ExifTool/html/TagNames/Ogg.html @@ -0,0 +1,50 @@ + + + + +Ogg Tags + + + +

Ogg Tags

+

ExifTool extracts the following types of information from Ogg files. See +http://www.xiph.org/vorbis/doc/ for the Ogg specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'FLAC'FLAC---> FLAC Tags
'ID3'ID3---> ID3 Tags
'Opus'Opus---> Opus Tags
'theora'Theora---> Theora Tags
'vorbis'Vorbis---> Vorbis Tags
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 14, 2016 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Olympus.html b/ExifTool/html/TagNames/Olympus.html new file mode 100644 index 0000000..6fd49d9 --- /dev/null +++ b/ExifTool/html/TagNames/Olympus.html @@ -0,0 +1,3800 @@ + + + + +Olympus Tags + + + +

Olympus Tags

+

+Tags 0x0000 through 0x0103 are used by some older Olympus cameras, and are +the same as Konica/Minolta tags. These tags are also used for some models +from other brands such as Acer, BenQ, Epson, Hitachi, HP, Maginon, Minolta, +Pentax, Ricoh, Samsung, Sanyo, SeaLife, Sony, Supra and Vivitar. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000MakerNoteVersionundef 
0x0001MinoltaCameraSettingsOld---> Minolta CameraSettings Tags
0x0003MinoltaCameraSettings---> Minolta CameraSettings Tags
0x0040CompressedImageSizeint32u 
0x0081PreviewImageDatano 
0x0088PreviewImageStartno 
0x0089PreviewImageLengthno 
0x0100ThumbnailImageundef 
0x0104BodyFirmwareVersionstring 
0x0200SpecialModeint32u[3]~(3 numbers: 1. Shooting mode: 0=Normal, 2=Fast, 3=Panorama; +2. Sequence Number; 3. Panorama Direction: 1=Left-right, +2=Right-left, 3=Bottom-Top, 4=Top-Bottom)
0x0201Qualityint16u~(Quality values are decoded based on the CameraType tag. All types +represent SQ, HQ and SHQ as sequential integers, but in general +SX-type cameras start with a value of 0 for SQ while others start +with 1)
0x0202Macroint16u0 = Off +
1 = On +
2 = Super Macro
0x0203BWModeint16u0 = Off +
1 = On +
6 = (none)
0x0204DigitalZoomrational64u 
0x0205FocalPlaneDiagonalrational64u 
0x0206LensDistortionParamsint16s[6] 
0x0207CameraTypestring--> Olympus CameraType Values
0x0208TextInfo---> Olympus TextInfo Tags
0x0209CameraIDstring 
0x020bEpsonImageWidthint32u 
0x020cEpsonImageHeightint32u 
0x020dEpsonSoftwarestring 
0x0280PreviewImageint8u(found in ERF and JPG images from some Epson models)
0x0300PreCaptureFramesint16u 
0x0301WhiteBoardint16u 
0x0302OneTouchWBint16u0 = Off +
1 = On +
2 = On (Preset)
0x0303WhiteBalanceBracketint16u 
0x0304WhiteBalanceBiasint16u 
0x0400SensorAreaundef[8](found in Epson ERF images)
0x0401BlackLevelint32u[4](found in Epson ERF images)
0x0403SceneModeint16u + +
0 = Normal +
1 = Standard +
2 = Auto +
3 = Intelligent Auto +
4 = Portrait +
5 = Landscape+Portrait +
6 = Landscape +
7 = Night Scene +
8 = Night+Portrait +
9 = Sport +
10 = Self Portrait +
11 = Indoor +
12 = Beach & Snow +
13 = Beach +
14 = Snow +
15 = Self Portrait+Self Timer +
16 = Sunset +
17 = Cuisine +
18 = Documents +
19 = Candle
  20 = Fireworks +
21 = Available Light +
22 = Vivid +
23 = Underwater Wide1 +
24 = Underwater Macro +
25 = Museum +
26 = Behind Glass +
27 = Auction +
28 = Shoot & Select1 +
29 = Shoot & Select2 +
30 = Underwater Wide2 +
31 = Digital Image Stabilization +
32 = Face Portrait +
33 = Pet +
34 = Smile Shot +
35 = Quick Shutter +
43 = Hand-held Starlight +
100 = Panorama +
101 = Magic Filter +
103 = HDR
+
0x0404SerialNumberstring 
0x0405Firmwarestring 
0x0e00PrintIM---> PrintIM Tags
0x0f00DataDumpno 
0x0f01DataDump2no 
0x0f04ZoomedPreviewStartint32u* 
0x0f05ZoomedPreviewLengthint32u* 
0x0f06ZoomedPreviewSizeint16u[2] 
0x1000ShutterSpeedValuerational64s 
0x1001ISOValuerational64s 
0x1002ApertureValuerational64s 
0x1003BrightnessValuerational64s 
0x1004FlashModeint16u2 = On +
3 = Off
0x1005FlashDeviceint16u0 = None +
1 = Internal +
4 = External +
5 = Internal + External
0x1006ExposureCompensationrational64s 
0x1007SensorTemperatureint16s 
0x1008LensTemperatureint16s 
0x1009LightConditionint16u 
0x100aFocusRangeint16u0 = Normal +
1 = Macro
0x100bFocusModeint16u0 = Auto +
1 = Manual
0x100cManualFocusDistancerational64u 
0x100dZoomStepCountint16u 
0x100eFocusStepCountint16u 
0x100fSharpnessint16u0 = Normal +
1 = Hard +
2 = Soft
0x1010FlashChargeLevelint16u 
0x1011ColorMatrixint16u[9] 
0x1012BlackLevelint16u[4] 
0x1013ColorTemperatureBG?int16u 
0x1014ColorTemperatureRG?int16u 
0x1015WBModeint16u[2] + +
1 = Auto +
'1 0' = Auto +
'1 2' = Auto (2) +
'1 4' = Auto (4) +
'2 2' = 3000 Kelvin +
'2 3' = 3700 Kelvin
  '2 4' = 4000 Kelvin +
'2 5' = 4500 Kelvin +
'2 6' = 5500 Kelvin +
'2 7' = 6500 Kelvin +
'2 8' = 7500 Kelvin +
'3 0' = One-touch
+
0x1017RedBalanceint16u[2] 
0x1018BlueBalanceint16u[2] 
0x1019ColorMatrixNumberint16u 
0x101aSerialNumberstring 
0x101bExternalFlashAE1_0?int32u 
0x101cExternalFlashAE2_0?int32u 
0x101dInternalFlashAE1_0?int32u 
0x101eInternalFlashAE2_0?int32u 
0x101fExternalFlashAE1?int32u 
0x1020ExternalFlashAE2?int32u 
0x1021InternalFlashAE1?int32u 
0x1022InternalFlashAE2?int32u 
0x1023FlashExposureComprational64s 
0x1024InternalFlashTableint16u 
0x1025ExternalFlashGValuerational64s 
0x1026ExternalFlashBounceint16u0 = No +
1 = Yes
0x1027ExternalFlashZoomint16u 
0x1028ExternalFlashModeint16u 
0x1029Contrastint16u0 = High +
1 = Normal +
2 = Low
0x102aSharpnessFactorint16u 
0x102bColorControlint16u[6] 
0x102cValidBitsint16u[2] 
0x102dCoringFilterint16u 
0x102eOlympusImageWidthint32u 
0x102fOlympusImageHeightint32u 
0x1030SceneDetectint16u 
0x1031SceneArea?int32u[8] 
0x1033SceneDetectData?int32u[720] 
0x1034CompressionRatiorational64u 
0x1035PreviewImageValidint32u0 = No +
1 = Yes
0x1036PreviewImageStartint32u* 
0x1037PreviewImageLengthint32u* 
0x1038AFResultint16u 
0x1039CCDScanModeint16u0 = Interlaced +
1 = Progressive
0x103aNoiseReductionint16u0 = Off +
1 = On
0x103bFocusStepInfinityint16u 
0x103cFocusStepNearint16u 
0x103dLightValueCenterrational64s 
0x103eLightValuePeripheryrational64s 
0x103fFieldCount?int16u 
0x2010Equipment +
EquipmentIFD
-
-
--> Olympus Equipment Tags +
--> Olympus Equipment Tags
0x2020CameraSettings +
CameraSettingsIFD
-
-
--> Olympus CameraSettings Tags +
--> Olympus CameraSettings Tags
0x2030RawDevelopment +
RawDevelopmentIFD
-
-
--> Olympus RawDevelopment Tags +
--> Olympus RawDevelopment Tags
0x2031RawDev2 +
RawDev2IFD
-
-
--> Olympus RawDevelopment2 Tags +
--> Olympus RawDevelopment2 Tags
0x2040ImageProcessing +
ImageProcessingIFD
-
-
--> Olympus ImageProcessing Tags +
--> Olympus ImageProcessing Tags
0x2050FocusInfo +
FocusInfoIFD +
CameraParameters
-
-
undef
--> Olympus FocusInfo Tags +
--> Olympus FocusInfo Tags
0x2100Olympus2100 +
Olympus2100IFD
-
-
--> Olympus FE Tags +
--> Olympus FE Tags
0x2200Olympus2200 +
Olympus2200IFD
-
-
--> Olympus FE Tags +
--> Olympus FE Tags
0x2300Olympus2300 +
Olympus2300IFD
-
-
--> Olympus FE Tags +
--> Olympus FE Tags
0x2400Olympus2400 +
Olympus2400IFD
-
-
--> Olympus FE Tags +
--> Olympus FE Tags
0x2500Olympus2500 +
Olympus2500IFD
-
-
--> Olympus FE Tags +
--> Olympus FE Tags
0x2600Olympus2600 +
Olympus2600IFD
-
-
--> Olympus FE Tags +
--> Olympus FE Tags
0x2700Olympus2700 +
Olympus2700IFD
-
-
--> Olympus FE Tags +
--> Olympus FE Tags
0x2800Olympus2800 +
Olympus2800IFD
-
-
--> Olympus FE Tags +
--> Olympus FE Tags
0x2900Olympus2900 +
Olympus2900IFD
-
-
--> Olympus FE Tags +
--> Olympus FE Tags
0x3000RawInfo +
RawInfoIFD
-
-
--> Olympus RawInfo Tags +
--> Olympus RawInfo Tags
0x4000MainInfo +
MainInfoIFD
-
-
--> Olympus Tags +
--> Olympus Tags
0x5000UnknownInfo +
UnknownInfoIFD
-
-
--> Olympus UnknownInfo Tags +
--> Olympus UnknownInfo Tags
+ +

Olympus CameraType Values

+

These values are currently decoded only for Olympus models. Models with +Olympus-style maker notes from other brands such as Acer, BenQ, Hitachi, HP, +Premier, Konica-Minolta, Maginon, Ricoh, Rollei, SeaLife, Sony, Supra, +Vivitar are not listed.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueCameraTypeValueCameraTypeValueCameraType
'D4028'= X-2,C-50Z'D4401'= XZ-1'S0019'= E-P1
'D4029'= E-20,E-20N,E-20P'D4402'= uT6010,ST6010'S0023'= E-620
'D4034'= C720UZ'D4406'= u7010,S7010 / u7020,S7020'S0026'= E-P2
'D4040'= E-1'D4407'= FE4010,X930'S0027'= E-PL1
'D4041'= E-300'D4408'= X560WP'S0029'= E-450
'D4083'= C2Z,D520Z,C220Z'D4409'= FE26,X21'S0030'= E-600
'D4106'= u20D,S400D,u400D'D4410'= FE4000,X920,X925'S0032'= E-P3
'D4120'= X-1'D4411'= FE46,X41,X42'S0033'= E-5
'D4122'= u10D,S300D,u300D'D4412'= FE5020,X935'S0034'= E-PL2
'D4125'= AZ-1'D4413'= uTough-3000'S0036'= E-M5
'D4141'= C150,D390'D4414'= StylusTough-6020'S0038'= E-PL3
'D4193'= C-5000Z'D4415'= StylusTough-8010'S0039'= E-PM1
'D4194'= X-3,C-60Z'D4417'= u5010,S5010'S0040'= E-PL1s
'D4199'= u30D,S410D,u410D'D4418'= u7040,S7040'S0042'= E-PL5
'D4205'= X450,D535Z,C370Z'D4419'= u9010,S9010'S0043'= E-PM2
'D4210'= C160,D395'D4423'= FE4040'S0044'= E-P5
'D4211'= C725UZ'D4424'= FE47,X43'S0045'= E-PL6
'D4213'= FerrariMODEL2003'D4426'= FE4030,X950'S0046'= E-PL7
'D4216'= u15D'D4428'= FE5030,X965,X960'S0047'= E-M1
'D4217'= u25D'D4430'= u7030,S7030'S0051'= E-M10
'D4220'= u-miniD,Stylus V'D4432'= SP600UZ'S0052'= E-M5MarkII
'D4221'= u40D,S500,uD500'D4434'= SP800UZ'S0059'= E-M10MarkII
'D4231'= FerrariMODEL2004'D4439'= FE4020,X940'S0061'= PEN-F
'D4240'= X500,D590Z,C470Z'D4442'= FE5035'S0065'= E-PL8
'D4244'= uD800,S800'D4448'= FE4050,X970'S0067'= E-M1MarkII
'D4256'= u720SW,S720SW'D4450'= FE5050,X985'S0068'= E-M10MarkIII
'D4261'= X600,D630,FE5500'D4454'= u-7050'S0076'= E-PL9
'D4262'= uD600,S600'D4464'= T10,X27'S0080'= E-M1X
'D4301'= u810/S810'D4470'= FE5040,X980'S0085'= E-PL10
'D4302'= u710,S710'D4472'= TG-310'S0089'= E-M5MarkIII
'D4303'= u700,S700'D4474'= TG-610'S0092'= E-M1MarkIII
'D4304'= FE100,X710'D4476'= TG-810'S0093'= E-P7
'D4305'= FE110,X705'D4478'= VG145,VG140,D715'S0095'= OM-1
'D4310'= FE-130,X-720'D4479'= VG130,D710'S0101'= OM-5
'D4311'= FE-140,X-725'D4480'= VG120,D705'SR45'= D220
'D4312'= FE150,X730'D4482'= VR310,D720'SR55'= D320L
'D4313'= FE160,X735'D4484'= VR320,D725'SR83'= D340L
'D4314'= u740,S740'D4486'= VR330,D730'SR85'= C830L,D340R
'D4315'= u750,S750'D4488'= VG110,D700'SR852'= C860L,D360L
'D4316'= u730/S730'D4490'= SP-610UZ'SR872'= C900Z,D400Z
'D4317'= FE115,X715'D4492'= SZ-10'SR874'= C960Z,D460Z
'D4321'= SP550UZ'D4494'= SZ-20'SR951'= C2000Z
'D4322'= SP510UZ'D4496'= SZ-30MR'SR952'= C21
'D4324'= FE170,X760'D4498'= SP-810UZ'SR953'= C21T.commu
'D4326'= FE200'D4500'= SZ-11'SR954'= C2020Z
'D4327'= FE190/X750'D4504'= TG-615'SR955'= C990Z,D490Z
'D4328'= u760,S760'D4508'= TG-620'SR956'= C211Z
'D4330'= FE180/X745'D4510'= TG-820'SR959'= C990ZS,D490Z
'D4331'= u1000/S1000'D4512'= TG-1'SR95A'= C2100UZ
'D4332'= u770SW,S770SW'D4516'= SH-21'SR971'= C100,D370
'D4333'= FE240/X795'D4519'= SZ-14'SR973'= C2,D230
'D4334'= FE210,X775'D4520'= SZ-31MR'SX151'= E100RS
'D4336'= FE230/X790'D4521'= SH-25MR'SX351'= C3000Z / C3030Z
'D4337'= FE220,X785'D4523'= SP-720UZ'SX354'= C3040Z
'D4338'= u725SW,S725SW'D4529'= VG170'SX355'= C2040Z
'D4339'= FE250/X800'D4531'= XZ-2'SX357'= C700UZ
'D4341'= u780,S780'D4535'= SP-620UZ'SX358'= C200Z,D510Z
'D4343'= u790SW,S790SW'D4536'= TG-320'SX374'= C3100Z,C3020Z
'D4344'= u1020,S1020'D4537'= VR340,D750'SX552'= C4040Z
'D4346'= FE15,X10'D4538'= VG160,X990,D745'SX553'= C40Z,D40Z
'D4348'= FE280,X820,C520'D4541'= SZ-12'SX556'= C730UZ
'D4349'= FE300,X830'D4545'= VH410'SX558'= C5050Z
'D4350'= u820,S820'D4546'= XZ-10'SX571'= C120,D380
'D4351'= u1200,S1200'D4547'= TG-2'SX574'= C300Z,D550Z
'D4352'= FE270,X815,C510'D4548'= TG-830'SX575'= C4100Z,C4000Z
'D4353'= u795SW,S795SW'D4549'= TG-630'SX751'= X200,D560Z,C350Z
'D4354'= u1030SW,S1030SW'D4550'= SH-50'SX752'= X300,D565Z,C450Z
'D4355'= SP560UZ'D4553'= SZ-16,DZ-105'SX753'= C750UZ
'D4356'= u1010,S1010'D4562'= SP-820UZ'SX754'= C740UZ
'D4357'= u830,S830'D4566'= SZ-15'SX755'= C755UZ
'D4359'= u840,S840'D4572'= STYLUS1'SX756'= C5060WZ
'D4360'= FE350WIDE,X865'D4574'= TG-3'SX757'= C8080WZ
'D4361'= u850SW,S850SW'D4575'= TG-850'SX758'= X350,D575Z,C360Z
'D4362'= FE340,X855,C560'D4579'= SP-100EE'SX759'= X400,D580Z,C460Z
'D4363'= FE320,X835,C540'D4580'= SH-60'SX75A'= AZ-2ZOOM
'D4364'= SP570UZ'D4581'= SH-1'SX75B'= D595Z,C500Z
'D4366'= FE330,X845,C550'D4582'= TG-835'SX75C'= X550,D545Z,C480Z
'D4368'= FE310,X840,C530'D4585'= SH-2 / SH-3'SX75D'= IR-300
'D4370'= u1050SW,S1050SW'D4586'= TG-4'SX75F'= C55Z,C5500Z
'D4371'= u1060,S1060'D4587'= TG-860'SX75G'= C170,D425
'D4372'= FE370,X880,C575'D4591'= TG-870'SX75J'= C180,D435
'D4374'= SP565UZ'D4593'= TG-5'SX771'= C760UZ
'D4377'= u1040,S1040'D4603'= TG-6'SX772'= C770UZ
'D4378'= FE360,X875,C570'D4809'= C2500L'SX773'= C745UZ
'D4379'= FE20,X15,C25'D4842'= E-10'SX774'= X250,D560Z,C350Z
'D4380'= uT6000,ST6000'D4856'= C-1'SX775'= X100,D540Z,C310Z
'D4381'= uT8000,ST8000'D4857'= C-1Z,D-150Z'SX776'= C460ZdelSol
'D4382'= u9000,S9000'DCHC'= D500L'SX777'= C765UZ
'D4384'= SP590UZ'DCHT'= D600L / D620L'SX77A'= D555Z,C315Z
'D4385'= FE3010,X895'K0055'= AIR-A01'SX851'= C7070WZ
'D4386'= FE3000,X890'S0003'= E-330'SX852'= C70Z,C7000Z
'D4387'= FE35,X30'S0004'= E-500'SX853'= SP500UZ
'D4388'= u550WP,S550WP'S0009'= E-400'SX854'= SP310
'D4390'= FE5000,X905'S0010'= E-510'SX855'= SP350
'D4391'= u5000'S0011'= E-3'SX873'= SP320
'D4392'= u7000,S7000'S0013'= E-410'SX875'= FE180/X745
'D4396'= FE5010,X915'S0016'= E-420'SX876'= FE190/X750
'D4397'= FE25,X20'S0017'= E-30  
'D4398'= FE45,X40'S0018'= E-520  
+ +

Olympus TextInfo Tags

+

This information is in text format (similar to APP12 information, but with +spaces instead of linefeeds). Below are tags which have been observed, but +any information found here will be extracted, even if the tag is not listed.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Resolution'Resolutionno 
'Type'CameraTypeno--> Olympus CameraType Values
+ +

Olympus Equipment Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000EquipmentVersionundef[4] 
0x0100CameraType2string[6]--> Olympus CameraType Values
0x0101SerialNumberstring[32] 
0x0102InternalSerialNumberstring[32](16 digits: 0-3=model, 4=year, 5-6=month, 8-12=unit number)
0x0103FocalPlaneDiagonalrational64u 
0x0104BodyFirmwareVersionint32u 
0x0201LensTypeint8u[6]--> Olympus LensType Values +
(6 numbers: 1. Make, 2. Unknown, 3. Model, 4. Sub-model, 5-6. Unknown. Only +the Make, Model and Sub-model are used to identify the lens type)
0x0202LensSerialNumberstring[32] 
0x0203LensModelstring 
0x0204LensFirmwareVersionint32u 
0x0205MaxApertureAtMinFocalint16u 
0x0206MaxApertureAtMaxFocalint16u 
0x0207MinFocalLengthint16u 
0x0208MaxFocalLengthint16u 
0x020aMaxApertureint16u 
0x020bLensPropertiesint16u 
0x0301Extenderint8u[6](6 numbers: 1. Make, 2. Unknown, 3. Model, 4. Sub-model, 5-6. Unknown. Only +the Make and Model are used to identify the extender) +
'0 00' = None +
'0 04' = Olympus Zuiko Digital EC-14 1.4x Teleconverter +
'0 08' = Olympus EX-25 Extension Tube +
'0 10' = Olympus Zuiko Digital EC-20 2.0x Teleconverter
0x0302ExtenderSerialNumberstring[32] 
0x0303ExtenderModelstring 
0x0304ExtenderFirmwareVersionint32u 
0x0403ConversionLensstring 
0x1000FlashTypeint16u0 = None +
2 = Simple E-System +
3 = E-System +
4 = E-System (body powered)
0x1001FlashModelint16u + +
0 = None +
1 = FL-20 +
2 = FL-50 +
3 = RF-11 +
4 = TF-22 +
5 = FL-36
  6 = FL-50R +
7 = FL-36R +
9 = FL-14 +
11 = FL-600R +
13 = FL-LM3 +
15 = FL-900R
+
0x1002FlashFirmwareVersionint32u 
0x1003FlashSerialNumberstring[32] 
+ +

Olympus LensType Values

+

The numerical values below are given in hexadecimal. (Prior to ExifTool +9.15 these were in decimal.)

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLensType
'0 00 00'= None
'0 01 00'= Olympus Zuiko Digital ED 50mm F2.0 Macro
'0 01 01'= Olympus Zuiko Digital 40-150mm F3.5-4.5
'0 01 10'= Olympus M.Zuiko Digital ED 14-42mm F3.5-5.6
'0 02 00'= Olympus Zuiko Digital ED 150mm F2.0
'0 02 10'= Olympus M.Zuiko Digital 17mm F2.8 Pancake
'0 03 00'= Olympus Zuiko Digital ED 300mm F2.8
'0 03 10'= Olympus M.Zuiko Digital ED 14-150mm F4.0-5.6 [II]
'0 04 10'= Olympus M.Zuiko Digital ED 9-18mm F4.0-5.6
'0 05 00'= Olympus Zuiko Digital 14-54mm F2.8-3.5
'0 05 01'= Olympus Zuiko Digital Pro ED 90-250mm F2.8
'0 05 10'= Olympus M.Zuiko Digital ED 14-42mm F3.5-5.6 L
'0 06 00'= Olympus Zuiko Digital ED 50-200mm F2.8-3.5
'0 06 01'= Olympus Zuiko Digital ED 8mm F3.5 Fisheye
'0 06 10'= Olympus M.Zuiko Digital ED 40-150mm F4.0-5.6
'0 07 00'= Olympus Zuiko Digital 11-22mm F2.8-3.5
'0 07 01'= Olympus Zuiko Digital 18-180mm F3.5-6.3
'0 07 10'= Olympus M.Zuiko Digital ED 12mm F2.0
'0 08 01'= Olympus Zuiko Digital 70-300mm F4.0-5.6
'0 08 10'= Olympus M.Zuiko Digital ED 75-300mm F4.8-6.7
'0 09 10'= Olympus M.Zuiko Digital 14-42mm F3.5-5.6 II
'0 10 01'= Kenko Tokina Reflex 300mm F6.3 MF Macro
'0 10 10'= Olympus M.Zuiko Digital ED 12-50mm F3.5-6.3 EZ
'0 11 10'= Olympus M.Zuiko Digital 45mm F1.8
'0 12 10'= Olympus M.Zuiko Digital ED 60mm F2.8 Macro
'0 13 10'= Olympus M.Zuiko Digital 14-42mm F3.5-5.6 II R
'0 14 10'= Olympus M.Zuiko Digital ED 40-150mm F4.0-5.6 R
'0 15 00'= Olympus Zuiko Digital ED 7-14mm F4.0
'0 15 10'= Olympus M.Zuiko Digital ED 75mm F1.8
'0 16 10'= Olympus M.Zuiko Digital 17mm F1.8
'0 17 00'= Olympus Zuiko Digital Pro ED 35-100mm F2.0
'0 18 00'= Olympus Zuiko Digital 14-45mm F3.5-5.6
'0 18 10'= Olympus M.Zuiko Digital ED 75-300mm F4.8-6.7 II
'0 19 10'= Olympus M.Zuiko Digital ED 12-40mm F2.8 Pro
'0 20 00'= Olympus Zuiko Digital 35mm F3.5 Macro
'0 20 10'= Olympus M.Zuiko Digital ED 40-150mm F2.8 Pro
'0 21 10'= Olympus M.Zuiko Digital ED 14-42mm F3.5-5.6 EZ
'0 22 00'= Olympus Zuiko Digital 17.5-45mm F3.5-5.6
'0 22 10'= Olympus M.Zuiko Digital 25mm F1.8
'0 23 00'= Olympus Zuiko Digital ED 14-42mm F3.5-5.6
'0 23 10'= Olympus M.Zuiko Digital ED 7-14mm F2.8 Pro
'0 24 00'= Olympus Zuiko Digital ED 40-150mm F4.0-5.6
'0 24 10'= Olympus M.Zuiko Digital ED 300mm F4.0 IS Pro
'0 25 10'= Olympus M.Zuiko Digital ED 8mm F1.8 Fisheye Pro
'0 26 10'= Olympus M.Zuiko Digital ED 12-100mm F4.0 IS Pro
'0 27 10'= Olympus M.Zuiko Digital ED 30mm F3.5 Macro
'0 28 10'= Olympus M.Zuiko Digital ED 25mm F1.2 Pro
'0 29 10'= Olympus M.Zuiko Digital ED 17mm F1.2 Pro
'0 30 00'= Olympus Zuiko Digital ED 50-200mm F2.8-3.5 SWD
'0 30 10'= Olympus M.Zuiko Digital ED 45mm F1.2 Pro
'0 31 00'= Olympus Zuiko Digital ED 12-60mm F2.8-4.0 SWD
'0 32 00'= Olympus Zuiko Digital ED 14-35mm F2.0 SWD
'0 32 10'= Olympus M.Zuiko Digital ED 12-200mm F3.5-6.3
'0 33 00'= Olympus Zuiko Digital 25mm F2.8
'0 33 10'= Olympus M.Zuiko Digital 150-400mm F4.5 TC1.25x IS Pro
'0 34 00'= Olympus Zuiko Digital ED 9-18mm F4.0-5.6
'0 34 10'= Olympus M.Zuiko Digital ED 12-45mm F4.0 Pro
'0 35 00'= Olympus Zuiko Digital 14-54mm F2.8-3.5 II
'0 35 10'= Olympus M.Zuiko 100-400mm F5.0-6.3
'0 36 10'= Olympus M.Zuiko Digital ED 8-25mm F4 Pro
'0 37 10'= Olympus M.Zuiko Digital ED 40-150mm F4.0 Pro
'0 39 10'= Olympus M.Zuiko Digital ED 90mm F3.5 Macro IS Pro
'1 01 00'= Sigma 18-50mm F3.5-5.6 DC
'1 01 10'= Sigma 30mm F2.8 EX DN
'1 02 00'= Sigma 55-200mm F4.0-5.6 DC
'1 02 10'= Sigma 19mm F2.8 EX DN
'1 03 00'= Sigma 18-125mm F3.5-5.6 DC
'1 03 10'= Sigma 30mm F2.8 DN | A
'1 04 00'= Sigma 18-125mm F3.5-5.6 DC
'1 04 10'= Sigma 19mm F2.8 DN | A
'1 05 00'= Sigma 30mm F1.4 EX DC HSM
'1 05 10'= Sigma 60mm F2.8 DN | A
'1 06 00'= Sigma APO 50-500mm F4.0-6.3 EX DG HSM
'1 06 10'= Sigma 30mm F1.4 DC DN | C
'1 07 00'= Sigma Macro 105mm F2.8 EX DG
'1 07 10'= Sigma 16mm F1.4 DC DN | C (017)
'1 08 00'= Sigma APO Macro 150mm F2.8 EX DG HSM
'1 09 00'= Sigma 18-50mm F2.8 EX DC Macro
'1 10 00'= Sigma 24mm F1.8 EX DG Aspherical Macro
'1 11 00'= Sigma APO 135-400mm F4.5-5.6 DG
'1 12 00'= Sigma APO 300-800mm F5.6 EX DG HSM
'1 13 00'= Sigma 30mm F1.4 EX DC HSM
'1 14 00'= Sigma APO 50-500mm F4.0-6.3 EX DG HSM
'1 15 00'= Sigma 10-20mm F4.0-5.6 EX DC HSM
'1 16 00'= Sigma APO 70-200mm F2.8 II EX DG Macro HSM
'1 17 00'= Sigma 50mm F1.4 EX DG HSM
'2 01 00'= Leica D Vario Elmarit 14-50mm F2.8-3.5 Asph.
'2 01 10'= Lumix G Vario 14-45mm F3.5-5.6 Asph. Mega OIS
'2 02 00'= Leica D Summilux 25mm F1.4 Asph.
'2 02 10'= Lumix G Vario 45-200mm F4.0-5.6 Mega OIS
'2 03 00'= Leica D Vario Elmar 14-50mm F3.8-5.6 Asph. Mega OIS
'2 03 01'= Leica D Vario Elmar 14-50mm F3.8-5.6 Asph.
'2 03 10'= Lumix G Vario HD 14-140mm F4.0-5.8 Asph. Mega OIS
'2 04 00'= Leica D Vario Elmar 14-150mm F3.5-5.6
'2 04 10'= Lumix G Vario 7-14mm F4.0 Asph.
'2 05 10'= Lumix G 20mm F1.7 Asph.
'2 06 10'= Leica DG Macro-Elmarit 45mm F2.8 Asph. Mega OIS
'2 07 10'= Lumix G Vario 14-42mm F3.5-5.6 Asph. Mega OIS
'2 08 10'= Lumix G Fisheye 8mm F3.5
'2 09 10'= Lumix G Vario 100-300mm F4.0-5.6 Mega OIS
'2 10 10'= Lumix G 14mm F2.5 Asph.
'2 11 10'= Lumix G 12.5mm F12 3D
'2 12 10'= Leica DG Summilux 25mm F1.4 Asph.
'2 13 10'= Lumix G X Vario PZ 45-175mm F4.0-5.6 Asph. Power OIS
'2 14 10'= Lumix G X Vario PZ 14-42mm F3.5-5.6 Asph. Power OIS
'2 15 10'= Lumix G X Vario 12-35mm F2.8 Asph. Power OIS
'2 16 10'= Lumix G Vario 45-150mm F4.0-5.6 Asph. Mega OIS
'2 17 10'= Lumix G X Vario 35-100mm F2.8 Power OIS
'2 18 10'= Lumix G Vario 14-42mm F3.5-5.6 II Asph. Mega OIS
'2 19 10'= Lumix G Vario 14-140mm F3.5-5.6 Asph. Power OIS
'2 20 10'= Lumix G Vario 12-32mm F3.5-5.6 Asph. Mega OIS
'2 21 10'= Leica DG Nocticron 42.5mm F1.2 Asph. Power OIS
'2 22 10'= Leica DG Summilux 15mm F1.7 Asph.
'2 23 10'= Lumix G Vario 35-100mm F4.0-5.6 Asph. Mega OIS
'2 24 10'= Lumix G Macro 30mm F2.8 Asph. Mega OIS
'2 25 10'= Lumix G 42.5mm F1.7 Asph. Power OIS
'2 26 10'= Lumix G 25mm F1.7 Asph.
'2 27 10'= Leica DG Vario-Elmar 100-400mm F4.0-6.3 Asph. Power OIS
'2 28 10'= Lumix G Vario 12-60mm F3.5-5.6 Asph. Power OIS
'2 29 10'= Leica DG Summilux 12mm F1.4 Asph.
'2 30 10'= Leica DG Vario-Elmarit 12-60mm F2.8-4 Asph. Power OIS
'2 31 10'= Lumix G Vario 45-200mm F4.0-5.6 II
'2 32 10'= Lumix G Vario 100-300mm F4.0-5.6 II
'2 33 10'= Lumix G X Vario 12-35mm F2.8 II Asph. Power OIS
'2 34 10'= Lumix G Vario 35-100mm F2.8 II
'2 35 10'= Leica DG Vario-Elmarit 8-18mm F2.8-4 Asph.
'2 36 10'= Leica DG Elmarit 200mm F2.8 Power OIS
'2 37 10'= Leica DG Vario-Elmarit 50-200mm F2.8-4 Asph. Power OIS
'2 38 10'= Leica DG Vario-Summilux 10-25mm F1.7 Asph.
'2 40 10'= Leica DG Vario-Summilux 25-50mm F1.7 Asph.
'3 01 00'= Leica D Vario Elmarit 14-50mm F2.8-3.5 Asph.
'3 02 00'= Leica D Summilux 25mm F1.4 Asph.
'5 01 10'= Tamron 14-150mm F3.5-5.8 Di III
'24 01 10'= Venus Optics Laowa 50mm F2.8 2x Macro
+ +

Olympus CameraSettings Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000CameraSettingsVersionundef[4] 
0x0100PreviewImageValidint32u0 = No +
1 = Yes
0x0101PreviewImageStartint32u* 
0x0102PreviewImageLengthint32u* 
0x0200ExposureModeint16u1 = Manual +
2 = Program +
3 = Aperture-priority AE +
4 = Shutter speed priority AE +
5 = Program-shift
0x0201AELockint16u0 = Off +
1 = On
0x0202MeteringModeint16u +
2 = Center-weighted average +
3 = Spot +
5 = ESP +
261 = Pattern+AF +
515 = Spot+Highlight control +
1027 = Spot+Shadow control
+
0x0203ExposureShiftrational64s 
0x0204NDFilteryes0 = Off +
1 = On
0x0300MacroModeint16u0 = Off +
1 = On +
2 = Super Macro
0x0301FocusModeint16u[n](1 or 2 values) +
[Value 0]
+
0 = Single AF +
1 = Sequential shooting AF +
2 = Continuous AF +
3 = Multi AF +
4 = Face Detect +
10 = MF
+[Value 1] +
0x0 = (none) +
Bit 0 = S-AF +
Bit 2 = C-AF +
Bit 4 = MF +
Bit 5 = Face Detect +
Bit 6 = Imager AF +
Bit 7 = Live View Magnification Frame +
Bit 8 = AF sensor +
Bit 9 = Starry Sky AF
+
0x0302FocusProcessint16u[n](1 or 2 values) +
[Value 0] +
0 = AF Not Used +
1 = AF Used
0x0303AFSearchint16u0 = Not Ready +
1 = Ready
0x0304AFAreasint32u[64]~(coordinates range from 0 to 255)
0x0305AFPointSelectedrational64s[5](coordinates expressed as a percent)
0x0306AFFineTuneint8u0 = Off +
1 = On
0x0307AFFineTuneAdjint16s[3] 
0x0308FocusBracketStepSizeint8u 
0x0309AISubjectTrackingModeint16u[Value 0] + +
0 = Off +
1 = Motorsports +
2 = Airplanes
  3 = Trains +
4 = Birds +
5 = Dogs & Cats
+[Value 1] +
0 = Object Not Found +
1 = Object Found
0x0400FlashModeint16u + +
0x0 = Off +
Bit 0 = On +
Bit 1 = Fill-in +
Bit 2 = Red-eye
  Bit 3 = Slow-sync +
Bit 4 = Forced On +
Bit 5 = 2nd Curtain
+
0x0401FlashExposureComprational64s 
0x0403FlashRemoteControlint16u + +
0x0 = Off +
0x1 = Channel 1, Low +
0x2 = Channel 2, Low +
0x3 = Channel 3, Low +
0x4 = Channel 4, Low +
0x9 = Channel 1, Mid +
0xa = Channel 2, Mid
  0xb = Channel 3, Mid +
0xc = Channel 4, Mid +
0x11 = Channel 1, High +
0x12 = Channel 2, High +
0x13 = Channel 3, High +
0x14 = Channel 4, High
+
0x0404FlashControlModeint16u[n](3 or 4 values) +
[Value 0] +
0 = Off +
3 = TTL +
4 = Auto +
5 = Manual
0x0405FlashIntensityrational64s[n](3 or 4 values) +
'undef undef undef' = n/a +
'undef undef undef undef' = n/a (x4)
0x0406ManualFlashStrengthrational64s[n](3 or 4 values) +
'undef undef undef' = n/a +
'undef undef undef undef' = n/a (x4)
0x0500WhiteBalance2int16u +
0 = Auto +
1 = Auto (Keep Warm Color Off) +
16 = 7500K (Fine Weather with Shade) +
17 = 6000K (Cloudy) +
18 = 5300K (Fine Weather) +
20 = 3000K (Tungsten light) +
21 = 3600K (Tungsten light-like) +
22 = Auto Setup +
23 = 5500K (Flash) +
33 = 6600K (Daylight fluorescent) +
34 = 4500K (Neutral white fluorescent) +
35 = 4000K (Cool white fluorescent) +
36 = White Fluorescent +
48 = 3600K (Tungsten light-like) +
67 = Underwater +
256 = One Touch WB 1 +
257 = One Touch WB 2 +
258 = One Touch WB 3 +
259 = One Touch WB 4 +
512 = Custom WB 1 +
513 = Custom WB 2 +
514 = Custom WB 3 +
515 = Custom WB 4
+
0x0501WhiteBalanceTemperatureint16u 
0x0502WhiteBalanceBracketint16s 
0x0503CustomSaturationint16s[3]~(3 numbers: 1. CS Value, 2. Min, 3. Max)
0x0504ModifiedSaturationint16u0 = Off +
1 = CM1 (Red Enhance) +
2 = CM2 (Green Enhance) +
3 = CM3 (Blue Enhance) +
4 = CM4 (Skin Tones)
0x0505ContrastSettingint16s[3](value, min, max)
0x0506SharpnessSettingint16s[3](value, min, max)
0x0507ColorSpaceint16u0 = sRGB +
1 = Adobe RGB +
2 = Pro Photo RGB
0x0509SceneModeint16u + +
0 = Standard +
6 = Auto +
7 = Sport +
8 = Portrait +
9 = Landscape+Portrait +
10 = Landscape +
11 = Night Scene +
12 = Self Portrait +
13 = Panorama +
14 = 2 in 1 +
15 = Movie +
16 = Landscape+Portrait +
17 = Night+Portrait +
18 = Indoor +
19 = Fireworks +
20 = Sunset +
21 = Beauty Skin +
22 = Macro +
23 = Super Macro +
24 = Food +
25 = Documents +
26 = Museum +
27 = Shoot & Select +
28 = Beach & Snow +
29 = Self Protrait+Timer +
30 = Candle +
31 = Available Light +
32 = Behind Glass +
33 = My Mode +
34 = Pet +
35 = Underwater Wide1
  36 = Underwater Macro +
37 = Shoot & Select1 +
38 = Shoot & Select2 +
39 = High Key +
40 = Digital Image Stabilization +
41 = Auction +
42 = Beach +
43 = Snow +
44 = Underwater Wide2 +
45 = Low Key +
46 = Children +
47 = Vivid +
48 = Nature Macro +
49 = Underwater Snapshot +
50 = Shooting Guide +
54 = Face Portrait +
57 = Bulb +
59 = Smile Shot +
60 = Quick Shutter +
63 = Slow Shutter +
64 = Bird Watching +
65 = Multiple Exposure +
66 = e-Portrait +
67 = Soft Background Shot +
142 = Hand-held Starlight +
154 = HDR +
197 = Panning +
203 = Light Trails +
204 = Backlight HDR +
205 = Silent +
206 = Multi Focus Shot
+
0x050aNoiseReductionint16u0x0 = (none) +
Bit 0 = Noise Reduction +
Bit 1 = Noise Filter +
Bit 2 = Noise Filter (ISO Boost) +
Bit 3 = Auto
0x050bDistortionCorrectionint16u0 = Off +
1 = On
0x050cShadingCompensationint16u0 = Off +
1 = On
0x050dCompressionFactorrational64u 
0x050fGradationint16s[n](3 or 4 values) +
[Values 0-2] +
'-1 -1 1' = Low Key +
'0 -1 1' = Normal +
'0 0 0' = n/a +
'1 -1 1' = High Key +
[Value 3] +
0 = User-Selected +
1 = Auto-Override
0x0520PictureModeint16u[n](1 or 2 values) +
[Value 0]
+
1 = Vivid +
2 = Natural +
3 = Muted +
4 = Portrait +
5 = i-Enhance +
6 = e-Portrait +
7 = Color Creator +
9 = Color Profile 1 +
10 = Color Profile 2 +
11 = Color Profile 3 +
12 = Monochrome Profile 1 +
13 = Monochrome Profile 2 +
14 = Monochrome Profile 3 +
256 = Monotone +
512 = Sepia
+
0x0521PictureModeSaturationint16s[3](value, min, max)
0x0522PictureModeHue?int16s 
0x0523PictureModeContrastint16s[3](value, min, max)
0x0524PictureModeSharpnessint16s[3](value, min, max)
0x0525PictureModeBWFilterint16s + +
0 = n/a +
1 = Neutral +
2 = Yellow
  3 = Orange +
4 = Red +
5 = Green
+
0x0526PictureModeToneint16s + +
0 = n/a +
1 = Neutral +
2 = Sepia
  3 = Blue +
4 = Purple +
5 = Green
+
0x0527NoiseFilterint16s[3]'-1 -2 1' = Low +
'-2 -2 1' = Off +
'0 -2 1' = Standard +
'0 0 0' = n/a +
'1 -2 1' = High
0x0529ArtFilterint16u[4][Value 0] + +
0 = Off +
1 = Soft Focus +
2 = Pop Art +
3 = Pale & Light Color +
4 = Light Tone +
5 = Pin Hole +
6 = Grainy Film +
9 = Diorama +
10 = Cross Process +
12 = Fish Eye +
13 = Drawing +
14 = Gentle Sepia +
15 = Pale & Light Color II +
16 = Pop Art II +
17 = Pin Hole II +
18 = Pin Hole III +
19 = Grainy Film II +
20 = Dramatic Tone +
21 = Punk
  22 = Soft Focus 2 +
23 = Sparkle +
24 = Watercolor +
25 = Key Line +
26 = Key Line II +
27 = Miniature +
28 = Reflection +
29 = Fragmented +
31 = Cross Process II +
32 = Dramatic Tone II +
33 = Watercolor I +
34 = Watercolor II +
35 = Diorama II +
36 = Vintage +
37 = Vintage II +
38 = Vintage III +
39 = Partial Color +
40 = Partial Color II +
41 = Partial Color III
+
0x052cMagicFilterint16u[4][Value 0] + +
0 = Off +
1 = Soft Focus +
2 = Pop Art +
3 = Pale & Light Color +
4 = Light Tone +
5 = Pin Hole +
6 = Grainy Film +
9 = Diorama +
10 = Cross Process +
12 = Fish Eye +
13 = Drawing +
14 = Gentle Sepia +
15 = Pale & Light Color II +
16 = Pop Art II +
17 = Pin Hole II +
18 = Pin Hole III +
19 = Grainy Film II +
20 = Dramatic Tone +
21 = Punk
  22 = Soft Focus 2 +
23 = Sparkle +
24 = Watercolor +
25 = Key Line +
26 = Key Line II +
27 = Miniature +
28 = Reflection +
29 = Fragmented +
31 = Cross Process II +
32 = Dramatic Tone II +
33 = Watercolor I +
34 = Watercolor II +
35 = Diorama II +
36 = Vintage +
37 = Vintage II +
38 = Vintage III +
39 = Partial Color +
40 = Partial Color II +
41 = Partial Color III
+
0x052dPictureModeEffectint16s[3]'-1 -1 1' = Low +
'0 -1 1' = Standard +
'0 0 0' = n/a +
'1 -1 1' = High
0x052eToneLevelyes[Values 0, 4, 8, 12, 16, 20 and 24] +
-31999 = Highlights +
-31998 = Shadows +
-31997 = Midtones +
0 = 0
0x052fArtFilterEffectint16u[20][Value 0] + +
0x0 = Off +
0x1 = Soft Focus +
0x2 = Pop Art +
0x3 = Pale & Light Color +
0x4 = Light Tone +
0x5 = Pin Hole +
0x6 = Grainy Film +
0x9 = Diorama +
0xa = Cross Process +
0xc = Fish Eye +
0xd = Drawing +
0xe = Gentle Sepia +
0xf = Pale & Light Color II +
0x10 = Pop Art II +
0x11 = Pin Hole II +
0x12 = Pin Hole III +
0x13 = Grainy Film II +
0x14 = Dramatic Tone +
0x15 = Punk
  0x16 = Soft Focus 2 +
0x17 = Sparkle +
0x18 = Watercolor +
0x19 = Key Line +
0x1a = Key Line II +
0x1b = Miniature +
0x1c = Reflection +
0x1d = Fragmented +
0x1f = Cross Process II +
0x20 = Dramatic Tone II +
0x21 = Watercolor I +
0x22 = Watercolor II +
0x23 = Diorama II +
0x24 = Vintage +
0x25 = Vintage II +
0x26 = Vintage III +
0x27 = Partial Color +
0x28 = Partial Color II +
0x29 = Partial Color III
+[Value 4] + +
0x0 = No Effect +
0x8010 = Star Light +
0x8020 = Pin Hole +
0x8030 = Frame +
0x8040 = Soft Focus
  0x8050 = White Edge +
0x8060 = B&W +
0x8080 = Blur Top and Bottom +
0x8081 = Blur Left and Right
+[Value 6] + +
0x0 = No Color Filter +
0x1 = Yellow Color Filter +
0x2 = Orange Color Filter
  0x3 = Red Color Filter +
0x4 = Green Color Filter
+
0x0532ColorCreatorEffectint16s[6] 
0x0537MonochromeProfileSettingsint16s[6][Value 0] + +
0 = No Filter +
1 = Yellow Filter +
2 = Orange Filter +
3 = Red Filter +
4 = Magenta Filter
  5 = Blue Filter +
6 = Cyan Filter +
7 = Green Filter +
8 = Yellow-green Filter
+
0x0538FilmGrainEffectint16s0 = Off +
1 = Low +
2 = Medium +
3 = High
0x0539ColorProfileSettingsint16s[14] 
0x053aMonochromeVignettingint16s(-5 to +5: positive is white vignetting, negative is black vignetting)
0x053bMonochromeColorint16s + +
0 = (none) +
1 = Normal +
2 = Sepia
  3 = Blue +
4 = Purple +
5 = Green
+
0x0600DriveModeint16u[n]~(2, 3 or 5 numbers: 1. Mode, 2. Shot number, 3. Mode bits, 5. Shutter mode)
0x0601PanoramaModeint16u~(2 numbers: 1. Mode, 2. Shot number)
0x0603ImageQuality2int16u1 = SQ +
2 = HQ +
3 = SHQ +
4 = RAW +
5 = SQ (5)
0x0604ImageStabilizationint32u0 = Off +
1 = On, Mode 1 +
2 = On, Mode 2 +
3 = On, Mode 3 +
4 = On, Mode 4
0x0804StackedImageint32u[2] +
'0 0' = No +
'3 2' = ND2 (1EV) +
'3 4' = ND4 (2EV) +
'3 8' = ND8 (3EV) +
'3 16' = ND16 (4EV) +
'3 32' = ND32 (5EV) +
'3 64' = ND64 (6EV) +
'5 4' = HDR1 +
'6 4' = HDR2 +
'8 8' = Tripod high resolution +
'11 12' = Hand-held high resolution (11 12) +
'11 16' = Hand-held high resolution (11 16) +
'1 *' = Live Composite (* images) +
'4 *' = Live Time/Bulb (* images) +
'9 *' = Focus-stacked (* images)
+
0x0900ManometerPressureint16u 
0x0901ManometerReadingint32s[2] 
0x0902ExtendedWBDetectint16u0 = Off +
1 = On
0x0903RollAngleint16s[2](converted to degrees of clockwise camera rotation)
0x0904PitchAngleint16s[2](converted to degrees of upward camera tilt)
0x0908DateTimeUTCstring 
+ +

Olympus RawDevelopment Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000RawDevVersionundef[4] 
0x0100RawDevExposureBiasValuerational64s 
0x0101RawDevWhiteBalanceValueint16u 
0x0102RawDevWBFineAdjustmentint16s 
0x0103RawDevGrayPointint16u[3] 
0x0104RawDevSaturationEmphasisint16s[3] 
0x0105RawDevMemoryColorEmphasisint16u 
0x0106RawDevContrastValueint16s[3] 
0x0107RawDevSharpnessValueint16s[3] 
0x0108RawDevColorSpaceint16u0 = sRGB +
1 = Adobe RGB +
2 = Pro Photo RGB
0x0109RawDevEngineint16u0 = High Speed +
1 = High Function +
2 = Advanced High Speed +
3 = Advanced High Function
0x010aRawDevNoiseReductionint16u0x0 = (none) +
Bit 0 = Noise Reduction +
Bit 1 = Noise Filter +
Bit 2 = Noise Filter (ISO Boost)
0x010bRawDevEditStatusint16u0 = Original +
1 = Edited (Landscape) +
6 = Edited (Portrait) +
8 = Edited (Portrait)
0x010cRawDevSettingsint16u + +
0x0 = (none) +
Bit 0 = WB Color Temp +
Bit 1 = WB Gray Point +
Bit 2 = Saturation +
Bit 3 = Contrast
  Bit 4 = Sharpness +
Bit 5 = Color Space +
Bit 6 = High Function +
Bit 7 = Noise Reduction
+
+ +

Olympus RawDevelopment2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000RawDevVersionundef[4] 
0x0100RawDevExposureBiasValuerational64s 
0x0101RawDevWhiteBalanceint16u1 = Color Temperature +
2 = Gray Point
0x0102RawDevWhiteBalanceValueint16u 
0x0103RawDevWBFineAdjustmentint16s 
0x0104RawDevGrayPointint16u[3] 
0x0105RawDevContrastValueint16s[3] 
0x0106RawDevSharpnessValueint16s[3] 
0x0107RawDevSaturationEmphasisint16s[3] 
0x0108RawDevMemoryColorEmphasisint16u 
0x0109RawDevColorSpaceint16u0 = sRGB +
1 = Adobe RGB +
2 = Pro Photo RGB
0x010aRawDevNoiseReductionint16u0x0 = (none) +
Bit 0 = Noise Reduction +
Bit 1 = Noise Filter +
Bit 2 = Noise Filter (ISO Boost)
0x010bRawDevEngineint16u0 = High Speed +
1 = High Function
0x010cRawDevPictureModeint16u1 = Vivid +
2 = Natural +
3 = Muted +
256 = Monotone +
512 = Sepia
0x010dRawDevPMSaturationint16s[3] 
0x010eRawDevPMContrastint16s[3] 
0x010fRawDevPMSharpnessint16s[3] 
0x0110RawDevPM_BWFilterint16u1 = Neutral +
2 = Yellow +
3 = Orange +
4 = Red +
5 = Green
0x0111RawDevPMPictureToneint16u1 = Neutral +
2 = Sepia +
3 = Blue +
4 = Purple +
5 = Green
0x0112RawDevGradationint16s[3] 
0x0113RawDevSaturation3int16s[3] 
0x0119RawDevAutoGradationint16u0 = Off +
1 = On
0x0120RawDevPMNoiseFilterint16u 
0x0121RawDevArtFilterint16u[4][Value 0] + +
0 = Off +
1 = Soft Focus +
2 = Pop Art +
3 = Pale & Light Color +
4 = Light Tone +
5 = Pin Hole +
6 = Grainy Film +
9 = Diorama +
10 = Cross Process +
12 = Fish Eye +
13 = Drawing +
14 = Gentle Sepia +
15 = Pale & Light Color II +
16 = Pop Art II +
17 = Pin Hole II +
18 = Pin Hole III +
19 = Grainy Film II +
20 = Dramatic Tone +
21 = Punk
  22 = Soft Focus 2 +
23 = Sparkle +
24 = Watercolor +
25 = Key Line +
26 = Key Line II +
27 = Miniature +
28 = Reflection +
29 = Fragmented +
31 = Cross Process II +
32 = Dramatic Tone II +
33 = Watercolor I +
34 = Watercolor II +
35 = Diorama II +
36 = Vintage +
37 = Vintage II +
38 = Vintage III +
39 = Partial Color +
40 = Partial Color II +
41 = Partial Color III
+
+ +

Olympus ImageProcessing Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000ImageProcessingVersionundef[4] 
0x0100WB_RBLevelsint16u[2] 
0x0102WB_RBLevels3000Kint16u[2] 
0x0103WB_RBLevels3300Kint16u[2] 
0x0104WB_RBLevels3600Kint16u[2] 
0x0105WB_RBLevels3900Kint16u[2] 
0x0106WB_RBLevels4000Kint16u[2] 
0x0107WB_RBLevels4300Kint16u[2] 
0x0108WB_RBLevels4500Kint16u[2] 
0x0109WB_RBLevels4800Kint16u[2] 
0x010aWB_RBLevels5300Kint16u[2] 
0x010bWB_RBLevels6000Kint16u[2] 
0x010cWB_RBLevels6600Kint16u[2] 
0x010dWB_RBLevels7500Kint16u[2] 
0x010eWB_RBLevelsCWB1int16u[2] 
0x010fWB_RBLevelsCWB2int16u[2] 
0x0110WB_RBLevelsCWB3int16u[2] 
0x0111WB_RBLevelsCWB4int16u[2] 
0x0113WB_GLevel3000Kint16u 
0x0114WB_GLevel3300Kint16u 
0x0115WB_GLevel3600Kint16u 
0x0116WB_GLevel3900Kint16u 
0x0117WB_GLevel4000Kint16u 
0x0118WB_GLevel4300Kint16u 
0x0119WB_GLevel4500Kint16u 
0x011aWB_GLevel4800Kint16u 
0x011bWB_GLevel5300Kint16u 
0x011cWB_GLevel6000Kint16u 
0x011dWB_GLevel6600Kint16u 
0x011eWB_GLevel7500Kint16u 
0x011fWB_GLevelint16u 
0x0200ColorMatrixint16u[9] 
0x0300Enhancerint16u 
0x0301EnhancerValuesint16u[7] 
0x0310CoringFilterint16u 
0x0311CoringValuesint16u[7] 
0x0600BlackLevel2int16u[4] 
0x0610GainBaseint16u 
0x0611ValidBitsint16u[2] 
0x0612CropLeftint16u[2] 
0x0613CropTopint16u[2] 
0x0614CropWidthint32u 
0x0615CropHeightint32u 
0x0635UnknownBlock1?undef(large unknown data block in ORF images but not JPG images)
0x0636UnknownBlock2?undef(large unknown data block in ORF images but not JPG images)
0x0805SensorCalibrationint16s[2](2 numbers: 1. Recommended maximum, 2. Calibration midpoint)
0x1010NoiseReduction2int16u0x0 = (none) +
Bit 0 = Noise Reduction +
Bit 1 = Noise Filter +
Bit 2 = Noise Filter (ISO Boost)
0x1011DistortionCorrection2int16u0 = Off +
1 = On
0x1012ShadingCompensation2int16u0 = Off +
1 = On
0x101cMultipleExposureModeint16u[2][Value 0] +
0 = Off +
1 = Live Composite +
2 = On (2 frames) +
3 = On (3 frames)
0x1103UnknownBlock3?undef(large unknown data block in ORF images but not JPG images)
0x1104UnknownBlock4?undef(large unknown data block in ORF images but not JPG images)
0x1112AspectRatioint8u[2] + +
'1 1' = 4:3 +
'1 4' = 1:1 +
'2 1' = 3:2 (RAW) +
'2 2' = 3:2 +
'3 1' = 16:9 (RAW) +
'3 3' = 16:9 +
'4 1' = 1:1 (RAW)
  '4 4' = 6:6 +
'5 5' = 5:4 +
'6 6' = 7:6 +
'7 7' = 6:5 +
'8 8' = 7:5 +
'9 1' = 3:4 (RAW) +
'9 9' = 3:4
+
0x1113AspectFrameint16u[4] 
0x1200FacesDetectedint32u[n](2 or 3 values)
0x1201FaceDetectAreaint16s[n](for models with 2 values in FacesDetected this gives X/Y coordinates in the +FaceDetectFrame for all 4 corners of the face rectangle. For models with 3 +values in FacesDetected this gives X/Y coordinates, size and rotation angle +of the face detect square)
0x1202MaxFacesint32u[3] 
0x1203FaceDetectFrameSizeint16u[6](width/height of the full face detect frame)
0x1207FaceDetectFrameCropint16s[12](X/Y offset and width/height of the cropped face detect frame)
0x1306CameraTemperatureno(this seems to be in degrees C only for some models)
0x1900KeystoneCompensationint8u[2]'0 0' = Off +
'0 1' = On
0x1901KeystoneDirectionint8u[2]0 = Vertical +
1 = Horizontal
0x1906KeystoneValueint16s[3](3 numbers: 1. Keystone Value, 2. Min, 3. Max)
+ +

Olympus FocusInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000FocusInfoVersionundef[4] 
0x0209AutoFocus?int16u0 = Off +
1 = On
0x0210SceneDetectint16u 
0x0211SceneArea?int32u[8] 
0x0212SceneDetectData?int32u[720] 
0x0300ZoomStepCountint16u 
0x0301FocusStepCountint16u 
0x0303FocusStepInfinityint16u 
0x0304FocusStepNearint16u 
0x0305FocusDistancerational64u 
0x0308AFPointint16u(for the E-3, E-5 and E-30 the value is separated into 2 parts: low 5 bits +give AF point, upper bits give AF target selection mode) +
[Value 0]
+ +
0x0 = (none) +
0x1 = Top-left (horizontal) +
0x2 = Top-center (horizontal) +
0x3 = Top-right (horizontal) +
0x4 = Left (horizontal) +
0x5 = Mid-left (horizontal) +
0x6 = Center (horizontal) +
0x7 = Mid-right (horizontal) +
0x8 = Right (horizontal) +
0x9 = Bottom-left (horizontal) +
0xa = Bottom-center (horizontal) +
0xb = Bottom-right (horizontal)
  0xc = Top-left (vertical) +
0xd = Top-center (vertical) +
0xe = Top-right (vertical) +
0xf = Left (vertical) +
0x10 = Mid-left (vertical) +
0x11 = Center (vertical) +
0x12 = Mid-right (vertical) +
0x13 = Right (vertical) +
0x14 = Bottom-left (vertical) +
0x15 = Bottom-center (vertical) +
0x16 = Bottom-right (vertical) +
0x1f = n/a
+[Value 1] + +
0x0 = Single Target +
0x40 = All Target
  0x80 = Dynamic Single Target +
0xe0 = n/a
+(models with 7-point AF) +
[Value 0]
+
0x0 = (none) +
0x2 = Top-center (horizontal) +
0x4 = Right (horizontal) +
0x5 = Mid-right (horizontal) +
0x6 = Center (horizontal) +
0x7 = Mid-left (horizontal) +
0x8 = Left (horizontal) +
0xa = Bottom-center (horizontal) +
0xc = Top-center (vertical) +
0xf = Right (vertical) +
0x10 = Mid-right (vertical) +
0x11 = Center (vertical) +
0x12 = Mid-left (vertical) +
0x13 = Left (vertical) +
0x15 = Bottom-center (vertical)
+[Value 1] +
0x0 = Single Target +
0x40 = All Target +
(models other than E-Mxxx and OM-x) +
0 = Left (or n/a) +
1 = Center (horizontal) +
2 = Right +
3 = Center (vertical) +
255 = None +
(other models)
0x031bAFPointDetails +
AFPointDetails
no
int16u
(models E-Mxxx and OM-x) +
[Value 0]
+ + +
0x0 = No Subject Detection +
0x1 = Motorsports
  0x2 = Airplanes +
0x3 = Trains
  0x4 = Birds +
0x5 = Dogs & Cats
+[Value 1] + +
0x0 = Face Priority  0x1 = Target Priority
+[Value 2] + +
0x0 = Normal AF  0x1 = AF on Half Press
+[Value 3] + + + +
0x0 = No Eye-AF  0x1 = Right Eye Priority  0x2 = Left Eye Priority  0x3 = Both Eyes Priority
+[Value 4] + +
0x0 = No Face Detection  0x1 = Face Detection
+[Value 5] + +
0x0 = No MF  0x1 = With MF
+[Value 6] + +
0x0 = AF Priority  0x1 = Release Priority
+[Value 7] + +
0x0 = No Object found  0x1 = Object found
+[Value 8] + + + +
0x0 = MF  0x1 = S-AF  0x2 = C-AF  0x6 = C-AF + TR
+(other models)
0x0328AFInfo---> Olympus AFInfo Tags
0x1201ExternalFlashint16u[2]'0 0' = Off +
'1 0' = On
0x1203ExternalFlashGuideNumber?rational64s 
0x1204ExternalFlashBounceint16u0 = Bounce or Off +
1 = Direct
0x1205ExternalFlashZoomrational64u 
0x1208InternalFlashint16u[n]0 = Off +
1 = On +
'0 0' = Off +
'1 0' = On
0x1209ManualFlashint16u[2]~(2 numbers: 1. 0=Off, 1=On, 2. Flash strength)
0x120aMacroLEDint16u0 = Off +
1 = On
0x1500SensorTemperatureint16s 
0x1600ImageStabilizationundef~ 
+ +

Olympus AFInfo Tags

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +

Olympus FE Tags

+

Some FE models write a large number of tags here, but most of this +information remains unknown.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0100BodyFirmwareVersionstring 
+ +

Olympus RawInfo Tags

+

These tags are found only in ORF images of some models (eg. C8080WZ).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000RawInfoVersionundef[4] 
0x0100WB_RBLevelsUsedint16u[2] 
0x0110WB_RBLevelsAutoint16u[2] 
0x0120WB_RBLevelsShadeint16u[2] 
0x0121WB_RBLevelsCloudyint16u[2] 
0x0122WB_RBLevelsFineWeatherint16u[2] 
0x0123WB_RBLevelsTungstenint16u[2] 
0x0124WB_RBLevelsEveningSunlightint16u[2] 
0x0130WB_RBLevelsDaylightFluorint16u[2] 
0x0131WB_RBLevelsDayWhiteFluorint16u[2] 
0x0132WB_RBLevelsCoolWhiteFluorint16u[2] 
0x0133WB_RBLevelsWhiteFluorescentint16u[2] 
0x0200ColorMatrix2int16u[9] 
0x0310CoringFilterint16u 
0x0311CoringValuesint16u[11] 
0x0600BlackLevel2int16u[4] 
0x0601YCbCrCoefficientsno(stored as int16u[6], but extracted as rational32u[3])
0x0611ValidPixelDepthint16u[2] 
0x0612CropLeftint16u 
0x0613CropTopint16u 
0x0614CropWidthint32u 
0x0615CropHeightint32u 
0x1000LightSourceint16u +
0 = Unknown +
16 = Shade +
17 = Cloudy +
18 = Fine Weather +
20 = Tungsten (Incandescent) +
22 = Evening Sunlight +
33 = Daylight Fluorescent +
34 = Day White Fluorescent +
35 = Cool White Fluorescent +
36 = White Fluorescent +
256 = One Touch White Balance +
512 = Custom 1-4
+
0x1001WhiteBalanceCompint16s[3] 
0x1010SaturationSettingint16s[3] 
0x1011HueSettingint16s[3] 
0x1012ContrastSettingint16s[3] 
0x1013SharpnessSettingint16s[3] 
0x2000CMExposureCompensationrational64s 
0x2001CMWhiteBalanceint16u 
0x2002CMWhiteBalanceCompint16s 
0x2010CMWhiteBalanceGrayPointint16u[3] 
0x2020CMSaturationint16s[3] 
0x2021CMHueint16s[3] 
0x2022CMContrastint16s[3] 
0x2023CMSharpnessint16s[3] 
+ +

Olympus UnknownInfo Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

Olympus DSS Tags

+

Information extracted from DSS/DS2 files and the ID3 XOLY frame of MP3 files +written by some Olympus voice recorders.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12Modelno 
38StartTimeno 
50EndTimeno 
62Durationno 
798Commentno 
+ +

Olympus AVI Tags

+

This information is found in Olympus AVI videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
18Makeno 
44Modelno--> Olympus CameraType Values
94FNumberno 
131DateTime1no 
157DateTime2no 
297ThumbInfo---> Olympus thmb2 Tags
+ +

Olympus thmb2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ThumbnailWidthno 
2ThumbnailHeightno 
4ThumbnailLengthno 
8ThumbnailImageno(160x120 JPEG thumbnail image)
+ +

Olympus WAV Tags

+

This information is found in WAV files from Olympus PCM linear recorders +like the LS-5, LS-10, LS-11.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12Modelno 
28FileNumberno 
38DateTimeOriginalno(time at start of recording)
50DateTimeEndno(time at end of recording)
62RecordingTimeno 
512Durationno 
522Index01no 
532Index02no 
542Index03no 
552Index04no 
562Index05no 
572Index06no 
582Index07no 
592Index08no 
602Index09no 
612Index10no 
622Index11no 
632Index12no 
642Index13no 
652Index14no 
662Index15no 
672Index16no 
+ +

Olympus MOV1 Tags

+

This information is found in MOV videos from Olympus models such as the +D540Z, D595Z, FE100, FE110, FE115, FE170 and FE200.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
24Modelno--> Olympus CameraType Values
38ExposureUnknown?no 
42FNumberno 
50ExposureCompensationno 
72FocalLengthno 
+ +

Olympus MOV2 Tags

+

This information is found in MOV videos from Olympus models such as the +FE120, FE140 and FE190.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
24Modelno(the actual model name, no decoding necessary)
54ExposureTimeno 
58FNumberno 
66ExposureCompensationno 
88FocalLengthno 
193ISOno 
+ +

Olympus MP4 Tags

+

This information is found in MP4 videos from Olympus models such as the +u7040 and u9010.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
24Modelno(oddly different than CameraType values in JPEG images by the same camera) + +
'SG472' = u7040,S7040 +
'SG473' = u9010,S9010 +
'SG475' = SP800UZ +
'SG551' = SZ-30MR +
'SG553' = SP-610UZ
  'SG554' = SZ-10 +
'SG555' = SZ-20 +
'SG573' = SZ-14 +
'SG575' = SP-620UZ
+
40FNumberno 
48ExposureCompensationno 
104MovableInfo---> Olympus MovableInfo Tags
114MovableInfo---> Olympus MovableInfo Tags
+ +

Olympus MovableInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ISOno 
44EncoderVersionno 
60DecoderVersionno 
131Thumbnail---> Olympus Thumbnail Tags
+ +

Olympus Thumbnail Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1ThumbnailWidthno 
2ThumbnailHeightno 
3ThumbnailLengthno 
4ThumbnailOffsetno 
+ +

Olympus MOV3 Tags

+

QuickTime information found in the TAGS atom of MOV videos from the E-M5.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'OLYM'OlympusAtom---> Olympus OLYM2 Tags
+ +

Olympus OLYM2 Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'prms'MakerNotes---> Olympus prms Tags
'scrn'PreviewInfo---> Olympus scrn2 Tags
'thmb'ThumbInfo---> Olympus thmb2 Tags
+ +

Olympus prms Tags

+

Information extracted from the "prms" atom in MOV videos from Olympus models +such as the OM E-M5.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
18Makeno 
44Modelno--> Olympus CameraType Values
131DateTime1no 
157DateTime2no 
383LensModelno 
+ +

Olympus scrn2 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
2OlympusPreview---> Olympus scrn Tags
+ +

Olympus scrn Tags

+

Information extracted from the "scrn" atom of Olympus MP4 videos.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PreviewImageLengthno 
4PreviewImageno(640x480 JPEG preview image)
+ +

Olympus thmb Tags

+

Information extracted from the "thmb" atom of Olympus MP4 videos.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ThumbnailLengthno 
4ThumbnailImageno(160x120 JPEG thumbnail image)
+ +

Olympus OLYM Tags

+

Tags found in the OLYM atom of MP4 videos from the TG-810.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
14Makeno 
40Modelno--> Olympus CameraType Values
90FNumberno 
127DateTimeOriginalno 
153DateTime2no 
265ThumbnailWidthno 
267ThumbnailHeightno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 28, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/OpenEXR.html b/ExifTool/html/TagNames/OpenEXR.html new file mode 100644 index 0000000..3376ed7 --- /dev/null +++ b/ExifTool/html/TagNames/OpenEXR.html @@ -0,0 +1,246 @@ + + + + +OpenEXR Tags + + + +

OpenEXR Tags

+

Information extracted from EXR images. Use the ExtractEmbedded option to +extract information from all frames of a multipart image. See +http://www.openexr.com/ for the official specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'_flags'FlagsnoBit 9 = Tiled +
Bit 10 = Long names +
Bit 11 = Deep data +
Bit 12 = Multipart
'_ver'EXRVersionno(low byte of Flags word)
'adoptedNeutral'AdoptedNeutralno 
'altitude'GPSAltitudeno 
'aperture'Apertureno 
'capDate'DateTimeOriginalno 
'channels'Channelsno 
'chromaticities'Chromaticitiesno 
'chunkCount'ChunkCountno 
'comments'Commentsno 
'compression'Compressionno + +
0 = None +
1 = RLE +
2 = ZIPS +
3 = ZIP
  4 = PIZ +
5 = PXR24 +
6 = B44 +
7 = B44A
+
'dataWindow'DataWindowno 
'displayWindow'DisplayWindowno 
'envmap'EnvironmentMapno0 = Latitude/Longitude +
1 = Cube
'expTime'ExposureTimeno 
'focus'FocusDistanceno 
'framesPerSecond'FramesPerSecondno 
'isoSpeed'ISOno 
'keyCode'KeyCodeno 
'latitude'GPSLatitudeno 
'lineOrder'LineOrderno0 = Increasing Y +
1 = Decreasing Y +
2 = Random Y
'longitude'GPSLongitudeno 
'lookModTransform'LookModTransformno 
'multiView'MultiViewno 
'name'Nameno 
'owner'Ownerno 
'pixelAspectRatio'PixelAspectRationo 
'preview'Previewno 
'renderingTransform'RenderingTransformno 
'screenWindowCenter'ScreenWindowCenterno 
'screenWindowWidth'ScreenWindowWidthno 
'tiles'Tilesno 
'timeCode'TimeCodeno 
'type'Typeno 
'utcOffset'TimeZoneno 
'version'Versionno 
'whiteLuminance'WhiteLuminanceno 
'worldToCamera'WorldToCamerano 
'worldToNDC'WorldToNDCno 
'wrapmodes'WrapModesno 
'xDensity'XResolutionno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 9, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Opus.html b/ExifTool/html/TagNames/Opus.html new file mode 100644 index 0000000..ebb8ab5 --- /dev/null +++ b/ExifTool/html/TagNames/Opus.html @@ -0,0 +1,63 @@ + + + + +Opus Tags + + + +

Opus Tags

+

Information extracted from Ogg Opus files. See +https://www.opus-codec.org/docs/ for the specification.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'OpusHead'Header---> Opus Header Tags
'OpusTags'Comments---> Vorbis Comments Tags
+ +

Opus Header Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0OpusVersionno 
1AudioChannelsno 
4SampleRateno 
8OutputGainno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 14, 2016 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Other.html b/ExifTool/html/TagNames/Other.html new file mode 100644 index 0000000..cd98918 --- /dev/null +++ b/ExifTool/html/TagNames/Other.html @@ -0,0 +1,42 @@ + + + + +Other Tags + + + +

Other PFM Tags

+

Tags extracted from Portable FloatMap images. See +http://www.pauldebevec.com/Research/HDR/PFM/ for the specification.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ByteOrderno 
ColorSpaceno'PF' = RGB +
'Pf' = Monochrome
ImageHeightno 
ImageWidthno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 12, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PCX.html b/ExifTool/html/TagNames/PCX.html new file mode 100644 index 0000000..59d841b --- /dev/null +++ b/ExifTool/html/TagNames/PCX.html @@ -0,0 +1,105 @@ + + + + +PCX Tags + + + +

PCX Tags

+

Tags extracted from PC Paintbrush images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Manufacturerno10 = ZSoft
1Softwareno0 = PC Paintbrush 2.5 +
2 = PC Paintbrush 2.8 (with palette) +
3 = PC Paintbrush 2.8 (without palette) +
4 = PC Paintbrush for Windows +
5 = PC Paintbrush 3.0+
2Encodingno1 = RLE
3BitsPerPixelno 
4LeftMarginno 
6TopMarginno 
8ImageWidthno(adjusted for LeftMargin)
10ImageHeightno(adjusted for TopMargin)
12XResolutionno 
14YResolutionno 
65ColorPlanesno 
66BytesPerLineno 
68ColorModeno0 = n/a +
1 = Color Palette +
2 = Grayscale
70ScreenWidthno 
72ScreenHeightno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jan 2, 2019 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PDF.html b/ExifTool/html/TagNames/PDF.html new file mode 100644 index 0000000..2c3c4b7 --- /dev/null +++ b/ExifTool/html/TagNames/PDF.html @@ -0,0 +1,752 @@ + + + + +PDF Tags + + + +

PDF Tags

+

+The tags listed in the PDF tables below are those which are used by ExifTool +to extract meta information, but they are only a small fraction of the total +number of available PDF tags. See +http://www.adobe.com/devnet/pdf/pdf_reference.html for the official PDF +specification.

+ +

ExifTool supports reading and writing PDF documents up to version 2.0, +including support for RC4, AES-128 and AES-256 encryption. A +Password option is provided to allow processing +of password-protected PDF files.

+ +

ExifTool may be used to write native PDF and XMP metadata to PDF files. It +uses an incremental update technique that has the advantages of being both +fast and reversible. If ExifTool was used to modify a PDF file, the +original may be recovered by deleting the PDF-update pseudo-group (with +-PDF-update:all= on the command line). However, there are two main +disadvantages to this technique:

+ +

1) A linearized PDF file is no longer linearized after the update, so it +must be subsequently re-linearized if this is required.

+ +

2) All metadata edits are reversible. While this would normally be +considered an advantage, it is a potential security problem because old +information is never actually deleted from the file. (However, after +running ExifTool the old information may be removed permanently using the +"qpdf" utility with this command: "qpdf --linearize in.pdf out.pdf".) +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Encrypt'Encrypt---> PDF Encrypt Tags
'Info'Info---> PDF Info Tags
'Root'Root---> PDF Root Tags
'_linearized'Linearizedno(flag set if document is linearized for fast web display; not a real Tag ID) +
'false' = No +
'true' = Yes
+ +

PDF Encrypt Tags

+

Tags extracted from the document Encrypt dictionary.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Filter'Encryptionno(extracted value is actually a combination of the Filter, SubFilter, V, R and +Length information from the Encrypt dictionary)
'P'UserAccessno + +
Bit 2 = Print +
Bit 3 = Modify +
Bit 4 = Copy +
Bit 5 = Annotate
  Bit 8 = Fill forms +
Bit 9 = Extract +
Bit 10 = Assemble +
Bit 11 = Print high-res
+
+ +

PDF Info Tags

+

As well as the tags listed below, the PDF specification allows for +user-defined tags to exist in the Info dictionary. These tags, which should +have corresponding XMP-pdfx entries in the XMP of the PDF XML Metadata +object, are also extracted by ExifTool.

+ +

Writable specifies the value format, and may be string, date, +integer, real, boolean or name for PDF tags.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AAPL:Keywords'AppleKeywordsstring+(keywords written by Apple utilities, although they seem to use PDF:Keywords +when reading)
'Author'Authorstring 
'CreationDate'CreateDatedate 
'Creator'Creatorstring 
'Keywords'Keywordsstring+(stored as a string but treated as a comma- or semicolon-separated list of +items when reading if the string contains commas or semicolons, whichever is +more numerous, otherwise it is treated a space-separated list of items. +Written as a comma-separated list. The list behaviour may be defeated by +setting the API NoPDFList option. Note that the corresponding +XMP-pdf:Keywords tag is not treated as a list, so the NoPDFList option +should be used when copying between these two.)
'ModDate'ModifyDatedate 
'Producer'Producerstring 
'Subject'Subjectstring 
'Title'Titlestring 
'Trapped'Trappedstring! 
+ +

PDF Root Tags

+

This is the PDF document catalog.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AcroForm'AcroForm---> PDF AcroForm Tags
'Lang'Languageno 
'MarkInfo'MarkInfo---> PDF MarkInfo Tags
'Metadata'Metadata---> PDF Metadata Tags
'PageLayout'PageLayoutno 
'PageMode'PageModeno 
'Pages'Pages---> PDF Pages Tags
'Perms'Perms---> PDF Perms Tags
'Version'PDFVersionno 
+ +

PDF AcroForm Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'_has_xfa'HasXFAno(this tag is defined if a document contains form fields, and is true if it +uses XML Forms Architecture; not a real Tag ID) +
'false' = No +
'true' = Yes
+ +

PDF MarkInfo Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Marked'TaggedPDFno(not a Tagged PDF if this tag is missing) +
'false' = No +
'true' = Yes
+ +

PDF Metadata Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'XML_stream'XMP---> XMP Tags
+ +

PDF Pages Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Count'PageCountno 
'Kids'Kids---> PDF Kids Tags
+ +

PDF Kids Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Kids'Kids---> PDF Kids Tags
'Metadata'Metadata---> PDF Metadata Tags
'PieceInfo'PieceInfo---> PDF PieceInfo Tags
'Resources'Resources---> PDF Resources Tags
+ +

PDF PieceInfo Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AdobePhotoshop'AdobePhotoshop---> PDF AdobePhotoshop Tags
'Illustrator'Illustrator---> PDF Illustrator Tags
+ +

PDF AdobePhotoshop Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Private'Private---> PDF Private Tags
+ +

PDF Private Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ImageResources'ImageResources---> PDF ImageResources Tags
+ +

PDF ImageResources Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'_stream'_stream---> Photoshop Tags
+ +

PDF Illustrator Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Private'Private---> PDF AIPrivate Tags
+ +

PDF AIPrivate Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AIMetaData'AIMetaData---> PDF AIMetaData Tags
'AIPDFPrivateData'AIPDFPrivateData---> PostScript Tags
'AIPrivateData'AIPrivateData---> PostScript Tags +
(the ExtractEmbedded option enables information to be extracted from embedded +PostScript documents in the AIPrivateData# and AIPDFPrivateData# streams)
'ContainerVersion'ContainerVersionno 
'CreatorVersion'CreatorVersionno 
'RoundTripVersion'RoundTripVersionno 
+ +

PDF AIMetaData Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'_stream'_stream---> PostScript Tags
+ +

PDF Resources Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ColorSpace'ColorSpace---> PDF ColorSpace Tags
'Properties'Properties---> PDF Properties Tags
'XObject'XObject---> PDF XObject Tags
+ +

PDF ColorSpace Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CS0'CS0---> PDF DefaultRGB Tags
'Cs1'Cs1---> PDF DefaultRGB Tags
'DefaultCMYK'DefaultCMYK---> PDF DefaultRGB Tags
'DefaultRGB'DefaultRGB---> PDF DefaultRGB Tags
+ +

PDF DefaultRGB Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ICCBased'ICCBased---> PDF ICCBased Tags
+ +

PDF ICCBased Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'_stream'_stream---> ICC_Profile Tags
+ +

PDF Properties Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'MC'MC---> PDF MC Tags +
(the ExtractEmbedded option enables information to be extracted from these +embedded metadata dictionaries)
+ +

PDF MC Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Metadata'Metadata---> PDF Metadata Tags
+ +

PDF XObject Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Im'Im---> PDF Im Tags +
(the ExtractEmbedded option enables information to be extracted from these +embedded images)
+ +

PDF Im Tags

+

Information extracted from embedded images with the ExtractEmbedded option. +The EmbeddedImage and its metadata are extracted only for JPEG and Jpeg2000 +image formats.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ColorSpace'EmbeddedImageColorSpaceno+ 
'Filter'EmbeddedImageFilterno+ 
'Height'EmbeddedImageHeightno 
'Image_stream'EmbeddedImageno 
'Width'EmbeddedImageWidthno 
+ +

PDF Perms Tags

+

Additional document permissions imposed by digital signatures.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'DocMDP'DocMDP---> PDF Signature Tags
'FieldMDP'FieldMDP---> PDF Signature Tags
'UR3'UR3---> PDF Signature Tags
+ +

PDF Signature Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ContactInfo'SignerContactInfono 
'Location'SigningLocationno 
'M'SigningDateno 
'Name'SigningAuthorityno 
'Prop_AuthTime'AuthenticationTimeno 
'Prop_AuthType'AuthenticationTypeno 
'Reason'SigningReasonno 
'Reference'Reference---> PDF Reference Tags
+ +

PDF Reference Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'TransformParams'TransformParams---> PDF TransformParams Tags
+ +

PDF TransformParams Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Action'FieldPermissionsno(FieldMDP signatures only) +
'All' = Disallow changes to all form fields +
'Exclude' = Allow changes to specified form fields +
'Include' = Disallow changes to specified form fields
'Annots'AnnotationUsageRightsno+(possible values are Create, Delete, Modify, Copy, Import and Export; +additional values for UR3 signatures are Online and SummaryView)
'Document'DocumentUsageRightsno+(only possible value is FullSave)
'EF'EmbeddedFileUsageRightsno+(possible values are Create, Delete, Modify and Import)
'Fields'FormFieldsno+(FieldMDP signatures only)
'Form'FormUsageRightsno+(possible values are FillIn, Import, Export, SubmitStandalone and +SpawnTemplate; additional values for UR3 signatures are BarcodePlaintext and +Online)
'FormEX'FormExtraUsageRightsno+(UR signatures only; only possible value is BarcodePlaintext)
'Msg'UsageRightsMessageno 
'P'ModificationPermissionsno(1-3 for DocMDP signatures, default 2; true/false for UR3 signatures, default +false) +
1 = No changes permitted +
2 = Fill forms, Create page templates, Sign +
3 = Fill forms, Create page templates, Sign, Create/Delete/Edit annotations +
'false' = Do not restrict applications to reader permissions +
'true' = Restrict all applications to reader permissions
'Signature'SignatureUsageRightsno+(only possible value is Modify)
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 10, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PGF.html b/ExifTool/html/TagNames/PGF.html new file mode 100644 index 0000000..c064e7b --- /dev/null +++ b/ExifTool/html/TagNames/PGF.html @@ -0,0 +1,81 @@ + + + + +PGF Tags + + + +

PGF Tags

+

The following table lists information extracted from the header of +Progressive Graphics File (PGF) images. As well, information is extracted +from the embedded PNG metadata image if it exists. See +http://www.libpgf.org/ for the PGF specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
3PGFVersionno 
8ImageWidthno 
12ImageHeightno 
16PyramidLevelsno 
17Qualityno 
18BitsPerPixelno 
19ColorComponentsno 
20ColorModeno + +
0 = Bitmap +
1 = Grayscale +
2 = Indexed +
3 = RGB
  4 = CMYK +
7 = Multichannel +
8 = Duotone +
9 = Lab
+
21BackgroundColorno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jan 26, 2011 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PICT.html b/ExifTool/html/TagNames/PICT.html new file mode 100644 index 0000000..e430514 --- /dev/null +++ b/ExifTool/html/TagNames/PICT.html @@ -0,0 +1,754 @@ + + + + +PICT Tags + + + +

PICT Tags

+

The PICT format contains no true meta information, except for the possible +exception of the LongComment opcode. By default, only ImageWidth, +ImageHeight and X/YResolution are extracted from a PICT image. Tags in the +following table represent image opcodes. Extraction of these tags is +experimental, and is only enabled with the Verbose or Unknown options.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000Nopno 
0x0001ClipRgnno 
0x0002BkPatno 
0x0003TxFontno 
0x0004TxFaceno 
0x0005TxModeno 
0x0006SpExtrano 
0x0007PnSizeno 
0x0008PnModeno 
0x0009PnPatno 
0x000aFillPatno 
0x000bOvSizeno 
0x000cOriginno 
0x000dTxSizeno 
0x000eFgColorno 
0x000fBkColorno 
0x0010TxRationo 
0x0011VersionOpno 
0x0012BkPixPatno 
0x0013PnPixPatno 
0x0014FillPixPatno 
0x0015PnLocHFracno 
0x0016ChExtrano 
0x0017Reservedno 
0x001aRGBFgColno 
0x001bRGBBkColno 
0x001cHiliteModeno 
0x001dHiliteColorno 
0x001eDefHiliteno 
0x001fOpColorno 
0x0020Lineno 
0x0021LineFromno 
0x0022ShortLineno 
0x0023ShortLineFromno 
0x0024Reservedno 
0x0028LongTextno 
0x0029DHTextno 
0x002aDVTextno 
0x002bDHDVTextno 
0x002cFontNameno 
0x002dLineJustifyno 
0x002eGlyphStateno 
0x002fReservedno 
0x0030FrameRectno 
0x0031PaintRectno 
0x0032EraseRectno 
0x0033InvertRectno 
0x0034FillRectno 
0x0035Reservedno 
0x0038FrameSameRectno 
0x0039PaintSameRectno 
0x003aEraseSameRectno 
0x003bInvertSameRectno 
0x003cFillSameRectno 
0x003dReservedno 
0x0040FrameRRectno 
0x0041PaintRRectno 
0x0042EraseRRectno 
0x0043InvertRRectno 
0x0044FillRRectno 
0x0045Reservedno 
0x0048FrameSameRRectno 
0x0049PaintSameRRectno 
0x004aEraseSameRRectno 
0x004bInvertSameRRectno 
0x004cFillSameRRectno 
0x004dReservedno 
0x0050FrameOvalno 
0x0051PaintOvalno 
0x0052EraseOvalno 
0x0053InvertOvalno 
0x0054FillOvalno 
0x0055Reservedno 
0x0058FrameSameOvalno 
0x0059PaintSameOvalno 
0x005aEraseSameOvalno 
0x005bInvertSameOvalno 
0x005cFillSameOvalno 
0x005dReservedno 
0x0060FrameArcno 
0x0061PaintArcno 
0x0062EraseArcno 
0x0063InvertArcno 
0x0064FillArcno 
0x0065Reservedno 
0x0068FrameSameArcno 
0x0069PaintSameArcno 
0x006aEraseSameArcno 
0x006bInvertSameArcno 
0x006cFillSameArcno 
0x006dReservedno 
0x0070FramePolyno 
0x0071PaintPolyno 
0x0072ErasePolyno 
0x0073InvertPolyno 
0x0074FillPolyno 
0x0075Reservedno 
0x0078FrameSamePolyno 
0x0079PaintSamePolyno 
0x007aEraseSamePolyno 
0x007bInvertSamePolyno 
0x007cFillSamePolyno 
0x007dReservedno 
0x0080FrameRgnno 
0x0081PaintRgnno 
0x0082EraseRgnno 
0x0083InvertRgnno 
0x0084FillRgnno 
0x0085Reservedno 
0x0088FrameSameRgnno 
0x0089PaintSameRgnno 
0x008aEraseSameRgnno 
0x008bInvertSameRgnno 
0x008cFillSameRgnno 
0x008dReservedno 
0x0090BitsRectno 
0x0091BitsRgnno 
0x0092Reservedno 
0x0098PackBitsRectno 
0x0099PackBitsRgnno 
0x009aDirectBitsRectno 
0x009bDirectBitsRgnno 
0x009cReservedno 
0x009dReservedno 
0x009eReservedno 
0x009fReservedno 
0x00a0ShortCommentno 
0x00a1LongComment---> Photoshop Tags +
--> ICC_Profile Tags
0x00a2Reservedno 
0x00b0Reservedno 
0x00d0Reservedno 
0x00ffOpEndPicno 
0x0100Reservedno 
0x0200Reservedno 
0x02ffVersionno 
0x0300Reservedno 
0x0bffReservedno 
0x0c00HeaderOpno 
0x0c01Reservedno 
0x7f00Reservedno 
0x8000Reservedno 
0x8100Reservedno 
0x8200CompressedQuickTimeno 
0x8201UncompressedQuickTimeno 
0xffffReservedno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 13, 2006 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PLIST.html b/ExifTool/html/TagNames/PLIST.html new file mode 100644 index 0000000..9643222 --- /dev/null +++ b/ExifTool/html/TagNames/PLIST.html @@ -0,0 +1,87 @@ + + + + +PLIST Tags + + + +

PLIST Tags

+

Apple Property List tags. ExifTool reads both XML and binary-format PLIST +files, and will extract any existing tags even if they aren't listed below. +These tags belong to the family 0 "PLIST" group, but family 1 group may be +either "XML" or "PLIST" depending on whether the format is XML or binary.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'MetaDataList//DateTimeOriginal'DateTimeOriginalno 
'MetaDataList//Duration'Durationno 
'MetaDataList//Geolocation/Latitude'GPSLatitudeno 
'MetaDataList//Geolocation/Longitude'GPSLongitudeno 
'MetaDataList//Geolocation/MapDatum'GPSMapDatumno 
'XMLFileType'XMLFileTypeno 
'cast//name'Castno+ 
'codirectors//name'Codirectorsno+ 
'directors//name'Directorsno+ 
'producers//name'Producersno+ 
'screenwriters//name'Screenwritersno+ 
'studio//name'Studiono+ 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 22, 2013 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PLUS.html b/ExifTool/html/TagNames/PLUS.html new file mode 100644 index 0000000..5dd2c9f --- /dev/null +++ b/ExifTool/html/TagNames/PLUS.html @@ -0,0 +1,2629 @@ + + + + +PLUS Tags + + + +

PLUS XMP Tags

+

PLUS (Picture Licensing Universal System) License Data Format 2.0.1 XMP +tags. Note that all controlled-vocabulary tags in this table (ie. tags with +a fixed set of values) have raw values which begin with +"http://ns.useplus.org/ldf/vocab/", but to reduce clutter this prefix has +been removed from the values shown below, and from the values read and +written with the -n option. See http://ns.useplus.org/ for the complete +specification.

+ +

These tags belong to the ExifTool XMP-plus family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AdultContentWarningstring'CW-AWR' = Adult Content Warning Required +
'CW-NRQ' = Not Required +
'CW-UNK' = Unknown
CopyrightOwnerstruct+--> CopyrightOwner Struct
CopyrightOwnerIDstring_+(CopyrightOwnerCopyrightOwnerID)
CopyrightOwnerNamestring_+(CopyrightOwnerCopyrightOwnerName)
CopyrightOwnerImageIDstring 
CopyrightRegistrationNumberstring 
CopyrightStatusstring'CS-PRO' = Protected +
'CS-PUB' = Public Domain +
'CS-UNK' = Unknown
CreditLineRequiredstring'CR-CAI' = Credit Adjacent To Image +
'CR-CCA' = Credit in Credits Area +
'CR-COI' = Credit on Image +
'CR-NRQ' = Not Required
Custom1lang-alt+ 
Custom10lang-alt+ 
Custom2lang-alt+ 
Custom3lang-alt+ 
Custom4lang-alt+ 
Custom5lang-alt+ 
Custom6lang-alt+ 
Custom7lang-alt+ 
Custom8lang-alt+ 
Custom9lang-alt+ 
DataMiningstring'DMI-ALLOWED' = Allowed +
'DMI-PROHIBITED' = Prohibited +
'DMI-PROHIBITED-AIMLTRAINING' = Prohibited for AI/ML training +
'DMI-PROHIBITED-EXCEPTSEARCHENGINEINDEXING' = Prohibited except for search engine indexing +
'DMI-PROHIBITED-GENAIMLTRAINING' = Prohibited for Generative AI/ML training +
'DMI-PROHIBITED-SEECONSTRAINT' = Prohibited, see plus:OtherConstraints +
'DMI-PROHIBITED-SEEEMBEDDEDRIGHTSEXPR' = Prohibited, see iptcExt:EmbdEncRightsExpr +
'DMI-PROHIBITED-SEELINKEDRIGHTSEXPR' = Prohibited, see iptcExt:LinkedEncRightsExpr +
'DMI-UNSPECIFIED' = Unspecified - no prohibition defined
EndUserstruct+--> EndUser Struct
EndUserIDstring_+(EndUserEndUserID)
EndUserNamestring_+(EndUserEndUserName)
FileNameAsDeliveredstring 
FirstPublicationDatedate 
ImageAlterationConstraintsstring+ +
'AL-CLR' = No Colorization +
'AL-CRP' = No Cropping +
'AL-DCL' = No De-Colorization +
'AL-FLP' = No Flipping +
'AL-MRG' = No Merging +
'AL-RET' = No Retouching
+
ImageCreatorstruct+--> ImageCreator Struct
ImageCreatorIDstring_+(ImageCreatorImageCreatorID)
ImageCreatorNamestring_+(ImageCreatorImageCreatorName)
ImageCreatorImageIDstring 
ImageDuplicationConstraintsstring'DP-LIC' = Duplication Only as Necessary Under License +
'DP-NDC' = No Duplication Constraints +
'DP-NOD' = No Duplication
ImageFileConstraintsstring+'IF-MFN' = Maintain File Name +
'IF-MFT' = Maintain File Type +
'IF-MID' = Maintain ID in File Name +
'IF-MMD' = Maintain Metadata
ImageFileFormatAsDeliveredstring'FF-BMP' = Windows Bitmap (BMP) +
'FF-DNG' = Digital Negative (DNG) +
'FF-EPS' = Encapsulated PostScript (EPS) +
'FF-GIF' = Graphics Interchange Format (GIF) +
'FF-JPG' = JPEG Interchange Formats (JPG, JIF, JFIF) +
'FF-OTR' = Other +
'FF-PIC' = Macintosh Picture (PICT) +
'FF-PNG' = Portable Network Graphics (PNG) +
'FF-PSD' = Photoshop Document (PSD) +
'FF-RAW' = Proprietary RAW Image Format +
'FF-TIF' = Tagged Image File Format (TIFF) +
'FF-WMP' = Windows Media Photo (HD Photo)
ImageFileSizeAsDeliveredstring'SZ-G50' = Greater than 50 MB +
'SZ-U01' = Up to 1 MB +
'SZ-U10' = Up to 10 MB +
'SZ-U30' = Up to 30 MB +
'SZ-U50' = Up to 50 MB
ImageSupplierstruct+--> ImageSupplier Struct
ImageSupplierImageIDstring 
ImageSupplierIDstring_+(ImageSupplierImageSupplierID)
ImageSupplierNamestring_+(ImageSupplierImageSupplierName)
ImageTypestring'TY-ILL' = Illustrated Image +
'TY-MCI' = Multimedia or Composited Image +
'TY-OTR' = Other +
'TY-PHO' = Photographic Image +
'TY-VID' = Video
Licenseestruct+--> Licensee Struct
LicenseeImageIDstring 
LicenseeImageNoteslang-alt 
LicenseeIDstring_+(LicenseeLicenseeID)
LicenseeNamestring_+(LicenseeLicenseeName)
LicenseEndDatedate 
LicenseeProjectReferencestring+ 
LicenseeTransactionIDstring+ 
LicenseIDstring 
LicenseStartDatedate 
LicenseTransactionDatedate 
Licensorstruct+--> Licensor Struct
LicensorImageIDstring 
LicensorCitystring_+(LicensorLicensorCity)
LicensorCountrystring_+(LicensorLicensorCountry)
LicensorEmailstring_+(LicensorLicensorEmail)
LicensorExtendedAddressstring_+(LicensorLicensorExtendedAddress)
LicensorIDstring_+(LicensorLicensorID)
LicensorNamestring_+(LicensorLicensorName)
LicensorPostalCodestring_+(LicensorLicensorPostalCode)
LicensorRegionstring_+(LicensorLicensorRegion)
LicensorStreetAddressstring_+(LicensorLicensorStreetAddress)
LicensorTelephone1string_+(LicensorLicensorTelephone1)
LicensorTelephone2string_+(LicensorLicensorTelephone2)
LicensorTelephoneType1string_+(LicensorLicensorTelephoneType1) +
'cell' = Cell +
'fax' = FAX +
'home' = Home +
'pager' = Pager +
'work' = Work
LicensorTelephoneType2string_+(LicensorLicensorTelephoneType2) +
'cell' = Cell +
'fax' = FAX +
'home' = Home +
'pager' = Pager +
'work' = Work
LicensorURLstring_+(LicensorLicensorURL)
LicensorNoteslang-alt 
LicensorTransactionIDstring+ 
MediaConstraintslang-alt 
MediaSummaryCodestring--> PLUS MediaMatrix Values
MinorModelAgeDisclosurestring +
'AG-A15' = Age 15 +
'AG-A16' = Age 16 +
'AG-A17' = Age 17 +
'AG-A18' = Age 18 +
'AG-A19' = Age 19 +
'AG-A20' = Age 20 +
'AG-A21' = Age 21 +
'AG-A22' = Age 22 +
'AG-A23' = Age 23 +
'AG-A24' = Age 24 +
'AG-A25' = Age 25 or Over +
'AG-U14' = Age 14 or Under +
'AG-UNK' = Age Unknown
+
ModelReleaseIDstring+ 
ModelReleaseStatusstring'MR-LMR' = Limited or Incomplete Model Releases +
'MR-NAP' = Not Applicable +
'MR-NON' = None +
'MR-UMR' = Unlimited Model Releases
OtherConditionslang-alt 
OtherConstraintslang-alt 
OtherImageInfolang-alt 
OtherLicenseDocumentsstring+ 
OtherLicenseInfolang-alt 
OtherLicenseRequirementslang-alt 
ProductOrServiceConstraintslang-alt 
PropertyReleaseIDstring+ 
PropertyReleaseStatusstring'PR-LPR' = Limited or Incomplete Property Releases +
'PR-NAP' = Not Applicable +
'PR-NON' = None +
'PR-UPR' = Unlimited Property Releases
RegionConstraintslang-alt 
Reusestring'RE-NAP' = Not Applicable +
'RE-REU' = Repeat Use
TermsAndConditionsTextlang-alt 
TermsAndConditionsURLstring 
PLUSVersionstring(called Version by the spec)
+ +

PLUS MediaMatrix Values

+

The lookup below is used to add human-readable descriptions to Media Matrix +ID's in PLUS Media Summary Codes.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueMediaMatrix
'1IAA'= 1 Usage Item:
'1IAB'= 2 Usage Items:
'1IAC'= 3 Usage Items:
'1IAD'= 4 Usage Items:
'1IAE'= 5 Usage Items:
'1UNA'= Usage Number A
'1UNB'= Usage Number B
'1UNC'= Usage Number C
'1UND'= Usage Number D
'1UNE'= Usage Number E
'2AAA'= Advertising|All Media Types|All Formats|All Distribution Formats
'2AAB'= All Categories|Book|All Book Types|All Distribution Formats
'2AAC'= All Categories|Periodicals|All Periodical Types|All Distribution Formats
'2AAD'= All Categories|Display|All Display Types|All Distribution Formats
'2AAE'= Editorial|All Media Types|All Formats|All Distribution Formats
'2AAG'= All Categories|Product Packaging|All Product Packaging Types|All Distribution Formats
'2AAH'= All Categories|Merchandise|All Merchandise Types|All Distribution Formats
'2AAI'= Internal Company Use|All Media Types|All Formats|All Distribution Formats
'2AAL'= All Categories|Mobile|All Mobile Types|All Distribution Formats
'2AAM'= Motion Picture & TV|All Media Types|All Formats|All Distribution Formats
'2AAN'= All Categories|Television Programming|All Television Programming Types|All Distribution Formats
'2AAP'= Products|All Media Types|All Formats|All Distribution Formats
'2AAR'= All Categories|Point of Purchase|All Point Of Purchase Types|All Distribution Formats
'2AAT'= All Categories|Marketing Materials|All Marketing Material Types|All Distribution Formats
'2AAU'= Personal Use|All Media Types|All Formats|All Distribution Formats
'2AAV'= All Categories|Music Video|All Music Video Types|All Distribution Formats
'2AAX'= All Categories|Motion Picture|All Motion Picture Types|All Distribution Formats
'2AAY'= Advertising|All Media Types|Promotional Reproduction of Licensed Usage in Context|All Distribution Formats
'2ACE'= Advertising|Periodicals|Magazine, Consumer|Internet Website
'2ACT'= Motion Picture & TV|Motion Picture|All Motion Picture Types|All Electronic Distribution Formats
'2ADD'= Products|Merchandise|Address Book|Printed
'2ADH'= Advertising|Point of Purchase|Adhesive Tag|Printed
'2ADS'= Advertising|Periodicals|Magazine, Consumer|Internet Email
'2AFT'= Advertising|Periodicals|Magazine, Trade|Internet Email
'2AGE'= Advertising|Periodicals|Magazine, Trade|Recordable Media
'2AGO'= Advertising|Periodicals|Magazine, Corporate|Internet Email
'2AHA'= Advertising|Periodicals|Magazine, Education|Internet Email
'2AID'= Motion Picture & TV|Motion Picture|Movie Trailer|All Electronic Distribution Formats
'2AIL'= Advertising|Periodicals|Magazine, Custom Published|Internet Email
'2AIM'= Motion Picture & TV|Motion Picture|Short Film|Recordable Media
'2ALB'= Products|Merchandise|Photo Album|Printed
'2ALL'= All Categories|All Media Types|All Formats|All Distribution Formats
'2ALP'= Advertising|Periodicals|Magazine, Custom Published|Recordable Media
'2AMI'= Advertising|Periodicals|Magazine, Custom Published|All Electronic Distribution Formats
'2AMP'= Advertising|Periodicals|Magazine Reprints, All Types|Internet Email
'2ANA'= Advertising|Periodicals|Magazine, All Types|Internet Website
'2ANN'= Advertising|Periodicals|Annual Report|Printed
'2ANT'= Products|Merchandise|Anthology|Printed
'2ANY'= Advertising|Periodicals|Magazine, All Types|Internet Downloadable File
'2APE'= Advertising|Periodicals|Magazine, All Types|Internet Email
'2APP'= Products|Merchandise|Apparel, General Apparel|Printed or Woven
'2APT'= Advertising|Periodicals|Magazine, All Types|All Internet Distribution Formats
'2ARC'= Motion Picture & TV|Motion Picture|Set Decor|Projected Display
'2ARK'= Advertising|Periodicals|Magazine, All Types|All Electronic Distribution Formats
'2ARM'= Advertising|Periodicals|Newspaper, Weekly Supplement|Internet Email
'2ART'= Advertising|Art|Art Display, All Art Types|Printed
'2ASH'= Advertising|Periodicals|Newspaper, Tabloid|Internet Downloadable File
'2ASK'= Motion Picture & TV|Motion Picture|Short Film|All Internet Distribution Formats
'2ATE'= Motion Picture & TV|Motion Picture|Movie Trailer|Projected Display
'2ATM'= Products|Merchandise|Card, ATM Card|Printed
'2ATT'= Products|Merchandise|Card, Phone Card|Printed
'2BAA'= All Categories|Website|All Web Page Types|All Distribution Formats
'2BAB'= Products|Art|Artist's Reference, Tattoo|Printed
'2BAG'= Advertising|Periodicals|All Periodical Types|Printed
'2BAH'= Advertising|Periodicals|Quarterly Report|Printed
'2BAJ'= Products|Merchandise|Computer Software|Recordable Media
'2BAK'= Products|Merchandise|Computer Software|Internet Downloadable File
'2BAL'= Advertising|Periodicals|Quarterly Report|Internet Email
'2BAM'= Advertising|Periodicals|Magazine, Advertorial|Internet Downloadable File
'2BAN'= Advertising|Display|Banner, All Types|Electronic Display
'2BAP'= Advertising|Periodicals|Magazine, Advertorial|Internet Website
'2BAR'= Advertising|Point of Purchase|All Point of Purchase Types|All Electronic Distribution Formats
'2BAS'= Advertising|Periodicals|Annual Report|Recordable Media
'2BAT'= Advertising|Periodicals|Free Standing Insert, All Insert Types|Printed
'2BAU'= Products|Merchandise|Computer Software|Internet Email
'2BAV'= Products|Merchandise|Computer Software|All Internet Distribution Formats
'2BAW'= Products|Merchandise|Diary|Recordable Media
'2BAY'= Advertising|Periodicals|Newspaper, Tabloid|Internet Email
'2BAZ'= Products|Merchandise|Diary|Internet Downloadable File
'2BBA'= Products|Merchandise|Diary|Internet Email
'2BCF'= Products|Merchandise|Retail Calendar, Multi-Page|Recordable Media
'2BCG'= Products|Merchandise|Retail Calendar, Multi-Page|All Electronic Distribution Formats
'2BCH'= Products|Merchandise|Retail Calendar, Multi-Page|All Internet Distribution Formats
'2BCI'= Products|Merchandise|Retail Calendar, Multi-Page|Internet Email
'2BCJ'= Products|Merchandise|Retail Calendar, Multi-Page|Internet Downloadable File
'2BED'= Advertising|Periodicals|Newspaper, All Types|Internet Email
'2BEE'= Editorial|Book|Textbook, Middle Reader|Printed
'2BEG'= Advertising|Book|Retail Book, Directory|Printed
'2BEL'= Advertising|Art|Art Display, All Art Types|Internet Website
'2BEN'= Advertising|Periodicals|Magazine, Advertorial|Printed
'2BER'= Motion Picture & TV|Music Video|All Music Video Types|Recordable Media
'2BES'= Motion Picture & TV|Music Video|All Music Video Types|All Internet Distribution Formats
'2BET'= Motion Picture & TV|Motion Picture|Movie Trailer|All Internet Distribution Formats
'2BEY'= Advertising|Art|Art Display, All Art Types|All Internet Distribution Formats
'2BFH'= Personal Use|Art|Art Display, All Art Types|All Electronic Distribution Formats
'2BFI'= Personal Use|Art|Art Display, All Art Types|All Internet Distribution Formats
'2BFJ'= Personal Use|Art|Art Display, All Art Types|Internet Website
'2BFK'= Personal Use|Art|Art Display, All Art Types|Electronic Display
'2BFN'= Personal Use|Art|Art Display, Display Print|Printed
'2BFP'= Personal Use|Art|Study Print, Educational|Printed
'2BFR'= Personal Use|Website|Web Page, All Types|Internet Website
'2BFS'= Personal Use|Website|Web Page, All Types|Recordable Media
'2BFT'= Personal Use|Website|Web Page, All Types|All Electronic Distribution Formats
'2BFU'= Personal Use|Website|Web Page, All Types|All Internet Distribution Formats
'2BHB'= Internal Company Use|Art|Art Display, Display Print|Printed
'2BHD'= Internal Company Use|Email|All Email Types|Intranet and Extranet Email
'2BHF'= Internal Company Use|Live Presentation|Internal Presentation|Projected Display
'2BHG'= Internal Company Use|Promotional Materials|Corporate Brochure|Recordable Media
'2BHH'= Internal Company Use|Promotional Materials|Corporate Brochure|Intranet and Extranet Downloadable File
'2BHI'= Internal Company Use|Promotional Materials|Corporate Brochure|Intranet and Extranet Email
'2BHK'= Internal Company Use|Promotional Materials|Corporate Calendar|Recordable Media
'2BHL'= Internal Company Use|Promotional Materials|Corporate Calendar|Intranet and Extranet Downloadable File
'2BHM'= Internal Company Use|Promotional Materials|Corporate Calendar|Intranet and Extranet Email
'2BHN'= Internal Company Use|Promotional Materials|Corporate Calendar|Printed
'2BHQ'= Internal Company Use|Promotional Materials|Card, Corporate Card|Printed
'2BHS'= Internal Company Use|Promotional Materials|Card, Corporate Card|Recordable Media
'2BHT'= Internal Company Use|Promotional Materials|Card, Corporate Card|Intranet and Extranet Downloadable File
'2BHU'= Internal Company Use|Promotional Materials|Card, Corporate Card|Intranet and Extranet Email
'2BHV'= Internal Company Use|Promotional Materials|Sales Kit|All Electronic Distribution Formats
'2BHW'= Internal Company Use|Promotional Materials|Training Materials|Printed
'2BHY'= Internal Company Use|Promotional Materials|Corporate Folder|Printed
'2BHZ'= Internal Company Use|Promotional Materials|CD ROM|Recordable Media
'2BIA'= Internal Company Use|Periodicals|Magazine, Custom Published|Recordable Media
'2BIB'= Advertising|Point of Purchase|All Point of Purchase Types|Electronic Display
'2BIC'= Internal Company Use|Periodicals|Magazine, Custom Published|Intranet and Extranet Website
'2BID'= Advertising|Periodicals|Newsletter, All Types|Internet Email
'2BIF'= Internal Company Use|Periodicals|Magazine, Custom Published|Intranet and Extranet Downloadable File
'2BIG'= Advertising|Display|All Display Types|Printed
'2BIH'= Internal Company Use|Periodicals|Magazine, Custom Published|All Electronic Distribution Formats
'2BII'= Internal Company Use|Periodicals|Magazine, Custom Published|All Intranet and Extranet Distribution Formats
'2BIL'= Advertising|Display|Billboard, All Types|Printed
'2BIN'= Advertising|Point of Purchase|All Point of Purchase Types|Printed
'2BIO'= Products|Merchandise|Birthday Book|Printed
'2BIS'= Advertising|Art|Art Display, All Art Types|All Electronic Distribution Formats
'2BIT'= Products|Merchandise|Card, Debit Card|Printed
'2BIZ'= Advertising|Marketing Materials|Card, Business Greeting Card|Printed
'2BJH'= Internal Company Use|Website|Webcast, All Types|Intranet and Extranet Website
'2BJK'= Internal Company Use|Website|Web Page, All Types|All Electronic Distribution Formats
'2BJL'= Internal Company Use|Website|Web Page, All Types|Recordable Media
'2BJN'= Internal Company Use|Website|Web Page, Content Body|Intranet and Extranet Website
'2BLA'= Products|Merchandise|Blank Note Book|Printed
'2BLD'= Advertising|Display|Billboard, Building Wrap|Printed
'2BNK'= Products|Merchandise|Card, Bank Card|Printed
'2BOA'= Editorial|Book|Textbook, Student Edition|Printed
'2BOB'= Advertising|Periodicals|Magazine, Consumer|Printed
'2BOD'= Advertising|Periodicals|All Periodical Types|Internet Downloadable File
'2BOG'= Advertising|Display|All Display Types|Electronic Display
'2BOO'= Editorial|Book|Retail Book, All Types|Printed
'2BOP'= Advertising|Periodicals|Magazine, Advertorial|Internet Email
'2BOS'= Advertising|Art|Art Display, All Art Types|Electronic Display
'2BOT'= Advertising|Point of Purchase|Bottlenecker|Printed
'2BOW'= Advertising|Periodicals|All Periodical Types|Internet Email
'2BOX'= Products|Merchandise|Gift Box|Printed
'2BOY'= Advertising|Art|Art Display, Display Print|Printed
'2BPE'= Advertising|Display|Transit Advertising, Bus Poster|Electronic Display
'2BRA'= Advertising|Display|Banner, Airborne Display|Printed
'2BRO'= Advertising|Display|Banner, All Types|Printed
'2BRR'= Advertising|Display|Banner, Backdrop|Printed
'2BUB'= Advertising|Display|Banner, Background|Printed
'2BUD'= Advertising|Display|Billboard, All Types|Electronic Display
'2BUG'= Editorial|Book|Retail Book, Children's Book|Printed
'2BUN'= Advertising|Marketing Materials|Promo Card|Printed
'2BUR'= Advertising|Display|Billboard, Bulletin|Electronic Display
'2BUS'= Advertising|Display|Transit Advertising, Bus Panel|Printed
'2BUY'= Advertising|Point of Purchase|Floor Graphic|Printed
'2BYE'= Advertising|Display|Billboard, Mobile Billboard|Electronic Display
'2BYS'= Advertising|Display|Billboard, Mobile Billboard|Printed
'2CAL'= Products|Merchandise|Retail Calendar, Multi-Page|Printed
'2CAR'= Advertising|Display|Shopping Cart|Printed
'2CAS'= Advertising|Point of Purchase|Case Card|Printed
'2CDR'= Products|Merchandise|CD ROM|Recordable Media
'2CHK'= Products|Merchandise|Check|Printed
'2CLO'= Products|Merchandise|Retail Calendar, One Page|Printed
'2COU'= Advertising|Point of Purchase|Counter Card|Printed
'2CRD'= Products|Merchandise|Card, Other Card|Printed
'2CRE'= Products|Merchandise|Card, Credit Card|Printed
'2CRT'= Advertising|Display|Shopping Cart|Electronic Display
'2CUS'= Advertising|Periodicals|Magazine, Custom Published|Printed
'2DAB'= Advertising|Display|Billboard, Rotating Billboard|Printed
'2DAD'= Advertising|Display|Billboard, Spectacular|Electronic Display
'2DAG'= Advertising|Display|Billboard, Spectacular|Printed
'2DAH'= Advertising|Display|Billboard, Wallscape|Electronic Display
'2DAK'= Advertising|Display|Billboard, Wallscape|Printed
'2DAL'= Advertising|Display|Event, Stadium Advertising|Electronic Display
'2DAM'= Advertising|Display|Event, Stadium Advertising|Printed
'2DAP'= Advertising|Display|Event, Trade Show Display|Electronic Display
'2DAW'= Advertising|Display|Poster, All Types|Electronic Display
'2DAY'= Products|Product Packaging|All Product Packaging Types|Printed
'2DEB'= Advertising|Periodicals|Magazine, Education|Printed
'2DEE'= Advertising|Periodicals|Wrapper|Printed
'2DEL'= Advertising|Display|Poster, Backlit Print|Printed
'2DEN'= Advertising|Display|Poster, Corporate Poster|Electronic Display
'2DEV'= Advertising|Display|Poster, Corporate Poster|Printed
'2DEW'= Editorial|Book|Trade Book, All Types|Printed
'2DEX'= Advertising|Display|Poster, Display Chrome|Printed
'2DEY'= Advertising|Display|Poster, Door Side Poster|Electronic Display
'2DIA'= Products|Merchandise|Diary|Printed
'2DIB'= Advertising|Display|Poster, Door Side Poster|Printed
'2DID'= Advertising|Display|Poster, Elevator Advertising|Electronic Display
'2DIG'= Motion Picture & TV|Motion Picture|Movie Trailer|Recordable Media
'2DIM'= Personal Use|Personal Review|All Review Types|All Electronic Distribution Formats
'2DIN'= Advertising|Display|Store Display, All Display Types|Electronic Display
'2DIP'= Motion Picture & TV|Motion Picture|All Motion Picture Types|All Internet Distribution Formats
'2DIR'= Editorial|Book|Retail Book, Directory|Printed
'2DIS'= Advertising|Display|Event, Trade Show Display|Printed
'2DIT'= Advertising|Display|Store Display, In-Store Poster|Electronic Display
'2DOC'= Motion Picture & TV|Motion Picture|Documentary Film|All Electronic Distribution Formats
'2DOE'= Advertising|Display|Terminal Advertising, All Types|Electronic Display
'2DOG'= Editorial|Book|Reference Book, All Types|Printed
'2DOL'= Advertising|Display|Terminal Advertising, Bus Stop Advertising|Electronic Display
'2DOM'= Advertising|Display|Terminal Advertising, Bus Stop Advertising|Printed
'2DON'= Advertising|Display|Terminal Advertising, Ferry Terminal Advertising|Electronic Display
'2DOR'= Advertising|Display|Terminal Advertising, Ferry Terminal Advertising|Printed
'2DOS'= Advertising|Display|Terminal Advertising, Shelter Advertising|Electronic Display
'2DOT'= Advertising|Marketing Materials|Artist's Reference, All Types|All Electronic Distribution Formats
'2DOW'= Advertising|Display|Terminal Advertising, Station Poster|Electronic Display
'2DRY'= Advertising|Display|Poster, Restroom Poster|Printed
'2DUB'= Advertising|Display|Terminal Advertising, Station Poster|Printed
'2DUE'= Advertising|Display|Terminal Advertising, Subway Terminal Advertising|Electronic Display
'2DUG'= Advertising|Display|Terminal Advertising, Subway Terminal Advertising|Printed
'2DUH'= Advertising|Periodicals|Magazine, Advertorial|All Electronic Distribution Formats
'2DUI'= Advertising|Display|Terminal Advertising, Train Terminal Advertising|Electronic Display
'2DUN'= Advertising|Display|Terminal Advertising, Train Terminal Advertising|Printed
'2DUO'= Advertising|Marketing Materials|All Marketing Material Types|Recordable Media
'2DUP'= Advertising|Periodicals|Magazine Reprints, All Types|Printed
'2DVA'= Advertising|Marketing Materials|DVD|Recordable Media
'2DVD'= Products|Merchandise|DVD|Recordable Media
'2DVE'= Editorial|Merchandise|DVD|Recordable Media
'2DVI'= Internal Company Use|Promotional Materials|DVD|Recordable Media
'2DVL'= Products|Product Packaging|Packaging For Recordable Media, Liner Notes|Printed
'2DVP'= Products|Product Packaging|Packaging For Recordable Media, All Packaging Types|Printed
'2DYE'= Advertising|Marketing Materials|All Marketing Material Types|Internet Email
'2EAB'= Editorial|Book|Retail Book, Directory|All E-Book Distribution Formats
'2EAC'= Advertising|Book|Retail Book, Directory|E-Book in Internet Website
'2EAD'= Advertising|Book|Retail Book, Directory|E-Book in Internet Downloadable File
'2EAE'= Advertising|Book|Retail Book, Directory|All E-Book Internet Distribution Formats
'2EAF'= Advertising|Book|Retail Book, Directory|E-Book on Recordable Media
'2EAG'= Advertising|Book|Retail Book, Directory|All E-Book Distribution Formats
'2EAH'= Advertising|Book|Textbook, All Types|E-Book in Internet Website
'2EAJ'= Advertising|Book|Textbook, All Types|E-Book in Internet Downloadable File
'2EAK'= Advertising|Book|Textbook, All Types|All E-Book Internet Distribution Formats
'2EAL'= Advertising|Book|Textbook, All Types|E-Book on Recordable Media
'2EAM'= Advertising|Book|Textbook, All Types|All E-Book Distribution Formats
'2EAN'= Advertising|Book|All Book Types|All E-Book Internet Distribution Formats
'2EAP'= Advertising|Book|All Book Types|All E-Book Distribution Formats
'2EAQ'= Editorial|Book|Reference Book, Encyclopedia|E-Book in Internet Website
'2EAR'= Advertising|Live Presentation|All Live Presentation Types|All Electronic Distribution Formats
'2EAT'= Products|Merchandise|Edible Media|Printed
'2EAU'= Editorial|Display|Poster, Educational Poster|Printed
'2EBA'= Editorial|Book|Textbook Ancillary Materials, Lab Manual|E-Book in Internet Downloadable File
'2EBB'= Editorial|Book|Textbook Ancillary Materials, Workbook|E-Book in Internet Downloadable File
'2EBC'= Editorial|Book|Retail Book, All Types|E-Book in Internet Downloadable File
'2EBD'= Editorial|Book|Textbook Ancillary Materials, Teacher's Manual|E-Book in Internet Downloadable File
'2EBE'= Editorial|Book|Retail Book, Children's Book|E-Book in Internet Downloadable File
'2EBF'= Editorial|Book|Retail Book, Concept Book|E-Book in Internet Downloadable File
'2EBG'= Editorial|Book|Retail Book, Directory|E-Book in Internet Downloadable File
'2EBH'= Editorial|Book|Retail Book, Handbook|E-Book in Internet Downloadable File
'2EBI'= Editorial|Book|Retail Book, Hi-lo Book|E-Book in Internet Downloadable File
'2EBJ'= Editorial|Book|Retail Book, Illustrated Book|E-Book in Internet Downloadable File
'2EBK'= Editorial|Book|All Book Types|E-Book in Internet Downloadable File
'2EBL'= Editorial|Book|Retail Book, Illustrated Guide|E-Book in Internet Downloadable File
'2EBM'= Editorial|Book|Retail Book, Manual|E-Book in Internet Downloadable File
'2EBN'= Editorial|Book|Retail Book, Novelty Book|E-Book in Internet Downloadable File
'2EBP'= Editorial|Book|Retail Book, Postcard Book|E-Book in Internet Downloadable File
'2EBQ'= Editorial|Book|Retail Book, Young Adult Book|E-Book in Internet Downloadable File
'2EBR'= Editorial|Book|Textbook, All Types|E-Book in Internet Downloadable File
'2EBS'= Editorial|Book|Textbook, Compendium|E-Book in Internet Downloadable File
'2EBT'= Editorial|Book|Textbook, Middle Reader|E-Book in Internet Downloadable File
'2EBV'= Editorial|Book|Textbook, Student Edition|E-Book in Internet Downloadable File
'2EBW'= Editorial|Book|Textbook Ancillary Materials, Teachers' Edition|E-Book in Internet Downloadable File
'2ECU'= Advertising|Marketing Materials|Artist's Reference, All Types|All Internet Distribution Formats
'2EDH'= Advertising|Marketing Materials|Artist's Reference, All Types|Printed
'2EDU'= Advertising|Marketing Materials|CD ROM|Recordable Media
'2EEL'= Editorial|Book|Textbook, Compendium|Printed
'2EFF'= Advertising|Marketing Materials|Brochure|Internet Downloadable File
'2EFS'= Advertising|Marketing Materials|Brochure|Internet Email
'2EFT'= Advertising|Marketing Materials|Brochure|Recordable Media
'2EFX'= Motion Picture & TV|Motion Picture|Prop|All Electronic Distribution Formats
'2EGA'= Editorial|Book|Textbook, All Types|E-Book in Internet Email
'2EGB'= Editorial|Book|Retail Book, Children's Book|E-Book in Internet Email
'2EGC'= Editorial|Book|Retail Book, Children's Book|All E-Book Distribution Formats
'2EGD'= Editorial|Book|Retail Book, Concept Book|E-Book in Internet Email
'2EGE'= Editorial|Book|Retail Book, Concept Book|All E-Book Distribution Formats
'2EGG'= Advertising|Marketing Materials|Brochure|Printed
'2EGH'= Editorial|Book|Retail Book, Handbook|E-Book in Internet Email
'2EGJ'= Editorial|Book|Retail Book, Handbook|All E-Book Distribution Formats
'2EGK'= Editorial|Book|Retail Book, Hi-lo Book|E-Book in Internet Email
'2EGL'= Editorial|Book|Retail Book, Hi-lo Book|All E-Book Distribution Formats
'2EGM'= Editorial|Book|Retail Book, Illustrated Book|E-Book in Internet Email
'2EGN'= Editorial|Book|Retail Book, Illustrated Book|All E-Book Distribution Formats
'2EGO'= Advertising|Marketing Materials|Coupon|Internet Downloadable File
'2EGP'= Editorial|Book|Retail Book, Illustrated Guide|E-Book in Internet Email
'2EGQ'= Editorial|Book|Retail Book, Illustrated Guide|All E-Book Distribution Formats
'2EGR'= Editorial|Book|Retail Book, Illustrated Guide|All E-Book Distribution Formats
'2EGS'= Editorial|Book|Retail Book, Manual|E-Book in Internet Email
'2EGT'= Editorial|Book|Retail Book, Manual|All E-Book Distribution Formats
'2EGV'= Editorial|Book|Retail Book, Novelty Book|E-Book in Internet Email
'2EGW'= Editorial|Book|Retail Book, Novelty Book|All E-Book Distribution Formats
'2EGY'= Editorial|Book|Retail Book, Postcard Book|E-Book in Internet Email
'2EGZ'= Editorial|Book|Retail Book, Postcard Book|All E-Book Distribution Formats
'2EJA'= Editorial|Book|All Book Types|All E-Book Distribution Formats
'2EJB'= Editorial|Book|Retail Book, Young Adult Book|E-Book in Internet Email
'2EJC'= Editorial|Book|Retail Book, Young Adult Book|All E-Book Distribution Formats
'2EJD'= Editorial|Book|Retail Book, All Types|E-Book in Internet Email
'2EJE'= Editorial|Book|Retail Book, All Types|All E-Book Distribution Formats
'2EJF'= Editorial|Book|Textbook, Compendium|E-Book in Internet Email
'2EJJ'= Editorial|Book|Textbook, Compendium|All E-Book Distribution Formats
'2EJK'= Editorial|Book|Textbook, Middle Reader|E-Book in Internet Email
'2EJL'= Editorial|Book|Textbook, Middle Reader|All E-Book Distribution Formats
'2EJM'= Editorial|Book|Textbook, Student Edition|E-Book in Internet Email
'2EJN'= Editorial|Book|Textbook, Student Edition|All E-Book Distribution Formats
'2EJP'= Editorial|Book|Textbook, All Types|All E-Book Distribution Formats
'2EJQ'= Editorial|Book|Textbook Ancillary Materials, Lab Manual|E-Book in Internet Email
'2EJR'= Editorial|Book|Textbook Ancillary Materials, Lab Manual|All E-Book Distribution Formats
'2EJS'= Editorial|Book|Textbook Ancillary Materials, Teachers' Edition|E-Book in Internet Email
'2EJT'= Editorial|Book|Textbook Ancillary Materials, Teachers' Edition|All E-Book Distribution Formats
'2EJU'= Editorial|Book|All Book Types|E-Book in Internet Email
'2EJV'= Editorial|Book|Textbook Ancillary Materials, Teacher's Manual|E-Book in Internet Email
'2EJW'= Editorial|Book|Textbook Ancillary Materials, Teacher's Manual|All E-Book Distribution Formats
'2EJY'= Editorial|Book|Textbook Ancillary Materials, Workbook|E-Book in Internet Email
'2EJZ'= Editorial|Book|Textbook Ancillary Materials, Workbook|All E-Book Distribution Formats
'2EKA'= Editorial|Book|All Book Types|All E-Book Internet Distribution Formats
'2EKB'= Editorial|Book|Retail Book, Children's Book|All E-Book Internet Distribution Formats
'2EKC'= Editorial|Book|Retail Book, Concept Book|All E-Book Internet Distribution Formats
'2EKD'= Editorial|Book|Retail Book, Handbook|All E-Book Internet Distribution Formats
'2EKE'= Editorial|Book|Retail Book, Hi-lo Book|All E-Book Internet Distribution Formats
'2EKF'= Editorial|Book|Retail Book, Illustrated Book|All E-Book Internet Distribution Formats
'2EKG'= Editorial|Book|Retail Book, Illustrated Guide|All E-Book Internet Distribution Formats
'2EKH'= Editorial|Book|Retail Book, Manual|All E-Book Internet Distribution Formats
'2EKJ'= Editorial|Book|Retail Book, Novelty Book|All E-Book Internet Distribution Formats
'2EKK'= Editorial|Book|Retail Book, Postcard Book|All E-Book Internet Distribution Formats
'2EKL'= Editorial|Book|Retail Book, Young Adult Book|All E-Book Internet Distribution Formats
'2EKM'= Editorial|Book|Retail Book, All Types|All E-Book Internet Distribution Formats
'2EKN'= Editorial|Book|Textbook, Compendium|All E-Book Internet Distribution Formats
'2EKP'= Editorial|Book|Textbook, Middle Reader|All E-Book Internet Distribution Formats
'2EKQ'= Editorial|Book|Textbook, Student Edition|All E-Book Internet Distribution Formats
'2EKR'= Editorial|Book|Textbook, All Types|All E-Book Internet Distribution Formats
'2EKS'= Editorial|Book|Textbook Ancillary Materials, Lab Manual|All E-Book Internet Distribution Formats
'2EKT'= Editorial|Book|Textbook Ancillary Materials, Teachers' Edition|All E-Book Internet Distribution Formats
'2EKU'= Editorial|Book|Textbook Ancillary Materials, Teacher's Manual|All E-Book Internet Distribution Formats
'2EKV'= Editorial|Book|Textbook Ancillary Materials, Workbook|All E-Book Internet Distribution Formats
'2EKW'= Editorial|Book|Artist's Reference, All Types|All E-Book Internet Distribution Formats
'2ELE'= Advertising|Display|Poster, Elevator Advertising|Printed
'2ELF'= Advertising|Marketing Materials|Catalog|Recordable Media
'2ELK'= Editorial|Book|Textbook Ancillary Materials, All Ancillary Types|Printed
'2ELL'= Advertising|Marketing Materials|Catalog|Internet Downloadable File
'2ELM'= Advertising|Book|Textbook, All Types|Printed
'2ELS'= Advertising|Marketing Materials|Catalog|Internet Email
'2EMA'= Advertising|Email|All Email Types|Internet Email
'2EMB'= Editorial|Book|Reference Book, Encyclopedia|E-Book in Internet Downloadable File
'2EMC'= Editorial|Book|Reference Book, Encyclopedia|All E-Book Internet Distribution Formats
'2EMD'= Editorial|Book|Reference Book, Encyclopedia|E-Book on Recordable Media
'2EMF'= Editorial|Book|Reference Book, Telephone Book|E-Book in Internet Website
'2EMG'= Editorial|Book|Reference Book, Telephone Book|E-Book in Internet Downloadable File
'2EMH'= Editorial|Book|Reference Book, Telephone Book|All E-Book Internet Distribution Formats
'2EMI'= Editorial|Book|Reference Book, Telephone Book|E-Book on Recordable Media
'2EMJ'= Editorial|Book|Reference Book, Telephone Book|All E-Book Distribution Formats
'2EMK'= Editorial|Book|Reference Book, All Types|E-Book in Internet Website
'2EML'= Editorial|Book|Reference Book, All Types|E-Book in Internet Downloadable File
'2EMM'= Editorial|Book|Reference Book, All Types|All E-Book Internet Distribution Formats
'2EMN'= Editorial|Book|Reference Book, All Types|E-Book on Recordable Media
'2EMP'= Editorial|Book|Reference Book, All Types|All E-Book Distribution Formats
'2EMQ'= Editorial|Book|Trade Book, All Types|E-Book in Internet Downloadable File
'2EMR'= Editorial|Book|Trade Book, All Types|E-Book in Internet Email
'2EMS'= Editorial|Book|Trade Book, All Types|All E-Book Internet Distribution Formats
'2EMT'= Editorial|Book|Trade Book, All Types|All E-Book Distribution Formats
'2EMU'= Editorial|Book|Textbook, Course Pack|Printed
'2EMV'= Editorial|Book|Artist's Reference, All Types|All E-Book Distribution Formats
'2END'= Advertising|Marketing Materials|Magalog|Internet Downloadable File
'2ENG'= Advertising|Marketing Materials|Coupon|Internet Email
'2ENS'= Advertising|Marketing Materials|Coupon|Recordable Media
'2EON'= Advertising|Marketing Materials|Magalog|Internet Email
'2EPA'= Editorial|Book|All Book Types|E-Book in Internet Website
'2EPB'= Editorial|Book|Retail Book, All Types|E-Book in Internet Website
'2EPC'= Editorial|Book|Retail Book, Children's Book|E-Book in Internet Website
'2EPD'= Editorial|Book|Retail Book, Concept Book|E-Book in Internet Website
'2EPE'= Editorial|Book|Retail Book, Directory|E-Book in Internet Website
'2EPF'= Editorial|Book|Retail Book, Handbook|E-Book in Internet Website
'2EPG'= Editorial|Book|Retail Book, Hi-lo Book|E-Book in Internet Website
'2EPH'= Editorial|Book|Retail Book, Illustrated Book|E-Book in Internet Website
'2EPJ'= Editorial|Book|Retail Book, Illustrated Guide|E-Book in Internet Website
'2EPK'= Editorial|Book|Retail Book, Manual|E-Book in Internet Website
'2EPL'= Editorial|Book|Retail Book, Novelty Book|E-Book in Internet Website
'2EPM'= Editorial|Book|Retail Book, Postcard Book|E-Book in Internet Website
'2EPN'= Editorial|Book|Retail Book, Young Adult Book|E-Book in Internet Website
'2EPP'= Editorial|Book|Textbook, All Types|E-Book in Internet Website
'2EPQ'= Editorial|Book|Textbook, Compendium|E-Book in Internet Website
'2EPR'= Editorial|Book|Textbook, Middle Reader|E-Book in Internet Website
'2EPS'= Editorial|Book|Textbook, Student Edition|E-Book in Internet Website
'2EPT'= Editorial|Book|Textbook Ancillary Materials, Teachers' Edition|E-Book in Internet Website
'2EPV'= Editorial|Book|Trade Book, All Types|E-Book in Internet Website
'2EPW'= Editorial|Book|Textbook Ancillary Materials, Teacher's Manual|E-Book in Internet Website
'2EPY'= Editorial|Book|Textbook Ancillary Materials, Workbook|E-Book in Internet Website
'2EPZ'= Editorial|Book|Textbook Ancillary Materials, Lab Manual|E-Book in Internet Website
'2ERA'= Editorial|Book|Trade Book, All Types|E-Book on Recordable Media
'2ERB'= Editorial|Book|Retail Book, Children's Book|E-Book on Recordable Media
'2ERC'= Editorial|Book|Retail Book, Concept Book|E-Book on Recordable Media
'2ERD'= Editorial|Book|Retail Book, Directory|E-Book on Recordable Media
'2ERF'= Editorial|Book|Retail Book, Handbook|E-Book on Recordable Media
'2ERG'= Editorial|Book|Retail Book, Hi-lo Book|E-Book on Recordable Media
'2ERH'= Editorial|Book|Retail Book, Illustrated Book|E-Book on Recordable Media
'2ERI'= Editorial|Book|Retail Book, Illustrated Guide|E-Book on Recordable Media
'2ERJ'= Editorial|Book|Retail Book, Manual|E-Book on Recordable Media
'2ERK'= Editorial|Book|Retail Book, Novelty Book|E-Book on Recordable Media
'2ERL'= Editorial|Book|Retail Book, Postcard Book|E-Book on Recordable Media
'2ERM'= Editorial|Book|Retail Book, Young Adult Book|E-Book on Recordable Media
'2ERN'= Editorial|Book|Retail Book, All Types|E-Book on Recordable Media
'2ERP'= Editorial|Book|Textbook, Compendium|E-Book on Recordable Media
'2ERQ'= Editorial|Book|Textbook, Middle Reader|E-Book on Recordable Media
'2ERS'= Editorial|Book|Textbook, Student Edition|E-Book on Recordable Media
'2ERT'= Editorial|Book|Textbook, All Types|E-Book on Recordable Media
'2ERU'= Editorial|Book|Textbook Ancillary Materials, Lab Manual|E-Book on Recordable Media
'2ERV'= Editorial|Book|Textbook Ancillary Materials, Teachers' Edition|E-Book on Recordable Media
'2ERW'= Editorial|Book|Textbook Ancillary Materials, Teacher's Manual|E-Book on Recordable Media
'2ERY'= Editorial|Book|Textbook Ancillary Materials, Workbook|E-Book on Recordable Media
'2ERZ'= Editorial|Book|All Book Types|E-Book on Recordable Media
'2ESS'= Advertising|Marketing Materials|Promotional Calendar, Multi-Page|Internet Downloadable File
'2ETA'= Advertising|Marketing Materials|Promotional Calendar, Multi-Page|Internet Email
'2EVE'= Products|Product Packaging|Retail Packaging, All Packaging Types|Printed
'2EWE'= Advertising|Marketing Materials|Promotional Calendar, One Page|All Electronic Distribution Formats
'2EYE'= Advertising|Live Presentation|Panel Presentation|Projected Display
'2FAB'= Internal Company Use|Website|Web Page, Content Body|Recordable Media
'2FAC'= Internal Company Use|Website|Web Page, Content Body|All Electronic Distribution Formats
'2FAD'= Advertising|Display|Poster, Movie Poster|Printed
'2FAH'= Internal Company Use|Website|Web Page, Design Element|Intranet and Extranet Website
'2FAI'= Internal Company Use|Website|Web Page, Design Element|Recordable Media
'2FAJ'= Internal Company Use|Website|Web Page, Design Element|All Electronic Distribution Formats
'2FAN'= Editorial|Periodicals|Newspaper, Tabloid|Printed
'2FAR'= Products|Merchandise|Retail Postcard|Printed
'2FAS'= Advertising|Marketing Materials|Promotional Calendar, One Page|Internet Downloadable File
'2FAT'= Advertising|Display|Poster, All Types|Printed
'2FAX'= Advertising|Marketing Materials|Magalog|Recordable Media
'2FAY'= Advertising|Marketing Materials|Promotional Calendar, One Page|Internet Email
'2FEA'= Motion Picture & TV|Motion Picture|Feature Film|Projected Display
'2FEH'= Advertising|Marketing Materials|Promotional E-card|Recordable Media
'2FEM'= Products|Merchandise|Textiles|Printed or Woven
'2FEN'= Advertising|Marketing Materials|Promotional E-card|Internet Downloadable File
'2FER'= Advertising|Display|Transit Advertising, Ferry Advertising|Printed
'2FET'= Advertising|Marketing Materials|Promotional E-card|Internet Email
'2FEU'= Advertising|Marketing Materials|Promotional E-card|All Internet Distribution Formats
'2FEZ'= Advertising|Merchandise|Apparel, General Apparel|Printed or Woven
'2FIB'= Products|Merchandise|Screen Saver|Internet Downloadable File
'2FID'= Advertising|Merchandise|Apparel, T-Shirts|Printed or Woven
'2FIE'= Advertising|Merchandise|Folder|Printed
'2FIG'= Advertising|Marketing Materials|Catalog|Printed
'2FIL'= Motion Picture & TV|Motion Picture|Feature Film|All Electronic Distribution Formats
'2FIN'= Advertising|Point of Purchase|Kiosk, All Types|Printed
'2FIR'= Advertising|Periodicals|All Periodical Types|Recordable Media
'2FIT'= Motion Picture & TV|Motion Picture|Short Film|All Electronic Distribution Formats
'2FIX'= Motion Picture & TV|Motion Picture|All Motion Picture Types|Projected Display
'2FIZ'= Advertising|Periodicals|All Periodical Types|Internet Website
'2FLU'= Advertising|Periodicals|All Periodical Types|All Electronic Distribution Formats
'2FLY'= Advertising|Marketing Materials|Flyer|Printed
'2FOB'= Advertising|Periodicals|All Periodical Types|All Internet Distribution Formats
'2FOE'= Internal Company Use|Periodicals|Magazine, Custom Published|Internet Email
'2FOG'= Advertising|Display|Store Display, All Display Types|Printed
'2FOH'= Advertising|Periodicals|Annual Report|Internet Downloadable File
'2FOL'= Products|Merchandise|Folder|Printed
'2FON'= Advertising|Periodicals|Annual Report|Internet Website
'2FOP'= Advertising|Periodicals|Annual Report|Internet Email
'2FOU'= Advertising|Periodicals|Annual Report|All Electronic Distribution Formats
'2FOX'= Editorial|Book|Textbook, All Types|Printed
'2FOY'= Advertising|Periodicals|Annual Report|All Internet Distribution Formats
'2FPO'= Internal Company Use|Comp Use|All Comp Types|All Electronic Distribution Formats
'2FRA'= Products|Product Packaging|Picture Frame Insert|Printed
'2FRO'= Internal Company Use|Comp Use|All Comp Types|Printed
'2FRY'= Motion Picture & TV|Motion Picture|Prop|Recordable Media
'2FUB'= Advertising|Periodicals|Belly Band|Printed
'2FUD'= Advertising|Periodicals|Cover Wrap|Printed
'2FUN'= Products|Merchandise|Playing Cards|Printed
'2FUR'= Advertising|Live Presentation|Sales Presentation|Projected Display
'2GAB'= Products|Merchandise|Game, Computer Game|Internet Downloadable File
'2GAD'= Products|Merchandise|Game, All Types|All Internet Distribution Formats
'2GAE'= Products|Merchandise|Game, Computer Game|All Internet Distribution Formats
'2GAG'= Products|Merchandise|Game, Computer Game|Recordable Media
'2GAH'= Products|Merchandise|Game, Computer Game|Internet Email
'2GAL'= Editorial|Display|Gallery Exhibition|Printed
'2GAM'= Products|Merchandise|Game, All Types|All Electronic Distribution Formats
'2GAN'= Products|Merchandise|Game, Computer Game|All Electronic Distribution Formats
'2GAP'= Products|Merchandise|Game, All Types|Printed
'2GAR'= Products|Merchandise|Game, All Types|Recordable Media
'2GAS'= Products|Merchandise|Game, All Types|Internet Email
'2GAT'= Products|Merchandise|Game, All Types|Internet Downloadable File
'2GBM'= Editorial|Book|Textbook, Course Pack|Internet Downloadable File
'2GBN'= Editorial|Book|Textbook, Course Pack|Internet Website
'2GBP'= Editorial|Book|Textbook, Course Pack|All Distribution Formats
'2GBQ'= Editorial|Book|Textbook, Course Pack|All Internet Distribution Formats
'2GBW'= Editorial|Book|Textbook Ancillary Materials, All Ancillary Types|All Distribution Formats
'2GBY'= Editorial|Book|Textbook Ancillary Materials, All Ancillary Types|All Internet Distribution Formats
'2GBZ'= Editorial|Book|Textbook Ancillary Materials, Educational Film Set|Projected Display
'2GDJ'= Editorial|Display|Gallery Exhibition|Electronic Display
'2GDK'= Editorial|Display|Museum Display|Electronic Display
'2GDM'= Editorial|Periodicals|Magazine, All Types|Internet Downloadable File
'2GDP'= Editorial|Periodicals|Magazine, All Types|Internet Website
'2GDQ'= Editorial|Periodicals|Magazine, All Types|Internet Email
'2GDR'= Editorial|Periodicals|Magazine, All Types|Recordable Media
'2GDS'= Editorial|Periodicals|Magazine, All Types|All Electronic Distribution Formats
'2GDT'= Editorial|Periodicals|Magazine, All Types|All Internet Distribution Formats
'2GDV'= Editorial|Periodicals|Magazine, Consumer|Recordable Media
'2GDW'= Editorial|Periodicals|Magazine, Consumer|Internet Website
'2GDY'= Editorial|Periodicals|Magazine, Consumer|Internet Downloadable File
'2GDZ'= Editorial|Periodicals|Magazine, Consumer|All Electronic Distribution Formats
'2GEA'= Editorial|Periodicals|Magazine, Consumer|All Internet Distribution Formats
'2GEC'= Editorial|Periodicals|Magazine, Custom Published|Recordable Media
'2GED'= Internal Company Use|Internal Review|All Review Types|Printed
'2GEE'= Editorial|Book|Textbook Ancillary Materials, Workbook|Printed
'2GEF'= Editorial|Periodicals|Magazine, Custom Published|Internet Website
'2GEG'= Editorial|Periodicals|Magazine, Custom Published|Internet Downloadable File
'2GEH'= Editorial|Periodicals|Magazine, Custom Published|All Electronic Distribution Formats
'2GEI'= Editorial|Periodicals|Magazine, Custom Published|All Internet Distribution Formats
'2GEK'= Editorial|Periodicals|Magazine, Education|Recordable Media
'2GEL'= Products|Merchandise|Screen Saver|Internet Email
'2GEM'= Editorial|Periodicals|Magazine, Consumer|Printed
'2GEN'= Advertising|Periodicals|Magazine, Consumer|Internet Downloadable File
'2GEP'= Editorial|Periodicals|Magazine, Education|Internet Website
'2GEQ'= Editorial|Periodicals|Magazine, Education|Internet Downloadable File
'2GER'= Editorial|Periodicals|Magazine, Education|All Electronic Distribution Formats
'2GES'= Editorial|Periodicals|Magazine, Education|All Internet Distribution Formats
'2GEV'= Editorial|Periodicals|Magazine, Partworks|Internet Website
'2GEW'= Editorial|Periodicals|Magazine, Partworks|Internet Downloadable File
'2GEY'= Advertising|Periodicals|Magazine, Consumer|All Electronic Distribution Formats
'2GEZ'= Editorial|Periodicals|Magazine, Partworks|Recordable Media
'2GFA'= Editorial|Periodicals|Magazine, Partworks|All Electronic Distribution Formats
'2GFB'= Editorial|Periodicals|Magazine, Partworks|All Internet Distribution Formats
'2GFD'= Editorial|Periodicals|Magazine, Trade|Recordable Media
'2GFG'= Editorial|Periodicals|Newsletter|Recordable Media
'2GFH'= Editorial|Periodicals|Newsletter|Internet Website
'2GFI'= Editorial|Periodicals|Newsletter|Internet Downloadable File
'2GFJ'= Editorial|Periodicals|Newsletter|All Electronic Distribution Formats
'2GFK'= Editorial|Periodicals|Newsletter|All Internet Distribution Formats
'2GFM'= Editorial|Periodicals|Newspaper, All Types|Recordable Media
'2GFN'= Editorial|Periodicals|Newspaper, All Types|Internet Website
'2GFP'= Editorial|Periodicals|Newspaper, All Types|Internet Downloadable File
'2GFQ'= Editorial|Periodicals|Newspaper, All Types|All Electronic Distribution Formats
'2GFR'= Editorial|Periodicals|Newspaper, All Types|All Internet Distribution Formats
'2GFS'= Editorial|Periodicals|Newspaper, Weekly Supplement|Recordable Media
'2GFT'= Products|Merchandise|Card, Gift Card|Printed
'2GFU'= Editorial|Periodicals|Newspaper, Weekly Supplement|All Electronic Distribution Formats
'2GFV'= Editorial|Periodicals|Newspaper, Weekly Supplement|All Internet Distribution Formats
'2GFW'= Editorial|Periodicals|Newspaper, Weekly Supplement|Internet Website
'2GFY'= Editorial|Periodicals|Newspaper, Weekly Supplement|Internet Downloadable File
'2GGA'= Editorial|Periodicals|Newspaper, Tabloid|Internet Downloadable File
'2GGD'= Editorial|Periodicals|Scholarly Journal|Recordable Media
'2GGF'= Editorial|Periodicals|Scholarly Journal|Internet Website
'2GGG'= Editorial|Periodicals|Scholarly Journal|Internet Downloadable File
'2GGH'= Editorial|Periodicals|Scholarly Journal|All Electronic Distribution Formats
'2GGI'= Editorial|Periodicals|Scholarly Journal|All Internet Distribution Formats
'2GGM'= Editorial|Merchandise|CD ROM|Recordable Media
'2GHA'= Editorial|Website|Webcast, All Types|Internet Website
'2GHB'= Editorial|Website|Web Page, All Types|Internet Website
'2GHC'= Editorial|Website|Web Page, All Types|Recordable Media
'2GHD'= Editorial|Website|Web Page, All Types|All Electronic Distribution Formats
'2GHF'= Editorial|Website|Web Page, All Types|All Internet Distribution Formats
'2GHG'= Editorial|Website|Web Page, Body Content|Internet Website
'2GHH'= Editorial|Website|Web Page, Body Content|Recordable Media
'2GHI'= Advertising|Periodicals|Magazine, Consumer|Recordable Media
'2GHJ'= Editorial|Website|Web Page, Body Content|All Electronic Distribution Formats
'2GHK'= Editorial|Website|Web Page, Body Content|All Internet Distribution Formats
'2GIB'= Advertising|Periodicals|Magazine, Consumer|All Internet Distribution Formats
'2GID'= Internal Company Use|Internal Review|All Review Types|All Electronic Distribution Formats
'2GIE'= Advertising|Periodicals|Magazine, Corporate|Internet Downloadable File
'2GIF'= Products|Merchandise|Gift Certificate|Printed
'2GIG'= Products|Merchandise|Datebook|Printed
'2GIN'= Advertising|Periodicals|Magazine, Corporate|Internet Website
'2GIP'= Advertising|Periodicals|Magazine, Corporate|Recordable Media
'2GIT'= Advertising|Periodicals|Magazine, Corporate|All Electronic Distribution Formats
'2GNU'= Advertising|Periodicals|Magazine, Corporate|All Internet Distribution Formats
'2GOA'= Internal Company Use|Promotional Materials|Corporate Brochure|Printed
'2GOB'= Advertising|Periodicals|Magazine, Advertorial|All Internet Distribution Formats
'2GOO'= Advertising|Periodicals|Magazine, All Types|Recordable Media
'2GOR'= Advertising|Periodicals|Magazine, Custom Published|Internet Website
'2GOS'= Advertising|Periodicals|Magazine, Custom Published|Internet Downloadable File
'2GOX'= Advertising|Periodicals|Magazine, Custom Published|All Internet Distribution Formats
'2GOY'= Internal Company Use|Art|Art Display, All Art Types|Printed
'2GRE'= Products|Merchandise|Card, Greeting Card|Printed
'2GUL'= Advertising|Periodicals|Magazine, Education|Internet Downloadable File
'2GUM'= Advertising|Periodicals|Magazine, Education|Internet Website
'2GUN'= Advertising|Point of Purchase|Kiosk, Interactive Kiosk|Electronic Display
'2GUT'= Advertising|Marketing Materials|Promotional Calendar, One Page|Recordable Media
'2GUV'= Advertising|Periodicals|Magazine, Education|Recordable Media
'2GUY'= Advertising|Periodicals|Magazine, Education|All Electronic Distribution Formats
'2GYM'= Advertising|Point of Purchase|Kiosk, Interactive Kiosk|Printed
'2GYP'= Advertising|Periodicals|Magazine, Education|All Internet Distribution Formats
'2HAD'= Advertising|Marketing Materials|Promotional Calendar, Multi-Page|Recordable Media
'2HAE'= Advertising|Periodicals|Magazine, Trade|Internet Downloadable File
'2HAG'= Advertising|Periodicals|Magazine, Trade|Internet Website
'2HAH'= Advertising|Periodicals|Magazine, Trade|All Electronic Distribution Formats
'2HAJ'= Advertising|Periodicals|Magazine, Trade|All Internet Distribution Formats
'2HAM'= Advertising|Marketing Materials|All Marketing Material Types|Internet Downloadable File
'2HAN'= Editorial|Book|Retail Book, Handbook|Printed
'2HAO'= Internal Company Use|Art|Art Display, All Art Types|Electronic Display
'2HAP'= Advertising|Periodicals|Magazine Reprints, All Types|Internet Downloadable File
'2HAS'= Advertising|Periodicals|Magazine Reprints, All Types|Internet Website
'2HAT'= Advertising|Periodicals|Magazine, Trade|Printed
'2HAW'= Advertising|Periodicals|Magazine Reprints, All Types|Recordable Media
'2HAY'= Advertising|Periodicals|Magazine Reprints, All Types|All Electronic Distribution Formats
'2HEH'= Advertising|Periodicals|Magazine Reprints, All Types|All Internet Distribution Formats
'2HEM'= Advertising|Display|Billboard, Bulletin|Printed
'2HEN'= Advertising|Periodicals|Newsletter, All Types|All Electronic Distribution Formats
'2HEP'= Advertising|Periodicals|Newsletter, All Types|Internet Downloadable File
'2HER'= Products|Merchandise|Card, Hero Card|Printed
'2HES'= Advertising|Periodicals|Newsletter, All Types|Internet Website
'2HET'= Advertising|Periodicals|Newsletter, All Types|Recordable Media
'2HEW'= Advertising|Periodicals|Newsletter, All Types|All Internet Distribution Formats
'2HEX'= Products|Merchandise|Screen Saver|All Electronic Distribution Formats
'2HEY'= Advertising|Periodicals|Newspaper, All Types|All Electronic Distribution Formats
'2HIC'= Advertising|Periodicals|Newspaper, All Types|All Internet Distribution Formats
'2HID'= Advertising|Periodicals|Newspaper, All Types|Internet Website
'2HIE'= Advertising|Periodicals|Newspaper, All Types|Internet Downloadable File
'2HIL'= Editorial|Book|Retail Book, Hi-lo Book|Printed
'2HIM'= Advertising|Periodicals|Newspaper, All Types|Recordable Media
'2HIP'= Advertising|Periodicals|Free Standing Insert, Advertorial Insert|Printed
'2HIS'= Advertising|Periodicals|Newspaper, Weekly Supplement|All Electronic Distribution Formats
'2HIT'= Motion Picture & TV|Motion Picture|Prop|All Internet Distribution Formats
'2HMM'= Advertising|Periodicals|Newspaper, Weekly Supplement|All Internet Distribution Formats
'2HOB'= Advertising|Periodicals|Newspaper, Weekly Supplement|Internet Website
'2HOD'= Advertising|Periodicals|Newspaper, Weekly Supplement|Internet Downloadable File
'2HOE'= Advertising|Periodicals|Newspaper, Weekly Supplement|Recordable Media
'2HOG'= Advertising|Book|All Book Types|Printed
'2HON'= Advertising|Display|Poster, Movie Poster|Electronic Display
'2HOP'= Motion Picture & TV|Motion Picture|Prop|Projected Display
'2HOT'= Advertising|Display|Terminal Advertising, Airport Display|Printed
'2HOW'= Advertising|Periodicals|Newspaper, Tabloid|All Electronic Distribution Formats
'2HOY'= Advertising|Periodicals|Newspaper, Tabloid|Internet Website
'2HUB'= Products|Merchandise|Double Postcard|Printed
'2HUE'= Advertising|Periodicals|Newspaper, Tabloid|Recordable Media
'2HUG'= Motion Picture & TV|Motion Picture|Set Decor|Recordable Media
'2HUH'= Advertising|Periodicals|Newspaper, Tabloid|All Internet Distribution Formats
'2HUM'= Motion Picture & TV|Motion Picture|Set Decor|All Electronic Distribution Formats
'2HUP'= Advertising|Periodicals|Quarterly Report|Internet Downloadable File
'2HUT'= Advertising|Periodicals|Quarterly Report|Internet Website
'2HYP'= Advertising|Periodicals|Quarterly Report|Recordable Media
'2JAB'= Motion Picture & TV|Motion Picture|Set Decor|All Internet Distribution Formats
'2JAG'= Advertising|Periodicals|Quarterly Report|All Electronic Distribution Formats
'2JAM'= Advertising|Marketing Materials|All Marketing Material Types|Printed
'2JAR'= Advertising|Periodicals|Magazine, Corporate|Printed
'2JAW'= Advertising|Live Presentation|Stage Performance|Projected Display
'2JAY'= Advertising|Periodicals|Newsletter, All Types|Printed
'2JEE'= Advertising|Periodicals|Quarterly Report|All Internet Distribution Formats
'2JET'= Editorial|Periodicals|Newspaper, All Types|Printed
'2JIB'= Advertising|Marketing Materials|Public Relations, All Types|Printed
'2JIG'= Products|Merchandise|Jigsaw Puzzle|Printed
'2JIL'= Products|Merchandise|Jigsaw Puzzle|Recordable Media
'2JIN'= Products|Merchandise|Jigsaw Puzzle|Internet Downloadable File
'2JOB'= Advertising|Marketing Materials|Public Relations, Press Kit|Recordable Media
'2JOE'= Advertising|Marketing Materials|Public Relations, Press Kit|All Electronic Distribution Formats
'2JOG'= Motion Picture & TV|Motion Picture|Documentary Film|Projected Display
'2JOT'= Motion Picture & TV|Motion Picture|In Theater Commercial|Projected Display
'2JOU'= Advertising|Display|Terminal Advertising, Airport Display|Electronic Display
'2JOW'= Advertising|Marketing Materials|Public Relations, Press Kit|All Internet Distribution Formats
'2JOY'= Advertising|Display|Terminal Advertising, All Types|Printed
'2JRN'= Products|Merchandise|Journal|Printed
'2JUG'= Advertising|Point of Purchase|Kiosk, Telephone Kiosk|Printed
'2JUN'= Advertising|Marketing Materials|Public Relations, Press Kit|Internet Downloadable File
'2JUS'= Advertising|Marketing Materials|Public Relations, Press Kit|Internet Email
'2JUT'= Advertising|Marketing Materials|Public Relations, Press Kit|Internet Website
'2JWL'= Advertising|Website|Web Page, Web Interstitial Ad|Internet Website
'2KAB'= Advertising|Marketing Materials|Public Relations, Press Kit|Television Broadcast
'2KAF'= Advertising|Marketing Materials|Public Relations, Press Kit|Printed
'2KAS'= Advertising|Marketing Materials|Public Relations, Press Release|Recordable Media
'2KAT'= Advertising|Marketing Materials|Public Relations, Press Release|Internet Downloadable File
'2KAY'= Advertising|Marketing Materials|Public Relations, Press Release|Internet Email
'2KEA'= Advertising|Marketing Materials|Public Relations, Press Release|Internet Website
'2KEF'= Advertising|Marketing Materials|Public Relations, Press Release|Television Broadcast
'2KEG'= Advertising|Periodicals|Newspaper, Weekly Supplement|Printed
'2KEN'= Advertising|Periodicals|Newspaper, Tabloid|Printed
'2KEP'= Products|Merchandise|Souvenir|Printed
'2KEX'= Advertising|Marketing Materials|Public Relations, Press Release|Printed
'2KID'= Advertising|Marketing Materials|Public Relations, Press Release|All Electronic Distribution Formats
'2KIN'= Editorial|Periodicals|Magazine, Consumer|Internet Email
'2KIO'= Advertising|Point of Purchase|Kiosk, All Types|Electronic Display
'2KIP'= Advertising|Periodicals|Program Advertising|Printed
'2KIT'= Personal Use|Art|Art Display, All Art Types|Printed
'2KOA'= Motion Picture & TV|Television Programming|Artist's Reference, All Types|Projected Display
'2KOB'= Motion Picture & TV|Television Programming|Artist's Reference, All Types|Internet Downloadable File
'2KOI'= Motion Picture & TV|Television Programming|Artist's Reference, All Types|All Internet Distribution Formats
'2KOP'= Motion Picture & TV|Television Programming|Artist's Reference, All Types|Recordable Media
'2KOR'= Motion Picture & TV|Television Programming|Artist's Reference, All Types|All Electronic Distribution Formats
'2KOS'= Motion Picture & TV|Television Programming|Artist's Reference, All Types|Television Broadcast
'2KUE'= Advertising|Website|Web Page, All Types|All Internet Distribution Formats
'2MAB'= Products|Merchandise|Map|All Electronic Distribution Formats
'2MAC'= Advertising|Website|Web Page, All Types|Recordable Media
'2MAD'= Products|Merchandise|Map|Internet Downloadable File
'2MAE'= Advertising|Website|Web Page, All Types|All Electronic Distribution Formats
'2MAG'= Editorial|Periodicals|Magazine, All Types|Printed
'2MAN'= Editorial|Book|Retail Book, Manual|Printed
'2MAP'= Products|Merchandise|Map|Printed
'2MAR'= Advertising|Website|Web Page, Design Element|Internet Website
'2MAS'= Advertising|Website|Web Page, Design Element|Recordable Media
'2MAT'= Products|Merchandise|Placemat|Printed
'2MAW'= Advertising|Website|Web Page, Design Element|All Electronic Distribution Formats
'2MAX'= Advertising|Website|Web Page, Design Element|All Internet Distribution Formats
'2MAY'= Editorial|Periodicals|Magazine, Education|Printed
'2MCH'= Products|Merchandise|Other Merchandise|Printed
'2MED'= Advertising|Website|Web Page, Web Banner Ad|Internet Website
'2MEG'= Advertising|Website|Web Page, Web Banner Ad|Recordable Media
'2MEL'= Editorial|Periodicals|Magazine, Partworks|Printed
'2MEM'= Advertising|Website|Web Page, Web Banner Ad|All Electronic Distribution Formats
'2MEN'= Advertising|Point of Purchase|Menu|Printed
'2MER'= Products|Merchandise|All Merchandise Types|Printed
'2MET'= Products|Merchandise|Map|Internet Email
'2MEW'= Advertising|Website|Web Page, Web Banner Ad|All Internet Distribution Formats
'2MIB'= Advertising|Website|Web Page, Web Interstitial Ad|Recordable Media
'2MID'= Advertising|Website|Web Page, Web Interstitial Ad|All Electronic Distribution Formats
'2MIG'= Advertising|Website|Web Page, Web Interstitial Ad|All Internet Distribution Formats
'2MIL'= Advertising|Website|Webcast, All Types|Internet Website
'2MIM'= Products|Merchandise|Map|All Internet Distribution Formats
'2MIR'= Products|Merchandise|Map|Recordable Media
'2MIX'= Motion Picture & TV|Motion Picture|Artist's Reference, All Types|Recordable Media
'2MMA'= Advertising|Marketing Materials|Bill Insert|Printed
'2MMB'= Advertising|Marketing Materials|Blow In Card|Printed
'2MMC'= Advertising|Marketing Materials|Bound-in Insert|Printed
'2MMD'= Advertising|Marketing Materials|Broadside|Printed
'2MME'= Advertising|Marketing Materials|Buckslip|Printed
'2MMF'= Advertising|Marketing Materials|Business Card|Printed
'2MMG'= Advertising|Marketing Materials|Business Envelope|Printed
'2MMI'= Advertising|Marketing Materials|Business Invitation|Printed
'2MMJ'= Advertising|Marketing Materials|Business Reply Card|Printed
'2MMK'= Advertising|Marketing Materials|Business Reply Envelope|Printed
'2MML'= Advertising|Marketing Materials|Business Stationery|Printed
'2MMM'= Advertising|Marketing Materials|Compliment Slip|Printed
'2MMN'= Advertising|Marketing Materials|Coupon|Printed
'2MMP'= Advertising|Marketing Materials|Coupon Packs|Printed
'2MMQ'= Advertising|Marketing Materials|Flyaway Card|Printed
'2MMR'= Advertising|Marketing Materials|Leaflet|Printed
'2MMS'= Advertising|Marketing Materials|Magalog|Printed
'2MMT'= Advertising|Marketing Materials|One Sheet|Printed
'2MMU'= Advertising|Marketing Materials|Onsert|Printed
'2MMV'= Advertising|Marketing Materials|Polybag|Printed
'2MMW'= Advertising|Marketing Materials|Promotional Calendar, One Page|Printed
'2MMX'= Advertising|Marketing Materials|Promotional Envelope|Printed
'2MMY'= Advertising|Marketing Materials|Sales Kit|Printed
'2MMZ'= Advertising|Marketing Materials|Self Mailer|Printed
'2MOB'= Advertising|Display|Store Display, In-Store Poster|Printed
'2MOG'= Editorial|Book|All Book Types|Printed
'2MOM'= Editorial|Periodicals|Magazine, Trade|Printed
'2MOO'= Advertising|Periodicals|Magazine, All Types|Printed
'2MOP'= Editorial|Periodicals|Magazine, Custom Published|Printed
'2MOR'= Editorial|Book|Artist's Reference, All Types|Printed
'2MOU'= Products|Merchandise|Mouse Pad|Printed
'2MOV'= Motion Picture & TV|Motion Picture|All Motion Picture Types|Recordable Media
'2MOW'= Editorial|Periodicals|Magazine, Trade|Internet Website
'2MPA'= Motion Picture & TV|Motion Picture|All Motion Picture Types|Television Broadcast
'2MPB'= Motion Picture & TV|Music Video|All Music Video Types|Television Broadcast
'2MPC'= Motion Picture & TV|Motion Picture|Artist's Reference, All Types|Television Broadcast
'2MPD'= Motion Picture & TV|Motion Picture|Documentary Film|Television Broadcast
'2MPE'= Motion Picture & TV|Motion Picture|Feature Film|Television Broadcast
'2MPF'= Motion Picture & TV|Motion Picture|Movie Trailer|Television Broadcast
'2MPG'= Motion Picture & TV|Motion Picture|Prop|Television Broadcast
'2MPH'= Motion Picture & TV|Motion Picture|Set Decor|Television Broadcast
'2MPI'= Motion Picture & TV|Motion Picture|Short Film|Television Broadcast
'2MPJ'= Motion Picture & TV|Motion Picture|All Motion Picture Types|Internet Downloadable File
'2MPK'= Motion Picture & TV|Music Video|All Music Video Types|Internet Downloadable File
'2MPL'= Motion Picture & TV|Motion Picture|Artist's Reference, All Types|Internet Downloadable File
'2MPM'= Motion Picture & TV|Motion Picture|Documentary Film|Internet Downloadable File
'2MPN'= Motion Picture & TV|Motion Picture|Feature Film|Internet Downloadable File
'2MPP'= Motion Picture & TV|Motion Picture|Movie Trailer|Internet Downloadable File
'2MPQ'= Motion Picture & TV|Motion Picture|Prop|Internet Downloadable File
'2MPR'= Motion Picture & TV|Motion Picture|Set Decor|Internet Downloadable File
'2MPS'= Motion Picture & TV|Motion Picture|Short Film|Internet Downloadable File
'2MRP'= Products|Merchandise|Plates|Printed
'2MUG'= Products|Merchandise|Mugs|Printed
'2MUS'= Editorial|Display|Museum Display|Printed
'2MUT'= Editorial|Periodicals|Magazine, Trade|Internet Downloadable File
'2NAB'= Editorial|Periodicals|Magazine, Trade|Internet Email
'2NAG'= Editorial|Periodicals|Magazine, Trade|All Internet Distribution Formats
'2NAH'= Editorial|Book|Reference Book, Encyclopedia|All Electronic Distribution Formats
'2NAP'= Motion Picture & TV|Motion Picture|Artist's Reference, All Types|All Electronic Distribution Formats
'2NAY'= Editorial|Book|Reference Book, Encyclopedia|Printed
'2NEL'= Editorial|Periodicals|Newsletter|Internet Email
'2NET'= Internal Company Use|Website|Web Page, All Types|Intranet and Extranet Website
'2NEW'= Advertising|Periodicals|Newspaper, All Types|Printed
'2NIP'= Editorial|Book|Reference Book, Telephone Book|Printed
'2NOB'= Editorial|Periodicals|Magazine, Trade|All Electronic Distribution Formats
'2NOD'= Motion Picture & TV|Motion Picture|Artist's Reference, All Types|All Internet Distribution Formats
'2NOV'= Products|Merchandise|Novelty Products|Printed
'2NOW'= Editorial|Periodicals|Magazine, Education|Internet Email
'2NUN'= Editorial|Periodicals|Magazine, Custom Published|Internet Email
'2NUT'= Advertising|Marketing Materials|Promotional Postcard|Printed
'2TAB'= Editorial|Periodicals|Magazine, Partworks|Internet Email
'2TAD'= Editorial|Periodicals|Scholarly Journal|Internet Email
'2TAE'= Editorial|Book|Retail Book, Coffee Table Book|Printed
'2TAG'= Advertising|Point of Purchase|Hang Tag|Printed
'2TAJ'= Editorial|Periodicals|Newspaper, All Types|Internet Email
'2TAL'= Advertising|Point of Purchase|Shelf Talker|Printed
'2TAN'= Advertising|Display|Terminal Advertising, Shelter Advertising|Printed
'2TAO'= Editorial|Periodicals|Newspaper, Weekly Supplement|Internet Email
'2TAP'= Motion Picture & TV|Motion Picture|Artist's Reference, All Types|Projected Display
'2TAT'= Personal Use|Art|Artist's Reference, Tattoo|Printed
'2TAV'= Editorial|Book|Retail Book, Concept Book|Printed
'2TAX'= Advertising|Display|Transit Advertising, Taxi Advertising|Printed
'2TEA'= Motion Picture & TV|Television Programming|All Television Advertising Types|Internet Downloadable File
'2TEB'= Motion Picture & TV|Television Programming|Commercial|Internet Downloadable File
'2TEC'= Motion Picture & TV|Television Programming|Commercial|All Internet Distribution Formats
'2TED'= Motion Picture & TV|Television Programming|All Television Advertising Types|Recordable Media
'2TEE'= Motion Picture & TV|Television Programming|All Editorial Television Types|Television Broadcast
'2TEF'= Motion Picture & TV|Television Programming|News Program, Flash|Television Broadcast
'2TEG'= Motion Picture & TV|Television Programming|Commercial|Recordable Media
'2TEH'= Motion Picture & TV|Television Programming|Infomercial|Internet Downloadable File
'2TEJ'= Motion Picture & TV|Television Programming|Infomercial|All Internet Distribution Formats
'2TEK'= Motion Picture & TV|Television Programming|Infomercial|Recordable Media
'2TEL'= Motion Picture & TV|Television Programming|On-Air Promotion|Internet Downloadable File
'2TEM'= Motion Picture & TV|Television Programming|On-Air Promotion|All Internet Distribution Formats
'2TEP'= Motion Picture & TV|Television Programming|On-Air Promotion|Recordable Media
'2TEQ'= Motion Picture & TV|Television Programming|All Television Advertising Types|All Internet Distribution Formats
'2TER'= Motion Picture & TV|Television Programming|Documentary Program|Internet Downloadable File
'2TES'= Motion Picture & TV|Television Programming|Documentary Program|All Internet Distribution Formats
'2TET'= Motion Picture & TV|Television Programming|Documentary Program|Recordable Media
'2TEU'= Motion Picture & TV|Television Programming|Educational Program|Internet Downloadable File
'2TEV'= Motion Picture & TV|Television Programming|Educational Program|All Internet Distribution Formats
'2TEW'= Motion Picture & TV|Television Programming|Educational Program|Recordable Media
'2TEX'= Editorial|Periodicals|Newspaper, Tabloid|Internet Website
'2TEY'= Motion Picture & TV|Television Programming|Entertainment Program|Internet Downloadable File
'2TEZ'= Motion Picture & TV|Television Programming|Entertainment Program|All Internet Distribution Formats
'2TIC'= Products|Merchandise|Sticker|Printed
'2TIE'= Editorial|Periodicals|Newspaper, Tabloid|Recordable Media
'2TIN'= Advertising|Point of Purchase|Slip Case|Printed
'2TIP'= Editorial|Periodicals|Newspaper, Tabloid|Internet Email
'2TKT'= Products|Merchandise|Ticket|Printed
'2TLA'= Motion Picture & TV|Television Programming|Entertainment Program|Recordable Media
'2TLB'= Motion Picture & TV|Television Programming|Made For TV Movie|All Internet Distribution Formats
'2TLC'= Motion Picture & TV|Television Programming|Made For TV Movie|Internet Downloadable File
'2TLD'= Motion Picture & TV|Television Programming|Made For TV Movie|Recordable Media
'2TLE'= Motion Picture & TV|Television Programming|News Program|Internet Downloadable File
'2TLF'= Motion Picture & TV|Television Programming|News Program|All Internet Distribution Formats
'2TLG'= Motion Picture & TV|Television Programming|News Program|Recordable Media
'2TLH'= Motion Picture & TV|Television Programming|Non Broadcast Pilot|Recordable Media
'2TLJ'= Motion Picture & TV|Television Programming|Non Broadcast Pilot|Projected Display
'2TLK'= Motion Picture & TV|Television Programming|Non-Profit Program|Internet Downloadable File
'2TLL'= Motion Picture & TV|Television Programming|Non-Profit Program|All Internet Distribution Formats
'2TLM'= Motion Picture & TV|Television Programming|Non-Profit Program|Recordable Media
'2TLN'= Motion Picture & TV|Television Programming|Prop|Internet Downloadable File
'2TLP'= Motion Picture & TV|Television Programming|Prop|All Internet Distribution Formats
'2TLQ'= Motion Picture & TV|Television Programming|Prop|Recordable Media
'2TLR'= Motion Picture & TV|Television Programming|Prop|Television Broadcast
'2TLS'= Motion Picture & TV|Television Programming|Set Decor|Internet Downloadable File
'2TLT'= Motion Picture & TV|Television Programming|Set Decor|All Internet Distribution Formats
'2TLU'= Motion Picture & TV|Television Programming|Set Decor|Recordable Media
'2TLV'= Motion Picture & TV|Television Programming|Set Decor|Television Broadcast
'2TLW'= Motion Picture & TV|Television Programming|All Editorial Television Types|Internet Downloadable File
'2TLY'= Motion Picture & TV|Television Programming|All Editorial Television Types|All Internet Distribution Formats
'2TLZ'= Motion Picture & TV|Television Programming|All Editorial Television Types|Recordable Media
'2TOD'= Products|Merchandise|Screen Saver|All Internet Distribution Formats
'2TOE'= Advertising|Live Presentation|Trade Show Presentation|Projected Display
'2TOM'= Editorial|Periodicals|Newsletter|Printed
'2TON'= Editorial|Periodicals|Newspaper, Tabloid|All Internet Distribution Formats
'2TOP'= Products|Merchandise|Poster, Retail Poster|Printed
'2TOT'= Editorial|Periodicals|Newspaper, Tabloid|All Electronic Distribution Formats
'2TOY'= Products|Merchandise|Toy|Printed
'2TRA'= Advertising|Display|Transit Advertising, All Types|Printed
'2TRB'= Advertising|Display|Transit Advertising, All Types|Electronic Display
'2TRC'= Advertising|Display|Transit Advertising, Bus Panel|Electronic Display
'2TRD'= Products|Merchandise|Trading Cards|Printed
'2TRE'= Advertising|Display|Transit Advertising, Bus Poster|Printed
'2TRF'= Advertising|Display|Transit Advertising, Bus Rear Display|Electronic Display
'2TRG'= Advertising|Display|Transit Advertising, Bus Rear Display|Printed
'2TRH'= Advertising|Display|Transit Advertising, Ferry Advertising|Electronic Display
'2TRI'= Advertising|Display|Transit Advertising, Subway Advertising|Electronic Display
'2TRJ'= Advertising|Display|Transit Advertising, Subway Advertising|Printed
'2TRK'= Advertising|Display|Transit Advertising, Train Advertising|Electronic Display
'2TRL'= Advertising|Display|Transit Advertising, Train Advertising|Printed
'2TRM'= Advertising|Display|Transit Advertising, Bus Wrap|Electronic Display
'2TRN'= Advertising|Display|Transit Advertising, Bus Wrap|Printed
'2TRQ'= Advertising|Display|Transit Advertising, Commercial Vehicles|Printed
'2TRR'= Advertising|Display|Transit Advertising, Commercial Vehicles|Electronic Display
'2TRY'= Motion Picture & TV|Motion Picture|Documentary Film|Recordable Media
'2TST'= Products|Merchandise|Apparel, T-Shirts|Printed or Woven
'2TUA'= Products|Merchandise|Virtual Reality|Recordable Media
'2TUB'= Editorial|Periodicals|Newspaper, Weekly Supplement|Printed
'2TUG'= Motion Picture & TV|Motion Picture|Documentary Film|All Internet Distribution Formats
'2TUX'= Internal Company Use|Periodicals|Magazine, Custom Published|Printed
'2TVA'= Motion Picture & TV|Television Programming|All Television Advertising Types|Television Broadcast
'2TVB'= Motion Picture & TV|Television Programming|All Television Advertising Types|All Electronic Distribution Formats
'2TVC'= Motion Picture & TV|Television Programming|Commercial|Television Broadcast
'2TVD'= Motion Picture & TV|Television Programming|Commercial|All Electronic Distribution Formats
'2TVE'= Motion Picture & TV|Television Programming|Infomercial|Television Broadcast
'2TVF'= Motion Picture & TV|Television Programming|Infomercial|All Electronic Distribution Formats
'2TVG'= Motion Picture & TV|Television Programming|On-Air Promotion|Television Broadcast
'2TVH'= Motion Picture & TV|Television Programming|On-Air Promotion|All Electronic Distribution Formats
'2TVI'= Motion Picture & TV|Television Programming|Documentary Program|Television Broadcast
'2TVJ'= Motion Picture & TV|Television Programming|Documentary Program|All Electronic Distribution Formats
'2TVK'= Motion Picture & TV|Television Programming|Educational Program|Television Broadcast
'2TVL'= Motion Picture & TV|Television Programming|Educational Program|All Electronic Distribution Formats
'2TVM'= Motion Picture & TV|Television Programming|Entertainment Program|Television Broadcast
'2TVN'= Motion Picture & TV|Television Programming|Entertainment Program|All Electronic Distribution Formats
'2TVP'= Motion Picture & TV|Television Programming|Made For TV Movie|All Electronic Distribution Formats
'2TVQ'= Motion Picture & TV|Television Programming|Set Decor|All Electronic Distribution Formats
'2TVR'= Motion Picture & TV|Music Video|All Music Video Types|All Electronic Distribution Formats
'2TVS'= Motion Picture & TV|Television Programming|News Program|Television Broadcast
'2TVT'= Motion Picture & TV|Television Programming|News Program|All Electronic Distribution Formats
'2TVU'= Motion Picture & TV|Television Programming|Non-Profit Program|Television Broadcast
'2TVV'= Motion Picture & TV|Television Programming|Non-Profit Program|All Electronic Distribution Formats
'2TVW'= Motion Picture & TV|Television Programming|Prop|All Electronic Distribution Formats
'2TVY'= Motion Picture & TV|Television Programming|Made For TV Movie|Television Broadcast
'2TVZ'= Motion Picture & TV|Television Programming|All Editorial Television Types|All Electronic Distribution Formats
'2UNL'= Unlicensed|Not Applicable|Not Applicable|Not Applicable
'2WAB'= Editorial|Book|Retail Book, Illustrated Book|Printed
'2WAG'= Motion Picture & TV|Motion Picture|Feature Film|All Internet Distribution Formats
'2WAL'= Products|Merchandise|Wallpaper|Printed
'2WAN'= Internal Company Use|Website|All Website Types|Intranet and Extranet Website
'2WAR'= Products|Merchandise|Computer Software|All Electronic Distribution Formats
'2WAX'= Advertising|Point of Purchase|Table Tent|Printed
'2WEB'= Advertising|Website|Web Page, All Types|Internet Website
'2WED'= Personal Use|Personal Review|All Review Types|Printed
'2WEE'= Products|Merchandise|Stamp|Printed
'2WET'= Advertising|Display|Poster, Restroom Poster|Electronic Display
'2WHA'= Editorial|Book|Retail Book, Illustrated Guide|Printed
'2WHO'= Products|Product Packaging|Wholesale Packaging, All Packaging Types|Printed
'2WIG'= Editorial|Periodicals|Scholarly Journal|Printed
'2WIN'= Motion Picture & TV|Motion Picture|Feature Film|Recordable Media
'2WIT'= Products|Merchandise|Stationery|Printed
'2WIZ'= Motion Picture & TV|Motion Picture|Short Film|Projected Display
'2WRP'= Products|Merchandise|Gift Wrap|Printed
'2WRY'= Products|Merchandise|Screen Saver|Recordable Media
'2YAK'= Editorial|Book|Textbook Ancillary Materials, Teachers' Edition|Printed
'2YAM'= Advertising|Marketing Materials|Promotional Calendar, Multi-Page|Printed
'2YAP'= Editorial|Book|Retail Book, Novelty Book|Printed
'2YEA'= Editorial|Book|Retail Book, Directory|All Electronic Distribution Formats
'2YEN'= Editorial|Book|Retail Book, Postcard Book|Printed
'2YET'= Editorial|Book|Textbook Ancillary Materials, Packaging For Recordable Media|Printed
'2YOK'= Editorial|Book|Retail Book, Young Adult Book|Printed
'2YUM'= Editorial|Book|Textbook Ancillary Materials, Lab Manual|Printed
'2ZAG'= Editorial|Mobile|All Mobile Types|Mobile
'2ZAM'= Products|Mobile|All Mobile Types|Mobile
'2ZAP'= Products|Mobile|Entertainment Programming|Mobile
'2ZEN'= Products|Mobile|Computer Software|Mobile
'2ZIG'= Products|Mobile|Wallpaper|Mobile
'2ZIP'= Products|Mobile|Game, All Types|Mobile
'2ZIT'= Personal Use|Mobile|Wallpaper|Mobile
'2ZOA'= Editorial|Book|Textbook, Course Pack|Recordable Media
'2ZOB'= Personal Use|Mobile|All Mobile Types|Mobile
'2ZOO'= Editorial|Book|Textbook, Course Pack|Internet Email
'2ZOT'= Editorial|Book|Textbook Ancillary Materials, Teacher's Manual|Printed
'2ZUM'= Internal Company Use|Mobile|All Mobile Types|Mobile
'2ZUS'= Advertising|Mobile|All Mobile Types|Mobile
'3PAA'= Any Placements on All Pages
'3PNB'= Multiple Placements on One Side
'3PNC'= Multiple Placements on Back Cover
'3PND'= Single Placement as Chapter Opener
'3PNE'= Single Placement on Front Side
'3PNF'= Single Placement on Front Cover And Back Cover
'3PNH'= Multiple Placements in Body Of Program
'3PNI'= Single Placement as Chapter Opener
'3PNJ'= Multiple Placements on Pop Ups
'3PNK'= Multiple Placements on Front Side
'3PNL'= Multiple Placements on Item
'3PNM'= Single Placement in Body Of Program
'3PNN'= Single Placement on Front Cover
'3PNP'= Single Placement on Both Sides
'3PNQ'= Single Placement in Content Body
'3PNR'= Single Placement on Back Cover
'3PNS'= Multiple Placements on Splash Pages
'3PNT'= Single Placement on Front Page
'3PNU'= Multiple Placements on Home Page
'3PNV'= Multiple Placements on Front Side
'3PNW'= Single Placement in Body Of Program
'3PNX'= Multiple Placements as Flash
'3PNY'= Multiple Placements on Inside Cover
'3PNZ'= Single Placement in Bibliography
'3PPA'= Single Placement on Dust Jacket
'3PPB'= Single Placement in Closing Sequence
'3PPC'= Multiple Placements in Packaging Interior
'3PPD'= Single Placement in Packaging Interior
'3PPE'= Single Placement as Flash
'3PPF'= Multiple Placements on Front Cover
'3PPG'= Single Placement on Splash Page
'3PPH'= Single Placement on Inside
'3PPI'= Multiple Placements on Landing Pages
'3PPJ'= Multiple Placements in Body Of Advertisement
'3PPK'= Single Placement on Inside Cover
'3PPL'= Single Placement on Both Sides
'3PPM'= Multiple Placements on Any Pages
'3PPN'= Multiple Placements in Closing Sequence
'3PPP'= Multiple Placements on Front Cover And Interior Pages
'3PPQ'= Multiple Placements on Home Page And Secondary Pages
'3PPR'= Single Placement in Any Part
'3PPS'= Single Placement on Landing Page
'3PPU'= Single Placement on Title Page
'3PPV'= Multiple Placements on Front Cover
'3PPW'= Single Placement as Forward
'3PPX'= Single Placement on Packaging Exterior|Front
'3PPY'= Single Placement on Home Page
'3PPZ'= Multiple Placements in Body Of Program
'3PQA'= Single Placement as Frontispiece
'3PQB'= Multiple Placements on Wrap Around Cover
'3PQC'= Single Placement on Back Side
'3PQD'= Single Placement on Secondary Page
'3PQE'= Single Placement on Spine
'3PQF'= Single Placements in Bibliography
'3PQG'= Single Placement on Any Interior Page
'3PQH'= Single Placement on Back Cover
'3PQI'= Single Placement on Preface
'3PQJ'= Single Placement on Wrap Around Cover
'3PQK'= Multiple Placements on Both Sides
'3PQL'= Multiple Placements in Closing Sequence
'3PQM'= Multiple Placements on Back Side
'3PQN'= Single Placement as Vignette
'3PQP'= Single Placement on Pop Up
'3PQQ'= Single Placement as Unit Opener
'3PQS'= Multiple Placements on Back Cover
'3PQT'= Single Placement on Table Of Contents
'3PQU'= Single Placement in Closing Sequence
'3PQW'= Multiple Placements on Any Interior Pages
'3PQX'= Single Placement on Any Interior Page
'3PQY'= Single Placement on Front Cover
'3PQZ'= Single Placement on Index
'3PRB'= Single Placement on Front Cover
'3PRC'= Single Placement on Table Of Contents
'3PRD'= Multiple Placements in Content Body
'3PRF'= Multiple Placements on Any Interior Pages
'3PRG'= Multiple Placements on Front Cover And Back Cover
'3PRH'= Single Placement on Any Interior Page
'3PRI'= Single Placement as Colophon
'3PRJ'= Multiple Placements on Front Cover
'3PRK'= Multiple Placements on Inside
'3PRL'= Multiple Placements on Any Interior Pages
'3PRM'= Single Placement on Front Cover And Back Cover
'3PRN'= Single Placement on Any Interior Page
'3PRP'= Single Placement in Body Of Advertisement
'3PRQ'= Single Placement on Screen
'3PRR'= Single Placement in Title Sequence
'3PRS'= Single Placement in Content Body
'3PRT'= Single Placement on Pop Up
'3PRU'= Single Placement on Front Side
'3PRV'= Multiple Placements on Both Sides
'3PRW'= Multiple Placements in Any Part
'3PRY'= Multiple Placements in Content Body
'3PRZ'= Multiple Placements on Front Cover
'3PSA'= Single Placement as Front Matter
'3PSB'= Multiple Placements on Back Side
'3PSC'= Single Placement in Title Sequence
'3PSD'= Multiple Placements on Screen
'3PSF'= Multiple Placements on Back Cover
'3PSH'= Single Placement on Back Cover
'3PSI'= Multiple Placements on Packaging Exterior|Front
'3PSJ'= Single Placement on Front Cover
'3PSK'= Multiple Placements in Title Sequence
'3PSL'= Single Placement on Flap
'3PSM'= Multiple Placements on Any Interior Pages
'3PSN'= Multiple Placements on Back Cover
'3PSP'= Single Placement on Back Cover
'3PSQ'= Single Placement on Back Side
'3PSR'= Single Placement on Inside Cover
'3PSS'= Multiple Placements on Front Cover And Back Cover
'3PST'= Single Placement on Any Interior Page
'3PSU'= Multiple Placements on Inside Cover
'3PSV'= Multiple Placements in Title Sequence
'3PSW'= Single Placement on Item
'3PSY'= Multiple Placements on Any Interior Pages
'3PSZ'= Multiple Placements on Secondary Pages
'3PTA'= Single Placement on One Side
'3PTB'= Single Placement on Front Cover And Interior Page
'3PTC'= Multiple Placements on Dust Jacket
'3PTD'= Single Placements on Interior, Covers and Jacket
'3PTE'= Multiple Placements on Interior, Covers and Jacket
'3PTF'= Single Placement on Section Opener Page
'3PTG'= Multiple Placements on Section Opener Page
'3PTH'= Single Placement on Section Opener and Front Page
'3PTI'= Multiple Placements on Section Opener and Front Page
'3PTJ'= Single Placement on Any Covers And Interior Pages
'3PTK'= Multiple Placements on Any Covers And Interior Pages
'3PTL'= Single Placement on Front Cover And Back Cover
'3PTM'= Multiple Placements on Front Cover And Back Cover
'3PTN'= Multiple Placements on Any Covers And Interior Pages
'3PTP'= Single Placement on Any Cover And Interior Page
'3PTQ'= Single Placement on Any Covers And Interior Pages
'3PTR'= Multiple Placements on Any Covers And Interior Pages
'3PTS'= Single Placement Anywhere On Packaging
'3PTT'= Multiple Placements Anywhere On Packaging
'3PTU'= Multiple Placements in Any Part
'3PTV'= Single Placement in Any Part
'3PTW'= Single Placement on Any Pages
'3PTY'= Single Placement on Home Page And Secondary Pages
'3PTZ'= Multiple Placements on Any Pages
'3PUB'= Multiple Placements in Any Part
'3PUC'= Single Placement in Any Part
'3PUD'= Single Placement on Back Cover Or Interior Page
'3PUF'= Single Placement on Front Cover Or Back Cover
'3PUH'= Multiple Placements on Packaging Exterior|Back or Side
'3PUJ'= Single Placement on Packaging Exterior|Back or Side
'3PUL'= Any Placements
'3PXX'= Not Applicable or None
'4SAA'= Any Size Image|Any Size Media
'4SAB'= Up To 1/2 Area Image|Up To A0 Display
'4SAC'= Up To Full Page Image|Up To Full Page Media
'4SAD'= Up To 1/4 Area Image|Up To 16 Sheet Display
'4SAE'= Up To 14 x 20 Inch Image|Any Size Media
'4SAF'= Up To 1/2 Area Image|Up To 4,800 Square Foot Display
'4SAG'= Up To Full Page Image|Any Size Page
'4SAH'= Up To Full Area Image|Up To 69 x 48 Inch Display
'4SAI'= Up To 1/2 Area Image|Up To 180 x 150 Pixels Ad
'4SAJ'= Up To 1/2 Area Image|Up To 11 x 36 Foot Display
'4SAK'= Up To 1/4 Page Image|Any Size Page
'4SAL'= Up To 1/4 Area Image|Up To Full Area Media
'4SAM'= Up To 1/2 Area Image|Up To B1 Media
'4SAN'= Up To Full Area Image|Up To 10 x 8 Foot Media
'4SAP'= Up To 1/4 Screen Image|Up To 32 Inch Screen
'4SAQ'= Up To 1/2 Area Image|Any Size Display
'4SAR'= Up To Full Area Image|Up To 728 x 90 Pixels Ad
'4SAS'= Up To 1/2 Area Image|Up To 24 x 36 Inch Media
'4SAT'= Up To 75 x 75 cm Image|Up To 100 x 100 cm Media
'4SAU'= Up To 1/2 Area Image|Up To 26 x 241 Inch Display
'4SAV'= Up To 8 x 12 Inch Image|Any Size Media
'4SAW'= Up To 1/2 Area Image|Up To 10,000 Square Foot Display
'4SAX'= Up To 1/2 Area Image|Up To 4 x 8 Foot Media
'4SAY'= Up To 1/2 Area Image|Up To 43 x 62 Inch Display
'4SAZ'= Up To 1/2 Area Image|Up To B0 Display
'4SBA'= Up To 1/2 Area Image|Up To 24 x 30 Inch Display
'4SBC'= Up To 1/4 Area Image|Up To 8 Sheet Display
'4SBD'= Up To Full Area Image|Up To 10,000 Square Foot Display
'4SBE'= Up To 1/2 Screen Image|Up To 63 Inch Screen
'4SBF'= Up To Full Area Image|Any Size Display
'4SBG'= Any Size Image|Up To Full Screen Ad
'4SBH'= Up To Full Area Image|Up To A1 Media
'4SBI'= Up To 1/4 Area Image|Up To 10 x 8 Foot Media
'4SBJ'= Up To 20 x 20 cm Image|Up To 100 x 100 cm Media
'4SBK'= Up To 1/16 Page Image|Any Size Page
'4SBL'= Up To 1/2 Area Image|Up To 8 Sheet Display
'4SBM'= Up To Full Area Image|Up To B0 Media
'4SBN'= Up To 1/8 Page Image|Any Size Page
'4SBP'= Any Size Image|Any Size Page
'4SBQ'= Up To 1/2 Area Image|Up To 40 x 60 Inch Media
'4SBR'= Up To 1/2 Area Image|Up To 30 x 240 Inch Display
'4SBS'= Up To 1/4 Area Image|Up To 83 x 135 Inch Display
'4SBT'= Up To Full Area Image|Up To 30 x 40 Inch Display
'4SBU'= Up To 1/2 Page Image|Up To 2 Page Ad
'4SBV'= Up To 1/2 Area Image|Up To 43 x 126 Inch Display
'4SBW'= Up To 1/2 Area Image|Up To 138 x 53 Inch Media
'4SBX'= Up To Full Area Image|Up To 83 x 135 Inch Display
'4SBY'= Up To 1/2 Area Image|Up To 60 x 40 Inch Display
'4SBZ'= Up To Full Screen Image|Up To 32 Inch Screen
'4SCA'= Up To 1/4 Area Image|Up To 468 x 60 Pixels Ad
'4SCB'= Up To 50 x 50 cm Image|Up To 100 x 100 cm Media
'4SCC'= Up To 1/2 Area Image|Up To B1 Display
'4SCD'= Up To 1/4 Area Image|Up To 48 Sheet Display
'4SCE'= Up To 20 x 20 Inch Image|Up To 40 x 40 Inch Media
'4SCF'= Up To 1/4 Area Image|Up To 300 x 600 Pixels Ad
'4SCG'= Up To 1/2 Area Image|Any Size Item
'4SCH'= Up To 1/4 Page Image|Up To 1/2 Page Ad
'4SCI'= Up To 100 x 100 cm Image|Up To 100 x 100 cm Media
'4SCJ'= Up To 1/4 Area Image|Up To B1 Media
'4SCK'= Up To 1/4 Area Image|Up To 728 x 90 Pixels Ad
'4SCL'= Up To 1/2 Area Image|Up To 30 x 40 Inch Display
'4SCM'= Up To 40 x 60 Inch Image|Any Size Media
'4SCN'= Up To 1/8 Page Image|Up To Full Page Media
'4SCP'= Up To Full Page Image|Up To Full Page Ad
'4SCQ'= Up To 70 x 100 cm Image|Any Size Media
'4SCR'= Up To 1/2 Area Image|Up To 14 x 48 Foot Display
'4SCS'= Up To 1/4 Screen Image|Up To 21 Inch Screen
'4SCT'= Up To 40 x 40 Inch Image|Up To 40 x 40 Inch Media
'4SCU'= Up To 6 x 9 Inch Image|Any Size Media
'4SCV'= Up To 1/2 Page Image|Up To 1/2 Page Ad
'4SCW'= Up To 1/2 Area Image|Up To 728 x 90 Pixels Ad
'4SCX'= Up To Full Area Image|Any Size Display
'4SCY'= Up To Full Screen Image|Up To 15 Inch Screen
'4SCZ'= Up To 1/4 Screen Image|Up To 15 Inch Screen
'4SDA'= Up To 11 x 15 cm Image|Any Size Media
'4SDB'= Up To 20 x 24 Inch Image|Any Size Media
'4SDC'= Up To 50 x 71 cm Image|Any Size Media
'4SDD'= Up To 1/4 Area Image|Up To 4 Sheet Display
'4SDE'= Up To 30 x 40 Inch Image|Any Size Media
'4SDF'= Up To 1/2 Area Image|Up To 96 Sheet Display
'4SDG'= Up To 24 x 30 Inch Image|Any Size Media
'4SDH'= Up To 1/4 Area Image|Up To 27 x 141 Inch Display
'4SDI'= Up To Full Area Image|Up To 12 x 24 Foot Media
'4SDJ'= Up To Full Area Image|Up To 138 x 53 Inch Media
'4SDK'= Up To Full Area Image|Up To 96 Sheet Display
'4SDL'= Up To Full Screen Image|Any Size Screen
'4SDM'= Up To 1/4 Area Image|Up To 27 x 85 Inch Display
'4SDN'= Up To Full Area Image|Up To 40 x 60 Inch Media
'4SDP'= Up To 30x 30 Inch Image|Up To 40 x 40 Inch Media
'4SDQ'= Up To 1/2 Screen Image|Up To 100 Diagonal Foot Screen
'4SDR'= Up To Full Area Image|Up To 26 x 241 Inch Display
'4SDS'= Up To Full Area Image|Up To 1,200 Square Foot Display
'4SDT'= Up To 1/4 Area Image|Up To 46 x 60 Inch Media
'4SDU'= Up To 1/2 Area Image|Up To 4 Sheet Display
'4SDV'= Up To 1/4 Area Image|Up To 40 x 60 Inch Media
'4SDW'= Up To Full Area Image|Up To 43 x 62 Inch Display
'4SDX'= Up To 1/2 Area Image|Up To 10 x 40 Foot Display
'4SDY'= Up To 60 x 85 cm Image|Any Size Media
'4SDZ'= Up To 1/2 Area Image|Up To A0 Media
'4SEA'= Up To 1/4 Area Image|Up To 30 Sheet Display
'4SEB'= Up To Full Screen Image|Up To 21 Inch Screen
'4SEC'= Up To Full Area Image|Up To A0 Display
'4SED'= Up To 1/2 Screen Image|Up To 15 Inch Screen
'4SEE'= Up To 42 x 60 cm Image|Any Size Media
'4SEF'= Up To 1/4 Area Image|Up To A1 Media
'4SEG'= Up To Full Area Image|Up To 300 x 600 Pixels Ad
'4SEH'= Up To 1/4 Area Image|Up To 43 x 126 Inch Display
'4SEI'= Up To 1/4 Area Image|Up To B0 Display
'4SEJ'= Up To 1/2 Page Image|Any Size Page
'4SEK'= Up To 1/4 Screen Image|Up To 63 Inch Screen
'4SEL'= Up To Full Area Image|Up To A0 Media
'4SEM'= Up To 1/4 Screen Image|Up To 100 Diagonal Foot Screen
'4SEN'= Up To 1/2 Area Image|Up To 16 Sheet Display
'4SEP'= Up To 1/2 Area Image|Up To 83 x 135 Inch Display
'4SEQ'= Up To 5 x 5 Inch Image|Up To 40 x 40 Inch Media
'4SER'= Up To 1/2 Area Image|Up To 12 Sheet Display
'4SES'= Up To Full Screen Image|Any Size Screen
'4SET'= Up To Full Area Image|Up To B0 Display
'4SEU'= Up To 1/3 Page Image|Any Size Page
'4SEV'= Up To 21 x 30 cm Image|Any Size Media
'4SEW'= Up To 1/4 Area Image|Up To 48 x 71 Inch Display
'4SEX'= Up To 1/2 Area Image|Up To 46 x 60 Inch Media
'4SEY'= Up To 1/4 Area Image|Up To 10,000 Square Foot Display
'4SEZ'= Up To 16 x 20 Inch Image|Any Size Media
'4SFA'= Up To 1/4 Area Image|Any Size Item
'4SFB'= Up To 1/4 Area Image|Up To 138 x 53 Inch Media
'4SFC'= Up To 15 x 21 cm Image|Any Size Media
'4SFD'= Up To Full Area Image|Up To 30 Sheet Display
'4SFE'= Up To 1/4 Area Image|Any Size Display
'4SFF'= Up To 1/4 Area Image|Any Size Media
'4SFG'= Up To 4 Page Image|Any Size Pages
'4SFH'= Up To 5 x 7 Inch Image|Any Size Media
'4SFI'= Up To 2 Page Image|Any Size Pages
'4SFJ'= Up To 2 Page Image|Any Size Pages
'4SFK'= Up To 1/4 Area Image|Up To 30 x 40 Inch Media
'4SFL'= Up To Full Area Image|Up To 4 Sheet Display
'4SFM'= Up To Full Area Image|Up To 11 x 36 Foot Display
'4SFN'= Up To 1/4 Area Image|Up To 25 x 13 Inch Media
'4SFP'= Up To 11 x 14 Inch Image|Any Size Media
'4SFQ'= Up To 30 x 42 cm Image|Any Size Media
'4SFR'= Up To 2 Page Image|Any Size Ad
'4SFS'= Up To Full Area Image|Up To 8 Sheet Display
'4SFT'= Up To 1/2 Area Image|Up To Full Screen Ad
'4SFU'= Up To Full Area Image|Up To 48 Sheet Display
'4SFV'= Up To 1/2 Screen Image|Up To 21 Inch Screen
'4SFW'= Up To Full Area Image|Up To 48 x 71 Inch Display
'4SFX'= Up To 8 x 10 Inch Image|Any Size Media
'4SFZ'= Up To 1/2 Area Image|Up To 27 x 141 Inch Display
'4SGA'= Up To Full Area Image|Up To 27 x 141 Inch Display
'4SGB'= Up To 1/2 Area Image|Up To 30 x 40 Inch Media
'4SGC'= Up To 1/2 Screen Image|Any Size Screen
'4SGD'= Up To Full Area Image|Up To 468 x 60 Pixels Ad
'4SGE'= Up To Full Area Image|Up To 14 x 48 Foot Display
'4SGF'= Up To 1/4 Page Image|Up To 1/4 Page Ad
'4SGG'= Up To Full Area Image|Up To 46 x 60 Inch Media
'4SGH'= Up To 1/4 Screen Image|Any Size Screen
'4SGI'= Up To Full Area Image|Up To 24 x 30 Inch Display
'4SGJ'= Up To 1/2 Screen Image|Up To 32 Inch Screen
'4SGK'= Up To Full Area Image|Up To 27 x 85 Inch Display
'4SGL'= Up To Full Page Image|Up To 2 Page Ad
'4SGM'= Up To Full Page Image|Any Size Ad
'4SGN'= Up To Full Area Image|Up To 43 x 126 Inch Display
'4SGP'= Any Size Image|Any Size Media
'4SGQ'= Up To 1/2 Area Image|Up To 48 Sheet Display
'4SGR'= Up To Full Area Image|Up To B1 Display
'4SGS'= Up To 1/2 Area Image|Up To 1,200 Square Foot Display
'4SGT'= Up To 1/2 Area Image|Up To B0 Media
'4SGU'= Up To 1/4 Area Image|Up To 14 x 48 Foot Display
'4SGV'= Up To 1/2 Area Image|Up To 25 x 13 Inch Media
'4SGW'= Up To 1/2 Page Image|Any Size Page
'4SGX'= Up To Full Area Image|Up To 4,800 Square Foot Display
'4SGY'= Up To 1/2 Screen Image|Up To 30 Diagonal Foot Screen
'4SGZ'= Up To 3 x 4.5 Inch Image|Any Size Media
'4SHA'= Up To 1/4 Area Image|Up To 10 x 40 Foot Display
'4SIB'= Up To 1/4 Area Image|Up To 69 x 48 Inch Display
'4SIC'= Up To 1/2 Area Image|Up To 27 x 85 Inch Display
'4SID'= Up To Full Area Image|Up To 30 x 240 Inch Display
'4SIE'= Up To 1/4 Screen Image|Up To 30 Diagonal Foot Screen
'4SIF'= Up To Full Area Image|Up To 12 Sheet Display
'4SIG'= Up To 18 x 25 cm Image|Any Size Media
'4SIH'= Up To 1/4 Area Image|Up To 24 x 30 Inch Display
'4SII'= Up To 1/2 Area Image|Up To 468 x 60 Pixels Ad
'4SIJ'= Up To 1/2 Screen Image|Up To 10 Diagonal Foot Screen
'4SIK'= Up To 1/4 Area Image|Up To 24 x 36 Inch Media
'4SIL'= Up To Full Area Image|Up To 60 x 40 Inch Display
'4SIM'= Up To 1/4 Area Image|Up To 12 x 24 Foot Media
'4SIN'= Any Size Image|Any Size Media
'4SIP'= Up To 1/4 Area Image|Up To B0 Media
'4SIQ'= Up To Full Area Image|Up To 24 x 36 Inch Media
'4SIR'= Up To 1/4 Area Image|Up To 26 x 53 Inch Media
'4SIS'= Up To Full Area Image|Up To 16 Sheet Display
'4SIT'= Up To Full Area Image|Up To 2,400 Square Foot Display
'4SIU'= Up To 1/4 Area Image|Up To Full Screen Ad
'4SIV'= Up To 10 x 10 Inch Image|Up To 40 x 40 Inch Media
'4SIW'= Any Size Image|Any Size Pages
'4SIX'= Up To 1/4 Area Image|Up To 11 x 36 Foot Display
'4SIY'= Up To 1/4 Page Image|Up To Full Page Ad
'4SIZ'= Up To 1/2 Page Image|Up To Full Page Media
'4SJA'= Up To 1/16 Page|Any Size Page
'4SJB'= Up To 1/4 Page Image|Any Size Page
'4SJC'= Up To 100 x 142 cm Image|Any Size Media
'4SJD'= Up To 21 x 30 cm Image|Any Size Media
'4SJE'= Up To 38 x 50 Inch Image|Any Size Media
'4SJF'= Up To Full Screen Image|Up To 63 Inch Screen
'4SJG'= Up To 1/4 Area Image|Up To A0 Media
'4SJI'= Up To 1/4 Area Image|Up To 4 x 8 Foot Media
'4SJJ'= Up To 25 x 36 cm Image|Any Size Media
'4SJK'= Any Size Image|Up To Full Card
'4SJL'= Up To 1/4 Area Image|Up To 30 x 240 Inch Display
'4SJM'= Up To 1/2 Area Image|Up To Full Area Media
'4SJN'= Up To Full Area Image|Up To 25 x 13 Inch Media
'4SJP'= Up To 1/4 Area Image|Up To 60 x 40 Inch Display
'4SJQ'= Up To 1/4 Area Image|Up To 4,800 Square Foot Display
'4SJR'= Up To 1/2 Area Image|Up To 50 x 24 Inch Media
'4SJS'= Up To 1/8 Page Image|Any Size Page
'4SJT'= Up To Full Area Image|Up To 180 x 150 Pixels Ad
'4SJU'= Up To 2 Page Image|Any Size Pages
'4SJV'= Up To Full Page Image|Any Size Page
'4SJW'= Up To 40 x 40 cm Image|Up To 100 x 100 cm Media
'4SJX'= Up To 1/4 Area Image|Up To B0 Display
'4SJY'= Up To Full Screen Image|Up To 30 Diagonal Foot Screen
'4SJZ'= Up To 3 Page Image|Any Size Pages
'4SKA'= Up To 1/4 Area Image|Up To 20 x 30 Inch Media
'4SKB'= Up To 11 x 14 Inch Image|Any Size Media
'4SKC'= Up To Full Screen Image|Up To 100 Diagonal Foot Screen
'4SKD'= Up To Full Page Image|Any Size Page
'4SKE'= Up To 1/8 Page Image|Any Size Page
'4SKF'= Up To 1/4 Area Image|Up To 672 Square Foot Display
'4SKG'= Any Size Image|Any Size Screen
'4SKH'= Up To Full Area Image|Up To 50 x 24 Inch Media
'4SKI'= Up To 30 x 30 cm Image|Up To 100 x 100 cm Media
'4SKJ'= Up To 1/2 Area Image|Up To A1 Media
'4SKK'= Up To Full Area Image|Up To B0 Display
'4SKL'= Up To 1/2 Area Image|Up To 26 x 53 Inch Media
'4SKM'= Up To Full Area Image|Up To 26 x 53 Inch Media
'4SKN'= Up To Full Area Image|Up To 20 x 30 Inch Media
'4SKP'= Any Size Image|Any Size Ad
'4SKQ'= Up To 1/4 Area Image|Up To 180 x 150 Pixels Ad
'4SKR'= Up To 1/4 Area Image|Up To 1,200 Square Foot Display
'4SKS'= Up To 1/2 Area Image|Up To 672 Square Foot Display
'4SKT'= Up To 1/4 Area Image|Up To 43 x 62 Inch Display
'4SKU'= Up To 1/2 Area Image|Up To 20 x 30 Inch Media
'4SKV'= Up To Full Area Image|Up To 10 x 40 Foot Display
'4SKW'= Up To 1/4 Area Image|Up To 30 x 40 Inch Display
'4SKX'= Up To 1/4 Area Image|Up To 96 Sheet Display
'4SKY'= Up To 1/2 Area Image|Up To 48 x 71 Inch Display
'4SKZ'= Up To Full Area Image|Up To Full Area Media
'4SLA'= Any Size Image|Any Size Pages
'4SLC'= Up To 1/4 Area Image|Up To 50 x 24 Inch Media
'4SLD'= Up To 1/2 Page Image|Any Size Page
'4SLE'= Up To Full Screen Image|Up To 10 Diagonal Foot Screen
'4SLF'= Up To 8 x 10 Inch Image|Any Size Media
'4SLG'= Up To Full Area Image|Up To 4 x 8 Foot Media
'4SLH'= Up To 30 x 42 cm Image|Any Size Media
'4SLI'= Up To 1/2 Area Image|Up To 10 x 8 Foot Media
'4SLJ'= Up To 1/4 Area Image|Up To 12 Sheet Display
'4SLK'= Up To 1/4 Area Image|Up To 2,400 Square Foot Display
'4SLL'= Up To 1/4 Area Image|Up To A0 Display
'4SLM'= Up To 1/2 Area Image|Up To 300 x 600 Pixels Ad
'4SLN'= Up To Full Area Image|Up To 672 Square Foot Display
'4SLP'= Up To 1/2 Area Image|Up To 30 Sheet Display
'4SLQ'= Up To 1/4 Area Image|Up To B1 Display
'4SLR'= Up To Full Area Image|Any Size Item
'4SLS'= Up To 1/2 Area Image|Up To 12 x 24 Foot Media
'4SLT'= Up To Full Area Image|Up To 30 x 40 Inch Media
'4SLU'= Up To Full Area Image|Up To B1 Media
'4SLV'= Up To 1/4 Page Image|Any Size Page
'4SLW'= Up To 1/4 Page Image|Up To Full Page Media
'4SLX'= Up To 1/2 Area Image|Up To B0 Display
'4SLY'= Up To 1/2 Page Image|Up To Full Page Ad
'4SLZ'= Up To 1/4 Screen Image|Up To 10 Diagonal Foot Screen
'4SMA'= Up To 1/2 Area Image|Up To 2,400 Square Foot Display
'4SMB'= Up To 2 Page Image|Up To 2 Page Ad
'4SMD'= Up To 25 x 36 cm Image|Any Size Media
'4SME'= Up To 13 x 18 cm Image|Any Size Media
'4SMF'= Up To 85 x 119 cm Image|Any Size Media
'4SMG'= Up To 3/4 Page Image|Any Size Page
'4SMH'= Up To 150 x 150 Pixels Image|Any Size Screen
'4SMJ'= Up To 300 x 600 Pixels Image|Any Size Screen
'4SMK'= Up To 1/2 Area Image|Any Size Media
'4SML'= Up To Full Area Image|Any Size Media
'4SMN'= Up To 1/4 Area Image|Any Size Display
'4SMP'= Up To 1/2 Area Image|Any Size Display
'4SMQ'= Up To Full Area Image|Any Size Display
'4SMR'= Up To 1/4 Area Image|Any Size Display
'4SMS'= Up To 1/4 Screen Image|Any Size Screen
'4SMT'= Up To 1/2 Screen Image|Any Size Screen
'4SMU'= Up To Full Screen Image|Any Size Screen
'4SMV'= Any Size Image|Any Size Media
'4SMW'= Up To Full Area Image|Any Size Display
'4SMX'= Up To 1/4 Area Image|Any Size Media
'4SMY'= Up To 1/2 Area Image|Any Size Media
'4SMZ'= Up To Full Area Image|Any Size Media
'4SNG'= Up To 1/2 Area Image|Any Size Display
'4SNO'= Up To Full Area Image|Any Size Display
'4SUL'= Any Sizes
'4SXX'= Not Applicable or None
'5VAA'= All Versions
'5VUG'= Single Edition
'5VUK'= Single Hardcover Edition
'5VUL'= Any Versions
'5VUP'= Single Version
'5VUU'= Multiple Hardcover Editions
'5VUY'= Multiple Versions
'5VUZ'= Single Issue
'5VVB'= Multiple Print Versions
'5VVC'= Single Paperback Edition
'5VVG'= Multiple Editions
'5VVH'= Single Edition in All Binding Formats
'5VVJ'= Multiple Issues
'5VVK'= Multiple Paperback Editions
'5VVL'= Multiple Editions in All Binding Formats
'5VVM'= Single Print Version
'5VXX'= Not Applicable or None
'6QAA'= Any Quantity
'6QAB'= Up To 10,000|Impressions
'6QAC'= Up To 1,000|Copies
'6QAD'= Up To 10,000|Copies
'6QAE'= Up To 100|Displays
'6QAF'= Up To 50,000|Copies
'6QAG'= Up To 500|Displays
'6QAH'= Up To 100,000|Print Run
'6QAI'= Up To 500|Copies
'6QAK'= Up To 10|Copies
'6QAL'= Up To 10|Displays
'6QAM'= Any Quantity Of|Copies
'6QAN'= Up To 50,000|Total Circulation
'6QAP'= Up To 500|Displays
'6QAQ'= Up To 5,000|Print Run
'6QAR'= Up To 3 Million|Print Run
'6QAS'= Up To 1,000|Copies
'6QAT'= Up To 5,000|Total Circulation
'6QAU'= One|Print Run
'6QAV'= Up To 5 Million|Total Circulation
'6QAW'= Up To 5|Copies
'6QAX'= Up To 500,000|Displays
'6QAY'= Up To 1,000|Reprints
'6QAZ'= Up To 100|Print Run
'6QBA'= Up To 1,000|Print Run
'6QBB'= Up To 10,000|Total Circulation
'6QBC'= Up To 2,500|Displays
'6QBD'= Up To 50|Displays
'6QBE'= One|Copy
'6QBF'= Up To 10,000|Reprints
'6QBG'= Up To 250,000|Print Run
'6QBH'= Up To 40,000|Print Run
'6QBI'= Up To 100 Million|Viewers
'6QBK'= Four|Copies
'6QBL'= Five|Copies
'6QBM'= Up To 10 Million|Impressions
'6QBN'= Up To 1 Million|Viewers
'6QBP'= Up To 5|Displays
'6QBQ'= Any Quantity Of|Viewers
'6QBR'= Up To 100,000|Impressions
'6QBS'= Up To 500|Copies
'6QBT'= Up To 25,000|Copies
'6QBU'= One|Copy
'6QBV'= Up To 25,000|Print Run
'6QBW'= Up To 250,000|Displays
'6QBX'= Up To 1 Million|Copies
'6QBY'= Up To 1,000|Displays
'6QBZ'= Up To 100,000|Viewers
'6QCA'= Up To 250|Copies
'6QCB'= Up To 100|Reprints
'6QCC'= Any Quantity Of|Viewers
'6QCD'= Up To 1,000|Total Circulation
'6QCE'= Up To 250|Displays
'6QCF'= Three|Copies
'6QCG'= Up To 50,000|Print Run
'6QCH'= One|Copy
'6QCI'= Up To 500,000|Total Circulation
'6QCJ'= Up To 5 Million|Displays
'6QCK'= Up To 2 Million|Total Circulation
'6QCL'= Up To 3 Million|Total Circulation
'6QCM'= Up To 100|Copies
'6QCN'= Up To 1 Million|Total Circulation
'6QCP'= Up To 10|Copies
'6QCQ'= Up To 10,000|Copies
'6QCR'= Up To 100|Copies
'6QCS'= Up To 100,000|Total Circulation
'6QCT'= Up To 2 Million|Print Run
'6QCU'= Any Quantity Of|Reprints
'6QCV'= Up To 100,000|Viewers
'6QCW'= Up To 5,000|Print Run
'6QCX'= One|Display
'6QCY'= Up To 1 Million|Impressions
'6QCZ'= Up To 1 Million|Print Run
'6QDA'= Up To 10|Print Run
'6QDB'= Up To 1 Million|Displays
'6QDC'= Up To 5 Million|Print Run
'6QDD'= Up To 100|Viewers
'6QDE'= Up To 250,000|Print Run
'6QDF'= Up To 1,000|Copies
'6QDG'= Up To 10|Reprints
'6QDH'= Up To 10|Print Run
'6QDI'= One|Display
'6QDJ'= Up To 500,000|Print Run
'6QDK'= Up To 10 Million|Total Circulation
'6QDL'= Any Quantity Of|Print Run
'6QDM'= Up To 500,000|Copies
'6QDN'= Up To 100,000|Copies
'6QDP'= Up To 50,000|Copies
'6QDQ'= Up To 100,000|Print Run
'6QDR'= Up To 10|Total Circulation
'6QDS'= Up To 25,000|Displays
'6QDT'= Up To 250,000|Total Circulation
'6QDU'= Up To 10 Million|Print Run
'6QDV'= Up To 50|Copies
'6QDW'= Up To 5,000|Copies
'6QDX'= Up To 25|Displays
'6QDY'= Up To 5|Displays
'6QDZ'= Up To 100,000|Displays
'6QEA'= Up To 100|Displays
'6QEB'= Up To 10|Copies
'6QEC'= Up To 10,000|Print Run
'6QED'= Up To 100,000|Copies
'6QEE'= Up To 10,000|Copies
'6QEF'= Up To 25,000|Copies
'6QEG'= Up To 10,000|Print Run
'6QEH'= One|Print Run
'6QEI'= Any Quantity Of|Displays
'6QEJ'= Up To 100|Total Circulation
'6QEK'= Up To 2 Million|Displays
'6QEL'= Up To 25|Displays
'6QEM'= Up To 50|Displays
'6QEN'= Up To 500,000|Print Run
'6QEP'= One|Copy
'6QEQ'= Up To 1,000|Viewers
'6QER'= Any Quantity Of|Copies
'6QES'= Up To 50,000|Displays
'6QET'= Up To 5,000|Displays
'6QEU'= Any Quantity Of|Print Run
'6QEV'= Any Quantity Of|Circulation
'6QEW'= Up To 2,500|Copies
'6QEX'= Two|Copies
'6QEY'= Up To 100|Print Run
'6QEZ'= Up To 10,000|Viewers
'6QFA'= Up To 3 Million|Displays
'6QFB'= Up To 1,000|Print Run
'6QFC'= Up To 5|Copies
'6QFD'= Up To 25,000|Print Run
'6QFE'= Up To 5,000|Copies
'6QFF'= Up To 10,000|Displays
'6QFG'= Up To 250|Displays
'6QFH'= Up To 10,000|Viewers
'6QFI'= One|Reprint
'6QFJ'= Up To 10|Displays
'6QFK'= Up To 250,000|Copies
'6QFL'= Up To 100|Copies
'6QFM'= Any Quantity Of|Impressions
'6QFN'= Any Quantity Of|Copies
'6QFP'= Up To 10 Million|Viewers
'6QFQ'= Any Quantity Of|Displays
'6QFR'= Up To 50,000|Print Run
'6QFS'= Up To 25,000|Total Circulation
'6QFT'= One|Copy
'6QFU'= Up To 25|Total Circulation
'6QFV'= Up To 50|Total Circulation
'6QFW'= Up To 250|Total Circulation
'6QFY'= Up To 500|Total Circulation
'6QFZ'= Up To 2,500|Total Circulation
'6QGB'= Up To 25 Million|Total Circulation
'6QGC'= Up To 50 Million|Total Circulation
'6QGD'= Up To 25|Copies
'6QGE'= Up To 50|Copies
'6QUL'= Any Quantity
'6QXX'= Not Applicable or None
'7DAA'= In Perpetuity
'7DUL'= Any Durations
'7DUQ'= Up To 10 Years
'7DUS'= Up To 6 Months
'7DUT'= Up To 10 Years
'7DUV'= Up To 6 Months
'7DUW'= Up To 1 Year
'7DUY'= Up To 3 Years
'7DUZ'= Up To 5 Years
'7DWB'= Up To 7 Years
'7DWC'= Up To 3 Months
'7DWE'= Up To 2 Years
'7DWG'= Up To 15 Years
'7DWI'= Up To 1 Year
'7DWJ'= Up To 1 Month
'7DWK'= Up To 2 Years
'7DWL'= Up To 1 Week
'7DWM'= In Perpetuity
'7DWP'= Up To 13 Weeks
'7DWR'= In Perpetuity
'7DWS'= Life Of Publication
'7DWT'= Up To 52 Weeks
'7DWU'= Up To 7 Years
'7DWV'= Up To 5 Years
'7DWW'= Up To 6 Months
'7DWY'= Up To 3 Months
'7DWZ'= Up To 2 Years
'7DXA'= Full Term Of Copyright
'7DXB'= Up To 1 Week
'7DXC'= Up To 1 Day
'7DXD'= Up To 1 Year
'7DXE'= Up To 1 Year
'7DXF'= Up To 1 Month
'7DXG'= Up To 10 Years
'7DXH'= Life Of Publication
'7DXI'= In Perpetuity
'7DXK'= Up To 1 Month
'7DXL'= Up To 1 Week
'7DXM'= Up To 1 Year
'7DXP'= Up To 3 Years
'7DXQ'= Up To 1 Year
'7DXR'= Up To 10 Years
'7DXS'= In Perpetuity
'7DXT'= Up To 1 Day
'7DXV'= Up To 15 Years
'7DXW'= Up To 6 Months
'7DXX'= Not Applicable or None
'7DXY'= Up To 15 Years
'7DXZ'= Up To 3 Months
'7DYA'= Up To 5 Years
'7DYB'= Up To 6 Months
'7DYC'= Up To 10 Years
'7DYD'= Up To 5 Years
'7DYE'= Up To 3 Months
'7DYF'= Up To 1 Day
'7DYG'= Up To 1 Day
'7DYI'= Up To 1 Month
'7DYJ'= Up To 2 Weeks
'7DYK'= Up To 2 Years
'7DYL'= Up To 3 Years
'7DYM'= Up To 1 Year
'7DYN'= Up To 5 Years
'7DYP'= Up To 1 Week
'7DYQ'= In Perpetuity
'7DYS'= Life Of Publication
'7DYT'= Up To 26 Weeks
'7DYV'= Life Of Event
'7DYW'= Up To 1 Day
'7DYX'= Up To 6 Months
'7DYY'= Up To 10 Years
'7DYZ'= Up To 1 Day
'7DZA'= Up To 1 Year
'7DZB'= Up To 3 Years
'7DZD'= Up To 1 Week
'7DZF'= Up To 2 Months
'7DZG'= Up To 1 Year
'7DZH'= Up To 2 Years
'7DZJ'= Up To 3 Years
'7DZK'= Up To 5 Years
'7DZL'= Up To 10 Years
'7DZM'= Up To 2 Years
'7DZN'= Up To 3 Years
'7DZP'= Up To 2 Years
'8IAA'= All Industries
'8IAD'= Advertising and Marketing
'8IAE'= Arts and Entertainment
'8IAG'= Agriculture, Farming and Horticulture
'8IAL'= Alcohol
'8IAP'= Consumer Appliances and Electronics
'8IAR'= Architecture and Engineering
'8IAT'= Airline Transportation
'8IAU'= Automotive
'8IAV'= Aviation
'8IBA'= Baby and Childcare
'8IBE'= Beauty and Personal Care
'8IBI'= Biotechnology
'8IBR'= Broadcast Media
'8ICC'= Construction and Contracting
'8ICE'= Communications Equipment and Services
'8ICG'= Counseling
'8ICH'= Chemicals
'8ICO'= Business Consulting and Services
'8IEC'= Ecology, Environmental and Conservation
'8IED'= Education
'8IEM'= Employment Training and Recruitment
'8IEN'= Energy, Utilities and Fuel
'8IEV'= Events and Conventions
'8IFA'= Fashion
'8IFB'= Food and Beverage Processing
'8IFI'= Financial Services and Banking
'8IFL'= Food and Beverage Retail
'8IFO'= Forestry and Wood Products
'8IFR'= Freight and Warehousing
'8IFS'= Food Services
'8IFU'= Furniture
'8IGA'= Games, Toys and Hobbies
'8IGC'= Greeting Card
'8IGI'= Gaming Industry
'8IGL'= Gardening and Landscaping
'8IGO'= Government and Politics
'8IGR'= Graphic Design
'8IHA'= Household Appliances
'8IHC'= Household Cleaning Products
'8IHH'= Hotels and Hospitality
'8IHI'= Heavy Industry
'8IHO'= Home Improvement
'8IHS'= Computer Hardware, Software and Peripherals
'8IIM'= Industry and Manufacturing
'8IIN'= Insurance
'8IIS'= Internet Services
'8IIT'= Information Technologies
'8ILS'= Legal Services
'8IME'= Medical and Healthcare
'8IMM'= Mining and Metals
'8IMS'= Microelectronics and Semiconductors
'8IMU'= Music
'8IMW'= Military and Weapons
'8INP'= Not For Profit, Social, Charitable
'8IOG'= Oil and Gas
'8IOI'= Other Industry
'8IOP'= Office Products
'8IPM'= Publishing Media
'8IPO'= Personal Use Only
'8IPP'= Pet Products and Services
'8IPR'= Public Relations
'8IPS'= Pharmaceuticals and Supplements
'8IPT'= Printing and Reprographics
'8IRE'= Real Estate
'8IRM'= Retail Merchandise
'8IRR'= Religion and Religious Services
'8ISC'= Sciences
'8ISF'= Sports, Fitness and Recreation
'8ISH'= Shipping
'8ISM'= Retail Sales and Marketing
'8ISO'= Software
'8ISS'= Safety and Security
'8ITB'= Tobacco
'8ITE'= Telecommunications
'8ITR'= Travel and Tourism
'8ITX'= Textiles and Apparel
'8IUL'= Any Industries
'8IXX'= Not Applicable or None
'8LAA'= All Languages
'8LAF'= Afrikaans
'8LAR'= Arabic
'8LBO'= Bosnian
'8LBU'= Bulgarian
'8LCA'= Chinese-Cantonese
'8LCH'= Chinese-Mandarin
'8LCP'= Chinese-Other
'8LCR'= Croatian
'8LCZ'= Czech
'8LDA'= Danish
'8LDU'= Dutch
'8LEN'= English
'8LES'= Estonian
'8LFI'= Finnish
'8LFR'= French
'8LGE'= German
'8LGR'= Greek
'8LHE'= Hebrew
'8LHI'= Hindi
'8LHU'= Hungarian
'8LIC'= Icelandic
'8LIG'= Irish Gaelic
'8LIN'= Indonesian
'8LIT'= Italian
'8LJA'= Japanese
'8LKO'= Korean
'8LLA'= Latvian
'8LMG'= Mongolian
'8LNO'= Norwegian
'8LOL'= Any One Language
'8LOT'= Other Language
'8LPO'= Polish
'8LPR'= Portuguese
'8LRO'= Romanian
'8LRU'= Russian
'8LSE'= Serbian
'8LSG'= Scottish Gaelic
'8LSH'= Swahili
'8LSI'= Sindhi
'8LSL'= Slovenian
'8LSP'= Spanish
'8LSV'= Slovakian
'8LSW'= Swedish
'8LSZ'= Swazi
'8LTA'= Tagalog
'8LTH'= Thai
'8LTU'= Turkish
'8LUL'= Any Languages
'8LUR'= Ukrainian
'8LXX'= Not Applicable or None
'8LYI'= Yiddish
'8RAA'= Worldwide
'8RAC'= Latin America and Caribbean|All Latin America and Caribbean
'8RAD'= Europe|Andorra
'8RAE'= Middle East|United Arab Emirates
'8RAF'= Middle East|Afghanistan
'8RAG'= Latin America and Caribbean|Antigua and Barbuda
'8RAH'= Broad International Region|All Spanish Speaking Countries
'8RAI'= Latin America and Caribbean|Anguilla
'8RAJ'= Africa|All Africa
'8RAK'= Africa|All African Mediterranean Countries
'8RAL'= Europe|Albania
'8RAM'= Europe|Armenia
'8RAN'= Latin America and Caribbean|Netherlands Antilles
'8RAO'= Africa|Angola
'8RAP'= Africa|All Central Africa
'8RAQ'= Africa|All Eastern Africa
'8RAR'= Latin America and Caribbean|Argentina
'8RAS'= Oceania|American Samoa
'8RAT'= Europe|Austria
'8RAU'= Oceania|Australia
'8RAV'= Asia|Up To 3 States Or Provinces
'8RAW'= Latin America and Caribbean|Aruba
'8RAX'= Europe|Aland Islands
'8RAY'= Asia|China-Northeast
'8RAZ'= Europe|Azerbaijan
'8RBA'= Europe|Bosnia and Herzegovina
'8RBB'= Latin America and Caribbean|Barbados
'8RBC'= Africa|All Southern Africa
'8RBD'= Asia|Bangladesh
'8RBE'= Europe|Belgium
'8RBF'= Africa|Burkina Faso
'8RBG'= Europe|Bulgaria
'8RBH'= Middle East|Bahrain
'8RBI'= Africa|Burundi
'8RBJ'= Africa|Benin
'8RBK'= Northern America|Canada-British Columbia
'8RBL'= Africa|All Western Africa
'8RBM'= Northern America|Bermuda
'8RBN'= Asia|Brunei Darussalam
'8RBO'= Latin America and Caribbean|Bolivia
'8RBP'= Africa|Ascension Island
'8RBQ'= Other Regions|Antarctica
'8RBR'= Latin America and Caribbean|Brazil
'8RBS'= Latin America and Caribbean|Bahamas
'8RBT'= Asia|Bhutan
'8RBW'= Africa|Botswana
'8RBX'= Middle East|Up To 5 States Or Provinces
'8RBY'= Europe|Belarus
'8RBZ'= Latin America and Caribbean|Belize
'8RCA'= Northern America|Canada
'8RCB'= Other Regions|All Arctic and Arctic Ocean Islands
'8RCC'= Oceania|Cocos, Keeling Islands
'8RCD'= Northern America|Up To 5 States Or Provinces
'8RCE'= Northern America|USA and Canada
'8RCF'= Africa|Central African Republic
'8RCG'= Africa|Congo
'8RCH'= Europe|Switzerland
'8RCI'= Africa|Cote D'Ivoire
'8RCJ'= Northern America|Canada-Ontario
'8RCK'= Oceania|Cook Islands
'8RCL'= Latin America and Caribbean|Chile
'8RCM'= Africa|Cameroon
'8RCN'= Asia|All China
'8RCO'= Latin America and Caribbean|Colombia
'8RCP'= Asia|All Eastern Asia
'8RCR'= Latin America and Caribbean|Costa Rica
'8RCS'= Europe|Serbia and Montenegro
'8RCT'= Oceania|All Oceania
'8RCU'= Latin America and Caribbean|Cuba
'8RCV'= Africa|Cape Verde
'8RCX'= Oceania|Christmas Island
'8RCY'= Europe|Cyprus
'8RCZ'= Europe|Czech Republic
'8RDA'= Asia|Tibet
'8RDB'= Asia|All Asia
'8RDC'= Asia|All Central Asia
'8RDE'= Europe|Germany
'8RDG'= Asia|All Southern Asia
'8RDH'= Asia|All Southeastern Asia
'8RDJ'= Africa|Djibouti
'8RDK'= Europe|Denmark
'8RDM'= Latin America and Caribbean|Dominica
'8RDO'= Latin America and Caribbean|Dominican Republic
'8RDQ'= Other Regions|All British Indian Ocean Territories
'8RDR'= Europe|Croatia
'8RDS'= Europe|Faeroe Islands
'8RDT'= Europe|Vatican City State
'8RDU'= Europe|All Europe
'8RDW'= Europe|All Baltic States
'8RDX'= Europe|All Benelux
'8RDY'= Europe|All Caucasian States
'8RDZ'= Africa|Algeria
'8REA'= Europe|All Eastern Europe
'8REB'= Europe|All European Mediterranean Countries
'8REC'= Latin America and Caribbean|Ecuador
'8RED'= Europe|All Northern Europe
'8REE'= Europe|Estonia
'8REF'= Europe|All Scandinavia
'8REG'= Africa|Egypt
'8REH'= Africa|Western Sahara
'8REI'= Europe|All United Kingdom
'8REJ'= Europe|All Western Europe
'8REK'= Broad International Region|Europe, Middle East and Africa
'8REL'= Europe|All European Union Countries
'8REM'= Europe|England
'8REN'= Europe|Scotland
'8RER'= Africa|Eritrea
'8RES'= Europe|Spain
'8RET'= Africa|Ethiopia
'8REU'= Other Regions|All French Southern Territories
'8REV'= Middle East|Palestinian Authority
'8REX'= Middle East|All Middle East
'8REY'= Middle East|All Middle Eastern Gulf States
'8RFB'= Other Regions|All Northern Atlantic Ocean Islands
'8RFF'= Oceania|Midway Islands
'8RFH'= Oceania|Rapa Nui, Easter Island
'8RFI'= Europe|Finland
'8RFJ'= Oceania|Fiji
'8RFK'= Latin America and Caribbean|Falkland Islands, Malvinas
'8RFL'= Oceania|Tahiti
'8RFM'= Oceania|Micronesia
'8RFP'= Oceania|Wallis and Futuna
'8RFQ'= Latin America and Caribbean|Patagonia
'8RFR'= Europe|France
'8RFS'= Latin America and Caribbean|All South America
'8RFT'= Latin America and Caribbean|All Andean Countries
'8RFU'= Latin America and Caribbean|All Southern Cone
'8RFV'= Latin America and Caribbean|All Amazonia
'8RFW'= Other Regions|All Southern Atlantic Ocean Islands
'8RFX'= Other Regions|All Southern Indian Ocean Islands
'8RFY'= Broad International Region|All Americas
'8RFZ'= Latin America and Caribbean|All Caribbean
'8RGA'= Africa|Gabon
'8RGC'= Latin America and Caribbean|All Central America
'8RGD'= Latin America and Caribbean|Grenada
'8RGE'= Europe|Georgia
'8RGF'= Latin America and Caribbean|French Guiana
'8RGG'= Europe|Guernsey
'8RGH'= Africa|Ghana
'8RGI'= Europe|Gibraltar
'8RGJ'= Northern America|All Northern American Countries
'8RGL'= Northern America|Greenland
'8RGM'= Africa|Gambia
'8RGN'= Africa|Guinea
'8RGP'= Latin America and Caribbean|Guadeloupe
'8RGQ'= Africa|Equatorial Guinea
'8RGR'= Europe|Greece
'8RGT'= Latin America and Caribbean|Guatemala
'8RGU'= Oceania|Guam
'8RGW'= Africa|Guinea-Bissau
'8RGY'= Latin America and Caribbean|Guyana
'8RGZ'= Latin America and Caribbean|Bequia
'8RHA'= Latin America and Caribbean|Bonaire
'8RHB'= Latin America and Caribbean|British Virgin Islands
'8RHC'= Latin America and Caribbean|Curacao
'8RHD'= Latin America and Caribbean|Saba
'8RHE'= Latin America and Caribbean|Saint Barthelemy
'8RHF'= Latin America and Caribbean|Saint Eustatius
'8RHG'= Latin America and Caribbean|Saint Martin
'8RHH'= Latin America and Caribbean|U.S. Virgin Islands
'8RHJ'= Northern America|USA-Central
'8RHK'= Asia|Hong Kong
'8RHN'= Latin America and Caribbean|Honduras
'8RHP'= Northern America|USA-Midwest
'8RHQ'= Northern America|USA
'8RHR'= Northern America|USA-Northeast
'8RHS'= Northern America|USA-Pacific Northwest
'8RHT'= Latin America and Caribbean|Haiti
'8RHU'= Europe|Hungary
'8RHW'= Northern America|USA-Southeast
'8RHX'= Northern America|USA-Southwest
'8RHY'= Northern America|USA-Minor Outlying Islands
'8RIA'= Northern America|USA-West
'8RIB'= Middle East|All Middle Eastern Mediterranean Countries
'8RID'= Asia|Indonesia
'8RIE'= Europe|Ireland
'8RIL'= Middle East|Israel
'8RIM'= Europe|Isle Of Man
'8RIN'= Asia|India
'8RIQ'= Middle East|Iraq
'8RIR'= Middle East|Iran
'8RIS'= Europe|Iceland
'8RIT'= Europe|Italy
'8RJE'= Europe|Jersey
'8RJM'= Latin America and Caribbean|Jamaica
'8RJO'= Middle East|Jordan
'8RJP'= Asia|Japan
'8RKE'= Africa|Kenya
'8RKG'= Asia|Kyrgyzstan
'8RKH'= Asia|Cambodia
'8RKI'= Oceania|Kiribati
'8RKM'= Oceania|Comoros
'8RKN'= Latin America and Caribbean|Saint Kitts and Nevis
'8RKP'= Asia|North Korea
'8RKR'= Asia|South Korea
'8RKW'= Middle East|Kuwait
'8RKY'= Latin America and Caribbean|Cayman Islands
'8RKZ'= Asia|Kazakhstan
'8RLA'= Asia|Laos
'8RLB'= Middle East|Lebanon
'8RLC'= Latin America and Caribbean|Saint Lucia
'8RLI'= Europe|Liechtenstein
'8RLK'= Oceania|Sri Lanka
'8RLR'= Africa|Liberia
'8RLS'= Africa|Lesotho
'8RLT'= Europe|Lithuania
'8RLU'= Europe|Luxembourg
'8RLV'= Europe|Latvia
'8RLY'= Africa|Libyan Arab Jamahiriya
'8RMA'= Africa|Morocco
'8RMC'= Europe|Monaco
'8RMD'= Europe|Moldova
'8RMG'= Oceania|Madagascar
'8RMH'= Oceania|Marshall Islands
'8RMK'= Europe|Macedonia
'8RML'= Africa|Mali
'8RMM'= Asia|Myanmar
'8RMN'= Asia|Mongolia
'8RMO'= Asia|Macao
'8RMP'= Oceania|Northern Mariana Islands
'8RMQ'= Latin America and Caribbean|Martinique
'8RMR'= Africa|Mauritania
'8RMS'= Latin America and Caribbean|Montserrat
'8RMT'= Europe|Malta
'8RMU'= Oceania|Mauritius
'8RMV'= Asia|Maldives
'8RMW'= Africa|Malawi
'8RMX'= Latin America and Caribbean|Mexico
'8RMY'= Asia|Malaysia
'8RMZ'= Africa|Mozambique
'8RNA'= Africa|Namibia
'8RNC'= Oceania|New Caledonia
'8RNE'= Africa|Niger
'8RNF'= Oceania|Norfolk Island
'8RNG'= Africa|Nigeria
'8RNI'= Latin America and Caribbean|Nicaragua
'8RNL'= Europe|Netherlands
'8RNO'= Europe|Norway
'8RNP'= Asia|Nepal
'8RNR'= Oceania|Nauru
'8RNU'= Oceania|Niue
'8RNZ'= Oceania|New Zealand
'8ROM'= Middle East|Oman
'8RPA'= Latin America and Caribbean|Panama
'8RPE'= Latin America and Caribbean|Peru
'8RPF'= Oceania|French Polynesia
'8RPG'= Oceania|Papua New Guinea
'8RPH'= Asia|Philippines
'8RPK'= Asia|Pakistan
'8RPL'= Europe|Poland
'8RPM'= Northern America|Saint Pierre and Miquelon
'8RPN'= Oceania|Pitcairn Islands
'8RPR'= Latin America and Caribbean|Puerto Rico
'8RPT'= Europe|Portugal
'8RPW'= Oceania|Palau
'8RPY'= Latin America and Caribbean|Paraguay
'8RQA'= Middle East|Qatar
'8RQB'= Africa|One Metropolitan Area, Adjoining Cities
'8RQC'= Africa|One Minor City, Up To 250,000 Population
'8RQD'= Asia|One Major City, Over 250,000 Population
'8RQE'= Asia|One Metropolitan Area, Adjoining Cities
'8RQF'= Asia|One Minor City, Up To 250,000 Population
'8RQJ'= Europe|One Major City, Over 250,000 Population
'8RQK'= Europe|One Metropolitan Area, Adjoining Cities
'8RQL'= Europe|One Minor City, Up To 250,000 Population
'8RQM'= Latin America and Caribbean|One Major City, Over 250,000 Population
'8RQN'= Latin America and Caribbean|One Metropolitan Area, Adjoining Cities
'8RQO'= Latin America and Caribbean|One Minor City, Up To 250,000 Population
'8RQP'= Middle East|One Major City, Over 250,000 Population
'8RQR'= Middle East|One Metropolitan Area, Adjoining Cities
'8RQS'= Middle East|One Minor City, Up To 250,000 Population
'8RQT'= Northern America|One Major City, Over 250,000 Population
'8RQU'= Northern America|One Metropolitan Area, Adjoining Cities
'8RQV'= Northern America|One Minor City, Up To 250,000 Population
'8RQW'= Oceania|One Major City, Over 250,000 Population
'8RQX'= Africa|One Major City, Over 250,000 Population
'8RQY'= Oceania|One Metropolitan Area, Adjoining Cities
'8RQZ'= Oceania|One Minor City, Up To 250,000 Population
'8RRB'= Africa|One State Or Province
'8RRC'= Asia|One State Or Province
'8RRE'= Africa|Reunion
'8RRF'= Europe|One State Or Province
'8RRG'= Latin America and Caribbean|One State Or Province
'8RRH'= Middle East|One State Or Province
'8RRJ'= Northern America|One State Or Province
'8RRK'= Oceania|One State Or Province
'8RRO'= Europe|Romania
'8RRU'= Europe|Russian Federation
'8RRW'= Africa|Rwanda
'8RSA'= Middle East|Saudi Arabia
'8RSB'= Oceania|Solomon Islands
'8RSC'= Oceania|Seychelles
'8RSD'= Africa|Sudan
'8RSE'= Europe|Sweden
'8RSG'= Asia|Singapore
'8RSH'= Africa|Saint Helena
'8RSI'= Europe|Slovenia
'8RSK'= Europe|Slovakia
'8RSL'= Africa|Sierra Leone
'8RSM'= Europe|San Marino
'8RSN'= Africa|Senegal
'8RSO'= Africa|Somalia
'8RSR'= Latin America and Caribbean|Suriname
'8RST'= Africa|Sao Tome and Principe
'8RSV'= Latin America and Caribbean|El Salvador
'8RSY'= Middle East|Syria
'8RSZ'= Africa|Swaziland
'8RTC'= Latin America and Caribbean|Turks and Caicos Islands
'8RTD'= Africa|Chad
'8RTG'= Africa|Togo
'8RTH'= Asia|Thailand
'8RTJ'= Asia|Tajikistan
'8RTK'= Oceania|Tokelau
'8RTL'= Asia|Timor-Leste
'8RTM'= Asia|Turkmenistan
'8RTN'= Africa|Tunisia
'8RTO'= Oceania|Tonga
'8RTR'= Middle East|Turkey
'8RTT'= Latin America and Caribbean|Trinidad and Tobago
'8RTV'= Oceania|Tuvalu
'8RTW'= Asia|Taiwan
'8RTZ'= Africa|Tanzania, United Republic Of
'8RUA'= Europe|Ukraine
'8RUB'= Asia|China-East
'8RUC'= Asia|China-North
'8RUD'= Asia|China-South Central
'8RUF'= Asia|China-Southwest
'8RUG'= Africa|Uganda
'8RUH'= Europe|Northern Ireland
'8RUI'= Europe|Wales
'8RUJ'= Latin America and Caribbean|All Latin America
'8RUK'= Northern America|USA-All Territories, Protectorates, Dependencies, Outposts
'8RUL'= Any Regions
'8RUM'= Northern America|Canada-Prairies
'8RUN'= Northern America|Canada-Atlantic Provinces
'8RUP'= Northern America|Canada-Quebec
'8RUQ'= Northern America|Canada-Northern Territories
'8RUR'= Oceania|All Australia and New Zealand
'8RUS'= Oceania|All Oceania excluding Australia and New Zealand
'8RUY'= Latin America and Caribbean|Uruguay
'8RUZ'= Asia|Uzbekistan
'8RVC'= Latin America and Caribbean|Saint Vincent and The Grenadines
'8RVE'= Latin America and Caribbean|Venezuela
'8RVN'= Asia|Viet Nam
'8RVU'= Oceania|Vanuatu
'8RWA'= Broad International Region|Worldwide Excluding Northern America
'8RWB'= Broad International Region|Worldwide Excluding USA
'8RWC'= Broad International Region|Worldwide Excluding USA and Europe
'8RWD'= Broad International Region|Worldwide Excluding Europe
'8RWE'= Broad International Region|Worldwide Excluding USA and UK
'8RWF'= Broad International Region|Worldwide Excluding UK
'8RWG'= Broad International Region|All English Speaking Countries
'8RWH'= Broad International Region|All English Speaking Countries Excluding USA
'8RWI'= Broad International Region|All Spanish Speaking Countries Excluding USA
'8RWJ'= Broad International Region|USA, Canada and Mexico
'8RWS'= Oceania|Samoa
'8RXX'= Not Applicable or None
'8RYB'= Africa|Up To 3 States Or Provinces
'8RYC'= Africa|Up To 5 States Or Provinces
'8RYD'= Asia|Up To 5 States Or Provinces
'8RYE'= Middle East|Yemen
'8RYF'= Europe|Up To 3 States Or Provinces
'8RYG'= Europe|Up To 5 States Or Provinces
'8RYH'= Latin America and Caribbean|Up To 3 States Or Provinces
'8RYI'= Latin America and Caribbean|Up To 5 States Or Provinces
'8RYJ'= Middle East|Up To 3 States Or Provinces
'8RYK'= Northern America|Up To 3 States Or Provinces
'8RYL'= Oceania|Up To 3 States Or Provinces
'8RYM'= Oceania|Up To 5 States Or Provinces
'8RYT'= Africa|Mayotte
'8RZA'= Africa|South Africa
'8RZM'= Africa|Zambia
'8RZW'= Africa|Zimbabwe
'9EIN'= Exclusivity For Industry
'9ELA'= Exclusivity For Language
'9EME'= Exclusivity For Media
'9ENE'= Non-Exclusive
'9ERE'= Exclusivity For Region
'9EXC'= All Exclusive
'9EXX'= Not Applicable or None
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 19, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PNG.html b/ExifTool/html/TagNames/PNG.html new file mode 100644 index 0000000..2f9a8c4 --- /dev/null +++ b/ExifTool/html/TagNames/PNG.html @@ -0,0 +1,691 @@ + + + + +PNG Tags + + + +

PNG Tags

+

Tags extracted from PNG images. See +http://www.libpng.org/pub/png/spec/1.2/ for the official PNG 1.2 +specification.

+ +

According to the specification, a PNG file should end at the IEND chunk, +however ExifTool will preserve any data found after this when writing unless +it is specifically deleted with -Trailer:All=. When reading, a minor +warning is issued if this trailer exists, and ExifTool will attempt to parse +this data as additional PNG chunks.

+ +

Also according to the PNG specification, there is no restriction on the +location of text-type chunks (tEXt, zTXt and iTXt). However, certain +utilities (including some Apple and Adobe utilities) won't read the XMP iTXt +chunk if it comes after the IDAT chunk, and at least one utility won't read +other text chunks here. For this reason, when writing, ExifTool 11.63 and +later create new text chunks (including XMP) before IDAT, and move existing +text chunks to before IDAT.

+ +

The PNG format contains CRC checksums that are validated when reading with +either the Verbose or Validate option. When writing, these checksums are +validated by default, but the FastScan option may be used to bypass this +check if speed is more of a concern.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'IHDR'ImageHeader---> PNG ImageHeader Tags
'PLTE'Paletteno 
'acTL'AnimationControl---> PNG AnimationControl Tags
'bKGD'BackgroundColorno 
'cHRM'PrimaryChromaticities---> PNG PrimaryChromaticities Tags
'cICP'CICodePoints---> PNG CICodePoints Tags
'caBX'JUMBF---> Jpeg2000 Tags
'dSIG'DigitalSignatureno 
'eXIf'eXIf---> EXIF Tags +
(this is where ExifTool will create new EXIF)
'fRAc'FractalParametersno 
'gAMA'Gammayes!(ExifTool reports the gamma for decoding the image, which is consistent with +the EXIF convention, but is the inverse of the stored encoding gamma)
'gIFg'GIFGraphicControlExtensionno 
'gIFt'GIFPlainTextExtensionno 
'gIFx'GIFApplicationExtensionno 
'hIST'PaletteHistogramno 
'iCCP'ICC_Profile---> ICC_Profile Tags +
(this is where ExifTool will write a new ICC_Profile. When creating a new +ICC_Profile, the SRGBRendering tag should be deleted if it exists)
'iCCP-name'ProfileNameyes(not a real tag ID, this tag represents the iCCP profile name, and may only +be written when the ICC_Profile is written)
'iDOT'AppleDataOffsetsno 
'iTXt'InternationalText---> PNG TextualData Tags
'oFFs'ImageOffsetno 
'pCAL'PixelCalibrationno 
'pHYs'PhysicalPixel---> PNG PhysicalPixel Tags
'sBIT'SignificantBitsno 
'sCAL'SubjectScale---> PNG SubjectScale Tags
'sPLT'SuggestedPaletteno 
'sRGB'SRGBRenderingyes!(this chunk should not be present if an iCCP chunk exists) +
0 = Perceptual +
1 = Relative Colorimetric +
2 = Saturation +
3 = Absolute Colorimetric
'sTER'StereoImage---> PNG StereoImage Tags
'tEXt'TextualData---> PNG TextualData Tags
'tIME'ModifyDateyes 
'tRNS'Transparencyno 
'tXMP'XMP---> XMP Tags +
(obsolete location specified by a September 2001 XMP draft)
'vpAg'VirtualPage---> PNG VirtualPage Tags
'zTXt'CompressedText---> PNG TextualData Tags
'zxIf'zxIf---> EXIF Tags +
(a once-proposed chunk for compressed EXIF)
+ +

PNG ImageHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ImageWidthno 
4ImageHeightno 
8BitDepthno 
9ColorTypeno0 = Grayscale +
2 = RGB +
3 = Palette +
4 = Grayscale with Alpha +
6 = RGB with Alpha
10Compressionno0 = Deflate/Inflate
11Filterno0 = Adaptive
12Interlaceno0 = Noninterlaced +
1 = Adam7 Interlace
+ +

PNG AnimationControl Tags

+

Tags found in the Animation Control chunk. See +https://wiki.mozilla.org/APNG_Specification for details.

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0AnimationFramesno 
1AnimationPlaysno 
+ +

PNG PrimaryChromaticities Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0WhitePointXno 
1WhitePointYno 
2RedXno 
3RedYno 
4GreenXno 
5GreenYno 
6BlueXno 
7BlueYno 
+ +

PNG CICodePoints Tags

+

These tags are found in the PNG cICP chunk and belong to the PNG-cICP family +1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ColorPrimariesno1 = BT.709 +
2 = Unspecified +
4 = BT.470 System M (historical) +
5 = BT.470 System B, G (historical) +
6 = BT.601 +
7 = SMPTE 240 +
8 = Generic film (color filters using illuminant C) +
9 = BT.2020, BT.2100 +
10 = SMPTE 428 (CIE 1921 XYZ) +
11 = SMPTE RP 431-2 +
12 = SMPTE EG 432-1 +
22 = EBU Tech. 3213-E
1TransferCharacteristicsno +
0 = For future use (0) +
1 = BT.709 +
2 = Unspecified +
3 = For future use (3) +
4 = BT.470 System M (historical) +
5 = BT.470 System B, G (historical) +
6 = BT.601 +
7 = SMPTE 240 M +
8 = Linear +
9 = Logarithmic (100 : 1 range) +
10 = Logarithmic (100 * Sqrt(10) : 1 range) +
11 = IEC 61966-2-4 +
12 = BT.1361 +
13 = sRGB or sYCC +
14 = BT.2020 10-bit systems +
15 = BT.2020 12-bit systems +
16 = SMPTE ST 2084, ITU BT.2100 PQ +
17 = SMPTE ST 428 +
18 = BT.2100 HLG, ARIB STD-B67
+
2MatrixCoefficientsno0 = Identity matrix +
1 = BT.709 +
2 = Unspecified +
3 = For future use (3) +
4 = US FCC 73.628 +
5 = BT.470 System B, G (historical) +
6 = BT.601 +
7 = SMPTE 240 M +
8 = YCgCo +
9 = BT.2020 non-constant luminance, BT.2100 YCbCr +
10 = BT.2020 constant luminance +
11 = SMPTE ST 2085 YDzDx +
12 = Chromaticity-derived non-constant luminance +
13 = Chromaticity-derived constant luminance +
14 = BT.2100 ICtCp
3VideoFullRangeFlagno 
+ +

PNG TextualData Tags

+

The PNG TextualData format allows arbitrary tag names to be used. The tags +listed below are the only ones that can be written (unless new user-defined +tags are added via the configuration file), however ExifTool will extract +any other TextualData tags that are found. All TextualData tags (including +tags not listed below) are removed when deleting all PNG tags.

+ +

These tags may be stored as tEXt, zTXt or iTXt chunks in the PNG image. By +default ExifTool writes new string-value tags as as uncompressed tEXt, or +compressed zTXt if the Compress (-z) option is used and Compress::Zlib is +available. Alternate language tags and values containing special characters +(unless the Latin character set is used) are written as iTXt, and compressed +if the Compress option is used and Compress::Zlib is available. Raw profile +information is always created as compressed zTXt if Compress::Zlib is +available, or tEXt otherwise. Standard XMP is written as uncompressed iTXt. +User-defined tags may set an 'iTXt' flag in the tag definition to be written +only as iTXt.

+ +

Alternate languages are accessed by suffixing the tag name with a '-', +followed by an RFC 3066 language code (eg. "PNG:Comment-fr", or +"Title-en-US"). See http://www.ietf.org/rfc/rfc3066.txt for the RFC 3066 +specification.

+ +

Some of the tags below are not registered as part of the PNG specification, +but are included here because they are generated by other software such as +ImageMagick.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Artist'Artiststring(unregistered)
'Author'Authorstring 
'Collection'Collectionstring 
'Comment'Commentstring 
'Copyright'Copyrightstring 
'Creation Time'CreationTimestring(stored in RFC-1123 format and converted to/from EXIF format by ExifTool)
'Description'Descriptionstring 
'Disclaimer'Disclaimerstring 
'Document'Documentstring(unregistered)
'Label'Labelstring(unregistered)
'Make'Makestring(unregistered)
'Model'Modelstring(unregistered)
'Raw profile type 8bim'Photoshop_Profile---> Photoshop Tags +
(unregistered)
'Raw profile type APP1'APP1_Profile---> EXIF Tags +
--> XMP Tags +
(unregistered)
'Raw profile type exif'EXIF_Profile---> EXIF Tags +
(unregistered)
'Raw profile type icc'ICC_Profile---> ICC_Profile Tags +
(unregistered)
'Raw profile type icm'ICC_Profile---> ICC_Profile Tags +
(unregistered)
'Raw profile type iptc'IPTC_Profile---> Photoshop Tags +
(unregistered. May be either IPTC IIM or Photoshop IRB format. This is +where ExifTool will add new IPTC, inside a Photoshop IRB container)
'Raw profile type xmp'XMP_Profile---> XMP Tags +
(unregistered)
'Software'Softwarestring 
'Source'Sourcestring 
'TimeStamp'TimeStampstring(unregistered)
'Title'Titlestring 
'URL'URLstring(unregistered)
'Warning'PNGWarningstring 
'XML:com.adobe.xmp'XMP---> XMP Tags +
(unregistered, but this is the location according to the June 2002 or later +XMP specification, and is where ExifTool will add a new XMP chunk if the +image didn't already contain XMP)
'create-date'CreateDatestring(unregistered)
'modify-date'ModDatestring(unregistered)
+ +

PNG PhysicalPixel Tags

+

These tags are found in the PNG pHYs chunk and belong to the PNG-pHYs family +1 group. They are all created together with default values if necessary +when any of these tags is written, and may only be deleted as a group.

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PixelsPerUnitXint32u(default 2834)
4PixelsPerUnitYint32u(default 2834)
8PixelUnitsint8u(default meters) +
0 = Unknown +
1 = meters
+ +

PNG SubjectScale Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SubjectUnitsno1 = meters +
2 = radians
1SubjectPixelWidthno 
2SubjectPixelHeightno 
+ +

PNG StereoImage Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0StereoModeno0 = Cross-fuse Layout +
1 = Diverging-fuse Layout
+ +

PNG VirtualPage Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0VirtualImageWidthno 
1VirtualImageHeightno 
2VirtualPageUnitsno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 9, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PSP.html b/ExifTool/html/TagNames/PSP.html new file mode 100644 index 0000000..ba1e5b2 --- /dev/null +++ b/ExifTool/html/TagNames/PSP.html @@ -0,0 +1,161 @@ + + + + +PSP Tags + + + +

PSP Tags

+

Tags extracted from Paint Shop Pro images (PSP, PSPIMAGE, PSPFRAME, +PSPSHAPE, PSPTUBE and TUB extensions).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'FileVersion'FileVersionno 
0x0000ImageInfo---> PSP Image Tags +
--> PSP Image Tags
0x0001CreatorInfo---> PSP Creator Tags
0x000aExtendedInfo---> PSP Ext Tags
+ +

PSP Image Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ImageWidthno 
4ImageHeightno 
8ImageResolutionno 
16ResolutionUnitno0 = None +
1 = inches +
2 = cm
17Compressionno0 = None +
1 = RLE +
2 = LZ77 +
3 = JPEG
19BitsPerSampleno 
21Planesno 
23NumColorsno 
+ +

PSP Creator Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000Titleno 
0x0001CreateDateno 
0x0002ModifyDateno 
0x0003Artistno 
0x0004Copyrightno 
0x0005Descriptionno 
0x0006CreatorAppIDno0 = Unknown +
1 = Paint Shop Pro
0x0007CreatorAppVersionno 
+ +

PSP Ext Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0003EXIFInfo---> EXIF Tags
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 12, 2010 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Palm.html b/ExifTool/html/TagNames/Palm.html new file mode 100644 index 0000000..770b8e9 --- /dev/null +++ b/ExifTool/html/TagNames/Palm.html @@ -0,0 +1,389 @@ + + + + +Palm Tags + + + +

Palm Tags

+

Information extracted from Palm database files (PDB and PRC extensions), +Mobipocket electronic books (MOBI), and Amazon Kindle KF7 and KF8 books (AZW +and AZW3).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0DatabaseNameno 
9CreateDateno 
10ModifyDateno 
11LastBackupDateno 
12ModificationNumberno 
15PalmFileTypeno +
'.pdfADBE' = Adobe Reader +
'BDOCWrdS' = WordSmith +
'BOOKMOBI' = Mobipocket +
'BVokBDIC' = BDicty +
'DATALSdb' = LIST +
'DB99DBOS' = DB (Database program) +
'DataPPrs' = eReader +
'DataPlkr' = Plucker +
'DataSprd' = QuickSheet +
'DataTlMl' = TealMeal +
'DataTlPt' = TealPaint +
'InfoINDB' = InfoView +
'InfoTlIf' = TealInfo +
'JbDbJBas' = JFile +
'JfDbJFil' = JFile Pro +
'Mdb1Mdb1' = MobileDB +
'PNRdPPrs' = eReader +
'PmDBPmDB' = HanDBase +
'SDocSilX' = iSilo 3 +
'SM01SMem' = SuperMemo +
'TEXtREAd' = PalmDOC +
'TEXtTlDc' = TealDoc +
'TdatTide' = Tides +
'ToGoToGo' = iSilo +
'ToRaTRPW' = TomeRaider +
'dataTDBP' = ThinkDB +
'vIMGView' = FireViewer (ImageViewer) +
'zTXTGPlm' = Weasel
+
+ +

Palm MOBI Tags

+

Information extracted from the MOBI header of Mobipocket and Amazon Kindle +KF7 and KF8 files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0Compressionno1 = None +
2 = PalmDOC +
17480 = HUFF/CDIC
1UncompressedTextLengthno 
3Encryptionno0 = None +
1 = Old Mobipocket +
2 = Mobipocket
6MobiTypeno +
2 = Mobipocket Book +
3 = PalmDoc Book +
4 = Audio +
232 = mobipocket? generated by kindlegen1.2 +
248 = KF8: generated by kindlegen2 +
257 = News +
258 = News_Feed +
259 = News_Magazine +
513 = PICS +
514 = WORD +
515 = XLS +
516 = PPT +
517 = TEXT +
518 = HTML
+
7CodePageno1252 = Windows Latin 1 (Western European) +
65001 = Unicode (UTF-8)
9MobiVersionno 
21BookNameno 
26MinimumVersionno 
+ +

Palm EXTH Tags

+

Information extracted from the MOBI extended header.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001DRMServerIDno 
0x0002DRMCommerceIDno 
0x0003DRM_E-BookBaseIDno 
0x0064Authorno 
0x0065Publisherno 
0x0066Imprintno 
0x0067Descriptionno 
0x0068ISBNno 
0x0069Subjectno+ 
0x006aPublishDateno 
0x006bReviewno 
0x006cContributorno 
0x006dRightsno 
0x006eSubjectCodeno 
0x006fBookTypeno 
0x0070Sourceno 
0x0071ASINno 
0x0072BookVersionno 
0x0073SampleFlagno 
0x0074StartReadingno 
0x0075Adultno 
0x0076RetailPriceno 
0x0077RetailPriceCurrencyno 
0x007dResourceCountno 
0x0081KF8CoverURIno 
0x00c8DictionaryShortNameno 
0x00ccCreatorSoftwareno1 = Mobigen +
2 = Mobipocket +
200 = Kindlegen (Windows) +
201 = Kindlegen (Linux) +
202 = Kindlegen (Mac)
0x00cdCreatorMajorVersionno 
0x00ceCreatorMinorVersionno 
0x00cfCreatorBuildNumberno 
0x00d0Watermarkno 
0x00d1Tamper-proofKeysno 
0x0191ClippingLimitno 
0x0192PublisherLimitno 
0x0194TextToSpeechno0 = Enabled +
1 = Disabled
0x0195RentalFlagno 
0x0196RentalExpirationDateno 
0x01f5CDETypeno 
0x01f6LastUpdateTimeno 
0x01f7UpdatedTitleno 
0x01f8ASIN2no 
0x020cLanguageno 
0x020dAlignmentno 
0x0217CreatorBuildNumber2no 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised May 31, 2014 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Panasonic.html b/ExifTool/html/TagNames/Panasonic.html new file mode 100644 index 0000000..bc7a123 --- /dev/null +++ b/ExifTool/html/TagNames/Panasonic.html @@ -0,0 +1,2136 @@ + + + + +Panasonic Tags + + + +

Panasonic Tags

+

+These tags are used in Panasonic/Leica cameras. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001ImageQualityint16u(quality of the main image, which may be in a different file) + +
1 = TIFF +
2 = High +
3 = Normal +
6 = Very High
  7 = RAW +
9 = Motion Picture +
11 = Full HD Movie +
12 = 4k Movie
+
0x0002FirmwareVersionundef(for some camera models such as the FZ30 this may be an internal production +reference number and not the actual firmware version)
0x0003WhiteBalanceint16u + +
1 = Auto +
2 = Daylight +
3 = Cloudy +
4 = Incandescent +
5 = Manual +
8 = Flash +
10 = Black & White
  11 = Manual 2 +
12 = Shade +
13 = Kelvin +
14 = Manual 3 +
15 = Manual 4 +
19 = Auto (cool)
+
0x0007FocusModeint16u + +
1 = Auto +
2 = Manual +
4 = Auto, Focus button +
5 = Auto, Continuous
  6 = AF-S +
7 = AF-C +
8 = AF-F
+
0x000fAFAreaModeint8u[2](DMC-FZ10) +
'0 1' = Spot Mode On +
'0 16' = Spot Mode Off +
(other models)
+
16 = Normal? +
'0 1' = 9-area +
'0 16' = 3-area (high speed) +
'0 23' = 23-area +
'0 49' = 49-area +
'0 225' = 225-area +
'1 0' = Spot Focusing +
'1 1' = 5-area +
'16 0' = 1-area +
'16 16' = 1-area (high speed) +
'32 0' = Tracking +
'32 1' = 3-area (left)? +
'32 2' = 3-area (center)? +
'32 3' = 3-area (right)? +
'64 0' = Face Detect +
'64 1' = Face Detect (animal detect on) +
'64 2' = Face Detect (animal detect off) +
'128 0' = Pinpoint focus +
'240 0' = Tracking
+
0x001aImageStabilizationint16u +
2 = On, Optical +
3 = Off +
4 = On, Mode 2 +
5 = On, Optical Panning +
6 = On, Body-only +
7 = On, Body-only Panning +
9 = Dual IS +
10 = Dual IS Panning +
11 = Dual2 IS +
12 = Dual2 IS Panning
+
0x001cMacroModeint16u1 = On +
2 = Off +
257 = Tele-Macro +
513 = Macro Zoom
0x001fShootingModeint16u + +
1 = Normal +
2 = Portrait +
3 = Scenery +
4 = Sports +
5 = Night Portrait +
6 = Program +
7 = Aperture Priority +
8 = Shutter Priority +
9 = Macro +
10 = Spot +
11 = Manual +
12 = Movie Preview +
13 = Panning +
14 = Simple +
15 = Color Effects +
16 = Self Portrait +
17 = Economy +
18 = Fireworks +
19 = Party +
20 = Snow +
21 = Night Scenery +
22 = Food +
23 = Baby +
24 = Soft Skin +
25 = Candlelight +
26 = Starry Night +
27 = High Sensitivity +
28 = Panorama Assist +
29 = Underwater +
30 = Beach +
31 = Aerial Photo +
32 = Sunset +
33 = Pet +
34 = Intelligent ISO +
35 = Clipboard +
36 = High Speed Continuous Shooting +
37 = Intelligent Auto +
39 = Multi-aspect +
41 = Transform +
42 = Flash Burst
  43 = Pin Hole +
44 = Film Grain +
45 = My Color +
46 = Photo Frame +
48 = Movie +
51 = HDR +
52 = Peripheral Defocus +
55 = Handheld Night Shot +
57 = 3D +
59 = Creative Control +
60 = Intelligent Auto Plus +
62 = Panorama +
63 = Glass Through +
64 = HDR +
66 = Digital Filter +
67 = Clear Portrait +
68 = Silky Skin +
69 = Backlit Softness +
70 = Clear in Backlight +
71 = Relaxing Tone +
72 = Sweet Child's Face +
73 = Distinct Scenery +
74 = Bright Blue Sky +
75 = Romantic Sunset Glow +
76 = Vivid Sunset Glow +
77 = Glistening Water +
78 = Clear Nightscape +
79 = Cool Night Sky +
80 = Warm Glowing Nightscape +
81 = Artistic Nightscape +
82 = Glittering Illuminations +
83 = Clear Night Portrait +
84 = Soft Image of a Flower +
85 = Appetizing Food +
86 = Cute Dessert +
87 = Freeze Animal Motion +
88 = Clear Sports Shot +
89 = Monochrome +
90 = Creative Control +
92 = Handheld Night Shot
+
0x0020Audioint16u1 = Yes +
2 = No +
3 = Stereo
0x0021DataDumpno 
0x0023WhiteBalanceBiasint16s 
0x0024FlashBiasint16s 
0x0025InternalSerialNumberundef[16](this number is unique, and contains the date of manufacture, but is not the +same as the number printed on the camera body)
0x0026PanasonicExifVersionundef 
0x0027VideoFrameRateint16u(only valid for older models) +
0 = n/a
0x0028ColorEffectint16u + +
1 = Off +
2 = Warm +
3 = Cool +
4 = Black & White
  5 = Sepia +
6 = Happy +
8 = Vivid
+
0x0029TimeSincePowerOnint32u(time in 1/100 s from when the camera was powered on to when the image is +written to memory card)
0x002aBurstModeint16u(decoding may be different for some models) +
0 = Off +
1 = On +
2 = Auto Exposure Bracketing (AEB) +
3 = Focus Bracketing +
4 = Unlimited +
8 = White Balance Bracketing +
17 = On (with flash) +
18 = Aperture Bracketing
+
0x002bSequenceNumberint32u 
0x002cContrastModeint16u(this decoding seems to work for some models such as the LC1, LX2, FZ7, FZ8, +FZ18 and FZ50, but may not be correct for other models such as the FX10, G1, L1, +L10 and LC80) +
0x0 = Normal +
0x1 = Low +
0x2 = High +
0x5 = Normal 2 +
0x6 = Medium Low +
0x7 = Medium High +
0xd = High Dynamic +
0x18 = Dynamic Range (film-like) +
0x2e = Match Filter Effects Toy +
0x37 = Match Photo Style L. Monochrome +
0x100 = Low +
0x110 = Normal +
0x120 = High
+(these values are used by the G2, GF1, GF2, GF3, GF5 and GF6) +
0 = -2 +
1 = -1 +
2 = Normal +
3 = +1 +
4 = +2 +
5 = Normal 2 +
7 = Nature (Color Film) +
9 = Expressive +
12 = Smooth (Color Film) or Pure (My Color) +
17 = Dynamic (B&W Film) +
22 = Smooth (B&W Film) +
25 = High Dynamic +
26 = Retro +
27 = Dynamic (Color Film) +
28 = Low Key +
29 = Toy Effect +
32 = Vibrant (Color Film) or Expressive (My Color) +
33 = Elegant (My Color) +
37 = Nostalgic (Color Film) +
41 = Dynamic Art (My Color) +
42 = Retro (My Color) +
45 = Cinema +
47 = Dynamic Mono +
50 = Impressive Art +
51 = Cross Process +
100 = High Dynamic 2 +
101 = Retro 2 +
102 = High Key 2 +
103 = Low Key 2 +
104 = Toy Effect 2 +
107 = Expressive 2 +
112 = Sepia +
117 = Miniature +
122 = Dynamic Monochrome +
127 = Old Days +
132 = Dynamic Monochrome 2 +
135 = Impressive Art 2 +
136 = Cross Process 2 +
137 = Toy Pop +
138 = Fantasy +
256 = Normal 3 +
272 = Standard +
288 = High +
(these values are used by the TZ10 and ZS7) +
0 = Normal +
1 = -2 +
2 = +2 +
5 = -1 +
6 = +1
0x002dNoiseReductionint16u(the encoding for this value is not consistent between models) + +
0 = Standard +
1 = Low (-1) +
2 = High (+1) +
3 = Lowest (-2) +
4 = Highest (+2) +
5 = +5
  6 = +6 +
65531 = -5 +
65532 = -4 +
65533 = -3 +
65534 = -2 +
65535 = -1
+
0x002eSelfTimerint16u +
0 = Off (0) +
1 = Off +
2 = 10 s +
3 = 2 s +
4 = 10 s / 3 pictures +
258 = 2 s after shutter pressed +
266 = 10 s after shutter pressed +
778 = 3 photos after 10 s
+
0x0030Rotationint16u1 = Horizontal (normal) +
3 = Rotate 180 +
6 = Rotate 90 CW +
8 = Rotate 270 CW
0x0031AFAssistLampint16u1 = Fired +
2 = Enabled but Not Used +
3 = Disabled but Required +
4 = Disabled and Not Required
0x0032ColorModeint16u0 = Normal +
1 = Natural +
2 = Vivid
0x0033BabyAgestring(or pet age)
0x0034OpticalZoomModeint16u1 = Standard +
2 = Extended
0x0035ConversionLensint16u1 = Off +
2 = Wide +
3 = Telephoto +
4 = Macro
0x0036TravelDayint16u 
0x0038BatteryLevelint16u + +
1 = Full +
2 = Medium +
3 = Low +
4 = Near Empty
  7 = Near Full +
8 = Medium Low +
256 = n/a
+
0x0039Contrastint16u0 = Normal
0x003aWorldTimeLocationint16u1 = Home +
2 = Destination
0x003bTextStampint16u1 = Off +
2 = On
0x003cProgramISOint16u-1 = n/a +
65534 = Intelligent ISO +
65535 = n/a
0x003dAdvancedSceneTypeint16u(used together with SceneMode to derive Composite AdvancedSceneMode)
0x003eTextStampint16u1 = Off +
2 = On
0x003fFacesDetectedint16u 
0x0040Saturationint16u0 = Normal
0x0041Sharpnessint16u0 = Normal
0x0042FilmModeint16u + +
0 = n/a +
1 = Standard (color) +
2 = Dynamic (color) +
3 = Nature (color) +
4 = Smooth (color)
  5 = Standard (B&W) +
6 = Dynamic (B&W) +
7 = Smooth (B&W) +
10 = Nostalgic +
11 = Vibrant
+
0x0043JPEGQualityint16u0 = n/a (Movie) +
2 = High +
3 = Standard +
6 = Very High +
255 = n/a (RAW only)
0x0044ColorTempKelvinint16u 
0x0045BracketSettingsint16u +
0 = No Bracket +
1 = 3 Images, Sequence 0/-/+ +
2 = 3 Images, Sequence -/0/+ +
3 = 5 Images, Sequence 0/-/+ +
4 = 5 Images, Sequence -/0/+ +
5 = 7 Images, Sequence 0/-/+ +
6 = 7 Images, Sequence -/0/+
+
0x0046WBShiftABint16u(positive is a shift toward blue)
0x0047WBShiftGMint16u(positive is a shift toward green)
0x0048FlashCurtainint16u0 = n/a +
1 = 1st +
2 = 2nd
0x0049LongExposureNoiseReductionint16u1 = Off +
2 = On
0x004bPanasonicImageWidthint32u 
0x004cPanasonicImageHeightint32u 
0x004dAFPointPositionrational64u[2](X Y coordinates of primary AF area center, in the range 0.0 to 1.0)
0x004eFaceDetInfo---> Panasonic FaceDetInfo Tags
0x0051LensTypestring 
0x0052LensSerialNumberstring 
0x0053AccessoryTypestring 
0x0054AccessorySerialNumberstring 
0x0059Transformundef[4](decoded as two 16-bit signed integers) +
'-1 1' = Slim Low +
'-3 2' = Slim High +
'0 0' = Off +
'1 1' = Stretch Low +
'3 2' = Stretch High
0x005dIntelligentExposureint16u(not valid for some models) +
0 = Off +
1 = Low +
2 = Standard +
3 = High
0x0060LensFirmwareVersionundef[4] 
0x0061FaceRecInfo---> Panasonic FaceRecInfo Tags
0x0062FlashWarningint16u0 = No +
1 = Yes (flash required but disabled)
0x0063RecognizedFaceFlags?undef[4] 
0x0065Titleundef 
0x0066BabyNameundef(or pet name)
0x0067Locationundef 
0x0069Countryundef 
0x006bStateundef 
0x006dCityundef(City/Town as stored by some models, or County/Township for others)
0x006fLandmarkundef 
0x0070IntelligentResolutionint8u0 = Off +
1 = Low +
2 = Standard +
3 = High +
4 = Extended
0x0076HDRShotint16u0 = Off +
3 = On
0x0077BurstSpeedint16u(images per second)
0x0079IntelligentD-Rangeint16u0 = Off +
1 = Low +
2 = Standard +
3 = High
0x007cClearRetouchint16u0 = Off +
1 = On
0x0080City2undef(City/Town/Village as stored by some models)
0x0086ManometerPressureint16u 
0x0089PhotoStyleint16u + +
0 = Auto +
1 = Standard or Custom +
2 = Vivid +
3 = Natural +
4 = Monochrome +
5 = Scenery +
6 = Portrait
  8 = Cinelike D +
9 = Cinelike V +
11 = L. Monochrome +
12 = Like709 +
15 = L. Monochrome D +
17 = V-Log +
18 = Cinelike D2
+
0x008aShadingCompensationint16u0 = Off +
1 = On
0x008bWBShiftIntelligentAutoint16u(value is -9 for blue to +9 for amber. Valid for Intelligent-Auto modes)
0x008cAccelerometerZint16u(positive is acceleration upwards)
0x008dAccelerometerXint16u(positive is acceleration to the left)
0x008eAccelerometerYint16u(positive is acceleration backwards)
0x008fCameraOrientationint8u + +
0 = Normal +
1 = Rotate CW +
2 = Rotate 180
  3 = Rotate CCW +
4 = Tilt Upwards +
5 = Tilt Downwards
+
0x0090RollAngleint16u(converted to degrees of clockwise camera rotation)
0x0091PitchAngleint16u(converted to degrees of upward camera tilt)
0x0092WBShiftCreativeControlint8u(WB shift or style strength. Valid for Creative-Control modes)
0x0093SweepPanoramaDirectionint8u0 = Off +
1 = Left to Right +
2 = Right to Left +
3 = Top to Bottom +
4 = Bottom to Top
0x0094SweepPanoramaFieldOfViewint16u 
0x0096TimerRecordingint8u0 = Off +
1 = Time Lapse +
2 = Stop-motion Animation +
3 = Focus Bracketing
0x009dInternalNDFilterrational64u 
0x009eHDRint16u + +
0 = Off +
100 = 1 EV +
200 = 2 EV +
300 = 3 EV
  32868 = 1 EV (Auto) +
32968 = 2 EV (Auto) +
33068 = 3 EV (Auto)
+
0x009fShutterTypeint16u0 = Mechanical +
1 = Electronic +
2 = Hybrid
0x00a1FilterEffectrational64u[0.5] +
'0 0' = Off +
'0 1' = Expressive +
'0 2' = Retro +
'0 4' = High Key +
'0 8' = Sepia +
'0 16' = High Dynamic +
'0 32' = Miniature Effect +
'0 1024' = Dynamic Monochrome +
'0 1048576' = Sunshine +
'0 134217728' = Silky Monochrome +
'0 16384' = One Point Color +
'0 2048' = Soft Focus +
'0 2097152' = Bleach Bypass +
'0 256' = Low Key +
'0 32768' = Star Filter +
'0 33554432' = Monochrome +
'0 4096' = Impressive Art +
'0 4194304' = Toy Pop +
'0 512' = Toy Effect +
'0 524288' = Old Days +
'0 67108864' = Rough Monochrome +
'0 8192' = Cross Process +
'0 8388608' = Fantasy
+
0x00a3ClearRetouchValuerational64u 
0x00a7OutputLUTyes(2-column by 432-row binary lookup table of unsigned short values for +converting to 16-bit output (1st column) from 14 bits (2nd column) with +camera contrast)
0x00abTouchAEint16u0 = Off +
1 = On
0x00acMonochromeFilterEffectint16u0 = Off +
1 = Yellow +
2 = Orange +
3 = Red +
4 = Green
0x00adHighlightShadowint16u[2] 
0x00afTimeStampstring 
0x00b3VideoBurstResolutionint16u1 = Off or 4K +
4 = 6K
0x00b4MultiExposureint16u0 = n/a +
1 = Off +
2 = On
0x00b9RedEyeRemovalint16u0 = Off +
1 = On
0x00bbVideoBurstModeint32u +
0x1 = Off +
0x4 = Post Focus +
0x18 = 4K Burst +
0x28 = 4K Burst (Start/Stop) +
0x48 = 4K Pre-burst +
0x108 = Loop Recording +
0x408 = Focus Stacking +
0x810 = 6K Burst +
0x820 = 6K Burst (Start/Stop) +
0x1001 = High Resolution Mode
+
0x00bcDiffractionCorrectionint16u0 = Off +
1 = Auto
0x00bdFocusBracketint16u(positive is further, negative is closer)
0x00beLongExposureNRUsedint16u0 = No +
1 = Yes
0x00bfPostFocusMergingint32u[2]'0 0' = Post Focus Auto Merging or None
0x00c1VideoPreburstint16u0 = No +
1 = 4K or 6K
0x00c4LensTypeMakeint16u 
0x00c5LensTypeModelint16u 
0x00caSensorTypeint16u0 = Multi-aspect +
1 = Standard
0x00d1ISOint32u 
0x00d2MonochromeGrainEffectint16u0 = Off +
1 = Low +
2 = Standard +
3 = High
0x00d6NoiseReductionStrengthrational64s 
0x00e4LensTypeModelint16u 
0x00e8MinimumISOint32u 
0x00eeDynamicRangeBoostint16u0 = Off +
1 = On
0x0e00PrintIM---> PrintIM Tags
0x2003TimeInfo---> Panasonic TimeInfo Tags
0x8000MakerNoteVersionundef 
0x8001SceneModeint16u + +
0 = Off +
1 = Normal +
2 = Portrait +
3 = Scenery +
4 = Sports +
5 = Night Portrait +
6 = Program +
7 = Aperture Priority +
8 = Shutter Priority +
9 = Macro +
10 = Spot +
11 = Manual +
12 = Movie Preview +
13 = Panning +
14 = Simple +
15 = Color Effects +
16 = Self Portrait +
17 = Economy +
18 = Fireworks +
19 = Party +
20 = Snow +
21 = Night Scenery +
22 = Food +
23 = Baby +
24 = Soft Skin +
25 = Candlelight +
26 = Starry Night +
27 = High Sensitivity +
28 = Panorama Assist +
29 = Underwater +
30 = Beach +
31 = Aerial Photo +
32 = Sunset +
33 = Pet +
34 = Intelligent ISO +
35 = Clipboard +
36 = High Speed Continuous Shooting +
37 = Intelligent Auto +
39 = Multi-aspect +
41 = Transform +
42 = Flash Burst
  43 = Pin Hole +
44 = Film Grain +
45 = My Color +
46 = Photo Frame +
48 = Movie +
51 = HDR +
52 = Peripheral Defocus +
55 = Handheld Night Shot +
57 = 3D +
59 = Creative Control +
60 = Intelligent Auto Plus +
62 = Panorama +
63 = Glass Through +
64 = HDR +
66 = Digital Filter +
67 = Clear Portrait +
68 = Silky Skin +
69 = Backlit Softness +
70 = Clear in Backlight +
71 = Relaxing Tone +
72 = Sweet Child's Face +
73 = Distinct Scenery +
74 = Bright Blue Sky +
75 = Romantic Sunset Glow +
76 = Vivid Sunset Glow +
77 = Glistening Water +
78 = Clear Nightscape +
79 = Cool Night Sky +
80 = Warm Glowing Nightscape +
81 = Artistic Nightscape +
82 = Glittering Illuminations +
83 = Clear Night Portrait +
84 = Soft Image of a Flower +
85 = Appetizing Food +
86 = Cute Dessert +
87 = Freeze Animal Motion +
88 = Clear Sports Shot +
89 = Monochrome +
90 = Creative Control +
92 = Handheld Night Shot
+
0x8002HighlightWarningint16u0 = Disabled +
1 = No +
2 = Yes
0x8003DarkFocusEnvironmentint16u1 = No +
2 = Yes
0x8004WBRedLevelint16u 
0x8005WBGreenLevelint16u 
0x8006WBBlueLevelint16u 
0x8008TextStampint16u1 = Off +
2 = On
0x8009TextStampint16u1 = Off +
2 = On
0x8010BabyAgestring(or pet age)
0x8012Transformundef[4](decoded as two 16-bit signed integers) +
'-1 1' = Slim Low +
'-3 2' = Slim High +
'0 0' = Off +
'1 1' = Stretch Low +
'3 2' = Stretch High
+ +

Panasonic FaceDetInfo Tags

+

Face detection position information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0NumFacePositionsint16u(number of detected face positions stored in this record. May be less than +FacesDetected)
1Face1Positionint16u[4](4 numbers: X/Y coordinates of the face center and width/height of face. +Coordinates are relative to an image twice the size of the thumbnail, or 320 +pixels wide)
5Face2Positionint16u[4] 
9Face3Positionint16u[4] 
13Face4Positionint16u[4] 
17Face5Positionint16u[4] 
+ +

Panasonic FaceRecInfo Tags

+

Tags written by cameras with facial recognition. These cameras not only +detect faces in an image, but also recognize specific people based a +user-supplied set of known faces.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FacesRecognizedint16u 
4RecognizedFace1Namestring[20] 
24RecognizedFace1Positionint16u[4](coordinates in same format as face detection tags above)
32RecognizedFace1Agestring[20] 
52RecognizedFace2Namestring[20] 
72RecognizedFace2Positionint16u[4] 
80RecognizedFace2Agestring[20] 
100RecognizedFace3Namestring[20] 
120RecognizedFace3Positionint16u[4] 
128RecognizedFace3Agestring[20] 
+ +

Panasonic TimeInfo Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PanasonicDateTimeundef[8] 
16TimeLapseShotNumberint32u 
+ +

Panasonic Leica2 Tags

+

These tags are used by the Leica M8.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0300Qualityint16u1 = Fine +
2 = Basic
0x0302UserProfileint32u1 = User Profile 1 +
2 = User Profile 2 +
3 = User Profile 3 +
4 = User Profile 0 (Dynamic)
0x0303SerialNumberint32u 
0x0304WhiteBalanceint16u(values above 0x8000 are converted to Kelvin color temperatures) + +
0 = Auto or Manual +
1 = Daylight +
2 = Fluorescent +
3 = Tungsten
  4 = Flash +
10 = Cloudy +
11 = Shade
+
0x0310LensTypeint32u--> Panasonic LensType Values
0x0311ExternalSensorBrightnessValuerational64s("blue dot" measurement)
0x0312MeasuredLVrational64s(imaging sensor or TTL exposure meter measurement)
0x0313ApproximateFNumberrational64u 
0x0320CameraTemperatureint32s 
0x0321ColorTemperatureint32u 
0x0322WBRedLevelrational64u 
0x0323WBGreenLevelrational64u 
0x0324WBBlueLevelrational64u 
0x0325UV-IRFilterCorrectionint32u0 = Not Active +
1 = Active
0x0330CCDVersionint32u 
0x0331CCDBoardVersionint32u 
0x0332ControllerBoardVersionint32u 
0x0333M16CVersionint32u 
0x0340ImageIDNumberint32u 
+ +

Panasonic LensType Values

+

the LensType value is obtained by splitting the stored value into 2 +integers: The stored value divided by 4, and its lower 2 bits. The second +number is used only if necessary to identify certain manually coded lenses +on the M9, or the focal length of some multi-focal lenses.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLensTypeValueLensType
1= Elmarit-M 21mm f/2.842= Tri-Elmar-M 28-35-50mm f/4 ASPH.
3= Elmarit-M 28mm f/2.8 (III)43= Summarit-M 35mm f/2.5
4= Tele-Elmarit-M 90mm f/2.8 (II)44= Summarit-M 50mm f/2.5
5= Summilux-M 50mm f/1.4 (II)45= Summarit-M 75mm f/2.5
6= Summicron-M 35mm f/2 (IV)46= Summarit-M 90mm f/2.5
7= Summicron-M 90mm f/2 (II)47= Summilux-M 21mm f/1.4 ASPH.
9= Elmarit-M 135mm f/2.8 (I/II)48= Summilux-M 24mm f/1.4 ASPH.
11= Summaron-M 28mm f/5.649= Noctilux-M 50mm f/0.95 ASPH.
12= Thambar-M 90mm f/2.250= Elmar-M 24mm f/3.8 ASPH.
16= Tri-Elmar-M 16-18-21mm f/4 ASPH.51= Super-Elmar-M 21mm f/3.4 Asph
23= Summicron-M 50mm f/2 (III)52= Apo-Telyt-M 18mm f/3.8 ASPH.
24= Elmarit-M 21mm f/2.8 ASPH.53= Apo-Telyt-M 135mm f/3.4
25= Elmarit-M 24mm f/2.8 ASPH.58= Noctilux-M 75mm f/1.25 ASPH.
26= Summicron-M 28mm f/2 ASPH.'0 0'= Uncoded lens
27= Elmarit-M 28mm f/2.8 (IV)'6 0'= Summilux-M 35mm f/1.4
28= Elmarit-M 28mm f/2.8 ASPH.'9 0'= Apo-Telyt-M 135mm f/3.4
29= Summilux-M 35mm f/1.4 ASPH.'16 1'= Tri-Elmar-M 16-18-21mm f/4 ASPH. (at 16mm)
30= Summicron-M 35mm f/2 ASPH.'16 2'= Tri-Elmar-M 16-18-21mm f/4 ASPH. (at 18mm)
31= Noctilux-M 50mm f/1'16 3'= Tri-Elmar-M 16-18-21mm f/4 ASPH. (at 21mm)
32= Summilux-M 50mm f/1.4 ASPH.'29 0'= Summilux-M 35mm f/1.4 ASPHERICAL
33= Summicron-M 50mm f/2 (IV, V)'31 0'= Noctilux-M 50mm f/1.2
34= Elmar-M 50mm f/2.8'39 0'= Tele-Elmar-M 135mm f/4 (II)
35= Summilux-M 75mm f/1.4'41 3'= Apo-Summicron-M 50mm f/2 ASPH.
36= Apo-Summicron-M 75mm f/2 ASPH.'42 1'= Tri-Elmar-M 28-35-50mm f/4 ASPH. (at 28mm)
37= Apo-Summicron-M 90mm f/2 ASPH.'42 2'= Tri-Elmar-M 28-35-50mm f/4 ASPH. (at 35mm)
38= Elmarit-M 90mm f/2.8'42 3'= Tri-Elmar-M 28-35-50mm f/4 ASPH. (at 50mm)
39= Macro-Elmar-M 90mm f/4'51 2'= Super-Elmar-M 14mm f/3.8 Asph
40= Macro-Adapter M'53 2'= Apo-Telyt-M 135mm f/3.4
41= Apo-Summicron-M 50mm f/2 ASPH.'53 3'= Apo-Summicron-M 50mm f/2 (VI)
+ +

Panasonic Leica3 Tags

+

These tags are used by the Leica R8 and R9 digital backs.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x000bSerialInfo---> Panasonic SerialInfo Tags
0x000dWB_RGBLevelsint16u[3] 
+ +

Panasonic SerialInfo Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
4SerialNumberno 
+ +

Panasonic Leica4 Tags

+

This information is written by the M9.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x3000Subdir3000---> Panasonic Subdir Tags
0x3100Subdir3100---> Panasonic Subdir Tags
0x3400Subdir3400---> Panasonic Subdir Tags
0x3900Subdir3900---> Panasonic Subdir Tags
+ +

Panasonic Subdir Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x300aContrastint32u0 = Low +
1 = Medium Low +
2 = Normal +
3 = Medium High +
4 = High
0x300bSharpeningint32u0 = Off +
1 = Low +
2 = Normal +
3 = Medium High +
4 = High
0x300dSaturationint32u + +
0 = Low +
1 = Medium Low +
2 = Normal +
3 = Medium High
  4 = High +
5 = Black & White +
6 = Vintage B&W
+
0x3033WhiteBalanceint32u +
0 = Auto +
1 = Tungsten +
2 = Fluorescent +
3 = Daylight Fluorescent +
4 = Daylight +
5 = Flash +
6 = Cloudy +
7 = Shade +
8 = Manual +
9 = Kelvin
+
0x3034JPEGQualityint32u94 = Basic +
97 = Fine
0x3036WB_RGBLevelsrational64u[3] 
0x3038UserProfilestring 
0x303aJPEGSizeint32u0 = 5216x3472 +
1 = 3840x2592 +
2 = 2592x1728 +
3 = 1728x1152 +
4 = 1280x864
0x3103SerialNumberstring 
0x3109FirmwareVersionstring 
0x312aBaseISOint32u 
0x312bSensorWidthint32u 
0x312cSensorHeightint32u 
0x312dSensorBitDepthint32u 
0x3402CameraTemperatureint32s 
0x3405LensTypeint32u--> Panasonic LensType Values
0x3406ApproximateFNumberrational64u 
0x3407MeasuredLVint32s(imaging sensor or TTL exposure meter measurement)
0x3408ExternalSensorBrightnessValueint32s("blue dot" measurement)
0x3901Data1---> Panasonic Data1 Tags
0x3902Data2---> Panasonic Data2 Tags
+ +

Panasonic Data1 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
22LensTypeint32u--> Panasonic LensType Values
+ +

Panasonic Data2 Tags

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +

Panasonic Leica5 Tags

+

This information is written by the X1, X2, X VARIO and T.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0303LensTypestring(Leica T only)
0x0305SerialNumberint32u 
0x0407OriginalFileNamestring 
0x0408OriginalDirectorystring 
0x040aFocusInfo---> Panasonic FocusInfo Tags
0x040dExposureModeint8u[4]'0 0 0 0' = Program AE +
'1 0 0 0' = Aperture-priority AE +
'1 1 0 0' = Aperture-priority AE (1) +
'2 0 0 0' = Shutter speed priority AE +
'3 0 0 0' = Manual
0x0410ShotInfo---> Panasonic ShotInfo Tags
0x0412FilmModestring 
0x0413WB_RGBLevelsrational64u[3] 
0x0500InternalSerialNumberundef 
+ +

Panasonic FocusInfo Tags

+
+
+ + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0FocusDistanceint16u 
1FocalLengthint16u 
+ +

Panasonic ShotInfo Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0FileIndexint16u 
+ +

Panasonic Leica6 Tags

+

This information is written by the S2 and M (Typ 240), as a trailer in JPEG +images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0300PreviewImageundef(S2 and M (Typ 240))
0x0301UnknownBlock?no(unknown 320kB block, not copied to JPEG images)
0x0303LensTypestring 
0x0304FocusDistanceint32u(focus distance in mm for most models, but cm for others)
0x0311ExternalSensorBrightnessValuerational64s(Leica S only)
0x0312MeasuredLVrational64s(Leica S only)
0x0320FirmwareVersionint8u[4](Leica S only)
0x0321LensSerialNumberint32u(Leica S only)
+ +

Panasonic Leica9 Tags

+

This information is written by the Leica S (Typ 007) and M10 models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0304FocusDistanceint32u(focus distance in mm for most models, but cm for others)
0x0311ExternalSensorBrightnessValuerational64s 
0x0312MeasuredLVrational64s 
0x034cUserProfilestring 
0x0359ISOSelectedint32s0 = Auto
0x035aFNumberint32s 
0x035bCorrelatedColorTempint16u 
0x035cColorTintint16s 
0x035dWhitePointrational64u[2] 
+ +

Panasonic Type2 Tags

+

This type of maker notes is used by models such as the NV-DS65, PV-D2002, +PV-DC3000, PV-DV203, PV-DV401, PV-DV702, PV-L2001, PV-SD4090, PV-SD5000 and +iPalm.

+
+
+ + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0MakerNoteTypeno 
3Gainno 
+ +

Panasonic PANA Tags

+

Tags extracted from the PANA and LEIC user data found in MP4 videos from +various Panasonic and Leica models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
4Modelno 
12Modelno 
16JPEG-likeData---> EXIF Tags
22Modelno 
52Version1no 
62Version2no 
80MakerNoteLeica5---> Panasonic Leica5 Tags
88ThumbnailWidthno(Panasonic models)
90ThumbnailHeightno 
92ThumbnailImageno 
1334ThumbnailWidthno(Leica X Vario)
1338ThumbnailHeightno 
1342ThumbnailLengthno 
1350ThumbnailImageno 
1358ThumbnailWidthno(Leica X Vario)
1362ThumbnailHeightno 
1366ThumbnailLengthno 
1374ThumbnailImageno 
16488ExifData---> EXIF Tags
16512ExifData---> EXIF Tags
0x00200080ExifData---> EXIF Tags
+ +

Panasonic AdvancedSceneMode Values

+

A Composite tag derived from Model, SceneMode and AdvancedSceneType.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueAdvancedSceneModeValueAdvancedSceneMode
'0 1'= Off'59 1'= Expressive
'2 2'= Outdoor Portrait'59 2'= Retro
'2 3'= Indoor Portrait'59 3'= High Key
'2 4'= Creative Portrait'59 4'= Sepia
'3 2'= Nature'59 5'= High Dynamic
'3 3'= Architecture'59 6'= Miniature
'3 4'= Creative Scenery'59 9'= Low Key
'4 2'= Outdoor Sports'59 10'= Toy Effect
'4 3'= Indoor Sports'59 11'= Dynamic Monochrome
'4 4'= Creative Sports'59 12'= Soft
'9 2'= Flower'66 1'= Impressive Art
'9 3'= Objects'66 2'= Cross Process
'9 4'= Creative Macro'66 3'= Color Select
'18 1'= High Sensitivity'66 4'= Star
'20 1'= Fireworks'90 3'= Old Days
'21 2'= Illuminations'90 4'= Sunshine
'21 4'= Creative Night Scenery'90 5'= Bleach Bypass
'26 1'= High-speed Burst (shot 1)'90 6'= Toy Pop
'27 1'= High-speed Burst (shot 2)'90 7'= Fantasy
'29 1'= Snow'90 8'= Monochrome
'30 1'= Starry Sky'90 9'= Rough Monochrome
'31 1'= Beach'90 10'= Silky Monochrome
'36 1'= High-speed Burst (shot 3)'92 1'= Handheld Night Shot
'39 1'= Aerial Photo / Underwater / Multi-aspect'DMC-TZ40 90 1'= Expressive
'45 2'= Cinema'DMC-TZ40 90 2'= Retro
'45 7'= Expressive'DMC-TZ40 90 3'= High Key
'45 8'= Retro'DMC-TZ40 90 4'= Sepia
'45 9'= Pure'DMC-TZ40 90 5'= High Dynamic
'45 10'= Elegant'DMC-TZ40 90 6'= Miniature
'45 12'= Monochrome'DMC-TZ40 90 9'= Low Key
'45 13'= Dynamic Art'DMC-TZ40 90 10'= Toy Effect
'45 14'= Silhouette'DMC-TZ40 90 11'= Dynamic Monochrome
'51 2'= HDR Art'DMC-TZ40 90 12'= Soft
'51 3'= HDR B&W  
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 13, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PanasonicRaw.html b/ExifTool/html/TagNames/PanasonicRaw.html new file mode 100644 index 0000000..f50ca08 --- /dev/null +++ b/ExifTool/html/TagNames/PanasonicRaw.html @@ -0,0 +1,686 @@ + + + + +PanasonicRaw Tags + + + +

PanasonicRaw Tags

+

These tags are found in IFD0 of Panasonic/Leica RAW, RW2 and RWL images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001PanasonicRawVersionundef 
0x0002SensorWidthno 
0x0003SensorHeightno 
0x0004SensorTopBorderno 
0x0005SensorLeftBorderno 
0x0006SensorBottomBorderno 
0x0007SensorRightBorderno 
0x0008SamplesPerPixelint16u! 
0x0009CFAPatternint16u!0 = n/a +
1 = [Red,Green][Green,Blue] +
2 = [Green,Red][Blue,Green] +
3 = [Green,Blue][Red,Green] +
4 = [Blue,Green][Green,Red]
0x000aBitsPerSampleint16u! 
0x000bCompressionint16u!34316 = Panasonic RAW 1 +
34826 = Panasonic RAW 2 +
34828 = Panasonic RAW 3 +
34830 = Panasonic RAW 4
0x000eLinearityLimitRedint16u 
0x000fLinearityLimitGreenint16u 
0x0010LinearityLimitBlueint16u 
0x0011RedBalanceint16u(found in Digilux 2 RAW images)
0x0012BlueBalanceint16u 
0x0013WBInfo---> PanasonicRaw WBInfo Tags
0x0017ISOint16u 
0x0018HighISOMultiplierRedint16u 
0x0019HighISOMultiplierGreenint16u 
0x001aHighISOMultiplierBlueint16u 
0x001bNoiseReductionParamsundef[n]!(the camera's default noise reduction setup. The first number is the number +of entries, then for each entry there are 4 numbers: an ISO speed, and +noise-reduction strengths the R, G and B channels)
0x001cBlackLevelRedint16u 
0x001dBlackLevelGreenint16u 
0x001eBlackLevelBlueint16u 
0x0024WBRedLevelint16u 
0x0025WBGreenLevelint16u 
0x0026WBBlueLevelint16u 
0x0027WBInfo2---> PanasonicRaw WBInfo2 Tags
0x002dRawFormatint16u! 
0x002eJpgFromRawundef!--> JPEG Tags +
(processed as an embedded document because it contains full EXIF)
0x002fCropTopint16u 
0x0030CropLeftint16u 
0x0031CropBottomint16u 
0x0032CropRightint16u 
0x010fMakestring 
0x0110Modelstring 
0x0111StripOffsetsno 
0x0112Orientationint16u +
1 = Horizontal (normal) +
2 = Mirror horizontal +
3 = Rotate 180 +
4 = Mirror vertical +
5 = Mirror horizontal and rotate 270 CW +
6 = Rotate 90 CW +
7 = Mirror horizontal and rotate 90 CW +
8 = Rotate 270 CW
+
0x0116RowsPerStripno 
0x0117StripByteCountsno 
0x0118RawDataOffsetno 
0x0119DistortionInfo---> PanasonicRaw DistortionInfo Tags
0x011cGammaint16u 
0x0120CameraIFD---> PanasonicRaw CameraIFD Tags
0x0121Multishotint32u0 = Off +
65536 = Pixel Shift
0x0127JpgFromRaw2no 
0x013bArtiststring 
0x02bcApplicationNotesint8u!--> XMP Tags
0x8298Copyrightstring 
0x83bbIPTC-NAAint32u!--> IPTC Tags
0x8769ExifOffset---> EXIF Tags
0x8825GPSInfo---> GPS Tags
+ +

PanasonicRaw WBInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0NumWBEntriesint16u 
1WBType1int16u--> EXIF LightSource Values
2WB_RBLevels1int16u[2] 
4WBType2int16u--> EXIF LightSource Values
5WB_RBLevels2int16u[2] 
7WBType3int16u--> EXIF LightSource Values
8WB_RBLevels3int16u[2] 
10WBType4int16u--> EXIF LightSource Values
11WB_RBLevels4int16u[2] 
13WBType5int16u--> EXIF LightSource Values
14WB_RBLevels5int16u[2] 
16WBType6int16u--> EXIF LightSource Values
17WB_RBLevels6int16u[2] 
19WBType7int16u--> EXIF LightSource Values
20WB_RBLevels7int16u[2] 
+ +

PanasonicRaw WBInfo2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0NumWBEntriesint16u 
1WBType1int16u--> EXIF LightSource Values
2WB_RGBLevels1int16u[3] 
5WBType2int16u--> EXIF LightSource Values
6WB_RGBLevels2int16u[3] 
9WBType3int16u--> EXIF LightSource Values
10WB_RGBLevels3int16u[3] 
13WBType4int16u--> EXIF LightSource Values
14WB_RGBLevels4int16u[3] 
17WBType5int16u--> EXIF LightSource Values
18WB_RGBLevels5int16u[3] 
21WBType6int16u--> EXIF LightSource Values
22WB_RGBLevels6int16u[3] 
25WBType7int16u--> EXIF LightSource Values
26WB_RGBLevels7int16u[3] 
+ +

PanasonicRaw DistortionInfo Tags

+

Lens distortion correction information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0002DistortionParam02int16s 
0x0004DistortionParam04int16s 
0x0005DistortionScaleint16s 
7.1DistortionCorrectionint16s[val & 0xf] +
0 = Off +
1 = On
0x0008DistortionParam08int16s 
0x0009DistortionParam09int16s 
0x000bDistortionParam11int16s 
0x000cDistortionN?int16s 
+ +

PanasonicRaw CameraIFD Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x1001MultishotOnno0 = No +
1 = Yes
0x1100FocusStepNearno 
0x1101FocusStepCountno 
0x1102FlashFiredno0 = No +
1 = Yes
0x1105ZoomPositionno(in the range 0-255 for most cameras)
0x1200LensAttachedno(many CameraIFD tags are invalid if there is no lens attached) +
0 = No +
1 = Yes
0x1201LensTypeMakeno 
0x1202LensTypeModelno 
0x1203FocalLengthIn35mmFormatno 
0x1301ApertureValueno 
0x1302ShutterSpeedValueno 
0x1303SensitivityValueno 
0x1305HighISOModeno1 = On +
2 = Off
0x1412FacesDetectedno0 = No +
1 = Yes
0x3200WB_CFA0_LevelDaylightno 
0x3201WB_CFA1_LevelDaylightno 
0x3202WB_CFA2_LevelDaylightno 
0x3203WB_CFA3_LevelDaylightno 
0x3300WhiteBalanceSetno--> PanasonicRaw WhiteBalance Values
0x3420WB_RedLevelAutono 
0x3421WB_BlueLevelAutono 
0x3501Orientationno +
1 = Horizontal (normal) +
2 = Mirror horizontal +
3 = Rotate 180 +
4 = Mirror vertical +
5 = Mirror horizontal and rotate 270 CW +
6 = Rotate 90 CW +
7 = Mirror horizontal and rotate 90 CW +
8 = Rotate 270 CW
+
0x3600WhiteBalanceDetectedno--> PanasonicRaw WhiteBalance Values
+ +

PanasonicRaw WhiteBalance Values

+
+
+ + + + + + + + + + + + + + + + + +
ValueWhiteBalanceValueWhiteBalanceValueWhiteBalance
0= Auto5= Flash10= Custom#3
1= Daylight6= n/a11= Custom#4
2= Cloudy7= n/a12= Shade
3= Tungsten8= Custom#113= Kelvin
4= n/a9= Custom#216= AWBc
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 28, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Parrot.html b/ExifTool/html/TagNames/Parrot.html new file mode 100644 index 0000000..f080c92 --- /dev/null +++ b/ExifTool/html/TagNames/Parrot.html @@ -0,0 +1,676 @@ + + + + +Parrot Tags + + + +

Parrot mett Tags

+

Streaming metadata found in Parrot drone videos. See +https://developer.parrot.com/docs/pdraw/metadata.html for the +specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'P1'ParrotV1---> Parrot V1 Tags
'P2'ParrotV2---> Parrot V2 Tags
'P3'ParrotV3---> Parrot V3 Tags
'E1'ParrotTimeStamp---> Parrot TimeStamp Tags
'E2'ParrotFollowMe---> Parrot FollowMe Tags
'E3'ParrotAutomation---> Parrot Automation Tags
'application/arcore-accel'ARCoreAccel---> Parrot ARCoreAccel Tags
'application/arcore-custom-event'ARCoreCustom---> Parrot ARCoreCustom Tags
'application/arcore-gyro'ARCoreGyro---> Parrot ARCoreGyro Tags
'application/arcore-video-0'ARCoreVideo---> Parrot ARCoreVideo Tags
+ +

Parrot V1 Tags

+

Parrot version 1 streaming metadata.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4DroneYawno 
6DronePitchno 
8DroneRollno 
10CameraPanno 
12CameraTiltno 
14FrameViewno 
22ExposureTimeno 
24ISOno 
26WifiRSSIno 
27Batteryno 
28GPSLatitudeno 
32GPSLongitudeno 
36GPSAltitudeno[val >> 8 & 0xffffff]
36.1GPSSatellitesno[val & 0xff]
40AltitudeFromTakeOffno 
44DistanceFromHomeno 
48SpeedXno 
50SpeedYno 
52SpeedZno 
54Binningno[val >> 7 & 0x1]
54.1FlyingStateno[val & 0x7f] + +
0 = Landed +
1 = Taking Off +
2 = Hovering
  3 = Flying +
4 = Landing +
5 = Emergency
+
55Animationno[val >> 7 & 0x1]
55.1PilotingModeno[val & 0x7f] +
0 = Manual +
1 = Return Home +
2 = Flight Plan +
3 = Follow Me
+ +

Parrot V2 Tags

+

Parrot version 2 basic streaming metadata.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4Elevationno(estimated distance from ground)
8GPSLatitudeno 
12GPSLongitudeno 
16GPSAltitudeno[val >> 8 & 0xffffff]
16.1GPSSatellitesno[val & 0xff]
20GPSVelocityNorthno 
22GPSVelocityEastno 
24GPSVelocityDownno 
26AirSpeedno 
28DroneQuaternionno 
36FrameViewno 
44CameraPanno 
46CameraTiltno 
48ExposureTimeno 
50ISOno 
52Binningno[val >> 7 & 0x1]
52.1FlyingStateno[val & 0x7f] + +
0 = Landed +
1 = Taking Off +
2 = Hovering +
3 = Flying +
4 = Landing
  5 = Emergency +
6 = User Takeoff +
7 = Motor Ramping +
8 = Emergency Landing
+
53Animationno[val >> 7 & 0x1]
53.1PilotingModeno[val & 0x7f] +
0 = Manual +
1 = Return Home +
2 = Flight Plan +
3 = Follow Me / Tracking +
4 = Magic Carpet +
5 = Move To
+
54WifiRSSIno 
55Batteryno 
'Groups'Groupsno 
+ +

Parrot V3 Tags

+

Parrot version 3 basic streaming metadata.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4Elevationno(estimated distance from ground)
8GPSLatitudeno 
12GPSLongitudeno 
16GPSAltitudeno[val >> 8 & 0xffffff]
16.1GPSSatellitesno[val & 0xff]
20GPSVelocityNorthno 
22GPSVelocityEastno 
24GPSVelocityDownno 
26AirSpeedno 
28DroneQuaternionno 
36FrameBaseViewno 
44FrameViewno 
52ExposureTimeno 
54ISOno 
56RedBalanceno 
58BlueBalanceno 
60FOVno(horizontal and vertical field of view in degrees)
64LinkGoodputno[val >> 8 & 0xffffff]
64.1LinkQualityno(0-5) +
[val & 0xff]
68WifiRSSIno 
69Batteryno 
70Binningno[val >> 7 & 0x1]
70.1FlyingStateno[val & 0x7f] + +
0 = Landed +
1 = Taking Off +
2 = Hovering +
3 = Flying +
4 = Landing
  5 = Emergency +
6 = User Takeoff +
7 = Motor Ramping +
8 = Emergency Landing
+
71Animationno[val >> 7 & 0x1]
71.1PilotingModeno[val & 0x7f] +
0 = Manual +
1 = Return Home +
2 = Flight Plan +
3 = Follow Me / Tracking +
4 = Magic Carpet +
5 = Move To
+
+ +

Parrot TimeStamp Tags

+

Parrot streaming metadata timestamp extension.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
4TimeStampno 
+ +

Parrot FollowMe Tags

+

Parrot streaming metadata follow-me extension.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4GPSTargetLatitudeno 
8GPSTargetLongitudeno 
12GPSTargetAltitudeno 
16Follow-meModenoBit 0 = Follow-me enabled +
Bit 1 = Follow-me +
Bit 2 = Angle locked
17Follow-meAnimationno0 = None +
1 = Orbit +
2 = Boomerang +
3 = Parabola +
4 = Zenith
+ +

Parrot Automation Tags

+

Parrot streaming metadata automation extension.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4GPSFramingLatitudeno 
8GPSFramingLongitudeno 
12GPSFramingAltitudeno 
16GPSDestLatitudeno 
20GPSDestLongitudeno 
24GPSDestAltitudeno 
28AutomationAnimationno + +
0 = None +
1 = Orbit +
2 = Boomerang +
3 = Parabola +
4 = Dolly Slide +
5 = Dolly Zoom +
6 = Reveal Vertical +
7 = Reveal Horizontal
  8 = Candle +
9 = Flip Front +
10 = Flip Back +
11 = Flip Left +
12 = Flip Right +
13 = Twist Up +
14 = Position Twist Up
+
29AutomationFlagsnoBit 0 = Follow-me enabled +
Bit 1 = Look-at-me enabled +
Bit 2 = Angle locked
+ +

Parrot ARCoreAccel Tags

+

ARCore accelerometer data.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4AccelerometerUnknown?no 
5Accelerometerno 
+ +

Parrot ARCoreCustom Tags

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +

Parrot ARCoreGyro Tags

+

ARCore accelerometer data.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4GyroscopeUnknown?no 
5Gyroscopeno 
+ +

Parrot ARCoreVideo Tags

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 21, 2022 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Pentax.html b/ExifTool/html/TagNames/Pentax.html new file mode 100644 index 0000000..824aa5b --- /dev/null +++ b/ExifTool/html/TagNames/Pentax.html @@ -0,0 +1,4926 @@ + + + + +Pentax Tags + + + +

Pentax Tags

+

+These tags are used in Pentax/Asahi cameras. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000PentaxVersionint8u[4] 
0x0001PentaxModelTypeint16u 
0x0002PreviewImageSizeint16u[2] 
0x0003PreviewImageLengthint32u* 
0x0004PreviewImageStartint32u* 
0x0005PentaxModelIDint32u--> Pentax PentaxModelID Values
0x0006Dateundef[4](changing either Date or Time will affect ShutterCount decryption)
0x0007Timeundef[3] 
0x0008Qualityint16u + +
0 = Good +
1 = Better +
2 = Best +
3 = TIFF +
4 = RAW
  5 = Premium +
7 = RAW (pixel shift enabled) +
8 = Dynamic Pixel Shift +
65535 = n/a
+
0x0009PentaxImageSizeint16u + +
0 = 640x480 +
1 = Full +
2 = 1024x768 +
3 = 1280x960 +
4 = 1600x1200 +
5 = 2048x1536 +
8 = 2560x1920 or 2304x1728 +
9 = 3072x2304 +
10 = 3264x2448 +
19 = 320x240 +
20 = 2288x1712 +
21 = 2592x1944 +
22 = 2304x1728 or 2592x1944 +
23 = 3056x2296 +
25 = 2816x2212 or 2816x2112 +
27 = 3648x2736
  29 = 4000x3000 +
30 = 4288x3216 +
31 = 4608x3456 +
129 = 1920x1080 +
135 = 4608x2592 +
257 = 3216x3216 +
'0 0' = 2304x1728 +
'4 0' = 1600x1200 +
'5 0' = 2048x1536 +
'8 0' = 2560x1920 +
'32 2' = 960x640 +
'33 2' = 1152x768 +
'34 2' = 1536x1024 +
'35 1' = 2400x1600 +
'36 0' = 3008x2008 or 3040x2024 +
'37 0' = 3008x2000
+
0x000bPictureModeint16u[n](1 or 2 values. Decimal values differentiate Optio 555 modes which are +different from other models) +
[Value 0]
+ +
0 = Program +
0.1 = Av +
1 = Shutter Speed Priority +
1.1 = M +
2 = Program AE +
2.1 = Tv +
3 = Manual +
3.1 = USER +
5 = Portrait +
6 = Landscape +
8 = Sport +
9 = Night Scene +
11 = Soft +
12 = Surf & Snow +
13 = Candlelight +
14 = Autumn +
15 = Macro +
17 = Fireworks +
18 = Text +
19 = Panorama +
20 = 3-D +
21 = Black & White +
22 = Sepia +
23 = Red +
24 = Pink +
25 = Purple +
26 = Blue +
27 = Green +
28 = Yellow +
30 = Self Portrait
  31 = Illustrations +
33 = Digital Filter +
35 = Night Scene Portrait +
37 = Museum +
38 = Food +
39 = Underwater +
40 = Green Mode +
49 = Light Pet +
50 = Dark Pet +
51 = Medium Pet +
53 = Underwater +
54 = Candlelight +
55 = Natural Skin Tone +
56 = Synchro Sound Record +
58 = Frame Composite +
59 = Report +
60 = Kids +
61 = Blur Reduction +
63 = Panorama 2 +
65 = Half-length Portrait +
66 = Portrait 2 +
74 = Digital Microscope +
75 = Blue Sky +
80 = Miniature +
81 = HDR +
83 = Fisheye +
85 = Digital Filter 4 +
221 = P +
255 = PICT
+
0x000cFlashModeint16u[n][Value 0] +
0x0 = Auto, Did not fire +
0x1 = Off, Did not fire +
0x2 = On, Did not fire +
0x3 = Auto, Did not fire, Red-eye reduction +
0x5 = On, Did not fire, Wireless (Master) +
0x100 = Auto, Fired +
0x102 = On, Fired +
0x103 = Auto, Fired, Red-eye reduction +
0x104 = On, Red-eye reduction +
0x105 = On, Wireless (Master) +
0x106 = On, Wireless (Control) +
0x108 = On, Soft +
0x109 = On, Slow-sync +
0x10a = On, Slow-sync, Red-eye reduction +
0x10b = On, Trailing-curtain Sync
+[Value 1] +
0x0 = n/a - Off-Auto-Aperture +
0x3f = Internal +
0x100 = External, Auto +
0x23f = External, Flash Problem +
0x300 = External, Manual +
0x304 = External, P-TTL Auto +
0x305 = External, Contrast-control Sync +
0x306 = External, High-speed Sync +
0x30c = External, Wireless +
0x30d = External, Wireless, High-speed Sync
+
0x000dFocusModeint16u(Pentax models) + +
0 = Normal +
1 = Macro +
2 = Infinity +
3 = Manual +
4 = Super Macro +
5 = Pan Focus +
16 = AF-S (Focus-priority) +
17 = AF-C (Focus-priority)
  18 = AF-A (Focus-priority) +
32 = Contrast-detect (Focus-priority) +
33 = Tracking Contrast-detect (Focus-priority) +
272 = AF-S (Release-priority) +
273 = AF-C (Release-priority) +
274 = AF-A (Release-priority) +
288 = Contrast-detect (Release-priority)
+(Asahi models) +
0 = Normal +
1 = Macro (1) +
2 = Macro (2) +
3 = Infinity
0x000eAFPointSelectedint16u(K-1) +
[Value 0]
+ +
0 = None +
1 = Top-left +
2 = Top Near-left +
3 = Top +
4 = Top Near-right +
5 = Top-right +
6 = Upper Far-left +
7 = Upper-left +
8 = Upper Near-left +
9 = Upper-middle +
10 = Upper Near-right +
11 = Upper-right +
12 = Upper Far-right +
13 = Far Far Left +
14 = Far Left +
15 = Left +
16 = Near-left +
17 = Center +
18 = Near-right +
19 = Right +
20 = Far Right +
21 = Far Far Right +
22 = Lower Far-left +
23 = Lower-left +
24 = Lower Near-left +
25 = Lower-middle +
26 = Lower Near-right +
27 = Lower-right
  28 = Lower Far-right +
29 = Bottom-left +
30 = Bottom Near-left +
31 = Bottom +
32 = Bottom Near-right +
33 = Bottom-right +
263 = Zone Select Upper-left +
264 = Zone Select Upper Near-left +
265 = Zone Select Upper Middle +
266 = Zone Select Upper Near-right +
267 = Zone Select Upper-right +
270 = Zone Select Far Left +
271 = Zone Select Left +
272 = Zone Select Near-left +
273 = Zone Select Center +
274 = Zone Select Near-right +
275 = Zone Select Right +
276 = Zone Select Far Right +
279 = Zone Select Lower-left +
280 = Zone Select Lower Near-left +
281 = Zone Select Lower-middle +
282 = Zone Select Lower Near-right +
283 = Zone Select Lower-right +
65531 = AF Select +
65532 = Face Detect AF +
65533 = Automatic Tracking AF +
65534 = Fixed Center +
65535 = Auto
+[Value 1] + +
0 = Single Point +
1 = Expanded Area 9-point (S)
  3 = Expanded Area 25-point (M) +
5 = Expanded Area 33-point (L)
+(K-3) +
[Value 0]
+ +
0 = None +
1 = Top-left +
2 = Top Near-left +
3 = Top +
4 = Top Near-right +
5 = Top-right +
6 = Upper-left +
7 = Upper Near-left +
8 = Upper-middle +
9 = Upper Near-right +
10 = Upper-right +
11 = Far Left +
12 = Left +
13 = Near-left +
14 = Center +
15 = Near-right +
16 = Right +
17 = Far Right +
18 = Lower-left +
19 = Lower Near-left +
20 = Lower-middle +
21 = Lower Near-right +
22 = Lower-right +
23 = Bottom-left +
24 = Bottom Near-left +
25 = Bottom +
26 = Bottom Near-right +
27 = Bottom-right +
257 = Zone Select Top-left +
258 = Zone Select Top Near-left
  259 = Zone Select Top +
260 = Zone Select Top Near-right +
261 = Zone Select Top-right +
262 = Zone Select Upper-left +
263 = Zone Select Upper Near-left +
264 = Zone Select Upper-middle +
265 = Zone Select Upper Near-right +
266 = Zone Select Upper-right +
267 = Zone Select Far Left +
268 = Zone Select Left +
269 = Zone Select Near-left +
270 = Zone Select Center +
271 = Zone Select Near-right +
272 = Zone Select Right +
273 = Zone Select Far Right +
274 = Zone Select Lower-left +
275 = Zone Select Lower Near-left +
276 = Zone Select Lower-middle +
277 = Zone Select Lower Near-right +
278 = Zone Select Lower-right +
279 = Zone Select Bottom-left +
280 = Zone Select Bottom Near-left +
281 = Zone Select Bottom +
282 = Zone Select Bottom Near-right +
283 = Zone Select Bottom-right +
65531 = AF Select +
65532 = Face Detect AF +
65533 = Automatic Tracking AF +
65534 = Fixed Center +
65535 = Auto
+[Value 1] + +
0 = Single Point +
1 = Expanded Area 9-point (S)
  3 = Expanded Area 25-point (M) +
5 = Expanded Area 27-point (L)
+(other models) +
[Value 0]
+ +
0 = None +
1 = Upper-left +
2 = Top +
3 = Upper-right +
4 = Left +
5 = Mid-left +
6 = Center +
7 = Mid-right +
8 = Right
  9 = Lower-left +
10 = Bottom +
11 = Lower-right +
65531 = AF Select +
65532 = Face Detect AF +
65533 = Automatic Tracking AF +
65534 = Fixed Center +
65535 = Auto
+
0x000fAFPointsInFocus +
AFPointsInFocus
int32u
int16u
(K-3 only) +
0x0 = (none) +
Bit 0 = Top-left +
Bit 1 = Top Near-left +
Bit 2 = Top +
Bit 3 = Top Near-right +
Bit 4 = Top-right +
Bit 5 = Upper-left +
Bit 6 = Upper Near-left +
Bit 7 = Upper-middle +
Bit 8 = Upper Near-right +
Bit 9 = Upper-right +
Bit 10 = Far Left +
Bit 11 = Left +
Bit 12 = Near-left +
Bit 13 = Center +
Bit 14 = Near-right +
Bit 15 = Right +
Bit 16 = Far Right +
Bit 17 = Lower-left +
Bit 18 = Lower Near-left +
Bit 19 = Lower-middle +
Bit 20 = Lower Near-right +
Bit 21 = Lower-right +
Bit 22 = Bottom-left +
Bit 23 = Bottom Near-left +
Bit 24 = Bottom +
Bit 25 = Bottom Near-right +
Bit 26 = Bottom-right
+(other models) +
0x0 = Fixed Center or Multiple +
0x1 = Top-left +
0x2 = Top-center +
0x3 = Top-right +
0x4 = Left +
0x5 = Center +
0x6 = Right +
0x7 = Bottom-left +
0x8 = Bottom-center +
0x9 = Bottom-right +
0xffff = None
+
0x0010FocusPositionint16u(related to focus distance but affected by focal length)
0x0012ExposureTimeint32u 
0x0013FNumberint16u 
0x0014ISOint16u(may be different than EXIF:ISO, which can round to the nearest full stop) + + + +
3 = 50 +
4 = 64 +
5 = 80 +
6 = 100 +
7 = 125 +
8 = 160 +
9 = 200 +
10 = 250 +
11 = 320 +
12 = 400 +
13 = 500 +
14 = 640 +
15 = 800 +
16 = 1000 +
17 = 1250 +
18 = 1600 +
19 = 2000 +
20 = 2500 +
21 = 3200 +
22 = 4000 +
23 = 5000
  24 = 6400 +
25 = 8000 +
26 = 10000 +
27 = 12800 +
28 = 16000 +
29 = 20000 +
30 = 25600 +
31 = 32000 +
32 = 40000 +
33 = 51200 +
34 = 64000 +
35 = 80000 +
36 = 102400 +
37 = 128000 +
38 = 160000 +
39 = 204800 +
40 = 256000 +
41 = 320000 +
42 = 409600 +
43 = 512000 +
44 = 640000
  45 = 819200 +
50 = 50 +
100 = 100 +
200 = 200 +
258 = 50 +
259 = 70 +
260 = 100 +
261 = 140 +
262 = 200 +
263 = 280 +
264 = 400 +
265 = 560 +
266 = 800 +
267 = 1100 +
268 = 1600 +
269 = 2200 +
270 = 3200 +
271 = 4500 +
272 = 6400 +
273 = 9000 +
274 = 12800
  275 = 18000 +
276 = 25600 +
277 = 36000 +
278 = 51200 +
279 = 72000 +
280 = 102400 +
281 = 144000 +
282 = 204800 +
283 = 288000 +
284 = 409600 +
285 = 576000 +
286 = 819200 +
400 = 400 +
800 = 800 +
1600 = 1600 +
3200 = 3200 +
65534 = Auto 2 +
65535 = Auto
+
0x0015LightReadingint16u(calibrated differently for different models. For the Optio WP, add 6 to get +approximate Light Value. May not be valid for some models, eg. Optio S)
0x0016ExposureCompensation +
ExposureCompensation
int16u
int16u[2]
(some models write two values here. The second value is meaning of the +second value is not yet known)
0x0017MeteringModeint16u0 = Multi-segment +
1 = Center-weighted average +
2 = Spot
0x0018AutoBracketingint16u[n](1 or 2 values: exposure bracket step in EV, then extended bracket if +available. Extended bracket values are printed as 'WB-BA', 'WB-GM', +'Saturation', 'Sharpness', 'Contrast', 'Hue' or 'HighLowKey' followed by +'+1', '+2' or '+3' for step size)
0x0019WhiteBalanceint16u + +
0 = Auto +
1 = Daylight +
2 = Shade +
3 = Fluorescent +
4 = Tungsten +
5 = Manual +
6 = Daylight Fluorescent +
7 = Day White Fluorescent +
8 = White Fluorescent
  9 = Flash +
10 = Cloudy +
11 = Warm White Fluorescent +
14 = Multi Auto +
15 = Color Temperature Enhancement +
17 = Kelvin +
65534 = Unknown +
65535 = User-Selected
+
0x001aWhiteBalanceModeint16u +
1 = Auto (Daylight) +
2 = Auto (Shade) +
3 = Auto (Flash) +
4 = Auto (Tungsten) +
6 = Auto (Daylight Fluorescent) +
7 = Auto (Day White Fluorescent) +
8 = Auto (White Fluorescent) +
10 = Auto (Cloudy) +
65534 = Unknown +
65535 = User-Selected
+
0x001bBlueBalanceint16u 
0x001cRedBalanceint16u 
0x001dFocalLengthint32u 
0x001eDigitalZoomint16u 
0x001fSaturationint16u[n](1 or 2 values) +
[Value 0]
+ +
0 = -2 (low) +
1 = 0 (normal) +
2 = +2 (high) +
3 = -1 (medium low) +
4 = +1 (medium high)
  5 = -3 (very low) +
6 = +3 (very high) +
7 = -4 (minimum) +
8 = +4 (maximum) +
65535 = None
+
0x0020Contrastint16u[n](1 or 2 values) +
[Value 0]
+ +
0 = -2 (low) +
1 = 0 (normal) +
2 = +2 (high) +
3 = -1 (medium low) +
4 = +1 (medium high)
  5 = -3 (very low) +
6 = +3 (very high) +
7 = -4 (minimum) +
8 = +4 (maximum) +
65535 = n/a
+
0x0021Sharpnessint16u[n](1 or 2 values) +
[Value 0]
+ +
0 = -2 (soft) +
1 = 0 (normal) +
2 = +2 (hard) +
3 = -1 (medium soft) +
4 = +1 (medium hard)
  5 = -3 (very soft) +
6 = +3 (very hard) +
7 = -4 (minimum) +
8 = +4 (maximum)
+
0x0022WorldTimeLocationint16u0 = Hometown +
1 = Destination
0x0023HometownCityint16u--> Pentax City Values
0x0024DestinationCityint16u--> Pentax City Values
0x0025HometownDSTint16u0 = No +
1 = Yes
0x0026DestinationDSTint16u0 = No +
1 = Yes
0x0027DSPFirmwareVersionundef 
0x0028CPUFirmwareVersionundef 
0x0029FrameNumberint32u 
0x002dEffectiveLV +
EffectiveLV
int16u
int32u
(camera-calculated light value, but includes exposure compensation)
0x0032ImageEditingundef[4] +
'0 0' = None +
'0 0 0 0' = None +
'0 0 0 4' = Digital Filter +
'1 0 0 0' = Resized +
'2 0 0 0' = Cropped +
'4 0 0 0' = Digital Filter 4 +
'6 0 0 0' = Digital Filter 6 +
'8 0 0 0' = Red-eye Correction +
'16 0 0 0' = Frame Synthesis?
+
0x0033PictureModeint8u[3][Values 0-1] + +
'0 0' = Program +
'0 1' = Hi-speed Program +
'0 2' = DOF Program +
'0 3' = MTF Program +
'0 4' = Standard +
'0 5' = Portrait +
'0 6' = Landscape +
'0 7' = Macro +
'0 8' = Sport +
'0 9' = Night Scene Portrait +
'0 10' = No Flash +
'0 11' = Night Scene +
'0 12' = Surf & Snow +
'0 13' = Text +
'0 14' = Sunset +
'0 15' = Kids +
'0 16' = Pet +
'0 17' = Candlelight +
'0 18' = Museum +
'0 19' = Food +
'0 20' = Stage Lighting +
'0 21' = Night Snap +
'0 23' = Blue Sky +
'0 24' = Sunset +
'0 26' = Night Scene HDR +
'0 27' = HDR +
'0 28' = Quick Macro +
'0 29' = Forest +
'0 30' = Backlight Silhouette +
'1 4' = Auto PICT (Standard) +
'1 5' = Auto PICT (Portrait) +
'1 6' = Auto PICT (Landscape) +
'1 7' = Auto PICT (Macro) +
'1 8' = Auto PICT (Sport)
  '2 0' = Program (HyP) +
'2 1' = Hi-speed Program (HyP) +
'2 2' = DOF Program (HyP) +
'2 3' = MTF Program (HyP) +
'2 22' = Shallow DOF (HyP) +
'3 0' = Green Mode +
'4 0' = Shutter Speed Priority +
'5 0' = Aperture Priority +
'6 0' = Program Tv Shift +
'7 0' = Program Av Shift +
'8 0' = Manual +
'9 0' = Bulb +
'10 0' = Aperture Priority, Off-Auto-Aperture +
'11 0' = Manual, Off-Auto-Aperture +
'12 0' = Bulb, Off-Auto-Aperture +
'13 0' = Shutter & Aperture Priority AE +
'15 0' = Sensitivity Priority AE +
'16 0' = Flash X-Sync Speed AE +
'18 0' = Auto Program (Normal) +
'18 1' = Auto Program (Hi-speed) +
'18 2' = Auto Program (DOF) +
'18 3' = Auto Program (MTF) +
'18 22' = Auto Program (Shallow DOF) +
'19 0' = Astrotracer +
'20 22' = Blur Control +
'249 0' = Movie (TAv) +
'250 0' = Movie (TAv, Auto Aperture) +
'251 0' = Movie (Manual) +
'252 0' = Movie (Manual, Auto Aperture) +
'253 0' = Movie (Av) +
'254 0' = Movie (Av, Auto Aperture) +
'255 0' = Movie (P, Auto Aperture) +
'255 4' = Video (4)
+[Value 2] + +
0 = 1/2 EV steps  1 = 1/3 EV steps
+
0x0034DriveModeint8u[4][Value 0] + +
0 = Single-frame +
1 = Continuous +
2 = Continuous (Lo)
  3 = Burst +
4 = Continuous (Medium) +
255 = Video
+[Value 1] + +
0 = No Timer +
1 = Self-timer (12 s) +
2 = Self-timer (2 s)
  15 = Video +
16 = Mirror Lock-up +
255 = n/a
+[Value 2] +
0 = Shutter Button +
1 = Remote Control (3 s delay) +
2 = Remote Control +
4 = Remote Continuous Shooting +
[Value 3]
+
0 = Single Exposure +
1 = Multiple Exposure +
2 = Composite Average +
3 = Composite Additive +
4 = Composite Bright +
8 = Interval Shooting +
10 = Interval Composite Average +
11 = Interval Composite Additive +
12 = Interval Composite Bright +
15 = Interval Movie +
16 = HDR +
32 = HDR Strong 1 +
48 = HDR Strong 2 +
64 = HDR Strong 3 +
80 = HDR Manual +
224 = HDR Auto +
255 = Video
+
0x0035SensorSizeint16u[2](includes masked pixels)
0x0037ColorSpaceint16u0 = sRGB +
1 = Adobe RGB
0x0038ImageAreaOffsetint16u[2] 
0x0039RawImageSizeint16u[2]~ 
0x003cAFPointsInFocusno(*istD only) + +
0x0 = (none) +
Bit 0 = Upper-left +
Bit 1 = Top +
Bit 2 = Upper-right +
Bit 3 = Left +
Bit 4 = Mid-left
  Bit 5 = Center +
Bit 6 = Mid-right +
Bit 7 = Right +
Bit 8 = Lower-left +
Bit 9 = Bottom +
Bit 10 = Lower-right
+
0x003dDataScalingint16u 
0x003ePreviewImageBordersint8u[4](top, bottom, left, right)
0x003fLensRec---> Pentax LensRec Tags
0x0040SensitivityAdjustint16u 
0x0041ImageEditCountint16u 
0x0047CameraTemperatureint8s 
0x0048AELockint16u0 = Off +
1 = On
0x0049NoiseReductionint16u0 = Off +
1 = On
0x004dFlashExposureComp +
FlashExposureComp
int32s
int8s[2]
 
0x004fImageToneint16u + +
0 = Natural +
1 = Bright +
2 = Portrait +
3 = Landscape +
4 = Vibrant +
5 = Monochrome
  6 = Muted +
7 = Reversal Film +
8 = Bleach Bypass +
9 = Radiant +
10 = Cross Processing +
11 = Flat
+
0x0050ColorTemperatureint16u 
0x0053ColorTempDaylightundef[4](0x0053-0x005a are 3 numbers: Kelvin, shift AB, shift GM)
0x0054ColorTempShadeundef[4] 
0x0055ColorTempCloudyundef[4] 
0x0056ColorTempTungstenundef[4] 
0x0057ColorTempFluorescentDundef[4] 
0x0058ColorTempFluorescentNundef[4] 
0x0059ColorTempFluorescentWundef[4] 
0x005aColorTempFlashundef[4] 
0x005cShakeReductionInfo---> Pentax SRInfo Tags +
--> Pentax SRInfo2 Tags
0x005dShutterCountundef[4](Note: May be reset by servicing! Also, does not include shutter actuations +for live view or video recording)
0x0060FaceInfo---> Pentax FaceInfo Tags
0x0062RawDevelopmentProcessint16u +
1 = 1 (K10D,K200D,K2000,K-m) +
3 = 3 (K20D) +
4 = 4 (K-7) +
5 = 5 (K-x) +
6 = 6 (645D) +
7 = 7 (K-r) +
8 = 8 (K-5,K-5II,K-5IIs) +
9 = 9 (Q) +
10 = 10 (K-01,K-30,K-50,K-500) +
11 = 11 (Q10) +
12 = 12 (MX-1,Q-S1,Q7) +
13 = 13 (K-3,K-3II) +
14 = 14 (645Z) +
15 = 15 (K-S1,K-S2) +
16 = 16 (K-1) +
17 = 17 (K-70) +
18 = 18 (KP) +
19 = 19 (GR III) +
20 = 20 (K-3III)
+
0x0067Hueint16u + +
0 = -2 +
1 = Normal +
2 = 2 +
3 = -1 +
4 = 1
  5 = -3 +
6 = 3 +
7 = -4 +
8 = 4 +
65535 = None
+
0x0068AWBInfo---> Pentax AWBInfo Tags
0x0069DynamicRangeExpansionundef[4](called highlight correction by Pentax for the K20D, K-5, K-01 and maybe +other models) +
[Value 0] +
0 = Off +
1 = On +
[Value 1] +
0 = 0 +
1 = Enabled +
2 = Auto
0x006bTimeInfo---> Pentax TimeInfo Tags
0x006cHighLowKeyAdjint16s[2] + + +
'-1 0' = -1 +
'-2 0' = -2 +
'-3 0' = -3
  '-4 0' = -4 +
'0 0' = 0 +
'1 0' = 1
  '2 0' = 2 +
'3 0' = 3 +
'4 0' = 4
+
0x006dContrastHighlightint16s[2] + + +
'-1 0' = -1 +
'-2 0' = -2 +
'-3 0' = -3
  '-4 0' = -4 +
'0 0' = 0 +
'1 0' = 1
  '2 0' = 2 +
'3 0' = 3 +
'4 0' = 4
+
0x006eContrastShadowint16s[2] + + +
'-1 0' = -1 +
'-2 0' = -2 +
'-3 0' = -3
  '-4 0' = -4 +
'0 0' = 0 +
'1 0' = 1
  '2 0' = 2 +
'3 0' = 3 +
'4 0' = 4
+
0x006fContrastHighlightShadowAdjint8u0 = Off +
1 = On
0x0070FineSharpnessint8u[n][Value 0] +
0 = Off +
1 = On +
[Value 1] +
0 = Normal +
2 = Extra fine
0x0071HighISONoiseReductionint8u[Value 0] + +
0 = Off +
1 = Weakest +
2 = Weak
  3 = Strong +
4 = Medium +
255 = Auto
+[Value 1] +
0 = Inactive +
1 = Active +
2 = Active (Weak) +
3 = Active (Strong) +
4 = Active (Medium) +
[Value 2] +
48 = ISO>400 +
56 = ISO>800 +
64 = ISO>1600 +
72 = ISO>3200
0x0072AFAdjustmentint16s 
0x0073MonochromeFilterEffectint16u + +
1 = Green +
2 = Yellow +
3 = Orange +
4 = Red +
5 = Magenta
  6 = Blue +
7 = Cyan +
8 = Infrared +
65535 = None
+
0x0074MonochromeToningint16u + +
0 = -4 +
1 = -3 +
2 = -2 +
3 = -1 +
4 = 0
  5 = 1 +
6 = 2 +
7 = 3 +
8 = 4 +
65535 = None
+
0x0076FaceDetectint8u[2] 
0x0077FaceDetectFrameSizeint16u[2] 
0x0079ShadowCorrectionint8u[n] + +
0 = Off +
1 = On +
2 = Auto 2 +
'0 0' = Off
  '1 1' = Weak +
'1 2' = Normal +
'1 3' = Strong +
'2 4' = Auto
+
0x007aISOAutoParametersint8u[2]'1 0' = Slow +
'2 0' = Standard +
'3 0' = Fast
0x007bCrossProcessint8u + +
0 = Off +
1 = Random +
2 = Preset 1 +
3 = Preset 2
  4 = Preset 3 +
33 = Favorite 1 +
34 = Favorite 2 +
35 = Favorite 3
+
0x007dLensCorr---> Pentax LensCorr Tags
0x007eWhiteLevelint32u 
0x007fBleachBypassToningint16u + +
0 = Off +
1 = Green +
2 = Yellow +
3 = Orange +
4 = Red
  5 = Magenta +
6 = Purple +
7 = Blue +
8 = Cyan +
65535 = n/a
+
0x0080AspectRatioyes0 = 4:3 +
1 = 3:2 +
2 = 16:9 +
3 = 1:1
0x0082BlurControlint8u[4][Value 0] +
0 = Off +
1 = Low +
2 = Medium +
3 = High
0x0085HDRint8u[4][Value 0] + +
0 = Off +
1 = HDR Auto +
2 = HDR 1
  3 = HDR 2 +
4 = HDR 3 +
5 = HDR Advanced
+[Value 1] +
0 = Auto-align Off +
1 = Auto-align On +
[Value 2] +
0 = n/a +
4 = 1 EV +
8 = 2 EV +
12 = 3 EV
0x0087ShutterTypeint8u0 = Normal +
1 = Electronic
0x0088NeutralDensityFilterint8u[n]0 = Off +
1 = On +
'0 2' = Off (0 2) +
'1 2' = On (1 2)
0x008bISOint32u 
0x0092IntervalShootingint16u[2](2 numbers: 1. Shot number 2. Total number of shots) +
'0 0' = Off
0x0095SkinToneCorrection +
SkinToneCorrection
int8s[2]
int8s[3]
'0 0' = Off +
'1 1' = On (type 1) +
'1 2' = On (type 2) +
'0 0 0' = Off
0x0096ClarityControlint8s[2]'0 0' = Off
0x0200BlackPointint16u[4] 
0x0201WhitePointint16u[4] 
0x0203ColorMatrixAint16s[9] 
0x0204ColorMatrixBint16s[9] 
0x0205CameraSettings +
CameraSettingsUnknown
-
-
--> Pentax CameraSettings Tags +
--> Pentax CameraSettingsUnknown Tags
0x0206AEInfo +
AEInfo2 +
AEInfo3 +
AEInfoUnknown
-
-
-
-
--> Pentax AEInfo Tags +
--> Pentax AEInfo2 Tags +
--> Pentax AEInfo3 Tags +
--> Pentax AEInfoUnknown Tags
0x0207LensInfo---> Pentax LensInfo Tags +
--> Pentax LensInfo2 Tags +
--> Pentax LensInfo3 Tags +
--> Pentax LensInfo4 Tags +
--> Pentax LensInfo5 Tags
0x0208FlashInfo +
FlashInfoUnknown
-
-
--> Pentax FlashInfo Tags +
--> Pentax FlashInfoUnknown Tags
0x0209AEMeteringSegmentsint8u[n](measurements from each of the 16 AE metering segments for models such as the +K10D, 77 metering segments for models such as the K-5, and 4050 metering +segments for the K-3, converted to LV)
0x020aFlashMeteringSegmentsint8u[n] 
0x020bSlaveFlashMeteringSegmentsint8u[n](used in wireless control mode)
0x020dWB_RGGBLevelsDaylightint16u[4] 
0x020eWB_RGGBLevelsShadeint16u[4] 
0x020fWB_RGGBLevelsCloudyint16u[4] 
0x0210WB_RGGBLevelsTungstenint16u[4] 
0x0211WB_RGGBLevelsFluorescentDint16u[4] 
0x0212WB_RGGBLevelsFluorescentNint16u[4] 
0x0213WB_RGGBLevelsFluorescentWint16u[4] 
0x0214WB_RGGBLevelsFlashint16u[4] 
0x0215CameraInfo---> Pentax CameraInfo Tags
0x0216BatteryInfo---> Pentax BatteryInfo Tags
0x021bSaturationInfo?no(only in PEF and DNG images)
0x021cColorMatrixA2undef[18] 
0x021dColorMatrixB2undef[18] 
0x021fAFInfo---> Pentax AFInfo Tags
0x0220HuffmanTable?no(found in K10D, K20D and K2000 PEF images)
0x0221KelvinWB---> Pentax KelvinWB Tags
0x0222ColorInfo---> Pentax ColorInfo Tags
0x0224EVStepInfo---> Pentax EVStepInfo Tags
0x0226ShotInfo---> Pentax ShotInfo Tags
0x0227FacePos---> Pentax FacePos Tags
0x0228FaceSize---> Pentax FaceSize Tags
0x0229SerialNumberstring(left blank by some cameras)
0x022aFilterInfo---> Pentax FilterInfo Tags +
--> Pentax FilterInfo Tags
0x022bLevelInfo---> Pentax LevelInfo Tags
0x022dWBLevels---> Pentax WBLevels Tags
0x022eArtiststring 
0x022fCopyrightstring 
0x0230FirmwareVersionstring(only in videos)
0x0231ContrastDetectAFAreaint16u[4](AF area of the most recent contrast-detect focus operation. Coordinates +are left, top, width and height in a 720x480 frame, with Y downwards)
0x0235CrossProcessParamsundef[10] 
0x0239LensInfoQ---> Pentax LensInfoQ Tags
0x023fModelstring 
0x0243PixelShiftInfo---> Pentax PixelShiftInfo Tags
0x0245AFPointInfo---> Pentax AFPointInfo Tags
0x03feDataDumpno 
0x03ffTempInfo +
UnknownInfo
-
-
--> Pentax TempInfo Tags +
--> Pentax UnknownInfo Tags
0x0402ToneCurveyes~ 
0x0403ToneCurvesyes~ 
0x0405UnknownBlock?undef(large unknown data block in PEF/DNG images but not JPG images)
0x0e00PrintIM---> PrintIM Tags
+ +

Pentax PentaxModelID Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValuePentaxModelIDValuePentaxModelIDValuePentaxModelID
0xd= Optio 330/4300x12c28= Optio S70x12e4e= Optio E90
0x12926= Optio 2300x12c2d= Optio L200x12e58= X90
0x12958= Optio 330GS0x12c32= Optio M200x12e6c= K-r
0x12962= Optio 450/5500x12c3c= Optio W200x12e76= K-5
0x1296c= Optio S0x12c46= Optio A200x12e8a= Optio RS1000/RS1500
0x12971= Optio S V1.010x12c78= Optio E300x12e94= Optio RZ10
0x12994= *ist D0x12c7d= Optio E350x12e9e= Optio LS1000
0x129b2= Optio 33L0x12c82= Optio T300x12ebc= Optio WG-1 GPS
0x129bc= Optio 33LF0x12c8c= Optio M300x12ed0= Optio S1
0x129c6= Optio 33WR/43WR/5550x12c91= Optio L300x12ee4= Q
0x129d5= Optio S40x12c96= Optio W300x12ef8= K-01
0x12a02= Optio MX0x12ca0= Optio A300x12f0c= Optio RZ18
0x12a0c= Optio S400x12cb4= Optio E400x12f16= Optio VS20
0x12a16= Optio S4i0x12cbe= Optio M400x12f2a= Optio WG-2 GPS
0x12a34= Optio 300x12cc3= Optio L400x12f48= Optio LS465
0x12a52= Optio S300x12cc5= Optio L360x12f52= K-30
0x12a66= Optio 750Z0x12cc8= Optio Z100x12f5c= X-5
0x12a70= Optio SV0x12cd2= K20D0x12f66= Q10
0x12a75= Optio SVi0x12cd4= Samsung GX200x12f70= K-5 II
0x12a7a= Optio X0x12cdc= Optio S100x12f71= K-5 II s
0x12a8e= Optio S5i0x12ce6= Optio A400x12f7a= Q7
0x12a98= Optio S500x12cf0= Optio V100x12f84= MX-1
0x12aa2= *ist DS0x12cfa= K200D0x12f8e= WG-3 GPS
0x12ab6= Optio MX40x12d04= Optio S120x12f98= WG-3
0x12ac0= Optio S5n0x12d0e= Optio E500x12fa2= WG-10
0x12aca= Optio WP0x12d18= Optio M500x12fb6= K-50
0x12afc= Optio S550x12d22= Optio L500x12fc0= K-3
0x12b10= Optio S5z0x12d2c= Optio V200x12fca= K-500
0x12b1a= *ist DL0x12d40= Optio W600x12fde= WG-4 GPS
0x12b24= Optio S600x12d4a= Optio M600x12fe8= WG-4
0x12b2e= Optio S450x12d68= Optio E60/M900x13006= WG-20
0x12b38= Optio S60x12d72= K20000x13010= 645Z
0x12b4c= Optio WPi0x12d73= K-m0x1301a= K-S1
0x12b56= BenQ DC X6000x12d86= Optio P700x13024= K-S2
0x12b60= *ist DS20x12d90= Optio L700x1302e= Q-S1
0x12b62= Samsung GX-1S0x12d9a= Optio E700x13056= WG-30
0x12b6a= Optio A100x12dae= X700x1307e= WG-30W
0x12b7e= *ist DL20x12db8= K-70x13088= WG-5 GPS
0x12b80= Samsung GX-1L0x12dcc= Optio W800x13092= K-1
0x12b9c= K100D0x12dea= Optio P800x1309c= K-3 II
0x12b9d= K110D0x12df4= Optio WS800x131f0= WG-M2
0x12ba2= K100D Super0x12dfe= K-x0x1320e= GR III
0x12bb0= Optio T10/T200x12e08= 645D0x13222= K-70
0x12be2= Optio W100x12e12= Optio E800x1322c= KP
0x12bf6= Optio M100x12e30= Optio W900x13240= K-1 Mark II
0x12c1e= K10D0x12e3a= Optio I-100x13254= K-3 Mark III
0x12c20= Samsung GX100x12e44= Optio H900x13290= WG-70
+ +

Pentax City Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueCityValueCityValueCityValueCityValueCity
0= Pago Pago15= Halifax30= Jeddah45= Phnom Penh60= Wellington
1= Honolulu16= Buenos Aires31= Tehran46= Ho Chi Minh61= Auckland
2= Anchorage17= Sao Paulo32= Dubai47= Jakarta62= Lima
3= Vancouver18= Rio de Janeiro33= Karachi48= Hong Kong63= Dakar
4= San Francisco19= Madrid34= Kabul49= Perth64= Algiers
5= Los Angeles20= London35= Male50= Beijing65= Helsinki
6= Calgary21= Paris36= Delhi51= Shanghai66= Athens
7= Denver22= Milan37= Colombo52= Manila67= Nairobi
8= Mexico City23= Rome38= Kathmandu53= Taipei68= Amsterdam
9= Chicago24= Berlin39= Dacca54= Seoul69= Stockholm
10= Miami25= Johannesburg40= Yangon55= Adelaide70= Lisbon
11= Toronto26= Istanbul41= Bangkok56= Tokyo71= Copenhagen
12= New York27= Cairo42= Kuala Lumpur57= Guam72= Warsaw
13= Santiago28= Jerusalem43= Vientiane58= Sydney73= Prague
14= Caracus29= Moscow44= Singapore59= Noumea74= Budapest
+ +

Pentax LensRec Tags

+

This record stores the LensType, plus one or two unknown bytes for some +models.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensTypeint8u[2]--> Pentax LensType Values
3ExtenderStatusint8u(not valid if a non-AF lens is used) +
0 = Not attached +
1 = Attached
+ +

Pentax LensType Values

+

The first number gives the series of the lens, and the second identifies the +lens model. Note that newer series numbers may not always be properly +identified by cameras running older firmware versions.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLensType
'0 0'= M-42 or No Lens
'1 0'= K or M Lens
'2 0'= A Series Lens
'3 0'= Sigma
'3 17'= smc PENTAX-FA SOFT 85mm F2.8
'3 18'= smc PENTAX-F 1.7X AF ADAPTER
'3 19'= smc PENTAX-F 24-50mm F4
'3 20'= smc PENTAX-F 35-80mm F4-5.6
'3 21'= smc PENTAX-F 80-200mm F4.7-5.6
'3 22'= smc PENTAX-F FISH-EYE 17-28mm F3.5-4.5
'3 23'= smc PENTAX-F 100-300mm F4.5-5.6 or Sigma Lens
'3 23'= Sigma AF 28-300mm F3.5-5.6 DL IF
'3 23'= Sigma AF 28-300mm F3.5-6.3 DG IF Macro
'3 23'= Tokina 80-200mm F2.8 ATX-Pro
'3 24'= smc PENTAX-F 35-135mm F3.5-4.5
'3 25'= smc PENTAX-F 35-105mm F4-5.6 or Sigma or Tokina Lens
'3 25'= Sigma 55-200mm F4-5.6 DC
'3 25'= Sigma AF 28-300mm F3.5-5.6 DL IF
'3 25'= Sigma AF 28-300mm F3.5-6.3 DL IF
'3 25'= Sigma AF 28-300mm F3.5-6.3 DG IF Macro
'3 25'= Tokina 80-200mm F2.8 ATX-Pro
'3 26'= smc PENTAX-F* 250-600mm F5.6 ED[IF]
'3 27'= smc PENTAX-F 28-80mm F3.5-4.5 or Tokina Lens
'3 27'= Tokina AT-X Pro AF 28-70mm F2.6-2.8
'3 28'= smc PENTAX-F 35-70mm F3.5-4.5 or Tokina Lens
'3 28'= Tokina 19-35mm F3.5-4.5 AF
'3 28'= Tokina AT-X AF 400mm F5.6
'3 29'= PENTAX-F 28-80mm F3.5-4.5 or Sigma or Tokina Lens
'3 29'= Sigma AF 18-125mm F3.5-5.6 DC
'3 29'= Tokina AT-X PRO 28-70mm F2.6-2.8
'3 30'= PENTAX-F 70-200mm F4-5.6
'3 31'= smc PENTAX-F 70-210mm F4-5.6 or Tokina or Takumar Lens
'3 31'= Tokina AF 730 75-300mm F4.5-5.6
'3 31'= Takumar-F 70-210mm F4-5.6
'3 32'= smc PENTAX-F 50mm F1.4
'3 33'= smc PENTAX-F 50mm F1.7
'3 34'= smc PENTAX-F 135mm F2.8 [IF]
'3 35'= smc PENTAX-F 28mm F2.8
'3 36'= Sigma 20mm F1.8 EX DG Aspherical RF
'3 38'= smc PENTAX-F* 300mm F4.5 ED[IF]
'3 39'= smc PENTAX-F* 600mm F4 ED[IF]
'3 40'= smc PENTAX-F Macro 100mm F2.8
'3 41'= smc PENTAX-F Macro 50mm F2.8 or Sigma Lens
'3 41'= Sigma 50mm F2.8 Macro
'3 42'= Sigma 300mm F2.8 EX DG APO IF
'3 44'= Sigma or Tamron Lens (3 44)
'3 44'= Sigma AF 10-20mm F4-5.6 EX DC
'3 44'= Sigma 12-24mm F4.5-5.6 EX DG
'3 44'= Sigma 17-70mm F2.8-4.5 DC Macro
'3 44'= Sigma 18-50mm F3.5-5.6 DC
'3 44'= Sigma 17-35mm F2.8-4 EX DG
'3 44'= Tamron 35-90mm F4-5.6 AF
'3 44'= Sigma AF 18-35mm F3.5-4.5 Aspherical
'3 46'= Sigma or Samsung Lens (3 46)
'3 46'= Sigma APO 70-200mm F2.8 EX
'3 46'= Sigma EX APO 100-300mm F4 IF
'3 46'= Samsung/Schneider D-XENON 50-200mm F4-5.6 ED
'3 50'= smc PENTAX-FA 28-70mm F4 AL
'3 51'= Sigma 28mm F1.8 EX DG Aspherical Macro
'3 52'= smc PENTAX-FA 28-200mm F3.8-5.6 AL[IF] or Tamron Lens
'3 52'= Tamron AF LD 28-200mm F3.8-5.6 [IF] Aspherical (171D)
'3 53'= smc PENTAX-FA 28-80mm F3.5-5.6 AL
'3 247'= smc PENTAX-DA FISH-EYE 10-17mm F3.5-4.5 ED[IF]
'3 248'= smc PENTAX-DA 12-24mm F4 ED AL[IF]
'3 250'= smc PENTAX-DA 50-200mm F4-5.6 ED
'3 251'= smc PENTAX-DA 40mm F2.8 Limited
'3 252'= smc PENTAX-DA 18-55mm F3.5-5.6 AL
'3 253'= smc PENTAX-DA 14mm F2.8 ED[IF]
'3 254'= smc PENTAX-DA 16-45mm F4 ED AL
'3 255'= Sigma Lens (3 255)
'3 255'= Sigma 18-200mm F3.5-6.3 DC
'3 255'= Sigma DL-II 35-80mm F4-5.6
'3 255'= Sigma DL Zoom 75-300mm F4-5.6
'3 255'= Sigma DF EX Aspherical 28-70mm F2.8
'3 255'= Sigma AF Tele 400mm F5.6 Multi-coated
'3 255'= Sigma 24-60mm F2.8 EX DG
'3 255'= Sigma 70-300mm F4-5.6 Macro
'3 255'= Sigma 55-200mm F4-5.6 DC
'3 255'= Sigma 18-50mm F2.8 EX DC
'4 1'= smc PENTAX-FA SOFT 28mm F2.8
'4 2'= smc PENTAX-FA 80-320mm F4.5-5.6
'4 3'= smc PENTAX-FA 43mm F1.9 Limited
'4 6'= smc PENTAX-FA 35-80mm F4-5.6
'4 7'= Irix 45mm F1.4
'4 8'= Irix 150mm F2.8 Macro
'4 9'= Irix 11mm F4 Firefly
'4 10'= Irix 15mm F2.4
'4 12'= smc PENTAX-FA 50mm F1.4
'4 15'= smc PENTAX-FA 28-105mm F4-5.6 [IF]
'4 16'= Tamron AF 80-210mm F4-5.6 (178D)
'4 19'= Tamron SP AF 90mm F2.8 (172E)
'4 20'= smc PENTAX-FA 28-80mm F3.5-5.6
'4 21'= Cosina AF 100-300mm F5.6-6.7
'4 22'= Tokina 28-80mm F3.5-5.6
'4 23'= smc PENTAX-FA 20-35mm F4 AL
'4 24'= smc PENTAX-FA 77mm F1.8 Limited
'4 25'= Tamron SP AF 14mm F2.8
'4 26'= smc PENTAX-FA Macro 100mm F3.5 or Cosina Lens
'4 26'= Cosina 100mm F3.5 Macro
'4 27'= Tamron AF 28-300mm F3.5-6.3 LD Aspherical[IF] Macro (185D/285D)
'4 28'= smc PENTAX-FA 35mm F2 AL
'4 29'= Tamron AF 28-200mm F3.8-5.6 LD Super II Macro (371D)
'4 34'= smc PENTAX-FA 24-90mm F3.5-4.5 AL[IF]
'4 35'= smc PENTAX-FA 100-300mm F4.7-5.8
'4 36'= Tamron AF 70-300mm F4-5.6 LD Macro 1:2
'4 37'= Tamron SP AF 24-135mm F3.5-5.6 AD AL (190D)
'4 38'= smc PENTAX-FA 28-105mm F3.2-4.5 AL[IF]
'4 39'= smc PENTAX-FA 31mm F1.8 AL Limited
'4 41'= Tamron AF 28-200mm Super Zoom F3.8-5.6 Aspherical XR [IF] Macro (A03)
'4 43'= smc PENTAX-FA 28-90mm F3.5-5.6
'4 44'= smc PENTAX-FA J 75-300mm F4.5-5.8 AL
'4 45'= Tamron Lens (4 45)
'4 45'= Tamron 28-300mm F3.5-6.3 Ultra zoom XR
'4 45'= Tamron AF 28-300mm F3.5-6.3 XR Di LD Aspherical [IF] Macro
'4 46'= smc PENTAX-FA J 28-80mm F3.5-5.6 AL
'4 47'= smc PENTAX-FA J 18-35mm F4-5.6 AL
'4 49'= Tamron SP AF 28-75mm F2.8 XR Di LD Aspherical [IF] Macro
'4 51'= smc PENTAX-D FA 50mm F2.8 Macro
'4 52'= smc PENTAX-D FA 100mm F2.8 Macro
'4 55'= Samsung/Schneider D-XENOGON 35mm F2
'4 56'= Samsung/Schneider D-XENON 100mm F2.8 Macro
'4 75'= Tamron SP AF 70-200mm F2.8 Di LD [IF] Macro (A001)
'4 214'= smc PENTAX-DA 35mm F2.4 AL
'4 229'= smc PENTAX-DA 18-55mm F3.5-5.6 AL II
'4 230'= Tamron SP AF 17-50mm F2.8 XR Di II
'4 231'= smc PENTAX-DA 18-250mm F3.5-6.3 ED AL [IF]
'4 237'= Samsung/Schneider D-XENOGON 10-17mm F3.5-4.5
'4 239'= Samsung/Schneider D-XENON 12-24mm F4 ED AL [IF]
'4 242'= smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM (SDM unused)
'4 243'= smc PENTAX-DA 70mm F2.4 Limited
'4 244'= smc PENTAX-DA 21mm F3.2 AL Limited
'4 245'= Samsung/Schneider D-XENON 50-200mm F4-5.6
'4 246'= Samsung/Schneider D-XENON 18-55mm F3.5-5.6
'4 247'= smc PENTAX-DA FISH-EYE 10-17mm F3.5-4.5 ED[IF]
'4 248'= smc PENTAX-DA 12-24mm F4 ED AL [IF]
'4 249'= Tamron XR DiII 18-200mm F3.5-6.3 (A14)
'4 250'= smc PENTAX-DA 50-200mm F4-5.6 ED
'4 251'= smc PENTAX-DA 40mm F2.8 Limited
'4 252'= smc PENTAX-DA 18-55mm F3.5-5.6 AL
'4 253'= smc PENTAX-DA 14mm F2.8 ED[IF]
'4 254'= smc PENTAX-DA 16-45mm F4 ED AL
'5 1'= smc PENTAX-FA* 24mm F2 AL[IF]
'5 2'= smc PENTAX-FA 28mm F2.8 AL
'5 3'= smc PENTAX-FA 50mm F1.7
'5 4'= smc PENTAX-FA 50mm F1.4
'5 5'= smc PENTAX-FA* 600mm F4 ED[IF]
'5 6'= smc PENTAX-FA* 300mm F4.5 ED[IF]
'5 7'= smc PENTAX-FA 135mm F2.8 [IF]
'5 8'= smc PENTAX-FA Macro 50mm F2.8
'5 9'= smc PENTAX-FA Macro 100mm F2.8
'5 10'= smc PENTAX-FA* 85mm F1.4 [IF]
'5 11'= smc PENTAX-FA* 200mm F2.8 ED[IF]
'5 12'= smc PENTAX-FA 28-80mm F3.5-4.7
'5 13'= smc PENTAX-FA 70-200mm F4-5.6
'5 14'= smc PENTAX-FA* 250-600mm F5.6 ED[IF]
'5 15'= smc PENTAX-FA 28-105mm F4-5.6
'5 16'= smc PENTAX-FA 100-300mm F4.5-5.6
'5 98'= smc PENTAX-FA 100-300mm F4.5-5.6
'6 1'= smc PENTAX-FA* 85mm F1.4 [IF]
'6 2'= smc PENTAX-FA* 200mm F2.8 ED[IF]
'6 3'= smc PENTAX-FA* 300mm F2.8 ED[IF]
'6 4'= smc PENTAX-FA* 28-70mm F2.8 AL
'6 5'= smc PENTAX-FA* 80-200mm F2.8 ED[IF]
'6 6'= smc PENTAX-FA* 28-70mm F2.8 AL
'6 7'= smc PENTAX-FA* 80-200mm F2.8 ED[IF]
'6 8'= smc PENTAX-FA 28-70mm F4AL
'6 9'= smc PENTAX-FA 20mm F2.8
'6 10'= smc PENTAX-FA* 400mm F5.6 ED[IF]
'6 13'= smc PENTAX-FA* 400mm F5.6 ED[IF]
'6 14'= smc PENTAX-FA* Macro 200mm F4 ED[IF]
'7 0'= smc PENTAX-DA 21mm F3.2 AL Limited
'7 58'= smc PENTAX-D FA Macro 100mm F2.8 WR
'7 75'= Tamron SP AF 70-200mm F2.8 Di LD [IF] Macro (A001)
'7 201'= smc Pentax-DA L 50-200mm F4-5.6 ED WR
'7 202'= smc PENTAX-DA L 18-55mm F3.5-5.6 AL WR
'7 203'= HD PENTAX-DA 55-300mm F4-5.8 ED WR
'7 204'= HD PENTAX-DA 15mm F4 ED AL Limited
'7 205'= HD PENTAX-DA 35mm F2.8 Macro Limited
'7 206'= HD PENTAX-DA 70mm F2.4 Limited
'7 207'= HD PENTAX-DA 21mm F3.2 ED AL Limited
'7 208'= HD PENTAX-DA 40mm F2.8 Limited
'7 212'= smc PENTAX-DA 50mm F1.8
'7 213'= smc PENTAX-DA 40mm F2.8 XS
'7 214'= smc PENTAX-DA 35mm F2.4 AL
'7 216'= smc PENTAX-DA L 55-300mm F4-5.8 ED
'7 217'= smc PENTAX-DA 50-200mm F4-5.6 ED WR
'7 218'= smc PENTAX-DA 18-55mm F3.5-5.6 AL WR
'7 220'= Tamron SP AF 10-24mm F3.5-4.5 Di II LD Aspherical [IF]
'7 221'= smc PENTAX-DA L 50-200mm F4-5.6 ED
'7 222'= smc PENTAX-DA L 18-55mm F3.5-5.6
'7 223'= Samsung/Schneider D-XENON 18-55mm F3.5-5.6 II
'7 224'= smc PENTAX-DA 15mm F4 ED AL Limited
'7 225'= Samsung/Schneider D-XENON 18-250mm F3.5-6.3
'7 226'= smc PENTAX-DA* 55mm F1.4 SDM (SDM unused)
'7 227'= smc PENTAX-DA* 60-250mm F4 [IF] SDM (SDM unused)
'7 228'= Samsung 16-45mm F4 ED
'7 229'= smc PENTAX-DA 18-55mm F3.5-5.6 AL II
'7 230'= Tamron AF 17-50mm F2.8 XR Di-II LD (Model A16)
'7 231'= smc PENTAX-DA 18-250mm F3.5-6.3 ED AL [IF]
'7 233'= smc PENTAX-DA 35mm F2.8 Macro Limited
'7 234'= smc PENTAX-DA* 300mm F4 ED [IF] SDM (SDM unused)
'7 235'= smc PENTAX-DA* 200mm F2.8 ED [IF] SDM (SDM unused)
'7 236'= smc PENTAX-DA 55-300mm F4-5.8 ED
'7 238'= Tamron AF 18-250mm F3.5-6.3 Di II LD Aspherical [IF] Macro
'7 241'= smc PENTAX-DA* 50-135mm F2.8 ED [IF] SDM (SDM unused)
'7 242'= smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM (SDM unused)
'7 243'= smc PENTAX-DA 70mm F2.4 Limited
'7 244'= smc PENTAX-DA 21mm F3.2 AL Limited
'8 0'= Sigma 50-150mm F2.8 II APO EX DC HSM
'8 3'= Sigma 18-125mm F3.8-5.6 DC HSM
'8 4'= Sigma 50mm F1.4 EX DG HSM
'8 6'= Sigma 4.5mm F2.8 EX DC Fisheye
'8 7'= Sigma 24-70mm F2.8 IF EX DG HSM
'8 8'= Sigma 18-250mm F3.5-6.3 DC OS HSM
'8 11'= Sigma 10-20mm F3.5 EX DC HSM
'8 12'= Sigma 70-300mm F4-5.6 DG OS
'8 13'= Sigma 120-400mm F4.5-5.6 APO DG OS HSM
'8 14'= Sigma 17-70mm F2.8-4.0 DC Macro OS HSM
'8 15'= Sigma 150-500mm F5-6.3 APO DG OS HSM
'8 16'= Sigma 70-200mm F2.8 EX DG Macro HSM II
'8 17'= Sigma 50-500mm F4.5-6.3 DG OS HSM
'8 18'= Sigma 8-16mm F4.5-5.6 DC HSM
'8 20'= Sigma 18-50mm F2.8-4.5 DC HSM
'8 21'= Sigma 17-50mm F2.8 EX DC OS HSM
'8 22'= Sigma 85mm F1.4 EX DG HSM
'8 23'= Sigma 70-200mm F2.8 APO EX DG OS HSM
'8 24'= Sigma 17-70mm F2.8-4 DC Macro OS HSM
'8 25'= Sigma 17-50mm F2.8 EX DC HSM
'8 27'= Sigma 18-200mm F3.5-6.3 II DC HSM
'8 28'= Sigma 18-250mm F3.5-6.3 DC Macro HSM
'8 29'= Sigma 35mm F1.4 DG HSM
'8 30'= Sigma 17-70mm F2.8-4 DC Macro HSM | C
'8 31'= Sigma 18-35mm F1.8 DC HSM
'8 32'= Sigma 30mm F1.4 DC HSM | A
'8 33'= Sigma 18-200mm F3.5-6.3 DC Macro HSM
'8 34'= Sigma 18-300mm F3.5-6.3 DC Macro HSM
'8 59'= HD PENTAX-D FA 150-450mm F4.5-5.6 ED DC AW
'8 60'= HD PENTAX-D FA* 70-200mm F2.8 ED DC AW
'8 61'= HD PENTAX-D FA 28-105mm F3.5-5.6 ED DC WR
'8 62'= HD PENTAX-D FA 24-70mm F2.8 ED SDM WR
'8 63'= HD PENTAX-D FA 15-30mm F2.8 ED SDM WR
'8 64'= HD PENTAX-D FA* 50mm F1.4 SDM AW
'8 65'= HD PENTAX-D FA 70-210mm F4 ED SDM WR
'8 66'= HD PENTAX-D FA 85mm F1.4 ED SDM AW
'8 67'= HD PENTAX-D FA 21mm F2.4 ED Limited DC WR
'8 195'= HD PENTAX DA* 16-50mm F2.8 ED PLM AW
'8 196'= HD PENTAX-DA* 11-18mm F2.8 ED DC AW
'8 197'= HD PENTAX-DA 55-300mm F4.5-6.3 ED PLM WR RE
'8 198'= smc PENTAX-DA L 18-50mm F4-5.6 DC WR RE
'8 199'= HD PENTAX-DA 18-50mm F4-5.6 DC WR RE
'8 200'= HD PENTAX-DA 16-85mm F3.5-5.6 ED DC WR
'8 209'= HD PENTAX-DA 20-40mm F2.8-4 ED Limited DC WR
'8 210'= smc PENTAX-DA 18-270mm F3.5-6.3 ED SDM
'8 211'= HD PENTAX-DA 560mm F5.6 ED AW
'8 215'= smc PENTAX-DA 18-135mm F3.5-5.6 ED AL [IF] DC WR
'8 226'= smc PENTAX-DA* 55mm F1.4 SDM
'8 227'= smc PENTAX-DA* 60-250mm F4 [IF] SDM
'8 232'= smc PENTAX-DA 17-70mm F4 AL [IF] SDM
'8 234'= smc PENTAX-DA* 300mm F4 ED [IF] SDM
'8 235'= smc PENTAX-DA* 200mm F2.8 ED [IF] SDM
'8 241'= smc PENTAX-DA* 50-135mm F2.8 ED [IF] SDM
'8 242'= smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM
'8 255'= Sigma Lens (8 255)
'8 255'= Sigma 70-200mm F2.8 EX DG Macro HSM II
'8 255'= Sigma 150-500mm F5-6.3 DG APO [OS] HSM
'8 255'= Sigma 50-150mm F2.8 II APO EX DC HSM
'8 255'= Sigma 4.5mm F2.8 EX DC HSM Circular Fisheye
'8 255'= Sigma 50-200mm F4-5.6 DC OS
'8 255'= Sigma 24-70mm F2.8 EX DG HSM
'9 0'= 645 Manual Lens
'9 3'= HD PENTAX-FA 43mm F1.9 Limited
'9 24'= HD PENTAX-FA 77mm F1.8 Limited
'9 39'= HD PENTAX-FA 31mm F1.8 AL Limited
'9 247'= HD PENTAX-DA FISH-EYE 10-17mm F3.5-4.5 ED [IF]
'10 0'= 645 A Series Lens
'11 1'= smc PENTAX-FA 645 75mm F2.8
'11 2'= smc PENTAX-FA 645 45mm F2.8
'11 3'= smc PENTAX-FA* 645 300mm F4 ED [IF]
'11 4'= smc PENTAX-FA 645 45-85mm F4.5
'11 5'= smc PENTAX-FA 645 400mm F5.6 ED [IF]
'11 7'= smc PENTAX-FA 645 Macro 120mm F4
'11 8'= smc PENTAX-FA 645 80-160mm F4.5
'11 9'= smc PENTAX-FA 645 200mm F4 [IF]
'11 10'= smc PENTAX-FA 645 150mm F2.8 [IF]
'11 11'= smc PENTAX-FA 645 35mm F3.5 AL [IF]
'11 12'= smc PENTAX-FA 645 300mm F5.6 ED [IF]
'11 14'= smc PENTAX-FA 645 55-110mm F5.6
'11 16'= smc PENTAX-FA 645 33-55mm F4.5 AL
'11 17'= smc PENTAX-FA 645 150-300mm F5.6 ED [IF]
'11 21'= HD PENTAX-D FA 645 35mm F3.5 AL [IF]
'13 18'= smc PENTAX-D FA 645 55mm F2.8 AL [IF] SDM AW
'13 19'= smc PENTAX-D FA 645 25mm F4 AL [IF] SDM AW
'13 20'= HD PENTAX-D FA 645 90mm F2.8 ED AW SR
'13 253'= HD PENTAX-DA 645 28-45mm F4.5 ED AW SR
'13 254'= smc PENTAX-DA 645 25mm F4 AL [IF] SDM AW
'21 0'= Pentax Q Manual Lens
'21 1'= 01 Standard Prime 8.5mm F1.9
'21 2'= 02 Standard Zoom 5-15mm F2.8-4.5
'21 6'= 06 Telephoto Zoom 15-45mm F2.8
'21 7'= 07 Mount Shield 11.5mm F9
'21 8'= 08 Wide Zoom 3.8-5.9mm F3.7-4
'21 233'= Adapter Q for K-mount Lens
'22 3'= 03 Fish-eye 3.2mm F5.6
'22 4'= 04 Toy Lens Wide 6.3mm F7.1
'22 5'= 05 Toy Lens Telephoto 18mm F8
'31 1'= GR Lens
+ +

Pentax SRInfo Tags

+

Shake reduction information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SRResultint8u0x0 = Not stabilized +
Bit 0 = Stabilized +
Bit 6 = Not ready
1ShakeReductionint8u + +
0 = Off +
1 = On +
4 = Off (4) +
5 = On but Disabled +
6 = On (Video)
  7 = On (7) +
15 = On (15) +
39 = On (mode 2) +
135 = On (135) +
167 = On (mode 1)
+
2SRHalfPressTimeint8u(time from when the shutter button was half pressed to when the shutter was +released, including time for focusing. Not valid for some models)
3SRFocalLengthint8u 
+ +

Pentax SRInfo2 Tags

+

Shake reduction information for the K-3.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SRResult?int8u 
1ShakeReductionint8u +
0 = Off +
1 = On +
4 = Off (AA simulation off) +
5 = On but Disabled +
6 = On (Video) +
7 = On (AA simulation off) +
8 = Off (AA simulation type 1) (8) +
12 = Off (AA simulation type 1) +
15 = On (AA simulation type 1) +
16 = Off (AA simulation type 2) (16) +
20 = Off (AA simulation type 2) +
23 = On (AA simulation type 2)
+
+ +

Pentax FaceInfo Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FacesDetectedint8u 
2FacePositionint8u[2](X/Y coordinates of the center of the main face in percent of frame size, +with positive Y downwards)
+ +

Pentax AWBInfo Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0WhiteBalanceAutoAdjustmentint8u0 = Off +
1 = On
1TungstenAWBint8u0 = Subtle Correction +
1 = Strong Correction
+ +

Pentax TimeInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1WorldTimeLocationint8u[val & 0x1] +
0 = Hometown +
1 = Destination
0.2HometownDSTint8u[val >> 1 & 0x1] +
0 = No +
1 = Yes
0.3DestinationDSTint8u[val >> 2 & 0x1] +
0 = No +
1 = Yes
2HometownCityint8u--> Pentax City Values
3DestinationCityint8u--> Pentax City Values
+ +

Pentax LensCorr Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0DistortionCorrectionint8u0 = Off +
1 = On
1ChromaticAberrationCorrectionint8u0 = Off +
1 = On
2PeripheralIlluminationCorrint8u0 = Off +
1 = On
3DiffractionCorrectionint8u0 = Off +
16 = On
+ +

Pentax CameraSettings Tags

+

Camera settings information written by Pentax DSLR cameras.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PictureMode2int8u +
0 = Scene Mode +
1 = Auto PICT +
2 = Program AE +
3 = Green Mode +
4 = Shutter Speed Priority +
5 = Aperture Priority +
6 = Program Tv Shift +
7 = Program Av Shift +
8 = Manual +
9 = Bulb +
10 = Aperture Priority, Off-Auto-Aperture +
11 = Manual, Off-Auto-Aperture +
12 = Bulb, Off-Auto-Aperture +
13 = Shutter & Aperture Priority AE +
15 = Sensitivity Priority AE +
16 = Flash X-Sync Speed AE
+
1.1ProgramLineint8u[val & 0x3] +
0 = Normal +
1 = Hi Speed +
2 = Depth +
3 = MTF
1.2EVStepsint8u[val >> 5 & 0x1] +
0 = 1/2 EV Steps +
1 = 1/3 EV Steps
1.3E-DialInProgramint8u[val >> 6 & 0x1] +
0 = Tv or Av +
1 = P Shift
1.4ApertureRingUseint8u[val >> 7 & 0x1] +
0 = Prohibited +
1 = Permitted
2FlashOptionsint8u(the camera flash options settings, set even if the flash is off) +
[val >> 4 & 0xf]
+
0 = Normal +
1 = Red-eye reduction +
2 = Auto +
3 = Auto, Red-eye reduction +
5 = Wireless (Master) +
6 = Wireless (Control) +
8 = Slow-sync +
9 = Slow-sync, Red-eye reduction +
10 = Trailing-curtain Sync
+
2.1MeteringMode2int8u(may not be valid for some models, eg. *ist D) +
[val & 0xf] +
0x0 = Multi-segment +
Bit 0 = Center-weighted average +
Bit 1 = Spot
3AFPointModeint8u[val >> 4 & 0xf] +
0x0 = Auto +
Bit 0 = Select +
Bit 1 = Fixed Center
3.1FocusMode2int8u[val & 0xf] +
0 = Manual +
1 = AF-S +
2 = AF-C +
3 = AF-A
4AFPointSelected2int16u + +
0x0 = Auto +
Bit 0 = Upper-left +
Bit 1 = Top +
Bit 2 = Upper-right +
Bit 3 = Left +
Bit 4 = Mid-left
  Bit 5 = Center +
Bit 6 = Mid-right +
Bit 7 = Right +
Bit 8 = Lower-left +
Bit 9 = Bottom +
Bit 10 = Lower-right
+
6ISOFloorint8u 
7DriveMode2int8u +
0x0 = Single-frame +
Bit 0 = Continuous +
Bit 1 = Continuous (Lo) +
Bit 2 = Self-timer (12 s) +
Bit 3 = Self-timer (2 s) +
Bit 4 = Remote Control (3 s delay) +
Bit 5 = Remote Control +
Bit 6 = Exposure Bracket +
Bit 7 = Multiple Exposure
+
8ExposureBracketStepSizeint8u + +
3 = 0.3 +
4 = 0.5 +
5 = 0.7 +
8 = 1.0
  11 = 1.3 +
12 = 1.5 +
13 = 1.7 +
16 = 2.0
+
9BracketShotNumberint8u + + +
0x0 = n/a +
0x2 = 1 of 2 +
0x3 = 1 of 3 +
0x5 = 1 of 5
  0x12 = 2 of 2 +
0x13 = 2 of 3 +
0x15 = 2 of 5 +
0x23 = 3 of 3
  0x25 = 3 of 5 +
0x35 = 4 of 5 +
0x45 = 5 of 5
+
10WhiteBalanceSetint8u[val >> 4 & 0xf] +
0 = Auto +
1 = Daylight +
2 = Shade +
3 = Cloudy +
4 = Daylight Fluorescent +
5 = Day White Fluorescent +
6 = White Fluorescent +
7 = Tungsten +
8 = Flash +
9 = Manual +
12 = Set Color Temperature 1 +
13 = Set Color Temperature 2 +
14 = Set Color Temperature 3
+
10.1MultipleExposureSetint8u[val & 0xf] +
0 = Off +
1 = On
13RawAndJpgRecordingint8u(K10D only) +
0x1 = JPEG (Best) +
0x4 = RAW (PEF, Best) +
0x5 = RAW+JPEG (PEF, Best) +
0x8 = RAW (DNG, Best) +
0x9 = RAW+JPEG (DNG, Best) +
0x21 = JPEG (Better) +
0x24 = RAW (PEF, Better) +
0x25 = RAW+JPEG (PEF, Better) +
0x28 = RAW (DNG, Better) +
0x29 = RAW+JPEG (DNG, Better) +
0x41 = JPEG (Good) +
0x44 = RAW (PEF, Good) +
0x45 = RAW+JPEG (PEF, Good) +
0x48 = RAW (DNG, Good) +
0x49 = RAW+JPEG (DNG, Good)
+
14.1JpgRecordedPixelsint8u(K10D only) +
[val & 0x3] +
0 = 10 MP +
1 = 6 MP +
2 = 2 MP
14.2LinkAEToAFPointint8u(K-5 only) +
[val & 0x1] +
0 = Off +
1 = On
14.3SensitivityStepsint8u(K-5 only) +
[val >> 1 & 0x1] +
0 = 1 EV Steps +
1 = As EV Steps
14.4ISOAutoint8u(K-5 only) +
[val >> 2 & 0x1] +
0 = Off +
1 = On
16FlashOptions2int8u(K10D only; set even if the flash is off) +
[val >> 4 & 0xf]
+
0 = Normal +
1 = Red-eye reduction +
2 = Auto +
3 = Auto, Red-eye reduction +
5 = Wireless (Master) +
6 = Wireless (Control) +
8 = Slow-sync +
9 = Slow-sync, Red-eye reduction +
10 = Trailing-curtain Sync
+
16.1MeteringMode3int8u(K10D only) +
[val & 0xf] +
0x0 = Multi-segment +
Bit 0 = Center-weighted average +
Bit 1 = Spot
17.1SRActiveint8u(K10D only; SR is active only when ShakeReduction is On, DriveMode is not +Remote or Self-timer, and Internal/ExternalFlashMode is not "On, Wireless") +
[val >> 7 & 0x1] +
0 = No +
1 = Yes
17.2Rotationint8u(K10D only) +
[val >> 5 & 0x3] +
0 = Horizontal (normal) +
1 = Rotate 180 +
2 = Rotate 90 CW +
3 = Rotate 270 CW
17.3ISOSettingint8u(K10D only) +
[val >> 2 & 0x1] +
0 = Manual +
1 = Auto
17.4SensitivityStepsint8u(K10D only) +
[val >> 1 & 0x1] +
0 = 1 EV Steps +
1 = As EV Steps
18TvExposureTimeSettingint8u(K10D only)
19AvApertureSettingint8u(K10D only)
20SvISOSettingint8u(K10D only)
21BaseExposureCompensationint8u(K10D only; exposure compensation without auto bracketing)
+ +

Pentax CameraSettingsUnknown Tags

+

This information has not yet been decoded for models such as the K-01.

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +

Pentax AEInfo Tags

+

Auto-exposure information for most Pentax models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AEExposureTimeint8u(val = 24 * 2**((32-raw)/8))
1AEApertureint8u(val = 2**((raw-68)/16))
2AE_ISOint8u(val = 100 * 2**((raw-32)/8))
3AEXvint8u(val = (raw-64)/8)
4AEBXvint8s(val = raw / 8)
5AEMinExposureTimeint8u(val = 24 * 2**((32-raw)/8))
6AEProgramModeint8u + +
0 = M, P or TAv +
1 = Av, B or X +
2 = Tv +
3 = Sv or Green Mode +
8 = Hi-speed Program +
11 = Hi-speed Program (P-Shift) +
16 = DOF Program +
19 = DOF Program (P-Shift) +
24 = MTF Program +
27 = MTF Program (P-Shift) +
35 = Standard +
43 = Portrait +
51 = Landscape +
59 = Macro +
67 = Sport
  75 = Night Scene Portrait +
83 = No Flash +
91 = Night Scene +
99 = Surf & Snow +
104 = Night Snap +
107 = Text +
115 = Sunset +
123 = Kids +
131 = Pet +
139 = Candlelight +
144 = SCN +
147 = Museum +
160 = Program +
184 = Shallow DOF Program +
216 = HDR
+
7AEFlagsno(indices after this are incremented by 1 for some models) +
Bit 3 = AE lock +
Bit 4 = Flash recommended? +
Bit 7 = Aperture wide open
8AEApertureStepsint8u(number of steps the aperture has been stopped down from wide open. There +are roughly 8 steps per F-stop for most lenses, or 18 steps for 645D lenses, +but it varies slightly by lens)
9AEMaxApertureint8u(val = 2**((raw-68)/16))
10AEMaxAperture2int8u(val = 2**((raw-68)/16))
11AEMinApertureint8u(val = 2**((raw-68)/16))
12AEMeteringModeint8u0x0 = Multi-segment +
Bit 4 = Center-weighted average +
Bit 5 = Spot
13AEWhiteBalanceint8u(K7 and Kx) +
[val >> 4 & 0xf]
+
0 = Standard +
1 = Daylight +
2 = Shade +
3 = Cloudy +
4 = Daylight Fluorescent +
5 = Day White Fluorescent +
6 = White Fluorescent +
7 = Tungsten +
8 = Unknown
+
13.1AEMeteringMode2int8u(K7 and Kx, override for an incompatible metering mode setting) +
[val & 0xf] +
0x0 = Multi-segment +
Bit 0 = Center-weighted average +
Bit 1 = Spot
14FlashExposureCompSetint8s(reports the camera setting, unlike tag 0x004d which reports 0 in Green mode +or if flash was on but did not fire. Both this tag and 0x004d report the +setting even if the flash is off)
21LevelIndicatorint8u 
+ +

Pentax AEInfo2 Tags

+

Auto-exposure information for the K-01.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
2AEExposureTimeint8u(val = 24 * 2**((32-raw)/8))
3AEApertureint8u(val = 2**((raw-68)/16))
4AE_ISOint8u(val = 100 * 2**((raw-32)/8))
5AEXvint8u(val = (raw-64)/8)
6AEBXvint8s(val = raw / 8)
8AEErrorint8s 
11AEApertureStepsint8u(number of steps the aperture has been stopped down from wide open. There +are roughly 8 steps per F-stop, but it varies slightly by lens)
15SceneModeint8u + +
0 = Off +
1 = HDR +
4 = Auto PICT +
5 = Portrait +
6 = Landscape +
7 = Macro +
8 = Sport +
9 = Night Scene Portrait +
10 = No Flash +
11 = Night Scene +
12 = Surf & Snow +
14 = Sunset
  15 = Kids +
16 = Pet +
17 = Candlelight +
18 = Museum +
20 = Food +
21 = Stage Lighting +
22 = Night Snap +
25 = Night Scene HDR +
26 = Blue Sky +
27 = Forest +
29 = Backlight Silhouette
+
16AEMaxApertureint8u(val = 2**((raw-68)/16))
17AEMaxAperture2int8u(val = 2**((raw-68)/16))
18AEMinApertureint8u(val = 2**((raw-68)/16))
19AEMinExposureTimeint8u(val = 24 * 2**((32-raw)/8))
+ +

Pentax AEInfo3 Tags

+

Auto-exposure information for the K-3, K-30, K-50 and K-500.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
16AEExposureTimeint8u(val = 24 * 2**((32-raw)/8))
17AEApertureint8u(val = 2**((raw-68)/16))
18AE_ISOint8u(val = 100 * 2**((raw-32)/8))
28AEMaxApertureint8u(val = 2**((raw-68)/16))
29AEMaxAperture2int8u(val = 2**((raw-68)/16))
30AEMinApertureint8u(val = 2**((raw-68)/16))
31AEMinExposureTimeint8u(val = 24 * 2**((32-raw)/8))
+ +

Pentax AEInfoUnknown Tags

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +

Pentax LensInfo Tags

+

Pentax lens information structure for models such as the *istD.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensTypeint8u[2]--> Pentax LensType Values
3LensData---> Pentax LensData Tags
+ +

Pentax LensInfo2 Tags

+

Pentax lens information structure for models such as the K10D and K20D.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensTypeint8u[4]--> Pentax LensType Values
4LensData---> Pentax LensData Tags
+ +

Pentax LensData Tags

+

Pentax lens data information. Some of these tags require interesting binary +gymnastics to decode them into useful values.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1AutoApertureint8u(not valid for the K-r, K-5 or K-5II) +
[val & 0x1] +
0 = On +
1 = Off
0.2MinApertureint8u(not valid for the K-r, K-5 or K-5II) +
[val >> 1 & 0x3] +
0 = 22 +
1 = 32 +
2 = 45 +
3 = 16
0.3LensFStopsint8u(not valid for the K-r, K-5 or K-5II) +
[val >> 4 & 0x7]
1LensKind?int8u 
2LC1?int8u 
3MinFocusDistanceint8u(minimum focus distance for the lens) +
[val >> 3 & 0x1f]
+ +
0 = 0.13-0.19 m +
1 = 0.20-0.24 m +
2 = 0.25-0.28 m +
3 = 0.28-0.30 m +
4 = 0.35-0.38 m +
5 = 0.40-0.45 m +
6 = 0.49-0.50 m +
7 = 0.6 m +
8 = 0.7 m +
9 = 0.8-0.9 m +
10 = 1.0 m
  11 = 1.1-1.2 m +
12 = 1.4-1.5 m +
13 = 1.5 m +
14 = 2.0 m +
15 = 2.0-2.1 m +
16 = 2.1 m +
17 = 2.2-2.9 m +
18 = 3.0 m +
19 = 4-5 m +
20 = 5.6 m
+
3.1FocusRangeIndexint8u[val & 0x7] + +
0 = 5 +
1 = 4 +
2 = 6 (far) +
3 = 7 (very far)
  4 = 2 +
5 = 3 +
6 = 1 (close) +
7 = 0 (very close)
+
4LC3?int8u 
5LC4?int8u 
6LC5?int8u 
7LC6?int8u 
8LC7?int8u 
9LensFocalLength +
LC8?
int8u
int8u
(focal length of lens alone, without adapter)
10NominalMaxApertureint8u[val >> 4 & 0xf]
10.1NominalMinApertureint8u[val & 0xf]
11LC10?int8u 
12LC11?int8u 
13LC12?int8u(ID's 13-16 are offset by 1 for the K-r, K-5 and K-5II)
14.1MaxApertureint8u(effective wide open aperture for current focal length) +
[val & 0x7f]
15LC14?int8u 
16LC15?int8u 
+ +

Pentax LensInfo3 Tags

+

Pentax lens information structure for 645D.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1LensTypeint8u[4]--> Pentax LensType Values
13LensData---> Pentax LensData Tags
+ +

Pentax LensInfo4 Tags

+

Pentax lens information structure for models such as the K-5 and K-r.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1LensTypeint8u[4]--> Pentax LensType Values
12LensData---> Pentax LensData Tags
+ +

Pentax LensInfo5 Tags

+

Pentax lens information structure for the K-01 and newer models.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1LensTypeint8u[5]--> Pentax LensType Values
15LensData---> Pentax LensData Tags
+ +

Pentax FlashInfo Tags

+

Flash information tags for the K10D, K20D and K200D.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FlashStatusint8u +
0x0 = Off +
0x1 = Off (1) +
0x2 = External, Did not fire +
0x6 = External, Fired +
0x8 = Internal, Did not fire (0x08) +
0x9 = Internal, Did not fire +
0xd = Internal, Fired
+
1InternalFlashModeint8u0x0 = n/a - Off-Auto-Aperture +
0x86 = Fired, Wireless (Control) +
0x95 = Fired, Wireless (Master) +
0xc0 = Fired +
0xc1 = Fired, Red-eye reduction +
0xc2 = Fired, Auto +
0xc3 = Fired, Auto, Red-eye reduction +
0xc6 = Fired, Wireless (Control), Fired normally not as control +
0xc8 = Fired, Slow-sync +
0xc9 = Fired, Slow-sync, Red-eye reduction +
0xca = Fired, Trailing-curtain Sync +
0xf0 = Did not fire, Normal +
0xf1 = Did not fire, Red-eye reduction +
0xf2 = Did not fire, Auto +
0xf3 = Did not fire, Auto, Red-eye reduction +
0xf4 = Did not fire, (Unknown 0xf4) +
0xf5 = Did not fire, Wireless (Master) +
0xf6 = Did not fire, Wireless (Control) +
0xf8 = Did not fire, Slow-sync +
0xf9 = Did not fire, Slow-sync, Red-eye reduction +
0xfa = Did not fire, Trailing-curtain Sync
2ExternalFlashModeint8u +
0x0 = n/a - Off-Auto-Aperture +
0x3f = Off +
0x40 = On, Auto +
0xbf = On, Flash Problem +
0xc0 = On, Manual +
0xc4 = On, P-TTL Auto +
0xc5 = On, Contrast-control Sync +
0xc6 = On, High-speed Sync +
0xcc = On, Wireless +
0xcd = On, Wireless, High-speed Sync +
0xf0 = Not Connected
+
3InternalFlashStrengthint8u(saved from the most recent flash picture, on a scale of about 0 to 100)
4TTL_DA_AUpint8u 
5TTL_DA_ADownint8u 
6TTL_DA_BUpint8u 
7TTL_DA_BDownint8u 
24.1ExternalFlashGuideNumberint8u(val = 2**(raw/16 + 4), with a few exceptions) +
[val & 0x1f]
25ExternalFlashExposureCompint8u + +
0 = n/a +
144 = n/a (Manual Mode) +
164 = -3.0 +
167 = -2.5 +
168 = -2.0 +
171 = -1.5
  172 = -1.0 +
175 = -0.5 +
176 = 0.0 +
179 = 0.5 +
180 = 1.0
+
26ExternalFlashBounceint8u(saved from the most recent external flash picture) +
0 = n/a +
16 = Direct +
48 = Bounce
+ +

Pentax FlashInfoUnknown Tags

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +

Pentax CameraInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0PentaxModelIDint32u--> Pentax PentaxModelID Values
1ManufactureDateint32u(this value, and the values of the tags below, may change if the camera is +serviced)
2ProductionCodeint32u[2] 
4InternalSerialNumberint32u 
+ +

Pentax BatteryInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0.1PowerSourceint8u[val & 0xf] +
1 = Camera Battery +
2 = Body Battery +
3 = Grip Battery +
4 = External Power Supply
1.1BodyBatteryStateint8u(*istD, K100D, K200D, K10D and K20D) +
[val >> 4 & 0xf] +
1 = Empty or Missing +
2 = Almost Empty +
3 = Running Low +
4 = Full +
(other models except the K110D, K2000 and K-m) +
[val >> 4 & 0xf] +
1 = Empty or Missing +
2 = Almost Empty +
3 = Running Low +
4 = Close to Full +
5 = Full +
(decoding unknown for other models) +
[val >> 4 & 0xf]
1.2GripBatteryState +
GripBatteryState?
int8u
int8u
(K10D and K20D) +
[val & 0xf] +
1 = Empty or Missing +
2 = Almost Empty +
3 = Running Low +
4 = Full +
(decoding unknown for other models) +
[val & 0xf]
2BodyBatteryADNoLoad +
BodyBatteryVoltage1
int8u
int16u
(roughly calibrated for K10D with a new Pentax battery)
3BodyBatteryADLoadint8u(roughly calibrated for K10D with a new Pentax battery)
4GripBatteryADNoLoad +
BodyBatteryVoltage2
int8u
int16u
 
5GripBatteryADLoadint8u 
6BodyBatteryVoltage3int16u(K-5, K-r and 645D only)
8BodyBatteryVoltage4int16u(K-5 and K-r only)
+ +

Pentax AFInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AFPointsUnknown1?int16u + +
0x0 = (none) +
0x777 = Central 9 points +
0x7ff = All +
Bit 0 = Upper-left +
Bit 1 = Top +
Bit 2 = Upper-right +
Bit 3 = Left
  Bit 4 = Mid-left +
Bit 5 = Center +
Bit 6 = Mid-right +
Bit 7 = Right +
Bit 8 = Lower-left +
Bit 9 = Bottom +
Bit 10 = Lower-right
+
2AFPointsUnknown2?int16u + +
0x0 = Auto +
Bit 0 = Upper-left +
Bit 1 = Top +
Bit 2 = Upper-right +
Bit 3 = Left +
Bit 4 = Mid-left
  Bit 5 = Center +
Bit 6 = Mid-right +
Bit 7 = Right +
Bit 8 = Lower-left +
Bit 9 = Bottom +
Bit 10 = Lower-right
+
4AFPredictorint16s 
6AFDefocusint8u 
7AFIntegrationTimeint8u(times less than 2 ms give a value of 0)
11AFPointsInFocusint8u(models other than the K-1 and K-3. May report two points in focus even +though a single AFPoint has been selected, in which case the selected +AFPoint is the first reported) + +
0 = None +
1 = Lower-left, Bottom +
2 = Bottom +
3 = Lower-right, Bottom +
4 = Mid-left, Center +
5 = Center (horizontal) +
6 = Mid-right, Center +
7 = Upper-left, Top +
8 = Top +
9 = Upper-right, Top +
10 = Right
  11 = Lower-left, Mid-left +
12 = Upper-left, Mid-left +
13 = Bottom, Center +
14 = Top, Center +
15 = Lower-right, Mid-right +
16 = Upper-right, Mid-right +
17 = Left +
18 = Mid-left +
19 = Center (vertical) +
20 = Mid-right
+
509AFHoldint8u(decoded only for the K-3 II) +
0 = Off +
1 = Short +
2 = Medium +
3 = Long
+ +

Pentax KelvinWB Tags

+

White balance Blue/Red gains as a function of color temperature.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
1KelvinWB_Daylightint16u[4] 
5KelvinWB_01int16u[4] 
9KelvinWB_02int16u[4] 
13KelvinWB_03int16u[4] 
17KelvinWB_04int16u[4] 
21KelvinWB_05int16u[4] 
25KelvinWB_06int16u[4] 
29KelvinWB_07int16u[4] 
33KelvinWB_08int16u[4] 
37KelvinWB_09int16u[4] 
41KelvinWB_10int16u[4] 
45KelvinWB_11int16u[4] 
49KelvinWB_12int16u[4] 
53KelvinWB_13int16u[4] 
57KelvinWB_14int16u[4] 
61KelvinWB_15int16u[4] 
65KelvinWB_16int16u[4] 
+ +

Pentax ColorInfo Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
16WBShiftABint8s(positive is a shift toward blue)
17WBShiftGMint8s(positive is a shift toward green)
+ +

Pentax EVStepInfo Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0EVStepsint8u0 = 1/2 EV Steps +
1 = 1/3 EV Steps
1SensitivityStepsint8u0 = 1 EV Steps +
1 = As EV Steps
+ +

Pentax ShotInfo Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
1CameraOrientationint8u(K-5, K-7, K-r and K-x) +
0x10 = Horizontal (normal) +
0x20 = Rotate 180 +
0x30 = Rotate 90 CW +
0x40 = Rotate 270 CW +
0x50 = Upwards +
0x60 = Downwards
+
+ +

Pentax FacePos Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0Face1Positionint16u[2](X/Y coordinates of face center in full-sized image)
2Face2Positionint16u[2] 
4Face3Positionint16u[2] 
6Face4Positionint16u[2] 
8Face5Positionint16u[2] 
10Face6Positionint16u[2] 
12Face7Positionint16u[2] 
14Face8Positionint16u[2] 
16Face9Positionint16u[2] 
18Face10Positionint16u[2] 
20Face11Positionint16u[2] 
22Face12Positionint16u[2] 
24Face13Positionint16u[2] 
26Face14Positionint16u[2] 
28Face15Positionint16u[2] 
30Face16Positionint16u[2] 
32Face17Positionint16u[2] 
34Face18Positionint16u[2] 
36Face19Positionint16u[2] 
38Face20Positionint16u[2] 
40Face21Positionint16u[2] 
42Face22Positionint16u[2] 
44Face23Positionint16u[2] 
46Face24Positionint16u[2] 
48Face25Positionint16u[2] 
50Face26Positionint16u[2] 
52Face27Positionint16u[2] 
54Face28Positionint16u[2] 
56Face29Positionint16u[2] 
58Face30Positionint16u[2] 
60Face31Positionint16u[2] 
62Face32Positionint16u[2] 
+ +

Pentax FaceSize Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0Face1Sizeint16u[2] 
2Face2Sizeint16u[2] 
4Face3Sizeint16u[2] 
6Face4Sizeint16u[2] 
8Face5Sizeint16u[2] 
10Face6Sizeint16u[2] 
12Face7Sizeint16u[2] 
14Face8Sizeint16u[2] 
16Face9Sizeint16u[2] 
18Face10Sizeint16u[2] 
20Face11Sizeint16u[2] 
22Face12Sizeint16u[2] 
24Face13Sizeint16u[2] 
26Face14Sizeint16u[2] 
28Face15Sizeint16u[2] 
30Face16Sizeint16u[2] 
32Face17Sizeint16u[2] 
34Face18Sizeint16u[2] 
36Face19Sizeint16u[2] 
38Face20Sizeint16u[2] 
40Face21Sizeint16u[2] 
42Face22Sizeint16u[2] 
44Face23Sizeint16u[2] 
46Face24Sizeint16u[2] 
48Face25Sizeint16u[2] 
50Face26Sizeint16u[2] 
52Face27Sizeint16u[2] 
54Face28Sizeint16u[2] 
56Face29Sizeint16u[2] 
58Face30Sizeint16u[2] 
60Face31Sizeint16u[2] 
62Face32Sizeint16u[2] 
+ +

Pentax FilterInfo Tags

+

The parameters associated with each type of digital filter are unique, and +these settings are also extracted with the DigitalFilter tag. Information +is not extracted for filters that are "Off" unless the Unknown option is +used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SourceDirectoryIndexint16u 
2SourceFileIndexint16u 
5DigitalFilter01undef[17]--> Pentax DigitalFilter Values
22DigitalFilter02undef[17]--> Pentax DigitalFilter Values
39DigitalFilter03undef[17]--> Pentax DigitalFilter Values
56DigitalFilter04undef[17]--> Pentax DigitalFilter Values
73DigitalFilter05undef[17]--> Pentax DigitalFilter Values
90DigitalFilter06undef[17]--> Pentax DigitalFilter Values
107DigitalFilter07undef[17]--> Pentax DigitalFilter Values
124DigitalFilter08undef[17]--> Pentax DigitalFilter Values
141DigitalFilter09undef[17]--> Pentax DigitalFilter Values
158DigitalFilter10undef[17]--> Pentax DigitalFilter Values
175DigitalFilter11undef[17]--> Pentax DigitalFilter Values
192DigitalFilter12undef[17]--> Pentax DigitalFilter Values
209DigitalFilter13undef[17]--> Pentax DigitalFilter Values
226DigitalFilter14undef[17]--> Pentax DigitalFilter Values
243DigitalFilter15undef[17]--> Pentax DigitalFilter Values
260DigitalFilter16undef[17]--> Pentax DigitalFilter Values
277DigitalFilter17undef[17]--> Pentax DigitalFilter Values
294DigitalFilter18undef[17]--> Pentax DigitalFilter Values
311DigitalFilter19undef[17]--> Pentax DigitalFilter Values
328DigitalFilter20undef[17]--> Pentax DigitalFilter Values
+ +

Pentax DigitalFilter Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueDigitalFilterValueDigitalFilterValueDigitalFilter
0= Off10= Toy Camera20= Shading
1= Base Parameter Adjust11= Retro21= Invert Color
2= Soft Focus12= Pastel23= Tone Expansion
3= High Contrast13= Water Color27= Unicolor Bold
4= Color Filter14= HDR28= Bold Monochrome
5= Extract Color16= Miniature29= Replace Color
6= Monochrome17= Starburst254= Custom Filter
7= Slim18= Posterization  
9= Fisheye19= Sketch Filter  
+ +

Pentax LevelInfo Tags

+

Tags decoded from the electronic level information for the K-5. May not be +valid for other models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LevelOrientationint8s[val & 0xf] +
0 = n/a +
1 = Horizontal (normal) +
2 = Rotate 180 +
3 = Rotate 90 CW +
4 = Rotate 270 CW +
9 = Horizontal; Off Level +
10 = Rotate 180; Off Level +
11 = Rotate 90 CW; Off Level +
12 = Rotate 270 CW; Off Level +
13 = Upwards +
14 = Downwards
+
0.1CompositionAdjustint8s[val >> 4 & 0xf] +
0 = Off +
2 = Composition Adjust +
10 = Composition Adjust + Horizon Correction +
12 = Horizon Correction
1RollAngleint8s(converted to degrees of clockwise camera rotation)
2PitchAngleint8s(converted to degrees of upward camera tilt)
5CompositionAdjustXint8s(steps to the right, 1/16 mm per step)
6CompositionAdjustYint8s(steps up, 1/16 mm per step)
7CompositionAdjustRotationint8s(steps clockwise, 1/8 degree per step)
+ +

Pentax WBLevels Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
2WB_RGGBLevelsDaylightint16u[4] 
11WB_RGGBLevelsShadeint16u[4] 
20WB_RGGBLevelsCloudyint16u[4] 
29WB_RGGBLevelsTungstenint16u[4] 
38WB_RGGBLevelsFluorescentDint16u[4] 
47WB_RGGBLevelsFluorescentNint16u[4] 
56WB_RGGBLevelsFluorescentWint16u[4] 
65WB_RGGBLevelsFlashint16u[4] 
74WB_RGGBLevelsFluorescentLint16u[4] 
83WB_RGGBLevelsUnknown?int16u[4] 
92WB_RGGBLevelsUserSelectedint16u[4] 
+ +

Pentax LensInfoQ Tags

+

More lens information stored by the Pentax Q.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12LensModelstring[30] 
42LensInfostring[20] 
+ +

Pentax PixelShiftInfo Tags

+

Pixel shift information stored by the K-3 II.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0PixelShiftResolutionint8u0 = Off +
1 = On
+ +

Pentax AFPointInfo Tags

+

AF point information written by the K-1.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
2NumAFPointsint16u 
4AFPointsInFocusint8u[9]~ 
4.1AFPointsSelectedint8u[9]~ 
4.2AFPointsSpecialint8u[9]~ 
+ +

Pentax TempInfo Tags

+

A number of additional temperature readings are extracted from this 256-byte +binary-data block in images from models such as the K-01, K-3, K-5, K-50 and +K-500. It is currently not known where the corresponding temperature +sensors are located in the camera.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12SensorTemperatureint16s 
14SensorTemperature2int16s 
20CameraTemperature4int16s 
22CameraTemperature5int16s 
+ +

Pentax UnknownInfo Tags

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +

Pentax Type2 Tags

+

These tags are used by the Pentax Optio 330 and 430, and are similar to the +tags used by Casio.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001RecordingModeint16u0 = Auto +
1 = Night Scene +
2 = Manual
0x0002Qualityint16u0 = Good +
1 = Better +
2 = Best
0x0003FocusModeint16u2 = Custom +
3 = Auto
0x0004FlashModeint16u1 = Auto +
2 = On +
4 = Off +
6 = Red-eye reduction
0x0007WhiteBalanceint16u + +
0 = Auto +
1 = Daylight +
2 = Shade
  3 = Tungsten +
4 = Fluorescent +
5 = Manual
+
0x000aDigitalZoomint32u 
0x000bSharpnessint16u0 = Normal +
1 = Soft +
2 = Hard
0x000cContrastint16u0 = Normal +
1 = Low +
2 = High
0x000dSaturationint16u0 = Normal +
1 = Low +
2 = High
0x0014ISOint16u + + +
10 = 100 +
16 = 200 +
50 = 50 +
100 = 100
  200 = 200 +
400 = 400 +
800 = 800 +
1600 = 1600
  3200 = 3200 +
65534 = Auto 2 +
65535 = Auto
+
0x0017ColorFilterint16u1 = Full +
2 = Black & White +
3 = Sepia
0x0e00PrintIM---> PrintIM Tags
0x1000HometownCityCodeundef[4] 
0x1001DestinationCityCodeundef[4] 
+ +

Pentax Type4 Tags

+

The following few tags are extracted from the wealth of information +available in maker notes of the Optio E20 and E25. These maker notes are +stored as ASCII text in a format very similar to some HP models.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'F/W Version'FirmwareVersionno 
+ +

Pentax S1 Tags

+

Tags extracted from the maker notes of AVI videos from the Optio S1.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000MakerNoteVersionno 
+ +

Pentax Junk Tags

+

Tags found in the JUNK chunk of AVI videos from the RS1000.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
12Modelno 
+ +

Pentax Junk2 Tags

+

This information is found in AVI videos from the Optio RZ18.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
18Makeno 
44Modelno 
94FNumberno 
131DateTime1no 
157DateTime2no 
299ThumbnailWidthno 
301ThumbnailHeightno 
303ThumbnailLengthno 
307ThumbnailImageno(160x120 JPEG thumbnail image)
+ +

Pentax AVI Tags

+

Pentax-specific RIFF tags found in AVI videos.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'hymn'MakerNotes---> Pentax Tags
'mknt'MakerNotes---> Pentax Tags
+ +

Pentax PENT Tags

+

Tags found in the PENT atom of MOV videos from the Optio WG-2 GPS.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
26Modelno 
56ExposureTimeno 
60FNumberno 
68ExposureCompensationno 
84FocalLengthno 
113DateTime1no 
139DateTime2no 
167ISOno 
199GPSVersionIDno 
207GPSLatitudeRefno'N' = North +
'S' = South
209GPSLatitudeno 
233GPSLongitudeRefno'E' = East +
'W' = West
235GPSLongitudeno 
259GPSAltitudeRefno0 = Above Sea Level +
1 = Below Sea Level
260GPSAltitudeno 
284GPSTimeStampno 
308GPSSatellitesno 
311GPSStatusno'A' = Measurement Active +
'V' = Measurement Void
313GPSMeasureModeno2 = 2-Dimensional Measurement +
3 = 3-Dimensional Measurement
315GPSMapDatumno 
322GPSDateStampno 
371AudioCodecIDno 
2003PreviewImageno(640x480 JPEG preview image)
+ +

Pentax PXTH Tags

+

Tags found in the PXTH atom of MOV videos from the K-01.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PreviewImageLengthno 
4PreviewImageno(640-pixel-wide JPEG preview)
+ +

Pentax MOV Tags

+

This information is found in MOV videos from cameras such as the Optio WP.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
38ExposureTimeno 
42FNumberno 
50ExposureCompensationno 
68WhiteBalanceno + +
0 = Auto +
1 = Daylight +
2 = Shade
  3 = Fluorescent +
4 = Tungsten +
5 = Manual
+
72FocalLengthno 
175ISOno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 19, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PhaseOne.html b/ExifTool/html/TagNames/PhaseOne.html new file mode 100644 index 0000000..4903d0f --- /dev/null +++ b/ExifTool/html/TagNames/PhaseOne.html @@ -0,0 +1,267 @@ + + + + +PhaseOne Tags + + + +

PhaseOne Tags

+

These tags are extracted from the maker notes of Phase One images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0100CameraOrientationno0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW +
3 = Rotate 180
0x0102SerialNumberstring 
0x0105ISOint32s 
0x0106ColorMatrix1float[9] 
0x0107WB_RGBLevelsfloat[3] 
0x0108SensorWidthint32s 
0x0109SensorHeightint32s 
0x010aSensorLeftMarginint32s 
0x010bSensorTopMarginint32s 
0x010cImageWidthint32s 
0x010dImageHeightint32s 
0x010eRawFormatint32s + +
1 = RAW 1 +
2 = RAW 2 +
3 = IIQ L
  5 = IIQ S +
6 = IIQ Sv2 +
8 = IIQ L16
+
0x010fRawDatano 
0x0110SensorCalibration---> PhaseOne SensorCalibration Tags
0x0112DateTimeOriginalno(may be used as a key to encrypt the raw data)
0x0113ImageNumberint32s 
0x0203Softwarestring 
0x0204Systemstring 
0x0210SensorTemperaturefloat 
0x0211SensorTemperature2float 
0x0212UnknownDate?int32u 
0x021cStripOffsetsno 
0x021dBlackLevelint32s 
0x0222SplitColumnint32s 
0x0223BlackLevelDataint16u[n] 
0x0226ColorMatrix2float[9] 
0x0267AFAdjustmentfloat 
0x0301FirmwareVersionsstring 
0x0400ShutterSpeedValuefloat 
0x0401ApertureValuefloat 
0x0402ExposureCompensationfloat 
0x0403FocalLengthfloat 
0x0410CameraModelstring 
0x0412LensModelstring 
0x0414MaxApertureValuefloat 
0x0415MinApertureValuefloat 
0x0455Viewfinderstring 
+ +

PhaseOne SensorCalibration Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0400SensorDefectsno 
0x0401AllColorFlatField1?no 
0x0407SerialNumberstring 
0x040bRedBlueFlatField?no 
0x0410AllColorFlatField2?no 
0x0416AllColorFlatField3?no 
0x0419LinearizationCoefficients1no 
0x041aLinearizationCoefficients2no 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Apr 13, 2021 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PhotoCD.html b/ExifTool/html/TagNames/PhotoCD.html new file mode 100644 index 0000000..af06903 --- /dev/null +++ b/ExifTool/html/TagNames/PhotoCD.html @@ -0,0 +1,403 @@ + + + + +PhotoCD Tags + + + +

PhotoCD Tags

+

Tags extracted from Kodak Photo CD Image Pac (PCD) files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
7SpecificationVersionno 
9AuthoringSoftwareReleaseno 
11ImageMagnificationDescriptorno 
13CreateDateno 
17ModifyDateno 
21ImageMediumno +
0 = Color negative +
1 = Color reversal +
2 = Color hard copy +
3 = Thermal hard copy +
4 = Black and white negative +
5 = Black and white reversal +
6 = Black and white hard copy +
7 = Internegative +
8 = Synthetic image
+
22ProductTypeno 
42ScannerVendorIDno 
62ScannerProductIDno 
78ScannerFirmwareVersionno 
82ScannerFirmwareDateno 
90ScannerSerialNumberno 
110ScannerPixelSizeno 
112ImageWorkstationMakeno 
132CharacterSetno +
1 = 38 characters ISO 646 +
2 = 65 characters ISO 646 +
3 = 95 characters ISO 646 +
4 = 191 characters ISO 8850-1 +
5 = ISO 2022 +
6 = Includes characters not ISO 2375 registered
+
133CharacterEscapeSequence?no 
165PhotoFinisherNameno 
228SceneBalanceAlgorithmRevisionno 
230SceneBalanceAlgorithmCommandno0 = Neutral SBA On, Color SBA On +
1 = Neutral SBA Off, Color SBA Off +
2 = Neutral SBA On, Color SBA Off +
3 = Neutral SBA Off, Color SBA On
325SceneBalanceAlgorithmFilmIDno +
1 = 3M ScotchColor AT 100 +
2 = 3M ScotchColor AT 200 +
3 = 3M ScotchColor HR2 400 +
7 = 3M Scotch HR 200 Gen 2 +
9 = 3M Scotch HR 400 Gen 2 +
16 = Agfa Agfacolor XRS 400 Gen 1 +
17 = Agfa Agfacolor XRG/XRS 400 +
18 = Agfa Agfacolor XRG/XRS 200 +
19 = Agfa Agfacolor XRS 1000 Gen 2 +
20 = Agfa Agfacolor XRS 400 Gen 2 +
21 = Agfa Agfacolor XRS/XRC 100 +
26 = Fuji Reala 100 (JAPAN) +
27 = Fuji Reala 100 Gen 1 +
28 = Fuji Reala 100 Gen 2 +
29 = Fuji SHR 400 Gen 2 +
30 = Fuji Super HG 100 +
31 = Fuji Super HG 1600 Gen 1 +
32 = Fuji Super HG 200 +
33 = Fuji Super HG 400 +
34 = Fuji Super HG 100 Gen 2 +
35 = Fuji Super HR 100 Gen 1 +
36 = Fuji Super HR 100 Gen 2 +
37 = Fuji Super HR 1600 Gen 2 +
38 = Fuji Super HR 200 Gen 1 +
39 = Fuji Super HR 200 Gen 2 +
40 = Fuji Super HR 400 Gen 1 +
43 = Fuji NSP 160S (Pro) +
45 = Kodak Kodacolor VR 100 Gen 2 +
47 = Kodak Gold 400 Gen 3 +
55 = Kodak Ektar 100 Gen 1 +
56 = Kodak Ektar 1000 Gen 1 +
57 = Kodak Ektar 125 Gen 1 +
58 = Kodak Royal Gold 25 RZ +
60 = Kodak Gold 1600 Gen 1 +
61 = Kodak Gold 200 Gen 2 +
62 = Kodak Gold 400 Gen 2 +
65 = Kodak Kodacolor VR 100 Gen 1 +
66 = Kodak Kodacolor VR 1000 Gen 2 +
67 = Kodak Kodacolor VR 1000 Gen 1 +
68 = Kodak Kodacolor VR 200 Gen 1 +
69 = Kodak Kodacolor VR 400 Gen 1 +
70 = Kodak Kodacolor VR 200 Gen 2 +
71 = Kodak Kodacolor VRG 100 Gen 1 +
72 = Kodak Gold 100 Gen 2 +
73 = Kodak Kodacolor VRG 200 Gen 1 +
74 = Kodak Gold 400 Gen 1 +
87 = Kodak Ektacolor Gold 160 +
88 = Kodak Ektapress 1600 Gen 1 PPC +
89 = Kodak Ektapress Gold 100 Gen 1 PPA +
90 = Kodak Ektapress Gold 400 PPB-3 +
92 = Kodak Ektar 25 Professional PHR +
97 = Kodak T-Max 100 Professional +
98 = Kodak T-Max 3200 Professional +
99 = Kodak T-Max 400 Professional +
101 = Kodak Vericolor 400 Prof VPH +
102 = Kodak Vericolor III Pro +
121 = Konika Konica Color SR-G 3200 +
122 = Konika Konica Color Super SR100 +
123 = Konika Konica Color Super SR 400 +
138 = Kodak Gold Unknown +
139 = Kodak Unknown Neg A- Normal SBA +
143 = Kodak Ektar 100 Gen 2 +
147 = Kodak Kodacolor CII +
148 = Kodak Kodacolor II +
149 = Kodak Gold Plus 200 Gen 3 +
150 = Kodak Internegative +10% Contrast +
151 = Agfa Agfacolor Ultra 50 +
152 = Fuji NHG 400 +
153 = Agfa Agfacolor XRG 100 +
154 = Kodak Gold Plus 100 Gen 3 +
155 = Konika Konica Color Super SR200 Gen 1 +
156 = Konika Konica Color SR-G 160 +
157 = Agfa Agfacolor Optima 125 +
158 = Agfa Agfacolor Portrait 160 +
162 = Kodak Kodacolor VRG 400 Gen 1 +
163 = Kodak Gold 200 Gen 1 +
164 = Kodak Kodacolor VRG 100 Gen 2 +
174 = Kodak Internegative +20% Contrast +
175 = Kodak Internegative +30% Contrast +
176 = Kodak Internegative +40% Contrast +
184 = Kodak TMax-100 D-76 CI = .40 +
185 = Kodak TMax-100 D-76 CI = .50 +
186 = Kodak TMax-100 D-76 CI = .55 +
187 = Kodak TMax-100 D-76 CI = .70 +
188 = Kodak TMax-100 D-76 CI = .80 +
189 = Kodak TMax-100 TMax CI = .40 +
190 = Kodak TMax-100 TMax CI = .50 +
191 = Kodak TMax-100 TMax CI = .55 +
192 = Kodak TMax-100 TMax CI = .70 +
193 = Kodak TMax-100 TMax CI = .80 +
195 = Kodak TMax-400 D-76 CI = .40 +
196 = Kodak TMax-400 D-76 CI = .50 +
197 = Kodak TMax-400 D-76 CI = .55 +
198 = Kodak TMax-400 D-76 CI = .70 +
214 = Kodak TMax-400 D-76 CI = .80 +
215 = Kodak TMax-400 TMax CI = .40 +
216 = Kodak TMax-400 TMax CI = .50 +
217 = Kodak TMax-400 TMax CI = .55 +
218 = Kodak TMax-400 TMax CI = .70 +
219 = Kodak TMax-400 TMax CI = .80 +
224 = 3M ScotchColor ATG 400/EXL 400 +
266 = Agfa Agfacolor Optima 200 +
267 = Konika Impressa 50 +
268 = Polaroid Polaroid CP 200 +
269 = Konika Konica Color Super SR200 Gen 2 +
270 = ILFORD XP2 400 +
271 = Polaroid Polaroid Color HD2 100 +
272 = Polaroid Polaroid Color HD2 400 +
273 = Polaroid Polaroid Color HD2 200 +
282 = 3M ScotchColor ATG-1 200 +
284 = Konika XG 400 +
307 = Kodak Universal Reversal B/W +
308 = Kodak RPC Copy Film Gen 1 +
312 = Kodak Universal E6 +
324 = Kodak Gold Ultra 400 Gen 4 +
328 = Fuji Super G 100 +
329 = Fuji Super G 200 +
330 = Fuji Super G 400 Gen 2 +
333 = Kodak Universal K14 +
334 = Fuji Super G 400 Gen 1 +
366 = Kodak Vericolor HC 6329 VHC +
367 = Kodak Vericolor HC 4329 VHC +
368 = Kodak Vericolor L 6013 VPL +
369 = Kodak Vericolor L 4013 VPL +
418 = Kodak Ektacolor Gold II 400 Prof +
430 = Kodak Royal Gold 1000 +
431 = Kodak Kodacolor VR 200 / 5093 +
432 = Kodak Gold Plus 100 Gen 4 +
443 = Kodak Royal Gold 100 +
444 = Kodak Royal Gold 400 +
445 = Kodak Universal E6 auto-balance +
446 = Kodak Universal E6 illum. corr. +
447 = Kodak Universal K14 auto-balance +
448 = Kodak Universal K14 illum. corr. +
449 = Kodak Ektar 100 Gen 3 SY +
456 = Kodak Ektar 25 +
457 = Kodak Ektar 100 Gen 3 CX +
458 = Kodak Ektapress Plus 100 Prof PJA-1 +
459 = Kodak Ektapress Gold II 100 Prof +
460 = Kodak Pro 100 PRN +
461 = Kodak Vericolor HC 100 Prof VHC-2 +
462 = Kodak Prof Color Neg 100 +
463 = Kodak Ektar 1000 Gen 2 +
464 = Kodak Ektapress Plus 1600 Pro PJC-1 +
465 = Kodak Ektapress Gold II 1600 Prof +
466 = Kodak Super Gold 1600 GF Gen 2 +
467 = Kodak Kodacolor 100 Print Gen 4 +
468 = Kodak Super Gold 100 Gen 4 +
469 = Kodak Gold 100 Gen 4 +
470 = Kodak Gold III 100 Gen 4 +
471 = Kodak Funtime 100 FA +
472 = Kodak Funtime 200 FB +
473 = Kodak Kodacolor VR 200 Gen 4 +
474 = Kodak Gold Super 200 Gen 4 +
475 = Kodak Kodacolor 200 Print Gen 4 +
476 = Kodak Super Gold 200 Gen 4 +
477 = Kodak Gold 200 Gen 4 +
478 = Kodak Gold III 200 Gen 4 +
479 = Kodak Gold Ultra 400 Gen 5 +
480 = Kodak Super Gold 400 Gen 5 +
481 = Kodak Gold 400 Gen 5 +
482 = Kodak Gold III 400 Gen 5 +
483 = Kodak Kodacolor 400 Print Gen 5 +
484 = Kodak Ektapress Plus 400 Prof PJB-2 +
485 = Kodak Ektapress Gold II 400 Prof G5 +
486 = Kodak Pro 400 PPF-2 +
487 = Kodak Ektacolor Gold II 400 EGP-4 +
488 = Kodak Ektacolor Gold 400 Prof EGP-4 +
489 = Kodak Ektapress Gold II Multspd PJM +
490 = Kodak Pro 400 MC PMC +
491 = Kodak Vericolor 400 Prof VPH-2 +
492 = Kodak Vericolor 400 Plus Prof VPH-2 +
493 = Kodak Unknown Neg Product Code 83 +
505 = Kodak Ektacolor Pro Gold 160 GPX +
508 = Kodak Royal Gold 200 +
517 = Kodak 4050000000 +
519 = Kodak Gold Plus 100 Gen 5 +
520 = Kodak Gold 800 Gen 1 +
521 = Kodak Gold Super 200 Gen 5 +
522 = Kodak Ektapress Plus 200 Prof +
523 = Kodak 4050 E6 auto-balance +
524 = Kodak 4050 E6 ilum. corr. +
525 = Kodak 4050 K14 +
526 = Kodak 4050 K14 auto-balance +
527 = Kodak 4050 K14 ilum. corr. +
528 = Kodak 4050 Reversal B&W +
532 = Kodak Advantix 200 +
533 = Kodak Advantix 400 +
534 = Kodak Advantix 100 +
535 = Kodak Ektapress Multspd Prof PJM-2 +
536 = Kodak Kodacolor VR 200 Gen 5 +
537 = Kodak Funtime 200 FB Gen 2 +
538 = Kodak Commercial 200 +
539 = Kodak Royal Gold 25 Copystand +
540 = Kodak Kodacolor DA 100 Gen 5 +
545 = Kodak Kodacolor VR 400 Gen 2 +
546 = Kodak Gold 100 Gen 6 +
547 = Kodak Gold 200 Gen 6 +
548 = Kodak Gold 400 Gen 6 +
549 = Kodak Royal Gold 100 Gen 2 +
550 = Kodak Royal Gold 200 Gen 2 +
551 = Kodak Royal Gold 400 Gen 2 +
552 = Kodak Gold Max 800 Gen 2 +
554 = Kodak 4050 E6 high contrast +
555 = Kodak 4050 E6 low saturation high contrast +
556 = Kodak 4050 E6 low saturation +
557 = Kodak Universal E-6 Low Saturation +
558 = Kodak T-Max T400 CN +
563 = Kodak Ektapress PJ100 +
564 = Kodak Ektapress PJ400 +
565 = Kodak Ektapress PJ800 +
567 = Kodak Portra 160NC +
568 = Kodak Portra 160VC +
569 = Kodak Portra 400NC +
570 = Kodak Portra 400VC +
575 = Kodak Advantix 100-2 +
576 = Kodak Advantix 200-2 +
577 = Kodak Advantix Black & White + 400 +
578 = Kodak Ektapress PJ800-2
+
331CopyrightStatusno1 = Restrictions apply +
255 = Not specified
332CopyrightFileNameno 
1538Orientationno[val & 0x3] +
0 = Horizontal (normal) +
1 = Rotate 270 CW +
2 = Rotate 180 +
3 = Rotate 90 CW
1538.1ImageWidthno[val >> 2 & 0x3]
1538.2ImageHeightno[val >> 2 & 0x3]
1538.3CompressionClassno[val >> 5 & 0x3] +
0 = Class 1 - 35mm film; Pictoral hard copy +
1 = Class 2 - Large format film +
2 = Class 3 - Text and graphics, high resolution +
3 = Class 4 - Text and graphics, high dynamic range
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 24, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PhotoMechanic.html b/ExifTool/html/TagNames/PhotoMechanic.html new file mode 100644 index 0000000..75fd711 --- /dev/null +++ b/ExifTool/html/TagNames/PhotoMechanic.html @@ -0,0 +1,199 @@ + + + + +PhotoMechanic Tags + + + +

PhotoMechanic Tags

+

The Photo Mechanic trailer contains data in an IPTC-format structure, with +soft edit information stored under record number 2.

+
+
+ + + + + + + + +
RecordTag NameWritableValues / Notes
2SoftEdit---> PhotoMechanic SoftEdit Tags
+ +

PhotoMechanic SoftEdit Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
209RawCropLeftint32s 
210RawCropTopint32s 
211RawCropRightint32s 
212RawCropBottomint32s 
213ConstrainedCropWidthint32s 
214ConstrainedCropHeightint32s 
215FrameNumint32s 
216Rotationint32s0 = 0 +
1 = 90 +
2 = 180 +
3 = 270
217CropLeftint32s 
218CropTopint32s 
219CropRightint32s 
220CropBottomint32s 
221Taggedint32s0 = No +
1 = Yes
222ColorClassint32s + +
0 = 0 (None) +
1 = 1 (Winner) +
2 = 2 (Winner alt) +
3 = 3 (Superior) +
4 = 4 (Superior alt)
  5 = 5 (Typical) +
6 = 6 (Typical alt) +
7 = 7 (Extras) +
8 = 8 (Trash)
+
223Ratingint32s 
236PreviewCropLeftint32s 
237PreviewCropTopint32s 
238PreviewCropRightint32s 
239PreviewCropBottomint32s 
+ +

PhotoMechanic XMP Tags

+

Below is a list of the observed PhotoMechanic XMP tags. The actual +namespace prefix is "photomechanic" but ExifTool shortens this in +the family 1 group name.

+ +

These tags belong to the ExifTool XMP-photomech family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ColorClassinteger + +
0 = 0 (None) +
1 = 1 (Winner) +
2 = 2 (Winner alt) +
3 = 3 (Superior) +
4 = 4 (Superior alt)
  5 = 5 (Typical) +
6 = 6 (Typical alt) +
7 = 7 (Extras) +
8 = 8 (Trash)
+
CountryCodestring/ 
EditStatusstring 
PMVersionstring 
Prefsstring(format is "Tagged:0, ColorClass:1, Rating:2, FrameNum:3")
Taggedboolean'False' = No +
'True' = Yes
TimeCreatedstring/ 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 14, 2016 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Photoshop.html b/ExifTool/html/TagNames/Photoshop.html new file mode 100644 index 0000000..4aeb948 --- /dev/null +++ b/ExifTool/html/TagNames/Photoshop.html @@ -0,0 +1,805 @@ + + + + +Photoshop Tags + + + +

Photoshop Tags

+

+Photoshop tags are found in PSD and PSB files, as well as inside embedded +Photoshop information in many other file types (JPEG, TIFF, PDF, PNG to name +a few).

+ +

Many Photoshop tags are marked as Unknown (indicated by a question mark +after the tag name) because the information they provide is not very useful +under normal circumstances. These unknown tags are not extracted unless the +Unknown (-u) option is used. See +http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/ for the +official specification

+ +

Photoshop path tags (Tag ID's 0x7d0 to 0xbb5) are not defined by default, +but a config file included in the full ExifTool distribution +(config_files/photoshop_paths.config) contains the tag definitions to allow +access to this information. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x03e8Photoshop2Info?no 
0x03e9MacintoshPrintInfo?no 
0x03eaXMLData?no 
0x03ebPhotoshop2ColorTable?no 
0x03edResolutionInfo---> Photoshop Resolution Tags
0x03eeAlphaChannelsNamesno 
0x03efDisplayInfo?no 
0x03f0PStringCaption?no 
0x03f1BorderInformation?no 
0x03f2BackgroundColor?no 
0x03f3PrintFlags?noByte 0 = Labels +
Byte 1 = Corner crop marks +
Byte 2 = Color bars +
Byte 3 = Registration marks +
Byte 4 = Negative +
Byte 5 = Emulsion down +
Byte 6 = Interpolate +
Byte 7 = Description +
Byte 8 = Print flags
0x03f4BW_HalftoningInfo?no 
0x03f5ColorHalftoningInfo?no 
0x03f6DuotoneHalftoningInfo?no 
0x03f7BW_TransferFunc?no 
0x03f8ColorTransferFuncs?no 
0x03f9DuotoneTransferFuncs?no 
0x03faDuotoneImageInfo?no 
0x03fbEffectiveBW?no 
0x03fcObsoletePhotoshopTag1?no 
0x03fdEPSOptions?no 
0x03feQuickMaskInfo?no 
0x03ffObsoletePhotoshopTag2?no 
0x0400TargetLayerID?no 
0x0401WorkingPath?no 
0x0402LayersGroupInfo?no 
0x0403ObsoletePhotoshopTag3?no 
0x0404IPTCData---> IPTC Tags
0x0405RawImageMode?no 
0x0406JPEG_Quality---> Photoshop JPEG_Quality Tags
0x0408GridGuidesInfo?no 
0x0409PhotoshopBGRThumbnailundef!(this is a JPEG image, but in BGR format instead of RGB)
0x040aCopyrightFlagint8u0 = False +
1 = True
0x040bURLstring 
0x040cPhotoshopThumbnailundef! 
0x040dGlobalAngleint32u 
0x040eColorSamplersResource?no 
0x040fICC_Profile---> ICC_Profile Tags
0x0410Watermark?no 
0x0411ICC_Untagged?no 
0x0412EffectsVisible?no 
0x0413SpotHalftone?no 
0x0414IDsBaseValue?no 
0x0415UnicodeAlphaNames?no 
0x0416IndexedColorTableCount?no 
0x0417TransparentIndex?no 
0x0419GlobalAltitudeint32u 
0x041aSliceInfo---> Photoshop SliceInfo Tags
0x041bWorkflowURLno 
0x041cJumpToXPEP?no 
0x041dAlphaIdentifiers?no 
0x041eURL_Listno+ 
0x0421VersionInfo---> Photoshop VersionInfo Tags
0x0422EXIFInfo---> EXIF Tags
0x0423ExifInfo2?no 
0x0424XMP---> XMP Tags
0x0425IPTCDigeststring!(this tag indicates provides a way for XMP-aware applications to indicate +that the XMP is synchronized with the IPTC. The MWG recommendation is to +ignore the XMP if IPTCDigest exists and doesn't match the CurrentIPTCDigest. +When writing, special values of "new" and "old" represent the digests of the +IPTC from the edited and original files respectively, and are undefined if +the IPTC does not exist in the respective file. Set this to "new" as an +indication that the XMP is synchronized with the IPTC)
0x0426PrintScaleInfo---> Photoshop PrintScaleInfo Tags
0x0428PixelInfo---> Photoshop PixelInfo Tags
0x0429LayerComps?no 
0x042aAlternateDuotoneColors?no 
0x042bAlternateSpotColors?no 
0x042dLayerSelectionIDs?no 
0x042eHDRToningInfo?no 
0x042fPrintInfo?no 
0x0430LayerGroupsEnabledID?no 
0x0431ColorSamplersResource2?no 
0x0432MeasurementScale?no 
0x0433TimelineInfo?no 
0x0434SheetDisclosure?no 
0x0435DisplayInfo?no 
0x0436OnionSkins?no 
0x0438CountInfo?no 
0x043aPrintInfo2?no 
0x043bPrintStyle?no 
0x043cMacintoshNSPrintInfo?no 
0x043dWindowsDEVMODE?no 
0x043eAutoSaveFilePath?no 
0x043fAutoSaveFormat?no 
0x0440PathSelectionState?no 
0x0bb7ClippingPathNameno 
0x0bb8OriginPathInfo?no 
0x1b58ImageReadyVariables?no 
0x1b59ImageReadyDataSets?no 
0x1f40LightroomWorkflow?no 
0x2710PrintFlagsInfo?no 
+ +

Photoshop Resolution Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0XResolutionint32u 
2DisplayedUnitsXint16u1 = inches +
2 = cm
4YResolutionint32u 
6DisplayedUnitsYint16u1 = inches +
2 = cm
+ +

Photoshop JPEG_Quality Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0PhotoshopQualityint16s 
1PhotoshopFormatno0 = Standard +
1 = Optimized +
257 = Progressive
2ProgressiveScansno1 = 3 Scans +
2 = 4 Scans +
3 = 5 Scans
+ +

Photoshop SliceInfo Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
20SlicesGroupNameno 
24NumSlicesno 
+ +

Photoshop VersionInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4HasRealMergedDatano0 = No +
1 = Yes
5WriterNameno 
9ReaderNameno 
+ +

Photoshop PrintScaleInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0PrintStyleno0 = Centered +
1 = Size to Fit +
2 = User Defined
2PrintPositionno 
10PrintScaleno 
+ +

Photoshop PixelInfo Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
4PixelAspectRationo 
+ +

Photoshop DocumentData Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Layr'Layers---> Photoshop Layers Tags
'Lr16'Layers---> Photoshop Layers Tags
+ +

Photoshop Layers Tags

+

Tags extracted from Photoshop layer information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'_xbnd'LayerBlendModesno+ + +
'colr' = Color +
'dark' = Darken +
'diff' = Difference +
'diss' = Dissolve +
'div ' = Color Dodge +
'dkCl' = Darker Color +
'fdiv' = Divide +
'fsub' = Subtract +
'hLit' = Hard Light +
'hMix' = Hard Mix +
'hue ' = Hue +
'idiv' = Color Burn +
'lLit' = Linear Light +
'lbrn' = Linear Burn
  'lddg' = Linear Dodge +
'lgCl' = Lighter Color +
'lite' = Lighten +
'lum ' = Luminosity +
'mul ' = Multiply +
'norm' = Normal +
'over' = Overlay +
'pLit' = Pin Light +
'pass' = Pass Through +
'sLit' = Soft Light +
'sat ' = Saturation +
'scrn' = Screen +
'smud' = Exclusion +
'vLit' = Vivid Light
+
'_xcnt'LayerCountno 
'_xnam'LayerNamesno+ 
'_xopc'LayerOpacitiesno+ 
'_xrct'LayerRectanglesno+(top left bottom right)
'_xvis'LayerVisibleno+0 = Yes +
2 = No
'lclr'LayerColorsno+ + +
0 = None +
1 = Red +
2 = Orange +
3 = Yellow
  4 = Green +
5 = Blue +
6 = Violet +
7 = Gray
+
'lsct'LayerSectionsno+0 = Layer +
1 = Folder (open) +
2 = Folder (closed) +
3 = Divider
'luni'LayerUnicodeNamesno+ 
'lyid'LayerIDs?no+ 
'shmd'LayerModifyDatesno+ 
+ +

Photoshop Header Tags

+

This information is found in the PSD file header.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
6NumChannelsno 
7ImageHeightno 
9ImageWidthno 
11BitDepthno 
12ColorModeno + +
0 = Bitmap +
1 = Grayscale +
2 = Indexed +
3 = RGB
  4 = CMYK +
7 = Multichannel +
8 = Duotone +
9 = Lab
+
+ +

Photoshop ImageData Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
0Compressionno0 = Uncompressed +
1 = RLE +
2 = ZIP without prediction +
3 = ZIP with prediction
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 23, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PostScript.html b/ExifTool/html/TagNames/PostScript.html new file mode 100644 index 0000000..9206cfa --- /dev/null +++ b/ExifTool/html/TagNames/PostScript.html @@ -0,0 +1,174 @@ + + + + +PostScript Tags + + + +

PostScript Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AI12_BuildNumber'AIBuildNumberno 
'AI3_ColorUsage'AIColorUsageno 
'AI5_FileFormat'AIFileFormatno 
'AI5_NumLayers'AINumLayersno 
'AI5_RulerUnits'AIRulerUnitsno + +
0 = Inches +
1 = Millimeters +
2 = Points
  3 = Picas +
4 = Centimeters +
6 = Pixels
+
'AI5_TargetResolution'AITargetResolutionno 
'AI8_CreatorVersion'AICreatorVersionno 
'AI9_ColorModel'AIColorModelno1 = RGB +
2 = CMYK
'Author'Authorstring 
'BeginDocument'EmbeddedFile---> PostScript Tags +
(extracted with ExtractEmbedded option)
'BeginICCProfile'ICC_Profile---> ICC_Profile Tags
'BeginPhotoshop'PhotoshopData---> Photoshop Tags
'BoundingBox'BoundingBoxno 
'Copyright'Copyrightstring 
'CreationDate'CreateDatestring 
'Creator'Creatorstring 
'EmbeddedFileName'EmbeddedFileNameno(not a real tag ID, but the file name from a BeginDocument statement. +Extracted with document metadata when ExtractEmbedded option is used)
'For'Forstring(for whom the document was prepared)
'ImageData'ImageDatano 
'Keywords'Keywordsstring 
'ModDate'ModifyDatestring 
'Pages'Pagesno 
'Routing'Routingstring 
'Subject'Subjectstring 
'TIFFPreview'TIFFPreviewno(not a real tag ID, but used to represent the TIFF preview extracted from DOS +EPS images)
'Title'Titlestring 
'Version'Versionstring 
'begin_xml_packet'XMP---> XMP Tags
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Apr 13, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/PrintIM.html b/ExifTool/html/TagNames/PrintIM.html new file mode 100644 index 0000000..6c90657 --- /dev/null +++ b/ExifTool/html/TagNames/PrintIM.html @@ -0,0 +1,32 @@ + + + + +PrintIM Tags + + + +

PrintIM Tags

+

+The format of the PrintIM information is known, however no PrintIM tags have +been decoded. Use the Unknown (-u) option to extract PrintIM information. +

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'PrintIMVersion'PrintIMVersionno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 19, 2010 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Qualcomm.html b/ExifTool/html/TagNames/Qualcomm.html new file mode 100644 index 0000000..38226f1 --- /dev/null +++ b/ExifTool/html/TagNames/Qualcomm.html @@ -0,0 +1,4779 @@ + + + + +Qualcomm Tags + + + +

Qualcomm Tags

+

The tags below have been observed in the JPEG APP7 "Qualcomm Camera +Attributes" segment written by some cameras such as the HP iPAQ Voice +Messenger. ExifTool will extract any information found from this segment, +even if it is not listed in this table.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AECAggressivenessno 
AECCurrentExpIndexno 
AECCurrentSensorLumano 
AECEnableno 
AECExposureIndexAdjStepno 
AECHighLumaRegionCountno 
AECHighLumaRegionThresholdno 
AECIndoorIdxno 
AECLumaTargetno 
AECLumaToleranceno 
AECModeno 
AECOdoorIdxno 
AECOutdoorBrightDiscardedno 
AECOutdoorBrightEnableno 
AECOutdoorBrightReductionno 
AECOutdoorBrightThresholdHIno 
AECOutdoorBrightThresholdLOno 
AECOutdoorGammaIndexno 
AECSnapshotDigitalGainno 
AECSnapshotExposureTimeMsno 
AECSnapshotLineCountno 
AECSnapshotSensorGainno 
AECVfeLumano 
AFBoundaryno 
AFCollectEndStatno 
AFEnableno 
AFFarEndno 
AFFineSrchPointsno 
AFFineStepno 
AFFocusTimeno 
AFGrossStepno 
AFModeno 
AFNearEndno 
AFPosDefMacrono 
AFPosDefNormno 
AFPositionno 
AFProcessno 
AFREnableno 
AFRFaster0ExpModno 
AFRFaster0Triggerno 
AFRFaster1ExpModno 
AFRFaster1Triggerno 
AFRFaster2ExpModno 
AFRFaster2Triggerno 
AFRFaster3ExpModno 
AFRFaster3Triggerno 
AFRFaster4ExpModno 
AFRFaster4Triggerno 
AFRPossibleFrameCntno 
AFRSlower0ExpModno 
AFRSlower0Triggerno 
AFRSlower1ExpModno 
AFRSlower1Triggerno 
AFRSlower2ExpModno 
AFRSlower2Triggerno 
AFRSlower3ExpModno 
AFRSlower3Triggerno 
AFRSlower4ExpModno 
AFRSlower4Triggerno 
AFResetLensAfterSnapno 
AFStepsNearFarno 
AFStepsNearInfinityno 
AFTestModeno 
AFTracePositions00no 
AFTracePositions01no 
AFTracePositions02no 
AFTracePositions03no 
AFTracePositions04no 
AFTracePositions05no 
AFTracePositions06no 
AFTracePositions07no 
AFTracePositions08no 
AFTracePositions09no 
AFTracePositions10no 
AFTracePositions11no 
AFTracePositions12no 
AFTracePositions13no 
AFTracePositions14no 
AFTracePositions15no 
AFTracePositions16no 
AFTracePositions17no 
AFTracePositions18no 
AFTracePositions19no 
AFTracePositions20no 
AFTracePositions21no 
AFTracePositions22no 
AFTracePositions23no 
AFTracePositions24no 
AFTracePositions25no 
AFTracePositions26no 
AFTracePositions27no 
AFTracePositions28no 
AFTracePositions29no 
AFTracePositions30no 
AFTracePositions31no 
AFTracePositions32no 
AFTracePositions33no 
AFTracePositions34no 
AFTracePositions35no 
AFTracePositions36no 
AFTracePositions37no 
AFTracePositions38no 
AFTracePositions39no 
AFTracePositions40no 
AFTracePositions41no 
AFTracePositions42no 
AFTracePositions43no 
AFTracePositions44no 
AFTracePositions45no 
AFTracePositions46no 
AFTracePositions47no 
AFTracePositions48no 
AFTracePositions49no 
AFTraceStats00no 
AFTraceStats01no 
AFTraceStats02no 
AFTraceStats03no 
AFTraceStats04no 
AFTraceStats05no 
AFTraceStats06no 
AFTraceStats07no 
AFTraceStats08no 
AFTraceStats09no 
AFTraceStats10no 
AFTraceStats11no 
AFTraceStats12no 
AFTraceStats13no 
AFTraceStats14no 
AFTraceStats15no 
AFTraceStats16no 
AFTraceStats17no 
AFTraceStats18no 
AFTraceStats19no 
AFTraceStats20no 
AFTraceStats21no 
AFTraceStats22no 
AFTraceStats23no 
AFTraceStats24no 
AFTraceStats25no 
AFTraceStats26no 
AFTraceStats27no 
AFTraceStats28no 
AFTraceStats29no 
AFTraceStats30no 
AFTraceStats31no 
AFTraceStats32no 
AFTraceStats33no 
AFTraceStats34no 
AFTraceStats35no 
AFTraceStats36no 
AFTraceStats37no 
AFTraceStats38no 
AFTraceStats39no 
AFTraceStats40no 
AFTraceStats41no 
AFTraceStats42no 
AFTraceStats43no 
AFTraceStats44no 
AFTraceStats45no 
AFTraceStats46no 
AFTraceStats47no 
AFTraceStats48no 
AFTraceStats49no 
AFUndershootProtectno 
AFVfeHorzOffsetno 
AFVfeHorzWidthno 
AFVfeMetricMaxno 
AFVfeVertHeightno 
AFVfeVertOffsetno 
ASF3EdgeDetectno 
ASF3EdgeFilterA11no 
ASF3EdgeFilterA12no 
ASF3EdgeFilterA13no 
ASF3EdgeFilterA21no 
ASF3EdgeFilterA22no 
ASF3EdgeFilterA23no 
ASF3EdgeFilterA31no 
ASF3EdgeFilterA32no 
ASF3EdgeFilterA33no 
ASF3Enableno 
ASF3LowerThresholdno 
ASF3NoiseFilterA11no 
ASF3NoiseFilterA12no 
ASF3NoiseFilterA13no 
ASF3NoiseFilterA21no 
ASF3NoiseFilterA22no 
ASF3NoiseFilterA23no 
ASF3NoiseFilterA31no 
ASF3NoiseFilterA32no 
ASF3NoiseFilterA33no 
ASF3UpperThresholdno 
ASF5BrtLoThresno 
ASF5BrtShrpDegF1no 
ASF5BrtShrpDegF2no 
ASF5BrtSmthPercentno 
ASF5BrtUpThresno 
ASF5Enableno 
ASF5ExposureIndex1no 
ASF5ExposureIndex2no 
ASF5Filter1A11no 
ASF5Filter1A12no 
ASF5Filter1A13no 
ASF5Filter1A14no 
ASF5Filter1A15no 
ASF5Filter1A21no 
ASF5Filter1A22no 
ASF5Filter1A23no 
ASF5Filter1A24no 
ASF5Filter1A25no 
ASF5Filter1A31no 
ASF5Filter1A32no 
ASF5Filter1A33no 
ASF5Filter1A34no 
ASF5Filter1A35no 
ASF5Filter1A41no 
ASF5Filter1A42no 
ASF5Filter1A43no 
ASF5Filter1A44no 
ASF5Filter1A45no 
ASF5Filter1A51no 
ASF5Filter1A52no 
ASF5Filter1A53no 
ASF5Filter1A54no 
ASF5Filter1A55no 
ASF5Filter2A11no 
ASF5Filter2A12no 
ASF5Filter2A13no 
ASF5Filter2A14no 
ASF5Filter2A15no 
ASF5Filter2A21no 
ASF5Filter2A22no 
ASF5Filter2A23no 
ASF5Filter2A24no 
ASF5Filter2A25no 
ASF5Filter2A31no 
ASF5Filter2A32no 
ASF5Filter2A33no 
ASF5Filter2A34no 
ASF5Filter2A35no 
ASF5Filter2A41no 
ASF5Filter2A42no 
ASF5Filter2A43no 
ASF5Filter2A44no 
ASF5Filter2A45no 
ASF5Filter2A51no 
ASF5Filter2A52no 
ASF5Filter2A53no 
ASF5Filter2A54no 
ASF5Filter2A55no 
ASF5FilterModeno 
ASF5LowLoThresno 
ASF5LowShrpDegF1no 
ASF5LowShrpDegF2no 
ASF5LowSmthPrcntno 
ASF5LowUpThresno 
ASF5LumaFilter00no 
ASF5LumaFilter01no 
ASF5LumaFilter02no 
ASF5LumaFilter03no 
ASF5LumaFilter04no 
ASF5LumaFilter05no 
ASF5LumaFilter06no 
ASF5LumaFilter07no 
ASF5LumaFilter08no 
ASF5MaxExposureIndexno 
ASF5NrmLoThresno 
ASF5NrmShrpDegF1no 
ASF5NrmShrpDegF2no 
ASF5NrmSmthPrcntno 
ASF5NrmUpThresno 
ASF5NrmizeFactor1no 
ASF5NrmizeFactor2no 
AWBAggressivenessno 
AWBAgwGridDist2Threshno 
AWBAlgorithmno 
AWBAveBgRationo 
AWBAveRgRationo 
AWBBlueGainAdjRef1no 
AWBBlueGainAdjRef2no 
AWBBlueGainAdjRef3no 
AWBBlueGainAdjRef4no 
AWBBlueGainAdjRef5no 
AWBBlueGainAdjRef6no 
AWBBlueGainAdjRef7no 
AWBBlueGainAdjRef8no 
AWBBlueGainRef1no 
AWBBlueGainRef2no 
AWBBlueGainRef3no 
AWBBlueGainRef4no 
AWBBlueGainRef5no 
AWBBlueGainRef6no 
AWBBlueGainRef7no 
AWBBlueGainRef8no 
AWBCcBiasno 
AWBCompactClusterR2no 
AWBEnableno 
AWBGreenOffsetBgno 
AWBGreenOffsetRgno 
AWBIndoorSampleInfluenceno 
AWBLoVfeC1no 
AWBLoVfeC2no 
AWBLoVfeC3no 
AWBLoVfeC4no 
AWBLoVfeM1no 
AWBLoVfeM2no 
AWBLoVfeM3no 
AWBLoVfeM4no 
AWBLoVfeMaxYno 
AWBLoVfeMinYno 
AWBLowLigColCorEnano 
AWBMaxBGainno 
AWBMaxGGainno 
AWBMaxRGainno 
AWBMinBGainno 
AWBMinGGainno 
AWBMinRGainno 
AWBNormVfeC1no 
AWBNormVfeC2no 
AWBNormVfeC3no 
AWBNormVfeC4no 
AWBNormVfeM1no 
AWBNormVfeM2no 
AWBNormVfeM3no 
AWBNormVfeM4no 
AWBNormVfeMaxYno 
AWBNormVfeMinYno 
AWBOudorVfeC1no 
AWBOudorVfeC2no 
AWBOudorVfeC3no 
AWBOudorVfeC4no 
AWBOudorVfeM1no 
AWBOudorVfeM2no 
AWBOudorVfeM3no 
AWBOudorVfeM4no 
AWBOudorVfeMaxYno 
AWBOudorVfeMinYno 
AWBOutdoorSampleInfluenceno 
AWBPrevWbBgainno 
AWBPrevWbGgainno 
AWBPrevWbRgainno 
AWBRedGainAdjRef1no 
AWBRedGainAdjRef2no 
AWBRedGainAdjRef3no 
AWBRedGainAdjRef4no 
AWBRedGainAdjRef5no 
AWBRedGainAdjRef6no 
AWBRedGainAdjRef7no 
AWBRedGainAdjRef8no 
AWBRedGainRef1no 
AWBRedGainRef2no 
AWBRedGainRef3no 
AWBRedGainRef4no 
AWBRedGainRef5no 
AWBRedGainRef6no 
AWBRedGainRef7no 
AWBRedGainRef8no 
AWBSampleDecisionno 
AWBSnapshotBGainno 
AWBSnapshotRGainno 
AntiBadingPixelClkno 
AntiBadingPixelClkPerLineno 
AntibandingEnableno 
BlckLvlEvenColsno 
BlckLvlOddColsno 
CamMclkHzno 
ChroSupChroThres1no 
ChroSupChroThres2no 
ChroSupLumaThres1no 
ChroSupLumaThres2no 
ChroSupLumaThres3no 
ChroSupLumaThres4no 
ChromSupressno 
ClipToAfRatono 
CurrResolno 
DayltConvChrmA_Mno 
DayltConvChrmA_Pno 
DayltConvChrmB_Mno 
DayltConvChrmB_Pno 
DayltConvChrmC_Mno 
DayltConvChrmC_Pno 
DayltConvChrmD_Mno 
DayltConvChrmD_Pno 
DayltConvChrmKCbno 
DayltConvChrmKCrno 
DayltConvLumaKno 
DayltConvLumaV0no 
DayltConvLumaV1no 
DayltConvLumaV2no 
DefConvChrmA_Mno 
DefConvChrmA_Pno 
DefConvChrmB_Mno 
DefConvChrmB_Pno 
DefConvChrmC_Mno 
DefConvChrmC_Pno 
DefConvChrmD_Mno 
DefConvChrmD_Pno 
DefConvChrmKCbno 
DefConvChrmKCrno 
DefConvLumaKno 
DefConvLumaV0no 
DefConvLumaV1no 
DefConvLumaV2no 
DefCorC0no 
DefCorC1no 
DefCorC2no 
DefCorC3no 
DefCorC4no 
DefCorC5no 
DefCorC6no 
DefCorC7no 
DefCorC8no 
DefCorK0no 
DefCorK1no 
DefCorK2no 
DefLumaGammaModeno 
DefRgbGammaModeno 
DefectPixCorEnableno 
DefectPixMaxThreshno 
DefectPixMinThreshno 
DiscardFrstFrmno 
FrmSkipPttrnno 
GammaEnableno 
HJREnableno 
HJRMaxNumFramesno 
HJROneToTwoOffsetno 
HJRTextureThresholdno 
HJR_NReductionFlatno 
HJR_NReductionTextureno 
IncandConvChrmA_Mno 
IncandConvChrmA_Pno 
IncandConvChrmB_Mno 
IncandConvChrmB_Pno 
IncandConvChrmC_Mno 
IncandConvChrmC_Pno 
IncandConvChrmD_Mno 
IncandConvChrmD_Pno 
IncandConvChrmKCbno 
IncandConvChrmKCrno 
IncandConvLumaKno 
IncandConvLumaV0no 
IncandConvLumaV1no 
IncandConvLumaV2no 
LADetectno 
LAEnableno 
MaxPrviewFpsno 
MaxVideoFpsno 
NghtshtFpsno 
NightshotModeno 
OutlierDistanceno 
PclkInvertno 
PrviewFpsno 
PrviewResolno 
R2ABlueCtbl00no 
R2ABlueCtbl01no 
R2ABlueCtbl02no 
R2ABlueCtbl03no 
R2ABlueCtbl04no 
R2ABlueCtbl05no 
R2ABlueCtbl06no 
R2ABlueCtbl07no 
R2ABlueCtbl08no 
R2ABlueCtbl09no 
R2ABlueCtbl10no 
R2ABlueCtbl11no 
R2ABlueCtbl12no 
R2ABlueCtbl13no 
R2ABlueCtbl14no 
R2ABlueCtbl15no 
R2ABlueCtbl16no 
R2ABlueCtbl17no 
R2ABlueCtbl18no 
R2ABlueCtbl19no 
R2ABlueCtbl20no 
R2ABlueCtbl21no 
R2ABlueCtbl22no 
R2ABlueCtbl23no 
R2ABlueCtbl24no 
R2ABlueCtbl25no 
R2ABlueCtbl26no 
R2ABlueCtbl27no 
R2ABlueCtbl28no 
R2ABlueCtbl29no 
R2ABlueCtbl30no 
R2ABlueCtbl31no 
R2ABlueStbl00no 
R2ABlueStbl01no 
R2ABlueStbl02no 
R2ABlueStbl03no 
R2ABlueStbl04no 
R2ABlueStbl05no 
R2ABlueStbl06no 
R2ABlueStbl07no 
R2ABlueStbl08no 
R2ABlueStbl09no 
R2ABlueStbl10no 
R2ABlueStbl11no 
R2ABlueStbl12no 
R2ABlueStbl13no 
R2ABlueStbl14no 
R2ABlueStbl15no 
R2ABlueStbl16no 
R2ABlueStbl17no 
R2ABlueStbl18no 
R2ABlueStbl19no 
R2ABlueStbl20no 
R2ABlueStbl21no 
R2ABlueStbl22no 
R2ABlueStbl23no 
R2ABlueStbl24no 
R2ABlueStbl25no 
R2ABlueStbl26no 
R2ABlueStbl27no 
R2ABlueStbl28no 
R2ABlueStbl29no 
R2ABlueStbl30no 
R2ABlueStbl31no 
R2ACxno 
R2ACyno 
R2AGreenCtbl00no 
R2AGreenCtbl01no 
R2AGreenCtbl02no 
R2AGreenCtbl03no 
R2AGreenCtbl04no 
R2AGreenCtbl05no 
R2AGreenCtbl06no 
R2AGreenCtbl07no 
R2AGreenCtbl08no 
R2AGreenCtbl09no 
R2AGreenCtbl10no 
R2AGreenCtbl11no 
R2AGreenCtbl12no 
R2AGreenCtbl13no 
R2AGreenCtbl14no 
R2AGreenCtbl15no 
R2AGreenCtbl16no 
R2AGreenCtbl17no 
R2AGreenCtbl18no 
R2AGreenCtbl19no 
R2AGreenCtbl20no 
R2AGreenCtbl21no 
R2AGreenCtbl22no 
R2AGreenCtbl23no 
R2AGreenCtbl24no 
R2AGreenCtbl25no 
R2AGreenCtbl26no 
R2AGreenCtbl27no 
R2AGreenCtbl28no 
R2AGreenCtbl29no 
R2AGreenCtbl30no 
R2AGreenCtbl31no 
R2AGreenStbl00no 
R2AGreenStbl01no 
R2AGreenStbl02no 
R2AGreenStbl03no 
R2AGreenStbl04no 
R2AGreenStbl05no 
R2AGreenStbl06no 
R2AGreenStbl07no 
R2AGreenStbl08no 
R2AGreenStbl09no 
R2AGreenStbl10no 
R2AGreenStbl11no 
R2AGreenStbl12no 
R2AGreenStbl13no 
R2AGreenStbl14no 
R2AGreenStbl15no 
R2AGreenStbl16no 
R2AGreenStbl17no 
R2AGreenStbl18no 
R2AGreenStbl19no 
R2AGreenStbl20no 
R2AGreenStbl21no 
R2AGreenStbl22no 
R2AGreenStbl23no 
R2AGreenStbl24no 
R2AGreenStbl25no 
R2AGreenStbl26no 
R2AGreenStbl27no 
R2AGreenStbl28no 
R2AGreenStbl29no 
R2AGreenStbl30no 
R2AGreenStbl31no 
R2AHeightno 
R2AIntervalsno 
R2ARedCtbl00no 
R2ARedCtbl01no 
R2ARedCtbl02no 
R2ARedCtbl03no 
R2ARedCtbl04no 
R2ARedCtbl05no 
R2ARedCtbl06no 
R2ARedCtbl07no 
R2ARedCtbl08no 
R2ARedCtbl09no 
R2ARedCtbl10no 
R2ARedCtbl11no 
R2ARedCtbl12no 
R2ARedCtbl13no 
R2ARedCtbl14no 
R2ARedCtbl15no 
R2ARedCtbl16no 
R2ARedCtbl17no 
R2ARedCtbl18no 
R2ARedCtbl19no 
R2ARedCtbl20no 
R2ARedCtbl21no 
R2ARedCtbl22no 
R2ARedCtbl23no 
R2ARedCtbl24no 
R2ARedCtbl25no 
R2ARedCtbl26no 
R2ARedCtbl27no 
R2ARedCtbl28no 
R2ARedCtbl29no 
R2ARedCtbl30no 
R2ARedCtbl31no 
R2ARedStbl00no 
R2ARedStbl01no 
R2ARedStbl02no 
R2ARedStbl03no 
R2ARedStbl04no 
R2ARedStbl05no 
R2ARedStbl06no 
R2ARedStbl07no 
R2ARedStbl08no 
R2ARedStbl09no 
R2ARedStbl10no 
R2ARedStbl11no 
R2ARedStbl12no 
R2ARedStbl13no 
R2ARedStbl14no 
R2ARedStbl15no 
R2ARedStbl16no 
R2ARedStbl17no 
R2ARedStbl18no 
R2ARedStbl19no 
R2ARedStbl20no 
R2ARedStbl21no 
R2ARedStbl22no 
R2ARedStbl23no 
R2ARedStbl24no 
R2ARedStbl25no 
R2ARedStbl26no 
R2ARedStbl27no 
R2ARedStbl28no 
R2ARedStbl29no 
R2ARedStbl30no 
R2ARedStbl31no 
R2ATbl00no 
R2ATbl01no 
R2ATbl02no 
R2ATbl03no 
R2ATbl04no 
R2ATbl05no 
R2ATbl06no 
R2ATbl07no 
R2ATbl08no 
R2ATbl09no 
R2ATbl10no 
R2ATbl11no 
R2ATbl12no 
R2ATbl13no 
R2ATbl14no 
R2ATbl15no 
R2ATbl16no 
R2ATbl17no 
R2ATbl18no 
R2ATbl19no 
R2ATbl20no 
R2ATbl21no 
R2ATbl22no 
R2ATbl23no 
R2ATbl24no 
R2ATbl25no 
R2ATbl26no 
R2ATbl27no 
R2ATbl28no 
R2ATbl29no 
R2ATbl30no 
R2ATbl31no 
R2AWidthno 
R2D65BlueCtbl00no 
R2D65BlueCtbl01no 
R2D65BlueCtbl02no 
R2D65BlueCtbl03no 
R2D65BlueCtbl04no 
R2D65BlueCtbl05no 
R2D65BlueCtbl06no 
R2D65BlueCtbl07no 
R2D65BlueCtbl08no 
R2D65BlueCtbl09no 
R2D65BlueCtbl10no 
R2D65BlueCtbl11no 
R2D65BlueCtbl12no 
R2D65BlueCtbl13no 
R2D65BlueCtbl14no 
R2D65BlueCtbl15no 
R2D65BlueCtbl16no 
R2D65BlueCtbl17no 
R2D65BlueCtbl18no 
R2D65BlueCtbl19no 
R2D65BlueCtbl20no 
R2D65BlueCtbl21no 
R2D65BlueCtbl22no 
R2D65BlueCtbl23no 
R2D65BlueCtbl24no 
R2D65BlueCtbl25no 
R2D65BlueCtbl26no 
R2D65BlueCtbl27no 
R2D65BlueCtbl28no 
R2D65BlueCtbl29no 
R2D65BlueCtbl30no 
R2D65BlueCtbl31no 
R2D65BlueStbl00no 
R2D65BlueStbl01no 
R2D65BlueStbl02no 
R2D65BlueStbl03no 
R2D65BlueStbl04no 
R2D65BlueStbl05no 
R2D65BlueStbl06no 
R2D65BlueStbl07no 
R2D65BlueStbl08no 
R2D65BlueStbl09no 
R2D65BlueStbl10no 
R2D65BlueStbl11no 
R2D65BlueStbl12no 
R2D65BlueStbl13no 
R2D65BlueStbl14no 
R2D65BlueStbl15no 
R2D65BlueStbl16no 
R2D65BlueStbl17no 
R2D65BlueStbl18no 
R2D65BlueStbl19no 
R2D65BlueStbl20no 
R2D65BlueStbl21no 
R2D65BlueStbl22no 
R2D65BlueStbl23no 
R2D65BlueStbl24no 
R2D65BlueStbl25no 
R2D65BlueStbl26no 
R2D65BlueStbl27no 
R2D65BlueStbl28no 
R2D65BlueStbl29no 
R2D65BlueStbl30no 
R2D65BlueStbl31no 
R2D65Cxno 
R2D65Cyno 
R2D65GreenCtbl00no 
R2D65GreenCtbl01no 
R2D65GreenCtbl02no 
R2D65GreenCtbl03no 
R2D65GreenCtbl04no 
R2D65GreenCtbl05no 
R2D65GreenCtbl06no 
R2D65GreenCtbl07no 
R2D65GreenCtbl08no 
R2D65GreenCtbl09no 
R2D65GreenCtbl10no 
R2D65GreenCtbl11no 
R2D65GreenCtbl12no 
R2D65GreenCtbl13no 
R2D65GreenCtbl14no 
R2D65GreenCtbl15no 
R2D65GreenCtbl16no 
R2D65GreenCtbl17no 
R2D65GreenCtbl18no 
R2D65GreenCtbl19no 
R2D65GreenCtbl20no 
R2D65GreenCtbl21no 
R2D65GreenCtbl22no 
R2D65GreenCtbl23no 
R2D65GreenCtbl24no 
R2D65GreenCtbl25no 
R2D65GreenCtbl26no 
R2D65GreenCtbl27no 
R2D65GreenCtbl28no 
R2D65GreenCtbl29no 
R2D65GreenCtbl30no 
R2D65GreenCtbl31no 
R2D65GreenStbl00no 
R2D65GreenStbl01no 
R2D65GreenStbl02no 
R2D65GreenStbl03no 
R2D65GreenStbl04no 
R2D65GreenStbl05no 
R2D65GreenStbl06no 
R2D65GreenStbl07no 
R2D65GreenStbl08no 
R2D65GreenStbl09no 
R2D65GreenStbl10no 
R2D65GreenStbl11no 
R2D65GreenStbl12no 
R2D65GreenStbl13no 
R2D65GreenStbl14no 
R2D65GreenStbl15no 
R2D65GreenStbl16no 
R2D65GreenStbl17no 
R2D65GreenStbl18no 
R2D65GreenStbl19no 
R2D65GreenStbl20no 
R2D65GreenStbl21no 
R2D65GreenStbl22no 
R2D65GreenStbl23no 
R2D65GreenStbl24no 
R2D65GreenStbl25no 
R2D65GreenStbl26no 
R2D65GreenStbl27no 
R2D65GreenStbl28no 
R2D65GreenStbl29no 
R2D65GreenStbl30no 
R2D65GreenStbl31no 
R2D65Heightno 
R2D65Intervalsno 
R2D65RedCtbl00no 
R2D65RedCtbl01no 
R2D65RedCtbl02no 
R2D65RedCtbl03no 
R2D65RedCtbl04no 
R2D65RedCtbl05no 
R2D65RedCtbl06no 
R2D65RedCtbl07no 
R2D65RedCtbl08no 
R2D65RedCtbl09no 
R2D65RedCtbl10no 
R2D65RedCtbl11no 
R2D65RedCtbl12no 
R2D65RedCtbl13no 
R2D65RedCtbl14no 
R2D65RedCtbl15no 
R2D65RedCtbl16no 
R2D65RedCtbl17no 
R2D65RedCtbl18no 
R2D65RedCtbl19no 
R2D65RedCtbl20no 
R2D65RedCtbl21no 
R2D65RedCtbl22no 
R2D65RedCtbl23no 
R2D65RedCtbl24no 
R2D65RedCtbl25no 
R2D65RedCtbl26no 
R2D65RedCtbl27no 
R2D65RedCtbl28no 
R2D65RedCtbl29no 
R2D65RedCtbl30no 
R2D65RedCtbl31no 
R2D65RedStbl00no 
R2D65RedStbl01no 
R2D65RedStbl02no 
R2D65RedStbl03no 
R2D65RedStbl04no 
R2D65RedStbl05no 
R2D65RedStbl06no 
R2D65RedStbl07no 
R2D65RedStbl08no 
R2D65RedStbl09no 
R2D65RedStbl10no 
R2D65RedStbl11no 
R2D65RedStbl12no 
R2D65RedStbl13no 
R2D65RedStbl14no 
R2D65RedStbl15no 
R2D65RedStbl16no 
R2D65RedStbl17no 
R2D65RedStbl18no 
R2D65RedStbl19no 
R2D65RedStbl20no 
R2D65RedStbl21no 
R2D65RedStbl22no 
R2D65RedStbl23no 
R2D65RedStbl24no 
R2D65RedStbl25no 
R2D65RedStbl26no 
R2D65RedStbl27no 
R2D65RedStbl28no 
R2D65RedStbl29no 
R2D65RedStbl30no 
R2D65RedStbl31no 
R2D65Tbl00no 
R2D65Tbl01no 
R2D65Tbl02no 
R2D65Tbl03no 
R2D65Tbl04no 
R2D65Tbl05no 
R2D65Tbl06no 
R2D65Tbl07no 
R2D65Tbl08no 
R2D65Tbl09no 
R2D65Tbl10no 
R2D65Tbl11no 
R2D65Tbl12no 
R2D65Tbl13no 
R2D65Tbl14no 
R2D65Tbl15no 
R2D65Tbl16no 
R2D65Tbl17no 
R2D65Tbl18no 
R2D65Tbl19no 
R2D65Tbl20no 
R2D65Tbl21no 
R2D65Tbl22no 
R2D65Tbl23no 
R2D65Tbl24no 
R2D65Tbl25no 
R2D65Tbl26no 
R2D65Tbl27no 
R2D65Tbl28no 
R2D65Tbl29no 
R2D65Tbl30no 
R2D65Tbl31no 
R2D65Widthno 
R2TL84BlueCtbl00no 
R2TL84BlueCtbl01no 
R2TL84BlueCtbl02no 
R2TL84BlueCtbl03no 
R2TL84BlueCtbl04no 
R2TL84BlueCtbl05no 
R2TL84BlueCtbl06no 
R2TL84BlueCtbl07no 
R2TL84BlueCtbl08no 
R2TL84BlueCtbl09no 
R2TL84BlueCtbl10no 
R2TL84BlueCtbl11no 
R2TL84BlueCtbl12no 
R2TL84BlueCtbl13no 
R2TL84BlueCtbl14no 
R2TL84BlueCtbl15no 
R2TL84BlueCtbl16no 
R2TL84BlueCtbl17no 
R2TL84BlueCtbl18no 
R2TL84BlueCtbl19no 
R2TL84BlueCtbl20no 
R2TL84BlueCtbl21no 
R2TL84BlueCtbl22no 
R2TL84BlueCtbl23no 
R2TL84BlueCtbl24no 
R2TL84BlueCtbl25no 
R2TL84BlueCtbl26no 
R2TL84BlueCtbl27no 
R2TL84BlueCtbl28no 
R2TL84BlueCtbl29no 
R2TL84BlueCtbl30no 
R2TL84BlueCtbl31no 
R2TL84BlueStbl00no 
R2TL84BlueStbl01no 
R2TL84BlueStbl02no 
R2TL84BlueStbl03no 
R2TL84BlueStbl04no 
R2TL84BlueStbl05no 
R2TL84BlueStbl06no 
R2TL84BlueStbl07no 
R2TL84BlueStbl08no 
R2TL84BlueStbl09no 
R2TL84BlueStbl10no 
R2TL84BlueStbl11no 
R2TL84BlueStbl12no 
R2TL84BlueStbl13no 
R2TL84BlueStbl14no 
R2TL84BlueStbl15no 
R2TL84BlueStbl16no 
R2TL84BlueStbl17no 
R2TL84BlueStbl18no 
R2TL84BlueStbl19no 
R2TL84BlueStbl20no 
R2TL84BlueStbl21no 
R2TL84BlueStbl22no 
R2TL84BlueStbl23no 
R2TL84BlueStbl24no 
R2TL84BlueStbl25no 
R2TL84BlueStbl26no 
R2TL84BlueStbl27no 
R2TL84BlueStbl28no 
R2TL84BlueStbl29no 
R2TL84BlueStbl30no 
R2TL84BlueStbl31no 
R2TL84Cxno 
R2TL84Cyno 
R2TL84GreenCtbl00no 
R2TL84GreenCtbl01no 
R2TL84GreenCtbl02no 
R2TL84GreenCtbl03no 
R2TL84GreenCtbl04no 
R2TL84GreenCtbl05no 
R2TL84GreenCtbl06no 
R2TL84GreenCtbl07no 
R2TL84GreenCtbl08no 
R2TL84GreenCtbl09no 
R2TL84GreenCtbl10no 
R2TL84GreenCtbl11no 
R2TL84GreenCtbl12no 
R2TL84GreenCtbl13no 
R2TL84GreenCtbl14no 
R2TL84GreenCtbl15no 
R2TL84GreenCtbl16no 
R2TL84GreenCtbl17no 
R2TL84GreenCtbl18no 
R2TL84GreenCtbl19no 
R2TL84GreenCtbl20no 
R2TL84GreenCtbl21no 
R2TL84GreenCtbl22no 
R2TL84GreenCtbl23no 
R2TL84GreenCtbl24no 
R2TL84GreenCtbl25no 
R2TL84GreenCtbl26no 
R2TL84GreenCtbl27no 
R2TL84GreenCtbl28no 
R2TL84GreenCtbl29no 
R2TL84GreenCtbl30no 
R2TL84GreenCtbl31no 
R2TL84GreenStbl00no 
R2TL84GreenStbl01no 
R2TL84GreenStbl02no 
R2TL84GreenStbl03no 
R2TL84GreenStbl04no 
R2TL84GreenStbl05no 
R2TL84GreenStbl06no 
R2TL84GreenStbl07no 
R2TL84GreenStbl08no 
R2TL84GreenStbl09no 
R2TL84GreenStbl10no 
R2TL84GreenStbl11no 
R2TL84GreenStbl12no 
R2TL84GreenStbl13no 
R2TL84GreenStbl14no 
R2TL84GreenStbl15no 
R2TL84GreenStbl16no 
R2TL84GreenStbl17no 
R2TL84GreenStbl18no 
R2TL84GreenStbl19no 
R2TL84GreenStbl20no 
R2TL84GreenStbl21no 
R2TL84GreenStbl22no 
R2TL84GreenStbl23no 
R2TL84GreenStbl24no 
R2TL84GreenStbl25no 
R2TL84GreenStbl26no 
R2TL84GreenStbl27no 
R2TL84GreenStbl28no 
R2TL84GreenStbl29no 
R2TL84GreenStbl30no 
R2TL84GreenStbl31no 
R2TL84Heightno 
R2TL84Intervalsno 
R2TL84RedCtbl00no 
R2TL84RedCtbl01no 
R2TL84RedCtbl02no 
R2TL84RedCtbl03no 
R2TL84RedCtbl04no 
R2TL84RedCtbl05no 
R2TL84RedCtbl06no 
R2TL84RedCtbl07no 
R2TL84RedCtbl08no 
R2TL84RedCtbl09no 
R2TL84RedCtbl10no 
R2TL84RedCtbl11no 
R2TL84RedCtbl12no 
R2TL84RedCtbl13no 
R2TL84RedCtbl14no 
R2TL84RedCtbl15no 
R2TL84RedCtbl16no 
R2TL84RedCtbl17no 
R2TL84RedCtbl18no 
R2TL84RedCtbl19no 
R2TL84RedCtbl20no 
R2TL84RedCtbl21no 
R2TL84RedCtbl22no 
R2TL84RedCtbl23no 
R2TL84RedCtbl24no 
R2TL84RedCtbl25no 
R2TL84RedCtbl26no 
R2TL84RedCtbl27no 
R2TL84RedCtbl28no 
R2TL84RedCtbl29no 
R2TL84RedCtbl30no 
R2TL84RedCtbl31no 
R2TL84RedStbl00no 
R2TL84RedStbl01no 
R2TL84RedStbl02no 
R2TL84RedStbl03no 
R2TL84RedStbl04no 
R2TL84RedStbl05no 
R2TL84RedStbl06no 
R2TL84RedStbl07no 
R2TL84RedStbl08no 
R2TL84RedStbl09no 
R2TL84RedStbl10no 
R2TL84RedStbl11no 
R2TL84RedStbl12no 
R2TL84RedStbl13no 
R2TL84RedStbl14no 
R2TL84RedStbl15no 
R2TL84RedStbl16no 
R2TL84RedStbl17no 
R2TL84RedStbl18no 
R2TL84RedStbl19no 
R2TL84RedStbl20no 
R2TL84RedStbl21no 
R2TL84RedStbl22no 
R2TL84RedStbl23no 
R2TL84RedStbl24no 
R2TL84RedStbl25no 
R2TL84RedStbl26no 
R2TL84RedStbl27no 
R2TL84RedStbl28no 
R2TL84RedStbl29no 
R2TL84RedStbl30no 
R2TL84RedStbl31no 
R2TL84Tbl00no 
R2TL84Tbl01no 
R2TL84Tbl02no 
R2TL84Tbl03no 
R2TL84Tbl04no 
R2TL84Tbl05no 
R2TL84Tbl06no 
R2TL84Tbl07no 
R2TL84Tbl08no 
R2TL84Tbl09no 
R2TL84Tbl10no 
R2TL84Tbl11no 
R2TL84Tbl12no 
R2TL84Tbl13no 
R2TL84Tbl14no 
R2TL84Tbl15no 
R2TL84Tbl16no 
R2TL84Tbl17no 
R2TL84Tbl18no 
R2TL84Tbl19no 
R2TL84Tbl20no 
R2TL84Tbl21no 
R2TL84Tbl22no 
R2TL84Tbl23no 
R2TL84Tbl24no 
R2TL84Tbl25no 
R2TL84Tbl26no 
R2TL84Tbl27no 
R2TL84Tbl28no 
R2TL84Tbl29no 
R2TL84Tbl30no 
R2TL84Tbl31no 
R2TL84Widthno 
RolloffEnableno 
SensorFmtno 
SensorTypeno 
SensrFulHghtno 
SensrFulWdthno 
SensrQtrHghtno 
SensrQtrWdthno 
SnapshotResolno 
TL84ConvChrmA_Mno 
TL84ConvChrmA_Pno 
TL84ConvChrmB_Mno 
TL84ConvChrmB_Pno 
TL84ConvChrmC_Mno 
TL84ConvChrmC_Pno 
TL84ConvChrmD_Mno 
TL84ConvChrmD_Pno 
TL84ConvChrmKCbno 
TL84ConvChrmKCrno 
TL84ConvLumaKno 
TL84ConvLumaV0no 
TL84ConvLumaV1no 
TL84ConvLumaV2no 
VideoFpsno 
YhiYloConvChrmA_Mno 
YhiYloConvChrmA_Pno 
YhiYloConvChrmB_Mno 
YhiYloConvChrmB_Pno 
YhiYloConvChrmC_Mno 
YhiYloConvChrmC_Pno 
YhiYloConvChrmD_Mno 
YhiYloConvChrmD_Pno 
YhiYloConvChrmKCbno 
YhiYloConvChrmKCrno 
YhiYloConvLumaKno 
YhiYloConvLumaV0no 
YhiYloConvLumaV1no 
YhiYloConvLumaV2no 
YhiYloCorC0no 
YhiYloCorC1no 
YhiYloCorC2no 
YhiYloCorC3no 
YhiYloCorC4no 
YhiYloCorC5no 
YhiYloCorC6no 
YhiYloCorC7no 
YhiYloCorC8no 
YhiYloCorK0no 
YhiYloCorK1no 
YhiYloCorK2no 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 20, 2012 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/QuickTime.html b/ExifTool/html/TagNames/QuickTime.html new file mode 100644 index 0000000..ec527ce --- /dev/null +++ b/ExifTool/html/TagNames/QuickTime.html @@ -0,0 +1,9082 @@ + + + + +QuickTime Tags + + + +

QuickTime Tags

+

+The QuickTime format is used for many different types of audio, video and +image files (most notably, MOV/MP4 videos and HEIC/CR3 images). ExifTool +extracts standard meta information and a variety of audio, video and image +parameters, as well as proprietary information written by many camera +models. Tags with a question mark after their name are not extracted unless +the Unknown option is set.

+ +

When writing, ExifTool creates both QuickTime and XMP tags by default, but +the group may be specified to write one or the other separately. If no +location is specified, newly created QuickTime tags are added in the +ItemList location if +possible, otherwise in +UserData, and +finally in Keys, +but this order may be changed by setting the PREFERRED level of the +appropriate table in the config file (see +example.config in the full distribution for an +example). Note that some tags with the same name but different ID's may +exist in the same location, but the family 7 group names may be used to +differentiate these. ExifTool currently writes only top-level metadata in +QuickTime-based files; it extracts other track-specific and timed metadata, +but can not yet edit tags in these locations (with the exception of +track-level date/time tags).

+ +

Beware that the Keys tags are actually stored inside the ItemList in the +file, so deleting the ItemList group as a block (ie. -ItemList:all=) also +deletes Keys tags. Instead, to preserve Keys tags the ItemList tags may be +deleted individually with -QuickTime:ItemList:all=.

+ +

Alternate language tags may be accessed for +ItemList and +Keys tags by adding +a 3-character ISO 639-2 language code and an optional ISO 3166-1 alpha 2 +country code to the tag name (eg. "ItemList:Artist-deu" or +"ItemList::Artist-deu-DE"). Most +UserData tags support a +language code, but without a country code. If no language code is specified +when writing, the default language is written and alternate languages for +the tag are deleted. Use the "und" language code to write the default +language without deleting alternate languages. Note that "eng" is treated +as a default language when reading, but not when writing.

+ +

According to the specification, integer-format QuickTime date/time tags +should be stored as UTC. Unfortunately, digital cameras often store local +time values instead (presumably because they don't know the time zone). For +this reason, by default ExifTool does not assume a time zone for these +values. However, if the API QuickTimeUTC option is set, then ExifTool will +assume these values are properly stored as UTC, and will convert them to +local time when extracting.

+ +

When writing string-based date/time tags, the system time zone is added if +the PrintConv option is enabled and no time zone is specified. This is +because Apple software may display crazy values if the time zone is missing +for some tags.

+ +

By default ExifTool will remove null padding from some QuickTime containers +in Canon CR3 files when writing, but the +QuickTimePad option may be used to preserve +the original size by padding with nulls if necessary.

+ +

See +https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/ +for the official specification. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'GPS 'GPSDataList2?no 
'IDIT'DateTimeOriginalstring 
'PICT'PreviewPICTno 
'_htc'HTCInfo---> QuickTime HTCInfo Tags
'ardt'ARDroneFileno 
'frea'Kodak_frea---> Kodak frea Tags
'free'KodakFree +
Pittasoft +
ThumbnailImage +
Free?
-
-
no
no
--> Kodak Free Tags +
--> QuickTime Pittasoft Tags
'ftyp'FileType---> QuickTime FileType Tags
'gps0'GPSTrack---> QuickTime Stream Tags
'gsen'GSensor---> QuickTime Stream Tags
'junk'Junk?no 
'mdat'MediaData?no 
'mdat-offset'MediaDataOffsetno 
'mdat-size'MediaDataSizeno(not a real tag ID, this tag represents the size of the 'mdat' data in bytes +and is used in the AvgBitrate calculation)
'meco'OtherMeta---> QuickTime OtherMeta Tags
'meta'Meta---> QuickTime Meta Tags
'moof'MovieFragment---> QuickTime MovieFragment Tags
'moov'Movie---> QuickTime Movie Tags
'mpvd'MotionPhotoVideoyes(MP4-format video saved in Samsung motion-photo HEIC images.)
'pict'PreviewPICTno 
'pnot'Preview---> QuickTime Preview Tags
'prrt'ARDroneTelemetryno(telemetry information for each video frame: status1, status2, time, pitch, +roll, yaw, speed, altitude)
'sefd'SamsungTrailer---> Samsung Trailer Tags
'skip'CanonSkip +
PreviewImage +
SkipInfo +
Skip?
-
no
-
no
--> Canon Skip Tags +
--> QuickTime SkipInfo Tags
'thm 'ThumbnailImageno 
'thum'ThumbnailImageno 
'udat'GPSLogno(parsed to extract GPS separately when ExtractEmbedded is used)
'udta'KenwoodData +
FLIRData
-
-
--> QuickTime Stream Tags +
--> FLIR UserData Tags
'uuid'XMP +
UUID-PROF +
UUID-Flip +
UUID-Canon2 +
SensorData +
SensorData +
PreviewImage +
UUID-Unknown?
-
-
-
-
-
no
no
no
--> XMP Tags +
--> QuickTime Profile Tags +
--> QuickTime Flip Tags +
--> Canon uuid2 Tags +
--> QuickTime Tags360Fly Tags +
(raw 360Fly sensor data without ExtractEmbedded option)
'wide'Wide?no 
+ +

QuickTime Stream Tags

+

The tags below are extracted from timed metadata in QuickTime and other +formats of video files when the ExtractEmbedded option is used. Although +most of these tags are combined into the single table below, ExifTool +currently reads 66 different formats of timed GPS metadata from video files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Accelerometerno(3-axis acceleration in units of g)
AccelerometerDatano 
AngularVelocityno 
CTMD---> Canon CTMD Tags
CameraDateTimeno 
Carno 
Distanceno 
ExposureCompensationno 
ExposureTimeno 
FNumberno 
FrameNumberno 
GPSAltitudeno 
GPSDOPno 
GPSDateTimeno 
GPSLatitudeno 
GPSLongitudeno 
GPSSatellitesno 
GPSSpeedno(in km/h unless GPSSpeedRef says otherwise)
GPSSpeedRefno'K' = km/h +
'M' = mph +
'N' = knots
GPSTimeStampno 
GPSTrackno(relative to true north unless GPSTrackRef says otherwise)
GPSTrackRefno'M' = Magnetic North +
'T' = True North
GSensorno 
INSV---> QuickTime INSV_MakerNotes Tags
ISOno 
JpgFromRawno 
KiloCaloriesno 
PreviewInfo---> QuickTime PreviewInfo Tags
RVMI_gReV +
RVMI_sReV
-
-
--> QuickTime RVMI_gReV Tags +
--> QuickTime RVMI_sReV Tags
RawGSensorno 
SampleDateTimeno 
SampleDurationno 
SampleTimeno(sample decoding time)
Textno 
TimeCodeno 
Unknown00?no 
Unknown01?no 
Unknown02?no 
Unknown03?no 
UserLabelno 
VerticalSpeedno 
VideoTimeStampno 
camm0 +
camm1 +
camm2 +
camm3 +
camm4 +
camm5 +
camm6 +
camm7
-
-
-
-
-
-
-
-
--> QuickTime camm0 Tags +
--> QuickTime camm1 Tags +
--> QuickTime camm2 Tags +
--> QuickTime camm3 Tags +
--> QuickTime camm4 Tags +
--> QuickTime camm5 Tags +
--> QuickTime camm6 Tags +
--> QuickTime camm7 Tags
fdsc---> GoPro fdsc Tags
gpmd_Kingslim +
gpmd_Rove +
gpmd_FMAS +
gpmd_GoPro
-
-
-
-
--> QuickTime Stream Tags +
--> QuickTime Stream Tags +
--> QuickTime Stream Tags +
--> GoPro GPMF Tags
marl---> QuickTime marl Tags
mebx---> QuickTime Keys Tags
mett---> Parrot mett Tags
rtmd---> Sony rtmd Tags
tx3g---> QuickTime tx3g Tags
+ +

QuickTime INSV_MakerNotes Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x000aSerialNumberno 
0x0012Modelno 
0x001aFirmwareno 
0x002aParametersno(number of lenses, 6-axis orientation of each lens, raw resolution)
+ +

QuickTime PreviewInfo Tags

+

Preview stored by TomTom Bandit ActionCam.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
8PreviewImageno 
+ +

QuickTime RVMI_gReV Tags

+

GPS information extracted from the RVMI box of MOV videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4GPSLatitudeno 
8GPSLongitudeno 
16GPSSpeedno 
18GPSTrackno 
+ +

QuickTime RVMI_sReV Tags

+

G-sensor information extracted from the RVMI box of MOV videos.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
4GSensorno 
+ +

QuickTime camm0 Tags

+

The camm0 through camm7 tables define tags extracted from the Google Street +View Camera Motion Metadata of MP4 videos. See +https://developers.google.com/streetview/publish/camm-spec for the +specification.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004AngleAxisno(angle axis orientation in radians in local coordinate system)
+ +

QuickTime camm1 Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004PixelExposureTimeno 
0x0008RollingShutterSkewTimeno 
+ +

QuickTime camm2 Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004AngularVelocityno(gyro angular velocity about X, Y and Z axes in rad/s)
+ +

QuickTime camm3 Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004Accelerationno(acceleration in the X, Y and Z directions in m/s^2)
+ +

QuickTime camm4 Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004Positionno(X, Y, Z position in local coordinate system)
+ +

QuickTime camm5 Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004GPSLatitudeno 
0x000cGPSLongitudeno 
0x0014GPSAltitudeno 
+ +

QuickTime camm6 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004GPSDateTimeno 
0x000cGPSMeasureModeno0 = No Measurement +
2 = 2-Dimensional Measurement +
3 = 3-Dimensional Measurement
0x0010GPSLatitudeno 
0x0018GPSLongitudeno 
0x0020GPSAltitudeno 
0x0024GPSHorizontalAccuracyno(metres)
0x0028GPSVerticalAccuracyno 
0x002cGPSVelocityEastno(m/s)
0x0030GPSVelocityNorthno 
0x0034GPSVelocityUpno 
0x0038GPSSpeedAccuracyno 
+ +

QuickTime camm7 Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004MagneticFieldno(microtesla)
+ +

QuickTime marl Tags

+

Tags extracted from the marl ctbx timed metadata of GM cars.

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

QuickTime Keys Tags

+

This directory contains a list of key names which are used to decode tags +written by the "mdta" handler. Also in this table are a few tags found in +timed metadata that are not yet writable by ExifTool. The prefix of +"com.apple.quicktime." has been removed from the TagID's below. These tags +support alternate languages in the same way as the +ItemList tags. Note +that by default, +ItemList and +UserData tags are +preferred when writing, so to create a tag when a same-named tag exists in +either of these tables, either the "Keys" location must be specified (eg. +-Keys:Author=Phil on the command line), or the PREFERRED level must be +changed via the config file.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Encoded_With'EncodedWithyes 
'album'Albumyes 
'apple.photos.variation-identifier'ApplePhotosVariationIdentifierint64s 
'artist'Artistyes 
'artwork'Artworkyes 
'author'Authoryes 
'camera.framereadouttimeinmicroseconds'FrameReadoutTimeyes 
'camera.identifier'CameraIdentifieryes 
'collection.user'UserCollectionyes 
'com.android.version'AndroidVersionyes 
'com.apple.photos.captureMode'CaptureModeyes 
'comment'Commentyes 
'content.identifier'ContentIdentifieryes 
'copyright'Copyrightyes 
'creationdate'CreationDateyes 
'description'Descriptionyes 
'detected-face'FaceInfo---> QuickTime FaceInfo Tags
'detected-face.bounds'DetectedFaceBoundsno 
'detected-face.face-id'DetectedFaceIDno 
'detected-face.roll-angle'DetectedFaceRollAngleno 
'detected-face.yaw-angle'DetectedFaceYawAngleno 
'direction.facing'CameraDirectionyes 
'direction.motion'CameraMotionyes 
'director'Directoryes 
'displayname'DisplayNameyes 
'genre'Genreyes 
'information'Informationyes 
'keywords'Keywordsyes 
'live-photo-info'LivePhotoInfono 
'live-photo.auto'LivePhotoAutoint8u 
'live-photo.vitality-score'LivePhotoVitalityScorefloat 
'live-photo.vitality-scoring-version'LivePhotoVitalityScoringVersionint64s 
'location.ISO6709'GPSCoordinatesyes(Google Photos may ignore this if the coorinates have more than 5 digits +after the decimal)
'location.accuracy.horizontal'LocationAccuracyHorizontalyes 
'location.body'LocationBodyyes 
'location.date'LocationDateyes 
'location.name'LocationNameyes 
'location.note'LocationNoteyes 
'location.role'LocationRoleyes0 = Shooting Location +
1 = Real Location +
2 = Fictional Location
'make'Makeyes 
'model'Modelyes 
'player.movie.audio.balance'Balanceyes 
'player.movie.audio.bass'Bassyes 
'player.movie.audio.gain'AudioGainyes 
'player.movie.audio.mute'Muteint8u0 = Off +
1 = On
'player.movie.audio.pitchshift'PitchShiftyes 
'player.movie.audio.treble'Trebleyes 
'player.movie.visual.brightness'Brightnessyes 
'player.movie.visual.color'Coloryes 
'player.movie.visual.contrast'Contrastyes 
'player.movie.visual.tint'Tintyes 
'player.version'PlayerVersionyes 
'producer'Produceryes 
'publisher'Publisheryes 
'rating.user'UserRatingyes 
'software'Softwareyes 
'still-image-time'StillImageTimeno(this tag always has a value of -1; the time of the still image is obtained +from the associated SampleTime)
'title'Titleyes 
'version'Versionyes 
'video-orientation'VideoOrientationno +
1 = Horizontal (normal) +
2 = Mirror horizontal +
3 = Rotate 180 +
4 = Mirror vertical +
5 = Mirror horizontal and rotate 270 CW +
6 = Rotate 90 CW +
7 = Mirror horizontal and rotate 90 CW +
8 = Rotate 270 CW
+
'year'Yearyes 
+ +

QuickTime FaceInfo Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'crec'FaceRec---> QuickTime FaceRec Tags
+ +

QuickTime FaceRec Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'cits'FaceItem---> QuickTime Keys Tags
+ +

QuickTime tx3g Tags

+

Tags extracted from the tx3g sbtl timed metadata of Yuneec drones, and +subtitle text in some other videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Alt'GPSAltitudeno 
'DateTime'DateTimeno 
'GimPitch'GimbalPitchno 
'GimRoll'GimbalRollno 
'GimYaw'GimbalYawno 
'Lat'GPSLatitudeno 
'Lon'GPSLongitudeno 
'Pitch'Pitchno 
'Roll'Rollno 
'Text'Textno 
'Yaw'Yawno 
+ +

QuickTime HTCInfo Tags

+

Tags written by some HTC camera phones.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'slmt'Unknown_slmt?no 
+ +

QuickTime Pittasoft Tags

+

Tags found in Pittasoft Blackvue dashcam "free" data.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'3gf 'AccelData---> QuickTime Stream Tags
'cprt'Copyrightno 
'gps 'GPSLogno(parsed to extract GPS separately when ExtractEmbedded is used)
'ptnm'OriginalFileNameno 
'ptrh'Ptrh---> QuickTime Pittasoft Tags
'sttm'StartTimeno 
'thum'PreviewImageno 
+ +

QuickTime FileType Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0MajorBrandno'3g2a' = 3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-0 V1.0 +
'3g2b' = 3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-A V1.0.0 +
'3g2c' = 3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-B v1.0 +
'3ge6' = 3GPP (.3GP) Release 6 MBMS Extended Presentations +
'3ge7' = 3GPP (.3GP) Release 7 MBMS Extended Presentations +
'3gg6' = 3GPP Release 6 General Profile +
'3gp1' = 3GPP Media (.3GP) Release 1 (probably non-existent) +
'3gp2' = 3GPP Media (.3GP) Release 2 (probably non-existent) +
'3gp3' = 3GPP Media (.3GP) Release 3 (probably non-existent) +
'3gp4' = 3GPP Media (.3GP) Release 4 +
'3gp5' = 3GPP Media (.3GP) Release 5 +
'3gp6' = 3GPP Media (.3GP) Release 6 Streaming Servers +
'3gs7' = 3GPP Media (.3GP) Release 7 Streaming Servers +
'CAEP' = Canon Digital Camera +
'CDes' = Convergent Design +
'F4A ' = Audio for Adobe Flash Player 9+ (.F4A) +
'F4B ' = Audio Book for Adobe Flash Player 9+ (.F4B) +
'F4P ' = Protected Video for Adobe Flash Player 9+ (.F4P) +
'F4V ' = Video for Adobe Flash Player 9+ (.F4V) +
'JP2 ' = JPEG 2000 Image (.JP2) [ISO 15444-1 ?] +
'JP20' = Unknown, from GPAC samples (prob non-existent) +
'KDDI' = 3GPP2 EZmovie for KDDI 3G cellphones +
'M4A ' = Apple iTunes AAC-LC (.M4A) Audio +
'M4B ' = Apple iTunes AAC-LC (.M4B) Audio Book +
'M4P ' = Apple iTunes AAC-LC (.M4P) AES Protected Audio +
'M4V ' = Apple iTunes Video (.M4V) Video +
'M4VH' = Apple TV (.M4V) +
'M4VP' = Apple iPhone (.M4V) +
'MPPI' = Photo Player, MAF [ISO/IEC 23000-3] +
'MSNV' = MPEG-4 (.MP4) for SonyPSP +
'NDAS' = MP4 v2 [ISO 14496-14] Nero Digital AAC Audio +
'NDSC' = MPEG-4 (.MP4) Nero Cinema Profile +
'NDSH' = MPEG-4 (.MP4) Nero HDTV Profile +
'NDSM' = MPEG-4 (.MP4) Nero Mobile Profile +
'NDSP' = MPEG-4 (.MP4) Nero Portable Profile +
'NDSS' = MPEG-4 (.MP4) Nero Standard Profile +
'NDXC' = H.264/MPEG-4 AVC (.MP4) Nero Cinema Profile +
'NDXH' = H.264/MPEG-4 AVC (.MP4) Nero HDTV Profile +
'NDXM' = H.264/MPEG-4 AVC (.MP4) Nero Mobile Profile +
'NDXP' = H.264/MPEG-4 AVC (.MP4) Nero Portable Profile +
'NDXS' = H.264/MPEG-4 AVC (.MP4) Nero Standard Profile +
'ROSS' = Ross Video +
'XAVC' = Sony XAVC +
'aax ' = Audible Enhanced Audiobook (.AAX) +
'avc1' = MP4 Base w/ AVC ext [ISO 14496-12:2005] +
'avif' = AV1 Image File Format (.AVIF) +
'caqv' = Casio Digital Camera +
'crx ' = Canon Raw (.CRX) +
'da0a' = DMB MAF w/ MPEG Layer II aud, MOT slides, DLS, JPG/PNG/MNG images +
'da0b' = DMB MAF, extending DA0A, with 3GPP timed text, DID, TVA, REL, IPMP +
'da1a' = DMB MAF audio with ER-BSAC audio, JPG/PNG/MNG images +
'da1b' = DMB MAF, extending da1a, with 3GPP timed text, DID, TVA, REL, IPMP +
'da2a' = DMB MAF aud w/ HE-AAC v2 aud, MOT slides, DLS, JPG/PNG/MNG images +
'da2b' = DMB MAF, extending da2a, with 3GPP timed text, DID, TVA, REL, IPMP +
'da3a' = DMB MAF aud with HE-AAC aud, JPG/PNG/MNG images +
'da3b' = DMB MAF, extending da3a w/ BIFS, 3GPP timed text, DID, TVA, REL, IPMP +
'dmb1' = DMB MAF supporting all the components defined in the specification +
'dmpf' = Digital Media Project +
'drc1' = Dirac (wavelet compression), encapsulated in ISO base media (MP4) +
'dv1a' = DMB MAF vid w/ AVC vid, ER-BSAC aud, BIFS, JPG/PNG/MNG images, TS +
'dv1b' = DMB MAF, extending dv1a, with 3GPP timed text, DID, TVA, REL, IPMP +
'dv2a' = DMB MAF vid w/ AVC vid, HE-AAC v2 aud, BIFS, JPG/PNG/MNG images, TS +
'dv2b' = DMB MAF, extending dv2a, with 3GPP timed text, DID, TVA, REL, IPMP +
'dv3a' = DMB MAF vid w/ AVC vid, HE-AAC aud, BIFS, JPG/PNG/MNG images, TS +
'dv3b' = DMB MAF, extending dv3a, with 3GPP timed text, DID, TVA, REL, IPMP +
'dvr1' = DVB (.DVB) over RTP +
'dvt1' = DVB (.DVB) over MPEG-2 Transport Stream +
'heic' = High Efficiency Image Format HEVC still image (.HEIC) +
'heix' = High Efficiency Image Format still image (.HEIF) +
'hevc' = High Efficiency Image Format HEVC sequence (.HEICS) +
'isc2' = ISMACryp 2.0 Encrypted File +
'iso2' = MP4 Base Media v2 [ISO 14496-12:2005] +
'iso3' = MP4 Base Media v3 +
'iso4' = MP4 Base Media v4 +
'iso5' = MP4 Base Media v5 +
'iso6' = MP4 Base Media v6 +
'iso7' = MP4 Base Media v7 +
'iso8' = MP4 Base Media v8 +
'iso9' = MP4 Base Media v9 +
'isom' = MP4 Base Media v1 [IS0 14496-12:2003] +
'jpm ' = JPEG 2000 Compound Image (.JPM) [ISO 15444-6] +
'jpx ' = JPEG 2000 with extensions (.JPX) [ISO 15444-2] +
'mif1' = High Efficiency Image Format still image (.HEIF) +
'mj2s' = Motion JPEG 2000 [ISO 15444-3] Simple Profile +
'mjp2' = Motion JPEG 2000 [ISO 15444-3] General Profile +
'mmp4' = MPEG-4/3GPP Mobile Profile (.MP4/3GP) (for NTT) +
'mp21' = MPEG-21 [ISO/IEC 21000-9] +
'mp41' = MP4 v1 [ISO 14496-1:ch13] +
'mp42' = MP4 v2 [ISO 14496-14] +
'mp71' = MP4 w/ MPEG-7 Metadata [per ISO 14496-12] +
'mqt ' = Sony / Mobile QuickTime (.MQV) US Patent 7,477,830 (Sony Corp) +
'msf1' = High Efficiency Image Format sequence (.HEIFS) +
'odcf' = OMA DCF DRM Format 2.0 (OMA-TS-DRM-DCF-V2_0-20060303-A) +
'opf2' = OMA PDCF DRM Format 2.1 (OMA-TS-DRM-DCF-V2_1-20070724-C) +
'opx2' = OMA PDCF DRM + XBS extensions (OMA-TS-DRM_XBS-V1_0-20070529-C) +
'pana' = Panasonic Digital Camera +
'qt ' = Apple QuickTime (.MOV/QT) +
'sdv ' = SD Memory Card Video +
'ssc1' = Samsung stereoscopic, single stream +
'ssc2' = Samsung stereoscopic, dual stream
1MinorVersionno 
2CompatibleBrandsno 
+ +

QuickTime OtherMeta Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'mere'MetaRelation---> QuickTime MetaRelation Tags
'meta'Meta---> QuickTime Meta Tags
+ +

QuickTime MetaRelation Tags

+
+
+ + + + +
Index4Tag NameWritableValues / Notes
[no tags known]
+ +

QuickTime Meta Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'bxml'BinaryXML?no 
'dinf'DataInfo---> QuickTime DataInfo Tags
'free'Free?no 
'hdlr'Handler---> QuickTime Handler Tags
'idat'MetaImageSizeno 
'iinf'ItemInformation---> QuickTime ItemInfo Tags +
--> QuickTime ItemInfo Tags
'iloc'ItemLocationno(parsed, but not extracted as a tag)
'ilst'ItemList---> QuickTime ItemList Tags
'ipmc'IPMPControl?no 
'ipro'ItemProtection?no 
'iprp'ItemProperties---> QuickTime ItemProp Tags
'iref'ItemReference---> QuickTime ItemRef Tags
'keys'Keys---> QuickTime Keys Tags
'pitm'PrimaryItemReferenceno 
'uuid'MetaVersion +
UUID-Unknown?
no
no
 
'xml 'XML---> XMP XML Tags
+ +

QuickTime DataInfo Tags

+

MP4 data information box.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'dref'DataRef---> QuickTime DataRef Tags
+ +

QuickTime DataRef Tags

+

MP4 data reference box.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
"url\0"URLno 
'url 'URLno 
'urn 'URNno 
+ +

QuickTime Handler Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4HandlerClassno'dhlr' = Data Handler +
'mhlr' = Media Handler
8HandlerTypeno + +
'alis' = Alias Data +
'camm' = Camera Metadata +
'crsm' = Clock Reference +
'data' = Data +
'hint' = Hint Track +
'ipsm' = IPMP +
'm7sm' = MPEG-7 Stream +
'mdir' = Metadata +
'mdta' = Metadata Tags +
'meta' = NRT Metadata +
'mjsm' = MPEG-J +
'nrtm' = Non-Real Time Metadata +
'ocsm' = Object Content
  'odsm' = Object Descriptor +
'pict' = Picture +
'priv' = Private +
'psmd' = Panasonic Static Metadata +
'sbtl' = Subtitle +
'sdsm' = Scene Description +
'soun' = Audio Track +
'subp' = Subpicture +
'text' = Text +
'tmcd' = Time Code +
'url ' = URL +
'vide' = Video Track
+
12HandlerVendorIDno--> QuickTime VendorID Values
24HandlerDescriptionno 
+ +

QuickTime VendorID Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
ValueVendorIDValueVendorIDValueVendorID
' KD '= Kodak'ZORA'= Zoran Corporation'olym'= Olympus
'AR.D'= Parrot AR.Drone'appl'= Apple'pana'= Panasonic
'FFMP'= FFmpeg'fe20'= Olympus (fe20)'pent'= Pentax
'GIC '= General Imaging Co.'kdak'= Kodak'pr01'= Olympus (pr01)
'KMPI'= Konica-Minolta'leic'= Leica'sany'= Sanyo
'NIKO'= Nikon'mino'= Minolta  
'SMI '= Sorenson Media Inc.'niko'= Nikon  
+ +

QuickTime ItemInfo Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'infe'ItemInfoEntryno(parsed, but not extracted as a tag)
+ +

QuickTime ItemList Tags

+

This is the preferred location for creating new QuickTime tags. Tags in +this table support alternate languages which are accessed by adding a +3-character ISO 639-2 language code and an optional ISO 3166-1 alpha 2 +country code to the tag name (eg. "ItemList:Title-fra" or +"ItemList::Title-fra-FR"). When creating a new Meta box to contain the +ItemList directory, by default ExifTool adds an 'mdir' (Metadata) Handler +box because Apple software may ignore ItemList tags otherwise, but the API +QuickTimeHandler option may be set to 0 to avoid this.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'----'iTunesInfo---> QuickTime iTunesInfo Tags
'@PST'ParentShortTitlestring 
'@ppi'ParentProductIDstring 
'@pti'ParentTitlestring 
'@sti'ShortTitlestring 
'AACR'Unknown_AACR?string 
'CDEK'Unknown_CDEK?string 
'CDET'Unknown_CDET?string 
'GUID'GUIDstring 
'VERS'ProductVersionstring 
'aART'AlbumArtiststring 
'akID'AppleStoreAccountTypeint8s0 = iTunes +
1 = AOL
'albm'Albumstring/ 
'apID'AppleStoreAccountstring 
'atID'ArtistIDint32s 
'auth'Authorstring 
'catg'Categorystring 
'cmID'ComposerIDstring 
'cnID'AppleStoreCatalogIDint32s 
'covr'CoverArtstring 
'cpil'Compilationint8s0 = No +
1 = Yes
'cprt'Copyrightstring 
'desc'Descriptionstring/ 
'disk'DiskNumberundef 
'dscp'Descriptionstring/ 
'egid'EpisodeGlobalUniqueIDstring 
'geID'GenreIDint32s--> QuickTime GenreID Values
'gnre'Genreundef/ 
'grup'Groupingstring/ 
'gshh'GoogleHostHeaderstring 
'gspm'GooglePingMessagestring 
'gspu'GooglePingURLstring 
'gssd'GoogleSourceDatastring 
'gsst'GoogleStartTimestring 
'gstd'GoogleTrackDurationstring 
'hdvd'HDVideoint8s0 = No +
1 = Yes
'itnu'iTunesUint8s0 = No +
1 = Yes
'keyw'Keywordstring 
'ldes'LongDescriptionstring 
'pcst'Podcastint8s0 = No +
1 = Yes
'perf'Performerstring 
'pgap'PlayGapint8s0 = Insert Gap +
1 = No Gap
'plID'AlbumIDint32s[2] 
'prID'ProductIDstring 
'purd'PurchaseDatestring 
'purl'PodcastURLstring 
'rate'RatingPercentstring 
'rldt'ReleaseDatestring 
'rtng'Ratingint8s0 = none +
1 = Explicit +
2 = Clean +
4 = Explicit (old)
'sdes'StoreDescriptionstring 
'sfID'AppleStoreCountryint32s--> QuickTime AppleStoreCountry Values
'shwm'ShowMovementint8s0 = No +
1 = Yes
'soaa'SortAlbumArtiststring 
'soal'SortAlbumstring 
'soar'SortArtiststring 
'soco'SortComposerstring 
'sonm'SortNamestring 
'sosn'SortShowstring 
'stik'MediaTypeint8s + +
0 = Movie (old) +
1 = Normal (Music) +
2 = Audiobook +
5 = Whacked Bookmark +
6 = Music Video +
9 = Movie
  10 = TV Show +
11 = Booklet +
14 = Ringtone +
21 = Podcast +
23 = iTunes U
+
'titl'Titlestring/ 
'tmpo'BeatsPerMinuteint16s 
'trkn'TrackNumberundef 
'tven'TVEpisodeIDstring 
'tves'TVEpisodeint32s 
'tvnn'TVNetworkNamestring 
'tvsh'TVShowstring 
'tvsn'TVSeasonint32u 
'yrrc'Yearstring 
"©ART"Artiststring 
"©alb"Albumstring 
"©ard"ArtDirectorstring 
"©arg"Arrangerstring 
"©aut"Authorstring/ 
"©cmt"Commentstring 
"©com"Composerstring/ 
"©con"Conductorstring 
"©cpy"Copyrightstring/ 
"©day"ContentCreateDatestring 
"©des"Descriptionstring 
"©dir"Directorstring 
"©enc"EncodedBystring 
"©gen"Genrestring 
"©grp"Groupingstring 
"©lyr"Lyricsstring 
"©mvc"MovementCountint16s 
"©mvi"MovementNumberint16s 
"©mvn"MovementNamestring 
"©nam"Titlestring 
"©nrt"Narratorstring 
"©ope"OriginalArtiststring 
"©prd"Producerstring 
"©pub"Publisherstring 
"©sne"SoundEngineerstring 
"©sol"Soloiststring 
"©st3"Subtitlestring 
"©too"Encoderstring 
"©trk"Trackstring 
"©wrk"Workstring 
"©wrt"Composerstring 
"©xpd"ExecutiveProducerstring 
"©xyz"GPSCoordinatesstring 
+ +

QuickTime GenreID Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueGenreID
2= Music|Blues
3= Music|Comedy
4= Music|Children's Music
5= Music|Classical
6= Music|Country
7= Music|Electronic
8= Music|Holiday
9= Music|Classical|Opera
10= Music|Singer/Songwriter
11= Music|Jazz
12= Music|Latino
13= Music|New Age
14= Music|Pop
15= Music|R&B/Soul
16= Music|Soundtrack
17= Music|Dance
18= Music|Hip-Hop/Rap
19= Music|World
20= Music|Alternative
21= Music|Rock
22= Music|Christian & Gospel
23= Music|Vocal
24= Music|Reggae
25= Music|Easy Listening
26= Podcasts
27= Music|J-Pop
28= Music|Enka
29= Music|Anime
30= Music|Kayokyoku
31= Music Videos
32= TV Shows
33= Movies
34= Music
35= iPod Games
36= App Store
37= Tones
38= Books
39= Mac App Store
40= Textbooks
50= Music|Fitness & Workout
51= Music|Pop|K-Pop
52= Music|Karaoke
53= Music|Instrumental
74= Audiobooks|News
75= Audiobooks|Programs & Performances
500= Fitness Music
501= Fitness Music|Pop
502= Fitness Music|Dance
503= Fitness Music|Hip-Hop
504= Fitness Music|Rock
505= Fitness Music|Alt/Indie
506= Fitness Music|Latino
507= Fitness Music|Country
508= Fitness Music|World
509= Fitness Music|New Age
510= Fitness Music|Classical
1001= Music|Alternative|College Rock
1002= Music|Alternative|Goth Rock
1003= Music|Alternative|Grunge
1004= Music|Alternative|Indie Rock
1005= Music|Alternative|New Wave
1006= Music|Alternative|Punk
1007= Music|Blues|Chicago Blues
1009= Music|Blues|Classic Blues
1010= Music|Blues|Contemporary Blues
1011= Music|Blues|Country Blues
1012= Music|Blues|Delta Blues
1013= Music|Blues|Electric Blues
1014= Music|Children's Music|Lullabies
1015= Music|Children's Music|Sing-Along
1016= Music|Children's Music|Stories
1017= Music|Classical|Avant-Garde
1018= Music|Classical|Baroque Era
1019= Music|Classical|Chamber Music
1020= Music|Classical|Chant
1021= Music|Classical|Choral
1022= Music|Classical|Classical Crossover
1023= Music|Classical|Early Music
1024= Music|Classical|Impressionist
1025= Music|Classical|Medieval Era
1026= Music|Classical|Minimalism
1027= Music|Classical|Modern Era
1028= Music|Classical|Opera
1029= Music|Classical|Orchestral
1030= Music|Classical|Renaissance
1031= Music|Classical|Romantic Era
1032= Music|Classical|Wedding Music
1033= Music|Country|Alternative Country
1034= Music|Country|Americana
1035= Music|Country|Bluegrass
1036= Music|Country|Contemporary Bluegrass
1037= Music|Country|Contemporary Country
1038= Music|Country|Country Gospel
1039= Music|Country|Honky Tonk
1040= Music|Country|Outlaw Country
1041= Music|Country|Traditional Bluegrass
1042= Music|Country|Traditional Country
1043= Music|Country|Urban Cowboy
1044= Music|Dance|Breakbeat
1045= Music|Dance|Exercise
1046= Music|Dance|Garage
1047= Music|Dance|Hardcore
1048= Music|Dance|House
1049= Music|Dance|Jungle/Drum'n'bass
1050= Music|Dance|Techno
1051= Music|Dance|Trance
1052= Music|Jazz|Big Band
1053= Music|Jazz|Bop
1054= Music|Easy Listening|Lounge
1055= Music|Easy Listening|Swing
1056= Music|Electronic|Ambient
1057= Music|Electronic|Downtempo
1058= Music|Electronic|Electronica
1060= Music|Electronic|IDM/Experimental
1061= Music|Electronic|Industrial
1062= Music|Singer/Songwriter|Alternative Folk
1063= Music|Singer/Songwriter|Contemporary Folk
1064= Music|Singer/Songwriter|Contemporary Singer/Songwriter
1065= Music|Singer/Songwriter|Folk-Rock
1066= Music|Singer/Songwriter|New Acoustic
1067= Music|Singer/Songwriter|Traditional Folk
1068= Music|Hip-Hop/Rap|Alternative Rap
1069= Music|Hip-Hop/Rap|Dirty South
1070= Music|Hip-Hop/Rap|East Coast Rap
1071= Music|Hip-Hop/Rap|Gangsta Rap
1072= Music|Hip-Hop/Rap|Hardcore Rap
1073= Music|Hip-Hop/Rap|Hip-Hop
1074= Music|Hip-Hop/Rap|Latin Rap
1075= Music|Hip-Hop/Rap|Old School Rap
1076= Music|Hip-Hop/Rap|Rap
1077= Music|Hip-Hop/Rap|Underground Rap
1078= Music|Hip-Hop/Rap|West Coast Rap
1079= Music|Holiday|Chanukah
1080= Music|Holiday|Christmas
1081= Music|Holiday|Christmas: Children's
1082= Music|Holiday|Christmas: Classic
1083= Music|Holiday|Christmas: Classical
1084= Music|Holiday|Christmas: Jazz
1085= Music|Holiday|Christmas: Modern
1086= Music|Holiday|Christmas: Pop
1087= Music|Holiday|Christmas: R&B
1088= Music|Holiday|Christmas: Religious
1089= Music|Holiday|Christmas: Rock
1090= Music|Holiday|Easter
1091= Music|Holiday|Halloween
1092= Music|Holiday|Holiday: Other
1093= Music|Holiday|Thanksgiving
1094= Music|Christian & Gospel|CCM
1095= Music|Christian & Gospel|Christian Metal
1096= Music|Christian & Gospel|Christian Pop
1097= Music|Christian & Gospel|Christian Rap
1098= Music|Christian & Gospel|Christian Rock
1099= Music|Christian & Gospel|Classic Christian
1100= Music|Christian & Gospel|Contemporary Gospel
1101= Music|Christian & Gospel|Gospel
1103= Music|Christian & Gospel|Praise & Worship
1104= Music|Christian & Gospel|Southern Gospel
1105= Music|Christian & Gospel|Traditional Gospel
1106= Music|Jazz|Avant-Garde Jazz
1107= Music|Jazz|Contemporary Jazz
1108= Music|Jazz|Crossover Jazz
1109= Music|Jazz|Dixieland
1110= Music|Jazz|Fusion
1111= Music|Jazz|Latin Jazz
1112= Music|Jazz|Mainstream Jazz
1113= Music|Jazz|Ragtime
1114= Music|Jazz|Smooth Jazz
1115= Music|Latino|Latin Jazz
1116= Music|Latino|Contemporary Latin
1117= Music|Latino|Pop Latino
1118= Music|Latino|Raices
1119= Music|Latino|Urbano latino
1120= Music|Latino|Baladas y Boleros
1121= Music|Latino|Rock y Alternativo
1122= Music|Brazilian
1123= Music|Latino|Musica Mexicana
1124= Music|Latino|Musica tropical
1125= Music|New Age|Environmental
1126= Music|New Age|Healing
1127= Music|New Age|Meditation
1128= Music|New Age|Nature
1129= Music|New Age|Relaxation
1130= Music|New Age|Travel
1131= Music|Pop|Adult Contemporary
1132= Music|Pop|Britpop
1133= Music|Pop|Pop/Rock
1134= Music|Pop|Soft Rock
1135= Music|Pop|Teen Pop
1136= Music|R&B/Soul|Contemporary R&B
1137= Music|R&B/Soul|Disco
1138= Music|R&B/Soul|Doo Wop
1139= Music|R&B/Soul|Funk
1140= Music|R&B/Soul|Motown
1141= Music|R&B/Soul|Neo-Soul
1142= Music|R&B/Soul|Quiet Storm
1143= Music|R&B/Soul|Soul
1144= Music|Rock|Adult Alternative
1145= Music|Rock|American Trad Rock
1146= Music|Rock|Arena Rock
1147= Music|Rock|Blues-Rock
1148= Music|Rock|British Invasion
1149= Music|Rock|Death Metal/Black Metal
1150= Music|Rock|Glam Rock
1151= Music|Rock|Hair Metal
1152= Music|Rock|Hard Rock
1153= Music|Rock|Metal
1154= Music|Rock|Jam Bands
1155= Music|Rock|Prog-Rock/Art Rock
1156= Music|Rock|Psychedelic
1157= Music|Rock|Rock & Roll
1158= Music|Rock|Rockabilly
1159= Music|Rock|Roots Rock
1160= Music|Rock|Singer/Songwriter
1161= Music|Rock|Southern Rock
1162= Music|Rock|Surf
1163= Music|Rock|Tex-Mex
1165= Music|Soundtrack|Foreign Cinema
1166= Music|Soundtrack|Musicals
1167= Music|Comedy|Novelty
1168= Music|Soundtrack|Original Score
1169= Music|Soundtrack|Soundtrack
1171= Music|Comedy|Standup Comedy
1172= Music|Soundtrack|TV Soundtrack
1173= Music|Vocal|Standards
1174= Music|Vocal|Traditional Pop
1175= Music|Jazz|Vocal Jazz
1176= Music|Vocal|Vocal Pop
1177= Music|African|Afro-Beat
1178= Music|African|Afro-Pop
1179= Music|World|Cajun
1180= Music|World|Celtic
1181= Music|World|Celtic Folk
1182= Music|World|Contemporary Celtic
1183= Music|Reggae|Modern Dancehall
1184= Music|World|Drinking Songs
1185= Music|Indian|Indian Pop
1186= Music|World|Japanese Pop
1187= Music|World|Klezmer
1188= Music|World|Polka
1189= Music|World|Traditional Celtic
1190= Music|World|Worldbeat
1191= Music|World|Zydeco
1192= Music|Reggae|Roots Reggae
1193= Music|Reggae|Dub
1194= Music|Reggae|Ska
1195= Music|World|Caribbean
1196= Music|World|South America
1197= Music|Arabic
1198= Music|World|North America
1199= Music|World|Hawaii
1200= Music|World|Australia
1201= Music|World|Japan
1202= Music|World|France
1203= Music|African
1204= Music|World|Asia
1205= Music|World|Europe
1206= Music|World|South Africa
1207= Music|Jazz|Hard Bop
1208= Music|Jazz|Trad Jazz
1209= Music|Jazz|Cool Jazz
1210= Music|Blues|Acoustic Blues
1211= Music|Classical|High Classical
1220= Music|Brazilian|Axe
1221= Music|Brazilian|Bossa Nova
1222= Music|Brazilian|Choro
1223= Music|Brazilian|Forro
1224= Music|Brazilian|Frevo
1225= Music|Brazilian|MPB
1226= Music|Brazilian|Pagode
1227= Music|Brazilian|Samba
1228= Music|Brazilian|Sertanejo
1229= Music|Brazilian|Baile Funk
1230= Music|Alternative|Chinese Alt
1231= Music|Alternative|Korean Indie
1232= Music|Chinese
1233= Music|Chinese|Chinese Classical
1234= Music|Chinese|Chinese Flute
1235= Music|Chinese|Chinese Opera
1236= Music|Chinese|Chinese Orchestral
1237= Music|Chinese|Chinese Regional Folk
1238= Music|Chinese|Chinese Strings
1239= Music|Chinese|Taiwanese Folk
1240= Music|Chinese|Tibetan Native Music
1241= Music|Hip-Hop/Rap|Chinese Hip-Hop
1242= Music|Hip-Hop/Rap|Korean Hip-Hop
1243= Music|Korean
1244= Music|Korean|Korean Classical
1245= Music|Korean|Korean Trad Song
1246= Music|Korean|Korean Trad Instrumental
1247= Music|Korean|Korean Trad Theater
1248= Music|Rock|Chinese Rock
1249= Music|Rock|Korean Rock
1250= Music|Pop|C-Pop
1251= Music|Pop|Cantopop/HK-Pop
1252= Music|Pop|Korean Folk-Pop
1253= Music|Pop|Mandopop
1254= Music|Pop|Tai-Pop
1255= Music|Pop|Malaysian Pop
1256= Music|Pop|Pinoy Pop
1257= Music|Pop|Original Pilipino Music
1258= Music|Pop|Manilla Sound
1259= Music|Pop|Indo Pop
1260= Music|Pop|Thai Pop
1261= Music|Vocal|Trot
1262= Music|Indian
1263= Music|Indian|Bollywood
1264= Music|Indian|Regional Indian|Tamil
1265= Music|Indian|Regional Indian|Telugu
1266= Music|Indian|Regional Indian
1267= Music|Indian|Devotional & Spiritual
1268= Music|Indian|Sufi
1269= Music|Indian|Indian Classical
1270= Music|Russian|Russian Chanson
1271= Music|World|Dini
1272= Music|Turkish|Halk
1273= Music|Turkish|Sanat
1274= Music|World|Dangdut
1275= Music|World|Indonesian Religious
1276= Music|World|Calypso
1277= Music|World|Soca
1278= Music|Indian|Ghazals
1279= Music|Indian|Indian Folk
1280= Music|Turkish|Arabesque
1281= Music|African|Afrikaans
1282= Music|World|Farsi
1283= Music|World|Israeli
1284= Music|Arabic|Khaleeji
1285= Music|Arabic|North African
1286= Music|Arabic|Arabic Pop
1287= Music|Arabic|Islamic
1288= Music|Soundtrack|Sound Effects
1289= Music|Folk
1290= Music|Orchestral
1291= Music|Marching
1293= Music|Pop|Oldies
1294= Music|Country|Thai Country
1295= Music|World|Flamenco
1296= Music|World|Tango
1297= Music|World|Fado
1298= Music|World|Iberia
1299= Music|Russian
1300= Music|Turkish
1301= Podcasts|Arts
1302= Podcasts|Society & Culture|Personal Journals
1303= Podcasts|Comedy
1304= Podcasts|Education
1305= Podcasts|Kids & Family
1306= Podcasts|Arts|Food
1307= Podcasts|Health
1309= Podcasts|TV & Film
1310= Podcasts|Music
1311= Podcasts|News & Politics
1314= Podcasts|Religion & Spirituality
1315= Podcasts|Science & Medicine
1316= Podcasts|Sports & Recreation
1318= Podcasts|Technology
1320= Podcasts|Society & Culture|Places & Travel
1321= Podcasts|Business
1323= Podcasts|Games & Hobbies
1324= Podcasts|Society & Culture
1325= Podcasts|Government & Organizations
1337= Music Videos|Classical|Piano
1401= Podcasts|Arts|Literature
1402= Podcasts|Arts|Design
1404= Podcasts|Games & Hobbies|Video Games
1405= Podcasts|Arts|Performing Arts
1406= Podcasts|Arts|Visual Arts
1410= Podcasts|Business|Careers
1412= Podcasts|Business|Investing
1413= Podcasts|Business|Management & Marketing
1415= Podcasts|Education|K-12
1416= Podcasts|Education|Higher Education
1417= Podcasts|Health|Fitness & Nutrition
1420= Podcasts|Health|Self-Help
1421= Podcasts|Health|Sexuality
1438= Podcasts|Religion & Spirituality|Buddhism
1439= Podcasts|Religion & Spirituality|Christianity
1440= Podcasts|Religion & Spirituality|Islam
1441= Podcasts|Religion & Spirituality|Judaism
1443= Podcasts|Society & Culture|Philosophy
1444= Podcasts|Religion & Spirituality|Spirituality
1446= Podcasts|Technology|Gadgets
1448= Podcasts|Technology|Tech News
1450= Podcasts|Technology|Podcasting
1454= Podcasts|Games & Hobbies|Automotive
1455= Podcasts|Games & Hobbies|Aviation
1456= Podcasts|Sports & Recreation|Outdoor
1459= Podcasts|Arts|Fashion & Beauty
1460= Podcasts|Games & Hobbies|Hobbies
1461= Podcasts|Games & Hobbies|Other Games
1462= Podcasts|Society & Culture|History
1463= Podcasts|Religion & Spirituality|Hinduism
1464= Podcasts|Religion & Spirituality|Other
1465= Podcasts|Sports & Recreation|Professional
1466= Podcasts|Sports & Recreation|College & High School
1467= Podcasts|Sports & Recreation|Amateur
1468= Podcasts|Education|Educational Technology
1469= Podcasts|Education|Language Courses
1470= Podcasts|Education|Training
1471= Podcasts|Business|Business News
1472= Podcasts|Business|Shopping
1473= Podcasts|Government & Organizations|National
1474= Podcasts|Government & Organizations|Regional
1475= Podcasts|Government & Organizations|Local
1476= Podcasts|Government & Organizations|Non-Profit
1477= Podcasts|Science & Medicine|Natural Sciences
1478= Podcasts|Science & Medicine|Medicine
1479= Podcasts|Science & Medicine|Social Sciences
1480= Podcasts|Technology|Software How-To
1481= Podcasts|Health|Alternative Health
1482= Podcasts|Arts|Books
1483= Podcasts|Fiction
1484= Podcasts|Fiction|Drama
1485= Podcasts|Fiction|Science Fiction
1486= Podcasts|Fiction|Comedy Fiction
1487= Podcasts|History
1488= Podcasts|True Crime
1489= Podcasts|News
1490= Podcasts|News|Business News
1491= Podcasts|Business|Management
1492= Podcasts|Business|Marketing
1493= Podcasts|Business|Entrepreneurship
1494= Podcasts|Business|Non-Profit
1495= Podcasts|Comedy|Improv
1496= Podcasts|Comedy|Comedy Interviews
1497= Podcasts|Comedy|Stand-Up
1498= Podcasts|Education|Language Learning
1499= Podcasts|Education|How To
1500= Podcasts|Education|Self-Improvement
1501= Podcasts|Education|Courses
1502= Podcasts|Leisure
1503= Podcasts|Leisure|Automotive
1504= Podcasts|Leisure|Aviation
1505= Podcasts|Leisure|Hobbies
1506= Podcasts|Leisure|Crafts
1507= Podcasts|Leisure|Games
1508= Podcasts|Leisure|Home & Garden
1509= Podcasts|Leisure|Video Games
1510= Podcasts|Leisure|Animation & Manga
1511= Podcasts|Government
1512= Podcasts|Health & Fitness
1513= Podcasts|Health & Fitness|Alternative Health
1514= Podcasts|Health & Fitness|Fitness
1515= Podcasts|Health & Fitness|Nutrition
1516= Podcasts|Health & Fitness|Sexuality
1517= Podcasts|Health & Fitness|Mental Health
1518= Podcasts|Health & Fitness|Medicine
1519= Podcasts|Kids & Family|Education for Kids
1520= Podcasts|Kids & Family|Stories for Kids
1521= Podcasts|Kids & Family|Parenting
1522= Podcasts|Kids & Family|Pets & Animals
1523= Podcasts|Music|Music Commentary
1524= Podcasts|Music|Music History
1525= Podcasts|Music|Music Interviews
1526= Podcasts|News|Daily News
1527= Podcasts|News|Politics
1528= Podcasts|News|Tech News
1529= Podcasts|News|Sports News
1530= Podcasts|News|News Commentary
1531= Podcasts|News|Entertainment News
1532= Podcasts|Religion & Spirituality|Religion
1533= Podcasts|Science
1534= Podcasts|Science|Natural Sciences
1535= Podcasts|Science|Social Sciences
1536= Podcasts|Science|Mathematics
1537= Podcasts|Science|Nature
1538= Podcasts|Science|Astronomy
1539= Podcasts|Science|Chemistry
1540= Podcasts|Science|Earth Sciences
1541= Podcasts|Science|Life Sciences
1542= Podcasts|Science|Physics
1543= Podcasts|Society & Culture|Documentary
1544= Podcasts|Society & Culture|Relationships
1545= Podcasts|Sports
1546= Podcasts|Sports|Soccer
1547= Podcasts|Sports|Football
1548= Podcasts|Sports|Basketball
1549= Podcasts|Sports|Baseball
1550= Podcasts|Sports|Hockey
1551= Podcasts|Sports|Running
1552= Podcasts|Sports|Rugby
1553= Podcasts|Sports|Golf
1554= Podcasts|Sports|Cricket
1555= Podcasts|Sports|Wrestling
1556= Podcasts|Sports|Tennis
1557= Podcasts|Sports|Volleyball
1558= Podcasts|Sports|Swimming
1559= Podcasts|Sports|Wilderness
1560= Podcasts|Sports|Fantasy Sports
1561= Podcasts|TV & Film|TV Reviews
1562= Podcasts|TV & Film|After Shows
1563= Podcasts|TV & Film|Film Reviews
1564= Podcasts|TV & Film|Film History
1565= Podcasts|TV & Film|Film Interviews
1602= Music Videos|Blues
1603= Music Videos|Comedy
1604= Music Videos|Children's Music
1605= Music Videos|Classical
1606= Music Videos|Country
1607= Music Videos|Electronic
1608= Music Videos|Holiday
1609= Music Videos|Classical|Opera
1610= Music Videos|Singer/Songwriter
1611= Music Videos|Jazz
1612= Music Videos|Latin
1613= Music Videos|New Age
1614= Music Videos|Pop
1615= Music Videos|R&B/Soul
1616= Music Videos|Soundtrack
1617= Music Videos|Dance
1618= Music Videos|Hip-Hop/Rap
1619= Music Videos|World
1620= Music Videos|Alternative
1621= Music Videos|Rock
1622= Music Videos|Christian & Gospel
1623= Music Videos|Vocal
1624= Music Videos|Reggae
1625= Music Videos|Easy Listening
1626= Music Videos|Podcasts
1627= Music Videos|J-Pop
1628= Music Videos|Enka
1629= Music Videos|Anime
1630= Music Videos|Kayokyoku
1631= Music Videos|Disney
1632= Music Videos|French Pop
1633= Music Videos|German Pop
1634= Music Videos|German Folk
1635= Music Videos|Alternative|Chinese Alt
1636= Music Videos|Alternative|Korean Indie
1637= Music Videos|Chinese
1638= Music Videos|Chinese|Chinese Classical
1639= Music Videos|Chinese|Chinese Flute
1640= Music Videos|Chinese|Chinese Opera
1641= Music Videos|Chinese|Chinese Orchestral
1642= Music Videos|Chinese|Chinese Regional Folk
1643= Music Videos|Chinese|Chinese Strings
1644= Music Videos|Chinese|Taiwanese Folk
1645= Music Videos|Chinese|Tibetan Native Music
1646= Music Videos|Hip-Hop/Rap|Chinese Hip-Hop
1647= Music Videos|Hip-Hop/Rap|Korean Hip-Hop
1648= Music Videos|Korean
1649= Music Videos|Korean|Korean Classical
1650= Music Videos|Korean|Korean Trad Song
1651= Music Videos|Korean|Korean Trad Instrumental
1652= Music Videos|Korean|Korean Trad Theater
1653= Music Videos|Rock|Chinese Rock
1654= Music Videos|Rock|Korean Rock
1655= Music Videos|Pop|C-Pop
1656= Music Videos|Pop|Cantopop/HK-Pop
1657= Music Videos|Pop|Korean Folk-Pop
1658= Music Videos|Pop|Mandopop
1659= Music Videos|Pop|Tai-Pop
1660= Music Videos|Pop|Malaysian Pop
1661= Music Videos|Pop|Pinoy Pop
1662= Music Videos|Pop|Original Pilipino Music
1663= Music Videos|Pop|Manilla Sound
1664= Music Videos|Pop|Indo Pop
1665= Music Videos|Pop|Thai Pop
1666= Music Videos|Vocal|Trot
1671= Music Videos|Brazilian
1672= Music Videos|Brazilian|Axe
1673= Music Videos|Brazilian|Baile Funk
1674= Music Videos|Brazilian|Bossa Nova
1675= Music Videos|Brazilian|Choro
1676= Music Videos|Brazilian|Forro
1677= Music Videos|Brazilian|Frevo
1678= Music Videos|Brazilian|MPB
1679= Music Videos|Brazilian|Pagode
1680= Music Videos|Brazilian|Samba
1681= Music Videos|Brazilian|Sertanejo
1682= Music Videos|Classical|High Classical
1683= Music Videos|Fitness & Workout
1684= Music Videos|Instrumental
1685= Music Videos|Jazz|Big Band
1686= Music Videos|Pop|K-Pop
1687= Music Videos|Karaoke
1688= Music Videos|Rock|Heavy Metal
1689= Music Videos|Spoken Word
1690= Music Videos|Indian
1691= Music Videos|Indian|Bollywood
1692= Music Videos|Indian|Regional Indian|Tamil
1693= Music Videos|Indian|Regional Indian|Telugu
1694= Music Videos|Indian|Regional Indian
1695= Music Videos|Indian|Devotional & Spiritual
1696= Music Videos|Indian|Sufi
1697= Music Videos|Indian|Indian Classical
1698= Music Videos|Russian|Russian Chanson
1699= Music Videos|World|Dini
1700= Music Videos|Turkish|Halk
1701= Music Videos|Turkish|Sanat
1702= Music Videos|World|Dangdut
1703= Music Videos|World|Indonesian Religious
1704= Music Videos|Indian|Indian Pop
1705= Music Videos|World|Calypso
1706= Music Videos|World|Soca
1707= Music Videos|Indian|Ghazals
1708= Music Videos|Indian|Indian Folk
1709= Music Videos|Turkish|Arabesque
1710= Music Videos|African|Afrikaans
1711= Music Videos|World|Farsi
1712= Music Videos|World|Israeli
1713= Music Videos|Arabic
1714= Music Videos|Arabic|Khaleeji
1715= Music Videos|Arabic|North African
1716= Music Videos|Arabic|Arabic Pop
1717= Music Videos|Arabic|Islamic
1718= Music Videos|Soundtrack|Sound Effects
1719= Music Videos|Folk
1720= Music Videos|Orchestral
1721= Music Videos|Marching
1723= Music Videos|Pop|Oldies
1724= Music Videos|Country|Thai Country
1725= Music Videos|World|Flamenco
1726= Music Videos|World|Tango
1727= Music Videos|World|Fado
1728= Music Videos|World|Iberia
1729= Music Videos|Russian
1730= Music Videos|Turkish
1731= Music Videos|Alternative|College Rock
1732= Music Videos|Alternative|Goth Rock
1733= Music Videos|Alternative|Grunge
1734= Music Videos|Alternative|Indie Rock
1735= Music Videos|Alternative|New Wave
1736= Music Videos|Alternative|Punk
1737= Music Videos|Blues|Acoustic Blues
1738= Music Videos|Blues|Chicago Blues
1739= Music Videos|Blues|Classic Blues
1740= Music Videos|Blues|Contemporary Blues
1741= Music Videos|Blues|Country Blues
1742= Music Videos|Blues|Delta Blues
1743= Music Videos|Blues|Electric Blues
1744= Music Videos|Children's Music|Lullabies
1745= Music Videos|Children's Music|Sing-Along
1746= Music Videos|Children's Music|Stories
1747= Music Videos|Christian & Gospel|CCM
1748= Music Videos|Christian & Gospel|Christian Metal
1749= Music Videos|Christian & Gospel|Christian Pop
1750= Music Videos|Christian & Gospel|Christian Rap
1751= Music Videos|Christian & Gospel|Christian Rock
1752= Music Videos|Christian & Gospel|Classic Christian
1753= Music Videos|Christian & Gospel|Contemporary Gospel
1754= Music Videos|Christian & Gospel|Gospel
1755= Music Videos|Christian & Gospel|Praise & Worship
1756= Music Videos|Christian & Gospel|Southern Gospel
1757= Music Videos|Christian & Gospel|Traditional Gospel
1758= Music Videos|Classical|Avant-Garde
1759= Music Videos|Classical|Baroque Era
1760= Music Videos|Classical|Chamber Music
1761= Music Videos|Classical|Chant
1762= Music Videos|Classical|Choral
1763= Music Videos|Classical|Classical Crossover
1764= Music Videos|Classical|Early Music
1765= Music Videos|Classical|Impressionist
1766= Music Videos|Classical|Medieval Era
1767= Music Videos|Classical|Minimalism
1768= Music Videos|Classical|Modern Era
1769= Music Videos|Classical|Orchestral
1770= Music Videos|Classical|Renaissance
1771= Music Videos|Classical|Romantic Era
1772= Music Videos|Classical|Wedding Music
1773= Music Videos|Comedy|Novelty
1774= Music Videos|Comedy|Standup Comedy
1775= Music Videos|Country|Alternative Country
1776= Music Videos|Country|Americana
1777= Music Videos|Country|Bluegrass
1778= Music Videos|Country|Contemporary Bluegrass
1779= Music Videos|Country|Contemporary Country
1780= Music Videos|Country|Country Gospel
1781= Music Videos|Country|Honky Tonk
1782= Music Videos|Country|Outlaw Country
1783= Music Videos|Country|Traditional Bluegrass
1784= Music Videos|Country|Traditional Country
1785= Music Videos|Country|Urban Cowboy
1786= Music Videos|Dance|Breakbeat
1787= Music Videos|Dance|Exercise
1788= Music Videos|Dance|Garage
1789= Music Videos|Dance|Hardcore
1790= Music Videos|Dance|House
1791= Music Videos|Dance|Jungle/Drum'n'bass
1792= Music Videos|Dance|Techno
1793= Music Videos|Dance|Trance
1794= Music Videos|Easy Listening|Lounge
1795= Music Videos|Easy Listening|Swing
1796= Music Videos|Electronic|Ambient
1797= Music Videos|Electronic|Downtempo
1798= Music Videos|Electronic|Electronica
1799= Music Videos|Electronic|IDM/Experimental
1800= Music Videos|Electronic|Industrial
1801= Music Videos|Hip-Hop/Rap|Alternative Rap
1802= Music Videos|Hip-Hop/Rap|Dirty South
1803= Music Videos|Hip-Hop/Rap|East Coast Rap
1804= Music Videos|Hip-Hop/Rap|Gangsta Rap
1805= Music Videos|Hip-Hop/Rap|Hardcore Rap
1806= Music Videos|Hip-Hop/Rap|Hip-Hop
1807= Music Videos|Hip-Hop/Rap|Latin Rap
1808= Music Videos|Hip-Hop/Rap|Old School Rap
1809= Music Videos|Hip-Hop/Rap|Rap
1810= Music Videos|Hip-Hop/Rap|Underground Rap
1811= Music Videos|Hip-Hop/Rap|West Coast Rap
1812= Music Videos|Holiday|Chanukah
1813= Music Videos|Holiday|Christmas
1814= Music Videos|Holiday|Christmas: Children's
1815= Music Videos|Holiday|Christmas: Classic
1816= Music Videos|Holiday|Christmas: Classical
1817= Music Videos|Holiday|Christmas: Jazz
1818= Music Videos|Holiday|Christmas: Modern
1819= Music Videos|Holiday|Christmas: Pop
1820= Music Videos|Holiday|Christmas: R&B
1821= Music Videos|Holiday|Christmas: Religious
1822= Music Videos|Holiday|Christmas: Rock
1823= Music Videos|Holiday|Easter
1824= Music Videos|Holiday|Halloween
1825= Music Videos|Holiday|Thanksgiving
1826= Music Videos|Jazz|Avant-Garde Jazz
1828= Music Videos|Jazz|Bop
1829= Music Videos|Jazz|Contemporary Jazz
1830= Music Videos|Jazz|Cool Jazz
1831= Music Videos|Jazz|Crossover Jazz
1832= Music Videos|Jazz|Dixieland
1833= Music Videos|Jazz|Fusion
1834= Music Videos|Jazz|Hard Bop
1835= Music Videos|Jazz|Latin Jazz
1836= Music Videos|Jazz|Mainstream Jazz
1837= Music Videos|Jazz|Ragtime
1838= Music Videos|Jazz|Smooth Jazz
1839= Music Videos|Jazz|Trad Jazz
1840= Music Videos|Latin|Alternative & Rock in Spanish
1841= Music Videos|Latin|Baladas y Boleros
1842= Music Videos|Latin|Contemporary Latin
1843= Music Videos|Latin|Latin Jazz
1844= Music Videos|Latin|Latin Urban
1845= Music Videos|Latin|Pop in Spanish
1846= Music Videos|Latin|Raices
1847= Music Videos|Latin|Musica Mexicana
1848= Music Videos|Latin|Salsa y Tropical
1849= Music Videos|New Age|Healing
1850= Music Videos|New Age|Meditation
1851= Music Videos|New Age|Nature
1852= Music Videos|New Age|Relaxation
1853= Music Videos|New Age|Travel
1854= Music Videos|Pop|Adult Contemporary
1855= Music Videos|Pop|Britpop
1856= Music Videos|Pop|Pop/Rock
1857= Music Videos|Pop|Soft Rock
1858= Music Videos|Pop|Teen Pop
1859= Music Videos|R&B/Soul|Contemporary R&B
1860= Music Videos|R&B/Soul|Disco
1861= Music Videos|R&B/Soul|Doo Wop
1862= Music Videos|R&B/Soul|Funk
1863= Music Videos|R&B/Soul|Motown
1864= Music Videos|R&B/Soul|Neo-Soul
1865= Music Videos|R&B/Soul|Soul
1866= Music Videos|Reggae|Modern Dancehall
1867= Music Videos|Reggae|Dub
1868= Music Videos|Reggae|Roots Reggae
1869= Music Videos|Reggae|Ska
1870= Music Videos|Rock|Adult Alternative
1871= Music Videos|Rock|American Trad Rock
1872= Music Videos|Rock|Arena Rock
1873= Music Videos|Rock|Blues-Rock
1874= Music Videos|Rock|British Invasion
1875= Music Videos|Rock|Death Metal/Black Metal
1876= Music Videos|Rock|Glam Rock
1877= Music Videos|Rock|Hair Metal
1878= Music Videos|Rock|Hard Rock
1879= Music Videos|Rock|Jam Bands
1880= Music Videos|Rock|Prog-Rock/Art Rock
1881= Music Videos|Rock|Psychedelic
1882= Music Videos|Rock|Rock & Roll
1883= Music Videos|Rock|Rockabilly
1884= Music Videos|Rock|Roots Rock
1885= Music Videos|Rock|Singer/Songwriter
1886= Music Videos|Rock|Southern Rock
1887= Music Videos|Rock|Surf
1888= Music Videos|Rock|Tex-Mex
1889= Music Videos|Singer/Songwriter|Alternative Folk
1890= Music Videos|Singer/Songwriter|Contemporary Folk
1891= Music Videos|Singer/Songwriter|Contemporary Singer/Songwriter
1892= Music Videos|Singer/Songwriter|Folk-Rock
1893= Music Videos|Singer/Songwriter|New Acoustic
1894= Music Videos|Singer/Songwriter|Traditional Folk
1895= Music Videos|Soundtrack|Foreign Cinema
1896= Music Videos|Soundtrack|Musicals
1897= Music Videos|Soundtrack|Original Score
1898= Music Videos|Soundtrack|Soundtrack
1899= Music Videos|Soundtrack|TV Soundtrack
1900= Music Videos|Vocal|Standards
1901= Music Videos|Vocal|Traditional Pop
1902= Music Videos|Jazz|Vocal Jazz
1903= Music Videos|Vocal|Vocal Pop
1904= Music Videos|African
1905= Music Videos|African|Afro-Beat
1906= Music Videos|African|Afro-Pop
1907= Music Videos|World|Asia
1908= Music Videos|World|Australia
1909= Music Videos|World|Cajun
1910= Music Videos|World|Caribbean
1911= Music Videos|World|Celtic
1912= Music Videos|World|Celtic Folk
1913= Music Videos|World|Contemporary Celtic
1914= Music Videos|World|Europe
1915= Music Videos|World|France
1916= Music Videos|World|Hawaii
1917= Music Videos|World|Japan
1918= Music Videos|World|Klezmer
1919= Music Videos|World|North America
1920= Music Videos|World|Polka
1921= Music Videos|World|South Africa
1922= Music Videos|World|South America
1923= Music Videos|World|Traditional Celtic
1924= Music Videos|World|Worldbeat
1925= Music Videos|World|Zydeco
1926= Music Videos|Christian & Gospel
1928= Music Videos|Classical|Art Song
1929= Music Videos|Classical|Brass & Woodwinds
1930= Music Videos|Classical|Solo Instrumental
1931= Music Videos|Classical|Contemporary Era
1932= Music Videos|Classical|Oratorio
1933= Music Videos|Classical|Cantata
1934= Music Videos|Classical|Electronic
1935= Music Videos|Classical|Sacred
1936= Music Videos|Classical|Guitar
1938= Music Videos|Classical|Violin
1939= Music Videos|Classical|Cello
1940= Music Videos|Classical|Percussion
1941= Music Videos|Electronic|Dubstep
1942= Music Videos|Electronic|Bass
1943= Music Videos|Hip-Hop/Rap|UK Hip-Hop
1944= Music Videos|Reggae|Lovers Rock
1945= Music Videos|Alternative|EMO
1946= Music Videos|Alternative|Pop Punk
1947= Music Videos|Alternative|Indie Pop
1948= Music Videos|New Age|Yoga
1949= Music Videos|Pop|Tribute
1950= Music Videos|Pop|Shows
1951= Music Videos|Cuban
1952= Music Videos|Cuban|Mambo
1953= Music Videos|Cuban|Chachacha
1954= Music Videos|Cuban|Guajira
1955= Music Videos|Cuban|Son
1956= Music Videos|Cuban|Bolero
1957= Music Videos|Cuban|Guaracha
1958= Music Videos|Cuban|Timba
1959= Music Videos|Soundtrack|Video Game
1960= Music Videos|Indian|Regional Indian|Punjabi|Punjabi Pop
1961= Music Videos|Indian|Regional Indian|Bengali|Rabindra Sangeet
1962= Music Videos|Indian|Regional Indian|Malayalam
1963= Music Videos|Indian|Regional Indian|Kannada
1964= Music Videos|Indian|Regional Indian|Marathi
1965= Music Videos|Indian|Regional Indian|Gujarati
1966= Music Videos|Indian|Regional Indian|Assamese
1967= Music Videos|Indian|Regional Indian|Bhojpuri
1968= Music Videos|Indian|Regional Indian|Haryanvi
1969= Music Videos|Indian|Regional Indian|Odia
1970= Music Videos|Indian|Regional Indian|Rajasthani
1971= Music Videos|Indian|Regional Indian|Urdu
1972= Music Videos|Indian|Regional Indian|Punjabi
1973= Music Videos|Indian|Regional Indian|Bengali
1974= Music Videos|Indian|Indian Classical|Carnatic Classical
1975= Music Videos|Indian|Indian Classical|Hindustani Classical
1976= Music Videos|African|Afro House
1977= Music Videos|African|Afro Soul
1978= Music Videos|African|Afrobeats
1979= Music Videos|African|Benga
1980= Music Videos|African|Bongo-Flava
1981= Music Videos|African|Coupe-Decale
1982= Music Videos|African|Gqom
1983= Music Videos|African|Highlife
1984= Music Videos|African|Kuduro
1985= Music Videos|African|Kizomba
1986= Music Videos|African|Kwaito
1987= Music Videos|African|Mbalax
1988= Music Videos|African|Ndombolo
1989= Music Videos|African|Shangaan Electro
1990= Music Videos|African|Soukous
1991= Music Videos|African|Taarab
1992= Music Videos|African|Zouglou
1993= Music Videos|Turkish|Ozgun
1994= Music Videos|Turkish|Fantezi
1995= Music Videos|Turkish|Religious
1996= Music Videos|Pop|Turkish Pop
1997= Music Videos|Rock|Turkish Rock
1998= Music Videos|Alternative|Turkish Alternative
1999= Music Videos|Hip-Hop/Rap|Turkish Hip-Hop/Rap
2000= Music Videos|African|Maskandi
2001= Music Videos|Russian|Russian Romance
2002= Music Videos|Russian|Russian Bard
2003= Music Videos|Russian|Russian Pop
2004= Music Videos|Russian|Russian Rock
2005= Music Videos|Russian|Russian Hip-Hop
2006= Music Videos|Arabic|Levant
2007= Music Videos|Arabic|Levant|Dabke
2008= Music Videos|Arabic|Maghreb Rai
2009= Music Videos|Arabic|Khaleeji|Khaleeji Jalsat
2010= Music Videos|Arabic|Khaleeji|Khaleeji Shailat
2011= Music Videos|Tarab
2012= Music Videos|Tarab|Iraqi Tarab
2013= Music Videos|Tarab|Egyptian Tarab
2014= Music Videos|Tarab|Khaleeji Tarab
2015= Music Videos|Pop|Levant Pop
2016= Music Videos|Pop|Iraqi Pop
2017= Music Videos|Pop|Egyptian Pop
2018= Music Videos|Pop|Maghreb Pop
2019= Music Videos|Pop|Khaleeji Pop
2020= Music Videos|Hip-Hop/Rap|Levant Hip-Hop
2021= Music Videos|Hip-Hop/Rap|Egyptian Hip-Hop
2022= Music Videos|Hip-Hop/Rap|Maghreb Hip-Hop
2023= Music Videos|Hip-Hop/Rap|Khaleeji Hip-Hop
2024= Music Videos|Alternative|Indie Levant
2025= Music Videos|Alternative|Indie Egyptian
2026= Music Videos|Alternative|Indie Maghreb
2027= Music Videos|Electronic|Levant Electronic
2028= Music Videos|Electronic|Electro-Cha'abi
2029= Music Videos|Electronic|Maghreb Electronic
2030= Music Videos|Folk|Iraqi Folk
2031= Music Videos|Folk|Khaleeji Folk
2032= Music Videos|Dance|Maghreb Dance
4000= TV Shows|Comedy
4001= TV Shows|Drama
4002= TV Shows|Animation
4003= TV Shows|Action & Adventure
4004= TV Shows|Classics
4005= TV Shows|Kids & Family
4006= TV Shows|Nonfiction
4007= TV Shows|Reality TV
4008= TV Shows|Sci-Fi & Fantasy
4009= TV Shows|Sports
4010= TV Shows|Teens
4011= TV Shows|Latino TV
4401= Movies|Action & Adventure
4402= Movies|Anime
4403= Movies|Classics
4404= Movies|Comedy
4405= Movies|Documentary
4406= Movies|Drama
4407= Movies|Foreign
4408= Movies|Horror
4409= Movies|Independent
4410= Movies|Kids & Family
4411= Movies|Musicals
4412= Movies|Romance
4413= Movies|Sci-Fi & Fantasy
4414= Movies|Short Films
4415= Movies|Special Interest
4416= Movies|Thriller
4417= Movies|Sports
4418= Movies|Western
4419= Movies|Urban
4420= Movies|Holiday
4421= Movies|Made for TV
4422= Movies|Concert Films
4423= Movies|Music Documentaries
4424= Movies|Music Feature Films
4425= Movies|Japanese Cinema
4426= Movies|Jidaigeki
4427= Movies|Tokusatsu
4428= Movies|Korean Cinema
4429= Movies|Russian
4430= Movies|Turkish
4431= Movies|Bollywood
4432= Movies|Regional Indian
4433= Movies|Middle Eastern
4434= Movies|African
6000= App Store|Business
6001= App Store|Weather
6002= App Store|Utilities
6003= App Store|Travel
6004= App Store|Sports
6005= App Store|Social Networking
6006= App Store|Reference
6007= App Store|Productivity
6008= App Store|Photo & Video
6009= App Store|News
6010= App Store|Navigation
6011= App Store|Music
6012= App Store|Lifestyle
6013= App Store|Health & Fitness
6014= App Store|Games
6015= App Store|Finance
6016= App Store|Entertainment
6017= App Store|Education
6018= App Store|Books
6020= App Store|Medical
6021= App Store|Magazines & Newspapers
6022= App Store|Catalogs
6023= App Store|Food & Drink
6024= App Store|Shopping
6025= App Store|Stickers
6026= App Store|Developer Tools
6027= App Store|Graphics & Design
7001= App Store|Games|Action
7002= App Store|Games|Adventure
7003= App Store|Games|Casual
7004= App Store|Games|Board
7005= App Store|Games|Card
7006= App Store|Games|Casino
7007= App Store|Games|Dice
7008= App Store|Games|Educational
7009= App Store|Games|Family
7011= App Store|Games|Music
7012= App Store|Games|Puzzle
7013= App Store|Games|Racing
7014= App Store|Games|Role Playing
7015= App Store|Games|Simulation
7016= App Store|Games|Sports
7017= App Store|Games|Strategy
7018= App Store|Games|Trivia
7019= App Store|Games|Word
8001= Tones|Ringtones|Alternative
8002= Tones|Ringtones|Blues
8003= Tones|Ringtones|Children's Music
8004= Tones|Ringtones|Classical
8005= Tones|Ringtones|Comedy
8006= Tones|Ringtones|Country
8007= Tones|Ringtones|Dance
8008= Tones|Ringtones|Electronic
8009= Tones|Ringtones|Enka
8010= Tones|Ringtones|French Pop
8011= Tones|Ringtones|German Folk
8012= Tones|Ringtones|German Pop
8013= Tones|Ringtones|Hip-Hop/Rap
8014= Tones|Ringtones|Holiday
8015= Tones|Ringtones|Inspirational
8016= Tones|Ringtones|J-Pop
8017= Tones|Ringtones|Jazz
8018= Tones|Ringtones|Kayokyoku
8019= Tones|Ringtones|Latin
8020= Tones|Ringtones|New Age
8021= Tones|Ringtones|Classical|Opera
8022= Tones|Ringtones|Pop
8023= Tones|Ringtones|R&B/Soul
8024= Tones|Ringtones|Reggae
8025= Tones|Ringtones|Rock
8026= Tones|Ringtones|Singer/Songwriter
8027= Tones|Ringtones|Soundtrack
8028= Tones|Ringtones|Spoken Word
8029= Tones|Ringtones|Vocal
8030= Tones|Ringtones|World
8050= Tones|Alert Tones|Sound Effects
8051= Tones|Alert Tones|Dialogue
8052= Tones|Alert Tones|Music
8053= Tones|Ringtones
8054= Tones|Alert Tones
8055= Tones|Ringtones|Alternative|Chinese Alt
8056= Tones|Ringtones|Alternative|College Rock
8057= Tones|Ringtones|Alternative|Goth Rock
8058= Tones|Ringtones|Alternative|Grunge
8059= Tones|Ringtones|Alternative|Indie Rock
8060= Tones|Ringtones|Alternative|Korean Indie
8061= Tones|Ringtones|Alternative|New Wave
8062= Tones|Ringtones|Alternative|Punk
8063= Tones|Ringtones|Anime
8064= Tones|Ringtones|Arabic
8065= Tones|Ringtones|Arabic|Arabic Pop
8066= Tones|Ringtones|Arabic|Islamic
8067= Tones|Ringtones|Arabic|Khaleeji
8068= Tones|Ringtones|Arabic|North African
8069= Tones|Ringtones|Blues|Acoustic Blues
8070= Tones|Ringtones|Blues|Chicago Blues
8071= Tones|Ringtones|Blues|Classic Blues
8072= Tones|Ringtones|Blues|Contemporary Blues
8073= Tones|Ringtones|Blues|Country Blues
8074= Tones|Ringtones|Blues|Delta Blues
8075= Tones|Ringtones|Blues|Electric Blues
8076= Tones|Ringtones|Brazilian
8077= Tones|Ringtones|Brazilian|Axe
8078= Tones|Ringtones|Brazilian|Baile Funk
8079= Tones|Ringtones|Brazilian|Bossa Nova
8080= Tones|Ringtones|Brazilian|Choro
8081= Tones|Ringtones|Brazilian|Forro
8082= Tones|Ringtones|Brazilian|Frevo
8083= Tones|Ringtones|Brazilian|MPB
8084= Tones|Ringtones|Brazilian|Pagode
8085= Tones|Ringtones|Brazilian|Samba
8086= Tones|Ringtones|Brazilian|Sertanejo
8087= Tones|Ringtones|Children's Music|Lullabies
8088= Tones|Ringtones|Children's Music|Sing-Along
8089= Tones|Ringtones|Children's Music|Stories
8090= Tones|Ringtones|Chinese
8091= Tones|Ringtones|Chinese|Chinese Classical
8092= Tones|Ringtones|Chinese|Chinese Flute
8093= Tones|Ringtones|Chinese|Chinese Opera
8094= Tones|Ringtones|Chinese|Chinese Orchestral
8095= Tones|Ringtones|Chinese|Chinese Regional Folk
8096= Tones|Ringtones|Chinese|Chinese Strings
8097= Tones|Ringtones|Chinese|Taiwanese Folk
8098= Tones|Ringtones|Chinese|Tibetan Native Music
8099= Tones|Ringtones|Christian & Gospel
8100= Tones|Ringtones|Christian & Gospel|CCM
8101= Tones|Ringtones|Christian & Gospel|Christian Metal
8102= Tones|Ringtones|Christian & Gospel|Christian Pop
8103= Tones|Ringtones|Christian & Gospel|Christian Rap
8104= Tones|Ringtones|Christian & Gospel|Christian Rock
8105= Tones|Ringtones|Christian & Gospel|Classic Christian
8106= Tones|Ringtones|Christian & Gospel|Contemporary Gospel
8107= Tones|Ringtones|Christian & Gospel|Gospel
8108= Tones|Ringtones|Christian & Gospel|Praise & Worship
8109= Tones|Ringtones|Christian & Gospel|Southern Gospel
8110= Tones|Ringtones|Christian & Gospel|Traditional Gospel
8111= Tones|Ringtones|Classical|Avant-Garde
8112= Tones|Ringtones|Classical|Baroque Era
8113= Tones|Ringtones|Classical|Chamber Music
8114= Tones|Ringtones|Classical|Chant
8115= Tones|Ringtones|Classical|Choral
8116= Tones|Ringtones|Classical|Classical Crossover
8117= Tones|Ringtones|Classical|Early Music
8118= Tones|Ringtones|Classical|High Classical
8119= Tones|Ringtones|Classical|Impressionist
8120= Tones|Ringtones|Classical|Medieval Era
8121= Tones|Ringtones|Classical|Minimalism
8122= Tones|Ringtones|Classical|Modern Era
8123= Tones|Ringtones|Classical|Orchestral
8124= Tones|Ringtones|Classical|Renaissance
8125= Tones|Ringtones|Classical|Romantic Era
8126= Tones|Ringtones|Classical|Wedding Music
8127= Tones|Ringtones|Comedy|Novelty
8128= Tones|Ringtones|Comedy|Standup Comedy
8129= Tones|Ringtones|Country|Alternative Country
8130= Tones|Ringtones|Country|Americana
8131= Tones|Ringtones|Country|Bluegrass
8132= Tones|Ringtones|Country|Contemporary Bluegrass
8133= Tones|Ringtones|Country|Contemporary Country
8134= Tones|Ringtones|Country|Country Gospel
8135= Tones|Ringtones|Country|Honky Tonk
8136= Tones|Ringtones|Country|Outlaw Country
8137= Tones|Ringtones|Country|Thai Country
8138= Tones|Ringtones|Country|Traditional Bluegrass
8139= Tones|Ringtones|Country|Traditional Country
8140= Tones|Ringtones|Country|Urban Cowboy
8141= Tones|Ringtones|Dance|Breakbeat
8142= Tones|Ringtones|Dance|Exercise
8143= Tones|Ringtones|Dance|Garage
8144= Tones|Ringtones|Dance|Hardcore
8145= Tones|Ringtones|Dance|House
8146= Tones|Ringtones|Dance|Jungle/Drum'n'bass
8147= Tones|Ringtones|Dance|Techno
8148= Tones|Ringtones|Dance|Trance
8149= Tones|Ringtones|Disney
8150= Tones|Ringtones|Easy Listening
8151= Tones|Ringtones|Easy Listening|Lounge
8152= Tones|Ringtones|Easy Listening|Swing
8153= Tones|Ringtones|Electronic|Ambient
8154= Tones|Ringtones|Electronic|Downtempo
8155= Tones|Ringtones|Electronic|Electronica
8156= Tones|Ringtones|Electronic|IDM/Experimental
8157= Tones|Ringtones|Electronic|Industrial
8158= Tones|Ringtones|Fitness & Workout
8159= Tones|Ringtones|Folk
8160= Tones|Ringtones|Hip-Hop/Rap|Alternative Rap
8161= Tones|Ringtones|Hip-Hop/Rap|Chinese Hip-Hop
8162= Tones|Ringtones|Hip-Hop/Rap|Dirty South
8163= Tones|Ringtones|Hip-Hop/Rap|East Coast Rap
8164= Tones|Ringtones|Hip-Hop/Rap|Gangsta Rap
8165= Tones|Ringtones|Hip-Hop/Rap|Hardcore Rap
8166= Tones|Ringtones|Hip-Hop/Rap|Hip-Hop
8167= Tones|Ringtones|Hip-Hop/Rap|Korean Hip-Hop
8168= Tones|Ringtones|Hip-Hop/Rap|Latin Rap
8169= Tones|Ringtones|Hip-Hop/Rap|Old School Rap
8170= Tones|Ringtones|Hip-Hop/Rap|Rap
8171= Tones|Ringtones|Hip-Hop/Rap|Underground Rap
8172= Tones|Ringtones|Hip-Hop/Rap|West Coast Rap
8173= Tones|Ringtones|Holiday|Chanukah
8174= Tones|Ringtones|Holiday|Christmas
8175= Tones|Ringtones|Holiday|Christmas: Children's
8176= Tones|Ringtones|Holiday|Christmas: Classic
8177= Tones|Ringtones|Holiday|Christmas: Classical
8178= Tones|Ringtones|Holiday|Christmas: Jazz
8179= Tones|Ringtones|Holiday|Christmas: Modern
8180= Tones|Ringtones|Holiday|Christmas: Pop
8181= Tones|Ringtones|Holiday|Christmas: R&B
8182= Tones|Ringtones|Holiday|Christmas: Religious
8183= Tones|Ringtones|Holiday|Christmas: Rock
8184= Tones|Ringtones|Holiday|Easter
8185= Tones|Ringtones|Holiday|Halloween
8186= Tones|Ringtones|Holiday|Thanksgiving
8187= Tones|Ringtones|Indian
8188= Tones|Ringtones|Indian|Bollywood
8189= Tones|Ringtones|Indian|Devotional & Spiritual
8190= Tones|Ringtones|Indian|Ghazals
8191= Tones|Ringtones|Indian|Indian Classical
8192= Tones|Ringtones|Indian|Indian Folk
8193= Tones|Ringtones|Indian|Indian Pop
8194= Tones|Ringtones|Indian|Regional Indian
8195= Tones|Ringtones|Indian|Sufi
8196= Tones|Ringtones|Indian|Regional Indian|Tamil
8197= Tones|Ringtones|Indian|Regional Indian|Telugu
8198= Tones|Ringtones|Instrumental
8199= Tones|Ringtones|Jazz|Avant-Garde Jazz
8201= Tones|Ringtones|Jazz|Big Band
8202= Tones|Ringtones|Jazz|Bop
8203= Tones|Ringtones|Jazz|Contemporary Jazz
8204= Tones|Ringtones|Jazz|Cool Jazz
8205= Tones|Ringtones|Jazz|Crossover Jazz
8206= Tones|Ringtones|Jazz|Dixieland
8207= Tones|Ringtones|Jazz|Fusion
8208= Tones|Ringtones|Jazz|Hard Bop
8209= Tones|Ringtones|Jazz|Latin Jazz
8210= Tones|Ringtones|Jazz|Mainstream Jazz
8211= Tones|Ringtones|Jazz|Ragtime
8212= Tones|Ringtones|Jazz|Smooth Jazz
8213= Tones|Ringtones|Jazz|Trad Jazz
8214= Tones|Ringtones|Pop|K-Pop
8215= Tones|Ringtones|Karaoke
8216= Tones|Ringtones|Korean
8217= Tones|Ringtones|Korean|Korean Classical
8218= Tones|Ringtones|Korean|Korean Trad Instrumental
8219= Tones|Ringtones|Korean|Korean Trad Song
8220= Tones|Ringtones|Korean|Korean Trad Theater
8221= Tones|Ringtones|Latin|Alternative & Rock in Spanish
8222= Tones|Ringtones|Latin|Baladas y Boleros
8223= Tones|Ringtones|Latin|Contemporary Latin
8224= Tones|Ringtones|Latin|Latin Jazz
8225= Tones|Ringtones|Latin|Latin Urban
8226= Tones|Ringtones|Latin|Pop in Spanish
8227= Tones|Ringtones|Latin|Raices
8228= Tones|Ringtones|Latin|Musica Mexicana
8229= Tones|Ringtones|Latin|Salsa y Tropical
8230= Tones|Ringtones|Marching Bands
8231= Tones|Ringtones|New Age|Healing
8232= Tones|Ringtones|New Age|Meditation
8233= Tones|Ringtones|New Age|Nature
8234= Tones|Ringtones|New Age|Relaxation
8235= Tones|Ringtones|New Age|Travel
8236= Tones|Ringtones|Orchestral
8237= Tones|Ringtones|Pop|Adult Contemporary
8238= Tones|Ringtones|Pop|Britpop
8239= Tones|Ringtones|Pop|C-Pop
8240= Tones|Ringtones|Pop|Cantopop/HK-Pop
8241= Tones|Ringtones|Pop|Indo Pop
8242= Tones|Ringtones|Pop|Korean Folk-Pop
8243= Tones|Ringtones|Pop|Malaysian Pop
8244= Tones|Ringtones|Pop|Mandopop
8245= Tones|Ringtones|Pop|Manilla Sound
8246= Tones|Ringtones|Pop|Oldies
8247= Tones|Ringtones|Pop|Original Pilipino Music
8248= Tones|Ringtones|Pop|Pinoy Pop
8249= Tones|Ringtones|Pop|Pop/Rock
8250= Tones|Ringtones|Pop|Soft Rock
8251= Tones|Ringtones|Pop|Tai-Pop
8252= Tones|Ringtones|Pop|Teen Pop
8253= Tones|Ringtones|Pop|Thai Pop
8254= Tones|Ringtones|R&B/Soul|Contemporary R&B
8255= Tones|Ringtones|R&B/Soul|Disco
8256= Tones|Ringtones|R&B/Soul|Doo Wop
8257= Tones|Ringtones|R&B/Soul|Funk
8258= Tones|Ringtones|R&B/Soul|Motown
8259= Tones|Ringtones|R&B/Soul|Neo-Soul
8260= Tones|Ringtones|R&B/Soul|Soul
8261= Tones|Ringtones|Reggae|Modern Dancehall
8262= Tones|Ringtones|Reggae|Dub
8263= Tones|Ringtones|Reggae|Roots Reggae
8264= Tones|Ringtones|Reggae|Ska
8265= Tones|Ringtones|Rock|Adult Alternative
8266= Tones|Ringtones|Rock|American Trad Rock
8267= Tones|Ringtones|Rock|Arena Rock
8268= Tones|Ringtones|Rock|Blues-Rock
8269= Tones|Ringtones|Rock|British Invasion
8270= Tones|Ringtones|Rock|Chinese Rock
8271= Tones|Ringtones|Rock|Death Metal/Black Metal
8272= Tones|Ringtones|Rock|Glam Rock
8273= Tones|Ringtones|Rock|Hair Metal
8274= Tones|Ringtones|Rock|Hard Rock
8275= Tones|Ringtones|Rock|Metal
8276= Tones|Ringtones|Rock|Jam Bands
8277= Tones|Ringtones|Rock|Korean Rock
8278= Tones|Ringtones|Rock|Prog-Rock/Art Rock
8279= Tones|Ringtones|Rock|Psychedelic
8280= Tones|Ringtones|Rock|Rock & Roll
8281= Tones|Ringtones|Rock|Rockabilly
8282= Tones|Ringtones|Rock|Roots Rock
8283= Tones|Ringtones|Rock|Singer/Songwriter
8284= Tones|Ringtones|Rock|Southern Rock
8285= Tones|Ringtones|Rock|Surf
8286= Tones|Ringtones|Rock|Tex-Mex
8287= Tones|Ringtones|Singer/Songwriter|Alternative Folk
8288= Tones|Ringtones|Singer/Songwriter|Contemporary Folk
8289= Tones|Ringtones|Singer/Songwriter|Contemporary Singer/Songwriter
8290= Tones|Ringtones|Singer/Songwriter|Folk-Rock
8291= Tones|Ringtones|Singer/Songwriter|New Acoustic
8292= Tones|Ringtones|Singer/Songwriter|Traditional Folk
8293= Tones|Ringtones|Soundtrack|Foreign Cinema
8294= Tones|Ringtones|Soundtrack|Musicals
8295= Tones|Ringtones|Soundtrack|Original Score
8296= Tones|Ringtones|Soundtrack|Sound Effects
8297= Tones|Ringtones|Soundtrack|Soundtrack
8298= Tones|Ringtones|Soundtrack|TV Soundtrack
8299= Tones|Ringtones|Vocal|Standards
8300= Tones|Ringtones|Vocal|Traditional Pop
8301= Tones|Ringtones|Vocal|Trot
8302= Tones|Ringtones|Jazz|Vocal Jazz
8303= Tones|Ringtones|Vocal|Vocal Pop
8304= Tones|Ringtones|African
8305= Tones|Ringtones|African|Afrikaans
8306= Tones|Ringtones|African|Afro-Beat
8307= Tones|Ringtones|African|Afro-Pop
8308= Tones|Ringtones|Turkish|Arabesque
8309= Tones|Ringtones|World|Asia
8310= Tones|Ringtones|World|Australia
8311= Tones|Ringtones|World|Cajun
8312= Tones|Ringtones|World|Calypso
8313= Tones|Ringtones|World|Caribbean
8314= Tones|Ringtones|World|Celtic
8315= Tones|Ringtones|World|Celtic Folk
8316= Tones|Ringtones|World|Contemporary Celtic
8317= Tones|Ringtones|World|Dangdut
8318= Tones|Ringtones|World|Dini
8319= Tones|Ringtones|World|Europe
8320= Tones|Ringtones|World|Fado
8321= Tones|Ringtones|World|Farsi
8322= Tones|Ringtones|World|Flamenco
8323= Tones|Ringtones|World|France
8324= Tones|Ringtones|Turkish|Halk
8325= Tones|Ringtones|World|Hawaii
8326= Tones|Ringtones|World|Iberia
8327= Tones|Ringtones|World|Indonesian Religious
8328= Tones|Ringtones|World|Israeli
8329= Tones|Ringtones|World|Japan
8330= Tones|Ringtones|World|Klezmer
8331= Tones|Ringtones|World|North America
8332= Tones|Ringtones|World|Polka
8333= Tones|Ringtones|Russian
8334= Tones|Ringtones|Russian|Russian Chanson
8335= Tones|Ringtones|Turkish|Sanat
8336= Tones|Ringtones|World|Soca
8337= Tones|Ringtones|World|South Africa
8338= Tones|Ringtones|World|South America
8339= Tones|Ringtones|World|Tango
8340= Tones|Ringtones|World|Traditional Celtic
8341= Tones|Ringtones|Turkish
8342= Tones|Ringtones|World|Worldbeat
8343= Tones|Ringtones|World|Zydeco
8345= Tones|Ringtones|Classical|Art Song
8346= Tones|Ringtones|Classical|Brass & Woodwinds
8347= Tones|Ringtones|Classical|Solo Instrumental
8348= Tones|Ringtones|Classical|Contemporary Era
8349= Tones|Ringtones|Classical|Oratorio
8350= Tones|Ringtones|Classical|Cantata
8351= Tones|Ringtones|Classical|Electronic
8352= Tones|Ringtones|Classical|Sacred
8353= Tones|Ringtones|Classical|Guitar
8354= Tones|Ringtones|Classical|Piano
8355= Tones|Ringtones|Classical|Violin
8356= Tones|Ringtones|Classical|Cello
8357= Tones|Ringtones|Classical|Percussion
8358= Tones|Ringtones|Electronic|Dubstep
8359= Tones|Ringtones|Electronic|Bass
8360= Tones|Ringtones|Hip-Hop/Rap|UK Hip Hop
8361= Tones|Ringtones|Reggae|Lovers Rock
8362= Tones|Ringtones|Alternative|EMO
8363= Tones|Ringtones|Alternative|Pop Punk
8364= Tones|Ringtones|Alternative|Indie Pop
8365= Tones|Ringtones|New Age|Yoga
8366= Tones|Ringtones|Pop|Tribute
8367= Tones|Ringtones|Pop|Shows
8368= Tones|Ringtones|Cuban
8369= Tones|Ringtones|Cuban|Mambo
8370= Tones|Ringtones|Cuban|Chachacha
8371= Tones|Ringtones|Cuban|Guajira
8372= Tones|Ringtones|Cuban|Son
8373= Tones|Ringtones|Cuban|Bolero
8374= Tones|Ringtones|Cuban|Guaracha
8375= Tones|Ringtones|Cuban|Timba
8376= Tones|Ringtones|Soundtrack|Video Game
8377= Tones|Ringtones|Indian|Regional Indian|Punjabi|Punjabi Pop
8378= Tones|Ringtones|Indian|Regional Indian|Bengali|Rabindra Sangeet
8379= Tones|Ringtones|Indian|Regional Indian|Malayalam
8380= Tones|Ringtones|Indian|Regional Indian|Kannada
8381= Tones|Ringtones|Indian|Regional Indian|Marathi
8382= Tones|Ringtones|Indian|Regional Indian|Gujarati
8383= Tones|Ringtones|Indian|Regional Indian|Assamese
8384= Tones|Ringtones|Indian|Regional Indian|Bhojpuri
8385= Tones|Ringtones|Indian|Regional Indian|Haryanvi
8386= Tones|Ringtones|Indian|Regional Indian|Odia
8387= Tones|Ringtones|Indian|Regional Indian|Rajasthani
8388= Tones|Ringtones|Indian|Regional Indian|Urdu
8389= Tones|Ringtones|Indian|Regional Indian|Punjabi
8390= Tones|Ringtones|Indian|Regional Indian|Bengali
8391= Tones|Ringtones|Indian|Indian Classical|Carnatic Classical
8392= Tones|Ringtones|Indian|Indian Classical|Hindustani Classical
8393= Tones|Ringtones|African|Afro House
8394= Tones|Ringtones|African|Afro Soul
8395= Tones|Ringtones|African|Afrobeats
8396= Tones|Ringtones|African|Benga
8397= Tones|Ringtones|African|Bongo-Flava
8398= Tones|Ringtones|African|Coupe-Decale
8399= Tones|Ringtones|African|Gqom
8400= Tones|Ringtones|African|Highlife
8401= Tones|Ringtones|African|Kuduro
8402= Tones|Ringtones|African|Kizomba
8403= Tones|Ringtones|African|Kwaito
8404= Tones|Ringtones|African|Mbalax
8405= Tones|Ringtones|African|Ndombolo
8406= Tones|Ringtones|African|Shangaan Electro
8407= Tones|Ringtones|African|Soukous
8408= Tones|Ringtones|African|Taarab
8409= Tones|Ringtones|African|Zouglou
8410= Tones|Ringtones|Turkish|Ozgun
8411= Tones|Ringtones|Turkish|Fantezi
8412= Tones|Ringtones|Turkish|Religious
8413= Tones|Ringtones|Pop|Turkish Pop
8414= Tones|Ringtones|Rock|Turkish Rock
8415= Tones|Ringtones|Alternative|Turkish Alternative
8416= Tones|Ringtones|Hip-Hop/Rap|Turkish Hip-Hop/Rap
8417= Tones|Ringtones|African|Maskandi
8418= Tones|Ringtones|Russian|Russian Romance
8419= Tones|Ringtones|Russian|Russian Bard
8420= Tones|Ringtones|Russian|Russian Pop
8421= Tones|Ringtones|Russian|Russian Rock
8422= Tones|Ringtones|Russian|Russian Hip-Hop
8423= Tones|Ringtones|Arabic|Levant
8424= Tones|Ringtones|Arabic|Levant|Dabke
8425= Tones|Ringtones|Arabic|Maghreb Rai
8426= Tones|Ringtones|Arabic|Khaleeji|Khaleeji Jalsat
8427= Tones|Ringtones|Arabic|Khaleeji|Khaleeji Shailat
8428= Tones|Ringtones|Tarab
8429= Tones|Ringtones|Tarab|Iraqi Tarab
8430= Tones|Ringtones|Tarab|Egyptian Tarab
8431= Tones|Ringtones|Tarab|Khaleeji Tarab
8432= Tones|Ringtones|Pop|Levant Pop
8433= Tones|Ringtones|Pop|Iraqi Pop
8434= Tones|Ringtones|Pop|Egyptian Pop
8435= Tones|Ringtones|Pop|Maghreb Pop
8436= Tones|Ringtones|Pop|Khaleeji Pop
8437= Tones|Ringtones|Hip-Hop/Rap|Levant Hip-Hop
8438= Tones|Ringtones|Hip-Hop/Rap|Egyptian Hip-Hop
8439= Tones|Ringtones|Hip-Hop/Rap|Maghreb Hip-Hop
8440= Tones|Ringtones|Hip-Hop/Rap|Khaleeji Hip-Hop
8441= Tones|Ringtones|Alternative|Indie Levant
8442= Tones|Ringtones|Alternative|Indie Egyptian
8443= Tones|Ringtones|Alternative|Indie Maghreb
8444= Tones|Ringtones|Electronic|Levant Electronic
8445= Tones|Ringtones|Electronic|Electro-Cha'abi
8446= Tones|Ringtones|Electronic|Maghreb Electronic
8447= Tones|Ringtones|Folk|Iraqi Folk
8448= Tones|Ringtones|Folk|Khaleeji Folk
8449= Tones|Ringtones|Dance|Maghreb Dance
9002= Books|Nonfiction
9003= Books|Romance
9004= Books|Travel & Adventure
9007= Books|Arts & Entertainment
9008= Books|Biographies & Memoirs
9009= Books|Business & Personal Finance
9010= Books|Children & Teens
9012= Books|Humor
9015= Books|History
9018= Books|Religion & Spirituality
9019= Books|Science & Nature
9020= Books|Sci-Fi & Fantasy
9024= Books|Lifestyle & Home
9025= Books|Self-Development
9026= Books|Comics & Graphic Novels
9027= Books|Computers & Internet
9028= Books|Cookbooks, Food & Wine
9029= Books|Professional & Technical
9030= Books|Parenting
9031= Books|Fiction & Literature
9032= Books|Mysteries & Thrillers
9033= Books|Reference
9034= Books|Politics & Current Events
9035= Books|Sports & Outdoors
10001= Books|Lifestyle & Home|Antiques & Collectibles
10002= Books|Arts & Entertainment|Art & Architecture
10003= Books|Religion & Spirituality|Bibles
10004= Books|Self-Development|Spirituality
10005= Books|Business & Personal Finance|Industries & Professions
10006= Books|Business & Personal Finance|Marketing & Sales
10007= Books|Business & Personal Finance|Small Business & Entrepreneurship
10008= Books|Business & Personal Finance|Personal Finance
10009= Books|Business & Personal Finance|Reference
10010= Books|Business & Personal Finance|Careers
10011= Books|Business & Personal Finance|Economics
10012= Books|Business & Personal Finance|Investing
10013= Books|Business & Personal Finance|Finance
10014= Books|Business & Personal Finance|Management & Leadership
10015= Books|Comics & Graphic Novels|Graphic Novels
10016= Books|Comics & Graphic Novels|Manga
10017= Books|Computers & Internet|Computers
10018= Books|Computers & Internet|Databases
10019= Books|Computers & Internet|Digital Media
10020= Books|Computers & Internet|Internet
10021= Books|Computers & Internet|Network
10022= Books|Computers & Internet|Operating Systems
10023= Books|Computers & Internet|Programming
10024= Books|Computers & Internet|Software
10025= Books|Computers & Internet|System Administration
10026= Books|Cookbooks, Food & Wine|Beverages
10027= Books|Cookbooks, Food & Wine|Courses & Dishes
10028= Books|Cookbooks, Food & Wine|Special Diet
10029= Books|Cookbooks, Food & Wine|Special Occasions
10030= Books|Cookbooks, Food & Wine|Methods
10031= Books|Cookbooks, Food & Wine|Reference
10032= Books|Cookbooks, Food & Wine|Regional & Ethnic
10033= Books|Cookbooks, Food & Wine|Specific Ingredients
10034= Books|Lifestyle & Home|Crafts & Hobbies
10035= Books|Professional & Technical|Design
10036= Books|Arts & Entertainment|Theater
10037= Books|Professional & Technical|Education
10038= Books|Nonfiction|Family & Relationships
10039= Books|Fiction & Literature|Action & Adventure
10040= Books|Fiction & Literature|African American
10041= Books|Fiction & Literature|Religious
10042= Books|Fiction & Literature|Classics
10043= Books|Fiction & Literature|Erotica
10044= Books|Sci-Fi & Fantasy|Fantasy
10045= Books|Fiction & Literature|Gay
10046= Books|Fiction & Literature|Ghost
10047= Books|Fiction & Literature|Historical
10048= Books|Fiction & Literature|Horror
10049= Books|Fiction & Literature|Literary
10050= Books|Mysteries & Thrillers|Hard-Boiled
10051= Books|Mysteries & Thrillers|Historical
10052= Books|Mysteries & Thrillers|Police Procedural
10053= Books|Mysteries & Thrillers|Short Stories
10054= Books|Mysteries & Thrillers|British Detectives
10055= Books|Mysteries & Thrillers|Women Sleuths
10056= Books|Romance|Erotic Romance
10057= Books|Romance|Contemporary
10058= Books|Romance|Paranormal
10059= Books|Romance|Historical
10060= Books|Romance|Short Stories
10061= Books|Romance|Suspense
10062= Books|Romance|Western
10063= Books|Sci-Fi & Fantasy|Science Fiction
10064= Books|Sci-Fi & Fantasy|Science Fiction & Literature
10065= Books|Fiction & Literature|Short Stories
10066= Books|Reference|Foreign Languages
10067= Books|Arts & Entertainment|Games
10068= Books|Lifestyle & Home|Gardening
10069= Books|Self-Development|Health & Fitness
10070= Books|History|Africa
10071= Books|History|Americas
10072= Books|History|Ancient
10073= Books|History|Asia
10074= Books|History|Australia & Oceania
10075= Books|History|Europe
10076= Books|History|Latin America
10077= Books|History|Middle East
10078= Books|History|Military
10079= Books|History|United States
10080= Books|History|World
10081= Books|Children & Teens|Children's Fiction
10082= Books|Children & Teens|Children's Nonfiction
10083= Books|Professional & Technical|Law
10084= Books|Fiction & Literature|Literary Criticism
10085= Books|Science & Nature|Mathematics
10086= Books|Professional & Technical|Medical
10087= Books|Arts & Entertainment|Music
10088= Books|Science & Nature|Nature
10089= Books|Arts & Entertainment|Performing Arts
10090= Books|Lifestyle & Home|Pets
10091= Books|Nonfiction|Philosophy
10092= Books|Arts & Entertainment|Photography
10093= Books|Fiction & Literature|Poetry
10094= Books|Self-Development|Psychology
10095= Books|Reference|Almanacs & Yearbooks
10096= Books|Reference|Atlases & Maps
10097= Books|Reference|Catalogs & Directories
10098= Books|Reference|Consumer Guides
10099= Books|Reference|Dictionaries & Thesauruses
10100= Books|Reference|Encyclopedias
10101= Books|Reference|Etiquette
10102= Books|Reference|Quotations
10103= Books|Reference|Words & Language
10104= Books|Reference|Writing
10105= Books|Religion & Spirituality|Bible Studies
10106= Books|Religion & Spirituality|Buddhism
10107= Books|Religion & Spirituality|Christianity
10108= Books|Religion & Spirituality|Hinduism
10109= Books|Religion & Spirituality|Islam
10110= Books|Religion & Spirituality|Judaism
10111= Books|Science & Nature|Astronomy
10112= Books|Science & Nature|Chemistry
10113= Books|Science & Nature|Earth Sciences
10114= Books|Science & Nature|Essays
10115= Books|Science & Nature|History
10116= Books|Science & Nature|Life Sciences
10117= Books|Science & Nature|Physics
10118= Books|Science & Nature|Reference
10119= Books|Self-Development|Self-Improvement
10120= Books|Nonfiction|Social Science
10121= Books|Sports & Outdoors|Baseball
10122= Books|Sports & Outdoors|Basketball
10123= Books|Sports & Outdoors|Coaching
10124= Books|Sports & Outdoors|Extreme Sports
10125= Books|Sports & Outdoors|Football
10126= Books|Sports & Outdoors|Golf
10127= Books|Sports & Outdoors|Hockey
10128= Books|Sports & Outdoors|Mountaineering
10129= Books|Sports & Outdoors|Outdoors
10130= Books|Sports & Outdoors|Racket Sports
10131= Books|Sports & Outdoors|Reference
10132= Books|Sports & Outdoors|Soccer
10133= Books|Sports & Outdoors|Training
10134= Books|Sports & Outdoors|Water Sports
10135= Books|Sports & Outdoors|Winter Sports
10136= Books|Reference|Study Aids
10137= Books|Professional & Technical|Engineering
10138= Books|Nonfiction|Transportation
10139= Books|Travel & Adventure|Africa
10140= Books|Travel & Adventure|Asia
10141= Books|Travel & Adventure|Specialty Travel
10142= Books|Travel & Adventure|Canada
10143= Books|Travel & Adventure|Caribbean
10144= Books|Travel & Adventure|Latin America
10145= Books|Travel & Adventure|Essays & Memoirs
10146= Books|Travel & Adventure|Europe
10147= Books|Travel & Adventure|Middle East
10148= Books|Travel & Adventure|United States
10149= Books|Nonfiction|True Crime
11001= Books|Sci-Fi & Fantasy|Fantasy|Contemporary
11002= Books|Sci-Fi & Fantasy|Fantasy|Epic
11003= Books|Sci-Fi & Fantasy|Fantasy|Historical
11004= Books|Sci-Fi & Fantasy|Fantasy|Paranormal
11005= Books|Sci-Fi & Fantasy|Fantasy|Short Stories
11006= Books|Sci-Fi & Fantasy|Science Fiction & Literature|Adventure
11007= Books|Sci-Fi & Fantasy|Science Fiction & Literature|High Tech
11008= Books|Sci-Fi & Fantasy|Science Fiction & Literature|Short Stories
11009= Books|Professional & Technical|Education|Language Arts & Disciplines
11010= Books|Communications & Media
11011= Books|Communications & Media|Broadcasting
11012= Books|Communications & Media|Digital Media
11013= Books|Communications & Media|Journalism
11014= Books|Communications & Media|Photojournalism
11015= Books|Communications & Media|Print
11016= Books|Communications & Media|Speech
11017= Books|Communications & Media|Writing
11018= Books|Arts & Entertainment|Art & Architecture|Urban Planning
11019= Books|Arts & Entertainment|Dance
11020= Books|Arts & Entertainment|Fashion
11021= Books|Arts & Entertainment|Film
11022= Books|Arts & Entertainment|Interior Design
11023= Books|Arts & Entertainment|Media Arts
11024= Books|Arts & Entertainment|Radio
11025= Books|Arts & Entertainment|TV
11026= Books|Arts & Entertainment|Visual Arts
11027= Books|Biographies & Memoirs|Arts & Entertainment
11028= Books|Biographies & Memoirs|Business
11029= Books|Biographies & Memoirs|Culinary
11030= Books|Biographies & Memoirs|Gay & Lesbian
11031= Books|Biographies & Memoirs|Historical
11032= Books|Biographies & Memoirs|Literary
11033= Books|Biographies & Memoirs|Media & Journalism
11034= Books|Biographies & Memoirs|Military
11035= Books|Biographies & Memoirs|Politics
11036= Books|Biographies & Memoirs|Religious
11037= Books|Biographies & Memoirs|Science & Technology
11038= Books|Biographies & Memoirs|Sports
11039= Books|Biographies & Memoirs|Women
11040= Books|Romance|New Adult
11042= Books|Romance|Romantic Comedy
11043= Books|Romance|Gay & Lesbian
11044= Books|Fiction & Literature|Essays
11045= Books|Fiction & Literature|Anthologies
11046= Books|Fiction & Literature|Comparative Literature
11047= Books|Fiction & Literature|Drama
11049= Books|Fiction & Literature|Fairy Tales, Myths & Fables
11050= Books|Fiction & Literature|Family
11051= Books|Comics & Graphic Novels|Manga|School Drama
11052= Books|Comics & Graphic Novels|Manga|Human Drama
11053= Books|Comics & Graphic Novels|Manga|Family Drama
11054= Books|Sports & Outdoors|Boxing
11055= Books|Sports & Outdoors|Cricket
11056= Books|Sports & Outdoors|Cycling
11057= Books|Sports & Outdoors|Equestrian
11058= Books|Sports & Outdoors|Martial Arts & Self Defense
11059= Books|Sports & Outdoors|Motor Sports
11060= Books|Sports & Outdoors|Rugby
11061= Books|Sports & Outdoors|Running
11062= Books|Self-Development|Diet & Nutrition
11063= Books|Science & Nature|Agriculture
11064= Books|Science & Nature|Atmosphere
11065= Books|Science & Nature|Biology
11066= Books|Science & Nature|Ecology
11067= Books|Science & Nature|Environment
11068= Books|Science & Nature|Geography
11069= Books|Science & Nature|Geology
11070= Books|Nonfiction|Social Science|Anthropology
11071= Books|Nonfiction|Social Science|Archaeology
11072= Books|Nonfiction|Social Science|Civics
11073= Books|Nonfiction|Social Science|Government
11074= Books|Nonfiction|Social Science|Social Studies
11075= Books|Nonfiction|Social Science|Social Welfare
11076= Books|Nonfiction|Social Science|Society
11077= Books|Nonfiction|Philosophy|Aesthetics
11078= Books|Nonfiction|Philosophy|Epistemology
11079= Books|Nonfiction|Philosophy|Ethics
11080= Books|Nonfiction|Philosophy|Language
11081= Books|Nonfiction|Philosophy|Logic
11082= Books|Nonfiction|Philosophy|Metaphysics
11083= Books|Nonfiction|Philosophy|Political
11084= Books|Nonfiction|Philosophy|Religion
11085= Books|Reference|Manuals
11086= Books|Kids
11087= Books|Kids|Animals
11088= Books|Kids|Basic Concepts
11089= Books|Kids|Basic Concepts|Alphabet
11090= Books|Kids|Basic Concepts|Body
11091= Books|Kids|Basic Concepts|Colors
11092= Books|Kids|Basic Concepts|Counting & Numbers
11093= Books|Kids|Basic Concepts|Date & Time
11094= Books|Kids|Basic Concepts|General
11095= Books|Kids|Basic Concepts|Money
11096= Books|Kids|Basic Concepts|Opposites
11097= Books|Kids|Basic Concepts|Seasons
11098= Books|Kids|Basic Concepts|Senses & Sensation
11099= Books|Kids|Basic Concepts|Size & Shape
11100= Books|Kids|Basic Concepts|Sounds
11101= Books|Kids|Basic Concepts|Words
11102= Books|Kids|Biography
11103= Books|Kids|Careers & Occupations
11104= Books|Kids|Computers & Technology
11105= Books|Kids|Cooking & Food
11106= Books|Kids|Arts & Entertainment
11107= Books|Kids|Arts & Entertainment|Art
11108= Books|Kids|Arts & Entertainment|Crafts
11109= Books|Kids|Arts & Entertainment|Music
11110= Books|Kids|Arts & Entertainment|Performing Arts
11111= Books|Kids|Family
11112= Books|Kids|Fiction
11113= Books|Kids|Fiction|Action & Adventure
11114= Books|Kids|Fiction|Animals
11115= Books|Kids|Fiction|Classics
11116= Books|Kids|Fiction|Comics & Graphic Novels
11117= Books|Kids|Fiction|Culture, Places & People
11118= Books|Kids|Fiction|Family & Relationships
11119= Books|Kids|Fiction|Fantasy
11120= Books|Kids|Fiction|Fairy Tales, Myths & Fables
11121= Books|Kids|Fiction|Favorite Characters
11122= Books|Kids|Fiction|Historical
11123= Books|Kids|Fiction|Holidays & Celebrations
11124= Books|Kids|Fiction|Monsters & Ghosts
11125= Books|Kids|Fiction|Mysteries
11126= Books|Kids|Fiction|Nature
11127= Books|Kids|Fiction|Religion
11128= Books|Kids|Fiction|Sci-Fi
11129= Books|Kids|Fiction|Social Issues
11130= Books|Kids|Fiction|Sports & Recreation
11131= Books|Kids|Fiction|Transportation
11132= Books|Kids|Games & Activities
11133= Books|Kids|General Nonfiction
11134= Books|Kids|Health
11135= Books|Kids|History
11136= Books|Kids|Holidays & Celebrations
11137= Books|Kids|Holidays & Celebrations|Birthdays
11138= Books|Kids|Holidays & Celebrations|Christmas & Advent
11139= Books|Kids|Holidays & Celebrations|Easter & Lent
11140= Books|Kids|Holidays & Celebrations|General
11141= Books|Kids|Holidays & Celebrations|Halloween
11142= Books|Kids|Holidays & Celebrations|Hanukkah
11143= Books|Kids|Holidays & Celebrations|Other
11144= Books|Kids|Holidays & Celebrations|Passover
11145= Books|Kids|Holidays & Celebrations|Patriotic Holidays
11146= Books|Kids|Holidays & Celebrations|Ramadan
11147= Books|Kids|Holidays & Celebrations|Thanksgiving
11148= Books|Kids|Holidays & Celebrations|Valentine's Day
11149= Books|Kids|Humor
11150= Books|Kids|Humor|Jokes & Riddles
11151= Books|Kids|Poetry
11152= Books|Kids|Learning to Read
11153= Books|Kids|Learning to Read|Chapter Books
11154= Books|Kids|Learning to Read|Early Readers
11155= Books|Kids|Learning to Read|Intermediate Readers
11156= Books|Kids|Nursery Rhymes
11157= Books|Kids|Government
11158= Books|Kids|Reference
11159= Books|Kids|Religion
11160= Books|Kids|Science & Nature
11161= Books|Kids|Social Issues
11162= Books|Kids|Social Studies
11163= Books|Kids|Sports & Recreation
11164= Books|Kids|Transportation
11165= Books|Young Adult
11166= Books|Young Adult|Animals
11167= Books|Young Adult|Biography
11168= Books|Young Adult|Careers & Occupations
11169= Books|Young Adult|Computers & Technology
11170= Books|Young Adult|Cooking & Food
11171= Books|Young Adult|Arts & Entertainment
11172= Books|Young Adult|Arts & Entertainment|Art
11173= Books|Young Adult|Arts & Entertainment|Crafts
11174= Books|Young Adult|Arts & Entertainment|Music
11175= Books|Young Adult|Arts & Entertainment|Performing Arts
11176= Books|Young Adult|Family
11177= Books|Young Adult|Fiction
11178= Books|Young Adult|Fiction|Action & Adventure
11179= Books|Young Adult|Fiction|Animals
11180= Books|Young Adult|Fiction|Classics
11181= Books|Young Adult|Fiction|Comics & Graphic Novels
11182= Books|Young Adult|Fiction|Culture, Places & People
11183= Books|Young Adult|Fiction|Dystopian
11184= Books|Young Adult|Fiction|Family & Relationships
11185= Books|Young Adult|Fiction|Fantasy
11186= Books|Young Adult|Fiction|Fairy Tales, Myths & Fables
11187= Books|Young Adult|Fiction|Favorite Characters
11188= Books|Young Adult|Fiction|Historical
11189= Books|Young Adult|Fiction|Holidays & Celebrations
11190= Books|Young Adult|Fiction|Horror, Monsters & Ghosts
11191= Books|Young Adult|Fiction|Crime & Mystery
11192= Books|Young Adult|Fiction|Nature
11193= Books|Young Adult|Fiction|Religion
11194= Books|Young Adult|Fiction|Romance
11195= Books|Young Adult|Fiction|Sci-Fi
11196= Books|Young Adult|Fiction|Coming of Age
11197= Books|Young Adult|Fiction|Sports & Recreation
11198= Books|Young Adult|Fiction|Transportation
11199= Books|Young Adult|Games & Activities
11200= Books|Young Adult|General Nonfiction
11201= Books|Young Adult|Health
11202= Books|Young Adult|History
11203= Books|Young Adult|Holidays & Celebrations
11204= Books|Young Adult|Holidays & Celebrations|Birthdays
11205= Books|Young Adult|Holidays & Celebrations|Christmas & Advent
11206= Books|Young Adult|Holidays & Celebrations|Easter & Lent
11207= Books|Young Adult|Holidays & Celebrations|General
11208= Books|Young Adult|Holidays & Celebrations|Halloween
11209= Books|Young Adult|Holidays & Celebrations|Hanukkah
11210= Books|Young Adult|Holidays & Celebrations|Other
11211= Books|Young Adult|Holidays & Celebrations|Passover
11212= Books|Young Adult|Holidays & Celebrations|Patriotic Holidays
11213= Books|Young Adult|Holidays & Celebrations|Ramadan
11214= Books|Young Adult|Holidays & Celebrations|Thanksgiving
11215= Books|Young Adult|Holidays & Celebrations|Valentine's Day
11216= Books|Young Adult|Humor
11217= Books|Young Adult|Humor|Jokes & Riddles
11218= Books|Young Adult|Poetry
11219= Books|Young Adult|Politics & Government
11220= Books|Young Adult|Reference
11221= Books|Young Adult|Religion
11222= Books|Young Adult|Science & Nature
11223= Books|Young Adult|Coming of Age
11224= Books|Young Adult|Social Studies
11225= Books|Young Adult|Sports & Recreation
11226= Books|Young Adult|Transportation
11227= Books|Communications & Media
11228= Books|Military & Warfare
11229= Books|Romance|Inspirational
11231= Books|Romance|Holiday
11232= Books|Romance|Wholesome
11233= Books|Romance|Military
11234= Books|Arts & Entertainment|Art History
11236= Books|Arts & Entertainment|Design
11243= Books|Business & Personal Finance|Accounting
11244= Books|Business & Personal Finance|Hospitality
11245= Books|Business & Personal Finance|Real Estate
11246= Books|Humor|Jokes & Riddles
11247= Books|Religion & Spirituality|Comparative Religion
11255= Books|Cookbooks, Food & Wine|Culinary Arts
11259= Books|Mysteries & Thrillers|Cozy
11260= Books|Politics & Current Events|Current Events
11261= Books|Politics & Current Events|Foreign Policy & International Relations
11262= Books|Politics & Current Events|Local Government
11263= Books|Politics & Current Events|National Government
11264= Books|Politics & Current Events|Political Science
11265= Books|Politics & Current Events|Public Administration
11266= Books|Politics & Current Events|World Affairs
11273= Books|Nonfiction|Family & Relationships|Family & Childcare
11274= Books|Nonfiction|Family & Relationships|Love & Romance
11275= Books|Sci-Fi & Fantasy|Fantasy|Urban
11276= Books|Reference|Foreign Languages|Arabic
11277= Books|Reference|Foreign Languages|Bilingual Editions
11278= Books|Reference|Foreign Languages|African Languages
11279= Books|Reference|Foreign Languages|Ancient Languages
11280= Books|Reference|Foreign Languages|Chinese
11281= Books|Reference|Foreign Languages|English
11282= Books|Reference|Foreign Languages|French
11283= Books|Reference|Foreign Languages|German
11284= Books|Reference|Foreign Languages|Hebrew
11285= Books|Reference|Foreign Languages|Hindi
11286= Books|Reference|Foreign Languages|Italian
11287= Books|Reference|Foreign Languages|Japanese
11288= Books|Reference|Foreign Languages|Korean
11289= Books|Reference|Foreign Languages|Linguistics
11290= Books|Reference|Foreign Languages|Other Languages
11291= Books|Reference|Foreign Languages|Portuguese
11292= Books|Reference|Foreign Languages|Russian
11293= Books|Reference|Foreign Languages|Spanish
11294= Books|Reference|Foreign Languages|Speech Pathology
11295= Books|Science & Nature|Mathematics|Advanced Mathematics
11296= Books|Science & Nature|Mathematics|Algebra
11297= Books|Science & Nature|Mathematics|Arithmetic
11298= Books|Science & Nature|Mathematics|Calculus
11299= Books|Science & Nature|Mathematics|Geometry
11300= Books|Science & Nature|Mathematics|Statistics
11301= Books|Professional & Technical|Medical|Veterinary
11302= Books|Professional & Technical|Medical|Neuroscience
11303= Books|Professional & Technical|Medical|Immunology
11304= Books|Professional & Technical|Medical|Nursing
11305= Books|Professional & Technical|Medical|Pharmacology & Toxicology
11306= Books|Professional & Technical|Medical|Anatomy & Physiology
11307= Books|Professional & Technical|Medical|Dentistry
11308= Books|Professional & Technical|Medical|Emergency Medicine
11309= Books|Professional & Technical|Medical|Genetics
11310= Books|Professional & Technical|Medical|Psychiatry
11311= Books|Professional & Technical|Medical|Radiology
11312= Books|Professional & Technical|Medical|Alternative Medicine
11317= Books|Nonfiction|Philosophy|Political Philosophy
11319= Books|Nonfiction|Philosophy|Philosophy of Language
11320= Books|Nonfiction|Philosophy|Philosophy of Religion
11327= Books|Nonfiction|Social Science|Sociology
11329= Books|Professional & Technical|Engineering|Aeronautics
11330= Books|Professional & Technical|Engineering|Chemical & Petroleum Engineering
11331= Books|Professional & Technical|Engineering|Civil Engineering
11332= Books|Professional & Technical|Engineering|Computer Science
11333= Books|Professional & Technical|Engineering|Electrical Engineering
11334= Books|Professional & Technical|Engineering|Environmental Engineering
11335= Books|Professional & Technical|Engineering|Mechanical Engineering
11336= Books|Professional & Technical|Engineering|Power Resources
11337= Books|Comics & Graphic Novels|Manga|Boys
11338= Books|Comics & Graphic Novels|Manga|Men
11339= Books|Comics & Graphic Novels|Manga|Girls
11340= Books|Comics & Graphic Novels|Manga|Women
11341= Books|Comics & Graphic Novels|Manga|Other
11342= Books|Comics & Graphic Novels|Manga|Yaoi
11343= Books|Comics & Graphic Novels|Manga|Comic Essays
12001= Mac App Store|Business
12002= Mac App Store|Developer Tools
12003= Mac App Store|Education
12004= Mac App Store|Entertainment
12005= Mac App Store|Finance
12006= Mac App Store|Games
12007= Mac App Store|Health & Fitness
12008= Mac App Store|Lifestyle
12010= Mac App Store|Medical
12011= Mac App Store|Music
12012= Mac App Store|News
12013= Mac App Store|Photography
12014= Mac App Store|Productivity
12015= Mac App Store|Reference
12016= Mac App Store|Social Networking
12017= Mac App Store|Sports
12018= Mac App Store|Travel
12019= Mac App Store|Utilities
12020= Mac App Store|Video
12021= Mac App Store|Weather
12022= Mac App Store|Graphics & Design
12201= Mac App Store|Games|Action
12202= Mac App Store|Games|Adventure
12203= Mac App Store|Games|Casual
12204= Mac App Store|Games|Board
12205= Mac App Store|Games|Card
12206= Mac App Store|Games|Casino
12207= Mac App Store|Games|Dice
12208= Mac App Store|Games|Educational
12209= Mac App Store|Games|Family
12210= Mac App Store|Games|Kids
12211= Mac App Store|Games|Music
12212= Mac App Store|Games|Puzzle
12213= Mac App Store|Games|Racing
12214= Mac App Store|Games|Role Playing
12215= Mac App Store|Games|Simulation
12216= Mac App Store|Games|Sports
12217= Mac App Store|Games|Strategy
12218= Mac App Store|Games|Trivia
12219= Mac App Store|Games|Word
13001= App Store|Magazines & Newspapers|News & Politics
13002= App Store|Magazines & Newspapers|Fashion & Style
13003= App Store|Magazines & Newspapers|Home & Garden
13004= App Store|Magazines & Newspapers|Outdoors & Nature
13005= App Store|Magazines & Newspapers|Sports & Leisure
13006= App Store|Magazines & Newspapers|Automotive
13007= App Store|Magazines & Newspapers|Arts & Photography
13008= App Store|Magazines & Newspapers|Brides & Weddings
13009= App Store|Magazines & Newspapers|Business & Investing
13010= App Store|Magazines & Newspapers|Children's Magazines
13011= App Store|Magazines & Newspapers|Computers & Internet
13012= App Store|Magazines & Newspapers|Cooking, Food & Drink
13013= App Store|Magazines & Newspapers|Crafts & Hobbies
13014= App Store|Magazines & Newspapers|Electronics & Audio
13015= App Store|Magazines & Newspapers|Entertainment
13017= App Store|Magazines & Newspapers|Health, Mind & Body
13018= App Store|Magazines & Newspapers|History
13019= App Store|Magazines & Newspapers|Literary Magazines & Journals
13020= App Store|Magazines & Newspapers|Men's Interest
13021= App Store|Magazines & Newspapers|Movies & Music
13023= App Store|Magazines & Newspapers|Parenting & Family
13024= App Store|Magazines & Newspapers|Pets
13025= App Store|Magazines & Newspapers|Professional & Trade
13026= App Store|Magazines & Newspapers|Regional News
13027= App Store|Magazines & Newspapers|Science
13028= App Store|Magazines & Newspapers|Teens
13029= App Store|Magazines & Newspapers|Travel & Regional
13030= App Store|Magazines & Newspapers|Women's Interest
15000= Textbooks|Arts & Entertainment
15001= Textbooks|Arts & Entertainment|Art & Architecture
15002= Textbooks|Arts & Entertainment|Art & Architecture|Urban Planning
15003= Textbooks|Arts & Entertainment|Art History
15004= Textbooks|Arts & Entertainment|Dance
15005= Textbooks|Arts & Entertainment|Design
15006= Textbooks|Arts & Entertainment|Fashion
15007= Textbooks|Arts & Entertainment|Film
15008= Textbooks|Arts & Entertainment|Games
15009= Textbooks|Arts & Entertainment|Interior Design
15010= Textbooks|Arts & Entertainment|Media Arts
15011= Textbooks|Arts & Entertainment|Music
15012= Textbooks|Arts & Entertainment|Performing Arts
15013= Textbooks|Arts & Entertainment|Photography
15014= Textbooks|Arts & Entertainment|Theater
15015= Textbooks|Arts & Entertainment|TV
15016= Textbooks|Arts & Entertainment|Visual Arts
15017= Textbooks|Biographies & Memoirs
15018= Textbooks|Business & Personal Finance
15019= Textbooks|Business & Personal Finance|Accounting
15020= Textbooks|Business & Personal Finance|Careers
15021= Textbooks|Business & Personal Finance|Economics
15022= Textbooks|Business & Personal Finance|Finance
15023= Textbooks|Business & Personal Finance|Hospitality
15024= Textbooks|Business & Personal Finance|Industries & Professions
15025= Textbooks|Business & Personal Finance|Investing
15026= Textbooks|Business & Personal Finance|Management & Leadership
15027= Textbooks|Business & Personal Finance|Marketing & Sales
15028= Textbooks|Business & Personal Finance|Personal Finance
15029= Textbooks|Business & Personal Finance|Real Estate
15030= Textbooks|Business & Personal Finance|Reference
15031= Textbooks|Business & Personal Finance|Small Business & Entrepreneurship
15032= Textbooks|Children & Teens
15033= Textbooks|Children & Teens|Fiction
15034= Textbooks|Children & Teens|Nonfiction
15035= Textbooks|Comics & Graphic Novels
15036= Textbooks|Comics & Graphic Novels|Graphic Novels
15037= Textbooks|Comics & Graphic Novels|Manga
15038= Textbooks|Communications & Media
15039= Textbooks|Communications & Media|Broadcasting
15040= Textbooks|Communications & Media|Digital Media
15041= Textbooks|Communications & Media|Journalism
15042= Textbooks|Communications & Media|Photojournalism
15043= Textbooks|Communications & Media|Print
15044= Textbooks|Communications & Media|Speech
15045= Textbooks|Communications & Media|Writing
15046= Textbooks|Computers & Internet
15047= Textbooks|Computers & Internet|Computers
15048= Textbooks|Computers & Internet|Databases
15049= Textbooks|Computers & Internet|Digital Media
15050= Textbooks|Computers & Internet|Internet
15051= Textbooks|Computers & Internet|Network
15052= Textbooks|Computers & Internet|Operating Systems
15053= Textbooks|Computers & Internet|Programming
15054= Textbooks|Computers & Internet|Software
15055= Textbooks|Computers & Internet|System Administration
15056= Textbooks|Cookbooks, Food & Wine
15057= Textbooks|Cookbooks, Food & Wine|Beverages
15058= Textbooks|Cookbooks, Food & Wine|Courses & Dishes
15059= Textbooks|Cookbooks, Food & Wine|Culinary Arts
15060= Textbooks|Cookbooks, Food & Wine|Methods
15061= Textbooks|Cookbooks, Food & Wine|Reference
15062= Textbooks|Cookbooks, Food & Wine|Regional & Ethnic
15063= Textbooks|Cookbooks, Food & Wine|Special Diet
15064= Textbooks|Cookbooks, Food & Wine|Special Occasions
15065= Textbooks|Cookbooks, Food & Wine|Specific Ingredients
15066= Textbooks|Engineering
15067= Textbooks|Engineering|Aeronautics
15068= Textbooks|Engineering|Chemical & Petroleum Engineering
15069= Textbooks|Engineering|Civil Engineering
15070= Textbooks|Engineering|Computer Science
15071= Textbooks|Engineering|Electrical Engineering
15072= Textbooks|Engineering|Environmental Engineering
15073= Textbooks|Engineering|Mechanical Engineering
15074= Textbooks|Engineering|Power Resources
15075= Textbooks|Fiction & Literature
15076= Textbooks|Fiction & Literature|Latino
15077= Textbooks|Fiction & Literature|Action & Adventure
15078= Textbooks|Fiction & Literature|African American
15079= Textbooks|Fiction & Literature|Anthologies
15080= Textbooks|Fiction & Literature|Classics
15081= Textbooks|Fiction & Literature|Comparative Literature
15082= Textbooks|Fiction & Literature|Erotica
15083= Textbooks|Fiction & Literature|Gay
15084= Textbooks|Fiction & Literature|Ghost
15085= Textbooks|Fiction & Literature|Historical
15086= Textbooks|Fiction & Literature|Horror
15087= Textbooks|Fiction & Literature|Literary
15088= Textbooks|Fiction & Literature|Literary Criticism
15089= Textbooks|Fiction & Literature|Poetry
15090= Textbooks|Fiction & Literature|Religious
15091= Textbooks|Fiction & Literature|Short Stories
15092= Textbooks|Health, Mind & Body
15093= Textbooks|Health, Mind & Body|Fitness
15094= Textbooks|Health, Mind & Body|Self-Improvement
15095= Textbooks|History
15096= Textbooks|History|Africa
15097= Textbooks|History|Americas
15098= Textbooks|History|Americas|Canada
15099= Textbooks|History|Americas|Latin America
15100= Textbooks|History|Americas|United States
15101= Textbooks|History|Ancient
15102= Textbooks|History|Asia
15103= Textbooks|History|Australia & Oceania
15104= Textbooks|History|Europe
15105= Textbooks|History|Middle East
15106= Textbooks|History|Military
15107= Textbooks|History|World
15108= Textbooks|Humor
15109= Textbooks|Language Studies
15110= Textbooks|Language Studies|African Languages
15111= Textbooks|Language Studies|Ancient Languages
15112= Textbooks|Language Studies|Arabic
15113= Textbooks|Language Studies|Bilingual Editions
15114= Textbooks|Language Studies|Chinese
15115= Textbooks|Language Studies|English
15116= Textbooks|Language Studies|French
15117= Textbooks|Language Studies|German
15118= Textbooks|Language Studies|Hebrew
15119= Textbooks|Language Studies|Hindi
15120= Textbooks|Language Studies|Indigenous Languages
15121= Textbooks|Language Studies|Italian
15122= Textbooks|Language Studies|Japanese
15123= Textbooks|Language Studies|Korean
15124= Textbooks|Language Studies|Linguistics
15125= Textbooks|Language Studies|Other Language
15126= Textbooks|Language Studies|Portuguese
15127= Textbooks|Language Studies|Russian
15128= Textbooks|Language Studies|Spanish
15129= Textbooks|Language Studies|Speech Pathology
15130= Textbooks|Lifestyle & Home
15131= Textbooks|Lifestyle & Home|Antiques & Collectibles
15132= Textbooks|Lifestyle & Home|Crafts & Hobbies
15133= Textbooks|Lifestyle & Home|Gardening
15134= Textbooks|Lifestyle & Home|Pets
15135= Textbooks|Mathematics
15136= Textbooks|Mathematics|Advanced Mathematics
15137= Textbooks|Mathematics|Algebra
15138= Textbooks|Mathematics|Arithmetic
15139= Textbooks|Mathematics|Calculus
15140= Textbooks|Mathematics|Geometry
15141= Textbooks|Mathematics|Statistics
15142= Textbooks|Medicine
15143= Textbooks|Medicine|Anatomy & Physiology
15144= Textbooks|Medicine|Dentistry
15145= Textbooks|Medicine|Emergency Medicine
15146= Textbooks|Medicine|Genetics
15147= Textbooks|Medicine|Immunology
15148= Textbooks|Medicine|Neuroscience
15149= Textbooks|Medicine|Nursing
15150= Textbooks|Medicine|Pharmacology & Toxicology
15151= Textbooks|Medicine|Psychiatry
15152= Textbooks|Medicine|Psychology
15153= Textbooks|Medicine|Radiology
15154= Textbooks|Medicine|Veterinary
15155= Textbooks|Mysteries & Thrillers
15156= Textbooks|Mysteries & Thrillers|British Detectives
15157= Textbooks|Mysteries & Thrillers|Hard-Boiled
15158= Textbooks|Mysteries & Thrillers|Historical
15159= Textbooks|Mysteries & Thrillers|Police Procedural
15160= Textbooks|Mysteries & Thrillers|Short Stories
15161= Textbooks|Mysteries & Thrillers|Women Sleuths
15162= Textbooks|Nonfiction
15163= Textbooks|Nonfiction|Family & Relationships
15164= Textbooks|Nonfiction|Transportation
15165= Textbooks|Nonfiction|True Crime
15166= Textbooks|Parenting
15167= Textbooks|Philosophy
15168= Textbooks|Philosophy|Aesthetics
15169= Textbooks|Philosophy|Epistemology
15170= Textbooks|Philosophy|Ethics
15171= Textbooks|Philosophy|Philosophy of Language
15172= Textbooks|Philosophy|Logic
15173= Textbooks|Philosophy|Metaphysics
15174= Textbooks|Philosophy|Political Philosophy
15175= Textbooks|Philosophy|Philosophy of Religion
15176= Textbooks|Politics & Current Events
15177= Textbooks|Politics & Current Events|Current Events
15178= Textbooks|Politics & Current Events|Foreign Policy & International Relations
15179= Textbooks|Politics & Current Events|Local Governments
15180= Textbooks|Politics & Current Events|National Governments
15181= Textbooks|Politics & Current Events|Political Science
15182= Textbooks|Politics & Current Events|Public Administration
15183= Textbooks|Politics & Current Events|World Affairs
15184= Textbooks|Professional & Technical
15185= Textbooks|Professional & Technical|Design
15186= Textbooks|Professional & Technical|Language Arts & Disciplines
15187= Textbooks|Professional & Technical|Engineering
15188= Textbooks|Professional & Technical|Law
15189= Textbooks|Professional & Technical|Medical
15190= Textbooks|Reference
15191= Textbooks|Reference|Almanacs & Yearbooks
15192= Textbooks|Reference|Atlases & Maps
15193= Textbooks|Reference|Catalogs & Directories
15194= Textbooks|Reference|Consumer Guides
15195= Textbooks|Reference|Dictionaries & Thesauruses
15196= Textbooks|Reference|Encyclopedias
15197= Textbooks|Reference|Etiquette
15198= Textbooks|Reference|Quotations
15199= Textbooks|Reference|Study Aids
15200= Textbooks|Reference|Words & Language
15201= Textbooks|Reference|Writing
15202= Textbooks|Religion & Spirituality
15203= Textbooks|Religion & Spirituality|Bible Studies
15204= Textbooks|Religion & Spirituality|Bibles
15205= Textbooks|Religion & Spirituality|Buddhism
15206= Textbooks|Religion & Spirituality|Christianity
15207= Textbooks|Religion & Spirituality|Comparative Religion
15208= Textbooks|Religion & Spirituality|Hinduism
15209= Textbooks|Religion & Spirituality|Islam
15210= Textbooks|Religion & Spirituality|Judaism
15211= Textbooks|Religion & Spirituality|Spirituality
15212= Textbooks|Romance
15213= Textbooks|Romance|Contemporary
15214= Textbooks|Romance|Erotic Romance
15215= Textbooks|Romance|Paranormal
15216= Textbooks|Romance|Historical
15217= Textbooks|Romance|Short Stories
15218= Textbooks|Romance|Suspense
15219= Textbooks|Romance|Western
15220= Textbooks|Sci-Fi & Fantasy
15221= Textbooks|Sci-Fi & Fantasy|Fantasy
15222= Textbooks|Sci-Fi & Fantasy|Fantasy|Contemporary
15223= Textbooks|Sci-Fi & Fantasy|Fantasy|Epic
15224= Textbooks|Sci-Fi & Fantasy|Fantasy|Historical
15225= Textbooks|Sci-Fi & Fantasy|Fantasy|Paranormal
15226= Textbooks|Sci-Fi & Fantasy|Fantasy|Short Stories
15227= Textbooks|Sci-Fi & Fantasy|Science Fiction
15228= Textbooks|Sci-Fi & Fantasy|Science Fiction & Literature
15229= Textbooks|Sci-Fi & Fantasy|Science Fiction & Literature|Adventure
15230= Textbooks|Sci-Fi & Fantasy|Science Fiction & Literature|High Tech
15231= Textbooks|Sci-Fi & Fantasy|Science Fiction & Literature|Short Stories
15232= Textbooks|Science & Nature
15233= Textbooks|Science & Nature|Agriculture
15234= Textbooks|Science & Nature|Astronomy
15235= Textbooks|Science & Nature|Atmosphere
15236= Textbooks|Science & Nature|Biology
15237= Textbooks|Science & Nature|Chemistry
15238= Textbooks|Science & Nature|Earth Sciences
15239= Textbooks|Science & Nature|Ecology
15240= Textbooks|Science & Nature|Environment
15241= Textbooks|Science & Nature|Essays
15242= Textbooks|Science & Nature|Geography
15243= Textbooks|Science & Nature|Geology
15244= Textbooks|Science & Nature|History
15245= Textbooks|Science & Nature|Life Sciences
15246= Textbooks|Science & Nature|Nature
15247= Textbooks|Science & Nature|Physics
15248= Textbooks|Science & Nature|Reference
15249= Textbooks|Social Science
15250= Textbooks|Social Science|Anthropology
15251= Textbooks|Social Science|Archaeology
15252= Textbooks|Social Science|Civics
15253= Textbooks|Social Science|Government
15254= Textbooks|Social Science|Social Studies
15255= Textbooks|Social Science|Social Welfare
15256= Textbooks|Social Science|Society
15257= Textbooks|Social Science|Society|African Studies
15258= Textbooks|Social Science|Society|American Studies
15259= Textbooks|Social Science|Society|Asia Pacific Studies
15260= Textbooks|Social Science|Society|Cross-Cultural Studies
15261= Textbooks|Social Science|Society|European Studies
15262= Textbooks|Social Science|Society|Immigration & Emigration
15263= Textbooks|Social Science|Society|Indigenous Studies
15264= Textbooks|Social Science|Society|Latin & Caribbean Studies
15265= Textbooks|Social Science|Society|Middle Eastern Studies
15266= Textbooks|Social Science|Society|Race & Ethnicity Studies
15267= Textbooks|Social Science|Society|Sexuality Studies
15268= Textbooks|Social Science|Society|Women's Studies
15269= Textbooks|Social Science|Sociology
15270= Textbooks|Sports & Outdoors
15271= Textbooks|Sports & Outdoors|Baseball
15272= Textbooks|Sports & Outdoors|Basketball
15273= Textbooks|Sports & Outdoors|Coaching
15274= Textbooks|Sports & Outdoors|Equestrian
15275= Textbooks|Sports & Outdoors|Extreme Sports
15276= Textbooks|Sports & Outdoors|Football
15277= Textbooks|Sports & Outdoors|Golf
15278= Textbooks|Sports & Outdoors|Hockey
15279= Textbooks|Sports & Outdoors|Motor Sports
15280= Textbooks|Sports & Outdoors|Mountaineering
15281= Textbooks|Sports & Outdoors|Outdoors
15282= Textbooks|Sports & Outdoors|Racket Sports
15283= Textbooks|Sports & Outdoors|Reference
15284= Textbooks|Sports & Outdoors|Soccer
15285= Textbooks|Sports & Outdoors|Training
15286= Textbooks|Sports & Outdoors|Water Sports
15287= Textbooks|Sports & Outdoors|Winter Sports
15288= Textbooks|Teaching & Learning
15289= Textbooks|Teaching & Learning|Adult Education
15290= Textbooks|Teaching & Learning|Curriculum & Teaching
15291= Textbooks|Teaching & Learning|Educational Leadership
15292= Textbooks|Teaching & Learning|Educational Technology
15293= Textbooks|Teaching & Learning|Family & Childcare
15294= Textbooks|Teaching & Learning|Information & Library Science
15295= Textbooks|Teaching & Learning|Learning Resources
15296= Textbooks|Teaching & Learning|Psychology & Research
15297= Textbooks|Teaching & Learning|Special Education
15298= Textbooks|Travel & Adventure
15299= Textbooks|Travel & Adventure|Africa
15300= Textbooks|Travel & Adventure|Americas
15301= Textbooks|Travel & Adventure|Americas|Canada
15302= Textbooks|Travel & Adventure|Americas|Latin America
15303= Textbooks|Travel & Adventure|Americas|United States
15304= Textbooks|Travel & Adventure|Asia
15305= Textbooks|Travel & Adventure|Caribbean
15306= Textbooks|Travel & Adventure|Essays & Memoirs
15307= Textbooks|Travel & Adventure|Europe
15308= Textbooks|Travel & Adventure|Middle East
15309= Textbooks|Travel & Adventure|Oceania
15310= Textbooks|Travel & Adventure|Specialty Travel
15311= Textbooks|Comics & Graphic Novels|Comics
15312= Textbooks|Reference|Manuals
16001= App Store|Stickers|Emoji & Expressions
16003= App Store|Stickers|Animals & Nature
16005= App Store|Stickers|Art
16006= App Store|Stickers|Celebrations
16007= App Store|Stickers|Celebrities
16008= App Store|Stickers|Comics & Cartoons
16009= App Store|Stickers|Eating & Drinking
16010= App Store|Stickers|Gaming
16014= App Store|Stickers|Movies & TV
16015= App Store|Stickers|Music
16017= App Store|Stickers|People
16019= App Store|Stickers|Places & Objects
16021= App Store|Stickers|Sports & Activities
16025= App Store|Stickers|Kids & Family
16026= App Store|Stickers|Fashion
100000= Music|Christian & Gospel
100001= Music|Classical|Art Song
100002= Music|Classical|Brass & Woodwinds
100003= Music|Classical|Solo Instrumental
100004= Music|Classical|Contemporary Era
100005= Music|Classical|Oratorio
100006= Music|Classical|Cantata
100007= Music|Classical|Electronic
100008= Music|Classical|Sacred
100009= Music|Classical|Guitar
100010= Music|Classical|Piano
100011= Music|Classical|Violin
100012= Music|Classical|Cello
100013= Music|Classical|Percussion
100014= Music|Electronic|Dubstep
100015= Music|Electronic|Bass
100016= Music|Hip-Hop/Rap|UK Hip-Hop
100017= Music|Reggae|Lovers Rock
100018= Music|Alternative|EMO
100019= Music|Alternative|Pop Punk
100020= Music|Alternative|Indie Pop
100021= Music|New Age|Yoga
100022= Music|Pop|Tribute
100023= Music|Pop|Shows
100024= Music|Cuban
100025= Music|Cuban|Mambo
100026= Music|Cuban|Chachacha
100027= Music|Cuban|Guajira
100028= Music|Cuban|Son
100029= Music|Cuban|Bolero
100030= Music|Cuban|Guaracha
100031= Music|Cuban|Timba
100032= Music|Soundtrack|Video Game
100033= Music|Indian|Regional Indian|Punjabi|Punjabi Pop
100034= Music|Indian|Regional Indian|Bengali|Rabindra Sangeet
100035= Music|Indian|Regional Indian|Malayalam
100036= Music|Indian|Regional Indian|Kannada
100037= Music|Indian|Regional Indian|Marathi
100038= Music|Indian|Regional Indian|Gujarati
100039= Music|Indian|Regional Indian|Assamese
100040= Music|Indian|Regional Indian|Bhojpuri
100041= Music|Indian|Regional Indian|Haryanvi
100042= Music|Indian|Regional Indian|Odia
100043= Music|Indian|Regional Indian|Rajasthani
100044= Music|Indian|Regional Indian|Urdu
100045= Music|Indian|Regional Indian|Punjabi
100046= Music|Indian|Regional Indian|Bengali
100047= Music|Indian|Indian Classical|Carnatic Classical
100048= Music|Indian|Indian Classical|Hindustani Classical
100049= Music|African|Afro House
100050= Music|African|Afro Soul
100051= Music|African|Afrobeats
100052= Music|African|Benga
100053= Music|African|Bongo-Flava
100054= Music|African|Coupe-Decale
100055= Music|African|Gqom
100056= Music|African|Highlife
100057= Music|African|Kuduro
100058= Music|African|Kizomba
100059= Music|African|Kwaito
100060= Music|African|Mbalax
100061= Music|African|Ndombolo
100062= Music|African|Shangaan Electro
100063= Music|African|Soukous
100064= Music|African|Taarab
100065= Music|African|Zouglou
100066= Music|Turkish|Ozgun
100067= Music|Turkish|Fantezi
100068= Music|Turkish|Religious
100069= Music|Pop|Turkish Pop
100070= Music|Rock|Turkish Rock
100071= Music|Alternative|Turkish Alternative
100072= Music|Hip-Hop/Rap|Turkish Hip-Hop/Rap
100073= Music|African|Maskandi
100074= Music|Russian|Russian Romance
100075= Music|Russian|Russian Bard
100076= Music|Russian|Russian Pop
100077= Music|Russian|Russian Rock
100078= Music|Russian|Russian Hip-Hop
100079= Music|Arabic|Levant
100080= Music|Arabic|Levant|Dabke
100081= Music|Arabic|Maghreb Rai
100082= Music|Arabic|Khaleeji|Khaleeji Jalsat
100083= Music|Arabic|Khaleeji|Khaleeji Shailat
100084= Music|Tarab
100085= Music|Tarab|Iraqi Tarab
100086= Music|Tarab|Egyptian Tarab
100087= Music|Tarab|Khaleeji Tarab
100088= Music|Pop|Levant Pop
100089= Music|Pop|Iraqi Pop
100090= Music|Pop|Egyptian Pop
100091= Music|Pop|Maghreb Pop
100092= Music|Pop|Khaleeji Pop
100093= Music|Hip-Hop/Rap|Levant Hip-Hop
100094= Music|Hip-Hop/Rap|Egyptian Hip-Hop
100095= Music|Hip-Hop/Rap|Maghreb Hip-Hop
100096= Music|Hip-Hop/Rap|Khaleeji Hip-Hop
100097= Music|Alternative|Indie Levant
100098= Music|Alternative|Indie Egyptian
100099= Music|Alternative|Indie Maghreb
100100= Music|Electronic|Levant Electronic
100101= Music|Electronic|Electro-Cha'abi
100102= Music|Electronic|Maghreb Electronic
100103= Music|Folk|Iraqi Folk
100104= Music|Folk|Khaleeji Folk
100105= Music|Dance|Maghreb Dance
40000000= iTunes U
40000001= iTunes U|Business & Economics
40000002= iTunes U|Business & Economics|Economics
40000003= iTunes U|Business & Economics|Finance
40000004= iTunes U|Business & Economics|Hospitality
40000005= iTunes U|Business & Economics|Management
40000006= iTunes U|Business & Economics|Marketing
40000007= iTunes U|Business & Economics|Personal Finance
40000008= iTunes U|Business & Economics|Real Estate
40000009= iTunes U|Engineering
40000010= iTunes U|Engineering|Chemical & Petroleum Engineering
40000011= iTunes U|Engineering|Civil Engineering
40000012= iTunes U|Engineering|Computer Science
40000013= iTunes U|Engineering|Electrical Engineering
40000014= iTunes U|Engineering|Environmental Engineering
40000015= iTunes U|Engineering|Mechanical Engineering
40000016= iTunes U|Music, Art, & Design
40000017= iTunes U|Music, Art, & Design|Architecture
40000019= iTunes U|Music, Art, & Design|Art History
40000020= iTunes U|Music, Art, & Design|Dance
40000021= iTunes U|Music, Art, & Design|Film
40000022= iTunes U|Music, Art, & Design|Design
40000023= iTunes U|Music, Art, & Design|Interior Design
40000024= iTunes U|Music, Art, & Design|Music
40000025= iTunes U|Music, Art, & Design|Theater
40000026= iTunes U|Health & Medicine
40000027= iTunes U|Health & Medicine|Anatomy & Physiology
40000028= iTunes U|Health & Medicine|Behavioral Science
40000029= iTunes U|Health & Medicine|Dentistry
40000030= iTunes U|Health & Medicine|Diet & Nutrition
40000031= iTunes U|Health & Medicine|Emergency Medicine
40000032= iTunes U|Health & Medicine|Genetics
40000033= iTunes U|Health & Medicine|Gerontology
40000034= iTunes U|Health & Medicine|Health & Exercise Science
40000035= iTunes U|Health & Medicine|Immunology
40000036= iTunes U|Health & Medicine|Neuroscience
40000037= iTunes U|Health & Medicine|Pharmacology & Toxicology
40000038= iTunes U|Health & Medicine|Psychiatry
40000039= iTunes U|Health & Medicine|Global Health
40000040= iTunes U|Health & Medicine|Radiology
40000041= iTunes U|History
40000042= iTunes U|History|Ancient History
40000043= iTunes U|History|Medieval History
40000044= iTunes U|History|Military History
40000045= iTunes U|History|Modern History
40000046= iTunes U|History|African History
40000047= iTunes U|History|Asia-Pacific History
40000048= iTunes U|History|European History
40000049= iTunes U|History|Middle Eastern History
40000050= iTunes U|History|North American History
40000051= iTunes U|History|South American History
40000053= iTunes U|Communications & Journalism
40000054= iTunes U|Philosophy
40000055= iTunes U|Religion & Spirituality
40000056= iTunes U|Languages
40000057= iTunes U|Languages|African Languages
40000058= iTunes U|Languages|Ancient Languages
40000061= iTunes U|Languages|English
40000063= iTunes U|Languages|French
40000064= iTunes U|Languages|German
40000065= iTunes U|Languages|Italian
40000066= iTunes U|Languages|Linguistics
40000068= iTunes U|Languages|Spanish
40000069= iTunes U|Languages|Speech Pathology
40000070= iTunes U|Writing & Literature
40000071= iTunes U|Writing & Literature|Anthologies
40000072= iTunes U|Writing & Literature|Biography
40000073= iTunes U|Writing & Literature|Classics
40000074= iTunes U|Writing & Literature|Literary Criticism
40000075= iTunes U|Writing & Literature|Fiction
40000076= iTunes U|Writing & Literature|Poetry
40000077= iTunes U|Mathematics
40000078= iTunes U|Mathematics|Advanced Mathematics
40000079= iTunes U|Mathematics|Algebra
40000080= iTunes U|Mathematics|Arithmetic
40000081= iTunes U|Mathematics|Calculus
40000082= iTunes U|Mathematics|Geometry
40000083= iTunes U|Mathematics|Statistics
40000084= iTunes U|Science
40000085= iTunes U|Science|Agricultural
40000086= iTunes U|Science|Astronomy
40000087= iTunes U|Science|Atmosphere
40000088= iTunes U|Science|Biology
40000089= iTunes U|Science|Chemistry
40000090= iTunes U|Science|Ecology
40000091= iTunes U|Science|Geography
40000092= iTunes U|Science|Geology
40000093= iTunes U|Science|Physics
40000094= iTunes U|Social Science
40000095= iTunes U|Law & Politics|Law
40000096= iTunes U|Law & Politics|Political Science
40000097= iTunes U|Law & Politics|Public Administration
40000098= iTunes U|Social Science|Psychology
40000099= iTunes U|Social Science|Social Welfare
40000100= iTunes U|Social Science|Sociology
40000101= iTunes U|Society
40000103= iTunes U|Society|Asia Pacific Studies
40000104= iTunes U|Society|European Studies
40000105= iTunes U|Society|Indigenous Studies
40000106= iTunes U|Society|Latin & Caribbean Studies
40000107= iTunes U|Society|Middle Eastern Studies
40000108= iTunes U|Society|Women's Studies
40000109= iTunes U|Teaching & Learning
40000110= iTunes U|Teaching & Learning|Curriculum & Teaching
40000111= iTunes U|Teaching & Learning|Educational Leadership
40000112= iTunes U|Teaching & Learning|Family & Childcare
40000113= iTunes U|Teaching & Learning|Learning Resources
40000114= iTunes U|Teaching & Learning|Psychology & Research
40000115= iTunes U|Teaching & Learning|Special Education
40000116= iTunes U|Music, Art, & Design|Culinary Arts
40000117= iTunes U|Music, Art, & Design|Fashion
40000118= iTunes U|Music, Art, & Design|Media Arts
40000119= iTunes U|Music, Art, & Design|Photography
40000120= iTunes U|Music, Art, & Design|Visual Art
40000121= iTunes U|Business & Economics|Entrepreneurship
40000122= iTunes U|Communications & Journalism|Broadcasting
40000123= iTunes U|Communications & Journalism|Digital Media
40000124= iTunes U|Communications & Journalism|Journalism
40000125= iTunes U|Communications & Journalism|Photojournalism
40000126= iTunes U|Communications & Journalism|Print
40000127= iTunes U|Communications & Journalism|Speech
40000128= iTunes U|Communications & Journalism|Writing
40000129= iTunes U|Health & Medicine|Nursing
40000130= iTunes U|Languages|Arabic
40000131= iTunes U|Languages|Chinese
40000132= iTunes U|Languages|Hebrew
40000133= iTunes U|Languages|Hindi
40000134= iTunes U|Languages|Indigenous Languages
40000135= iTunes U|Languages|Japanese
40000136= iTunes U|Languages|Korean
40000137= iTunes U|Languages|Other Languages
40000138= iTunes U|Languages|Portuguese
40000139= iTunes U|Languages|Russian
40000140= iTunes U|Law & Politics
40000141= iTunes U|Law & Politics|Foreign Policy & International Relations
40000142= iTunes U|Law & Politics|Local Governments
40000143= iTunes U|Law & Politics|National Governments
40000144= iTunes U|Law & Politics|World Affairs
40000145= iTunes U|Writing & Literature|Comparative Literature
40000146= iTunes U|Philosophy|Aesthetics
40000147= iTunes U|Philosophy|Epistemology
40000148= iTunes U|Philosophy|Ethics
40000149= iTunes U|Philosophy|Metaphysics
40000150= iTunes U|Philosophy|Political Philosophy
40000151= iTunes U|Philosophy|Logic
40000152= iTunes U|Philosophy|Philosophy of Language
40000153= iTunes U|Philosophy|Philosophy of Religion
40000154= iTunes U|Social Science|Archaeology
40000155= iTunes U|Social Science|Anthropology
40000156= iTunes U|Religion & Spirituality|Buddhism
40000157= iTunes U|Religion & Spirituality|Christianity
40000158= iTunes U|Religion & Spirituality|Comparative Religion
40000159= iTunes U|Religion & Spirituality|Hinduism
40000160= iTunes U|Religion & Spirituality|Islam
40000161= iTunes U|Religion & Spirituality|Judaism
40000162= iTunes U|Religion & Spirituality|Other Religions
40000163= iTunes U|Religion & Spirituality|Spirituality
40000164= iTunes U|Science|Environment
40000165= iTunes U|Society|African Studies
40000166= iTunes U|Society|American Studies
40000167= iTunes U|Society|Cross-cultural Studies
40000168= iTunes U|Society|Immigration & Emigration
40000169= iTunes U|Society|Race & Ethnicity Studies
40000170= iTunes U|Society|Sexuality Studies
40000171= iTunes U|Teaching & Learning|Educational Technology
40000172= iTunes U|Teaching & Learning|Information/Library Science
40000173= iTunes U|Languages|Dutch
40000174= iTunes U|Languages|Luxembourgish
40000175= iTunes U|Languages|Swedish
40000176= iTunes U|Languages|Norwegian
40000177= iTunes U|Languages|Finnish
40000178= iTunes U|Languages|Danish
40000179= iTunes U|Languages|Polish
40000180= iTunes U|Languages|Turkish
40000181= iTunes U|Languages|Flemish
50000024= Audiobooks
50000040= Audiobooks|Fiction
50000041= Audiobooks|Arts & Entertainment
50000042= Audiobooks|Biographies & Memoirs
50000043= Audiobooks|Business & Personal Finance
50000044= Audiobooks|Kids & Young Adults
50000045= Audiobooks|Classics
50000046= Audiobooks|Comedy
50000047= Audiobooks|Drama & Poetry
50000048= Audiobooks|Speakers & Storytellers
50000049= Audiobooks|History
50000050= Audiobooks|Languages
50000051= Audiobooks|Mysteries & Thrillers
50000052= Audiobooks|Nonfiction
50000053= Audiobooks|Religion & Spirituality
50000054= Audiobooks|Science & Nature
50000055= Audiobooks|Sci Fi & Fantasy
50000056= Audiobooks|Self-Development
50000057= Audiobooks|Sports & Outdoors
50000058= Audiobooks|Technology
50000059= Audiobooks|Travel & Adventure
50000061= Music|Spoken Word
50000063= Music|Disney
50000064= Music|French Pop
50000066= Music|German Pop
50000068= Music|German Folk
50000069= Audiobooks|Romance
50000070= Audiobooks|Audiobooks Latino
50000071= Books|Comics & Graphic Novels|Manga|Action
50000072= Books|Comics & Graphic Novels|Manga|Comedy
50000073= Books|Comics & Graphic Novels|Manga|Erotica
50000074= Books|Comics & Graphic Novels|Manga|Fantasy
50000075= Books|Comics & Graphic Novels|Manga|Four Cell Manga
50000076= Books|Comics & Graphic Novels|Manga|Gay & Lesbian
50000077= Books|Comics & Graphic Novels|Manga|Hard-Boiled
50000078= Books|Comics & Graphic Novels|Manga|Heroes
50000079= Books|Comics & Graphic Novels|Manga|Historical Fiction
50000080= Books|Comics & Graphic Novels|Manga|Mecha
50000081= Books|Comics & Graphic Novels|Manga|Mystery
50000082= Books|Comics & Graphic Novels|Manga|Nonfiction
50000083= Books|Comics & Graphic Novels|Manga|Religious
50000084= Books|Comics & Graphic Novels|Manga|Romance
50000085= Books|Comics & Graphic Novels|Manga|Romantic Comedy
50000086= Books|Comics & Graphic Novels|Manga|Science Fiction
50000087= Books|Comics & Graphic Novels|Manga|Sports
50000088= Books|Fiction & Literature|Light Novels
50000089= Books|Comics & Graphic Novels|Manga|Horror
50000090= Books|Comics & Graphic Novels|Comics
50000091= Books|Romance|Multicultural
50000092= Audiobooks|Erotica
50000093= Audiobooks|Light Novels
+ +

QuickTime AppleStoreCountry Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueAppleStoreCountryValueAppleStoreCountry
143441= United States143523= Moldova
143442= France143524= Armenia
143443= Germany143525= Botswana
143444= United Kingdom143526= Bulgaria
143445= Austria143528= Jordan
143446= Belgium143529= Kenya
143447= Finland143530= Macedonia
143448= Greece143531= Madagascar
143449= Ireland143532= Mali
143450= Italy143533= Mauritius
143451= Luxembourg143534= Niger
143452= Netherlands143535= Senegal
143453= Portugal143536= Tunisia
143454= Spain143537= Uganda
143455= Canada143538= Anguilla
143456= Sweden143539= Bahamas
143457= Norway143540= Antigua and Barbuda
143458= Denmark143541= Barbados
143459= Switzerland143542= Bermuda
143460= Australia143543= British Virgin Islands
143461= New Zealand143544= Cayman Islands
143462= Japan143545= Dominica
143463= Hong Kong143546= Grenada
143464= Singapore143547= Montserrat
143465= China143548= St. Kitts and Nevis
143466= Republic of Korea143549= St. Lucia
143467= India143550= St. Vincent and The Grenadines
143468= Mexico143551= Trinidad and Tobago
143469= Russia143552= Turks and Caicos
143470= Taiwan143553= Guyana
143471= Vietnam143554= Suriname
143472= South Africa143555= Belize
143473= Malaysia143556= Bolivia
143474= Philippines143557= Cyprus
143475= Thailand143558= Iceland
143476= Indonesia143559= Bahrain
143477= Pakistan143560= Brunei Darussalam
143478= Poland143561= Nigeria
143479= Saudi Arabia143562= Oman
143480= Turkey143563= Algeria
143481= United Arab Emirates143564= Angola
143482= Hungary143565= Belarus
143483= Chile143566= Uzbekistan
143484= Nepal143568= Azerbaijan
143485= Panama143571= Yemen
143486= Sri Lanka143572= Tanzania
143487= Romania143573= Ghana
143489= Czech Republic143575= Albania
143491= Israel143576= Benin
143492= Ukraine143577= Bhutan
143493= Kuwait143578= Burkina Faso
143494= Croatia143579= Cambodia
143495= Costa Rica143580= Cape Verde
143496= Slovakia143581= Chad
143497= Lebanon143582= Republic of the Congo
143498= Qatar143583= Fiji
143499= Slovenia143584= Gambia
143501= Colombia143585= Guinea-Bissau
143502= Venezuela143586= Kyrgyzstan
143503= Brazil143587= Lao People's Democratic Republic
143504= Guatemala143588= Liberia
143505= Argentina143589= Malawi
143506= El Salvador143590= Mauritania
143507= Peru143591= Federated States of Micronesia
143508= Dominican Republic143592= Mongolia
143509= Ecuador143593= Mozambique
143510= Honduras143594= Namibia
143511= Jamaica143595= Palau
143512= Nicaragua143597= Papua New Guinea
143513= Paraguay143598= Sao Tome and Principe
143514= Uruguay143599= Seychelles
143515= Macau143600= Sierra Leone
143516= Egypt143601= Solomon Islands
143517= Kazakhstan143602= Swaziland
143518= Estonia143603= Tajikistan
143519= Latvia143604= Turkmenistan
143520= Lithuania143605= Zimbabwe
143521= Malta  
+ +

QuickTime iTunesInfo Tags

+

ExifTool will extract any iTunesInfo tags that exist, even if they are not +defined in this table. These tags belong to the family 1 "iTunes" group, +and are not currently writable.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ARTISTS'Artistsno 
'BARCODE'Barcodeno 
'CATALOGNUMBER'CatalogNumberno 
'DISCNUMBER'DiscNumberno 
'Dynamic Range (DR)'DynamicRangeno 
'Dynamic Range (R128)'DynamicRangeR128no 
'Encoding Params'EncodingParams---> QuickTime EncodingParams Tags
'LABEL'Labelno 
'MEDIA'Mediano 
'MOOD'Moodno 
'Peak Level (R128)'PeakLevelR128no 
'Peak Level (Sample)'PeakLevelSampleno 
'RATING'Ratingno 
'SCRIPT'Scriptno 
'TRACKNUMBER'TrackNumberno 
'Volume Level (R128)'VolumeLevelR128no 
'Volume Level (ReplayGain)'ReplayVolumeLevelno 
'iTunEXTC'ContentRatingno(standard | rating | score | reasons)
'iTunMOVI'iTunMOVI---> PLIST Tags
'iTunNORM'VolumeNormalizationno 
'iTunSMPB'iTunSMPBno 
'iTunes_CDDB_1'CDDB1Infono 
'iTunes_CDDB_TrackNumber'CDDBTrackNumberno 
'initialkey'InitialKeyno 
'originaldate'OriginalDateno 
'originalyear'OriginalYearno 
'popularimeter'Popularimeterno 
'replaygain_track_gain'ReplayTrackGainno 
'replaygain_track_peak'ReplayTrackPeakno 
'tool'iTunToolno 
'~length'Lengthno 
+ +

QuickTime EncodingParams Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'abrt'AudioAvailableBitRateRangeno 
'acbf'AudioBitRateControlModeno 
'acef'AudioExtendFrequenciesno 
'brat'AudioCurrentTargetBitRateno 
'cdcv'AudioComponentVersionno 
'cmnc'AudioAvailableNumberChannelsno 
'init'AudioIsInitializedno 
'lmrc'AudioDoesSampleRateConversionno 
'mdel'AudioMinimumDelayModeno 
'mnip'AudioMinimumNumberInputPacketsno 
'mnop'AudioMinimumNumberOutputPacketsno 
'oppr'AudioOutputPrecedenceno 
'pad0'AudioZeroFramesPaddedno 
'pakb'AudioMaximumPacketByteSizeno 
'pakd'AudioRequiresPacketDescriptionno 
'pakf'AudioPacketFrameSizeno 
'prmm'AudioCodecPrimeMethodno 
'srcq'AudioQualitySettingno 
'tbuf'AudioInputBufferSizeno 
'ubuf'AudioUsedInputBufferSizeno 
'ursr'AudioUseRecommendedSampleRateno 
'vbrq'AudioVBRQualityno 
'vers'AudioEncodingParamsVersionno 
'vpk?'AudioHasVariablePacketByteSizesno 
+ +

QuickTime ItemProp Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ipco'ItemPropertyContainer---> QuickTime ItemPropCont Tags
'ipma'ItemPropertyAssociationno(parsed, but not extracted as a tag)
+ +

QuickTime ItemPropCont Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'auxC'AuxiliaryImageTypeno 
'av1C'AV1Configuration---> QuickTime AV1Config Tags
'clap'CleanApertureno(4 numbers: width, height, left and top)
'clli'ContentLightLevel---> QuickTime ContentLightLevel Tags
'colr'ICC_Profile +
ColorRepresentation
-
-
--> ICC_Profile Tags +
--> QuickTime ColorRep Tags
'hvcC'HEVCConfiguration---> QuickTime HEVCConfig Tags
'irot'Rotationint8u! 
'ispe'ImageSpatialExtentno 
'pasp'PixelAspectRatioint32u! 
'pixi'ImagePixelDepthno 
'rloc'RelativeLocationno 
+ +

QuickTime AV1Config Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AV1ConfigurationVersionno[val & 0x7f]
1SeqProfile?no[val >> 5 & 0x7]
1.1SeqLevelIdx0?no[val & 0x1f]
2SeqTier0?no[val >> 7 & 0x1]
2.1HighBitDepth?no[val >> 6 & 0x1]
2.2TwelveBit?no[val >> 5 & 0x1]
2.3ChromaFormatno(bits: 0x04 = Monochrome, 0x02 = SubSamplingX, 0x01 = SubSamplingY) +
[val >> 2 & 0x7] +
0 = YUV 4:4:4 +
2 = YUV 4:2:2 +
3 = YUV 4:2:0 +
7 = Monochrome 4:0:0
2.4ChromaSamplePositionno[val & 0x3] +
0 = Unknown +
1 = Vertical +
2 = Colocated +
3 = (reserved)
3InitialDelaySamples?no 
+ +

QuickTime ContentLightLevel Tags

+
+
+ + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0MaxContentLightLevelno 
1MaxPicAverageLightLevelno 
+ +

QuickTime ColorRep Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ColorProfilesno 
4ColorPrimariesno1 = BT.709 +
2 = Unspecified +
4 = BT.470 System M (historical) +
5 = BT.470 System B, G (historical) +
6 = BT.601 +
7 = SMPTE 240 +
8 = Generic film (color filters using illuminant C) +
9 = BT.2020, BT.2100 +
10 = SMPTE 428 (CIE 1931 XYZ) +
11 = SMPTE RP 431-2 +
12 = SMPTE EG 432-1 +
22 = EBU Tech. 3213-E
6TransferCharacteristicsno +
0 = For future use (0) +
1 = BT.709 +
2 = Unspecified +
3 = For future use (3) +
4 = BT.470 System M (historical) +
5 = BT.470 System B, G (historical) +
6 = BT.601 +
7 = SMPTE 240 M +
8 = Linear +
9 = Logarithmic (100 : 1 range) +
10 = Logarithmic (100 * Sqrt(10) : 1 range) +
11 = IEC 61966-2-4 +
12 = BT.1361 +
13 = sRGB or sYCC +
14 = BT.2020 10-bit systems +
15 = BT.2020 12-bit systems +
16 = SMPTE ST 2084, ITU BT.2100 PQ +
17 = SMPTE ST 428 +
18 = BT.2100 HLG, ARIB STD-B67
+
8MatrixCoefficientsno0 = Identity matrix +
1 = BT.709 +
2 = Unspecified +
3 = For future use (3) +
4 = US FCC 73.628 +
5 = BT.470 System B, G (historical) +
6 = BT.601 +
7 = SMPTE 240 M +
8 = YCgCo +
9 = BT.2020 non-constant luminance, BT.2100 YCbCr +
10 = BT.2020 constant luminance +
11 = SMPTE ST 2085 YDzDx +
12 = Chromaticity-derived non-constant luminance +
13 = Chromaticity-derived constant luminance +
14 = BT.2100 ICtCp
+ +

QuickTime HEVCConfig Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0HEVCConfigurationVersionno 
1GeneralProfileSpaceno[val >> 6 & 0x3] +
0 = Conforming
1.1GeneralTierFlagno[val >> 5 & 0x1] +
0 = Main Tier +
1 = High Tier
1.2GeneralProfileIDCno[val & 0x1f] +
0 = No Profile +
1 = Main +
2 = Main 10 +
3 = Main Still Picture +
4 = Format Range Extensions +
5 = High Throughput +
6 = Multiview Main +
7 = Scalable Main +
8 = 3D Main +
9 = Screen Content Coding Extensions +
10 = Scalable Format Range Extensions +
11 = High Throughput Screen Content Coding Extensions
2GenProfileCompatibilityFlagsnoBit 20 = High Throughput Screen Content Coding Extensions +
Bit 21 = Scalable Format Range Extensions +
Bit 22 = Screen Content Coding Extensions +
Bit 23 = 3D Main +
Bit 24 = Scalable Main +
Bit 25 = Multiview Main +
Bit 26 = High Throughput +
Bit 27 = Format Range Extensions +
Bit 28 = Main Still Picture +
Bit 29 = Main 10 +
Bit 30 = Main +
Bit 31 = No Profile
6ConstraintIndicatorFlagsno 
12GeneralLevelIDCno 
13MinSpatialSegmentationIDCno[val & 0xfff]
15ParallelismTypeno[val & 0x3]
16ChromaFormatno[val & 0x3] +
0 = Monochrome +
1 = 4:2:0 +
2 = 4:2:2 +
3 = 4:4:4
17BitDepthLumano[val & 0x7]
18BitDepthChromano[val & 0x7]
19AverageFrameRateno 
21ConstantFrameRateno[val >> 6 & 0x3] +
0 = Unknown +
1 = Constant Frame Rate +
2 = Each Temporal Layer is Constant Frame Rate
21.1NumTemporalLayersno[val >> 3 & 0x7]
21.2TemporalIDNestedno[val >> 2 & 0x1] +
0 = No +
1 = Yes
+ +

QuickTime ItemRef Tags

+

The Item reference entries listed in the table below contain information about +the associations between items in the file. This information is used by +ExifTool, but these entries are not extracted as tags.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'auxl'AuxiliaryImageRefno 
'cdsc'ContentDescribesno 
'dimg'DerivedImageRefno 
'thmb'ThumbnailRefno 
+ +

QuickTime MovieFragment Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'meta'Meta---> QuickTime Meta Tags
'mfhd'MovieFragmentHeader---> QuickTime MovieFragHdr Tags
'traf'TrackFragment---> QuickTime TrackFragment Tags
+ +

QuickTime MovieFragHdr Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
1MovieFragmentSequenceno 
+ +

QuickTime TrackFragment Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'meta'Meta---> QuickTime Meta Tags
+ +

QuickTime Movie Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'cmov'CompressedMovie---> QuickTime CMovie Tags
'gps 'GPSDataList?no 
'htka'HTCTrack---> QuickTime Track Tags
'iods'InitialObjectDescriptor?no 
'meco'OtherMeta---> QuickTime OtherMeta Tags
'meta'Meta---> QuickTime Meta Tags
'mvhd'MovieHeader---> QuickTime MovieHeader Tags
'trak'Track---> QuickTime Track Tags
'udta'UserData---> QuickTime UserData Tags
'uuid'UUID-USMT +
UUID-Canon +
GarminGPS +
GarminGPS +
UUID-Unknown?
-
-
-
no
no
--> QuickTime UserMedia Tags +
--> Canon uuid Tags +
--> QuickTime Stream Tags +
(Garmin GPS sensor data)
+ +

QuickTime CMovie Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'dcom'Compressionno 
+ +

QuickTime Track Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'mdia'Media---> QuickTime Media Tags
'meco'OtherMeta---> QuickTime OtherMeta Tags
'meta'Meta---> QuickTime Meta Tags
'tapt'TrackAperture---> QuickTime TrackAperture Tags
'tkhd'TrackHeader---> QuickTime TrackHeader Tags
'tref'TrackRef---> QuickTime TrackRef Tags
'udta'UserData---> QuickTime UserData Tags
'uuid'UUID-USMT +
SphericalVideoXML +
UUID-Unknown?
-
-
no
--> QuickTime UserMedia Tags +
--> XMP Tags
+ +

QuickTime Media Tags

+

MP4 media box.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'hdlr'Handler---> QuickTime Handler Tags
'mdhd'MediaHeader---> QuickTime MediaHeader Tags
'minf'MediaInfo---> QuickTime MediaInfo Tags
+ +

QuickTime MediaHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0MediaHeaderVersionno 
1MediaCreateDateint32u(converted from UTC to local time if the QuickTimeUTC option is set)
2MediaModifyDateint32u(converted from UTC to local time if the QuickTimeUTC option is set)
3MediaTimeScaleno 
4MediaDurationno 
5MediaLanguageCodeno 
+ +

QuickTime MediaInfo Tags

+

MP4 media info box.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'dinf'DataInfo---> QuickTime DataInfo Tags
'gmhd'GenMediaHeader---> QuickTime GenMediaHeader Tags
'hdlr'Handler---> QuickTime Handler Tags
'hmhd'HintHeader---> QuickTime HintHeader Tags
'nmhd'NullMediaHeader?no 
'smhd'AudioHeader---> QuickTime AudioHeader Tags
'stbl'SampleTable---> QuickTime SampleTable Tags
'vmhd'VideoHeader---> QuickTime VideoHeader Tags
+ +

QuickTime GenMediaHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'gmin'GenMediaInfo---> QuickTime GenMediaInfo Tags
'text'Text?no 
'tmcd'TimeCode---> QuickTime TimeCode Tags
+ +

QuickTime GenMediaInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0GenMediaVersionno 
1GenFlagsno 
4GenGraphicsModeno--> QuickTime GraphicsMode Values
6GenOpColorno 
12GenBalanceno 
+ +

QuickTime GraphicsMode Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueGraphicsModeValueGraphicsModeValueGraphicsMode
0x0= srcCopy0xb= patBic0x26= subOver
0x1= srcOr0xc= notPatCopy0x27= addMin
0x2= srcXor0xd= notPatOr0x31= grayishTextOr
0x3= srcBic0xe= notPatXor0x32= hilite
0x4= notSrcCopy0xf= notPatBic0x40= ditherCopy
0x5= notSrcOr0x20= blend0x100= Alpha
0x6= notSrcXor0x21= addPin0x101= White Alpha
0x7= notSrcBic0x22= addOver0x102= Pre-multiplied Black Alpha
0x8= patCopy0x23= subPin0x110= Component Alpha
0x9= patOr0x24= transparent  
0xa= patXor0x25= addMax  
+ +

QuickTime TimeCode Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'tcmi'TCMediaInfo---> QuickTime TCMediaInfo Tags
+ +

QuickTime TCMediaInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4TextFontno0 = System
6TextFaceno + +
0x0 = Plain +
Bit 0 = Bold +
Bit 1 = Italic +
Bit 2 = Underline
  Bit 3 = Outline +
Bit 4 = Shadow +
Bit 5 = Condense +
Bit 6 = Extend
+
8TextSizeno 
12TextColorno 
18BackgroundColorno 
24FontNameno 
+ +

QuickTime HintHeader Tags

+

MP4 hint media header.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
2MaxPDUSizeno 
3AvgPDUSizeno 
4MaxBitrateno 
6AvgBitrateno 
+ +

QuickTime AudioHeader Tags

+

MP4 audio media header.

+
+
+ + + + + + + + +
Index2Tag NameWritableValues / Notes
2Balanceno 
+ +

QuickTime SampleTable Tags

+

MP4 sample table box.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'co64'ChunkOffset64?no 
'cslg'CompositionToDecodeTimelineMapping?no 
'ctts'CompositionTimeToSample?no 
'padb'SamplePaddingBits?no 
'sbgp'SampleToGroup?no 
'sdtp'IdependentAndDisposableSamples?no 
'sgpd'SampleGroupDescription?no 
'stco'ChunkOffset?no 
'stdp'SampleDegradationPriority?no 
'stps'PartialSyncSamplesno 
'stsc'SampleToChunk?no 
'stsd'AudioSampleDesc +
VideoSampleDesc +
HintSampleDesc +
MetaSampleDesc +
OtherSampleDesc
-
-
-
-
-
--> QuickTime AudioSampleDesc Tags +
--> QuickTime ImageDesc Tags +
--> QuickTime HintSampleDesc Tags +
--> QuickTime MetaSampleDesc Tags +
--> QuickTime OtherSampleDesc Tags
'stsh'ShadowSyncSampleTable?no 
'stss'SyncSampleTable?no 
'stsz'SampleSizes?no 
'stts'VideoFrameRate +
TimeToSampleTable?
no
no
(average rate calculated from time-to-sample table for video media)
'stz2'CompactSampleSizes?no 
'subs'Sub-sampleInformation?no 
+ +

QuickTime AudioSampleDesc Tags

+

MP4 audio sample description. This hybrid atom contains both data and child +atoms.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID/IndexTag NameWritableValues / Notes
4AudioFormatno 
20AudioVendorIDno--> QuickTime VendorID Values
24AudioChannelsno 
26AudioBitsPerSampleno 
32AudioSampleRateno 
'SA3D'SpatialAudio---> QuickTime SpatialAudio Tags
'chan'AudioChannelLayout---> QuickTime ChannelLayout Tags
'damr'DecodeConfig---> QuickTime DecodeConfig Tags
'pinf'PurchaseInfo---> QuickTime ProtectionInfo Tags
'sinf'ProtectionInfo---> QuickTime ProtectionInfo Tags
'wave'Wave---> QuickTime Wave Tags
+ +

QuickTime SpatialAudio Tags

+

Spatial Audio tags.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SpatialAudioVersionno 
1AmbisonicTypeno0 = Periphonic
2AmbisonicOrderno 
6AmbisonicChannelOrderingno0 = ACN
7AmbisonicNormalizationno0 = SN3D
8AmbisonicChannelsno 
12AmbisonicChannelMapno 
+ +

QuickTime ChannelLayout Tags

+

Audio channel layout.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4LayoutFlagsno + +
0 = UseDescriptions +
1 = UseBitmap +
100 = Mono +
101 = Stereo +
102 = StereoHeadphones +
103 = MatrixStereo +
104 = MidSide +
105 = XY +
106 = Binaural +
107 = Ambisonic_B_Format +
108 = Quadraphonic +
109 = Pentagonal +
110 = Hexagonal +
111 = Octagonal +
112 = Cube +
113 = MPEG_3_0_A +
114 = MPEG_3_0_B +
115 = MPEG_4_0_A +
116 = MPEG_4_0_B +
117 = MPEG_5_0_A +
118 = MPEG_5_0_B +
119 = MPEG_5_0_C +
120 = MPEG_5_0_D +
121 = MPEG_5_1_A +
122 = MPEG_5_1_B +
123 = MPEG_5_1_C +
124 = MPEG_5_1_D +
125 = MPEG_6_1_A +
126 = MPEG_7_1_A +
127 = MPEG_7_1_B +
128 = MPEG_7_1_C +
129 = Emagic_Default_7_1 +
130 = SMPTE_DTV +
131 = ITU_2_1 +
132 = ITU_2_2 +
133 = DVD_4 +
134 = DVD_5 +
135 = DVD_6 +
136 = DVD_10 +
137 = DVD_11 +
138 = DVD_18 +
139 = AudioUnit_6_0 +
140 = AudioUnit_7_0 +
141 = AAC_6_0
  142 = AAC_6_1 +
143 = AAC_7_0 +
144 = AAC_Octagonal +
145 = TMH_10_2_std +
146 = TMH_10_2_full +
147 = DiscreteInOrder +
148 = AudioUnit_7_0_Front +
149 = AC3_1_0_1 +
150 = AC3_3_0 +
151 = AC3_3_1 +
152 = AC3_3_0_1 +
153 = AC3_2_1_1 +
154 = AC3_3_1_1 +
155 = EAC_6_0_A +
156 = EAC_7_0_A +
157 = EAC3_6_1_A +
158 = EAC3_6_1_B +
159 = EAC3_6_1_C +
160 = EAC3_7_1_A +
161 = EAC3_7_1_B +
162 = EAC3_7_1_C +
163 = EAC3_7_1_D +
164 = EAC3_7_1_E +
165 = EAC3_7_1_F +
166 = EAC3_7_1_G +
167 = EAC3_7_1_H +
168 = DTS_3_1 +
169 = DTS_4_1 +
170 = DTS_6_0_A +
171 = DTS_6_0_B +
172 = DTS_6_0_C +
173 = DTS_6_1_A +
174 = DTS_6_1_B +
175 = DTS_6_1_C +
176 = DTS_7_0 +
177 = DTS_7_1 +
178 = DTS_8_0_A +
179 = DTS_8_0_B +
180 = DTS_8_1_A +
181 = DTS_8_1_B +
182 = DTS_6_1_D +
183 = AAC_7_1_B +
65535 = Unknown
+
6AudioChannelsno 
8AudioChannelTypesno +
Bit 0 = Left +
Bit 1 = Right +
Bit 2 = Center +
Bit 3 = LFEScreen +
Bit 4 = LeftSurround +
Bit 5 = RightSurround +
Bit 6 = LeftCenter +
Bit 7 = RightCenter +
Bit 8 = CenterSurround +
Bit 9 = LeftSurroundDirect +
Bit 10 = RightSurroundDirect +
Bit 11 = TopCenterSurround +
Bit 12 = VerticalHeightLeft +
Bit 13 = VerticalHeightCenter +
Bit 14 = VerticalHeightRight +
Bit 15 = TopBackLeft +
Bit 16 = TopBackCenter +
Bit 17 = TopBackRight
+
12NumChannelDescriptionsno 
16Channel1Labelno--> QuickTime ChannelLabel Values
20Channel1FlagsnoBit 0 = Rectangular +
Bit 1 = Spherical +
Bit 2 = Meters
24Channel1Coordinatesno(3 numbers: for rectangular coordinates left/right, back/front, down/up; for +spherical coordinates left/right degrees, down/up degrees, distance)
36Channel2Labelno--> QuickTime ChannelLabel Values
40Channel2FlagsnoBit 0 = Rectangular +
Bit 1 = Spherical +
Bit 2 = Meters
44Channel2Coordinatesno 
56Channel3Labelno--> QuickTime ChannelLabel Values
60Channel3FlagsnoBit 0 = Rectangular +
Bit 1 = Spherical +
Bit 2 = Meters
64Channel3Coordinatesno 
76Channel4Labelno--> QuickTime ChannelLabel Values
80Channel4FlagsnoBit 0 = Rectangular +
Bit 1 = Spherical +
Bit 2 = Meters
84Channel4Coordinatesno 
96Channel5Labelno--> QuickTime ChannelLabel Values
100Channel5FlagsnoBit 0 = Rectangular +
Bit 1 = Spherical +
Bit 2 = Meters
104Channel5Coordinatesno 
116Channel6Labelno--> QuickTime ChannelLabel Values
120Channel6FlagsnoBit 0 = Rectangular +
Bit 1 = Spherical +
Bit 2 = Meters
124Channel6Coordinatesno 
136Channel7Labelno--> QuickTime ChannelLabel Values
140Channel7FlagsnoBit 0 = Rectangular +
Bit 1 = Spherical +
Bit 2 = Meters
144Channel7Coordinatesno 
156Channel8Labelno--> QuickTime ChannelLabel Values
160Channel8FlagsnoBit 0 = Rectangular +
Bit 1 = Spherical +
Bit 2 = Meters
164Channel8Coordinatesno 
+ +

QuickTime ChannelLabel Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueChannelLabelValueChannelLabelValueChannelLabel
0= Unused36= RightWide305= ForeignLanguage
1= Left37= LFE2400= Discrete
2= Right38= LeftTotal65536= Discrete_0
3= Center39= RightTotal65537= Discrete_1
4= LFEScreen40= HearingImpaired65538= Discrete_2
5= LeftSurround41= Narration65539= Discrete_3
6= RightSurround42= Mono65540= Discrete_4
7= LeftCenter43= DialogCentricMix65541= Discrete_5
8= RightCenter44= CenterSurroundDirect65542= Discrete_6
9= CenterSurround45= Haptic65543= Discrete_7
10= LeftSurroundDirect100= UseCoordinates65544= Discrete_8
11= RightSurroundDirect200= Ambisonic_W65545= Discrete_9
12= TopCenterSurround201= Ambisonic_X65546= Discrete_10
13= VerticalHeightLeft202= Ambisonic_Y65547= Discrete_11
14= VerticalHeightCenter203= Ambisonic_Z65548= Discrete_12
15= VerticalHeightRight204= MS_Mid65549= Discrete_13
16= TopBackLeft205= MS_Side65550= Discrete_14
17= TopBackCenter206= XY_X65551= Discrete_15
18= TopBackRight207= XY_Y131071= Discrete_65535
33= RearSurroundLeft301= HeadphonesLeft4294967295= Unknown
34= RearSurroundRight302= HeadphonesRight  
35= LeftWide304= ClickTrack  
+ +

QuickTime DecodeConfig Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0EncoderVendorno 
4EncoderVersionno 
+ +

QuickTime ProtectionInfo Tags

+

Child atoms found in "sinf" and/or "pinf" atoms.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'enda'Endiannessno0 = Big-endian (Motorola, MM) +
1 = Little-endian (Intel, II)
'frma'OriginalFormatno 
'schi'SchemeInfo---> QuickTime SchemeInfo Tags
'schm'SchemeType---> QuickTime SchemeType Tags
+ +

QuickTime SchemeInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'cert'Certificateno 
'iviv'InitializationVectorno 
'key 'KeyIDno 
'name'UserNameno 
'righ'Rights---> QuickTime Rights Tags
'user'UserIDno 
+ +

QuickTime Rights Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'aver'VersionRestrictionsno 
'medi'MediaFlagsno 
'mode'ModeFlagsno 
'plat'Platformno 
'song'ItemIDno 
'tool'ItemToolno 
'tran'TransactionIDno 
'veID'ItemVendorIDno 
+ +

QuickTime SchemeType Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4SchemeTypeno 
8SchemeVersionno 
10SchemeURLno 
+ +

QuickTime Wave Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'enda'Endiannessno0 = Big-endian (Motorola, MM) +
1 = Little-endian (Intel, II)
'frma'PurchaseFileFormatno 
+ +

QuickTime ImageDesc Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID/IndexTag NameWritableValues / Notes
2CompressorIDno 
10VendorIDno--> QuickTime VendorID Values
16SourceImageWidthno 
17SourceImageHeightno 
18XResolutionno 
20YResolutionno 
25CompressorNameno 
41BitDepthno 
'CDI1'CDI1---> Canon CDI1 Tags
'CMP1'CMP1---> Canon CMP1 Tags
'JPEG'JPEGInfo?no 
'avcC'AVCConfiguration?no 
'btrt'BitrateInfo---> QuickTime Bitrate Tags
'clap'CleanAperture---> QuickTime CleanAperture Tags
'colr'ColorRepresentation---> QuickTime ColorRep Tags
'fiel'VideoFieldOrderno[Value 0] +
1 = Progressive +
2 = 2:1 Interlaced
'gama'Gammano 
'pasp'PixelAspectRationo 
'st3d'Stereoscopic3Dno0 = Monoscopic +
1 = Stereoscopic Top-Bottom +
2 = Stereoscopic Left-Right +
3 = Stereoscopic Stereo-Custom +
4 = Stereoscopic Right-Left
'sv3d'SphericalVideo---> QuickTime sv3d Tags
+ +

QuickTime Bitrate Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0BufferSizeno 
1MaxBitrateno 
2AverageBitrateno 
+ +

QuickTime CleanAperture Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index8Tag NameWritableValues / Notes
0CleanApertureWidthno 
1CleanApertureHeightno 
2CleanApertureOffsetXno 
3CleanApertureOffsetYno 
+ +

QuickTime sv3d Tags

+

Tags defined by the Spherical Video V2 specification. See +https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md +for the specification.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'proj'Projection---> QuickTime proj Tags
'svhd'MetadataSourceno 
+ +

QuickTime proj Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'cbmp'CubemapProj---> QuickTime cbmp Tags
'equi'EquirectangularProj---> QuickTime equi Tags
'prhd'ProjectionHeader---> QuickTime prhd Tags
+ +

QuickTime cbmp Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1Layoutno 
2Paddingno 
+ +

QuickTime equi Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1ProjectionBoundsTopno 
2ProjectionBoundsBottomno 
3ProjectionBoundsLeftno 
4ProjectionBoundsRightno 
+ +

QuickTime prhd Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1PoseYawDegreesno 
2PosePitchDegreesno 
3PoseRollDegreesno 
+ +

QuickTime HintSampleDesc Tags

+

MP4 hint sample description.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID/IndexTag NameWritableValues / Notes
4HintFormatno 
16HintTrackVersionno 
20MaxPacketSizeno 
'snro'SequenceNumberRandomOffsetno 
'tims'RTPTimeScaleno 
'tsro'TimestampRandomOffsetno 
+ +

QuickTime MetaSampleDesc Tags

+

MP4 metadata sample description.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004MetaFormatno 
0x0008MetaTypeno 
'btrt'BitrateInfo---> QuickTime Bitrate Tags
'keys'Keys---> QuickTime Keys Tags
+ +

QuickTime OtherSampleDesc Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004OtherFormatno 
0x0018PlaybackFrameRateno 
'ftab'FontTableno 
'name'OtherNameno 
+ +

QuickTime VideoHeader Tags

+

MP4 video media header.

+
+
+ + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
2GraphicsModeno--> QuickTime GraphicsMode Values
3OpColorno 
+ +

QuickTime TrackAperture Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'clef'CleanApertureDimensionsno 
'enof'EncodedPixelsDimensionsno 
'prof'ProductionApertureDimensionsno 
+ +

QuickTime TrackHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0TrackHeaderVersionno 
1TrackCreateDateint32u(converted from UTC to local time if the QuickTimeUTC option is set)
2TrackModifyDateint32u(converted from UTC to local time if the QuickTimeUTC option is set)
3TrackIDno 
5TrackDurationno 
8TrackLayerno 
9TrackVolumeno 
10MatrixStructurefixed32s[9]!(writable for the video track via the Composite Rotation tag)
19ImageWidthno 
20ImageHeightno 
+ +

QuickTime TrackRef Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'cdsc'ContentDescribesno 
'chap'ChapterListTrackIDno 
'mpod'ElementaryStreamTrackno 
'tmcd'TimeCodeno 
+ +

QuickTime UserData Tags

+

Tag ID's beginning with the copyright symbol (hex 0xa9) are multi-language +text. Alternate language tags are accessed by adding a dash followed by a +3-character ISO 639-2 language code to the tag name. ExifTool will extract +any multi-language user data tags found, even if they aren't in this table. +Note when creating new tags, +ItemList tags are +preferred over these, so to create the tag when a same-named ItemList tag +exists, either "UserData" must be specified (eg. -UserData:Artist=Monet +on the command line), or the PREFERRED level must be changed via +the config file.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'@day'ContentCreateDatestring/(some stupid Ricoh programmer used the '@' symbol instead of the copyright +symbol in these tag ID's for the Ricoh Theta Z1 and maybe other models)
'@mak'Makestring/ 
'@mod'Modelstring/ 
'@sec'SamsungSec---> Samsung sec Tags
'@swr'SoftwareVersionstring/ 
'@xyz'GPSCoordinatesstring/ 
'AllF'PlayAllFramesint8u 
'CAME'SerialNumberHashstring 
'CNCV'CompressorVersionstring 
'CNFV'FirmwareVersionstring 
'CNMN'Modelstring/ 
'CNOP'CanonCNOP---> Canon CNOP Tags
'CNTH'CanonCNTH---> Canon CNTH Tags
'DcMD'KodakDcMD---> Kodak DcMD Tags
'FFMV'FujiFilmFFMV---> FujiFilm FFMV Tags
'FIRM'FirmwareVersionstring/ 
"FOV\0"FieldOfViewstring 
'GPMF'GoProGPMF---> GoPro GPMF Tags
'GoPr'GoProTypestring 
'INFO'SamsungINFO---> Samsung INFO Tags
'LEIC'LeicaLEIC---> Panasonic PANA Tags
'LENS'LensSerialNumberstring 
'LOOP'LoopStyleint32u1 = Normal +
2 = Palindromic
'Lvlm'LevelMeter?rational64s 
'MMA0'MinoltaMMA0---> Minolta MMA Tags
'MMA1'MinoltaMMA1---> Minolta MMA Tags
'MVTG'FujiFilmMVTG---> EXIF Tags
'NCDT'NikonNCDT---> Nikon NCDT Tags
'PANA'PanasonicPANA---> Panasonic PANA Tags
'PENT'PentaxPENT---> Pentax PENT Tags
'PXMN'MakerNotePentax5b +
MakerNotePentax5c +
MakerNotePentaxUnknown
-
-
string
--> Pentax Tags +
--> Pentax Tags
'PXTH'PentaxPreview---> Pentax PXTH Tags
'QVMI'CasioQVMI---> EXIF Tags
'RMKN'RicohRMKN---> EXIF Tags
'RTHU'PreviewImageno 
'SDLN'PlayModestring 
'SNum'SerialNumberstring/ 
'SelO'PlaySelectionint8u 
'TAGS'FujiFilmTags +
KodakTags +
KonicaMinoltaTags +
MinoltaTags +
NikonTags +
OlympusTags1 +
OlympusTags2 +
OlympusTags3 +
OlympusTags4 +
PentaxTags +
SamsungTags +
SanyoMOV +
SanyoMP4 +
UnknownTags?
-
-
-
-
-
-
-
-
-
-
-
-
-
string
--> FujiFilm MOV Tags +
--> Kodak MOV Tags +
--> Minolta MOV1 Tags +
--> Minolta MOV2 Tags +
--> Nikon MOV Tags +
--> Olympus MOV1 Tags +
--> Olympus MOV2 Tags +
--> Olympus MP4 Tags +
--> Olympus MOV3 Tags +
--> Pentax MOV Tags +
--> Samsung MP4 Tags +
--> Sanyo MOV Tags +
--> Sanyo MP4 Tags
'TTMD'TomTomMetaData---> QuickTime TomTom Tags
'WLOC'WindowLocationint16u 
'XMP_'XMP---> XMP Tags
'Xtra'MicrosoftXtra---> Microsoft Xtra Tags
'_cx_'CX?rational64s 
'_cy_'CY?rational64s 
'_yaw'Yawrational64s/ 
'albm'Albumstring/(used in 3gp videos)
'albr'AlbumArtiststring 
'angl'CameraAnglestring 
'apmd'ApertureModeundef 
'auth'Authorstring/(used in 3gp videos)
'ccid'ContentIDstring 
'cdis'ContentDistributorIDstring 
'chpl'ChapterListno 
'clfn'ClipFileNamestring 
'clid'ClipIDstring 
'clsf'Classificationundef/(string in the form "Entity=XXXX Index=### XXXXX", used in 3gp videos)
'cmid'CameraIDstring 
'cmnm'Modelstring/ 
'coll'CollectionNamestring/(used in 3gp videos)
'cprt'Copyrightstring/(used in 3gp videos)
'cver'CodeVersionstring 
'cvru'CoverURIstring 
'date'DateTimeOriginalstring(Apple Photos has been reported to show a crazy date/time for some MP4 files +containing this tag, but perhaps only if it is missing a time zone)
'dscp'Descriptionstring/(used in 3gp videos)
'gnre'Genrestring/(used in 3gp videos)
'hinf'HintTrackInfo---> QuickTime HintTrackInfo Tags
'hinv'HintVersionstring 
'hnti'HintInfo---> QuickTime HintInfo Tags
'htcb'HTCBinary---> QuickTime HTCBinary Tags
'icnu'IconURIstring 
'infu'InfoURLstring 
'kgtt'TrackTypestring 
'kywd'Keywordsno(not writable because Apple doesn't follow the 3gp specification)
'loci'LocationInformationundef/(string in the form "XXXXX Role=XXX Lat=XXX Lon=XXX Alt=XXX Body=XXX +Notes=XXX", used in 3gp videos)
'lrcu'LyricsURIstring 
'lvlm'LevelMeter?rational64s 
'manu'Makeno 
'mcvr'PreviewImagestring 
'meta'Meta---> QuickTime Meta Tags
'modl'Modelno 
'name'Namestring 
'perf'Performerstring/(used in 3gp videos)
'pmcc'GarminSettingsstring 
'pose'pose---> Kodak pose Tags
'ptch'Pitchrational64s/ 
'ptv 'PrintToVideo---> QuickTime Video Tags
'rads'Rads?rational64s 
'reel'ReelNamestring 
'roll'Rollrational64s/ 
'rtng'Ratingundef/(string in the form "Entity=XXXX Criteria=XXXX XXXXX", used in 3gp videos)
'scen'Scenestring 
'scrn'OlympusPreview---> Olympus scrn Tags
'shot'ShotNamestring 
'slno'SerialNumberstring 
'smta'SamsungSmta---> Samsung smta Tags
'tags'Audible_tags---> Audible tags Tags
'thmb'MakerNotePentax5a +
OlympusThumbnail +
ThumbnailImage +
ThumbnailPNG +
UnknownThumbnail
-
-
string
string
string
--> Pentax Tags +
--> Olympus thmb Tags
'titl'Titlestring/(used in 3gp videos)
'urat'UserRatingundef/(used in 3gp videos)
'uuid'GarminSoftware +
UUID-Unknown?
string
no
 
'vndr'Vendorstring 
'vrot'AccelerometerDatano(accelerometer readings for each frame of the video, expressed as sets of +yaw, pitch and roll angles in degrees)
'yrrc'Yearundef/(used in 3gp videos)
"©ART"Artiststring 
"©TIM"StartTimecodestring 
"©TSC"StartTimeScalestring 
"©TSZ"StartTimeSampleSizestring 
"©alb"Albumstring 
"©arg"Arrangerstring 
"©ark"ArrangerKeywordsstring 
"©cmt"Commentstring 
"©cok"ComposerKeywordsstring 
"©com"Composerstring 
"©cpy"Copyrightstring 
"©day"ContentCreateDatestring 
"©dir"Directorstring 
"©ed1"Edit1string 
"©ed2"Edit2string 
"©ed3"Edit3string 
"©ed4"Edit4string 
"©ed5"Edit5string 
"©ed6"Edit6string 
"©ed7"Edit7string 
"©ed8"Edit8string 
"©ed9"Edit9string 
"©enc"EncoderIDstring 
"©fmt"Formatstring 
"©fpt"Pitchstring 
"©frl"Rollstring 
"©fyw"Yawstring 
"©gen"Genrestring 
"©gpt"CameraPitchstring 
"©grl"CameraRollstring 
"©grp"Groupingstring 
"©gyw"CameraYawstring 
"©inf"Informationstring 
"©isr"ISRCCodestring 
"©lab"RecordLabelNamestring 
"©lal"RecordLabelURLstring 
"©lyr"Lyricsstring 
"©mak"Makestring 
"©mal"MakerURLstring 
"©mdl"Modelstring/(non-standard-format DJI tag)
"©mod"Modelstring 
"©nam"Titlestring 
"©pdk"ProducerKeywordsstring 
"©phg"RecordingCopyrightstring 
"©prd"Producerstring 
"©prf"Performersstring 
"©prk"PerformerKeywordsstring 
"©prl"PerformerURLstring 
"©req"Requirementsstring 
"©snk"SubtitleKeywordsstring 
"©snm"Subtitlestring 
"©src"SourceCreditsstring 
"©swf"SongWriterstring 
"©swk"SongWriterKeywordsstring 
"©swr"SoftwareVersionstring 
"©too"Encoderstring 
"©trk"Trackstring 
"©wrt"Composerstring/ 
"©xsp"SpeedXstring 
"©xyz"GPSCoordinatesstring 
"©ysp"SpeedYstring 
"©zsp"SpeedZstring 
+ +

QuickTime TomTom Tags

+

Tags found in TomTom Bandit Action Cam MP4 videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'TTAD'TomTomAD---> QuickTime Stream Tags
'TTHL'TomTomHL?no 
'TTID'TomTomIDno 
'TTVD'TomTomVDno 
'TTVI'TomTomVI?no 
+ +

QuickTime HintTrackInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'dimm'ImmediateDataBytesno 
'dmax'LargestPacketDurationno 
'dmed'MediaTrackBytesno 
'drep'RepeatedDataBytesno 
'maxr'MaxDataRateno 
'npck'NumPacketsno 
'nump'NumPacketsno 
'payt'PayloadTypeno 
'pmax'LargestPacketSizeno 
'tmax'MaxTransmissionTimeno 
'tmin'MinTransmissionTimeno 
'totl'TotalBytesno 
'tpaY'TotalBytesNoRTPHeadersno 
'tpay'TotalBytesNoRTPHeadersno 
'tpyl'TotalBytesNoRTPHeadersno 
'trpY'TotalBytesno 
'trpy'TotalBytesno 
+ +

QuickTime HintInfo Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'rtp 'RealtimeStreamingProtocolno 
'sdp 'StreamingDataProtocolno 
+ +

QuickTime HTCBinary Tags

+
+
+ + + + +
Index4Tag NameWritableValues / Notes
[no tags known]
+ +

QuickTime Video Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0DisplaySizeno0 = Normal +
1 = Double Size +
2 = Half Size +
3 = Full Screen +
4 = Current Size
6SlideShowno0 = No +
1 = Yes
+ +

QuickTime UserMedia Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'MTDT'MetaData---> QuickTime MetaData Tags
+ +

QuickTime MetaData Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001Titleno 
0x0003ProductionDateno 
0x0004Softwareno 
0x0005Productno 
0x000aTrackPropertyno[Value 0] +
0x0 = No presentation +
Bit 0 = Main track +
[Value 1] +
0x0 = No attributes +
Bit 15 = Read only
0x000bTimeZoneno 
0x000cModifyDateno 
+ +

QuickTime MovieHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0MovieHeaderVersionno 
1CreateDateint32u(converted from UTC to local time if the QuickTimeUTC option is set)
2ModifyDateint32u(converted from UTC to local time if the QuickTimeUTC option is set)
3TimeScaleno 
4Durationno 
5PreferredRateno 
6PreferredVolumeno 
9MatrixStructureno 
18PreviewTimeno 
19PreviewDurationno 
20PosterTimeno 
21SelectionTimeno 
22SelectionDurationno 
23CurrentTimeno 
24NextTrackIDno 
+ +

QuickTime Preview Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0PreviewDateint32u(converted from UTC to local time if the QuickTimeUTC option is set)
2PreviewVersionno 
3PreviewAtomTypeno 
5PreviewAtomIndexno 
+ +

QuickTime SkipInfo Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'thma'ThumbnailImageno 
'ver 'Versionno 
+ +

QuickTime Profile Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'APRF'AudioProfile---> QuickTime AudioProf Tags
'FPRF'FileGlobalProfile---> QuickTime FileProf Tags
'OLYM'OlympusOLYM---> Olympus OLYM Tags
'VPRF'VideoProfile---> QuickTime VideoProf Tags
+ +

QuickTime AudioProf Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0AudioProfileVersion?no 
1AudioTrackIDno 
2AudioCodecno 
3AudioCodecInfo?no 
4AudioAttributesnoBit 0 = Encrypted +
Bit 1 = Variable bitrate +
Bit 2 = Dual mono
5AudioAvgBitrateno 
6AudioMaxBitrateno 
7AudioSampleRateno 
8AudioChannelsno 
+ +

QuickTime FileProf Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0FileProfileVersion?no 
1FileFunctionFlagsnoBit 28 = Fragmented +
Bit 29 = Additional tracks +
Bit 30 = Edited
+ +

QuickTime VideoProf Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0VideoProfileVersion?no 
1VideoTrackIDno 
2VideoCodecno 
3VideoCodecInfo?no 
4VideoAttributesnoBit 0 = Encrypted +
Bit 1 = Variable bitrate +
Bit 2 = Variable frame rate +
Bit 3 = Interlaced
5VideoAvgBitrateno 
6VideoMaxBitrateno 
7VideoAvgFrameRateno 
8VideoMaxFrameRateno 
9VideoSizeno 
10PixelAspectRationo 
+ +

QuickTime Flip Tags

+

Found in MP4 files from Flip Video cameras.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1PreviewImageWidthno 
2PreviewImageHeightno 
13PreviewImageLengthno 
14SerialNumberno 
28PreviewImageno 
+ +

QuickTime Tags360Fly Tags

+

Timed metadata found in MP4 videos from the 360Fly.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001Accel360Fly---> QuickTime Accel360Fly Tags
0x0002Gyro360Fly---> QuickTime Gyro360Fly Tags
0x0003Mag360Fly---> QuickTime Mag360Fly Tags
0x0005GPS360Fly---> QuickTime GPS360Fly Tags
0x0006Rot360Fly---> QuickTime Rot360Fly Tags
0x00faFusion360Fly---> QuickTime Fusion360Fly Tags
+ +

QuickTime Accel360Fly Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1AccelMode?no 
2SampleTimeno 
10AccelYPRno 
+ +

QuickTime Gyro360Fly Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1GyroMode?no 
2SampleTimeno 
10GyroYPRno 
+ +

QuickTime Mag360Fly Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1MagMode?no 
2SampleTimeno 
10MagnetometerXYZno 
+ +

QuickTime GPS360Fly Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1GPSMode?no 
2SampleTimeno 
10GPSLatitudeno 
14GPSLongitudeno 
18GPSAltitudeno 
22GPSSpeedno(converted to km/hr)
24GPSTrackno 
26Accelerationno 
+ +

QuickTime Rot360Fly Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1RotMode?no 
2SampleTimeno 
10RotationXYZno 
+ +

QuickTime Fusion360Fly Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1FusionMode?no 
2SampleTimeno 
10FusionYPRno 
+ +

QuickTime ImageFile Tags

+

Tags used in QTIF QuickTime Image Files.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'idat'ImageDatano 
'idsc'ImageDescription---> QuickTime ImageDesc Tags
'iicc'ICC_Profile---> ICC_Profile Tags
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 10, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/RIFF.html b/ExifTool/html/TagNames/RIFF.html new file mode 100644 index 0000000..4f6a613 --- /dev/null +++ b/ExifTool/html/TagNames/RIFF.html @@ -0,0 +1,1748 @@ + + + + +RIFF Tags + + + +

RIFF Tags

+

The RIFF container format is used various types of fines including AVI, WAV, +WEBP, LA, OFR, PAC and WV. According to the EXIF specification, Meta +information is embedded in two types of RIFF LIST chunks: INFO and +exif, and information about the audio content is stored in the fmt +chunk. As well as this information, some video information and proprietary +manufacturer-specific information is also extracted.

+ +

Large AVI videos may be a concatenation of two or more RIFF chunks. For +these files, information is extracted from subsequent RIFF chunks as +sub-documents, but the Duration is calculated for the full video.

+ +

ExifTool currently has the ability to write EXIF, XMP and ICC_Profile +metadata to WEBP images, but can't yet write to other RIFF-based formats.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ALPH'ALPH---> RIFF ALPH Tags
'ANIM'ANIM---> RIFF ANIM Tags
'ANMF'ANMF---> RIFF ANMF Tags
'CSET'CharacterSet---> RIFF CSET Tags
'EXIF'EXIF +
UnknownEXIF
-
no
(WebP files) +
--> EXIF Tags +
--> EXIF Tags
'ICCP'ICC_Profile---> ICC_Profile Tags +
(WebP files)
'IDIT'DateTimeOriginalno 
'JUNK'OlympusJunk +
CasioJunk +
RicohJunk +
PentaxJunk +
PentaxJunk2 +
LucasJunk +
TextJunk
-
-
-
-
-
-
no
--> Olympus AVI Tags +
--> EXIF Tags +
--> Ricoh AVI Tags +
--> Pentax Junk Tags +
--> Pentax Junk2 Tags +
--> QuickTime Stream Tags
'JUNQ'OldXMPno 
'LIST_INF0'Info---> RIFF Info Tags
'LIST_INFO'Info---> RIFF Info Tags
'LIST_Tdat'Tdat---> RIFF Tdat Tags
'LIST_adtl'AssociatedDataList---> RIFF Tags
'LIST_exif'Exif---> RIFF Exif Tags
'LIST_hdrl'Hdrl---> RIFF Hdrl Tags
'LIST_hydt'PentaxData---> Pentax AVI Tags
'LIST_ncdt'NikonData---> Nikon AVI Tags
'LIST_pntx'PentaxData2---> Pentax AVI Tags
'SGLT'BikeBroAccel---> QuickTime Stream Tags
'SLLT'BikeBroGPS---> QuickTime Stream Tags
'VP8 'VP8Bitstream---> RIFF VP8 Tags
'VP8L'VP8L---> RIFF VP8L Tags
'VP8X'VP8X---> RIFF VP8X Tags
'XMP 'XMP---> XMP Tags +
(WebP files)
'_PMX'XMP---> XMP Tags +
(AVI and WAV files)
'aXML'AXML---> XMP XML Tags
'acid'Acidizer---> RIFF Acidizer Tags
'afsp'Afspno 
'bext'BroadcastExtension---> RIFF BroadcastExt Tags
'cue 'CuePointsno(config_files/cutepointlist.config from full distribution will decode this +and generate a list of cue points with labels)
'ds64'DataSize64---> RIFF DS64 Tags
'fact'NumberOfSamplesno 
'fmt 'AudioFormat---> RIFF AudioFormat Tags
'gps0'GPSTrack---> QuickTime Stream Tags
'gsen'GSensor---> QuickTime Stream Tags
'guan'Guanono 
'iXML'IXML---> XMP XML Tags
'id3 'ID3---> ID3 Tags
'inst'Instrument---> RIFF Instrument Tags
'labl'CuePointLabelno 
'list'ListTypeno 
'ltxt'LabeledTextno(CuePointID Length Purpose Country Language Dialect Codepage Text)
'note'CuePointNoteno 
'olym'Olym---> Olympus WAV Tags
'plst'Playlistno 
'smpl'Sampler---> RIFF Sampler Tags
'tx_USER'UserText---> RIFF UserText Tags
'tx_Unknown'Textno(streamed text, extracted when the ExtractEmbedded option is used)
+ +

RIFF ALPH Tags

+

WebP alpha chunk.

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AlphaPreprocessingno[val & 0x3] +
0 = none +
1 = Level Reduction
0.1AlphaFilteringno[val & 0x3] +
0 = none +
1 = Horizontal +
2 = Vertical +
3 = Gradient
0.2AlphaCompressionno[val & 0x3] +
0 = none +
1 = Lossless
+ +

RIFF ANIM Tags

+

WebP animation chunk.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0BackgroundColorno 
4AnimationLoopCountno 
+ +

RIFF ANMF Tags

+

WebP animation frame chunk.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
12Durationno(extracted as the sum of durations of all animation frames)
+ +

RIFF CSET Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0CodePageno 
1CountryCodeno 
2LanguageCodeno 
3Dialectno 
+ +

RIFF Info Tags

+

RIFF INFO tags found in AVI video and WAV audio files. Tags which are part +of the EXIF 2.3 specification have an underlined Tag Name in the HTML +version of this documentation. Other tags are found in AVI files generated +by some software.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AGES'Ratedno 
'CMNT'Commentno 
'CODE'EncodedByno 
'COMM'Commentsno 
'DIRC'Directoryno 
'DISP'SoundSchemeTitleno 
'DTIM'DateTimeOriginalno 
'GENR'Genreno 
'IARL'ArchivalLocationno 
'IART'Artistno 
'IAS1'FirstLanguageno 
'IAS2'SecondLanguageno 
'IAS3'ThirdLanguageno 
'IAS4'FourthLanguageno 
'IAS5'FifthLanguageno 
'IAS6'SixthLanguageno 
'IAS7'SeventhLanguageno 
'IAS8'EighthLanguageno 
'IAS9'NinthLanguageno 
'IBSU'BaseURLno 
'ICAS'DefaultAudioStreamno 
'ICDS'CostumeDesignerno 
'ICMS'Commissionedno 
'ICMT'Commentno 
'ICNM'Cinematographerno 
'ICNT'Countryno 
'ICOP'Copyrightno 
'ICRD'DateCreatedno 
'ICRP'Croppedno 
'IDIM'Dimensionsno 
'IDIT'DateTimeOriginalno 
'IDPI'DotsPerInchno 
'IDST'DistributedByno 
'IEDT'EditedByno 
'IENC'EncodedByno 
'IENG'Engineerno 
'IGNR'Genreno 
'IKEY'Keywordsno 
'ILGT'Lightnessno 
'ILGU'LogoURLno 
'ILIU'LogoIconURLno 
'ILNG'Languageno 
'IMBI'MoreInfoBannerImageno 
'IMBU'MoreInfoBannerURLno 
'IMED'Mediumno 
'IMIT'MoreInfoTextno 
'IMIU'MoreInfoURLno 
'IMUS'MusicByno 
'INAM'Titleno 
'IPDS'ProductionDesignerno 
'IPLT'NumColorsno 
'IPRD'Productno 
'IPRO'ProducedByno 
'IRIP'RippedByno 
'IRTD'Ratingno 
'ISBJ'Subjectno 
'ISFT'Softwareno 
'ISGN'SecondaryGenreno 
'ISHP'Sharpnessno 
'ISMP'TimeCodeno 
'ISRC'Sourceno 
'ISRF'SourceFormno 
'ISTD'ProductionStudiono 
'ISTR'Starringno 
'ITCH'Technicianno 
'ITRK'TrackNumberno 
'IWMU'WatermarkURLno 
'IWRI'WrittenByno 
'LANG'Languageno 
'LOCA'Locationno 
'PRT1'Partno 
'PRT2'NumberOfPartsno 
'RATE'Rateno 
'STAR'Starringno 
'STAT'Statisticsno[Value 3] +
0 = Bad +
1 = OK
'TAPE'TapeNameno 
'TCDO'EndTimecodeno 
'TCOD'StartTimecodeno 
'TITL'Titleno 
'TLEN'Lengthno 
'TORG'Organizationno 
'TRCK'TrackNumberno 
'TURL'URLno 
'TVER'Versionno 
'VMAJ'VegasVersionMajorno 
'VMIN'VegasVersionMinorno 
'YEAR'Yearno 
+ +

RIFF Tdat Tags

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

RIFF Exif Tags

+

These tags are part of the EXIF 2.3 specification for WAV audio files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ecor'Makeno 
'emdl'Modelno 
'emnt'MakerNotesno 
'erel'RelatedImageFileno 
'etim'TimeCreatedno 
'eucm'UserCommentno 
'ever'ExifVersionno 
+ +

RIFF Hdrl Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'IDIT'DateTimeOriginalno 
'ISMP'TimeCodeno 
'LIST_odml'OpenDML---> RIFF OpenDML Tags
'LIST_strl'Stream---> RIFF Stream Tags
'avih'AVIHeader---> RIFF AVIHeader Tags
+ +

RIFF OpenDML Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'dmlh'ExtendedAVIHeader---> RIFF ExtAVIHdr Tags
+ +

RIFF ExtAVIHdr Tags

+
+
+ + + + + + + + +
Index4Tag NameWritableValues / Notes
0TotalFrameCountno 
+ +

RIFF Stream Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'strd'StreamData---> RIFF StreamData Tags
'strf'AudioFormat +
VideoFormat
-
-
--> RIFF AudioFormat Tags +
--> BMP Tags
'strh'StreamHeader---> RIFF StreamHeader Tags
'strn'StreamNameno 
+ +

RIFF StreamData Tags

+

This chunk is used to store proprietary information in AVI videos from some +cameras. The first 4 characters of the data are used as the Tag ID below.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AVIF'AVIF---> EXIF Tags
'CASI'CasioData---> Casio AVI Tags
'Zora'VendorNameno 
'unknown'UnknownDatano 
+ +

RIFF AudioFormat Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0Encodingno--> RIFF AudioEncoding Values
1NumChannelsno 
2SampleRateno 
4AvgBytesPerSecno 
7BitsPerSampleno 
+ +

RIFF AudioEncoding Values

+

These "TwoCC" audio encoding codes are used in RIFF and ASF files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueAudioEncoding
0x1= Microsoft PCM
0x2= Microsoft ADPCM
0x3= Microsoft IEEE float
0x4= Compaq VSELP
0x5= IBM CVSD
0x6= Microsoft a-Law
0x7= Microsoft u-Law
0x8= Microsoft DTS
0x9= DRM
0xa= WMA 9 Speech
0xb= Microsoft Windows Media RT Voice
0x10= OKI-ADPCM
0x11= Intel IMA/DVI-ADPCM
0x12= Videologic Mediaspace ADPCM
0x13= Sierra ADPCM
0x14= Antex G.723 ADPCM
0x15= DSP Solutions DIGISTD
0x16= DSP Solutions DIGIFIX
0x17= Dialoic OKI ADPCM
0x18= Media Vision ADPCM
0x19= HP CU
0x1a= HP Dynamic Voice
0x20= Yamaha ADPCM
0x21= SONARC Speech Compression
0x22= DSP Group True Speech
0x23= Echo Speech Corp.
0x24= Virtual Music Audiofile AF36
0x25= Audio Processing Tech.
0x26= Virtual Music Audiofile AF10
0x27= Aculab Prosody 1612
0x28= Merging Tech. LRC
0x30= Dolby AC2
0x31= Microsoft GSM610
0x32= MSN Audio
0x33= Antex ADPCME
0x34= Control Resources VQLPC
0x35= DSP Solutions DIGIREAL
0x36= DSP Solutions DIGIADPCM
0x37= Control Resources CR10
0x38= Natural MicroSystems VBX ADPCM
0x39= Crystal Semiconductor IMA ADPCM
0x3a= Echo Speech ECHOSC3
0x3b= Rockwell ADPCM
0x3c= Rockwell DIGITALK
0x3d= Xebec Multimedia
0x40= Antex G.721 ADPCM
0x41= Antex G.728 CELP
0x42= Microsoft MSG723
0x43= IBM AVC ADPCM
0x45= ITU-T G.726
0x50= Microsoft MPEG
0x51= RT23 or PAC
0x52= InSoft RT24
0x53= InSoft PAC
0x55= MP3
0x59= Cirrus
0x60= Cirrus Logic
0x61= ESS Tech. PCM
0x62= Voxware Inc.
0x63= Canopus ATRAC
0x64= APICOM G.726 ADPCM
0x65= APICOM G.722 ADPCM
0x66= Microsoft DSAT
0x67= Microsoft DSAT DISPLAY
0x69= Voxware Byte Aligned
0x70= Voxware AC8
0x71= Voxware AC10
0x72= Voxware AC16
0x73= Voxware AC20
0x74= Voxware MetaVoice
0x75= Voxware MetaSound
0x76= Voxware RT29HW
0x77= Voxware VR12
0x78= Voxware VR18
0x79= Voxware TQ40
0x7a= Voxware SC3
0x7b= Voxware SC3
0x80= Soundsoft
0x81= Voxware TQ60
0x82= Microsoft MSRT24
0x83= AT&T G.729A
0x84= Motion Pixels MVI MV12
0x85= DataFusion G.726
0x86= DataFusion GSM610
0x88= Iterated Systems Audio
0x89= Onlive
0x8a= Multitude, Inc. FT SX20
0x8b= Infocom ITS A/S G.721 ADPCM
0x8c= Convedia G729
0x8d= Not specified congruency, Inc.
0x91= Siemens SBC24
0x92= Sonic Foundry Dolby AC3 APDIF
0x93= MediaSonic G.723
0x94= Aculab Prosody 8kbps
0x97= ZyXEL ADPCM
0x98= Philips LPCBB
0x99= Studer Professional Audio Packed
0xa0= Malden PhonyTalk
0xa1= Racal Recorder GSM
0xa2= Racal Recorder G720.a
0xa3= Racal G723.1
0xa4= Racal Tetra ACELP
0xb0= NEC AAC NEC Corporation
0xff= AAC
0x100= Rhetorex ADPCM
0x101= IBM u-Law
0x102= IBM a-Law
0x103= IBM ADPCM
0x111= Vivo G.723
0x112= Vivo Siren
0x120= Philips Speech Processing CELP
0x121= Philips Speech Processing GRUNDIG
0x123= Digital G.723
0x125= Sanyo LD ADPCM
0x130= Sipro Lab ACEPLNET
0x131= Sipro Lab ACELP4800
0x132= Sipro Lab ACELP8V3
0x133= Sipro Lab G.729
0x134= Sipro Lab G.729A
0x135= Sipro Lab Kelvin
0x136= VoiceAge AMR
0x140= Dictaphone G.726 ADPCM
0x150= Qualcomm PureVoice
0x151= Qualcomm HalfRate
0x155= Ring Zero Systems TUBGSM
0x160= Microsoft Audio1
0x161= Windows Media Audio V2 V7 V8 V9 / DivX audio (WMA) / Alex AC3 Audio
0x162= Windows Media Audio Professional V9
0x163= Windows Media Audio Lossless V9
0x164= WMA Pro over S/PDIF
0x170= UNISYS NAP ADPCM
0x171= UNISYS NAP ULAW
0x172= UNISYS NAP ALAW
0x173= UNISYS NAP 16K
0x174= MM SYCOM ACM SYC008 SyCom Technologies
0x175= MM SYCOM ACM SYC701 G726L SyCom Technologies
0x176= MM SYCOM ACM SYC701 CELP54 SyCom Technologies
0x177= MM SYCOM ACM SYC701 CELP68 SyCom Technologies
0x178= Knowledge Adventure ADPCM
0x180= Fraunhofer IIS MPEG2AAC
0x190= Digital Theater Systems DTS DS
0x200= Creative Labs ADPCM
0x202= Creative Labs FASTSPEECH8
0x203= Creative Labs FASTSPEECH10
0x210= UHER ADPCM
0x215= Ulead DV ACM
0x216= Ulead DV ACM
0x220= Quarterdeck Corp.
0x230= I-Link VC
0x240= Aureal Semiconductor Raw Sport
0x241= ESST AC3
0x250= Interactive Products HSX
0x251= Interactive Products RPELP
0x260= Consistent CS2
0x270= Sony SCX
0x271= Sony SCY
0x272= Sony ATRAC3
0x273= Sony SPC
0x280= TELUM Telum Inc.
0x281= TELUMIA Telum Inc.
0x285= Norcom Voice Systems ADPCM
0x300= Fujitsu FM TOWNS SND
0x301= Fujitsu (not specified)
0x302= Fujitsu (not specified)
0x303= Fujitsu (not specified)
0x304= Fujitsu (not specified)
0x305= Fujitsu (not specified)
0x306= Fujitsu (not specified)
0x307= Fujitsu (not specified)
0x308= Fujitsu (not specified)
0x350= Micronas Semiconductors, Inc. Development
0x351= Micronas Semiconductors, Inc. CELP833
0x400= Brooktree Digital
0x401= Intel Music Coder (IMC)
0x402= Ligos Indeo Audio
0x450= QDesign Music
0x500= On2 VP7 On2 Technologies
0x501= On2 VP6 On2 Technologies
0x680= AT&T VME VMPCM
0x681= AT&T TCP
0x700= YMPEG Alpha (dummy for MPEG-2 compressor)
0x8ae= ClearJump LiteWave (lossless)
0x1000= Olivetti GSM
0x1001= Olivetti ADPCM
0x1002= Olivetti CELP
0x1003= Olivetti SBC
0x1004= Olivetti OPR
0x1100= Lernout & Hauspie
0x1101= Lernout & Hauspie CELP codec
0x1102= Lernout & Hauspie SBC codec
0x1103= Lernout & Hauspie SBC codec
0x1104= Lernout & Hauspie SBC codec
0x1400= Norris Comm. Inc.
0x1401= ISIAudio
0x1500= AT&T Soundspace Music Compression
0x181c= VoxWare RT24 speech codec
0x181e= Lucent elemedia AX24000P Music codec
0x1971= Sonic Foundry LOSSLESS
0x1979= Innings Telecom Inc. ADPCM
0x1c07= Lucent SX8300P speech codec
0x1c0c= Lucent SX5363S G.723 compliant codec
0x1f03= CUseeMe DigiTalk (ex-Rocwell)
0x1fc4= NCT Soft ALF2CD ACM
0x2000= FAST Multimedia DVM
0x2001= Dolby DTS (Digital Theater System)
0x2002= RealAudio 1 / 2 14.4
0x2003= RealAudio 1 / 2 28.8
0x2004= RealAudio G2 / 8 Cook (low bitrate)
0x2005= RealAudio 3 / 4 / 5 Music (DNET)
0x2006= RealAudio 10 AAC (RAAC)
0x2007= RealAudio 10 AAC+ (RACP)
0x2500= Reserved range to 0x2600 Microsoft
0x3313= makeAVIS (ffvfw fake AVI sound from AviSynth scripts)
0x4143= Divio MPEG-4 AAC audio
0x4201= Nokia adaptive multirate
0x4243= Divio G726 Divio, Inc.
0x434c= LEAD Speech
0x564c= LEAD Vorbis
0x5756= WavPack Audio
0x674f= Ogg Vorbis (mode 1)
0x6750= Ogg Vorbis (mode 2)
0x6751= Ogg Vorbis (mode 3)
0x676f= Ogg Vorbis (mode 1+)
0x6770= Ogg Vorbis (mode 2+)
0x6771= Ogg Vorbis (mode 3+)
0x7000= 3COM NBX 3Com Corporation
0x706d= FAAD AAC
0x7a21= GSM-AMR (CBR, no SID)
0x7a22= GSM-AMR (VBR, including SID)
0xa100= Comverse Infosys Ltd. G723 1
0xa101= Comverse Infosys Ltd. AVQSBC
0xa102= Comverse Infosys Ltd. OLDSBC
0xa103= Symbol Technologies G729A
0xa104= VoiceAge AMR WB VoiceAge Corporation
0xa105= Ingenient Technologies Inc. G726
0xa106= ISO/MPEG-4 advanced audio Coding
0xa107= Encore Software Ltd G726
0xa109= Speex ACM Codec xiph.org
0xdfac= DebugMode SonicFoundry Vegas FrameServer ACM Codec
0xe708= Unknown -
0xf1ac= Free Lossless Audio Codec FLAC
0xfffe= Extensible
0xffff= Development
+ +

RIFF StreamHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0StreamTypeno'auds' = Audio +
'iavs' = Interleaved Audio+Video +
'mids' = MIDI +
'txts' = Text +
'vids' = Video
1AudioCodec +
VideoCodec +
Codec
no
no
no
 
5AudioSampleRate +
VideoFrameRate +
StreamSampleRate
no
no
no
 
8AudioSampleCount +
VideoFrameCount +
StreamSampleCount
no
no
no
 
10Qualityno 
11SampleSizeno 
+ +

RIFF AVIHeader Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0FrameRateno 
1MaxDataRateno 
4FrameCountno 
6StreamCountno 
8ImageWidthno 
9ImageHeightno 
+ +

RIFF VP8 Tags

+

This chunk is found in simple-format (lossy) WebP files. See +https://developers.google.com/speed/webp/docs/riff_container for the WebP +container specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0VP8Versionno[val >> 1 & 0x7] +
0 = 0 (bicubic reconstruction, normal loop) +
1 = 1 (bilinear reconstruction, simple loop) +
2 = 2 (bilinear reconstruction, no loop) +
3 = 3 (no reconstruction, no loop)
6ImageWidthno[val & 0x3fff]
6.1HorizontalScaleno[val >> 14 & 0x3]
8ImageHeightno[val & 0x3fff]
8.1VerticalScaleno[val >> 14 & 0x3]
+ +

RIFF VP8L Tags

+

This chunk is found in lossless WebP files.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1ImageWidthno 
2ImageHeightno 
+ +

RIFF VP8X Tags

+

This chunk is found in extended WebP files.

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0WebP_Flagsno(flags used in Extended WebP images) +
Bit 1 = Animation +
Bit 2 = XMP +
Bit 3 = EXIF +
Bit 4 = Alpha +
Bit 5 = ICC Profile
4ImageWidthno 
6ImageHeightno 
+ +

RIFF Acidizer Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AcidizerFlagsnoBit 0 = One shot +
Bit 1 = Root note set +
Bit 2 = Stretch +
Bit 3 = Disk-based +
Bit 4 = High octave
4RootNoteno + + +
48 = C +
49 = C# +
50 = D +
51 = D# +
52 = E +
53 = F +
54 = F# +
55 = G
  56 = G# +
57 = A +
58 = A# +
59 = B +
60 = High C +
61 = High C# +
62 = High D +
63 = High D#
  64 = High E +
65 = High F +
66 = High F# +
67 = High G +
68 = High G# +
69 = High A +
70 = High A# +
71 = High B
+
12Beatsno 
16Meterno 
20Tempono 
+ +

RIFF BroadcastExt Tags

+

Information found in the Broadcast Audio Extension chunk (see +http://tech.ebu.ch/docs/tech/tech3285.pdf).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Descriptionno 
256Originatorno 
288OriginatorReferenceno 
320DateTimeOriginalno 
338TimeReferenceno(first sample count since midnight)
346BWFVersionno 
348BWF_UMIDno 
602CodingHistoryno 
+ +

RIFF DS64 Tags

+

64-bit data sizes for MBWF/RF64 files. See +https://tech.ebu.ch/docs/tech/tech3306-2009.pdf for the specification.

+
+
+ + + + + + + + + + + + + + + + + + +
Index8Tag NameWritableValues / Notes
0RIFFSize64no 
1DataSize64no 
2NumberOfSamples64no 
+ +

RIFF Instrument Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0UnshiftedNoteno 
1FineTuneno 
2Gainno 
3LowNoteno 
4HighNoteno 
5LowVelocityno 
6HighVelocityno 
+ +

RIFF Sampler Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0Manufacturerno 
1Productno 
2SamplePeriodno 
3MIDIUnityNoteno 
4MIDIPitchFractionno 
5SMPTEFormatno0 = none +
24 = 24 fps +
25 = 25 fps +
29 = 29 fps +
30 = 30 fps
6SMPTEOffsetno(HH:MM:SS:FF)
7NumSampleLoopsno 
8SamplerDataLenno 
9SamplerDatano 
+ +

RIFF UserText Tags

+

Tags decoded from the USER-format txts stream written by Momento M6 dashcam. +Extracted only if the ExtractEmbedded option is used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
28GPSAltitudeno 
40Accelerometerno 
56GPSSpeedno 
60GPSLatitudeno 
64GPSLongitudeno 
68GPSDateTimeno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 8, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/RSRC.html b/ExifTool/html/TagNames/RSRC.html new file mode 100644 index 0000000..ca4d28b --- /dev/null +++ b/ExifTool/html/TagNames/RSRC.html @@ -0,0 +1,75 @@ + + + + +RSRC Tags + + + +

RSRC Tags

+

Tags extracted from Mac OS resource files, DFONT files and "._" sidecar +files. These tags may also be extracted from the resource fork of any file +in OS X, either by adding "/..namedfork/rsrc" to the filename to process the +resource fork alone, or by using the ExtractEmbedded (-ee) option to process +the resource fork as a sub-document of the main file. When writing, +ExifTool preserves the Mac OS resource fork by default, but it may deleted +with -rsrc:all= on the command line.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'8BIM'PhotoshopInfo---> Photoshop Tags
'POST_0x01f5'PostscriptFont---> PostScript Tags
'STR _0xbff3'ApplicationMissingMsgno 
'STR _0xbff4'CreatorApplicationno 
'STR#_0x0080'Keywordsno 
'TEXT_0x0080'Descriptionno 
'sfnt'Font---> Font Name Tags
'usro_0x0000'OpenWithApplicationno 
'vers_0x0001'ApplicationVersionno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 15, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/RTF.html b/ExifTool/html/TagNames/RTF.html new file mode 100644 index 0000000..7458ff1 --- /dev/null +++ b/ExifTool/html/TagNames/RTF.html @@ -0,0 +1,151 @@ + + + + +RTF Tags + + + +

RTF Tags

+

This table lists standard tags of the RTF information group, but ExifTool +will also extract any non-standard tags found in this group. As well, +ExifTool will extract any custom properties that are found. See +http://www.microsoft.com/en-ca/download/details.aspx?id=10725 for the +specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'author'Authorno 
'buptim'BackupTimeno 
'category'Categoryno 
'comment'Commentno 
'company'Companyno 
'copyright'Copyrightno 
'creatim'CreateDateno 
'doccomm'Commentsno 
'edmins'TotalEditTimeno 
'hlinkbase'HyperlinkBaseno 
'id'InternalIDNumberno 
'keywords'Keywordsno 
'manager'Managerno 
'nofchars'Charactersno 
'nofcharsws'CharactersWithSpacesno(according to the 2007 Microsoft RTF specification this is clearly the number +of characters NOT including spaces, but Microsoft Word writes this as the +number WITH spaces, so ExifTool names this tag according to the de facto +standard)
'nofpages'Pagesno 
'nofwords'Wordsno 
'operator'LastModifiedByno 
'printim'LastPrintedno 
'revtim'ModifyDateno 
'subject'Subjectno 
'title'Titleno 
'vern'InternalVersionNumberno 
'version'RevisionNumberno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 30, 2012 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Radiance.html b/ExifTool/html/TagNames/Radiance.html new file mode 100644 index 0000000..d15c913 --- /dev/null +++ b/ExifTool/html/TagNames/Radiance.html @@ -0,0 +1,93 @@ + + + + +Radiance Tags + + + +

Radiance Tags

+

Information extracted from Radiance RGBE HDR images. Tag ID's are all +uppercase as stored in the file, but converted to lowercase by when +extracting to avoid conflicts with internal ExifTool variables. See +http://radsite.lbl.gov/radiance/refer/filefmts.pdf and +http://www.graphics.cornell.edu/online/formats/rgbe/ for the +specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'_command'Commandno 
'_comment'Commentno 
'_orient'Orientationno +
'+X +Y' = Rotate 90 CW +
'+X -Y' = Mirror horizontal and rotate 270 CW +
'+Y +X' = Mirror vertical +
'+Y -X' = Rotate 180 +
'-X +Y' = Mirror horizontal and rotate 90 CW +
'-X -Y' = Rotate 270 CW +
'-Y +X' = Horizontal (normal) +
'-Y -X' = Mirror horizontal
+
'colorcorr'ColorCorrectionno 
'exposure'Exposureno(divide pixel values by this to get watts/steradian/meter^2)
'format'Formatno 
'gamma'Gammano 
'pixaspect'PixelAspectRationo 
'primaries'ColorPrimariesno 
'software'Softwareno 
'view'Viewno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Oct 29, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Rawzor.html b/ExifTool/html/TagNames/Rawzor.html new file mode 100644 index 0000000..bf2f117 --- /dev/null +++ b/ExifTool/html/TagNames/Rawzor.html @@ -0,0 +1,46 @@ + + + + +Rawzor Tags + + + +

Rawzor Tags

+

Rawzor files store compressed images of other formats. As well as the +information listed below, exiftool uncompresses and extracts the meta +information from the original image.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CompressionFactorno 
OriginalFileSizeno 
OriginalFileTypeno 
RawzorCreatorVersionno 
RawzorRequiredVersionno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 8, 2010 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Real.html b/ExifTool/html/TagNames/Real.html new file mode 100644 index 0000000..bc2a2eb --- /dev/null +++ b/ExifTool/html/TagNames/Real.html @@ -0,0 +1,781 @@ + + + + +Real Tags + + + +

Real Tags

+

+ExifTool recognizes three basic types of Real audio/video files: 1) +RealMedia (RM, RV and RMVB), 2) RealAudio (RA), and 3) Real Metafile (RAM +and RPM). +

+

Real Media Tags

+

These Tag ID's are Chunk ID's used in RealMedia and RealVideo (RM, RV and +RMVB) files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'CONT'CONT---> Real ContentDescr Tags
'MDPR'MDPR---> Real MediaProps Tags
'PROP'PROP---> Real Properties Tags
'RJMD'RJMD---> Real Metadata Tags
+ +

Real ContentDescr Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceTag NameWritableValues / Notes
0TitleLen?no 
1Titleno 
2AuthorLen?no 
3Authorno 
4CopyrightLen?no 
5Copyrightno 
6CommentLen?no 
7Commentno 
+ +

Real MediaProps Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceTag NameWritableValues / Notes
0StreamNumberno 
1StreamMaxBitrateno 
2StreamAvgBitrateno 
3StreamMaxPacketSizeno 
4StreamAvgPacketSizeno 
5StreamStartTimeno 
6StreamPrerollno 
7StreamDurationno 
8StreamNameLen?no 
9StreamNameno 
10StreamMimeLen?no 
11StreamMimeTypeno 
12FileInfoLen?no 
13FileInfoLen2?no 
14FileInfoVersionno 
15PhysicalStreams?no 
16PhysicalStreamNumbers?no 
17DataOffsets?no 
18NumRules?no 
19PhysicalStreamNumberMap?no 
20NumProperties?no 
21FileInfoProperties---> Real FileInfo Tags
+ +

Real FileInfo Tags

+

The following tags have been observed in the FileInfo properties, but any +other existing information will also be extracted.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Audiences'Audiencesno 
'Audio Format'AudioFormatno 
'Content Rating'ContentRatingno +
0 = No Rating +
1 = All Ages +
2 = Older Children +
3 = Younger Teens +
4 = Older Teens +
5 = Adult Supervision Recommended +
6 = Adults Only
+
'Creation Date'CreateDateno 
'Description'Descriptionno 
'File ID'FileIDno 
'Generated By'Softwareno 
'Indexable'Indexableno0 = False +
1 = True
'Keywords'Keywordsno 
'Modification Date'ModifyDateno 
'Target Audiences'TargetAudiencesno 
'Video Quality'VideoQualityno 
'audioMode'AudioModeno 
'videoMode'VideoModeno 
+ +

Real Properties Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceTag NameWritableValues / Notes
0MaxBitrateno 
1AvgBitrateno 
2MaxPacketSizeno 
3AvgPacketSizeno 
4NumPacketsno 
5Durationno 
6Prerollno 
7IndexOffset?no 
8DataOffset?no 
9NumStreamsno 
10FlagsnoBit 0 = Allow Recording +
Bit 1 = Perfect Play +
Bit 2 = Live +
Bit 3 = Allow Download
+ +

Real Metadata Tags

+

The tags below represent information which has been observed in the Real +Metadata format, but ExifTool will extract any information it finds in this +format. (As far as I can tell from the referenced documentation, string +values should be plain text, but this is not the case for the only sample +file I have been able to obtain containing this information. These tags +could also be split into separate sub-directories, but this will wait until +I have better documentation or a more complete set of samples.)

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Album/Name'AlbumNameno 
'Track/Category'TrackCategoryno 
'Track/Comments'TrackCommentsno 
'Track/Lyrics'TrackLyricsno 
+ +

Real Audio Tags

+

Tags in the following table reference information extracted from various +versions of RealAudio (RA) files.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'.ra3'RA3---> Real AudioV3 Tags
'.ra4'RA4---> Real AudioV4 Tags
'.ra5'RA5---> Real AudioV5 Tags
+ +

Real AudioV3 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceTag NameWritableValues / Notes
0Channelsno 
1Unknown?no 
2BytesPerMinuteno 
3AudioBytesno 
4TitleLen?no 
5Titleno 
6ArtistLen?no 
7Artistno 
8CopyrightLen?no 
9Copyrightno 
10CommentLen?no 
11Commentno 
+ +

Real AudioV4 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceTag NameWritableValues / Notes
0FourCC1?no 
1AudioFileSize?no 
2Version2?no 
3HeaderSize?no 
4CodecFlavorID?no 
5CodedFrameSize?no 
6AudioBytesno 
7BytesPerMinuteno 
8Unknown?no 
9SubPacketH?no 
10AudioFrameSizeno 
11SubPacketSize?no 
12Unknown?no 
13SampleRateno 
14Unknown?no 
15BitsPerSampleno 
16Channelsno 
17FourCC2Len?no 
18FourCC2?no 
19FourCC3Len?no 
20FourCC3?no 
21Unknown?no 
22Unknown?no 
23TitleLen?no 
24Titleno 
25ArtistLen?no 
26Artistno 
27CopyrightLen?no 
28Copyrightno 
29CommentLen?no 
30Commentno 
+ +

Real AudioV5 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceTag NameWritableValues / Notes
0FourCC1?no 
1AudioFileSize?no 
2Version2?no 
3HeaderSize?no 
4CodecFlavorID?no 
5CodedFrameSize?no 
6AudioBytesno 
7BytesPerMinuteno 
8Unknown?no 
9SubPacketH?no 
10FrameSize?no 
11SubPacketSize?no 
12SampleRateno 
13SampleRate2?no 
14BitsPerSampleno 
15Channelsno 
16Genr?no 
17FourCC3?no 
+ +

Real Metafile Tags

+

Tags representing information extracted from Real Audio Metafile and +RealMedia Plug-in Metafile (RAM and RPM) files.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'txt'Textno 
'url'URLno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 29, 2010 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Reconyx.html b/ExifTool/html/TagNames/Reconyx.html new file mode 100644 index 0000000..7f50dd8 --- /dev/null +++ b/ExifTool/html/TagNames/Reconyx.html @@ -0,0 +1,396 @@ + + + + +Reconyx Tags + + + +

Reconyx Tags

+

The following tags are extracted from the maker notes of Reconyx Hyperfire +cameras such as the HC500, HC600 and PC900.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0MakerNoteVersionno 
1FirmwareVersionno 
4FirmwareDateint16u[2] 
6TriggerModestring[2]'C' = CodeLoc Not Entered +
'E' = External Sensor +
'M' = Motion Detection +
'T' = Time Lapse
7Sequenceint16u[2] 
9EventNumberint16u[2] 
11DateTimeOriginalint16u[6] 
18MoonPhaseint16u + +
0 = New +
1 = New Crescent +
2 = First Quarter +
3 = Waxing Gibbous
  4 = Full +
5 = Waning Gibbous +
6 = Last Quarter +
7 = Old Crescent
+
19AmbientTemperatureFahrenheitint16s 
20AmbientTemperatureint16s 
21SerialNumberundef[30] 
36Contrastint16u 
37Brightnessint16u 
38Sharpnessint16u 
39Saturationint16u 
40InfraredIlluminatorint16u0 = Off +
1 = On
41MotionSensitivityint16u 
42BatteryVoltageint16u 
43UserLabelstring[22] 
+ +

Reconyx Type2 Tags

+

Tags extracted from models such as the UltraFire.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
24FirmwareVersionundef[7] 
31Micro1Versionundef[7] 
38BootLoaderVersionundef[7] 
45Micro2Versionundef[7] 
52TriggerModeundef[1]'M' = Motion Detection +
'P' = Point and Shoot +
'T' = Time Lapse
53Sequenceint8u[2] 
55EventNumberint32u 
59DateTimeOriginalint8u[7] 
66DayOfWeekint8u + +
0 = Sunday +
1 = Monday +
2 = Tuesday +
3 = Wednesday
  4 = Thursday +
5 = Friday +
6 = Saturday
+
67MoonPhaseint8u + +
0 = New +
1 = New Crescent +
2 = First Quarter +
3 = Waxing Gibbous
  4 = Full +
5 = Waning Gibbous +
6 = Last Quarter +
7 = Old Crescent
+
68AmbientTemperatureFahrenheitint16s 
70AmbientTemperatureint16s 
72Illuminationint8u0 = Off +
1 = On
73BatteryVoltageint16u 
75SerialNumberstring[15] 
90UserLabelstring[21] 
+ +

Reconyx Type3 Tags

+

Tags extracted from models such as the HF2 PRO.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
16FileNumberint16u 
18DirectoryNumberint16u 
42FirmwareVersionint16u[3] 
48FirmwareDateint16u[2] 
52TriggerModestring[2]'M' = Motion Detection +
'P' = Point and Shoot +
'T' = Time Lapse
54Sequenceint16u[2] 
58EventNumberint16u[2] 
62DateTimeOriginalint16u[6] 
74DayOfWeekint16u + +
0 = Sunday +
1 = Monday +
2 = Tuesday +
3 = Wednesday
  4 = Thursday +
5 = Friday +
6 = Saturday
+
76MoonPhaseint16u + +
0 = New +
1 = New Crescent +
2 = First Quarter +
3 = Waxing Gibbous
  4 = Full +
5 = Waning Gibbous +
6 = Last Quarter +
7 = Old Crescent
+
78AmbientTemperatureFahrenheitint16s 
80AmbientTemperatureint16s 
82Contrastint16u 
84Brightnessint16u 
86Sharpnessint16u 
88Saturationint16u 
90Flashint16u0 = Off +
1 = On
92AmbientInfraredint16u 
94AmbientLightint16u 
96MotionSensitivityint16u 
98BatteryVoltageint16u 
100BatteryVoltageAvgint16u 
102BatteryTypeint16u 
104UserLabelstring[22] 
126SerialNumberunicode[15] 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 7, 2019 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Red.html b/ExifTool/html/TagNames/Red.html new file mode 100644 index 0000000..d2e2e80 --- /dev/null +++ b/ExifTool/html/TagNames/Red.html @@ -0,0 +1,292 @@ + + + + +Red Tags + + + +

Red Tags

+

Tags extracted from Redcode R3D video files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'RED1'Red1Header---> Red RED1 Tags
'RED2'Red2Header---> Red RED2 Tags
0x1000StartEdgeCodeno 
0x1001StartTimecodeno 
0x1002OtherDate1no 
0x1003OtherDate2no 
0x1004OtherDate3no 
0x1005DateTimeOriginalno 
0x1006SerialNumberno 
0x1019CameraTypeno 
0x101aReelNumberno 
0x101bTakeno 
0x1023DateCreatedno 
0x1024TimeCreatedno 
0x1025FirmwareVersionno 
0x1029ReelTimecodeno 
0x102aStorageTypeno 
0x1030StorageFormatDateno 
0x1031StorageFormatTimeno 
0x1032StorageSerialNumberno 
0x1033StorageModelno 
0x1036AspectRationo 
0x1042Revisionno 
0x1056OriginalFileNameno 
0x106eLensMakeno 
0x106fLensNumberno 
0x1070LensModelno 
0x1071Modelno 
0x107cCameraOperatorno 
0x1086VideoFormatno 
0x1096Filterno 
0x10a0Brainno 
0x10a1Sensorno 
0x200dColorTemperatureno 
0x204bRGBCurvesno 
0x2066OriginalFrameRateno 
0x4037CropAreano 
0x403bISOno 
0x406aFNumberno 
0x406bFocalLengthno 
0x606cFocusDistanceno 
+ +

Red RED1 Tags

+

Redcode version 1 header.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
7RedcodeVersionno 
54ImageWidthno 
58ImageHeightno 
62FrameRateno 
67OriginalFileNameno 
+ +

Red RED2 Tags

+

Redcode version 2 header.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
7RedcodeVersionno 
76ImageWidthno 
80ImageHeightno 
86FrameRateno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jan 30, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Ricoh.html b/ExifTool/html/TagNames/Ricoh.html new file mode 100644 index 0000000..b8e6d9c --- /dev/null +++ b/ExifTool/html/TagNames/Ricoh.html @@ -0,0 +1,812 @@ + + + + +Ricoh Tags + + + +

Ricoh Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001MakerNoteTypestring 
0x0002FirmwareVersionstring 
0x0005SerialNumber +
InternalSerialNumber
undef[16]
undef[16]
(the serial number stamped on the camera begins with 2 model-specific letters +followed by the last 8 digits of this value. For the GXR, this is the +serial number of the lens unit)
0x0e00PrintIM---> PrintIM Tags
0x1000RecordingFormatint16u2 = JPEG +
3 = DNG
0x1001ImageInfo +
ExposureProgram
-
int16u
--> Ricoh ImageInfo Tags +
(GR) +
+
1 = Auto +
2 = Program AE +
3 = Aperture-priority AE +
4 = Shutter speed priority AE +
5 = Shutter/aperture priority AE +
6 = Manual +
7 = Movie
+
0x1002DriveModeint16u(valid only for some models) +
0 = Single-frame +
1 = Continuous +
8 = AF-priority Continuous
0x1003Sharpness +
WhiteBalance
int32u
int16u
0 = Sharp +
1 = Normal +
2 = Soft +
(GR)
+
0 = Auto +
1 = Multi-P Auto +
2 = Daylight +
3 = Cloudy +
4 = Incandescent 1 +
5 = Incandescent 2 +
6 = Daylight Fluorescent +
7 = Neutral White Fluorescent +
8 = Cool White Fluorescent +
9 = Warm White Fluorescent +
10 = Manual +
11 = Kelvin +
12 = Shade
+
0x1004WhiteBalanceFineTuneint16u(2 numbers: amount of adjustment towards Amber and Green. Not valid for all +models)
0x1006FocusModeint16u + +
1 = Manual +
2 = Multi AF +
3 = Spot AF +
4 = Snap +
5 = Infinity
  7 = Face Detect +
8 = Subject Tracking +
9 = Pinpoint AF +
10 = Movie
+
0x1007AutoBracketingint16u + +
0 = Off +
9 = AE +
11 = WB +
16 = DR
  17 = Contrast +
18 = WB2 +
19 = Effect
+
0x1009MacroModeint16u0 = Off +
1 = On
0x100aFlashModeint16u +
0 = Off +
1 = Auto, Fired +
2 = On +
3 = Auto, Fired, Red-eye reduction +
4 = Slow Sync +
5 = Manual +
6 = On, Red-eye reduction +
7 = Synchro, Red-eye reduction +
8 = Auto, Did not fire
+
0x100bFlashExposureComprational64s 
0x100cManualFlashOutputrational64s + + +
-288 = 1/64 +
-240 = 1/32 +
-216 = 1/22 +
-192 = 1/16
  -168 = 1/11 +
-144 = 1/8 +
-120 = 1/5.6 +
-96 = 1/4
  -72 = 1/2.8 +
-48 = 1/2 +
-24 = 1/1.4 +
0 = Full
+
0x100dFullPressSnapint16u0 = Off +
1 = On
0x100eDynamicRangeExpansionint16u0 = Off +
3 = Weak +
4 = Medium +
5 = Strong
0x100fNoiseReductionint16u0 = Off +
1 = Weak +
2 = Medium +
3 = Strong
0x1010ImageEffectsint16u + +
0 = Standard +
1 = Vivid +
3 = Black & White +
5 = B&W Toning Effect +
6 = Setting 1 +
7 = Setting 2 +
9 = High-contrast B&W
  10 = Cross Process +
11 = Positive Film +
12 = Bleach Bypass +
13 = Retro +
15 = Miniature +
17 = High Key
+
0x1011Vignettingint16u0 = Off +
1 = Low +
2 = Medium +
3 = High
0x1012Contrastint32u2147483647 = MAX
0x1013Saturationint32u 
0x1014Sharpnessint32u 
0x1015ToningEffectint16u + +
0 = Off +
1 = Sepia +
2 = Red +
3 = Green
  4 = Blue +
5 = Purple +
6 = B&W +
7 = Color
+
0x1016HueAdjustint16u + +
0 = Off +
1 = Basic +
2 = Magenta +
3 = Yellow
  4 = Normal +
5 = Warm +
6 = Cool
+
0x1017WideAdapterint16u0 = Not Attached +
2 = Attached
0x1018CropModeint16u0 = Off +
1 = On (35mm) +
2 = On (47mm)
0x1019NDFilterint16u0 = Off +
1 = On
0x101aWBBracketShotNumberint16u 
0x1200AFStatusint16u0 = Out of Focus +
1 = In Focus
0x1201AFAreaXPosition1int32u(manual AF area position in a 1280x864 image)
0x1202AFAreaYPosition1int32u 
0x1203AFAreaXPositionint32u(manual AF area position in the full image)
0x1204AFAreaYPositionint32u 
0x1205AFAreaModeint16u0 = Auto +
2 = Manual
0x1307ColorTempKelvinint32u 
0x1308ColorTemperatureint32u 
0x1500FocalLengthrational64u 
0x1601SensorWidthint32u 
0x1602SensorHeightint32u 
0x1603CroppedImageWidthint32u 
0x1604CroppedImageHeightint32u 
0x2001RicohSubdir +
RicohSubdirIFD +
RicohRR1Subdir
-
-
-
--> Ricoh Subdir Tags +
--> Ricoh Subdir Tags +
--> Ricoh Subdir Tags
0x4001ThetaSubdir---> Ricoh ThetaSubdir Tags
+ +

Ricoh ImageInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0RicohImageWidthint16u 
2RicohImageHeightint16u 
6RicohDateint8u[7] 
28PreviewImageStartint16u* 
30PreviewImageLengthint16u* 
32FlashModeint8u0 = Off +
1 = Auto +
2 = On
33Macroint8u0 = Off +
1 = On
34Sharpnessint8u0 = Sharp +
1 = Normal +
2 = Soft
38WhiteBalanceint8u + +
0 = Auto +
1 = Daylight +
2 = Cloudy +
3 = Tungsten
  4 = Fluorescent +
5 = Manual +
7 = Detail +
9 = Multi-pattern Auto
+
39ISOSettingint8u + + +
0 = Auto +
1 = 64 +
2 = 100 +
4 = 200
  6 = 400 +
7 = 800 +
8 = 1600 +
9 = Auto
  10 = 3200 +
11 = 100 (Low)
+
40Saturationint8u + +
0 = High +
1 = Normal +
2 = Low +
3 = B&W
  6 = Toning Effect +
9 = Vivid +
10 = Natural
+
+ +

Ricoh Subdir Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0004ManufactureDate1string[20] 
0x0005ManufactureDate2string[20] 
0x001aFaceInfo---> Ricoh FaceInfo Tags
0x0029FirmwareInfo---> Ricoh FirmwareInfo Tags
0x002aNoiseReductionint32u0 = Off +
1 = Weak +
2 = Strong +
3 = Max
0x002cSerialInfo---> Ricoh SerialInfo Tags
+ +

Ricoh FaceInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
181FacesDetectedint8u 
182FaceDetectFrameSizeint16u[2] 
188Face1Positionint16u[4](left, top, width and height of detected face in coordinates of +FaceDetectFrameSize with increasing Y downwards)
200Face2Positionint16u[4] 
212Face3Positionint16u[4] 
224Face4Positionint16u[4] 
236Face5Positionint16u[4] 
248Face6Positionint16u[4] 
260Face7Positionint16u[4] 
272Face8Positionint16u[4] 
+ +

Ricoh FirmwareInfo Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0FirmwareRevisionstring[12] 
12FirmwareRevision2string[12] 
+ +

Ricoh SerialInfo Tags

+

This information is found in images from the GXR.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0BodyFirmwarestring[16] 
16BodySerialNumberstring[16] 
32LensFirmwarestring[16] 
48LensSerialNumberstring[16] 
+ +

Ricoh ThetaSubdir Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0003Accelerometerrational64s[2] 
0x0004Compassrational64u 
0x000aTimeZonestring 
+ +

Ricoh Type2 Tags

+

Tags written by models such as the Ricoh HZ15 and the Pentax XG-1. These +are not writable due to numerous formatting errors as written by these +cameras.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0207RicohModelno 
0x0300RicohMakeno 
+ +

Ricoh Text Tags

+

Some Ricoh DC and RDC models use a text-based format for their maker notes +instead of the IFD format used by the Caplio models. Below is a list of known +tags in this information.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Bg'BlueGainno 
'Gg'GreenGainno 
'Rev'FirmwareVersionno 
'Rg'RedGainno 
'Rv'FirmwareVersionno 
+ +

Ricoh RMETA Tags

+

The Ricoh Caplio Pro G3 has the ability to add custom fields to the APP5 +"RMETA" segment of JPEG images. While only a few observed tags have been +defined below, ExifTool will extract any information found here.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Azimuth'Azimuthno + + + +
1 = N +
2 = NNE +
3 = NE +
4 = ENE
  5 = E +
6 = ESE +
7 = SE +
8 = SSE
  9 = S +
10 = SSW +
11 = SW +
12 = WSW
  13 = W +
14 = WNW +
15 = NW +
16 = NNW
+
'Condition'Conditionno1 = Good +
2 = Fair +
3 = Poor +
4 = Damaged
'Lit'Litno1 = Yes +
2 = No
'Location'Locationno1 = Verge +
2 = Gantry +
3 = Central reservation +
4 = Roundabout
'Sign type'SignTypeno1 = Directional +
2 = Warning +
3 = Information
'_audio'SoundFileno(audio data recorded in JPEG images by the G700SE)
'_barcode'Barcodesno+ 
+ +

Ricoh AVI Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'mnrt'MakerNoteRicoh---> Ricoh Tags
'rdc2'RicohRDC2?no 
'thum'ThumbnailImageno 
'ucmt'Commentno 
+ +

Ricoh LensID Values

+

Lens units available for the GXR, used by the Ricoh Composite LensID tag. Note +that unlike lenses for all other makes of cameras, the focal lengths in these +model names have already been scaled to include the 35mm crop factor.

+
+
+ + + + + + + + +
ValueLensIDValueLensID
'RL1'= GR Lens A12 50mm F2.5 Macro'RL5'= GR Lens A12 28mm F2.5
'RL2'= Ricoh Lens S10 24-70mm F2.5-4.4 VC'RL6'= Ricoh Lens A16 24-85mm F3.5-5.5
'RL3'= Ricoh Lens P10 28-300mm F3.5-5.6 VC'RL8'= Mount A12
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 27, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Samsung.html b/ExifTool/html/TagNames/Samsung.html new file mode 100644 index 0000000..317fdfd --- /dev/null +++ b/ExifTool/html/TagNames/Samsung.html @@ -0,0 +1,1137 @@ + + + + +Samsung Tags + + + +

Samsung Tags

+

Tags found in the binary "STMN" format maker notes written by a number of +Samsung models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0MakerNoteVersionundef[8] 
2PreviewImageStartint32u* 
3PreviewImageLengthint32u* 
11SamsungIFD---> Samsung IFD Tags
+ +

Samsung IFD Tags

+

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.

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +

Samsung Type2 Tags

+

Tags found in the EXIF-format maker notes of newer Samsung models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001MakerNoteVersionundef[4] 
0x0002DeviceTypeint32u0x1000 = Compact Digital Camera +
0x2000 = High-end NX Camera +
0x3000 = HXM Video Camera +
0x12000 = Cell Phone +
0x300000 = SMX Video Camera
0x0003SamsungModelIDint32u0x100101c = NX10 +
0x1001226 = HMX-S15BP +
0x1001233 = HMX-Q10 +
0x1001234 = HMX-H304 +
0x100130c = NX100 +
0x1001327 = NX11 +
0x170104b = ES65, ES67 / VLUU ES65, ES67 / SL50 +
0x170104e = ES70, ES71 / VLUU ES70, ES71 / SL600 +
0x1701052 = ES73 / VLUU ES73 / SL605 +
0x1701055 = ES25, ES27 / VLUU ES25, ES27 / SL45 +
0x1701300 = ES28 / VLUU ES28 +
0x1701303 = ES74,ES75,ES78 / VLUU ES75,ES78 +
0x2001046 = PL150 / VLUU PL150 / TL210 / PL151 +
0x2001048 = PL100 / TL205 / VLUU PL100 / PL101 +
0x2001311 = PL120,PL121 / VLUU PL120,PL121 +
0x2001315 = PL170,PL171 / VLUUPL170,PL171 +
0x200131e = PL210, PL211 / VLUU PL210, PL211 +
0x2701317 = PL20,PL21 / VLUU PL20,PL21 +
0x2a0001b = WP10 / VLUU WP10 / AQ100 +
0x3000000 = Various Models (0x3000000) +
0x3a00018 = Various Models (0x3a00018) +
0x400101f = ST1000 / ST1100 / VLUU ST1000 / CL65 +
0x4001022 = ST550 / VLUU ST550 / TL225 +
0x4001025 = Various Models (0x4001025) +
0x400103e = VLUU ST5500, ST5500, CL80 +
0x4001041 = VLUU ST5000, ST5000, TL240 +
0x4001043 = ST70 / VLUU ST70 / ST71 +
0x400130a = Various Models (0x400130a) +
0x400130e = ST90,ST91 / VLUU ST90,ST91 +
0x4001313 = VLUU ST95, ST95 +
0x4a00015 = VLUU ST60 +
0x4a0135b = ST30, ST65 / VLUU ST65 / ST67 +
0x5000000 = Various Models (0x5000000) +
0x5001038 = Various Models (0x5001038) +
0x500103a = WB650 / VLUU WB650 / WB660 +
0x500103c = WB600 / VLUU WB600 / WB610 +
0x500133e = WB150 / WB150F / WB152 / WB152F / WB151 +
0x5a0000f = WB5000 / HZ25W +
0x5a0001e = WB5500 / VLUU WB5500 / HZ50W +
0x6001036 = EX1 +
0x700131c = VLUU SH100, SH100 +
0x27127002 = SMX-C20N
0x0011OrientationInfo---> Samsung OrientationInfo Tags
0x0020SmartAlbumColorint16u[2]'0 0' = n/a +
[Value 0]
+ +
0 = Red +
1 = Yellow +
2 = Green +
3 = Blue
  4 = Magenta +
5 = Black +
6 = White +
7 = Various
+
0x0021PictureWizardint16u--> Samsung PictureWizard Tags
0x0030LocalLocationNamestring 
0x0031LocationNamestring 
0x0035PreviewIFD---> Nikon PreviewIFD Tags +
--> Nikon PreviewIFD Tags
0x0040RawDataByteOrderyes0 = Little-endian (Intel, II) +
1 = Big-endian (Motorola, MM)
0x0041WhiteBalanceSetupint32u0 = Auto +
1 = Manual
0x0043CameraTemperaturerational64s 
0x0050RawDataCFAPatternyes0 = Unchanged +
1 = Swap +
65535 = Roll
0x0100FaceDetectint16u0 = Off +
1 = On
0x0120FaceRecognitionint32u0 = Off +
1 = On
0x0123FaceNamestring 
0xa001FirmwareNamestring 
0xa002SerialNumberstring 
0xa003LensTypeint16u[n][Value 0] +
0 = Built-in or Manual Lens +
1 = Samsung NX 30mm F2 Pancake +
2 = Samsung NX 18-55mm F3.5-5.6 OIS +
3 = Samsung NX 50-200mm F4-5.6 ED OIS +
4 = Samsung NX 20-50mm F3.5-5.6 ED +
5 = Samsung NX 20mm F2.8 Pancake +
6 = Samsung NX 18-200mm F3.5-6.3 ED OIS +
7 = Samsung NX 60mm F2.8 Macro ED OIS SSA +
8 = Samsung NX 16mm F2.4 Pancake +
9 = Samsung NX 85mm F1.4 ED SSA +
10 = Samsung NX 45mm F1.8 +
11 = Samsung NX 45mm F1.8 2D/3D +
12 = Samsung NX 12-24mm F4-5.6 ED +
13 = Samsung NX 16-50mm F2-2.8 S ED OIS +
14 = Samsung NX 10mm F3.5 Fisheye +
15 = Samsung NX 16-50mm F3.5-5.6 Power Zoom ED OIS +
20 = Samsung NX 50-150mm F2.8 S ED OIS +
21 = Samsung NX 300mm F2.8 ED OIS
0xa004LensFirmwarestring 
0xa005InternalLensSerialNumberstring 
0xa010SensorAreasint32u[8](full and valid sensor areas)
0xa011ColorSpaceint16u0 = sRGB +
1 = Adobe RGB
0xa012SmartRangeint16u0 = Off +
1 = On
0xa013ExposureCompensationrational64s 
0xa014ISOint32u 
0xa018ExposureTimerational64u 
0xa019FNumberrational64u 
0xa01aFocalLengthIn35mmFormatint32u 
0xa020EncryptionKeyint32u[11]!(key used to decrypt the tags below)
0xa021WB_RGGBLevelsUncorrectedint32u[4](these tags not corrected for WB_RGGBLevelsBlack)
0xa022WB_RGGBLevelsAutoint32u[4] 
0xa023WB_RGGBLevelsIlluminator1int32u[4] 
0xa024WB_RGGBLevelsIlluminator2int32u[4] 
0xa025HighlightLinearityLimitint32u 
0xa028WB_RGGBLevelsBlackint32s[4] 
0xa030ColorMatrixint32s[9] 
0xa031ColorMatrixSRGBint32s[9] 
0xa032ColorMatrixAdobeRGBint32s[9] 
0xa033CbCrMatrixDefaultint32s[4] 
0xa034CbCrMatrixint32s[4] 
0xa035CbCrGainDefaultint32u[2] 
0xa036CbCrGainint32u[2] 
0xa040ToneCurveSRGBDefaultint32u[23](first value gives the number of tone curve entries. This is followed by an +array of X coordinates then an array of Y coordinates)
0xa041ToneCurveAdobeRGBDefaultint32u[23] 
0xa042ToneCurveSRGBint32u[23] 
0xa043ToneCurveAdobeRGBint32u[23] 
0xa048RawData?int32s[12] 
0xa050Distortion?int32s[8] 
0xa051ChromaticAberration?int16u[22] 
0xa052Vignetting?int16u[15] 
0xa053VignettingCorrection?int16u[15] 
0xa054VignettingSetting?int16u[15] 
+ +

Samsung OrientationInfo Tags

+

Camera orientation information written by the Gear 360 (SM-C200).

+
+
+ + + + + + + + + + + + + + + + + + +
Index8Tag NameWritableValues / Notes
0YawAngle?rational64s(always zero)
1PitchAnglerational64s(upward tilt of rear camera in degrees)
2RollAnglerational64s(clockwise rotation of rear camera in degrees)
+ +

Samsung PictureWizard Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0PictureWizardModeint16u + + +
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
+
1PictureWizardColorint16u 
2PictureWizardSaturationint16u 
3PictureWizardSharpnessint16u 
4PictureWizardContrastint16u 
+ +

Samsung APP5 Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ssuniqueid'UniqueIDno 
+ +

Samsung Trailer Tags

+

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, and from the 'sefd' atom in HEIC images from the +Samsung S10+.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
DepthMapData +
DepthMapData2
no
no
 
DepthMapNameno 
DualCameraImageno 
DualCameraImageNameno 
DualShotExtra---> Samsung DualShotExtra Tags
EmbeddedAudioFileno 
EmbeddedAudioFileNameno 
EmbeddedImage +
EmbeddedImage2
no
no
 
EmbeddedImageNameno 
EmbeddedVideoFileno 
EmbeddedVideoTypeno 
MCCDatano202 = Greece (202) +
204 = Netherlands (204) +
206 = Belgium (206) +
208 = France (208) +
212 = Monaco (212) +
213 = Andorra (213) +
214 = Spain (214) +
216 = Hungary (216) +
218 = Bosnia & Herzegov. (218) +
219 = Croatia (219) +
220 = Serbia (220) +
221 = Kosovo (221) +
222 = Italy (222) +
226 = Romania (226) +
228 = Switzerland (228) +
230 = Czech Rep. (230) +
231 = Slovakia (231) +
232 = Austria (232) +
234 = United Kingdom (234) +
235 = United Kingdom (235) +
238 = Denmark (238) +
240 = Sweden (240) +
242 = Norway (242) +
244 = Finland (244) +
246 = Lithuania (246) +
247 = Latvia (247) +
248 = Estonia (248) +
250 = Russian Federation (250) +
255 = Ukraine (255) +
257 = Belarus (257) +
259 = Moldova (259) +
260 = Poland (260) +
262 = Germany (262) +
266 = Gibraltar (266) +
268 = Portugal (268) +
270 = Luxembourg (270) +
272 = Ireland (272) +
274 = Iceland (274) +
276 = Albania (276) +
278 = Malta (278) +
280 = Cyprus (280) +
282 = Georgia (282) +
283 = Armenia (283) +
284 = Bulgaria (284) +
286 = Turkey (286) +
288 = Faroe Islands (288) +
289 = Abkhazia (289) +
290 = Greenland (290) +
292 = San Marino (292) +
293 = Slovenia (293) +
294 = Macedonia (294) +
295 = Liechtenstein (295) +
297 = Montenegro (297) +
302 = Canada (302) +
308 = St. Pierre & Miquelon (308) +
310 = United States / Guam (310) +
311 = United States / Guam (311) +
312 = United States (312) +
316 = United States (316) +
330 = Puerto Rico (330) +
334 = Mexico (334) +
338 = Jamaica (338) +
340 = French Guiana / Guadeloupe / Martinique (340) +
342 = Barbados (342) +
344 = Antigua and Barbuda (344) +
346 = Cayman Islands (346) +
348 = British Virgin Islands (348) +
350 = Bermuda (350) +
352 = Grenada (352) +
354 = Montserrat (354) +
356 = Saint Kitts and Nevis (356) +
358 = Saint Lucia (358) +
360 = St. Vincent & Gren. (360) +
362 = Bonaire, Sint Eustatius and Saba / Curacao / Netherlands Antilles (362) +
363 = Aruba (363) +
364 = Bahamas (364) +
365 = Anguilla (365) +
366 = Dominica (366) +
368 = Cuba (368) +
370 = Dominican Republic (370) +
372 = Haiti (372) +
374 = Trinidad and Tobago (374) +
376 = Turks and Caicos Islands / US Virgin Islands (376) +
400 = Azerbaijan (400) +
401 = Kazakhstan (401) +
402 = Bhutan (402) +
404 = India (404) +
405 = India (405) +
410 = Pakistan (410) +
412 = Afghanistan (412) +
413 = Sri Lanka (413) +
414 = Myanmar (Burma) (414) +
415 = Lebanon (415) +
416 = Jordan (416) +
417 = Syrian Arab Republic (417) +
418 = Iraq (418) +
419 = Kuwait (419) +
420 = Saudi Arabia (420) +
421 = Yemen (421) +
422 = Oman (422) +
424 = United Arab Emirates (424) +
425 = Israel / Palestinian Territory (425) +
426 = Bahrain (426) +
427 = Qatar (427) +
428 = Mongolia (428) +
429 = Nepal (429) +
430 = United Arab Emirates (430) +
431 = United Arab Emirates (431) +
432 = Iran (432) +
434 = Uzbekistan (434) +
436 = Tajikistan (436) +
437 = Kyrgyzstan (437) +
438 = Turkmenistan (438) +
440 = Japan (440) +
441 = Japan (441) +
450 = South Korea (450) +
452 = Viet Nam (452) +
454 = Hongkong, China (454) +
455 = Macao, China (455) +
456 = Cambodia (456) +
457 = Laos P.D.R. (457) +
460 = China (460) +
466 = Taiwan (466) +
467 = North Korea (467) +
470 = Bangladesh (470) +
472 = Maldives (472) +
502 = Malaysia (502) +
505 = Australia (505) +
510 = Indonesia (510) +
514 = Timor-Leste (514) +
515 = Philippines (515) +
520 = Thailand (520) +
525 = Singapore (525) +
528 = Brunei Darussalam (528) +
530 = New Zealand (530) +
537 = Papua New Guinea (537) +
539 = Tonga (539) +
540 = Solomon Islands (540) +
541 = Vanuatu (541) +
542 = Fiji (542) +
544 = American Samoa (544) +
545 = Kiribati (545) +
546 = New Caledonia (546) +
547 = French Polynesia (547) +
548 = Cook Islands (548) +
549 = Samoa (549) +
550 = Micronesia (550) +
552 = Palau (552) +
553 = Tuvalu (553) +
555 = Niue (555) +
602 = Egypt (602) +
603 = Algeria (603) +
604 = Morocco (604) +
605 = Tunisia (605) +
606 = Libya (606) +
607 = Gambia (607) +
608 = Senegal (608) +
609 = Mauritania (609) +
610 = Mali (610) +
611 = Guinea (611) +
612 = Ivory Coast (612) +
613 = Burkina Faso (613) +
614 = Niger (614) +
615 = Togo (615) +
616 = Benin (616) +
617 = Mauritius (617) +
618 = Liberia (618) +
619 = Sierra Leone (619) +
620 = Ghana (620) +
621 = Nigeria (621) +
622 = Chad (622) +
623 = Central African Rep. (623) +
624 = Cameroon (624) +
625 = Cape Verde (625) +
626 = Sao Tome & Principe (626) +
627 = Equatorial Guinea (627) +
628 = Gabon (628) +
629 = Congo, Republic (629) +
630 = Congo, Dem. Rep. (630) +
631 = Angola (631) +
632 = Guinea-Bissau (632) +
633 = Seychelles (633) +
634 = Sudan (634) +
635 = Rwanda (635) +
636 = Ethiopia (636) +
637 = Somalia (637) +
638 = Djibouti (638) +
639 = Kenya (639) +
640 = Tanzania (640) +
641 = Uganda (641) +
642 = Burundi (642) +
643 = Mozambique (643) +
645 = Zambia (645) +
646 = Madagascar (646) +
647 = Reunion (647) +
648 = Zimbabwe (648) +
649 = Namibia (649) +
650 = Malawi (650) +
651 = Lesotho (651) +
652 = Botswana (652) +
653 = Swaziland (653) +
654 = Comoros (654) +
655 = South Africa (655) +
657 = Eritrea (657) +
659 = South Sudan (659) +
702 = Belize (702) +
704 = Guatemala (704) +
706 = El Salvador (706) +
708 = Honduras (708) +
710 = Nicaragua (710) +
712 = Costa Rica (712) +
714 = Panama (714) +
716 = Peru (716) +
722 = Argentina Republic (722) +
724 = Brazil (724) +
730 = Chile (730) +
732 = Colombia (732) +
734 = Venezuela (734) +
736 = Bolivia (736) +
738 = Guyana (738) +
740 = Ecuador (740) +
744 = Paraguay (744) +
746 = Suriname (746) +
748 = Uruguay (748) +
750 = Falkland Islands (Malvinas) (750) +
901 = International Networks / Satellite Networks (901)
SingleShotDepthMapno 
SingleShotMeta---> Samsung SingleShotMeta Tags
SurroundShotVideono 
SurroundShotVideoNameno 
TimeStampno 
+ +

Samsung DualShotExtra Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
16DepthMapWidthno(index varies depending on model)
17DepthMapHeightno(index varies depending on model)
+ +

Samsung SingleShotMeta Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'beautyColorLevel'BeautyColorLevelno 
'beautyRetouchLevel'BeautyRetouchLevelno 
'blurStrength'BlurStrengthno 
'bokehShape'BokehShapeno 
'colorpopStrength'ColorpopStrengthno 
'depthHWHeight'DepthHWHeightno 
'depthHWWidth'DepthHWWidthno 
'depthSWHeight'DepthSWHeightno 
'depthSWWidth'DepthSWWidthno 
'deviceOrientation'DeviceOrientationno 
'effectStrength'EffectStrengthno 
'effectType'EffectTypeno 
'flipStatus'FlipStatusno 
'inputHeight'InputHeightno 
'inputWidth'InputWidthno 
'isArtBokeh'IsArtBokehno 
'lensFacing'LensFacingno 
'monoStrength'MonoStrengthno 
'objectOrientation'ObjectOrientationno 
'outputHeight'OutputHeightno 
'outputWidth'OutputWidthno 
'perfMode'PerfModeno 
'segHeight'SegHeightno 
'segWidth'SegWidthno 
'sidelightStrength'SidelightStrengthno 
'spinStrength'SpinStrengthno 
'vintageStrength'VintageStrengthno 
'zoomStrength'ZoomStrengthno 
+ +

Samsung sec Tags

+

This information is found in the @sec atom of Samsung MP4 videos from models +such as the WB30F.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
32Modelno 
512ThumbnailWidthno 
516ThumbnailHeightno 
520ThumbnailLengthno 
524ThumbnailImageno(the THM image, embedded metadata is extracted as the first sub-document)
+ +

Samsung INFO Tags

+

This information is found in MP4 videos from Samsung models such as the +SMX-C20N.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'EFCT'Effectno 
'QLTY'Qualityno 
+ +

Samsung MP4 Tags

+

This information is found in Samsung MP4 videos from models such as the +WP10.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
24Modelno 
46ExposureTimeno 
50FNumberno 
58ExposureCompensationno 
106ISOno 
125Softwareno 
244Thumbnail---> Samsung Thumbnail Tags
+ +

Samsung Thumbnail Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1ThumbnailWidthno 
2ThumbnailHeightno 
3ThumbnailLengthno 
4ThumbnailOffsetno 
+ +

Samsung smta Tags

+

This information is found in the smta atom of Samsung MP4 videos from models +such as the Galaxy S4.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'svss'SamsungSvss---> Samsung svss Tags
+ +

Samsung svss Tags

+

This information is found in the svss atom of Samsung MP4 videos from models +such as the Galaxy S4.

+
+
+ + + + +
Tag IDTag NameWritableValues / Notes
[no tags known]
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 10, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Sanyo.html b/ExifTool/html/TagNames/Sanyo.html new file mode 100644 index 0000000..36d6e74 --- /dev/null +++ b/ExifTool/html/TagNames/Sanyo.html @@ -0,0 +1,367 @@ + + + + +Sanyo Tags + + + +

Sanyo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x00ffMakerNoteOffsetint32u 
0x0100SanyoThumbnailundef 
0x0200SpecialModeint32u[3] 
0x0201SanyoQualityint16u +
0x0 = Normal/Very Low +
0x1 = Normal/Low +
0x2 = Normal/Medium Low +
0x3 = Normal/Medium +
0x4 = Normal/Medium High +
0x5 = Normal/High +
0x6 = Normal/Very High +
0x7 = Normal/Super High +
0x100 = Fine/Very Low +
0x101 = Fine/Low +
0x102 = Fine/Medium Low +
0x103 = Fine/Medium +
0x104 = Fine/Medium High +
0x105 = Fine/High +
0x106 = Fine/Very High +
0x107 = Fine/Super High +
0x200 = Super Fine/Very Low +
0x201 = Super Fine/Low +
0x202 = Super Fine/Medium Low +
0x203 = Super Fine/Medium +
0x204 = Super Fine/Medium High +
0x205 = Super Fine/High +
0x206 = Super Fine/Very High +
0x207 = Super Fine/Super High
+
0x0202Macroint16u0 = Normal +
1 = Macro +
2 = View +
3 = Manual
0x0204DigitalZoomrational64u 
0x0207SoftwareVersionyes 
0x0208PictInfoyes 
0x0209CameraIDyes 
0x020eSequentialShotint16u0 = None +
1 = Standard +
2 = Best +
3 = Adjust Exposure
0x020fWideRangeint16u0 = Off +
1 = On
0x0210ColorAdjustmentModeint16u0 = Off +
1 = On
0x0213QuickShotint16u0 = Off +
1 = On
0x0214SelfTimerint16u0 = Off +
1 = On
0x0216VoiceMemoint16u0 = Off +
1 = On
0x0217RecordShutterReleaseint16u0 = Record while down +
1 = Press start, press stop
0x0218FlickerReduceint16u0 = Off +
1 = On
0x0219OpticalZoomOnint16u0 = Off +
1 = On
0x021bDigitalZoomOnint16u0 = Off +
1 = On
0x021dLightSourceSpecialint16u0 = Off +
1 = On
0x021eResavedint16u0 = No +
1 = Yes
0x021fSceneSelectint16u + +
0 = Off +
1 = Sport +
2 = TV +
3 = Night
  4 = User 1 +
5 = User 2 +
6 = Lamp
+
0x0223ManualFocusDistance +
FaceInfo
rational64u
-
Sanyo FaceInfo
0x0224SequenceShotIntervalint16u0 = 5 frames/s +
1 = 10 frames/s +
2 = 15 frames/s +
3 = 20 frames/s
0x0225FlashModeint16u0 = Auto +
1 = Force +
2 = Disabled +
3 = Red eye
0x0e00PrintIM---> PrintIM Tags
0x0f00DataDumpno 
+ +

Sanyo FaceInfo Tags

+
+
+ + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0FacesDetectedint32u 
4FacePositionint32u[4](left, top, right and bottom coordinates of detected face in an unrotated +640-pixel-wide image, with increasing Y downwards)
+ +

Sanyo MOV Tags

+

This information is found in Sanyo MOV videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
24Modelno 
38ExposureTimeno 
42FNumberno 
50ExposureCompensationno 
68WhiteBalanceno + +
0 = Auto +
1 = Daylight +
2 = Shade
  3 = Fluorescent +
4 = Tungsten +
5 = Manual
+
72FocalLengthno 
+ +

Sanyo MP4 Tags

+

This information is found in Sanyo MP4 videos.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Makeno 
24Modelno 
50FNumberno 
58ExposureCompensationno 
106ISOno 
209Softwareno(these tags are shifted up by 1 byte for some models like the HD1A)
210Softwareno 
241Thumbnail---> Sanyo Thumbnail Tags
242Thumbnail---> Sanyo Thumbnail Tags
+ +

Sanyo Thumbnail Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1ThumbnailWidthno 
2ThumbnailHeightno 
3ThumbnailLengthno 
4ThumbnailOffsetno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Apr 30, 2019 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Scalado.html b/ExifTool/html/TagNames/Scalado.html new file mode 100644 index 0000000..00da437 --- /dev/null +++ b/ExifTool/html/TagNames/Scalado.html @@ -0,0 +1,46 @@ + + + + +Scalado Tags + + + +

Scalado Tags

+

Tags extracted from the JPEG APP4 "SCALADO" segment found in images from +HTC, LG and Samsung phones. (Presumably written by Scalado mobile software, +http://www.scalado.com/.)

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'HGHT'PreviewImageHeightno 
'QUAL'PreviewQualityno 
'SPMO'DataLength?no 
'WDTH'PreviewImageWidthno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 13, 2013 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Shortcuts.html b/ExifTool/html/TagNames/Shortcuts.html new file mode 100644 index 0000000..e962af7 --- /dev/null +++ b/ExifTool/html/TagNames/Shortcuts.html @@ -0,0 +1,307 @@ + + + + +Shortcuts Tags + + + +

Shortcuts Tags

+

+Shortcut tags are convenience tags that represent one or more other tag +names. They are used like regular tags to read and write the information +for a specified set of tags.

+ +

The shortcut tags below have been pre-defined, but user-defined shortcuts +may be added via the %Image::ExifTool::UserDefined::Shortcuts lookup in the +~/.ExifTool_config file. See the Image::ExifTool::Shortcuts documentation +for more details. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableRefers ToValues / Notes
AllDatesyesDateTimeOriginal +
CreateDate +
ModifyDate
(contrary to the shortcut name, this represents only the common EXIF +date/time tags. To access all date/time tags, use Time:All instead)
CanonyesFileName +
Model +
DateTimeOriginal +
ShootingMode +
ShutterSpeed +
Aperture +
MeteringMode +
ExposureCompensation +
ISO +
Lens +
FocalLength +
ImageSize +
Quality +
Flash +
FlashType +
ConditionalFEC +
RedEyeReduction +
ShutterCurtainHack +
WhiteBalance +
FocusMode +
Contrast +
Sharpness +
Saturation +
ColorTone +
ColorSpace +
LongExposureNoiseReduction +
FileSize +
FileNumber +
DriveMode +
OwnerName +
SerialNumber
 
ColorSpaceTagsyesExifIFD:ColorSpace +
ExifIFD:Gamma +
InteropIFD:InteropIndex +
ICC_Profile
(standard tags which carry color space information. Useful for preserving +color space when deleting all other metadata)
CommonyesFileName +
FileSize +
Model +
DateTimeOriginal +
ImageSize +
Quality +
FocalLength +
ShutterSpeed +
Aperture +
ISO +
WhiteBalance +
Flash
 
CommonIFD0yesIFD0:ImageDescription +
IFD0:Make +
IFD0:Model +
IFD0:Software +
IFD0:ModifyDate +
IFD0:Artist +
IFD0:Copyright +
IFD0:Rating +
IFD0:RatingPercent +
IFD0:DNGLensInfo +
IFD0:PanasonicTitle +
IFD0:PanasonicTitle2 +
IFD0:XPTitle +
IFD0:XPComment +
IFD0:XPAuthor +
IFD0:XPKeywords +
IFD0:XPSubject
(common metadata tags found in IFD0 of TIFF-format images. Used to simpify +deletion of all metadata from these images. See +FAQ number 7 for details)
ImageDataMD5yesImageDataHash 
LargeTagsyesCanonVRD +
DLOData +
EXIF +
ICC_Profile +
IDCPreviewImage +
ImageData +
IPTC +
JpgFromRaw +
OriginalRawImage +
OtherImage +
PreviewImage +
ThumbnailImage +
TIFFPreview +
XML +
XMP +
ZoomedPreviewImage
(large binary data tags which may be excluded to reduce memory usage if +memory limitations are a problem)
MakerNotesyesMakerNotes +
MakerNoteApple +
MakerNoteCanon +
MakerNoteCasio +
MakerNoteCasio2 +
MakerNoteDJI +
MakerNoteDJIInfo +
MakerNoteFLIR +
MakerNoteFujiFilm +
MakerNoteGE +
MakerNoteGE2 +
MakerNoteHasselblad +
MakerNoteHP +
MakerNoteHP2 +
MakerNoteHP4 +
MakerNoteHP6 +
MakerNoteISL +
MakerNoteJVC +
MakerNoteJVCText +
MakerNoteKodak1a +
MakerNoteKodak1b +
MakerNoteKodak2 +
MakerNoteKodak3 +
MakerNoteKodak4 +
MakerNoteKodak5 +
MakerNoteKodak6a +
MakerNoteKodak6b +
MakerNoteKodak7 +
MakerNoteKodak8a +
MakerNoteKodak8b +
MakerNoteKodak8c +
MakerNoteKodak9 +
MakerNoteKodak10 +
MakerNoteKodak11 +
MakerNoteKodak12 +
MakerNoteKodakUnknown +
MakerNoteKyocera +
MakerNoteMinolta +
MakerNoteMinolta2 +
MakerNoteMinolta3 +
MakerNoteMotorola +
MakerNoteNikon +
MakerNoteNikon2 +
MakerNoteNikon3 +
MakerNoteNintendo +
MakerNoteOlympus +
MakerNoteOlympus2 +
MakerNoteOlympus3 +
MakerNoteLeica +
MakerNoteLeica2 +
MakerNoteLeica3 +
MakerNoteLeica4 +
MakerNoteLeica5 +
MakerNoteLeica6 +
MakerNoteLeica7 +
MakerNoteLeica8 +
MakerNoteLeica9 +
MakerNoteLeica10 +
MakerNotePanasonic +
MakerNotePanasonic2 +
MakerNotePanasonic3 +
MakerNotePentax +
MakerNotePentax2 +
MakerNotePentax3 +
MakerNotePentax4 +
MakerNotePentax5 +
MakerNotePentax6 +
MakerNotePhaseOne +
MakerNoteReconyx +
MakerNoteReconyx2 +
MakerNoteReconyx3 +
MakerNoteRicoh +
MakerNoteRicoh2 +
MakerNoteRicohPentax +
MakerNoteRicohText +
MakerNoteSamsung1a +
MakerNoteSamsung1b +
MakerNoteSamsung2 +
MakerNoteSanyo +
MakerNoteSanyoC4 +
MakerNoteSanyoPatch +
MakerNoteSigma +
MakerNoteSony +
MakerNoteSony2 +
MakerNoteSony3 +
MakerNoteSony4 +
MakerNoteSony5 +
MakerNoteSonyEricsson +
MakerNoteSonySRF +
MakerNoteUnknownText +
MakerNoteUnknownBinary +
MakerNoteUnknown
(useful when copying tags between files to either copy the maker notes as a +block or prevent it from being copied)
NikonyesModel +
SubSecDateTimeOriginal +
ShutterCount +
LensSpec +
FocalLength +
ImageSize +
ShutterSpeed +
Aperture +
ISO +
NoiseReduction +
ExposureProgram +
ExposureCompensation +
WhiteBalance +
WhiteBalanceFineTune +
ShootingMode +
Quality +
MeteringMode +
FocusMode +
ImageOptimization +
ToneComp +
ColorHue +
ColorSpace +
HueAdjustment +
Saturation +
Sharpness +
Flash +
FlashMode +
FlashExposureComp
 
UnsafeyesIFD0:YCbCrPositioning +
IFD0:YCbCrCoefficients +
IFD0:TransferFunction +
ExifIFD:ComponentsConfiguration +
ExifIFD:CompressedBitsPerPixel +
InteropIFD:InteropIndex +
InteropIFD:InteropVersion +
InteropIFD:RelatedImageWidth +
InteropIFD:RelatedImageHeight
(Unsafe tags in JPEG images which are normally not copied. Defined here +as a shortcut to use when rebuilding JPEG EXIF from scratch. See +FAQ number 20 for more information)
ls-lyesFilePermissions +
FileHardLinks +
FileUserID +
FileGroupID +
FileSize# +
FileModifyDate +
FileName
(mimics columns shown by Unix "ls -l" command. Includes some tags which are +extracted only if the API SystemTags option +is enabled)
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 8, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Sigma.html b/ExifTool/html/TagNames/Sigma.html new file mode 100644 index 0000000..13e4870 --- /dev/null +++ b/ExifTool/html/TagNames/Sigma.html @@ -0,0 +1,736 @@ + + + + +Sigma Tags + + + +

Sigma Tags

+

These tags are written by Sigma/Foveon cameras. In the early days Sigma was +a class leader by releasing their maker note specification to the public, +but since then they have deviated from this standard and newer camera models +are less than consistent about their metadata formats.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0002SerialNumberstring 
0x0003DriveModestring 
0x0004ResolutionModestring 
0x0005AFModestring 
0x0006FocusSettingstring 
0x0007WhiteBalancestring 
0x0008ExposureModestring'A' = Aperture-priority AE +
'M' = Manual +
'P' = Program AE +
'S' = Shutter speed priority AE
0x0009MeteringModestring8 = Multi-segment +
'A' = Average +
'C' = Center-weighted average
0x000aLensFocalRangestring 
0x000bColorSpacestring 
0x000cExposureCompensation +
ExposureAdjust?
string
rational64s
 
0x000dContrast +
Contrast
string
rational64s
 
0x000eShadow +
Shadow
string
rational64s
 
0x000fHighlight +
Highlight
string
rational64s
 
0x0010Saturation +
Saturation
string
rational64s
 
0x0011Sharpness +
Sharpness
string
rational64s
 
0x0012X3FillLight +
X3FillLight
string
rational64s
 
0x0014ColorAdjustment +
ColorAdjustment
string
rational64s[3]
 
0x0015AdjustmentModestring 
0x0016Qualitystring 
0x0017Firmwarestring 
0x0018Softwarestring 
0x0019AutoBracketstring 
0x001aPreviewImageStart +
ChrominanceNoiseReduction
int32u*
string
(Sigma Photo Pro writes ChrominanceNoiseReduction here, but various +models use this for PreviewImageStart)
0x001bPreviewImageLength +
LuminanceNoiseReduction
int32u*
string
(Sigma Photo Pro writes LuminanceNoiseReduction here, but various models use +this for PreviewImageLength)
0x001cPreviewImageSize +
PreviewImageStart
int16u[2]
int32u*
(PreviewImageStart for the SD1 and Merrill/Quattro models, and +PreviewImageSize for others)
0x001dMakerNoteVersion +
PreviewImageLength
undef
int32u*
(PreviewImageLength for the SD1 and Merrill/Quattro models, and +MakerNoteVersion for others)
0x001ePreviewImageSizeint16u[2](only valid for some models)
0x001fAFPoint +
MakerNoteVersion
string
undef
(MakerNoteVersion for the SD1 and Merrill/Quattro models, and AFPoint for +others)
0x0022FileFormatstring(models other than the SD1 and Merrill/Quattro models)
0x0024Calibrationstring(models other than the SD1 and Merrill/Quattro models)
0x0026FileFormatstring(some newer models only)
0x0027LensType +
LensType
string
int16u
(some newer models only) +
--> Sigma LensType Values +
(some other models like the fp) +
--> Sigma LensType Values
0x002aLensFocalRangerational64u[2](some newer models only)
0x002bLensMaxApertureRangerational64u[2](some newer models only)
0x002cColorModeint32u(not valid for some models) + +
0 = n/a +
1 = Sepia +
2 = B&W +
3 = Standard +
4 = Vivid
  5 = Neutral +
6 = Portrait +
7 = Landscape +
8 = FOV Classic Blue
+
0x0030LensApertureRange +
Calibration
string
string
(Calibration for the SD1 and Merrill/Quattro models, and LensApertureRange +for others. Note that LensApertureRange changes with focal length, and some +models report the maximum aperture here)
0x0031FNumberrational64u(models other than the SD1 and Merrill/Quattro models)
0x0032ExposureTimerational64u(models other than the SD1 and Merrill/Quattro models)
0x0033ExposureTime2string(models other than the SD1, SD9, SD15 and Merrill/Quattro models)
0x0034BurstShotint32u(models other than the SD1 and Merrill/Quattro models)
0x0035ExposureCompensationrational64s(models other than the SD1 and Merrill/Quattro models)
0x0039SensorTemperaturestring(models other than the SD1 and Merrill/Quattro models)
0x003aFlashExposureComprational64s(models other than the SD1 and Merrill/Quattro models)
0x003bFirmwarestring(models other than the SD1 and Merrill/Quattro models)
0x003cWhiteBalancestring(models other than the SD1 and Merrill/Quattro models)
0x003dPictureModestring(same as ColorMode, but "Standard" when ColorMode is Sepia or B&W)
0x0048LensApertureRangestring(some newer models only)
0x0049FNumberrational64u(some newer models only)
0x004aExposureTimerational64u(some newer models only)
0x004bExposureTime2string(SD1 and DP Merrill models only) +
(DP Quattro models only)
0x004dExposureCompensationrational64s(some newer models only)
0x0055SensorTemperaturestring(some newer models only)
0x0056FlashExposureComprational64s(some newer models only)
0x0057Firmware2string(some newer models only)
0x0058WhiteBalancestring(some newer models only)
0x0059DigitalFilterstring(some newer models only)
0x0084Modelstring 
0x0086ISOint16u 
0x0087ResolutionModestring 
0x0088WhiteBalancestring 
0x008cFirmwarestring 
0x011fCameraCalibrationfloat[9] 
0x0120WBSettings---> Sigma WBSettings Tags
0x0121WBSettings2---> Sigma WBSettings2 Tags
+ +

Sigma LensType Values

+

Sigma LensType values are hexadecimal numbers stored as a string (without +the leading "0x").

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLensTypeValueLensType
0x10= Sigma 50mm F2.8 EX DG MACRO0x589= Sigma APO 70-200mm F2.8 EX DG OS HSM
0x10= Sigma 70mm F2.8 EX DG Macro0x594= Sigma 300-800mm F5.6 EX HSM IF APO
0x10= Sigma 105mm F2.8 EX DG Macro0x595= Sigma 300-800mm F5.6 EX DG APO HSM
0x16= Sigma 18-50mm F3.5-5.6 DC0x597= Sigma 200-500mm F2.8 APO EX DG
0x103= Sigma 180mm F3.5 EX IF HSM APO Macro0x5a8= Sigma 70-300mm F4-5.6 APO DG Macro (Motorized)
0x104= Sigma 150mm F2.8 EX DG HSM APO Macro0x5a9= Sigma 70-300mm F4-5.6 DG Macro (Motorized)
0x105= Sigma 180mm F3.5 EX DG HSM APO Macro0x605= Sigma 24-70mm F3.5-5.6 ASP HF
0x106= Sigma 150mm F2.8 EX DG OS HSM APO Macro0x633= Sigma 28-70mm F2.8-4 HS
0x107= Sigma 180mm F2.8 EX DG OS HSM APO Macro0x634= Sigma 28-70mm F2.8-4 DG
0x129= Sigma Lens (0x129)0x635= Sigma 24-105mm F4 DG OS HSM | A
0x129= Sigma 14mm F2.8 EX Aspherical0x644= Sigma 28-80mm F3.5-5.6 ASP HF Macro
0x129= Sigma 30mm F1.40x659= Sigma 28-80mm F3.5-5.6 Mini Zoom Macro II ASP
0x131= Sigma Lens (0x131)0x661= Sigma 28-105mm F2.8-4 IF ASP
0x131= Sigma 17-70mm F2.8-4.5 DC Macro0x663= Sigma 28-105mm F3.8-5.6 IF UC-III ASP
0x131= Sigma 70-200mm F2.8 APO EX HSM0x664= Sigma 28-105mm F2.8-4 IF DG ASP
0x131= Sigma 120-300mm F2.8 APO EX IF HSM0x667= Sigma 24-135mm F2.8-4.5 IF ASP
0x134= Sigma 100-300mm F4 EX DG HSM APO0x668= Sigma 17-70mm F2.8-4 DC Macro OS HSM
0x135= Sigma 120-300mm F2.8 EX DG HSM APO0x669= Sigma 17-70mm F2.8-4.5 DC HSM Macro
0x136= Sigma 120-300mm F2.8 EX DG OS HSM APO0x684= Sigma 55-200mm F4-5.6 DC
0x137= Sigma 120-300mm F2.8 DG OS HSM | S0x686= Sigma 50-200mm F4-5.6 DC OS HSM
0x143= Sigma 600mm F8 Mirror0x689= Sigma 17-70mm F2.8-4.5 DC Macro
0x145= Sigma Lens (0x145)0x690= Sigma 50-150mm F2.8 EX DC HSM APO
0x145= Sigma 15-30mm F3.5-4.5 EX DG Aspherical0x691= Sigma 50-150mm F2.8 EX DC APO HSM II
0x145= Sigma 18-50mm F2.8 EX DG0x692= Sigma APO 50-150mm F2.8 EX DC OS HSM
0x145= Sigma 20-40mm F2.8 EX DG0x693= Sigma 50-100mm F1.8 DC HSM | A
0x150= Sigma 30mm F1.4 DC HSM0x709= Sigma 28-135mm F3.8-5.6 IF ASP Macro
0x152= Sigma Lens (0x152)0x723= Sigma 135-400mm F4.5-5.6 ASP APO
0x152= Sigma APO 800mm F5.6 EX DG HSM0x725= Sigma 80-400mm F4.5-5.6 EX OS
0x152= Sigma 12-24mm F4.5-5.6 EX DG ASP HSM0x726= Sigma 80-400mm F4.5-5.6 EX DG OS APO
0x152= Sigma 10-20mm F4-5.6 EX DC HSM0x727= Sigma 135-400mm F4.5-5.6 DG ASP APO
0x165= Sigma 70-200mm F2.8 EX0x728= Sigma 120-400mm F4.5-5.6 DG APO OS HSM
0x169= Sigma 18-50mm F2.8 EX DC0x729= Sigma 100-400mm F5-6.3 DG OS HSM | C
0x183= Sigma 500mm F4.5 EX HSM APO0x730= Sigma 60-600mm F4.5-6.3 DG OS HSM | S
0x184= Sigma 500mm F4.5 EX DG HSM APO0x733= Sigma 170-500mm F5-6.3 ASP APO
0x185= Sigma 500mm F4 DG OS HSM | S0x734= Sigma 170-500mm F5-6.3 DG ASP APO
0x194= Sigma 300mm F2.8 EX HSM APO0x735= Sigma 50-500mm F4-6.3 EX RF HSM APO
0x195= Sigma 300mm F2.8 EX DG HSM APO0x736= Sigma 50-500mm F4-6.3 EX DG HSM APO
0x200= Sigma 12-24mm F4.5-5.6 EX DG ASP HSM0x737= Sigma 150-500mm F5-6.3 APO DG OS HSM
0x201= Sigma 10-20mm F4-5.6 EX DC HSM0x738= Sigma 50-500mm F4.5-6.3 APO DG OS HSM
0x202= Sigma 10-20mm F3.5 EX DC HSM0x740= Sigma 150-600mm F5-6.3 DG OS HSM | S
0x203= Sigma 8-16mm F4.5-5.6 DC HSM0x745= Sigma 150-600mm F5-6.3 DG OS HSM | C
0x204= Sigma 12-24mm F4.5-5.6 DG HSM II0x777= Sigma 18-200mm F3.5-6.3 DC
0x205= Sigma 12-24mm F4 DG HSM | A0x77d= Sigma 18-200mm F3.5-6.3 DC (Motorized)
0x210= Sigma 18-35mm F1.8 DC HSM | A0x785= Sigma 28-200mm F3.5-5.6 DL ASP IF HZM Macro
0x240= Sigma 135mm F1.8 DG HSM | A0x787= Sigma 28-200mm F3.5-5.6 Compact ASP HZ Macro
0x256= Sigma 105mm F2.8 EX Macro0x789= Sigma 18-125mm F3.5-5.6 DC
0x257= Sigma 105mm F2.8 EX DG Macro0x790= Sigma 28-300mm F3.5-6.3 DL ASP IF HZM
0x258= Sigma 105mm F2.8 EX DG OS HSM Macro0x793= Sigma 28-300mm F3.5-6.3 Macro
0x259= Sigma 105mm F1.4 DG HSM | A0x794= Sigma 28-200mm F3.5-5.6 DG Compact ASP HZ Macro
0x270= Sigma 70mm F2.8 EX DG Macro0x795= Sigma 28-300mm F3.5-6.3 DG Macro
0x271= Sigma 70mm F2.8 DG Macro | A0x823= Sigma 1.4X TC EX APO
0x300= Sigma 30mm F1.4 EX DC HSM0x824= Sigma 1.4X Teleconverter EX APO DG
0x301= Sigma 30mm F1.4 DC HSM | A0x853= Sigma 18-125mm F3.8-5.6 DC OS HSM
0x302= Sigma 30mm F1.4 DC DN | C0x861= Sigma 18-50mm F2.8-4.5 DC OS HSM
0x310= Sigma 50mm F1.4 EX DG HSM0x870= Sigma 2.0X Teleconverter TC-2001
0x311= Sigma 50mm F1.4 DG HSM | A0x875= Sigma 2.0X TC EX APO
0x320= Sigma 85mm F1.4 EX DG HSM0x876= Sigma 2.0X Teleconverter EX APO DG
0x321= Sigma 85mm F1.4 DG HSM | A0x879= Sigma 1.4X Teleconverter TC-1401
0x330= Sigma 30mm F2.8 EX DN0x880= Sigma 18-250mm F3.5-6.3 DC OS HSM
0x340= Sigma 35mm F1.4 DG HSM0x882= Sigma 18-200mm F3.5-6.3 II DC OS HSM
0x345= Sigma 50mm F2.8 EX Macro0x883= Sigma 18-250mm F3.5-6.3 DC Macro OS HSM
0x346= Sigma 50mm F2.8 EX DG Macro0x884= Sigma 17-70mm F2.8-4 DC OS HSM Macro | C
0x350= Sigma 60mm F2.8 DN | A0x885= Sigma 18-200mm F3.5-6.3 DC OS HSM Macro | C
0x400= Sigma 19mm F2.8 EX DN0x886= Sigma 18-300mm F3.5-6.3 DC OS HSM Macro | C
0x401= Sigma 24mm F1.4 DG HSM | A0x888= Sigma 18-200mm F3.5-6.3 DC OS
0x411= Sigma 20mm F1.8 EX DG ASP RF0x890= Sigma Mount Converter MC-11
0x412= Sigma 20mm F1.4 DG HSM | A0x929= Sigma 60mm F2.8 DN | A
0x432= Sigma 24mm F1.8 EX DG ASP Macro0x1003= Sigma 19mm F2.8
0x440= Sigma 28mm F1.8 EX DG ASP Macro0x1004= Sigma 30mm F2.8
0x450= Sigma 14mm F1.8 DH HSM | A0x1005= Sigma 50mm F2.8 Macro
0x461= Sigma 14mm F2.8 EX ASP HSM0x1006= Sigma 19mm F2.8
0x475= Sigma 15mm F2.8 EX Diagonal FishEye0x1007= Sigma 30mm F2.8
0x476= Sigma 15mm F2.8 EX DG Diagonal Fisheye0x1008= Sigma 50mm F2.8 Macro
0x477= Sigma 10mm F2.8 EX DC HSM Fisheye0x1009= Sigma 14mm F4
0x483= Sigma 8mm F4 EX Circular Fisheye0x4001= Lumix S 24-105mm F4 Macro OIS (S-R24105)
0x484= Sigma 8mm F4 EX DG Circular Fisheye0x4002= Lumix S 70-200mm F4 OIS (S-R70200)
0x485= Sigma 8mm F3.5 EX DG Circular Fisheye0x4003= Lumix S 50mm F1.4 (S-X50)
0x486= Sigma 4.5mm F2.8 EX DC HSM Circular Fisheye0x4006= Lumix S 24-70mm F2.8 (S-E2470)
0x504= Sigma 70-300mm F4-5.6 Macro Super0x4007= Lumix S 16-35mm F4 (S-R1635)
0x505= Sigma APO 70-300mm F4-5.6 Macro Super0x4008= Lumix S 70-200mm F2.8 OIS (S-E70200)
0x506= Sigma 70-300mm F4-5.6 APO Macro Super II0x400b= Lumix S 20-60mm F3.5-5.6 (S-R2060)
0x507= Sigma 70-300mm F4-5.6 DL Macro Super II0x400c= Lumix S 85mm F1.8 (S-S85)
0x508= Sigma 70-300mm F4-5.6 DG APO Macro0x400d= Lumix S 70-300 F4.5-5.6 Macro OIS (S-R70300)
0x509= Sigma 70-300mm F4-5.6 DG Macro0x400f= Lumix S 24mm F1.8 (S-S24)
0x510= Sigma 17-35 F2.8-4 EX DG ASP0x4010= Lumix S 35mm F1.8 (S-S35)
0x512= Sigma 15-30mm F3.5-4.5 EX DG ASP DF0x4011= LUMIX S 18mm F1.8 (S-S18)
0x513= Sigma 20-40mm F2.8 EX DG0x6001= Sigma 150-600mm F5-6.3 DG OS HSM | S
0x519= Sigma 17-35 F2.8-4 EX ASP HSM0x6003= Sigma 45mm F2.8 DG DN | C
0x520= Sigma 100-300mm F4.5-6.7 DL0x6005= Sigma 14-24mm F2.8 DG DN | A
0x521= Sigma 18-50mm F3.5-5.6 DC Macro0x6006= Sigma 50mm F1.4 DG HSM | A
0x527= Sigma 100-300mm F4 EX IF HSM0x6011= Sigma 24-70mm F2.8 DG DN | A
0x529= Sigma 120-300mm F2.8 EX HSM IF APO0x6012= Sigma 100-400mm F5-6.3 DG DN OS | C
0x545= Sigma 28-70mm F2.8 EX ASP DF0x6013= Sigma 100-400mm F5-6.3 DG DN OS | C + TC-1411
0x547= Sigma 24-60mm F2.8 EX DG0x6015= Sigma 85mm F1.4 DG DN | A
0x548= Sigma 24-70mm F2.8 EX DG Macro0x6017= Sigma 65mm F2 DG DN | C
0x549= Sigma 28-70mm F2.8 EX DG0x6018= Sigma 35mm F2 DG DN | C
0x566= Sigma 70-200mm F2.8 EX IF APO0x601a= Sigma 28-70mm F2.8 DG DN | C
0x567= Sigma 70-200mm F2.8 EX IF HSM APO0x601b= Sigma 150-600mm F5-6.3 DG DN OS | S
0x568= Sigma 70-200mm F2.8 EX DG IF HSM APO0x6020= Sigma 35mm F1.4 DG DN | A
0x569= Sigma 70-200 F2.8 EX DG HSM APO Macro0x6021= Sigma 90mm F2.8 DG DN | C
0x571= Sigma 24-70mm F2.8 IF EX DG HSM0x6023= Sigma 20mm F2 DG DN | C
0x572= Sigma 70-300mm F4-5.6 DG OS0x6025= Sigma 20mm F1.4 DG DN | A
0x576= Sigma 24-70mm F2.8 DG OS HSM | A0x6026= Sigma 24mm F1.4 DG DN | A
0x579= Sigma 70-200mm F2.8 EX DG HSM APO Macro0x602c= Sigma 50mm F1.4 DG DN | A (2023)
0x580= Sigma 18-50mm F2.8 EX DC0x8005= Sigma 35mm F1.4 DG HSM | A
0x581= Sigma 18-50mm F2.8 EX DC Macro0x8009= Sigma 18-35mm F1.8 DC HSM | A
0x582= Sigma 18-50mm F2.8 EX DC HSM Macro0x8900= Sigma 70-300mm F4-5.6 DG OS
0x583= Sigma 17-50mm F2.8 EX DC OS HSM0xa100= Sigma 24-70mm F2.8 DG Macro
0x588= Sigma 24-35mm F2 DG HSM | A  
+ +

Sigma WBSettings Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0WB_RGBLevelsAutofloat[3] 
3WB_RGBLevelsDaylightfloat[3] 
6WB_RGBLevelsShadefloat[3] 
9WB_RGBLevelsOvercastfloat[3] 
12WB_RGBLevelsIncandescentfloat[3] 
15WB_RGBLevelsFluorescentfloat[3] 
18WB_RGBLevelsFlashfloat[3] 
21WB_RGBLevelsCustom1float[3] 
24WB_RGBLevelsCustom2float[3] 
27WB_RGBLevelsCustom3float[3] 
+ +

Sigma WBSettings2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
0WB_RGBLevelsUnknown0?float[3] 
3WB_RGBLevelsUnknown1?float[3] 
6WB_RGBLevelsUnknown2?float[3] 
9WB_RGBLevelsUnknown3?float[3] 
12WB_RGBLevelsUnknown4?float[3] 
15WB_RGBLevelsUnknown5?float[3] 
18WB_RGBLevelsUnknown6?float[3] 
21WB_RGBLevelsUnknown7?float[3] 
24WB_RGBLevelsUnknown8?float[3] 
27WB_RGBLevelsUnknown9?float[3] 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Mar 15, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/SigmaRaw.html b/ExifTool/html/TagNames/SigmaRaw.html new file mode 100644 index 0000000..1e62ff0 --- /dev/null +++ b/ExifTool/html/TagNames/SigmaRaw.html @@ -0,0 +1,439 @@ + + + + +SigmaRaw Tags + + + +

SigmaRaw Tags

+

These tags are used in Sigma and Foveon RAW (.X3F) images. Metadata is also +extracted from the JpgFromRaw image if it exists (all models but the SD9 and +SD10). Currently, metadata may only be written to the embedded JpgFromRaw.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Header'Header---> SigmaRaw Header Tags
'Header4'Header4---> SigmaRaw Header4 Tags
'HeaderExt'HeaderExt---> SigmaRaw HeaderExt Tags
'IMA2'PreviewImage +
JpgFromRaw
no
no
 
'IMAG'PreviewImageno 
'PROP'Properties---> SigmaRaw Properties Tags
+ +

SigmaRaw Header Tags

+

Information extracted from the header of an X3F file.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001FileVersionno 
0x0002ImageUniqueIDno 
0x0006MarkBitsno 
0x0007ImageWidthno 
0x0008ImageHeightno 
0x0009Rotationno 
0x000aWhiteBalanceno 
0x0012SceneCaptureTypeno 
+ +

SigmaRaw Header4 Tags

+

Header information for version 4.0 or greater X3F.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1FileVersionno 
10ImageWidthno 
11ImageHeightno 
12Rotationno 
+ +

SigmaRaw HeaderExt Tags

+

Extended header data found in version 2.1 and 2.2 files

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000Unusedno 
0x0001ExposureAdjustno 
0x0002Contrastno 
0x0003Shadowno 
0x0004Highlightno 
0x0005Saturationno 
0x0006Sharpnessno 
0x0007RedAdjustno 
0x0008GreenAdjustno 
0x0009BlueAdjustno 
0x000aX3FillLightno 
+ +

SigmaRaw Properties Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'AEMODE'MeteringModeno8 = 8-segment +
'A' = Average +
'C' = Center-weighted average
'AFAREA'AFAreano 
'AFINFOCUS'AFInFocusno 
'AFMODE'FocusModeno 
'APERTURE'FNumberno 
'AP_DESC'ApertureDisplayedno 
'BRACKET'BracketShotno 
'BURST'BurstShotno 
'CAMMANUF'Makeno 
'CAMMODEL'Modelno 
'CAMNAME'CameraNameno 
'CAMSERIAL'SerialNumberno 
'CM_DESC'SceneCaptureTypeno 
'COLORSPACE'ColorSpaceno 
'DRIVE'DriveModeno + +
'10S' = 10 s Timer +
'2S' = 2 s Timer +
'AB' = Auto Bracket +
'MULTI' = Multi Shot
  'OFF' = Off +
'SINGLE' = Single Shot +
'UP' = Mirror Up
+
'EVAL_STATE'EvalStateno 
'EXPCOMP'ExposureCompensationno 
'EXPNET'NetExposureCompensationno 
'EXPTIME'IntegrationTimeno 
'FIRMVERS'FirmwareVersionno 
'FLASH'FlashModeno 
'FLASHEXPCOMP'FlashExpCompno 
'FLASHPOWER'FlashPowerno 
'FLASHTTLMODE'FlashTTLModeno 
'FLASHTYPE'FlashTypeno 
'FLENGTH'FocalLengthno 
'FLEQ35MM'FocalLengthIn35mmFormatno 
'FOCUS'Focusno'AF' = Auto-focus Locked +
'M' = Manual +
'NO LOCK' = Auto-focus Didn't Lock
'IMAGEBOARDID'ImageBoardIDno 
'IMAGERBOARDID'ImagerBoardIDno 
'IMAGERTEMP'SensorTemperatureno 
'ISO'ISOno 
'LENSARANGE'LensApertureRangeno 
'LENSFRANGE'LensFocalRangeno 
'LENSMODEL'LensTypeno--> Sigma LensType Values
'PMODE'ExposureProgramno'A' = Aperture Priority +
'M' = Manual +
'P' = Program +
'S' = Shutter Priority
'RESOLUTION'Qualityno'HI' = High +
'LOW' = Low +
'MED' = Medium
'SENSORID'SensorIDno 
'SHUTTER'ExposureTimeno 
'SH_DESC'ShutterSpeedDisplayedno 
'TIME'DateTimeOriginalno 
'VERSION_BF'VersionBFno 
'WB_DESC'WhiteBalanceno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 2, 2014 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Sony.html b/ExifTool/html/TagNames/Sony.html new file mode 100644 index 0000000..81f2956 --- /dev/null +++ b/ExifTool/html/TagNames/Sony.html @@ -0,0 +1,12041 @@ + + + + +Sony Tags + + + +

Sony Tags

+

The following information has been decoded from the MakerNotes of Sony +cameras. Some of these tags have been inherited from the Minolta +MakerNotes.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0010CameraInfo +
CameraInfo2 +
CameraInfo3 +
CameraInfoUnknown
-
-
-
-
--> Sony CameraInfo Tags +
--> Sony CameraInfo2 Tags +
--> Sony CameraInfo3 Tags +
--> Sony CameraInfoUnknown Tags
0x0020FocusInfo +
MoreInfo
-
-
--> Sony FocusInfo Tags +
--> Sony MoreInfo Tags
0x0102Qualityint32u +
0 = RAW +
1 = Super Fine +
2 = Fine +
3 = Standard +
4 = Economy +
5 = Extra Fine +
6 = RAW + JPEG/HEIF +
7 = Compressed RAW +
8 = Compressed RAW + JPEG +
9 = Light +
4294967295 = n/a
+
0x0104FlashExposureComprational64s 
0x0105Teleconverterint32u +
0x0 = None +
0x4 = Minolta/Sony AF 1.4x APO (D) (0x04) +
0x5 = Minolta/Sony AF 2x APO (D) (0x05) +
0x48 = Minolta/Sony AF 2x APO (D) +
0x50 = Minolta AF 2x APO II +
0x60 = Minolta AF 2x APO +
0x88 = Minolta/Sony AF 1.4x APO (D) +
0x90 = Minolta AF 1.4x APO II +
0xa0 = Minolta AF 1.4x APO
+
0x0112WhiteBalanceFineTuneint32u 
0x0114CameraSettings +
CameraSettings2 +
CameraSettings3 +
CameraSettingsUnknown
-
-
-
-
--> Sony CameraSettings Tags +
--> Sony CameraSettings2 Tags +
--> Sony CameraSettings3 Tags +
--> Sony CameraSettingsUnknown Tags
0x0115WhiteBalanceint32u +
0x0 = Auto +
0x1 = Color Temperature/Color Filter +
0x10 = Daylight +
0x20 = Cloudy +
0x30 = Shade +
0x40 = Tungsten +
0x50 = Flash +
0x60 = Fluorescent +
0x70 = Custom +
0x80 = Underwater
+
0x0116ExtraInfo +
ExtraInfo2 +
ExtraInfo3
-
-
-
--> Sony ExtraInfo Tags +
--> Sony ExtraInfo2 Tags +
--> Sony ExtraInfo3 Tags
0x0e00PrintIM---> PrintIM Tags
0x1000MultiBurstModeundef(MultiBurst tags valid only for models with this feature, like the F88) +
0 = Off +
1 = On
0x1001MultiBurstImageWidthint16u 
0x1002MultiBurstImageHeightint16u 
0x1003Panorama---> Sony Panorama Tags
0x2001PreviewImageundef(HD-size preview in JPEG images from almost all DSLR/SLT/ILCA/NEX/ILCE.)
0x2002Ratingint32u 
0x2004Contrastint32s 
0x2005Saturationint32s 
0x2006Sharpnessint32s 
0x2007Brightnessint32s 
0x2008LongExposureNoiseReductionint32u +
0x0 = Off +
0x1 = On (unused) +
0x10001 = On (dark subtracted) +
0xffff0000 = Off (65535) +
0xffff0001 = On (65535) +
0xffffffff = n/a
+
0x2009HighISONoiseReductionint16u + +
0 = Off +
1 = Low +
2 = Normal
  3 = High +
256 = Auto +
65535 = n/a
+
0x200aHDRint32u(stored as a 32-bit integer, but read as two 16-bit integers) +
[Value 0]
+ + +
0x0 = Off +
0x1 = Auto +
0x10 = 1.0 EV +
0x11 = 1.5 EV +
0x12 = 2.0 EV
  0x13 = 2.5 EV +
0x14 = 3.0 EV +
0x15 = 3.5 EV +
0x16 = 4.0 EV +
0x17 = 4.5 EV
  0x18 = 5.0 EV +
0x19 = 5.5 EV +
0x1a = 6.0 EV
+[Value 1] +
0x0 = Uncorrected image +
0x1 = HDR image (good) +
0x2 = HDR image (fail 1) +
0x3 = HDR image (fail 2)
0x200bMultiFrameNoiseReductionint32u(may not be valid for RS100) +
0 = Off +
1 = On +
255 = n/a
0x200ePictureEffectint16u +
0 = Off +
1 = Toy Camera +
2 = Pop Color +
3 = Posterization +
4 = Posterization B/W +
5 = Retro Photo +
6 = Soft High Key +
7 = Partial Color (red) +
8 = Partial Color (green) +
9 = Partial Color (blue) +
10 = Partial Color (yellow) +
13 = High Contrast Monochrome +
16 = Toy Camera (normal) +
17 = Toy Camera (cool) +
18 = Toy Camera (warm) +
19 = Toy Camera (green) +
20 = Toy Camera (magenta) +
32 = Soft Focus (low) +
33 = Soft Focus +
34 = Soft Focus (high) +
48 = Miniature (auto) +
49 = Miniature (top) +
50 = Miniature (middle horizontal) +
51 = Miniature (bottom) +
52 = Miniature (left) +
53 = Miniature (middle vertical) +
54 = Miniature (right) +
64 = HDR Painting (low) +
65 = HDR Painting +
66 = HDR Painting (high) +
80 = Rich-tone Monochrome +
97 = Water Color +
98 = Water Color 2 +
112 = Illustration (low) +
113 = Illustration +
114 = Illustration (high)
+
0x200fSoftSkinEffectint32u0 = Off +
1 = Low +
2 = Mid +
3 = High +
4294967295 = n/a
0x2010Tag2010a +
Tag2010b +
Tag2010c +
Tag2010d +
Tag2010e +
Tag2010f +
Tag2010g +
Tag2010h +
Tag2010i
-
-
-
-
-
-
-
-
-
--> Sony Tag2010a Tags +
--> Sony Tag2010b Tags +
--> Sony Tag2010c Tags +
--> Sony Tag2010d Tags +
--> Sony Tag2010e Tags +
--> Sony Tag2010f Tags +
--> Sony Tag2010g Tags +
--> Sony Tag2010h Tags +
--> Sony Tag2010i Tags
0x2011VignettingCorrectionint32u0 = Off +
2 = Auto +
4294967295 = n/a
0x2012LateralChromaticAberrationint32u0 = Off +
2 = Auto +
4294967295 = n/a
0x2013DistortionCorrectionSettingint32u0 = Off +
2 = Auto +
4294967295 = n/a
0x2014WBShiftAB_GMint32s[2](2 numbers: 1. positive is a shift toward amber, 2. positive is a shift +toward magenta)
0x2016AutoPortraitFramedint16u("Yes" if this image was created by the Auto Portrait Framing feature) +
0 = No +
1 = Yes
0x2017FlashActionint32u0 = Did not fire +
1 = Flash Fired +
2 = External Flash Fired +
3 = Wireless Controlled Flash Fired
0x201aElectronicFrontCurtainShutterint32u0 = Off +
1 = On
0x201bFocusModeint8u + +
0 = Manual +
2 = AF-S +
3 = AF-C
  4 = AF-A +
6 = DMF +
7 = AF-D
+
0x201cAFAreaModeSettingint8u(SLT models) +
0 = Wide +
4 = Local +
8 = Zone +
9 = Spot +
(NEX, ILCE and some DSC models)
+
0 = Wide +
1 = Center +
3 = Flexible Spot +
4 = Flexible Spot (LA-EA4) +
9 = Center (LA-EA4) +
11 = Zone +
12 = Expanded Flexible Spot
+(ILCA models) +
0 = Wide +
4 = Flexible Spot +
8 = Zone +
9 = Center +
12 = Expanded Flexible Spot
0x201dFlexibleSpotPositionint16u[2](X and Y coordinates of the AF point, valid only when AFAreaMode is Flexible +Spot)
0x201eAFPointSelectedint8u(SLT models or ILCE with LA-EA2/EA4) + +
0 = Auto +
1 = Center +
2 = Top +
3 = Upper-right +
4 = Right +
5 = Lower-right +
6 = Bottom +
7 = Lower-left +
8 = Left +
9 = Upper-left
  10 = Far Right +
11 = Far Left +
12 = Upper-middle +
13 = Near Right +
14 = Lower-middle +
15 = Near Left +
16 = Upper Far Right +
17 = Lower Far Right +
18 = Lower Far Left +
19 = Upper Far Left
+(ILCA-68 and ILCA-77M2) + + + + +
-1 = Auto +
0 = A5 +
1 = A6 +
2 = A7 +
3 = B2 +
4 = B3 +
5 = B4 +
6 = B5 +
7 = B6 +
8 = B7 +
9 = B8 +
10 = B9 +
11 = B10 +
12 = C1 +
13 = C2 +
14 = C3
  15 = C4 +
16 = C5 +
17 = C6 +
18 = C7 +
19 = C8 +
20 = C9 +
21 = C10 +
22 = C11 +
23 = D1 +
24 = D2 +
25 = D3 +
26 = D4 +
27 = D5 +
28 = D6 +
29 = D7 +
30 = D8
  31 = D9 +
32 = D10 +
33 = D11 +
34 = E1 +
35 = E2 +
36 = E3 +
37 = E4 +
38 = E5 +
39 = E6 (Center) +
40 = E7 +
41 = E8 +
42 = E9 +
43 = E10 +
44 = E11 +
45 = F1 +
46 = F2
  47 = F3 +
48 = F4 +
49 = F5 +
50 = F6 +
51 = F7 +
52 = F8 +
53 = F9 +
54 = F10 +
55 = F11 +
56 = G1 +
57 = G2 +
58 = G3 +
59 = G4 +
60 = G5 +
61 = G6 +
62 = G7
  63 = G8 +
64 = G9 +
65 = G10 +
66 = G11 +
67 = H2 +
68 = H3 +
69 = H4 +
70 = H5 +
71 = H6 +
72 = H7 +
73 = H8 +
74 = H9 +
75 = H10 +
76 = I5 +
77 = I6 +
78 = I7
+(ILCA-99M2 when AFAreaModeSetting is not Zone) + + + +
0 = Auto +
93 = A5 (93) +
94 = A6 (94) +
95 = A7 (95) +
106 = B2 (106) +
107 = B3 (107) +
108 = B4 (108) +
110 = B5 (110) +
111 = B6 (111) +
112 = B7 (112) +
114 = B8 (114) +
115 = B9 (115) +
116 = B10 (116) +
122 = C1 (122) +
123 = C2 (123) +
124 = C3 (124) +
125 = C4 (125) +
127 = C5 (127) +
128 = C6 (128) +
129 = C7 (129)
  131 = C8 (131) +
132 = C9 (132) +
133 = C10 (133) +
134 = C11 (134) +
139 = D1 (139) +
140 = D2 (140) +
141 = D3 (141) +
142 = D4 (142) +
144 = D5 (144) +
145 = D6 (145) +
146 = D7 (146) +
148 = D8 (148) +
149 = D9 (149) +
150 = D10 (150) +
151 = D11 (151) +
156 = E1 (156) +
157 = E2 (157) +
158 = E3 (158) +
159 = E4 (159) +
161 = E5 (161)
  162 = E6 (162, Center) +
163 = E7 (163) +
165 = E8 (165) +
166 = E9 (166) +
167 = E10 (167) +
168 = E11 (168) +
173 = F1 (173) +
174 = F2 (174) +
175 = F3 (175) +
176 = F4 (176) +
178 = F5 (178) +
179 = F6 (179) +
180 = F7 (180) +
182 = F8 (182) +
183 = F9 (183) +
184 = F10 (184) +
185 = F11 (185) +
190 = G1 (190) +
191 = G2 (191) +
192 = G3 (192)
  193 = G4 (193) +
195 = G5 (195) +
196 = G6 (196) +
197 = G7 (197) +
199 = G8 (199) +
200 = G9 (200) +
201 = G10 (201) +
202 = G11 (202) +
208 = H2 (208) +
209 = H3 (209) +
210 = H4 (210) +
212 = H5 (212) +
213 = H6 (213) +
214 = H7 (214) +
216 = H8 (216) +
217 = H9 (217) +
218 = H10 (218) +
229 = I5 (229) +
230 = I6 (230) +
231 = I7 (231)
+(ILCA models when AFAreaModeSetting is set to Zone) + +
0 = n/a +
1 = Top Left Zone +
2 = Top Zone +
3 = Top Right Zone +
4 = Left Zone
  5 = Center Zone +
6 = Right Zone +
7 = Bottom Left Zone +
8 = Bottom Zone +
9 = Bottom Right Zone
+(NEX and ILCE models) + +
0 = n/a +
1 = Center Zone +
2 = Top Zone +
3 = Right Zone +
4 = Left Zone
  5 = Bottom Zone +
6 = Bottom Right Zone +
7 = Bottom Left Zone +
8 = Top Left Zone +
9 = Top Right Zone
+
0x2020AFPointsUsedno(SLT models, or NEX/ILCE with A-mount lenses) + +
0x0 = (none) +
Bit 0 = Center +
Bit 1 = Top +
Bit 2 = Upper-right +
Bit 3 = Right +
Bit 4 = Lower-right +
Bit 5 = Bottom +
Bit 6 = Lower-left +
Bit 7 = Left +
Bit 8 = Upper-left
  Bit 9 = Far Right +
Bit 10 = Far Left +
Bit 11 = Upper-middle +
Bit 12 = Near Right +
Bit 13 = Lower-middle +
Bit 14 = Near Left +
Bit 15 = Upper Far Right +
Bit 16 = Lower Far Right +
Bit 17 = Lower Far Left +
Bit 18 = Upper Far Left
+(ILCA models) + + + +
0x0 = (none) +
Bit 0 = A5 +
Bit 1 = A6 +
Bit 2 = A7 +
Bit 3 = B2 +
Bit 4 = B3 +
Bit 5 = B4 +
Bit 6 = B5 +
Bit 7 = B6 +
Bit 8 = B7 +
Bit 9 = B8 +
Bit 10 = B9 +
Bit 11 = B10 +
Bit 12 = C1 +
Bit 13 = C2 +
Bit 14 = C3 +
Bit 15 = C4 +
Bit 16 = C5 +
Bit 17 = C6 +
Bit 18 = C7
  Bit 19 = C8 +
Bit 20 = C9 +
Bit 21 = C10 +
Bit 22 = C11 +
Bit 23 = D1 +
Bit 24 = D2 +
Bit 25 = D3 +
Bit 26 = D4 +
Bit 27 = D5 +
Bit 28 = D6 +
Bit 29 = D7 +
Bit 30 = D8 +
Bit 31 = D9 +
Bit 32 = D10 +
Bit 33 = D11 +
Bit 34 = E1 +
Bit 35 = E2 +
Bit 36 = E3 +
Bit 37 = E4 +
Bit 38 = E5
  Bit 39 = E6 +
Bit 40 = E7 +
Bit 41 = E8 +
Bit 42 = E9 +
Bit 43 = E10 +
Bit 44 = E11 +
Bit 45 = F1 +
Bit 46 = F2 +
Bit 47 = F3 +
Bit 48 = F4 +
Bit 49 = F5 +
Bit 50 = F6 +
Bit 51 = F7 +
Bit 52 = F8 +
Bit 53 = F9 +
Bit 54 = F10 +
Bit 55 = F11 +
Bit 56 = G1 +
Bit 57 = G2 +
Bit 58 = G3
  Bit 59 = G4 +
Bit 60 = G5 +
Bit 61 = G6 +
Bit 62 = G7 +
Bit 63 = G8 +
Bit 64 = G9 +
Bit 65 = G10 +
Bit 66 = G11 +
Bit 67 = H2 +
Bit 68 = H3 +
Bit 69 = H4 +
Bit 70 = H5 +
Bit 71 = H6 +
Bit 72 = H7 +
Bit 73 = H8 +
Bit 74 = H9 +
Bit 75 = H10 +
Bit 76 = I5 +
Bit 77 = I6 +
Bit 78 = I7
+
0x2021AFTrackingint8u0 = Off +
1 = Face tracking +
2 = Lock On AF
0x2022FocalPlaneAFPointsUsedno(On-sensor/focal-plane phase AF points for ILCE with hybrid AF) +
0x0 = (none) +
0x0 = (none)
0x2023MultiFrameNREffectint32u0 = Normal +
1 = High
0x2026WBShiftAB_GM_Preciseint32s[2]~(2 numbers: 1. positive is a shift toward amber, 2. positive is a shift +toward magenta)
0x2027FocusLocationint16u[4] 
0x2028VariableLowPassFilterint16u[2]'0 0' = n/a +
'1 0' = Off +
'1 1' = Standard +
'1 2' = High +
'65535 65535' = n/a
0x2029RAWFileTypeint16u0 = Compressed RAW +
1 = Uncompressed RAW +
2 = Lossless Compressed RAW +
65535 = n/a
0x202aTag202a---> Sony Tag202a Tags
0x202bPrioritySetInAWBint8u0 = Standard +
1 = Ambience +
2 = White
0x202cMeteringMode2int16u +
0x100 = Multi-segment +
0x200 = Center-weighted average +
0x301 = Spot (Standard) +
0x302 = Spot (Large) +
0x400 = Average +
0x500 = Highlight
+
0x202dExposureStandardAdjustmentrational64s 
0x202eQualityint16u[2] +
'0 0' = n/a +
'0 1' = Standard +
'0 2' = Fine +
'0 3' = Extra Fine +
'0 4' = Light +
'1 0' = RAW +
'1 1' = RAW + Standard +
'1 2' = RAW + Fine +
'1 3' = RAW + Extra Fine +
'1 4' = RAW + Light +
'2 0' = S-size RAW +
'3 0' = M-size RAW +
'3 2' = M-size RAW + Fine +
'3 3' = M-size RAW + Extra Fine
+
0x202fPixelShiftInfoundef'00000000 0 0 0x0' = n/a
0x2031SerialNumberstring 
0x2032Shadowsint32s 
0x2033Highlightsint32s 
0x2034Fadeint32s 
0x2035SharpnessRangeint32s 
0x2036Clarityint32s 
0x2037FocusFrameSizeno(width and height of FocusFrame, centered on FocusLocation)
0x2039JPEG-HEIFSwitchint16u0 = JPEG +
1 = HEIF +
65535 = n/a
0x3000ShotInfo---> Sony ShotInfo Tags
0x900bTag900b---> Sony Tag900b Tags
0x9050Tag9050a +
Tag9050b +
Tag9050c +
Tag9050d
-
-
-
-
--> Sony Tag9050a Tags +
--> Sony Tag9050b Tags +
--> Sony Tag9050c Tags +
--> Sony Tag9050d Tags
0x9400Tag9400a +
Tag9400b +
Tag9400c
-
-
-
--> Sony Tag9400a Tags +
--> Sony Tag9400b Tags +
--> Sony Tag9400c Tags
0x9401Tag9401---> Sony Tag9401 Tags
0x9402Tag9402---> Sony Tag9402 Tags
0x9403Tag9403---> Sony Tag9403 Tags
0x9404Tag9404a +
Tag9404b +
Tag9404c
-
-
-
--> Sony Tag9404a Tags +
--> Sony Tag9404b Tags +
--> Sony Tag9404c Tags
0x9405Tag9405a +
Tag9405b
-
-
--> Sony Tag9405a Tags +
--> Sony Tag9405b Tags
0x9406Tag9406---> Sony Tag9406 Tags
0x940aTag940a---> Sony Tag940a Tags
0x940cTag940c---> Sony Tag940c Tags
0x940eAFInfo +
Tag940e
-
-
--> Sony AFInfo Tags +
--> Sony Tag940e Tags
0x9416Sony_0x9416---> Sony Tag9416 Tags
0xb000FileFormatint8u[4] + +
'0 0 0 2' = JPEG +
'1 0 0 0' = SR2 +
'2 0 0 0' = ARW 1.0 +
'3 0 0 0' = ARW 2.0 +
'3 1 0 0' = ARW 2.1 +
'3 2 0 0' = ARW 2.2 +
'3 3 0 0' = ARW 2.3
  '3 3 1 0' = ARW 2.3.1 +
'3 3 2 0' = ARW 2.3.2 +
'3 3 3 0' = ARW 2.3.3 +
'3 3 5 0' = ARW 2.3.5 +
'4 0 0 0' = ARW 4.0 +
'4 0 1 0' = ARW 4.0.1
+
0xb001SonyModelIDint16u + +
2 = DSC-R1 +
256 = DSLR-A100 +
257 = DSLR-A900 +
258 = DSLR-A700 +
259 = DSLR-A200 +
260 = DSLR-A350 +
261 = DSLR-A300 +
262 = DSLR-A900 (APS-C mode) +
263 = DSLR-A380/A390 +
264 = DSLR-A330 +
265 = DSLR-A230 +
266 = DSLR-A290 +
269 = DSLR-A850 +
270 = DSLR-A850 (APS-C mode) +
273 = DSLR-A550 +
274 = DSLR-A500 +
275 = DSLR-A450 +
278 = NEX-5 +
279 = NEX-3 +
280 = SLT-A33 +
281 = SLT-A55 / SLT-A55V +
282 = DSLR-A560 +
283 = DSLR-A580 +
284 = NEX-C3 +
285 = SLT-A35 +
286 = SLT-A65 / SLT-A65V +
287 = SLT-A77 / SLT-A77V +
288 = NEX-5N +
289 = NEX-7 +
290 = NEX-VG20E +
291 = SLT-A37 +
292 = SLT-A57 +
293 = NEX-F3 +
294 = SLT-A99 / SLT-A99V +
295 = NEX-6 +
296 = NEX-5R +
297 = DSC-RX100 +
298 = DSC-RX1 +
299 = NEX-VG900 +
300 = NEX-VG30E +
302 = ILCE-3000 / ILCE-3500 +
303 = SLT-A58 +
305 = NEX-3N +
306 = ILCE-7 +
307 = NEX-5T +
308 = DSC-RX100M2 +
309 = DSC-RX10 +
310 = DSC-RX1R +
311 = ILCE-7R +
312 = ILCE-6000
  313 = ILCE-5000 +
317 = DSC-RX100M3 +
318 = ILCE-7S +
319 = ILCA-77M2 +
339 = ILCE-5100 +
340 = ILCE-7M2 +
341 = DSC-RX100M4 +
342 = DSC-RX10M2 +
344 = DSC-RX1RM2 +
346 = ILCE-QX1 +
347 = ILCE-7RM2 +
350 = ILCE-7SM2 +
353 = ILCA-68 +
354 = ILCA-99M2 +
355 = DSC-RX10M3 +
356 = DSC-RX100M5 +
357 = ILCE-6300 +
358 = ILCE-9 +
360 = ILCE-6500 +
362 = ILCE-7RM3 +
363 = ILCE-7M3 +
364 = DSC-RX0 +
365 = DSC-RX10M4 +
366 = DSC-RX100M6 +
367 = DSC-HX99 +
369 = DSC-RX100M5A +
371 = ILCE-6400 +
372 = DSC-RX0M2 +
373 = DSC-HX95 +
374 = DSC-RX100M7 +
375 = ILCE-7RM4 +
376 = ILCE-9M2 +
378 = ILCE-6600 +
379 = ILCE-6100 +
380 = ZV-1 +
381 = ILCE-7C +
382 = ZV-E10 +
383 = ILCE-7SM3 +
384 = ILCE-1 +
385 = ILME-FX3 +
386 = ILCE-7RM3A +
387 = ILCE-7RM4A +
388 = ILCE-7M4 +
389 = ZV-1F +
390 = ILCE-7RM5 +
391 = ILME-FX30 +
393 = ZV-E1 +
394 = ILCE-6700 +
395 = ZV-1M2
+
0xb020CreativeStylestring +
'AdobeRGB' = Adobe RGB +
'Autumnleaves' = Autumn Leaves +
'BW' = B&W +
'Clear' = Clear +
'Deep' = Deep +
'FL' = FL +
'IN' = IN +
'Landscape' = Landscape +
'Light' = Light +
'Neutral' = Neutral +
'Nightview' = Night View/Portrait +
'None' = None +
'Portrait' = Portrait +
'Real' = Real +
'SH' = SH +
'Sepia' = Sepia +
'Standard' = Standard +
'Sunset' = Sunset +
'VV2' = Vivid 2 +
'Vivid' = Vivid
+
0xb021ColorTemperatureint32u 
0xb022ColorCompensationFilterint32u(negative is green, positive is magenta)
0xb023SceneModeint32u + +
0 = Standard +
1 = Portrait +
2 = Text +
3 = Night Scene +
4 = Sunset +
5 = Sports +
6 = Landscape +
7 = Night Portrait +
8 = Macro +
9 = Super Macro +
16 = Auto +
17 = Night View/Portrait +
18 = Sweep Panorama
  19 = Handheld Night Shot +
20 = Anti Motion Blur +
21 = Cont. Priority AE +
22 = Auto+ +
23 = 3D Sweep Panorama +
24 = Superior Auto +
25 = High Sensitivity +
26 = Fireworks +
27 = Food +
28 = Pet +
33 = HDR +
65535 = n/a
+
0xb024ZoneMatchingint32u0 = ISO Setting Used +
1 = High Key +
2 = Low Key
0xb025DynamicRangeOptimizerint32u + +
0 = Off +
1 = Standard +
2 = Advanced Auto +
3 = Auto +
8 = Advanced Lv1 +
9 = Advanced Lv2 +
10 = Advanced Lv3
  11 = Advanced Lv4 +
12 = Advanced Lv5 +
16 = Lv1 +
17 = Lv2 +
18 = Lv3 +
19 = Lv4 +
20 = Lv5
+
0xb026ImageStabilizationint32u0 = Off +
1 = On +
4294967295 = n/a
0xb027LensTypeint32u--> Sony LensType Values
0xb028MinoltaMakerNote---> Minolta Tags
0xb029ColorModeint32u + +
0 = Standard +
1 = Vivid +
2 = Portrait +
3 = Landscape +
4 = Sunset +
5 = Night View/Portrait +
6 = B&W +
7 = Adobe RGB +
12 = Neutral +
13 = Clear +
14 = Deep +
15 = Light +
16 = Autumn Leaves
  17 = Sepia +
18 = FL +
19 = Vivid 2 +
20 = IN +
21 = SH +
100 = Neutral +
101 = Clear +
102 = Deep +
103 = Light +
104 = Night View +
105 = Autumn Leaves +
255 = Off +
4294967295 = n/a
+
0xb02aLensSpecint8u[8](like LensInfo, but also specifies lens features: DT, E, ZA, G, SSM, SAM, +OSS, STF, Reflex, Macro and Fisheye)
0xb02bFullImageSizeint32u[2] 
0xb02cPreviewImageSizeint32u[2] 
0xb040Macroint16u0 = Off +
1 = On +
2 = Close Focus +
65535 = n/a
0xb041ExposureModeint16u + +
0 = Program AE +
1 = Portrait +
2 = Beach +
3 = Sports +
4 = Snow +
5 = Landscape +
6 = Auto +
7 = Aperture-priority AE +
8 = Shutter speed priority AE +
9 = Night Scene / Twilight +
10 = Hi-Speed Shutter +
11 = Twilight Portrait +
12 = Soft Snap/Portrait +
13 = Fireworks +
14 = Smile Shutter +
15 = Manual
  18 = High Sensitivity +
19 = Macro +
20 = Advanced Sports Shooting +
29 = Underwater +
33 = Food +
34 = Sweep Panorama +
35 = Handheld Night Shot +
36 = Anti Motion Blur +
37 = Pet +
38 = Backlight Correction HDR +
39 = Superior Auto +
40 = Background Defocus +
41 = Soft Skin +
42 = 3D Image +
65535 = n/a
+
0xb042FocusModeint16u(not valid for all models) +
1 = AF-S +
2 = AF-C +
4 = Permanent-AF +
65535 = n/a
0xb043AFAreaModeint16u(older models) + +
0 = Default +
1 = Multi +
2 = Center +
3 = Spot +
4 = Flexible Spot
  6 = Touch +
14 = Tracking +
15 = Face Tracking +
65535 = n/a
+(DSC-HX9V generation cameras) +
0 = Multi +
1 = Center +
2 = Spot +
3 = Flexible Spot +
10 = Selective (for Miniature effect) +
14 = Tracking +
15 = Face Tracking +
255 = Manual
+
0xb044AFIlluminatorint16u0 = Off +
1 = Auto +
65535 = n/a
0xb047JPEGQualityint16u0 = Standard +
1 = Fine +
2 = Extra Fine +
65535 = n/a
0xb048FlashLevelint16s + + +
-32768 = Low +
-9 = -9/3 +
-8 = -8/3 +
-7 = -7/3 +
-6 = -6/3 +
-5 = -5/3 +
-4 = -4/3
  -3 = -3/3 +
-2 = -2/3 +
-1 = -1/3 +
0 = Normal +
1 = +1/3 +
2 = +2/3 +
3 = +3/3
  4 = +4/3 +
5 = +5/3 +
6 = +6/3 +
9 = +9/3 +
128 = n/a +
32767 = High
+
0xb049ReleaseModeint16u +
0 = Normal +
2 = Continuous +
5 = Exposure Bracketing +
6 = White Balance Bracketing +
8 = DRO Bracketing +
65535 = n/a
+
0xb04aSequenceNumberint16u(shot number in continuous burst) +
0 = Single +
65535 = n/a
0xb04bAnti-Blurint16u0 = Off +
1 = On (Continuous) +
2 = On (Shooting) +
65535 = n/a
0xb04eFocusModeint16u(valid for DSC-HX9V generation and newer) +
0 = Manual +
2 = AF-S +
3 = AF-C +
5 = Semi-manual +
6 = DMF
0xb04fDynamicRangeOptimizerint16u0 = Off +
1 = Standard +
2 = Plus
0xb050HighISONoiseReduction2int16u(DSC models only) +
0 = Normal +
1 = High +
2 = Low +
3 = Off +
65535 = n/a
0xb052IntelligentAutoint16u0 = Off +
1 = On +
2 = Advanced
0xb054WhiteBalanceint16u(decoding of the Fluorescent settings matches the EXIF standard, which is +different than the names used by Sony for some models) +
0 = Auto +
4 = Custom +
5 = Daylight +
6 = Cloudy +
7 = Cool White Fluorescent +
8 = Day White Fluorescent +
9 = Daylight Fluorescent +
10 = Incandescent2 +
11 = Warm White Fluorescent +
14 = Incandescent +
15 = Flash +
17 = Underwater 1 (Blue Water) +
18 = Underwater 2 (Green Water) +
19 = Underwater Auto
+
+ +

Sony LensType Values

+

"New" or "II" appear in brackets if the original version of the lens has the +same LensType. Special logic is employed to identify the attached lens when +a Metabones Canon EF adapter is used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLensType
0= Minolta AF 28-85mm F3.5-4.5 New
1= Minolta AF 80-200mm F2.8 HS-APO G
2= Minolta AF 28-70mm F2.8 G
3= Minolta AF 28-80mm F4-5.6
4= Minolta AF 85mm F1.4G
5= Minolta AF 35-70mm F3.5-4.5 [II]
6= Minolta AF 24-85mm F3.5-4.5 [New]
7= Minolta AF 100-300mm F4.5-5.6 APO [New] or 100-400mm or Sigma Lens
7= Minolta AF 100-400mm F4.5-6.7 APO
7= Sigma AF 100-300mm F4 EX DG IF
8= Minolta AF 70-210mm F4.5-5.6 [II]
9= Minolta AF 50mm F3.5 Macro
10= Minolta AF 28-105mm F3.5-4.5 [New]
11= Minolta AF 300mm F4 HS-APO G
12= Minolta AF 100mm F2.8 Soft Focus
13= Minolta AF 75-300mm F4.5-5.6 (New or II)
14= Minolta AF 100-400mm F4.5-6.7 APO
15= Minolta AF 400mm F4.5 HS-APO G
16= Minolta AF 17-35mm F3.5 G
17= Minolta AF 20-35mm F3.5-4.5
18= Minolta AF 28-80mm F3.5-5.6 II
19= Minolta AF 35mm F1.4 G
20= Minolta/Sony 135mm F2.8 [T4.5] STF
22= Minolta AF 35-80mm F4-5.6 II
23= Minolta AF 200mm F4 Macro APO G
24= Minolta/Sony AF 24-105mm F3.5-4.5 (D) or Sigma or Tamron Lens
24= Sigma 18-50mm F2.8
24= Sigma 17-70mm F2.8-4.5 DC Macro
24= Sigma 20-40mm F2.8 EX DG Aspherical IF
24= Sigma 18-200mm F3.5-6.3 DC
24= Sigma DC 18-125mm F4-5,6 D
24= Tamron SP AF 28-75mm F2.8 XR Di LD Aspherical [IF] Macro
24= Sigma 15-30mm F3.5-4.5 EX DG Aspherical
25= Minolta AF 100-300mm F4.5-5.6 APO (D) or Sigma Lens
25= Sigma 100-300mm F4 EX (APO (D) or D IF)
25= Sigma 70mm F2.8 EX DG Macro
25= Sigma 20mm F1.8 EX DG Aspherical RF
25= Sigma 30mm F1.4 EX DC
25= Sigma 24mm F1.8 EX DG ASP Macro
27= Minolta AF 85mm F1.4 G (D)
28= Minolta/Sony AF 100mm F2.8 Macro (D) or Tamron Lens
28= Tamron SP AF 90mm F2.8 Di Macro
28= Tamron SP AF 180mm F3.5 Di LD [IF] Macro
29= Minolta/Sony AF 75-300mm F4.5-5.6 (D)
30= Minolta AF 28-80mm F3.5-5.6 (D) or Sigma Lens
30= Sigma AF 10-20mm F4-5.6 EX DC
30= Sigma AF 12-24mm F4.5-5.6 EX DG
30= Sigma 28-70mm EX DG F2.8
30= Sigma 55-200mm F4-5.6 DC
31= Minolta/Sony AF 50mm F2.8 Macro (D) or F3.5
31= Minolta/Sony AF 50mm F3.5 Macro
32= Minolta/Sony AF 300mm F2.8 G or 1.5x Teleconverter
33= Minolta/Sony AF 70-200mm F2.8 G
35= Minolta AF 85mm F1.4 G (D) Limited
36= Minolta AF 28-100mm F3.5-5.6 (D)
38= Minolta AF 17-35mm F2.8-4 (D)
39= Minolta AF 28-75mm F2.8 (D)
40= Minolta/Sony AF DT 18-70mm F3.5-5.6 (D)
41= Minolta/Sony AF DT 11-18mm F4.5-5.6 (D) or Tamron Lens
41= Tamron SP AF 11-18mm F4.5-5.6 Di II LD Aspherical IF
42= Minolta/Sony AF DT 18-200mm F3.5-6.3 (D)
43= Sony 35mm F1.4 G (SAL35F14G)
44= Sony 50mm F1.4 (SAL50F14)
45= Carl Zeiss Planar T* 85mm F1.4 ZA (SAL85F14Z)
46= Carl Zeiss Vario-Sonnar T* DT 16-80mm F3.5-4.5 ZA (SAL1680Z)
47= Carl Zeiss Sonnar T* 135mm F1.8 ZA (SAL135F18Z)
48= Carl Zeiss Vario-Sonnar T* 24-70mm F2.8 ZA SSM (SAL2470Z) or Other Lens
48= Carl Zeiss Vario-Sonnar T* 24-70mm F2.8 ZA SSM II (SAL2470Z2)
48= Tamron SP 24-70mm F2.8 Di USD
49= Sony DT 55-200mm F4-5.6 (SAL55200)
50= Sony DT 18-250mm F3.5-6.3 (SAL18250)
51= Sony DT 16-105mm F3.5-5.6 (SAL16105)
52= Sony 70-300mm F4.5-5.6 G SSM (SAL70300G) or G SSM II or Tamron Lens
52= Sony 70-300mm F4.5-5.6 G SSM II (SAL70300G2)
52= Tamron SP 70-300mm F4-5.6 Di USD
53= Sony 70-400mm F4-5.6 G SSM (SAL70400G)
54= Carl Zeiss Vario-Sonnar T* 16-35mm F2.8 ZA SSM (SAL1635Z) or ZA SSM II
54= Carl Zeiss Vario-Sonnar T* 16-35mm F2.8 ZA SSM II (SAL1635Z2)
55= Sony DT 18-55mm F3.5-5.6 SAM (SAL1855) or SAM II
55= Sony DT 18-55mm F3.5-5.6 SAM II (SAL18552)
56= Sony DT 55-200mm F4-5.6 SAM (SAL55200-2)
57= Sony DT 50mm F1.8 SAM (SAL50F18) or Tamron Lens or Commlite CM-EF-NEX adapter
57= Tamron SP AF 60mm F2 Di II LD [IF] Macro 1:1
57= Tamron 18-270mm F3.5-6.3 Di II PZD
58= Sony DT 30mm F2.8 Macro SAM (SAL30M28)
59= Sony 28-75mm F2.8 SAM (SAL2875)
60= Carl Zeiss Distagon T* 24mm F2 ZA SSM (SAL24F20Z)
61= Sony 85mm F2.8 SAM (SAL85F28)
62= Sony DT 35mm F1.8 SAM (SAL35F18)
63= Sony DT 16-50mm F2.8 SSM (SAL1650)
64= Sony 500mm F4 G SSM (SAL500F40G)
65= Sony DT 18-135mm F3.5-5.6 SAM (SAL18135)
66= Sony 300mm F2.8 G SSM II (SAL300F28G2)
67= Sony 70-200mm F2.8 G SSM II (SAL70200G2)
68= Sony DT 55-300mm F4.5-5.6 SAM (SAL55300)
69= Sony 70-400mm F4-5.6 G SSM II (SAL70400G2)
70= Carl Zeiss Planar T* 50mm F1.4 ZA SSM (SAL50F14Z)
128= Tamron or Sigma Lens (128)
128= Tamron AF 18-200mm F3.5-6.3 XR Di II LD Aspherical [IF] Macro
128= Tamron AF 28-300mm F3.5-6.3 XR Di LD Aspherical [IF] Macro
128= Tamron AF 28-200mm F3.8-5.6 XR Di Aspherical [IF] Macro
128= Tamron SP AF 17-35mm F2.8-4 Di LD Aspherical IF
128= Sigma AF 50-150mm F2.8 EX DC APO HSM II
128= Sigma 10-20mm F3.5 EX DC HSM
128= Sigma 70-200mm F2.8 II EX DG APO MACRO HSM
128= Sigma 10mm F2.8 EX DC HSM Fisheye
128= Sigma 50mm F1.4 EX DG HSM
128= Sigma 85mm F1.4 EX DG HSM
128= Sigma 24-70mm F2.8 IF EX DG HSM
128= Sigma 18-250mm F3.5-6.3 DC OS HSM
128= Sigma 17-50mm F2.8 EX DC HSM
128= Sigma 17-70mm F2.8-4 DC Macro HSM
128= Sigma 150mm F2.8 EX DG OS HSM APO Macro
128= Sigma 150-500mm F5-6.3 APO DG OS HSM
128= Tamron AF 28-105mm F4-5.6 [IF]
128= Sigma 35mm F1.4 DG HSM
128= Sigma 18-35mm F1.8 DC HSM
128= Sigma 50-500mm F4.5-6.3 APO DG OS HSM
128= Sigma 24-105mm F4 DG HSM | A
128= Sigma 30mm F1.4
128= Sigma 35mm F1.4 DG HSM | A
128= Sigma 105mm F2.8 EX DG OS HSM Macro
128= Sigma 180mm F2.8 EX DG OS HSM APO Macro
128= Sigma 18-300mm F3.5-6.3 DC Macro HSM | C
128= Sigma 18-50mm F2.8-4.5 DC HSM
129= Tamron Lens (129)
129= Tamron 200-400mm F5.6 LD
129= Tamron 70-300mm F4-5.6 LD
131= Tamron 20-40mm F2.7-3.5 SP Aspherical IF
135= Vivitar 28-210mm F3.5-5.6
136= Tokina EMZ M100 AF 100mm F3.5
137= Cosina 70-210mm F2.8-4 AF
138= Soligor 19-35mm F3.5-4.5
139= Tokina AF 28-300mm F4-6.3
142= Cosina AF 70-300mm F4.5-5.6 MC
146= Voigtlander Macro APO-Lanthar 125mm F2.5 SL
194= Tamron SP AF 17-50mm F2.8 XR Di II LD Aspherical [IF]
202= Tamron SP AF 70-200mm F2.8 Di LD [IF] Macro
203= Tamron SP 70-200mm F2.8 Di USD
204= Tamron SP 24-70mm F2.8 Di USD
212= Tamron 28-300mm F3.5-6.3 Di PZD
213= Tamron 16-300mm F3.5-6.3 Di II PZD Macro
214= Tamron SP 150-600mm F5-6.3 Di USD
215= Tamron SP 15-30mm F2.8 Di USD
216= Tamron SP 45mm F1.8 Di USD
217= Tamron SP 35mm F1.8 Di USD
218= Tamron SP 90mm F2.8 Di Macro 1:1 USD (F017)
220= Tamron SP 150-600mm F5-6.3 Di USD G2
224= Tamron SP 90mm F2.8 Di Macro 1:1 USD (F004)
255= Tamron Lens (255)
255= Tamron SP AF 17-50mm F2.8 XR Di II LD Aspherical
255= Tamron AF 18-250mm F3.5-6.3 XR Di II LD
255= Tamron AF 55-200mm F4-5.6 Di II LD Macro
255= Tamron AF 70-300mm F4-5.6 Di LD Macro 1:2
255= Tamron SP AF 200-500mm F5.0-6.3 Di LD IF
255= Tamron SP AF 10-24mm F3.5-4.5 Di II LD Aspherical IF
255= Tamron SP AF 70-200mm F2.8 Di LD IF Macro
255= Tamron SP AF 28-75mm F2.8 XR Di LD Aspherical IF
255= Tamron AF 90-300mm F4.5-5.6 Telemacro
1868= Sigma MC-11 SA-E Mount Converter with not-supported Sigma lens
2550= Minolta AF 50mm F1.7
2551= Minolta AF 35-70mm F4 or Other Lens
2551= Sigma UC AF 28-70mm F3.5-4.5
2551= Sigma AF 28-70mm F2.8
2551= Sigma M-AF 70-200mm F2.8 EX Aspherical
2551= Quantaray M-AF 35-80mm F4-5.6
2551= Tokina 28-70mm F2.8-4.5 AF
2552= Minolta AF 28-85mm F3.5-4.5 or Other Lens
2552= Tokina 19-35mm F3.5-4.5
2552= Tokina 28-70mm F2.8 AT-X
2552= Tokina 80-400mm F4.5-5.6 AT-X AF II 840
2552= Tokina AF PRO 28-80mm F2.8 AT-X 280
2552= Tokina AT-X PRO [II] AF 28-70mm F2.6-2.8 270
2552= Tamron AF 19-35mm F3.5-4.5
2552= Angenieux AF 28-70mm F2.6
2552= Tokina AT-X 17 AF 17mm F3.5
2552= Tokina 20-35mm F3.5-4.5 II AF
2553= Minolta AF 28-135mm F4-4.5 or Other Lens
2553= Sigma ZOOM-alpha 35-135mm F3.5-4.5
2553= Sigma 28-105mm F2.8-4 Aspherical
2553= Sigma 28-105mm F4-5.6 UC
2553= Tokina AT-X 242 AF 24-200mm F3.5-5.6
2554= Minolta AF 35-105mm F3.5-4.5
2555= Minolta AF 70-210mm F4 Macro or Sigma Lens
2555= Sigma 70-210mm F4-5.6 APO
2555= Sigma M-AF 70-200mm F2.8 EX APO
2555= Sigma 75-200mm F2.8-3.5
2556= Minolta AF 135mm F2.8
2557= Minolta/Sony AF 28mm F2.8
2558= Minolta AF 24-50mm F4
2560= Minolta AF 100-200mm F4.5
2561= Minolta AF 75-300mm F4.5-5.6 or Sigma Lens
2561= Sigma 70-300mm F4-5.6 DL Macro
2561= Sigma 300mm F4 APO Macro
2561= Sigma AF 500mm F4.5 APO
2561= Sigma AF 170-500mm F5-6.3 APO Aspherical
2561= Tokina AT-X AF 300mm F4
2561= Tokina AT-X AF 400mm F5.6 SD
2561= Tokina AF 730 II 75-300mm F4.5-5.6
2561= Sigma 800mm F5.6 APO
2561= Sigma AF 400mm F5.6 APO Macro
2561= Sigma 1000mm F8 APO
2562= Minolta AF 50mm F1.4 [New]
2563= Minolta AF 300mm F2.8 APO or Sigma Lens
2563= Sigma AF 50-500mm F4-6.3 EX DG APO
2563= Sigma AF 170-500mm F5-6.3 APO Aspherical
2563= Sigma AF 500mm F4.5 EX DG APO
2563= Sigma 400mm F5.6 APO
2564= Minolta AF 50mm F2.8 Macro or Sigma Lens
2564= Sigma 50mm F2.8 EX Macro
2565= Minolta AF 600mm F4 APO
2566= Minolta AF 24mm F2.8 or Sigma Lens
2566= Sigma 17-35mm F2.8-4 EX Aspherical
2572= Minolta/Sony AF 500mm F8 Reflex
2578= Minolta/Sony AF 16mm F2.8 Fisheye or Sigma Lens
2578= Sigma 8mm F4 EX [DG] Fisheye
2578= Sigma 14mm F3.5
2578= Sigma 15mm F2.8 Fisheye
2579= Minolta/Sony AF 20mm F2.8 or Tokina Lens
2579= Tokina AT-X Pro DX 11-16mm F2.8
2581= Minolta AF 100mm F2.8 Macro [New] or Sigma or Tamron Lens
2581= Sigma AF 90mm F2.8 Macro
2581= Sigma AF 105mm F2.8 EX [DG] Macro
2581= Sigma 180mm F5.6 Macro
2581= Sigma 180mm F3.5 EX DG Macro
2581= Tamron 90mm F2.8 Macro
2585= Minolta AF 35-105mm F3.5-4.5 New or Tamron Lens
2585= Beroflex 35-135mm F3.5-4.5
2585= Tamron 24-135mm F3.5-5.6
2588= Minolta AF 70-210mm F3.5-4.5
2589= Minolta AF 80-200mm F2.8 APO or Tokina Lens
2589= Tokina 80-200mm F2.8
2590= Minolta AF 200mm F2.8 G APO + Minolta AF 1.4x APO or Other Lens + 1.4x
2590= Minolta AF 600mm F4 HS-APO G + Minolta AF 1.4x APO
2591= Minolta AF 35mm F1.4
2592= Minolta AF 85mm F1.4 G (D)
2593= Minolta AF 200mm F2.8 APO
2594= Minolta AF 3x-1x F1.7-2.8 Macro
2596= Minolta AF 28mm F2
2597= Minolta AF 35mm F2 [New]
2598= Minolta AF 100mm F2
2601= Minolta AF 200mm F2.8 G APO + Minolta AF 2x APO or Other Lens + 2x
2601= Minolta AF 600mm F4 HS-APO G + Minolta AF 2x APO
2604= Minolta AF 80-200mm F4.5-5.6
2605= Minolta AF 35-80mm F4-5.6
2606= Minolta AF 100-300mm F4.5-5.6
2607= Minolta AF 35-80mm F4-5.6
2608= Minolta AF 300mm F2.8 HS-APO G
2609= Minolta AF 600mm F4 HS-APO G
2612= Minolta AF 200mm F2.8 HS-APO G
2613= Minolta AF 50mm F1.7 New
2615= Minolta AF 28-105mm F3.5-4.5 xi
2616= Minolta AF 35-200mm F4.5-5.6 xi
2618= Minolta AF 28-80mm F4-5.6 xi
2619= Minolta AF 80-200mm F4.5-5.6 xi
2620= Minolta AF 28-70mm F2.8 G
2621= Minolta AF 100-300mm F4.5-5.6 xi
2624= Minolta AF 35-80mm F4-5.6 Power Zoom
2628= Minolta AF 80-200mm F2.8 HS-APO G
2629= Minolta AF 85mm F1.4 New
2631= Minolta AF 100-300mm F4.5-5.6 APO
2632= Minolta AF 24-50mm F4 New
2638= Minolta AF 50mm F2.8 Macro New
2639= Minolta AF 100mm F2.8 Macro
2641= Minolta/Sony AF 20mm F2.8 New
2642= Minolta AF 24mm F2.8 New
2644= Minolta AF 100-400mm F4.5-6.7 APO
2662= Minolta AF 50mm F1.4 New
2667= Minolta AF 35mm F2 New
2668= Minolta AF 28mm F2 New
2672= Minolta AF 24-105mm F3.5-4.5 (D)
3046= Metabones Canon EF Speed Booster
4567= Tokina 70-210mm F4-5.6
4568= Tokina AF 35-200mm F4-5.6 Zoom SD
4570= Tamron AF 35-135mm F3.5-4.5
4571= Vivitar 70-210mm F4.5-5.6
4574= 2x Teleconverter or Tamron or Tokina Lens
4574= Tamron SP AF 90mm F2.5
4574= Tokina RF 500mm F8.0 x2
4574= Tokina 300mm F2.8 x2
4575= 1.4x Teleconverter
4585= Tamron SP AF 300mm F2.8 LD IF
4586= Tamron SP AF 35-105mm F2.8 LD Aspherical IF
4587= Tamron AF 70-210mm F2.8 SP LD
4812= Metabones Canon EF Speed Booster Ultra
6118= Canon EF Adapter
6528= Sigma 16mm F2.8 Filtermatic Fisheye
6553= E-Mount, T-Mount, Other Lens or no lens
6553= Arax MC 35mm F2.8 Tilt+Shift
6553= Arax MC 80mm F2.8 Tilt+Shift
6553= Zenitar MF 16mm F2.8 Fisheye M42
6553= Samyang 500mm Mirror F8.0
6553= Pentacon Auto 135mm F2.8
6553= Pentacon Auto 29mm F2.8
6553= Helios 44-2 58mm F2.0
18688= Sigma MC-11 SA-E Mount Converter with not-supported Sigma lens
25501= Minolta AF 50mm F1.7
25511= Minolta AF 35-70mm F4 or Other Lens
25511= Sigma UC AF 28-70mm F3.5-4.5
25511= Sigma AF 28-70mm F2.8
25511= Sigma M-AF 70-200mm F2.8 EX Aspherical
25511= Quantaray M-AF 35-80mm F4-5.6
25511= Tokina 28-70mm F2.8-4.5 AF
25521= Minolta AF 28-85mm F3.5-4.5 or Other Lens
25521= Tokina 19-35mm F3.5-4.5
25521= Tokina 28-70mm F2.8 AT-X
25521= Tokina 80-400mm F4.5-5.6 AT-X AF II 840
25521= Tokina AF PRO 28-80mm F2.8 AT-X 280
25521= Tokina AT-X PRO [II] AF 28-70mm F2.6-2.8 270
25521= Tamron AF 19-35mm F3.5-4.5
25521= Angenieux AF 28-70mm F2.6
25521= Tokina AT-X 17 AF 17mm F3.5
25521= Tokina 20-35mm F3.5-4.5 II AF
25531= Minolta AF 28-135mm F4-4.5 or Other Lens
25531= Sigma ZOOM-alpha 35-135mm F3.5-4.5
25531= Sigma 28-105mm F2.8-4 Aspherical
25531= Sigma 28-105mm F4-5.6 UC
25531= Tokina AT-X 242 AF 24-200mm F3.5-5.6
25541= Minolta AF 35-105mm F3.5-4.5
25551= Minolta AF 70-210mm F4 Macro or Sigma Lens
25551= Sigma 70-210mm F4-5.6 APO
25551= Sigma M-AF 70-200mm F2.8 EX APO
25551= Sigma 75-200mm F2.8-3.5
25561= Minolta AF 135mm F2.8
25571= Minolta/Sony AF 28mm F2.8
25581= Minolta AF 24-50mm F4
25601= Minolta AF 100-200mm F4.5
25611= Minolta AF 75-300mm F4.5-5.6 or Sigma Lens
25611= Sigma 70-300mm F4-5.6 DL Macro
25611= Sigma 300mm F4 APO Macro
25611= Sigma AF 500mm F4.5 APO
25611= Sigma AF 170-500mm F5-6.3 APO Aspherical
25611= Tokina AT-X AF 300mm F4
25611= Tokina AT-X AF 400mm F5.6 SD
25611= Tokina AF 730 II 75-300mm F4.5-5.6
25611= Sigma 800mm F5.6 APO
25611= Sigma AF 400mm F5.6 APO Macro
25611= Sigma 1000mm F8 APO
25621= Minolta AF 50mm F1.4 [New]
25631= Minolta AF 300mm F2.8 APO or Sigma Lens
25631= Sigma AF 50-500mm F4-6.3 EX DG APO
25631= Sigma AF 170-500mm F5-6.3 APO Aspherical
25631= Sigma AF 500mm F4.5 EX DG APO
25631= Sigma 400mm F5.6 APO
25641= Minolta AF 50mm F2.8 Macro or Sigma Lens
25641= Sigma 50mm F2.8 EX Macro
25651= Minolta AF 600mm F4 APO
25661= Minolta AF 24mm F2.8 or Sigma Lens
25661= Sigma 17-35mm F2.8-4 EX Aspherical
25721= Minolta/Sony AF 500mm F8 Reflex
25781= Minolta/Sony AF 16mm F2.8 Fisheye or Sigma Lens
25781= Sigma 8mm F4 EX [DG] Fisheye
25781= Sigma 14mm F3.5
25781= Sigma 15mm F2.8 Fisheye
25791= Minolta/Sony AF 20mm F2.8 or Tokina Lens
25791= Tokina AT-X Pro DX 11-16mm F2.8
25811= Minolta AF 100mm F2.8 Macro [New] or Sigma or Tamron Lens
25811= Sigma AF 90mm F2.8 Macro
25811= Sigma AF 105mm F2.8 EX [DG] Macro
25811= Sigma 180mm F5.6 Macro
25811= Sigma 180mm F3.5 EX DG Macro
25811= Tamron 90mm F2.8 Macro
25851= Beroflex 35-135mm F3.5-4.5
25858= Minolta AF 35-105mm F3.5-4.5 New or Tamron Lens
25858= Tamron 24-135mm F3.5-5.6
25881= Minolta AF 70-210mm F3.5-4.5
25891= Minolta AF 80-200mm F2.8 APO or Tokina Lens
25891= Tokina 80-200mm F2.8
25901= Minolta AF 200mm F2.8 G APO + Minolta AF 1.4x APO or Other Lens + 1.4x
25901= Minolta AF 600mm F4 HS-APO G + Minolta AF 1.4x APO
25911= Minolta AF 35mm F1.4
25921= Minolta AF 85mm F1.4 G (D)
25931= Minolta AF 200mm F2.8 APO
25941= Minolta AF 3x-1x F1.7-2.8 Macro
25961= Minolta AF 28mm F2
25971= Minolta AF 35mm F2 [New]
25981= Minolta AF 100mm F2
26011= Minolta AF 200mm F2.8 G APO + Minolta AF 2x APO or Other Lens + 2x
26011= Minolta AF 600mm F4 HS-APO G + Minolta AF 2x APO
26041= Minolta AF 80-200mm F4.5-5.6
26051= Minolta AF 35-80mm F4-5.6
26061= Minolta AF 100-300mm F4.5-5.6
26071= Minolta AF 35-80mm F4-5.6
26081= Minolta AF 300mm F2.8 HS-APO G
26091= Minolta AF 600mm F4 HS-APO G
26121= Minolta AF 200mm F2.8 HS-APO G
26131= Minolta AF 50mm F1.7 New
26151= Minolta AF 28-105mm F3.5-4.5 xi
26161= Minolta AF 35-200mm F4.5-5.6 xi
26181= Minolta AF 28-80mm F4-5.6 xi
26191= Minolta AF 80-200mm F4.5-5.6 xi
26201= Minolta AF 28-70mm F2.8 G
26211= Minolta AF 100-300mm F4.5-5.6 xi
26241= Minolta AF 35-80mm F4-5.6 Power Zoom
26281= Minolta AF 80-200mm F2.8 HS-APO G
26291= Minolta AF 85mm F1.4 New
26311= Minolta AF 100-300mm F4.5-5.6 APO
26321= Minolta AF 24-50mm F4 New
26381= Minolta AF 50mm F2.8 Macro New
26391= Minolta AF 100mm F2.8 Macro
26411= Minolta/Sony AF 20mm F2.8 New
26421= Minolta AF 24mm F2.8 New
26441= Minolta AF 100-400mm F4.5-6.7 APO
26621= Minolta AF 50mm F1.4 New
26671= Minolta AF 35mm F2 New
26681= Minolta AF 28mm F2 New
26721= Minolta AF 24-105mm F3.5-4.5 (D)
30464= Metabones Canon EF Speed Booster
45671= Tokina 70-210mm F4-5.6
45681= Tokina AF 35-200mm F4-5.6 Zoom SD
45701= Tamron AF 35-135mm F3.5-4.5
45711= Vivitar 70-210mm F4.5-5.6
45741= 2x Teleconverter or Tamron or Tokina Lens
45741= Tamron SP AF 90mm F2.5
45741= Tokina RF 500mm F8.0 x2
45741= Tokina 300mm F2.8 x2
45751= 1.4x Teleconverter
45851= Tamron SP AF 300mm F2.8 LD IF
45861= Tamron SP AF 35-105mm F2.8 LD Aspherical IF
45871= Tamron AF 70-210mm F2.8 SP LD
48128= Metabones Canon EF Speed Booster Ultra
61184= Canon EF Adapter
65280= Sigma 16mm F2.8 Filtermatic Fisheye
65535= E-Mount, T-Mount, Other Lens or no lens
65535= Arax MC 35mm F2.8 Tilt+Shift
65535= Arax MC 80mm F2.8 Tilt+Shift
65535= Zenitar MF 16mm F2.8 Fisheye M42
65535= Samyang 500mm Mirror F8.0
65535= Pentacon Auto 135mm F2.8
65535= Pentacon Auto 29mm F2.8
65535= Helios 44-2 58mm F2.0
+ +

Sony CameraInfo Tags

+

Camera information for the A700, A850 and A900.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensSpecundef[8] 
20FocusModeSettingint8u(FocusModeSetting for the A700, A850 and A900) +
0 = Manual +
1 = AF-S +
2 = AF-C +
3 = AF-A +
4 = DMF
21AFPointSelectedint8u + +
0 = Auto +
1 = Center +
2 = Top +
3 = Upper-right +
4 = Right +
5 = Lower-right
  6 = Bottom +
7 = Lower-left +
8 = Left +
9 = Upper-left +
10 = Far Right +
11 = Far Left
+
25AFPointint8u + +
0 = Upper-left +
1 = Left +
2 = Lower-left +
3 = Far Left +
4 = Bottom Assist-left +
5 = Bottom +
6 = Bottom Assist-right +
7 = Center (7) +
8 = Center (horizontal) +
9 = Center (9) +
10 = Center (10) +
11 = Center (11)
  12 = Center (12) +
13 = Center (vertical) +
14 = Center (14) +
15 = Top Assist-left +
16 = Top +
17 = Top Assist-right +
18 = Far Right +
19 = Upper-right +
20 = Right +
21 = Lower-right +
22 = Center F2.8
+
30AFStatusActiveSensorint16s + +
-32768 = Out of Focus  0 = In Focus
+
32AFStatusUpper-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
34AFStatusLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
36AFStatusLower-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
38AFStatusFarLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
40AFStatusBottomAssist-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
42AFStatusBottomint16s + +
-32768 = Out of Focus  0 = In Focus
+
44AFStatusBottomAssist-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
46AFStatusCenter-7int16s + +
-32768 = Out of Focus  0 = In Focus
+
48AFStatusCenter-horizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
50AFStatusCenter-9int16s + +
-32768 = Out of Focus  0 = In Focus
+
52AFStatusCenter-10int16s + +
-32768 = Out of Focus  0 = In Focus
+
54AFStatusCenter-11int16s + +
-32768 = Out of Focus  0 = In Focus
+
56AFStatusCenter-12int16s + +
-32768 = Out of Focus  0 = In Focus
+
58AFStatusCenter-verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
60AFStatusCenter-14int16s + +
-32768 = Out of Focus  0 = In Focus
+
62AFStatusTopAssist-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
64AFStatusTopint16s + +
-32768 = Out of Focus  0 = In Focus
+
66AFStatusTopAssist-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
68AFStatusFarRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
70AFStatusUpper-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
72AFStatusRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
74AFStatusLower-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
76AFStatusCenterF2-8int16s + +
-32768 = Out of Focus  0 = In Focus
+
304AFMicroAdjValueint8u 
305AFMicroAdjModeint8u[val >> 7 & 0x1] +
0 = Off +
1 = On
305.1AFMicroAdjRegisteredLensesint8u(number of registered lenses with a non-zero AFMicroAdjValue) +
[val & 0x7f]
+ +

Sony CameraInfo2 Tags

+

Camera information for the DSLR-A200, A230, A290, A300, A330, A350, A380 and +A390.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensSpecundef[8] 
20AFPointSelectedint8u + +
0 = Auto +
1 = Center +
2 = Top +
3 = Upper-right +
4 = Right
  5 = Lower-right +
6 = Bottom +
7 = Lower-left +
8 = Left +
9 = Upper-left
+
21FocusModeSettingint8u(FocusModeSetting for other models) +
0 = Manual +
1 = AF-S +
2 = AF-C +
3 = AF-A +
4 = DMF
24AFPointint8u + +
0 = Top-right +
1 = Bottom-right +
2 = Bottom +
3 = Middle Horizontal
  4 = Center Vertical +
5 = Top +
6 = Top-left +
7 = Bottom-left
+
27AFStatusActiveSensorint16s + +
-32768 = Out of Focus  0 = In Focus
+
29AFStatusTop-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
31AFStatusBottom-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
33AFStatusBottomint16s + +
-32768 = Out of Focus  0 = In Focus
+
35AFStatusMiddleHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
37AFStatusCenterVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
39AFStatusTopint16s + +
-32768 = Out of Focus  0 = In Focus
+
41AFStatusTop-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
43AFStatusBottom-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
45AFStatusLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
47AFStatusCenterHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
49AFStatusRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
+ +

Sony CameraInfo3 Tags

+

Camera information stored by the A33, A35, A55, A450, A500, A550, A560, +A580, NEX-3/5/5C/C3 and VG10E. Some tags are valid only for some of these +models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0LensSpecundef[8] 
14FocalLengthint16u 
16FocalLengthTeleZoomint16u 
20AFPointSelectedint8u + +
0 = Auto +
1 = Center +
2 = Top +
3 = Upper-right +
4 = Right
  5 = Lower-right +
6 = Bottom +
7 = Lower-left +
8 = Left +
9 = Upper-left
+
21FocusModeint8u0 = Manual +
1 = AF-S +
2 = AF-C +
3 = AF-A
24AFPointint8u + +
0 = Top-right +
1 = Bottom-right +
2 = Bottom +
3 = Middle Horizontal
  4 = Center Vertical +
5 = Top +
6 = Top-left +
7 = Bottom-left
+
25FocusStatusint8u(not valid with Contrast AF or for NEX models) +
0 = Manual - Not confirmed (0) +
4 = Manual - Not confirmed (4) +
16 = AF-C - Confirmed +
24 = AF-C - Not Confirmed +
64 = AF-S - Confirmed
27AFStatusActiveSensorint16s + +
-32768 = Out of Focus  0 = In Focus
+
28AFPointSelectedint8u(not valid for Contrast AF) + +
0 = Auto +
1 = Center +
2 = Top +
3 = Upper-right +
4 = Right +
5 = Lower-right +
6 = Bottom +
7 = Lower-left
  8 = Left +
9 = Upper-left +
10 = Far Right +
11 = Far Left +
12 = Upper-middle +
13 = Near Right +
14 = Lower-middle +
15 = Near Left
+
29FocusMode +
AFStatusTop-right
int8u
int16s
0 = Manual +
1 = AF-S +
2 = AF-C +
3 = AF-A
+ +
-32768 = Out of Focus  0 = In Focus
+
31AFStatusBottom-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
32AFPointint8u(the AF sensor used for focusing. Not valid for Contrast AF) + +
0 = Upper-left +
1 = Left +
2 = Lower-left +
3 = Far Left +
4 = Top (horizontal) +
5 = Near Right +
6 = Center (horizontal) +
7 = Near Left +
8 = Bottom (horizontal) +
9 = Top (vertical)
  10 = Center (vertical) +
11 = Bottom (vertical) +
12 = Far Right +
13 = Upper-right +
14 = Right +
15 = Lower-right +
16 = Upper-middle +
17 = Lower-middle +
255 = (none)
+
33AFStatusActiveSensor +
AFStatusBottom
int16s
int16s
+ +
-32768 = Out of Focus  0 = In Focus
+ + +
-32768 = Out of Focus  0 = In Focus
+
35AFStatus15 +
AFStatusMiddleHorizontal
-
int16s
--> Sony AFStatus15 Tags +
+ +
-32768 = Out of Focus  0 = In Focus
+
37AFStatusCenterVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
39AFStatusTopint16s + +
-32768 = Out of Focus  0 = In Focus
+
41AFStatusTop-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
43AFStatusBottom-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
45AFStatusLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
47AFStatusCenterHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
49AFStatusRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
+ +

Sony AFStatus15 Tags

+

AF Status information for models with 15-point AF.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AFStatusUpper-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
2AFStatusLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
4AFStatusLower-leftint16s + +
-32768 = Out of Focus  0 = In Focus
+
6AFStatusFarLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
8AFStatusTopHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
10AFStatusNearRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
12AFStatusCenterHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
14AFStatusNearLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
16AFStatusBottomHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
18AFStatusTopVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
20AFStatusCenterVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
22AFStatusBottomVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
24AFStatusFarRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
26AFStatusUpper-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
28AFStatusRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
30AFStatusLower-rightint16s + +
-32768 = Out of Focus  0 = In Focus
+
32AFStatusUpper-middleint16s + +
-32768 = Out of Focus  0 = In Focus
+
34AFStatusLower-middleint16s + +
-32768 = Out of Focus  0 = In Focus
+
+ +

Sony CameraInfoUnknown Tags

+
+
+ + + + +
Index1Tag NameWritableValues / Notes
[no tags known]
+ +

Sony FocusInfo Tags

+

More camera settings and focus information decoded for models such as the +A200, A230, A290, A300, A330, A350, A380, A390, A700, A850 and A900.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
14DriveMode2int8u(A230, A290, A330, A380 and A390) +
0x1 = Single Frame +
0x2 = Continuous High +
0x4 = Self-timer 10 sec +
0x5 = Self-timer 2 sec, Mirror Lock-up +
0x7 = Continuous Bracketing +
0xa = Remote Commander +
0xb = Continuous Self-timer
+(A200, A300, A350, A700, A850 and A900) +
0x1 = Single Frame +
0x2 = Continuous High +
0x4 = Self-timer 10 sec +
0x5 = Self-timer 2 sec, Mirror Lock-up +
0x6 = Single-frame Bracketing +
0x7 = Continuous Bracketing +
0xa = Remote Commander +
0xb = Mirror Lock-up +
0x12 = Continuous Low +
0x18 = White Balance Bracketing Low +
0x19 = D-Range Optimizer Bracketing Low +
0x28 = White Balance Bracketing High +
0x29 = D-Range Optimizer Bracketing High
+
16Rotationint8u0 = Horizontal (normal) +
1 = Rotate 270 CW +
2 = Rotate 90 CW
20ImageStabilizationSettingint8u0 = Off +
1 = On
21DynamicRangeOptimizerModeint8u0 = Off +
1 = Standard +
2 = Advanced Auto +
3 = Advanced Level
43BracketShotNumberint8u(WB and DRO bracketing)
44WhiteBalanceBracketingint8u0 = Off +
1 = Low +
2 = High
45BracketShotNumber2int8u 
46DynamicRangeOptimizerBracketint8u0 = Off +
1 = Low +
2 = High
47ExposureBracketShotNumberint8u 
63ExposureProgramint8u--> Sony ExposureProgram Values
65CreativeStyleint8u + +
1 = Standard +
2 = Vivid +
3 = Portrait +
4 = Landscape +
5 = Sunset +
6 = Night View/Portrait +
8 = B&W
  9 = Adobe RGB +
11 = Neutral +
12 = Clear +
13 = Deep +
14 = Light +
15 = Autumn Leaves +
16 = Sepia
+
109ISOSettingint8u 
111ISOint8u 
119DynamicRangeOptimizerModeint8u0 = Off +
1 = Standard +
2 = Advanced Auto +
3 = Advanced Level
121DynamicRangeOptimizerLevelint8u 
2118ShutterCountint32u(only valid for some DSLR models)
2491FocusPositionint8u(only valid for some DSLR models)
4368TiffMeteringImageno(13-bit RBGG (?) 40x30 pixels, presumably metering info, extracted as a +16-bit TIFF image;)
+ +

Sony ExposureProgram Values

+
+
+ + + + + + + + + + + + + + + + + +
ValueExposureProgramValueExposureProgramValueExposureProgram
0= Auto8= Program Shift A19= Night Portrait
1= Manual9= Program Shift S20= Landscape
2= Program AE16= Portrait21= Macro
3= Aperture-priority AE17= Sports35= Auto No Flash
4= Shutter speed priority AE18= Sunset  
+ +

Sony MoreInfo Tags

+

More camera settings information decoded for the A450, A500, A550, A560, +A580, A33, A35, A55, NEX-3/5/C3 and VG10E.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001MoreSettings---> Sony MoreSettings Tags
0x0002FaceInfo +
FaceInfoA
-
-
--> Sony FaceInfo Tags +
--> Sony FaceInfoA Tags
0x0107TiffMeteringImageno(10-bit RGB data from the 1200 AE metering segments, extracted as a 16-bit +TIFF image)
0x0201MoreInfo0201---> Sony MoreInfo0201 Tags
0x0401MoreInfo0401---> Sony MoreInfo0401 Tags
+ +

Sony MoreSettings Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1DriveMode2int8u +
0x10 = Single Frame +
0x21 = Continuous High +
0x22 = Continuous Low +
0x30 = Speed Priority Continuous +
0x51 = Self-timer 10 sec +
0x52 = Self-timer 2 sec, Mirror Lock-up +
0x71 = Continuous Bracketing 0.3 EV +
0x75 = Continuous Bracketing 0.7 EV +
0x91 = White Balance Bracketing Low +
0x92 = White Balance Bracketing High +
0xc0 = Remote Commander
+
2ExposureProgramint8u--> Sony ExposureProgram2 Values
3MeteringModeint8u1 = Multi-segment +
2 = Center-weighted average +
3 = Spot
4DynamicRangeOptimizerSettingint8u1 = Off +
16 = On (Auto) +
17 = On (Manual)
5DynamicRangeOptimizerLevelint8u 
6ColorSpaceint8u1 = sRGB +
2 = Adobe RGB
7CreativeStyleSettingint8u + +
16 = Standard +
32 = Vivid +
64 = Portrait
  80 = Landscape +
96 = B&W +
160 = Sunset
+
8ContrastSettingint8s 
9SaturationSettingint8s 
10SharpnessSettingint8s 
13WhiteBalanceSettingint8u--> Sony WhiteBalanceSetting Values
14ColorTemperatureSettingint8u 
15ColorCompensationFilterSetint8s(negative is green, positive is magenta)
16FlashModeint8u + +
1 = Flash Off +
16 = Autoflash +
17 = Fill-flash
  18 = Slow Sync +
19 = Rear Sync +
20 = Wireless
+
17LongExposureNoiseReductionint8u1 = Off +
16 = On
18HighISONoiseReductionint8u16 = Low +
17 = High +
19 = Auto
19FocusModeint8u17 = AF-S +
18 = AF-C +
19 = AF-A +
32 = Manual +
48 = DMF
21MultiFrameNoiseReductionint8u0 = n/a +
1 = Off +
16 = On +
255 = None
22HDRSettingint8u1 = Off +
16 = On (Auto) +
17 = On (Manual)
23HDRLevelint8u + + +
33 = 1 EV +
34 = 1.5 EV +
35 = 2 EV
  36 = 2.5 EV +
37 = 3 EV +
38 = 3.5 EV
  39 = 4 EV +
40 = 5 EV +
41 = 6 EV
+
24ViewingModeint8u16 = ViewFinder +
33 = Focus Check Live View +
34 = Quick AF Live View
25FaceDetectionint8u1 = Off +
16 = On
26CustomWB_RBLevelsint16uRev[2] 
30BrightnessValue +
ExposureCompensationSet
int8u
int8u
(A450, A500 and A550) +
(other models)
31ISO +
FlashExposureCompSet
int8u
int8u
(A450, A500 and A550) +
(other models)
32FNumber +
LiveViewAFMethod
int8u
int8u
(A450, A500 and A550) +
(other models except the NEX-3/5/5C) +
0 = n/a +
1 = Phase-detect AF +
2 = Contrast AF
33ExposureTime +
ISO
int8u
int8u
(A450, A500 and A550) +
(NEX-3/5/5C)
34FNumberint8u(NEX-3/5/5C only)
35FocalLength2 +
ExposureTime
int8u
int8u
(A450, A500 and A550) +
(NEX-3/5/5C)
36ExposureCompensation2int16s(A450, A500 and A550)
37FocalLength2 +
ISO
int8u
int8u
(NEX-3/5/5C) +
(other models except the A450, A500 and A550)
38FlashExposureCompSet2 +
ExposureCompensation2 +
FNumber
int16s
int16s
int8u
(A450, A500 and A550) +
(NEX-3/5/5C) +
(other models)
39ExposureTimeint8u(models other than the A450, A500, A550 and NEX-3/5/5C)
40Orientation2int8u(A450, A500 and A550) +
1 = Horizontal (normal) +
2 = Rotate 180 +
6 = Rotate 90 CW +
8 = Rotate 270 CW
41FocusPosition2 +
FocalLength2
int8u
int8u
(A450, A500 and A550) +
(other models except the NEX-3/5/5C)
42FlashAction +
ExposureCompensation2
int8u
int16s
(A450, A500 and A550) +
0 = Did not fire +
1 = Fired +
(other models except the NEX-3/5/5C)
43FocusPosition2int8u(NEX-3/5/5C only)
44FocusMode2 +
FlashAction +
FlashExposureCompSet2
int8u
int8u
int16s
(A450, A500 and A550) +
0 = AF +
1 = MF +
(NEX-3/5/5C FlashAction2) +
0 = Did not fire +
1 = Fired +
(other models FlashExposureCompSet2)
46FocusMode2 +
Orientation2
int8u
int8u
(NEX-3/5/5C) +
0 = AF +
1 = MF +
(other models except the A450, A500 and A550) +
1 = Horizontal (normal) +
2 = Rotate 180 +
6 = Rotate 90 CW +
8 = Rotate 270 CW
47FocusPosition2int8u(models other than the A450, A500, A550 and NEX-3/5/5C)
48FlashActionint8u(models other than the A450, A500, A550 and NEX-3/5/5C) +
0 = Did not fire +
1 = Fired
50FocusMode2int8u(models other than the A450, A500, A550 and NEX-3/5/5C) +
0 = AF +
1 = MF
119FlashAction2int8u0 = Did not fire +
2 = External Flash fired (2) +
3 = Built-in Flash fired +
4 = External Flash fired (4)
120FlashActionExternalint8u121 = Fired +
122 = Fired +
136 = Did not fire
124FlashActionExternalint8u136 = Did not fire +
167 = Fired +
182 = Fired, HSS
130FlashStatusint8u0 = None +
2 = External
134FlashStatusint8u0 = None +
1 = Built-in +
2 = External
+ +

Sony ExposureProgram2 Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueExposureProgram2ValueExposureProgram2ValueExposureProgram2
1= Program AE52= Sports129= Pop Color
2= Aperture-priority AE53= Sunset130= Posterization
3= Shutter speed priority AE54= Night view131= Posterization B/W
4= Manual55= Night view/portrait132= Retro Photo
5= Cont. Priority AE56= Handheld Night Shot133= High-key
16= Auto57= 3D Sweep Panorama134= Partial Color Red
17= Auto (no flash)64= Auto 2135= Partial Color Green
18= Auto+65= Auto 2 (no flash)136= Partial Color Blue
49= Portrait80= Sweep Panorama137= Partial Color Yellow
50= Landscape96= Anti Motion Blur138= High Contrast Monochrome
51= Macro128= Toy Camera  
+ +

Sony WhiteBalanceSetting Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueWhiteBalanceSettingValueWhiteBalanceSetting
0x10= Auto (-3)0x45= Cloudy (+2)
0x11= Auto (-2)0x46= Cloudy (+3)
0x12= Auto (-1)0x50= Tungsten (-3)
0x13= Auto (0)0x51= Tungsten (-2)
0x14= Auto (+1)0x52= Tungsten (-1)
0x15= Auto (+2)0x53= Tungsten (0)
0x16= Auto (+3)0x54= Tungsten (+1)
0x20= Daylight (-3)0x55= Tungsten (+2)
0x21= Daylight (-2)0x56= Tungsten (+3)
0x22= Daylight (-1)0x60= Fluorescent (-3)
0x23= Daylight (0)0x61= Fluorescent (-2)
0x24= Daylight (+1)0x62= Fluorescent (-1)
0x25= Daylight (+2)0x63= Fluorescent (0)
0x26= Daylight (+3)0x64= Fluorescent (+1)
0x30= Shade (-3)0x65= Fluorescent (+2)
0x31= Shade (-2)0x66= Fluorescent (+3)
0x32= Shade (-1)0x70= Flash (-3)
0x33= Shade (0)0x71= Flash (-2)
0x34= Shade (+1)0x72= Flash (-1)
0x35= Shade (+2)0x73= Flash (0)
0x36= Shade (+3)0x74= Flash (+1)
0x40= Cloudy (-3)0x75= Flash (+2)
0x41= Cloudy (-2)0x76= Flash (+3)
0x42= Cloudy (-1)0xa3= Custom
0x43= Cloudy (0)0xf3= Color Temperature/Color Filter
0x44= Cloudy (+1)  
+ +

Sony FaceInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0FacesDetectedint16s-1 = n/a
1Face1Positionint16u[4](re-ordered and scaled to return the top, left, height and width of detected +face, with coordinates relative to the full-sized unrotated image and +increasing Y downwards)
6Face2Positionint16u[4] 
11Face3Positionint16u[4] 
16Face4Positionint16u[4] 
21Face5Positionint16u[4] 
26Face6Positionint16u[4] 
31Face7Positionint16u[4] 
36Face8Positionint16u[4] 
+ +

Sony FaceInfoA Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
3FacesDetectedno 
11PotentialFace1Positionint16u[4] 
21PotentialFace2Positionint16u[4] 
31PotentialFace3Positionint16u[4] 
41PotentialFace4Positionint16u[4] 
51PotentialFace5Positionint16u[4] 
61PotentialFace6Positionint16u[4] 
71PotentialFace7Positionint16u[4] 
81PotentialFace8Positionint16u[4] 
91Face1Positionint16u[4] 
101Face2Positionint16u[4] 
111Face3Positionint16u[4] 
121Face4Positionint16u[4] 
+ +

Sony MoreInfo0201 Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
283ImageCountint32u(not valid for the A450, A500 or A550)
293ShutterCountint32u(not valid for the A450, A500 or A550)
330ShutterCountint32u(A450, A500 and A550 only)
+ +

Sony MoreInfo0401 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
1102ShotNumberSincePowerUpint32u(Not valid for the NEX-3 or NEX-5)
+ +

Sony CameraSettings Tags

+

Camera settings for the A200, A300, A350, A700, A850 and A900.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ExposureTimeint16u 
1FNumberint16u 
2HighSpeedSyncint16u0 = Off +
1 = On
3ExposureCompensationSetint16u 
4DriveModeint16u[val & 0xff] +
0x1 = Single Frame +
0x2 = Continuous High +
0x4 = Self-timer 10 sec +
0x5 = Self-timer 2 sec, Mirror Lock-up +
0x6 = Single-frame Bracketing +
0x7 = Continuous Bracketing +
0xa = Remote Commander +
0xb = Mirror Lock-up +
0x12 = Continuous Low +
0x18 = White Balance Bracketing Low +
0x19 = D-Range Optimizer Bracketing Low +
0x28 = White Balance Bracketing High +
0x29 = D-Range Optimizer Bracketing High
+
5WhiteBalanceSettingint16u +
2 = Auto +
4 = Daylight +
5 = Fluorescent +
6 = Tungsten +
7 = Flash +
16 = Cloudy +
17 = Shade +
18 = Color Temperature/Color Filter +
32 = Custom 1 +
33 = Custom 2 +
34 = Custom 3
+
6WhiteBalanceFineTuneint16u 
7ColorTemperatureSetint16u 
8ColorCompensationFilterSetint16u(negative is green, positive is magenta)
12ColorTemperatureCustomint16u 
13ColorCompensationFilterCustomint16u(negative is green, positive is magenta)
15WhiteBalanceint16u + +
2 = Auto +
4 = Daylight +
5 = Fluorescent +
6 = Tungsten +
7 = Flash
  12 = Color Temperature +
13 = Color Filter +
14 = Custom +
16 = Cloudy +
17 = Shade
+
16FocusModeSettingint16u0 = Manual +
1 = AF-S +
2 = AF-C +
3 = AF-A +
4 = DMF
17AFAreaModeint16u0 = Wide +
1 = Local +
2 = Spot
18AFPointSettingint16u + +
1 = Center +
2 = Top +
3 = Upper-right +
4 = Right +
5 = Lower-right +
6 = Bottom
  7 = Lower-left +
8 = Left +
9 = Upper-left +
10 = Far Right +
11 = Far Left
+
19FlashModeint16u + +
0 = Autoflash +
2 = Rear Sync +
3 = Wireless
  4 = Fill-flash +
5 = Flash Off +
6 = Slow Sync
+
20FlashExposureCompSetint16u 
21MeteringModeint16u1 = Multi-segment +
2 = Center-weighted average +
4 = Spot
22ISOSettingint16u 
24DynamicRangeOptimizerModeint16u0 = Off +
1 = Standard +
2 = Advanced Auto +
3 = Advanced Level
25DynamicRangeOptimizerLevelint16u 
26CreativeStyleint16u + +
1 = Standard +
2 = Vivid +
3 = Portrait +
4 = Landscape +
5 = Sunset +
6 = Night View/Portrait +
8 = B&W
  9 = Adobe RGB +
11 = Neutral +
12 = Clear +
13 = Deep +
14 = Light +
15 = Autumn Leaves +
16 = Sepia
+
27ColorSpaceint16u0 = sRGB +
1 = Adobe RGB +
5 = Adobe RGB (A700)
28Sharpnessint16u 
29Contrastint16u 
30Saturationint16u 
31ZoneMatchingValueint16u 
34Brightnessint16u 
35FlashControlint16u0 = ADI +
1 = Pre-flash TTL +
2 = Manual
40PrioritySetupShutterReleaseint16u0 = AF +
1 = Release
41AFIlluminatorint16u0 = Auto +
1 = Off
42AFWithShutterint16u0 = On +
1 = Off
43LongExposureNoiseReductionint16u0 = Off +
1 = On
44HighISONoiseReductionint16u0 = Normal +
1 = Low +
2 = High +
3 = Off
45ImageStyleint16u + +
1 = Standard +
2 = Vivid +
3 = Portrait +
4 = Landscape +
5 = Sunset +
7 = Night View/Portrait +
8 = B&W +
9 = Adobe RGB
  11 = Neutral +
129 = StyleBox1 +
130 = StyleBox2 +
131 = StyleBox3 +
132 = StyleBox4 +
133 = StyleBox5 +
134 = StyleBox6
+
46FocusModeSwitchint16u0 = AF +
1 = Manual
47ShutterSpeedSettingint16u(used in M, S and Program Shift S modes)
48ApertureSettingint16u(used in M, A and Program Shift A modes)
60ExposureProgramint16u--> Sony ExposureProgram Values
61ImageStabilizationSettingint16u0 = Off +
1 = On
62FlashActionint16u0 = Did not fire +
1 = Fired +
2 = External Flash, Did not fire +
3 = External Flash, Fired
63Rotationint16u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
64AELockint16u1 = Off +
2 = On
76FlashAction2int16u +
1 = Fired, Autoflash +
2 = Fired, Fill-flash +
3 = Fired, Rear Sync +
4 = Fired, Wireless +
5 = Did not fire +
6 = Fired, Slow Sync +
17 = Fired, Autoflash, Red-eye reduction +
18 = Fired, Fill-flash, Red-eye reduction +
34 = Fired, Fill-flash, HSS
+
77FocusModeint16u0 = Manual +
1 = AF-S +
2 = AF-C +
3 = AF-A +
4 = DMF
80BatteryStateint16u2 = Empty +
3 = Very Low +
4 = Low +
5 = Sufficient +
6 = Full
81BatteryLevelint16u 
83FocusStatusint16u0x0 = Not confirmed +
0x4 = Not confirmed, Tracking +
Bit 0 = Confirmed +
Bit 1 = Failed +
Bit 2 = Tracking
84SonyImageSizeint16u1 = Large +
2 = Medium +
3 = Small
85AspectRatioint16u1 = 3:2 +
2 = 16:9
86Qualityint16u + +
0 = RAW +
2 = CRAW +
16 = Extra Fine +
32 = Fine
  34 = RAW + JPEG +
35 = CRAW + JPEG +
48 = Standard
+
88ExposureLevelIncrementsint16u33 = 1/3 EV +
50 = 1/2 EV
106RedEyeReductionint16u0 = Off +
1 = On
154FolderNumberint16u[val & 0x3ff]
155ImageNumberint16u[val & 0x3fff]
+ +

Sony CameraSettings2 Tags

+

Camera settings for the A230, A290, A330, A380 and A390.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
0ExposureTimeint16u 
1FNumberint16u 
2HighSpeedSyncint16u0 = Off +
1 = On
3ExposureCompensationSetint16u 
4WhiteBalanceSettingint16u +
2 = Auto +
4 = Daylight +
5 = Fluorescent +
6 = Tungsten +
7 = Flash +
16 = Cloudy +
17 = Shade +
18 = Color Temperature/Color Filter +
32 = Custom 1 +
33 = Custom 2 +
34 = Custom 3
+
5WhiteBalanceFineTuneint16u 
6ColorTemperatureSetint16u 
7ColorCompensationFilterSetint16u(negative is green, positive is magenta)
8CustomWB_RGBLevelsint16u[3] 
11ColorTemperatureCustomint16u 
12ColorCompensationFilterCustomint16u(negative is green, positive is magenta)
14WhiteBalanceint16u + +
2 = Auto +
4 = Daylight +
5 = Fluorescent +
6 = Tungsten +
7 = Flash
  12 = Color Temperature +
13 = Color Filter +
14 = Custom +
16 = Cloudy +
17 = Shade
+
15FocusModeSettingint16u0 = Manual +
1 = AF-S +
2 = AF-C +
3 = AF-A
16AFAreaModeint16u0 = Wide +
1 = Local +
2 = Spot
17AFPointSettingint16u + +
1 = Center +
2 = Top +
3 = Upper-right +
4 = Right +
5 = Lower-right
  6 = Bottom +
7 = Lower-left +
8 = Left +
9 = Upper-left
+
18FlashExposureCompSetint16u 
19MeteringModeint16u1 = Multi-segment +
2 = Center-weighted average +
4 = Spot
20ISOSettingint16u 
22DynamicRangeOptimizerModeint16u0 = Off +
1 = Standard +
2 = Advanced Auto +
3 = Advanced Level
23DynamicRangeOptimizerLevelint16u 
24CreativeStyleint16u + +
1 = Standard +
2 = Vivid +
3 = Portrait +
4 = Landscape
  5 = Sunset +
6 = Night View/Portrait +
8 = B&W
+
25Sharpnessint16u 
26Contrastint16u 
27Saturationint16u 
31FlashControlint16u0 = ADI +
1 = Pre-flash TTL +
2 = Manual
37LongExposureNoiseReductionint16u0 = Off +
1 = On
38HighISONoiseReductionint16u0 = Off +
1 = Low +
2 = Normal +
3 = High
39ImageStyleint16u + +
1 = Standard +
2 = Vivid +
3 = Portrait +
4 = Landscape
  5 = Sunset +
7 = Night View/Portrait +
8 = B&W
+
40ShutterSpeedSettingint16u(used in M, S and Program Shift S modes)
41ApertureSettingint16u(used in M, A and Program Shift A modes)
60ExposureProgramint16u--> Sony ExposureProgram Values
61ImageStabilizationSettingint16u0 = Off +
1 = On
62FlashActionint16u0 = Did not fire +
1 = Fired +
2 = External Flash, Did not fire +
3 = External Flash, Fired
63Rotationint16u0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW
64AELockint16u1 = Off +
2 = On
76FlashAction2int16u +
1 = Fired, Autoflash +
2 = Fired, Fill-flash +
3 = Fired, Rear Sync +
4 = Fired, Wireless +
5 = Did not fire +
6 = Fired, Slow Sync +
17 = Fired, Autoflash, Red-eye reduction +
18 = Fired, Fill-flash, Red-eye reduction +
34 = Fired, Fill-flash, HSS
+
77FocusModeint16u0 = Manual +
1 = AF-S +
2 = AF-C +
3 = AF-A
83FocusStatusint16u0x0 = Not confirmed +
0x4 = Not confirmed, Tracking +
Bit 0 = Confirmed +
Bit 1 = Failed +
Bit 2 = Tracking
84SonyImageSizeint16u1 = Large +
2 = Medium +
3 = Small
85AspectRatioint16u1 = 3:2 +
2 = 16:9
86Qualityint16u + +
0 = RAW +
2 = CRAW +
16 = Extra Fine +
32 = Fine
  34 = RAW + JPEG +
35 = CRAW + JPEG +
48 = Standard
+
88ExposureLevelIncrementsint16u33 = 1/3 EV +
50 = 1/2 EV
126DriveModeint16u[val & 0xff] +
1 = Single Frame +
2 = Continuous High +
4 = Self-timer 10 sec +
5 = Self-timer 2 sec, Mirror Lock-up +
7 = Continuous Bracketing +
10 = Remote Commander +
11 = Continuous Self-timer
+
127FlashModeint16u + +
0 = Autoflash +
2 = Rear Sync +
3 = Wireless
  4 = Fill-flash +
5 = Flash Off +
6 = Slow Sync
+
131ColorSpaceint16u5 = Adobe RGB +
6 = sRGB
+ +

Sony CameraSettings3 Tags

+

Camera settings for models such as the A33, A35, A55, A450, A500, A550, +A560, A580, NEX-3, NEX-5, NEX-C3 and NEX-VG10E.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ShutterSpeedSettingint8u(used only in M and S exposure modes)
1ApertureSettingint8u(used only in M and A exposure modes)
2ISOSettingint8u0 = Auto +
254 = n/a
3ExposureCompensationSetint8u 
4DriveModeSettingint8u +
0x10 = Single Frame +
0x21 = Continuous High +
0x22 = Continuous Low +
0x30 = Speed Priority Continuous +
0x51 = Self-timer 10 sec +
0x52 = Self-timer 2 sec, Mirror Lock-up +
0x71 = Continuous Bracketing 0.3 EV +
0x75 = Continuous Bracketing 0.7 EV +
0x91 = White Balance Bracketing Low +
0x92 = White Balance Bracketing High +
0xc0 = Remote Commander
+
5ExposureProgramint8u--> Sony ExposureProgram2 Values
6FocusModeSettingint8u17 = AF-S +
18 = AF-C +
19 = AF-A +
32 = Manual +
48 = DMF
7MeteringModeint8u1 = Multi-segment +
2 = Center-weighted average +
3 = Spot
9SonyImageSizeint8u + +
21 = Large (3:2) +
22 = Medium (3:2) +
23 = Small (3:2)
  25 = Large (16:9) +
26 = Medium (16:9) +
27 = Small (16:9)
+
10AspectRatioint8u4 = 3:2 +
8 = 16:9
11Qualityint8u2 = RAW +
4 = RAW + JPEG +
6 = Fine +
7 = Standard
12DynamicRangeOptimizerSettingint8u1 = Off +
16 = On (Auto) +
17 = On (Manual)
13DynamicRangeOptimizerLevelint8u 
14ColorSpaceint8u1 = sRGB +
2 = Adobe RGB
15CreativeStyleSettingint8u + +
16 = Standard +
32 = Vivid +
64 = Portrait
  80 = Landscape +
96 = B&W +
160 = Sunset
+
16ContrastSettingint8s 
17SaturationSettingint8s 
18SharpnessSettingint8s 
22WhiteBalanceSettingint8u--> Sony WhiteBalanceSetting Values
23ColorTemperatureSettingint8u 
24ColorCompensationFilterSetint8s(negative is green, positive is magenta)
25CustomWB_RGBLevelsint16uRev[3] 
32FlashModeint8u + +
1 = Flash Off +
16 = Autoflash +
17 = Fill-flash
  18 = Slow Sync +
19 = Rear Sync +
20 = Wireless
+
33FlashControlint8u1 = ADI Flash +
2 = Pre-flash TTL
35FlashExposureCompSetint8u 
36AFAreaModeint8u1 = Wide +
2 = Spot +
3 = Local +
4 = Flexible
37LongExposureNoiseReductionint8u1 = Off +
16 = On
38HighISONoiseReductionint8u16 = Low +
17 = High +
19 = Auto
39SmileShutterModeint8u17 = Slight Smile +
18 = Normal Smile +
19 = Big Smile
40RedEyeReductionint8u1 = Off +
16 = On
45HDRSettingint8u1 = Off +
16 = On (Auto) +
17 = On (Manual)
46HDRLevelint8u + + +
33 = 1 EV +
34 = 1.5 EV +
35 = 2 EV
  36 = 2.5 EV +
37 = 3 EV +
38 = 3.5 EV
  39 = 4 EV +
40 = 5 EV +
41 = 6 EV
+
47ViewingModeint8u16 = ViewFinder +
33 = Focus Check Live View +
34 = Quick AF Live View
48FaceDetectionint8u1 = Off +
16 = On
49SmileShutterint8u1 = Off +
16 = On
50SweepPanoramaSizeint8u1 = Standard +
2 = Wide
51SweepPanoramaDirectionint8u1 = Right +
2 = Left +
3 = Up +
4 = Down
52DriveModeint8u +
0x10 = Single Frame +
0x21 = Continuous High +
0x22 = Continuous Low +
0x30 = Speed Priority Continuous +
0x51 = Self-timer 10 sec +
0x52 = Self-timer 2 sec, Mirror Lock-up +
0x71 = Continuous Bracketing 0.3 EV +
0x75 = Continuous Bracketing 0.7 EV +
0x91 = White Balance Bracketing Low +
0x92 = White Balance Bracketing High +
0xc0 = Remote Commander +
0xd1 = Continuous - HDR +
0xd2 = Continuous - Multi Frame NR +
0xd3 = Continuous - Handheld Night Shot +
0xd4 = Continuous - Anti Motion Blur +
0xd5 = Continuous - Sweep Panorama +
0xd6 = Continuous - 3D Sweep Panorama
+
53MultiFrameNoiseReductionint8u0 = n/a +
1 = Off +
16 = On +
255 = None
54LiveViewAFSettingint8u0 = n/a +
1 = Phase-detect AF +
2 = Contrast AF
56PanoramaSize3Dint8u0 = n/a +
1 = Standard +
2 = Wide +
3 = 16:9
131AFButtonPressedint8u1 = No +
16 = Yes
132LiveViewMeteringint8u0 = n/a +
16 = 40 Segment +
32 = 1200-zone Evaluative
133ViewingMode2int8u0 = n/a +
16 = Viewfinder +
33 = Focus Check Live View +
34 = Quick AF Live View
134AELockint8u1 = On +
2 = Off
135FlashStatusBuilt-inint8u1 = Off +
2 = On
136FlashStatusExternalint8u1 = None +
2 = Off +
3 = On
139LiveViewFocusModeint8u0 = n/a +
1 = AF +
16 = Manual
153LensMountint8u1 = Unknown +
16 = A-mount +
17 = E-mount
268SequenceNumberint8u0 = Single +
255 = n/a
276FolderNumberint32u[val >> 14 & 0x3ff]
276.1ImageNumberint32u[val & 0x3fff]
512ShotNumberSincePowerUp2int32u(same as ShotNumberSincePowerUp for single-shot images, but includes all +shots of the current image in multi-shot modes like HDR, panorama, and +multi-frame noise reduction)
643AFButtonPressedint8u1 = No +
16 = Yes
644LiveViewMeteringint8u0 = n/a +
16 = 40 Segment +
32 = 1200-zone Evaluative
645ViewingMode2int8u0 = n/a +
16 = Viewfinder +
33 = Focus Check Live View +
34 = Quick AF Live View
646AELockint8u1 = On +
2 = Off
647FlashStatusBuilt-inint8u(A450, A500 and A550) +
1 = Off +
2 = On
648FlashStatusExternalint8u(A450, A500 and A550) +
1 = None +
2 = Off +
3 = On
651LiveViewFocusModeint8u0 = n/a +
1 = AF +
16 = Manual
780SequenceNumberint8u(A450, A500 and A550) +
0 = Single +
255 = n/a
788ImageNumberint16u(A450, A500 and A550) +
[val & 0x3fff]
790FolderNumberint16u(A450, A500 and A550) +
[val & 0x3ff]
1008LensE-mountVersionint16u 
1011LensFirmwareVersionint16u~ 
1015LensType2int16u--> Sony LensType2 Values
1024ImageNumberint16u(A450, A500 and A550) +
[val & 0x3fff]
1026FolderNumberint16u(A450, A500 and A550) +
[val & 0x3ff]
+ +

Sony LensType2 Values

+

Lens type numbers for Sony E-mount lenses used by NEX/ILCE models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueLensType2
0= Unknown E-mount lens or other lens
0= Sigma 19mm F2.8 [EX] DN
0= Sigma 30mm F2.8 [EX] DN
0= Sigma 60mm F2.8 DN
0= Sony E 18-200mm F3.5-6.3 OSS LE
0= Tamron 18-200mm F3.5-6.3 Di III VC
0= Tokina FiRIN 20mm F2 FE AF
0= Tokina FiRIN 20mm F2 FE MF
0= Zeiss Touit 12mm F2.8
0= Zeiss Touit 32mm F1.8
0= Zeiss Touit 50mm F2.8 Macro
0= Zeiss Loxia 50mm F2
0= Zeiss Loxia 35mm F2
1= Sony LA-EA1 or Sigma MC-11 Adapter
2= Sony LA-EA2 Adapter
3= Sony LA-EA3 Adapter
6= Sony LA-EA4 Adapter
7= Sony LA-EA5 Adapter
44= Metabones Canon EF Smart Adapter
78= Metabones Canon EF Smart Adapter Mark III or Other Adapter
184= Metabones Canon EF Speed Booster Ultra
234= Metabones Canon EF Smart Adapter Mark IV
239= Metabones Canon EF Speed Booster
24593= LA-EA4r MonsterAdapter
32784= Sony E 16mm F2.8
32785= Sony E 18-55mm F3.5-5.6 OSS
32786= Sony E 55-210mm F4.5-6.3 OSS
32787= Sony E 18-200mm F3.5-6.3 OSS
32788= Sony E 30mm F3.5 Macro
32789= Sony E 24mm F1.8 ZA or Samyang AF 50mm F1.4
32789= Samyang AF 50mm F1.4
32790= Sony E 50mm F1.8 OSS or Samyang AF 14mm F2.8
32790= Samyang AF 14mm F2.8
32791= Sony E 16-70mm F4 ZA OSS
32792= Sony E 10-18mm F4 OSS
32793= Sony E PZ 16-50mm F3.5-5.6 OSS
32794= Sony FE 35mm F2.8 ZA or Samyang Lens
32794= Samyang AF 24mm F2.8
32794= Samyang AF 35mm F2.8
32795= Sony FE 24-70mm F4 ZA OSS
32796= Sony FE 85mm F1.8 or Viltrox PFU RBMH 85mm F1.8
32796= Viltrox PFU RBMH 85mm F1.8
32797= Sony E 18-200mm F3.5-6.3 OSS LE
32798= Sony E 20mm F2.8
32799= Sony E 35mm F1.8 OSS
32800= Sony E PZ 18-105mm F4 G OSS
32801= Sony FE 12-24mm F4 G
32802= Sony FE 90mm F2.8 Macro G OSS
32803= Sony E 18-50mm F4-5.6
32804= Sony FE 24mm F1.4 GM
32805= Sony FE 24-105mm F4 G OSS
32807= Sony E PZ 18-200mm F3.5-6.3 OSS
32808= Sony FE 55mm F1.8 ZA
32810= Sony FE 70-200mm F4 G OSS
32811= Sony FE 16-35mm F4 ZA OSS
32812= Sony FE 50mm F2.8 Macro
32813= Sony FE 28-70mm F3.5-5.6 OSS
32814= Sony FE 35mm F1.4 ZA
32815= Sony FE 24-240mm F3.5-6.3 OSS
32816= Sony FE 28mm F2
32817= Sony FE PZ 28-135mm F4 G OSS
32819= Sony FE 100mm F2.8 STF GM OSS
32820= Sony E PZ 18-110mm F4 G OSS
32821= Sony FE 24-70mm F2.8 GM
32822= Sony FE 50mm F1.4 ZA
32823= Sony FE 85mm F1.4 GM or Samyang AF 85mm F1.4
32823= Samyang AF 85mm F1.4
32824= Sony FE 50mm F1.8
32826= Sony FE 21mm F2.8 (SEL28F20 + SEL075UWC)
32827= Sony FE 16mm F3.5 Fisheye (SEL28F20 + SEL057FEC)
32828= Sony FE 70-300mm F4.5-5.6 G OSS
32829= Sony FE 100-400mm F4.5-5.6 GM OSS
32830= Sony FE 70-200mm F2.8 GM OSS
32831= Sony FE 16-35mm F2.8 GM
32848= Sony FE 400mm F2.8 GM OSS
32849= Sony E 18-135mm F3.5-5.6 OSS
32850= Sony FE 135mm F1.8 GM
32851= Sony FE 200-600mm F5.6-6.3 G OSS
32852= Sony FE 600mm F4 GM OSS
32853= Sony E 16-55mm F2.8 G
32854= Sony E 70-350mm F4.5-6.3 G OSS
32855= Sony FE C 16-35mm T3.1 G
32858= Sony FE 35mm F1.8
32859= Sony FE 20mm F1.8 G
32860= Sony FE 12-24mm F2.8 GM
32862= Sony FE 50mm F1.2 GM
32863= Sony FE 14mm F1.8 GM
32864= Sony FE 28-60mm F4-5.6
32865= Sony FE 35mm F1.4 GM
32866= Sony FE 24mm F2.8 G
32867= Sony FE 40mm F2.5 G
32868= Sony FE 50mm F2.5 G
32871= Sony FE PZ 16-35mm F4 G
32873= Sony E PZ 10-20mm F4 G
32874= Sony FE 70-200mm F2.8 GM OSS II
32875= Sony FE 24-70mm F2.8 GM II
32876= Sony E 11mm F1.8
32877= Sony E 15mm F1.4 G
32878= Sony FE 20-70mm F4 G
32879= Sony FE 50mm F1.4 GM
32884= Sony FE 70-200mm F4 Macro G OSS II
33072= Sony FE 70-200mm F2.8 GM OSS + 1.4X Teleconverter
33073= Sony FE 70-200mm F2.8 GM OSS + 2X Teleconverter
33076= Sony FE 100mm F2.8 STF GM OSS (macro mode)
33077= Sony FE 100-400mm F4.5-5.6 GM OSS + 1.4X Teleconverter
33078= Sony FE 100-400mm F4.5-5.6 GM OSS + 2X Teleconverter
33079= Sony FE 400mm F2.8 GM OSS + 1.4X Teleconverter
33080= Sony FE 400mm F2.8 GM OSS + 2X Teleconverter
33081= Sony FE 200-600mm F5.6-6.3 G OSS + 1.4X Teleconverter
33082= Sony FE 200-600mm F5.6-6.3 G OSS + 2X Teleconverter
33083= Sony FE 600mm F4 GM OSS + 1.4X Teleconverter
33084= Sony FE 600mm F4 GM OSS + 2X Teleconverter
33085= Sony FE 70-200mm F2.8 GM OSS II + 1.4X Teleconverter
33086= Sony FE 70-200mm F2.8 GM OSS II + 2X Teleconverter
33087= Sony FE 70-200mm F4 Macro G OSS II + 1.4X Teleconverter
33088= Sony FE 70-200mm F4 Macro G OSS II + 2X Teleconverter
49201= Zeiss Touit 12mm F2.8
49202= Zeiss Touit 32mm F1.8
49203= Zeiss Touit 50mm F2.8 Macro
49216= Zeiss Batis 25mm F2
49217= Zeiss Batis 85mm F1.8
49218= Zeiss Batis 18mm F2.8
49219= Zeiss Batis 135mm F2.8
49220= Zeiss Batis 40mm F2 CF
49232= Zeiss Loxia 50mm F2
49233= Zeiss Loxia 35mm F2
49234= Zeiss Loxia 21mm F2.8
49235= Zeiss Loxia 85mm F2.4
49236= Zeiss Loxia 25mm F2.4
49456= Tamron E 18-200mm F3.5-6.3 Di III VC
49457= Tamron 28-75mm F2.8 Di III RXD
49458= Tamron 17-28mm F2.8 Di III RXD
49459= Tamron 35mm F2.8 Di III OSD M1:2
49460= Tamron 24mm F2.8 Di III OSD M1:2
49461= Tamron 20mm F2.8 Di III OSD M1:2
49462= Tamron 70-180mm F2.8 Di III VXD
49463= Tamron 28-200mm F2.8-5.6 Di III RXD
49464= Tamron 70-300mm F4.5-6.3 Di III RXD
49465= Tamron 17-70mm F2.8 Di III-A VC RXD
49466= Tamron 150-500mm F5-6.7 Di III VC VXD
49467= Tamron 11-20mm F2.8 Di III-A RXD
49468= Tamron 18-300mm F3.5-6.3 Di III-A VC VXD
49469= Tamron 35-150mm F2-F2.8 Di III VXD
49470= Tamron 28-75mm F2.8 Di III VXD G2
49471= Tamron 50-400mm F4.5-6.3 Di III VC VXD
49472= Tamron 20-40mm F2.8 Di III VXD
49473= Tokina atx-m 85mm F1.8 FE or Viltrox lens
49473= Viltrox 23mm F1.4 E
49473= Viltrox 56mm F1.4 E
49712= Tokina FiRIN 20mm F2 FE AF
49713= Tokina FiRIN 100mm F2.8 FE MACRO
49714= Tokina atx-m 11-18mm F2.8 E
50480= Sigma 30mm F1.4 DC DN | C
50481= Sigma 50mm F1.4 DG HSM | A
50482= Sigma 18-300mm F3.5-6.3 DC MACRO OS HSM | C + MC-11
50483= Sigma 18-35mm F1.8 DC HSM | A + MC-11
50484= Sigma 24-35mm F2 DG HSM | A + MC-11
50485= Sigma 24mm F1.4 DG HSM | A + MC-11
50486= Sigma 150-600mm F5-6.3 DG OS HSM | C + MC-11
50487= Sigma 20mm F1.4 DG HSM | A + MC-11
50488= Sigma 35mm F1.4 DG HSM | A
50489= Sigma 150-600mm F5-6.3 DG OS HSM | S + MC-11
50490= Sigma 120-300mm F2.8 DG OS HSM | S + MC-11
50492= Sigma 24-105mm F4 DG OS HSM | A + MC-11
50493= Sigma 17-70mm F2.8-4 DC MACRO OS HSM | C + MC-11
50495= Sigma 50-100mm F1.8 DC HSM | A + MC-11
50499= Sigma 85mm F1.4 DG HSM | A
50501= Sigma 100-400mm F5-6.3 DG OS HSM | C + MC-11
50503= Sigma 16mm F1.4 DC DN | C
50507= Sigma 105mm F1.4 DG HSM | A
50508= Sigma 56mm F1.4 DC DN | C
50512= Sigma 70-200mm F2.8 DG OS HSM | S + MC-11
50513= Sigma 70mm F2.8 DG MACRO | A
50514= Sigma 45mm F2.8 DG DN | C
50515= Sigma 35mm F1.2 DG DN | A
50516= Sigma 14-24mm F2.8 DG DN | A
50517= Sigma 24-70mm F2.8 DG DN | A
50518= Sigma 100-400mm F5-6.3 DG DN OS | C
50521= Sigma 85mm F1.4 DG DN | A
50522= Sigma 105mm F2.8 DG DN MACRO | A
50523= Sigma 65mm F2 DG DN | C
50524= Sigma 35mm F2 DG DN | C
50525= Sigma 24mm F3.5 DG DN | C
50526= Sigma 28-70mm F2.8 DG DN | C
50527= Sigma 150-600mm F5-6.3 DG DN OS | S
50528= Sigma 35mm F1.4 DG DN | A
50529= Sigma 90mm F2.8 DG DN | C
50530= Sigma 24mm F2 DG DN | C
50531= Sigma 18-50mm F2.8 DC DN | C
50532= Sigma 20mm F2 DG DN | C
50533= Sigma 16-28mm F2.8 DG DN | C
50534= Sigma 20mm F1.4 DG DN | A
50535= Sigma 24mm F1.4 DG DN | A
50536= Sigma 60-600mm F4.5-6.3 DG DN OS | S
50537= Sigma 50mm F2 DG DN | C
50538= Sigma 17mm F4 DG DN | C
50539= Sigma 50mm F1.4 DG DN | A
50540= Sigma 14mm F1.4 DG DN | A
50544= Sigma 23mm F1.4 DC DN | C
50992= Voigtlander SUPER WIDE-HELIAR 15mm F4.5 III
50993= Voigtlander HELIAR-HYPER WIDE 10mm F5.6
50994= Voigtlander ULTRA WIDE-HELIAR 12mm F5.6 III
50995= Voigtlander MACRO APO-LANTHAR 65mm F2 Aspherical
50996= Voigtlander NOKTON 40mm F1.2 Aspherical
50997= Voigtlander NOKTON classic 35mm F1.4
50998= Voigtlander MACRO APO-LANTHAR 110mm F2.5
50999= Voigtlander COLOR-SKOPAR 21mm F3.5 Aspherical
51000= Voigtlander NOKTON 50mm F1.2 Aspherical
51001= Voigtlander NOKTON 21mm F1.4 Aspherical
51002= Voigtlander APO-LANTHAR 50mm F2 Aspherical
51003= Voigtlander NOKTON 35mm F1.2 Aspherical SE
51006= Voigtlander APO-LANTHAR 35mm F2 Aspherical
51504= Samyang AF 50mm F1.4
51505= Samyang AF 14mm F2.8 or Samyang AF 35mm F2.8
51505= Samyang AF 35mm F2.8
51507= Samyang AF 35mm F1.4
51508= Samyang AF 45mm F1.8
51510= Samyang AF 18mm F2.8 or Samyang AF 35mm F1.8
51510= Samyang AF 35mm F1.8
51512= Samyang AF 75mm F1.8
51513= Samyang AF 35mm F1.8
51514= Samyang AF 24mm F1.8
51515= Samyang AF 12mm F2.0
51516= Samyang AF 24-70mm F2.8
51517= Samyang AF 50mm F1.4 II
51518= Samyang AF 135mm F1.8
+ +

Sony CameraSettingsUnknown Tags

+
+
+ + + + +
Index2Tag NameWritableValues / Notes
[no tags known]
+ +

Sony ExtraInfo Tags

+

Extra hardware information for the A850 and A900.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1BatteryTemperatureint8u 
2BatteryUnknown?no 
8BatteryVoltage?no 
10ImageStabilization2?int8u191 = On (191) +
207 = On (207) +
210 = On (210) +
213 = On +
246 = Off
12BatteryLevelint8u 
26ExtraInfoVersionint8u[4] 
+ +

Sony ExtraInfo2 Tags

+

Extra hardware information for the A230/290/330/380/390.

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4BatteryLevelint8u 
18ImageStabilizationint8u0 = Off +
64 = On
+ +

Sony ExtraInfo3 Tags

+

Extra hardware information for the A33, A35, A55, A450, A500, A550, A560, +A580 and NEX-3/5/C3/VG10.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0BatteryUnknown?int16u 
2BatteryTemperatureint8u 
4BatteryLevelint8u 
6BatteryVoltage1int16u 
8BatteryVoltage2int16u 
17ImageStabilizationint8u0 = Off +
64 = On
20BatteryState +
ExposureProgram +
ModeDialPosition
int8u
int8u
int8u
(BatteryState for SLT models) +
1 = Empty +
2 = Low +
3 = Half full +
4 = Almost full +
5 = Full +
(ExposureProgram for the A450, A500 and A550)
+
241 = Landscape +
243 = Aperture-priority AE +
245 = Portrait +
246 = Auto +
247 = Program AE +
249 = Macro +
252 = Sunset +
253 = Sports +
255 = Manual
+(ModeDialPosition for other DSLR models) +
248 = No Flash +
249 = Aperture-priority AE +
250 = SCN +
251 = Shutter speed priority AE +
252 = Auto +
253 = Program AE +
254 = Panorama +
255 = Manual
+
22MemoryCardConfiguration +
CameraOrientation
int8u
int8u
244 = MemoryStick in use, SD card present +
245 = MemoryStick in use, SD slot empty +
252 = SD card in use, MemoryStick present +
254 = SD card in use, MemoryStick slot empty +
[val >> 6 & 0x3] +
0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW +
3 = Rotate 180
24CameraOrientationint8u[val >> 4 & 0x3] +
0 = Horizontal (normal) +
1 = Rotate 90 CW +
2 = Rotate 270 CW +
3 = Rotate 180
+ +

Sony Panorama Tags

+

Tags found in panorama images from various Sony DSC, NEX, SLT and DSLR +cameras. The width/height values of these tags are not affected by camera +rotation -- the width is always the longer dimension.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index4Tag NameWritableValues / Notes
1PanoramaFullWidthint32u 
2PanoramaFullHeightint32u 
3PanoramaDirectionint32u0 = Left or Up +
1 = Right or Down
4PanoramaCropLeftint32u 
5PanoramaCropTopint32u 
6PanoramaCropRightint32u 
7PanoramaCropBottomint32u 
8PanoramaFrameWidthint32u 
9PanoramaFrameHeightint32u 
10PanoramaSourceWidthint32u 
11PanoramaSourceHeightint32u 
+ +

Sony Tag2010a Tags

+

Valid for NEX-5N.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1200MeterInfo?---> Sony MeterInfo Tags
4392ReleaseMode3int8u +
0 = Normal +
1 = Continuous +
2 = Bracketing +
4 = Continuous - Burst +
5 = Continuous - Speed/Advance Priority +
6 = Normal - Self-timer +
9 = Single Burst Shooting
+
4396ReleaseMode2int8u--> Sony ReleaseMode2 Values
4404SelfTimerint8u0 = Off +
1 = Self-timer 10 s +
2 = Self-timer 2 s
4408FlashModeint8u + +
0 = Autoflash +
1 = Fill-flash +
2 = Flash Off
  3 = Slow Sync +
4 = Rear Sync +
6 = Wireless
+
4414StopsAboveBaseISOint16u 
4416BrightnessValueint16u 
4420DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
4424HDRSettingint8u + +
0 = Off +
1 = HDR Auto +
3 = HDR 1 EV +
5 = HDR 2 EV
  7 = HDR 3 EV +
9 = HDR 4 EV +
11 = HDR 5 EV +
13 = HDR 6 EV
+
4428ExposureCompensationint16s 
4446PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4447PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4451PictureEffect2int8u--> Sony PictureEffect2 Values
4464Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG
4468MeteringModeint8u0 = Multi-segment +
2 = Center-weighted average +
3 = Spot +
4 = Average +
5 = Highlight
4469ExposureProgramint8u--> Sony ExposureProgram3 Values
4476WB_RGBLevelsint16u[3] 
+ +

Sony ReleaseMode2 Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueReleaseMode2ValueReleaseMode2
0= Normal16= Continuous - 3D Image
1= Continuous17= Continuous - Burst 2
2= Continuous - Exposure Bracketing18= Normal - iAuto+
3= DRO or White Balance Bracketing19= Continuous - Speed/Advance Priority
5= Continuous - Burst20= Continuous - Multi Frame NR
6= Single Frame - Capture During Movie23= Single-frame - Exposure Bracketing
7= Continuous - Sweep Panorama26= Continuous Low
8= Continuous - Anti-Motion Blur, Hand-held Twilight27= Continuous - High Sensitivity
9= Continuous - HDR28= Smile Shutter
10= Continuous - Background defocus29= Continuous - Tele-zoom Advance Priority
13= Continuous - 3D Sweep Panorama146= Single Frame - Movie Capture
15= Continuous - High Resolution Sweep Panorama  
+ +

Sony PictureEffect2 Values

+
+
+ + + + + + + + + + + + + + + + + +
ValuePictureEffect2ValuePictureEffect2ValuePictureEffect2
0= Off5= Soft High Key10= Rich-tone Monochrome
1= Toy Camera6= Partial Color11= Miniature
2= Pop Color7= High Contrast Monochrome12= Water Color
3= Posterization8= Soft Focus13= Illustration
4= Retro Photo9= HDR Painting  
+ +

Sony ExposureProgram3 Values

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueExposureProgram3ValueExposureProgram3ValueExposureProgram3
0= Program AE11= Twilight Portrait27= Gourmet
1= Aperture-priority AE12= Sunset28= Pet
2= Shutter speed priority AE14= Action (High speed)29= Macro
3= Manual16= Sports30= Backlight Correction HDR
4= Auto17= Handheld Night Shot33= Sweep Panorama
5= iAuto18= Anti Motion Blur36= Background Defocus
6= Superior Auto19= High Sensitivity37= Soft Skin
7= iAuto+21= Beach42= 3D Image
8= Portrait22= Snow43= Cont. Priority AE
9= Landscape23= Fireworks45= Document
10= Twilight26= Underwater46= Party
+ +

Sony MeterInfo Tags

+

Information possibly related to metering. Extracted only if the Unknown +option is used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0MeterInfo1Row1int32u[27] 
108MeterInfo1Row2int32u[27] 
216MeterInfo1Row3int32u[27] 
324MeterInfo1Row4int32u[27] 
432MeterInfo1Row5int32u[27] 
540MeterInfo1Row6int32u[27] 
648MeterInfo1Row7int32u[27] 
756MeterInfo2Row1int32u[33] 
888MeterInfo2Row2int32u[33] 
1020MeterInfo2Row3int32u[33] 
1152MeterInfo2Row4int32u[33] 
1284MeterInfo2Row5int32u[33] 
1416MeterInfo2Row6int32u[33] 
1548MeterInfo2Row7int32u[33] 
1680MeterInfo2Row8int32u[33] 
1812MeterInfo2Row9int32u[33] 
+ +

Sony Tag2010b Tags

+

Valid for SLT-A65/A77, NEX-7/VG20E.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SequenceImageNumberint32u(number of images captured in burst sequence)
4SequenceFileNumberint32u(file number in burst sequence)
8ReleaseMode2int32u--> Sony ReleaseMode2 Values
438SonyDateTimeundef[7] 
804DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
1204MeterInfo?---> Sony MeterInfo Tags
4392ReleaseMode3int8u +
0 = Normal +
1 = Continuous +
2 = Bracketing +
4 = Continuous - Burst +
5 = Continuous - Speed/Advance Priority +
6 = Normal - Self-timer +
9 = Single Burst Shooting
+
4396ReleaseMode2int8u--> Sony ReleaseMode2 Values
4404SelfTimerint8u0 = Off +
1 = Self-timer 10 s +
2 = Self-timer 2 s
4408FlashModeint8u + +
0 = Autoflash +
1 = Fill-flash +
2 = Flash Off
  3 = Slow Sync +
4 = Rear Sync +
6 = Wireless
+
4414StopsAboveBaseISOint16u 
4416BrightnessValueint16u 
4420DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
4424HDRSettingint8u + +
0 = Off +
1 = HDR Auto +
3 = HDR 1 EV +
5 = HDR 2 EV
  7 = HDR 3 EV +
9 = HDR 4 EV +
11 = HDR 5 EV +
13 = HDR 6 EV
+
4428ExposureCompensationint16s 
4450PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4451PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4455PictureEffect2int8u--> Sony PictureEffect2 Values
4468Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG
4472MeteringModeint8u0 = Multi-segment +
2 = Center-weighted average +
3 = Spot +
4 = Average +
5 = Highlight
4473ExposureProgramint8u--> Sony ExposureProgram3 Values
4480WB_RGBLevelsint16u[3] 
4632SonyISOint16u 
6691DistortionCorrParamsint16s[16] 
+ +

Sony Tag2010c Tags

+

Valid for SLT-A37/A57 and NEX-F3.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SequenceImageNumberint32u(number of images captured in burst sequence)
4SequenceFileNumberint32u(file number in burst sequence)
8ReleaseMode2int32u--> Sony ReleaseMode2 Values
512DigitalZoomRatioint8u 
528SonyDateTimeundef[7] 
768DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
1168MeterInfo?---> Sony MeterInfo Tags
4356ReleaseMode3int8u +
0 = Normal +
1 = Continuous +
2 = Bracketing +
4 = Continuous - Burst +
5 = Continuous - Speed/Advance Priority +
6 = Normal - Self-timer +
9 = Single Burst Shooting
+
4360ReleaseMode2int8u--> Sony ReleaseMode2 Values
4368SelfTimerint8u0 = Off +
1 = Self-timer 10 s +
2 = Self-timer 2 s
4372FlashModeint8u + +
0 = Autoflash +
1 = Fill-flash +
2 = Flash Off
  3 = Slow Sync +
4 = Rear Sync +
6 = Wireless
+
4378StopsAboveBaseISOint16u 
4380BrightnessValueint16u 
4384DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
4388HDRSettingint8u + +
0 = Off +
1 = HDR Auto +
3 = HDR 1 EV +
5 = HDR 2 EV
  7 = HDR 3 EV +
9 = HDR 4 EV +
11 = HDR 5 EV +
13 = HDR 6 EV
+
4392ExposureCompensationint16s 
4414PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4415PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4419PictureEffect2int8u--> Sony PictureEffect2 Values
4432Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG
4436MeteringModeint8u0 = Multi-segment +
2 = Center-weighted average +
3 = Spot +
4 = Average +
5 = Highlight
4437ExposureProgramint8u--> Sony ExposureProgram3 Values
4444WB_RGBLevelsint16u[3] 
4596SonyISOint16u 
+ +

Sony Tag2010d Tags

+

Valid for DSC-HX10V/HX20V/HX200V/TX66/TX200V/TX300V/WX50/WX100/WX150, but +not valid for panorama images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SequenceImageNumberint32u(number of images captured in burst sequence)
4SequenceFileNumberint32u(file number in burst sequence)
8ReleaseMode2int32u--> Sony ReleaseMode2 Values
510SonyDateTimeundef[7] 
892DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
1292MeterInfo?---> Sony MeterInfo Tags
4480ReleaseMode3int8u +
0 = Normal +
1 = Continuous +
2 = Bracketing +
4 = Continuous - Burst +
5 = Continuous - Speed/Advance Priority +
6 = Normal - Self-timer +
9 = Single Burst Shooting
+
4484ReleaseMode2int8u--> Sony ReleaseMode2 Values
4492SelfTimerint8u0 = Off +
1 = Self-timer 10 s +
2 = Self-timer 2 s
4496FlashModeint8u + +
0 = Autoflash +
1 = Fill-flash +
2 = Flash Off
  3 = Slow Sync +
4 = Rear Sync +
6 = Wireless
+
4502StopsAboveBaseISOint16u 
4504BrightnessValueint16u 
4508DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
4512HDRSettingint8u + +
0 = Off +
1 = HDR Auto +
3 = HDR 1 EV +
5 = HDR 2 EV
  7 = HDR 3 EV +
9 = HDR 4 EV +
11 = HDR 5 EV +
13 = HDR 6 EV
+
4538PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4539PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4543PictureEffect2int8u--> Sony PictureEffect2 Values
4560MeteringModeint8u0 = Multi-segment +
2 = Center-weighted average +
3 = Spot +
4 = Average +
5 = Highlight
4561ExposureProgramint8u--> Sony ExposureProgram3 Values
4568WB_RGBLevelsint16u[3] 
4720SonyISOint16u 
+ +

Sony Tag2010e Tags

+

Valid for SLT-A58/A99, ILCE-3000/3500, NEX-3N/5R/5T/6/VG30E/VG900, +DSC-RX100, DSC-RX1/RX1R. Also valid for DSC-HX300/HX50V/TX30/WX60/WX200/ +WX300, but not for panorama images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SequenceImageNumberint32u(number of images captured in burst sequence)
4SequenceFileNumberint32u(file number in burst sequence)
8ReleaseMode2int32u--> Sony ReleaseMode2 Values
540DigitalZoomRatioint8u 
556SonyDateTimeundef[7] 
808DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
1208MeterInfo?---> Sony MeterInfo Tags
4444ReleaseMode3int8u +
0 = Normal +
1 = Continuous +
2 = Bracketing +
4 = Continuous - Burst +
5 = Continuous - Speed/Advance Priority +
6 = Normal - Self-timer +
9 = Single Burst Shooting
+
4448ReleaseMode2int8u--> Sony ReleaseMode2 Values
4456SelfTimerint8u0 = Off +
1 = Self-timer 10 s +
2 = Self-timer 2 s
4460FlashModeint8u + +
0 = Autoflash +
1 = Fill-flash +
2 = Flash Off
  3 = Slow Sync +
4 = Rear Sync +
6 = Wireless
+
4466StopsAboveBaseISOint16u 
4468BrightnessValueint16u 
4472DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
4476HDRSettingint8u + +
0 = Off +
1 = HDR Auto +
3 = HDR 1 EV +
5 = HDR 2 EV
  7 = HDR 3 EV +
9 = HDR 4 EV +
11 = HDR 5 EV +
13 = HDR 6 EV
+
4480ExposureCompensationint16s 
4502PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4503PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4507PictureEffect2int8u--> Sony PictureEffect2 Values
4520Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG
4524MeteringModeint8u0 = Multi-segment +
2 = Center-weighted average +
3 = Spot +
4 = Average +
5 = Highlight
4525ExposureProgramint8u--> Sony ExposureProgram3 Values
4532WB_RGBLevelsint16u[3] 
4692SonyISOint16u 
4696SonyISOint16u 
4728FocalLengthint16u 
4730MinFocalLengthint16u 
4732MaxFocalLengthint16u 
4736SonyISOint16u 
6256DistortionCorrParamsint16s[16] 
6289LensFormatint8u0 = Unknown +
1 = APS-C +
2 = Full-frame
6290LensMountint8u0 = Unknown +
1 = A-mount +
2 = E-mount
6291LensType2int16u--> Sony LensType2 Values
6294LensTypeint16u--> Sony LensType Values
6296DistortionCorrParamsPresentint8u0 = No +
1 = Yes
6297DistortionCorrParamsNumberint8u11 = 11 (APS-C) +
16 = 16 (Full-frame)
6444AspectRatioint8u0 = 16:9 +
1 = 4:3 +
2 = 3:2 +
3 = 1:1 +
5 = Panorama
6792AspectRatioint8u0 = 16:9 +
1 = 4:3 +
2 = 3:2 +
3 = 1:1 +
5 = Panorama
+ +

Sony Tag2010f Tags

+

Valid for DSC-RX100M2, DSC-QX10/QX100.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ReleaseMode2int32u--> Sony ReleaseMode2 Values
80DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
480MeterInfo?---> Sony MeterInfo Tags
4116ReleaseMode3int8u +
0 = Normal +
1 = Continuous +
2 = Bracketing +
4 = Continuous - Burst +
5 = Continuous - Speed/Advance Priority +
6 = Normal - Self-timer +
9 = Single Burst Shooting
+
4120ReleaseMode2int8u--> Sony ReleaseMode2 Values
4128SelfTimerint8u0 = Off +
1 = Self-timer 10 s +
2 = Self-timer 2 s
4132FlashModeint8u + +
0 = Autoflash +
1 = Fill-flash +
2 = Flash Off
  3 = Slow Sync +
4 = Rear Sync +
6 = Wireless
+
4138StopsAboveBaseISOint16u 
4140BrightnessValueint16u 
4144DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
4148HDRSettingint8u + +
0 = Off +
1 = HDR Auto +
3 = HDR 1 EV +
5 = HDR 2 EV
  7 = HDR 3 EV +
9 = HDR 4 EV +
11 = HDR 5 EV +
13 = HDR 6 EV
+
4152ExposureCompensationint16s 
4174PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4175PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
4179PictureEffect2int8u--> Sony PictureEffect2 Values
4192Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG
4196MeteringModeint8u0 = Multi-segment +
2 = Center-weighted average +
3 = Spot +
4 = Average +
5 = Highlight
4197ExposureProgramint8u--> Sony ExposureProgram3 Values
4204WB_RGBLevelsint16u[3] 
4404FocalLengthint16u 
4406MinFocalLengthint16u 
4408MaxFocalLengthint16u 
4412SonyISOint16u 
6444AspectRatioint8u0 = 16:9 +
1 = 4:3 +
2 = 3:2 +
3 = 1:1 +
5 = Panorama
+ +

Sony Tag2010g Tags

+

Valid for DSC-HX60V/HX350/HX400V/QX30/RX10/RX100M3/WX220/WX350, +ILCE-7/7R/7S/7M2/5000/5100/6000/QX1, ILCA-68/77M2.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ReleaseMode2int32u--> Sony ReleaseMode2 Values
80DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
524ReleaseMode3int8u +
0 = Normal +
1 = Continuous +
2 = Bracketing +
4 = Continuous - Burst +
5 = Continuous - Speed/Advance Priority +
6 = Normal - Self-timer +
9 = Single Burst Shooting
+
528ReleaseMode2int8u--> Sony ReleaseMode2 Values
536SelfTimerint8u0 = Off +
1 = Self-timer 10 s +
2 = Self-timer 2 s
540FlashModeint8u + +
0 = Autoflash +
1 = Fill-flash +
2 = Flash Off
  3 = Slow Sync +
4 = Rear Sync +
6 = Wireless
+
546StopsAboveBaseISOint16u 
548BrightnessValueint16u 
552DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
556HDRSettingint8u + +
0 = Off +
1 = HDR Auto +
3 = HDR 1 EV +
5 = HDR 2 EV
  7 = HDR 3 EV +
9 = HDR 4 EV +
11 = HDR 5 EV +
13 = HDR 6 EV
+
560ExposureCompensationint16s 
582PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
583PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
587PictureEffect2int8u--> Sony PictureEffect2 Values
600Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG
604MeteringModeint8u0 = Multi-segment +
2 = Center-weighted average +
3 = Spot +
4 = Average +
5 = Highlight
605ExposureProgramint8u--> Sony ExposureProgram3 Values
612WB_RGBLevelsint16u[3] 
812FocalLengthint16u 
814MinFocalLengthint16u 
816MaxFocalLengthint16u 
836SonyISOint16u 
904MeterInfo?---> Sony MeterInfo Tags
6300DistortionCorrParamsint16s[16] 
6333LensFormatint8u0 = Unknown +
1 = APS-C +
2 = Full-frame
6334LensMountint8u0 = Unknown +
1 = A-mount +
2 = E-mount
6335LensType2int16u--> Sony LensType2 Values
6338LensTypeint16u--> Sony LensType Values
6340DistortionCorrParamsPresentint8u0 = No +
1 = Yes
6341DistortionCorrParamsNumberint8u11 = 11 (APS-C) +
16 = 16 (Full-frame)
6488AspectRatioint8u0 = 16:9 +
1 = 4:3 +
2 = 3:2 +
3 = 1:1 +
5 = Panorama
+ +

Sony Tag2010h Tags

+

Valid for DSC-HX80/HX90V/RX0/RX1RM2/RX10M2/RX10M3/RX100M4/RX100M5/WX500, +ILCE-6300/6500/7RM2/7SM2, ILCA-99M2.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ReleaseMode2int32u--> Sony ReleaseMode2 Values
80DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
524ReleaseMode3int8u +
0 = Normal +
1 = Continuous +
2 = Bracketing +
4 = Continuous - Burst +
5 = Continuous - Speed/Advance Priority +
6 = Normal - Self-timer +
9 = Single Burst Shooting
+
528ReleaseMode2int8u--> Sony ReleaseMode2 Values
536SelfTimerint8u0 = Off +
1 = Self-timer 5 or 10 s +
2 = Self-timer 2 s
540FlashModeint8u + +
0 = Autoflash +
1 = Fill-flash +
2 = Flash Off
  3 = Slow Sync +
4 = Rear Sync +
6 = Wireless
+
546StopsAboveBaseISOint16u 
548BrightnessValueint16u 
552DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
556HDRSettingint8u + +
0 = Off +
1 = HDR Auto +
3 = HDR 1 EV +
5 = HDR 2 EV
  7 = HDR 3 EV +
9 = HDR 4 EV +
11 = HDR 5 EV +
13 = HDR 6 EV
+
560ExposureCompensationint16s 
582PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
583PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
587PictureEffect2int8u--> Sony PictureEffect2 Values
600Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG
604MeteringModeint8u0 = Multi-segment +
2 = Center-weighted average +
3 = Spot +
4 = Average +
5 = Highlight
605ExposureProgramint8u--> Sony ExposureProgram3 Values
612WB_RGBLevelsint16u[3] 
812FocalLengthint16u 
814MinFocalLengthint16u 
816MaxFocalLengthint16u 
838SonyISOint16u 
904MeterInfo?---> Sony MeterInfo Tags
920MeterInfo?---> Sony MeterInfo Tags
6348DistortionCorrParamsint16s[16] 
6381LensFormatint8u0 = Unknown +
1 = APS-C +
2 = Full-frame
6382LensMountint8u0 = Unknown +
1 = A-mount +
2 = E-mount
6383LensType2int16u--> Sony LensType2 Values
6386LensTypeint16u--> Sony LensType Values
6388DistortionCorrParamsPresentint8u0 = No +
1 = Yes
6389DistortionCorrParamsNumberint8u11 = 11 (APS-C) +
16 = 16 (Full-frame)
6444AspectRatioint8u0 = 16:9 +
1 = 4:3 +
2 = 3:2 +
3 = 1:1 +
5 = Panorama
+ +

Sony Tag2010i Tags

+

Valid for ILCE-6100/6400/6600/7C/7M3/7RM3/7RM4/9/9M2, DSC-RX0M2/RX10M4/RX100M6/ +RX100M5A/RX100M7/HX99.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4ReleaseMode2int32u--> Sony ReleaseMode2 Values
78DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
516ReleaseMode3int8u +
0 = Normal +
1 = Continuous +
2 = Bracketing +
4 = Continuous - Burst +
5 = Continuous - Speed/Advance Priority +
6 = Normal - Self-timer +
9 = Single Burst Shooting
+
520ReleaseMode2int8u--> Sony ReleaseMode2 Values
528SelfTimerint8u0 = Off +
1 = Self-timer 5 or 10 s +
2 = Self-timer 2 s
529FlashModeint8u + +
0 = Autoflash +
1 = Fill-flash +
2 = Flash Off
  3 = Slow Sync +
4 = Rear Sync +
6 = Wireless
+
535StopsAboveBaseISOint16u 
537BrightnessValueint16u 
539DynamicRangeOptimizerint8u + +
0 = Off +
1 = Auto +
3 = Lv1 +
4 = Lv2
  5 = Lv3 +
6 = Lv4 +
7 = Lv5 +
8 = n/a
+
543HDRSettingint8u + +
0 = Off +
1 = HDR Auto +
3 = HDR 1 EV +
5 = HDR 2 EV
  7 = HDR 3 EV +
9 = HDR 4 EV +
11 = HDR 5 EV +
13 = HDR 6 EV
+
547ExposureCompensationint16s 
567PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
568PictureProfileint8u +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
572PictureEffect2int8u--> Sony PictureEffect2 Values
583Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG
587MeteringModeint8u0 = Multi-segment +
2 = Center-weighted average +
3 = Spot +
4 = Average +
5 = Highlight
588ExposureProgramint8u--> Sony ExposureProgram3 Values
594WB_RGBLevelsint16u[3] 
778FocalLengthint16u 
780MinFocalLengthint16u 
782MaxFocalLengthint16u 
800SonyISOint16u 
877MeterInfo?---> Sony MeterInfo9 Tags
6096DistortionCorrParamsint16s[16] 
6129LensFormatint8u0 = Unknown +
1 = APS-C +
2 = Full-frame
6130LensMountint8u0 = Unknown +
1 = A-mount +
2 = E-mount
6131LensType2int16u--> Sony LensType2 Values
6134LensTypeint16u--> Sony LensType Values
6136DistortionCorrParamsPresentint8u0 = No +
1 = Yes
6137DistortionCorrParamsNumberint8u11 = 11 (APS-C) +
16 = 16 (Full-frame)
6284AspectRatioint8u0 = 16:9 +
1 = 4:3 +
2 = 3:2 +
3 = 1:1 +
5 = Panorama
+ +

Sony MeterInfo9 Tags

+

Information possibly related to metering. Extracted only if the Unknown +option is used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0MeterInfo1Row1no 
90MeterInfo1Row2no 
180MeterInfo1Row3no 
270MeterInfo1Row4no 
360MeterInfo1Row5no 
450MeterInfo1Row6no 
540MeterInfo1Row7no 
630MeterInfo2Row1no 
740MeterInfo2Row2no 
850MeterInfo2Row3no 
960MeterInfo2Row4no 
1070MeterInfo2Row5no 
1180MeterInfo2Row6no 
1290MeterInfo2Row7no 
1400MeterInfo2Row8no 
1510MeterInfo2Row9no 
+ +

Sony Tag202a Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1FocalPlaneAFPointsUsedint8u 
2FocalPlaneAFPointAreaint16u[2] 
6FocalPlaneAFPointLocation1int16u[2] 
10FocalPlaneAFPointLocation2int16u[2] 
14FocalPlaneAFPointLocation3int16u[2] 
18FocalPlaneAFPointLocation4int16u[2] 
22FocalPlaneAFPointLocation5int16u[2] 
26FocalPlaneAFPointLocation6int16u[2] 
30FocalPlaneAFPointLocation7int16u[2] 
34FocalPlaneAFPointLocation8int16u[2] 
38FocalPlaneAFPointLocation9int16u[2] 
42FocalPlaneAFPointLocation10int16u[2] 
46FocalPlaneAFPointLocation11int16u[2] 
50FocalPlaneAFPointLocation12int16u[2] 
54FocalPlaneAFPointLocation13int16u[2] 
58FocalPlaneAFPointLocation14int16u[2] 
62FocalPlaneAFPointLocation15int16u[2] 
+ +

Sony ShotInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
2FaceInfoOffsetno 
6SonyDateTimestring[20] 
26SonyImageHeightint16u 
28SonyImageWidthint16u 
48FacesDetectedint16u 
50FaceInfoLengthno 
52MetaVersionstring[16] 
72FaceInfo1---> Sony FaceInfo1 Tags
94FaceInfo2---> Sony FaceInfo2 Tags
+ +

Sony FaceInfo1 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Face1Positionint16u[4](top, left, height and width of detected face. Coordinates are relative to +the full-sized unrotated image, with increasing Y downwards)
32Face2Positionint16u[4] 
64Face3Positionint16u[4] 
96Face4Positionint16u[4] 
128Face5Positionint16u[4] 
160Face6Positionint16u[4] 
192Face7Positionint16u[4] 
224Face8Positionint16u[4] 
+ +

Sony FaceInfo2 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Face1Positionint16u[4](top, left, height and width of detected face. Coordinates are relative to +the full-sized unrotated image, with increasing Y downwards)
37Face2Positionint16u[4] 
74Face3Positionint16u[4] 
111Face4Positionint16u[4] 
148Face5Positionint16u[4] 
185Face6Positionint16u[4] 
222Face7Positionint16u[4] 
259Face8Positionint16u[4] 
+ +

Sony Tag900b Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
2FacesDetectedno + + +
0 = 0 +
33 = 5 +
57 = 2
  77 = 4 +
93 = 3 +
98 = 1
  115 = 8 +
168 = 6 +
241 = 7
+
189FaceDetectionno0 = Off +
98 = On
+ +

Sony Tag9050a Tags

+

Data for tags 0x9050, 0x94xx and 0x2010 is encrypted by a simple +substitution cipher, but the deciphered values are listed below.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SonyMaxApertureint8u 
1SonyMinApertureint8u 
32Shutterint16u[3]'0 0 0' = Silent / Electronic (0 0 0)
49FlashStatusint8u +
0 = No Flash present +
2 = Flash Inhibited +
64 = Built-in Flash present +
65 = Built-in Flash Fired +
66 = Built-in Flash Inhibited +
128 = External Flash present +
129 = External Flash Fired
+
50ShutterCountint32u(total number of image exposures made by the camera, modulo 65536 for some +models)
58SonyExposureTimeint16u 
60SonyFNumberint16u 
63ReleaseMode2int8u--> Sony ReleaseMode2 Values
76ShutterCount2int32u 
81SonyDateTime2undef[6] 
103ReleaseMode2int8u--> Sony ReleaseMode2 Values
124InternalSerialNumberint8u[4]~ 
240InternalSerialNumberint8u[5] 
261LensMountint8u0 = Unknown +
1 = A-mount +
2 = E-mount
262LensFormatint8u0 = Unknown +
1 = APS-C +
2 = Full-frame
263LensType2int16u--> Sony LensType2 Values
265LensTypeint16u--> Sony LensType Values +
(SLT models, and NEX with A-mount lenses)
267DistortionCorrParamsPresentint8u0 = No +
1 = Yes
276APS-CSizeCaptureint8u0 = Off +
1 = On
277LensSpecFeaturesundef[2] 
278LensSpecFeaturesundef[2] 
416ShutterCount3int32u 
426ShutterCount3int32u 
445ShutterCount3int32u 
+ +

Sony Tag9050b Tags

+

Valid from July 2015 for ILCE-6100/6300/6400/6500/6600/7C/7M3/7RM2/7RM3/7RM4/ +7SM2/9/9M2, ILCA-99M2.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0SonyMaxApertureint8u 
1SonyMinApertureint8u 
38Shutterint16u[3]'0 0 0' = Silent / Electronic (0 0 0)
57FlashStatusint8u +
0 = No Flash present +
2 = Flash Inhibited +
64 = Built-in Flash present +
65 = Built-in Flash Fired +
66 = Built-in Flash Inhibited +
128 = External Flash present +
129 = External Flash Fired
+
58ShutterCountint32u(total number of image exposures made by the camera)
70SonyExposureTimeint16u 
72SonyFNumberint16u 
75ReleaseMode2int8u--> Sony ReleaseMode2 Values
80ShutterCount2int32u 
82ShutterCount2int32u 
88ShutterCount2int32u 
97SonyTimeMinSecno 
107ReleaseMode2int8u--> Sony ReleaseMode2 Values
109ReleaseMode2int8u--> Sony ReleaseMode2 Values
115ReleaseMode2int8u--> Sony ReleaseMode2 Values
136InternalSerialNumberint8u[6]~ 
261LensMountint8u0 = Unknown +
1 = A-mount +
2 = E-mount
262LensFormatint8u0 = Unknown +
1 = APS-C +
2 = Full-frame
263LensType2int16u--> Sony LensType2 Values
265LensTypeint16u--> Sony LensType Values +
(SLT models, and NEX with A-mount lenses)
267DistortionCorrParamsPresentint8u0 = No +
1 = Yes
276APS-CSizeCaptureint8u0 = Off +
1 = On
278LensSpecFeaturesundef[2] 
415ShutterCount3int32u 
459ShutterCount3int32u 
461ShutterCount3int32u 
491APS-CSizeCaptureint8u0 = Off +
1 = On
493LensSpecFeaturesundef[2] 
494APS-CSizeCaptureint8u0 = Off +
1 = On
496LensSpecFeaturesundef[2] 
538APS-CSizeCaptureint8u0 = Off +
1 = On
540LensSpecFeatures +
APS-CSizeCapture
undef[2]
int8u
0 = Off +
1 = On
542LensSpecFeaturesundef[2] 
+ +

Sony Tag9050c Tags

+

Valid from July 2020 for ILCE-1/7SM3, ILME-FX3.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
38Shutterint16u[3]'0 0 0' = Silent / Electronic (0 0 0)
57FlashStatusint8u +
0 = No Flash present +
2 = Flash Inhibited +
64 = Built-in Flash present +
65 = Built-in Flash Fired +
66 = Built-in Flash Inhibited +
128 = External Flash present +
129 = External Flash Fired
+
58ShutterCountint32u~(total number of image exposures made by the camera)
70SonyExposureTimeint16u 
72SonyFNumberint16u 
75ReleaseMode2int8u--> Sony ReleaseMode2 Values
80ShutterCount2int32u 
102SonyExposureTimeint16u 
104SonyFNumberint16u 
107ReleaseMode2int8u--> Sony ReleaseMode2 Values
136InternalSerialNumberint8u[6]~ 
138InternalSerialNumberint8u[6]~ 
+ +

Sony Tag9050d Tags

+

Valid for ILCE-6700/ZV-E1.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
10ShutterCountint32u(total number of image exposures made by the camera)
26SonyExposureTimeint16u 
28SonyFNumberint16u 
31ReleaseMode2int8u--> Sony ReleaseMode2 Values
56InternalSerialNumberint8u[6]~ 
+ +

Sony Tag9400a Tags

+

Valid for many DSC, NEX and SLT models

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8SequenceImageNumberint32u(number of images captured in burst sequence)
12SequenceFileNumberint32u(file number in burst sequence)
16ReleaseMode2int8u--> Sony ReleaseMode2 Values
18DigitalZoomint8u0 = No +
1 = Yes
26ShotNumberSincePowerUpint32u 
34SequenceLengthint8u +
0 = Continuous +
1 = 1 shot +
2 = 2 shots +
3 = 3 shots +
4 = 4 shots +
5 = 5 shots +
6 = 6 shots +
10 = 10 shots +
100 = Continuous - iSweep Panorama +
200 = Continuous - Sweep Panorama
+
40CameraOrientationint8u1 = Horizontal (normal) +
3 = Rotate 180 +
6 = Rotate 90 CW +
8 = Rotate 270 CW
41Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG +
3 = JPEG + MPO
68SonyImageHeightint16u~ 
82ModelReleaseYearint8u~ 
+ +

Sony Tag9400b Tags

+

Valid for NEX-3N, ILCE-3000/3500, SLT-A58, DSC-WX60, DSC-WX300, DSC-RX100M2, +DSC-HX50V, DSC-QX10/QX100.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8SequenceImageNumberint32u(number of images captured in burst sequence)
12SequenceFileNumberint32u(file number in burst sequence)
16ReleaseMode2int8u--> Sony ReleaseMode2 Values
18DigitalZoomint8u0 = No +
1 = Yes
22ShotNumberSincePowerUpint32u 
30SequenceLengthint8u +
0 = Continuous +
1 = 1 shot +
2 = 2 shots +
3 = 3 shots +
4 = 4 shots +
5 = 5 shots +
6 = 6 shots +
10 = 10 shots +
100 = Continuous - iSweep Panorama +
200 = Continuous - Sweep Panorama
+
36CameraOrientationint8u1 = Horizontal (normal) +
3 = Rotate 180 +
6 = Rotate 90 CW +
8 = Rotate 270 CW
37Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG +
3 = JPEG + MPO
63SonyImageHeightint16u~ 
70ModelReleaseYearint8u~ 
+ +

Sony Tag9400c Tags

+

Valid for DSC-HX60V/HX80/HX90V/HX99/HX350/HX400V/QX30/RX0/RX1RM2/RX10/ +RX10M2/RX10M3/RX10M4/RX100M3/RX100M4/RX100M5/RX100M5A/RX100M6/RX100M7/WX220/ +WX350/WX500, ILCE-1/7/7C/7R/7S/7M2/7M3/7RM2/7RM3/7RM4/7SM2/7SM3/9/9M2/5000/ +5100/6000/6100/6300/6400/6500/6600/QX1, ILCA-68/77M2/99M2.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
9ReleaseMode2int8u--> Sony ReleaseMode2 Values
10ShotNumberSincePowerUpint32u(valid only for some models)
18SequenceImageNumberint32u(number of images captured in burst sequence)
22SequenceLengthint8u +
0 = Continuous +
1 = 1 shot +
2 = 2 shots +
3 = 3 shots +
4 = 4 shots +
5 = 5 shots +
6 = 6 shots +
7 = 7 shots +
9 = 9 shots +
10 = 10 shots +
12 = 12 shots +
16 = 16 shots +
100 = Continuous - iSweep Panorama +
200 = Continuous - Sweep Panorama
+
26SequenceFileNumberint32u(file number in burst sequence)
30SequenceLengthint8u + +
0 = Continuous +
1 = 1 file +
2 = 2 files +
3 = 3 files
  5 = 5 files +
7 = 7 files +
9 = 9 files +
10 = 10 files
+
41CameraOrientationint8u1 = Horizontal (normal) +
3 = Rotate 180 +
6 = Rotate 90 CW +
8 = Rotate 270 CW
42Quality2int8u0 = JPEG +
1 = RAW +
2 = RAW + JPEG +
3 = JPEG + MPO +
1 = JPEG +
2 = RAW +
3 = RAW + JPEG +
4 = HEIF +
6 = RAW + HEIF
71SonyImageHeightint16u~ 
83ModelReleaseYearint8u~ 
307ShutterTypeint8u7 = Electronic +
23 = Mechanical
313ShutterTypeint8u7 = Electronic +
23 = Mechanical
319ShutterTypeint8u7 = Electronic +
23 = Mechanical
+ +

Sony Tag9401 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
994ISOInfo---> Sony ISOInfo Tags
1102ISOInfo---> Sony ISOInfo Tags
1176ISOInfo---> Sony ISOInfo Tags
1181ISOInfo---> Sony ISOInfo Tags
1185ISOInfo---> Sony ISOInfo Tags
1186ISOInfo---> Sony ISOInfo Tags
1210ISOInfo---> Sony ISOInfo Tags
1437ISOInfo---> Sony ISOInfo Tags
1588ISOInfo---> Sony ISOInfo Tags
1590ISOInfo---> Sony ISOInfo Tags
1612ISOInfo---> Sony ISOInfo Tags
1619ISOInfo---> Sony ISOInfo Tags
1656ISOInfo---> Sony ISOInfo Tags
1720ISOInfo---> Sony ISOInfo Tags
1758ISOInfo---> Sony ISOInfo Tags
1767ISOInfo---> Sony ISOInfo Tags
+ +

Sony ISOInfo Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0ISOSettingint8u 
2ISOAutoMinint8u 
4ISOAutoMaxint8u 
+ +

Sony Tag9402 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4AmbientTemperatureint8s 
22FocusModeint8u[val & 0x7f] +
0 = Manual +
2 = AF-S +
3 = AF-C +
4 = AF-A +
6 = DMF
23AFAreaModeint8u +
0 = Multi +
1 = Center +
2 = Spot +
3 = Flexible Spot +
10 = Selective (for Miniature effect) +
11 = Zone +
12 = Expanded Flexible Spot +
14 = Tracking +
15 = Face Tracking +
20 = Animal Eye Tracking +
255 = Manual
+
45FocusPosition2int8u 
+ +

Sony Tag9403 Tags

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
5CameraTemperatureint8s 
+ +

Sony Tag9404a Tags

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
11ExposureProgramint8u--> Sony ExposureProgram3 Values
13IntelligentAutoint8u0 = Off +
1 = On
25LensZoomPositionint16u 
+ +

Sony Tag9404b Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
12ExposureProgramint8u--> Sony ExposureProgram3 Values
14IntelligentAutoint8u0 = Off +
1 = On
30LensZoomPositionint16u 
32FocusPosition2int8u 
+ +

Sony Tag9404c Tags

+
+
+ + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
11ExposureProgramint8u--> Sony ExposureProgram3 Values
13IntelligentAutoint8u0 = Off +
1 = On
+ +

Sony Tag9405a Tags

+

Valid for SLT, NEX, ILCE-3000/3500 and several DSC models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
1536DistortionCorrParamsPresentint8u0 = No +
1 = Yes
1537DistortionCorrectionint8u0 = None +
1 = Applied
1539LensFormatint8u0 = Unknown +
1 = APS-C +
2 = Full-frame
1540LensMountint8u0 = Unknown +
1 = A-mount +
2 = E-mount
1541LensType2int16u--> Sony LensType2 Values +
(E-mount lenses)
1544LensTypeint16u--> Sony LensType Values +
(A-mount lenses on SLT and NEX)
1610VignettingCorrParamsint16s[16] 
1642ChromaticAberrationCorrParamsint16s[32] 
1738DistortionCorrParamsint16s[16] 
+ +

Sony Tag9405b Tags

+

Valid for DSC-HX60V/HX80/HX90V/HX99/HX350/HX400V/QX30/RX0/RX10/RX10M2/ +RX10M3/RX10M4/RX100M3/RX100M4/RX100M5/RX100M5A/RX100M6/RX100M7/WX220/WX350, +ILCE-7/7M2/7M3/7R/7RM2/7RM3/7RM4/7S/7SM2/9/9M2/5000/5100/6000/6100/6300/ +6400/6500/6600/QX1, ILCA-68/77M2/99M2.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
4SonyISOint16u 
6BaseISOint16u 
10StopsAboveBaseISOint16u 
14SonyExposureTime2int16u 
16ExposureTimerational32u 
20SonyFNumberint16u 
22SonyMaxApertureValueint16u 
36SequenceImageNumberint32u(number of images captured in burst sequence)
52ReleaseMode2int8u--> Sony ReleaseMode2 Values
62SonyImageWidthMaxint16u 
64SonyImageHeightMaxint16u 
66HighISONoiseReductionint8u0 = Off +
1 = Low +
2 = Normal +
3 = High
68LongExposureNoiseReductionint8u0 = Off +
1 = On
70PictureEffect2int8u--> Sony PictureEffect2 Values
72ExposureProgramint8u--> Sony ExposureProgram3 Values
74CreativeStyleint8u +
0 = Standard +
1 = Vivid +
2 = Neutral +
3 = Portrait +
4 = Landscape +
5 = B&W +
6 = Clear +
7 = Deep +
8 = Light +
9 = Sunset +
10 = Night View/Portrait +
11 = Autumn Leaves +
13 = Sepia +
15 = FL +
16 = VV2 +
17 = IN +
18 = SH +
255 = Off
+
82Sharpnessint8s 
90DistortionCorrParamsPresentint8u0 = No +
1 = Yes
91DistortionCorrectionint8u0 = None +
1 = Applied
93LensFormatint8u0 = Unknown +
1 = APS-C +
2 = Full-frame
94LensMountint8u0 = Unknown +
1 = A-mount +
2 = E-mount
96LensType2int16u--> Sony LensType2 Values +
(E-mount lenses)
98LensTypeint16u--> Sony LensType Values +
(A-mount lenses on SLT and NEX)
100DistortionCorrParamsint16s[16] 
834LensZoomPositionint16u 
842VignettingCorrParamsint16s[16] 
846LensZoomPositionint16u 
848VignettingCorrParamsint16s[16] 
858LensZoomPositionint16u 
860VignettingCorrParamsint16s[16] 
872VignettingCorrParamsint16s[16] 
892ChromaticAberrationCorrParamsint16s[32] 
900ChromaticAberrationCorrParamsint16s[32] 
924ChromaticAberrationCorrParamsint16s[32] 
944ChromaticAberrationCorrParamsint16s[32] 
952ChromaticAberrationCorrParamsint16s[32] 
+ +

Sony Tag9406 Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
5BatteryTemperature +
BatteryLevel
int8u
int8u
 
6BatteryLevelGrip1int8u 
7BatteryLevelint8u 
8BatteryLevelGrip2int8u 
+ +

Sony Tag940a Tags

+

These tags are currently extracted for SLT models only.

+
+
+ + + + + + + + +
Index1Tag NameWritableValues / Notes
4AFPointsSelectedint32u + +
0x0 = (none) +
0x7801 = Center Zone +
0x1821c = Right Zone +
0x3ffff = (all LA-EA4) +
0x605c0 = Left Zone +
0x7fffffff = (all) +
0xffffffff = n/a +
Bit 0 = Center +
Bit 1 = Top +
Bit 2 = Upper-right +
Bit 3 = Right +
Bit 4 = Lower-right +
Bit 5 = Bottom
  Bit 6 = Lower-left +
Bit 7 = Left +
Bit 8 = Upper-left +
Bit 9 = Far Right +
Bit 10 = Far Left +
Bit 11 = Upper-middle +
Bit 12 = Near Right +
Bit 13 = Lower-middle +
Bit 14 = Near Left +
Bit 15 = Upper Far Right +
Bit 16 = Lower Far Right +
Bit 17 = Lower Far Left +
Bit 18 = Upper Far Left
+
+ +

Sony Tag940c Tags

+

E-mount cameras only.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8LensMount2int8u0 = Unknown +
1 = A-mount (1) +
4 = E-mount +
5 = A-mount (5)
9LensType3int16u--> Sony LensType2 Values
11CameraE-mountVersionint16u 
13LensE-mountVersionint16u 
20LensFirmwareVersionint16u~ 
+ +

Sony AFInfo Tags

+

These tags are currently extracted for SLT models only.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
2AFTypeint8u1 = 15-point +
2 = 19-point +
3 = 79-point
4AFStatusActiveSensorint16s + +
-32768 = Out of Focus  0 = In Focus
+
5FocusModeint8u(ILCA models only) +
0 = Manual +
2 = AF-S +
3 = AF-C +
4 = AF-A +
6 = DMF
7AFPointint8u(models with 15-point AF) + +
0 = Upper-left +
1 = Left +
2 = Lower-left +
3 = Far Left +
4 = Top (horizontal) +
5 = Near Right +
6 = Center (horizontal) +
7 = Near Left +
8 = Bottom (horizontal)
  9 = Top (vertical) +
10 = Center (vertical) +
11 = Bottom (vertical) +
12 = Far Right +
13 = Upper-right +
14 = Right +
15 = Lower-right +
16 = Upper-middle +
17 = Lower-middle
+(models with 19-point AF) + +
0 = Upper Far Left +
1 = Upper-left (horizontal) +
2 = Far Left (horizontal) +
3 = Left (horizontal) +
4 = Lower Far Left +
5 = Lower-left (horizontal) +
6 = Upper-left (vertical) +
7 = Left (vertical) +
8 = Lower-left (vertical) +
9 = Far Left (vertical) +
10 = Top (horizontal) +
11 = Near Right +
12 = Center (horizontal) +
13 = Near Left +
14 = Bottom (horizontal)
  15 = Top (vertical) +
16 = Upper-middle +
17 = Center (vertical) +
18 = Lower-middle +
19 = Bottom (vertical) +
20 = Upper Far Right +
21 = Upper-right (horizontal) +
22 = Far Right (horizontal) +
23 = Right (horizontal) +
24 = Lower Far Right +
25 = Lower-right (horizontal) +
26 = Far Right (vertical) +
27 = Upper-right (vertical) +
28 = Right (vertical) +
29 = Lower-right (vertical)
+
8AFPointInFocusint8u(models with 15-point AF) + +
0 = Upper-left +
1 = Left +
2 = Lower-left +
3 = Far Left +
4 = Top (horizontal) +
5 = Near Right +
6 = Center (horizontal) +
7 = Near Left +
8 = Bottom (horizontal) +
9 = Top (vertical)
  10 = Center (vertical) +
11 = Bottom (vertical) +
12 = Far Right +
13 = Upper-right +
14 = Right +
15 = Lower-right +
16 = Upper-middle +
17 = Lower-middle +
255 = (none)
+(models with 19-point AF) + +
0 = Upper Far Left +
1 = Upper-left (horizontal) +
2 = Far Left (horizontal) +
3 = Left (horizontal) +
4 = Lower Far Left +
5 = Lower-left (horizontal) +
6 = Upper-left (vertical) +
7 = Left (vertical) +
8 = Lower-left (vertical) +
9 = Far Left (vertical) +
10 = Top (horizontal) +
11 = Near Right +
12 = Center (horizontal) +
13 = Near Left +
14 = Bottom (horizontal) +
15 = Top (vertical)
  16 = Upper-middle +
17 = Center (vertical) +
18 = Lower-middle +
19 = Bottom (vertical) +
20 = Upper Far Right +
21 = Upper-right (horizontal) +
22 = Far Right (horizontal) +
23 = Right (horizontal) +
24 = Lower Far Right +
25 = Lower-right (horizontal) +
26 = Far Right (vertical) +
27 = Upper-right (vertical) +
28 = Right (vertical) +
29 = Lower-right (vertical) +
255 = (none)
+
9AFPointAtShutterReleaseint8u(models with 15-point AF) + +
0 = Upper-left +
1 = Left +
2 = Lower-left +
3 = Far Left +
4 = Top (horizontal) +
5 = Near Right +
6 = Center (horizontal) +
7 = Near Left +
8 = Bottom (horizontal) +
9 = Top (vertical)
  10 = Center (vertical) +
11 = Bottom (vertical) +
12 = Far Right +
13 = Upper-right +
14 = Right +
15 = Lower-right +
16 = Upper-middle +
17 = Lower-middle +
30 = (out of focus)
+(models with 19-point AF) + +
0 = Upper Far Left +
1 = Upper-left (horizontal) +
2 = Far Left (horizontal) +
3 = Left (horizontal) +
4 = Lower Far Left +
5 = Lower-left (horizontal) +
6 = Upper-left (vertical) +
7 = Left (vertical) +
8 = Lower-left (vertical) +
9 = Far Left (vertical) +
10 = Top (horizontal) +
11 = Near Right +
12 = Center (horizontal) +
13 = Near Left +
14 = Bottom (horizontal) +
15 = Top (vertical)
  16 = Upper-middle +
17 = Center (vertical) +
18 = Lower-middle +
19 = Bottom (vertical) +
20 = Upper Far Right +
21 = Upper-right (horizontal) +
22 = Far Right (horizontal) +
23 = Right (horizontal) +
24 = Lower Far Right +
25 = Lower-right (horizontal) +
26 = Far Right (vertical) +
27 = Upper-right (vertical) +
28 = Right (vertical) +
29 = Lower-right (vertical) +
30 = (out of focus)
+
10AFAreaModeint8u0 = Wide +
1 = Spot +
2 = Local +
3 = Zone
11FocusModeint8u + +
0 = Manual +
2 = AF-S +
3 = AF-C
  4 = AF-A +
6 = DMF +
7 = AF-D
+
16AFPointsUsedint8u[10] + + +
0x0 = (none) +
Bit 0 = A5 +
Bit 1 = A6 +
Bit 2 = A7 +
Bit 3 = B2 +
Bit 4 = B3 +
Bit 5 = B4 +
Bit 6 = B5 +
Bit 7 = B6 +
Bit 8 = B7 +
Bit 9 = B8 +
Bit 10 = B9 +
Bit 11 = B10 +
Bit 12 = C1 +
Bit 13 = C2 +
Bit 14 = C3 +
Bit 15 = C4 +
Bit 16 = C5 +
Bit 17 = C6 +
Bit 18 = C7 +
Bit 19 = C8 +
Bit 20 = C9 +
Bit 21 = C10 +
Bit 22 = C11 +
Bit 23 = D1 +
Bit 24 = D2 +
Bit 25 = D3
  Bit 26 = D4 +
Bit 27 = D5 +
Bit 28 = D6 +
Bit 29 = D7 +
Bit 30 = D8 +
Bit 31 = D9 +
Bit 32 = D10 +
Bit 33 = D11 +
Bit 34 = E1 +
Bit 35 = E2 +
Bit 36 = E3 +
Bit 37 = E4 +
Bit 38 = E5 +
Bit 39 = E6 +
Bit 40 = E7 +
Bit 41 = E8 +
Bit 42 = E9 +
Bit 43 = E10 +
Bit 44 = E11 +
Bit 45 = F1 +
Bit 46 = F2 +
Bit 47 = F3 +
Bit 48 = F4 +
Bit 49 = F5 +
Bit 50 = F6 +
Bit 51 = F7 +
Bit 52 = F8
  Bit 53 = F9 +
Bit 54 = F10 +
Bit 55 = F11 +
Bit 56 = G1 +
Bit 57 = G2 +
Bit 58 = G3 +
Bit 59 = G4 +
Bit 60 = G5 +
Bit 61 = G6 +
Bit 62 = G7 +
Bit 63 = G8 +
Bit 64 = G9 +
Bit 65 = G10 +
Bit 66 = G11 +
Bit 67 = H2 +
Bit 68 = H3 +
Bit 69 = H4 +
Bit 70 = H5 +
Bit 71 = H6 +
Bit 72 = H7 +
Bit 73 = H8 +
Bit 74 = H9 +
Bit 75 = H10 +
Bit 76 = I5 +
Bit 77 = I6 +
Bit 78 = I7
+
17AFStatus15 +
AFStatus19
-
-
--> Sony AFStatus15 Tags +
--> Sony AFStatus19 Tags
55AFPointint8u + +
0 = B4 +
1 = C4 +
2 = D4 +
3 = E4 +
4 = F4 +
5 = G4 +
6 = H4 +
7 = B3 +
8 = C3 +
9 = D3 +
10 = E3 +
11 = F3 +
12 = G3 +
13 = H3 +
14 = B2 +
15 = C2 +
16 = D2 +
17 = E2 +
18 = F2 +
19 = G2 +
20 = H2 +
21 = C1 +
22 = D1 +
23 = E1 +
24 = F1 +
25 = G1 +
26 = A7 Vertical +
27 = A6 Vertical +
28 = A5 Vertical +
29 = C7 Vertical +
30 = C6 Vertical +
31 = C5 Vertical +
32 = E7 Vertical +
33 = E6 Center Vertical +
34 = E5 Vertical +
35 = G7 Vertical +
36 = G6 Vertical +
37 = G5 Vertical +
38 = I7 Vertical +
39 = I6 Vertical +
40 = I5 Vertical +
41 = A7 +
42 = B7 +
43 = C7 +
44 = D7 +
45 = E7 +
46 = F7 +
47 = G7
  48 = H7 +
49 = I7 +
50 = A6 +
51 = B6 +
52 = C6 +
53 = D6 +
54 = E6 Center +
55 = F6 +
56 = G6 +
57 = H6 +
58 = I6 +
59 = A5 +
60 = B5 +
61 = C5 +
62 = D5 +
63 = E5 +
64 = F5 +
65 = G5 +
66 = H5 +
67 = I5 +
68 = C11 +
69 = D11 +
70 = E11 +
71 = F11 +
72 = G11 +
73 = B10 +
74 = C10 +
75 = D10 +
76 = E10 +
77 = F10 +
78 = G10 +
79 = H10 +
80 = B9 +
81 = C9 +
82 = D9 +
83 = E9 +
84 = F9 +
85 = G9 +
86 = H9 +
87 = B8 +
88 = C8 +
89 = D8 +
90 = E8 +
91 = F8 +
92 = G8 +
93 = H8 +
94 = E6 Center F2.8 +
255 = (none)
+
56AFPointInFocusint8u + +
0 = B4 +
1 = C4 +
2 = D4 +
3 = E4 +
4 = F4 +
5 = G4 +
6 = H4 +
7 = B3 +
8 = C3 +
9 = D3 +
10 = E3 +
11 = F3 +
12 = G3 +
13 = H3 +
14 = B2 +
15 = C2 +
16 = D2 +
17 = E2 +
18 = F2 +
19 = G2 +
20 = H2 +
21 = C1 +
22 = D1 +
23 = E1 +
24 = F1 +
25 = G1 +
26 = A7 Vertical +
27 = A6 Vertical +
28 = A5 Vertical +
29 = C7 Vertical +
30 = C6 Vertical +
31 = C5 Vertical +
32 = E7 Vertical +
33 = E6 Center Vertical +
34 = E5 Vertical +
35 = G7 Vertical +
36 = G6 Vertical +
37 = G5 Vertical +
38 = I7 Vertical +
39 = I6 Vertical +
40 = I5 Vertical +
41 = A7 +
42 = B7 +
43 = C7 +
44 = D7 +
45 = E7 +
46 = F7 +
47 = G7
  48 = H7 +
49 = I7 +
50 = A6 +
51 = B6 +
52 = C6 +
53 = D6 +
54 = E6 Center +
55 = F6 +
56 = G6 +
57 = H6 +
58 = I6 +
59 = A5 +
60 = B5 +
61 = C5 +
62 = D5 +
63 = E5 +
64 = F5 +
65 = G5 +
66 = H5 +
67 = I5 +
68 = C11 +
69 = D11 +
70 = E11 +
71 = F11 +
72 = G11 +
73 = B10 +
74 = C10 +
75 = D10 +
76 = E10 +
77 = F10 +
78 = G10 +
79 = H10 +
80 = B9 +
81 = C9 +
82 = D9 +
83 = E9 +
84 = F9 +
85 = G9 +
86 = H9 +
87 = B8 +
88 = C8 +
89 = D8 +
90 = E8 +
91 = F8 +
92 = G8 +
93 = H8 +
94 = E6 Center F2.8 +
255 = (none)
+
57AFPointAtShutterReleaseint8u + +
0 = B4 +
1 = C4 +
2 = D4 +
3 = E4 +
4 = F4 +
5 = G4 +
6 = H4 +
7 = B3 +
8 = C3 +
9 = D3 +
10 = E3 +
11 = F3 +
12 = G3 +
13 = H3 +
14 = B2 +
15 = C2 +
16 = D2 +
17 = E2 +
18 = F2 +
19 = G2 +
20 = H2 +
21 = C1 +
22 = D1 +
23 = E1 +
24 = F1 +
25 = G1 +
26 = A7 Vertical +
27 = A6 Vertical +
28 = A5 Vertical +
29 = C7 Vertical +
30 = C6 Vertical +
31 = C5 Vertical +
32 = E7 Vertical +
33 = E6 Center Vertical +
34 = E5 Vertical +
35 = G7 Vertical +
36 = G6 Vertical +
37 = G5 Vertical +
38 = I7 Vertical +
39 = I6 Vertical +
40 = I5 Vertical +
41 = A7 +
42 = B7 +
43 = C7 +
44 = D7 +
45 = E7 +
46 = F7 +
47 = G7
  48 = H7 +
49 = I7 +
50 = A6 +
51 = B6 +
52 = C6 +
53 = D6 +
54 = E6 Center +
55 = F6 +
56 = G6 +
57 = H6 +
58 = I6 +
59 = A5 +
60 = B5 +
61 = C5 +
62 = D5 +
63 = E5 +
64 = F5 +
65 = G5 +
66 = H5 +
67 = I5 +
68 = C11 +
69 = D11 +
70 = E11 +
71 = F11 +
72 = G11 +
73 = B10 +
74 = C10 +
75 = D10 +
76 = E10 +
77 = F10 +
78 = G10 +
79 = H10 +
80 = B9 +
81 = C9 +
82 = D9 +
83 = E9 +
84 = F9 +
85 = G9 +
86 = H9 +
87 = B8 +
88 = C8 +
89 = D8 +
90 = E8 +
91 = F8 +
92 = G8 +
93 = H8 +
94 = E6 Center F2.8 +
95 = (none)
+
58AFAreaModeint8u0 = Wide +
1 = Center +
2 = Flexible Spot +
3 = Zone +
4 = Expanded Flexible Spot
59AFStatusActiveSensorint16s + +
-32768 = Out of Focus  0 = In Focus
+
67ExposureProgramint8u--> Sony ExposureProgram3 Values
80AFMicroAdjint8s 
125AFStatus79---> Sony AFStatus79 Tags
366AFPointsUsedint32u(SLT models only) + +
0x0 = (none) +
Bit 0 = Center +
Bit 1 = Top +
Bit 2 = Upper-right +
Bit 3 = Right +
Bit 4 = Lower-right +
Bit 5 = Bottom +
Bit 6 = Lower-left +
Bit 7 = Left +
Bit 8 = Upper-left
  Bit 9 = Far Right +
Bit 10 = Far Left +
Bit 11 = Upper-middle +
Bit 12 = Near Right +
Bit 13 = Lower-middle +
Bit 14 = Near Left +
Bit 15 = Upper Far Right +
Bit 16 = Lower Far Right +
Bit 17 = Lower Far Left +
Bit 18 = Upper Far Left
+
381AFMicroAdjint8s 
382ExposureProgramint8u--> Sony ExposureProgram3 Values
+ +

Sony AFStatus19 Tags

+

AF Status information for models with 19-point AF.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AFStatusUpperFarLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
2AFStatusUpper-leftHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
4AFStatusFarLeftHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
6AFStatusLeftHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
8AFStatusLowerFarLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
10AFStatusLower-leftHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
12AFStatusUpper-leftVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
14AFStatusLeftVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
16AFStatusLower-leftVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
18AFStatusFarLeftVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
20AFStatusTopHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
22AFStatusNearRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
24AFStatusCenterHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
26AFStatusNearLeftint16s + +
-32768 = Out of Focus  0 = In Focus
+
28AFStatusBottomHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
30AFStatusTopVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
32AFStatusUpper-middleint16s + +
-32768 = Out of Focus  0 = In Focus
+
34AFStatusCenterVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
36AFStatusLower-middleint16s + +
-32768 = Out of Focus  0 = In Focus
+
38AFStatusBottomVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
40AFStatusUpperFarRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
42AFStatusUpper-rightHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
44AFStatusFarRightHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
46AFStatusRightHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
48AFStatusLowerFarRightint16s + +
-32768 = Out of Focus  0 = In Focus
+
50AFStatusLower-rightHorizontalint16s + +
-32768 = Out of Focus  0 = In Focus
+
52AFStatusFarRightVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
54AFStatusUpper-rightVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
56AFStatusRightVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
58AFStatusLower-rightVerticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
+ +

Sony AFStatus79 Tags

+

AF Status information for models with 79-point AF.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0AFStatus_00_B4int16s + +
-32768 = Out of Focus  0 = In Focus
+
2AFStatus_01_C4int16s + +
-32768 = Out of Focus  0 = In Focus
+
4AFStatus_02_D4int16s + +
-32768 = Out of Focus  0 = In Focus
+
6AFStatus_03_E4int16s + +
-32768 = Out of Focus  0 = In Focus
+
8AFStatus_04_F4int16s + +
-32768 = Out of Focus  0 = In Focus
+
10AFStatus_05_G4int16s + +
-32768 = Out of Focus  0 = In Focus
+
12AFStatus_06_H4int16s + +
-32768 = Out of Focus  0 = In Focus
+
14AFStatus_07_B3int16s + +
-32768 = Out of Focus  0 = In Focus
+
16AFStatus_08_C3int16s + +
-32768 = Out of Focus  0 = In Focus
+
18AFStatus_09_D3int16s + +
-32768 = Out of Focus  0 = In Focus
+
20AFStatus_10_E3int16s + +
-32768 = Out of Focus  0 = In Focus
+
22AFStatus_11_F3int16s + +
-32768 = Out of Focus  0 = In Focus
+
24AFStatus_12_G3int16s + +
-32768 = Out of Focus  0 = In Focus
+
26AFStatus_13_H3int16s + +
-32768 = Out of Focus  0 = In Focus
+
28AFStatus_14_B2int16s + +
-32768 = Out of Focus  0 = In Focus
+
30AFStatus_15_C2int16s + +
-32768 = Out of Focus  0 = In Focus
+
32AFStatus_16_D2int16s + +
-32768 = Out of Focus  0 = In Focus
+
34AFStatus_17_E2int16s + +
-32768 = Out of Focus  0 = In Focus
+
36AFStatus_18_F2int16s + +
-32768 = Out of Focus  0 = In Focus
+
38AFStatus_19_G2int16s + +
-32768 = Out of Focus  0 = In Focus
+
40AFStatus_20_H2int16s + +
-32768 = Out of Focus  0 = In Focus
+
42AFStatus_21_C1int16s + +
-32768 = Out of Focus  0 = In Focus
+
44AFStatus_22_D1int16s + +
-32768 = Out of Focus  0 = In Focus
+
46AFStatus_23_E1int16s + +
-32768 = Out of Focus  0 = In Focus
+
48AFStatus_24_F1int16s + +
-32768 = Out of Focus  0 = In Focus
+
50AFStatus_25_G1int16s + +
-32768 = Out of Focus  0 = In Focus
+
52AFStatus_26_A7_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
54AFStatus_27_A6_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
56AFStatus_28_A5_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
58AFStatus_29_C7_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
60AFStatus_30_C6_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
62AFStatus_31_C5_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
64AFStatus_32_E7_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
66AFStatus_33_E6_Center_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
68AFStatus_34_E5_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
70AFStatus_35_G7_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
72AFStatus_36_G6_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
74AFStatus_37_G5_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
76AFStatus_38_I7_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
78AFStatus_39_I6_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
80AFStatus_40_I5_Verticalint16s + +
-32768 = Out of Focus  0 = In Focus
+
82AFStatus_41_A7int16s + +
-32768 = Out of Focus  0 = In Focus
+
84AFStatus_42_B7int16s + +
-32768 = Out of Focus  0 = In Focus
+
86AFStatus_43_C7int16s + +
-32768 = Out of Focus  0 = In Focus
+
88AFStatus_44_D7int16s + +
-32768 = Out of Focus  0 = In Focus
+
90AFStatus_45_E7int16s + +
-32768 = Out of Focus  0 = In Focus
+
92AFStatus_46_F7int16s + +
-32768 = Out of Focus  0 = In Focus
+
94AFStatus_47_G7int16s + +
-32768 = Out of Focus  0 = In Focus
+
96AFStatus_48_H7int16s + +
-32768 = Out of Focus  0 = In Focus
+
98AFStatus_49_I7int16s + +
-32768 = Out of Focus  0 = In Focus
+
100AFStatus_50_A6int16s + +
-32768 = Out of Focus  0 = In Focus
+
102AFStatus_51_B6int16s + +
-32768 = Out of Focus  0 = In Focus
+
104AFStatus_52_C6int16s + +
-32768 = Out of Focus  0 = In Focus
+
106AFStatus_53_D6int16s + +
-32768 = Out of Focus  0 = In Focus
+
108AFStatus_54_E6_Centerint16s + +
-32768 = Out of Focus  0 = In Focus
+
110AFStatus_55_F6int16s + +
-32768 = Out of Focus  0 = In Focus
+
112AFStatus_56_G6int16s + +
-32768 = Out of Focus  0 = In Focus
+
114AFStatus_57_H6int16s + +
-32768 = Out of Focus  0 = In Focus
+
116AFStatus_58_I6int16s + +
-32768 = Out of Focus  0 = In Focus
+
118AFStatus_59_A5int16s + +
-32768 = Out of Focus  0 = In Focus
+
120AFStatus_60_B5int16s + +
-32768 = Out of Focus  0 = In Focus
+
122AFStatus_61_C5int16s + +
-32768 = Out of Focus  0 = In Focus
+
124AFStatus_62_D5int16s + +
-32768 = Out of Focus  0 = In Focus
+
126AFStatus_63_E5int16s + +
-32768 = Out of Focus  0 = In Focus
+
128AFStatus_64_F5int16s + +
-32768 = Out of Focus  0 = In Focus
+
130AFStatus_65_G5int16s + +
-32768 = Out of Focus  0 = In Focus
+
132AFStatus_66_H5int16s + +
-32768 = Out of Focus  0 = In Focus
+
134AFStatus_67_I5int16s + +
-32768 = Out of Focus  0 = In Focus
+
136AFStatus_68_C11int16s + +
-32768 = Out of Focus  0 = In Focus
+
138AFStatus_69_D11int16s + +
-32768 = Out of Focus  0 = In Focus
+
140AFStatus_70_E11int16s + +
-32768 = Out of Focus  0 = In Focus
+
142AFStatus_71_F11int16s + +
-32768 = Out of Focus  0 = In Focus
+
144AFStatus_72_G11int16s + +
-32768 = Out of Focus  0 = In Focus
+
146AFStatus_73_B10int16s + +
-32768 = Out of Focus  0 = In Focus
+
148AFStatus_74_C10int16s + +
-32768 = Out of Focus  0 = In Focus
+
150AFStatus_75_D10int16s + +
-32768 = Out of Focus  0 = In Focus
+
152AFStatus_76_E10int16s + +
-32768 = Out of Focus  0 = In Focus
+
154AFStatus_77_F10int16s + +
-32768 = Out of Focus  0 = In Focus
+
156AFStatus_78_G10int16s + +
-32768 = Out of Focus  0 = In Focus
+
158AFStatus_79_H10int16s + +
-32768 = Out of Focus  0 = In Focus
+
160AFStatus_80_B9int16s + +
-32768 = Out of Focus  0 = In Focus
+
162AFStatus_81_C9int16s + +
-32768 = Out of Focus  0 = In Focus
+
164AFStatus_82_D9int16s + +
-32768 = Out of Focus  0 = In Focus
+
166AFStatus_83_E9int16s + +
-32768 = Out of Focus  0 = In Focus
+
168AFStatus_84_F9int16s + +
-32768 = Out of Focus  0 = In Focus
+
170AFStatus_85_G9int16s + +
-32768 = Out of Focus  0 = In Focus
+
172AFStatus_86_H9int16s + +
-32768 = Out of Focus  0 = In Focus
+
174AFStatus_87_B8int16s + +
-32768 = Out of Focus  0 = In Focus
+
176AFStatus_88_C8int16s + +
-32768 = Out of Focus  0 = In Focus
+
178AFStatus_89_D8int16s + +
-32768 = Out of Focus  0 = In Focus
+
180AFStatus_90_E8int16s + +
-32768 = Out of Focus  0 = In Focus
+
182AFStatus_91_F8int16s + +
-32768 = Out of Focus  0 = In Focus
+
184AFStatus_92_G8int16s + +
-32768 = Out of Focus  0 = In Focus
+
186AFStatus_93_H8int16s + +
-32768 = Out of Focus  0 = In Focus
+
188AFStatus_94_E6_Center_F2-8int16s + +
-32768 = Out of Focus  0 = In Focus
+
+ +

Sony Tag940e Tags

+

E-mount models.

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
6662TiffMeteringImageWidthno 
6663TiffMeteringImageHeightno 
6664TiffMeteringImageno(13(?)-bit intensity data from 1320 (1200) metering segments, extracted as a +16-bit TIFF image)
+ +

Sony Tag9416 Tags

+

Valid for the ILCE-1/6700/7M4/7RM5/7SM3, ILME-FX3/FX30, ZV-E1.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0Tag9416_0000no 
4SonyISOno 
6StopsAboveBaseISOno 
10SonyExposureTime2no 
12ExposureTimeno 
16SonyFNumber2no 
18SonyMaxApertureValueno 
29SequenceImageNumberno(number of images captured in burst sequence)
53ExposureProgramno--> Sony ExposureProgram3 Values
72LensMountno0 = Unknown +
1 = A-mount +
2 = E-mount +
3 = A-mount (3)
73LensFormatno0 = Unknown +
1 = APS-C +
2 = Full-frame
74LensMountno0 = Unknown +
1 = A-mount +
2 = E-mount
75LensType2no--> Sony LensType2 Values
77LensTypeno--> Sony LensType Values
79DistortionCorrParamsno 
112PictureProfileno +
0 = Gamma Still - Standard/Neutral (PP2) +
1 = Gamma Still - Portrait +
3 = Gamma Still - Night View/Portrait +
4 = Gamma Still - B&W/Sepia +
5 = Gamma Still - Clear +
6 = Gamma Still - Deep +
7 = Gamma Still - Light +
8 = Gamma Still - Vivid +
9 = Gamma Still - Real +
10 = Gamma Movie (PP1) +
22 = Gamma ITU709 (PP3 or PP4) +
24 = Gamma Cine1 (PP5) +
25 = Gamma Cine2 (PP6) +
26 = Gamma Cine3 +
27 = Gamma Cine4 +
28 = Gamma S-Log2 (PP7) +
29 = Gamma ITU709 (800%) +
31 = Gamma S-Log3 (PP8 or PP9) +
33 = Gamma HLG2 (PP10) +
34 = Gamma HLG3 +
36 = Off +
37 = FL +
38 = VV2 +
39 = IN +
40 = SH
+
113FocalLengthno 
115MinFocalLengthno 
117MaxFocalLengthno 
2191VignettingCorrParamsno 
2193VignettingCorrParamsno 
2205VignettingCorrParamsno 
2229APS-CSizeCaptureno0 = Off +
1 = On
2231APS-CSizeCaptureno0 = Off +
1 = On
2277APS-CSizeCaptureno0 = Off +
1 = On
2324ChromaticAberrationCorrParamsno 
2326ChromaticAberrationCorrParamsno 
2373ChromaticAberrationCorrParamsno 
+ +

Sony PIC Tags

+

The TextInfo data is extracted as a block to preserve the formatting, and +some of the more interesting information is extracted as separate tags.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'BC:'Barcodeno 
'BarCode:'Barcodeno 
'Capt:'SensorTemperatureno 
'FWVer:'FirmwareVersionno 
'IFD'PIC_IFD---> Sony Tags
'Temp:'CameraTemperatureno 
'Temp:Clbt:'BoardTemperatureno 
'TextInfo1'TextInfo1no 
'TextInfo2'TextInfo2no 
'VR Enable C:'VibrationReductionno0 = Off +
1 = On
'barcode:'Barcodeno 
+ +

Sony Ericsson Tags

+

Maker notes found in images from some Sony Ericsson phones.

+
+
+ + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0201PreviewImageStartint32u*(a small 320x200 preview image)
0x0202PreviewImageLengthint32u* 
0x2000MakerNoteVersionundef[4] 
+ +

Sony SRF Tags

+

The maker notes in SRF (Sony Raw Format) images contain 7 IFD's with family +1 group names SRF0 through SRF6. SRF0 and SRF1 use the tags in this table, +while SRF2 through SRF5 use the tags in the next table, and SRF6 uses +standard EXIF tags. All information other than SRF0 is encrypted, but +thanks to Dave Coffin the decryption algorithm is known. SRF images are +written by the Sony DSC-F828 and DSC-V3.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000SRF2Keyno(key to decrypt maker notes from the start of SRF2)
0x0001DataKeyno(key to decrypt the rest of the file from the end of the maker notes)
+ +

Sony SRF2 Tags

+

These tags are found in the SRF2 through SRF5 IFD's.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0002SRF6Offsetno 
0x0003SRFDataOffset?no 
0x0004RawDataOffsetno 
0x0005RawDataLengthno 
0x0043MaxApertureAtMaxFocalno 
0x0044MaxApertureAtMinFocalno 
0x0045MinFocalLengthno 
0x0046MaxFocalLengthno 
0x00c0WBRedDaylightno 
0x00c1WBGreenDaylightno 
0x00c2WBBlueDaylightno 
0x00c3WBRedCloudyno 
0x00c4WBGreenCloudyno 
0x00c5WBBlueCloudyno 
0x00c6WBRedFluorescentno 
0x00c7WBGreenFluorescentno 
0x00c8WBBlueFluorescentno 
0x00c9WBRedTungstenno 
0x00caWBGreenTungstenno 
0x00cbWBBlueTungstenno 
0x00ccWBRedFlashno 
0x00cdWBGreenFlashno 
0x00ceWBBlueFlashno 
0x00d0WBRedAsShotno 
0x00d1WBGreenAsShotno 
0x00d2WBBlueAsShotno 
+ +

Sony SR2Private Tags

+

The SR2 format uses the DNGPrivateData tag to reference a private IFD +containing these tags. SR2 images are written by the Sony DSC-R1, but +this information is also written to ARW images by other models.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x7200SR2SubIFDOffsetno 
0x7201SR2SubIFDLengthno 
0x7221SR2SubIFDKeyno(key to decrypt SR2SubIFD)
0x7240IDC_IFD---> SonyIDC Tags
0x7241IDC2_IFD---> SonyIDC Tags
0x7250MRWInfo---> MinoltaRaw Tags
+ +

Sony SR2SubIFD Tags

+

Tags in the encrypted SR2SubIFD

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x7300BlackLevelint16u[4]! 
0x7302WB_GRBGLevelsAutoint16s[4]! 
0x7303WB_GRBGLevelsint16s[4]! 
0x7310BlackLevelint16u[4]! 
0x7312WB_RGGBLevelsAutoint16s[4]! 
0x7313WB_RGGBLevelsint16s[4]! 
0x7480WB_RGBLevelsDaylightint16s[4]! 
0x7481WB_RGBLevelsCloudyint16s[4]! 
0x7482WB_RGBLevelsTungstenint16s[4]! 
0x7483WB_RGBLevelsFlashint16s[4]! 
0x7484WB_RGBLevels4500Kint16s[4]! 
0x7486WB_RGBLevelsFluorescentint16s[4]! 
0x74a0MaxApertureAtMaxFocalno 
0x74a1MaxApertureAtMinFocalno 
0x74a2MaxFocalLengthno 
0x74a3MinFocalLengthno 
0x74c0SR2DataIFD---> Sony SR2DataIFD Tags
0x7800ColorMatrixno 
0x7820WB_RGBLevelsDaylightint16s[3]! 
0x7821WB_RGBLevelsCloudyint16s[3]! 
0x7822WB_RGBLevelsTungstenint16s[3]! 
0x7823WB_RGBLevelsFlashint16s[3]! 
0x7824WB_RGBLevels4500Kint16s[3]! 
0x7825WB_RGBLevelsShadeint16s[3]! 
0x7826WB_RGBLevelsFluorescentint16s[3]! 
0x7827WB_RGBLevelsFluorescentP1int16s[3]! 
0x7828WB_RGBLevelsFluorescentP2int16s[3]! 
0x7829WB_RGBLevelsFluorescentM1int16s[3]! 
0x782aWB_RGBLevels8500Kint16s[3]! 
0x782bWB_RGBLevels6000Kint16s[3]! 
0x782cWB_RGBLevels3200Kint16s[3]! 
0x782dWB_RGBLevels2500Kint16s[3]! 
0x787fWhiteLevelint16u[3]! 
0x797dVignettingCorrParamsno 
0x7980ChromaticAberrationCorrParamsno 
0x7982DistortionCorrParamsno 
+ +

Sony SR2DataIFD Tags

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x7770ColorModeno 
+ +

Sony PMP Tags

+

These tags are written in the proprietary-format header of PMP images from +the DSC-F1.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
8JpgFromRawStartno(OK, not really a RAW file, but this mechanism is used to allow extraction of +the JPEG image from a PMP file)
12JpgFromRawLengthno 
22SonyImageWidthno 
24SonyImageHeightno 
27Orientationno0 = Horizontal (normal) +
1 = Rotate 270 CW +
2 = Rotate 180 +
3 = Rotate 90 CW
29ImageQualityno8 = Snap Shot +
23 = Standard +
51 = Fine
52Commentno 
76DateTimeOriginalno 
84ModifyDateno 
102ExposureTimeno 
106FNumberno 
108ExposureCompensationno 
112FocalLengthno 
118Flashno0 = No Flash +
1 = Fired
+ +

Sony rtmd Tags

+

These tags are extracted from the 'rtmd' timed metadata of MP4 videos from +some models when the ExtractEmbedded option is used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x8000FNumberno 
0x8109ExposureTimeno 
0x810aMasterGainAdjustmentno 
0x810bISOno 
0x810cElectricalExtenderMagnificationno 
0x8500GPSVersionIDno 
0x8501GPSLatitudeRefno'N' = North +
'S' = South
0x8502GPSLatitudeno 
0x8503GPSLongitudeRefno'E' = East +
'W' = West
0x8504GPSLongitudeno 
0x8507GPSTimeStampno 
0x8509GPSStatusno'A' = Measurement Active +
'V' = Measurement Void
0x850aGPSMeasureModeno2 = 2-Dimensional Measurement +
3 = 3-Dimensional Measurement
0x8512GPSMapDatumno 
0x851dGPSDateStampno 
0xe303WhiteBalanceno + +
1 = Incandescent +
2 = Fluorescent +
4 = Daylight
  5 = Cloudy +
6 = Custom +
255 = Preset
+
0xe304DateTimeno 
0xe43bPitchRollYawno 
0xe44bAccelerometerno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 10, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/SonyIDC.html b/ExifTool/html/TagNames/SonyIDC.html new file mode 100644 index 0000000..c277dc7 --- /dev/null +++ b/ExifTool/html/TagNames/SonyIDC.html @@ -0,0 +1,339 @@ + + + + +SonyIDC Tags + + + +

SonyIDC Tags

+

Tags written by the Sony Image Data Converter utility in ARW images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0201IDCPreviewStartint32u* 
0x0202IDCPreviewLengthint32u* 
0x8000IDCCreativeStyleint32u + +
1 = Camera Setting +
2 = Standard +
3 = Real +
4 = Vivid +
5 = Adobe RGB +
6 = A100 Standard +
7 = Neutral +
8 = Portrait +
9 = Landscape
  10 = Clear +
11 = Deep +
12 = Light +
13 = Sunset +
14 = Night View +
15 = Autumn Leaves +
16 = B&W +
17 = Sepia
+
0x8001CreativeStyleWasChangedint32u(set if the creative style was ever changed) +
0 = No +
1 = Yes
0x8002PresetWhiteBalanceint32u +
1 = Camera Setting +
2 = Color Temperature +
3 = Specify Gray Point +
4 = Daylight +
5 = Cloudy +
6 = Shade +
7 = Cool White Fluorescent +
8 = Day Light Fluorescent +
9 = Day White Fluorescent +
10 = Warm White Fluorescent +
11 = Tungsten +
12 = Flash +
13 = Auto
+
0x8013ColorTemperatureAdjint16u 
0x8014PresetWhiteBalanceAdjint32s 
0x8015ColorCorrectionint32s 
0x8016SaturationAdjint32s 
0x8017ContrastAdjint32s 
0x8018BrightnessAdjint32s 
0x8019HueAdjint32s 
0x801aSharpnessAdjint32s 
0x801bSharpnessOvershootint32s 
0x801cSharpnessUndershootint32s 
0x801dSharpnessThresholdint32s 
0x801eNoiseReductionModeint16u0 = Off +
1 = On
0x8021GrayPointint16u[4] 
0x8022D-RangeOptimizerModeint16u0 = Off +
1 = Auto +
2 = Manual
0x8023D-RangeOptimizerValueint32s 
0x8024D-RangeOptimizerHighlightint32s 
0x8026HighlightColorDistortReductint16u0 = Standard +
1 = Advanced
0x8027NoiseReductionValueint32s 
0x8028EdgeNoiseReductionint32s 
0x8029ColorNoiseReductionint32s 
0x802dD-RangeOptimizerShadowint32s 
0x8030PeripheralIllumCentralRadiusint32s 
0x8031PeripheralIllumCentralValueint32s 
0x8032PeripheralIllumPeriphValueint32s 
0x8040DistortionCompensationint32s-1 = n/a +
1 = On +
2 = Off
0x9000ToneCurveBrightnessXint16u[n] 
0x9001ToneCurveRedXint16u[n] 
0x9002ToneCurveGreenXint16u[n] 
0x9003ToneCurveBlueXint16u[n] 
0x9004ToneCurveBrightnessYint16u[n] 
0x9005ToneCurveRedYint16u[n] 
0x9006ToneCurveGreenYint16u[n] 
0x9007ToneCurveBlueYint16u[n] 
0x900dChromaticAberrationCorrectionint32s1 = On +
2 = Off
0x900eInclinationCorrectionint32u0 = Off +
1 = On
0x900fInclinationAngleint32s 
0x9010Croppingint32u0 = Off +
1 = On
0x9011CropAreaint32u[4] 
0x9012PreviewImageSizeint32u[2] 
0x9013PxShiftPeriphEdgeNRint32s0 = Off +
1 = On
0x9014PxShiftPeriphEdgeNRValueint32s 
0x9017WhitesAdjint32s 
0x9018BlacksAdjint32s 
0x9019HighlightsAdjint32s 
0x901aShadowsAdjint32s 
0xd000CurrentVersionint32u 
0xd001VersionIFD---> SonyIDC Tags +
(there is one VersionIFD for each entry in the "Version Stack")
0xd100VersionCreateDatestring(date/time when this entry was created in the "Version Stack")
0xd101VersionModifyDatestring 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 21, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Stim.html b/ExifTool/html/TagNames/Stim.html new file mode 100644 index 0000000..480504d --- /dev/null +++ b/ExifTool/html/TagNames/Stim.html @@ -0,0 +1,201 @@ + + + + +Stim Tags + + + +

Stim Tags

+

These tags are part of the CIPA Stereo Still Image specification, and are +found in the APP3 "Stim" segment of JPEG images. See +https://web.archive.org/web/20190718152459/http://www.cipa.jp/std/documents/e/DC-006_E.pdf +for the official specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0000StimVersionno 
0x0001ApplicationDatano 
0x0002ImageArrangementno0 = Parallel View Alignment +
1 = Cross View Alignment
0x0003ImageRotationno1 = None
0x0004ScalingFactorno 
0x0005CropXSizeno 
0x0006CropYSizeno 
0x0007CropX---> Stim CropX Tags
0x0008CropY---> Stim CropY Tags
0x0009ViewTypeno0 = No Pop-up Effect +
1 = Pop-up Effect
0x000aRepresentativeImageno0 = Left Viewpoint +
1 = Right Viewpoint
0x000bConvergenceBaseImageno0 = Left Viewpoint +
1 = Right Viewpoint +
255 = Equivalent for Both Viewpoints
0x000cAssumedDisplaySizeno 
0x000dAssumedDistanceViewno 
0x000eRepresentativeDisparityNearno 
0x000fRepresentativeDisparityFarno 
0x0010InitialDisplayEffectno0 = Off +
1 = On
0x0011ConvergenceDistanceno 
0x0012CameraArrangementIntervalno 
0x0013ShootingCountno 
+ +

Stim CropX Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0CropXCommonOffsetno0 = Common Offset Setting +
1 = Individual Offset Setting
2CropXViewpointNumberno 
3CropXOffsetno 
7CropXViewpointNumber2no 
8CropXOffset2no 
+ +

Stim CropY Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0CropYCommonOffsetno0 = Common Offset Setting +
1 = Individual Offset Setting
2CropYViewpointNumberno 
3CropYOffsetno 
7CropYViewpointNumber2no 
8CropYOffset2no 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Nov 27, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Text.html b/ExifTool/html/TagNames/Text.html new file mode 100644 index 0000000..f6c0b74 --- /dev/null +++ b/ExifTool/html/TagNames/Text.html @@ -0,0 +1,76 @@ + + + + +Text Tags + + + +

Text Tags

+

Although basic text files contain no metadata, the following tags are +determined from a simple analysis of the data in TXT and CSV files. +Statistics are generated only for 8-bit encodings, but the FastScan (-fast) +option may be used to limit processing to the first 64 kB in which case some +tags are not produced. To avoid long processing delays, ExifTool will issue +a minor warning and process only the first 64 kB of any file larger than 20 +MB unless the IgnoreMinorErrors (-m) +option is used.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ByteOrderMarkno0 = No +
1 = Yes
ColumnCountno 
Delimiterno'' = (none) +
"\x09" = Tab +
',' = Comma +
';' = Semicolon
LineCountno 
MIMEEncodingno 
Newlinesno'' = (none) +
"\x0a" = Unix LF +
"\x0d" = Macintosh CR +
"\x0d\x0a" = Windows CRLF
Quotingno'' = (none) +
'"' = Double quotes +
''' = Single quotes
RowCountno 
WordCountno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 14, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Theora.html b/ExifTool/html/TagNames/Theora.html new file mode 100644 index 0000000..7353c42 --- /dev/null +++ b/ExifTool/html/TagNames/Theora.html @@ -0,0 +1,103 @@ + + + + +Theora Tags + + + +

Theora Tags

+

Information extracted from Ogg Theora video files. See +http://www.theora.org/doc/Theora.pdf for the Theora specification.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0080Identification---> Theora Identification Tags
0x0081Comments---> Vorbis Comments Tags
+ +

Theora Identification Tags

+

Tags extracted from the Theora identification header.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0TheoraVersionno 
7ImageWidthno 
10ImageHeightno 
13XOffsetno 
14YOffsetno 
15FrameRateno 
23PixelAspectRationo 
29ColorSpaceno0 = Undefined +
1 = Rec. 470M +
2 = Rec. 470BG
30NominalVideoBitrateno0 = Unspecified
33Qualityno 
34PixelFormatno0 = 4:2:0 +
2 = 4:2:2 +
3 = 4:4:4
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 16, 2011 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Torrent.html b/ExifTool/html/TagNames/Torrent.html new file mode 100644 index 0000000..1209465 --- /dev/null +++ b/ExifTool/html/TagNames/Torrent.html @@ -0,0 +1,189 @@ + + + + +Torrent Tags + + + +

Torrent Tags

+

Below are tags commonly found in BitTorrent files. As well as these tags, +any other existing tags will be extracted. For convenience, list items are +expanded into individual tags with an index in the tag name, but only the +tags with index "1" are listed in the tables below. See +https://wiki.theory.org/BitTorrentSpecification for the BitTorrent +specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'announce'Announceno 
'announce-list'AnnounceList1no 
'comment'Commentno 
'created by'Creatorno 
'creation date'CreateDateno 
'encoding'Encodingno 
'info'Info---> Torrent Info Tags +
(extracted as a structure with the Struct option)
'url-list'URLList1no 
+ +

Torrent Info Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'file-duration'File1Durationno 
'file-media'File1Mediano 
'files'Files---> Torrent Files Tags
'length'Lengthno 
'md5sum'MD5Sumno 
'name'Nameno 
'name.utf-8'NameUTF-8no 
'piece length'PieceLengthno 
'pieces'Piecesno(concatenation of 20-byte SHA-1 digests for each piece)
'private'Privateno 
'profiles'Profiles---> Torrent Profiles Tags
+ +

Torrent Files Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'length'File1Lengthno 
'md5sum'File1MD5Sumno 
'path'File1Pathno 
'path.utf-8'File1PathUTF-8no 
+ +

Torrent Profiles Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'acodec'Profile1AudioCodecno 
'height'Profile1Heightno 
'vcodec'Profile1VideoCodecno 
'width'Profile1Widthno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 24, 2019 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Unknown.html b/ExifTool/html/TagNames/Unknown.html new file mode 100644 index 0000000..7edcad6 --- /dev/null +++ b/ExifTool/html/TagNames/Unknown.html @@ -0,0 +1,32 @@ + + + + +Unknown Tags + + + +

Unknown Tags

+

+The following tags are decoded in unsupported maker notes. Use the Unknown +(-u) option to display other unknown tags. +

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0e00PrintIM---> PrintIM Tags
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Apr 11, 2006 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/VCard.html b/ExifTool/html/TagNames/VCard.html new file mode 100644 index 0000000..6d4ed36 --- /dev/null +++ b/ExifTool/html/TagNames/VCard.html @@ -0,0 +1,558 @@ + + + + +VCard Tags + + + +

VCard Tags

+

This table lists common vCard tags, but ExifTool will also extract any other +vCard tags found. Tag names may have "Pref" added to indicate the preferred +instance of a vCard property, and other "TYPE" parameters may also added to +the tag name. VCF files may contain multiple vCard entries which are +distinguished by the ExifTool family 3 group name (document number). See +http://tools.ietf.org/html/rfc6350 for the vCard 4.0 specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Adr'Addressno 
'Anniversary'Anniversaryno 
'Bday'Birthdayno 
'Email'Emailno 
'Fn'FormattedNameno 
'Gender'Genderno 
'Geo'Geolocationno 
'Impp'IMPPno 
'Lang'Languageno 
'Logo'Logono 
'N'Nameno 
'Nickname'Nicknameno 
'Note'Noteno 
'Org'Organizationno 
'Photo'Photono 
'Prodid'Softwareno 
'Rev'Revisionno 
'Sound'Soundno 
'Tel'Telephoneno 
'Title'JobTitleno 
'Tz'TimeZoneno 
'Uid'UIDno 
'Url'URLno 
'Version'VCardVersionno 
'X-abdate'ABDateno 
'X-ablabel'ABLabelno 
'X-abrelatednames'ABRelatedNamesno 
'X-abuid'AB_UIDno 
'X-aim'AIMno 
'X-icq'ICQno 
'X-socialprofile'SocialProfileno 
+ +

VCard VCalendar Tags

+

The VCard module is also used to process iCalendar ICS files since they use +a format similar to vCard. The following table lists standard iCalendar +tags, but any existing tags will be extracted. Top-level iCalendar +components (eg. Event, Todo, Timezone, etc.) are used for the family 1 group +names, and embedded components (eg. Alarm) are added as a prefix to the tag +name. See http://tools.ietf.org/html/rfc5545 for the official iCalendar +2.0 specification.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Acknowledged'Acknowledgedno 
'Action'Actionno 
'Attach'Attachmentno 
'Attendee'Attendeeno 
'Calscale'CalendarScaleno 
'Categories'Categoriesno 
'Class'Classificationno 
'Comment'Commentno 
'Completed'DateTimeCompletedno 
'Contact'Contactno 
'Created'DateCreatedno 
'Description'Descriptionno 
'Dtend'DateTimeEndno 
'Dtstamp'DateTimeStampno 
'Dtstart'DateTimeStartno 
'Due'DateTimeDueno 
'Duration'Durationno 
'Exdate'ExceptionDateTimesno 
'Freebusy'FreeBusyTimeno 
'Geo'Geolocationno 
'Last-modified'ModifyDateno 
'Location'Locationno 
'Method'Methodno 
'Organizer'Organizerno 
'Percent-complete'PercentCompleteno 
'Priority'Priorityno 
'Prodid'Softwareno 
'Rdate'RecurrenceDateTimesno 
'Recurrence-id'RecurrenceIDno 
'Related-to'RelatedTono 
'Repeat'Repeatno 
'Request-status'RequestStatusno 
'Resources'Resourcesno 
'Rrule'RecurrenceRuleno 
'Sequence'SequenceNumberno 
'Status'Statusno 
'Summary'Summaryno 
'Transp'TimeTransparencyno 
'Trigger'Triggerno 
'Tzid'TimezoneIDno 
'Tzname'TimezoneNameno 
'Tzoffsetfrom'TimezoneOffsetFromno 
'Tzoffsetto'TimezoneOffsetTono 
'Tzurl'TimeZoneURLno 
'Uid'UIDno 
'Url'URLno 
'Version'VCalendarVersionno 
'X-apple-calendar-color'CalendarColorno 
'X-apple-default-alarm'DefaultAlarmno 
'X-apple-local-default-alarm'LocalDefaultAlarmno 
'X-microsoft-cdo-alldayevent'AllDayEventno 
'X-microsoft-cdo-appt-sequence'AppointmentSequenceno 
'X-microsoft-cdo-busystatus'BusyStatusno 
'X-microsoft-cdo-importance'Importanceno0 = Low +
1 = Normal +
2 = High
'X-microsoft-cdo-insttype'InstanceTypeno0 = Non-recurring Appointment +
1 = Recurring Appointment +
2 = Single Instance of Recurring Appointment +
3 = Exception to Recurring Appointment
'X-microsoft-cdo-intendedstatus'IntendedBusyStatusno 
'X-microsoft-cdo-ownerapptid'OwnerAppointmentIDno 
'X-microsoft-disallow-counter'DisallowCounterProposalno 
'X-microsoft-donotforwardmeeting'DoNotForwardMeetingno 
'X-microsoft-locations'MeetingLocationsno 
'X-wr-alarmuid'AlarmUIDno 
'X-wr-caldesc'CalendarDescriptionno 
'X-wr-calname'CalendarNameno 
'X-wr-relcalid'CalendarIDno 
'X-wr-timezone'TimeZone2no 
+ +

VCard VNote Tags

+

Tags extracted from V-Note VNT files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'Body'Bodyno 
'Dcreated'CreateDateno 
'Last-modified'ModifyDateno 
'Version'Versionno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Feb 9, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/Vorbis.html b/ExifTool/html/TagNames/Vorbis.html new file mode 100644 index 0000000..2b7ff8b --- /dev/null +++ b/ExifTool/html/TagNames/Vorbis.html @@ -0,0 +1,244 @@ + + + + +Vorbis Tags + + + +

Vorbis Tags

+

Information extracted from Ogg Vorbis files. See +http://www.xiph.org/vorbis/doc/ for the Vorbis specification.

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
0x0001Identification---> Vorbis Identification Tags
0x0003Comments---> Vorbis Comments Tags
+ +

Vorbis Comments Tags

+

The tags below are only some common tags found in the Vorbis comments of Ogg +Vorbis and Ogg FLAC audio files, however ExifTool will extract values from +any tag found, even if not listed here.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'ACTOR'Actorno 
'ALBUM'Albumno 
'ARTIST'Artistno+ 
'COMMENT'Commentno 
'COMPOSER'Composerno 
'CONTACT'Contactno+ 
'COPYRIGHT'Copyrightno 
'COVERART'CoverArtno(base64-encoded image)
'COVERARTMIME'CoverArtMIMETypeno 
'DATE'Dateno 
'DESCRIPTION'Descriptionno 
'DIRECTOR'Directorno 
'ENCODED_BY'EncodedByno 
'ENCODED_USING'EncodedUsingno 
'ENCODER'Encoderno 
'ENCODER_OPTIONS'EncoderOptionsno 
'GENRE'Genreno 
'ISRC'ISRCNumberno 
'LICENSE'Licenseno 
'LOCATION'Locationno 
'METADATA_BLOCK_PICTURE'Picture---> FLAC Picture Tags
'ORGANIZATION'Organizationno 
'PERFORMER'Performerno+ 
'PRODUCER'Producerno 
'REPLAYGAIN_ALBUM_GAIN'ReplayGainAlbumGainno 
'REPLAYGAIN_ALBUM_PEAK'ReplayGainAlbumPeakno 
'REPLAYGAIN_TRACK_GAIN'ReplayGainTrackGainno 
'REPLAYGAIN_TRACK_PEAK'ReplayGainTrackPeakno 
'TITLE'Titleno 
'TRACKNUMBER'TrackNumberno 
'VERSION'Versionno 
'vendor'Vendorno(from comment header)
+ +

Vorbis Identification Tags

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0VorbisVersionno 
4AudioChannelsno 
5SampleRateno 
9MaximumBitrateno 
13NominalBitrateno 
17MinimumBitrateno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jul 14, 2016 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/WPG.html b/ExifTool/html/TagNames/WPG.html new file mode 100644 index 0000000..9af8945 --- /dev/null +++ b/ExifTool/html/TagNames/WPG.html @@ -0,0 +1,132 @@ + + + + +WPG Tags + + + +

WPG Tags

+

Tags extracted from WordPerfect Graphics (WPG) images.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ImageHeightInchesno 
ImageWidthInchesno 
Recordsno+(records for version 1.0 files) + +
0x01 = Fill Attributes +
0x02 = Line Attributes +
0x03 = Marker Attributes +
0x04 = Polymarker +
0x05 = Line +
0x06 = Polyline +
0x07 = Rectangle +
0x08 = Polygon +
0x09 = Ellipse +
0x0a = Reserved +
0x0b = Bitmap (Type 1) +
0x0c = Graphics Text (Type 1) +
0x0d = Graphics Text Attributes +
0x0e = Color Map
  0x0f = Start WPG (Type 1) +
0x10 = End WPG +
0x11 = PostScript Data (Type 1) +
0x12 = Output Attributes +
0x13 = Curved Polyline +
0x14 = Bitmap (Type 2) +
0x15 = Start Figure +
0x16 = Start Chart +
0x17 = PlanPerfect Data +
0x18 = Graphics Text (Type 2) +
0x19 = Start WPG (Type 2) +
0x1a = Graphics Text (Type 3) +
0x1b = PostScript Data (Type 2)
+
RecordsV2no+(records for version 2.0 files) + +
0x00 = End Marker +
0x01 = Start WPG +
0x02 = End WPG +
0x03 = Form Settings +
0x04 = Ruler Settings +
0x05 = Grid Settings +
0x06 = Layer +
0x08 = Pen Style Definition +
0x09 = Pattern Definition +
0x0a = Comment +
0x0b = Color Transfer +
0x0c = Color Palette +
0x0d = DP Color Palette +
0x0e = Bitmap Data +
0x0f = Text Data +
0x10 = Chart Style +
0x11 = Chart Data +
0x12 = Object Image +
0x15 = Polyline +
0x16 = Polyspline +
0x17 = Polycurve +
0x18 = Rectangle +
0x19 = Arc +
0x1a = Compound Polygon +
0x1b = Bitmap +
0x1c = Text Line +
0x1d = Text Block +
0x1e = Text Path +
0x1f = Chart +
0x20 = Group
  0x21 = Object Capsule +
0x22 = Font Settings +
0x25 = Pen Fore Color +
0x26 = DP Pen Fore Color +
0x27 = Pen Back Color +
0x28 = DP Pen Back Color +
0x29 = Pen Style +
0x2a = Pen Pattern +
0x2b = Pen Size +
0x2c = DP Pen Size +
0x2d = Line Cap +
0x2e = Line Join +
0x2f = Brush Gradient +
0x30 = DP Brush Gradient +
0x31 = Brush Fore Color +
0x32 = DP Brush Fore Color +
0x33 = Brush Back Color +
0x34 = DP Brush Back Color +
0x35 = Brush Pattern +
0x36 = Horizontal Line +
0x37 = Vertical Line +
0x38 = Poster Settings +
0x39 = Image State +
0x3a = Envelope Definition +
0x3b = Envelope +
0x3c = Texture Definition +
0x3d = Brush Texture +
0x3e = Texture Alignment +
0x3f = Pen Texture
+
WPGVersionno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised May 3, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/WTV.html b/ExifTool/html/TagNames/WTV.html new file mode 100644 index 0000000..fb260f1 --- /dev/null +++ b/ExifTool/html/TagNames/WTV.html @@ -0,0 +1,381 @@ + + + + +WTV Tags + + + +

WTV Tags

+

Tags found in Windows recorded TV (WTV) videos.

+
+
+ + + + + + + + +
Tag IDTag NameWritableValues / Notes
'table.0.entries.legacy_attrib'Metdata---> WTV Metadata Tags
+ +

WTV Metadata Tags

+

ExifTool will extract any tag found, even if not in this table.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ATSCContentno + +
0 = No  1 = Yes
+
ActualSoftPostPaddingno 
ActualSoftPrePaddingno 
Bitrate?no(unknown units)
BrandingImageIDno 
BrandingNameno 
ContentProtectedno + +
0 = No  1 = Yes
+
ContentProtectedPercentno 
DTVContentno + +
0 = No  1 = Yes
+
Durationno 
EncodeTimeno 
EndTimeno 
ExpirationDate?no 
ExpirationSpan?no(unknown units)
Genreno 
HDContentno + +
0 = No  1 = Yes
+
HardPostPaddingno 
HardPrePaddingno 
InBandRatingAttributesno 
InBandRatingLevelno 
InBandRatingSystemno 
KeepUntilno 
Languageno 
MediaClassPrimaryIDno 
MediaClassSecondaryIDno 
MediaCreditsno 
MediaIsDelayno + +
0 = No  1 = Yes
+
MediaIsFinaleno + +
0 = No  1 = Yes
+
MediaIsLiveno + +
0 = No  1 = Yes
+
MediaIsMovieno + +
0 = No  1 = Yes
+
MediaIsPremiereno + +
0 = No  1 = Yes
+
MediaIsRepeatno + +
0 = No  1 = Yes
+
MediaIsSAPno + +
0 = No  1 = Yes
+
MediaIsSportno + +
0 = No  1 = Yes
+
MediaIsStereono + +
0 = No  1 = Yes
+
MediaIsSubtitledno + +
0 = No  1 = Yes
+
MediaIsTapeno + +
0 = No  1 = Yes
+
MediaNetworkAffiliationno 
MediaOriginalBroadcastDateTimeno 
MediaOriginalChannelno 
MediaOriginalChannelSubNumberno 
MediaOriginalRunTimeno 
MediaStationCallSignno 
MediaStationNameno 
MediaThumbAspectRatioXno 
MediaThumbAspectRatioYno 
MediaThumbHeightno 
MediaThumbRatingAttributesno 
MediaThumbRatingLevelno 
MediaThumbRatingSystemno 
MediaThumbRetno 
MediaThumbStrideno 
MediaThumbTimeStamp?no(unknown units)
MediaThumbWidthno 
OriginalReleaseTimeno 
OriginalSoftPostPaddingno 
OriginalSoftPrePaddingno 
ParentalRatingno 
ParentalRatingReasonno 
ProgramIDno 
Providerno 
ProviderCopyrightno 
ProviderRatingno 
Qualityno 
RequestIDno 
ScheduleItemIDno 
SeriesUIDno 
ServiceIDno 
Subtitleno 
SubtitleDescriptionno 
Titleno 
VideoClosedCaptioningno + +
0 = No  1 = Yes
+
Watchedno + +
0 = No  1 = Yes
+
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 5, 2018 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/XMP.html b/ExifTool/html/TagNames/XMP.html new file mode 100644 index 0000000..f1b63b8 --- /dev/null +++ b/ExifTool/html/TagNames/XMP.html @@ -0,0 +1,20009 @@ + + + + +XMP Tags + + + +

XMP Tags

+

+XMP stands for "Extensible Metadata Platform", an XML/RDF-based metadata +format which is being pushed by Adobe. Information in this format can be +embedded in many different image file types including JPG, JP2, TIFF, GIF, +EPS, PDF, PSD, IND, INX, PNG, DJVU, SVG, PGF, MIFF, XCF, CRW, DNG and a +variety of proprietary TIFF-based RAW images, as well as MOV, AVI, ASF, WMV, +FLV, SWF and MP4 videos, and WMA and audio formats supporting ID3v2 +information.

+ +

The XMP Tag ID's aren't listed because in most cases they are identical +to the Tag Name (aside from differences in case). Tags with different +ID's are mentioned in the Notes column of the HTML version of this +document.

+ +

All XMP information is stored as character strings. The Writable column +specifies the information format: string is an unformatted string, +integer is a string of digits (possibly beginning with a '+' or '-'), +real is a floating point number, rational is entered as a floating +point number but stored as two integer strings separated by a '/' +character, date is a date/time string entered in the format "YYYY:mm:dd +HH:MM:SS[.ss][+/-HH:MM]" but some partial date/time formats are also +accepted (see faq.html#Q5), boolean is either +"True" or "False" (but "true" and "false" may be written as a ValueConv +value for compatibility with non-conforming applications), struct +indicates a structured tag, and lang-alt is a tag that supports alternate +languages.

+ +

When reading, struct tags are extracted only if the Struct (-struct) +option is used. Otherwise the corresponding Flattened tags, indicated by +an underline (_) after the Writable type, are extracted. When +copying, by default both structured and flattened tags are available, but +the flattened tags are considered "unsafe" so they aren't copied unless +specified explicitly. The Struct option may be disabled by setting Struct +to 0 via the API or with --struct on the command line to copy only flattened +tags, or enabled by setting Struct to 1 via the API or with -struct on the +command line to copy only as structures. When writing, the Struct option +has no effect, and both structured and flattened tags may be written. See +struct.html for more details.

+ +

Individual languages for lang-alt tags are accessed by suffixing the tag +name with a '-', followed by an RFC 3066 language code (eg. "XMP:Title-fr", +or "Rights-en-US"). (See http://www.ietf.org/rfc/rfc3066.txt for the RFC +3066 specification.) A lang-alt tag with no language code accesses the +"x-default" language, but causes other languages for this tag to be deleted +when writing. The "x-default" language code may be specified when writing +to preserve other existing languages (eg. "XMP-dc:Description-x-default"). +When reading, "x-default" is not specified.

+ +

The XMP tags are organized according to schema Namespace in the following +tables. The ExifTool family 1 group names are derived from the namespace +prefixes by adding a leading "XMP-" (eg. "XMP-dc"). A few of the longer +prefixes have been shortened (as mentioned in the documentation below) to +avoid excessively long ExifTool group names. The tags of any namespace may +be deleted as a group by specifying the family 1 group name (eg. +"-XMP-dc:all=" on the command line). This includes namespaces which are not +pre-defined by ExifTool.

+ +

In cases where a tag name exists in more than one namespace, less common +namespaces are avoided when writing. However, a specific namespace may be +written by providing a family 1 group name for the tag (eg. XMP-crs:Contrast +or XMP-exif:Contrast). When deciding on which tags to add to an image, +using standard schemas such as dc, xmp, +iptcCore and iptcExt is +recommended if possible.

+ +

For structures, the heading of the first column is Field Name. Field +names are very similar to tag names, except they are used to identify fields +inside structures instead of stand-alone tags. See +the Field Name section of the Structured Information documentation for more +details.

+ +

ExifTool will extract XMP information even if it is not listed in these +tables, but other tags are not writable unless added as user-defined tags in +the ExifTool config file. For example, the pdfx namespace doesn't have a +predefined set of tag names because it is used to store application-defined +PDF information, so although this information will be extracted, it is only +writable if the corresponding user-defined tags have been created.

+ +

The tables below list tags from the official XMP specification (with an +underlined Namespace in the HTML version of this documentation), as well +as extensions from various other sources. See +http://www.adobe.com/devnet/xmp/ for the official XMP specification. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NamespaceWritableValues / Notes
aas---> XMP aas Tags
acdsee---> XMP acdsee Tags
album---> XMP Album Tags
apple-fi---> XMP apple_fi Tags
ast---> Nikon ast Tags
aux---> XMP aux Tags
cc---> XMP cc Tags
cell---> XMP cell Tags
crd---> XMP crd Tags
creatorAtom---> XMP creatorAtom Tags
crs---> XMP crs Tags
dc---> XMP dc Tags
Device---> XMP Device Tags
dex---> XMP dex Tags
DICOM---> XMP DICOM Tags
digiKam---> XMP digiKam Tags
drone-dji---> DJI XMP Tags
dwc---> DarwinCore Tags
et---> XMP ExifTool Tags
exif---> XMP exif Tags
exifEX---> XMP exifEX Tags
expressionmedia---> XMP ExpressionMedia Tags
extensis---> XMP extensis Tags
fpv---> XMP fpv Tags
GAudio---> XMP GAudio Tags
GCamera---> XMP GCamera Tags
GCreations---> XMP GCreations Tags
GDepth---> XMP GDepth Tags
getty---> XMP GettyImages Tags
GFocus---> XMP GFocus Tags
GImage---> XMP GImage Tags
GPano---> XMP GPano Tags
GSpherical---> XMP GSpherical Tags
hdr---> XMP hdr Tags
hdrgm---> XMP hdrgm Tags
ics---> XMP ics Tags
iptcCore---> XMP iptcCore Tags
iptcExt---> XMP iptcExt Tags
LImage---> XMP LImage Tags
lr---> XMP Lightroom Tags
mediapro---> XMP MediaPro Tags
microsoft---> Microsoft XMP Tags
MP---> Microsoft MP Tags
MP1---> Microsoft MP1 Tags
mwg-coll---> MWG Collections Tags
mwg-kw---> MWG Keywords Tags
mwg-rs---> MWG Regions Tags
nine---> Nikon nine Tags
panorama---> XMP panorama Tags
pdf---> XMP pdf Tags
pdfx---> XMP pdfx Tags
photomech---> PhotoMechanic XMP Tags
photoshop---> XMP photoshop Tags
PixelLive---> XMP PixelLive Tags
plus---> PLUS XMP Tags
pmi---> XMP pmi Tags
prism---> XMP prism Tags
prl---> XMP prl Tags
prm---> XMP prm Tags
pur---> XMP pur Tags
rdf---> XMP rdf Tags
sdc---> Nikon sdc Tags
swf---> XMP swf Tags
tiff---> XMP tiff Tags
x---> XMP x Tags
xmp---> XMP xmp Tags
xmpBJ---> XMP xmpBJ Tags
xmpDM---> XMP xmpDM Tags
xmpMM---> XMP xmpMM Tags
xmpNote---> XMP xmpNote Tags
xmpPLUS---> XMP xmpPLUS Tags
xmpRights---> XMP xmpRights Tags
xmpTPg---> XMP xmpTPg Tags
+ +

XMP aas Tags

+

Apple Adjustment Settings used by iPhone/iPad.

+ +

These tags belong to the ExifTool XMP-aas family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AffineAreal 
AffineBreal 
AffineCreal 
AffineDreal 
AffineXreal 
AffineYreal 
CropHinteger/ 
CropWinteger/ 
CropXinteger/ 
CropYinteger/ 
Curve0xreal 
Curve0yreal 
Curve1xreal 
Curve1yreal 
Curve2xreal 
Curve2yreal 
Curve3xreal 
Curve3yreal 
Curve4xreal 
Curve4yreal 
FaceBalanceOrigIreal 
FaceBalanceOrigQreal 
FaceBalanceStrengthreal 
FaceBalanceWarmthreal 
Highlightsreal/ 
Shadowsreal/ 
Vibrancereal/ 
+ +

XMP acdsee Tags

+

ACD Systems ACDSee namespace tags.

+ +

(A note to software developers: Re-inventing your own private tags instead +of using the equivalent tags in standard XMP namespaces defeats one of the +most valuable features of metadata: interoperability. Your applications +mumble to themselves instead of speaking out for the rest of the world to +hear.)

+ +

These tags belong to the ExifTool XMP-acdsee family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Authorstring/ 
Captionstring/ 
Categoriesstring/ 
Collectionsstring/ 
DateTimedate/ 
DPPlang-alt/(newer version of XML raw processing settings)
EditStatusstring/ 
FixtureIdentifierstring/ 
Keywordsstring/+ 
Notesstring/ 
ObjectCyclestring/ 
OriginatingProgramstring/ 
Ratingreal/ 
Rawrppusedboolean/ 
ReleaseDatestring/ 
ReleaseTimestring/ 
RPPlang-alt/(raw processing settings in XML format)
Snapshotsstring/+ 
Taggedboolean/ 
+ +

XMP Album Tags

+

Adobe Album namespace tags.

+ +

These tags belong to the ExifTool XMP-album family 1 group.

+
+
+ + + + + + + +
Tag NameWritableValues / Notes
Notesstring 
+ +

XMP apple_fi Tags

+

Face information tags written by the Apple iPhone 5 inside the mwg-rs +RegionExtensions.

+ +

These tags belong to the ExifTool XMP-apple-fi family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AngleInfoRollinteger 
AngleInfoYawinteger 
ConfidenceLevelinteger 
FaceIDinteger 
TimeStampinteger 
+ +

XMP aux Tags

+

Adobe-defined auxiliary EXIF tags. This namespace existed in the XMP +specification until it was dropped in 2012, presumably due to the +introduction of the EXIF 2.3 for XMP specification and the exifEX namespace +at this time. For this reason, tags below with equivalents in the +exifEX namespace are avoided when writing.

+ +

These tags belong to the ExifTool XMP-aux family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ApproximateFocusDistancerational 
DistortionCorrectionAlreadyAppliedboolean 
EnhanceDenoiseAlreadyAppliedboolean 
EnhanceDenoiseLumaAmountstring 
EnhanceDenoiseVersionstring 
EnhanceDetailsAlreadyAppliedboolean 
EnhanceDetailsVersionstring 
EnhanceSuperResolutionAlreadyAppliedboolean 
EnhanceSuperResolutionScalerational 
EnhanceSuperResolutionVersionstring 
Firmwarestring 
FlashCompensationrational 
ImageNumberstring 
IsMergedHDRboolean 
IsMergedPanoramaboolean 
LateralChromaticAberrationCorrectionAlreadyAppliedboolean 
Lensstring 
LensDistortInfostring 
LensIDstring 
LensInfostring/(4 rational values giving focal and aperture ranges)
LensSerialNumberstring/ 
NeutralDensityFactorstring 
OwnerNamestring/ 
SerialNumberstring/ 
VignetteCorrectionAlreadyAppliedboolean 
+ +

XMP cc Tags

+

Creative Commons namespace tags. Note that the CC specification for XMP is +non-existent, so ExifTool must make some assumptions about the format of the +specific properties in XMP (see http://creativecommons.org/ns).

+ +

These tags belong to the ExifTool XMP-cc family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AttributionNamestring 
AttributionURLstring 
DeprecatedOndate 
Jurisdictionstring 
LegalCodestring 
Licensestring 
MorePermissionsstring 
Permitsstring+'cc:DerivativeWorks' = Derivative Works +
'cc:Distribution' = Distribution +
'cc:Reproduction' = Reproduction +
'cc:Sharing' = Sharing
Prohibitsstring+'cc:CommercialUse' = Commercial Use +
'cc:HighIncomeNationUse' = High Income Nation Use
Requiresstring+ +
'cc:Attribution' = Attribution +
'cc:Copyleft' = Copyleft +
'cc:LesserCopyleft' = Lesser Copyleft +
'cc:Notice' = Notice +
'cc:ShareAlike' = Share Alike +
'cc:SourceCode' = Source Code
+
UseGuidelinesstring 
+ +

XMP cell Tags

+

Location tags written by some Sony Ericsson phones.

+ +

These tags belong to the ExifTool XMP-cell family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CellTowerIDstring(called cellid by the spec)
CellGlobalIDstring(called cgi by the spec)
LocationAreaCodestring(called lac by the spec)
MobileCountryCodestring(called mcc by the spec)
MobileNetworkCodestring(called mnc by the spec)
CellRstring(called r by the spec)
+ +

XMP crd Tags

+

Adobe Camera Raw Defaults tags.

+ +

These tags belong to the ExifTool XMP-crd family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AlreadyAppliedboolean/ 
AutoBrightnessboolean/ 
AutoContrastboolean/ 
AutoExposureboolean/ 
AutoLateralCAinteger/ 
AutoShadowsboolean/ 
AutoToneboolean/ 
AutoToneDigeststring/ 
AutoToneDigestNoSatstring/ 
AutoWhiteVersioninteger/ 
Blacks2012integer/ 
BlueHueinteger/ 
BlueSaturationinteger/ 
Brightnessinteger/ 
CameraModelRestrictionstring/ 
CameraProfilestring/ 
CameraProfileDigeststring/ 
ChromaticAberrationBinteger/ 
ChromaticAberrationRinteger/ 
CircularGradientBasedCorrectionsstruct+--> Correction Struct
CircGradBasedCorrActiveboolean/_(CircularGradientBasedCorrectionsCorrectionActive)
CircGradBasedCorrAmountreal/_(CircularGradientBasedCorrectionsCorrectionAmount)
CircGradBasedCorrMasksstruct_+--> CorrectionMask Struct +
(CircularGradientBasedCorrectionsCorrectionMasks)
CircGradBasedCorrMaskAlphareal/_(CircularGradientBasedCorrectionsCorrectionMasksAlpha)
CircGradBasedCorrMaskAnglereal/_(CircularGradientBasedCorrectionsCorrectionMasksAngle)
CircGradBasedCorrMaskBottomreal/_(CircularGradientBasedCorrectionsCorrectionMasksBottom)
CircGradBasedCorrMaskCenterValuereal/_(CircularGradientBasedCorrectionsCorrectionMasksCenterValue)
CircGradBasedCorrMaskCenterWeightreal/_(CircularGradientBasedCorrectionsCorrectionMasksCenterWeight)
CircGradBasedCorrMaskRangestruct_+--> CorrRangeMask Struct +
(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMask; called CorrectionRangeMask by the spec)
CircGradBasedCorrMaskRangeAreaModelsstruct_+--> AreaModels Struct +
(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels)
CircGradBasedCorrMaskRangeAreaModelsComponentsstring/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents)
CircGradBasedCorrMaskRangeAreaModelsColorSampleInfostring/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
CircGradBasedCorrMaskRangeColorAmountreal/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount)
CircGradBasedCorrMaskRangeDepthFeatherreal/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather)
CircGradBasedCorrMaskRangeDepthMaxreal/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax)
CircGradBasedCorrMaskRangeDepthMinreal/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin)
CircGradBasedCorrMaskRangeInvertboolean/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert)
CircGradBasedCorrMaskRangeLumFeatherreal/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather)
CircGradBasedCorrMaskRangeLuminanceDepthSampleInfostring/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo)
CircGradBasedCorrMaskRangeLumMaxreal/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax)
CircGradBasedCorrMaskRangeLumMinreal/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin)
CircGradBasedCorrMaskRangeLumRangestring/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange)
CircGradBasedCorrMaskRangeSampleTypeinteger/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType)
CircGradBasedCorrMaskRangeTypestring/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskType)
CircGradBasedCorrMaskRangeVersionstring/_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion)
CircGradBasedCorrMaskDabsstring/_(CircularGradientBasedCorrectionsCorrectionMasksDabs)
CircGradBasedCorrMaskFeatherreal/_(CircularGradientBasedCorrectionsCorrectionMasksFeather)
CircGradBasedCorrMaskFlippedboolean/_(CircularGradientBasedCorrectionsCorrectionMasksFlipped)
CircGradBasedCorrMaskFlowreal/_(CircularGradientBasedCorrectionsCorrectionMasksFlow)
CircGradBasedCorrMaskFullXreal/_(CircularGradientBasedCorrectionsCorrectionMasksFullX)
CircGradBasedCorrMaskFullYreal/_(CircularGradientBasedCorrectionsCorrectionMasksFullY)
CircGradBasedCorrMaskInputDigeststring/_(CircularGradientBasedCorrectionsCorrectionMasksInputDigest)
CircGradBasedCorrMaskLeftreal/_(CircularGradientBasedCorrectionsCorrectionMasksLeft)
CircGradBasedCorrMaskMaskActiveboolean/_(CircularGradientBasedCorrectionsCorrectionMasksMaskActive)
CircGradBasedCorrMaskMaskBlendModeinteger/_(CircularGradientBasedCorrectionsCorrectionMasksMaskBlendMode)
CircGradBasedCorrMaskMaskDigeststring/_(CircularGradientBasedCorrectionsCorrectionMasksMaskDigest)
CircGradBasedCorrMaskMaskInvertedboolean/_(CircularGradientBasedCorrectionsCorrectionMasksMaskInverted)
CircGradBasedCorrMaskMaskNamestring/_(CircularGradientBasedCorrectionsCorrectionMasksMaskName)
CircGradBasedCorrMaskMasksstruct_+--> CorrectionMask Struct +
(CircularGradientBasedCorrectionsCorrectionMasksMasks)
CircGradBasedCorrMaskMasksAlphareal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksAlpha)
CircGradBasedCorrMaskMasksAnglereal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksAngle)
CircGradBasedCorrMaskMasksBottomreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksBottom)
CircGradBasedCorrMaskMasksCenterValuereal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksCenterValue)
CircGradBasedCorrMaskMasksCenterWeightreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksCenterWeight)
CircGradBasedCorrMaskMasksDabsstring/_+(CircularGradientBasedCorrectionsCorrectionMasksMasksDabs)
CircGradBasedCorrMaskMasksFeatherreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksFeather)
CircGradBasedCorrMaskMasksFlippedboolean/_(CircularGradientBasedCorrectionsCorrectionMasksMasksFlipped)
CircGradBasedCorrMaskMasksFlowreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksFlow)
CircGradBasedCorrMaskMasksFullXreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksFullX)
CircGradBasedCorrMaskMasksFullYreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksFullY)
CircGradBasedCorrMaskMasksInputDigeststring/_(CircularGradientBasedCorrectionsCorrectionMasksMasksInputDigest)
CircGradBasedCorrMaskMasksLeftreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksLeft)
CircGradBasedCorrMaskMasksMaskActiveboolean/_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskActive)
CircGradBasedCorrMaskMasksMaskBlendModeinteger/_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskBlendMode)
CircGradBasedCorrMaskMasksMaskDigeststring/_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskDigest)
CircGradBasedCorrMaskMasksMaskInvertedboolean/_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskInverted)
CircGradBasedCorrMaskMasksMaskNamestring/_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskName)
CircGradBasedCorrMaskMasksMaskSubTypestring/_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskSubType)
CircGradBasedCorrMaskMasksMaskSyncIDstring/_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskSyncID)
CircGradBasedCorrMaskMasksValuereal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskValue)
CircGradBasedCorrMaskMasksMaskVersionstring/_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskVersion)
CircGradBasedCorrMaskMasksMidpointreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksMidpoint)
CircGradBasedCorrMaskMasksOriginstring/_(CircularGradientBasedCorrectionsCorrectionMasksMasksOrigin)
CircGradBasedCorrMaskMasksPerimeterValuereal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksPerimeterValue)
CircGradBasedCorrMaskMasksRadiusreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksRadius)
CircGradBasedCorrMaskMasksReferencePointstring/_(CircularGradientBasedCorrectionsCorrectionMasksMasksReferencePoint)
CircGradBasedCorrMaskMasksRightreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksRight)
CircGradBasedCorrMaskMasksRoundnessreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksRoundness)
CircGradBasedCorrMaskMasksSizeXreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksSizeX)
CircGradBasedCorrMaskMasksSizeYreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksSizeY)
CircGradBasedCorrMaskMasksTopreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksTop)
CircGradBasedCorrMaskMaskSubTypestring/_(CircularGradientBasedCorrectionsCorrectionMasksMaskSubType)
CircGradBasedCorrMaskMasksVersioninteger/_(CircularGradientBasedCorrectionsCorrectionMasksMasksVersion)
CircGradBasedCorrMaskMasksWhatstring/_(CircularGradientBasedCorrectionsCorrectionMasksMasksWhat)
CircGradBasedCorrMaskMasksWholeImageAreastring/_(CircularGradientBasedCorrectionsCorrectionMasksMasksWholeImageArea)
CircGradBasedCorrMaskMasksXreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksX)
CircGradBasedCorrMaskMasksYreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksY)
CircGradBasedCorrMaskMaskSyncIDstring/_(CircularGradientBasedCorrectionsCorrectionMasksMaskSyncID)
CircGradBasedCorrMaskMasksZeroXreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksZeroX)
CircGradBasedCorrMaskMasksZeroYreal/_(CircularGradientBasedCorrectionsCorrectionMasksMasksZeroY)
CircGradBasedCorrMaskValuereal/_(CircularGradientBasedCorrectionsCorrectionMasksMaskValue)
CircGradBasedCorrMaskMaskVersionstring/_(CircularGradientBasedCorrectionsCorrectionMasksMaskVersion)
CircGradBasedCorrMaskMidpointreal/_(CircularGradientBasedCorrectionsCorrectionMasksMidpoint)
CircGradBasedCorrMaskOriginstring/_(CircularGradientBasedCorrectionsCorrectionMasksOrigin)
CircGradBasedCorrMaskPerimeterValuereal/_(CircularGradientBasedCorrectionsCorrectionMasksPerimeterValue)
CircGradBasedCorrMaskRadiusreal/_(CircularGradientBasedCorrectionsCorrectionMasksRadius)
CircGradBasedCorrMaskReferencePointstring/_(CircularGradientBasedCorrectionsCorrectionMasksReferencePoint)
CircGradBasedCorrMaskRightreal/_(CircularGradientBasedCorrectionsCorrectionMasksRight)
CircGradBasedCorrMaskRoundnessreal/_(CircularGradientBasedCorrectionsCorrectionMasksRoundness)
CircGradBasedCorrMaskSizeXreal/_(CircularGradientBasedCorrectionsCorrectionMasksSizeX)
CircGradBasedCorrMaskSizeYreal/_(CircularGradientBasedCorrectionsCorrectionMasksSizeY)
CircGradBasedCorrMaskTopreal/_(CircularGradientBasedCorrectionsCorrectionMasksTop)
CircGradBasedCorrMaskVersioninteger/_(CircularGradientBasedCorrectionsCorrectionMasksVersion)
CircGradBasedCorrMaskWhatstring/_(CircularGradientBasedCorrectionsCorrectionMasksWhat)
CircGradBasedCorrMaskWholeImageAreastring/_(CircularGradientBasedCorrectionsCorrectionMasksWholeImageArea)
CircGradBasedCorrMaskXreal/_(CircularGradientBasedCorrectionsCorrectionMasksX)
CircGradBasedCorrMaskYreal/_(CircularGradientBasedCorrectionsCorrectionMasksY)
CircGradBasedCorrMaskZeroXreal/_(CircularGradientBasedCorrectionsCorrectionMasksZeroX)
CircGradBasedCorrMaskZeroYreal/_(CircularGradientBasedCorrectionsCorrectionMasksZeroY)
CircGradBasedCorrCorrectionNamestring/_+(CircularGradientBasedCorrectionsCorrectionName)
CircGradBasedCorrRangeMaskstruct_+--> CorrRangeMask Struct +
(CircularGradientBasedCorrectionsCorrectionRangeMask; called CorrectionRangeMask by the spec)
CircGradBasedCorrRangeMaskAreaModelsstruct_+--> AreaModels Struct +
(CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModels)
CircGradBasedCorrRangeMaskAreaModelsComponentsstring/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents)
CircGradBasedCorrRangeMaskAreaModelsColorSampleInfostring/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
CircGradBasedCorrRangeMaskColorAmountreal/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskColorAmount)
CircGradBasedCorrRangeMaskDepthFeatherreal/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskDepthFeather)
CircGradBasedCorrRangeMaskDepthMaxreal/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskDepthMax)
CircGradBasedCorrRangeMaskDepthMinreal/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskDepthMin)
CircGradBasedCorrRangeMaskInvertboolean/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskInvert)
CircGradBasedCorrRangeMaskLumFeatherreal/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskLumFeather)
CircGradBasedCorrRangeMaskLuminanceDepthSampleInfostring/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo)
CircGradBasedCorrRangeMaskLumMaxreal/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskLumMax)
CircGradBasedCorrRangeMaskLumMinreal/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskLumMin)
CircGradBasedCorrRangeMaskLumRangestring/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskLumRange)
CircGradBasedCorrRangeMaskSampleTypeinteger/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskSampleType)
CircGradBasedCorrRangeMaskTypestring/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskType)
CircGradBasedCorrRangeMaskVersionstring/_+(CircularGradientBasedCorrectionsCorrectionRangeMaskVersion)
CircGradBasedCorrCorrectionSyncIDstring/_+(CircularGradientBasedCorrectionsCorrectionSyncID)
CircGradBasedCorrBlacks2012real/_(CircularGradientBasedCorrectionsLocalBlacks2012)
CircGradBasedCorrBrightnessreal/_(CircularGradientBasedCorrectionsLocalBrightness)
CircGradBasedCorrClarityreal/_(CircularGradientBasedCorrectionsLocalClarity)
CircGradBasedCorrClarity2012real/_(CircularGradientBasedCorrectionsLocalClarity2012)
CircGradBasedCorrContrastreal/_(CircularGradientBasedCorrectionsLocalContrast)
CircGradBasedCorrContrast2012real/_(CircularGradientBasedCorrectionsLocalContrast2012)
CircGradBasedCorrDefringereal/_(CircularGradientBasedCorrectionsLocalDefringe)
CircGradBasedCorrDehazereal/_(CircularGradientBasedCorrectionsLocalDehaze)
CircGradBasedCorrExposurereal/_(CircularGradientBasedCorrectionsLocalExposure)
CircGradBasedCorrExposure2012real/_(CircularGradientBasedCorrectionsLocalExposure2012)
CircGradBasedCorrHighlights2012real/_(CircularGradientBasedCorrectionsLocalHighlights2012)
CircGradBasedCorrHuereal/_(CircularGradientBasedCorrectionsLocalHue)
CircGradBasedCorrLuminanceNoisereal/_(CircularGradientBasedCorrectionsLocalLuminanceNoise)
CircGradBasedCorrMoirereal/_(CircularGradientBasedCorrectionsLocalMoire)
CircGradBasedCorrSaturationreal/_(CircularGradientBasedCorrectionsLocalSaturation)
CircGradBasedCorrShadows2012real/_(CircularGradientBasedCorrectionsLocalShadows2012)
CircGradBasedCorrSharpnessreal/_(CircularGradientBasedCorrectionsLocalSharpness)
CircGradBasedCorrTemperaturereal/_(CircularGradientBasedCorrectionsLocalTemperature)
CircGradBasedCorrTexturereal/_(CircularGradientBasedCorrectionsLocalTexture)
CircGradBasedCorrTintreal/_(CircularGradientBasedCorrectionsLocalTint)
CircGradBasedCorrToningHuereal/_(CircularGradientBasedCorrectionsLocalToningHue)
CircGradBasedCorrToningSaturationreal/_(CircularGradientBasedCorrectionsLocalToningSaturation)
CircGradBasedCorrWhites2012real/_(CircularGradientBasedCorrectionsLocalWhites2012)
CircGradBasedCorrWhatstring/_(CircularGradientBasedCorrectionsWhat)
Clarityinteger/ 
Clarity2012integer/ 
ClipboardAspectRatiointeger/ 
ClipboardOrientationinteger/ 
Clusterstring/ 
ColorGradeBlendinginteger/ 
ColorGradeGlobalHueinteger/ 
ColorGradeGlobalLuminteger/ 
ColorGradeGlobalSatinteger/ 
ColorGradeHighlightLuminteger/ 
ColorGradeMidtoneHueinteger/ 
ColorGradeMidtoneLuminteger/ 
ColorGradeMidtoneSatinteger/ 
ColorGradeShadowLuminteger/ 
ColorNoiseReductioninteger/ 
ColorNoiseReductionDetailinteger/ 
ColorNoiseReductionSmoothnessinteger/ 
CompatibleVersionstring/ 
ContactInfostring/ 
Contrastinteger/ 
Contrast2012integer/ 
Converterstring/ 
ConvertToGrayscaleboolean/ 
Copyrightstring/ 
CropAnglereal/ 
CropBottomreal/ 
CropConstrainToWarpinteger/ 
CropHeightreal/ 
CropLeftreal/ 
CropRightreal/ 
CropTopreal/ 
CropUnitinteger/0 = pixels +
1 = inches +
2 = cm
CropUnitsinteger/0 = pixels +
1 = inches +
2 = cm
CropWidthreal/ 
DefaultAutoGrayboolean/ 
DefaultAutoToneboolean/ 
DefaultsSpecificToISOboolean/ 
DefaultsSpecificToSerialboolean/ 
Defringeinteger/ 
DefringeGreenAmountinteger/ 
DefringeGreenHueHiinteger/ 
DefringeGreenHueLointeger/ 
DefringePurpleAmountinteger/ 
DefringePurpleHueHiinteger/ 
DefringePurpleHueLointeger/ 
Dehazereal/ 
Descriptionlang-alt/ 
DNGIgnoreSidecarsboolean/ 
Exposurereal/ 
Exposure2012real/ 
FillLightinteger/ 
GradientBasedCorrectionsstruct+--> Correction Struct
GradientBasedCorrActiveboolean/_(GradientBasedCorrectionsCorrectionActive)
GradientBasedCorrAmountreal/_(GradientBasedCorrectionsCorrectionAmount)
GradientBasedCorrMasksstruct_+--> CorrectionMask Struct +
(GradientBasedCorrectionsCorrectionMasks)
GradientBasedCorrMaskAlphareal/_(GradientBasedCorrectionsCorrectionMasksAlpha)
GradientBasedCorrMaskAnglereal/_(GradientBasedCorrectionsCorrectionMasksAngle)
GradientBasedCorrMaskBottomreal/_(GradientBasedCorrectionsCorrectionMasksBottom)
GradientBasedCorrMaskCenterValuereal/_(GradientBasedCorrectionsCorrectionMasksCenterValue)
GradientBasedCorrMaskCenterWeightreal/_(GradientBasedCorrectionsCorrectionMasksCenterWeight)
GradientBasedCorrMaskRangestruct_+--> CorrRangeMask Struct +
(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMask; called CorrectionRangeMask by the spec)
GradientBasedCorrMaskRangeAreaModelsstruct_+--> AreaModels Struct +
(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels)
GradientBasedCorrMaskRangeAreaModelsComponentsstring/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents)
GradientBasedCorrMaskRangeAreaModelsColorSampleInfostring/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
GradientBasedCorrMaskRangeColorAmountreal/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount)
GradientBasedCorrMaskRangeDepthFeatherreal/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather)
GradientBasedCorrMaskRangeDepthMaxreal/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax)
GradientBasedCorrMaskRangeDepthMinreal/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin)
GradientBasedCorrMaskRangeInvertboolean/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert)
GradientBasedCorrMaskRangeLumFeatherreal/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather)
GradientBasedCorrMaskRangeLuminanceDepthSampleInfostring/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo)
GradientBasedCorrMaskRangeLumMaxreal/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax)
GradientBasedCorrMaskRangeLumMinreal/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin)
GradientBasedCorrMaskRangeLumRangestring/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange)
GradientBasedCorrMaskRangeSampleTypeinteger/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType)
GradientBasedCorrMaskRangeTypestring/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskType)
GradientBasedCorrMaskRangeVersionstring/_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion)
GradientBasedCorrMaskDabsstring/_(GradientBasedCorrectionsCorrectionMasksDabs)
GradientBasedCorrMaskFeatherreal/_(GradientBasedCorrectionsCorrectionMasksFeather)
GradientBasedCorrMaskFlippedboolean/_(GradientBasedCorrectionsCorrectionMasksFlipped)
GradientBasedCorrMaskFlowreal/_(GradientBasedCorrectionsCorrectionMasksFlow)
GradientBasedCorrMaskFullXreal/_(GradientBasedCorrectionsCorrectionMasksFullX)
GradientBasedCorrMaskFullYreal/_(GradientBasedCorrectionsCorrectionMasksFullY)
GradientBasedCorrMaskInputDigeststring/_(GradientBasedCorrectionsCorrectionMasksInputDigest)
GradientBasedCorrMaskLeftreal/_(GradientBasedCorrectionsCorrectionMasksLeft)
GradientBasedCorrMaskMaskActiveboolean/_(GradientBasedCorrectionsCorrectionMasksMaskActive)
GradientBasedCorrMaskMaskBlendModeinteger/_(GradientBasedCorrectionsCorrectionMasksMaskBlendMode)
GradientBasedCorrMaskMaskDigeststring/_(GradientBasedCorrectionsCorrectionMasksMaskDigest)
GradientBasedCorrMaskMaskInvertedboolean/_(GradientBasedCorrectionsCorrectionMasksMaskInverted)
GradientBasedCorrMaskMaskNamestring/_(GradientBasedCorrectionsCorrectionMasksMaskName)
GradientBasedCorrMaskMasksstruct_+--> CorrectionMask Struct +
(GradientBasedCorrectionsCorrectionMasksMasks)
GradientBasedCorrMaskMasksAlphareal/_(GradientBasedCorrectionsCorrectionMasksMasksAlpha)
GradientBasedCorrMaskMasksAnglereal/_(GradientBasedCorrectionsCorrectionMasksMasksAngle)
GradientBasedCorrMaskMasksBottomreal/_(GradientBasedCorrectionsCorrectionMasksMasksBottom)
GradientBasedCorrMaskMasksCenterValuereal/_(GradientBasedCorrectionsCorrectionMasksMasksCenterValue)
GradientBasedCorrMaskMasksCenterWeightreal/_(GradientBasedCorrectionsCorrectionMasksMasksCenterWeight)
GradientBasedCorrMaskMasksDabsstring/_+(GradientBasedCorrectionsCorrectionMasksMasksDabs)
GradientBasedCorrMaskMasksFeatherreal/_(GradientBasedCorrectionsCorrectionMasksMasksFeather)
GradientBasedCorrMaskMasksFlippedboolean/_(GradientBasedCorrectionsCorrectionMasksMasksFlipped)
GradientBasedCorrMaskMasksFlowreal/_(GradientBasedCorrectionsCorrectionMasksMasksFlow)
GradientBasedCorrMaskMasksFullXreal/_(GradientBasedCorrectionsCorrectionMasksMasksFullX)
GradientBasedCorrMaskMasksFullYreal/_(GradientBasedCorrectionsCorrectionMasksMasksFullY)
GradientBasedCorrMaskMasksInputDigeststring/_(GradientBasedCorrectionsCorrectionMasksMasksInputDigest)
GradientBasedCorrMaskMasksLeftreal/_(GradientBasedCorrectionsCorrectionMasksMasksLeft)
GradientBasedCorrMaskMasksMaskActiveboolean/_(GradientBasedCorrectionsCorrectionMasksMasksMaskActive)
GradientBasedCorrMaskMasksMaskBlendModeinteger/_(GradientBasedCorrectionsCorrectionMasksMasksMaskBlendMode)
GradientBasedCorrMaskMasksMaskDigeststring/_(GradientBasedCorrectionsCorrectionMasksMasksMaskDigest)
GradientBasedCorrMaskMasksMaskInvertedboolean/_(GradientBasedCorrectionsCorrectionMasksMasksMaskInverted)
GradientBasedCorrMaskMasksMaskNamestring/_(GradientBasedCorrectionsCorrectionMasksMasksMaskName)
GradientBasedCorrMaskMasksMaskSubTypestring/_(GradientBasedCorrectionsCorrectionMasksMasksMaskSubType)
GradientBasedCorrMaskMasksMaskSyncIDstring/_(GradientBasedCorrectionsCorrectionMasksMasksMaskSyncID)
GradientBasedCorrMaskMasksValuereal/_(GradientBasedCorrectionsCorrectionMasksMasksMaskValue)
GradientBasedCorrMaskMasksMaskVersionstring/_(GradientBasedCorrectionsCorrectionMasksMasksMaskVersion)
GradientBasedCorrMaskMasksMidpointreal/_(GradientBasedCorrectionsCorrectionMasksMasksMidpoint)
GradientBasedCorrMaskMasksOriginstring/_(GradientBasedCorrectionsCorrectionMasksMasksOrigin)
GradientBasedCorrMaskMasksPerimeterValuereal/_(GradientBasedCorrectionsCorrectionMasksMasksPerimeterValue)
GradientBasedCorrMaskMasksRadiusreal/_(GradientBasedCorrectionsCorrectionMasksMasksRadius)
GradientBasedCorrMaskMasksReferencePointstring/_(GradientBasedCorrectionsCorrectionMasksMasksReferencePoint)
GradientBasedCorrMaskMasksRightreal/_(GradientBasedCorrectionsCorrectionMasksMasksRight)
GradientBasedCorrMaskMasksRoundnessreal/_(GradientBasedCorrectionsCorrectionMasksMasksRoundness)
GradientBasedCorrMaskMasksSizeXreal/_(GradientBasedCorrectionsCorrectionMasksMasksSizeX)
GradientBasedCorrMaskMasksSizeYreal/_(GradientBasedCorrectionsCorrectionMasksMasksSizeY)
GradientBasedCorrMaskMasksTopreal/_(GradientBasedCorrectionsCorrectionMasksMasksTop)
GradientBasedCorrMaskMaskSubTypestring/_(GradientBasedCorrectionsCorrectionMasksMaskSubType)
GradientBasedCorrMaskMasksVersioninteger/_(GradientBasedCorrectionsCorrectionMasksMasksVersion)
GradientBasedCorrMaskMasksWhatstring/_(GradientBasedCorrectionsCorrectionMasksMasksWhat)
GradientBasedCorrMaskMasksWholeImageAreastring/_(GradientBasedCorrectionsCorrectionMasksMasksWholeImageArea)
GradientBasedCorrMaskMasksXreal/_(GradientBasedCorrectionsCorrectionMasksMasksX)
GradientBasedCorrMaskMasksYreal/_(GradientBasedCorrectionsCorrectionMasksMasksY)
GradientBasedCorrMaskMaskSyncIDstring/_(GradientBasedCorrectionsCorrectionMasksMaskSyncID)
GradientBasedCorrMaskMasksZeroXreal/_(GradientBasedCorrectionsCorrectionMasksMasksZeroX)
GradientBasedCorrMaskMasksZeroYreal/_(GradientBasedCorrectionsCorrectionMasksMasksZeroY)
GradientBasedCorrMaskValuereal/_(GradientBasedCorrectionsCorrectionMasksMaskValue)
GradientBasedCorrMaskMaskVersionstring/_(GradientBasedCorrectionsCorrectionMasksMaskVersion)
GradientBasedCorrMaskMidpointreal/_(GradientBasedCorrectionsCorrectionMasksMidpoint)
GradientBasedCorrMaskOriginstring/_(GradientBasedCorrectionsCorrectionMasksOrigin)
GradientBasedCorrMaskPerimeterValuereal/_(GradientBasedCorrectionsCorrectionMasksPerimeterValue)
GradientBasedCorrMaskRadiusreal/_(GradientBasedCorrectionsCorrectionMasksRadius)
GradientBasedCorrMaskReferencePointstring/_(GradientBasedCorrectionsCorrectionMasksReferencePoint)
GradientBasedCorrMaskRightreal/_(GradientBasedCorrectionsCorrectionMasksRight)
GradientBasedCorrMaskRoundnessreal/_(GradientBasedCorrectionsCorrectionMasksRoundness)
GradientBasedCorrMaskSizeXreal/_(GradientBasedCorrectionsCorrectionMasksSizeX)
GradientBasedCorrMaskSizeYreal/_(GradientBasedCorrectionsCorrectionMasksSizeY)
GradientBasedCorrMaskTopreal/_(GradientBasedCorrectionsCorrectionMasksTop)
GradientBasedCorrMaskVersioninteger/_(GradientBasedCorrectionsCorrectionMasksVersion)
GradientBasedCorrMaskWhatstring/_(GradientBasedCorrectionsCorrectionMasksWhat)
GradientBasedCorrMaskWholeImageAreastring/_(GradientBasedCorrectionsCorrectionMasksWholeImageArea)
GradientBasedCorrMaskXreal/_(GradientBasedCorrectionsCorrectionMasksX)
GradientBasedCorrMaskYreal/_(GradientBasedCorrectionsCorrectionMasksY)
GradientBasedCorrMaskZeroXreal/_(GradientBasedCorrectionsCorrectionMasksZeroX)
GradientBasedCorrMaskZeroYreal/_(GradientBasedCorrectionsCorrectionMasksZeroY)
GradientBasedCorrCorrectionNamestring/_+(GradientBasedCorrectionsCorrectionName)
GradientBasedCorrRangeMaskstruct_+--> CorrRangeMask Struct +
(GradientBasedCorrectionsCorrectionRangeMask; called CorrectionRangeMask by the spec)
GradientBasedCorrRangeMaskAreaModelsstruct_+--> AreaModels Struct +
(GradientBasedCorrectionsCorrectionRangeMaskAreaModels)
GradientBasedCorrRangeMaskAreaModelsComponentsstring/_+(GradientBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents)
GradientBasedCorrRangeMaskAreaModelsColorSampleInfostring/_+(GradientBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
GradientBasedCorrRangeMaskColorAmountreal/_+(GradientBasedCorrectionsCorrectionRangeMaskColorAmount)
GradientBasedCorrRangeMaskDepthFeatherreal/_+(GradientBasedCorrectionsCorrectionRangeMaskDepthFeather)
GradientBasedCorrRangeMaskDepthMaxreal/_+(GradientBasedCorrectionsCorrectionRangeMaskDepthMax)
GradientBasedCorrRangeMaskDepthMinreal/_+(GradientBasedCorrectionsCorrectionRangeMaskDepthMin)
GradientBasedCorrRangeMaskInvertboolean/_+(GradientBasedCorrectionsCorrectionRangeMaskInvert)
GradientBasedCorrRangeMaskLumFeatherreal/_+(GradientBasedCorrectionsCorrectionRangeMaskLumFeather)
GradientBasedCorrRangeMaskLuminanceDepthSampleInfostring/_+(GradientBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo)
GradientBasedCorrRangeMaskLumMaxreal/_+(GradientBasedCorrectionsCorrectionRangeMaskLumMax)
GradientBasedCorrRangeMaskLumMinreal/_+(GradientBasedCorrectionsCorrectionRangeMaskLumMin)
GradientBasedCorrRangeMaskLumRangestring/_+(GradientBasedCorrectionsCorrectionRangeMaskLumRange)
GradientBasedCorrRangeMaskSampleTypeinteger/_+(GradientBasedCorrectionsCorrectionRangeMaskSampleType)
GradientBasedCorrRangeMaskTypestring/_+(GradientBasedCorrectionsCorrectionRangeMaskType)
GradientBasedCorrRangeMaskVersionstring/_+(GradientBasedCorrectionsCorrectionRangeMaskVersion)
GradientBasedCorrCorrectionSyncIDstring/_+(GradientBasedCorrectionsCorrectionSyncID)
GradientBasedCorrBlacks2012real/_(GradientBasedCorrectionsLocalBlacks2012)
GradientBasedCorrBrightnessreal/_(GradientBasedCorrectionsLocalBrightness)
GradientBasedCorrClarityreal/_(GradientBasedCorrectionsLocalClarity)
GradientBasedCorrClarity2012real/_(GradientBasedCorrectionsLocalClarity2012)
GradientBasedCorrContrastreal/_(GradientBasedCorrectionsLocalContrast)
GradientBasedCorrContrast2012real/_(GradientBasedCorrectionsLocalContrast2012)
GradientBasedCorrDefringereal/_(GradientBasedCorrectionsLocalDefringe)
GradientBasedCorrDehazereal/_(GradientBasedCorrectionsLocalDehaze)
GradientBasedCorrExposurereal/_(GradientBasedCorrectionsLocalExposure)
GradientBasedCorrExposure2012real/_(GradientBasedCorrectionsLocalExposure2012)
GradientBasedCorrHighlights2012real/_(GradientBasedCorrectionsLocalHighlights2012)
GradientBasedCorrHuereal/_(GradientBasedCorrectionsLocalHue)
GradientBasedCorrLuminanceNoisereal/_(GradientBasedCorrectionsLocalLuminanceNoise)
GradientBasedCorrMoirereal/_(GradientBasedCorrectionsLocalMoire)
GradientBasedCorrSaturationreal/_(GradientBasedCorrectionsLocalSaturation)
GradientBasedCorrShadows2012real/_(GradientBasedCorrectionsLocalShadows2012)
GradientBasedCorrSharpnessreal/_(GradientBasedCorrectionsLocalSharpness)
GradientBasedCorrTemperaturereal/_(GradientBasedCorrectionsLocalTemperature)
GradientBasedCorrTexturereal/_(GradientBasedCorrectionsLocalTexture)
GradientBasedCorrTintreal/_(GradientBasedCorrectionsLocalTint)
GradientBasedCorrToningHuereal/_(GradientBasedCorrectionsLocalToningHue)
GradientBasedCorrToningSaturationreal/_(GradientBasedCorrectionsLocalToningSaturation)
GradientBasedCorrWhites2012real/_(GradientBasedCorrectionsLocalWhites2012)
GradientBasedCorrWhatstring/_(GradientBasedCorrectionsWhat)
GrainAmountinteger/ 
GrainFrequencyinteger/ 
GrainSeedinteger/ 
GrainSizeinteger/ 
GrayMixerAquainteger/ 
GrayMixerBlueinteger/ 
GrayMixerGreeninteger/ 
GrayMixerMagentainteger/ 
GrayMixerOrangeinteger/ 
GrayMixerPurpleinteger/ 
GrayMixerRedinteger/ 
GrayMixerYellowinteger/ 
GreenHueinteger/ 
GreenSaturationinteger/ 
Grouplang-alt/ 
HasCropboolean/ 
HasSettingsboolean/ 
HDREditModeinteger/ 
Highlight2012integer/ 
HighlightRecoveryinteger/ 
Highlights2012integer/ 
HueAdjustmentAquainteger/ 
HueAdjustmentBlueinteger/ 
HueAdjustmentGreeninteger/ 
HueAdjustmentMagentainteger/ 
HueAdjustmentOrangeinteger/ 
HueAdjustmentPurpleinteger/ 
HueAdjustmentRedinteger/ 
HueAdjustmentYellowinteger/ 
IncrementalTemperatureinteger/ 
IncrementalTintinteger/ 
JPEGHandlingstring/ 
LensManualDistortionAmountinteger/ 
LensProfileChromaticAberrationScaleinteger/ 
LensProfileDigeststring/ 
LensProfileDistortionScaleinteger/ 
LensProfileEnableinteger/ 
LensProfileFilenamestring/ 
LensProfileIsEmbeddedboolean/ 
LensProfileMatchKeyCameraModelNamestring/ 
LensProfileMatchKeyExifMakestring/ 
LensProfileMatchKeyExifModelstring/ 
LensProfileMatchKeyIsRawboolean/ 
LensProfileMatchKeyLensIDstring/ 
LensProfileMatchKeyLensInfostring/ 
LensProfileMatchKeyLensNamestring/ 
LensProfileMatchKeySensorFormatFactorreal/ 
LensProfileNamestring/ 
LensProfileSetupstring/ 
LensProfileVignettingScaleinteger/ 
Lookstruct--> Look Struct
LookAmountstring/_ 
LookClusterstring/_ 
LookCopyrightstring/_ 
LookGrouplang-alt/_ 
LookNamestring/(NOT a flattened tag!)
LookParametersstruct_--> LookParms Struct
LookParametersCameraProfilestring/_ 
LookParametersClarity2012string/_ 
LookParametersConvertToGrayscalestring/_ 
LookParametersLookTablestring/_ 
LookParametersProcessVersionstring/_ 
LookParametersToneCurvePV2012string/_+ 
LookParametersToneCurvePV2012Bluestring/_+ 
LookParametersToneCurvePV2012Greenstring/_+ 
LookParametersToneCurvePV2012Redstring/_+ 
LookParametersVersionstring/_ 
LookSupportsAmountstring/_ 
LookSupportsMonochromestring/_ 
LookSupportsOutputReferredstring/_ 
LookUUIDstring/_ 
LuminanceAdjustmentAquainteger/ 
LuminanceAdjustmentBlueinteger/ 
LuminanceAdjustmentGreeninteger/ 
LuminanceAdjustmentMagentainteger/ 
LuminanceAdjustmentOrangeinteger/ 
LuminanceAdjustmentPurpleinteger/ 
LuminanceAdjustmentRedinteger/ 
LuminanceAdjustmentYellowinteger/ 
LuminanceNoiseReductionContrastinteger/ 
LuminanceNoiseReductionDetailinteger/ 
LuminanceSmoothinginteger/ 
MaskGroupBasedCorrectionsstruct+--> Correction Struct
MaskGroupBasedCorrActiveboolean/_(MaskGroupBasedCorrectionsCorrectionActive)
MaskGroupBasedCorrAmountreal/_(MaskGroupBasedCorrectionsCorrectionAmount)
MaskGroupBasedCorrMaskstruct_+--> CorrectionMask Struct +
(MaskGroupBasedCorrectionsCorrectionMasks)
MaskGroupBasedCorrMaskAlphareal/_(MaskGroupBasedCorrectionsCorrectionMasksAlpha)
MaskGroupBasedCorrMaskAnglereal/_(MaskGroupBasedCorrectionsCorrectionMasksAngle)
MaskGroupBasedCorrMaskBottomreal/_(MaskGroupBasedCorrectionsCorrectionMasksBottom)
MaskGroupBasedCorrMaskCenterValuereal/_(MaskGroupBasedCorrectionsCorrectionMasksCenterValue)
MaskGroupBasedCorrMaskCenterWeightreal/_(MaskGroupBasedCorrectionsCorrectionMasksCenterWeight)
MaskGroupBasedCorrMaskRangestruct_+--> CorrRangeMask Struct +
(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMask; called CorrectionRangeMask by the spec)
MaskGroupBasedCorrMaskRangeAreaModelsstruct_+--> AreaModels Struct +
(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels)
MaskGroupBasedCorrMaskRangeAreaModelsComponentsstring/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents)
MaskGroupBasedCorrMaskRangeAreaModelsColorSampleInfostring/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
MaskGroupBasedCorrMaskRangeColorAmountreal/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount)
MaskGroupBasedCorrMaskRangeDepthFeatherreal/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather)
MaskGroupBasedCorrMaskRangeDepthMaxreal/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax)
MaskGroupBasedCorrMaskRangeDepthMinreal/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin)
MaskGroupBasedCorrMaskRangeInvertboolean/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert)
MaskGroupBasedCorrMaskRangeLumFeatherreal/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather)
MaskGroupBasedCorrMaskRangeLuminanceDepthSampleInfostring/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo)
MaskGroupBasedCorrMaskRangeLumMaxreal/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax)
MaskGroupBasedCorrMaskRangeLumMinreal/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin)
MaskGroupBasedCorrMaskRangeLumRangestring/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange)
MaskGroupBasedCorrMaskRangeSampleTypeinteger/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType)
MaskGroupBasedCorrMaskRangeTypestring/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskType)
MaskGroupBasedCorrMaskRangeVersionstring/_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion)
MaskGroupBasedCorrMaskDabsstring/_+(MaskGroupBasedCorrectionsCorrectionMasksDabs)
MaskGroupBasedCorrMaskFeatherreal/_(MaskGroupBasedCorrectionsCorrectionMasksFeather)
MaskGroupBasedCorrMaskFlippedboolean/_(MaskGroupBasedCorrectionsCorrectionMasksFlipped)
MaskGroupBasedCorrMaskFlowreal/_(MaskGroupBasedCorrectionsCorrectionMasksFlow)
MaskGroupBasedCorrMaskFullXreal/_(MaskGroupBasedCorrectionsCorrectionMasksFullX)
MaskGroupBasedCorrMaskFullYreal/_(MaskGroupBasedCorrectionsCorrectionMasksFullY)
MaskGroupBasedCorrMaskInputDigeststring/_(MaskGroupBasedCorrectionsCorrectionMasksInputDigest)
MaskGroupBasedCorrMaskLeftreal/_(MaskGroupBasedCorrectionsCorrectionMasksLeft)
MaskGroupBasedCorrMaskMaskActiveboolean/_(MaskGroupBasedCorrectionsCorrectionMasksMaskActive)
MaskGroupBasedCorrMaskMaskBlendModeinteger/_(MaskGroupBasedCorrectionsCorrectionMasksMaskBlendMode)
MaskGroupBasedCorrMaskMaskDigeststring/_(MaskGroupBasedCorrectionsCorrectionMasksMaskDigest)
MaskGroupBasedCorrMaskMaskInvertedboolean/_(MaskGroupBasedCorrectionsCorrectionMasksMaskInverted)
MaskGroupBasedCorrMaskMaskNamestring/_(MaskGroupBasedCorrectionsCorrectionMasksMaskName)
MaskGroupBasedCorrMaskMasksstruct_+--> CorrectionMask Struct +
(MaskGroupBasedCorrectionsCorrectionMasksMasks)
MaskGroupBasedCorrMaskMasksAlphareal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksAlpha)
MaskGroupBasedCorrMaskMasksAnglereal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksAngle)
MaskGroupBasedCorrMaskMasksBottomreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksBottom)
MaskGroupBasedCorrMaskMasksCenterValuereal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksCenterValue)
MaskGroupBasedCorrMaskMasksCenterWeightreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksCenterWeight)
MaskGroupBasedCorrMaskMasksDabsstring/_+(MaskGroupBasedCorrectionsCorrectionMasksMasksDabs)
MaskGroupBasedCorrMaskMasksFeatherreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksFeather)
MaskGroupBasedCorrMaskMasksFlippedboolean/_(MaskGroupBasedCorrectionsCorrectionMasksMasksFlipped)
MaskGroupBasedCorrMaskMasksFlowreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksFlow)
MaskGroupBasedCorrMaskMasksFullXreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksFullX)
MaskGroupBasedCorrMaskMasksFullYreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksFullY)
MaskGroupBasedCorrMaskMasksInputDigeststring/_(MaskGroupBasedCorrectionsCorrectionMasksMasksInputDigest)
MaskGroupBasedCorrMaskMasksLeftreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksLeft)
MaskGroupBasedCorrMaskMasksMaskActiveboolean/_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskActive)
MaskGroupBasedCorrMaskMasksMaskBlendModeinteger/_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskBlendMode)
MaskGroupBasedCorrMaskMasksMaskDigeststring/_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskDigest)
MaskGroupBasedCorrMaskMasksMaskInvertedboolean/_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskInverted)
MaskGroupBasedCorrMaskMasksMaskNamestring/_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskName)
MaskGroupBasedCorrMaskMasksMaskSubTypestring/_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskSubType)
MaskGroupBasedCorrMaskMasksMaskSyncIDstring/_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskSyncID)
MaskGroupBasedCorrMaskMasksValuereal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskValue)
MaskGroupBasedCorrMaskMasksMaskVersionstring/_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskVersion)
MaskGroupBasedCorrMaskMasksMidpointreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksMidpoint)
MaskGroupBasedCorrMaskMasksOriginstring/_(MaskGroupBasedCorrectionsCorrectionMasksMasksOrigin)
MaskGroupBasedCorrMaskMasksPerimeterValuereal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksPerimeterValue)
MaskGroupBasedCorrMaskMasksRadiusreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksRadius)
MaskGroupBasedCorrMaskMasksReferencePointstring/_(MaskGroupBasedCorrectionsCorrectionMasksMasksReferencePoint)
MaskGroupBasedCorrMaskMasksRightreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksRight)
MaskGroupBasedCorrMaskMasksRoundnessreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksRoundness)
MaskGroupBasedCorrMaskMasksSizeXreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksSizeX)
MaskGroupBasedCorrMaskMasksSizeYreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksSizeY)
MaskGroupBasedCorrMaskMasksTopreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksTop)
MaskGroupBasedCorrMaskMaskSubTypestring/_(MaskGroupBasedCorrectionsCorrectionMasksMaskSubType)
MaskGroupBasedCorrMaskMasksVersioninteger/_(MaskGroupBasedCorrectionsCorrectionMasksMasksVersion)
MaskGroupBasedCorrMaskMasksWhatstring/_(MaskGroupBasedCorrectionsCorrectionMasksMasksWhat)
MaskGroupBasedCorrMaskMasksWholeImageAreastring/_(MaskGroupBasedCorrectionsCorrectionMasksMasksWholeImageArea)
MaskGroupBasedCorrMaskMasksXreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksX)
MaskGroupBasedCorrMaskMasksYreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksY)
MaskGroupBasedCorrMaskMaskSyncIDstring/_(MaskGroupBasedCorrectionsCorrectionMasksMaskSyncID)
MaskGroupBasedCorrMaskMasksZeroXreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksZeroX)
MaskGroupBasedCorrMaskMasksZeroYreal/_(MaskGroupBasedCorrectionsCorrectionMasksMasksZeroY)
MaskGroupBasedCorrMaskValuereal/_(MaskGroupBasedCorrectionsCorrectionMasksMaskValue)
MaskGroupBasedCorrMaskMaskVersionstring/_(MaskGroupBasedCorrectionsCorrectionMasksMaskVersion)
MaskGroupBasedCorrMaskMidpointreal/_(MaskGroupBasedCorrectionsCorrectionMasksMidpoint)
MaskGroupBasedCorrMaskOriginstring/_(MaskGroupBasedCorrectionsCorrectionMasksOrigin)
MaskGroupBasedCorrMaskPerimeterValuereal/_(MaskGroupBasedCorrectionsCorrectionMasksPerimeterValue)
MaskGroupBasedCorrMaskRadiusreal/_(MaskGroupBasedCorrectionsCorrectionMasksRadius)
MaskGroupBasedCorrMaskReferencePointstring/_(MaskGroupBasedCorrectionsCorrectionMasksReferencePoint)
MaskGroupBasedCorrMaskRightreal/_(MaskGroupBasedCorrectionsCorrectionMasksRight)
MaskGroupBasedCorrMaskRoundnessreal/_(MaskGroupBasedCorrectionsCorrectionMasksRoundness)
MaskGroupBasedCorrMaskSizeXreal/_(MaskGroupBasedCorrectionsCorrectionMasksSizeX)
MaskGroupBasedCorrMaskSizeYreal/_(MaskGroupBasedCorrectionsCorrectionMasksSizeY)
MaskGroupBasedCorrMaskTopreal/_(MaskGroupBasedCorrectionsCorrectionMasksTop)
MaskGroupBasedCorrMaskVersioninteger/_(MaskGroupBasedCorrectionsCorrectionMasksVersion)
MaskGroupBasedCorrMaskWhatstring/_(MaskGroupBasedCorrectionsCorrectionMasksWhat)
MaskGroupBasedCorrMaskWholeImageAreastring/_(MaskGroupBasedCorrectionsCorrectionMasksWholeImageArea)
MaskGroupBasedCorrMaskXreal/_(MaskGroupBasedCorrectionsCorrectionMasksX)
MaskGroupBasedCorrMaskYreal/_(MaskGroupBasedCorrectionsCorrectionMasksY)
MaskGroupBasedCorrMaskZeroXreal/_(MaskGroupBasedCorrectionsCorrectionMasksZeroX)
MaskGroupBasedCorrMaskZeroYreal/_(MaskGroupBasedCorrectionsCorrectionMasksZeroY)
MaskGroupBasedCorrCorrectionNamestring/_+(MaskGroupBasedCorrectionsCorrectionName)
MaskGroupBasedCorrRangeMaskstruct_+--> CorrRangeMask Struct +
(MaskGroupBasedCorrectionsCorrectionRangeMask; called CorrectionRangeMask by the spec)
MaskGroupBasedCorrRangeMaskAreaModelsstruct_+--> AreaModels Struct +
(MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModels)
MaskGroupBasedCorrRangeMaskAreaModelsComponentsstring/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents)
MaskGroupBasedCorrRangeMaskAreaModelsColorSampleInfostring/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
MaskGroupBasedCorrRangeMaskColorAmountreal/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskColorAmount)
MaskGroupBasedCorrRangeMaskDepthFeatherreal/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskDepthFeather)
MaskGroupBasedCorrRangeMaskDepthMaxreal/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskDepthMax)
MaskGroupBasedCorrRangeMaskDepthMinreal/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskDepthMin)
MaskGroupBasedCorrRangeMaskInvertboolean/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskInvert)
MaskGroupBasedCorrRangeMaskLumFeatherreal/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskLumFeather)
MaskGroupBasedCorrRangeMaskLuminanceDepthSampleInfostring/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo)
MaskGroupBasedCorrRangeMaskLumMaxreal/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskLumMax)
MaskGroupBasedCorrRangeMaskLumMinreal/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskLumMin)
MaskGroupBasedCorrRangeMaskLumRangestring/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskLumRange)
MaskGroupBasedCorrRangeMaskSampleTypeinteger/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskSampleType)
MaskGroupBasedCorrRangeMaskTypestring/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskType)
MaskGroupBasedCorrRangeMaskVersionstring/_+(MaskGroupBasedCorrectionsCorrectionRangeMaskVersion)
MaskGroupBasedCorrCorrectionSyncIDstring/_+(MaskGroupBasedCorrectionsCorrectionSyncID)
MaskGroupBasedCorrBlacks2012real/_(MaskGroupBasedCorrectionsLocalBlacks2012)
MaskGroupBasedCorrBrightnessreal/_(MaskGroupBasedCorrectionsLocalBrightness)
MaskGroupBasedCorrClarityreal/_(MaskGroupBasedCorrectionsLocalClarity)
MaskGroupBasedCorrClarity2012real/_(MaskGroupBasedCorrectionsLocalClarity2012)
MaskGroupBasedCorrContrastreal/_(MaskGroupBasedCorrectionsLocalContrast)
MaskGroupBasedCorrContrast2012real/_(MaskGroupBasedCorrectionsLocalContrast2012)
MaskGroupBasedCorrDefringereal/_(MaskGroupBasedCorrectionsLocalDefringe)
MaskGroupBasedCorrDehazereal/_(MaskGroupBasedCorrectionsLocalDehaze)
MaskGroupBasedCorrExposurereal/_(MaskGroupBasedCorrectionsLocalExposure)
MaskGroupBasedCorrExposure2012real/_(MaskGroupBasedCorrectionsLocalExposure2012)
MaskGroupBasedCorrHighlights2012real/_(MaskGroupBasedCorrectionsLocalHighlights2012)
MaskGroupBasedCorrHuereal/_(MaskGroupBasedCorrectionsLocalHue)
MaskGroupBasedCorrLuminanceNoisereal/_(MaskGroupBasedCorrectionsLocalLuminanceNoise)
MaskGroupBasedCorrMoirereal/_(MaskGroupBasedCorrectionsLocalMoire)
MaskGroupBasedCorrSaturationreal/_(MaskGroupBasedCorrectionsLocalSaturation)
MaskGroupBasedCorrShadows2012real/_(MaskGroupBasedCorrectionsLocalShadows2012)
MaskGroupBasedCorrSharpnessreal/_(MaskGroupBasedCorrectionsLocalSharpness)
MaskGroupBasedCorrTemperaturereal/_(MaskGroupBasedCorrectionsLocalTemperature)
MaskGroupBasedCorrTexturereal/_(MaskGroupBasedCorrectionsLocalTexture)
MaskGroupBasedCorrTintreal/_(MaskGroupBasedCorrectionsLocalTint)
MaskGroupBasedCorrToningHuereal/_(MaskGroupBasedCorrectionsLocalToningHue)
MaskGroupBasedCorrToningSaturationreal/_(MaskGroupBasedCorrectionsLocalToningSaturation)
MaskGroupBasedCorrWhites2012real/_(MaskGroupBasedCorrectionsLocalWhites2012)
MaskGroupBasedCorrWhatstring/_(MaskGroupBasedCorrectionsWhat)
MoireFilterstring/'Off' = Off +
'On' = On
Namelang-alt/ 
NegativeCacheLargePreviewSizeinteger/ 
NegativeCacheMaximumSizereal/ 
NegativeCachePathstring/ 
OverrideLookVignetteboolean/ 
PaintBasedCorrectionsstruct+--> Correction Struct
PaintCorrectionActiveboolean/_(PaintBasedCorrectionsCorrectionActive)
PaintCorrectionAmountreal/_(PaintBasedCorrectionsCorrectionAmount)
PaintBasedCorrectionMasksstruct_+--> CorrectionMask Struct +
(PaintBasedCorrectionsCorrectionMasks)
PaintCorrectionMaskAlphareal/_(PaintBasedCorrectionsCorrectionMasksAlpha)
PaintCorrectionMaskAnglereal/_(PaintBasedCorrectionsCorrectionMasksAngle)
PaintCorrectionMaskBottomreal/_(PaintBasedCorrectionsCorrectionMasksBottom)
PaintCorrectionMaskCenterValuereal/_(PaintBasedCorrectionsCorrectionMasksCenterValue)
PaintCorrectionMaskCenterWeightreal/_(PaintBasedCorrectionsCorrectionMasksCenterWeight)
PaintCorrectionMaskRangestruct_+--> CorrRangeMask Struct +
(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMask; called CorrectionRangeMask by the spec)
PaintCorrectionMaskRangeAreaModelsstruct_+--> AreaModels Struct +
(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels)
PaintCorrectionMaskRangeAreaModelsComponentsstring/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents)
PaintCorrectionMaskRangeAreaModelsColorSampleInfostring/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
PaintCorrectionMaskRangeColorAmountreal/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount)
PaintCorrectionMaskRangeDepthFeatherreal/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather)
PaintCorrectionMaskRangeDepthMaxreal/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax)
PaintCorrectionMaskRangeDepthMinreal/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin)
PaintCorrectionMaskRangeInvertboolean/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert)
PaintCorrectionMaskRangeLumFeatherreal/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather)
PaintCorrectionMaskRangeLuminanceDepthSampleInfostring/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo)
PaintCorrectionMaskRangeLumMaxreal/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax)
PaintCorrectionMaskRangeLumMinreal/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin)
PaintCorrectionMaskRangeLumRangestring/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange)
PaintCorrectionMaskRangeSampleTypeinteger/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType)
PaintCorrectionMaskRangeTypestring/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskType)
PaintCorrectionMaskRangeVersionstring/_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion)
PaintCorrectionMaskDabsstring/_(PaintBasedCorrectionsCorrectionMasksDabs)
PaintCorrectionMaskFeatherreal/_(PaintBasedCorrectionsCorrectionMasksFeather)
PaintCorrectionMaskFlippedboolean/_(PaintBasedCorrectionsCorrectionMasksFlipped)
PaintCorrectionMaskFlowreal/_(PaintBasedCorrectionsCorrectionMasksFlow)
PaintCorrectionMaskFullXreal/_(PaintBasedCorrectionsCorrectionMasksFullX)
PaintCorrectionMaskFullYreal/_(PaintBasedCorrectionsCorrectionMasksFullY)
PaintCorrectionMaskInputDigeststring/_(PaintBasedCorrectionsCorrectionMasksInputDigest)
PaintCorrectionMaskLeftreal/_(PaintBasedCorrectionsCorrectionMasksLeft)
PaintCorrectionMaskMaskActiveboolean/_(PaintBasedCorrectionsCorrectionMasksMaskActive)
PaintCorrectionMaskMaskBlendModeinteger/_(PaintBasedCorrectionsCorrectionMasksMaskBlendMode)
PaintCorrectionMaskMaskDigeststring/_(PaintBasedCorrectionsCorrectionMasksMaskDigest)
PaintCorrectionMaskMaskInvertedboolean/_(PaintBasedCorrectionsCorrectionMasksMaskInverted)
PaintCorrectionMaskMaskNamestring/_(PaintBasedCorrectionsCorrectionMasksMaskName)
PaintCorrectionMaskMasksstruct_+--> CorrectionMask Struct +
(PaintBasedCorrectionsCorrectionMasksMasks)
PaintCorrectionMaskMasksAlphareal/_(PaintBasedCorrectionsCorrectionMasksMasksAlpha)
PaintCorrectionMaskMasksAnglereal/_(PaintBasedCorrectionsCorrectionMasksMasksAngle)
PaintCorrectionMaskMasksBottomreal/_(PaintBasedCorrectionsCorrectionMasksMasksBottom)
PaintCorrectionMaskMasksCenterValuereal/_(PaintBasedCorrectionsCorrectionMasksMasksCenterValue)
PaintCorrectionMaskMasksCenterWeightreal/_(PaintBasedCorrectionsCorrectionMasksMasksCenterWeight)
PaintCorrectionMaskMasksDabsstring/_+(PaintBasedCorrectionsCorrectionMasksMasksDabs)
PaintCorrectionMaskMasksFeatherreal/_(PaintBasedCorrectionsCorrectionMasksMasksFeather)
PaintCorrectionMaskMasksFlippedboolean/_(PaintBasedCorrectionsCorrectionMasksMasksFlipped)
PaintCorrectionMaskMasksFlowreal/_(PaintBasedCorrectionsCorrectionMasksMasksFlow)
PaintCorrectionMaskMasksFullXreal/_(PaintBasedCorrectionsCorrectionMasksMasksFullX)
PaintCorrectionMaskMasksFullYreal/_(PaintBasedCorrectionsCorrectionMasksMasksFullY)
PaintCorrectionMaskMasksInputDigeststring/_(PaintBasedCorrectionsCorrectionMasksMasksInputDigest)
PaintCorrectionMaskMasksLeftreal/_(PaintBasedCorrectionsCorrectionMasksMasksLeft)
PaintCorrectionMaskMasksMaskActiveboolean/_(PaintBasedCorrectionsCorrectionMasksMasksMaskActive)
PaintCorrectionMaskMasksMaskBlendModeinteger/_(PaintBasedCorrectionsCorrectionMasksMasksMaskBlendMode)
PaintCorrectionMaskMasksMaskDigeststring/_(PaintBasedCorrectionsCorrectionMasksMasksMaskDigest)
PaintCorrectionMaskMasksMaskInvertedboolean/_(PaintBasedCorrectionsCorrectionMasksMasksMaskInverted)
PaintCorrectionMaskMasksMaskNamestring/_(PaintBasedCorrectionsCorrectionMasksMasksMaskName)
PaintCorrectionMaskMasksMaskSubTypestring/_(PaintBasedCorrectionsCorrectionMasksMasksMaskSubType)
PaintCorrectionMaskMasksMaskSyncIDstring/_(PaintBasedCorrectionsCorrectionMasksMasksMaskSyncID)
PaintCorrectionMaskMasksValuereal/_(PaintBasedCorrectionsCorrectionMasksMasksMaskValue)
PaintCorrectionMaskMasksMaskVersionstring/_(PaintBasedCorrectionsCorrectionMasksMasksMaskVersion)
PaintCorrectionMaskMasksMidpointreal/_(PaintBasedCorrectionsCorrectionMasksMasksMidpoint)
PaintCorrectionMaskMasksOriginstring/_(PaintBasedCorrectionsCorrectionMasksMasksOrigin)
PaintCorrectionMaskMasksPerimeterValuereal/_(PaintBasedCorrectionsCorrectionMasksMasksPerimeterValue)
PaintCorrectionMaskMasksRadiusreal/_(PaintBasedCorrectionsCorrectionMasksMasksRadius)
PaintCorrectionMaskMasksReferencePointstring/_(PaintBasedCorrectionsCorrectionMasksMasksReferencePoint)
PaintCorrectionMaskMasksRightreal/_(PaintBasedCorrectionsCorrectionMasksMasksRight)
PaintCorrectionMaskMasksRoundnessreal/_(PaintBasedCorrectionsCorrectionMasksMasksRoundness)
PaintCorrectionMaskMasksSizeXreal/_(PaintBasedCorrectionsCorrectionMasksMasksSizeX)
PaintCorrectionMaskMasksSizeYreal/_(PaintBasedCorrectionsCorrectionMasksMasksSizeY)
PaintCorrectionMaskMasksTopreal/_(PaintBasedCorrectionsCorrectionMasksMasksTop)
PaintCorrectionMaskMaskSubTypestring/_(PaintBasedCorrectionsCorrectionMasksMaskSubType)
PaintCorrectionMaskMasksVersioninteger/_(PaintBasedCorrectionsCorrectionMasksMasksVersion)
PaintCorrectionMaskMasksWhatstring/_(PaintBasedCorrectionsCorrectionMasksMasksWhat)
PaintCorrectionMaskMasksWholeImageAreastring/_(PaintBasedCorrectionsCorrectionMasksMasksWholeImageArea)
PaintCorrectionMaskMasksXreal/_(PaintBasedCorrectionsCorrectionMasksMasksX)
PaintCorrectionMaskMasksYreal/_(PaintBasedCorrectionsCorrectionMasksMasksY)
PaintCorrectionMaskMaskSyncIDstring/_(PaintBasedCorrectionsCorrectionMasksMaskSyncID)
PaintCorrectionMaskMasksZeroXreal/_(PaintBasedCorrectionsCorrectionMasksMasksZeroX)
PaintCorrectionMaskMasksZeroYreal/_(PaintBasedCorrectionsCorrectionMasksMasksZeroY)
PaintCorrectionMaskValuereal/_(PaintBasedCorrectionsCorrectionMasksMaskValue)
PaintCorrectionMaskMaskVersionstring/_(PaintBasedCorrectionsCorrectionMasksMaskVersion)
PaintCorrectionMaskMidpointreal/_(PaintBasedCorrectionsCorrectionMasksMidpoint)
PaintCorrectionMaskOriginstring/_(PaintBasedCorrectionsCorrectionMasksOrigin)
PaintCorrectionMaskPerimeterValuereal/_(PaintBasedCorrectionsCorrectionMasksPerimeterValue)
PaintCorrectionMaskRadiusreal/_(PaintBasedCorrectionsCorrectionMasksRadius)
PaintCorrectionMaskReferencePointstring/_(PaintBasedCorrectionsCorrectionMasksReferencePoint)
PaintCorrectionMaskRightreal/_(PaintBasedCorrectionsCorrectionMasksRight)
PaintCorrectionMaskRoundnessreal/_(PaintBasedCorrectionsCorrectionMasksRoundness)
PaintCorrectionMaskSizeXreal/_(PaintBasedCorrectionsCorrectionMasksSizeX)
PaintCorrectionMaskSizeYreal/_(PaintBasedCorrectionsCorrectionMasksSizeY)
PaintCorrectionMaskTopreal/_(PaintBasedCorrectionsCorrectionMasksTop)
PaintCorrectionMaskVersioninteger/_(PaintBasedCorrectionsCorrectionMasksVersion)
PaintCorrectionMaskWhatstring/_(PaintBasedCorrectionsCorrectionMasksWhat)
PaintCorrectionMaskWholeImageAreastring/_(PaintBasedCorrectionsCorrectionMasksWholeImageArea)
PaintCorrectionMaskXreal/_(PaintBasedCorrectionsCorrectionMasksX)
PaintCorrectionMaskYreal/_(PaintBasedCorrectionsCorrectionMasksY)
PaintCorrectionMaskZeroXreal/_(PaintBasedCorrectionsCorrectionMasksZeroX)
PaintCorrectionMaskZeroYreal/_(PaintBasedCorrectionsCorrectionMasksZeroY)
PaintCorrectionCorrectionNamestring/_+(PaintBasedCorrectionsCorrectionName)
PaintCorrectionRangeMaskstruct_+--> CorrRangeMask Struct +
(PaintBasedCorrectionsCorrectionRangeMask; called CorrectionRangeMask by the spec)
PaintCorrectionRangeMaskAreaModelsstruct_+--> AreaModels Struct +
(PaintBasedCorrectionsCorrectionRangeMaskAreaModels)
PaintCorrectionRangeMaskAreaModelsComponentsstring/_+(PaintBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents)
PaintCorrectionRangeMaskAreaModelsColorSampleInfostring/_+(PaintBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
PaintCorrectionRangeMaskColorAmountreal/_+(PaintBasedCorrectionsCorrectionRangeMaskColorAmount)
PaintCorrectionRangeMaskDepthFeatherreal/_+(PaintBasedCorrectionsCorrectionRangeMaskDepthFeather)
PaintCorrectionRangeMaskDepthMaxreal/_+(PaintBasedCorrectionsCorrectionRangeMaskDepthMax)
PaintCorrectionRangeMaskDepthMinreal/_+(PaintBasedCorrectionsCorrectionRangeMaskDepthMin)
PaintCorrectionRangeMaskInvertboolean/_+(PaintBasedCorrectionsCorrectionRangeMaskInvert)
PaintCorrectionRangeMaskLumFeatherreal/_+(PaintBasedCorrectionsCorrectionRangeMaskLumFeather)
PaintCorrectionRangeMaskLuminanceDepthSampleInfostring/_+(PaintBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo)
PaintCorrectionRangeMaskLumMaxreal/_+(PaintBasedCorrectionsCorrectionRangeMaskLumMax)
PaintCorrectionRangeMaskLumMinreal/_+(PaintBasedCorrectionsCorrectionRangeMaskLumMin)
PaintCorrectionRangeMaskLumRangestring/_+(PaintBasedCorrectionsCorrectionRangeMaskLumRange)
PaintCorrectionRangeMaskSampleTypeinteger/_+(PaintBasedCorrectionsCorrectionRangeMaskSampleType)
PaintCorrectionRangeMaskTypestring/_+(PaintBasedCorrectionsCorrectionRangeMaskType)
PaintCorrectionRangeMaskVersionstring/_+(PaintBasedCorrectionsCorrectionRangeMaskVersion)
PaintCorrectionCorrectionSyncIDstring/_+(PaintBasedCorrectionsCorrectionSyncID)
PaintCorrectionBlacks2012real/_(PaintBasedCorrectionsLocalBlacks2012)
PaintCorrectionBrightnessreal/_(PaintBasedCorrectionsLocalBrightness)
PaintCorrectionClarityreal/_(PaintBasedCorrectionsLocalClarity)
PaintCorrectionClarity2012real/_(PaintBasedCorrectionsLocalClarity2012)
PaintCorrectionContrastreal/_(PaintBasedCorrectionsLocalContrast)
PaintCorrectionContrast2012real/_(PaintBasedCorrectionsLocalContrast2012)
PaintCorrectionDefringereal/_(PaintBasedCorrectionsLocalDefringe)
PaintCorrectionDehazereal/_(PaintBasedCorrectionsLocalDehaze)
PaintCorrectionExposurereal/_(PaintBasedCorrectionsLocalExposure)
PaintCorrectionExposure2012real/_(PaintBasedCorrectionsLocalExposure2012)
PaintCorrectionHighlights2012real/_(PaintBasedCorrectionsLocalHighlights2012)
PaintCorrectionHuereal/_(PaintBasedCorrectionsLocalHue)
PaintCorrectionLuminanceNoisereal/_(PaintBasedCorrectionsLocalLuminanceNoise)
PaintCorrectionMoirereal/_(PaintBasedCorrectionsLocalMoire)
PaintCorrectionSaturationreal/_(PaintBasedCorrectionsLocalSaturation)
PaintCorrectionShadows2012real/_(PaintBasedCorrectionsLocalShadows2012)
PaintCorrectionSharpnessreal/_(PaintBasedCorrectionsLocalSharpness)
PaintCorrectionTemperaturereal/_(PaintBasedCorrectionsLocalTemperature)
PaintCorrectionTexturereal/_(PaintBasedCorrectionsLocalTexture)
PaintCorrectionTintreal/_(PaintBasedCorrectionsLocalTint)
PaintCorrectionToningHuereal/_(PaintBasedCorrectionsLocalToningHue)
PaintCorrectionToningSaturationreal/_(PaintBasedCorrectionsLocalToningSaturation)
PaintCorrectionWhites2012real/_(PaintBasedCorrectionsLocalWhites2012)
PaintCorrectionWhatstring/_(PaintBasedCorrectionsWhat)
ParametricDarksinteger/ 
ParametricHighlightsinteger/ 
ParametricHighlightSplitinteger/ 
ParametricLightsinteger/ 
ParametricMidtoneSplitinteger/ 
ParametricShadowsinteger/ 
ParametricShadowSplitinteger/ 
PerspectiveAspectinteger/ 
PerspectiveHorizontalinteger/ 
PerspectiveRotatereal/ 
PerspectiveScaleinteger/ 
PerspectiveUprightinteger/ + +
0 = Off +
1 = Auto +
2 = Full
  3 = Level +
4 = Vertical +
5 = Guided
+
PerspectiveVerticalinteger/ 
PerspectiveXreal/ 
PerspectiveYreal/ 
PostCropVignetteAmountinteger/ 
PostCropVignetteFeatherinteger/ 
PostCropVignetteHighlightContrastinteger/ 
PostCropVignetteMidpointinteger/ 
PostCropVignetteRoundnessinteger/ 
PostCropVignetteStyleinteger/1 = Highlight Priority +
2 = Color Priority +
3 = Paint Overlay
PresetTypestring/ 
ProcessVersionstring/ 
RangeMaskstruct--> RangeMask Struct +
(called RangeMaskMapInfo by the spec)
RangeMaskMapInfostruct_--> MapInfo Struct +
(RangeMaskMapInfoRangeMaskMapInfo)
RangeMaskMapInfoLabMaxstring/_(RangeMaskMapInfoRangeMaskMapInfoLabMax)
RangeMaskMapInfoLabMinstring/_(RangeMaskMapInfoRangeMaskMapInfoLabMin)
RangeMaskMapInfoLumEqstring/_+(RangeMaskMapInfoRangeMaskMapInfoLumEq)
RangeMaskMapInfoRGBMaxstring/_(RangeMaskMapInfoRangeMaskMapInfoRGBMax)
RangeMaskMapInfoRGBMinstring/_(RangeMaskMapInfoRangeMaskMapInfoRGBMin)
RawFileNamestring/ 
RedEyeInfostring/+ 
RedHueinteger/ 
RedSaturationinteger/ 
RetouchAreasstruct+--> RetouchArea Struct
RetouchAreaFeatherreal/_(RetouchAreasFeather)
RetouchAreaMasksstruct_+--> CorrectionMask Struct +
(RetouchAreasMasks)
RetouchAreaMaskAlphareal/_(RetouchAreasMasksAlpha)
RetouchAreaMaskAnglereal/_(RetouchAreasMasksAngle)
RetouchAreaMaskBottomreal/_(RetouchAreasMasksBottom)
RetouchAreaMaskCenterValuereal/_(RetouchAreasMasksCenterValue)
RetouchAreaMaskCenterWeightreal/_(RetouchAreasMasksCenterWeight)
RetouchAreaMaskRangestruct_+--> CorrRangeMask Struct +
(RetouchAreasMasksCorrectionRangeMask; called CorrectionRangeMask by the spec)
RetouchAreaMaskRangeAreaModelsstruct_+--> AreaModels Struct +
(RetouchAreasMasksCorrectionRangeMaskAreaModels)
RetouchAreaMaskRangeAreaModelsComponentsstring/_+(RetouchAreasMasksCorrectionRangeMaskAreaModelsAreaComponents)
RetouchAreaMaskRangeAreaModelsColorSampleInfostring/_+(RetouchAreasMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
RetouchAreaMaskRangeColorAmountreal/_+(RetouchAreasMasksCorrectionRangeMaskColorAmount)
RetouchAreaMaskRangeDepthFeatherreal/_+(RetouchAreasMasksCorrectionRangeMaskDepthFeather)
RetouchAreaMaskRangeDepthMaxreal/_+(RetouchAreasMasksCorrectionRangeMaskDepthMax)
RetouchAreaMaskRangeDepthMinreal/_+(RetouchAreasMasksCorrectionRangeMaskDepthMin)
RetouchAreaMaskRangeInvertboolean/_+(RetouchAreasMasksCorrectionRangeMaskInvert)
RetouchAreaMaskRangeLumFeatherreal/_+(RetouchAreasMasksCorrectionRangeMaskLumFeather)
RetouchAreaMaskRangeLuminanceDepthSampleInfostring/_+(RetouchAreasMasksCorrectionRangeMaskLuminanceDepthSampleInfo)
RetouchAreaMaskRangeLumMaxreal/_+(RetouchAreasMasksCorrectionRangeMaskLumMax)
RetouchAreaMaskRangeLumMinreal/_+(RetouchAreasMasksCorrectionRangeMaskLumMin)
RetouchAreaMaskRangeLumRangestring/_+(RetouchAreasMasksCorrectionRangeMaskLumRange)
RetouchAreaMaskRangeSampleTypeinteger/_+(RetouchAreasMasksCorrectionRangeMaskSampleType)
RetouchAreaMaskRangeTypestring/_+(RetouchAreasMasksCorrectionRangeMaskType)
RetouchAreaMaskRangeVersionstring/_+(RetouchAreasMasksCorrectionRangeMaskVersion)
RetouchAreaMaskDabsstring/_(RetouchAreasMasksDabs)
RetouchAreaMaskFeatherreal/_(RetouchAreasMasksFeather)
RetouchAreaMaskFlippedboolean/_(RetouchAreasMasksFlipped)
RetouchAreaMaskFlowreal/_(RetouchAreasMasksFlow)
RetouchAreaMaskFullXreal/_(RetouchAreasMasksFullX)
RetouchAreaMaskFullYreal/_(RetouchAreasMasksFullY)
RetouchAreaMaskInputDigeststring/_(RetouchAreasMasksInputDigest)
RetouchAreaMaskLeftreal/_(RetouchAreasMasksLeft)
RetouchAreaMaskMaskActiveboolean/_(RetouchAreasMasksMaskActive)
RetouchAreaMaskMaskBlendModeinteger/_(RetouchAreasMasksMaskBlendMode)
RetouchAreaMaskMaskDigeststring/_(RetouchAreasMasksMaskDigest)
RetouchAreaMaskMaskInvertedboolean/_(RetouchAreasMasksMaskInverted)
RetouchAreaMaskMaskNamestring/_(RetouchAreasMasksMaskName)
RetouchAreaMaskMasksstruct_+--> CorrectionMask Struct +
(RetouchAreasMasksMasks)
RetouchAreaMaskMasksAlphareal/_(RetouchAreasMasksMasksAlpha)
RetouchAreaMaskMasksAnglereal/_(RetouchAreasMasksMasksAngle)
RetouchAreaMaskMasksBottomreal/_(RetouchAreasMasksMasksBottom)
RetouchAreaMaskMasksCenterValuereal/_(RetouchAreasMasksMasksCenterValue)
RetouchAreaMaskMasksCenterWeightreal/_(RetouchAreasMasksMasksCenterWeight)
RetouchAreaMaskMasksDabsstring/_+(RetouchAreasMasksMasksDabs)
RetouchAreaMaskMasksFeatherreal/_(RetouchAreasMasksMasksFeather)
RetouchAreaMaskMasksFlippedboolean/_(RetouchAreasMasksMasksFlipped)
RetouchAreaMaskMasksFlowreal/_(RetouchAreasMasksMasksFlow)
RetouchAreaMaskMasksFullXreal/_(RetouchAreasMasksMasksFullX)
RetouchAreaMaskMasksFullYreal/_(RetouchAreasMasksMasksFullY)
RetouchAreaMaskMasksInputDigeststring/_(RetouchAreasMasksMasksInputDigest)
RetouchAreaMaskMasksLeftreal/_(RetouchAreasMasksMasksLeft)
RetouchAreaMaskMasksMaskActiveboolean/_(RetouchAreasMasksMasksMaskActive)
RetouchAreaMaskMasksMaskBlendModeinteger/_(RetouchAreasMasksMasksMaskBlendMode)
RetouchAreaMaskMasksMaskDigeststring/_(RetouchAreasMasksMasksMaskDigest)
RetouchAreaMaskMasksMaskInvertedboolean/_(RetouchAreasMasksMasksMaskInverted)
RetouchAreaMaskMasksMaskNamestring/_(RetouchAreasMasksMasksMaskName)
RetouchAreaMaskMasksMaskSubTypestring/_(RetouchAreasMasksMasksMaskSubType)
RetouchAreaMaskMasksMaskSyncIDstring/_(RetouchAreasMasksMasksMaskSyncID)
RetouchAreaMaskMasksValuereal/_(RetouchAreasMasksMasksMaskValue)
RetouchAreaMaskMasksMaskVersionstring/_(RetouchAreasMasksMasksMaskVersion)
RetouchAreaMaskMasksMidpointreal/_(RetouchAreasMasksMasksMidpoint)
RetouchAreaMaskMasksOriginstring/_(RetouchAreasMasksMasksOrigin)
RetouchAreaMaskMasksPerimeterValuereal/_(RetouchAreasMasksMasksPerimeterValue)
RetouchAreaMaskMasksRadiusreal/_(RetouchAreasMasksMasksRadius)
RetouchAreaMaskMasksReferencePointstring/_(RetouchAreasMasksMasksReferencePoint)
RetouchAreaMaskMasksRightreal/_(RetouchAreasMasksMasksRight)
RetouchAreaMaskMasksRoundnessreal/_(RetouchAreasMasksMasksRoundness)
RetouchAreaMaskMasksSizeXreal/_(RetouchAreasMasksMasksSizeX)
RetouchAreaMaskMasksSizeYreal/_(RetouchAreasMasksMasksSizeY)
RetouchAreaMaskMasksTopreal/_(RetouchAreasMasksMasksTop)
RetouchAreaMaskMaskSubTypestring/_(RetouchAreasMasksMaskSubType)
RetouchAreaMaskMasksVersioninteger/_(RetouchAreasMasksMasksVersion)
RetouchAreaMaskMasksWhatstring/_(RetouchAreasMasksMasksWhat)
RetouchAreaMaskMasksWholeImageAreastring/_(RetouchAreasMasksMasksWholeImageArea)
RetouchAreaMaskMasksXreal/_(RetouchAreasMasksMasksX)
RetouchAreaMaskMasksYreal/_(RetouchAreasMasksMasksY)
RetouchAreaMaskMaskSyncIDstring/_(RetouchAreasMasksMaskSyncID)
RetouchAreaMaskMasksZeroXreal/_(RetouchAreasMasksMasksZeroX)
RetouchAreaMaskMasksZeroYreal/_(RetouchAreasMasksMasksZeroY)
RetouchAreaMaskValuereal/_(RetouchAreasMasksMaskValue)
RetouchAreaMaskMaskVersionstring/_(RetouchAreasMasksMaskVersion)
RetouchAreaMaskMidpointreal/_(RetouchAreasMasksMidpoint)
RetouchAreaMaskOriginstring/_(RetouchAreasMasksOrigin)
RetouchAreaMaskPerimeterValuereal/_(RetouchAreasMasksPerimeterValue)
RetouchAreaMaskRadiusreal/_(RetouchAreasMasksRadius)
RetouchAreaMaskReferencePointstring/_(RetouchAreasMasksReferencePoint)
RetouchAreaMaskRightreal/_(RetouchAreasMasksRight)
RetouchAreaMaskRoundnessreal/_(RetouchAreasMasksRoundness)
RetouchAreaMaskSizeXreal/_(RetouchAreasMasksSizeX)
RetouchAreaMaskSizeYreal/_(RetouchAreasMasksSizeY)
RetouchAreaMaskTopreal/_(RetouchAreasMasksTop)
RetouchAreaMaskVersioninteger/_(RetouchAreasMasksVersion)
RetouchAreaMaskWhatstring/_(RetouchAreasMasksWhat)
RetouchAreaMaskWholeImageAreastring/_(RetouchAreasMasksWholeImageArea)
RetouchAreaMaskXreal/_(RetouchAreasMasksX)
RetouchAreaMaskYreal/_(RetouchAreasMasksY)
RetouchAreaMaskZeroXreal/_(RetouchAreasMasksZeroX)
RetouchAreaMaskZeroYreal/_(RetouchAreasMasksZeroY)
RetouchAreaMethodstring/_(RetouchAreasMethod)
RetouchAreaOffsetYreal/_(RetouchAreasOffsetY)
RetouchAreaOpacityreal/_(RetouchAreasOpacity)
RetouchAreaSeedinteger/_(RetouchAreasSeed)
RetouchAreaSourceStatestring/_(RetouchAreasSourceState)
RetouchAreaSourceXreal/_(RetouchAreasSourceX)
RetouchAreaSpotTypestring/_(RetouchAreasSpotType)
RetouchInfostring/+ 
Saturationinteger/ 
SaturationAdjustmentAquainteger/ 
SaturationAdjustmentBlueinteger/ 
SaturationAdjustmentGreeninteger/ 
SaturationAdjustmentMagentainteger/ 
SaturationAdjustmentOrangeinteger/ 
SaturationAdjustmentPurpleinteger/ 
SaturationAdjustmentRedinteger/ 
SaturationAdjustmentYellowinteger/ 
SDRBlendreal/ 
SDRBrightnessreal/ 
SDRContrastreal/ 
SDRHighlightsreal/ 
SDRShadowsreal/ 
SDRWhitesreal/ 
Shadowsinteger/ 
Shadows2012integer/ 
ShadowTintinteger/ 
SharpenDetailinteger/ 
SharpenEdgeMaskinginteger/ 
SharpenRadiusreal/ 
Sharpnessinteger/ 
ShortNamelang-alt/ 
Smoothnessinteger/ 
SortNamelang-alt/ 
SplitToningBalanceinteger/(also used for newer ColorGrade settings)
SplitToningHighlightHueinteger/(also used for newer ColorGrade settings)
SplitToningHighlightSaturationinteger/(also used for newer ColorGrade settings)
SplitToningShadowHueinteger/(also used for newer ColorGrade settings)
SplitToningShadowSaturationinteger/(also used for newer ColorGrade settings)
SupportsAmountboolean/ 
SupportsColorboolean/ 
SupportsHighDynamicRangeboolean/ 
SupportsMonochromeboolean/ 
SupportsNormalDynamicRangeboolean/ 
SupportsOutputReferredboolean/ 
SupportsSceneReferredboolean/ 
ColorTemperatureinteger/(called Temperature by the spec)
Textureinteger/ 
TIFFHandlingstring/ 
Tintinteger/ 
ToggleStyleAmountinteger/ 
ToggleStyleDigeststring/ 
ToneCurvestring/+ 
ToneCurveBluestring/+ 
ToneCurveGreenstring/+ 
ToneCurveNamestring/'Custom' = Custom +
'Linear' = Linear +
'Medium Contrast' = Medium Contrast +
'Strong Contrast' = Strong Contrast
ToneCurveName2012string/ 
ToneCurvePV2012string/+ 
ToneCurvePV2012Bluestring/+ 
ToneCurvePV2012Greenstring/+ 
ToneCurvePV2012Redstring/+ 
ToneCurveRedstring/+ 
ToneMapStrengthreal/ 
UprightCenterModeinteger/ 
UprightCenterNormXreal/ 
UprightCenterNormYreal/ 
UprightDependentDigeststring/ 
UprightFocalLength35mmreal/ 
UprightFocalModeinteger/ 
UprightFourSegments_0string/ 
UprightFourSegments_1string/ 
UprightFourSegments_2string/ 
UprightFourSegments_3string/ 
UprightFourSegmentsCountinteger/ 
UprightGuidedDependentDigeststring/ 
UprightPreviewboolean/ 
UprightTransform_0string/ 
UprightTransform_1string/ 
UprightTransform_2string/ 
UprightTransform_3string/ 
UprightTransform_4string/ 
UprightTransform_5string/ 
UprightTransformCountinteger/ 
UprightVersioninteger/ 
UUIDstring/ 
Versionstring/ 
Vibranceinteger/ 
VignetteAmountinteger/ 
VignetteMidpointinteger/ 
Whatstring/ 
WhiteBalancestring/ +
'As Shot' = As Shot +
'Auto' = Auto +
'Cloudy' = Cloudy +
'Custom' = Custom +
'Daylight' = Daylight +
'Flash' = Flash +
'Fluorescent' = Fluorescent +
'Shade' = Shade +
'Tungsten' = Tungsten
+
Whites2012integer/ 
+ +

XMP Correction Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
CorrectionActiveboolean 
CorrectionAmountreal 
CorrectionMasksCorrectionMask+--> CorrectionMask Struct
CorrectionNamestring 
CorrRangeMaskCorrRangeMask(called CorrectionRangeMask by the spec) +
--> CorrRangeMask Struct
CorrectionSyncIDstring 
LocalBlacks2012real 
LocalBrightnessreal 
LocalClarityreal 
LocalClarity2012real 
LocalContrastreal 
LocalContrast2012real 
LocalDefringereal 
LocalDehazereal 
LocalExposurereal 
LocalExposure2012real 
LocalHighlights2012real 
LocalHuereal 
LocalLuminanceNoisereal 
LocalMoirereal 
LocalSaturationreal 
LocalShadows2012real 
LocalSharpnessreal 
LocalTemperaturereal 
LocalTexturereal 
LocalTintreal 
LocalToningHuereal 
LocalToningSaturationreal 
LocalWhites2012real 
Whatstring 
+ +

XMP CorrectionMask Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Alphareal 
Anglereal 
Bottomreal 
CenterValuereal 
CenterWeightreal 
CorrRangeMaskCorrRangeMask(called CorrectionRangeMask by the spec) +
--> CorrRangeMask Struct
Dabsstring+ 
Featherreal 
Flippedboolean 
Flowreal 
FullXreal 
FullYreal 
InputDigeststring 
Leftreal 
MaskActiveboolean 
MaskBlendModeinteger 
MaskDigeststring 
MaskInvertedboolean 
MaskNamestring 
MaskSubTypestring 
MaskSyncIDstring 
MaskValuereal 
MaskVersionstring 
MasksCorrectionMask--> CorrectionMask Struct
Midpointreal 
Originstring 
PerimeterValuereal 
Radiusreal 
ReferencePointstring 
Rightreal 
Roundnessreal 
SizeXreal 
SizeYreal 
Topreal 
Versioninteger 
Whatstring 
WholeImageAreastring 
Xreal 
Yreal 
ZeroXreal 
ZeroYreal 
+ +

XMP CorrRangeMask Struct

+

Called CorrectionRangeMask by the spec.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
AreaModelsAreaModels+--> AreaModels Struct
ColorAmountreal 
DepthFeatherreal 
DepthMaxreal 
DepthMinreal 
Invertboolean 
LumFeatherreal 
LumMaxreal 
LumMinreal 
LumRangestring 
LuminanceDepthSampleInfostring 
SampleTypeinteger 
Typestring 
Versionstring 
+ +

XMP AreaModels Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
AreaComponentsstring+ 
ColorRangeMaskAreaSampleInfostring 
+ +

XMP Look Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Amountstring 
Clusterstring 
Copyrightstring 
Grouplang-alt 
Namestring 
ParametersLookParms--> LookParms Struct
SupportsAmountstring 
SupportsMonochromestring 
SupportsOutputReferredstring 
UUIDstring 
+ +

XMP LookParms Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
CameraProfilestring 
Clarity2012string 
ConvertToGrayscalestring 
LookTablestring 
ProcessVersionstring 
ToneCurvePV2012string+ 
ToneCurvePV2012Bluestring+ 
ToneCurvePV2012Greenstring+ 
ToneCurvePV2012Redstring+ 
Versionstring 
+ +

XMP RangeMask Struct

+

This structure is actually called RangeMaskMapInfo, but it only contains one +element which is a RangeMaskMapInfo structure (Yes, really!). So these are +renamed to RangeMask and MapInfo respectively to avoid confusion and +redundancy in the tag names.

+
+
+ + + + + + + +
Field NameWritableValues / Notes
RangeMaskMapInfoMapInfo--> MapInfo Struct
+ +

XMP MapInfo Struct

+

Called RangeMaskMapInfo by the specification, the same as the containing +structure.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
LabMaxstring 
LabMinstring 
LumEqstring+ 
RGBMaxstring 
RGBMinstring 
+ +

XMP RetouchArea Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Featherreal 
MasksCorrectionMask+--> CorrectionMask Struct
Methodstring 
OffsetYreal 
Opacityreal 
Seedinteger 
SourceStatestring 
SourceXreal 
SpotTypestring 
+ +

XMP creatorAtom Tags

+

Adobe creatorAtom tags, written by After Effects.

+ +

These tags belong to the ExifTool XMP-creatorAtom family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AeProjectLinkstruct--> AEProjectLink Struct
AeProjectLinkCompositionIDstring_ 
AeProjectLinkFullPathstring_ 
AeProjectLinkRenderOutputModuleIndexstring_ 
AeProjectLinkRenderQueueItemIDstring_ 
AeProjectLinkRenderTimeStampinteger_ 
MacAtomstruct--> MacAtom Struct
MacAtomApplicationCodestring_ 
MacAtomInvocationAppleEventstring_ 
MacAtomPosixProjectPathstring_ 
WindowsAtomstruct--> WindowsAtom Struct
WindowsAtomExtensionstring_ 
WindowsAtomInvocationFlagsstring_ 
WindowsAtomUncProjectPathstring_ 
+ +

XMP AEProjectLink Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
CompositionIDstring 
FullPathstring 
RenderOutputModuleIndexstring 
RenderQueueItemIDstring 
RenderTimeStampinteger 
+ +

XMP MacAtom Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
ApplicationCodestring 
InvocationAppleEventstring 
PosixProjectPathstring 
+ +

XMP WindowsAtom Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Extensionstring 
InvocationFlagsstring 
UncProjectPathstring 
+ +

XMP crs Tags

+

Photoshop Camera Raw namespace tags. It is a shame that Adobe pollutes the +metadata space with these incredibly bulky image editing parameters.

+ +

These tags belong to the ExifTool XMP-crs family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AlreadyAppliedboolean 
AutoBrightnessboolean 
AutoContrastboolean 
AutoExposureboolean 
AutoLateralCAinteger 
AutoShadowsboolean 
AutoToneboolean 
AutoToneDigeststring 
AutoToneDigestNoSatstring 
AutoWhiteVersioninteger 
Blacks2012integer 
BlueHueinteger 
BlueSaturationinteger 
Brightnessinteger 
CameraModelRestrictionstring 
CameraProfilestring 
CameraProfileDigeststring 
ChromaticAberrationBinteger 
ChromaticAberrationRinteger 
CircularGradientBasedCorrectionsstruct+--> Correction Struct
CircGradBasedCorrActiveboolean_(CircularGradientBasedCorrectionsCorrectionActive)
CircGradBasedCorrAmountreal_(CircularGradientBasedCorrectionsCorrectionAmount)
CircGradBasedCorrMasksstruct_+--> CorrectionMask Struct +
(CircularGradientBasedCorrectionsCorrectionMasks)
CircGradBasedCorrMaskAlphareal_(CircularGradientBasedCorrectionsCorrectionMasksAlpha)
CircGradBasedCorrMaskAnglereal_(CircularGradientBasedCorrectionsCorrectionMasksAngle)
CircGradBasedCorrMaskBottomreal_(CircularGradientBasedCorrectionsCorrectionMasksBottom)
CircGradBasedCorrMaskCenterValuereal_(CircularGradientBasedCorrectionsCorrectionMasksCenterValue)
CircGradBasedCorrMaskCenterWeightreal_(CircularGradientBasedCorrectionsCorrectionMasksCenterWeight)
CircGradBasedCorrMaskRangestruct_+--> CorrRangeMask Struct +
(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMask; called CorrectionRangeMask by the spec)
CircGradBasedCorrMaskRangeAreaModelsstruct_+--> AreaModels Struct +
(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels)
CircGradBasedCorrMaskRangeAreaModelsComponentsstring_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents)
CircGradBasedCorrMaskRangeAreaModelsColorSampleInfostring_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
CircGradBasedCorrMaskRangeColorAmountreal_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount)
CircGradBasedCorrMaskRangeDepthFeatherreal_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather)
CircGradBasedCorrMaskRangeDepthMaxreal_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax)
CircGradBasedCorrMaskRangeDepthMinreal_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin)
CircGradBasedCorrMaskRangeInvertboolean_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert)
CircGradBasedCorrMaskRangeLumFeatherreal_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather)
CircGradBasedCorrMaskRangeLuminanceDepthSampleInfostring_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo)
CircGradBasedCorrMaskRangeLumMaxreal_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax)
CircGradBasedCorrMaskRangeLumMinreal_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin)
CircGradBasedCorrMaskRangeLumRangestring_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange)
CircGradBasedCorrMaskRangeSampleTypeinteger_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType)
CircGradBasedCorrMaskRangeTypestring_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskType)
CircGradBasedCorrMaskRangeVersionstring_+(CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion)
CircGradBasedCorrMaskDabsstring_(CircularGradientBasedCorrectionsCorrectionMasksDabs)
CircGradBasedCorrMaskFeatherreal_(CircularGradientBasedCorrectionsCorrectionMasksFeather)
CircGradBasedCorrMaskFlippedboolean_(CircularGradientBasedCorrectionsCorrectionMasksFlipped)
CircGradBasedCorrMaskFlowreal_(CircularGradientBasedCorrectionsCorrectionMasksFlow)
CircGradBasedCorrMaskFullXreal_(CircularGradientBasedCorrectionsCorrectionMasksFullX)
CircGradBasedCorrMaskFullYreal_(CircularGradientBasedCorrectionsCorrectionMasksFullY)
CircGradBasedCorrMaskInputDigeststring_(CircularGradientBasedCorrectionsCorrectionMasksInputDigest)
CircGradBasedCorrMaskLeftreal_(CircularGradientBasedCorrectionsCorrectionMasksLeft)
CircGradBasedCorrMaskMaskActiveboolean_(CircularGradientBasedCorrectionsCorrectionMasksMaskActive)
CircGradBasedCorrMaskMaskBlendModeinteger_(CircularGradientBasedCorrectionsCorrectionMasksMaskBlendMode)
CircGradBasedCorrMaskMaskDigeststring_(CircularGradientBasedCorrectionsCorrectionMasksMaskDigest)
CircGradBasedCorrMaskMaskInvertedboolean_(CircularGradientBasedCorrectionsCorrectionMasksMaskInverted)
CircGradBasedCorrMaskMaskNamestring_(CircularGradientBasedCorrectionsCorrectionMasksMaskName)
CircGradBasedCorrMaskMasksstruct_+--> CorrectionMask Struct +
(CircularGradientBasedCorrectionsCorrectionMasksMasks)
CircGradBasedCorrMaskMasksAlphareal_(CircularGradientBasedCorrectionsCorrectionMasksMasksAlpha)
CircGradBasedCorrMaskMasksAnglereal_(CircularGradientBasedCorrectionsCorrectionMasksMasksAngle)
CircGradBasedCorrMaskMasksBottomreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksBottom)
CircGradBasedCorrMaskMasksCenterValuereal_(CircularGradientBasedCorrectionsCorrectionMasksMasksCenterValue)
CircGradBasedCorrMaskMasksCenterWeightreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksCenterWeight)
CircGradBasedCorrMaskMasksDabsstring_+(CircularGradientBasedCorrectionsCorrectionMasksMasksDabs)
CircGradBasedCorrMaskMasksFeatherreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksFeather)
CircGradBasedCorrMaskMasksFlippedboolean_(CircularGradientBasedCorrectionsCorrectionMasksMasksFlipped)
CircGradBasedCorrMaskMasksFlowreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksFlow)
CircGradBasedCorrMaskMasksFullXreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksFullX)
CircGradBasedCorrMaskMasksFullYreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksFullY)
CircGradBasedCorrMaskMasksInputDigeststring_(CircularGradientBasedCorrectionsCorrectionMasksMasksInputDigest)
CircGradBasedCorrMaskMasksLeftreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksLeft)
CircGradBasedCorrMaskMasksMaskActiveboolean_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskActive)
CircGradBasedCorrMaskMasksMaskBlendModeinteger_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskBlendMode)
CircGradBasedCorrMaskMasksMaskDigeststring_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskDigest)
CircGradBasedCorrMaskMasksMaskInvertedboolean_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskInverted)
CircGradBasedCorrMaskMasksMaskNamestring_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskName)
CircGradBasedCorrMaskMasksMaskSubTypestring_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskSubType)
CircGradBasedCorrMaskMasksMaskSyncIDstring_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskSyncID)
CircGradBasedCorrMaskMasksValuereal_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskValue)
CircGradBasedCorrMaskMasksMaskVersionstring_(CircularGradientBasedCorrectionsCorrectionMasksMasksMaskVersion)
CircGradBasedCorrMaskMasksMidpointreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksMidpoint)
CircGradBasedCorrMaskMasksOriginstring_(CircularGradientBasedCorrectionsCorrectionMasksMasksOrigin)
CircGradBasedCorrMaskMasksPerimeterValuereal_(CircularGradientBasedCorrectionsCorrectionMasksMasksPerimeterValue)
CircGradBasedCorrMaskMasksRadiusreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksRadius)
CircGradBasedCorrMaskMasksReferencePointstring_(CircularGradientBasedCorrectionsCorrectionMasksMasksReferencePoint)
CircGradBasedCorrMaskMasksRightreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksRight)
CircGradBasedCorrMaskMasksRoundnessreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksRoundness)
CircGradBasedCorrMaskMasksSizeXreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksSizeX)
CircGradBasedCorrMaskMasksSizeYreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksSizeY)
CircGradBasedCorrMaskMasksTopreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksTop)
CircGradBasedCorrMaskMaskSubTypestring_(CircularGradientBasedCorrectionsCorrectionMasksMaskSubType)
CircGradBasedCorrMaskMasksVersioninteger_(CircularGradientBasedCorrectionsCorrectionMasksMasksVersion)
CircGradBasedCorrMaskMasksWhatstring_(CircularGradientBasedCorrectionsCorrectionMasksMasksWhat)
CircGradBasedCorrMaskMasksWholeImageAreastring_(CircularGradientBasedCorrectionsCorrectionMasksMasksWholeImageArea)
CircGradBasedCorrMaskMasksXreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksX)
CircGradBasedCorrMaskMasksYreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksY)
CircGradBasedCorrMaskMaskSyncIDstring_(CircularGradientBasedCorrectionsCorrectionMasksMaskSyncID)
CircGradBasedCorrMaskMasksZeroXreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksZeroX)
CircGradBasedCorrMaskMasksZeroYreal_(CircularGradientBasedCorrectionsCorrectionMasksMasksZeroY)
CircGradBasedCorrMaskValuereal_(CircularGradientBasedCorrectionsCorrectionMasksMaskValue)
CircGradBasedCorrMaskMaskVersionstring_(CircularGradientBasedCorrectionsCorrectionMasksMaskVersion)
CircGradBasedCorrMaskMidpointreal_(CircularGradientBasedCorrectionsCorrectionMasksMidpoint)
CircGradBasedCorrMaskOriginstring_(CircularGradientBasedCorrectionsCorrectionMasksOrigin)
CircGradBasedCorrMaskPerimeterValuereal_(CircularGradientBasedCorrectionsCorrectionMasksPerimeterValue)
CircGradBasedCorrMaskRadiusreal_(CircularGradientBasedCorrectionsCorrectionMasksRadius)
CircGradBasedCorrMaskReferencePointstring_(CircularGradientBasedCorrectionsCorrectionMasksReferencePoint)
CircGradBasedCorrMaskRightreal_(CircularGradientBasedCorrectionsCorrectionMasksRight)
CircGradBasedCorrMaskRoundnessreal_(CircularGradientBasedCorrectionsCorrectionMasksRoundness)
CircGradBasedCorrMaskSizeXreal_(CircularGradientBasedCorrectionsCorrectionMasksSizeX)
CircGradBasedCorrMaskSizeYreal_(CircularGradientBasedCorrectionsCorrectionMasksSizeY)
CircGradBasedCorrMaskTopreal_(CircularGradientBasedCorrectionsCorrectionMasksTop)
CircGradBasedCorrMaskVersioninteger_(CircularGradientBasedCorrectionsCorrectionMasksVersion)
CircGradBasedCorrMaskWhatstring_(CircularGradientBasedCorrectionsCorrectionMasksWhat)
CircGradBasedCorrMaskWholeImageAreastring_(CircularGradientBasedCorrectionsCorrectionMasksWholeImageArea)
CircGradBasedCorrMaskXreal_(CircularGradientBasedCorrectionsCorrectionMasksX)
CircGradBasedCorrMaskYreal_(CircularGradientBasedCorrectionsCorrectionMasksY)
CircGradBasedCorrMaskZeroXreal_(CircularGradientBasedCorrectionsCorrectionMasksZeroX)
CircGradBasedCorrMaskZeroYreal_(CircularGradientBasedCorrectionsCorrectionMasksZeroY)
CircGradBasedCorrCorrectionNamestring_+(CircularGradientBasedCorrectionsCorrectionName)
CircGradBasedCorrRangeMaskstruct_+--> CorrRangeMask Struct +
(CircularGradientBasedCorrectionsCorrectionRangeMask; called CorrectionRangeMask by the spec)
CircGradBasedCorrRangeMaskAreaModelsstruct_+--> AreaModels Struct +
(CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModels)
CircGradBasedCorrRangeMaskAreaModelsComponentsstring_+(CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents)
CircGradBasedCorrRangeMaskAreaModelsColorSampleInfostring_+(CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
CircGradBasedCorrRangeMaskColorAmountreal_+(CircularGradientBasedCorrectionsCorrectionRangeMaskColorAmount)
CircGradBasedCorrRangeMaskDepthFeatherreal_+(CircularGradientBasedCorrectionsCorrectionRangeMaskDepthFeather)
CircGradBasedCorrRangeMaskDepthMaxreal_+(CircularGradientBasedCorrectionsCorrectionRangeMaskDepthMax)
CircGradBasedCorrRangeMaskDepthMinreal_+(CircularGradientBasedCorrectionsCorrectionRangeMaskDepthMin)
CircGradBasedCorrRangeMaskInvertboolean_+(CircularGradientBasedCorrectionsCorrectionRangeMaskInvert)
CircGradBasedCorrRangeMaskLumFeatherreal_+(CircularGradientBasedCorrectionsCorrectionRangeMaskLumFeather)
CircGradBasedCorrRangeMaskLuminanceDepthSampleInfostring_+(CircularGradientBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo)
CircGradBasedCorrRangeMaskLumMaxreal_+(CircularGradientBasedCorrectionsCorrectionRangeMaskLumMax)
CircGradBasedCorrRangeMaskLumMinreal_+(CircularGradientBasedCorrectionsCorrectionRangeMaskLumMin)
CircGradBasedCorrRangeMaskLumRangestring_+(CircularGradientBasedCorrectionsCorrectionRangeMaskLumRange)
CircGradBasedCorrRangeMaskSampleTypeinteger_+(CircularGradientBasedCorrectionsCorrectionRangeMaskSampleType)
CircGradBasedCorrRangeMaskTypestring_+(CircularGradientBasedCorrectionsCorrectionRangeMaskType)
CircGradBasedCorrRangeMaskVersionstring_+(CircularGradientBasedCorrectionsCorrectionRangeMaskVersion)
CircGradBasedCorrCorrectionSyncIDstring_+(CircularGradientBasedCorrectionsCorrectionSyncID)
CircGradBasedCorrBlacks2012real_(CircularGradientBasedCorrectionsLocalBlacks2012)
CircGradBasedCorrBrightnessreal_(CircularGradientBasedCorrectionsLocalBrightness)
CircGradBasedCorrClarityreal_(CircularGradientBasedCorrectionsLocalClarity)
CircGradBasedCorrClarity2012real_(CircularGradientBasedCorrectionsLocalClarity2012)
CircGradBasedCorrContrastreal_(CircularGradientBasedCorrectionsLocalContrast)
CircGradBasedCorrContrast2012real_(CircularGradientBasedCorrectionsLocalContrast2012)
CircGradBasedCorrDefringereal_(CircularGradientBasedCorrectionsLocalDefringe)
CircGradBasedCorrDehazereal_(CircularGradientBasedCorrectionsLocalDehaze)
CircGradBasedCorrExposurereal_(CircularGradientBasedCorrectionsLocalExposure)
CircGradBasedCorrExposure2012real_(CircularGradientBasedCorrectionsLocalExposure2012)
CircGradBasedCorrHighlights2012real_(CircularGradientBasedCorrectionsLocalHighlights2012)
CircGradBasedCorrHuereal_(CircularGradientBasedCorrectionsLocalHue)
CircGradBasedCorrLuminanceNoisereal_(CircularGradientBasedCorrectionsLocalLuminanceNoise)
CircGradBasedCorrMoirereal_(CircularGradientBasedCorrectionsLocalMoire)
CircGradBasedCorrSaturationreal_(CircularGradientBasedCorrectionsLocalSaturation)
CircGradBasedCorrShadows2012real_(CircularGradientBasedCorrectionsLocalShadows2012)
CircGradBasedCorrSharpnessreal_(CircularGradientBasedCorrectionsLocalSharpness)
CircGradBasedCorrTemperaturereal_(CircularGradientBasedCorrectionsLocalTemperature)
CircGradBasedCorrTexturereal_(CircularGradientBasedCorrectionsLocalTexture)
CircGradBasedCorrTintreal_(CircularGradientBasedCorrectionsLocalTint)
CircGradBasedCorrToningHuereal_(CircularGradientBasedCorrectionsLocalToningHue)
CircGradBasedCorrToningSaturationreal_(CircularGradientBasedCorrectionsLocalToningSaturation)
CircGradBasedCorrWhites2012real_(CircularGradientBasedCorrectionsLocalWhites2012)
CircGradBasedCorrWhatstring_(CircularGradientBasedCorrectionsWhat)
Clarityinteger 
Clarity2012integer 
ClipboardAspectRatiointeger 
ClipboardOrientationinteger 
Clusterstring 
ColorGradeBlendinginteger 
ColorGradeGlobalHueinteger 
ColorGradeGlobalLuminteger 
ColorGradeGlobalSatinteger 
ColorGradeHighlightLuminteger 
ColorGradeMidtoneHueinteger 
ColorGradeMidtoneLuminteger 
ColorGradeMidtoneSatinteger 
ColorGradeShadowLuminteger 
ColorNoiseReductioninteger 
ColorNoiseReductionDetailinteger 
ColorNoiseReductionSmoothnessinteger 
CompatibleVersionstring 
ContactInfostring 
Contrastinteger/ 
Contrast2012integer 
Converterstring 
ConvertToGrayscaleboolean 
Copyrightstring/ 
CropAnglereal 
CropBottomreal 
CropConstrainToWarpinteger 
CropHeightreal 
CropLeftreal 
CropRightreal 
CropTopreal 
CropUnitinteger0 = pixels +
1 = inches +
2 = cm
CropUnitsinteger0 = pixels +
1 = inches +
2 = cm
CropWidthreal 
DefaultAutoGrayboolean 
DefaultAutoToneboolean 
DefaultsSpecificToISOboolean 
DefaultsSpecificToSerialboolean 
Defringeinteger 
DefringeGreenAmountinteger 
DefringeGreenHueHiinteger 
DefringeGreenHueLointeger 
DefringePurpleAmountinteger 
DefringePurpleHueHiinteger 
DefringePurpleHueLointeger 
Dehazereal 
Descriptionlang-alt/ 
DNGIgnoreSidecarsboolean 
Exposurereal 
Exposure2012real 
FillLightinteger 
GradientBasedCorrectionsstruct+--> Correction Struct
GradientBasedCorrActiveboolean_(GradientBasedCorrectionsCorrectionActive)
GradientBasedCorrAmountreal_(GradientBasedCorrectionsCorrectionAmount)
GradientBasedCorrMasksstruct_+--> CorrectionMask Struct +
(GradientBasedCorrectionsCorrectionMasks)
GradientBasedCorrMaskAlphareal_(GradientBasedCorrectionsCorrectionMasksAlpha)
GradientBasedCorrMaskAnglereal_(GradientBasedCorrectionsCorrectionMasksAngle)
GradientBasedCorrMaskBottomreal_(GradientBasedCorrectionsCorrectionMasksBottom)
GradientBasedCorrMaskCenterValuereal_(GradientBasedCorrectionsCorrectionMasksCenterValue)
GradientBasedCorrMaskCenterWeightreal_(GradientBasedCorrectionsCorrectionMasksCenterWeight)
GradientBasedCorrMaskRangestruct_+--> CorrRangeMask Struct +
(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMask; called CorrectionRangeMask by the spec)
GradientBasedCorrMaskRangeAreaModelsstruct_+--> AreaModels Struct +
(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels)
GradientBasedCorrMaskRangeAreaModelsComponentsstring_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents)
GradientBasedCorrMaskRangeAreaModelsColorSampleInfostring_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
GradientBasedCorrMaskRangeColorAmountreal_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount)
GradientBasedCorrMaskRangeDepthFeatherreal_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather)
GradientBasedCorrMaskRangeDepthMaxreal_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax)
GradientBasedCorrMaskRangeDepthMinreal_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin)
GradientBasedCorrMaskRangeInvertboolean_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert)
GradientBasedCorrMaskRangeLumFeatherreal_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather)
GradientBasedCorrMaskRangeLuminanceDepthSampleInfostring_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo)
GradientBasedCorrMaskRangeLumMaxreal_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax)
GradientBasedCorrMaskRangeLumMinreal_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin)
GradientBasedCorrMaskRangeLumRangestring_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange)
GradientBasedCorrMaskRangeSampleTypeinteger_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType)
GradientBasedCorrMaskRangeTypestring_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskType)
GradientBasedCorrMaskRangeVersionstring_+(GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion)
GradientBasedCorrMaskDabsstring_(GradientBasedCorrectionsCorrectionMasksDabs)
GradientBasedCorrMaskFeatherreal_(GradientBasedCorrectionsCorrectionMasksFeather)
GradientBasedCorrMaskFlippedboolean_(GradientBasedCorrectionsCorrectionMasksFlipped)
GradientBasedCorrMaskFlowreal_(GradientBasedCorrectionsCorrectionMasksFlow)
GradientBasedCorrMaskFullXreal_(GradientBasedCorrectionsCorrectionMasksFullX)
GradientBasedCorrMaskFullYreal_(GradientBasedCorrectionsCorrectionMasksFullY)
GradientBasedCorrMaskInputDigeststring_(GradientBasedCorrectionsCorrectionMasksInputDigest)
GradientBasedCorrMaskLeftreal_(GradientBasedCorrectionsCorrectionMasksLeft)
GradientBasedCorrMaskMaskActiveboolean_(GradientBasedCorrectionsCorrectionMasksMaskActive)
GradientBasedCorrMaskMaskBlendModeinteger_(GradientBasedCorrectionsCorrectionMasksMaskBlendMode)
GradientBasedCorrMaskMaskDigeststring_(GradientBasedCorrectionsCorrectionMasksMaskDigest)
GradientBasedCorrMaskMaskInvertedboolean_(GradientBasedCorrectionsCorrectionMasksMaskInverted)
GradientBasedCorrMaskMaskNamestring_(GradientBasedCorrectionsCorrectionMasksMaskName)
GradientBasedCorrMaskMasksstruct_+--> CorrectionMask Struct +
(GradientBasedCorrectionsCorrectionMasksMasks)
GradientBasedCorrMaskMasksAlphareal_(GradientBasedCorrectionsCorrectionMasksMasksAlpha)
GradientBasedCorrMaskMasksAnglereal_(GradientBasedCorrectionsCorrectionMasksMasksAngle)
GradientBasedCorrMaskMasksBottomreal_(GradientBasedCorrectionsCorrectionMasksMasksBottom)
GradientBasedCorrMaskMasksCenterValuereal_(GradientBasedCorrectionsCorrectionMasksMasksCenterValue)
GradientBasedCorrMaskMasksCenterWeightreal_(GradientBasedCorrectionsCorrectionMasksMasksCenterWeight)
GradientBasedCorrMaskMasksDabsstring_+(GradientBasedCorrectionsCorrectionMasksMasksDabs)
GradientBasedCorrMaskMasksFeatherreal_(GradientBasedCorrectionsCorrectionMasksMasksFeather)
GradientBasedCorrMaskMasksFlippedboolean_(GradientBasedCorrectionsCorrectionMasksMasksFlipped)
GradientBasedCorrMaskMasksFlowreal_(GradientBasedCorrectionsCorrectionMasksMasksFlow)
GradientBasedCorrMaskMasksFullXreal_(GradientBasedCorrectionsCorrectionMasksMasksFullX)
GradientBasedCorrMaskMasksFullYreal_(GradientBasedCorrectionsCorrectionMasksMasksFullY)
GradientBasedCorrMaskMasksInputDigeststring_(GradientBasedCorrectionsCorrectionMasksMasksInputDigest)
GradientBasedCorrMaskMasksLeftreal_(GradientBasedCorrectionsCorrectionMasksMasksLeft)
GradientBasedCorrMaskMasksMaskActiveboolean_(GradientBasedCorrectionsCorrectionMasksMasksMaskActive)
GradientBasedCorrMaskMasksMaskBlendModeinteger_(GradientBasedCorrectionsCorrectionMasksMasksMaskBlendMode)
GradientBasedCorrMaskMasksMaskDigeststring_(GradientBasedCorrectionsCorrectionMasksMasksMaskDigest)
GradientBasedCorrMaskMasksMaskInvertedboolean_(GradientBasedCorrectionsCorrectionMasksMasksMaskInverted)
GradientBasedCorrMaskMasksMaskNamestring_(GradientBasedCorrectionsCorrectionMasksMasksMaskName)
GradientBasedCorrMaskMasksMaskSubTypestring_(GradientBasedCorrectionsCorrectionMasksMasksMaskSubType)
GradientBasedCorrMaskMasksMaskSyncIDstring_(GradientBasedCorrectionsCorrectionMasksMasksMaskSyncID)
GradientBasedCorrMaskMasksValuereal_(GradientBasedCorrectionsCorrectionMasksMasksMaskValue)
GradientBasedCorrMaskMasksMaskVersionstring_(GradientBasedCorrectionsCorrectionMasksMasksMaskVersion)
GradientBasedCorrMaskMasksMidpointreal_(GradientBasedCorrectionsCorrectionMasksMasksMidpoint)
GradientBasedCorrMaskMasksOriginstring_(GradientBasedCorrectionsCorrectionMasksMasksOrigin)
GradientBasedCorrMaskMasksPerimeterValuereal_(GradientBasedCorrectionsCorrectionMasksMasksPerimeterValue)
GradientBasedCorrMaskMasksRadiusreal_(GradientBasedCorrectionsCorrectionMasksMasksRadius)
GradientBasedCorrMaskMasksReferencePointstring_(GradientBasedCorrectionsCorrectionMasksMasksReferencePoint)
GradientBasedCorrMaskMasksRightreal_(GradientBasedCorrectionsCorrectionMasksMasksRight)
GradientBasedCorrMaskMasksRoundnessreal_(GradientBasedCorrectionsCorrectionMasksMasksRoundness)
GradientBasedCorrMaskMasksSizeXreal_(GradientBasedCorrectionsCorrectionMasksMasksSizeX)
GradientBasedCorrMaskMasksSizeYreal_(GradientBasedCorrectionsCorrectionMasksMasksSizeY)
GradientBasedCorrMaskMasksTopreal_(GradientBasedCorrectionsCorrectionMasksMasksTop)
GradientBasedCorrMaskMaskSubTypestring_(GradientBasedCorrectionsCorrectionMasksMaskSubType)
GradientBasedCorrMaskMasksVersioninteger_(GradientBasedCorrectionsCorrectionMasksMasksVersion)
GradientBasedCorrMaskMasksWhatstring_(GradientBasedCorrectionsCorrectionMasksMasksWhat)
GradientBasedCorrMaskMasksWholeImageAreastring_(GradientBasedCorrectionsCorrectionMasksMasksWholeImageArea)
GradientBasedCorrMaskMasksXreal_(GradientBasedCorrectionsCorrectionMasksMasksX)
GradientBasedCorrMaskMasksYreal_(GradientBasedCorrectionsCorrectionMasksMasksY)
GradientBasedCorrMaskMaskSyncIDstring_(GradientBasedCorrectionsCorrectionMasksMaskSyncID)
GradientBasedCorrMaskMasksZeroXreal_(GradientBasedCorrectionsCorrectionMasksMasksZeroX)
GradientBasedCorrMaskMasksZeroYreal_(GradientBasedCorrectionsCorrectionMasksMasksZeroY)
GradientBasedCorrMaskValuereal_(GradientBasedCorrectionsCorrectionMasksMaskValue)
GradientBasedCorrMaskMaskVersionstring_(GradientBasedCorrectionsCorrectionMasksMaskVersion)
GradientBasedCorrMaskMidpointreal_(GradientBasedCorrectionsCorrectionMasksMidpoint)
GradientBasedCorrMaskOriginstring_(GradientBasedCorrectionsCorrectionMasksOrigin)
GradientBasedCorrMaskPerimeterValuereal_(GradientBasedCorrectionsCorrectionMasksPerimeterValue)
GradientBasedCorrMaskRadiusreal_(GradientBasedCorrectionsCorrectionMasksRadius)
GradientBasedCorrMaskReferencePointstring_(GradientBasedCorrectionsCorrectionMasksReferencePoint)
GradientBasedCorrMaskRightreal_(GradientBasedCorrectionsCorrectionMasksRight)
GradientBasedCorrMaskRoundnessreal_(GradientBasedCorrectionsCorrectionMasksRoundness)
GradientBasedCorrMaskSizeXreal_(GradientBasedCorrectionsCorrectionMasksSizeX)
GradientBasedCorrMaskSizeYreal_(GradientBasedCorrectionsCorrectionMasksSizeY)
GradientBasedCorrMaskTopreal_(GradientBasedCorrectionsCorrectionMasksTop)
GradientBasedCorrMaskVersioninteger_(GradientBasedCorrectionsCorrectionMasksVersion)
GradientBasedCorrMaskWhatstring_(GradientBasedCorrectionsCorrectionMasksWhat)
GradientBasedCorrMaskWholeImageAreastring_(GradientBasedCorrectionsCorrectionMasksWholeImageArea)
GradientBasedCorrMaskXreal_(GradientBasedCorrectionsCorrectionMasksX)
GradientBasedCorrMaskYreal_(GradientBasedCorrectionsCorrectionMasksY)
GradientBasedCorrMaskZeroXreal_(GradientBasedCorrectionsCorrectionMasksZeroX)
GradientBasedCorrMaskZeroYreal_(GradientBasedCorrectionsCorrectionMasksZeroY)
GradientBasedCorrCorrectionNamestring_+(GradientBasedCorrectionsCorrectionName)
GradientBasedCorrRangeMaskstruct_+--> CorrRangeMask Struct +
(GradientBasedCorrectionsCorrectionRangeMask; called CorrectionRangeMask by the spec)
GradientBasedCorrRangeMaskAreaModelsstruct_+--> AreaModels Struct +
(GradientBasedCorrectionsCorrectionRangeMaskAreaModels)
GradientBasedCorrRangeMaskAreaModelsComponentsstring_+(GradientBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents)
GradientBasedCorrRangeMaskAreaModelsColorSampleInfostring_+(GradientBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
GradientBasedCorrRangeMaskColorAmountreal_+(GradientBasedCorrectionsCorrectionRangeMaskColorAmount)
GradientBasedCorrRangeMaskDepthFeatherreal_+(GradientBasedCorrectionsCorrectionRangeMaskDepthFeather)
GradientBasedCorrRangeMaskDepthMaxreal_+(GradientBasedCorrectionsCorrectionRangeMaskDepthMax)
GradientBasedCorrRangeMaskDepthMinreal_+(GradientBasedCorrectionsCorrectionRangeMaskDepthMin)
GradientBasedCorrRangeMaskInvertboolean_+(GradientBasedCorrectionsCorrectionRangeMaskInvert)
GradientBasedCorrRangeMaskLumFeatherreal_+(GradientBasedCorrectionsCorrectionRangeMaskLumFeather)
GradientBasedCorrRangeMaskLuminanceDepthSampleInfostring_+(GradientBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo)
GradientBasedCorrRangeMaskLumMaxreal_+(GradientBasedCorrectionsCorrectionRangeMaskLumMax)
GradientBasedCorrRangeMaskLumMinreal_+(GradientBasedCorrectionsCorrectionRangeMaskLumMin)
GradientBasedCorrRangeMaskLumRangestring_+(GradientBasedCorrectionsCorrectionRangeMaskLumRange)
GradientBasedCorrRangeMaskSampleTypeinteger_+(GradientBasedCorrectionsCorrectionRangeMaskSampleType)
GradientBasedCorrRangeMaskTypestring_+(GradientBasedCorrectionsCorrectionRangeMaskType)
GradientBasedCorrRangeMaskVersionstring_+(GradientBasedCorrectionsCorrectionRangeMaskVersion)
GradientBasedCorrCorrectionSyncIDstring_+(GradientBasedCorrectionsCorrectionSyncID)
GradientBasedCorrBlacks2012real_(GradientBasedCorrectionsLocalBlacks2012)
GradientBasedCorrBrightnessreal_(GradientBasedCorrectionsLocalBrightness)
GradientBasedCorrClarityreal_(GradientBasedCorrectionsLocalClarity)
GradientBasedCorrClarity2012real_(GradientBasedCorrectionsLocalClarity2012)
GradientBasedCorrContrastreal_(GradientBasedCorrectionsLocalContrast)
GradientBasedCorrContrast2012real_(GradientBasedCorrectionsLocalContrast2012)
GradientBasedCorrDefringereal_(GradientBasedCorrectionsLocalDefringe)
GradientBasedCorrDehazereal_(GradientBasedCorrectionsLocalDehaze)
GradientBasedCorrExposurereal_(GradientBasedCorrectionsLocalExposure)
GradientBasedCorrExposure2012real_(GradientBasedCorrectionsLocalExposure2012)
GradientBasedCorrHighlights2012real_(GradientBasedCorrectionsLocalHighlights2012)
GradientBasedCorrHuereal_(GradientBasedCorrectionsLocalHue)
GradientBasedCorrLuminanceNoisereal_(GradientBasedCorrectionsLocalLuminanceNoise)
GradientBasedCorrMoirereal_(GradientBasedCorrectionsLocalMoire)
GradientBasedCorrSaturationreal_(GradientBasedCorrectionsLocalSaturation)
GradientBasedCorrShadows2012real_(GradientBasedCorrectionsLocalShadows2012)
GradientBasedCorrSharpnessreal_(GradientBasedCorrectionsLocalSharpness)
GradientBasedCorrTemperaturereal_(GradientBasedCorrectionsLocalTemperature)
GradientBasedCorrTexturereal_(GradientBasedCorrectionsLocalTexture)
GradientBasedCorrTintreal_(GradientBasedCorrectionsLocalTint)
GradientBasedCorrToningHuereal_(GradientBasedCorrectionsLocalToningHue)
GradientBasedCorrToningSaturationreal_(GradientBasedCorrectionsLocalToningSaturation)
GradientBasedCorrWhites2012real_(GradientBasedCorrectionsLocalWhites2012)
GradientBasedCorrWhatstring_(GradientBasedCorrectionsWhat)
GrainAmountinteger 
GrainFrequencyinteger 
GrainSeedinteger 
GrainSizeinteger 
GrayMixerAquainteger 
GrayMixerBlueinteger 
GrayMixerGreeninteger 
GrayMixerMagentainteger 
GrayMixerOrangeinteger 
GrayMixerPurpleinteger 
GrayMixerRedinteger 
GrayMixerYellowinteger 
GreenHueinteger 
GreenSaturationinteger 
Grouplang-alt/ 
HasCropboolean 
HasSettingsboolean 
HDREditModeinteger 
Highlight2012integer 
HighlightRecoveryinteger 
Highlights2012integer 
HueAdjustmentAquainteger 
HueAdjustmentBlueinteger 
HueAdjustmentGreeninteger 
HueAdjustmentMagentainteger 
HueAdjustmentOrangeinteger 
HueAdjustmentPurpleinteger 
HueAdjustmentRedinteger 
HueAdjustmentYellowinteger 
IncrementalTemperatureinteger 
IncrementalTintinteger 
JPEGHandlingstring 
LensManualDistortionAmountinteger 
LensProfileChromaticAberrationScaleinteger 
LensProfileDigeststring 
LensProfileDistortionScaleinteger 
LensProfileEnableinteger 
LensProfileFilenamestring 
LensProfileIsEmbeddedboolean 
LensProfileMatchKeyCameraModelNamestring 
LensProfileMatchKeyExifMakestring 
LensProfileMatchKeyExifModelstring 
LensProfileMatchKeyIsRawboolean 
LensProfileMatchKeyLensIDstring 
LensProfileMatchKeyLensInfostring 
LensProfileMatchKeyLensNamestring 
LensProfileMatchKeySensorFormatFactorreal 
LensProfileNamestring 
LensProfileSetupstring 
LensProfileVignettingScaleinteger 
Lookstruct--> Look Struct
LookAmountstring_ 
LookClusterstring_ 
LookCopyrightstring_ 
LookGrouplang-alt_ 
LookNamestring(NOT a flattened tag!)
LookParametersstruct_--> LookParms Struct
LookParametersCameraProfilestring_ 
LookParametersClarity2012string_ 
LookParametersConvertToGrayscalestring_ 
LookParametersLookTablestring_ 
LookParametersProcessVersionstring_ 
LookParametersToneCurvePV2012string_+ 
LookParametersToneCurvePV2012Bluestring_+ 
LookParametersToneCurvePV2012Greenstring_+ 
LookParametersToneCurvePV2012Redstring_+ 
LookParametersVersionstring_ 
LookSupportsAmountstring_ 
LookSupportsMonochromestring_ 
LookSupportsOutputReferredstring_ 
LookUUIDstring_ 
LuminanceAdjustmentAquainteger 
LuminanceAdjustmentBlueinteger 
LuminanceAdjustmentGreeninteger 
LuminanceAdjustmentMagentainteger 
LuminanceAdjustmentOrangeinteger 
LuminanceAdjustmentPurpleinteger 
LuminanceAdjustmentRedinteger 
LuminanceAdjustmentYellowinteger 
LuminanceNoiseReductionContrastinteger 
LuminanceNoiseReductionDetailinteger 
LuminanceSmoothinginteger 
MaskGroupBasedCorrectionsstruct+--> Correction Struct
MaskGroupBasedCorrActiveboolean_(MaskGroupBasedCorrectionsCorrectionActive)
MaskGroupBasedCorrAmountreal_(MaskGroupBasedCorrectionsCorrectionAmount)
MaskGroupBasedCorrMaskstruct_+--> CorrectionMask Struct +
(MaskGroupBasedCorrectionsCorrectionMasks)
MaskGroupBasedCorrMaskAlphareal_(MaskGroupBasedCorrectionsCorrectionMasksAlpha)
MaskGroupBasedCorrMaskAnglereal_(MaskGroupBasedCorrectionsCorrectionMasksAngle)
MaskGroupBasedCorrMaskBottomreal_(MaskGroupBasedCorrectionsCorrectionMasksBottom)
MaskGroupBasedCorrMaskCenterValuereal_(MaskGroupBasedCorrectionsCorrectionMasksCenterValue)
MaskGroupBasedCorrMaskCenterWeightreal_(MaskGroupBasedCorrectionsCorrectionMasksCenterWeight)
MaskGroupBasedCorrMaskRangestruct_+--> CorrRangeMask Struct +
(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMask; called CorrectionRangeMask by the spec)
MaskGroupBasedCorrMaskRangeAreaModelsstruct_+--> AreaModels Struct +
(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels)
MaskGroupBasedCorrMaskRangeAreaModelsComponentsstring_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents)
MaskGroupBasedCorrMaskRangeAreaModelsColorSampleInfostring_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
MaskGroupBasedCorrMaskRangeColorAmountreal_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount)
MaskGroupBasedCorrMaskRangeDepthFeatherreal_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather)
MaskGroupBasedCorrMaskRangeDepthMaxreal_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax)
MaskGroupBasedCorrMaskRangeDepthMinreal_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin)
MaskGroupBasedCorrMaskRangeInvertboolean_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert)
MaskGroupBasedCorrMaskRangeLumFeatherreal_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather)
MaskGroupBasedCorrMaskRangeLuminanceDepthSampleInfostring_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo)
MaskGroupBasedCorrMaskRangeLumMaxreal_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax)
MaskGroupBasedCorrMaskRangeLumMinreal_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin)
MaskGroupBasedCorrMaskRangeLumRangestring_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange)
MaskGroupBasedCorrMaskRangeSampleTypeinteger_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType)
MaskGroupBasedCorrMaskRangeTypestring_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskType)
MaskGroupBasedCorrMaskRangeVersionstring_+(MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion)
MaskGroupBasedCorrMaskDabsstring_+(MaskGroupBasedCorrectionsCorrectionMasksDabs)
MaskGroupBasedCorrMaskFeatherreal_(MaskGroupBasedCorrectionsCorrectionMasksFeather)
MaskGroupBasedCorrMaskFlippedboolean_(MaskGroupBasedCorrectionsCorrectionMasksFlipped)
MaskGroupBasedCorrMaskFlowreal_(MaskGroupBasedCorrectionsCorrectionMasksFlow)
MaskGroupBasedCorrMaskFullXreal_(MaskGroupBasedCorrectionsCorrectionMasksFullX)
MaskGroupBasedCorrMaskFullYreal_(MaskGroupBasedCorrectionsCorrectionMasksFullY)
MaskGroupBasedCorrMaskInputDigeststring_(MaskGroupBasedCorrectionsCorrectionMasksInputDigest)
MaskGroupBasedCorrMaskLeftreal_(MaskGroupBasedCorrectionsCorrectionMasksLeft)
MaskGroupBasedCorrMaskMaskActiveboolean_(MaskGroupBasedCorrectionsCorrectionMasksMaskActive)
MaskGroupBasedCorrMaskMaskBlendModeinteger_(MaskGroupBasedCorrectionsCorrectionMasksMaskBlendMode)
MaskGroupBasedCorrMaskMaskDigeststring_(MaskGroupBasedCorrectionsCorrectionMasksMaskDigest)
MaskGroupBasedCorrMaskMaskInvertedboolean_(MaskGroupBasedCorrectionsCorrectionMasksMaskInverted)
MaskGroupBasedCorrMaskMaskNamestring_(MaskGroupBasedCorrectionsCorrectionMasksMaskName)
MaskGroupBasedCorrMaskMasksstruct_+--> CorrectionMask Struct +
(MaskGroupBasedCorrectionsCorrectionMasksMasks)
MaskGroupBasedCorrMaskMasksAlphareal_(MaskGroupBasedCorrectionsCorrectionMasksMasksAlpha)
MaskGroupBasedCorrMaskMasksAnglereal_(MaskGroupBasedCorrectionsCorrectionMasksMasksAngle)
MaskGroupBasedCorrMaskMasksBottomreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksBottom)
MaskGroupBasedCorrMaskMasksCenterValuereal_(MaskGroupBasedCorrectionsCorrectionMasksMasksCenterValue)
MaskGroupBasedCorrMaskMasksCenterWeightreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksCenterWeight)
MaskGroupBasedCorrMaskMasksDabsstring_+(MaskGroupBasedCorrectionsCorrectionMasksMasksDabs)
MaskGroupBasedCorrMaskMasksFeatherreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksFeather)
MaskGroupBasedCorrMaskMasksFlippedboolean_(MaskGroupBasedCorrectionsCorrectionMasksMasksFlipped)
MaskGroupBasedCorrMaskMasksFlowreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksFlow)
MaskGroupBasedCorrMaskMasksFullXreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksFullX)
MaskGroupBasedCorrMaskMasksFullYreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksFullY)
MaskGroupBasedCorrMaskMasksInputDigeststring_(MaskGroupBasedCorrectionsCorrectionMasksMasksInputDigest)
MaskGroupBasedCorrMaskMasksLeftreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksLeft)
MaskGroupBasedCorrMaskMasksMaskActiveboolean_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskActive)
MaskGroupBasedCorrMaskMasksMaskBlendModeinteger_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskBlendMode)
MaskGroupBasedCorrMaskMasksMaskDigeststring_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskDigest)
MaskGroupBasedCorrMaskMasksMaskInvertedboolean_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskInverted)
MaskGroupBasedCorrMaskMasksMaskNamestring_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskName)
MaskGroupBasedCorrMaskMasksMaskSubTypestring_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskSubType)
MaskGroupBasedCorrMaskMasksMaskSyncIDstring_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskSyncID)
MaskGroupBasedCorrMaskMasksValuereal_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskValue)
MaskGroupBasedCorrMaskMasksMaskVersionstring_(MaskGroupBasedCorrectionsCorrectionMasksMasksMaskVersion)
MaskGroupBasedCorrMaskMasksMidpointreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksMidpoint)
MaskGroupBasedCorrMaskMasksOriginstring_(MaskGroupBasedCorrectionsCorrectionMasksMasksOrigin)
MaskGroupBasedCorrMaskMasksPerimeterValuereal_(MaskGroupBasedCorrectionsCorrectionMasksMasksPerimeterValue)
MaskGroupBasedCorrMaskMasksRadiusreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksRadius)
MaskGroupBasedCorrMaskMasksReferencePointstring_(MaskGroupBasedCorrectionsCorrectionMasksMasksReferencePoint)
MaskGroupBasedCorrMaskMasksRightreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksRight)
MaskGroupBasedCorrMaskMasksRoundnessreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksRoundness)
MaskGroupBasedCorrMaskMasksSizeXreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksSizeX)
MaskGroupBasedCorrMaskMasksSizeYreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksSizeY)
MaskGroupBasedCorrMaskMasksTopreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksTop)
MaskGroupBasedCorrMaskMaskSubTypestring_(MaskGroupBasedCorrectionsCorrectionMasksMaskSubType)
MaskGroupBasedCorrMaskMasksVersioninteger_(MaskGroupBasedCorrectionsCorrectionMasksMasksVersion)
MaskGroupBasedCorrMaskMasksWhatstring_(MaskGroupBasedCorrectionsCorrectionMasksMasksWhat)
MaskGroupBasedCorrMaskMasksWholeImageAreastring_(MaskGroupBasedCorrectionsCorrectionMasksMasksWholeImageArea)
MaskGroupBasedCorrMaskMasksXreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksX)
MaskGroupBasedCorrMaskMasksYreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksY)
MaskGroupBasedCorrMaskMaskSyncIDstring_(MaskGroupBasedCorrectionsCorrectionMasksMaskSyncID)
MaskGroupBasedCorrMaskMasksZeroXreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksZeroX)
MaskGroupBasedCorrMaskMasksZeroYreal_(MaskGroupBasedCorrectionsCorrectionMasksMasksZeroY)
MaskGroupBasedCorrMaskValuereal_(MaskGroupBasedCorrectionsCorrectionMasksMaskValue)
MaskGroupBasedCorrMaskMaskVersionstring_(MaskGroupBasedCorrectionsCorrectionMasksMaskVersion)
MaskGroupBasedCorrMaskMidpointreal_(MaskGroupBasedCorrectionsCorrectionMasksMidpoint)
MaskGroupBasedCorrMaskOriginstring_(MaskGroupBasedCorrectionsCorrectionMasksOrigin)
MaskGroupBasedCorrMaskPerimeterValuereal_(MaskGroupBasedCorrectionsCorrectionMasksPerimeterValue)
MaskGroupBasedCorrMaskRadiusreal_(MaskGroupBasedCorrectionsCorrectionMasksRadius)
MaskGroupBasedCorrMaskReferencePointstring_(MaskGroupBasedCorrectionsCorrectionMasksReferencePoint)
MaskGroupBasedCorrMaskRightreal_(MaskGroupBasedCorrectionsCorrectionMasksRight)
MaskGroupBasedCorrMaskRoundnessreal_(MaskGroupBasedCorrectionsCorrectionMasksRoundness)
MaskGroupBasedCorrMaskSizeXreal_(MaskGroupBasedCorrectionsCorrectionMasksSizeX)
MaskGroupBasedCorrMaskSizeYreal_(MaskGroupBasedCorrectionsCorrectionMasksSizeY)
MaskGroupBasedCorrMaskTopreal_(MaskGroupBasedCorrectionsCorrectionMasksTop)
MaskGroupBasedCorrMaskVersioninteger_(MaskGroupBasedCorrectionsCorrectionMasksVersion)
MaskGroupBasedCorrMaskWhatstring_(MaskGroupBasedCorrectionsCorrectionMasksWhat)
MaskGroupBasedCorrMaskWholeImageAreastring_(MaskGroupBasedCorrectionsCorrectionMasksWholeImageArea)
MaskGroupBasedCorrMaskXreal_(MaskGroupBasedCorrectionsCorrectionMasksX)
MaskGroupBasedCorrMaskYreal_(MaskGroupBasedCorrectionsCorrectionMasksY)
MaskGroupBasedCorrMaskZeroXreal_(MaskGroupBasedCorrectionsCorrectionMasksZeroX)
MaskGroupBasedCorrMaskZeroYreal_(MaskGroupBasedCorrectionsCorrectionMasksZeroY)
MaskGroupBasedCorrCorrectionNamestring_+(MaskGroupBasedCorrectionsCorrectionName)
MaskGroupBasedCorrRangeMaskstruct_+--> CorrRangeMask Struct +
(MaskGroupBasedCorrectionsCorrectionRangeMask; called CorrectionRangeMask by the spec)
MaskGroupBasedCorrRangeMaskAreaModelsstruct_+--> AreaModels Struct +
(MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModels)
MaskGroupBasedCorrRangeMaskAreaModelsComponentsstring_+(MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents)
MaskGroupBasedCorrRangeMaskAreaModelsColorSampleInfostring_+(MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
MaskGroupBasedCorrRangeMaskColorAmountreal_+(MaskGroupBasedCorrectionsCorrectionRangeMaskColorAmount)
MaskGroupBasedCorrRangeMaskDepthFeatherreal_+(MaskGroupBasedCorrectionsCorrectionRangeMaskDepthFeather)
MaskGroupBasedCorrRangeMaskDepthMaxreal_+(MaskGroupBasedCorrectionsCorrectionRangeMaskDepthMax)
MaskGroupBasedCorrRangeMaskDepthMinreal_+(MaskGroupBasedCorrectionsCorrectionRangeMaskDepthMin)
MaskGroupBasedCorrRangeMaskInvertboolean_+(MaskGroupBasedCorrectionsCorrectionRangeMaskInvert)
MaskGroupBasedCorrRangeMaskLumFeatherreal_+(MaskGroupBasedCorrectionsCorrectionRangeMaskLumFeather)
MaskGroupBasedCorrRangeMaskLuminanceDepthSampleInfostring_+(MaskGroupBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo)
MaskGroupBasedCorrRangeMaskLumMaxreal_+(MaskGroupBasedCorrectionsCorrectionRangeMaskLumMax)
MaskGroupBasedCorrRangeMaskLumMinreal_+(MaskGroupBasedCorrectionsCorrectionRangeMaskLumMin)
MaskGroupBasedCorrRangeMaskLumRangestring_+(MaskGroupBasedCorrectionsCorrectionRangeMaskLumRange)
MaskGroupBasedCorrRangeMaskSampleTypeinteger_+(MaskGroupBasedCorrectionsCorrectionRangeMaskSampleType)
MaskGroupBasedCorrRangeMaskTypestring_+(MaskGroupBasedCorrectionsCorrectionRangeMaskType)
MaskGroupBasedCorrRangeMaskVersionstring_+(MaskGroupBasedCorrectionsCorrectionRangeMaskVersion)
MaskGroupBasedCorrCorrectionSyncIDstring_+(MaskGroupBasedCorrectionsCorrectionSyncID)
MaskGroupBasedCorrBlacks2012real_(MaskGroupBasedCorrectionsLocalBlacks2012)
MaskGroupBasedCorrBrightnessreal_(MaskGroupBasedCorrectionsLocalBrightness)
MaskGroupBasedCorrClarityreal_(MaskGroupBasedCorrectionsLocalClarity)
MaskGroupBasedCorrClarity2012real_(MaskGroupBasedCorrectionsLocalClarity2012)
MaskGroupBasedCorrContrastreal_(MaskGroupBasedCorrectionsLocalContrast)
MaskGroupBasedCorrContrast2012real_(MaskGroupBasedCorrectionsLocalContrast2012)
MaskGroupBasedCorrDefringereal_(MaskGroupBasedCorrectionsLocalDefringe)
MaskGroupBasedCorrDehazereal_(MaskGroupBasedCorrectionsLocalDehaze)
MaskGroupBasedCorrExposurereal_(MaskGroupBasedCorrectionsLocalExposure)
MaskGroupBasedCorrExposure2012real_(MaskGroupBasedCorrectionsLocalExposure2012)
MaskGroupBasedCorrHighlights2012real_(MaskGroupBasedCorrectionsLocalHighlights2012)
MaskGroupBasedCorrHuereal_(MaskGroupBasedCorrectionsLocalHue)
MaskGroupBasedCorrLuminanceNoisereal_(MaskGroupBasedCorrectionsLocalLuminanceNoise)
MaskGroupBasedCorrMoirereal_(MaskGroupBasedCorrectionsLocalMoire)
MaskGroupBasedCorrSaturationreal_(MaskGroupBasedCorrectionsLocalSaturation)
MaskGroupBasedCorrShadows2012real_(MaskGroupBasedCorrectionsLocalShadows2012)
MaskGroupBasedCorrSharpnessreal_(MaskGroupBasedCorrectionsLocalSharpness)
MaskGroupBasedCorrTemperaturereal_(MaskGroupBasedCorrectionsLocalTemperature)
MaskGroupBasedCorrTexturereal_(MaskGroupBasedCorrectionsLocalTexture)
MaskGroupBasedCorrTintreal_(MaskGroupBasedCorrectionsLocalTint)
MaskGroupBasedCorrToningHuereal_(MaskGroupBasedCorrectionsLocalToningHue)
MaskGroupBasedCorrToningSaturationreal_(MaskGroupBasedCorrectionsLocalToningSaturation)
MaskGroupBasedCorrWhites2012real_(MaskGroupBasedCorrectionsLocalWhites2012)
MaskGroupBasedCorrWhatstring_(MaskGroupBasedCorrectionsWhat)
MoireFilterstring'Off' = Off +
'On' = On
Namelang-alt/ 
NegativeCacheLargePreviewSizeinteger 
NegativeCacheMaximumSizereal 
NegativeCachePathstring 
OverrideLookVignetteboolean 
PaintBasedCorrectionsstruct+--> Correction Struct
PaintCorrectionActiveboolean_(PaintBasedCorrectionsCorrectionActive)
PaintCorrectionAmountreal_(PaintBasedCorrectionsCorrectionAmount)
PaintBasedCorrectionMasksstruct_+--> CorrectionMask Struct +
(PaintBasedCorrectionsCorrectionMasks)
PaintCorrectionMaskAlphareal_(PaintBasedCorrectionsCorrectionMasksAlpha)
PaintCorrectionMaskAnglereal_(PaintBasedCorrectionsCorrectionMasksAngle)
PaintCorrectionMaskBottomreal_(PaintBasedCorrectionsCorrectionMasksBottom)
PaintCorrectionMaskCenterValuereal_(PaintBasedCorrectionsCorrectionMasksCenterValue)
PaintCorrectionMaskCenterWeightreal_(PaintBasedCorrectionsCorrectionMasksCenterWeight)
PaintCorrectionMaskRangestruct_+--> CorrRangeMask Struct +
(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMask; called CorrectionRangeMask by the spec)
PaintCorrectionMaskRangeAreaModelsstruct_+--> AreaModels Struct +
(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels)
PaintCorrectionMaskRangeAreaModelsComponentsstring_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents)
PaintCorrectionMaskRangeAreaModelsColorSampleInfostring_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
PaintCorrectionMaskRangeColorAmountreal_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount)
PaintCorrectionMaskRangeDepthFeatherreal_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather)
PaintCorrectionMaskRangeDepthMaxreal_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax)
PaintCorrectionMaskRangeDepthMinreal_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin)
PaintCorrectionMaskRangeInvertboolean_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert)
PaintCorrectionMaskRangeLumFeatherreal_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather)
PaintCorrectionMaskRangeLuminanceDepthSampleInfostring_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo)
PaintCorrectionMaskRangeLumMaxreal_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax)
PaintCorrectionMaskRangeLumMinreal_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin)
PaintCorrectionMaskRangeLumRangestring_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange)
PaintCorrectionMaskRangeSampleTypeinteger_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType)
PaintCorrectionMaskRangeTypestring_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskType)
PaintCorrectionMaskRangeVersionstring_+(PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion)
PaintCorrectionMaskDabsstring_(PaintBasedCorrectionsCorrectionMasksDabs)
PaintCorrectionMaskFeatherreal_(PaintBasedCorrectionsCorrectionMasksFeather)
PaintCorrectionMaskFlippedboolean_(PaintBasedCorrectionsCorrectionMasksFlipped)
PaintCorrectionMaskFlowreal_(PaintBasedCorrectionsCorrectionMasksFlow)
PaintCorrectionMaskFullXreal_(PaintBasedCorrectionsCorrectionMasksFullX)
PaintCorrectionMaskFullYreal_(PaintBasedCorrectionsCorrectionMasksFullY)
PaintCorrectionMaskInputDigeststring_(PaintBasedCorrectionsCorrectionMasksInputDigest)
PaintCorrectionMaskLeftreal_(PaintBasedCorrectionsCorrectionMasksLeft)
PaintCorrectionMaskMaskActiveboolean_(PaintBasedCorrectionsCorrectionMasksMaskActive)
PaintCorrectionMaskMaskBlendModeinteger_(PaintBasedCorrectionsCorrectionMasksMaskBlendMode)
PaintCorrectionMaskMaskDigeststring_(PaintBasedCorrectionsCorrectionMasksMaskDigest)
PaintCorrectionMaskMaskInvertedboolean_(PaintBasedCorrectionsCorrectionMasksMaskInverted)
PaintCorrectionMaskMaskNamestring_(PaintBasedCorrectionsCorrectionMasksMaskName)
PaintCorrectionMaskMasksstruct_+--> CorrectionMask Struct +
(PaintBasedCorrectionsCorrectionMasksMasks)
PaintCorrectionMaskMasksAlphareal_(PaintBasedCorrectionsCorrectionMasksMasksAlpha)
PaintCorrectionMaskMasksAnglereal_(PaintBasedCorrectionsCorrectionMasksMasksAngle)
PaintCorrectionMaskMasksBottomreal_(PaintBasedCorrectionsCorrectionMasksMasksBottom)
PaintCorrectionMaskMasksCenterValuereal_(PaintBasedCorrectionsCorrectionMasksMasksCenterValue)
PaintCorrectionMaskMasksCenterWeightreal_(PaintBasedCorrectionsCorrectionMasksMasksCenterWeight)
PaintCorrectionMaskMasksDabsstring_+(PaintBasedCorrectionsCorrectionMasksMasksDabs)
PaintCorrectionMaskMasksFeatherreal_(PaintBasedCorrectionsCorrectionMasksMasksFeather)
PaintCorrectionMaskMasksFlippedboolean_(PaintBasedCorrectionsCorrectionMasksMasksFlipped)
PaintCorrectionMaskMasksFlowreal_(PaintBasedCorrectionsCorrectionMasksMasksFlow)
PaintCorrectionMaskMasksFullXreal_(PaintBasedCorrectionsCorrectionMasksMasksFullX)
PaintCorrectionMaskMasksFullYreal_(PaintBasedCorrectionsCorrectionMasksMasksFullY)
PaintCorrectionMaskMasksInputDigeststring_(PaintBasedCorrectionsCorrectionMasksMasksInputDigest)
PaintCorrectionMaskMasksLeftreal_(PaintBasedCorrectionsCorrectionMasksMasksLeft)
PaintCorrectionMaskMasksMaskActiveboolean_(PaintBasedCorrectionsCorrectionMasksMasksMaskActive)
PaintCorrectionMaskMasksMaskBlendModeinteger_(PaintBasedCorrectionsCorrectionMasksMasksMaskBlendMode)
PaintCorrectionMaskMasksMaskDigeststring_(PaintBasedCorrectionsCorrectionMasksMasksMaskDigest)
PaintCorrectionMaskMasksMaskInvertedboolean_(PaintBasedCorrectionsCorrectionMasksMasksMaskInverted)
PaintCorrectionMaskMasksMaskNamestring_(PaintBasedCorrectionsCorrectionMasksMasksMaskName)
PaintCorrectionMaskMasksMaskSubTypestring_(PaintBasedCorrectionsCorrectionMasksMasksMaskSubType)
PaintCorrectionMaskMasksMaskSyncIDstring_(PaintBasedCorrectionsCorrectionMasksMasksMaskSyncID)
PaintCorrectionMaskMasksValuereal_(PaintBasedCorrectionsCorrectionMasksMasksMaskValue)
PaintCorrectionMaskMasksMaskVersionstring_(PaintBasedCorrectionsCorrectionMasksMasksMaskVersion)
PaintCorrectionMaskMasksMidpointreal_(PaintBasedCorrectionsCorrectionMasksMasksMidpoint)
PaintCorrectionMaskMasksOriginstring_(PaintBasedCorrectionsCorrectionMasksMasksOrigin)
PaintCorrectionMaskMasksPerimeterValuereal_(PaintBasedCorrectionsCorrectionMasksMasksPerimeterValue)
PaintCorrectionMaskMasksRadiusreal_(PaintBasedCorrectionsCorrectionMasksMasksRadius)
PaintCorrectionMaskMasksReferencePointstring_(PaintBasedCorrectionsCorrectionMasksMasksReferencePoint)
PaintCorrectionMaskMasksRightreal_(PaintBasedCorrectionsCorrectionMasksMasksRight)
PaintCorrectionMaskMasksRoundnessreal_(PaintBasedCorrectionsCorrectionMasksMasksRoundness)
PaintCorrectionMaskMasksSizeXreal_(PaintBasedCorrectionsCorrectionMasksMasksSizeX)
PaintCorrectionMaskMasksSizeYreal_(PaintBasedCorrectionsCorrectionMasksMasksSizeY)
PaintCorrectionMaskMasksTopreal_(PaintBasedCorrectionsCorrectionMasksMasksTop)
PaintCorrectionMaskMaskSubTypestring_(PaintBasedCorrectionsCorrectionMasksMaskSubType)
PaintCorrectionMaskMasksVersioninteger_(PaintBasedCorrectionsCorrectionMasksMasksVersion)
PaintCorrectionMaskMasksWhatstring_(PaintBasedCorrectionsCorrectionMasksMasksWhat)
PaintCorrectionMaskMasksWholeImageAreastring_(PaintBasedCorrectionsCorrectionMasksMasksWholeImageArea)
PaintCorrectionMaskMasksXreal_(PaintBasedCorrectionsCorrectionMasksMasksX)
PaintCorrectionMaskMasksYreal_(PaintBasedCorrectionsCorrectionMasksMasksY)
PaintCorrectionMaskMaskSyncIDstring_(PaintBasedCorrectionsCorrectionMasksMaskSyncID)
PaintCorrectionMaskMasksZeroXreal_(PaintBasedCorrectionsCorrectionMasksMasksZeroX)
PaintCorrectionMaskMasksZeroYreal_(PaintBasedCorrectionsCorrectionMasksMasksZeroY)
PaintCorrectionMaskValuereal_(PaintBasedCorrectionsCorrectionMasksMaskValue)
PaintCorrectionMaskMaskVersionstring_(PaintBasedCorrectionsCorrectionMasksMaskVersion)
PaintCorrectionMaskMidpointreal_(PaintBasedCorrectionsCorrectionMasksMidpoint)
PaintCorrectionMaskOriginstring_(PaintBasedCorrectionsCorrectionMasksOrigin)
PaintCorrectionMaskPerimeterValuereal_(PaintBasedCorrectionsCorrectionMasksPerimeterValue)
PaintCorrectionMaskRadiusreal_(PaintBasedCorrectionsCorrectionMasksRadius)
PaintCorrectionMaskReferencePointstring_(PaintBasedCorrectionsCorrectionMasksReferencePoint)
PaintCorrectionMaskRightreal_(PaintBasedCorrectionsCorrectionMasksRight)
PaintCorrectionMaskRoundnessreal_(PaintBasedCorrectionsCorrectionMasksRoundness)
PaintCorrectionMaskSizeXreal_(PaintBasedCorrectionsCorrectionMasksSizeX)
PaintCorrectionMaskSizeYreal_(PaintBasedCorrectionsCorrectionMasksSizeY)
PaintCorrectionMaskTopreal_(PaintBasedCorrectionsCorrectionMasksTop)
PaintCorrectionMaskVersioninteger_(PaintBasedCorrectionsCorrectionMasksVersion)
PaintCorrectionMaskWhatstring_(PaintBasedCorrectionsCorrectionMasksWhat)
PaintCorrectionMaskWholeImageAreastring_(PaintBasedCorrectionsCorrectionMasksWholeImageArea)
PaintCorrectionMaskXreal_(PaintBasedCorrectionsCorrectionMasksX)
PaintCorrectionMaskYreal_(PaintBasedCorrectionsCorrectionMasksY)
PaintCorrectionMaskZeroXreal_(PaintBasedCorrectionsCorrectionMasksZeroX)
PaintCorrectionMaskZeroYreal_(PaintBasedCorrectionsCorrectionMasksZeroY)
PaintCorrectionCorrectionNamestring_+(PaintBasedCorrectionsCorrectionName)
PaintCorrectionRangeMaskstruct_+--> CorrRangeMask Struct +
(PaintBasedCorrectionsCorrectionRangeMask; called CorrectionRangeMask by the spec)
PaintCorrectionRangeMaskAreaModelsstruct_+--> AreaModels Struct +
(PaintBasedCorrectionsCorrectionRangeMaskAreaModels)
PaintCorrectionRangeMaskAreaModelsComponentsstring_+(PaintBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents)
PaintCorrectionRangeMaskAreaModelsColorSampleInfostring_+(PaintBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
PaintCorrectionRangeMaskColorAmountreal_+(PaintBasedCorrectionsCorrectionRangeMaskColorAmount)
PaintCorrectionRangeMaskDepthFeatherreal_+(PaintBasedCorrectionsCorrectionRangeMaskDepthFeather)
PaintCorrectionRangeMaskDepthMaxreal_+(PaintBasedCorrectionsCorrectionRangeMaskDepthMax)
PaintCorrectionRangeMaskDepthMinreal_+(PaintBasedCorrectionsCorrectionRangeMaskDepthMin)
PaintCorrectionRangeMaskInvertboolean_+(PaintBasedCorrectionsCorrectionRangeMaskInvert)
PaintCorrectionRangeMaskLumFeatherreal_+(PaintBasedCorrectionsCorrectionRangeMaskLumFeather)
PaintCorrectionRangeMaskLuminanceDepthSampleInfostring_+(PaintBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo)
PaintCorrectionRangeMaskLumMaxreal_+(PaintBasedCorrectionsCorrectionRangeMaskLumMax)
PaintCorrectionRangeMaskLumMinreal_+(PaintBasedCorrectionsCorrectionRangeMaskLumMin)
PaintCorrectionRangeMaskLumRangestring_+(PaintBasedCorrectionsCorrectionRangeMaskLumRange)
PaintCorrectionRangeMaskSampleTypeinteger_+(PaintBasedCorrectionsCorrectionRangeMaskSampleType)
PaintCorrectionRangeMaskTypestring_+(PaintBasedCorrectionsCorrectionRangeMaskType)
PaintCorrectionRangeMaskVersionstring_+(PaintBasedCorrectionsCorrectionRangeMaskVersion)
PaintCorrectionCorrectionSyncIDstring_+(PaintBasedCorrectionsCorrectionSyncID)
PaintCorrectionBlacks2012real_(PaintBasedCorrectionsLocalBlacks2012)
PaintCorrectionBrightnessreal_(PaintBasedCorrectionsLocalBrightness)
PaintCorrectionClarityreal_(PaintBasedCorrectionsLocalClarity)
PaintCorrectionClarity2012real_(PaintBasedCorrectionsLocalClarity2012)
PaintCorrectionContrastreal_(PaintBasedCorrectionsLocalContrast)
PaintCorrectionContrast2012real_(PaintBasedCorrectionsLocalContrast2012)
PaintCorrectionDefringereal_(PaintBasedCorrectionsLocalDefringe)
PaintCorrectionDehazereal_(PaintBasedCorrectionsLocalDehaze)
PaintCorrectionExposurereal_(PaintBasedCorrectionsLocalExposure)
PaintCorrectionExposure2012real_(PaintBasedCorrectionsLocalExposure2012)
PaintCorrectionHighlights2012real_(PaintBasedCorrectionsLocalHighlights2012)
PaintCorrectionHuereal_(PaintBasedCorrectionsLocalHue)
PaintCorrectionLuminanceNoisereal_(PaintBasedCorrectionsLocalLuminanceNoise)
PaintCorrectionMoirereal_(PaintBasedCorrectionsLocalMoire)
PaintCorrectionSaturationreal_(PaintBasedCorrectionsLocalSaturation)
PaintCorrectionShadows2012real_(PaintBasedCorrectionsLocalShadows2012)
PaintCorrectionSharpnessreal_(PaintBasedCorrectionsLocalSharpness)
PaintCorrectionTemperaturereal_(PaintBasedCorrectionsLocalTemperature)
PaintCorrectionTexturereal_(PaintBasedCorrectionsLocalTexture)
PaintCorrectionTintreal_(PaintBasedCorrectionsLocalTint)
PaintCorrectionToningHuereal_(PaintBasedCorrectionsLocalToningHue)
PaintCorrectionToningSaturationreal_(PaintBasedCorrectionsLocalToningSaturation)
PaintCorrectionWhites2012real_(PaintBasedCorrectionsLocalWhites2012)
PaintCorrectionWhatstring_(PaintBasedCorrectionsWhat)
ParametricDarksinteger 
ParametricHighlightsinteger 
ParametricHighlightSplitinteger 
ParametricLightsinteger 
ParametricMidtoneSplitinteger 
ParametricShadowsinteger 
ParametricShadowSplitinteger 
PerspectiveAspectinteger 
PerspectiveHorizontalinteger 
PerspectiveRotatereal 
PerspectiveScaleinteger 
PerspectiveUprightinteger + +
0 = Off +
1 = Auto +
2 = Full
  3 = Level +
4 = Vertical +
5 = Guided
+
PerspectiveVerticalinteger 
PerspectiveXreal 
PerspectiveYreal 
PostCropVignetteAmountinteger 
PostCropVignetteFeatherinteger 
PostCropVignetteHighlightContrastinteger 
PostCropVignetteMidpointinteger 
PostCropVignetteRoundnessinteger 
PostCropVignetteStyleinteger1 = Highlight Priority +
2 = Color Priority +
3 = Paint Overlay
PresetTypestring 
ProcessVersionstring 
RangeMaskstruct--> RangeMask Struct +
(called RangeMaskMapInfo by the spec)
RangeMaskMapInfostruct_--> MapInfo Struct +
(RangeMaskMapInfoRangeMaskMapInfo)
RangeMaskMapInfoLabMaxstring_(RangeMaskMapInfoRangeMaskMapInfoLabMax)
RangeMaskMapInfoLabMinstring_(RangeMaskMapInfoRangeMaskMapInfoLabMin)
RangeMaskMapInfoLumEqstring_+(RangeMaskMapInfoRangeMaskMapInfoLumEq)
RangeMaskMapInfoRGBMaxstring_(RangeMaskMapInfoRangeMaskMapInfoRGBMax)
RangeMaskMapInfoRGBMinstring_(RangeMaskMapInfoRangeMaskMapInfoRGBMin)
RawFileNamestring 
RedEyeInfostring+ 
RedHueinteger 
RedSaturationinteger 
RetouchAreasstruct+--> RetouchArea Struct
RetouchAreaFeatherreal_(RetouchAreasFeather)
RetouchAreaMasksstruct_+--> CorrectionMask Struct +
(RetouchAreasMasks)
RetouchAreaMaskAlphareal_(RetouchAreasMasksAlpha)
RetouchAreaMaskAnglereal_(RetouchAreasMasksAngle)
RetouchAreaMaskBottomreal_(RetouchAreasMasksBottom)
RetouchAreaMaskCenterValuereal_(RetouchAreasMasksCenterValue)
RetouchAreaMaskCenterWeightreal_(RetouchAreasMasksCenterWeight)
RetouchAreaMaskRangestruct_+--> CorrRangeMask Struct +
(RetouchAreasMasksCorrectionRangeMask; called CorrectionRangeMask by the spec)
RetouchAreaMaskRangeAreaModelsstruct_+--> AreaModels Struct +
(RetouchAreasMasksCorrectionRangeMaskAreaModels)
RetouchAreaMaskRangeAreaModelsComponentsstring_+(RetouchAreasMasksCorrectionRangeMaskAreaModelsAreaComponents)
RetouchAreaMaskRangeAreaModelsColorSampleInfostring_+(RetouchAreasMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo)
RetouchAreaMaskRangeColorAmountreal_+(RetouchAreasMasksCorrectionRangeMaskColorAmount)
RetouchAreaMaskRangeDepthFeatherreal_+(RetouchAreasMasksCorrectionRangeMaskDepthFeather)
RetouchAreaMaskRangeDepthMaxreal_+(RetouchAreasMasksCorrectionRangeMaskDepthMax)
RetouchAreaMaskRangeDepthMinreal_+(RetouchAreasMasksCorrectionRangeMaskDepthMin)
RetouchAreaMaskRangeInvertboolean_+(RetouchAreasMasksCorrectionRangeMaskInvert)
RetouchAreaMaskRangeLumFeatherreal_+(RetouchAreasMasksCorrectionRangeMaskLumFeather)
RetouchAreaMaskRangeLuminanceDepthSampleInfostring_+(RetouchAreasMasksCorrectionRangeMaskLuminanceDepthSampleInfo)
RetouchAreaMaskRangeLumMaxreal_+(RetouchAreasMasksCorrectionRangeMaskLumMax)
RetouchAreaMaskRangeLumMinreal_+(RetouchAreasMasksCorrectionRangeMaskLumMin)
RetouchAreaMaskRangeLumRangestring_+(RetouchAreasMasksCorrectionRangeMaskLumRange)
RetouchAreaMaskRangeSampleTypeinteger_+(RetouchAreasMasksCorrectionRangeMaskSampleType)
RetouchAreaMaskRangeTypestring_+(RetouchAreasMasksCorrectionRangeMaskType)
RetouchAreaMaskRangeVersionstring_+(RetouchAreasMasksCorrectionRangeMaskVersion)
RetouchAreaMaskDabsstring_(RetouchAreasMasksDabs)
RetouchAreaMaskFeatherreal_(RetouchAreasMasksFeather)
RetouchAreaMaskFlippedboolean_(RetouchAreasMasksFlipped)
RetouchAreaMaskFlowreal_(RetouchAreasMasksFlow)
RetouchAreaMaskFullXreal_(RetouchAreasMasksFullX)
RetouchAreaMaskFullYreal_(RetouchAreasMasksFullY)
RetouchAreaMaskInputDigeststring_(RetouchAreasMasksInputDigest)
RetouchAreaMaskLeftreal_(RetouchAreasMasksLeft)
RetouchAreaMaskMaskActiveboolean_(RetouchAreasMasksMaskActive)
RetouchAreaMaskMaskBlendModeinteger_(RetouchAreasMasksMaskBlendMode)
RetouchAreaMaskMaskDigeststring_(RetouchAreasMasksMaskDigest)
RetouchAreaMaskMaskInvertedboolean_(RetouchAreasMasksMaskInverted)
RetouchAreaMaskMaskNamestring_(RetouchAreasMasksMaskName)
RetouchAreaMaskMasksstruct_+--> CorrectionMask Struct +
(RetouchAreasMasksMasks)
RetouchAreaMaskMasksAlphareal_(RetouchAreasMasksMasksAlpha)
RetouchAreaMaskMasksAnglereal_(RetouchAreasMasksMasksAngle)
RetouchAreaMaskMasksBottomreal_(RetouchAreasMasksMasksBottom)
RetouchAreaMaskMasksCenterValuereal_(RetouchAreasMasksMasksCenterValue)
RetouchAreaMaskMasksCenterWeightreal_(RetouchAreasMasksMasksCenterWeight)
RetouchAreaMaskMasksDabsstring_+(RetouchAreasMasksMasksDabs)
RetouchAreaMaskMasksFeatherreal_(RetouchAreasMasksMasksFeather)
RetouchAreaMaskMasksFlippedboolean_(RetouchAreasMasksMasksFlipped)
RetouchAreaMaskMasksFlowreal_(RetouchAreasMasksMasksFlow)
RetouchAreaMaskMasksFullXreal_(RetouchAreasMasksMasksFullX)
RetouchAreaMaskMasksFullYreal_(RetouchAreasMasksMasksFullY)
RetouchAreaMaskMasksInputDigeststring_(RetouchAreasMasksMasksInputDigest)
RetouchAreaMaskMasksLeftreal_(RetouchAreasMasksMasksLeft)
RetouchAreaMaskMasksMaskActiveboolean_(RetouchAreasMasksMasksMaskActive)
RetouchAreaMaskMasksMaskBlendModeinteger_(RetouchAreasMasksMasksMaskBlendMode)
RetouchAreaMaskMasksMaskDigeststring_(RetouchAreasMasksMasksMaskDigest)
RetouchAreaMaskMasksMaskInvertedboolean_(RetouchAreasMasksMasksMaskInverted)
RetouchAreaMaskMasksMaskNamestring_(RetouchAreasMasksMasksMaskName)
RetouchAreaMaskMasksMaskSubTypestring_(RetouchAreasMasksMasksMaskSubType)
RetouchAreaMaskMasksMaskSyncIDstring_(RetouchAreasMasksMasksMaskSyncID)
RetouchAreaMaskMasksValuereal_(RetouchAreasMasksMasksMaskValue)
RetouchAreaMaskMasksMaskVersionstring_(RetouchAreasMasksMasksMaskVersion)
RetouchAreaMaskMasksMidpointreal_(RetouchAreasMasksMasksMidpoint)
RetouchAreaMaskMasksOriginstring_(RetouchAreasMasksMasksOrigin)
RetouchAreaMaskMasksPerimeterValuereal_(RetouchAreasMasksMasksPerimeterValue)
RetouchAreaMaskMasksRadiusreal_(RetouchAreasMasksMasksRadius)
RetouchAreaMaskMasksReferencePointstring_(RetouchAreasMasksMasksReferencePoint)
RetouchAreaMaskMasksRightreal_(RetouchAreasMasksMasksRight)
RetouchAreaMaskMasksRoundnessreal_(RetouchAreasMasksMasksRoundness)
RetouchAreaMaskMasksSizeXreal_(RetouchAreasMasksMasksSizeX)
RetouchAreaMaskMasksSizeYreal_(RetouchAreasMasksMasksSizeY)
RetouchAreaMaskMasksTopreal_(RetouchAreasMasksMasksTop)
RetouchAreaMaskMaskSubTypestring_(RetouchAreasMasksMaskSubType)
RetouchAreaMaskMasksVersioninteger_(RetouchAreasMasksMasksVersion)
RetouchAreaMaskMasksWhatstring_(RetouchAreasMasksMasksWhat)
RetouchAreaMaskMasksWholeImageAreastring_(RetouchAreasMasksMasksWholeImageArea)
RetouchAreaMaskMasksXreal_(RetouchAreasMasksMasksX)
RetouchAreaMaskMasksYreal_(RetouchAreasMasksMasksY)
RetouchAreaMaskMaskSyncIDstring_(RetouchAreasMasksMaskSyncID)
RetouchAreaMaskMasksZeroXreal_(RetouchAreasMasksMasksZeroX)
RetouchAreaMaskMasksZeroYreal_(RetouchAreasMasksMasksZeroY)
RetouchAreaMaskValuereal_(RetouchAreasMasksMaskValue)
RetouchAreaMaskMaskVersionstring_(RetouchAreasMasksMaskVersion)
RetouchAreaMaskMidpointreal_(RetouchAreasMasksMidpoint)
RetouchAreaMaskOriginstring_(RetouchAreasMasksOrigin)
RetouchAreaMaskPerimeterValuereal_(RetouchAreasMasksPerimeterValue)
RetouchAreaMaskRadiusreal_(RetouchAreasMasksRadius)
RetouchAreaMaskReferencePointstring_(RetouchAreasMasksReferencePoint)
RetouchAreaMaskRightreal_(RetouchAreasMasksRight)
RetouchAreaMaskRoundnessreal_(RetouchAreasMasksRoundness)
RetouchAreaMaskSizeXreal_(RetouchAreasMasksSizeX)
RetouchAreaMaskSizeYreal_(RetouchAreasMasksSizeY)
RetouchAreaMaskTopreal_(RetouchAreasMasksTop)
RetouchAreaMaskVersioninteger_(RetouchAreasMasksVersion)
RetouchAreaMaskWhatstring_(RetouchAreasMasksWhat)
RetouchAreaMaskWholeImageAreastring_(RetouchAreasMasksWholeImageArea)
RetouchAreaMaskXreal_(RetouchAreasMasksX)
RetouchAreaMaskYreal_(RetouchAreasMasksY)
RetouchAreaMaskZeroXreal_(RetouchAreasMasksZeroX)
RetouchAreaMaskZeroYreal_(RetouchAreasMasksZeroY)
RetouchAreaMethodstring_(RetouchAreasMethod)
RetouchAreaOffsetYreal_(RetouchAreasOffsetY)
RetouchAreaOpacityreal_(RetouchAreasOpacity)
RetouchAreaSeedinteger_(RetouchAreasSeed)
RetouchAreaSourceStatestring_(RetouchAreasSourceState)
RetouchAreaSourceXreal_(RetouchAreasSourceX)
RetouchAreaSpotTypestring_(RetouchAreasSpotType)
RetouchInfostring+ 
Saturationinteger/ 
SaturationAdjustmentAquainteger 
SaturationAdjustmentBlueinteger 
SaturationAdjustmentGreeninteger 
SaturationAdjustmentMagentainteger 
SaturationAdjustmentOrangeinteger 
SaturationAdjustmentPurpleinteger 
SaturationAdjustmentRedinteger 
SaturationAdjustmentYellowinteger 
SDRBlendreal 
SDRBrightnessreal 
SDRContrastreal 
SDRHighlightsreal 
SDRShadowsreal 
SDRWhitesreal 
Shadowsinteger 
Shadows2012integer 
ShadowTintinteger 
SharpenDetailinteger 
SharpenEdgeMaskinginteger 
SharpenRadiusreal 
Sharpnessinteger/ 
ShortNamelang-alt 
Smoothnessinteger 
SortNamelang-alt 
SplitToningBalanceinteger(also used for newer ColorGrade settings)
SplitToningHighlightHueinteger(also used for newer ColorGrade settings)
SplitToningHighlightSaturationinteger(also used for newer ColorGrade settings)
SplitToningShadowHueinteger(also used for newer ColorGrade settings)
SplitToningShadowSaturationinteger(also used for newer ColorGrade settings)
SupportsAmountboolean 
SupportsColorboolean 
SupportsHighDynamicRangeboolean 
SupportsMonochromeboolean 
SupportsNormalDynamicRangeboolean 
SupportsOutputReferredboolean 
SupportsSceneReferredboolean 
ColorTemperatureinteger(called Temperature by the spec)
Textureinteger 
TIFFHandlingstring 
Tintinteger 
ToggleStyleAmountinteger 
ToggleStyleDigeststring 
ToneCurvestring+ 
ToneCurveBluestring+ 
ToneCurveGreenstring+ 
ToneCurveNamestring'Custom' = Custom +
'Linear' = Linear +
'Medium Contrast' = Medium Contrast +
'Strong Contrast' = Strong Contrast
ToneCurveName2012string 
ToneCurvePV2012string+ 
ToneCurvePV2012Bluestring+ 
ToneCurvePV2012Greenstring+ 
ToneCurvePV2012Redstring+ 
ToneCurveRedstring+ 
ToneMapStrengthreal 
UprightCenterModeinteger 
UprightCenterNormXreal 
UprightCenterNormYreal 
UprightDependentDigeststring 
UprightFocalLength35mmreal 
UprightFocalModeinteger 
UprightFourSegments_0string 
UprightFourSegments_1string 
UprightFourSegments_2string 
UprightFourSegments_3string 
UprightFourSegmentsCountinteger 
UprightGuidedDependentDigeststring 
UprightPreviewboolean 
UprightTransform_0string 
UprightTransform_1string 
UprightTransform_2string 
UprightTransform_3string 
UprightTransform_4string 
UprightTransform_5string 
UprightTransformCountinteger 
UprightVersioninteger 
UUIDstring/ 
Versionstring 
Vibranceinteger 
VignetteAmountinteger 
VignetteMidpointinteger 
Whatstring 
WhiteBalancestring/ +
'As Shot' = As Shot +
'Auto' = Auto +
'Cloudy' = Cloudy +
'Custom' = Custom +
'Daylight' = Daylight +
'Flash' = Flash +
'Fluorescent' = Fluorescent +
'Shade' = Shade +
'Tungsten' = Tungsten
+
Whites2012integer 
+ +

XMP dc Tags

+

Dublin Core namespace tags.

+ +

These tags belong to the ExifTool XMP-dc family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Contributorstring+ 
Coveragestring 
Creatorstring+ 
Datedate+ 
Descriptionlang-alt 
Formatstring 
Identifierstring 
Languagestring+ 
Publisherstring+ 
Relationstring+ 
Rightslang-alt 
Sourcestring/ 
Subjectstring+ 
Titlelang-alt 
Typestring+ 
+ +

XMP Device Tags

+

Google depth-map Device tags. See +https://developer.android.com/training/camera2/Dynamic-depth-v1.0.pdf for +the specification.

+ +

These tags belong to the ExifTool XMP-Device family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AppInfostruct--> AppInfo Struct
AppInfoApplicationstring_ 
AppInfoItemURIstring_ 
AppInfoVersionstring_ 
Camerasstruct+--> DeviceCameras Struct
Camerastruct_+--> DeviceCamera Struct +
(CamerasCamera)
CameraAppInfostruct_+--> AppInfo Struct +
(CamerasCameraAppInfo)
CameraAppInfoApplicationstring_+(CamerasCameraAppInfoApplication)
CameraAppInfoItemURIstring_+(CamerasCameraAppInfoItemURI)
CameraAppInfoVersionstring_+(CamerasCameraAppInfoVersion)
CameraDepthMapstruct_+--> DeviceDepthMap Struct +
(CamerasCameraDepthMap)
CameraDepthMapConfidenceURIstring_+(CamerasCameraDepthMapConfidenceURI)
CameraDepthMapDepthURIstring_+(CamerasCameraDepthMapDepthURI)
CameraDepthMapFarreal_+(CamerasCameraDepthMapFar)
CameraDepthMapFocalTablestring_+(CamerasCameraDepthMapFocalTable)
CameraDepthMapFocalTableEntryCountinteger_+(CamerasCameraDepthMapFocalTableEntryCount)
CameraDepthMapFormatstring_+(CamerasCameraDepthMapFormat)
CameraDepthMapItemSemanticstring_+(CamerasCameraDepthMapItemSemantic)
CameraDepthMapMeasureTypestring_+(CamerasCameraDepthMapMeasureType)
CameraDepthMapNearreal_+(CamerasCameraDepthMapNear)
CameraDepthMapSoftwarestring_+(CamerasCameraDepthMapSoftware)
CameraDepthMapUnitsstring_+(CamerasCameraDepthMapUnits)
CameraImagestruct_+--> DeviceImage Struct +
(CamerasCameraImage)
CameraImageItemSemanticstring_+(CamerasCameraImageItemSemantic)
CameraImageItemURIstring_+(CamerasCameraImageItemURI)
CameraImagingModelstruct_+--> DeviceImagingModel Struct +
(CamerasCameraImagingModel)
CameraImagingModelDistortionstring_+(CamerasCameraImagingModelDistortion)
CameraImagingModelDistortionCountinteger_+(CamerasCameraImagingModelDistortionCount)
CameraImagingModelFocalLengthXreal_+(CamerasCameraImagingModelFocalLengthX)
CameraImagingModelFocalLengthYreal_+(CamerasCameraImagingModelFocalLengthY)
CameraImagingModelImageHeightinteger_+(CamerasCameraImagingModelImageHeight)
CameraImagingModelImageWidthinteger_+(CamerasCameraImagingModelImageWidth)
CameraImagingModelPixelAspectRatioreal_+(CamerasCameraImagingModelPixelAspectRatio)
CameraImagingModelPrincipalPointXreal_+(CamerasCameraImagingModelPrincipalPointX)
CameraImagingModelPrincipalPointYreal_+(CamerasCameraImagingModelPrincipalPointY)
CameraImagingModelSkewreal_+(CamerasCameraImagingModelSkew)
CameraLightEstimatestruct_+--> DeviceLightEstimate Struct +
(CamerasCameraLightEstimate)
CameraLightEstimateColorCorrectionBreal_+(CamerasCameraLightEstimateColorCorrectionB)
CameraLightEstimateColorCorrectionGreal_+(CamerasCameraLightEstimateColorCorrectionG)
CameraLightEstimateColorCorrectionRreal_+(CamerasCameraLightEstimateColorCorrectionR)
CameraLightEstimatePixelIntensityreal_+(CamerasCameraLightEstimatePixelIntensity)
CameraPointCloudstruct_+--> DevicePointCloud Struct +
(CamerasCameraPointCloud)
CameraPointCloudMetricboolean_+(CamerasCameraPointCloudMetric)
CameraPointCloudPointCloudinteger_+(CamerasCameraPointCloudPointCloud)
CameraPointCloudPointsstring_+(CamerasCameraPointCloudPoints)
CameraPosestruct_+--> Pose Struct +
(CamerasCameraPose)
CameraPosePositionXreal_+(CamerasCameraPosePositionX)
CameraPosePositionYreal_+(CamerasCameraPosePositionY)
CameraPosePositionZreal_+(CamerasCameraPosePositionZ)
CameraPoseRotationWreal_+(CamerasCameraPoseRotationW)
CameraPoseRotationXreal_+(CamerasCameraPoseRotationX)
CameraPoseRotationYreal_+(CamerasCameraPoseRotationY)
CameraPoseRotationZreal_+(CamerasCameraPoseRotationZ)
CameraPoseTimestampinteger_+(CamerasCameraPoseTimestamp)
CameraTraitstring_+(CamerasCameraTrait)
CameraVendorInfostruct_+--> VendorInfo Struct +
(CamerasCameraVendorInfo)
CameraVendorInfoManufacturerstring_+(CamerasCameraVendorInfoManufacturer)
CameraVendorInfoModelstring_+(CamerasCameraVendorInfoModel)
CameraVendorInfoNotesstring_+(CamerasCameraVendorInfoNotes)
Containerstruct--> DeviceContainer Struct
ContainerDirectorystruct_+--> DeviceDirectory Struct
ContainerDirectoryItemstruct_+--> DeviceItem Struct
ContainerDirectoryItemDataURIstring_+ 
ContainerDirectoryItemLengthinteger_+ 
ContainerDirectoryItemMimestring_+ 
ContainerDirectoryItemPaddinginteger_+ 
EarthPosstruct--> EarthPose Struct
EarthPosAltitudereal_ 
EarthPosLatitudereal_ 
EarthPosLongitudereal_ 
EarthPosRotationWreal_ 
EarthPosRotationXreal_ 
EarthPosRotationYreal_ 
EarthPosRotationZreal_ 
EarthPosTimestampinteger_ 
Planesstruct+--> DevicePlanes Struct
Planestruct_+--> DevicePlane Struct +
(PlanesPlane)
PlaneBoundarystring_+(PlanesPlaneBoundary)
PlaneBoundaryVertexCountinteger_+(PlanesPlaneBoundaryVertexCount)
PlaneExtentXreal_+(PlanesPlaneExtentX)
PlaneExtentZreal_+(PlanesPlaneExtentZ)
PlanePosestruct_+--> Pose Struct +
(PlanesPlanePose)
PlanePosePositionXreal_+(PlanesPlanePosePositionX)
PlanePosePositionYreal_+(PlanesPlanePosePositionY)
PlanePosePositionZreal_+(PlanesPlanePosePositionZ)
PlanePoseRotationWreal_+(PlanesPlanePoseRotationW)
PlanePoseRotationXreal_+(PlanesPlanePoseRotationX)
PlanePoseRotationYreal_+(PlanesPlanePoseRotationY)
PlanePoseRotationZreal_+(PlanesPlanePoseRotationZ)
PlanePoseTimestampinteger_+(PlanesPlanePoseTimestamp)
Posestruct--> Pose Struct
PosePositionXreal_ 
PosePositionYreal_ 
PosePositionZreal_ 
PoseRotationWreal_ 
PoseRotationXreal_ 
PoseRotationYreal_ 
PoseRotationZreal_ 
PoseTimestampinteger_ 
Profilesstruct+--> DeviceProfiles Struct
Profilestruct_+--> DeviceProfile Struct +
(ProfilesProfile)
ProfileCameraIndicesinteger_+(ProfilesProfileCameraIndices)
ProfileTypestring_+(ProfilesProfileType)
VendorInfostruct--> VendorInfo Struct
VendorInfoManufacturerstring_ 
VendorInfoModelstring_ 
VendorInfoNotesstring_ 
+ +

XMP AppInfo Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Applicationstring 
ItemURIstring 
Versionstring 
+ +

XMP DeviceCameras Struct

+
+
+ + + + + + + +
Field NameWritableValues / Notes
CameraDeviceCamera--> DeviceCamera Struct
+ +

XMP DeviceCamera Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
AppInfoAppInfo--> AppInfo Struct
DepthMapDeviceDepthMap--> DeviceDepthMap Struct
ImageDeviceImage--> DeviceImage Struct
ImagingModelDeviceImagingModel--> DeviceImagingModel Struct
LightEstimateDeviceLightEstimate--> DeviceLightEstimate Struct
PointCloudDevicePointCloud--> DevicePointCloud Struct
PosePose--> Pose Struct
Traitstring 
VendorInfoVendorInfo--> VendorInfo Struct
+ +

XMP DeviceDepthMap Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
ConfidenceURIstring 
DepthURIstring 
Farreal 
FocalTablestring 
FocalTableEntryCountinteger 
Formatstring 
ItemSemanticstring 
MeasureTypestring 
Nearreal 
Softwarestring 
Unitsstring 
+ +

XMP DeviceImage Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
ItemSemanticstring 
ItemURIstring 
+ +

XMP DeviceImagingModel Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Distortionstring 
DistortionCountinteger 
FocalLengthXreal 
FocalLengthYreal 
ImageHeightinteger 
ImageWidthinteger 
PixelAspectRatioreal 
PrincipalPointXreal 
PrincipalPointYreal 
Skewreal 
+ +

XMP DeviceLightEstimate Struct

+
+
+ + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
ColorCorrectionBreal 
ColorCorrectionGreal 
ColorCorrectionRreal 
PixelIntensityreal 
+ +

XMP DevicePointCloud Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Metricboolean 
PointCloudinteger 
Pointsstring 
+ +

XMP Pose Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
PositionXreal 
PositionYreal 
PositionZreal 
RotationWreal 
RotationXreal 
RotationYreal 
RotationZreal 
Timestampinteger 
+ +

XMP VendorInfo Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Manufacturerstring 
Modelstring 
Notesstring 
+ +

XMP DeviceContainer Struct

+
+
+ + + + + + + +
Field NameWritableValues / Notes
DirectoryDeviceDirectory+--> DeviceDirectory Struct
+ +

XMP DeviceDirectory Struct

+
+
+ + + + + + + +
Field NameWritableValues / Notes
ItemDeviceItem--> DeviceItem Struct
+ +

XMP DeviceItem Struct

+
+
+ + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
DataURIstring 
Lengthinteger 
Mimestring 
Paddinginteger 
+ +

XMP EarthPose Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Altitudereal 
Latitudereal 
Longitudereal 
RotationWreal 
RotationXreal 
RotationYreal 
RotationZreal 
Timestampinteger 
+ +

XMP DevicePlanes Struct

+
+
+ + + + + + + +
Field NameWritableValues / Notes
PlaneDevicePlane--> DevicePlane Struct
+ +

XMP DevicePlane Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Boundarystring 
BoundaryVertexCountinteger 
ExtentXreal 
ExtentZreal 
PosePose--> Pose Struct
+ +

XMP DeviceProfiles Struct

+
+
+ + + + + + + +
Field NameWritableValues / Notes
ProfileDeviceProfile--> DeviceProfile Struct
+ +

XMP DeviceProfile Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
CameraIndicesinteger+ 
Typestring 
+ +

XMP dex Tags

+

Description Explorer namespace tags. These tags are not very common. The +Source and Rating tags are avoided when writing due to name conflicts with +other XMP tags. (see http://www.optimasc.com/products/fileid/)

+ +

These tags belong to the ExifTool XMP-dex family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CRC32integer 
FFIDstring 
LicenseTypestring +
'adware' = Adware +
'commercial' = Commercial +
'demo' = Demo +
'freeware' = Freeware +
'open source' = Open Source +
'public domain' = Public Domain +
'shareware' = Shareware +
'unknown' = Unknown
+
OSinteger 
Ratingstring/ 
Revisionstring 
ShortDescriptionlang-alt 
Sourcestring/ 
+ +

XMP DICOM Tags

+

DICOM namespace tags. These XMP tags allow some DICOM information to be +stored in files of other than DICOM format. See the +DICOM Tags documentation for a list +of tags available in DICOM-format files.

+ +

These tags belong to the ExifTool XMP-DICOM family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
EquipmentInstitutionstring 
EquipmentManufacturerstring 
PatientBirthDatedate(called PatientDOB by the spec)
PatientIDstring 
PatientNamestring 
PatientSexstring 
SeriesDateTimedate 
SeriesDescriptionstring 
SeriesModalitystring 
SeriesNumberstring 
StudyDateTimedate 
StudyDescriptionstring 
StudyIDstring 
StudyPhysicianstring 
+ +

XMP digiKam Tags

+

DigiKam namespace tags.

+ +

These tags belong to the ExifTool XMP-digiKam family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CaptionsAuthorNameslang-alt 
CaptionsDateTimeStampslang-alt 
ColorLabelstring 
ImageHistorystring/(different format from EXIF:ImageHistory)
ImageUniqueIDstring/ 
LensCorrectionSettingsstring 
PicasawebGPhotoIdstring 
PickLabelstring 
TagsListstring+ 
+ +

XMP ExifTool Tags

+

These tags belong to the ExifTool XMP-et family 1 group.

+
+
+ + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
OriginalImageHashstring(used to store ExifTool ImageDataHash digest)
OriginalImageHashTypestring(ImageHashType API setting, default 'MD5')
OriginalImageMD5string(deprecated)
+ +

XMP exif Tags

+

EXIF namespace for EXIF tags. See +https://web.archive.org/web/20180921145139if_/http://www.cipa.jp:80/std/documents/e/DC-010-2017_E.pdf +for the specification.

+ +

These tags belong to the ExifTool XMP-exif family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ApertureValuerational 
BrightnessValuerational 
CFAPatternstruct--> CFAPattern Struct
CFAPatternColumnsinteger_ 
CFAPatternRowsinteger_ 
CFAPatternValuesinteger_+ 
ColorSpaceinteger1 = sRGB +
2 = Adobe RGB +
65535 = Uncalibrated
ComponentsConfigurationinteger+ + +
0 = - +
1 = Y +
2 = Cb +
3 = Cr
  4 = R +
5 = G +
6 = B
+
CompressedBitsPerPixelrational 
Contrastinteger0 = Normal +
1 = Low +
2 = High
CustomRenderedinteger0 = Normal +
1 = Custom
DateTimeDigitizeddate 
DateTimeOriginaldate 
DeviceSettingDescriptionstruct--> DeviceSettings Struct
DeviceSettingDescriptionColumnsinteger_ 
DeviceSettingDescriptionRowsinteger_ 
DeviceSettingDescriptionSettingsstring_+ 
DigitalZoomRatiorational 
ExifVersionstring 
ExposureCompensationrational(called ExposureBiasValue by the spec)
ExposureIndexrational 
ExposureModeinteger0 = Auto +
1 = Manual +
2 = Auto bracket
ExposurePrograminteger +
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
+
ExposureTimerational 
FileSourceinteger1 = Film Scanner +
2 = Reflection Print Scanner +
3 = Digital Camera
Flashstruct--> Flash Struct
FlashEnergyrational 
FlashFiredboolean_'False' = False +
'True' = True
FlashFunctionboolean_'False' = False +
'True' = True
FlashModeinteger_0 = Unknown +
1 = On +
2 = Off +
3 = Auto
FlashpixVersionstring 
FlashRedEyeModeboolean_'False' = False +
'True' = True
FlashReturninteger_0 = No return detection +
2 = Return not detected +
3 = Return detected
FNumberrational 
FocalLengthrational 
FocalLengthIn35mmFormatinteger(called FocalLengthIn35mmFilm by the spec)
FocalPlaneResolutionUnitinteger(values 1, 4 and 5 are not standard EXIF) +
1 = None +
2 = inches +
3 = cm +
4 = mm +
5 = um
FocalPlaneXResolutionrational 
FocalPlaneYResolutionrational 
GainControlinteger0 = None +
1 = Low gain up +
2 = High gain up +
3 = Low gain down +
4 = High gain down
GPSAltituderational 
GPSAltitudeRefinteger0 = Above Sea Level +
1 = Below Sea Level
GPSAreaInformationstring 
GPSDestBearingrational 
GPSDestBearingRefstring'M' = Magnetic North +
'T' = True North
GPSDestDistancerational 
GPSDestDistanceRefstring'K' = Kilometers +
'M' = Miles +
'N' = Nautical Miles
GPSDestLatitudestring 
GPSDestLongitudestring 
GPSDifferentialinteger0 = No Correction +
1 = Differential Corrected
GPSDOPrational 
GPSHPositioningErrorrational 
GPSImgDirectionrational 
GPSImgDirectionRefstring'M' = Magnetic North +
'T' = True North
GPSLatitudestring 
GPSLongitudestring 
GPSMapDatumstring 
GPSMeasureModeinteger2 = 2-Dimensional Measurement +
3 = 3-Dimensional Measurement
GPSProcessingMethodstring 
GPSSatellitesstring 
GPSSpeedrational 
GPSSpeedRefstring'K' = km/h +
'M' = mph +
'N' = knots
GPSStatusstring'A' = Measurement Active +
'V' = Measurement Void
GPSDateTimedate(called GPSTimeStamp by the spec; a date/time tag called GPSTimeStamp by the XMP specification. This tag is +renamed here to prevent direct copy from EXIF:GPSTimeStamp which is a +time-only tag. Instead, the value of this tag should be taken from +Composite:GPSDateTime when copying from EXIF)
GPSTrackrational 
GPSTrackRefstring'M' = Magnetic North +
'T' = True North
GPSVersionIDstring 
ImageUniqueIDstring 
ISOinteger+(called ISOSpeedRatings by the spec)
LightSourcestring--> EXIF LightSource Values
MakerNotestring 
MaxApertureValuerational 
MeteringModeinteger +
1 = Average +
2 = Center-weighted average +
3 = Spot +
4 = Multi-spot +
5 = Multi-segment +
6 = Partial +
255 = Other
+
NativeDigeststring 
Opto-ElectricConvFactorstruct--> OECF Struct +
(called OECF by the spec)
OECFColumnsinteger_ 
OECFNamesstring_+ 
OECFRowsinteger_ 
OECFValuesrational_+ 
ExifImageWidthinteger(called PixelXDimension by the spec)
ExifImageHeightinteger(called PixelYDimension by the spec)
RelatedSoundFilestring 
Saturationinteger0 = Normal +
1 = Low +
2 = High
SceneCaptureTypeinteger0 = Standard +
1 = Landscape +
2 = Portrait +
3 = Night
SceneTypeinteger1 = Directly photographed
SensingMethodinteger(values 1 and 6 are not standard EXIF) +
1 = Monochrome area +
2 = One-chip color area +
3 = Two-chip color area +
4 = Three-chip color area +
5 = Color sequential area +
6 = Monochrome linear +
7 = Trilinear +
8 = Color sequential linear
+
Sharpnessinteger0 = Normal +
1 = Soft +
2 = Hard
ShutterSpeedValuerational 
SpatialFrequencyResponsestruct--> OECF Struct
SpatialFrequencyResponseColumnsinteger_ 
SpatialFrequencyResponseNamesstring_+ 
SpatialFrequencyResponseRowsinteger_ 
SpatialFrequencyResponseValuesrational_+ 
SpectralSensitivitystring 
SubjectAreainteger+ 
SubjectDistancerational 
SubjectDistanceRangeinteger0 = Unknown +
1 = Macro +
2 = Close +
3 = Distant
SubjectLocationinteger+ 
UserCommentlang-alt 
WhiteBalanceinteger0 = Auto +
1 = Manual
+ +

XMP CFAPattern Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Columnsinteger 
Rowsinteger 
Valuesinteger+ 
+ +

XMP DeviceSettings Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Columnsinteger 
Rowsinteger 
Settingsstring+ 
+ +

XMP Flash Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
FiredbooleanFalse = False +
True = True
FunctionbooleanFalse = False +
True = True
Modeinteger0 = Unknown +
1 = On +
2 = Off +
3 = Auto
RedEyeModebooleanFalse = False +
True = True
Returninteger0 = No return detection +
2 = Return not detected +
3 = Return detected
+ +

XMP OECF Struct

+
+
+ + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Columnsinteger 
Namesstring+ 
Rowsinteger 
Valuesrational+ 
+ +

XMP exifEX Tags

+

EXIF tags added by the EXIF 2.32 for XMP specification (see +https://cipa.jp/std/documents/download_e.html?DC-010-2020_E).

+ +

These tags belong to the ExifTool XMP-exifEX family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Accelerationrational 
SerialNumberstring(called BodySerialNumber by the spec)
CameraElevationAnglerational 
OwnerNamestring(called CameraOwnerName by the spec)
CompositeImageinteger0 = Unknown +
1 = Not a Composite Image +
2 = General Composite Image +
3 = Composite Image Captured While Shooting
CompositeImageCountinteger+ 
CompositeImageExposureTimesstruct--> CompImageExp Struct
CompImageMaxExposureAllrational_(CompositeImageExposureTimesMaxExposureTimesOfAll)
CompImageMaxExposureUsedrational_(CompositeImageExposureTimesMaxExposureTimesOfUsed)
CompImageMinExposureAllrational_(CompositeImageExposureTimesMinExposureTimesOfAll)
CompImageMinExposureUsedrational_(CompositeImageExposureTimesMinExposureTimesOfUsed)
CompImageImagesPerSequenceinteger_(CompositeImageExposureTimesNumberOfImagesInSequences)
CompImageNumSequencesinteger_(CompositeImageExposureTimesNumberOfSequences)
CompImageSumExposureAllrational_(CompositeImageExposureTimesSumOfExposureTimesOfAll)
CompImageSumExposureUsedrational_(CompositeImageExposureTimesSumOfExposureTimesOfUsed)
CompImageTotalExposurePeriodrational_(CompositeImageExposureTimesTotalExposurePeriod)
CompImageValuesrational_+(CompositeImageExposureTimesValues)
Gammarational 
Humidityrational 
InteropIndexstring(called InteroperabilityIndex by the spec) +
'R03' = R03 - DCF option file (Adobe RGB) +
'R98' = R98 - DCF basic file (sRGB) +
'THM' = THM - DCF thumbnail file
ISOSpeedinteger 
ISOSpeedLatitudeyyyinteger 
ISOSpeedLatitudezzzinteger 
LensMakestring 
LensModelstring 
LensSerialNumberstring 
LensInforational+(called LensSpecification by the spec; unfortunately the EXIF 2.3 for XMP specification defined this new tag +instead of using the existing XMP-aux:LensInfo)
PhotographicSensitivityinteger 
Pressurerational 
RecommendedExposureIndexinteger 
SensitivityTypeinteger0 = Unknown +
1 = Standard Output Sensitivity +
2 = Recommended Exposure Index +
3 = ISO Speed +
4 = Standard Output Sensitivity and Recommended Exposure Index +
5 = Standard Output Sensitivity and ISO Speed +
6 = Recommended Exposure Index and ISO Speed +
7 = Standard Output Sensitivity, Recommended Exposure Index and ISO Speed
StandardOutputSensitivityinteger 
AmbientTemperaturerational(called Temperature by the spec)
WaterDepthrational 
+ +

XMP CompImageExp Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
MaxExposureTimesOfAllrational 
MaxExposureTimesOfUsedrational 
MinExposureTimesOfAllrational 
MinExposureTimesOfUsedrational 
NumberOfImagesInSequencesinteger 
NumberOfSequencesinteger 
SumOfExposureTimesOfAllrational 
SumOfExposureTimesOfUsedrational 
TotalExposurePeriodrational 
Valuesrational+ 
+ +

XMP ExpressionMedia Tags

+

Microsoft Expression Media namespace tags. These tags are avoided when +writing due to name conflicts with tags in other schemas.

+ +

These tags belong to the ExifTool XMP-expressionmedia family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CatalogSetsstring/+ 
Eventstring/ 
Peoplestring/+ 
Statusstring/ 
+ +

XMP extensis Tags

+

Tags used by Extensis Portfolio.

+ +

These tags belong to the ExifTool XMP-extensis family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Approvedboolean 
ApprovedBystring 
ClientNamestring 
JobNamestring 
JobStatusstring 
RoutedTostring 
RoutingNotesstring 
WorkToDostring 
+ +

XMP fpv Tags

+

Fast Picture Viewer tags (see +http://www.fastpictureviewer.com/help/#rtfcomments).

+ +

These tags belong to the ExifTool XMP-fpv family 1 group.

+
+
+ + + + + + + +
Tag NameWritableValues / Notes
RichTextCommentstring 
+ +

XMP GAudio Tags

+

These tags belong to the ExifTool XMP-GAudio family 1 group.

+
+
+ + + + + + + + + + + +
Tag NameWritableValues / Notes
AudioDatastring(called Data by the spec)
AudioMimeTypestring(called Mime by the spec)
+ +

XMP GCamera Tags

+

Camera information found in Google panorama images.

+ +

These tags belong to the ExifTool XMP-GCamera family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
BurstIDstring 
BurstPrimarystring 
DisableAutoCreationstring+ 
HDRPMakerNotestring(called hdrp_makernote by the spec)
MicroVideointeger 
MicroVideoOffsetinteger 
MicroVideoPresentationTimestampUsinteger 
MicroVideoVersioninteger 
PortraitNotestring 
PortraitRequeststring(High Definition Render Pipeline (HDRP) data)
PortraitVersionstring 
ShotLogDatastring(called shot_log_data by the spec)
SpecialTypeIDstring+ 
+ +

XMP GCreations Tags

+

Google creations tags.

+ +

These tags belong to the ExifTool XMP-GCreations family 1 group.

+
+
+ + + + + + + + + + + +
Tag NameWritableValues / Notes
CameraBurstIDstring 
Typestring/ 
+ +

XMP GDepth Tags

+

Google depthmap information. See +https://developers.google.com/depthmap-metadata/ for the specification.

+ +

These tags belong to the ExifTool XMP-GDepth family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Confidencestring/ 
ConfidenceMimestring/ 
DepthImagestring/(called Data by the spec)
Farreal/ 
Formatstring/'RangeInverse' = RangeInverse +
'RangeLinear' = RangeLinear
ImageHeightreal/ 
ImageWidthreal/ 
Manufacturerstring/ 
MeasureTypestring/'OpticalAxis' = OpticalAxis +
'OpticalRay' = OpticalRay
Mimestring/ 
Modelstring/ 
Nearreal/ 
Softwarestring/ 
Unitsstring/ 
+ +

XMP GettyImages Tags

+

The actual Getty Images namespace prefix is "GettyImagesGIFT", which is the +prefix recorded in the file, but ExifTool shortens this for the family 1 +group name.

+ +

These tags belong to the ExifTool XMP-getty family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AssetIDstring 
CallForImagestring 
CameraFilenamestring 
CameraMakeModelstring/ 
CameraSerialNumberstring/ 
Compositionstring 
ExclusiveCoveragestring 
GIFTFtpPrioritystring 
ImageRankstring 
MediaEventIdDatestring 
OriginalCreateDateTimedate/ 
OriginalFileNamestring 
ParentMediaEventIDstring 
ParentMEIDstring 
Personalitystring+ 
PrimaryFTPstring+ 
RoutingDestinationsstring+ 
RoutingExclusionsstring+ 
SecondaryFTPstring+ 
TimeShotstring 
+ +

XMP GFocus Tags

+

Focus information found in Google depthmap images.

+ +

These tags belong to the ExifTool XMP-GFocus family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
BlurAtInfinityreal 
FocalDistancereal 
FocalPointXreal 
FocalPointYreal 
+ +

XMP GImage Tags

+

These tags belong to the ExifTool XMP-GImage family 1 group.

+
+
+ + + + + + + + + + + +
Tag NameWritableValues / Notes
ImageDatastring(called Data by the spec)
ImageMimeTypestring(called Mime by the spec)
+ +

XMP GPano Tags

+

Panorama tags written by Google Photosphere. See +https://developers.google.com/panorama/metadata/ for the specification.

+ +

These tags belong to the ExifTool XMP-GPano family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CaptureSoftwarestring 
CroppedAreaImageHeightPixelsreal 
CroppedAreaImageWidthPixelsreal 
CroppedAreaLeftPixelsreal 
CroppedAreaTopPixelsreal 
ExposureLockUsedboolean 
FirstPhotoDatedate 
FullPanoHeightPixelsreal 
FullPanoWidthPixelsreal 
InitialCameraDollyreal 
InitialHorizontalFOVDegreesreal 
InitialVerticalFOVDegreesreal 
InitialViewHeadingDegreesreal 
InitialViewPitchDegreesreal 
InitialViewRollDegreesreal 
LargestValidInteriorRectHeightreal 
LargestValidInteriorRectLeftreal 
LargestValidInteriorRectTopreal 
LargestValidInteriorRectWidthreal 
LastPhotoDatedate 
PoseHeadingDegreesreal 
PosePitchDegreesreal 
PoseRollDegreesreal 
ProjectionTypestring 
SourcePhotosCountinteger 
StitchingSoftwarestring 
UsePanoramaViewerboolean 
+ +

XMP GSpherical Tags

+

Not actually XMP. These RDF/XML tags are used in Google spherical MP4 +videos. These tags are written into the video track of MOV/MP4 files, and +not at the top level like other XMP tags. See +https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md +for the specification.

+ +

These tags belong to the ExifTool XMP-GSpherical family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CroppedAreaImageHeightPixelsinteger/ 
CroppedAreaImageWidthPixelsinteger/ 
CroppedAreaLeftPixelsinteger/ 
CroppedAreaTopPixelsinteger/ 
FullPanoHeightPixelsinteger/ 
FullPanoWidthPixelsinteger/ 
InitialViewHeadingDegreesreal/ 
InitialViewPitchDegreesreal/ 
InitialViewRollDegreesreal/ 
ProjectionTypestring/ 
SourceCountinteger/ 
Sphericalboolean/ 
StereoModestring/ 
Stitchedboolean/ 
StitchingSoftwarestring/ 
TimeStampinteger/ 
+ +

XMP hdr Tags

+

HDR metadata namespace tags written by ACR 15.1. The actual namespace +prefix is "hdr_metadata", which is the prefix recorded in the file, but +ExifTool shortens this for the family 1 group name.

+ +

These tags belong to the ExifTool XMP-hdr family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CCVAvgLuminanceNitsreal(called ccv_avg_luminance_nits by the spec)
CCVMaxLuminanceNitsreal(called ccv_max_luminance_nits by the spec)
CCVMinLuminanceNitsreal(called ccv_min_luminance_nits by the spec)
CCVPrimariesXYstring(called ccv_primaries_xy by the spec)
CCVWhiteXYstring(called ccv_white_xy by the spec)
SceneReferredboolean(called scene_referred by the spec)
+ +

XMP hdrgm Tags

+

Tags used in Adobe gain map images.

+ +

These tags belong to the ExifTool XMP-hdrgm family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
BaseRenditionIsHDRboolean 
GainMapMaxreal+ 
GainMapMinreal+ 
Gammareal/+ 
HDRCapacityMaxreal 
HDRCapacityMinreal 
OffsetHDRreal+ 
OffsetSDRreal+ 
Versionstring/ 
+ +

XMP ics Tags

+

Tags used by IDimager. Nested TagStructure structures are unrolled to an +arbitrary depth of 6 to avoid infinite recursion.

+ +

These tags belong to the ExifTool XMP-ics family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AppVersionstring/ 
ImageRefstring 
SubVersionsstruct+--> SubVersion Struct
SubVersionFileNamestring_+(SubVersionsFileName)
SubVersionReferencestring_+(SubVersionsVersRef)
TagStructurestruct+--> TagStructure Struct
LabelName1string_+(TagStructureLabelName)
ParentReference1string_+(TagStructureParentReference)
Reference1string_+(TagStructureReference)
SubLabels1struct_+--> TagStructure Struct +
(TagStructureSubLabels)
LabelName2string_+(TagStructureSubLabelsLabelName)
ParentReference2string_+(TagStructureSubLabelsParentReference)
Reference2string_+(TagStructureSubLabelsReference)
SubLabels2struct_+--> TagStructure Struct +
(TagStructureSubLabelsSubLabels)
LabelName3string_+(TagStructureSubLabelsSubLabelsLabelName)
ParentReference3string_+(TagStructureSubLabelsSubLabelsParentReference)
Reference3string_+(TagStructureSubLabelsSubLabelsReference)
SubLabels3struct_+--> TagStructure Struct +
(TagStructureSubLabelsSubLabelsSubLabels)
LabelName4string_+(TagStructureSubLabelsSubLabelsSubLabelsLabelName)
ParentReference4string_+(TagStructureSubLabelsSubLabelsSubLabelsParentReference)
Reference4string_+(TagStructureSubLabelsSubLabelsSubLabelsReference)
SubLabels4struct_+--> TagStructure Struct +
(TagStructureSubLabelsSubLabelsSubLabelsSubLabels)
LabelName5string_+(TagStructureSubLabelsSubLabelsSubLabelsSubLabelsLabelName)
ParentReference5string_+(TagStructureSubLabelsSubLabelsSubLabelsSubLabelsParentReference)
Reference5string_+(TagStructureSubLabelsSubLabelsSubLabelsSubLabelsReference)
SubLabels5struct_+--> TagStructure Struct +
(TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabels)
LabelName6string_+(TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabelsLabelName)
ParentReference6string_+(TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabelsParentReference)
Reference6string_+(TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabelsReference)
TimeStampdate/ 
+ +

XMP SubVersion Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
FileNamestring 
VersRefstring 
+ +

XMP TagStructure Struct

+
+
+ + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
LabelNamestring 
ParentReferencestring 
Referencestring 
SubLabelsTagStructure+--> TagStructure Struct
+ +

XMP iptcCore Tags

+

IPTC Core namespace tags. The actual IPTC Core namespace prefix is +"Iptc4xmpCore", which is the prefix recorded in the file, but ExifTool +shortens this for the family 1 group name. (see +http://www.iptc.org/IPTC4XMP/)

+ +

These tags belong to the ExifTool XMP-iptcCore family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AltTextAccessibilitylang-alt 
CountryCodestring 
CreatorContactInfostruct--> ContactInfo Struct
CreatorCitystring_(CreatorContactInfoCiAdrCity)
CreatorCountrystring_(CreatorContactInfoCiAdrCtry)
CreatorAddressstring_(CreatorContactInfoCiAdrExtadr)
CreatorPostalCodestring_(CreatorContactInfoCiAdrPcode)
CreatorRegionstring_(CreatorContactInfoCiAdrRegion)
CreatorWorkEmailstring_(CreatorContactInfoCiEmailWork)
CreatorWorkTelephonestring_(CreatorContactInfoCiTelWork)
CreatorWorkURLstring_(CreatorContactInfoCiUrlWork)
ExtDescrAccessibilitylang-alt 
IntellectualGenrestring 
Locationstring 
Scenestring+ 
SubjectCodestring+ 
+ +

XMP ContactInfo Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
CiAdrCitystring 
CiAdrCtrystring 
CiAdrExtadrstring 
CiAdrPcodestring 
CiAdrRegionstring 
CiEmailWorkstring 
CiTelWorkstring 
CiUrlWorkstring 
+ +

XMP iptcExt Tags

+

This table contains tags defined by the IPTC Extension schema version 1.7 +and IPTC Video Metadata version 1.3. The actual namespace prefix is +"Iptc4xmpExt", but ExifTool shortens this for the family 1 group name. (See +http://www.iptc.org/standards/photo-metadata/iptc-standard/ and +https://iptc.org/standards/video-metadata-hub/.)

+ +

These tags belong to the ExifTool XMP-iptcExt family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AboutCvTermstruct+--> CVTermDetails Struct
AboutCvTermCvIdstring_+ 
AboutCvTermIdstring_+(AboutCvTermCvTermId)
AboutCvTermNamelang-alt_+(AboutCvTermCvTermName)
AboutCvTermRefinedAboutstring_+(AboutCvTermCvTermRefinedAbout)
AdditionalModelInformationstring(called AddlModelInfo by the spec)
ArtworkOrObjectstruct+--> ArtworkOrObjectDetails Struct
ArtworkCircaDateCreatedstring!_+(ArtworkOrObjectAOCircaDateCreated)
ArtworkContentDescriptionlang-alt_+(ArtworkOrObjectAOContentDescription)
ArtworkContributionDescriptionlang-alt_+(ArtworkOrObjectAOContributionDescription)
ArtworkCopyrightNoticestring_+(ArtworkOrObjectAOCopyrightNotice)
ArtworkCreatorstring_+(ArtworkOrObjectAOCreator)
ArtworkCreatorIDstring_+(ArtworkOrObjectAOCreatorId)
ArtworkCopyrightOwnerIDstring_+(ArtworkOrObjectAOCurrentCopyrightOwnerId)
ArtworkCopyrightOwnerNamestring_+(ArtworkOrObjectAOCurrentCopyrightOwnerName)
ArtworkLicensorIDstring_+(ArtworkOrObjectAOCurrentLicensorId)
ArtworkLicensorNamestring_+(ArtworkOrObjectAOCurrentLicensorName)
ArtworkDateCreateddate_+(ArtworkOrObjectAODateCreated)
ArtworkPhysicalDescriptionlang-alt_+(ArtworkOrObjectAOPhysicalDescription)
ArtworkSourcestring_+(ArtworkOrObjectAOSource)
ArtworkSourceInventoryNostring_+(ArtworkOrObjectAOSourceInvNo)
ArtworkSourceInvURLstring_+(ArtworkOrObjectAOSourceInvURL)
ArtworkStylePeriodstring_+(ArtworkOrObjectAOStylePeriod)
ArtworkTitlelang-alt_+(ArtworkOrObjectAOTitle)
AudioBitrateinteger 
AudioBitrateModestring'fixed' = Fixed +
'variable' = Variable
AudioBitsPerSampleinteger 
AudioChannelCountinteger 
CircaDateCreatedstring 
ContainerFormatstruct--> Entity Struct
ContainerFormatIdentifierstring_+ 
ContainerFormatNamelang-alt_ 
Contributorstruct+--> EntityWithRole Struct
ContributorIdentifierstring_+ 
ContributorNamelang-alt_+ 
ContributorRolestring_+ 
CopyrightYearinteger 
Creatorstruct+--> EntityWithRole Struct
CreatorIdentifierstring_+ 
CreatorNamelang-alt_+ 
CreatorRolestring_+ 
ControlledVocabularyTermstring+(called CVterm by the spec; deprecated by version 1.2)
DataOnScreenstruct+--> TextRegion Struct
DataOnScreenRegionstruct_+--> Area Struct
DataOnScreenRegionDreal_+ 
DataOnScreenRegionHreal_+ 
DataOnScreenRegionTextstring_+ 
DataOnScreenRegionUnitstring_+ 
DataOnScreenRegionWreal_+ 
DataOnScreenRegionXreal_+ 
DataOnScreenRegionYreal_+ 
DigitalImageGUIDstring(called DigImageGUID by the spec)
DigitalSourceFileTypestring(now deprecated -- replaced by DigitalSourceType)
DigitalSourceTypestring 
Dopesheetlang-alt 
DopesheetLinkstruct+--> QualifiedLink Struct
DopesheetLinkLinkstring_+ 
DopesheetLinkLinkQualifierstring_+ 
EmbdEncRightsExprstruct+--> EEREDetails Struct
EmbeddedEncodedRightsExprstring_+(EmbdEncRightsExprEncRightsExpr)
EmbeddedEncodedRightsExprTypestring_+(EmbdEncRightsExprRightsExprEncType)
EmbeddedEncodedRightsExprLangIDstring_+(EmbdEncRightsExprRightsExprLangId)
Episodestruct--> EpisodeOrSeason Struct
EpisodeIdentifierstring_ 
EpisodeNamestring_ 
EpisodeNumberstring_ 
Eventlang-alt 
ShownEventstruct+--> Entity Struct +
(called EventExt by the spec)
ShownEventIdentifierstring_+(EventExtIdentifier)
ShownEventNamelang-alt_+(EventExtName)
EventIDstring+ 
ExternalMetadataLinkstring+ 
FeedIdentifierstring 
Genrestruct+--> CVTermDetails Struct
GenreCvIdstring_+ 
GenreCvTermIdstring_+ 
GenreCvTermNamelang-alt_+ 
GenreCvTermRefinedAboutstring_+ 
Headlinelang-alt/ 
ImageRegionstruct+--> ImageRegion Struct
ImageRegionNamelang-alt_+ 
ImageRegionCtypestruct_+--> Entity Struct +
(ImageRegionRCtype)
ImageRegionCtypeIdentifierstring_+(ImageRegionRCtypeIdentifier)
ImageRegionCtypeNamelang-alt_+(ImageRegionRCtypeName)
ImageRegionBoundarystruct_+--> RegionBoundary Struct +
(ImageRegionRegionBoundary)
ImageRegionBoundaryHreal_+(ImageRegionRegionBoundaryRbH)
ImageRegionBoundaryRxreal_+(ImageRegionRegionBoundaryRbRx)
ImageRegionBoundaryShapestring_+(ImageRegionRegionBoundaryRbShape) +
'circle' = Circle +
'polygon' = Polygon +
'rectangle' = Rectangle
ImageRegionBoundaryUnitstring_+(ImageRegionRegionBoundaryRbUnit) +
'pixel' = Pixel +
'relative' = Relative
ImageRegionBoundaryVerticesstruct_+--> BoundaryPoint Struct +
(ImageRegionRegionBoundaryRbVertices)
ImageRegionBoundaryVerticesXreal_+(ImageRegionRegionBoundaryRbVerticesRbX)
ImageRegionBoundaryVerticesYreal_+(ImageRegionRegionBoundaryRbVerticesRbY)
ImageRegionBoundaryWreal_+(ImageRegionRegionBoundaryRbW)
ImageRegionBoundaryXreal_+(ImageRegionRegionBoundaryRbX)
ImageRegionBoundaryYreal_+(ImageRegionRegionBoundaryRbY)
ImageRegionIDstring_+(ImageRegionRId)
ImageRegionRolestruct_+--> Entity Struct +
(ImageRegionRRole)
ImageRegionRoleIdentifierstring_+(ImageRegionRRoleIdentifier)
ImageRegionRoleNamelang-alt_+(ImageRegionRRoleName)
IPTCLastEditeddate 
LinkedEncRightsExprstruct+--> LEREDetails Struct
LinkedEncodedRightsExprstring_+(LinkedEncRightsExprLinkedRightsExpr)
LinkedEncodedRightsExprTypestring_+(LinkedEncRightsExprRightsExprEncType)
LinkedEncodedRightsExprLangIDstring_+(LinkedEncRightsExprRightsExprLangId)
LocationCreatedstruct+--> LocationDetails Struct
LocationCreatedCitystring_+ 
LocationCreatedCountryCodestring_+ 
LocationCreatedCountryNamestring_+ 
LocationCreatedGPSAltituderational_+ 
LocationCreatedGPSLatitudestring_+ 
LocationCreatedGPSLongitudestring_+ 
LocationCreatedIdentifierstring_+ 
LocationCreatedLocationIdstring_+ 
LocationCreatedLocationNamelang-alt_+ 
LocationCreatedProvinceStatestring_+ 
LocationCreatedSublocationstring_+ 
LocationCreatedWorldRegionstring_+ 
LocationShownstruct+--> LocationDetails Struct
LocationShownCitystring_+ 
LocationShownCountryCodestring_+ 
LocationShownCountryNamestring_+ 
LocationShownGPSAltituderational_+ 
LocationShownGPSLatitudestring_+ 
LocationShownGPSLongitudestring_+ 
LocationShownIdentifierstring_+ 
LocationShownLocationIdstring_+ 
LocationShownLocationNamelang-alt_+ 
LocationShownProvinceStatestring_+ 
LocationShownSublocationstring_+ 
LocationShownWorldRegionstring_+ 
MaxAvailHeightinteger 
MaxAvailWidthinteger 
MetadataAuthoritystruct--> Entity Struct
MetadataAuthorityIdentifierstring_+ 
MetadataAuthorityNamelang-alt_ 
MetadataLastEditeddate 
MetadataLastEditorstruct--> Entity Struct
MetadataLastEditorIdentifierstring_+ 
MetadataLastEditorNamelang-alt_ 
ModelAgeinteger+ 
OrganisationInImageCodestring+ 
OrganisationInImageNamestring+ 
ParentIDstring 
PersonHeardstruct+--> Entity Struct
PersonHeardIdentifierstring_+ 
PersonHeardNamelang-alt_+ 
PersonInImagestring+ 
PersonInImageWDetailsstruct+--> PersonDetails Struct
PersonInImageCharacteristicstruct_+--> CVTermDetails Struct +
(PersonInImageWDetailsPersonCharacteristic)
PersonInImageCvTermCvIdstring_+(PersonInImageWDetailsPersonCharacteristicCvId)
PersonInImageCvTermIdstring_+(PersonInImageWDetailsPersonCharacteristicCvTermId)
PersonInImageCvTermNamelang-alt_+(PersonInImageWDetailsPersonCharacteristicCvTermName)
PersonInImageCvTermRefinedAboutstring_+(PersonInImageWDetailsPersonCharacteristicCvTermRefinedAbout)
PersonInImageDescriptionlang-alt_+(PersonInImageWDetailsPersonDescription)
PersonInImageIdstring_+(PersonInImageWDetailsPersonId)
PersonInImageNamelang-alt_+(PersonInImageWDetailsPersonName)
PlanningRefstruct+--> EntityWithRole Struct
PlanningRefIdentifierstring_+ 
PlanningRefNamelang-alt_+ 
PlanningRefRolestring_+ 
ProductInImagestruct+--> ProductDetails Struct
ProductInImageDescriptionlang-alt_+(ProductInImageProductDescription)
ProductInImageGTINstring_+(ProductInImageProductGTIN)
ProductInImageProductIdstring_+ 
ProductInImageNamelang-alt_+(ProductInImageProductName)
PublicationEventstruct+--> PublicationEvent Struct
PublicationEventDatedate_+ 
PublicationEventIdentifierstring_+ 
PublicationEventNamestring_+ 
Ratingstruct+--> Rating Struct
RatingRegionstruct_+--> LocationDetails Struct +
(RatingRatingRegion)
RatingRegionCitystring_+(RatingRatingRegionCity)
RatingRegionCountryCodestring_+(RatingRatingRegionCountryCode)
RatingRegionCountryNamestring_+(RatingRatingRegionCountryName)
RatingRegionGPSAltituderational_+(RatingRatingRegionGPSAltitude)
RatingRegionGPSLatitudestring_+(RatingRatingRegionGPSLatitude)
RatingRegionGPSLongitudestring_+(RatingRatingRegionGPSLongitude)
RatingRegionIdentifierstring_+(RatingRatingRegionIdentifier)
RatingRegionLocationIdstring_+(RatingRatingRegionLocationId)
RatingRegionLocationNamelang-alt_+(RatingRatingRegionLocationName)
RatingRegionProvinceStatestring_+(RatingRatingRegionProvinceState)
RatingRegionSublocationstring_+(RatingRatingRegionSublocation)
RatingRegionWorldRegionstring_+(RatingRatingRegionWorldRegion)
RatingScaleMaxValuestring_+(RatingRatingScaleMaxValue)
RatingScaleMinValuestring_+(RatingRatingScaleMinValue)
RatingSourceLinkstring_+(RatingRatingSourceLink)
RatingValuestring_+(RatingRatingValue)
RatingValueLogoLinkstring_+(RatingRatingValueLogoLink)
RecDevicestruct--> Device Struct
RecDeviceAttLensDescriptionstring_ 
RecDeviceManufacturerstring_ 
RecDeviceModelNamestring_ 
RecDeviceOwnersDeviceIdstring_ 
RecDeviceSerialNumberstring_ 
RegistryIDstruct+--> RegistryEntryDetails Struct
RegistryEntryRolestring_+(RegistryIdRegEntryRole)
RegistryItemIDstring_+(RegistryIdRegItemId)
RegistryOrganisationIDstring_+(RegistryIdRegOrgId)
ReleaseReadyboolean 
Seasonstruct--> EpisodeOrSeason Struct
SeasonIdentifierstring_ 
SeasonNamestring_ 
SeasonNumberstring_ 
Seriesstruct--> Series Struct
SeriesIdentifierstring_ 
SeriesNamestring_ 
Snapshotstruct+--> LinkedImage Struct +
(called SnapshotLink by the spec)
SnapshotFormatstring_+(SnapshotLinkFormat)
SnapshotHeightPixelsinteger_+(SnapshotLinkHeightPixels)
SnapshotImageRolestring_+(SnapshotLinkImageRole)
SnapshotLinkstring_+(SnapshotLinkLink)
SnapshotLinkQualifierstring_+(SnapshotLinkLinkQualifier)
SnapshotUsedVideoFramestruct_+--> Timecode Struct +
(SnapshotLinkUsedVideoFrame)
SnapshotUsedVideoFrameTimeFormatstring_+(SnapshotLinkUsedVideoFrameTimeFormat) +
'23976Timecode' = 23.976 fps +
'24Timecode' = 24 fps +
'25Timecode' = 25 fps +
'2997DropTimecode' = 29.97 fps (drop) +
'2997NonDropTimecode' = 29.97 fps (non-drop) +
'30Timecode' = 30 fps +
'50Timecode' = 50 fps +
'5994DropTimecode' = 59.94 fps (drop) +
'5994NonDropTimecode' = 59.94 fps (non-drop) +
'60Timecode' = 60 fps
+
SnapshotUsedVideoFrameTimeValuestring_+(SnapshotLinkUsedVideoFrameTimeValue)
SnapshotUsedVideoFrameValueinteger_+(SnapshotLinkUsedVideoFrameValue; only in XMP 2008 spec; an error?)
SnapshotWidthPixelsinteger_+(SnapshotLinkWidthPixels)
StorylineIdentifierstring+ 
StreamReadystring'false' = False +
'true' = True +
'unknown' = Unknown
StylePeriodstring 
SupplyChainSourcestruct+--> Entity Struct
SupplyChainSourceIdentifierstring_+ 
SupplyChainSourceNamelang-alt_+ 
TemporalCoveragestruct--> TemporalCoverage Struct
TemporalCoverageFromdate_(TemporalCoverageTempCoverageFrom)
TemporalCoverageTodate_(TemporalCoverageTempCoverageTo)
Transcriptlang-alt 
TranscriptLinkstruct+--> QualifiedLink Struct
TranscriptLinkLinkstring_+ 
TranscriptLinkLinkQualifierstring_+ 
VideoBitrateinteger 
VideoBitrateModestring'fixed' = Fixed +
'variable' = Variable
VideoDisplayAspectRatiorational 
VideoEncodingProfilestring 
VideoShotTypestruct+--> Entity Struct
VideoShotTypeIdentifierstring_+ 
VideoShotTypeNamelang-alt_+ 
VideoStreamsCountinteger 
VisualColorstring(called VisualColour by the spec) +
'bw-monochrome' = Monochrome +
'colour' = Color
WorkflowTagstruct--> CVTermDetails Struct
WorkflowTagCvIdstring_ 
WorkflowTagCvTermIdstring_ 
WorkflowTagCvTermNamelang-alt_ 
WorkflowTagCvTermRefinedAboutstring_ 
+ +

XMP CVTermDetails Struct

+
+
+ + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
CvIdstring 
CvTermIdstring 
CvTermNamelang-alt 
CvTermRefinedAboutstring 
+ +

XMP ArtworkOrObjectDetails Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
AOCircaDateCreatedstring 
AOContentDescriptionlang-alt 
AOContributionDescriptionlang-alt 
AOCopyrightNoticestring 
AOCreatorstring+ 
AOCreatorIdstring+ 
AOCurrentCopyrightOwnerIdstring 
AOCurrentCopyrightOwnerNamestring 
AOCurrentLicensorIdstring 
AOCurrentLicensorNamestring 
AODateCreateddate 
AOPhysicalDescriptionlang-alt 
AOSourcestring 
AOSourceInvNostring 
AOSourceInvURLstring 
AOStylePeriodstring+ 
AOTitlelang-alt 
+ +

XMP Entity Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
Identifierstring+ 
Namelang-alt 
+ +

XMP EntityWithRole Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Identifierstring+ 
Namelang-alt 
Rolestring+ 
+ +

XMP TextRegion Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
RegionArea--> Area Struct
RegionTextstring 
+ +

XMP Area Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Dreal 
Hreal 
Unitstring 
Wreal 
Xreal 
Yreal 
+ +

XMP QualifiedLink Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
Linkstring 
LinkQualifierstring 
+ +

XMP EEREDetails Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
EncRightsExprstring 
RightsExprEncTypestring 
RightsExprLangIdstring 
+ +

XMP EpisodeOrSeason Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Identifierstring 
Namestring 
Numberstring 
+ +

XMP ImageRegion Struct

+

This structure is new in the IPTC Extension version 1.5 specification. As +well as the fields defined below, this structure may contain any top-level +XMP tags, but since they aren't pre-defined the only way to add these tags +is to write ImageRegion as a structure with these tags as new fields.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Namelang-alt 
RegionBoundaryRegionBoundary--> RegionBoundary Struct
RCtypeEntity+--> Entity Struct
RIdstring 
RRoleEntity+--> Entity Struct
+ +

XMP RegionBoundary Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
RbHreal 
RbRxreal 
RbShapestringcircle = Circle +
polygon = Polygon +
rectangle = Rectangle
RbUnitstringpixel = Pixel +
relative = Relative
RbVerticesBoundaryPoint+--> BoundaryPoint Struct
RbWreal 
RbXreal 
RbYreal 
+ +

XMP BoundaryPoint Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
RbXreal 
RbYreal 
+ +

XMP LEREDetails Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
LinkedRightsExprstring 
RightsExprEncTypestring 
RightsExprLangIdstring 
+ +

XMP LocationDetails Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Citystring 
CountryCodestring 
CountryNamestring 
GPSAltituderational 
GPSLatitudestring 
GPSLongitudestring 
Identifierstring+ 
LocationIdstring+ 
LocationNamelang-alt 
ProvinceStatestring 
Sublocationstring 
WorldRegionstring 
+ +

XMP PersonDetails Struct

+
+
+ + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
PersonCharacteristicCVTermDetails+--> CVTermDetails Struct
PersonDescriptionlang-alt 
PersonIdstring+ 
PersonNamelang-alt 
+ +

XMP ProductDetails Struct

+
+
+ + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
ProductDescriptionlang-alt 
ProductGTINstring 
ProductIdstring 
ProductNamelang-alt 
+ +

XMP PublicationEvent Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Datedate 
Identifierstring 
Namestring 
+ +

XMP Rating Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
RatingRegionLocationDetails+--> LocationDetails Struct
RatingScaleMaxValuestring 
RatingScaleMinValuestring 
RatingSourceLinkstring 
RatingValuestring 
RatingValueLogoLinkstring 
+ +

XMP Device Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
AttLensDescriptionstring 
Manufacturerstring 
ModelNamestring 
OwnersDeviceIdstring 
SerialNumberstring 
+ +

XMP RegistryEntryDetails Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
RegEntryRolestring 
RegItemIdstring 
RegOrgIdstring 
+ +

XMP Series Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
Identifierstring 
Namestring 
+ +

XMP LinkedImage Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
HeightPixelsinteger 
ImageRolestring 
Linkstring 
LinkQualifierstring+ 
UsedVideoFrameTimecode--> Timecode Struct
WidthPixelsinteger 
Formatstring 
+ +

XMP Timecode Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
TimeFormatstring23976Timecode = 23.976 fps +
24Timecode = 24 fps +
25Timecode = 25 fps +
2997DropTimecode = 29.97 fps (drop) +
2997NonDropTimecode = 29.97 fps (non-drop) +
30Timecode = 30 fps +
50Timecode = 50 fps +
5994DropTimecode = 59.94 fps (drop) +
5994NonDropTimecode = 59.94 fps (non-drop) +
60Timecode = 60 fps
TimeValuestring 
Valueinteger(only in XMP 2008 spec; an error?)
+ +

XMP TemporalCoverage Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
TempCoverageFromdate 
TempCoverageTodate 
+ +

XMP LImage Tags

+

Tags written by RED smartphones.

+ +

These tags belong to the ExifTool XMP-LImage family 1 group.

+
+
+ + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
MajorVersionstring 
MinorVersionstring 
RightAlbedostring(Right stereoscopic image)
+ +

XMP Lightroom Tags

+

Adobe Lightroom "lr" namespace tags.

+ +

These tags belong to the ExifTool XMP-lr family 1 group.

+
+
+ + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
HierarchicalSubjectstring+ 
PrivateRTKInfostring 
WeightedFlatSubjectstring+ 
+ +

XMP MediaPro Tags

+

iView MediaPro namespace tags.

+ +

These tags belong to the ExifTool XMP-mediapro family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CatalogSetsstring+ 
Eventstring/(avoided due to conflict with XMP-iptcExt:Event)
Locationstring/(avoided due to conflict with XMP-iptcCore:Location)
Peoplestring+ 
Statusstring 
UserFieldsstring+ 
+ +

XMP panorama Tags

+

Adobe Photoshop Panorama-profile tags.

+ +

These tags belong to the ExifTool XMP-panorama family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Transformationstring 
VirtualFocalLengthreal 
VirtualImageXCenterreal 
VirtualImageYCenterreal 
+ +

XMP pdf Tags

+

Adobe PDF namespace tags. The official XMP specification defines only +Keywords, PDFVersion, Producer and Trapped. The other tags are included +because they have been observed in PDF files, but some are avoided when +writing due to name conflicts with other XMP namespaces.

+ +

These tags belong to the ExifTool XMP-pdf family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Authorstring 
Copyrightstring/ 
CreationDatedate 
Creatorstring/ 
Keywordsstring 
Markedboolean/ 
ModDatedate 
PDFVersionstring 
Producerstring 
Subjectstring/ 
Titlestring/ 
Trappedstring'False' = False +
'True' = True +
'Unknown' = Unknown
+ +

XMP pdfx Tags

+

PDF extension tags. This namespace is used to store application-defined PDF +information, so there are no pre-defined tags. User-defined tags must be +created to enable writing of XMP-pdfx information.

+ +

These tags belong to the ExifTool XMP-pdfx family 1 group.

+
+
+ + + + +
Tag NameWritableValues / Notes
[no tags known]
+ +

XMP photoshop Tags

+

Adobe Photoshop namespace tags.

+ +

These tags belong to the ExifTool XMP-photoshop family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AuthorsPositionstring 
CameraProfilesstruct+--> Camera Struct
CameraProfilesApertureValuereal_+ 
CameraProfilesAuthorstring_+ 
CameraProfilesAutoScaleboolean_+ 
CameraProfilesCameraPrettyNamestring_+ 
CameraProfilesCameraRawProfileboolean_+ 
CameraProfilesFocalLengthreal_+ 
CameraProfilesFocusDistancereal_+ 
CameraProfilesLensstring_+ 
CameraProfilesLensPrettyNamestring_+ 
CameraProfilesMakestring_+ 
CameraProfilesModelstring_+ 
CameraProfilesPerspectiveModelstruct_+--> PerspectiveModel Struct
CameraProfilesPerspectiveModelImageXCenterreal_+ 
CameraProfilesPerspectiveModelImageYCenterreal_+ 
CameraProfilesPerspectiveModelRadialDistortParam1real_+ 
CameraProfilesPerspectiveModelRadialDistortParam2real_+ 
CameraProfilesPerspectiveModelRadialDistortParam3real_+ 
CameraProfilesPerspectiveModelScaleFactorreal_+ 
CameraProfilesPerspectiveModelVersionstring_+ 
CameraProfilesProfileNamestring_+ 
CameraProfilesSensorFormatFactorreal_+ 
CameraProfilesUniqueCameraModelstring_+ 
CaptionWriterstring 
Categorystring 
Citystring 
ColorModeinteger + +
0 = Bitmap +
1 = Grayscale +
2 = Indexed +
3 = RGB
  4 = CMYK +
7 = Multichannel +
8 = Duotone +
9 = Lab
+
Countrystring 
Creditstring 
DateCreateddate 
DocumentAncestorsstring+ 
EmbeddedXMPDigeststring 
Headlinestring 
Historystring 
ICCProfileNamestring(called ICCProfile by the spec)
Instructionsstring 
LegacyIPTCDigeststring 
SidecarForExtensionstring 
Sourcestring 
Statestring 
SupplementalCategoriesstring+ 
TextLayersstruct+--> Layer Struct
TextLayerNamestring_+(TextLayersLayerName)
TextLayerTextstring_+(TextLayersLayerText)
TransmissionReferencestring(Now used as a job identifier)
Urgencyinteger(should be in the range 1-8 to conform with the XMP spec) +
0 = 0 (reserved) +
1 = 1 (most urgent) +
2 = 2 +
3 = 3 +
4 = 4 +
5 = 5 (normal urgency) +
6 = 6 +
7 = 7 +
8 = 8 (least urgent) +
9 = 9 (user-defined priority)
+
+ +

XMP Camera Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
ApertureValuereal 
Authorstring 
AutoScaleboolean 
CameraPrettyNamestring 
CameraRawProfileboolean 
FocalLengthreal 
FocusDistancereal 
Lensstring 
LensPrettyNamestring 
Makestring 
Modelstring 
PerspectiveModelPerspectiveModel--> PerspectiveModel Struct
ProfileNamestring 
SensorFormatFactorreal 
UniqueCameraModelstring 
+ +

XMP PerspectiveModel Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
ImageXCenterreal 
ImageYCenterreal 
RadialDistortParam1real 
RadialDistortParam2real 
RadialDistortParam3real 
ScaleFactorreal 
Versionstring 
+ +

XMP Layer Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
LayerNamestring 
LayerTextstring 
+ +

XMP PixelLive Tags

+

PixelLive namespace tags. These tags are not writable because they are very +uncommon and I haven't been able to locate a reference which gives the +namespace URI.

+ +

These tags belong to the ExifTool XMP-PixelLive family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Authorno 
Commentsno 
Copyrightno 
Dateno 
Genreno 
Titleno 
+ +

XMP pmi Tags

+

PRISM Metadata for Images 3.0 namespace tags. (see +http://www.prismstandard.org/)

+ +

These tags belong to the ExifTool XMP-pmi family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Colorstring/ + +
'bw' = BW +
'color' = Color +
'duotone' = Duotone
  'quadtone' = Quadtone +
'sepia' = Sepia +
'tritone' = Tritone
+
ContactInfostring/ 
DisplayNamestring/ 
DistributorProductIDstring/ 
EventAliasstring/ 
EventEndstring/ 
EventStartstring/ 
EventSubtypestring/ 
EventTypestring/ 
Fieldstring/ 
Framingstring/ 
Locationstring/ 
Makestring/ 
Manufacturerstring/ 
Modelstring/ 
ModelYearstring/ 
ObjectDescriptionstring/ 
ObjectSubtypestring/ 
ObjectTypestring/ 
Orientationstring/'horizontal' = Horizontal +
'vertical' = Vertical
PositionDescriptorstring/ 
ProductIDstring/ 
ProductIDTypestring/ 
Seasonstring/'fall' = Fall +
'spring' = Spring +
'summer' = Summer +
'winter' = Winter
SequenceNamestring/ 
SequenceNumberstring/ 
SequenceTotalNumberstring/ 
Settingstring/ 
ShootIDstring/ 
SlideshowNamestring/ 
SlideshowNumberinteger/ 
SlideshowTotalNumberinteger/ 
Viewpointstring/ 
VisualTechniquestring/ 
+ +

XMP prism Tags

+

Publishing Requirements for Industry Standard Metadata 3.0 namespace +tags. (see +https://www.w3.org/Submission/2020/SUBM-prism-20200910/prism-basic.html/)

+ +

These tags belong to the ExifTool XMP-prism family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AcademicFieldstring/ 
AggregateIssueNumberinteger/ 
AggregationTypestring/+ 
AlternateTitlestruct+--> prismAlternateTitle Struct
AlternateTitleA-langstring/_+ 
AlternateTitleA-platformstring/_+ 
AlternateTitleTextstring/_+ 
BlogTitlestring/ 
BlogURLstring/ 
BookEditionstring/ 
ByteCountinteger/ 
Channelstruct+--> prismChannel Struct
ChannelA-langstring/_+ 
ChannelChannelstring/_+ 
ChannelSubchannel1string/_+ 
ChannelSubchannel2string/_+ 
ChannelSubchannel3string/_+ 
ChannelSubchannel4string/_+ 
ComplianceProfilestring/'three' = Three
ContentTypestring/ 
CopyrightYearstring/ 
CorporateEntitystring/+ 
CoverDatedate/ 
CoverDisplayDatestring/ 
CreationDatedate/ 
DateRecieveddate/ 
Devicestring/ 
Distributorstring/ 
DOIstring/ 
Editionstring/ 
EIssnstring/ 
EndingPagestring/ 
Eventstring/+ 
Genrestring/+ 
HasAlternativestring/+ 
HasCorrectionstruct--> prismHasCorrection Struct
HasCorrectionA-langstring/_ 
HasCorrectionA-platformstring/_ 
HasCorrectionTextstring/_ 
HasTranslationstring/+ 
Industrystring/+ 
IsAlternativeOfstring/+ 
ISBNstring/+ 
IsCorrectionOfstring/+ 
ISSNstring/ 
IssueIdentifierstring/ 
IssueNamestring/ 
IssueTeaserstring/ 
IssueTypestring/ 
IsTranslationOfstring/ 
Keywordstring/+ 
KillDatestruct--> prismKillDate Struct
KillDateA-platformstring/_ 
KillDateDatedate/_ 
Linkstring/+ 
Locationstring/+ 
ModificationDatedate/ 
NationalCatalogNumberstring/ 
Numberstring/ 
Objectstring/+ 
OffSaleDatestruct+--> prismOffSaleDate Struct
OffSaleDateA-platformstring/_+ 
OffSaleDateDatedate/_+ 
OnSaleDatestruct+--> prismOnSaleDate Struct
OnSaleDateA-platformstring/_+ 
OnSaleDateDatedate/_+ 
OnSaleDaystruct+--> prismOnSaleDay Struct
OnSaleDayA-platformstring/_+ 
OnSaleDayDaystring/_+ 
Organizationstring/+ 
OriginPlatformstring/+ +
'broadcast' = Broadcast +
'email' = E-Mail +
'mobile' = Mobile +
'other' = Other +
'print' = Print +
'recordableMedia' = Recordable Media +
'web' = Web
+
PageCountinteger/ 
PageProgressionDirectionstring/'LTR' = Left to Right +
'RTL' = Right to Left
PageRangestring/+ 
Personstring/ 
Platformstring/ 
ProductCodestring/ 
Professionstring/ 
PublicationDatestruct+--> prismPublicationDate Struct
PublicationDateA-platformstring/_+ 
PublicationDateDatedate/_+ 
PublicationDisplayDatestruct+--> prismPublicationDate Struct
PublicationDisplayDateA-platformstring/_+ 
PublicationDisplayDateDatedate/_+ 
PublicationNamestring/ 
PublishingFrequencystring/ 
Ratingstring/ 
SamplePageRangestring/ 
Sectionstring/ 
SellingAgencystring/ 
SeriesNumberinteger/ 
SeriesTitlestring/ 
Sportstring/ 
StartingPagestring/ 
Subsection1string/ 
Subsection2string/ 
Subsection3string/ 
Subsection4string/ 
Subtitlestring/ 
SupplementDisplayIDstring/ 
SupplementStartingPagestring/ 
SupplementTitlestring/ 
Teaserstring/+ 
Tickerstring/+ 
TimePeriodstring/ 
URLstruct+--> prismUrl Struct
URLA-platformstring/_+ 
URLUrlstring/_+ 
UspsNumberstring/ 
VersionIdentifierstring/ 
Volumestring/ 
WordCountinteger/ 
+ +

XMP prismAlternateTitle Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
A-langstring 
A-platformstring 
Textstring 
+ +

XMP prismChannel Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
A-langstring 
Channelstring 
Subchannel1string 
Subchannel2string 
Subchannel3string 
Subchannel4string 
+ +

XMP prismHasCorrection Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
A-langstring 
A-platformstring 
Textstring 
+ +

XMP prismKillDate Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
A-platformstring 
Datedate 
+ +

XMP prismOffSaleDate Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
A-platformstring 
Datedate 
+ +

XMP prismOnSaleDate Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
A-platformstring 
Datedate 
+ +

XMP prismOnSaleDay Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
A-platformstring 
Daystring 
+ +

XMP prismPublicationDate Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
A-platformstring 
Datedate 
+ +

XMP prismUrl Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
A-platformstring 
Urlstring 
+ +

XMP prl Tags

+

PRISM Rights Language 2.1 namespace tags. These tags have been deprecated +since the release of the PRISM Usage Rights 3.0. (see +https://www.w3.org/submissions/2020/SUBM-prism-20200910/prism-image.html)

+ +

These tags belong to the ExifTool XMP-prl family 1 group.

+
+
+ + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Geographystring/+ 
Industrystring/+ 
Usagestring/+ 
+ +

XMP prm Tags

+

PRISM Recipe Metadata 3.0 namespace tags. (see +http://www.prismstandard.org/)

+ +

These tags belong to the ExifTool XMP-prm family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
CookingEquipmentstring/ 
CookingMethodstring/ 
Coursestring/ 
Cuisinestring/ 
DietaryNeedsstring/ 
DishTypestring/ 
Durationstring/ 
IngredientExclusionstring/ 
MainIngredientstring/ 
Mealstring/ 
RecipeEndingPagestring/ 
RecipePageRangestring/ 
RecipeSourcestring/ 
RecipeStartingPagestring/ 
RecipeTitlestring/ 
ServingSizestring/ 
SkillLevelstring/ 
SpecialOccasionstring/ 
Yieldstring/ 
+ +

XMP pur Tags

+

PRISM Usage Rights 3.0 namespace tags. (see +http://www.prismstandard.org/)

+ +

These tags belong to the ExifTool XMP-pur family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AdultContentWarningstring/+ 
Agreementstring/+ 
Copyrightlang-alt/ 
CreditLinestring/+ 
EmbargoDatedate/+ 
ExclusivityEndDatedate/+ 
ExpirationDatedate/+ 
ImageSizeRestrictionstring/ 
OptionEndDatedate/+ 
Permissionsstring/+ 
Restrictionsstring/+ 
ReuseProhibitedboolean/ 
RightsAgentstring/ 
RightsOwnerstring/ 
+ +

XMP rdf Tags

+

Most RDF attributes are handled internally, but the "about" attribute is +treated specially to allow it to be set to a specific value if required.

+ +

These tags belong to the ExifTool XMP-rdf family 1 group.

+
+
+ + + + + + + +
Tag NameWritableValues / Notes
Aboutstring! 
+ +

XMP swf Tags

+

Adobe SWF namespace tags.

+ +

These tags belong to the ExifTool XMP-swf family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
BackgroundAlphainteger(called bgalpha by the spec)
ForwardLockboolean 
MaxStorageinteger 
Typestring/ 
+ +

XMP tiff Tags

+

EXIF namespace for TIFF tags. See +https://web.archive.org/web/20180921145139if_/http://www.cipa.jp:80/std/documents/e/DC-010-2017_E.pdf +for the specification.

+ +

These tags belong to the ExifTool XMP-tiff family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Artiststring 
BitsPerSampleinteger+ 
Compressioninteger--> EXIF Compression Values
Copyrightlang-alt 
DateTimedate 
ImageDescriptionlang-alt 
ImageHeightinteger(called ImageLength by the spec)
ImageWidthinteger 
Makestring 
Modelstring 
NativeDigeststring/ 
Orientationinteger +
1 = Horizontal (normal) +
2 = Mirror horizontal +
3 = Rotate 180 +
4 = Mirror vertical +
5 = Mirror horizontal and rotate 270 CW +
6 = Rotate 90 CW +
7 = Mirror horizontal and rotate 90 CW +
8 = Rotate 270 CW
+
PhotometricInterpretationinteger +
0 = WhiteIsZero +
1 = BlackIsZero +
2 = RGB +
3 = RGB Palette +
4 = Transparency Mask +
5 = CMYK +
6 = YCbCr +
8 = CIELab +
9 = ICCLab +
10 = ITULab +
32803 = Color Filter Array +
32844 = Pixar LogL +
32845 = Pixar LogLuv +
32892 = Sequential Color Filter +
34892 = Linear Raw +
51177 = Depth Map +
52527 = Semantic Mask
+
PlanarConfigurationinteger1 = Chunky +
2 = Planar
PrimaryChromaticitiesrational+ 
ReferenceBlackWhiterational+ 
ResolutionUnitinteger(the value 1 is not standard EXIF) +
1 = None +
2 = inches +
3 = cm
SamplesPerPixelinteger 
Softwarestring 
TransferFunctioninteger+ 
WhitePointrational+ 
XResolutionrational 
YCbCrCoefficientsrational+ 
YCbCrPositioninginteger1 = Centered +
2 = Co-sited
YCbCrSubSamplinginteger+(while technically this is a list-type tag, for compatibility with its EXIF +counterpart it is written and read as a simple string) +
'1 1' = YCbCr4:4:4 (1 1) +
'1 2' = YCbCr4:4:0 (1 2) +
'1 4' = YCbCr4:4:1 (1 4) +
'2 1' = YCbCr4:2:2 (2 1) +
'2 2' = YCbCr4:2:0 (2 2) +
'2 4' = YCbCr4:2:1 (2 4) +
'4 1' = YCbCr4:1:1 (4 1) +
'4 2' = YCbCr4:1:0 (4 2)
+
YResolutionrational 
+ +

XMP x Tags

+

The "x" namespace is used for the "xmpmeta" wrapper, and may contain an +"xmptk" attribute that is extracted as the XMPToolkit tag. When writing, +the XMPToolkit tag is generated automatically by ExifTool unless +specifically set to another value.

+ +

These tags belong to the ExifTool XMP-x family 1 group.

+
+
+ + + + + + + +
Tag NameWritableValues / Notes
XMPToolkitstring!(called xmptk by the spec)
+ +

XMP xmp Tags

+

XMP namespace tags. If the older "xap", "xapBJ", "xapMM" or "xapRights" +namespace prefixes are found, they are translated to the newer "xmp", +"xmpBJ", "xmpMM" and "xmpRights" prefixes for use in family 1 group names.

+ +

These tags belong to the ExifTool XMP-xmp family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Advisorystring+(deprecated)
Authorstring/(non-standard)
BaseURLstring 
CreateDatedate 
CreatorToolstring 
Descriptionlang-alt/(non-standard)
Formatstring/(non-standard)
Identifierstring/+ 
Keywordsstring/(non-standard)
Labelstring 
MetadataDatedate 
ModifyDatedate 
Nicknamestring 
PageInfostruct+--> PageInfo Struct
PageImageFormatstring_+(PageInfoFormat)
PageImageHeightinteger_+(PageInfoHeight)
PageImagestring_+(PageInfoImage)
PageImagePageNumberinteger_+(PageInfoPageNumber)
PageImageWidthinteger_+(PageInfoWidth)
Ratingreal(a value from 0 to 5, or -1 for "rejected")
RatingPercentreal/(non-standard)
Thumbnailsstruct+--> Thumbnail Struct
ThumbnailFormatstring_+(ThumbnailsFormat)
ThumbnailHeightinteger_+(ThumbnailsHeight)
ThumbnailImagestring/_+(ThumbnailsImage)
ThumbnailWidthinteger_+(ThumbnailsWidth)
Titlelang-alt/(non-standard)
+ +

XMP PageInfo Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
PageNumberinteger 
Formatstring 
Heightinteger 
Imagestring 
Widthinteger 
+ +

XMP Thumbnail Struct

+
+
+ + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Formatstring 
Heightinteger 
Imagestring 
Widthinteger 
+ +

XMP xmpBJ Tags

+

XMP Basic Job Ticket namespace tags.

+ +

These tags belong to the ExifTool XMP-xmpBJ family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
JobRefstruct+--> JobRef Struct
JobRefIdstring_+ 
JobRefNamestring_+ 
JobRefUrlstring_+ 
+ +

XMP JobRef Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Idstring 
Namestring 
Urlstring 
+ +

XMP xmpDM Tags

+

XMP Dynamic Media namespace tags.

+ +

These tags belong to the ExifTool XMP-xmpDM family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
AbsPeakAudioFilePathstring 
Albumstring 
AltTapeNamestring 
AltTimecodestruct--> Timecode Struct
AltTimecodeTimeFormatstring_ +
'23976Timecode' = 23.976 fps +
'24Timecode' = 24 fps +
'25Timecode' = 25 fps +
'2997DropTimecode' = 29.97 fps (drop) +
'2997NonDropTimecode' = 29.97 fps (non-drop) +
'30Timecode' = 30 fps +
'50Timecode' = 50 fps +
'5994DropTimecode' = 59.94 fps (drop) +
'5994NonDropTimecode' = 59.94 fps (non-drop) +
'60Timecode' = 60 fps
+
AltTimecodeTimeValuestring_ 
AltTimecodeValueinteger_(only in XMP 2008 spec; an error?)
Artiststring/ 
AudioChannelTypestring +
5.1 = 5.1 +
7.1 = 7.1 +
'16 Channel' = 16 Channel +
'Mono' = Mono +
'Other' = Other +
'Stereo' = Stereo
+
AudioCompressorstring 
AudioModDatedate 
AudioSampleRateinteger 
AudioSampleTypestring +
'16Int' = 16-bit integer +
'24Int' = 24-bit integer +
'32Float' = 32-bit float +
'32Int' = 32-bit integer +
'8Int' = 8-bit integer +
'Compressed' = Compressed +
'Other' = Other +
'Packed' = Packed
+
BeatSpliceParamsstruct--> BeatSpliceStretch Struct
BeatSpliceParamsRiseInDecibelreal_ 
BeatSpliceParamsRiseInTimeDurationstruct_--> Time Struct
BeatSpliceParamsRiseInTimeDurationScalerational_ 
BeatSpliceParamsRiseInTimeDurationValueinteger_ 
BeatSpliceParamsUseFileBeatsMarkerboolean_ 
CameraAnglestring 
CameraLabelstring 
CameraModelstring 
CameraMovestring 
Clientstring 
DMCommentstring(called comment by the spec)
Composerstring 
ContributedMediastruct+--> Media Struct
ContributedMediaDurationstruct_+--> Time Struct
ContributedMediaDurationScalerational_+ 
ContributedMediaDurationValueinteger_+ 
ContributedMediaManagedboolean_+ 
ContributedMediaPathstring_+ 
ContributedMediaStartTimestruct_+--> Time Struct
ContributedMediaStartTimeScalerational_+ 
ContributedMediaStartTimeValueinteger_+ 
ContributedMediaTrackstring_+ 
ContributedMediaWebStatementstring_+ 
Copyrightstring/ 
Directorstring 
DirectorPhotographystring 
DiscNumberstring 
Durationstruct--> Time Struct
DurationScalerational_ 
DurationValueinteger_ 
Engineerstring 
FileDataRaterational 
Genrestring 
Goodboolean 
Instrumentstring 
IntroTimestruct--> Time Struct
IntroTimeScalerational_ 
IntroTimeValueinteger_ 
Keystring + + +
'A' = A +
'A#' = A# +
'B' = B +
'C' = C
  'C#' = C# +
'D' = D +
'D#' = D# +
'E' = E
  'F' = F +
'F#' = F# +
'G' = G +
'G#' = G#
+
LogCommentstring 
Loopboolean 
Lyricsstring 
Markersstruct+--> Marker Struct
MarkersCommentstring_+ 
MarkersCuePointParamsstruct_+--> CuePointParam Struct
MarkersCuePointParamsKeystring_+ 
MarkersCuePointParamsValuestring_+ 
MarkersCuePointTypestring_+ 
MarkersDurationstring_+ 
MarkersLocationstring_+ 
MarkersNamestring_+ 
MarkersProbabilityreal_+ 
MarkersSpeakerstring_+ 
MarkersStartTimestring_+ 
MarkersTargetstring_+ 
MarkersTypestring_+ 
MetadataModDatedate 
NumberOfBeatsreal 
OutCuestruct--> Time Struct
OutCueScalerational_ 
OutCueValueinteger_ 
PartOfCompilationboolean 
ProjectNamestring 
ProjectRefstruct--> ProjectLink Struct
ProjectRefPathstring_ 
ProjectRefTypestring_'audio' = Audio +
'custom' = Custom +
'movie' = Movie +
'still' = Still Image
PullDownstring + +
'SSWWW' = SSWWW +
'SWWWS' = SWWWS +
'SWWWW' = SWWWW +
'WSSWW' = WSSWW +
'WSWWW' = WSWWW
  'WWSSW' = WWSSW +
'WWSWW' = WWSWW +
'WWWSS' = WWWSS +
'WWWSW' = WWWSW +
'WWWWS' = WWWWS
+
RelativePeakAudioFilePathstring 
RelativeTimestampstruct--> Time Struct
RelativeTimestampScalerational_ 
RelativeTimestampValueinteger_ 
ReleaseDatedate 
ResampleParamsstruct--> ResampleStretch Struct
ResampleParamsQualitystring_'High' = High +
'Low' = Low +
'Medium' = Medium
ScaleTypestring'Both' = Both +
'Major' = Major +
'Minor' = Minor +
'Neither' = Neither
Scenestring/ 
ShotDatedate 
ShotDaystring 
ShotLocationstring 
ShotNamestring 
ShotNumberstring 
ShotSizestring 
SpeakerPlacementstring 
StartTimecodestruct--> Timecode Struct
StartTimecodeTimeFormatstring_ +
'23976Timecode' = 23.976 fps +
'24Timecode' = 24 fps +
'25Timecode' = 25 fps +
'2997DropTimecode' = 29.97 fps (drop) +
'2997NonDropTimecode' = 29.97 fps (non-drop) +
'30Timecode' = 30 fps +
'50Timecode' = 50 fps +
'5994DropTimecode' = 59.94 fps (drop) +
'5994NonDropTimecode' = 59.94 fps (non-drop) +
'60Timecode' = 60 fps
+
StartTimecodeTimeValuestring_ 
StartTimecodeValueinteger_(only in XMP 2008 spec; an error?)
StartTimeSampleSizeinteger 
StartTimeScalestring 
StretchModestring'Beat Splice' = Beat Splice +
'Fixed length' = Fixed length +
'Hybrid' = Hybrid +
'Resample' = Resample +
'Time-Scale' = Time-Scale
TakeNumberinteger 
TapeNamestring 
Temporeal 
TimeScaleParamsstruct--> TimeScaleStretch Struct
TimeScaleParamsFrameOverlappingPercentagereal_ 
TimeScaleParamsFrameSizereal_ 
TimeScaleParamsQualitystring_'High' = High +
'Low' = Low +
'Medium' = Medium
TimeSignaturestring + + +
'12/8' = 12/8 +
'2/4' = 2/4 +
'3/4' = 3/4
  '4/4' = 4/4 +
'5/4' = 5/4 +
'6/8' = 6/8
  '7/4' = 7/4 +
'9/8' = 9/8 +
'other' = other
+
TrackNumberinteger 
Tracksstruct+--> Track Struct
TracksFrameRatestring_+ 
TracksMarkersstruct_+--> Marker Struct
TracksMarkersCommentstring_+ 
TracksMarkersCuePointParamsstruct_+--> CuePointParam Struct
TracksMarkersCuePointParamsKeystring_+ 
TracksMarkersCuePointParamsValuestring_+ 
TracksMarkersCuePointTypestring_+ 
TracksMarkersDurationstring_+ 
TracksMarkersLocationstring_+ 
TracksMarkersNamestring_+ 
TracksMarkersProbabilityreal_+ 
TracksMarkersSpeakerstring_+ 
TracksMarkersStartTimestring_+ 
TracksMarkersTargetstring_+ 
TracksMarkersTypestring_+ 
TracksTrackNamestring_+ 
TracksTrackTypestring_+ 
VideoAlphaModestring'none' = None +
'pre-multiplied' = Pre-multiplied +
'straight' = Straight
VideoAlphaPremultipleColorstruct--> Colorant Struct
VideoAlphaPremultipleColorAinteger_ 
VideoAlphaPremultipleColorBinteger_ 
VideoAlphaPremultipleColorBlackreal_ 
VideoAlphaPremultipleColorBlueinteger_ 
VideoAlphaPremultipleColorCyanreal_ 
VideoAlphaPremultipleColorGrayinteger_ 
VideoAlphaPremultipleColorGreeninteger_ 
VideoAlphaPremultipleColorLreal_ 
VideoAlphaPremultipleColorMagentareal_ 
VideoAlphaPremultipleColorModestring_'CMYK' = CMYK +
'LAB' = Lab +
'RGB' = RGB
VideoAlphaPremultipleColorRedinteger_ 
VideoAlphaPremultipleColorSwatchNamestring_ 
VideoAlphaPremultipleColorTintinteger_(not part of 2010 XMP specification)
VideoAlphaPremultipleColorTypestring_ 
VideoAlphaPremultipleColorYellowreal_ 
VideoAlphaUnityIsTransparentboolean 
VideoColorSpacestring'CCIR-601' = CCIR-601 +
'CCIR-709' = CCIR-709 +
'sRGB' = sRGB
VideoCompressorstring 
VideoFieldOrderstring'Lower' = Lower +
'Progressive' = Progressive +
'Upper' = Upper
VideoFrameRatereal 
VideoFrameSizestruct--> Dimensions Struct
VideoFrameSizeHreal_ 
VideoFrameSizeUnitstring_ 
VideoFrameSizeWreal_ 
VideoModDatedate 
VideoPixelAspectRatiorational 
VideoPixelDepthstring +
'16Int' = 16-bit integer +
'24Int' = 24-bit integer +
'32Float' = 32-bit float +
'32Int' = 32-bit integer +
'8Int' = 8-bit integer +
'Other' = Other
+
+ +

XMP BeatSpliceStretch Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
RiseInDecibelreal 
RiseInTimeDurationTime--> Time Struct
UseFileBeatsMarkerboolean 
+ +

XMP Time Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
Scalerational 
Valueinteger 
+ +

XMP Media Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
DurationTime--> Time Struct
Managedboolean 
Pathstring 
StartTimeTime--> Time Struct
Trackstring 
WebStatementstring 
+ +

XMP Marker Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Commentstring 
CuePointParamsCuePointParam+--> CuePointParam Struct
CuePointTypestring 
Durationstring 
Locationstring 
Namestring 
Probabilityreal 
Speakerstring 
StartTimestring 
Targetstring 
Typestring 
+ +

XMP CuePointParam Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
Keystring 
Valuestring 
+ +

XMP ProjectLink Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
Pathstring 
Typestringaudio = Audio +
custom = Custom +
movie = Movie +
still = Still Image
+ +

XMP ResampleStretch Struct

+
+
+ + + + + + + +
Field NameWritableValues / Notes
QualitystringHigh = High +
Low = Low +
Medium = Medium
+ +

XMP TimeScaleStretch Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
FrameOverlappingPercentagereal 
FrameSizereal 
QualitystringHigh = High +
Low = Low +
Medium = Medium
+ +

XMP Track Struct

+
+
+ + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
FrameRatestring 
MarkersMarker+--> Marker Struct
TrackNamestring 
TrackTypestring 
+ +

XMP Colorant Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Ainteger 
Binteger 
Lreal 
Blackreal 
Blueinteger 
Cyanreal 
Grayinteger 
Greeninteger 
Magentareal 
ModestringCMYK = CMYK +
LAB = Lab +
RGB = RGB
Redinteger 
SwatchNamestring 
Tintinteger(not part of 2010 XMP specification)
Typestring 
Yellowreal 
+ +

XMP Dimensions Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Hreal 
Unitstring 
Wreal 
+ +

XMP xmpMM Tags

+

XMP Media Management namespace tags.

+ +

These tags belong to the ExifTool XMP-xmpMM family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
DerivedFromstruct--> ResourceRef Struct
DerivedFromAlternatePathsstring_+ 
DerivedFromDocumentIDstring_ 
DerivedFromFilePathstring_ 
DerivedFromFromPartstring_ 
DerivedFromInstanceIDstring_ 
DerivedFromLastModifyDatedate_ 
DerivedFromLastURLstring_ 
DerivedFromLinkCategorystring_ 
DerivedFromLinkFormstring_ 
DerivedFromManagerstring_ 
DerivedFromManagerVariantstring_ 
DerivedFromManageTostring_ 
DerivedFromManageUIstring_ 
DerivedFromMaskMarkersstring_'All' = All +
'None' = None
DerivedFromOriginalDocumentIDstring_ 
DerivedFromPartMappingstring_ 
DerivedFromPlacedResolutionUnitstring_ 
DerivedFromPlacedXResolutionstring_ 
DerivedFromPlacedYResolutionstring_ 
DerivedFromRenditionClassstring_ 
DerivedFromRenditionParamsstring_ 
DerivedFromToPartstring_ 
DerivedFromVersionIDstring_ 
DocumentIDstring 
Historystruct+--> ResourceEvent Struct
HistoryActionstring_+ 
HistoryChangedstring_+ 
HistoryInstanceIDstring_+ 
HistoryParametersstring_+ 
HistorySoftwareAgentstring_+ 
HistoryWhendate_+ 
Ingredientsstruct+--> ResourceRef Struct
IngredientsAlternatePathsstring_+ 
IngredientsDocumentIDstring_+ 
IngredientsFilePathstring_+ 
IngredientsFromPartstring_+ 
IngredientsInstanceIDstring_+ 
IngredientsLastModifyDatedate_+ 
IngredientsLastURLstring_+ 
IngredientsLinkCategorystring_+ 
IngredientsLinkFormstring_+ 
IngredientsManagerstring_+ 
IngredientsManagerVariantstring_+ 
IngredientsManageTostring_+ 
IngredientsManageUIstring_+ 
IngredientsMaskMarkersstring_+'All' = All +
'None' = None
IngredientsOriginalDocumentIDstring_+ 
IngredientsPartMappingstring_+ 
IngredientsPlacedResolutionUnitstring_+ 
IngredientsPlacedXResolutionstring_+ 
IngredientsPlacedYResolutionstring_+ 
IngredientsRenditionClassstring_+ 
IngredientsRenditionParamsstring_+ 
IngredientsToPartstring_+ 
IngredientsVersionIDstring_+ 
InstanceIDstring 
LastURLstring 
ManagedFromstruct--> ResourceRef Struct
ManagedFromAlternatePathsstring_+ 
ManagedFromDocumentIDstring_ 
ManagedFromFilePathstring_ 
ManagedFromFromPartstring_ 
ManagedFromInstanceIDstring_ 
ManagedFromLastModifyDatedate_ 
ManagedFromLastURLstring_ 
ManagedFromLinkCategorystring_ 
ManagedFromLinkFormstring_ 
ManagedFromManagerstring_ 
ManagedFromManagerVariantstring_ 
ManagedFromManageTostring_ 
ManagedFromManageUIstring_ 
ManagedFromMaskMarkersstring_'All' = All +
'None' = None
ManagedFromOriginalDocumentIDstring_ 
ManagedFromPartMappingstring_ 
ManagedFromPlacedResolutionUnitstring_ 
ManagedFromPlacedXResolutionstring_ 
ManagedFromPlacedYResolutionstring_ 
ManagedFromRenditionClassstring_ 
ManagedFromRenditionParamsstring_ 
ManagedFromToPartstring_ 
ManagedFromVersionIDstring_ 
Managerstring 
ManagerVariantstring 
ManageTostring 
ManageUIstring 
Manifeststruct+--> ManifestItem Struct
ManifestLinkFormstring_+ 
ManifestPlacedResolutionUnitstring_+ 
ManifestPlacedXResolutionreal_+ 
ManifestPlacedYResolutionreal_+ 
ManifestReferencestruct_+--> ResourceRef Struct
ManifestReferenceAlternatePathsstring_+ 
ManifestReferenceDocumentIDstring_+ 
ManifestReferenceFilePathstring_+ 
ManifestReferenceFromPartstring_+ 
ManifestReferenceInstanceIDstring_+ 
ManifestReferenceLastModifyDatedate_+ 
ManifestReferenceLastURLstring_+ 
ManifestReferenceLinkCategorystring_+ 
ManifestReferenceLinkFormstring_+ 
ManifestReferenceManagerstring_+ 
ManifestReferenceManagerVariantstring_+ 
ManifestReferenceManageTostring_+ 
ManifestReferenceManageUIstring_+ 
ManifestReferenceMaskMarkersstring_+'All' = All +
'None' = None
ManifestReferenceOriginalDocumentIDstring_+ 
ManifestReferencePartMappingstring_+ 
ManifestReferencePlacedResolutionUnitstring_+ 
ManifestReferencePlacedXResolutionstring_+ 
ManifestReferencePlacedYResolutionstring_+ 
ManifestReferenceRenditionClassstring_+ 
ManifestReferenceRenditionParamsstring_+ 
ManifestReferenceToPartstring_+ 
ManifestReferenceVersionIDstring_+ 
OriginalDocumentIDstring 
Pantrystruct+--> PantryItem Struct
PantryInstanceIDstring_ 
PreservedFileNamestring 
RenditionClassstring 
RenditionOfstruct--> ResourceRef Struct
RenditionOfAlternatePathsstring_+ 
RenditionOfDocumentIDstring_ 
RenditionOfFilePathstring_ 
RenditionOfFromPartstring_ 
RenditionOfInstanceIDstring_ 
RenditionOfLastModifyDatedate_ 
RenditionOfLastURLstring_ 
RenditionOfLinkCategorystring_ 
RenditionOfLinkFormstring_ 
RenditionOfManagerstring_ 
RenditionOfManagerVariantstring_ 
RenditionOfManageTostring_ 
RenditionOfManageUIstring_ 
RenditionOfMaskMarkersstring_'All' = All +
'None' = None
RenditionOfOriginalDocumentIDstring_ 
RenditionOfPartMappingstring_ 
RenditionOfPlacedResolutionUnitstring_ 
RenditionOfPlacedXResolutionstring_ 
RenditionOfPlacedYResolutionstring_ 
RenditionOfRenditionClassstring_ 
RenditionOfRenditionParamsstring_ 
RenditionOfToPartstring_ 
RenditionOfVersionIDstring_ 
RenditionParamsstring 
SaveIDinteger 
Subjectstring/+(undocumented)
VersionIDstring 
Versionsstruct+--> Version Struct
VersionsCommentsstring_+ 
VersionsEventstruct_+--> ResourceEvent Struct
VersionsEventActionstring_+ 
VersionsEventChangedstring_+ 
VersionsEventInstanceIDstring_+ 
VersionsEventParametersstring_+ 
VersionsEventSoftwareAgentstring_+ 
VersionsEventWhendate_+ 
VersionsModifierstring_+ 
VersionsModifyDatedate_+ 
VersionsVersionstring_+ 
+ +

XMP ResourceRef Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
AlternatePathsstring+ 
DocumentIDstring 
FilePathstring 
FromPartstring 
InstanceIDstring 
LastModifyDatedate 
LastURLstring 
LinkCategorystring 
LinkFormstring 
ManageTostring 
ManageUIstring 
Managerstring 
ManagerVariantstring 
MaskMarkersstringAll = All +
None = None
OriginalDocumentIDstring 
PartMappingstring 
PlacedResolutionUnitstring 
PlacedXResolutionstring 
PlacedYResolutionstring 
RenditionClassstring 
RenditionParamsstring 
ToPartstring 
VersionIDstring 
+ +

XMP ResourceEvent Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Actionstring 
Changedstring 
InstanceIDstring 
Parametersstring 
SoftwareAgentstring 
Whendate 
+ +

XMP ManifestItem Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
LinkFormstring 
PlacedResolutionUnitstring 
PlacedXResolutionreal 
PlacedYResolutionreal 
ReferenceResourceRef--> ResourceRef Struct
+ +

XMP PantryItem Struct

+

This structure must have an InstanceID field, but may also contain any other +XMP properties.

+
+
+ + + + + + + +
Field NameWritableValues / Notes
InstanceIDstring 
+ +

XMP Version Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
Commentsstring 
EventResourceEvent--> ResourceEvent Struct
Modifierstring 
ModifyDatedate 
Versionstring 
+ +

XMP xmpNote Tags

+

XMP Note namespace tags.

+ +

These tags belong to the ExifTool XMP-xmpNote family 1 group.

+
+
+ + + + + + + +
Tag NameWritableValues / Notes
HasExtendedXMPstring*(this tag is protected so it is not writable directly. Instead, it is set +automatically to the GUID of the extended XMP when writing extended XMP to a +JPEG image)
+ +

XMP xmpPLUS Tags

+

XMP Picture Licensing Universal System (PLUS) tags as written by some older +Adobe applications. See PLUS XMP Tags +for the current PLUS tags.

+ +

These tags belong to the ExifTool XMP-xmpPLUS family 1 group.

+
+
+ + + + + + + + + + + +
Tag NameWritableValues / Notes
CreditLineReqboolean/ 
ReuseAllowedboolean/ 
+ +

XMP xmpRights Tags

+

XMP Rights Management namespace tags.

+ +

These tags belong to the ExifTool XMP-xmpRights family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Certificatestring 
Markedboolean 
Ownerstring+ 
UsageTermslang-alt 
WebStatementstring 
+ +

XMP xmpTPg Tags

+

XMP Paged-Text namespace tags.

+ +

These tags belong to the ExifTool XMP-xmpTPg family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Colorantsstruct+--> Colorant Struct
ColorantAinteger_+(ColorantsA)
ColorantBinteger_+(ColorantsB)
ColorantBlackreal_+(ColorantsBlack)
ColorantBlueinteger_+(ColorantsBlue)
ColorantCyanreal_+(ColorantsCyan)
ColorantGrayinteger_+(ColorantsGray)
ColorantGreeninteger_+(ColorantsGreen)
ColorantLreal_+(ColorantsL)
ColorantMagentareal_+(ColorantsMagenta)
ColorantModestring_+(ColorantsMode) +
'CMYK' = CMYK +
'LAB' = Lab +
'RGB' = RGB
ColorantRedinteger_+(ColorantsRed)
ColorantSwatchNamestring_+(ColorantsSwatchName)
ColorantTintinteger_+(ColorantsTint; not part of 2010 XMP specification)
ColorantTypestring_+(ColorantsType)
ColorantYellowreal_+(ColorantsYellow)
Fontsstruct+--> Font Struct
ChildFontFilesstring_+(FontsChildFontFiles)
FontCompositeboolean_+(FontsComposite)
FontFacestring_+(FontsFontFace)
FontFamilystring_+(FontsFontFamily)
FontFileNamestring_+(FontsFontFileName)
FontNamestring_+(FontsFontName)
FontTypestring_+(FontsFontType)
FontVersionstring_+(FontsVersionString)
HasVisibleOverprintboolean 
HasVisibleTransparencyboolean 
MaxPageSizestruct--> Dimensions Struct
MaxPageSizeHreal_ 
MaxPageSizeUnitstring_ 
MaxPageSizeWreal_ 
NPagesinteger 
PlateNamesstring+ 
SwatchGroupsstruct+--> SwatchGroup Struct
SwatchGroupsColorantsstruct_+--> Colorant Struct
SwatchColorantAinteger_+(SwatchGroupsColorantsA)
SwatchColorantBinteger_+(SwatchGroupsColorantsB)
SwatchColorantBlackreal_+(SwatchGroupsColorantsBlack)
SwatchColorantBlueinteger_+(SwatchGroupsColorantsBlue)
SwatchColorantCyanreal_+(SwatchGroupsColorantsCyan)
SwatchColorantGrayinteger_+(SwatchGroupsColorantsGray)
SwatchColorantGreeninteger_+(SwatchGroupsColorantsGreen)
SwatchColorantLreal_+(SwatchGroupsColorantsL)
SwatchColorantMagentareal_+(SwatchGroupsColorantsMagenta)
SwatchColorantModestring_+(SwatchGroupsColorantsMode) +
'CMYK' = CMYK +
'LAB' = Lab +
'RGB' = RGB
SwatchColorantRedinteger_+(SwatchGroupsColorantsRed)
SwatchColorantSwatchNamestring_+(SwatchGroupsColorantsSwatchName)
SwatchColorantTintinteger_+(SwatchGroupsColorantsTint; not part of 2010 XMP specification)
SwatchColorantTypestring_+(SwatchGroupsColorantsType)
SwatchColorantYellowreal_+(SwatchGroupsColorantsYellow)
SwatchGroupNamestring_+(SwatchGroupsGroupName)
SwatchGroupTypeinteger_+(SwatchGroupsGroupType)
+ +

XMP Font Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
ChildFontFilesstring+ 
Compositeboolean 
FontFacestring 
FontFamilystring 
FontFileNamestring 
FontNamestring 
FontTypestring 
VersionStringstring 
+ +

XMP SwatchGroup Struct

+
+
+ + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
ColorantsColorant+--> Colorant Struct
GroupNamestring 
GroupTypeinteger 
+ +

XMP XML Tags

+
+
+ + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'dc'dc---> XMP dc Tags
'lastUpdate'LastUpdateno 
+ +

XMP SVG Tags

+

SVG (Scalable Vector Graphics) image tags. By default, only the top-level +SVG and Metadata tags are extracted from these images, but all graphics tags +may be extracted by setting the Unknown option to 2 (-U on the command +line). The SVG tags are not part of XMP as such, but are included with the +XMP module for convenience. (see http://www.w3.org/TR/SVG11/)

+ +

These tags belong to the ExifTool XMP-svg family 1 group.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag IDTag NameWritableValues / Notes
'height'ImageHeightno 
'id'IDno 
'metadataId'MetadataIDno 
'version'SVGVersionno 
'width'ImageWidthno 
+ +

XMP CopyrightOwner Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
CopyrightOwnerIDstring 
CopyrightOwnerNamestring 
+ +

XMP EndUser Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
EndUserIDstring 
EndUserNamestring 
+ +

XMP ImageCreator Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
ImageCreatorIDstring 
ImageCreatorNamestring 
+ +

XMP ImageSupplier Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
ImageSupplierIDstring 
ImageSupplierNamestring 
+ +

XMP Licensee Struct

+
+
+ + + + + + + + + + + +
Field NameWritableValues / Notes
LicenseeIDstring 
LicenseeNamestring 
+ +

XMP Licensor Struct

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameWritableValues / Notes
LicensorCitystring 
LicensorCountrystring 
LicensorEmailstring 
LicensorExtendedAddressstring 
LicensorIDstring 
LicensorNamestring 
LicensorPostalCodestring 
LicensorRegionstring 
LicensorStreetAddressstring 
LicensorTelephone1string 
LicensorTelephone2string 
LicensorTelephoneType1stringcell = Cell +
fax = FAX +
home = Home +
pager = Pager +
work = Work
LicensorTelephoneType2stringcell = Cell +
fax = FAX +
home = Home +
pager = Pager +
work = Work
LicensorURLstring 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Sep 19, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/ZIP.html b/ExifTool/html/TagNames/ZIP.html new file mode 100644 index 0000000..1796ae8 --- /dev/null +++ b/ExifTool/html/TagNames/ZIP.html @@ -0,0 +1,245 @@ + + + + +ZIP Tags + + + +

ZIP Tags

+

The following tags are extracted from ZIP archives. ExifTool also extracts +additional meta information from compressed documents inside some ZIP-based +files such Office Open XML (DOCX, PPTX and XLSX), Open Document (ODB, ODC, +ODF, ODG, ODI, ODP, ODS and ODT), iWork (KEY, PAGES, NUMBERS), Capture One +Enhanced Image Package (EIP), Adobe InDesign Markup Language (IDML), +Electronic Publication (EPUB), and Sketch design files (SKETCH). The +ExifTool family 3 groups may be used to organize ZIP tags by embedded +document number (ie. the exiftool -g3 option).

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index2Tag NameWritableValues / Notes
2ZipRequiredVersionno 
3ZipBitFlagno 
4ZipCompressionno +
0 = None +
1 = Shrunk +
2 = Reduced with compression factor 1 +
3 = Reduced with compression factor 2 +
4 = Reduced with compression factor 3 +
5 = Reduced with compression factor 4 +
6 = Imploded +
7 = Tokenized +
8 = Deflated +
9 = Enhanced Deflate using Deflate64(tm) +
10 = Imploded (old IBM TERSE) +
12 = BZIP2 +
14 = LZMA (EFS) +
18 = IBM TERSE (new) +
19 = IBM LZ77 z Architecture (PFS) +
96 = JPEG recompressed +
97 = WavPack compressed +
98 = PPMd version I, Rev 1
+
5ZipModifyDateno 
7ZipCRCno 
9ZipCompressedSizeno 
11ZipUncompressedSizeno 
15ZipFileNameno 
'_com'ZipFileCommentno 
+ +

ZIP GZIP Tags

+

These tags are extracted from GZIP (GNU ZIP) archives, but currently only +for the first file in the archive.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
2Compressionno8 = Deflated
3FlagsnoBit 0 = Text +
Bit 1 = CRC16 +
Bit 2 = ExtraFields +
Bit 3 = FileName +
Bit 4 = Comment
4ModifyDateno 
8ExtraFlagsno0 = (none) +
2 = Maximum Compression +
4 = Fastest Algorithm
9OperatingSystemno +
0 = FAT filesystem (MS-DOS, OS/2, NT/Win32) +
1 = Amiga +
2 = VMS (or OpenVMS) +
3 = Unix +
4 = VM/CMS +
5 = Atari TOS +
6 = HPFS filesystem (OS/2, NT) +
7 = Macintosh +
8 = Z-System +
9 = CP/M +
10 = TOPS-20 +
11 = NTFS filesystem (NT) +
12 = QDOS +
13 = Acorn RISCOS +
255 = unknown
+
10ArchivedFileNameno 
11Commentno 
+ +

ZIP RAR Tags

+

These tags are extracted from RAR archive files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
0CompressedSizeno 
4UncompressedSizeno 
8OperatingSystemno0 = MS-DOS +
1 = OS/2 +
2 = Win32 +
3 = Unix
13ModifyDateno 
18PackingMethodno + +
0x30 = Stored +
0x31 = Fastest +
0x32 = Fast
  0x33 = Normal +
0x34 = Good Compression +
0x35 = Best Compression
+
25ArchivedFileNameno 
+ +

ZIP RAR5 Tags

+

These tags are extracted from RAR v5 and 7z archive files.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
ArchivedFileNameno 
CompressedSizeno 
FileVersionno 
ModifyDateno 
OperatingSystemno0 = Win32 +
1 = Unix
UncompressedSizeno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Jun 8, 2023 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/ZISRAW.html b/ExifTool/html/TagNames/ZISRAW.html new file mode 100644 index 0000000..79eefe6 --- /dev/null +++ b/ExifTool/html/TagNames/ZISRAW.html @@ -0,0 +1,41 @@ + + + + +ZISRAW Tags + + + +

ZISRAW Tags

+

As well as the header information listed below, ExifTool also extracts the +top-level XML-based metadata from Zeiss Integrated Software RAW (ZISRAW) CZI +files.

+
+
+ + + + + + + + + + + + + + + + + + +
Index1Tag NameWritableValues / Notes
32ZISRAWVersionno 
48PrimaryFileGUIDno 
64FileGUIDno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Aug 10, 2020 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/iWork.html b/ExifTool/html/TagNames/iWork.html new file mode 100644 index 0000000..af4d31b --- /dev/null +++ b/ExifTool/html/TagNames/iWork.html @@ -0,0 +1,50 @@ + + + + +iWork Tags + + + +

iWork Tags

+

The Apple iWork '09 file format is a ZIP archive containing XML files +similar to the Office Open XML (OOXML) format. Metadata tags in iWork +files are extracted even if they don't appear below.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tag NameWritableValues / Notes
Authorno 
Commentno 
Copyrightno 
Keywordsno 
Projectsno+ 
Titleno 
+ +
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Last revised Nov 11, 2009 +

<-- ExifTool Tag Names

+ + diff --git a/ExifTool/html/TagNames/index.html b/ExifTool/html/TagNames/index.html new file mode 100644 index 0000000..2e8b27e --- /dev/null +++ b/ExifTool/html/TagNames/index.html @@ -0,0 +1,251 @@ + + + + +ExifTool Tag Names + + + +

ExifTool Tag Names

+

+The tables listed below give the names of all tags recognized by ExifTool. +They contain a total of 26993 tags, with 16973 unique tag names. +

+
+
+ + +
Tag Table Index
+JPEG
+EXIF
+IPTC
+XMP
+GPS
+GeoTiff
+PLUS
+ICC_Profile
+PrintIM
+Photoshop
+Apple
+NikonSettings
+Canon
+CanonCustom
+CanonVRD
+Casio
+DJI
+FLIR
+FujiFilm
+GE
+HP
+JVC
+Kodak
+Leaf
+Minolta
+Motorola
+Nikon
+NikonCustom
+NikonCapture
+Nintendo
+Olympus
+Panasonic
+Pentax
+PhaseOne
+Reconyx
+Sanyo
+Samsung
+Ricoh
+Sigma
+Sony
+SonyIDC
+Unknown
+DNG
+CanonRaw
+KyoceraRaw
+MinoltaRaw
+PanasonicRaw
+SigmaRaw
+Lytro
+JFIF
+FlashPix
+MPF
+InfiRay
+Stim
+Scalado
+InfiRay
+GoPro
+InfiRay
+Qualcomm
+InfiRay
+Jpeg2000
+JSON
+CBOR
+PLIST
+APP12
+AFCP
+DarwinCore
+FotoStation
+PhotoMechanic
+Microsoft
+GIMP
+MIE
+GIF
+BMP
+BPG
+WPG
+ICO
+PICT
+PNG
+MNG
+FLIF
+DjVu
+DPX
+OpenEXR
+ZISRAW
+MRC
+LIF
+MIFF
+PCX
+PGF
+PSP
+PhotoCD
+Radiance
+PFM
+PDF
+PostScript
+ID3
+ITC
+QuickTime
+RIFF
+FLAC
+Parrot
+Ogg
+Vorbis
+Opus
+Theora
+APE
+Audible
+MPC
+MPEG
+M2TS
+H264
+MISB
+Matroska
+MOI
+MXF
+DV
+Flash
+Real
+Red
+AIFF
+ASF
+WTV
+DICOM
+FITS
+HTML
+Palm
+Torrent
+EXE
+LNK
+Font
+VCard
+Text
+RSRC
+Rawzor
+ZIP
+RTF
+OOXML
+iWork
+ISO
+MacOS
+Extra
+Composite
+Shortcuts
+MWG +
+

+Tag ID, Index# or Sequence is given in the first column of each +table. A Tag ID is the computer-readable equivalent of a tag name, and +is the identifier that is actually stored in the file. Index# refers to +the offset of a value when found at a fixed position within a data block +(# is the multiplier for calculating a byte offset: 1, 2, 4 or +8). These offsets may have a decimal part which is used only to +differentiate tags with values stored at the same position. (Note that +writable tags within binary data blocks are not individually deletable, +and the usual alternative is to set them to a value of zero.) Sequence +gives the order of values for a serial data stream.

+ +

A Tag Name is the handle by which the information is accessed in +ExifTool. In some instances, more than one name may correspond to a single +tag ID. In these cases, the actual name used depends on the context in +which the information is found. Valid characters in a tag name are A-Z, +a-z, 0-9, hyphen (-) and underline (_). Case is not significant. A +question mark (?) after a tag name indicates that the information is +either not understood, not verified, or not very useful -- these tags are +not extracted by ExifTool unless the Unknown (-u) option is enabled. Be +aware that some tag names are different than the descriptions printed out by +default when extracting information with exiftool. To see the tag names +instead of the descriptions, use exiftool -s.

+ +

The Writable column indicates whether the tag is writable by ExifTool. +Anything but a no in this column means the tag is writable. A yes +indicates writable information that is either unformatted or written using +the existing format. Other expressions give details about the format of the +stored value, and vary depending on the general type of information. The +format name may be followed by a number in square brackets to indicate the +number of values written, or the number of characters in a fixed-length +string (including a null terminator which is added if required).

+ +

A plus sign (+) after an entry in the Writable column indicates a +List tag which supports multiple values and allows individual values to +be added and deleted. A slash (/) indicates a tag that ExifTool will +Avoid when writing. These will be edited but not created if another +same-named tag may be created instead. To create these tags, the group +should be specified. A tilde (~) indicates a tag this is writable only +when the print conversion is disabled (by setting PrintConv to 0, using the +-n option, or suffixing the tag name with a # character). An exclamation +point (!) indicates a tag that is considered Unsafe to write under +normal circumstances. These tags are not written unless specified +explicitly (ie. not when wildcards or "all" are used), and care should be +taken when editing them manually since they may affect the way an image is +rendered. An asterisk (*) indicates a Protected tag which is not +writable directly, but is written automatically by ExifTool (often when a +corresponding Composite or +Extra tag is written). A colon +(:) indicates a Mandatory tag which may be added automatically when +writing. Normally MakerNotes tags may not be deleted individually, but a +caret (^) indicates a Deletable MakerNotes tag.

+ +

The HTML version of these tables also lists possible Values for +discrete-valued tags, as well as Notes for some tags. The Values are +listed with the computer-readable values on the left of the equals sign +(=), and the human-readable values on the right. The human-readable +values are used by default when reading and writing, but the +computer-readable values may be accessed by disabling the value conversion +with the -n option on the command line, by setting the PrintConv option to 0 +in the API, or or on a per-tag basis by adding a hash (#) after the tag +name.

+ +

Note: If you are familiar with common meta-information tag names, you may +find that some ExifTool tag names are different than expected. The usual +reason for this is to make the tag names more consistent across different +types of meta information. To determine a tag name, either consult this +documentation or run exiftool -s on a file containing the information in +question.

+ +

(This documentation is the result of decades of research, testing and +reverse engineering, and is the most complete metadata tag list available +anywhere on the internet. It is provided not only for ExifTool users, but +more importantly as a public service to help augment the collective +knowledge, and is often used as a primary source of information in the +development of other metadata software. Please help keep this documentation +as accurate and complete as possible, and feed any new discoveries back to +ExifTool. A big thanks to everyone who has helped with this so far!) +

+
+(This document generated automatically by Image::ExifTool::BuildTagLookup) +
Created Feb 15, 2005 +
Last revised Sep 19, 2023 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/TagNames/style.css b/ExifTool/html/TagNames/style.css new file mode 100644 index 0000000..0a0a5fb --- /dev/null +++ b/ExifTool/html/TagNames/style.css @@ -0,0 +1,31 @@ +body { color: black; background: white; margin-top: 0; + font-family: helvetica; font-size: .9em } +code { color: #840; font-family: monospace; white-space: nowrap } + +table.frame { background: #fb7 } /* frame color */ +table.frame td { padding: 0 } /* frame padding */ +table.frame tr { background: white }/* main cell color */ +table.inner { background: white } /* table cell borders */ +table.inner th { padding: 2px } /* header padding */ +table.inner td { padding: 2px } /* cell padding */ +table.sep td { padding: 0 2px; /* separate table values */ + font-size: smaller } +table.cols tr { background: transparent } +table.cols td { padding: 0; /* PrintConvColumns table */ + font-size: smaller } +table tr { vertical-align: top } /* universal text alignment */ +table tr.b { background: #feb } /* alternate cell color */ +table td.b { background: #feb } /* alternate cell color */ +table tr.h { background: #fb7 } /* table header */ +table td.c { text-align: center } /* centered text cells */ +table td.r { text-align: right } /* numerical tag ID's */ +table td.n { white-space: nowrap } /* to avoid wrapping some tag names */ + +h2 { margin: .6em 0 .6em 0 }/* table headings */ +h2.top { margin: .3em 0 .6em 0 }/* top heading */ +p { margin: 1em 1em } /* paragraph text */ +p.lf { margin: 1em 0 } /* text footer */ + +span.n { color: #666 } /* notes text color */ +span.s { font-size: smaller } /* small values/notes text */ +span.l { font-size: larger } /* index table heading */ diff --git a/ExifTool/html/ancient_history.html b/ExifTool/html/ancient_history.html new file mode 100644 index 0000000..01f1df9 --- /dev/null +++ b/ExifTool/html/ancient_history.html @@ -0,0 +1,11545 @@ + + + + ExifTool Ancient History + + + + + +

ExifTool Ancient History

+ +

Note: This page gives the history of older exiftool versions. See +history.html for the recent history.

+ +Oct. 19, 2022 - Version 12.49 +
    +
  • Added read support for Windows ICO and CUR files +
  • Added ability to shift EXIF OffsetTime tags (eg. "-OffsetTime+=+02:00") +
  • Added a few new XMP tags and print conversions +
  • Added a print conversion for Photoshop:PrintFlags +
  • Added a new SonyModelID (thanks LibRaw) +
  • Added a few new Canon RF LensType values (thanks Norbert Wasser) +
  • Added a new Canon LensType +
  • Added a new Nikon LensID +
  • Decode 'riff' metadata blocks in FLAC audio files +
  • Decode RIFF 'acid' chunk written by Acidizer +
  • Enhanced the -d option %f sub-second date/time format code to allow the + decimal point to be dropped (eg. "%-3f") +
  • Patched another Sigma Photo Pro incompatibility when writing X3F images + (Sigma will also fix this at their end in the next SPP release) +
+ +Oct. 13, 2022 - Version 12.48 +
    +
  • Added support for new XMP-photoshop:CameraProfiles structure +
  • Added a new SonyModelID and Sony LensType (thanks Jos Roost) +
  • Decode more tags for the Sony ILME-FX30 (thanks Jos Roost) +
  • Decode a couple of new Panasonic tags, and improved decoding of others +
  • Decode STANAG-4609 MISB timed metadata from M2TS videos +
  • Decode a new Nikon tag (thanks Warren Hatch) +
  • Decode a couple of new FujiFilm tags (thanks Honza Pokorny) +
  • Improved round-off errors when writing QuickTime:MatrixStructure via the + Composite:Rotation tag +
  • Increased Verbose level of "nothing changed" message added in 12.45 +
  • Removed "Z" (Zulu) designation from some of the MS-DOC date/time tags + because they most certainly are in local time as written by Word 2011 for + Mac (while some other MS-DOC and FlashPix date/time tags extracted without a + "Z" are actually in Zulu time -- a bit of a mess really) +
  • Prevent dynamically-generated Unknown tags from being extracted when the + -validate option is used without -u +
  • Patched to better handle irregular timestamps in streaming GPS of NextBase + dashcam videos +
  • Fixed incompatibility with Sigma Photo Pro which could result in Sigma Photo + Pro corrupting an ExifTool-edited X3F image (the section length in the + footer needed to include the padding to a 4-byte boundary, thanks Sigma + engineer Yuki Miyahara) +
  • Fixed problem which could prevent ExifTool from reading all GPS points from + some INNOV M2TS videos +
+ +Oct. 3, 2022 - Version 12.47 +
    +
  • Added a new Nikon LensID (thanks David Püschel) +
  • Fixed bug introduced in 12.46 which resulted in a runtime error when -j was + combined with -b +
+ +Oct. 1, 2022 - Version 12.46 - "Write WEBP" +
    +
  • Added WEBP write support +
  • Added the abilty to write Panasonic GH6 RW2 files +
  • Added a new Canon LensType +
  • Added a number of new Sigma LensType values (thanks LibRaw) +
  • Added support for BigTIFF format code 16 in Apple ProRaw maker notes +
  • Added config_files/frameCount.config to extract MP4 FrameCount +
  • Added a MIE OriginalImageSize tag +
  • Added some extra -validate checks for RIFF-based file formats +
  • Extract FrameRate from MP4 tmcd box +
  • Decode a new Apple tag (thanks Neal Krawetz) +
  • Decode more information from Nikon Z-camera videos +
  • Decode streaming GPS from Garmin DriveAssist 51 MP4 videos +
  • Changed the names of two FujiFilm FirmwareVersion tags +
  • Enhanced WEBP FileType identification to denote Extended WEBP +
  • Preserve fractional seconds when extracting Samsung TimeStamp times +
  • Patched to avoid reporting Photoshop:ProgressiveScans unless PhotoshopFormat + is Progressive +
  • Patched to test QuickTime UserData tags with default 0x0000 language code to + see if they contain UTF8 characters, and if so assume UTF8 encoding and + ignore the CharsetQuickTime setting +
  • Patched to avoid potential deep recursion when reading/writing malicious CRW + images +
  • Fixed "Invalid ID3 frame size" problem when reading ID3v2 with an extended + header (very uncommon, but Audacity uses this) +
  • Fixed typo in the name of a new DNG 1.6 tag +
  • Fixed some verbose warnings when reading Nikon Z-camera NEF files +
  • Fixed decoding of a couple of Nikon Z9 tags for newer firmware versions + (thanks Warren Hatch) +
+ +Sept. 16, 2022 - Version 12.45 +
    +
  • Added new IPTC Video Metadata version 1.3 tags +
  • Added a couple of new Canon lenses (thanks Norbert Wasser) +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added known Unknown value for IPTC ChromaticityColorant (thanks Herb) +
  • Added new Nikon WhiteBalanceFineTune tag (thanks Miloš Komarčević) +
  • Extract the raw thermal data from all frames of a SEQ file when -ee2 is used +
  • Decode individual tags in QuickTime ColorRepresentation +
  • Decode a new Matroska tag +
  • Improved verbose "nothing changed" messages when writing +
  • Patched -ee option to extract metadata after the first Cluster in MKV videos + (previously only -U and -v did this) +
  • Patched to differentiate Java bytecode .class files from Mach-O fat binaries +
  • Patched to avoid "Use of uninitialized value" warning when deleting GPS + coordinates via the newly writable Composite tags +
  • Patched to avoid duplicating raw data when writing Sony ARW images where the + raw data is double-referenced as both strips and tiles (affects ARW images + from some newer models like the ILCE-1 when SonyRawFileType is "Lossless + Compressed RAW 2") +
  • Patched to avoid "fixing" the order of IFD entries in TIFF-based RAW files + to improve compatibility with some RAW viewers +
  • Minor change to Composite FileNumber to remove "-" when -n is used +
  • Fixed problem extracting some timed metadata when "-api ignoretags=all" was + used with "-api requesttags" to request the specific information +
  • Fixed -validate feature to avoid incorrectly warning about non-capitalized + boolean values in XMP +
+ +July 21, 2022 - Version 12.44 +
    +
  • Added a few new Sony lenses (thanks Jos Roost) +
  • Decode Accelerometer and Gyroscope data from ARCore videos +
  • Decode a couple of new Motorola tags (thanks Neal Krawetz) +
  • Decode FujiFilm FirmwareVersion (thanks Justin Arkinson) +
  • Decode MetaType for timed metadata in videos +
  • Decode a number of new Nikon Z tags (thanks Warren Hatch) +
  • Extract more types of embedded images from FlashPix-format files +
  • Made Composite GPSLatitude and GPSLongitude writable for setting GPS + coordinates and reference directions with one assignment +
  • Fixed bug introduced in 12.39 which broke extraction of timed GPS from some + INNOVV videos +
  • Fixed bug introduced in 12.43 which broke extraction of timed GPSDateTime + from Insta360 videos +
+ +July 6, 2022 - Version 12.43 +
    +
  • Added the ability to geotag from Google Takeout JSON files +
  • Added a few new Canon RF LensType values and a couple of new CanonModelID's + (thanks Norbert Wasser) +
  • Added new values to a couple of FujiFilm tags (thanks Greybeard) +
  • Added a new Nikon LensID (thanks BertJan Bakker) +
  • Recognize Autodesk Revit files (but don't yet support reading metadata) +
  • Decode DriveSerialNumber from LNK files (github #145) +
  • Decode Apple FocusDistanceRange (thanks Neal Krawetz) +
  • Made a number of Sony SR2SubIFD tags writable +
  • Tolerate dashes instead of colons as date separators in -geotag CSV files +
  • Patched to read new format accelerometer data from Insta360 files +
  • Patched to avoid outputting some Unknown tags when the -validate option is + used after a previously -execute'd command used the -u option +
  • Fixed names of Canon G9 WB levels tags (changed from GRGB to GRBG) (thanks + Christoph) +
  • Fixed typo in new Olympus AISubjectTrackingMode value +
  • Fixed "use of undefined value" warning when reading DJI metadata +
  • API Changes: + +
+ +June 1, 2022 - Version 12.42 (production release) +
    +
  • Added support for reading maker notes from Panasonic DC-GH6 videos +
  • Added conversion for Samsung MCCData +
  • Added a new Nikon LensID (thanks Chris) +
  • Added a few new Canon LensType values +
  • Added a couple of new Olympus StackedImage values (thanks Eberhard) +
  • Added a few new values for some Nikon Settings tags (thanks Warren Hatch) +
  • Added a "lang:" element to the -json output for alternate language tags when + -D, -H or -t is used +
  • Update DNG writer to not issue an error when writing DNG 1.6 files +
  • Decode information from DJI "ae_dbg_info" maker notes +
  • Decode Olympus AISubjectTrackingMode +
  • Changed ExifTool FileSize print conversion to use kB/MB/GB units instead of + KiB/MiB/GiB +
  • Changed "is not shiftable" warning to appear in -v (instead of just -v3) + output +
  • Patched to allow PDF Encrypt object to be "null" +
  • Fixed bug reading ICC_Profile 'meta' tags +
+ +Apr. 7, 2022 - Version 12.41 +
    +
  • Added support for "OM SYSTEM" maker notes +
  • Added 2 new Sony LensType values (thanks Jos Roost) +
  • Added some new Canon lenses (thanks LibRaw) +
  • Added a new Nikon LensID (thanks Bert Ligtvoet) +
  • Added a new Canon ContinuousDrive value (thanks Wolfgang Gülcker) +
  • Enhanced -v0 option to also print new file name when renaming, moving or + copying a file +
  • Updated xmp2exif.args and exif2xmp.args helper files to reflect the IPTC + Photometadata Mapping Guidelines version 2022.1 +
  • Made "Invalid Xxx data" a minor warning for MakerNote data +
  • Patched to allow writing of MP4 videos which have other tracks with a + missing sample description entry +
  • Patched MacOS version to specify directory for external utilities (setfile, + xattr, stat, mdls and osascript from /usr/bin, and tag from /usr/local/bin) +
  • Fixed long-standing problem where Windows version could behave differently + for -if conditions containing undefined tags +
  • Fixed problem where -W+! combined with -j or -X produced invalid JSON or XML + when processing multiple files +
  • Fixed potential "uninitialized value $time in division" runtime warning when + reading MP4 videos +
+ +Feb. 9, 2022 - Version 12.40 +
    +
  • Added PageCount tag to return the number of pages in a multi-page TIFF +
  • Added a new Nikon LensID (thanks Wolfgang Exler) +
  • Added a few more Sony LensTypes (thanks Jos Roost) +
  • Decode some new Canon tags (thanks Mark Reid) +
  • Decode another Nikon Z9 tag (thanks Warren Hatch) +
  • Decode Nikon NKSC GPSImgDirection (thanks Olaf) +
  • Improved handling of empty XMP structures in lists +
  • Tolerate leading UTF-8 BOM in -geotag log files +
  • Updated photoshop_paths.config to include WorkingPath +
  • Patched to allow writing of MP4 videos which have url tracks with a missing + sample description entry +
  • Fixed deep recursion error when reading multi-page TIFF images with more + than 100 pages +
  • Fixed potential deep recursion runtime error when writing nested XMP + structures +
  • Fixed warning which could be generated when writing new + Composite:GPSCoordinates tag +
  • Fixed description of GPR (General Purpose RAW) file type +
  • Fixed typo in the name of a new Nikon tag (thanks Herb) +
+ +Jan. 13, 2022 - Version 12.39 +
    +
  • Added a new Pentax LensType (thanks Christian Shulz) +
  • Added a couple of new Nikon LensID's +
  • Added support for Nikon NKSC sidecar files +
  • Decode another type of timed GPS from MP4 videos +
  • Decode more tags for the Nikon Z7 and Z9 (thanks Warren Hatch) +
  • Decode a couple more FLIR tags +
  • Extract ZIP file comments +
  • Made PNG ProfileName, SRGBRendering and Gamma writable +
  • Patched to avoid possible problem running "more" to show documentation in + Windows version +
  • Fixed problem writing Composite:GPSPosition with coordinates in DMS format, + and made this tag protected when writing +
  • Fixed bug where invalid date/time tags could be written to PNG files when + attempting to shift a non-existent date/time tag +
  • Fixed spelling of a few Matroska tag names (thanks Martin Hoppenheit) +
+ +Dec. 20, 2021 - Version 12.38 +
    +
  • Decode a number of new tags for the Nikon Z9 (thanks Warren Hatch) +
  • Patched incorrect decoding of AEBShotCount for the Canon EOS 90D +
  • Patched EXR reader to support long tag names +
  • Patched security issue (thanks Joe Lothan) +
  • Fixed an incorrect tag ID for a new Nikon MakerNote tag (github #108) +
  • Fixed XMP-exif:GPSMeasureMode conversions to match EXIF +
  • Fixed problem where some namespaces may be undeclared in the -X output when + using the -struct option +
+ +Dec. 8, 2021 - Version 12.37 +
    +
  • Decode timed GPS from Vantrue S1 dashcam MP4 videos +
  • Decode ColorData tags for the Canon EOS R3 (thanks LibRaw) +
  • Decode more makernotes tags for Nikon Z cameras (thanks Warren Hatch) +
  • Extract TransparentColor from GIF images +
  • Improved parsing of input time values for GPSTimeStamp to properly handle a + "." separator +
  • Improved warning when incorrectly using "<" instead of "=" to assign a tag + value +
  • Shortened a few of the new obscenely-long XMP-crs tag names +
  • Avoid writing XMP-dwc:EventID and XMP-tiff:NativeDigest +
  • Avoid printing same structure twice in -j and -X output when -l is used +
  • Fixed typo in a QuickTime tag name (thanks Hubert) +
  • Fixed two XMP-crs tag names and typo in a NikonSettings value (thanks Herb) +
  • Fixed patch of version 12.25 to avoid writing XMP which contains an + undefined namespace +
+ +Nov. 16, 2021 - Version 12.36 +
    +
  • IMPORTANT: Fixed bug introduced in 12.35 which corrupted JPEG 2000 images + when removing all metadata with -all= +
  • Added feature to bypass processing of specified XMP namespaces and + properties (to improve performance in cases where the XMP suffers from + Adobe-editing bloat) +
  • Added a number of new XMP tags used by Lightroom 11.0 +
  • Decode a number of new Nikon tags (thanks Warren Hatch) +
  • Made the Composite GPSPosition tag writable +
  • Fixed erroneous "Skipped unknown bytes after JPEG SOS" warning +
  • Fixed group for new writable Jpeg2000 color tags in -listx output +
  • Fixed problem finding files in Windows when using wildcards in file name and + a drive letter with no slash +
+ +Nov. 11, 2021 - Version 12.35 +
    +
  • Added ability to write ICC_Profile (and other color specifications) to + Jpeg2000 images +
  • Added %o code to -W option format string +
  • Added %f code to -d option for fractional seconds +
  • Added a couple of new Sony LensType values (thanks Jos Roost) +
  • Added a new CanonModelID (thanks Norbert Wasser) +
  • Added a new Nikon LensID +
  • Decode more Nikon MakerNotes tags for some new models (thanks Warren Hatch) +
  • Extract ThumbnailImage from some DJI drone videos +
  • Enhanced -ee option to extract metadata from all frames in a SEQ file +
  • Patched to avoid possible "Use of uninitialized value" runtime warning +
  • Fixed a couple of misspelt new ICC_Profile tag names (thanks Herb) +
  • Fixed problem generating the correct file extension when extracting + OriginalRawImage from a DNG file using the -W option with the %s format code +
  • Fixed bug introduced in 11.91 where exiftool couldn't find its libraries + when run via a soft link. Also changed to look for config file in the link + target directory instead of the directory of the link itself +
+ +Oct. 27, 2021 - Version 12.34 +
    +
  • Added support for ICC.2:2019 (Profile version 5.0.0 - iccMAX) color profiles +
  • Added ability to detect/delete a Windows Zone.Identifier alternate data + stream (ADS) via the new ZoneIdentifier tag (thanks Alex Xu) +
  • Added support for the Sony ILCE-7M4 (thanks Jos Roost) +
  • Added a new Sony lens (thanks LibRaw and Jos Roost) +
  • Added a new SonyModelID (thanks LibRaw) +
  • Added a new Canon RF lens (thanks Norbert Wasser) +
  • Improved handling of some SVG files +
  • Patched -overwrite_original_in_place option to open the output file in + update mode rather than write mode (to allow some write optimizations on + certain filesystems) (thanks Joel Low) +
  • Fixed case of tag ID for new XMP-iptcExt:EventID (thanks Michael Steidl) +
  • Fixed problem extracting ICC_Profile information from some PDF files +
  • API Changes: + +
+ +Oct. 16, 2021 - Version 12.33 +
    +
  • Added support for DNG version 1.6.0.0 +
  • Added two new Sony LensType values (thanks Jos Roost and LibRaw) +
  • Added some new elements to the XMP-crs:Look structure (thanks Herb) +
  • Added a few new IPTC XMP tags (thanks Michael Steidl) +
  • Added a new Canon RF lens (thanks Norbert Wasser) +
  • Decode Canon ShutterMode (thanks John Moyer) +
  • Extract LensModel from some Olympus MOV videos +
  • Generate MediaDataOffset/Size for MOV videos with zero-sized mdat chunk +
  • Improvements to CBOR reader, including hex dump with -v3 option +
  • Recognize Final Cut Pro XML files +
  • Allow binary data of Protected tags to be extracted with the -X -j and -php + options with -b by setting the API RequestAll option to 3 +
  • Changed name of "Canon EF 80-200mm f/4.5-5.6" lens with LensType 38 to add + "II" to the name (Exiv2 issue 1906) +
  • Fixed runtime warning when processing files with a .DIR extension +
+ +Sept. 30, 2021 - Version 12.32 +
    +
  • Added support for CBOR-format metadata in JUMBF (note that JUMBF support is + still experimental) +
  • Added a new Nikon LensID +
  • Added a new Pentax LensType +
  • Decode timed GPS for two more dashcam formats +
  • Support reference direction columns in -geotag CSV input +
  • Removed generation of GPSSpeedRef and GPSTrackRef tags in timed metadata for + most dashcam formats when speed is km/h and track is relative to true north +
  • Patched to allow writing of console output to named pipes +
  • Fixed formatting of InternalSerialNumber for some Panasonic cameras +
  • Fixed bug in arg_files/xmp2exif.args support file +
+ +Sept. 22, 2021 - Version 12.31 +
    +
  • Added a new SonyModelID and a couple of new Sony lenses (thanks Jos Roost) +
  • Added a new Canon LensType (thanks Chris Skopec) +
  • Added Composite GPSLatitude/Longitude tags for Sony videos to combine the + reference hemispheres as with the Composite tags for EXIF GPS +
  • Decode DPX AspectRatio +
  • Decode more GoPro MP4 tags +
  • Extract ICC_Profile from CS0 object in PDF files +
  • Extract encrypted GPS from Akaso V1 dashcam videos (can't yet decrypt) +
  • Improved handling of QuickTime iTunesInfo tags, and created new "iTunes" + family 1 group for these +
  • Patched so NoPDFList option also applies when writing +
  • Patched to allow user-defined PNG TextualData tags to be written only as iTXt +
  • Patched PDF reader to avoid concatenating values of multiple List-type tags + into a single tag +
+ +Aug. 12, 2021 - Version 12.30 (production release) +
    +
  • Added read support for Portable FloatMap (PFM) images (this was a bit of a + pain because they have the same file extension as Printer Font Metrix files) +
  • Added a few new Nikon LensID values (thanks LibRaw) +
  • Added a new Canon LensType +
  • Added a new Olympus CameraType (thanks LibRaw) +
  • Added minor warning about unknown data between JPEG segments +
  • Added a couple of new NikonSettings tags (thanks Warren Hatch) +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Decode 'id3 ' chunk in WAV audio files +
  • Decode timed GPS from concatenated Garmin dashcam videos +
  • Decode SamsungTrailer information from sefd atom in HEIC images +
  • Decode more Sony MakerNote tags for the ZV-E10 (thanks Jos Roost) +
  • Decode DepthMapTiff from JPEG images of more Samsung models +
  • Decode timed GPS from M2TS videos of yet another type of dashcam +
  • Extract PreviewImage from Xaiomi MP4 videos +
  • Changed name of second EmbeddedImage in Samsung trailer to EmbeddedImage2 +
  • Improved Dutch translations for GPS tags (thanks Peter Dubbelman) +
  • Allow ICC_Profile to be "deleted" from AVIF files (actually, the profile + isn't really deleted. Instead, a zero-length profile is written to allow a + profile to be added back later since QuickTime item property containers + currently can't be created) +
  • Patched to remove 2 GB size limit when reading Photoshop ImageSourceData +
+ +July 9, 2021 - Version 12.29 +
    +
  • Added a few new Nikon and Olympus lenses (thanks LibRaw) +
  • Improved a QuickTime "File format error" message to be more meaningful, and + made it a minor error +
  • Changed PNG writer to add EXIF before IDAT +
  • Some changes the way JUMBF metadata is handled +
  • Patched to read timed GPS from a different type of INSV videos +
  • Patched a security issue +
  • Fixed problem where ExifTool could hang when processing mebx timed metadata +
+ +June 22, 2021 - Version 12.28 +
    +
  • Added read support for Leica Image File (LIF) images +
  • Added a new Olympus LensType (thanks LibRaw) +
  • Decode another Panasonic tag (thanks LibRaw) +
  • Decode more timed metadata from Sony MP4 videos +
  • Attempt to shorten tag names for metadata in CZI files +
  • Allow full QuickTime Keys tag ID's in UserDefined tags (fixes backward + incompatibility introduced in 12.02) +
  • Patched to handle special characters in Torrent tag values +
+ +June 9, 2021 - Version 12.27 +
    +
  • Added a new SonyModelID value +
  • Added two new Nikon LensID values (thanks Daniel) +
  • Added a new Pentax RawDevelopmentProcess value +
  • Added a few new Sony LensType values (thanks Jos Roost) +
  • Added warning if IPTCDigest is not current +
  • Decode a couple more Pentax tags (thanks LibRaw) +
  • Decode streaming GPS from Novatek INNOVV MP4 and TS videos +
  • Improved tag names in config_files/covert_regions.config (thanks StarGeek) +
  • Changed MIME types for MS Office macro-enabled formats to add the .12 +
  • Patched Canon LensID logic to properly identify the Canon RF 24-105mm F4 L + IS USM lens +
  • Patched decoding of camm6 GPSDateTime to use a flexible epoch because other + apps don't seem to use a consistent time zero +
  • Fixed family 7 group names for QuickTime Keys tags +
  • Fixed problem reading BeatsPerMinute from some MP4 files +
  • Fixed hemisphere problem when extracting GPS from 70mai dashcam videos +
+ +May 20, 2021 - Version 12.26 (production release) +
    +
  • Added support for JPEG Stereo (JPS) images +
  • Added a new Sony LensType (thanks LibRaw) +
  • Added a new PentaxModelID (thanks LibRaw) +
  • Changed ExifTool namespace URI to use exiftool.org instead of exiftool.ca in + the -X option output (exiftool.ca is still recognized when reading XML) +
  • Improved handling of large-array warnings in -htmldump output +
  • Changed handling of escaped characters in #[CSTR] lines of -@ argfile +
  • Patched security vulnerability in argument of -lang option +
  • Fixed problem which could cause a "Wide character" warning and generate a + corrupted output file when writing some illegal values +
+ +Apr. 22, 2021 - Version 12.25 +
    +
  • JPEG XL support is now official +
  • Added read support for Medical Research Council (MRC) image files +
  • Added ability to write a number of 3gp tags in video files +
  • Added a new Sony PictureProfile value (thanks Jos Roost) +
  • Added a new Sony LensType (thanks LibRaw) +
  • Added a new Nikon LensID (thanks Niels Kristian Bech Jensen) +
  • Added a new Canon LensType +
  • Decode more GPS information from Blackvue dashcam videos +
  • Decode a couple of new NikonSettings tags (thanks Warren Hatch) +
  • Decode a few new RIFF tags +
  • Improved Validate option to add minor warning if standard XMP is missing + xpacket wrapper +
  • Avoid decoding some large arrays in DNG images to improve performance unless + the -m option is used +
  • Patched bug that could give runtime warning when trying to write an empty + XMP structure +
  • Fixed decoding of ImageWidth/Height for JPEG XL images +
  • Fixed problem were Microsoft Xtra tags couldn't be deleted +
+ +Apr. 13, 2021 - Version 12.24 +
    +
  • Added a new PhaseOne RawFormat value (thanks LibRaw) +
  • Decode a new Sony tag (thanks Jos Roost) +
  • Decode a few new Panasonic and FujiFilm tags (thanks LibRaw and Greybeard) +
  • Updated acdsee.config in distribution (thanks StarGeek) +
  • Recognize AutoCAD DXF files +
  • More work on experimental JUMBF read support +
  • More work on experimental JPEG XL read/write support +
  • Patched security vulnerability in DjVu reader +
+ +Apr. 1, 2021 - Version 12.23 +
    +
  • Added support for Olympus ORI files +
  • Added experimental read/write support for JPEG XL images +
  • Added experimental read support for JUMBF metadata in JPEG and Jpeg2000 + images +
  • Added built-in support for parsing GPS track from Denver ACG-8050 videos + with the -ee option +
  • Added a some new Sony lenses (thanks Jos Roost and LibRaw) +
  • Changed priority of Samsung trailer tags so the first DepthMapImage takes + precedence when -a is not used +
  • Improved identification of M4A audio files +
  • Patched to avoid escaping ',' in "Binary data" message when -struct is used +
  • Removed Unknown flag from MXF VideoCodingSchemeID tag +
  • Fixed -forcewrite=EXIF to apply to EXIF in binary header of EPS files +
  • API Changes: + +
+ +Mar. 17, 2021 - Version 12.22 +
    +
  • Added a few new Sony LensTypes and a new SonyModelID (thanks Jos Roost and + LibRaw) +
  • Added Extra BaseName tag +
  • Added a new CanonModelID (thanks LibRaw) +
  • Decode timed GPS from unlisted programs in M2TS videos with the -ee3 option +
  • Decode more Sony rtmd tags +
  • Decode some tags for the Sony ILME-FX3 (thanks Jos Roost) +
  • Allow negative values to be written to XMP-aux:LensID +
  • Recognize HEVC video program in M2TS files +
  • Enhanced -b option so --b suppresses tags with binary data +
  • Improved flexibility when writing GPS coordinates: +
      +
    • Now pulls latitude and longitude from a combined GPSCoordinates string +
    • Recognize full word "South" and "West" to write negative coordinates +
    +
  • Improved warning when trying to write an integer QuickTime date/time tag and + Time::Local is not available +
  • Convert GPSSpeed from mph to km/h in timed GPS from Garmin MP4 videos +
+ +Feb. 24, 2021 - Version 12.21 +
    +
  • Added a few new iOS QuickTime tags +
  • Decode a couple more Sony rtmd tags +
  • Patch to avoid possible "Use of uninitialized value" warning when attempting + to write QuickTime date/time tags with an invalid value +
  • Fixed problem writing Microsoft Xtra tags +
  • Fixed Windows daylight savings time patch for file times that was broken in + 12.19 (however directory times will not yet handle DST properly) +
+ +Feb. 23, 2021 - Version 12.20 +
    +
  • Added ability to write some Microsoft Xtra tags in MOV/MP4 videos +
  • Added two new Canon LensType values (thanks Norbert Wasser) +
  • Added a new Nikon LensID +
  • Fixed problem reading FITS comments that start before column 11 +
+ +Feb. 18, 2021 - Version 12.19 +
    +
  • Added -list_dir option +
  • Added the "ls-l" Shortcut tag +
  • Extract Comment and History from FITS files +
  • Enhanced FilePermissions to include device type (similar to "ls -l") +
  • Changed the name of Apple ContentIdentifier tag to MediaGroupUUID + (thanks Neal Krawetz) +
  • Fixed a potential "substr outside of string" runtime error when reading + corrupted EXIF +
  • Fixed edge case where NikonScanIFD may not be copied properly when copying + MakerNotes to another file +
  • API Changes: +
      +
    • Added ability to read/write System tags of directories +
    • Enhanced GetAllGroups() to support family 7 and take optional ExifTool + reference +
    • Changed QuickTimeHandler option default to 1 +
    +
+ +Feb. 9, 2021 - Version 12.18 +
    +
  • Added a new SonyModelID +
  • Decode a number of Sony tags for the ILCE-1 (thanks Jos Roost) +
  • Decode a couple of new Canon tags (thanks LibRaw) +
  • Patched to read differently formatted UserData:Keywords as written by iPhone +
  • Patched to tolerate out-of-order Nikon MakerNote IFD entries when obtaining + tags necessary for decryption +
  • Fixed a few possible Condition warnings for some NikonSettings tags +
+ +Feb. 3, 2021 - Version 12.17 +
    +
  • Added a new Canon FocusMode value +
  • Added a new FujiFilm FilmMode value +
  • Added a number of new XMP-crs tags (thanks Herb) +
  • Decode a new H264 MDPM tag +
  • Allow non-conforming lower-case XMP boolean "true" and "false" values to be + written, but only when print conversion is disabled +
  • Improved Validate option to warn about non-capitalized boolean XMP values +
  • Improved logic for setting GPSLatitude/LongitudeRef values when writing +
  • Changed -json and -php options so the -a option is implied even without the + -g option +
  • Avoid extracting audio/video data from AVI videos when -ee -u is used +
  • Patched decoding of Canon ContinuousShootingSpeed for newer firmware + versions of the EOS-1DXmkIII +
  • Re-worked LensID patch of version 12.00 (github issue #51) +
  • Fixed a few typos in newly-added NikonSettings tags (thanks Herb) +
  • Fixed problem where group could not be specified for PNG-pHYs tags when + writing +
+ +Jan. 21, 2021 - Version 12.16 (production release) +
    +
  • Extract another form of video subtitle text +
  • Enhanced -ee option with -ee2 and -ee3 to allow parsing of the H264 video + stream in MP4 files +
  • Changed a Nikon FlashMode value +
  • Fixed problem that caused a failed DPX test on Strawberry Perl +
  • API Changes: + +
+ +Jan. 18, 2021 - Version 12.15 (production release) +
    +
  • Added a couple of new Sony LensType values (thanks LibRaw and Jos Roost) +
  • Added a new Nikon FlashMode value (thanks Mike) +
  • Decode NikonSettings (thanks Warren Hatch) +
  • Decode thermal information from DJI RJPEG images +
  • Fixed extra newline in -echo3 and -echo4 outputs added in version 12.10 +
  • Fixed out-of-memory problem when writing some very large PNG files under + Windows +
+ +Jan. 6, 2021 - Version 12.14 +
    +
  • Added support for 2 more types of timed GPS in video files (that makes 49 + different formats now supported) +
  • Added validity check for PDF trailer dictionary Size +
  • Added a new Pentax LensType +
  • Extract metadata from Jpeg2000 Association box +
  • Changed -g:XX:YY and -G:XX:YY options to show empty strings for non-existent + groups +
  • Patched to issue warning and avoid writing date/time values with a zero + month or day number +
  • Patched to avoid runtime warnings if trying to set FileName to an empty + string +
  • Fixed issue that could cause GPS test number 12 to fail on some systems +
  • Fixed problem extracting XML as a block from Jpeg2000 images, and extract + XML tags in the XML group instead of XMP +
+ +Dec. 24, 2020 - Version 12.13 +
    +
  • Added -i HIDDEN option to ignore files with names that start with "." +
  • Added a few new Nikon ShutterMode values (thanks Jan Škoda) +
  • Added ability to write Google GCamera MicroVideo XMP tags +
  • Add time zone automatically to most string-based QuickTime date/time tags + when writing unless the PrintConv option is disabled +
  • Decode a new Sony tag (thanks LibRaw) +
  • Changed behaviour when writing only pseudo tags to return an error and avoid + writing any other tags if writing FileName fails +
  • Print "X image files read" message even if only 1 file is read when at least + one other file has failed the -if condition +
+ +Dec. 4, 2020 - Version 12.12 +
    +
  • Added ability to geotag from DJI CSV log files +
  • Added a new CanonModelID +
  • Added a couple of new Sony LensType values (thanks LibRaw) +
  • Enhanced -csvDelim option to allow "\t", "\n", "\r" and "\\" +
  • Unescape "\b" and "\f" in imported JSON values +
  • Fixed bug introduced in 12.10 which generated a "Not an integer" warning + when attempting to shift some QuickTime date/time tags +
  • Fixed shared-write permission problem with -@ argfile when using -stay_open + and a filename containing special characters on Windows +
+ +Nov. 27, 2020 - Version 12.11 +
    +
  • Added -csvDelim option +
  • Added new Canon and Olympus LensType values (thanks LibRaw) +
  • Added a warning if ICC_Profile is deleted from an image (github issue #63) +
  • EndDir() function for -if option now works when -fileOrder is used +
  • Changed FileSize conversion to use binary prefixes since that is how the + conversion is currently done (eg. MiB instead of MB) +
  • Patched -csv option so columns aren't resorted when using -G option and one + of the tags is missing from a file +
  • Fixed incompatiblity with Google Photos when writing UserData:GPSCoordinates + to MP4 videos +
  • Fixed problem where the tags available in a -p format string were limited to + the same as the -if[NUM] option when NUM was specified +
  • Fixed incorrect decoding of SourceFileIndex/SourceDirectoryIndex for Ricoh + models +
+ +Nov. 12, 2020 - Version 12.10 +
    +
  • Added -validate test for proper TIFF magic number in JPEG EXIF header +
  • Added support for Nikon Z7 LensData version 0801 +
  • Added a new XMP-GPano tag +
  • Decode ColorData for the Canon EOS 1DXmkIII (thanks LibRaw) +
  • Decode more tags for the Sony ILCE-7SM3 (thanks Jos Roost) +
  • Automatically apply QuickTimeUTC option for CR3 files +
  • Improved decoding of XAttrMDLabel from MacOS files +
  • Ignore time zones when writing date/time values and using the -d option +
  • Enhanced -echo3 and -echo4 options to allow exit status to be returned +
  • Changed -execute so the -q option no longer suppresses the "{ready}" message + when a synchronization number is used (eg. -execute123) +
+ +Oct. 29, 2020 - Version 12.09 +
    +
  • Added ability to copy CanonMakerNotes from CR3 images to other file types +
  • Added read support for ON1 presets file (.ONP) +
  • Added two new CanonModelID values +
  • Added trailing "/" when writing QuickTime:GPSCoordinates +
  • Added a number of new XMP-crs tags +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added a new Nikon Z lens (thanks LibRaw) +
  • Added a new Canon LensType +
  • Decode ColorData for Canon EOS R5/R6 +
  • Decode a couple of new HEIF tags +
  • Decode FirmwareVersion for Canon M50 +
  • Improved decoding of Sony CreativeStyle tags (thanks Jos Roost) +
  • Improved parsing of Radiance files to recognize comments +
  • Renamed GIF AspectRatio tag to PixelAspectRatio +
  • Patched EndDir() feature so subdirectories are always processed when -r is + used (previously, EndDir() would end processing of a directory completely) +
  • Yet another tweak to the EventTime formatting rules (also allow time-only + values with fractional seconds and a time zone) +
  • Avoid loading GoPro module unnecessarily when reading MP4 videos from some + other cameras +
  • Fixed problem with an incorrect naming of CodecID tags in some MKV videos +
  • Fixed verbose output to avoid "adding" messages for existing flattened XMP + tags +
+ +Oct. 15, 2020 - Version 12.08 +
    +
  • Added read support for MacOS "._" sidecar files +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Recognize Mac OS X xattr files +
  • Extract ThumbnailImage from MP4 videos of more dashcam models +
  • Improved decoding of a number of Sony tags (thanks Jos Roost) +
  • Fixed problem where the special -if EndDir() function didn't work properly + for directories after the one in which it was initially called +
  • Patched to read DLL files which don't have a .rsrc section (thanks Hank) +
  • Patched to support new IGC date format when geotagging +
  • Patched to read DLL files with an invalid size in the header +
+ +Oct. 2, 2020 - Version 12.07 +
    +
  • Added support for GoPro .360 videos +
  • Added some new Canon RF and Nikkor Z lenses (thanks LibRaw) +
  • Added some new Sony LensType and CreativeStyle values and decode some + ILCE-7C tags (thanks Jos Roost) +
  • Added a number of new Olympus SceneMode values (thanks Herb) +
  • Added a new Nikon LensID +
  • Decode more timed metadata from Insta360 videos (thanks Thomas Allen) +
  • Decode timed GPS from videos of more Garmin dashcam models +
  • Decode a new GoPro video tag +
  • Reformat time-only EventTime values when writing and prevent arbitrary + strings from being written +
  • Patched to accept backslashes in SourceFile entries for -csv option +
+ +Sept. 11, 2020 - Version 12.06 +
    +
  • Added read support for Lyrics3 metadata (and fixed problem where APE + metadata may be ignored if Lyrics3 exists) +
  • Added a new Panasonic VideoBurstMode value (thanks Klaus Homeister) +
  • Added a new Olympus MultipleExposureMode value +
  • Added a new Nikon LensID +
  • Added back conversions for XMP-dwc EventTime that were removed in 12.04 with + a patch to allow time-only values +
  • Decode GIF AspectRatio +
  • Decode Olympus FocusBracketStepSize (thanks Karsten) +
  • Extract PNG iDOT chunk in Binary format with the name AppleDataOffsets +
  • Process PNG images which do not start with mandatory IHDR chunk +
+ +Aug. 24, 2020 - Version 12.05 +
    +
  • Added a new Panasonic SelfTimer value (thanks Herb) +
  • Decode a few more DPX tags (thanks Harry Mallon) +
  • Extract AIFF APPL tag as ApplicationData +
  • Fixed bug writing QuickTime ItemList 'gnre' Genre values +
  • Fixed an incorrect value for Panasonic VideoBurstResolution (thanks Herb) +
  • Fixed problem when applying a time shift to some invalid makernote date/time + values +
+ +Aug. 10, 2020 - Version 12.04 +
    +
  • Added read support for Zeiss ZISRAW CZI files +
  • Added some new values for a couple of Olympus tags (thanks Sebastian) +
  • Decode a number of new tags for the Sony ILCE-7SM3 (thanks Jos Roost) +
  • Removed formatting restrictions on XMP-dwc:EventTime to allow a time-only + value to be written +
  • Moved new QuckTime ItemList tags added in version 12.02 to the proper group + (they were incorrectly added to the Keys group) +
  • Improved QuickTime -v3 output to show default language codes +
  • Patched -lang option to work for the values of some tags with coded + translations +
  • Patched the format of a number of QuickTime tags when writing for improved + compatibility with iTunes and AtomicParsley +
  • Patched to write a default QuickTime language code of 0x0000 (null) instead + of 0x55c4 ('und') +
+ +July 29, 2020 - Version 12.03 +
    +
  • Added family 7 group names to allow tag ID's to be specified when reading + and writing +
  • Fixed a couple of typos in tag values (thanks Herb) +
  • API Changes: + +
  • Internal Changes: +
      +
    • Changed Composite tag ID's to use "-" instead of "::" as a separator +
    +
+ +July 27, 2020 - Version 12.02 +
    +
  • Added support for a number of new QuickTime ItemList tags +
  • Added support for writing XMP-xmp:RatingPercent +
  • Added a new Sony LensType (thanks Jos Roost and LibRaw) +
  • Added a new Pentax LensType (thanks James O'Neill) +
  • Decode barcodes from Ricoh APP5 RMETA segment +
  • Decode a few new QuickTime tags written by Ricoh and Garmin cameras +
  • Decode timed GPS from Sony A7R IV MP4 videos +
  • Decode timed GPS from 70mai dashcam videos +
  • Decode a few new Panasonic tags (thanks Klaus Homeister) +
  • Decode altitude and more accurate latitude/longitude from Transcend Driver + Pro 230 MP4 videos +
  • Improved decoding of some Canon EOS 1DXmkIII custom functions +
  • Allow integer QuickTime TrackNumber and DiskNumber values +
  • Relax validity check of QuickTime:ContentCreateDate when writing with -n +
  • Removed "Com" from the start of some unknown QuickTime ItemList tag names +
  • Patched CanonCustom decoding for bug in Canon EOS-1DX firmware +
  • Changed QuickTime CleanAperture tags decode as signed rationals +
+ +June 24, 2020 - Version 12.01 +
    +
  • Added a new NEFCompression value (thanks Warren Hatch) +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Decode timed GPS from Rove Stealth 4K dashcam videos +
  • Fixed bug which would corrupt TIFF images with 16-bit image data offsets + when writing (these are very rare) +
+ +June 11, 2020 - Version 12.00 (production release) +
    +
  • Added two new Olympus LensTypes (thanks Don Komarechka for one) +
  • Added two new Sony LensType values (thanks Jos Roost) +
  • Added a few new Nikon LensID's (thanks Mathieu Carbou) +
  • Added support for the Sony ZV-1 (thanks Jos Roost) +
  • Added a new CanonModelID (thanks Jos Roost) +
  • Added missing MimeType values for HEICS and HEIFS files +
  • Added definitions for a number of new XMP-crs tags +
  • Recognize WOFF and WOFF2 font files +
  • Decode streaming GPS from Roadhawk, EEEkit and 360Fly MP4 videos +
  • Decode a number of new tags for the Nikon D6 (thanks Warren Hatch) +
  • Decode a couple more AF tags for the D500/D850 +
  • Decode a number of new Panasonic tags +
  • Improved Composite LensID logic (thanks Matt Stancliff) +
  • Enhanced -v option to state when a directory has 0 entries +
  • Removed a couple of incorrect Validate warnings for bilevel TIFF images +
  • Drop ContrastCurve tag when copying from NEF to JPEG +
  • Changed -csv output to add "Unknown" group name to column headings for + missing tags when -f and -G options are used +
  • Patched to support new XMP LensID format for Nikon cameras as written by + Apple Photos (thanks Mattsta) +
  • Fixed problem extracting metadata from Sigma DP2 Quattro X3F files +
  • Fixed End() and EndDir() functions so they work when writing and when the -v + option is used +
  • Fixed problem recognizing some PGM files +
  • Fixed bug in offsets for some Photoshop information in -v3 output +
  • Fixed problem writing a list containing empty elements inside an XMP + structure +
  • API Changes: + +
+ +May 11, 2020 - Version 11.99 +
    +
  • Added a new Nikon LensID (thanks Mykyta Kozlov) +
  • Added a new Canon LensType +
  • Added a newn PentaxModelID +
  • Decode a few new QuickTime tags +
  • Decode new ID3 Grouping tag +
  • Decode a few more MinoltaRaw tags (thanks LibRaw) +
  • Fixed runtime warning which could occur when reading corrupted RTF files +
  • Fixed another potential pitfall in M2TS Duration calculation +
  • Fixed problem extracting some unknown QuickTime:Keys tags +
  • Fixed problem decoding Nikon D850 orientation tags +
  • Fixed bug where TIFF image data may not be padded to an even number of bytes +
+ +May 1, 2020 - Version 11.98 +
    +
  • Added a new Nikon LensID (thanks Warren Hatch) +
  • Added a new Sony LensType (thanks LibRaw) +
  • Added a new Canon LensType +
  • Patched to extract EXIF with an "Exif\0\0" header from WebP images +
  • Enhanced -efile option and added to the documentation +
  • Minor tweak to -htmlDump output (disallow locking of empty selection) +
  • Fixed problem determining Duration of some M2TS videos +
+ +Apr. 27, 2020 - Version 11.97 +
    +
  • Added experimental -efile option (undocumented) +
  • Decode NMEA GGA sentence from streaming GPS of some dashcam videos +
+ +Apr. 24, 2020 - Version 11.96 +
    +
  • Decode streaming GPS from Lucas LK-7900 Ace AVI videos +
  • Changed new Exit/ExitDir function names to End/EndDir +
  • Fixed inconsistencies when using "-use mwg" together with the -wm option +
+ +Apr. 23, 2020 - Version 11.95 +
    +
  • Added Exit() and ExitDir() functions for use in -if conditions (NOTE: these + function names changed to End() and EndDir() in ExifTool 11.96) +
  • Enhanced -geotag feature to support a more flexible input CSV file format +
  • Enhanced -if and API Filter options to allow access to ExifTool object via + $self +
  • Fixed problem reading HEIC Exif with a missing header +
+ +Apr. 17, 2020 - Version 11.94 +
    +
  • Added support for QuickTime ItemList:GPSCoordinates +
  • Added additional Validate test for overlapping EXIF values +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added a new Nikon LensID +
  • Decode a few more Nikon tags (thanks Warren Hatch) +
  • Decode Pentax ShutterType +
  • Changed color of locked highlighted selection in -htmlDump output +
  • Fixed problem reading PDF files written by Microsoft Print-to-PDF +
  • Fixed problem where -X output would produce invalid XML for MP4 files + containing an HTCTrack +
+ +Apr. 3, 2020 - Version 11.93 +
    +
  • Added new config file to the distribution for writing Pix4D XMP-Camera tags + (config_files/pix4d.config) +
  • Added support for the DOSCyrillic (cp866) character set +
  • Added IO::String to the Windows EXE version +
  • Improved identification of Canon RF lenses (thanks LibRaw) +
  • Enhanced -htmlDump output to add "File offset" entry for EXIF tags and + ability lock highlighted selection by clicking the mouse +
  • Enhanced -srcfile option to generate OriginalFileName and OriginalDirectory + UserParam tags +
  • Patched HEIC writer to add missing pitm box if necessary +
  • Fixed problem adding back EXIF after deleting it from HEIC file +
  • Fixed minor problem with incorrect number of bytes being reported for + invalid header in corrupt files +
  • API Changes: +
      +
    • Enhanced UserParam option to allow parameters to be extracted as if + they were normal tags +
    +
+ +Mar. 19, 2020 - Version 11.92 +
    +
  • Added a new Nikon LensID (thanks Wolfgang Exler) +
  • Decode a few new Leica tags (thanks Tim Gray) +
  • Decode AccelerometerData from Samsung Gear 360 videos +
  • Fixed a couple of problems decoding timed GPS metadata from NextBase dashcam + videos +
  • Fixed problem where -X option could produce invalid XML when reading + corrupted XMP +
+ +Mar. 5, 2020 - Version 11.91 +
    +
  • Added undocumented -xpath option for use by alternate Windows version +
  • Decode a couple of new Panasonic tags +
  • Documented -ec option (available since version 11.54) +
  • Reverted -htmlDump fix of 11.90 because it broke more than it fixed, and + instead applied a targeted patch to fix this problem for RW2 files +
+ +Mar. 3, 2020 - Version 11.90 +
    +
  • Added a new Sony LensType (thanks LibRaw and Jos Roost) +
  • Added two new Olympus LensType values +
  • Added a new Canon LensType +
  • Added some new Canon RecordMode values +
  • Added some new QuickTime GeneralProfileIDC values +
  • Added new values for a couple of FujiFilm tags +
  • Added a number of new QuickTime GenreID values +
  • Decode Nikon Z6/Z7 phase-detect AF points (thanks Andy Dragon) +
  • Patched to avoid possible "Undefined subroutine" error in MacOS 10.15 +
  • Fixed incorrect offsets in -htmlDump output for some file types +
+ +Feb. 25, 2020 - Version 11.89 +
    +
  • Added support for Exif 2.32 for XMP +
  • Recognize the HIF file extension +
  • Improved verbose output for QuickTime iref items +
  • Patched to create new GPS metadata in Canon CR3 images using a default byte + order that is the same as existing EXIF boxes +
  • Patched to add missing newline that could occur in XMP with the API Compact + Shorthand option +
+ +Feb. 20, 2020 - Version 11.88 +
    +
  • Added write support for new Google depth-map XMP tags +
  • Added config_files/depthmap.config to the distribution +
  • Added minor error when attempting to write FFF images due to incompatibility + with Hasselblad Phocus software +
  • Patched to avoid "Invalid iloc offset size" error when writing some + QuickTime-based files +
  • Fixed incorrect ColumnCount for CSV files +
  • Fixed various spelling errors (thanks Jens Schleusener) +
  • Fixed bug writing QuickTime:Rotation in HEIC files +
+ +Feb. 13, 2020 - Version 11.87 +
    +
  • Added read support for CSV files +
  • Added "--" option to indicate the end of options +
  • Added ability to read/write/copy/delete the JPEG trailer as a block +
  • Added new Olympus CameraType and LensType values (thanks LibRaw) +
  • Decode a few more FujiFilm tags +
  • Enhanced -fast option (API FastScan) to bypass PNG CRC validation when + writing +
+ +Feb. 4, 2020 - Version 11.86 +
    +
  • Added support for DNG version 1.5 +
  • Added config_files/acdsee.config to the full distribution (thanks StarGeek) +
  • Added a new Sony LensType (thanks Jos Roost and LibRaw) +
  • Decode two more bits from Nikon LensType (thanks LibRaw) +
  • Decode QuickTime MovieFragmentSequence +
  • Patched HEIC writer to add missing iref box if necessary +
  • Fixed typo in a Canon LensType value +
  • API Changes: +
      +
    • Patched ImageInfo() to recognize a stringified object as a file name +
    +
+ +Jan. 28, 2020 - Version 11.85 (production release) +
    +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added a new Olympus CameraType (thanks LibRaw) +
  • Added a two new Pentax LensType values +
  • Added a new FujiFilm FocusMode +
  • Decode timed GPS from Akaso dashcam MOV videos +
  • Decode Insta360 trailer from INSP images and made Insta360 a deletable group +
  • Patched kml.fmt file to limit maximum image size (thanks Fedor Kotov) +
  • Fixed problem decoding values from Leica M10 and S maker notes +
+ +Jan. 10, 2020 - Version 11.84 +
    +
  • Decode accelerometer data from timed metadata of more dashcam videos +
  • Decode Canon G9 white balance tags (thanks LibRaw) +
  • Recognize INSP files +
+ +Jan. 9, 2020 - Version 11.83 +
    +
  • Added a couple of new XMP-crs tags (thanks Herb) +
  • Fixed bug introduced in 11.82 with the -php -D output +
  • Fixed problem where some flattened XMP tags could be written when they + should be avoided +
+ +Jan. 8, 2020 - Version 11.82 +
    +
  • Added a new Canon LensType +
  • Added a new CanonModelID (thanks LibRaw) +
  • Added ability to process SubDirectories in QuickTime Keys tags +
  • Removed minor error when writing PDF 2.0 files (github issue #30) +
  • Fixed problem where trailing null bytes were removed from binary values in + the -php output when the -b option was used +
+ +Jan. 2, 2020 - Version 11.81 +
    +
  • Added a new Nikon LensID +
  • Added two new CanonModelID's (thanks LibRaw) +
  • Decode AVIF AV1 configuration record +
  • Changed names of QuickTime MovieData tags to "MediaData" +
  • Patched to use 4-digit years in Time::Local calls +
  • Patched Composite sub-second date/time tags to do additional validation of + source EXIF date/time tags before adding sub seconds +
  • Fixed problem where -json output could produce invalid JSON when -struct was + used and the structure field names contained special characters (github + issue #32) +
  • Fixed spelling in a Panasonic SceneMode value (thanks Hubert) +
+ +Dec. 17, 2019 - Version 11.80 +
    +
  • Added a new Canon LensType +
  • Added a new Nikon Z LensID (thanks LibRaw) +
  • Added a few new Sony LensType values (thanks Jos Roost) +
  • Attempt to improve reliability of Samsung DepthMapWidth/Height decoding +
  • Updated a number of Canon-mount Tamron lens names to include the Tamron + model number +
  • Patched MOV/MP4 writer to allow a small amount of garbage at the end of a + file to be deleted when writing with the -m option +
  • Fixed bug where some Composite tags may not have taken priority over other + tags as they should have +
+ +Dec. 12, 2019 - Version 11.79 +
    +
  • Added support for AVIF files +
  • Added new Canon, Sigma and Sony LensType values (thanks LibRaw) +
  • Made PDF 2.0 writable at your own risk with the -m option (github issue #30) +
  • Enhanced -validate feature to warn about duplicate languages in an XMP + lang-alt list +
  • Fixed inconsistency between documentation and ExifTool capabilities for + "Writable" status of some tags +
+ +Dec. 5, 2019 - Version 11.78 +
    +
  • Added a new Nikon LensID (thanks Chris) +
  • Added two new FujiFilm SceneRecognition values +
  • Patched to avoid crash in Windows when writing a negative epoch time using + the "-d %s" option +
  • Fixed problem editing MIE tags when using the "-wm w" option +
+ +Nov. 27, 2019 - Version 11.77 +
    +
  • Added a new Nikon LensID (thanks Joe Schönberg) +
  • Added a number of new Olympus LensType values (thanks LibRaw) +
  • Added a new Canon LensType +
  • Decode timed GPS from Ambarella A12 dash cam MP4 videos +
  • Decode a number of new Sigma tags (thanks LibRaw) +
  • Decode a couple of new PanasonicRaw tags (thanks LibRaw) +
  • Enhanced -fileOrder option to add -fast feature +
+ +Nov. 12, 2019 - Version 11.76 +
    +
  • Added support for the Sony ILCE-9M2 (thanks Jos Roost) +
  • Added a couple of new XMP-GCamera tags +
  • Added MIMEType values for some formats that previously reported + "application/unknown" +
  • Enhanced -geotag feature to write pitch to CameraElevationAngle if available +
  • Improved determination of MIMEEncoding for TXT files +
+ +Nov. 4, 2019 - Version 11.75 +
    +
  • Added ability to read some basic characteristics of TXT files +
  • Added kml_track.fmt to the fmt_files of the full distribution +
  • Added built-in support for decoding GPS from the four video subtitle text + formats that were previously handled by separate config files, and removed + these config files from the distribution +
  • Derive GPSDateTime from CreateDate and SampleTime if not already available + when extracting timed GPS metadata from QuickTime-format videos +
  • Changed family 2 groups of some Extra tags +
+ +Oct. 29, 2019 - Version 11.74 +
    +
  • Added support for new XMP IPTC Extension version 1.5 tags +
  • Added a new Nikon LensID (thanks LibRaw) +
  • Decode GPS track from Auto-Vox dashcam MOV videos +
  • Improved Russian translations (thanks Andrei Korzhyts and Alexander) +
  • Enhanced convert_regions.config to support new IPTC Extension 1.5 ImageRegion +
  • Changed the way the FlatName element works when used in a structure element + (the structure name is now added as a prefix to the flattened tag name) +
  • Patched gpx.fmt and gpx_wpt.fmt to support sub-seconds in GPSDateTime value +
+ +Oct. 23, 2019 - Version 11.73 +
    +
  • Decode timed metadata from Parrot drone videos +
  • Patched dji.config file to properly handle time zones +
  • Fixed bug which caused runtime error when reading timed metadata from Cobra + Dash Cam AVI videos +
+ +Oct. 22, 2019 - Version 11.72 +
    +
  • Added warning messages for corrupted Photoshop document data +
  • Added a new Olympus CameraType +
  • Added a new Canon LensType +
  • Decode more Sigma tags +
  • Improved Russian translations (thanks Alexander) +
  • Updated decoding of some CanonCustom settings for recent models +
  • Documented DNG OpcodeList values +
+ +Oct. 16, 2019 - Version 11.71 +
    +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added a few new Nikon Z LensID's +
  • Added a simple print conversion for DNG OpcodeList tags (note that due to + this, these tags must now be copied using the -n option) +
  • Fixed problems determining some video parameters for DV files +
  • Changed behaviour of -sep option when writing empty list items +
  • API Changes: + +
+ +Oct. 10, 2019 - Version 11.70 (production release) +
    +
  • Added a new CanonModelID (thanks Laurent Clévy) +
  • Improved identification of Office Open XML files (github issue #27) +
  • Removed RAF version check when writing FujiFilm RAF files +
  • Limited the number of accelerometer records that ExifTool will read by + default with the -ee option from INSV files to avoid excessive processing + times and memory usage +
  • Patched Windows version to allow reading of shared files with Unicode names + (thanks Eriksson) +
  • Patched to avoid converting some bad GPS coordinates (thanks Csaba Toth) +
  • Fixed verbose output to include YCbCrSubSampling for JPEG files +
  • Fixed conversion and group names for the new FujiFilm tag added in 11.68 +
  • Fixed format of GeoTiffDirectory and GeoTiffDoubleParams when writing +
+ +Oct. 2, 2019 - Version 11.69 +
    +
  • Fixed bug introduced in version 11.66 where the sign was lost when writing + coordinate values between 0 and -1 to QuickTime:GPSCoordinates +
+ +Oct. 1, 2019 - Version 11.68 +
    +
  • Added read support for yet another type of streaming GPS in MP4 videos +
  • Added a number of new FujiFlashMode values +
  • Decode a new FujiFilm tag +
  • Made NikonCaptureOffsets and NikonCaptureVersion deletable +
  • Enhanced tag name documentation to indicate deletable MakerNotes tags +
+ +Sept. 30, 2019 - Version 11.67 +
    +
  • Added config_files/thinkware.config to the distribution +
  • Fixed bug decoding negative GPS coordinates from INSV videos +
+ +Sept. 30, 2019 - Version 11.66 +
    +
  • Added a new Nikon LensID (thanks LibRaw) +
  • Added a few new Canon LensType values (thanks LibRaw and Tom Lachecki) +
  • Decode a few more Hasselblad tags (thanks LibRaw) +
  • Decode a new Canon tag (thanks Laurent Clévy) +
  • Decode more Samsung trailer tags +
  • Extract BWF iXML, aXML and UMID from RIFF-format files +
  • Extract ICC_Profile from more types of PDF files +
  • Enhanced %s of the -W option to recognize the PICT format +
  • Recognize MacOS alias files +
  • Changed name of Ricoh CropMode35mm tag and added a new value (thanks LibRaw) +
  • Minor change to a Minolta lens name (thanks Jos Roost) +
  • Fixed problem where NikonCapture information couldn't be deleted from an NEF +
  • Fixed problem identifying some SVG files +
  • Fixed typo in a CanonModelID value (thanks Dmitry) +
  • Fixed bug which could result in "Internal error: no list index" warning when + creating nested XMP lang-alt lists +
  • Fixed the names of a few Tamron lenses for Nikon (thanks Tom Lachecki) +
  • Fixed problem extracting Layer information from some PSD files +
  • Fixed writing of QuickTime GPSCoordinates to use the correct number of + digits before the decimal point for latitude and longitude +
+ +Aug. 29, 2019 - Version 11.65 +
    +
  • Added new SonyModelID and Sony LensType values (thanks LibRaw and Jos Roost) +
  • Added support for some new Sony models (thanks Jos Roost) +
  • Added a couple of new CanonModelID values (thanks LibRaw) +
  • Added a new Canon ColorDataVersion value +
  • Enhanced FastScan option so a setting of 2 stops processing PNG images at + the IDAT chunk when reading +
  • Preserve order of nested lang-alt list entries when -struct option is used +
+ +Aug. 28, 2019 - Version 11.64 +
    +
  • Added a new Canon LensType (thanks LibRaw) +
  • Added a new Nikon LensID (thanks Bruno) +
  • Added config file for converting streaming GPS from BlueSkySea dashcam +
  • Decode FocusDistance for Nikon Z6/Z7 +
  • Documented groups in families 5 and 6 (available but undocumented since + Exiftool version 8.22 and 11.50 respectively) +
  • Fixed some ordering problems when writing/copying nested XMP lang-alt lists +
  • Fixed some minor quirks with QuickTime language codes (thanks Hayo Baan) +
  • Fixed a CanonModelID value (thanks Dmitry) +
  • API Changes: + +
+ +Aug. 20, 2019 - Version 11.63 - "PNG Early Text" +
    +
  • Added a few new Sigma lenses (thanks LibRaw) +
  • Improved handling of Canon CNTH atom in MOV/MP4 videos +
  • Changed PNG writer to place all text chunks before IDAT (not just XMP) + (github issue #23) +
  • Issue minor warning for any text chunk after PNG IDAT (not just XMP) +
  • Enhanced ForceWrite feature to allow "PNG" to be specified (to move existing + text chunks to before IDAT without editing any metadata) +
  • Removed Windows "surrogate" warning for files that wouldn't be processed + anyway +
  • Fixed some entries in the Minolta LensType list (thanks Jos Roost) +
  • Fixed identification of a Sony lens (thanks Jos Roost) +
+ +Aug. 15, 2019 - Version 11.62 +
    +
  • Added a number of new Canon, Pentax, Sony and Sigma lenses (thanks LibRaw) +
  • Removed some extraneous verbose warnings when geotagging +
  • Removed Minolta LensType value for a non-existent lens (thanks LibRaw) +
  • Patched problem writing some simple qualified XMP values +
  • Patched to avoid writing files in Windows with Unicode surrogate characters + in their name unless the -overwrite_original_in_place option is used +
  • Fixed an incorrect Pentax LensType (thanks LibRaw) +
  • Fixed family 2 group names of some XMP-exifEX and XMP Composite tags +
+ +Aug. 7, 2019 - Version 11.61 +
    +
  • Added a new FujiFilm CropMode (thanks LibRaw) +
  • Added a few new proprietary CustomRendered values (thanks Jeffrey Friedl) +
  • Added a new Nikon LensID and fixed a Canon LensType (thanks LibRaw) +
  • Added a new CanonModelID +
  • Decode more Sony DSC-RX100M7 tags (thanks Jos Roost) +
  • Write standard EXIF to PNG even if non-standard EXIF already exists +
  • Changed a Minolta/Sony LensType (thanks LibRaw) +
  • Changed Composite GPS reference direction tags to be derived from only the + XMP-exif GPS coordinate tags (and not other XMP GPS coordinates) +
  • Reverted a PNG Validation check that was removed from 11.60 +
  • Patched to avoid problems overriding new values when writing thumbnail and + preview images +
+ +July 30, 2019 - Version 11.60 +
    +
  • Added a few new Sigma LensType values (thanks LibRaw) +
  • Updated Sony makernote decoding for the DSC-RX100M7 (thanks Jos Roost) +
  • Various internal improvements to PNG reader/writer +
  • Fixed bug in RIFF decoder that could cause an "undefined subroutine" error + (thanks Hayo Baan) +
  • Fixed problem writing some QuickTime tags if the PREFERRED levels were + changed via the config file +
  • Install Changes: +
      +
    • Properly erase all temporary files after validation tests +
    +
+ +July 25, 2019 - Version 11.59 +
    +
  • Added a new SonyModelID (thanks LibRaw) +
  • Changed block delete to allow subsequent writing of tags from the same group + (like a group delete) +
  • Minor changes to warnings and verbose output when writing PNG images +
  • Fixed potential runtime warning on an error rewriting XMP in a PNG image +
+ +July 25, 2019 - Version 11.58 +
    +
  • Added a number of new Canon and Sony LensType values (thanks LibRaw) +
  • Decode NikonMeteringMode for the D500 +
  • Decode LensID for Nikon Z lenses +
  • Extract RawThermalImage from Parrot Bebop-Pro Thermal images +
  • Validate PNG CRC values when writing or using the Validate option +
  • Improved Russian translation (thanks Andrei Korzhyts) +
  • Improved identification of some Tamron lenses for Canon cameras +
  • Changed name of D810MeteringMode tag to NikonMeteringMode +
  • Patched writing of XMP in PNG images to always come before IDAT, and warn if + XMP comes after IDAT when reading +
  • Fixed problem replacing multiple lang-alt default-language structure + elements in lists of XMP structures (behaviour for other languages still not + ideal) +
  • API Changes: +
      +
    • Removed PNGEarlyXMP option +
    • Fixed problem introduced in 11.54 which caused Options('UserParam') to + return undef +
    +
  • Internal Changes: +
      +
    • A block delete of EXIF, XMP, IPTC, etc now sets the group delete flag +
    +
+ +July 19, 2019 - Version 11.57 +
    +
  • Improved decoding of some tags for the Sony ILCE-7RM4 (thanks Jos Roost) +
  • Minor change to a Sony lens name +
  • Fixed format of a number of 8-bit integer QuickTime tags when writing +
  • Fixed problem replacing multiple structure elements in lists of XMP + structures +
+ +July 18, 2019 - Version 11.56 +
    +
  • Added support for the Sony ILCE-7RM4 (thanks Jos Roost) +
  • Added a new SonyModelID (thanks LibRaw) +
  • Added a few new Sony/Minolta LensType values (thanks LibRaw and Jos Roost) +
  • Decode some new Nikon and Motorola tags (thanks Neal Krawetz) +
  • Decode a couple more ColorData tags for some Canon models +
  • Extract PreviewImage from DNG files which don't have a .DNG extension +
  • Extract Huawei APP7 maker notes with the Unknown (-u) option +
  • Internal change in LensID logic for Sony E-type lenses +
+ +July 12, 2019 - Version 11.55 +
    +
  • Added write support for XMP-crs:Texture and XMP-drs tags +
  • Added a number of new Panasonic NoiseReduction values +
  • Added definition for a new Kodak tag (thanks LibRaw) +
  • Added a couple of new Panasonic AFAreaMode values (thanks Daniel Beichl) +
  • Added a couple of new Sony/Minolta LensTypes (thanks Jos Roost and LibRaw) +
  • Added a new CanonModelID +
  • Decode HEVCConfiguration record from HEIC images +
  • Decode a new Panasonic tag +
  • Decode a new QuickTime tag +
  • Changed internal handling of Composite tag ID's to include module name +
  • Removed "FE" designation from Samyang E-mount lenses +
  • Dropped Validate warning about missing GPSProcessingMethod tag +
+ +July 2, 2019 - Version 11.54 +
    +
  • Added new Canon and Sony/Minolta LensType values (thanks LibRaw) +
  • Added a number of new Sony/Minolta LensType values (thanks Jos Roost) +
  • Added "Unknown" value for new EXIF CompositeImage tag +
  • Added ability to write GSpherical tags in video track of MOV/MP4 files +
  • Added support for geotagging from GPS/IMU CSV-format files +
  • Improved Russian translation (thanks Alexander) +
  • Improved Validate feature to check ExifVersion/GPSVersionID numbers +
  • Accept unsigned numbers when setting GPSAltitudeRef from a numerical value +
  • Fixed decoding of DepthMapWidth/Height for some Samsung live-focus images +
  • Fixed a couple of incorrect/incomplete CanonModelID values (thanks LibRaw) +
  • Fixed problem identifying some Canon lenses when used on a Sony camera with + a Metabones adapter +
  • API Changes: +
      +
    • Added FilterW option +
    • Enhanced Compact option to improve flexibility and include features of + XMPShorthand option +
    • Removed XMPShorthand option from documentation +
    +
+ +June 24, 2019 - Version 11.53 - "Exif 2.32" +
    +
  • Added support for the new tags of the Exif 2.32 specification +
  • Added a new SamsungModelID (thanks LibRaw) +
  • Added warning if extracting ZIP file contents without the -a option +
  • Added ability to extract EmbeddedVideo from the trailer of Android JPEG + images with the ExtractEmbedded option +
  • Decode timed GPS from Cobra Dash Cam AVI videos +
  • Decode a new GoPro tag +
  • Enhanced -struct option to allow extraction of structured Torrent Info +
  • Improved error handling when an unexpected terminator is encountered while + writing a QuickTime-format file +
  • Renamed one of the Nikon Saturation tags to "SaturationAdj" +
  • Removed warning message when writing FujiFilm RAFVersion 0240 and 0261 files +
  • Fixed encoding problem when writing some QuickTime UserData tags with + strings containing special characters +
  • API Changes: + +
+ +June 17, 2019 - Version 11.52 +
    +
  • Added a few new Nikon CropHiSpeed values (thanks Hayo Baan) +
  • Added a new Nikon LensID (thanks Yves) +
  • Fixed problem where reading a large, corrupt AIFF file may could take an + excessively long time +
  • API Changes: +
      +
    • Enhanced Compact option to add levels 3, 4 and 5 (github issue #16) +
    +
+ +June 13, 2019 - Version 11.51 +
    +
  • Decode Canon DistortionCorrection tags +
  • Removed a minor EXIF warning when processing EPS files with a DOS header +
  • Fixed bug which caused an error when rewriting some EPS files multiple times +
+ +June 11, 2019 - Version 11.50 (production release) +
    +
  • Added a new Canon LensType and two new Sony LensTypes (thanks LibRaw) +
  • Added tiff_version and rotate_regions config files to the distribution +
  • Added two new QuickTime Keys tags and made some existing Keys unwritable +
  • Improved Composite LensID logic to make better use of EXIF LensModel +
  • Improved logic when writing BinaryData tags to allow multiple interdependent + tags to be written in a single command +
  • Improved -htmldump output to show names of Unknown tags +
  • Allow advanced formatting expressions to access the current tag key ($tag) +
  • Remove escaped nulls from -json string values +
  • Reverted change in ExifTool 11.38 so that Composite GPS reference directions + are generated again even if the EXIF versions of these tags already exist +
  • Fixed an incorrect FlashPix CodePage conversion +
+ +June 5, 2019 - Version 11.49 +
    +
  • Added inverse print conversion for one of the QuickTime ItemList Genre tags +
  • Avoid creating a few obscure QuickTime UserData tags when writing +
  • Fixed problem where some QuickTime groups were not being created when + writing QuickTime tags without specifying a group +
  • Fixed problem where QuickTime Keys tags could be duplicated when writing an + existing alternate-language tag +
  • Fixed problem were QuickTime Keys alternate-language tags would not be + written when deleting the corresponding default-language tag in the same + command +
  • Fixed some inconsistencies when writing QuickTime tags using the -wm + (WriteMode) option +
  • Fixed an incorrect Pentax Sigma LensType value +
+ +June 1, 2019 - Version 11.48 +
    +
  • Added write support for Google GCamera and GCreation XMP tags +
  • Renamed XMP-GDepth "Data" tag to "DepthImage" +
  • Fixed bug where some QuickTime UserData tags could be duplicated when + writing +
+ +May 31, 2019 - Version 11.47 +
    +
  • Fixed problem which resulted in a warning for one of the CanonVRD tests on + some platforms +
+ +May 31, 2019 - Version 11.46 - "CR3 update" +
    +
  • Added ability to write CanonVRD tags in CR3 images +
  • Decode a couple more tags from Canon CR3 images +
  • Enhanced Validate option to check for duplicate QuickTime atoms +
  • Relaxed constraints when writing IPTC date tags to allow use of separators + other than a colon +
  • Fixed CR3 writing to update CTBO table with any changed offsets or sizes + (although this table doesn't seem to be used by any RAW viewer, it may be + used in-camera to improve response time when browsing images) +
+ +May 29, 2019 - Version 11.45 +
    +
  • CORRUPTION WARNING: Patched problem where Canon DPP would destroy a CR3 + image if the file had previously been edited by DPP then Exiftool
    + (If you have edited any CR3 images with ExifTool that had been previously + edited by DPP, then re-edit with ExifTool 11.45 or later to restructure the + file so DPP doesn't destroy it if used later to edit the file again) +
  • Added ability to create and delete QuickTime Keys tags +
  • Added sample config file (mini0806.config) to generate GPS tags from + subtitle Text in Mini 0806 dashcam videos +
  • Added new Canon and Nikon lenses (thanks LibRaw) +
  • Added a new Olympus CameraType (thanks LibRaw) +
  • Decode CanonVRD tags from CR3 images +
  • Improved handling of QuickTime language tags when writing +
  • Fixed bug introduced in 11.38 which could cause "Use of uninitialized value" + runtime warning when reading XMP GPS tags +
  • Fixed bug where QuickTime tags could be written when another group was + specified +
  • API Changes: + +
+ +May 21, 2019 - Version 11.44 +
    +
  • Added ability to extract XMP as a block from XMP files +
  • Prevent ExifIFD from being deleted from any RAW file type +
  • Fixed problem where some Canon tags couldn't be written in CR3 files +
  • Fixed problem reading QuickTime Keys tags with a space in the tag ID +
  • Fixed incorrect family 1 group when reading some QuickTime Keys tags +
+ +May 17, 2019 - Version 11.43 - "Write HEIC and CR3" +
    +
  • Added ability to write/create EXIF and write ICC_Profile in HEIC images +
  • Added ability to write/create EXIF and write MakerNotes in CR3 images + (one might hope/expect EXIF to be stored in the same location for HEIC and + CR3 since they are both based on the QuickTime file format, but in fact they + couldn't be more different, and both are much more complicated than + necessary, which of course follows the seemingly established practice of + intentional obfuscation and zero standardization in video metadata) +
  • Added support for QuickTime ItemList:Author and Keys:DisplayName tags +
  • Prevent MakerNotes from being deleted from any RAW file type +
  • Fixed writing of XMP in HEIC files to conform with the HEIC specification + (obviously, Apple couldn't put this XMP in the same place as any other + QuickTime-based file format, because Apple is, after all, king of "Let's + reinvent the wheel!") +
  • Fixed problem where API WriteMode option wouldn't always prevent groups from + being created when group creation was disabled +
+ +May 13, 2019 - Version 11.42 +
    +
  • Added ability to edit ThumbnailImage in Canon MOV videos +
  • Improved verbose hex dump for HEIC files +
  • Fixed another "Chunk offset outside movie data" error when writing some HEIC + files +
+ +May 9, 2019 - Version 11.41 +
    +
  • Added write support and improved language handling for 3GP QuickTime tags +
  • Fixed format problems writing some binary values to QuickTime tags +
  • Fixed some language translations (thanks Herbert Kauer) +
+ +May 7, 2019 - Version 11.40 +
    +
  • Added a new Canon LensType +
  • Added a new value for EXIF:SceneCaptureType used by some Samsung cameras +
  • Fixed QuickTime writing to preserve existing same-named default-language + tags in other groups when writing a default language tag +
+ +May 3, 2019 - Version 11.39 - "Create QuickTime tags" +
    +
  • Added ability to create new QuickTime tags in MOV/MP4 videos +
  • Added two new Canon LensTypes and a new CanonModelID (thanks LibRaw) +
  • Added a few new Sony/Minolta LensType values (thanks Jos Roost) +
  • Added a number of new QuickTime GenreID values +
  • Added range check on date/time values when writing +
  • Decode Canon EOS D60 black levels +
  • Split off some QuickTime tags into different family 1 groups +
  • Fixed "Chunk offset outside movie data" error when writing some HEIC files +
  • Fixed decoding of Pentax AutoBracketing for K-1 and K-5 (github issue #15) +
  • Fixed some QuickTime family 2 group names +
  • Fixed bug introduced in 11.38 that broke extraction of thumbnail images from + Canon MOV videos +
+ +Apr. 24, 2019 - Version 11.38 +
    +
  • Added Extra JPEGImageLength tag +
  • Added nksc.config to the sample config files +
  • Added a couple more Sony/Minolta LensTypes (thanks Jos Roost) +
  • Added a couple of new Sigma LensType values +
  • Decode a couple more tags from Pittasoft dashcam videos +
  • Decode two new FLIR tags (thanks Corinne Berthier) +
  • Decode a new ERF tag, and fix wrong format for some others (thanks LibRaw) +
  • Improved decoding of Sigma maker notes for some models +
  • Enhanced Composite tag logic to allow a scalar Inhibit entry +
  • Enhanced XMP processing to support readable subdirectories embedded in a tag +
  • Updated some language translations +
  • Patched Composite GPS reference direction tags to prevent them from being + created if these tags already exist +
  • Fixed problem reading some odd PDF files +
+ +Apr. 17, 2019 - Version 11.37 +
    +
  • Added a new Sony AFAreaMode (thanks Jos Roost) +
  • Decode GPS and other tags from Pittasoft Blackvue dashcam videos +
  • Improved decoding of FujiFilm FlickerReduction +
  • Ignore any garbage before an NMEA sentence when geotagging +
  • Fixed bug which could result in loss of timed GPS metadata when writing MP4 + videos +
+ +Apr. 15, 2019 - Version 11.36 +
    +
  • Added a number of new MacOS tags +
  • Added a new CanonModelID (thanks Laurent Clévy) +
  • Added some new Canon EasyMode and AFAreaMode values +
  • Added two new Canon AspectRatio values (thanks LibRaw) +
  • Decode a new Nikon tag (thanks LibRaw) +
  • Decode some new FujiFilm tags +
  • Updated Sony maker notes for the DSC-RX0M2 (thanks Jos Roost) +
  • Hide the Nikon ShotInfo offset tags +
  • Fixed problem decoding NikonCustom settings for some D810 firmware versions +
  • Fixed typo in a warning message (thanks Hayo Baan) +
+ +Apr. 9, 2019 - Version 11.35 +
    +
  • Added print conversion for MDItemFSLabel +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added an additional -validate check for PNG images +
  • Decode a few more FujiFilm RAF tags (thanks LibRaw) +
  • Decode a couple more QuickTime tags +
  • Allow "Copy0" to be specified as a group name for the copy number of the + primary tag when extracting information +
  • Improved the Composite ImageSize tag to report the RawImageCroppedSize for + FujiFilm RAF images +
  • Changed Composite ImageSize tag to use a space instead of "x" as a separator + when the -n option is used +
  • Fixed problem writing user-defined PhaseOne SensorCalibration tags +
  • Fixed problem where a List-type tag may not be split into individual items + with the -sep option when using the advanced-formatting "@" feature +
  • API Changes: +
      +
    • Patched a potential pitfall if calling code used both the old List and + ListSep options at the same time as the new ListJoin option +
    +
+ +Apr. 4, 2019 - Version 11.34 +
    +
  • Added a couple of new Canon LensType values (thanks LibRaw for one) +
  • Added a new CanonExposureMode value (thanks Arnold van Oostrum) +
  • Added support for FujiFilm X-H1 Ver2.01 RAF images +
  • Decode a couple of new Sony tags (thanks LibRaw) +
  • Improved decoding of Sony Shutter tag (thanks Jos Roost) +
  • Improved identification of some Sony lenses (thanks Jos Roost) +
  • Improved parsing of streamed metadata from TomTom Bandit videos +
  • Improved warning for truncated QuickTime atom +
  • Accept wider range of formats when writing QuickTime:GPSCoordinates +
  • API Changes: +
      +
    • Changed SetFileName() 'Link' option name to 'HardLink' (but still allow + 'Link' for backward compatibility) +
    +
+ +Mar. 28, 2019 - Version 11.33 +
    +
  • Added write support for HEIC/HEIF files +
  • Added new write-only SymLink tag for creating symbolic links +
  • Made EXIF GDALMetadata and GDALNoData writable +
  • Enhanced writing capabilities for MOV/MP4 videos +
  • Enhanced -validate option to add more IPTC checks +
  • Updated decoding of Sony ILCE-9 maker notes for firmware version 5.00 + (thanks Jos Roost) +
  • Fixed problem reading streamed metadata from some TomTom Bandit videos +
  • API Changes: +
      +
    • Added SymLink option to SetFileName() +
    +
+ +Mar. 14, 2019 - Version 11.32 +
    +
  • Added a new Nikon LensID (thanks Kenneth Cochran) +
  • Added a couple of new QuickTime HandlerType values +
  • Decode streamed metadata from DuDuBell M1 and VSYS M6L dashcam videos +
  • Attempt to improve Nikon lens identification +
  • API Changes: +
      +
    • Added new single-argument version of ShiftTime() +
    +
+ +Mar. 7, 2019 - Version 11.31 +
    +
  • Added read support for FITS images +
  • Another try at removing spaces from some DICOM values (github issues #10/12) +
+ +Mar. 6, 2019 - Version 11.30 (production release) +
    +
  • Added a new Sony/Minolta LensType (thanks Jos Roost) +
  • Decode streaming metadata from TomTom Bandit Action Cam MP4 videos +
  • Decode Reconyx HF2 PRO maker notes +
  • Decode ColorData for some new Canon models (thanks LibRaw) +
  • Enhanced -geotag feature to set AmbientTemperature if available +
  • Remove non-significant spaces from some DICOM values (github issues #10/12) +
  • Fixed possible "'x' outside of string" error when reading corrupted EXIF +
  • Fixed incorrect write group for GeoTIFF tags added in version 11.24 +
+ +Feb. 28, 2019 - Version 11.29 +
    +
  • Added support for Ricoh GR III maker notes +
  • Added a new Canon LensType (thanks Claude Jolicoeur) +
  • Added a new XMP-crs tag (github issue #8) +
  • Enhanced -csv option to output base64-encoded binary data when combined with + -b or when the -charset option is used and the text has invalid characters + (github issue #11) +
  • Remove trailing space from even-length DICOM values (github issue #9) +
  • Patched to avoid "Hexadecimal number > 0xffffffff non-portable" warning + (github issue #6) +
  • Fixed meta charset attribute in -htmlDump output +
+ +Feb. 21, 2019 - Version 11.28 +
    +
  • Added support for reading INSV video and decode streaming GPS +
  • Added a new Pentax LensType (thanks Louis Granboulan) +
  • Added a new FujiFilm ImageStabilization value +
  • Allow exiftool to be run via a symbolic link on Mac/Linux +
  • Reverted INDD patch of version 11.27 (ie. raise error again on incorrectly + terminated INDD object list) +
  • Changed handling of temporary documentation file in Windows version +
+ +Feb. 14, 2019 - Version 11.27 +
    +
  • Added support for more XMP-dji-drone tags +
  • Added new Olympus CameraType and LensType values (thanks LibRaw) +
  • Added a new Canon LensType (thanks LibRaw) +
  • Added a new CanonModelID +
  • Decode yet another type of GPS from DashCam videos +
  • Allow FileName to be written when only case is changed on case-insensitive + filesystems +
  • Improved identification of some iWork file types +
  • Recognize the LRV file extension +
  • Changed Windows version to use the parent folder of PAR_GLOBAL_TEMP for the + temporary documentation file +
  • Don't raise an error if an INDD object list is terminated by spaces instead + of nulls +
  • Fixed some problems with new -htmldump IFD highlighting feature +
  • Fixed bug introduced in 11.24 with "-o -.EXT" feature +
+ +Jan. 21, 2019 - Version 11.26 +
    +
  • Added a new Nikon LensID (thanks LibRaw) +
  • Decode more tags for the Sony ICLE-6400 (thanks Jos Roost and LibRaw) +
  • Enhanced -htmldump feature to highlight IFD when mousing over IFD offset +
+ +Jan. 15, 2019 - Version 11.25 +
    +
  • Added a new Sony/Minolta LensType (thanks LibRaw) +
  • Added a new Nikon LensID +
  • Decode Leica D-Lux7 maker notes +
  • Decode more Nikon AF tags for newer models +
  • Decode Samsung Type2 maker notes with lower case Make +
  • Decode another Sony tag (thanks Jos Roost) +
  • Improved decoding of Nikon LensType +
  • Improved time shift feature to fix some incorrectly formatted date/time + values +
  • Renamed some Sony ImageCount tags to ShutterCount (thanks Jos Roost) +
  • Fixed problem reading back metadata written to some odd PDF files +
+ +Jan. 8, 2019 - Version 11.24 +
    +
  • Compatibility Notice: Changed the meaning of '-' and '+' modifiers for %C + formatting code (does not affect lower-case %c code) +
  • Decode a number of new Nikon tags (thanks Michael Tapes for samples) +
  • Added new Olympus FlashType and FlashModel values (thanks Per) +
  • Added a new Canon LensType +
  • Added a new Nikon LensID +
  • Made more GeoTIFF tags writable +
  • Handle XMP rdf:value when reading +
  • Improved warning when trying to read a file with a zero-length name +
  • Fixed decoding of PictureControl tags for Nikon Z-7 +
  • Fixed problem writing date/time values with " DST" designator at end of + date/time string +
  • Fixed problem in Windows which could cause ExifTool to abort due to a + Win32::FindFile error if a file name contained surrogate Unicode characters +
+ +Dec. 21, 2018 - Version 11.23 +
    +
  • Recognize DWG and DWF files +
  • Minor improvement to some -validate warnings +
  • Tolerate leading UTF-8 byte order mark (BOM) at start of JSON files +
  • Fixed problem recognizing some streaming camm metadata in QuickTime videos +
+ +Dec. 13, 2018 - Version 11.22 +
    +
  • Added read support for PC Paintbrush (PCX) files +
  • Added two new Sony/Minolta LensTypes (thanks Jos Roost and LibRaw) +
  • Decode LensData tags for some newer Nikon models +
  • Decode ColorData for the Canon EOS R (thanks LibRaw) +
  • Recognize DCX files +
+ +Dec. 7, 2018 - Version 11.21 +
    +
  • Added a new Sony/Minolta LensType (thanks Jos Roost) +
  • Added a new Olympus FlashModel (thanks Michael Meissner) +
  • Improved decoding of FujiFilm InternalSerialNumber (thanks LibRaw) +
  • Minor improvements to decoding of GPS from some dashcam videos +
  • Made XMP-getty:Personality a List-type tag +
  • Made it an error to use the -o option or write FileName or Directory tags + when using the TestName dry-run feature +
  • Fixed problem using -E with other character sets when writing +
+ +Nov. 20, 2018 - Version 11.20 +
    +
  • Added a new Panasonic WhiteBalance value +
  • Added a new Nikon LensID (thanks LibRaw) +
  • Decode streaming GPS from MOV videos for another dashcam model +
  • Improved -E option to support character sets other than UTF-8 +
+ +Nov. 14, 2018 - Version 11.19 +
    +
  • Added -fast4 option +
  • Enhanced -if option to allow arbitrary Perl expressions instead of just + logic expressions +
  • API Changes: + +
+ +Nov. 12, 2018 - Version 11.18 +
    +
  • Decode a new Nikon tag (thanks Richard Butler) +
  • Decode a new FujiFilm tag +
  • Updated decoding of Sony maker notes for newer models (thanks Jos Roost) +
  • Enhanced -if option to allow fast processing pass to evaluate the condition +
  • Improved warning for unknown JPEG APP segment +
+ +Nov. 4, 2018 - Version 11.17 +
    +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Added a new Sony/Minolta LensType and a new SonyModelID (thanks LibRaw) +
  • Decode GPS from Garmin Dashcam videos +
  • Changed type of J2C files from a JPEG 2000 image to a JPEG 2000 codestream +
+ +Oct. 26, 2018 - Version 11.16 +
    +
  • Decode FLIR GPS information +
  • Decode 3D image from RED Hydrogen smartphone +
  • Minor improvements to decoding of new FujiFilm tags +
  • Fixed problem where writing Shortcut tags with the -E option would double + unescape the HTML entities +
+ +Oct. 25, 2018 - Version 11.15 +
    +
  • Added a couple of new Canon LensType values (thanks LibRaw and Andrew Shieh) +
  • Added a new Nikon LensID +
  • Added definitions for a few more VCard tags +
  • Added a new FujiFilm ShutterType value +
  • Decode some new FujiFilm tags (thanks Richard Butler) +
  • Store XMP GPS coordinates with two extra digits of precision and trim + trailing zeros +
  • Improved technique for handling rounding errors in times and GPS seconds +
  • Removed "Undersized IFD0 StripByteCounts" minor warning when writing ORF + files since this is a "feature" of most Olympus models +
  • Warn about undefined EXIF values with -validate option +
  • Changed the way Mask-ed values are decoded (do bit shift automatically) +
  • Changed FujiFilm HighISONoiseReduction tag to just "NoiseReduction", and + avoid extracting historic NoiseReduction tag if value is "n/a" +
  • Fixed potential problem reading GeoTiff tags with multiple SHORT values +
  • API Changes: + +
+ +Oct. 16, 2018 - Version 11.14 +
    +
  • Added more TIFF Compression values +
  • Added more AIFF CompressionType values +
  • Added more Nikon NEFCompression values (thanks LibRaw) +
  • Added a new Canon RecordMode +
  • Decode some new Canon custom functions +
  • Patched "Invalid VignettingCorrUnknown2 data" warning for EOS R CR3 images +
  • Fixed bug were any argument beginning with "-progress" on the command line + was interpreted as the -progress option +
+ +Oct. 9, 2018 - Version 11.13 +
    +
  • Decode GPS from NextBase 512G dashcam MOV videos (different than 512GW) +
  • Added a new Canon LensType (thanks LibRaw) +
  • Minor improvements to verbose dump of streaming GPS metadata +
  • Reverted change of version 10.71 which resulted in Windows not recognizing + PNG CreationTime as written by ExifTool (added this feature to the API + StrictDate option instead) +
  • Improved decoding of Nikon CropHiSpeed (thanks LibRaw) +
  • Improved -fast option to reduce memory usage when reading JPG, PNG, + QuickTime-based and RIFF-based files via a sequential stream +
  • Fixed DOF calculation to use ApproximateFocusDistance if available +
  • API Changes: +
      +
    • Enhanced StrictDate option to reformat PNG CreateTime according to PNG + specification +
    +
+ +Oct. 2, 2018 - Version 11.12 +
    +
  • Added a new Sony/Minolta LensType (thanks LibRaw and Jos Roost) +
  • Added a new Nikon LensID +
  • Decode a few new Sony SRF2 tags (thanks LibRaw) +
  • Decode GPS from NextBase 512GW dashcam MOV videos +
  • Validate MS-DOC FIB before extracting contained tags +
  • Fixed bug extracting GPSSpeed for some dashcam models +
+ +Sept. 27, 2018 - Version 11.11 (production release) +
    +
  • Added ARQ to the list of supported file types +
  • Added support for GIMP XCF version 4 and later +
  • Added a new QuickTime HandlerType value +
  • Added read support for Apple AAE files +
  • Added a new CanonModelID and some new Canon LensType values (thanks LibRaw) +
  • Added a number of new Nikon LensID values (thanks Robert Rottmerhusen) +
  • Added a new Sony/Minolta LensType (thanks LibRaw) +
  • Decode more Sony IDC tags (thanks Jos Roost) +
  • Decode some new Panasonic tags (thanks Klaus Homeister) +
  • Decode more tags from Nikon MOV videos +
  • Decode a new Nikon tag (thanks LibRaw) +
  • Decode a large number of new Kodak IFD tags (thanks Jim McGarvey) +
  • Decode streaming GPS from videos of more dashcam and drone models +
  • Decode more tags from Microsoft Word DOC files +
  • Updated arg_files/iptcCore.args for IPTC Extension version 1.4 +
  • Patched to read corrupted MakN data written by buggy Adobe Camera Raw +
  • Downgraded "Undersized StripByteCounts" error for some RAW file types +
  • Fixed incorrect decoding of embedded GPS in Rexing V1P dashcam videos +
  • Fixed incorrect format for DNGPrivateData +
  • Fixed potential error when deleting maker notes from some images +
  • Fixed problem decoding Apple PLIST information from some files +
  • Fixed bug in Windows with CR/LF sequences in list values of the -X output +
  • Fixed some inconsistencies in detecting file name conflicts when writing the + TestName tag +
+ +Aug. 17, 2018 - Version 11.10 +
    +
  • Added support for Canon 1DX firmware 2.1.0 +
  • Added a new Canon LensType (thanks LibRaw) +
  • Added a new Nikon LensID (thanks LibRaw) +
  • Added a new CanonModelID +
  • Decode more tags for newer Sony DSC models (thanks Jos Roost) +
  • Decode some new SonyIDC tags (thanks Jos Roost) +
  • Decode a number of new Panasonic tags (thanks Klaus Homeister) +
  • Improved validation of XMP namespaces +
  • Changed "File not found" messages to "Error: File not found" +
  • Fixed problem editing tags in Canon DR4 directory +
+ +Aug. 13, 2018 - Version 11.09 +
    +
  • Added new Pentax and Canon LensType values +
  • Decode Google Camera Motion metadata from MP4 videos +
  • Decode more PanasonicRaw tags (thanks Klaus Homeister) +
  • Removed warning when multiple Word document LastSavedBy tags exist and the + Duplicates option wasn't enabled (added Note in tag name docs instead) +
+ +Aug. 1, 2018 - Version 11.08 +
    +
  • Decode more tags from Microsoft Word documents, including LastSavedBy +
  • Decode image file characteristics from Windows EXE files +
  • Decode more PanasonicRaw tags (thanks Klaus Homeister) +
  • Changed names of new Samsung trailer tags +
  • Fixed potential problems converting C-style escaped strings +
  • Fixed new "#[CSTR]" feature to work with -stay_open option +
+ +July 27, 2018 - Version 11.07 +
    +
  • Added "#[CSTR]" feature to -@ argfile +
  • Added some new Sony LensType values (thanks Jos Roost) +
  • Decode more tags from Samsung trailer +
  • Decode an undocumented DNG tag +
  • Decode some new Panasonic tags (thanks Klaus Homeister) +
  • Improved/fixed a few Validate warnings +
  • Made MakerNote "Bad SubDirectory start" warnings minor +
  • Fixed NoDups() function to work with special characters as list separators +
+ +July 6, 2018 - Version 11.06 +
    +
  • Fixed "undefined value" bug when reading ImageSourceData from a JPEG file +
+ +July 5, 2018 - Version 11.05 +
    +
  • Added a number of new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Fixed out-of-memory problem when writing some large TIFF images in Windows +
+ +July 4, 2018 - Version 11.04 +
    +
  • Added a check on TIFF image data size when writing or using Validate option +
  • Added a few new Sony lenses (thanks LibRaw) +
  • Added a new Nikon LensType +
  • Improved validation of XMP with Validate option +
  • Drop PhaseOne tags larger than 8 kB when copying PhaseOne maker notes to + another file +
  • Fixed out-of-memory problem when reading some large TIFF images in Windows +
+ +June 21, 2018 - Version 11.03 +
    +
  • Added support for new Exif 2.31 for XMP tags +
  • Added support for another FujiFilm X-T1 firmware version +
  • Decode more Panasonic tags (thanks Klaus Homeister) +
+ +June 13, 2018 - Version 11.02 +
    +
  • Added support for a different format of Apple iWorks files +
  • Added undocumented API FixCorruptedMOV option to allow fixing MOV videos + with multiple 'mdat' atoms which were corrupted by ExifTool +
  • Decode more QuickTime tags +
  • Decode more PanasonicRaw tags (thanks Klaus Homeister) +
  • Improved decoding of makernotes in ARW images from Hasselblad cameras + (thanks LibRaw) +
  • Fixed some problems writing multi-segment EXIF in JPEG images +
+ +June 11, 2018 - Version 11.01 (production release) +
    +
  • Added a new ProfileCMMType (thanks Neal Krawetz) +
  • Added a Validate warning about non-standard EXIF or XMP in PNG images +
  • Added a new Canon LensType +
  • Decode a couple more PanasonicRaw tags (thanks Klaus Homeister) +
  • Patched to avoid adding tags to QuickTime videos with multiple 'mdat' atoms + --> avoids potential corruption of these videos! +
+ +June 7, 2018 - Version 11.00 (production release) +
    +
  • Added read support for WTV and DVR-MS videos +
  • Added print conversions for some ASF date/time tags +
  • Added a new SonyModelID (thanks LibRaw) +
  • Decode a new PanasonicRaw tag (thanks Klaus Homeister) +
  • Decode some new Sony RX100 VI tags (thanks LibRaw and Jos Roost) +
  • Made Padding and OffsetSchema tags "unsafe" so they aren't copied by default +
+ +May 29, 2018 - Version 10.99 +
    +
  • Decode layer information from Photoshop ImageSourceData in TIFF images +
  • Updated to the IPTC video metadata 1.2 specification +
  • Patched DateFmt() utility function to apply GlobalTimeShift if used +
  • Improved error message when trying to write a file with a wrong extension +
  • Fixed unnecessary warning when setting FileCreateDate in Windows +
+ +May 22, 2018 - Version 10.98 +
    +
  • Added additional Validate checks for JPEG thumbnail tags +
  • Added a new Canon LensType (thanks LibRaw) +
  • Decode a number of new Nikon ColorBalance tags (thanks LibRaw) +
  • Disable extraction of Nikon D850 PhotoShootingMenuBank from NEF images + (apparently not valid in this type of file) +
  • Fixed problem with writable user-defined Composite tags introduced in 10.16 +
  • Fixed unnecessary Validate warning about missing GPSVersionID +
  • Fixed incorrect "wrong IFD" Validate warnings in CR3 images +
+ +May 17, 2018 - Version 10.97 - "Multi-segment EXIF" +
    +
  • Added read/write support for multi-segment EXIF in JPEG images +
  • Added a number of new Canon LensType values (thanks LibRaw) +
  • Added support for Panasonic DC-FT7 makernotes and metadata in MP4 videos +
  • Decode a number of new Nikon WB tags (thanks LibRaw) +
  • Improved warning message when attempting to write to an invalid tag name +
  • Enhanced Validate feature to perform more tests on TIFF and JPEG images
    + [The Validate feature is no longer considered experimental] +
+ +May 9, 2018 - Version 10.96 +
    +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added a few new Panasonic lenses (thanks LibRaw) +
  • Added Composite tags for GPSDestLatitudeRef and GPSDestLongitudeRef +
  • Decode maker notes from Kodak PixPro AZ901 +
  • Extract Preview images from iWork files +
  • Improved identification of Apple iWork files +
  • Fixed arg_files to handle GPS destination reference directions +
+ +May 4, 2018 - Version 10.95 +
    +
  • Added new Nikon LensID's (thanks Warren Hatch, LibRaw and Jami Bradley) +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Decode a new Samsung tag (thanks LibRaw) +
  • Decode Photoshop Lr16 layer information +
  • Decode more Leica tags (thanks LibRaw) +
  • Updated DarwinCore tags to current specification +
  • Improved validation of JPEG files +
  • Disabled writing of buggy Samsung EK-GN120 SRW files +
  • Fixed conversion for Nikon D850 ExposureDelayMode (thanks Jami Bradley) +
  • Fixed "x outside string" error when reading a truncated zip file +
  • Fixed "uninitialized value" error when writing a corrupted JPEG image +
+ +Apr. 19, 2018 - Version 10.94 +
    +
  • Added read/write support for Canon CRM files +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added a new CanonModelID +
  • Decode a new Samsung tag (thanks LibRaw) +
  • Fixed AIColorModel conversion +
+ +Apr. 13, 2018 - Version 10.93 +
    +
  • Added a new Canon Quality value (thanks Norbert Wasser) +
  • Added a new Pentax Quality value (thanks LibRaw) +
  • Decode some new Sony ARW tags (thanks Jos Roost) +
  • Decode some AI-specific tags from PDF and PostScript files +
  • Decode a new QuickTime tag +
  • Enhanced -geotag option to support NMEA from GLONASS and other systems +
  • Fixed bug decoding seconds of ZIP file timestamps (thanks Lars Wallenborn) +
+ +Apr. 19, 2018 - Version 10.92 +
    +
  • Decode GPS from videos of more camera models +
  • Tolerate white space before header in PDF files +
+ +Apr. 9, 2018 - Version 10.91 +
    +
  • Added read/write support for MacOS FileCreateDate (writing this is the same + as MDItemFSCreationDate, but reading uses a different mechanism which + doesn't have the delayed-update issue of MDItemFSCreationDate) +
  • Added ability to write MacOS MDItemUserTags (requires "tag" utility) +
  • Decode a new Sony tag (thanks LibRaw) +
  • Properly un-escape quotes in extracted MacOS MDItem values +
  • Fixed another subtle order-of-operations anomaly +
  • API Changes: +
      +
    • Enhanced GetValue() to accept a tag name with group prefix(es) +
    +
+ +Apr. 5, 2018 - Version 10.90 +
    +
  • Improved decoding of Sony PictureProfile (thanks Jos Roost) +
  • Fixed problem introduced in 10.61 with order of command-line operations when + mixing copied values with assigned values +
+ +Apr. 2, 2018 - Version 10.89 +
    +
  • Added ability to rotate MP4/MOV videos by writing Rotation angle +
  • Added two new Sony PictureProfile values (thanks Albert Shan) +
  • Decode more Nikon tags +
  • Updated some values of the QuickTime MediaType tag +
+ +Mar. 27, 2018 - Version 10.88 +
    +
  • Added example config file (dji.config) to generate Composite GPS and other + tags from Text metadata embedded in DJI drone videos +
  • Added a new PentaxModelID +
  • Documented new advanced-formatting "@" feature which has existed since + version 10.53 but not fully functional until 10.87 +
  • Decode a new Sony tag (thanks LibRaw) +
  • User-defined Composite tags now show up in the -list output +
  • Improved speed for generating Composite SubDoc tags with -ee option +
  • Fixed problem creating writable UserDefined Composite tags +
  • Fixed an incorrect PRISM tag name +
+ +Mar. 20, 2018 - Version 10.87 +
    +
  • Added a new FujiFilm ShutterType value (thanks Albert Shan) +
  • Decode more timed metadata from CR3 images +
  • Decode Samsung DualShot depth map +
  • Decode a new Canon tag +
  • Improved decoding of some Canon color information (thanks LibRaw) +
  • Fixed print conversion of ID3v2 Genre values with multiple genres +
+ +Mar. 15, 2018 - Version 10.86 +
    +
  • Decode timed GPS and accelerometer data from BikeBro AVI videos +
  • Decode a new Sony tag and add a new value to another tag (thanks Jos Roost) +
  • Decode GPSAltitude from some videos with the -ee option +
  • Decode some new tags from Kodak PixPro 4KVR360 JPEG images and MP4 videos +
  • Decode GPS information from CR3 images +
  • Fixed unnecessary warnings when Validate option used on CR3 images +
+ +Mar. 14, 2018 - Version 10.85 +
    +
  • Decode GPSTrack from MOV videos of more dashcam models +
  • Decode a few new CanonVRD tags for DPP 4.8.20 +
  • Decode a new Sony tag (thanks Jos Roost) +
  • Decode a few more Canon tags (thanks LibRaw) +
  • Improved processing speed when using the -ee option on some video files +
  • Fixed incorrect names for a couple of CanonVRD HSL tags +
  • Fixed problem where UTF-8 validation missed some invalid sequences +
  • Fixed bug introduced in 10.84 with GPSPosition when -n option is used +
+ +Mar. 12, 2018 - Version 10.84 +
    +
  • Decode GPS from MOV videos of more dashcam models with -ee option +
  • Decode a new Sony tag (thanks Jos Roost) +
  • Convert GPS speeds extracted from MOV videos with the -ee option to km/h +
  • Avoid converting empty GPS coordinates to 0.000000 +
  • Fixed some bugs extracting Novatek GPS from MP4 videos +
+ +Mar. 7, 2018 - Version 10.83 +
    +
  • Added read support for Sketch design files +
  • Added Light LRI files to the list of recognized file types +
  • Added a new Canon LensType (thanks LibRaw) +
  • Decode a couple of new Sony tags (thanks Jos Roost) +
  • Extract JpgFromRaw image from CR3 images +
  • Improved warning message when attempting to write a tag in a specific group + that isn't writable +
  • Changed group name of JSON tags from "File" to "JSON" +
  • Fixed some incorrect offsets in -v3 output for CR3 images +
+ +Mar. 1, 2018 - Version 10.82 +
    +
  • Added support for Canon's new CR3 raw file format +
  • Added a few new CanonModelID/SonyModelID values (thanks LibRaw) +
  • Added support for the Sony ILCE-7M3 (thanks Jos Roost) +
  • Decode timed GPS information from Insta360 MP4 videos with the -ee option +
  • Write XMP before mdat in MOV/MP4/CR3 files if possible +
  • Fixed "'x' outside of string" runtime error when reading some Sony images +
  • Fixed problem with some hex dumps going to the console when -v3 was combined + with the -w option +
+ +Feb. 26, 2018 - Version 10.81 +
    +
  • Added new values for a few Panasonic tags (thanks Bernd-Michael Kemper) +
  • Added a new Canon, Olympus and Sony LensTypes (thanks LibRaw) +
  • Added a new PanasonicRaw Compression type (thanks LibRaw) +
  • Added definitions for a number of new MacOS tags +
  • Decode CameraInfo for Canon 5DmkIII firmware 1.3.5 +
  • Removed INX from list of writable files (-listwf option output) +
  • Fixed problem introduced in version 10.16 that could cause a "Can't create" + error when using the -o option to write certain types of files +
  • Fixed problem introduced in version 10.34 resulting in a "Can't delete all + meta information" error when writing .PS files +
+ +Feb. 22, 2018 - Version 10.80 (production release) +
    +
  • Added read/write support for Reconyx UltraFire maker notes +
  • Added a new Sony/Minolta lens (thanks Jos Roost) +
  • Decode a new PanasonicRaw tag (thanks LibRaw) +
  • Extract ImageWidth/Height for main image of an HEIC file +
  • Internal changes: +
      +
    • Changed TimeNow() make ExifTool object optional +
    +
+ +Feb. 11, 2018 - Version 10.79 +
    +
  • Added a new Olympus CameraType (thanks LibRaw) +
  • Added a new XMP-microsoft tag (thanks José Oliver-Didier) +
  • Decode a new GoPro QuickTime tag +
  • Convert nulls IPTC:DocumentHistory to newlines +
  • Removed all null terminators from JSON and PHP output +
  • Fixed writing of GPSDateStamp and GPSTimeStamp to be able to set to "now" +
  • Internal changes: +
      +
    • Changed TimeNow() to require ExifTool object as first argument +
    +
+ +Jan. 31, 2018 - Version 10.78 +
    +
  • Added a few new values for some Olympus tags (thanks John) +
  • Decode GoPro APP6 metadata in JPEG images and more GoPro MP4 tags +
  • Decode more Red tags, and improved decoding of others +
  • Decode face detection information from timed metadata with the -ee option +
  • Fixed problem writing shorthand XMP containing CDATA sections +
  • Fixed problem copying XMP-acdsee:Snapshots +
  • Fixed decoding of a few Panasonic RAW tags (thanks Klaus Homeister) +
+ +Jan. 26, 2018 - Version 10.77 +
    +
  • Added read support for Redcode R3D RAW videos +
  • Enhanced -sep option to specify separator and terminator for binary output +
  • Removed null terminator from JSON output of ICC_Profile:CharTarget +
  • Improved error messages to help diagnosing some types of corrupted files +
  • Return an exit status of 2 instead of 1 if all files fail the -if condition +
  • Fixed decoding of QuickTime chapter names +
  • Fixed incorrect MimeType for RMD files +
  • Fixed problem where exit status of 1 was returned when writing with a -if + condition if any of the files failed the condition +
+ +Jan. 22, 2018 - Version 10.76 +
    +
  • Added ability to write shorthand XMP with the -z option +
  • Added write support for Google XMP GFocus tags +
  • Improved decoding of GoPro timed metadata +
  • Renamed ASF PlayDuration to Duration +
  • Fixed problem where fractional seconds were ignored when geotagging from an + NMEA track log with no date stamps +
  • Fixed runtime warning when reading XMP with an empty structure in a list +
  • API Changes: + +
+ +Jan. 12, 2018 - Version 10.75 +
    +
  • Added another Sony/Minolta lens (thanks Jos Roost) +
  • Decode more QuickTime tags +
  • Decode a number of new tags from GoPro Hero6 MP4 videos +
  • Enhanded "Unknown file type" error to indicate if "File is empty" or "File + header is all binary zeros" +
  • Improved decoding of some Sony tags (thanks Jos Roost) +
  • Improved decoding of QuickTime timed metadata +
  • Marked ArtworkCircaDateCreated as "unsafe" for writing to avoid it being + added when attempting to shift all date/time tags +
  • Fixed bug which could cause runtime error when reading some old Sony maker + notes (thanks Tamas Lovag) +
+ +Jan. 8, 2018 - Version 10.74 +
    +
  • Added a new Sony/Minolta lens (thanks Jos Roost) +
  • Added print conversion for ICC_Profile DeviceManufacturer and ProfileCreator +
  • Added informational warning when the ExtractEmbedded option may be useful +
  • Improved experimental Validate feature for RAW files +
  • Fixed bug in experimental Validate feature that could cause out-of-memory + error when combined with "-use mwg" +
+ +Jan. 5, 2018 - Version 10.73 +
    +
  • Added read/write support for GoPro RAW (GPR) files +
  • Added a new Sony/Minolta lens (thanks Sylvain) +
  • Improved conversions for GPS tags extracted from video streams +
+ +Jan. 4, 2018 - Version 10.72 +
    +
  • Added IF feature to -p option +
  • Decode streamed GPS position and other streamed metadata from MP4 videos + when the -ee option is used +
  • Fixed problem geotagging GPSAltitude from some GPX files +
+ +Jan. 2, 2018 - Version 10.71 +
    +
  • Decode some more ICC_Profile tags (thanks Eef Vreeland) +
  • Decode MechanicalShutterCount for Nikon D850 (thanks Xavier Jubier) +
  • Convert PNG:CreationTime values to/from standard date format +
  • Fixed problem loading default config file from application directory +
  • Fixed problem reading XMP where a namespace is defined after an attribute + which uses the namespace +
+ +Dec. 27, 2017 - Version 10.70 +
    +
  • Search application directory for -config file +
  • Improved robustness of JSON import +
  • Enhancements to experimental Validate feature +
  • Fixed bug introduced in 10.69 which could result in hang when writing + multi-segment JPEG metadata +
+ +Dec. 18, 2017 - Version 10.69 +
    +
  • Added "OK" UserParam for use in -if conditions +
  • Allow writing an empty JPEG Comment +
  • Check for proper location of Photoshop metadata with -validate or -use mwg +
  • Exit status now set to 1 if command was aborted due to invalid arguments +
  • Translate "UTF8" to appropriate escape sequence when writing + IPTC:CodedCharacterSet with the -n option +
  • Improved "Not a valid TIFF" error message to be more specific about the file + type for TIFF-based formats +
  • Fixed problem parsing Honeywell PTNTHPR NMEA sentences from some GPS devices +
+ +Dec. 5, 2017 - Version 10.68 +
    +
  • Added ability to set tag values and API options to an empty string using + "^=" on the command line +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added a new Nikon LensID +
  • Decode more tags from some newer Leica models +
  • Decode a new Apple tag +
  • Fixed indeterminate order of extracted XMP structures +
+ +Nov. 16, 2017 - Version 10.67 +
    +
  • Fixed problem introduced in 10.66 with -execute not returning the command + number in the "{ready}" message when -stay_open was used +
  • API Changes: + +
+ +Nov. 14, 2017 - Version 10.66 +
    +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Updated en-ca and en-gb language translations +
  • Minor format change to experimental Validate feature return value +
  • Prevent JFIF from being added to a JPEG containing Adobe APP14 +
  • Changed a number of Canon LensType strings to add "USM" to L model names +
  • Patched for compatibility with Time::Piece version 1.29_04 and later, and + improved error handling when writing formatted date/time values +
  • Fixed bug in Composite MWG CreateDate and DateTimeOriginal tags which could + cause existing tags to be hidden when not using the -a option +
  • Fixed problem using '#' suffix not properly fixed in 10.65 +
  • Fixed problem decoding Nikon D810 MultiExposure tags +
+ +Oct. 31, 2017 - Version 10.65 +
    +
  • Added support for DOSLatinUS (cp437) and DOSLatin1 (cp850) character sets +
  • Added Extra ForceWrite tag for forcing metadata in a file to be rewritten +
  • Added write support for RAF images from some newer FujiFilm models +
  • Added a new SonyModelID, Sony LensType and Olympus LensTypes (thanks LibRaw) +
  • Added a new Pentax LensType +
  • Added a new Nikon LensID +
  • Decode more Sony tags for new models (thanks Jos Roost) +
  • Decode Nikon D850 ShotInfo tags and custom settings (thanks Warren Hatch) +
  • Decode Nikon D850 ColorTemperatureAuto (thanks LibRaw) +
  • Decode Photoshop LayerModifyDates and LayerIDs +
  • Improved decoding of Nikon D5/D500/D810 ShotInfo tags +
  • Allow advanced formatting expressions to return a list reference +
  • Fixed problem in Composite:GPSAltitude when derived from an "undef" altitude +
  • Fixed bug which could result in runtime warning when excluding some tags + from being extracted +
+ +Oct. 17, 2017 - Version 10.64 +
    +
  • Added a new Nikon LensID +
  • Added a new SonyModelID +
  • Added a new CanonModelID (thanks LibRaw) +
  • Added some new non-standard CustomRendered values +
  • Decode FrameRate from FLIR SEQ files (thanks Sebastian Häni) +
  • Enhanced shift feature to be able to shift some not-so-simple numerical + values (eg. GPSLatitude) with -TAG+=VALUE syntax +
  • Fixed problem with possible malformed UTF-8 when writing IPTC values that + require truncation +
  • Fixed incorrect Writable type for XMP-GSpherical:TimeStamp +
  • Fixed incorrect family 2 group name for Nikon HDRInfo tags +
+ +Oct. 4, 2017 - Version 10.63 +
    +
  • Added a number of new Canon LensTypes (thanks Norbert Wasser for some) +
  • Added a new CanonModelID +
  • Added a new Olympus CameraType +
  • Decode MD5Signature in FLAC StreamInfo (thanks Tim Eliseo) +
  • Improved decoding of HEIC/HEIF metadata +
  • Removed useless write support for QuickTime date/time tags in HEIC/HEIF + images +
  • Fixed "Incorrect XMP stream length" problem when writing some INDD files +
+ +Sept. 28, 2017 - Version 10.62 +
    +
  • Added preliminary support for HEIC/HEIF images +
  • Added support for Google depthmap metadata (XMP-GDepth) +
  • Added some new Sony/Minolta lenses (thanks LibRaw and Jos Roost) +
  • Added a new CanonModelID (thanks Norbert Wasser) +
  • Added a new Nikon LensID (thanks Michael Tapes) +
  • Decode a new Sony tag (thanks Jos Roost) +
  • Decode some new Nikon tags (thanks Warren Hatch) +
  • Decode maker notes from Leica TL2 +
  • Enhanced ID3 -v2 output to show frame flags +
  • Fixed problem decoding Nikon D810 camera tilt angles +
  • Fixed problem where SphericalVideoXML metadata was deleted when writing XMP + to a QuickTime-format file containing this information +
+ +Aug. 18, 2017 - Version 10.61 +
    +
  • Added a new Canon LensType (thanks LibRaw) +
  • Added a number of new Sigma, Nikon and Sony lenses (thanks Jos Roost) +
  • Added a new Nikon LensID (thanks Yang You) +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Decode Panasonic FocusDistance (thanks David Ellsworth) +
  • Updated to the IPTC video metadata 1.1 specification +
  • Restored the ability to delete JpgFromRaw from RAW images (broken in 10.38) +
  • Fixed problem decoding maker notes from Pentax K-70 AVI videos +
  • Fixed problem conditionally replacing a tag if the value to be deleted was + taken from another tag while the new value was assigned directly +
+ +July 21, 2017 - Version 10.60 +
    +
  • Added two new Sony/Minolta LensTypes (thanks Jos Roost) +
  • Added a new Pentax LensType (thanks Dieter Pearcey) +
  • Added new Composite UniquePathPoints tag to photoshop_paths.config +
  • Extract raw-data JFIF/JFXX thumbnails as ThumbnailTIFF +
  • Improved Sony LensSpec conversion +
  • Updated German translations (thanks Herbert Kauer) +
  • Set family 1 group name of JFXX ThumbnailImage to JFXX instead of JFIF +
  • Fixed problem with %C no longer incrementing properly +
+ +July 7, 2017 - Version 10.59 +
    +
  • Added a new Canon LensType (thanks LibRaw) +
  • Added a new Nikon LensID +
  • Added "wrong extension" warning to experimental Validate feature +
  • Decode Pentax maker notes in Q-S1 AVI videos +
  • Updated iptc2exif.args and exif2iptc.args to support new EXIF OffsetTime + tags (thanks Herb) +
  • Patched potential problem with "Use of uninitialized value $pos" error when + importing malformed JSON data +
  • Patched to avoid runtime warning due to invalid Nikon ShutterCount value +
  • Raise an error if -b is used with the -csv option +
  • Changed PNG exIf chunk name to eXIf +
  • Fixed bug introduced in 10.26 which could cause hang when %C is used in an + output file name +
  • Fixed MWG:DateTimeOriginal and MWG:CreateDate to return XMP when EXIF and + IPTC don't exist +
+ +June 29, 2017 - Version 10.58 +
    +
  • Added read support for RIFF-format MBWF/RF64 files +
  • Added write support for dji-drone XMP tags +
  • Added a new Canon LensType (thanks Steve Bates) +
  • Added a few new Sony/Minolta LensType values (thanks Jos Roost) +
  • Added a couple of new CanonModelID values (thanks LibRaw and Norbert Wasser) +
  • Decode some new FujiFilm tags (thanks Chris Schucker) +
  • Enhanced FileSize print conversion to show in "GB" for large files +
  • Fixed "outside of string in unpack" errors when reading some corrupted + EXE/ICC files +
  • Fixed problem extracting GIF MIDISong metadata +
+ +June 20, 2017 - Version 10.57 +
    +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Added write support for PNG Collection tag +
  • Added a few new CanonModelID values +
  • Added some new Pentax ShakeReduction values +
  • Removed ability to create PNG zxIf chunks +
  • Documented -progress:%b feature (added in 10.26) +
  • No longer report FileType, FileTypeExtension or MIMEType for JPEG/TIFF + images with an unknown header +
  • Relaxed case requirement for "SourceFile" header in CSV and JSON import +
  • Fixed decoding of Pentax ExposureCompensation for newer Ricoh models +
  • Fixed some incorrect "Wrong IFD" messages with experimental Validate feature +
  • Fixed a couple of Sony/Minolta lens names (thanks Jos Roost) +
  • Fixed "Error reading PreviewImage" warning for some Sony models +
+ +June 6, 2017 - Version 10.56 +
    +
  • Removed ordering constraints between Geotag/Geosync and Geotime assignments + on the command line +
  • Removed debugging print statement left in photoshop_paths.config +
+ +June 5, 2017 - Version 10.55 (production release) +
    +
  • Added support for GIF multimedia extensions +
  • Added a couple of new Sony/Minolta lenses (thanks Chris) +
  • Added a new Nikon LensID (thanks Jakob Dettner) +
  • Added new Composite TotalPathPoints tag to photoshop_paths.config +
  • Decode a number of new Sony tags and updated some others (thanks Jos Roost) +
  • Decode a new Pentax tag and fixed decoding of another (thanks Andras + Salamon) +
  • Updated iptcCore.args for new IPTC specification +
  • Changed description of a couple of AVI Model tags to match EXIF +
  • Patched tests to avoid failures with Perl 5.25.11 due to missing "." in @INC +
  • Fixed an incorrect warning from the experimental Validate feature +
+ +May 26, 2017 - Version 10.54 +
    +
  • Added support for Google XMP GImage and GAudio tags +
  • Added a new Olympus CameraType (thanks LibRaw) +
  • Added a two new Sony lenses and decode more ILCE-9 tags (thanks Jos Roost) +
  • Added new values to some Pentax tags (thanks Andras Salamon) +
  • Added a new Canon LensType +
  • Added an additional checks to the experimental Validate feature +
  • Improved user-defined FileTypes feature to provide more flexibility +
  • Enhanced -ext option to allow specific files extensions to be processed + along with supported files +
  • API Changes: + +
+ +May 17, 2017 - Version 10.53 +
    +
  • Added support for "MeSa" Photoshop IRB resource +
  • Made XMP-GSpherical tags writable +
  • Improved German translations (thanks Jobi) +
+ +May 12, 2017 - Version 10.52 +
    +
  • Added some new values to a number of FujiFilm tags and changed some others + (thanks Albert Shan) +
  • Decode a number of new Sony tags for the ILCE-9 (thanks Jos Roost) +
  • Made SonyISO writable +
  • Changed behaviour of advanced formatting expression for Shortcut tags so it + now applies to the combined value rather than individual constituent values +
  • Minor changes to some Pentax print conversions +
  • Fixed problem using new NoDups utility with Shortcut tags +
+ +May 2, 2017 - Version 10.51 +
    +
  • Added "NoDups" utility function for use in advanced formatting expressions +
  • Added a new Pentax LensType (thanks JohnK) +
  • Added some new Pentax DriveMode values (thanks Andras Salamon) +
  • Enhanced -ver option to report Perl include directories with -v2 +
  • Improved warning message when advanced formatting expression returns undef +
  • Minor change to a few FujiFilm print conversion strings (thanks Albert Shan) +
  • Changed behaviour when interpolating Shortcut tags in a string (the values + are now separated according to the -sep option setting instead of simply + being concatenated) +
  • Patched to allow file times to be set on systems where futimes is not + available +
+ +Apr. 20, 2017 - Version 10.50 (production release) +
    +
  • Decode a new Pentax tag (thanks Andras Salamon) +
  • Improved decoding of Olympus DriveMode (thanks Herbert Kauer) +
  • Improved handling of errors from utime when setting file times +
  • Fixed potential hang problem when reading corrupted QuickTime metadata +
  • Fixed problem deleting duplicate EXIF tags when writing other tags at the + same time +
+ +Apr. 10, 2017 - Version 10.49 +
    +
  • Added "DateFmt" utility function for use in advanced formatting expressions +
  • Added a new Sony/Minolta LensType (thanks LibRaw) +
  • Decode a new Panasonic tag +
  • Fixed problem decoding Sony VariableLowPassFilter values (thanks Jos Roost) +
  • Fixed problem setting XMP:About when creating new XMP in a file +
  • Fixed an incorrect Pentax DriveMode value (thanks Andras Salamon) +
  • API Changes +
      +
    • Allow access to the advanced formatting expression via a new ExifTool + "FMT_EXPR" member variable +
    +
+ +Apr. 3, 2017 - Version 10.48 +
    +
  • Added some new FujiFlashMode values (thanks Albert Shan) +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added a new Canon LensType (thanks LibRaw) +
  • Added a new CanonModelID and minor changes to some others (thanks Dmitry) +
  • Decode two Pentax tags and added a number of new values for other Pentax + tags (thanks Andras Salamon) +
  • Decode a new Sony tag (thanks Jos Roost) +
  • Improvements to the experimental Validate feature +
  • Fixed problem which could cause hang when reading bad PPT documents +
+ +Mar. 20, 2017 - Version 10.47 +
    +
  • Added read support for JSON-format files +
  • Added two new Sony/Minolta lenses (thanks Jos Roost) +
  • Added a number of new Pentax tag values +
  • Decode a new Canon CR2 tag (thanks Ed Hannon) +
  • Decode WB information for Canon 800D (thanks LibRaw) +
  • Improved config_files/photoshop_paths.config to indicate start of paths +
  • Attempt to validate new file names in Windows before renaming images +
  • Experimental Validate feature no longer warns about Windows XP tags +
  • Fixed problem extracting layer information from very large PSD/PSB files +
+ +Mar. 8, 2017 - Version 10.46 +
    +
  • Moved Mac System tags from the Extra table into a new MacOS group and added + ability to extract them by requesting the MacOS group +
  • Updated QuickTime GenreID list (thanks François Bonzon) +
  • Fixed "Invalid xref" problem when reading some PDF files +
  • Fixed error in Minolta lens list (thanks Jos Roost) +
  • Fixed minor problem with -U option generating Unknown tags for some known + bytes in variable-sized strings +
  • API Changes: +
      +
    • Enhanced RequestTags option to allow groups to be requested +
    +
+ +Mar. 2, 2017 - Version 10.45 +
    +
  • Added ability to write a number of Mac OS X system tags (including the file + creation date!) +
  • Added ability to extract OS X extended attributes ("XAttr" tags) +
  • API Changes: + +
+ +Feb. 24, 2017 - Version 10.44 +
    +
  • Added a few new CanonModelID values and a new Canon LensType +
  • Added two new Nikon lenses (thanks Rolf Probst) +
  • Added a few new Sony/Minolta lenses (thanks Jos Roost) +
  • Added two new Sony MeteringMode values (thanks Jos Roost) +
  • Improved verbose dump of Photoshop Layer information +
  • Patched to allow "FileName encoding not specified" warnings to be avoided by + setting -charset filename="" +
  • Fixed problem in photoshop_paths.config printing some paths +
+ +Feb. 16, 2017 - Version 10.43 +
    +
  • Restrict writing of EXIF:FlashEnergy to a single value as per EXIF spec +
  • Reverted format change of Sony ImageCount tag +
  • Changed PNG new eXIF/zXIF chunk names to "exIf" and "zxIf" until the + proposed chunks are accepted (of course, while maintaining backward + compatibility for reading/updating the other chunks) +
  • Lowered priority of XMP-pdf:Keywords so it doesn't take precedence over + PDF:Keywords when the Duplicates option is not used +
  • Improved config_files/convert_regions.config to handle the case where the + RegionInfoMP is missing a Rectangle +
+ +Feb. 10, 2017 - Version 10.42 +
    +
  • Added ability to read/write PNG eXIF and zXIF chunks, and made these the + place where new EXIF is created in PNG images (zXIF if the -z option is + used, or eXIF otherwise) +
  • Added ability to copy Photoshop OriginPathInfo with photoshop_paths.config +
  • Made FileUserID and FileGroupID writable +
  • Changed format for a Sony ImageCount tag +
  • Improvements to experimental Validate feature +
  • Fixed incorrect XMP swf namespace URI +
  • Fixed problem using new -p section feature when combined with -w or -ee +
  • Fixed formatting problem in -listx output when -lang option was used +
  • Fixed problem where UserComment wasn't removed if found in IFD0 when writing + it to the correct IFD +
+ +Feb. 1, 2017 - Version 10.41 +
    +
  • Added an experimental metadata validation feature (invoked either by + requesting the new Extra Validate tag or by setting the API Validate option) +
  • Added support for PSDT file extension +
  • Added age.config to the distribution +
  • Added a new Sony lens (thanks Jos Roost) +
  • Added a new PentaxModelID (thanks Louis Granboulan) +
  • Enhanced -p option to allow files to be grouped in sections +
  • Made makernote offset warning minor +
  • Relaxed parsing of NMEA GGA sentence so comma after the geoid units is now + optional +
  • Patched problem extracting value of an unsafe binary tag with the -b option + when specified using -TAG# instead of -TAG with -n +
  • API Changes: +
      +
    • Added experimental Validate option +
    +
+ +Jan. 14, 2017 - Version 10.40 (production release) +
    +
  • Fixed tests that were failing on some platforms +
+ +Jan. 13, 2017 - Version 10.39 +
    +
  • Added Perl version and Unicode settings to -ver -v output +
  • Added a new Sony LensType2 value +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • More improvements to sample time_zone.config file (thanks Hayo Baan) +
  • Fixed problem with MWG date/time tags introduced in version 10.34 +
  • Fixed problem setting the value of a tag from a binary file when the + PERL_UNICODE environment or the perl -C option is used to force UTF-8 + treatment of @ARGV elements +
+ +Jan. 5, 2017 - Version 10.38 +
    +
  • Added a couple of new XMP-ics tags +
  • Added a new Nikon LensID (thanks Ken Cochran) +
  • Decode a couple more PhaseOne tags +
  • Increased priority of Sony 0x0115 WhiteBalance when reading +
  • Range check QuickTime date/time values when writing +
  • Apply CharsetPhotoshop setting to decoding of Photoshop LayerNames +
  • Improved identification of Nikon NRW images +
  • Minor improvements to verbose dump of FLIR information +
  • Improvements to sample time_zone.config file (thanks Hayo Baan) +
  • Removed trailing null in -b output for GPSDateStamp +
  • Changed "TAG is not supported" warning when writing to "TAG is not defined" +
  • Changed groups of Composite Preview/Thumbnail/JpgFromRaw/etc images to match + the tags from which they are derived +
  • Changed description of Composite Nikon LensSpec tag to match the tag name +
  • Fixed problems reading/writing PreviewImage from some DNG files +
+ +Dec. 19, 2016 - Version 10.37 +
    +
  • Decode more information from BMP V4 and V5 images +
  • Added a few new FujiFlashMode values (thanks Albert Shan) +
  • Changed -geotime default to use unconverted value of DateTimeOriginal +
  • Changed a couple of Sony Voigtlander LensType strings (thanks Jos Roost) +
  • Warn about invalid tag names used on the command line +
  • Generate default-language version of QuickTime tags even if the same-named + tag already exists in another group +
  • Fixed bug reading some Photoshop layer information +
  • Fixed problems in sample config file time_zone.config (thanks Hayo Baan) +
+ +Nov. 24, 2016 - Version 10.36 (production release) +
    +
  • Added 3D Studio MAX files to the list of supported file types +
  • Decode more Sony tags (thanks Jos Roost) +
  • Decode a couple more FlashPix tags +
  • Minor changes to some of the new IPTC Extension tags +
  • Fixed problem reading some FlashPix (Windows Compound Binary Format) files +
+ +Nov. 21, 2016 - Version 10.35 +
    +
  • Fixed bug in Windows version introduced in 10.32 which could cause ExifTool + to exit with an error if the -lang option was used +
+ +Nov. 21, 2016 - Version 10.34 +
    +
  • Added support for new IPTC Extension version 1.3 + video metadata XMP tags +
  • Added missing print conversion for PreviewDateTime +
  • Decode a few new FujiFilm tags (thanks Zilvinas Brobliauskas) +
  • Enhanced MWG date/time tags to support new EXIF time offsets +
  • Patched loophole in WriteMode which would allow creation of new metadata + files when creation of new groups was disallowed +
  • Fixed problem where some EXIF date/time tags may not shifted when shifting + all date/time tags with "-time:all-=VAL" for ExifTool version 10.28-10.33 or + when the MWG feature was used +
+ +Nov. 11, 2016 - Version 10.33 +
    +
  • Windows EXE version is 32-bit again, and packaged with Perl 5.24.0 +
  • Fixed encoding problem with EXIF:Copyright when writing MWG tags using an + alternate EXIF charset +
+ +Nov. 9, 2016 - Version 10.32 +
    +
  • WARNING: The Windows EXE version for this release is 64-bit (and packaged + with Perl 5.22.2 instead of 5.24.0) +
  • Time::Piece may now be used as an alternative to POSIX::strptime for parsing + date/time values when writing, and is included in the Windows package +
  • Added a number of new XMP tags (thanks StarGeek) +
  • Added support for a few new Sony cameras (thanks Jos Roost) +
  • Added new Nikon LensID (thanks Tanel) +
  • Decode a new Nikon tag (thanks Warren Hatch) +
  • Decode FLIF encoding type +
  • Decode a new Samsung tag (thanks Klaus Homeister) +
  • Ignore -filter option for a tag if it returns an undefined value +
+ +Oct. 19, 2016 - Version 10.31 +
    +
  • Added write support for FLIF images +
  • Added support for animated PNG images +
  • Added a few new SamsungModelID values +
  • Added a new Canon LensType +
  • Added a new Sony/Minolta LensType (thanks Jos Roost) +
  • Decode more Samsung tags (thanks Klaus Homeister and Sreerag Raghavan) +
  • Decode more Nikon tags (thanks Warren Hatch) +
  • Changed "TAG does not exist" warning when writing to "TAG is not supported" +
  • Fixed problem importing information from CSV or JSON databases for files + with special characters in their name +
+ +Oct. 13, 2016 - Version 10.30 +
    +
  • Added read support for FLIF images +
  • Added a couple of new Minolta/Sony LensType values (thanks Jos Roost) +
  • Added a new SonyModelID (thanks LibRaw and Jos Roost) +
  • Added a new digiKam XMP tag +
  • Decode a new Apple tag (thanks Neal Krawetz) +
  • Decode a few new FujiFilm tags (thanks Chris Schucker) +
  • Decode more Nikon D5 custom settings (thanks Warren Hatch) +
  • Decode a couple more Samsung tags (thanks Klaus Homeister) +
  • Improved decoding of Nikon D500/D5 ShotInfo information +
  • Enhanced -ver option to output system information when -v is added +
  • Minor change to parsing of -@ argfile (comment lines may may no longer have + spaces before the "#") +
  • Patched Jpeg2000 reader to read bad UUID-EXIF boxes +
  • Lowered priority of unknown XMP tags when reading +
  • Fixed problem in new xmp2exif.args date/time arguments introduced in 10.28 +
  • Fixed potential "Use of uninitialized value" warning when decoding + compressed PNG iTXt chunk +
+ +Oct. 5, 2016 - Version 10.29 +
    +
  • Added a couple of new Sony LensType values (thanks LibRaw) +
  • Decode a few new Sony tags +
  • Decode a few new FLIR tags +
  • Decode some new Nikon D5 tags (thanks Warren Hatch) +
  • Decode a new Apple tag +
  • Enhanced -geotag option to allow tagging from KML placemarks with a TimeSpan +
  • Enhanced -d option (and API DateFormat option) to perform inverse date/time + conversion when writing if the POSIX::strptime module is available. If + POSIX::strptime is not available then the behaviour is like older versions + (ie. the date/time is not converted) unless the API StrictDate option is set + in which case a warning is issued and the tag is not written +
+ +Sept. 27, 2016 - Version 10.28 - "Exif 2.31" +
    +
  • Added support for new Exif 2.31 tags +
  • Added some new Canon LensType values (thanks Norbert Wasser for one) +
  • Added a new Olympus LensType (thanks LibRaw and Niels Kristian Bech Jensen) +
  • Added a new Sony LensType and SonyModelID (thanks Jos Roost) +
  • Added a new Pentax LensType +
  • Added fotoware.config and bibble.config files to the distribution +
  • Made Composite SubSecDateTimeOriginal, SubSecCreateDate and SubSecModifyDate + tags writable, and expanded to include new Exif 2.31 time zone tags +
  • Fixed problem writing user-defined structured tag elements with a dot (.) in + their tag ID +
+ +Sept. 23, 2016 - Version 10.27 +
    +
  • Added a new CanonModelID (thanks LibRaw) +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added a few new NikonLensID's (thanks Yang You and Robert Rottmerhusen) +
  • Added a couple of new Olympus LensType values (thanks LibRaw and Niels + Kristian Bech Jensen) +
  • Added a new Olympus CameraType +
  • Decode some Canon 80D, 750D, 760D and 1200D CameraInfo tags +
  • Changed writing of some ExposureTime and ExposureCompensation tags to allow + the exact numerator and denominator of the stored rational value to be + specified +
  • Fixed Timecode printout in -v3 output for M2TS videos (thanks Ken Neighbors) +
  • Fixed some problems with the new "-progress:TITLE" feature +
  • Fixed problem where "_exiftool_tmp" file could be left around after a failed + write attempt +
  • Fixed potential "isn't numeric" runtime error when reading a PDF file with + the -ee option +
+ +Sept. 15, 2016 - Version 10.26 +
    +
  • Added read support for GSpherical metadata in MP4 videos +
  • Added a few new XMP-xmpMM tags and a new XMP-crs tag +
  • Added some new Minolta/Sony lenses (thanks Jos Roost) +
  • Added two new CanonModelID's (thanks Norbert Wasser and Laurent Clévy) +
  • Added two new Canon LensType's (thanks Norbert Wasser) +
  • Decode a number of Nikon D610 custom settings (thanks Tor) +
  • Removed a questionable Samsung tag +
  • Marked TestName tag as "unsafe" for writing +
  • Enhanced -progress option with ability to set console window title +
  • Changed behaviour of %C to increment for each processed file as documented + (was incrementing for each output file created) +
  • Patched to recreate XMP in the standard location of PNG images when deleting + certain non-standard XMP as a group and recreating in one step +
  • Fixed runtime warning when writing 0 to MinoltaRaw ISOSetting +
  • Fixed problem writing SRW images from some newer Samsung models +
+ +Aug. 3, 2016 - Version 10.25 +
    +
  • Added a new Pentax PictureMode (thanks Louis Granboulan) +
  • Added a new Nikon LensID (thanks LibRaw) +
  • Decode a new Samsung tag (thanks LibRaw) +
  • Decode a few more Canon tags (thanks Anton Reiser) +
  • Removed "Avoid" flag for XMP-crs:ColorTemperature +
  • Changed the format of a number of XMP-GPano tags from integer to real +
  • Fixed incorrect tag ID's for some obscure Island Graphics EXIF tags +
  • Fixed decoding of some UTF-8 DNG tags which may be stored in BYTE format +
+ +July 27, 2016 - Version 10.24 +
    +
  • Added support for DJI Phantom maker notes +
  • Added a few more XMP-crs tags +
  • Added ability to write DNG OpcodeList tags +
  • Added a new Sony/Minolta LensType (thanks Jos Roost) +
  • Added a few new FujiFilm Saturation values +
  • Added a new FujiFlashMode value and fixed an incorrect Italian translation + (thanks Massimo Sanna, ApolloOne) +
  • Decode more Pentax tags (thanks Louis Granboulan) +
  • Changed -config option to search the current directory first for the config + file (patches problem introduced in ExifTool 10.21 for Windows where the + working directory might not be searched when using the -config option) +
  • Changed print conversion of ProcessingTime to show 3 significant digits +
  • Fixed bug decoding PanasonicRaw DistortionInfo in DNG images +
+ +July 14, 2016 - Version 10.23 +
    +
  • Added read support for Ogg Opus audio files +
  • Added ability to geotag only GPS date/time if no position information is + available by setting Geotag to "DATETIMEONLY" (all caps) +
  • Added "-charset RIFF" option +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Decode a number of new Canon tags (thanks Kai Harrekilde-Petersen) +
  • Changed handling of special characters in RIFF-format files (eg. AVI, WAV) +
  • Changed MIME type of OGG files to audio/ogg (was audio/x-ogg) +
  • Minor change to wording of new Nikon D80 Rotation tag for consistency +
  • API Changes: + +
+ +July 7, 2016 - Version 10.22 +
    +
  • Added read support for BPG images +
  • Minor changes to a few of the new Nikon tags +
  • Fixed problem in Windows version where not all 10.21 updates were included + in the release +
+ +June 29, 2016 - Version 10.21 +
    +
  • WARNING: The Windows EXE package for this release was built on Windows 10 + using Perl 5.24 instead of Windows XP and Perl 5.8 -- please watch for + problems and report anything that you find +
  • Added a new Minolta/Sony LensType (thanks LibRaw) +
  • Added a new element to the XMP Colorant structure +
  • Added a new Pentax lens (thanks Louis Granboulan) +
  • Decode Nikon D5/D500 AF information (thanks Michael Tapes for samples) +
  • Decode a number of new Olympus tags (thanks Daniel Pollock) +
  • Decode a number of new Nikon tags (thanks Warren Hatch) +
  • Decode Pentax K-1 AF points (thanks Louis Granboulan) +
  • Extract a new DPX tag +
  • Patched to avoid writing an empty structure field for an undefined value +
+ +June 13, 2016 - Version 10.20 (production release) +
    +
  • Added a few new Sigma LensTypes (thanks LibRaw and Jos Roost) +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added two new Canon LensTypes (thanks Jos Roost and Norbert Wasser) +
  • Added a couple of new PentaxModelID's (thanks Louis Granboulan for one) +
  • Added a new Pentax LensType (thanks Louis Granboulan) +
  • Added a few new Olympus PictureMode values (thanks Daniel Pollock) +
  • Added a few more XMP tags +
  • Decode a new Nikon video tag (thanks Hayo Baan) +
  • Patched to allow protected binary data tags to be extracted when -b is + combined with -php or -X if the tag is specifically requested +
  • Fixed bug introduced in version 9.96 where extended XMP is ignored if the + MWG module is used +
  • Fixed problem where the MWG module wasn't loaded automatically if there were + MWG tags in the argument to the -p option +
  • Fixed column alignment of alternate-language output (requires + Unicode::LineBreak to be installed) +
  • Fixed problem writing Sigma:LensFocalRange +
+ +May 31, 2016 - Version 10.19 +
    +
  • Added a few new Sony and Sigma LensType values (thanks Jos Roost) +
  • Decode more Nikon tags (thanks Warren Hatch) +
  • Fixed an incorrect Sigma LensType (thanks LibRaw) +
  • Fixed decoding of D500 custom settings for NEF images (thanks Warren Hatch) +
+ +May 27, 2016 - Version 10.18 +
    +
  • Added a number of new Sigma LensTypes (thanks LibRaw) +
  • Added a few new Sony/Minolta lenses (thanks Jos Roost) +
  • Added ability to write FilePermissions +
  • Decode NikonCustom settings for the D500 (thanks Warren Hatch) +
  • Decode PLUS MediaSummaryCode values +
  • Use hexadecimal for Sigma LensType values +
  • Changed -fileOrder option to sort numbers in strings numerically +
  • Fixed typo in Samsung lens name +
+ +May 16, 2016 - Version 10.17 +
    +
  • Added support for Leica X-U (Typ 113) maker notes +
  • Added a new Pentax LensType (thanks Louis Granboulan) +
  • Added a number of new Sony lenses (thanks Jos Roost) +
  • Added a new Canon LensType (thanks Mees Dekker) +
  • Extract TIFF-format thumbnails and previews +
  • Patched to ignore XML entities inside comments +
  • Fixed inconsistent conversion of PreviewColorSpace values +
  • Fixed writing of TargetPrinter tag +
  • Fixed bug introduced in 10.16 which which could cause a runtime warning when + using the -o option and not writing any "real" tags +
+ +May 3, 2016 - Version 10.16 +
    +
  • Added %D, %F and %E filename format codes +
  • Added a new Minolta lens (thanks Jos Roost) +
  • Decode Photoshop Compression mode +
  • Decode Nikon MultiExposure information for the D5 +
  • Updated decoding of Sony tags for ILCA-68 (thanks Jos Roost) +
  • Fixed bug adding back XMP tags after deleting all XMP from MOV/MP4 files +
  • Fixed problem using -o option when reading from stdin (ie. FILE is "-") +
  • Fixed problem where user-defined Composite tags may not always override + pre-defined Composite tags with the same name, and added feature to allow + the user to specify whether they should override existing tags or not +
+ +Apr. 20, 2016 - Version 10.15 (production release) +
    +
  • Added .a and .o to the list of supported file types +
  • Added a few new Sony/Minolta lenses (thanks Jos Roost and LibRaw) +
  • Decode more Photoshop tags (thanks Taylor Bangs for some) +
  • Decode more information from static library (.a) files +
  • Decode a few more tags from GoPro MP4 videos (thanks Calvin Hass) +
  • Decode ColorData for Canon EOS 1300D (thanks LibRaw) +
  • Updated Sony decoding for newer models (thanks Jos Roost) +
  • Fixed bug where ScaleFactor35efl could be calculated incorrectly for Canon + images from some models which have had their EXIF rebuilt +
+ +Apr. 8, 2016 - Version 10.14 +
    +
  • Added read support for ISO 9660 disk images +
  • Added a few new Nikon ISOExpansion values (thanks LibRaw) +
  • Added a few new Olympus LensType values (thanks Niels Kristian Bech Jensen) +
  • Added a couple of new SonyModelID values (thanks LibRaw for one) +
  • Added a new Olympus CameraType +
  • Added config_files/gps2utm.config to the distribution +
  • Decode Canon ColorData for the EOS 80D (thanks LibRaw) +
  • Decode a few new Samsung tags (thanks François) +
  • Decode a new Fuji tag (thanks Frank Markesteijn) +
  • Calculate Duration for APE audio files +
  • Tightened constraints on M2TS file recognition +
  • Improved verbose dump of ID3 information +
  • Changed XMP-acdsee:Snapshots to a Binary data tag +
  • Fixed bug which prevented writing of various Sony FocalLength tags +
  • API Changes: +
      +
    • Fixed bug where FileModifyDate wasn't set properly when WriteInfo() was + called without a destination file name and other "real" tags were + written at the same time +
    +
+ +Mar. 12, 2016 - Version 10.13 +
    +
  • Added a few new Canon LensType values (thanks Niels Kristian Bech Jensen and + LibRaw) +
  • Added a new CanonModelID +
  • Added a number of new Nikon RetouchHistory modes +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Changed a couple of Sigma "| C" lens names for consistency +
  • API Changes: +
      +
    • Fixed bug which could cause the API Filter option to be ignored for some + tags when copying tags with the Composite option set +
    +
+ +Mar. 4, 2016 - Version 10.12 +
    +
  • Added a new PentaxModelID and SonyModelID (thanks LibRaw) +
  • Added a number of new CanonModelID values (thanks Norbert Wasser for one) +
  • Added a new Olypus LensType (thanks Niels Kristian Bech Jensen) +
  • Added two new Pentax LensType values +
  • Added a few new Nikon LensID values and updated some others +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Decode a new Nikon tag +
  • Decode a new CanonCustom tag for the EOS 80D +
  • Improved decoding of SonyRawFileType (thanks Jos Roost and LibRaw) +
  • Changed "Optimised" to "Optimized" in a Photoshop tag value +
  • Fixed warning that could be generated by the Canon FileNumber conversion +
+ +Feb. 17, 2016 - Version 10.11 +
    +
  • Added a couple of new Olympus CameraType values (thanks LibRaw for one) +
  • Added some new ACDSee XMP tags (thanks Malus) +
  • Added a few more XMP-crs tags +
  • Added a new CanonModelID (thanks Norbert Wasser) +
  • Added a couple of new Sony LensType values (thanks Jos Roost and LibRaw) +
  • Added support for PDF ASCII85Decode filter +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Decode a new Canon tag (thanks Juha Iso-Sipilä) +
  • Decode a few more Photoshop tags +
  • Convert MDItem date/time values to local time +
  • Patched to read incorrectly written DJI GPSCoordinates in MOV videos +
+ +Jan. 22, 2016 - Version 10.10 (production release) +
    +
  • Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) +
  • Added a couple of new Olympus FlashModel values +
  • Added a new Nikon LensID +
  • Added a new Pentax LensType +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Decode H264:DateTimeOriginal DST flag, and add " DST" to time string if set +
  • Decode a few more CanonCustom settings +
  • Fixed problem creating user-defined XMP structure elements with names + containing characters which are illegal in tag names +
  • Improved mechanism for generating tags which must be specifically requested + when copying or used in -if or -p expressions +
  • Improved warning in Windows if help documentation file can't be created +
  • Patched Composite:FileNumber to handle case where Canon:FileNumber is 10000 +
  • Patched reading FujiFilm RawImageWidth/Height for new X-Pro2 RAF images +
  • Fixed problem reading PDF objects which begin with a comment line +
  • Fixed problem which could result in ExifTool corrupting a PDF file when + writing +
  • API Changes: +
      +
    • Added RequestTags option +
    • No longer generate MDItem tags when RequestAll option is set +
    +
+ +Jan. 4, 2016 - Version 10.09 +
    +
  • Added ability to extract OS X system metadata ("MDItem" tags) +
  • Added a value conversion for GoogleTrackDuration +
  • Enhanced the -i option to allow full path names to be specified +
  • Fixed a potential runtime error when writing corrupted JPEG images +
  • API Changes: + +
+ +Dec. 22, 2015 - Version 10.08 +
    +
  • Added ability to write empty XMP structures +
  • Added write support for PhaseOne MakerNotes tags in IIQ files +
  • Added a new Nikon LensID (thanks David Püschel) +
  • Decode a new Olympus tag and improved decoding of DriveMode +
  • Minor improvements to HtmlDump of PhaseOne IIQ and PDF files +
  • Patched to allow overwriting of empty XMP written by some PhaseOne cameras +
  • Fixed bug in HtmlDump feature that could cause a "substr outside of string" + error +
  • API Changes: +
      +
    • Changed API QuickTimeUTC option to also enforce proper time zero +
    +
+ +Nov. 26, 2015 - Version 10.07 +
    +
  • Fixed problem with warnings on some systems about unimplemented functions + for FileGroupID and FileUserID when -p or -if were used +
+ +Nov. 26, 2015 - Version 10.06 +
    +
  • Added a new Canon LensType (thanks LibRaw) +
  • Added a new Pentax LensType (thanks Louis Granboulan) +
  • Added a couple of new XMP-digiKam tags +
  • Added a new CanonModelID +
  • Added a new ACDSee XMP tag (thanks Malus) +
  • Decode a new Canon tag +
  • Improved a few lens names (thanks Jos Roost) +
  • Patched to remove trailing null when reading improperly written QuickTime + strings +
  • Fixed bug where SystemTags weren't available for use with the -p and -if + options +
+ +Nov. 6, 2015 - Version 10.05 +
    +
  • Added new Canon and Sony LensType values +
  • Added some new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added a new PentaxModelID +
  • Added preliminary support for Motorola maker notes +
  • Changed format of XMP-photoshop:DocumentAncestors to write a simple Bag of + strings rather than structures (since this is what the Adobe software + writes, contrary to their own XMP specification) +
  • Fixed problem where HASH references may be exposed when copying a list of + structures to a non-list-type tag +
  • API Changes: +
      +
    • Added Filter option +
    • Changed GetNewValues method name to GetNewValue (GetNewValues still + works for backward compatibility) +
    +
+ +Oct. 28, 2015 - Version 10.04 + + +Oct. 21, 2015 - Version 10.03 +
    +
  • Added support for JPEG 2000 extended-length boxes +
  • Added a few new Canon LensType and CanonModelID values +
  • Added a new Nikon LensID (thanks LibRaw) +
  • Added ExifTool version number as a comment in -listx output +
  • Added support for Leica SL (Typ 601) maker notes +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Decode more Sony tags for some new models (thanks Jos Roost) +
  • Decode a new Pentax tag +
  • Patched for invalid makernote entry written by Sony ILCE-7M2 v1.21 +
  • Patched problem reading EXE resources with a missing null terminator +
  • Updated Windows distribution package to use latest version of PAR +
+ +Sept. 17, 2015 - Version 10.02 +
    +
  • Added ability to read PNG chunks after the normal PNG end of file (IEND) +
  • Added ability to delete a PNG trailer (with -trailer:all=) +
  • Added some new Nikon LensID's (thanks Robert Rottmerhusen and LibRaw) +
  • Added a few new Canon LensType's (thanks Jos Roost and LibRaw) +
  • Added a new Pentax LensType (thanks Niels Kristian Bech Jensen) +
  • Decode some new PanasonicRaw tags (thanks Andrew) +
  • Decode a new Pentax tag +
  • Enhanced -m option to allow IPTC values which are too short to be written +
+ +Sept. 3, 2015 - Version 10.01 +
    +
  • Added a new Olympus CameraType +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Added a few new QuickTime GenreID values (thanks François Bonzon) +
  • Decode a new Pentax tag +
  • Leica programmers should all be ashamed of the complete shambles of metadata + they have created +
  • Minor change to the name of a Sigma lens for Canon +
  • Improved recognition of Pentax lens adapter +
  • Implemented NoPDFList for -b option of Windows version too +
  • Renamed a few Sony tags and improved decoding of others (thanks Jos Roost) +
  • Renamed a few Pentax tags +
  • Fixed problem importing structured information from -X option output +
  • Fixed round-off errors in value and typo in name of CanonVRD:GammaWhitePoint +
  • Fixed test failures if Encode, POSIX or Time::Local modules are missing +
+ +Aug. 18, 2015 - Version 10.00 (production release) + + +July 23, 2015 - Version 9.99 +
    +
  • Added support for the Leica Q (Typ 116) maker notes +
  • Added two new SonyModelID's (thanks Jos Roost for one) +
  • Added two new Sony LensType2 values (thanks Jos Roost) +
  • Added a new Pentax LensType +
  • Extract a number of new File System tags when API SystemTags option is set +
  • Decode a new FujiFilm tag (thanks TonyB) +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Disabled writing of XMP to PostScript-format Adobe Illustrator files +
  • SourceFile values in -csv and -json input/output are now converted to/from + filename character set (set by -charset filename option) when + reading/writing +
  • Renamed Flash:FileAttributes to FlashAttributes +
  • Renamed FujiFilm:AFPointSet to AFMode and improved decoding +
  • Fixed problem where a partial command could be executed if the -stay_open + option was used and the command is aborted due to an error in arguments +
  • Fixed problem with OS X installer on El Capitan (now installs in + /usr/local/bin instead of /usr/bin) +
  • API Changes: + +
+ +June 26, 2015 - Version 9.98 +
    +
  • Added read support for DSS and DS2 file formats +
  • Added write support for XMP-mwg-rs:Rotation (seen in XMP from LR6) +
  • Added a new Sigma LensType (thanks Niels Kristian Bech Jensen) +
  • Decode Pentax DiffractionCorrection +
  • Decode Olympus ID3 XOLY frame +
  • Decode a few more Sony tags +
  • Improved reliability of decoding Nikon D810 ShotInfo and custom settings +
  • Changed name of Pentax VignettingCorrection to PeripheralIlluminationCorr +
  • Fixed problem with user parameters in tag name expressions when copying +
+ +June 2, 2015 - Version 9.97 +
    +
  • Added a new Pentax Quality value +
  • Added a new Panasonic AdvancedSceneMode (thanks Horst Wandres) +
  • Minor improvement to lens identification logic based on XMP information +
  • Changed a few DR4 tag names +
  • Fixed improper scoping of XMP namespace prefixes (so conflicting prefixes + are now properly resolved) +
+ +May 20, 2015 - Version 9.96 +
    +
  • Added support for CanonVRD version 4 information and DR4 files +
  • Added two new Canon LensType values (thanks Norbert Wasser) +
  • Added two new Olympus LensType values (thanks Niels Kristian Bech Jensen) +
  • Added a new Minolta/Sony LensType and fixed another one (thanks Jos Roost) +
  • Added a new Nikon LensID (thanks John Helour) +
  • Added a few new QuickTime tags +
  • Added a new PentaxModelID and a new Pentax PictureMode +
  • Added a few new XMP-aux tags +
  • Decode a couple more DPX tags +
  • Decode NikonCustom tags for D810 firmware version 1.02 +
  • Improved -htmlDump output for extended XMP and some other JPEG segments +
  • Improved a Canon lens name +
  • Documented the -userParam command-line option (which has existed since 9.90) +
  • Changed default behaviour to ignore extended XMP with an incorrect GUID (as + per the XMP specification) +
  • Changed the case of a few tag names for consistency +
  • Patched problem with Sony cameras giving incorrect LensInfo for some third + party lenses, leading to an incorrect LensID by ExifTool (thanks Jos Roost) +
  • Patched problem where GPS minutes or seconds could round up to 60 +
  • Fixed picasa_faces.config to rotate regions if necessary for RAW file types + (thanks Stargeek) +
  • API Changes: + +
+ +May 9, 2015 - Version 9.95 +
    +
  • Added a few new Minolta/Sony lenses (thanks Jos Roost) +
  • Added config_files/photoshop_paths.config to the full distribution +
  • Avoid rebuilding maker notes when using -tagsFromFile with -fast2 option +
  • Validate tag names when redirecting (ie. "-DSTTAG<SRCTAG") +
+ +May 3, 2015 - Version 9.94 +
    +
  • Added Geotag support for Bramor gEO log files +
  • Added support for iCalendar ICS files +
  • Added support for Leica M Monochrom (Type 246) maker notes +
  • Added new "Preview" group for all embedded preview images +
  • Added a new PentaxModelID (thanks Louis Granboulan) +
  • Added a new Canon LensType (thanks Niels Kristian Bech Jensen) +
  • Added tag name to "Invalid EXIF text encoding" warning +
  • Decode ColorData for Canon EOS 5DS and EOS 5DS R (thanks LibRaw) +
  • Changed ListItem option so that it also applies when copying tags +
  • Enhanced FileTypeExtension to return an uppercase extension when the print + conversion is disabled (eg. the -n option) +
  • Fixed incorrect FileTypeExtension for virtual device drivers +
  • Fixed an improperly formatted warning for duplicate PDF entries +
  • Fixed bug creating extended XMP in JPEG images when deleting all XMP and + adding back in the same step +
+ +Apr. 25, 2015 - Version 9.93 +
    +
  • Added FileTypeExtension tag +
  • Added a few new CanonModelID values +
  • Added a new Olympus CameraType +
  • Added a few new Minolta/Sony lenses (thanks Jos Roost) +
  • Updated arg_files/iptcCore.args for IPTC Extension version 1.2 tags +
  • Improved VCard parsing/decoding +
  • Improved -fast3 file identification logic +
  • Remove "px" string from SVG ImageWidth/Height tags +
  • Changed DOF calculation to use ApproximateFocusDistance +
  • Fixed missing FileType for MOI files +
  • Fixed potential "Internal Error" bug when writing XMP structures +
+ +Apr. 11, 2015 - Version 9.92 +
    +
  • Added support for PRISM pmi and prm tags, and updated to PRISM 3.0 +
  • Added read support for Audible .AA files +
  • Added support for Adobe XMP-creatorAtom tags +
  • Added a couple of new XMP-xmpDM tags +
  • Added a new CanonModelID and a new Ricoh WhiteBalance (thanks LibRaw) +
  • Added a new Olympus CameraType +
  • Added a new Nikon LensID (thanks Jürgen Sahlberg) +
  • Updated to XMP PLUS 1.2.1 specification +
  • Enhanced DOF calculation to use AverageFocusDistance or + FocusDistanceUpper/Lower if available +
  • Patched to tolerate different version numbers in XMP namespace URI's +
+ +Apr. 7, 2015 - Version 9.91 +
    +
  • Added read support for VCard files +
  • Added support for AAX files +
  • Added --sort option +
  • Added a number of new QuickTime GenreID values (thanks François Bonzon) +
  • Added new Canon, Nikon, Olympus and Samsung lenses (thanks LibRaw and Niels + Kristian Bech Jensen) +
  • Added a new Olympus CameraType +
  • Added a new Sony FlashMode value +
  • Decode a new Apple makernote tag +
  • Decode a number of new QuickTime tags found in Audible audio books +
  • Improved the naming of a FujiFilm tag (thanks LibRaw) +
  • Removed "not yet tested" warning when writing FujiFilm RAF version 0200 +
  • Renamed distribution file config_files/ExifTool_config to example.config +
  • Increased maximum metadata atom size for QuickTime files and added warning + if an atom is larger than the limit +
  • Changed writing of filesystem date/time tags to override the -P option +
  • Fixed problem writing FileCreateDate in Windows when the -o option was used +
+ +Mar. 14, 2015 - Version 9.90 (production release) +
    +
  • Added config_files/picasa_faces.config to the distribution (thanks StarGeek) +
  • Added a few new Minolta/Sony and Nikon lenses (thanks LibRaw) +
  • Added new Canon and Sigma lenses (thanks Niels Kristian Bech Jensen) +
  • Decode Nikon D810 custom settings (thanks Warren Hatch) +
  • Decode a few new Nikon tags +
  • Fixed superfluous "Open '' failed" warning which could occur in Windows +
  • Fixed problem reading multi-valued Microsoft Xtra tags +
  • Fixed problem on Windows using -overwrite_original_in_place with Unicode + file names +
+ +Mar. 7, 2015 - Version 9.89 +
    +
  • Added some new Minolta/Sony LensType values (thanks Jos Roost and LibRaw) +
  • Fixed Windows case-sensitivity and sort-order problems when using wildcards + in file names on the command line +
  • API Changes: + +
+ +Feb. 28, 2015 - Version 9.88 +
    +
  • Decode a few more ID3 tags +
  • Improved decoding of some Pentax tags +
  • Extended -list and -listw options to include flattened tags +
  • Patched to recognize ID3v2.3 tags in ID3v2.4 metadata and visa versa +
  • Patched byte ordering problems with Nikon FileInfo tags for the D5500 +
  • Fixed bug where other groups were ignored when multiple groups were + specified when writing and one of those groups was a specific EXIF IFD (eg. + -ExifIFD:Time:all= ignored the Time constraint) +
+ +Feb. 24, 2015 - Version 9.87 +
    +
  • Added a number of new Sigma LensType values (thanks LibRaw) +
  • Fixed bug introduced in the Windows version of 9.85 where "*.*" on the + command line matched "." and "..", causing unexpected files to be processed +
  • Fixed problem extracting some Microsoft tags from MP4/MOV videos +
+ +Feb. 22, 2015 - Version 9.86 +
    +
  • Added check for valid filename encoding when using wildcards in Windows +
  • Added support for Sigma X3F version 4.1 images +
  • Added a number of new Sigma LensType values (thanks LibRaw and Niels + Kristian Bech Jensen) +
  • Added a new Canon LensType (thanks Michael Tapes) +
  • Added a new Leica LensType (thanks LibRaw) +
  • Added a new Olympus CameraType +
  • Decode a couple more FujiFilm RAF tags (thanks Frank Markesteijn) +
  • Always preserve FileCreateDate when writing on Windows if Win32::API and + Win32API::File are available +
  • Changed names of and properly decode new Olympus Pitch/Roll tags +
  • Changed rounding method for rational values in an attempt to fix failed + tests on FreeBSD 10.1 and Perl 5.20.1 with uselongdouble enabled +
  • Resolved issue with duplicate SamsungModelID values +
+ +Feb. 14, 2015 - Version 9.85 +
    +
  • Added support for Ricoh WG-M1 maker notes +
  • Added a few new Pentax LensType's (thanks Louis Granboulan) +
  • Decode a couple of new Pentax tags (thanks Louis Granboulan) +
  • Enhanced JSON import so an object with a missing SourceFile has the same + effect as a SourceFile of "*". +
  • Changed MIMEType for executable script files +
  • Patched remaining known problems involving Windows Unicode file names + (creating directories and using wildcards should now work) +
  • Patched long-standing Windows daylight-savings-time bug, and removed the + dependency on Win32API::File::Time (reported file times should now be + correct, but may disagree with the Windows "dir" command) +
  • Fixed decoding of FLIR:PlanckO (thanks Tomas) +
+ +Feb. 10, 2015 - Version 9.84 +
    +
  • Added a new CanonModelID +
  • Added a new Pentax LensType and PentaxModelID (thanks Louis Granboulan) +
  • Fixed bug introduced in 9.83 that broke the -P option on Windows +
+ +Feb. 7, 2015 - Version 9.83 +
    +
  • Added support for new IPTC Extension version 1.2 XMP tags +
  • Added support for Leica X (Typ 113) maker notes +
  • Added read support for ChartTIFF tags +
  • Added a few new Canon LensType values (thanks Norbert Wasser for two) +
  • Added a few new Nikon LensID's (thanks David Püschel, Robert + Rottmerhusen and Niels) +
  • Added a number of new Olympus ArtFilterEffect values (thanks Phiber) +
  • Added some new Olympus AspectRatio values (thanks Herb) +
  • Added a new PentaxModelID +
  • Added a new Olympus CameraType (thanks LibRaw) +
  • Added new values for various Canon tags +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Decode a couple more Nikon ShootingMode bits (thanks Leegong) +
  • Updated to Nov 2014 XMP specification +
  • Patched incompatibility between Windows Unicode update and Perl 5.005 +
+ +Jan. 15, 2015 - Version 9.82 +
    +
  • Added support for Windows Unicode names for input CSV and JSON files +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Improved checks and warnings for invalid file name encoding on Windows +
  • Improved Polish translations (thanks Kacper Perschke) +
  • Changed exif2xmp.args and xmp2exif.args files (available in the full + ExifTool distribution) to avoid using non-standard XMP namespaces +
  • Fixed problem setting file times for Windows Unicode file names +
+ +Jan. 12, 2015 - Version 9.81 +
    +
  • Added Composite:Megapixels tag +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Fixed problem in Windows using Unicode file names with the -o option +
+ +Jan. 7, 2015 - Version 9.80 +
    +
  • Added a few new Canon and Minolta/Sony LensType values (thanks LibRaw) +
  • Added a few new CanonModelID's +
  • Decode a few new Canon tags +
  • Suppress warning for Canon 7DmkIII VignettingCorrUnknown2 data format +
  • Fixed runtime warning when piping an MOI file to exiftool +
  • Fixed bug in -fileOrder option introduced in version 9.79 +
  • Fixed bug using Windows Unicode file names with -tagsFromFile option +
+ +Jan. 4, 2015 - Version 9.79 - Windows Unicode file names +
    +
  • Added support for Windows Unicode file names (but note that support still + isn't complete -- some things like setting FileModifyDate or creating a + directory with a Unicode name still need work) +
  • Added read support for MOI files +
  • Added a few more Canon LensType's (thanks LibRaw and Martin) +
  • Added a new Nikon LensID +
  • Added a couple of new Panasonic ImageQuality values +
  • Decode a new FujiFilm tag and added a new FilmMode value +
  • Allow zero-length group names to be specified (which provides a method to + directly access the 0th copy of a duplicate tag using the empty family 4 + group name by specifying "4:TAG") +
  • Fixed invalid Samsung tag name +
  • Fixed writing of XMP:ExposureCompensation with fractional values +
  • API Changes: + +
+ +Dec. 13, 2014 - Version 9.78 +
    +
  • Added a new Nikon LensID and a new Canon LensType (thanks LibRaw) +
  • Decode a new unknown atom in Canon MOV videos +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Decode a number of new Samsung tags +
  • Improved MakerNote compatibility when writing by adding the same amount of + padding after the MakerNote IFD as that written by the specific camera model +
  • Changed the DNG BaselineExposureOffset to a signed rational (contrary to the + DNG 1.4 specification, which specifies an unsigned RATIONAL, but obviously + Adobe meant for it to store negative values) +
  • Changed name of new QuickTime Rating tag to RatingPercent +
  • Fixed problem introduced in 9.64 where multiple list values from some tags + in ZIP-based files were extracted as separate tags instead of as a list when + the -struct option was used, and implemented a different patch to just + suppress the structure warnings without changing the 9.63 behaviour +
  • Fixed problem where some EXIF information in MOV videos could be corrupted + when writing +
+ +Nov. 28, 2014 - Version 9.77 +
    +
  • Added FilePath tag (generated only if specified and Cwd is available) +
  • Added a new Panasonic ShootingMode (thanks Horst Wandres) +
  • Added a new FujiFilm WhiteBalance value +
  • Added a new QuickTime vendor ID +
  • Added a new Pentax RawDevelopmentProcess value +
  • Decode a few new QuickTime tags +
  • Decode some new tags in Kodak SP360 MP4 videos +
  • Fixed bug that could cause a runtime error when parsing a Canon EOS 40D + image which had been edited by Nikon Capture software +
  • Fixed bug which could produce invalid XML in the -X output when -struct was + also used +
+ +Nov. 15, 2014 - Version 9.76 (production release) +
    +
  • Added support for the LFR format (same as LFP) +
  • Added support for Samsung models which write 2 values for LensType +
  • Added a new Samsung LensType (thanks Nick Livchits) +
  • Added a few new Nikon LensID's (thanks David Püschel) +
  • Added a new PentaxModelID +
  • Added some new Olympus WhiteBalance2 values (thanks LibRaw) +
  • Added a new EXIF:Compression value +
  • Decode a new FujiFilm tag +
  • Decode a few more private TIFF tags +
  • Improved warning message if "DIR" or "FILE" is used literally on the command + line +
  • Improved "-j -b" output to encode any non-UTF8 values as Base64 +
  • Enhanced -fast option (API FastScan option) to allow file processing to be + bypassed entirely +
  • Changed conversions for a couple of Nikon 1 AF tags for consistency +
  • Changed reported FileType for LNK, KEY, KTH, NUMBERS, NMBTEMPLATE and PAGES + files to match extension +
  • Fixed problems adding new XMP and deleting old XMP from MP4 videos +
  • Fixed problem writing FileModifyDate/FileCreateDate when + -overwrite_original_in_place is used +
+ +Nov. 2, 2014 - Version 9.75 +
    +
  • Added support for FLIR version 101 metadata +
  • Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) +
  • Added a new Pentax LensType (thanks Louis Granboulan) +
  • Added a new Samsung LensType +
  • Decode a few more Nikon D4S tags (thanks Warren Hatch) +
  • Decode a few more Sony tags (thanks Jos Roost) +
  • Decode a new FujiFilm tag +
  • Improved formula for calculating Sony:FocusDistance2 (thanks Jos Roost) +
  • Changed XMP parsing to impose standard namespace prefixes, thus avoiding + (some) problems reading the ugly XMP generated by Microsoft products +
  • Fixed decoding of Nikon FileInfo tags for recent DSLR models +
+ +Oct. 15, 2014 - Version 9.74 +
    +
  • Added a number of new QuickTime GenreID values (thanks François Bonzon) +
  • Added support for some newer Sony cameras (thanks Jos Roost) +
  • Decode more Nikon D4S custom settings (thanks Warren Hatch) +
  • Improved lens identification logic for XMP metadata (thanks Jos Roost) +
  • Fixed typo in Condition for a few D3 NikonCustom tags +
  • Fixed bug which could cause a "Use of uninitialized value $val" runtime + error when reading XML +
+ +Oct. 13, 2014 - Version 9.73 +
    +
  • Added a new CanonModelID (thanks LibRaw and Niels Kristian Bech Jensen) +
  • Added a few new Canon and Sony lenses (thanks Jos Roost) +
  • Delete unknown PNG TextualData tags when deleting all PNG tags +
  • Decode AF points for Canon PowerShot G1X Mark II +
  • Decode a number of Pentax, Olympus, Panasonic and Sony tags (thanks LibRaw) +
  • Decode a few more Nikon NCTG tags in MOV videos (thanks Stuart Bennett) +
  • Decode Nikon D4S custom settings (thanks Warren Hatch) +
  • Decode some makernote tags in Sony Xperia Z3 images +
  • Make PhotoshopThumbnail and PhotoshopBGRThumbnail writable (but "unsafe") +
  • Changed order of operations so flattened tags always take precedence over + structured tags when writing a mix of both types of tags +
  • Fixed potential bug decoding some Nikon 1 AF points +
+ +Sept. 26, 2014 - Version 9.72 +
    +
  • Added a few new Pentax ISO values +
  • Added a new CanonModelID and SonyModelID (thanks LibRaw) +
  • Added a new Canon LensType +
  • Decode AF points for some newer Nikon 1 cameras (thanks Chris Reimold for + his help, and Graham Woolf for the samples) +
  • Improved verbose output for recently decoded Samsung trailer +
  • Fixed Samsung trailer writer to properly update offsets in the QDIO block +
+ +Sept. 20, 2014 - Version 9.71 +
    +
  • Added a couple of mew CanonModelID's (thanks LibRaw) +
  • Added a couple of new Nikon LensID's (thanks Robert Rottmerhusen and LibRaw) +
  • Added minor warning when deleting all PDF metadata (because it isn't really + deleted) +
  • Added a new Samsung LensType +
  • Added a new Sony/Minolta LensType (thanks Marcus Holland-Moritz) +
  • Decode "Sound & Shot" trailer written by some Samsung Galaxy models +
  • Decode a number of new Sony tags including FocusDistance2 (thanks Jos Roost) +
  • Decode a number of new Canon and Kodak tags (thanks LibRaw) +
  • Decode a new EXIF tag +
  • Decode a few new CanonCustom tags +
  • Recognize the JPE file extension +
  • Fixed swapped CanonCustom AEMicroadjustment/FEMicroadjustment tag names +
  • Fixed bug in -listItem option which sometimes prevented it from working +
  • Fixed interference by -sep option when writing structured tags +
  • API Changes: + +
+ +Sept. 3, 2014 - Version 9.70 (production release) +
    +
  • Added read support for Pentax 645Z makernotes in MOV videos +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Added a new SonyModelID (thanks LibRaw) +
  • Added a new Pentax LensType (thanks Dominique Schrekling) +
  • Added a new PentaxModelID +
  • Added a new Olympus CameraType (thanks LibRaw) +
  • Decode a number of new Sony tags and values (thanks Jos Roost) +
  • Decode a new Panasonic tag +
  • Decode another QuickTime tag +
  • Updated Nikon ISOExpansion values for new models (thanks LibRaw) +
  • Improved verbose dump for LFP images +
  • Patched to repair incorrect list types in XMP structures when writing +
  • Patched to avoid MPEG Layer 3 check for files with a "MUS" extension +
  • Fixed incorrect behaviour of -addTagsFromFile when adding list items from + multiple variable-named source files +
+ +July 27, 2014 - Version 9.69 +
    +
  • Added ability to write PNG PhysicalPixel (PNG-pHYs) tags +
  • Added a few new Panasonic ContrastMode values +
  • Added minor warning when creating EXIF or IPTC in PNG images +
  • Decode a few more Sony tags and values (thanks Jos Roost) +
  • Fixed problem writing Olympus:LensType +
  • API Changes: +
      +
    • Shift.pl now autoloads when ShiftTime() is called +
    • The 3rd argument to ShiftTime() is now optional +
    +
+ +July 19, 2014 - Version 9.68 +
    +
  • Added read support for Lytro LFP files +
  • Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) +
  • Added support for the maker notes of some recent Ricoh/Pentax models +
  • Allow Composite tag to have no Require'd or Desire'd tags +
  • Improved -geotag verbose output +
  • Changed so XMP tags take priority when reading QuickTime-format files +
  • Changed PSD file description to "Photoshop Document" +
  • Fixed problem where -geotag option could fail if -v4 was used +
  • Fixed problem writing SonyDateTime2 +
+ +July 6, 2014 - Version 9.67 +
    +
  • Added support for Sigma X3F version 4.0 files from the DP2 Quattro +
  • Added support for Sony XAVC video files +
  • Added a new Nikon PhaseDetectAF value +
  • Added conversion for Casio EX-ZR300 BestShotMode (thanks Manfred) +
  • Decode more proprietary information from some Panasonic/Leica MP4 videos +
  • Fixed inaccuracies in decoding some SigmaRaw tags from the X3F header +
+ +July 1, 2014 - Version 9.66 +
    +
  • Added read support for Kodak PixPro S-1 maker notes +
  • Added new Canon, Pentax and Sigma LensTypes (thanks LibRaw for the Canon) +
  • Added support for Sigma DP2 Quattro maker notes +
  • Decode AFMicroAdj for Sony A77M2 +
  • Improved decoding of Pentax K-3 AFPointSelected (thanks Dan) +
  • Swap "GPS track start/end" labels in -geotag -v2 output if track is stored + in reverse chronological order +
  • Changed handling of IPTC groups so that standard IPTC always has a family 1 + group name of "IPTC", and takes priority over non-standard IPTC when + duplicates are not allowed +
  • Patched to recognize incorrect XMP URI's written by Nikon NX2 +
  • Fixed problem with possible duplication of lens names when attempting to + identify lens used by a Nikon camera from a Photoshop-mangled image +
  • Fixed problem parsing some NMEA sentences, and added support for GPZDA +
  • Fixed typo in a CanonCustom value +
  • Fixed bug where standard IPTC may be incorrectly written into a proprietary + PhotoMechanic IPTC-format SoftEdit record +
+ +June 20, 2014 - Version 9.65 +
    +
  • Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) +
  • Added a print conversion for Unknown ICC_Profile MeasurementGeometry +
  • Added LargeTags shortcut +
  • Avoid loading data for some tags with large binary data values if they were + specifically excluded by the command +
  • Improved memory handling when processing CanonVRD information +
  • Patched potential "subscript -1" runtime error when reading EPS files +
+ +June 14, 2014 - Version 9.64 +
    +
  • Added write-only TestName tag for dry-run tests of file renaming feature +
  • Added a few new Olympus CameraType values +
  • Added a new new Nikon LensID's +
  • Added a new Canon LensType +
  • Patched to avoid structure warnings when copying tags from ZIP-based files +
  • Patched to deal with incorrect byte ordering in FlashPix date/time values + written by some cameras +
  • Fixed problem using advanced formatting feature in a -if condition +
  • Fixed problem parsing Canon VignettingCorr for some newer PowerShot models +
  • API Changes: +
      +
    • Added 'Test' option to SetFileName() +
    +
+ +May 31, 2014 - Version 9.63 +
    +
  • Added basic read support for EPUB and MOBI electronic books +
  • Added ability to combine -lang option with -listx to extract only one language +
  • Remove leading XML comment from XMP if it exists when writing as a block +
  • Another attempt to patch problem obtaining a consistent "full path" for + source files when importing a CSV database in Windows +
+ +May 24, 2014 - Version 9.62 +
    +
  • Added message about SourceFile names to -v2 output when importing a database + with -csv= or -json= +
  • Added patch to allow extraction of some incorrectly written FlashPix tags +
  • Extract a couple of new FlashPix tags for some FujiFilm models +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Changed -n option for GPSTimeStamp to return nanosecond precision +
+ +May 18, 2014 - Version 9.61 +
    +
  • Added ability to combine -l with -listf, -listr or -listwf to add + descriptions of the file type +
  • Added a few new Canon LensType values (thanks Norbert Wasser and Mark) +
  • Extract AE metering segments again for the Pentax K-3 +
  • Decode a number of new FujiFilm RAF tags (thanks LibRaw) +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Changed the case of some AF point values for consistency +
  • Fixed an incorrect Olympus FocusMode value +
  • Fixed problem introduced in 9.60 where a "LensID argument isn't numeric" + warning could occur under some conditions +
  • Fixed quirk where ExifTool could incorrectly report that a file was changed + when conditionally deleting a non-existent tag as a block +
+ +May 11, 2014 - Version 9.60 (production release) +
    +
  • Added a few new values for some Pentax tags +
  • Added a new QuickTime Rating value (thanks François Bonzon) +
  • Added a new SonyModelID (thanks LibRaw) +
  • Added a new Canon and a new Pentax LensType +
  • Decode a number of new tags from WebP extended-format files +
  • Decode a couple more Nikon tags +
  • Decode a new Canon 300D tag (thanks LibRaw) +
  • Decode a new Sony tag (thanks Jos Roost) +
  • Updated Sony maker note decoding for ILCA-77M2 (thanks Jos Roost) +
  • Renamed QuickTime ©day atom to ContentCreateDate (was previously named + Year or CreateDate depending on its location) +
  • Changed a number of "Unknown (-1)" values to "n/a" (thanks Herbert Kauer) +
  • Minor change to some German translations (thanks Herbert Kauer) +
  • Patched spec file for building RPM under Redhat 6 (thanks Norbert de Rooy) +
  • Fixed a problem writing 3-character Nikon CountryCode values +
  • Fixed problem when importing CSV files in Windows where an appropriate + SourceFile entry may not be found if the file specification does not match + exactly but the absolute path is the same +
+ +May 3, 2014 - Version 9.59 +
    +
  • Added support for Leica T maker notes +
  • Added a few new Olympus CameraType values +
  • Added a number of new AppleStoreCountry values (thanks François Bonzon) +
  • Added a print conversion for QuickTime GenreID (thanks François Bonzon) +
  • Added more values for some NikonCapture tags (thanks LibRaw) +
  • Improved decoding for a number of Sony tags (thanks Jos Roost) +
  • Improved decoding of some Olympus tags (thanks Herbert Kauer) +
  • Improved writing of GPSAltitudeRef to recognize any signed number +
  • Patched problem where some QuickTime UTF-8 values were being decoded + incorrectly +
+ +Apr. 19, 2014 - Version 9.58 +
    +
  • Added verbose warning and HtmlDump flag for out-of-sequence EXIF tag ID's +
  • Improved decoding of NikonCapture WBAdjLighting (thanks LibRaw) +
  • Improved German translations (thanks Herbert Kauer) +
  • Fixed -v3 to report absolute offsets for RIFF and NikonCapture information +
  • Fixed problem in Windows version which caused ExifTool to exit with an error + when importing from a CSV file with a non-existent SourceFile entry +
+ +Apr. 12, 2014 - Version 9.57 +
    +
  • Added a Composite tag to calculate Duration for AIFF files +
  • Added a couple of new Canon lenses +
  • Added a new value for NikonCapture:WBAdjLighting (thanks LibRaw) +
  • Added a new Olympus CameraType (thanks LibRaw) +
  • Decode a couple more Canon tags +
  • Improved French translations (thanks Alphonse Philippe) +
  • Patched to correctly sort out-of-sequence GPS IFD entries +
  • Fixed possible "uninitialized value" runtime error when reading corrupted + CanonCustom2 information +
  • Fixed unnecessary warning when writing an image with zero-length maker notes +
  • Fixed incorrect family 2 group for XMP-exif:GPSImgDirectionRef +
+ +Apr. 5, 2014 - Version 9.56 +
    +
  • Added new Canon and Pentax LensType values +
  • Added a couple more Canon ContinuousDrive modes +
  • Added a new Sigma ColorMode +
  • Decode some new QuickTime tags written by the HTC One (M8) in MP4 videos +
  • Decode telemetry information from AR Drone videos +
  • Extract PreviewImage from GoPro JPG files +
  • Improved German translations (thanks Herbert Kauer) +
  • Improved French translations (thanks Alphonse Philippe) +
  • Updated en_ca and en_gb translations +
  • Patched to avoid "excessive count" warning for a 16-bit TransferFunction +
  • Fixed decoding of Canon AEBShotCount for some models +
+ +Mar. 29, 2014 - Version 9.55 +
    +
  • Added new write-only HardLink tag for creating hard links +
  • Added support for Leica S maker notes +
  • Added support for Nintendo maker notes +
  • Added limited read support for FLIR "AFF" SEQ images +
  • Added a new Pentax LensType (thanks Bruce Rusk) +
  • Added two new NEFCompression values (thanks LibRaw) +
  • Added a few new CanonModelID values +
  • Added a new Sony LensType2 (thanks Jos Roost) +
  • Added some unknown Sony tags possibly related to metering (thanks Jos Roost) +
  • Decode orientation information for Ricoh Theta (thanks Paulo Costa) +
  • Decrypt Nikon ColorBalanceUnknown data for -U and -v2+ options +
  • Updated recognition of Metabones Canon adapters on Sony cameras to support + new adapter firmware versions +
  • Improved verbose messages when reading GPS track logs +
  • Changed writing of EXIF:Copyright to allow newline conversion to be avoided + by disabling print conversion +
  • Fixed an incorrect French translation (thanks Alphonse Philippe) +
  • API Changes: +
      +
    • Fixed problem where Directory tag wasn't written if any "real" tags were + changed when calling WriteInfo() +
    +
+ +Mar. 1, 2014 - Version 9.54 +
    +
  • Added a new Pentax LensType +
  • Decode a few more FLIR tags +
  • Generate missing default language tags for alternate-language QuickTime tags + without them +
  • Improved identification of some file sub-types based on filename extension +
  • Fixed bug extracting alternate-language QuickTime tags with numerical ID's + when processing multiple files in a single command +
  • Fixed potential problem when using some of the recently added options with + the -stay_open feature +
  • Fixed problem loading a config file with a single quote in the file name +
  • Fixed problem writing a shortcut tag when the target tag has a group name +
  • Fixed problem recognizing HTML files with a leading UTF-8 BOM +
+ +Feb. 22, 2014 - Version 9.53 (production release) +
    +
  • Added user-defined file types feature +
  • Added support for a few more XMP-expressionmedia tags +
  • Decode CameraTemperature from a few new Canon models +
  • Decode another Sony tag (thanks Jos Roost) +
  • Recognize the JXR extension +
  • Changed wording of IPTC "Unrecognized data" warning +
  • Patched round-off error problem in seconds of some date/time values +
  • Fixed problem creating EXV files with the -o option +
  • Fixed problem conditionally replacing Extra block-type tags +
+ +Feb. 17, 2014 - Version 9.52 +
    +
  • Fixed bug introduced in 9.44 which resulted in incorrect maker note offsets + when copying maker notes from DNG images of some Pentax models +
+ +Feb. 15, 2014 - Version 9.51 +
    +
  • Added a number of new CanonModelID values +
  • Added a new Sony/Minolta LensType (thanks Jos Roost) +
  • Added a new Panasonic BurstMode +
  • Added a new Pentax LensType +
  • Added new ColorSpaceTags shortcut +
  • Decode a number of new PanasonicRaw tags (thanks LibRaw) +
  • Decode ColorData tags for the new Canon EOS 1200D +
  • Improved Sony maker note decoding for some newer models (thanks Jos Roost) +
  • Fixed conversion of XMP:LensID for Pentax lenses on Ricoh-branded cameras +
+ +Feb. 8, 2014 - Version 9.50 +
    +
  • Added support for Exiv2 EXV metadata files +
  • Added ability to read/write/copy GeoTiff tags as a block +
  • Added ability to combine -b with -j to write base64-encoded binary data in + JSON output format +
  • Added a new Nikon LensID +
  • Added a new Sony/Minolta LensType (thanks Jos Roost) +
  • Added new Extra ProcessingTime tag +
  • Added a new Olympus CameraType +
  • Added header signature to .EXIF files +
  • Decode a new Olympus tag (thanks LibRaw) +
  • Improved conversion of some Canon RecordMode values +
  • Improved decoding of tags for Hasselblad Stellar (thanks Jos Roost) +
  • Fixed -htmlDump offsets for information extracted from some embedded images +
  • Fixed hemisphere problem in Composite GPS tags derived from + QuickTime:LocationInformation +
  • API Changes: +
      +
    • Option names are now case insensitive +
    +
+ +Feb. 1, 2014 - Version 9.49 +
    +
  • Added new Olympus and Samsung LensType values, and fixed an Olympus lens + name (thanks Niels Kristian Bech Jensen) +
  • Added another Polish tag translation (thanks Kacper Perschke) +
  • Added a new Panasonic ColorEffect value +
  • Decode a new FujiFilm tag (thanks LibRaw) +
  • Adjusted scaling factor for H264 ExposureTime values (thanks Francois) +
+ +Jan. 25, 2014 - Version 9.48 +
    +
  • Added a new GeoTiff tag +
  • Added a couple of new Canon LensType values +
  • Added a few new XMP DarwinCore tags +
  • Updated Sony MakerNotes for the ILCE-5000 (thanks Jos Roost) +
  • Improved Polish translations (thanks Kacper Perschke) +
  • Fixed problem writing RicohSubdirIFD in GR images +
  • Fixed runtime error when writing PNG image with a TIFF-format EXIF profile +
+ +Jan. 18, 2014 - Version 9.47 +
    +
  • Added read support for JPEG-HDR APP11 information +
  • Added read support for Media Jukebox APP9 information +
  • Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added a new Minolta/Sony LensType and fixed some Tamron lens names (thanks + Jos Roost) +
  • Decode more FLIR tags (thanks Tomas) +
  • Patched for quirk in Kodak PixPro AZ362 maker notes +
+ +Jan. 11, 2014 - Version 9.46 (production release) +
    +
  • Added a few Nikon lenses (thanks Niels for two) +
  • Added a few new XMP-xmpDM tags (thanks Mats Peterson) +
  • Added a new Pentax DriveMode (thanks Doug O'Brien) +
  • Added a new Olympus lens (thanks Niels Kristian Bech Jensen) +
  • Decode a number of new QuickTime tags +
  • Decode a few new Canon filter tags +
  • Improved -d option to properly handle time zones with %s and %z +
  • Improved conversions for Panasonic Composite AdvancedSceneMode +
  • Changed a few Tamron lens strings (thanks Niels Kristian Bech Jensen) +
  • Patched to avoid round-off errors in seconds of filesystem time tags +
  • Fixed extraction of CompressorID for some MOV videos +
+ +Dec. 21, 2013 - Version 9.45 +
    +
  • Added details about movie data offset in MOV verbose dump +
  • Added a new Pentax WhiteBalance value +
  • Added a new Sony/Minolta LensType (thanks Jos Roost) +
  • Decode a new QuickTime tag +
  • Prevent illegal tag names to be generated from user-defined XMP tags +
  • Patched to allow reading some improperly formatted EXIF UserComment values +
  • Fixed problem where reading some large M2TS files could take a loooong time +
+ +Dec. 11, 2013 - Version 9.44 +
    +
  • Added a new Pentax WhiteBalance value +
  • Added back the K-3 SRResult tag, but make it Unknown and with no print + conversion +
  • Fixed problem copying the maker notes of some recent Pentax models +
+ +Dec. 8, 2013 - Version 9.43 +
    +
  • Added a new Olympus CameraType +
  • Added new Canon and Pentax LensType values +
  • Added a new Panasonic ShootingMode +
  • Added a new CanonModelID +
  • Decode a number of new Sony Tags (thanks Jos Roost) +
  • Decode a new Pentax tag +
  • Fixed decoding of a few Pentax tags for newer models +
  • Fixed decoding of CameraTemperature for Olympus Stylus 1 +
  • Fixed bug when writing QuickTime date/time tags with QuickTimeUTC option set +
+ +Dec. 1, 2013 - Version 9.42 +
    +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Added a new Pentax LensType (thanks Louis Granboulan) +
  • Added a new Sony LensType (thanks Fredrik Agert) +
  • Added a new Nikon LensID (thanks LibRaw) +
  • Added a new Olympus CameraType +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Decode a number of new Panasonic tags (thanks Thomas Modes) +
  • Decode a few new Pentax tags (thanks Klaus Homeister and others) +
  • Improved Olympus SensorTemperature calibration (thanks Eric Sibert) +
  • Patched decoding of EXIF:UserComment to tolerate invalid character code + written by Canon Zoombrowser EX 4.5 +
  • Patched to allow GPS information to be extracted from incorrectly formatted + EXIF written by Windows Phone OS 7.5 (observed in some HTC and Nokia phones) +
  • Fixed runtime warnings which could occur when -j or -php combined with -f +
+ +Nov. 16, 2013 - Version 9.41 +
    +
  • Added the ability to use multiple group qualifiers on tag names when writing + and copying (eg. "-QuickTime:Time:All=now") +
  • Added ability to geotag from Google Location Services KML files +
  • Added a new Pentax LensType (thanks Louis Granboulan) +
  • Added a new Canon LensType (thanks David Monro) +
  • Added a new Sony LensType2 (thanks Jos Roost) +
  • Decode a bunch more Nikon, Canon, Sony and FujiFilm WhiteBalance tags + (thanks LibRaw) +
  • Extract information from the new Pentax APP7 segment +
+ +Nov. 8, 2013 - Version 9.40 - "Write QuickTime" +
    +
  • Added ability to write XMP and edit date/time tags in QuickTime-format files + (eg. MOV, MP4) +
  • Added -api option to allow API options to be set via the command line +
  • Added ability to specify family 2 group name when writing +
  • Added write support for a number of new XMP tags +
  • Added a new Nikon LensID +
  • Added new Canon and Minolta/Sony LensType values (thanks Jos Roost) +
  • Added a few of the new values defined in the DICOM 2011 specification +
  • Add standard XMP to a PNG image when writing, even if XMP already exists in + another non-standard chunk +
  • Decode a few more Sony tags (thanks Jos Roost) +
  • Decode a few more QuickTime tags +
  • Decode a few more Panasonic tags (thanks Thomas Modes) +
  • Extract EXIF information from WEBP images +
  • Extract a few more unknown tags in Samsung MP4 videos +
  • Extract ProfileName from the PNG iCCP chunk +
  • Improved error message if no matches found when writing to tag(s) specified + by wildcards +
  • Improved XML output so EXIF tags now report a count (if constant, and > 1) +
  • Avoid writing a few XMP-crs tags which have XMP-exifEX equivalents unless + specified explicitly +
  • Patched POD documentation in MIE.pm to remove non-ASCII characters +
  • Fixed bug adding back XMP tags in PDF files after deleting all in the same + command (also made XMP the preferred group when writing PDF files) +
  • Fixed bug extracting some font information from QuickTime videos +
  • Fixed inconsistencies in behaviour when extracting XML as a block from + JPEG2000 images +
  • Fixed problem where FileName was changed when using -srcfile option and + writing only the Directory +
  • API Changes: +
      +
    • Added the PNGEarlyXMP option +
    +
  • Internal Changes: +
      +
    • Changed all "$exifTool" variable names to "$et" throughout -- my + apologies to the diff engines +
    +
+ +Oct. 19, 2013 - Version 9.39 +
    +
  • Added a new PentaxCameraID and some new LensTypes (thanks Louis Granboulan) +
  • Added a new Nikon LensID +
  • Added a new Panasonic ShutterType +
  • Additions and improvements to Pentax makernote decoding for K-3 +
  • Decode a number of new tags including purchase information from MP4 videos +
  • Decode FLIR information acquired by Extech MeterLink meters (thanks Tomas) +
  • Decode more Sony tags (thanks Jos Roost) +
  • Patched to suppress the run-time "No such file or directory" error that has + been seen when using the -csv option on Windows systems +
+ +Oct. 7, 2013 - Version 9.38 +
    +
  • Added read support for DPX images +
  • Added a new Pentax LensType +
  • Added a few new CanonModelID values +
  • Added a new XMP-apple-fi tag +
  • Decode a few more Canon tags (thanks Tomasz Kawecki) +
  • Decode a few more Sony tags (thanks Jos Roost) +
  • Improved the names of a few Apple tags (thanks Neal Krawetz) and added new + Composite tag +
  • Tolerate NMEA sentences with missing degrees written by some crappy software +
  • Changed Duration conversion to print number of days if more than 24 hours +
+ +Sept. 14, 2013 - Version 9.37 +
    +
  • Added support for maker notes from Apple iPhone5 iOS 7 +
  • Added two more Torrent tags +
  • Added a new Pentax DigitalFilter +
  • Added new Olympus CameraType and LensType values +
  • Decode a couple more Olympus tags +
  • More improvements to Sony decoding (thanks Jos Roost) +
  • Improved decoding of Scalado JPEG APP4 information +
  • Fixed problem where a PreviewImage could be reported in either the File or + Composite group, depending on the details of the command +
+ +Sept. 7, 2013 - Version 9.36 +
    +
  • Added read support for BitTorrent description files (bencode format) +
  • Added a couple of new Nikon LensID's (thanks Jürgen Sahlberg) +
  • Added support for PNG 8bim raw profile +
  • Added or fixed a few Pentax LensType values (thanks Louis Granboulan) +
  • Added ability to delete DNGAdobeData and DNGPrivateData +
  • Decode more Sony tags and improved decoding of others (thanks Jos Roost) +
  • Decode a number of new FujiFilm tags and fixed 2 incorrect Saturation values +
  • Decode a number of Canon CameraInfo tags for the 70D (thanks Tomasz Kawecki) +
  • Patched to issue minor warning and extract only the first 1000 values from + XMP list-type tags containing more than 1000 items (all values may be + extracted by ignoring this warning with the -m option) +
  • Patched decoding of PNG IPTC raw profile to allow either IIM or IRB data +
  • Flagged ImageSourceData as "unsafe" (avoids excessive memory usage when + copying all tags because this data may be larger than the image itself for + Photoshop TIFF images) +
  • Disabled feature introduced in version 9.14 which allowed multiple tags + (specified by wildcards) to be copied into a single list. This feature had + the unintended side-effect of generating duplicate list items when copying + list-type tags if there were multiple source tags with the same name. If + necessary, -addTagsFromFile may still be used to copy the values of multiple + tags into a single list. +
+ +Aug. 17, 2013 - Version 9.35 +
    +
  • Added a new Canon LensType (thanks Oliver) +
  • Added two new Olympus CameraType values +
  • Added some new Pentax LensType values (thanks Louis Granboulan) +
  • Added a new RIFF StreamType value +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Decode CameraTemperature from more Canon models +
  • Extract thumbnail information from Leica X VARIO MP4 videos +
  • Improved decoding of Pentax LensData (thanks Louis Granboulan) +
  • Patched to avoid a warning for the messed-up Leica M maker notes +
  • Changed a few Pentax Samsung/Schneider lens names for consistency +
  • Changed "Can't delete" message to indicate if the tag is Permanent +
  • Fixed the case of a few tag names (thanks Romain) +
+ +July 27, 2013 - Version 9.34 +
    +
  • Added support for Ricoh GR maker notes (in MOV videos too) +
  • Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Added support for Sony DSC-TF1 maker notes (thanks Jos Roost) +
  • Added patch for messed up Leica M (Typ 240) MakerNote trailer +
  • Added a few new CanonModelID's and a few new PentaxModelID's +
  • Added some new XMP-crs tags written by LR5 +
  • Added a few new Nikon LensID's +
  • Decode a number of new Ricoh GR tags (thanks Tim Gray) +
  • Recognize the Nikon SB-700 external flash +
  • Updated MWG location tags to conform with the MWG 2.0 specification (but + continue writing legacy IPTC Core location tags) +
  • Removed "[Minor]" designation from "excessive count" warning if count is + greater than 2M +
  • Avoid processing multiple EXIF IFD's if only one should exist +
+ +July 13, 2013 - Version 9.33 +
    +
  • Added support for EXIF UTF-16 Unicode text (previously treated as UCS-2) +
  • Added support for Leica X Vario maker notes +
  • Added a couple of new SonyModelID values (thanks Jos Roost) +
  • Added a new CanonModelID and a new Olympus CameraType +
  • Added a new Canon LensType +
  • Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) +
  • Decode some new Panasonic tags and added values for others (thanks Thomas) +
  • Improved decoding of Olympus RawDevArtFilter +
  • Improved decoding of some Sony tags for the RX100M2 +
  • Changed application to always return an error status when exiting if an + error was encountered when extracting information +
+ +June 22, 2013 - Version 9.32 +
    +
  • Added support for "Exif 2.3 for XMP" tags +
  • Added a few new Olympus LensTypes (thanks Niels Kristian Bech Jensen) +
  • Added a few new PentaxModelID's +
  • Added two new Samsung LensType values (thanks Pascal de Bruijn) +
  • Decode a new Olympus tag +
  • Fixed problem extracting audio comments from Ricoh G700SE images +
  • Fixed a non-conforming CanonModelID string +
+ +June 8, 2013 - Version 9.31 +
    +
  • Added a number of new Photoshop tags (but marked as Unknown) +
  • Added a few new values for some Olympus tags +
  • Added conversion for Olympus SensorTemperature +
  • Added two new CanonModelID's +
  • Added support for Reconyx firmware 4.0.0 +
  • Decode a number of new QuickTime and FLIR tags in MP4 videos +
  • Decode more Sony tags (thanks Jos Roost) +
  • Decode a new Olympus tag +
  • The API List option may now be used in the config file for the same effect + as -sep when combined with the -X, -j or -php option on the command line +
  • Fixed problem where some QuickTime string values could have terminating NULL + characters, which caused problems when renaming files using these tags +
+ +May 25, 2013 - Version 9.30 +
    +
  • Added a new Canon LensType +
  • Decode many Canon 700D CameraInfo tags +
  • Also delete null characters with the default advanced formatting filter +
  • Tolerate leading whitespace in HTML files +
  • Fixed decoding of Canon 650D CameraInfo FocalLength +
  • Fixed bug in new advanced formatting feature which gave incorrect + output when used in the -p option for processing multiple files +
+ +May 18, 2013 - Version 9.29 +
    +
  • Added another H264 Model value (thanks Rob Lewis) +
  • Added support for Canon 5DmkIII firmware version 1.2.1 +
  • Added recognition of IBM AVC video files +
  • Added a new CanonModelID +
  • Decode more FLIR tags (thanks Tomas) +
  • Decode H264 MDPM TimeCode +
  • More improvements to Sony LensType decoding (thanks Jos Roost) +
  • Extract information from the ASF Metadata Library in WMV files +
  • Extract ColorBalanceVersion for unknown Nikon ColorBalance information +
  • Updated some ID3 Genre names (thanks Mats Peterson) +
  • Fixed warning when using -p with a string containing a newline +
  • Fixed some incorrect Pentax Q LensType values +
+ +Apr. 21, 2013 - Version 9.28 +
    +
  • Added the ability to delete unknown JPEG APP segments by segment name +
  • Added a bunch of new ID3 Genre values (thanks Mats Peterson) +
  • Decode a few more Sony tags (thanks Jos Roost) +
  • Decode a few more tricky FLIR tags (thanks Tomas) +
  • Improved Dutch language translation (thanks Peter van der Laan) +
  • Patched to avoid warning in images where the AFMicroAdj data has been + truncated by Canon DPP +
  • Fixed -tagsFromFile and -v so they may now be used when writing via pipes +
  • Fixed writing of Panasonic LensType tags that were broken in the 9.15 update +
  • Fixed incorrect case for list type of XMP DocumentAncestors and TextLayers +
  • API Changes: +
      +
    • Allow a File::RandomAccess reference as an input to WriteInfo() +
    +
  • +
+ +Apr. 15, 2013 - Version 9.27 (production release) +
    +
  • Fixed "ARRAY ref" runtime error introduced in 9.25 that could occur when + using the -X option +
  • Fixed runtime warning which could occur when conditionally deleting XMP + structure +
+ +Apr. 13, 2013 - Version 9.26 +
    +
  • Added read support for FLIR FFF and FPF images and decode more FLIR tags +
  • Added some new Pentax LensType's and Nikon LensID's +
  • Added a few new Panasonic ContrastMode values +
  • Decode a number of Canon 6D tags +
  • Allow CanonRaw tags to be written using "CIFF" as a group name +
  • Improved decoding of Canon ColorData information for newer EOS models +
  • Improved decoding of a number of Sony tags (thanks Jos Roost) +
  • Removed index number from duplicate Composite TagID's in XML output +
  • Fixed byte-order problem for a few Nikon D5200 and D7100 tags +
  • Fixed incompatibility with old-style (pre-8.46) XMP user-defined structure + definitions +
+ +Apr. 6, 2013 - Version 9.25 (production release) +
    +
  • Added read support for FLIR thermal image metadata in JPEG images +
  • Added write support for DNG version 1.4 images +
  • Added a new Pentax DriveMode value and a new Pentax LensType +
  • Added two new Olympus CameraType values +
  • Added print conversion for XMP Flash tags to provide alternate language + support +
  • Decode a few more Nikon and Pentax tags +
  • Decode more Sony tags (thanks Jos Roost) +
  • Decode more Panasonic tags and changed decoding of others +
  • Enhanced -j and -php options to work with -D, -H and -l +
  • Improved German translations (thanks Herbert Kauer) +
  • Patched decoding of QuickTime date/time tags to accommodate Samsung and Sony + cameras that use an incorrect time zero of 1970 instead of 1904. This patch + will only work for videos produced before 2036, so hopefully Samsung and + Sony will fix this problem at their end before then (care to place a wager?) +
  • Fixed issues when using "-wm cg" and writing metadata as a block +
  • Fixed possible "division by zero" error when reading undefined XMP rational +
+ +Mar. 23, 2013 - Version 9.24 +
    +
  • Added ability to overwrite plus append output files (-w+!) +
  • Added support for Sigma X3F version 3.0 images +
  • Added a few new values for some Pentax tags +
  • Added a few new CanonModelID's +
  • Decode Nikon D5100 and D5200 custom settings plus a few other Nikon tags +
  • Allow the value for missing tags extracted with the -f option to be + configured via the API MissingTagValue setting (default is still "-") +
  • Improved decoding of Sony LensSpec (again, thanks Jos Roost) +
  • Fixed bug reading QuickTime extended-size atoms +
+ +Mar. 10, 2013 - Version 9.23 +
    +
  • Added -W (-tagOut) and -Wext (-tagOutExt) options to allow multiple tags + to be extracted to separate output files from a single source file +
  • Added append feature to -w (-w+) +
  • Added ability to extract SoundFile from Ricoh RMETA +
  • Added more SonyModelID and Sony LensType values and improved Sony LensType + decoding (thanks Jos Roost) +
  • Added a new Olympus LensType (thanks Niels Kristian Bech Jensen) +
  • Added another Pentax LensType +
  • Decode more Nikon flash information (thanks Alyda Gilmore for the samples) +
  • Decode Pentax Kelvin white balance tags (thanks Klaus Homeister) +
  • Extract PDF embedded image color space +
  • Improved Spanish translations (thanks Emilio Sancha) +
  • More patches to avoid "APP1 segment too large" errors when copying all tags + from some RAW images +
+ +Mar. 2, 2013 - Version 9.22 +
    +
  • Fixed problem extracting metadata from encrypted embedded JPEG images in PDF + files and added the ability to extract JPEG 2000 information too +
+ +Mar. 2, 2013 - Version 9.21 +
    +
  • Added ability to extract embedded images and their metadata from PDF files +
  • Added read support for binary-format PLIST files +
  • Added support for Sigma DP3 Merrill maker notes +
  • Added a few new Sigma LensType values +
  • Added a new FujiFilm PictureMode value +
  • Decode a number of new Pentax tags (thanks Klaus Homeister) +
  • Decode more Sony tags (thanks Jos Roost) +
  • Decode some new Nikon D800 tags (thanks Alyda Gilmore for the samples) +
  • Decode a number of new tags in 3GP videos +
  • Decode Pentax CameraType +
  • Made a few more DNG tags writable (but protected) +
  • Fixed problem reading XREF table of some PDF files +
  • API Changes: +
      +
    • The CombineInfo() routine is now deprecated because it is likely that + nobody ever used it. If anyone actually uses this, please let me know +
    +
  • +
+ +Feb. 20, 2013 - Version 9.20 +
    +
  • NOTICE: This release fixes a problem in the 9.19 Windows version that could + cause ExifTool to crash when writing metadata to some files (it seems that + one of the files in the 9.19 Windows package was corrupted) +
  • Added a new PentaxModelID +
  • Added write support for a few Getty Images XMP tags +
  • Decode Sony AFAreaModeSetting (thanks Jos Roost) +
+ +Feb. 20, 2013 - Version 9.19 +
    +
  • Added read support for Phase One IIQ maker notes +
  • Added a couple of new Minolta Teleconverter values +
  • Patched problem which could result in runtime warning when extracting + information from a file with an incorrectly formatted PreviewImage pointer +
  • Improved handling of unknown maker notes when writing to reduce the chance + of corruption (fixes problem of corrupted SilverFast maker notes) +
  • Fixed bug in HtmlDump where unused bytes at end of MakerNotes were not shown + if they came at the end of a TIFF-format file +
+ +Feb. 16, 2013 - Version 9.18 +
    +
  • Decode more AF information for Sony SLT models (thanks Andy Johnson for the + samples) +
  • Recognize CameraInfo and ColorData information from newer Canon 1DX firmware +
  • Organized support files in full Perl distribution into separate directories +
  • Improved German and Spanish translations (thanks Herbert Kauer and Emilio + Sancha) +
  • Fixed inconsistency where a priority tag could be hidden by a same-named tag + in the same group when using the -j or -X option combined with -g or -G +
  • Fixed problem in standard tests that could cause ExifTool test 25 to fail +
+ +Feb. 9, 2013 - Version 9.17 +
    +
  • Added PLIST and MODD to the list of supported file extensions +
  • Added track name to UserData tags within QuickTime tracks +
  • Added a new Pentax LensType (thanks Pietu Pohjalainen) +
  • Added a new Canon LensType +
  • Decode binary data in PLIST and MODD files +
  • Decode new Canon 1DX CustomFunctions +
  • Issue a minor warning and ignore duplicate PDF Info dictionaries unless the + -m option is used +
  • Improved date/time parsing when writing to allow single-digit fields +
  • Improved decoding/naming of a few Sony tags (thanks Jos Roost) +
  • Improved German translations (thanks Herbert Kauer) +
  • Changed a few PLIST tag names +
  • Fixed decoding of Olympus CameraType for some models +
  • Fixed problem calculating AvgBitrate for some video files +
  • Fixed problem writing Canon:LensSerialNumber +
+ +Feb. 2, 2013 - Version 9.16 +
    +
  • Added support for DarwinCore XMP tags +
  • Added support for CinemaDNG tags +
  • Added basic support for parsing XML PLIST information, and use this to + extract tags from QuickTime iTunesInfo Data +
  • Added a new Pentax lens (thanks Niels Kristian Bech Jensen) +
  • Added some new Sony E-mount lenses (thanks Jos Roost) +
  • Added a new NEFBitDepth value (thanks Jos Roost) +
  • Added a new CanonModelID +
  • Decode a few more Sony tags (thanks Jos Roost) +
  • Improved decoding of QuickTime iTunesInfo tags +
  • Improved Spanish translations (thanks Emilio Sancha) +
  • Improved handling of errors in Perl expression of new formatting feature +
  • Improved -p option to also handle structures +
  • Changed a number of Sigma lens names for Olympus to conform with official + Sigma model names (thanks Niels Kristian Bech Jensen) +
  • Moved the MWG XMP tags documentation to the MWG page +
  • Patched to allow reading GPX track logs with no version number +
  • Fixed problem reading an ID3 POPM frame with a missing counter +
  • Fixed bug which could cause "uninitialized value" runtime warning when + reading Nikon maker notes with an empty RetouchHistory +
  • API Changes: +
      +
    • Compatibility Notice: The MWG Composite tags are no longer automatically + loaded just by using the MWG module. Image::ExifTool::MWG::Load() must + now be called explicitly to load these tags +
    +
+ +Jan. 27, 2013 - Version 9.15 +
    +
  • Added advanced formatting feature to -p and -tagsFromFile options +
  • Added -echo3 and -echo4 options +
  • Added a few more Olympus LensType values, removed one, changed some lens + names for consistency (all thanks Niels Kristian Bech Jensen), and use + hexadecimal instead of decimal for numerical LensType values +
  • Added a number of new Sony E-mount lenses +
  • Added a new Tamron lens for Sony (thanks Marcin Krol) +
  • Trim trailing spaces from Panasonic LensType strings +
  • Fixed bug which could cause "Can't call method GetMarkerPointers" runtime + warning when writing certain types of corrupted images +
  • Fixed problem copying PrevewImage from some corrupted files +
  • Fixed problem identifying a Sigma lens for Nikon at some focal lengths +
  • API Changes: +
      +
    • Added AddUserDefinedTags() method +
    • Added formatting feature for tag values in SetNewValuesFromFile() +
    +
+ +Jan. 18, 2013 - Version 9.14 +
    +
  • Added -wm (-writeMode) option to provide control over tag write/create mode +
  • Added ability to use wildcards in target tag names when writing +
  • Added ability to read/write Jpeg2000 XML tag as a block +
  • Added ability to delete MPF segment (with -MPF:All=) +
  • Added a number of new Olympus lenses (thanks Niels Kristian Bech Jensen) +
  • Added a new Nikon LensID (thanks Robert Rottmerhusen) +
  • Added a number of new Pentax LensType's (thanks Alan Robinson for one) +
  • Added a few new CanonModelID's and Canon LensType's +
  • Decode ID3v2 POPM and OWNE frames +
  • Decode new Canon 6D CustomFunctions +
  • Improved calculation of ScaleFactor35efl for Canon cameras +
  • Changed priority of PDF Info tags so tags from most recent Info dictionary + take precedence (to partially accommodate the questionable Acrobat Pro + incremental update technique) +
  • Changed some verbose warnings when attempting to write "unsafe" tags +
  • Changed behaviour so that "unsafe" tags are not copied for any tag specified + using a wildcard (previously this was the behaviour for a tag name of 'all' + or '*', but not names like 'gps*') +
  • Fixed bug where a Composite tag could sometimes not be generated when the + -struct option was used if the tag was derived from an XMP List-type tag +
  • Fixed problem conditionally deleting GIF Comment and MIE tags +
  • Fixed decoding of RawImageWidth/Height from FujiFilm X-E1 RAF images +
  • API Changes: + +
+ +Jan. 10, 2013 - Version 9.13 (production release) +
    +
  • Added basic validation of ExifVersion and FlashpixVersion tags when writing +
  • Fixed problem where MPF PreviewImage was lost when editing metadata in JPEG + images from the Nikon D4, D600 or D800 +
+ +Jan. 2, 2013 - Version 9.12 (production release) +
    +
  • Fixed problem introduced in 9.10 preserving file modification date/time when + some options are used +
+ +Jan. 2, 2013 - Version 9.11 (production release) +
    +
  • Improved decoding of some Sony tags +
  • Changed 3 tag names to avoid a leading digit to fix XML validation problem +
  • Fixed bug introduced in 9.04 that could double-encipher some Sony MakerNote + information when writing (affected files are fixed by writing any tag with + ExifTool 9.11) +
+ +Dec. 29, 2012 - Version 9.10 +
    +
  • Added write support for a few new XMP-crs and XMP-photomech tags +
  • Added a new Samsung LensType (thanks Jaroslav Stepanek) +
  • Added a new Pentax LensType (thanks Helmut Schütz) +
  • Added a new Canon LensType +
  • Decode Sony A99 FocusMode (thanks Michael Tapes for the samples) +
  • Tolerate (but warn about) up to 4 bytes of garbage at start of EXIF segment +
  • Changed -P option to also preserve FileCreateDate on Windows (requires + Win32API::File::Time) +
  • Changed "[minor]" warning messages to capitalize the "M" (ie. "[Minor]") if + processing is affected when the warning is ignored +
  • Patched to avoid problem of slow processing with some corrupted EXIF +
+ +Dec. 15, 2012 - Version 9.09 +
    +
  • Added a few new Google XMP GPano tags +
  • Added a new Olympus CameraType +
  • Added a couple of new Minolta LensTypes +
  • Added two new Nikon LensID's (thanks David Püschel and Robert + Rottmerhusen) +
  • Decode Nikon D7000 AFPointsUsed and make this tag writable +
  • Decode a new Olympus tag (thanks Christoph Anton Mitterer) +
  • Renamed one of the FujiFilm RAF RawImageWidth/Height pairs to + RawImageFullWidth/Height +
  • Changed -stay_open when combined with -q to flush output after each command + (as already done without -q) (requires IO::Handle) +
  • Fixed problem shifting FileCreateDate when writing other "real" tags in the + same command +
+ +Nov. 26, 2012 - Version 9.08 +
    +
  • Fixed bug introduced in 9.07 that broke writing of FileModifyDate +
+ +Nov. 24, 2012 - Version 9.07 +
    +
  • Added ability to read/write FileCreateDate (Windows only) +
  • Added ability to read FileInodeChangeDate (non-Windows only) +
  • Added support for new tags in DNG 1.4 specification +
  • Added support for Google Photosphere GPano XMP tags +
  • Added a couple of new Olympus filter effects +
  • Changed a Panasonic LensType (thanks Olaf Ulrich) +
  • API Changes: +
      +
    • Enhanced SetFileModifyDate() to write FileCreateDate (Windows only) +
    +
+ +Nov. 17, 2012 - Version 9.06 +
    +
  • Added support for Nikon maker notes in images from any camera make (as + written by Capture NX2) +
  • Added support for FujiFilm X-E1 RAF images +
  • Added a new Olympus CameraType +
  • Added a new PentaxModelID and a new Pentax LensType +
  • Extract FileCreateDate (Windows) and FileInodeChangeDate (other systems) +
  • Fixed bug decoding UTF-16 ID3 synchronized lyrics +
+ +Nov. 10, 2012 - Version 9.05 +
    +
  • Added ability to read APE metadata from MP3 audio files +
  • Decode ID3 synchronized lyrics/text information +
  • Decode maker notes in Leica V-LUX40 MP4 videos +
  • Decode Sony A99 AFPointSelected (thanks Michael Tapes for the samples) +
  • Improved decoding of some Sony tags (thanks Jos Roost) +
  • API Changes: +
      +
    • Removed GeoNoInterpolate option (just set GeoMaxIntSecs to 0 instead) +
    +
+ +Nov. 3, 2012 - Version 9.04 (production release) +
    +
  • Added two new Sony LensType values (thanks Matthias Paul) +
  • Added a few new Canon LensType values +
  • Added a couple of new PentaxModelID's and decode some new K-5 II values +
  • Added support for some new XMP tags written by the Apple iPhone 5 +
  • Added a new Olympus CameraType +
  • Decode more Sony tags/values (thanks Jos Roost) +
  • Decode Nikon HDRInfo (thanks Stefan) +
  • Decode some FlashInfo tags for new Nikon models +
  • Decode a few WM ID3 tags (some documentation on these would be nice) +
  • Fixed bug which could cause truncated/garbage ID3v2 strings to be returned +
  • Fixed -globalTimeShift option to also work when copying tags +
  • Fixed decoding of Nikon AFFineTuneAdj for FirmwareVersion 1.10B (thanks + Michael Tapes for the samples for this and the A77) +
  • Fixed problem where a few tags (FileSequence, NewGUID and Now) were not + available for use with the -p option +
  • API Changes: +
      +
    • Added RequestAll and GeoNoInterpolate options +
    • Fixed problem in SetNewValue when setting the Raw value of some tags +
    +
+ +Oct. 13, 2012 - Version 9.03 +
    +
  • Added new feature to provide control over directory levels in %d strings +
  • Added ability to write OtherImage in NEF images +
  • Added a new Pentax LensType +
  • Added a few new CanonModelID's (thanks Laurent Clévy) +
  • Added a new Nikon LensID (thanks Geert De Soete) +
  • Added a few new Olympus CameraType values +
  • Decode some new CameraInfo tags for the Canon EOS 650D +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Improved decoding of some Sigma tags for the DP1/DP2 Merrill +
  • Give priority to EXIF tags over SigmaRaw tags X3F images +
  • Changed Samsung lens names to include "NX" (thanks Jaroslav Stepanek) +
  • Fixed misleading verbose "TAG is not writable" messages when copying + list-type tags +
  • API Changes: +
      +
    • Enhanced GetValue() to allow return of 'Rational' value +
    +
+ +Sept. 6, 2012 - Version 9.02 +
    +
  • Added a new Nikon LensID (thanks Joseph Heled) +
  • Added a new EXIF SubFileType value used in DNG images +
  • Added write support for Apple Adjustment Settings XMP tags (XMP-aas) +
  • Added a couple of new Samsung LensType values (thanks Jaroslav Stepanek) +
  • Added a couple of new Canon LensType values and a new CanonModelID +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Enhanced "-o -" feature to allow output file type to be specified +
  • Extract last file access time as FileAccessDate +
  • Allow tags to be set from files which are zero bytes in size +
  • Made ProfileHueSatMap tags Binary if they are too long +
  • Changed names of some PanasonicRaw DistortionInfo tags +
  • Changed decoding for a Sony ExposureMode value +
  • Fixed hang/crash that could occur when writing to an image with corrupted + Sony MoreInfo data (eg. SLT-A55V JPEG corrupted by GIMP) +
+ +Aug. 25, 2012 - Version 9.01 (production release) +
    +
  • Added a couple of new CanonModelID values +
  • Added a couple of new Canon LensType values (thanks Pascal de Bruijn) +
  • Added a new PentaxModelID and a few new Pentax PictureMode values +
  • Decode a new Pentax ISO tag +
  • Improved -listx output for XMP structure tags +
  • Fixed "unexpected end of file" problems with some compressed MIE files +
+ +Aug. 18, 2012 - Version 9.00 +
    +
  • Added support for PDF encryption V5.6 (new in Adobe Reader X) +
  • Added a few new XMP-cc tags and changed a few others to rdf:resource type +
  • Added a new Sony LensType and values for other Sony tags (thanks Jos Roost) +
  • Added a new Nikon LensID +
  • Added a new Panasonic LensType (thanks Olaf Ulrich) +
  • Added patch to fix simple XMP tags written incorrectly as lang-alt type +
  • Decode some Panasonic RW2 lens distortion correction tags +
  • Decode some WEBP image characteristics from the VP8 bitstream +
  • Decode more Leica MakerNote information +
  • Calculate CurrentIPTCDigest for IPTC in PostScript files +
  • Changed the names of a couple of WBShift tags +
  • Improved parsing of -if expressions to interpret a dash after a tag name as + a minus sign instead of part of the tag name +
  • Patched problem with conditional deletion of an incorrectly null-terminated + JPEG Comment +
  • Fixed hang bug when reading unsupported Microsoft Xtra information in MOV + videos +
+ +Aug. 3, 2012 - Version 8.99 +
    +
  • Added patch to avoid "Error renaming temporary file" errors in Windows +
  • Decode some new Sony tags and values (thanks Mike Reit and Jos Roost) +
  • Improved Italian translation (thanks Michele Locati) +
  • Improved decoding of H264 ImageStabilization +
  • Changed names of PanasonicRaw ImageWidth/Height tags, and added new + Composite tags to calculate actual size of RW2 images +
  • Fixed "Corrupted Ricoh RMETA data" warning for images from some Ricoh models +
  • Fixed problem writing information to some EPS images +
+ +July 28, 2012 - Version 8.98 +
    +
  • Added a new Pentax LensType and two new PentaxModelID's +
  • Added a new CanonModelID and a new Olympus CameraType +
  • Added a new Composite Duration tag for Vorbis audio files +
  • Added more elements to Microsoft Regions XMP structure and fixed tag name + documentation for this +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Changed name of Minolta BatteryLevel tag to BatteryState +
  • Patched problem with conditional deletion of IPTC string-type tags which are + incorrectly null terminated (eg. written by Picasa 2.0) +
  • Fixed problem copying Canon 5DmkIII MakerNotes from CR2 to JPEG images +
  • Fixed runtime error when writing some images with corrupted EXIF +
+ +July 6, 2012 - Version 8.97 +
    +
  • Added a new Canon LensType +
  • Added support for GPX attitude information as written by Arduino +
  • Added write support for XMP-expressionmedia:CatalogSets +
  • Made CFARepeatPatternDim and CFAPattern2 writable but protected +
  • Minor improvement to decoding of Sony FaceInfo +
  • Fixed problem reading some GPX track logs +
+ +June 30, 2012 - Version 8.96 +
    +
  • Added -globalTimeShift option +
  • Added new values for a couple of Nikon tags (thanks Michael Relt) +
  • Added a few new Sony PictureEffect values +
  • Added a new Olympus LensType +
  • Decode a new Sony A100 tag and improved/renamed some others (thanks Igal + Milchtaich) +
  • Changed -restore_original and -delete_original options to scan directories + only for writable file types +
  • Enhanced -srcfile option to allow multiple source files to be specified +
  • Patched possible round-off problem when extracting rational values +
  • Fixed bug which could cause runtime error when reading some HTML files and + improved reliability when extracting HTML "meta" tags +
  • API Changes: + +
+ +June 16, 2012 - Version 8.95 +
    +
  • Added a few new Sony PictureEffect values +
  • Added a new Olympus lens type (thanks Niels Kristian Bech Jensen) +
  • Improved decoding of Canon IntelligentContrast +
  • Improved user-defined lens logic to attempt to choose the best matching + user-defined lens if more than one is possible +
+ +June 9, 2012 - Version 8.94 +
    +
  • Added ability to read/write IPTC as a block +
  • Added a few Nikon LensID's (thanks Mike Pollock and Robert Rottmerhusen) +
  • Added a new Olympus LensType (thanks Brad Grier) +
  • Added new values for a few Olympus tags +
  • Decode more Sony tags (thanks Jos Roost and Igal Milchtaich) +
  • Decode Canon IntelligentContrast and add a new CanonModelID +
  • Changed names of Canon Sort/LongFocal tags to Min/MaxFocalLength +
+ +May 26, 2012 - Version 8.93 +
    +
  • Added some new Nikon RetouchHistory values +
  • Added a couple of new Pentax LensType values +
  • Added some new Olympus MagicFilter and LensType values +
  • Added a new CanonModelID +
  • Decode more Sony tags (thanks Jos Roost) +
  • Decode some MakerNote information in Olympus E-M5 MOV videos +
  • Decode a couple more Canon tags +
  • Patched to overcome formatting problems in Samsung NX200 JPEG maker notes +
+ +May 12, 2012 - Version 8.92 +
    +
  • Added read support for PCD (Kodak Photo CD Image Pac) files +
  • Added Geotag support for Winplus Beacon text-format GPS log files +
  • Added support for Leica X2 MakeNotes +
  • Added NewGUID tag +
  • Decode Panasonic ManometerPressure tag (thanks Christoph Mitterer) +
  • Decode more Sony tags (thanks Jos Roost) +
  • Changed a few Canon-mount Tokina lens model names for consistency +
+ +May 5, 2012 - Version 8.91 +
    +
  • Added -progress option +
  • Added support for XMP fpv namespace +
  • Added a new Canon EasyMode value and fixed an incorrect one +
  • Added a couple of new Canon LensTypes +
  • Decode a number of new tags for the Canon 1DX and 5DmkIII +
  • Improved the names of a few Sony tags (thanks Jos Roost) +
  • Fixed -sep option to apply to interpolated tag values in a string when + copying +
+ +Apr. 28, 2012 - Version 8.90 (production release) +
    +
  • Added ability to fix double-UTF-encoded embedded XMP +
  • Added a warning for invalid XMP +
  • Added a new Minolta/Sony LensType (thanks Matthias) +
  • Added a new values for some Canon tags +
  • Decode ColorBalance information for a few more Nikon models +
  • Ignore trailing whitespace when writing converted values +
  • Enhanced the -z option to avoid writing the 2424 bytes of padding in XMP +
  • Improved decoding of some Sony MakerNotes tags (thanks Jos Roost) +
  • Improved "best guess" for fixing corrupted makernote offsets of some Sony + models +
+ +Apr. 21, 2012 - Version 8.89 +
    +
  • Added new Nikon and Ricoh LensID's +
  • Added a new Olympus CameraType +
  • Added new Canon LensType, EasyMode and CanonModelID values +
  • Added new Pentax PictureMode and PentaxModelID values +
  • Added support for IDimager XMP tags +
  • Added a number of new XMP-crs tags used by LR4 +
  • Decode a few more QuickTime tags +
  • More improvements decoding Minolta/Sony CameraSettings (thanks Jos Roost) +
  • Enhanced -ext option to allow files with any extension to be processed +
  • Increased maximum number of SubIFD's to accommodate some DNG 1.4 images +
  • Lowered priority of JPEG APP12 PictureInfo tags when reading +
  • Created mechanism to allow self-referential XMP structures +
+ +Apr. 15, 2012 - Version 8.88 +
    +
  • Added a new Canon LensType (thanks Gerald Erdmann) +
  • Decode a number of new Olympus tags and values +
  • Decode a few more QuickTime tags +
  • Many more improvements and additions to Sony decoding (thanks Jos Roost) +
  • Changed Ricoh InternalSerialNumber to also convert numerical value +
  • Removed the ability to create IFD1 in TIFF-format images (you shouldn't + really do this anyway) +
  • Fixed incorrect IFD number in some error messages when writing +
+ +Apr. 9, 2012 - Version 8.87 +
    +
  • Added a new PentaxModelID +
  • Added new values for some Panasonic tags +
  • Added a couple of new Canon LensTypes +
  • Decode a few more Sony tags and values (thanks Jos Roost) +
  • Decode more CanonVRD tags +
  • Decode makernotes from Pentax WG-2 GPS MOV videos +
  • Changed Panasonic AdvancedSceneMode to a Composite tag +
  • Fixed problem introduced in 8.70 where excluding groups from deletion didn't + work when copying back tags in the same command +
  • Fixed problem repairing incorrect makernotes offsets in JPEG images from + Sony SLT and NEX cameras +
+ +Apr. 3, 2012 - Version 8.86 +
    +
  • Added a few new values for some Panasonic tags +
  • Added a new CanonModelID and a new Canon LensType +
  • Added a new Nikon LensID +
  • Decode more Sony CameraSettings3 information (thanks Jos Roost) +
  • Decode another Canon 5D tag +
  • Decode some new CanonVRD DLO tags +
  • Changed decoding of CanonVRD VRDVersion tag +
  • Changed formatting of a Pentax LensType for consistency with other lenses +
  • Patched decoding of Reconyx:DateTimeOriginal to accommodate values written + with an incorrect byte order by some models +
+ +Mar. 25, 2012 - Version 8.85 (production release) +
    +
  • Added a couple more Olympus CameraType values +
  • Added two new Pentax LensType's and a PentaxModelID +
  • Decode a number of new Sony CameraSettings3 tags (thanks Jos Roost) +
  • Decode a few new Pentax K-01 tags +
  • Decode new custom functions of the Canon 5D Mark III +
  • Recognize another non-standard APP1 XMP header +
  • Increased unrolled depth of XMP-mwg-kw:HierarchicalKeywords from 4 to 6 +
  • Extended "-charset exif=CHARSET" to also apply to EXIF UserComment when + stored as ASCII +
  • Changed name of Olympus MaxApertureAtCurrentFocal to to MaxAperture +
  • Patched to avoid possibility of unnecessary "references previous directory" + warning when the length of one directory is zero +
+ +Mar. 17, 2012 - Version 8.84 +
    +
  • Added a few more SonyModelID's (thanks Jos Roost) +
  • Added a new CanonModelID and a number of new Canon LensType values +
  • Added a new Minolta/Sony LensType +
  • Decode CameraTemperature for a number of new Canon PowerShot models +
  • Decode information from PANA atom of Panasonic DMC-FT20 MP4 videos +
  • Decode a bit more of the Casio MakerNotes +
  • Improved Polish translations for EXIF information (thanks Kacper Perschke) +
  • Changed some warning messages for invalid IFD entries +
  • Patched to allow writing of Sony MakerNotes containing invalid IFD entries +
+ +Mar. 13, 2012 - Version 8.83 +
    +
  • Added a new SonyModelID and a new Nikon LensID (thanks Gregg Lee and Jos + Roost) +
  • Added Finnish translations (thanks Jens Duttke and Jarkko Mäkineva) +
  • Fixed the Composite:LensID problem properly this time (with any luck) +
+ +Mar. 13, 2012 - Version 8.82 +
    +
  • Added ability to extract information from PostScript-type DFONT files +
  • Added a new Minolta/Sony LensType (thanks Jos Roost) +
  • Improved geotagging of orientation information when extrapolating past end + of track +
  • Changed behaviour while copying information to allow flattened tags to be + specified without the need to use the --struct option +
  • Removed unnecessary warning when writing PreviewImage to Ricoh DNG file +
  • Fixed problem introduced in 8.81 which prevented generation of the Composite + LensID for Nikon images when duplicate tags were disabled +
  • API Changes: +
      +
    • Added NoFlat option to SetNewValues() +
    • Changed Struct option to allow copying of both structured and flattened + tags at the same time +
    +
+ +Mar. 9, 2012 - Version 8.81 +
    +
  • Added some new Canon, Pentax and Sony/Minolta LensType's +
  • Added a few new FujiFilm PictureMode values (thanks Kai Lappalainen) +
  • Added some new FujiFilm FilmMode values +
  • Added a couple of new CanonModelID values +
  • Added local timezone message to -v2 geotagging output +
  • Made all Pentax LensType tags writable +
  • Improved Composite LensID logic to use Sony LensSpec value if available +
  • Fixed problem opening files with path names that begin with "&" +
+ +Feb. 25, 2012 - Version 8.80 +
    +
  • Added a new Olympus CameraType +
  • Improved geotagging to tolerate out-of-sequence and missing NMEA sentences +
  • Increased the maximum XMP tag ID length to 250 characters to allow very deep + user-defined structure hierarchies +
+ +Feb. 20, 2012 - Version 8.79 +
    +
  • Avoid deleting the JPEG APP14 Adobe segment when deleting all metadata +
  • Added ability to read/write/create JPEG APP14 Adobe segment as a block +
  • Added some new CanonModelID values +
  • Added another Panasonic WhiteBalance value (thanks PeterK) +
  • Decode Panasonic ColorTempKelvin tag +
  • Decode information from Qualcomm APP7 JPEG segment +
  • Extract PreviewImage for a few more uncommon camera models +
  • Strengthened MP3 file recognition to avoid mis-identification of some files +
  • Fixed problems reading "sfnt" resource in some DFONT files +
  • Fixed problems writing some LensType values for 3rd-party lenses +
+ +Feb. 11, 2012 - Version 8.78 +
    +
  • Added basic read support for a few obscure audio formats (LA, OFR, PAC, WV) +
  • Added a couple more Canon LensType values +
  • Decode some new Kodak tags in MP4 videos +
  • Patched timezone problem on MirBSD due to leap-second "feature" of this OS +
  • Fixed problem converting Adobe XMP LensID's for Pentax lenses +
  • Fixed runtime warning due to conflict with some Vorbis tag ID's +
  • Fixed problem which could result in duplicate columns in -csv output when + used with -f and the "#" suffix on a tag name +
  • API Changes: + +
+ +Jan. 27, 2012 - Version 8.77 (production release) +
    +
  • Added some new and updated some existing Sony/Minolta LensType values +
  • Added two missing Minolta Teleconverter values +
  • Added a new Canon LensType +
  • Decode Olympus ArtFilterEffect +
  • Enhanced -c (CoordFormat) option to allow signed coordinate output +
  • Changed -sort option to always sort -json and -X outputs by tag name +
  • Minor change to an Olympus LensType name (thanks Niels Kristian Bech Jensen) +
  • Fixed problem geotagging orientation information from PTNTHPR sentence +
  • Fixed decoding of negative Pentax EffectiveLV values +
  • Fixed typo in an Olympus LensType +
+ +Jan. 18, 2012 - Version 8.76 +
    +
  • Added -sort option to sort output by tag name or description +
  • Added support for FujiFilm RAF version 1.03 images and downgraded RAF + version error to a warning +
  • Added a number of new Minolta/Sony LensType's +
  • Added a new CanonModelID +
  • Decode FocusPosition for Sony A850 and calculate Composite FocusDistance +
  • Decode IFD found in some Samsung Type1 maker notes +
  • Patched Olympus test to fix failure on some platforms +
  • Patched -json output to filter out invalid UTF-8 characters +
  • API Changes: +
      +
    • Added Sort2 option and 'Descr' setting for Sort option +
    • Added secondary sort option to GetFoundTags() and GetTagList() +
    • Changed name of Sort 'Alpha' setting to 'Tag' (but 'Alpha' still works + for backward compatibility) +
    +
+ +Jan. 8, 2012 - Version 8.75 (production release) +
    +
  • Added -php output option (thanks Marcel) +
  • Decode another AIFF tag and handle character encoding in AIFF text values +
  • Recognize PHP files +
  • Enhanced Geotag feature to write speed/track from NMEA GPRMC sentence, and + orientation information from Honeywell NMEA PTNTHPR sentence +
  • Changed verbose XMP output to print raw values +
  • Lowered default priority of "avoided" tags so they don't override other + same-named tags when reading with duplicate tags disabled +
  • Patched tests to ignore MirBSD leap-second unconformity +
  • Patched ZIP module to avoid failed tests with Perl 5.6.2 on GNU/Linux 2.6 +
  • Fixed problem reading xref table of some PDF files created by PScript5.dll +
  • Fixed problem reading RicohSubdir from AVI videos of the GR Digital 4 +
+ +Dec. 28, 2011 - Version 8.74 +
    +
  • Added read/write support for Hasselblad FFF images +
  • Added iptcCore.args convenience file to the distribution package +
  • Catch CONT signal to allow calling applications to trigger an immediate + response (avoiding a delay of up to 0.01 sec) after writing arguments to a + -stay_open ARGFILE +
  • Protect against some infinite loops that could be created when using some of + the advanced exiftool options +
  • Improved decoding of Samsung PictureWizard (thanks Pascal de Bruijn) +
  • Improved handling of bad IFD entries in -htmlDump output +
  • Changed print conversion of EXIF:FNumber and XMP:FNumber to use 2 decimal + digits for values less than 1.0, and disable conversion for invalid values +
  • Tightened up the -stay_open feature to fix a few potential problems +
  • Fixed bug using -csv+= or -json+= for non-list-type tags +
  • Fixed problem deleting unknown makernotes as a block +
  • API Changes: +
      +
    • Enhanced SetNewValue() AddValue option to allow this option to be + ignored for non-list tags +
    +
+ +Dec. 16, 2011 - Version 8.73 +
    +
  • Added read support for OpenEXR and Radiance RGBE images +
  • Added a couple of new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added a new PentaxModelID +
  • Added a new Olympus CameraType +
  • Created new FileSequence tag for use in batch processing +
  • Decode maker notes from Pentax Optio RZ18 AVI videos +
  • Tolerate unrecognized IPTC records (but still issue warning) +
  • Changed ScaleFactor35efl calculation to also use Pentax SensorSize +
  • Minor changes to two Samsung lens names (thanks Pascal de Bruijn) +
+ +Dec. 8, 2011 - Version 8.72 +
    +
  • Added support for reading XMP from INX files +
  • Added PDF HasXFA tag +
  • Added a new XMP Colorants field (not in 2010 XMP specification) +
  • Decode Casio BestShotMode for yet more cameras +
  • Decode a few more Casio ImageStabilization values +
  • Decode a few more Olympus tags and added conversion for CameraType +
  • Protect against reading insanely large XMP (> 300 MB) in INDD files +
  • Extract large (> 64 kB) unknown XMP tags as binary data +
  • Reduced memory requirements for XMP processing (by 1/10) +
  • Fixed another place where empty XMP structures could hide (in lists) +
+ +Nov. 19, 2011 - Version 8.71 +
    +
  • Added two new Olympus LensType values (thanks Martin Hilbers) +
  • Avoid recreating duplicate groups when deleting whole groups and adding back + tags in the same step +
  • Fixed problem where the QuickTime -charset option didn't work for some tags +
  • Fixed bug introduced in 8.69 which could cause excessive memory usage when + reading QuickTime videos with the -u option +
  • Fixed problem where existing empty XMP structure couldn't be deleted or + overwritten as a structured tag +
+ +Nov. 15, 2011 - Version 8.70 +
    +
  • Compatibility Notice: Changed order of operations when batch processing with + -tagsFromFile option to be consistent with non-batch mode +
  • Added -listItem option +
  • Added read support for IDML files +
  • Added a new Canon LensType (thanks Jon Charnas) +
  • Added a couple of new Samsung LensType's (thanks Tae-Sun Park) +
  • Added support for another DigiKam XMP tag +
  • Decode a couple more ID3 tags +
  • Decode Casio BestShotMode for more cameras +
  • Improved decoding of Casio AFMode +
  • Extract unknown FLAC blocks as binary data +
  • Changed ITC:ImageType to make "numerical" value more friendly +
  • Changed priority of two unreliable Samsung tags +
  • Fixed bug where ExifTool could produce improperly formatted XMP when writing + structure elements to a previously empty XMP structure (the empty XMP + structure was not being properly deleted). Affected XMP may be repaired by + re-writing any element of the structure with this version of ExifTool +
  • API Changes: +
      +
    • Added ProtectSaved option to SetNewValue() and return save count from + SaveNewValues() +
    +
+ +Nov. 9, 2011 - Version 8.69 +
    +
  • IMPORTANT: Fixed bug which could corrupt GIF images when writing a Comment + to a GIF image containing XMP metadata +
  • Added ability to read/write ICC_Profile in GIF images +
  • Added ability to specify internal encoding of EXIF "ASCII" strings and + QuickTime strings +
  • Added a new DigiKam XMP tag +
  • Documented -echo option (has been an undocumented feature since 6.86) +
  • Decode a number of new Sony tags +
  • Decode a few new Pentax tags and added a few new values +
  • Decode a few new QuickTime and ID3 tags +
  • Decode Casio BestShotMode for a number of models +
  • Improved validity checking of ICC_Profile segments in JPEG image +
  • Tolerate UTF-8 byte order mark (BOM) in input CSV and JSON files +
  • No longer trim trailing spaces from arguments in -@ argfiles +
  • Upgraded Windows executable version to use PAR 1.002 +
  • Changed priority of the Sony DynamicRangeOptimizer tags +
  • Changed MWG feature to use UTF8 encoding for EXIF strings by default +
  • Changed the -b option to avoid loading large binary values for tags that + have been excluded with the -x option or --TAG +
  • Changed Canon AFMicroAdjActive to AFMicroAdjMode and improved decoding +
  • Fixed problem where the PreviewImage could be lost when writing to images + from some newer Sony cameras +
  • Fixed problem reporting duplicate information when -if used with -TAG# +
  • Fixed incorrectly written XMP-tiff:YCbCrSubSampling tag +
  • Fixed problem opening files with names beginning and/or ending with some + characters such as SPACE, '>', '<' and '|'; however file names ending + with '|' are still not allowed +
  • API Changes: +
      +
    • Added CharsetEXIF and CharsetQuickTime options +
    +
+ +Oct. 21, 2011 - Version 8.68 +
    +
  • Added a new CanonModelID and a new SonyModelID +
  • Added new Canon and Pentax LensType's +
  • Decode more makernote information from Nikon MOV videos +
  • Improved decoding of Sony LensSpec and enabled writing of this tag +
  • Overhauled Minolta/Sony LensType list for consistency with official Sony + lens names and removed a couple of anomalous entries (thanks Jos Roost) +
  • Fixed problem with negative temperatures in Reconyx makernotes +
  • Fixed bug which could cause runtime warnings when -f used with -X and -l +
  • Fixed some minor problems when using -X with MWG option +
  • Fixed issue where some missing tags could be printed when -f option was used + in combination with wildcard tag names +
+ +Oct. 13, 2011 - Version 8.67 +
    +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Decode tags from FujiIFD in HS10 and X100 RAF images +
  • Decode LocationInfo tags from Nikon maker notes +
  • Decode GPS tags from Nikon MOV videos +
  • Decode information from Microsoft "Xtra" atom in QuickTime files +
  • Decode Sony LensSpec information (thanks Jos Roost) +
  • Use more specific MakerNotes names in warning messages and verbose output +
  • Updated Canon CustomFunctions for the EOS 600D and 1100D +
  • Improved handling of some corrupted RIFF files +
  • Improved decoding of Samsung manual lens types (thanks Pascal de Bruijn) +
  • Changed "No writable tags found" warning to "No writable tags set from" +
  • Fixed problem handling resource forks in newer versions of OS X +
  • Fixed problem writing XMP as a block to Jpeg2000 images +
  • Fixed problem which could cause XMP and IPTC to be ignored when using MWG + feature with TIFF images and performing multiple operations in a single + command +
+ +Oct. 3, 2011 - Version 8.66 +
    +
  • Added the ability to use "$GROUP:all" in -if and -p expressions (evaluates + to "1" if any tag exists in the specified group, or "0" otherwise) +
  • Added a new Sony/Minolta LensType (thanks Florian Knorn) +
  • Added list of recommended modules to Perl installation +
  • Decode ColorBalance information for a few new Nikon models +
  • Updated Canon CustomFunctions for the EOS 600D and 1100D +
  • Fixed problem writing "now" to MWG date/time tags +
+ +Sept. 24, 2011 - Version 8.65 (production release) +
    +
  • Added a few new CanonModelID's +
  • Added a new Sony/Minolta LensType +
  • Added a new Canon LensType (thanks Klaus Reinfeld) +
  • Added a number of new Olympus ArtFilter/MagicFilter values +
  • Included new .args files in distribution: exif2iptc.args and iptc2exif.args +
  • Enhanced writing of date/time tags to recognize "now" for the current time +
  • Improved decoding of H264 Gain +
  • Minor improvement to -htmlDump for some invalid IFD entries +
  • Allow PostScript date/time tags to be written without the -n option +
  • Allow NikonCapture:ExposureAdj2 to be written without the -n option +
  • Fixed problem introduced in version 8.62 where DateTimeOriginal in IFD0 of + NEF images was no longer updated when shifting times +
  • Fixed problem where keywords could be duplicated when exporting to XMP while + using the MWG module +
  • Fixed problem reading PDF images with extra whitespace before xref table +
  • Fixed format problem in CSV output for filenames containing a comma or quote +
  • Fixed problem reading concatenated AVI videos +
+ +Sept. 10, 2011 - Version 8.64 +
    +
  • Added 2 new ACDSee XMP tags (thanks Hannes Leubbers) +
  • Added a new Sony FileFormat value +
  • Added a new CanonModelID +
  • Added a few new Pentax DigitalFilter and ImageTone values +
  • Enhanced -execute option to allow a command ID number to be added +
  • Enhanced -csv and -json import features to also key on canonical SourceFile + path (requires Cwd module) +
  • Improved Composite LensID logic for some Sony cameras +
  • Fixed misleading error message when using -if option on file that doesn't + exist +
  • Fixed problems decoding a number of inconsistent tags in the Sigma SD1 maker + notes +
+ +Aug. 27, 2011 - Version 8.63 +
    +
  • Added support for a number of new Open Document file extensions +
  • Added a few new CanonModelID and SonyModelID values +
  • Added a new Ricoh GXR LensID +
  • Added a new Sony/Minolta LensType (thanks Mladen Sever) +
  • Added patch to read the improperly formatted DateTimeOriginal in AVI videos + written by the Kodak Easyshare Sport camera +
  • API Changes: + +
+ +Aug. 21, 2011 - Version 8.62 - "JPEG2000 Update" +
    +
  • Added read support for JPEG2000 codestream format (J2C) +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added a few new Pentax LensType's +
  • Added a few new Sony/Minolta LensType's (thanks Wolfram for 2 of these) +
  • Added two new Sony Teleconverter values (thanks Wolfram) +
  • Decode a few more JPEG2000 UUID's written by Adobe JPEG2000 plugin +
  • Decode additional JPEG2000 ColorSpecification information +
  • Recognize a few more JPEG2000 file extensions +
  • Updated some CanonModelID's +
  • Tolerate extra comma at end of line in imported -csv files +
  • Changed name of Kodak Type9 SerialNumber tag to UnknownNumber +
  • Fixed bug which in rare situations could result in an erroneous "IFD pointer + references previous IFD" warning +
  • Fixed another memory leak when writing and removed circular references from + ExifTool object to prevent future bugs like this +
  • Fixed problem in Windows where values in the -X (XML) output containing + CR+LF were converted to CR+CR+LF +
  • Fixed superfluous warning which could occur when using += to decrement a + numerical tag +
  • Fixed an incorrectly spelt Pentax city name (thanks John Francis) +
+ +July 16, 2011 - Version 8.61 +
    +
  • Added the ability to increment/decrement tags with numerical values using += +
  • Added support for Extensis Portfolio XMP tags plus a number of non-standard + and/or undocumented XMP-xmp and XMP-xmpMM tags +
  • Added read support for Microsoft Compiled HTML (CHM) format +
  • Added read support for Ogg Video (OGV) files +
  • Added new LensType values for Pentax (thanks Heike Herrmann), Sony/Minolta + (thanks Fabio Suprani and Florian Knorn), Nikon (thanks Jens Kriese), + Olympus and Sigma cameras +
  • Added a new QuickTime VendorID +
  • Recognize DEX (Dalvik Executable) files +
  • Identify Windows 64-bit EXE/DLL files and relax EXE validation +
  • Validate date/time values when reading NMEA GPS log files +
  • Changed decoding of CFAPattern to return a string of numbers with -n option +
  • Extract all unknown makernote blocks as undef, regardless of actual format +
  • Improved print conversion of Pentax ShakeReduction +
  • Fixed problem processing some Ogg files with multiple streams +
  • Fixed incorrect namespace URI for stArea (used by MWG 2.0 regions) +
  • Fixed problem with spaces in -geotag path when using wildcards +
  • Fixed problem writing PDF:Keywords list items individually if they contain + special characters +
  • API Changes: +
      +
    • Enhanced SetNewValue() to allow increment/decrement of numerical tags +
    +
+ +June 25, 2011 - Version 8.60 (production release) +
    +
  • Added Composite Flash tag to facilitate copying of flash information between + XMP and EXIF +
  • Added new Pentax and Canon LensType values and fixed a Pentax lens name +
  • Added a few new Leica LensType's (thanks Olaf Ulrich) +
  • Added a new PentaxModelID +
  • Enhanced GPSDateStamp conversion to tolerate null separators (Casio EX-H20G) +
  • Made DNG LinearizationCurve and Nikon ContrastCurve writable but protected +
  • Renamed Nikon LinearizationTable to NEFLinearizationTable and made writable + but protected +
  • Removed Leica M8 FrameSelector tag since it seems to have evolved into an + extension of the LensType tag for newer lenses +
  • Fixed problem with order of operations when using multiple -if options +
+ +June 11, 2011 - Version 8.59 +
    +
  • Added new Composite:LensID derived from XMP-aux:LensID +
  • Added new PentaxModelID and CanonModelID values +
  • Added a new Pentax LensType (thanks Artur) +
  • Decode maker notes in Pentax Optio S1 AVI videos +
  • Extract PreviewWMF from DOCX files +
  • Recognize WMF images +
  • Fixed decoding of CanonVRD WBAdjRGBLevels and renamed to WBAdjRGGBLevels +
+ +June 2, 2011 - Version 8.58 +
    +
  • Decode a number of CameraInfo tags for the Canon EOS 600D and 1100D +
  • Improved speed by a factor of 2 when reading M2TS videos +
  • Fixed memory leak with -stay_open feature when writing +
+ +May 26, 2011 - Version 8.57 +
    +
  • Added a couple of new Canon LensType values +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added format string to -v2 output for IPTC tags +
  • Added extra logic to avoid misidentifying unknown IFD-style maker notes +
  • Decode custom settings for Nikon D700 and D7000 +
  • Fixed problem recognizing NikonCaptureData for ViewNX version 2.1.1 +
+ +Apr. 16, 2011 - Version 8.56 +
    +
  • Added a new Canon LensType (thanks Rodolfo Borges) +
  • Decode EXIF information in FujiFilm HS20EXR MOV videos +
  • Decode NikonCaptureEditVersions when ExtractEmbedded option is used + (previously called NikonCaptureHistory) +
  • Decode another Samsung tag (thanks Tae-Sun Park) +
  • Recognize CaptureOne ".newer" COS files +
  • Reverted JSON output to pre-8.51 behaviour by removing '#' suffix from tag + names when print conversion is disabled on a per-tag basis +
  • Fixed bug introduced in 8.32 interpreting some expressions when copying tags +
+ +Apr. 11, 2011 - Version 8.55 +
    +
  • Added write support for FujiFilm RAF version 0716 images +
  • Added support for a number of new LR3 XMP tags (thanks Wolfgang Guelcker) +
  • Decode some more Samsung tags (thanks Tae-Sun Park) +
  • Improved handling of incorrectly formatted XMP +
  • Recognize a few alternate PS and EPS file extensions (thanks Jeff Harmon) +
  • Reverted a few Pentax macro lens names (less consistent, but at least they + match the official Pentax names) +
  • Fixed problem reading some XMP custom properties +
  • Fixed minor problem in HtmlDump output for Canon MakerNotes footer +
+ +Apr. 2, 2011 - Version 8.54 +
    +
  • Added a number of new values for various tags +
  • Added a new Nikon LensID +
  • Decode a number of encrypted Samsung SRW tags (thanks Tae-Sun Park) +
  • Enhanced -s option so allow a number to be specified +
  • Fixed problem reading some Casio EX-Z35 MakerNote values +
+ +Mar. 27, 2011 - Version 8.53 +
    +
  • Added a new Olympus LensType +
  • Added a new Nikon LensID +
  • Added a new PentaxModelID value +
  • Decode new Pentax MakerNotes format of Optio WG-1 GPS +
  • Decode Casio, Ricoh and Sanyo face detection information (thanks Jeffrey + Friedl and Emilio for samples) +
  • Decode FujiFilm face recognition information (thanks Jeffrey Friedl) +
  • Decode a new FujiFilm tag for GE models +
  • Allow writing GPSLatitudeRef/GPSLongitudeRef with a signed number +
  • Return proper FileType for M4P audio files +
  • Combined Canon FaceDetectFrameWidth/FaceDetectFrameHeight tags into + FaceDetectFrameSize for consistency with other makes +
  • API Changes: +
      +
    • Fixed problem when specifying family 1 group in call to SetNewValue() + when tags were previously extracted with ExtractInfo() +
    +
+ +Mar. 20, 2011 - Version 8.52 +
    +
  • Added -listr option and mechanism to recognize some unsupported file types +
  • Added read support for VSD (Microsoft Visio Drawing) files +
  • Added a new Pentax LensType and improved consistency of macro lens names +
  • Added another CanonModelID +
  • Calculate Duration for M2TS (AVCHD) videos +
  • Decode a new FujiFilm tag +
  • Recognize .TS extension +
  • Recognize FotoStation IPTC record 240 +
  • Attempt to better identify FPX-format MSOffice documents with incorrect file + extensions +
  • Fixed bug applying time shift to Nikon PowerUpTime +
  • API Changes: +
      +
    • Enhanced GetNewValues() to allow group name to be specified +
    • Allow description flag to be set to '0' when calling GetFileType() to + return types of recognized-yet-unsupported files +
    +
+ +Mar. 12, 2011 - Version 8.51 +
    +
  • Added -csv option for import/export of CSV database files +
  • Added ability to import JSON files +
  • Added read support for APP1 "Ocad" segment +
  • Added a new Nikon LensID (thanks Robert Rottmerhusen) +
  • Decode more Reconyx MakerNotes tags (thanks Robert Hass of Reconyx!) +
  • Report the number of encryption bits in the PDF:Encryption tag value +
  • Allow empty group name when specifying a tag +
  • Improved decoding of Olympus ArtFilter and MagicFilter tags +
  • Improved exception handling to continue with next -execute command after + aborting a command due to a serious error +
  • Fixed problem reading indexed PGF images +
+ +Mar. 1, 2011 - Version 8.50 (production release) +
    +
  • Added Composite tags to convert QuickTime GPS information +
  • Added a couple new Sony PMP Orientation values (thanks Mike Battilana) +
  • Added a couple of new Nikon LensID's (thanks Rolando Ruzic) +
  • Added a new Canon LensType (thanks Gerald Kapounek) +
  • Decode new Nikon, Olympus, Pentax and Sony face detection tags (thanks + Jeffrey Friedl) +
  • Decode Ricoh FirmwareRevision tags +
  • Allow GPSLatitudeRef and GPSLongitudeRef to be written with a GPS coordinate + containing a N/S/E/W designator +
  • Removed Canon20D shortcut and changed Canon shortcut +
  • Removed LEGRIA/VIXIA/iVIS from CanonModelID names +
  • Renumbered Canon FacePosition tags to start at Face1Position +
+ +Feb. 12, 2011 - Version 8.49 +
    +
  • Added a number of new values for various Canon tags +
  • Added a new Pentax LensType +
  • Added ability to write Nikon PowerUpTime tag +
  • Added a number of MachO CPUSubtype's and improved handling of 64-bit flag +
  • Decode ColorData for the Canon EOS 600D and 1100D +
  • Decode a few new Sony tags +
  • Set document number for FlashPix tags extracted from embedded documents +
  • Attempted to patch OS X 10.6 quirk where FileModifyDate may not be preserved + for some files when -P is combined with -overwrite_original_in_place +
+ +Feb. 3, 2011 - Version 8.48 +
    +
  • Added a new Canon LensType value +
  • Changed order of stored information when rewriting existing IPTC tags (to + make the order of items in list-type tags consistent with XMP when deleting + and adding back values in the same command) +
  • Fixed problems with format of binary data in lists for some output options +
+ +Jan. 29, 2011 - Version 8.47 +
    +
  • Added -args option +
  • Added read support for PGF (Progressive Graphics File) images +
  • Added write support for Phase One IIQ images +
  • Added ability to write XMP-xmpMM:Pantry +
  • Added print conversions for a number of closed-choice XMP properties +
  • Added some new CanonModelID's +
  • Included new argument files in distribution: pdf2xmp.args and xmp2pdf.args +
  • Avoid copying TIFF trailers containing nothing but zeros when rewriting +
  • Handle binary data in serialized structure output +
  • Moved BMP tags to the File group +
  • Fixed bug reading/writing some IPTC binary data tags +
  • Fixed problem copying XMP:Thumbnails structure +
  • Fixed conversion of MXF:ByteOrder value +
  • Fixed potential "Undefined subroutine ConvertStruct" crash bug +
  • API Changes: +
      +
    • Fixed bug introduced in 8.46 when calling GetValue(xxx,'Raw') +
    +
+ +Jan. 22, 2011 - Version 8.46 +
    +
  • Simplified definition of user-defined XMP structures: flattened tags are now + automatically generated, and UserDefined::xmpStruct is no longer needed (but + backward compatibility is maintained with the old-style definitions) +
  • Added ability to handle multi-dimensional arrays in structured output +
  • Added a new Canon LensType (thanks Jean-Michel Dubois) +
  • Added some new XMP-xmpMM tags +
  • Enabled writing of a number of XMP-crs tags +
  • Decode Reconyx TriggerMode tag +
  • Relaxed structure validation to allow a structure to be written even if + there were errors with some fields +
  • Patched problem with formatting of very large numbers in JSON (-j) output +
  • Fixed a few problems reading and writing structured information +
  • Fixed bug which could cause hang with some user-defined tag definitions +
+ +Jan. 12, 2011 - Version 8.45 +
    +
  • Fixed a couple of minor bugs with the new -struct option +
+ +Jan. 12, 2011 - Version 8.44 - "Structured XMP" +
    +
  • Added ability to specify XMP structures when writing (yet another Christmas + vacation spent adding a significant new feature to ExifTool) +
  • Added support for new XMP tags in the MWG 2.0 specification +
  • Added read support for DV video files +
  • Added support for Reconyx maker notes +
  • Added option to overwrite existing text output files (-w!) +
  • Added ability to ignore symbolic directory links with "-i SYMLINKS" +
  • Added support for Sony Ericsson XMP cell phone location tags +
  • Added a few new CanonModelID's +
  • Added a new Minolta/Sony LensType (thanks Jean-Michel Dubois) +
  • Added a new Olympus LensType +
  • Added print conversion for all Bitrate tags +
  • Decode a couple new RIFF tags +
  • Decode CameraTemperature for a few new Canon PowerShot models +
  • Improved -struct option to work with all text output formats +
  • Changed behaviour of XMP lang-alt lists to conform to the July 2010 + specification (x-default item is no longer mandatory) +
  • Renamed AudioSampleBits tags to AudioBitsPerSample +
  • Renamed XMP-crs:Temperature tag to ColorTemperature +
  • Minor change to behaviour when replacing values in XMP lists: new list + items are now all inserted in place of the first deleted item (previously + new items were inserted one-by-one into the holes left by deleted items) +
  • Fixed bug writing alternate languages for XMP-iptcExt:ArtworkTitle tag +
  • Fixed problem where console echo was disabled when using -k option from a + bash script +
  • Attempted to patch problem of -b option affecting newline sequence for + subsequent -execute commands in Windows +
  • API Changes: +
      +
    • SetNewValue() now accepts structured values (as HASH references or + serialized strings) +
    • Struct option now has 3 settings (undef, 0 and 1) +
    +
+ +Dec. 21, 2010 - Version 8.43 +
    +
  • Added read support for MXF (Material Exchange Format) files +
  • Added support for GE (General Imaging) maker notes +
  • Added a couple of new Pentax LensType's +
  • Added a couple of new CanonModelID's +
  • Added a few more values to Casio UnknownMode +
  • Recognize 3GPP and 3GP2 file extensions +
  • Improved handling of character encoding errors +
  • Changed Duration format to always include hours for times > 1 minute +
  • Fixed minor quirk in HtmlDump output +
  • Fixed race condition with -stay_open when reading options requiring + additional arguments from the argfile +
+ +Dec. 11, 2010 - Version 8.42 +
    +
  • Added a couple more Samsung LensType values +
  • Added a few new Canon EasyMode values and a Canon LensType value +
  • Added a new PentaxModelID +
  • Decode some new H264 tags (thanks Dave Nicholson) +
  • Decode JUNK chunk in Pentax RS1000 AVI videos +
  • Flush console output before "{ready}" message when using -stay_open +
  • Improved decoding of some Canon and Pentax tags (thanks Dave Nicholson) +
  • Fixed problem copying makernotes from Nikon NRW image to JPEG +
  • Fixed incorrect decoding of some AEInfo tags for newer Pentax DSLR's +
+ +Dec. 3, 2010 - Version 8.41 +
    +
  • Added a new PentaxModelID +
  • Added a few new values for some Canon tags +
  • Added some non-standard values to a few XMP-exif tags +
  • Decode a new Ricoh tag and added a LensID +
  • Decode more Pentax K-5 tags and values +
  • Improved decoding of Battery tags for various Pentax DSLR models +
  • Fixed bug where time could be wrong by up to 2 seconds when shifting + multiple date/time values containing fractional seconds +
+ +Nov. 21, 2010 - Version 8.40 (production release) +
    +
  • Added -restore_original and -delete_original options +
  • Added new Canon, Pentax and Sony LensType values +
  • Decode more Pentax K-5 tags +
  • Decode a number of new tags in Nikon D7000 MOV videos +
  • Decode FocusDistance tags for the Canon EOS 60D +
  • Decode a few new Panasonic tags +
  • Decode a few maker note tags from Flip Video MP4 files +
  • Extract PDF PageMode and PageLayout tags +
  • Changed family 2 group names for a number of PDF tags +
  • Changed Canon LensType strings for a few lenses with updated models +
  • Patched problem reading GPX files which contain no newlines +
+ +Nov. 12, 2010 - Version 8.39 +
    +
  • Added read support for RAR archive files +
  • Added warning for non-standard XMP APP1 header in JPEG images +
  • Added a new Canon LensType (thanks Rolando Ruzic) +
  • Decode more Olympus WAV tags +
  • Decode a few more PDF document property tags +
  • Decode a new Canon tag +
  • Extract firmware revision letter with Nikon FirmwareVersion +
  • Improved decoding of some Pentax tags +
  • Changed names of a couple of Pentax tags +
  • Changed name of ASF:FileSize to FileLength to avoid conflict +
  • Fixed problem creating output files on network drives in Windows +
  • Fixed bug where MWG module wasn't loaded automatically when -execute was + used +
+ +Nov. 7, 2010 - Version 8.38 +
    +
  • Added support for Nikon D3 firmware 2.02 +
  • Decode many new Pentax K-5 tags and improved decoding of others +
  • Decode a few more Nikon D3 and D3S settings (thanks Warren Hatch) +
  • Decode some new Olympus WAV tags (thanks Tomasz Kawecki) +
  • Decode a few new Canon DPP 3.9.2 tags +
  • Decode PDF digital signature permission information +
  • Improved recognition of Adobe Illustrator PS-format AI files +
  • Disable writing XMP to Adobe Illustrator version 8 and older EPS files +
+ +Oct. 31, 2010 - Version 8.37 +
    +
  • Added ability to switch ARGFILE while -stay_open is active +
  • Fixed a couple of bugs with the new -stay_open option +
  • Fixed problem with -E option that caused double-escaping of Composite tags +
+ +Oct. 30, 2010 - Version 8.36 +
    +
  • Added ability to read/write metadata in Sigma X3F images containing a + JpgFromRaw (eg. all Sigma models except the SD9 and SD10) +
  • Added -stay_open option to avoid startup delay when called from other + applications +
  • Added a new Pentax LensType (thanks Hubert Meier) +
  • Decode a couple of new tags written by Sigma Photo Pro +
  • Changed family 0 group name for SonyIDC tags to "MakerNotes" +
  • Improved Composite:LensID to use LensModel if available when LensType is + "Unknown" +
  • Fixed problem extracting ThumbnailImage from some FujiFilm RAF images +
  • Fixed problem calculating Red/BlueBalance for some newer Nikon models +
+ +Oct. 23, 2010 - Version 8.35 - "PDF Encryption" +
    +
  • Added support for PDF AES-128 and AES-256 encryption (requires Digest::SHA + for AES-256 support) +
  • Added -password option for processing password-protected PDF documents +
  • Added write support for a couple more FujiFilm RAF versions +
  • Added a number of new Olympus SceneMode values +
  • Added a few new SonyModelID's +
  • Added a new Nikon LensID (thanks marten) +
  • Added a Canon LensType and fixed an incorrect one (thanks Andreas Huggel) +
  • Decode a number of new Canon tags +
  • Decode a few new Nikon D3S settings (thanks Warren Hatch) +
  • Extract PDF UserAccess +
  • Extract Olympus ZoomedPreviewImage +
  • Updated decoding of Olympus AFPoint for recent E-models +
  • Avoid writing mandatory IPTC tags unless another IPTC tag actually changes + (eg. trying to delete a non-existent IPTC tag will no longer have the side + effect of generating mandatory IPTC tags) +
  • Improved language translations +
  • Improved error message when trying to write a file with the wrong extension +
  • Renamed a couple of Olympus tags +
  • Fixed problem reading/writing PDF tags from some encrypted stream objects +
  • API Changes: + +
+ +Oct. 7, 2010 - Version 8.34 +
    +
  • Added read support for XCF and WebP images and WebM videos +
  • Added a couple of new PentaxModelID's +
  • Decode a number of new Canon 60D MakerNotes tags (thanks Bogdan for + LensSerialNumber) +
  • Decode FrameCount from MakerNotes in Nikon MOV videos +
  • Decode Ambience and some video tags from Canon +
  • Decode more Canon EOS 1D Mark IV CameraInfo tags +
  • Updated decoding of Pentax HighISONoiseReduction for newer models +
  • Changed description of Canon SerialNumber tags +
  • Fixed problem with extra comma in JSON output when -w option was used +
+ +Oct. 3, 2010 - Version 8.33 +
    +
  • Added ability to specify numerator and denominator of rational values +
  • Decode more Canon custom picture style settings (thanks Tom Kawecki) +
  • Decode Samsung MP4 "TAGS" information from WP10 videos +
  • Decode thumbnail image and maker notes from Canon S95 MOV videos +
  • Decode Microsoft Photo 1.1 EXIF and XMP information +
  • Fixed problem copying tags dynamically from files with read errors +
  • Fixed problem setting FileName with a Windows UNC path (leading "\\") +
+ +Sept. 25, 2010 - Version 8.32 +
    +
  • Added the ability to use wildcards ('?' and '*') in tag names when + extracting or copying information +
  • Added a number of new CanonModelID's +
  • Decode a few more QuickTime tags and improved decoding of others +
  • Decode UserDefPictureStyle tags for more Canon cameras (thanks Tom Kawecki) +
  • Extract unknown text-based maker notes under new MakerNoteUnknownText tag +
  • Tested writing of PDF 1.7 files and removed warning for this version +
  • Identify Canon MakerNote footer in HtmlDump of DNG images +
  • Updated MimeType for PSD, AVI, AIFF plus a number of raw file formats +
  • Changed FileType for Adobe Illustrator (AI) files +
  • Fixed "Can't handle XMP attribute 'rdf:xmlns'" error when writing some XMP +
+ +Sept. 17, 2010 - Version 8.31 - "CRW+XMP" +
    +
  • Added ability to read/write XMP inside CanonVRD, which finally provides a + technique to write XMP in CRW images! (thanks Mike Kobzar for help testing) +
  • Added a couple of new Canon LensType's and CanonModelID's +
  • Added a number of new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added a new Sony LensType (thanks Mladen Sever) +
  • Treat 'eng' as a default language in ID3v2 information +
  • Recognize AIT file extension (AI file) +
  • Fixed problem where ExifTool could refuse to write PDF files containing + XMP-pdf:PDFVersion information +
+ +Sept. 11, 2010 - Version 8.30 +
    +
  • Added a couple of new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added a couple more Sigma LensType values +
  • Added a few more tag values for the new Sony SLT-A33, SLT-A55V and DSLR-A560 +
  • Added a few more values for various Casio tags +
  • Added a new Canon LensType (thanks Guido) +
  • Decode Panasonic ContrastMode for the TZ10/ZS7 +
  • Decode some Canon CameraInfo tags for the 60D +
  • Updated Canon custom functions for the 60D +
  • Updated Flash video to add some new values and decode some new tags +
  • Updated QuickTime decoding for new track and movie header formats +
  • Named a couple of unknown Canon tags +
  • Made Nikon PictureControl and NikonCaptureOutput directories block writable +
  • Fixed problem geotagging when any coordinate was exactly zero +
  • Fixed typo in Canon AFAssistBeam converted value +
  • Fixed problem displaying exiftool documentation on OS/2 (thanks Ilya + Zakharevich) +
+ +Aug. 22, 2010 - Version 8.29 +
    +
  • Added a few new CanonModelID's +
  • Added verbose messages for "unsafe" and "protected" tags which are not + copied +
  • Decode CameraTemperature for a few new Canon models +
  • Decode a few new Panasonic tags (thanks Zdenĕk Mihula) +
  • Decode a number of new 3rd party RIFF tags +
  • Recognize Casio-type maker notes in Concord cameras +
  • Handle "CDATA" sections in XML/XMP +
  • Fixed problem that could cause value to be added twice when writing MWG + list-type tags without specifying a group +
  • Fixed bug extracting altitude from GPX files containing "rtept" nodes which + could result in an altitude being associated with the next GPS fix +
  • Fixed problem deleting PreviewImage from MIE files +
+ +Aug. 14, 2010 - Version 8.28 +
    +
  • Added ability to specify Photoshop encoding (-charset Photoshop=CHARSET) +
  • Added support for maker notes of some Sony Ericsson phones +
  • Improved conversion for SigmaRaw:FocalLengthIn35mmFormat (thanks Niels + Kristian Bech Jensen) +
  • Fixed bug in calculation of AvgBitrate for QuickTime videos (thanks Mats + Peterson) +
  • Improved error handling when reading Matroska files +
  • Fixed -GROUP:geotag= to allow multiple geotag groups to be deleted + separately +
+ +July 31, 2010 - Version 8.27 +
    +
  • Added support for QuickTime localized languages and character encodings +
  • Added support for alternate language ICC_Profile tags +
  • Added a new XMP-swf tag +
  • Added a new Sony LensType (thanks Mladen Sever) +
  • Added ability to specify any group (not only family 0 and 1) for source tag + when copying +
  • Decode a number of new QuickTime tags +
  • Decode MakerNoteKodak9 maker notes in a few non-Kodak cameras +
  • Extract NikonCaptureHistory and drop when copying Nikon MakerNotes +
  • Calculate AvgBitrate for QuickTime movies +
  • Fixed names of a few recently added ICC_Profile tags (thanks Jeff Harmon) +
  • Fixed bug calculating duration of AVI videos for which FrameCount is zero +
  • Fixed tag ID for XMP-iptcExt:AdditionalModelInformation +
  • Fixed decoding of ShiftJIS character set +
+ +July 20, 2010 - Version 8.26 +
    +
  • Decode a number of new ICC_Profile tags added in approved revisions to the + specification +
  • Drop NikonCaptureData when copying Nikon MakerNotes (it may be too large for + a JPEG APP1 segment when copying from an NEF image) +
  • Made NikonCaptureData writable as a block and NikonCapture a deletable group +
  • Minor addition to tooltip for HtmlDump of offset values +
  • Fixed problem writing to an incorrectly-typed XMP list (patch for LR3 bug) +
  • Fixed problem setting file ownership on OS/2 systems when writing (thanks + Ilya Zakharevich) +
  • Fixed incorrect ICC_Profile tag name (thanks Jeff Harmon) +
+ +July 13, 2010 - Version 8.25 (production release) +
    +
  • Added CommonIFD0 shortcut tag to help when deleting metata from TIFF images +
  • Added a new Pentax LensType and fixed an incorrect one +
  • Added a new Panasonic ColorMode +
  • Decode FLAC picture metadata +
  • Changed ASF Preview tags to be consistent with ID3 and FLAC Picture tags +
  • Patched problem with funny dash character in cut-n-paste from documentation + on some systems (by allowing the funny dash in command-line arguments) +
  • Fixed misleading warning message which could appear when writing MWG tags +
  • Fixed typo in an ID3 tag name (thanks Mats Peterson) +
  • Fixed an incorrect Sony lens name (thanks Stephen Bishop) +
  • Fixed problem misidentifying some other RAW files as Epson ERF +
+ +June 30, 2010 - Version 8.24 +
    +
  • Added ability to write some Kodak APP3 Meta tags +
  • Added a few new Olympus LensType's and new values for a couple of other tags +
  • Added support for yet another Kodak MakerNote variation (M580) +
  • Added conversion for OOXML DocSecurity tag (thanks Jeff Harmon) +
  • Added another Nikon ExternalFlashFlags value (thanks Warren Hatch) +
  • Decode more Canon VRD tags (thanks Gert Kello) and changed some tag names +
  • Decode a couple of new Canon 7D tags (thanks Vesa Kivisto) +
  • Decode a few more Sigma tags +
  • Decode HTML tags written by Microsoft Office +
  • Decode some MakerNotes tags from Samsung MP4 videos +
  • Allow RFC 8601 date/time values to be written without seconds +
  • Fixed conversion for Kodak Meta:SerialNumber +
  • Changed conversion of Canon FocusDistanceUpper/Lower tags to add units (m) +
  • Changed the names of some Nikon FlashExposureComp tags +
  • Changed name of RTF CharactersNoWhiteSpace tag to CharactersWithSpaces to + conform with what Microsoft does with their software as opposed to what they + say in their RTF specification +
  • Changed a few FlashPix tags for better consistency with OOXML and RTF +
  • Properly convert OOXML Unicode character entities +
  • Fixed problem writing some Sigma MakerNote tags +
  • Fixed problem writing incorrect value for "Uncalibrated" XMP:ColorSpace +
  • Fixed bug where some unknown Canon values were extracted twice with -U +
+ +June 20, 2010 - Version 8.23 +
    +
  • Added write support for FujiFilm RAF images from the HS10 and S100FS +
  • Added read support for RTF files +
  • Added read support for FPXR in JPEG APP4 as written by some HP cameras +
  • Added ability to copy files of any type (now does a straight copy instead of + processing the file if no new values are set for any "real" tag) +
  • Added new values for CanonModelID, PentaxModelID and SonyModelID +
  • Added a new Ricoh LensID +
  • Added conversion for "Off" and "On" values when writing EXIF:Flash +
  • Added a new Canon LensType and changed the name of one Sigma lens +
  • Decode more Canon VRD tags and update to DPP 3.8 (thanks Gert Kello) +
  • Decode FujiFilm AutoDynamicRange +
  • Changed some DNG tags to make them writable (but "unsafe") +
+ +June 9, 2010 - Version 8.22 +
    +
  • Implemented PNG alternate language tags and special character translations +
  • Added print conversion for XMP-photoshop:ColorMode +
  • Decode some new Pentax 645D tags/values and added more PentaxModelID's +
  • Changed family 1 group names for Matroska Chapters +
  • Changed frame rate conversions to round to 3 decimal points +
  • Enable summary messages when -b is combined with -w +
  • Assume local system timezone on specified date (instead of current local + timezone) when writing an IPTC time tag with a date/time value which doesn't + include a timezone +
  • Fixed conversion of Matroska:ChapterTimeStart/End values +
  • Fixed an incorrect Panasonic Lens name (thanks Michael Byczkowski) +
+ +June 2, 2010 - Version 8.21 +
    +
  • Added read support for Matroska multimedia files (MKA, MKV and MKS) +
  • Added a new PentaxModelID (Optio E80) +
  • Decode some information from Casio EX-7000SX APP1 "QVCI", HP Photosmart + R837 APP6 "TDHD" JPEG segments +
  • Extract more Samsung and HP PreviewImages hidden in other JPEG APP segments +
  • Extract unknown tags with numerical ID's by default when -v option is used +
  • Updated default GPSVersionID to 2.3.0.0 when writing +
  • Fixed bug geotagging from KML file (lat/long were swapped) +
+ +May 26, 2010 - Version 8.20 - "Exif 2.3" +
    +
  • Added read support for Open Document files (ODP, ODS, ODT) +
  • Added Composite:AudioBitrate tag for VBR MPEG audio +
  • Added support for IPTC:CatalogSets written by iView MediaPro +
  • Decode Olympus MagicFilter tag and add a two new SceneMode values +
  • Decode a few new Sony tags written by NEX models +
  • Decode a number of new Sony A100 tags (thanks Igal Milchtaich) +
  • Decode some information from MPEG audio LAME header +
  • Updated to Exif 2.3 specification (!!) +
  • Allow date/time tags to be shifted by the values of other tags when using + the -tagsFromFile feature +
  • Fixed formatting of QuickTime:CreateDate as written by iPhone +
  • Fixed problem conditionally replacing some blank EXIF tags and alternate + language tags in XMP +
+ +May 11, 2010 - Version 8.19 +
    +
  • Added ability to read/write Samsung PreviewImage trailer +
  • Added two new PentaxModelID's (Optio H90 and W90) +
  • Added a new Canon LensType +
  • Added a new CanonModelID +
  • Decode more Sony tags/values (thanks Michael Reitinger) +
  • Decode more Leica M9 tags (thanks Michael Byczkowski and Carl Bretteville) +
  • Updated to XMP April 2010 specification +
  • Avoid extracting Sony DSLR-A100 tags which have "n/a" values +
  • Improved German language translations (thanks Herbert Kauer) +
  • Improved efficiency of Composite tag calculations +
  • Made RSRC a deletable group +
  • Tolerate extra white space at the start of an XMP file +
  • Changed MWG logic to ignore blank EXIF tags +
  • Changed a few print conversion strings to improve interoperability +
  • Changed XMP namespace prefix 'prismusagerights' to 'pur' as per most recent + PRISM specification +
  • Patched memory problem in Windows when processing very large EPS files +
  • Fixed a couple of incorrectly named Sony Panorama tags +
  • Fixed bug which could prevent file from being updated when deleting + mandatory tags and adding back tags in other locations +
+ +Apr. 16, 2010 - Version 8.18 +
    +
  • Added read support for Sony DSC-F1 PMP images +
  • Added a new Nikon LensID (thanks Jeffrey Friedl) +
  • Decode a number of new Sony tags (thanks Michael Reitinger) +
  • Decode a few more Leica M9 tags (thanks Michael Byczkowski) +
  • Preserve original file permissions and ownership when writing +
  • Made Canon DustRemovalData writable +
  • Changed some Pentax WhiteBalance strings for consistency +
  • Patched potential security problem when writing values +
  • Fixed bug extracting unsynchronized ID3v2.4 information +
+ +Apr. 9, 2010 - Version 8.17 +
    +
  • Added a new Sony ExposureMode (thanks Michael Reitinger) +
  • Decode Casio DriveMode (thanks Robert Chi) +
  • Decode CameraTemperature for more Canon EOS models (thanks Vesa Kivisto) +
  • Updated to the DICOM 2009 specification (Note: Changed some DICOM tag names) +
  • Improved conversions for XMP:LensInfo, EXIF:DNGLensInfo and Nikon:Lens +
  • Changed case of some Canon DriveMode strings +
  • Fixed divide-by-zero error when Geotagging from a track with only one point +
  • Fixed incorrect ImageHeight reported for top-to-bottom BMP images +
  • API Changes: +
      +
    • Fixed a problem passing options to Image::ExifTool::TagInfoXML::Write() +
    +
+ +Mar. 31, 2010 - Version 8.16 +
    +
  • Preserve Mac OS resource fork when writing (OS X only) +
  • Added a number of new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Decode a couple more Mac OS resources +
  • Decode Olympus LensModel tag (thanks Martin Hilbers) +
  • Extract PrintIMVersion tag from PrintIM information +
  • Separate extraction of Leica FrameSelector information from LensType tag +
  • Recognize Bitstream PFA/PFB font files +
  • Patched ActivePerl 5.10 bug which could cause Perl crash during Geotag tests +
  • Fixed another Geotag test that fails due to round-off errors on some systems +
+ +Mar. 18, 2010 - Version 8.15 (production release) +
    +
  • Added read support for Macintosh resource files: +
      +
    • Generate ResourceForkSize tag if data exists in a file's resource fork +
    • Enhanced -ee option to process resource fork as a sub-document +
    +
  • Added a new PentaxModelID (Optio I-10) +
  • Decode Panasonic DMC-ZS7 landmark tags +
  • Fixed decoding of Pentax Optio 555 PictureMode and added a number of new + values (thanks Ralf Medow) +
+ +Mar. 16, 2010 - Version 8.14 +
    +
  • Added some new Canon AFMode values for the EOS 7D (thanks Dieter Steiner) + and renamed tag to AFAreaMode +
  • Decode ColorData and some new MOV tags for the production Canon EOS 550D +
  • Decode Panasonic IntelligentResolution tag +
  • Allow times with timezones in GPX track logs +
  • Improved handling of maker notes in Olympus MP4 videos +
  • Changed H264 GPS tags to the GPS group +
  • Fixed date/time format error in reverse geotagging GPX example +
  • Fixed problem introduced in version 8.09 where XMP:GPSLatitude/GPSLongitude + require the -a option to be extracted +
  • API Changes: +
      +
    • Fixed bug where some options (Charset, Escape, Exclude and Lang) weren't + activated properly when set via options hash in calls to some functions +
    • Fixed some potential problems when used with mod_perl +
    +
+ +Mar. 5, 2010 - Version 8.13 +
    +
  • Added read/write support for Samsung SRW images and decode some NX10 maker + note tags (thanks Tae-Sun Park) +
  • Added new values for some Sony tags (thanks Michael Reitinger) +
  • Added a new Canon LensType +
  • Decode maker notes in Nikon Coolpix S8000 MOV videos +
  • Decode a number of obscure TIFF FX tags +
  • Implemented list-type behaviour for MWG:Creator tag +
  • More improvements to German translations (thanks Herbert Kauer) +
  • Changed name of NikonPreview group to PreviewIFD +
  • Fixed problem which prevented ThumbnailImage from being written to ARW, SR2 + and PEF images +
+ +Feb. 26, 2010 - Version 8.12 +
    +
  • Added a number of missing ProgramMode values for the Sony DSLR-A330 +
  • Added XMP-iptcCore:DigitalSourceType (IPTC Extension version 1.1) +
  • Added a couple more Nikon LensID's (thanks Jens Kriese and Robert + Rottmerhusen) +
  • Improved German language tag descriptions (thanks Herbert Kauer) +
  • Improved identification of some RAW file types +
  • Moved MPF PreviewImage into the Composite group +
  • Fixed some problems in HtmlDump output +
  • Fixed problem copying makernotes as a block into DNGAdobeData +
+ +Feb. 20, 2010 - Version 8.11 +
    +
  • Added support for Leica S2 maker notes +
  • Added a bunch of new CanonModelID's +
  • Decode MacroMagnification for more Canon models (MP-E 65mm only) +
  • Decode a number of Canon CameraInfo tags for the 1DmkIV and 550D +
  • Updated CanonCustom tags for the 550D +
  • Improved parsing of Canon OriginalDecisionData +
  • Improved decoding of Canon CameraInfo LensType +
  • Improved decoding of some Sigma tags +
  • Recognize a number of new Paint Shop Pro file extensions +
  • Prevent a directory from being recreated in the wrong location when deleting + a group and adding back information in the same step +
  • Changed -fileOrder option to sort numbers numerically +
  • Fixed bug in -fileOrder option when directory names are specified +
  • Fixed problem extracting information from some Panasonic AVCHD videos +
  • Fixed some minor compatibility problems with Perl 5.11 +
  • Fixed problem which could result in runtime error when using MWG feature +
  • Fixed an inconsistency in the way duplicate tags were handled in the grouped + JSON (-j -g) and short XML (-X -s) output formats +
+ +Feb. 8, 2010 - Version 8.10 (production release) +
    +
  • Added read/write support for Photoshop PSB file format +
  • Added -fileOrder option to provide control over file processing order +
  • Added a few new Sony/Minolta LensTypes (thanks Marcin Krol) +
  • Added more Nikon LensID's (thanks Robert Rottmerhusen) +
  • Decode metadata from all frames in AVCHD H.264 video with -ee option +
  • Decode more H.264 tags and improved decoding of others +
  • Improved decoding of some Olympus E-P1 tags +
  • Improved handling of some types of unknown maker notes +
  • Enhanced -p option to support output file headers and footers, and to parse + embedded documents as separate input files when combined with -ee +
  • Relaxed validation of PFM files to accommodate incorrect device type string + written by FontForge software +
  • API Changes: +
      +
    • Enhanced GetFileType() to return descriptions for more file types +
    +
+ +Jan. 29, 2010 - Version 8.09 +
    +
  • Added a number of new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Decode GPS position and some camera settings from AVCHD (.M2TS) video +
  • Decode a few new PhotoMechanic tags +
  • Decode MacroMagnification for the Canon MP-E 65mm f/2.8 1-5x Macro Photo + lens in EOS 5DmkII and 40D images +
  • Delete multiple Photoshop segments in JPEG images when deleting all + Photoshop information and adding some back in one step +
  • Print warning message in Windows when there are no matching files to process +
  • Changed print conversion for PSP CreatorAppVersion +
  • Fixed problem rewriting NikonCapture information written by NX2 +
+ +Jan. 25, 2010 - Version 8.08 +
    +
  • Added read support for Paint Shop Pro images (PSP and PSPIMAGE) +
  • Added ability to decode a number of new character sets including JIS, and + completely overhauled character encoding routines +
  • Fixed problem reading old OS/2-format BMP images +
+ +Jan. 19, 2010 - Version 8.07 +
    +
  • Added read support for a number of font file formats (OTF, TTF, TTC, PFA, + PFB, PFM, DFONT, AFM, ACFM and AMFM) +
  • Added (experimental) read support for FLA files +
  • Added a few new Sony LensType's (thanks Sander Stols) +
  • Added a new Canon LensType (thanks Mark Berger) +
  • Set BigTIFF MIME type to "image/x-tiff-big" (unofficial) +
  • Fixed bug in GPS time drift correction when dates are specified for both GPS + and image times +
  • Fixed problem reading some IGC GPS logs +
+ +Jan. 12, 2010 - Version 8.06 +
    +
  • Added a few new CanonModelID's +
  • Fixed a bug introduced in 8.05 which broke rewriting of XMP in MWG mode +
+ +Jan. 10, 2010 - Version 8.05 - "Strict MWG" +
    +
  • Improved MWG conformance by ignoring non-standard EXIF, IPTC and XMP when + the MWG module is loaded +
  • CurrentIPTCDigest tag is now only generated for IPTC in the standard + location (as specified by the MWG recommendation) +
  • Added support for 3rd party trailers on ARW images +
  • Changed names of Sony IDC date/time tags and decode the last unknown IDC tag +
  • Fixed "-TAG-= -TAG=VALUE" syntax to work with shiftable (date/time) tags + and tags with conversions +
  • Fixed incorrect tag format when writing some PhotoMechanic tags +
  • Fixed problem where some tags couldn't be written in Olympus ORF images +
+ +Jan. 7, 2010 - Version 8.04 - "Write ARW" +
    +
  • Added write support for Sony ARW and SR2 images (at long last!) +
      +
    • WARNING: Some Adobe utilities (Photoshop Camera Raw 5.6, DNG Converter + 5.6, LightRoom 2.6) have a bug which causes the tone curve to be + incorrect for edited ARW images from some Sony cameras (A500, A550, + A700, A850, A900 and maybe others) +
    +
  • Compatibility Notice: Embedded JPEG in ARW and SR2 images is now extracted + as PreviewImage instead of JpgFromRaw +
  • Added read/write support for Sony IDC tags +
  • Added support for Leica X1 maker notes and decode a few tags +
  • Added support for DigiKam XMP schema +
  • Added a new Minolta/Sony LensType (thanks Jean-Michel Dubois) +
  • Decode Nikon D90 AFAreaModeSetting +
  • Decode Nikon NEFBitDepth (thanks Warren Hatch) +
  • Decode a few new Sony SRF, Casio AVI and MSOffice TIFF tags +
  • Enhanced Geosync tag to allow GPS time-drift correction +
  • Fixed Nikon D3 FlashSyncSpeed values (thanks Warren Hatch) +
+ +Dec. 19, 2009 - Version 8.03 +
    +
  • Added a new Nikon ExternalFlashFlags value (thanks Warren Hatch) +
  • Implemented -charset id3=CHARSET option in Windows version too (oops!) +
  • Improved heuristic for guessing EXIF "Unicode" string byte order +
  • Improved decoding of some obscure QuickTime tags +
  • Renamed Casio SelfTimer tag to ReleaseMode and added new values +
  • Fixed problem converting numerical M4P Genre values +
+ +Dec. 15, 2009 - Version 8.02 +
    +
  • Added MIME types for Apple iWork file formats +
  • Added bitmask to -v2 output for applicable tags +
  • Added a new Canon LensType and fixed an incorrect one (thanks Hugh + Griffiths) +
  • Added a few new Ricoh Saturation values (written by GXR) +
  • Added ability to specify character set for ID3v1 information +
  • Added French translations for some Nikon tags (thanks Harry Nizard) +
  • Extract FilePermissions information +
  • Decode Nikon D90 custom settings +
  • Decode a few more Nikon tags and removed AutoBracketRelease (thanks Warren + Hatch) +
  • Decode a few more GIF tags (and changed groups of some others) +
  • Decode some information from JPEG APP4 "SCALADO" segment +
  • Updated DICOM decoding to latest (2008) specification +
  • Enhanced -fast option to allow MakerNote information to be skipped +
  • Changed -v0 to enable output autoflushing for STDERR as well as STDOUT +
  • Improved decoding of some QuickTime tags (fixes M4P Genre problem) +
  • API Changes: +
      +
    • Added CharsetID3 option +
    • Changed name of IPTCCharset option to CharsetIPTC (but IPTCCharset may + still be used for backward compatibility) +
    +
+ +Dec. 1, 2009 - Version 8.01 +
    +
  • Compatibility Notice: Extract full-sized preview from X3F images as + JpgFromRaw instead of PreviewImage +
  • Added support for the new X3F version 2.3 files written by the Sigma DP2 +
  • Added support for a few more XMP-acdsee tags +
  • Decode Nikon D3 custom settings (thanks Warren Hatch) and extrapolate to + D3S, D3X and D300S +
  • Decode the few remaining Nikon D300 custom settings (thanks Stuart Solomon + for providing sample images) +
  • Decode Nikon D5000 custom settings +
  • Decode Nikon FlashColorFilter tag (thanks Warren Hatch) +
  • Decode a few more PNG tags +
  • Created a new family 1 group for Nikon custom settings +
  • Improved write conversions for EXIF Contrast, Saturation and Sharpness +
  • Fixed problem with %f and %e when the source file has no extension +
  • Fixed problem decoding Nikon D3 flash group B and C intensities +
  • Fixed missing MIME type for XLT files +
+ +Nov. 20, 2009 - Version 8.00 (production release) +
    +
  • Added read support for Apple iWork '09 files (Keynote, Pages and Numbers) +
  • Added ability to write Nikon SerialNumber and ShutterCount tags +
  • Added a few new Nikon LensID's and changed Tamron lens names to include + model number (thanks Robert Rottmerhusen) +
  • Decode a number of new Nikon tags (thanks Warren Hatch for much of this) +
  • Decode a few new Sony tags and improved others (thanks Igal Milchtaich) +
  • Decode a few new Ricoh tags, renamed RicohDateTime1/2, Revision and + MakerNoteVersion tags, and added some print conversions +
  • Decode Parallax in FujiFilm MPO MPImage2 images (thanks John Goodman) +
  • Decode Canon EOS 1D Mark IV custom functions +
  • Decode a number of new tags in MPEG-4 videos +
  • Decode a large number of private GE DICOM tags +
  • Decode a few more tags in AVI videos and attempt to fix problem calculating + duration when multiple video streams exist +
  • Enhanced -ee option to extract information from embedded MPF images +
  • Improved Nikon LensID conversion to recognize user-defined lenses +
  • Improved decoding of a few Olympus tags (ArtFilter, FaceDetect and + FocusProcess) +
  • Improved handling of warnings when processing corrupted ZIP files +
  • Improved recognition of Canon teleconverters in Composite LensID tag +
  • Added patch for Leica M8 bug which writes incorrect format for EXIF + ExposureCompensation and ShutterSpeedValue +
  • Changed prefix of unknown Leica M9 tags from LeicaSubdir to Leica_Subdir +
  • Fixed problem writing encrypted Nikon WB Levels +
  • Fixed problems reading PDF tags written by OS X 10.6 utilities +
  • Fixed problem where the -charset option didn't work properly for some XML + character entities when reading XMP +
+ +Nov. 6, 2009 - Version 7.99 +
    +
  • Added read support for Office Open XML files and improved recognition of + many MS Office file types +
  • Added read support for Phase One IIQ and Capture One COS and EIP files +
  • Added read support for GZIP information (first archived file only) +
  • Added a new Canon LensType (thanks Karsten Söte) +
  • Added a new Nikon LensID (thanks Geert De Soete) +
  • Decode a few new Sony tags +
  • Decode MakerNotes in Pentax AVI videos +
  • Decode SerialNumber for newer Pentax cameras +
  • Decode Canon FlashMeteringMode for most EOS models +
  • Disabled some Sony A230 CameraInfo tags which weren't valid for this model +
  • Give names to a number of unknown QuickTime atoms +
  • Recognize VOB file extension (but audio information in MPEG private stream + is not yet decoded) +
  • Tolerate extra white space in GPX attributes when geotagging (fixes problem + reading GlobalSat GPX files) +
  • Minor improvements to FlashPix decoding +
  • Changed names of all ZIP tags to avoid name conflicts with other tags +
  • Changed Composite ImageSize to use ExifImageWidth/Height for CR2 images +
  • Changed names of QuickTime image and video track description + ImageWidth/Height tags to SourceImageWidth/Height +
  • Fixed problems when -if option was combined with -v or -htmlDump +
  • Fixed problem parsing NMEA track logs where coordinates have the wrong + number of digits due to missing leading zeros (Holux M-241) +
  • Fixed an incorrect Pentax LensType +
+ +Oct. 28, 2009 - Version 7.98 +
    +
  • Implemented MWG support via a plug-in module ("-use MWG") +
  • Added -config and -use options +
  • Added ability to read Sony Vegas tags in AVI videos +
  • Added a couple of new Canon LensType's +
  • Added a new Panasonic ShootingMode (thanks Joerg) +
  • Added a new PentaxModelID (Optio P80) +
  • Added a new CanonModelID +
  • Added a few new Canon 1D Mark IV custom functions values +
  • Added warning for superfluous tag names on the command line when writing +
  • Decode a few more tags for the Canon EOS 5D and 7D +
  • Decode a number of new tags in Quicktime-based files (including MP4 and JP2) +
  • Impose length limit on IPTC values when writing as per spec. (for backward + compatibility, the length check may be disabled with the -m option) +
  • Improved checks for invalid EXIF offsets and changed some warning messages +
  • Improved decoding for a few Canon tags (and renamed NoiseReduction tag) +
  • Improved date/time formatting to accept date-only values +
  • Implemented print conversion for ID3 date/time tags +
  • Enhanced writing of Photoshop:IPTCDigest to allow a special value of 'old' + to represent the digest of the IPTC from the original file +
  • Updated iptc2xmp.args and xmp2iptc.args to handle IPTC + DigitalCreationDate/Time +
  • Recognize a number of Sigma LensType's in X3F images +
  • Recognize a large number of additional audio/video file extensions +
  • Minor improvements to -htmldump output +
  • Minor changes to some application warning messages +
  • Fixed problem writing Canon CameraTemperature tags +
  • Fixed "Error reading Info object" warning when reading a PDF file after + deleting all PDF tags +
  • API Changes: +
      +
    • Added ability to specify config file via $Image::ExifTool::configFile +
    • Added EditGroup option for SetNewValue() +
    +
+ +Oct. 13, 2009 - Version 7.97 +
    +
  • Added ability to disable print conversion on a per-tag basis by suffixing + the tag name with a '#' character +
  • Added a new PentaxModelID (Optio WS80) +
  • Decode a few more Sony tags +
  • Decode a number of new Casio tags and values +
  • Decode CameraTemperature for Canon PowerShot models (thanks Vesa Kivisto) +
  • Improved warning messages for the -ext option +
  • Improved DOF calculation to use ObjectDistance if SubjectDistance and + FocusDistance are not available +
  • Improved -X output to support more of the new -charset encodings +
  • Made Composite:FileNumber writable +
  • Use more detailed makernote directory names in EXIF warning messages +
  • Decreased priority of tags in IFD1 of JPEG images to avoid taking precedence + over tags from IFD0 or ExifIFD +
  • Changed print conversion strings for TIFF SampleFormat tag +
  • Renamed Casio ObjectDistance tag to FocusDistance +
  • Fixed invalid character in a Minolta/Sony LensType string +
  • Fixed bug decoding NITFVersion tag +
  • Fixed bug where binary data was returned without the -b option when using an + expression involving tag names for some tags such as ThumbnailImage +
  • Fixed two problems which could result in runtime warnings when: +
      +
    • reading truncated ICC_Profile information +
    • using -htmldump on an image containing invalid EXIF offsets +
    +
  • API Changes: +
      +
    • Added ability to disable print conversion by suffixing tag name with '#' +
    • Changed name of BigTIFF 'ifd8' format to 'ifd64' for consistency +
    +
+ +Oct. 2, 2009 - Version 7.96 +
    +
  • Added new Geosync tag to allow geotagging of images with timestamps which + are not pre-synchronized to GPS time +
  • Added patch to avoid crash bug in Canon DPP software when OwnerName is set + to a value that is exactly 3 characters long (doh!) +
  • Added a few new Olympus LensType's (thanks Godfrey DiGiorgi) +
  • Added a couple more Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added minor warning when fixing invalid counts in Kodak MakerNotes +
  • Decode a few new tags and values for the Panasonic GF1 +
  • Improved parsing of command-line arguments to remove order dependencies of + certain options +
  • Minor improvement to decoding of Olympus FaceDetect tag +
  • Changed "Error reading PreviewImage from file" to a minor warning +
  • Changed conversion of Canon MeasuredEV to correspond more closely to + LightValue (by adding 5 to the MeasuredEV value, which seems to be good for + all EOS models, but it may be high by up to 1 EV for some PowerShot models) +
  • Fixed problems decoding some CameraInfo tags for the Canon 7D with the new + production firmware (1.0.7) +
  • Fixed problems writing some CameraInfo tags for the Canon 50D and 5DmkII +
+ +Sept. 24, 2009 - Version 7.95 +
    +
  • Added read support for LNK (Windows shortcut) file metadata +
  • Added patch to fix incorrect count written by a number of recent Kodak + cameras to some tags in SubIFD3 of the MakerNotes +
  • Added a few more Sony/Minolta LensType's +
  • Added a couple more Canon LensType's (thanks Norbert Wasser) +
  • Added a PentaxModelID for the new K-x +
  • Decode a couple more Canon VignettingCorr tags +
  • Improved Canon FocusDistance conversions to indicate "inf" for maximum value +
  • Improved DOF calculation to use SubjectDistance if FocusDistance is not + available +
  • Changed -fast, -scanForXMP and -unknown options to also apply when copying + tags with -tagsFromFile +
+ +Sept. 11, 2009 - Version 7.94 +
    +
  • Added support for Leica M9 makernote format and decode a few new tags +
  • Added a few new Leica LensType's +
  • Added support for IGC GPS track logs (thanks Lionel Genet) +
  • Added a number of alternate Macintosh character sets and changed a couple of + character set names for -charset option +
  • Decode even more Sony A100 tags (thanks Igal Milchtaich!) +
  • Improved handling of FlashPix character translations +
  • Changed a couple of Sony and Minolta AF tag names to be more consistent +
+ +Sept. 5, 2009 - Version 7.93 +
    +
  • Added a new CanonModelID +
  • Added a couple of new Nikon LensType's (thanks Robert Rottmerhusen) +
  • Added a few new Pentax LensType's +
  • Decode a number of new tags for the Canon EOS 7D +
  • Calculate Duration for WAV audio files +
  • Allow exponents when writing GPS coordinates (eg. "-gpslatitude=7.657e+01") +
  • Print available character sets if no CHARSET is given for -charset option +
  • Improved -v3 and -htmldump output to show MPF image data +
  • Fixed -E option to work with tag descriptions when -lang option used +
  • Fixed problem reading large FlashPix-format documents +
  • API Changes: + +
+ +Aug. 29, 2009 - Version 7.92 +
    +
  • Fixed new "-charset iptc=CHARSET" feature to work with -tagsFromFile +
+ +Aug. 29, 2009 - Version 7.91 +
    +
  • Added -charset option and support for additional Windows and Mac character + sets. Character sets now supported are: UTF-8, Latin1, Latin2, Cyrillic, + Greek, Turkish, Hebrew, Arabic, Baltic, Vietnam, Thai and MacRoman +
  • Fixed problem with some duplicate Nikon LensID's +
  • Fixed incorrect Duration calculation for multi-channel FLAC audio files +
  • Compatibility Notice: Removed "CreatorContactInfo" shortcuts which were + added to ease the transition when some Iptc4xmpCore tag names were changed + in version 7.45 +
  • API Changes: + +
+ +Aug. 24, 2009 - Version 7.90 +
    +
  • Added -ex (-escapeXML) option +
  • Added a few more Minolta M42-type lenses (thanks Lukasz Stelmach) +
  • Added a number of new CanonModelID's +
  • Decode more Sony A100 tags (thanks Igal Milchtaich) +
  • Decode a few more Kodak WhiteBalance tags +
  • Decode a couple more JPEG APP segments +
  • Internal changes to Composite tag calculation algorithm +
  • Patched problem with renaming files on OS/2 that caused failed tests +
+ +Aug. 18, 2009 - Version 7.89 (production release) +
    +
  • IMPORTANT: Not quite done with NRW fixes -- fixed similar bug which could + corrupt NRW images when writing new values larger than 10 MB +
+ +Aug. 17, 2009 - Version 7.88 (production release) +
    +
  • IMPORTANT: Fixed bug introduced in version 7.77 which causes Nikon NRW + images to be corrupted when writing +
  • Decode a number of Sony A100 Camera Settings tags (thanks Igal Milchtaich) +
  • Improved accuracy of some CameraInfo values for Canon PowerShot models +
  • Tolerate blank lines in PDF xref tables +
  • Fixed problem where -E didn't escape values when copying with -tagsFromFile +
  • Fixed bug identifying AF Micro-Nikkor 105mm f/2.8D lens +
+ +Aug. 14, 2009 - Version 7.87 +
    +
  • Added a new Sony lens (thanks Lukasz Stelmach) +
  • Added a few new Pentax City and PictureMode values (thanks Niels Kristian + Bech Jensen) +
  • Added lookup for XMP-photoshop:Urgency +
  • Added a few new Nikon RetouchHistory values +
  • Decode a number of new Sony tags for the A700 (thanks Rüdiger Lange) +
  • Decode Canon PeripheralLighting tags +
  • Decode Olympus AFFineTuneAdj (thanks Yrjo Rauste) +
  • Extract System tags from unknown file types +
  • Enhanced -E option to work when writing, and when used in combination with + other options such as -p +
  • Tolerate white space around "=" in XMP attributes (allowed by XML spec) +
  • Improved error handling when parsing bad EXIF IFD entries +
  • API Changes: + +
+ +July 25, 2009 - Version 7.86 +
    +
  • Added support for reading Garmin TCX track logs with the -geotag option +
  • Added a number of new Canon, Olympus and Pentax LensType's +
  • Enabled writing of .AI (Adobe Illustrator) files +
  • Minor changes to DICOM decoding +
+ +July 21, 2009 - Version 7.85 +
    +
  • Added a new Sony LensType +
  • Added a new Pentax LensType (thanks Albert Bogner) +
  • Added a new PentaxModelID value (Optio W80) +
  • Added a few new JPEGDigest values (thanks Franz Buchinger) +
  • Added check for proper support of IFD-format value types +
  • Decode Nikon D300 firmware 1.10 camera settings (thanks Stuart Solomon) +
  • Improved handling of Olympus makernotes for recent models and fixed error + messages resulting from makernote format changes in Stylus 550WP images +
  • Improved geotagging by allowing different NMEA sentences with slightly + different timestamps (within 10 seconds) in the same fix +
  • Fixed decoding of some CameraSettings tags for the new Sony A330 and A380 +
  • API Changes: + +
+ +July 16, 2009 - Version 7.84 (Windows only) +
    +
  • Fixed bug in -geotag option of Windows version when using wildcards in the + GPS track filename +
+ +July 13, 2009 - Version 7.83 +
    +
  • Added preliminary read support for M2TS/AVCHD video files (much pain for + little gain) +
  • Added family 4 group names (instance number) to provide a technique for + differentiating same-named tags extracted from the same location via the + command-line application +
  • Added a new family 1 group ("System") to differentiate tags obtained from + the file system +
  • Added a couple of new Canon LensType values +
  • Decode ID3 Picture attributes +
  • Decode ICC_Profile ColorantTableOut +
  • Changed application to return a value of 1 if all files fail condition +
  • Made the IPTC CodedCharacterSet tag "unsafe" to copy by default (since this + could result in incorrect encoding for existing IPTC in the destination + image) +
  • Fixed bug handing some non-standard offset formats when writing EXIF +
  • Fixed problem with MakerNote warnings for Samsung WB500 +
  • Fixed problem reading Leica M8 makernotes when copied between JPEG and DNG + images +
  • Fixed problem extracting ThumbnailImage from Sanyo VPC-FH1 MP4 videos +
  • Fixed problem extracting ThumbnailImage from some Sony DSLR-A100 ARW images + (due to a bug in some A100 firmware versions which results in incorrect + ThumbnailOffset values) +
+ +July 2, 2009 - Version 7.82 (production release) +
    +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Decode another Nikon AVI tag +
  • A number of improvements, bug fixes and additions to ID3 decoding +
+ +June 28, 2009 - Version 7.81 +
    +
  • Added a few missing print conversions to Nikon, Kyocera and FlashPix + date/time tags +
+ +June 26, 2009 - Version 7.80 +
    +
  • IMPORTANT: Fixed bug introduced in 7.77 which had the potential to corrupt + TIFF-format images when writing to an image containing a SubIFD tag larger + than 10 MB (not that I've ever seen one of these in the wild) +
  • Added support for DNG version 1.3 +
  • Decode makernotes in Nikon AVI videos +
  • Decode QuickTime MatrixStructure tag and added Composite Rotation tag to + calculate the rotation of the QuickTime video track +
  • Updated CanonCustom tags for the EOS 500D +
  • The -fast option now stops parsing of WAV and AVI files at audio/video data +
  • API Changes: +
      +
    • Improved handling of $/ by localizing internally +
    +
+ +June 20, 2009 - Version 7.79 +
    +
  • Added read/write support for Adobe InDesign files (.IND, .INDD, .INDT) +
  • Added ability to geotag with KML files (Note: each Placemark must contain a + TimeStamp for this to work) +
  • Added undocumented XMP-xmp PagInfo tags written by Adobe InDesign +
  • Added conversion for MPF:PanOrientation +
  • Many improvements and additions to Olympus and Panasonic makernote decoding +
  • Improved logic of -scanForXMP option +
  • Recognize MPO file extension (Extended Multi-Picture format) +
  • Distinguish between infinite (inf) and undefined (undef) rational values +
  • Changed namespace prefixes for xapG and xapGImg to match current XMP spec +
  • Changed print conversion for Casio AFPointPosition +
  • Made "Error reading value" warning minor when reading makernotes values +
  • Allow all tags to be deleted from an XMP file +
  • Fixed group names for a few Panasonic and Sony makernote tags +
+ +June 13, 2009 - Version 7.78 +
    +
  • Added read support for the new CIPA standards: Multi Picture Format (MPF) + and Stereo Still Image format (Stim) +
  • Added support for Kodak type 10 makernotes (Z980) +
  • Added a new Pentax LensType and a new Nikon LensID (thanks Jens Duttke) +
  • Added %C format code for output file names +
  • Decode a number of camera settings from Sony DSLR images +
+ +June 7, 2009 - Version 7.77 +
    +
  • Added -struct option for JSON (-j) and XML (-X) outputs +
  • Added 2 new Pentax LensType's and a PentaxModelID (thanks Jens Duttke) +
  • Decode large preview in APP2 of images from newer Samsung models +
  • Extract FujiFilm PreviewImage from improperly written FPXR segment +
  • Improved decoding of Nikon WB levels for some models +
  • Reduced memory usage when writing DNG and some other RAW image files +
  • Changed format of Canon D30 SerialNumber to remove the hyphen and add + leading 0's if less than 9 characters (now same format as printed on camera) +
  • Changed writing of GPSTimeStamp and GPSDateStamp to adjust date/time to UTC + if it contains a timezone, and added timezone ("Z") to Composite:GPSDateTime +
  • Suppress "Unlisted FPXR segment (index 255)" warning from some Kodak images +
  • Suppress "Unrecognized MakerNotes" warning for Samsung STMN-type maker notes +
  • Made "Unrecognized MakerNotes" a minor warning +
  • Fixed problems reading/writing large PreviewImage in some Sony JPEG images +
  • Fixed problem decoding some base64 values in XML files +
  • API Changes: + +
+ +May 20, 2009 - Version 7.76 +
    +
  • Added support for Leica RWL raw images (just RW2 with a different name -- + Panasonic is pulling the same dumb stunt as Nikon with NRW) +
  • Added ability to specify geotagging parameters via config file +
  • Added two new Canon LensType's (thanks Jose Oliver-Didier) +
  • Added a couple more Panasonic FilmMode values +
  • Added bitmapped value lookups to -listx output +
  • Decode Panasonic face recognition information (DMC-TZ7) +
  • Decode some new FujiFilm face detection tags +
  • Implemented language translations for bitmapped values +
  • Enhanced -geotag option to allow wildcards in track file name +
  • Minor changes to Nikon AF point decoding +
  • Allow empty string when writing unknown values (ie. "Unknown ()") +
  • Pad numerical IPTC values with zeros if necessary when writing +
  • Fixed problem with -geotag feature interpolating in some NMEA logs +
  • API Changes: +
      +
    • Added GeoMaxHDOP, GeoMaxPDOP, GeoMaxIntSecs and GeoMaxExtSecs options +
    +
+ +May 9, 2009 - Version 7.75 +
    +
  • Added a few new translations (thanks Jens Duttke et al) +
  • Added warning when stream mode data is encountered in a ZIP file (this + is currently not supported) +
  • Added a couple of new Nikon ActiveD-Lighting values (thanks Werner Kober) +
  • Added and changed some Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added ability to specify user-defined option defaults in config file +
  • Added write support for FujiFilm S5Pro firmware 1.11 RAF images +
  • Decode AF point information for more Nikon models (thanks Werner Kober) +
  • Improvements to new geotagging feature +
  • Changed language code for simplified Chinese from "zh_s" to "zh_cn" +
  • Changed user-defined shortcuts to Image::ExifTool::UserDefined::Shortcuts +
  • Limit PrintConv precision of Composite GPSAltitude to 1 decimal place +
  • API Changes: +
      +
    • Changed WriteInfo() to use a temporary file instead of a memory buffer + when a source file name is given with no destination file +
    • Attempt (yet again) to fix problems when UTF-8 encoded strings are + passed to exiftool functions +
    +
+ +Apr. 10, 2009 - Version 7.74 +
    +
  • Added geotagging feature and new -geotag option (guess who finally bought a + hand-held GPS!) +
  • Added a few new Casio RecordMode values +
  • Decode FujiFilm EXRAuto and EXRMode tags (FinePix F200EXR) +
  • Decode Olympus ArtFilter tag +
  • Allow EXIF ISO to have multiple values as per EXIF spec +
  • Improved XMP-exif and XMP-tiff list-type tags to allow copying from EXIF +
  • Changed handling of ComponentsConfiguration to facilitate copying between + EXIF and XMP +
  • Changed name of EXIF tag 0x9214 from SubjectLocation to SubjectArea to match + EXIF specification +
  • Changed behaviour when writing pre-existing EXIF tags to use the standard + EXIF field type instead of preserving the existing type (fixes problem + rewriting some incorrectly typed EXIF tags) +
  • Fixed error if a shift value is not given when shifting a date/time tag +
  • Fixed makernote offsets error message when writing Pentax Optio WP images +
  • API Changes: +
      +
    • Added EditOnly option to SetNewValue() +
    +
+ +Mar. 31, 2009 - Version 7.73 +
    +
  • Added write support for Panasonic RW2 images (including IPTC and XMP) +
  • Added ability to write IPTC and XMP to Panasonic/Leica RAW images and fixed + bug introduced in version 7.64 which disabled write support for these images +
  • Added a new Canon EasyMode value (thanks Irwin Poche) +
  • Added a number of new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added CanonModelID for the new 500D +
  • Decode many CameraInfo and ColorData tags for the Canon EOS 500D +
  • Decode track-level 'meta' atom in MOV videos +
  • Enhanced Canon Composite:ShootingMode logic to distinguish Bulb mode +
  • Improved decoding of Canon TargetExposureTime +
  • Changed name of Panasonic RW2 PreviewImage to JpgFromRaw +
  • Fixed bug where JPEGDigest wasn't generated for some images +
  • Fixed problem where -F didn't permanently fix makernote offsets for some + images when writing +
  • Fixed bug decoding Canon RawMeasuredRGGB and MeasuredRGGBData which resulted + in a failed test on 64-bit systems +
+ +Mar. 20, 2009 - Version 7.72 +
    +
  • Added a new Minolta/Sony LensType (thanks Jens Duttke) +
  • Added support for localized language descriptions of "lang-alt" tags +
  • Added support for Nikon NRW files (please just kill me now) +
  • Added two new PentaxModelID's and a new PentaxImageSize +
  • Decode Pentax PEF HuffmanTable as Unknown Binary tag +
  • Decode Leaf and Kodak records in DNGAdobeData information +
  • Made "Empty PrintIM data" a minor warning +
  • Minor improvement to Canon lens recognition logic +
  • Changed Composite:LensID to also return a value for Olympus lenses +
  • Changed copying behaviour to preserve the specific location (family 1 group) + when source group is specified and destination group is "all" or "*" + (eg. "-exif:all>all:all" now preserves the IFD of each tag) +
  • Fixed a number of incorrect Minolta/Sony lens names (thanks Olaf Ulrich) +
  • Fixed bug rewriting MIE trailers on TIFF images +
+ +Mar. 12, 2009 - Version 7.71 +
    +
  • Added a new Pentax LensType (thanks Akos Szalkai) +
  • Added a new Canon LensType (thanks Kurt Garloff) +
  • Added new PentaxModelID for the Optio P70 +
  • Added XMP list-type flag (Alt, Bag or Seq) to "-f -listx" output +
  • Decode a number of new Canon tags (thanks Vesa Kivisto) +
  • Removed unreliable Canon Composite FlashOn tag (use Flash instead) +
  • Removed Nikon FlashModel tag and replaced it with ExternalFlashFirmware +
  • Changed tags in Canon "ColorBalance" tables to signed integer and renamed + the tables to "ColorData" +
  • Changed formatting for Canon FocalUnits +
  • Changes to -X output: +
      +
    • Now uses 'rdf:datatype' instead of 'et:encoding' (thanks Alexander Vonk) +
    • Improved long (-l) output to produce valid RDF/XML, and added 'et:val' +
    +
  • Improved handling of unknown XMP lang-alt tags +
  • Fixed family 2 group names for a few tags +
+ +Feb. 26, 2009 - Version 7.70 +
    +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added a number of new CanonModelID's +
  • Added ability to use -f before -listx to output 'flags' attribute +
  • Added xml:lang attribute to -X output (when used with -t, -H or -D) to + identify alternate language entries for XMP lang-alt tags +
  • Decode Canon ImageUniqueID and added a new EasyMode value +
  • Created "unsafe" shortcut used when rebuilding JPEG EXIF metadata from + scratch +
  • Changed Olympus lens "pre-release" designation to "release 1" +
  • Changed exiftool to continue after encountering "Error opening directory" +
  • Enhanced makernote-offset-fix logic to account for problems like those + caused by bugs in Picasa and ACDSee +
  • API Changes: +
      +
    • Enhanced GetTagID() to also return language code in list context +
    +
+ +Feb. 17, 2009 - Version 7.69 +
    +
  • Added a new Nikon LensID (thanks Jens Kriese) +
  • Added a new Pentax LensType (thanks Jens Duttke) +
  • Added Extra JPEGDigest tag +
  • Recognize new Panasonic APP2 MPF information written by FX40 +
  • Improved -@ option to allow a UTF-8 BOM at the start of the input file +
  • Augmented -listx output to include indexed value conversions +
  • Changed Japanese and Chinese language codes to 'ja' and 'zh' (ISO 639-1) +
  • Fixed a few problems with some CanonCustom tags +
+ +Feb. 13, 2009 - Version 7.68 +
    +
  • Added French translations for XMP and Composite tags (thanks Jean Piquemal) +
  • Decode Panasonic AdvancedSceneMode, added a few more SceneMode values, and + fixed incorrect format for TextStamp +
  • Decode a missing Canon 1DmkII custom function +
  • Changed Czech language code to 'cs' (as per ISO 639-1) +
  • Relaxed XMP date/time validation to allow writing year-only and year-month + values (YYYY and YYYY:MM) without requiring the -n option +
  • More work on language translations (this will be ongoing) +
  • Fixed problem shifting XMP date/time values with missing seconds +
  • Fixed some family 1 group names in -listx output +
+ +Feb. 9, 2009 - Version 7.67 (production release) +
    +
  • IMPORTANT: Fixed bug introduced in version 7.01 which could cause corruption + of TIFF-format images in very rare situations when adding tags to an image + containing very large (> 10 MB) binary data blocks +
+ +Feb. 7, 2009 - Version 7.66 +
    +
  • Improved language support +
  • Changed conversion for a couple of the EXIF Flash values +
  • Removed trailing white space from Make and Model values +
  • Removed null terminators that may be left on some string values +
  • Fixed problem with family 1 group names for QuickTime Date tags +
  • Fixed problem with invalid names being generated for some unknown tags +
  • Fixed decoding of ASF PreviewMimeType and PreviewDescription +
  • Fixed formatting problems with -j output when combined with some options +
+ +Feb. 5, 2009 - Version 7.65 +
    +
  • Added -j option for JSON (JavaScript Object Notation) output format +
  • Improved French language translation for File group (thanks Jean Piquemal) +
  • Enhanced -listx option to give short output when used after -s +
  • Renamed "tagid" attribute to "id" in -X output to match -listx output +
  • Fixed bug introduced in 7.64 which resulted in runtime warning when + extracting non-existent tags with the -f option +
  • Fixed problem which could cause runtime error with -listx option on some + systems +
+ +Feb. 3, 2009 - Version 7.64 - "Babel fish" +
    +
  • Added -listx and -lang options +
  • Added preliminary support for the following languages (thanks Jens!): +
      +
    • en [default] +
    • ch_s (thanks Haibing Zhong) [renamed 'zh_cn' in 7.75] +
    • cz (thanks Petr Michálek) [renamed 'cs' in 7.68] +
    • de (thanks Jens Duttke) +
    • en_ca (for those of us who like to see "colour" spelled properly) +
    • en_gb (correct "colour" plus a few other quirks) +
    • es (thanks Santiago del Brío González) +
    • fr (thanks Bernard Guillotin) +
    • it (thanks Emilio Dati) +
    • jp (thanks Kazunari Nishina) [renamed 'ja' in 7.69] +
    • nl (thanks Peter Moonen and Herman Beld) +
    • pl (thanks Przemyslaw Sulek) +
    +
  • Added support for new XMP Windows Live Photo Gallery tags +
  • Decode two new Panasonic tags and improved decoding of some others +
  • Decode a few new 3rd party EXIF and IPTC tags +
  • Enhanced -X output by adding -t feature for tag table information +
  • Improved decoding of Photoshop ClippingPathName and remove Unknown flag +
  • Renamed Panasonic EXIF "Title" tag to "PanasonicTitle" and improved decoding +
  • Fixed problem which could cause crash if reading corrupted images on Windows +
  • Fixed inconsistencies rewriting XMP which uses extra rdf:Description + elements instead of rdf:parseType='Resource' attribute +
  • Fixed decoding of Nikon D40 RemoteOnDuration +
  • API Changes: + +
+ +Jan. 23, 2009 - Version 7.63 +
    +
  • Added new Composite tags: SubSecCreateDate and SubSecModifyDate +
  • Decode Sony DSLR WB_RGBLevels tags (thanks Andrey Tverdokhleb) +
  • Decode a few more NikonScan tags (thanks Brendt Wohlberg) +
  • Included new argument files in distribution: xmp2exif.args and exif2xmp.args +
  • Improved decoding of PentaxModelID for K-m and K2000 +
  • Minor change to decoding of Canon 1DmkIII ISOSpeedRange +
  • Downgrade "MRW format error" to a warning when reading ARW images containing + MRW information that has been corrupted by the Sony IDC utility +
  • Renamed Kodak SubSecTime tag to Time +
  • Changed Composite DateTimeCreated tag to use only IPTC tags +
  • Changed name of Sony/Minolta MRW WBLevels tag to reflect ordering of color + components +
  • Fixed problems recognizing some MP3 files +
+ +Jan. 16, 2009 - Version 7.62 +
    +
  • Decode a number of new tags for recent Canon EOS models +
  • Decode ID3v2.3 Compilation tag (written by iTunes) +
  • Added a number of new ID3 genre's and improved ID3v2 genre conversion +
  • Avoid converting MIE ISO 8859-1 string values +
  • Enhanced XML output (-X) to work with binary data (-b) option and encode + values in base64 if necessary +
  • Fixed problem with invalid UTF-8 when writing XMP or using -X (XML) option +
+ +Jan. 10, 2009 - Version 7.61 +
    +
  • Added a new Pentax LensType and a new PentaxModelID (thanks Denis Bourez) +
  • Added ability to copy makernotes from Pentax or Samsung native DNG image +
  • Decode makernotes in Samsung GX model DNG images +
  • Decode CameraTemperature for Canon EOS cameras with Live View (thanks + Karl-Heinz Klotz) +
  • Decode a number of Canon 5DmkII CameraInfo tags +
  • Included 2 new argument files in distribution: xmp2gps.args and gps2xmp.args +
  • Prevent writing of TIFF images containing the obsolete (and unsupported) + TIFF 6.0 JPEG extensions +
  • Fixed bug which could result in runtime warning when writing makernotes as a + block +
+ +Jan. 6, 2009 - Version 7.60 (production release) +
    +
  • Decode a few more Nikon D700 FlashInfo tags (thanks Jens Duttke) +
  • Defined (empty) XMP-pdfx tag table, mainly for documentation purposes +
  • Fixed problem where the behaviour of -tagsFromFile changed to that of + -addTagsFromFile if the first specified tag was an exclusion +
  • Fixed XMP writer to allow a namespace to be deleted after a mass copy +
  • Fixed bug introduced in 7.58 which could cause hang when using -tagsFromFile +
+ +Dec. 23, 2008 - Version 7.59 +
    +
  • Removed file size limit when setting tag value from contents of a file +
+ +Dec. 22, 2008 - Version 7.58 +
    +
  • Added new Canon, Nikon and Olympus lenses (thanks Jan Boelsma and Geert De + Soete) +
  • Added write support for FujiFilm S5000 Ver3.00 and S9500 Ver1.01 RAF images +
  • Extract RAFVersion tag from FujiFilm RAF images +
  • Decode ColorBalance information for PowerShot G10 +
  • Decode Sharpness for Canon EOS 50D +
  • More improvements to Canon 50D and 5DmkII makernote decoding +
  • Attempt to identify unknown Nikon lenses which exist in LensID list with a + different LensIDNumber (to patch Sigma lens renumbering debacle) +
  • Removed limit of 1000 items in an XMP list-type tag when writing +
  • Increased maximum size of file from 16MB to 100MB when setting tag value + from the contents of a file +
  • Improved performance when extracting a large number of same-named tags +
  • Fixed bug which resulted in "segment too large" error message when rewriting + multi-segment XMP if XMP was edited but nothing was actually changed +
+ +Dec. 11, 2008 - Version 7.57 +
    +
  • Added read support for Panasonic RW2 raw images (and extract meta + information from embedded PreviewImage as Doc1) +
  • Added new Pentax K-m PictureModes and new PentaxModelID for the Optio S12 +
  • Decode ColorBalance information for Canon 50D and 5DmkII +
  • Decode Panasonic RAW/RW2 information from DNG images +
  • Decode Canon SRAWQuality tag +
  • Recognize DCP (DNG Camera Profile) files +
  • Updated Canon CustomFunctions for the EOS 5D Mark II +
  • Changed name of "OtherImage" tags to "JpgFromRaw" in IFD0 of SR2 and ARW + images, and to "ThumbnailImage" in IFD0 of MRW images +
  • Changed EXIF DeviceSettingDescription and ProfileLookTableData to binary + data tags +
  • Fixed problem reading/writing ThumbnailImage in Minolta A200 MRW images +
  • Fixed ColorBalance2 tags for AsShot and Auto modes of Canon 1DmkII/1DSmkII +
+ +Dec. 2, 2008 - Version 7.56 +
    +
  • Decode CompressorVersion from Canon 5D Mark II videos +
  • Fixed family 1 group classifications for tags in QuickTime video tracks +
  • Fixed problem with new -sep feature when separator contained spaces +
+ +Dec. 2, 2008 - Version 7.55 +
    +
  • Added a number of new CanonVRD tags for DPP 3.4/3.5 (thanks Bogdan) +
  • Added a new FocusMode for the Pentax K-m +
  • Added a new Nikon LensID (thanks Niels Kristian) +
  • Decode some tags from Kodak C1013 maker notes (type 9) +
  • Enhanced -sep option to allow list-type tag values to be split when writing +
  • API Changes: + +
+ +Nov. 26, 2008 - Version 7.54 +
    +
  • Added a few old XMP-crs tags that were missed +
  • Show numerator and denominator for rational EXIF values in verbose mode +
  • Changed htmldump tooltip font +
  • Fixed bugs in HTML reader that could cause runtime error or hang +
+ +Nov. 19, 2008 - Version 7.53 +
    +
  • Added read/write support for EXIF files +
  • Added ability to write EXIF as a block (finally!) +
  • Added ability to write CanonVRD information to MIE files +
  • Added timezone to "Now" tag value +
  • Added a new CanonModelID (FS100) +
  • Added write support for ACDSee XMP tags (XMP-acdsee:RPP) +
  • Added a few new XMP-cc tags +
  • Decode CameraOrientation for a number of Canon EOS models (thanks Bogdan) +
  • Allow XMP to be copied as a block with -tagsFromFile option +
  • Highlight odd value offsets in -htmldump output +
  • Improved htmldump tooltip display +
  • Minor improvements to MIE reader +
  • API Changes: +
      +
    • The full XMP block is now extracted with the Binary option, so the XMP + block is marked as "unsafe" and the Protected flag must be set (as with + other writable blocks) when calling SetNewValue() +
    +
+ +Nov. 4, 2008 - Version 7.52 +
    +
  • Added ability to extract AI private data from PDF files +
  • Added extract embedded option (-ee, -extractEmbedded) +
  • Added new group family 3 and ability to specify multiple group names for a + single tag when extracting information +
  • Added a new Sony lens and decode two new Sony tags (thanks Jens Duttke) +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added a new Olympus LensType (thanks Michael Meissner) +
  • Decode a few new Nikon tags (thanks Jens Duttke) +
  • Enhanced command line parsing to allow long names for most options +
  • Improved verbose output when writing makernotes +
  • Allow writing of empty string values in EXIF information +
  • Fixed problem rewriting XMP lists that contained no entries +
  • Fixed bug writing JpgFromRaw and ThumbnailImage to CRW files that could make + the image unreadable by Canon utilities (affected images may be repaired by + rewriting the same tag with this version of exiftool) +
  • Fixed bug where some Canon MakerNote values could not be written +
  • Fixed bug introduced in version 7.49 that broke the use of wildcards in + filenames for the Windows version +
  • API Changes: +
      +
    • Enhanced a number of functions to accept multiple group names separated + by colons +
    +
+ +Oct. 27, 2008 - Version 7.51 (production release) +
    +
  • Fixed problems which caused failed test or warning with Perl 5.6 or older + (does do not affect Mac or Windows versions) +
  • Fixed Windows application so help is displayed when run with no options +
+ +Oct. 26, 2008 - Version 7.50 (production release) "XMP 2008" +
    +
  • Added a number of new XMP tags from new XMP specification released Oct. 17 +
  • Added support for extended XMP segment in JPEG images (as per new XMP spec) +
  • Added a number of new Minolta/Sony lenses (thanks Jens Duttke) +
  • Added a new Canon LensType (thanks Andreas Huggel and Pascal de Bruijn) +
  • Added new PRISM 2.1 XMP tags +
  • Added ability to read/write x:xmptk attribute (via XMP-x:XMPToolkit tag) +
  • Added ability to specify user-defined Lenses +
  • Decode XMP in ASF (WMA/WMV), FLV, SWF and MP4 audio and video files +
  • Preserve byte order of EXIF information when copying to MIE file +
  • Allow byte order for newly created MIE files to be set by ExifByteOrder tag + (and API ByteOrder option) +
  • Allow backslashes in filenames on non-Windows-like systems +
  • Removed 's' from XMP-xmp:Thumbnails tag names and set Avoid flag for + XMP-xmp:ThumbnailImage +
  • Fixed definitions of some XMP-xmpDM tags +
  • Fixed some PDF reader bugs (thanks Leonhard Zachl for one patch) +
  • API Changes: + +
+ +Oct. 16, 2008 - Version 7.49 +
    +
  • Added new PentaxModelID for K-m/K2000 plus a new LensID used by K-m +
  • Added --a option and made -a the default behaviour for the -X option +
  • Added ability to read/write XMP-rdf:about attribute +
  • Added new "Resource" flag which may be set in user-defined XMP tags to write + a value as an rdf:resource instead of a normal string +
  • Allow decimal (real) values to be written to XMP-xmp:Rating (contrary to + current XMP specification, but as per MWG recommendation) +
  • Fixed file renaming bug in Windows that caused the file to be moved into the + current directory instead of leaving it in the original directory when the + source file was specified using backslashes as directory separators +
+ +Oct. 14, 2008 - Version 7.48 +
    +
  • Added support for XMP PRISM 2.0 schema tags +
  • Added two more ZIP compression types +
  • Added conversions for XMP-plus date tags +
  • Changed conversion of all Digest tags to make the -n value readable +
  • Changed some error handling to avoid generating console warnings +
+ +Oct. 11, 2008 - Version 7.47 - "Jumbo" +
    +
  • Added -X option to output extracted information in XML format +
  • Added -listwf option to list extensions of writable files +
  • Added a number of new Nikon and Pentax LensTypes (thanks Robert + Rottmerhusen, Jens Duttke and Bozi) +
  • Decode Canon 1000D custom functions +
  • Decode a number of new tags written by Nikon Capture NX 2 +
  • Decode many FlashInfo tags for the Nikon D90 and D700 +
  • Implemented character set translation for MIE information (-L option) +
  • Improved speed when scanning unknown file to determine FileType +
  • Fixed bug where some writable EXIF tags gave a "not writable" message when + reading tag value from a dynamic file (eg. "-TAG<=%f.txt") +
  • Fixed problem double-escaping characters when -h and -S were used together +
  • Fixed decoding of Nikon FlashModel for SU-800 Remote Commander +
  • Fixed swapped Nikon FlashGroupBControlMode/FlashGroupCControlMode tags +
  • Fixed bug reading PDF files that could cause "Argument isn't numeric in + subtraction" warning (note that writing PDF files with this problem could + cause format errors which may be fixed by reverting with "-pdf-update:all=") +
  • API Changes: +
      +
    • Fixed CanWrite() to be consistent with documentation +
    +
+ +Oct. 2, 2008 - Version 7.46 +
    +
  • Fixed bug which could cause a runtime warning when writing images in a + directory containing an unrecognized file type +
  • Fixed an IPTC-XMP test that failed in other time zones (this was a test + problem, not an exiftool bug) +
+ +Oct. 1, 2008 - Version 7.45 +
    +
  • Added support for new XMP IPTC Extension 1.0 tags (rev 2) +
  • Added a few more TIFF Compression values (for MDI files) +
  • Decode a few new Nikon Flash tags +
  • Decode Canon 50D custom functions +
  • Calculate CurrentIPTCDigest tag (if Digest::MD5 is available) +
  • Renamed Photoshop CaptionDigest tag back to IPTCDigest again +
  • Avoid touching IPTC data block when only Photoshop information is changed +
  • Allow IPTCDigest to be set to the special value of 'new', representing the + new IPTC digest of the output file +
  • Updated iptc2xmp.args and xmp2iptc.args to write IPTCDigest as per MWG + recommendation +
  • Allow zone-less date/time values in XMP (as per MWG and upcoming XMP spec) +
  • Allow brackets in $$ and $/ expressions (eg. ${$} and ${/} now work) +
  • Changed decoding of EXIF:Copyright to allow two separate strings as per spec +
  • Changed a number of XMP Iptc4xmpCore tag names and added a corresponding set + of aliases (shortcuts) for backward compatibility +
  • Changed some XMP xmpTPg tag names +
  • Fixed problem extracting lists from other information types in MIE files +
+ +Sept. 26, 2008 - Version 7.44 +
    +
  • Added read support for DjVu images +
  • Added two new Sony LensType's (thanks Mladen Sever) +
  • Added a new Pentax LensType (thanks Jens Duttke) +
  • Decode a few new Canon 450D and 1000D tags (thanks Bogdan) +
+ +Sept. 17, 2008 - Version 7.43 +
    +
  • Added two new Pentax LensTypes (thanks Jens Duttke and Anton Bondar) +
  • Added PentaxModelID's for the Optio E60 and M60 +
  • Added a number of new CanonModelID's +
  • Extract XMP from MOV and AVI videos (as written by Adobe CS3 Bridge) +
  • Decode information from QuickTime HintInfo atoms (hinf and hnti) +
  • Decode Canon 50D/5DmkII AutoLightingOptimizer +
  • Enable writing of ThumbnailImage in CR2 images +
  • Avoid extracting invalid Canon FocusDistance tags +
  • Improved handling of timezones in date/time values (fixes failed EXE test) +
+ +Sept. 11, 2008 - Version 7.42 +
    +
  • Added read support for Windows, MacOS and Unix executable and library files +
  • Added read support for ZIP and RWZ (Rawzor) compressed files +
  • Added a number of new XMP tags written by PS Elements 4.0 (thanks Drew + Holland) and LightRoom 2.0 +
  • Added new Sony, Canon and Nikon LensTypes (thanks Jens Duttke and Werner + Kober) +
  • Decode a few new Canon CameraInfo tags for the 40D, 50D, 450D and 1000D + (thanks D.J. Cristi) +
  • Decode Nikon D90 LensData +
  • Define version number etc. in properties of exiftool Windows executable +
  • Improved handling of corrupted makernote offsets when writing +
  • Fixed problem where FileType could be incorrect for a TIFF-based file with + the wrong extension +
+ +Aug. 28, 2008 - Version 7.41 +
    +
  • Added new Composite LensID tag and changed a number of LensType values in + an attempt to disambiguate Canon, Pentax, Minolta and Sony 3rd party lenses +
  • Added -sep option to specify separator for values in list-type tags +
  • Added a new Nikon LensID (thanks Jens Duttke) +
  • Added CanonModelID values for new models (SX110, A1000, A2000, E1, 50D) +
  • Decode some CameraInfo tags of the Canon EOS 450D and 1000D (thanks Bogdan) +
  • Decode a few new tags in Kodak MOV videos +
  • Updated CanonVRD decoding for version 3.40 (DPP 3.4.1, thanks Bogdan) +
  • Allow writable EXIF properties to be overridden by user-defined tags +
  • Relaxed PDF parsing to allow xref tables with zero entries +
  • Renamed Sigma LensID tag to LensType +
  • Changed PDF update structure to better conform with PDF specification +
  • Changed conversion of Olympus ManometerReading values +
  • Reverted back to Perl 5.8 for Windows EXE version (fixes problem running + exiftool.exe using a non-standard TEMP directory) +
  • Patched DST problem in Windows when "Automatically adjust clock for daylight + savings time" is used in Windows Date and Time settings +
  • Fixed problems in the QuickTime parser that could cause exiftool to hang +
  • Fixed bug which could cause an error to be reported when writing a DNG image + containing ProfileIFD information +
  • API Changes: + +
+ +Aug. 17, 2008 - Version 7.40 +
    +
  • Fixed -p option in Windows executable version (caused by packaging problem + with Perl 5.10 release) +
+ +July 30, 2008 - Version 7.39 +
    +
  • Added a number of new Canon LensType values (thanks Rich Taylor) +
  • Added a new Pentax LensType (thanks Jens Duttke) +
  • Added a new Sony LensType (thanks Mladen Sever) +
  • Added support for writing invalid IFD entries used by some Kodak Z cameras +
  • Updated Canon CustomFunctions for EOS 450D +
  • Made a few more DNG tags writable +
  • Renamed CIFF TvValue and AvValue tags to ShutterSpeedValue and ApertureValue + and added conversions (to seconds and F-number) as with EXIF tags +
+ +July 18, 2008 - Version 7.38 +
    +
  • Same as version 7.37 except that Windows executable is packaged with Perl + 5.10.0 instead of 5.8.7 -- this fixes a problem with FileModifyDate and DST +
+ +July 16, 2008 - Version 7.37 +
    +
  • Added -addTagsFromFile option (variant of -tagsFromFile which allows copying + multiple tags into the values of a single list-type tag) +
  • Added a new Sony LensID (thanks Jens Duttke) +
  • Added PentaxModelID for the Optio W60 +
  • Added a couple of new YCbCrSubSampling values (thanks Jens Duttke) and made + values consistent across different types of meta information +
  • Decoded Canon Categories tag (thanks Darryl Zurn) +
  • Reduced priority of XMP-xmp date/time tags so the EXIF tags are preferred +
  • Fixed problem where time may be duplicated in Composite:DateTimeCreated +
  • API Changes: +
      +
    • Added ability to pass options to SetNewValuesFromFile +
    +
+ +July 8, 2008 - Version 7.36 +
    +
  • Added a new Nikon LensID (thanks Jens Duttke) +
  • Fixed bug introduced in 7.33 where a SubIFD error was erroneously reported + when writing an already edited NEF image +
+ +July 6, 2008 - Version 7.35 +
    +
  • Added two new Nikon LensIDs (thanks Geert De Soete and Jens Duttke) +
  • Added XMP-pdf:Trapped tag +
  • Added Composite:GPSAltitude tag (like Composite:GPSLatitude/GPSLongitude) +
  • Added a couple of new PentaxModelID values +
  • Decode Canon 450D Sharpness tag (thanks Bogdan) +
  • Decode Nikon D300 AFAreaMode and AutoFocus tags (thanks Jens Duttke) +
  • Extract Pentax SaturationInfo as an Unknown tag (thanks Dave Nicholson) +
  • Renamed Canon LensType string tag (ID 0x0095) to LensModel +
  • Changed JFIFVersion print conversion to match the formatting used in the + JFIF specification +
  • Fixed a Minolta LensID entry for Tamron lenses +
  • Fixed problem excluding XMP family 1 groups from deletion in some file types +
+ +June 28, 2008 - Version 7.34 +
    +
  • Added names for a few more of the Unknown Photoshop tags +
  • Added support for XMP files with leading XML comments +
  • Added support for older XMP "x:xapmeta", and XMP without "x:xmpmeta" element +
  • Changed priority of XMP:Source tags when writing so XMP-photoshop:Source is + now preferred over XMP-dc:Source +
  • Renamed Photoshop IPTCDigest to CaptionDigest and removed Unknown status +
  • Improved parsing of IPTC time values when writing, and assume the local + timezone (if available) instead of UTC when a timezone is not specified +
  • Improved handling of lists that exist in multiple groups in the same file +
  • Disabled shifting of list-type date/time tags (allows += to add list items) +
  • Reduced priority of XMP-exif and XMP-tiff tags so these values don't + override more reliable EXIF and TIFF tags when extracting information + without specifying a group +
  • Fixed quirk where exiftool could add an extra padding byte to the makernotes +
  • Fixed incorrect tag ID that prevented ImageStabilization from being decoded + in Sony DSLR-A100 images (thanks Ger Vermeulen) +
  • Fixed problem where error/warning messages could be duplicated for + subsequent files when copying tags from multiple files +
+ +June 21, 2008 - Version 7.33 +
    +
  • WARNING: Older ExifTool versions will not properly rewrite DNG 1.2 images + which contain multiple color profiles +
  • Added DNGVersion check to avoid future problems with major DNG revisions +
  • Added support for new DNG version 1.2.0.0 tags +
  • Added support for XMP PLUS License Data Format 1.2.0 tags +
  • Added a new Pentax LensType (thanks Peter) +
  • Added a new Canon LensType +
  • Added support for user-defined XMP structures +
  • Decode a few new Sony tags (thanks Marcus Holland-Moritz) +
  • Decode Nikon Capture NX 2 NikonICCProfile information (thanks Jens Duttke) +
  • Extract MP3 VBR and ID3Size tags +
  • Improved accuracy of MP3 Duration calculation (account for VBR and ID3Size) +
+ +June 12, 2008 - Version 7.32 +
    +
  • Added a new Pentax LensType (thanks yeryry) +
  • Decode ColorBalance information for Canon 450D and 1000D +
  • Fixed names of a few NikonCapture D-LightingHQ tags (thanks Jens Duttke) +
  • Fixed bug where a list-type tag was not created when simultaneously adding + and deleting values from the list +
+ +June 10, 2008 - Version 7.31 +
    +
  • Added proper support for special characters in PDF text strings +
  • Added support for a number of new XMP tags written by Adobe Lightroom 1.4 +
  • Added ability to write XMP-xmp:ThumbnailsImage +
  • Added Photoshop IPTCDigest tag +
  • Added two new Nikon LensID's (thanks Jens Duttke) +
  • Added a new Pentax LensType (thanks Bogdan) +
  • Added a new CanonModelID for the EOS 1000D +
  • Decode a few new Pentax tags (thanks Dave Nicholson) +
  • Increased precision of GPS coordinates when copying with -tagsFromFile +
  • Fixed problem which could result in "Argument isn't numeric" runtime warning + when attempting to write an Unknown value to a bitmapped tag +
+ +May 31, 2008 - Version 7.30 (production release) +
    +
  • Adjusted MakerNote error checks to be a compromise between 7.28 and 7.29 +
  • Fixed various htmlDump problems +
  • Fixed bug which could cause runtime warnings when attempting to write + certain types of unsupported images +
+ +May 28, 2008 - Version 7.29 +
    +
  • Renamed Pentax ModelRevision tag to ProductionCode and improved print + conversion to indicate if camera has been serviced +
  • Added check to prevent EXIF tags from being written to JPEG images if they + would obviously exceed the maximum JPEG segment size limit +
  • Relaxed error checks when writing JPEG images to allow MakerNotes to be + rebuilt if the MakerNote IFD is not contained within the MakerNotes data +
  • Fixed decoding of Pentax ExternalFlashGuideNumber when AF360 is used with + the wide angle panel +
  • Fixed unnecessary "Multiple new values for IFD0 tag 0x927c" warning which + could occur when copying MakerNotes from some images +
+ +May 26, 2008 - Version 7.28 +
    +
  • Added new Canon CustomFunctions values from the EOS 1DmkIII firmware update, + and a new CanonExposureMode value (thanks David Pitcher) +
  • Added a new Olympus LensType (thanks Виктор Лушников) +
  • Decode Pentax ExternalFlashBounce tag (thanks Cvetan Ivanov) +
  • Renamed Pentax ExternalFlashZoom tag to ExternalFlashGuideNumber and + improved decoding (thanks Cvetan Ivanov) +
  • Fixed bug which could prevent maker notes from being copied when copying all + tags from a file containing a PreviewImage +
  • Fixed problems decoding some Sony ARW images +
  • Fixed problem writing some makernote values in sub-IFD's +
  • Fixed "APP1 segment too large" problem where PreviewImage was not dropped + as it should have been when copying all tags from some RAW images +
+ +May 24, 2008 - Version 7.27 - "GIF+XMP" +
    +
  • Added ability to read/write XMP in GIF images +
  • Added ability to write to GIF87a images (by upgrading them to GIF89a) +
  • Added GIFVersion tag +
  • Improved decoding of Canon 1DmkIII/1DSmkIII TimeStamp tags +
  • Changed print conversion of EXIF/XMP GPSStatus tags to make more sense +
  • Fixed bug introduced in version 7.22 that could cause exiftool to abort with + an "'x' outside string" error when processing some DNG images +
  • API Changes: +
      +
    • Extract FileSize information from images passed as a scalar reference +
    +
+ +May 21, 2008 - Version 7.26 +
    +
  • Added write support for FujiFilm FinePix S5 Pro V1.04 RAF images +
  • Added support for new Kodak TIFF-format maker notes used by the Z1085 +
  • Added new Pentax and Nikon LensType's (thanks Jens Duttke, Dave Nicholson + and Robert Rottmerhusen) +
  • Added some new Minolta LensID's (thanks Thomas Käßner) +
  • Added new CanonModelID's and a 1DmkIII TimeStamp (thanks Ger Vermeulen) +
  • Decode a number of new Pentax K10D tags (thanks Dave Nicholson) +
  • Decode Panasonic Title tag (thanks Jens Duttke) +
  • Recognize a few more uncommon top-level QuickTime atoms +
  • Changed decoding of some Olympus tags for new E-520 +
  • Changed warning when empty PrintIM data is encountered (eg. as written in + Sony A700 ARW files when Adobe RGB color mode is used) +
  • Dropped Canon PreviewFocalPlaneX/YResolution tags since they never really + existed (thanks Ger Vermeulen for pointing out the Canon bug which lead to + this false assumption) +
  • Fixed duplicate tag problem with Pentax LensData when -U option used +
  • Fixed bug which could cause a runtime warning when copying Nikon maker notes +
  • Fixed bug in exiftool application which could cause all tags to be copied + instead of just the specified tags when creating an output XMP or MIE file + and using the -tagsFromFile option +
+ +Apr. 18, 2008 - Version 7.25 (production release) +
    +
  • Added read support for DIVX video files +
  • Added a new Nikon LensID (thanks Tanel Kuusk) +
  • Decode a number of new Pentax K10D tags and values (thanks Dave Nicholson) +
  • Decode a few new Nikon tags (thanks Jens Duttke) +
  • Decode Nikon VignetteControl tag found in D3 images with new 1.10 firmware + (thanks Alexandre Naaman) +
  • Improved formatting of video duration times +
  • Improved print conversion for video Compression values +
  • Apply print conversion for XMP:FocalLengthIn35mmFormat to add "mm" +
  • Fixed MIME type of JPEG 2000 images +
  • Fixed problem decoding new Nikon D300 AFPrioritySelection tags +
  • API Changes: +
      +
    • Fixed CanWrite so it returns false for non-writable TIFF-based files +
    +
+ +Apr. 10, 2008 - Version 7.24 +
    +
  • Added read support for SVG (Scalable Vector Graphics) images +
  • Added support for non-standard Apple iPhone PNG images +
  • Added support for ISL maker note format +
  • Added a couple of new Olympus LensType's +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added values for various Sony tags (thanks Jens Duttke) +
  • Decode Nikon D300 custom settings (thanks Jens Duttke) +
  • Decode Nikon D300 AFFineTuneAdj (thanks Neil Nappe) +
  • Decode a number of new Pentax tags and values (thanks Jens Duttke) +
  • Decode a number of new QuickTime tags, including 'mdta' information +
  • Decode a missing Custom Function for Canon 450D +
  • Avoid extracting any unknown tag in binary data tables when -u option used +
  • Avoid writing Canon 1D/1DS RAW images masquerading as TIF (writing 1D + RAW images is not yet supported) +
  • Improved parsing of AFCP ThumbnailImage and PreviewImage +
  • Downgraded errors in the NikonScan and NikonPreview IFD's to allow writing + of images with these problems without requiring the -m option +
+ +Mar. 27, 2008 - Version 7.23 +
    +
  • Decode a number of new Pentax K20D/K200D tags and values +
  • Fixed bug introduced in 7.18 which caused "Error parsing XMP" warning when + deleting all XMP and writing new XMP tags in the same step +
+ +Mar. 25, 2008 - Version 7.22 +
    +
  • Added support for Olympus-style Sony makernotes (DSC-S45/500/650/700/750) +
  • Added %c 'n' modifier to number output files from 1 instead of 0 +
  • Added Extra "Now" tag used for setting a tag value to the current date/time +
  • Added a new Nikon LensID (thanks Jens Duttke) +
  • Added ability to specify byte order for EXIF Unicode text and fixed problem + where text wasn't always written in EXIF byte order by default +
  • Added a new Canon LensType (thanks Hal Williamson) +
  • Added a few new CanonModelID values +
  • Decode a new Pentax K20D tag and add a few new values to other tags (thanks + Jens Duttke) +
  • Recognize non-standard Nikon ICC Profile files +
  • Improved error checking when writing a JPEG image with a bad IFD +
  • Fixed bug where IFD0 could be deleted when writing JPEG with a bad IFD1 +
  • Fixed some Olympus LensType names for Leica lenses +
  • Fixed problem extracting some writable directories as a block +
  • Fixed bug which could cause "Not an ARRAY" error when reading PDF files +
+ +Mar. 12, 2008 - Version 7.21 (production release) +
    +
  • Added support for Leica M8 maker notes (in both DNG and JPEG images) +
  • Added ability to write encrypted Nikon makernote information (!!) +
  • Added a new Olympus Leica lens (thanks Chris Shaw) +
  • Decode a couple of new Canon 40D and 1DmkIII tags (thanks Chris Huebsch) +
  • Decode Adobe RAF data in DNG images +
  • Decode a few new Nikon D3 and D300 tags (thanks Jens Duttke) +
  • Calculate VideoFrameRate for QuickTime MOV videos +
  • Marked DNG OriginalRawFileName and OriginalRawFileData as "unsafe" to copy +
  • Changed decoding of Casio BestShotMode +
  • Renamed Nikon NEFCurve tags (thanks Jens Duttke) +
  • Patched problem parsing OriginalDecisionData for the Canon EOS 5D +
+ +Mar. 7, 2008 - Version 7.20 +
    +
  • Added a few new Minolta LensID's +
  • Added two more TIFF-IT tags to the EXIF table +
  • Added a number of new RIFF and ASF Audio Encoding values +
  • Added a new new values for some Canon tags (thanks Dave Nicholson) +
  • Decode a number of new Pentax K10D tags (thanks Dave Nicholson) +
  • Decode a number of new MP4/QuickTime tags +
  • Decode makernotes in Casio, Kodak, Minolta, Olympus and Ricoh AVI and MOV + videos +
  • Improved decoding of Casio maker notes and decode a few new tags (thanks + Jens Duttke) +
  • Removed incorrect CanonD30 ColorTemperature and ColorMatrix tags +
  • Fixed Location translation in iptc2xmp.args and xmp2iptc.args +
  • Fixed problem decoding some Nikon tags in images edited by Capture NX +
  • Fixed decoding of InternalSerialNumber for Canon 5D +
  • Fixed decoding of Nikon D3 color balance information +
  • Fixed decoding of Minolta 7D FocusMode (thanks Jens Duttke) +
+ +Feb. 25, 2008 - Version 7.19 +
    +
  • Added a new Pentax LensType and some new Panasonic NoiseReduction values + (thanks Jens Duttke) +
  • Decode Nikon D40 and D40X custom settings plus a couple of other tags +
  • Decode a couple of new Pentax K10D tags (thanks Dave Nicholson) +
  • Improved reliability of Canon FocalPlaneXSize and FocalPlaneYSize tags +
  • Recognize HP Type2 maker notes in images from other makes +
  • Write TIFF ApplicationNotes in 'int8u' format as per XMP specification +
  • Made TIFF ApplicationNotes writable as a block +
  • Changed HtmlDump to show actual IFD format if different than read format +
  • Changed some MeteringMode strings to be more consistent +
  • Fixed problem adding back JFIF information after deleting JFIF group +
+ +Feb. 21, 2008 - Version 7.18 +
    +
  • Added ability to exclude XMP family 1 groups from deletion +
  • Added patch to recognize new Ricoh R50 maker notes +
  • Added a new Minolta LensID (thanks Jens Duttke) +
  • Decode AFPointsUsed for Nikon D3 and D300 (thanks Jens Duttke) +
  • Decode a couple of new Pentax K10D tags (thanks Dave Nicholson) +
  • Improved decoding of Nikon FlashInfo tags (thanks Jens Duttke) +
  • Renamed Olympus FlashExposureCompensation tag to FlashExposureComp +
  • Patched problem with Perl 5.10.x which broke conversion of UTF8 strings +
  • Fixed problem where an ExposureTime of 1 second was ignored in CRW images +
  • Fixed problem where special characters were not handled properly when using + the -L option while copying IPTC tags +
  • Fixed bug which could cause a runtime error when attempting to write JFIF + information after deleting JFIF:all in the same step +
+ +Feb. 16, 2008 - Version 7.17 +
    +
  • Extract duplicate tags when -p option is used +
  • Fixed bug introduced in 7.00 which broke the use of group family numbers and + groups ending with a digit in tag format strings (eg. "$IFD0:Model") +
+ +Feb. 14, 2008 - Version 7.16 +
    +
  • Added a couple of new Pentax LensTypes (thanks Jens Duttke) +
  • Added a few more EXIF:Compression values +
  • Decode color balance levels in Leaf MOS images +
  • Decode a number of new tags from JPEG, TIFF, KDC and DCR images of older + Kodak models +
  • Improved decoding of TIFF SampleFormat tag +
  • Made a number of DNG tags "unsafe" so they aren't copied by default +
  • Allow JPEG EXIF segment to be deleted and a new EXIF segment to be created + with a different byte order in a single command +
  • Attempted to improve reliability of ScaleFactor35efl calculation for newer + Canon models +
  • Fixed a couple more places where we still needed a space before "mm" +
  • Fixed problem with LightValue calculation which caused failed tests for Perl + 5.6.2 on Darwin +
+ +Feb. 5, 2008 - Version 7.15 (production release) +
    +
  • Added a few new CanonModelID's and PentaxModelID's +
  • Added support for new Pentax K20D/K200D values for some tags +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Decode a few new Sigma tags, including PreviewImage +
  • Decode a few more tags in Canon CRW images (thanks Dave Nicholson) +
  • Improved Sony ARW parsing (fix some problems and extract more tags) +
  • Improved handling of timezone when writing EXIF and XMP information (the + timezone is now added to XMP date/time values and removed from EXIF + date/time values if necessary unless the -n option is used) +
  • Recognize a few more FLV AudioEncoding and VideoEncoding values +
  • Allow "pseudo" tags to be copied from unrecognized file types +
  • Made FileModifyDate an "unsafe" tag so it isn't copied unless specified +
  • Changed all "sec" units to "s" with a leading space for consistency +
  • Fixed bug introduced in version 6.91 that could prevent some XMP date/time + tags from being written when copying with "-all:all" +
+ +Jan. 25, 2008 - Version 7.14 +
    +
  • Added read support for Kodak KDC raw images +
  • Added ability to read/write Canon OriginalDecisionData in JPEG, CR2 and DNG + images +
  • Added ValueConv translations for some of the new Nikon PictureControl tags +
  • Decode a number of new Nikon tags (thanks Jens Duttke and Gregor Dorlars) +
  • Decode Canon CR2Segmentation tag +
  • Decode a new Canon CustomFunction of the EOS 450D +
  • Improved handling of mandatory tags in EXIF information +
  • Changed all FocalLength print conversions to add a space before "mm" +
  • Renamed Canon Self-timer tags to SelfTimer for consistency +
  • Fixed some problem with -htmlDump for some types of trailer information +
  • Fixed problem which could give a runtime warning when attempting to delete a + permanent tag +
+ +Jan. 17, 2008 - Version 7.13 +
    +
  • Decode a couple more Nikon and Sony tags +
  • Decode Windows HD Photo "Padding" tag +
  • Recognize HDP (Windows HD Photo) file extension +
  • Designated EXIF CompressedBitsPerPixel and ComponentsConfiguration as + "unsafe" tags so they aren't copied by -tagsFromFile by default +
  • Changed priority of new Nikon D3/D300 ISO tag +
  • Changed Canon LensType for a Tamron lens (thanks Monica Wallek) +
  • Fixed incorrect TagID for new Panasonic Sharpness tag +
+ +Jan. 15, 2008 - Version 7.12 +
    +
  • Added read support for ITC (iTunes Cover Flow) files +
  • Added ability to deal with corrupted IPTC written by Nikon Capture NX +
  • Added a few new Canon LensType's (thanks Steve Balcombe) +
  • Decode a number of new Nikon D3/D300 tags (thanks Gregor Dorlars) +
  • Decode a number of new FujiFilm and Panasonic tags and values +
  • Decode ColorBalance information for the Canon 40D, 1DmkIII and 1DSmkIII +
  • Improved decoding of Nikon D80 VibrationReduction tag (thanks Jens Duttke) +
  • Renamed Pentax WBShiftBA and WBShiftGM tags to WBShiftAB and WBShiftMG (now + more consistent with Pentax software, but inconsistent with Canon naming) +
  • Fixed a CanonImageHeight tag which was incorrectly named CanonImageWidth +
+ +Jan. 10, 2008 - Version 7.11 +
    +
  • Decode a number of new Canon tags and improved decoding of many old tags +
  • Renamed EXIF:RelatedImageLength to RelatedImageHeight (hopefully all + ImageWidth/Height tag names are now consistent) +
+ +Jan. 7, 2008 - Version 7.10 +
    +
  • Added support for escape sequences and continuation comments in EPS files +
  • Added ability to read/write Sony A700 PreviewImage (tag 0x2001) +
  • Added a new Sony ColorMode value (thanks Philippe Devaux) +
  • Decode a number of new Minolta tags +
  • Improved handling of newlines when writing PDF information +
  • Improved decoding of Canon 40D and 1DmkIII FocusDistance tags (thanks + Wolfgang Hoffmann) +
  • Fixed problem creating multiple output meta files with some commands +
  • Fixed problem deleting XMP by value for strings with escaped characters +
  • Fixed bug when trying to write output image to console with "-o -" +
  • Fixed problem where %c (copy number) was changed when the new file name + should have been the same as the source file +
+ +Jan. 3, 2008 - Version 7.09 +
    +
  • Decode Canon ThumbnailImageValidArea +
  • Improved decoding of some Olympus tags (thanks Frank Ledwon) +
  • Improved decoding of some Pentax tags (thanks Dave Nicholson) +
  • Improved error messages when writing PDF files +
  • Changed XMP-cc namespace URI (spec apparently changed for some reason) +
  • Changed Photoshop XMLData to a binary data tag +
  • Changed conversion strings for Canon ModifiedSharpnessFrequency values +
  • Changed Olympus NoiseReduction "ISO Boost" value back to "Noise Filter (ISO + Boost)" +
  • Fixed minor problem writing PDF cross-reference stream after multiple edits +
  • Fixed problem redirecting some verbose output to an output text file +
+ +Dec. 21, 2007 - Version 7.08 +
    +
  • Added write support for PDF files which use only cross-reference streams +
  • Added a number of new Olympus tags, and changed names of some existing tags +
  • Fixed problem decoding some PDF cross-reference streams +
  • Fixed bug introduced in 7.07 which broke copying between two list-type tags +
+ +Dec. 18, 2007 - Version 7.07 +
    +
  • Added ability to write XMP and PDF information to PDF files, with revert + capability! (use "-PDF-update:all=" to undo all exiftool edits) +
  • Added PDF:AppleKeywords tag (written by Apple Preview) +
  • Added Composite FOV (Field Of View) tag +
  • Added a few more Minolta/Sony LensID's +
  • Added new Canon and Pentax LensType's (thanks Magne Nilsen and Jens Duttke) +
  • Added "Nothing changed" message in verbose mode for files that weren't + changed when writing +
  • Added minor warning when invalid IFD entries are removed during writing (you + will get this, for instance, when ExifTool fixes the entry count problem in + Canon EOS 40D firmware 1.0.4 maker notes) +
  • Patched Canon 40D firmware 1.0.4 problem for JPEG images too +
  • Decode specified "unknown" zero values for four EXIF tags (ExposureProgram, + LightSource, MeteringMode and SubjectDistanceRange) instead of handling as a + truly unknown value (if this makes sense) +
  • Extract PreviewImage from newer Panasonic RAW images (thanks Jens Duttke) +
  • Recognize Pentax-type Kodak maker notes (eg. Easyshare 883) +
  • Made "Entries out of sequence" a minor warning since this problem is fixed +
  • Allow decimal seconds to be written in time values without needing to use -n +
  • Improved parsing of PDF files +
  • Improved behaviour when copying list-type tags to to non-List tags +
  • Improved exiftool summary message for files that were copied without changes +
  • Adjusted Pentax K10D battery percentage calibration +
  • Changed names of Pentax FirmwareID tags +
  • Fixed runtime warning that could occur with some invalid tag names +
  • Fixed problem decoding Pentax:LensCodes for some images (thanks Jens Duttke) +
  • API Changes: +
      +
    • Also allow File::RandomAccess reference as argument to ImageInfo() +
    +
+ +Dec. 7, 2007 - Version 7.06 +
    +
  • Permanently fix MakerNote offsets with -F option when writing +
  • A few more Pentax tag improvements (thanks Dave and Jens) +
+ +Dec. 6, 2007 - Version 7.05 +
    +
  • Patched problem rewriting Canon 40D CR2 images caused by bug in the 40D + firmware 1.0.4 which writes an improperly formatted MakerNote IFD +
  • More improvements in decoding Pentax K10D tags (thanks Dave Nicholson) +
  • Translate non-standard XMP namespace prefixes +
  • Changed a couple of Kodak Meta tags to Binary data type +
  • Renamed Pentax MeasuredLV to EffectiveLV (thanks Jens Duttke) +
+ +Dec. 3, 2007 - Version 7.04 +
    +
  • COMPATIBILITY WARNING: Renamed EXIF:ExifImageLength to ExifImageHeight and + XMP:GPSTimeStamp to GPSDateTime +
  • Added write support Minolta A200 MRW images +
  • Added read support for Hasselblad 3FR raw images +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added a new Canon LensType (thanks Bogdan) +
  • Added ability to insert a newline using "$/" in a print format string +
  • Decode some new FujiFilm and Pentax tags (thanks Jens Duttke) +
  • Decode some new Pentax and Canon tags (thanks Dave Nicholson) +
  • Recognize a few new Olympus lenses (thanks Michael Meissner) +
  • Improved decoding of Sony ARW images and added support for A700 +
  • Improved warnings for HtmlDump option +
  • Improved string parsing when writing date/time tags +
  • Fixed problem extracting Canon CRW RGGB values from DNG images +
+ +Nov. 17, 2007 - Version 7.03 +
    +
  • Fixed problem deleting XMP family 1 groups from JPEG images +
+ +Nov. 16, 2007 - Version 7.02 +
    +
  • Added ability to delete XMP family 1 groups (eg. "-XMP-crss:all=") +
  • Fixed problem writing XMP as a block to XMP file +
+ +Nov. 15, 2007 - Version 7.01 +
    +
  • Added ability to write FujiFilm RAF images (thanks Jens Duttke) +
  • Added -T option (equivalent to -t -S -q -f) +
  • Decode a number of new Pentax tags and values (thanks Dave Nicholson) +
  • Decode a new Canon LensType value (thanks Bogdan) +
  • Decode the not-so-accurate FocusDistanceUpper and FocusDistanceLower in + Canon EOS 1DmkIII and 40D images (thanks Heiko Hinrichs) +
  • Allow FileSource tag to be assigned values outside the EXIF standard +
  • Made ImageSourceData a protected tag +
  • Avoid loading huge binary data blocks into memory unless necessary (avoids + out-of-memory problem when processing huge, layered Photoshop TIFF images) +
  • Improved HtmlDump speed and memory usage by not loading "snipped" data +
  • Improved decoding of Nikon ShootingMode +
  • Various improvements and bug fixes when reading FujiFilm RAF information +
  • Fixed problem decoding CRW images where ImageWidth wasn't extracted with -U +
+ +Oct. 23, 2007 - Version 7.00 (production release) +
    +
  • IMPORTANT: Fixed problem writing ORF images from newer Olympus cameras which + could lead to errors when the image is opened by another utility (affected + images may be repaired by rewriting with this version of ExifTool) +
  • Added -ScanForXMP option +
  • Added ability to extract ID3v2 PRIV tags (including XMP) and the ID3:MCDI + tag (plus unknown ID3v2 tags with the -u option) +
  • Added new PentaxModelID's for Optio V10 and A40 +
  • Added support for Casio-like and HP-like Pentax maker notes +
  • Added ICC_Profile WCSProfiles tag (thanks Jens Duttke) +
  • Added ability to write and create CanonVRD as a block +
  • Added ability to shift GPSTimeStamp tag +
  • Added ability to write DNG AsShotICCProfile and CurrentICCProfile tags +
  • Decode VRDOffset tag in Canon MakerNotes +
  • Shortcuts may now be used in redirections and expressions, and with group + names +
  • Improved decoding of CanonVRD information (also decode new DPP 3.0 tags and + fixed a problem which could give a "Possibly corrupt CanonVRD" warning) +
  • Improved decoding of FujiFilm RAF images, and extract JPEG Preview +
  • Improved handling of Pentax Casio-style maker notes +
  • Improved conversion for Pentax K10D AFPointsInFocus +
  • Enhanced Composite tag syntax to simplify user-defined tag definitions +
  • Changed decoding of Nikon VibrationReduction 0x0075 tag +
  • Changed a number of Pentax and Casio tags to improve consistency +
  • Dump unsupported files with -htmlDump only if -u option is used +
  • Fixed problem which could cause a virtual hang when writing large EPS files +
  • Fixed problem of misleading error messages when attempting to write + unsupported file formats +
  • Fixed problem outputting list-type tags with -b option +
  • Fixed bug where the "image files created" count could miss some files +
  • Fixed problem where "Error rebuilding maker notes" warning could be issued + in cases where the maker notes do not need rebuilding +
+ +Oct. 6, 2007 - Version 6.99 +
    +
  • Added support for IView MediaPro XMP tags +
  • Added ability to read multiple comments from GIF89a images +
  • Added some new PentaxModelID's (Optio L20, T20, Z10) +
  • Added minor warning for unknown JPEG APP segments when -u option is used +
  • Extract information from JPEG APP13 "Adobe_CM" segment +
  • Improved -htmlDump output to show TIFF image data and trailer (the TIFF dump + is now complete) +
  • Improved decoding of Minolta WhiteBalance for some DiMAGE models +
  • Improved decoding of Panasonic FirmwareVersion when -n option is used +
  • Increased precision of 64-bit rational conversion from 7 to 10 digits +
  • Fixed problem which caused failed tests with Perl 5.005_05 +
  • Fixed problem where some groups could not easily be excluded when deleting + all other information (eg. "-all= --exif:all" now behaves as expected) +
  • Fixed problem decoding ICC Profile "dtim" format values +
  • Fixed typo in a Minolta FlashMetering value (thanks Jens Duttke) +
  • Fixed problem in API which could result in a UTF-8 encoded file not being + properly identified if it was passed as a scalar reference to WriteInfo() +
+ +Sept. 23, 2007 - Version 6.98 +
    +
  • Added ExifByteOrder tag (writable to set byte order for new Exif segments) +
  • Added CanonModelID for new EOS-1Ds Mark III +
  • Added value conversions for Pentax AEFlashTv, AEXv and AEBXv tags +
  • Decode Pentax ShutterCount (with help from Jens Duttke) +
  • Decode Pentax AFPointsInFocus for newer DSLR models (thanks Jens Duttke) +
  • Improved decoding of a Pentax LensType (thanks Jens Duttke) +
  • Renamed Pentax AutoAFPoint to AFPointsInFocus and improved conversion +
  • Renamed Pentax AEDump to AEMeteringSegments and converted values to + approximate LV equivalent units +
  • Fixed problem where some warnings were not being properly handled when + attempting to write an invalid value to some tags +
+ +Sept. 14, 2007 - Version 6.97 +
    +
  • Added support for Canon EOS 40D Custom Functions +
  • Added ability to decode new Nikon D3 and D300 LensData +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Decode Olympus NoiseFilter tag (thanks Ioannis Panagiotopoulos) +
  • Decode a few new Nikon ShotInfo tags (thanks Jens Duttke) +
  • Improved decoding of Canon AF point information +
  • Improved decoding of Nikon HighISONoiseReduction +
  • Renamed Nikon VRState to VibrationReduction +
  • Fixed typo which prevented some Olympus MakerNote tags from being written +
+ +Sept. 5, 2007 - Version 6.96 +
    +
  • Added ability to read/write XMP alternate languages +
  • Added ability to create new GPS information in Panasonic RAW images +
  • Added a few new PentaxModelID's (Optio E40, M40 and S10) +
  • Added a couple of new Pentax LensType's (thanks Jens Duttke) +
  • Added a new Olympus Sigma LensType (thanks Jens Duttke) +
  • Added EOS 40D CanonModelID and prepared for new 40D custom functions +
  • Decode a large number of new Canon tags +
  • Decode SerialNumber from previously unknown maker notes of some Kodak models +
  • Decode Olympus ImageStabilization tag (thanks Jens Birch) +
  • Improved decoding of Canon Self-timer and AFPoint values +
  • Improved decoding of some tags for high end Canon EOS models +
  • Renamed Pentax LensCoefficients to LensCodes and print 16 values +
  • Renamed Panasonic ImageStabilizer to ImageStabilization +
  • Renamed all AFPointsUsed tags to AFPointsInFocus +
  • Fixed decoding of ICC_Profile DeviceAttributes +
+ +Aug. 21, 2007 - Version 6.95 +
    +
  • Added support for new Kodak IFD-format makernotes used by the P712, P850, + P880, Z612 and Z712 +
  • Added a few new Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added LensType's for 2 new Pentax DA* lenses (thanks Jens Duttke) +
  • Added 2 new FujiFilm S5 WhiteBalance values (thanks Paul Samuelson) +
  • Added a number of new CanonModelID's +
  • Extract TIFFPreview from DOS EPS images +
  • Decode a number of new Panasonic tags, and added a number of new SceneMode's +
  • Decode FujiFilm S5 ColorTemperature tag (thanks Paul Samuelson) +
  • Improved handling of unknown XMP list-type tags +
  • Suppress EPS 'unterminated Document data' warning +
  • Fixed decoding of ASCII-type Panasonic FirmwareVersion +
  • Fixed bug calculating leap years for years outside the range 1601-2399 +
  • API Changes: +
      +
    • Changed WriteInfo() behaviour to be more consistent when editing file in + place and a new FileName is specified (original file is now deleted) +
    • Improved warning message when trying to write an 'unsafe' tag +
    +
+ +July 26, 2007 - Version 6.94 +
    +
  • Added a few new XMP-crs tags +
  • Added ability to create a new Photoshop IRB record in TIFF-format images +
  • Added a few new EXIF:Compression values (thanks Jens Duttke) +
  • Added a number of new Panasonic/Leica tags, and changed the names of some + Panasonic tags, including reverting FirmwareVersion (thanks Jens Duttke) +
  • Added test for Unknown (Bulb) Pentax ExposureTime value (thanks Jens Duttke) +
  • Added a new Nikon LensID (thanks Vladimir Sauta) +
  • Avoid extracting information from documents embedded in EPS images + (this is temporary; eventually I want to figure out a way to allow this + information to be extracted separately) +
  • Decode Red/BlueBalance from Leica Digilux 2 RAW images (thanks Jens Duttke) +
  • Changed conversion for Sony A100 Rotation tag to conform to EXIF:Rotation +
  • Changed decoding of one of the Pentax ExternalFlashBounce tags (thanks + Michael Meissner) +
  • Extract EncodingProcess, BitsPerSample, ColorComponents and YCbCrSubSampling + from JPEG SOF segment +
  • Show raw horizontal/vertical widths in the converted YCbCrSubSampling value +
  • Improved conversion of some Pentax tags (thanks Jens Duttke) +
  • Avoid loading data blocks larger than 16MB from QuickTime images +
  • Allow PDF:Keywords to be comma-delimited +
  • Fixed problem where a tag would be removed from both IFD0 and ExifIFD even + if only IFD0 or ExifIFD was specified +
  • Fixed problem with byte order mark showing up in output when decoding + hex-encoded Unicode values from PDF images +
  • Fixed problem where ExifTool could hang when reading corrupted ASF files +
  • Fixed possible problem with infinite recursion in FlashPix-format files +
+ +July 6, 2007 - Version 6.93 +
    +
  • Added read support for BigTIFF images (with extensions BTF, TIF and TIFF) +
  • Added a number of new Olympus tags and fixed decoding of a few others + (thanks Jens Duttke) +
  • Added a number of new SigmaRaw tags (found in SD14 X3F images) +
  • Changed conversion for Canon LensType 152 (used by various Sigma models) +
  • Fixed problem editing XMP containing new "Camera Raw Saved Settings" + properties (written by Adobe Lightroom) +
+ +June 29, 2007 - Version 6.92 +
    +
  • Added read support for FLV (Flash Video) files +
  • Added read support for EXIF and IPTC and write support for EXIF, IPTC and + XMP in JPEG 2000 images +
  • Added read/write support for Sinar CS1 raw images +
  • Added read support for Kodak DCR and K25 raw images +
  • Added ability to read/write improperly byte-swapped IPTC information +
  • Added check for infinity value of Casio ObjectDistance +
  • Added a new Nikon LensID (thanks Bruce Stevens) +
  • Improved decoding of APP12 "Ducky" segment (thanks Heinrich Giesen) and + added write/create support +
  • Improved handling of warning messages when setting new values +
  • Changed print conversion for Olympus PictureModeSaturation, + PictureModeContrast and PictureModeSharpness to label min and max values +
  • Fixed problem introduced in 6.91 when writing some EPS images +
  • Fixed group names for Pentax CameraInfo tags +
  • Fixed bug which could result in negative Canon SerialNumber values +
  • Fixed decoding of some Canon EOS 1DmkIII custom function values +
  • Fixed problem copying subdirectories in new-style Olympus maker notes +
  • Fixed problem of missing last character when decoding ID3 Unicode strings +
  • Fixed problems decoding some ID3 URL values +
  • Fixed inconsistency where the -if option may have used a different tag than + the one normally extracted when a group name was specified and multiple + matching tags existed in the group +
+ +June 5, 2007 - Version 6.91 +
    +
  • Added support for new XMP-lr, XMP-photoshop and XMP-DICOM tags of PS CS3 +
  • Added new Sigma lens to Pentax LensID list +
  • Added a few new Nikon and Canon LensID's (thanks Jens Duttke) +
  • Added Canon IXY Digital 810 IS to CanonModelID +
  • Recognize Photoshop "AgHg" resource type +
  • Removed "warnings" dependency in exiftool application +
  • Updated XMP:FileSource values to match EXIF:FileSource +
  • Greatly improved processing speed for some large EPS images +
  • Improved handling of XMP date/time formatting +
  • Officially support writing of MEF images +
  • Applied patch to convert Pentax LensType for changes in K10D firmware 1.2 +
  • Fixed decoding of Pentax BatteryBodyGripStates (thanks Jens Duttke) +
+ +May 10, 2007 - Version 6.90 (production release) +
    +
  • Added CanonModelID values for new PowerShot S5 IS and SD850 IS +
  • Encode IPTC values in default CodedCharacterSet when writing new values at + the same time as deleting the existing CodedCharacterSet +
  • Renamed Nikon FirmwareVersion to MakerNoteVersion and Panasonic + FirmwareVersion to ProductionVersion (thanks Jens Duttke) +
  • Allow EXIF GPS coordinates to be negative when writing (take absolute value) +
  • Revert "$evalWarning" fix (false alarm) +
+ +May 7, 2007 - Version 6.89 +
    +
  • Added support for maker notes of some Hewlett-Packard models +
  • Decode Pentax ImageProcessing tag +
  • Fixed problem which gave "$evalWarning" errors on some systems +
+ +May 2, 2007 - Version 6.88 +
    +
  • Added read support for Mamiya MEF images +
  • Implement long overdue change to standardize FocalPlaneResolutionUnit values +
  • Decode Panasonic BabyAge + some new ShootingMode values (thanks Jens Duttke) +
  • Improved recognition of maker notes for some camera models +
  • Fixed bug that could cause an incorrect "tag is not writable" warning +
  • Fixed problems converting WDP PixelFormat values +
  • Fixed decoding of Canon 350D AFPointsUsed (thanks Bogdan) +
  • API Changes: +
      +
    • Added option to allow makernote block to be extracted without rebuilding +
    +
+ +Apr. 26, 2007 - Version 6.87 +
    +
  • Added read/write/delete support for recognized trailers in PSD images +
  • Added PhotoMechanic IPTC:Prefs tag +
  • Added ability to decode double-UTF-encoded XMP files +
  • Added a few more Canon, Pentax and Nikon lens types (thanks Hayo Baan and + Robert Rottmerhusen for Nikon entries) +
  • Added ability to create new user-defined MIE groups +
  • Decode a new Nikon lens tag: ExitPupilPosition (thanks Robert Rottmerhusen) +
  • Increased precision (from 20m to 2mm) when writing XMP GPS coordinates +
  • Renamed Panasonic SpotMode tag to AFMode and improved decoding +
  • The -e (Composite) option now also applies when copying tags +
  • Minor changes to IPTC verbose output and error handling +
  • Minor changes to a few warning messages +
  • Avoid converting XMP values as rational or date if tag is known and not + specified with these formats +
  • Identify CR2 header and Canon MakerNote footer in -htmlDump output +
  • Reverted change from version 6.85 to once again allow JPEG thumbnails to be + written to TIFF-type images (perfectly valid for many TIFF-based RAW formats + even though it isn't technically correct in a proper TIFF) +
  • Added test to check for invalid encoding when Image::ExifTool is loaded +
  • Fixed problem shifting Canon:TimeStamp tag +
  • Fixed failed FlashPix test on Cygwin Perl 5.8.2 (roundoff errors again) +
  • Fixed problem where some types of write errors could result in exiftool + reporting that a file was updated when it wasn't +
+ +Apr. 10, 2007 - Version 6.86 +
    +
  • Added -execute, -srcfile and -common_args options to allow complex + processing with multiple commands in a single invocation +
  • Added ability to write Panasonic RAW files +
  • Added Panasonic ConversionLens tag +
  • Improved decoding of Panasonic/Leica Contrast and SpotMode tags +
  • Changed -@ to insert arguments at the current position in the command line + (rather than at the end) +
  • Once again automatically fix Canon maker note offsets (this feature was + removed in 6.84 due to a bug bug report that turned out to be a false alarm) +
  • Fixed bug in -if option which could incorrectly cause a failed condition + when using expressions containing multiple tags with proper-case names +
  • Fixed problem extracting binary data when -if option was used +
  • Fixed bug which caused error when setting CodedCharacterSet to "UTF8" +
  • Fixed decoding of InternalSerialNumber for FujiFilm FinePix F40fd +
  • Fixed problem using "-TAG+<=FMT" or "-TAG-<=FMT" on command line +
+ +Apr. 3, 2007 - Version 6.85 +
    +
  • Prevent JPEG thumbnail image from being written to TIFF-type images +
  • Fixed a couple of problems decoding Canon EOS 1D Mark III tags +
  • Fixed bug which generated an error message when rewriting maker notes in + Adobe-edited Pentax K10D native DNG images +
+ +Mar. 30, 2007 - Version 6.84 +
    +
  • Added a number of new XMP-crs tags, plus new XMP-lr (Adobe Lightroom) group +
  • No longer automatically fix Canon makernote offsets (but still use makernote + footer if present to calculate recommended fix) +
  • Fixed problem where some errors were not properly counted in the summary + statistics with the -overwrite_original_in_place option +
  • Fixed problem parsing XMP shorthand format for values containing '=' symbol +
+ +Mar. 24, 2007 - Version 6.83 +
    +
  • Automatically fix corrupted makernote offsets when reading images from Canon + models which include a makernote offset footer +
  • Added CanonModelID and CameraType values for 2 new Canon DV cameras +
  • Renamed SPIFF ResolutionUnits tag to ResolutionUnit +
  • Fixed formatting of GPSTimeStamp value +
+ +Mar. 20, 2007 - Version 6.82 +
    +
  • Added read/write support for new Canon EOS-1D Mark III custom functions +
  • Made a few makernotes warnings minor when writing +
  • Append "mm" to FocalLengthIn25mmFormat value +
  • Fixed problem which could cause "uninitialized value" warning when writing +
  • Fixed problem writing Canon EOS D60 custom functions +
+ +Mar. 17, 2007 - Version 6.81 +
    +
  • Added l/u modifiers for lower/uppercase in filename format codes (eg. "%le") +
  • Added equivalent IXY names to CanonModelID for PowerShot SD750 and SD1000 +
  • Added a few new Pentax ModelID's (Optio E30, T30, W30, A30) +
  • Allow non-encrypted Nikon ColorBalance values to be written +
  • Fixed problem where some encrypted Nikon information was not properly + protected against writing +
+ +Mar. 14, 2007 - Version 6.80 +
    +
  • Added Olympus ManometerReading tag +
  • Added ability to edit private IPTC and XMP information found inside + PhotoshopSettings record of TIFF images +
  • Renamed NikonShotInfoVers tag to ShotInfoVersion and added + MultiExposureVersion tag +
  • Search further in MPEG file to look for first audio/video frame headers +
  • Use default resolution information from JPEG JFIF segment for mandatory EXIF + resolution tags when creating new EXIF segment +
  • Enhanced %c format code so %+c adds an underline before the copy number +
+ +Mar. 7, 2007 - Version 6.79 +
    +
  • Translate special characters in ID3 information when reading +
  • Improved conversions for GPSTimeStamp and GPSDateStamp when writing so they + can be set from a normal date/time tag (eg. "-gpstimestamp<createdate") +
  • Added support for Nikon D40X plus a new LensID (thanks Robert Rottmerhusen) +
  • Added a new Canon LensType (thanks Warren Stockton) +
  • Removed D70Boring shortcut tag +
  • Fixed minor problem in HtmlDump of MakerNotes header introduced in 6.78 +
  • Fixed problem decoding second Pentax K10D LensType value for some lenses +
+ +Feb. 28, 2007 - Version 6.78 +
    +
  • Decode Nikon D200 multi-exposure tags +
  • Decode Canon BlackLevel tag and added a few new CanonModelID's +
  • Added support for new Olympus u760 maker note format (finally Olympus fixes + the major blunders of their older maker note design!) +
  • Added support for the rare Canon EOS K236 (variation of EOS 400D) +
  • Improved decoding of Canon EOS 1D Mark III tags +
  • Included PDF version of MIE format specification in distribution +
  • Reformat invalid EXIF date/time values when writing (unless -n option used) +
  • Minor updates to some Pentax tags for Optio M30 +
+ +Feb. 20, 2007 - Version 6.77 - "XML/HTML special characters" +
    +
  • Translate numeric character references when reading XMP +
  • Translate all HTML 4 character references to UTF-8 when reading HTML +
  • Translate all non-ASCII characters to HTML character entities with -h or -E +
  • Added full UTF-8 translation support when run with Perl pre-5.6.1 +
  • Decode a few new Sigma SD14 tags +
  • Decode a couple more Nikon tags (thanks Greg Troxel) +
+ +Feb. 16, 2007 - Version 6.76 (production release) +
    +
  • Added patch for Perl 5.6.x bug which caused an HTML test to fail +
  • Added a few new Pentax tags and fixed a LensType value (thanks Axel Kellner) +
+ +Feb. 14, 2007 - Version 6.75 (production release) +
    +
  • Added read support for DOC, XLS and PPT documents +
  • Added Composite GPS tags to facilitate copying GPS between EXIF and XMP +
  • Added patch for problems in Sanyo J1, J2, J4, S1, S3 and S4 maker notes +
  • Added new Microsoft OffsetSchema tag (new, ill-conceived PhotoInfo tag) +
  • Decode more Pentax tags and improved decoding for some K10D tags +
  • Shortened tag name of HTML:MSSmartTagsPreventParsing to NoMSSmartTags +
  • Fixed oversight to allow new IPTC and XMP records to be added to ORF images +
  • Fixed problem extracting RIFF MakerNotes by tag name +
  • Fixed problem with drag-n-drop of Windows files on a network drive +
  • Fixed problem copying GPSAltitude from EXIF to XMP +
+ +Feb. 2, 2007 - Version 6.74 +
    +
  • Added support for chained SubIFD's in TIFF images +
  • Updated GeoTiff support for new definitions in libgeotiff-1.2.3 +
  • Fixed problem when rewriting unknown records in Adobe DNGPrivateData +
  • Fixed bug introduced in 6.47 that could prevent Photoshop EXIF CameraRAW + tags from being extracted properly +
+ +Jan. 31, 2007 - Version 6.73 +
    +
  • Added read support for meta information in HTML and XHTML documents +
  • Added ability to write certain EXIF tags (eg. UserComment) as Unicode +
  • Added character set translation for XMP information; the -L option now works + for all common meta information formats! (see updated FAQ #10 for details) +
  • Added a few more XMP-microsoft tags (thanks Kees Moerman) +
  • Decode FirmwareRevision found in some Canon PowerShot models +
  • Preserve date/time tags that exist in the wrong EXIF IFD when shifting times +
  • Fixed bug which could result in an incorrect value for the Directory tag +
  • Fixed problem parsing XMP with BOM introduced in 6.71 +
+ +Jan. 25, 2007 - Version 6.72 +
    +
  • Added XMP-microsoft:LastKeywordIPTC tag +
  • Renamed new MicrosoftPhoto Rating2 tag to RatingPercent +
  • Fixed problem where rdf:about attribute could be lost when writing XMP +
+ +Jan. 24, 2007 - Version 6.71 +
    +
  • Decode a lot of new Pentax DSLR information (thanks Cvetan Ivanov) +
  • Patched Microsoft Photo bugs in XMP formatting +
  • Patched Microsoft Photo bug in EXIF Unicode text byte ordering +
  • Added support for XMP-microsoft tags and 2 new Microsoft EXIF tags +
  • Added a few new XMP tags (NativeDigest, ColorMode and ICCProfileName) +
  • Added ability to add or delete copied tags from list (eg. "-SRCTAG+>DSTTAG") +
  • Added a few more Canon EasyMode values (thanks Samson Tai) +
  • Added CanonModelID values for new A450, A460 and A550 +
  • Changed the -if option so the condition automatically fails if the + expression generates a warning (use -v to show the warning) +
  • Specified LF character (0x0a) for MIE text newline sequence +
  • Catch warnings if perldoc doesn't exist when running with no arguments +
  • Minor tweaks/fixes to htmldump output +
+ +Jan. 19, 2007 - Version 6.70 - "IPTC Character Coding" +
    +
  • Translate coded characters in IPTC string values (UTF8 and Latin only), and + assume Latin encoding if no CodedCharacterSet (see FAQ #10 for details) +
  • Enhanced IPTC:CodedCharacterSet print conversion so "ESC % G" is now printed + as "UTF8" (either may be used when writing) +
  • Specified ISO 8859-1 character set for MIE ASCII string values +
  • Added warnings for UTF-8 conversion errors +
  • Decode a few new Pentax tags +
  • Decode maker notes in Pentax DNG images +
+ +Jan. 8, 2007 - Version 6.69 +
    +
  • Decode information in NikonScanIFD +
  • Enhanced -p option to allow expressions to be used +
  • The -p option no longer suppresses error and warning messages +
  • Made ImageSourceData writable +
  • Reduced font size of htmldump output +
  • Fixed "Argument isn't numeric" error when reading an image with a missing + IFD offset +
+ +Jan. 3, 2007 - Version 6.68 +
    +
  • Added mechanism to allow Composite tags to be writable +
  • Recognize XMP sidecar files that begin with a UTF BOM (byte order mark) +
  • Changed TIFF ImageSourceData tag to a Binary data type +
  • Fixed problem which could cause warning when writing XMP in PNG images +
  • Fixed bug when shifting times in an XMP sidecar file that caused an invalid + date/time to be written if the tag didn't previously exist +
  • Fixed problem where writing to a JPEG image containing a PreviewImage could + report that the file was updated even if nothing was changed +
+ +Dec. 30, 2006 - Version 6.67 - "Adobe DNGPrivateData" +
    +
  • Added ability to write MakerNote information written by Adobe DNG Converter +
  • Added ability to copy Adobe MakerNote and CRW information from DNG images +
  • Added ability to read/write Adobe CRW and MRW information in DNG images +
  • Added ability to read Adobe SR2 information in DNG images +
  • Added a few more Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added ability to delete a specific MIE document in multi-document files +
  • Improved handling of tags in multi-document MIE files +
  • Improved verbose and htmlDump output for unknown JPEG trailers +
  • Improved handling of ignored minor errors when writing MakerNotes +
  • Decode Panasonic LensType tag +
  • Changed description for Canon:OwnerName tag +
  • Minor changes to HtmlDump output +
  • Fixed parsing of XMP date/time values with no seconds +
+ +Dec. 20, 2006 - Version 6.66 (production release) +
    +
  • Added a few more Pentax K10D PictureMode's (thanks Axel Kellner) +
  • Added a few new Nikon LensID's and Olympus LensType's +
  • Added Canon 1D PictureStyle's +
  • Updated CanonModelID strings for a few new models +
  • Changed tagID for MIE:GPSDifferential +
  • Minor change to MIE specification for unknown data formats (MIE 1.1) +
+ +Dec. 15, 2006 - Version 6.65 - "MIE 1.0" +
    +
  • Added ability to read/write MIE trailers in JPEG and TIFF images +
  • Added a number of new MIE tags and changed some existing tags +
  • Added support for units in MIE values +
  • Added new Pentax K10D PictureMode's (thanks Axel Kellner) +
  • Avoid creating non-native groups in MIE, PNG and EPS images unless necessary +
  • Fixed problem with -P option so it now works when -o option is used +
  • Fixed bug where 'all' was replaced with '*' in redirection expressions +
  • Fixed "APP1 segment too large" error when copying all tags from some Canon + CR2 images to a JPEG (fixed initially in 6.08, but broken again in 6.47) +
+ +Dec. 8, 2006 - Version 6.64 +
    +
  • Added Nikon ImageAuthentication tag (thanks Jeffrey Friedl) +
  • Added Canon RecordMode and OpticalZoomCode and Composite DigitalZoom tag +
  • Applied FocalUnits scaling to Canon ShortFocal, LongFocal and + ScaledFocalLength tags, and renamed ScaledFocalLength to FocalLength +
  • Allow (but ignore) leading family number on tag group when writing +
  • Fixed calculation of 35mm scaling factor when Canon digital zoom is applied +
  • Fixed bug which could cause "'x' outside of string" error when reading Nikon + images with the -U option +
+ +Dec. 6, 2006 - Version 6.63 +
    +
  • Changed the sense of the '-' modifier for the new '%c' format code +
+ +Dec. 6, 2006 - Version 6.62 +
    +
  • Added '%c' format code to add copy number if output file exists +
  • Added a couple of new Nikon LensID's (Werner Kober, Robert Rottmerhusen) +
  • Made -htmlDump tag names purple if actual offset differs from stored offset +
+ +Dec. 4, 2006 - Version 6.61 +
    +
  • MakerNotes offsets are now permanently fixed when the makernotes are copied + using -tagsFromFile with the -F option +
  • Fixed typo in MakerNoteSanyoC4 tag name of MakerNotes shortcut +
  • Minor improvements to htmldump style +
+ +Dec. 2, 2006 - Version 6.60 +
    +
  • Added -k option of stand-alone version to regular distribution +
  • Fixed bug adding/deleting XMP tags in a list (introduced in 6.50) +
  • Fixed decoding of Canon 5D LongExposureNoiseReduction +
  • Fixed problem writing AFCP where incorrect offset could be written +
  • Fixed bug in -p option which caused it to abort if all tag names were + contained in braces (thanks Joel Becker) +
  • Stand-alone Windows executable: +
      +
    • Print application documentation after "No file specified" warning +
    +
+ +Nov. 30, 2006 - Version 6.59 +
    +
  • Do not delete IFD1 when deleting all meta information from a TIFF image +
  • Added a couple of new CanonImageSize values: "Postcard" and "Widescreen" +
  • Added a few new Olympus LensType's (thanks Lilo Huang for one) +
  • Improved handling of invalid date values +
  • Fixed "divide by zero" warning if FocalPlaneXYResolution is "inf" +
  • Fixed incorrect "unknown trailer" verbose message when writing JPEG images +
  • Stand-alone Windows executable: +
      +
    • Allow quoting of options embedded in executable name +
    +
+ +Nov. 25, 2006 - Version 6.58 +
    +
  • Added a few more Nikon LensID's (thanks Robert Rottmerhusen) +
  • Added missing print conversion for RIFF DateTimeOriginal +
  • Improved HTML 4.01 compliance of -htmlDump output +
  • Lowered priority of ID3v1 tags so ID3v2 takes precedence if both exist +
  • Minor change to names of some Vorbis and APE tags +
  • Made Ogg file type all capitals +
  • Patched problem which could cause ExifTool to die if input file is corrupt +
  • Fixed GPSDOP description (GPS Dilution of Precision, thanks Greg Troxel) +
  • Fixed problem which could generate a run-time error when attempting to write + to a corrupted JPEG image +
  • API Changes: +
      +
    • GetFileType() may now also be used to return a file description +
    +
+ +Nov. 19, 2006 - Version 6.57 (production release) +
    +
  • Missing tags in -p and redirection expressions are now set to an empty + string ('') by default, or a dash ('-') if the -f option is used +
  • Added ability to use %f,%d,%e tokens in "-TAG<=FILE" argument +
  • Added new Nikon LensID (thanks Werner Kober) +
  • Set missing tags to '' instead of '-' in redirected expressions if -m used +
  • Renamed LV tag to LightValue +
  • Improved decoding of Sony DSLR-A100 maker notes +
  • Attempted to clarify date/time shift documentation in Shift.pl +
  • Fixed bug which could result in CanonVRD information not being recognized +
  • Fixed bug in new SetResourceName feature of user-defined Photoshop tags +
  • First release of stand-alone Windows executable +
  • API Changes: + +
+ +Nov. 15, 2006 - Version 6.56 - "Audio Update" +
    +
  • Added read support for a number of audio file formats: Ogg Vorbis, + Ogg FLAC, FLAC, APE (Monkey's Audio) and MPC (Musepack) +
  • Improved parsing of ID3 v2.3 and v2.4 information +
  • Added a number of new Pentax *istD tags (thanks Douglas O'Brien) +
  • Added ability to print processed file names when writing (-v0 option) +
  • Patched problem with makernotes offsets in Sanyo C4 images +
  • Fixed problem that prevented some Olympus RAW files from being written +
  • Fixed bug where XMP values could be improperly converted as a rational +
+ +Nov. 8, 2006 - Version 6.55 +
    +
  • Added read/write support for Canon VRD (Recipe Data) files and trailers +
  • Changed name of CanonDPP module and group to CanonVRD +
+ +Nov. 3, 2006 - Version 6.54 +
    +
  • Added write support for ORF (Olympus RAW) images +
  • Added Panasonic TravelDay tag (thanks Marcel Coenen) +
  • Show Photoshop resource block names in verbose output, and preserve these + names when copying tags from file +
  • Changed write format of Nikon WhiteBalanceFineTune from int16u to int16s + (thanks Giridhar Appaji Nag) +
  • Allow Flags to be used in UserDefined tags +
  • Added trailer signature to MIE format specification +
  • Fixed problem with the -list and -listw options (dynamically loaded tags + weren't appearing in the list) +
+ +Nov. 1, 2006 - Version 6.53 +
    +
  • IMPORTANT: Fixed bug introduced in 6.51 which could result in a corrupted + image (!!) when rewriting TIFF-format files containing an unknown trailer + (this includes all TIFF-based RAW formats except CR2). The good news is + that unknown trailers should be very uncommon, and nobody has reported any + problems yet, so with any luck I caught this before it affected anyone. But + please update immediately to 6.53 if you downloaded 6.51 or 6.52. +
+ +Nov. 1, 2006 - Version 6.52 +
    +
  • Added read/write support for trailers in CRW images +
  • Dropped historic support for obsolete -group# option +
+ +Oct. 31, 2006 - Version 6.51 - "Trailer Update" +
    +
  • Improved handling of trailers in JPEG and TIFF-format images: +
      +
    • Added read/write support for PhotoMechanic and FotoStation trailers +
    • Recognize and handle Canon DPP trailers +
    • Added AFCP trailer read/write support for TIFF (previously JPEG only) +
    • Added ability to read/write multiple trailers in the same image +
    • Trailers are now dumped with verbose and htmlDump options +
    • Trailers are now deleted when deleting all tags +
    • Added ability to delete trailers individually by group or altogether + with "-Trailer:all=" +
    +
  • Changed reading/writing XMP in PNG images to conform with XMP specification + (but continue to support the XMP profile format used previously) +
  • Avoid writing duplicate XMP tags in less common namespaces +
  • More consistent handling of unknown IPTC tags +
  • Added -listd option to list deletable groups +
  • IPTC time-only tags may now be set from date/time values (this already + worked for date-only tags) +
  • Fixed problem rewriting international text (iTXt) chunks in PNG images +
  • API Changes: +
      +
    • Added GetDeleteGroups() routine +
    +
+ +Oct. 26, 2006 - Version 6.50 +
    +
  • Changed name of new "-eval" option to "-if" +
  • Added read support for PhotoStudio Unicode comment (thanks Dec Anisimov) +
  • Recognize the "PHUT" Photoshop IRB resource type (thanks Dec Anisimov) +
  • Extract PhotoshopBGRThumbnail image from Photoshop information +
  • Write PNG compressed text for new tags when -z option is used +
  • Added ability to write PNG:ModifyDate +
  • Don't print Olympus LensType "release" if used to differentiate lenses +
  • Changed TagName documentation to show actual format written instead of + format used to interpret the data (which differs only for a few odd tags) +
  • Fixed bug in PNG writer which could cause duplicate tags to be written +
  • Fixed minor problem in HtmlDump output +
  • Fixed logic bug when writing XMP using += or -= +
+ +Oct. 21, 2006 - Version 6.49 +
    +
  • Added -eval option for conditional batch processing [changed to -if in 6.50] +
  • Allow .ExifTool_config file to be placed in application directory +
  • Decode copyright information from JPEG APP12 "Ducky" segment +
  • Decode Casio FirmwareDate +
  • Added IFD0 ProcessingSoftware tag (0x000b, written by ACD Systems) +
  • Added print conversion for InteropIndex +
  • Write InteropVersion automatically when creating a new InteropIFD +
  • Made RelatedImageFileFormat writable +
  • Protect all InteropIFD tags from being copied by default with -TagsFromFile +
  • Renamed XMP ExifImageHeight to ExifImageLength (to correspond with EXIF tag) +
+ +Oct. 19, 2006 - Version 6.48 +
    +
  • Decode Minolta 7D FlashExposureComp (thanks Jeffery Small) +
  • Decode InternalSerialNumber from newer FujiFilm models +
  • Improved decoding of new Pentax PictureMode tag (thanks Doug O'Brien) +
  • Updated CustomFunctions in Canon CRW images and recognize CIFF extension +
  • Added a couple new Pentax LensType's (thanks Barney Garrett) +
  • Changed "AdobeRGB" to "Adobe RGB" in all ColorSpace values for consistency +
  • Fixed bug in recent update to extract large preview from Epson JPEG images +
  • Fixed problem in -htmldump output introduced in 6.46 +
  • Various documentation improvements and updates +
+ +Oct. 15, 2006 - Version 6.47 +
    +
  • Decode JPEG APP6 "EPPIM" segment used in Toshiba images +
  • Process PICT images to extract JPEG preview when -u option is used +
  • Added OtherImage composite tag +
  • Added PentaxModelID for K110D and a new K110D PictureMode tag +
  • Fixed problem extracting CoverArt from some MP4 audio files +
  • Fixed problem decoding Canon BulbDuration (affects Composite ShutterSpeed) +
  • Fixed problem reading/writing large Epson preview image in R-D1 JPEG images + and allow large (>64kB) preview images for all make/models +
+ +Oct. 11, 2006 - Version 6.46 +
    +
  • The "-ext" option now overrides internal file selection rules +
  • Expand filename wildcards on Windows command line (thanks Marjolein Katsma) +
  • Enhanced warnings when copying information to a specific tag +
  • Changed family 0 group name: GPS->EXIF +
  • Changed family 1 group names: APP12->PictureInfo,GraphicConverter->GraphConv +
  • Added a couple of new Pentax LensType's +
  • Added JPEG.pm module (mainly for documentation purposes) +
  • Fixed bug when re-writing NEF files which caused new preview image written + by Nikon Capture 4.4.0 to be lost +
  • Fixed bug which could cause problems if a user-defined composite tag is + created with the same name as an existing tag +
+ +Oct. 6, 2006 - Version 6.45 +
    +
  • Added ability to create JFIF segment +
  • Decode information in JPEG APP8 "SPIFF", APP12 "Ducky", and APP15 + GraphicConverter segments +
  • Improved html dump feature to dump all JPEG APP segments +
  • Decode maker notes in FujiFilm AVI videos +
  • Renamed Nikon AFMode tag to AFAreaMode (thanks Tobias Briseno) +
  • Changed "Image Quality" description to "Quality" +
  • Added option to allow the htmlDump base offset to be specified +
  • Changed EV tag name to LV since this is technically more correct +
  • Print warnings if syntax problems are found in .ExifTool_config file +
  • Use HOMEDRIVE and HOMEPATH (Windows cmd shell environment variables) for + .ExifTool_config path if neither EXIFTOOL_HOME nor HOME are available +
  • Fixed some problems which were causing failed tests when using ActivePerl +
  • User-defined Composite tags now override composite tags of the same name +
  • Added a few more PentaxModelID's (K10D, A20, M20, W20) +
+ +Oct. 2, 2006 - Version 6.44 +
    +
  • Now deletes all JPEG APP segments when deleting all information +
  • Decode Ricoh APP5 RMETA information (custom fields in Caplio Pro G3 images) +
  • Decode AVI Audio/Video stream headers +
  • Recognize and preserve PhotoMechanic trailer when editing TIFF-based images +
  • Added ability to delete JFIF, CIFF, Meta and FlashPix groups +
  • Added ability to exclude groups when deleting all information +
  • Added a number of new Canon, Nikon, Pentax, Sony and Minolta tags +
  • Added description for GPSDOP tag (GPS Degree Of Precision) +
+ +Sept. 26, 2006 - Version 6.43 +
    +
  • Added read support for M4A audio files +
  • Simplified and documented technique for adding user-defined Composite tags +
  • Issue minor warning when a tag used in an expression doesn't exist, instead + of silently inserting a '-' (use -m option for previous behaviour) +
+ +Sept. 21, 2006 - Version 6.42 (production release) +
    +
  • Re-worked Sony and Minolta LensID lists and added a number of new lenses +
  • Extract maker note information from Sanyo MOV and MP4 videos +
  • Recognize ARW extension of Sony Alpha-100 RAW images +
  • Improved extraction of PreviewImage from damaged Minolta images +
+ +Sept. 18, 2006 - Version 6.41 +
    +
  • Fixed calculation of Canon ISO in some images and renamed ShotISO to BaseISO +
  • Minor improvement to order of operations when deleting multiple groups and + adding back information in batch mode +
+ +Sept. 14, 2006 - Version 6.40 +
    +
  • Added ability to delete a group and write back information in one step +
      +
    • Compatibility Warning: This changes previous behaviour when adding and + deleting information in the same operation if new tag values are set + after a group has been flagged for deletion +
    +
  • Fixed problem writing to specific MIE groups +
  • Minor improvements to verbose output while writing +
  • Added a few new CanonModelID's (PowerShot G7, SD900, SD800IS, SD40) +
+ +Sept. 12, 2006 - Version 6.37 +
    +
  • Decode Sony LensID's (thanks Thomas Bodenmann) +
  • Added another Canon LensType +
  • Added shortcut MakerNotes tag to represent the maker notes tags from all + manufacturers (useful when copying tags between files) +
  • Improved MPEG decoding and calculate approx. Duration based on avg. bitrate +
  • Issue a minor error when rewriting an empty IFD (previously this was fatal) +
  • Print 2 decimal points of MeasuredEV (avoids round-off errors resulting in + failed tests on some systems) +
+ +Sept. 6, 2006 - Version 6.36 (production release) +
    +
  • Added a few more Canon LensType's +
  • Improved decoding of Canon 400D ExposureTime and FileNumber +
  • Decode AFPointsUsed for PowerShot models with 9 AF points +
  • Fixed decoding of Canon 5D PictureStyle +
+ +Sept. 5, 2006 - Version 6.35 +
    +
  • Added Canon NumAFPoints tag +
  • Added support for Canon 400D custom functions +
  • Renamed Canon AFPointsUsed20D to AFPointsUsed and decode for 30D and 400D +
  • Changed phrasing in a text string to bypass bug in rpm build causing it to + obtain incorrect dependencies +
+ +Sept. 3, 2006 - Version 6.34 +
    +
  • Removed empirical offset from Canon:MeasuredEV +
+ +Sept. 1, 2006 - Version 6.33 +
    +
  • Added Composite:EV and Canon:MeasuredEV tags [comments welcome] +
+ +Sept. 1, 2006 - Version 6.32 +
    +
  • Decode a new value of "Auto High" for Canon CameraISO +
  • Added new Canon AutoISO tag, renamed Canon:ISO tag to ShotISO, and added a + new composite ISO tag to give the ISO that was actually used +
  • Decode CanonModelID's for recently announced Canon cameras (400D, etc) +
  • Decode PentaxModelID for Optio S7 +
  • XMP Changes: +
      +
    • Added support for rdf:nodeID attribute in XMP information +
    • Changed XMP file MIME type from application/xmp to application/rdf+xml + to correspond with XMP specification +
    • Write 'rdf:about' instead of 'about' (unqualified use now deprecated) +
    • Don't write blank-line padding (as per XMP spec) for .XMP files +
    • Fixed problem extracting XMP information from some EPS files +
    • Fixed typos in some (not commonly used) XMP namespace URI's +
    +
  • Fixed FocalLength conversion for some Pentax-built BenQ and Samsung models +
+ +Aug. 23, 2006 - Version 6.31 +
    +
  • Decode a number of new values for FujiFilm PictureMode (thanks Michael + Meissner) +
  • Properly parse AVI DateTimeOriginal tag when month name is all capitals +
  • Improved compatibility when running "exiftool" with no arguments (thanks + Jesse Zhang) +
  • Added support for Nikon D80 lens information and recognize a new lens + (thanks Robert Rottmerhusen) +
  • Improvements to Pentax maker note decoding (thanks Ger Vermeulen) +
  • Fixed problem when extracting information from image in memory when the + UTF-8 flag is set for the image data (fixes install on RHEL 3) +
+ +July 28, 2006 - Version 6.30 +
    +
  • Added ability to read/write APP0 CIFF segment (found in Canon PowerShot A5 + and PowerShot Pro 70 images) +
  • Improved decoding of Canon 30D FileNumber (was ShutterCount) +
  • Made EXIF tags ImageNumber and ImageHistory writable +
  • Fixed decoding of TargetExposureTime for Canon 20D/250D and ExposureTime + for Kiss Digital N +
  • Fixed problem processing GIF images which don't contain a color table +
  • Fixed bug in EXIF tag name documentation introduced in 6.12 where ExifIFD + group was not properly shown +
  • Fixed typo in exiftool pod documentation ("GROUP:TAG" was reversed) +
+ +July 24, 2006 - Version 6.29 (production release) +
    +
  • Added XMP-xmpMM:PreservedFileName tag (used by Photoshop CS) +
  • Fixed problem reading TIFF images which don't start at the beginning of the + file +
+ +July 12, 2006 - Version 6.28 +
    +
  • Fixed bug introduced in 6.04 which prevented PNG tags from being deleted +
  • Improved decoding of Canon PictureStyle information +
+ +July 7, 2006 - Version 6.27 +
    +
  • Decode a number of new tags in Canon, Casio, FujiFilm, Minolta, Nikon, + Panasonic, Pentax, Ricoh and Sony and maker notes +
  • Improved recognition of various Minolta maker note formats +
  • Added a number of new Nikon Capture tags +
  • Added support for XML-formatted XMP files +
  • Properly handle mixed linefeed characters in PostScript images +
  • Improved formatting of DICOM date/time values +
  • Added "Actual Offset" entry to HtmlDump tooltip information +
+ +June 27, 2006 - Version 6.26 +
    +
  • Avoid creating new SubIFD when copying all tags with "-all:all" from a RAW + or TIFF image (this gave problems if image was subsequently edited by PSCS2) +
  • Fixed decoding of a few Nikon LensID strings +
  • Minor fixes and changes to htmlDump and verbose output +
  • Added a new Pentax LensType (thanks Kazumichi Kawabata) +
+ +June 19, 2006 - Version 6.25 +
    +
  • Added read/write support for WDP (Windows Media Photo) images +
  • Improved algorithm to recognize maker notes offsets which need fixing +
  • Properly handle maker notes which have value offsets relative to the + individual IFD entries (Kyocera, Rollei and some Konica and Toshiba models) +
  • Decode a couple of new Sigma lens values in Canon LensType +
  • Decreased block size for buffered files to improve performance over slow + pipes +
+ +June 9, 2006 - Version 6.24 +
    +
  • Added -fast option to avoid scanning to the end of JPEG images to check for + an AFCP or PreviewImage trailer +
  • Recognize PS files which start with %!Adobe-PS instead of %!PS +
  • Improved FlashPix verbose output +
  • API Changes: + +
+ +June 7, 2006 - Version 6.23 +
    +
  • Added new feature allowing tag-name expressions to be used with the + -TagsFromFile option +
+ +June 5, 2006 - Version 6.22 +
    +
  • Added read support for FPX (FlashPix) images and FPXR (FlashPix Ready) + JPEG APP2 meta information +
  • Added AllDates shortcut tag to allow DateTimeOriginal, CreateDate and + ModifyDate to all be written via a single tag +
  • Added shortcuts to tag name documentation +
  • Return "0000:00:00 00:00:00" instead of "1970:01:01 00:00:00" as the string + representation of numerical times with a value of zero +
+ +May 26, 2006 - Version 6.21 +
    +
  • Changed CR2 identification logic to properly identify CR2 images which have + been edited by PhotoMechanic +
+ +May 24, 2006 - Version 6.20 +
    +
  • Added read support for Real audio/video (RA, RM, RV, RMVB, RAM, RPM) files +
  • Downgraded "Error reading value..." message from an error to a warning +
  • Fixed bug where IgnoreMinorErrors option could get set when writing images + with NikonCapture information +
  • Fixed two ID3 tag names which contained spaces +
  • Fixed problem parsing DateTimeOriginal in Casio EX-Z30 AVI files (thanks + Joachim Loehr) +
  • Fixed problem with apostrophes in HTML documentation for some browsers +
  • API Changes: +
      +
    • Can now call Options() with undefined value to set option value to undef +
    +
+ +May 16, 2006 - Version 6.19 +
    +
  • Added read support for SWF (Shockwave Flash) files +
+ +May 15, 2006 - Version 6.18 +
    +
  • Added read support for MPEG audio/video files +
  • Decode audio information in MP3 files +
  • Print Nikon:LensPosition in hex +
+ +May 12, 2006 - Version 6.17 (production release) +
    +
  • Fixed problem with rpmbuild on Mandriva 2006.0 (thanks Niels Kristian) +
  • Fixed typo in iptc2xmp.args and xmp2iptc.args which prevented the XMP + Instructions from being copied properly (thanks Mark Tate) +
  • Handle byte order mark in unicode EXIF strings +
+ +May 8, 2006 - Version 6.16 +
    +
  • Write %ADO_ContainsXMP comment when adding XMP to EPS images +
  • Don't issue DSC warning when writing Adobe version 3.1 EPS images +
  • Added separate table for decoding tags in IFD0 of Panasonic RAW images +
  • Improvements to Nikon AF point decoding (thanks Roger Larsson) +
  • Allow .ExifTool_config directory to be specified by setting the + EXIFTOOL_HOME environment variable +
  • Made all maker note write errors minor so they can be ignored if necessary, + allowing information to be written to images with corrupted maker notes +
  • Minor change to perl-Image-ExifTool.spec to fix problem with rpmbuild + (thanks Volker Kuhlmann) +
  • Fixed bug which could cause incorrect date to be calculated when shifting + date/time values +
+ +Apr. 20, 2006 - Version 6.15 +
    +
  • Changes to MIE specification involving string lists and alternate languages +
+ +Apr. 18, 2006 - Version 6.14 +
    +
  • Fixed some problems with EPS writer and removed beta testing status (thanks + to Tim Kordick for help with testing) +
  • Created new MIE meta information format [Note: The MIE module is fully + functional but the MIE format specification is still in development] +
  • Added print conversion for SpatialFrequencyResponse +
  • Extended meaning of -z option when writing to allow compressed information + to be written to MIE files +
  • Added Minolta FlashMetering tag +
  • API Changes: +
      +
    • Added 'Compress' option +
    +
+ +Apr. 9, 2006 - Version 6.13 +
    +
  • Fixed problem with writing FileName that caused format codes not to be + properly expanded if the specified filename already existed +
  • Standardized reported FileType for ACR, AIFC, CRW, JP2, PS and PSD files +
  • Allow 2 values to be written for EXIF TimeZoneOffset and make EXIF + SecurityClassification writable +
+ +Apr. 5, 2006 - Version 6.12 +
    +
  • Avoid printing garbage for DNG maker note information that was not copied + properly by the Adobe DNG converter (affects converted ORF images) +
  • Disabled "Possibly incorrect maker notes offsets" warning for a number of + Olympus models +
  • Fixed bug introduced in 6.04 which could cause endless loop (eeek!) when + writing tags with PostScript equivalents +
  • Fixed error reading some DICOM images +
+ +Apr. 3, 2006 - Version 6.11 +
    +
  • Added a few new Pentax LensType's +
  • Fixed bug rewriting MOS images (this bug introduced in version 5.95 caused + an error message and prevented the file from being rewritten) +
+ +Mar. 31, 2006 - Version 6.10 +
    +
  • Added ability to use filename format codes %d, %f and %e in values written + to FileName and Directory tags +
  • Fixed problem of odd filenames being generated when setting FileName from an + invalid date/time tag +
  • Removed debugging print statement forgotten in Olympus code of 6.07 (oops) +
  • API Changes: + +
+ +Mar. 30, 2006 - Version 6.09 +
    +
  • Made FileName and Directory writable (enabling a whole new functionality!) +
  • Added ability to write DOS-style EPS images [Note: still in beta testing] +
  • Increased precision of Composite Red/BlueBalance print conversion +
  • When combining the -o and -overwrite_original options, the original file is + now erased if the new file is written successfully +
  • Added a new Nikon lens (thanks Werner Kober) +
  • API Changes: +
      +
    • Added SetFileName() routine +
    • In list context, CountNewValues() now also returns a "pseudo" tag count +
    +
+ +Mar. 25, 2006 - Version 6.08 +
    +
  • Made YCbCrCoefficients and YCbCrPositioning protected when writing +
  • Decode some new Nikon-specific tags in QuickTime videos from Nikon cameras +
  • Calculate Red/BlueBalance for Olympus images +
  • Fixed "APP1 segment too large" problem when copying all tags from Canon + EOS-5D or EOS-30D CR2 image to JPEG image +
  • Fixed problem running "exiftool" with no arguments in Windows cmd shell +
+ +Mar. 22, 2006 - Version 6.07 +
    +
  • Added a number of new Olympus tags (thanks Frank Ledwon) +
  • Decode Adobe JPEG APP14 segment (thanks Didier Giet) +
  • Made Rotation writable in CRW images +
  • Changed some FujiFilm WhiteBalance strings +
  • No longer return multiple tags when a group is specified unless the + duplicates option is enabled or the group name is 'all' or '*' +
+ +Mar. 20, 2006 - Version 6.06 +
    +
  • Added validity check for Canon FocalPlaneX/YSize which resulted in incorrect + values of FocalLength35efl being calculated for some PowerShot models +
  • Made Opto-ElectricConvFactor value binary +
+ +Mar. 18, 2006 - Version 6.05 +
    +
  • Improved JPEG writer to tolerate any segment ordering +
  • Fixed Olympus ExtenderStatus to work with E-330 (thanks Mark Dapoz) +
+ +Mar. 15, 2006 - Version 6.04 +
    +
  • Added write support for EPS and PS images [Note: still in beta testing -- + must currently use the -m option to enable writing to EPS images] +
  • Added ability to write ICC_Profile data as a block +
  • Added read/write support for ICC and ICM color profile files +
  • Added read/write support for ERF (Epson Raw Format) images +
  • Added a couple of new Olympus tags and LensType's (thanks Mark Dapoz) +
  • Added ability to scan past unknown header to find JPEG or TIFF image +
  • Added Canon EOS 30D custom functions +
  • Renamed Panasonic SerialNumber tag to InternalSerialNumber +
  • Renamed Canon 5D PictureNumber tag to ImageNumber +
  • Improved MRW reading and writing +
  • Decode a number of new Minolta tags and changed names of some existing tags +
  • Decode some type-specific data in ASF StreamProperties, including video + ImageWidth and ImageHeight +
  • Extract a few more PostScript tags and derive ImageWidth and ImageHeight for + PostScript documents +
  • Some improvements to Panasonic decoding (thanks Tels) +
  • API Changes: +
      +
    • 'Unsafe' tags are now copied by SetNewValuesFromFile() if specified + explicitly +
    +
  • Internal Changes: +
      +
    • SubDirectory tags are no longer Writable by default in WRITABLE tables +
    +
+ +Mar. 2, 2006 - Version 6.03 +
    +
  • Added print conversion for CFAPlaneColor +
  • Decode CFAPattern as written incorrectly in ASCII by some Panasonic cameras +
  • Added recently announced Canon cameras to CanonModelID list +
  • API Changes: +
      +
    • Added ability to prefix tag name with group in arguments to ImageInfo() + (read/write symmetry is now improved since this feature already existed + in the write routines, and now group names can be used in shortcuts) +
    • Changed order of filtering for Group# option and tag exclusions to be + applied after extracting tags specified in calls to ImageInfo() +
    +
+ +Feb. 26, 2006 - Version 6.02 +
    +
  • Fixed problem rewriting Photoshop IRB resources as written by some + applications (eg. PixVue) +
  • Improved decoding of AVI files to increase speed and extract more tags +
  • Added -overwrite_original_in_place option +
  • Added a number of new XMP tags and bring XMP support up to new + specification, plus a few undocumented XMP-aux tags (thanks Lou Salkind) +
  • Added support for large DNG preview image (with JpgFromRaw tag) +
  • Added ability to decode DNG Adobe MakerNotes +
  • Added SEMInfo tag (thanks Robert Mucke) +
  • Decode (but don't rewrite) old PS APP13 "Adobe_Photoshop2.5:" segment +
+ +Feb. 20, 2006 - Version 6.01 +
    +
  • Added back RedBalance and BlueBalance as composite tags +
  • Fixed potential problem in File::RandomAccess which could cause a "substr + outside of string" warning +
+ +Feb. 19, 2006 - Version 6.00 (production release) +
    +
  • Added read support for Sony SR2 raw images (but most tags still unknown) +
  • Added read support for Kyocera Contax N Digital RAW images +
  • Added ability to write or delete shortcuts which reference multiple tags + (previously only shortcuts referencing a single tag were writable) +
  • Changed descriptions of FNumber, ExposureTime, ISO, DateTimeOriginal, + CreateDate and ModifyDate to more closely match their tag names +
  • Separated Canon and Nikon Red/BlueBalance information into individual + components with tag names like WB_RGGBLevels +
  • Decoded a number of new Canon tags for EOS models, including ColorBalance + tables, 20D AF points and SensorInfo (thanks Rainer Honle) +
  • Fixed incorrect decoding of EOS 10D/300D color balance modes +
  • More additions and minor fixes to Canon decoding +
  • Made EOS-1D personal functions writable +
  • Added ability to write bitmasks at the PrintConv level +
  • Set MIME type for all RAW image formats to "image/x-raw" +
  • The -f option is no longer implied when -S and -s are combined +
  • Fixed bug introduced in 5.99 which broke the "-tagsFromFile @" feature +
  • Fixed problem with offsets in verbose dump of CRW images +
  • Fixed problem with some tags in Canon images not printing without -a option +
  • Fixed problem with validation of Canon PictureInfo for images rotated by + Canon ZoomBrowser EX (thanks Joshua Bixby) +
+ +Feb. 1, 2006 - Version 5.99 +
    +
  • Major additions to Canon maker note decoding, including EOS-1D personal + functions (thanks Rainer Honle for decoding many 5D tags) +
  • Added Canon maker note footer when rewriting Canon maker notes +
  • Attempt to fix problem where ScaleFactor35efl was calculated incorrectly for + some Canon images +
  • Reduce memory usage and speed up writing of large TIFF images +
  • Fixed problem with binary data offsets in verbose dump +
  • Fixed problem writing Comment if 'File' group specified +
  • Fixed bug which could cause formatting error in htmlDump output +
+ +Jan. 22, 2006 - Version 5.98 +
    +
  • Enhanced FMT syntax for -o, -w and -tagsFromFile options +
  • Decode maker notes of Samsung DX-1S +
  • Added ability to list tags in a specific group +
  • Recognize maker notes of a few more Kodak models +
  • Added a few more Canon LensType's +
  • Added missing semicolons in HtmlDump JavaScript output +
+ +Jan. 16, 2006 - Version 5.97 +
    +
  • Added support for Canon 5D custom functions (thanks Rainer Honle) +
  • Added support for Canon 1DmkII and 350D custom functions +
  • General fixes and improvements to Canon custom functions +
  • Renamed ICC_Profile Copyright to ProfileCopyright +
  • Report all extraction errors when copying only specified tags from file +
  • Avoid issuing "Error rebuilding maker notes" warning when copying maker + notes that don't require rebuilding +
+ +Jan. 14, 2006 - Version 5.96 +
    +
  • Fixed problem where XMP information could be lost when writing PSD images +
+ +Jan. 12, 2006 - Version 5.95 +
    +
  • Decode AIFF SampleRate +
  • Fixed problem where FileType was being set twice for AIFF files +
  • Patched problem reading some file types through Windows cmd shell pipeline +
  • Properly identify CR2 images read via pipes (previously identified as TIFF) +
  • Improved formatting of printed values for some DNG tags +
  • Fixed problem with EXIF format of some tags when writing +
  • Changed 'rational' format names to match full bit size of value +
+ +Jan. 10, 2006 - Version 5.94 +
    +
  • Fixed problem extracting OriginalRawImage from little-endian DNG images +
  • Fixed problem where "unreferenced bytes" error could be incorrectly issued + when deleting all EXIF from a TIFF image +
+ +Jan. 9, 2006 - Version 5.93 +
    +
  • Added ability to write JFIF information +
+ +Jan. 9, 2006 - Version 5.92 +
    +
  • Added ability to extract and decompress original raw image from DNG +
  • Fixed problem extracting information from some image types in pipelines +
  • Decode more information in PSD images +
+ +Jan. 7, 2006 - Version 5.91 +
    +
  • Added write support for PSD images +
  • Made a couple more Photoshop tags writable +
+ +Jan. 6, 2006 - Version 5.90 +
    +
  • Added read support for AIFF audio files +
  • Made Photoshop:XResolution and Photoshop:YResolution writable +
  • Fixed problem with processing some RIFF files +
  • Added a new Canon LensType +
  • API Changes: +
      +
    • SetNewValue() now accepts an ARRAY reference for setting list-type tags + such as Keywords, or a SCALAR reference for binary data, so it may now + be called directly with any value returned by GetValue(). +
    +
+ +Jan. 3, 2006 - Version 5.89 +
    +
  • Recognize Panasonic Type 2 maker notes +
  • Changed Nikon LensID to a composite tag to allow better decoding of + non-Nikon lenses, and added a bunch of new lenses to the list +
+ +Jan. 1, 2006 - Version 5.88 +
    +
  • Added ability to read and write AFCP information in JPEG images +
  • Added read support for WMV video and WMA audio files (ASF format files) +
  • Added EXIF tags 0x82a5-0x82ac +
  • Fixed TagID of IntergraphPacketData tag +
  • Fixed problem in rewriting some types of JVC maker notes +
  • Renamed WAV module to RIFF +
+ +Dec. 22, 2005 - Version 5.87 (production release) +
    +
  • Added support for JVC maker notes +
  • Extract a number of new DNG tags plus DNG JPEG preview image +
  • Renamed DNGCameraSerialNumber tag to CameraSerialNumber +
+ +Dec. 20, 2005 - Version 5.86 +
    +
  • Added support for AVI and MP4 videos +
  • Improved decoding of Olympus maker notes +
  • Improved APP12 decoding +
  • Improved CanonPictureInfo validation to work with more PowerShot models +
  • Display Canon 1D serial numbers with 6 digits +
  • Decode maker notes of Nikon D1 +
  • Combining -t with -S now gives a single-line tab-delimited list of values +
  • Extract preview image for Samsung Digimax i5 +
+ +Dec. 13, 2005 - Version 5.85 +
    +
  • Added ability to read and write XMP files which don't have an xpacket header +
  • Fixed problem deleting entire XMP data block using '-xmp=' syntax +
  • More minor HtmlDump improvements +
+ +Dec. 12, 2005 - Version 5.84 +
    +
  • Minor improvements to HtmlDump output +
+ +Dec. 12, 2005 - Version 5.83 +
    +
  • Added -F option to allow maker notes offsets to be fixed +
  • Added -htmlDump option to generate a verbose HTML-based hex dump of EXIF + and/or TIFF information (cool new diagnostic tool) +
  • Attempt to validate maker notes offsets and issue warning if they look wrong +
  • Fixed problem rewriting PreviewImage in some Olympus and Pentax images +
  • Increased speed for extracting large preview images +
  • Improved synthetic maker notes when coping tags from CRW file +
  • Display absolute offsets for EXIF values in very very verbose mode +
  • Verbose option output is now written to file if -w option used +
  • Speed up rewriting of some TIFF images when using ActivePerl 5.8.x for + Windows (image strips are now copied in a single block if they are + contiguous in the file to avoid ActivePerl bug which causes extremely poor + performance when concatenating a large number of memory blocks) +
  • Added a couple of new Nikon and Pentax lens ID's (thanks Robert Rottmerhusen + and David Buret) +
  • Decode PrintIM information in Casio QV-4000 +
  • Fixed Decoding of Canon EOS D60 serial numbers to agree with Canon utilities +
  • API Changes: +
      +
    • Added HtmlDump and TextOut options +
    +
+ +Nov. 26, 2005 - Version 5.82 +
    +
  • Fixed bug which caused error rewriting Minolta MRW images +
  • Added MRW write test +
  • Improved MRW verbose output +
+ +Nov. 24, 2005 - Version 5.81 +
    +
  • Changed writing of TIFF so that existing IPTC will be rewritten as int32u + whenever IPTC is edited, regardless of original format type. This allows + files to be 'fixed' even if IPTC was previously another format (now we get + to see if there is any software out there that barfs on int32u's...) +
  • Changed the -s option so tag names are displayed instead of descriptions + (now similar to the -S option, but values are aligned in a column) +
  • Remove padding at the end of IPTC record when writing +
  • Fixed problem which was generating a warning with ActivePerl 5.6.1 +
+ +Nov. 22, 2005 - Version 5.80 +
    +
  • Changed writing of new TIFF IPTC information to make it visible in Nikon + Capture (for some reason requires int32u format) +
  • Installed patch for building of ExifTool RPMS on Mandriva Linux (thanks + Niels Kristian) +
+ +Nov. 22, 2005 - Version 5.79 +
    +
  • Fixed problem which could render XMP information unreadable by Photoshop + when editing some XMP written by Photoshop CS2 +
+ +Nov. 21, 2005 - Version 5.78 +
    +
  • Fixed problem which could generate an error when adding IFD1 to an image +
+ +Nov. 18, 2005 - Version 5.77 (production release) +
    +
  • Allow integer tag values to be specified in hex (with leading '0x') +
  • Fixed problem which generated warnings about symbol "@indent" in Nikon.pm + when using older versions of Perl (observed with 5.6.1) +
+ +Nov. 16, 2005 - Version 5.76 +
    +
  • Tolerate extra null padding at end of TIFF images (as written by Photoshop + CS) when rewriting TIFF images +
  • Minor improvements to DICOM image processing +
  • Updated FAQ +
+ +Nov. 14, 2005 - Version 5.75 +
    +
  • Fixed problem decompressing deflated DICOM images +
+ +Nov. 14, 2005 - Version 5.74 +
    +
  • Added read support for DICOM (DCM, DC3, DIC, DICM) and ACR-NEMA (ACR) + medical image files +
  • Decode a lot more Nikon Capture information and add write ability +
  • Updated Nikon makernote decoding for D200 and new AF-S Nikkor 18-200 lens + (thanks Werner Kober) +
  • Added a number of new Canon LensType's (thanks Volker Gering) +
  • Recognize file types even if they have the wrong extension +
+ +Nov. 7, 2005 - Version 5.73 +
    +
  • Added ability to shift date/time tag values +
  • Extract Red/BlueBalance tags for Nikon D2Hs, D50 and D2X +
  • Decode Nikon Capture Data to extract IPTC information and Rotation +
  • Added a new Olympus LensType (thanks Michael Meissner) +
+ +Oct. 28, 2005 - Version 5.72 +
    +
  • Added ability to create XMP data files. This is more significant than it + sounds: The -o option may now be used to generate XMP files from + information in any other format, or even to create an XMP file from nothing + more than tags defined on the command line. +
  • Added printout of number of directories created with -w and -o options +
  • Improved error handling +
  • Effectively set preferred group to 'XMP' when writing XMP data files +
  • Fixed problem rewriting maker notes of some Pentax cameras +
  • API Changes: +
      +
    • Added CanWrite() and CanCreate() functions +
    • Allow WriteInfo() source file to be undefined to create new file +
    • Allow WriteInfo() output file to be undefined to edit file in place +
    • Added extra argument to WriteInfo() to specify output file type +
    +
+ +Oct. 24, 2005 - Version 5.71 +
    +
  • Added ability to read/write .XMP data files +
  • Added -listf option to print list of recognized file types +
  • Changed "-group#" option to "-listg#" (but still support old -group#) +
  • Moved Kodak APP3 "Meta" tags from EXIF to a new Kodak "Meta" group +
+ +Oct. 23, 2005 - Version 5.70 +
    +
  • Significant internal changes to improve speed and reduce memory usage +
  • Fixed a bug introduced in version 5.63 which caused incorrect XMP GPS + coordinates to be returned +
  • Changed handling of Kodak date records +
  • API Changes: +
      +
    • Added ability to access original 'Raw' values via GetValue() +
    • GetValue() now returns empty array in list context if value is undefined +
    • Values are now converted as they are requested, so the PrintConv option + now applies to GetInfo() and GetValue() instead of ExtractInfo() +
    +
+ +Oct. 19, 2005 - Version 5.69 +
    +
  • Changed UTF-8 bug fix introduced in 5.67 to improve portability and allow it + to work with Perl versions back to 5.6 +
  • Changed some offsets in verbose output from relative to absolute addressing +
  • Improved APP12 decoding +
  • Changed technique for rounding off extracted rational values +
  • API Changes: +
      +
    • Changed handling of floating point numbers to tolerate locales where a + comma is used instead of a decimal point +
    +
+ +Oct. 17, 2005 - Version 5.68 +
    +
  • Added support for reading Sigma RAW (X3F) images +
+ +Oct. 13, 2005 - Version 5.67 (production release) +
    +
  • Added support for reading PICT images +
  • Fixed a problem when writing information via the ExifTool API if using Perl + 5.8 or later and passing a UTF-8 encoded string to SetNewValue(). The + problem generated an error which prevented the file from being written +
  • Fixed timezone problem in timestamps of QuickTime images which was causing + a failed test +
+ +Oct. 10, 2005 - Version 5.66 +
    +
  • Enhanced -tagsFromFile option to allow %d, %f and %e in filenames +
  • Extract a few more tags from Canon EOS 5D images +
  • Allow multiple ICC_Profiles to be extracted from same image and add a number + to the group1 name for subsequent profiles to make the tag locations unique +
  • Changed Photoshop PixelsPerInchX/Y and QuickTime DotsPerInchX/Y tag names to + X/YResolution. Neither has a corresponding ResolutionUnit tag, so inches + should be assumed if no resolution unit is present +
  • Added tests of Nikon, Sony and PDF decryption algorithms +
+ +Oct. 7, 2005 - Version 5.65 +
    +
  • Added read support for QuickTime MOV videos (and QTIF images if anyone + cares) +
  • Extract maker note information from Sony SRF raw images +
  • Improved Jpeg2000 decoding +
  • Decode a few more Photoshop tags +
  • Issue an error if there is extra data after the normal end of file when + rewriting TIFF images (avoids possible data loss if attempting to write an + unsupported RAW image with a TIFF-like data structure) +
  • Added ability to replace existing tags with user defined tags +
  • Denote minor errors/warnings by adding '[minor]' to the message (these are + the errors which can be ignored with the -m option) +
  • Fixed problem of missing LeafSubIFD when rewriting MOS images +
  • Removed hack to write Leaf maker note information at start of image +
+ +Sept. 30, 2005 - Version 5.64 +
    +
  • Improved writing of Canon CR2 images to preserve CR2 header and editing + information written by Canon Digital Photo Professional software +
  • Extract information from JPEG APP0 JFIF segments +
  • Added support for extracting Creo Leaf meta information from MOS images +
  • Added ability to define new tags in .ExifTool_config file and added a sample + ExifTool_config file to the distribution +
  • Extended the -w option to allow an expression to be specified +
  • Allow tag aliases to be used when writing +
  • Changed print conversion of FileSize tag +
  • Internal changes to tag lookup to improve speed when writing information +
  • Decode Photoshop resolution information +
+ +Sept. 21, 2005 - Version 5.63 +
    +
  • Added read support for MP3 and WAV audio files (Oops... ExifTool has + expanded beyond its "Image" roots!) +
  • Added write support for PNG and MRW (Minolta RAW) images +
  • Improved decoding of PNG profile information and added a few new PNG tags +
  • Changes to handling of GPS coordinates: +
      +
    • Added -c (CoordFormat) option to format output of GPS coordinates +
    • Added GPSPosition composite tag +
    • GPS coordinates now show as decimal degrees with the -n option +
    • Much more flexible about the input coordinate format when writing +
    • Enforce proper formatting of XMP GPS coordinates +
    +
  • Added XMP-xmp Rating and Label tags, and a few missing XMP-exif GPS tags +
  • Added new XMP-dex group +
  • Added two new lenses to the Minolta LensID list (thanks Pedro Corte-Real) +
  • Added a new lens to the Olympus list (thanks Shingo Noguchi) +
+ +Sept. 7, 2005 - Version 5.62 +
    +
  • Fixed problem reading FujiFilm maker notes from RAF images +
  • Extract comments from PPM/PGM/PBM images and add write support +
  • Extract maker notes from Nikon Coolscan scanner images +
+ +Sept. 3, 2005 - Version 5.61 +
    +
  • Added read support for PBM, PGM and PPM file formats +
  • Added read support for RAF (FujiFilm RAW) file format +
+ +Sept. 2, 2005 - Version 5.60 +
    +
  • Fixed bug where tag was deleted if TAG+=VALUE used for a non-list type tag +
  • Fixed problem where reading some CRW files could generate a "Use of + uninitialized value in concatenation" warning +
  • Restructured XMP to separate tags by namespace +
  • Added XMP-xmpTPg, XMP-cc, XMP-xmpPLUS and XMP-PixelLive groups +
  • Improved logic for editing XMP list-type tags +
  • Removed SubDirectory tags from -list option output +
  • More updates to Pentax LensType list +
  • Changed Nikon FileSystemVersion tag name to FirmwareVersion +
  • Added NikonCaptureData and NikonCaptureVersion tags +
+ +Aug. 24, 2005 - Version 5.55 (production release) +
    +
  • Added patch to fix word ordering when unpacking doubles on ARM systems with + little-endian byte order but big-endian word order (thanks Riku Voipio) +
  • Added another lens to the Pentax LensType list +
+ +Aug. 22, 2005 - Version 5.54 +
    +
  • Fixed problem introduced in version 5.50 which broke ability to delete + groups of information +
  • Added a couple of new Pentax LensType's +
  • Renamed Olympus Lens tag to LensType +
+ +July 29, 2005 - Version 5.53 +
    +
  • Added -ext option to allow files to be processed or excluded from processing + based on their extension +
  • Added MimeType tag +
  • Convert PDF UTF-16 character strings to UTF-8 (or Windows Latin1 if '-L' + option used) +
+ +July 28, 2005 - Version 5.52 +
    +
  • Removed warning message when writing CR2 files that was intended only for + Canon 1D TIFF files +
+ +July 27, 2005 - Version 5.51 +
    +
  • Assume '-TagsFromFile @' for any redirected tags (eg. '-SRCTAG>DSTTAG' or + '-DSTTAG<SRCTAG') which are specified without a prior '-TagsFromFile' +
+ +July 27, 2005 - Version 5.50 +
    +
  • Don't rewrite entire file if only FileModifyDate is being changed +
  • API Changes: +
      +
    • Added CountNewValues() and SetFileModifyDate() +
    +
+ +July 26, 2005 - Version 5.49 +
    +
  • Decode encrypted PDF documents +
  • Extract metadata from individual PDF pages +
  • Speed up parsing of PDF files which use cross-reference streams +
  • Improvements to verbose PDF output +
  • Updated Nikon LensID's (thanks Robert Rottmerhusen) +
  • Minor changes to Canon LensType strings (thanks Michael Tiemann) +
+ +July 21, 2005 - Version 5.48 +
    +
  • Fixed parsing of XMP-pdf CreationDate and ModDate tags +
+ +July 21, 2005 - Version 5.47 +
    +
  • Fixed problem where existing item in list was getting overwritten when + adding to XMP lists with '-TAG+=VALUE' syntax +
  • Improved verbose output for PDF files and recurse into all Kids dictionaries +
  • Don't print warnings when setting the values of non-priority tags unless + verbose +
  • Added support for PDF-like Adobe Illustrator (.AI) files +
+ +July 19, 2005 - Version 5.46 (production release) +
    +
  • Fixed bug which could cause CRW file to be corrupted under some conditions + when writing and rewriting the same file +
  • Added new Canon MaxAperture tag and a few more Canon LensType's (thanks + Michael Tiemann) +
  • Changed PDF decoding to follow 'Next' links at the same level to avoid deep + recursion in long linked lists +
+ +July 19, 2005 - Version 5.45 +
    +
  • Set FileType tags properly for newly added formats +
  • Added Canon TargetAperture and TargetExposureTime and decode Canon 1D Mark + II lens information structure (thanks Michael Tiemann) +
  • Decode more Canon lenses and Canon TargetImageType +
  • Changed Priority of Error and Warning tags so that first message takes + precedence +
  • Fixed problem where Nikon D70 files grew by 20 bytes each time they were + written +
  • Minor changes to BMP tags +
  • Added support for AI (Adobe Illustrator) file format +
  • Added BMP, PDF, Photoshop and PostScript tests +
+ +July 16, 2005 - Version 5.44 +
    +
  • Added read support for BMP (and DIB) images +
+ +July 16, 2005 - Version 5.43 +
    +
  • Allow shortcut tags to be used with -tagsFromFile +
+ +July 15, 2005 - Version 5.42 +
    +
  • Added ability to read PostScript (EPS and PS) and PDF images +
  • Decode PhotoshopSettings in TIFF images +
+ +July 8, 2005 - Version 5.41 +
    +
  • Added ability to read Photoshop PSD images +
+ +July 8, 2005 - Version 5.40 +
    +
  • Improved decoding of Minolta MRW files to support new cameras +
  • Changed Minolta ImageQuality values to conform with Minolta terminology + (thanks to Niels Kristian Bech Jensen) +
  • Write Windows XP tags to IFD0 instead of ExifIFD (they worked fine in the + ExifIFD, but Windows writes them to IFD0 so they really should go there) +
  • Really quiet option (-q -q) still suppresses warnings, but no longer + suppresses errors +
+ +July 6, 2005 - Version 5.39 +
    +
  • Using -b option now disables -h, -H and -g options +
  • Decode Canon Panorama information +
  • Improved maker note decoding for some Minolta camera models +
  • Changed base offset for Casio EX-Z3 to fix problems decoding some maker note + information (it looks like the samples from dpreview.com I had used to code + this originally had been corrupted by 3rd party software because new samples + downloaded from another web site didn't have the same problem) +
  • Improved validation of PreviewImage +
+ +July 4, 2005 - Version 5.38 +
    +
  • Translate older 'xap' XMP namespace prefixes (xap, xapRights, xapMM and + xapBJ) to their newer 'xmp' counterparts (xmp, xmpRights, xmpMM and xmpBJ) + when generating XMP family 1 group names +
  • Added Minolta LensID (thanks to Shingo Noguchi) +
  • Other changes to Minolta tags (and fix incorrect spellings of Konica, thanks + Niels Kristian Bech Jensen) +
  • Updated Nikon LensID's (thanks Robert Rottmerhusen) +
+ +June 29, 2005 - Version 5.37 +
    +
  • Removed unknown status from Photoshop CopyrightFlag and made it writable +
  • Decode a new Canon EasyMode value +
+ +June 28, 2005 - Version 5.36 +
    +
  • Added new composite tags: DOF, CircleOfConfusion and HyperfocalDistance +
  • Minor changes to simplify and improve generated XMP when writing +
  • Convert FocusDistance tag values to meters +
  • Reject ScaleFactor35efl if outside reasonable limits +
  • Added a few more Nikon LensID's (thanks Robert Rottmerhusen) +
  • Ignore white space around '=' sign of arguments in '-@' file +
+ +June 24, 2005 - Version 5.35 +
    +
  • Added support for MNG and JNG images +
  • Added a few new PNG tags +
+ +June 21, 2005 - Version 5.34 +
    +
  • Decode ASCII-based APP12 information (tested with Agfa and Polaroid images) +
  • Decode remaining PNG chunks in original spec except for IDAT (image data) +
  • Only generate FileSize and FileModifyDate tags for plain files +
+ +June 16, 2005 - Version 5.33 +
    +
  • Changed print conversions for Contrast, Saturation and Sharpness throughout + to be more consistent and to better conform with the EXIF specification +
  • Decode Minolta Dimage Z2 MinoltaImageSize +
+ +June 15, 2005 - Version 5.32 (production release) +
    +
  • Changes to a few PNG and MIFF tag names +
  • Improved PNG/MIFF documentation +
+ +June 14, 2005 - Version 5.31 +
    +
  • Decode compressed information in PNG images if Compress::Zlib is available +
  • Decode profile information (including EXIF, XMP, IPTC and ICC_Profile + information) from PNG and MIFF images +
  • Updated Nikon LensID strings and decode D50 lens info (thanks Robert + Rottmerhusen) +
+ +June 10, 2005 - Version 5.30 +
    +
  • Added PNG and MIFF read support +
  • Decode Nikon SensorPixelSize +
+ +June 9, 2005 - Version 5.27 +
    +
  • Added -q option +
+ +June 8, 2005 - Version 5.26 +
    +
  • Automatically fix out-of-sequence entries in IFD when writing to comply with + the TIFF specification (but not in maker notes) +
  • Create new EXIF information using the same byte order as the maker notes + when using -tagsFromFile to copy maker notes to a file which previously + contained no EXIF information +
  • Fixed problem which could copy corrupted maker notes if using multiple + -tagsFromFile options in a single command +
  • Changed Orientation "Rotate 90 CCW" to "Rotate 270 CW", and changed Canon + AutoRotate strings to match +
  • Made StripOffsets and StripByteCounts binary data if output is too long +
  • Allow "-TagsFromFile '-TAG<SRCTAG'" as well as the current '-SRCTAG>TAG' +
  • Recognize some more Nikon lenses +
  • API Changes: +
      +
    • Added ByteOrder option to specify byte ordering when creating new EXIF + segment in a JPEG file +
    +
+ +June 3, 2005 - Version 5.25 (production release) +
    +
  • Fixed problem with writing IPTC Time tags +
  • Changed Composite ShutterSpeed to ignore bulb duration if it is negative +
  • API Changes: +
      +
    • Allow tag name to be prefixed by group in calls to SetNewValue() +
    +
+ +June 1, 2005 - Version 5.24 +
    +
  • Added new "XMP" tag to allow read/write of XMP data as a block +
  • Added numbers to subsequent SubIFD group names to allow tags in various + SubIFD's to be accessed individually +
  • Give priority to tags in full resolution image (whichever TIFF directory + this is in) +
  • Renamed ExifData tag to EXIF (but didn't make it writable as a block like + XMP) +
  • Recognize maker notes from more Konica Minolta cameras +
  • Extract PreviewImage for Samsung Digimax V700, Kenox V10 and Digimax V10 +
  • Changed validation of CanonPictureInfo to work with more PowerShot cameras + (Note: for these cameras, CanonImageHeightAsShot may not be meaningful) +
  • Added a number of new IPTC ApplicationRecord tags +
  • Added Nikon ExposureDifference tag +
  • Removed trailing white space in values printed by exiftool +
+ +May 27, 2005 - Version 5.23 +
    +
  • Changed behaviour of -tagsfromfile slightly so that '-GROUP:TAG>DSTTAG' now + commutes information between different groups unless a destination group is + specified +
  • Improved reliability of calculating offsets in Pentax maker notes +
+ +May 26, 2005 - Version 5.22 +
    +
  • Fixed problem with new '-tagsFromFile @' feature which occurred when + simultaneously copying tags and writing new values to multiple target files + (the new values were only getting written to the first file) +
+ +May 25, 2005 - Version 5.21 +
    +
  • Allow target file to be specified by '@' with -TagsFromFile option +
  • Fixed bug which caused internal error when using -TagsFromFile option to + copy PrintIM information to a file that already contained PrintIM data +
  • Fixed problem which broke the (now deprecated) -allTagsFromFile=FILE syntax +
  • Fixed problem decoding Pentax Date for some Optio cameras +
  • Fixed problem in GeoTiff decoding which could cause some tags to be missed +
  • Decode a number of new Pentax tags (using my new Optio WP!) +
  • Made Photoshop URL writable +
  • Limit length of JPEG segment dump at Verbose=4, and add Verbose=5 level +
  • API Changes: +
      +
    • Added SaveNewValues() and RestoreNewValues() +
    +
+ +May 20, 2005 - Version 5.20 +
    +
  • Give names to many Photoshop tags, but leave them marked as 'Unknown' so + they aren't extracted under normal circumstances (must use the -u option) +
  • Read/write Kyocera maker notes properly (although Kyocera information + remains unknown) +
  • Changed installation tests to tolerate rounding-off errors or format + differences in floating point numbers +
+ +May 17, 2005 - Version 5.19 +
    +
  • Added -overwrite_original option +
+ +May 16, 2005 - Version 5.18 (production release) +
    +
  • Added -@ option and two utility files (iptc2xmp.args and xmp2iptc.args) to + use with this option for translating between IPTC and XMP tag names +
  • Disable normal console output if -v option used and no tags specified +
  • Repair incorrect first byte of MRW preview images when extracting +
  • More tweaking of -TagsFromFile order of operations +
+ +May 14, 2005 - Version 5.17 +
    +
  • Allow 'All' to be used as a group name with '-TagsFromFile' option to + preserve original tag groups (eg. '-all:all') +
  • PrintIM information is now copied with -TagsFromFile +
  • Decode EXIF:Gamma tag +
  • Decode Canon 350D FileNumber +
  • Made a few more tags writable +
  • Don't rewrite TIFF files which could be Canon 1D RAW files since this + format currently isn't supported (can use the -m option to write anyway, + which will remove the RAW image data if this is a 1D file) +
  • Don't add null terminator to UserComment, GPSProcessingMethod or + GPSAreaInformation +
  • Improved logic for handling command line tag names and exclusions, + especially when associated with the -TagsFromFile option +
+ +May 10, 2005 - Version 5.16 +
    +
  • Decode a number of new Nikon lens-related tags (thanks again Robert + Rottmerhusen) +
  • Various other improvements +
+ +May 7, 2005 - Version 5.15 +
    +
  • Added powerful new information redirection feature to -TagsFromFile option +
  • Added writable File:FileModifyDate tag which represents the filesystem + date/time of last modification +
  • Allow '*' to also be used as well as 'all' to represent all tags, although + this feature is not documented for the command-line options because 'all' is + more convenient since '*' must be quoted to prevent shell globbing +
+ +May 5, 2005 - Version 5.11 +
    +
  • Fixed problem where the proper tags weren't excluded from being extracted if + -GROUP:All and --TAG options are used together on the command line +
+ +May 5, 2005 - Version 5.10 +
    +
  • Changed -AllTagsFromFile option to -TagsFromFile and allow copied tags to be + specified on the command line. (-AllTagsFromFile is preserved as an alias + to -TagsFromFile for backward compatibility.) +
  • Allow -GROUP:All and --GROUP:All on command line to extract or exclude all + tags in specified group +
  • Allow family 1 group names to be used when deleting groups with -GROUP:All= +
  • Added composite CFAPattern derived from CFARepeatPatternDim and CFAPattern2 +
  • Fixed problem where tags which can exist in both IFD0 and ExifIFD weren't + being properly removed from one IFD when written to the other +
  • Added FAQ +
+ +May 2, 2005 - Version 5.06 +
    +
  • Made a few more EXIF tags writable +
  • No longer add null-terminator to JPEG comment (was confusing xv) +
+ +Apr. 20, 2005 - Version 5.05 (production release) +
    +
  • Added Nikon LensFStops tag (thanks to Robert Rottmerhusen) +
  • Reliability improvements for writing maker notes information +
  • exiftool now returns error status if there were errors reading/writing files +
+ +Apr. 18, 2005 - Version 5.04 +
    +
  • Fixed problem where maker notes of Olympus C2500L could get corrupted when + writing +
+ +Apr. 18, 2005 - Version 5.03 +
    +
  • ExifTool now requires Perl version 5.004 or higher (previously 5.002 was OK) +
  • Restrict the size of preview images where data is referenced directly as + the value data of an IFD entry (only affects Casio images) +
  • Fixed problems rewriting some Casio maker notes +
  • Change priority of orientation (and a number of other tags which may appear + in IFD1) so value in IFD0 takes precedence of value in IFD1 if it exists +
  • API Changes: +
      +
    • Allow any file reference, not only GLOB references, to be used in + function calls +
    +
+ +Apr. 16, 2005 - Version 5.02 +
    +
  • Fixed problem rewriting Pentax *istD preview image +
+ +Apr. 15, 2005 - Version 5.01 +
    +
  • Major speed improvements for writing large JPEG files with preview images +
  • Fixed problem rewriting preview in Olympus E-1 and E-300 images +
  • Old large preview is now properly removed when writing new small preview +
  • Allow PreviewImage to be deleted (ie. set length to zero) +
  • Don't extract images that have zero length +
  • Deleting MakerNotes group now works in conjunction with -allTagsFromFile +
  • Change image validation again to only validate images for tags that were + specifically requested +
  • Separate lookups by manufacturer for Olympus lens information +
+ +Apr. 14, 2005 - Version 5.00 +
    +
  • ALL MAJOR PLANNED WRITING FEATURES NOW IMPLEMENTED! +
  • Finally solved problem of writing large preview images in JPEG files +
  • -AllTagsFromFile now sets PreviewImage to 'dummy' if it exists in the maker + notes to avoid writing a large preview to the destination file (now you have + to do this manually afterwards if this is what you want) +
  • Fixed problem rewriting Olympus E1 maker note subdirectories +
  • Only validate extracted images when Binary (-b) option is used +
  • Rename Olympus PreviewImageAvailable to PreviewImageValid, and check/set + this tag when reading/writing the preview image +
  • Change priority of X/YResolution tags so IFD0 value takes precedence +
  • Changes to Olympus Lens decoding +
+ +Apr. 11, 2005 - Version 4.95 +
    +
  • Added ability to delete all meta information, or all information in a group +
  • Create some mandatory IPTC tags automatically when writing IPTC information +
  • Decoded a bunch more Olympus tags (thanks to Frank Ledwon) +
  • Decoded a couple more Canon 1D MkII tags (thanks to Denny Priebe) +
  • Fixed problem where Sony maker notes could be corrupted when rewriting file +
  • Fixed problem that could cause wrong tag description to be printed for + missing tags when the -f option is used +
  • Account for different encoding of Canon ExposureTime in 20D and 350D, and + lower priority of Canon ExposureTime and FNumber so regular EXIF values take + precedence because it appears these values may be model dependent (I hate it + when that happens) +
+ +Apr. 6, 2005 - Version 4.94 +
    +
  • Added support for Kodak DX3215 and DX3700 +
  • Improved Kodak decoding and changed some Kodak tag names +
  • Improved logic to guard against cyclical recursion in EXIF directories +
  • Allow tags to be edited in IFD2, IFD3, etc... +
  • Patched problem when writing Canon 350D images due to probable bug in 350D + firmware (version 1.0.1) that writes an incorrect ThumbnailLength in IFD1 +
+ +Apr. 2, 2005 - Version 4.93 (production release) +
    +
  • Added IPTC XMP Core support +
  • Added support for Kodak CX4200 plus other minor Kodak changes +
  • Made Kodak maker notes writable +
  • Minor changes to Olympus tag names and decoding +
  • Split HTML TagNames documentation into separate files +
+ +Mar. 31, 2005 - Version 4.92 +
    +
  • Added support for Kodak and Ricoh cameras +
  • Decode still more Olympus E-1/E-300 tags (thanks Markku Hänninen) +
  • Added 'Directory' tag +
  • Decode a few more Pentax tags (thanks to John Francis) +
  • Allow newlines in tag values on command line when writing +
  • Fixed problem rewriting makernotes with sub directories (eg. Olympus) +
+ +Mar. 28, 2005 - Version 4.91 +
    +
  • Decode yet more Olympus E-1/E-300 tags (thanks Markku Hänninen) +
  • Changed decoding of Olympus E-300 Quality tag +
  • Patched bug in Olympus maker notes that was causing ExifTool to report an + error when reading ORF files +
  • Fixed problem where strings weren't being properly truncated at the null + terminator if there was a newline after the terminator +
  • Improved decoding for some Nikon tags (thanks Tom Christiansen) +
  • Added Nikon shortcut +
  • Added composite SubSecDateTimeOriginal tag +
  • Fixed problem where CRW file without file extension wasn't being identified + properly +
  • Fixed problem extracting thumbnail from some (specifically Olympus) images +
  • Changed verbose output to always show original EXIF format +
  • Skip over EXIF entries with unknown format instead of aborting (while + reading only) +
  • Recognize TIFF field type 13 +
+ +Mar. 24, 2005 - Version 4.90 +
    +
  • Extract Olympus PreviewImage, and decode a bunch more Olympus tags +
  • Improvements to documentation +
+ +Mar. 23, 2005 - Version 4.89 +
    +
  • Decode subdirectories in Olympus maker notes (now much more information is + extracted for E-1 and E-300 cameras, although most is still unknown) +
+ +Mar. 22, 2005 - Version 4.88 +
    +
  • Convert exiftool help to POD format +
+ +Mar. 15, 2005 - Version 4.87 (production release) +
    +
  • Added notes to TagNames documentation +
+ +Mar. 11, 2005 - Version 4.86 +
    +
  • Extract PreviewImage from CR2 files +
  • Create mandatory GPS tags when adding new GPS directory +
  • Bring IPTC newsphoto support up to spec (as if anyone uses this crap) +
  • Fixed problem when setting 8-bit integer IPTC values +
+ +Mar. 10, 2005 - Version 4.85 +
    +
  • Create most mandatory EXIF entries automatically when a new EXIF directory + is created +
  • Fixed problem which caused an error when adding XMP information to a TIFF + file which didn't previously contain XMP +
  • Made '=' optional with -AllTagsFromFile option +
  • Fixed problem with verbose dump of zero-length directory (eg. Sony F717 + maker notes) +
+ +Mar. 9, 2005 - Version 4.84 +
    +
  • Interpret Olympus ImageQuality of 6 as RAW +
  • Remove validation of TIFF identifier to allow forward compatibility with + untested RAW file formats (ORF files in particular seem to fiddle with this + identifier) +
+ +Mar. 8, 2005 - Version 4.83 +
    +
  • Extract ThumbnailImage from Canon CRW files written by some cameras +
  • Recognize ORF files from Olympus C5060WZ (and hopefully some others too!) +
+ +Mar. 7, 2005 - Version 4.82 +
    +
  • Made a number of new EXIF tags writable, but classify them as 'unsafe' so + they aren't copied over with the -AllTagsFromFile option +
  • Recognize a number of new and very uncommon EXIF tags +
  • Remove copy number from tag name when using the -S option +
  • Interpret Photoshop XMP:ColorSpace value of 4294967295 as 'Uncalibrated' +
+ +Mar. 4, 2005 - Version 4.81 +
    +
  • Added user-definable shortcuts +
  • Fixed problem with XMP:Identifier (should have existed in both XMP-dc and + XMP-xmp) +
+ +Mar. 2, 2005 - Version 4.80 +
    +
  • The -n option now prints binary data values as "Binary data #### bytes" +
  • API Changes: (NOTE: Change in API behaviour for binary data values) +
      +
    • Changed returned ValueConv values so that binary data is now returned as + a SCALAR reference, the same as with PrintConv values +
    +
+ +Mar. 1, 2005 - Version 4.73 (production release) +
    +
  • Minor changes to XMP parsing to increase speed and improve validation +
+ +Feb. 28, 2005 - Version 4.72 +
    +
  • Extract info from UTF-16 and UTF-32 encoded XMP +
  • Convert EXIF text fields if encoded in Unicode +
  • Fixed a few incorrect XP character translation codes +
  • Fixed name of Nikon ColorBalanceD2H tag +
+ +Feb. 25, 2005 - Version 4.71 +
    +
  • Fixed bug introduced in 4.70 which caused error when transferring + information using -AllTagsFromFile from a RAW file to a JPEG file +
+ +Feb. 24, 2005 - Version 4.70 +
    +
  • Allow family 1 group name to be specified for any tag while writing +
  • Fixed problem with writing Nikon PreviewImage to NEF files +
+ +Feb. 23, 2005 - Version 4.67 +
    +
  • Added -L option to allow XP characters to be converted to Latin character + set instead of UTF-8. (Now XP characters can be displayed properly in + terminal windows which use either the UTF-8 or WinLatin1 character set.) +
  • Make JpgFromRaw image writable in Nikon NEF files +
+ +Feb. 21, 2005 - Version 4.66 +
    +
  • Recognize JPEG 2000 XMP UUID information +
  • Extract Meta information from JPEG APP3 +
  • Yet more playing with XP characters (this has been a learning process for + me). Now special characters show up properly in my OSX terminal window, and + the reverse translation works so now they get written properly as well (for + Perl 5.6.1 or greater anyway... Earlier versions don't have the required + UTF-8 support to handle these special characters) +
  • Improvements to TagNames documentation (including changing format names to + make them more consistent across different types of meta information) +
+ +Feb. 18, 2005 - Version 4.65 +
    +
  • Fixed problem in translating XP characters +
+ +Feb. 17, 2005 - Version 4.64 (production release) +
    +
  • Added new tag name documentation (replaces old tag lists) +
  • Made a few more DNG tags writable +
+ +Feb. 15, 2005 - Version 4.63 +
    +
  • Remove null terminators in ICC_Profile 'desc' strings +
  • Treat Olympus CameraID as a string (why wasn't it written this way?) +
  • Added print conversion for EXIF:CFAPattern +
+ +Feb. 14, 2005 - Version 4.62 +
    +
  • Convert XPTitle, XPComment, XPKeywords etc from XP character codes and add + write support for these tags +
  • Decode JPEG 2000 Resolution, Label and URL information +
  • Another try at patching 3 digit exponent situation which causes failed tests + on MSWin32-x86 +
  • Removed .J2K from recognized extensions (since apparently this is a raw JP2 + codestream, and doesn't contain any metadata that ExifTool can extract) +
+ +Feb. 14, 2005 - Version 4.61 +
    +
  • Don't print filename line when -p option used +
  • JPEG 2000 improvements +
  • Also recognize .JPX and .J2K extensions +
+ +Feb. 11, 2005 - Version 4.60 +
    +
  • Added support for reading the JPEG 2000 (.JP2) files +
  • Improved warnings on errors while setting tag values +
+ +Feb. 10, 2005 - Version 4.54 +
    +
  • Added ColorTemperature tag for many Canon models +
  • Added AutoRotate for Canon 10D and 300D +
  • Lowered priority of Nikon ISO so that EXIF ISO is used instead if both exist +
  • Changed names of PentaxISO and Casio ISOSetting to ISO, and lowered priority + as with Nikon ISO +
  • Made Photoshop EXIF Camera RAW tags writable +
+ +Feb. 7, 2005 - Version 4.53 (production release) +
    +
  • Added FileNumber for Canon 20D (decoded by Juha Eskelinen) +
  • Removed CanonA0Tag +
+ +Feb. 4, 2005 - Version 4.52 +
    +
  • Added another CanonRaw test +
  • Changes to Canon CRW documentation +
+ +Feb. 4, 2005 - Version 4.51 +
    +
  • Finally found documentation for Canon CRW files (CIFF format)!! +
  • Changed CanonRaw to bring code up to CIFF specification +
  • Added a bunch more CanonRaw tags +
  • Updated Canon CRW documentation +
+ +Feb. 2, 2005 - Version 4.50 +
    +
  • Allow writing to specific IFD +
  • Allow permanent tags (eg. MakerNotes tags) 'deleted' by setting them to an + empty string if '' is a valid value for the tag +
  • Added test for rewriting Nikon D70 information +
  • Added missing inverse conversion routines for GPS tags (now they are all + writable) +
  • Decoded a few more Canon and CanonRaw tags +
  • Added -z option to extract information from images in compressed files +
  • Improved CanonRaw verbose output +
  • Remove garbage after null terminator in CanonRaw string-type tags +
+ +Jan. 30, 2005 - Version 4.45 +
    +
  • Added a few more Canon tags +
  • Fixed bug with divide by zero error (in Perl, '0.0' is a true value -- doh!) +
+ +Jan. 30, 2005 - Version 4.44 +
    +
  • Sort entries in synthesized Canon MakerNotes directory +
  • Interpret Canon custom functions for models other than 10D in CRW files +
+ +Jan. 29, 2005 - Version 4.43 +
    +
  • Synthesize Canon MakerNotes information when using -allTagsFromFile for a + CRW file +
  • Decode WhiteBalance table in Canon maker notes +
  • Rename CanonRaw CanonFileType tag to CanonImageType +
+ +Jan. 28, 2005 - Version 4.42 +
    +
  • Fixed problem where multiple IPTC tags could be created if replacing + specific IPTC tag values with 'TAG-=VALUE' +
  • Made EXIF SceneType writable +
  • Renamed Nikon ISOUsed tag to ISO +
  • Added documentation of Canon RAW (CRW) file format +
+ +Jan. 27, 2005 - Version 4.41 +
    +
  • Added write support for Canon exposure parameters +
  • Change validation of CanonPictureInfo to get it working for Canon 20D +
+ +Jan. 26, 2005 - Version 4.40 +
    +
  • Added ability to write Canon RAW (CRW) files. With this format you aren't + allowed to add or delete any new tags (just as with the MakerNotes), except + for JpgFromRaw, which I like to be able to delete to save disk space +
  • Added validation of JpgFromRaw images +
  • Relax filtering on non-ASCII characters by exiftool script to allow + high-ASCII characters to be printed +
  • Changed the tense of Orientation values to try to make the meaning more + clear. This tag can be a bit confusing. It gives the rotation that must be + applied to the image to view it properly (hence the rotation of the camera + when the picture was taken). +
  • Patched problem which was causing failed tests on some platforms (floating + point format has 3 digits in exponent on Perl 5.8.5 MSWin32-x86, grrrr...) +
  • API Changes: +
      +
    • Added 'Compact' option to not write blank padding as per XMP and IPTC + specs +
    +
+ +Jan. 24, 2005 - Version 4.36 (production release) +
    +
  • Added support for reading Olympus Raw Format (ORF) +
+ +Jan. 23, 2005 - Version 4.35 +
    +
  • Moved a couple of the informational warnings to verbose mode +
  • Suppress warnings an non-critical errors with -m option +
  • Made a few more of the EXIF tags writable +
  • Made model-dependent tags Pentax FocalLength and Olympus Quality writable +
  • Added ability to write CanonCustom tags +
  • Added range check for integer values +
+ +Jan. 21, 2005 - Version 4.34 +
    +
  • Fixed problem when writing Canon maker notes with -allTagsFromFile +
  • Added -o option to write to different file or directory +
  • Added handler to clean up temporary file on Ctrl-C +
  • Re-wrote routine to rationalize floating point values (it is slower now, but + produces much prettier fractions) +
  • Other minor improvements to writer code +
+ +Jan. 19, 2005 - Version 4.33 +
    +
  • Added check at higher level and return warning if trying to delete + information from maker notes +
  • Make GPS latitude and longitude a bit more flexible about the format they + accept when writing +
  • Updates to documentation +
+ +Jan. 19, 2005 - Version 4.32 +
    +
  • Now rewrites Casio EX-Z3 maker notes properly (well, not actually + 'properly', but the way they were written in the first place, which is + wrong) +
  • Added warning when writing information if original IFD entries were not in + the proper sequence, which is a violation of EXIF specs. (And surprise, + you'll never guess who does this too... Yup, the EX-Z3.) +
  • Fixed parsing problem with GPSProcessingMethod and GPSAreaInformation +
  • No longer truncates 'undef' values at first null character +
  • Changed all DataDump tags to binary data types +
  • Changed some warning messages +
  • Documented the -m option (it's now official, even though it's been there + since version 4.10) +
  • Added some more writer tests +
+ +Jan. 18, 2005 - Version 4.31 +
    +
  • Now also copies over preview image in Nikon NEF files +
+ +Jan. 18, 2005 - Version 4.30 +
    +
  • Now copies over preview images in EXIF data (large, external previews still + not copied) +
  • Account for funny offsets in Casio EX-Z3 maker notes while extracting data + (but haven't figured out how to handle them when writing) +
  • Fixed bug introduced in 4.20 that broke extraction of Canon PreviewImage +
+ +Jan. 17, 2005 - Version 4.23 +
    +
  • Improve handling of unrecognized maker notes when writing +
+ +Jan. 17, 2005 - Version 4.22 +
    +
  • Added check in -AllTagsFromFile to test for pointers in the maker notes + directory running outside the maker notes data. If they do, a warning is + issued and the maker notes are rebuilt properly before copying. +
  • Fixed problem which could corrupt some values when editing maker notes +
+ +Jan. 17, 2005 - Version 4.21 +
    +
  • Added Olympus Red/BlueBalance +
+ +Jan. 17, 2005 - Version 4.20 +
    +
  • Added ability to edit MakerNotes! +
  • Added more validation when writing IPTC information +
  • Fixed display of Nikon FlashExposureComp for negative values +
  • Fixed problem where the large JPEG image in Nikon and Pentax raw files was + misidentified as the ThumbnailImage. It is now extracted as JpgFromRaw. + This allows all 3 JPEG images contained in Pentax PEF files to be extracted: + ThumbnailImage, PreviewImage and JpgFromRaw. +
  • Fixed problem on systems that use backslashes in directory names that + prevented exiftool from finding its libraries if not installed +
  • Changed many Pentax tag names to remove "Pentax" prefix and conform more to + the other tag names (moving information between files of different formats + is much easier if tags have standardized names): +
      +
    • PentaxPictureMode => PictureMode +
    • PentaxFocusMode => FocusMode +
    • PentaxWhiteBalance => WhiteBalance +
    • PentaxAEMetering => MeteringMode +
    • PentaxFocalLength => FocalLength +
    • PentaxZoom => DigitalZoom +
    • PentaxSaturation => Saturation +
    • PentaxContrast => Contrast +
    • PentaxSharpness => Sharpness +
    +
  • Fixed FocalLength conversion for Pentax Optio S +
  • Fixed printout of Nikon FileSystemVersion for older Nikon models +
  • More improvements to reliabilty of preview image extraction +
  • Fixed Quality for Olympus E-1 +
+ +Jan. 12, 2005 - Version 4.15 +
    +
  • Added Pentax LensType and RawImageSize tags +
  • Change printing of some unknown values to hexadecimal +
  • Now recognizes Nikon PEF files +
  • More reliable extraction of preview and thumbnail images, particularly for + the various models of Pentax cameras +
  • Added decoding of the Canon 20D custom functions and a new Canon20D shortcut + (thanks to Christian Koller) +
  • Improved write logic for EXIF information +
  • Improved logic in determining byte ordering of maker notes +
+ +Jan. 10, 2005 - Version 4.14 +
    +
  • Fixed problem introduced in 4.13 that messed up new 4.12 features. doh. + (and added test to keep this from happening again!). +
  • No longer store bad directory data as a tag (dump in verbose output instead) +
+ +Jan. 9, 2005 - Version 4.13 +
    +
  • Added check on size of new ThumbnailImage so ExifTool doesn't try to write + an image that is too large (>60k) into the JPEG EXIF APP1 segment +
+ +Jan. 9, 2005 - Version 4.12 +
    +
  • -AllTagsFromFile option now copies over the maker notes +
  • Changed some misleading warning messages +
+ +Jan. 8, 2005 - Version 4.11 +
    +
  • Improved validation of tag values with -AllTagsFromFile option +
+ +Jan. 7, 2005 - Version 4.10 +
    +
  • Added ability to write EXIF, IPTC and XMP tags in JPEG and TIFF files! +
  • Allow Photoshop APP13 data to span multiple segments (read and write) +
  • Added -TAG+=VALUE, -TAG-=VALUE and -TAG<=VALUE syntaxes +
  • Added -GROUP:TAG syntax to allow tag group to be specified +
  • Added powerful -AllTagsFromFile=SRCFILE option to copy all tags from file +
  • Added -listw option to list all writable tags +
  • Added -E option to escape output values for HTML +
  • Fixed -w option to only replace extension after last '.' in filename if more + than one '.' +
  • Unescape XMP character codes when extracting values (and escape again when + writing) +
  • Now processes all IFD's of TIFF imags (not just IFD0) +
  • Added data length check in hex dump of verbose option +
  • Allow group name to be specified as prefix to tag name on command line +
  • Renamed a few Nikon tags: FlashExposureComp to FlashExposureBracketValue, + FEC to FlashExposureComp, and ShutterReleaseMode to ShootingMode +
  • Extract Nikon preview image +
  • Changed descriptions for Aperture and Shutter Speed to drop the Av/Tv + Canonism +
  • Improved logic to recognize more types of unknown maker notes +
  • Recognize a couple more values of the Canon WhiteBalance tag +
  • Renamed IPTC 'SupplementalCategory' to 'SupplementalCategories' +
  • Handle timezone in times +
  • API Changes: +
      +
    • Fixed problem where first tag name passed to GetInfo() was ignored +
    • The values returned by ImageInfo() and GetInfo() may contain array + references to indicate lists of values if PrintConv is disabled +
    • Added a bunch of new stuff... +
    +
+ +Dec. 15, 2004 - Version 4.05 +
    +
  • Added a couple of Nikon tags (thanks Brian Ristuccia) +
  • Now preserves original file by renaming to "NAME_original" when writing + information +
  • Don't preserve file time by default when writing. Added -P option to do + this. +
  • Changes to spec file +
+ +Dec. 11, 2004 - Version 4.04 +
    +
  • Fixed problem which could corrupt JPEG images when adding comments (Note: if + done, the damage can reversed by removing the comments with the same version + of ExifTool that added them.) +
+ +Dec. 6, 2004 - Version 4.03 +
    +
  • Major overhaul of verbose message output +
  • Change -v option to allow verbose level to be specified (eg. -v3 = very very + verbose) +
  • Added a new Nikon tag (thanks Thomas Walter) +
  • Count images which were unchanged when writing tags +
  • Changed FileType 'JPG' to 'JPEG' +
+ +Dec. 2, 2004 - Version 4.02 +
    +
  • Fixed problem with rewriting some JPEG images +
  • Preserve original file modification time when updating tags in a file +
  • Report of number of files updated +
  • API Changes: +
      +
    • Changed arguments of WriteInfo() and allow scalar and file references to + be used +
    +
+ +Dec. 1, 2004 - Version 4.01 +
    +
  • Changed -o option to -w to avoid confusion since we now write image files + too +
  • Added warning if specified image file doesn't exist +
+ +Dec. 1, 2004 - Version 4.00 +
    +
  • Started down the road of adding write support: +
      +
    • Allow writing of Comment tag to JPEG and GIF files +
    +
  • API for write functions still under development and is likely to change +
  • Clean up formatting of Nikon string tags (fix case and remove trailing + spaces) +
+ +Nov. 30, 2004 - Version 3.96 +
    +
  • Changed JPEG read routine to speed things up a bit +
  • Added a few more ICC_Profile tags +
+ +Nov. 25, 2004 - Version 3.95 +
    +
  • Improved compatibility with old Perl versions (now runs, albeit with + warnings, on 5.003) +
+ +Nov. 25, 2004 - Version 3.94 (production release) +
    +
  • Patched problem with reading XMP data using Perl 5.6.x (Perl bug) +
  • Put lib directory first in exiftool include list to take precedence over + installed versions +
  • Continue trying to parse JPEG image after an unrecognized APP1 segment +
+ +Nov. 24, 2004 - Version 3.93 (production release) +
    +
  • Final round of ICC_Profile updates +
  • Increase precision of extracted rational values +
  • Internal Changes: +
      +
    • Build in better support for all data formats +
    • Standardize data format names +
    • Clean up and streamline data read routine +
    +
+ +Nov. 22, 2004 - Version 3.92 +
    +
  • Fixed problem with -p option when multiple files are specified +
  • Enhancements to ICC_Profile information, including extracting information + from profile header +
  • Subdivide ICC_Profile group in family 1 +
  • Added Minolta ImageStabilization tag +
+ +Nov. 20, 2004 - Version 3.91 +
    +
  • Fixed problem where some tags were not extracted properly from Canon CR2 + files +
  • Internal Changes: +
      +
    • Cleaned up and simplified pointer calculations and dirInfo members +
    +
+ +Nov. 20, 2004 - Version 3.90 +
    +
  • Extract information from ICC Profiles +
  • Extract undocumented IFD0 Photoshop tags +
  • Added support for Minolta RAW (MRW) file format +
  • Added support for Konica-Minolta cameras +
  • Improved decoding for Minolta maker notes +
  • Extract (the sometimes misleading) EXIF WhiteBalance tag even if + WhiteBalance was extracted from the maker notes if the Duplicates option is + set. (Previously it was only extracted as an Unknown tag in this case.) +
  • API Changes: +
      +
    • Return list of all tags in image if GetFoundTags() or GetTagList() are + called before ImageInfo() or GetInfo() +
    +
+ +Nov. 15, 2004 - Version 3.85 +
    +
  • Extract a couple more Photoshop tags (including PhotoshopQuality) +
  • All XMP lists now comma separated (previously, 'alt' lists were separated by + '|') +
  • API Changes: +
      +
    • GetValue() now returns reference to array if values form a list and + ValueConv is specified +
    +
+ +Nov. 12, 2004 - Version 3.84 +
    +
  • Added test of GetTagID() +
  • Fixed bug in GetTagID() which was causing special tags to get overwritten +
+ +Nov. 12, 2004 - Version 3.83 +
    +
  • Added -D and -H command line options +
  • API Changes: +
      +
    • Added GetTagID() +
    +
+ +Nov. 11, 2004 - Version 3.82 (production release) +
    +
  • Improved diagnostic output for failed tests in installation +
+ +Nov. 11, 2004 - Version 3.81 +
    +
  • Updated Olympus module to also support Epson cameras +
  • Moved MakerNotes code into separate module +
  • Added tests for Sony and Unknown maker notes +
+ +Nov. 10, 2004 - Version 3.80 +
    +
  • Added support for Panasonic/Leica cameras +
  • Updated Pentax module to also support Asahi cameras +
  • Decode a couple more Minolta camera model types +
+ +Nov. 4, 2004 - Version 3.74 (production release) +
    +
  • Properly localize $_ in public Image::ExifTool subroutines +
+ +Nov. 3, 2004 - Version 3.73 +
    +
  • Changes to tests to avoid false failures on MSWin32-x86-multi-thread 4.0 +
+ +Nov. 1, 2004 - Version 3.72 (production release) +
    +
  • Fixed minor bug in generation of family 1 XMP group names +
  • Changes to Photoshop family 2 groups +
+ +Oct. 30, 2004 - Version 3.71 +
    +
  • Switched group families 0 and 1 so the general location is now the default +
  • Fixed bug when sorting by order of group for any family other than 0 +
  • Added test 17 to ExifTool.t +
+ +Oct. 29, 2004 - Version 3.70 +
    +
  • Major improvements to XMP parsing +
  • Divided XMP group in family 0 based on the XMP namespace prefix +
  • Changed a few long tables to binary type +
  • Recognize some new YCbCrSubSampling values +
  • Display DNG LocalizedCameraModel in plain text +
  • Patched problem in FileSource reported by Sigma cameras +
  • Added information about tag format to verbose hex dump +
+ +Oct. 22, 2004 - Version 3.61 +
    +
  • Added support for DNG file format +
  • Added and updated a number of EXIF tags for FAX and other uncommon images +
  • Added Photoshop URL tag +
  • Attempt to extract image from files with unrecognized extensions assuming + TIFF format +
  • Added "Image format error" if the image type is recognized but the format is + bad +
  • Changed "Unknown file type" error to "Unknown image type" +
  • Moved POD documentation into separate .pod files +
  • Started referencing sources for tag definitions in the source code +
+ +Oct. 1, 2004 - Version 3.60 (production release) - initial CPAN release! +
    +
  • Changed group family 0 to divide EXIF group into individual IFD groups +
  • Fixed typos in some Casio tag names +
  • API Changes: +
      +
    • Changed name of File::RandomAccessFile to File::RandomAccess +
    • Changed default setting of Duplicates to 1 +
    +
+ +Sept. 21, 2004 - Version 3.51 +
    +
  • Improvements to interpretation of Nikon D70 ISO settings +
+ +Sept. 16, 2004 - Version 3.50 +
    +
  • Fixed problem with duplicate tags showing up without the -a option +
  • Changed Nikon DataDump to a binary type +
  • Added D70Boring shortcut +
+ +Sept. 14, 2004 - Version 3.49 +
    +
  • Changed installation to also install the 'exiftool' script +
+ +Sept. 13, 2004 - Version 3.48 +
    +
  • Changed UserComment to skip first 8 bytes since the comments come after an 8 + byte character code +
+ +Sept. 10, 2004 - Version 3.47 +
    +
  • Added support for second type of Casio maker notes (MakerNoteCasio2) +
+ +Sept. 1, 2004 - Version 3.46 +
    +
  • Fixed minor bug in PrintConv of FileNumber for CanonRaw files +
+ +June 3, 2004 - Version 3.45 +
    +
  • Recognize Canon 1D Mk II raw files (.CR2) +
    (Note: Not properly decoding maker notes from these files yet) +
+ +May 28, 2004 - Version 3.44 +
    +
  • Improved validity check of Sony maker notes +
+ +May 18, 2004 - Version 3.43 +
    +
  • A couple more changes to the Nikon maker notes +
+ +May 17, 2004 - Version 3.42 +
    +
  • Additions to Nikon maker notes for values derived from D70 +
+ +Apr. 28, 2004 - Version 3.41 +
    +
  • Fixed some errors when running on older Perl versions +
+ +Apr. 7, 2004 - Version 3.40 +
    +
  • Try to extract data from unrecognized maker notes (assuming standard EXIF + format) +
  • Added tests for different maker notes +
+ +Apr. 6, 2004 - Version 3.37 +
    +
  • Added support for Sigma maker notes +
  • Remember to add new files to MANIFEST so they get included in release. Doh +
+ +Apr. 6, 2004 - Version 3.36 +
    +
  • Added support for Sanyo and Minolta maker notes +
  • Added skeleton for interpreting Sony maker notes +
  • Interpret Pentax PrintIM +
+ +Apr. 6, 2004 - Version 3.35 +
    +
  • Added support for Nikon PrintIM +
  • Changed names of duplicate EXIF tags +
+ +Apr. 5, 2004 - Version 3.34 +
    +
  • Added all missing tag definitions from TIFF 6 standard +
  • Added a few more EXIF tag definitions +
  • Interpret PrintIM IFD +
  • Fixed interpretation of Interoperability IFD +
  • Fixed potential endless loop bug introduced in version 3.33 +
+ +Apr. 5, 2004 - Version 3.33 +
    +
  • Parse SubIFD of Nikon NEF file (now extracts raw image size and thumbnail + image) +
+ +Apr. 2, 2004 - Version 3.32 +
    +
  • Changes to some Nikon tag names +
  • Added Nikon Saturation +
  • Documentation improvements +
+ +Mar. 31, 2004 - Version 3.31 +
    +
  • Now recognizes NEF (Nikon Electronic image Format) files +
+ +Mar. 29, 2004 - Version 3.30 +
    +
  • Removed -w option +
  • Fixed problem with some XMP tags being put in the EXIF group +
  • More minor speed improvements +
  • API Changes: +
      +
    • GetDescription() now requires an ExifTool object reference +
    • Removed WarnDuplicateDescriptions() +
    +
+ +Mar. 26, 2004 - Version 3.27 +
    +
  • Optimized a few routines to speed things up a bit +
  • API Changes: +
      +
    • Changed GetDescription() documentation to indicate it is called with an + ExifTool object (this is still optional, but will be mandatory with the + next version) +
    +
+ +Mar. 25, 2004 - Version 3.26 +
    +
  • Don't generate warning if end of IPTC block is padded with nulls +
+ +Mar. 19, 2004 - Version 3.25 +
    +
  • Fixed problem with 'Input' sort order +
+ +Mar. 19, 2004 - Version 3.24 +
    +
  • Only return PreviewImage if it is a valid JPG (otherwise set 'Warning') +
+ +Mar. 16, 2004 - Version 3.23 +
    +
  • API Changes: +
      +
    • Added GetGroups() +
    • GetGroup() now returns group names for all families if used in list + context and family not specified +
    +
+ +Mar. 12, 2004 - Version 3.22 +
    +
  • API Changes: +
      +
    • Changed GetInfo() to return list of tags like ImageInfo() if list + reference provided +
    • Fixed bug that caused GetInfo() to ignore specified tags +
    +
+ +Mar. 11, 2004 - Version 3.21 +
    +
  • Fixed problem with Composite group in family 1 +
  • Changed case of Exif to EXIF in family 1 +
  • -group option now lists Composite group as it should +
  • Internal Changes: +
      +
    • Cleaned up handling of function arguments +
    +
+ +Mar. 10, 2004 - Version 3.20 +
    +
  • Added -group option +
  • Added group families 1 and 2 +
  • Can now specify excluded tags with leading '-' (replaces -x option) +
  • API Changes: +
      +
    • Added ClearOptions(), ExtractInfo(), GetInfo(), CombineInfo(), + GetTagList() and GetAllGroups() +
    • Removed IsVerbose() function (use Options('Verbose') instead) +
    • Allow groups to be excluded by specifying leading '-' on group name +
    • ImageInfo() and GetInfo() now use specified group order to set tag + precedence if Duplicates option is not set +
    • Change default value of Duplicates option back to 0 +
    +
+ +Mar. 1, 2004 - Version 3.15 +
    +
  • Changed format of all date and time tags to EXIF standard +
  • Added some composite date/time tags +
  • Fixed date formatting so -d option should now work with all combined + date/time tags +
  • Other minor changes to GPS information +
  • Improvements to TIFF processing +
  • Set value to "Undefined" if PrintConv evaluates to undefined value +
  • Added -G option +
  • API Changes: +
      +
    • Changed all option names: shortened and changed to mixed case (sorry!) +
    +
  • Internal changes: +
      +
    • Standardized arguments to all processing procedures +
    • Made call to processing procedure more automatic +
    • Removed TABLE_TYPE tag and added PROCESS_PROC +
    • Added ProcessTagTable() member function +
    +
+ +Feb. 27, 2004 - Version 3.14 +
    +
  • Added GPS tag conversions and GPS test +
  • Values that can't be converted now show up simply as "Unknown (X)" +
+ +Feb. 26, 2004 - Version 3.13 +
    +
  • Print out errors from exiftool script (since Image::ExifTool no longer + prints them) +
  • Added more tests +
  • Failed tests now leave ".failed" file in "t" directory for post mortem +
+ +Feb. 25, 2004 - Version 3.12 +
    +
  • Moved all image-related warnings to new Warning tag +
+ +Feb. 25, 2004 - Version 3.11 +
    +
  • Added GeoTiff support +
  • Added -x option +
  • Improvements to documentation +
  • Improve XMP parsing for 'Bag' elements +
  • Capitalize first letter of XMP tag descriptions +
  • Patch problem with APP13 resource written by older Photoshop versions +
  • API Changes: +
      +
    • Added EXCLUDE and GROUP# options +
    • Change default value of SAVE_DUPLICATES option to 1 +
    +
+ +Feb. 20, 2004 - Version 3.10 +
    +
  • Restructuring only -- the behaviour of the exiftool script was not changed +
  • Moved html documentation to new html directory +
  • API Changes: +
      +
    • Conform to standard Perl module mechanics: +
        +
      • Changed ExifTool package name to Image::ExifTool +
      • Added Makefile.PL and other standard files +
      • Added Perl pod documentation +
      • Added standard test files +
      • Moved modules into lib directory +
      • Changed "TagTables" directory name to "ExifTool" +
      +
    • Added extra parameter in new RandomAccessFile +
    +
+ +Feb. 20, 2004 - Version 3.05 +
    +
  • Fixed problem where output files (-o) weren't written if -p option used +
+ +Feb. 19, 2004 - Version 3.04 +
    +
  • Added -U option to allow display of unknown values in Canon binary data + blocks +
  • Made unknown tag names more specific when -u or -U option used +
  • Added RawData and DecoderTable tags (for Canon RAW file) +
+ +Feb. 17, 2004 - Version 3.03 +
    +
  • Fixed RandomAccessFile package name (should have been + File::RandomAccessFile) +
  • Added IxusAFPoint tag to Canon maker notes (thanks Michael Rommel) +
  • Avoid scanning past end of Canon binary data blocks +
  • API Changes: +
      +
    • GetFoundTags() and GetRequestedTags() now return list instead of list + reference +
    +
+ +Feb. 16, 2004 - Version 3.02 +
    +
  • Improved handling of Pentax maker notes +
+ +Feb. 15, 2004 - Version 3.01 +
    +
  • API Changes: +
      +
    • Added GetValue() function +
    • Completed API documentation +
    +
+ +Feb. 13, 2004 - Version 3.00 +
    +
  • Removed -all option (it is now the default -- specify -common for previous + default behaviour) +
  • Added -a option to allow printout of duplicate tag values +
  • API Changes: +
      +
    • I am finally happy with the API, so future major changes are less likely + (hence the major version number) +
    • No longer return ARRAY reference for list of tags (Instead, tag values + are joined in a comma separated list if tag 'List' flag is set) +
    • Added SAVE_DUPLICATES option +
    • Added BuildCompositeTags() to EXPORT_OK list +
    • GetFoundTags() now sorts tags in specified order +
    • GetDescriptions() longer returns undef if the description doesn't exist +
    +
+ +Feb. 12, 2004 - Version 2.71 +
    +
  • Still more playing with Pentax maker notes +
  • More API Changes: +
      +
    • Added RandomAccessFile.pm +
    • All image file i/o now done through a RandomAccessFile object +
      --> allows proper piping and use of string i/o +
    • Allow scalar reference to be passed to ImageInfo() (for string i/o) +
    +
+ +Feb. 11, 2004 - Version 2.70 +
    +
  • More tweaking of Pentax maker notes +
  • Changed API to be more object oriented: +
      +
    • Removed SetVerbose(), ExtractUnknown(), SetDateFormat(), + EnablePrintConversion(), EnableCompositeTags() +
    • Added Options() to replace above functions +
    • Changed WarnDuplicateTags() to WarnDuplicateDescriptions() +
    • Added GetFoundTags() and GetRequestedTags() +
    • Many functions now take ExifTool object reference as first argument +
    • ImageInfo() no longer returns reference to ExifTool object when used in + list context (you have to use "new ExifTool" and the OO form of + ImageInfo() if you want the object) +
    +
+ +Feb. 10, 2004 - Version 2.62 +
    +
  • Added -u option to allow display of unknown tags +
  • Major changes to Pentax maker notes (still needs work) (thanks Wayne Smith) +
+ +Feb. 09, 2004 - Version 2.61 +
    +
  • Allow file reference to be passed to ImageInfo() +
  • Allow file to be read from standard input by specifying "-" as file name +
  • Added FileType tag +
+ +Feb. 07, 2004 - Version 2.60 +
    +
  • Improve IPTC parsing and add support for more IPTC data types +
  • Read Photoshop APP13 records properly +
  • Added -g option +
  • Move shortcuts into separate module +
  • Changes to API: +
      +
    • Removed LoadAllTables() and added GetAllTags() +
    • Removed GetDescriptions() and added GetDescription() +
    • Changed GetShortcuts() to return a list +
    • Added tag groups and GetGroup() function +
    • Return object data from ImageInfo() for use in GetGroup() +
    +
+ +Jan. 30, 2004 - Version 2.51 +
    +
  • Speed up JPG reading code +
  • API no longer returns references to image-specific static data +
  • Added ExifToolVersion tag +
+ +Jan. 29, 2004 - Version 2.50 +
    +
  • Changed API to return binary data as SCALAR reference and + list of values as ARRAY reference (thanks Dan Heller for the suggestions) +
  • Attempt to make case of tag descriptions more consistent +
+ +Jan. 28, 2004 - Version 2.41 +
    +
  • Scan photoshop JPG 0xe1 garbage for possible XMP information +
+ +Jan. 27, 2004 - Version 2.40 +
    +
  • Improved handling of XMP data +
  • Changed output format and added -l option +
+ +Jan. 21, 2004 - Version 2.36 +
    +
  • Don't output trailing linefeed when -b option used +
+ +Jan. 19, 2004 - Version 2.35 +
    +
  • Changes to verbose output +
  • Added TagTables::CanonRaw::CleanRaw() as an API utility function +
+ +Jan. 16, 2004 - Version 2.34 +
    +
  • Added 'Validate' check for Canon data fields +
  • Changed ScaleFactor35efl to use FocalLengthIn35mmFormat if available +
+ +Jan. 15, 2004 - Version 2.33 +
    +
  • Added ScaleFactor35efl, FocalLength35efl, Lens35efl +
  • Allow Composite tags to Require/Desire each other +
  • Changed FlashType to use FlashBits instead of CanonFlashMode +
+ +Jan. 13, 2004 - Version 2.32 +
    +
  • Added -d (date format) option +
  • Added -p (print format file) option +
+ +Jan. 9, 2004 - Version 2.31 +
    +
  • Exif WhiteBalance no longer overrides maker-specific WhiteBalance +
+ +Jan. 8, 2004 - Version 2.30 +
    +
  • Added support for IPTC format information +
+ +Jan. 6, 2004 - Version 2.25 +
    +
  • Fixed problem with ImageInfo() function prototype +
  • Fixed printout of JpgFromRaw message (doesn't affect JPG extraction) +
  • Set output files to binmode (including STDOUT) if -b option used (thanks + David Anson) +
+ +Jan. 1, 2004 - Version 2.24 +
    +
  • Fixed -list option to show all available tag names +
+ +Dec. 18, 2003 - Version 2.23 +
    +
  • Changed "Disable" routines to "Enable" +
+ +Dec. 17, 2003 - Version 2.22 +
    +
  • Fixed make/model tags which I broke with a recent change +
  • Removed null terminator from returned strings +
+ +Dec. 16, 2003 - Version 2.21 +
    +
  • Fixed problem with decoding some Nikon maker notes +
  • General improvements and tweaks to the code +
+ +Dec. 14, 2003 - Version 2.20 +
    +
  • Now extracts preview image from 300D JPG files (PreviewImage) +
  • Changed ThumbnailData tag name to ThumbnailImage +
+ +Dec. 12, 2003 - Version 2.10 +
    +
  • ExifTool::ImageInfo now returns reference to hash instead of hash +
+ +Dec. 10, 2003 - Version 2.01 +
    +
  • Minor fixes for reading of RAW files +
+ +Dec. 09, 2003 - Version 2.00 +
    +
  • Added support for Olympus, Casio and Nikon cameras +
  • Now recognizes GPS information +
  • Moved config information to TagTables modules +
  • Restructured API +
+ +Dec. 05, 2003 - Version 1.72 +
    +
  • Changes to composite Aperture and ShutterSpeed decisions +
+ +Dec. 05, 2003 - Version 1.71 +
    +
  • Read 10D Custom functions from CRW file too (thanks dpophyte) +
+ +Dec. 05, 2003 - Version 1.70 +
    +
  • Added custom functions for 10D and 1D +
+ +Dec. 04, 2003 - Version 1.62 +
    +
  • Decode known flash bits +
+ +Dec. 04, 2003 - Version 1.61 +
    +
  • Override ShutterSpeed with BulbDuration if available +
  • Change -s option to add tab-separated list +
+ +Dec. 03, 2003 - Version 1.60 +
    +
  • Big improvements in reading Canon RAW files +
+ +Nov. 29, 2003 - Version 1.50 +
    +
  • Added ability to extract JPG from RAW +
  • Added ExifData tag to allow entire EXIF block to be dumped +
+ +Nov. 26, 2003 - Version 1.40 +
    +
  • Split up config files to speed things up +
  • Added ability to extract binary data +
  • Added ThumbnailData tag (to allow extracting JPG thumbnails) +
+ +Nov. 25, 2003 - Version 1.30 +
    +
  • Added experimental Canon RAW (CRW) file support +
+ +Nov. 22, 2003 - Version 1.20 +
    +
  • Now reads TIFF files too +
+ +Nov. 20, 2003 - Version 1.12 +
    +
  • Don't translate Photoshop Brightness, etc +
+ +Nov. 20, 2003 - Version 1.11 +
    +
  • Attempt to fix problem on hp +
  • Clean up code a bit +
  • Added '-ver' command-line option +
+ +Nov. 20, 2003 - Version 1.10 +
    +
  • Added support for XMP format +
+ +Nov. 19, 2003 - Version 1.00 +
    +
  • Initial release (extracts information from JPEG and GIF images, with Canon, + FujiFilm and Pentax makernote support) +
+ +
+

<-- Back to recent history

+ + diff --git a/ExifTool/html/canon_raw.html b/ExifTool/html/canon_raw.html new file mode 100644 index 0000000..7e5761c --- /dev/null +++ b/ExifTool/html/canon_raw.html @@ -0,0 +1,368 @@ + + + +The Canon RAW (CRW) File Format + + + + +

The Canon RAW (CRW) File Format

+ +

This is a description of the Canon CRW file format. CRW files are written in +Camera Image File Format (CIFF). The original Canon CIFF specification can be +downloaded from http://xyrion.org/ciff/ +(which I finally discovered after writing this document!). Note that Canon has +three different RAW formats, with newer models producing a +TIFF-based CR2 or +QuickTime-based CR3 as opposed +to the CIFF-based CRW format documented here.

+ +

Comments about the CRW Format

+ +

The Canon CRW file format is a joy to work with. It has a structure that is +fundamentally similar to TIFF, with directories that point to data within the +file. But a major improvement over TIFF is that the offsets are not absolute, +they are relative to the start of the data block for each directory. This +allows subdirectories within a file to be moved or copied to another file +without having to adjust the offsets, which is fantastic because it means that +rewriter software doesn't have to understand the complete format in order to be +able to successfully rewrite the file.

+ +

Also, the data comes before the directories in the file, which is the natural +way to write information and minimizes the amount memory needed to buffer the +data (unlike EXIF, which is typically the reverse).

+ +
+A short rant about TIFF inadequacies: +
TIFF format on the other hand, really sucks in comparison (this includes +JPEG too, since JPEG uses TIFF format to store the EXIF information). The main +problems are the use of absolute offsets and the ambiguity between integers and +pointers (such as those used for custom IFD's). Because absolute offsets +require adjusting whenever anything is moved in the file, the format of ALL +contained data structures must be understood to properly edit the file. This +results in an impossible situation when presented with undocumented custom +structures like those used in the maker notes written by modern digital cameras. +This is why it is so common for image editors to either scramble the maker notes +or discard them completely. The official TIFF recommendation is to discard +unknown information when rewriting the image (as Photoshop does), but for many, +including myself, this option is simply unacceptable. +
+ +

The bottom line is that rewriting a Canon CRW file is about 20 times easier, +and much less prone to errors than rewriting a TIFF or JPEG.

+ +

CRW (CIFF) Format Specification

+

File Header

+

A Canon CRW file starts with the following byte sequence:

+
+ + + + + + + + + + + + +
OffsetNumber
of bytes
ValueNameDescription
02"II"ByteOrder"II" means Intel (little-endian) order, which is the only order I've +seen since Canon is using x86 processors in its current cameras, but +presumably this could be "MM" for future models. +
240x0000001aHeaderLength32-bit integer giving the length of the CRW file header. For +current camera models the header is 26 bytes long.
68"HEAPCCDR"SignatureThis series of characters identifies the file as a Canon CRW +file. The signature is "HEAPJPGM" for CIFF information in APP0 of JPEG images.
1440x00010002CRWVersion32-bit integer giving the major (high 16 bits) and minor +(low 16 bits) CRW file version numbers. The version is 1.2 for current cameras.
1880ReservedTwo 32-bit integers, currently set to zero.
+ +

Directory Block Structure

+

The root directory block begins immediately after the file header (at the position +specified by HeaderLength), and ends at the end of the file. The structure +of this block is as follows:

+
+ + + + + + + + + + + + +
Offset
within block
Number
of bytes
ValueNameDescription
0S-
-
-
-
-
ValueDataThe value data referenced by offsets in the directory
S2NDirCount16-bit integer giving the number of directory entries
S + 2N * 10-
-
-
DirEntriesThe CRW directory entries
S + 2 + N*10any-OtherData(be aware there may be other data hiding here)
BlockSize - 44SDirStart32-bit integer giving the size of the ValueData
+ +

To parse a CRW directory block, first read the 4 bytes at the end of the +block to get the location (S) of DirCount. Next read DirCount to +determine the number of entries (N) in the directory, then read the directory +entries.

+ +

The ValueData may contain complete subdirectory blocks, each of which +has the same format as specified above (and these subdirectories may themselves +contain sub-subdirectories...). For these subdirectories, BlockSize is +given by the Size specified in the corresponding directory entry. For +the root directory, BlockSize is the length of the file.

+ +

The OtherData normally does not exist (eg. the DirEntries +usually end at the DirStart pointer), but this is not always the case: +Canon Digital Photo Professional uses this area in the root directory block to +store VRD data in edited CRW images. (Note +that VRD supports embedded XMP, which provides a technique to store modern +metadata in CRW images!)

+ +

CRW Directory Entry Format

+

The CRW directory consists of N 10-byte entries. The format of each entry is +as follows:

+
+ + + + + + + + +
Offset
within entry
Number
of bytes
NameDescription
02Tag16-bit integer identifying the type of data
24Size32-bit integer giving the number of bytes in the value data
64Offset32-bit integer offset that gives the number of bytes from the start of the + ValueData block to the start of the value data for this directory entry
+ +

Data values with lengths shorter than 8 bytes may be stored in the directory +Size and Offset fields. For these values the DataLocation +bits in the Tag are set to 0x4000 (see DataLocation table +below).

+ +

Data stored in the ValueData block must be aligned on even 2-byte +boundaries, yielding Offsets which are divisible by 2. To achieve this, +records with an odd number of bytes must be padded with a zero byte.

+ +

Tag Bits

+

The 16-bit Tag value is composed of 3 bit fields, as follows:

+
+ + + + + + + +
BitsMaskNameDescription
14-150xc000DataLocationSpecifies the location of the data
11-130x3800DataFormatIdentifies the information format
0-100x07ffTagIndexIndex to identify the specific type of information
+ +

DataLocation

+

Value data may either be stored in the ValueData block, or within the +Size and Offset fields of the directory entry if it is less than 8 +bytes long. The DataLocation bits specify where the value is stored:

+
+ + + + + + +
DataLocationWhereDescription
0x0000ValueDataValues are stored in the ValueData block, at the specified Offset + and Size
0x4000DirectoryValues are stored in the Size and Offset fields + of the directory entry. Values stored here are limited to a maximum size of 8 bytes.
0x8000
0xc000
?-
+ +

DataFormat

+

Three Tag bits are used to specify the data format:

+
+ + + + + + + + + + + + + + + +
DataFormatAlignmentDescription
0x00001-ByteA series of bytes
0x08001-ByteA null-terminated ASCII string
0x10002-ByteA series of 16-bit integers
0x18004-ByteA series of 32-bit integers or floats
0x20001-ByteA structure which is a mixture of formats
0x2800
0x3000
1-ByteA subdirectory block
0x3800?-
+ +

TagID = DataFormat + TagIndex

+

Together, the DataFormat and TagIndex fields form a TagID +which can be used to identify tags within the CRW file. The following is a list +of known TagID values and their corresponding tag numbers in the EXIF +maker notes when found in a JPEG or TIFF image. Also listed is the TagID of the +SubDirectory where the information is found. The Size listed +below is an observed size of the value data, and is not necessarily expected to +remain constant for all camera models.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TagIDEXIFSubDirNameSizeDescription
1-Byte Alignment
0x0000-anyNullRecord0This is a null directory entry
0x0001-anyFreeBytesvariesUnused bytes in the ValueData
0x0006-0x300b-8-
0x0032-0x300bCanonColorInfo1768 or 2048Block of color information (format unknown)
0x0036-0x300b?varies-
0x003f-0x300b?5120-
0x0040-0x300b?256-
0x0041-0x300b?256-
ASCII Strings
0x0805-0x2804CanonFileDescription32Description of the file format. + eg) "EOS DIGITAL REBEL CMOS RAW"
0x0805-0x300aUserComment256User comment (usually blank)
0x080a-0x2807CanonRawMakeModel32Two end-to-end null-terminated ASCII strings giving the camera make and model. + eg) "Canon","Canon EOS DIGITAL REBEL"
0x080b0x070x3004CanonFirmwareVersion32Firmware version. eg) "Firmware Version 1.1.1"
0x080c-?ComponentVersion?-
0x080d-0x3004ROMOperationMode4eg) The string "USA" for 300D's sold in North America
0x08100x090x2807OwnerName32Owner's name. eg) "Phil Harvey"
0x08150x060x2804CanonImageType32Type of file. eg) "CRW:EOS DIGITAL REBEL CMOS RAW"
0x0816-0x300aOriginalFileName32Original file name. eg) "CRW_1834.CRW"
0x0817-0x300aThumbnailFileName32Thumbnail file name. eg) "CRW_1834.THM"
2-Byte Alignment
0x100a-0x300aTargetImageType20=real-world subject, 1=written document
0x1010-0x3002ShutterReleaseMethod20=single shot, 1=continuous shooting
0x1011-0x3002ShutterReleaseTiming20=priority on shutter, 1=priority on focus
0x1014-0x3002-8-
0x1016-0x3002ReleaseSetting2-
0x101c-0x3004BaseISO2The camera body's base ISO sensitivity
0x1026-0x300a-6-
0x10280x030x300bCanonFlashInfo?8Unknown information, flash related
0x10290x020x300bFocalLength8Four 16 bit integers: 0) unknown, 1) focal length in mm, 2-3) sensor width + and height in units of 1/1000 inch
0x102a0x040x300bCanonShotInfovariesData block giving shot information
0x102c-0x300bCanonColorInfo2256Data block of color information (format unknown)
0x102d0x010x300bCanonCameraSettingsvariesData block giving camera settings
0x1030-0x300bWhiteSample102 or 118White sample information with encrypted 8x8 sample data
0x1031-0x300bSensorInfo34Sensor size and resolution information
0x10330x0f0x300bCanonCustomFunctionsvariesData block giving Canon custom settings
0x10380x120x300bCanonAFInfovariesData block giving AF-specific information
0x10390x130x300b?8-
0x103c-0x300b?156-
0x107f-0x300b-varies-
0x10930x930x300bCanonFileInfo18Data block giving file-specific information
0x10a80xa80x300b?20-
0x10a90xa90x300bColorBalance82Table of 16-bit integers. The first integer (like many other data blocks) is + the number of bytes in the record. This is followed by red, green1, green2 and blue + levels for WhiteBalance settings: auto, daylight, shade, cloudy, tungsten, + fluorescent, flash, custom and kelvin. The final 4 entries appear to be some sort of + baseline red, green1, green2 and blue levels.
0x10aa0xaa0x300b?10-
0x10ad-0x300b?62-
0x10ae0xae0x300bColorTemperature216-bit integer giving the color temperature
0x10af-0x300b?2-
0x10b40xb40x300bColorSpace216-bit integer specifying the color space + (1=sRGB, 2=Adobe RGB, 0xffff=uncalibrated)
0x10b50xb50x300bRawJpgInfo10Data block giving embedded JPG information
0x10c00xc00x300b?26-
0x10c10xc10x300b?26-
0x10c2-0x300b?884-
4-Byte Alignment
0x1803-0x300aImageFormat832-bit integer specifying image format (0x20001 for CRW), followed + by 32-bit float giving target compression ratio
0x1804-0x300aRecordID4The number of pictures taken since the camera was manufactured
0x1805-0x3002-8-
0x1806-0x3002SelfTimerTime432-bit integer giving self-timer time in milliseconds
0x1807-0x3002TargetDistanceSetting432-bit float giving target distance in mm
0x180b0x0c0x3004SerialNumber4The camera body number for EOS models. eg) 00560012345
0x180e-0x300aTimeStamp1232-bit integer giving the time in seconds when the picture was taken, + followed by a 32-bit timezone in seconds
0x1810-0x300aImageInfo28Data block containing image information, including rotation
0x1812-0x3004-40-
0x1813-0x3002FlashInfo8Two 32-bit floats: The flash guide number and the flash threshold
0x1814-0x3003MeasuredEV432-bit float giving the measured EV
0x18170x080x300aFileNumber432-bit integer giving the number of this file. eg) 1181834
0x1818-0x3002ExposureInfo12Three 32-bit floats: Exposure compensation, Tv, Av
0x1819-0x300b-64-
0x18340x100x300bCanonModelID4Unsigned 32-bit integer giving unique model ID
0x1835-0x300bDecoderTable16RAW decoder table information
0x183b0x150x300bSerialNumberFormat432-bit integer (0x90000000=format 1, 0xa0000000=format 2)
Mixed Data Records
0x2005-rootRawDatavariesThe raw data itself (the bulk of the CRW file)
0x2007-rootJpgFromRawvariesThe embedded JPEG image (2048x1360 pixels for the 300D with Canon firmware)
0x2008-rootThumbnailImagevariesThumbnail image (JPEG, 160x120 pixels)
SubDirectory Blocks
0x2804-0x300aImageDescriptionvariesThe image description subdirectory
0x2807-0x300aCameraObjectvariesThe camera object subdirectory
0x3002-0x300aShootingRecordvariesThe shooting record subdirectory
0x3003-0x300aMeasuredInfovariesThe measured information subdirectory
0x3004-0x2807CameraSpecificationvariesThe camera specification subdirectory
0x300a-rootImagePropsvariesThe main subdirectory containing all meta information
0x300b-0x300aExifInformationvariesThe subdirectory containing most of the JPEG/TIFF EXIF information
+ +

Revisions

+

Mar. 21, 2018 - Added mention of the CR3 file format

+

Sep. 20, 2010 - Added note about XMP support in the VRD data

+

Feb. 12, 2009 - Moved a couple of entries in the TagID table that were in +the wrong section

+

Feb. 4, 2008 - Added a number of unknown TagID's, decode some tags in the +WhiteSample and DecoderTable data blocks, and fix incorrect SubDir for +SerialNumber tag

+

Nov. 16, 2007 - Added a number of unknown blocks

+

Sep. 12, 2007 - Change name of CanonPictureInfo record to CanonAFInfo

+

Nov. 28, 2006 - Highlight the possibility of OtherData existing in the +directory block

+
+Please e-mail me if you find any errors or omissions in this document. My +address is philharvey66 at gmail.com - Thanks! +

Created Jan 28, 2005 +
Last revised Mar 21, 2018

+

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/commentary.html b/ExifTool/html/commentary.html new file mode 100644 index 0000000..6ff16d3 --- /dev/null +++ b/ExifTool/html/commentary.html @@ -0,0 +1,336 @@ + + + +Commentary on Meta Information Formats + + + + + + +

Commentary on Meta Information Formats
+(or "Why this or that Format Sucks")

+ + +

EXIF / TIFF

+

EXIF uses the TIFF format to store information. The biggest problem with +this format is that all pointers are absolute (ie. relative to the start of +the file). This means that the pointers need fixing up if the position of +any information changes. This is significant because pointers are often +hidden inside proprietary structures which should remain opaque (such as +maker notes). The result is that some EXIF information is easily corrupted +when editing a file. This problem is ignored by many software packages. +Much effort was required in the development of ExifTool to avoid the loss of +information such as this.

+ +

A significant problem is that an IFD (image file directory) pointer has +the same format code as a long integer, so information in any non-recognized +IFD is lost when the image is rewritten. There is a seldom-used format +code of 13 that has been proposed to indicate an IFD, but this is not part +of the EXIF/TIFF specification and is not in common usage. [Hey! +Olympus has just started using this code for the IFD's in the maker notes of +their new Camera models! - 2007/03/08]

+ +

Also, it was wrong to specify a count instead of a size for each +directory entry because this makes it impossible to add new format types +without requiring that older readers simply discard data of unknown type +(since the data size isn't known, the old readers don't know how to copy the +data).

+ +

All these shortcomings make TIFF a very non-extensible format which by +design will result in the loss of non-standard information.

+ +

And we don't learn from our mistakes (OK, maybe some of us can, but +apparently not big corporations like Adobe or Microsoft), because the new +DNG and WDP formats are TIFF-based too.

+ +

The EXIF specification also has some significant shortcomings to add to the +TIFF problems, like minimal special character abilities, no language support, +and no way to specify time zone.

+ + +

EXIF Maker Notes

+

Maker notes are a can of worms. The EXIF specification unfortunately +made no mention about the information format of the maker notes. If +properly designed, the specification would have provided a maker note IFD +tag to allow IFD-style maker notes to be used. Many manufacturers +implemented it this way, but each with a different technique and many with +odd quirks. This deficiency combined with +problems in the TIFF (EXIF) format mean that most software will quite +unknowingly scramble your maker notes when an image is rewritten. To +prevent this, specific knowledge about files written by individual +manufacturers is necessary, and of course the manufacturers are not prone to +divulging this information to the public so it must all be reverse +engineered.

+ +

The official TIFF recommendation is to delete unknown information when +rewriting an image[1], and this irresponsible +strategy has been adopted by some applications such as Adobe Photoshop, +resulting in a total loss of maker note information. This is very +unfortunate because even corrupted maker notes contain useful information, +and smart utilities (like ExifTool) are often able to recover the +information from damaged maker notes.

+ + +

JPEG

+

The JPEG format acts only as a container for meta information, and +defines no meta information format itself. But it sucks just a as a +container format because it limits the maximum contiguous block size to +64kB. This causes problems because many cameras store more than 64kB of +additional information in their JPEG images. Usually, this is due to the +medium-sized JPEG image that many cameras embed for preview purposes. It +would have been nice if there was a standard way to embed this preview in +JPEG images (without the 64kB limitation). [Update: In February +2009 CIPA released the MPF format specification which allows storage of +preview images larger than 64kB, but unfortunately this specification +has problems of its own.]

+ +

Aside: The EXIF specification could have easily allowed the information +to span multiple JPEG segments, which would have been smart, but still wouldn't +have solved the problem entirely since it could result in a non-contiguous +preview image (this would be a problem for cameras since they typically want to +use this image for quick review of pictures, and re-mapping the image into a +contiguous memory space would be costly). [According to the November +2014 XMP specification (part 3, page 13), Adobe allows multi-segment EXIF, and +this is supported by ExifTool 10.97 and later.]

+ + +

IPTC IIM

+

IPTC IIM earns a few points because it is the easiest to process of the +common meta information formats. However, the format is overly restrictive +and not very extensible. Format information and human-readable tag names +are not part of the specification, so it is not possible to meaningfully +interpret unknown values. Also, there are many interdependencies between +tags that make it very annoying for writing, and the special character +support is ill-conceived.

+ + +

XMP

+

The XMP format is a good example of a designed-by-committee +specification. It is based on XML, which is much more complicated than +necessary and tries to make everyone happy by providing a multitude of +format styles and features (shorthand format, etc, etc). This makes it very +difficult to properly implement the complete specification. Not even the +Adobe software supports all of the format options of their own XMP +specification. And the format is exceedingly verbose, wasting disk space, +bandwidth and time.

+ +

Also, XMP only supports textual information. Binary information must be +text-encoded if stored in XMP. This makes it very bulky, slow and +completely unsuitable for storage of binary information.

+ +

Mainly because of the complexity of the specification, and despite +Adobe's best efforts, XMP still isn't very well supported by meta +information editing tools. The best hope for small application developers +is to link against an off-the-shelf XMP or XML library. Apparently Adobe +realizes this and is now providing an XMP development kit, but this solution +is only useful for those platforms and computer languages that they choose +to support.

+ + +

PDF

+

...and I thought XMP was too complicated. PDF is just insane. Because +of this, it is unlikely that ExifTool will ever support this format for +writing. [Dec. 18, 2007 Update: I have just added PDF write support +using the incremental update feature of PDF. It was still a bit of work, but +somewhat simpler (and MUCH faster!) than rewriting the entire PDF. But there +are disadvantages: The resulting PDF is not "linearized", and there are privacy +and security issues with leaving old metadata in the file.]

+ + +

PNG

+

PNG sucks because meta information can be stored only in text format. +This was a huge mistake, and as a work-around software (eg. ImageMagick) +must encode binary information in ASCII-hex, then compress it to offset the +increase in size, and store it as a compressed text block. This adds +unnecessary complexity, greatly slows down processing, and makes it much +harder to develop software which supports PNG meta information. +[July 2017: PNG adopts eXIf chunk to allow EXIF metadata to be +stored in native binary format. This is useful, but still doesn't allow storage +of arbitrary binary metadata.]

+ + +

PICT

+

The PICT format just plain sucks. It is one of the worst-designed image +formats in existence, second only to FlashPix. I don't believe that it was +originally designed as a file format. Instead it appears to have emerged +from the internal Apple QuickDraw structures. The files are not designed to +be processed by any software other than the Mac OS. Intimate knowledge of +the QuickDraw structures is required just to parse the PICT image, even if +the reader doesn't care about the contained information. Because of this, +PICT images are very fragile and a simple data error or programming bug will +invalidate an entire image. Possibly for these reasons the PICT format +never gained popularity outside the Apple world. Also, the concept of meta +information is all but ignored in the PICT design.

+ + +

FlashPix

+

Oh. My. God. Don't get me started on this one. Let's just say that basing +an image file format on the File Allocation Table (FAT) structure of a floppy +disk with fixed 512-byte sectors is monumentally idiotic. Microsoft Word, +Excel and PowerPoint documents also use this insane format.

+ + +

DICOM Medical Images

+

The DICOM format is reasonably well designed. The UID's are cumbersome, +but achieve their purpose. The biggest bungle in the design is the implicit +VR syntax, which presumably exists for historical reasons to provide +backward compatibility with pre-existing ACR-NEMA images. The DICOM +specification document is horrible and obviously written by committee, and +was possibly the single largest impediment to implementing support for this +format.

+ +

Technical: The files would be easier to parse if the transfer syntax +became effective immediately after the transfer syntax data element instead +of at the end of the meta information group. Also, the deflated data should +have contained the zlib header, as in PNG and PDF images. This would make +it easier to read and allow CRC validation of the datastream. -- Can you +tell I wrote this just after adding DICOM support to ExifTool?

+ + +

AVCHD (.M2TS) (added 2009-10-29)

+

This format is used in .M2TS video files (and Blu-Ray HD DVD's). It is +painfully obvious that this was never designed as a storage format. It uses +MPEG-2 transport stream (M2TS) container, which is a communications protocol +and never should have been used for storage. The M2TS format is based on a +188-byte packet size which makes no sense for modern filesystems. And to +make things worse, metadata in AVCHD files is stored in the H.264 video +stream (!!) which uses insane and convoluted data structures such as the +variable-bit-length exponential-Golomb which are painful and inefficient to +parse in software, and as far as I can tell the format for the metadata +stored in these streams is proprietary and undocumented.

+ + +

ISO Considered Harmless

+

This is not a meta information format, but since many format +specifications are imprisoned by the ISO it deserves mentioning...

+ +

In my experience, the International Standards Organization is extremely +counter-productive. Their goal is to promote standardization, but by +charging too much money for copies of the standards documents they defeat +their own purpose. The effect is that small companies and individuals +developing software (including open source software such as ExifTool) do not +have access to the official standard. Instead, software is often based on +old, obsolete documents or drafts which are sometimes publicly available, or +on 3rd party descriptions which are often incomplete. To sum things up: ISO +sucks.

+ +
+

OK. So all those formats suck. What doesn't?

+ + +

Canon CRW Images (CIFF format)

+

This format uses a directory structure similar to TIFF, but all offsets +are relative and unknown information can handled properly when rewriting. +Because of this, it is much more extensible than TIFF, but the format is +still limited by 16-bit integer tag ID's among other TIFF weaknesses. It +isn't great, but at least it doesn't suck.

+ + +

QuickTime Videos

+

The Apple QuickTime MOV format is very nice format. (Which is a breath +of fresh air after the Apple PICT travesty.) It was a smart move to use +this format for the MP4 specification. I would only change 2 things about +this format: 1) Use longer tag names instead of the current 4 characters (I +know that 4 characters is convenient because it can be used as an integer in +lookup tables, but it would be nice if the tag names were a bit more +meaningful). 2) Add a format code to the Atom definition so simple unknown +information could be decoded properly. These changes would allow some +unknown information to be interpreted, which would make it possible for +information added by 3rd parties to be useful.

+ +

... but there are some significant problems with the QuickTime specification:

+ +

0) The biggest problem with some QuickTime-based files (ie. MOV, MP4, +HEIC) is that atoms may store absolute offsets into the media data, and +these pointers are easily invalidated by editing the file. This puts a huge +burdon on the editing application to update all necessary pointers, and +makes it much, much harder than it should be to edit these files (it takes +significant effort to just determine which atoms may containing pointers).

+ +

1) The metadata organization is a mess because tags may be stored in more +than one location (eg. UserData vs. InfoList), and conflicting metadata +directories may exist, even in the same location (as written by iTunes).

+ +

2) The length of 'udta' text strings with language codes is written +incorrectly by many (most?) utilities, including Apple iTunes. The language +strings are supposed to use a "small integer atom format" where the first +two bytes give the size of the string including the length word and language +codes. But often the size doesn't include these 4 bytes, which sort of +defeats the purpose of the multiple languages because it is difficult to +parse past the first entry if you don't know the size.

+ +

3) Cameras without time zone information are forced to write date/time +tags as local time, even though the specification says these should be UTC. +This leads to an unresolvable inconsistency in the time offset because +one can not distinguish between these two cases.

+ + +

My Format (Meta Information Encapsulation, or "MIE")

+

Now it is time to put my money where my mouth is: I have designed a +format which doesn't suck. It is called "MIE", which stands for Meta +Information Encapsulation.

+ +

The MIE format is hierarchical like QuickTime, but it uses meaningful tag +names and specifies a data format for all values. As well, this format +offers a number of other features:

+ +
    +
  • Can be used as a wrapper around any type of file, as a trailer appended + to other formats, or as a stand-alone meta information format +
  • Extensible +
  • Compact +
  • Meaningful tag ID's +
  • Streamable (and single-pass writing possible) +
  • Relocatable data elements (ie. no fixed offsets) +
  • Relatively simple to implement reader/writer +
  • Supports large data lengths (up to 4GB*4GB) +
  • Localized text feature +
  • Built-in support for numerical units of measure +
  • Multiple documents in a single file +
+ +

The specification for this format can be found +here, and is implemented in +Image::ExifTool::MIE.pm

+ +

OK. Flame away. I can take it.

+ + +

References

+ +
    +
  1. http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf, page 26
  2. +
+ +
+Created Nov 15, 2005
+Last revised Feb 4, 2020 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/config.html b/ExifTool/html/config.html new file mode 100644 index 0000000..8526d0a --- /dev/null +++ b/ExifTool/html/config.html @@ -0,0 +1,377 @@ + + +example.config + + + +
+#------------------------------------------------------------------------------
+# File:         example.config
+#
+# Description:  Example user configuration file for Image::ExifTool
+#
+# Notes:        This example file shows how to define your own shortcuts and
+#               add new EXIF, IPTC, XMP, PNG, MIE and Composite tags, as well
+#               as how to specify preferred lenses for the LensID tag, and
+#               define new file types and default ExifTool option values.
+#
+#               Note that unknown tags may be extracted even if they aren't
+#               defined, but tags must be defined to be written.  Also note
+#               that it is possible to override an existing tag definition
+#               with a user-defined tag.
+#
+#               To activate this file, rename it to ".ExifTool_config" and
+#               place it in your home directory or the exiftool application
+#               directory.  (On Mac and some Windows systems this must be done
+#               via the command line since the GUI's may not allow filenames to
+#               begin with a dot.  Use the "rename" command in Windows or "mv"
+#               on the Mac.)  This causes ExifTool to automatically load the
+#               file when run.  Your home directory is determined by the first
+#               defined of the following environment variables:
+#
+#                   1.  EXIFTOOL_HOME
+#                   2.  HOME
+#                   3.  HOMEDRIVE + HOMEPATH
+#                   4.  (the current directory)
+#
+#               Alternatively, the -config option of the exiftool application
+#               may be used to load a specific configuration file (note that
+#               this must be the first option on the command line):
+#
+#                   exiftool -config example.config ...
+#
+#               This example file defines the following 16 new tags as well as
+#               a number of Shortcut and Composite tags:
+#
+#                   1.  EXIF:NewEXIFTag
+#                   2.  GPS:GPSPitch
+#                   3.  GPS:GPSRoll
+#                   4.  IPTC:NewIPTCTag
+#                   5.  XMP-xmp:NewXMPxmpTag
+#                   6.  XMP-exif:GPSPitch
+#                   7.  XMP-exif:GPSRoll
+#                   8.  XMP-xxx:NewXMPxxxTag1
+#                   9.  XMP-xxx:NewXMPxxxTag2
+#                  10.  XMP-xxx:NewXMPxxxTag3
+#                  11.  XMP-xxx:NewXMPxxxStruct
+#                  12.  PNG:NewPngTag1
+#                  13.  PNG:NewPngTag2
+#                  14.  PNG:NewPngTag3
+#                  15.  MIE-Meta:NewMieTag1
+#                  16.  MIE-Test:NewMieTag2
+#
+#               For detailed information on the definition of tag tables and
+#               tag information hashes, see lib/Image/ExifTool/README.
+#------------------------------------------------------------------------------
+
+# Shortcut tags are used when extracting information to simplify
+# commonly used commands.  They can be used to represent groups
+# of tags, or to provide an alias for a tag name.
+%Image::ExifTool::UserDefined::Shortcuts = (
+    MyShortcut => ['exif:createdate','exposuretime','aperture'],
+    MyAlias => 'FocalLengthIn35mmFormat',
+);
+
+# NOTE: All tag names used in the following tables are case sensitive.
+
+# The %Image::ExifTool::UserDefined hash defines new tags to be added
+# to existing tables.
+%Image::ExifTool::UserDefined = (
+    # All EXIF tags are added to the Main table, and WriteGroup is used to
+    # specify where the tag is written (default is ExifIFD if not specified):
+    'Image::ExifTool::Exif::Main' => {
+        # Example 1.  EXIF:NewEXIFTag
+        0xd000 => {
+            Name => 'NewEXIFTag',
+            Writable => 'int16u',
+            WriteGroup => 'IFD0',
+        },
+        # add more user-defined EXIF tags here...
+    },
+    # the Geotag feature writes these additional GPS tags if available:
+    'Image::ExifTool::GPS::Main' => {
+        # Example 2.  GPS:GPSPitch
+        0xd000 => {
+            Name => 'GPSPitch',
+            Writable => 'rational64s',
+        },
+        # Example 3.  GPS:GPSRoll
+        0xd001 => {
+            Name => 'GPSRoll',
+            Writable => 'rational64s',
+        },
+    },
+    # IPTC tags are added to a specific record type (eg. application record):
+    # (Note: IPTC tag ID's are limited to the range 0-255)
+    'Image::ExifTool::IPTC::ApplicationRecord' => {
+        # Example 4.  IPTC:NewIPTCTag
+        160 => {
+            Name => 'NewIPTCTag',
+            Format => 'string[0,16]',
+        },
+        # add more user-defined IPTC ApplicationRecord tags here...
+    },
+    # XMP tags may be added to existing namespaces:
+    'Image::ExifTool::XMP::xmp' => {
+        # Example 5.  XMP-xmp:NewXMPxmpTag
+        NewXMPxmpTag => { Groups => { 2 => 'Author' } },
+        # add more user-defined XMP-xmp tags here...
+    },
+    # special Geotag tags for XMP-exif:
+    'Image::ExifTool::XMP::exif' => {
+        # Example 6.  XMP-exif:GPSPitch
+        GPSPitch => { Writable => 'rational', Groups => { 2 => 'Location' } },
+        # Example 7.  XMP-exif:GPSRoll
+        GPSRoll  => { Writable => 'rational', Groups => { 2 => 'Location' } },
+    },
+    # new XMP namespaces (eg. xxx) must be added to the Main XMP table:
+    'Image::ExifTool::XMP::Main' => {
+        # namespace definition for examples 8 to 11
+        xxx => { # <-- must be the same as the NAMESPACE prefix
+            SubDirectory => {
+                TagTable => 'Image::ExifTool::UserDefined::xxx',
+                # (see the definition of this table below)
+            },
+        },
+        # add more user-defined XMP namespaces here...
+    },
+    # new PNG tags are added to the PNG::TextualData table:
+    'Image::ExifTool::PNG::TextualData' => {
+        # Example 12.  PNG:NewPngTag1
+        NewPngTag1 => { },
+        # Example 13.  PNG:NewPngTag2
+        NewPngTag2 => { iTXt => 1 }, # (force this tag to be written as iTXt)
+        # Example 14.  PNG:NewPngTag3
+        NewPngTag3 => { },
+    },
+    # add a new MIE tag (NewMieTag1) and group (MIE-Test) to MIE-Meta
+    # (Note: MIE group names must NOT end with a number)
+    'Image::ExifTool::MIE::Meta' => {
+        # Example 15.  MIE-Meta:NewMieTag1
+        NewMieTag1 => {
+            Writable => 'rational64u',
+            Units => [ 'cm', 'in' ],
+        },
+        # new MIE "Test" group for example 16
+        Test => {
+            SubDirectory => {
+                TagTable => 'Image::ExifTool::UserDefined::MIETest',
+                DirName => 'MIE-Test',
+            },
+        },
+    },
+    # Composite tags are added to the Composite table:
+    'Image::ExifTool::Composite' => {
+        # Composite tags have values that are derived from the values of
+        # other tags.  The Require/Desire elements below specify constituent
+        # tags that must/may exist, and the keys of these hashes are used as
+        # indices in the @val array of the ValueConv expression to access the
+        # numerical (-n) values of these tags.  Print-converted values are
+        # accessed via the @prt array.  All Require'd tags must exist for
+        # the Composite tag to be evaluated.  If no Require'd tags are
+        # specified, then at least one of the Desire'd tags must exist.  See
+        # the Composite table in Image::ExifTool::Exif for more examples,
+        # and lib/Image/ExifTool/README for all of the details.
+        BaseName => {
+            Require => {
+                0 => 'FileName',
+            },
+            # remove the extension from FileName
+            ValueConv => '$val[0] =~ /(.*)\./ ? $1 : $val[0]',
+        },
+        # the next few examples demonstrate simplifications which may be
+        # used if only one tag is Require'd or Desire'd:
+        # 1) the Require lookup may be replaced with a simple tag name
+        # 2) "$val" may be used to represent "$val[0]" in the expression
+        FileExtension => {
+            Require => 'FileName',
+            ValueConv => '$val=~/\.([^.]*)$/; $1',
+        },
+        # override CircleOfConfusion tag to use D/1750 instead of D/1440
+        CircleOfConfusion => {
+            Require => 'ScaleFactor35efl',
+            Groups => { 2 => 'Camera' },
+            ValueConv => 'sqrt(24*24+36*36) / ($val * 1750)',
+            # an optional PrintConv may be used to format the value
+            PrintConv => 'sprintf("%.3f mm",$val)',
+        },
+        # generate a description for this file type
+        FileTypeDescription => {
+            Require => 'FileType',
+            ValueConv => 'GetFileType($val,1) || $val',
+        },
+        # calculate physical image size based on resolution
+        PhysicalImageSize => {
+            Require => {
+                0 => 'ImageWidth',
+                1 => 'ImageHeight',
+                2 => 'XResolution',
+                3 => 'YResolution',
+                4 => 'ResolutionUnit',
+            },
+            ValueConv => '$val[0]/$val[2] . " " . $val[1]/$val[3]',
+            # (the @prt array contains print-formatted values)
+            PrintConv => 'sprintf("%.1fx%.1f $prt[4]", split(" ",$val))',
+        },
+        # [advanced] select largest JPEG preview image
+        BigImage => {
+            Groups => { 2 => 'Preview' },
+            Desire => {
+                0 => 'JpgFromRaw',
+                1 => 'PreviewImage',
+                2 => 'OtherImage',
+                # (DNG and A100 ARW may be have 2 PreviewImage's)
+                3 => 'PreviewImage (1)',
+                # (if the MPF has 2 previews, MPImage3 could be the larger one)
+                4 => 'MPImage3',
+            },
+            # ValueConv may also be a code reference
+            # Inputs: 0) reference to list of values, 1) ExifTool object
+            ValueConv => sub {
+                my $val = shift;
+                my ($image, $bigImage, $len, $bigLen);
+                foreach $image (@$val) {
+                    next unless ref $image eq 'SCALAR';
+                    # check for JPEG image (or "Binary data" if -b not used)
+                    next unless $$image =~ /^(\xff\xd8\xff|Binary data (\d+))/;
+                    $len = $2 || length $$image; # get image length
+                    # save largest image
+                    next if defined $bigLen and $bigLen >= $len;
+                    $bigLen = $len;
+                    $bigImage = $image;
+                }
+                return $bigImage;
+            },
+        },
+        # **** ADD ADDITIONAL COMPOSITE TAG DEFINITIONS HERE ****
+    },
+);
+
+# This is a basic example of the definition for a new XMP namespace.
+# This table is referenced through a SubDirectory tag definition
+# in the %Image::ExifTool::UserDefined definition above.
+# The namespace prefix for these tags is 'xxx', which corresponds to
+# an ExifTool family 1 group name of 'XMP-xxx'.
+%Image::ExifTool::UserDefined::xxx = (
+    GROUPS => { 0 => 'XMP', 1 => 'XMP-xxx', 2 => 'Image' },
+    NAMESPACE => { 'xxx' => 'http://ns.myname.com/xxx/1.0/' },
+    WRITABLE => 'string', # (default to string-type tags)
+    # Example 8.  XMP-xxx:NewXMPxxxTag1 (an alternate-language tag)
+    # - replace "NewXMPxxxTag1" with your own tag name (eg. "MyTag")
+    NewXMPxxxTag1 => { Writable => 'lang-alt' },
+    # Example 9.  XMP-xxx:NewXMPxxxTag2 (a string tag in the Author category)
+    NewXMPxxxTag2 => { Groups => { 2 => 'Author' } },
+    # Example 10.  XMP-xxx:NewXMPxxxTag3 (an unordered List-type tag)
+    NewXMPxxxTag3 => { List => 'Bag' },
+    # Example 11.  XMP-xxx:NewXMPxxxStruct (a structured tag)
+    # - example structured XMP tag
+    NewXMPxxxStruct => {
+        # the "Struct" entry defines the structure fields
+        Struct => {
+            # optional namespace prefix and URI for structure fields
+            # (required only if different than NAMESPACE above)
+            # --> multiple entries may exist in this namespace lookup,
+            # with the last one alphabetically being the default for
+            # the fields, but each field may have a "Namespace"
+            # element to specify which prefix to use
+            NAMESPACE => { 'test' => 'http://x.y.z/test/' },
+            # optional structure name (used for warning messages only)
+            STRUCT_NAME => 'MyStruct',
+            # optional rdf:type property for the structure
+            TYPE => 'http://x.y.z/test/xystruct',
+            # structure fields (very similar to tag definitions)
+            X => { Writable => 'integer' },
+            Y => { Writable => 'integer' },
+            # a nested structure...
+            Things => {
+                List => 'Bag',
+                Struct => {
+                    NAMESPACE => { thing => 'http://x.y.z/thing/' },
+                    What  => { },
+                    Where => { },
+                },
+            },
+        },
+        List => 'Seq', # structures may also be elements of a list
+    },
+    # Each field in the structure has a corresponding flattened tag that is
+    # generated automatically with an ID made from a concatenation of the
+    # original structure tag ID and the field name (after capitalizing the
+    # first letter of the field name if necessary).  The Name and/or
+    # Description of these flattened tags may be changed if desired, but all
+    # other tag properties are taken from the structure field definition.
+    # The "Flat" flag must be used when setting the Name or Description of a
+    # flattened tag.  For example:
+    NewXMPxxxStructX => { Name => 'SomeOtherName', Flat => 1 },
+);
+
+# Adding a new MIE group requires a few extra definitions
+use Image::ExifTool::MIE;
+%Image::ExifTool::UserDefined::MIETest = (
+    %Image::ExifTool::MIE::tableDefaults,   # default MIE table entries
+    GROUPS      => { 0 => 'MIE', 1 => 'MIE-Test', 2 => 'Document' },
+    WRITE_GROUP => 'MIE-Test',
+    # Example 16.  MIE-Test:NewMieTag2
+    NewMieTag2  => { },     # new user-defined tag in MIE-Test group
+);
+
+# A special 'Lenses' list can be defined to give priority to specific lenses
+# in the logic to determine a lens model for the Composite:LensID tag
+@Image::ExifTool::UserDefined::Lenses = (
+    'Sigma AF 10-20mm F4-5.6 EX DC',
+    'Tokina AF193-2 19-35mm f/3.5-4.5',
+);
+
+# User-defined file types to recognize
+%Image::ExifTool::UserDefined::FileTypes = (
+    XXX => { # <-- the extension of the new file type (case insensitive)
+        # BaseType specifies the format upon which this file is based (case
+        # sensitive).  If BaseType is defined, then the file will be fully
+        # supported, and in this case the Magic pattern should not be defined
+        BaseType => 'TIFF',
+        MIMEType => 'image/x-xxx',
+        Description => 'My XXX file type',
+        # if the BaseType is writable by ExifTool, then the new file type
+        # will also be writable unless otherwise specified, like this:
+        Writable => 0,
+    },
+    YYY => {
+        # without BaseType, the file will be recognized but not supported
+        Magic => '0123abcd',    # regular expression to match at start of file
+        MIMEType => 'application/test',
+        Description => 'My YYY file type',
+    },
+    ZZZ => {
+        # if neither BaseType nor Magic are defined, the file will be
+        # recognized by extension only.  MIMEType will be application/unknown
+        # unless otherwise specified
+        Description => 'My ZZZ file type',
+    },
+    # if only BaseType is specified, then the following simplified syntax
+    # may be used.  In this example, files with extension "TTT" will be
+    # processed as JPEG files
+    TTT => 'JPEG',
+);
+
+# Change default location for writing QuickTime tags so Keys is preferred
+# (by default, the PREFERRED levels are: ItemList=2, UserData=1, Keys=0)
+use Image::ExifTool::QuickTime;
+$Image::ExifTool::QuickTime::Keys{PREFERRED} = 3;
+
+# Specify default ExifTool option values
+# (see the Options function documentation for available options)
+%Image::ExifTool::UserDefined::Options = (
+    CoordFormat => '%.6f',  # change default GPS coordinate format
+    Duplicates => 1,        # make -a default for the exiftool app
+    GeoMaxHDOP => 4,        # ignore GPS fixes with HDOP > 4
+    RequestAll => 3,        # request additional tags not normally generated
+);
+
+#------------------------------------------------------------------------------
+1;  #end
+
+
+

# <-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/data_members.html b/ExifTool/html/data_members.html new file mode 100644 index 0000000..e88b9a6 --- /dev/null +++ b/ExifTool/html/data_members.html @@ -0,0 +1,323 @@ + + + +ExifTool Object Data Members + + + +

ExifTool Object Data Members

+ +

The following table lists the data members of the ExifTool object. (If this +was C++, these would be documented next to the definitions in the header file, +but this is Perl, so I couldn't do that...)

+ +

Aside from the variables defined below, there may be other temporary +variables added by individual modules. Temporary variables are identified by at +least one lower case letter in their name, and are deleted automatically before +reading or writing each new image. Make, Model and CameraType are temporary +variables like this, but these three are special because they are initialized to +an empty string instead of being deleted at the start of processing for each +image, which allows them to be tested without having to check for an undefined +value.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
ADD_DIRSHash of parent directories for each directory added during + writing (Note: entry may exist but be undefined for top-level + directory!) To avoid adding a directory twice, entries may be + deleted from this lookup, but only AFTER WriteDirectory() is called.
BASEAbsolute position of current image in containing file + (for example, when processing JPEG image in FujiFilm RAF file)
BOTHReference to HASH of ValueConv/PrintConv arrays, saved for + tags when GetValue is called for 'Both' values, and used to avoid having to + recalculate both values again (which speeds up building the Composite tags + because often the same tag is Require'd by multiple Composite tags).
CameraTypeCamera type used for decoding some Olympus tags
CHANGEDNumber of tags changed during writing
CHARSETLookup table for current Charset setting
CHECK_WARNUsed to return CHECK_PROC warnings
COMP_KEYSLookup for tag keys used in Composite tags
CompressionCompression for current EXIF IFD (or '' if no Compression tag). + Valid only in EXIF directories for tags after 0x0103.
CUR_LANGHash reference for localized language lookup corresponding + to current 'Lang' options setting.
CUR_WRITE_GROUPCurrent family 1 group while writing. Currently + used only when writing QuickTime tracks.
DEL_GROUPHash of groups to delete. Value is 1 to delete group + completely, or 2 if tags were added back after deleting the group. May + have a leading "-" if excluded from a delete.
DEL_PREVIEWFlag to delete preview from after JPEG EOI
DemoteErrorsErrors are demoted to warnings and this count is + incremented if non-zero to begin with
DIR_COUNTHash reference for counters of various types of directories (ICC, IPTC,...)
DIR_NAMEName of directory currently being processed
DOC_COUNTNumber of top-level embedded sub-documents processed while + extracting, or 0 if none were processed
DOC_NUMCurrent document number while extracting (0, '' or undef for the + main document). May contain a hyphen for sub-subdocuments (eg. '2-3-2').
DoneID3Set to 1 if audio file was scanned for ID3, + or 2 if there was an ID3v1 trailer found
DropTagsFlag to exclude "Drop" tags when writing
DUPL_TAGHash of last used tag key index numbers for each duplicate tag.
EDIT_DIRSHash of parent directories for each directory edited during + writing (Note: entry may exist but be undefined for top-level + directory!)
EndUsed by application to signal end of processing
EndDirUsed by application to signal end of the current directory
ESCAPE_PROCRoutine for XML or HTML escaping if Escape option is set
EXCL_TAG_LOOKUPHash for looking up excluded tags (keys are lower case tag names without group)
EXCL_XMP_LOOKUPHash for looking up excluded "XMP-xxx:yyy" tags (keys are lower case including group). + "XMP-xxx" may be any ExifTool family 1 XMP group name, or "XMP-all" for any XMP group. "yyy" may + be any XMP property name (NOT ExifTool tag name), or "all" for all tags in a group
EXIF_DATAEXIF data block (valid while reading from file)
EXIF_POSPosition of EXIF data in file
FILENAMEName of input file while extracting (only if we opened it, '' otherwise)
FILE_ORDERHash of numbers to give the sequence the tags were + extracted from file, keyed by tag key
FILE_EXTUpper-case file extension (JPG, PEF, AVI, etc...) May be undefined if + file has no extension.
FILE_SEQUENCEFile sequence number when extracting information
FILE_TYPERoot file format type (JPEG, TIFF, RIFF, etc...)
FileTypeActual identified file type
FIRST_EXIF_POSPosition of first EXIF in file
FMT_EXPRThe current advanced formatting expression when interpolating tag + values in a string. This member allows the formatting expression to be accessed in + the ValueConv function of user-defined Composite tags, which provided a mechanism for + arguments to be passed to the ValueConv function.
FORCE_WRITEHash reference for metadata types that are being forced to be + rewritten (see Extra ForceWrite tag)
FOUND_DIRNames of directories found in file (used by Validate feature)
FOUND_TAGSFound tags with proper case and order
GLOBAL_TIME_OFFSETTime offset hash for first shifted date/time value of GlobalTimeShift option
HTML_DUMPReference to HtmlDump object when using HtmlDump feature
INDENTIndent string for verbose output
INDENT2Indent used in verbose SetNewValue output before writing
IN_RESOURCEFlag indicates we are processing the resource fork of a Mac OS file
IO_TAG_LISTList for output of requested tags in proper case and order
IsWritingFlag set to indicate that metadata is being written to this file
LIST_TAGSHash of tag keys by tagInfo reference for active list-type + tags. This hash is cleared at the start of processing for each directory. + Not used during when writing.
LOW_PRIORITY_DIRLookup based on directory name for directories where + undefined Priority tags get set to Priority 0 (eg. PreviewIFD in all images, + and IFD1 in JPEG images). This is equivalent to setting PRIORITY to 0 for + the tag table only for a specific directory name. A '*' entry in the lookup + causes the priority of tags in any directory to be lowered.
MakeCamera make set during reading and writing
MAKER_NOTE_BYTE_ORDERByte order of extracted maker notes if extracted + as a block.
MAKER_NOTE_INFOUsed by Image::ExifTool::CanonRaw as temporary storage for + maker note information when synthesizing maker notes from CRW file with the + MakerNotes option.
MAKER_NOTE_FIXUPFixup information for maker notes extracted with the MakerNotes + option (unless MakerNotes option is 2). Used by SetNewValuesFromFile() to adjust offsets + in maker note IFD
ModelCamera model name set during reading and writing
MOVED_KEYTag key of last tag replaced by a higher priority tag + in a call to FoundTag().
NEW_VALUEHash of new value information keyed by tag info reference. + Elements of new value information hash are: + + + + + + +
TagInfo-tag information reference
DelValue-list of specific values to delete
Value-list of values to add
IsCreating-must be set for the tag to be added, otherwise just + changed if it already exists. A value of 2 will create a new tag if its group already + exists, but won't add a new group
Next-reference to next new value hash in linked list + if there is more than one new value hash (for writing to different groups) for this + tagInfo
Save-flag set by call to SaveNewValues() if this value + should be saved
NewIPTCDigestMD5 digest for most recently written IPTC data (undefined if IPTC doesn't + exist or if it was deleted). Calculated only if a value of 'new' is being written/deleted + to/from the Photoshop:IPTCDigest tag
NO_LISTSet if List tags shouldn't be accumulated into a list. Undefined to + accumulate lists as usual, or 0 to accumulate lists in TAG_EXTRA "NoList" element (and + set a TAG_EXTRA "NoListDel" flag for tags that wouldn't have been created).
NO_STRUCTSet if structures shouldn't be restored for this metadata. Used + to avoid restoring structures for non-XMP (eg. XML) information.
NO_STRUCT_WARNSet if structures warnings should be ignored when rebuilding + structures (for questionable XML).
NO_UNKNOWNDon't generate unknown tags automatically even if Unknown option is set
NUM_FOUNDNumber of tags found
OldIPTCDigestMD5 digest for original IPTC data (undefined if IPTC didn't + exist). Calculated only if a value of 'old' is being written/deleted to/from the + Photoshop:IPTCDigest tag
OPTIONSExifTool options
PATHList reference to SubDirectory path. eg) + [qw(JPEG APP13 Photoshop IPTC)]
PDF_CAPTUREUsed by PDF module to store information when writing
PreferredGroupPreferred family 1 group for writing tags to the current file. + Not defined to use the default group priorities. Set in call to InitWriteDirs().
PREVIEW_INFOInformation about preview image used while writing JPEG + images only. This is a reference to a hash with the following entries: + + + + + + + + +
Absolute-flag set if PreviewImage pointer is relative to the + start of file
BaseShift-shift for base of pointer offsets
Data-data for PreviewImage to be written (may either be data + for the old or new image, or 'LOAD_PREVIEW' to indicate that the data must be loaded from outside + the EXIF segment)
Fixup-[mandatory] fixup for PreviewImage pointers when writing JPEG images
IsShort-flag indicates PreviewImage pointers + are 2-byte (instead of 4-byte) integers
IsTrailer-flag indicates image should always be written as a JPEG + trailer, even if it fits inside the APP1 EXIF segment (as with Samsung STMN maker notes)
Relative-flag to indicate that pointer offsets are relative
PRIORITYHash of tag Priority values for current tag, keyed by tag name (not tag key!)
PRIORITY_DIRName of priority directory (ie. the IFD for the full resolution image)
PROCESSEDReference to hash of address/directory names which were processed + during reading and/or writing
RAFRandom access file object for reading from input file
RATIONALHash of rational values as a string fraction keyed by tag key
RecreatedDirectories recreated when writing (used to prevent recreating + multiple directories of the same type)
REQUESTED_TAGSList of requested tags in original case
REQ_TAG_LOOKUPHash for looking up requested tags (keys are lower case tag names)
SAVE_COUNTCounts number of times SaveNewValues() has been called
SAVE_DEL_GROUPHash of saved DEL_GROUP entries
SAVE_NEW_VALUEHash of new value information which was overwritten after + a call to SaveNewValues()
SaveFormatHash reference to check which TIFF format types were used + in the file. Valid only if the (undocumented) SaveFormat option is used.
SET_GROUP0Value to use for family 0 group name when new tags + are found
SET_GROUP1Value to use for family 1 group name when new tags + are found. May start with a '+' sign to add to existing group name
SubfileTypeSubfileType for current EXIF IFD (or '' if no SubfileType tag). + Valid only in EXIF directories for tags after 0x00fe.
TAG_EXTRAHash of extra tag information keyed by tag key. Current extra +information may include: + + + + + + + + + +
G0-override for family 0 group name
G1-dynamic family 1 group name (eg. IFD name or XMP + namespace), with a leading '+' to add value to the existing group 1 name
G3-number for embedded documents (eg. 1, 2, etc)
G#-family # group name
Units-used internally to store units for GoPro values
Struct-used internally to store structure properties
NoList-used internally to store values for List-type tags
NoListDel-flag used internally to delete value if included in another list
TAG_INFOHash of tagInfo hash references, keyed by tag key
TAGS_FROM_FILEFlag used during extraction when called from SetNewValuesFromFile(). + Test this flag when extracting tags that are not normally extracted.
TIFF_ENDLocation of end of regular TIFF file set by WriteExif()
TIFF_TYPEType of TIFF data, only set if FILE_TYPE is TIFF. Valid values + are APP1, APP3, TIFF, CR2, MRW, JP2, JPX, NEF, PEF, ORF, DNG, etc.
ValidateFlag to do extra validation checks
VALUEHash of raw tag values keyed by tag key
WARNED_ONCEHash of warning messages that have been issued once already
WRITE_GROUPSList of write groups in order of priority
WRITE_PRIORITYHash of write priority values keyed by group name
WRITTENHash of tags written (currently used only for filesystem date/time tags)
XMP_CAPTUREHash of tag information (value, attribute hash) keyed by XMP + property path, used by XMP module during writing
XMP_ERRORError string used by XMP module during writing
XMP_NSHash of URI's keyed by namespace prefix, used by XMP module + during writing to remember all used namespaces
+
+

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/exiftool_pod.html b/ExifTool/html/exiftool_pod.html new file mode 100644 index 0000000..511b8fe --- /dev/null +++ b/ExifTool/html/exiftool_pod.html @@ -0,0 +1,2071 @@ + + + + +exiftool Application Documentation + + + + + + + +
+ exiftool Application Documentation +
+ + + + + +

NAME

+ +

exiftool - Read and write meta information in files

+ +

SYNOPSIS

+ +

Reading

+ +

exiftool [OPTIONS] [-TAG...] [--TAG...] FILE...

+ +

Writing

+ +

exiftool [OPTIONS] -TAG[+-<]=[VALUE]... FILE...

+ +

Copying

+ +

exiftool [OPTIONS] -tagsFromFile SRCFILE [-[DSTTAG<]SRCTAG...] FILE...

+ +

Other

+ +

exiftool [ -ver | -list[w|f|r|wf|g[NUM]|d|x] ]

+ +

For specific examples, see the EXAMPLES sections below.

+ +

This documentation is displayed if exiftool is run without an input FILE when one is expected.

+ +

DESCRIPTION

+ +

A command-line interface to Image::ExifTool, used for reading and writing meta information in a variety of file types. FILE is one or more source file names, directory names, or - for the standard input. Metadata is read from source files and printed in readable form to the console (or written to output text files with -w).

+ +

To write or delete metadata, tag values are assigned using -TAG=[VALUE], and/or the -geotag, -csv= or -json= options. To copy or move metadata, the -tagsFromFile feature is used. By default the original files are preserved with _original appended to their names -- be sure to verify that the new files are OK before erasing the originals. Once in write mode, exiftool will ignore any read-specific options.

+ +

Note: If FILE is a directory name then only supported file types in the directory are processed (in write mode only writable types are processed). However, files may be specified by name, or the -ext option may be used to force processing of files with any extension. Hidden files in the directory are also processed. Adding the -r option causes subdirectories to be processed recursively, but subdirectories with names beginning with "." are skipped unless -r. is used.

+ +

Below is a list of file types and meta information formats currently supported by ExifTool (r = read, w = write, c = create):

+ +
  File Types
+  ------------+-------------+-------------+-------------+------------
+  360   r/w   | DPX   r     | ITC   r     | NUMBERS r   | RAW   r/w
+  3FR   r     | DR4   r/w/c | J2C   r     | O     r     | RIFF  r
+  3G2   r/w   | DSS   r     | JNG   r/w   | ODP   r     | RSRC  r
+  3GP   r/w   | DV    r     | JP2   r/w   | ODS   r     | RTF   r
+  7Z    r     | DVB   r/w   | JPEG  r/w   | ODT   r     | RW2   r/w
+  A     r     | DVR-MS r    | JSON  r     | OFR   r     | RWL   r/w
+  AA    r     | DYLIB r     | JXL   r     | OGG   r     | RWZ   r
+  AAE   r     | EIP   r     | K25   r     | OGV   r     | RM    r
+  AAX   r/w   | EPS   r/w   | KDC   r     | ONP   r     | SEQ   r
+  ACR   r     | EPUB  r     | KEY   r     | OPUS  r     | SKETCH r
+  AFM   r     | ERF   r/w   | LA    r     | ORF   r/w   | SO    r
+  AI    r/w   | EXE   r     | LFP   r     | ORI   r/w   | SR2   r/w
+  AIFF  r     | EXIF  r/w/c | LIF   r     | OTF   r     | SRF   r
+  APE   r     | EXR   r     | LNK   r     | PAC   r     | SRW   r/w
+  ARQ   r/w   | EXV   r/w/c | LRV   r/w   | PAGES r     | SVG   r
+  ARW   r/w   | F4A/V r/w   | M2TS  r     | PBM   r/w   | SWF   r
+  ASF   r     | FFF   r/w   | M4A/V r/w   | PCD   r     | THM   r/w
+  AVI   r     | FITS  r     | MACOS r     | PCX   r     | TIFF  r/w
+  AVIF  r/w   | FLA   r     | MAX   r     | PDB   r     | TORRENT r
+  AZW   r     | FLAC  r     | MEF   r/w   | PDF   r/w   | TTC   r
+  BMP   r     | FLIF  r/w   | MIE   r/w/c | PEF   r/w   | TTF   r
+  BPG   r     | FLV   r     | MIFF  r     | PFA   r     | TXT   r
+  BTF   r     | FPF   r     | MKA   r     | PFB   r     | VCF   r
+  CHM   r     | FPX   r     | MKS   r     | PFM   r     | VNT   r
+  COS   r     | GIF   r/w   | MKV   r     | PGF   r     | VRD   r/w/c
+  CR2   r/w   | GLV   r/w   | MNG   r/w   | PGM   r/w   | VSD   r
+  CR3   r/w   | GPR   r/w   | MOBI  r     | PLIST r     | WAV   r
+  CRM   r/w   | GZ    r     | MODD  r     | PICT  r     | WDP   r/w
+  CRW   r/w   | HDP   r/w   | MOI   r     | PMP   r     | WEBP  r/w
+  CS1   r/w   | HDR   r     | MOS   r/w   | PNG   r/w   | WEBM  r
+  CSV   r     | HEIC  r/w   | MOV   r/w   | PPM   r/w   | WMA   r
+  CUR   r     | HEIF  r/w   | MP3   r     | PPT   r     | WMV   r
+  CZI   r     | HTML  r     | MP4   r/w   | PPTX  r     | WPG   r
+  DCM   r     | ICC   r/w/c | MPC   r     | PS    r/w   | WTV   r
+  DCP   r/w   | ICO   r     | MPG   r     | PSB   r/w   | WV    r
+  DCR   r     | ICS   r     | MPO   r/w   | PSD   r/w   | X3F   r/w
+  DFONT r     | IDML  r     | MQV   r/w   | PSP   r     | XCF   r
+  DIVX  r     | IIQ   r/w   | MRC   r     | QTIF  r/w   | XLS   r
+  DJVU  r     | IND   r/w   | MRW   r/w   | R3D   r     | XLSX  r
+  DLL   r     | INSP  r/w   | MXF   r     | RA    r     | XMP   r/w/c
+  DNG   r/w   | INSV  r     | NEF   r/w   | RAF   r/w   | ZIP   r
+  DOC   r     | INX   r     | NKSC  r/w   | RAM   r     |
+  DOCX  r     | ISO   r     | NRW   r/w   | RAR   r     |
+
+  Meta Information
+  ----------------------+----------------------+---------------------
+  EXIF           r/w/c  |  CIFF           r/w  |  Ricoh RMETA    r
+  GPS            r/w/c  |  AFCP           r/w  |  Picture Info   r
+  IPTC           r/w/c  |  Kodak Meta     r/w  |  Adobe APP14    r
+  XMP            r/w/c  |  FotoStation    r/w  |  MPF            r
+  MakerNotes     r/w/c  |  PhotoMechanic  r/w  |  Stim           r
+  Photoshop IRB  r/w/c  |  JPEG 2000      r    |  DPX            r
+  ICC Profile    r/w/c  |  DICOM          r    |  APE            r
+  MIE            r/w/c  |  Flash          r    |  Vorbis         r
+  JFIF           r/w/c  |  FlashPix       r    |  SPIFF          r
+  Ducky APP12    r/w/c  |  QuickTime      r    |  DjVu           r
+  PDF            r/w/c  |  Matroska       r    |  M2TS           r
+  PNG            r/w/c  |  MXF            r    |  PE/COFF        r
+  Canon VRD      r/w/c  |  PrintIM        r    |  AVCHD          r
+  Nikon Capture  r/w/c  |  FLAC           r    |  ZIP            r
+  GeoTIFF        r/w/c  |  ID3            r    |  (and more)
+ +

OPTIONS

+ +

Case is not significant for any command-line option (including tag and group names), except for single-character options when the corresponding upper-case option exists. Many single-character options have equivalent long-name versions (shown in brackets), and some options have inverses which are invoked with a leading double-dash. Unrecognized options are interpreted as tag names (for this reason, multiple single-character options may NOT be combined into one argument). Contrary to standard practice, options may appear after source file names on the exiftool command line.

+ +

Option Overview

+ +

Tag operations

+ +
  -TAG or --TAG                    Extract or exclude specified tag
+  -TAG[+-^]=[VALUE]                Write new value for tag
+  -TAG[+-]<=DATFILE                Write tag value from contents of file
+  -[+]TAG[+-]<SRCTAG               Copy tag value (see -tagsFromFile)
+
+  -tagsFromFile SRCFILE            Copy tag values from file
+  -x TAG      (-exclude)           Exclude specified tag
+ +

Input-output text formatting

+ +
  -args       (-argFormat)         Format metadata as exiftool arguments
+  -b          (-binary)            Output metadata in binary format
+  -c FMT      (-coordFormat)       Set format for GPS coordinates
+  -charset [[TYPE=]CHARSET]        Specify encoding for special characters
+  -csv[[+]=CSVFILE]                Export/import tags in CSV format
+  -csvDelim STR                    Set delimiter for CSV file
+  -d FMT      (-dateFormat)        Set format for date/time values
+  -D          (-decimal)           Show tag ID numbers in decimal
+  -E,-ex,-ec  (-escape(HTML|XML|C))Escape tag values for HTML, XML or C
+  -f          (-forcePrint)        Force printing of all specified tags
+  -g[NUM...]  (-groupHeadings)     Organize output by tag group
+  -G[NUM...]  (-groupNames)        Print group name for each tag
+  -h          (-htmlFormat)        Use HTML formatting for output
+  -H          (-hex)               Show tag ID numbers in hexadecimal
+  -htmlDump[OFFSET]                Generate HTML-format binary dump
+  -j[[+]=JSONFILE] (-json)         Export/import tags in JSON format
+  -l          (-long)              Use long 2-line output format
+  -L          (-latin)             Use Windows Latin1 encoding
+  -lang [LANG]                     Set current language
+  -listItem INDEX                  Extract specific item from a list
+  -n          (--printConv)        No print conversion
+  -p FMTFILE  (-printFormat)       Print output in specified format
+  -php                             Export tags as a PHP Array
+  -s[NUM]     (-short)             Short output format
+  -S          (-veryShort)         Very short output format
+  -sep STR    (-separator)         Set separator string for list items
+  -sort                            Sort output alphabetically
+  -struct                          Enable output of structured information
+  -t          (-tab)               Output in tab-delimited list format
+  -T          (-table)             Output in tabular format
+  -v[NUM]     (-verbose)           Print verbose messages
+  -w[+|!] EXT (-textOut)           Write (or overwrite!) output text files
+  -W[+|!] FMT (-tagOut)            Write output text file for each tag
+  -Wext EXT   (-tagOutExt)         Write only specified file types with -W
+  -X          (-xmlFormat)         Use RDF/XML output format
+ +

Processing control

+ +
  -a          (-duplicates)        Allow duplicate tags to be extracted
+  -e          (--composite)        Do not generate composite tags
+  -ee[NUM]    (-extractEmbedded)   Extract information from embedded files
+  -ext[+] EXT (-extension)         Process files with specified extension
+  -F[OFFSET]  (-fixBase)           Fix the base for maker notes offsets
+  -fast[NUM]                       Increase speed when extracting metadata
+  -fileOrder[NUM] [-]TAG           Set file processing order
+  -i DIR      (-ignore)            Ignore specified directory name
+  -if[NUM] EXPR                    Conditionally process files
+  -m          (-ignoreMinorErrors) Ignore minor errors and warnings
+  -o OUTFILE  (-out)               Set output file or directory name
+  -overwrite_original              Overwrite original by renaming tmp file
+  -overwrite_original_in_place     Overwrite original by copying tmp file
+  -P          (-preserve)          Preserve file modification date/time
+  -password PASSWD                 Password for processing protected files
+  -progress[NUM][:[TITLE]]         Show file progress count
+  -q          (-quiet)             Quiet processing
+  -r[.]       (-recurse)           Recursively process subdirectories
+  -scanForXMP                      Brute force XMP scan
+  -u          (-unknown)           Extract unknown tags
+  -U          (-unknown2)          Extract unknown binary tags too
+  -wm MODE    (-writeMode)         Set mode for writing/creating tags
+  -z          (-zip)               Read/write compressed information
+ +

Other options

+ +
  -@ ARGFILE                       Read command-line arguments from file
+  -k          (-pause)             Pause before terminating
+  -list[w|f|wf|g[NUM]|d|x]         List various exiftool capabilities
+  -ver                             Print exiftool version number
+  --                               End of options
+ +

Special features

+ +
  -geotag TRKFILE                  Geotag images from specified GPS log
+  -globalTimeShift SHIFT           Shift all formatted date/time values
+  -use MODULE                      Add features from plug-in module
+ +

Utilities

+ +
  -delete_original[!]              Delete "_original" backups
+  -restore_original                Restore from "_original" backups
+ +

Advanced options

+ +
  -api OPT[[^]=[VAL]]              Set ExifTool API option
+  -common_args                     Define common arguments
+  -config CFGFILE                  Specify configuration file name
+  -echo[NUM] TEXT                  Echo text to stdout or stderr
+  -efile[NUM][!] ERRFILE           Save names of files with errors
+  -execute[NUM]                    Execute multiple commands on one line
+  -fileNUM ALTFILE                 Load tags from alternate file
+  -list_dir                        List directories, not their contents
+  -srcfile FMT                     Process a different source file
+  -stay_open FLAG                  Keep reading -@ argfile even after EOF
+  -userParam PARAM[[^]=[VAL]]      Set user parameter (API UserParam opt)
+ +

Option Details

+ +

Tag operations

+ +
+ +
-TAG
+
+ +

Extract information for the specified tag (eg. -CreateDate). Multiple tags may be specified in a single command. A tag name is the handle by which a piece of information is referenced. See Image::ExifTool::TagNames for documentation on available tag names. A tag name may include leading group names separated by colons (eg. -EXIF:CreateDate, or -Doc1:XMP:Creator), and each group name may be prefixed by a digit to specify family number (eg. -1IPTC:City). (Note that the API SavePath and SaveFormat options must be used for the family 5 and 6 groups respectively to be available.) Use the -listg option to list available group names by family.

+ +

A special tag name of All may be used to indicate all meta information (ie. -All). This is particularly useful when a group name is specified to extract all information in a group (but beware that unless the -a option is also used, some tags in the group may be suppressed by same-named tags in other groups). The wildcard characters ? and * may be used in a tag name to match any single character and zero or more characters respectively. These may not be used in a group name, with the exception that a group name of * (or All) may be used to extract all instances of a tag (as if -a was used). Note that arguments containing wildcards must be quoted on the command line of most systems to prevent shell globbing.

+ +

A # may be appended to the tag name to disable the print conversion on a per-tag basis (see the -n option). This may also be used when writing or copying tags.

+ +

If no tags are specified, all available information is extracted (as if -All had been specified).

+ +

Note: Descriptions, not tag names, are shown by default when extracting information. Use the -s option to see the tag names instead.

+ +
+
--TAG
+
+ +

Exclude specified tag from extracted information. Same as the -x option. Group names and wildcards are permitted as described above for -TAG. Once excluded from the output, a tag may not be re-included by a subsequent option. May also be used following a -tagsFromFile option to exclude tags from being copied (when redirecting to another tag, it is the source tag that should be excluded), or to exclude groups from being deleted when deleting all information (eg. -all= --exif:all deletes all but EXIF information). But note that this will not exclude individual tags from a group delete (unless a family 2 group is specified, see note 4 below). Instead, individual tags may be recovered using the -tagsFromFile option (eg. -all= -tagsfromfile @ -artist).

+ +

To speed processing when reading XMP, exclusions in XMP groups also bypass processing of the corresponding XMP property and any contained properties. For example, --xmp-crs:all may speed processing significantly in cases where a large number of XMP-crs tags exist. To use this feature to bypass processing of a specific XMP property, the property name must be used instead of the ExifTool tag name (eg. --xmp-crs:dabs). Also, XMP-all may be used to to indicate any XMP namespace (eg. --xmp-all:dabs).

+ +
+
-TAG[+-^]=[VALUE]
+
+ +

Write a new value for the specified tag (eg. -comment=wow), or delete the tag if no VALUE is given (eg. -comment=). += and -= are used to add or remove existing entries from a list, or to shift date/time values (see Image::ExifTool::Shift.pl and note 6 below for more details). += may also be used to increment numerical values (or decrement if VALUE is negative), and -= may be used to conditionally delete or replace a tag (see "WRITING EXAMPLES" for examples). ^= is used to write an empty string instead of deleting the tag when no VALUE is given, but otherwise it is equivalent to =. (Note that the caret must be quoted on the Windows command line.)

+ +

TAG may contain one or more leading family 0, 1, 2 or 7 group names, prefixed by optional family numbers, and separated colons. If no group name is specified, the tag is created in the preferred group, and updated in any other location where a same-named tag already exists. The preferred group in JPEG and TIFF-format images is the first group in the following list where TAG is valid: 1) EXIF, 2) IPTC, 3) XMP.

+ +

The wildcards * and ? may be used in tag names to assign the same value to multiple tags. When specified with wildcards, "Unsafe" tags are not written. A tag name of All is equivalent to * (except that it doesn't require quoting, while arguments with wildcards do on systems with shell globbing), and is often used when deleting all metadata (ie. -All=) or an entire group (eg. -XMP-dc:All=, see note 4 below). Note that not all groups are deletable, and that the JPEG APP14 "Adobe" group is not removed by default with -All= because it may affect the appearance of the image. However, color space information is removed, so the colors may be affected (but this may be avoided by copying back the tags defined by the ColorSpaceTags shortcut). Use the -listd option for a complete list of deletable groups, and see note 5 below regarding the "APP" groups. Also, within an image some groups may be contained within others, and these groups are removed if the containing group is deleted:

+ +
  JPEG Image:
+  - Deleting EXIF or IFD0 also deletes ExifIFD, GlobParamIFD,
+    GPS, IFD1, InteropIFD, MakerNotes, PrintIM and SubIFD.
+  - Deleting ExifIFD also deletes InteropIFD and MakerNotes.
+  - Deleting Photoshop also deletes IPTC.
+
+  TIFF Image:
+  - Deleting EXIF only removes ExifIFD which also deletes
+    InteropIFD and MakerNotes.
+
+  MOV/MP4 Video:
+  - Deleting ItemList also deletes Keys tags.
+ +

Notes:

+ +

1) Many tag values may be assigned in a single command. If two assignments affect the same tag, the latter takes precedence (except for list-type tags, for which both values are written).

+ +

2) In general, MakerNotes tags are considered "Permanent", and may be edited but not created or deleted individually. This avoids many potential problems, including the inevitable compatibility problems with OEM software which may be very inflexible about the information it expects to find in the maker notes.

+ +

3) Changes to PDF files by ExifTool are reversible (by deleting the update with -PDF-update:all=) because the original information is never actually deleted from the file. So ExifTool alone may not be used to securely edit metadata in PDF files.

+ +

4) Specifying -GROUP:all= deletes the entire group as a block only if a single family 0 or 1 group is specified. Otherwise all deletable tags in the specified group(s) are removed individually, and in this case is it possible to exclude individual tags from a mass delete. For example, -time:all --Exif:Time:All removes all deletable Time tags except those in the EXIF. This difference also applies if family 2 is specified when deleting all groups. For example, -2all:all= deletes tags individually, while -all:all= deletes entire blocks.

+ +

5) The "APP" group names ("APP0" through "APP15") are used to delete JPEG application segments which are not associated with another deletable group. For example, specifying -APP14:All= will NOT delete the APP14 "Adobe" segment because this is accomplished with -Adobe:All. But note that these unnamed APP segments may not be excluded with --APPxx:all) when deleting all information.

+ +

6) When shifting a value, the shift is applied to the original value of the tag, overriding any other values previously assigned to the tag on the same command line. To shift a date/time value and copy it to another tag in the same operation, use the -globalTimeShift option.

+ +

Special feature: Integer values may be specified in hexadecimal with a leading 0x, and simple rational values may be specified as fractions.

+ +
+
-TAG<=DATFILE or -TAG<=FMT
+
+ +

Set the value of a tag from the contents of file DATFILE. The file name may also be given by a FMT string where %d, %f and %e represent the directory, file name and extension of the original FILE (see the -w option for more details). Note that quotes are required around this argument to prevent shell redirection since it contains a < symbol. If DATFILE/FMT is not provided, the effect is the same as -TAG=, and the tag is simply deleted. +<= or -<= may also be used to add or delete specific list entries, or to shift date/time values.

+ +
+
-tagsFromFile SRCFILE or FMT
+
+ +

Copy tag values from SRCFILE to FILE. Tag names on the command line after this option specify the tags to be copied, or excluded from the copy. Wildcards are permitted in these tag names. If no tags are specified, then all possible tags (see note 1 below) from the source file are copied to same-named tags in the preferred location of the output file (the same as specifying -all). More than one -tagsFromFile option may be used to copy tags from multiple files.

+ +

By default, this option will update any existing and writable same-named tags in the output FILE, but will create new tags only in their preferred groups. This allows some information to be automatically transferred to the appropriate group when copying between images of different formats. However, if a group name is specified for a tag then the information is written only to this group (unless redirected to another group, see below). If All is used as a group name, then the specified tag(s) are written to the same family 1 group they had in the source file (ie. the same specific location, like ExifIFD or XMP-dc). For example, the common operation of copying all writable tags to the same specific locations in the output FILE is achieved by adding -all:all. A different family may be specified by adding a leading family number to the group name (eg. -0all:all preserves the same general location, like EXIF or XMP).

+ +

SRCFILE may be the same as FILE to move information around within a single file. In this case, @ may be used to represent the source file (ie. -tagsFromFile @), permitting this feature to be used for batch processing multiple files. Specified tags are then copied from each file in turn as it is rewritten. For advanced batch use, the source file name may also be specified using a FMT string in which %d, %f and %e represent the directory, file name and extension of FILE. (eg. the current FILE would be represented by %d%f.%e, with the same effect as @). See the -w option for FMT string examples.

+ +

A powerful redirection feature allows a destination tag to be specified for each copied tag. With this feature, information may be written to a tag with a different name or group. This is done using "'-DSTTAG<SRCTAG'" or "'-SRCTAG>DSTTAG'" on the command line after -tagsFromFile, and causes the value of SRCTAG to be copied from SRCFILE and written to DSTTAG in FILE. Has no effect unless SRCTAG exists in SRCFILE. Note that this argument must be quoted to prevent shell redirection, and there is no = sign as when assigning new values. Source and/or destination tags may be prefixed by a group name and/or suffixed by #. Wildcards are allowed in both the source and destination tag names. A destination group and/or tag name of All or * writes to the same family 1 group and/or tag name as the source (but the family may be specified by adding a leading number to the group name, eg. 0All writes to the same family 0 group as the source). If no destination group is specified, the information is written to the preferred group. Whitespace around the > or < is ignored. As a convenience, -tagsFromFile @ is assumed for any redirected tags which are specified without a prior -tagsFromFile option. Copied tags may also be added or deleted from a list with arguments of the form "'-SRCTAG+<DSTTAG'" or "'-SRCTAG-<DSTTAG'" (but see Note 5 below).

+ +

An extension of the redirection feature allows strings involving tag names to be used on the right hand side of the < symbol with the syntax "'-DSTTAG<STR'", where tag names in STR are prefixed with a $ symbol. See the -p option and the "Advanced formatting feature" section for more details about this syntax. Strings starting with a = sign must insert a single space after the < to avoid confusion with the <= operator which sets the tag value from the contents of a file. A single space at the start of the string is removed if it exists, but all other whitespace in the string is preserved. See note 8 below about using the redirection feature with list-type stags, shortcuts or when using wildcards in tag names.

+ +

See "COPYING EXAMPLES" for examples using -tagsFromFile.

+ +

Notes:

+ +

1) Some tags (generally tags which may affect the appearance of the image) are considered "Unsafe" to write, and are only copied if specified explicitly (ie. no wildcards). See the tag name documentation for more details about "Unsafe" tags.

+ +

2) Be aware of the difference between excluding a tag from being copied (--TAG), and deleting a tag (-TAG=). Excluding a tag prevents it from being copied to the destination image, but deleting will remove a pre-existing tag from the image.

+ +

3) The maker note information is copied as a block, so it isn't affected like other information by subsequent tag assignments on the command line, and individual makernote tags may not be excluded from a block copy. Also, since the PreviewImage referenced from the maker notes may be rather large, it is not copied, and must be transferred separately if desired.

+ +

4) The order of operations is to copy all specified tags at the point of the -tagsFromFile option in the command line. Any tag assignment to the right of the -tagsFromFile option is made after all tags are copied. For example, new tag values are set in the order One, Two, Three then Four with this command:

+ +
    exiftool -One=1 -tagsFromFile s.jpg -Two -Four=4 -Three d.jpg
+ +

This is significant in the case where an overlap exists between the copied and assigned tags because later operations may override earlier ones.

+ +

5) The normal behaviour of copied tags differs from that of assigned tags for list-type tags and conditional replacements because each copy operation on a tag overrides any previous operations. While this avoids duplicate list items when copying groups of tags from a file containing redundant information, it also prevents values of different tags from being copied into the same list when this is the intent. To accumulate values from different operations into the same list, add a + after the initial - of the argument. For example:

+ +
    exiftool -tagsfromfile @ '-subject<make' '-+subject<model' ...
+ +

Similarly, -+DSTTAG must be used when conditionally replacing a tag to prevent overriding earlier conditions.

+ +

6) The -a option (allow duplicate tags) is always in effect when copying tags from SRCFILE, but the highest priority tag is always copied last so it takes precedence.

+ +

7) Structured tags are copied by default when copying tags. See the -struct option for details.

+ +

8) With the redirection feature, copying a tag directly (ie. "'-DSTTAG<SRCTAG'") is not the same as interpolating its value inside a string (ie. "'-DSTTAG<$SRCTAG'") for source tags which are list-type tags, shortcut tags, tag names containing wildcards, or UserParam variables. When copying directly, the values of each matching source tag are copied individually to the destination tag (as if they were separate assignments). However, when interpolated inside a string, list items and the values of shortcut tags are concatenated (with a separator set by the -sep option), and wildcards are not allowed. Also, UserParam variables are available only when interpolated in a string. Another difference is that a minor warning is generated if a tag doesn't exist when interpolating its value in a string (with $), but isn't when copying the tag directly.

+ +

Finally, the behaviour is different when a destination tag or group of All is used. When copying directly, a destination group and/or tag name of All writes to the same family 1 group and/or tag name as the source. But when interpolated in a string, the identity of the source tags are lost and the value is written to all possible groups/tags. For example, the string form must be used in the following command since the intent is to set the value of all existing date/time tags from CreateDate:

+ +
    exiftool '-time:all<$createdate' -wm w FILE
+ +
+
-x TAG (-exclude)
+
+ +

Exclude the specified tag. There may be multiple -x options. This has the same effect as --TAG on the command line. See the --TAG documentation above for a complete description.

+ +
+
+ +

Input-output text formatting

+ +

Note that trailing spaces are removed from extracted values for most output text formats. The exceptions are -b, -csv, -j and -X.

+ +
+ +
-args (-argFormat)
+
+ +

Output information in the form of exiftool arguments, suitable for use with the -@ option when writing. May be combined with the -G option to include group names. This feature may be used to effectively copy tags between images, but allows the metadata to be altered by editing the intermediate file (out.args in this example):

+ +
    exiftool -args -G1 --filename --directory src.jpg > out.args
+    exiftool -@ out.args -sep ', ' dst.jpg
+ +

Note: Be careful when copying information with this technique since it is easy to write tags which are normally considered "Unsafe". For instance, the FileName and Directory tags are excluded in the example above to avoid renaming and moving the destination file. Also note that the second command above will produce warning messages for any tags which are not writable.

+ +

As well, the -sep option should be used as in the second command above to maintain separate list items when writing metadata back to image files, and the -struct option may be used when extracting to preserve structured XMP information.

+ +
+
-b, --b (-binary, --binary)
+
+ +

Output requested metadata in binary format without tag names or descriptions (-b or -binary). This option is mainly used for extracting embedded images or other binary data, but it may also be useful for some text strings since control characters (such as newlines) are not replaced by '.' as they are in the default output. By default, list items are separated by a newline when extracted with the -b option, but this may be changed (see the -sep option for details). May be combined with -j, -php or -X to extract binary data in JSON, PHP or XML format, but note that "Unsafe" tags are not extracted as binary unless they are specified explicitly or the API RequestAll option is set to 3 or higher.

+ +

With a leading double dash (--b or --binary), tags which contain binary data are suppressed in the output when reading.

+ +
+
-c FMT (-coordFormat)
+
+ +

Set the print format for GPS coordinates. FMT uses the same syntax as a printf format string. The specifiers correspond to degrees, minutes and seconds in that order, but minutes and seconds are optional. For example, the following table gives the output for the same coordinate using various formats:

+ +
            FMT                  Output
+    -------------------    ------------------
+    "%d deg %d' %.2f"\"    54 deg 59' 22.80"  (default for reading)
+    "%d %d %.8f"           54 59 22.80000000  (default for copying)
+    "%d deg %.4f min"      54 deg 59.3800 min
+    "%.6f degrees"         54.989667 degrees
+ +

Notes:

+ +

1) To avoid loss of precision, the default coordinate format is different when copying tags using the -tagsFromFile option.

+ +

2) If the hemisphere is known, a reference direction (N, S, E or W) is appended to each printed coordinate, but adding a + to the format specifier (eg. %+.6f) prints a signed coordinate instead.

+ +

3) This print formatting may be disabled with the -n option to extract coordinates as signed decimal degrees.

+ +
+
-charset [[TYPE=]CHARSET]
+
+ +

If TYPE is ExifTool or not specified, this option sets the ExifTool character encoding for output tag values when reading and input values when writing, with a default of UTF8. If no CHARSET is given, a list of available character sets is returned. Valid CHARSET values are:

+ +
    CHARSET     Alias(es)        Description
+    ----------  ---------------  ----------------------------------
+    UTF8        cp65001, UTF-8   UTF-8 characters (default)
+    Latin       cp1252, Latin1   Windows Latin1 (West European)
+    Latin2      cp1250           Windows Latin2 (Central European)
+    Cyrillic    cp1251, Russian  Windows Cyrillic
+    Greek       cp1253           Windows Greek
+    Turkish     cp1254           Windows Turkish
+    Hebrew      cp1255           Windows Hebrew
+    Arabic      cp1256           Windows Arabic
+    Baltic      cp1257           Windows Baltic
+    Vietnam     cp1258           Windows Vietnamese
+    Thai        cp874            Windows Thai
+    DOSLatinUS  cp437            DOS Latin US
+    DOSLatin1   cp850            DOS Latin1
+    DOSCyrillic cp866            DOS Cyrillic
+    MacRoman    cp10000, Roman   Macintosh Roman
+    MacLatin2   cp10029          Macintosh Latin2 (Central Europe)
+    MacCyrillic cp10007          Macintosh Cyrillic
+    MacGreek    cp10006          Macintosh Greek
+    MacTurkish  cp10081          Macintosh Turkish
+    MacRomanian cp10010          Macintosh Romanian
+    MacIceland  cp10079          Macintosh Icelandic
+    MacCroatian cp10082          Macintosh Croatian
+ +

TYPE may be FileName to specify the encoding of file names on the command line (ie. FILE arguments). In Windows, this triggers use of wide-character i/o routines, thus providing support for Unicode file names. See the "WINDOWS UNICODE FILE NAMES" section below for details.

+ +

Other values of TYPE listed below are used to specify the internal encoding of various meta information formats.

+ +
    TYPE       Description                                  Default
+    ---------  -------------------------------------------  -------
+    EXIF       Internal encoding of EXIF "ASCII" strings    (none)
+    ID3        Internal encoding of ID3v1 information       Latin
+    IPTC       Internal IPTC encoding to assume when        Latin
+                IPTC:CodedCharacterSet is not defined
+    Photoshop  Internal encoding of Photoshop IRB strings   Latin
+    QuickTime  Internal encoding of QuickTime strings       MacRoman
+    RIFF       Internal encoding of RIFF strings            0
+ +

See https://exiftool.org/faq.html#Q10 for more information about coded character sets, and the Image::ExifTool Options for more details about the -charset settings.

+ +
+
-csv[[+]=CSVFILE]
+
+ +

Export information in CSV format, or import information if CSVFILE is specified. When importing, the CSV file must be in exactly the same format as the exported file. The first row of the CSVFILE must be the ExifTool tag names (with optional group names) for each column of the file, and values must be separated by commas. A special "SourceFile" column specifies the files associated with each row of information (and a SourceFile of "*" may be used to define default tags to be imported for all files which are combined with any tags specified for the specific SourceFile processed). The -csvDelim option may be used to change the input/output field delimiter if something other than a comma is required.

+ +

The following examples demonstrate basic use of the -csv option:

+ +
    # generate CSV file with common tags from all images in a directory
+    exiftool -common -csv dir > out.csv
+
+    # update metadata for all images in a directory from CSV file
+    exiftool -csv=a.csv dir
+ +

When importing, empty values are ignored unless the -f option is used and the API MissingTagValue is set to an empty string (in which case the tag is deleted). Also, FileName and Directory columns are ignored if they exist (ie. ExifTool will not attempt to write these tags with a CSV import), but all other columns are imported. To force a tag to be deleted, use the -f option and set the value to "-" in the CSV file (or to the MissingTagValue if this API option was used). Multiple databases may be imported in a single command.

+ +

When exporting a CSV file, the -g or -G option adds group names to the tag headings. If the -a option is used to allow duplicate tag names, the duplicate tags are only included in the CSV output if the column headings are unique. Adding the -G4 option ensures a unique column heading for each tag. The -b option may be added to output binary data, encoded in base64 if necessary (indicated by ASCII "base64:" as the first 7 bytes of the value). Values may also be encoded in base64 if the -charset option is used and the value contains invalid characters.

+ +

When exporting specific tags, the CSV columns are arranged in the same order as the specified tags provided the column headings exactly match the specified tag names, otherwise the columns are sorted in alphabetical order.

+ +

When importing from a CSV file, only files specified on the command line are processed. Any extra entries in the CSV file are ignored.

+ +

List-type tags are stored as simple strings in a CSV file, but the -sep option may be used to split them back into separate items when importing.

+ +

Special feature: -csv+=CSVFILE may be used to add items to existing lists. This affects only list-type tags. Also applies to the -j option.

+ +

Note that this option is fundamentally different than all other output format options because it requires information from all input files to be buffered in memory before the output is written. This may result in excessive memory usage when processing a very large number of files with a single command. Also, it makes this option incompatible with the -w and -W options. When processing a large number of files, it is recommended to either use the JSON (-j) or XML (-X) output format, or use -p to generate a fixed-column CSV file instead of using the -csv option.

+ +
+
-csvDelim STR
+
+ +

Set the delimiter for separating CSV entries for CSV file input/output via the -csv option. STR may contain "\t", "\n", "\r" and "\\" to represent TAB, LF, CR and '\' respectively. A double quote is not allowed in the delimiter. Default is ','.

+ +
+
-d FMT (-dateFormat)
+
+ +

Set the format for date/time tag values. The FMT string may contain formatting codes beginning with a percent character (%) to represent the various components of a date/time value. The specifics of the FMT syntax are system dependent -- consult the strftime man page on your system for details. The default format is equivalent to "%Y:%m:%d %H:%M:%S". This option has no effect on date-only or time-only tags and ignores timezone information if present. ExifTool adds a %f format code to represent fractional seconds, and supports an optional width to specify the number of digits after the decimal point (eg. %3f would give something like .437), and a minus sign to drop the decimal point (eg. %-3f would give 437). Only one -d option may be used per command. Requires POSIX::strptime or Time::Piece for the inversion conversion when writing.

+ +
+
-D (-decimal)
+
+ +

Show tag ID number in decimal when extracting information.

+ +
+
-E, -ex, -ec (-escapeHTML, -escapeXML, -escapeC)
+
+ +

Escape characters in output tag values for HTML (-E), XML (-ex) or C (-ec). For HTML, all characters with Unicode code points above U+007F are escaped as well as the following 5 characters: & (&amp;) ' (&#39;) " (&quot;) > (&gt;) and < (&lt;). For XML, only these 5 characters are escaped. The -E option is implied with -h, and -ex is implied with -X. For C, all control characters and the backslash are escaped. The inverse conversion is applied when writing tags.

+ +
+
-f (-forcePrint)
+
+ +

Force printing of tags even if they don't exist. This option applies to tags specified on the command line, or with the -p, -if or -tagsFromFile options. When -f is used, the value of any missing tag is set to a dash (-) by default, but this may be configured via the API MissingTagValue option. -f is also used to add a 'flags' attribute to the -listx output, or to allow tags to be deleted when writing with the -csv=CSVFILE feature.

+ +
+
-g[NUM][:NUM...] (-groupHeadings)
+
+ +

Organize output by tag group. NUM specifies a group family number, and may be 0 (general location), 1 (specific location), 2 (category), 3 (document number), 4 (instance number), 5 (metadata path), 6 (EXIF/TIFF format), 7 (tag ID) or 8 (file number). -g0 is assumed if a family number is not specified. May be combined with other options to add group names to the output. Multiple families may be specified by separating them with colons. By default the resulting group name is simplified by removing any leading Main: and collapsing adjacent identical group names, but this can be avoided by placing a colon before the first family number (eg. -g:3:1). Use the -listg option to list group names for a specified family. The API SavePath and SaveFormat options are automatically enabled if the respective family 5 or 6 group names are requested. See the API GetGroup documentation for more information.

+ +
+
-G[NUM][:NUM...] (-groupNames)
+
+ +

Same as -g but print group name for each tag. -G0 is assumed if NUM is not specified. May be combined with a number of other options to add group names to the output. Note that NUM may be added wherever -G is mentioned in the documentation. See the -g option above for details.

+ +
+
-h (-htmlFormat)
+
+ +

Use HTML table formatting for output. Implies the -E option. The formatting options -D, -H, -g, -G, -l and -s may be used in combination with -h to influence the HTML format.

+ +
+
-H (-hex)
+
+ +

Show tag ID number in hexadecimal when extracting information.

+ +
+
-htmlDump[OFFSET]
+
+ +

Generate a dynamic web page containing a hex dump of the EXIF information. This can be a very powerful tool for low-level analysis of EXIF information. The -htmlDump option is also invoked if the -v and -h options are used together. The verbose level controls the maximum length of the blocks dumped. An OFFSET may be given to specify the base for displayed offsets. If not provided, the EXIF/TIFF base offset is used. Use -htmlDump0 for absolute offsets. Currently only EXIF/TIFF and JPEG information is dumped, but the -u option can be used to give a raw hex dump of other file formats.

+ +
+
-j[[+]=JSONFILE] (-json)
+
+ +

Use JSON (JavaScript Object Notation) formatting for console output, or import JSON file if JSONFILE is specified. This option may be combined with -g to organize the output into objects by group, or -G to add group names to each tag. List-type tags with multiple items are output as JSON arrays unless -sep is used. By default XMP structures are flattened into individual tags in the JSON output, but the original structure may be preserved with the -struct option (this also causes all list-type XMP tags to be output as JSON arrays, otherwise single-item lists would be output as simple strings). The -a option is implied when -json is used, but entries with identical JSON names are suppressed in the output. (-G4 may be used to ensure that all tags have unique JSON names.) Adding the -D or -H option changes tag values to JSON objects with "val" and "id" fields, and adding -l adds a "desc" field, and a "num" field if the numerical value is different from the converted "val". The -b option may be added to output binary data, encoded in base64 if necessary (indicated by ASCII "base64:" as the first 7 bytes of the value), and -t may be added to include tag table information (see -t for details). The JSON output is UTF-8 regardless of any -L or -charset option setting, but the UTF-8 validation is disabled if a character set other than UTF-8 is specified. Note that ExifTool quotes JSON values only if they don't look like numbers (regardless of the original storage format or the relevant metadata specification).

+ +

If JSONFILE is specified, the file is imported and the tag definitions from the file are used to set tag values on a per-file basis. The special "SourceFile" entry in each JSON object associates the information with a specific target file. An object with a missing SourceFile or a SourceFile of "*" defines default tags for all target files which are combined with any tags specified for the specific SourceFile processed. The imported JSON file must have the same format as the exported JSON files with the exception that options exporting JSON objects instead of simple values are not compatible with the import file format (ie. export with -D, -H, -l, or -T is not compatable, and use -G instead of -g). Additionally, tag names in the input JSON file may be suffixed with a # to disable print conversion.

+ +

Unlike CSV import, empty values are not ignored, and will cause an empty value to be written if supported by the specific metadata type. Tags are deleted by using the -f option and setting the tag value to "-" (or to the MissingTagValue setting if this API option was used). Importing with -j+=JSONFILE causes new values to be added to existing lists.

+ +
+
-l (-long)
+
+ +

Use long 2-line Canon-style output format. Adds a description and unconverted value (if it is different from the converted value) to the XML, JSON or PHP output when -X, -j or -php is used. May also be combined with -listf, -listr or -listwf to add descriptions of the file types.

+ +
+
-L (-latin)
+
+ +

Use Windows Latin1 encoding (cp1252) for output tag values instead of the default UTF-8. When writing, -L specifies that input text values are Latin1 instead of UTF-8. Equivalent to -charset latin.

+ +
+
-lang [LANG]
+
+ +

Set current language for tag descriptions and converted values. LANG is de, fr, ja, etc. Use -lang with no other arguments to get a list of available languages. The default language is en if -lang is not specified. Note that tag/group names are always English, independent of the -lang setting, and translation of warning/error messages has not yet been implemented. May also be combined with -listx to output descriptions in one language only.

+ +

By default, ExifTool uses UTF-8 encoding for special characters, but the -L or -charset option may be used to invoke other encodings. Note that ExifTool uses Unicode::LineBreak if available to help preserve the column alignment of the plain text output for languages with a variable-width character set.

+ +

Currently, the language support is not complete, but users are welcome to help improve this by submitting their own translations. To submit a translation, follow these steps (you must have Perl installed for this):

+ +

1. Download and unpack the latest Image-ExifTool full distribution.

+ +

2. 'cd' into the Image-ExifTool directory.

+ +

3. Run this command to make an XML file of the desired tags (eg. EXIF):

+ +
   ./exiftool -listx -exif:all > out.xml
+ +

4. Copy this text into a file called 'import.pl' in the exiftool directory:

+ +
    push @INC, 'lib';
+    require Image::ExifTool::TagInfoXML;
+    my $file = shift or die "Expected XML file name\n";
+    $Image::ExifTool::TagInfoXML::makeMissing = shift;
+    Image::ExifTool::TagInfoXML::BuildLangModules($file,8);
+ +

5. Run the 'import.pl' script to Import the XML file, generating the 'MISSING' entries for your language (eg. Russian):

+ +
   perl import.pl out.xml ru
+ +

6. Edit the generated language module lib/Image/ExifTool/Lang/ru.pm, and search and replace all 'MISSING' strings in the file with your translations.

+ +

7. Email the module ('ru.pm' in this example) to philharvey66 at gmail.com

+ +

8. Thank you!!

+ +
+
-listItem INDEX
+
+ +

For list-type tags, this causes only the item with the specified index to be extracted. INDEX is 0 for the first item in the list. Negative indices may also be used to reference items from the end of the list. Has no effect on single-valued tags. Also applies to tag values when copying from a tag, and in -if conditions.

+ +
+
-n (--printConv)
+
+ +

Disable print conversion for all tags. By default, extracted values are converted to a more human-readable format, but the -n option disables this conversion, revealing the machine-readable values. For example:

+ +
    > exiftool -Orientation -S a.jpg
+    Orientation: Rotate 90 CW
+    > exiftool -Orientation -S -n a.jpg
+    Orientation: 6
+ +

The print conversion may also be disabled on a per-tag basis by suffixing the tag name with a # character:

+ +
    > exiftool -Orientation# -Orientation -S a.jpg
+    Orientation: 6
+    Orientation: Rotate 90 CW
+ +

These techniques may also be used to disable the inverse print conversion when writing. For example, the following commands all have the same effect:

+ +
    > exiftool -Orientation='Rotate 90 CW' a.jpg
+    > exiftool -Orientation=6 -n a.jpg
+    > exiftool -Orientation#=6 a.jpg
+ +
+
-p FMTFILE or STR (-printFormat)
+
+ +

Print output in the format specified by the given file or string. The argument is interpreted as a string unless a file of that name exists, in which case the string is loaded from the contents of the file. Tag names in the format file or string begin with a $ symbol and may contain leading group names and/or a trailing # (to disable print conversion). Case is not significant. Braces {} may be used around the tag name to separate it from subsequent text (and must be used if subsequent text begins with an alphanumeric character, hyphen, underline, colon or number sign). Use $$ to represent a $ symbol, and $/ for a newline.

+ +

Multiple -p options may be used, each contributing a line (or more) of text to the output. Lines beginning with #[HEAD] and #[TAIL] are output before the first processed file and after the last processed file respectively. Lines beginning with #[SECT] and #[ENDS] are output before and after each section of files. A section is defined as a group of consecutive files with the same section header (eg. files are grouped by directory if #[SECT] contains $directory). Lines beginning with #[BODY] and lines not beginning with # are output for each processed file. Lines beginning with #[IF] are not output, but all BODY lines are skipped if any tag on an IF line doesn't exist. Other lines beginning with # are ignored. (To output a line beginning with #, use #[BODY]#.) For example, this format file:

+ +
    # this is a comment line
+    #[HEAD]-- Generated by ExifTool $exifToolVersion --
+    File: $FileName - $DateTimeOriginal
+    (f/$Aperture, ${ShutterSpeed}s, ISO $EXIF:ISO)
+    #[TAIL]-- end --
+ +

with this command:

+ +
    exiftool -p test.fmt a.jpg b.jpg
+ +

produces output like this:

+ +
    -- Generated by ExifTool 12.67 --
+    File: a.jpg - 2003:10:31 15:44:19
+    (f/5.6, 1/60s, ISO 100)
+    File: b.jpg - 2006:05:23 11:57:38
+    (f/8.0, 1/13s, ISO 100)
+    -- end --
+ +

The values of List-type tags with multiple items and Shortcut tags representing multiple tags are joined according the -sep option setting when interpolated in the string.

+ +

When -ee (-extractEmbedded) is combined with -p, embedded documents are effectively processed as separate input files.

+ +

If a specified tag does not exist, a minor warning is issued and the line with the missing tag is not printed. However, the -f option may be used to set the value of missing tags to '-' (but this may be configured via the API MissingTagValue option), or the -m option may be used to ignore minor warnings and leave the missing values empty. Alternatively, -q -q may be used to simply suppress the warning messages.

+ +

The "Advanced formatting feature" may be used to modify the values of individual tags within the -p option string.

+ +

Note that the API RequestTags option is automatically set for all tags used in the FMTFILE or STR. This allows all other tags to be ignored using -API IgnoreTags=all, resulting in reduced memory usage and increased speed.

+ +
+
-php
+
+ +

Format output as a PHP Array. The -g, -G, -D, -H, -l, -sep and -struct options combine with -php, and duplicate tags are handled in the same way as with the -json option. As well, the -b option may be added to output binary data, and -t may be added to include tag table information (see -t for details). Here is a simple example showing how this could be used in a PHP script:

+ +
    <?php
+    eval('$array=' . `exiftool -php -q image.jpg`);
+    print_r($array);
+    ?>
+ +
+
-s[NUM] (-short)
+
+ +

Short output format. Prints tag names instead of descriptions. Add NUM or up to 3 -s options for even shorter formats:

+ +
    -s1 or -s        - print tag names instead of descriptions
+    -s2 or -s -s     - no extra spaces to column-align values
+    -s3 or -s -s -s  - print values only (no tag names)
+ +

Also effective when combined with -t, -h, -X or -listx options.

+ +
+
-S (-veryShort)
+
+ +

Very short format. The same as -s2 or two -s options. Tag names are printed instead of descriptions, and no extra spaces are added to column-align values.

+ +
+
-sep STR (-separator)
+
+ +

Specify separator string for items in list-type tags. When reading, the default is to join list items with ", ". When writing, this option causes values assigned to list-type tags to be split into individual items at each substring matching STR (otherwise they are not split by default). Space characters in STR match zero or more whitespace characters in the value.

+ +

Note that an empty separator ("") is allowed, and will join items with no separator when reading, or split the value into individual characters when writing.

+ +

For pure binary output (-b used without -j, -php or -X), the first -sep option specifies a list-item separator, and a second -sep option specifies a terminator for the end of the list (or after each value if not a list). In these strings, \n, \r and \t may be used to represent a newline, carriage return and tab respectively. By default, binary list items are separated by a newline, and no terminator is added.

+ +
+
-sort, --sort
+
+ +

Sort output by tag description, or by tag name if the -s option is used. When sorting by description, the sort order will depend on the -lang option setting. Without the -sort option, tags appear in the order they were specified on the command line, or if not specified, the order they were extracted from the file. By default, tags are organized by groups when combined with the -g or -G option, but this grouping may be disabled with --sort.

+ +
+
-struct, --struct
+
+ +

Output structured XMP information instead of flattening to individual tags. This option works well when combined with the XML (-X) and JSON (-j) output formats. For other output formats, XMP structures and lists are serialized into the same format as when writing structured information (see https://exiftool.org/struct.html for details). When copying, structured tags are copied by default unless --struct is used to disable this feature (although flattened tags may still be copied by specifying them individually unless -struct is used). These options have no effect when assigning new values since both flattened and structured tags may always be used when writing.

+ +
+
-t (-tab)
+
+ +

Output a tab-delimited list of description/values (useful for database import). May be combined with -s to print tag names instead of descriptions, or -S to print tag values only, tab-delimited on a single line. The -t option may be combined with -j, -php or -X to add tag table information (table, tag id, and index for cases where multiple conditional tags exist with the same ID).

+ +
+
-T (-table)
+
+ +

Output tag values in table form. Equivalent to -t -S -q -f.

+ +
+
-v[NUM] (-verbose)
+
+ +

Print verbose messages. NUM specifies the level of verbosity in the range 0-5, with higher numbers being more verbose. If NUM is not given, then each -v option increases the level of verbosity by 1. With any level greater than 0, most other options are ignored and normal console output is suppressed unless specific tags are extracted. Using -v0 causes the console output buffer to be flushed after each line (which may be useful to avoid delays when piping exiftool output), and prints the name of each processed file when writing and the new file name when renaming, moving or copying. Verbose levels above -v0 do not flush after each line. Also see the -progress option.

+ +
+
-w[+|!] EXT or FMT (-textOut)
+
+ +

Write console output to files with names ending in EXT, one for each source file. The output file name is obtained by replacing the source file extension (including the '.') with the specified extension (and a '.' is added to the start of EXT if it doesn't already contain one). Alternatively, a FMT string may be used to give more control over the output file name and directory. In the format string, %d, %f and %e represent the directory, filename and extension of the source file, and %c represents a copy number which is automatically incremented if the file already exists. %d includes the trailing '/' if necessary, but %e does not include the leading '.'. For example:

+ +
    -w %d%f.txt       # same effect as "-w txt"
+    -w dir/%f_%e.out  # write files to "dir" as "FILE_EXT.out"
+    -w dir2/%d%f.txt  # write to "dir2", keeping dir structure
+    -w a%c.txt        # write to "a.txt" or "a1.txt" or "a2.txt"...
+ +

Existing files will not be changed unless an exclamation point is added to the option name (ie. -w! or -textOut!) to overwrite the file, or a plus sign (ie. -w+ or -textOut+) to append to the existing file. Both may be used (ie. -w+! or -textOut+!) to overwrite output files that didn't exist before the command was run, and append the output from multiple source files. For example, to write one output file for all source files in each directory:

+ +
    exiftool -filename -createdate -T -w+! %d/out.txt -r DIR
+ +

Capitalized format codes %D, %F, %E and %C provide slightly different alternatives to the lower case versions. %D does not include the trailing '/', %F is the full filename including extension, %E includes the leading '.', and %C increments the count for each processed file (see below).

+ +

Notes:

+ +

1) In a Windows BAT file the % character is represented by %%, so an argument like %d%f.txt is written as %%d%%f.txt.

+ +

2) If the argument for -w does not contain a valid format code (eg. %f), then it is interpreted as a file extension, but there are three different ways to create a single output file from multiple source files:

+ +
    # 1. Shell redirection
+    exiftool FILE1 FILE2 ... > out.txt
+
+    # 2. With the -w option and a zero-width format code
+    exiftool -w+! %0fout.txt FILE1 FILE2 ...
+
+    # 3. With the -W option (see the -W option below)
+    exiftool -W+! out.txt FILE1 FILE2 ...
+ +

Advanced features:

+ +

A substring of the original file name, directory or extension may be taken by specifying a field width immediately following the '%' character. If the width is negative, the substring is taken from the end. The substring position (characters to ignore at the start or end of the string) may be given by a second optional value after a decimal point. For example:

+ +
    Input File Name     Format Specifier    Output File Name
+    ----------------    ----------------    ----------------
+    Picture-123.jpg     %7f.txt             Picture.txt
+    Picture-123.jpg     %-.4f.out           Picture.out
+    Picture-123.jpg     %7f.%-3f            Picture.123
+    Picture-123a.jpg    Meta%-3.1f.txt      Meta123.txt
+ +

(Note that special characters may have a width of greater than one.)

+ +

For %d and %D, the field width/position specifiers may be applied to the directory levels instead of substring position by using a colon instead of a decimal point in the format specifier. For example:

+ +
    Source Dir     Format   Result       Notes
+    ------------   ------   ----------   ------------------
+    pics/2012/02   %2:d     pics/2012/   take top 2 levels
+    pics/2012/02   %-:1d    pics/2012/   up one directory level
+    pics/2012/02   %:1d     2012/02/     ignore top level
+    pics/2012/02   %1:1d    2012/        take 1 level after top
+    pics/2012/02   %-1:D    02           bottom level folder name
+    /Users/phil    %:2d     phil/        ignore top 2 levels
+ +

(Note that the root directory counts as one level when an absolute path is used as in the last example above.)

+ +

For %c, these modifiers have a different effects. If a field width is given, the copy number is padded with zeros to the specified width. A leading '-' adds a dash before the copy number, and a '+' adds an underline. By default, the copy number is omitted from the first file of a given name, but this can be changed by adding a decimal point to the modifier. For example:

+ +
    -w A%-cZ.txt      # AZ.txt, A-1Z.txt, A-2Z.txt ...
+    -w B%5c.txt       # B.txt, B00001.txt, B00002.txt ...
+    -w C%.c.txt       # C0.txt, C1.txt, C2.txt ...
+    -w D%-.c.txt      # D-0.txt, D-1.txt, D-2.txt ...
+    -w E%-.4c.txt     # E-0000.txt, E-0001.txt, E-0002.txt ...
+    -w F%-.4nc.txt    # F-0001.txt, F-0002.txt, F-0003.txt ...
+    -w G%+c.txt       # G.txt, G_1.txt G_2.txt ...
+    -w H%-lc.txt      # H.txt, H-b.txt, H-c.txt ...
+    -w I.%.3uc.txt    # I.AAA.txt, I.AAB.txt, I.AAC.txt ...
+ +

A special feature allows the copy number to be incremented for each processed file by using %C (upper case) instead of %c. This allows a sequential number to be added to output file names, even if the names are different. For %C, a copy number of zero is not omitted as it is with %c. A leading '-' causes the number to be reset at the start of each new directory, and '+' has no effect. The number before the decimal place gives the starting index, the number after the decimal place gives the field width. The following examples show the output filenames when used with the command exiftool rose.jpg star.jpg jet.jpg ...:

+ +
    -w %C%f.txt       # 0rose.txt, 1star.txt, 2jet.txt
+    -w %f-%10C.txt    # rose-10.txt, star-11.txt, jet-12.txt
+    -w %.3C-%f.txt    # 000-rose.txt, 001-star.txt, 002-jet.txt
+    -w %57.4C%f.txt   # 0057rose.txt, 0058star.txt, 0059jet.txt
+ +

All format codes may be modified by 'l' or 'u' to specify lower or upper case respectively (ie. %le for a lower case file extension). When used to modify %c or %C, the numbers are changed to an alphabetical base (see example H above). Also, %c and %C may be modified by 'n' to count using natural numbers starting from 1, instead of 0 (see example F above).

+ +

This same FMT syntax is used with the -o and -tagsFromFile options, although %c and %C are only valid for output file names.

+ +
+
-W[+|!] FMT (-tagOut)
+
+ +

This enhanced version of the -w option allows a separate output file to be created for each extracted tag. See the -w option documentation above for details of the basic functionality. Listed here are the differences between -W and -w:

+ +

1) With -W, a new output file is created for each extracted tag.

+ +

2) -W supports four additional format codes: %t, %g and %s represent the tag name, group name, and suggested extension for the output file (based on the format of the data), and %o represents the value of the OriginalRawFileName or OriginalFileName tag from the input file (including extension). The %g code may be followed by a single digit to specify the group family number (eg. %g1), otherwise family 0 is assumed. The substring width/position/case specifiers may be used with these format codes in exactly the same way as with %f and %e.

+ +

3) The argument for -W is interpreted as a file name if it contains no format codes. (For -w, this would be a file extension.) This change allows a simple file name to be specified, which, when combined with the append feature, provides a method to write metadata from multiple source files to a single output file without the need for shell redirection. For example, the following pairs of commands give the same result:

+ +
    # overwriting existing text file
+    exiftool test.jpg > out.txt     # shell redirection
+    exiftool test.jpg -W+! out.txt  # equivalent -W option
+
+    # append to existing text file
+    exiftool test.jpg >> out.txt    # shell redirection
+    exiftool test.jpg -W+ out.txt   # equivalent -W option
+ +

4) Adding the -v option to -W sends a list of the tags and output file names to the console instead of giving a verbose dump of the entire file. (Unless appending all output to one file for each source file by using -W+ with an output file FMT that does not contain %t, %g, %s or %o.)

+ +

5) Individual list items are stored in separate files when -W is combined with -b, but note that for separate files to be created %c or %C must be used in FMT to give the files unique names.

+ +
+
-Wext EXT, --Wext EXT (-tagOutExt)
+
+ +

This option is used to specify the type of output file(s) written by the -W option. An output file is written only if the suggested extension matches EXT. Multiple -Wext options may be used to write more than one type of file. Use --Wext to write all but the specified type(s).

+ +
+
-X (-xmlFormat)
+
+ +

Use ExifTool-specific RDF/XML formatting for console output. Implies the -a option, so duplicate tags are extracted. The formatting options -b, -D, -H, -l, -s, -sep, -struct and -t may be used in combination with -X to affect the output, but note that the tag ID (-D, -H and -t), binary data (-b) and structured output (-struct) options are not effective for the short output (-s). Another restriction of -s is that only one tag with a given group and name may appear in the output. Note that the tag ID options (-D, -H and -t) will produce non-standard RDF/XML unless the -l option is also used.

+ +

By default, -X outputs flattened tags, so -struct should be added if required to preserve XMP structures. List-type tags with multiple values are formatted as an RDF Bag, but they are combined into a single string when -s or -sep is used. Using -L changes the XML encoding from "UTF-8" to "windows-1252". Other -charset settings change the encoding only if there is a corresponding standard XML character set. The -b option causes binary data values to be written, encoded in base64 if necessary. The -t option adds tag table information to the output (see -t for details).

+ +

Note: This output is NOT the same as XMP because it uses dynamically-generated property names corresponding to the ExifTool tag names with ExifTool family 1 group names as namespaces, and not the standard XMP properties and namespaces. To write XMP instead, use the -o option with an XMP extension for the output file.

+ +
+
+ +

Processing control

+ +
+ +
-a, --a (-duplicates, --duplicates)
+
+ +

Allow (-a) or suppress (--a) duplicate tag names to be extracted. By default, duplicate tags are suppressed when reading unless the -ee or -X options are used or the Duplicates option is enabled in the configuration file. When writing, this option allows multiple Warning messages to be shown. Duplicate tags are always extracted when copying.

+ +
+
-e (--composite)
+
+ +

Extract existing tags only -- don't generate composite tags.

+ +
+
-ee[NUM] (-extractEmbedded)
+
+ +

Extract information from embedded documents in EPS files, embedded EPS information and JPEG and Jpeg2000 images in PDF files, embedded MPF images in JPEG and MPO files, streaming metadata in AVCHD videos, and the resource fork of Mac OS files. Implies the -a option. Use -g3 or -G3 to identify the originating document for extracted information. Embedded documents containing sub-documents are indicated with dashes in the family 3 group name. (eg. Doc2-3 is the 3rd sub-document of the 2nd embedded document.) Note that this option may increase processing time substantially, especially for PDF files with many embedded images or videos with streaming metadata.

+ +

When used with -ee, the -p option is evaluated for each embedded document as if it were a separate input file. This allows, for example, generation of GPS track logs from timed metadata in videos. See https://exiftool.org/geotag.html#Inverse for examples.

+ +

Setting NUM to 2 causes the H264 video stream in MP4 videos to be parsed until the first Supplemental Enhancement Information (SEI) message is decoded, or 3 to parse the entire H624 stream and decode all SEI information. For M2TS videos, a setting of 3 causes the entire file to be parsed in search of unlisted programs which may contain timed GPS.

+ +
+
-ext[+] EXT, --ext EXT (-extension)
+
+ +

Process only files with (-ext) or without (--ext) a specified extension. There may be multiple -ext and --ext options. A plus sign may be added (ie. -ext+) to add the specified extension to the normally processed files. EXT may begin with a leading '.', which is ignored. Case is not significant. "*" may be used to process files with any extension (or none at all), as in the last three examples:

+ +
    exiftool -ext JPG DIR             # process only JPG files
+    exiftool --ext cr2 --ext dng DIR  # supported files but CR2/DNG
+    exiftool -ext+ txt DIR            # supported files plus TXT
+    exiftool -ext "*" DIR             # process all files
+    exiftool -ext "*" --ext xml DIR   # process all but XML files
+    exiftool -ext "*" --ext . DIR     # all but those with no ext
+ +

Using this option has two main advantages over specifying *.EXT on the command line: 1) It applies to files in subdirectories when combined with the -r option. 2) The -ext option is case-insensitive, which is useful when processing files on case-sensitive filesystems.

+ +

Note that all files specified on the command line will be processed regardless of extension unless the -ext option is used.

+ +
+
-F[OFFSET] (-fixBase)
+
+ +

Fix the base for maker notes offsets. A common problem with some image editors is that offsets in the maker notes are not adjusted properly when the file is modified. This may cause the wrong values to be extracted for some maker note entries when reading the edited file. This option allows an integer OFFSET to be specified for adjusting the maker notes base offset. If no OFFSET is given, ExifTool takes its best guess at the correct base. Note that exiftool will automatically fix the offsets for images which store original offset information (eg. newer Canon models). Offsets are fixed permanently if -F is used when writing EXIF to an image. eg)

+ +
    exiftool -F -exif:resolutionunit=inches image.jpg
+ +
+
-fast[NUM]
+
+ +

Increase speed of extracting information. With -fast (or -fast1), ExifTool will not scan to the end of a JPEG image to check for an AFCP or PreviewImage trailer, or past the first comment in GIF images or the audio/video data in WAV/AVI files to search for additional metadata. These speed benefits are small when reading images directly from disk, but can be substantial if piping images through a network connection. For more substantial speed benefits, -fast2 also causes exiftool to avoid extracting any EXIF MakerNote information, and to stop processing at the IDAT chunk of PNG images and the mdat atom of QuickTime-format files (but note that some files may store metadata after this). -fast3 avoids extracting metadata from the file, and returns only pseudo System tags, but still reads the file header to obtain an educated guess at FileType. -fast4 doesn't even read the file header, and returns only System tags and a FileType based on the file extension. -fast5 also disables generation of the Composite tags (like -e). Has no effect when writing.

+ +

Note that a separate -fast setting may be used for evaluation of a -if condition, or when ordering files with the -fileOrder option. See the -if and -fileOrder options for details.

+ +
+
-fileOrder[NUM] [-]TAG
+
+ +

Set file processing order according to the sorted value of the specified TAG. For example, to process files in order of date:

+ +
    exiftool -fileOrder DateTimeOriginal DIR
+ +

Additional -fileOrder options may be added for secondary sort keys. Numbers are sorted numerically, and all other values are sorted alphabetically. Files missing the specified tag are sorted last. The sort order may be reversed by prefixing the tag name with a - (eg. -fileOrder -createdate). Print conversion of the sorted values is disabled with the -n option, or a # appended to the tag name. Other formatting options (eg. -d) have no effect on the sorted values. Note that the -fileOrder option can incur large performance penalty since it involves an additional initial processing pass of all files, but this impact may be reduced by specifying a NUM to effectively set the -fast level for the initial pass. For example, -fileOrder4 may be used if TAG is a pseudo System tag. If multiple -fileOrder options are used, the extraction is done at the lowest -fast level. Note that files are sorted across directory boundaries if multiple input directories are specified.

+ +
+
-i DIR (-ignore)
+
+ +

Ignore specified directory name. DIR may be either an individual folder name, or a full path. If a full path is specified, it must match the Directory tag exactly to be ignored. Use multiple -i options to ignore more than one directory name. A special DIR value of SYMLINKS (case sensitive) may be specified to avoid recursing into directories which are symbolic links when the -r option is used. As well, a value of HIDDEN (case sensitive) may be used to ignore files with names that start with a "." (ie. hidden files on Unix systems) when scanning a directory.

+ +
+
-if[NUM] EXPR
+
+ +

Specify a condition to be evaluated before processing each FILE. EXPR is a Perl-like logic expression containing tag names prefixed by $ symbols. It is evaluated with the tags from each FILE in turn, and the file is processed only if the expression returns true. Unlike Perl variable names, tag names are not case sensitive and may contain a hyphen. As well, tag names may have a leading group names separated by colons, and/or a trailing # character to disable print conversion. The expression $GROUP:all evaluates to 1 if any tag exists in the specified GROUP, or 0 otherwise (see note 2 below). When multiple -if options are used, all conditions must be satisfied to process the file. Returns an exit status of 2 if all files fail the condition. Below are a few examples:

+ +
    # extract shutterspeed from all Canon images in a directory
+    exiftool -shutterspeed -if '$make eq "Canon"' dir
+
+    # add one hour to all images created on or after Apr. 2, 2006
+    exiftool -alldates+=1 -if '$CreateDate ge "2006:04:02"' dir
+
+    # set EXIF ISO value if possible, unless it is set already
+    exiftool '-exif:iso<iso' -if 'not $exif:iso' dir
+
+    # find images containing a specific keyword (case insensitive)
+    exiftool -if '$keywords =~ /harvey/i' -filename dir
+ +

Adding NUM to the -if option causes a separate processing pass to be executed for evaluating EXPR at a -fast level given by NUM (see the -fast option documentation for details). Without NUM, only one processing pass is done at the level specified by the -fast option. For example, using -if5 is possible if EXPR uses only pseudo System tags, and may significantly speed processing if enough files fail the condition.

+ +

The expression has access to the current ExifTool object through $self, and the following special functions are available to allow short-circuiting of the file processing. Both functions have a return value of 1. Case is significant for function names.

+ +
    End()    - end processing after this file
+    EndDir() - end processing of files in the current directory
+               after this file (not compatible with -fileOrder)
+ +

Notes:

+ +

1) The -n and -b options also apply to tags used in EXPR.

+ +

2) Some binary data blocks are not extracted unless specified explicitly. These tags are not available for use in the -if condition unless they are also specified on the command line. The alternative is to use the $GROUP:all syntax. (eg. Use $exif:all instead of $exif in EXPR to test for the existence of EXIF tags.)

+ +

3) Tags in the string are interpolated in a similar way to -p before the expression is evaluated. In this interpolation, $/ is converted to a newline and $$ represents a single $ symbol. So Perl variables, if used, require a double $, and regular expressions ending in $/ must use $$/ instead.

+ +

4) The condition accesses only tags from the file being processed unless the -fileNUM option is used to read an alternate file and the corresponding family 8 group name is specified for the tag. See the -fileNUM option details for more information.

+ +

5) The -a option has no effect on the evaluation of the expression, and the values of duplicate tags are accessible only by specifying a group name (such as a family 4 instance number, eg. $Copy1:TAG, $Copy2:TAG, etc).

+ +

6) A special "OK" UserParam is available to test the success of the previous command when -execute was used, and may be used like any other tag in the condition (ie. "$OK").

+ +

7) The API RequestTags option is automatically set for all tags used in the -if condition.

+ +
+
-m (-ignoreMinorErrors)
+
+ +

Ignore minor errors and warnings. This enables writing to files with minor errors and disables some validation checks which could result in minor warnings. Generally, minor errors/warnings indicate a problem which usually won't result in loss of metadata if ignored. However, there are exceptions, so ExifTool leaves it up to you to make the final decision. Minor errors and warnings are indicated by "[minor]" at the start of the message. Warnings which affect processing when ignored are indicated by "[Minor]" (with a capital "M"). Note that this causes missing values in -tagsFromFile, -p and -if strings to be set to an empty string rather than an undefined value.

+ +
+
-o OUTFILE or FMT (-out)
+
+ +

Set the output file or directory name when writing information. Without this option, when any "real" tags are written the original file is renamed to FILE_original and output is written to FILE. When writing only FileName and/or Directory "pseudo" tags, -o causes the file to be copied instead of moved, but directories specified for either of these tags take precedence over that specified by the -o option.

+ +

OUTFILE may be - to write to stdout. The output file name may also be specified using a FMT string in which %d, %f and %e represent the directory, file name and extension of FILE. Also, %c may be used to add a copy number. See the -w option for FMT string examples.

+ +

The output file is taken to be a directory name if it already exists as a directory or if the name ends with '/'. Output directories are created if necessary. Existing files will not be overwritten. Combining the -overwrite_original option with -o causes the original source file to be erased after the output file is successfully written.

+ +

A special feature of this option allows the creation of certain types of files from scratch, or with the metadata from another type of file. The following file types may be created using this technique:

+ +
    XMP, EXIF, EXV, MIE, ICC/ICM, VRD, DR4
+ +

The output file type is determined by the extension of OUTFILE (specified as -.EXT when writing to stdout). The output file is then created from a combination of information in FILE (as if the -tagsFromFile option was used), and tag values assigned on the command line. If no FILE is specified, the output file may be created from scratch using only tags assigned on the command line.

+ +
+
-overwrite_original
+
+ +

Overwrite the original FILE (instead of preserving it by adding _original to the file name) when writing information to an image. Caution: This option should only be used if you already have separate backup copies of your image files. The overwrite is implemented by renaming a temporary file to replace the original. This deletes the original file and replaces it with the edited version in a single operation. When combined with -o, this option causes the original file to be deleted if the output file was successfully written (ie. the file is moved instead of copied).

+ +
+
-overwrite_original_in_place
+
+ +

Similar to -overwrite_original except that an extra step is added to allow the original file attributes to be preserved. For example, on a Mac this causes the original file creation date, type, creator, label color, icon, Finder tags, other extended attributes and hard links to the file to be preserved (but note that the Mac OS resource fork is always preserved unless specifically deleted with -rsrc:all=). This is implemented by opening the original file in update mode and replacing its data with a copy of a temporary file before deleting the temporary. The extra step results in slower performance, so the -overwrite_original option should be used instead unless necessary.

+ +

Note that this option reverts to the behaviour of the -overwrite_original option when also writing the FileName and/or Directory tags.

+ +
+
-P (-preserve)
+
+ +

Preserve the filesystem modification date/time (FileModifyDate) of the original file when writing. Note that some filesystems store a creation date (ie. FileCreateDate on Windows and Mac systems) which is not affected by this option. This creation date is preserved on Windows systems where Win32API::File and Win32::API are available regardless of this setting. For other systems, the -overwrite_original_in_place option may be used if necessary to preserve the creation date. The -P option is superseded by any value written to the FileModifyDate tag.

+ +
+
-password PASSWD
+
+ +

Specify password to allow processing of password-protected PDF documents. If a password is required but not given, a warning is issued and the document is not processed. This option is ignored if a password is not required.

+ +
+
-progress[NUM][:[TITLE]]
+
+ +

Show the progress when processing files. Without a colon, the -progress option adds a progress count in brackets after the name of each processed file, giving the current file number and the total number of files to be processed. Implies the -v0 option, causing the names of processed files to also be printed when writing. When combined with the -if option, the total count includes all files before the condition is applied, but files that fail the condition will not have their names printed. If NUM is specified, the progress is shown every NUM input files.

+ +

If followed by a colon (ie. -progress:), the console window title is set according to the specified TITLE string. If no TITLE is given, a default TITLE string of "ExifTool %p%%" is assumed. In the string, %f represents the file name, %p is the progress as a percent, %r is the progress as a ratio, %##b is a progress bar of width "##" (where "##" is an integer specifying the bar width in characters, or 20 characters by default if "##" is omitted), and %% is a % character. May be combined with the normal -progress option to also show the progress count in console messages. (Note: For this feature to function correctly on Mac/Linux, stderr must go to the console.)

+ +
+
-q (-quiet)
+
+ +

Quiet processing. One -q suppresses normal informational messages, and a second -q suppresses warnings as well. Error messages can not be suppressed, although minor errors may be downgraded to warnings with the -m option, which may then be suppressed with -q -q.

+ +
+
-r[.] (-recurse)
+
+ +

Recursively process files in subdirectories. Only meaningful if FILE is a directory name. Subdirectories with names beginning with "." are not processed unless "." is added to the option name (ie. -r. or -recurse.). By default, exiftool will also follow symbolic links to directories if supported by the system, but this may be disabled with -i SYMLINKS (see the -i option for details). Combine this with -ext options to control the types of files processed.

+ +
+
-scanForXMP
+
+ +

Scan all files (even unsupported formats) for XMP information unless found already. When combined with the -fast option, only unsupported file types are scanned. Warning: It can be time consuming to scan large files.

+ +
+
-u (-unknown)
+
+ +

Extract values of unknown tags. Add another -u to also extract unknown information from binary data blocks. This option applies to tags with numerical tag ID's, and causes tag names like "Exif_0xc5d9" to be generated for unknown information. It has no effect on information types which have human-readable tag ID's (such as XMP), since unknown tags are extracted automatically from these formats.

+ +
+
-U (-unknown2)
+
+ +

Extract values of unknown tags as well as unknown information from some binary data blocks. This is the same as two -u options.

+ +
+
-wm MODE (-writeMode)
+
+ +

Set mode for writing/creating tags. MODE is a string of one or more characters from the list below. The default write mode is wcg.

+ +
    w - Write existing tags
+    c - Create new tags
+    g - create new Groups as necessary
+ +

For example, use -wm cg to only create new tags (and avoid editing existing ones).

+ +

The level of the group is the SubDirectory level in the metadata structure. For XMP or IPTC this is the full XMP/IPTC block (the family 0 group), but for EXIF this is the individual IFD (the family 1 group).

+ +
+
-z (-zip)
+
+ +

When reading, causes information to be extracted from .gz and .bz2 compressed images (only one image per archive; requires gzip and bzip2 to be available). When writing, causes compressed information to be written if supported by the metadata format (eg. PNG supports compressed textual metadata, JXL supports compressed EXIF and XML, and MIE supports any compressed metadata), disables the recommended padding in embedded XMP (saving 2424 bytes when writing XMP in a file), and writes XMP in shorthand format -- the equivalent of setting the API Compress=1 and Compact="NoPadding,Shorthand".

+ +
+
+ +

Other options

+ +
+ +
-@ ARGFILE
+
+ +

Read command-line arguments from the specified file. The file contains one argument per line (NOT one option per line -- some options require additional arguments, and all arguments must be placed on separate lines). Blank lines and lines beginning with # are ignored (unless they start with #[CSTR], in which case the rest of the line is treated as a C string, allowing standard C escape sequences such as "\n" for a newline). White space at the start of a line is removed. Normal shell processing of arguments is not performed, which among other things means that arguments should not be quoted and spaces are treated as any other character. ARGFILE may exist relative to either the current directory or the exiftool directory unless an absolute pathname is given.

+ +

For example, the following ARGFILE will set the value of Copyright to "Copyright YYYY, Phil Harvey", where "YYYY" is the year of CreateDate:

+ +
    -d
+    %Y
+    -copyright<Copyright $createdate, Phil Harvey
+ +

Arguments in ARGFILE behave exactly the same as if they were entered at the location of the -@ option on the command line, with the exception that the -config and -common_args options may not be used in an ARGFILE.

+ +
+
-k (-pause)
+
+ +

Pause with the message -- press any key -- or -- press RETURN -- (depending on your system) before terminating. This option is used to prevent the command window from closing when run as a Windows drag and drop application.

+ +
+
-list, -listw, -listf, -listr, -listwf, -listg[NUM], -listd, -listx
+
+ +

Print a list of all valid tag names (-list), all writable tag names (-listw), all supported file extensions (-listf), all recognized file extensions (-listr), all writable file extensions (-listwf), all tag groups [in a specified family] (-listg[NUM]), all deletable tag groups (-listd), or an XML database of tag details including language translations (-listx). The -list, -listw and -listx options may be followed by an additional argument of the form -GROUP:All to list only tags in a specific group, where GROUP is one or more family 0-2 group names (excepting EXIF IFD groups) separated by colons. With -listg, NUM may be given to specify the group family, otherwise family 0 is assumed. The -l option may be combined with -listf, -listr or -listwf to add file descriptions to the list. The -lang option may be combined with -listx to output descriptions in a single language. Here are some examples:

+ +
    -list               # list all tag names
+    -list -EXIF:All     # list all EXIF tags
+    -list -xmp:time:all # list all XMP tags relating to time
+    -listw -XMP-dc:All  # list all writable XMP-dc tags
+    -listf              # list all supported file extensions
+    -listr              # list all recognized file extensions
+    -listwf             # list all writable file extensions
+    -listg1             # list all groups in family 1
+    -listd              # list all deletable groups
+    -listx -EXIF:All    # list database of EXIF tags in XML format
+    -listx -XMP:All -s  # list short XML database of XMP tags
+ +

When combined with -listx, the -s option shortens the output by omitting the descriptions and values (as in the last example above), and -f adds 'flags' and 'struct' attributes if applicable. The flags are formatted as a comma-separated list of the following possible values: Avoid, Binary, List, Mandatory, Permanent, Protected, Unknown and Unsafe (see the Tag Name documentation). For XMP List tags, the list type (Alt, Bag or Seq) is added to the flags, and flattened structure tags are indicated by a Flattened flag with 'struct' giving the ID of the parent structure.

+ +

Note that none of the -list options require an input FILE.

+ +
+
-ver
+
+ +

Print exiftool version number. The -v option may be added to print addition system information (see the README file of the full distribution for more details about optional libraries), or -v2 to also list the Perl include directories.

+ +
+
--
+
+ +

Indicates the end of options. Any remaining arguments are treated as file names, even if they begin with a dash (-).

+ +
+
+ +

Special features

+ +
+ +
-geotag TRKFILE
+
+ +

Geotag images from the specified GPS track log file. Using the -geotag option is equivalent to writing a value to the Geotag tag. The GPS position is interpolated from the track at a time specified by the value written to the Geotime tag. If Geotime is not specified, the value is copied from DateTimeOriginal# (the # is added to copy the unformatted value, avoiding potential conflicts with the -d option). For example, the following two commands are equivalent:

+ +
    exiftool -geotag trk.log image.jpg
+    exiftool -geotag trk.log "-Geotime<DateTimeOriginal#" image.jpg
+ +

When the Geotime value is converted to UTC, the local system timezone is assumed unless the date/time value contains a timezone. Writing Geotime causes the following tags to be written (provided they can be calculated from the track log, and they are supported by the destination metadata format): GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef, GPSAltitude, GPSAltitudeRef, GPSDateStamp, GPSTimeStamp, GPSDateTime, GPSTrack, GPSTrackRef, GPSSpeed, GPSSpeedRef, GPSImgDirection, GPSImgDirectionRef, GPSPitch, GPSRoll, AmbientTemperature and CameraElevationAngle. By default, tags are created in EXIF, and updated in XMP only if they already exist. However, EXIF:Geotime or XMP:Geotime may be specified to write only EXIF or XMP tags respectively. Note that GPSPitch and GPSRoll are non-standard, and require user-defined tags in order to be written.

+ +

The Geosync tag may be used to specify a time correction which is applied to each Geotime value for synchronization with GPS time. For example, the following command compensates for image times which are 1 minute and 20 seconds behind GPS:

+ +
    exiftool -geosync=+1:20 -geotag a.log DIR
+ +

Advanced Geosync features allow a piecewise linear time drift correction and synchronization from previously geotagged images. See "geotag.html" in the full ExifTool distribution for more information.

+ +

Multiple -geotag options may be used to concatenate GPS track log data. Also, a single -geotag option may be used to load multiple track log files by using wildcards in the TRKFILE name, but note that in this case TRKFILE must be quoted on most systems (with the notable exception of Windows) to prevent filename expansion. For example:

+ +
    exiftool -geotag "TRACKDIR/*.log" IMAGEDIR
+ +

Currently supported track file formats are GPX, NMEA RMC/GGA/GLL, KML, IGC, Garmin XML and TCX, Magellan PMGNTRK, Honeywell PTNTHPR, Bramor gEO, Winplus Beacon TXT, and GPS/IMU CSV files. See "GEOTAGGING EXAMPLES" for examples. Also see "geotag.html" in the full ExifTool distribution and the Image::ExifTool Options for more details and for information about geotag configuration options.

+ +
+
-globalTimeShift SHIFT
+
+ +

Shift all formatted date/time values by the specified amount when reading. Does not apply to unformatted (-n) output. SHIFT takes the same form as the date/time shift when writing (see Image::ExifTool::Shift.pl for details), with a negative shift being indicated with a minus sign (-) at the start of the SHIFT string. For example:

+ +
    # return all date/times, shifted back by 1 hour
+    exiftool -globalTimeShift -1 -time:all a.jpg
+
+    # set the file name from the shifted CreateDate (-1 day) for
+    # all images in a directory
+    exiftool "-filename<createdate" -globaltimeshift "-0:0:1 0:0:0" \
+        -d %Y%m%d-%H%M%S.%%e dir
+ +
+
-use MODULE
+
+ +

Add features from specified plug-in MODULE. Currently, the MWG module is the only plug-in module distributed with exiftool. This module adds read/write support for tags as recommended by the Metadata Working Group. As a convenience, -use MWG is assumed if the group name prefix starts with MWG: exactly for any requested tag. See the MWG Tags documentation for more details. Note that this option is not reversible, and remains in effect until the application terminates, even across the -execute option.

+ +
+
+ +

Utilities

+ +
+ +
-restore_original
+
+ +
+
-delete_original[!]
+
+ +

These utility options automate the maintenance of the _original files created by exiftool. They have no effect on files without an _original copy. The -restore_original option restores the specified files from their original copies by renaming the _original files to replace the edited versions. For example, the following command restores the originals of all JPG images in directory DIR:

+ +
    exiftool -restore_original -ext jpg DIR
+ +

The -delete_original option deletes the _original copies of all files specified on the command line. Without a trailing ! this option prompts for confirmation before continuing. For example, the following command deletes a.jpg_original if it exists, after asking "Are you sure?":

+ +
    exiftool -delete_original a.jpg
+ +

These options may not be used with other options to read or write tag values in the same command, but may be combined with options such -ext, -if, -r, -q and -v.

+ +
+
+ +

Advanced options

+ +

Among other things, the advanced options allow complex processing to be performed from a single command without the need for additional scripting. This may be particularly useful for implementations such as Windows drag-and-drop applications. These options may also be used to improve performance in multi-pass processing by reducing the overhead required to load exiftool for each invocation.

+ +
+ +
-api [OPT[[^]=[VAL]]]
+
+ +

Set ExifTool API option. OPT is an API option name. The option value is set to 1 if =VAL is omitted. If VAL is omitted, the option value is set to undef if = is used, or an empty string with ^=. If OPT is not specified a list of available options is returned. The option name is not case senstive, but the option values are. See Image::ExifTool Options for option details. This overrides API options set via the config file.

+ +
+
-common_args
+
+ +

Specifies that all arguments following this option are common to all executed commands when -execute is used. This and the -config option are the only options that may not be used inside a -@ ARGFILE. Note that by definition this option and its arguments MUST come after all other options on the command line.

+ +
+
-config CFGFILE
+
+ +

Load specified configuration file instead of the default ".ExifTool_config". If used, this option must come before all other arguments on the command line and applies to all -execute'd commands. The CFGFILE must exist relative to the current working directory or the exiftool application directory unless an absolute path is specified. Loading of the default config file may be disabled by setting CFGFILE to an empty string (ie. ""). See https://exiftool.org/config.html and config_files/example.config in the full ExifTool distribution for details about the configuration file syntax.

+ +
+
-echo[NUM] TEXT
+
+ +

Echo TEXT to stdout (-echo or -echo1) or stderr (-echo2). Text is output as the command line is parsed, before the processing of any input files. NUM may also be 3 or 4 to output text (to stdout or stderr respectively) after processing is complete. For -echo3 and -echo4, "${status}" may be used in the TEXT string to represent the numerical exit status of the command (see "EXIT STATUS").

+ +
+
-efile[NUM][!] ERRFILE
+
+ +

Save the names of files giving errors (NUM missing or 1), files that were unchanged (NUM is 2), files that fail the -if condition (NUM is 4), files that were updated (NUM is 8), files that were created (NUM is 16), or any combination thereof by summing NUM (eg. -efile3 is the same has having both -efile and -efile2 options with the same ERRFILE). By default, file names are appended to any existing ERRFILE, but ERRFILE is overwritten if an exclamation point is added to the option (eg. -efile!). Saves the name of the file specified by the -srcfile option if applicable.

+ +
+
-execute[NUM]
+
+ +

Execute command for all arguments up to this point on the command line (plus any arguments specified by -common_args). The result is as if the commands were executed as separate command lines (with the exception of the -config and -use options which remain in effect for subsequent commands). Allows multiple commands to be executed from a single command line. NUM is an optional number that is echoed in the "{ready}" message when using the -stay_open feature. If a NUM is specified, the -q option no longer suppresses the output "{readyNUM}" message.

+ +
+
-fileNUM ALTFILE
+
+ +

Read tags from an alternate source file. Among other things, this allows tags from different files to be compared and combined using the -if and -p options. Tags from alternate files are accessed via the corresponding family 8 group name (eg. File1:TAG for the -file1 option, File2:TAG for -file2, etc). ALTFILE may contain filename formatting codes like the -w option (%d, %f, etc), and/or tag names with a leading $ symbol to access tags from the source file in the same way as the -p option (so any other dollar symbol in the file name must be doubled, eg. money$$.jpg). For example, assuming that the OriginalFileName tag has been set in the edited file, a command to copy Rights from the original file could look like this:

+ +
    exiftool -file1 '$originalfilename' '-rights<file1:rights' edited.jpg
+ +

Composite tags may access tags from alternate files using the appropriate (case-sensitive) family 8 group name.

+ +
+
-list_dir
+
+ +

List directories themselves instead of their contents. This option effectively causes directories to be treated as normal files when reading and writing. For example, with this option the output of the ls -la command on Mac/Linux may be approximated by this exiftool command:

+ +
    exiftool -list_dir -T -ls-l -api systemtags -fast5 .* *
+ +

(The -T option formats the output in tab-separated columns, -ls-l is a shortcut tag, the API SystemTags option is required to extract some necessary tags, and the -fast5 option is added for speed since only system tags are being extracted.)

+ +
+
-srcfile FMT
+
+ +

Specify a different source file to be processed based on the name of the original FILE. This may be useful in some special situations for processing related preview images or sidecar files. See the -w option for a description of the FMT syntax. Note that file name FMT strings for all options are based on the original FILE specified from the command line, not the name of the source file specified by -srcfile.

+ +

For example, to copy metadata from NEF files to the corresponding JPG previews in a directory where other JPG images may exist:

+ +
    exiftool -ext nef -tagsfromfile @ -srcfile %d%f.jpg dir
+ +

If more than one -srcfile option is specified, the files are tested in order and the first existing source file is processed. If none of the source files already exist, then exiftool uses the first -srcfile specified.

+ +

A FMT of @ may be used to represent the original FILE, which may be useful when specifying multiple -srcfile options (eg. to fall back to processing the original FILE if no sidecar exists).

+ +

When this option is used, two special UserParam tags (OriginalFileName and OriginalDirectory) are generated to allow access to the original FILE name and directory.

+ +
+
-stay_open FLAG
+
+ +

If FLAG is 1 or True (case insensitive), causes exiftool keep reading from the -@ ARGFILE even after reaching the end of file. This feature allows calling applications to pre-load exiftool, thus avoiding the overhead of loading exiftool for each command. The procedure is as follows:

+ +

1) Execute exiftool -stay_open True -@ ARGFILE, where ARGFILE is the name of an existing (possibly empty) argument file or - to pipe arguments from the standard input.

+ +

2) Write exiftool command-line arguments to ARGFILE, one argument per line (see the -@ option for details).

+ +

3) Write -execute\n to ARGFILE, where \n represents a newline sequence. (Note: You may need to flush your write buffers here if using buffered output.) ExifTool will then execute the command with the arguments received up to this point, send a "{ready}" message to stdout when done (unless the -q or -T option is used), and continue trying to read arguments for the next command from ARGFILE. To aid in command/response synchronization, any number appended to the -execute option is echoed in the "{ready}" message. For example, -execute613 results in "{ready613}". When this number is added, -q no longer suppresses the "{ready}" message. (Also, see the -echo3 and -echo4 options for additional ways to pass signals back to your application.)

+ +

4) Repeat steps 2 and 3 for each command.

+ +

5) Write -stay_open\nFalse\n (or -stay_open\n0\n) to ARGFILE when done. This will cause exiftool to process any remaining command-line arguments then exit normally.

+ +

The input ARGFILE may be changed at any time before step 5 above by writing the following lines to the currently open ARGFILE:

+ +
    -stay_open
+    True
+    -@
+    NEWARGFILE
+ +

This causes ARGFILE to be closed, and NEWARGFILE to be kept open. (Without the -stay_open here, exiftool would have returned to reading arguments from ARGFILE after reaching the end of NEWARGFILE.)

+ +

Note: When writing arguments to a disk file there is a delay of up to 0.01 seconds after writing -execute\n before exiftool starts processing the command. This delay may be avoided by sending a CONT signal to the exiftool process immediately after writing -execute\n. (There is no associated delay when writing arguments via a pipe with -@ -, so the signal is not necessary when using this technique.)

+ +
+
-userParam PARAM[[^]=[VAL]]
+
+ +

Set user parameter. PARAM is an arbitrary user parameter name. This is an interface to the API UserParam option (see the Image::ExifTool Options documentation), and provides a method to access user-defined parameters in arguments to the -if and -p options as if they were any other tag. Appending a hash tag (#) to PARAM (eg. -userParam MyTag#=yes) also causes the parameter to be extracted as a normal tag in the UserParam group. Similar to the -api option, the parameter value is set to 1 if =VAL is omitted, undef if just VAL is omitted with =, or an empty string if VAL is omitted with ^=.

+ +
    exiftool -p '$test from $filename' -userparam test=Hello FILE
+ +
+
+ +

Advanced formatting feature

+ +

An advanced formatting feature allows modification of the value of any tag interpolated within a -if or -p option argument, or a -tagsFromFile redirection string. Tag names within these strings are prefixed by a $ symbol, and an arbitrary Perl expression may be applied to the tag value by placing braces around the tag name and inserting the expression after the name, separated by a semicolon (ie. ${TAG;EXPR}). The expression acts on the value of the tag through the default input variable ($_), and has access to the full ExifTool API through the current ExifTool object ($self) and the tag key ($tag). It may contain any valid Perl code, including translation (tr///) and substitution (s///) operations, but note that braces within the expression must be balanced. The example below prints the camera Make with spaces translated to underlines, and multiple consecutive underlines replaced by a single underline:

+ +
    exiftool -p '${make;tr/ /_/;s/__+/_/g}' image.jpg
+ +

An @ may be added after the tag name to make the expression act on individual list items for list-type tags, simplifying list processing. Set $_ to undef to remove an item from the list. As an example, the following command returns all subjects not containing the string "xxx":

+ +
    exiftool -p '${subject@;$_=undef if /xxx/}' image.jpg
+ +

A default expression of tr(/\\?*:|"<>\0)()d is assumed if the expression is empty (ie. ${TAG;}). This removes the characters / \ ? * : | < > and null from the printed value. (These characters are illegal in Windows file names, so this feature is useful if tag values are used in file names.)

+ +

Helper functions

+ +

DateFmt

+ +

Simplifies reformatting of individual date/time values. This function acts on a standard EXIF-formatted date/time value in $_ and formats it according to the specified format string (see the -d option). To avoid trying to reformat an already-formatted date/time value, a # must be added to the tag name (as in the example below) if the -d option is also used. For example:

+ +
    exiftool -p '${createdate#;DateFmt("%Y-%m-%d_%H%M%S")}' a.jpg
+ +

ShiftTime

+ +

Shifts EXIF-formatted date/time string by a specified amount. Start with a leading minus sign to shift backwards in time. See Image::ExifTool::Shift.pl for details about shift syntax. For example, to shift a date/time value back by one year:

+ +
    exiftool -p '${createdate;ShiftTime("-1:0:0 0")}' a.jpg
+ +

NoDups

+ +

Removes duplicate items from a list with a separator specified by the -sep option. This function is most useful when copying list-type tags. For example, the following command may be used to remove duplicate Keywords:

+ +
    exiftool -sep '##' '-keywords<${keywords;NoDups}' a.jpg
+ +

The -sep option is necessary to split the string back into individual list items when writing to a list-type tag.

+ +

An optional flag argument may be set to 1 to cause NoDups to set $_ to undef if no duplicates existed, thus preventing the file from being rewritten unnecessarily:

+ +
    exiftool -sep '##' '-keywords<${keywords;NoDups(1)}' a.jpg
+ +

Note that function names are case sensitive.

+ +

ExifTool 12.64 adds an API NoDups option which makes the NoDups helper function largely redundant, with all the functionality except the ability to avoid rewriting the file if there are no duplicates, but with the advantage the duplicates may be removed when accumulating list items from multiple sources. An equivalent to the above commands using this feature would be:

+ +
    exiftool -tagsfromfile @ -keywords -api nodups a.jpg
+ +

WINDOWS UNICODE FILE NAMES

+ +

In Windows, command-line arguments are specified using the current code page and are recoded automatically to the system code page. This recoding is not done for arguments in ExifTool arg files, so by default filenames in arg files use the system code page. Unfortunately, these code pages are not complete character sets, so not all file names may be represented.

+ +

ExifTool 9.79 and later allow the file name encoding to be specified with -charset filename=CHARSET, where CHARSET is the name of a valid ExifTool character set, preferably UTF8 (see the -charset option for a complete list). Setting this triggers the use of Windows wide-character i/o routines, thus providing support for most Unicode file names (see note 4). But note that it is not trivial to pass properly encoded file names on the Windows command line (see https://exiftool.org/faq.html#Q18 for details), so placing them in a UTF-8 encoded -@ argfile and using -charset filename=utf8 is recommended if possible.

+ +

A warning is issued if a specified filename contains special characters and the filename character set was not provided. However, the warning may be disabled by setting -charset filename="", and ExifTool may still function correctly if the system code page matches the character set used for the file names.

+ +

When a directory name is provided, the file name encoding need not be specified (unless the directory name contains special characters), and ExifTool will automatically use wide-character routines to scan the directory.

+ +

The filename character set applies to the FILE arguments as well as filename arguments of -@, -geotag, -o, -p, -srcfile, -tagsFromFile, -csv=, -j= and -TAG<=. However, it does not apply to the -config filename, which always uses the system character set. The -charset filename= option must come before the -@ option to be effective, but the order doesn't matter with respect to other options.

+ +

Notes:

+ +

1) FileName and Directory tag values still use the same encoding as other tag values, and are converted to/from the filename character set when writing/reading if specified.

+ +

2) Unicode support is not yet implemented for other Windows-based systems like Cygwin.

+ +

3) See "WRITING READ-ONLY FILES" below for a note about editing read-only files with Unicode names.

+ +

4) Unicode file names with surrogate pairs (code points over U+FFFF) still cause problems.

+ +

WRITING READ-ONLY FILES

+ +

In general, ExifTool may be used to write metadata to read-only files provided that the user has write permission in the directory. However, there are three cases where file write permission is also required:

+ +

1) When using the -overwrite_original_in_place option.

+ +

2) When writing only pseudo System tags (eg. FileModifyDate).

+ +

3) On Windows if the file has Unicode characters in its name, and a) the -overwrite_original option is used, or b) the _original backup already exists.

+ +

Hidden files in Windows behave as read-only files when attempting to write any real tags to the file -- an error is generated when using the -overwrite_original_in_place, otherwise writing should be successful and the hidden attribute will be removed. But the -if option may be used to avoid processing hidden files (provided Win32API::File is available):

+ +
    exiftool -if "$fileattributes !~ /Hidden/" ...
+ +

READING EXAMPLES

+ +

Note: Beware when cutting and pasting these examples into your terminal! Some characters such as single and double quotes and hyphens may have been changed into similar-looking yet functionally-different characters by the text formatter used to display this documentation. Also note that Windows users must use double quotes instead of single quotes as below around arguments containing special characters.

+ +
+ +
exiftool -a -u -g1 a.jpg
+
+ +

Print all meta information in an image, including duplicate and unknown tags, sorted by group (for family 1). For performance reasons, this command may not extract all available metadata. (Metadata in embedded documents, metadata extracted by external utilities, and metadata requiring excessive processing time may not be extracted). Add -ee3 and -api RequestAll=3 to the command to extract absolutely everything available.

+ +
+
exiftool -common dir
+
+ +

Print common meta information for all images in dir. -common is a shortcut tag representing common EXIF meta information.

+ +
+
exiftool -T -createdate -aperture -shutterspeed -iso dir > out.txt
+
+ +

List specified meta information in tab-delimited column form for all images in dir to an output text file named "out.txt".

+ +
+
exiftool -s -ImageSize -ExposureTime b.jpg
+
+ +

Print ImageSize and ExposureTime tag names and values.

+ +
+
exiftool -l -canon c.jpg d.jpg
+
+ +

Print standard Canon information from two image files.

+ +
+
exiftool -r -w .txt -common pictures
+
+ +

Recursively extract common meta information from files in pictures directory, writing text output to .txt files with the same names.

+ +
+
exiftool -b -ThumbnailImage image.jpg > thumbnail.jpg
+
+ +

Save thumbnail image from image.jpg to a file called thumbnail.jpg.

+ +
+
exiftool -b -JpgFromRaw -w _JFR.JPG -ext NEF -r .
+
+ +

Recursively extract JPG image from all Nikon NEF files in the current directory, adding _JFR.JPG for the name of the output JPG files.

+ +
+
exiftool -a -b -W %d%f_%t%-c.%s -preview:all dir
+
+ +

Extract all types of preview images (ThumbnailImage, PreviewImage, JpgFromRaw, etc.) from files in directory "dir", adding the tag name to the output preview image file names.

+ +
+
exiftool -d '%r %a, %B %e, %Y' -DateTimeOriginal -S -s -ext jpg .
+
+ +

Print formatted date/time for all JPG files in the current directory.

+ +
+
exiftool -IFD1:XResolution -IFD1:YResolution image.jpg
+
+ +

Extract image resolution from EXIF IFD1 information (thumbnail image IFD).

+ +
+
exiftool '-*resolution*' image.jpg
+
+ +

Extract all tags with names containing the word "Resolution" from an image.

+ +
+
exiftool -xmp:author:all -a image.jpg
+
+ +

Extract all author-related XMP information from an image.

+ +
+
exiftool -xmp -b a.jpg > out.xmp
+
+ +

Extract complete XMP data record intact from a.jpg and write it to out.xmp using the special XMP tag (see the Extra tags in Image::ExifTool::TagNames).

+ +
+
exiftool -p '$filename has date $dateTimeOriginal' -q -f dir
+
+ +

Print one line of output containing the file name and DateTimeOriginal for each image in directory dir.

+ +
+
exiftool -ee3 -p '$gpslatitude, $gpslongitude, $gpstimestamp' a.m2ts
+
+ +

Extract all GPS positions from an AVCHD video.

+ +
+
exiftool -icc_profile -b -w icc image.jpg
+
+ +

Save complete ICC_Profile from an image to an output file with the same name and an extension of .icc.

+ +
+
exiftool -htmldump -w tmp/%f_%e.html t/images
+
+ +

Generate HTML pages from a hex dump of EXIF information in all images from the t/images directory. The output HTML files are written to the tmp directory (which is created if it didn't exist), with names of the form 'FILENAME_EXT.html'.

+ +
+
exiftool -a -b -ee -embeddedimage -W Image_%.3g3.%s file.pdf
+
+ +

Extract embedded JPG and JP2 images from a PDF file. The output images will have file names like "Image_#.jpg" or "Image_#.jp2", where "#" is the ExifTool family 3 embedded document number for the image.

+ +
+
+ +

WRITING EXAMPLES

+ +

Note that quotes are necessary around arguments which contain certain special characters such as >, < or any white space. These quoting techniques are shell dependent, but the examples below will work for most Unix shells. With the Windows cmd shell however, double quotes should be used (eg. -Comment="This is a new comment").

+ +
+ +
exiftool -Comment='This is a new comment' dst.jpg
+
+ +

Write new comment to a JPG image (replaces any existing comment).

+ +
+
exiftool -comment= -o newdir -ext jpg .
+
+ +

Remove comment from all JPG images in the current directory, writing the modified images to a new directory.

+ +
+
exiftool -keywords=EXIF -keywords=editor dst.jpg
+
+ +

Replace existing keyword list with two new keywords (EXIF and editor).

+ +
+
exiftool -Keywords+=word -o newfile.jpg src.jpg
+
+ +

Copy a source image to a new file, and add a keyword (word) to the current list of keywords.

+ +
+
exiftool -exposurecompensation+=-0.5 a.jpg
+
+ +

Decrement the value of ExposureCompensation by 0.5 EV. Note that += with a negative value is used for decrementing because the -= operator is used for conditional deletion (see next example).

+ +
+
exiftool -credit-=xxx dir
+
+ +

Delete Credit information from all files in a directory where the Credit value was xxx.

+ +
+
exiftool -xmp:description-de='k&uuml;hl' -E dst.jpg
+
+ +

Write alternate language for XMP:Description, using HTML character escaping to input special characters.

+ +
+
exiftool -all= dst.jpg
+
+ +

Delete all meta information from an image. Note: You should NOT do this to RAW images (except DNG) since proprietary RAW image formats often contain information in the makernotes that is necessary for converting the image.

+ +
+
exiftool -all= -comment='lonely' dst.jpg
+
+ +

Delete all meta information from an image and add a comment back in. (Note that the order is important: -comment='lonely' -all= would also delete the new comment.)

+ +
+
exiftool -all= --jfif:all dst.jpg
+
+ +

Delete all meta information except JFIF group from an image.

+ +
+
exiftool -Photoshop:All= dst.jpg
+
+ +

Delete Photoshop meta information from an image (note that the Photoshop information also includes IPTC).

+ +
+
exiftool -r -XMP-crss:all= DIR
+
+ +

Recursively delete all XMP-crss information from images in a directory.

+ +
+
exiftool '-ThumbnailImage<=thumb.jpg' dst.jpg
+
+ +

Set the thumbnail image from specified file (Note: The quotes are necessary to prevent shell redirection).

+ +
+
exiftool '-JpgFromRaw<=%d%f_JFR.JPG' -ext NEF -r .
+
+ +

Recursively write JPEG images with filenames ending in _JFR.JPG to the JpgFromRaw tag of like-named files with extension .NEF in the current directory. (This is the inverse of the -JpgFromRaw command of the "READING EXAMPLES" section above.)

+ +
+
exiftool -DateTimeOriginal-='0:0:0 1:30:0' dir
+
+ +

Adjust original date/time of all images in directory dir by subtracting one hour and 30 minutes. (This is equivalent to -DateTimeOriginal-=1.5. See Image::ExifTool::Shift.pl for details.)

+ +
+
exiftool -createdate+=3 -modifydate+=3 a.jpg b.jpg
+
+ +

Add 3 hours to the CreateDate and ModifyDate timestamps of two images.

+ +
+
exiftool -AllDates+=1:30 -if '$make eq "Canon"' dir
+
+ +

Shift the values of DateTimeOriginal, CreateDate and ModifyDate forward by 1 hour and 30 minutes for all Canon images in a directory. (The AllDates tag is provided as a shortcut for these three tags, allowing them to be accessed via a single tag.)

+ +
+
exiftool -xmp:city=Kingston image1.jpg image2.nef
+
+ +

Write a tag to the XMP group of two images. (Without the xmp: this tag would get written to the IPTC group since City exists in both, and IPTC is preferred by default.)

+ +
+
exiftool -LightSource-='Unknown (0)' dst.tiff
+
+ +

Delete LightSource tag only if it is unknown with a value of 0.

+ +
+
exiftool -whitebalance-=auto -WhiteBalance=tung dst.jpg
+
+ +

Set WhiteBalance to Tungsten only if it was previously Auto.

+ +
+
exiftool -comment-= -comment='new comment' a.jpg
+
+ +

Write a new comment only if the image doesn't have one already.

+ +
+
exiftool -o %d%f.xmp dir
+
+ +

Create XMP meta information data files for all images in dir.

+ +
+
exiftool -o test.xmp -owner=Phil -title='XMP File'
+
+ +

Create an XMP data file only from tags defined on the command line.

+ +
+
exiftool '-ICC_Profile<=%d%f.icc' image.jpg
+
+ +

Write ICC_Profile to an image from a .icc file of the same name.

+ +
+
exiftool -hierarchicalkeywords='{keyword=one,children={keyword=B}}'
+
+ +

Write structured XMP information. See https://exiftool.org/struct.html for more details.

+ +
+
exiftool -trailer:all= image.jpg
+
+ +

Delete any trailer found after the end of image (EOI) in a JPEG file. A number of digital cameras store a large PreviewImage after the JPEG EOI, and the file size may be reduced significantly by deleting this trailer. See the JPEG Tags documentation for a list of recognized JPEG trailers.

+ +
+
+ +

COPYING EXAMPLES

+ +

These examples demonstrate the ability to copy tag values between files.

+ +
+ +
exiftool -tagsFromFile src.cr2 dst.jpg
+
+ +

Copy the values of all writable tags from src.cr2 to dst.jpg, writing the information to same-named tags in the preferred groups.

+ +
+
exiftool -TagsFromFile src.jpg -all:all dst.jpg
+
+ +

Copy the values of all writable tags from src.jpg to dst.jpg, preserving the original tag groups.

+ +
+
exiftool -all= -tagsfromfile src.jpg -exif:all dst.jpg
+
+ +

Erase all meta information from dst.jpg image, then copy EXIF tags from src.jpg.

+ +
+
exiftool -exif:all= -tagsfromfile @ -all:all -unsafe bad.jpg
+
+ +

Rebuild all EXIF meta information from scratch in an image. This technique can be used in JPEG images to repair corrupted EXIF information which otherwise could not be written due to errors. The Unsafe tag is a shortcut for unsafe EXIF tags in JPEG images which are not normally copied. See the tag name documentation for more details about unsafe tags.

+ +
+
exiftool -Tagsfromfile a.jpg out.xmp
+
+ +

Copy meta information from a.jpg to an XMP data file. If the XMP data file out.xmp already exists, it will be updated with the new information. Otherwise the XMP data file will be created. Only metadata-only files may be created like this (files containing images may be edited but not created). See "WRITING EXAMPLES" above for another technique to generate XMP files.

+ +
+
exiftool -tagsFromFile a.jpg -XMP:All= -ThumbnailImage= -m b.jpg
+
+ +

Copy all meta information from a.jpg to b.jpg, deleting all XMP information and the thumbnail image from the destination.

+ +
+
exiftool -TagsFromFile src.jpg -title -author=Phil dst.jpg
+
+ +

Copy title from one image to another and set a new author name.

+ +
+
exiftool -TagsFromFile a.jpg -ISO -TagsFromFile b.jpg -comment dst.jpg
+
+ +

Copy ISO from one image and Comment from another image to a destination image.

+ +
+
exiftool -tagsfromfile src.jpg -exif:all --subifd:all dst.jpg
+
+ +

Copy only the EXIF information from one image to another, excluding SubIFD tags.

+ +
+
exiftool '-FileModifyDate<DateTimeOriginal' dir
+
+ +

Use the original date from the meta information to set the same file's filesystem modification date for all images in a directory. (Note that -TagsFromFile @ is assumed if no other -TagsFromFile is specified when redirecting information as in this example.)

+ +
+
exiftool -TagsFromFile src.jpg '-xmp:all<all' dst.jpg
+
+ +

Copy all possible information from src.jpg and write in XMP format to dst.jpg.

+ +
+
exiftool '-Description<${FileName;s/\.[^.]*$//}' dir
+
+ +

Set the image Description from the file name after removing the extension. This example uses the "Advanced formatting feature" to perform a substitution operation to remove the last dot and subsequent characters from the file name.

+ +
+
exiftool -@ iptc2xmp.args -iptc:all= a.jpg
+
+ +

Translate IPTC information to XMP with appropriate tag name conversions, and delete the original IPTC information from an image. This example uses iptc2xmp.args, which is a file included with the ExifTool distribution that contains the required arguments to convert IPTC information to XMP format. Also included with the distribution are xmp2iptc.args (which performs the inverse conversion) and a few more .args files for other conversions between EXIF, IPTC and XMP.

+ +
+
exiftool -tagsfromfile %d%f.CR2 -r -ext JPG dir
+
+ +

Recursively rewrite all JPG images in dir with information copied from the corresponding CR2 images in the same directories.

+ +
+
exiftool '-keywords+<make' image.jpg
+
+ +

Add camera make to list of keywords.

+ +
+
exiftool '-comment<ISO=$exif:iso Exposure=${shutterspeed}' dir
+
+ +

Set the Comment tag of all images in dir from the values of the EXIF:ISO and ShutterSpeed tags. The resulting comment will be in the form "ISO=100 Exposure=1/60".

+ +
+
exiftool -TagsFromFile src.jpg -icc_profile dst.jpg
+
+ +

Copy ICC_Profile from one image to another.

+ +
+
exiftool -TagsFromFile src.jpg -all:all dst.mie
+
+ +

Copy all meta information in its original form from a JPEG image to a MIE file. The MIE file will be created if it doesn't exist. This technique can be used to store the metadata of an image so it can be inserted back into the image (with the inverse command) later in a workflow.

+ +
+
exiftool -o dst.mie -all:all src.jpg
+
+ +

This command performs exactly the same task as the command above, except that the -o option will not write to an output file that already exists.

+ +
+
exiftool -b -jpgfromraw -w %d%f_%ue.jpg -execute -b -previewimage -w %d%f_%ue.jpg -execute -tagsfromfile @ -srcfile %d%f_%ue.jpg -overwrite_original -common_args --ext jpg DIR
+
+ +

[Advanced] Extract JpgFromRaw or PreviewImage from all but JPG files in DIR, saving them with file names like image_EXT.jpg, then add all meta information from the original files to the extracted images. Here, the command line is broken into three sections (separated by -execute options), and each is executed as if it were a separate command. The -common_args option causes the --ext jpg DIR arguments to be applied to all three commands, and the -srcfile option allows the extracted JPG image to be the source file for the third command (whereas the RAW files are the source files for the other two commands).

+ +
+
+ +

RENAMING EXAMPLES

+ +

By writing the FileName and Directory tags, files are renamed and/or moved to new directories. This can be particularly useful and powerful for organizing files by date when combined with the -d option. New directories are created as necessary, but existing files will not be overwritten. The format codes %d, %f and %e may be used in the new file name to represent the directory, name and extension of the original file, and %c may be used to add a copy number if the file already exists (see the -w option for details). Note that if used within a date format string, an extra '%' must be added to pass these codes through the date/time parser. (And further note that in a Windows batch file, all '%' characters must also be escaped, so in this extreme case '%%%%f' is necessary to pass a simple '%f' through the two levels of parsing.) See https://exiftool.org/filename.html for additional documentation and examples.

+ +
+ +
exiftool -filename=new.jpg dir/old.jpg
+
+ +

Rename old.jpg to new.jpg in directory dir.

+ +
+
exiftool -directory=%e dir
+
+ +

Move all files from directory dir into directories named by the original file extensions.

+ +
+
exiftool '-Directory<DateTimeOriginal' -d %Y/%m/%d dir
+
+ +

Move all files in dir into a directory hierarchy based on year, month and day of DateTimeOriginal. eg) This command would move the file dir/image.jpg with a DateTimeOriginal of 2005:10:12 16:05:56 to 2005/10/12/image.jpg.

+ +
+
exiftool -o . '-Directory<DateTimeOriginal' -d %Y/%m/%d dir
+
+ +

Same effect as above except files are copied instead of moved.

+ +
+
exiftool '-filename<%f_${model;}.%e' dir
+
+ +

Rename all files in dir by adding the camera model name to the file name. The semicolon after the tag name inside the braces causes characters which are invalid in Windows file names to be deleted from the tag value (see the "Advanced formatting feature" for an explanation).

+ +
+
exiftool '-FileName<CreateDate' -d %Y%m%d_%H%M%S%%-c.%%e dir
+
+ +

Rename all images in dir according to the CreateDate date and time, adding a copy number with leading '-' if the file already exists (%-c), and preserving the original file extension (%e). Note the extra '%' necessary to escape the filename codes (%c and %e) in the date format string.

+ +
+
exiftool -r '-FileName<CreateDate' -d %Y-%m-%d/%H%M_%%f.%%e dir
+
+ +

Both the directory and the filename may be changed together via the FileName tag if the new FileName contains a '/'. The example above recursively renames all images in a directory by adding a CreateDate timestamp to the start of the filename, then moves them into new directories named by date.

+ +
+
exiftool '-FileName<${CreateDate}_$filenumber.jpg' -d %Y%m%d -ext jpg .
+
+ +

Set the filename of all JPG images in the current directory from the CreateDate and FileNumber tags, in the form "20060507_118-1861.jpg".

+ +
+
+ +

GEOTAGGING EXAMPLES

+ +

ExifTool implements geotagging from GPS log files via 3 special tags: Geotag (which for convenience is also implemented as an exiftool option), Geosync and Geotime. The examples below highlight some geotagging features. See https://exiftool.org/geotag.html for additional documentation. (Note that geotagging from known GPS coordinates is done by writing the GPS tags directly rather than using the -geotag option.)

+ +
+ +
exiftool -geotag track.log a.jpg
+
+ +

Geotag an image (a.jpg) from position information in a GPS track log (track.log). Since the Geotime tag is not specified, the value of DateTimeOriginal is used for geotagging. Local system time is assumed unless DateTimeOriginal contains a timezone.

+ +
+
exiftool -geotag t.log -geotime='2009:04:02 13:41:12-05:00' a.jpg
+
+ +

Geotag an image with the GPS position for a specific time.

+ +
+
exiftool -geotag log.gpx '-xmp:geotime<createdate' dir
+
+ +

Geotag all images in directory dir with XMP tags instead of EXIF tags, based on the image CreateDate.

+ +
+
exiftool -geotag a.log -geosync=-20 dir
+
+ +

Geotag images in directory dir, accounting for image timestamps which were 20 seconds ahead of GPS.

+ +
+
exiftool -geotag a.log -geosync=1.jpg -geosync=2.jpg dir
+
+ +

Geotag images using time synchronization from two previously geotagged images (1.jpg and 2.jpg), synchronizing the image and GPS times using a linear time drift correction.

+ +
+
exiftool -geotag a.log '-geotime<${createdate}+01:00' dir
+
+ +

Geotag images in dir using CreateDate with the specified timezone. If CreateDate already contained a timezone, then the timezone specified on the command line is ignored.

+ +
+
exiftool -geotag= a.jpg
+
+ +

Delete GPS tags which may have been added by the geotag feature. Note that this does not remove all GPS tags -- to do this instead use -gps:all=.

+ +
+
exiftool -xmp:geotag= a.jpg
+
+ +

Delete XMP GPS tags which were added by the geotag feature.

+ +
+
exiftool -xmp:geotag=track.log a.jpg
+
+ +

Geotag an image with XMP tags, using the time from DateTimeOriginal.

+ +
+
exiftool -geotag a.log -geotag b.log -r dir
+
+ +

Combine multiple track logs and geotag an entire directory tree of images.

+ +
+
exiftool -geotag 'tracks/*.log' -r dir
+
+ +

Read all track logs from the tracks directory.

+ +
+
exiftool -p gpx.fmt dir > out.gpx
+
+ +

Generate a GPX track log from all images in directory dir. This example uses the gpx.fmt file included in the full ExifTool distribution package and assumes that the images in dir have all been previously geotagged.

+ +
+
+ +

PIPING EXAMPLES

+ +
+ +
cat a.jpg | exiftool -
+
+ +

Extract information from stdin.

+ +
+
exiftool image.jpg -thumbnailimage -b | exiftool -
+
+ +

Extract information from an embedded thumbnail image.

+ +
+
cat a.jpg | exiftool -iptc:keywords+=fantastic - > b.jpg
+
+ +

Add an IPTC keyword in a pipeline, saving output to a new file.

+ +
+
curl -s http://a.domain.com/bigfile.jpg | exiftool -fast -
+
+ +

Extract information from an image over the internet using the cURL utility. The -fast option prevents exiftool from scanning for trailer information, so only the meta information header is transferred.

+ +
+
exiftool a.jpg -thumbnailimage -b | exiftool -comment=wow - | exiftool a.jpg -thumbnailimage'<=-'
+
+ +

Add a comment to an embedded thumbnail image. (Why anyone would want to do this I don't know, but I've included this as an example to illustrate the flexibility of ExifTool.)

+ +
+
+ +

INTERRUPTING EXIFTOOL

+ +

Interrupting exiftool with a CTRL-C or SIGINT will not result in partially written files or temporary files remaining on the hard disk. The exiftool application traps SIGINT and defers it until the end of critical processes if necessary, then does a proper cleanup before exiting.

+ +

EXIT STATUS

+ +

The exiftool application exits with a status of 0 on success, or 1 if an error occurred, or 2 if all files failed the -if condition (for any of the commands if -execute was used).

+ +

AUTHOR

+ +

Copyright 2003-2023, Phil Harvey

+ +

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

+ +

SEE ALSO

+ +

Image::ExifTool(3pm), Image::ExifTool::TagNames(3pm), Image::ExifTool::Shortcuts(3pm), Image::ExifTool::Shift.pl

+ + + +
+ exiftool Application Documentation +
+ + + + + + diff --git a/ExifTool/html/faq.html b/ExifTool/html/faq.html new file mode 100644 index 0000000..96c4461 --- /dev/null +++ b/ExifTool/html/faq.html @@ -0,0 +1,1825 @@ + + + + ExifTool FAQ + + + + + + +

ExifTool FAQ

+ + +

1. "Is there a forum for discussing ExifTool issues?"

+ +
+ExifTool issues can be discussed on the ExifTool forum at +https://exiftool.org/forum/ +
+ + +

2. "How do I determine the tag name for some information?"

+ +
When you run exiftool, by default it prints descriptions, not tag +names, for the information it extracts. These descriptions are in English +unless the -lang option is used to select another language. Note +that descriptions often contain spaces between words, but tag names never do. +Also, tag names are always English, regardless of the -lang +setting. To print the tag names instead instead of descriptions, use the +-s option when extracting information. eg) +
exiftool -s image.jpg
+Valid characters in tag names are A-Z, a-z, +0-9, _ and -. See the +tag name documentation for a complete list of +available tag names.
+ +
Tag names may be optionally prefixed by a group name to specify a +particular information type or location. Use the -g0, +-g1, etc (or -G0, -G1, etc) options when +extracting information to see the corresponding group names. See the Tag Groups section of the ExifTool home page for +more information.
+ + +

3a. "ExifTool reports the wrong value or doesn't extract a tag", +
3b. "ExifTool doesn't write a tag properly", or +
3c. "Other software can't read information written by ExifTool"

+ +
[Also see FAQ number 23 for reasons why +ExifTool may not write some tags to certain file types.]
+ +
First, make sure you are looking at the right information. Use +ExifTool with a command like this to extract all information from the file, +along with the location it was written: + +
exiftool -a -G1 -s c:\images\test.jpg
+ +In this command, -a allows duplicate tags to be extracted, +-G1 shows the family 1 group name (ie. the location) of each tag, +and -s shows the tag names instead of their descriptions. +(Substitute the path name of your file in place of +"c:\images\test.jpg".)
+ +
When duplicate tags exist, only one is extracted unless the +-a option is used. Beware that options like -EXIF:all +select all EXIF tags from the extracted tags, so EXIF tags hidden by duplicate +tags in other locations will not appear in the output for +-EXIF:all. For example, the command + +
exiftool -gps:all image.jpg
+ +will NOT necessarily extract all EXIF GPS tags because some may have been +suppressed by same-named tags in other groups. To be sure all EXIF GPS tags are +extracted, the -a option must be used: + +
exiftool -a -gps:all image.jpg
+ +[But note that GPS information may be stored in metadata formats +other than just EXIF, so restricting the command to the EXIF "GPS" group may +miss GPS information in other formats. The following command is better suited +to extract all GPS from a wide variety of file formats:] + +
exiftool -a "-gps*" -ee video.mp4
+ +If you are having problems with other software reading information written by +ExifTool, if possible try first writing the information from the other software, +then use ExifTool (with the -a and -G1 options) to +determine where the information was written. Once you know where it should go, +you can use ExifTool to write to this location. You can read or write +information in a specific location by prefixing the tag name on the command line +with the desired group name. eg.) "-ExifIFD:DateTimeOriginal" +
+ +
This problem may also occur if contradictory information exists in +different meta information formats within the same file. For example, often XMP +will be ignored if IPTC exists and the Photoshop:IPTCDigest does not agree with +the IPTC content. The +Metadata Working Group +recommends techniques to keep the EXIF, IPTC and XMP metadata synchronized. +These recommendations are implemented by the ExifTool +MWG tags. For maximum compatibility with the +widest range of applications, it is suggested that these MWG tags be used +whenever possible.
+ +
One final note: When writing, the -v2 option may be +useful because it provides details about what ExifTool is writing, and where. +
+ + +

4. "ExifTool reports more than one shutter speed or aperture value, and +they are slightly different"

+ +
+There are a number of different ways that aperture and shutter speed information +are stored in many images. The standard EXIF values (EXIF:FNumber and +EXIF:ExposureTime) should correspond to the values displayed by your camera, but +these values may have been rounded off by the camera and/or ExifTool. (You can +use the -n option to avoid rounding by ExifTool and show the full +value in decimal form.) The corresponding EXIF APEX values (EXIF:ApertureValue +and EXIF:ShutterSpeedValue) may be different due to their own round-off errors. +If available, the MakerNotes values may be the most accurate because they +haven't been rounded off to nice even values for display, so with these you may +see odd values like 1/102 instead of 1/100, etc. +
+ + +

5. "How do I format date and time information for writing?"

+ +
All information (including date/time information) is written in the +same format as it is read out. When reading, ExifTool converts all date and +time information to standard EXIF format, so this is also the way it is +specified when writing. The standard EXIF date/time format is +"YYYY:mm:dd HH:MM:SS", and some meta information formats such as +XMP also allow sub-seconds and a timezone to be specified. The timezone format +is "+HH:MM", "-HH:MM" or "Z". For +example: + +
exiftool -xmp:dateTimeOriginal="2005:10:23 20:06:34.33-05:00" a.jpg
+
+ +When writing XMP or other information types which allow incomplete date/time +values, the following input formats are also accepted: + +
YYYY
+YYYY:mm
+YYYY:mm:dd
+YYYY:mm:dd HH:MM
+
+ +Having said this, ExifTool is very flexible about the actual format of input +date/time values when writing, and will attempt to reformat any values into the +standard format unless the -n option is used. Any separators may +be used (or in fact, none at all). The first 4 consecutive digits found in the +value are interpreted as the year, then next 2 digits are the month, and so on. +[The year must be 4 digits. Other fields are expected to be 2 +digits, but a single digit is allowed if the subsequent character is a +non-digit.] For EXIF date/time values, all 6 date/time fields must exist +("YYYYmmddHHMMSS"), but XMP date/time values require only the year +("YYYY"). This feature facilitates useful operations such as +setting date/time tags from a date embedded in the file name. For example, the +command + +
exiftool "-alldates<filename" c:\images
+ +will set the common date/time tags from the file name for all images in the +directory "c:\images". This will work for any file name which +matches the above criteria (eg. "IMG_20110927_103000.jpg"). +[AllDates is a shortcut for 3 tag names: DateTimeOriginal, +CreateDate and ModifyDate. See the Shortcuts +Tags documentation for more information.]
+ +
The -d option provides additional flexibility in +parsing strings when writing date/time tags with ExifTool 10.32 or later if +POSIX::strptime or Time::Piece is installed (use "exiftool -ver -v" +to check the installed packages). The format of the -d argument is +the same for reading and writing.
+ +
The -n option may be used to disable all of the +date/time reformatting when reading and writing which will allow otherwise +invalid date/time values to be written (eg. partial EXIF dates). The +reformatting may be disabled on a per-tag basis by adding "#" +to the tag name instead of using -n
+ +
Special feature: A value of "now" may be used to +represent the current time when writing any date/time tag. For example: +
exiftool -xmp:dateTimeOriginal=now a.jpg
+[There is also a Now tag which may +be used for a similar purpose by copying its value to another tag, but copying +tags adds an extra read stage to the processing which is best avoided if +performance is an issue.] +
+ + +

6. "I get 'Can't convert TAG (not in PrintConv)' errors when +writing a tag"

+ +
+By default, ExifTool applies a print conversion (PrintConv) to extracted +information to make the output more human-readable. Some conversions involve +lookup tables which are documented in the Values column of the +tag name documentation. For example, the +GPSAltitudeRef tag defines the following conversions: +
0 = Above Sea Level
+1 = Below Sea Level
+
+For this tag, a value of '0' is printed as 'Above Sea Level', and '1' is printed +as 'Below Sea Level'. Reading and writing with ExifTool is symmetrical [with +the possible exception of list-type tags -- see FAQ number 17 +below], so a value that is printed as 'Above Sea Level' must also be written in +that form. (In other words, the inverse print conversion is applied when writing +values.) For example, to write GPSAltitudeRef you can type: +
exiftool -gpsaltituderef="Above Sea Level" image.jpg
+
+or any unambiguous short form may be used and ExifTool will know what you mean, eg) +
exiftool -gpsaltituderef=above image.jpg
+
+Alternatively, the print conversion can be disabled for all tags with the +-n option, or for individual tags by suffixing the tag name with a +'#' character. In either case the printed value of GPSAltitudeRef +will be '0' or '1' when extracting information, and the value is written in the +same way. So following two commands have exactly the same effect as above: +
exiftool -gpsaltituderef=0 -n image.jpg
+exiftool -gpsaltituderef#=0 image.jpg
+
+Integer values may also be specified in hexadecimal (with a leading '0x'). For +example, the following commands are all equivalent: +
exiftool -flash=1 -n image.jpg
+exiftool -flash=0x1 -n image.jpg
+exiftool -flash#=1 image.jpg
+exiftool -flash#=0x1 image.jpg
+exiftool -flash=fired image.jpg
+
+Programmers: These techniques look like this when calling Image::ExifTool +functions from a Perl script: +
$exifTool->SetNewValue(flash => 1, Type => 'ValueConv');
+$exifTool->SetNewValue(flash => 0x1, Type => 'ValueConv');
+$exifTool->SetNewValue('flash#' => 1);
+$exifTool->SetNewValue('flash#' => 0x1);
+$exifTool->SetNewValue(flash => 'fired');
+
+ + +

7. "I can't delete all EXIF information from a TIFF file using +'exiftool -exif:all= img.tif'"

+ +
This is because of the way a TIFF file is structured. With a JPEG +image, this command removes IFD0 (the main Image File Directory) as well as any +subdirectories, thus removing all EXIF information. But with the TIFF format, +the main image itself is stored in IFD0, so deleting this directory would +destroy the image. The same is true for any TIFF-based RAW file such as DNG, +CR2, NEF, etc. For these types of files, ExifTool just deletes the ExifIFD +subdirectory, so any information stored in other directories is preserved (BUT +NOTE THAT WITH PROPRIETARY RAW FORMATS THIS MAY DELETE INFORMATION THAT IS +NECESSARY FOR PROPER RENDERING OF THE IMAGE). +
+ +
Use "exiftool -a -G1 -s img.tif" to see where the +information is stored. Any tags remaining in other IFD's must be deleted +individually from a TIFF-format file if desired. For convenience, a CommonIFD0 +shortcut tag is provided to simplify the +deletion of common metadata tags from IFD0 by adding "-CommonIFD0=" +to the command line. +
+ + +

8a. "All maker note information is lost if I change the Make or Model tag", or +
8b. "I can't copy maker note information to an image", or +
8c. "I can't view a RAW image after changing the model tag"

+ +
+The Make and Model tags are used by some image utilities (including ExifTool) to +determine the format of the maker note information. Deleting or changing either +of these tags may prevent these utilities from recognizing or properly +interpreting the maker notes (which, for a RAW image, may mean that the image +can no longer be properly rendered). Also beware that the maker notes +information may be damaged if an image is edited when the maker notes are not +properly recognized. So it is a good idea not to edit the Make and Model tags in +the first place.
+ +
If you really want to delete the Make and Model information, you +might as well delete the maker notes too. You can do this with either of the +following commands: +
exiftool -make= -model= -makernotes:all= image.jpg
+exiftool -make= -model= -makernotes= image.jpg
+
+For the same reason, maker notes can not be copied to an image with an +incompatible Make or Model. To do this, the Make and Model tags must also be +copied. eg) +
exiftool -tagsfromfile src.jpg -makernotes -make -model dst.jpg
+
+(Note that in this case the "-makernotes:all" syntax does not work +because it attempts to copy the maker note tags individually. Since maker note +tags may not be created individually, they must instead be copied as a block +with "-makernotes".) +
+ + +

9a. "The information is different when I copy all tags to a new file", or +
9b. "The tag locations change when I use -tagsfromfile +to copy information"

+ +
+This feature is explained under the -tagsFromFile option in +the exiftool application documentation, but the +question is common enough that it is discussed here in more detail.
+ +
By default, ExifTool will store information in preferred locations +when either writing new information or copying information between files. This +freedom allows ExifTool to write or copy information to files of different +formats without requiring the user to know details about where the information +is stored.
+ +
The preferred general locations for information written to JPEG +images are 1) EXIF, 2) IPTC and 3) XMP. As an example, information extracted +from the maker notes will be preferentially written (on a tag-by-tag basis) in +EXIF format when copying information between two JPEG images. But if a specific +tag doesn't exist in EXIF, then the tag is written to the first valid group in +the order specified above. The advantage of "translating" the information to +EXIF is that it then becomes readable by applications which only support +standard EXIF. The disadvantage is that you don't get an exact copy of the +original information structure.
+ +
But ExifTool gives you the ability to customize this behaviour to +write the information to wherever you want. This is done by specifying a group +name for the tag(s) to be copied. This applies even if the group name is +"all", in which case the original family 1 group is preserved. So +to copy all information and preserve the original structure, use this syntax: + +
exiftool -tagsfromfile src.jpg -all:all dst.jpg
+
+ +In this command, since no destination tag was specified, the destination is the +same as the source (ie. "-all:all>all:all"), so the information is +copied to the same family 1 group.
+ +
Here are some examples to show you the type of control you have over +where the information is written. All commands in each example are equivalent: + +
# copy all tags to preferred groups (no destination group)
+exiftool -tagsfromfile src.jpg dst.jpg
+exiftool -tagsfromfile src.jpg -all dst.jpg
+exiftool -tagsfromfile src.jpg "-all>all" dst.jpg
+exiftool -tagsfromfile src.jpg "-all:all>all" dst.jpg
+
+# copy all tags, preserving family 1 group (destination group 'all')
+exiftool -tagsfromfile src.jpg -all:all dst.jpg
+exiftool -tagsfromfile src.jpg "-all>all:all" dst.jpg
+exiftool -tagsfromfile src.jpg "-all:all>all:all" dst.jpg
+
+# copy all tags to EXIF group (destination group 'exif')
+# [the destination family 1 group is the preferred EXIF IFD]
+exiftool -tagsfromfile src.jpg "-all>exif:all" dst.jpg
+exiftool -tagsfromfile src.jpg "-all:all>exif:all" dst.jpg
+
+# copy XMP tags to XMP group (destination group 'xmp')
+# [the destination family 1 group is the preferred XMP namespace]
+exiftool -tagsfromfile src.jpg "-xmp:all" dst.jpg
+exiftool -tagsfromfile src.jpg "-xmp:all>xmp:all" dst.jpg
+
+# copy XMP tags, preserving family 1 group (destination group 'all')
+exiftool -tagsfromfile src.jpg "-xmp:all>all:all" dst.jpg
+
+# copy XMP tags to preferred groups (no destination group)
+exiftool -tagsfromfile src.jpg "-xmp:all>all" dst.jpg
+
+# copy XMP tags to EXIF only (destination group 'exif')
+# [the destination family 1 group is the preferred EXIF IFD]
+exiftool -tagsfromfile src.jpg "-xmp:all>exif:all" dst.jpg
+
+ +The same rules illustrated above also apply when copying individual tags.
+ +
Note: If no destination group is specified, a new tag is created if +necessary only in the preferred group, but if the same tag already exists in +another group, then this information is also updated. (Otherwise inconsistent +values for the same information would exist in different locations. Of course, +you can always generate inconsistencies like this if you really want to by +specifically writing contradictory information to different groups.) +
+ +
Certain types of meta information (such as EXIF, IPTC, XMP and +ICC_Profile) may also be copied as a block. This technique copies all +meta information, even if ExifTool doesn't have the ability to write some +individual tags contained in the block. For all block types except EXIF, the +metadata is copied byte-for-byte from the original image. With EXIF however, +the metadata may be restructured to ensure that it is self-contained. Also note +that EXIF may not be written as a block to TIFF-based file formats. Beware that +any existing metadata of this type in the destination file will be +overwritten by the new block. + +
# copy EXIF as a block between same-named JPG files in different directories
+exiftool -tagsfromfile SRCDIR/%f.%e -exif -ext jpg DSTDIR
+
+# copy XMP as a block from one file to another
+exiftool -tagsfromfile src.jpg -xmp dst.cr2
+
+ + +

10. "How does ExifTool handle coded character sets?"

+ + + + +
[Also see FAQ number 18 for help with +special characters in a Windows console.]
+ +
Certain meta information formats allow coded character sets other +than plain ASCII. When reading, most known encodings are converted to the +external character set according to the exiftool "-charset CHARSET" +or -L option, or to UTF‑8 by default. When writing, the +inverse conversion is performed. Alternatively, special characters may be +converted to/from HTML character entities with the -E option. +
+ +
A distinction is made between the external character set +visible to the ExifTool user, and the internal character used to store +text in the metadata of a file. These character sets may be specified +separately as follows: +
  1. The external character set for tag values passed to/from +ExifTool is UTF‑8 by default, but it may be changed through any of these +command-line options: +
    -charset CHARSET   or   +-charset exiftool=CHARSET   or   -L +
    +The encoding of file and directory names (eg. the FILE argument on +the command line) is different. By default, these names are passed straight +through to the standard C I/O routines without recoding. On Mac/Linux these +routines expect UTF‑8, but on Windows they use the system code page (which +is dependent on your system settings). However, as of ExifTool 9.79, the +external filename encoding may be specified: +
    -charset filename=CHARSET
    +When this is done, file and directory names are converted from the specified +encoding to one appropriate for system I/O routines. In Windows, this also has +the effect of enabling Unicode filename support via the special Windows +wide-character I/O routines if the required Perl modules are available +(these are included in the Windows executable version of ExifTool). See +WINDOWS UNICODE FILE NAMES +in the application documentation for more details.

  2. +
  3. The internal character set for strings stored in file metadata may be +specified for some metadata types: +
    -charset TYPE=CHARSET
    +(where TYPE is "exif", "iptc", +"id3", "photoshop", "quicktime" or +"riff")
+ +Valid CHARSET values are (with aliases given in brackets, case is +not significant): + +
+ + + + + + + + + + + +
UTF8(cp65001, UTF‑8)DOSLatinUS(cp437)
Latin(cp1252, Latin1)DOSLatin1(cp850)
Latin2(cp1250)DOSCyrillic(cp866)
Cyrillic(cp1251, Russian)     MacRoman(cp10000, Mac, Roman)
Greek(cp1253)MacLatin2(cp10029)
Turkish(cp1254)MacCyrillic(cp10007)
Hebrew(cp1255)MacGreek(cp10006)
Arabic(cp1256)MacTurkish(cp10081)
Baltic(cp1257)MacRomanian(cp10010)
Vietnam(cp1258)MacIceland(cp10079)
Thai(cp874)MacCroatian(cp10082)
+ +The -L option is equivalent to "-charset Latin", +"-charset Latin1" and "-charset cp1252".
+ +
Type-specific details are given below about the special character +handling for EXIF, IPTC, XMP, PNG, ID3, PDF, Photoshop, QuickTime, AIFF, RIFF, +MIE and Vorbis information:
+ + + +
EXIF: Most textual information in EXIF is +stored in ASCII format (called "string" in the +EXIF Tags documentation). By default ExifTool +does not convert these strings. However, it is not uncommon for applications to +write UTF‑8 or other encodings where ASCII is expected. To deal with +these, ExifTool allows the internal EXIF string encoding to be specified with +"-charset exif=CHARSET", which causes EXIF string values to be +converted from the specified character set when reading, and stored with this +character set when writing. The +MWG recommends using +UTF‑8 encoding for EXIF strings, and in keeping with this the +"-use mwg" feature sets the default internal EXIF string encoding +to UTF‑8 (ie. "-charset exif=utf8"), but note that this will +have no effect unless the external encoding is also set to something other than +the default of UTF‑8.
+ +
A few EXIF tags (UserComment, GPSProcessingMethod and +GPSAreaInformation) support a designated internal text encoding, with values +stored as ASCII, Unicode (UCS‑2) or JIS. When reading these tags, ExifTool +converts Unicode and JIS to the external character set specified by the +-charset or -L option, or to UTF‑8 by default. +ASCII text is not converted. When writing, text is stored as ASCII unless the +string contains special characters, in which case it is converted from the +external character set (UTF‑8 by default), and stored as Unicode. ExifTool +writes Unicode in native EXIF byte ordering by default, but the byte order may +be specified by setting the ExifUnicodeByteOrder tag (see the +Extra Tags documentation).
+ +
The EXIF "XP" tags (XPTitle, XPComment, etc) are always stored +internally as little-endian Unicode (UCS‑2), and are read and written +using the specified external character set.
+ + + +
IPTC: +The value of the IPTC:CodedCharacterSet tag determines how the internal IPTC +string values are interpreted. If CodedCharacterSet exists and has a value of +"UTF8" (or "ESC % G") then string values are +assumed to be stored as UTF‑8. Otherwise the internal IPTC encoding is +assumed to be Windows Latin1 (cp1252), but this can be changed with +"-charset iptc=CHARSET". When reading, these strings are converted +to UTF‑8 by default, or to the external character set specified by the +-charset or -L option. When writing, the inverse +conversions are performed. No conversion is done if the internal (IPTC) and +external (ExifTool) character sets are the same. Note that ISO 2022 character +set shifting is not supported. Instead, a warning is issued and the string is +not converted if an ISO 2022 shift code is encountered. See the +IPTC IIM +specification for more information about IPTC character coding.
+ +
ExifTool may be used to convert IPTC values to a different internal +encoding. To do this, all IPTC tags must be rewritten along with the desired +value of CodedCharacterSet. For example, the following command changes the +internal IPTC encoding to UTF‑8 (from Windows Latin1 unless +CodedCharacterSet was already "UTF8"): +
exiftool -tagsfromfile @ -iptc:all -codedcharacterset=utf8 a.jpg
+
or from Windows Latin2 (cp1250) to UTF‑8: +
exiftool -tagsfromfile @ -iptc:all -codedcharacterset=utf8 -charset iptc=latin2 a.jpg
+
and this command changes it back from UTF‑8 to Windows Latin1 (cp1252): +
exiftool -tagsfromfile @ -iptc:all -codedcharacterset= a.jpg
+
or to Windows Latin2: +
exiftool -tagsfromfile @ -iptc:all -codedcharacterset= -charset iptc=latin2 a.jpg
+
Note that unless CodedCharacterSet is UTF‑8, applications have no +reliable way to determine the IPTC character encoding. For this reason, it is +recommended that CodedCharacterSet be set to "UTF8" when creating +new IPTC.
+ +
Refers to the older +IPTC IIM format. +The more recent +IPTC +Photo Metadata Standard actually uses the XMP format (see below). +
+ + + +
XMP: Exiftool reads XMP encoded as +UTF‑8, UTF‑16 or UTF‑32, and converts them all to UTF‑8 +internally. Also, all XML character entity references and numeric character +references are converted. When writing, ExifTool always encodes XMP as +UTF‑8, converting the following 5 characters to XML character references: +& < > ' ". By default no further conversion is +performed, however the -charset or -L option may be +used to convert text to/from a specified external character set when +reading/writing.
+ +
PNG: +PNG TextualData tags are stored as +tEXt, zTXt and iTXt chunks in PNG images. The tEXt and zTXt chunks use ISO +8859-1 encoding, while iTXt uses UTF‑8. When reading, ExifTool converts +all PNG textual data to the external character set specified by the +-charset or -L option, or to UTF‑8 by default. +When writing, ExifTool generates a tEXt chunk (or zTXt with the -z +option) if the text doesn't contain special characters or if Latin encoding is +specified (-L or -charset latin); otherwise an iTXt +chunk is used and the text is converted from the specified external character +set and stored as UTF‑8.
+ +
JPEG Comment: The encoding for the JPEG +Comment (COM segment) is not specified, so ExifTool reads/writes this text +without conversion.
+ +
ID3: The ID3v1 specification officially +supports only ISO 8859‑1 encoding (a subset of Windows Latin1), although +some applications may incorrectly use other character sets. By default ExifTool +converts ID3v1 text from Latin to the external character set specified by the +-charset or -L option, or to UTF‑8 by default. +However, the internal ID3v1 charset may be specified with +"-charset id3=CHARSET". The encoding for ID3v2 information is +stored in the file, so ExifTool converts ID3v2 text from this encoding to the +external character set specified by -charset or -L, or +to UTF‑8 by default. ExifTool does not currently write ID3 +information.
+ +
PDF: PDF text strings are stored in either +PDFDocEncoding (similar to Windows Latin1) or Unicode (UCS‑2). When +reading, ExifTool converts to the external character set specified by the +-charset or -L option, or to UTF‑8 by default. +When writing, ExifTool encodes input text from the specified character set as +Unicode only if the string contains special characters, otherwise PDFDocEncoding +is used.
+ +
Photoshop: Some Photoshop resource +names are stored as Pascal strings with unknown encoding. By default, ExifTool +assumes MacRoman encoding and converts this to UTF‑8, but the internal and +external character sets may be specified with +"-charset Photoshop=CHARSET" and "-charset CHARSET" +respectively.
+ +
QuickTime: QuickTime text strings may +be stored in a variety of poorly documented formats, and ExifTool does its best +to decode these according to the -charset option setting. For some +QuickTime strings where the internal encoding is not known, ExifTool assumes a +default encoding of MacRoman, but this may be changed with +"-charset QuickTime=CHARSET".
+ +
AIFF: AIFF strings are assumed to be +stored in MacRoman, and are converted according to the -charset +option when reading.
+ +
RIFF: The internal encoding of RIFF +strings (eg. in AVI and WAV files) is assumed to be Latin unless otherwise +specified by the RIFF CSET chunk or the "-charset RIFF=CHARSET" +option.
+ +
MIE: MIE strings are stored as either +UTF‑8 or ISO 8859‑1. When reading, UTF‑8 strings are converted +according to the -charset or -L option, and ISO +8859‑1 strings are never converted. When writing, input strings are +converted from the specified character set to UTF‑8. The resulting +strings are stored as UTF‑8 if they contain multi-byte UTF‑8 +character sequences, otherwise they are stored as ISO 8859‑1.
+ +
Vorbis: Vorbis comments are stored as +UTF‑8, and are converted to the character set specified by +-charset or -L when reading.
+ +
+Programmers: ExifTool returns all values as byte strings of encoded +characters. Perl wide characters are not used. The encoding is UTF‑8 by +default, but valid UTF‑8 can not be guaranteed for all values, so the +caller must validate the encoding if necessary. The encodings described above +are set by the various Charset options of +the API. +

Note: Some settings of the system PERL_UNICODE environment +variable may be incompatible with ExifTool's character handling. +
+ + + + + +

11. "My user-defined tags don't work"

+ +
+For examples of how to add user-defined tags, see the +ExifTool_config file in the ExifTool distribution. +It may be useful to activate this file as a test before trying to implement +your own config file. To activate this file, copy it to your HOME +directory then rename it to ".ExifTool_config". +
Note: The config file must be renamed at the +command line because neither the Windows nor Mac GUI allow a file name to +begin with a ".". To do this in Windows, run "cmd.exe" and type +the following (pressing ENTER at the end of each line): +
cd %HOMEPATH%
+rename ExifTool_config .ExifTool_config
+
+or on a Mac, open the "Terminal" application (from the /Applications/Utilities +folder) and type this command then press RETURN: +
mv ExifTool_config .ExifTool_config
+
+ +With the sample config file installed, you should be able to write the example +tags. A command like this: +
exiftool -v2 -NewXMPxmpTag=test FILE
+should print this as the first line of its output: +
Writing XMP-xmp:NewXMPxmpTag
+
+If this doesn't work, the most common problem is that the +".ExifTool_config" configuration file isn't getting loaded. In +this case, there are a few things you can try: +
    +
  1. Make sure the config file name is correct. It must be +".ExifTool_config" (note the leading ".", and the +capital "T").
  2. +
  3. Set either the HOME or the EXIFTOOL_HOME environment variable +to the name of the directory where you put your ".ExifTool_config" +file.
  4. +
  5. Put the config file in the same directory as the exiftool app. (Also, be +sure the config filename starts with a dot! See the note above for help +renaming the config file.)
  6. +
  7. If you can't get the config file to load automatically, you can try loading +it manually with the exiftool -config CFGFILE option. (Note: +This must be the first option on the command line.) This allows loading +of a config file with any name.
  8. +
+
+ +
If necessary, you can verify that ExifTool is loading your config +file by adding the following line to your file: + +
print "LOADED!\n";
+
+ +If you see a "LOADED!" message when you run exiftool, but your new +tags still don't work, make sure you are using the proper tag name and that the +file you are writing can support this type of information.
+ +
Note that all tag names in the config file are case +sensitive. Specifically, the case must be correct for tag names in +Composite tag Require/Desire entries. Also note that XMP tag names are +generated automatically by capitalizing the tag ID unless the tag definition +contains a "Name" entry.
+ +
Programmers: To specify the config file directory from within +a Perl script when using the ExifTool API, set the EXIFTOOL_HOME +environment variable before loading the ExifTool module: +
BEGIN { $ENV{EXIFTOOL_HOME} = '/config_file_directory' }
+use Image::ExifTool;
+
+Also see the Configuration section of the +ExifTool API documentation for techniques to use a config file with another +name, or to disable the config file feature. +
+ + +

12. "How do I export information from exiftool to a database?"

+ +
[See FAQ number 26 for help with the reverse +-- importing metadata from a database.]
+ +
+It is often easiest to export information formatted as a tab-delimited or +comma-separated list of values using the exiftool -T or +-csv option. As well, the -r option is useful for +recursing through all images in a hierarchy of directories. For example: + +
exiftool -T -r -filename -exposuremode -ISO t/images > out.txt
+
+ +This command recursively processes all images in the "t/images" +directory, extracting FileName, ExposureMode and ISO tags, and writing the +output to a tab-delimited text file called "out.txt". After the +command has executed, "out.txt" will look something like this: + +
Canon.jpg       Manual  100
+Casio.jpg       -       64
+Nikon.jpg       -       100
+OlympusE1.jpg   Auto    400
+
+ +One limitation of the -T option is that a list of tags to extract +must be specified. Otherwise, all information is extracted from each input +file, and the columns would contain values from random tags.
+ +
The -csv (comma separated values) option solves this +dilemma by pre-extracting information from all input files, then producing a +sorted list of available tag names as the first row of the output, and +organizing the information into columns for each tag. As well, a first column +labelled "SourceFile" is generated. These features make it practical to use the +-csv option for extracting all information from multiple images. +For example, this command: + +
exiftool -csv -r t/images > out.csv
+ +gives an output like this: + +
SourceFile,AEBBracketValue,AELock,AFAreaHeight,AFAreaMode,AFAreas,[...]
+t/images/Canon.jpg,0,,151,,,[...]
+t/images/Casio.jpg,,,,,,[...]
+t/images/Nikon.jpg,,,,Single Area,,[...]
+t/images/OlympusE1.jpg,,Off,,,"Center (121,121)-(133,133)",[...]
+
+ +Note that the number of columns in the -csv output may be very +large if all information is extracted. Missing tags are indicated by empty +strings as in the example above, or by dashes if the -f option is +used.
+ +
It should be possible to import these files directly into most +database applications. On the command line, any list of tag names may be used, +and any number of file or directory names may be specified. (Hint: If your +command line starts to get too long, you may want to look into using the +-@ option and/or the ShortCut +feature).
+ +
In Windows, a .BAT file containing the exiftool command may be used +to give drag and drop functionality. Dropping a folder on the following .BAT +file will create "out.txt" in the folder: + +
echo "FileName<tab>Aperture<tab>ISO" > %1\out.txt
+exiftool -T -r -filename -aperture -ISO %1 >> %1\out.txt
+
+ +The "echo" command was included to add column headings to the +output. (The tab character in the echo command, indicated by +"<tab>", may be generated in Mac/Linux shells with CTRL-v +then TAB, or in a Windows cmd shell with TAB when cmd.exe is run with the +/f:off option to disable tab completion.) +
+ +
Other possible export formats include RDF/XML (with the +-X option), or JSON (with the -j option). These +methods allow transfer of more complex data sets (including structured +information with the -struct option), but require that the +importing software supports these formats.
+ +
Finally, the -p option may be used to generate any +arbitrary output format. For example, the following format file +(let's call it "my.fmt") may be used to emulate a CSV-formatted +output: + +
#[HEAD]FileName,Aperture,ISO
+$filename,$aperture,$iso
+
+ +with a command like this: + +
exiftool -f -r -p my.fmt t/images > out.csv
+
+ +But note that any values containing commas, quotes or some other special +characters may mess up the CSV formatting. If this is a possibility, the +-api filter option may be added to the command to quote values +if necessary. For example: + +
# in a Windows CMD shell
+exiftool -api filter="$_ = qq{""""$_""""} if s/""/""""/g or /(^\s+|\s+$)/ or /[,\n\r]/" ...
+
+# in Mac/Linux
+exiftool -api filter='$_ = qq{"$_"} if s/"/""/g or /(^\s+|\s+$)/ or /[,\n\r]/' ...
+ +The -p option argument may also be a format string instead of a +file name. The following command has the same effect as above except that the +row of headings is not printed (Note: Use single quotes as below on Mac/Linux, +or double quotes instead on Windows): + +
exiftool -f -r -p '$filename,$aperture,$iso' t/images > out.csv
+
+ +With the -f option, the value of any missing tag is printed as a +dash (or the value of the +API MissingTagValue option, if set). +Without this option, missing tags generate a minor warning and the line in the +-p output is not printed. The -m option may be used +to ignore minor warnings, which causes these lines to be printed with an empty +value for missing tags.
+ +
+See the -p option in the application +documentation for more information about this feature. +
+ + +

13a. "Why is my file smaller after I use ExifTool to write information?", or +
13b. "Why do some Offset tags change value when I write other tags?", or +
13c. "How does editing a file with ExifTool affect image quality?"

+ +
+There are various specific reasons why this can happen, but the general answer +is: When ExifTool writes an image, the meta information may be restructured in +such a way that it takes less space than in the original file, or so that some +tags are stored at different offsets in the file.
+ +
The EXIF/TIFF standard allows for blocks of unreferenced data to +exist in an image. Some digital cameras write JPEG or TIFF-based RAW files +which contain large blocks of unused data, usually filled with binary zeros. +The reason for this could be to simplify camera algorithms by allowing +variable-sized information to be written at fixed offsets in the output image. +When ExifTool rewrites an image it does not copy these unused blocks. This can +result in a significant reduction in file size for some images. [The +-htmlDump option may be used to view the file structure if you are +interested in seeing these unused data blocks -- use a command like +"exiftool -htmlDump a.jpg > out.html", then open +out.html in your web browser. Unused data blocks are +brown in this output.]
+ +
Also, the size of an XMP record may easily shrink or grow when it is +rewritten, even if no meta information is changed. This is partly due to the +fact that the XMP specification recommends a few KB of padding at the end of the +record (ExifTool adds 2424 bytes by default, but this padding is omitted if the +-z command-line option or the +API Compact NoPadding setting is used), and +partly due to the flexibility of the XMP format which allows the information to +be written in various styles, some of which are more compact than others (the +-z option and the API Compact +settings also cause a more compact form to be written).
+ +
You may also notice that the values of some "offset" tags (like +ThumbnailOffset and PreviewImageStart) may change when the file is rewritten. +This is normal, and simply indicates that the associated data is now stored at a +different position within the file.
+ +
ExifTool does not modify the image data itself, so editing a file +with ExifTool is "lossless" as far as the image is concerned.
+ + +

14a. "What format do I use for writing GPS coordinates?", or
+14b. "How do I change the format of extracted GPS coordinates?"

+ +
ExifTool is very flexible in the formats allowed for entering GPS +coordinates. Any string containing between 1 and 3 floating point numbers is +valid. The numbers represent degrees, (and optionally) minutes and +seconds with any number of decimal places.
+ +
For EXIF GPS coordinates, the reference direction is specified +separately with the EXIF:GPSLatitudeRef or EXIF:GPSLongitudeRef tag. +GPSLatitudeRef may be written with a string containing "N", +"North", "S" or "South", or with a signed +coordinate value (positive for the northern hemisphere or negative for the +south). GPSLongitudeRef accepts similar values, but for E/East (positive) and +W/West (negative).
+ +
For XMP GPS coordinates, the reference direction is specified within +the XMP:GPSLatitude or XMP:GPSLongitude value, with west longitudes and south +latitudes being specified either by negative coordinate values or by ending the +string with "W" or "S".
+ +
Here are some examples of equivalent ways to specify a GPS +latitude in both EXIF and XMP: + +
exiftool -exif:gpslatitude="42 30 0.00" -exif:gpslatituderef=S a.jpg
+exiftool -exif:gpslatitude="42 deg 30.00 min" -exif:gpslatituderef=S a.jpg
+exiftool -exif:gpslatitude=42.5 -exif:gpslatituderef=S a.jpg
+
+exiftool -xmp:gpslatitude="42 30 0.00 S" a.jpg
+exiftool -xmp:gpslatitude=42.50S a.jpg
+exiftool -xmp:gpslatitude=-42.5 a.jpg
+
+ +Similar styles may be used for longitude. ExifTool will convert any of these +coordinate styles to the proper format for the specific tag used. +
+ +
ExifTool 12.22 and later allow combined lat/lon GPSCoordinates +values to be written to GPSLatitude and GPSLongitude, and the appropriate +latitude or longitude part will be pulled from the input string. Version 12.36 +and later make Composite:GPSPosition writable, allowing EXIF GPS coorinates and +reference directions to be all writable via a single tag: + +
# write all 4 EXIF GPS tags (version 12.36 and later)
+exiftool -gpsposition="42 30 0.00 S, 33 15 0.00 W"
+exiftool -gpsposition="-42.5, -33.25"
+ +Version 12.44 and later make Composite:GPSLatitude/GPSLongitude writable, +allowing the separate EXIF coordinate and reference direction tags to be written +together. (Note that the Composite group must be specified here because these +tags are otherwise avoided when writing due to possible confusion when +attempting to write EXIF tags.) + +
# write EXIF:GPSLatitude and GPSLatitudeRef together (12.44 and later)
+exiftool -composite:gpslatitude="42 30 0.00 S" a.jpg
+exiftool -composite:gpslatitude="-42.5" a.jpg
+ +
When reading, by default ExifTool reports coordinates in the format +
DDD deg MM' SS.SS"
+where DDD is degrees, MM is minutes, and SS.SS +is seconds. The -n option may be used to change this to decimal +degrees, or any arbitrary format may be specified with the -c +command-line option (see the application documentation), +or the API CoordFormat option. +
+ + +

15. "I get MakerNote warnings or errors when reading or writing information"

+ +
Problems like this may be caused by image editing software which +doesn't properly update offsets in the MakerNotes when rewriting an image. These +offsets are used as pointers to reference tag values and structures within the +metadata, and errors like this may lead to missing or incorrect values for some +MakerNotes tags. In many cases, ExifTool will detect this type of problem and +issue a warning like this when reading (or an error when writing): + +
Warning: [minor] Possibly incorrect maker notes offsets (fix by -340?)
+
+ +[Be aware that if multiple warnings occur, the -a +option must be used to see them all, since by default only one warning is +displayed per file.]
+ +
This is a particularly insidious problem that is sometimes difficult +for ExifTool to correct automagically, so it requires some operator +intervention. If this warning occurs, you have a few alternatives:
+ +
1) Use the -F option to allow ExifTool to attempt to +fix the incorrect offsets. If ExifTool was correct in its diagnosis, then this +option will fix the incorrect offsets. This is usually the appropriate choice +if this problem was caused by editing the image with other +software.
+ +
2) Use the -m option to ignore the warning (or downgrade +the error to a warning when writing). This causes ExifTool to honour the +existing maker note offsets, and may be the correct choice if images straight +out of the camera have this problem.
+ +
Often, the first choice (-F) is the right thing to do, +but this depends on many factors, so it is best to try both methods then compare +the resulting maker note information to see which works best for your +situation.
+ +
When writing, -F applies a permanent correction to the +maker notes. Note that some MakerNote information may be lost permanently +if the proper correction is not applied when writing images with this +problem.
+ +
3) The third alternative is to adjust the maker note offsets by a +specific amount. This is done by appending an integer to the -F +option. For example, with the warning above (where ExifTool suggests "fix by +-340?"), -F would be equivalent to -F-340. See the +-F option documentation for +more details. This advanced feature may require some technical knowledge about +the structure of EXIF information (and here, ExifTool's -htmlDump +feature may be very useful for visualizing this structure).
+ +
Other types of MakerNote errors may also prevent the file from +being written. However, most MakerNote errors are designated as minor, +which allows them to be ignored by using the -m option. For +example: + +
Error: [minor] Bad format (65535) for MakerNotes entry 17
+
+ +Using -m will downgrade the minor error to a warning, allowing the +file to be written, but some MakerNote information may be lost when ignoring +errors like this.
+ +
You may also get one of these warnings when reading or writing a +file containing unknown maker notes: + +
Warning: [minor] Unrecognized MakerNotes
+Warning: [minor] Maker notes could not be parsed
+ +These warnings indicate that ExifTool didn't recognize the maker notes in a +file, so it couldn't extract any information from them when reading, or had to +copy the maker notes as a block when writing. If the maker notes contain +absolute offsets then this could result in corrupted makernote information when +writing, but this is very unlikely.
+ + +

16. "Why doesn't ExifTool rename my AVI files?"

+ +
By default, ExifTool only processes writable file +types when any +tag is being written and a +directory name is specified on the command line. To force exiftool to process +other files, they must either be listed on the command line by name, or be +specified using the -ext or -ext+ option, something +like this: + +
# process AVI files in addition to other writable file types
+exiftool -ext+ AVI -d pics/%Y/%m "-directory<dateTimeOriginal" DIR
+
+# process only AVI and JPG files
+exiftool -ext AVI -ext JPG -d pics/%Y/%m "-directory<dateTimeOriginal" DIR
+
+ +When -ext is used, only files with the specified extension(s) are +processed, however -ext+ may be used to add to the list of normally +processed files (as in the first example above). Multiple -ext or +-ext+ options may be used in the same command (as in the second +example above) to process any number of different file types, or +-ext "*" may be used to process files with any extension (or none at all). +
+ +
The +-listwf option may be used to list the extensions of all writable +file types, or see here for a table of +supported file types. +
This includes "pseudo" tags like +FileName, Directory, FileModifyDate and FileCreateDate.
+ + +

17. "List-type tags do not behave as expected"

+ +
Tags indicated by a plus sign (+) in the +tag name documentation are list-type tags. +Two examples of common list-type tags are +IPTC:Keywords and +XMP:Subject. These tags may contain multiple +items which are combined into a single string when reading. (By default, +extracted list items are separated by a comma and a space, but the +-sep option may be used to change this.) When writing, separate +items are assigned individually. For example, the following command writes +three keywords to all writable files in directory DIR, replacing +any previously existing keywords: + +
exiftool -keywords=one -keywords=two -keywords=three DIR
+
+ +List items are assigned separately as above, NOT all together, because this +would represent a single keyword: + +
exiftool -keywords="one, two, three" test.jpg  (WRONG!)
+
+ +The -sep option may be used to split values of list-type tags into +separate items when writing. For example, + +
exiftool -sep ", " -keywords="one, two, three" DIR
+
+ +will store three separate keywords, the same as the first example above. This +feature may also be used to split a tag value into separate items if it was +originally stored incorrectly as a single string: + +
exiftool -sep ", " -tagsfromfile @ -keywords test.jpg
+
+ +However, sometimes it is desirable to have list items which contain a comma, and +this is allowed: + +
exiftool -contributor="Harvey, Phil" -contributor="Marley, Bob" a.jpg
+
+ +But to distinguish these entries when extracting information, a different list +separator or a different output format must be used. For instance, the +following command uses "//" to separate list items, + +
exiftool -contributor -sep "//" a.jpg
+
+ +and produces an output like this: + +
Contributor  : Harvey, Phil//Marley, Bob
+
+ +Alternatively, the -j, -php and -X +options use an output format which preserves the structure of a +list (if -sep is NOT used).
+ +
Note that the writing examples above overwrite any values which +already existed in the original file for these tags. Instead, to add or delete +items from an existing list, use "+=" or "-=" in place +of "=". For example: + +
exiftool -keywords+="add this" -keywords-="remove this" DIR
+
+ +With commands like this, new items are added to the list in place of the first +deleted item, or at the end of the list if no items were removed.
+ +
Note: Using "=" is equivalent to "+=" in +any command where the same List-type tag is set with "+=" or +"-=" in another assignment. (ie. existing items will be preserved +unless specifically deleted with "-=".) [For non +List-type tags, "+=" has a different meaning, and is used to +increment numerical values or shift dates.]
+ +
To prevent duplication when adding new +items, specific items can be deleted then added back again in the same +command. For example, the following command adds the keywords "one" and "two", +ensuring that they are not duplicated if they already existed in the keywords of +an image: + +
exiftool -keywords-=one -keywords+=one -keywords-=two -keywords+=two DIR
+
+ +When copying list tags using the -tagsFromFile feature, +items are copied individually to form proper lists if the tag is copied directly +(note that -tagsFromFile @ is implied by the "<" +operation in this and the following commands, causing tags to be copied from the +original file): + +
exiftool "-keywords<subject" DIR
+ +However, this is not the case if the tag is interpolated within a format string +(ie. has a leading "$" symbol), like this + +
exiftool "-keywords<$subject" DIR  (WRONG!)
+ +but here the -sep option may be used to split the list back into +separate items: + +
exiftool "-keywords<$subject" -sep "//" DIR
+ +Note there is a complication when copying multiple tags to a single list +tag: Here, any assignment to a tag overrides earlier assignments to the same +tag in the command. For instance, this command: + +
exiftool "-keywords<filename" "-keywords<comment" DIR  (WRONG!)
+
+ +writes only the value from the Comment tag. This may seem strange, but it prevents +duplicate items from being added to a list when copying a group of tags from a +file containing duplicate information. Alternatively, a leading + +may be added to accumulate queued items when +copying from multiple tags to a single list: + +
exiftool "-keywords<filename" "-+keywords<comment" DIR
+
+ +Note that as with "=" in the first three examples above, the +"<" operation of this command overwrites any Keywords that +existed previously in the original file. To add to or remove from the existing +keywords, use "+<" or "-<". +
+ +
One final note: In rare cases when copying the contents of two other +tags into a single list, the resulting queued list values may contain duplicates +that would be written to the target file. There is an +API NoDups option which removes duplicates items +from list values that are queued for writing. For example, + +
exiftool -tagsfromfile a.jpg -subject -tagsfromfile b.jpg -+subject -api nodups c.jpg
+
+ +combines Subject items from a.jpg and b.jpg and writes them without duplicates +to c.jpg (overwriting any previous Subject in c.jpg).
+ + +

18a. "Special characters don't display properly in my Windows console", or +
18b. "I'm having problems with special characters on the Windows command line"

+ +
The Windows cmd.exe console uses an MS-DOS encoding by default +(cp437 or something similar, depending on your region). The exiftool +-charset option may be used to encode the exiftool output for a +specific Windows code page, which may help display some special characters, but +instead it may be better to switch the console to UTF‑8 (the native +ExifTool character encoding). This is especially useful if you are using the +-lang option to translate exiftool output to another language. To +change the Windows console to UTF‑8, follow these steps: + +
  1. Run "cmd.exe" to open a Windows console (select "Run..." from the +Start menu and enter "cmd").
  2. +
  3. Change the font in the console Properties to any True Type font (eg. "TT +Lucida Console").
  4. +
  5. Type "chcp 65001" then press ENTER at the command prompt.
  6. +
+ +The console should now be able to display UTF‑8 characters (cp65001). But +note that the TT Lucida Console font shipped with Windows, at least my version, +may not be very complete, and doesn't seem to contain Japanese or Chinese +characters.
+ +
To permanently set the font, select "Save properties for future +windows" when changing the font Properties. Also, you can automatically run +"chcp 65001" every time "cmd.exe" is launched by changing the +Windows Registry for the Command Processor: Run "regedit" and put "chcp +65001" into Data field for "HKEY_LOCAL_MACHINE\Software\Microsoft\Command +Processor\Autorun". (Unfortunately, I haven't been able to figure out how to +change the code page for exiftool when launched via the Windows GUI. If anyone +can figure out how to do this, please let me know.) +
+ +
Note that Windows will recode arguments on the command +line from the current console code page to the system code page, so the +ExifTool -charset should be set to the system code page for +command-line arguments. However, this technique may yield unexpected results +since not all characters may be represented using the system code page. To get +around this limitation, arguments may be read from an ExifTool argument file +using the -@ option. UTF‑8 encoding is recommended for the +argument file, and with this you would also set -charset filename=utf8 +if using special characters in filename arguments within the file.
+ +
Another way to bypass the console/command-line encoding problems for +individual tag values is to extract/write to/from a separate text file and use a +UTF‑8 aware text editor to view/edit the text. For example: + +
# extracting...
+exiftool image.jpg > out.txt
+
+# writing...
+exiftool "-subject<=subject.txt" image.jpg
+ + +

19. "How do I change the format of an extracted tag value?"

+ +
The exiftool application has built-in options which allow you to +display numeric values (-n), escape special HTML characters +(-E), and change the date/time (-d) and GPS coordinate +(-c) formats, but sometimes more control is needed over the +formatting of a value...
+ +
The -p and -tagsFromFile options provide +an advanced translation feature that allows arbitrary Perl expressions to be +used to modify tag values. The basic syntax is this: + +
${TAG;EXPR}
+ +where TAG is the tag name, and EXPR is a Perl +expression acting on the default input variable ($_), which is +initially the original value of the tag. For example, the following command +sets the FileName from Artist, translating spaces to underlines: + +
exiftool '-filename<${artist;tr/ /_/}.%e' image.jpg
+ +(Note: Use single quotes as above in Mac/Linux, or double quotes instead +in Windows.)
+ +
Another technique is to create a user-defined Composite tag to do +the reformatting. Here is a basic config file that reformats the Artist tag to +provide a new MyArtist tag with the same character translation as the example +above: + +
%Image::ExifTool::UserDefined = (
+   'Image::ExifTool::Composite' => {
+       MyArtist => {
+           Require => 'Artist',
+           ValueConv => '$val =~ tr/ /_/; $val',
+       },
+   },
+);
+1; # end
+
+ +With this config file, an Artist value of "Phil Harvey" yields a corresponding +MyArtist value of "Phil_Harvey". The ValueConv string may be any valid Perl +expression, and is evaluated to obtain the value for the new tag. In this +expression, $val represents the ValueConv value of the Require'd +tag.
+ +
To activate the config file, it must be named +".ExifTool_config" and placed either in your home directory or in +the same directory as the exiftool application. Note that the file name begins +with a ".", so if you are in Windows or on a Mac you may need to +rename the file from the command line since the GUI might not like file names +beginning with a ".". [An alternative is to load the +config file with the -config option -- see the +application documentation +for details.]
+ +
User-defined Composite tags have many other features, including the +ability to combine the values of multiple tags. See the +config file documentation for more details about +user-defined tags, and lib/Image/ExifTool/README in the full distribution for a +complete description of ValueConv features. Also, a +quick +search of the ExifTool forum should reveal a number of user-defined tag +examples, and there are other good (and often more complex) examples which can +be found in the %Image::ExifTool::Exif::Composite hash of the +lib/Image/ExifTool/Exif.pm source code. +
+ + +

20a. "ExifTool won't write an image due to errors", or +
20b. "How do I repair corrupted EXIF?"

+ +
Minor errors may be ignored using the -m option +(FAQ 15 discusses this with respect to MakerNote errors), but +sometimes there are more serious errors which can't be ignored. ExifTool may be +used to fix metadata problems in JPEG images by deleting all metadata and +rebuilding it from scratch. The command looks like this: + +
exiftool -all= -tagsfromfile @ -all:all -unsafe -icc_profile bad.jpg
+
+ +where "bad.jpg" is the name of the image that requires fixing. This +command deletes all metadata then copies all writable tags that can be extracted +from the original image to the same locations in the updated image. The +"Unsafe" tag is a shortcut +for unsafe EXIF tags in JPEG images which are not normally copied. JPEG images +may also contain an ICC color profile which should be preserved. The +"ICC_Profile" tag is also marked as unsafe, but is not part of the +EXIF so it isn't covered by the "Unsafe" shortcut and must be +specified separately.
+ +
After repairing an image like this you should be able to write to it +without errors, but note that some metadata from the original image may have +been lost in the process.
+ +
Note: ExifTool will not modify the JPEG image data, so if the +image itself is corrupted (eg. if you get a message saying "Not a valid JPEG"), +then ExifTool can not be used to repair the image. Also, ExifTool may not +be used like this to repair TIFF-based files or RAW files -- the risk of image +corruption is too great because the image is stored in the same IFD as the +metadata in these files.
+ +
If there are also MakerNote problems in the file, you may want to +add the -F option to the command. See FAQ 15 +for details. For example, to rebuild the EXIF only and fix the MakerNote +offsets you could do this: + +
exiftool -exif:all= -tagsfromfile @ -exif:all -unsafe -thumbnailimage -F bad.jpg
+
+ +Advanced: The byte order of the newly created EXIF is set by the value of +the ExifByteOrder tag. Since this tag does not belong to the EXIF group, it is +not copied with -exif:all above (but would be copied with +-all:all as in the first example). If ExifByteOrder is not set +then the byte order is determined by the ordering of the MakerNotes if they are +copied, otherwise big-endian ("MM") byte order is used by default. +ExifByteOrder may be set to a specific value to force a particular byte order +when creating new EXIF (eg. "-ExifByteOrder=II" for little-endian). +
+ + +

21. "How do I read/write values containing newline characters?"

+ +
When reading, by default exiftool converts all control characters to +"." to avoid messing up the output formatting, so newlines will appear as a "." +in the output. The -b option may be used to bypass all output +formatting (except that a line-feed character is inserted between items in a +list), but this may not be appropriate when the values of many tags must be +extracted. In this case, the formatted output (-p), JSON +(-j), XML (-X) and PHP (-php) options +provide alternative output formats which preserve newlines in values.
+ +
Alternatively, the API Filter option +may be used to apply an arbitrary filter to all extracted values. For example, +the following command changes newlines to "\n": + +
exiftool -api filter="s/\n/\\n/g" FILE
+ +When writing, there are a number of options: + +
    +
  1. In many shells, a newline may be inserted directly in the command +line: +

    Bourne shells (press RETURN inside a quoted string)

    +
    exiftool -comment="line 1
    +line 2" image.jpg
    +
    +

    (Also, in Bourne shells the character sequence $'\n' +may be used for a newline.)

    +

    C shells (press "\" then RETURN inside a quoted string)

    +
    exiftool -comment="line 1\
    +line 2" image.jpg
    +
    +[Unfortunately the Windows cmd shell provides no method to get a +newline (CR/LF in Windows) into the command line. A linefeed (LF) may be +inserted with CTRL-T, but I have found no way to insert a carriage return +(CR).]

    +
  2. +
  3. Use the -ec or -E option to allow C-style escape +sequences or HTML character entities (Note: +In Windows a newline is "\r\n" or "&#xd;&#xa;" +instead of just "\n" or "&#xa;"). For example, +using C-style escape sequences: +
    exiftool -ec "-comment=line 1\nline 2" image.jpg
    +or using HTML character entities: +
    exiftool -E "-comment=line 1&#xa;line 2" image.jpg
    +
  4. +
  5. Write the tag from the contents of a separate text file: +
    exiftool "-comment<=file.txt" image.jpg
    +
  6. +
  7. Use $/ in a redirection expression: (Note: Single quotes +must be used in Mac/Linux shells around arguments containing a dollar sign, +but double quotes are used instead in Windows. Also note that this technique +is slower since the implied -tagsFromFile adds an extra unnecessary +processing pass to read tags from the file, but most of this delay may be +avoided with the -fast3 option.) +
    exiftool '-comment<line 1$/line 2' image.jpg
    +
  8. +
  9. Use the "#[CSTR]" feature to allow C-style escape sequences +for a specific line in a -@ argfile. For example, this command: +
    exiftool -@ test.args image.jpg
    +with this "test.args" file: +
    #[CSTR]-comment=line 1\nline 2
  10. +
+
+ + +

22. "In what order are command-line assignments applied when writing?"

+ +
When writing, tag assignments on the command line are queued and +applied together as each target file is processed. In general, assignments +later on the command line override earlier assignments (ie. they are evaluated +left-to-right), but there are exceptions: +
  1. When writing list-type tags (eg. -keywords=one), new values +are accumulated rather than overriding earlier assignments.
     
  2. +
  3. When copying values to list-type tags (eg. +"-keywords<filename"), subsequent copies to the same tag +override earlier operations with the exception that new values may be added to +the queued list if a leading + is added to the target tag name (eg. +"-+keywords<filename").
     
  4. +
  5. Tags copied with the -tagsFromFile option are assigned in +order, and all together at the point in the command line where the +-tagsFromFile option is located, regardless of whether these tags +are specified immediately after the -tagsFromFile option or later +on the command line. Remember that "-tagsFromFile @" is implied +unless another file is specified when redirecting information with arguments +like "-DSTTAG<SRCTAG".
Note: When copying tag values, +adding to lists, or shifting date/time values, the source value is always the +original value found in the file, regardless of any previous assignments. For +example, the following command sets Subject to the original value of Title in +the file (NOT to "test"): +
exiftool -title=test "-subject<title" a.jpg
+ + +

23a. "Why do I get '0 image files updated' when writing?", or
+23b. "ExifTool doesn't write some tags to a file"

+ +
There are a few reasons why this may happen: + +
  1. The value of the tag is not being set correctly.
+ +This may be due to a tag value which can't be converted, in which case you +should warning like this (note: you may need to use the -v3 option +to see the warning if other same-named tags are being set properly by the same +assignment): +
Warning: Can't convert IFD0:Orientation (not in PrintConv)
+
+You get this warning if you write an invalid value to a tag which accepts only +specific values. See the "Values" column in the appropriate table of the +tag name documentation for a list of valid +values for these types of tags. The value conversion may also be bypassed with +the -n option, allowing numerical values to be written directly. +See FAQ number 6 for more details. + +
  1. The information type isn't supported by the format of the +target file.
+ +Warnings are NOT generated when a tag isn't written because it is +normal that many tags can't be written when copying between files of different +formats.
+ +
Tags are not written if the format of the target file doesn't +support the specific type of meta information. For example, CRW images do not +support EXIF or IPTC metadata. See the +Supported File Types table for an indication +of the tags supported by your file. If the tags aren't supported for your file +type, then a metadata sidecar file is an +alternative.
+ +
Also note that MakerNotes tags can not be created or deleted +individually, so they can only be written if they already exist in a +file. The entire MakerNotes must be created or deleted as a block (see +FAQ number 8 for details). + +
  1. A time value is being shifted but the specified tag doesn't +exist.
+ +For example, -datetimeoriginal+=1 will have no effect unless +the DateTimeOriginal tag exists in the image.
+ + +

24. "When I write a file the date/time gets reset to today's date"

+ +
You should be aware of the difference between date/time values +stored in the metadata of the file itself and date/time values stored in the +filesystem (ie. in the disk directory information). A command like this may be +used to extract all date/time information with an indication of where it is +stored: + +
exiftool -time:all -a -G0:1 -s c:\images\test.jpg
+ +and should give an output something like this: + +
[File:System]   FileModifyDate                  : 2009:10:05 20:40:36-04:00
+[File:System]   FileAccessDate                  : 2009:10:07 09:22:12-04:00
+[File:System]   FileCreateDate                  : 2009:10:05 20:40:36-04:00
+[EXIF:IFD0]     ModifyDate                      : 2003:10:31 15:44:19
+[EXIF:ExifIFD]  DateTimeOriginal                : 2003:10:31 15:44:19
+[EXIF:ExifIFD]  CreateDate                      : 2003:10:31 15:44:19
+ +The -G0:1 option causes the family 0 and 1 group names to be +reported in square brackets for each tag. Tags labelled "File:System" +are "pseudo" tags stored in the filesystem, while the others are real +tags stored in the metadata of the file.
+ +
ExifTool's default behaviour is to set all filesystem times to +the current date/time when any "real" tag is written, but the +-P option may be used to preserve the original FileModifyDate. +FileAccessDate represents the time the file was last accessed, and is set to the +current date/time whenever any software (including ExifTool) accesses the +file.
+ +
On systems where a filesystem creation date is maintained, ExifTool +also sets this to the current date/time when the file is edited. On Windows the +creation date is readable/writable through the FileCreateDate tag (see the +Extra Tags documentation), and is preserved +with the -P option. On MacOS, FileCreateDate is available but +extracted only if specified explicitly, and is writable only if "setfile" is +available. The -P option does not preserve the creation date when +editing a file on Mac systems, but the -overwrite_original_in_place +option may be used to preserve all Finder information including the creation +date, or the FileCreateDate may be copied back specifically (ie. +-tagsfromfile @ -FileCreateDate). On Linux, the file creation date +is not stored.
+ +
For example, commands like this act on common metadata tags, setting +the filesystem modification date/time to the current date/time: + +
# common metadata date/time tags are incremented by 1 hour, while
+# FileModifyDate is set to the current date/time
+exiftool -alldates+=1 c:\images
+ +[The AllDates tag is a shortcut which represents the 3 common +metadata date/time tags: DateTimeOriginal, CreateDate and +ModifyDate.]
+ +
However, FileModifyDate may be preserved with the -P +option: + +
# FileModifyDate is not changed
+exiftool -alldates+=1 -P c:\images
+ +ExifTool also allows FileModifyDate to be written, which provides full control +over the filesystem modification date/time when writing: + +
# FileModifyDate is incremented by 1 hour
+exiftool -alldates+=1 -filemodifydate+=1 c:\images
+
+# FileModifyDate is set from the value of DateTimeOriginal
+# (before DateTimeOriginal is incremented by 1 hour)
+exiftool -alldates+=1 "-filemodifydate<datetimeoriginal" c:\images
+
+ + +

25. "Can ExifTool be used as an image validator?"

+ +
ExifTool is not designed as an image validator but it does have a +-validate feature which enables extra validation checks, mainly for +JPEG and TIFF-format images (officially released in version 10.97, May 2018, +but continually being improved). Here is an example command to apply these +validation checks to a JPEG image: + +
exiftool -validate -warning -error -a test.jpg
+ +Note there are other packages specifically designed for file validation, for +example: JHOVE.
+ + +

26. "How do I import information from a database?"

+ +
[See FAQ number 12 for help with the reverse +-- exporting metadata to a database.]
+ +
ExifTool has the ability to import metadata from CSV and JSON format +database files for writing to output image files. For example, the following +commands use imported metadata to write to all image files in a directory. + +
# import from CSV file
+exiftool -csv="c:\Users\Phil\test.csv" "c:\Users\Phil\Images"
+
+# import from JSON file
+exiftool -json="c:\Users\Phil\test.json" "c:\Users\Phil\Images"
+ +For the above commands, the input CSV or JSON file must have the same format as +the output from these commands: + +
# export to CSV file
+exiftool -csv "c:\Users\Phil\Images" > "c:\Users\Phil\test.csv"
+
+# export to JSON file
+exiftool -json "c:\Users\Phil\Images" > "c:\Users\Phil\test.json"
+ +Specifically, the first row of the CSV file must contain the tag names. +The first column must be a special "SourceFile" column, containing the names of +the associated image files, with paths specified in the same way as on the +command line. The JSON file contains similar entries, but is not structured in +row/column format.
+ +
For each image file specified on the command line, all tags for the +database entry with the corresponding SourceFile name are written (with the +exception of the FileName and Directory tags, which are ignored). A special +SourceFile name of "*" may be used to match any image file. A warning is issued +and nothing is written if a specified file does not match any SourceFile entry +in the database.
+ +
Tag names may be prefixed by a group name to write to a specific +group (using the same format as when -G or -G1 is +added to the export command). To delete a tag, set the tag value to "-" (or the +MissingTagValue if this API option +was used) and use the -f option when importing. Tags with an empty +value are ignored in a CSV import, or set to an empty string in a JSON import +(unless -f is used and the +API MissingTagValue option is set to +an empty string, in which case the tag is deleted).
+ +
See the -csv or -j option in the +application documentation +for more details.
+ + +

27. "My ExifTool command doesn't work from a Windows .BAT file"

+ +
In a Windows .BAT file the "%" character is +significant, so all "%" characters in ExifTool commands must be +changed to "%%". For example, this command line: + +
exiftool "-FileName<CreateDate" -d "%Y%m%d_%H%M%S.%%e" image.jpg
+ +must be changed to this in a .BAT file: + +
exiftool "-FileName<CreateDate" -d "%%Y%%m%%d_%%H%%M%%S.%%%%e" image.jpg
+
+ + +

28. "How do I copy a shifted date/time value?"

+ +
The -TAG+= and -TAG-= arguments always act +on the existing tag value, so they can not be used to shift a new +tag value without running a second command. For example, this is wrong +because the copy operation is superseded by the shift, and the result is that +the existing DateTimeOriginal is shifted back by two hours: + +
exiftool "-datetimeoriginal<filemodifydate" -datetimeoriginal-=2 image.jpg  (WRONG!)
+
+ +The -globalTimeShift option fills this niche -- it shifts all +date/time tags by the specified amount when reading or copying, allowing a +shifted date/time value to be copied to another tag. So this is the proper +technique for shifting a date/time value when copying: + +
exiftool "-datetimeoriginal<filemodifydate" -globaltimeshift -2 image.jpg
+ +An alternative is to use the ShiftTime function of the +advanced formatting feature: + +
exiftool '-datetimeoriginal<${filemodifydate;ShiftTime("-2")}' image.jpg
+ +(Note: Swap the single and double quotes when running this command under Windows.) +
+Programmers: Here are the equivalent ExifTool API function calls: +
# using the GlobalTimeShift option
+$exiftool->Options(GlobalTimeShift => '-2');
+$exifTool->SetNewValuesFromFile($src, 'datetimeoriginal<filemodifydate');
+
+# using ShiftTime in an advanced formatting expression
+$exifTool->SetNewValuesFromFile($src, 'datetimeoriginal<${filemodifydate;ShiftTime("-2")}');
+
+ + +

29. "My options don't work in a -@ ARGFILE"

+ +
The ExifTool application has the ability to read options from an +ARGFILE using the -@ option. The parsing of these arguments is +different from the command line. Arguments in this file are separated by +newline characters instead of spaces. Also, arguments should not be quoted. +For example, on the command line you may have: + +
-d "%Y:%m:%d %H:%M:%S"
+ +but in an ARGFILE, this should be + +
-d
+%Y:%m:%d %H:%M:%S
+ + +

30. "How do I extract absolutely all metadata from a file?"

+ +
By default, duplicate tags, unknown tags, embedded tags, and System +tags that require external utilities are not extracted. The main reason for +this is performance; extracting these tags will significantly increase +processing time for some files. The following command extracts everything +possible with ExifTool: + +
exiftool -ee3 -U -G3:1 -api requestall=3 -api largefilesupport FILE
+ +(The -G3:1 option is included in the above command only to give an +indication of where the metadata was stored.)
+ + +

31. "Why does ExifTool rewrite the entire file when I am only changing one small thing?"

+ +
Generally it is possible in theory to edit existing fixed-length tags without having to rewrite an entire file, but ExifTool doesn't do this for 3 reasons: + +
    +
  1. The algorithm to do this would be completely different than the more general +and powerful case where you want to be able to add/delete tags and/or write +values with different lengths. Plus, implementing this feature would be a lot +of work, and it would only be useful in certain situations.
  2. +
  3. Some file formats (like MP4) allow metadata to be written at the beginning +or the end of a file. Writing it at the end of the file could avoid the need to +rewrite the entire starting section, but metadata is more useful at the start of +a file because then other programs (especially ones that stream the file over +the internet) don't have to read through the entire file to find the metadata. +Also, some software won't read metadata that comes at the end of a file +(probably for this reason).
  4. +
  5. ExifTool is designed to allow files to be read/written via pipes, which are +not seekable, so in-place editing is not possible with these files and again, +like 1, two completely different algorithms would be required.
  6. +
+ + +

32. "How do I safely delete all metadata from a file?"

+ +
First of all, all metadata shouldn't be removed from some file types +(such as RAW images) because this information is necessary for display of the +image. JPEG is the most popular image format and most suited to erasing all +metadata because the image and metadata are well separated in this format. +However, even with JPEG images care should be taken because the metadata +may contain color space information which should be maintained to preserve the +color rendition.
+ +
Here is a command that may be used to safely delete all metadata +from .JPG images in a directory: + +
exiftool -ext jpg -all= --icc_profile:all -tagsfromfile @ -colorspacetags DIR
+ +This command deletes all metadata except the ICC Profile if it exists, then +copies back any EXIF color space tags (adding any mandatory EXIF tags using +default values if necessary). +
+ +
+Last revised Jun 12, 2023 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/filename.html b/ExifTool/html/filename.html new file mode 100644 index 0000000..64df7b4 --- /dev/null +++ b/ExifTool/html/filename.html @@ -0,0 +1,380 @@ + + + + FileName and Directory tags + + + + +

Writing "FileName" and "Directory" tags

+ +

By writing the FileName and/or Directory pseudo tags, ExifTool +can be used to rename and/or move images into directories according to any +information contained in the image. This is a powerful feature when combined +with the -tagsFromFile ability to copy the values from other tags. +The most common use of this feature is to organize images by date/time, but any +other tag value may also be used.

+ +

Writing the Directory tag moves a file to a specified directory. The +directory is created if it doesn't already exist.

+ +

Writing the FileName tag renames a file. If the new FileName +has a directory specification (ie. contains a '/' character), then +the file is also moved to the specified directory (see +example 6 below), and the directory is created if necessary. +Existing files will not be overwritten (but see "Warning" below).

+ +

The write-only TestName tag provides a mechanism for dry-run testing +of the rename feature. Writing TestName displays the old and new names +without making any changes to the files. (Note that there is no corresponding +test tag for Directory, but TestName supports a full path name +just like FileName, so the directory may be tested as well.)

+ +

When writing the FileName, Directory or TestName tag, +%d, %f and %e may be used to represent +the directory, name and extension of the original file (in a similar way to the +-o, -w and -tagsFromFile options). Also, +%c may be used to add a copy number to the output file name to +avoid collisions with existing file names. Note that these codes must be escaped +with an extra % if used within a date format string. Modifiers may +also be used to change the default behaviour of these format codes. See the +-w option in the application +documentation for details.

+ +

When organizing files by date/time, the -d (date format) option +is essential for specifying a format for the date/time tag(s) used to generate +the new file and/or directory name. The examples below demonstrate the use of +this feature. Also, a list of of common date format codes +is provided for reference.

+ +
Advanced feature: The write-only +HardLink tag may be written using a technique similar to FileName. +Instead of renaming or moving the file, writing HardLink creates a hard +link with the specified name. This feature allows files to be organized without +affecting the originals. See the Extra Tags +documentation for more information.
+ +

Notes:

+ +

Writing the FileName and/or Directory tags alone causes the +file to be renamed or moved, not copied. However, if any "real" tags are +written at the same time, then the file is rewritten to the new destination and +the original file is left unchanged. (Only writable system tags may be set +without causing the file to be rewritten.) If desired, the +-overwrite_original option may be used to remove the original copy +when the file is rewritten.

+ +

Conversely, the -o option may be used to force exiftool to +always create a copy of the file, even if no meta-information tags are written. +This is true even if the -o option is superseded by writing the +Directory tag, or through a FileName which includes a directory +specification. (See example 5 below. Also see +example 11 for the directory precedence rules.)

+ +

If the -d option is used, the unformatted date/time value must +be valid (ie. in the form "YYYY:mm:dd HH:MM:SS"), otherwise the +date formatting will fail and the file will not be renamed or moved (but this may +be changed via the API StrictDate option).

+ +

Also note that the -d formatting applies to date/time tags used +in -if conditions unless the print conversion is disabled by adding +a # suffix to the tag name.

+ +

In a Windows batch file, all % characters must be escaped as +%%. This can result in extreme format codes like +%%%%f when using the -d option.

+ +
Warning: +Writing illegal file names in Windows can have unpredictable results and +may result in data loss. Illegal characters in Windows file names are: +
    / \ ? * : | " < >
+Any tag used in generating a file name which may contain these characters must +first be filtered to remove or translate these characters. A special feature +allows these characters to be removed from a tag value by adding a semicolon +inside the braces after a tag name. For example: +
    exiftool "-filename<${model;}.%e" c:\images
+This advanced formatting feature may also be used to do arbitrary reformatting +of any tag value used in a format string. See the -p option in the +application documentation for more details. +
+ +

Examples

+ + +
 0. +exiftool -d %Y%m%d_%H%M%%-c.%%e "-testname<CreateDate" DIR +
+
+The TestName tag is used for dry-run testing of the file renaming +feature. The above command is identical to that of the next example except that +TestName is written instead of FileName. So instead of renaming +the files, this command prints the old and new file names without actually +changing anything. For example: +
> exiftool -d %Y%m%d_%H%M%%-c.%%e "-testname<CreateDate" tmp
+'tmp/a.jpg' --> 'tmp/20031031_1544.jpg'
+'tmp/b.jpg' --> 'tmp/20010519_1836.jpg'
+    1 directories scanned
+    0 image files updated
+    2 image files unchanged
+
+ + +
 1. +exiftool -d %Y%m%d_%H%M%%-c.%%e "-filename<CreateDate" DIR +
+
+Rename all images in directory 'DIR' to names like +'20060327_1058-2.jpg', with individual file names derived from the +value of the CreateDate (plus a copy number with a leading '-' if a +file with the same name already exists), and with the same extension as the +original image. [Note that copying tag values with '<' implies +'-tagsfromfile @' unless otherwise specified. See the +-tagsFromFile description in the +application documentation for details.] +

FAQ: "Why doesn't this command rename my files?"

+

There are 2 common reasons for this:

+

a) When a directory name is specified, this command will only rename "writable" +files in the directory. Use 'exiftool -listwf' to list the +extensions of currently writable file types. The -ext option may +be used to rename other file types (eg. '-ext avi').

+

b) For this command to work, the CreateDate tag must exist in the source +file. Use 'exiftool -createdate FILE' to see if a file contains +this information. If it doesn't, you may need to use another date/time tag such +as DateTimeOriginal or FileModifyDate. To see all available date/time tags in a +file (and their locations), use 'exiftool -a -G1 -s -time:all FILE'.

+
+ + +
 2. +exiftool -d %Y-%m-%d "-directory<datetimeoriginal" image.jpg +
+
+Move 'image.jpg' into a directory with a name given by +DateTimeOriginal, in the form '2006-03-27'. +
+ + +
 3. +exiftool '-filename<%f_$imagesize.%e' dir +
+
+This example uses an expression to add the image size to the name of all images +in directory 'dir'. For example, this would rename a 640x480 image +called 'image.jpg' to 'image_640x480.jpg'. (Note that +the single quotes are necessary in Unix shells due to the '$' +symbol, but double quotes must be used instead when running in a Windows cmd shell.) +
+ + +
 4. +exiftool -r -directory=%e_images/%d pics +
+
+Recursively move all images based in directory 'pics' to +separate directory trees organized by file extension. For instance, in this +example the file 'pics/toys/new_car.jpg' is moved to +'jpg_images/pics/toys/new_car.jpg'. +
+ + +
 5. +exiftool -r -o %e_images/%d pics
+exiftool -r -o . -directory=%e_images/%d pics +
+
+Both of these commands have the same effect as example 4 above except that +images are copied instead of moved since the -o option was used. +The output directory specified by the -o option is overridden in +the second example by writing the Directory tag. This technique of using +-o with a dummy directory name is necessary when you want the files +to be copied instead of moved and the directory name is derived from the value +of other tags (eg. '-directory<createDate'). (Because the +values of other tags may not be used with the -o option.) +
+ + +
 6. +exiftool -r -d %Y/%m/%d/image_%H%M%S.%%e "-filename<filemodifydate" DIR +
+
+Recursively rename all images in 'DIR' and any contained +subdirectories to the form 'image_HHMMSS.EXT' (where +'ext' is the original file extension), and move them into a new +directory hierarchy based on date of file modification, with path names like +'2006/03/27/image_105859.jpg'. +
+ +

The following examples demonstrate the interaction of this feature with +other ExifTool options:

+ + +
 7. +exiftool -filename=new.jpg dir/image.jpg +
+
+Rename 'dir/image.jpg' to 'dir/new.jpg'. +
+ + +
 8. +exiftool -filename=new.jpg -comment=xxx dir/image.jpg +
+
+Copy 'dir/image.jpg', add a new comment, and write output to +'dir/new.jpg'. The original file 'dir/image.jpg' is +not changed. +
+ + +
 9. +exiftool -filename=new.jpg -comment=xxx -overwrite_original dir/image.jpg +
+
+Rewrite 'dir/image.jpg', adding a new comment and writing output to +'dir/new.jpg'. The original file 'dir/image.jpg' is +removed. +
+ + +
10. +exiftool -o tmp/ -filename=new.jpg image.jpg
+exiftool -o tmp/xxx.jpg -filename=new.jpg image.jpg
+exiftool -o tmp/new.jpg image.jpg +
+
+A file name or directory specified via the FileName or Directory +tag takes precedence over that specified by the -o option, so these +three commands all have the same effect: 'tmp/new.jpg' is created +without changing 'image.jpg'. Note that in the first command, the +trailing '/' on 'tmp/' is necessary if the +'tmp' directory doesn't already exist, otherwise 'tmp' +would be taken as a file name and 'new.jpg' would be created in the +current directory. As illustrated in example 4 above, the file is rewritten +instead of simply being renamed when the '-o' option is used. +
+ + +
11. +exiftool -directory=dir1 -filename=dir2/out.jpg -o dir3/ dir4/image.jpg +
+
+This example demonstrates the priorities of directory names specified using +different techniques. The output directory is taken from the first directory +specified from the following list: 1) the Directory tag, 2) the +directory part of the FileName tag, 3) the directory part of the +-o option, or 4) the source directory, in that order. The order of +the arguments on the command line is not significant. In this example, the +target file is copied (not moved, because the -o option was used) +to 'dir1/image.jpg'.
+
Note that both the FileName tag and the -o +option may be used without a directory specification, in which case the +directory with the next highest priority is used. Also note that if +-o specifies a directory, then the directory name must end with a +'/' or the directory must already exist, otherwise ExifTool will +interpret the last part as a file name. (eg. '-o images/test' +specifies the 'images' directory unless the +'images/test' directory already exists, while '-o +images/test/' unambiguously specifies the 'images/test' +directory.) +
+ + +
12. +exiftool -d %Y/%m "-directory<filemodifydate" "-directory<createdate" "-directory<datetimeoriginal" . +
+
Move image files from the current directory (.) into a +directory hierarchy based on year/month. In this command the Directory tag is +set from multiple other date/time tags. ExifTool evaluates the command-line +arguments left to right, and latter assignments to the same tag override earlier +ones, so the Directory for each image is ultimately set by the rightmost copy +argument that is valid for that image. Specifically, Directory is set from +DateTimeOriginal if it exists, otherwise CreateDate if it exists, and finally +FileModifyDate (which always exists) is used as a fallback.
+ + +
13. +exiftool -d %Y '-filename<$createdate/${model;}/${createdate#;DateFmt("%Y-%m-%d_%H%M%S")}.%e' pics +
+
Multiple date/time formatting codes become necessary when other +tags are place between date/time fields. The DateFmt helper +function is provided for this reason, and is applied to the unformatted tag +value (ie. tag name ending with '#') to achieve a different +date/time formatting in the same string. (Note that the single quotes are +necessary in Unix shells due to the '$' symbol, but double quotes +must be used instead when running in a Windows cmd shell.)
+ +

Common Date Format Codes

+

Date format codes are used in the argument to the -d option to +represent components of the date/time string. The codes listed below are common +to most systems, but additional codes may be available on your specific system +-- see your strftime man page for details. The default EXIF date/time +formatting is equivalent to '%Y:%m:%d %H:%M:%S'.

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
%a- abbreviated locale weekday name
%A- full locale weekday name
%b- abbreviated locale month name
%B- full locale month name
%c- preferred locale date/time representation
%d- day of month (01-31)
%f- fractional seconds (see note 1 below)
%H- hour on a 24-hour clock (00-23)
%I- hour on a 12-hour clock (01-12)
%j- day of year (001-366)
%m- month number (01-12)
%M- minute (00-59)
%p- 'AM' or 'PM'
%s- number of seconds since the Epoch, UTC (see note 2 below)
%S- seconds (00-59)
%w- weekday number (0-6)
%W- week number of the year (00-53)
%x- preferred locale date representation
%X- preferred locale time representation
%y- 2-digit year (00-99)
%Y- 4-digit year (eg. 2006)
%z- time zone in the form +/-hhmm (see note 2 below)
%Z- system time zone name (see note 3 below)
%%- a literal '%' character
+

ExifTool file name format codes may be used inside a date format +string when a date/time tag is used to set the value of the FileName or +Directory tags via the command-line interface. In this case, an extra +'%' must be added to pass the format code through the date/time +parser:

+
+ + + + +
%%d- original file directory (including trailing "/" if necessary)
%%f- original file name (without the extension)
%%e- original file extension (not including the ".")
%%c- copy number (output files only)
+

See the -w option in the exiftool +application documentation for details about special features available +with these name format codes.

+

Notes:

+
    +
  1. The %f format code is an ExifTool-specific enhancement and +supports an optional number of digits after the decimal point. For example, +%3f gives fractional seconds with 3 digits (eg. ".123"). A +"-" may be added to drop the decimal (eg. %-3f would +give "123"). The value is rounded to the specified number of digits, and the +date/time value is incremented by one second if the rounding would overflow to +the next second, even if the number of decimal digits is zero (ie. +%0f). Without %f, the fractional seconds are simply +discarded and no rounding is performed.

  2. +
  3. The %s and %z format codes use the time zone +specified by the date/time value. If the date/time value does not include a +time zone specification, then it is interpreted as a local time in the system +time zone. These format codes are parsed by ExifTool, so they should work on +all systems.

  4. +
  5. The %Z format code ignores any time zone specified in the +date/time value, and returns the system time-zone name for the given date/time +interpreted as a local system time.
  6. +
+
+

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/geotag.html b/ExifTool/html/geotag.html new file mode 100644 index 0000000..9a03849 --- /dev/null +++ b/ExifTool/html/geotag.html @@ -0,0 +1,728 @@ + + + + Geotagging with ExifTool + + + + + +

Geotagging with ExifTool

+

The ExifTool geotagging feature adds GPS tags to images based on data from a +GPS track log file. The GPS track log file is loaded, and linear interpolation +is used to determine the GPS position at the time of the image, then the +following tags are written to the image (if the corresponding information +is available):

+ +
GPSLatitude        GPSLongitude      GPSAltitude          GPSDateStamp
+GPSLatitudeRef     GPSLongitudeRef   GPSAltitudeRef       GPSTimeStamp
+GPSTrack           GPSSpeed          GPSImgDirection      GPSPitch        
+GPSTrackRef        GPSSpeedRef       GPSImgDirectionRef   GPSRoll
+AmbientTemperature CameraElevationAngle
+
+ +
Note: GPSPitch and GPSRoll are not standard tags, and +must be user-defined.
+ +

Currently supported GPS track log file formats:

+ +
    +
  • GPX
  • +
  • NMEA (RMC, GGA, GLL and GSA sentences)
  • +
  • KML
  • +
  • IGC (glider format)
  • +
  • Garmin XML and TCX
  • +
  • Magellan eXplorist PMGNTRK
  • +
  • Honeywell PTNTHPR (see Orientation)
  • +
  • Bramor gEO log
  • +
  • Winplus Beacon .TXT
  • +
  • Google Takeout .JSON
  • +
  • GPS/IMU .CSV
  • +
  • DJI .CSV
  • +
  • ExifTool .CSV file
  • +
+ + +

Implementation

+ +

Geotagging is accomplished in ExifTool through the use of three special +write-only Extra tags: +Geotag, Geosync and Geotime.

+ + +

Geotag

+ +

The Geotag tag is used to define the GPS track log data. The +geotagging feature is activated by assigning the name of a track log file to +this tag. As an example, the following command line adds GPS tags to all images +in the "/Users/Phil/Pictures" directory based on GPS positions stored in the +track log file "track.log" in the current directory:

+ +
exiftool -geotag=track.log /Users/Phil/Pictures
+
+ +

For convenience (and to make this feature more prominent in the +documentation), the exiftool application also provides a -geotag +option, so this command is equivalent to the one above:

+ +
exiftool -geotag track.log /Users/Phil/Pictures
+
+ +

Multiple GPS log files may be loaded simultaneously by using more than one +-geotag option or -geotag= assignment in the same +command, or by using wildcards in the filename argument of the +-geotag option. This allows batch processing of images spanning +different tracks with a single command. When using wildcards the argument may +need to be quoted on some systems to prevent shell globbing, but note that +wildcards are not supported with the -geotag= syntax. See the +examples below.

+ +

Deleting the Geotag tag (with -geotag=) causes the +GPS tags written by the -geotag feature to be deleted.

+ +

A special feature allows writing of only GPS date/time tags when there is no +position available by specifying a log file name of "DATETIMEONLY" (all +capitals).

+ +
Programmers: You may write either a GPS log file name +or the GPS log data as the value for Geotag. If the value contains +a newline or a null byte it is assumed to be data, otherwise it is taken as a +file name.
+ + +

Geosync

+ +

The Geosync tag is needed only when the image timestamps are not +properly synchronized with GPS time. The value written to Geosync +may take a number of different forms, but the basic format is that of a simple +time difference which is added to Geotime before interpolating the +GPS position in the track log. This time difference may be of the form "SS", +"MM:SS", "HH:MM:SS" or "DD HH:MM:SS" (where SS=seconds, MM=minutes, HH=hours and +DD=days), and a leading "+" or "-" may be added for positive or negative +differences (positive if the GPS time was ahead of the camera clock). +Fractional seconds are allowed (eg. "SS.ss").

+ +

For example, "-geosync=-1:20" specifies that synchronization +with GPS time is achieved by subtracting 1 minute and 20 seconds from the +Geotime value. See the Time Synchronization Tip +below for more details.

+ +
Note that a single decimal value is interpreted as +seconds when written to Geosync. This is different from of other +date/time shift values where a single value is normally taken as hours. +
+ +

The Geosync value may also be specified using 3 different +formats which provide a GPS time and a corresponding camera clock time. While +these formats may be used for a simple (constant) time synchronization offset, +they are necessary when performing a clock drift correction (with multiple +synchronization points), and are described below.

+ +

Camera clock drift correction:

+ +

A more advanced Geosync feature allows the GPS time and the +image time to be specified together, facilitating a time drift correction if +more than one synchronization point is provided. For this, the value written to +Geosync takes one of the following forms:

+ +
+ + + + + + +
FormatNotes
FILEBoth GPS and image timestamps are extracted from the +specified file. eg) -geosync=image.jpg
GPSTIME@FILEGPS time is taken from the +Geosync value and the image timestamp is extracted from the +specified file. eg) -geosync="12:58:05@image.jpg"
GPSTIME@IMGTIMEBoth GPS and image timestamps are +taken from the Geosync value. eg) +-geosync="12:58:05@2010:01:02 12:25:26"
+ +

The values of GPSTIME and IMGTIME specified on the command line +may contain a date, but it is not necessary.

+ +

Notes:

+
    +
  1. If either the GPS or the image timestamp does not contain a date, the two +timestamps are assumed to be on days which place them within 12 hours of each +other.
  2. +
  3. If neither the GPS nor the image timestamps contain a date, this +synchronization point may only be used for constant time offset (ie. no time +drift correction will be applied).
  4. +
  5. Both the GPS and the image times are assumed to be local unless another +timezone is specified (unless taken from GPSTimeStamp which is UTC).
  6. +
  7. Both the GPS and the image time values may contain decimal seconds.
  8. +
  9. The applied value of the time drift correction is calculated from a +piecewise linear interpolation/extrapolation between the synchronization points +if more than one Geosync value is defined.
  10. +
  11. When extracting from file, timestamps are taken from the first available of +the following tags: +
      +
    • Image timestamp: SubSecDateTimeOriginal, SubSecCreateDate, SubSecModifyDate, +DateTimeOriginal, CreateDate, ModifyDate, FileModifyDate
    • +
    • GPS timestamp: GPSDateTime, GPSTimeStamp
    • +
  12. +
+ + +

Geotime

+ +

The Geotime tag specifies the point in time for which the GPS +position is calculated (by interpolating between fixes in the GPS track log). +Unless a group is specified, exiftool writes the generated tags to the default +groups. If a value for Geotime is not given, it is taken from +unformatted value of DateTimeOriginal for each image (as if +"-Geotime<DateTimeOriginal#" had been specified), but the value +may be copied from any other date/time tag or set directly from a date/time +string.

+ +

If the date/time tag does not include a timezone then one may be added (eg. +"-Geotime<${CreateDate}-05:00"), otherwise the local system time +is assumed. Decimal seconds are supported in the time value.

+ +

By default, GPS tags are created in EXIF and the corresponding +XMP tags are updated only if they already exist. However, an EXIF or XMP group +name may be specified to force writing only to the specified location. For +example, writing XMP:Geotime or EXIF:Geotime will +write the generated GPS tags only to XMP or EXIF respectively. Note that when +written to XMP, the GPSLatitudeRef and GPSLongitudeRef +tags are not used, and the XMP GPSDateTime tag is written instead +of the separate EXIF GPSDateStamp and GPSTimeStamp +tags.

+ +

See the Examples section below for sample command +lines illustrating various aspects of the geotagging feature.

+ +
Programmers: Note that Geotime must always +be specified when geotagging via the API (the default value of +DateTimeOriginal# is implemented by the application). Also, +Geotime must be set after both Geotag and +Geosync (the exiftool application reorders the assignments to +ensure this). +
+ + +

ExifTool CSV Log File Format

+ +

ExifTool supports input log files in CSV format with the first row containing +headings in the form of ExifTool tag names or descriptions. Valid column +headings are:

+ +
+ + + + + + + + + + + + + + +
Column HeadingDescription
GPSDateTimeDate and time in standard EXIF format, or other +format specified by the -d option if used.
Time is assumed to be +in UTC unless the values contain another time zone
GPSDateStampDate in standard EXIF format
GPSTimeStampTime in EXIF format. UTC is assumed unless +the values include a time zone
GPSLatitudeLatitude in flexible ExifTool format (see FAQ 14)
GPSLongitudeLongitude in flexible ExifTool format (see FAQ 14)
GPSLatitudeRefString beginning with "S" for southern coordinates (used only if GPSLatitude isn't signed or doesn't specify hemisphere)
GPSLongitudeRefString beginning with "W" for western coordinates (used only if GPSLongitude isn't signed or doesn't specify hemisphere)
GPSAltitudeAltitude in meters relative to sea level (negative for below sea level)
GPSSpeedSpeed in knots, or specified units if "(km/h)", "(mph)" or "(m/s)" appears in heading
GPSTrackCompass heading in degrees true
GPSImgDirectionCamera compass direction in degrees true
GPSPitch or
CameraElevationAngle
Pitch angle in degrees with positive pitch upwards
GPSRollRoll angle in degrees
+ +

Required columns are GPSDateTime (or GPSDateStamp and GPSTimeStamp), +GPSLatitude and GPSLongitude. All other columns are optional, and unrecognized +columns are ignored.

+ + +

Examples

+ + +

1. Geotag all images in the "c:\images" directory from position information in a +GPS track log ("c:\gps logs\track.log"). Since the Geotime time is +not specified, the value of DateTimeOriginal# is used. Local +system time is assumed unless DateTimeOriginal# contains a +timezone:

+ +
exiftool -geotag "c:\gps logs\track.log" c:\images
+ + +

2. Geotag all images in directory "dir" from the GPS positions in "track.log" +(in the current directory), for a camera clock that was running 25 seconds +slower than the GPS clock:

+ +
exiftool -geotag track.log -geosync=+25 dir
+ + +

3. Geotag an image with the GPS position for a specific time:

+ +
exiftool -geotag t.log -geotime="2009:04:02 13:41:12-05:00" a.jpg
+ + +

4. Geotag all images in directory "dir" with XMP tags instead of EXIF tags, +based on the image CreateDate:

+ +
exiftool -geotag log.gpx "-xmp:geotime<createdate" dir
+ + +

5. Geotag images in "dir" using CreateDate with the specified +timezone. If CreateDate already contained a timezone, then the +timezone specified on the command line is ignored. (Note that in Windows, +double quotes (") must be used instead of single quotes +(') around the -geotime argument in the next 2 +commands):

+ +
exiftool -geotag a.log '-geotime<${createdate}+01:00' dir
+ + +

6. Geotag images for which the camera clock was set to UTC (+00:00), using +the time from DateTimeOriginal:

+ +
exiftool -geotag trk.gpx '-geotime<${DateTimeOriginal}+00:00' dir
+ + +

7. Delete GPS tags which were added by the geotag feature. (Note that this does +not remove all GPS tags -- to do this instead use -gps:all=):

+ +
exiftool -geotag= a.jpg
+ + +

8. Delete XMP GPS tags which were added by the geotag feature:

+ +
exiftool -xmp:geotag= a.jpg
+ + +

9. Geotag an image with XMP tags, using the time from +DateTimeOriginal:

+ +
exiftool -xmp:geotag=track.log a.jpg
+ + +

10. Combine multiple track logs and geotag an entire directory tree of +images:

+ +
exiftool -geotag a.log -geotag b.log -r dir
+ + +

11. Use wildcards to load multiple track files (the quotes are necessary for most +operating systems to prevent filename expansion):

+ +
exiftool -geotag "logs/*.log" dir
+ + +

12. Geotag from a sub-second date/time value with a sub-second time synchronization +(only possible if the EXIF sub-second time stamps are available):

+ +
exiftool -Geotag a.log -Geosync=+13.42 "-Geotime<SubSecDateTimeOriginal" dir
+
+ + +

13. Geotag images with a piecewise linear time drift correction using the GPS +time synchronization from three already-geotagged images:

+ +
exiftool -geotag a.log -geosync=1.jpg -geosync=2.jpg -geosync=3.jpg dir
+
+ + +

Options

+ +

Geotagging may be configured via the following ExifTool options. These +options may be set using either the -api option of the command-line +application, the Options() function of the API, or the +%Image::ExifTool::UserDefined::Options hash of the config file. (See the +sample config file for details about how to use the +config file.)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescriptionValuesDefault
GeoMaxIntSecsMaximum interpolation time in seconds for geotagging. Geotagging is + treated as an extrapolation if the Geotime value lies between two fixes in + the same track which are separated by a number of seconds greater than this. + Otherwise, the coordinates are calculated as a linear interpolation between + the nearest fixes on either side of the Geotime value. Set to 0 to disable + interpolation and use the coordinates of the nearest fix instead (provided + it is within GeoMaxExtSecs, otherwise geotagging fails).A floating point number1800
GeoMaxExtSecsMaximum extrapolation time in seconds for geotagging. Geotagging fails + if the Geotime value lies outside a GPS track by a number of seconds greater + than this. Otherwise, the coordinates of the nearest fix are taken.A floating point number1800
GeoMaxHDOPMaximum Horizontal (2D) Dilution Of Precision for geotagging. GPS fixes are + ignored if the HDOP is greater than this.A floating point number, or undef to disableundef
GeoMaxPDOPMaximum Position (3D) Dilution Of Precision for geotagging. GPS fixes are + ignored if the PDOP is greater than this.A floating point number, or undef to disableundef
GeoMinSatsMinimum number of satellites for geotagging. GPS fixes are ignored if the + number of acquired satellites is less than this.A positive integer, or undef to disableundef
GeoSpeedRefReference units for writing GPSSpeed when geotagging. + + + +
Kk or km/h= km/h
Mm or mph= mph
(anything else)= knots
undef
+ + +

Orientation

+ +

ExifTool reads orientation information from the PTNTHPR sentence generated by +some Honeywell digital compasses. This is a proprietary NMEA sentence which +contains information about heading, pitch and roll angles. When this +information is available, the heading is written to GPSImgDirection (and +GPSImgDirectionRef is set to "T"), and pitch to CameraElevationAngle, but no +standard tag exists for roll. Regardless, ExifTool attempts to write +GPSRoll (and GPSPitch). For these tags to be written, appropriate +user-defined tags must be created. Below is a simple config file which +defines the necessary EXIF GPS tags. Corresponding XMP-exif tags may also be +created. See the config file documentation for +more information.

+ +
%Image::ExifTool::UserDefined = (
+    'Image::ExifTool::GPS::Main' => {
+        0xd000 => {
+            Name => 'GPSPitch',
+            Writable => 'rational64s',
+        },
+        0xd001 => {
+            Name => 'GPSRoll',
+            Writable => 'rational64s',
+        },
+    },
+);
+1; #end
+
+ + +

Troubleshooting

+ + +

1. "No track points found in GPS file"

+ +
If you see the above message, either exiftool does not yet support +your track log file format, or your track log does not contain the necessary +position/timestamp information. For instance, in KML files each Placemark must +contain a TimeStamp. If you believe your track log contains the necessary +information, please send me a sample file and I will add support for this +format.
+ + +

2. "No writable tags set" or "0 image files updated"

+ +
If you see these without any other warning messages, it is likely +that Geotime didn't get set properly.
+ +
Be sure that the necessary date/time tag exists in your image for +copying to Geotime. Unless otherwise specified, the required tag +is DateTimeOriginal. The following command may be used to list the +names and values of all available date/time tags in an image: + +
exiftool -s -time:all image.jpg
+
+ +Even if there is no metadata in the image you may be able to set +Geotime from the filesystem modification date for the image (which +will appear as FileModifyDate in the output of the above command). +In this case you may also want to include the -P option to preserve +the original value of FileModifyDate: + +
exiftool -geotag track.gpx "-geotime<filemodifydate" -P image.jpg
+
+ +Without the -P option, FileModifyDate is set to the +current date/time when the file is rewritten.
+ + +

3. "Warning: Time is too far before track in File:Geotime (ValueConvInv)"

+ +
If you see a warning like this, you may have a time zone problem, +or a time synchronization issue. Keep in mind that GPS times are in UTC, but +the camera times are typically in your local time zone.
+ +
To see more details about what ExifTool is doing, try adding the +-v2 option to your command. You should then see messages like +this if the GPS track log was loaded successfully: + +
Loaded 372 points from GPS track log file 'my_track.log'
+  GPS track start: 2009:03:30 19:45:25 UTC
+  GPS track end:   2009:04:03 11:16:04 UTC
+
+ +If the number of points loaded and start/end times seem reasonable, then +the problem is likely in the time synchronization. Also printed will be the +UTC time for the image: + +
  Geotime value:   2009:04:03 10:57:01 UTC (local timezone is -05:00)
+
+ +The "Geotime value" must lie within 1/2 hour of a valid GPS fix in the track log +for a position to be calculated. (1/2 hour is the default, but this can be +configured via the geotagging Options.) The time +calibration relies on proper synchronization between the GPS time and your +camera's clock. If a timezone is not specified, the local system time zone (as +set by the shell's TZ environment variable) is printed in the above message and +used to convert the Geotime value to UTC. You should specify the +timezone for Geotime if your images were taken in a different +timezone (see Examples above). If the camera clock was +wrong, the Geosync tag may be used to apply a time correction, or +the ExifTool time shift feature may be used to adjust the image times before +geotagging -- see the Time Synchronization tip below for +examples.
+ + +

Tips

+ + +

1. Time Synchronization

+ +
One way to accurately synchronize your images with GPS time is to +take a picture of the time displayed on your GPS unit while you are out +shooting. Then after you download your images you can use this image to +synchronize the image timestamps for geotagging. This is done by using an image +viewer to read the time from the GPS display in the image, and exiftool to +extract DateTimeOriginal from the file. For example, if the time in +the GPS display reads 19:32:21 UTC and DateTimeOriginal is +14:31:49, then for this image the camera clock was 32 seconds slow (assuming +that the timezone of the camera clock was -05:00). There +are various ways to use this time synchronization to improve your geotagging +accuracy:
+ +
A) Use the Geosync tag to specify the time difference +while geotagging. Using this technique the existing image timestamps will not +be corrected, but the GPSTimeStamp tag created by the geotagging +process will contain the correct GPS time: + +
exiftool -geosync=+00:00:32 -geotag my_gps.log C:\Images
+
+or equivalently, +
exiftool -geosync=19:32:21Z@14:31:49-05:00 -geotag my_gps.log C:\Images
+
+ +(Note that this technique may also be used for a more advanced time +drift correction. See the Geosync section above for +details)
+ +
B) First fix the image timestamps by shifting them to synchronize +with GPS time, then geotag using the corrected timestamps: + +
exiftool -alldates+=00:00:32 C:\Images
+exiftool -geotag my_gps.log C:\Images
+
+ +C) Do both in the same command: + +
exiftool -alldates+=00:00:32 -geosync=+00:00:32 -geotag my_gps.log C:\Images
+
+ +The examples above assume that your track log file (my_gps.log) +is in the current directory, that the images were downloaded to the +C:\Images directory, and that the computer and camera clocks are +in the same timezone.
+ +
+ +

Inverse Geotagging

+ +

ExifTool also has the ability to create a GPS track file from a series of +geotagged images. The -p option may be used to output files in +any number of formats. This section gives examples for creating GPX and KML +output files from a set of geotagged images, or from a geotagged video file. +(But note that the -ee3 option must be added to the commands below +to extract the full track from a video file.)

+ + +

Creating a GPX track log

+ +

The following print format file may be used to generate a GPX track log from +one or more geotagged images:

+ +
#------------------------------------------------------------------------------
+# File:         gpx.fmt
+#
+# Description:  Example ExifTool print format file to generate a GPX track log
+#
+# Usage:        exiftool -p gpx.fmt -ee3 FILE [...] > out.gpx
+#
+# Requires:     ExifTool version 10.49 or later
+#
+# Revisions:    2010/02/05 - P. Harvey created
+#               2018/01/04 - PH Added IF to be sure position exists
+#               2018/01/06 - PH Use DateFmt function instead of -d option
+#               2019/10/24 - PH Preserve sub-seconds in GPSDateTime value
+#
+# Notes:     1) Input file(s) must contain GPSLatitude and GPSLongitude.
+#            2) The -ee3 option is to extract the full track from video files.
+#            3) The -fileOrder option may be used to control the order of the
+#               generated track points when processing multiple files.
+#------------------------------------------------------------------------------
+#[HEAD]<?xml version="1.0" encoding="utf-8"?>
+#[HEAD]<gpx version="1.0"
+#[HEAD] creator="ExifTool $ExifToolVersion"
+#[HEAD] xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+#[HEAD] xmlns="http://www.topografix.com/GPX/1/0"
+#[HEAD] xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">
+#[HEAD]<trk>
+#[HEAD]<number>1</number>
+#[HEAD]<trkseg>
+#[IF]  $gpslatitude $gpslongitude
+#[BODY]<trkpt lat="$gpslatitude#" lon="$gpslongitude#">
+#[BODY]  <ele>$gpsaltitude#</ele>
+#[BODY]  <time>${gpsdatetime#;my ($ss)=/\.\d+/g;DateFmt("%Y-%m-%dT%H:%M:%SZ");s/Z/${ss}Z/ if $ss}</time>
+#[BODY]</trkpt>
+#[TAIL]</trkseg>
+#[TAIL]</trk>
+#[TAIL]</gpx>
+
+ +

This example assumes that the GPSLatitude, +GPSLongitude, GPSAltitude and GPSDateTime +tags are all available in each processed FILE. Warnings will be +generated for missing tags. The output GPX format will invalid if any +GPSLatitude or GPSLongitude tags are missing, but will +be OK for missing GPSAltitude or GPSDateTime tags.

+ +

Note that the order of track points in the output GPX file will be the same +as the order of processing the input files, which may not be chronological +depending on how the files are named. The -fileOrder option may be +used to force processing of files in a particular order. For example, the +following command processes files in order of increasing GPSDateTime: +

+ +
exiftool -fileOrder gpsdatetime -p gpx.fmt /Users/Phil/Pictures > out.gpx
+
+ +

Since no directory was specified for gpx.fmt, this file must +exist in the current directory when the above command is executed. (If the +gpx.fmt file can't be found then the -p argument is +interpreted as a string instead of a file name, and the text +"gpx.fmt" is sent to the output, which isn't what we want.)

+ +

The -if option may be added to ensure that only files containing +GPS information are processed. For example, the following command creates +"out.gpx" in the current directory from all pictures containing +GPSDateTime information in directory "pics" and its +sub-directories:

+ +
exiftool -r -if '$gpsdatetime' -fileOrder gpsdatetime -p gpx.fmt pics > out.gpx
+
+ +

Note: In Windows, double quotes (") must be used instead of +single quotes (') around the -if argument above.

+ +

The "fmt_files" directory of the full exiftool distribution contains this +sample format file ("gpx.fmt") as well as a sample which creates GPX waypoints +with pictures ("gpx_wpt.fmt").

+ + +

Creating a Google Earth KML file

+ +

Below is an example of a print format file which generates a Google Earth KML +file from a collection of geotagged images. This example uses the SECT feature added +in ExifTool 10.41 to divide the placemarks into folders based on directory name:

+ +
#------------------------------------------------------------------------------
+# File:         kml.fmt
+#
+# Description:  Example ExifTool print format file for generating a
+#               Google Earth KML file from a collection of geotagged images
+#
+# Usage:        exiftool -p kml.fmt -r DIR [...] > out.kml
+#
+# Requires:     ExifTool version 10.41 or later
+#
+# Revisions:    2010/02/05 - P. Harvey created
+#               2013/02/05 - PH Fixed camera icon to work with new Google Earth
+#               2017/02/02 - PH Organize into folders based on file directory
+#               2018/01/04 - PH Added IF to be sure position exists
+#               2020/01/11 - F. Kotov Limited image preview size to 500px
+#
+# Notes:     1) Input files must contain GPSLatitude and GPSLongitude.
+#            2) Add the -ee3 option to extract the full track from video files.
+#            3) For Google Earth to be able to find the images, the input
+#               images must be specified using relative paths, and "out.kml"
+#               must stay in the same directory as where the command was run.
+#            4) Google Earth is picky about the case of the image file extension,
+#               and may not be able to display the image if an upper-case
+#               extension is used.
+#            5) The -fileOrder option may be used to control the order of the
+#               generated placemarks when processing multiple files.
+#------------------------------------------------------------------------------
+#[HEAD]<?xml version="1.0" encoding="UTF-8"?>
+#[HEAD]<kml xmlns="http://earth.google.com/kml/2.0">
+#[HEAD]  <Document>
+#[HEAD]    <name>My Photos</name>
+#[HEAD]    <open>1</open>
+#[HEAD]    <Style id="Photo">
+#[HEAD]      <IconStyle>
+#[HEAD]        <Icon>
+#[HEAD]          <href>http://maps.google.com/mapfiles/kml/pal4/icon38.png</href>
+#[HEAD]          <scale>1.0</scale>
+#[HEAD]        </Icon>
+#[HEAD]      </IconStyle>
+#[HEAD]    </Style>
+#[SECT]    <Folder>
+#[SECT]      <name>$main:directory</name>
+#[SECT]      <open>0</open>
+#[IF]  $gpslatitude $gpslongitude
+#[BODY]      <Placemark>
+#[BODY]        <description><![CDATA[<img src='$main:directory/$main:filename'
+#[BODY]          style='max-width:500px;max-height:500px;'> ]]>
+#[BODY]        </description>
+#[BODY]        <Snippet/>
+#[BODY]        <name>$filename</name>
+#[BODY]        <styleUrl>#Photo</styleUrl>
+#[BODY]        <Point>
+#[BODY]          <altitudeMode>clampedToGround</altitudeMode>
+#[BODY]          <coordinates>$gpslongitude#,$gpslatitude#,0</coordinates>
+#[BODY]        </Point>
+#[BODY]      </Placemark>
+#[ENDS]    </Folder>
+#[TAIL]  </Document>
+#[TAIL]</kml>
+
+ +

This example file is included in the "fmt_files" directory of the full +ExifTool distribution. See +this +forum post for more useful tips about creating KML files.

+ +
+Created Apr 2, 2009
+Last revised Sep 23, 2021 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/history.html b/ExifTool/html/history.html new file mode 100644 index 0000000..4c25991 --- /dev/null +++ b/ExifTool/html/history.html @@ -0,0 +1,393 @@ + + + + ExifTool Version History + + + + +

ExifTool Version History

+ +

RSS feed: https://exiftool.org/rss.xml

+ +

Note: The most recent production release is Version 12.60. (Other versions are +considered development releases, and are not uploaded to MetaCPAN.)

+ + + + +Sept. 19, 2023 - Version 12.67 +
    +
  • Added a new Pentax LensType (thanks dmont) +
  • Added a new FujiFilm FilmMode and FaceElementTypes values (thanks Greybeard) +
  • Fixed error writing new DataMining tag where URI prefix wasn't being + properly added to the value +
+ +Sept. 19, 2023 - Version 12.66 +
    +
  • Added a few new Canon LensType values (thanks Norbert Wasser) +
  • Added conversions for a few Apple:ImageCaptureType values +
  • Added new XMP tag for PLUS version 2.0.1 +
  • Added a new CanonModelID (thanks Laurent Clevy) +
  • Decode another tag from Canon 1DS raw images (Hubert Figuière, github #219) +
  • Decode JPGCompression for newer Nikon models (thanks Warren Hatch) +
  • Fixed bug introduced in 12.65 where duplicate tags were not returned even + when the groups where specified explicitly +
  • API Changes: + +
+ +Aug. 10, 2023 - Version 12.65 +
    +
  • Added a new QuickTime Keys tag +
  • Added a new CanonModelID (thanks Laurent Clevy) +
  • Added a new Canon LensType (thanks Norbert Wasser) +
  • Added number in brackets to converted Samsung MCCData value +
  • Decode a number of new Sony tags (thanks Jos Roost) +
  • Decode a few new FlashPix tags (github #217) +
  • Improved decoding of Nikon Z9 firmware 4.0 tags (thanks Warren Hatch) +
  • Improved parsing of PDF:Keywords to support semicolon-separated lists +
  • Enhanced -api option to show list of available options if no argument is + provided +
  • Lowered priority of IFD1 tags in ARW images so IFD0/SubIFD take precedence +
  • Changed QuickTime tag names for atID (AlbumTitleID to ArtistID) and plID + (PlayListID to AlbumID) (github issue #216), and added cmID (ComposerID) +
  • Changed Apple:MediaGroupUUID tag name back to ContentIdentifier +
  • Patched the -d option to handle the %s format code internally when writing + (avoids problems due to inconsistent behaviour of this format code in the + strptime function on different systems) +
  • Patched patch of version 12.32 to restore ability to read from named pipes +
  • Fixed bug which could cause a hang when processing a corrupt BigTIFF image +
  • Fixed document number for auxiliary image metadata in HEIC files +
  • Fixed misspelt Apple tag name (thanks Neal Krawetz) +
  • API Changes: + +
+ +June 28, 2023 - Version 12.64 +
    +
  • Added a new Sony LensType (thanks Jos Roost) +
  • Added config_files/guano.config to the distribution (thanks StarGeek) +
  • Added support for Garmin Low-resolution Video (GLV) files +
  • Added JUMBF to the list of deletable groups +
  • Added (untested) read support for spherical video tags in Matroska videos +
  • Decode a number of new Nikon Z9 tags (thanks Warren Hatch) +
  • Decode AmbisonicAudio tags in spherical MP4 vidoes +
  • Decode another Apple tag +
  • Improved French translations (thanks Philippe Bonnaure of GraphicConverter) +
  • Patched to allow writing QuickTime-based videos where the audio/video sample + description comes after the sample pointers +
  • Fixed parsing of GPS from Insta360 videos to properly skip void fixes +
  • Fixed problem where Apple iPhone 14 images produced invalid XML in -X output + when using -struct option +
  • API Changes: + +
+ +June 8, 2023 - Version 12.63 +
    +
  • Added ability to read/write/create Brotli-compressed metadata in JXL images + (requires IO::Compress::Brotli) +
  • Added partial support for Exif 3.0 specification: +
      +
    • Added new EXIF tags +
    • Added MPF Original Preservation Image type +
    • Support for reading 'utf8' values (but still write only as 'string') +
    +
  • Added support for Adobe XMP-hdrgm (HDR Gain Map) tags +
  • Added support for reading 7z files (thanks Amir Gooran, github #205) (but + currently this doesn't work for the Windows .exe version because I haven't + been able to install Compress::Raw::Lzma for ActivePerl) +
  • Added XMP-panorama tags +
  • Added warning if -csv is used with -p +
  • Added warning if trying to geotag from a UTF-16 track log +
  • Decode ImageWidth/Height from JXL images using partial codestreams +
  • Decode more Sony tags for some newer models (thanks Jos Roost) +
  • Extract GainMapImage (hrgm box) from JXL files +
  • Extract Guano information from WAV files +
  • Enhanced ImageDataMD5 feature and renamed to ImageDataHash (with + ImageDataMD5 alias for backward compatibility) +
  • Changed RARVersion tag name to FileVersion +
  • Fixed bug introduced in 12.46 which could cause a hang when reading a + corrupted RIFF-based file +
  • Fixed writing of Composition:GPSPosition when -n is used +
  • API Changes: + +
+ +May 3, 2023 - Version 12.62 +
    +
  • Added basic read support for WPG images +
  • Added ImageDataMD5 support for HEIC images +
  • Added support for RAR version 5.0 files (thanks Amir Gooran, github #203) +
  • Added a few new XMP-aux tags (thanks John Ellis) +
  • Made Composite tags available for use in -fileNUM argument +
  • Better handling of FlashPix VT_EMPTY value +
  • Fixed "Can't write" error when specifying a .webp file for the -o option +
  • API Changes: + +
+ +Apr. 24, 2023 - Version 12.61 +
    +
  • Added ImageDataMD5 support for J2C and JXL images +
  • Added support for PDF 2.0 (specification is finally freely available) +
  • Added ability to extract timed Accelerometer data from Azdome GS63H MP4 + videos which don't contain GPS +
  • Added some new Sony lenses (thanks Jos Roost) +
  • Decode some new tags for the Sony ZV-E1 (thanks Jos Roost) +
  • Decode more tags for the Nikon Z30 (thanks Xavier) +
  • Enhanced -fileNUM option to allow tags from the main file to be used in the + file name string +
  • Validate sample offset and size when calculating ImageDataMD5 for MP4 videos + (note: may change ImageDataMD5 value for videos where audio data runs past + end of media data) +
  • Return error when attempting to write a fragmented JXL file +
  • Improved robustness for determining image size for corrupted JPEG +
  • Patched to allow Insta360 GPS records of unexpected length and tweaked + verification algorithm to determine validity of these records +
  • Fixed bug introduced in 12.57 where -progress:%f gave runtime warnings +
  • Fixed "--" option to ignore subsequent -common_args option +
  • Fixed incorrect ImageDataMD5 for Sony A100 ARW images +
  • Fixed problem reading new XMP-et:OriginalImageMD5 tag +
+ +Apr. 5, 2023 - Version 12.60 (production release) +
    +
  • Added a new Sony FileFormat value +
  • Added Validate warning about duplicate EXIF +
  • Added ability to edit JPEG APP1 EXIF segment with incorrect header +
  • Decode a few new Sony ARW tags +
  • Improved -htmldump of non-EXIF-based maker notes +
  • Enhanced -geotag from CSV files support GPSSpeed (with variable units), + "bearing" for GPSTrack, and GPSDateTime in format "dd.mm.YYYY HH:MM:SS" +
  • Enhanced ImageDataMD5 to also support CRW, RAF, X3F and AVIF images +
  • Enhanced -efile option to also record updated and created file names +
  • Family 8 group names may now also be used in Composite Require/Desire tags +
  • Fixed handling of undefined tags in -if conditions to conform with + documentation and match -p and -tagsFromFile behaviour when -m or -f option + is used +
  • Fixed problem where setting the Geotime value didn't work when using an + advanced-formatting expression containing a greater-than symbol (>) +
+ +Mar. 28, 2023 - Version 12.59 +
    +
  • COMPATIBILITY WARNING: Changed the calculated ImageDataMD5 for JPEG images + to include all data from the SOS to the EOI (including the SOS marker but + not the EOI marker) +
  • Added new -fileNUM option to load tags from alternate files +
  • Added family 8 groups for accessing tags from alternate files +
  • Added new XMP-et:OriginalImageMD5 tag for storing ImageDataMD5 value +
  • Added verbose ImageDataMD5 message for JPEG files +
  • Added a new Nikon LensID (thanks Warren Hatch) +
  • Decode a new Olympus tag and improved decoding of another (thanks Herb) +
  • Decode a couple of new PanasonicRaw tags +
  • Decode image coordinates for a couple more VNT object types +
  • Enhanced ImageDataMD5 to also support MRW, CR3, IIQ, PNG, MOV/MP4 and some + RIFF-based files +
  • Improved verbose messages when deleting NikonApp trailer +
  • Patched to avoid structure warnings when copying tags from Nikon files + containing NKSC metadata +
  • Fixed %-C filename format code to work properly with the -fileOrder and + -progress options +
  • Fixed potential ValueConv warning when reading LIF files +
  • API Changes: + +
+ +Mar. 15, 2023 - Version 12.58 +
    +
  • Added Extra ImageDataMD5 tag to calculate MD5 of image data only +
  • Added support for reading DJI APP4 and APP7 JPEG segments +
  • Added a new SonyModelID value +
  • Decode a few new Nikon tags (thanks Warren Hatch) +
  • Downgraded "Windows file times" to a minor warning when Win32::API or + Win32API::File is not installed while reading metadata +
  • Patched possible runtime warning when API IgnoreTags option is used to + ignore FileType +
  • Fixed problem extracting NetName from Windows LNK files +
  • Fixed issue where the %C filename format code would increment the count on + an output filename collision, but it is supposed to count the input files +
+ +Feb. 23, 2023 - Version 12.57 +
    +
  • Added two new Nikon Z lenses (thanks LibRaw) +
  • Added a new Sigma LensType (thanks LibRaw) +
  • Added a new Olympus LensType (thanks Herb) +
  • Decode more new Nikon tags (thanks Warren Hatch) +
  • Decode Photoshop LayerColors, LayerSections and LayerVisible tags +
  • Improved Verbose output for QuickTime-format files +
  • Set family 1 group name for Garmin GPS from uuid atom +
  • Enhanced -progress option to allow message to be displayed every NUM files +
  • Significant improvements to parsing of Nikon ShotInfo records for newer + models +
  • Removed hex dump of APP segments from -v3 output when writing +
  • Fixed bug writing negative MIE GPS coordinates +
  • Fixed bug where a duplicate XMP could be generated when writing XMP to a + JPEG XL image which already contained XMP +
  • Fixed problem where HEAD lines may be duplicated in an output file if the -p + option was combined with -w+ or -W+ +
+ +Feb. 9, 2023 - Version 12.56 +
    +
  • Added support for VNT files (both Scene7 Vignette and V-Note document) +
  • Added read support for InfiRay IJPEG metadata (thanks Marcos Del Sol Vives) +
  • Added some new Sony LensType values (thanks Jos Roost and François Piette) +
  • Added a new FujiFilm VideoRecordingMode value (thanks Greybeard) +
  • Added two new Canon LensTypes and CanonModelIDs (thanks Norbert Wasser) +
  • Added ability to extract semantic images from Apple ProRaw DNG files +
  • Added read support for the PNG cICP chunk +
  • Decode more Nikon tags (thanks Warren Hatch) +
  • Extract PreviewImage from Insta360 trailer record 0x200 +
  • Extract EmbeddedImageRectangle and some other new tags from VNT files +
  • Minor improvement to arg_files/xmp2exif.args (thanks StarGeek) +
  • Enhanced -ee option to extract metadata from all frames of a multipart EXR + image +
  • Removed EXR Layout tag and incorporated into new Flags tag +
  • Patched possible hang problem when reading corrupted .rm audio files +
+ +Jan. 17, 2023 - Version 12.55 +
    +
  • Added support for geotagging from FlightAware KML files +
  • Decode two more types of timed GPS from MOV/MP4 videos (66 types now) +
  • Decode a few new Nikon tags (thanks Warren Hatch) +
  • Decode a new Samsung HEIC tag +
  • Decode FujiFilm RollAngle +
  • Fixed bug where the FlatName property wasn't working properly for some + user-defined structure tags +
+ +Jan. 6, 2023 - Version 12.54 +
    +
  • Decode a number of new Apple tags (thanks Frank Rupprecht) +
  • Increased precision of Sony FocusDistance2 conversion +
  • Fixed problem where GPSAltitude wasn't being set when geotagging from KML + files +
  • Fixed bug writing HEIC/AVIF files which have a zero-sized mdat (ie. media + data extends to end of file) which could cause an incorrect mdat size to be + written +
+ +Jan. 4, 2023 - Version 12.53 +
    +
  • Added support for a number of new XMP tags written by ACR 15.1 +
  • Added a new Nikon LensID +
  • Decode timed GPS from Lamax S9 dual dashcam MOV videos +
  • Decode a number of new Nikon tags (thanks Warren Hatch) +
  • Decode a couple of new Canon tags (thanks John Moyer) +
  • Decode FujiFilm BWMagentaGreen tag +
  • Enable block-write of EXIF to JXL files +
  • Accept values of "now" and "Z" when writing EXIF OffsetTime tags +
  • Changed priority of XMP when reading/writing HEIC files so that it is no + longer preferred as with other QuickTime-based formats +
  • Changed family 1 group name of Canon DR4 tags from CanonVRD to CanonDR4 to + allow newer tags to be differentiated from older ones. The family 0 group + name for both remains CanonVRD +
  • Patched to recognize JXL EXIF box with non-zero header length +
  • Patched to avoid runtime error when writing a PDF with an Info dictionary + which was stored incorrectly as a direct object +
  • Fixed problem writing EXIF to JXL images where a new EXIF box was created + even if one previously existed +
+ +Dec. 6, 2022 - Version 12.52 +
    +
  • Added a few new Nikon LensID's (thanks LibRaw and Chris) +
  • Added Slovak translations (thanks Peter Bagin) +
  • Made SphericalVideoXML readable/writable as a block +
  • Improved handling of Matroska metadata tags, including language support +
  • Improved French translations (thanks Philippe Bonnaure of GraphicConverter) +
  • Improved Composite:GPSAltitude conversion to honour -lang setting +
  • Improved -v2 messages to indicate files extracted from zip archives +
+ +Nov. 21, 2022 - Version 12.51 +
    +
  • Added a new Olympus LensType (thanks Herb) +
  • Extract C2PA JUMBF metadata from PNG images and extract C2PA Salt values +
  • Decode NikonSettings for Z9 firmware 3.0 (thanks Warren Hatch) +
  • Decode additional camm metadata from Insta360 Pro2 MP4 videos +
  • Improved Verbose output when writing Composite tags to add a "+" sign to + indicate related tags that are being written +
  • Enhanced -geotag option CSV format to support GPSImgDirection column +
  • Fixed problem where -w+ option didn't work in Windows if there were Unicode + characters in the path name +
  • Fixed problem where only the last image of the sequence was extracted + (multiple times) when using -ee2 to extract embedded images from FLIR SEQ + files +
  • Fixed issue where GPS reference directions may be unknowingly written when + using ExifTool 12.44 or later to write GPSLatitude or GPSLongitude without + specifying a group name. The fix was to Avoid writing the Composite tags + unless the Composite group is specified explicitly +
  • Fixed -geotag to write orientation and track tags even if some tags in the + category were missing +
  • Fixed inconsistency in selecting which tag to output with the -json option + when multiple tags with the same JSON key exist and the -TAG# feature is + used to disable print conversion +
  • Fixed problem writing QuickTime:PlayListID +
  • Fixed problem writing QuickTime tags when specifying tag ID (ie. family 7 + group) as well as a language code +
+ +Nov. 8, 2022 - Version 12.50 (production release) +
    +
  • Added a new XMP-GCreations tag +
  • Added a few new Sony lenses (thanks Jos Roost) +
  • Added new SonyModelID and Olympus CameraType values (thanks LibRaw and Herb) +
  • Added a couple of new XMP tags (thanks José Oliver-Didier) +
  • Added a new Nikon Z lens (thanks LibRaw) +
  • Added a new Canon LensType and CanonModelID (thanks Norbert Wasser and + LibRaw) +
  • Added some new Pentax lenses (thanks LibRaw) +
  • Added experimental support for timed GPS in TS videos from Jomise T860S-GM + dashcam (more samples are needed for this to be finalized) +
  • Decode information written in "skip" atom of 70mai Pro Plus+ MP4 videos +
  • Decode timed accelerometer data from Kenwood dashcam MP4 videos +
  • Decode a few new Nikon Z9 tags (thanks Stefan Grüßen) +
  • Decode ColorData for some newer Canon models (thanks LibRaw) +
  • Decode a number of new tags for the Sony ILCE-7RM5 (thanks Jos Roost) +
  • Updated IPTC XMP tags to correspond with new Photo Metadata 2022.1 standard +
  • Extract JPEG previews from FujiFilm HIF images +
  • Changed -if option so multiple -if options are evaluated at the lowest + specified -fast level +
  • Changed MIMEType for ICO and CUR files +
  • Enhanced -fast2 so it stops processing QuickTime files at mdat atom +
  • Enhanced -listx output so -f also indicates the ID of the parent structure + for Flattened tags +
  • Improved conversion of IPTC date-only and time-only tags to allow formatting + with the -d option +
  • Improved Canon and Nikon TimeZone tags to accept a wider variety of input + formats when writing +
  • Disabled extraction of Nikon Z9 MenuSettings for firmware 3.0 until they can + be properly decoded (thanks Warren Hatch) +
  • Fixed decoding of AF points for some newer Nikon models +
  • Fixed inconsistent year and time zone for Kenwood dashcam timed GPS in MP4 + videos +
+ +History of older versions (back to Nov. 19, 2003 - Version 1.00) -->

+ +
+

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/htmldump.html b/ExifTool/html/htmldump.html new file mode 100644 index 0000000..db84f5e --- /dev/null +++ b/ExifTool/html/htmldump.html @@ -0,0 +1,769 @@ + + + + +HTML Dump (t/images/FujiFilm.jpg) + + + +

ExifTool HtmlDump Option

+

Below is an example of the ExifTool HtmlDump output (the exiftool +"-htmlDump" option). Move your cursor over the data to highlight +individual blocks and reveal tool tips for extra information. (You must have +JavaScript enabled.)

+ +

The command used to generate the output below was:

+ +
exiftool -htmldump t/images/FujiFilm.jpg > out.html
+ +

In this output, normal +data blocks are light or dark green, +unused blocks are brown, and double-referenced blocks are +red or pink. A +blue tag name indicates the value is referenced by an +offset in the IFD (ie. the value is longer than 4 bytes), +purple indicates that the actual offset differs from the +recorded offset, and a red name indicates an invalid +offset. Odd offsets are flagged with "(odd)" and +out-of-sequence tag ID's are indicated by "(seq)" because +the TIFF specification states that values must begin on a (2-byte) word boundary +and that IFD entries must be in order of increasing tag ID. All data in the +maker notes value block is underlined.

+ +

Clicking the mouse locks current the highlighted entry, and clicking again +unlocks it.

+ +

Currently, only TIFF/EXIF and JPEG meta information is dumped. The leftmost +column of the output gives the offset in hex relative to the start of the TIFF +header (by default). For a TIFF image this is at the start of the file, but for +a JPEG image it is 10 bytes after the start of the APP1 EXIF segment. However, +this base offset can be specified on the command line with "-htmlDump#" (eg. +"-htmlDump0" for absolute offsets), or via the API with the "HtmlDumpBase" +option.

+ + + +
-000c
+ 0000
+ 000a
+ 0016
+ 0022
+ 002e
+ 003a
+ 0046
+ 0052
+ 005e
+ 006a
+ 0076
+ 0082
+ 008e
+ 0090
+ 00a0
+ 00b0
+ 00c0
+ 00d0
+ 00e0
+ 00f0
+ 0100
+ 0106
+ 0112
+ 011e
+ 012a
+ 0136
+ 0142
+ 014e
+ 015a
+ 0166
+ 0172
+ 017e
+ 018a
+ 0196
+ 01a2
+ 01ae
+ 01ba
+ 01c6
+ 01d2
+ 01de
+ 01ea
+ 01f6
+ 0202
+ 020e
+ 021a
+ 0226
+ 0232
+ 023e
+ 024a
+ 0256
+ 0260
+ 0270
+ 0280
+ 0290
+ 02a0
+ 02b0
+ 02c0
+ 02d0
+ 02dc
+ 02e8
+ 02f4
+ 0300
+ 030c
+ 0318
+ 0324
+ 0330
+ 033c
+ 0348
+ 0354
+ 0360
+ 036c
+ 0378
+ 0384
+ 0390
+ 03a0
+ 03aa
+ 03b6
+ 03c2
+ 03c8
+ 03d4
+ 03e0
+ 03ec
+ 03f8
+ 0404
+ 0410
+ 041c
+ 0428
+ 0430
+ 0440
+ 0450
+ 0460
+ 0470
+ 0480
+ 0490
+ 04a0
+ 04b0
+ 04c0
+ 04d0
+ 04e0
+ 04f0
+ 0500
+ 0510
+ 0520
+ 0530
+ 0540
+ 0550
+
            ff d8 ff e1  04 60 45 78 69 66 00 00
+49 49 2a 00 08 00 00 00  0b 00
+0f 01 02 00 09 00 00 00  92 00 00 00
+10 01 02 00 10 00 00 00  9c 00 00 00
+12 01 03 00 01 00 00 00  01 00 00 00
+1a 01 05 00 01 00 00 00  ac 00 00 00
+1b 01 05 00 01 00 00 00  b4 00 00 00
+28 01 03 00 01 00 00 00  02 00 00 00
+31 01 02 00 27 00 00 00  bc 00 00 00
+32 01 02 00 14 00 00 00  e4 00 00 00
+13 02 03 00 01 00 00 00  02 00 00 00
+98 82 02 00 0b 00 00 00  f8 00 00 00
+69 87 04 00 01 00 00 00  04 01 00 00
+                                           c6 03
+00 00 46 55 4a 49 46 49  4c 4d 00 00 46 69 6e 65
+50 69 78 32 34 30 30 5a  6f 6f 6d 00 48 00 00 00
+01 00 00 00 48 00 00 00  01 00 00 00 44 69 67 69
+74 61 6c 20 43 61 6d 65  72 61 20 46 69 6e 65 50
+69 78 32 34 30 30 5a 6f  6f 6d 20 56 65 72 31 2e
+37 30 00 00 32 30 30 31  3a 30 35 3a 31 39 20 31
+38 3a 33 36 3a 34 31 00  20 20 20 20 20 20 20 20
+20 20 00 00 1c 00
+9d 82 05 00 01 00 00 00  5a 02 00 00
+22 88 03 00 01 00 00 00  02 00 00 00
+27 88 03 00 01 00 00 00  64 00 00 00
+00 90 07 00 04 00 00 00  30 32 31 30
+03 90 02 00 14 00 00 00  62 02 00 00
+04 90 02 00 14 00 00 00  76 02 00 00
+01 91 07 00 04 00 00 00  01 02 03 00
+02 91 05 00 01 00 00 00  8a 02 00 00
+01 92 0a 00 01 00 00 00  92 02 00 00
+02 92 05 00 01 00 00 00  9a 02 00 00
+03 92 0a 00 01 00 00 00  a2 02 00 00
+04 92 0a 00 01 00 00 00  aa 02 00 00
+05 92 05 00 01 00 00 00  b2 02 00 00
+07 92 03 00 01 00 00 00  05 00 00 00
+09 92 03 00 01 00 00 00  01 00 00 00
+0a 92 05 00 01 00 00 00  ba 02 00 00
+7c 92 07 00 d6 00 00 00  c2 02 00 00
+00 a0 07 00 04 00 00 00  30 31 30 30
+01 a0 03 00 01 00 00 00  01 00 00 00
+02 a0 04 00 01 00 00 00  40 06 00 00
+03 a0 04 00 01 00 00 00  b0 04 00 00
+05 a0 04 00 01 00 00 00  a8 03 00 00
+0e a2 05 00 01 00 00 00  98 03 00 00
+0f a2 05 00 01 00 00 00  a0 03 00 00
+10 a2 03 00 01 00 00 00  03 00 00 00
+17 a2 03 00 01 00 00 00  02 00 00 00
+00 a3 07 00 01 00 00 00  03 00 00 00
+01 a3 07 00 01 00 00 00  01 00 00 00
+                  00 00  00 00 5e 01 00 00 64 00
+00 00 32 30 30 31 3a 30  35 3a 31 39 20 31 38 3a
+33 36 3a 34 31 00 32 30  30 31 3a 30 35 3a 31 39
+20 31 38 3a 33 36 3a 34  31 00 10 00 00 00 0a 00
+00 00 58 02 00 00 64 00  00 00 68 01 00 00 64 00
+00 00 c8 00 00 00 64 00  00 00 00 00 00 00 64 00
+00 00 68 01 00 00 64 00  00 00 58 02 00 00 64 00
+00 00 46 55 4a 49 46 49  4c 4d 0c 00 00 00 0f 00
+00 00 07 00 04 00 00 00  30 31 33 30
+00 10 02 00 08 00 00 00  c6 00 00 00
+01 10 03 00 01 00 00 00  03 00 00 00
+02 10 03 00 01 00 00 00  00 00 00 00
+10 10 03 00 01 00 00 00  03 00 00 00
+11 10 0a 00 01 00 00 00  ce 00 00 00
+20 10 03 00 01 00 00 00  00 00 00 00
+21 10 03 00 01 00 00 00  00 00 00 00
+30 10 03 00 01 00 00 00  00 00 00 00
+31 10 03 00 01 00 00 00  00 00 00 00
+00 11 03 00 01 00 00 00  00 00 00 00
+00 12 03 00 01 00 00 00  00 00 00 00
+00 13 03 00 01 00 00 00  00 00 00 00
+01 13 03 00 01 00 00 00  00 00 00 00
+02 13 03 00 01 00 00 00  00 00 00 00
+            00 00 00 00  4e 4f 52 4d 41 4c 20 00
+00 00 00 00 0a 00 00 00  ed 0b 00 00 01 00 00 00
+ed 0b 00 00 01 00 00 00  02 00
+01 00 02 00 04 00 00 00  52 39 38 00
+02 00 07 00 04 00 00 00  30 31 30 30
+      00 00 00 00 08 00
+03 01 03 00 01 00 00 00  06 00 00 00
+12 01 03 00 01 00 00 00  01 00 00 00
+1a 01 05 00 01 00 00 00  2c 04 00 00
+1b 01 05 00 01 00 00 00  34 04 00 00
+28 01 03 00 01 00 00 00  02 00 00 00
+01 02 04 00 01 00 00 00  3c 04 00 00
+02 02 04 00 01 00 00 00  1c 00 00 00
+13 02 03 00 01 00 00 00  02 00 00 00
+                         00 00 00 00 48 00 00 00
+01 00 00 00 48 00 00 00  01 00 00 00 3c 44 75 6d
+6d 79 20 74 68 75 6d 62  6e 61 69 6c 20 69 6d 61
+67 65 20 64 61 74 61 3e  ff db 00 84 00 14 10 10
+19 12 19 27 17 17 27 32  26 1f 26 32 2e 26 26 26
+26 2e 3e 35 35 35 35 35  3e 44 41 41 41 41 41 41
+44 44 44 44 44 44 44 44  44 44 44 44 44 44 44 44
+44 44 44 44 44 44 44 44  44 44 44 44 44 01 15 19
+19 20 1c 20 26 18 18 26  36 26 20 26 36 44 36 2b
+2b 36 44 44 44 42 35 42  44 44 44 44 44 44 44 44
+44 44 44 44 44 44 44 44  44 44 44 44 44 44 44 44
+44 44 44 44 44 44 44 44  44 44 44 44 44 44 ff c0
+00 11 08 00 08 00 08 03  01 22 00 02 11 01 03 11
+01 ff c4 00 4b 00 01 01  00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 06  01 01 00 00 00 00 00 00
+00 00 00 00 00 00 00 00  00 00 10 01 00 00 00 00
+00 00 00 00 00 00 00 00  00 00 00 00 11 01 00 00
+00 00 00 00 00 00 00 00  00 00 00 00 00 00 ff da
+00 0c 03 01 00 02 11 03  11 00 3f 00 b3 00 1f ff
+d9
+
    .....`Exif..
+II*.......
+............
+............
+............
+............
+............
+(...........
+1...'.......
+2...........
+............
+............
+i...........
+              ..
+..FUJIFILM..Fine
+Pix2400Zoom.H...
+....H.......Digi
+tal Camera FineP
+ix2400Zoom Ver1.
+70..2001:05:19 1
+8:36:41.        
+  ....
+........Z...
+"...........
+'.......d...
+........0210
+........b...
+........v...
+............
+............
+............
+............
+............
+............
+............
+............
+............
+............
+|...........
+........0100
+............
+........@...
+............
+............
+............
+............
+............
+............
+............
+............
+      ....^...d.
+..2001:05:19 18:
+36:41.2001:05:19
+ 18:36:41.......
+..X...d...h...d.
+......d.......d.
+..h...d...X...d.
+..FUJIFILM......
+........0130
+............
+............
+............
+............
+............
+ ...........
+!...........
+0...........
+1...........
+............
+............
+............
+............
+............
+    ....NORMAL .
+................
+..........
+........R98.
+........0100
+  ......
+............
+............
+........,...
+........4...
+(...........
+........<...
+............
+............
+        ....H...
+....H.......<Dum
+my thumbnail ima
+ge data>........
+...'..'2&.&2.&&&
+&.>55555>DAAAAAA
+DDDDDDDDDDDDDDDD
+DDDDDDDDDDDDD...
+. . &..&6& &6D6+
++6DDDB5BDDDDDDDD
+DDDDDDDDDDDDDDDD
+DDDDDDDDDDDDDD..
+........."......
+....K...........
+................
+................
+................
+................
+..........?.....
+.
+
JPEG header APP1 header Exif header
+TIFF header IFD0 entries
+IFD0-00 Make
+IFD0-01 Model
+IFD0-02 Orientation
+IFD0-03 XResolution
+IFD0-04 YResolution
+IFD0-05 ResolutionUnit
+IFD0-06 Software
+IFD0-07 ModifyDate
+IFD0-08 YCbCrPositioning
+IFD0-09 Copyright
+IFD0-10 ExifOffset
+Next IFD
+Make value [pad byte]
+Model value
+XResolution value YResolution value
+Software value
+
+[pad byte]
+ModifyDate value
+Copyright value [pad byte] ExifIFD entries
+ExifIFD-00 FNumber
+ExifIFD-01 ExposureProgram
+ExifIFD-02 ISO
+ExifIFD-03 ExifVersion
+ExifIFD-04 DateTimeOriginal
+ExifIFD-05 CreateDate
+ExifIFD-06 ComponentsConfiguration
+ExifIFD-07 CompressedBitsPerPixel
+ExifIFD-08 ShutterSpeedValue
+ExifIFD-09 ApertureValue
+ExifIFD-10 BrightnessValue
+ExifIFD-11 ExposureCompensation
+ExifIFD-12 MaxApertureValue
+ExifIFD-13 MeteringMode
+ExifIFD-14 Flash
+ExifIFD-15 FocalLength
+ExifIFD-16 MakerNotes
+ExifIFD-17 FlashpixVersion
+ExifIFD-18 ColorSpace
+ExifIFD-19 ExifImageWidth
+ExifIFD-20 ExifImageHeight
+ExifIFD-21 InteropOffset
+ExifIFD-22 FocalPlaneXResolution
+ExifIFD-23 FocalPlaneYResolution
+ExifIFD-24 FocalPlaneResolutionUnit
+ExifIFD-25 SensingMethod
+ExifIFD-26 FileSource
+ExifIFD-27 SceneType
+Next IFD
+FNumber value
+DateTimeOriginal value
+CreateDate value
+CompressedBitsPerPixel value ShutterSpeedValue value
+ApertureValue value BrightnessValue value
+ExposureCompensation value MaxApertureValue value
+FocalLength value MakerNotes header MakerNoteFujiFilm entries
+MakerNotes-00 Version
+MakerNotes-01 Quality
+MakerNotes-02 Sharpness
+MakerNotes-03 WhiteBalance
+MakerNotes-04 FujiFlashMode
+MakerNotes-05 FlashExposureComp
+MakerNotes-06 Macro
+MakerNotes-07 FocusMode
+MakerNotes-08 SlowSync
+MakerNotes-09 PictureMode
+MakerNotes-10 AutoBracketing
+MakerNotes-11 Tag 0x1200
+MakerNotes-12 BlurWarning
+MakerNotes-13 FocusWarning
+MakerNotes-14 ExposureWarning
+Next IFD Quality value
+FlashExposureComp value FocalPlaneXResolution value
+FocalPlaneYResolution value InteropIFD entries
+InteropIFD-00 InteropIndex
+InteropIFD-01 InteropVersion
+Next IFD IFD1 entries
+IFD1-00 Compression
+IFD1-01 Orientation
+IFD1-02 XResolution
+IFD1-03 YResolution
+IFD1-04 ResolutionUnit
+IFD1-05 ThumbnailOffset
+IFD1-06 ThumbnailLength
+IFD1-07 YCbCrPositioning
+Next IFD
+XResolution value YResolution value
+(IFD1:Thumbnail data)
+[JPEG DQT]
+
+
+
+
+
+
+
+[JPEG SOF0]
+
+[JPEG DHT]
+
+
+
+[JPEG Image Data]
+JPEG EOI
+
+
+
+
JPEG header
SOI Marker
(2 bytes)
+
APP1 header
Data size: 1118 bytes
(4 bytes)
+
Exif header
APP1 data type: Exif
(6 bytes)
+
TIFF header
Byte order: Little endian
Identifier: 0x002a
IFD0 offset: 0x0008
(8 bytes)
+
IFD0 entries
Entry count: 11
(2 bytes)
+
IFD0-00 Make
Tag ID: 0x010f
Format: string[9]
Size: 9 bytes
Value offset: 0x0092
Value: FUJIFILM
+
IFD0-01 Model
Tag ID: 0x0110
Format: string[16]
Size: 16 bytes
Value offset: 0x009c
Value: FinePix2400Zoom
+
IFD0-02 Orientation
Tag ID: 0x0112
Format: int16u[1]
Size: 2 bytes
Value: 1
+
IFD0-03 XResolution
Tag ID: 0x011a
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x00ac
Value: 72 (72/1)
+
IFD0-04 YResolution
Tag ID: 0x011b
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x00b4
Value: 72 (72/1)
+
IFD0-05 ResolutionUnit
Tag ID: 0x0128
Format: int16u[1]
Size: 2 bytes
Value: 2
+
IFD0-06 Software
Tag ID: 0x0131
Format: string[39]
Size: 39 bytes
Value offset: 0x00bc
Value: Digital Camera FinePix2400Zo[...]
+
IFD0-07 ModifyDate
Tag ID: 0x0132
Format: string[20]
Size: 20 bytes
Value offset: 0x00e4
Value: 2001:05:19 18:36:41
+
IFD0-08 YCbCrPositioning
Tag ID: 0x0213
Format: int16u[1]
Size: 2 bytes
Value: 2
+
IFD0-09 Copyright
Tag ID: 0x8298
Format: string[11] read as undef[11]
Size: 11 bytes
Value offset: 0x00f8
Value: .
+
IFD0-10 ExifOffset
Tag ID: 0x8769
Format: int32u[1]
Size: 4 bytes
Value: 0x0104
+
Next IFD
IFD1 offset: 0x03c6
(4 bytes)
+
ExifIFD entries
Entry count: 28
(2 bytes)
+
ExifIFD-00 FNumber
Tag ID: 0x829d
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x025a
Value: 3.5 (350/100)
+
ExifIFD-01 ExposureProgram
Tag ID: 0x8822
Format: int16u[1]
Size: 2 bytes
Value: 2
+
ExifIFD-02 ISO
Tag ID: 0x8827
Format: int16u[1]
Size: 2 bytes
Value: 100
+
ExifIFD-03 ExifVersion
Tag ID: 0x9000
Format: undef[4]
Size: 4 bytes
Value: 0210
+
ExifIFD-04 DateTimeOriginal
Tag ID: 0x9003
Format: string[20]
Size: 20 bytes
Value offset: 0x0262
Value: 2001:05:19 18:36:41
+
ExifIFD-05 CreateDate
Tag ID: 0x9004
Format: string[20]
Size: 20 bytes
Value offset: 0x0276
Value: 2001:05:19 18:36:41
+
ExifIFD-06 ComponentsConfiguration
Tag ID: 0x9101
Format: undef[4] read as int8u[4]
Size: 4 bytes
Value: 1 2 3 0
+
ExifIFD-07 CompressedBitsPerPixel
Tag ID: 0x9102
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x028a
Value: 1.6 (16/10)
+
ExifIFD-08 ShutterSpeedValue
Tag ID: 0x9201
Format: rational64s[1]
Size: 8 bytes
Value offset: 0x0292
Value: 6 (600/100)
+
ExifIFD-09 ApertureValue
Tag ID: 0x9202
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x029a
Value: 3.6 (360/100)
+
ExifIFD-10 BrightnessValue
Tag ID: 0x9203
Format: rational64s[1]
Size: 8 bytes
Value offset: 0x02a2
Value: 2 (200/100)
+
ExifIFD-11 ExposureCompensation
Tag ID: 0x9204
Format: rational64s[1]
Size: 8 bytes
Value offset: 0x02aa
Value: 0 (0/100)
+
ExifIFD-12 MaxApertureValue
Tag ID: 0x9205
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x02b2
Value: 3.6 (360/100)
+
ExifIFD-13 MeteringMode
Tag ID: 0x9207
Format: int16u[1]
Size: 2 bytes
Value: 5
+
ExifIFD-14 Flash
Tag ID: 0x9209
Format: int16u[1]
Size: 2 bytes
Value: 0x1
+
ExifIFD-15 FocalLength
Tag ID: 0x920a
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x02ba
Value: 6 (600/100)
+
ExifIFD-16 MakerNotes
Tag ID: 0x927c
Format: undef[214]
Size: 214 bytes
Value offset: 0x02c2
Value: FUJIFILM..............0130..[...]
+
ExifIFD-17 FlashpixVersion
Tag ID: 0xa000
Format: undef[4]
Size: 4 bytes
Value: 0100
+
ExifIFD-18 ColorSpace
Tag ID: 0xa001
Format: int16u[1]
Size: 2 bytes
Value: 0x1
+
ExifIFD-19 ExifImageWidth
Tag ID: 0xa002
Format: int32u[1]
Size: 4 bytes
Value: 1600
+
ExifIFD-20 ExifImageHeight
Tag ID: 0xa003
Format: int32u[1]
Size: 4 bytes
Value: 1200
+
ExifIFD-21 InteropOffset
Tag ID: 0xa005
Format: int32u[1]
Size: 4 bytes
Value: 0x03a8
+
ExifIFD-22 FocalPlaneXResolution
Tag ID: 0xa20e
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x0398
Value: 3053 (3053/1)
+
ExifIFD-23 FocalPlaneYResolution
Tag ID: 0xa20f
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x03a0
Value: 3053 (3053/1)
+
ExifIFD-24 FocalPlaneResolutionUnit
Tag ID: 0xa210
Format: int16u[1]
Size: 2 bytes
Value: 3
+
ExifIFD-25 SensingMethod
Tag ID: 0xa217
Format: int16u[1]
Size: 2 bytes
Value: 2
+
ExifIFD-26 FileSource
Tag ID: 0xa300
Format: undef[1]
Size: 1 bytes
Value: 3
+
ExifIFD-27 SceneType
Tag ID: 0xa301
Format: undef[1]
Size: 1 bytes
Value: 1
+
Next IFD
Next IFD offset: 0x0000
(4 bytes)
+
MakerNotes header
MakerNoteFujiFilm
(12 bytes)
+
MakerNoteFujiFilm entries
Entry count: 15
(2 bytes)
+
MakerNotes-00 Version
Tag ID: 0x0000
Format: undef[4]
Size: 4 bytes
Value: 0130
+
MakerNotes-01 Quality
Tag ID: 0x1000
Format: string[8]
Size: 8 bytes
Value offset: 0x00c6
Actual offset: 0x0388
Offset base: 0x02c2
Value: NORMAL
+
MakerNotes-02 Sharpness
Tag ID: 0x1001
Format: int16u[1]
Size: 2 bytes
Value: 0x3
+
MakerNotes-03 WhiteBalance
Tag ID: 0x1002
Format: int16u[1]
Size: 2 bytes
Value: 0x0
+
MakerNotes-04 FujiFlashMode
Tag ID: 0x1010
Format: int16u[1]
Size: 2 bytes
Value: 0x3
+
MakerNotes-05 FlashExposureComp
Tag ID: 0x1011
Format: rational64s[1]
Size: 8 bytes
Value offset: 0x00ce
Actual offset: 0x0390
Offset base: 0x02c2
Value: 0 (0/10)
+
MakerNotes-06 Macro
Tag ID: 0x1020
Format: int16u[1]
Size: 2 bytes
Value: 0
+
MakerNotes-07 FocusMode
Tag ID: 0x1021
Format: int16u[1]
Size: 2 bytes
Value: 0
+
MakerNotes-08 SlowSync
Tag ID: 0x1030
Format: int16u[1]
Size: 2 bytes
Value: 0
+
MakerNotes-09 PictureMode
Tag ID: 0x1031
Format: int16u[1]
Size: 2 bytes
Value: 0x0
+
MakerNotes-10 AutoBracketing
Tag ID: 0x1100
Format: int16u[1]
Size: 2 bytes
Value: 0
+
MakerNotes-11 Tag 0x1200
Tag ID: 0x1200
Format: int16u[1]
Size: 2 bytes
Value: 0
+
MakerNotes-12 BlurWarning
Tag ID: 0x1300
Format: int16u[1]
Size: 2 bytes
Value: 0
+
MakerNotes-13 FocusWarning
Tag ID: 0x1301
Format: int16u[1]
Size: 2 bytes
Value: 0
+
MakerNotes-14 ExposureWarning
Tag ID: 0x1302
Format: int16u[1]
Size: 2 bytes
Value: 0
+
Next IFD
Next IFD offset: 0x0000
(4 bytes)
+
InteropIFD entries
Entry count: 2
(2 bytes)
+
InteropIFD-00 InteropIndex
Tag ID: 0x0001
Format: string[4]
Size: 4 bytes
Value: R98
+
InteropIFD-01 InteropVersion
Tag ID: 0x0002
Format: undef[4]
Size: 4 bytes
Value: 0100
+
Next IFD
Next IFD offset: 0x0000
(4 bytes)
+
IFD1 entries
Entry count: 8
(2 bytes)
+
IFD1-00 Compression
Tag ID: 0x0103
Format: int16u[1]
Size: 2 bytes
Value: 6
+
IFD1-01 Orientation
Tag ID: 0x0112
Format: int16u[1]
Size: 2 bytes
Value: 1
+
IFD1-02 XResolution
Tag ID: 0x011a
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x042c
Value: 72 (72/1)
+
IFD1-03 YResolution
Tag ID: 0x011b
Format: rational64u[1]
Size: 8 bytes
Value offset: 0x0434
Value: 72 (72/1)
+
IFD1-04 ResolutionUnit
Tag ID: 0x0128
Format: int16u[1]
Size: 2 bytes
Value: 2
+
IFD1-05 ThumbnailOffset
Tag ID: 0x0201
Format: int32u[1]
Size: 4 bytes
Value: 0x043c
+
IFD1-06 ThumbnailLength
Tag ID: 0x0202
Format: int32u[1]
Size: 4 bytes
Value: 28
+
IFD1-07 YCbCrPositioning
Tag ID: 0x0213
Format: int16u[1]
Size: 2 bytes
Value: 2
+
Next IFD
IFD2 offset: 0x0000
(4 bytes)
+
(IFD1:Thumbnail data)
Size: 28 bytes
+
[JPEG DQT]
(134 bytes)
+
[JPEG SOF0]
(19 bytes)
+
[JPEG DHT]
(77 bytes)
+
[JPEG Image Data]
(17 bytes)
+
JPEG EOI
(2 bytes)
+ diff --git a/ExifTool/html/idiosyncracies.html b/ExifTool/html/idiosyncracies.html new file mode 100644 index 0000000..114054c --- /dev/null +++ b/ExifTool/html/idiosyncracies.html @@ -0,0 +1,293 @@ + + + +Maker Note Idio(t)syncrasies + + + +

Maker Note Idio(t)syncrasies

+ +

It really is surprising how stupid some (...many, ...most?) manufacturers +seem to be when it comes to writing what should be a fairly simple file +format.

+ +

One positive thing is that most manufacturers seem to have standardized on an +EXIF-like IFD (Image File Directory) structure for their maker notes. But many +problems arise because of a fundamental design flaw in the EXIF/TIFF format. +Values longer than 4 bytes are stored at a location referenced by an offset from +an absolute position in the file (where offset 0 is the start of the EXIF/TIFF +information).

+ +

The difficulty is that these offsets must be recalculated when a file is +rewritten, but in general this is not possible (particularly for the maker +notes) because the format of all information is not known. Some manufacturers +have attempted to avoid this problem using offsets which are relative to the +start of the maker note IFD instead of the usual start of EXIF. This is a good +idea if implemented properly, but this is not done consistently. (And some +manufacturers are not even consistent about how the offsets are calculated from +one camera model to the next!)

+ +
Technical aside: +
If EXIF were designed properly, all offsets would +be relative to 4 bytes after the end of the IFD, which is the normal position +for values to be stored, and all value data for the IFD would be stored in a +block at this location. If this was done, an entire IFD could be relocated +easily without causing problems.
+ +

Below is a list of idiosyncrasies in files written by the digital cameras or +software from various manufacturers. Many of these quirks relate to the offset +problem mentioned above.

+ +
+ +

Canon: The 350D (firmware 1.0.1) gets the size of the thumbnail image +wrong and reports it to be 10 bytes too long. This can cause the reported +thumbnail image data to run off the end of the APP1 segment. A bug in version +1.0.4 of the 40D firmware causes it to write a maker note entry count that is +one greater than it should be.

+ +

Casio: The preview image is referenced by two different offsets (the +PreviewImage tag plus a PreviewImageStart/PreviewImageLength pair). Also, the +offset for the PrintIM information is relative to the start of the IFD entry +even though other offsets aren't.

+ +

Concord: Some models write PrintIM information with an entry-based +offset like Casio.

+ +

General Electric: A number of GE cameras store zero offsets for some +maker note tags (possibly to indicate that the tags do not exist), and other +offsets are 12 bytes too high for some models (like the A1230, E1035 and G2). +

+ +

Hewlett-Packard: The PhotoSmart 720 (one of the few HP models to use +EXIF-format maker notes) uses a format code of 5 (rational64u) for tag 0x0204, +but stores a rational32u value. Other models show about as much standardization +as the Kodak point-and-shoot lineup. Also, some models (C945, M22, M23, R507, +R607, R707, R717, R725, R727, R817, R818, R827, R927 and R960) write the EXIF +ComponentsConfiguration incorrectly as ASCII characters (like the Leica M8 and +M9).

+ +

Kodak: Professional DCS Photo Desk software writes a cyclical EXIF +directory such that the InteropIFD pointer points back to IFD0. +Point-and-shoot models show little standardization in maker note format. +Some models with IFD-format maker notes store incorrect count values for +a number of tags (this is particularly nasty), and may contain blank IFD +entries which are filled with 0xff's (not zeros like other makes).

+ +

Konica: The KD-300Z writes all maker notes offsets relative to the +start of the individual IFD entry.

+ +

Kyocera: A number of models write all maker notes offsets relative +to the start of the individual IFD entry.

+ +

Leica: Leica is hands-down the most inconsistent +company when it comes to writing makernote information. Various models use +different signatures and different bases for the offsets for the maker notes. As +well as this, they do a number of really peculiar things with in their +metadata.

+ +

The M8 and M9 write the EXIF ComponentsConfiguration value in +ASCII instead of binary. The M8 writes EXIF ExposureCompensation and +ShutterSpeedValue incorrectly as a unsigned rationals when they should be +signed. This leads to crazy values like "+65536" for small negative exposure +compensations, and "0 s" for long exposure times. (NOTE: These are all EXIF +idiosyncrasies since the values are in the standard EXIF, not the maker +notes.) In DNG images, the M8 uses maker note offsets relative to the +start of the maker notes in JPEG images (very reasonable), but relative to +the end of the maker note header in DNG images. I think this was a mistake +because this is changed in M9 DNG images to be the same as JPEG images.

+ +

2010-02-20: The Leica S2 maker note format is the MOST idiotic I have seen, and has the +following peculiarities:

+ +
    +
  • It is stored as a trailer after the JPEG EOI (but referenced from a pointer +inside the APP1 EXIF segment).
  • +
  • Most of the offsets in this MakerNote IFD are relative to the start of the +file instead of the EXIF TIFF header (which is particularly cruel because they +are broken if other software simply adds a leading JFIF segment, but there is no +simple way to detect that this has happened. Normally this could be detected by +analyzing the pointers, but this doesn't work here because all of the unused +data in the Leica maker notes make normal pointer assumptions impossible).
  • +
  • The PreviewImage offset is relative to the start of the MakerNote data +(which is MUCH more reasonable, but using two different offset bases in the same +directory is yet another level of idiocy).
  • +
  • In DNG images the maker notes use an absolute base offset. Yet again +different from the M8 and M9. Consistency isn't Leica's strong suit.
  • +
+ +

2013-07-27: Strike that. The most idiotic award now goes to the Leica M (Typ 240), which +adds these quirks (firmware 1.1.0.2):

+ +
    +
  • Tag 0x0301 has the same offset as the PreviewImage in the maker note IFD, +although it looks like the data for this tag probably comes after the +PreviewImage.
  • +
  • Tag 0x0302 has an invalid offset (0xffffffff).
  • +
+ +

2015-09-03: OK, I give up on Leica. The new Leica S (Typ 007) attains a new level of +stupidity by storing the preview image in the JPG file using a completely +nonsensical technique (in IFD2 of the EXIF segment, with the data being stored +outside the EXIF segment after the JPEG EOI), as well as various other brainless +blunders. [@Leica: Try reading the MPF specification.]

+ +

Minolta: An obvious bug in the firmware of the Z2 writes an incorrect +offset for the 'MinoltaCameraSettings2' information -- it writes the offset of +the offset itself instead of the offset of the value (hahaha!). Other +offsets are correct.

+ +

Nikon: D2H NEF files have huge blocks with all zero data (3.7 MB in +my test file!).

+ +

Olympus: The E-1 and E-300 have subdirectories in the maker notes, but +not only does the data size of these subdirectories exclude the subdirectory +value data, but also it is 2 bytes too small for the directory information +itself (doh! -- they forgot to include the entry count). Similarly, the stored +size of the maker note data block is too small for many models, which results +in a loss of data if the maker notes are copied as a block when an image is +rewritten.

+ +

Pentax: The Optio 330 uses an offset for the PrintIM information which +is relative to the start of the IFD entry (hmmm, like some Casio models...). +Also, preview image offsets in the maker notes are given relative to the EXIF +base rather than the maker note base (like all other maker notes offsets).

+ +

The Optio 550, 555, 33WR and 43WR all specify a PrintIM directory at a the +same offset of 0x29a with length 40 bytes, but the only PrintIM information in +the file is nowhere near that offset and is 128 bytes long. Also for these +models, tag 0x002e has a constant value of 0x6a6 even though its position +changes. Finally, all of these models plus the Optio WP waste many kilobytes of +space in each image with large unused data blocks in the EXIF information.

+ +

The Optio 330RS and 430RS double reference the preview image information.

+ +

Note that the worst problems are with the Optio 230, 330, and 430, which +carry the Asahi brand name.

+ +

Photoshop and Nikon Capture: Both of these packages write TIFF IPTC +information as 'int32u' (or 'LONG'). This is wrong +(see +reference). Nikon Capture goes one step further and simply ignores IPTC +that is written correctly as 'undef' or 'int8u'. (So for compatibility, +ExifTool also writes this incorrectly as 'int32u'.) Photoshop completely +deletes the maker notes when an image is edited.

+ +

Ricoh: There is an IFD subdirectory in the Ricoh maker notes of both the +Caplio RR30 and RR1. The RR30 uses standard EXIF offsets (relative to the start +of the EXIF data), but for the RR1 the offsets are relative to the start of the +subdirectory. The G700 uses MPF offsets relative to the start of the file, +instead of the start of the MPF segment as per the MPF spec. The HX15 uses +a standard EXIF maker note structure, but there are 2 extra padding bytes +between the IFD entry count and the 1st IFD entry. The HZ15 and Pentax XG-1 (by +Ricoh) both have an extra 2 bytes after the IFD entry counts. All value offsets +are erroneously 0 for the HZ15, and there are other problems with the offsets +stored by the XG-1.

+ +

Rollei: The DK4010 writes all maker notes offsets relative to the +start of the individual IFD entry.

+ +

Sanyo: The offsets written in the maker notes of the J1, J2, J3, S1, +S3 and S4 have very little to do with reality. Apparently the Sanyo +programmers have no understanding of the concept of an IFD offset.

+ +

Skanhex: With some Skanhex models (SX-210Z3, SX-330Z3, SX3300, +SX410Z3), the 264-byte makernotes block contains no useful information, and +overlaps values from the ExifIFD. For these models there is also a large block +(typically 1195 bytes) of unreferenced information in the EXIF data immediately +following the IteropIFD. This block begins with the character sequence +"SKANH\0", and contains exactly the same information in all 20 of my sample +images that contain this block (except for a variable amount of padding at the +end with 0xff bytes). These quirks also affect some Gateway, Jenoptik, Medion, +Samsung and Yakumo models built by Skanhex.

+ +

Toshiba: The PDR-3310 writes all maker notes offsets relative to the +start of the individual IFD entry. (very similar to Konica KD-300Z)

+ +
+

RAW file Idiosyncrasies

+ +

Minolta MRW: The A200 stores the thumbnail image offset in IFD0 +relative to the start of file, while all other offsets are relative to the start +of the TIFF header, which is 48 bytes into the file. Also, the A200 stores the +StripOffsets and the StripByteCounts values in the wrong byte order.

+ +

Sony ARW: The maker notes of ARW images are not self-contained, so +some information is lost when the images are rewritten by other software +(including the Adobe DNG converter). The A100 with firmware 1.00 sets the high +word of the thumbnail image offset to zero, but it should sometimes be 0x0001. +(This problem is fixed for firmware 1.01.) Also with the A100, the +JpgFromRawLength stored in IFD0 may be wrong (although this value is also stored +in the MakerNotes and is correct here). As well, much information in these +images is encrypted, which complicates things somewhat. Even the Sony IDC +utility can't properly rewrite ARW files -- it corrupts the embedded MRW record +when used to edit ARW images. Even funnier: IDC v3.0 will crash when loading +some original A100 firmware 1.00 images, but no longer crashes if the images are +first edited with ExifTool (probably because ExifTool fixes the above mentioned +problems when it rewrites the image).

+ +

Hasselblad FFF: Many Hasselblad camera models +write TIFF-format FFF raw files which contain a double-referenced +reduced-resolution image that is referenced from both IFD0 and IFD1.Immediately +following the data for this image is an unreferenced data block that the +Hasselblad Phocus software uses for an updated preview to reflect the raw +development settings. This unreferenced data is lost if the FFF file is edited +using a TIFF-compatible algorithm, which results in the Phocus no longer +updating the preview when the settings are changed.

+ +

Leica DNG: The makernote offsets for the M8 are relative to the start +of the makernote IFD in JPEG images, but relative to the start of the makernote +header (8 bytes earlier) in DNG images. [2009-09-09: This is fixed for the +M9 which has offsets relative to the start of the makernote header for both JPEG +and DNG.]

+ +

Nikon NEF: Aside from the encryption that Nikon uses to try to hide +some information in their maker notes, the NEF files in general seem fairly well +behaved. Even so, the Nikon Transfer utility (version 1.3) still manages to +corrupt some information in the 0th SubIFD when it is used to process NEF +images. (Beware that other Nikon utilities may have this same problem if they +use the same NEF writing routines.) But luckly the lost information isn't very +important. (Only a few tags from the embedded full-sized preview image are +lost: XResolution, YResolution and YCbCrPositioning.) Also, Nikon Transfer and +Nikon Capture both write an incorrect size for the maker notes, which could +cause loss of MakerNote information if the file is edited by other software (but +this isn't a problem with ExifTool, which will fix this type of problem +automatically when writing).

+ +

Nikon NRW: Nikon should have just called this NEF with a different +version number -- there should be no need to pollute the universe with zillions +of unnecessary file extensions. Oh right, they weren't smart enough to include +a file identifier containing a version number in their NEF images -- Doh! In +these images, CFAPattern2 is written incorrectly with UNDEFINED instead of BYTE +format.

+ +

Phase One IIQ: Many values are referenced from more than one location +in the TIFF structure of these images. For instance, the IFD0 strip data +actually exists within the MakerNotes data block. This is a poor design, and +leads to duplicated information when the image is rewritten.

+ +

Ricoh DNG: The GR Digital IV (firmware 1.14) stores an incorrect +length for the JPEG preview in SubIFD1.

+ +

Samsung SRW: Yet another TIFF-based raw image with no proper file +identifier. In these images the thumbnail is stored inside a SubIFD of IFD1 +instead of directly in IFD1 (dumb, dumb...). Also, the NX200 (firmware +NX200_011181) uses a base offset for the X/YResolution values that is different +from the PreviewImageStart pointer, both in the MakerNotes PreviewIFD. (Note +that the NX100 uses the same base for both, so this is certainly a firmware bug +for the NX200. [2012-06-21: This problem now also affects the EX1, NX20 and +WB2000] [2013-07-25: Add the NX2000 to this list])

+ +

2018-04-30: The Samsung EK-GN120 has many +problems in the makernote offsets. Most of the offsets are based on the start of +the maker notes, but the PreviewIFD is offset is wrong by 36 bytes, and some +offsets in the PreviewIFD are based on the start of the maker notes while others +are absolute. What a mess!

+ +
+Created Mar 25, 2005 +
Last revised Feb 24, 2020 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/index.html b/ExifTool/html/index.html new file mode 100644 index 0000000..052ae45 --- /dev/null +++ b/ExifTool/html/index.html @@ -0,0 +1,1587 @@ + + + +ExifTool by Phil Harvey + + + + + + + + + + + + + +

ExifTool by Phil Harvey

+

Read, Write and Edit Meta Information!

+

Also available --> Utility to fix Nikon NEF images corrupted by Nikon software

+

Note: If exiftool.org goes down, it is because of the crappy DreamHost +web hosting which disables an "unlimited traffic" web site if a single bot hammers +the site with a moderate load. An alternate ExifTool homepage is available at +http://exiftool.sourceforge.net/

+ + + +
+ +Download Version 12.67 (5.0 MB) - +Sept. 19, 2023
+ +

ExifTool is a platform-independent Perl +library plus a command-line application for +reading, writing and editing meta information in a +wide variety of files. +ExifTool supports many different metadata formats including +EXIF, +GPS, +IPTC, +XMP, +JFIF, +GeoTIFF, +ICC Profile, +Photoshop IRB, +FlashPix, +AFCP and +ID3, +Lyrics3, +as well as the maker notes of many digital cameras by +Canon, +Casio, +DJI, +FLIR, +FujiFilm, +GE, +GoPro, +HP, +JVC/Victor, +Kodak, +Leaf, +Minolta/Konica-Minolta, +Motorola, +Nikon, +Nintendo, +Olympus/Epson, +Panasonic/Leica, +Pentax/Asahi, +Phase One, +Reconyx, +Ricoh, +Samsung, +Sanyo, +Sigma/Foveon and +Sony.

+ +

ExifTool is also available as a stand-alone Windows executable and a +MacOS package: (Note that these versions contain the executable +only, and do not include the HTML documentation or other files of the full +distribution above.)

+ +
+Windows Executable: + + exiftool-12.67.zip (6.9 MB)
+ +

The stand-alone Windows executable does not require Perl. Just +download and un-zip the archive then double-click on +"exiftool(-k).exe" to read the application documentation, +drag-and-drop files and folders to view meta information, or rename to +"exiftool.exe" for command-line use. Runs on all versions +of Windows.

+ +

(Note: Oliver Betz provides an +alternate ExifTool Windows installer +that avoids some problems of the self-extracting archive version above. Please post +here +if you have any problems/comments with this version.)

+ +
+MacOS Package: + + ExifTool-12.67.dmg (3.2 MB)
+ +

The MacOS package installs the ExifTool command-line application and +libraries in /usr/local/bin. After installing, type "exiftool" in a +Terminal window to run exiftool and read the application documentation.

+ +

Read the installation instructions for help +installing ExifTool on Windows, MacOS and Unix systems.

+ + + +

Features

+ + +

A Note to Unix Power-Users

If you feel +the need to use "find" or "awk" in conjunction with ExifTool, then you probably +haven't discovered the full power of ExifTool. Read about the -ext, +-if, -p and -tagsFromFile options in the +application documentation. (This is +common mistake number 3.)
+ +

What People are Saying about ExifTool

+
+"In my experience, nothing but nothing is as complete, powerful, and flexible as +Phil Harvey's exiftool ... I've never seen anything that's in the same ballpark for power." +- dpreview forum
+
"While there are a lot of image tools available, nothing comes close for accessing/updating +the metadata like ExifTool" - merg's blog
+
"Fast, reliable and amazingly comprehensive ..." +- CPAN ratings
+
"... the one piece of free software that gets the most +detailed exif data of /any/ tool I've found." +- gnome mail archives +
+ +
"ExifTool makes every other EXIF reader (and writer) than I've +seen, including the camera manufacturers' readers, look lame." +- photo.net Nikon forum +
+
"Insanely great tool with a long learning curve ..." - +Adobe Forums +
+
"ExifTool has been outstanding in our custom used Tesla image gallery build. +We are able to aggregate image meta from our user base and incorporate this into development +iterations to continually optimize our platform..." +- Find My Electric
+
"... it's super awesome, it's super reliable and after many years of +development it's still being updated!" - +P_W999 blog +
+
"... it is the mother of all EXIF utilities; the BFG of meta-data +extraction; the Pan Galactic Gargle Blaster of EXIF tools ... This thing will +suck the last bit of metadata out of whatever image file you throw at it!" - +Open Photography Forums +
+
"... it is total fucking gibberish to me." - +Reddit Linux Questions +
+ +

Supported File Types

+

ExifTool can Read, Write and/or Create files in the following formats. +Also listed are the support levels for EXIF, IPTC (IIM), XMP, ICC_Profile and other metadata types +for each file format.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
File TypeSupportDescriptionEXIFIPTCXMPICC1Other
360R/WGoPro 360 video (QuickTime-based)R/W3R/W3R/W/C-R/W/C QuickTime, R GoPro
3FRRHasselblad RAW (TIFF-based)RRRR-
3G2, 3GP2R/W3rd Gen. Partnership Project 2 a/v (QuickTime-based)R/W3R/W3R/W/C-R/W/C QuickTime
3GP, 3GPPR/W3rd Gen. Partnership Project a/v (QuickTime-based)R/W3R/W3R/W/C-R/W/C QuickTime
7zR7z Archive----R ZIP
ARUnix static library code Archive----R EXE
AARAudible Audiobook----R Audible
AAERApple edit information (XML PLIST-based)----R PLIST
AAXR/WAudible Enhanced Audiobook (QuickTime-based)R/W3R/W3R/W/C-R/W/C QuickTime
ACRRAmerican College of Radiology ACR-NEMA (DICOM-like)----R DICOM
AFM, ACFM, AMFMRAdobe [Composite/Multiple Master] Font Metrics----R Font
AI, AITR/WAdobe Illustrator [Template] (PS or PDF)R/W/C4R/W/C4R/W/C5R/W/C4R/W/C PDF PostScript, R Photoshop
AIFF, AIF, AIFCRAudio Interchange File Format [Compressed]----R AIFF ID3 Lyrics3
APERMonkey's Audio----R APE ID3 Lyrics3
ARQR/WSony Alpha Pixel-Shift RAW (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W Sony SonyIDC
ARWR/WSony Alpha RAW (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W Sony SonyIDC
ASFRMicrosoft Advanced Systems Format--R-R ASF
AVIRAudio Video Interleaved (RIFF-based)R3-R-R RIFF
AVIFR/WAV1 Image File Format (QuickTime-based)R/W/C-R/W/CR/WR/W QuickTime
BMP, DIBRWindows BitMaP / Device Independent Bitmap----R BMP
BPGRBetter Portable GraphicsR-RRR BPG
BTFRBigTIFF (64-bit Tagged Image File Format)RRRR-
CHMRMicrosoft Compiled HTML format----R EXE
COSRCapture One Settings (XML-based)----R XML
CR2R/WCanon RAW 2 (TIFF-based) (CR2 spec)R/W/CR/W/CR/W/CR/W/CR/W Canon, R/W/C CanonVRD2
CR3R/WCanon RAW 3 (QuickTime-based) (CR3 spec)R/W/C-R/W/C-R/W Canon QuickTime, R/W/C CanonVRD2
CRMR/WCanon RAW Movie (QuickTime-based)R/W/C-R/W/C-R/W Canon QuickTime
CRW, CIFFR/WCanon RAW Camera Image File Format (CRW spec)--R/W/C-R/W CanonRaw, R/W/C CanonVRD2
CS1R/WSinar CaptureShop 1-shot RAW (PSD-based)R/W/CR/W/CR/W/CR/W/CR Photoshop
CSVRComma-Separated Values----R Text
CZIRZeiss Integrated Software RAW (ZISRAW)----R ZISRAW, R XML
DCM, DC3, DIC, DICMRDICOM - Digital Imaging and Communications in Medicine----R DICOM
DCPR/WDNG Camera Profile (DNG-like)R/W/CR/W/CR/W/CR/W/C-
DCRRKodak Digital Camera RAW (TIFF-based)RRRR-
DFONTRMacintosh Data Fork Font----R Font
DIVXRDivX media format (ASF-based)--R-R ASF
DJVU, DJVRDjVu image (AIFF-like)--R-R DJVU
DNGR/WDigital Negative (TIFF-based)R/W/CR/W/CR/W/CR/W/C-
DOC, DOTRMicrosoft Word Document/Template (FPX-like)--RRR FlashPix
DOCX, DOCMROffice Open XML Document [Macro-enabled]----R XML ZIP
DOTX, DOTMROffice Open XML Document Template [Macro-enabled]----R XML ZIP
DPXRDigital Picture Exchange----R DPX
DR4R/W/C2Canon DPP version 4 Recipe----R/W/C CanonVRD2
DSS, DS2RDigital Speech Standard [2]----R Olympus
DYLIBRMacOS Mach-O executable and library files----R EXE
DVRDigital Video----R DV
DVBR/WDigital Video Broadcasting (QuickTime-based)R/W3R/W3R/W/C-R/W/C QuickTime
DVR-MSRMicrosoft Digital Video Recording (ASF-based)--R-R ASF
EIPRCapture One Enhanced Image Package (ZIP-based)R---R XML ZIP
EPS, EPSF, PSR/W[Encapsulated] PostScript FormatR/W/CR/W/CR/W/CR/W/CR/W/C PostScript, R Photoshop
EPUBRElectronic Publication (ZIP/XML-based)----R XML ZIP
ERFR/WEpson RAW Format (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W Olympus
EXE, DLLRDOS/Windows executable and library files----R EXE
EXIFR/W/CExchangeable Image File Format metadata (TIFF-based)R/W/C----
EXRROpen EXR (Extended Range)----R OpenEXR
EXVR/W/CExiv2 metadata file (JPEG-based)R/W/CR/W/CR/W/CR/W/CSupported JPEG Meta Information
F4A, F4B, F4P, F4VR/WAdobe Flash Player 9+ Audio/Video (QuickTime-based)R/W3R/W3R/W/C-R/W/C QuickTime
FFFR/W6Hasselblad Flexible File Format (TIFF-based)R/W/CR/W/CR/W/CR/W/C-
FFFRFLIR Systems thermal image File Format----R FLIR
FITSRFlexible Image Transport System----R FITS
FLARMacromedia/Adobe Flash project (FPX-like)--RRR FlashPix
FLACRFree Lossless Audio Codec----R FLAC ID3 Lyrics3
FLIFR/WFree Lossless Image FormatR/W/C-R/W/CR/W/CR FLIF
FLVRFlash Video--R-R Flash
FPFRFLIR Public image Format----R FLIR
FPXRFlashPix image--RRR FlashPix
GIFR/WCompuserve Graphics Interchange Format--R/W/CR/W/CR/W/C GIF
GLVR/WGarmin Low-resolution Video (QuickTime-based)R/W3R/W3R/W/C-R/W/C QuickTime
GPRR/WGoPro RAW (DNG-based)R/W/CR/W/CR/W/CR/W/C-
GZ, GZIPRGNU ZIP compressed archive----R ZIP
HDP, WDP, JXRR/WWindows HD Photo / Media Photo / JPEG XR (TIFF-based)R/W/CR/W/CR/W/CR/W/C-
HDRRRadiance RGBE High Dynamic-Range----R Radiance
HEIC, HEIF, HIFR/WHigh Efficiency Image Format (QuickTime-based)R/W/C-R/W/CR/WR/W QuickTime
HTML, HTM, XHTMLR[Extensible] HyperText Markup Language----R HTML
ICC, ICMR/W/C1International Color Consortium color profile---R/W/C-
ICO, CURRWindows Icon / Cursor----R ICO
ICS, ICALRiCalendar Schedule----R VCalendar
IDMLRAdobe InDesign Markup Language (ZIP/XML-based)----R XML ZIP
IIQR/WPhase One Intelligent Image Quality RAW (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W PhaseOne
IND, INDD, INDTR/WAdobe InDesign Document/Template--R/W/C--
INSPR/WInsta360 Picture (JPEG-based)R/W/CR/W/CR/W/CR/W/CSupported JPEG Meta Information
INSVRInsta360 Video (QuickTime-based)--R-R QuickTime
INXRAdobe InDesign Interchange (XML-based)--R--
ISORISO 9660 disk image----R ISO
ITCRiTunes Cover Flow artwork----R ITC
J2C, J2K, JPCRJPEG 2000 codestreamR3R3RRR Jpeg2000 Photoshop3
JP2, JPF, JPM, JPXR/WJPEG 2000 image [Compound/Extended]R/W/C3R/W/C3R/W/CRR/W/C Jpeg2000, R Photoshop3
JPEG, JPG, JPER/WJoint Photographic Experts Group imageR/W/CR/W/CR/W/CR/W/CSupported JPEG Meta Information
JSONRJavaScript Object Notation----R JSON
JXLR/WJPEG XL (codestream and ISO BMFF) (Jpeg200-based)R/W/C-R/W/C--
K25RKodak DC25 RAW (TIFF-based)RRRR-
KDCRKodak Digital Camera RAW (TIFF-based)RRRRR Kodak
KEY, KTHRApple iWork '09 Keynote presentation/Theme----R XML ZIP
LARLossless Audio (RIFF-based)R3-R-R RIFF
LFP, LFRRLytro Light Field Picture----R Lytro
LIFRLeica Image File----R LIF
LNKRMicrosoft Shell Link (Windows shortcut)----R LNK
LRVR/WLow-Resolution Video (QuickTime-based)R/W3R/W3R/W/C-R/W/C QuickTime
M2TS, MTS, M2T, TSRMPEG-2 Transport Stream (used for AVCHD video)----R M2TS H264 MISB
M4A, M4B, M4P, M4VR/WMPEG-4 Audio/Video (QuickTime-based)R/W3R/W3R/W/C-R/W/C QuickTime
MACOSRMacOS "._" sidecar file (may have any extension)----R XAttr RSRC
MAXR3D Studio MAX (FPX-like)--RRR FlashPix
MEFR/WMamiya (RAW) Electronic Format (TIFF-based)R/W/CR/W/CR/W/CR/W/C-
MIER/W/CMeta Information Encapsulation (MIE specification)R/W/CR/W/CR/W/CR/W/CR/W/C MIE
MIFF, MIFRMagick Image File FormatRRRRR MIFF Photoshop
MKA, MKV, MKSRMatroska Audio/Video/Subtitle----R Matroska
MOBI, AZW, AZW3RMobipocket electronic book (Palm-based)----R Palm MOBI
MODDRSony Picture Motion metadata (XML PLIST-based)----R PLIST
MOIRMOD Information file----R MOI
MOSR/WCreo Leaf Mosaic (TIFF-based)R/W/CR/W/CR/W/CR/W/CR Leaf
MOV, QTR/WApple QuickTime MovieR/W3R/W3R/W/C-R/W/C QuickTime
MP3RMPEG-1 layer 3 audio----R MPEG ID3 Lyrics3 APE
MP4R/WMotion Picture Experts Group version 4 (QuickTime-based)R/W3R/W3R/W/C-R/W/C QuickTime
MPCRMusepack Audio----R MPC ID3 Lyrics3 APE
MPEG, MPG, M2VRMotion Picture Experts Group version 1 or 2----R MPEG ID3 Lyrics3
MPOR/WExtended Multi-Picture format (JPEG with MPF extensions)R/W/CR/W/CR/W/CR/W/CSupported JPEG Meta Information
MQVR/WSony Mobile QuickTime VideoR/W3R/W3R/W/C-R/W/C QuickTime
MRWR/WMinolta RAWR/W/CR/W/CR/W/CR/W/CR/W MinoltaRaw Minolta
MRCRMedical Research Council----R MRC
MXFRMaterial Exchange Format----R MXF
NEFR/WNikon (RAW) Electronic Format (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W Nikon NikonCapture
NKSCR/WNikon Sidecar (XMP-based)--R/W/C--
NMBTEMPLATERApple iWork '09 Numbers Template----R XML ZIP
NRWR/WNikon RAW (2) (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W Nikon NikonCapture
NUMBERSRApple iWork '09 Numbers spreadsheet----R XML ZIP
ORUnix compiled code Object----R EXE
ODB, ODC, ODF, ODG,
ODI, ODP, ODS, ODT
ROpen Document Database/Chart/Formula/Graphics/
Image/Presentation/Spreadsheet/Text (ZIP/XML-based)
----R XML ZIP
OFRROptimFROG audio (RIFF-based)R3-R-R RIFF
OGG, OGVROgg bitstream container----R FLAC ID3 Lyrics3 Theora Vorbis
ONPRON1 Presets----R JSON PLIST
OPUSROgg Opus audio----R FLAC ID3 Lyrics3 Opus Vorbis
ORF, ORIR/WOlympus RAW Format (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W Olympus
OTFROpen Type Font----R Font
PACRLossless Predictive Audio Compression (RIFF-based)R3-R-R RIFF
PAGESRApple iWork '09 Pages document----R XML ZIP
PCDRKodak Photo CD Image Pac----R PhotoCD
PCXRPC Paintbrush----R PCX
PDB, PRCRPalm Database----R Palm
PDFR/W7Adobe Portable Document FormatR3R3R/W/CR3R/W/C PDF, R Photoshop
PEFR/WPentax (RAW) Electronic Format (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W Pentax
PFA, PFBRPostScript Font ASCII/Binary----R Font
PFMRPrinter Font Metrics----R Font
PFMRPortable FloatMap----R PFM
PGFRProgressive Graphics File----R PGF PNG
PICT, PCTRApple Picture file---RR PICT Photoshop
PLISTRApple Property List (binary and XML formats)----R PLIST
PMPRSony DSC-F1 Cyber-Shot image----R Sony
PNG, JNG, MNGR/WPortable/JPEG/Multiple-image Network GraphicsR/W/C3R/W/C3R/W/CR/W/CR/W/C PNG
PPM, PBM, PGMR/WPortable Pixel/Bit/Gray Map----R PPM, R/W/C Comment
PPT, PPS, POTRPowerPoint Presentation/Slideshow/Template (FPX-like)--RRR FlashPix
POTX, POTMROffice Open XML Presentation Template [Macro-enabled]----R XML ZIP
PPAX, PPAMROffice Open XML Presentation Addin [Macro-enabled]----R XML ZIP
PPSX, PPSMROffice Open XML Presentation Slideshow [Macro-enabled]----R XML ZIP
PPTX, PPTMROffice Open XML Presentation [Macro-enabled]----R XML ZIP
PSD, PSB, PSDTR/WPhotoShop Document / Large Document / TemplateR/W/CR/W/CR/W/CR/W/CR Photoshop
PSP, PSPIMAGERPaint Shop ProR---R PSP
QTIF, QTI, QIFR/WQuickTime Image FileR/W3R/W3R/W/C-R/W/C QuickTime
R3DRRedcode RAW video----R Red
RARReal Audio----R Real ID3 Lyrics3
RAFR/WFujiFilm RAW FormatR/W/CR/W/CR/W/CR/W/CR/W FujiFilm
RAM, RPMRReal Audio/Plug-in Metafile----R Real
RARRRAR Archive----R ZIP
RAWRKyocera Contax N Digital RAW----R KyoceraRaw
RAWR/WPanasonic RAW (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W PanasonicRaw Panasonic
RIFF, RIFRResource Interchange File FormatR3-R-R RIFF
RM, RV, RMVBRReal Media/Video [Variable Bitrate]----R Real
RSRCRMac OS Resource----R RSRC Photoshop PostScript Font
RTFRRich Text Format----R RTF
RW2R/WPanasonic RAW 2 (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W PanasonicRaw Panasonic
RWLR/WLeica RAW (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W PanasonicRaw Panasonic
RWZRRawzor compressed imageRRRRR Rawzor
SEQRFLIR Systems image Sequence----R FLIR
SKETCHRSketch design file----R JSON ZIP
SORUnix ELF executable and Shared Object files----R EXE
SR2R/WSony RAW 2 (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W Sony
SRFRSony RAW Format (TIFF-based)RRRRR Sony
SRWR/WSamsung RAW format (TIFF-based)R/W/CR/W/CR/W/CR/W/CR/W Samsung
SVGRScalable Vector Graphics (XML-based)----R SVG
SWFRShockwave Flash--R-R Flash
THMR/WThumbnail image (JPEG)R/W/CR/W/CR/W/CR/W/CSupported JPEG Meta Information
THMXROffice Open XML Theme----R XML ZIP
TIFF, TIFR/WTagged Image File FormatR/W/CR/W/CR/W/CR/W/CR/W/C GeoTIFF1, R/W Trailers
TTF, TTCRTrue Type Font/Collection----R Font
TORRENTRBitTorrent description file----R Torrent
TXTRText files----R Text
VCF, VCARDRVirtual Card----R VCard
VNTRScene7 Vignette (FPX-like)---RR FlashPix
VNTRV-Note document----R VNote
VOBRVideo Object (MPEG-based)----R MPEG
VRDR/W/C2Canon DPP Recipe Data--R/W/C-R/W/C CanonVRD2
VSDRMicrosoft Visio Drawing (FPX-like)--RRR FlashPix
WAVRWindows digital audio WAVeform (RIFF-based)R3-R-R RIFF
WEBMRGoogle Web Movie (Matroska-based)----R Matroska
WEBPR/WGoogle Web Picture (RIFF-based)R/W/C-R/W/CR/W/CR RIFF
WMA, WMVRWindows Media Audio/Video (ASF-based)--R-R ASF
WPGRWordPerfect Graphics----R WPG
WTVRWindows recorded TV show----R WTV
WVRWavePack lossless audio (RIFF-based)R3-R-R RIFF
X3FR/WSigma/Foveon RAWR/W/CR/W/CR/W/CR/W/CR/W Sigma, R SigmaRaw
XCFRGIMP native image formatRRRRR GIMP
XLS, XLTRMicrosoft Excel Spreadsheet/Template (FPX-like)--RRR FlashPix
XLSX, XLSM, XLSBROffice Open XML Spreadsheet [Macro-enabled/Binary]----R XML ZIP
XLTX, XLTMROffice Open XML Spreadsheet Template [Macro-enabled]----R XML ZIP
XMPR/W/CExtensible Metadata Platform sidecar file--R/W/C--
ZIPRZIP archive----R ZIP
1 Block write only, +2 Block create only, +3 Non-standard format, +4 Only writable for PostScript-format file type, +5 Only writable for PDF-format file type, +6 Only writable when ignoring minor errors due to Phocus incompatibility, +7 Old metdata is never actually deleted
+

Supported JPEG Meta Information

+

ExifTool can Read, Write and/or Create the following types +of meta information in JPEG images:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JPEG Meta InformationSupportDescription
APP0 - JFIFR/W/CJPEG File Interchange Format
APP0 - JFXXRExtended JFIF
APP0 - CIFFR/WCamera Image File Format (used by some Canon models)
APP0 - AVI1RJPEG AVI information
APP0 - OcadRPhotobucket Ocad segment
APP1 - EXIFR/W/CExchangeable Image File Format (multi-segment)
APP1 - XMPR/W/CExtensible Metadata Platform (multi-segment)
APP1 - QVCIRCasio QV-7000SX QVCI information
APP1 - FLIRRFLIR thermal imaging data (multi-segment)
APP1 - RawThermalImageRThermal image from Parrot Bebop-Pro Thermal drone
APP2 - ICCR/W/CInternational Color Consortium (multi-segment)
APP2 - FPXRRFlashPix Ready (multi-segment)
APP2 - MPFRMulti-Picture Format
APP2 - InfiRay VersionRInfiRay IJPEG Version header
APP2 - PreviewImageRSamsung/GE APP2 preview image (multi-segment)
APP3 - Kodak MetaR/WKodak Meta information (EXIF-like)
APP3 - StimRStereo Still Image format
APP3 - JPSRJPEG Stereo image
APP3 - ThermalDataRDJI RJPEG thermal data (multi-segment)
APP3 - ImagingDataRInfiRay IJPEG IR+thermal+visible data (multi-segment)
APP3 - PreviewImageRSamsung/HP preview image (multi-segment)
APP4 - ScaladoR(presumably written by Scalado mobile software)
APP4 - ThermalParamsRThermal parameters from DJI RJPEG file
APP4 - ThermalParams2RDJI thermal parameters type 2
APP4 - ThermalParams3RDJI thermal parameters type 3
APP4 - FPXRRFlashPix Ready in non-standard location (multi-segment)
APP4 - InfiRay FactoryRInfiRay IJPEG Factory Temperature
APP4 - PreviewImageR(continued from APP3)
APP5 - Ricoh RMETARRicoh custom fields
APP5 - Samsung UniqueIDRSamsung Unique ID
APP5 - ThermalCalibrationRThermal calibration data from DJI RJPEG file
APP5 - InfiRay PictureRInfiRay IJPEG Picture Temperature
APP5 - PreviewImageR(continued from APP4)
APP6 - EPPIMRToshiba PrintIM
APP6 - NITFRNational Imagery Transmission Format
APP6 - HP TDHDRHewlett-Packard Photosmart R837 TDHD information
APP6 - GoProRGoPro Metadata Format (GPMF) information
APP6 - DJI DTATRDJI Thermal Analysis Tool record (JSON format)
APP6 - InfiRay MixModeRInfiRay IJPEG Mix Mode
APP7 - PentaxRPentax APP7 maker notes
APP7 - QualcommRQualcomm Camera Attributes
APP7 - HuaweiRHuawei APP7 maker notes (extract with Unknown option)
APP7 - InfiRay OpModeRInfiRay IJPEG Operation Mode
APP6 - DJI InfoRDJI debug information
APP8 - SPIFFRStill Picture Interchange File Format
APP8 - InfiRay IsothermalRInfiRay IJPEG Isothermal
APP9 - Media JukeboxRMedia Jukebox XML information
APP9 - InfiRay SensorRInfiRay IJPEG Sensor Information
APP10 - CommentRPhotoStudio Unicode Comment
APP11 - JPEG-HDRRJPEG-HDR compressed ratio image
APP11 - JUMBFRJpeg Universal Metadata Box Format (multi-segment)
APP12 - Picture InfoRASCII-based Picture Information
APP12 - DuckyR/W/CPhotoshop "Save for Web"
APP13 - Photoshop IRBR/W/CImage Resource Block (multi-segment, includes IPTC)
APP13 - Adobe CMRAdobe Color Management
APP14 - AdobeR/W/CAdobe DCT filter
APP15 - GraphicConverterRGraphicConverter quality
COMR/W/CJPEG Comment (multi-segment)
DQTR(used to calculate the Extra:JPEGDigest tag value)
SOFRJPEG Start Of Frame
JPEG Trailer 1SupportDescription
AFCP trailerR/WAXS File Concatenation Protocol (includes IPTC)
CanonVRD trailerR/W/CCanon DPP Recipe Data (includes DR4)
FotoStation trailerR/WFotoWare FotoStation (includes IPTC)
PhotoMechanic trailerR/WCamera Bits Photo Mechanic
MIE trailerR/WMeta Information Encapsulation
Samsung trailerRSamsung Galaxy trailer
Insta360 trailerRInsta360 trailer found in INSP files
NikonApp trailerRNikon trailer added by NX Studio to NEF/NRW files
PreviewImage trailerR/W/C(preview image written after JPEG EOI)
EmbeddedVideo trailerR(extracted only with ExtractEmbedded option)
1 All trailers except Samsung, Insta360, PreviewImage and EmbeddedVideo also have +R/W support in TIFF images, and the NikonApp trailer is used in NEF/NRW files.
+ +

System Requirements

+ +

Requires Perl 5.004 or later. No other libraries or software required, but +some optional Perl modules may be added to enable certain ExifTool features (for +details, see the DEPENDENCIES section of the README file included in the full +distribution).

+ +

Windows users: A stand-alone Windows executable +version of ExifTool is available which doesn't require Perl. You can also use +the pure Perl version if you already have Perl installed. (You can get a good, +free Perl interpreter from +activeperl.com.)

+ +

Everyone else (Mac, Unix, etc): Don't worry, you already have +Perl installed.

+ +

Running ExifTool

+ +

The exiftool application provides a +convenient command-line interface for the +Image::ExifTool Perl package (both included in the +full distribution). Once you have downloaded and extracted the distribution, you +can immediately run exiftool (without building or installing) by typing +"DIR/exiftool FILE" (or +"perl DIR/exiftool FILE" in Windows), where +DIR is the exiftool directory and FILE +is the name of an image file, including directory name. Read the +installation instructions or the README file included +in the full distribution for help installing ExifTool.

+ +

Many command-line options are available to allow you to access a wide range +of features. Run exiftool with no arguments for a +complete list of available options with +examples.

+ +

Running in Windows

+ +

i) From the command line:

+ +

The Perl application ("exiftool") is run by typing "perl +exiftool". Alternately, you may be able to rename it to +"exiftool.pl" and type "exiftool.pl", but this +requires that the proper Windows associations have been made for the +".pl" extension.

+ +

The stand-alone version ("exiftool(-k).exe") should be +renamed to "exiftool.exe" to allow it to be run by typing +"exiftool" at the command line.

+ +

If the exiftool executable ("exiftool.pl" or +"exiftool.exe") is not in the current directory or your system +PATH, then its directory must be specified on the command line (eg. by typing +"c:\path_to_exiftool\exiftool.pl" or +"c:\path_to_exiftool\exiftool").

+ +

Note that when typing commands in the "cmd.exe" shell, you should use double +quotes instead of single quotes as shown in some examples.

+ +

ii) Stand-alone version in the Windows GUI:

+ +

Double-click on "exiftool(-k).exe" to read the application +documentation, or drag-and-drop files and folders to run exiftool on the +selected files.

+ +

Simple options may be added inside brackets in the name of the stand-alone +executable. (But note that the characters /\?*:|"<> may not +be used because they are invalid in Windows file names.) In this way, the +behaviour of the drag-and-drop application can be customized. For example:

+ +
+ + + + + + + + + +
Executable NameOperation
exiftool(-k).exe
Print meta information in window and pause before terminating.
exiftool(-k -a -u -g1 -w txt).exe
Generate output ".txt" files with detailed meta information.
exiftool(-k -o %d%f.xmp).exe
Generate sidecar ".xmp" files.
exiftool(-copyright='Phil Harvey').exe
Add copyright information (and don't pause before terminating).
+ +

Hint: Options may also be added to the "Target" property of a Windows +shortcut for the executable. Using a shortcut has 3 advantages over adding +options in the file name: 1) different shortcuts may be created without +requiring multiple copies of the executable, 2) characters which are invalid +in file names may be used, and 3) the shortcuts can be given more meaningful +(and convenient) file names.

+ +

As well, it may be useful to increase the window and buffer sizes to display +more information: Right-click on the window's title bar then select +"Properties" from the menu and change the window layout settings.

+ +

Example Output

+ +
+
+> exiftool -h -canon t/images/Canon.jpg
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
File NameCanon.jpg
Camera Model NameCanon EOS DIGITAL REBEL
Date/Time Original2003:12:04 06:46:52
Shooting ModeBulb
Shutter Speed4
Aperture14.0
Metering ModeCenter-weighted average
Exposure Compensation0
ISO100
Lens18.0 - 55.0 mm
Focal Length34.0 mm
Image Size8x8
QualityRAW
FlashNo Flash
White BalanceAuto
Focus ModeManual Focus (3)
Contrast+1
Sharpness+1
Saturation+1
Color ToneNormal
Color SpacesRGB
File Size2.6 kB
File Number118-1861
Drive ModeContinuous Shooting
Owner NamePhil Harvey
Serial Number0560018150
+
+> exiftool -lang de -h -canon t/images/Canon.jpg
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DateinameCanon.jpg
KameramodellCanon EOS DIGITAL REBEL
Erstellungsdatum/-uhrzeit2003:12:04 06:46:52
AufnahmemodusBulb
Belichtungsdauer4
Blende14.0
BelichtungsmessmethodeMittenbetont
Belichtungskorrektur0
ISO-Empfindlichkeit100
Objektiv18.0 - 55.0 mm
Brennweite34.0 mm
Bildgröße8x8
QualitätRAW
BlitzmodusBlitz wurde nicht ausgelöst
WeißabgleichAutomatisch
Fokus-ModusManueller Fokus (3)
Kontrast+1
Schärfe+1
Farbsättigung+1
FarbtonNormal
FarbraumsRGB
Dateigröße2.6 kB
Dateinummer118-1861
AufnahmeartSerienaufnahme
Name des BesitzersPhil Harvey
Seriennummer0560018150
+
+
+ +

Verbose and HtmlDump Output

+ +

The Verbose (-v) and HtmlDump (-htmlDump) options print additional +information that can be very useful for debugging or when decoding new tags.

+ +

Tag Names Explained

+ +

A tag name is a "handle" that is used to refer to a specific piece of meta +information. Tag names are entered on the command line with a leading +'-', in the order you want them displayed. Valid characters +in a tag name are A-Z (case is not significant), 0-9, hyphen (-) +and underline (_). The tag name may be prefixed by a group name +(separated by a colon) to identify a specific information type or location. A +special tag name of "All" may be used to represent all tags, or all +tags in a specified group. For example:

+ +
+exiftool -filename -imagesize -exif:fnumber -xmp:all image.jpg
+
+ +

A complete list of ExifTool Tag Names +accompanies this documentation. As well, current lists of available tag names +and writable tag names may be obtained using the exiftool -list and +-listw options. But perhaps the easiest way to determine a tag name +is to use the -s option to print the tag names instead of +descriptions for all information in a file. It may also be helpful to use the +-G option to display the group names, and the -H or +-D option to print the numerical tag ID's for reference.

+ +

Notes:

+ +
  1. Tag names sometimes differ from their descriptions. Use the +-s command-line option to see the actual tag names instead of the +descriptions shown when extracting information.
  2. +
  3. When extracting information, tags will not appear in the output unless they +exist in the file, even if they are specified on the command line. The +-f option may be used to force all specified tags to be displayed +(not including tags specified with wildcards or by -GROUP:all).
  4. +
  5. Information for a given tag name may occur in multiple locations within a +single file. By default these duplicate tags are suppressed, but the -a +option may be used to extract all tags.
  6. +
  7. Tag names may be suffixed by a '#' character to disable the +print conversion on a per-tag basis. See the +-n option in the application +documentation for more information.
  8. +
+ +

Shortcut Tags

+

Shortcut tags represent one or more other tags, and are used like any other +tag when reading, writing or copying information.

+ +

ExifTool defines a few shortcut tags +in the Image::ExifTool::Shortcuts module, and allows users to define their own +shortcuts in a configuration file called +".ExifTool_config" in their home directory or exiftool application +directory. Here is a simple example that defines two shortcuts:

+ +
+%Image::ExifTool::UserDefined::Shortcuts = (
+    MyShortcut => ['createdate','exposuretime','aperture'],
+    MyAlias => 'FocalLengthIn35mmFormat',
+);
+
+ +

In this example, MyShortcut is a shortcut for the CreateDate, ExposureTime +and Aperture tags, and MyAlias is a shortcut for FocalLengthIn35mmFormat.

+ +

The current shortcuts may be listed with the -list option.

+ +

The ~/.ExifTool_config file may also be used to define new tags. +For more information about the configuration file, see the +sample configuration file included with the ExifTool +distribution.

+ +

Windows tip: You may have difficulty generating a filename beginning +with a '.' in the Windows GUI, but it can be done with the +"rename" command at the cmd.exe prompt.

+ +

Tag Groups

+ +

ExifTool classifies tags into groups in various families. Here is a list +of the group names in each family:

+ +
+ + + + + + + + + + + + + + + + + + + +
FamilyGroup Names
0 (Information Type)AFCP, AIFF, APE, APP0, APP1, APP11, APP12, APP13, APP14, APP15, APP2, APP3, +APP4, APP5, APP6, APP8, ASF, Audible, CanonVRD, Composite, DICOM, DNG, DV, +DjVu, Ducky, EXE, EXIF, ExifTool, FITS, FLAC, FLIR, File, Flash, FlashPix, +Font, FotoStation, GIF, GIMP, GeoTiff, GoPro, H264, HTML, ICC_Profile, ID3, +IPTC, ISO, ITC, JFIF, JPEG, JSON, JUMBF, Jpeg2000, LNK, Leaf, Lytro, M2TS, +MIE, MIFF, MISB, MNG, MOI, MPC, MPEG, MPF, MXF, MakerNotes, Matroska, Meta, +Ogg, OpenEXR, Opus, PDF, PICT, PLIST, PNG, PSP, Palm, Parrot, PanasonicRaw, +PhotoCD, PhotoMechanic, Photoshop, PostScript, PrintIM, QuickTime, RAF, +RIFF, RSRC, RTF, Radiance, Rawzor, Real, Red, SVG, SigmaRaw, Stim, Theora, +Torrent, Trailer, UserParam, VCard, Vorbis, WTV, XML, XMP, ZIP +
1 (Specific Location)AC3, AFCP, AIFF, APE, ASF, AVI1, Adobe, AdobeCM, AdobeDNG, Apple, Audible, +CBOR, CIFF, CameraIFD, Canon, CanonCustom, CanonDR4, CanonRaw, CanonVRD, +Casio, Chapter#, Composite, DICOM, DJI, DNG, DV, DjVu, DjVu-Meta, Ducky, +EPPIM, EXE, EXIF, ExifIFD, ExifTool, FITS, FLAC, FLIR, File, Flash, +FlashPix, Font, FotoStation, FujiFilm, FujiIFD, GE, GIF, GIMP, GPS, +GSpherical, Garmin, GeoTiff, GlobParamIFD, GoPro, GraphConv, H264, HP, HTC, +HTML, HTML-dc, HTML-ncc, HTML-office, HTML-prod, HTML-vw96, HTTP-equiv, +ICC-chrm, ICC-clrt, ICC-header, ICC-meas, ICC-meta, ICC-view, ICC_Profile, +ICC_Profile#, ID3, ID3v1, ID3v1_Enh, ID3v2_2, ID3v2_3, ID3v2_4, IFD0, IFD1, +IPTC, IPTC#, ISO, ITC, InfiRay, Insta360, InteropIFD, ItemList, JFIF, JFXX, +JPEG, JPEG-HDR, JPS, JSON, JUMBF, JVC, Jpeg2000, KDC_IFD, Keys, Kodak, +KodakBordersIFD, KodakEffectsIFD, KodakIFD, KyoceraRaw, LNK, Leaf, +LeafSubIFD, Leica, Lyrics3, Lytro, M2TS, MAC, MIE-Audio, MIE-Camera, +MIE-Canon, MIE-Doc, MIE-Extender, MIE-Flash, MIE-GPS, MIE-Geo, MIE-Image, +MIE-Lens, MIE-Main, MIE-MakerNotes, MIE-Meta, MIE-Orient, MIE-Preview, +MIE-Thumbnail, MIE-UTM, MIE-Unknown, MIE-Video, MIFF, MISB, MNG, MOBI, MOI, +MPC, MPEG, MPF0, MPImage, MS-DOC, MXF, MacOS, MakerNotes, MakerUnknown, +Matroska, MediaJukebox, Meta, MetaIFD, Microsoft, Minolta, MinoltaRaw, +Motorola, NITF, Nikon, NikonCapture, NikonCustom, NikonScan, NikonSettings, +NineEdits, Nintendo, Ocad, Ogg, Olympus, OpenEXR, Opus, PDF, PICT, PNG, +PNG-cICP, PNG-pHYs, PSP, Palm, Panasonic, PanasonicRaw, Parrot, Pentax, +PhaseOne, PhotoCD, PhotoMechanic, Photoshop, PictureInfo, PostScript, +PreviewIFD, PrintIM, ProfileIFD, Qualcomm, QuickTime, RAF, RAF2, RIFF, +RMETA, RSRC, RTF, Radiance, Rawzor, Real, Real-CONT, Real-MDPR, Real-PROP, +Real-RA3, Real-RA4, Real-RA5, Real-RJMD, Reconyx, Red, Ricoh, SPIFF, SR2, +SR2DataIFD, SR2SubIFD, SRF#, SVG, Samsung, Sanyo, Scalado, Sigma, SigmaRaw, +Sony, SonyIDC, Stim, SubIFD, System, Theora, Torrent, Track#, UserData, +VCalendar, VCard, VNote, Version0, Vorbis, WTV, XML, XMP, XMP-DICOM, +XMP-Device, XMP-GAudio, XMP-GCamera, XMP-GCreations, XMP-GDepth, XMP-GFocus, +XMP-GImage, XMP-GPano, XMP-GSpherical, XMP-LImage, XMP-MP, XMP-MP1, +XMP-PixelLive, XMP-aas, XMP-acdsee, XMP-album, XMP-apple-fi, XMP-ast, +XMP-aux, XMP-cc, XMP-cell, XMP-crd, XMP-creatorAtom, XMP-crs, XMP-dc, +XMP-dex, XMP-digiKam, XMP-drone-dji, XMP-dwc, XMP-et, XMP-exif, XMP-exifEX, +XMP-expressionmedia, XMP-extensis, XMP-fpv, XMP-getty, XMP-hdr, XMP-hdrgm, +XMP-ics, XMP-iptcCore, XMP-iptcExt, XMP-lr, XMP-mediapro, XMP-microsoft, +XMP-mwg-coll, XMP-mwg-kw, XMP-mwg-rs, XMP-nine, XMP-panorama, XMP-pdf, +XMP-pdfx, XMP-photomech, XMP-photoshop, XMP-plus, XMP-pmi, XMP-prism, +XMP-prl, XMP-prm, XMP-pur, XMP-rdf, XMP-sdc, XMP-swf, XMP-tiff, XMP-x, +XMP-xmp, XMP-xmpBJ, XMP-xmpDM, XMP-xmpMM, XMP-xmpNote, XMP-xmpPLUS, +XMP-xmpRights, XMP-xmpTPg, ZIP, iTunes +
2 (Category)Audio, Author, Camera, Device, Document, ExifTool, Image, Location, Other, +Preview, Printing, Time, Unknown, Video +
3 (Document Number)Doc#, Main +
4 (Instance Number)Copy# +
5 (Metadata Path)eg. JPEG-APP1-IFD0-ExifIFD +
6 (EXIF/TIFF Format)int8u, string, int16u, int32u, rational64u, int8s, undef, int16s, int32s, +rational64s, float, double, ifd, unicode, complex, int64u, int64s, ifd64
7 (Tag ID)ID-xxx (where xxx is the tag ID. Numerical ID's are given in hex with a +leading "0x" if the HexTagIDs API option +is set, as are characters in non-numerical ID's which are not valid in a group +name. Note that unlike other group names, family 7 group names are case +sensitive.)
8 (File Number)File# (for files loaded via -fileNUM option)
+ +

The exiftool output can be organized based on these groups using the +-g or -G option (ie. -g1 to see family 1 +groups, or -g3:1 to see both family 3 and family 1 group names in +the output. See the -g option in the exiftool application +documentation for more details, and the GetGroup +function in the ExifTool library for a description of the group families. Note +that when writing, only family 0, 1, 2 and 7 group names may be used.

+ +

Writing Meta Information

+ +

When writing information, ExifTool preserves the original file by adding +"_original" to the file name. Be sure to keep a copy of the +original, or thoroughly validate the new file before erasing the original. +(Read here for some ramblings on the subject of +writing meta information.)

+ +

Syntax

+ +

Tag values are written rather than being extracted if any tag name ends with +a '=' symbol (or if the -tagsFromFile or +-geotag options are used). The '=' may be prefixed by +'+', '-' or '<' to add a value, remove +a value or set a value from file. The following table outlines the different +write syntaxes:

+ +
+ + + + + + + + + +
SyntaxResult
-TAG=Deletes all occurrences of TAG
-all=Deletes all meta information!
-GROUP:TAG=Deletes TAG only in specified group
-GROUP:all=Deletes all information in specified group
-[GROUP:]TAG=VALUESets value of TAG (only in GROUP if specified)
-[GROUP:]TAG+=VALUEAdds item to a list, shifts a date/time, or increments a number
-[GROUP:]TAG-=VALUERemoves item from a list, shifts a date/time, or deletes TAG if it has the specified value
-[GROUP:]TAG<=FILESets tag value from contents of specified file
See the Writer Limitations for some limitations of this feature.
+ +

Quotes are required around VALUE if it contains spaces or other +special characters, and around the whole argument if the '<=' +syntax is used (to prevent shell redirection).

+ +

A special feature allows the print conversion to be disabled on a per-tag +basis by suffixing any tag name (including 'all') with the +'#' character. This has the same effect as the +-n option, but for a single tag. See the +-n option in the application +documentation for more details.

+ +

Note: Changes to PDF files are reversible because the original +metadata is never actually deleted from these files. See the +PDF Tags documentation for details.

+ +

Group Priorities

+ +

ExifTool prioritizes the following types of meta information when writing:

+ +
+1) EXIF,   2) IPTC,   3) XMP +
+ +

Many tag names are valid for more than one of these groups. If a group name is +not specified when writing information, then the information is added only to +the highest priority group for which the tag name is valid (however, the +information is updated in all groups where the tag already existed). The +priority of the groups is given by the list above. Specifically, this means that +new information is added preferentially to the EXIF group, or to the IPTC group +if no corresponding EXIF tag exists, or finally to the XMP group.

+ +

Alternatively, information may be written to a specific group only, bypassing +these priorities, by providing a group name for the tag. The +"Writing Meta Information" section above gave the syntax +rules for exiftool command-line arguments to do this. Any family 0, 1, 2 or 7 group +name may be used when writing information, although not all groups are writable.

+ +

The "-tagsFromFile" Option

+ +

A special ExifTool option allows copying tags from one file to another. The +command-line syntax for doing this is +"-tagsFromFile SRCFILE". Any tags specified after this +option on the command line are extracted from source file and written to the +destination file. If no tags are specified, then all writable tags are copied. +This option is very simple, yet very powerful. Depending on the formats of the +source and destination files, some of tags read may not be valid in the +destination file, in which case they aren't written.

+ +

This option may also be used to transfer information between different tags +within a single image or between different images. See the +-tagsFromFile +option in the application documentation for more details.

+ +

Writer Limitations

+
    +
  • ExifTool will not rewrite a file if it detects a significant problem +with the file format.
  • +
  • ExifTool has been tested with a wide range of different images, but since it +is not possible to test it with every known image type, there is the possibility +that it will corrupt some files. Be sure to keep backups of your files.
  • +
  • Even though ExifTool does some validation of the information written, it is +still possible to write illegal values which may cause problems when +reading the images with other software. So take care to validate the +information you are writing.
  • +
  • ExifTool is not guaranteed to remove metadata completely from a file +when attempting to delete all metadata. For JPEG images, all APP segments +(except Adobe APP14, which is not removed +by default) and trailers are removed which effectively removes all metadata, but +for other formats the results are less complete: +
      +
    • JPEG - APP segments (except Adobe APP14) and trailers are removed.
    • +
    • TIFF - XMP, IPTC, ICC_Profile and the ExifIFD are removed, but some EXIF may remain in IFD0. (The CommonIFD0 + Shortcut tag is provided to simplify removal of common metadata tags from IFD0.)
    • +
    • PNG - Only XMP, EXIF, ICC_Profile and native PNG textual data chunks are removed.
    • +
    • PDF - The original metadata is never actually removed.
    • +
    • PS - Only XMP and some native PostScript tags may be deleted.
    • +
    • MOV/MP4 - Most top-level metadata is removed.
    • +
    • RAW formats - It is not recommended to remove all metadata from RAW images +because this will likely remove some proprietary information that is necessary +for proper rendering of the image.
    • +
  • +
+ +

Known Problems

+
    +
  • [2022-08-23] +Sony Imaging Edge Desktop has problems displaying some ExifTool-edited ARW +images, although other software (such as Adobe utilites) have no problems with these.
  • +
  • [2020-02-18] +Hasselblad Phocus software will no longer update the small preview or +thumbnail images of FFF files edited by ExifTool. This is perhaps due to +some unreferenced preview information +in the file that is lost when edited by ExifTool, but this does not seem to have +any other effect. +[ExifTool 11.88 and later issue a minor error when attempting to +write FFF files, but this problem seems to be fixed in Phocus v3.5.4]
  • +
  • [2019-05-29] +Canon Digital Photo Professional 4 (DPP4) will destroy a CR3 image +when editing if it had previously been edited by DPP4 followed by ExifTool. +[ExifTool 11.45 fixes this by structuring the CR3 to make it safe +for editing with DPP4, and may be used to restructure files written by older +ExifTool versions.]
  • +
  • [2018-09-27] +The Sony Imaging Edge applications give an error when trying to open +ARW or ARQ images edited by ExifTool, although other RAW image +utilities including Sony IDC (Sony's older RAW image converter), Adobe +Photoshop, Lightroom and DNG Converter, Apple Preview, dcraw, Capture One, +Affinity Photo, and LibRaw's SonyPixelShift2DNG have no problems with these.
  • +
  • [2016-08-03] +Some antivirus software has been known to cause problems for the Windows +version of ExifTool. Norton Antivirus may delete ExifTool when it is run, +Windows Defender may slow down launching of ExifTool or hang it altogether, +and Bitdefender Antivirus may block ExifTool from writing files. +Presumably this is due to the way the ExifTool package for Windows works -- it +unpacks executable files into a temporary directory and runs from there, which +apparently may be seen as a threat by antivirus software. A work-around is to +add ExifTool to the exclusion list of the antivirus software.
  • +
  • [2016-05-27] +Adobe Camera Raw and DNG Converter 9.5.1 fail to recognize edited +Samsung SRW images from some models (NX30, NX300, NX2000 and EK-GN120). +[This problem was fixed for the NX models in ExifTool 10.26, and +writing of EK-GN120 files was disabled in ExifTool 10.95]
  • +
  • In Windows, ExifTool will not process files with Unicode characters +in the file name. This is due to an underlying lack of support for Unicode +filenames in the Windows standard C I/O libraries. [This deficiency +was addressed in ExifTool 9.79, and ExifTool now supports Windows Unicode file +names with some exceptions. See the +WINDOWS UNICODE FILE NAMES +section of the application documentation for details.]
  • +
  • [2013-11-08] +Apple Spotlight and Preview (OS X 10.8.5) and Adobe Photoshop +CC (version 14.0) ignore XMP in PNG images if it comes after the image +data, which is where ExifTool adds new XMP. This should be considered as a bug +in the Apple and Adobe software since XMP is allowed to exist after the image +data according to the XMP and PNG specifications. [ExifTool 9.40 +provides the PNGEarlyXMP API option to allow writing XMP before the IDAT chunk, +but there are caveats associated with its use. ExifTool 11.58 and later remove +this option and always write XMP before IDAT, and 11.63 and later write all +text chunks before IDAT.]
  • +
  • [2013-04-21] +Memory available to ExifTool in the Windows EXE version is limited to +a few hundred MB. This limitation has been known to cause unreasonably long +processing times (almost 7 minutes) for some large EPS files (> 200 MB) which +are processed much faster by the Perl version (< 6 seconds).
  • +
  • [2010-01-12] +There is a bug in a number of Adobe utilities which causes some +edited Sony ARW images to be displayed with the wrong tone curve. This +problem has been observed in Photoshop CS4 Camera Raw 5.6, DNG Converter 5.6 and +Lightroom 2.6 with ARW images from the A500, A550, A700, A850 and A900. Other +software such as the Sony IDC utility, Apple RAW utilities, dcraw and Capture +One have no problems with edited images. [This bug is fixed in +Camera Raw 6.3 and LR 3.3]
  • +
  • [2007-07-06] +There is a bug in the Apple RAW file support (OS X 10.4.11) which +prevents some edited Pentax PEF images from being displayed properly. +Other software such as the Pentax Silkypix software and dcraw have no problems +with these images. [This bug is fixed in OS X 10.5.4]
  • +
+ +

Security Issues

+

Some ExifTool options (-config, -if, +-p, -fileNUM, -api filter, +-api filterw and copying arguments like "-DSTTAG<STR") +have the ability to execute Perl code from external files or within command-line +arguments. This may be a security problem if ExifTool is executed from another +application that blindly passes untrusted file names on the command line (since +they may be interpreted as ExifTool options if they begin with a dash). To be +secure the calling application must either place file names after the +"--" option, or ensure that input file names do not start with a +dash (U+002D) or a Unicode minus sign (U+2212). One way to accomplish this is +to prefix input file names with a known directory name, eg.) +"./FILENAME".

As well, untrusted window title strings should +not be used with the -progress:TITLE option.

+ +

Date/Time Shift Feature

+ +

Have you ever forgotten to set the date/time on your digital camera before +taking a bunch of pictures? ExifTool has a time shift feature that makes it +easy to apply a batch fix to the timestamps of the images (eg. change the "Date +Picture Taken" reported by Windows Explorer). Say for example that your camera +clock was reset to 2000:01:01 00:00:00 when you put in a new battery at +2005:11:03 10:48:00. Then all of the pictures you took subsequently have +timestamps that are wrong by 5 years, 10 months, 2 days, 10 hours and 48 +minutes. To fix this, put all of the images in the same directory +("DIR") and run exiftool:

+ +
+exiftool "-DateTimeOriginal+=5:10:2 10:48:0" DIR
+
+ +

The example above changes only the DateTimeOriginal tag, but any writable date +or time tag can be shifted, and multiple tags may be written with a single +command line. Commonly, in JPEG images, the DateTimeOriginal, CreateDate and +ModifyDate values must all be changed. For convenience, a +Shortcut tag called AllDates has been defined to +represent these three tags. So, for example, if you forgot to set your camera +clock back 1 hour at the end of daylight savings time in the fall, you can fix +the images with:

+ +
+exiftool -AllDates-=1 DIR
+
+ +

See +Image::ExifTool::Shift.pl +(download in PDF format) +for details about the syntax of the time shift string.

+ +

Note: Not all date/time information is covered by the AllDates +shortcut. Specifically, the filesystem date/time tags are not included, and this +command will reset FileModifyDate to the current date/time as it should when the +file is modified, unless either the -P option is used, or +FileModifyDate is set to something else. To shift FileModifyDate along with the +other tags, add -FileModifyDate-=1 to the command above.

+ +

Renaming and/or Moving Files

+ +

By writing a new value to the FileName and/or Directory tags, +files can be renamed and/or moved to different directories. This can be a very +powerful tool in combination with the -d (date format) option for +organizing images by date/time. For example, the following command renames all +images in directory "DIR" according to the individual file's +creation date in the form "YYYYmmdd_HHMMSS.ext".

+ +
+exiftool "-FileName<CreateDate" -d "%Y%m%d_%H%M%S.%%e" DIR
+
+ +

Or a new directory can be specified by setting the value of the Directory +tag. For example, the following command moves all images originally in +directory "DIR" into a directory hierarchy organized by +year/month/day:

+ +
+exiftool "-Directory<DateTimeOriginal" -d "%Y/%m/%d" DIR
+
+ +

Read here for more details about this powerful +feature.

+ +

Improving Performance

+ +

There is a significant overhead in loading ExifTool, so performance may be +greatly improved by taking advantage of ExifTool's batch processing capabilities +(the ability to process multiple files or entire directories with a single +command) to reduce the number of executed commands when performing complex +operations or processing multiple files. +[One exiftool user + +documented a 60x speed increase by processing a large number of files with a single +command instead of running exiftool separately on each file.] Also, the +-execute option may be used to perform multiple independent +operations with a single invocation of exiftool, and together with the +-stay_open option provides a method for calling applications to +avoid this startup overhead.

+ +

It has also been observed that the loading time of ExifTool for Windows +increases significantly when Windows Defender is active. Disabling Windows +Defender may speed things up significantly.

+ +

The processing speed of ExifTool can be improved when extracting information +by reducing the amount of work that it must do. Decrease the number of +extracted tags by specifying desired tags individually (-TAG) or by +group (-GROUP:all), and disable the composite tags (-e) +and the print conversions (-n) if these features aren't required. +Note that the exclude options (-x or --TAG) are not +very efficient, and may have a negative impact on performance if a large number +of tags are excluded individually. The exception is XMP groups, which are +bypassed in processing so they are never even extracted -- specifying +--XMP-crs:all and -XMP-crd:all may speed processing +significantly by avoiding processing of bulky Adobe image-editing information. +The API IgnoreTags option (added in +ExifTool 12.43) may also be used for significant speed benefits in some cases.

+ +

The -fast option can significantly increase speed when +extracting information from JPEG images which are piped across a slow network +connection. However, with this option any information in a JPEG trailer is not +extracted. For more substantial speed benefits, -fast2 may be +used to also avoid extracting MakerNote information if this is not required, +or -fast4 if only pseudo System tags are required.

+ +

When writing, avoid copying tags (with -tagsFromFile) or using +the -if or -fileOrder option because these will add +the extra step of extracting tags from the file. Without these the write +operation is accomplished with a single pass of each file.

+ +
However, +note that when the -csv option is used, information from all files +is buffered in memory before the CSV output is written. This may be very memory +intensive and result in poor performance when reading a large number of files in +a single command.
+ +

The Image::ExifTool Perl Library Module

+ +

The "exiftool" script provides a command-line interface to the +Image::ExifTool Perl library module which is part of the ExifTool distribution. +The Image::ExifTool module can be used in any Perl script to provide easy access +to meta information. Here is an example of a very simple script that uses +Image::ExifTool to print out all recognized meta information in a file:

+ +
+#!/usr/bin/perl -w
+use Image::ExifTool ':Public';
+my $file = shift or die "Please specify filename";
+my $info = ImageInfo($file);
+foreach (keys %$info) {
+    print "$_ : $info->{$_}\n";
+}
+
+ +

Note that some tag values may be returned as SCALAR references indicating +binary data. The simple script above does not handle this case.

+ +

See the Image::ExifTool Documentation for more details.

+ +

Additional Documentation and Resources

+ + +

User-contributed Documentation

+ + +

Related Utilities

+ +

Below are some free utilities which take advantage of the ExifTool engine:

+ +

Windows

+ + +

MacOS

+
    +
  • Download +a stand-alone PPC droplet to extract preview images from RAW files (thanks to Brett Gross)
  • +
  • Download +three droplets to extract information [exiftool must be installed] (thanks to Rob Lewis)
  • + +
  • MacMetaMod: Droplet for adding Keywords to images
  • +
  • GPSPhotoLinker: Geotagging on the Mac
  • + +
  • PhotoGPSEditor and PhotoInfoEditor: Geocoding utilities
  • +
  • MetaDataMover (source): GUI-based automator utility for moving/renaming images
  • +
  • CS1ToCR2: Utility that uses Sony GPS-CS1 log files to add GPS information to CR2 images
  • + +
  • Geotagger: Droplet for inserting GPS coordinates in your photos
  • +
  • Raw Photo Processor: Raw converter for MacOS
  • +
  • GraphicConverter: Full-featured image editor [noteworthy, but not free]
  • +
  • GeoTag: Geotagging application for iPhone and MacOS
  • +
  • ImageFuser: Fuses multiple exposures of a scene into one image with improved detail/exposure
  • +
  • GeoNamesTagger: Docklet to easily update image metadata with location specific information
  • +
  • SetEXIFData: Add/modify EXIF data in images
  • +
  • GeoTagster: Geotagging from GPX files ($0.99 paid app)
  • +
  • Exif Photoworker: Smart rename and organize your photos and videos in a few clicks
  • +
  • MetaImage: Reads, writes and edits your image metadata in a familiar interface
  • +
+ +

Linux

+
    +
  • rawimage: A kfile plugin and thumbnail image handler for RAW formats
  • + + +
  • Hugin: Panorama photo stitcher
  • +
  • FotoPreProcessor: PyQt4-based frontend for exiftool to graphically edit metadata
  • +
  • ExZenToo: Script for basic ExifTool GUI using Zenity
  • +
  • PDFMted: A set of bash scripts for easy viewing and editing of PDF metadata
  • +
  • exiftool-zsh-completion: zsh completion for exiftool
  • +
  • Image MetaWriter: Batch processing Linux command-line program for adding metadata to images
  • +
+ +

Android

+ + +

Multi-Platform

+
    +
  • ImageIngester: Windows and MacOS image workflow automator
  • +
  • gpsPhoto: Geotag your images from a GPS (GPX) track log
  • +
  • renrot: Perl utility to perform various processing tasks on images
  • +
  • GPicSync: Windows/Linux utility to geocode photos from a GPX track log and create KML files
  • +
  • FlickFleck: Tool to transfer images from memory card, rotate, rename, and organize by date
  • + +
  • Geotag: Open source Java-based geotagging application
  • +
  • PhotoGrok: Java-based GUI front-end for ExifTool to display images organized by any EXIF tag
  • +
  • XnView: View and convert graphic files
  • +
  • Mapivi: Open-source and cross-platform picture manager
  • +
  • ResourceSpace: Open source digital asset management system
  • +
  • fix_corrupted_nef: Utility to fix Nikon D4/D600/D800/D800E NEF images corrupted by Nikon Transfer 1
  • +
  • pyExifToolGUI: Python-based graphical frontend for ExifTool
  • +
  • jExifToolGUI: Java-based graphical frontend for ExifTool
  • +
  • MDQC: AVPreserve tool for metadata quality control across large numbers of digital assets
  • +
  • FastPhotoTagger: Add metadata to images (requires Java runtime engine)
  • +
  • Digi-libris: Metadata centric software for the automatic organization of your own catalogue
  • +
  • FreezeFrame: Photo/video library manager (requires Java 8)
  • +
+ +

Online

+ + +

Lightroom Plugins

+ + +

Programming

+
    +
  • C++ ExifTool: Performance-oriented C++ interface for the exiftool application (by Phil Harvey)
  • +
  • Download sample AppleScript to extract tags into AppleScript record (thanks to Rob Lewis)
  • +
  • Download example of a simple Visual C++ wrapper for exiftool (thanks Mark Borg and 黃瑞昌)
  • +
  • Download C# version of simple exiftool wrapper (thanks Willem Semmelink)
  • +
  • Example C# code for running ExifTool with the -stay_open option
  • +
  • Download Visual Basic 6.0 example script v1.01 for reading tags with exiftool (thanks Michael Wandel)
  • +
  • Sample VB.NET subroutine to extract a preview image (thanks Claus Beckmann)
  • +
  • Sample VBA for Mac code to extract start timecode from a WAV file (thanks Adam Newns)
  • +
  • tagInfoSql: SQLite database of ExifTool tag repository, including Perl script (thanks Wernfried)
  • +
  • ExifToolIO: .NET wrapper for ExifTool, optimized for speed (using VB.NET)
  • +
  • ExifToolWrapper: .NET wrapper for ExifTool (using C#)
  • +
  • MiniExiftool: Ruby library wrapper for ExifTool
  • +
  • exiftoolr: Ruby wrapper for ExifTool
  • +
  • pyexiftool: Python wrapper for ExifTool
  • +
  • PyExifInfo: Another Python wrapper for ExifTool
  • +
  • PHPExiftool: PHP wrapper for ExifTool (in development)
  • +
  • ExifTool_PHP_StayOpen: ExifTool PHP fast processing script using -stayOpen and Gearman
  • +
  • Moss: Collection of Java utilities which includes an exiftool interface
  • +
  • im4java: Java interface to ImageMagick, ExifTool, and other image utilities
  • +
  • Java ExifTool: Enhanced Java Integration for ExifTool
  • +
  • J-ExifTool: Open-source, cross platform Java7 library to read/write Exif tags in images
  • +
  • exiftool-vendored: Blazing-fast, cross-platform Node.js access to ExifTool
  • +
  • How to call ExifTool from Delphi, by Bogdan Hrastnik
  • +
  • Qt5 and Qt6 interface for ExifTool, by Philippe Vianney-Liaud
  • +
  • exiftoolr: ExifTool functionality from R
  • +
+ +

Other Links

+ + +

Boldly Go where No Man has Gone Before...

+ +

There is still much unknown information in the maker notes for many camera +models. (To see this information, run exiftool with the -U +option.) In this area, ExifTool is very much a collaborative effort, and +development relies heavily on the input from camera owners to help decode new +meta information. If you manage to figure out what any of it means, send me an +e-mail (philharvey66 at gmail.com) and I'll add your new discoveries to +ExifTool. Many thanks to all who have helped so far...

+ +

Acknowledgements

+ +

Thanks to everyone who has sent in bug reports, comments, or suggestions, and +special thanks to the following people for their valuable input and/or additions +to the code:

+ +
    +
  • Malcolm Wotton for his help with the D30 Custom Functions
  • +
  • David Anson for his help sorting out binary file problems on Windows
  • +
  • Leon Booyens for his suggestions
  • +
  • Dan Heller for his bug reports, detailed suggestions and guidance
  • +
  • Wayne Smith for his help figuring out the Pentax maker notes
  • +
  • Michael Rommel for his bug fixes and additions to the Canon maker notes
  • +
  • Joseph Heled for help figuring out some of the Nikon D70 maker notes
  • +
  • Joachim Loehr for adding the Casio type 2 maker notes
  • +
  • Greg Troxel for his suggestions and for adding ExifTool to pkgsrc
  • +
  • Thomas Walter for figuring out some Nikon tags
  • +
  • Brian Ristuccia for more information about some Nikon tags
  • +
  • Christian Koller for decoding the 20D custom functions
  • +
  • Matt Madrid for his testing and feedback
  • +
  • Tom Christiansen for his help decoding some Nikon tags
  • +
  • Markku Hänninen for help decoding tags for the Olympus E-1
  • +
  • Frank Ledwon for decoding many new Olympus tags
  • +
  • Robert Rottmerhusen for decoding many tricky Nikon lens data tags
  • +
  • Michael Tiemann for decoding a number of new Canon tags
  • +
  • Albert Bogner for his image samples, testing and useful suggestions
  • +
  • Rainer Hönle for decoding a number of new Canon 5D tags
  • +
  • Nilesh Patel for his help with the web page layout
  • +
  • Jens Duttke for his suggestions, bug reports and help decoding new tags
  • +
  • Dave Nicholson for decoding new tags in Pentax and Canon maker notes
  • +
  • Bogdan Hrastnik for his feedback, decoding efforts, user support and ExifTool GUI
  • +
  • Igal Milchtaich for decoding many Sony A100 tags
  • +
  • Laurent Clévy for his work analyzing Canon RAW images
  • +
  • Warren Hatch for decoding many Nikon tags
  • +
  • Jos Roost for decoding many Sony tags for various models
  • +
  • Iliah Borg and LibRaw for decoding many raw development tags
  • +
  • Bryan K. Williams and Hayo Baan for their help with the ExifTool Forum
  • +
+ +

License

+ +

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

+ +

Donate

+ +

ExifTool is free, but due to popular request I am providing a way for +those who feel the need to send me some money. It is really not necessary, +but thank you very much if you decide to make a contribution:

+
+ + + + +
+
+ + + + + + +$ + +
+
+
+

(Your generous donations have provided the funds used to register +exiftool.org and pay for web site hosting, and for the Mac Mini used to generate +distribution files, run the necessary Windows virtual machine, and maintain +source-code and forum backups.)

+ +

Phil's Background

+ +

Phil has a master's degree in nuclear physics and is now officially retired +from his position at Queen's University where he worked with the Nobel-prize-winning +Sudbury Neutrino Observatory (SNO) team +and other SNOLab experiments from 1990 to 2020. For SNO he wrote the software +to aquire, format, store and display all of the hundreds of terabytes of data +generated by the detector; software that continues to be used by +SNO+ for their even greater data +volume.

+ +

ExifTool started as a simple utility used to display metadata from +images hosted on the SNO web site, but quickly expanded in scope as Phil got +involved with digital photography beginning in 2001. During retirement Phil +continues to enjoy digital photography, a hobby which he now applies to bird +watching. A selection of his birding pictures may be found in his recent +Birds of Kingston +book.

+ +

Contact Me

+ +

If you have any comments, suggestions or questions, please post to the +ExifTool Forum so +other people may benefit from your experiences. (I check the forum at least as +often as my email.) Otherwise, if you must contact me directly, my e-mail +address is on the first line of the README file in the full distribution. +Thanks.   - Phil Harvey

+ + + + diff --git a/ExifTool/html/install.html b/ExifTool/html/install.html new file mode 100644 index 0000000..406c981 --- /dev/null +++ b/ExifTool/html/install.html @@ -0,0 +1,266 @@ + + + +Installing ExifTool + + + +

Installing ExifTool

+ +
Note: ExifTool does not need +to be installed to run. Just download and extract either the full Perl +distribution on Mac/Linux, or the Windows EXE version on Windows, and run it +directly. [But note that if you move the Perl "exiftool" +application, you must also move its "lib" directory to the same location. This +doesn't apply to the Windows version which unpacks the libraries into a +temporary directory.]

However, the benefits of installation are: +
  • Makes ExifTool available to all users.
  • +
  • Saves typing on the command line (by placing "exiftool" in your PATH).
  • +
  • Installs the ExifTool documentation and API libraries (full Perl version only).
  • +
+ +

See the appropriate section below with instructions for installing or +uninstalling ExifTool on your specific platform:

+ + + +

Also see these instructions for help running +ExifTool.

+ +
+

Windows

+ +

In Windows, there is a choice of two different versions of ExifTool to +install. The Perl distribution requires Perl to be installed on your system. +(A good, free Perl interpreter can be downloaded from +activeperl.com.)

+

If you don't already have Perl, it is easier to install the stand-alone ExifTool +executable, but note that the stand-alone version doesn't include the HTML +documentation or some other files of the full distribution.

+ +

Stand-Alone Executable

+
    +
  1. Download the Windows Executable from the ExifTool home page. +
    (The file you download should be named "exiftool-12.67.zip".)
  2. +
  3. Extract "exiftool(-k).exe" from the +".zip" file, and place it on your Desktop. +
    (Double-click on "exiftool-12.67.zip" to open +the archive, then drag "exiftool(-k).exe" to your Desktop.)
  4. +
+

You can now double-click on "exiftool(-k).exe" to read the +application documentation, or drag-and-drop files and folders to run exiftool on +selected files. To install exiftool for use from the command line, +continue with the following steps:

+
    +
  1. Rename "exiftool(-k).exe" to "exiftool.exe". +
    (or "exiftool(-k)" to "exiftool" if file name +extensions are hidden on your system)
  2. +
  3. Move "exiftool.exe" to the "C:\WINDOWS" directory +(or any other directory in your PATH).
  4. +
+

You can now run exiftool by typing "exiftool" at the command +prompt. (To get to the command prompt, select "Run..." from the Windows "Start" +menu, then type "cmd" and press Return.)

+

Notes:

+
    +
  1. In Windows 7, running exiftool may require administrator privileges. If +necessary, this may be enabled by right clicking on exiftool, then selecting +"Run this program as administrator" from the Compatibility settings.
  2. +
  3. Windows 10 users have sometimes reported that exiftool hangs when run. +This may be due to Windows Defender blocking or slowing down exiftool. To solve +this, either disable Windows Defender or add an exclusion for exiftool (but note +that exiftool may still run more slowly if you just add an exclusion).
  4. +
  5. Occasionally users have reported that exiftool gives errors when running +this version for this first time. It is possible this may happen if the +installation process gets interrupted. In this case, follow the Uninstalling +instructions below then re-install exiftool. Be sure that you have sufficient +disk space in your TEMP directory for exiftool to unpack about 12 MB of +temporary files -- these are the Perl libraries used by the exiftool application +which are unpacked the first time exiftool is run.
  6. +
+

Uninstalling:

+
    +
  1. Drag "exiftool(-k).exe" (or "C:\WINDOWS\exiftool.exe") +into the Recycle bin.
  2. +
  3. Drag the directory "par-XXX" from your temporary directory to +the Recycle bin. Here "XXX" is your user name (in ASCII-hex for +ExifTool 10.21 or later) and the location of the temporary directory depends on +the value of the TEMP environment variable (typically +"C:\Documents and Settings\USER\Local Settings\Temp" +for Windows XP, or "C:\Users\USER\AppData\Local\Temp" for Windows 7 +or later). Alternately, this command may be used to delete these files: +
    for /D %d in (%TEMP%\par-*) do rmdir /s /q %d
    +
  4. +
+

Full Perl Distribution

+

You must have Perl installed to use this version. (A free version of Perl +can be downloaded from +activeperl.com.)

+
    +
  1. Download the Image-ExifTool distribution from the ExifTool home page +
    (The file you download should be named "Image-ExifTool-12.67.tar.gz".)
  2. +
  3. Extract the ExifTool files from the archive. +
    (The archive is a gzipped tar file, and can be opened with +various Windows utilities, including WinZip.)
  4. +
  5. Rename "exiftool" to "exiftool.pl" +in the exiftool distribution.
  6. +
  7. Move "exiftool.pl" and the "lib" +directory from the exiftool distribution to "C:\WINDOWS" +(or any other directory in your PATH).
  8. +
+

Now, if you have made the proper Windows associations for the +".pl" extension (an option in the ActivePerl installation), you +can run exiftool by typing "exiftool.pl" at the +"cmd.exe" prompt. Otherwise you should type +"perl c:\windows\exiftool.pl".

+

Uninstalling:

+
    +
  1. Drag "C:\WINDOWS\exiftool.pl" and +"C:\WINDOWS\lib" into the Recycle bin. You should first confirm +that "C:\WINDOWS\lib" contains only the "File" +and "Image" sub-directories. Do not delete it if it +contains anything else.
  2. +
+ +
+

MacOS

+ +

If you have installed the BSDSDK package from the Xcode Developer Tools +(ie. if you have the "make" utility), you should +follow the install procedure for Unix platforms in the next +section instead of the steps below. The Unix install has the advantage of making +the ExifTool library available for your Perl scripts, as well as installing the +man pages and POD documentation.

+

Otherwise, you have a choice of two packages to install: The MacOS package, or +the full Perl distribution. Both of the procedures below install the ExifTool +files in the same location. Installing from the MacOS package is easier, but the +full distribution includes HTML documentation and some other files not included +in the MacOS package. Both versions run natively on PPC and Intel Macs.

+ +

MacOS Package

+
    +
  1. Download the ExifTool MacOS Package from the ExifTool home page. +
    (The file you download should be named "ExifTool-12.67.dmg".)
  2. +
  3. Install as a normal MacOS package. +
    (Open the disk image, double-click on the install package, and follow the instructions. +See the second item in the Notes section below if you are stopped with an "unidentified developer" message.) +
  4. +
+

You can now run exiftool by typing "exiftool" in a Terminal window.

+

If this doesn't work, then it is likely you have an older version of MacOS for which +/usr/local/bin isn't in the default PATH. To fix this, add the following line to your +~/.profile settings using a text editor:

+
export PATH=$PATH:/usr/local/bin
+ +

Full Perl Distribution

+
    +
  1. Download the Image-ExifTool distribution from the ExifTool home page +to your Desktop. +
    (The file you download should be named "Image-ExifTool-12.67.tar.gz".)
  2. +
  3. Launch the Terminal application from the Utilities folder in your Applications folder.
  4. +
  5. In the Terminal window, type the following: +
        cd ~/Desktop
    +    tar -xzf Image-ExifTool-12.67.tar.gz
    +    cd Image-ExifTool-12.67
    +    sudo cp -r exiftool lib /usr/local/bin
    +
    +(Note: The last step above will require you to enter your +password.)
  6. +
+

You can now run exiftool by typing "exiftool" in a Terminal window.

+ +

Notes:

  • Both MacOS installation techniques outlined above place +exiftool and its lib directory in /usr/local/bin, while the standard Unix +"make install" described below puts "exiftool" in +/usr/local/bin and the individual libraries in /Library/Perl/#.#.#, where "#.#.#" is +your Perl version. If both sets of libraries exist, /usr/local/bin/lib takes +precedence for exiftool, but /Library/Perl/#.#.# is the default for any other +Perl scripts.
  • +
  • In MacOS 10.8 or later, you may see this message when you try to open the install package: +
    "ExifTool-12.67.pkg" can't be opened because it is from an +unidentified developer.
    The solution is to control-click on the pkg +then select "Open" from the pop-up menu instead of just double-clicking. An alternative +is to lower the security settings by changing "Allow applications downloaded from" to +"Anywhere" in the General "Security & Privacy" System Preferences. +(Read here or +here +for a full description.)
+ +

Uninstalling

+
    +
  1. Launch the "Terminal" application from the Applications Utilities folder.
  2. +
  3. Type "open /usr/local/bin" (without the quotes) in the Terminal +window, then press RETURN. (This opens a folder that you normally can't access +from MacOS.)
  4. +
  5. Drag "exiftool" and "lib" into the trash from the +"bin" folder you opened. You should first confirm that +"lib" contains only two sub-folders: "File" and +"Image". If it contains anything else, don't trash it because you +have the wrong "lib" folder.
  6. +
+ +
+

Unix Platforms

+ +
    +
  1. Download the Image-ExifTool distribution from the ExifTool home page +
    (The file you download should be named "Image-ExifTool-12.67.tar.gz".)
  2. +
  3. Unpack the distribution and make it your current directory by typing: +
        cd <your download directory>
    +    gzip -dc Image-ExifTool-12.67.tar.gz | tar -xf -
    +    cd Image-ExifTool-12.67
    +
    +(At this point you may run exiftool by typing +"./exiftool <image file name>".)
  4. +
  5. Test and install ExifTool by typing: +
    +    perl Makefile.PL
    +    make test
    +    sudo make install
    +
    +(Note: The "make test" step is not required, but +useful because it runs a full suite of tests to verify that ExifTool is working +properly on your system. The "sudo make install" command requires +that you have su access, and will prompt for your password. This will make +ExifTool and its documentation accessible to all users on your system. If you +don't have su access, you can run ExifTool in your own account by moving +"exiftool" and its "lib" directory to any convenient +location, preferably somewhere in your PATH.)
  6. +
+

You can now run exiftool by typing "exiftool". Also, you can +consult the ExifTool documentation with commands like:

+
perldoc exiftool
+perldoc Image::ExifTool
+perldoc Image::ExifTool::TagNames
+
+

or

+
man exiftool
+man Image::ExifTool
+man Image::ExifTool::TagNames
+
+ +

Uninstalling

+
    +
  1. Type "sudo make uninstall" from the distribution directory. +
    (Note: Unfortunately, newer systems may give an "Uninstall +is unsafe and deprecated" message even though uninstalling ExifTool is safe +because it has no dependencies. If this happens, the necessary commands to +remove the installed files will be listed, and these commands must be run +manually.)
  2. +
+ +
+

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/metafiles.html b/ExifTool/html/metafiles.html new file mode 100644 index 0000000..00cc69b --- /dev/null +++ b/ExifTool/html/metafiles.html @@ -0,0 +1,323 @@ + + + + Metadata Sidecar Files + + + + + + +

Metadata Sidecar Files

+ +

Metadata for images and other file types may be stored in a separate metadata +file. These are the only files that exiftool can create from scratch. A common +example of this is the XMP "sidecar" file (which is discussed in the next +section in some detail). Other supported metadata file types are EXIF, MIE, +EXV, ICC and VRD. As well, ExifTool supports XML-format output, which can also +be used to generate metadata sidecar files.

+ +
+ +

XMP Sidecar Files

+ +

There are a number of different ways to generate an XMP sidecar file with +exiftool, and the method you choose depends on your circumstances and +preferences. Below are a number of example commands which write an output XMP +file from information in a source file of any type.

+ +

1. Copy same-named tags from all information types to preferred locations in XMP:

+

(SRC.EXT is the source file name and +extension, and DST is the destination file name)

+
exiftool -tagsfromfile SRC.EXT DST.xmp
+ +

2. Rewrite source file to destination XMP file:

+

(same effect as above, but the command will exit with an error if the output XMP file already exists)

+
exiftool SRC.EXT -o DST.xmp
+ +

3. Copy XMP, preserving original locations:

+

(ie. copies XMP tags only to the same namespaces in the destination file)

+
exiftool -tagsfromfile SRC.EXT -all:all DST.xmp
+

Advanced: Notice that -all:all is used above instead of +-xmp:all even though only XMP tags will be copied (since the destination +is an XMP file). This is because -all:all preserves the family 1 group +(ie. XMP namespace) while -xmp:all would copy tags to the preferred XMP +namespace, which may be different for XMP tags that exist in multiple namespaces. +To get the best of both worlds, "-all:all<xmp:all" may be used to +avoid the inefficiencies of assigning tags which aren't copied, while still +preserving the family 1 group.

+ +

4. Rewrite source to XMP file, preserving locations:

+

(same effect as above, but the command will fail if the XMP file already exists)

+
exiftool SRC.EXT -o DST.xmp -all:all
+ +

5. Generate XMP from EXIF and IPTC using standard tag name mappings:

+

(the .args files are available in the full ExifTool distribution)

+
exiftool -tagsfromfile SRC.EXT -@ exif2xmp.args -@ iptc2xmp.args DST.xmp
+ +

6. Copy XMP as a block to an XMP file:

+

(writing as a block is the only way to transfer unknown or non-writable XMP tags)

+
exiftool -tagsfromfile SRC.EXT -xmp DST.xmp
+

Note that this will not deal with extended XMP segments in JPEG +images if they exist.

+ +

7. Extract XMP as a block and write to output XMP file: (same effect as above)

+
exiftool -xmp -b SRC.EXT > DST.xmp
+

As with the previous command, this command will not copy extended XMP +segments in JPEG images, but in this case the -a option may be +added to also extract extended XMP blocks. However, the result would be a +non-standard XMP file that ExifTool could read but other utilities may not.

+ +

8. Extract XMP as a block to an output text file with .xmp extension:

+

(same effect as above, but the destination file name will be the same +as the source file, and this command will fail if the XMP file exists while the +previous command will overwrite an existing file)

+
exiftool -xmp -b -w xmp SRC.EXT
+

The advantage of this command is that it may be applied to multiple +source files or entire directories.

+ +

9. Restore all XMP tags from an XMP sidecar file to XMP in a JPG image:

+
exiftool -tagsfromfile SRC.xmp -all:all DST.jpg
+ +

10. Restore XMP as a block from an XMP sidecar file to a JPG image:

+

(same effect as above except that any non-writable XMP tags would be +copied by this command, and the 2 kB of padding recommended by the XMP +specification is not added when copying as a block)

+
exiftool -tagsfromfile SRC.xmp -xmp DST.jpg
+

or equivalently

+
exiftool "-xmp<=SRC.xmp" DST.jpg
+ +

Batch Processing

+ +

Multiple files may be processed in a single command by specifying multiple +file and/or directory names on the command line. The examples below demonstrate +how to process all files with a specific extension in an entire directory tree.

+ +

11. Create XMP sidecar files for all files with extension EXT in a +directory tree:

+

(when batch-generating sidecar files from many images, the -o +form of the command is easier to use, but can not be used to modify +existing XMP files)

+
exiftool -ext EXT -o %d%f.xmp -r DIR
+

where DIR is the name of the directory containing +the images. The -r option causes sub-directories to be recursively +processed. Multiple -ext options may be used to process different +file types in a single command. With this command, same-named tags from any type +of metadata will be written to the preferred XMP namespace in the output XMP +file. To copy only XMP tags, -xmp:all may be added to the command. +(See example 14 for more about this.)

+ +

12. Copy tags to sidecar files that already exist:

+

(same as above, but copies only to existing XMP files)

+
exiftool -ext xmp -tagsfromfile %d%f.EXT -r DIR
+

This command will add tags from the source files to information that +already exists in the XMP files, but note that this command searches for the XMP +files instead of the image files, so it will not generate new XMP sidecar files +if some images don't have them. For this, the advanced (ie. tricky and +confusing to use) -srcfile option comes in handy:

+ +

13. Copy tags to sidecar files, generating new files if necessary:

+

(same as above, but also creates new XMP files if they don't exist)

+
exiftool -ext EXT -tagsfromfile @ -srcfile %d%f.xmp -r DIR
+

Note that as with the previous two commands, this command will +commute metadata from other groups to the preferred location in XMP.

+ +

14. Copy only XMP tags to the same namespace in sidecar files:

+

(same as above, but copies only XMP and preserves specific tag locations)

+
exiftool -ext EXT -tagsfromfile @ "-all:all<xmp:all" -srcfile %d%f.xmp -r DIR
+

In this command, if "-xmp:all" was used instead of +"-all:all<xmp:all", then all XMP tags would have been copied to +their preferred namespaces in the sidecar file. But by writing to the +destination group of "all", the specific location (ie. XMP +namespace) of each tag is preserved.

+ +

15. Copy XMP from sidecar files back to the same locations in the source files:

+

(the inverse of the previous command)

+
exiftool -ext EXT -tagsfromfile %d%f.xmp -all:all -r DIR
+

Here, +-all:all copies all metadata (in this case only XMP, since the +sidecar XMP file contains no other types) to the same specific locations in the +target files (extension EXT).

+ +

16. Write a tag to XMP sidecar if it exists, or the original file otherwise:

+
exiftool -ext EXT -artist="Phil" -srcfile %d%f.xmp -srcfile @ DIR
+

When multiple -srcfile options are used, the first +existing file is processed. If none of the specified source files exists, then +the first one in the list is created (however, this won't happen with this +example since one of the specified source files is "@", which +represents the original file name).

+ +

17. Create XMP sidecar file in another directory:

+
exiftool -ext EXT -o DSTDIR/%f.xmp -r SRCDIR
+

By specifying a directory name instead of %d, this +command writes XMP files to DSTDIR instead of the original +source directory. The same technique may be used in any of the above commands to +write XMP to a sidecar file in a different directory.

+ +

Via the API

+ +

By specifying different tags in the +SetNewValuesFromFile +call, the above examples numbered 1-6 are programmed like this:

+ +
+$exifTool->SetNewValuesFromFile('SRC.EXT', @tags_to_copy);
+$exifTool->WriteInfo(undef, 'DST.xmp');
+
+ +

and examples 7 and 8 use this general technique:

+ +
+my $info = ImageInfo('SRC.EXT', 'xmp');
+die "No XMP" unless $$info{XMP};
+open FILE, '>DST.xmp';
+print FILE ${$$info{XMP}};
+close FILE;
+
+ +
+ +

ExifTool XML Files

+ +

Closely related to the XMP sidecar file is the XML file written using the +exiftool -X option. This file is RDF/XML format like XMP, but uses +exiftool-specific namespaces to give an exact mapping for all exiftool tag +names. This type of file is better suited to general information +storage/recovery since it facilitates copying of more original metadata than an +XMP file, but it doesn't have the portability of an XMP file or the ability to +store native-format data like a MIE or EXV file, and ExifTool can not be used to +edit XML files as it can with other metadata files. Below are example commands +demonstrating the use of exiftool XML files.

+ +

Create an exiftool XML sidecar file:

+
exiftool -X a.jpg > a.xml
+ +

Restore original meta information from exiftool XML file:

+
exiftool -tagsfromfile a.xml -all:all a.jpg
+ +

The -X option also supports extracting binary data when +-b is added. For example, the above command may be modified to +also store the binary MakerNotes block like this:

+
exiftool -X -b -makernotes -all a.jpg > a.xml
+

Note that we needed to add -makernotes because it isn't +extracted as a block unless requested, and since we specified a tag to extract +we also needed to add -all to continue extracting other tags as +well. Restoring the original metadata from this file is the same as in the +previous example.

+ +

Via the API

+ +

There is no way to automatically produce a sidecar exiftool XML file via the +API since this function is accomplished with an output formatting option of the +exiftool application. However, the API may be used to read and copy tags +from an exiftool XML file just like any other file format. When reading +ExifTool XML files, all tags except those in the ExifTool, +File and Composite groups are extracted with their +original family 1 groups to facilitate copying of these tags back into their +original locations in an image.

+ +
+ +

EXIF Files

+ +

EXIF files store EXIF information in the same TIFF-based format as the EXIF +APP1 segment of a JPEG image, but without the "Exif\0\0" header. The three +commands below illustrate techniques for copying the entire EXIF block from a +source image (SRCFILE) to an output EXIF file +(out.exif):

+ +
exiftool -exif -b SRCFILE > out.exif
+
+exiftool -tagsfromfile SRCFILE -exif out.exif
+
+exiftool -o out.exif -exif SRCFILE
+ +

The Extra EXIF tag used in each of the +above commands (the "-exif" argument) represents the EXIF metadata +in the form of a binary data block. JPEG, PNG, JP2, MIE and MIFF files all +support storage of EXIF data blocks in this format, although exiftool does not +currently write MIFF images.

+ +

Tags may also be copied individually to and from an EXIF file, but remember +that this will not copy "unsafe" tags unless they are specified explicitly. The +following command creates an EXIF file from the metadata in a source file:

+ +
exiftool -o out.exif -all -unsafe SRCFILE
+
+ +

This technique works for any type of source file, provided the file contains +at least one tag with the same name as an EXIF tag. Below is an example of how +to apply this to all files in a directory:

+ +
exiftool -o %d%f.exif -all -unsafe DIR
+
+ +
+ +

MIE Files

+ +

The MIE file format allows storage of +native binary meta information, and is the best option for saving metadata from +a file in its original format. Here are two examples that copy all individual +tags plus the ICC Profile to a MIE sidecar file:

+ +
exiftool -tagsfromfile a.jpg -all:all -icc_profile a.mie
+
exiftool -o a.mie -all:all -icc_profile a.jpg
+ +

And the following command performs the inverse operation, restoring metadata +in a JPG image from a MIE file:

+ +
exiftool -tagsfromfile a.mie -all:all -icc_profile a.jpg
+ +

Information can also be copied in block form to a MIE file. This allows +preservation of the original data structure as well as unknown and non-writable +tags. The command below copies the full EXIF segment as a block from a JPEG +image,

+ +
exiftool -tagsfromfile a.jpg -exif a.mie
+ +

which is functionally different from copying all writable EXIF tags +individually with a command more like this

+ +
exiftool -tagsfromfile a.jpg -exif:all a.mie
+ +

Block-writable tags are listed in the +Extra Tags documentation.

+ +

MIE files also have the ability to store information in compressed format with +the -z option (provided Compress::Zlib is installed on your system), +which may be useful if disk space is at a premium.

+ +
+ +

EXV Files

+ +

EXV files are used by Exiv2, and are +basically a JPEG file without the image data, so they may be used as a metadata +file to contain any information supported by the JPEG format (EXIF, XMP, IPTC, +etc.). ExifTool has full read, write and create support for this format.

+ +
+Created Nov 12, 2008
+Last revised July 31, 2019 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/mistakes.html b/ExifTool/html/mistakes.html new file mode 100644 index 0000000..abe7ea2 --- /dev/null +++ b/ExifTool/html/mistakes.html @@ -0,0 +1,155 @@ + + + + Common ExifTool Mistakes + + + + + +

Common Mistakes when using ExifTool

+ + +

1. Missing Duplicate Tags

+
By default, ExifTool will suppress duplicate tags in the output. +Often this is desirable but sometimes one can be fooled into thinking +that information doesn't exist when it is just hidden by another tag +with the same name. For instance, the following command won't necessarily +return all of the EXIF tags: +
exiftool -exif:all image.jpg
+
+To avoid this problem, use the -a option. +
+ + +

2. Over-use of Wildcards in File Names (eg. "*.*")

+
It is often preferable to specify a directory name (eg. ".") instead +of using wildcards (eg. "*.*") for a number of reasons: +
    +
  1. "*.*" will process any file with a "." in the name. This includes files +that ExifTool should not normally process (like the "_original" backup files for +example). By specifying a directory name instead, ExifTool will process only +supported file types. Or the -ext option may be used to process +specific file types.
  2. +
  3. "*.*" will process any sub-directories which contain "." +in the name. This may be unexpected.
  4. +
  5. The -r option (to recursively process sub-directories) is only +effective when a directory name is specified, so it doesn't work when +"*.*" is specified (unless the first-level directories have a "." +in the name, as mentioned in point b above).
  6. +
  7. Arguments like "*.jpg" are a problem on systems with +case-sensitive file names (like OS X and Linux) because JPG images with +uppercase extensions will be missed. It is better to avoid this problem and use +"-ext jpg ." to process all JPG images in the current directory +because this technique is case-insensitive.
  8. +
  9. This can be a security problem on systems where the shell automatically +expands wildcards (eg. Mac and Linux) because a malicious arrangement of file +names could potentially have unwanted effects since they may be interpreted as +ExifTool options (see Security Issues).
  10. +
  11. There are problems using wildcards to match files with Unicode characters in +their names on Windows systems.
  12. +
+
+ + +

3. Over-scripting

+ +
Often users write shell scripts to do some specific batch processing +when the exiftool application already has the ability to do this either without +scripting or with a greatly simplified script. This includes the ability to +recursively scan sub-directories for a specific file extension (case insensitive), +rename files from metadata values, and move files to different directories. +
+ +
For example, this Unix script (from +here): + +
find -name '*.jpg' | while read PIC; do
+DATE=$(exiftool -p '$DateTimeOriginal' $PIC |
+sed 's/[: ]//g')
+touch -t $(echo $DATE | sed 's/\(..$\)/\.\1/') $PIC
+mv -i $PIC $(dirname $PIC)/$DATE.jpg
+done
+
+ +may be replaced with this single command: +
exiftool -d %Y%m%d "-filename<datetimeoriginal" "-filemodifydate<datetimeoriginal#" -ext jpg -r .
+ +Running as a single command is much faster because the startup time of loading +ExifTool is significant.
+ + +

4. Writing/Copying Multiple Tags

+
Any number of tags may be specified on a single command line, but +often people execute a separate command to write or copy each tag, which is very +inefficient. Combining all of the tag assignments into a single command avoids +the significant overhead of launching exiftool for the subsequent commands. For +example: +
exiftool -artist=phil -modifydate=now -tagsfromfile %d%f.xmp -xmp:title -xmp:description -ext jpg c:\images
+
+The -@ option may be used to read command-line arguments from a +file, and may be useful in situations where there are a large number of +arguments.
+ + +

5. Errors and Inefficiencies when Redirecting Tags

+ +
The syntax to redirect tags when copying is "-DSTTAG<SRCTAG", +but the syntax also allows a string containing tag names (prefixed by $) +to be used in place of SRCTAG. Three common mistakes when using this +syntax are: +
    +
  1. Adding a leading "-" before the SRCTAG. (eg. +"-EXIF:Artist<-XMP:Creator") This is wrong, and the tag will +not be copied. (It should be "-EXIF:Artist<XMP:Creator".)
  2. +
  3. Adding a leading "$" when copying a simple tag. (eg. +"-comment<$filename"). This is usually not necessary (see +exception below) and it is less efficient for ExifTool to process the source +string than it is to copy the tag directly. Also, values of list-type and +shortcut tags are concatenated in the string rather than being copied +individually, and wildcards are not allowed. Another difference is that a minor +warning is generated if a tag doesn't exist when interpolating its value in a +string (with "$"), but isn't when copying the tag directly.
  4. +
  5. Using "=" instead of "<" to copy a tag, or +using "<" instead of "=" to assign a value. +
    "<" is used for copying, in which case the +source (right-hand-side) operand is interpreted as either +
      +
    1. a tag name (if the operand does not contain a "$" symbol), or
    2. +
    3. a string containing tag names prefixed by "$" symbols (if the operand contains a "$" symbol).
    4. +
    +"=" is used to assign a simple tag value, and the source operand is +a string that is written directly to the destination tag.
    +(And the combination "<=" is used to assign a tag value from the contents of a file.)
    +
  6. +
+An exception to rule "b" above occurs when trying to copy the value of one tag +to a group of different tags, for example: +
exiftool "-time:all<datetimeoriginal" FILE  (WRONG!)
+The above command doesn't work because the destination tag name of "All" writes +to the tag with the same name as the source (ie. only "DateTimeOriginal"). However, +when interpolated in a string the identity of the source tag is lost, so the +following command will write to all tags in the Time group: +
exiftool "-time:all<$datetimeoriginal" FILE
+(Note that single quotes would be necessary in the above command under Mac/Linux.) +
+ +
+Last revised Mar 11, 2021 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/overview.png b/ExifTool/html/overview.png new file mode 100644 index 0000000000000000000000000000000000000000..2d7113e7e955fb1ca396cabd88bf95bf6e0d43e8 GIT binary patch literal 27207 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 + + + Problems with current Metadata Standards + + + + + + +

Problems with current Metadata Standards

+ +

It seems that all metadata standards have their own unique problems. This +page documents some significant structural problems found in some current +metadata and file format specifications, and gives possible solutions to these +problems. [Also see my Commentary on Meta +Information Formats.]

+ + +

TIFF 6.0 [1]

+ +

A significant problem of the 1992 TIFF 6.0 specification is that there is no +way to distinguish an IFD (image file directory) offset from a simple integer +value. As a result, new IFD's may not be created without risking corruption of +the files by unaware software. This is not only a problem for proprietary maker +notes which commonly use a TIFF IFD structure, but is also a problem for +extensibility of TIFF-based RAW image formats (as demonstrated by the DNG 1.3 +specification -- see below).

+ +

A simple solution:

+ +

Use a TIFF field type of 13 (IFD) instead of 4 (LONG) for IFD offsets. +This was first proposed in 1995 by Adobe in their PageMaker +6.0 TIFF Technical Notes[2], +but unfortunately it never found its way into the TIFF specification. Even so, +Olympus Optical Co. has shown some intelligence and is using this field type in +the maker notes of their recent digital cameras. +

+ +

Another useful addition: (added 2009-09-27)

+ +

A number of camera and cell phone manufacturers (Concord, Kodak, Motorola, +Nokia, Olympus, Pentax, Ricoh, Samsung and Sony) leave blank IFD entries in the +maker notes of images from some models. Presumably this simplifies the embedded +software by allowing the output file structure to be kept constant even when the +number of maker note IFD entries changes. It could be useful if this feature +was added explicitly to the official TIFF specification by defining a field type +of 0 as a "free IFD entry" to be ignored. (Note that this ability already +exists implicitly at a certain level in the specification, which states: +"Readers should skip over fields containing an unexpected field +type".)

+ + +

DNG 1.3 [3]

+ +

With the DNG 1.3 specification of June 2009, Adobe added a new Camera Profile +IFD referenced by an offset using the standard (and unfortunate) TIFF LONG field +type. This means that the new Profile IFD will be lost if the file is rewritten +by any software which does not have explicit knowledge of the 1.3 specification. +But to make things worse, Adobe didn't even use a standard IFD format for the +data. Instead, the IFD begins with a TIFF-style header and uses relative instead +of absolute offsets. This would have been a good idea if the IFD was stored as +the value of an UNDEFINED tag rather than referenced from a LONG offset. (If +done this way, the new information would have been preserved if the file was +rewritten by unaware software.) But as implemented it just adds to the pain of +parsing the file by requiring even more specialized code to be written in +support of the DNG 1.3 format.

+ +

A simple solution:

+ +

Sack the Adobe developers who were responsible for this, and use field type +13 (as recommended above for TIFF 6.0) and a standard TIFF IFD structure when +adding new IFD's in the future.

+ + +

EXIF 2.3 [4]

+ +

The EXIF specification has been the standard for digital camera metadata for +many years, and while the digital camera technology has advanced significantly +in these years, the EXIF specification has not. There are a number of +significant problems with the EXIF specification which have never been +addressed.

+ +

Some current problems with the EXIF specification are:

+ +
    +
  1. Maker note data structure has no restrictions and is easily invalidated when +editing EXIF.
  2. +
  3. No facility for storing the time zone for date/time values. [finally +added to Exif 2.31 spec.]
  4. +
  5. Very limited support for alternate character sets (essentially only the +UserComment tag has this feature).
  6. +
  7. No alternate language support.
  8. +
  9. Byte ordering for "Unicode" text strings, and the meaning of "Unicode" +itself is not clearly specified.
  10. +
  11. Mandatory tags are unnecessary and painful to implement.
  12. +
  13. ApertureValue and MaxApertureValue are stored as unsigned RATIONAL, which +means that lenses with F numbers faster than 1.0 (with equivalent APEX values of +less than zero) can not be represented.
  14. +
  15. MaxApertureValue is defined as "The smallest F number of the lens". This +definition is unclear in the case of zoom lenses where the maximum aperture +varies across the zoom range. Some manufacturers (Canon, Nikon, Sony) store the +maximum aperture at the specific focal length, but others (Olympus, Pentax) +store the absolute maximum aperture of the lens.
  16. +
  17. EXIF is not extensible, and is missing definitions for storing some +information that could be very useful to camera owners (eg. camera pitch/roll +angles, sensor temperature, face detection, auto-focus points, image +stabilization, flash exposure compensation, etc). [Some of these tags +were added in the Exif 2.31 specification.]
  18. +
  19. 0xffffffff in the numerator or denominator of some rational-value tags is +used to indicate an unknown or infinite value. This is completely unnecessary +and not supported by many applications because it requires extra tag-specific +logic to be implemented.
  20. +
  21. The total size of EXIF metadata in JPEG images is limited to 65527 bytes +or less.
  22. +
+ +

Simple solutions:

+ +
    +
  1. Specify that maker note data must be self-contained (ie. must not exceed the +bounds of the maker note value data), and must be relocatable (ie. must not use +absolute offsets). [I would have suggested defining a new maker note tag +with field type 13 (IFD) which references a standard format IFD, but I am afraid +that no camera maker would ever jump on board with this suggestion now that they +have already been seduced by the dark side.]
  2. +
  3. Change the specification to allow an optional time zone of the format +"-05:00" to be appended to the date/time string values.
  4. +
  5. Change the specification to allow UTF-8 in ASCII-type values as recommended +by the MWG[5].
  6. +
  7. No simple solution for this. XMP[6] +is the only reasonable alternative if alternate language support is +required.
  8. +
  9. Specify that the Unicode byte order must be the same as the EXIF byte +ordering. (This is the only reasonable choice, but for some reason both +Microsoft and Apple seem to write Unicode using the native processor byte +ordering. regardless of the EXIF byte order.) Also, it should be made clear +that by "Unicode" the EXIF specification actually means UCS-2, although updating +this to allow UTF-16 surrogate pairs may be a good idea.
  10. +
  11. Define reasonable fallback values for required tags which are missing.
  12. +
  13. Allow ApertureValue and MaxApertureValue to be stored as signed SRATIONAL.
  14. +
  15. Define whether MaxApertureValue is taken at the current focal length of +a zoom lens, or over the entire zoom range.
  16. +
  17. Expand the standard to include additional useful information available from +modern digital cameras.
  18. +
  19. Use a rational value of 1/0 to indicate infinity, and 0/0 to indicate an +unknown/undefined value.
  20. +
  21. Allow EXIF to span multiple JPEG segments. Multiple consecutive EXIF +segments are simply concatenated into a single data block when reading. +[Apparently Adobe has been doing this for years[11], +and this ability was added to ExifTool in version 10.97.]
  22. +
+ + +

Microsoft "OffsetSchema" Tag [7]

+ +

In February 2007 Microsoft proposed a new PhotoInfo tag called "OffsetSchema" +(hex. 0xEA1D, dec. 59933) in an attempt to patch a deficiency in the EXIF maker +note specification (see point 1 in EXIF 2.2 section above). This tag represents +the offset difference between the original maker note location in the EXIF and +the new location after editing, and is designed to allow the maker note tag +values to be accessed after the location of the maker notes is changed by +editing the EXIF. [Bless their little hearts for trying to improve this +situation, but while the idea is good the implementation is flawed and +ultimately unworkable.]

+ +

There are two main problems with the implementation, and the second is a show +stopper:

+ +
1. For this new tag to be available to a single-pass metadata reader, it +must come before the maker note data (hex. 0x927C, dec. 37500). But +since the EXIF/TIFF format specifies that tags must be stored in numerical +order, the maker note tag (hex. 0x927C) comes before the OffsetSchema tag +(hex. 0xEA1D).
+ +
2. The OffsetSchema tag will be invalidated by any software that +rewrites the EXIF and moves the maker notes without properly updating the tag. +In an ideal world all application developers would release an updated version +of their software which treats the OffsetSchema properly, and all users would +update to this new version. But since this is the real world it just won't +happen, which makes the value of OffsetSchema unreliable. Too bad, because this +wouldn't have been a problem if Microsoft had specified that the new tag +represented the original offset of the maker notes instead of the difference +from the original position. With this change, the tag wouldn't need updating +when the EXIF is edited, and the information would be much more reliable. The +only problem here would be editing software that explicitly changes the maker +note offsets. However, software with this ability is rare, and it is more +reasonable to ask that the OffsetSchema tag simply be deleted by any software +that updates the maker note offsets. (Software must be fairly advanced in the +first place if it parses the proprietary maker note data structures and changes +these offsets.)
+ +

A simple solution:

+ +

Create a new tag which comes before the maker notes (hex. 0x927B, dec. 37499 +would be good) and represents the original offset of the maker notes.

+ + +

JPEG File Interchange Format [8]

+ +

The JPEG File Interchange Format version 1.02 was released in 1992. The +biggest structural problem with this standard is that metadata in these files in +is stored in segments which have a maximum size of 65533 bytes. This limit has +necessitated a number of creative solutions, each creating complications and +problems of their own. [See my comments on the PreviewImage problem for example.]

+ +

A simple solution:

+ +

Since the value of the segment size word includes the 2 bytes of the segment +size word itself, a value of 0 or 1 is not allowed by the current JPEG standard. +The standard could be enhanced so a value of 1 indicates an extended JPEG +segment where the 2-byte size word (with value 0x0001) is followed immediately +by a 4-byte integer giving the size of the extended JPEG segment. This would +allow segment sizes of up to 4294967291 bytes (assuming the size includes these +4 bytes). Further, a value of 0 could be defined for an 8-byte integer if one +really wanted to support huge metadata segments. Either change to an existing +JPEG would break all current JPEG reader/writers, but the change is trivial and +could easily be implemented.

+ +

An alternative solution:

+ +

Define a new application marker segment which uses a 4-byte size word. This +technique is already used for the extended JPEG2000 codestream MCT, MCC and MIC +marker segments.

+ +

Another solution: [implemented in ExifTool 10.97]

+ +

We can work around this limitation for EXIF metadata by allowing it to span +multiple segments using the same technique as for Photoshop APP13 metadata.

+ + +

Multi-Picture Format (MPF) [9]

+ +

In February 2009 CIPA[10] released a +"Multi-Picture Format" standard for storing large images in JPEG files. This +format is yet another attempt to bypass the JPEG segment-size limit (see above) +to store large preview images. But again, there is a significant problem with +this standard: Pointers in the new APP2 MPF segment use offsets relative to the +start of the MPF header in this segment to reference image data after the JPEG +EOI. Unfortunately, these offsets are quickly broken if any data after the MPF +segment changes length. This problem could have been avoided if offsets had +been specified relative to the end of file, but it is too late for this now that +the specification is public. However, another problem is that information after +the JPEG EOI is often discarded by software when the file is edited.

+ +

A possible work-around:

+ +

Enforce the rule that the MPF APP2 segment must come after all other APP +segments. (It would have been smart if this was specified in the CIPA standard, +but sadly this wasn't the case.) If this is done, then metadata in the remaining +APP segments (EXIF, IPTC, XMP, etc) can safely be edited without breaking the +MPF offsets. I suggest that all metadata editors employ this strategy, +regardless of the segment order specified in the standard (which says that the +MPF APP2 segment must come immediately after the EXIF APP1 segment).

+ +

Unfortunately this work-around has the same problems as the Microsoft +OffsetSchema tag because the MPF information may easily be invalidated by an +unaware editor, and it doesn't address the problem of losing data stored after +the JPEG EOI.

+ +

A simple solution:

+ +

Change the JPEG specification to allow larger segments (as mentioned above in +the JPEG section), and change the MPF specification to store all information +inside a JPEG segment.

+ +

[2014-04-29: CIPA has changed the location of these standards documents, +so the URL's referenced here are now broken.]

+ + +

References

+ +
    +
  1. http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
  2. +
  3. http://partners.adobe.com/public/developer/en/tiff/TIFFPM6.pdf
  4. +
  5. http://www.adobe.com/products/dng/pdfs/dng_spec_1_3_0_0.pdf
  6. +
  7. http://www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-008-2010_E.pdf
  8. +
  9. https://web.archive.org/web/20180822085951/http://metadataworkinggroup.com/pdf/mwg_guidance.pdf
  10. +
  11. http://www.adobe.com/devnet/xmp/
  12. +
  13. http://support.microsoft.com/kb/927527
  14. +
  15. http://www.jpeg.org/public/jfif.pdf
  16. +
  17. http://www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-X007-KEY_E.pdf
  18. +
  19. http://www.cipa.jp/english/hyoujunka/kikaku/cipa_e_kikaku_list.html
  20. +
  21. XMP Specification part 3 November 2014, p. 13
  22. +
+
+Created Sep 17, 2009
+Last revised May 16, 2018 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/struct.html b/ExifTool/html/struct.html new file mode 100644 index 0000000..9099c04 --- /dev/null +++ b/ExifTool/html/struct.html @@ -0,0 +1,436 @@ + + + + Structured Information + + + + + +

Structured Information

+ +

ExifTool has the ability to read and write XMP structures through the use of +either structured or flattened tags. The ability to write via structured +input was added in ExifTool version 8.44; older versions accepted only flattened +tags as input.

+ +

To illustrate the concept of a flattened tag, the XMP-exif:Flash +structure contains Fired and Mode fields (among +others). The flattened tags corresponding to these structure fields are +XMP-exif:FlashFired and XMP-exif:FlashMode. In the +XMP Tags documentation, flattened tags are +indicated by an underline (_) after the Writable type.

+ +

This page describes various techniques used to read and write XMP structures +using both structured and flattened tags.

+ + +

Reading

+ +

When reading, structures are flattened by default, and ExifTool +returns one "flattened" tag for each field in the structure:

+ +
+
> exiftool -xmp:all a.xmp
+XMP Toolkit                     : Image::ExifTool 8.44
+Flash Fired                     : True
+Flash Mode                      : On
+Flash Return                    : Return not detected
+
+ +

But the -struct option may be used to give structured +output. In this mode structures are returned instead of separate +"flattened" tags:

+ +
+
> exiftool -struct -xmp:all a.xmp
+XMP Toolkit                     : Image::ExifTool 8.44
+Flash                           : {Fired=True,Mode=On,Return=Return not detected}
+
+ +

(Note: As illustrated in the example above, structures are +serialized for console output by the ExifTool +application. However, via the API with the Struct option, +they are returned as Perl HASH references.)

+ +

The -struct option may also be combined with the JSON +(-j), PHP (-php) or XML (-X) output +formats to provide a structured format which may be more compatible with other +applications.

+ + +

Writing

+ +

When writing, flattened tags and structures may be used interchangeably. +For example, the following commands all have the same effect.

+ +
+
exiftool -flashmode=on -flashreturn=not -flashfired=true a.xmp
+exiftool -xmp:flash="{mode=on,fired=true}" -flashreturn=not a.xmp
+exiftool -xmp:flash="{mode=on,fired=true,return=not}" a.xmp
+
+ +

(Note: Structures must be serialized when +writing via the command-line application, in the same format as when reading with +the -struct option.)

+ +

An advantage of writing in structured form is that it can be easier to +achieve the desired hierarchy with complex structures or when there are multiple +structures in a list. For example, this command adds a new hierarchical keyword +to the XMP-mwg-kw:HierarchicalKeywords list:

+ +
+
exiftool -hierarchicalkeywords+="{keyword=cat,children={keyword=Siamese}}" a.jpg
+
+ +

But the flattened tags may be more convenient for adding or replacing a +single field in an existing structure because writing as a structure would +require that the entire structure be replaced. For example, the following command +adds a new second-level keyword to an existing HierarchicalKeywords structure:

+ +
+
exiftool -hierarchicalkeywords2+="Persian" a.jpg
+
+ +

Tricky: There is one drawback when using this technique to add new +fields to existing structures in lists: New fields are added to the first +structure which doesn't already contain the corresponding field. So before +adding a new field to a arbitrary structure, dummy fields must first be added to +all earlier structures in the list which are missing this field. However, the +alternative of adding a new field by writing structured information also has its +drawbacks. Here, although a specific structure in a list can easily be targeted +through any unique combination of field values, the drawback is that the entire +structure must be replaced (see Deleting / Replacing +below).

+ +

The flattened tag names may also be used to write structures at any level +in a complex hierarchy. The following example writes a third-level +structure inside a HierarchicalKeywords structure:

+ +
+
exiftool -hierarchicalkeywords2Children='{Keyword=Tabby,Applied=true}' a.jpg
+
+ +

(Note: Containing structures are created as necessary. In this +case, the HierarchicalKeywords and top-level KeywordInfo structures would be +created if they didn't already exist.)

+ +

The order of structure fields is not significant, so they may be read +in a different order than written, unlike arrays which maintain the same order. +To give a predictable output, fields in structured information are sorted in +alphabetical order of field name by ExifTool when reading and writing.

+ +

If there are errors converting some fields of the input structure, +other fields are still written and a warning is issued (but only one warning per +structure is reported). This also applies when copying structured information +except that the -v3 option must be used to see the warnings when +copying.

+ +

Programmers: Structured information is written and read as Perl HASH +references via the ExifTool API, but it may also be written as a serialized +string. The following two techniques are equivalent:

+ +
+
# as a HASH reference
+$exifTool->SetNewValue('XMP:Flash' => { mode=>'on', fired=>'true', return=>'not' });
+
+# as a serialized string
+$exifTool->SetNewValue('XMP:Flash' => '{mode=on,fired=true,return=not}');
+
+ + +

Copying

+ +

By default, tags are copied as structures, but flattened tag names +may still be copied by specifying them explicitly. (Flattened +tags are treated as "unsafe" for copying so they are not copied by default +unless the Struct feature is disabled; see below.) Copying as structures +allows the hierarchy of complex structures to be preserved.

+ +
+
# this copies all XMP information as structures
+# (flattened tags are not copied by default...)
+exiftool -tagsfromfile src.jpg -xmp:all dst.jpg
+
+# ... but flattened tags may be copied individually.  Here the
+# first level hierarchical keywords are copied to the Subject tag
+# (this may be done in the same command as one that copies structures)
+exiftool -tagsfromfile src.jpg "-subject<hierarchicalkeywords1" dst.jpg
+
+ +

Note that when copying a specific structure, only the top-level structures +may be specified:

+ +
+
# this copies the complete keyword hierarchy
+exiftool -tagsfromfile src.jpg -keywordinfo dst.jpg
+
+# WRONG because HierarchicalKeywords is NOT a top-level structure!
+exiftool -tagsfromfile src.jpg -hierarchicalkeywords dst.jpg
+
+ +

The copy-as-structure feature may be disabled with --struct on +the command line, or by setting the Struct option +to 0 via the API. When this is done, only flattened tags are copied, and +structures may not be specified. Conversely, if the structure option is enabled +(by setting the Struct option to 1 via the API, +or with -struct on the command line), only structures are copied, +and flattened tags may not be specified.

+ +

(Note: ExifTool 8.43 and earlier copied as flattened tags only, but +copying as structures has been the default since the ability to write structured +information was introduced in version 8.44. An enhancement in version 8.82 +allowed flattened tags to be copied explicitly without the need to disable the +Struct option.)

+ + +

Deleting / Replacing

+ +

A complete structure is deleted by specifying one or more matching +fields. All fields must match for the structure to be deleted. For example, +the following command deletes all HierarchicalKeywords structures which have the +Keyword "Terrier" at the second level:

+ +
+
exiftool -hierarchicalkeywords-="{Children={Keyword=Terrier}}" a.jpg
+
+ +

Structure fields may also be deleted individually using the flattened +tag names. The following command deletes only the matching fields from the +second-level of all HierarchicalKeywords structures:

+ +
+
exiftool -hierarchicalkeywords2-="Terrier" a.jpg
+
+ +

Individual structure fields may NOT be deleted by writing a structure +with an empty field. Instead, a command like this overwrites the entire +structure with a new structure containing an empty field:

+ +
+
exiftool -CreatorContactInfo="{CiAdrCity=}" a.jpg  # WRONG!
+
+ +

When deleting and adding back items in lists in the same command, new items +are inserted at the point in the list where the first item was removed, or at +the end of the list if no items were deleted. This applies to lists of +structures as well as simple lists of string values, and provides a mechanism to +replace a specific structure or field.

+ + +

Field Names

+ +

Structure field names use a format very similar to tag names in +ExifTool. The following table lists some similarities and differences +between tag names and structure field names:

+ +
+ + + + + + + + + +
FeatureExampleTag NamesField Names
Case InsensitivityTitle, title, TITLEYesYes
Alternate Language SuffixTitle-deYesYes
Numerical Value SuffixMode#YesYes
Group Name PrefixXMP-dc:TitleYesNo
+ Except that group name prefixes are +allowed in structures which support arbitrary XMP fields (eg. +Region Extensions) +
+ + +

Serialization

+ +

Structures are serialized when reading or writing from the command line. +However, serialization is not done when reading via the API, and is optional +when writing via the API. The default serialization algorithm is outlined +below, but note that ExifTool 12.64 has an +API StructFormat option to allow +JSON-format serialized structures.

+ +

Default serialization algorithm

+ +
    +
  1. Escape the following characters in string values (structure field values and +list items) by adding a leading pipe symbol (|): +
      +
    • pipe symbols (|) and commas (,) anywhere in the string
    • +
    • closing curly brackets (}) anywhere in structure field values
    • +
    • closing square brackets (]) anywhere in list items
    • +
    • an opening curly ({) or square ([) +bracket, or whitespace character (SPACE, TAB, CR or LF) if it appears at the +beginning of the string
    • +
    +(Note: Any other character may be escaped by adding +a leading pipe symbol without effect.)
  2. +
  3. Enclose structures in curly brackets. Use an equal sign (=) +to separate field names from their corresponding values, and a comma between +structure fields.
  4. +
  5. Enclose lists in square brackets, with a comma between list items.
  6. +
  7. Optional whitespace padding may be added anywhere except inside a structure +field name, or inside or after a string value, and an optional comma may be +added after the last field in a structure.
  8. +
+ +

For example, with this command:

+ +
+
exiftool "-RegionInfo<=INFILE" a.xmp
+
+ +

and the INFILE below, structured information is written to XMP-mwg-rs:RegionInfo.

+ +
+
{
+  AppliedToDimensions =
+  {
+     W = 4288,
+     H = 2848,
+     Unit = pixel,
+  },
+  RegionList =
+  [
+    {
+      Area =
+      {
+        W = 0.15, H = 0.17, X = 0.3, Y = 0.4,
+        Unit = normalized,
+      },
+      Description = A Physics Icon {relatively speaking|},
+      Name = Albert Einstein,
+      Type = Face,
+      Extensions = {
+        XMP-xmpRights:UsageTerms = copyright Phil Harvey,
+        XMP-xmpRights:UsageTerms-fr = droit d'auteur Phil Harvey,
+      },
+      SeeAlso = dc:subject,
+    },
+    {
+      Area =
+      {
+        W = 0.06, H = 0.09, X = 0.5, Y = 0.6,
+        Unit = normalized,
+      },
+      Description = this is a test|, what did you expect?,
+      Type = Focus,
+      FocusUsage = Evaluated|, Used,
+    }
+  ],
+}
+
+ +

In this example, white space has been added in all allowed locations for +demonstration purposes and to improve readability. Also, optional commas have +been added after the last field of each structure. (Note that a comma may NOT +be added after the last item in a list because this would be interpreted as an +additional list item consisting of a zero-length string.)

+ + +

Examples

+ +

Here is an example of an advanced console session showing some commands which +manipulate a complex list of structures (see the +XMP-iptcExt tag documentation for details +about the ArtworkOrObject structure tags used):

+ +
+
# 1. Create a XMP-iptcExt:ArtworkOrObject structure using flattened tags
+> exiftool -artworktitle="a title" a.xmp
+    1 image files created
+
+# -- Read back as flattened tags (-S is used just to shorten the output)
+> exiftool -xmp-iptcext:all -S a.xmp
+ArtworkTitle: a title
+
+# -- Read back as a structure
+> exiftool -xmp-iptcext:all -S -struct a.xmp
+ArtworkOrObject: [{AOTitle=a title}]
+
+# 2. Write another field to the structure as a flattened tag
+> exiftool -artworkcreator=phil a.xmp
+    1 image files updated
+
+# -- Note that the structure now has a new field
+> exiftool -xmp-iptcext:all -S -struct a.xmp
+ArtworkOrObject: [{AOCreator=[phil],AOTitle=a title}]
+
+# 3. Add another creator using the "+=" operator
+> exiftool -artworkcreator+=joe a.xmp
+    1 image files updated
+
+# -- It was added to the first AOCreator list
+> exiftool -xmp-iptcext:all -S -struct a.xmp
+ArtworkOrObject: [{AOCreator=[phil,joe],AOTitle=a title}]
+
+# 4. Add another artwork title
+> exiftool -artworktitle+="another one" a.xmp
+    1 image files updated
+
+# -- This created a new ArtworkOrObject structure in the list of structures
+# (AOTitle itself is not a list, so a new structure must be created)
+> exiftool -xmp-iptcext:all -S -struct a.xmp
+ArtworkOrObject: [{AOCreator=[phil,joe],AOTitle=a title},{AOTitle=another one}]
+
+# 5. Simply write a different title (do not add with "+=")
+> exiftool -artworktitle="different" a.xmp
+    1 image files updated
+
+# -- This deleted all existing AOTitle fields and wrote back only one
+# (if the second ArtworkOrObject structure had contained more fields, they would have been
+# preserved, and the second structure would still exist, but without an AOTitle field)
+> exiftool -xmp-iptcext:all -S -struct a.xmp
+ArtworkOrObject: [{AOCreator=[phil,joe],AOTitle=different}]
+
+# 6. Add a completely new structure to the list
+# (this is very difficult to do properly using flattened tags)
+> exiftool -artworkorobject+="{aotitle=help,aocreator=[paul,ringo]}" a.xmp
+    1 image files updated
+
+# -- The new structure was added with the specified fields
+> exiftool -xmp-iptcext:all -S -struct a.xmp
+ArtworkOrObject: [{AOCreator=[phil,joe],AOTitle=different},{AOCreator=[paul,ringo],AOTitle=help}]
+
+# -- See how the relationships are lost when reading as flattened tags
+> exiftool -xmp-iptcext:all -S a.xmp
+ArtworkCreator: phil, joe, paul, ringo
+ArtworkTitle: different, help
+
+# 7. Delete all structures containing a specific field value
+> exiftool -artworkorobject-="{AOCreator=phil}" a.xmp
+    1 image files updated
+
+# -- The ArtworkOrObject list now contains only one structure
+> exiftool -xmp-iptcext:all -S -struct a.xmp
+ArtworkOrObject: [{AOCreator=[paul,ringo],AOTitle=help}]
+ + +

User-Defined Structures

+ +

User-defined XMP structure tags may be created via the ExifTool config file. +See the NewXMPxxxStruct tag definition in the XMP-xxx +examples of the sample config file for more details.

+ +
+Last revised Jun 12, 2023 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/style.css b/ExifTool/html/style.css new file mode 100644 index 0000000..e2fb232 --- /dev/null +++ b/ExifTool/html/style.css @@ -0,0 +1,31 @@ +body { font-family: helvetica; font-size: .9em; + color: black; background: white } +a:link { color: blue } +a:visited { color: #508 } +a:active { color: red } +.blk { color: black } +.red { color: #c00 } +.grn { color: #080 } +.blu { color: blue } +.com { color: #a00 } +.aside { color: #864 } +.lt { color: #666 } +.lg { font-size: larger } +.sm { font-size: smaller } +.up { margin: 0 0 .5em 0 } +p { margin: 1em 1em } +p.lf { margin: 1em 0 } +.code { color: #800; } +code { color: #800; font-family: monaco, monospace; + font-size: 1em; white-space: nowrap } +pre { font-family: monaco, monospace; font-size: 1em } +div.index { float: right; clear: both; + border-left: 1px solid gray; + margin: .5em 0 .5em .5em; padding: 0 .5em } +table.box { padding: .2em; background: #ddd } +table.norm { border-collapse: collapse; + border: 1px solid gray; } +table.norm th { border: 1px solid gray; background: #ddd } +table.norm td { border: 1px solid gray; padding: 2px 4px } +table.clear td { border: 0; padding: 0 2px } +table.tight td { padding: 1px 4px } diff --git a/ExifTool/html/under.html b/ExifTool/html/under.html new file mode 100644 index 0000000..d00138a --- /dev/null +++ b/ExifTool/html/under.html @@ -0,0 +1,165 @@ + + + + Under the Hood + + + + + + +

"Under the Hood" of ExifTool

+ +

This page explains some details of ExifTool's inner workings.

+ + +

Overview of ExifTool

+ +

Below is a diagram showing the flow of information for the exiftool +application. Indicated outside the boxes on the diagram are some command-line +options associated with the various stages of processing. All of these options +are directly associated with options or function calls available via the API +(Application Programming Interface), with the exception of the output text +formatting which is handled at the application level.

+ +
ExifTool Overview
+ +

The information flow is separated into two distinct modes: 1) +Reading or extracting information, and 2) +Writing or editing. The application runs in +read mode by default, but switches to write mode +if a new value is assigned to any tag (via "-TAG=", +"-tagsFromFile", "-geotag", "-csv=" or +"-json=" on the command line).

+ + +

Value Conversions

+

When ExifTool reads or writes the value of a tag, there are 3 separate +conversions applied to each value, resulting in 4 different levels for the value +of each tag. By default, users interact only with the human-readable +("PrintConv") value, but other levels are exposed through various exiftool +options:

+ +
  1. The "PrintConv" value is the final human-readable value +which has been converted for display. Often, the "PrintConv" conversion will +translate numbers into words for better readability. The -lang +(Lang) option is used to specify the language for this conversion, and the +-c and -d (CoordFormat and DateFormat) options specify +this formatting for GPS coordinates and date/time values.
+ +
  1. For numerical values, the "ValueConv" value is a +machine-readable value suitable for use in calculations, typically converted to +standard units (eg. degrees, meters, or seconds). For date/time values the +standard EXIF date/time format is used ("YYYY:mm:dd HH:MM:SS" plus decimal +seconds and time zone if they exist). For tags which are a closed choice of +string, this is the stored value of the string. The ValueConv value is +returned for all tags when the -n option is used, or for individual +tags by suffixing the tag name with a # character.
+ +
  1. The "Raw" value is the value after initial formatting is +applied to the binary data from the file. Most tags have no separate "ValueConv" +conversion, so for these tags the "Raw" value is the same as the "ValueConv" +value. Values stored in rational form also have a "Rational" value. Both +Raw and Rational values may be seen by using the -v option.
+ +
  1. The "Binary" value is the actual binary data stored in the +file. This data is displayed in hexadecimal form with the -v3 +option, or by using the -htmlDump feature. Note that this value is +not related to the -b (-binary) option, which actually +returns the "ValueConv" value and is used for tags where this value can not be +presented in a simple text format. The Writable column in the +Tag Name documentation +gives the format of this binary data for writable tags.
+ +

Below are some examples of these different values for a few tags:

+ +
+ + + + + + + + + + + + + + +
Tag3. PrintConv2. ValueConv1. Raw (Rational)0. Binary
EXIF:OrientationHorizontal (normal)11
00 01
EXIF:GPSLatitude45 deg 20' 11.00"45.336388888888945 20 11
(45/1 20/1 11/1)
00 00 00 2d 00 00 00 01
00 00 00 14 00 00 00 01
00 00 00 0b 00 00 00 01
XMP:GPSLatitude45 deg 20' 11.00"45.336388888888945,20.183333N"45,20.183333N"
EXIF:ExposureTime1/300.033333333330.03333333333
(1/30)
00 00 00 01 00 00 00 1e
EXIF:ShutterSpeedValue1/300.03333333346291764.90689059
(19868/4049)
00 00 4d 9c 00 00 0f d1
EXIF:ModifyDate(set by -d option)2016:11:25 11:56:392016:11:25 11:56:39"2016:11:25 11:56:39\0"
XMP:ModifyDate(set by -d option)2016:11:25 11:56:39.00-05:002016-11-25T11:56:39.00-05:00"2016-11-25T11:56:39.00-05:00"
+ + +

Underlying Philosophies

+ +

You have the right to know about the metadata contained in your images. A main +goal of the Exiftool project is to make this information freely available, both to +the general public and as a resource for other developers.

+ +

In the design of exiftool, there have been a number of underlying philosophies +which have helped to influence the overall development:

+ +
    +
  1. Make Image::ExifTool as independent as possible + from other libraries to make it portable and easy to install. (Portable to + a wide range of systems and Perl versions.)
  2. +
  3. Keep the interface simple for simple tasks (sometimes at the expense of making + it more complicated for complex tasks).
  4. +
  5. The API functions should be isolated from + the details of the metadata formats (otherwise the interface turns into a giant + hairball, like the metadata).
  6. +
  7. Maintain flexibility to allow users the freedom to do what they want + (eg. support user-defined tags).
  8. +
  9. Design the code to be efficient for batch processing, even if it increases the + initial overhead.
  10. +
  11. When writing files, remember 3 things: 1) data integrity, 2) data integrity, and + 3) data integrity. If you can't do it right, don't do it at all.
  12. +
  13. If possible, recognize file types by their structure, not by their extension.
  14. +
  15. Maintain backward compatibility when making changes to the ExifTool API or + command line application.
  16. +
+ + +

Why Write ExifTool in Perl?

+ +

At the start of ExifTool development, Perl, Python and C++ were all considered as +possible languages for the project. It was recognized that the project would require +considerable effort, and the choice of language could heavily influence the amount of +work necessary. Python was a strong contender, but was discounted due to a personal +preference for C-like syntax. Perl was chosen over C++ for the main reason that +it would be less work to develop and support the project. Looking back, this was +definitely the right choice, and there was the added benefit of a strong +infrastructure in support of +testing and +distributing Perl software.

+ +

Perl 5 is very mature and extremely stable, so there is almost zero time wasted +dealing with compilation issues. Compare this to C++, where a majority of +development time for a large project may be spent in this area. Also, Perl's +built-in regular expressions are fantastically useful for all of the string +manipulations necessary for a metadata library. The biggest problem with Perl is +its lack of support for Windows Unicode file names.

+ +

The bottom line is that most of the development time is spent dealing with the +mechanics of metadata, with the result that ExifTool is full-featured metadata +library. With one main developer (Phil Harvey) and 280 thousand lines of code (as of +May 2023), this is a real accomplishment.

+ +
+Created Jun 24, 2009
+Last revised May 3, 2023 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/verbose.html b/ExifTool/html/verbose.html new file mode 100644 index 0000000..10badda --- /dev/null +++ b/ExifTool/html/verbose.html @@ -0,0 +1,211 @@ + + + + ExifTool Verbose Option + + + + +

ExifTool Verbose Option

+ +

With the Verbose option enabled, ExifTool prints a verbose log to the console as +it extracts the meta information from a file, or writes information to a file.

+ +

The -v option of the exiftool application allows control of the Verbose +setting. A number between 0 and 5 may be specified with the -v option to set a +specific Verbose level. For example, -v2 sets Verbose = 2. Without a number, +-v increments the current Verbose level by one. Multiple -v options are allowed, +so the following two examples give the same result:

+ +
+exiftool -v -v -v t/images/Canon.jpg
+
+exiftool -v3 t/images/Canon.jpg
+
+ +

Below are example outputs for different Verbose settings when extracting +information.

+ +

Verbose = 1

+

Prints tag names and extracted values. These are the raw values straight +from the file, before any conversions are applied. Also printed are details of +the file and directory structure:

+ +
+  ExifToolVersion = 10.00
+  FileName = Canon.jpg
+  Directory = t/images
+  FileSize = 2697
+  FileModifyDate = 1159902631
+  FileAccessDate = 1439991906
+  FileInodeChangeDate = 1439903472
+  FilePermissions = 33188
+  FileType = JPEG
+  FileTypeExtension = JPG
+  MIMEType = image/jpeg
+JPEG APP1 (2442 bytes):
+  ExifByteOrder = II
+  + [IFD0 directory with 9 entries]
+  | 0)  Make = Canon
+  | 1)  Model = Canon EOS DIGITAL REBEL
+  | 2)  Orientation = 1
+  | 3)  XResolution = 180 (180/1)
+  | 4)  YResolution = 180 (180/1)
+  | 5)  ResolutionUnit = 2
+  | 6)  ModifyDate = 2003:12:04 06:46:52
+  | 7)  YCbCrPositioning = 1
+  | 8)  ExifOffset (SubDirectory) -->
+  | + [ExifIFD directory with 31 entries]
+  | | 0)  ExposureTime = 4 (4/1)
+  | | 1)  FNumber = 14 (14/1)
+  | | 2)  ISO = 100
+  | | 3)  ExifVersion = 0221
+  | | 4)  DateTimeOriginal = 2003:12:04 06:46:52
+  [etc...]
+
+ +

Verbose = 2

+

Prints all of the information from Verbose = 1, plus additional information +about the tag ID, data size and format:

+ +
+  ExifToolVersion = 10.00
+  FileName = Canon.jpg
+  Directory = t/images
+  FileSize = 2697
+  FileModifyDate = 1159902631
+  FileAccessDate = 1439991927
+  FileInodeChangeDate = 1439903472
+  FilePermissions = 33188
+  FileType = JPEG
+  FileTypeExtension = JPG
+  MIMEType = image/jpeg
+JPEG APP1 (2442 bytes):
+  ExifByteOrder = II
+  + [IFD0 directory with 9 entries]
+  | 0)  Make = Canon
+  |     - Tag 0x010f (6 bytes, string[6])
+  | 1)  Model = Canon EOS DIGITAL REBEL
+  |     - Tag 0x0110 (24 bytes, string[24])
+  | 2)  Orientation = 1
+  |     - Tag 0x0112 (2 bytes, int16u[1])
+  | 3)  XResolution = 180 (180/1)
+  |     - Tag 0x011a (8 bytes, rational64u[1])
+  | 4)  YResolution = 180 (180/1)
+  |     - Tag 0x011b (8 bytes, rational64u[1])
+  | 5)  ResolutionUnit = 2
+  |     - Tag 0x0128 (2 bytes, int16u[1])
+  | 6)  ModifyDate = 2003:12:04 06:46:52
+  |     - Tag 0x0132 (20 bytes, string[20])
+  | 7)  YCbCrPositioning = 1
+  |     - Tag 0x0213 (2 bytes, int16u[1])
+  | 8)  ExifOffset (SubDirectory) -->
+  |     - Tag 0x8769 (4 bytes, int32u[1])
+  | + [ExifIFD directory with 31 entries]
+  | | 0)  ExposureTime = 4 (4/1)
+  | |     - Tag 0x829a (8 bytes, rational64u[1])
+  | | 1)  FNumber = 14 (14/1)
+  | |     - Tag 0x829d (8 bytes, rational64u[1])
+  | | 2)  ISO = 100
+  | |     - Tag 0x8827 (2 bytes, int16u[1])
+  | | 3)  ExifVersion = 0221
+  | |     - Tag 0x9000 (4 bytes, undef[4])
+  | | 4)  DateTimeOriginal = 2003:12:04 06:46:52
+  | |     - Tag 0x9003 (20 bytes, string[20])
+  [etc...]
+
+ +

Verbose = 3

+

Adds a hex dump of the binary data associated with each tag. The hex offsets +are relative to the start of the file, unlike the default HtmlDump output where +offsets are relative to the start of the TIFF header (the native base for +TIFF/EXIF offsets). If the data is very long, only the first few lines of the +dump are printed:

+ +
+  ExifToolVersion = 10.00
+  FileName = Canon.jpg
+  Directory = t/images
+  FileSize = 2697
+  FileModifyDate = 1159902631
+  FileAccessDate = 1439991933
+  FileInodeChangeDate = 1439903472
+  FilePermissions = 33188
+  FileType = JPEG
+  FileTypeExtension = JPG
+  MIMEType = image/jpeg
+JPEG APP1 (2442 bytes):
+    0006: 45 78 69 66 00 00 49 49 2a 00 08 00 00 00 09 00 [Exif..II*.......]
+    0016: 0f 01 02 00 06 00 00 00 7a 00 00 00 10 01 02 00 [........z.......]
+    0026: 18 00 00 00 80 00 00 00 12 01 03 00 01 00 00 00 [................]
+    0036: 01 00 00 00 1a 01 05 00 01 00 00 00 98 00 00 00 [................]
+    0046: 1b 01 05 00 01 00 00 00 a0 00 00 00 28 01 03 00 [............(...]
+    0056: 01 00 00 00 02 00 00 00 32 01 02 00 14 00 00 00 [........2.......]
+    0066: a8 00 00 00 13 02 03 00 01 00 00 00 01 00 00 00 [................]
+    [snip 2330 bytes]
+  ExifByteOrder = II
+  + [IFD0 directory with 9 entries]
+  | 0)  Make = Canon
+  |     - Tag 0x010f (6 bytes, string[6]):
+  |         0086: 43 61 6e 6f 6e 00                               [Canon.]
+  | 1)  Model = Canon EOS DIGITAL REBEL
+  |     - Tag 0x0110 (24 bytes, string[24]):
+  |         008c: 43 61 6e 6f 6e 20 45 4f 53 20 44 49 47 49 54 41 [Canon EOS DIGITA]
+  |         009c: 4c 20 52 45 42 45 4c 00                         [L REBEL.]
+  | 2)  Orientation = 1
+  |     - Tag 0x0112 (2 bytes, int16u[1]):
+  |         0036: 01 00                                           [..]
+  | 3)  XResolution = 180 (180/1)
+  |     - Tag 0x011a (8 bytes, rational64u[1]):
+  |         00a4: b4 00 00 00 01 00 00 00                         [........]
+  | 4)  YResolution = 180 (180/1)
+  |     - Tag 0x011b (8 bytes, rational64u[1]):
+  |         00ac: b4 00 00 00 01 00 00 00                         [........]
+  | 5)  ResolutionUnit = 2
+  |     - Tag 0x0128 (2 bytes, int16u[1]):
+  |         005a: 02 00                                           [..]
+  | 6)  ModifyDate = 2003:12:04 06:46:52
+  |     - Tag 0x0132 (20 bytes, string[20]):
+  |         00b4: 32 30 30 33 3a 31 32 3a 30 34 20 30 36 3a 34 36 [2003:12:04 06:46]
+  |         00c4: 3a 35 32 00                                     [:52.]
+  | 7)  YCbCrPositioning = 1
+  |     - Tag 0x0213 (2 bytes, int16u[1]):
+  |         0072: 01 00                                           [..]
+  | 8)  ExifOffset (SubDirectory) -->
+  |     - Tag 0x8769 (4 bytes, int32u[1]):
+  |         007e: bc 00 00 00                                     [....]
+  | + [ExifIFD directory with 31 entries]
+  | | 0)  ExposureTime = 4 (4/1)
+  | |     - Tag 0x829a (8 bytes, rational64u[1]):
+  | |         0242: 04 00 00 00 01 00 00 00                         [........]
+  | | 1)  FNumber = 14 (14/1)
+  | |     - Tag 0x829d (8 bytes, rational64u[1]):
+  | |         024a: 0e 00 00 00 01 00 00 00                         [........]
+  | | 2)  ISO = 100
+  | |     - Tag 0x8827 (2 bytes, int16u[1]):
+  | |         00ea: 64 00                                           [d.]
+  | | 3)  ExifVersion = 0221
+  | |     - Tag 0x9000 (4 bytes, undef[4]):
+  | |         00f6: 30 32 32 31                                     [0221]
+  | | 4)  DateTimeOriginal = 2003:12:04 06:46:52
+  | |     - Tag 0x9003 (20 bytes, string[20]):
+  | |         0252: 32 30 30 33 3a 31 32 3a 30 34 20 30 36 3a 34 36 [2003:12:04 06:46]
+  | |         0262: 3a 35 32 00                                     [:52.]
+  [etc...]
+
+ +

Verbose = 4 and Verbose = 5

+

These Verbose levels give similar output to Verbose = 3, except that with level +4 there is no limit on the length of data dumps of tag values, and with level 5 +the limit is also removed for dumps of JPEG segments. Note that the output may +be very long at these levels.

+ +
+

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/html/writing.html b/ExifTool/html/writing.html new file mode 100644 index 0000000..61b0d14 --- /dev/null +++ b/ExifTool/html/writing.html @@ -0,0 +1,239 @@ + + + + Writing Meta Information + + + + + +

Writing Meta Information

+ +

Abstract

+

Writing meta information is more complicated than it may appear at first +glance, which may be one reason why there are very few utilities around that do +it. ExifTool uses tag names to identify the different pieces of meta information +that can be extracted from a file. There are thousands of different tags that +ExifTool recognizes, and many of these tag names are common between different +metadata formats (the WhiteBalance tag is the worst offender, and can be found +in 65 different places [ExifTool 10.64]), and sometimes the information can even +be stored in different places within a single format. Couple this with the fact +that many manufacturers store meta information in undocumented formats which +must be reverse engineered (and each of which have their particular quirks), and +you have a very complex situation.

+ +

ExifTool attempts to simplify this situation as much as possible by making +reasonable decisions about where to write the information you specify, yet it +maintains flexibility by allowing you to configure its priorities if necessary, +or even override the decision making process entirely.

+ + +

Background

+

For a long time, I resisted adding write abilities to ExifTool even though it +was an oft-requested feature. My concerns in adding this feature were:

+
    +
  1. It would complicate the ExifTool interface and make it too confusing for +typical users.
  2. +
  3. It would complicate the code enough to slow down processing for normal use.
  4. +
  5. It would take a LOT of work to implement.
  6. +
+

After thinking about this for a while, I was finally able to come up with some +solutions:

+ +

1. I designed an interface that I think is easy to use for people who +don't want to know the details of the file structure, yet powerful enough for +people who want to do very specific things to the information.

+ +

2. I isolated all of the writing code as much as possible into separate files +which autoload as required. This keeps the compilation fast for people who +don't require the write feature. Also, I have left the reading routines +unchanged, so they aren't slowed down by the extra code needed when writing +information. Unfortunately, this meant I couldn't borrow a lot of code from the +read routines (even more work for me!), but it had the advantage that I could +perform additional optimizations in the write routines that I couldn't do +otherwise. Although the startup costs of this implementation are fairly high +(for writing only), it should be quite fast for batch writing of multiple files.

+ +

3. I decided to bite the bullet and invest the time required (...guess what I +did for my Christmas vacation!). Although I thought that a big project like +this would be better suited to C++ (faster execution and a broader potential +user base), after programming this so far in Perl I have grown to really +appreciate the automatic memory handling and other great features of Perl such +as hash lookups and incredible flexibility in text manipulations afforded by +regular expressions.

+ + +

Current Implementation

+

Currently, ExifTool can write most of the EXIF tags that anyone could reasonably +want to change (but some tags are protected because they describe physical +characteristics of the image that you can not change with ExifTool, eg. +Compression). Also, all of the GPS, IPTC and XMP information and most of the +MakerNotes information can be edited. This gives you great power, but with +great power, comes great responsibility...

+ +

It is possible for you to write nonsense into a file, which could cause other +image readers to throw up their hands in despair and refuse to read the image. +For this reason, it is best to always preserve the original copy of your image +file. The "exiftool" script does this for you automatically by renaming the +original file and always working on a copy.

+ +

The writing logic for ExifTool is the reverse of the reading logic. You +provide human-readable values and ExifTool will perform the conversions for you. +For instance, you can set "WhiteBalance" to "Daylight" and ExifTool will change +the value of WhiteBalance in the image wherever the tag is found provided that +"Daylight" is a valid value for that location. ExifTool will even do some simple +matching so that you could even just set it to "day", and ExifTool will search +through the valid values and will choose the one that contains the string "day". +If the value is ambiguous, the tag will not be set. If no tags can be set with +the specified value, ExifTool returns an error message.

+ +

The tag values can also be specified at a numerical level by disabling the +print conversions that are normally applied. This can be done on a tag-by-tag +basis or on a global basis through either the application or the API.

+ +

As well as changing tag values wherever they are found in the image, exiftool +will also create the tag in the preferred group if it didn't exist there before. +By default, the preferred group is the first of the following where the tag is +found: 1) EXIF, 2) IPTC, 3) XMP. Alternatively, the desired group (in family 0 +or 1) can be specified so ExifTool only writes the tag to a single location. For +example, with the command line interface, this is done using an argument like +"-EXIF:WhiteBalance=Manual".

+ +

If a tag is added to a group that doesn't exist, the new group is created in +the file, and required mandatory tags may be created. Conversely, if the last +non-mandatory tag is deleted from a group, the group is removed from the file.

+ + +

Mandatory Tags

+ +

The EXIF and IPTC standards both specify some mandatory tags. ExifTool will +automatically create many of these mandatory tags as required when writing new +information (and remove them again when deleting information if only mandatory +tags remain). However, some mandatory tags (particularly in the IPTC +information) can not be easily added automatically, so it is left up to the user +to add these tags if required.

+ +
+Rant: Let me say that the whole concept of mandatory tags is flawed. +Instead of mandatory tags, the standard should specify default values to be +assumed if the tags don't exist. A robust reader has to do this anyway, so it +is redundant to require that this information must be written. In the case +where there is no simple default value, the reader must be able to deal with the +missing tag, otherwise it places the burden on the writer to magically pull a +reasonable value out of thin air. Of course, you may say that the writer could +get this information from the user, but conditions like this add an unnecessary +level complexity to the user interface. +
+ + +

The JPEG Segment Size Limitation

+ +

An unfortunate aspect of the JPEG format is that the size of a single segment +is limited to less than 64 kB. With the 2-byte size word at the start of each +segment, this leaves 65533 bytes for data. The EXIF specification states that +the data must fit within a single APP1 segment (which results in the +preview image problem discussed below), however APP13 Photoshop and +APP2 ICC Profile and FPXR information may span multiple segments. This +multi-segment information is handled properly by ExifTool.

+ +

JPEG comments may also exceed the size of a single COM segment. If +necessary, comments are automatically split into separate segments when writing. +However, when reading they are not joined together because some utilities store +distinctly different comments in separate segments. To extract all JPEG +comments into a single file, and combine any comments that may have been split +into multiple segments, use "exiftool -a -b -comment src.jpg > +comment.out".

+ + +

The Preview Image Problem

+ +

Writing the preview image in JPEG files poses many problems of its own. These +problems stem from the fact that the JPEG standard is inadequate for storing +large preview images due to the 64kB limit on segment size as mentioned above. +(Note that TIFF images don't have this problem since they have a 4GB limit.) +Some manufacturers get around this by appending the preview after the normal end +of the JPEG file (JPEG EOI), but this causes complications because it means that +the preview image pointers in the EXIF information now point outside the EXIF +segment. This is truly unfortunate because it greatly complicates things for +image writing software. Most other software can't deal with a preview image and +will simply remove a preview like this when rewriting the file.

+ +

However, as of ExifTool version 5, the preview images are handled properly +when writing EXIF information in JPEG files. But for reasons of efficiency, the +EXIF segment is not edited when writing information if no EXIF tags are being +changed (eg. if only XMP or IPTC information is being edited). In this case, +the preview image pointers could be invalidated because the length of the data +between the EXIF segment (which comes near the start of the file) and the +preview image (at the end of the file) is likely to change. ExifTool gets +around this when reading JPEG images by looking for the preview at the end of +the file and updating pointers if necessary, but the preview image may not be +readable by other software (it should be noted though that very few image +readers even know the preview image exists). However, the preview pointers in +such a file can be fixed if necessary by simply using ExifTool to edit any EXIF +information.

+ +

2009-05-26: New Samsung cameras recently started embedding preview +images larger than 64 kB, and of course they created a new technique to do so. +If they were smart, they would have developed a simple technique that could be +used by others in the future, but of course they were stupid, and didn't think +that far ahead. (Such is the normal path of dumb camera manufacturers when it +comes to metadata.)

+ +

The new Samsung models simply split the preview and write it to separate APP2 +segments with no header. If they had written a header (like "PREVIEW\0" for +example), then the technique could be portable and useful. But they didn't. +Without a header, the data can not easily be distinguished from other random APP2 +data, so this technique is not generally useful. Of course, there are +disadvantages with splitting up a preview into separate JPEG segments, so this +technique in general is not ideal.

+ +

2009-06-12: +CIPA +has recently released a "Multi-Picture Format" standard for storing large +images in JPEG files. Again, there is a big problem with this standard: It +uses offsets that are relative to the start of the MPF header (in the new MPF +APP2 segment) to reference images after the JPEG EOI. These offsets will +quickly be broken if any data after the MPF segment changes length. This +problem could have been avoided if offsets had been specified relative to the +end of file, but it is too late for this now that the specification is +public.

+ +

The only workable alternative I can see is to enforce the rule that the MPF +APP2 segment must come after all other APP segments. (It would have been smart +if this was specified in the CIPA standard, but sadly this isn't the case.) If +this is done, then metadata in the remaining APP segments (EXIF, IPTC, XMP, etc) +can safely be edited without breaking the MPF offsets. I suggest that all +metadata editors employ this strategy, regardless of the segment order specified +in the standard (which says that the MPF APP2 segment must come immediately +after the EXIF APP1 segment).

+ + +

IFD0/ExifIFD Ambiguity

+ +

ExifTool has a preferred location (IFD) where it writes all EXIF tags. +However, a number of tags are written to different locations by various digital +cameras or image editors. Specifically, the following tags have been observed +in both IFD0 and ExifIFD: Make, Model, Software, Artist, DateTimeOriginal, +SensingMethod, CustomRendered, ExposureMode, WhiteBalance, DigitalZoomRatio and +SceneCaptureType. To handle this ambiguity, ExifTool will delete the tag if it +exists in IFD0 when it is written to ExifIFD, and vice versa.

+ +
+Created Dec 30, 2004
+Last revised Oct 4, 2010 +

<-- Back to ExifTool home page

+ + diff --git a/ExifTool/lib/File/RandomAccess.pm b/ExifTool/lib/File/RandomAccess.pm new file mode 100644 index 0000000..4c368bd --- /dev/null +++ b/ExifTool/lib/File/RandomAccess.pm @@ -0,0 +1,418 @@ +#------------------------------------------------------------------------------ +# File: RandomAccess.pm +# +# Description: Buffer to support random access reading of sequential file +# +# Revisions: 02/11/2004 - P. Harvey Created +# 02/20/2004 - P. Harvey Added flag to disable SeekTest in new() +# 11/18/2004 - P. Harvey Fixed bug with seek relative to end of file +# 01/02/2005 - P. Harvey Added DEBUG code +# 01/09/2006 - P. Harvey Fixed bug in ReadLine() when using +# multi-character EOL sequences +# 02/20/2006 - P. Harvey Fixed bug where seek past end of file could +# generate "substr outside string" warning +# 06/10/2006 - P. Harvey Decreased $CHUNK_SIZE from 64k to 8k +# 11/23/2006 - P. Harvey Limit reads to < 0x80000000 bytes +# 11/26/2008 - P. Harvey Fixed bug in ReadLine when reading from a +# scalar with a multi-character newline +# 01/24/2009 - PH Protect against reading too much at once +# 10/04/2018 - PH Added NoBuffer option +# +# Notes: Calls the normal file i/o routines unless SeekTest() fails, in +# which case the file is buffered in memory to allow random access. +# SeekTest() is called automatically when the object is created +# unless specified. +# +# May also be used for string i/o (just pass a scalar reference) +# +# Legal: Copyright (c) 2003-2023 Phil Harvey (philharvey66 at gmail.com) +# This library is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +#------------------------------------------------------------------------------ + +package File::RandomAccess; + +use strict; +require 5.002; +require Exporter; + +use vars qw($VERSION @ISA @EXPORT_OK); +$VERSION = '1.11'; +@ISA = qw(Exporter); + +sub Read($$$); + +# constants +my $CHUNK_SIZE = 8192; # size of chunks to read from file (must be power of 2) +my $SKIP_SIZE = 65536; # size to skip when fast-forwarding over sequential data +my $SLURP_CHUNKS = 16; # read this many chunks at a time when slurping + +#------------------------------------------------------------------------------ +# Create new RandomAccess object +# Inputs: 0) reference to RandomAccess object or RandomAccess class name +# 1) file reference or scalar reference +# 2) flag set if file is already random access (disables automatic SeekTest) +sub new($$;$) +{ + my ($that, $filePt, $isRandom) = @_; + my $class = ref($that) || $that; + my $self; + + if (ref $filePt eq 'SCALAR') { + # string i/o + $self = { + BUFF_PT => $filePt, + BASE => 0, + POS => 0, + LEN => length($$filePt), + TESTED => -1, + }; + bless $self, $class; + } else { + # file i/o + my $buff = ''; + $self = { + FILE_PT => $filePt, # file pointer + BUFF_PT => \$buff, # reference to file data + BASE => 0, # location of start of buffer in file + POS => 0, # current position in buffer + LEN => 0, # length of data in buffer + TESTED => 0, # 0=untested, 1=passed, -1=failed (requires buffering) + }; + bless $self, $class; + $self->SeekTest() unless $isRandom; + } + return $self; +} + +#------------------------------------------------------------------------------ +# Enable DEBUG code +# Inputs: 0) reference to RandomAccess object +sub Debug($) +{ + my $self = shift; + $self->{DEBUG} = { }; +} + +#------------------------------------------------------------------------------ +# Perform seek test and turn on buffering if necessary +# Inputs: 0) reference to RandomAccess object +# Returns: 1 if seek test passed (ie. no buffering required) +# Notes: Must be done before any other i/o +sub SeekTest($) +{ + my $self = shift; + unless ($self->{TESTED}) { + my $fp = $self->{FILE_PT}; + if (seek($fp, 1, 1) and seek($fp, -1, 1)) { + $self->{TESTED} = 1; # test passed + } else { + $self->{TESTED} = -1; # test failed (requires buffering) + } + } + return $self->{TESTED} == 1 ? 1 : 0; +} + +#------------------------------------------------------------------------------ +# Get current position in file +# Inputs: 0) reference to RandomAccess object +# Returns: current position in file +sub Tell($) +{ + my $self = shift; + my $rtnVal; + if ($self->{TESTED} < 0) { + $rtnVal = $self->{POS} + $self->{BASE}; + } else { + $rtnVal = tell($self->{FILE_PT}); + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Seek to position in file +# Inputs: 0) reference to RandomAccess object +# 1) position, 2) whence (0 or undef=from start, 1=from cur pos, 2=from end) +# Returns: 1 on success +# Notes: When buffered, this doesn't quite behave like seek() since it will return +# success even if you seek outside the limits of the file. However if you +# do this, you will get an error on your next Read(). +sub Seek($$;$) +{ + my ($self, $num, $whence) = @_; + $whence = 0 unless defined $whence; + my $rtnVal; + if ($self->{TESTED} < 0) { + my $newPos; + if ($whence == 0) { + $newPos = $num - $self->{BASE}; # from start of file + } elsif ($whence == 1) { + $newPos = $num + $self->{POS}; # relative to current position + } elsif ($self->{NoBuffer} and $self->{FILE_PT}) { + $newPos = -1; # (can't seek relative to end if no buffering) + } else { + $self->Slurp(); # read whole file into buffer + $newPos = $num + $self->{LEN}; # relative to end of file + } + if ($newPos >= 0) { + $self->{POS} = $newPos; + $rtnVal = 1; + } + } else { + $rtnVal = seek($self->{FILE_PT}, $num, $whence); + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Read from the file +# Inputs: 0) reference to RandomAccess object, 1) buffer, 2) bytes to read +# Returns: Number of bytes read +sub Read($$$) +{ + my $self = shift; + my $len = $_[1]; + my $rtnVal; + + # protect against reading too much at once + # (also from dying with a "Negative length" error) + if ($len & 0xf8000000) { + return 0 if $len < 0; + # read in smaller blocks because Windows attempts to pre-allocate + # memory for the full size, which can lead to an out-of-memory error + my $maxLen = 0x4000000; # (MUST be less than bitmask in "if" above) + my $num = Read($self, $_[0], $maxLen); + return $num if $num < $maxLen; + for (;;) { + $len -= $maxLen; + last if $len <= 0; + my $l = $len < $maxLen ? $len : $maxLen; + my $buff; + my $n = Read($self, $buff, $l); + last unless $n; + $_[0] .= $buff; + $num += $n; + last if $n < $l; + } + return $num; + } + # read through our buffer if necessary + if ($self->{TESTED} < 0) { + # purge old data before reading in NoBuffer mode + $self->Purge() or return 0 if $self->{NoBuffer}; + my $buff; + my $newPos = $self->{POS} + $len; + # number of bytes to read from file + my $num = $newPos - $self->{LEN}; + if ($num > 0 and $self->{FILE_PT}) { + # read data from file in multiples of $CHUNK_SIZE + $num = (($num - 1) | ($CHUNK_SIZE - 1)) + 1; + $num = read($self->{FILE_PT}, $buff, $num); + if ($num) { + ${$self->{BUFF_PT}} .= $buff; + $self->{LEN} += $num; + } + } + # number of bytes left in data buffer + $num = $self->{LEN} - $self->{POS}; + if ($len <= $num) { + $rtnVal = $len; + } elsif ($num <= 0) { + $_[0] = ''; + return 0; + } else { + $rtnVal = $num; + } + # return data from our buffer + $_[0] = substr(${$self->{BUFF_PT}}, $self->{POS}, $rtnVal); + $self->{POS} += $rtnVal; + } else { + # read directly from file + $_[0] = '' unless defined $_[0]; + $rtnVal = read($self->{FILE_PT}, $_[0], $len) || 0; + } + if ($self->{DEBUG}) { + my $pos = $self->Tell() - $rtnVal; + unless ($self->{DEBUG}->{$pos} and $self->{DEBUG}->{$pos} > $rtnVal) { + $self->{DEBUG}->{$pos} = $rtnVal; + } + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Read a line from file (end of line is $/) +# Inputs: 0) reference to RandomAccess object, 1) buffer +# Returns: Number of bytes read +sub ReadLine($$) +{ + my $self = shift; + my $rtnVal; + my $fp = $self->{FILE_PT}; + + if ($self->{TESTED} < 0) { + my ($num, $buff); + $self->Purge() or return 0 if $self->{NoBuffer}; + my $pos = $self->{POS}; + if ($fp) { + # make sure we have some data after the current position + while ($self->{LEN} <= $pos) { + $num = read($fp, $buff, $CHUNK_SIZE); + return 0 unless $num; + ${$self->{BUFF_PT}} .= $buff; + $self->{LEN} += $num; + } + # scan and read until we find the EOL (or hit EOF) + for (;;) { + $pos = index(${$self->{BUFF_PT}}, $/, $pos); + if ($pos >= 0) { + $pos += length($/); + last; + } + $pos = $self->{LEN}; # have scanned to end of buffer + $num = read($fp, $buff, $CHUNK_SIZE) or last; + ${$self->{BUFF_PT}} .= $buff; + $self->{LEN} += $num; + } + } else { + # string i/o + $pos = index(${$self->{BUFF_PT}}, $/, $pos); + if ($pos < 0) { + $pos = $self->{LEN}; + $self->{POS} = $pos if $self->{POS} > $pos; + } else { + $pos += length($/); + } + } + # read the line from our buffer + $rtnVal = $pos - $self->{POS}; + $_[0] = substr(${$self->{BUFF_PT}}, $self->{POS}, $rtnVal); + $self->{POS} = $pos; + } else { + $_[0] = <$fp>; + if (defined $_[0]) { + $rtnVal = length($_[0]); + } else { + $rtnVal = 0; + } + } + if ($self->{DEBUG}) { + my $pos = $self->Tell() - $rtnVal; + unless ($self->{DEBUG}->{$pos} and $self->{DEBUG}->{$pos} > $rtnVal) { + $self->{DEBUG}->{$pos} = $rtnVal; + } + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Read whole file into buffer (without changing read pointer) +# Inputs: 0) reference to RandomAccess object +sub Slurp($) +{ + my $self = shift; + my $fp = $self->{FILE_PT} || return; + # read whole file into buffer (in large chunks) + my ($buff, $num); + while (($num = read($fp, $buff, $CHUNK_SIZE * $SLURP_CHUNKS)) != 0) { + ${$self->{BUFF_PT}} .= $buff; + $self->{LEN} += $num; + } +} + +#------------------------------------------------------------------------------ +# Purge internal buffer [internal use only] +# Inputs: 0) reference to RandomAccess object +# Returns: 1 on success, or 0 if current buffer position is negative +# Notes: This is called only in NoBuffer mode +sub Purge($) +{ + my $self = shift; + return 1 unless $self->{FILE_PT}; + return 0 if $self->{POS} < 0; # error if we can't read from here + if ($self->{POS} > $CHUNK_SIZE) { + my $purge = $self->{POS} - ($self->{POS} % $CHUNK_SIZE); + if ($purge >= $self->{LEN}) { + # read up to current position in 64k chunks, discarding as we go + while ($self->{POS} > $self->{LEN}) { + $self->{BASE} += $self->{LEN}; + $self->{POS} -= $self->{LEN}; + ${$self->{BUFF_PT}} = ''; + $self->{LEN} = read($self->{FILE_PT}, ${$self->{BUFF_PT}}, $SKIP_SIZE); + last if $self->{LEN} < $SKIP_SIZE; + } + } elsif ($purge > 0) { + ${$self->{BUFF_PT}} = substr ${$self->{BUFF_PT}}, $purge; + $self->{BASE} += $purge; + $self->{POS} -= $purge; + $self->{LEN} -= $purge; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Set binary mode +# Inputs: 0) reference to RandomAccess object +sub BinMode($) +{ + my $self = shift; + binmode($self->{FILE_PT}) if $self->{FILE_PT}; +} + +#------------------------------------------------------------------------------ +# Close the file and free the buffer +# Inputs: 0) reference to RandomAccess object +sub Close($) +{ + my $self = shift; + + if ($self->{DEBUG}) { + local $_; + if ($self->Seek(0,2)) { + $self->{DEBUG}->{$self->Tell()} = 0; # set EOF marker + my $last; + my $tot = 0; + my $bad = 0; + foreach (sort { $a <=> $b } keys %{$self->{DEBUG}}) { + my $pos = $_; + my $len = $self->{DEBUG}->{$_}; + if (defined $last and $last < $pos) { + my $bytes = $pos - $last; + $tot += $bytes; + $self->Seek($last); + my $buff; + $self->Read($buff, $bytes); + my $warn = ''; + if ($buff =~ /[^\0]/) { + $bad += ($pos - $last); + $warn = ' - NON-ZERO!'; + } + printf "0x%.8x - 0x%.8x (%d bytes)$warn\n", $last, $pos, $bytes; + } + my $cur = $pos + $len; + $last = $cur unless defined $last and $last > $cur; + } + print "$tot bytes missed"; + $bad and print ", $bad non-zero!"; + print "\n"; + } else { + warn "File::RandomAccess DEBUG not working (file already closed?)\n"; + } + delete $self->{DEBUG}; + } + # close the file + if ($self->{FILE_PT}) { + close($self->{FILE_PT}); + delete $self->{FILE_PT}; + } + # reset the buffer + my $emptyBuff = ''; + $self->{BUFF_PT} = \$emptyBuff; + $self->{BASE} = 0; + $self->{LEN} = 0; + $self->{POS} = 0; +} + +#------------------------------------------------------------------------------ +1; # end diff --git a/ExifTool/lib/File/RandomAccess.pod b/ExifTool/lib/File/RandomAccess.pod new file mode 100644 index 0000000..d8276f2 --- /dev/null +++ b/ExifTool/lib/File/RandomAccess.pod @@ -0,0 +1,250 @@ +#------------------------------------------------------------------------------ +# File: RandomAccess.pod -- Documentation for File::RandomAccess +# +# Description: Buffer to support random access reading of sequential file +# +# Legal: Copyright (c) 2003-2023 Phil Harvey (philharvey66 at gmail.com) +# This library is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +#------------------------------------------------------------------------------ + +=head1 NAME + +File::RandomAccess - Random access reads of sequential file or scalar + +=head1 SYNOPSIS + + use File::RandomAccess; + + $raf = new File::RandomAccess(\*FILE, $disableSeekTest); + + $raf = new File::RandomAccess(\$data); + + $err = $raf->Seek($pos); + $num = $raf->Read($buff, $bytes); + +=head1 DESCRIPTION + +Allows random access to sequential file by buffering the file if necessary. +Also allows access to data in memory to be accessed as if it were a file. + +=head1 METHODS + +=over 4 + +=item B + +Creates a new RandomAccess object given a file reference or +reference to data in memory. + + # Read from open file or pipe + $raf = new File::RandomAccess(\*FILE); + + # Read from data in memory + $raf = new File::RandomAccess(\$data); + +=over 4 + +=item Inputs: + +0) Reference to RandomAccess object or RandomAccess class name. + +1) File reference or scalar reference. + +2) Flag set if file is already random access (disables automatic SeekTest). + +=item Returns: + +Reference to RandomAccess object. + +=back + +=item B + +Performs test seek() on file to determine if buffering is necessary. If +the seek() fails, then the file is buffered to allow random access. +B() is automatically called from B unless specified. + + $result = $raf->SeekTest(); + +=over 4 + +=item Inputs: + +0) Reference to RandomAccess object. + +=item Returns: + +1 if seek test passed (ie. no buffering required). + +=item Notes: + +Must be called before any other i/o. + +=back + +=item B + +Get current position in file + + $pos = $raf->Tell(); + +=over 4 + +=item Inputs: + +0) Reference to RandomAccess object. + +=item Returns: + +Current position in file + +=back + +=item B + +Seek to specified position in file. When buffered, this doesn't quite +behave like seek() since it returns success even if you seek outside the +limits of the file. + + $success = $raf->Seek($pos, 0); + +=over 4 + +=item Inputs: + +0) Reference to RandomAccess object. + +1) Position. + +2) Whence (0=from start, 1=from cur pos, 2=from end). + +=item Returns: + +1 on success, 0 otherwise + +=back + +=item B + +Read data from the file. + + $num = $raf->Read($buff, 1024); + +=over 4 + +=item Inputs: + +0) Reference to RandomAccess object. + +1) Buffer. + +2) Number of bytes to read. + +=item Returns: + +Number of bytes actually read. + +=back + +=item B + +Read a line from file (end of line is $/). + +=over 4 + +=item Inputs: + +0) Reference to RandomAccess object. + +1) Buffer. + +=item Returns: + +Number of bytes read. + +=back + +=item B + +Read whole file into buffer, without changing read pointer. + +=over 4 + +=item Inputs: + +0) Reference to RandomAccess object. + +=item Returns: + +Nothing. + +=back + +=item B + +Set binary mode for file. + +=over 4 + +=item Inputs: + +0) Reference to RandomAccess object. + +=item Returns: + +Nothing. + +=back + +=item B + +Close the file and free the buffer. + +=over 4 + +=item Inputs: + +0) Reference to RandomAccess object. + +=item Returns: + +Nothing. + +=back + +=back + +=head1 OPTIONS + +=over 4 + +=item B + +Avoid buffering sequential files. + + $raf->{NoBuffer} = 1; + +When this option is set, old data is purged from the internal buffer before +a read operation on a sequential file. In this mode, memory requirements +may be significantly reduced when reading sequential files, but seeking +backward is limited to within the size of the internal buffer (which will be +at least as large as the last returned data block), and seeking relative to +the end of file is not allowed. + +=back + +=head1 AUTHOR + +Copyright 2003-2023 Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L + +=cut + +# end diff --git a/ExifTool/lib/Image/ExifTool.pm b/ExifTool/lib/Image/ExifTool.pm new file mode 100644 index 0000000..9593b58 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool.pm @@ -0,0 +1,9370 @@ +#------------------------------------------------------------------------------ +# File: ExifTool.pm +# +# Description: Read and write meta information +# +# URL: https://exiftool.org/ +# +# Revisions: Nov. 12/2003 - P. Harvey Created +# (See html/history.html for revision history) +# +# Legal: Copyright (c) 2003-2023, Phil Harvey (philharvey66 at gmail.com) +# This library is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +#------------------------------------------------------------------------------ + +package Image::ExifTool; + +use strict; +require 5.004; # require 5.004 for UNIVERSAL::isa (otherwise 5.002 would do) +require Exporter; +use File::RandomAccess; +use overload; + +use vars qw($VERSION $RELEASE @ISA @EXPORT_OK %EXPORT_TAGS $AUTOLOAD @fileTypes + %allTables @tableOrder $exifAPP1hdr $xmpAPP1hdr $xmpExtAPP1hdr + $psAPP13hdr $psAPP13old @loadAllTables %UserDefined $evalWarning + %noWriteFile %magicNumber @langs $defaultLang %langName %charsetName + %mimeType $swapBytes $swapWords $currentByteOrder %unpackStd + %jpegMarker %specialTags %fileTypeLookup $testLen $exeDir + %static_vars); + +$VERSION = '12.67'; +$RELEASE = ''; +@ISA = qw(Exporter); +%EXPORT_TAGS = ( + # all public non-object-oriented functions: + Public => [qw( + ImageInfo AvailableOptions GetTagName GetShortcuts GetAllTags + GetWritableTags GetAllGroups GetDeleteGroups GetFileType CanWrite + CanCreate AddUserDefinedTags + )], + # exports not part of the public API, but used by ExifTool modules: + DataAccess => [qw( + ReadValue GetByteOrder SetByteOrder ToggleByteOrder Get8u Get8s Get16u + Get16s Get32u Get32s Get64u GetFloat GetDouble GetFixed32s Write + WriteValue Tell Set8u Set8s Set16u Set32u Set64u Set64s + )], + Utils => [qw(GetTagTable TagTableKeys GetTagInfoList AddTagToTable HexDump)], + Vars => [qw(%allTables @tableOrder @fileTypes)], +); + +# set all of our EXPORT_TAGS in EXPORT_OK +Exporter::export_ok_tags(keys %EXPORT_TAGS); + +# test for problems that can arise if encoding.pm is used +{ my $t = "\xff"; die "Incompatible encoding!\n" if ord($t) != 0xff; } + +# The following functions defined in Image::ExifTool::Writer.pl are declared +# here so their prototypes will be available. These Writer routines will be +# autoloaded when any of them is called. +sub SetNewValue($;$$%); +sub SetNewValuesFromFile($$;@); +sub GetNewValue($$;$); +sub GetNewValues($$;$); +sub CountNewValues($); +sub SaveNewValues($); +sub RestoreNewValues($); +sub WriteInfo($$;$$); +sub SetFileModifyDate($$;$$$); +sub SetFileName($$;$$$); +sub SetSystemTags($$); +sub GetAllTags(;$); +sub GetWritableTags(;$); +sub GetAllGroups($;$); +sub GetNewGroups($); +sub GetDeleteGroups(); +sub AddUserDefinedTags($%); +sub SetAlternateFile($$$); +# non-public routines below +sub InsertTagValues($$$;$$$); +sub IsWritable($); +sub IsSameFile($$$); +sub IsRawType($); +sub GetNewFileName($$); +sub LoadAllTables(); +sub GetNewTagInfoList($;$); +sub GetNewTagInfoHash($@); +sub GetLangInfo($$); +sub Get64s($$); +sub Get64u($$); +sub GetFixed64s($$); +sub GetExtended($$); +sub Set64u(@); +sub Set64s(@); +sub DecodeBits($$;$); +sub EncodeBits($$;$$); +sub Filter($$$); +sub HexDump($;$%); +sub DumpTrailer($$); +sub DumpUnknownTrailer($$); +sub VerboseInfo($$$%); +sub VerboseValue($$$;$); +sub VPrint($$@); +sub Rationalize($;$); +sub Write($@); +sub WriteTrailerBuffer($$$); +sub AddNewTrailers($;@); +sub Tell($); +sub WriteValue($$;$$$$); +sub WriteDirectory($$$;$); +sub WriteBinaryData($$$); +sub CheckBinaryData($$$); +sub WriteTIFF($$$); +sub PackUTF8(@); +sub UnpackUTF8($); +sub SetPreferredByteOrder($;$); +sub ImageDataHash($$$;$$); +sub CopyBlock($$$); +sub CopyFileAttrs($$$); +sub TimeNow(;$$); +sub InverseDateTime($$;$$); +sub NewGUID(); +sub MakeTiffHeader($$$$;$$); + +# other subroutine definitions +sub SplitFileName($); +sub EncodeFileName($$;$); +sub Open($*$;$); +sub Exists($$;$); +sub IsDirectory($$); +sub Rename($$$); +sub Unlink($@); +sub SetFileTime($$;$$$$); +sub DoEscape($$); +sub ConvertFileSize($); +sub ParseArguments($;@); #(defined in attempt to avoid mod_perl problem) +sub ReadValue($$$;$$$); + +# list of main tag tables to load in LoadAllTables() (sub-tables are recursed +# automatically). Note: They will appear in this order in the documentation +# unless tweaked in BuildTagLookup::GetTableOrder(). +@loadAllTables = qw( + PhotoMechanic Exif GeoTiff CanonRaw KyoceraRaw Lytro MinoltaRaw PanasonicRaw + SigmaRaw JPEG GIMP Jpeg2000 GIF BMP BMP::OS2 BMP::Extra BPG BPG::Extensions + WPG ICO PICT PNG MNG FLIF DjVu DPX OpenEXR ZISRAW MRC LIF MRC::FEI12 MIFF + PCX PGF PSP PhotoCD Radiance Other::PFM PDF PostScript Photoshop::Header + Photoshop::Layers Photoshop::ImageData FujiFilm::RAF FujiFilm::IFD + Samsung::Trailer Sony::SRF2 Sony::SR2SubIFD Sony::PMP ITC ID3 ID3::Lyrics3 + FLAC Ogg Vorbis APE APE::NewHeader APE::OldHeader Audible MPC MPEG::Audio + MPEG::Video MPEG::Xing M2TS QuickTime QuickTime::ImageFile QuickTime::Stream + QuickTime::Tags360Fly Matroska Matroska::StdTag MOI MXF DV Flash Flash::FLV + Real::Media Real::Audio Real::Metafile Red RIFF AIFF ASF WTV DICOM FITS MIE + JSON HTML XMP::SVG Palm Palm::MOBI Palm::EXTH Torrent EXE EXE::PEVersion + EXE::PEString EXE::MachO EXE::PEF EXE::ELF EXE::AR EXE::CHM LNK Font VCard + Text VCard::VCalendar VCard::VNote RSRC Rawzor ZIP ZIP::GZIP ZIP::RAR + ZIP::RAR5 RTF OOXML iWork ISO FLIR::AFF FLIR::FPF MacOS MacOS::MDItem + FlashPix::DocTable +); + +# alphabetical list of current Lang modules +@langs = qw(cs de en en_ca en_gb es fi fr it ja ko nl pl ru sk sv tr zh_cn zh_tw); + +$defaultLang = 'en'; # default language + +# language names +%langName = ( + cs => 'Czech (ÄŒeÅ¡tina)', + de => 'German (Deutsch)', + en => 'English', + en_ca => 'Canadian English', + en_gb => 'British English', + es => 'Spanish (Español)', + fi => 'Finnish (Suomi)', + fr => 'French (Français)', + it => 'Italian (Italiano)', + ja => 'Japanese (日本語)', + ko => 'Korean (한국어)', + nl => 'Dutch (Nederlands)', + pl => 'Polish (Polski)', + ru => 'Russian (РуÑÑкий)', + sk => 'Slovak (SlovenÄina)', + sv => 'Swedish (Svenska)', + 'tr'=> 'Turkish (Türkçe)', + zh_cn => 'Simplified Chinese (简体中文)', + zh_tw => 'Traditional Chinese (ç¹é«”中文)', +); + +# recognized file types, in the order we test unknown files +# Notes: 1) There is no need to test for like types separately here +# 2) Put types with weak file signatures at end of list to avoid false matches +# 3) PLIST must be in this list for the binary PLIST format, although it may +# cause a file to be checked twice for XML +@fileTypes = qw(JPEG EXV CRW DR4 TIFF GIF MRW RAF X3F JP2 PNG MIE MIFF PS PDF + PSD XMP BMP WPG BPG PPM RIFF AIFF ASF MOV MPEG Real SWF PSP FLV + OGG FLAC APE MPC MKV MXF DV PMP IND PGF ICC ITC FLIR FLIF FPF + LFP HTML VRD RTF FITS XCF DSS QTIF FPX PICT ZIP GZIP PLIST RAR + 7Z BZ2 CZI TAR EXE EXR HDR CHM LNK WMF AVC DEX DPX RAW Font RSRC + M2TS MacOS PHP PCX DCX DWF DWG DXF WTV Torrent VCard LRI R3D AA + PDB PFM2 MRC LIF JXL MOI ISO ALIAS JSON MP3 DICOM PCD ICO TXT); + +# file types that we can write (edit) +my @writeTypes = qw(JPEG TIFF GIF CRW MRW ORF RAF RAW PNG MIE PSD XMP PPM EPS + X3F PS PDF ICC VRD DR4 JP2 JXL EXIF AI AIT IND MOV EXV FLIF + RIFF); +my %writeTypes; # lookup for writable file types (hash filled if required) + +# file extensions that we can't write for various base types +%noWriteFile = ( + TIFF => [ qw(3FR DCR K25 KDC SRF) ], + XMP => [ qw(SVG INX) ], + JP2 => [ qw(J2C JPC) ], + MOV => [ qw(INSV) ], +); +# file extensions that we can only write for various base types +my %onlyWriteFile = ( RIFF => [ qw(WEBP) ] ); + +# file types that we can create from scratch +# - must update CanCreate() documentation if this list is changed! +my %createTypes = map { $_ => 1 } qw(XMP ICC MIE VRD DR4 EXIF EXV); + +# file type lookup for all recognized file extensions (upper case) +# (if extension may be more than one type, the type is a list where +# the writable type should come first if it exists) +%fileTypeLookup = ( + '360' => ['MOV', 'GoPro 360 video'], + '3FR' => ['TIFF', 'Hasselblad RAW format'], + '3G2' => ['MOV', '3rd Gen. Partnership Project 2 audio/video'], + '3GP' => ['MOV', '3rd Gen. Partnership Project audio/video'], + '3GP2'=> '3G2', + '3GPP'=> '3GP', + '7Z' => ['7Z', '7z archive'], + A => ['EXE', 'Static library'], + AA => ['AA', 'Audible Audiobook'], + AAE => ['PLIST','Apple edit information'], + AAX => ['MOV', 'Audible Enhanced Audiobook'], + ACR => ['DICOM','American College of Radiology ACR-NEMA'], + ACFM => ['Font', 'Adobe Composite Font Metrics'], + AFM => ['Font', 'Adobe Font Metrics'], + AMFM => ['Font', 'Adobe Multiple Master Font Metrics'], + AI => [['PDF','PS'], 'Adobe Illustrator'], + AIF => 'AIFF', + AIFC => ['AIFF', 'Audio Interchange File Format Compressed'], + AIFF => ['AIFF', 'Audio Interchange File Format'], + AIT => 'AI', + ALIAS=> ['ALIAS','MacOS file alias'], + APE => ['APE', "Monkey's Audio format"], + APNG => ['PNG', 'Animated Portable Network Graphics'], + ARW => ['TIFF', 'Sony Alpha RAW format'], + ARQ => ['TIFF', 'Sony Alpha Pixel-Shift RAW format'], + ASF => ['ASF', 'Microsoft Advanced Systems Format'], + AVC => ['AVC', 'Advanced Video Connection'], # (extensions are actually _AU,_AD,_IM,_ID) + AVI => ['RIFF', 'Audio Video Interleaved'], + AVIF => ['MOV', 'AV1 Image File Format'], + AZW => 'MOBI', # (see http://wiki.mobileread.com/wiki/AZW) + AZW3 => 'MOBI', + BMP => ['BMP', 'Windows Bitmap'], + BPG => ['BPG', 'Better Portable Graphics'], + BTF => ['BTF', 'Big Tagged Image File Format'], #(unofficial) + BZ2 => ['BZ2', 'BZIP2 archive'], + CHM => ['CHM', 'Microsoft Compiled HTML format'], + CIFF => ['CRW', 'Camera Image File Format'], + COS => ['COS', 'Capture One Settings'], + CR2 => ['TIFF', 'Canon RAW 2 format'], + CR3 => ['MOV', 'Canon RAW 3 format'], + CRM => ['MOV', 'Canon RAW Movie'], + CRW => ['CRW', 'Canon RAW format'], + CS1 => ['PSD', 'Sinar CaptureShop 1-Shot RAW'], + CSV => ['TXT', 'Comma-Separated Values'], + CUR => ['ICO', 'Windows Cursor'], + CZI => ['CZI', 'Zeiss Integrated Software RAW'], + DC3 => 'DICM', + DCM => 'DICM', + DCP => ['TIFF', 'DNG Camera Profile'], + DCR => ['TIFF', 'Kodak Digital Camera RAW'], + DCX => ['DCX', 'Multi-page PC Paintbrush'], + DEX => ['DEX', 'Dalvik Executable format'], + DFONT=> ['Font', 'Macintosh Data fork Font'], + DIB => ['BMP', 'Device Independent Bitmap'], + DIC => 'DICM', + DICM => ['DICOM','Digital Imaging and Communications in Medicine'], + DIR => ['DIR', 'Directory'], + DIVX => ['ASF', 'DivX media format'], + DJV => 'DJVU', + DJVU => ['AIFF', 'DjVu image'], + DLL => ['EXE', 'Windows Dynamic Link Library'], + DNG => ['TIFF', 'Digital Negative'], + DOC => ['FPX', 'Microsoft Word Document'], + DOCM => [['ZIP','FPX'], 'Office Open XML Document Macro-enabled'], + # Note: I have seen a password-protected DOCX file which was FPX-like, so I assume + # that any other MS Office file could be like this too. The only difference is + # that the ZIP and FPX formats are checked first, so if this is wrong, no biggie. + DOCX => [['ZIP','FPX'], 'Office Open XML Document'], + DOT => ['FPX', 'Microsoft Word Template'], + DOTM => [['ZIP','FPX'], 'Office Open XML Document Template Macro-enabled'], + DOTX => [['ZIP','FPX'], 'Office Open XML Document Template'], + DPX => ['DPX', 'Digital Picture Exchange' ], + DR4 => ['DR4', 'Canon VRD version 4 Recipe'], + DS2 => ['DSS', 'Digital Speech Standard 2'], + DSS => ['DSS', 'Digital Speech Standard'], + DV => ['DV', 'Digital Video'], + DVB => ['MOV', 'Digital Video Broadcasting'], + 'DVR-MS'=>['ASF', 'Microsoft Digital Video recording'], + DWF => ['DWF', 'Autodesk drawing (Design Web Format)'], + DWG => ['DWG', 'AutoCAD Drawing'], + DYLIB=> ['EXE', 'Mach-O Dynamic Link Library'], + DXF => ['DXF', 'AutoCAD Drawing Exchange Format'], + EIP => ['ZIP', 'Capture One Enhanced Image Package'], + EPS => ['EPS', 'Encapsulated PostScript Format'], + EPS2 => 'EPS', + EPS3 => 'EPS', + EPSF => 'EPS', + EPUB => ['ZIP', 'Electronic Publication'], + ERF => ['TIFF', 'Epson Raw Format'], + EXE => ['EXE', 'Windows executable file'], + EXR => ['EXR', 'Open EXR'], + EXIF => ['EXIF', 'Exchangable Image File Metadata'], + EXV => ['EXV', 'Exiv2 metadata'], + F4A => ['MOV', 'Adobe Flash Player 9+ Audio'], + F4B => ['MOV', 'Adobe Flash Player 9+ audio Book'], + F4P => ['MOV', 'Adobe Flash Player 9+ Protected'], + F4V => ['MOV', 'Adobe Flash Player 9+ Video'], + FFF => [['TIFF','FLIR'], 'Hasselblad Flexible File Format'], + FIT => 'FITS', + FITS => ['FITS', 'Flexible Image Transport System'], + FLAC => ['FLAC', 'Free Lossless Audio Codec'], + FLA => ['FPX', 'Macromedia/Adobe Flash project'], + FLIF => ['FLIF', 'Free Lossless Image Format'], + FLIR => ['FLIR', 'FLIR File Format'], # (not an actual extension) + FLV => ['FLV', 'Flash Video'], + FPF => ['FPF', 'FLIR Public image Format'], + FPX => ['FPX', 'FlashPix'], + GIF => ['GIF', 'Compuserve Graphics Interchange Format'], + GLV => ['MOV', 'Garmin Low-resolution Video'], + GPR => ['TIFF', 'General Purpose RAW'], # https://gopro.github.io/gpr/ + GZ => 'GZIP', + GZIP => ['GZIP', 'GNU ZIP compressed archive'], + HDP => ['TIFF', 'Windows HD Photo'], + HDR => ['HDR', 'Radiance RGBE High Dynamic Range'], + HEIC => ['MOV', 'High Efficiency Image Format still image'], + HEIF => ['MOV', 'High Efficiency Image Format'], + HIF => 'HEIF', + HTM => 'HTML', + HTML => ['HTML', 'HyperText Markup Language'], + ICAL => 'ICS', + ICC => ['ICC', 'International Color Consortium'], + ICM => 'ICC', + ICO => ['ICO', 'Windows Icon'], + ICS => ['VCard','iCalendar Schedule'], + IDML => ['ZIP', 'Adobe InDesign Markup Language'], + IIQ => ['TIFF', 'Phase One Intelligent Image Quality RAW'], + IND => ['IND', 'Adobe InDesign'], + INDD => ['IND', 'Adobe InDesign Document'], + INDT => ['IND', 'Adobe InDesign Template'], + INSV => ['MOV', 'Insta360 Video'], + INSP => ['JPEG', 'Insta360 Picture'], + INX => ['XMP', 'Adobe InDesign Interchange'], + ISO => ['ISO', 'ISO 9660 disk image'], + ITC => ['ITC', 'iTunes Cover Flow'], + J2C => ['JP2', 'JPEG 2000 codestream'], + J2K => 'J2C', + JNG => ['PNG', 'JPG Network Graphics'], + JP2 => ['JP2', 'JPEG 2000 file'], + # JP4? - looks like a JPEG but the image data is different + JPC => 'J2C', + JPE => 'JPEG', + JPEG => ['JPEG', 'Joint Photographic Experts Group'], + JPF => 'JP2', + JPG => 'JPEG', + JPM => ['JP2', 'JPEG 2000 compound image'], + JPS => ['JPEG', 'JPEG Stereo image'], + JPX => ['JP2', 'JPEG 2000 with extensions'], + JSON => ['JSON', 'JavaScript Object Notation'], + JXL => ['JXL', 'JPEG XL'], + JXR => ['TIFF', 'JPEG XR'], + K25 => ['TIFF', 'Kodak DC25 RAW'], + KDC => ['TIFF', 'Kodak Digital Camera RAW'], + KEY => ['ZIP', 'Apple Keynote presentation'], + KTH => ['ZIP', 'Apple Keynote Theme'], + LA => ['RIFF', 'Lossless Audio'], + LFP => ['LFP', 'Lytro Light Field Picture'], + LFR => 'LFP', # (Light Field RAW) + LIF => ['LIF', 'Leica Image File'], + LNK => ['LNK', 'Windows shortcut'], + LRI => ['LRI', 'Light RAW'], + LRV => ['MOV', 'Low-Resolution Video'], + M2T => 'M2TS', + M2TS => ['M2TS', 'MPEG-2 Transport Stream'], + M2V => ['MPEG', 'MPEG-2 Video'], + M4A => ['MOV', 'MPEG-4 Audio'], + M4B => ['MOV', 'MPEG-4 audio Book'], + M4P => ['MOV', 'MPEG-4 Protected'], + M4V => ['MOV', 'MPEG-4 Video'], + MACOS=> ['MacOS','MacOS ._ sidecar file'], + MAX => ['FPX', '3D Studio MAX'], + MEF => ['TIFF', 'Mamiya (RAW) Electronic Format'], + MIE => ['MIE', 'Meta Information Encapsulation format'], + MIF => 'MIFF', + MIFF => ['MIFF', 'Magick Image File Format'], + MKA => ['MKV', 'Matroska Audio'], + MKS => ['MKV', 'Matroska Subtitle'], + MKV => ['MKV', 'Matroska Video'], + MNG => ['PNG', 'Multiple-image Network Graphics'], + MOBI => ['PDB', 'Mobipocket electronic book'], + MODD => ['PLIST','Sony Picture Motion metadata'], + MOI => ['MOI', 'MOD Information file'], + MOS => ['TIFF', 'Creo Leaf Mosaic'], + MOV => ['MOV', 'Apple QuickTime movie'], + MP3 => ['MP3', 'MPEG-1 Layer 3 audio'], + MP4 => ['MOV', 'MPEG-4 video'], + MPC => ['MPC', 'Musepack Audio'], + MPEG => ['MPEG', 'MPEG-1 or MPEG-2 audio/video'], + MPG => 'MPEG', + MPO => ['JPEG', 'Extended Multi-Picture format'], + MQV => ['MOV', 'Sony Mobile Quicktime Video'], + MRC => ['MRC', 'Medical Research Council image'], + MRW => ['MRW', 'Minolta RAW format'], + MTS => 'M2TS', + MXF => ['MXF', 'Material Exchange Format'], + # NDPI => ['TIFF', 'Hamamatsu NanoZoomer Digital Pathology Image'], + NEF => ['TIFF', 'Nikon (RAW) Electronic Format'], + NEWER => 'COS', + NKSC => ['XMP', 'Nikon Sidecar'], + NMBTEMPLATE => ['ZIP','Apple Numbers Template'], + NRW => ['TIFF', 'Nikon RAW (2)'], + NUMBERS => ['ZIP','Apple Numbers spreadsheet'], + O => ['EXE', 'Relocatable Object'], + ODB => ['ZIP', 'Open Document Database'], + ODC => ['ZIP', 'Open Document Chart'], + ODF => ['ZIP', 'Open Document Formula'], + ODG => ['ZIP', 'Open Document Graphics'], + ODI => ['ZIP', 'Open Document Image'], + ODP => ['ZIP', 'Open Document Presentation'], + ODS => ['ZIP', 'Open Document Spreadsheet'], + ODT => ['ZIP', 'Open Document Text file'], + OFR => ['RIFF', 'OptimFROG audio'], + OGG => ['OGG', 'Ogg Vorbis audio file'], + OGV => ['OGG', 'Ogg Video file'], + ONP => ['JSON', 'ON1 Presets'], + OPUS => ['OGG', 'Ogg Opus audio file'], + ORF => ['ORF', 'Olympus RAW format'], + ORI => 'ORF', + OTF => ['Font', 'Open Type Font'], + PAC => ['RIFF', 'Lossless Predictive Audio Compression'], + PAGES => ['ZIP', 'Apple Pages document'], + PBM => ['PPM', 'Portable BitMap'], + PCD => ['PCD', 'Kodak Photo CD Image Pac'], + PCT => 'PICT', + PCX => ['PCX', 'PC Paintbrush'], + PDB => ['PDB', 'Palm Database'], + PDF => ['PDF', 'Adobe Portable Document Format'], + PEF => ['TIFF', 'Pentax (RAW) Electronic Format'], + PFA => ['Font', 'PostScript Font ASCII'], + PFB => ['Font', 'PostScript Font Binary'], + PFM => [['Font','PFM2'], 'Printer Font Metrics'], # (description is overridden for Portable FloatMap images) + PGF => ['PGF', 'Progressive Graphics File'], + PGM => ['PPM', 'Portable Gray Map'], + PHP => ['PHP', 'PHP Hypertext Preprocessor'], + PHP3 => 'PHP', + PHP4 => 'PHP', + PHP5 => 'PHP', + PHPS => 'PHP', + PHTML=> 'PHP', + PICT => ['PICT', 'Apple PICTure'], + PLIST=> ['PLIST','Apple Property List'], + PMP => ['PMP', 'Sony DSC-F1 Cyber-Shot PMP'], # should stand for Proprietery Metadata Package ;) + PNG => ['PNG', 'Portable Network Graphics'], + POT => ['FPX', 'Microsoft PowerPoint Template'], + POTM => [['ZIP','FPX'], 'Office Open XML Presentation Template Macro-enabled'], + POTX => [['ZIP','FPX'], 'Office Open XML Presentation Template'], + PPAM => [['ZIP','FPX'], 'Office Open XML Presentation Addin Macro-enabled'], + PPAX => [['ZIP','FPX'], 'Office Open XML Presentation Addin'], + PPM => ['PPM', 'Portable Pixel Map'], + PPS => ['FPX', 'Microsoft PowerPoint Slideshow'], + PPSM => [['ZIP','FPX'], 'Office Open XML Presentation Slideshow Macro-enabled'], + PPSX => [['ZIP','FPX'], 'Office Open XML Presentation Slideshow'], + PPT => ['FPX', 'Microsoft PowerPoint Presentation'], + PPTM => [['ZIP','FPX'], 'Office Open XML Presentation Macro-enabled'], + PPTX => [['ZIP','FPX'], 'Office Open XML Presentation'], + PRC => ['PDB', 'Palm Database'], + PS => ['PS', 'PostScript'], + PS2 => 'PS', + PS3 => 'PS', + PSB => ['PSD', 'Photoshop Large Document'], + PSD => ['PSD', 'Photoshop Document'], + PSDT => ['PSD', 'Photoshop Document Template'], + PSP => ['PSP', 'Paint Shop Pro'], + PSPFRAME => 'PSP', + PSPIMAGE => 'PSP', + PSPSHAPE => 'PSP', + PSPTUBE => 'PSP', + QIF => 'QTIF', + QT => 'MOV', + QTI => 'QTIF', + QTIF => ['QTIF', 'QuickTime Image File'], + R3D => ['R3D', 'Redcode RAW Video'], + RA => ['Real', 'Real Audio'], + RAF => ['RAF', 'FujiFilm RAW Format'], + RAM => ['Real', 'Real Audio Metafile'], + RAR => ['RAR', 'RAR Archive'], + RAW => [['RAW','TIFF'], 'Kyocera Contax N Digital RAW or Panasonic RAW'], + RIF => 'RIFF', + RIFF => ['RIFF', 'Resource Interchange File Format'], + RM => ['Real', 'Real Media'], + RMVB => ['Real', 'Real Media Variable Bitrate'], + RPM => ['Real', 'Real Media Plug-in Metafile'], + RSRC => ['RSRC', 'Mac OS Resource'], + RTF => ['RTF', 'Rich Text Format'], + RV => ['Real', 'Real Video'], + RW2 => ['TIFF', 'Panasonic RAW 2'], + RWL => ['TIFF', 'Leica RAW'], + RWZ => ['RWZ', 'Rawzor compressed image'], + SEQ => ['FLIR', 'FLIR image Sequence'], + SKETCH => ['ZIP', 'Sketch design file'], + SO => ['EXE', 'Shared Object file'], + SR2 => ['TIFF', 'Sony RAW Format 2'], + SRF => ['TIFF', 'Sony RAW Format'], + SRW => ['TIFF', 'Samsung RAW format'], + SVG => ['XMP', 'Scalable Vector Graphics'], + SWF => ['SWF', 'Shockwave Flash'], + TAR => ['TAR', 'TAR archive'], + THM => ['JPEG', 'Thumbnail'], + THMX => [['ZIP','FPX'], 'Office Open XML Theme'], + TIF => 'TIFF', + TIFF => ['TIFF', 'Tagged Image File Format'], + TORRENT => ['Torrent', 'BitTorrent description file'], + TS => 'M2TS', + TTC => ['Font', 'True Type Font Collection'], + TTF => ['Font', 'True Type Font'], + TUB => 'PSP', + TXT => ['TXT', 'Text file'], + VCARD=> ['VCard','Virtual Card'], + VCF => 'VCARD', + VOB => ['MPEG', 'Video Object'], + VNT => [['FPX','VCard'], 'Scene7 Vignette or V-Note text file'], + VRD => ['VRD', 'Canon VRD Recipe Data'], + VSD => ['FPX', 'Microsoft Visio Drawing'], + WAV => ['RIFF', 'WAVeform (Windows digital audio)'], + WDP => ['TIFF', 'Windows Media Photo'], + WEBM => ['MKV', 'Google Web Movie'], + WEBP => ['RIFF', 'Google Web Picture'], + WMA => ['ASF', 'Windows Media Audio'], + WMF => ['WMF', 'Windows Metafile Format'], + WMV => ['ASF', 'Windows Media Video'], + WV => ['RIFF', 'WavePack lossless audio'], + X3F => ['X3F', 'Sigma RAW format'], + XCF => ['XCF', 'GIMP native image format'], + XHTML=> ['HTML', 'Extensible HyperText Markup Language'], + XLA => ['FPX', 'Microsoft Excel Add-in'], + XLAM => [['ZIP','FPX'], 'Office Open XML Spreadsheet Add-in Macro-enabled'], + XLS => ['FPX', 'Microsoft Excel Spreadsheet'], + XLSB => [['ZIP','FPX'], 'Office Open XML Spreadsheet Binary'], + XLSM => [['ZIP','FPX'], 'Office Open XML Spreadsheet Macro-enabled'], + XLSX => [['ZIP','FPX'], 'Office Open XML Spreadsheet'], + XLT => ['FPX', 'Microsoft Excel Template'], + XLTM => [['ZIP','FPX'], 'Office Open XML Spreadsheet Template Macro-enabled'], + XLTX => [['ZIP','FPX'], 'Office Open XML Spreadsheet Template'], + XMP => ['XMP', 'Extensible Metadata Platform'], + WOFF => ['Font', 'Web Open Font Format'], + WOFF2=> ['Font', 'Web Open Font Format2'], + WPG => ['WPG', 'WordPerfect Graphics'], + WTV => ['WTV', 'Windows recorded TV show'], + ZIP => ['ZIP', 'ZIP archive'], +); + +# typical extension for each file type (if different than FileType) +# - case is not significant +my %fileTypeExt = ( + 'Canon 1D RAW' => 'tif', + DICOM => 'dcm', + FLIR => 'fff', + GZIP => 'gz', + JPEG => 'jpg', + M2TS => 'mts', + MPEG => 'mpg', + TIFF => 'tif', + VCard => 'vcf', +); + +# descriptions for file types not found in above file extension lookup +my %fileDescription = ( + DICOM => 'Digital Imaging and Communications in Medicine', + XML => 'Extensible Markup Language', + 'Win32 EXE' => 'Windows 32-bit Executable', + 'Win32 DLL' => 'Windows 32-bit Dynamic Link Library', + 'Win64 EXE' => 'Windows 64-bit Executable', + 'Win64 DLL' => 'Windows 64-bit Dynamic Link Library', + VNote => 'V-Note document', +); + +# MIME types for applicable file types above +# (missing entries default to 'application/unknown', but note that other MIME +# types may be specified by some modules, eg. QuickTime.pm and RIFF.pm) +%mimeType = ( + '3FR' => 'image/x-hasselblad-3fr', + '7Z' => 'application/x-7z-compressed', + AA => 'audio/audible', + AAE => 'application/vnd.apple.photos', + AI => 'application/vnd.adobe.illustrator', + AIFF => 'audio/x-aiff', + ALIAS=> 'application/x-macos', + APE => 'audio/x-monkeys-audio', + APNG => 'image/apng', + ASF => 'video/x-ms-asf', + ARW => 'image/x-sony-arw', + BMP => 'image/bmp', + BPG => 'image/bpg', + BTF => 'image/x-tiff-big', #(NC) (ref http://www.asmail.be/msg0055371937.html) + BZ2 => 'application/bzip2', + 'Canon 1D RAW' => 'image/x-raw', # (uses .TIF file extension) + CHM => 'application/x-chm', + COS => 'application/octet-stream', #PH (NC) + CR2 => 'image/x-canon-cr2', + CR3 => 'image/x-canon-cr3', + CRM => 'video/x-canon-crm', + CRW => 'image/x-canon-crw', + CSV => 'text/csv', + CUR => 'image/x-cursor', #PH (NC) + CZI => 'image/x-zeiss-czi', #PH (NC) + DCP => 'application/octet-stream', #PH (NC) + DCR => 'image/x-kodak-dcr', + DCX => 'image/dcx', + DEX => 'application/octet-stream', + DFONT=> 'application/x-dfont', + DICOM=> 'application/dicom', + DIVX => 'video/divx', + DJVU => 'image/vnd.djvu', + DNG => 'image/x-adobe-dng', + DOC => 'application/msword', + DOCM => 'application/vnd.ms-word.document.macroEnabled.12', + DOCX => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + DOT => 'application/msword', + DOTM => 'application/vnd.ms-word.template.macroEnabledTemplate', + DOTX => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + DPX => 'image/x-dpx', + DR4 => 'application/octet-stream', #PH (NC) + DS2 => 'audio/x-ds2', + DSS => 'audio/x-dss', + DV => 'video/x-dv', + 'DVR-MS' => 'video/x-ms-dvr', + DWF => 'model/vnd.dwf', + DWG => 'image/vnd.dwg', + DXF => 'application/dxf', + EIP => 'application/x-captureone', #(NC) + EPS => 'application/postscript', + ERF => 'image/x-epson-erf', + EXE => 'application/octet-stream', + EXR => 'image/x-exr', + EXV => 'image/x-exv', + FFF => 'image/x-hasselblad-fff', + FITS => 'image/fits', + FLA => 'application/vnd.adobe.fla', + FLAC => 'audio/flac', + FLIF => 'image/flif', + FLIR => 'image/x-flir-fff', #PH (NC) + FLV => 'video/x-flv', + Font => 'application/x-font-type1', # covers PFA, PFB and PFM (not sure about PFM) + FPF => 'image/x-flir-fpf', #PH (NC) + FPX => 'image/vnd.fpx', + GIF => 'image/gif', + GPR => 'image/x-gopro-gpr', + GZIP => 'application/x-gzip', + HDP => 'image/vnd.ms-photo', + HDR => 'image/vnd.radiance', + HTML => 'text/html', + ICC => 'application/vnd.iccprofile', + ICO => 'image/x-icon', #PH (NC) + ICS => 'text/calendar', + IDML => 'application/vnd.adobe.indesign-idml-package', + IIQ => 'image/x-raw', + IND => 'application/x-indesign', + INX => 'application/x-indesign-interchange', #PH (NC) + ISO => 'application/x-iso9660-image', + ITC => 'application/itunes', + J2C => 'image/x-j2c', #PH (NC) + JNG => 'image/jng', + JP2 => 'image/jp2', + JPEG => 'image/jpeg', + JPM => 'image/jpm', + JPS => 'image/x-jps', + JPX => 'image/jpx', + JSON => 'application/json', + JXL => 'image/jxl', #PH (NC) + JXR => 'image/jxr', + K25 => 'image/x-kodak-k25', + KDC => 'image/x-kodak-kdc', + KEY => 'application/x-iwork-keynote-sffkey', + LFP => 'image/x-lytro-lfp', #PH (NC) + LIF => 'image/x-lif', + LNK => 'application/octet-stream', + LRI => 'image/x-light-lri', + M2T => 'video/mpeg', + M2TS => 'video/m2ts', + MAX => 'application/x-3ds', + MEF => 'image/x-mamiya-mef', + MIE => 'application/x-mie', + MIFF => 'application/x-magick-image', + MKA => 'audio/x-matroska', + MKS => 'application/x-matroska', + MKV => 'video/x-matroska', + MNG => 'video/mng', + MOBI => 'application/x-mobipocket-ebook', + MOI => 'application/octet-stream', #PH (NC) + MOS => 'image/x-raw', + MOV => 'video/quicktime', + MP3 => 'audio/mpeg', + MP4 => 'video/mp4', + MPC => 'audio/x-musepack', + MPEG => 'video/mpeg', + MRC => 'image/x-mrc', + MRW => 'image/x-minolta-mrw', + MXF => 'application/mxf', + NEF => 'image/x-nikon-nef', + NKSC => 'application/x-nikon-nxstudio', + NRW => 'image/x-nikon-nrw', + NUMBERS => 'application/x-iwork-numbers-sffnumbers', + ODB => 'application/vnd.oasis.opendocument.database', + ODC => 'application/vnd.oasis.opendocument.chart', + ODF => 'application/vnd.oasis.opendocument.formula', + ODG => 'application/vnd.oasis.opendocument.graphics', + ODI => 'application/vnd.oasis.opendocument.image', + ODP => 'application/vnd.oasis.opendocument.presentation', + ODS => 'application/vnd.oasis.opendocument.spreadsheet', + ODT => 'application/vnd.oasis.opendocument.text', + OGG => 'audio/ogg', + OGV => 'video/ogg', + ONP => 'application/on1', + ORF => 'image/x-olympus-orf', + OTF => 'application/x-font-otf', + PAGES=> 'application/x-iwork-pages-sffpages', + PBM => 'image/x-portable-bitmap', + PCD => 'image/x-photo-cd', + PCX => 'image/pcx', + PDB => 'application/vnd.palm', + PDF => 'application/pdf', + PEF => 'image/x-pentax-pef', + PFA => 'application/x-font-type1', # (needed if handled by PostScript module) + PGF => 'image/pgf', + PGM => 'image/x-portable-graymap', + PHP => 'application/x-httpd-php', + PICT => 'image/pict', + PLIST=> 'application/xml', # (binary PLIST format is 'application/x-plist', recognized at run time) + PMP => 'image/x-sony-pmp', #PH (NC) + PNG => 'image/png', + POT => 'application/vnd.ms-powerpoint', + POTM => 'application/vnd.ms-powerpoint.template.macroEnabled.12', + POTX => 'application/vnd.openxmlformats-officedocument.presentationml.template', + PPAM => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + PPAX => 'application/vnd.openxmlformats-officedocument.presentationml.addin', # (NC, PH invented) + PPM => 'image/x-portable-pixmap', + PPS => 'application/vnd.ms-powerpoint', + PPSM => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + PPSX => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + PPT => 'application/vnd.ms-powerpoint', + PPTM => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + PPTX => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + PS => 'application/postscript', + PSD => 'application/vnd.adobe.photoshop', + PSP => 'image/x-paintshoppro', #(NC) + QTIF => 'image/x-quicktime', + R3D => 'video/x-red-r3d', #PH (invented) + RA => 'audio/x-pn-realaudio', + RAF => 'image/x-fujifilm-raf', + RAM => 'audio/x-pn-realaudio', + RAR => 'application/x-rar-compressed', + RAW => 'image/x-raw', + RM => 'application/vnd.rn-realmedia', + RMVB => 'application/vnd.rn-realmedia-vbr', + RPM => 'audio/x-pn-realaudio-plugin', + RSRC => 'application/ResEdit', + RTF => 'text/rtf', + RV => 'video/vnd.rn-realvideo', + RW2 => 'image/x-panasonic-rw2', + RWL => 'image/x-leica-rwl', + RWZ => 'image/x-rawzor', #(duplicated in Rawzor.pm) + SEQ => 'image/x-flir-seq', #PH (NC) + SKETCH => 'application/sketch', + SR2 => 'image/x-sony-sr2', + SRF => 'image/x-sony-srf', + SRW => 'image/x-samsung-srw', + SVG => 'image/svg+xml', + SWF => 'application/x-shockwave-flash', + TAR => 'application/x-tar', + THMX => 'application/vnd.ms-officetheme', + TIFF => 'image/tiff', + Torrent => 'application/x-bittorrent', + TTC => 'application/x-font-ttf', + TTF => 'application/x-font-ttf', + TXT => 'text/plain', + VCard=> 'text/vcard', + VRD => 'application/octet-stream', #PH (NC) + VSD => 'application/x-visio', + WDP => 'image/vnd.ms-photo', + WEBM => 'video/webm', + WMA => 'audio/x-ms-wma', + WMF => 'application/x-wmf', + WMV => 'video/x-ms-wmv', + WPG => 'image/x-wpg', + WTV => 'video/x-ms-wtv', + X3F => 'image/x-sigma-x3f', + XCF => 'image/x-xcf', + XLA => 'application/vnd.ms-excel', + XLAM => 'application/vnd.ms-excel.addin.macroEnabled.12', + XLS => 'application/vnd.ms-excel', + XLSB => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + XLSM => 'application/vnd.ms-excel.sheet.macroEnabled.12', + XLSX => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + XLT => 'application/vnd.ms-excel', + XLTM => 'application/vnd.ms-excel.template.macroEnabled.12', + XLTX => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + XML => 'application/xml', + XMP => 'application/rdf+xml', + ZIP => 'application/zip', +); + +# module names for processing routines of each file type +# - undefined entries default to same module name as file type +# - module name '' defaults to Image::ExifTool +# - module name '0' indicates a recognized but unsupported file +my %moduleName = ( + AA => 'Audible', + ALIAS=> 0, + AVC => 0, + BTF => 'BigTIFF', + BZ2 => 0, + CRW => 'CanonRaw', + CHM => 'EXE', + COS => 'CaptureOne', + CZI => 'ZISRAW', + DEX => 0, + DOCX => 'OOXML', + DCX => 0, + DIR => 0, + DR4 => 'CanonVRD', + DSS => 'Olympus', + DWF => 0, + DWG => 0, + DXF => 0, + EPS => 'PostScript', + EXIF => '', + EXR => 'OpenEXR', + EXV => '', + ICC => 'ICC_Profile', + IND => 'InDesign', + FLV => 'Flash', + FPF => 'FLIR', + FPX => 'FlashPix', + GZIP => 'ZIP', + HDR => 'Radiance', + JP2 => 'Jpeg2000', + JPEG => '', + JXL => 'Jpeg2000', + LFP => 'Lytro', + LRI => 0, + MOV => 'QuickTime', + MKV => 'Matroska', + MP3 => 'ID3', + MRW => 'MinoltaRaw', + OGG => 'Ogg', + ORF => 'Olympus', + PDB => 'Palm', + PCD => 'PhotoCD', + PFM2 => 'Other', + PHP => 0, + PMP => 'Sony', + PS => 'PostScript', + PSD => 'Photoshop', + QTIF => 'QuickTime', + R3D => 'Red', + RAF => 'FujiFilm', + RAR => 'ZIP', + RAW => 'KyoceraRaw', + RWZ => 'Rawzor', + SWF => 'Flash', + TAR => 0, + TIFF => '', + TXT => 'Text', + VRD => 'CanonVRD', + WMF => 0, + X3F => 'SigmaRaw', + XCF => 'GIMP', +); + +$testLen = 1024; # number of bytes to read when testing for magic number + +# quick "magic number" file test used to avoid loading module unnecessarily: +# - regular expression evaluated on first $testLen bytes of file +# - must match beginning at first byte in file +# - this test must not be more stringent than module logic +%magicNumber = ( + AA => '.{4}\x57\x90\x75\x36', + AIFF => '(FORM....AIF[FC]|AT&TFORM)', + ALIAS=> "book\0\0\0\0mark\0\0\0\0", + APE => '(MAC |APETAGEX|ID3)', + ASF => '\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c', + AVC => '\+A\+V\+C\+', + Torrent => 'd\d+:\w+', + BMP => 'BM', + BPG => "BPG\xfb", + BTF => '(II\x2b\0|MM\0\x2b)', + BZ2 => 'BZh[1-9]\x31\x41\x59\x26\x53\x59', + CHM => 'ITSF.{20}\x10\xfd\x01\x7c\xaa\x7b\xd0\x11\x9e\x0c\0\xa0\xc9\x22\xe6\xec', + CRW => '(II|MM).{4}HEAP(CCDR|JPGM)', + CZI => 'ZISRAWFILE\0{6}', + DCX => '\xb1\x68\xde\x3a', + DEX => "dex\n035\0", + DICOM=> '(.{128}DICM|\0[\x02\x04\x06\x08]\0[\0-\x20]|[\x02\x04\x06\x08]\0[\0-\x20]\0)', + DOCX => 'PK\x03\x04', + DPX => '(SDPX|XPDS)', + DR4 => 'IIII\x04\0\x04\0', + DSS => '(\x02dss|\x03ds2)', + DV => '\x1f\x07\0[\x3f\xbf]', # (not tested if extension recognized) + DWF => '\(DWF V\d', + DWG => 'AC10\d{2}\0', + DXF => '\s*0\s+\0?\s*SECTION\s+2\s+HEADER', + EPS => '(%!PS|%!Ad|\xc5\xd0\xd3\xc6)', + EXE => '(MZ|\xca\xfe\xba\xbe|\xfe\xed\xfa[\xce\xcf]|[\xce\xcf]\xfa\xed\xfe|Joy!peff|\x7fELF|#!\s*/\S*bin/|!\x0a)', + EXIF => '(II\x2a\0|MM\0\x2a)', + EXR => '\x76\x2f\x31\x01', + EXV => '\xff\x01Exiv2', + FITS => 'SIMPLE = {20}T', + FLAC => '(fLaC|ID3)', + FLIF => 'FLIF[0-\x6f][0-2]', + FLIR => '[AF]FF\0', + FLV => 'FLV\x01', + Font => '((\0\x01\0\0|OTTO|true|typ1)[\0\x01]|ttcf\0[\x01\x02]\0\0|\0[\x01\x02]|' . + '(.{6})?%!(PS-(AdobeFont-|Bitstream )|FontType1-)|Start(Comp|Master)?FontMetrics|wOF[F2])', + FPF => 'FPF Public Image Format\0', + FPX => '\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1', + GIF => 'GIF8[79]a', + GZIP => '\x1f\x8b\x08', + HDR => '#\?(RADIANCE|RGBE)\x0a', + HTML => '(\xef\xbb\xbf)?\s*(?i)<(!DOCTYPE\s+HTML|HTML|\?xml)', # (case insensitive) + ICC => '.{12}(scnr|mntr|prtr|link|spac|abst|nmcl|nkpf|cenc|mid |mlnk|mvis)(XYZ |Lab |Luv |YCbr|Yxy |RGB |GRAY|HSV |HLS |CMYK|CMY |[2-9A-F]CLR|nc..|\0{4}){2}', + ICO => '\0\0[\x01\x02]\0[^0]\0', # (reasonably assume that the file contains less than 256 images) + IND => '\x06\x06\xed\xf5\xd8\x1d\x46\xe5\xbd\x31\xef\xe7\xfe\x74\xb7\x1d', + # ISO => signature is at byte 32768 + ITC => '.{4}itch', + JP2 => '(\0\0\0\x0cjP( |\x1a\x1a)\x0d\x0a\x87\x0a|\xff\x4f\xff\x51\0)', + JPEG => '\xff\xd8\xff', + JSON => '(\xef\xbb\xbf)?\s*(\[\s*)?\{\s*"[^"]*"\s*:', + JXL => '\xff\x0a|\0\0\0\x0cJXL \x0d\x0a......ftypjxl ', + LFP => '\x89LFP\x0d\x0a\x1a\x0a', + LIF => '\x70\0{3}.{4}\x2a.{4}<\0', + LNK => '.{4}\x01\x14\x02\0{5}\xc0\0{6}\x46', + LRI => 'LELR \0', + M2TS => '(....)?\x47', + MacOS=> '\0\x05\x16\x07\0.\0\0Mac OS X ', + MIE => '~[\x10\x18]\x04.0MIE', + MIFF => 'id=ImageMagick', + MKV => '\x1a\x45\xdf\xa3', + MOV => '.{4}(free|skip|wide|ftyp|pnot|PICT|pict|moov|mdat|junk|uuid)', # (duplicated in WriteQuickTime.pl !!) + # MP3 => difficult to rule out + MPC => '(MP\+|ID3)', + MOI => 'V6', + MPEG => '\0\0\x01[\xb0-\xbf]', + MRC => '.{64}[\x01\x02\x03]\0\0\0[\x01\x02\x03]\0\0\0[\x01\x02\x03]\0\0\0.{132}MAP[\0 ](\x44\x44|\x44\x41|\x11\x11)\0\0', + MRW => '\0MR[MI]', + MXF => '\x06\x0e\x2b\x34\x02\x05\x01\x01\x0d\x01\x02', # (not tested if extension recognized) + OGG => '(OggS|ID3)', + ORF => '(II|MM)', + # PCD => signature is at byte 2048 + PCX => '\x0a[\0-\x05]\x01[\x01\x02\x04\x08].{64}[\0-\x02]', + PDB => '.{60}(\.pdfADBE|TEXtREAd|BVokBDIC|DB99DBOS|PNRdPPrs|DataPPrs|vIMGView|PmDBPmDB|InfoINDB|ToGoToGo|SDocSilX|JbDbJBas|JfDbJFil|DATALSdb|Mdb1Mdb1|BOOKMOBI|DataPlkr|DataSprd|SM01SMem|TEXtTlDc|InfoTlIf|DataTlMl|DataTlPt|dataTDBP|TdatTide|ToRaTRPW|zTXTGPlm|BDOCWrdS)', + PDF => '\s*%PDF-\d+\.\d+', + PFM => 'P[Ff]\x0a\d+ \d+\x0a[-+0-9.]+\x0a', + PGF => 'PGF', + PHP => '<\?php\s', + PICT => '(.{10}|.{522})(\x11\x01|\x00\x11)', + PLIST=> '(bplist0|\s*<|\xfe\xff\x00)', + PMP => '.{8}\0{3}\x7c.{112}\xff\xd8\xff\xdb', + PNG => '(\x89P|\x8aM|\x8bJ)NG\r\n\x1a\n', + PPM => 'P[1-6]\s+', + PS => '(%!PS|%!Ad|\xc5\xd0\xd3\xc6)', + PSD => '8BPS\0[\x01\x02]', + PSP => 'Paint Shop Pro Image File\x0a\x1a\0{5}', + QTIF => '.{4}(idsc|idat|iicc)', + R3D => '\0\0..RED(1|2)', + RAF => 'FUJIFILM', + RAR => 'Rar!\x1a\x07\x01?\0', + RAW => '(.{25}ARECOYK|II|MM)', + Real => '(\.RMF|\.ra\xfd|pnm://|rtsp://|http://)', + RIFF => '(RIFF|LA0[234]|OFR |LPAC|wvpk|RF64)', # RIFF plus other variants + RSRC => '(....)?\0\0\x01\0', + RTF => '[\n\r]*\\{[\n\r]*\\\\rtf', + RWZ => 'rawzor', + SWF => '[FC]WS[^\0]', + TAR => '.{257}ustar( )?\0', # (this doesn't catch old-style tar files) + TXT => '(\xff\xfe|(\0\0)?\xfe\xff|(\xef\xbb\xbf)?[\x07-\x0d\x20-\x7e\x80-\xfe]*$)', + TIFF => '(II|MM)', # don't test magic number (some raw formats are different) + VCard=> '(?i)BEGIN:(VCARD|VCALENDAR|VNOTE)\r\n', + VRD => 'CANON OPTIONAL DATA\0', + WMF => '(\xd7\xcd\xc6\x9a\0\0|\x01\0\x09\0\0\x03)', + WPG => '\xff\x57\x50\x43', + WTV => '\xb7\xd8\x00\x20\x37\x49\xda\x11\xa6\x4e\x00\x07\xe9\x5e\xad\x8d', + X3F => 'FOVb', + XCF => 'gimp xcf ', + XMP => '\0{0,3}(\xfe\xff|\xff\xfe|\xef\xbb\xbf)?\0{0,3}\s*<', + ZIP => 'PK\x03\x04', +); + +# file types with weak magic number recognition +my %weakMagic = ( MP3 => 1 ); + +# file types that are determined by the process proc when FastScan == 3 +# (when done, the process proc must exit after SetFileType if FastScan is 3) +my %processType = map { $_ => 1 } qw(JPEG TIFF XMP AIFF EXE Font PS Real VCard TXT); + +# Compact/XMPShorthand option settings +my %compactOpt = ( + nopadding => 'NoPadding', noindent => 'NoIndent', nonewline => 'NoNewline', + shorthand => 'Shorthand', onedesc => 'OneDesc', + all => ['NoPadding','NoIndent','NoNewline','Shorthand','OneDesc'], + allspace => ['NoPadding','NoIndent','NoNewline'], allformat => ['Shorthand','OneDesc'], + # aliases to cover anticipated user typos + nonewlines => 'NoNewline', nospace => 'NoIndent', nospaces => 'NoIndent', + nopad => 'NoPadding', onedescr => 'OneDesc', + # allow numerical settings for backward compatibility + 0 => 'None', + 1 => 'NoPadding', + 2 => ['NoPadding','NoIndent'], + 3 => ['NoPadding','NoIndent','OneDesc'], + 4 => ['NoPadding','NoIndent','OneDesc','NoNewline'], + 5 => ['NoPadding','NoIndent','OneDesc','NoNewline','Shorthand'], +); +my %xmpShorthandOpt = ( 0 => 'None', 1 => 'Shorthand', 2 => ['Shorthand','OneDesc'] ); + +# lookup for valid character set names (keys are all lower case) +%charsetName = ( + # Charset setting alias(es) + # ------------------------- -------------------------------------------- + utf8 => 'UTF8', cp65001 => 'UTF8', 'utf-8' => 'UTF8', + latin => 'Latin', cp1252 => 'Latin', latin1 => 'Latin', + latin2 => 'Latin2', cp1250 => 'Latin2', + cyrillic => 'Cyrillic', cp1251 => 'Cyrillic', russian => 'Cyrillic', + greek => 'Greek', cp1253 => 'Greek', + turkish => 'Turkish', cp1254 => 'Turkish', + hebrew => 'Hebrew', cp1255 => 'Hebrew', + arabic => 'Arabic', cp1256 => 'Arabic', + baltic => 'Baltic', cp1257 => 'Baltic', + vietnam => 'Vietnam', cp1258 => 'Vietnam', + thai => 'Thai', cp874 => 'Thai', + doslatinus => 'DOSLatinUS', cp437 => 'DOSLatinUS', + doslatin1 => 'DOSLatin1', cp850 => 'DOSLatin1', + doscyrillic => 'DOSCyrillic', cp866 => 'DOSCyrillic', + macroman => 'MacRoman', cp10000 => 'MacRoman', mac => 'MacRoman', roman => 'MacRoman', + maclatin2 => 'MacLatin2', cp10029 => 'MacLatin2', + maccyrillic => 'MacCyrillic', cp10007 => 'MacCyrillic', + macgreek => 'MacGreek', cp10006 => 'MacGreek', + macturkish => 'MacTurkish', cp10081 => 'MacTurkish', + macromanian => 'MacRomanian', cp10010 => 'MacRomanian', + maciceland => 'MacIceland', cp10079 => 'MacIceland', + maccroatian => 'MacCroatian', cp10082 => 'MacCroatian', +); + +# list of available options +# +-----------------------------------------------------+ +# ! DON'T FORGET!! When adding any new option, must ! +# ! decide how it is handled in SetNewValuesFromFile() ! +# +-----------------------------------------------------+ +# (Note: All options must exist in this lookup, even if undefined, +# to facilitate case-insensitive options. 'Group#' is handled specially) +my @availableOptions = ( + [ 'Binary', undef, 'flag to extract binary values even if tag not specified' ], + [ 'ByteOrder', undef, 'default byte order when creating EXIF information' ], + [ 'Charset', 'UTF8', 'character set for converting Unicode characters' ], + [ 'CharsetEXIF', undef, 'internal EXIF "ASCII" string encoding' ], + [ 'CharsetFileName', undef, 'external encoding for file names' ], + [ 'CharsetID3', 'Latin','internal ID3v1 character set' ], + [ 'CharsetIPTC', 'Latin','fallback IPTC character set if no CodedCharacterSet' ], + [ 'CharsetPhotoshop', 'Latin','internal encoding for Photoshop resource names' ], + [ 'CharsetQuickTime', 'MacRoman', 'internal QuickTime string encoding' ], + [ 'CharsetRIFF', 0, 'internal RIFF string encoding (0=default to Latin)' ], + [ 'Compact', { }, 'write compact XMP' ], + [ 'Composite', 1, 'flag to calculate Composite tags' ], + [ 'Compress', undef, 'flag to write new values as compressed if possible' ], + [ 'CoordFormat', undef, 'GPS lat/long coordinate format' ], + [ 'DateFormat', undef, 'format for date/time' ], + [ 'Duplicates', 1, 'flag to save duplicate tag values' ], + [ 'Escape', undef, 'escape special characters' ], + [ 'Exclude', undef, 'tags to exclude' ], + [ 'ExtendedXMP', 1, 'strategy for reading extended XMP' ], + [ 'ExtractEmbedded', undef, 'flag to extract information from embedded documents' ], + [ 'FastScan', undef, 'flag to avoid scanning for trailer' ], + [ 'Filter', undef, 'output filter for all tag values' ], + [ 'FilterW', undef, 'input filter when writing tag values' ], + [ 'FixBase', undef, 'fix maker notes base offsets' ], + [ 'GeoMaxIntSecs', 1800, 'geotag maximum interpolation time (secs)' ], + [ 'GeoMaxExtSecs', 1800, 'geotag maximum extrapolation time (secs)' ], + [ 'GeoMaxHDOP', undef, 'geotag maximum HDOP' ], + [ 'GeoMaxPDOP', undef, 'geotag maximum PDOP' ], + [ 'GeoMinSats', undef, 'geotag minimum satellites' ], + [ 'GeoSpeedRef', undef, 'geotag GPSSpeedRef' ], + [ 'GlobalTimeShift', undef, 'apply time shift to all extracted date/time values' ], + [ 'Group#', undef, 'return tags for specified groups in family #' ], + [ 'HexTagIDs', 0, 'use hex tag ID\'s in family 7 group names' ], + [ 'HtmlDump', 0, 'HTML dump (0-3, higher # = bigger limit)' ], + [ 'HtmlDumpBase', undef, 'base address for HTML dump' ], + [ 'IgnoreMinorErrors',undef, 'ignore minor errors when reading/writing' ], + [ 'IgnoreTags', undef, 'list of tags to ignore when extracting' ], + [ 'ImageHashType', 'MD5', 'image hash algorithm' ], + [ 'Lang', $defaultLang, 'localized language for descriptions etc' ], + [ 'LargeFileSupport', undef, 'flag indicating support of 64-bit file offsets' ], + [ 'List', undef, '[deprecated, use ListSplit and ListJoin instead]' ], + [ 'ListItem', undef, 'used to return a specific item from lists' ], + [ 'ListJoin', ', ', 'join lists together with this separator' ], + [ 'ListSep', ', ', '[deprecated, use ListSplit and ListJoin instead]' ], + [ 'ListSplit', undef, 'regex for splitting list-type tag values when writing' ], + [ 'MakerNotes', undef, 'extract maker notes as a block' ], + [ 'MDItemTags', undef, 'extract MacOS metadata item tags' ], + [ 'MissingTagValue', undef, 'value for missing tags when expanded in expressions' ], + [ 'NoMultiExif', undef, 'raise error when writing multi-segment EXIF' ], + [ 'NoPDFList', undef, 'flag to avoid splitting PDF List-type tag values' ], + [ 'NoWarning', undef, 'regular expression for warnings to suppress' ], + [ 'Password', undef, 'password for password-protected PDF documents' ], + [ 'PrintConv', 1, 'flag to enable print conversion' ], + [ 'QuickTimeHandler', 1, 'flag to add mdir Handler to newly created Meta box' ], + [ 'QuickTimePad', undef, 'flag to preserve padding of QuickTime CR3 tags' ], + [ 'QuickTimeUTC', undef, 'assume that QuickTime date/time tags are stored as UTC' ], + [ 'RequestAll', undef, 'extract all tags that must be specifically requested' ], + [ 'RequestTags', undef, 'extra tags to request (on top of those in the tag list)' ], + [ 'SaveFormat', undef, 'save family 6 tag TIFF format' ], + [ 'SavePath', undef, 'save family 5 location path' ], + [ 'ScanForXMP', undef, 'flag to scan for XMP information in all files' ], + [ 'Sort', 'Input','order to sort found tags (Input, File, Tag, Descr, Group#)' ], + [ 'Sort2', 'File', 'secondary sort order for tags in a group (File, Tag, Descr)' ], + [ 'StrictDate', undef, 'flag to return undef for invalid date conversions' ], + [ 'Struct', undef, 'return structures as hash references' ], + [ 'StructFormat', undef, 'format for structure serialization when reading/writing' ], + [ 'SystemTags', undef, 'extract additional File System tags' ], + [ 'TextOut', \*STDOUT, 'file for Verbose/HtmlDump output' ], + [ 'TimeZone', undef, 'local time zone' ], + [ 'Unknown', 0, 'flag to get values of unknown tags (0-2)' ], + [ 'UserParam', { }, 'user parameters for additional user-defined tag values' ], + [ 'Validate', undef, 'perform additional validation' ], + [ 'Verbose', 0, 'print verbose messages (0-5, higher # = more verbose)' ], + [ 'WindowsWideFile', undef, 'force the use of Windows wide-character file routines' ], # (see forum15208) + [ 'WriteMode', 'wcg', 'enable all write modes by default' ], + [ 'XAttrTags', undef, 'extract MacOS extended attribute tags' ], + [ 'XMPAutoConv', 1, 'automatic conversion of unknown XMP tag values' ], + [ 'XMPShorthand', 0, '[deprecated, use Compact=Shorthand instead]' ], +); + +# default family 0 group priority for writing +# (NOTE: tags in groups not specified here will not be written unless +# overridden by the module or specified when writing) +my @defaultWriteGroups = qw( + EXIF IPTC XMP MakerNotes QuickTime Photoshop ICC_Profile CanonVRD Adobe +); + +# group hash for ExifTool-generated tags +my %allGroupsExifTool = ( 0 => 'ExifTool', 1 => 'ExifTool', 2 => 'ExifTool' ); + +# special tag names (not used for tag info) +%specialTags = map { $_ => 1 } qw( + TABLE_NAME SHORT_NAME PROCESS_PROC WRITE_PROC CHECK_PROC + GROUPS FORMAT FIRST_ENTRY TAG_PREFIX PRINT_CONV + WRITABLE TABLE_DESC NOTES IS_OFFSET IS_SUBDIR + EXTRACT_UNKNOWN NAMESPACE PREFERRED SRC_TABLE PRIORITY + AVOID WRITE_GROUP LANG_INFO VARS DATAMEMBER + SET_GROUP1 PERMANENT INIT_TABLE +); + +# headers for various segment types +$exifAPP1hdr = "Exif\0\0"; +$xmpAPP1hdr = "http://ns.adobe.com/xap/1.0/\0"; +$xmpExtAPP1hdr = "http://ns.adobe.com/xmp/extension/\0"; +$psAPP13hdr = "Photoshop 3.0\0"; +$psAPP13old = 'Adobe_Photoshop2.5:'; + +sub DummyWriteProc { return 1; } + +# lookup for user lenses defined in @Image::ExifTool::UserDefined::Lenses +%Image::ExifTool::userLens = ( ); + +# queued plug-in tags to add to lookup +@Image::ExifTool::pluginTags = ( ); +%Image::ExifTool::pluginTags = ( ); + +my %systemTagsNotes = ( + Notes => q{ + extracted only if specifically requested or the L or L API + option is set + }, +); + +# tag information for preview image -- this should be used for all +# PreviewImage tags so they are handled properly when reading/writing +%Image::ExifTool::previewImageTagInfo = ( + Name => 'PreviewImage', + Writable => 'undef', + # a value of 'none' is ok... + WriteCheck => '$val eq "none" ? undef : $self->CheckImage(\$val)', + DataTag => 'PreviewImage', + # accept either scalar or scalar reference + RawConv => '$self->ValidateImage(ref $val ? $val : \$val, $tag)', + # we allow preview image to be set to '', but we don't want a zero-length value + # in the IFD, so set it temporarily to 'none'. Note that the length is <= 4, + # so this value will fit in the IFD so the preview fixup won't be generated. + ValueConvInv => '$val eq "" and $val="none"; $val', +); + +# extra tags that aren't truly EXIF tags, but are generated by the script +# Note: any tag in this list with a name corresponding to a Group0 name is +# used to write the entire corresponding directory as a block. +%Image::ExifTool::Extra = ( + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + VARS => { NO_ID => 1 }, # tag ID's aren't meaningful for these tags + WRITE_PROC => \&DummyWriteProc, + Error => { + Priority => 0, + Groups => \%allGroupsExifTool, + Notes => q{ + returns errors that may have occurred while reading or writing a file. Any + Error will prevent the file from being processed. Minor errors may be + downgraded to warnings with the -m or L option + }, + }, + Warning => { + Priority => 0, + Groups => \%allGroupsExifTool, + Notes => q{ + returns warnings that may have occurred while reading or writing a file. + Use the -a or L option to see all warnings if more than one + occurred. Minor warnings may be ignored with the -m or L + option. Minor warnings with a capital "M" in the "[Minor]" designation + indicate that the processing is affected by ignoring the warning + }, + }, + Comment => { + Notes => 'comment embedded in JPEG, GIF89a or PPM/PGM/PBM image', + Writable => 1, + WriteGroup => 'Comment', + Priority => 0, # to preserve order of JPEG COM segments + }, + Directory => { + Groups => { 1 => 'System', 2 => 'Other' }, + Notes => q{ + the directory of the file as specified in the call to ExifTool, or "." if no + directory was specified. May be written to move the file to another + directory that will be created if doesn't already exist + }, + Writable => 1, + WritePseudo => 1, + DelCheck => q{"Can't delete"}, + Protected => 1, + RawConv => '$self->ConvertFileName($val)', + # translate backslashes in directory names and add trailing '/' + ValueConvInv => '$_ = $self->InverseFileName($val); m{[^/]$} and $_ .= "/"; $_', + }, + FileName => { + Groups => { 1 => 'System', 2 => 'Other' }, + Writable => 1, + WritePseudo => 1, + DelCheck => q{"Can't delete"}, + Protected => 1, + Notes => q{ + may be written with a full path name to set FileName and Directory in one + operation. This is such a powerful feature that a TestName tag is provided + to allow dry-run tests before actually writing the file name. See + L for more information on writing the + FileName, Directory and TestName tags + }, + RawConv => '$self->ConvertFileName($val)', + ValueConvInv => '$self->InverseFileName($val)', + }, + BaseName => { + Groups => { 1 => 'System', 2 => 'Other' }, + Notes => q{ + file name without extension. Not generated unless specifically requested or + the API L option is set + }, + }, + FilePath => { + Groups => { 1 => 'System', 2 => 'Other' }, + Notes => q{ + absolute path of source file. Not generated unless specifically requested or + the API L option is set. Does not support Windows Unicode file + names + }, + }, + TestName => { + Writable => 1, + WritePseudo => 1, + DelCheck => q{"Can't delete"}, + Protected => 1, + WriteOnly => 1, + Notes => q{ + this write-only tag may be used instead of FileName for dry-run tests of the + file renaming feature. Writing this tag prints the old and new file names + to the console, but does not affect the file itself + }, + ValueConvInv => '$self->InverseFileName($val)', + }, + FileSequence => { + Groups => { 0 => 'ExifTool', 1 => 'ExifTool', 2 => 'Other' }, + Notes => q{ + sequence number for each source file when extracting or copying information, + including files that fail the -if condition of the command-line application, + beginning at 0 for the first file. Not generated unless specifically + requested or the API L option is set + }, + }, + FileSize => { + Groups => { 1 => 'System', 2 => 'Other' }, + Notes => q{ + note that the print conversion for this tag uses historic prefixes: 1 kB = + 1024 bytes, etc. + }, + PrintConv => \&ConvertFileSize, + }, + ResourceForkSize => { + Groups => { 1 => 'System', 2 => 'Other' }, + Notes => q{ + size of the file's resource fork if it contains data. Mac OS only. If this + tag is generated the L option may be used to extract + resource-fork information as a sub-document. When writing, the resource + fork is preserved by default, but it may be deleted with C<-rsrc:all=> on + the command line + }, + PrintConv => \&ConvertFileSize, + }, + ZoneIdentifier => { + Groups => { 1 => 'System', 2 => 'Other' }, + Notes => q{ + Windows only. Existence indicates that the file has a Zone.Identifier + alternate data stream, which is used by some Windows browsers to mark + downloaded files as possibly unsafe to run. May be deleted to remove this + stream. Requires Win32API::File + }, + Writable => 1, + WritePseudo => 1, + Protected => 1, + }, + FileType => { + Groups => { 2 => 'Other' }, + Notes => q{ + a short description of the file type. For many file types this is the just + the uppercase file extension + }, + }, + FileTypeExtension => { + Groups => { 2 => 'Other' }, + Notes => q{ + a common lowercase extension for this file type, or uppercase with the -n + option + }, + PrintConv => 'lc $val', + }, + FileModifyDate => { + Description => 'File Modification Date/Time', + Notes => q{ + the filesystem modification date/time. Note that ExifTool may not be able + to handle filesystem dates before 1970 depending on the limitations of the + system's standard libraries + }, + Groups => { 1 => 'System', 2 => 'Time' }, + Writable => 1, + WritePseudo => 1, + DelCheck => q{"Can't delete"}, + # all writable pseudo-tags must be protected so -tagsfromfile fails with + # unrecognized files unless a pseudo tag is specified explicitly + Protected => 1, + Shift => 'Time', + ValueConv => 'ConvertUnixTime($val,1)', + ValueConvInv => 'GetUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + FileAccessDate => { + Description => 'File Access Date/Time', + Notes => q{ + the date/time of last access of the file. Note that this access time is + updated whenever any software, including ExifTool, reads the file + }, + Groups => { 1 => 'System', 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + FileCreateDate => { + Description => 'File Creation Date/Time', + Notes => q{ + the filesystem creation date/time. Windows/Mac only. In Windows, the file + creation date/time is preserved by default when writing if Win32API::File + and Win32::API are available. On Mac, this tag is extracted only if it or + the MacOS group is specifically requested or the API L option is + set to 2 or higher. Requires "setfile" for writing on Mac, which may be + installed by typing C in the Terminal + }, + Groups => { 1 => 'System', 2 => 'Time' }, + Writable => 1, + WritePseudo => 1, + DelCheck => q{"Can't delete"}, + Protected => 1, # all writable pseudo-tags must be protected! + Shift => 'Time', + ValueConv => '$^O eq "darwin" ? $val : ConvertUnixTime($val,1)', + ValueConvInv => q{ + return GetUnixTime($val,1) if $^O eq 'MSWin32'; + return $val if $^O eq 'darwin'; + warn "This tag is Windows/Mac only\n"; + return undef; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + FileInodeChangeDate => { + Description => 'File Inode Change Date/Time', + Notes => q{ + the date/time when the file's directory information was last changed. + Non-Windows systems only + }, + Groups => { 1 => 'System', 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + FilePermissions => { + Groups => { 1 => 'System', 2 => 'Other' }, + Notes => q{ + r=read, w=write and x=execute permissions for the file owner, group and + others. The ValueConv value is an octal number so bit test operations on + this value should be done in octal, eg. 'oct($filePermissions#) & 0200' + }, + Writable => 1, + WritePseudo => 1, + DelCheck => q{"Can't delete"}, + Protected => 1, # all writable pseudo-tags must be protected! + ValueConv => 'sprintf("%.3o", $val)', + ValueConvInv => 'oct($val & 07777)', + PrintConv => sub { + my ($mask, $val) = (0400, oct(shift)); + my %types = ( + 0010000 => 'p', + 0020000 => 'c', + 0040000 => 'd', + 0060000 => 'b', + 0120000 => 'l', + 0140000 => 's', + ); + my $str = $types{$val & 0170000} || '-'; + while ($mask) { + foreach (qw(r w x)) { + $str .= $val & $mask ? $_ : '-'; + $mask >>= 1; + } + } + return $str; + }, + PrintConvInv => sub { + my ($bit, $val, $str) = (8, 0, shift); + $str = substr($str, 1) if length($str) == 10; + return undef if length($str) != 9; + while ($bit >= 0) { + foreach (qw(r w x)) { + $val |= (1 << $bit) if substr($str, 8-$bit, 1) eq $_; + --$bit; + } + } + return sprintf('%.3o', $val); + }, + }, + FileAttributes => { + Groups => { 1 => 'System', 2 => 'Other' }, + Notes => q{ + extracted only if specifically requested or the L or L API + option is set. 2 or 3 values: 0. File type, 1. Attribute bits, 2. Windows + attribute bits if Win32API::File is available + }, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => [{ # stat device types (bitmask 0xf000) + 0x0000 => 'Unknown', + 0x1000 => 'FIFO', + 0x2000 => 'Character', + 0x3000 => 'Mux Character', + 0x4000 => 'Directory', + 0x5000 => 'XENIX Named', + 0x6000 => 'Block', + 0x7000 => 'Mux Block', + 0x8000 => 'Regular', + 0x9000 => 'VxFS Compressed', + 0xa000 => 'Symbolic Link', + 0xb000 => 'Solaris Shadow Inode', + 0xc000 => 'Socket', + 0xd000 => 'Solaris Door', + 0xe000 => 'BSD Whiteout', + },{ BITMASK => { # stat attribute bits (bitmask 0x0e00) + 9 => 'Sticky', + 10 => 'Set Group ID', + 11 => 'Set User ID', + }},{ BITMASK => { # Windows attribute bits + 0 => 'Read Only', + 1 => 'Hidden', + 2 => 'System', + 3 => 'Volume Label', + 4 => 'Directory', + 5 => 'Archive', + 6 => 'Device', + 7 => 'Normal', + 8 => 'Temporary', + 9 => 'Sparse File', + 10 => 'Reparse Point', + 11 => 'Compressed', + 12 => 'Offline', + 13 => 'Not Content Indexed', + 14 => 'Encrypted', + }}], + }, + FileDeviceID => { + Groups => { 1 => 'System', 2 => 'Other' }, + %systemTagsNotes, + PrintConv => '(($val >> 24) & 0xff) . "." . ($val & 0xffffff)', # (major.minor) + }, + FileDeviceNumber => { Groups => { 1 => 'System', 2 => 'Other' }, %systemTagsNotes }, + FileInodeNumber => { Groups => { 1 => 'System', 2 => 'Other' }, %systemTagsNotes }, + FileHardLinks => { Groups => { 1 => 'System', 2 => 'Other' }, %systemTagsNotes }, + FileUserID => { + Groups => { 1 => 'System', 2 => 'Other' }, + Notes => q{ + extracted only if specifically requested or the L or L API + option is set. Returns user ID number with the -n option, or name + otherwise. May be written with either user name or number + }, + Writable => 1, + WritePseudo => 1, + DelCheck => q{"Can't delete"}, + Protected => 1, # all writable pseudo-tags must be protected! + PrintConv => 'eval { getpwuid($val) } || $val', + PrintConvInv => 'eval { getpwnam($val) } || ($val=~/[^0-9]/ ? undef : $val)', + }, + FileGroupID => { + Groups => { 1 => 'System', 2 => 'Other' }, + Notes => q{ + extracted only if specifically requested or the L or L API + option is set. Returns group ID number with the -n option, or name + otherwise. May be written with either group name or number + }, + Writable => 1, + WritePseudo => 1, + DelCheck => q{"Can't delete"}, + Protected => 1, # all writable pseudo-tags must be protected! + PrintConv => 'eval { getgrgid($val) } || $val', + PrintConvInv => 'eval { getgrnam($val) } || ($val=~/[^0-9]/ ? undef : $val)', + }, + FileBlockSize => { Groups => { 1 => 'System', 2 => 'Other' }, %systemTagsNotes }, + FileBlockCount => { Groups => { 1 => 'System', 2 => 'Other' }, %systemTagsNotes }, + HardLink => { + Writable => 1, + DelCheck => q{"Can't delete"}, + WriteOnly => 1, + WritePseudo => 1, + Protected => 1, + Notes => q{ + this write-only tag is used to create a hard link with the specified name to + the source file. If the source file is edited, copied, renamed or moved in + the same operation as writing HardLink, then the link is made to the updated + file. Note that subsequent editing of either hard-linked file by exiftool + will break the link unless the -overwrite_original_in_place option is used + }, + ValueConvInv => '$val=~tr/\\\\/\//; $val', + }, + SymLink => { + Writable => 1, + DelCheck => q{"Can't delete"}, + WriteOnly => 1, + WritePseudo => 1, + Protected => 1, + Notes => q{ + this write-only tag is used to create a symbolic link with the specified + name to the source file. If the source file is edited, copied, renamed or + moved in the same operation as writing SymLink, then the link is made to the + updated file. The link uses an absolute path unless it is created in the + current working directory. Valid only for file systems that support + symbolic links. Note that subsequent editing of the file via the symbolic + link by exiftool will cause the link to be replaced by the edited file + without changing the original unless the -overwrite_original_in_place option + is used + }, + ValueConvInv => '$val=~tr/\\\\/\//; $val', + }, + MIMEType => { Notes => 'the MIME type of the source file', Groups => { 2 => 'Other' } }, + ImageWidth => { Notes => 'the width of the image in number of pixels' }, + ImageHeight => { Notes => 'the height of the image in number of pixels' }, + XResolution => { Notes => 'the horizontal pixel resolution' }, + YResolution => { Notes => 'the vertical pixel resolution' }, + MaxVal => { Notes => 'maximum pixel value in PPM or PGM image' }, + EXIF => { + Notes => q{ + the full EXIF data block from JPEG, PNG, JP2, MIE and MIFF images. This tag + is generated only if specifically requested + }, + Groups => { 0 => 'EXIF', 1 => 'EXIF' }, + Flags => ['Writable' ,'Protected', 'Binary', 'DelGroup'], + WriteCheck => q{ + return undef if $val =~ /^(II\x2a\0|MM\0\x2a)/; + return 'Invalid EXIF data'; + }, + }, + IPTC => { + Notes => q{ + the full IPTC data block. This tag is generated only if specifically + requested + }, + Groups => { 0 => 'IPTC', 1 => 'IPTC' }, + Flags => ['Writable', 'Protected', 'Binary', 'DelGroup'], + Priority => 0, # so main IPTC (which hopefully comes first) takes priority + WriteCheck => q{ + return undef if $val =~ /^(\x1c|\0+$)/; + return 'Invalid IPTC data'; + }, + }, + XMP => { + Notes => q{ + the XMP data block, but note that extended XMP in JPEG images may be split + into multiple blocks. This tag is generated only if specifically requested + }, + Groups => { 0 => 'XMP', 1 => 'XMP' }, + Flags => ['Writable', 'Protected', 'Binary', 'DelGroup'], + Priority => 0, # so main xmp (which usually comes first) takes priority + WriteCheck => q{ + require Image::ExifTool::XMP; + return Image::ExifTool::XMP::CheckXMP($self, $tagInfo, \$val); + }, + }, + XML => { + Notes => 'the XML data block, extracted for some file types', + Groups => { 0 => 'XML', 1 => 'XML' }, + Binary => 1, + }, + ICC_Profile => { + Notes => q{ + the full ICC_Profile data block. This tag is generated only if specifically + requested + }, + Groups => { 0 => 'ICC_Profile', 1 => 'ICC_Profile' }, + Flags => ['Writable' ,'Protected', 'Binary', 'DelGroup'], + WriteCheck => q{ + require Image::ExifTool::ICC_Profile; + return Image::ExifTool::ICC_Profile::ValidateICC(\$val); + }, + }, + CanonVRD => { + Notes => q{ + the full Canon DPP VRD trailer block. This tag is generated only if + specifically requested + }, + Groups => { 0 => 'CanonVRD', 1 => 'CanonVRD' }, + Flags => ['Writable' ,'Protected', 'Binary', 'DelGroup'], + Permanent => 0, # (this is 1 by default for MakerNotes tags) + WriteCheck => q{ + return undef if $val =~ /^CANON OPTIONAL DATA\0/; + return 'Invalid CanonVRD data'; + }, + }, + CanonDR4 => { + Notes => q{ + the full Canon DPP version 4 DR4 block. This tag is generated only if + specifically requested + }, + Groups => { 0 => 'CanonVRD', 1 => 'CanonVRD' }, + Flags => ['Writable' ,'Protected', 'Binary'], + Permanent => 0, # (this is 1 by default for MakerNotes tags) + WriteCheck => q{ + return undef if $val =~ /^IIII\x04\0\x04\0/; + return 'Invalid CanonDR4 data'; + }, + }, + Adobe => { + Notes => q{ + the JPEG APP14 Adobe segment. Extracted only if specified. See the + L for more information + }, + Groups => { 0 => 'APP14', 1 => 'Adobe' }, + WriteGroup => 'Adobe', + Flags => ['Writable' ,'Protected', 'Binary'], + }, + CurrentIPTCDigest => { + Notes => q{ + MD5 digest of existing IPTC data. All zeros if IPTC exists but Digest::MD5 + is not installed. Only calculated for IPTC in the standard location as + specified by the L. ExifTool + automates the handling of this tag in the MWG module -- see the + L for details + }, + ValueConv => 'unpack("H*", $val)', + }, + PreviewImage => { + Notes => 'JPEG-format embedded preview image', + Groups => { 2 => 'Preview' }, + Writable => 1, + WriteCheck => '$self->CheckImage(\$val)', + WriteGroup => 'All', + # can't delete, so set to empty string and return no error + DelCheck => '$val = ""; return undef', + # accept either scalar or scalar reference + RawConv => '$self->ValidateImage(ref $val ? $val : \$val, $tag)', + }, + ThumbnailImage => { + Groups => { 2 => 'Preview' }, + Notes => 'JPEG-format embedded thumbnail image', + RawConv => '$self->ValidateImage(ref $val ? $val : \$val, $tag)', + }, + OtherImage => { + Groups => { 2 => 'Preview' }, + Notes => 'other JPEG-format embedded image', + RawConv => '$self->ValidateImage(ref $val ? $val : \$val, $tag)', + }, + PreviewPNG => { + Groups => { 2 => 'Preview' }, + Notes => 'PNG-format embedded preview image', + Binary => 1, + }, + PreviewWMF => { + Groups => { 2 => 'Preview' }, + Notes => 'WMF-format embedded preview image', + Binary => 1, + }, + PreviewTIFF => { + Groups => { 2 => 'Preview' }, + Notes => 'TIFF-format embedded preview image', + Binary => 1, + }, + PreviewPDF => { + Groups => { 2 => 'Preview' }, + Notes => 'PDF-format embedded preview image', + Binary => 1, + }, + ExifByteOrder => { + Writable => 1, + DelCheck => q{"Can't delete"}, + Notes => q{ + represents the byte order of EXIF information. May be written to set the + byte order only for newly created EXIF segments + }, + PrintConv => { + II => 'Little-endian (Intel, II)', + MM => 'Big-endian (Motorola, MM)', + }, + }, + ExifUnicodeByteOrder => { + Writable => 1, + WriteOnly => 1, + DelCheck => q{"Can't delete"}, + Notes => q{ + specifies the byte order to use when writing EXIF Unicode text. The EXIF + specification is particularly vague about this byte ordering, and different + applications use different conventions. By default ExifTool writes Unicode + text in EXIF byte order, but this write-only tag may be used to force a + specific order. Applies to the EXIF UserComment tag when writing special + characters + }, + PrintConv => { + II => 'Little-endian (Intel, II)', + MM => 'Big-endian (Motorola, MM)', + }, + }, + ExifToolVersion => { + Description => 'ExifTool Version Number', + Groups => \%allGroupsExifTool, + Notes => 'the version of ExifTool currently running', + }, + ProcessingTime => { + Groups => { 0 => 'ExifTool', 1 => 'ExifTool', 2 => 'Other' }, + Notes => q{ + the clock time in seconds taken by ExifTool to extract information from this + file. Not generated unless specifically requested or the L API + option is set. Requires Time::HiRes + }, + PrintConv => 'sprintf("%.3g s", $val)', + }, + RAFVersion => { Notes => 'RAF file version number' }, + JPEGDigest => { + Notes => q{ + an MD5 digest of the JPEG quantization tables is combined with the component + sub-sampling values to generate the value of this tag. The result is + compared to known values in an attempt to deduce the originating software + based only on the JPEG image data. For performance reasons, this tag is + generated only if specifically requested or the API L option is set + to 3 or higher + }, + }, + JPEGQualityEstimate => { + Notes => q{ + an estimate of the IJG JPEG quality setting for the image, calculated from + the quantization tables. For performance reasons, this tag is generated + only if specifically requested or the API L option is set to 3 or + higher + }, + }, + JPEGImageLength => { + Notes => q{ + byte length of JPEG image without metadata. For performance reasons, this + tag is generated only if specifically requested or the API L option + is set to 3 or higher + }, + }, + # Validate (added from Validate.pm) + Now => { + Groups => { 0 => 'ExifTool', 1 => 'ExifTool', 2 => 'Time' }, + Notes => q{ + the current date/time. Useful when setting the tag values, eg. + C<"-modifydate. Not generated unless specifically requested or the + API L option is set + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + NewGUID => { + Groups => { 0 => 'ExifTool', 1 => 'ExifTool', 2 => 'Other' }, + Notes => q{ + generates a new, random GUID with format + YYYYmmdd-HHMM-SSNN-PPPP-RRRRRRRRRRRR, where Y=year, m=month, d=day, H=hour, + M=minute, S=second, N=file sequence number in hex, P=process ID in hex, and + R=random hex number; without dashes with the -n option. Not generated + unless specifically requested or the API L option is set + }, + PrintConv => '$val =~ s/(.{8})(.{4})(.{4})(.{4})/$1-$2-$3-$4-/; $val', + }, + ID3Size => { Notes => 'size of the ID3 data block' }, + Geotag => { + Writable => 1, + WriteOnly => 1, + WriteNothing => 1, + AllowGroup => '(exif|gps|xmp|xmp-exif)', + Notes => q{ + this write-only tag is used to define the GPS track log data or track log + file name. Currently supported track log formats are GPX, NMEA RMC/GGA/GLL, + KML, IGC, Garmin XML and TCX, Magellan PMGNTRK, Honeywell PTNTHPR, Winplus + Beacon text, and Bramor gEO log files. May be set to the special value of + "DATETIMEONLY" (all caps) to set GPS date/time tags if no input track points + are available. See L for details + }, + DelCheck => q{ + require Image::ExifTool::Geotag; + # delete associated tags + Image::ExifTool::Geotag::SetGeoValues($self, undef, $wantGroup); + }, + ValueConvInv => q{ + require Image::ExifTool::Geotag; + # always warn because this tag is never set (warning is "\n" on success) + my $result = Image::ExifTool::Geotag::LoadTrackLog($self, $val); + return '' if not defined $result; # deleting geo tags + return $result if ref $result; # geotag data hash reference + warn "$result\n"; # error string + }, + }, + Geotime => { + Writable => 1, + WriteOnly => 1, + AllowGroup => '(exif|gps|xmp|xmp-exif)', + Notes => q{ + this write-only tag is used to define a date/time for interpolating a + position in the GPS track specified by the Geotag tag. Writing this tag + causes GPS information to be written into the EXIF or XMP of the target + files. The local system timezone is assumed if the date/time value does not + contain a timezone. May be deleted to delete associated GPS tags. A group + name of "EXIF" or "XMP" may be specified to write or delete only EXIF or XMP + GPS tags + }, + DelCheck => q{ + require Image::ExifTool::Geotag; + # delete associated tags + Image::ExifTool::Geotag::SetGeoValues($self, undef, $wantGroup); + }, + ValueConvInv => q{ + require Image::ExifTool::Geotag; + warn Image::ExifTool::Geotag::SetGeoValues($self, $val, $wantGroup) . "\n"; + return undef; + }, + }, + Geosync => { + Writable => 1, + WriteOnly => 1, + WriteNothing => 1, + AllowGroup => '(exif|gps|xmp|xmp-exif)', + Shift => 'Time', # enables "+=" syntax as well as "=+" + Notes => q{ + this write-only tag specifies a time difference to add to Geotime for + synchronization with the GPS clock. For example, set this to "-12" if the + camera clock is 12 seconds faster than GPS time. Input format is + "[+-][[[DD ]HH:]MM:]SS[.ss]". Additional features allow calculation of time + differences and time drifts, and extraction of synchronization times from + image files. See the L for details + }, + ValueConvInv => q{ + require Image::ExifTool::Geotag; + return Image::ExifTool::Geotag::ConvertGeosync($self, $val); + }, + }, + ForceWrite => { + Groups => { 0 => '*', 1 => '*', 2 => '*' }, + Writable => 1, + WriteOnly => 1, + Notes => q{ + write-only tag used to force metadata in a file to be rewritten even if no + tag values are changed. May be set to "EXIF", "IPTC", "XMP" or "PNG" to + force the corresponding metadata type to be rewritten, "FixBase" to cause + EXIF to be rewritten only if the MakerNotes offset base was fixed, or "All" + to rewrite all of these metadata types. Values are case insensitive, and + multiple values may be separated with commas, eg. C<-ForceWrite=exif,xmp> + }, + }, + EmbeddedVideo => { Groups => { 0 => 'Trailer', 2 => 'Video' } }, + Trailer => { + Groups => { 0 => 'Trailer' }, + Notes => q{ + the full JPEG trailer data block. Extracted only if specifically requested + or the API RequestAll option is set to 3 or higher + }, + Writable => 1, + Protected => 1, + }, + PageCount => { Notes => 'the number of pages in a multi-page TIFF document' }, + SphericalVideoXML => { + Groups => { 0 => 'QuickTime', 1 => 'GSpherical', 2 => 'Video' }, + # (group 1 is 'GSpherical' to trigger creation of this tag when writing, + # but when reading the family 1 group is the track number) + Flags => [ 'Writable', 'Binary', 'Protected' ], + Notes => q{ + the SphericalVideoXML block from MP4/MOV videos. This tag is generated only + if specifically requested + }, + }, + ImageDataHash => { + Notes => q{ + Hash of image data. Generated only if specifically requested for JPEG, TIFF, + PNG, CRW, CR3, MRW, RAF, X3F, IIQ, JP2, JXL, HEIC and AVIF images, MOV/MP4 + videos, and some RIFF-based files such as AVI, WAV and WEBP. The hash + algorithm is set by the API L option, and is 'MD5' by default. + The hash includes the main image data, plus JpgFromRaw/OtherImage for some + formats, but does not include ThumbnailImage or PreviewImage. Includes + video and audio data for MOV/MP4. The L provide a way to store + the this hash value and the hash type in the file. + }, + }, +); + +# tags defined by UserParam option (added at runtime) +%Image::ExifTool::UserParam = ( + GROUPS => { 0 => 'UserParam', 1 => 'UserParam', 2 => 'Other' }, + PRIORITY => 0, +); + +# YCbCrSubSampling values (used by JPEG SOF, EXIF and XMP) +%Image::ExifTool::JPEG::yCbCrSubSampling = ( + '1 1' => 'YCbCr4:4:4 (1 1)', #PH + '2 1' => 'YCbCr4:2:2 (2 1)', #14 in Exif.pm + '2 2' => 'YCbCr4:2:0 (2 2)', #14 in Exif.pm + '4 1' => 'YCbCr4:1:1 (4 1)', #14 in Exif.pm + '4 2' => 'YCbCr4:1:0 (4 2)', #PH + '1 2' => 'YCbCr4:4:0 (1 2)', #PH + '1 4' => 'YCbCr4:4:1 (1 4)', #JD + '2 4' => 'YCbCr4:2:1 (2 4)', #JD +); + +# define common JPEG segments here to avoid overhead of loading JPEG module + +# JPEG SOF (start of frame) tags +# (ref http://www.w3.org/Graphics/JPEG/itu-t81.pdf) +%Image::ExifTool::JPEG::SOF = ( + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + NOTES => 'This information is extracted from the JPEG Start Of Frame segment.', + VARS => { NO_ID => 1 }, # tag ID's aren't meaningful for these tags + EncodingProcess => { + PrintHex => 1, + PrintConv => { + 0x0 => 'Baseline DCT, Huffman coding', + 0x1 => 'Extended sequential DCT, Huffman coding', + 0x2 => 'Progressive DCT, Huffman coding', + 0x3 => 'Lossless, Huffman coding', + 0x5 => 'Sequential DCT, differential Huffman coding', + 0x6 => 'Progressive DCT, differential Huffman coding', + 0x7 => 'Lossless, Differential Huffman coding', + 0x9 => 'Extended sequential DCT, arithmetic coding', + 0xa => 'Progressive DCT, arithmetic coding', + 0xb => 'Lossless, arithmetic coding', + 0xd => 'Sequential DCT, differential arithmetic coding', + 0xe => 'Progressive DCT, differential arithmetic coding', + 0xf => 'Lossless, differential arithmetic coding', + } + }, + BitsPerSample => { }, + ImageHeight => { }, + ImageWidth => { }, + ColorComponents => { }, + YCbCrSubSampling => { + Notes => 'calculated from components table', + PrintConv => \%Image::ExifTool::JPEG::yCbCrSubSampling, + }, +); + +# JPEG JFIF APP0 definitions +%Image::ExifTool::JFIF::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'JFIF', 1 => 'JFIF', 2 => 'Image' }, + DATAMEMBER => [ 2, 3, 5 ], + 0 => { + Name => 'JFIFVersion', + Format => 'int8u[2]', + PrintConv => 'sprintf("%d.%.2d", split(" ",$val))', + Mandatory => 1, + }, + 2 => { + Name => 'ResolutionUnit', + Writable => 1, + RawConv => '$$self{JFIFResolutionUnit} = $val', + PrintConv => { + 0 => 'None', + 1 => 'inches', + 2 => 'cm', + }, + Priority => -1, + Mandatory => 1, + }, + 3 => { + Name => 'XResolution', + Format => 'int16u', + Writable => 1, + Priority => -1, + RawConv => '$$self{JFIFXResolution} = $val', + Mandatory => 1, + }, + 5 => { + Name => 'YResolution', + Format => 'int16u', + Writable => 1, + Priority => -1, + RawConv => '$$self{JFIFYResolution} = $val', + Mandatory => 1, + }, + 7 => { + Name => 'ThumbnailWidth', + RawConv => '$val ? $$self{JFIFThumbnailWidth} = $val : undef', + }, + 8 => { + Name => 'ThumbnailHeight', + RawConv => '$val ? $$self{JFIFThumbnailHeight} = $val : undef', + }, + 9 => { + Name => 'ThumbnailTIFF', + Groups => { 2 => 'Preview' }, + Format => 'undef[3*($val{7}||0)*($val{8}||0)]', + Notes => 'raw RGB thumbnail data, extracted as a TIFF image', + RawConv => 'length($val) ? $val : undef', + ValueConv => sub { + my ($val, $et) = @_; + my $len = length $val; + return \ "Binary data $len bytes" unless $et->Options('Binary'); + my $img = MakeTiffHeader($$et{JFIFThumbnailWidth},$$et{JFIFThumbnailHeight},3,8) . $val; + return \$img; + }, + }, +); +%Image::ExifTool::JFIF::Extension = ( + GROUPS => { 0 => 'JFIF', 1 => 'JFXX', 2 => 'Image' }, + NOTES => 'Thumbnail images extracted from the JFXX segment.', + 0x10 => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Notes => 'JPEG-format thumbnail image', + RawConv => '$self->ValidateImage(\$val,$tag)', + }, + 0x11 => { # (untested) + Name => 'ThumbnailTIFF', + Groups => { 2 => 'Preview' }, + Notes => 'raw palette-color thumbnail data, extracted as a TIFF image', + RawConv => '(length $val > 770 and $val !~ /^\0\0/) ? $val : undef', + ValueConv => sub { + my ($val, $et) = @_; + my $len = length $val; + return \ "Binary data $len bytes" unless $et->Options('Binary'); + my ($w, $h) = unpack('CC', $val); + my $img = MakeTiffHeader($w,$h,1,8,undef,substr($val,2,768)) . substr($val,770); + return \$img; + }, + }, + 0x13 => { + Name => 'ThumbnailTIFF', + Groups => { 2 => 'Preview' }, + Notes => 'raw RGB thumbnail data, extracted as a TIFF image', + RawConv => '(length $val > 2 and $val !~ /^\0\0/) ? $val : undef', + ValueConv => sub { + my ($val, $et) = @_; + my $len = length $val; + return \ "Binary data $len bytes" unless $et->Options('Binary'); + my ($w, $h) = unpack('CC', $val); + my $img = MakeTiffHeader($w,$h,3,8) . substr($val,2); + return \$img; + }, + }, + # Apple may add "AMPF" to the end of the JFIF record, + # possibly indicating the existence of MPF images (ref forum12677) +); + +# Composite tags (accumulation of all Composite tag tables) +%Image::ExifTool::Composite = ( + GROUPS => { 0 => 'Composite', 1 => 'Composite' }, + TABLE_NAME => 'Image::ExifTool::Composite', + SHORT_NAME => 'Composite', + VARS => { NO_ID => 1 }, # want empty tagID's for Composite tags + WRITE_PROC => \&DummyWriteProc, +); + +my %compositeID; # lookup for new ID's of Composite tags based on original ID + +# static private ExifTool variables + +%allTables = ( ); # list of all tables loaded (except Composite tags) +@tableOrder = ( ); # order the tables were loaded + +#------------------------------------------------------------------------------ +# Warning handler routines (warning string stored in $evalWarning) +# +# Set warning message +# Inputs: 0) warning string (undef to reset warning) +sub SetWarning($) { $evalWarning = $_[0]; } + +# Get warning message +sub GetWarning() { return $evalWarning; } + +# Clean unnecessary information (line number, LF) from warning +# Inputs: 0) warning string or undef to use $evalWarning +# Returns: cleaned warning +sub CleanWarning(;$) +{ + my $str = shift; + unless (defined $str) { + return undef unless defined $evalWarning; + $str = $evalWarning; + } + $str = $1 if $str =~ /(.*) at /s; + $str =~ s/\s+$//s; + return $str; +} + +#============================================================================== +# New - create new ExifTool object +# Inputs: 0) reference to exiftool object or ExifTool class name +# Returns: blessed ExifTool object ref +sub new +{ + local $_; + my $that = shift; + my $class = ref($that) || $that || 'Image::ExifTool'; + my $self = bless {}, $class; + + # make sure our main Exif tag table has been loaded + GetTagTable("Image::ExifTool::Exif::Main"); + + $self->ClearOptions(); # create default options hash + $$self{VALUE} = { }; # must initialize this for warning messages + $$self{PATH} = [ ]; # (this too) + $$self{DEL_GROUP} = { }; # lookup for groups to delete when writing + $$self{SAVE_COUNT} = 0; # count calls to SaveNewValues() + $$self{FILE_SEQUENCE} = 0; # sequence number for files when reading + $$self{FILES_WRITTEN} = 0; # count of files successfully written + $$self{INDENT2} = ''; # indentation of verbose messages from SetNewValue + $$self{ALT_EXIFTOOL} = { }; # alternate exiftool objects + + # initialize our new groups for writing + $self->SetNewGroups(@defaultWriteGroups); + + return $self; +} + +#------------------------------------------------------------------------------ +# ImageInfo - return specified information from image file +# Inputs: 0) [optional] ExifTool object reference +# 1) filename, file reference, or scalar data reference +# 2-N) list of tag names to find (or tag list reference or options reference) +# Returns: reference to hash of tag/value pairs (with "Error" entry on error) +# Notes: +# - if no tags names are specified, the values of all tags are returned +# - tags may be specified with leading '-' to exclude, or trailing '#' for ValueConv +# - can pass a reference to list of tags to find, in which case the list will +# be updated with the tags found in the proper case and in the specified order. +# - can pass reference to hash specifying options +# - returned tag values may be scalar references indicating binary data +# - see ClearOptions() below for a list of options and their default values +# Examples: +# use Image::ExifTool 'ImageInfo'; +# my $info = ImageInfo($file, 'DateTimeOriginal', 'ImageSize'); +# - or - +# my $et = new Image::ExifTool; +# my $info = $et->ImageInfo($file, \@tagList, {Sort=>'Group0'} ); +sub ImageInfo($;@) +{ + local $_; + # get our ExifTool object ($self) or create one if necessary + my $self; + if (ref $_[0] and UNIVERSAL::isa($_[0],'Image::ExifTool')) { + $self = shift; + } else { + $self = new Image::ExifTool; + } + my %saveOptions = %{$$self{OPTIONS}}; # save original options + + # initialize file information + $$self{FILENAME} = $$self{RAF} = undef; + + $self->ParseArguments(@_); # parse our function arguments + $self->ExtractInfo(undef); # extract meta information from image + my $info = $self->GetInfo(undef); # get requested information + + $$self{OPTIONS} = \%saveOptions; # restore original options + + return $info; # return requested information +} + +#------------------------------------------------------------------------------ +# Get/set ExifTool options +# Inputs: 0) ExifTool object reference, +# 1) Parameter name (case insensitive), 2) Value to set the option +# 3-N) More parameter/value pairs +# Returns: original value of last option specified +sub Options($$;@) +{ + local $_; + my $self = shift; + my $options = $$self{OPTIONS}; + my $oldVal; + + while (@_) { + my $param = shift; + my $plus; + # fix parameter case if necessary + unless (exists $$options{$param}) { + $plus = $param =~ s/\+$//; + my ($fixed) = grep /^$param$/i, keys %$options; + if ($fixed) { + $param = $fixed; + } else { + $param =~ s/^Group(\d*)$/Group$1/i; + } + } + $oldVal = $$options{$param}; + if (ref $oldVal eq 'HASH' and ($param eq 'Compact' or $param eq 'XMPShorthand')) { + # get previous Compact/XMPShorthand setting + $oldVal = $$oldVal{$param}; + } + last unless @_; + my $newVal = shift; + if ($param eq 'Lang') { + # allow this to be set to undef to select the default language + $newVal = $defaultLang unless defined $newVal; + if ($newVal eq $defaultLang) { + $$options{$param} = $newVal; + delete $$self{CUR_LANG}; + # make sure the language is available + } else { + my %langs = map { $_ => 1 } @langs; + if ($langs{$newVal} and eval "require Image::ExifTool::Lang::$newVal") { + my $xlat = "Image::ExifTool::Lang::${newVal}::Translate"; + no strict 'refs'; + if (%$xlat) { + $$self{CUR_LANG} = \%$xlat; + $$options{$param} = $newVal; + } + } + } # else don't change Lang + } elsif ($param eq 'Exclude' and defined $newVal) { + # clone Exclude list and expand shortcuts + my @exclude; + if (ref $newVal eq 'ARRAY') { + @exclude = @$newVal; + } else { + @exclude = ($newVal); + } + ExpandShortcuts(\@exclude, 1); # (also remove '#' suffix) + $$options{$param} = \@exclude; + } elsif ($param =~ /^Charset/ or $param eq 'IPTCCharset') { + # only allow valid character sets to be set + if ($newVal) { + my $charset = $charsetName{lc $newVal}; + if ($charset) { + $$options{$param} = $charset; + # maintain backward-compatibility with old IPTCCharset option + $$options{CharsetIPTC} = $charset if $param eq 'IPTCCharset'; + } else { + warn "Invalid Charset $newVal\n"; + } + } elsif ($param eq 'CharsetEXIF' or $param eq 'CharsetFileName' or $param eq 'CharsetRIFF') { + $$options{$param} = $newVal; # only these may be set to a false value + } elsif ($param eq 'CharsetQuickTime') { + $$options{$param} = 'MacRoman'; # QuickTime defaults to MacRoman + } else { + $$options{$param} = 'Latin'; # all others default to Latin + } + } elsif ($param eq 'UserParam') { + # clear options if $newVal is undef + defined $newVal or $$options{$param} = {}, next; + my $table = GetTagTable('Image::ExifTool::UserParam'); + # allow initialization of entire UserParam hash + if (ref $newVal eq 'HASH') { + my %newParams; + foreach (sort keys %$newVal) { + my $lcTag = lc $_; + $newParams{$lcTag} = $$newVal{$_}; + delete $$table{$lcTag}; + AddTagToTable($table, $lcTag, $_); + } + $$options{$param} = \%newParams; + next; + } + my ($force, $paramName); + # set/reset single UserParam parameter + if ($newVal =~ /(.*?)=(.*)/s) { + $paramName = $1; + $newVal = $2; + $force = 1 if $paramName =~ s/\^$//; + $paramName =~ tr/-_a-zA-Z0-9#//dc; + $param = lc $paramName; + } else { + ($param = lc $newVal) =~ tr/-_a-zA-Z0-9#//dc; + undef $newVal; + } + delete $$table{$param}; + $oldVal = $$options{UserParam}{$param}; + if (defined $newVal) { + if (length $newVal or $force) { + $$options{UserParam}{$param} = $newVal; + AddTagToTable($table, $param, $paramName); + } else { + delete $$options{UserParam}{$param}; + } + } + # remove alternate version of tag + $param .= '#' unless $param =~ s/#$//; + delete $$table{$param}; + delete $$options{UserParam}{$param}; + } elsif ($param eq 'RequestTags') { + if (defined $newVal) { + # parse list from delimited string if necessary + my @reqList = (ref $newVal eq 'ARRAY') ? @$newVal : ($newVal =~ /[-\w?*:]+/g); + ExpandShortcuts(\@reqList); + # add to existing list + $$options{$param} or $$options{$param} = [ ]; + foreach (@reqList) { + /^(.*:)?([-\w?*]*)#?$/ or next; + push @{$$options{$param}}, lc($2) if $2; + next unless $1; + # add requested groups with trailing colon + push @{$$options{$param}}, lc($_).':' foreach split /:/, $1; + } + } else { + $$options{$param} = undef; # clear the list + } + } elsif ($param eq 'IgnoreTags') { + if (defined $newVal) { + # parse list from delimited string if necessary + my @ignoreList = (ref $newVal eq 'ARRAY') ? @$newVal : ($newVal =~ /[-\w?*:]+/g); + ExpandShortcuts(\@ignoreList); + # add to existing tags to ignore + $$options{$param} or $$options{$param} = { }; + foreach (@ignoreList) { + /^(.*:)?([-\w?*]+)#?$/ or next; + $$options{$param}{lc $2} = 1; + } + } else { + $$options{$param} = undef; # clear the option + } + } elsif ($param eq 'ListJoin') { + $$options{$param} = $newVal; + # set the old List and ListSep options for backward compatibility + if (defined $newVal) { + $$options{List} = 0; + $$options{ListSep} = $newVal; + } else { + $$options{List} = 1; + # (ListSep must be defined) + } + } elsif ($param eq 'List') { + $$options{$param} = $newVal; + # set the new ListJoin option for forward compatibility + $$options{ListJoin} = $newVal ? undef : $$options{ListSep}; + } elsif ($param eq 'Compact' or $param eq 'XMPShorthand') { + # set Compact and XMPShorthand options, preserving backward compatibility + my ($p, %compact); + foreach $p ('Compact','XMPShorthand') { + my $val = $param eq $p ? $newVal : $$options{Compact}{$p}; + if (defined $val) { + my @v = ($val =~ /\w+/g); + my $opt = ($p eq 'Compact') ? \%compactOpt : \%xmpShorthandOpt; + foreach (@v) { + my $set = $$opt{lc $_} or warn("Invalid $p setting '${_}'\n"), return $oldVal; + ref $set or $compact{$set} = 1, next; + $compact{$_} = 1 foreach @$set; + } + } + $compact{$p} = $val; # preserve most recent setting + } + $$options{Compact} = $$options{XMPShorthand} = \%compact; + } elsif ($param eq 'NoWarning') { + # validate regular expression + undef $evalWarning; + if (defined $newVal) { + local $SIG{'__WARN__'} = \&SetWarning; + eval { $param =~ /$newVal/ }; + $@ and $evalWarning = $@; + } + if ($evalWarning) { + warn 'NoWarning: ' . CleanWarning() . "\n"; + next; + } + # add to existing expression if specified + if ($plus and defined $oldVal) { + $newVal = defined $newVal ? "$oldVal|$newVal" : $oldVal; + } + $$options{$param} = $newVal; + } elsif ($param eq 'ImageHashType') { + if (defined $newVal and $newVal =~ /^(MD5|SHA256|SHA512)$/i) { + $$options{$param} = uc($newVal); + } else { + warn("Invalid $param setting '${newVal}'\n"), return $oldVal; + } + } elsif ($param eq 'StructFormat') { + if (defined $newVal) { + $newVal =~ /^(JSON|JSONQ)$/i or warn("Invalid $param setting '${newVal}'\n"), return $oldVal; + $newVal = uc($newVal); + } + $$options{$param} = $newVal; + } else { + if ($param eq 'Escape') { + # set ESCAPE_PROC + if (defined $newVal and $newVal eq 'XML') { + require Image::ExifTool::XMP; + $$self{ESCAPE_PROC} = \&Image::ExifTool::XMP::EscapeXML; + } elsif (defined $newVal and $newVal eq 'HTML') { + require Image::ExifTool::HTML; + $$self{ESCAPE_PROC} = \&Image::ExifTool::HTML::EscapeHTML; + } else { + delete $$self{ESCAPE_PROC}; + } + # must forget saved values since they depend on Escape method + $$self{BOTH} = { }; + } elsif ($param eq 'GlobalTimeShift') { + delete $$self{GLOBAL_TIME_OFFSET}; # reset our calculated offset + } elsif ($param eq 'TimeZone' and defined $newVal and length $newVal) { + $ENV{TZ} = $newVal; + eval { require POSIX; POSIX::tzset() }; + } elsif ($param eq 'Validate') { + # load Validate module if Validate option enabled + $newVal and require Image::ExifTool::Validate; + } + $$options{$param} = $newVal; + } + } + return $oldVal; +} + +#------------------------------------------------------------------------------ +# ClearOptions - set options to default values +# Inputs: 0) ExifTool object reference +sub ClearOptions($) +{ + local $_; + my $self = shift; + + $$self{OPTIONS} = { }; # clear all options + + # load default options + $$self{OPTIONS}{$$_[0]} = $$_[1] foreach @availableOptions; + + # keep necessary member variables in sync with options + delete $$self{CUR_LANG}; + delete $$self{ESCAPE_PROC}; + + # load user-defined default options + if (%Image::ExifTool::UserDefined::Options) { + foreach (keys %Image::ExifTool::UserDefined::Options) { + $self->Options($_, $Image::ExifTool::UserDefined::Options{$_}); + } + } +} + +#------------------------------------------------------------------------------ +# Extract meta information from image +# Inputs: 0) ExifTool object reference +# 1-N) Same as ImageInfo() +# Returns: 1 if this was a valid image, 0 otherwise +# Notes: pass an undefined value to avoid parsing arguments +# Internal 'ReEntry' option allows this routine to be called recursively +sub ExtractInfo($;@) +{ + local $_; + my $self = shift; + my $options = $$self{OPTIONS}; # pointer to current options + my $fast = $$options{FastScan} || 0; + my $req = $$self{REQ_TAG_LOOKUP}; + my $reqAll = $$options{RequestAll} || 0; + my (%saveOptions, $reEntry, $rsize, $zid, $type, @startTime, $saveOrder, $isDir); + + # check for internal ReEntry option to allow recursive calls to ExtractInfo + if (ref $_[1] eq 'HASH' and $_[1]{ReEntry} and + (ref $_[0] eq 'SCALAR' or ref $_[0] eq 'GLOB')) + { + # save necessary members for restoring later + $reEntry = { + RAF => $$self{RAF}, + PROCESSED => $$self{PROCESSED}, + EXIF_DATA => $$self{EXIF_DATA}, + EXIF_POS => $$self{EXIF_POS}, + FILE_TYPE => $$self{FILE_TYPE}, + }; + $saveOrder = GetByteOrder(), + $$self{RAF} = new File::RandomAccess($_[0]); + $$self{PROCESSED} = { }; + delete $$self{EXIF_DATA}; + delete $$self{EXIF_POS}; + } else { + if (defined $_[0] or $$options{HtmlDump} or $$req{validate}) { + %saveOptions = %$options; # save original options + + # require duplicates for html dump + $self->Options(Duplicates => 1) if $$options{HtmlDump}; + # enable Validate option if Validate tag is requested + $self->Options(Validate => 1) if $$req{validate}; + + if (defined $_[0]) { + # only initialize filename if called with arguments + $$self{FILENAME} = undef; # name of file (or '' if we didn't open it) + $$self{RAF} = undef; # RandomAccess object reference + + $self->ParseArguments(@_); # initialize from our arguments + } + } + # initialize ExifTool object members + $self->Init(); + + delete $$self{MAKER_NOTE_FIXUP}; # fixup information for extracted maker notes + delete $$self{MAKER_NOTE_BYTE_ORDER}; + + # return our version number + $self->FoundTag('ExifToolVersion', "$VERSION$RELEASE"); + $self->FoundTag('Now', $self->TimeNow()) if $$req{now} or $reqAll; + $self->FoundTag('NewGUID', NewGUID()) if $$req{newguid} or $reqAll; + # generate sequence number if necessary + $self->FoundTag('FileSequence', $$self{FILE_SEQUENCE}) if $$req{filesequence} or $reqAll; + + if ($$req{processingtime} or $reqAll) { + eval { require Time::HiRes; @startTime = Time::HiRes::gettimeofday() }; + if (not @startTime and $$req{processingtime}) { + $self->WarnOnce('Install Time::HiRes to generate ProcessingTime'); + } + } + + # create Hash object if ImageDataHash is requested + if ($$req{imagedatahash} and not $$self{ImageDataHash}) { + my $imageHashType = $self->Options('ImageHashType'); + if ($imageHashType =~ /^SHA(256|512)$/i) { + if (require Digest::SHA) { + $$self{ImageDataHash} = Digest::SHA->new($1); + } else { + $self->WarnOnce("Install Digest::SHA to calculate image data SHA$1"); + } + } elsif (require Digest::MD5) { + $$self{ImageDataHash} = Digest::MD5->new; + } else { + $self->WarnOnce('Install Digest::MD5 to calculate image data MD5'); + } + } + ++$$self{FILE_SEQUENCE}; # count files read + } + + my $filename = $$self{FILENAME}; # image file name ('' if already open) + my $raf = $$self{RAF}; # RandomAccess object + + local *EXIFTOOL_FILE; # avoid clashes with global namespace + + my $realname = $filename; + unless ($raf) { + # save file name + if (defined $filename and $filename ne '') { + unless ($filename eq '-') { + # extract file name from pipe if necessary + $realname =~ /\|$/ and $realname =~ s/^.*?"(.*?)".*/$1/s; + my ($dir, $name) = SplitFileName($realname); + $self->FoundTag('FileName', $name); + if ($$req{basename} or + ($reqAll and not $$self{EXCL_TAG_LOOKUP}{basename})) + { + $self->FoundTag('BaseName', $name =~ /(.*)\./ ? $1 : $name); + } + $self->FoundTag('Directory', $dir) if defined $dir and length $dir; + if ($$req{filepath} or + ($reqAll and not $$self{EXCL_TAG_LOOKUP}{filepath})) + { + local $SIG{'__WARN__'} = \&SetWarning; + if (eval { require Cwd }) { + my $path = eval { Cwd::abs_path($filename) }; + $self->FoundTag('FilePath', $path) if defined $path; + } elsif ($$req{filepath}) { + $self->WarnOnce('The Perl Cwd module must be installed to use FilePath'); + } + } + # get size of resource fork on Mac OS + $rsize = -s "$filename/..namedfork/rsrc" if $^O eq 'darwin' and not $$self{IN_RESOURCE}; + # check to see if Zone.Identifier file exists in Windows + if ($^O eq 'MSWin32' and eval { require Win32API::File }) { + my $wattr; + my $zfile = "${filename}:Zone.Identifier"; + if ($self->EncodeFileName($zfile)) { + $wattr = eval { Win32API::File::GetFileAttributesW($zfile) }; + } else { + $wattr = eval { Win32API::File::GetFileAttributes($zfile) }; + } + $zid = 1 unless $wattr == Win32API::File::INVALID_FILE_ATTRIBUTES(); + } + } + # open the file + if ($self->Open(\*EXIFTOOL_FILE, $filename)) { + # create random access file object + $raf = new File::RandomAccess(\*EXIFTOOL_FILE); + # patch to force pipe to be buffered because seek returns success + # in Windows cmd shell pipe even though it really failed + $$raf{TESTED} = -1 if $filename eq '-' or $filename =~ /\|$/; + $$self{RAF} = $raf; + } elsif ($self->IsDirectory($filename)) { + $isDir = 1; + } else { + $self->Error('Error opening file'); + } + } else { + $self->Error('No file specified'); + } + } + + while ($raf or $isDir) { + my (@stat, $plainFile); + if ($reEntry) { + # we already set these tags + } elsif (not $raf) { + @stat = stat $filename; + } elsif (not $$raf{FILE_PT}) { + # get file size from image in memory + $self->FoundTag('FileSize', length ${$$raf{BUFF_PT}}); + } elsif (-f $$raf{FILE_PT}) { + # get file tags if this is a plain file + @stat = stat _; + $plainFile = 1; + # hack to patch Windows daylight savings time bug + @stat[8,9,10] = $self->GetFileTime($$raf{FILE_PT}) if $^O eq 'MSWin32'; + } else { + # (note that Windows directories will still show the + # daylight savings time bug -- should fix this sometime) + @stat = stat $$raf{FILE_PT}; + } + my $fileSize = $stat[7]; + $self->FoundTag('FileSize', $stat[7]) if defined $stat[7]; + $self->FoundTag('ResourceForkSize', $rsize) if $rsize; + $self->FoundTag('ZoneIdentifier', 'Exists') if $zid; + $self->FoundTag('FileModifyDate', $stat[9]) if defined $stat[9]; + $self->FoundTag('FileAccessDate', $stat[8]) if defined $stat[8]; + my $cTag = $^O eq 'MSWin32' ? 'FileCreateDate' : 'FileInodeChangeDate'; + $self->FoundTag($cTag, $stat[10]) if defined $stat[10]; + $self->FoundTag('FilePermissions', $stat[2]) if defined $stat[2]; + # extract more system info if SystemTags option is set + if (@stat) { + my $sys = $$options{SystemTags} || ($reqAll and not defined $$options{SystemTags}); + if ($sys or $$req{fileattributes}) { + my @attr = ($stat[2] & 0xf000, $stat[2] & 0x0e00); + # add Windows file attributes if available + if ($^O eq 'MSWin32' and defined $filename and $filename ne '' and $filename ne '-') { + local $SIG{'__WARN__'} = \&SetWarning; + if (eval { require Win32API::File }) { + my $wattr; + my $file = $filename; + if ($self->EncodeFileName($file)) { + $wattr = eval { Win32API::File::GetFileAttributesW($file) }; + } else { + $wattr = eval { Win32API::File::GetFileAttributes($file) }; + } + push @attr, $wattr if defined $wattr and $wattr != 0xffffffff; + } + } + $self->FoundTag('FileAttributes', "@attr"); + } + $self->FoundTag('FileDeviceNumber', $stat[0]) if $sys or $$req{filedevicenumber}; + $self->FoundTag('FileInodeNumber', $stat[1]) if $sys or $$req{fileinodenumber}; + $self->FoundTag('FileHardLinks', $stat[3]) if $sys or $$req{filehardlinks}; + $self->FoundTag('FileUserID', $stat[4]) if $sys or $$req{fileuserid}; + $self->FoundTag('FileGroupID', $stat[5]) if $sys or $$req{filegroupid}; + $self->FoundTag('FileDeviceID', $stat[6]) if $sys or $$req{filedeviceid}; + $self->FoundTag('FileBlockSize', $stat[11]) if $sys or $$req{fileblocksize}; + $self->FoundTag('FileBlockCount', $stat[12]) if $sys or $$req{fileblockcount}; + } + # extract MDItem tags if requested (only on plain files) + if ($^O eq 'darwin' and defined $filename and $filename ne '' and defined $fileSize) { + my $reqMacOS = ($reqAll > 1 or $$req{'macos:'}); + my $crDate = ($reqMacOS || $$req{filecreatedate}); + my $mdItem = ($reqMacOS || $$options{MDItemTags} || grep /^mditem/, keys %$req); + my $xattr = ($reqMacOS || $$options{XAttrTags} || grep /^xattr/, keys %$req); + if ($crDate or $mdItem or $xattr) { + require Image::ExifTool::MacOS; + Image::ExifTool::MacOS::GetFileCreateDate($self, $filename) if $crDate; + Image::ExifTool::MacOS::ExtractMDItemTags($self, $filename) if $mdItem and $plainFile; + Image::ExifTool::MacOS::ExtractXAttrTags($self, $filename) if $xattr; + } + } + # do whatever else we can with directories, then return + if ($isDir or (defined $stat[2] and ($stat[2] & 0170000) == 0040000)) { + $self->FoundTag('FileType', 'DIR'); + $self->FoundTag('FileTypeExtension', ''); + $self->ExtractAltInfo(); + $raf->Close() if $raf; + return 1; + } + # get list of file types to check + my ($tiffType, %noMagic, $recognizedExt); + my $ext = $$self{FILE_EXT} = GetFileExtension($realname); + # set $recognizedExt if this file type is recognized by extension only + $recognizedExt = $ext if defined $ext and not defined $magicNumber{$ext} and + defined $moduleName{$ext} and not $moduleName{$ext}; + my @fileTypeList = GetFileType($realname); + if ($fast >= 4) { + if (@fileTypeList) { + $type = shift @fileTypeList; + $self->SetFileType($$self{FILE_TYPE} = $type); + } else { + $self->Error('Unknown file type'); + } + $self->ExtractAltInfo(); + last; # don't read the file + } + if (@fileTypeList) { + # add remaining types to end of list so we test them all + my $pat = join '|', @fileTypeList; + push @fileTypeList, grep(!/^($pat)$/, @fileTypes); + $tiffType = $$self{FILE_EXT}; + unless ($fast == 3) { + $noMagic{MXF} = 1; # don't do magic number test on MXF or DV files + $noMagic{DV} = 1; + } + } else { + # scan through all recognized file types + @fileTypeList = @fileTypes; + $tiffType = 'TIFF'; + } + push @fileTypeList, ''; # end of list marker + # initialize the input file for seeking in binary data + $raf->BinMode(); # set binary mode before we start reading + my $pos = $raf->Tell(); # get file position so we can rewind + # loop through list of file types to test + my ($buff, $seekErr); + my %dirInfo = ( RAF => $raf, Base => $pos, TestBuff => \$buff ); + # read start of file for testing + $raf->Read($buff, $testLen) or $buff = ''; + $raf->Seek($pos, 0) or $seekErr = 1; + until ($seekErr) { + my $unkHeader; + $type = shift @fileTypeList; + if ($type) { + if ($magicNumber{$type}) { + # do quick test for this file type to avoid loading module unnecessarily + next if $buff !~ /^$magicNumber{$type}/s and not $noMagic{$type}; + } else { + # keep checking for other types if we recognize this file only by extension + next if defined $moduleName{$type} and not $moduleName{$type}; + next if $fast > 2; # keep checking if we aren't processing the file + } + next if $weakMagic{$type} and defined $recognizedExt; + } elsif (not defined $type) { + last; + } elsif ($recognizedExt) { + $type = $recognizedExt; # set type from recognized file extension only + } else { + # last ditch effort to scan past unknown header for JPEG/TIFF + next unless $buff =~ /(\xff\xd8\xff|MM\0\x2a|II\x2a\0)/g; + $type = ($1 eq "\xff\xd8\xff") ? 'JPEG' : 'TIFF'; + my $skip = pos($buff) - length($1); + $dirInfo{Base} = $pos + $skip; + $raf->Seek($pos + $skip, 0) or $seekErr = 1, last; + $self->Warn("Processing $type-like data after unknown $skip-byte header"); + $unkHeader = 1 unless $$self{DOC_NUM}; + } + # save file type in member variable + $$self{FILE_TYPE} = $type; + $dirInfo{Parent} = ($type eq 'TIFF') ? $tiffType : $type; + # don't process the file when FastScan == 3 + if ($fast == 3 and not $processType{$type}) { + unless ($weakMagic{$type} and (not $ext or $ext ne $type)) { + $self->SetFileType($dirInfo{Parent}); + } + last; + } + my $module = $moduleName{$type}; + $module = $type unless defined $module; + my $func = "Process$type"; + + # load module if necessary + if ($module) { + require "Image/ExifTool/$module.pm"; + $func = "Image::ExifTool::${module}::$func"; + } elsif ($module eq '0') { + $self->SetFileType(); + $self->Warn('Unsupported file type'); + last; + } + push @{$$self{PATH}}, $type; # save file type in metadata PATH + + # process the file + no strict 'refs'; + my $result = &$func($self, \%dirInfo); + use strict 'refs'; + + pop @{$$self{PATH}}; + + if ($result) { # all done if successful + if ($unkHeader) { + $self->DeleteTag('FileType'); + $self->DeleteTag('FileTypeExtension'); + $self->DeleteTag('MIMEType'); + $self->VPrint(0,"Reset file type due to unknown header\n"); + } + last; + } + # seek back to try again from the same position in the file + $raf->Seek($pos, 0) or $seekErr = 1, last; + } + if (not defined $type and not $$self{DOC_NUM}) { + # if we were given a single image with a known type there + # must be a format error since we couldn't read it, otherwise + # it is likely we don't support images of this type + my $fileType = GetFileType($realname) || ''; + my $err; + if (not length $buff) { + $err = 'File is empty'; + } else { + my $ch = substr($buff, 0, 1); + if (length $buff < 16 or $buff =~ /[^\Q$ch\E]/) { + if ($fileType eq 'RAW') { + $err = 'Unsupported RAW file type'; + } elsif ($fileType) { + $err = 'File format error'; + } else { + $err = 'Unknown file type'; + } + } else { + # provide some insight into the content of some corrupted files + if ($$self{OPTIONS}{FastScan}) { + $err = 'File header is all'; + } else { + my $num = 0; + for (;;) { + $raf->Read($buff, 65536) or undef($num), last; + $buff =~ /[^\Q$ch\E]/g and $num += pos($buff) - 1, last; + $num += length($buff); + } + if ($num) { + $err = 'First ' . ConvertFileSize($num) . ' of file is'; + } else { + $err = 'Entire file is'; + } + } + if ($ch eq "\0") { + $err .= ' binary zeros'; + } elsif ($ch eq ' ') { + $err .= ' ASCII spaces'; + } elsif ($ch =~ /[a-zA-Z0-9]/) { + $err .= " ASCII '${ch}' characters"; + } else { + $err .= sprintf(" binary 0x%.2x's", ord $ch); + } + } + } + $self->Error($err); + } + if ($seekErr) { + $self->Error('Error seeking in file'); + } elsif ($self->Options('ScanForXMP') and (not defined $type or + (not $fast and not $$self{FoundXMP}))) + { + # scan for XMP + $raf->Seek($pos, 0); + require Image::ExifTool::XMP; + Image::ExifTool::XMP::ScanForXMP($self, $raf) and $type = ''; + } + # extract binary EXIF data block only if requested + if (defined $$self{EXIF_DATA} and length $$self{EXIF_DATA} > 16 and + ($$req{exif} or + # (not extracted normally, so check TAGS_FROM_FILE) + ($$self{TAGS_FROM_FILE} and not $$self{EXCL_TAG_LOOKUP}{exif}))) + { + $self->FoundTag('EXIF', $$self{EXIF_DATA}); + } + unless ($reEntry) { + $$self{PATH} = [ ]; # reset PATH + $self->ExtractAltInfo(); + # do our HTML dump if requested + if ($$self{HTML_DUMP}) { + $raf->Seek(0, 2); # seek to end of file + $$self{HTML_DUMP}->FinishTiffDump($self, $raf->Tell()); + my $pos = $$options{HtmlDumpBase}; + $pos = ($$self{FIRST_EXIF_POS} || 0) unless defined $pos; + my $dataPt = defined $$self{EXIF_DATA} ? \$$self{EXIF_DATA} : undef; + undef $dataPt if defined $$self{EXIF_POS} and $pos != $$self{EXIF_POS}; + undef $dataPt if $$self{ExtendedEXIF}; # can't use EXIF block if not contiguous + my $success = $$self{HTML_DUMP}->Print($raf, $dataPt, $pos, + $$options{TextOut}, $$options{HtmlDump}, + $$self{FILENAME} ? "HTML Dump ($$self{FILENAME})" : 'HTML Dump'); + $self->Warn("Error reading $$self{HTML_DUMP}{ERROR}") if $success < 0; + } + } + if ($filename) { + $raf->Close(); # close the file if we opened it + # process the resource fork as an embedded file on Mac filesystems + if ($rsize and $$options{ExtractEmbedded}) { + local *RESOURCE_FILE; + if ($self->Open(\*RESOURCE_FILE, "$filename/..namedfork/rsrc")) { + $$self{DOC_NUM} = $$self{DOC_COUNT} + 1; + $$self{IN_RESOURCE} = 1; + $self->ExtractInfo(\*RESOURCE_FILE, { ReEntry => 1 }); + close RESOURCE_FILE; + delete $$self{IN_RESOURCE}; + } else { + $self->Warn('Error opening resource fork'); + } + } + } + last; # (loop was a cheap "goto") + } + + # generate Validate tag if requested + if ($$options{Validate} and not $reEntry) { + Image::ExifTool::Validate::FinishValidate($self, $$req{validate}); + } + + @startTime and $self->FoundTag('ProcessingTime', Time::HiRes::tv_interval(\@startTime)); + + # add user-defined parameters that ended with '!' + if (%{$$options{UserParam}}) { + my $doMsg = $$options{Verbose}; + my $table = GetTagTable('Image::ExifTool::UserParam'); + foreach (sort keys %{$$options{UserParam}}) { + next unless /#$/; + if ($doMsg) { + $self->VPrint(0, "UserParam tags:\n"); + undef $doMsg; + } + $self->HandleTag($table, $_, $$options{UserParam}{$_}); + } + } + + # restore original options + %saveOptions and $$self{OPTIONS} = \%saveOptions; + + if ($reEntry) { + # restore necessary members when exiting re-entrant code + $$self{$_} = $$reEntry{$_} foreach keys %$reEntry; + SetByteOrder($saveOrder); + } elsif ($$self{ImageDataHash}) { + my $digest = $$self{ImageDataHash}->hexdigest; + # (don't store empty digest) + $self->FoundTag(ImageDataHash => $digest) unless + $digest eq 'd41d8cd98f00b204e9800998ecf8427e' or + $digest eq 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' or + $digest eq 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'; + } + + # ($type may be undef without an Error when processing sub-documents) + return 0 if not defined $type or exists $$self{VALUE}{Error}; + return 1; +} + +#------------------------------------------------------------------------------ +# Get hash of extracted meta information +# Inputs: 0) ExifTool object reference +# 1-N) options hash reference, tag list reference or tag names +# Returns: Reference to information hash +# Notes: - pass an undefined value to avoid parsing arguments +# - If groups are specified, first groups take precedence if duplicate +# tags found but Duplicates option not set. +# - tag names may end in '#' to extract ValueConv value +sub GetInfo($;@) +{ + local $_; + my $self = shift; + my %saveOptions; + + unless (@_ and not defined $_[0]) { + %saveOptions = %{$$self{OPTIONS}}; # save original options + # must set FILENAME so it isn't parsed from the arguments + $$self{FILENAME} = '' unless defined $$self{FILENAME}; + $self->ParseArguments(@_); + } + + # get reference to list of tags for which we will return info + my ($rtnTags, $byValue, $wildTags) = $self->SetFoundTags(); + + # build hash of tag information + my (%info, %ignored); + my $conv = $$self{OPTIONS}{PrintConv} ? 'PrintConv' : 'ValueConv'; + foreach (@$rtnTags) { + my $val = $self->GetValue($_, $conv); + defined $val or $ignored{$_} = 1, next; + $info{$_} = $val; + } + + # override specified tags with ValueConv value if necessary + if (@$byValue) { + # first determine the number of times each non-ValueConv value is used + my %nonVal; + $nonVal{$_} = ($nonVal{$_} || 0) + 1 foreach @$rtnTags; + --$nonVal{$$rtnTags[$_]} foreach @$byValue; + # loop through ValueConv tags, updating tag keys and returned values + foreach (@$byValue) { + my $tag = $$rtnTags[$_]; + my $val = $self->GetValue($tag, 'ValueConv'); + next unless defined $val; + my $vtag = $tag; + # generate a new tag key like "Tag #" or "Tag #(1)" + $vtag =~ s/( |$)/ #/; + unless (defined $$self{VALUE}{$vtag}) { + $$self{VALUE}{$vtag} = $$self{VALUE}{$tag}; + $$self{TAG_INFO}{$vtag} = $$self{TAG_INFO}{$tag}; + $$self{TAG_EXTRA}{$vtag} = $$self{TAG_EXTRA}{$tag}; + $$self{FILE_ORDER}{$vtag} = $$self{FILE_ORDER}{$tag}; + # remove existing PrintConv entry unless we are using it too + delete $info{$tag} unless $nonVal{$tag}; + } + $$rtnTags[$_] = $vtag; # store ValueConv value with new tag key + $info{$vtag} = $val; # return ValueConv value + } + } + + # remove ignored tags from the list + my $reqTags = $$self{REQUESTED_TAGS} || [ ]; + if (%ignored) { + if (not @$reqTags) { + my @goodTags; + foreach (@$rtnTags) { + push @goodTags, $_ unless $ignored{$_}; + } + $rtnTags = $$self{FOUND_TAGS} = \@goodTags; + } elsif (@$wildTags) { + # only remove tags specified by wildcard + my @goodTags; + my $i = 0; + foreach (@$rtnTags) { + if (@$wildTags and $i == $$wildTags[0]) { + shift @$wildTags; + push @goodTags, $_ unless $ignored{$_}; + } else { + push @goodTags, $_; + } + ++$i; + } + $rtnTags = $$self{FOUND_TAGS} = \@goodTags; + } + } + + # return sorted tag list if provided with a list reference + if ($$self{IO_TAG_LIST}) { + # use file order by default if no tags specified + # (no such thing as 'Input' order in this case) + my $sort = $$self{OPTIONS}{Sort}; + $sort = 'File' unless @$reqTags or ($sort and $sort ne 'Input'); + # return tags in specified sort order + @{$$self{IO_TAG_LIST}} = $self->GetTagList($rtnTags, $sort, $$self{OPTIONS}{Sort2}); + } + + # restore original options + %saveOptions and $$self{OPTIONS} = \%saveOptions; + + return \%info; +} + +#------------------------------------------------------------------------------ +# Inputs: 0) ExifTool object reference +# 1) [optional] reference to info hash or tag list ref (default is found tags) +# 2) [optional] sort order ('File', 'Input', ...) +# 3) [optional] secondary sort order +# Returns: List of tags in specified order +sub GetTagList($;$$$) +{ + local $_; + my ($self, $info, $sort, $sort2) = @_; + + my $foundTags; + if (ref $info eq 'HASH') { + my @tags = keys %$info; + $foundTags = \@tags; + } elsif (ref $info eq 'ARRAY') { + $foundTags = $info; + } + my $fileOrder = $$self{FILE_ORDER}; + + if ($foundTags) { + # make sure a FILE_ORDER entry exists for all tags + # (note: already generated bogus entries for FOUND_TAGS case below) + foreach (@$foundTags) { + next if defined $$fileOrder{$_}; + $$fileOrder{$_} = 999; + } + } else { + $sort = $info if $info and not $sort; + $foundTags = $$self{FOUND_TAGS} || $self->SetFoundTags() or return undef; + } + $sort or $sort = $$self{OPTIONS}{Sort}; + + # return original list if no sort order specified + return @$foundTags unless $sort and $sort ne 'Input'; + + if ($sort eq 'Tag' or $sort eq 'Alpha') { + return sort @$foundTags; + } elsif ($sort =~ /^Group(\d*(:\d+)*)/) { + my $family = $1 || 0; + # want to maintain a basic file order with the groups + # ordered in the way they appear in the file + my (%groupCount, %groupOrder); + my $numGroups = 0; + my $tag; + foreach $tag (sort { $$fileOrder{$a} <=> $$fileOrder{$b} } @$foundTags) { + my $group = $self->GetGroup($tag, $family); + my $num = $groupCount{$group}; + $num or $num = $groupCount{$group} = ++$numGroups; + $groupOrder{$tag} = $num; + } + $sort2 or $sort2 = $$self{OPTIONS}{Sort2}; + if ($sort2) { + if ($sort2 eq 'Tag' or $sort2 eq 'Alpha') { + return sort { $groupOrder{$a} <=> $groupOrder{$b} or $a cmp $b } @$foundTags; + } elsif ($sort2 eq 'Descr') { + my $desc = $self->GetDescriptions($foundTags); + return sort { $groupOrder{$a} <=> $groupOrder{$b} or + $$desc{$a} cmp $$desc{$b} } @$foundTags; + } + } + return sort { $groupOrder{$a} <=> $groupOrder{$b} or + $$fileOrder{$a} <=> $$fileOrder{$b} } @$foundTags; + } elsif ($sort eq 'Descr') { + my $desc = $self->GetDescriptions($foundTags); + return sort { $$desc{$a} cmp $$desc{$b} } @$foundTags; + } else { + return sort { $$fileOrder{$a} <=> $$fileOrder{$b} } @$foundTags; + } +} + +#------------------------------------------------------------------------------ +# Get list of found tags in specified sort order +# Inputs: 0) ExifTool object reference, 1) sort order ('File', 'Input', ...) +# 2) secondary sort order +# Returns: List of tag keys in specified order +# Notes: If not specified, sort order is taken from OPTIONS +sub GetFoundTags($;$$) +{ + local $_; + my ($self, $sort, $sort2) = @_; + my $foundTags = $$self{FOUND_TAGS} || $self->SetFoundTags() or return undef; + return $self->GetTagList($foundTags, $sort, $sort2); +} + +#------------------------------------------------------------------------------ +# Get list of requested tags +# Inputs: 0) ExifTool object reference +# Returns: List of requested tag keys +sub GetRequestedTags($) +{ + local $_; + return @{$_[0]{REQUESTED_TAGS}}; +} + +#------------------------------------------------------------------------------ +# Get tag value +# Inputs: 0) ExifTool object reference +# 1) tag key or tag name with optional group names (case sensitive) +# (or flattened tagInfo for getting field values, not part of public API) +# 2) [optional] Value type: PrintConv, ValueConv, Both, Raw or Rational, the default +# is PrintConv or ValueConv, depending on the PrintConv option setting +# 3) raw field value (not part of public API) +# Returns: Scalar context: tag value or undefined +# List context: list of values or empty list +sub GetValue($$;$) +{ + local $_; + my ($self, $tag, $type) = @_; # plus: ($fieldValue) + my (@convTypes, $tagInfo, $valueConv, $both); + my $rawValue = $$self{VALUE}; + + # get specific tag key if tag has a group name + if ($tag =~ /^(.*):(.+)/) { + my ($gp, $tg) = ($1, $2); + my ($i, $key, @keys); + # build list of tag keys in the order of priority (no index + # is top priority, otherwise higher index is higher priority) + for ($key=$tg, $i=$$self{DUPL_TAG}{$tg} || 0; ; --$i) { + push @keys, $key if defined $$rawValue{$key}; + last if $i <= 0; + $key = "$tg ($i)"; + } + if (@keys) { + $key = $self->GroupMatches($gp, \@keys); + $tag = $key if $key; + } + } + # figure out what conversions to do + if ($type) { + return $$self{RATIONAL}{$tag} if $type eq 'Rational'; + } else { + $type = $$self{OPTIONS}{PrintConv} ? 'PrintConv' : 'ValueConv'; + } + + # start with the raw value + my $value = $$rawValue{$tag}; + if (not defined $value) { + return () unless ref $tag; + # get the value of a structure field + $tagInfo = $tag; + $tag = $$tagInfo{Name}; + $value = $_[3]; + # (note: type "Both" is not allowed for structure fields) + if ($type ne 'Raw') { + push @convTypes, 'ValueConv'; + push @convTypes, 'PrintConv' unless $type eq 'ValueConv'; + } + } else { + $tagInfo = $$self{TAG_INFO}{$tag}; + if ($$tagInfo{Struct} and ref $value) { + # must load XMPStruct.pl just in case (should already be loaded if + # a structure was extracted, but we could also arrive here if a simple + # list of values was stored incorrectly in a Struct tag) + require 'Image/ExifTool/XMPStruct.pl'; + # convert strucure field values + unless ($type eq 'Both') { + # (note: ConvertStruct handles the filtering and escaping too if necessary) + return Image::ExifTool::XMP::ConvertStruct($self,$tagInfo,$value,$type); + } + $valueConv = Image::ExifTool::XMP::ConvertStruct($self,$tagInfo,$value,'ValueConv'); + $value = Image::ExifTool::XMP::ConvertStruct($self,$tagInfo,$value,'PrintConv'); + # (must not save these in $$self{BOTH} because the values may have been escaped) + return ($valueConv, $value); + } + if ($type ne 'Raw') { + # use values we calculated already if we stored them + $both = $$self{BOTH}{$tag}; + if ($both) { + if ($type eq 'PrintConv') { + $value = $$both[1]; + } elsif ($type eq 'ValueConv') { + $value = $$both[0]; + $value = $$both[1] unless defined $value; + } else { + ($valueConv, $value) = @$both; + } + } else { + push @convTypes, 'ValueConv'; + push @convTypes, 'PrintConv' unless $type eq 'ValueConv'; + } + } + } + + # do the conversions + my (@val, @prt, @raw, $convType); + foreach $convType (@convTypes) { + # don't convert a scalar reference or structure + last if ref $value eq 'SCALAR' and not $$tagInfo{ConvertBinary}; + my $conv = $$tagInfo{$convType}; + unless (defined $conv) { + if ($convType eq 'ValueConv') { + next unless $$tagInfo{Binary}; + $conv = '\$val'; # return scalar reference for binary values + } else { + # use PRINT_CONV from tag table if PrintConv doesn't exist + next unless defined($conv = $$tagInfo{Table}{PRINT_CONV}); + next if exists $$tagInfo{$convType}; + } + } + # save old ValueConv value if we want Both + $valueConv = $value if $type eq 'Both' and $convType eq 'PrintConv'; + my ($i, $val, $vals, @values, $convList); + # split into list if conversion is an array + if (ref $conv eq 'ARRAY') { + $convList = $conv; + $conv = $$convList[0]; + my @valList = (ref $value eq 'ARRAY') ? @$value : split ' ', $value; + # reorganize list if specified (Note: The writer currently doesn't + # relist values, so they may be grouped but the order must not change) + my $relist = $$tagInfo{Relist}; + if ($relist) { + my (@newList, $oldIndex); + foreach $oldIndex (@$relist) { + my ($newVal, @join); + if (ref $oldIndex) { + foreach (@$oldIndex) { + push @join, $valList[$_] if defined $valList[$_]; + } + $newVal = join(' ', @join) if @join; + } else { + $newVal = $valList[$oldIndex]; + } + push @newList, $newVal if defined $newVal; + } + $value = \@newList; + } else { + $value = \@valList; + } + return () unless @$value; + } + # initialize array so we can iterate over values in list + if (ref $value eq 'ARRAY') { + if (defined $$tagInfo{RawJoin}) { + $val = join ' ', @$value; + } else { + $i = 0; + $vals = $value; + $val = $$vals[0]; + } + } else { + $val = $value; + } + # loop through all values in list + for (;;) { + if (defined $conv) { + # get values of required tags if this is a Composite tag + if (ref $val eq 'HASH' and not @val) { + # disable escape of source values so we don't double escape them + my $oldEscape = $$self{ESCAPE_PROC}; + delete $$self{ESCAPE_PROC}; + # temporarily delete filter so it isn't applied to the Require'd values + my $oldFilter = $$self{OPTIONS}{Filter}; + delete $$self{OPTIONS}{Filter}; + foreach (keys %$val) { + next unless defined $$val{$_}; + $raw[$_] = $$rawValue{$$val{$_}}; + ($val[$_], $prt[$_]) = $self->GetValue($$val{$_}, 'Both'); + next if defined $val[$_] or not $$tagInfo{Require}{$_}; + $$self{OPTIONS}{Filter} = $oldFilter if defined $oldFilter; + $$self{ESCAPE_PROC} = $oldEscape; + return (); + } + $$self{OPTIONS}{Filter} = $oldFilter if defined $oldFilter; + $$self{ESCAPE_PROC} = $oldEscape; + # set $val to $val[0], or \@val for a CODE ref conversion + $val = ref $conv eq 'CODE' ? \@val : $val[0]; + } + if (ref $conv eq 'HASH') { + # look up converted value in hash + if (not defined($value = $$conv{$val})) { + if ($$conv{BITMASK}) { + $value = DecodeBits($val, $$conv{BITMASK}, $$tagInfo{BitsPerWord}); + } else { + # use alternate conversion routine if available + if ($$conv{OTHER}) { + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + $value = &{$$conv{OTHER}}($val, undef, $conv); + $self->Warn("$convType $tag: " . CleanWarning()) if $evalWarning; + } + if (not defined $value) { + if ($$tagInfo{PrintHex} and $val and IsInt($val) and + $convType eq 'PrintConv') + { + $value = sprintf('Unknown (0x%x)',$val); + } else { + $value = "Unknown ($val)"; + } + } + } + } + # override with our localized language PrintConv if available + my $tmp; + if ($$self{CUR_LANG} and $convType eq 'PrintConv' and + # (no need to check for lang-alt tag names -- they won't have a PrintConv) + ref($tmp = $$self{CUR_LANG}{$$tagInfo{Name}}) eq 'HASH' and + ($tmp = $$tmp{PrintConv})) + { + if ($$conv{BITMASK} and not defined $$conv{$val}) { + my @vals = split ', ', $value; + foreach (@vals) { + $_ = $$tmp{$_} if defined $$tmp{$_}; + } + $value = join ', ', @vals; + } elsif (defined($tmp = $$tmp{$value})) { + $value = $self->Decode($tmp, 'UTF8'); + } + } + } else { + # call subroutine or do eval to convert value + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + if (ref $conv eq 'CODE') { + $value = &$conv($val, $self); + } else { + #### eval ValueConv/PrintConv ($val, $self, @val, @prt, @raw) + $value = eval $conv; + $@ and $evalWarning = $@; + } + $self->Warn("$convType $tag: " . CleanWarning()) if $evalWarning; + } + } else { + $value = $val; + } + last unless $vals; + # must store a separate copy of each binary data value in the list + if (ref $value eq 'SCALAR') { + my $tval = $$value; + $value = \$tval; + } + # save this converted value and step to next value in list + push @values, $value if defined $value; + if (++$i >= scalar(@$vals)) { + $value = \@values if @values; + last; + } + $val = $$vals[$i]; + if ($convList) { + my $nextConv = $$convList[$i]; + if ($nextConv and $nextConv eq 'REPEAT') { + undef $convList; + } else { + $conv = $nextConv; + } + } + } + # return undefined now if no value + return () unless defined $value; + # join back into single value if split for conversion list + if ($convList and ref $value eq 'ARRAY') { + $value = join($convType eq 'PrintConv' ? '; ' : ' ', @$value); + } + } + if ($type eq 'Both') { + # save both (unescaped) values because we often need them again + # (Composite tags need "Both" and often Require one tag for various Composite tags) + $$self{BOTH}{$tag} = [ $valueConv, $value ] unless $both; + # escape values if necessary + if ($$self{ESCAPE_PROC}) { + DoEscape($value, $$self{ESCAPE_PROC}); + if (defined $valueConv) { + DoEscape($valueConv, $$self{ESCAPE_PROC}); + } else { + $valueConv = $value; + } + } elsif (not defined $valueConv) { + # $valueConv is undefined if there was no print conversion done + $valueConv = $value; + } + $self->Filter($$self{OPTIONS}{Filter}, \$value); + # return Both values as a list (ValueConv, PrintConv) + return ($valueConv, $value); + } + # escape value if necessary + DoEscape($value, $$self{ESCAPE_PROC}) if $$self{ESCAPE_PROC}; + + # filter if necessary + $self->Filter($$self{OPTIONS}{Filter}, \$value) if $$self{OPTIONS}{Filter} and $type eq 'PrintConv'; + + if (ref $value eq 'ARRAY') { + if (defined $$self{OPTIONS}{ListItem}) { + $value = $$value[$$self{OPTIONS}{ListItem}]; + } elsif (wantarray) { + # return array if requested + return @$value; + } elsif ($type eq 'PrintConv' and not $$self{OPTIONS}{List} and not ref $$value[0]) { + # join PrintConv values in comma-separated string if List option not used + # and list contains simple scalars (otherwise return ARRAY ref) + $value = join $$self{OPTIONS}{ListSep}, @$value; + } + } + return $value; +} + +#------------------------------------------------------------------------------ +# Get tag identification number +# Inputs: 0) ExifTool object reference, 1) tag key +# Returns: Scalar context: tag ID if available, otherwise '' +# List context: 0) tag ID (or ''), 1) language code (or undef) +sub GetTagID($$) +{ + my ($self, $tag) = @_; + my $tagInfo = $$self{TAG_INFO}{$tag}; + return '' unless $tagInfo and defined $$tagInfo{TagID}; + my $id = $$tagInfo{KeysID} || $$tagInfo{TagID}; + return ($id, $$tagInfo{LangCode}) if wantarray; + return $id; +} + +#------------------------------------------------------------------------------ +# Get description for specified tag +# Inputs: 0) ExifTool object reference, 1) tag key +# Returns: Tag description +# Notes: Will always return a defined value, even if description isn't available +sub GetDescription($$) +{ + local $_; + my ($self, $tag) = @_; + my ($desc, $name); + my $tagInfo = $$self{TAG_INFO}{$tag}; + # ($tagInfo won't be defined for missing tags extracted with -f) + if ($tagInfo) { + # use alternate language description if available + while ($$self{CUR_LANG}) { + $desc = $$self{CUR_LANG}{$$tagInfo{Name}}; + if ($desc) { + # must look up Description if this tag also has a PrintConv + $desc = $$desc{Description} or last if ref $desc; + } else { + # look up default language of lang-alt tag + last unless $$tagInfo{LangCode} and + ($name = $$tagInfo{Name}) =~ s/-$$tagInfo{LangCode}$// and + $desc = $$self{CUR_LANG}{$name}; + $desc = $$desc{Description} or last if ref $desc; + $desc .= " ($$tagInfo{LangCode})"; + } + # escape description if necessary + DoEscape($desc, $$self{ESCAPE_PROC}) if $$self{ESCAPE_PROC}; + # return description in proper Charset + return $self->Decode($desc, 'UTF8'); + } + $desc = $$tagInfo{Description}; + } + # just make the tag more readable if description doesn't exist + unless ($desc) { + $desc = MakeDescription(GetTagName($tag)); + # save description in tag information + $$tagInfo{Description} = $desc if $tagInfo; + } + return $desc; +} + +#------------------------------------------------------------------------------ +# Get group name for specified tag +# Inputs: 0) ExifTool object reference +# 1) tag key (or reference to tagInfo hash, not part of the public API) +# 2) [optional] group family (-1 to get extended group list, or multiple +# families separated by colons to return multiple groups as a string) +# Returns: Scalar context: group name (for family 0 if not otherwise specified) +# List context: group name if family specified, otherwise list of +# group names for each family. Returns '' for undefined tag. +# Notes: Multiple families may be specified with ':' in family argument (eg. '1:2') +sub GetGroup($$;$) +{ + local $_; + my ($self, $tag, $family) = @_; + my ($tagInfo, @groups, @families, $simplify, $byTagInfo, $ex, $noID); + if (ref $tag eq 'HASH') { + $tagInfo = $tag; + $tag = $$tagInfo{Name}; + # set flag so we don't get extra information for an extracted tag + $byTagInfo = 1; + } else { + $tagInfo = $$self{TAG_INFO}{$tag} || { }; + $ex = $$self{TAG_EXTRA}{$tag}; + } + my $groups = $$tagInfo{Groups}; + # fill in default groups unless already done + # (after this, Groups 0-2 in tagInfo are guaranteed to be defined) + unless ($$tagInfo{GotGroups}) { + my $tagTablePtr = $$tagInfo{Table} || { GROUPS => { } }; + # construct our group list + $groups or $groups = $$tagInfo{Groups} = { }; + # fill in default groups + foreach (0..2) { + $$groups{$_} = $$tagTablePtr{GROUPS}{$_} || '' unless $$groups{$_}; + } + # set flag indicating group list was built + $$tagInfo{GotGroups} = 1; + } + if (defined $family and $family ne '-1') { + if ($family =~ /[^\d]/) { + @families = ($family =~ /\d+/g); + return(($ex && $$ex{G0}) || $$groups{0}) unless @families; + $simplify = 1 unless $family =~ /^:/; + undef $family; + foreach (0..2) { $groups[$_] = $$groups{$_}; } + $noID = 1 if @families == 1 and $families[0] != 7; + } else { + return(($ex && $$ex{"G$family"}) || $$groups{$family}) if $family == 0 or $family == 2; + $groups[1] = $$groups{1}; + } + } else { + return(($ex && $$ex{G0}) || $$groups{0}) unless wantarray; + foreach (0..2) { $groups[$_] = $$groups{$_}; } + } + $groups[3] = 'Main'; + $groups[4] = ($tag =~ /\((\d+)\)$/) ? "Copy$1" : ''; + # handle dynamic group names if necessary + unless ($byTagInfo) { + if ($ex) { + $groups[0] = $$ex{G0} if $$ex{G0}; + $groups[1] = $$ex{G1} =~ /^\+(.*)/ ? "$groups[1]$1" : $$ex{G1} if $$ex{G1}; + $groups[3] = 'Doc' . $$ex{G3} if $$ex{G3}; + $groups[5] = $$ex{G5} || $groups[1] if defined $$ex{G5}; + if (defined $$ex{G6}) { + $groups[5] = '' unless defined $groups[5]; # (can't leave a hole in the array) + $groups[6] = $$ex{G6}; + } + } + if ($$ex{G8}) { + $groups[7] = ''; + $groups[8] = $$ex{G8}; + } + # generate tag ID group names unless obviously not needed + unless ($noID) { + my $id = $$tagInfo{KeysID} || $$tagInfo{TagID}; + if (not defined $id) { + $id = ''; # (just to be safe) + } elsif ($id =~ /^\d+$/) { + $id = sprintf('0x%x', $id) if $$self{OPTIONS}{HexTagIDs}; + } else { + $id =~ s/([^-_A-Za-z0-9])/sprintf('%.2x',ord $1)/ge; + } + $groups[7] = 'ID-' . $id; + defined $groups[$_] or $groups[$_] = '' foreach (5,6); + } + } + if ($family) { + return $groups[$family] || '' if $family > 0; + # add additional matching group names to list + # eg) for MIE-Doc, also add MIE1, MIE1-Doc, MIE-Doc1 and MIE1-Doc1 + # and for MIE2-Doc3, also add MIE2, MIE-Doc3, MIE2-Doc and MIE-Doc + if ($groups[1] =~ /^MIE(\d*)-(.+?)(\d*)$/) { + push @groups, 'MIE' . ($1 || '1'); + push @groups, 'MIE' . ($1 ? '' : '1') . "-$2$3"; + push @groups, "MIE$1-$2" . ($3 ? '' : '1'); + push @groups, 'MIE' . ($1 ? '' : '1') . "-$2" . ($3 ? '' : '1'); + } + } + if (@families) { + my @grps; + # create list of group names (without identical adjacent groups if simplifying) + foreach (@families) { + my $grp = $groups[$_]; + unless ($grp) { + next if $simplify; + $grp = ''; + } + push @grps, $grp unless $simplify and @grps and $grp eq $grps[-1]; + } + # remove leading "Main:" if simplifying + shift @grps if $simplify and @grps > 1 and $grps[0] eq 'Main'; + # return colon-separated string of group names + return join ':', @grps; + } + return @groups; +} + +#------------------------------------------------------------------------------ +# Get group names for specified tags +# Inputs: 0) ExifTool object reference +# 1) [optional] information hash reference (default all extracted info) +# 2) [optional] group family (default 0) +# Returns: List of group names in alphabetical order +sub GetGroups($;$$) +{ + local $_; + my $self = shift; + my $info = shift; + my $family; + + # figure out our arguments + if (ref $info ne 'HASH') { + $family = $info; + $info = $$self{VALUE}; + } else { + $family = shift; + } + $family = 0 unless defined $family; + + # get a list of all groups in specified information + my ($tag, %groups); + foreach $tag (keys %$info) { + $groups{ $self->GetGroup($tag, $family) } = 1; + } + return sort keys %groups; +} + +#------------------------------------------------------------------------------ +# Set priority for group where new values are written +# Inputs: 0) ExifTool object reference, +# 1-N) group names (reset to default if no groups specified) +# - used when new tag values are set (ie. before files are written) +sub SetNewGroups($;@) +{ + local $_; + my ($self, @groups) = @_; + @groups or @groups = @defaultWriteGroups; + my $count = @groups * 10; + my %priority; + foreach (@groups) { + $priority{lc($_)} = $count; + $count -= 10; + } + $priority{file} = 500; # 'File' group is always written (Comment) + $priority{composite} = 500; # 'Composite' group is always written + # set write priority (higher # is higher priority) + $$self{WRITE_PRIORITY} = \%priority; + $$self{WRITE_GROUPS} = \@groups; +} + +#------------------------------------------------------------------------------ +# Build Composite tags from Require'd/Desire'd tags +# Inputs: 0) ExifTool object reference, 1) flag to build only tags that require +# tags from alternate files (without this, these tags are ignored) +# Note: Tag values are calculated in alphabetical order unless a tag Require's +# or Desire's another Composite tag, in which case the calculation is +# deferred until after the other tag is calculated. +sub BuildCompositeTags($) +{ + local $_; + my ($self, $altOnly) = @_; + + $$self{BuildingComposite} = 1; + + my $compTable = GetTagTable('Image::ExifTool::Composite'); + my @tagList = sort keys %$compTable; + my $rawValue = $$self{VALUE}; + my $compKeys = $$self{COMP_KEYS}; + my (%cache, $allBuilt); + + for (;;) { + my (%notBuilt, $tag, @deferredTags); + foreach (@tagList) { + $notBuilt{$$compTable{$_}{Name}} = 1 unless $specialTags{$_}; + } +COMPOSITE_TAG: + foreach $tag (@tagList) { + next if $specialTags{$tag}; + my $tagInfo = $self->GetTagInfo($compTable, $tag); + next unless $tagInfo; + my $tagName = $$compTable{$tag}{Name}; + # put required tags into array and make sure they all exist + my $subDoc = ($$tagInfo{SubDoc} and $$self{DOC_COUNT}); + my $require = $$tagInfo{Require} || { }; + my $desire = $$tagInfo{Desire} || { }; + my $inhibit = $$tagInfo{Inhibit} || { }; + # loop through sub-documents if necessary + my $docNum = 0; + for (;;) { + my (%tagKey, $found, $index, $requireAlt); + # save Require'd and Desire'd tag values in list + for ($index=0; ; ++$index) { + my $reqTag = $$require{$index} || $$desire{$index} || $$inhibit{$index}; + unless ($reqTag) { + # allow Composite with no Require'd or Desire'd tags + $found = 1 if $index == 0; + last; + } + if ($subDoc) { + # handle SubDoc tags specially to cache tag keys for faster + # processing when there are a large number of sub-documents + # - get document number from the tag groups if specified, + # otherwise we are looping through all documents for this tag + my $doc = $reqTag =~ s/\b(Main|Doc(\d+)):// ? ($2 || 0) : $docNum; + # make fast lookup for keys of this tag with specified groups other than doc group + # (similar to code in InsertTagValues(), but this is case-sensitive) + my $cacheTag = $cache{$reqTag}; + unless ($cacheTag) { + $cacheTag = $cache{$reqTag} = [ ]; + my $reqGroup; + $reqTag =~ s/^(.*):// and $reqGroup = $1; + my ($i, $key, @keys); + # build list of tag keys in order of precedence + for ($key=$reqTag, $i=$$self{DUPL_TAG}{$reqTag} || 0; ; --$i) { + push @keys, $key if defined $$rawValue{$key}; + last if $i <= 0; + $key = "$reqTag ($i)"; + } + @keys = $self->GroupMatches($reqGroup, \@keys) if defined $reqGroup; + if (@keys) { + my $ex = $$self{TAG_EXTRA}; + # loop through tags in reverse order of precedence so the higher + # priority tag will win in the case of duplicates within a doc + $$cacheTag[$$ex{$_} ? $$ex{$_}{G3} || 0 : 0] = $_ foreach reverse @keys; + } + } + # (set $reqTag to a bogus key if not found) + $reqTag = $$cacheTag[$doc] || "$reqTag (0)"; + } elsif ($reqTag =~ /^(.*):(.+)/) { + my ($reqGroup, $name) = ($1, $2); + if ($reqGroup eq 'Composite' and $notBuilt{$name}) { + # defer only until all other tags are built if + # we are inhibiting based on another Composite tag + unless ($$inhibit{$index} and $allBuilt) { + push @deferredTags, $tag; + next COMPOSITE_TAG; + } + } + my ($i, $key, @keys, $altFile); + my $et = $self; + # get tags from alternate file if a family 8 group was specified + if ($reqTag =~ /\b(File\d+):/i and $$self{ALT_EXIFTOOL}{$1}) { + $et = $$self{ALT_EXIFTOOL}{$1}; + $altFile = $1; + # set flags indicating we require tags from alternate files + $$self{DoAltComposite} = $requireAlt = 1; + } + # (CAREFUL! keys may not be sequential if one was deleted) + for ($key=$name, $i=$$et{DUPL_TAG}{$name} || 0; ; --$i) { + push @keys, $key if defined $$et{VALUE}{$key}; + last if $i <= 0; + $key = "$name ($i)"; + } + # make sure the necessary information is available from the alternate file + $self->CopyAltInfo($altFile, \@keys) if $altFile; + # find first matching tag + $key = $self->GroupMatches($reqGroup, \@keys); + $reqTag = $key || "$name (0)"; + } elsif ($notBuilt{$reqTag} and not $$inhibit{$index}) { + # calculate this tag later if it relies on another + # Composite tag which hasn't been calculated yet + push @deferredTags, $tag; + next COMPOSITE_TAG; + } + if (defined $$rawValue{$reqTag}) { + if ($$inhibit{$index}) { + $found = 0; + last; + } else { + $found = 1; + } + } elsif ($$require{$index}) { + $found = 0; + last; # don't continue since we require this tag + } + $tagKey{$index} = $reqTag; + } + # stop now if this requires alternate tags and we aren't building them + last if $requireAlt xor $altOnly; + if ($docNum) { + if ($found) { + $$self{DOC_NUM} = $docNum; + # save pointers to all used tag keys + foreach (keys %tagKey) { + $$compKeys{$_} or $$compKeys{$_} = [ ]; + push @{$$compKeys{$tagKey{$_}}}, [ \%tagKey, $_ ]; + } + $self->FoundTag($tagInfo, \%tagKey); + delete $$self{DOC_NUM}; + } + next if ++$docNum <= $$self{DOC_COUNT}; + last; + } elsif ($found) { + delete $notBuilt{$tagName}; # this tag is OK to build now + # keep track of all Require'd tag keys + foreach (keys %tagKey) { + # only tag keys with same name as a Composite tag + # can be replaced (also eliminates keys with + # instance numbers which can't be replaced either) + next unless $compositeID{$tagKey{$_}}; + } + # save pointers to all used tag keys + foreach (keys %tagKey) { + $$compKeys{$_} or $$compKeys{$_} = [ ]; + push @{$$compKeys{$tagKey{$_}}}, [ \%tagKey, $_ ]; + } + # save reference to tag key lookup as value for Composite tag + my $key = $self->FoundTag($tagInfo, \%tagKey); + } elsif (not defined $found) { + delete $notBuilt{$tagName}; # tag can't be built anyway + } + last unless $subDoc; + # don't process sub-documents if there is no chance to build this tag + # (can be very time-consuming if there are many docs) + if (%$require) { + foreach (keys %$require) { + my $reqTag = $$require{$_}; + $reqTag =~ s/.*://; + next COMPOSITE_TAG unless defined $$rawValue{$reqTag}; + } + $docNum = 1; # go ahead and process the 1st sub-document + } else { + my @try = ref $$tagInfo{SubDoc} ? @{$$tagInfo{SubDoc}} : keys %$desire; + # at least one of the specified desire tags must exist + foreach (@try) { + my $desTag = $$desire{$_} or next; + $desTag =~ s/.*://; + defined $$rawValue{$desTag} and $docNum = 1, last; + } + last unless $docNum; + } + } + } + last unless @deferredTags; + if (@deferredTags == @tagList) { + if ($allBuilt) { + # everything was deferred in the last pass, + # must be a circular dependency + warn "Circular dependency in Composite tags\n"; + last; + } + $allBuilt = 1; # try once more, ignoring Composite Inhibit tags + } + @tagList = @deferredTags; # calculate deferred tags now + } + delete $$self{BuildingComposite}; +} + +#------------------------------------------------------------------------------ +# Get reference to Composite tag info hash +# Inputs: 0) case-sensitive Composite tag name +# Returns: tagInfo hash or undef +sub GetCompositeTagInfo($) +{ + my $tag = shift; + return undef unless $compositeID{$tag}; + return $Image::ExifTool::Composite{$compositeID{$tag}[0]}; +} + +#------------------------------------------------------------------------------ +# Return List ExifTool API options +# Returns: 0) reference to list of available options -- each entry is a list +# [0=option name, 1=default value, 2=description] +sub AvailableOptions() +{ + return \@availableOptions; +} + +#------------------------------------------------------------------------------ +# Get tag name (removes copy index) +# Inputs: 0) Tag key +# Returns: Tag name +sub GetTagName($) +{ + local $_; + $_[0] =~ /^(\S+)/; + return $1; +} + +#------------------------------------------------------------------------------ +# Get list of shortcuts +# Returns: Shortcut list (sorted alphabetically) +sub GetShortcuts() +{ + local $_; + require Image::ExifTool::Shortcuts; + return sort keys %Image::ExifTool::Shortcuts::Main; +} + +#------------------------------------------------------------------------------ +# Get file type for specified extension +# Inputs: 0) file name or extension (case is not significant), +# or FileType value if a description is requested +# 1) flag to return long description instead of type ('0' to return any recognized type) +# Returns: File type (or desc) or undef if extension not supported or if +# description is the same as the input FileType. In list context, +# may return more than one file type if the file may be different formats. +# Returns list of all supported extensions if no file specified +sub GetFileType(;$$) +{ + local $_; + my ($file, $desc) = @_; + unless (defined $file) { + my @types; + if (defined $desc and $desc eq '0') { + # return all recognized types + @types = sort keys %fileTypeLookup; + } else { + # return all supported types + foreach (sort keys %fileTypeLookup) { + my $module = $moduleName{$_}; + $module = $moduleName{$fileTypeLookup{$_}} unless defined $module; + push @types, $_ unless defined $module and $module eq '0'; + } + } + return @types; + } + my ($fileType, $subType); + my $fileExt = GetFileExtension($file); + unless ($fileExt) { + if ($file =~ s/ \((.*)\)$//) { + $subType = $1; + $fileExt = GetFileExtension($file); + } + $fileExt = uc($file) unless $fileExt; + } + $fileExt and $fileType = $fileTypeLookup{$fileExt}; # look up the file type + $fileType = $fileTypeLookup{$fileType} while $fileType and not ref $fileType; + # return description if specified + # (allow input $file to be a FileType for this purpose) + if ($desc) { + if ($fileType) { + if ($static_vars{OverrideFileDescription} and $static_vars{OverrideFileDescription}{$fileExt}) { + $desc = $static_vars{OverrideFileDescription}{$fileExt}; + } else { + $desc = $$fileType[1]; + } + } else { + $desc = $fileDescription{$file}; + } + $desc .= ", $subType" if $subType; + return $desc; + } elsif ($fileType and (not defined $desc or $desc ne '0')) { + # return only supported file types + my $mod = $moduleName{$$fileType[0]}; + undef $fileType if defined $mod and $mod eq '0'; + } + $fileType or return (); + $fileType = $$fileType[0]; # get file type (or list of types) + if (wantarray) { + return @$fileType if ref $fileType eq 'ARRAY'; + } elsif ($fileType) { + $fileType = $fileExt if ref $fileType eq 'ARRAY'; + } + return $fileType; +} + +#------------------------------------------------------------------------------ +# Return true if we can write the specified file type +# Inputs: 0) file name or ext +# Returns: true if writable, 0 if not writable, undef if unrecognized +sub CanWrite($) +{ + local $_; + my $file = shift or return undef; + my ($type) = GetFileType($file) or return undef; + if ($noWriteFile{$type}) { + # can't write TIFF files with certain extensions (various RAW formats) + my $ext = GetFileExtension($file) || uc($file); + return grep(/^$ext$/, @{$noWriteFile{$type}}) ? 0 : 1 if $ext; + } + if ($onlyWriteFile{$type}) { + my $ext = GetFileExtension($file) || uc($file); + return grep(/^$ext$/, @{$onlyWriteFile{$type}}) ? 1 : 0 if $ext; + } + unless (%writeTypes) { + $writeTypes{$_} = 1 foreach @writeTypes; + } + return $writeTypes{$type}; +} + +#------------------------------------------------------------------------------ +# Return true if we can create the specified file type +# Inputs: 0) file name or ext +# Returns: true if creatable, 0 if not writable, undef if unrecognized +sub CanCreate($) +{ + local $_; + my $file = shift or return undef; + my $ext = GetFileExtension($file) || uc($file); + my $type = GetFileType($file) or return undef; + return 1 if $createTypes{$ext} or $createTypes{$type}; + return 0; +} + +#============================================================================== +# Functions below this are not part of the public API + +# Initialize member variables before reading or writing a new file +# Inputs: 0) ExifTool object reference +sub Init($) +{ + local $_; + my $self = shift; + # delete all DataMember variables (lower-case names) + foreach (keys %$self) { + /[a-z]/ and delete $$self{$_}; + } + undef %static_vars; # clear all static variables + delete $$self{FOUND_TAGS}; # list of found tags + delete $$self{EXIF_DATA}; # the EXIF data block + delete $$self{EXIF_POS}; # EXIF position in file + delete $$self{FIRST_EXIF_POS}; # position of first EXIF in file + delete $$self{HTML_DUMP}; # html dump information + delete $$self{SET_GROUP0}; # group0 name override + delete $$self{SET_GROUP1}; # group1 name override + delete $$self{DOC_NUM}; # current embedded document number + $$self{DOC_COUNT} = 0; # count of embedded documents processed + $$self{BASE} = 0; # base for offsets from start of file + $$self{FILE_ORDER} = { }; # * hash of tag order in file ('*' = based on tag key) + $$self{VALUE} = { }; # * hash of raw tag values + $$self{BOTH} = { }; # * hash for Value/PrintConv values of Require'd tags + $$self{RATIONAL} = { }; # * hash of original rational components + $$self{TAG_INFO} = { }; # * hash of tag information + $$self{TAG_EXTRA} = { }; # * hash of extra tag information (dynamic group names) + $$self{PRIORITY} = { }; # * priority of current tags + $$self{LIST_TAGS} = { }; # hash of tagInfo refs for active List-type tags + $$self{PROCESSED} = { }; # hash of processed directory start positions + $$self{DIR_COUNT} = { }; # count various types of directories + $$self{DUPL_TAG} = { }; # last-used index for duplicate-tag keys + $$self{WARNED_ONCE}= { }; # WarnOnce() warnings already issued + $$self{WRITTEN} = { }; # list of tags written (selected tags only) + $$self{FORCE_WRITE}= { }; # ForceWrite lookup (set from ForceWrite tag) + $$self{FOUND_DIR} = { }; # hash of directory names found in file + $$self{COMP_KEYS} = { }; # lookup for tag keys used in Composite tags + $$self{PATH} = [ ]; # current subdirectory path in file when reading + $$self{NUM_FOUND} = 0; # total number of tags found (incl. duplicates) + $$self{CHANGED} = 0; # number of tags changed (writer only) + $$self{INDENT} = ' '; # initial indent for verbose messages + $$self{PRIORITY_DIR} = ''; # the priority directory name + $$self{LOW_PRIORITY_DIR} = { PreviewIFD => 1 }; # names of priority 0 directories + $$self{TIFF_TYPE} = ''; # type of TIFF data (APP1, TIFF, NEF, etc...) + $$self{FMT_EXPR} = undef; # current advanced formatting expression + $$self{Make} = ''; # camera make + $$self{Model} = ''; # camera model + $$self{CameraType} = ''; # Olympus camera type + $$self{FileType} = ''; # identified file type + if ($self->Options('HtmlDump')) { + require Image::ExifTool::HtmlDump; + $$self{HTML_DUMP} = new Image::ExifTool::HtmlDump; + } + # make sure our TextOut is a file reference + $$self{OPTIONS}{TextOut} = \*STDOUT unless ref $$self{OPTIONS}{TextOut}; +} + +#------------------------------------------------------------------------------ +# Combine information from a list of info hashes +# Unless Duplicates is enabled, first entry found takes priority +# Inputs: 0) ExifTool object reference, 1-N) list of info hash references +# Returns: Combined information hash reference +sub CombineInfo($;@) +{ + local $_; + my $self = shift; + my (%combinedInfo, $info, $tag, %haveInfo); + + if ($$self{OPTIONS}{Duplicates}) { + while ($info = shift) { + foreach $tag (keys %$info) { + $combinedInfo{$tag} = $$info{$tag}; + } + } + } else { + while ($info = shift) { + foreach $tag (keys %$info) { + my $tagName = GetTagName($tag); + next if $haveInfo{$tagName}; + $haveInfo{$tagName} = 1; + $combinedInfo{$tag} = $$info{$tag}; + } + } + } + return \%combinedInfo; +} + +#------------------------------------------------------------------------------ +# Read metadata from alternate files and build composite tags +# Inputs: 0) ExifTool ref +# Notes: This is called after reading the main file so the tags are available +# for being used in the file name, but before building Composite tags +# so tags from the alternate files may be used in the Composite tags +sub ExtractAltInfo($) +{ + my $self = shift; + # extract information from alternate files if necessary + my ($g8, $altExifTool); + my $opts = $$self{OPTIONS}; + if ($$opts{Composite} and (not $$opts{FastScan} or $$opts{FastScan} < 5)) { + # build all composite tags except those requiring tags from alternate files + $self->BuildCompositeTags(); + } + foreach $g8 (sort keys %{$$self{ALT_EXIFTOOL}}) { + $altExifTool = $$self{ALT_EXIFTOOL}{$g8}; + next if $$altExifTool{DID_EXTRACT}; # avoid extracting twice + $$altExifTool{OPTIONS} = $$self{OPTIONS}; + $$altExifTool{GLOBAL_TIME_OFFSET} = $$self{GLOBAL_TIME_OFFSET}; + $$altExifTool{REQ_TAG_LOOKUP} = $$self{REQ_TAG_LOOKUP}; + my $fileName = $$altExifTool{ALT_FILE}; + # allow tags from the main file to be used in the alternate file names + # (eg. -file1 '$originalfilename') + if ($fileName =~ /\$/) { + my @tags = reverse sort keys %{$$self{VALUE}}; + $fileName = $self->InsertTagValues(\@tags, $fileName, 'Warn'); + next unless defined $fileName; + } + $altExifTool->ExtractInfo($fileName); + # set family 8 group name for all tags + foreach (keys %{$$altExifTool{VALUE}}) { + my $ex = $$altExifTool{TAG_EXTRA}{$_}; + $ex or $ex = $$altExifTool{TAG_EXTRA}{$_} = { }; + $$ex{G8} = $g8; + } + $$altExifTool{DID_EXTRACT} = 1; + } + # if necessary, build composite tags that rely on tags from alternate files + $self->BuildCompositeTags(1) if $$self{DoAltComposite}; +} + +#------------------------------------------------------------------------------ +# Get tag table name +# Inputs: 0) ExifTool object reference, 1) tag key +# Returns: Table name if available, otherwise '' +sub GetTableName($$) +{ + my ($self, $tag) = @_; + my $tagInfo = $$self{TAG_INFO}{$tag} or return ''; + return $$tagInfo{Table}{SHORT_NAME}; +} + +#------------------------------------------------------------------------------ +# Get tag index number +# Inputs: 0) ExifTool object reference, 1) tag key +# Returns: Table index number, or undefined if this tag isn't indexed +sub GetTagIndex($$) +{ + my ($self, $tag) = @_; + my $tagInfo = $$self{TAG_INFO}{$tag} or return undef; + return $$tagInfo{Index}; +} + +#------------------------------------------------------------------------------ +# Find value for specified tag +# Inputs: 0) ExifTool ref, 1) tag name, 2) tag group (family 1) +# Returns: value or undef +sub FindValue($$$) +{ + my ($et, $tag, $grp) = @_; + my ($i, $val); + my $value = $$et{VALUE}; + for ($i=0; ; ++$i) { + my $key = $tag . ($i ? " ($i)" : ''); + last unless defined $$value{$key}; + if ($et->GetGroup($key, 1) eq $grp) { + $val = $$value{$key}; + last; + } + } + return $val; +} + +#------------------------------------------------------------------------------ +# Get tag key for next existing tag +# Inputs: 0) ExifTool ref, 1) tag key or case-sensitive tag name +# Returns: Key of next existing tag, or undef if no more +# Notes: This routine is provided for iterating through duplicate tags in the +# ValueConv of Composite tags. +sub NextTagKey($$) +{ + my ($self, $tag) = @_; + my $i = ($tag =~ s/ \((\d+)\)$//) ? $1 + 1 : 1; + $tag = "$tag ($i)"; + return $tag if defined $$self{VALUE}{$tag}; + return undef; +} + +#------------------------------------------------------------------------------ +# Does a string contain valid UTF-8 characters? +# Inputs: 0) string reference, 1) true to allow last character to be truncated +# Returns: 0=regular ASCII, -1=invalid UTF-8, 1=valid UTF-8 with maximum 16-bit +# wide characters, 2=valid UTF-8 requiring 32-bit wide characters +# Notes: Changes current string position +# (see http://www.fileformat.info/info/unicode/utf8.htm for help understanding this) +sub IsUTF8($;$) +{ + my ($strPt, $trunc) = @_; + pos($$strPt) = 0; # start at beginning of string + return 0 unless $$strPt =~ /([\x80-\xff])/g; + my $rtnVal = 1; + for (;;) { + my $ch = ord($1); + # minimum lead byte for 2-byte sequence is 0xc2 (overlong sequences + # not allowed), 0xf8-0xfd are restricted by RFC 3629 (no 5 or 6 byte + # sequences), and 0xfe and 0xff are not valid in UTF-8 strings + return -1 if $ch < 0xc2 or $ch >= 0xf8; + # determine number of bytes remaining in sequence + my $n; + if ($ch < 0xe0) { + $n = 1; + } elsif ($ch < 0xf0) { + $n = 2; + } else { + $n = 3; + # character code is greater than 0xffff if more than 2 extra bytes + # were required in the UTF-8 character + $rtnVal = 2; + } + my $pos = pos $$strPt; + unless ($$strPt =~ /\G([\x80-\xbf]{$n})/g) { + return $rtnVal if $trunc and $pos + $n > length $$strPt; + return -1; + } + # the following is ref https://www.cl.cam.ac.uk/%7Emgk25/ucs/utf8_check.c + if ($n == 2) { + return -1 if ($ch == 0xe0 and (ord($1) & 0xe0) == 0x80) or + ($ch == 0xed and (ord($1) & 0xe0) == 0xa0) or + ($ch == 0xef and ord($1) == 0xbf and + (ord(substr $1, 1) & 0xfe) == 0xbe); + } else { + return -1 if ($ch == 0xf0 and (ord($1) & 0xf0) == 0x80) or + ($ch == 0xf4 and ord($1) > 0x8f) or $ch > 0xf4; + } + last unless $$strPt =~ /([\x80-\xff])/g; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Split file name into directory and name parts +# Inptus: 0) file name +# Returns: 0) directory, 1) filename +sub SplitFileName($) +{ + my $file = shift; + my ($dir, $name); + if (eval { require File::Basename }) { + $dir = File::Basename::dirname($file); + $name = File::Basename::basename($file); + } else { + ($name = $file) =~ tr/\\/\//; + # remove path + if ($name =~ s/(.*)\///) { + $dir = length($1) ? $1 : '/'; + } else { + $dir = '.'; + } + } + return ($dir, $name); +} + +#------------------------------------------------------------------------------ +# Encode file name for calls to system i/o routines +# Inputs: 0) ExifTool ref, 1) file name in CharSetFileName, 2) flag to force conversion +# Returns: true if Windows Unicode routines should be used (in which case +# the file name will be encoded as a null-terminated UTF-16LE string) +sub EncodeFileName($$;$) +{ + my ($self, $file, $force) = @_; + my $enc = $$self{OPTIONS}{CharsetFileName}; + $force = 1 if $$self{OPTIONS}{WindowsWideFile}; + if ($enc) { + if ($file =~ /[\x80-\xff]/ or $force) { + # encode for use in Windows Unicode functions if necessary + if ($^O eq 'MSWin32') { + local $SIG{'__WARN__'} = \&SetWarning; + if (eval { require Win32API::File }) { + # recode as UTF-16LE and add null terminator + $_[1] = $self->Decode($file, $enc, undef, 'UTF16', 'II') . "\0\0"; + return 1; + } + $self->WarnOnce('Install Win32API::File for Windows Unicode file support'); + } else { + # recode as UTF-8 for other platforms if necessary + $_[1] = $self->Decode($file, $enc, undef, 'UTF8') unless $enc eq 'UTF8'; + } + } + } elsif ($^O eq 'MSWin32' and $file =~ /[\x80-\xff]/ and not defined $enc) { + $self->WarnOnce('FileName encoding not specified') if IsUTF8(\$file) < 0; + } + return 0; +} + +#------------------------------------------------------------------------------ +# Modified perl open() routine to properly handle special characters in file names +# Inputs: 0) ExifTool ref, 1) filehandle, 2) filename, +# 3) mode: '<' or undef = read, '>' = write, '+<' = update +# Returns: true on success +# Note: Must call like "$et->Open(\*FH,$file)", not "$et->Open(FH,$file)" to avoid +# "unopened filehandle" errors due to a change in scope of the filehandle +sub Open($*$;$) +{ + my ($self, $fh, $file, $mode) = @_; + + $file =~ s/^([\s&])/.\/$1/; # protect leading whitespace or ampersand + # default to read mode ('<') unless input is a trusted pipe + $mode = (($file =~ /\|$/ and $$self{TRUST_PIPE}) ? '' : '<') unless $mode; + delete $$self{TRUST_PIPE}; + if ($mode) { + if ($self->EncodeFileName($file)) { + # handle Windows Unicode file name + local $SIG{'__WARN__'} = \&SetWarning; + my ($access, $create); + if ($mode eq '>' or $mode eq '>>') { + eval { + $access = Win32API::File::GENERIC_WRITE(); + if ($mode eq '>>') { + $access |= Win32API::File::FILE_APPEND_DATA(); + $create = Win32API::File::OPEN_ALWAYS(); + } else { + $create = Win32API::File::CREATE_ALWAYS(); + } + } + } else { + eval { + $access = Win32API::File::GENERIC_READ(); + $access |= Win32API::File::GENERIC_WRITE() if $mode eq '+<'; # update + $create = Win32API::File::OPEN_EXISTING(); + } + } + my $share = 0; + eval { + unless ($access & Win32API::File::GENERIC_WRITE()) { + $share = Win32API::File::FILE_SHARE_READ() | Win32API::File::FILE_SHARE_WRITE(); + } + }; + my $wh = eval { Win32API::File::CreateFileW($file, $access, $share, [], $create, 0, []) }; + return undef unless $wh; + my $fd = eval { Win32API::File::OsFHandleOpenFd($wh, 0) }; + if (not defined $fd or $fd < 0) { + eval { Win32API::File::CloseHandle($wh) }; + return undef; + } + $file = "&=$fd"; # specify file by descriptor + } else { + # add leading space to protect against leading characters like '>' + # in file name, and trailing "\0" to protect trailing spaces + $file = " $file\0"; + } + } + return open $fh, "$mode$file"; +} + +#------------------------------------------------------------------------------ +# Check to see if a file exists (with Windows Unicode support) +# Inputs: 0) ExifTool ref, 1) file name, 2) flag if we are writing this file +# Returns: true if file exists +sub Exists($$;$) +{ + my ($self, $file, $writing) = @_; + + if ($self->EncodeFileName($file)) { + local $SIG{'__WARN__'} = \&SetWarning; + my $wh = eval { Win32API::File::CreateFileW($file, + Win32API::File::GENERIC_READ(), + Win32API::File::FILE_SHARE_READ(), [], + Win32API::File::OPEN_EXISTING(), 0, []) }; + return 0 unless $wh; + eval { Win32API::File::CloseHandle($wh) }; + } elsif ($writing) { + # (named pipes already exist, but we pretend that they don't + # so we will be able to write them, so test with for pipe -p) + return(-e $file and not -p $file); + } else { + return(-e $file); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Return true if file is a directory (with Windows Unicode support) +# Inputs: 0) ExifTool ref, 1) file name +# Returns: true if file is a directory (false if file isn't, or doesn't exist) +sub IsDirectory($$) +{ + my ($et, $file) = @_; + if ($et->EncodeFileName($file)) { + local $SIG{'__WARN__'} = \&SetWarning; + my $attrs = eval { Win32API::File::GetFileAttributesW($file) }; + my $dirBit = eval { Win32API::File::FILE_ATTRIBUTE_DIRECTORY() } || 0; + return 1 if $attrs and $attrs != 0xffffffff and $attrs & $dirBit; + } else { + return -d $file; + } + return 0; +} + +#------------------------------------------------------------------------------ +# Get file times (Unix seconds since the epoch) +# Inputs: 0) ExifTool ref, 1) file name or ref +# Returns: 0) access time, 1) modification time, 2) creation time (or undefs on error) +my $k32GetFileTime; +sub GetFileTime($$) +{ + my ($self, $file) = @_; + + # open file by name if necessary + unless (ref $file) { + local *FH; + unless ($self->Open(\*FH, $file)) { + if ($self->IsDirectory($file)) { + my @rtn = (stat $file)[8, 9, 10]; + return @rtn if defined $rtn[0]; + } + $self->Warn("GetFileTime error for '${file}'"); + return (); + } + $file = *FH; # (not \*FH, so *FH will be kept open until $file goes out of scope) + } + # on Windows, try to work around incorrect file times when daylight saving time is in effect + if ($^O eq 'MSWin32') { + if (not eval { require Win32::API }) { + $self->WarnOnce('Install Win32::API for proper handling of Windows file times', 1); + } elsif (not eval { require Win32API::File }) { + $self->WarnOnce('Install Win32API::File for proper handling of Windows file times', 1); + } else { + # get Win32 handle, needed for GetFileTime + my $win32Handle = eval { Win32API::File::GetOsFHandle($file) }; + unless ($win32Handle) { + $self->Warn("Win32API::File::GetOsFHandle returned invalid handle"); + return (); + } + # get FILETIME structs + my ($atime, $mtime, $ctime, $time); + $atime = $mtime = $ctime = pack 'LL', 0, 0; + unless ($k32GetFileTime) { + return () if defined $k32GetFileTime; + $k32GetFileTime = new Win32::API('KERNEL32', 'GetFileTime', 'NPPP', 'I'); + unless ($k32GetFileTime) { + $self->Warn('Error calling Win32::API::GetFileTime'); + $k32GetFileTime = 0; + return (); + } + } + unless ($k32GetFileTime->Call($win32Handle, $ctime, $atime, $mtime)) { + $self->Warn("Win32::API::GetFileTime returned " . Win32::GetLastError()); + return (); + } + # convert FILETIME structs to Unix seconds + foreach $time ($atime, $mtime, $ctime) { + my ($lo, $hi) = unpack 'LL', $time; # unpack FILETIME struct + # FILETIME is in 100 ns intervals since 0:00 UTC Jan 1, 1601 + # (89 leap years between 1601 and 1970) + $time = ($hi * 4294967296 + $lo) * 1e-7 - (((1970-1601)*365+89)*24*3600); + } + return ($atime, $mtime, $ctime); + } + } + # other os (or Windows fallback) + return (stat $file)[8, 9, 10]; +} + +#------------------------------------------------------------------------------ +# Parse function arguments and set member variables accordingly +# Inputs: Same as ImageInfo() +# - sets REQUESTED_TAGS, REQ_TAG_LOOKUP, IO_TAG_LIST, FILENAME, RAF, OPTIONS +sub ParseArguments($;@) +{ + my $self = shift; + my $options = $$self{OPTIONS}; + my @oldGroupOpts = grep /^Group/, keys %{$$self{OPTIONS}}; + my (@exclude, $wasExcludeOpt); + + $$self{REQUESTED_TAGS} = [ ]; + $$self{REQ_TAG_LOOKUP} = { }; + $$self{EXCL_TAG_LOOKUP} = { }; + $$self{IO_TAG_LIST} = undef; + delete $$self{EXCL_XMP_LOOKUP}; + + # handle our input arguments + while (@_) { + my $arg = shift; + if (ref $arg and not overload::Method($arg, q[""])) { + if (ref $arg eq 'ARRAY') { + $$self{IO_TAG_LIST} = $arg; + foreach (@$arg) { + if (/^-(.*)/) { + push @exclude, $1; + } else { + push @{$$self{REQUESTED_TAGS}}, $_; + } + } + } elsif (ref $arg eq 'HASH') { + my $opt; + foreach $opt (keys %$arg) { + # a single new group option overrides all old group options + if (@oldGroupOpts and $opt =~ /^Group/) { + foreach (@oldGroupOpts) { + delete $$options{$_}; + } + undef @oldGroupOpts; + } + $self->Options($opt, $$arg{$opt}); + $opt eq 'Exclude' and $wasExcludeOpt = 1; + } + } elsif (ref $arg eq 'SCALAR' or UNIVERSAL::isa($arg,'GLOB')) { + next if defined $$self{RAF}; + # convert image data from UTF-8 to character stream if necessary + # (patches RHEL 3 UTF8 LANG problem) + if (ref $arg eq 'SCALAR' and $] >= 5.006 and + (eval { require Encode; Encode::is_utf8($$arg) } or $@)) + { + # repack by hand if Encode isn't available + my $buff = $@ ? pack('C*',unpack($] < 5.010000 ? 'U0C*' : 'C0C*',$$arg)) : Encode::encode('utf8',$$arg); + $arg = \$buff; + } + $$self{RAF} = new File::RandomAccess($arg); + # set filename to empty string to indicate that + # we have a file but we didn't open it + $$self{FILENAME} = ''; + } elsif (UNIVERSAL::isa($arg, 'File::RandomAccess')) { + $$self{RAF} = $arg; + $$self{FILENAME} = ''; + } else { + warn "Don't understand ImageInfo argument $arg\n"; + } + } elsif (defined $$self{FILENAME}) { + if ($arg =~ /^-(.*)/) { + push @exclude, $1; + } else { + push @{$$self{REQUESTED_TAGS}}, $arg; + } + } else { + $$self{FILENAME} = $arg; + } + } + # add additional requested tags to lookup + if ($$options{RequestTags}) { + $$self{REQ_TAG_LOOKUP}{$_} = 1 foreach @{$$options{RequestTags}}; + } + # expand shortcuts in tag arguments if provided + if (@{$$self{REQUESTED_TAGS}}) { + ExpandShortcuts($$self{REQUESTED_TAGS}); + # initialize lookup for requested tags + foreach (@{$$self{REQUESTED_TAGS}}) { + /^(.*:)?([-\w?*]*)#?$/ or next; + $$self{REQ_TAG_LOOKUP}{lc($2)} = 1 if $2; + next unless $1; + $$self{REQ_TAG_LOOKUP}{lc($_).':'} = 1 foreach split /:/, $1; + } + } + if (@exclude or $wasExcludeOpt) { + # must add existing excluded tags + push @exclude, @{$$options{Exclude}} if $$options{Exclude}; + $$options{Exclude} = \@exclude; + # expand shortcuts in new exclude list + ExpandShortcuts($$options{Exclude}, 1); # (also remove '#' suffix) + } + # generate lookup for excluded tags + if ($$options{Exclude}) { + foreach (@{$$options{Exclude}}) { + /([-\w]+)#?$/ and $$self{EXCL_TAG_LOOKUP}{lc $1} = 1; + if (/(xmp-.*:[-\w]+)#?/i) { + $$self{EXCL_XMP_LOOKUP} or $$self{EXCL_XMP_LOOKUP} = { }; + $$self{EXCL_XMP_LOOKUP}{lc $1} = 1; + } + } + # exclude list is used only for EXCL_TAG_LOOKUP when TAGS_FROM_FILE is set + undef $$options{Exclude} if $$self{TAGS_FROM_FILE}; + } +} + +#------------------------------------------------------------------------------ +# Does group name match the tag ID? +# Inputs: 0) tag ID, 1) group name (with "ID-" removed) +# Returns: true on success +sub IsSameID($$) +{ + my ($id, $grp) = @_; + for (;;) { + return 1 if $grp eq $id; # decimal ID's or raw ID's + if ($id =~ /^\d+$/) { # numerical numerical ID's may be in hex + return 1 if $grp =~ s/^0x0*// and $grp eq sprintf('%x', $id); + } else { # other ID's may conform to ExifTool group name conventions + my $tmp = $id; + return 1 if $tmp =~ s/([^-_A-Za-z0-9])/sprintf('%.2x',ord $1)/ge and $grp eq $tmp; + } + last unless $id =~ s/-.*//; # remove language code if it exists + } + return 0; +} + +#------------------------------------------------------------------------------ +# Get list of tags in specified group +# Inputs: 0) ExifTool ref, 1) group spec, 2) tag key or reference to list of tag keys +# Returns: list of matching tags in list context, or first match in scalar context +# Notes: Group spec may contain multiple groups separated by colons, each +# possibly with a leading family number +sub GroupMatches($$$) +{ + my ($self, $group, $tagList) = @_; + $tagList = [ $tagList ] unless ref $tagList; + my ($tag, @matches); + # check each group name individually (eg. "Author:1IPTC") + my @grps = split ':', $group; + my (@fmys, $g); + for ($g=0; $g<@grps; ++$g) { + if ($grps[$g] =~ s/^(\d*)(id-)?//i) { + $fmys[$g] = $1 if length $1; + if ($2) { + $fmys[$g] = 7; + next; # (don't convert tag ID's to lower case) + } + } + $grps[$g] = lc $grps[$g]; + $grps[$g] = '' if $grps[$g] eq 'copy0'; # accept 'Copy0' for primary tag + } + foreach $tag (@$tagList) { + my @groups = $self->GetGroup($tag, -1); + for ($g=0; $g<@grps; ++$g) { + my $grp = $grps[$g]; + next if $grp eq '*' or $grp eq 'all'; + my $f; + if (defined($f = $fmys[$g])) { + last unless defined $groups[$f]; + if ($f == 7) { + next if IsSameID($self->GetTagID($tag), $grp); + } else { + next if $grp eq lc $groups[$f]; + } + last; + } else { + last unless grep /^$grp$/i, @groups; + } + } + if ($g == @grps) { + return $tag unless wantarray; + push @matches, $tag; + } + } + return wantarray ? @matches : $matches[0]; +} + +#------------------------------------------------------------------------------ +# Remove specified tags from returned tag list, updating indices in other lists +# Inputs: 0) tag list ref, 1) index list ref, 2) index list ref, 3) hash ref, +# 4) true to include tags from hash instead of excluding +# Returns: nothing, but updates input lists +sub RemoveTagsFromList($$$$;$) +{ + local $_; + my ($tags, $list1, $list2, $exclude, $inv) = @_; + my @filteredTags; + + if (@$list1 or @$list2) { + while (@$tags) { + my $tag = pop @$tags; + my $i = @$tags; + if ($$exclude{$tag} xor $inv) { + # remove index of excluded tag from each list + @$list1 = map { $_ < $i ? $_ : $_ == $i ? () : $_ - 1 } @$list1; + @$list2 = map { $_ < $i ? $_ : $_ == $i ? () : $_ - 1 } @$list2; + } else { + unshift @filteredTags, $tag; + } + } + } else { + foreach (@$tags) { + push @filteredTags, $_ unless $$exclude{$_} xor $inv; + } + } + $_[0] = \@filteredTags; # update tag list +} + +#------------------------------------------------------------------------------ +# Copy tags from alternate input file +# Inputs: 0) ExifTool ref, 1) family 8 group, 2) list ref for tag keys to copy +# - updates tag key list to match keys newly added to $self +sub CopyAltInfo($$$) +{ + my ($self, $g8, $tags) = @_; + my ($tag, $vtag); + return unless $g8 =~ /(\d+)/; + my $et = $$self{ALT_EXIFTOOL}{$g8} or return; + my $altOrder = ($1 + 1) * 100000; # increment file order + foreach $tag (@$tags) { + ($vtag = $tag) =~ s/( |$)/ #[$g8]/; + unless (defined $$self{VALUE}{$vtag}) { + $$self{VALUE}{$vtag} = $$et{VALUE}{$tag}; + $$self{TAG_INFO}{$vtag} = $$et{TAG_INFO}{$tag}; + $$self{TAG_EXTRA}{$vtag} = $$et{TAG_EXTRA}{$tag} || { }; + $$self{FILE_ORDER}{$vtag} = ($$et{FILE_ORDER}{$tag} || 0) + $altOrder; + } + $tag = $vtag; + } +} + +#------------------------------------------------------------------------------ +# Set list of found tags from previously requested tags +# Inputs: 0) ExifTool object reference +# Returns: 0) Reference to list of found tag keys (in order of requested tags) +# 1) Reference to list of indices for tags requested by value +# 2) Reference to list of indices for tags specified by wildcard or "all" +# Notes: index lists are returned in increasing order +sub SetFoundTags($) +{ + local $_; + my $self = shift; + my $options = $$self{OPTIONS}; + my $reqTags = $$self{REQUESTED_TAGS} || [ ]; + my $duplicates = $$options{Duplicates}; + my $exclude = $$options{Exclude}; + my $fileOrder = $$self{FILE_ORDER}; + my @groupOptions; + # ignore empty group options + $$options{$_} and push @groupOptions, $_ foreach sort grep /^Group/, keys %$options; + my $doDups = $duplicates || $exclude || @groupOptions; + my ($tag, $rtnTags, @byValue, @wildTags); + + # only return requested tags if specified + if (@$reqTags) { + $rtnTags or $rtnTags = [ ]; + # scan through the requested tags and generate a list of tags we found + my $tagHash = $$self{VALUE}; + my $reqTag; + foreach $reqTag (@$reqTags) { + my (@matches, $group, $allGrp, $allTag, $byValue, $g8); + my $et = $self; + if ($reqTag =~ /^(.*):(.+)/) { + ($group, $tag) = ($1, $2); + if ($group =~ /^(\*|all)$/i) { + $allGrp = 1; + } elsif ($reqTag =~ /\bfile(\d+):/i) { + $g8 = "File$1"; + $et = $$self{ALT_EXIFTOOL}{$g8} || $self; + $fileOrder = $$et{FILE_ORDER}; + $tagHash = $$et{VALUE}; + } elsif ($group !~ /^[-\w:]*$/) { + $self->Warn("Invalid group name '${group}'"); + $group = 'invalid'; + } + } else { + $tag = $reqTag; + } + $byValue = 1 if $tag =~ s/#$// and $$options{PrintConv}; + if (defined $$tagHash{$reqTag} and not $doDups) { + $matches[0] = $tag; + } elsif ($tag =~ /^(\*|all)$/i) { + # tag name of '*' or 'all' matches all tags + if ($doDups or $allGrp) { + @matches = grep(!/#/, keys %$tagHash); + } else { + @matches = grep(!/ /, keys %$tagHash); + } + next unless @matches; # don't want entry in list for '*' tag + $allTag = 1; + } elsif ($tag =~ /[*?]/) { + # allow wildcards in tag names + $tag =~ s/\*/[-\\w]*/g; + $tag =~ s/\?/[-\\w]/g; + $tag .= '( \\(.*)?' if $doDups or $allGrp; + @matches = grep(/^$tag$/i, keys %$tagHash); + next unless @matches; # don't want entry in list for wildcard tags + $allTag = 1; + } elsif ($doDups or defined $group) { + # must also look for tags like "Tag (1)" + # (but be sure not to match temporary ValueConv entries like "Tag #") + @matches = grep(/^$tag( \(|$)/i, keys %$tagHash); + } elsif ($tag =~ /^[-\w]+$/) { + # find first matching value + # (use in list context to return value instead of count) + ($matches[0]) = grep /^$tag$/i, keys %$tagHash; + defined $matches[0] or undef @matches; + } else { + $self->Warn("Invalid tag name '${tag}'"); + } + if (defined $group and not $allGrp) { + # keep only specified group + @matches = $et->GroupMatches($group, \@matches); + next unless @matches or not $allTag; + } + if (@matches > 1) { + # maintain original file order for multiple tags + @matches = sort { $$fileOrder{$a} <=> $$fileOrder{$b} } @matches; + # return only the highest priority tag unless duplicates wanted + unless ($doDups or $allTag or $allGrp) { + $tag = shift @matches; + my $oldPriority = $$et{PRIORITY}{$tag} || 1; + foreach (@matches) { + my $priority = $$et{PRIORITY}{$_}; + $priority = 1 unless defined $priority; + next unless $priority >= $oldPriority; + $tag = $_; + $oldPriority = $priority || 1; + } + @matches = ( $tag ); + } + } elsif (not @matches) { + # put entry in return list even without value (value is undef) + $matches[0] = $byValue ? "$tag #(0)" : "$tag (0)"; + # bogus file order entry to avoid warning if sorting in file order + $$self{FILE_ORDER}{$matches[0]} = 9999; + } + # copy over necessary information for tags from alternate files + if ($g8) { + $self->CopyAltInfo($g8, \@matches); + # restore variables to original values for main file + $fileOrder = $$self{FILE_ORDER}; + $tagHash = $$self{VALUE}; + } + # save indices of tags extracted by value + push @byValue, scalar(@$rtnTags) .. (scalar(@$rtnTags)+scalar(@matches)-1) if $byValue; + # save indices of wildcard tags + push @wildTags, scalar(@$rtnTags) .. (scalar(@$rtnTags)+scalar(@matches)-1) if $allTag; + push @$rtnTags, @matches; + } + } else { + # no requested tags, so we want all tags + my @allTags; + if ($doDups) { + @allTags = keys %{$$self{VALUE}}; + } else { + # only include tag if it doesn't end in a copy number + @allTags = grep(!/ /, keys %{$$self{VALUE}}); + } + $rtnTags = \@allTags; + } + + # filter excluded tags and group options + while (($exclude or @groupOptions) and @$rtnTags) { + if ($exclude) { + my ($pat, %exclude); + foreach $pat (@$exclude) { + my $group; + if ($pat =~ /^(.*):(.+)/) { + ($group, $tag) = ($1, $2); + if ($group =~ /^(\*|all)$/i) { + undef $group; + } elsif ($group !~ /^[-\w:]*$/) { + $self->Warn("Invalid group name '${group}'"); + $group = 'invalid'; + } + } else { + $tag = $pat; + } + my @matches; + if ($tag =~ /^(\*|all)$/i) { + @matches = @$rtnTags; + } else { + # allow wildcards in tag names + $tag =~ s/\*/[-\\w]*/g; + $tag =~ s/\?/[-\\w]/g; + @matches = grep(/^$tag( |$)/i, @$rtnTags); + } + @matches = $self->GroupMatches($group, \@matches) if $group and @matches; + $exclude{$_} = 1 foreach @matches; + } + if (%exclude) { + # remove excluded tags from return list(s) + RemoveTagsFromList($rtnTags, \@byValue, \@wildTags, \%exclude); + last unless @$rtnTags; # all done if nothing left + } + last if $duplicates and not @groupOptions; + } + # filter groups if requested, or to remove duplicates + my (%keepTags, %wantGroup, $family, $groupOpt); + my $allGroups = 1; + # build hash of requested/excluded group names for each group family + my $wantOrder = 0; + foreach $groupOpt (@groupOptions) { + $groupOpt =~ /^Group(\d*(:\d+)*)/ or next; + $family = $1 || 0; + $wantGroup{$family} or $wantGroup{$family} = { }; + my $groupList; + if (ref $$options{$groupOpt} eq 'ARRAY') { + $groupList = $$options{$groupOpt}; + } else { + $groupList = [ $$options{$groupOpt} ]; + } + foreach (@$groupList) { + # groups have priority in order they were specified + ++$wantOrder; + my ($groupName, $want); + if (/^-(.*)/) { + # excluded group begins with '-' + $groupName = $1; + $want = 0; # we don't want tags in this group + } else { + $groupName = $_; + $want = $wantOrder; # we want tags in this group + $allGroups = 0; # don't want all groups if we requested one + } + $wantGroup{$family}{$groupName} = $want; + } + } + # loop through all tags and decide which ones we want + my (@tags, %bestTag); +GR_TAG: foreach $tag (@$rtnTags) { + my $wantTag = $allGroups; # want tag by default if want all groups + foreach $family (keys %wantGroup) { + my $group = $self->GetGroup($tag, $family); + my $wanted = $wantGroup{$family}{$group}; + next unless defined $wanted; + next GR_TAG unless $wanted; # skip tag if group excluded + # take lowest non-zero want flag + next if $wantTag and $wantTag < $wanted; + $wantTag = $wanted; + } + next unless $wantTag; + $duplicates and $keepTags{$tag} = 1, next; + # determine which tag we want to keep + my $tagName = GetTagName($tag); + my $bestTag = $bestTag{$tagName}; + if (defined $bestTag) { + next if $wantTag > $keepTags{$bestTag}; + if ($wantTag == $keepTags{$bestTag}) { + # want two tags with the same name -- keep the latest one + if ($tag =~ / \((\d+)\)$/) { + my $tagNum = $1; + next if $bestTag !~ / \((\d+)\)$/ or $1 > $tagNum; + } + } + # this tag is better, so delete old best tag + delete $keepTags{$bestTag}; + } + $keepTags{$tag} = $wantTag; # keep this tag (for now...) + $bestTag{$tagName} = $tag; # this is our current best tag + } + # include only tags we want to keep in return lists + RemoveTagsFromList($rtnTags, \@byValue, \@wildTags, \%keepTags, 1); + last; + } + $$self{FOUND_TAGS} = $rtnTags; # save found tags + + # return reference to found tag keys (and list of indices of tags to extract by value) + return wantarray ? ($rtnTags, \@byValue, \@wildTags) : $rtnTags; +} + +#------------------------------------------------------------------------------ +# Utility to load our write routines if required (called via AUTOLOAD) +# Inputs: 0) autoload function, 1-N) function arguments +# Returns: result of function or dies if function not available +sub DoAutoLoad(@) +{ + my $autoload = shift; + my @callInfo = split(/::/, $autoload); + my $file = 'Image/ExifTool/Write'; + + return if $callInfo[$#callInfo] eq 'DESTROY'; + if (@callInfo == 4) { + # load Image/ExifTool/WriteMODULE.pl + $file .= "$callInfo[2].pl"; + } elsif ($callInfo[-1] eq 'ShiftTime') { + $file = 'Image/ExifTool/Shift.pl'; # load Shift.pl + } else { + # load Image/ExifTool/Writer.pl + $file .= 'r.pl'; + } + # attempt to load the package + eval { require $file } or die "Error while attempting to call $autoload\n$@\n"; + unless (defined &$autoload) { + my @caller = caller(0); + # reproduce Perl's standard 'undefined subroutine' message: + die "Undefined subroutine $autoload called at $caller[1] line $caller[2]\n"; + } + no strict 'refs'; + return &$autoload(@_); # call the function +} + +#------------------------------------------------------------------------------ +# AutoLoad our writer routines when necessary +# +sub AUTOLOAD +{ + return DoAutoLoad($AUTOLOAD, @_); +} + +#------------------------------------------------------------------------------ +# Add warning tag +# Inputs: 0) ExifTool object reference, 1) warning message +# 2) true if minor (2 if behaviour changes when warning is ignored, +# or 3 if warning shouldn't be issued when Validate option is used) +# Returns: true if warning tag was added +sub Warn($$;$) +{ + my ($self, $str, $ignorable) = @_; + my $noWarn = $self->Options('NoWarning'); + if ($ignorable) { + return 0 if $$self{OPTIONS}{IgnoreMinorErrors}; + return 0 if $ignorable eq '3' and $$self{OPTIONS}{Validate}; + return 1 if defined $noWarn and eval { $str =~ /$noWarn/ }; + $str = $ignorable eq '2' ? "[Minor] $str" : "[minor] $str"; + } + $self->FoundTag('Warning', $str) unless defined $noWarn and eval { $str =~ /$noWarn/ }; + return 1; +} + +#------------------------------------------------------------------------------ +# Add warning tag only once per processed file +# Inputs: 0) ExifTool object reference, 1) warning message, 2) true if minor +# Returns: true if warning tag was added +sub WarnOnce($$;$) +{ + my ($self, $str, $ignorable) = @_; + return 0 if $ignorable and $$self{OPTIONS}{IgnoreMinorErrors}; + unless ($$self{WARNED_ONCE}{$str}) { + $self->Warn($str, $ignorable); + $$self{WARNED_ONCE}{$str} = 1; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Add error tag +# Inputs: 0) ExifTool object reference, 1) error message, 2) true if minor +# Returns: true if error tag was added, otherwise warning was added +sub Error($$;$) +{ + my ($self, $str, $ignorable) = @_; + if ($$self{DemoteErrors}) { + $self->Warn($str) and ++$$self{DemoteErrors}; + return 1; + } elsif ($ignorable) { + $$self{OPTIONS}{IgnoreMinorErrors} and $self->Warn($str), return 0; + $str = "[minor] $str"; + } + $self->FoundTag('Error', $str); + return 1; +} + +#------------------------------------------------------------------------------ +# Expand shortcuts +# Inputs: 0) reference to list of tags, 1) set to remove trailing '#' +# Notes: Handles leading '-' for excluded tags, trailing '#' for ValueConv, +# multiple group names, and redirected tags +sub ExpandShortcuts($;$) +{ + my ($tagList, $removeSuffix) = @_; + return unless $tagList and @$tagList; + + require Image::ExifTool::Shortcuts; + + # expand shortcuts + my $suffix = $removeSuffix ? '' : '#'; + my @expandedTags; + my ($entry, $tag, $excl); + foreach $entry (@$tagList) { + # skip things like options hash references in list + if (ref $entry) { + push @expandedTags, $entry; + next; + } + # remove leading '-' + ($excl, $tag) = $entry =~ /^(-?)(.*)/s; + my ($post, @post, $pre, $v); + # handle redirection + if (not $excl and $tag =~ /(.+?)([-+]?[<>].+)/s) { + ($tag, $post) = ($1, $2); + if ($post =~ /^[-+]?>/ or $post !~ /\$/) { + # expand shortcuts in postfix (rhs of redirection) + my ($op, $p2, $t2) = ($post =~ /([-+]?[<>])(.+:)?(.+)/); + $p2 = '' unless defined $p2; + $v = ($t2 =~ s/#$//) ? $suffix : ''; # ValueConv suffix + my ($match) = grep /^\Q$t2\E$/i, keys %Image::ExifTool::Shortcuts::Main; + if ($match) { + foreach (@{$Image::ExifTool::Shortcuts::Main{$match}}) { + /^-/ and next; # ignore excluded tags + if ($p2 and /(.+:)(.+)/) { + push @post, "$op$_$v"; + } else { + push @post, "$op$p2$_$v"; + } + } + next unless @post; + $post = shift @post; + } + } + } else { + $post = ''; + } + # handle group names + if ($tag =~ /(.+:)(.+)/) { + ($pre, $tag) = ($1, $2); + } else { + $pre = ''; + } + $v = ($tag =~ s/#$//) ? $suffix : ''; # ValueConv suffix + # loop over all postfixes + for (;;) { + # expand the tag name + my ($match) = grep /^\Q$tag\E$/i, keys %Image::ExifTool::Shortcuts::Main; + if ($match) { + if ($excl) { + # entry starts with '-', so exclude all tags in this shortcut + foreach (@{$Image::ExifTool::Shortcuts::Main{$match}}) { + /^-/ and next; # ignore excluded exclude tags + # group of expanded tag takes precedence + if ($pre and /(.+:)(.+)/) { + push @expandedTags, "$excl$_"; + } else { + push @expandedTags, "$excl$pre$_"; + } + } + } elsif (length $pre or length $post or $v) { + foreach (@{$Image::ExifTool::Shortcuts::Main{$match}}) { + /(-?)(.+:)?(.+)/; + if ($2) { + # group from expanded tag takes precedence + push @expandedTags, "$_$v$post"; + } else { + push @expandedTags, "$1$pre$3$v$post"; + } + } + } else { + push @expandedTags, @{$Image::ExifTool::Shortcuts::Main{$match}}; + } + } else { + push @expandedTags, "$excl$pre$tag$v$post"; + } + last unless @post; + $post = shift @post; + } + } + @$tagList = @expandedTags; +} + +#------------------------------------------------------------------------------ +# Add hash of Composite tags to our composites +# Inputs: 0) hash reference to table of Composite tags to add or module name, +# 1) override existing tag definition +sub AddCompositeTags($;$) +{ + local $_; + my ($add, $override) = @_; + my ($module, $prefix, $tagID); + unless (ref $add) { + ($prefix = $add) =~ s/.*:://; + $module = $add; + $add .= '::Composite'; + no strict 'refs'; + $add = \%$add; + $prefix .= '-'; + } else { + $prefix = 'UserDefined-'; + } + my $defaultGroups = $$add{GROUPS}; + my $compTable = GetTagTable('Image::ExifTool::Composite'); + + # make sure default groups are defined in families 0 and 1 + if ($defaultGroups) { + $$defaultGroups{0} or $$defaultGroups{0} = 'Composite'; + $$defaultGroups{1} or $$defaultGroups{1} = 'Composite'; + $$defaultGroups{2} or $$defaultGroups{2} = 'Other'; + } else { + $defaultGroups = $$add{GROUPS} = { 0 => 'Composite', 1 => 'Composite', 2 => 'Other' }; + } + SetupTagTable($add); # generate Name, TagID, etc + foreach $tagID (sort keys %$add) { + next if $specialTags{$tagID}; # must skip special tags + my $tagInfo = $$add{$tagID}; + my $new = $prefix . $tagID; # new tag ID for Composite table + $$tagInfo{Module} = $module if $$tagInfo{Writable}; + $$tagInfo{Override} = 1 if $override and not defined $$tagInfo{Override}; + $$tagInfo{IsComposite} = 1; + # handle Composite tags with the same name + if ($compositeID{$tagID}) { + # determine if we want to override this tag + # (=0 keep both, >0 override, <0 keep existing) + my $over = ($$tagInfo{Override} || 0) - ($$compTable{$compositeID{$tagID}[0]}{Override} || 0); + next if $over < 0; + if ($over) { + # remove existing tags with this ID + delete $$compTable{$_} foreach @{$compositeID{$tagID}}; + delete $compositeID{$tagID}; + } + } + # make sure new TagID is unique by adding index if necessary + # (could only happen for UserDefined tags now that module name is added to tag ID) + my $n = 0; + while ($$compTable{$new}) { + $new =~ s/-\d+$// if $n++; + $new .= "-$n"; + } + # use new ID and save it so we can use it in TagLookup + $$tagInfo{NewTagID} = $new unless $tagID eq $new; + + # add new ID to lookup of Composite tag ID's + $compositeID{$tagID} = [ ] unless $compositeID{$tagID}; + unshift @{$compositeID{$tagID}}, $new; # (most recent one first) + + # convert scalar Require/Desire/Inhibit entries + my ($type, @hashes, @scalars, %used); + foreach $type ('Require','Desire','Inhibit') { + my $req = $$tagInfo{$type} or next; + push @{ref($req) eq 'HASH' ? \@hashes : \@scalars}, $type; + } + if (@scalars) { + # make lookup for indices that are used + foreach $type (@hashes) { + $used{$_} = 1 foreach keys %{$$tagInfo{$type}}; + } + my $next = 0; + foreach $type (@scalars) { + ++$next while $used{$next}; + $$tagInfo{$type} = { $next++ => $$tagInfo{$type} }; + } + } + # add this Composite tag to our main Composite table + $$tagInfo{Table} = $compTable; + # (use the original TagID, even if we changed it, so don't do this:) + $$tagInfo{TagID} = $new; + # save tag under new ID in Composite table + $$compTable{$new} = $tagInfo; + # set all default groups in tag + my $groups = $$tagInfo{Groups}; + $groups or $groups = $$tagInfo{Groups} = { }; + # fill in default groups + foreach (keys %$defaultGroups) { + $$groups{$_} or $$groups{$_} = $$defaultGroups{$_}; + } + # set flag indicating group list was built + $$tagInfo{GotGroups} = 1; + } +} + +#------------------------------------------------------------------------------ +# Add tags to TagLookup (used for writing) +# Inputs: 0) source hash of tag definitions, 1) name of destination tag table +sub AddTagsToLookup($$) +{ + my ($tagHash, $table) = @_; + if (defined &Image::ExifTool::TagLookup::AddTags) { + Image::ExifTool::TagLookup::AddTags($tagHash, $table); + } elsif (not $Image::ExifTool::pluginTags{$tagHash}) { + # queue these tags until TagLookup is loaded + push @Image::ExifTool::pluginTags, [ $tagHash, $table ]; + # set flag so we don't load same tags twice + $Image::ExifTool::pluginTags{$tagHash} = 1; + } +} + +#------------------------------------------------------------------------------ +# Expand tagInfo Flags +# Inputs: 0) tagInfo hash ref +# Notes: $$tagInfo{Flags} must be defined to call this routine +sub ExpandFlags($) +{ + my $tagInfo = shift; + my $flags = $$tagInfo{Flags}; + if (ref $flags eq 'ARRAY') { + foreach (@$flags) { + $$tagInfo{$_} = 1; + } + } elsif (ref $flags eq 'HASH') { + my $key; + foreach $key (keys %$flags) { + $$tagInfo{$key} = $$flags{$key}; + } + } else { + $$tagInfo{$flags} = 1; + } +} + +#------------------------------------------------------------------------------ +# Set up tag table (must be done once for each tag table used) +# Inputs: 0) Reference to tag table +# Notes: - generates 'Name' field from key if it doesn't exist +# - stores 'Table' pointer and 'TagID' value +# - expands 'Flags' for quick lookup +sub SetupTagTable($) +{ + my $tagTablePtr = shift; + my $avoid = $$tagTablePtr{AVOID}; + my ($tagID, $tagInfo); + foreach $tagID (TagTableKeys($tagTablePtr)) { + my @infoArray = GetTagInfoList($tagTablePtr,$tagID); + # process conditional tagInfo arrays + foreach $tagInfo (@infoArray) { + $$tagInfo{Table} = $tagTablePtr; + $$tagInfo{TagID} = $tagID; + $$tagInfo{Name} or $$tagInfo{Name} = MakeTagName($tagID); + $$tagInfo{Flags} and ExpandFlags($tagInfo); + $$tagInfo{Avoid} = $avoid if defined $avoid; + # calculate BitShift from Mask if necessary + if ($$tagInfo{Mask} and not defined $$tagInfo{BitShift}) { + my ($mask, $bitShift) = ($$tagInfo{Mask}, 0); + ++$bitShift until $mask & (1 << $bitShift); + $$tagInfo{BitShift} = $bitShift; + } + } + next unless @infoArray > 1; + # add an "Index" member to each tagInfo in a list + my $index = 0; + foreach $tagInfo (@infoArray) { + $$tagInfo{Index} = $index++; + } + } +} + +#------------------------------------------------------------------------------ +# Utilities to check for numerical types +# Inputs: 0) value; Returns: true if value is a numerical type +# Notes: May change commas to decimals in floats for use in other locales +sub IsFloat($) { + return 1 if $_[0] =~ /^[+-]?(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/; + # allow comma separators (for other locales) + return 0 unless $_[0] =~ /^[+-]?(?=\d|,\d)\d*(,\d*)?([Ee]([+-]?\d+))?$/; + $_[0] =~ tr/,/./; # but translate ',' to '.' + return 1; +} +sub IsInt($) { return scalar($_[0] =~ /^[+-]?\d+$/); } +sub IsHex($) { return scalar($_[0] =~ /^(0x)?[0-9a-f]{1,8}$/i); } +sub IsRational($) { return scalar($_[0] =~ m{^[-+]?\d+/\d+$}); } + +# round floating point value to specified number of significant digits +# Inputs: 0) value, 1) number of sig digits; Returns: rounded number +sub RoundFloat($$) +{ + my ($val, $sig) = @_; + return sprintf("%.${sig}g", $val); +} + +# Convert strings to floating point numbers (or undef) +# Inputs: 0-N) list of strings (may be undef) +# Returns: last value converted +sub ToFloat(@) +{ + local $_; + foreach (@_) { + next unless defined $_; + # (add 0 to convert "0.0" to "0" for tests) + $_ = /((?:[+-]?)(?=\d|\.\d)\d*(?:\.\d*)?(?:[Ee](?:[+-]?\d+))?)/ ? $1 + 0 : undef; + } + return $_[-1]; +} + +#------------------------------------------------------------------------------ +# Utility routines to for reading binary data values from file + +my %unpackMotorola = ( S => 'n', L => 'N', C => 'C', c => 'c' ); +my %unpackIntel = ( S => 'v', L => 'V', C => 'C', c => 'c' ); +my %unpackRev = ( N => 'V', V => 'N', C => 'C', n => 'v', v => 'n', c => 'c' ); + +# the following 4 variables are defined in 'use vars' instead of using 'my' +# because mod_perl 5.6.1 apparently has a problem with setting file-scope 'my' +# variables from within subroutines (ref communication with Pavel Merdin): +# $swapBytes - set if EXIF header is not native byte ordering +# $swapWords - swap 32-bit words in doubles (ARM quirk) +$currentByteOrder = 'MM'; # current byte ordering ('II' or 'MM') +%unpackStd = %unpackMotorola; + +# Swap bytes in data if necessary +# Inputs: 0) data, 1) number of bytes +# Returns: swapped data +sub SwapBytes($$) +{ + return $_[0] unless $swapBytes; + my ($val, $bytes) = @_; + my $newVal = ''; + $newVal .= substr($val, $bytes, 1) while $bytes--; + return $newVal; +} +# Swap words. Inputs: 8 bytes of data, Returns: swapped data +sub SwapWords($) +{ + return $_[0] unless $swapWords and length($_[0]) == 8; + return substr($_[0],4,4) . substr($_[0],0,4) +} + +# Unpack value, letting unpack() handle byte swapping +# Inputs: 0) unpack template, 1) data reference, 2) offset +# Returns: unpacked number +# - uses value of %unpackStd to determine the unpack template +# - can only be called for 'S' or 'L' templates since these are the only +# templates for which you can specify the byte ordering. +sub DoUnpackStd(@) +{ + $_[2] and return unpack("x$_[2] $unpackStd{$_[0]}", ${$_[1]}); + return unpack($unpackStd{$_[0]}, ${$_[1]}); +} +# same, but with reversed byte order +sub DoUnpackRev(@) +{ + my $fmt = $unpackRev{$unpackStd{$_[0]}}; + $_[2] and return unpack("x$_[2] $fmt", ${$_[1]}); + return unpack($fmt, ${$_[1]}); +} +# Pack value +# Inputs: 0) template, 1) value, 2) data ref (or undef), 3) offset (if data ref) +# Returns: packed value +sub DoPackStd(@) +{ + my $val = pack($unpackStd{$_[0]}, $_[1]); + $_[2] and substr(${$_[2]}, $_[3], length($val)) = $val; + return $val; +} +# same, but with reversed byte order +sub DoPackRev(@) +{ + my $val = pack($unpackRev{$unpackStd{$_[0]}}, $_[1]); + $_[2] and substr(${$_[2]}, $_[3], length($val)) = $val; + return $val; +} + +# Unpack value, handling the byte swapping manually +# Inputs: 0) # bytes, 1) unpack template, 2) data reference, 3) offset +# Returns: unpacked number +# - uses value of $swapBytes to determine byte ordering +sub DoUnpack(@) +{ + my ($bytes, $template, $dataPt, $pos) = @_; + my $val; + if ($swapBytes) { + $val = ''; + $val .= substr($$dataPt,$pos+$bytes,1) while $bytes--; + } else { + $val = substr($$dataPt,$pos,$bytes); + } + defined($val) or return undef; + return unpack($template,$val); +} + +# Unpack double value +# Inputs: 0) unpack template, 1) data reference, 2) offset +# Returns: unpacked number +sub DoUnpackDbl(@) +{ + my ($template, $dataPt, $pos) = @_; + my $val = substr($$dataPt,$pos,8); + defined($val) or return undef; + # swap bytes and 32-bit words (ARM quirk) if necessary, then unpack value + return unpack($template, SwapWords(SwapBytes($val, 8))); +} + +# Inputs: 0) data reference, 1) offset into data +sub Get8s($$) { return DoUnpackStd('c', @_); } +sub Get8u($$) { return DoUnpackStd('C', @_); } +sub Get16s($$) { return DoUnpack(2, 's', @_); } +sub Get16u($$) { return DoUnpackStd('S', @_); } +sub Get32s($$) { return DoUnpack(4, 'l', @_); } +sub Get32u($$) { return DoUnpackStd('L', @_); } +sub GetFloat($$) { return DoUnpack(4, 'f', @_); } +sub GetDouble($$) { return DoUnpackDbl('d', @_); } +sub Get16uRev($$) { return DoUnpackRev('S', @_); } +sub Get32uRev($$) { return DoUnpackRev('L', @_); } + +# rationals may be a floating point number, 'inf' or 'undef' +my ($ratNumer, $ratDenom); +sub GetRational32s($$) +{ + my ($dataPt, $pos) = @_; + $ratNumer = Get16s($dataPt,$pos); + $ratDenom = Get16s($dataPt, $pos + 2) or return $ratNumer ? 'inf' : 'undef'; + # round off to a reasonable number of significant figures + return RoundFloat($ratNumer / $ratDenom, 7); +} +sub GetRational32u($$) +{ + my ($dataPt, $pos) = @_; + $ratNumer = Get16u($dataPt,$pos); + $ratDenom = Get16u($dataPt, $pos + 2) or return $ratNumer ? 'inf' : 'undef'; + return RoundFloat($ratNumer / $ratDenom, 7); +} +sub GetRational64s($$) +{ + my ($dataPt, $pos) = @_; + $ratNumer = Get32s($dataPt,$pos); + $ratDenom = Get32s($dataPt, $pos + 4) or return $ratNumer ? 'inf' : 'undef'; + return RoundFloat($ratNumer / $ratDenom, 10); +} +sub GetRational64u($$) +{ + my ($dataPt, $pos) = @_; + $ratNumer = Get32u($dataPt,$pos); + $ratDenom = Get32u($dataPt, $pos + 4) or return $ratNumer ? 'inf' : 'undef'; + return RoundFloat($ratNumer / $ratDenom, 10); +} +sub GetFixed16s($$) +{ + my ($dataPt, $pos) = @_; + my $val = Get16s($dataPt, $pos) / 0x100; + return int($val * 1000 + ($val<0 ? -0.5 : 0.5)) / 1000; +} +sub GetFixed16u($$) +{ + my ($dataPt, $pos) = @_; + return int((Get16u($dataPt, $pos) / 0x100) * 1000 + 0.5) / 1000; +} +sub GetFixed32s($$) +{ + my ($dataPt, $pos) = @_; + my $val = Get32s($dataPt, $pos) / 0x10000; + # remove insignificant digits + return int($val * 1e5 + ($val>0 ? 0.5 : -0.5)) / 1e5; +} +sub GetFixed32u($$) +{ + my ($dataPt, $pos) = @_; + # remove insignificant digits + return int((Get32u($dataPt, $pos) / 0x10000) * 1e5 + 0.5) / 1e5; +} +# Inputs: 0) value, 1) data ref, 2) offset +sub Set8s(@) { return DoPackStd('c', @_); } +sub Set8u(@) { return DoPackStd('C', @_); } +sub Set16u(@) { return DoPackStd('S', @_); } +sub Set32u(@) { return DoPackStd('L', @_); } +sub Set16uRev(@) { return DoPackRev('S', @_); } + +#------------------------------------------------------------------------------ +# Get current byte order ('II' or 'MM') +sub GetByteOrder() { return $currentByteOrder; } + +#------------------------------------------------------------------------------ +# Set byte ordering +# Inputs: 0) 'MM'=motorola, 'II'=intel (will translate 'BigEndian', 'LittleEndian') +# Returns: 1 on success +sub SetByteOrder($) +{ + my $order = shift; + + if ($order eq 'MM') { # big endian (Motorola) + %unpackStd = %unpackMotorola; + } elsif ($order eq 'II') { # little endian (Intel) + %unpackStd = %unpackIntel; + } elsif ($order =~ /^Big/i) { + $order = 'MM'; + %unpackStd = %unpackMotorola; + } elsif ($order =~ /^Little/i) { + $order = 'II'; + %unpackStd = %unpackIntel; + } else { + return 0; + } + my $val = unpack('S','A '); + my $nativeOrder; + if ($val == 0x4120) { # big endian + $nativeOrder = 'MM'; + } elsif ($val == 0x2041) { # little endian + $nativeOrder = 'II'; + } else { + warn sprintf("Unknown native byte order! (pattern %x)\n",$val); + return 0; + } + $currentByteOrder = $order; # save current byte order + + # swap bytes if our native CPU byte ordering is not the same as the EXIF + $swapBytes = ($order ne $nativeOrder); + + # little-endian ARM has big-endian words for doubles (thanks Riku Voipio) + # (Note: Riku's patch checked for '0ff3', but I think it should be 'f03f' since + # 1 is '000000000000f03f' on an x86 -- so check for both, but which is correct?) + my $pack1d = pack('d', 1); + $swapWords = ($pack1d eq "\0\0\x0f\xf3\0\0\0\0" or + $pack1d eq "\0\0\xf0\x3f\0\0\0\0"); + return 1; +} + +#------------------------------------------------------------------------------ +# Change byte order +sub ToggleByteOrder() +{ + SetByteOrder(GetByteOrder() eq 'II' ? 'MM' : 'II'); +} + +#------------------------------------------------------------------------------ +# hash lookups for reading values from data +my %formatSize = ( + int8s => 1, + int8u => 1, + int16s => 2, + int16u => 2, + int16uRev => 2, + int32s => 4, + int32u => 4, + int32uRev => 4, + int64s => 8, + int64u => 8, + rational32s => 4, + rational32u => 4, + rational64s => 8, + rational64u => 8, + fixed16s => 2, + fixed16u => 2, + fixed32s => 4, + fixed32u => 4, + fixed64s => 8, + float => 4, + double => 8, + extended => 10, + unicode => 2, + complex => 8, + string => 1, + binary => 1, + 'undef' => 1, + ifd => 4, + ifd64 => 8, + ue7 => 1, + utf8 => 1, # (Exif 3.0) +); +my %readValueProc = ( + int8s => \&Get8s, + int8u => \&Get8u, + int16s => \&Get16s, + int16u => \&Get16u, + int16uRev => \&Get16uRev, + int32s => \&Get32s, + int32u => \&Get32u, + int32uRev => \&Get32uRev, + int64s => \&Get64s, + int64u => \&Get64u, + rational32s => \&GetRational32s, + rational32u => \&GetRational32u, + rational64s => \&GetRational64s, + rational64u => \&GetRational64u, + fixed16s => \&GetFixed16s, + fixed16u => \&GetFixed16u, + fixed32s => \&GetFixed32s, + fixed32u => \&GetFixed32u, + fixed64s => \&GetFixed64s, + float => \&GetFloat, + double => \&GetDouble, + extended => \&GetExtended, + ifd => \&Get32u, + ifd64 => \&Get64u, +); +# lookup for all rational types +my %isRational = ( + rational32u => 1, + rational32s => 1, + rational64u => 1, + rational64s => 1, +); +sub FormatSize($) { return $formatSize{$_[0]}; } + +#------------------------------------------------------------------------------ +# Read value from binary data (with current byte ordering) +# Inputs: 0) data reference, 1) value offset, 2) format string, +# 3) number of values (or undef to use all data), +# 4) valid data length relative to offset (or undef to use all data), +# 5) optional pointer to returned rational +# Returns: converted value, or undefined if data isn't there +# or list of values in list context +sub ReadValue($$$;$$$) +{ + my ($dataPt, $offset, $format, $count, $size, $ratPt) = @_; + + my $len = $formatSize{$format}; + unless ($len) { + warn "Unknown format $format"; + $len = 1; + } + $size = length($$dataPt) - $offset unless defined $size; + unless ($count) { + return '' if defined $count or $size < $len; + $count = int($size / $len); + } + # make sure entry is inside data + if ($len * $count > $size) { + $count = int($size / $len); # shorten count if necessary + $count < 1 and return undef; # return undefined if no data + } + my @vals; + my $proc = $readValueProc{$format}; + if (not $proc) { + # handle undef/binary/string (also unsupported unicode/complex) + $vals[0] = substr($$dataPt, $offset, $count * $len); + # truncate string at null terminator if necessary + $vals[0] =~ s/\0.*//s if $format eq 'string'; + } elsif ($isRational{$format} and $ratPt) { + # store rationals separately as string fractions + my @rat; + for (;;) { + push @vals, &$proc($dataPt, $offset); + push @rat, "$ratNumer/$ratDenom"; + last if --$count <= 0; + $offset += $len; + } + $$ratPt = join(' ',@rat); + } else { + for (;;) { + push @vals, &$proc($dataPt, $offset); + last if --$count <= 0; + $offset += $len; + } + } + return @vals if wantarray; + return join(' ', @vals) if @vals > 1; + return $vals[0]; +} + +#------------------------------------------------------------------------------ +# Decode string with specified encoding +# Inputs: 0) ExifTool object ref, 1) string to decode +# 2) source character set name (undef for current Charset) +# 3) optional source byte order (2-byte and 4-byte fixed-width sets only) +# 4) optional destination character set (defaults to Charset setting) +# 5) optional destination byte order (2-byte and 4-byte fixed-width only) +# Returns: string in destination encoding +# Note: ExifTool ref may be undef if character both character sets are provided +# (but in this case no warnings will be issued) +sub Decode($$$;$$$) +{ + my ($self, $val, $from, $fromOrder, $to, $toOrder) = @_; + $from or $from = $$self{OPTIONS}{Charset}; + $to or $to = $$self{OPTIONS}{Charset}; + if ($from ne $to and length $val) { + require Image::ExifTool::Charset; + my $cs1 = $Image::ExifTool::Charset::csType{$from}; + my $cs2 = $Image::ExifTool::Charset::csType{$to}; + if ($cs1 and $cs2 and not $cs2 & 0x002) { + # treat as straight ASCII if no character will need remapping + if (($cs1 | $cs2) & 0x680 or $val =~ /[\x80-\xff]/) { + my $uni = Image::ExifTool::Charset::Decompose($self, $val, $from, $fromOrder); + $val = Image::ExifTool::Charset::Recompose($self, $uni, $to, $toOrder); + } + } elsif ($self) { + my $set = $cs1 ? $to : $from; + unless ($$self{"DecodeWarn$set"}) { + $self->Warn("Unsupported character set ($set)"); + $$self{"DecodeWarn$set"} = 1; + } + } + } + return $val; +} + +#------------------------------------------------------------------------------ +# Encode string with specified encoding +# Inputs: 0) ExifTool object ref, 1) string, 2) destination character set name, +# 3) optional destination byte order (2-byte and 4-byte fixed-width sets only) +# Returns: string in specified encoding +sub Encode($$$;$) +{ + my ($self, $val, $to, $toOrder) = @_; + return $self->Decode($val, undef, undef, $to, $toOrder); +} + +#------------------------------------------------------------------------------ +# Decode bit mask +# Inputs: 0) value to decode, 1) Reference to hash for decoding (or undef) +# 2) optional bits per word (defaults to 32) +sub DecodeBits($$;$) +{ + my ($vals, $lookup, $bits) = @_; + $bits or $bits = 32; + my ($val, $i, @bitList); + my $num = 0; + foreach $val (split ' ', $vals) { + for ($i=0; $i<$bits; ++$i) { + next unless $val & (1 << $i); + my $n = $i + $num; + if (not $lookup) { + push @bitList, $n; + } elsif ($$lookup{$n}) { + push @bitList, $$lookup{$n}; + } else { + push @bitList, "[$n]"; + } + } + $num += $bits; + } + return '(none)' unless @bitList; + return join($lookup ? ', ' : ',', @bitList); +} + +#------------------------------------------------------------------------------ +# Validate an extracted image and repair if necessary +# Inputs: 0) ExifTool object reference, 1) image reference, 2) tag name or key +# Returns: image reference or undef if it wasn't valid +# Note: should be called from RawConv, not ValueConv +sub ValidateImage($$$) +{ + my ($self, $imagePt, $tag) = @_; + return undef if $$imagePt eq 'none'; + unless ($$imagePt =~ /^(Binary data|\xff\xd8\xff)/ or + # the first byte of the preview of some Minolta cameras is wrong, + # so check for this and set it back to 0xff if necessary + $$imagePt =~ s/^.(\xd8\xff\xdb)/\xff$1/s or + $self->Options('IgnoreMinorErrors')) + { + # issue warning only if the tag was specifically requested + if ($$self{REQ_TAG_LOOKUP}{lc GetTagName($tag)}) { + $self->Warn("$tag is not a valid JPEG image",1); + return undef; + } + } + return $imagePt; +} + +#------------------------------------------------------------------------------ +# Validate a tag name argument (including group name and wildcards, etc) +# Inputs: 0) tag name +# Returns: true if tag name is valid +# - a tag name may contain [-_A-Za-z0-9], but may not start with [-0-9] +# - tag names may contain wildcards [?*], and end with a hash [#] +# - may have group name prefixes (which may have family number prefix), separated by colons +# - a group name may be zero or more characters +sub ValidTagName($) +{ + my $tag = shift; + return $tag =~ /^(([-\w]*|\d*\*):)*[_a-zA-Z?*][-\w?*]*#?$/; +} + +#------------------------------------------------------------------------------ +# Generate a valid tag name based on the tag ID or name +# Inputs: 0) tag ID or name +# Returns: valid tag name +sub MakeTagName($) +{ + my $name = shift; + $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters + $name = ucfirst $name; # capitalize first letter + $name = "Tag$name" if length($name) < 2 or $name =~ /^[-0-9]/; + # must at least 2 characters long and not start with - or 0-9- + return $name; +} + +#------------------------------------------------------------------------------ +# Make description from a tag name +# Inputs: 0) tag name 1) optional tagID to add at end of description +# Returns: description +sub MakeDescription($;$) +{ + my ($tag, $tagID) = @_; + # start with the tag name and force first letter to be upper case + my $desc = ucfirst($tag); + # translate underlines to spaces + $desc =~ tr/_/ /; + # remove hex TagID from name (to avoid inserting spaces in the number) + $desc =~ s/ (0x[\da-f]+)$//i and $tagID = $1 unless defined $tagID; + # put a space between lower/UPPER case and lower/number combinations + $desc =~ s/([a-z])([A-Z\d])/$1 $2/g; + # put a space between acronyms and words + $desc =~ s/([A-Z])([A-Z][a-z])/$1 $2/g; + # put spaces after numbers (if more than one character follows the number) + $desc =~ s/(\d)([A-Z]\S)/$1 $2/g; + # add TagID to description + $desc .= ' ' . $tagID if defined $tagID; + return $desc; +} + +#------------------------------------------------------------------------------ +# Get descriptions for all tags in an array +# Inputs: 0) ExifTool ref, 1) reference to list of tag keys +# Returns: reference to hash lookup for descriptions +# Note: Returned descriptions are NOT escaped by ESCAPE_PROC +sub GetDescriptions($$) +{ + local $_; + my ($self, $tags) = @_; + my %desc; + my $oldEscape = $$self{ESCAPE_PROC}; + delete $$self{ESCAPE_PROC}; + $desc{$_} = $self->GetDescription($_) foreach @$tags; + $$self{ESCAPE_PROC} = $oldEscape; + return \%desc; +} + +#------------------------------------------------------------------------------ +# Apply filter to value(s) if necessary +# Inputs: 0) ExifTool ref, 1) filter expression, 2) reference to value to filter +# Returns: true unless a filter returned undef; changes value if necessary +sub Filter($$$) +{ + local $_; + my ($self, $filter, $valPt) = @_; + return 1 unless defined $filter and defined $$valPt; + my $rtnVal; + if (not ref $$valPt) { + $_ = $$valPt; + #### eval Filter ($_, $self) + eval $filter; + if (defined $_) { + $$valPt = $_; + $rtnVal = 1; + } + } elsif (ref $$valPt eq 'SCALAR') { + my $val = $$$valPt; # make a copy to avoid filtering twice + $rtnVal = $self->Filter($filter, \$val); + $$valPt = \$val; + } elsif (ref $$valPt eq 'ARRAY') { + my @val = @{$$valPt}; # make a copy to avoid filtering twice + $self->Filter($filter, \$_) and $rtnVal = 1 foreach @val; + $$valPt = \@val; + } elsif (ref $$valPt eq 'HASH') { + my %val = %{$$valPt}; # make a copy to avoid filtering twice + $self->Filter($filter, \$val{$_}) and $rtnVal = 1 foreach keys %val; + $$valPt = \%val; + } else { + $rtnVal = 1; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Return printable value +# Inputs: 0) ExifTool object reference +# 1) value to print, 2) line length limit (undef defaults to 60, 0=unlimited) +sub Printable($;$) +{ + my ($self, $outStr, $maxLen) = @_; + return '(undef)' unless defined $outStr; + $outStr =~ tr/\x01-\x1f\x7f-\xff/./; + $outStr =~ s/\x00//g; + my $verbose = $$self{OPTIONS}{Verbose}; + if ($verbose < 4) { + if ($maxLen) { + $maxLen = 20 if $maxLen < 20; # minimum length is 20 + } elsif (defined $maxLen) { + $maxLen = length $outStr; # 0 is unlimited + } else { + $maxLen = 60; # default maximum is 60 + } + } else { + $maxLen = length $outStr; + # limit to 2048 characters if verbose < 5 + $maxLen = 2048 if $maxLen > 2048 and $verbose < 5; + } + + # limit length if necessary + $outStr = substr($outStr,0,$maxLen-6) . '[snip]' if length($outStr) > $maxLen; + return $outStr; +} + +#------------------------------------------------------------------------------ +# Convert date/time from Exif format +# Inputs: 0) ExifTool object reference, 1) Date/time in EXIF format +# Returns: Formatted date/time string +sub ConvertDateTime($$) +{ + my ($self, $date) = @_; + my $fmt = $$self{OPTIONS}{DateFormat}; + my $shift = $$self{OPTIONS}{GlobalTimeShift}; + if ($shift) { + my $dir = ($shift =~ s/^([-+])// and $1 eq '-') ? -1 : 1; + my $offset = $$self{GLOBAL_TIME_OFFSET}; + $offset or $offset = $$self{GLOBAL_TIME_OFFSET} = { }; + ShiftTime($date, $shift, $dir, $offset); + } + # only convert date if a format was specified and the date is recognizable + if ($fmt) { + # separate time zone if it exists + my $tz; + $date =~ s/([-+]\d{2}:\d{2}|Z)$// and $tz = $1; + # a few cameras use incorrect date/time formatting: + # - slashes instead of colons in date (RolleiD330, ImpressCam) + # - date/time values separated by colon instead of space (Polariod, Sanyo, Sharp, Vivitar) + # - single-digit seconds with leading space (HP scanners) + my @a = reverse ($date =~ /\d+/g); # be very flexible about date/time format + if (@a and $a[-1] >= 1000 and $a[-1] < 3000 and eval { require POSIX }) { + shift @a while @a > 6; # remove superfluous entries + unshift @a, 1 while @a < 3; # add month and day if necessary + unshift @a, 0 while @a < 6; # add h,m,s if necessary + $a[4] -= 1; # base month is 1 + # parse our %f fractional seconds first (and round up seconds if necessary) + # - if there are multiple %f codes, they all get the same number of digits as the first + if ($fmt =~ /%(-?)\.?(\d*)f/) { + my ($neg, $dig) = ($1, $2); + my $frac = $date =~ /(\.\d+)/ ? $1 : ''; + if (not $frac) { + $frac = '.' . ('0' x $dig) if $dig; + } elsif (length $dig) { + if ($dig+1 > length($frac)) { + $frac .= '0' x ($dig+1-length($frac)); + } elsif ($dig+1 < length($frac)) { + $frac = sprintf("%.${dig}f", $frac); + while ($frac =~ s/^(\d)// and $1 ne '0') { + # this is a pain, but we must round up to the next second + ++$a[0] < 60 and last; + $a[0] = 0; + ++$a[1] < 60 and last; + $a[1] = 0; + ++$a[2] < 24 and last; + $a[2] = 0; + require 'Image/ExifTool/Shift.pl'; + ++$a[3] <= DaysInMonth($a[4]+1, $a[5]) and last; + $a[3] = 1; + ++$a[4] < 12 and last; + $a[4] = 0; + ++$a[5]; + last; # (this was a goto) + } + } + } + $neg and $frac =~ s/^\.//; + $fmt =~ s/(^|[^%])((%%)*)%-?\.?\d*f/$1$2$frac/g; + } + # parse %z and %s ourself (to handle time zones properly) + if ($fmt =~ /%[sz]/) { + # use system time zone unless otherwise specified + $tz = TimeZoneString(\@a, TimeLocal(@a)) if not $tz and eval { require Time::Local }; + # remove colon, setting to UTC if time zone is not numeric + $tz = ($tz and $tz=~/^([-+]\d{2}):(\d{2})$/) ? "$1$2" : '+0000'; + $fmt =~ s/(^|[^%])((%%)*)%z/$1$2$tz/g; # convert '%z' format codes + if ($fmt =~ /%s/ and eval { require Time::Local }) { + # calculate seconds since the Epoch, UTC + my $s = Time::Local::timegm(@a) - 60 * ($tz - int($tz/100) * 40); + $fmt =~ s/(^|[^%])((%%)*)%s/$1$2$s/g; # convert '%s' format codes + } + } + $a[5] -= 1900; # strftime year starts from 1900 + $date = POSIX::strftime($fmt, @a); # generate the formatted date/time + } elsif ($$self{OPTIONS}{StrictDate}) { + undef $date; + } + } + return $date; +} + +#------------------------------------------------------------------------------ +# Print conversion for time span value +# Inputs: 0) time ticks, 1) number of seconds per tick (default 1) +# Returns: readable time +sub ConvertTimeSpan($;$) +{ + my ($val, $mult) = @_; + if (Image::ExifTool::IsFloat($val) and $val != 0) { + $val *= $mult if $mult; + if ($val < 60) { + $val = "$val seconds"; + } elsif ($val < 3600) { + my $fmt = ($mult and $mult >= 60) ? '%d' : '%.1f'; + my $s = ($val == 60 and $mult) ? '' : 's'; + $val = sprintf("$fmt minute$s", $val / 60); + } elsif ($val < 24 * 3600) { + $val = sprintf("%.1f hours", $val / 3600); + } else { + $val = sprintf("%.1f days", $val / (24 * 3600)); + } + } + return $val; +} + +#------------------------------------------------------------------------------ +# Patched timelocal() that fixes ActivePerl timezone bug +# Inputs/Returns: same as timelocal() +# Notes: must 'require Time::Local' before calling this routine. +# Also note that year should be full year, and not relative to 1900 as with localtime +sub TimeLocal(@) +{ + my $tm = Time::Local::timelocal(@_); + if ($^O eq 'MSWin32') { + # patch for ActivePerl timezone bug + my @t2 = localtime($tm); + my $t2 = Time::Local::timelocal(@t2); + # adjust timelocal() return value to be consistent with localtime() + $tm += $tm - $t2; + } + return $tm; +} + +#------------------------------------------------------------------------------ +# Get time zone in minutes +# Inputs: 0) localtime array ref, 1) gmtime array ref +# Returns: time zone offset in minutes +sub GetTimeZone($$) +{ + my ($tm, $gm) = @_; + # compute the number of minutes between localtime and gmtime + my $min = $$tm[2] * 60 + $$tm[1] - ($$gm[2] * 60 + $$gm[1]); + if ($$tm[3] != $$gm[3]) { + # account for case where one date wraps to the first of the next month + $$gm[3] = $$tm[3] - ($$tm[3]==1 ? 1 : -1) if abs($$tm[3]-$$gm[3]) != 1; + # adjust for the +/- one day difference + $min += ($$tm[3] - $$gm[3]) * 24 * 60; + } + # MirBSD patch to round to the nearest 30 minutes because + # it includes leap seconds in localtime but not gmtime + $min = int($min / 30 + ($min > 0 ? 0.5 : -0.5)) * 30 if $^O eq 'mirbsd'; + return $min; +} + +#------------------------------------------------------------------------------ +# Get time zone string +# Inputs: 0) time zone offset in minutes +# or 0) localtime array ref, 1) corresponding time value +# Returns: time zone string ("+/-HH:MM") +sub TimeZoneString($;$) +{ + my $min = shift; + if (ref $min) { + my @gm = gmtime(shift); + $min = GetTimeZone($min, \@gm); + } + my $sign = '+'; + $min < 0 and $sign = '-', $min = -$min; + $min = int($min + 0.5); # round off to nearest minute + my $h = int($min / 60); + return sprintf('%s%.2d:%.2d', $sign, $h, $min - $h * 60); +} + +#------------------------------------------------------------------------------ +# Convert Unix time to EXIF date/time string +# Inputs: 0) Unix time value, 1) non-zero to convert to local time, +# 2) number of digits after the decimal for fractional seconds +# Returns: EXIF date/time string (with timezone for local times) +sub ConvertUnixTime($;$$) +{ + my ($time, $toLocal, $dec) = @_; + return '0000:00:00 00:00:00' if $time == 0; + my (@tm, $tz); + if ($dec) { + my $frac = $time - int($time); + $time = int($time); + $frac < 0 and $frac += 1, $time -= 1; + $dec = sprintf('%.*f', $dec, $frac); + # remove number before decimal and increment integer time if it was rounded up + $dec =~ s/^(\d)// and $1 eq '1' and $time += 1; + } else { + $time = int($time + 1e-6) if $time != int($time); # avoid round-off errors + $dec = ''; + } + if ($toLocal) { + @tm = localtime($time); + $tz = TimeZoneString(\@tm, $time); + } else { + @tm = gmtime($time); + $tz = ''; + } + my $str = sprintf("%4d:%.2d:%.2d %.2d:%.2d:%.2d$dec%s", + $tm[5]+1900, $tm[4]+1, $tm[3], $tm[2], $tm[1], $tm[0], $tz); + return $str; +} + +#------------------------------------------------------------------------------ +# Get Unix time from EXIF-formatted date/time string with optional timezone +# Inputs: 0) EXIF date/time string, 1) non-zero if time is local, or 2 to assume UTC +# Returns: Unix time (seconds since 0:00 GMT Jan 1, 1970) or undefined on error +sub GetUnixTime($;$) +{ + my ($timeStr, $isLocal) = @_; + return 0 if $timeStr eq '0000:00:00 00:00:00'; + my @tm = ($timeStr =~ /^(\d+)[-:](\d+)[-:](\d+)\s+(\d+):(\d+):(\d+)(.*)/); + return undef unless @tm == 7; + unless (eval { require Time::Local }) { + warn "Time::Local is not installed\n"; + return undef; + } + my ($tzStr, $tzSec) = (pop(@tm), 0); + # use specified timezone offset (if given) instead of local system time + # if we are converting a local time value + if ($isLocal) { + if ($tzStr =~ /(?:Z|([-+])(\d+):(\d+))/i) { + # use specified timezone if one exists + $tzSec = ($2 * 60 + $3) * ($1 eq '-' ? -60 : 60) if $1; + undef $isLocal; # convert using GMT corrected for specified timezone + } elsif ($isLocal eq '2') { + undef $isLocal; + } + } + $tm[1] -= 1; # convert month + @tm = reverse @tm; # change to order required by timelocal() + my $val = $isLocal ? TimeLocal(@tm) : Time::Local::timegm(@tm) - $tzSec; + # handle fractional seconds + $val += $1 if $tzStr and $tzStr =~ /^(\.\d+)/; + return $val; +} + +#------------------------------------------------------------------------------ +# Print conversion for file size +# Inputs: 0) file size in bytes +# Returns: converted file size +sub ConvertFileSize($) +{ + my $val = shift; + $val < 2000 and return "$val bytes"; + $val < 10000 and return sprintf('%.1f kB', $val / 1000); + $val < 2000000 and return sprintf('%.0f kB', $val / 1000); + $val < 10000000 and return sprintf('%.1f MB', $val / 1000000); + $val < 2000000000 and return sprintf('%.0f MB', $val / 1000000); + $val < 10000000000 and return sprintf('%.1f GB', $val / 1000000000); + return sprintf('%.0f GB', $val / 1000000000); +} + +#------------------------------------------------------------------------------ +# Convert seconds to duration string (handles negative durations) +# Inputs: 0) floating point seconds +# Returns: duration string in form "S.SS s", "H:MM:SS" or "DD days HH:MM:SS" +sub ConvertDuration($) +{ + my $time = shift; + return $time unless IsFloat($time); + return '0 s' if $time == 0; + my $sign = ($time > 0 ? '' : (($time = -$time), '-')); + return sprintf("$sign%.2f s", $time) if $time < 30; + $time += 0.5; # to round off to nearest second + my $h = int($time / 3600); + $time -= $h * 3600; + my $m = int($time / 60); + $time -= $m * 60; + if ($h > 24) { + my $d = int($h / 24); + $h -= $d * 24; + $sign = "$sign$d days "; + } + return sprintf("$sign%d:%.2d:%.2d", $h, $m, int($time)); +} + +#------------------------------------------------------------------------------ +# Print conversion for bitrate values +# Inputs: 0) bitrate in bits per second +# Returns: human-readable bitrate string +# Notes: returns input value without formatting if it isn't numerical +sub ConvertBitrate($) +{ + my $bitrate = shift; + IsFloat($bitrate) or return $bitrate; + my @units = ('bps', 'kbps', 'Mbps', 'Gbps'); + for (;;) { + my $units = shift @units; + $bitrate >= 1000 and @units and $bitrate /= 1000, next; + my $fmt = $bitrate < 100 ? '%.3g' : '%.0f'; + return sprintf("$fmt $units", $bitrate); + } +} + +#------------------------------------------------------------------------------ +# Convert file name for printing +# Inputs: 0) ExifTool ref, 1) file name in CharsetFileName character set +# Returns: converted file name in external character set +sub ConvertFileName($$) +{ + my ($self, $val) = @_; + my $enc = $$self{OPTIONS}{CharsetFileName}; + $val = $self->Decode($val, $enc) if $enc; + return $val; +} + +#------------------------------------------------------------------------------ +# Inverse conversion for file name (encode in CharsetFileName) +# Inputs: 0) ExifTool ref, 1) file name in external character set +# Returns: file name in CharsetFileName character set +sub InverseFileName($$) +{ + my ($self, $val) = @_; + my $enc = $$self{OPTIONS}{CharsetFileName}; + $val = $self->Encode($val, $enc) if $enc; + $val =~ tr/\\/\//; # make sure we are using forward slashes + return $val; +} + +#------------------------------------------------------------------------------ +# Save information for HTML dump +# Inputs: 0) ExifTool hash ref, 1) start offset, 2) data size +# 3) comment string, 4) tool tip (or SAME), 5) flags, 6) IFD name +sub HDump($$$$;$$$) +{ + my $self = shift; + $$self{HTML_DUMP} or return; + my ($pos, $len, $com, $tip, $flg, $ifd) = @_; + $pos += $$self{BASE} if $$self{BASE}; + # skip structural data blocks which have been removed from the middle of this dump + # (SkipData list contains ordered [start,end+1] offsets to skip) + if ($$self{SkipData}) { + my $end = $pos + $len; + my $skip; + foreach $skip (@{$$self{SkipData}}) { + $end <= $$skip[0] and last; + $pos >= $$skip[1] and $pos += $$skip[1] - $$skip[0], next; + if ($pos != $$skip[0]) { + $$self{HTML_DUMP}->Add($pos, $$skip[0]-$pos, $com, $tip, $flg, $ifd); + $len -= $$skip[0] - $pos; + $tip = 'SAME'; + } + $pos = $$skip[1]; + } + } + $$self{HTML_DUMP}->Add($pos, $len, $com, $tip, $flg, $ifd); +} + +#------------------------------------------------------------------------------ +# Identify trailer ending at specified offset from end of file +# Inputs: 0) RAF reference, 1) offset from end of file (0 by default) +# Returns: Trailer info hash (with RAF and DirName set), +# or undef if no recognized trailer was found +# Notes: leaves file position unchanged +sub IdentifyTrailer($;$) +{ + my $raf = shift; + my $offset = shift || 0; + my $pos = $raf->Tell(); + my ($buff, $type, $len); + while ($raf->Seek(-$offset, 2) and ($len = $raf->Tell()) > 0) { + # read up to 64 bytes before specified offset from end of file + $len = 64 if $len > 64; + $raf->Seek(-$len, 1) and $raf->Read($buff, $len) == $len or last; + if ($buff =~ /AXS(!|\*).{8}$/s) { + $type = 'AFCP'; + } elsif ($buff =~ /\xa1\xb2\xc3\xd4$/) { + $type = 'FotoStation'; + } elsif ($buff =~ /cbipcbbl$/) { + $type = 'PhotoMechanic'; + } elsif ($buff =~ /^CANON OPTIONAL DATA\0/) { + $type = 'CanonVRD'; + } elsif ($buff =~ /~\0\x04\0zmie~\0\0\x06.{4}[\x10\x18]\x04$/s or + $buff =~ /~\0\x04\0zmie~\0\0\x0a.{8}[\x10\x18]\x08$/s) + { + $type = 'MIE'; + } elsif ($buff =~ /\0\0(QDIOBS|SEFT)$/) { + $type = 'Samsung'; + } elsif ($buff =~ /8db42d694ccc418790edff439fe026bf$/s) { + $type = 'Insta360'; + } elsif ($buff =~ m(\0{6}/NIKON APP$)) { + $type = 'NikonApp'; + } + last; + } + $raf->Seek($pos, 0); # restore original file position + return $type ? { RAF => $raf, DirName => $type } : undef; +} + +#------------------------------------------------------------------------------ +# Read/rewrite trailer information (including multiple trailers) +# Inputs: 0) ExifTool object ref, 1) DirInfo ref: +# - requires RAF and DirName +# - OutFile is a scalar reference for writing +# - scans from current file position if ScanForAFCP is set +# Returns: 1 if trailer was processed or couldn't be processed (or written OK) +# 0 if trailer was recognized but offsets need fixing (or write error) +# - DirName, DirLen, DataPos, Offset, Fixup and OutFile are updated +# - preserves current file position and byte order +sub ProcessTrailers($$) +{ + my ($self, $dirInfo) = @_; + my $dirName = $$dirInfo{DirName}; + my $outfile = $$dirInfo{OutFile}; + my $offset = $$dirInfo{Offset} || 0; + my $fixup = $$dirInfo{Fixup}; + my $raf = $$dirInfo{RAF}; + my $pos = $raf->Tell(); + my $byteOrder = GetByteOrder(); + my $success = 1; + my $path = $$self{PATH}; + + for (;;) { # loop through all trailers + my ($proc, $outBuff); + if ($dirName eq 'Insta360') { + require 'Image/ExifTool/QuickTimeStream.pl'; + $proc = 'Image::ExifTool::QuickTime::ProcessInsta360'; + } elsif ($dirName eq 'NikonApp') { + require Image::ExifTool::Nikon; + $proc = 'Image::ExifTool::Nikon::ProcessNikonApp'; + } else { + require "Image/ExifTool/$dirName.pm"; + $proc = "Image::ExifTool::${dirName}::Process$dirName"; + } + if ($outfile) { + # write to local buffer so we can add trailer in proper order later + $$outfile and $$dirInfo{OutFile} = \$outBuff, $outBuff = ''; + # must generate new fixup if necessary so we can shift + # the old fixup separately after we prepend this trailer + delete $$dirInfo{Fixup}; + } + delete $$dirInfo{DirLen}; # reset trailer length + $$dirInfo{Offset} = $offset; # set offset from end of file + $$dirInfo{Trailer} = 1; # set Trailer flag in case proc cares + # add trailer and DirName to SubDirectory PATH + push @$path, 'Trailer', $dirName; + + # read or write this trailer + # (proc takes Offset as positive offset from end of trailer to end of file, + # and returns DataPos and DirLen, and Fixup if applicable, and updates + # OutFile when writing) + no strict 'refs'; + my $result = &$proc($self, $dirInfo); + use strict 'refs'; + + # restore PATH (pop last 2 items) + splice @$path, -2; + + # check result + if ($outfile) { + if ($result > 0) { + if ($outBuff) { + # write trailers to OutFile in original order + $$outfile = $outBuff . $$outfile; + # must adjust old fixup start if it exists + $$fixup{Start} += length($outBuff) if $fixup; + $outBuff = ''; # free memory + } + if ($$dirInfo{Fixup}) { + if ($fixup) { + # add fixup for subsequent trailers to the fixup for this trailer + # (but first we must adjust for the new start position) + $$fixup{Shift} += $$dirInfo{Fixup}{Start}; + $$fixup{Start} -= $$dirInfo{Fixup}{Start}; + $$dirInfo{Fixup}->AddFixup($fixup); + } + $fixup = $$dirInfo{Fixup}; # save fixup + } + } else { + $success = 0 if $self->Error("Error rewriting $dirName trailer", 2); + last; + } + } elsif ($result < 0) { + # can't continue if we must scan for this trailer + $success = 0; + last; + } + last unless $result > 0 and $$dirInfo{DirLen}; + # look for next trailer + $offset += $$dirInfo{DirLen}; + my $nextTrail = IdentifyTrailer($raf, $offset) or last; + $dirName = $$dirInfo{DirName} = $$nextTrail{DirName}; + $raf->Seek($pos, 0); + } + SetByteOrder($byteOrder); # restore original byte order + $raf->Seek($pos, 0); # restore original file position + $$dirInfo{OutFile} = $outfile; # restore original outfile + $$dirInfo{Offset} = $offset; # return offset from EOF to start of first trailer + $$dirInfo{Fixup} = $fixup; # return fixup information + return $success; +} + +#------------------------------------------------------------------------------ +# JPEG constants + +# JPEG marker names +%jpegMarker = ( + 0x00 => 'NULL', + 0x01 => 'TEM', + 0xc0 => 'SOF0', # to SOF15, with a few exceptions below + 0xc4 => 'DHT', + 0xc8 => 'JPGA', + 0xcc => 'DAC', + 0xd0 => 'RST0', # to RST7 + 0xd8 => 'SOI', + 0xd9 => 'EOI', + 0xda => 'SOS', + 0xdb => 'DQT', + 0xdc => 'DNL', + 0xdd => 'DRI', + 0xde => 'DHP', + 0xdf => 'EXP', + 0xe0 => 'APP0', # to APP15 + 0xf0 => 'JPG0', + 0xfe => 'COM', +); + +# lookup for size of JPEG marker length word +# (2 bytes assumed unless specified here) +my %markerLenBytes = ( + 0x00 => 0, 0x01 => 0, + 0xd0 => 0, 0xd1 => 0, 0xd2 => 0, 0xd3 => 0, 0xd4 => 0, 0xd5 => 0, 0xd6 => 0, 0xd7 => 0, + 0xd8 => 0, 0xd9 => 0, 0xda => 0, + # J2C + 0x30 => 0, 0x31 => 0, 0x32 => 0, 0x33 => 0, 0x34 => 0, 0x35 => 0, 0x36 => 0, 0x37 => 0, + 0x38 => 0, 0x39 => 0, 0x3a => 0, 0x3b => 0, 0x3c => 0, 0x3d => 0, 0x3e => 0, 0x3f => 0, + 0x4f => 0, + 0x92 => 0, 0x93 => 0, + # J2C extensions + 0x74 => 4, 0x75 => 4, 0x77 => 4, +); + +#------------------------------------------------------------------------------ +# Get JPEG marker name +# Inputs: 0) Jpeg number +# Returns: marker name +sub JpegMarkerName($) +{ + my $marker = shift; + my $markerName = $jpegMarker{$marker}; + unless ($markerName) { + $markerName = $jpegMarker{$marker & 0xf0}; + if ($markerName and $markerName =~ /^([A-Z]+)\d+$/) { + $markerName = $1 . ($marker & 0x0f); + } else { + $markerName = sprintf("marker 0x%.2x", $marker); + } + } + return $markerName; +} + +#------------------------------------------------------------------------------ +# Adjust directory start position +# Inputs: 0) dirInfo ref, 1) start offset +# 2) Base for offsets (relative to DataPos, defaults to absolute Base of 0) +sub DirStart($$;$) +{ + my ($dirInfo, $start, $base) = @_; + $$dirInfo{DirStart} = $start; + $$dirInfo{DirLen} -= $start; + if (defined $base) { + $$dirInfo{Base} = $$dirInfo{DataPos} + $base; + $$dirInfo{DataPos} = -$base; # (relative to Base!) + } +} + +#------------------------------------------------------------------------------ +# Extract metadata from a jpg image +# Inputs: 0) ExifTool object reference, 1) dirInfo ref with RAF set +# Returns: 1 on success, 0 if this wasn't a valid JPEG file +sub ProcessJPEG($$) +{ + local $_; + my ($self, $dirInfo) = @_; + my $options = $$self{OPTIONS}; + my $verbose = $$options{Verbose}; + my $out = $$options{TextOut}; + my $fast = $$options{FastScan} || 0; + my $raf = $$dirInfo{RAF}; + my $req = $$self{REQ_TAG_LOOKUP}; + my $htmlDump = $$self{HTML_DUMP}; + my %dumpParms = ( Out => $out ); + my ($ch, $s, $length, $hash, $hashsize); + my ($success, $wantTrailer, $trailInfo, $foundSOS, $gotSize, %jumbfChunk); + my (@iccChunk, $iccChunkCount, $iccChunksTotal, @flirChunk, $flirCount, $flirTotal); + my ($preview, $scalado, @dqt, $subSampling, $dumpEnd, %extendedXMP); + + # get pointer to hash object if it exists and we are the top-level JPEG or JP2 + if ($$self{FILE_TYPE} =~ /^(JPEG|JP2)$/ and not $$self{DOC_NUM}) { + $hash = $$self{ImageDataHash}; + $hashsize = 0; + } + + # check to be sure this is a valid JPG (or J2C, or EXV) file + return 0 unless $raf->Read($s, 2) == 2 and $s =~ /^\xff[\xd8\x4f\x01]/; + if ($s eq "\xff\x01") { + return 0 unless $raf->Read($s, 5) == 5 and $s eq 'Exiv2'; + $$self{FILE_TYPE} = 'EXV'; + } + my $appBytes = 0; + my $calcImageLen = $$req{jpegimagelength}; + if ($$options{RequestAll} and $$options{RequestAll} > 2) { + $calcImageLen = 1; + } + if (not $$self{VALUE}{FileType} or ($$self{DOC_NUM} and $$options{ExtractEmbedded})) { + $self->SetFileType(); # set FileType tag + return 1 if $fast == 3; # don't process file when FastScan == 3 + $$self{LOW_PRIORITY_DIR}{IFD1} = 1; # lower priority of IFD1 tags + } + $$raf{NoBuffer} = 1 if $self->Options('FastScan'); # disable buffering in FastScan mode + + $dumpParms{MaxLen} = 128 if $verbose < 4; + if ($htmlDump) { + $dumpEnd = $raf->Tell(); + my ($n, $t, $m) = $s eq 'Exiv2' ? (7,'EXV','TEM') : (2,'JPEG','SOI'); + my $pos = $dumpEnd - $n; + $self->HDump(0, $pos, '[unknown header]') if $pos; + $self->HDump($pos, $n, "$t header", "$m Marker"); + } + my $path = $$self{PATH}; + my $pn = scalar @$path; + + # set input record separator to 0xff (the JPEG marker) to make reading quicker + local $/ = "\xff"; + + my ($nextMarker, $nextSegDataPt, $nextSegPos, $combinedSegData, $firstSegPos, @skipData); + + # read file until we reach an end of image (EOI) or start of scan (SOS) + Marker: for (;;) { + # set marker and data pointer for current segment + my $marker = $nextMarker; + my $segDataPt = $nextSegDataPt; + my $segPos = $nextSegPos; + my $skipped; + undef $nextMarker; + undef $nextSegDataPt; +# +# read ahead to the next segment unless we have reached EOI, SOS or SOD +# + unless ($marker and ($marker==0xd9 or ($marker==0xda and not $wantTrailer and not $hash) or + $marker==0x93)) + { + # read up to next marker (JPEG markers begin with 0xff) + my $buff; + $raf->ReadLine($buff) or last; + $skipped = length($buff) - 1; + # JPEG markers can be padded with unlimited 0xff's + for (;;) { + $raf->Read($ch, 1) or last Marker; + $nextMarker = ord($ch); + last unless $nextMarker == 0xff; + ++$skipped; + } + # read segment data if it exists + if (not defined $markerLenBytes{$nextMarker}) { + # read record length word + last unless $raf->Read($s, 2) == 2; + my $len = unpack('n',$s); # get data length + last unless defined($len) and $len >= 2; + $nextSegPos = $raf->Tell(); + $len -= 2; # subtract size of length word + last unless $raf->Read($buff, $len) == $len; + $nextSegDataPt = \$buff; # set pointer to our next data + } elsif ($markerLenBytes{$nextMarker} == 4) { + # handle J2C extensions with 4-byte length word + last unless $raf->Read($s, 4) == 4; + my $len = unpack('N',$s); # get data length + last unless defined($len) and $len >= 4; + $nextSegPos = $raf->Tell(); + $len -= 4; # subtract size of length word + last unless $raf->Seek($len, 1); + } elsif ($hash and defined $marker and ($marker == 0x00 or $marker == 0xda or + ($marker >= 0xd0 and $marker <= 0xd7))) + { + # calculate hash for image data (includes leading ff d9 but not trailing ff da) + $hash->add("\xff" . chr($marker)); + my $n = $skipped - (length($buff) - 1); # number of extra 0xff's + if (not $n) { + $buff = substr($buff, 0, -1); # remove trailing 0xff + } elsif ($n > 1) { + $buff .= "\xff" x ($n - 1); # add back extra 0xff's + } + $hash->add($buff); + $hashsize += $skipped + 2; + } + # read second segment too if this was the first + next unless defined $marker; + } + # set some useful variables for the current segment + my $markerName = JpegMarkerName($marker); + $$path[$pn] = $markerName; + # issue warning if we skipped some garbage + if ($skipped and not $foundSOS and $markerName ne 'SOS') { + $self->Warn("Skipped unknown $skipped bytes after JPEG $markerName segment", 1); + if ($htmlDump) { + $self->HDump($nextSegPos-4-$skipped, $skipped, "[unknown $skipped bytes]", undef, 0x08); + $dumpEnd = $nextSegPos - 4; + } + } +# +# parse the current segment +# + # handle SOF markers: SOF0-SOF15, except DHT(0xc4), JPGA(0xc8) and DAC(0xcc) + if (($marker & 0xf0) == 0xc0 and ($marker == 0xc0 or $marker & 0x03)) { + $length = length $$segDataPt; + if ($verbose) { + print $out "JPEG $markerName ($length bytes):\n"; + HexDump($segDataPt, undef, %dumpParms, Addr=>$segPos) if $verbose>2; + } elsif ($htmlDump) { + $self->HDump($segPos-4, $length+4, "[JPEG $markerName]", undef, 0x08); + $dumpEnd = $segPos + $length; + } + next if $length < 6 or $gotSize; + $gotSize = 1; # (ignore subsequent SOF segments in probably corrupted JPEG) + # extract some useful information + my ($p, $h, $w, $n) = unpack('Cn2C', $$segDataPt); + my $sof = GetTagTable('Image::ExifTool::JPEG::SOF'); + $self->HandleTag($sof, 'ImageWidth', $w); + $self->HandleTag($sof, 'ImageHeight', $h); + $self->HandleTag($sof, 'EncodingProcess', $marker - 0xc0); + $self->HandleTag($sof, 'BitsPerSample', $p); + $self->HandleTag($sof, 'ColorComponents', $n); + next unless $n == 3 and $length >= 15; + my ($i, $hmin, $hmax, $vmin, $vmax); + # loop through all components to determine sampling frequency + $subSampling = ''; + for ($i=0; $i<$n; ++$i) { + my $sf = Get8u($segDataPt, 7 + 3 * $i); + $subSampling .= sprintf('%.2x', $sf); + # isolate horizontal and vertical components + my ($hf, $vf) = ($sf >> 4, $sf & 0x0f); + unless ($i) { + $hmin = $hmax = $hf; + $vmin = $vmax = $vf; + next; + } + # determine min/max frequencies + $hmin = $hf if $hf < $hmin; + $hmax = $hf if $hf > $hmax; + $vmin = $vf if $vf < $vmin; + $vmax = $vf if $vf > $vmax; + } + if ($hmin and $vmin) { + my ($hs, $vs) = ($hmax / $hmin, $vmax / $vmin); + $self->HandleTag($sof, 'YCbCrSubSampling', "$hs $vs"); + } + next; + } elsif ($marker == 0xd9) { # EOI + pop @$path; + $verbose and print $out "JPEG EOI\n"; + my $pos = $raf->Tell(); + if ($htmlDump and $dumpEnd) { + $self->HDump($dumpEnd, $pos-2-$dumpEnd, '[JPEG Image Data]', undef, 0x08); + $self->HDump($pos-2, 2, 'JPEG EOI', undef); + $dumpEnd = 0; + } + if ($foundSOS or $$self{FILE_TYPE} eq 'EXV') { + $success = 1; + } else { + $self->Warn('Missing JPEG SOS'); + } + if ($$req{trailer}) { + # read entire trailer into memory + if ($raf->Seek(0,2)) { + my $len = $raf->Tell() - $pos; + if ($len) { + my $buff; + $raf->Seek($pos, 0); + $self->FoundTag(Trailer => \$buff) if $raf->Read($buff,$len) == $len; + $raf->Seek($pos, 0); + } + } else { + $self->Warn('Error seeking in file'); + } + } + # we are here because we are looking for trailer information + if ($wantTrailer) { + my $start = $$self{PreviewImageStart}; + if ($start or $$options{ExtractEmbedded}) { + my $buff; + # most previews start right after the JPEG EOI, but the Olympus E-20 + # preview is 508 bytes into the trailer, the K-M Maxxum 7D preview is + # 979 bytes in, and Sony previews can start up to 32 kB into the trailer. + # (and Minolta and Sony previews can have a random first byte...) + my $scanLen = $$self{Make} =~ /Sony/i ? 65536 : 1024; + if ($raf->Read($buff, $scanLen)) { + if ($buff =~ /^.{4}ftyp/s) { + my $val; + if ($raf->Seek(0,2)) { + my $len = $raf->Tell() - $pos; + if ($$options{Binary}) { + $val = \$buff if $raf->Seek($pos,0) and $raf->Read($buff,$len)==$len; + } else { + $val = \ "Binary data $len bytes"; + } + if ($val) { + $self->FoundTag('EmbeddedVideo', $val); + } else { + $self->Warn('Error reading trailer'); + } + } else { + $self->Warn('Error seeking to end of file'); + } + } elsif ($buff =~ /\xff\xd8\xff./g or + ($$self{Make} =~ /(Minolta|Sony)/i and $buff =~ /.\xd8\xff\xdb/g)) + { + # adjust PreviewImageStart to this location + my $actual = $pos + pos($buff) - 4; + if ($start and $start ne $actual and $verbose > 1) { + print $out "(Fixed PreviewImage location: $start -> $actual)\n"; + } + # update preview image offsets + if ($start) { + $$self{VALUE}{PreviewImageStart} = $actual if $$self{VALUE}{PreviewImageStart}; + $$self{PreviewImageStart} = $actual; + } + # load preview now if we tried and failed earlier + if ($$self{PreviewError} and $$self{PreviewImageLength}) { + if ($raf->Seek($actual, 0) and $raf->Read($buff, $$self{PreviewImageLength})) { + $self->FoundTag('PreviewImage', $buff); + delete $$self{PreviewError}; + } + } + } + } + $raf->Seek($pos, 0); + } + } + # process trailer now or finish processing trailers + # and scan for AFCP if necessary + my $fromEnd = 0; + if ($trailInfo) { + $$trailInfo{ScanForAFCP} = 1; # scan now if necessary + $self->ProcessTrailers($trailInfo); + # save offset from end of file to start of first trailer + $fromEnd = $$trailInfo{Offset}; + undef $trailInfo; + } + if ($$self{LeicaTrailer}) { + $raf->Seek(0, 2); + $$self{LeicaTrailer}{TrailPos} = $pos; + $$self{LeicaTrailer}{TrailLen} = $raf->Tell() - $pos - $fromEnd; + Image::ExifTool::Panasonic::ProcessLeicaTrailer($self); + } + # finally, dump remaining information in JPEG trailer + if ($verbose or $htmlDump) { + my $endPos = $$self{LeicaTrailerPos}; + unless ($endPos) { + $raf->Seek(0, 2); + $endPos = $raf->Tell() - $fromEnd; + } + $self->DumpUnknownTrailer({ + RAF => $raf, + DataPos => $pos, + DirLen => $endPos - $pos + }) if $endPos > $pos; + } + $self->FoundTag('JPEGImageLength', $pos - $appBytes) if $calcImageLen; + last; # all done parsing file + } elsif ($marker == 0xda) { # SOS + pop @$path; + $foundSOS = 1; + # all done with meta information unless we have a trailer + $verbose and print $out "JPEG SOS\n"; + unless ($fast) { + $trailInfo = IdentifyTrailer($raf); + # process trailer now unless we are doing verbose dump + if ($trailInfo and $verbose < 3 and not $htmlDump) { + # process trailers (keep trailInfo to finish processing later + # only if we can't finish without scanning from end of file) + $self->ProcessTrailers($trailInfo) and undef $trailInfo; + } + if ($wantTrailer and $$self{PreviewImageStart}) { + # seek ahead and validate preview image + my $buff; + my $curPos = $raf->Tell(); + if ($raf->Seek($$self{PreviewImageStart}, 0) and + $raf->Read($buff, 4) == 4 and + $buff =~ /^.\xd8\xff[\xc4\xdb\xe0-\xef]/) + { + undef $wantTrailer; + } + $raf->Seek($curPos, 0) or last; + } + # seek ahead and process Leica trailer + if ($$self{LeicaTrailer}) { + require Image::ExifTool::Panasonic; + Image::ExifTool::Panasonic::ProcessLeicaTrailer($self); + $wantTrailer = 1 if $$self{LeicaTrailer}; + } else { + $wantTrailer = 1 if $$options{ExtractEmbedded}; + } + next if $trailInfo or $wantTrailer or $verbose > 2 or $htmlDump; + } + # must scan to EOI if Validate or JpegCompressionFactor used + next if $$options{Validate} or $calcImageLen or $$req{trailer} or $hash; + # nothing interesting to parse after start of scan (SOS) + $success = 1; + last; # all done parsing file + } elsif ($marker == 0x93) { + pop @$path; + $verbose and print $out "JPEG SOD\n"; + $success = 1; + if ($hash and $$self{FILE_TYPE} eq 'JP2') { + my $pos = $raf->Tell(); + $self->ImageDataHash($raf, undef, 'SOD'); + $raf->Seek($pos, 0); + } + next if $verbose > 2 or $htmlDump; + last; # all done parsing file + } elsif (defined $markerLenBytes{$marker}) { + # handle other stand-alone markers and segments we skipped over + $verbose and $marker and print $out "JPEG $markerName\n"; + next; + } elsif ($marker == 0xdb and length($$segDataPt) and # DQT + # save the DQT data only if JPEGDigest has been requested + # (Note: since we aren't checking the API RequestAll option here, the application + # must use the RequestTags option to generate these tags if they have not been + # specifically requested. The reason is that there is too much overhead involved + # in the calculation of this tag to make this worth the CPU time.) + ($$req{jpegdigest} or $$req{jpegqualityestimate} + or ($$options{RequestAll} and $$options{RequestAll} > 2))) + { + my $num = unpack('C',$$segDataPt) & 0x0f; # get table index + $dqt[$num] = $$segDataPt if $num < 4; # save for hash calculation + } + # handle all other markers + my $dumpType = ''; + my ($desc, $tip, $xtra); + $length = length $$segDataPt; + $appBytes += $length + 4 if ($marker & 0xf0) == 0xe0; # total size of APP segments + if ($verbose) { + print $out "JPEG $markerName ($length bytes):\n"; + if ($verbose > 2) { + my %extraParms = ( Addr => $segPos ); + $extraParms{MaxLen} = 128 if $verbose == 4; + HexDump($segDataPt, undef, %dumpParms, %extraParms); + } + } + # prepare dirInfo hash for processing this information + my %dirInfo = ( + Parent => $markerName, + DataPt => $segDataPt, + DataPos => $segPos, + DataLen => $length, + DirStart => 0, + DirLen => $length, + Base => 0, + ); + if ($marker == 0xe0) { # APP0 (JFIF, JFXX, CIFF, AVI1, Ocad) + if ($$segDataPt =~ /^JFIF\0/) { + $dumpType = 'JFIF'; + DirStart(\%dirInfo, 5); # start at byte 5 + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::JFIF::Main'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^JFXX\0(\x10|\x11|\x13)/) { + my $tag = ord $1; + $dumpType = 'JFXX'; + my $tagTablePtr = GetTagTable('Image::ExifTool::JFIF::Extension'); + my $tagInfo = $self->GetTagInfo($tagTablePtr, $tag); + $self->FoundTag($tagInfo, substr($$segDataPt, 6)); + } elsif ($$segDataPt =~ /^(II|MM).{4}HEAPJPGM/s) { + next if $fast > 1; # skip processing for very fast + $dumpType = 'CIFF'; + my %dirInfo = ( RAF => new File::RandomAccess($segDataPt) ); + $$self{SET_GROUP1} = 'CIFF'; + push @{$$self{PATH}}, 'CIFF'; + require Image::ExifTool::CanonRaw; + Image::ExifTool::CanonRaw::ProcessCRW($self, \%dirInfo); + pop @{$$self{PATH}}; + delete $$self{SET_GROUP1}; + } elsif ($$segDataPt =~ /^(AVI1|Ocad)/) { + $dumpType = $1; + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable("Image::ExifTool::JPEG::$dumpType"); + DirStart(\%dirInfo, 4); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } elsif ($marker == 0xe1) { # APP1 (EXIF, XMP, QVCI, PARROT) + # (some Kodak cameras don't put a second "\0", and I have seen an + # example where there was a second 4-byte APP1 segment header) + if ($$segDataPt =~ /^(.{0,4})Exif\0./is) { + undef $dumpType; # (will be dumped here) + # this is EXIF data -- + # get the data block (into a common variable) + my $hdrLen = length($exifAPP1hdr); + if (length $1) { + $hdrLen += length $1; + $self->Warn('Unknown garbage at start of EXIF segment',1); + } elsif ($$segDataPt !~ /^Exif\0/) { + $self->Warn('Incorrect EXIF segment identifier',1); + } + if ($htmlDump) { + $self->HDump($segPos-4, 4, 'APP1 header', "Data size: $length bytes"); + $self->HDump($segPos, $hdrLen, 'Exif header', 'APP1 data type: Exif'); + $dumpEnd = $segPos + $length; + } + my $dataPt = $segDataPt; + if (defined $combinedSegData) { + push @skipData, [ $segPos-4, $segPos+$hdrLen ]; + $combinedSegData .= substr($$segDataPt,$hdrLen); + undef $$segDataPt; + $dataPt = \$combinedSegData; + $segPos = $firstSegPos; + } + # peek ahead to see if the next segment is extended EXIF + if ($nextMarker == $marker and + $$nextSegDataPt =~ /^$exifAPP1hdr(?!(MM\0\x2a|II\x2a\0))/) + { + # initialize combined data if necessary + unless (defined $combinedSegData) { + $combinedSegData = $$segDataPt; + undef $$segDataPt; + $firstSegPos = $segPos; + $self->Warn('File contains multi-segment EXIF',1); + $$self{ExtendedEXIF} = 1; + } + next; + } + $dirInfo{DataPt} = $dataPt; + $dirInfo{DataPos} = $segPos; + $dirInfo{DataLen} = $dirInfo{DirLen} = length $$dataPt; + DirStart(\%dirInfo, $hdrLen, $hdrLen); + $$self{SkipData} = \@skipData if @skipData; + # extract the EXIF information (it is in standard TIFF format) + $self->ProcessTIFF(\%dirInfo) or $self->Warn('Malformed APP1 EXIF segment'); + # avoid looking for preview unless necessary because it really slows + # us down -- only look for it if we found pointer, and preview is + # outside EXIF, and PreviewImage is specifically requested + my $start = $self->GetValue('PreviewImageStart', 'ValueConv'); + my $plen = $self->GetValue('PreviewImageLength', 'ValueConv'); + if (not $start or not $plen and $$self{PreviewError}) { + $start = $$self{PreviewImageStart}; + $plen = $$self{PreviewImageLength}; + } + if ($start and $plen and IsInt($start) and IsInt($plen) and + $start + $plen > $$self{EXIF_POS} + length($$self{EXIF_DATA}) and + ($$req{previewimage} or + # (extracted normally, so check Binary option) + ($$options{Binary} and not $$self{EXCL_TAG_LOOKUP}{previewimage}))) + { + $$self{PreviewImageStart} = $start; + $$self{PreviewImageLength} = $plen; + $wantTrailer = 1; + } + if (@skipData) { + undef @skipData; + delete $$self{SkipData}; + } + undef $$dataPt; + next; + } elsif ($$segDataPt =~ /^$xmpExtAPP1hdr/) { + # off len -- extended XMP header (75 bytes total): + # 0 35 bytes - signature + # 35 32 bytes - GUID (MD5 hash of full extended XMP data in ASCII) + # 67 4 bytes - total size of extended XMP data + # 71 4 bytes - offset for this XMP data portion + $dumpType = 'Extended XMP'; + if ($length > 75) { + my ($size, $off) = unpack('x67N2', $$segDataPt); + my $guid = substr($$segDataPt, 35, 32); + if ($guid =~ /[^A-Za-z0-9]/) { # (technically, should be uppercase) + $self->WarnOnce($tip = 'Invalid extended XMP GUID'); + } else { + my $extXMP = $extendedXMP{$guid}; + if (not $extXMP) { + $extXMP = $extendedXMP{$guid} = { }; + } elsif ($size != $$extXMP{Size}) { + $self->WarnOnce('Inconsistent extended XMP size'); + } + $$extXMP{Size} = $size; + $$extXMP{$off} = substr($$segDataPt, 75); + $tip = "Full length: $size\nChunk offset: $off\nChunk length: " . + ($length - 75) . "\nGUID: $guid"; + # (delay processing extended XMP until after reading all segments) + } + } else { + $self->WarnOnce($tip = 'Invalid extended XMP segment'); + } + } elsif ($$segDataPt =~ /^QVCI\0/) { + $dumpType = 'QVCI'; + my $tagTablePtr = GetTagTable('Image::ExifTool::Casio::QVCI'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^FLIR\0/ and $length >= 8) { + $dumpType = 'FLIR'; + # must concatenate FLIR chunks (note: handle the case where + # some software erroneously writes zeros for the chunk counts) + my $chunkNum = Get8u($segDataPt, 6); + my $chunksTot = Get8u($segDataPt, 7) + 1; # (note the "+ 1"!) + $verbose and printf $out "$$self{INDENT}FLIR chunk %d of %d\n", + $chunkNum + 1, $chunksTot; + if (defined $flirTotal) { + # abort parsing FLIR if the total chunk count is inconsistent + undef $flirCount if $chunksTot != $flirTotal; + } else { + $flirCount = 0; + $flirTotal = $chunksTot; + } + if (defined $flirCount) { + if (defined $flirChunk[$chunkNum]) { + $self->WarnOnce('Duplicate FLIR chunk number(s)'); + $flirChunk[$chunkNum] .= substr($$segDataPt, 8); + } else { + $flirChunk[$chunkNum] = substr($$segDataPt, 8); + } + # process the FLIR information if we have all of the chunks + if (++$flirCount >= $flirTotal) { + my $flir = ''; + defined $_ and $flir .= $_ foreach @flirChunk; + undef @flirChunk; # free memory + my $tagTablePtr = GetTagTable('Image::ExifTool::FLIR::FFF'); + my %dirInfo = ( + DataPt => \$flir, + Parent => $markerName, + DirName => 'FLIR', + ); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + undef $flirCount; # prevent reprocessing + } + } else { + $self->WarnOnce('Invalid or extraneous FLIR chunk(s)'); + } + } elsif ($$segDataPt =~ /^PARROT\0(II\x2a\0|MM\0\x2a)/) { + # (don't know if this could span multiple segments) + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::Main'); + $self->HandleTag($tagTablePtr, 'APP1', $$segDataPt); + $dumpType = 'Parrot'; + } else { + # Hmmm. Could be XMP, let's see + my $processed; + if ($$segDataPt =~ /^(http|XMP\0)/ or $$segDataPt =~ /<(exif:|\?xpacket)/) { + $dumpType = 'XMP'; + # also try to parse XMP with a non-standard header + # (note: this non-standard XMP is ignored when writing) + my $start = ($$segDataPt =~ /^$xmpAPP1hdr/) ? length($xmpAPP1hdr) : 0; + my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); + DirStart(\%dirInfo, $start); + $dirInfo{DirName} = $start ? 'XMP' : 'XML', + $processed = $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + if ($processed and not $start) { + $self->Warn('Non-standard header for APP1 XMP segment'); + } + } + if ($verbose and not $processed) { + $self->Warn("Ignored APP1 segment length $length (unknown header)"); + } + } + } elsif ($marker == 0xe2) { # APP2 (ICC Profile, FPXR, MPF, InfiRay, PreviewImage) + if ($$segDataPt =~ /^ICC_PROFILE\0/ and $length >= 14) { + $dumpType = 'ICC_Profile'; + # must concatenate profile chunks (note: handle the case where + # some software erroneously writes zeros for the chunk counts) + my $chunkNum = Get8u($segDataPt, 12); + my $chunksTot = Get8u($segDataPt, 13); + $verbose and print $out "$$self{INDENT}ICC_Profile chunk $chunkNum of $chunksTot\n"; + if (defined $iccChunksTotal) { + # abort parsing ICC_Profile if the total chunk count is inconsistent + undef $iccChunkCount if $chunksTot != $iccChunksTotal; + } else { + $iccChunkCount = 0; + $iccChunksTotal = $chunksTot; + $self->Warn('ICC_Profile chunk count is zero') if !$chunksTot; + } + if (defined $iccChunkCount) { + if (defined $iccChunk[$chunkNum]) { + $self->WarnOnce('Duplicate ICC_Profile chunk number(s)'); + $iccChunk[$chunkNum] .= substr($$segDataPt, 14); + } else { + $iccChunk[$chunkNum] = substr($$segDataPt, 14); + } + # process profile if we have all of the chunks + if (++$iccChunkCount >= $iccChunksTotal) { + my $icc_profile = ''; + defined $_ and $icc_profile .= $_ foreach @iccChunk; + undef @iccChunk; # free memory + my $tagTablePtr = GetTagTable('Image::ExifTool::ICC_Profile::Main'); + my %dirInfo = ( + DataPt => \$icc_profile, + DataPos => $segPos + 14, + DataLen => length($icc_profile), + DirStart => 0, + DirLen => length($icc_profile), + Parent => $markerName, + ); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + undef $iccChunkCount; # prevent reprocessing + } + } else { + $self->WarnOnce('Invalid or extraneous ICC_Profile chunk(s)'); + } + } elsif ($$segDataPt =~ /^FPXR\0/) { + next if $fast > 1; # skip processing for very fast + $dumpType = 'FPXR'; + my $tagTablePtr = GetTagTable('Image::ExifTool::FlashPix::Main'); + # set flag if this is the last FPXR segment + $dirInfo{LastFPXR} = not ($nextMarker==$marker and $$nextSegDataPt=~/^FPXR\0/), + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^MPF\0/) { + undef $dumpType; # (will be dumped here) + DirStart(\%dirInfo, 4, 4); + $dirInfo{Multi} = 1; # the MP Attribute IFD will be MPF1 + if ($htmlDump) { + $self->HDump($segPos-4, 4, 'APP2 header', "Data size: $length bytes"); + $self->HDump($segPos, 4, 'MPF header', 'APP2 data type: MPF'); + $dumpEnd = $segPos + $length; + } + # extract the MPF information (it is in standard TIFF format) + my $tagTablePtr = GetTagTable('Image::ExifTool::MPF::Main'); + $self->ProcessTIFF(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^....IJPEG\0/s) { + $dumpType = 'InfiRay Version'; + $$self{HasIJPEG} = 1; + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::InfiRay::Version'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^(|QVGA\0|BGTH)\xff\xd8\xff[\xdb\xe0\xe1]/) { + # Samsung/GE/GoPro="", BenQ DC C1220/Pentacon/Polaroid="QVGA\0", + # Digilife DDC-690/Rollei="BGTH" + $dumpType = 'Preview Image'; + $preview = substr($$segDataPt, length($1)); + } elsif ($preview) { + $dumpType = 'Preview Image'; + $preview .= $$segDataPt; + } + if ($preview and $nextMarker ne $marker) { + $self->FoundTag('PreviewImage', $preview); + undef $preview; + } + } elsif ($marker == 0xe3) { # APP3 (Kodak "Meta", Stim) + if ($$segDataPt =~ /^(Meta|META|Exif)\0\0/) { + undef $dumpType; # (will be dumped here) + DirStart(\%dirInfo, 6, 6); + if ($htmlDump) { + $self->HDump($segPos-4, 10, 'APP3 Meta header'); + $dumpEnd = $segPos + $length; + } + my $tagTablePtr = GetTagTable('Image::ExifTool::Kodak::Meta'); + $self->ProcessTIFF(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^Stim\0/) { + undef $dumpType; # (will be dumped here) + DirStart(\%dirInfo, 6, 6); + if ($htmlDump) { + $self->HDump($segPos-4, 4, 'APP3 header', "Data size: $length bytes"); + $self->HDump($segPos, 5, 'Stim header', 'APP3 data type: Stim'); + $dumpEnd = $segPos + $length; + } + # extract the Stim information (it is in standard TIFF format) + my $tagTablePtr = GetTagTable('Image::ExifTool::Stim::Main'); + $self->ProcessTIFF(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^_JPSJPS_/) { + $dumpType = 'JPS'; + $self->OverrideFileType('JPS') if $$self{FILE_TYPE} eq 'JPEG'; + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::JPS'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$self{HasIJPEG} or $$self{Make} eq 'DJI') { + $dumpType = $$self{HasIJPEG} ? 'InfiRay ImagingData' : 'DJI ThermalData'; + # add this data to the combined data if it exists + my $dataPt = $segDataPt; + if (defined $combinedSegData) { + $combinedSegData .= $$segDataPt; + $dataPt = \$combinedSegData; + } + if ($nextMarker == $marker) { + $combinedSegData = $$segDataPt unless defined $combinedSegData; + } else { + # process InfiRay/DJI thermal data + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::Main'); + $self->HandleTag($tagTablePtr, 'APP3', $$dataPt); + undef $combinedSegData; + } + } elsif ($$segDataPt =~ /^\xff\xd8\xff\xdb/) { + $dumpType = 'PreviewImage'; # (Samsung, HP, BenQ) + $preview = $$segDataPt; + } + if ($preview and $nextMarker ne 0xe4) { # this preview continues in APP4 + $self->FoundTag('PreviewImage', $preview); + undef $preview; + } + } elsif ($marker == 0xe4) { # APP4 (InfiRay, "SCALADO", FPXR, DJI, PreviewImage) + if ($$segDataPt =~ /^SCALADO\0/ and $length >= 16) { + $dumpType = 'SCALADO'; + my ($num, $idx, $len) = unpack('x8n2N', $$segDataPt); + # assume that the segments are in order and just concatinate them + $scalado = '' unless defined $scalado; + $scalado .= substr($$segDataPt, 16); + if ($idx == $num - 1) { + if ($len != length $scalado) { + $self->Warn('Possibly corrupted APP4 SCALADO data', 1); + } + my %dirInfo = ( + Parent => $markerName, + DataPt => \$scalado, + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::Scalado::Main'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + undef $scalado; + } + } elsif ($$segDataPt =~ /^FPXR\0/) { + next if $fast > 1; # skip processing for very fast + $dumpType = 'FPXR'; + my $tagTablePtr = GetTagTable('Image::ExifTool::FlashPix::Main'); + # set flag if this is the last FPXR segment + $dirInfo{LastFPXR} = not ($nextMarker==$marker and $$nextSegDataPt=~/^FPXR\0/), + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$self{Make} eq 'DJI' and $$segDataPt =~ /^\xaa\x55\x12\x06/) { + $dumpType = 'DJI ThermalParams'; + DirStart(\%dirInfo, 0, 0); + my $tagTablePtr = GetTagTable('Image::ExifTool::DJI::ThermalParams'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$self{Make} eq 'DJI' and $$segDataPt =~ /^(.{32})?.{32}\x2c\x01\x20\0/s) { + $dumpType = 'DJI ThermalParams2'; + DirStart(\%dirInfo, $1 ? 32 : 0, 0); + my $tagTablePtr = GetTagTable('Image::ExifTool::DJI::ThermalParams2'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$self{Make} eq 'DJI' and $$segDataPt =~ /^.{32}\xaa\x55\x38\0/s) { + $dumpType = 'DJI ThermalParams3'; + DirStart(\%dirInfo, 32, 0); + my $tagTablePtr = GetTagTable('Image::ExifTool::DJI::ThermalParams3'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$self{HasIJPEG} and $length >= 120) { + $dumpType = 'InfiRay Factory'; + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::InfiRay::Factory'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($preview) { + # continued Samsung S1060 preview from APP3 + $dumpType = 'PreviewImage'; + $preview .= $$segDataPt; + } + # (also seen "QTI Debug Metadata\0" segment in some newer Samsung images) + # BenQ DC E1050 continues preview in APP5 + if ($preview and $nextMarker ne 0xe5) { + $self->FoundTag('PreviewImage', $preview); + undef $preview; + } + } elsif ($marker == 0xe5) { # APP5 (InfiRay, Ricoh "RMETA") + if ($$segDataPt =~ /^RMETA\0/) { + # (NOTE: apparently these may span multiple segments, but I haven't seen + # a sample like this, so multi-segment support hasn't yet been implemented) + $dumpType = 'Ricoh RMETA'; + DirStart(\%dirInfo, 6, 6); + my $tagTablePtr = GetTagTable('Image::ExifTool::Ricoh::RMETA'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^ssuniqueid\0/) { + my $tagTablePtr = GetTagTable('Image::ExifTool::Samsung::APP5'); + $self->HandleTag($tagTablePtr, 'ssuniqueid', substr($$segDataPt, 11)); + } elsif ($$self{Make} eq 'DJI') { + $dumpType = 'DJI ThermalCal'; + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::Main'); + $self->HandleTag($tagTablePtr, 'APP5', $$segDataPt); + } elsif ($$self{HasIJPEG} and $length >= 38) { + $dumpType = 'InfiRay Picture'; + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::InfiRay::Picture'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($preview) { + $dumpType = 'PreviewImage'; + $preview .= $$segDataPt; + $self->FoundTag('PreviewImage', $preview); + undef $preview; + } + } elsif ($marker == 0xe6) { # APP6 (InfiRay, Toshiba EPPIM, NITF, HP_TDHD) + if ($$segDataPt =~ /^EPPIM\0/) { + undef $dumpType; # (will be dumped here) + DirStart(\%dirInfo, 6, 6); + if ($htmlDump) { + $self->HDump($segPos-4, 10, 'APP6 EPPIM header'); + $dumpEnd = $segPos + $length; + } + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::EPPIM'); + $self->ProcessTIFF(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^NITF\0/) { + $dumpType = 'NITF'; + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::NITF'); + DirStart(\%dirInfo, 5); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^TDHD\x01\0\0\0/ and $length > 12) { + # HP Photosmart R837 APP6 "TDHD" segment + $dumpType = 'TDHD'; + my $tagTablePtr = GetTagTable('Image::ExifTool::HP::TDHD'); + # (ignore first TDHD element because size includes 12-byte tag header) + DirStart(\%dirInfo, 12); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^GoPro\0/) { + # GoPro segment + $dumpType = 'GoPro'; + my $tagTablePtr = GetTagTable('Image::ExifTool::GoPro::GPMF'); + DirStart(\%dirInfo, 6); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^DTAT\0\0.\{/s) { + $dumpType = 'DJI_DTAT'; + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::Main'); + $self->HandleTag($tagTablePtr, 'APP6', $$segDataPt); + } elsif ($$self{HasIJPEG} and $length >= 129) { + $dumpType = 'InfiRay MixMode'; + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::InfiRay::MixMode'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } elsif ($marker == 0xe7) { # APP7 (InfiRay, Pentax, Huawei, Qualcomm) + if ($$segDataPt =~ /^PENTAX \0(II|MM)/) { + # found in K-3 images (is this multi-segment??) + SetByteOrder($1); + undef $dumpType; # (dump this ourself) + my $hdrLen = 10; + my $tagTablePtr = GetTagTable('Image::ExifTool::Pentax::Main'); + DirStart(\%dirInfo, $hdrLen, 0); + $dirInfo{DirName} = 'Pentax APP7'; + if ($htmlDump) { + $self->HDump($segPos-4, 4, 'APP7 header', "Data size: $length bytes"); + $self->HDump($segPos, $hdrLen, 'Pentax header', 'APP7 data type: Pentax'); + $dumpEnd = $segPos + $length; + } + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$segDataPt =~ /^HUAWEI\0\0(II|MM)/) { + SetByteOrder($1); + undef $dumpType; # (dump this ourself) + my $hdrLen = 16; + my $tagTablePtr = GetTagTable('Image::ExifTool::Unknown::Main'); + DirStart(\%dirInfo, $hdrLen, 8); + $dirInfo{DirName} = 'Huawei APP7'; + if ($htmlDump) { + $self->HDump($segPos-4, 4, 'APP7 header', "Data size: $length bytes"); + $self->HDump($segPos, $hdrLen, 'Huawei header', 'APP7 data type: Huawei'); + $dumpEnd = $segPos + $length; + } + $$self{SET_GROUP0} = 'APP7'; + $$self{SET_GROUP1} = 'Huawei'; + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + delete $$self{SET_GROUP0}; + delete $$self{SET_GROUP1}; + } elsif ($$segDataPt =~ /^DJI-DBG\0/) { + $dumpType = 'DJI Info'; + my $tagTablePtr = GetTagTable('Image::ExifTool::DJI::Info'); + DirStart(\%dirInfo, 8, 0); + $$self{SET_GROUP0} = 'APP7'; + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + delete $$self{SET_GROUP0}; + } elsif ($$segDataPt =~ /^\x1aQualcomm Camera Attributes/) { + # found in HP iPAQ_VoiceMessenger + $dumpType = 'Qualcomm'; + my $tagTablePtr = GetTagTable('Image::ExifTool::Qualcomm::Main'); + DirStart(\%dirInfo, 27); + $dirInfo{DirName} = 'Qualcomm'; + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$self{HasIJPEG} and $length >= 32) { + $dumpType = 'InfiRay OpMode'; + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::InfiRay::OpMode'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } elsif ($marker == 0xe8) { # APP8 (InfiRay, SPIFF) + # my sample SPIFF has 32 bytes of data, but spec states 30 + if ($$segDataPt =~ /^SPIFF\0/ and $length == 32) { + $dumpType = 'SPIFF'; + DirStart(\%dirInfo, 6); + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::SPIFF'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$self{HasIJPEG} and $length >= 32) { + $dumpType = 'InfiRay Isothermal'; + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::InfiRay::Isothermal'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } elsif ($marker == 0xe9) { # APP9 (InfiRay, Media Jukebox) + if ($$segDataPt =~ /^Media Jukebox\0/ and $length > 22) { + $dumpType = 'MediaJukebox'; + # (start parsing after the "") + DirStart(\%dirInfo, 22); + $dirInfo{DirName} = 'MediaJukebox'; + require Image::ExifTool::XMP; + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::MediaJukebox'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr, \&Image::ExifTool::XMP::ProcessXMP); + } elsif ($$self{HasIJPEG} and $length >= 768) { + $dumpType = 'InfiRay Sensor'; + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::InfiRay::Sensor'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } elsif ($marker == 0xea) { # APP10 (PhotoStudio Unicode comments) + if ($$segDataPt =~ /^UNICODE\0/) { + $dumpType = 'PhotoStudio'; + my $comment = $self->Decode(substr($$segDataPt,8), 'UCS2', 'MM'); + $self->FoundTag('Comment', $comment); + } elsif ($$segDataPt =~ /^AROT\0/ and $length > 10) { + # iPhone "AROT" segment containing integrated intensity per 16 scan lines + # (with number of elements N = ImageHeight / 16 - 1, ref PH/NealKrawetz) + $xtra = 'segment (N=' . unpack('x6N', $$segDataPt) . ')'; + } + } elsif ($marker == 0xeb) { # APP11 (JPEG-HDR, JUMBF) + if ($$segDataPt =~ /^HDR_RI /) { + $dumpType = 'JPEG-HDR'; + my $dataPt = $segDataPt; + if (defined $combinedSegData) { + if ($$segDataPt =~ /~\0/g) { + $combinedSegData .= substr($$segDataPt,pos($$segDataPt)); + } else { + $self->Warn('Invalid format for JPEG-HDR extended segment'); + } + $dataPt = \$combinedSegData; + } + if ($nextMarker == $marker and $$nextSegDataPt =~ /^HDR_RI /) { + $combinedSegData = $$segDataPt unless defined $combinedSegData; + } else { + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::HDR'); + my %dirInfo = ( DataPt => $dataPt ); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + undef $combinedSegData; + } + } elsif ($$segDataPt =~ /^(JP..)/s and length($$segDataPt) >= 16) { + # JUMBF extension marker + my $hdr = $1; + $dumpType = 'JUMBF'; + SetByteOrder('MM'); + my $seq = Get32u($segDataPt, 4) - 1; # (start from 0) + my $len = Get32u($segDataPt, 8); + my $type = substr($$segDataPt, 12, 4); + my $hdrLen; + if ($len == 1 and length($$segDataPt) >= 24) { + $len = Get64u($$segDataPt, 16); + $hdrLen = 16; + } else { + $hdrLen = 8; + } + $jumbfChunk{$type} or $jumbfChunk{$type} = [ ]; + if ($len < $hdrLen) { + $self->Warn('Invalid JUMBF segment'); + } elsif ($seq < 0) { + $self->Warn('Invalid JUMBF sequence number'); + } elsif (defined $jumbfChunk{$type}[$seq]) { + $self->Warn('Duplicate JUMBF sequence number'); + } else { + # add to list of JUMBF chunks + $jumbfChunk{$type}[$seq] = substr($$segDataPt, 8 + $hdrLen); + # check to see if we have a complete JUMBF box + my $size = $hdrLen; + foreach (@{$jumbfChunk{$type}}) { + defined $_ or $size = 0, last; + $size += length $_; + } + if ($size == $len) { + my $buff = join '', substr($$segDataPt,8,$hdrLen), @{$jumbfChunk{$type}}; + $dirInfo{DataPt} = \$buff; + $dirInfo{DataPos} = $segPos + 8; # (shows correct offsets for single-segment JUMBF) + $dirInfo{DataLen} = $dirInfo{DirLen} = $size; + my $tagTablePtr = GetTagTable('Image::ExifTool::Jpeg2000::Main'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + delete $jumbfChunk{$type}; + } + } + } + } elsif ($marker == 0xec) { # APP12 (Ducky, Picture Info) + if ($$segDataPt =~ /^Ducky/) { + $dumpType = 'Ducky'; + DirStart(\%dirInfo, 5); + my $tagTablePtr = GetTagTable('Image::ExifTool::APP12::Ducky'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } else { + my $tagTablePtr = GetTagTable('Image::ExifTool::APP12::PictureInfo'); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr) and $dumpType = 'Picture Info'; + } + } elsif ($marker == 0xed) { # APP13 (Photoshop, Adobe_CM) + my $isOld; + if ($$segDataPt =~ /^$psAPP13hdr/ or ($$segDataPt =~ /^$psAPP13old/ and $isOld=1)) { + $dumpType = 'Photoshop'; + # add this data to the combined data if it exists + my $dataPt = $segDataPt; + if (defined $combinedSegData) { + $combinedSegData .= substr($$segDataPt,length($psAPP13hdr)); + $dataPt = \$combinedSegData; + } + # peek ahead to see if the next segment is photoshop data too + if ($nextMarker == $marker and $$nextSegDataPt =~ /^$psAPP13hdr/) { + # initialize combined data if necessary + $combinedSegData = $$segDataPt unless defined $combinedSegData; + # (will handle the Photoshop data the next time around) + } else { + my $hdrLen = $isOld ? 27 : 14; + # process APP13 Photoshop record + my $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main'); + my %dirInfo = ( + DataPt => $dataPt, + DataPos => $segPos, + DataLen => length $$dataPt, + DirStart => $hdrLen, # directory starts after identifier + DirLen => length($$dataPt) - $hdrLen, + Parent => $markerName, + ); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + undef $combinedSegData; + } + } elsif ($$segDataPt =~ /^Adobe_CM/) { + $dumpType = 'Adobe_CM'; + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::AdobeCM'); + DirStart(\%dirInfo, 8); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } elsif ($marker == 0xee) { # APP14 (Adobe) + if ($$segDataPt =~ /^Adobe/) { + # extract as a block if requested, or if copying tags from file + if ($$req{adobe} or + # (not extracted normally, so check TAGS_FROM_FILE) + ($$self{TAGS_FROM_FILE} and not $$self{EXCL_TAG_LOOKUP}{adobe})) + { + $self->FoundTag('Adobe', $$segDataPt); + } + $dumpType = 'Adobe'; + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::Adobe'); + DirStart(\%dirInfo, 5); + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } elsif ($marker == 0xef) { # APP15 (GraphicConverter) + if ($$segDataPt =~ /^Q\s*(\d+)/ and $length == 4) { + $dumpType = 'GraphicConverter'; + my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::GraphConv'); + $self->HandleTag($tagTablePtr, 'Q', $1); + } + } elsif ($marker == 0xfe) { # COM (JPEG comment) + $dumpType = 'Comment'; + $$segDataPt =~ s/\0+$//; # some dumb softwares add null terminators + $self->FoundTag('Comment', $$segDataPt); + } elsif ($marker == 0x64) { # CME (J2C comment and extension) + $dumpType = 'Comment'; + if ($length > 2) { + my $reg = unpack('n', $$segDataPt); # get registration value + my $val = substr($$segDataPt, 2); + $val = $self->Decode($val, 'Latin') if $reg == 1; + # (actually an extension for $reg==65535, but store as binary comment) + $self->FoundTag('Comment', ($reg==0 or $reg==65535) ? \$val : $val); + } + } elsif ($marker == 0x51) { # SIZ (J2C) + my ($w, $h) = unpack('x2N2', $$segDataPt); + unless ($gotSize) { + $gotSize = 1; + $self->FoundTag('ImageWidth', $w); + $self->FoundTag('ImageHeight', $h); + } + } elsif (($marker & 0xf0) != 0xe0) { + $dumpType = "$markerName segment"; + $desc = "[JPEG $markerName]"; # (other known JPEG segments) + } + if (defined $dumpType) { + if (not $dumpType and ($$options{Unknown} or $$options{Validate})) { + my $str = ($$segDataPt =~ /^([\x20-\x7e]{1,20})\0/) ? " '${1}'" : ''; + $xtra = 'segment' unless $xtra; + $self->Warn("Unknown $markerName$str $xtra", 1); + } + if ($htmlDump) { + $desc or $desc = $markerName . ($dumpType ? " $dumpType" : '') . ' segment'; + $self->HDump($segPos-4, $length+4, $desc, $tip, 0x08); + $dumpEnd = $segPos + $length; + } + } + undef $$segDataPt; + } + # process extended XMP now if it existed + if (%extendedXMP) { + my $guid; + # GUID indicated by the last main XMP segment + my $goodGuid = $$self{VALUE}{HasExtendedXMP} || ''; + # GUID of the extended XMP that we will process ('2' for all) + my $readGuid = $$options{ExtendedXMP} || 0; + $readGuid = $goodGuid if $readGuid eq '1'; + foreach $guid (sort keys %extendedXMP) { + next unless length $guid == 32; # ignore other (internal) keys + my $extXMP = $extendedXMP{$guid}; + my ($off, @offsets, $warn); + # make sure we have all chunks, and create a list of sorted offsets + for ($off=0; $off<$$extXMP{Size}; ) { + last unless defined $$extXMP{$off}; + push @offsets, $off; + $off += length $$extXMP{$off}; + } + unless ($off == $$extXMP{Size}) { + $self->Warn("Incomplete extended XMP (GUID $guid)"); + next; + } + if ($guid eq $readGuid or $readGuid eq '2') { + $warn = 'Reading non-' if $guid ne $goodGuid; + my $buff = ''; + # assemble XMP all together + $buff .= $$extXMP{$_} foreach @offsets; + my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); + my %dirInfo = ( + DataPt => \$buff, + Parent => 'APP1', + IsExtended => 1, + ); + $$path[$pn] = 'APP1'; + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + pop @$path; + } else { + $warn = 'Ignored '; + $warn .= 'non-' if $guid ne $goodGuid; + } + $self->Warn("${warn}standard extended XMP (GUID $guid)") if $warn; + delete $extendedXMP{$guid}; + } + } + # print verbose hash message if necessary + print $out "$$self{INDENT}(ImageDataHash: $hashsize bytes of JPEG image data)\n" if $hashsize and $verbose; + # calculate JPEGDigest if requested + if (@dqt) { + require Image::ExifTool::JPEGDigest; + Image::ExifTool::JPEGDigest::Calculate($self, \@dqt, $subSampling); + } + # issue necessary warnings + $self->Warn('Invalid JUMBF size or missing JUMBF chunk') if %jumbfChunk; + $self->Warn('Incomplete ICC_Profile record', 1) if defined $iccChunkCount; + $self->Warn('Incomplete FLIR record', 1) if defined $flirCount; + $self->Warn('Error reading PreviewImage', 1) if $$self{PreviewError}; + $success or $self->Warn('JPEG format error'); + pop @$path if @$path > $pn; + return 1; +} + +#------------------------------------------------------------------------------ +# Extract metadata from an Exiv2 EXV file +# Inputs: 0) ExifTool object reference, 1) dirInfo ref with RAF set +# Returns: 1 on success, 0 if this wasn't a valid JPEG file +sub ProcessEXV($$) +{ + my ($self, $dirInfo) = @_; + return $self->ProcessJPEG($dirInfo); +} + +#------------------------------------------------------------------------------ +# Process EXIF file +# Inputs/Returns: same as ProcessTIFF +sub ProcessEXIF($$;$) +{ + my ($self, $dirInfo, $tagTablePtr) = @_; + return $self->ProcessTIFF($dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Process TIFF data (wrapper for DoProcessTIFF to allow re-entry) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) optional tag table ref +# Returns: 1 if this looked like a valid EXIF block, 0 otherwise, or -1 on write error +sub ProcessTIFF($$;$) +{ + my ($self, $dirInfo, $tagTablePtr) = @_; + my $exifData = $$self{EXIF_DATA}; + my $exifPos = $$self{EXIF_POS}; + my $rtnVal = $self->DoProcessTIFF($dirInfo, $tagTablePtr); + # restore original EXIF information (in case ProcessTIFF is nested) + if (defined $exifData) { + $$self{EXIF_DATA} = $exifData; + $$self{EXIF_POS} = $exifPos; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Process TIFF data +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) optional tag table ref +# Returns: 1 if this looked like a valid EXIF block, 0 otherwise, or -1 on write error +sub DoProcessTIFF($$;$) +{ + my ($self, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $fileType = $$dirInfo{Parent} || ''; + my $raf = $$dirInfo{RAF}; + my $base = $$dirInfo{Base} || 0; + my $outfile = $$dirInfo{OutFile}; + my ($err, $sig, $canonSig, $otherSig); + + # attempt to read TIFF header + $$self{EXIF_DATA} = ''; + if ($raf) { + if ($outfile) { + $raf->Seek(0, 0) or return 0; + if ($base) { + $raf->Read($$dataPt, $base) == $base or return 0; + Write($outfile, $$dataPt) or $err = 1; + } + } else { + $raf->Seek($base, 0) or return 0; + } + # extract full EXIF block (for block copy) from EXIF file + my $amount = $fileType eq 'EXIF' ? 65536 * 8 : 8; + my $n = $raf->Read($$self{EXIF_DATA}, $amount); + if ($n < 8) { + return 0 if $n or not $outfile or $fileType ne 'EXIF'; + # create EXIF file from scratch + delete $$self{EXIF_DATA}; + undef $raf; + } + if ($n > 8) { + $raf->Seek(8, 0); + if ($n == $amount) { + $$self{EXIF_DATA} = substr($$self{EXIF_DATA}, 0, 8); + $self->Warn('EXIF too large to extract as a block'); #(shouldn't happen) + } + } + } elsif ($dataPt and length $$dataPt) { + # save a copy of the EXIF data + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart); + $$self{EXIF_DATA} = substr($$dataPt, $dirStart, $dirLen); + $self->VerboseDir('TIFF') if $$self{OPTIONS}{Verbose} and length($$self{INDENT}) > 2; + } elsif ($outfile) { + delete $$self{EXIF_DATA}; # create from scratch + } else { + $$self{EXIF_DATA} = ''; + } + unless (defined $$self{EXIF_DATA}) { + # set default byte order for creating new GPS in CR3 images + my $defaultByteOrder; + if ($$dirInfo{DirName} and $$dirInfo{DirName} eq 'GPS') { + $defaultByteOrder = $$self{SaveExifByteOrder}; + } + # create TIFF information from scratch + if ($self->SetPreferredByteOrder($defaultByteOrder) eq 'MM') { + $$self{EXIF_DATA} = "MM\0\x2a\0\0\0\x08"; + } else { + $$self{EXIF_DATA} = "II\x2a\0\x08\0\0\0"; + } + } + $$self{EXIF_POS} = $base + $$self{BASE}; + $$self{FIRST_EXIF_POS} = $$self{EXIF_POS} unless defined $$self{FIRST_EXIF_POS}; + $dataPt = \$$self{EXIF_DATA}; + + # set byte ordering + my $byteOrder = substr($$dataPt,0,2); + SetByteOrder($byteOrder) or return 0; + + # verify the byte ordering + my $identifier = Get16u($dataPt, 2); + # identifier is 0x2a for TIFF (but 0x4f52, 0x5352 or ?? for ORF) + # no longer do this because various files use different values + # (TIFF=0x2a, RW2/RWL=0x55, HDP=0xbc, BTF=0x2b, ORF=0x4f52/0x5352/0x????) + # return 0 unless $identifier == 0x2a; + $self->Warn('Invalid magic number in EXIF TIFF header') if $fileType eq 'APP1' and $identifier != 0x2a; + + # get offset to IFD0 + return 0 if length $$dataPt < 8; + my $offset = Get32u($dataPt, 4); + $offset >= 8 or return 0; + + if ($raf) { + # check for canon or EXIF signature + # (Canon CR2 images should have an offset of 16, but it may be + # greater if edited by PhotoMechanic) + if ($identifier == 0x2a and $offset >= 16) { + $raf->Read($sig, 8) == 8 or return 0; + $$dataPt .= $sig; + if ($sig =~ /^(CR\x02\0|\xba\xb0\xac\xbb|ExifMeta)/) { + if ($sig eq 'ExifMeta') { + $self->SetFileType($fileType = 'EXIF'); + $otherSig = $sig; + } else { + $fileType = $sig =~ /^CR/ ? 'CR2' : 'Canon 1D RAW'; + $canonSig = $sig; + } + $self->HDump($base+8, 8, "[$fileType header]") if $$self{HTML_DUMP}; + } + } elsif ($identifier == 0x55 and $fileType =~ /^(RAW|RW2|RWL|TIFF)$/) { + # panasonic RAW, RW2 or RWL file + my $magic; + # test for RW2/RWL magic number + if ($offset >= 0x18 and $raf->Read($magic, 16) and + $magic eq "\x88\xe7\x74\xd8\xf8\x25\x1d\x4d\x94\x7a\x6e\x77\x82\x2b\x5d\x6a") + { + $fileType = 'RW2' unless $fileType eq 'RWL'; + $self->HDump($base + 8, 16, '[RW2/RWL header]') if $$self{HTML_DUMP}; + $otherSig = $magic; # save signature for writing + } else { + $fileType = 'RAW'; + } + $tagTablePtr = GetTagTable('Image::ExifTool::PanasonicRaw::Main'); + } elsif ($fileType eq 'TIFF') { + if ($identifier == 0x2b) { + # this looks like a BigTIFF image + $raf->Seek(0); + require Image::ExifTool::BigTIFF; + my $result = Image::ExifTool::BigTIFF::ProcessBTF($self, $dirInfo); + if ($result) { + $self->FoundTag(PageCount => $$self{PageCount}) if $$self{MultiPage}; + return 1; + } + } elsif ($identifier == 0x4f52 or $identifier == 0x5352) { + # Olympus ORF image (set FileType now because base type is 'ORF') + $self->SetFileType($fileType = 'ORF'); + } elsif ($identifier == 0x4352) { + $fileType = 'DCP'; + } elsif ($byteOrder eq 'II' and ($identifier & 0xff) == 0xbc) { + $fileType = 'HDP'; # Windows HD Photo file + # check version number + my $ver = Get8u($dataPt, 3); + if ($ver > 1) { + $self->Error("Windows HD Photo version $ver files not yet supported"); + return 1; + } + } + } elsif ($fileType eq 'ARW') { + $$self{LOW_PRIORITY_DIR}{IFD1} = 1; # lower priority of IFD1 tags in ARW files + } + # we have a valid TIFF (or whatever) file + if ($fileType and not $$self{VALUE}{FileType}) { + my $lookup = $fileTypeLookup{$fileType}; + $lookup = $fileTypeLookup{$lookup} unless ref $lookup or not $lookup; + # use file extension to pre-determine type if extension is TIFF-based or type is RAW + my $baseType = $lookup ? (ref $$lookup[0] ? $$lookup[0][0] : $$lookup[0]) : ''; + my $t = ($baseType eq 'TIFF' or $fileType =~ /RAW/) ? $fileType : undef; + $self->SetFileType($t); + } + # don't process file if FastScan == 3 + return 1 if not $outfile and $$self{OPTIONS}{FastScan} and $$self{OPTIONS}{FastScan} == 3; + } + # (accommodate CR3 images which have a TIFF directory with ExifIFD at the top level) + my $ifdName = ($$dirInfo{DirName} and $$dirInfo{DirName} =~ /^(ExifIFD|GPS)$/) ? $1 : 'IFD0'; + if (not $tagTablePtr or $$tagTablePtr{GROUPS}{0} eq 'EXIF') { + $self->FoundTag('ExifByteOrder', $byteOrder) unless $outfile; + } elsif ($$tagTablePtr{GROUPS}{0} eq 'MakerNotes') { # (for writing CR3 maker notes) + $ifdName = $$tagTablePtr{GROUPS}{0}; + } else { + $ifdName = $$tagTablePtr{GROUPS}{1}; + } + if ($$self{HTML_DUMP}) { + my $tip = sprintf("Byte order: %s endian\nIdentifier: 0x%.4x\n$ifdName offset: 0x%.4x", + ($byteOrder eq 'II') ? 'Little' : 'Big', $identifier, $offset); + $self->HDump($base, 8, 'TIFF header', $tip, 0); + } + # remember where we found the TIFF data (APP1, APP3, TIFF, NEF, etc...) + $$self{TIFF_TYPE} = $fileType; + + # get reference to the main EXIF table + $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); + + # build directory information hash + my %dirInfo = ( + Base => $base, + DataPt => $dataPt, + DataLen => length $$dataPt, + DataPos => 0, + DirStart => $offset, + DirLen => length($$dataPt) - $offset, + RAF => $raf, + DirName => $ifdName, + Parent => $fileType, + ImageData=> 'Main', # set flag to get information to copy main image data later + Multi => $$dirInfo{Multi}, + ); + + # extract information from the image + unless ($outfile) { + # process the directory + $self->ProcessDirectory(\%dirInfo, $tagTablePtr); + # process GeoTiff information if available + if ($$self{VALUE}{GeoTiffDirectory}) { + require Image::ExifTool::GeoTiff; + Image::ExifTool::GeoTiff::ProcessGeoTiff($self); + } + # process information in recognized trailers + if ($raf) { + my $trailInfo = IdentifyTrailer($raf); + if ($trailInfo) { + $$trailInfo{ScanForAFCP} = 1; # scan to find AFCP if necessary + $self->ProcessTrailers($trailInfo); + } + # dump any other known trailer (eg. A100 RAW Data) + if ($$self{HTML_DUMP} and $$self{KnownTrailer}) { + my $known = $$self{KnownTrailer}; + $raf->Seek(0, 2); + my $len = $raf->Tell() - $$known{Start}; + $len -= $$trailInfo{Offset} if $trailInfo; # account for other trailers + $self->HDump($$known{Start}, $len, "[$$known{Name}]") if $len > 0; + } + } + # update FileType if necessary now that we know more about the file + if ($$self{DNGVersion} and $$self{FileType} !~ /^(DNG|GPR)$/) { + # override whatever FileType we set since we now know it is DNG + $self->OverrideFileType($$self{TIFF_TYPE} = 'DNG'); + } + if ($$self{TIFF_TYPE} eq 'TIFF') { + $self->FoundTag(PageCount => $$self{PageCount}) if $$self{MultiPage}; + } + if ($$self{ImageDataHash} and $$self{A100DataOffset} and $raf->Seek($$self{A100DataOffset},0)) { + $self->ImageDataHash($raf, undef, 'A100'); + } + return 1; + } +# +# rewrite the image +# + if ($$dirInfo{NoTiffEnd}) { + delete $$self{TIFF_END}; + } else { + # initialize TIFF_END so it will be updated by WriteExif() + $$self{TIFF_END} = 0; + } + if ($canonSig) { + # write Canon CR2 specially because it has a header we want to preserve, + # and possibly trailers added by the Canon utilities and/or PhotoMechanic + $dirInfo{OutFile} = $outfile; + require Image::ExifTool::CanonRaw; + Image::ExifTool::CanonRaw::WriteCR2($self, \%dirInfo, $tagTablePtr) or $err = 1; + } else { + # write TIFF header (8 bytes [plus optional signature] followed by IFD) + if ($fileType eq 'EXIF') { + $otherSig = 'ExifMeta'; # force this signature for all EXIF files + } elsif (not defined $otherSig) { + $otherSig = ''; + } + my $offset = 8 + length($otherSig); + # construct tiff header + my $header = substr($$dataPt, 0, 4) . Set32u($offset) . $otherSig; + $dirInfo{NewDataPos} = $offset; + $dirInfo{HeaderPtr} = \$header; + # preserve padding between image data blocks in ORF images + # (otherwise dcraw has problems because it assumes fixed block spacing) + $dirInfo{PreserveImagePadding} = 1 if $fileType eq 'ORF' or $identifier != 0x2a; + my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + if (not defined $newData) { + $err = 1; + } elsif (length($newData)) { + # update header length in case more was added + my $hdrLen = length $header; + if ($hdrLen != 8) { + Set32u($hdrLen, \$header, 4); + # also update preview fixup if necessary + my $pi = $$self{PREVIEW_INFO}; + $$pi{Fixup}{Start} += $hdrLen - 8 if $pi and $$pi{Fixup}; + } + if ($$self{TIFF_TYPE} eq 'ARW' and not $err) { + # write any required ARW trailer and patch other ARW quirks + require Image::ExifTool::Sony; + my $errStr = Image::ExifTool::Sony::FinishARW($self, $dirInfo, \$newData, + $dirInfo{ImageData}); + $errStr and $self->Error($errStr); + delete $dirInfo{ImageData}; # (was copied by FinishARW) + } else { + Write($outfile, $header, $newData) or $err = 1; + } + undef $newData; # free memory + } + # copy over image data now if necessary + if (ref $dirInfo{ImageData} and not $err) { + $self->CopyImageData($dirInfo{ImageData}, $outfile) or $err = 1; + delete $dirInfo{ImageData}; + } + } + # make local copy of TIFF_END now (it may be reset when processing trailers) + my $tiffEnd = $$self{TIFF_END}; + delete $$self{TIFF_END}; + + # rewrite trailers if they exist + if ($raf and $tiffEnd and not $err) { + my ($buf, $trailInfo); + $raf->Seek(0, 2) or $err = 1; + my $extra = $raf->Tell() - $tiffEnd; + # check for trailer and process if possible + for (;;) { + last unless $extra > 12; + $raf->Seek($tiffEnd); # seek back to end of image + $trailInfo = IdentifyTrailer($raf); + last unless $trailInfo; + my $tbuf = ''; + $$trailInfo{OutFile} = \$tbuf; # rewrite trailer(s) + $$trailInfo{ScanForAFCP} = 1; # scan for AFCP if necessary + # rewrite all trailers to buffer + unless ($self->ProcessTrailers($trailInfo)) { + undef $trailInfo; + $err = 1; + last; + } + # calculate unused bytes before trailer + $extra = $$trailInfo{DataPos} - $tiffEnd; + last; # yes, the 'for' loop was just a cheap 'goto' + } + # ignore a single zero byte if used for padding + if ($extra > 0 and $tiffEnd & 0x01) { + $raf->Seek($tiffEnd, 0) or $err = 1; + $raf->Read($buf, 1) or $err = 1; + defined $buf and $buf eq "\0" and --$extra, ++$tiffEnd; + } + if ($extra > 0) { + my $known = $$self{KnownTrailer}; + if ($$self{DEL_GROUP}{Trailer} and not $known) { + $self->VPrint(0, " Deleting unknown trailer ($extra bytes)\n"); + ++$$self{CHANGED}; + } elsif ($known) { + $self->VPrint(0, " Copying $$known{Name} ($extra bytes)\n"); + $raf->Seek($tiffEnd, 0) or $err = 1; + CopyBlock($raf, $outfile, $extra) or $err = 1; + } else { + $raf->Seek($tiffEnd, 0) or $err = 1; + # preserve unknown trailer only if it contains non-null data + # (Photoshop CS adds a trailer with 2 null bytes) + my $size = $extra; + for (;;) { + my $n = $size > 65536 ? 65536 : $size; + $raf->Read($buf, $n) == $n or $err = 1, last; + if ($buf =~ /[^\0]/) { + $self->VPrint(0, " Preserving unknown trailer ($extra bytes)\n"); + # copy the trailer since it contains non-null data + Write($outfile, "\0"x($extra-$size)) or $err = 1, last if $size != $extra; + Write($outfile, $buf) or $err = 1, last; + CopyBlock($raf, $outfile, $size-$n) or $err = 1 if $size > $n; + last; + } + $size -= $n; + next if $size > 0; + $self->VPrint(0, " Deleting blank trailer ($extra bytes)\n"); + last; + } + } + } + # write trailer buffer if necessary + $self->WriteTrailerBuffer($trailInfo, $outfile) or $err = 1 if $trailInfo; + # add any new trailers we are creating + my $trailPt = $self->AddNewTrailers(); + Write($outfile, $$trailPt) or $err = 1 if $trailPt; + } + # check DNG version + if ($$self{DNGVersion}) { + my $ver = $$self{DNGVersion}; + # currently support up to DNG version 1.6 + unless ($ver =~ /^(\d+) (\d+)/ and "$1.$2" <= 1.6) { + $ver =~ tr/ /./; + $self->Error("DNG Version $ver not yet tested", 1); + } + } + return $err ? -1 : 1; +} + +#------------------------------------------------------------------------------ +# Return list of tag table keys (ignoring special keys) +# Inputs: 0) reference to tag table +# Returns: List of table keys (unsorted) +sub TagTableKeys($) +{ + local $_; + my $tagTablePtr = shift; + my @keyList; + foreach (keys %$tagTablePtr) { + push(@keyList, $_) unless $specialTags{$_}; + } + return @keyList; +} + +#------------------------------------------------------------------------------ +# GetTagTable +# Inputs: 0) table name +# Returns: tag table reference, or undefined if not found +# Notes: Always use this function instead of requiring module and using table +# directly since this function also does the following the first time the table +# is loaded: +# - requires new module if necessary +# - generates default GROUPS hash and Group 0 name from module name +# - registers Composite tags if Composite table found +# - saves descriptions for tags in specified table +# - generates default TAG_PREFIX to be used for unknown tags +sub GetTagTable($) +{ + my $tableName = shift or return undef; + my $table = $allTables{$tableName}; + + unless ($table) { + no strict 'refs'; + unless (%$tableName) { + # try to load module for this table + if ($tableName =~ /(.*)::/) { + my $module = $1; + if (eval "require $module") { + # load additional modules if required + if (not %$tableName) { + if ($module eq 'Image::ExifTool::XMP') { + require 'Image/ExifTool/XMP2.pl'; + } elsif ($tableName eq 'Image::ExifTool::QuickTime::Stream') { + require 'Image/ExifTool/QuickTimeStream.pl'; + } + } + } else { + $@ and warn $@; + } + } + unless (%$tableName) { + warn "Can't find table $tableName\n"; + return undef; + } + } + no strict 'refs'; + $table = \%$tableName; + use strict 'refs'; + &{$$table{INIT_TABLE}}($table) if $$table{INIT_TABLE}; + $$table{TABLE_NAME} = $tableName; # set table name + ($$table{SHORT_NAME} = $tableName) =~ s/^Image::ExifTool:://; + # set default group 0 and 1 from module name unless already specified + my $defaultGroups = $$table{GROUPS}; + $defaultGroups or $defaultGroups = $$table{GROUPS} = { }; + unless ($$defaultGroups{0} and $$defaultGroups{1}) { + if ($tableName =~ /Image::.*?::([^:]*)/) { + $$defaultGroups{0} = $1 unless $$defaultGroups{0}; + $$defaultGroups{1} = $1 unless $$defaultGroups{1}; + } else { + $$defaultGroups{0} = $tableName unless $$defaultGroups{0}; + $$defaultGroups{1} = $tableName unless $$defaultGroups{1}; + } + } + $$defaultGroups{2} = 'Other' unless $$defaultGroups{2}; + if ($$defaultGroups{0} eq 'XMP' or $$table{NAMESPACE}) { + # initialize some XMP table defaults + require Image::ExifTool::XMP; + Image::ExifTool::XMP::RegisterNamespace($table); # register all table namespaces + # set default write/check procs + $$table{WRITE_PROC} = \&Image::ExifTool::XMP::WriteXMP unless $$table{WRITE_PROC}; + $$table{CHECK_PROC} = \&Image::ExifTool::XMP::CheckXMP unless $$table{CHECK_PROC}; + $$table{LANG_INFO} = \&Image::ExifTool::XMP::GetLangInfo unless $$table{LANG_INFO}; + } + # generate a tag prefix for unknown tags if necessary + unless (defined $$table{TAG_PREFIX}) { + my $tagPrefix; + if ($tableName =~ /Image::.*?::(.*)::Main/ || $tableName =~ /Image::.*?::(.*)/) { + ($tagPrefix = $1) =~ s/::/_/g; + } else { + $tagPrefix = $tableName; + } + $$table{TAG_PREFIX} = $tagPrefix; + } + # set up the new table + SetupTagTable($table); + # add any user-defined tags (except Composite tags, which are handled specially) + if (%UserDefined and $UserDefined{$tableName} and $table ne \%Image::ExifTool::Composite) { + my $tagID; + foreach $tagID (TagTableKeys($UserDefined{$tableName})) { + next if $specialTags{$tagID}; + delete $$table{$tagID}; # replace any existing entry + AddTagToTable($table, $tagID, $UserDefined{$tableName}{$tagID}, 1); + } + } + # remember order we loaded the tables in + push @tableOrder, $tableName; + # insert newly loaded table into list + $allTables{$tableName} = $table; + } + # must check each time to add UserDefined Composite tags because the Composite table + # may be loaded before the UserDefined tags are available + if ($table eq \%Image::ExifTool::Composite and not $$table{VARS}{LOADED_USERDEFINED} and + %UserDefined and $UserDefined{$tableName}) + { + my $userComp = $UserDefined{$tableName}; + delete $UserDefined{$tableName}; # (must delete first to avoid infinite recursion) + AddCompositeTags($userComp, 1); + $UserDefined{$tableName} = $userComp; # (add back again for adding writable tags later) + $$table{VARS}{LOADED_USERDEFINED} = 1; # set flag to avoid doing this again + } + return $table; +} + +#------------------------------------------------------------------------------ +# Process an image directory +# Inputs: 0) ExifTool object reference, 1) directory information reference +# 2) tag table reference, 3) optional reference to processing procedure +# Returns: Result from processing (1=success) +sub ProcessDirectory($$$;$) +{ + my ($self, $dirInfo, $tagTablePtr, $proc) = @_; + + return 0 unless $tagTablePtr and $dirInfo; + # use default proc from tag table or EXIF proc as fallback if no proc specified + $proc or $proc = $$tagTablePtr{PROCESS_PROC} || \&Image::ExifTool::Exif::ProcessExif; + # set directory name from default group0 name if not done already + my $dirName = $$dirInfo{DirName}; + unless ($dirName) { + $dirName = $$tagTablePtr{GROUPS}{0}; + $dirName = $$tagTablePtr{GROUPS}{1} if $dirName =~ /^APP\d+$/; # (use specific APP name) + $$dirInfo{DirName} = $dirName; + } + + # guard against cyclical recursion into the same directory + if (defined $$dirInfo{DirStart} and defined $$dirInfo{DataPos} and + # directories don't overlap if the length is zero + ($$dirInfo{DirLen} or not defined $$dirInfo{DirLen})) + { + my $addr = $$dirInfo{DirStart} + $$dirInfo{DataPos} + ($$dirInfo{Base}||0) + $$self{BASE}; + if ($$self{PROCESSED}{$addr}) { + $self->Warn("$dirName pointer references previous $$self{PROCESSED}{$addr} directory"); + # patch for bug in Windows phone 7.5 O/S that writes incorrect InteropIFD pointer + return 0 unless $dirName eq 'GPS' and $$self{PROCESSED}{$addr} eq 'InteropIFD'; + } + $$self{PROCESSED}{$addr} = $dirName unless $$tagTablePtr{VARS} and $$tagTablePtr{VARS}{ALLOW_REPROCESS}; + } + my $oldOrder = GetByteOrder(); + my @save = @$self{'INDENT','DIR_NAME','Compression','SubfileType'}; + $$self{LIST_TAGS} = { }; # don't build lists across different directories + $$self{INDENT} .= '| '; + $$self{DIR_NAME} = $dirName; + push @{$$self{PATH}}, $dirName; + $$self{FOUND_DIR}{$dirName} = 1; + + # process the directory + no strict 'refs'; + my $rtnVal = &$proc($self, $dirInfo, $tagTablePtr); + use strict 'refs'; + + pop @{$$self{PATH}}; + @$self{'INDENT','DIR_NAME','Compression','SubfileType'} = @save; + SetByteOrder($oldOrder); + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Get Metadata path +# Inputs: 0) ExifTool object ref +# Return: Metadata path string +sub MetadataPath($) +{ + my $self = shift; + return join '-', @{$$self{PATH}} +} + +#------------------------------------------------------------------------------ +# Get standardized file extension +# Inputs: 0) file name +# Returns: standardized extension (all uppercase), or undefined if no extension +sub GetFileExtension($) +{ + my $filename = shift; + my $fileExt; + if ($filename and $filename =~ /^.*\.([^.]+)$/s) { + $fileExt = uc($1); # change extension to upper case + # convert TIF extension to TIFF because we use the + # extension for the file type tag of TIFF images + $fileExt eq 'TIF' and $fileExt = 'TIFF'; + } + return $fileExt; +} + +#------------------------------------------------------------------------------ +# Get list of tag information hashes for given tag ID +# Inputs: 0) Tag table reference, 1) tag ID +# Returns: Array of tag information references +# Notes: Generates tagInfo hash if necessary +sub GetTagInfoList($$) +{ + my ($tagTablePtr, $tagID) = @_; + my $tagInfo = $$tagTablePtr{$tagID}; + + if ($specialTags{$tagID}) { + # (hopefully this won't happen) + warn "Tag $tagID conflicts with internal ExifTool variable in $$tagTablePtr{TABLE_NAME}\n"; + } elsif (ref $tagInfo eq 'HASH') { + return ($tagInfo); + } elsif (ref $tagInfo eq 'ARRAY') { + return @$tagInfo; + } elsif ($tagInfo) { + # create hash with name + $tagInfo = $$tagTablePtr{$tagID} = { Name => $tagInfo }; + return ($tagInfo); + } + return (); +} + +#------------------------------------------------------------------------------ +# Find tag information, processing conditional tags +# Inputs: 0) ExifTool object reference, 1) tagTable pointer, 2) tag ID +# 3) optional value reference, 4) optional format type, 5) optional value count +# Returns: pointer to tagInfo hash, undefined if none found, or '' if $valPt needed +# Notes: You should always call this routine to find a tag in a table because +# this routine will evaluate conditional tags. +# Arguments 3-5 are only required if the information type allows $valPt, $format and/or +# $count in a Condition, and if not given when needed this routine returns ''. +sub GetTagInfo($$$;$$$) +{ + my ($self, $tagTablePtr, $tagID) = @_; + my ($valPt, $format, $count); + + my @infoArray = GetTagInfoList($tagTablePtr, $tagID); + # evaluate condition + my $tagInfo; + foreach $tagInfo (@infoArray) { + my $condition = $$tagInfo{Condition}; + if ($condition) { + ($valPt, $format, $count) = splice(@_, 3) if @_ > 3; + return '' if $condition =~ /\$(valPt|format|count)\b/ and not defined $valPt; + # set old value for use in condition if needed + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + #### eval Condition ($self, [$valPt, $format, $count]) + unless (eval $condition) { + $@ and $evalWarning = $@; + $self->Warn("Condition $$tagInfo{Name}: " . CleanWarning()) if $evalWarning; + next; + } + } + # don't return Unknown tags unless that option is set (also see forum13716) + if ($$tagInfo{Unknown} and not $$self{OPTIONS}{Unknown} and not + ($$self{OPTIONS}{Verbose} or $$self{HTML_DUMP} or + ($$self{OPTIONS}{Validate} and not $$tagInfo{AddedUnknown}))) + { + return undef; + } + # return the tag information we found + return $tagInfo; + } + # generate information for unknown tags (numerical only) if required + if (not $tagInfo and ($$self{OPTIONS}{Unknown} or $$self{OPTIONS}{Verbose}) and + $tagID =~ /^\d+$/ and not $$self{NO_UNKNOWN}) + { + my $printConv; + if (defined $$tagTablePtr{PRINT_CONV}) { + $printConv = $$tagTablePtr{PRINT_CONV}; + } else { + # limit length of printout (can be very long) + $printConv = 'length($val) > 60 ? substr($val,0,55) . "[...]" : $val'; + } + my $hex = sprintf("0x%.4x", $tagID); + my $prefix = $$tagTablePtr{TAG_PREFIX}; + $tagInfo = { + Name => "${prefix}_$hex", + Description => MakeDescription($prefix, $hex), + Unknown => 1, + Writable => 0, # can't write unknown tags + PrintConv => $printConv, + AddedUnknown => 1, + }; + # add tag information to table + AddTagToTable($tagTablePtr, $tagID, $tagInfo); + } else { + undef $tagInfo; + } + return $tagInfo; +} + +#------------------------------------------------------------------------------ +# Add new tag to table (must use this routine to add new tags to a table) +# Inputs: 0) reference to tag table, 1) tag ID +# 2) [optional] tag name or reference to tag information hash +# 3) [optional] flag to avoid adding prefix when generating tag name +# Returns: tagInfo ref +# Notes: - will not override existing entry in table +# - info need contain no entries when this routine is called +# - tag name is cleaned if necessary +sub AddTagToTable($$;$$) +{ + my ($tagTablePtr, $tagID, $tagInfo, $noPrefix) = @_; + + # generate tag info hash if necessary + $tagInfo = $tagInfo ? { Name => $tagInfo } : { } unless ref $tagInfo eq 'HASH'; + + # define necessary entries in information hash + if ($$tagInfo{Groups}) { + # fill in default groups from table GROUPS + foreach (keys %{$$tagTablePtr{GROUPS}}) { + next if $$tagInfo{Groups}{$_}; + $$tagInfo{Groups}{$_} = $$tagTablePtr{GROUPS}{$_}; + } + } else { + $$tagInfo{Groups} = { %{$$tagTablePtr{GROUPS}} }; + } + $$tagInfo{Flags} and ExpandFlags($tagInfo); + $$tagInfo{GotGroups} = 1, + $$tagInfo{Table} = $tagTablePtr; + $$tagInfo{TagID} = $tagID; + if (defined $$tagTablePtr{AVOID} and not defined $$tagInfo{Avoid}) { + $$tagInfo{Avoid} = $$tagTablePtr{AVOID}; + } + + my $name = $$tagInfo{Name}; + $name = $tagID unless defined $name; + $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters + $name = ucfirst $name; # capitalize first letter + # add tag-name prefix if specified and tag name not provided + unless (defined $$tagInfo{Name} or $noPrefix or not $$tagTablePtr{TAG_PREFIX}) { + # make description to prevent tagID from getting mangled by MakeDescription() + $$tagInfo{Description} = MakeDescription($$tagTablePtr{TAG_PREFIX}, $name); + $name = "$$tagTablePtr{TAG_PREFIX}_$name"; + } + # tag names must be at least 2 characters long and prefer them to start with a letter + $name = "Tag$name" if length($name) < 2 or $name !~ /^[A-Z]/i; + $$tagInfo{Name} = $name; + # add tag to table, but never override existing entries (could potentially happen + # if someone thinks there isn't any tagInfo because a condition wasn't satisfied) + unless (defined $$tagTablePtr{$tagID} or $specialTags{$tagID}) { + $$tagTablePtr{$tagID} = $tagInfo; + } + $$tagInfo{AddedUnknown} = 1 if $$tagInfo{Unknown}; + return $tagInfo; +} + +#------------------------------------------------------------------------------ +# Handle simple extraction of new tag information +# Inputs: 0) ExifTool object ref, 1) tag table reference, 2) tagID, 3) value, +# 4-N) parameters hash: Index, DataPt, DataPos, Base, Start, Size, Parent, +# TagInfo, ProcessProc, RAF, Format, Count +# Returns: tag key or undef if tag not found +# Notes: if value is not defined, it is extracted from DataPt using TagInfo +# Format and Count if provided +sub HandleTag($$$$;%) +{ + my ($self, $tagTablePtr, $tag, $val, %parms) = @_; + my $verbose = $$self{OPTIONS}{Verbose}; + my $pfmt = $parms{Format}; + my $tagInfo = $parms{TagInfo} || $self->GetTagInfo($tagTablePtr, $tag, \$val, $pfmt, $parms{Count}); + my $dataPt = $parms{DataPt}; + my ($subdir, $format, $noTagInfo, $rational); + + if ($tagInfo) { + $subdir = $$tagInfo{SubDirectory}; + } else { + return undef unless $verbose; + $tagInfo = { Name => "tag $tag" }; # create temporary tagInfo hash + $noTagInfo = 1; + } + # read value if not done already (not necessary for subdir) + unless (defined $val or ($subdir and not $$tagInfo{Writable} and not $$tagInfo{RawConv})) { + my $start = $parms{Start} || 0; + my $dLen = $dataPt ? length($$dataPt) : -1; + my $size = $parms{Size}; + $size = $dLen unless defined $size; + # read from data in memory if possible + if ($start >= 0 and $start + $size <= $dLen) { + $format = $$tagInfo{Format} || $$tagTablePtr{FORMAT}; + $format = $pfmt if not $format and $pfmt and $formatSize{$pfmt}; + if ($format) { + $val = ReadValue($dataPt, $start, $format, $$tagInfo{Count}, $size, \$rational); + } else { + $val = substr($$dataPt, $start, $size); + } + } else { + $self->Warn("Error extracting value for $$tagInfo{Name}"); + return undef; + } + } + # do verbose print if necessary + if ($verbose) { + undef $tagInfo if $noTagInfo; + $parms{Value} = $val; + $parms{Value} .= " ($rational)" if defined $rational; + $parms{Table} = $tagTablePtr; + if ($format) { + my $count = int(($parms{Size} || 0) / ($formatSize{$format} || 1)); + $parms{Format} = $format . "[$count]"; + } + $self->VerboseInfo($tag, $tagInfo, %parms); + } + if ($tagInfo) { + if ($subdir) { + my $subdirStart = $parms{Start}; + my $subdirLen = $parms{Size}; + if ($$tagInfo{RawConv} and not $$tagInfo{Writable}) { + my $conv = $$tagInfo{RawConv}; + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + if (ref $conv eq 'CODE') { + $val = &$conv($val, $self); + } else { + my ($priority, @grps); + # NOTE: RawConv is evaluated in Writer.pl and twice in ExifTool.pm + #### eval RawConv ($self, $val, $tag, $tagInfo, $priority, @grps) + $val = eval $conv; + $@ and $evalWarning = $@; + } + $self->Warn("RawConv $tag: " . CleanWarning()) if $evalWarning; + return undef unless defined $val; + $val = $$val if ref $val eq 'SCALAR'; + $dataPt = \$val; + $subdirStart = 0; + $subdirLen = length $val; + } + if ($$subdir{Start}) { + my $valuePtr = 0; + #### eval Start ($valuePtr) + my $off = eval $$subdir{Start}; + $subdirStart += $off; + $subdirLen -= $off; + } + $dataPt or $dataPt = \$val; + # process subdirectory information + my %dirInfo = ( + DirName => $$subdir{DirName} || $$tagInfo{Name}, + DataPt => $dataPt, + DataLen => length $$dataPt, + DataPos => $parms{DataPos}, + DirStart => $subdirStart, + DirLen => $subdirLen, + Parent => $parms{Parent}, + Base => $parms{Base}, + Multi => $$subdir{Multi}, + TagInfo => $tagInfo, + RAF => $parms{RAF}, + ); + my $oldOrder = GetByteOrder(); + if ($$subdir{ByteOrder}) { + if ($$subdir{ByteOrder} eq 'Unknown') { + if ($subdirStart + 2 <= $subdirLen) { + # attempt to determine the byte ordering of an IFD-style subdirectory + my $num = Get16u($dataPt, $subdirStart); + ToggleByteOrder if $num & 0xff00 and ($num>>8) > ($num&0xff); + } + } else { + SetByteOrder($$subdir{ByteOrder}); + } + } + my $subTablePtr = GetTagTable($$subdir{TagTable}) || $tagTablePtr; + $self->ProcessDirectory(\%dirInfo, $subTablePtr, $$subdir{ProcessProc} || $parms{ProcessProc}); + SetByteOrder($oldOrder); + # return now unless directory is writable as a block + return undef unless $$tagInfo{Writable}; + } + my $key = $self->FoundTag($tagInfo, $val); + # save original components of rational numbers + $$self{RATIONAL}{$key} = $rational if defined $rational and defined $key; + return $key; + } + return undef; +} + +#------------------------------------------------------------------------------ +# Add tag to hash of extracted information +# Inputs: 0) ExifTool object reference +# 1) reference to tagInfo hash or tag name +# 2) data value (or reference to require hash if Composite) +# 3) optional family 0 group, 4) optional family 1 group +# Returns: tag key or undef if no value +sub FoundTag($$$;@) +{ + local $_; + my ($self, $tagInfo, $value, @grps) = @_; + my ($tag, $noListDel, $tbl); + my $options = $$self{OPTIONS}; + + if (ref $tagInfo eq 'HASH') { + $tag = $$tagInfo{Name} or warn("No tag name\n"), return undef; + $tbl = $$tagInfo{Table}; + } else { + $tag = $tagInfo; + # look for tag in Extra + $tbl = GetTagTable('Image::ExifTool::Extra'); + $tagInfo = $self->GetTagInfo($tbl, $tag); + # make temporary hash if tag doesn't exist in Extra + # (not advised to do this since the tag won't show in list) + $tagInfo or $tagInfo = { Name => $tag, Groups => \%allGroupsExifTool }; + $$options{Verbose} and $self->VerboseInfo(undef, $tagInfo, Value => $value); + } + # get tag priority + my $priority = $$tagInfo{Priority}; + unless (defined $priority) { + $priority = $$tbl{PRIORITY}; + $priority = 0 if not defined $priority and $$tagInfo{Avoid}; + } + $grps[0] or $grps[0] = $$self{SET_GROUP0}; + $grps[1] or $grps[1] = $$self{SET_GROUP1}; + my $valueHash = $$self{VALUE}; + + if ($$tagInfo{RawConv}) { + # initialize @val for use in Composite RawConv expressions + my @val; + if (ref $value eq 'HASH' and $$tagInfo{IsComposite}) { + foreach (keys %$value) { $val[$_] = $$valueHash{$$value{$_}}; } + } + my $conv = $$tagInfo{RawConv}; + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + if (ref $conv eq 'CODE') { + $value = &$conv($value, $self); + $$self{grps} and @grps = @{$$self{grps}}, delete $$self{grps}; + } else { + my $val = $value; # do this so eval can use $val + # NOTE: RawConv is also evaluated in Writer.pl + #### eval RawConv ($self, $val, $tag, $tagInfo, $priority, @grps) + $value = eval $conv; + $@ and $evalWarning = $@; + } + $self->Warn("RawConv $tag: " . CleanWarning()) if $evalWarning; + return undef unless defined $value; + } + # ignore specified tags (AFTER doing RawConv if necessary!) + if ($$options{IgnoreTags}) { + if ($$options{IgnoreTags}{all}) { + return undef unless $$self{REQ_TAG_LOOKUP}{lc $tag}; + } else { + return undef if $$options{IgnoreTags}{lc $tag}; + } + } + # handle duplicate tag names + if (defined $$valueHash{$tag}) { + # add to list if there is an active list for this tag + if ($$self{LIST_TAGS}{$tagInfo}) { + $tag = $$self{LIST_TAGS}{$tagInfo}; # use key from previous list tag + if (defined $$self{NO_LIST}) { + # accumulate list in TAG_EXTRA "NoList" element + if (defined $$self{TAG_EXTRA}{$tag}{NoList}) { + push @{$$self{TAG_EXTRA}{$tag}{NoList}}, $value; + } else { + $$self{TAG_EXTRA}{$tag}{NoList} = [ $$valueHash{$tag}, $value ]; + } + $noListDel = 1; # set flag to delete this tag if re-listed + } else { + if (ref $$valueHash{$tag} ne 'ARRAY') { + $$valueHash{$tag} = [ $$valueHash{$tag} ]; + } + push @{$$valueHash{$tag}}, $value; + return $tag; # return without creating a new entry + } + } + # get next available tag key + my $nextInd = $$self{DUPL_TAG}{$tag} = ($$self{DUPL_TAG}{$tag} || 0) + 1; + my $nextTag = "$tag ($nextInd)"; +# +# take tag with highest priority +# + # promote existing 0-priority tag so it takes precedence over a new 0-tag + # (unless old tag was a sub-document and new tag isn't. Also, never override + # a Warning tag because they may be added by ValueConv, which could be confusing) + my $oldPriority = $$self{PRIORITY}{$tag}; + unless ($oldPriority) { + if ($$self{DOC_NUM} or not $$self{TAG_EXTRA}{$tag} or $tag eq 'Warning' or + not $$self{TAG_EXTRA}{$tag}{G3}) + { + $oldPriority = 1; + } else { + $oldPriority = 0; # don't promote sub-document tag over main document + } + } + # set priority for this tag + if (defined $priority) { + # increase 0-priority tags if this is the priority directory + $priority = 1 if not $priority and $$self{DIR_NAME} and + $$self{DIR_NAME} eq $$self{PRIORITY_DIR}; + } elsif ($$self{LOW_PRIORITY_DIR}{'*'} or + ($$self{DIR_NAME} and $$self{LOW_PRIORITY_DIR}{$$self{DIR_NAME}})) + { + $priority = 0; # default is 0 for a LOW_PRIORITY_DIR + } else { + $priority = 1; # the normal default + } + if ($priority >= $oldPriority and (not $$self{DOC_NUM} or + ($$self{TAG_EXTRA}{$tag} and $$self{TAG_EXTRA}{$tag}{G3} and + $$self{DOC_NUM} eq $$self{TAG_EXTRA}{$tag}{G3})) and not $noListDel) + { + # move existing tag out of the way since this tag is higher priority + # (NOTE: any new members added here must also be added to DeleteTag()) + $$self{PRIORITY}{$nextTag} = $$self{PRIORITY}{$tag}; + $$valueHash{$nextTag} = $$valueHash{$tag}; + $$self{FILE_ORDER}{$nextTag} = $$self{FILE_ORDER}{$tag}; + my $oldInfo = $$self{TAG_INFO}{$nextTag} = $$self{TAG_INFO}{$tag}; + foreach ('TAG_EXTRA','RATIONAL') { + if ($$self{$_}{$tag}) { + $$self{$_}{$nextTag} = $$self{$_}{$tag}; + delete $$self{$_}{$tag}; + } + } + delete $$self{BOTH}{$tag}; + # update tag key for list if necessary + $$self{LIST_TAGS}{$oldInfo} = $nextTag if $$self{LIST_TAGS}{$oldInfo}; + # update this key if used in a Composite tag + if ($$self{COMP_KEYS}{$tag}) { + $$_[0]{$$_[1]} = $nextTag foreach @{$$self{COMP_KEYS}{$tag}}; + $$self{COMP_KEYS}{$nextTag} = $$self{COMP_KEYS}{$tag}; + delete $$self{COMP_KEYS}{$tag}; + } + } else { + $tag = $nextTag; # don't override the existing tag + } + $$self{PRIORITY}{$tag} = $priority; + $$self{TAG_EXTRA}{$tag}{NoListDel} = 1 if $noListDel; + } elsif ($priority) { + # set tag priority (only if exists and is non-zero) + $$self{PRIORITY}{$tag} = $priority; + } + + # save the raw value, file order, tagInfo ref, group1 name, + # and tag key for lists if necessary + $$valueHash{$tag} = $value; + $$self{FILE_ORDER}{$tag} = ++$$self{NUM_FOUND}; + $$self{TAG_INFO}{$tag} = $tagInfo; + # set dynamic groups 0, 1 and 3 if necessary + $$self{TAG_EXTRA}{$tag}{G0} = $grps[0] if $grps[0]; + $$self{TAG_EXTRA}{$tag}{G1} = $grps[1] if $grps[1]; + if ($$self{DOC_NUM}) { + $$self{TAG_EXTRA}{$tag}{G3} = $$self{DOC_NUM}; + if ($$self{DOC_NUM} =~ /^(\d+)/) { + # keep track of maximum 1st-level sub-document number + $$self{DOC_COUNT} = $1 unless $$self{DOC_COUNT} >= $1; + } + } + # save path if requested + $$self{TAG_EXTRA}{$tag}{G5} = $self->MetadataPath() if $$options{SavePath}; + + # remember this tagInfo if we will be accumulating values in a list + # (but don't override earlier list if this may be deleted by NoListDel flag) + if ($$tagInfo{List} and not $$self{NO_LIST} and not $noListDel) { + $$self{LIST_TAGS}{$tagInfo} = $tag; + } + + # validate tag if requested (but only for simple values -- could result + # in infinite recursion if called for a Composite tag (HASH ref value) + # because FoundTag is called in the middle of building Composite tags + if ($$options{Validate} and not ref $value) { + Image::ExifTool::Validate::ValidateRaw($self, $tag, $value); + } + + return $tag; +} + +#------------------------------------------------------------------------------ +# Make current directory the priority directory if not set already +# Inputs: 0) ExifTool object reference +sub SetPriorityDir($) +{ + my $self = shift; + $$self{PRIORITY_DIR} = $$self{DIR_NAME} unless $$self{PRIORITY_DIR}; +} + +#------------------------------------------------------------------------------ +# Set family 0 or 1 group name specific to this tag instance +# Inputs: 0) ExifTool ref, 1) tag key, 2) group name, 3) family (default 1) +sub SetGroup($$$;$) +{ + my ($self, $tagKey, $extra, $fam) = @_; + $$self{TAG_EXTRA}{$tagKey}{defined $fam ? "G$fam" : 'G1'} = $extra; +} + +#------------------------------------------------------------------------------ +# Delete specified tag +# Inputs: 0) ExifTool object ref, 1) tag key +sub DeleteTag($$) +{ + my ($self, $tag) = @_; + delete $$self{VALUE}{$tag}; + delete $$self{FILE_ORDER}{$tag}; + delete $$self{TAG_INFO}{$tag}; + delete $$self{TAG_EXTRA}{$tag}; + delete $$self{PRIORITY}{$tag}; + delete $$self{RATIONAL}{$tag}; + delete $$self{BOTH}{$tag}; +} + +#------------------------------------------------------------------------------ +# Escape all elements of a value +# Inputs: 0) value, 1) escape proc +sub DoEscape($$) +{ + my ($val, $key); + if (not ref $_[0]) { + $_[0] = &{$_[1]}($_[0]); + } elsif (ref $_[0] eq 'ARRAY') { + foreach $val (@{$_[0]}) { + DoEscape($val, $_[1]); + } + } elsif (ref $_[0] eq 'HASH') { + foreach $key (keys %{$_[0]}) { + DoEscape($_[0]{$key}, $_[1]); + } + } +} + +#------------------------------------------------------------------------------ +# Set the FileType and MIMEType tags +# Inputs: 0) ExifTool object reference +# 1) Optional file type (uses FILE_TYPE if not specified) +# 2) Optional MIME type (uses our lookup if not specified) +# 3) Optional recommended extension (converted to lower case; uses FileType if undef) +# Notes: Will NOT set file type twice (subsequent calls ignored) +sub SetFileType($;$$$) +{ + my ($self, $fileType, $mimeType, $normExt) = @_; + unless ($$self{FileType} and not $$self{DOC_NUM}) { + my $baseType = $$self{FILE_TYPE}; + my $ext = $$self{FILE_EXT}; + $fileType or $fileType = $baseType; + # handle sub-types which are identified by extension + if (defined $ext and $ext ne $fileType and not $$self{DOC_NUM}) { + my ($f,$e) = @fileTypeLookup{$fileType,$ext}; + if (ref $f eq 'ARRAY' and ref $e eq 'ARRAY' and $$f[0] eq $$e[0]) { + # make sure $fileType was a root type and not another sub-type + $fileType = $ext if $$f[0] eq $fileType or not $fileTypeLookup{$$f[0]}; + } + } + $mimeType or $mimeType = $mimeType{$fileType}; + # use base file type if necessary (except if 'TIFF', which is a special case) + $mimeType = $mimeType{$baseType} unless $mimeType or $baseType eq 'TIFF'; + unless (defined $normExt) { + $normExt = $fileTypeExt{$fileType}; + $normExt = $fileType unless defined $normExt; + } + # ($$self{FileType} is the file type of the main document) + $$self{FileType} = $fileType unless $$self{DOC_NUM}; + $self->FoundTag('FileType', $fileType); + $self->FoundTag('FileTypeExtension', uc $normExt); + $self->FoundTag('MIMEType', $mimeType || 'application/unknown'); + } +} + +#------------------------------------------------------------------------------ +# Override the FileType and MIMEType tags +# Inputs: 0) ExifTool object ref, 1) file type, 2) MIME type, 3) normal extension (lower case) +# Notes: does nothing if FileType was not previously defined (ie. when writing) +sub OverrideFileType($$;$$) +{ + my ($self, $fileType, $mimeType, $normExt) = @_; + if (defined $$self{VALUE}{FileType} and $fileType ne $$self{VALUE}{FileType}) { + $$self{FileType} = $fileType; + $$self{VALUE}{FileType} = $fileType; + unless (defined $normExt) { + $normExt = $fileTypeExt{$fileType}; + $normExt = $fileType unless defined $normExt; + } + $$self{VALUE}{FileTypeExtension} = uc $normExt; + $mimeType or $mimeType = $mimeType{$fileType}; + $$self{VALUE}{MIMEType} = $mimeType if $mimeType; + if ($$self{OPTIONS}{Verbose}) { + $self->VPrint(0,"$$self{INDENT}FileType [override] = $fileType\n"); + $self->VPrint(0,"$$self{INDENT}FileTypeExtension [override] = $$self{VALUE}{FileTypeExtension}\n"); + $self->VPrint(0,"$$self{INDENT}MIMEType [override] = $mimeType\n") if $mimeType; + } + } +} + +#------------------------------------------------------------------------------ +# Modify the value of the MIMEType tag +# Inputs: 0) ExifTool object reference, 1) file or MIME type +# Notes: combines existing type with new type: ie) a/b + c/d => c/b-d +sub ModifyMimeType($;$) +{ + my ($self, $mime) = @_; + $mime =~ m{/} or $mime = $mimeType{$mime} or return; + my $old = $$self{VALUE}{MIMEType}; + if (defined $old) { + my ($a, $b) = split '/', $old; + my ($c, $d) = split '/', $mime; + $d =~ s/^x-//; + $$self{VALUE}{MIMEType} = "$c/$b-$d"; + $self->VPrint(0, " Modified MIMEType = $c/$b-$d\n"); + } else { + $self->FoundTag('MIMEType', $mime); + } +} + +#------------------------------------------------------------------------------ +# Print verbose output +# Inputs: 0) ExifTool ref, 1) verbose level (prints if level > this), 2-N) print args +sub VPrint($$@) +{ + my $self = shift; + my $level = shift; + if ($$self{OPTIONS}{Verbose} and $$self{OPTIONS}{Verbose} > $level) { + my $out = $$self{OPTIONS}{TextOut}; + print $out @_; + print $out "\n" unless $_[-1] =~ /\n$/; + } +} + +#------------------------------------------------------------------------------ +# Print verbose directory information +# Inputs: 0) ExifTool object reference, 1) directory name or dirInfo ref +# 2) number of entries in directory (or 0 if unknown) +# 3) optional size of directory in bytes +sub VerboseDir($$;$$) +{ + my ($self, $name, $entries, $size) = @_; + return unless $$self{OPTIONS}{Verbose}; + if (ref $name eq 'HASH') { + $size = $$name{DirLen} unless $size; + $name = $$name{Name} || $$name{DirName}; + } + my $indent = substr($$self{INDENT}, 0, -2); + my $out = $$self{OPTIONS}{TextOut}; + my $str = ($entries or defined $entries and not $size) ? " with $entries entries" : ''; + $str .= ", $size bytes" if $size; + print $out "$indent+ [$name directory$str]\n"; +} + +#------------------------------------------------------------------------------ +# Verbose dump +# Inputs: 0) ExifTool ref, 1) data ref, 2-N) HexDump options +sub VerboseDump($$;%) +{ + my $self = shift; + my $dataPt = shift; + my $verbose = $$self{OPTIONS}{Verbose}; + if ($verbose and $verbose > 2) { + my %parms = ( + Prefix => $$self{INDENT}, + Out => $$self{OPTIONS}{TextOut}, + MaxLen => $verbose < 4 ? 96 : $verbose < 5 ? 2048 : undef, + ); + HexDump($dataPt, undef, %parms, @_); + } +} + +#------------------------------------------------------------------------------ +# Print data in hex +# Inputs: 0) data +# Returns: hex string +# (this is a convenience function for use in debugging PrintConv statements) +sub PrintHex($) +{ + my $val = shift; + return join(' ', unpack('H2' x length($val), $val)); +} + +#------------------------------------------------------------------------------ +# Extract binary data from file +# 0) ExifTool object reference, 1) offset, 2) length, 3) tag name if conditional +# Returns: binary data, or undef on error +# Notes: Returns "Binary data #### bytes" instead of data unless tag is +# specifically requested or the Binary option is set +sub ExtractBinary($$$;$) +{ + my ($self, $offset, $length, $tag) = @_; + my ($isPreview, $buff); + + if ($tag) { + if ($tag eq 'PreviewImage') { + # save PreviewImage start/length in case we want to dump trailer + $$self{PreviewImageStart} = $offset; + $$self{PreviewImageLength} = $length; + $isPreview = 1; + } + my $lcTag = lc $tag; + if ((not $$self{OPTIONS}{Binary} or $$self{EXCL_TAG_LOOKUP}{$lcTag}) and + not $$self{OPTIONS}{Verbose} and not $$self{REQ_TAG_LOOKUP}{$lcTag}) + { + return "Binary data $length bytes"; + } + } + unless ($$self{RAF}->Seek($offset,0) + and $$self{RAF}->Read($buff, $length) == $length) + { + $tag or $tag = 'binary data'; + if ($isPreview and not $$self{BuildingComposite}) { + $$self{PreviewError} = 1; + } else { + $self->Warn("Error reading $tag from file", $isPreview); + } + return undef; + } + return $buff; +} + +#------------------------------------------------------------------------------ +# Process binary data +# Inputs: 0) ExifTool object ref, 1) directory information ref, 2) tag table ref +# Returns: 1 on success +# Notes: dirInfo may contain VarFormatData (reference to empty list) to return +# details about any variable-length-format tags in the table (used when writing) +sub ProcessBinaryData($$$) +{ + my ($self, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = length $$dataPt; + my $dirStart = $$dirInfo{DirStart} || 0; + my $maxLen = $dataLen - $dirStart; + my $size = $$dirInfo{DirLen}; + my $base = $$dirInfo{Base} || 0; + my $verbose = $$self{OPTIONS}{Verbose}; + my $unknown = $$self{OPTIONS}{Unknown}; + my $dataPos = $$dirInfo{DataPos} || 0; + + $size = $maxLen if not defined $size or $size > $maxLen; + # get default format ('int8u' unless specified) + my $defaultFormat = $$tagTablePtr{FORMAT} || 'int8u'; + my $increment = $formatSize{$defaultFormat}; + unless ($increment) { + warn "Unknown format $defaultFormat\n"; + $defaultFormat = 'int8u'; + $increment = $formatSize{$defaultFormat}; + } + # prepare list of tag numbers to extract + my (@tags, $topIndex); + if ($unknown > 1 and defined $$tagTablePtr{FIRST_ENTRY}) { + # don't create a stupid number of tags if data is huge + my $sizeLimit = $size < 65536 ? $size : 65536; + # scan through entire binary table + $topIndex = int($sizeLimit/$increment); + @tags = ($$tagTablePtr{FIRST_ENTRY}..($topIndex - 1)); + # add in floating point tag ID's if they exist + my @ftags = grep /\./, TagTableKeys($tagTablePtr); + @tags = sort { $a <=> $b } @tags, @ftags if @ftags; + } elsif ($$dirInfo{DataMember}) { + @tags = @{$$dirInfo{DataMember}}; + $verbose = 0; # no verbose output of extracted values when writing + } elsif ($$dirInfo{MixedTags}) { + # process sorted integer-ID tags only + @tags = sort { $a <=> $b } grep /^\d+$/, TagTableKeys($tagTablePtr); + } else { + # extract known tags in numerical order + @tags = sort { ($a < 0 ? $a + 1e9 : $a) <=> ($b < 0 ? $b + 1e9 : $b) } TagTableKeys($tagTablePtr); + } + $self->VerboseDir('BinaryData', undef, $size) if $verbose; + # avoid creating unknown tags for tags that fail condition if Unknown is 1 + $$self{NO_UNKNOWN} = 1 if $unknown < 2; + my ($index, %val); + my $nextIndex = 0; + my $varSize = 0; + foreach $index (@tags) { + my ($tagInfo, $val, $saveNextIndex, $len, $mask, $wasVar, $rational); + if ($$tagTablePtr{$index}) { + $tagInfo = $self->GetTagInfo($tagTablePtr, $index); + unless ($tagInfo) { + next unless defined $tagInfo; + # $entry = offset of value relative to directory start (or end if negative) + my $entry = int($index) * $increment + $varSize; + if ($entry < 0) { + $entry += $size; + next if $entry < 0; + } + next if $entry >= $size; + my $more = $size - $entry; + $more = 128 if $more > 128; + my $v = substr($$dataPt, $entry+$dirStart, $more); + $tagInfo = $self->GetTagInfo($tagTablePtr, $index, \$v); + next unless $tagInfo; + } + next if $$tagInfo{Unknown} and + ($$tagInfo{Unknown} > $unknown or $index < $nextIndex); + } elsif ($topIndex and $$tagTablePtr{$index - $topIndex}) { + $tagInfo = $self->GetTagInfo($tagTablePtr, $index - $topIndex) or next; + } else { + # don't generate unknown tags in binary tables unless Unknown > 1 + next unless $unknown > 1; + next if $index < $nextIndex; # skip if data already used + $tagInfo = $self->GetTagInfo($tagTablePtr, $index) or next; + $$tagInfo{Unknown} = 2; # set unknown to 2 for binary unknowns + } + # get relative offset of this entry + my $entry = int($index) * $increment + $varSize; + # allow negative indices to represent bytes from end + if ($entry < 0) { + $entry += $size; + next if $entry < 0; + } + my $more = $size - $entry; + last if $more <= 0; # all done if we have reached the end of data + my $count = 1; + my $format = $$tagInfo{Format}; + if (not $format) { + $format = $defaultFormat; + } elsif ($format eq 'string') { + # string with no specified count runs to end of block + $count = $more; + } elsif ($format eq 'pstring') { + $format = 'string'; + $count = Get8u($dataPt, ($entry++)+$dirStart); + --$more; + } elsif (not $formatSize{$format}) { + if ($format =~ /(.*)\[(.*)\]/) { + # handle format count field + $format = $1; + $count = $2; + # evaluate count to allow count to be based on previous values + #### eval Format size (%val, $size, $self) + $count = eval $count; + $@ and warn("Format $$tagInfo{Name}: $@"), next; + next if $count < 0; + # allow a variable-length value of any format + # (note: the next incremental index points to data immediately after + # this value, regardless of the size of this value, even if it is zero) + if ($format =~ s/^var_//) { + $varSize += $count * ($formatSize{$format} || 1) - $increment; + $wasVar = 1; + # save variable size data if required for writing + if ($$dirInfo{VarFormatData}) { + push @{$$dirInfo{VarFormatData}}, [ $index, $varSize, $format ]; + } + # don't extract value if large and we wanted it just to get + # the variable-format information when writing + next if $$tagInfo{LargeTag} and $$dirInfo{VarFormatData}; + } + } elsif ($format =~ /^var_/) { + # handle variable-length string formats + $format = substr($format, 4); + pos($$dataPt) = $entry + $dirStart; + undef $count; + if ($format eq 'ustring') { + $count = pos($$dataPt) - ($entry+$dirStart) if $$dataPt =~ /\G(..)*?\0\0/sg; + $varSize -= 2; # ($count includes base size of 2 bytes) + } elsif ($format eq 'pstring') { + $count = Get8u($dataPt, ($entry++)+$dirStart); + --$more; + } elsif ($format eq 'pstr32' or $format eq 'ustr32') { + last if $more < 4; + $count = Get32u($dataPt, $entry + $dirStart); + $count *= 2 if $format eq 'ustr32'; + $entry += 4; + $more -= 4; + $nextIndex += 4 / $increment; # (increment next index for int32u) + } elsif ($format eq 'int16u') { + # int16u size of binary data to follow + last if $more < 2; + $count = Get16u($dataPt, $entry + $dirStart) + 2; + $varSize -= 2; # ($count includes size word) + $format = 'undef'; + } elsif ($format eq 'ue7') { + require Image::ExifTool::BPG; + ($val, $count) = Image::ExifTool::BPG::Get_ue7($dataPt, $entry + $dirStart); + last unless defined $val; + --$varSize; # ($count includes base size of 1 byte) + } elsif ($$dataPt =~ /\0/g) { + $count = pos($$dataPt) - ($entry+$dirStart); + --$varSize; # ($count includes base size of 1 byte) + } + $count = $more if not defined $count or $count > $more; + $varSize += $count; # shift subsequent indices + unless (defined $val) { + $val = substr($$dataPt, $entry+$dirStart, $count); + $val = $self->Decode($val, 'UCS2') if $format eq 'ustring' or $format eq 'ustr32'; + $val =~ s/\0.*//s unless $format eq 'undef'; # truncate at null + } + $wasVar = 1; + # save variable size data if required for writing + if ($$dirInfo{VarFormatData}) { + push @{$$dirInfo{VarFormatData}}, [ $index, $varSize, $format ]; + } + } + } + # hook to allow format, etc to be set dynamically + if (defined $$tagInfo{Hook}) { + my $oldVarSize = $varSize; + my $pos = $entry + $dirStart; + #### eval Hook ($format, $varSize, $size, $dataPt, $pos) + eval $$tagInfo{Hook}; + # save variable size data if required for writing (in case changed by Hook) + if ($$dirInfo{VarFormatData}) { + $#{$$dirInfo{VarFormatData}} -= 1 if $wasVar; # remove previous entry for this tag + push @{$$dirInfo{VarFormatData}}, [ $index, $varSize, $format ]; + } elsif ($varSize != $oldVarSize and $verbose > 2) { + my ($tmp, $sign) = ($varSize, '+'); + $tmp < 0 and $tmp = -$tmp, $sign = '-'; + $self->VPrint(2, sprintf("$$self{INDENT}\[offsets adjusted by ${sign}0x%.4x after 0x%.4x $$tagInfo{Name}]\n", $tmp, $index)); + } + } + if ($unknown > 1) { + # calculate next valid index for unknown tag + my $ni = int $index; + $ni += (($formatSize{$format} || 1) * $count) / $increment unless $wasVar; + $saveNextIndex = $nextIndex; + $nextIndex = $ni unless $nextIndex > $ni; + } + # allow large tags to be excluded from extraction + # (provides a work-around for some tight memory situations) + next if $$tagInfo{LargeTag} and $$self{EXCL_TAG_LOOKUP}{lc $$tagInfo{Name}}; + # read value now if necessary + unless (defined $val and not $$tagInfo{SubDirectory}) { + $val = ReadValue($dataPt, $entry+$dirStart, $format, $count, $more, \$rational); + next unless defined $val; + $mask = $$tagInfo{Mask}; + $val = ($val & $mask) >> $$tagInfo{BitShift} if $mask; + } + if ($verbose and not $$tagInfo{Hidden}) { + if (not $$tagInfo{SubDirectory} or $$tagInfo{Format}) { + $len = $count * ($formatSize{$format} || 1); + $len = $more if $len > $more; + } else { + $len = $more; + } + $self->VerboseInfo($index, $tagInfo, + Table => $tagTablePtr, + Value => $val, + DataPt => $dataPt, + Size => $len, + Start => $entry+$dirStart, + Addr => $entry+$dirStart+$base+$dataPos, + Format => $format, + Count => $count, + Extra => $mask ? sprintf(', mask 0x%.2x',$mask) : undef, + ); + } + # parse nested BinaryData directories + if ($$tagInfo{SubDirectory}) { + my $subdir = $$tagInfo{SubDirectory}; + my $subTablePtr = GetTagTable($$subdir{TagTable}); + # use specified subdirectory length if given + if ($$tagInfo{Format} and $formatSize{$format}) { + $len = $count * $formatSize{$format}; + $len = $more if $len > $more; + } else { + $len = $more; # directory size is all of remaining data + if ($$subTablePtr{PROCESS_PROC} and + $$subTablePtr{PROCESS_PROC} eq \&ProcessBinaryData) + { + # the rest of the data will be printed in the subdirectory + $nextIndex = $size / $increment; + } + } + my $subdirBase = $base; + if (defined $$subdir{Base}) { + #### eval Base ($start,$base) + my $start = $entry + $dirStart + $dataPos; + $subdirBase = eval($$subdir{Base}) + $base; + } + my $start = $$subdir{Start} || 0; + if ($start =~ /\$/) { + # ignore directories with a zero offset (ie. missing Nikon ShotInfo entries) + next unless $val; + #### eval Start ($val, $dirStart) + $start = eval($start); + next if $start < $dirStart or $start > $dataLen; + $len = $$subdir{DirLen}; + $len = $dataLen - $start unless $len and $len <= $dataLen - $start; + } else { + $start += $dirStart + $entry; + } + my %subdirInfo = ( + DataPt => $dataPt, + DataPos => $dataPos, + DataLen => $dataLen, + DirStart => $start, + DirLen => $len, + Base => $subdirBase, + ); + delete $$self{NO_UNKNOWN}; + $self->ProcessDirectory(\%subdirInfo, $subTablePtr, $$subdir{ProcessProc}); + $$self{NO_UNKNOWN} = 1 if $unknown < 2; + next; + } + if ($$tagInfo{IsOffset} and $$tagInfo{IsOffset} ne '3') { + my $et = $self; + #### eval IsOffset ($val, $et) + $val += $base + $$self{BASE} if eval $$tagInfo{IsOffset}; + } + $val{$index} = $val; + my $oldBase; + if ($$tagInfo{SetBase}) { + $oldBase = $$self{BASE}; + $$self{BASE} += $base; + } + my $key = $self->FoundTag($tagInfo,$val); + $$self{BASE} = $oldBase if defined $oldBase; + if ($key) { + $$self{RATIONAL}{$key} = $rational if defined $rational; + } else { + # don't increment nextIndex if we didn't extract a tag + $nextIndex = $saveNextIndex if defined $saveNextIndex; + } + } + delete $$self{NO_UNKNOWN}; + return 1; +} + +#.............................................................................. +# Load .ExifTool_config file from user's home directory +# (use of noConfig is now deprecated, use configFile = '' instead) +until ($Image::ExifTool::noConfig) { + my $config = $Image::ExifTool::configFile; + my $file; + if (not defined $config) { + $config = '.ExifTool_config'; + # get our home directory (HOMEDRIVE and HOMEPATH are used in Windows cmd shell) + my $home = $ENV{EXIFTOOL_HOME} || $ENV{HOME} || + ($ENV{HOMEDRIVE} || '') . ($ENV{HOMEPATH} || '') || '.'; + # look for the config file in 1) the home directory, 2) the program dir + $file = "$home/$config"; + } else { + length $config or last; # filename of "" disables configuration + $file = $config; + } + # also check executable directory unless path is absolute + $exeDir = ($0 =~ /(.*)[\\\/]/) ? $1 : '.' unless defined $exeDir; + -r $file or $config =~ /^\// or $file = "$exeDir/$config"; + unless (-r $file) { + warn("Config file not found\n") if defined $Image::ExifTool::configFile; + last; + } + unshift @INC, '.'; # look in current directory first + eval { require $file }; # load the config file + shift @INC; + # print warning (minus "Compilation failed" part) + $@ and $_=$@, s/Compilation failed.*//s, warn $_; + last; +} +# read user-defined lenses (may have been defined by script instead of config file) +if (@Image::ExifTool::UserDefined::Lenses) { + foreach (@Image::ExifTool::UserDefined::Lenses) { + $Image::ExifTool::userLens{$_} = 1; + } +} +# add user-defined file types +if (%Image::ExifTool::UserDefined::FileTypes) { + foreach (sort keys %Image::ExifTool::UserDefined::FileTypes) { + my $fileInfo = $Image::ExifTool::UserDefined::FileTypes{$_}; + my $type = uc $_; + ref $fileInfo eq 'HASH' or $fileTypeLookup{$type} = $fileInfo, next; + my $baseType = $$fileInfo{BaseType}; + if ($baseType) { + if ($$fileInfo{Description}) { + $fileTypeLookup{$type} = [ $baseType, $$fileInfo{Description} ]; + } else { + $fileTypeLookup{$type} = $baseType; + } + if (defined $$fileInfo{Writable} and not $$fileInfo{Writable}) { + # first make sure we are using an actual base type and not a derived type + $baseType = $fileTypeLookup{$baseType} while $baseType and not ref $fileTypeLookup{$baseType}; + # mark this type as not writable + $noWriteFile{$baseType} or $noWriteFile{$baseType} = [ ]; + push @{$noWriteFile{$baseType}}, $type; + } + } else { + $fileTypeLookup{$type} = [ $type, $$fileInfo{Description} || $type ]; + $moduleName{$type} = 0; # not supported + if ($$fileInfo{Magic}) { + $magicNumber{$type} = $$fileInfo{Magic}; + push @fileTypes, $type unless grep /^$type$/, @fileTypes; + } + } + $mimeType{$type} = $$fileInfo{MIMEType} if defined $$fileInfo{MIMEType}; + } +} + +#------------------------------------------------------------------------------ +1; # end diff --git a/ExifTool/lib/Image/ExifTool.pod b/ExifTool/lib/Image/ExifTool.pod new file mode 100644 index 0000000..1155fc4 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool.pod @@ -0,0 +1,2897 @@ +#------------------------------------------------------------------------------ +# File: ExifTool.pod - Documentation for File::ExifTool +# +# Description: Read and write meta information +# +# URL: https://exiftool.org/ +# +# Legal: Copyright (c) 2003-2023, Phil Harvey (philharvey66 at gmail.com) +# This library is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +#------------------------------------------------------------------------------ + +=head1 NAME + +Image::ExifTool - Read and write meta information + +=head1 SYNOPSIS + + use Image::ExifTool qw(:Public); + + # ---- Simple procedural usage ---- + + # Get hash of meta information tag names/values from an image + $info = ImageInfo('a.jpg'); + + # ---- Object-oriented usage ---- + + # Create a new Image::ExifTool object + $exifTool = Image::ExifTool->new; + + # Extract meta information from an image + $exifTool->ExtractInfo($file, \%options); + + # Get list of tags in the order they were found in the file + @tagList = $exifTool->GetFoundTags('File'); + + # Get the value of a specified tag + $value = $exifTool->GetValue($tag, $type); + + # Get a tag description + $description = $exifTool->GetDescription($tag); + + # Get the group name associated with this tag + $group = $exifTool->GetGroup($tag, $family); + + # Set a new value for a tag + $exifTool->SetNewValue($tag, $newValue); + + # Write new meta information to a file + $success = $exifTool->WriteInfo($srcfile, $dstfile); + + # ...plus a host of other useful methods... + +=head1 DESCRIPTION + +Reads and writes meta information in a wide variety of files, including the +maker notes of many digital cameras by various manufacturers such as Canon, +Casio, DJI, FLIR, FujiFilm, GE, GoPro, HP, JVC/Victor, Kodak, Leaf, +Minolta/Konica-Minolta, Nikon, Nintendo, Olympus/Epson, Panasonic/Leica, +Pentax/Asahi, Phase One, Reconyx, Ricoh, Samsung, Sanyo, Sigma/Foveon and +Sony. + +Below is a list of file types and meta information formats currently +supported by ExifTool (r = read, w = write, c = create): + + File Types + ------------+-------------+-------------+-------------+------------ + 360 r/w | DPX r | ITC r | NUMBERS r | RAW r/w + 3FR r | DR4 r/w/c | J2C r | O r | RIFF r + 3G2 r/w | DSS r | JNG r/w | ODP r | RSRC r + 3GP r/w | DV r | JP2 r/w | ODS r | RTF r + 7Z r | DVB r/w | JPEG r/w | ODT r | RW2 r/w + A r | DVR-MS r | JSON r | OFR r | RWL r/w + AA r | DYLIB r | JXL r | OGG r | RWZ r + AAE r | EIP r | K25 r | OGV r | RM r + AAX r/w | EPS r/w | KDC r | ONP r | SEQ r + ACR r | EPUB r | KEY r | OPUS r | SKETCH r + AFM r | ERF r/w | LA r | ORF r/w | SO r + AI r/w | EXE r | LFP r | ORI r/w | SR2 r/w + AIFF r | EXIF r/w/c | LIF r | OTF r | SRF r + APE r | EXR r | LNK r | PAC r | SRW r/w + ARQ r/w | EXV r/w/c | LRV r/w | PAGES r | SVG r + ARW r/w | F4A/V r/w | M2TS r | PBM r/w | SWF r + ASF r | FFF r/w | M4A/V r/w | PCD r | THM r/w + AVI r | FITS r | MACOS r | PCX r | TIFF r/w + AVIF r/w | FLA r | MAX r | PDB r | TORRENT r + AZW r | FLAC r | MEF r/w | PDF r/w | TTC r + BMP r | FLIF r/w | MIE r/w/c | PEF r/w | TTF r + BPG r | FLV r | MIFF r | PFA r | TXT r + BTF r | FPF r | MKA r | PFB r | VCF r + CHM r | FPX r | MKS r | PFM r | VNT r + COS r | GIF r/w | MKV r | PGF r | VRD r/w/c + CR2 r/w | GLV r/w | MNG r/w | PGM r/w | VSD r + CR3 r/w | GPR r/w | MOBI r | PLIST r | WAV r + CRM r/w | GZ r | MODD r | PICT r | WDP r/w + CRW r/w | HDP r/w | MOI r | PMP r | WEBP r/w + CS1 r/w | HDR r | MOS r/w | PNG r/w | WEBM r + CSV r | HEIC r/w | MOV r/w | PPM r/w | WMA r + CUR r | HEIF r/w | MP3 r | PPT r | WMV r + CZI r | HTML r | MP4 r/w | PPTX r | WPG r + DCM r | ICC r/w/c | MPC r | PS r/w | WTV r + DCP r/w | ICO r | MPG r | PSB r/w | WV r + DCR r | ICS r | MPO r/w | PSD r/w | X3F r/w + DFONT r | IDML r | MQV r/w | PSP r | XCF r + DIVX r | IIQ r/w | MRC r | QTIF r/w | XLS r + DJVU r | IND r/w | MRW r/w | R3D r | XLSX r + DLL r | INSP r/w | MXF r | RA r | XMP r/w/c + DNG r/w | INSV r | NEF r/w | RAF r/w | ZIP r + DOC r | INX r | NKSC r/w | RAM r | + DOCX r | ISO r | NRW r/w | RAR r | + + Meta Information + ----------------------+----------------------+--------------------- + EXIF r/w/c | CIFF r/w | Ricoh RMETA r + GPS r/w/c | AFCP r/w | Picture Info r + IPTC r/w/c | Kodak Meta r/w | Adobe APP14 r + XMP r/w/c | FotoStation r/w | MPF r + MakerNotes r/w/c | PhotoMechanic r/w | Stim r + Photoshop IRB r/w/c | JPEG 2000 r | DPX r + ICC Profile r/w/c | DICOM r | APE r + MIE r/w/c | Flash r | Vorbis r + JFIF r/w/c | FlashPix r | SPIFF r + Ducky APP12 r/w/c | QuickTime r | DjVu r + PDF r/w/c | Matroska r | M2TS r + PNG r/w/c | MXF r | PE/COFF r + Canon VRD r/w/c | PrintIM r | AVCHD r + Nikon Capture r/w/c | FLAC r | ZIP r + GeoTIFF r/w/c | ID3 r | (and more) + +=head1 CONFIGURATION + +User-defined tags can be added via the ExifTool configuration file, or by +defining the %Image::ExifTool::UserDefined hash before calling any ExifTool +methods. See "ExifTool_config" in the ExifTool distribution for more +details. + +By default ExifTool looks for a configuration file named ".ExifTool_config" +first in your home directory, then in the directory of the application +script, but a different directory may be specified by setting the +EXIFTOOL_HOME environment variable, or a different file may be specified by +setting the ExifTool C variable before using Image::ExifTool. +For example: + + BEGIN { $Image::ExifTool::configFile = '/Users/phil/myconfig.cfg' } + use Image::ExifTool; + +The configuration feature may also be disabled by setting C to +an empty string: + + BEGIN { $Image::ExifTool::configFile = '' } + use Image::ExifTool; + +=head1 EXPORTS + +Exports nothing by default, but L and all static methods may be +exported with the C<:Public> export list. + +=head1 METHODS + +All ExifTool features are accessed through the methods of the public +interface listed below. Other Image::ExifTool methods and modules should +not be accessed directly because their interface may change with future +versions. + +None of these methods should ever die or issue warnings to STDERR if called +with the proper arguments (with the exception of L which may +send an error message to STDERR, but only when called in scalar context). +Error and warning messages that occur during processing are stored in the +values of the Error and Warning tags, and are accessible via the +L method to retrieve a single Error or Warning message, or +L to retrieve any number of them. + +The ExifTool methods are not thread safe. + +=head2 new + +Creates a new ExifTool object. + + $exifTool = Image::ExifTool->new; + +One ExifTool object may be used to process many files, so creating multiple +ExifTool objects usually is not necessary. + +Note that ExifTool uses AUTOLOAD to load non-member methods, so any class +using Image::ExifTool as a base class must define an AUTOLOAD which calls +Image::ExifTool::DoAutoLoad(). eg) + + sub AUTOLOAD + { + Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); + } + +=head2 ImageInfo + +Read image file and return meta information. This is the one step function +for retrieving meta information from an image. Internally, L +calls L to extract the information, L to generate +the information hash, and L for the returned tag list. + + # return meta information for 2 tags only (procedural) + $info = ImageInfo($filename, $tag1, $tag2); + + # return information about an open image file (object-oriented) + $info = $exifTool->ImageInfo(\*FILE); + + # return information from image data in memory for specified tags + %options = (PrintConv => 0); + @tagList = qw(filename imagesize xmp:creator exif:* -ifd1:*); + $info = ImageInfo(\$imageData, \@tagList, \%options); + + # extract information from an embedded thumbnail image + $info = ImageInfo('image.jpg', 'thumbnailimage'); + $thumbInfo = ImageInfo($$info{ThumbnailImage}); + +=over 4 + +=item Inputs: + +L is very flexible about the input arguments, and interprets +them based on their type. It may be called with one or more arguments. +The one required argument is either a SCALAR (the image file name), a file +reference (a reference to the image file) or a SCALAR reference (a +reference to the image in memory). Other arguments are optional. The +order of the arguments is not significant, except that the first SCALAR is +taken to be the file name unless a file reference or scalar reference comes +earlier in the argument list. + +Below is an explanation of how the L function arguments are +interpreted: + +=over 4 + +=item ExifTool ref + +L may be called with an ExifTool object if desired. Advantages +of using the object-oriented form are that options may be set before calling +L, and the object may be used afterward to access member +functions. Must be the first argument if used. + +=item SCALAR + +The first scalar argument is taken to be the file name unless an earlier +argument specified the image data via a file reference (file ref) or data +reference (SCALAR ref). The remaining scalar arguments are names of tags +for requested information. All tags are returned if no tags are specified. + +Tag names are case-insensitive and may be prefixed by optional group names +separated by colons. A group name may begin with a family number (eg. +'1IPTC:Keywords'), to restrict matches to a specific family. In the tag +name, a '?' matches any single character and a '*' matches zero or more +characters. Thus 'GROUP:*' represents all tags in a specific group. +Wildcards may not be used in group names, with the exception that a group +name of '*' may be used to extract all available instances of a tag +regardless of the L setting (eg. '*:WhiteBalance'). Multiple +groups may be specified (eg. 'EXIF:Time:*' extracts all EXIF Time tags). And +finally, a leading '-' indicates a tag to be excluded (eg. '-IFD1:*'), or a +trailing '#' causes the ValueConv value to be returned for this tag. + +Note that keys in the returned information hash and elements of the returned +tag list are not necessarily the same as these tag names because group names +are removed, the case may be changed, and an instance number may be added. +For this reason it is best to use either the keys of the returned hash or +the elements of the returned tag list when accessing the tag values. + +See L for a complete +list of ExifTool tag names. + +=item File ref + +A reference to an open image file. If you use this method (or a SCALAR +reference) to access information in an image, the FileName and Directory +tags will not be returned. (Also, a number of the File System tags will not +be returned unless it is a plain file.) Image processing begins at the +current file position, and on return the file position is unspecified. May +be either a standard filehandle, or a reference to a +L object. Note that the file remains +open and must be closed by the caller after L returns. + +[Advanced: To allow a non-rewindable stream (eg. a network socket) to be +re-read after processing with ExifTool, first wrap the file reference in a +L object, then pass this object to +L. The L object will +buffer the file if necessary, and may be used to re-read the file after +L returns.] + +=item SCALAR ref + +A reference to image data in memory. + +=item ARRAY ref + +Reference to a list of tag names. On entry, any elements in the list are +added to the list of requested tags. Tags with names beginning with '-' are +excluded. On return, this list is updated to contain an ordered list of tag +keys for the returned information. + +There will be 1:1 correspondence between the requested tags and the returned +tag keys only if the L option is 0 and L is 'Input'. +(With L enabled, there may be more entries in the returned list +of tag keys, and with other L settings the entries may not be in the +same order as requested.) If a requested tag doesn't exist, a tag key is +still generated, but the tag value is undefined. + +B Do not reuse this list in subsequent calls to L because +it returns tag keys, not names, and the list will grow for each call +resulting in increasingly slower performance. + +=item HASH ref + +Reference to a hash containing the options settings valid for this call +only. See L documentation below for a list of available options. +Options specified as arguments to L take precedence over +L settings. + +=back + +=item Return Values: + +L returns a reference to a hash of tag-key/value pairs. The tag +keys are identifiers -- essentially case-sensitive tag names with an +appended instance number if multiple tags with the same name were extracted +from the image. Many of the ExifTool functions require a tag key as an +argument. Use L to get the tag name for a given tag +key. Note that the case of the tag names may not be the same as requested. +Here is a simple example to print out the information returned by +L: + + foreach (sort keys %$info) { + print "$_ => $$info{$_}\n"; + } + +Values of the returned hash are usually simple scalars, but a scalar +reference is used to indicate binary data and an array reference may be used +to indicate a list. Also, a hash reference may be returned if the L +option is used. Lists of values are joined by commas into a single +string only if the PrintConv option is enabled and the ListJoin option is +enabled (which are the defaults). Note that binary values are not +necessarily extracted unless specifically requested, or the Binary option is +enabled and the tag is not specifically excluded. If not extracted the +value is a reference to a string of the form "Binary data ##### bytes". + +The code below gives an example of how to handle these return values, as +well as illustrating the use of other ExifTool functions: + + use Image::ExifTool; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Unknown => 1); + my $info = $exifTool->ImageInfo('a.jpg'); + my $group = ''; + my $tag; + foreach $tag ($exifTool->GetFoundTags('Group0')) { + if ($group ne $exifTool->GetGroup($tag)) { + $group = $exifTool->GetGroup($tag); + print "---- $group ----\n"; + } + my $val = $info->{$tag}; + if (ref $val eq 'SCALAR') { + if ($$val =~ /^Binary data/) { + $val = "($$val)"; + } else { + my $len = length($$val); + $val = "(Binary data $len bytes)"; + } + } + printf("%-32s : %s\n", $exifTool->GetDescription($tag), $val); + } + +=item Notes: + +ExifTool returns all values as byte strings of encoded characters. Perl +wide characters are not used. See L for details about +the encodings. By default, most returned values are encoded in UTF-8. For +these, Encode::decode_utf8() may be used to convert to a sequence of logical +Perl characters. + +As well as tags representing information extracted from the image, the +following L generated by +ExifTool may be returned: + + ExifToolVersion - The ExifTool version number. + + Error - An error message if the image could not be processed. + + Warning - A warning message if problems were encountered while + processing the image. + +=back + +=head2 Options + +Get/set ExifTool options. This function can be called to set the default +options for an ExifTool object. Options set this way are in effect for +all function calls but may be overridden by options passed as arguments +to some functions. Option names are not case sensitive, but option values +are. + +The default option values may be changed by defining a +%Image::ExifTool::UserDefined::Options hash. See the ExifTool_config file +in the full ExifTool distribution for examples. Unless otherwise noted, a +default of undef has the same effect as a value of 0 for options with +numerical values. + + # exclude the 'OwnerName' tag from returned information + $exifTool->Options(Exclude => 'OwnerName'); + + # only get information in EXIF or MakerNotes groups + $exifTool->Options(Group0 => ['EXIF', 'MakerNotes']); + + # ignore information from IFD1 + $exifTool->Options(Group1 => '-IFD1'); + + # sort by groups in family 2, and extract unknown tags + $exifTool->Options(Sort => 'Group2', Unknown => 1); + + # reset DateFormat option + $exifTool->Options(DateFormat => undef); + + # do not extract duplicate tag names + $oldSetting = $exifTool->Options(Duplicates => 0); + + # get current Verbose setting + $isVerbose = $exifTool->Options('Verbose'); + + # set a user parameter + $exifTool->Options(UserParam => 'MyParam=some value'); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) Option parameter name (case-insensitive) + +2) [optional] Option parameter value (may be undef to clear option) + +3-N) [optional] Additional parameter/value pairs + +=item Option Parameters: + +Note that these API options may also be used in the exiftool application via +the command-line B<-api> option. + +=over 4 + +=item Binary + +Flag to extract the value data for all binary tags. Tag values representing +large binary data blocks (eg. ThumbnailImage) are not necessarily extracted +unless this option is set or the tag is specifically requested by name. +Default is undef. + +=item BlockExtract + +Flag to extract some directories (mentioned in the +L) as a block. +Setting this to a value of 2 also prevents parsing the block to extract tags +contained within. + +=item ByteOrder + +The byte order for newly created EXIF segments when writing. Note that if +EXIF information already exists, the existing order is maintained. Valid +values are 'MM', 'II' and undef. If ByteOrder is not defined (the default), +then the maker note byte order is used (if they are being copied), otherwise +big-endian ('MM') order is assumed. This can also be set via the +L, but the ByteOrder +option takes precedence if both are set. + +=item Charset + +Character set for encoding character tag values passed to/from ExifTool with +code points above U+007F. Default is 'UTF8'. Valid values are listed +below, case is not significant: + + Value Alias(es) Description + ----------- --------------- ---------------------------------- + UTF8 cp65001, UTF-8 UTF-8 characters + Latin cp1252, Latin1 Windows Latin1 (West European) + Latin2 cp1250 Windows Latin2 (Central European) + Cyrillic cp1251, Russian Windows Cyrillic + Greek cp1253 Windows Greek + Turkish cp1254 Windows Turkish + Hebrew cp1255 Windows Hebrew + Arabic cp1256 Windows Arabic + Baltic cp1257 Windows Baltic + Vietnam cp1258 Windows Vietnamese + Thai cp874 Windows Thai + DOSLatinUS cp437 DOS Latin US + DOSLatin1 cp850 DOS Latin1 + DOSCyrillic cp866 DOS Cyrillic + MacRoman cp10000, Roman Macintosh Roman + MacLatin2 cp10029 Macintosh Latin2 (Central Europe) + MacCyrillic cp10007 Macintosh Cyrillic + MacGreek cp10006 Macintosh Greek + MacTurkish cp10081 Macintosh Turkish + MacRomanian cp10010 Macintosh Romanian + MacIceland cp10079 Macintosh Icelandic + MacCroatian cp10082 Macintosh Croatian + +Note that this option affects some types of information when reading/writing +the file and other types when getting/setting tag values, so it must be +defined for both types of access. See the L section +for more information about the handling of special characters. + +=item CharsetEXIF + +Internal encoding to use for stored EXIF "ASCII" string values. May also be +set to undef to pass through EXIF "ASCII" values without recoding. Set to +"UTF8" to conform with the MWG recommendation. Default is undef. + +=item CharsetFileName + +External character set used for file names passed to ExifTool functions. +When set in Windows, this triggers use of Windows wide-character i/o library +routines (requires Win32API::File). Default is undef. May also be set to +an empty string to avoid "encoding not specified" warnings on Windows. + +=item CharsetID3 + +Internal encoding to assume for ID3v1 strings. By the specification ID3v1 +strings should be encoded in ISO 8859-1 (essentially Latin), but some +applications may use local encoding instead. Default is 'Latin'. + +=item CharsetIPTC + +Fallback internal IPTC character set to assume if IPTC information contains +no CodedCharacterSet tag. Possible values are the same as the L +option. Default is 'Latin'. + +Note that this option affects some types of information when reading/writing +the file and other types when getting/setting tag values, so it must be +defined for both types of access. + +=item CharsetPhotoshop + +Internal encoding to assume for Photoshop IRB resource names. Default is +'Latin'. + +=item CharsetQuickTime + +Internal encoding to assume for QuickTime strings stored with an unspecified +encoding. Default is 'MacRoman'. + +=item CharsetRIFF + +Internal encoding to assume for strings in RIFF metadata (eg. AVI and WAV +files). The default value of 0 assumes "Latin" encoding unless otherwise +specified by the RIFF CSET chunk. Set to undef to pass through strings +without recoding. Default is 0. + +=item Compact + +Comma-delimited list of settings for writing compact XMP. Below is a list +of available settings. Note that 'NoPadding' effects only embedded XMP +since padding is never written for stand-alone XMP files. Also note that +'OneDesc' is not recommended when writing XMP larger than 64 kB to a JPG +file because it interferes with ExifTool's technique of splitting off large +rdf:Description elements into the extended XMP. Case is not significant for +any of these options. Aliases are given in brackets. Default is undef. + + NoPadding - Avoid 2 kB of recommended padding at end of XMP (NoPad) + NoIndent - No spaces to indent lines (NoSpace, NoSpaces) + NoNewline - Avoid unnecessary newlines (NoNewlines) + Shorthand - Use XMP Shorthand format + OneDesc - Combine properties into a single rdf:Description (OneDescr) + AllSpace - Equivalent to 'NoPadding,NoIndent,NoNewline' + AllFormat - Equivalent to 'Shorthand,OneDesc' + All - Equivalent to 'AllSpace,AllFormat' + +=item Composite + +Flag to generate Composite tags when extracting information. Default is 1. + +=item Compress + +Flag to write new values in compressed format if possible. Has no effect +unless the relevant compression library is available. Valid when writing +metadata to PNG, JXL or MIE images. Setting this to zero causes JXL +metadata to be rewritten as uncompressed when edited. Default is undef. + +=item CoordFormat + +Format for printing GPS coordinates. This is a printf format string with +specifiers for degrees, minutes and seconds in that order, however minutes +and seconds may be omitted. If the hemisphere is known, a reference +direction (N, S, E or W) is appended to each printed coordinate, but adding +a C<+> to the first format specifier (eg. C<%+.6f>) prints a signed +coordinate instead. For example, the following table gives the output for +the same coordinate using various formats: + + CoordFormat Example Output + ------------------- ------------------ + q{%d deg %d' %.2f"} 54 deg 59' 22.80" (default for reading) + q{%d %d %.8f} 54 59 22.80000000 (default for copying) + q{%d deg %.4f min} 54 deg 59.3800 min + q{%.6f degrees} 54.989667 degrees + +Note: To avoid loss of precision, the default coordinate format is +different when copying tags with L. + +=item DateFormat + +Format for printing date/time values. See C in the L +package and L for details about +the format string. If the date can not be converted, the value is left +unchanged unless the StrictDate option is set. Timezones are ignored. The +inverse conversion (ie. when calling L) is performed only if +POSIX::strptime or Time::Piece is installed. The default setting of undef +causes date/time values to remain in standard EXIF format (similar to a +DateFormat of "%Y:%m:%d %H:%M:%S"). + +=item Duplicates + +Flag to return values from tags with duplicate names when extracting +information. Default is 1. + +=item Escape + +Escape special characters in extracted values for HTML or XML. Also +unescapes HTML or XML character entities in input values passed to +L. Valid settings are 'HTML', 'XML' or undef. Default is +undef. + +=item Exclude + +Exclude specified tags when extracting information. Note that this option +is applied after all of the tags have already been loaded into memory (so +different tags may be excluded in subsequent calls to L). See the +IgnoreTags option to save memory by not loading the tags in the first place. +The option value is either a tag name or reference to a list of tag names to +exclude. The case of tag names is not significant. This option is ignored +for specifically requested tags. Tags may also be excluded by preceding +their name with a '-' in the arguments to L. + +=item ExtendedXMP + +This setting affects the reading and editing of extended XMP in JPEG images. +According to the XMP specification, extended XMP is only valid if it has the +GUID specified by the HasExtendedXMP tag, so by default ExifTool will ignore +other extended XMP, but this option allows full control over the extended +XMP to be extracted. + + 0 - Ignore all extended XMP + 1 - Read extended XMP with valid GUID only (default) + 2 - Read extended XMP with any GUID + - Read extended XMP with a specific GUID + +=item ExtractEmbedded + +Flag to extract information from embedded documents in EPS files, embedded +EPS information and JPEG and Jpeg2000 images in PDF files, embedded MPF +images in JPEG and MPO files, metadata after the first Cluster in MKV files, +timed metadata in videos, all frames of a multipart EXR image, and the +resource fork of Mac OS files. A setting of 2 also causes the H264 video +stream in MP4 files to be parsed until the first SEI message is decoded, or +3 to parse the entire H264 stream in MP4 videos and the entire M2TS file to +look for any unlisted program containing GPS metadata. Default is undef. + +=item FastScan + +Flag to increase speed when reading files by avoiding extraction of some +types of metadata. With this option set to 1, ExifTool will not scan to the +end of a JPEG image to check for an AFCP, CanonVRD, FotoStation, +PhotoMechanic, MIE or PreviewImage trailer. This also stops the parsing +after the first comment in GIF images, and at the audio/video data of +RIFF-format files (AVI, WAV, etc), so any trailing metadata (eg. XMP written +by some utilities) may be missed. Also disables input buffering for some +types of files to reduce memory usage when reading from a non-seekable +stream, and bypasses CRC validation for speed when writing PNG files. When +combined with the ScanForXMP option, prevents scanning for XMP in recognized +file types. With a value of 2, ExifTool will also avoid extracting any EXIF +MakerNote information, and will stop processing at the IDAT chunk of PNG +images and the mdat atom in QuickTime-format files. (By the PNG +specification, metadata is allowed after IDAT, but ExifTool always writes it +before because some utilities will ignore it otherwise.) When set to 3 or +higher, only pseudo system tags and FileType are generated. For 3, the file +header is read to provide an educated guess at FileType. For 4, the file is +not read at all and FileType is determined based on the file's extension. +For 5, generation of Composite tags is also disabled (like setting +L to 0). Default is undef. + +=item Filter + +Perl expression used to filter values for all tags. The expression acts on +the value of the Perl default variable ($_), and changes the value of this +variable as required. The current ExifTool object may be accessed through +$self. The value is not changed if $_ is set to undef. List items are +filtered individually. Applies to all returned values unless PrintConv +option is disabled. + +=item FilterW + +Perl expression used to filter PrintConv values when writing. The +expression acts on the value of the Perl default variable ($_), and changes +the value of this variable as required. The current ExifTool object may be +accessed through $self. The tag is not written if $_ is set to undef. + +=item FixBase + +Fix maker notes base offset. A common problem with image editing software +is that offsets in the maker notes are not adjusted properly when the file +is modified. This may cause the wrong values to be extracted for some maker +note entries when reading the edited file. FixBase specifies an integer +value to be added to the maker notes base offset. It may also be set to the +empty string ('') for ExifTool will take its best guess at the correct base, +or undef (the default) for no base adjustment. + +=item GeoMaxIntSecs + +Maximum interpolation time in seconds for geotagging. Geotagging is treated +as an extrapolation if the Geotime value lies between two fixes in the same +track which are separated by a number of seconds greater than this. +Otherwise, the coordinates are calculated as a linear interpolation between +the nearest fixes on either side of the Geotime value. Set to 0 to disable +interpolation and use the coordinates of the nearest fix instead (provided +it is within GeoMaxExtSecs, otherwise geotagging fails). Default is 1800. + +=item GeoMaxExtSecs + +Maximum extrapolation time in seconds for geotagging. Geotagging fails if +the Geotime value lies outside a GPS track by a number of seconds greater +than this. Otherwise, for an extrapolation the coordinates of the nearest +fix are taken (ie. it is assumed that you weren't moving during this +period). Default is 1800. + +=item GeoMaxHDOP + +Maximum Horizontal (2D) Dilution Of Precision for geotagging. GPS fixes are +ignored if the HDOP is greater than this. Default is undef. + +=item GeoMaxPDOP + +Maximum Position (3D) Dilution Of Precision for geotagging. GPS fixes are +ignored if the PDOP is greater than this. Default is undef. + +=item GeoMinSats + +Minimum number of satellites for geotagging. GPS fixes are ignored if the +number of acquired satellites is less than this. Default is undef. + +=item GeoSpeedRef + +Reference units for writing GPSSpeed when geotagging: + + 'K', 'k' or 'km/h' - km/h + 'M', 'm' or 'mph' - mph + - knots (default undef) + +=item GlobalTimeShift + +Time shift to apply to all extracted date/time PrintConv values. Does not +affect ValueConv values. Value is a date/time shift string (see +L), with a leading +'-' for negative shifts. Default is undef. + +=item Group# + +Extract tags only for specified groups in family # (Group0 assumed if # +not given). The option value may be a single group name or a reference +to a list of groups. Case is significant in group names. Specify a group +to be excluded by preceding group name with a '-'. See L for a +description of group families, and L for lists of +group names. + +=item HexTagIDs + +Return hexadecimal instead of decimal for the family 7 group names of tags +with numerical ID's. + +=item HtmlDump + +Dump information in hex to dynamic HTML web page. The value may be 0-3 for +increasingly larger limits on the maximum block size. Default is 0. Output +goes to the file specified by the TextOut option (\*STDOUT by default). + +=item HtmlDumpBase + +Base for HTML dump offsets. If not defined, the EXIF/TIFF base offset is +used. Set to 0 for absolute offsets. Default is undef. + +=item IgnoreMinorErrors + +Flag to ignore minor errors. Causes minor errors to be downgraded to +warnings, and minor warnings to be ignored. This option is provided mainly +to allow writing of files when minor errors occur, but by ignoring some +minor warnings the behaviour of ExifTool may be changed to allow some +questionable operations to proceed (such as extracting thumbnail and preview +images even if they don't have a recognizable header). Minor errors and +warnings are denoted by "[minor]" at the start of the message, or "[Minor]" +(with a capital "M") for warnings that affect processing when ignored. + +=item IgnoreTags + +List of tag names to ignore when reading. This may help in situations where +memory is limited because the ignored tag values are not stored in memory. +The tag names are case insensitive and group names and wildcards are not +allowed. A special tag name of "All" may be used to ignore all tags except +those specified by the L option. Set to undef to clear the +previous IgnoreTags list. Default is undef. + +=item ImageHashType + +Sets type of hash algorithem used for the ImageDataHash tag calculation. +Supported options are 'MD5', 'SHA256', and 'SHA512'. Default is 'MD5'. + +=item Lang + +Localized language for exiftool tag descriptions, etc. Available languages +are given by the Image::ExifTool::Lang module names (eg. 'fr', 'zh_cn'). If +the specified language isn't available, the option is not changed. May be +set to undef to select the built-in default language. Default is 'en'. + +=item LargeFileSupport + +Flag to indicate that 64-bit file offsets are supported on this system. +Default is undef. + +=item ListItem + +Return only a specific item from list-type values. A value of 0 returns the +first item in the list, 1 return the second item, etc. Negative indices may +also be used, with -1 representing the last item in the list. Applies only +to the top-level list of nested lists. Default is undef to return all items +in the list. + +=item ListJoin + +Separator used to join the PrintConv value of multi-item List-type tags into +a single string. If not defined, multi-item lists are returned as a list +reference. Does not affect ValueConv values. Default is ', '. + +=item ListSplit + +Regular expression used to split values of list-type tags into individual +items when writing. (eg. use ',\\s*' to split a comma-separated list.) +Split when writing either PrintConv or ValueConv values. Default is undef. + +=item MakerNotes + +Option to extract MakerNotes and other writable subdirectories (such as +PrintIM) as a data block. Normally when the MakerNotes are extracted they +are rebuilt to include data outside the boundaries of the original maker +note data block, but a value of 2 disables this feature. Possible values +are: + + 0 - Do not extract writable subdirectories (same as default of undef) + 1 - Extract and rebuild maker notes into self-contained block + 2 - Extract without rebuilding maker notes + +=item MDItemTags + +Flag to extract the OS X metadata item tags (see the "mdls" man page and +L for more information). + +=item MissingTagValue + +Value for missing tags in tag name expressions (or tags where the advanced +formatting expression returns undef). If not set, a minor error is issued +for missing values, or the value is set to '' if L is +set. Default is undef. + +=item NoDups + +Flag to remove duplicate items from queued values for List-type tags when +writing. This applies only to queued values, and doesn't resolve duplicates +with existing values in the file when adding to an existing list. Default +is undef. + +=item NoMultiExif + +Raise error when attempting to write multi-segment EXIF in a JPEG image. +Default is undef. + +=item NoPDFList + +Flag to avoid splitting PDF list-type tag values into separate items. +Default is undef. + +=item NoWarning[+] + +Regular expression to suppress matching warning messages. For example, a +value of "^Ignored" suppresses all warnings that begin with the word +"Ignored". Has no other effect on processing, unlike IgnoreMinorWarnings +for some warnings. Start the expression with "(?i)" for case-insensitive +matching. Use NoWarning+ to add to existing expressions. Default is undef. + +=item Password + +Password for reading/writing password-protected PDF documents. Ignored if a +password is not required. Character encoding of the password is determined +by the value of the Charset option at processing time. Default is undef. + +=item PrintConv + +Flag to enable automatic print conversion. Also enables inverse +print conversion for writing. Default is 1. + +=item QuickTimeHandler + +Flag set to add an 'mdir' Handler to a newly created Meta box when adding +QuickTime ItemList tags. Adobe Bridge does not add this Handler, but it is +commonly found in samples from other software, and it has been reported that +Apple QuickTime Player and Photos.apps will ignore ItemList tags if this is +missing. Default is 1. + +=item QuickTimePad + +Flag to preserve the padding of some QuickTime atoms when writing. +QuickTime-based Canon CR3 files pad the values of container atoms with null +bytes. This padding is removed by default when the file is rewritten, but +setting this option to 1 adds padding to preserve the original atom size if +the new atom would be smaller than the original. Default is undef. + +=item QuickTimeUTC + +Flag set to assume that QuickTime date/time values are stored as UTC, +causing conversion to local time when they are extracted and from local time +when written. According to the QuickTime specification date/time values +should be UTC, but many digital cameras store local time instead (presumably +because they don't know the time zone), so the default is to not convert +these times (except for Canon CR3 files, which always use UTC times). This +option also disables the autodetection of incorrect time-zero offsets in +QuickTime date/time values, and enforces a time zero of 1904 as per the +QuickTime specification. + +=item RequestAll + +Flag to request all tags to be extracted. This causes some tags to be +generated which normally would not be unless specifically requested (by +passing the tag name to L or L). May be set to 2 +or 3 to enable generation of some additional tags as mentioned in the tag +name documentation. Default is undef. + +=item RequestTags + +List of additional tag and/or group names to request in the next call to +L. This option is useful only for tags/groups which aren't +extracted unless specifically requested. Value may be a list reference, a +delimited string of names (any delimiter is allowed), or undef to clear the +current RequestTags list. Groups are requested by adding a colon after the +name (eg. "MacOS:"). Names are converted to lower case as they are added to +the list. Default is undef. + +=item SaveFormat + +Flag to save EXIF/TIFF format type as the family 6 group name when +extracting information. Without this option set, the family 6 group names +are not generated. Default is undef. See the L option for more +details. + +=item SavePath + +Flag to save the metadata path as the family 5 group name when extracting +information. Without this option set, the family 5 group names are not +generated. Default is undef. See the L option for more details. + +=item ScanForXMP + +Flag to scan all files (even unrecognized formats) for XMP information +unless XMP was already found in the file. When combined with the FastScan +option, only unrecognized file types are scanned for XMP. Default is undef. + +=item Sort + +Specifies order to sort tags in returned list: + + Input - Sort in same order as input tag arguments (default) + File - Sort in order that tags were found in the file + Tag - Sort alphabetically by tag name + Descr - Sort by tag description (for current Lang setting) + Group# - Sort by tag group, where # is zero or more family + numbers separated by colons. If # is not specified, + Group0 is assumed. See GetGroup for a description + of group families. + +=item Sort2 + +Secondary sort order used for tags within each group when Sort is 'Group': + + File - Sort in order tags were found in the file (default) + Tag - Sort alphabetically by tag name + Descr - Sort by tag description (for current Lang setting) + +=item StrictDate + +Flag to return undefined value for any date which can't be converted when +the DateFormat option is used. Default is undef. + + undef - Same as 0 for reading/writing, or 1 for copying + 0 - Return date/time value unchanged if it can't be converted + 1 - Return undef if date/time value can't be converted + +When set to 1 while writing a PrintConv date/time value with the DateFormat +option set, the value is written only if POSIX::strptime or Time::Piece is +available and can successfully convert the value. + +For PNG CreationTime, a setting of 1 has the additional effect of causing +the date/time to be reformatted according to PNG 1.2 recommendation +(RFC-1123) when writing, and a warning to be issued for any non-standard +value when reading (but note that Windows may not recognize PNG date/time +values in standard format). + +=item Struct + +Flag to return XMP structures as hash references instead of flattening into +individual tags. Has no effect when writing since both flattened and +structured tags may always be written. Possible values are: + + undef - (default) Same as 0 for reading, 2 for copying + 0 - Read/copy flattened tags + 1 - Read/copy structured tags + 2 - Read/copy both flattened and structured tags, but flag + flattened tags as 'unsafe' for copying + +=item StructFormat + +Format for serialized structures when reading/writing. + + undef - Default ExifTool format + JSON - JSON format + JSONQ - JSON with quoted numerical values + +=item SystemTags + +Flag to extract the following additional File System tags: FileAttributes, +FileDeviceNumber, FileInodeNumber, FileHardLinks, FileUserID, FileGroupID, +FileDeviceID, FileBlockSize and FileBlockCount. + +=item TextOut + +Output file reference for Verbose and HtmlDump options. Default is +\*STDOUT. + +=item TimeZone + +Time zone for local date/time values. May be set to any valid TZ string. +Uses the system time zone if not specified. Default is undef. (Requires +POSIX::tzset, which may not be available in Windows. A work-around in +Windows is to CzoneE> before running ExifTool.) + +=item Unknown + +Flag to get the values of unknown tags. If set to 1, unknown tags are +extracted from EXIF (or other tagged-format) directories. If set to 2, +unknown tags are also extracted from binary data blocks. Default is 0. + +=item UserParam + +Special option to set/get user-defined parameters. Useful to allow external +input into tag name expressions and ValueConv logic. Valid UserParam values +are: + + PARAM - Get parameter + PARAM= - Clear parameter + PARAM^= - Set parameter to empty string + PARAM=VALUE - Set parameter + - Set entire UserParam hash lookup + undef - Clear all user parameters + +Where I is the user-defined parameter name (case insensitive). + +User-defined parameters may be accessed in tag name expressions by prefixing +the parameter name with a dollar sign just like normal tags, or via the API +by calling C. Appending a hash tag (C<#>) to +the parameter name also causes the parameter to be extracted as a normal tag +(in the UserParam group). If called without additional arguments, +C returns a reference to the hash of all user +parameters (with lower-case names). + +=item Validate + +Flag to perform extra validation metadata checks when reading, causing extra +warnings to be generated if problems are found. Default is undef. + +=item Verbose + +Print verbose messages to file specified by TextOut option. Value may be +from 0 to 5 for increasingly verbose messages. Default is 0. With the +verbose option set, messages are printed to the console as the file is +parsed. Level 1 prints the tag names and raw values. Level 2 adds more +details about the tags. Level 3 adds a hex dump of the tag data, but with +limits on the number of bytes dumped. Levels 4 and 5 remove the dump limit +on tag values and JPEG segment data respectively. + +=item WindowsWideFile + +Force the use of wide-character Windows I/O functions when the +L option is used. This may be necessary when files are on +a network drive and the current directory name contains Unicode characters. +By default, the wide-character functions are used only if the specified file +path contains Unicode characters. + +=item WriteMode + +Set tag write/create mode. Value is a string of one or more characters from +list below. Default is 'wcg'. + + w - Write existing tags + c - Create new tags + g - create new Groups as necessary + +The level of the group differs for different types of metadata. For XMP or +IPTC this is the full XMP/IPTC block (the family 0 group), but for EXIF this +is the individual IFD (the family 1 group). The 'w' and 'c' modes are +tested only when L is called, but the 'g' mode is also tested +in L. + +=item XAttrTags + +Flag to extract the OS X extended attribute tags (see the "xattr" man page +and L for more information). + +=item XMPAutoConv + +Flag to enable automatic conversion for unknown XMP tags with values that +look like rational numbers or dates. Default is 1. + +=back + +=item Return Values: + +The original value of the last specified parameter. + +=back + +=head2 ClearOptions + +Reset all options to their default values. Loads user-defined default +option values from the %Image::ExifTool::UserDefined::Options hash in the +.ExifTool_config file if it exists. + + $exifTool->ClearOptions(); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +=item Return Values: + +(none) + +=back + +=head2 ExtractInfo + +Extract all meta information from an image. + + $success = $exifTool->ExtractInfo('image.jpg', \%options); + +=over 4 + +=item Inputs: + +L takes exactly the same arguments as L. The only +difference is that a list of tag keys is not returned if an ARRAY reference +is given. The following options are effective in the call to +L: + +Binary, Charset, CharsetEXIF, CharsetFileName, CharsetID3, CharsetIPTC, +CharsetPhotoshop, CharsetQuickTime, CharsetRIFF, Composite, ExtendedXMP, +ExtractEmbedded, FastScan, FixBase, HtmlDump, HtmlDumpBase, +IgnoreMinorErrors, IgnoreTags, Lang, LargeFileSupport, MakerNotes, +MDItemTags, NoPDFList, Password, QuickTimeUTC (enforced 1904 time zero), +RequestAll, RequestTags, SaveFormat, SavePath, ScanForXMP, Struct, TextOut, +Unknown, Verbose, WindowsWideFile, XAttrTags and XMPAutoConv. + +=item Return Value: + +1 if this was a recognized file format, 0 otherwise (and 'Error' tag set). + +=back + +=head2 GetInfo + +L is called to return meta information after it has been extracted +from the image by a previous call to L or L. This +function may be called repeatedly after a single call to L or +L. + + # get image width and height only + $info = $exifTool->GetInfo('ImageWidth', 'ImageHeight'); + + # get all Error and Warning messages + $info = $exifTool->GetInfo('Error', 'Warning'); + + # get information for all tags in list (list updated with tags found) + $info = $exifTool->GetInfo(\@ioTagList); + + # get all information in Author or Location groups + $info = $exifTool->GetInfo({Group2 => ['Author', 'Location']}); + +=over 4 + +=item Inputs: + +Inputs are the same as L and L except that an +image can not be specified. Options in effect are: + +Charset, CoordFormat, DateFormat, Duplicates, Escape, Exclude, Filter, +Group#, GlobalTimeShift, Lang, ListItem, ListJoin, PrintConv, Sort (if a tag +list reference is given) and StrictDate. + +=item Return Value: + +Reference to information hash, the same as with L. + +=back + +The following options are effective in the call to L: + +Charset, CoordFormat, DateFormat, Duplicates, Escape, Exclude, Filter, +Group#, GlobalTimeShift, Lang, ListItem, ListJoin, PrintConv, QuickTimeUTC +(conversion to local time), Sort (if a tag list reference is given) and +StrictDate. + +=head2 WriteInfo + +Write meta information to a file. The specified source file is rewritten to +the same-type destination file with new information as specified by previous +calls to L. The necessary segments and/or directories are +created in the destination file as required to store the specified +information. May be called repeatedly to write the same information to +additional files without the need to call L again. + +ExifTool queues all new values that are assigned via calls to +L, then applies them to any number of files through one or +more calls to L. These queued values may be accessed through +L, and are completely separate from metadata extracted from +files via L or L and accessed through L +or L. + +To be clear, it is NOT necessary to call L or L +before L. L changes only metadata specified by +previous calls to L. + + # add information to a source file, writing output to new file + $exifTool->WriteInfo($srcfile, $dstfile); + + # create XMP data file from scratch + $exifTool->WriteInfo(undef, $dstfile, 'XMP'); + + # overwrite file (you do have backups, right?) + $exifTool->WriteInfo($srcfile); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) Source file name, file reference, scalar reference, or undef to create a +file from scratch. A reference to a +L object is also allowed as a source, +but in this case the destination is not optional. + +2) [optional] Destination file name, file reference, scalar reference to +write to memory, or undef to overwrite the original file. May be '-' to +write to stdout. + +3) [optional] Destination file type. Ignored if a source is defined. + +=item Return Value: + +1 if file was written OK, 2 if file was written but no changes made, 0 on +file write error. + +If an error code is returned, an Error tag is set and GetValue('Error') can +be called to obtain the error description. A Warning tag may be set even if +this routine is successful. Calling WriteInfo clears any pre-existing Error +and Warning tags. + + $errorMessage = $exifTool->GetValue('Error'); + $warningMessage = $exifTool->GetValue('Warning'); + +=item Notes: + +The source file name may be undefined to create a file from scratch +(currently only XMP, MIE, ICC, VRD, DR4, EXV and EXIF files can be created +in this way -- see L for details). If undefined, the +destination file type is required unless the type can be determined from the +extension of the destination file name. + +If a destination file name is given, the specified file must not exist +because an existing destination file will not be overwritten. Any new +values for FileName, Directory or HardLink are ignored when a destination +file name is specified. + +The destination file name may be undefined to overwrite the original file +(make sure you have backups!). In this case, if a source file name is +provided, a temporary file is created and renamed to replace the source file +if no errors occurred while writing. Otherwise, if a source file reference +or scalar reference is used, the image is first written to memory then +copied back to replace the original if there were no errors. + +On Mac OS systems, the file resource fork is preserved if this routine is +called with a source file name. + +=back + +The following ExifTool options are effective in the call to L: + +ByteOrder, Charset, CharsetEXIF, CharsetFileName, CharsetIPTC, Compact, +Compress, FixBase, IgnoreMinorErrors, NoMultiExif, NoPDFList, Password, +QuickTimeHandler, QuickTimePad, Verbose, WindowsWideFile and WriteMode. + +=head2 GetTagList + +Get a sorted list of tags from the specified information hash or tag list. + + @tags = $exifTool->GetTagList($info, 'Group0'); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) [optional] Information hash reference or tag list reference + +2) [optional] Sort order ('Input', 'File', 'Tag', 'Descr' or 'Group#') + +3) [optional] Secondary sort order ('File', 'Tag' or 'Descr') + +If the information hash or tag list reference is not provided, then the list +of found tags from the last call to L, L or +L is used instead, and the result is the same as if +L was called. If sort order is not specified, the sort order +is taken from the current options settings. + +=item Return Values: + +A list of tag keys in the specified order. + +=back + +=head2 GetFoundTags + +Get list of found tags in specified sort order. The found tags are the tags +for the information obtained from the most recent call to L, +L or L for this object. + + @tags = $exifTool->GetFoundTags('File'); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) [optional] Sort order ('Input', 'File', 'Tag', 'Descr' or 'Group#') + +2) [optional] Secondary sort order ('File', 'Tag' or 'Descr') + +If sort order is not specified, the sort order from the ExifTool options is +used. + +=item Return Values: + +A list of tag keys in the specified order. + +=back + +=head2 GetRequestedTags + +Get list of requested tags. These are the tags that were specified in the +arguments of the most recent call to L, L or +L, including tags specified via a tag list reference. Shortcut +tags are expanded in the list. + + @tags = $exifTool->GetRequestedTags(); + +=over 4 + +=item Inputs: + +(none) + +=item Return Values: + +List of requested tag keys in the same order that the tags were specified. +Note that this list will be empty if tags were not specifically requested +(ie. If extracting all tags). + +=back + +=head2 GetValue + +Get the value of a specified tag. The returned value is either the +human-readable (PrintConv) value, the converted machine-readable (ValueConv) +value, the original raw (Raw) value, or the original rational (Rational) +value for rational formats. If the value type is not specified, the +PrintConv value is returned if the PrintConv option is set, otherwise the +ValueConv value is returned. The PrintConv values are same as the values +returned by L and L in the tag/value hash unless the +PrintConv option is disabled. + +Tags which represent lists of multiple values (as may happen with 'Keywords' +for example) are handled specially. In scalar context, the returned +PrintConv value for these tags is either a string of values or a list +reference (depending on the ListJoin option setting), and the ValueConv +value is always a list reference. But in list context, L always +returns the list itself. + +Note that L requires a case-sensitive tag key as an argument. To +retrieve tag information based on a case-insensitive tag name (with an +optional group specifier), use L instead. + + # PrintConv example + my $val = $exifTool->GetValue($tag); + if (ref $val eq 'SCALAR') { + print "$tag = (unprintable value)\n"; + } else { + print "$tag = $val\n"; + } + + # ValueConv examples + my $val = $exifTool->GetValue($tag, 'ValueConv'); + if (ref $val eq 'ARRAY') { + print "$tag is a list of values\n"; + } elsif (ref $val eq 'SCALAR') { + print "$tag represents binary data\n"; + } else { + print "$tag is a simple scalar\n"; + } + + my @keywords = $exifTool->GetValue('Keywords', 'ValueConv'); + +The following options are in effect when L is called: + +Charset, CoordFormat, DateFormat, Escape, Filter, GlobalTimeShift, Lang, +ListItem, ListJoin, PrintConv, QuickTimeUTC (conversion to local time), +StrictDate and TimeZone. + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) Tag key, or case-sensitive tag name with optional group prefix(es) + +2) [optional] Value type: 'PrintConv', 'ValueConv', 'Both', 'Raw' or +'Rational' + +The default value type is 'PrintConv' if the PrintConv option is set, +otherwise the default is 'ValueConv'. A value type of 'Both' returns both +ValueConv and PrintConv values as a list. 'Rational' returns the raw +rational value as a string fraction for rational types, or undef for other +types. + +=item Return Values: + +The value of the specified tag. If the tag represents a list of multiple +values and the ListJoin option is enabled then PrintConv returns a string of +values, otherwise a reference to the list is returned in scalar context. The +list itself is returned in list context. (Unless 'Both' values are +requested, in which case two list references are returned, regardless of +context.) Values may also be scalar references to binary data, or hash +references if the L option is set. + +Note: It is possible for L to return an undefined ValueConv or +PrintConv value (or an empty list in list context) even if the tag exists, +since it is possible for these conversions to yield undefined values. And +the Rational value will be undefined for any non-rational tag. The Raw +value should always exist if the tag exists. + +=back + +=head2 SetNewValue + +Set the new value for a tag. The routine may be called multiple times to +set the values of many tags before using L to write the new +values to an image. These values remain queued for writing to subsequent +files until L is called without arguments to reset the queued +values. + +For list-type tags (like Keywords), either call repeatedly with the same tag +name for each value, or call with a reference to the list of values. + + # set a new value for a tag (errors go to STDERR) + $success = $exifTool->SetNewValue($tag, $value); + + # set a new value and capture any error message + ($success, $errStr) = $exifTool->SetNewValue($tag, $value); + + # delete information for specified tag if it exists in image + # (also resets AddValue and DelValue options for this tag) + $exifTool->SetNewValue($tag); + + # reset all values from previous calls to SetNewValue() + $exifTool->SetNewValue(); + + # delete a specific keyword + $exifTool->SetNewValue('Keywords', $word, DelValue => 1); + + # set keywords (a list-type tag) with two new values + $exifTool->SetNewValue(Keywords => 'word1'); + $exifTool->SetNewValue(Keywords => 'word2'); + # equivalent, but set both in one call using an array reference + $exifTool->SetNewValue(Keywords => ['word1','word2']); + + # add a keyword without replacing existing keywords in the file + $exifTool->SetNewValue(Keywords => $word, AddValue => 1); + + # conditionally add a tag if it didn't exist before, + # or replace it if it had a specified value ("old value") + $exifTool->SetNewValue(Description => '', DelValue => 1); + $exifTool->SetNewValue(Description => 'old value', DelValue => 1); + $exifTool->SetNewValue(Description => 'new value'); + + # set a tag in a specific group + $exifTool->SetNewValue(Headline => $val, Group => 'XMP'); + $exifTool->SetNewValue('XMP:Headline' => $val); # (equivalent) + + # shift original date/time back by 2.5 hours + $exifTool->SetNewValue(DateTimeOriginal => '2:30', Shift => -1); + + # write a tag only if it had a specific value + # (the order of the following calls is not significant) + $exifTool->SetNewValue(Title => $oldVal, DelValue => 1); + $exifTool->SetNewValue(Title => $newVal); + + # write tag by numerical value + $exifTool->SetNewValue(Orientation => 6, Type => 'ValueConv'); + $exifTool->SetNewValue('Orientation#' => 6); # (equivalent) + + # delete all but EXIF tags + $exifTool->SetNewValue('*'); # delete all... + $exifTool->SetNewValue('EXIF:*', undef, Replace => 2); # ...but EXIF + + # write structured information as a HASH reference + $exifTool->SetNewValue('XMP:Flash' => { + mode => 'on', + fired => 'true', + return => 'not' + }); + + # write structured information as a serialized string + $exifTool->SetNewValue('XMP:Flash'=>'{mode=on,fired=true,return=not}'); + +(See L for a description of the +structure serialization technique.) + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) [optional] Tag key or tag name, or undef to clear all new values. The +tag name may be prefixed by one or more family 0, 1 or 2 group names with +optional leading family numbers, separated by colons (eg. 'EXIF:Artist', +'XMP:Time:*'), which is equivalent to using a Group option argument. Also, +a '#' may be appended to the tag name (eg. 'EXIF:Orientation#'), with the +same effect as setting Type to 'ValueConv'. Wildcards ('*' and '?') may be +used in the tag name to assign or delete multiple tags simultaneously. A +tag name of '*' is special when deleting information, and will delete an +entire group even if some individual tags in the group are not writable, but +only if a single family 0 or 1 group is specified (otherwise the tags are +deleted individually). Use L to get a list of deletable +group names, and see L +for a complete list of tag names. + +2) [optional] New value for tag. Undefined to delete tag from file. May be +a scalar, scalar reference, list reference to set a list of values, or hash +reference for a structure. Integer values may be specified as a hexadecimal +string (with a leading '0x'), and simple rational values may be specified in +fractional form (eg. '4/10'). Structure tags may be specified either as a +hash reference or a serialized string (see the last two examples above). + +3-N) [optional] SetNewValue option/value pairs (see below). + +=item SetNewValue Options: + +=over 4 + +=item AddValue + +Specifies that the value be added to an existing list in a file rather than +overwriting the existing values. Valid settings are 0 (overwrite any +existing tag value), 1 (add to an existing list and warn for non-list tags) +or 2 (add to existing list and overwrite non-list tags). Default is 0. + +=item DelValue + +Delete existing tag from a file if it has the specified value. For +list-type tags this deletes a specified item from the list. For non-list +tags this may be used to conditionally replace a tag by providing a new +value in a separate call to L (see examples above). For +structured tags, the entire structure is deleted/replaced only if all of the +specified fields match the existing structure. Option values are 0 or 1. +Default is 0. + +=item EditGroup + +Create tags in existing groups only. Don't create new group. Valid values +are 0 and 1. Effectively removes the 'g' from the ExifTool WriteMode option +for this tag only. Default is 0. + +=item EditOnly + +Edit tag only if it already exists. Don't create new tag. Valid values are +0 and 1. Effectively removes the 'c' from the ExifTool WriteMode option for +this tag only. Default is 0. + +=item Group + +Specifies group name where tag should be written. This option is superseded +by any group specified in the tag name. If not specified, tag is written to +highest priority group as specified by L. May be one or more +family 0, 1 or 2 groups with optional leading family number, separated by +colons. Case is not significant. + +=item NoFlat + +Treat flattened tags as 'unsafe'. + +=item NoShortcut + +Disables default behaviour of looking up tag in shortcuts if not found +otherwise. + +=item Protected + +Bit mask for tag protection levels to write. Bit 0x01 allows writing of +'unsafe' tags (ie. tags not copied automatically via +L). Bit 0x02 allows writing of 'protected' tags, and +should only be used internally by ExifTool. See +L, for a list of tag +names indicating 'unsafe' and 'protected' tags. Default is 0. + +=item ProtectSaved + +Avoid setting new values which were saved after the Nth call to +L. Has no effect on unsaved values, or values saved before +Nth call. Option value is N. Default is undef. + +=item Replace + +Flag to replace the previous new values for this tag (ie. replace the values +set in previous calls to L). This option is most commonly +used to replace previously-set new values for list-type tags. Valid values +are 0 (set new value normally -- adds to new values for list-type tags), 1 +(reset any previous new values before setting new value) or 2 (reset +previous new values only; new value argument is ignored). Default is 0. + +=item Shift + +Shift the tag by the specified value. Currently only date/time tags and +tags with numerical values may be shifted. Undefined for no shift, 1 for a +positive shift, or -1 for a negative shift. A value of 0 causes a positive +shift to be applied if the tag is shiftable and AddValue is set, or a +negative shift for date/time tags only if DelValue is set. Default is undef. +See L for more +information. + +=item Type + +The type of value being set. Valid values are PrintConv, ValueConv or Raw. +Default is PrintConv if the L Option is set, otherwise +ValueConv. + +=back + +=item Return Values: + +In scalar context, returns the number of tags set and error messages are +printed to STDERR. In list context, returns the number of tags set, and the +error string (which is undefined if there was no error). + +=item Notes: + +When deleting groups of tags, the Replace option may be used to exclude +specific groups from a mass delete. However, this technique may not be used +to exclude individual tags from a group delete (unless a family 2 group was +specified in the delete). Instead, use L to recover +the values of individual tags after deleting a group. + +When deleting all tags from a JPEG image, the APP14 "Adobe" information is +not deleted by default because doing so may affect the appearance of the +image. However, this information may be deleted by specifying it +explicitly, either by group (with 'Adobe:*') or as a block (with 'Adobe'). + +=back + +The following ExifTool options are effective in the call to L: + +Charset, DateFormat, Escape, IgnoreMinorErrors, Lang, ListJoin, ListSplit, +PrintConv, QuickTimeUTC, StrictDate, TimeZone, Verbose and WriteMode. + +=head2 GetNewValue + +Get the new Raw value for a tag. This is the value set by L +this is queued to be written to file. List-type tags may return multiple +values in list context. + + $rawVal = $exifTool->GetNewValue($tag); + + @rawVals = $exifTool->GetNewValue($tag); + +=over 4 + +=item Notes: + +The API NoDups option applies when this routine is called, and removes +duplicate items from values returned for List-type tags. + +=item Inputs: + +0) ExifTool object reference + +1) Tag name (case sensitive, may be prefixed by family 0, 1 or 7 group +names, separated by colons) + +=item Return Values: + +List of new Raw tag values, or first value in list when called in scalar +context. The list may be empty either if the tag isn't being written, or if +it is being deleted (ie. if L was called without a value). + +=back + +=head2 SetNewValuesFromFile + +A very powerful routine that sets new values for tags from information found +in a specified file. + + # set new values from all information in a file... + my $info = $exifTool->SetNewValuesFromFile($srcFile); + # ...then write these values to another image + my $result = $exifTool->WriteInfo($file2, $outFile); + + # set all new values, preserving original groups + $exifTool->SetNewValuesFromFile($srcFile, '*:*'); + + # set specific information + $exifTool->SetNewValuesFromFile($srcFile, @tags); + + # set new value from a different tag in specific group + $exifTool->SetNewValuesFromFile($fp, 'XMP-dc:SubjectSetNewValuesFromFile($fp, 'XMP-dc:Subject+SetNewValuesFromFile($file, + 'CommentSetNewValuesFromFile($file, { Replace => 0 }, + 'keywordsSetNewValuesFromFile($file, '*:*SetNewValuesFromFile($file, 'gps*'); + + # set FileName from Model, translating questionable characters + $exifTool->SetNewValuesFromFile($file, + 'filename<${model; tr(/\\\\?*:|"><)(_) }.jpg'); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) File name, file reference, or scalar reference + +2-N) [optional] List of tag names to set or options hash references. All +writable tags are set if none are specified. The tag names are not case +sensitive, and may be prefixed by one or more family 0, 1, 2 or 7 group +names with optional leading family numbers, separated by colons (eg. +'exif:iso'). A leading '-' indicates tags to be excluded (eg. '-comment'), +or a trailing '#' causes the ValueConv value to be copied (same as setting +the Type option to 'ValueConv' for this tag only). A leading '+' sets the +Replace option to 0 on a per-tag basis (see Options below). Wildcards ('*' +and '?') may be used in the tag name. A tag name of '*' is commonly used +when a group is specified to copy all tags in the group (eg. 'XMP:*'). A +special feature allows tag names of the form 'DSTTAGESRCTAG' (or +'SRCTAGEDSTTAG') to be specified to copy information to a tag with a +different name or a specified group. Both 'SRCTAG' and 'DSTTAG' may contain +wildcards and/or be prefixed by a group name (eg. +'fileModifyDateEmodifyDate' or 'xmp:*E*'), and/or suffixed by a '#' +to disable print conversion. Copied tags may also be added or deleted from +a list with arguments of the form 'DSTTAG+ESRCTAG' or +'DSTTAG-ESRCTAG'. Tags are evaluated in order, so exclusions apply only +to tags included earlier in the list. An extension of this feature allows +the tag value to be set from a string containing tag names with leading '$' +symbols (eg. 'CommentEthe file is $filename'). Braces '{}' may be used +around the tag name to separate it from subsequent text, and a '$$' is used +to to represent a '$' symbol. The behaviour for missing tags in expressions +is defined by the L option. The tag value may be modified +via changes to the default input variable ($_) in a Perl expression placed +inside the braces and after a semicolon following the tag name (see the last +example above). A '@' may be added after the tag name (before the +semicolon) to make the expression act on individual list items instead of +the concatenated string for list-type tags. Braces within the expression +must be balanced. Multiple options hash references may be passed to set +different options for different tags. Options apply to subsequent tags in +the argument list. + +By default, this routine will commute information between same-named tags in +different groups, allowing information to be translated between images with +different formats. This behaviour may be modified by specifying a group +name for extracted tags (even if '*' is used as a group name), in which case +the information is written to the original group, unless redirected to a +different group. When '*' is used for a group name, by default the family 1 +group of the original tag is preserved, but a different family may be +specified with a leading family number. (For example, specifying '*:*' +copies all information while preserving the original family 1 groups, while +'0*:*' preserves the family 0 group.) + +=item SetNewValuesFromFile Options: + +The options are the same was for L, and are passed directly +to L internally, with a few exceptions: + +- The Replace option defaults to 1 instead of 0 as with L, +however the tag name argument may be prefixed with '+' to set the Replace +option to 0 for this argument only. + +- The AddValue or DelValue option is set for individual tags if '+>' or '->' +(or '+E' or '-E') are used. + +- The Group option is set for tags where a group name is given. + +- The Protected flag is set to 1 for individually specified tags. + +- The Type option also applies to extracted tags. + +=item Return Values: + +A hash of information that was set successfully. May include Warning or +Error entries if there were problems reading the input file. + +=item Notes: + +The PrintConv option applies to this routine, but it normally should be left +on to provide more reliable transfer of information between groups. + +If a preview image exists, it is not copied. The preview image must be +transferred separately if desired, in a separate call to L + +When simply copying all information between files of the same type, it is +usually desirable to preserve the original groups by specifying '*:*' for +the tags to set. + +The L option is always in effect for tags extracted from the +source file using this routine. + +The L option is enabled by default for tags extracted by this +routine. This allows the hierarchy of complex structures to be preserved +when copying, but the Struct option may be set to 0 to override this +behaviour and copy as flattened tags instead. + +=back + +=head2 CountNewValues + +Return the total number of new values set. + + $numSet = $exifTool->CountNewValues(); + ($numSet, $numPseudo) = $exifTool->CountNewValues(); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +=item Return Values: + +In scalar context, returns the total number of tags with new values set. In +list context, also returns the number of "pseudo" tag values which have been +set. "Pseudo" tags are tags like FileName and FileModifyDate which are not +contained within the file and can be changed without rewriting the file. + +=back + +=head2 SaveNewValues + +Save state of new values to be later restored by L. + + $exifTool->SaveNewValues(); # save state of new values + $exifTool->SetNewValue(ISO => 100); # set new value for ISO + $exifTool->WriteInfo($src, $dst1); # write ISO + previous new values + $exifTool->RestoreNewValues(); # restore previous new values + $exifTool->WriteInfo($src, $dst2); # write previous new values only + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +=item Return Value: + +Count of the number of times this routine has been called (N) since the last +time the new values were reset. + +=back + +=head2 RestoreNewValues + +Restore new values to the settings that existed when L was +last called. May be called repeatedly after a single call to +L. See L above for an example. + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +=item Return Value: + +None. + +=back + +=head2 SetAlternateFile + +Specify alternate file from which to read metadata. Tags from the alternate +file are available after L is called or during a call to +L by using a family 8 group name (eg. 'File1' in the +example below). + + $exifTool->SetAlternateFile(File1 => 'images/test1.jpg'); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) Family 8 group name, case insensitive (eg. 'File1', 'File2'...) + +2) Name of alternate input file, or undef to reset + +=item Return Values: + +1 on success, or 0 if the group name is invalid. + +=back + +=head2 SetFileModifyDate + +Write the filesystem modification or creation time from the new value of the +FileModifyDate or FileCreateDate tag. + + $exifTool->SetNewValue(FileModifyDate => '2000:01:02 03:04:05-05:00', + Protected => 1); + $result = $exifTool->SetFileModifyDate($file); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) File name + +2) [optional] Base time if applying shift (days before $^T) + +3) [optional] Tag to write: 'FileModifyDate' (default), or 'FileCreateDate' + +=item Return Value: + +1 if the time was changed, 0 if nothing was done, or -1 if there was an +error setting the time. + +=item Notes: + +Equivalent to, but more efficient than calling L when only the +FileModifyDate or FileCreateDate tag has been set. If a timezone is not +specified, local time is assumed. When shifting, the time of the original +file is used unless the optional base time is specified. + +The ability to write FileCreateDate is currently restricted to Windows +systems only. + +=back + +=head2 SetFileName + +Set the file name and directory, or create a hard link. If not specified, +the new file name is derived from the new values of the FileName and +Directory tags, or from the HardLink or SymLink tag if creating a link. If +the FileName tag contains a '/', then the file is renamed into a new +directory. If FileName ends with '/', then it is taken as a directory name +and the file is moved into the new directory. The new value for the +Directory tag takes precedence over any directory specified in FileName. + + $result = $exifTool->SetFileName($file); + $result = $exifTool->SetFileName($file, $newName); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) Current file name + +2) [optional] New file name + +3) [optional] 'HardLink' or 'SymLink' to create a hard or symbolic link +instead of renaming the file, or 'Test' to test renaming feature by printing +the old and new names instead of changing anything. + +=item Return Value: + +1 on success, 0 if nothing was done, or -1 if there was an error renaming the +file or creating the link. + +=item Notes: + +Will not overwrite existing files. New directories are created as necessary. +If the file is successfully renamed, the new file name may be accessed via +C<$$exifTool{NewName}>. + +=back + +=head2 SetNewGroups + +Set the order of the preferred groups when adding new information. In +subsequent calls to L, new information will be created in the +first valid group of this list. This has an impact only if the group is not +specified when calling L and if the tag name exists in more +than one group. The default order is EXIF, IPTC, XMP, MakerNotes, +QuickTime, Photoshop, ICC_Profile, CanonVRD, Adobe. Any family 0 group name +may be used. Case is not significant. + + $exifTool->SetNewGroups('XMP','EXIF','IPTC'); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1-N) Groups in order of priority. If no groups are specified, the priorities +are reset to the defaults. + +=item Return Value: + +None. + +=back + +=head2 GetNewGroups + +Get current group priority list. + + @groups = $exifTool->GetNewGroups(); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +=item Return Values: + +List of group names in order of write priority. Highest priority first. + +=back + +=head2 GetTagID + +Get the ID for the specified tag. The ID is the IFD tag number in EXIF +information, the property name in XMP information, or the data offset in a +binary data block. For some tags, such as Composite tags where there is no +ID, an empty string is returned. In list context, also returns a language +code for the tag if available and different from the default language (eg. +with alternate language entries for XMP "lang-alt" tags). + + $id = $exifTool->GetTagID($tag); + ($id, $lang) = $exifTool->GetTagID($tag); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) Tag key + +=item Return Values: + +In scalar context, returns the tag ID or '' if there is no ID for this tag. +In list context, returns the tag ID (or '') and the language code (or +undef). + +=back + +=head2 GetDescription + +Get description for specified tag. This function will always return a +defined value. In the case where the description doesn't exist, one is +generated from the tag name. + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) Tag key + +=item Return Values: + +A description for the specified tag. + +=back + +=head2 GetGroup + +Get group name(s) for a specified tag. + + # return family 0 group name (eg. 'EXIF'); + $group = $exifTool->GetGroup($tag, 0); + + # return all groups (eg. qw{EXIF IFD0 Author Main}) + @groups = $exifTool->GetGroup($tag); + + # return groups as a string (eg. 'Main:IFD0:Author') + $group = $exifTool->GetGroup($tag, ':3:1:2'); + + # return groups as a simplified string (eg. 'IFD0:Author') + $group = $exifTool->GetGroup($tag, '3:1:2'); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) Tag key + +2) [optional] Group family number, or string of numbers separated by colons + +=item Return Values: + +Group name (or '' if tag has no group). If no group family is specified, +L returns the name of the group in family 0 when called in scalar +context, or the names of groups for all families in list context. Returns a +string of group names separated by colons if the input group family contains +a colon. The string is simplified to remove a leading 'Main:' and adjacent +identical group names unless the family string begins with a colon. + +=item Notes: + +The group family numbers are currently available: + + 0) Information Type (eg. EXIF, XMP, IPTC) + 1) Specific Location (eg. IFD0, XMP-dc) + 2) Category (eg. Author, Time) + 3) Document Number (eg. Main, Doc1, Doc3-2) + 4) Instance Number (eg. Copy1, Copy2, Copy3...) + 5) Metadata Path (eg. JPEG-APP1-IFD0-ExifIFD) + 6) EXIF/TIFF Format (eg. int8u, int32u, undef, string) + 7) Tag ID (eg. ID-271, ID-rights, ID-a9aut) + 8) Alternate File Number (eg. File1, File2, File3...) + +Families 0 and 1 are based on the file structure, and are similar except +that family 1 is more specific and sub-divides some groups to give more +detail about the specific location where the information was found. For +example, the EXIF group is split up based on the specific IFD (Image File +Directory), the MakerNotes group is divided into groups for each +manufacturer, and the XMP group is separated based on the XMP namespace +prefix. Note that only common XMP namespaces are listed in the +L, but additional +namespaces may be present in some XMP data. Also note that the 'XMP-xmp...' +group names may appear in the older form 'XMP-xap...' since these names +evolved as the XMP standard was developed. The ICC_Profile group is broken +down to give information about the specific ICC_Profile tag from which +multiple values were extracted. As well, information extracted from the +ICC_Profile header is separated into the ICC-header group. + +Family 2 classifies information based on the logical category to which the +information refers. + +Family 3 gives the document number for tags extracted from embedded +documents, or 'Main' for tags from the main document. (See the +L option for extracting tags from embedded documents.) +Nested sub-documents (if they exist) are indicated by numbers separated with +dashes in the group name, to an arbitrary depth. (eg. 'Doc2-3-1' is the 1st +sub-sub-document of the 3rd sub-document of the 2nd embedded document of the +main file.) Document numbers are also used to differentiate samples for +timed metadata in videos. + +Family 4 provides a method for differentiating tags when multiple tags exist +with the same name in the same location. The primary instance of a tag (the +tag extracted when the Duplicates option is disabled and no group is +specified) has no family 4 group name, but additional instances have family +4 group names of 'Copy1', 'Copy2', 'Copy3', etc. For convenience, the +primary tag may also be accessed using a group name of 'Copy0'. + +Family 5 is experimental, and gives the complete path for the metadata in +the file. Generated only if the L option is used when +extracting. + +Family 6 is currently used only for EXIF/TIFF metadata, and gives the format +type of the extracted value. Generated only if the L option is +used when extracting. + +Family 7 is used for tag ID's. The group names are the actual tag ID's, +with a leading "ID-" string. Non-numerical ID's have characters other than +[-_A-Za-z0-9] converted to hex. Numerical tag ID's are returned in hex if +the L option is set, otherwise decimal is used. When specifying +a family 7 group name, numerical ID's may be in hex or decimal, and +non-numerical ID's may or may not have characters other than [-_A-Za-z0-9] +converted to hex. Note that unlike other group names, the tag ID's of +family 7 group names are case sensitive (but the leading "ID-" is not). + +Family 8 specifies the alternate file set from a call to L. + +See L for complete lists of group names. + +=back + +=head2 GetGroups + +Get list of group names that exist in the specified information. + + @groups = $exifTool->GetGroups($info, 2); + @groups = $exifTool->GetGroups('3:1'); + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +1) [optional] Info hash ref (default is all extracted info) + +2) [optional] Group family number, or string of numbers (default 0) + +=item Return Values: + +List of group names in alphabetical order. If information hash is not +specified, the group names are returned for all extracted information. See +L for an description of family numbers and family number strings. + +=back + +=head2 BuildCompositeTags + +Builds composite tags from required tags. The composite tags are +convenience tags which are derived from the values of other tags. This +routine is called automatically by L and L if the +Composite option is set. + +=over 4 + +=item Inputs: + +0) ExifTool object reference + +=item Return Values: + +(none) + +=item Notes: + +Tag values are calculated in alphabetical order unless a tag Require's or +Desire's another composite tag, in which case the calculation is deferred +until after the other tag is calculated. + +Composite tags may need to read data from the image for their value to be +determined, and for these L must be called while the +image is available. This is only a problem if L is called with +a filename (as opposed to a file reference or scalar reference) since in +this case the file is closed before L returns. Here the +Composite option may be used so that L is called from +within L, before the file is closed. + +=back + +=head2 AvailableOptions [static] + +Get a list of available API options. (See L for option details.) + +=over 4 + +=item Inputs: + +(none) + +=item Return Values: + +Reference to list of available options. Each entry in the list is a list +reference with 3 items: 0=Option name, 1=Default value, 2=Description. + + my $opts = Image::ExifTool::AvailableOptions(); + foreach (@$opts) { + my ($optionName, $defaultValue, $description) = @$_; + ... + } + +=back + +=head2 GetTagName [static] + +Get name of tag from tag key. This is a convenience function that +strips the embedded instance number, if it exists, from the tag key. + +Note: "static" in the heading above indicates that the function does not +require an ExifTool object reference as the first argument. All functions +documented below are also static. + + $tagName = Image::ExifTool::GetTagName($tag); + +=over 4 + +=item Inputs: + +0) Tag key + +=item Return Value: + +Tag name. This is the same as the tag key but has the instance number +removed. + +=back + +=head2 GetShortcuts [static] + +Get a list of shortcut tags. + +=over 4 + +=item Inputs: + +(none) + +=item Return Values: + +List of shortcut tags (as defined in Image::ExifTool::Shortcuts). + +=back + +=head2 GetAllTags [static] + +Get list of all available tag names. + + @tagList = Image::ExifTool::GetAllTags($group); + +=over 4 + +=item Inputs: + +0) [optional] Group name, or string of group names separated by colons + +=item Return Values: + +A list of all available tags in alphabetical order, or all tags in a +specified group or intersection of groups. The group name is case +insensitive, and any group in families 0-2 may be used except for EXIF +family 1 groups (ie. the specific IFD). + +=back + +=head2 GetWritableTags [static] + +Get list of all writable tag names. + + @tagList = Image::ExifTool::GetWritableTags($group); + +=over 4 + +=item Inputs: + +0) [optional] Group name, or string of group names separated by colons + +=item Return Values: + +A list of all writable tags in alphabetical order. These are the tags for +which values may be set through L. If a group name is given, +returns only writable tags in specified group(s). The group name is case +insensitive, and any group in families 0-2 may be used except for EXIF +family 1 groups (ie. the specific IFD). + +=back + +=head2 GetAllGroups [static] + +Get list of all group names in specified family. + + @groupList = Image::ExifTool::GetAllGroups($family); + +=over 4 + +=item Inputs: + +0) Group family number (0-7) + +=item Return Values: + +A list of all groups in the specified family in alphabetical order. + +=back + +Here is a complete list of groups for each of these families: + +=over 4 + +=item Family 0 (Information Type): + +AFCP, AIFF, APE, APP0, APP1, APP11, APP12, APP13, APP14, APP15, APP2, APP3, +APP4, APP5, APP6, APP8, ASF, Audible, CanonVRD, Composite, DICOM, DNG, DV, +DjVu, Ducky, EXE, EXIF, ExifTool, FITS, FLAC, FLIR, File, Flash, FlashPix, +Font, FotoStation, GIF, GIMP, GeoTiff, GoPro, H264, HTML, ICC_Profile, ID3, +IPTC, ISO, ITC, JFIF, JPEG, JSON, JUMBF, Jpeg2000, LNK, Leaf, Lytro, M2TS, +MIE, MIFF, MISB, MNG, MOI, MPC, MPEG, MPF, MXF, MakerNotes, Matroska, Meta, +Ogg, OpenEXR, Opus, PDF, PICT, PLIST, PNG, PSP, Palm, Parrot, PanasonicRaw, +PhotoCD, PhotoMechanic, Photoshop, PostScript, PrintIM, QuickTime, RAF, +RIFF, RSRC, RTF, Radiance, Rawzor, Real, Red, SVG, SigmaRaw, Stim, Theora, +Torrent, Trailer, UserParam, VCard, Vorbis, WTV, XML, XMP, ZIP + +=item Family 1 (Specific Location): + +AC3, AFCP, AIFF, APE, ASF, AVI1, Adobe, AdobeCM, AdobeDNG, Apple, Audible, +CBOR, CIFF, CameraIFD, Canon, CanonCustom, CanonDR4, CanonRaw, CanonVRD, +Casio, Chapter#, Composite, DICOM, DJI, DNG, DV, DjVu, DjVu-Meta, Ducky, +EPPIM, EXE, EXIF, ExifIFD, ExifTool, FITS, FLAC, FLIR, File, Flash, +FlashPix, Font, FotoStation, FujiFilm, FujiIFD, GE, GIF, GIMP, GPS, +GSpherical, Garmin, GeoTiff, GlobParamIFD, GoPro, GraphConv, H264, HP, HTC, +HTML, HTML-dc, HTML-ncc, HTML-office, HTML-prod, HTML-vw96, HTTP-equiv, +ICC-chrm, ICC-clrt, ICC-header, ICC-meas, ICC-meta, ICC-view, ICC_Profile, +ICC_Profile#, ID3, ID3v1, ID3v1_Enh, ID3v2_2, ID3v2_3, ID3v2_4, IFD0, IFD1, +IPTC, IPTC#, ISO, ITC, InfiRay, Insta360, InteropIFD, ItemList, JFIF, JFXX, +JPEG, JPEG-HDR, JPS, JSON, JUMBF, JVC, Jpeg2000, KDC_IFD, Keys, Kodak, +KodakBordersIFD, KodakEffectsIFD, KodakIFD, KyoceraRaw, LNK, Leaf, +LeafSubIFD, Leica, Lyrics3, Lytro, M2TS, MAC, MIE-Audio, MIE-Camera, +MIE-Canon, MIE-Doc, MIE-Extender, MIE-Flash, MIE-GPS, MIE-Geo, MIE-Image, +MIE-Lens, MIE-Main, MIE-MakerNotes, MIE-Meta, MIE-Orient, MIE-Preview, +MIE-Thumbnail, MIE-UTM, MIE-Unknown, MIE-Video, MIFF, MISB, MNG, MOBI, MOI, +MPC, MPEG, MPF0, MPImage, MS-DOC, MXF, MacOS, MakerNotes, MakerUnknown, +Matroska, MediaJukebox, Meta, MetaIFD, Microsoft, Minolta, MinoltaRaw, +Motorola, NITF, Nikon, NikonCapture, NikonCustom, NikonScan, NikonSettings, +NineEdits, Nintendo, Ocad, Ogg, Olympus, OpenEXR, Opus, PDF, PICT, PNG, +PNG-cICP, PNG-pHYs, PSP, Palm, Panasonic, PanasonicRaw, Parrot, Pentax, +PhaseOne, PhotoCD, PhotoMechanic, Photoshop, PictureInfo, PostScript, +PreviewIFD, PrintIM, ProfileIFD, Qualcomm, QuickTime, RAF, RAF2, RIFF, +RMETA, RSRC, RTF, Radiance, Rawzor, Real, Real-CONT, Real-MDPR, Real-PROP, +Real-RA3, Real-RA4, Real-RA5, Real-RJMD, Reconyx, Red, Ricoh, SPIFF, SR2, +SR2DataIFD, SR2SubIFD, SRF#, SVG, Samsung, Sanyo, Scalado, Sigma, SigmaRaw, +Sony, SonyIDC, Stim, SubIFD, System, Theora, Torrent, Track#, UserData, +VCalendar, VCard, VNote, Version0, Vorbis, WTV, XML, XMP, XMP-DICOM, +XMP-Device, XMP-GAudio, XMP-GCamera, XMP-GCreations, XMP-GDepth, XMP-GFocus, +XMP-GImage, XMP-GPano, XMP-GSpherical, XMP-LImage, XMP-MP, XMP-MP1, +XMP-PixelLive, XMP-aas, XMP-acdsee, XMP-album, XMP-apple-fi, XMP-ast, +XMP-aux, XMP-cc, XMP-cell, XMP-crd, XMP-creatorAtom, XMP-crs, XMP-dc, +XMP-dex, XMP-digiKam, XMP-drone-dji, XMP-dwc, XMP-et, XMP-exif, XMP-exifEX, +XMP-expressionmedia, XMP-extensis, XMP-fpv, XMP-getty, XMP-hdr, XMP-hdrgm, +XMP-ics, XMP-iptcCore, XMP-iptcExt, XMP-lr, XMP-mediapro, XMP-microsoft, +XMP-mwg-coll, XMP-mwg-kw, XMP-mwg-rs, XMP-nine, XMP-panorama, XMP-pdf, +XMP-pdfx, XMP-photomech, XMP-photoshop, XMP-plus, XMP-pmi, XMP-prism, +XMP-prl, XMP-prm, XMP-pur, XMP-rdf, XMP-sdc, XMP-swf, XMP-tiff, XMP-x, +XMP-xmp, XMP-xmpBJ, XMP-xmpDM, XMP-xmpMM, XMP-xmpNote, XMP-xmpPLUS, +XMP-xmpRights, XMP-xmpTPg, ZIP, iTunes + +=item Family 2 (Category): + +Audio, Author, Camera, Device, Document, ExifTool, Image, Location, Other, +Preview, Printing, Time, Unknown, Video + +=item Family 3 (Document Number): + +Doc#, Main + +=item Family 4 (Instance Number): + +Copy# + +=item Family 5 (Metadata Path): + +eg. JPEG-APP1-IFD0-ExifIFD + +=item Family 6 (EXIF/TIFF Format): + +int8u, string, int16u, int32u, rational64u, int8s, undef, int16s, int32s, +rational64s, float, double, ifd, unicode, complex, int64u, int64s, ifd64 + +=item Family 7 (Tag ID): + +ID-xxx (Where xxx is the tag ID. Numerical ID's are returned in hex with a +leading "0x" if the HexTagIDs option is set, or decimal otherwise. +Characters in non-numerical ID's which are not valid in a group name are +returned as 2 hex digits.) + +=item Family 8 (Alternate File): + +File# + +=back + +Note: This function may also be called as an ExifTool member function to +allow the HexTagIDs option to be set when retrieving family 7 group names. + +=head2 GetDeleteGroups [static] + +Get list of all deletable group names. + + @delGroups = Image::ExifTool::GetDeleteGroups(); + +=over 4 + +=item Inputs: + +None. + +=item Return Values: + +A list of deletable group names in alphabetical order. The current list of +deletable group names is: + +Adobe, AFCP, APP0, APP1, APP10, APP11, APP12, APP13, APP14, APP15, APP2, +APP3, APP4, APP5, APP6, APP7, APP8, APP9, Audio, Author, Camera, CanonVRD, +CIFF, Document, Ducky, EXIF, ExifIFD, ExifTool, File, FlashPix, FotoStation, +GlobParamIFD, GPS, ICC_Profile, IFD0, IFD1, Image, Insta360, InteropIFD, +IPTC, ItemList, JFIF, Jpeg2000, Keys, Location, MakerNotes, Meta, MetaIFD, +Microsoft, MIE, MPF, NikonCapture, Other, PDF, PDF-update, PhotoMechanic, +Photoshop, PNG, PNG-pHYs, Preview, PrintIM, Printing, QuickTime, RMETA, +RSRC, SubIFD, Time, Trailer, UserData, Video, XML, XML-*, XMP, XMP-* + +To schedule a group for deletion, call L with a tag name like +'EXIF:*' and an undefined tag value. + +Deleting a family 0 or 1 group will delete the entire corresponding block of +metadata, but deleting a family 2 group (eg. Audio, Author, Camera, etc.) +deletes the individual tags belonging to that category. + +The 'Trailer' group allows all trailers in JPEG and TIFF-format images to be +deleted at once, including unknown trailers. Note that the JPEG "APP" +groups are special, and are used only to delete application segments which +are not associated with another deletable group. For example, deleting +'APP14:*' will delete other APP14 segments, but not the APP14 "Adobe" +segment. + +=back + +=head2 GetFileType [static] + +Get type of file given file name. + + my $type = Image::ExifTool::GetFileType($filename); + my $desc = Image::ExifTool::GetFileType($filename, 1); + +=over 4 + +=item Inputs: + +0) [optional] File name (or just an extension) + +1) [optional] Flag to return a description instead of a type. Default is +undef. Set to 0 to also return types of recognized but unsupported files +(otherwise the return value for unsupported files is undef), or 1 to return +descriptions. + +=item Return Value: + +A string, based on the file extension, which indicates the basic format of +the file. Note that some files may be based on other formats (like many RAW +image formats are based on TIFF). In list context, may return more than one +file type if the file may be based on different formats. Returns undef if +files with this extension are not yet supported by ExifTool. Returns a list +of extensions for all supported file types if no input extension is +specified (or all recognized file types if the description flag is set to +0). Returns a more detailed description of the specific file format when the +description flag is set. + +=back + +=head2 CanWrite [static] + +Can the specified file be written? + + my $writable = Image::ExifTool::CanWrite($filename); + +=over 4 + +=item Inputs: + +0) File name or extension + +=item Return Value: + +True if ExifTool supports writing files of this type (based on the file +extension). + +=back + +=head2 CanCreate [static] + +Can the specified file be created? + + my $creatable = Image::ExifTool::CanCreate($filename); + +=over 4 + +=item Inputs: + +0) File name or extension + +=item Return Value: + +True if ExifTool can create files with this extension from scratch. +Currently, this can only be done with XMP, MIE, ICC, VRD, DR4, EXV and EXIF +files. + +=back + +=head2 AddUserDefinedTags [static] + +Add user-defined tags to an existing tag table at run time. This differs +from the usual technique of creating user-defined tags via the +%Image::ExifTool::UserDefined hash (see the ExifTool_config file in the +Image::ExifTool distribution) because it allows tags to be added after a tag +table has been initialized. + + use Image::ExifTool ':Public'; + my %tags = ( + TestTagID1 => { Name => 'TestTagName1' }, + TestTagID2 => { Name => 'TestTagName2' }, + ); + my $num = AddUserDefinedTags('Image::ExifTool::PDF::Info', %tags); + +=over 4 + +=item Inputs: + +0) Destination tag table name + +1-N) Pairs of tag ID / tag information hash references for the new tags + +=item Return Value: + +The number of tags added. + +=item Notes + +Pre-existing tags with the same ID will be replaced in the destination +table. See lib/Image/ExifTool/README in the full distribution for full +details on the elements of the tag information hash. + +=back + +=head1 CHARACTER ENCODINGS + +Certain meta information formats allow coded character sets other than plain +ASCII. When reading, most known encodings are converted to the external +character set according to the L option, or to UTF-8 by default. +When writing, the inverse conversions are performed. Alternatively, special +characters may be converted to/from HTML character entities with the +L HTML option. + +A distinction is made between the external character set visible via the +ExifTool API, and the internal character used to store text in the metadata +of a file. These character sets may be specified separately as follows: + +=over 4 + +=item External Character Sets: + +The encoding for tag values passed to/from ExifTool API functions is set via +the L option, which is 'UTF8' by default. + +The encoding of file names is specified via the L option. +By default, L is not defined, and file names passed to +ExifTool are used directly in calls to the system i/o routines (which expect +UTF-8 strings on Mac/Linux, but default to the system code page on Windows). +In this mode on Windows a warning is issued if a file name contains special +characters, but this warning may be avoided by setting L +to an empty string. Setting L to any other value causes +file names to be converted from the specified encoding to one appropriate +for the system. In Windows this also has the effect of activating Unicode +filename support via the special Windows wide-character i/o routines if +Win32API::File is available. + +=item Internal Character Sets: + +The encodings used to store strings in the various metadata formats. These +encodings may be changed for certain types of metadata via the +L, L, L, L, +L and L options. + +=back + +Values are returned as byte strings of encoded characters. Perl wide +characters are not used. By default, most returned strings are encoded in +UTF-8. For these, Encode::decode_utf8() may be used to convert to a +sequence of logical Perl characters. Note that some settings of the +PERL_UNICODE environment variable may be incompatible with ExifTool's +character handling. + +More specific details are given below about how character coding is handled +for EXIF, IPTC, XMP, PNG, ID3, PDF, Photoshop, QuickTime, AIFF, MIE and +Vorbis information: + +=head2 EXIF + +Most textual information in EXIF is stored in ASCII format (called "string" +in the L). By +default ExifTool does not convert these strings. However, it is not +uncommon for applications to write UTF-8 or other encodings where ASCII is +expected. To deal with these, ExifTool allows the internal EXIF string +encoding to be specified with L, which causes EXIF string +values to be converted from the specified character set when reading, and +stored with this character set when writing. (The MWG recommends using +UTF-8 encoding for EXIF strings, and in keeping with this the +L module sets the default internal EXIF string +encoding to UTF-8, but note that this will have no effect unless the +external encoding is also set to something other than the default of UTF-8.) + +A few EXIF tags (UserComment, GPSProcessingMethod and GPSAreaInformation) +support a designated internal text encoding, with values stored as ASCII, +Unicode (UCS-2) or JIS. When reading these tags, ExifTool converts Unicode +and JIS to the external character set specified by the L +option, or to UTF-8 by default. ASCII text is not converted. When writing, +text is stored as ASCII unless the string contains special characters, in +which case it is converted from the external character set (UTF-8 by +default), and stored as Unicode. ExifTool writes Unicode in native EXIF byte +ordering by default, but the byte order may be specified by setting the +ExifUnicodeByteOrder tag (see the +L). + +The EXIF "XP" tags (XPTitle, XPComment, etc) are always stored as +little-endian Unicode (UCS-2), and are read and written using the specified +character set. + +=head2 IPTC + +The value of the IPTC:CodedCharacterSet tag determines how the internal IPTC +string values are interpreted. If CodedCharacterSet exists and has a value +of 'UTF8' (or 'ESC % G') then string values are assumed to be stored as +UTF-8, otherwise Windows Latin1 (cp1252, 'Latin') coding is assumed by +default, but this can be changed with the L option. When +reading, these strings are converted to the character set specified by the +L option. When writing, the inverse conversions are performed. +No conversion is done if the internal (IPTC) and external (ExifTool) +character sets are the same. Note that ISO 2022 character set shifting is +not supported. Instead, a warning is issued and the string is not converted +if an ISO 2022 shift code is encountered. See L +for the official IPTC specification. + +ExifTool may be used to convert IPTC values to a different internal +encoding. To do this, all IPTC tags must be rewritten along with the +desired value of CodedCharacterSet. For example, the following command +changes the internal IPTC encoding to UTF-8 (from Windows Latin1 unless +CodedCharacterSet was already 'UTF8'): + + exiftool -tagsfromfile @ -iptc:all -codedcharacterset=utf8 a.jpg + +or from Windows Latin2 (cp1250) to UTF-8: + + exiftool -tagsfromfile @ -iptc:all -codedcharacterset=utf8 \ + -charset iptc=latin2 a.jpg + +and this command changes it back from UTF-8 to Windows Latin1 (cp1252): + + exiftool -tagsfromfile @ -iptc:all -codedcharacterset= a.jpg + +or to Windows Latin2: + + exiftool -tagsfromfile @ -iptc:all -codedcharacterset= \ + -charset iptc=latin2 a.jpg + +Unless CodedCharacterSet is 'UTF8', applications have no reliable way to +determine the IPTC character encoding. For this reason, it is recommended +that CodedCharacterSet be set to 'UTF8' when creating new IPTC. + +(Note: Here, "IPTC" Refers to the older IPTC IIM format. The more recent +IPTC Core and Extension specifications actually use the XMP format.) + +=head2 XMP + +ExifTool reads XMP encoded as UTF-8, UTF-16 or UTF-32, and converts them all +to UTF-8 internally. Also, all XML character entity references and numeric +character references are converted. When writing, ExifTool always encodes +XMP as UTF-8, converting the following 5 characters to XML character +references: E E E E<39> E. By default no further +conversion is performed, however if the L option is other than +'UTF8' then text is converted to/from the specified character set when +reading/writing. + +=head2 PNG + +L are +stored as tEXt, zTXt and iTXt chunks in PNG images. The tEXt and zTXt +chunks use ISO 8859-1 encoding, while iTXt uses UTF-8. When reading, +ExifTool converts all PNG textual data to the character set specified by the +L option. When writing, ExifTool generates a tEXt chunk (or zTXt +with the L option) if the text doesn't contain special characters +or if Latin encoding is specified; otherwise an iTXt chunk is used and the +text is converted from the specified character set and stored as UTF-8. + +=head2 JPEG Comment + +The encoding for the JPEG Comment (COM segment) is not specified, so +ExifTool reads/writes this text without conversion. + +=head2 ID3 + +The ID3v1 specification officially supports only ISO 8859-1 encoding (a +subset of Windows Latin1), although some applications may incorrectly use +other character sets. By default ExifTool converts ID3v1 text from Latin to +the character set specified by the L option. However, the +internal ID3v1 charset may be specified with the L option. The +encoding for ID3v2 information is stored in the file, so ExifTool converts +ID3v2 text from this encoding to the character set specified by the +L option. ExifTool does not currently write ID3 information. + +=head2 PDF + +PDF text strings are stored in either PDFDocEncoding (similar to Windows +Latin1) or Unicode (UCS-2). When reading, ExifTool converts to the +character set specified by the L option. When writing, ExifTool +encodes input text from the specified character set as Unicode only if the +string contains special characters, otherwise PDFDocEncoding is used. + +=head2 Photoshop + +Some Photoshop resource names are stored as Pascal strings with unknown +encoding. By default, ExifTool assumes MacRoman encoding and converts this +to UTF-8, but the internal and external character sets may be specified with +the L and L options respectively. + +=head2 QuickTime + +QuickTime text strings may be stored in a variety of poorly document +formats. ExifTool does its best to decode these according to the L +option setting. For some QuickTime strings, ExifTool assumes a default +encoding of MacRoman, but this may be changed with the L +option. + +=head2 AIFF + +AIFF strings are assumed to be stored in MacRoman, and are converted +according to the L option when reading. + +=head2 RIFF + +The internal encoding of RIFF strings (eg. in AVI and WAV files) is assumed +to be Latin unless otherwise specified by the RIFF CSET chunk or the +L option. + +=head2 MIE + +MIE strings are stored as either UTF-8 or ISO 8859-1. When reading, UTF-8 +strings are converted according to the L option, and ISO 8859-1 +strings are never converted. When writing, input strings are converted from +the specified character set to UTF-8. The resulting strings are stored as +UTF-8 if they contain multi-byte UTF-8 character sequences, otherwise they +are stored as ISO 8859-1. + +=head2 Vorbis + +Vorbis comments are stored as UTF-8, and are converted to the character set +specified by the L option. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Many people have helped in the development of ExifTool through their bug +reports, comments and suggestions, and/or additions to the code. See the +ACKNOWLEDGEMENTS in the individual Image::ExifTool modules and in +html/index.html of the Image::ExifTool distribution package for a list of +people who have contributed to this project. + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L + +=cut + +# end diff --git a/ExifTool/lib/Image/ExifTool/7Z.pm b/ExifTool/lib/Image/ExifTool/7Z.pm new file mode 100644 index 0000000..1b8d0bc --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/7Z.pm @@ -0,0 +1,793 @@ +#------------------------------------------------------------------------------ +# File: 7Z.pm +# +# Description: Read 7z archive meta information +# +# Revisions: 2023/04/28 - Amir Gooran (Cyberno) +# 2023-05-06 - PH Minor changes in ExifTool interfacing +# +# References: 1) https://py7zr.readthedocs.io/en/latest/archive_format.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::7Z; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.00'; + +sub ReadUInt32 { + my $buff; + + $_[0]->Read($buff, 4); + my ($output) = unpack('L', $buff); + return $output; +} + +sub ReadUInt64 { + my $buff; + my $output; + + $_[0]->Read($buff, 1); + my $b = ord($buff); + if($b == 255){ # read real uint64 + $_[0]->Read($buff, 8); + my ($output) = unpack('Q', $buff); + return $output; + } + my @blen = (0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFE); + + my $mask = 0x80; + my $vlen = 8; + + for (my $l = 0 ; $l < scalar(@blen) ; $l++) { + my $v = $blen[$l]; + if($b <= $v){ + $vlen = $l; + last; + } + $mask >>= 1; + } + if($vlen == 0){ + return $b & ($mask - 1); + } + $_[0]->Read($buff, $vlen); + $buff .= "\0\0\0\0\0\0\0\0"; + + my $value = unpack('Q', $buff); + my $highpart = $b & ($mask - 1); + return $value + ($highpart << ($vlen * 8)); +} + +sub ReadRealUInt64 { + my $buff; + + $_[0]->Read($buff, 8); + my $value = unpack('Q', $buff); + return $value; +} + +sub ReadBoolean { + my $buff; + my $count = $_[1]; + my $checkall = $_[2]; + my @result = (); + + if($checkall){ + $_[0]->Read($buff, 1); + my $all_defined = ord($buff); + if($all_defined != 0){ + @result = (1)x$count; + return @result; + } + } + + my $b = 0; + my $mask = 0; + + for (my $i = 0 ; $i < $count ; $i++) { + if($mask == 0){ + $_[0]->Read($buff, 1); + $b = ord($buff); + $mask = 0x80; + } + push(@result, ($b & $mask) != 0); + $mask >>= 1; + } + return @result; +} + +sub ReadUTF16 { + my $val = ""; + my $ch; + + for(my $i=0; $i < 65536; $i++){ + $_[0]->Read($ch, 2); + if($ch eq "\0\0"){ + last; + } + $val .= $ch; + } + return $val; +} + +sub ReadPackInfo { + my $et = shift; + + my $buff; + my %out_packinfo = (); + $out_packinfo{"packsizes"} = (); + + $out_packinfo{"packpos"} = ReadUInt64($_[0]); + my $numstreams = ReadUInt64($_[0]); + $et->VPrint(0, "Number Of Streams: $numstreams\n"); + + $_[0]->Read($buff, 1); + my $pid = ord($buff); + + my @packsizes; + if($pid == 9){ # size + for (my $i = 0 ; $i < $numstreams ; $i++) { + push(@{ $out_packinfo{"packsizes"} }, ReadUInt64($_[0])); + } + $_[0]->Read($buff, 1); + $pid = ord($buff); + if($pid == 10){ # crc + my @crcs; + my @digestdefined = ReadBoolean($_[0], $numstreams, 1); + foreach my $crcexist (@digestdefined) { + if($crcexist){ + push(@crcs, ReadUInt32($_[0])); + } + } + $_[0]->Read($buff, 1); + $pid = ord($buff); + } + } + if($pid != 0) { # end id expected + return 0; + } + return \%out_packinfo; +} + +sub findInBinPair { + my @bindpairs = @{$_[0]}; + my $index = $_[1]; + + for (my $i = 0; $i < scalar(@bindpairs); $i++) { + if($bindpairs[$i] == $index){ + return $i; + } + } + return -1; +} + +sub ReadFolder { + my $et = shift; + my $buff; + my $totalin = 0; + my $totalout = 0; + my %out_folder = (); + $out_folder{"packed_indices"} = (); + $out_folder{"bindpairs"} = (); + $out_folder{"coders"} = (); + + my $num_coders = ReadUInt64($_[0]); + $et->VPrint(0, "Number of coders: $num_coders\n"); + + for (my $i = 0; $i < $num_coders; $i++) { + my %c = (); + $_[0]->Read($buff, 1); + my $b = ord($buff); + my $methodsize = $b & 0xF; + my $iscomplex = ($b & 0x10) == 0x10; + my $hasattributes = ($b & 0x20) == 0x20; + if($methodsize > 0){ + $_[0]->Read($buff, $methodsize); + $c{"method"} = $buff; + } + else{ + $c{"method"} = "\0"; + } + if($iscomplex){ + $c{"numinstreams"} = ReadUInt64($_[0]); + $c{"numoutstreams"} = ReadUInt64($_[0]); + } + else{ + $c{"numinstreams"} = 1; + $c{"numoutstreams"} = 1; + } + $totalin += $c{"numinstreams"}; + $totalout += $c{"numoutstreams"}; + if($hasattributes){ + my $proplen = ReadUInt64($_[0]); + $_[0]->Read($buff, $proplen); + $c{"properties"} = $buff; + } + else { + $c{"properties"} = undef; + } + $et->VPrint(0, "Reading coder $i\n"); + push(@{ $out_folder{"coders"} }, \%c); + } + my $num_bindpairs = $totalout - 1; + for (my $i = 0; $i < $num_bindpairs; $i++) { + my @bond = (ReadUInt64($_[0]), ReadUInt64($_[0])); + push(@{ $out_folder{"bindpairs"} }, @bond); + } + my $num_packedstreams = $totalin - $num_bindpairs; + if($num_packedstreams == 1){ + for (my $i = 0; $i < $totalin; $i++) { + if(findInBinPair(\@{ $out_folder{"bindpairs"} }, $i) < 0){ + push(@{ $out_folder{"packed_indices"} }, $i); + } + } + } + else{ + for (my $i = 0; $i < $num_packedstreams; $i++) { + push(@{ $out_folder{"packed_indices"} }, ReadUInt64($_[0])); + } + } + + return \%out_folder; +} + +sub RetrieveCodersInfo{ + my $et = shift; + my $buff; + my @folders = @{ $_[1] }; + + $_[0]->Read($buff, 1); + my $pid = ord($buff); + + if($pid != 0x0c){ # coders unpack size id expected + return 0; + } + foreach my $folder (@folders) { + $folder->{"unpacksizes"} = (); + foreach my $c (@{ $folder->{"coders"} }) { + for (my $i = 0 ; $i < $c->{"numoutstreams"} ; $i++) { + push(@{ $folder->{"unpacksizes" } }, ReadUInt64($_[0])); + } + } + } + $_[0]->Read($buff, 1); + $pid = ord($buff); + + if($pid == 0x0a){ #crc + my $numfolders = scalar(@folders); + $et->VPrint(0, "Number of folders: $numfolders\n"); + my @defined = ReadBoolean($_[0], $numfolders, 1); + my @crcs; + foreach my $crcexist (@defined) { + if($crcexist){ + push(@crcs, ReadUInt32($_[0])); + } + } + for (my $i = 0 ; $i < $numfolders ; $i++) { + $folders[$i]->{"digestdefined"} = $defined[$i]; + $folders[$i]->{"crc"} = $crcs[$i]; + } + $_[0]->Read($buff, 1); + $pid = ord($buff); + } + + if($pid != 0x00){ # end id expected + $et->VPrint(0, "Invalid PID: $pid\n"); + return 0; + } + return 1; +} + +sub ReadUnpackInfo { + my $et = shift; + my $buff; + my %out_unpackinfo = (); + + $_[0]->Read($buff, 1); + my $pid = ord($buff); + + if($pid != 0xb) { # folder id expected + return 0; + } + + $out_unpackinfo{"numfolders"} = ReadUInt64($_[0]); + $out_unpackinfo{"folders"} = (); + + $_[0]->Read($buff, 1); + my $external = ord($buff); + + if($external == 0x00){ + for (my $i = 0 ; $i < $out_unpackinfo{"numfolders"}; $i++) { + $et->VPrint(0, "Reading folder $i\n"); + my $folder = ReadFolder($et, $_[0]); + push(@{ $out_unpackinfo{"folders"} }, $folder); + } + } + return 0 unless RetrieveCodersInfo($et, $_[0], $out_unpackinfo{"folders"}); + return \%out_unpackinfo; +} + +sub ReadSubstreamsInfo { + my $et = shift; + my $buff; + my %out_substreamsinfo = (); + $out_substreamsinfo{"num_unpackstreams_folders"} = (); + + my $numfolders = $_[1]; + my $folders = $_[2]; + + $_[0]->Read($buff, 1); + my $pid = ord($buff); + if($pid == 13){ # num unpack stream + $et->VPrint(0, "Num unpack stream detected.\n"); + for (my $i = 0 ; $i < $numfolders; $i++) { + push(@{ $out_substreamsinfo{"num_unpackstreams_folders"} }, ReadUInt64($_[0])); + } + $_[0]->Read($buff, 1); + $pid = ord($buff); + } + else{ + @{ $out_substreamsinfo{"num_unpackstreams_folders"} } = (1)x$numfolders; + } + if($pid == 9){ # size property + $et->VPrint(0, "Size property detected.\n"); + $out_substreamsinfo{"unpacksizes"} = (); + for(my $i=0; $i< scalar(@{ $out_substreamsinfo{"num_unpackstreams_folders"} }); $i++){ + my $totalsize = 0; + for(my $j=1; $j < @{ $out_substreamsinfo{"num_unpackstreams_folders"} }[$i]; $j++){ + my $size = ReadUInt64($_[0]); + push(@{ $out_substreamsinfo{"unpacksizes"} }, $size); + $totalsize += $size; + } + # self.unpacksizes.append(folders[i].get_unpack_size() - totalsize) + } + $_[0]->Read($buff, 1); + $pid = ord($buff); + } + my $num_digests = 0; + my $num_digests_total = 0; + for (my $i = 0 ; $i < $numfolders; $i++) { + my $numsubstreams = @{ $out_substreamsinfo{"num_unpackstreams_folders"} }[$i]; + if($numsubstreams != 1 or not @{ $folders }[$i]->{"digestdefined"}){ + $num_digests += $numsubstreams; + } + $num_digests_total += $numsubstreams; + } + $et->VPrint(0, "Num Digests Total: $num_digests_total\n"); + if($pid == 10) { # crc property + $et->VPrint(0, "CRC property detected.\n"); + my @crcs; + my @defined = ReadBoolean($_[0], $num_digests, 1); + foreach my $crcexist (@defined) { + push(@crcs, ReadUInt32($_[0])); + } + $_[0]->Read($buff, 1); + $pid = ord($buff); + } + if($pid != 0x00){ # end id expected + return 0; + } + return \%out_substreamsinfo; +} + +sub ReadStreamsInfo { + my $et = shift; + my $buff; + my $unpackinfo; + my %out_streamsinfo = (); + + $_[0]->Read($buff, 1); + my $pid = ord($buff); + if($pid == 6){ # pack info + my $packinfo = ReadPackInfo($et, $_[0]); + return 0 unless $packinfo; + $out_streamsinfo{"packinfo"} = $packinfo; + $_[0]->Read($buff, 1); + $pid = ord($buff); + } + if($pid == 7) { # unpack info + $et->VPrint(0, "Unpack info data detected.\n"); + $unpackinfo = ReadUnpackInfo($et, $_[0]); + return 0 unless $unpackinfo; + $out_streamsinfo{"unpackinfo"} = $unpackinfo; + $_[0]->Read($buff, 1); + $pid = ord($buff); + } + if($pid == 8){ # substreams info + $et->VPrint(0, "Substreams info data detected.\n"); + my $substreamsinfo = ReadSubstreamsInfo($et, $_[0], $unpackinfo->{"numfolders"}, $unpackinfo->{"folders"}); + return 0 unless $substreamsinfo; + $out_streamsinfo{"substreamsinfo"} = $substreamsinfo; + $_[0]->Read($buff, 1); + $pid = ord($buff); + } + if($pid != 0x00){ # end id expected + $et->VPrint(0, "Invalid PID: $pid\n"); + return 0; + } + return \%out_streamsinfo; +} + +sub IsNativeCoder { + my $coder = $_[0]; + + if(ord(substr($coder->{"method"}, 0, 1)) == 3){ + if(ord(substr($coder->{"method"}, 1, 1)) == 1) { + if(ord(substr($coder->{"method"}, 2, 1)) == 1) { + return "LZMA"; + } + } + } + elsif(ord(substr($coder->{"method"}, 0, 1)) == 6){ + if(ord(substr($coder->{"method"}, 1, 1)) == 0xf1) { + if(ord(substr($coder->{"method"}, 2, 1)) == 7) { + if(ord(substr($coder->{"method"}, 3, 1)) == 1) { + return "7zAES"; + } + } + } + } +} + +sub GetDecompressor { + my $et = shift; + + my $folder = $_[0]; + my %out_decompressor = (); + $out_decompressor{"chain"} = (); + $out_decompressor{"input_size"} = $_[1]; + $out_decompressor{"_unpacksizes"} = $folder->{"unpacksizes"}; + @{ $out_decompressor{"_unpacked"} } = (0) x scalar(@{ $out_decompressor{"_unpacksizes"} }); + $out_decompressor{"consumed"} = 0; + $out_decompressor{"block_size"} = 32768; + $out_decompressor{"_unused"} = []; + + foreach my $coder (@{ $folder->{"coders"} }) { + my $algorithm = IsNativeCoder($coder); + if($algorithm eq "7zAES") { + $et->Warn("File is encrypted.", 0); + return 0; + } + else{ + push(@{ $out_decompressor{"chain"} }, $algorithm); + } + } + + return \%out_decompressor; +} + +sub ReadData { + my $et = shift; + my $decompressor = $_[1]; + my $rest_size = $decompressor->{"input_size"} - $decompressor->{"consumed"}; + my $unused_s = scalar(@{ $decompressor->{"_unused"} }); + my $read_size = $rest_size - $unused_s; + my $data = ""; + if($read_size > $decompressor->{"block_size"} - $unused_s){ + $read_size = $decompressor->{"block_size"} - $unused_s; + } + if($read_size > 0){ + $decompressor->{"consumed"} += $_[0]->Read($data, $read_size); + $et->VPrint(0, "Compressed size: $read_size\n"); + } + return $data; +} + +sub Decompress_Internal { + my $data = ""; + for(my $i=0; $i < scalar(@{ $_[0]->{"chain"} }); $i++){ + if(@{ $_[0]->{"_unpacked"} }[$i] < @{ $_[0]->{"_unpacksizes"} }[$i]){ + my %opts = (); + $opts{"Filter"} = Lzma::Filter::Lzma1(); + my ($z, $status) = Compress::Raw::Lzma::RawDecoder->new( %opts ); + $status = $z->code($_[1], $data); + @{ $_[0]->{"_unpacked"} }[$i] += length($data); + } + } + return $data; +} + +sub Decompress { + my $et = shift; + my $max_length = $_[1]; + my $data = ReadData($et, $_[0], $_[1]); + my $tmp = Decompress_Internal($_[1], $data); + return $tmp; +} + +sub ReadName { + my $numfiles = $_[1]; + + for(my $i=0; $i < $numfiles; $i++){ + @{ $_[2] }[$i]->{"filename"} = ReadUTF16($_[0]); + } +} + +sub ReadTimes { + my $et = shift; + my $external; + my $numfiles = $_[1]; + my $name = $_[2]; + + my @defined = ReadBoolean($_[0], $numfiles, 1); + $_[0]->Read($external, 1); + if(ord($external) != 0){ + $et->Warn("Invalid or corrupted file. (ReadTimes)"); + return 0; + } + + for(my $i=0; $i < $numfiles; $i++){ + if($defined[$i]){ + my $value = ReadRealUInt64($_[0]); + $value = $value / 10000000.0 - 11644473600; + @{ $_[3] }[$i]->{$name} = $value; + } + else{ + @{ $_[3] }[$i]->{$name} = undef; + } + } +} + +sub ReadAttributes { + my $numfiles = $_[1]; + + for(my $i=0; $i < $numfiles; $i++){ + if($_[2][$i]){ + my $value = ReadUInt32($_[0]); + @{ $_[3] }[$i]->{"attributes"} = $value >> 8; + } + else{ + @{ $_[3] }[$i]->{"attributes"} = undef; + } + } +} + +sub ReadFilesInfo { + my $et = shift; + my $buff; + + my $numfiles = ReadUInt64($_[0]); + my @out_files = (); + for(my $i = 0; $i < $numfiles; $i++){ + my %new_file = (); + $new_file{"emptystream"} = 0; + push(@out_files, \%new_file); + } + my $numemptystreams = 0; + $et->VPrint(0, "Number of files: $numfiles\n"); + while(1){ + $_[0]->Read($buff, 1); + my $prop = ord($buff); + if($prop == 0){ # end + return \@out_files; + } + my $size = ReadUInt64($_[0]); + if($prop == 25) { # dummy + $_[0]->Seek($size, 1); + next; + } + $_[0]->Read($buff, $size); + my $buffer = new File::RandomAccess(\$buff); + if($prop == 14){ # empty stream + my @isempty = ReadBoolean($buffer, $numfiles, 0); + my $numemptystreams = 0; + for(my $i = 0; $i < $numfiles; $i++){ + if($isempty[$i] == 0){ + $out_files[$i]->{"emptystream"} = 0; + } + else{ + $out_files[$i]->{"emptystream"} = 1; + $numemptystreams++; + } + } + } + elsif($prop == 15) { # empty file + + } + elsif($prop == 17){ # name + $et->VPrint(0, "Name prop detected.\n"); + my $external; + $buffer->Read($external, 1); + my $is_external = ord($external); + if($is_external == 0){ + ReadName($buffer, $numfiles, \@out_files); + } + } + elsif($prop == 20){ # last write time + $et->VPrint(0, "Last write time detected.\n"); + ReadTimes($et, $buffer, $numfiles, "lastwritetime", \@out_files); + } + elsif($prop == 21){ # attributes + $et->VPrint(0, "File attributes detected.\n"); + my $external; + my @defined = ReadBoolean($buffer, $numfiles, 1); + $_[0]->Read($external, 1); + if(ord($external) == 0){ + ReadAttributes($buffer, $numfiles, \@defined, \@out_files); + } + else{ + my $dataindex = ReadUINT64($buffer); + #TODO: try to read external data + } + } + } +} + +sub ExtractHeaderInfo { + my $et = shift; + my $buff; + my %out_headerinfo = (); + $out_headerinfo{"files_info"} = (); + my $files_info; + + $_[0]->Read($buff, 1); + my $pid = ord($buff); + + if($pid == 0x04){ + my $mainstreams = ReadStreamsInfo($et, $_[0]); + if($mainstreams == 0){ + $et->Warn("Invalid or corrupted file. (ExtractHeaderInfo)"); + return 0; + } + $_[0]->Read($buff, 1); + $pid = ord($buff); + } + if($pid == 0x05){ + $et->VPrint(0, "File info pid reached.\n"); + $files_info = ReadFilesInfo($et, $_[0]); + push(@{ $out_headerinfo{"files_info"} }, $files_info); + $_[0]->Read($buff, 1); + $pid = ord($buff); + } + if($pid != 0x00){ # end id expected + $et->VPrint(0, "Invalid PID: $pid\n"); + return 0; + } + return \%out_headerinfo; +} + +sub DisplayFiles { + my $et = shift; + my $docNum = 0; + my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR5'); + + foreach my $currentfile (@{ $_[0] }){ + $$et{DOC_NUM} = ++$docNum; + $et->HandleTag($tagTablePtr, 'ModifyDate', $currentfile->{"lastwritetime"}); + $et->HandleTag($tagTablePtr, 'ArchivedFileName', $currentfile->{"filename"}); + } + delete $$et{DOC_NUM}; + if($docNum > 1 and not $et->Options('Duplicates')){ + $et->Warn("Use the Duplicates option to extract tags for all $docNum files", 1); + } +} + +#------------------------------------------------------------------------------ +# Extract information from a 7z file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid 7z file +sub Process7Z($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($flags, $buff); + + return 0 unless $raf->Read($buff, 6) and $buff eq "7z\xbc\xaf\x27\x1c"; + + $et->SetFileType(); + + $raf->Read($buff, 2); + my ($major_version, $minor_version) = unpack('cc', $buff); + my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR5'); + $et->HandleTag($tagTablePtr, 'FileVersion', sprintf('7z v%d.%.2d',$major_version,$minor_version)); + + $raf->Seek(4, 1); # skip Start Header CRC + + $raf->Read($buff, 20); + my ($nextheaderoffset, $nextheadersize) = unpack('QQx', $buff); + $et->VPrint(0, "NextHeaderOffset: $nextheaderoffset, NextHeaderSize: $nextheadersize\n"); + + $raf->Seek($nextheaderoffset, 1); # going to next header offset + $raf->Read($buff, 1); + my $pid = ord($buff); + if($pid == 1){ # normal header + $et->VPrint(0,"Normal header detected. trying to decode\n"); + my $headerinfo = ExtractHeaderInfo($et, $raf); + if($headerinfo == 0){ + $et->Warn("Invalid or corrupted file."); + return 1; + } + DisplayFiles($et, @{ $headerinfo->{"files_info"} }); + } + elsif($pid == 23){ # encoded header + unless (eval { require Compress::Raw::Lzma }) { + $et->Warn("Install Compress::Raw::Lzma to read encoded 7z information"); + return 1; + } + $et->VPrint(0, "Encoded Header detected. trying to decode\n"); + my $streamsinfo = ReadStreamsInfo($et, $raf); + if($streamsinfo == 0){ + $et->Warn("Invalid or corrupted file."); + return 1; + } + my $buffer2 = (); + foreach my $folder (@{ $streamsinfo->{"unpackinfo"}->{"folders"} }) { + my @uncompressed = @{ $folder->{"unpacksizes"} }; + my $compressed_size = $streamsinfo->{"packinfo"}->{"packsizes"}[0]; + my $uncompressed_size = @uncompressed[scalar(@uncompressed) - 1]; + my $decomporessor = GetDecompressor($et, $folder, $compressed_size); + if($decomporessor == 0){ + $et->Warn("Invalid or corrupted file."); + return 1; + } + + my $src_start = 32; + $src_start += $streamsinfo->{"packinfo"}->{"packpos"}; + $raf->Seek($src_start, 0); + my $remaining = $uncompressed_size; + my $folder_data = ""; + while($remaining > 0){ + $folder_data .= Decompress($et, $raf, $decomporessor, $remaining); + $remaining = $uncompressed_size - length($folder_data); + } + $buffer2 = new File::RandomAccess(\$folder_data); + } + $buffer2->Seek(0, 0); + $buffer2->Read($buff, 1); + $pid = ord($buff); + if($pid != 0x01){ # header field expected + return 0; + } + my $headerinfo = ExtractHeaderInfo($et, $buffer2); + if($headerinfo == 0){ + $et->Warn("Invalid or corrupted file."); + return 1; + } + DisplayFiles($et, @{ $headerinfo->{"files_info"} }); + }else{ # Unknown header + return 0; + } + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::7Z - Read 7z archives + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from 7z archives. + +=head1 AUTHOR + +Copyright 2023, Amir Gooran + +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 + +=back + +=head1 SEE ALSO + +L + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/AES.pm b/ExifTool/lib/Image/ExifTool/AES.pm new file mode 100644 index 0000000..7662bcd --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/AES.pm @@ -0,0 +1,501 @@ +#------------------------------------------------------------------------------ +# File: AES.pm +# +# Description: AES encryption with cipher-block chaining +# +# Revisions: 2010/10/14 - P. Harvey Created +# +# References: 1) http://www.hoozi.com/Articles/AESEncryption.htm +# 2) http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf +# 3) http://www.faqs.org/rfcs/rfc3602.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::AES; + +use strict; +use vars qw($VERSION @ISA @EXPORT_OK); +require Exporter; + +$VERSION = '1.01'; +@ISA = qw(Exporter); +@EXPORT_OK = qw(Crypt); + +my $seeded; # flag set if we already seeded random number generator +my $nr; # number of rounds in AES cipher +my @cbc; # cipher-block chaining bytes + +# arrays (all unsigned character) to hold intermediate results during encryption +my @state = ([],[],[],[]); # the 2-dimensional state array +my @RoundKey; # round keys + +my @sbox = ( + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, +); + +# reverse sbox +my @rsbox = ( + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, +); + +# the round constant word array, $rcon[i], contains the values given by +# x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +# Note that i starts at 1, not 0). +my @rcon = ( + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, + 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, + 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, + 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, + 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, + 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, + 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, + 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, + 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, + 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, + 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, + 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, + 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, + 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, + 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, +); + +#------------------------------------------------------------------------------ +# This function produces 4*($nr+1) round keys. +# The round keys are used in each round to encrypt the states. +# Inputs: 0) key string (must be 16, 24 or 32 bytes long) +sub KeyExpansion($) +{ + my $key = shift; + my @key = unpack 'C*', $key; # convert the key into a byte array + my $nk = int(length($key) / 4); # number of 32-bit words in the key + $nr = $nk + 6; # number of rounds + + # temporary variables (all unsigned characters) + my ($i,@temp); + + # The first round key is the key itself. + for ($i=0; $i<$nk; ++$i) { + @RoundKey[$i*4..$i*4+3] = @key[$i*4..$i*4+3]; + } + # All other round keys are found from the previous round keys. + while ($i < (4 * ($nr+1))) { + + @temp[0..3] = @RoundKey[($i-1)*4..($i-1)*4+3]; + + if ($i % $nk == 0) { + # rotate the 4 bytes in a word to the left once + # [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + @temp[0..3] = @temp[1,2,3,0]; + + # take a four-byte input word and apply the S-box + # to each of the four bytes to produce an output word. + @temp[0..3] = @sbox[@temp[0..3]]; + + $temp[0] = $temp[0] ^ $rcon[$i/$nk]; + + } elsif ($nk > 6 && $i % $nk == 4) { + + @temp[0..3] = @sbox[@temp[0..3]]; + } + $RoundKey[$i*4+0] = $RoundKey[($i-$nk)*4+0] ^ $temp[0]; + $RoundKey[$i*4+1] = $RoundKey[($i-$nk)*4+1] ^ $temp[1]; + $RoundKey[$i*4+2] = $RoundKey[($i-$nk)*4+2] ^ $temp[2]; + $RoundKey[$i*4+3] = $RoundKey[($i-$nk)*4+3] ^ $temp[3]; + ++$i; + } +} + +#------------------------------------------------------------------------------ +# This function adds the round key to state. +# The round key is added to the state by an XOR function. +sub AddRoundKey($) +{ + my $round = shift; + my ($i,$j); + for ($i=0; $i<4; ++$i) { + my $k = $round*16 + $i*4; + for ($j=0; $j<4; ++$j) { + $state[$j][$i] ^= $RoundKey[$k + $j]; + } + } +} + +#------------------------------------------------------------------------------ +# Substitute the values in the state matrix with values in an S-box +sub SubBytes() +{ + my $i; + for ($i=0; $i<4; ++$i) { + @{$state[$i]}[0..3] = @sbox[@{$state[$i]}[0..3]]; + } +} + +sub InvSubBytes() +{ + my $i; + for ($i=0; $i<4; ++$i) { + @{$state[$i]}[0..3] = @rsbox[@{$state[$i]}[0..3]]; + } +} + +#------------------------------------------------------------------------------ +# Shift the rows in the state to the left. +# Each row is shifted with different offset. +# Offset = Row number. So the first row is not shifted. +sub ShiftRows() +{ + # rotate first row 1 columns to left + @{$state[1]}[0,1,2,3] = @{$state[1]}[1,2,3,0]; + + # rotate second row 2 columns to left + @{$state[2]}[0,1,2,3] = @{$state[2]}[2,3,0,1]; + + # rotate third row 3 columns to left + @{$state[3]}[0,1,2,3] = @{$state[3]}[3,0,1,2]; +} + +sub InvShiftRows() +{ + # rotate first row 1 columns to right + @{$state[1]}[0,1,2,3] = @{$state[1]}[3,0,1,2]; + + # rotate second row 2 columns to right + @{$state[2]}[0,1,2,3] = @{$state[2]}[2,3,0,1]; + + # rotate third row 3 columns to right + @{$state[3]}[0,1,2,3] = @{$state[3]}[1,2,3,0]; +} + +#------------------------------------------------------------------------------ +# Find the product of {02} and the argument to xtime modulo 0x1b +# Note: returns an integer which may need to be trimmed to 8 bits +sub xtime($) +{ + return ($_[0]<<1) ^ ((($_[0]>>7) & 1) * 0x1b); +} + +#------------------------------------------------------------------------------ +# Multiply numbers in the field GF(2^8) +sub Mult($$) +{ + my ($x, $y) = @_; + return (($y & 1) * $x) ^ + (($y>>1 & 1) * xtime($x)) ^ + (($y>>2 & 1) * xtime(xtime($x))) ^ + (($y>>3 & 1) * xtime(xtime(xtime($x)))) ^ + (($y>>4 & 1) * xtime(xtime(xtime(xtime($x))))); +} + +#------------------------------------------------------------------------------ +# Mix the columns of the state matrix +sub MixColumns() +{ + my ($i,$t0,$t1,$t2); + for ($i=0; $i<4; ++$i) { + $t0 = $state[0][$i]; + $t2 = $state[0][$i] ^ $state[1][$i] ^ $state[2][$i] ^ $state[3][$i]; + $t1 = $state[0][$i] ^ $state[1][$i] ; $t1 = xtime($t1) & 0xff; $state[0][$i] ^= $t1 ^ $t2 ; + $t1 = $state[1][$i] ^ $state[2][$i] ; $t1 = xtime($t1) & 0xff; $state[1][$i] ^= $t1 ^ $t2 ; + $t1 = $state[2][$i] ^ $state[3][$i] ; $t1 = xtime($t1) & 0xff; $state[2][$i] ^= $t1 ^ $t2 ; + $t1 = $state[3][$i] ^ $t0 ; $t1 = xtime($t1) & 0xff; $state[3][$i] ^= $t1 ^ $t2 ; + } +} + +sub InvMixColumns() +{ + my $i; + for ($i=0; $i<4; ++$i) { + my $a = $state[0][$i]; + my $b = $state[1][$i]; + my $c = $state[2][$i]; + my $d = $state[3][$i]; + $state[0][$i] = (Mult($a,0x0e) ^ Mult($b,0x0b) ^ Mult($c,0x0d) ^ Mult($d,0x09)) & 0xff; + $state[1][$i] = (Mult($a,0x09) ^ Mult($b,0x0e) ^ Mult($c,0x0b) ^ Mult($d,0x0d)) & 0xff; + $state[2][$i] = (Mult($a,0x0d) ^ Mult($b,0x09) ^ Mult($c,0x0e) ^ Mult($d,0x0b)) & 0xff; + $state[3][$i] = (Mult($a,0x0b) ^ Mult($b,0x0d) ^ Mult($c,0x09) ^ Mult($d,0x0e)) & 0xff; + } +} + +#------------------------------------------------------------------------------ +# Encrypt (Cipher) or decrypt (InvCipher) a block of data with CBC +# Inputs: 0) string to cipher (must be 16 bytes long) +# Returns: cipher'd string +sub Cipher($) +{ + my @in = unpack 'C*', $_[0]; # unpack input plaintext + my ($i, $j, $round); + + # copy the input PlainText to state array and apply the CBC + for ($i=0; $i<4; ++$i) { + for ($j=0; $j<4; ++$j) { + my $k = $i*4 + $j; + $state[$j][$i] = $in[$k] ^ $cbc[$k]; + } + } + + # add the First round key to the state before starting the rounds + AddRoundKey(0); + + # there will be $nr rounds; the first $nr-1 rounds are identical + for ($round=1; ; ++$round) { + SubBytes(); + ShiftRows(); + if ($round < $nr) { + MixColumns(); + AddRoundKey($round); + } else { + # MixColumns() is not used in the last round + AddRoundKey($nr); + last; + } + } + + # the encryption process is over + # copy the state array to output array (and save for CBC) + for ($i=0; $i<4; ++$i) { + for ($j=0; $j<4; ++$j) { + $cbc[$i*4+$j] = $state[$j][$i]; + } + } + return pack 'C*', @cbc; # return packed ciphertext +} + +sub InvCipher($) +{ + my @in = unpack 'C*', $_[0]; # unpack input ciphertext + my (@out, $i, $j, $round); + + # copy the input CipherText to state array + for ($i=0; $i<4; ++$i) { + for ($j=0; $j<4; ++$j) { + $state[$j][$i] = $in[$i*4 + $j]; + } + } + + # add the First round key to the state before starting the rounds + AddRoundKey($nr); + + # there will be $nr rounds; the first $nr-1 rounds are identical + for ($round=$nr-1; ; --$round) { + InvShiftRows(); + InvSubBytes(); + AddRoundKey($round); + # InvMixColumns() is not used in the last round + last if $round <= 0; + InvMixColumns(); + } + + # copy the state array to output array and reverse the CBC + for ($i=0; $i<4; ++$i) { + for ($j=0; $j<4; ++$j) { + my $k = $i*4 + $j; + $out[$k] = $state[$j][$i] ^ $cbc[$k]; + } + } + @cbc = @in; # update CBC for next block + return pack 'C*', @out; # return packed plaintext +} + +#------------------------------------------------------------------------------ +# Encrypt/Decrypt using AES-CBC algorithm (with fixed 16-byte blocks) +# Inputs: 0) data reference (with leading 16-byte initialization vector when decrypting) +# 1) encryption key (16, 24 or 32 bytes for AES-128, AES-192 or AES-256) +# 2) encrypt flag (false for decryption, true with length 16 bytes to +# encrypt using this as the CBC IV, or true with other length to +# encrypt with a randomly-generated IV) +# 3) flag to disable padding +# Returns: error string, or undef on success +# Notes: encrypts/decrypts data in place (encrypted data returned with leading IV) +sub Crypt($$;$$) +{ + my ($dataPt, $key, $encrypt, $noPad) = @_; + + # validate key length + my $keyLen = length $key; + unless ($keyLen == 16 or $keyLen == 24 or $keyLen == 32) { + return "Invalid AES key length ($keyLen)"; + } + my $partLen = length($$dataPt) % 16; + my ($pos, $i); + if ($encrypt) { + if (length($encrypt) == 16) { + @cbc = unpack 'C*', $encrypt; + } else { + # generate a random 16-byte CBC initialization vector + unless ($seeded) { + srand(time() & ($$ + ($$<<15))); + $seeded = 1; + } + for ($i=0; $i<16; ++$i) { + $cbc[$i] = int(rand(256)); + } + $encrypt = pack 'C*', @cbc; + } + $$dataPt = $encrypt . $$dataPt; # add IV to the start of the data + # add required padding so we can recover the + # original string length after decryption + # (padding bytes have value set to padding length) + my $padLen = 16 - $partLen; + $$dataPt .= (chr($padLen)) x $padLen unless $padLen == 16 and $noPad; + $pos = 16; # start encrypting at byte 16 (after the IV) + } elsif ($partLen) { + return 'Invalid AES ciphertext length'; + } elsif (length $$dataPt >= 32) { + # take the CBC initialization vector from the start of the data + @cbc = unpack 'C16', $$dataPt; + $$dataPt = substr($$dataPt, 16); + $pos = 0; # start decrypting from byte 0 (now that IV is removed) + } else { + $$dataPt = ''; # empty text + return undef; + } + # the KeyExpansion routine must be called before encryption + KeyExpansion($key); + + # loop through the data and convert in blocks + my $dataLen = length $$dataPt; + my $last = $dataLen - 16; + my $func = $encrypt ? \&Cipher : \&InvCipher; + while ($pos <= $last) { + # cipher this block + substr($$dataPt, $pos, 16) = &$func(substr($$dataPt, $pos, 16)); + $pos += 16; + } + unless ($encrypt or $noPad) { + # remove padding if necessary (padding byte value gives length of padding) + my $padLen = ord(substr($$dataPt, -1, 1)); + return 'AES decryption error (invalid pad byte)' if $padLen > 16; + $$dataPt = substr($$dataPt, 0, $dataLen - $padLen); + } + return undef; +} + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::AES - AES encryption with cipher-block chaining + +=head1 SYNOPSIS + + use Image::ExifTool::AES qw(Crypt); + + $err = Crypt(\$plaintext, $key, 1); # encryption + + $err = Crypt(\$ciphertext, $key); # decryption + +=head1 DESCRIPTION + +This module contains an implementation of the AES encryption/decryption +algorithms with cipher-block chaining (CBC) and RFC 2898 PKCS #5 padding. +This is the AESV2 and AESV3 encryption mode used in PDF documents. + +=head1 EXPORTS + +Exports nothing by default, but L may be exported. + +=head1 METHODS + +=head2 Crypt + +Implement AES encryption/decryption with cipher-block chaining. + +=over 4 + +=item Inputs: + +0) Scalar reference for data to encrypt/decrypt. + +1) Encryption key string (must have length 16, 24 or 32). + +2) [optional] Encrypt flag (false to decrypt). + +3) [optional] Flag to avoid removing padding after decrypting, or to avoid +adding 16 bytes of padding before encrypting when data length is already a +multiple of 16 bytes. + +=item Returns: + +On success, the return value is undefined and the data is encrypted or +decrypted as specified. Otherwise returns an error string and the data is +left in an indeterminate state. + +=item Notes: + +The length of the encryption key dictates the AES mode, with lengths of 16, +24 and 32 bytes resulting in AES-128, AES-192 and AES-256. + +When encrypting, the input data may be any length and will be padded to an +even 16-byte block size using the specified padding technique. If the +encrypt flag has length 16, it is used as the initialization vector for +the cipher-block chaining, otherwise a random IV is generated. Upon +successful return the data will be encrypted, with the first 16 bytes of +the data being the CBC IV. + +When decrypting, the input data begins with the 16-byte CBC initialization +vector. + +=back + +=head1 BUGS + +This code is blindingly slow. But in truth, slowing down processing is the +main purpose of encryption, so this really can't be considered a bug. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 + +=item L + +=item L + +=back + +=head1 SEE ALSO + +L + +=cut diff --git a/ExifTool/lib/Image/ExifTool/AFCP.pm b/ExifTool/lib/Image/ExifTool/AFCP.pm new file mode 100644 index 0000000..05ec45a --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/AFCP.pm @@ -0,0 +1,281 @@ +#------------------------------------------------------------------------------ +# File: AFCP.pm +# +# Description: Read/write AFCP trailer +# +# Revisions: 12/26/2005 - P. Harvey Created +# +# References: 1) http://web.archive.org/web/20080828211305/http://www.tocarte.com/media/axs_afcp_spec.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::AFCP; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.08'; + +sub ProcessAFCP($$); + +%Image::ExifTool::AFCP::Main = ( + PROCESS_PROC => \&ProcessAFCP, + NOTES => q{ +AFCP stands for AXS File Concatenation Protocol, and is a poorly designed +protocol for appending information to the end of files. This can be used as +an auxiliary technique to store IPTC information in images, but is +incompatible with some file formats. + +ExifTool will read and write (but not create) AFCP IPTC information in JPEG +and TIFF images. + +See +L +for the AFCP specification. + }, + IPTC => { SubDirectory => { TagTable => 'Image::ExifTool::IPTC::Main' } }, + TEXT => 'Text', + Nail => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + # (the specification allows for a variable amount of padding before + # the image after a 10-byte header, so look for the JPEG SOI marker, + # otherwise assume a fixed 8 bytes of padding) + RawConv => q{ + pos($val) = 10; + my $start = ($val =~ /\xff\xd8\xff/g) ? pos($val) - 3 : 18; + my $img = substr($val, $start); + return $self->ValidateImage(\$img, $tag); + }, + }, + PrVw => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + RawConv => q{ + pos($val) = 10; + my $start = ($val =~ /\xff\xd8\xff/g) ? pos($val) - 3 : 18; + my $img = substr($val, $start); + return $self->ValidateImage(\$img, $tag); + }, + }, +); + +#------------------------------------------------------------------------------ +# Read/write AFCP information in a file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# (Set 'ScanForAFCP' member in dirInfo to scan from current position for AFCP) +# Returns: 1 on success, 0 if this file didn't contain AFCP information +# -1 on write error or if the offsets were incorrect on reading +# - updates DataPos to point to actual AFCP start if ScanForAFCP is set +# - updates DirLen to trailer length +# - returns Fixup reference in dirInfo hash when writing +sub ProcessAFCP($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $curPos = $raf->Tell(); + my $offset = $$dirInfo{Offset} || 0; # offset from end of file + my $rtnVal = 0; + +NoAFCP: for (;;) { + my ($buff, $fix, $dirBuff, $valBuff, $fixup, $vers); + # look for AXS trailer + last unless $raf->Seek(-12-$offset, 2) and + $raf->Read($buff, 12) == 12 and + $buff =~ /^(AXS(!|\*))/; + my $endPos = $raf->Tell(); + my $hdr = $1; + SetByteOrder($2 eq '!' ? 'MM' : 'II'); + my $startPos = Get32u(\$buff, 4); + if ($raf->Seek($startPos, 0) and $raf->Read($buff, 12) == 12 and $buff =~ /^$hdr/) { + $fix = 0; + } else { + $rtnVal = -1; + # look for start of AXS trailer if 'ScanForAFCP' + last unless $$dirInfo{ScanForAFCP} and $raf->Seek($curPos, 0); + my $actualPos = $curPos; + # first look for header right at current position + for (;;) { + last if $raf->Read($buff, 12) == 12 and $buff =~ /^$hdr/; + last NoAFCP if $actualPos != $curPos; + # scan for AXS header (could be after preview image) + for (;;) { + my $buf2; + $raf->Read($buf2, 65536) or last NoAFCP; + $buff .= $buf2; + if ($buff =~ /$hdr/g) { + $actualPos += pos($buff) - length($hdr); + last; # ok, now go back and re-read header + } + $buf2 = substr($buf2, -3); # only need last 3 bytes for next test + $actualPos += length($buff) - length($buf2); + $buff = $buf2; + } + last unless $raf->Seek($actualPos, 0); # seek to start of AFCP + } + # calculate shift for fixing AFCP offsets + $fix = $actualPos - $startPos; + } + # set variables returned in dirInfo hash + $$dirInfo{DataPos} = $startPos + $fix; # actual start position + $$dirInfo{DirLen} = $endPos - ($startPos + $fix); + + $rtnVal = 1; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my $outfile = $$dirInfo{OutFile}; + if ($outfile) { + # allow all AFCP information to be deleted + if ($$et{DEL_GROUP}{AFCP}) { + $verbose and print $out " Deleting AFCP\n"; + ++$$et{CHANGED}; + last; + } + $dirBuff = $valBuff = ''; + require Image::ExifTool::Fixup; + $fixup = $$dirInfo{Fixup}; + $fixup or $fixup = $$dirInfo{Fixup} = new Image::ExifTool::Fixup; + $vers = substr($buff, 4, 2); # get version number + } else { + $et->DumpTrailer($dirInfo) if $verbose or $$et{HTML_DUMP}; + } + # read AFCP directory data + my $numEntries = Get16u(\$buff, 6); + my $dir; + unless ($raf->Read($dir, 12 * $numEntries) == 12 * $numEntries) { + $et->Error('Error reading AFCP directory', 1); + last; + } + if ($verbose > 2 and not $outfile) { + my $dat = $buff . $dir; + print $out " AFCP Directory:\n"; + $et->VerboseDump(\$dat, Addr => $$dirInfo{DataPos}, Width => 12); + } + $fix and $et->Warn("Adjusted AFCP offsets by $fix", 1); +# +# process AFCP directory +# + my $tagTablePtr = GetTagTable('Image::ExifTool::AFCP::Main'); + my ($index, $entry); + for ($index=0; $index<$numEntries; ++$index) { + my $entry = 12 * $index; + my $tag = substr($dir, $entry, 4); + my $size = Get32u(\$dir, $entry + 4); + my $offset = Get32u(\$dir, $entry + 8); + if ($size < 0x80000000 and + $raf->Seek($offset+$fix, 0) and + $raf->Read($buff, $size) == $size) + { + if ($outfile) { + # rewrite this information + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo and $$tagInfo{SubDirectory}) { + my %subdirInfo = ( + DataPt => \$buff, + DirStart => 0, + DirLen => $size, + DataPos => $offset + $fix, + Parent => 'AFCP', + ); + my $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + my $newDir = $et->WriteDirectory(\%subdirInfo, $subTable); + if (defined $newDir) { + $size = length $newDir; + $buff = $newDir; + } + } + $fixup->AddFixup(length($dirBuff) + 8); + $dirBuff .= $tag . Set32u($size) . Set32u(length $valBuff); + $valBuff .= $buff; + } else { + # extract information + $et->HandleTag($tagTablePtr, $tag, $buff, + DataPt => \$buff, + Size => $size, + Index => $index, + DataPos => $offset + $fix, + ); + } + } else { + $et->Warn("Bad AFCP directory"); + $rtnVal = -1 if $outfile; + last; + } + } + if ($outfile and length($dirBuff)) { + my $outPos = Tell($outfile); # get current outfile position + # apply fixup to directory pointers + my $valPos = $outPos + 12; # start of value data + $fixup->{Shift} += $valPos + length($dirBuff); + $fixup->ApplyFixup(\$dirBuff); + # write the AFCP header, directory, value data and EOF record (with zero checksums) + Write($outfile, $hdr, $vers, Set16u(length($dirBuff)/12), Set32u(0), + $dirBuff, $valBuff, $hdr, Set32u($outPos), Set32u(0)) or $rtnVal = -1; + # complete fixup so the calling routine can apply further shifts + $fixup->AddFixup(length($dirBuff) + length($valBuff) + 4); + $fixup->{Start} += $valPos; + $fixup->{Shift} -= $valPos; + } + last; + } + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::AFCP - Read/write AFCP trailer + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract +information from the AFCP trailer. Although the AFCP specification is +compatible with various file formats, ExifTool currently only processes AFCP +in JPEG images. + +=head1 NOTES + +AFCP is a specification which allows meta information (including IPTC) to be +appended to the end of a file. + +It is a poorly designed protocol because (like TIFF) it uses absolute +offsets to specify data locations. This is a huge blunder because it makes +the AFCP information dependent on the file length, so it is easily +invalidated by image editing software which doesn't recognize the AFCP +trailer to fix up these offsets when the file length changes. ExifTool will +attempt to fix these invalid offsets if possible. + +Scanning for AFCP information may be time consuming, especially when reading +from a sequential device, since the information is at the end of the file. +In these instances, the ExifTool FastScan option may be used to disable +scanning for AFCP information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 + +=back + +=head1 SEE ALSO + +L, +L + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/AIFF.pm b/ExifTool/lib/Image/ExifTool/AIFF.pm new file mode 100644 index 0000000..a862a0d --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/AIFF.pm @@ -0,0 +1,312 @@ +#------------------------------------------------------------------------------ +# File: AIFF.pm +# +# Description: Read AIFF meta information +# +# Revisions: 01/06/2006 - P. Harvey Created +# 09/22/2008 - PH Added DjVu support +# +# References: 1) http://developer.apple.com/documentation/QuickTime/INMAC/SOUND/imsoundmgr.30.htm#pgfId=3190 +# 2) http://astronomy.swin.edu.au/~pbourke/dataformats/aiff/ +# 3) http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::AIFF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::ID3; + +$VERSION = '1.12'; + +# information for time/date-based tags (time zero is Jan 1, 1904) +my %timeInfo = ( + Groups => { 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val - ((66 * 365 + 17) * 24 * 3600))', + PrintConv => '$self->ConvertDateTime($val)', +); + +# AIFF info +%Image::ExifTool::AIFF::Main = ( + GROUPS => { 2 => 'Audio' }, + NOTES => q{ + Tags extracted from Audio Interchange File Format (AIFF) files. See + L for + the AIFF specification. + }, +# FORM => 'Format', + FVER => { + Name => 'FormatVersion', + SubDirectory => { TagTable => 'Image::ExifTool::AIFF::FormatVers' }, + }, + COMM => { + Name => 'Common', + SubDirectory => { TagTable => 'Image::ExifTool::AIFF::Common' }, + }, + COMT => { + Name => 'Comment', + SubDirectory => { TagTable => 'Image::ExifTool::AIFF::Comment' }, + }, + NAME => { + Name => 'Name', + ValueConv => '$self->Decode($val, "MacRoman")', + }, + AUTH => { + Name => 'Author', + Groups => { 2 => 'Author' }, + ValueConv => '$self->Decode($val, "MacRoman")', + }, + '(c) ' => { + Name => 'Copyright', + Groups => { 2 => 'Author' }, + ValueConv => '$self->Decode($val, "MacRoman")', + }, + ANNO => { + Name => 'Annotation', + ValueConv => '$self->Decode($val, "MacRoman")', + }, + 'ID3 ' => { + Name => 'ID3', + SubDirectory => { + TagTable => 'Image::ExifTool::ID3::Main', + ProcessProc => \&Image::ExifTool::ID3::ProcessID3, + }, + }, + APPL => 'ApplicationData', # (first 4 bytes are the application signature) +# SSND => 'SoundData', +# MARK => 'Marker', +# INST => 'Instrument', +# MIDI => 'MidiData', +# AESD => 'AudioRecording', +); + +%Image::ExifTool::AIFF::Common = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + FORMAT => 'int16u', + 0 => 'NumChannels', + 1 => { Name => 'NumSampleFrames', Format => 'int32u' }, + 3 => 'SampleSize', + 4 => { Name => 'SampleRate', Format => 'extended' }, #3 + 9 => { + Name => 'CompressionType', + Format => 'string[4]', + PrintConv => { + NONE => 'None', + ACE2 => 'ACE 2-to-1', + ACE8 => 'ACE 8-to-3', + MAC3 => 'MAC 3-to-1', + MAC6 => 'MAC 6-to-1', + sowt => 'Little-endian, no compression', + alaw => 'a-law', + ALAW => 'A-law', + ulaw => 'mu-law', + ULAW => 'Mu-law', + 'GSM '=> 'GSM', + G722 => 'G722', + G726 => 'G726', + G728 => 'G728', + }, + }, + 11 => { #PH + Name => 'CompressorName', + Format => 'pstring', + ValueConv => '$self->Decode($val, "MacRoman")', + }, +); + +%Image::ExifTool::AIFF::FormatVers = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int32u', + 0 => { Name => 'FormatVersionTime', %timeInfo }, +); + +%Image::ExifTool::AIFF::Comment = ( + PROCESS_PROC => \&Image::ExifTool::AIFF::ProcessComment, + GROUPS => { 2 => 'Audio' }, + 0 => { Name => 'CommentTime', %timeInfo }, + 1 => 'MarkerID', + 2 => { + Name => 'Comment', + ValueConv => '$self->Decode($val, "MacRoman")', + }, +); + +%Image::ExifTool::AIFF::Composite = ( + Duration => { + Require => { + 0 => 'AIFF:SampleRate', + 1 => 'AIFF:NumSampleFrames', + }, + RawConv => '($val[0] and $val[1]) ? $val[1] / $val[0] : undef', + PrintConv => 'ConvertDuration($val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::AIFF'); + + +#------------------------------------------------------------------------------ +# Process AIFF Comment chunk +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref +# Returns: 1 on success +sub ProcessComment($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + my $verbose = $et->Options('Verbose'); + return 0 unless $dirLen > 2; + my $numComments = unpack('n',$$dataPt); + my $pos = 2; + my $i; + $verbose and $et->VerboseDir('Comment', $numComments); + for ($i=0; $i<$numComments; ++$i) { + last if $pos + 8 > $dirLen; + my ($time, $markerID, $size) = unpack("x${pos}Nnn", $$dataPt); + $et->HandleTag($tagTablePtr, 0, $time); + $et->HandleTag($tagTablePtr, 1, $markerID) if $markerID; + $pos += 8; + last if $pos + $size > $dirLen; + my $val = substr($$dataPt, $pos, $size); + $et->HandleTag($tagTablePtr, 2, $val); + ++$size if $size & 0x01; # account for padding byte if necessary + $pos += $size; + } +} + +#------------------------------------------------------------------------------ +# Extract information from a AIFF file +# Inputs: 0) ExifTool object reference, 1) DirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid AIFF file +sub ProcessAIFF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $err, $tagTablePtr, $page, $type, $n); + + # verify this is a valid AIFF file + return 0 unless $raf->Read($buff, 12) == 12; + my $fast3 = $$et{OPTIONS}{FastScan} && $$et{OPTIONS}{FastScan} == 3; + my $pos = 12; + # check for DjVu image + if ($buff =~ /^AT&TFORM/) { + # http://www.djvu.org/ + # http://djvu.sourceforge.net/specs/djvu3changes.txt + my $buf2; + return 0 unless $raf->Read($buf2, 4) == 4 and $buf2 =~ /^(DJVU|DJVM)/; + $pos += 4; + $buff = substr($buff, 4) . $buf2; + $et->SetFileType('DJVU'); + return 1 if $fast3; + $tagTablePtr = GetTagTable('Image::ExifTool::DjVu::Main'); + # modify FileType to indicate a multi-page document + $$et{VALUE}{FileType} .= " (multi-page)" if $buf2 eq 'DJVM' and $$et{VALUE}{FileType}; + $type = 'DjVu'; + } else { + return 0 unless $buff =~ /^FORM....(AIF(F|C))/s; + $et->SetFileType($1); + return 1 if $fast3; + $tagTablePtr = GetTagTable('Image::ExifTool::AIFF::Main'); + $type = 'AIFF'; + } + SetByteOrder('MM'); + my $verbose = $et->Options('Verbose'); +# +# Read through the IFF chunks +# + for ($n=0;;++$n) { + $raf->Read($buff, 8) == 8 or last; + $pos += 8; + my ($tag, $len) = unpack('a4N', $buff); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + $et->VPrint(0, "AIFF '${tag}' chunk ($len bytes of data): ", $raf->Tell(),"\n"); + # AIFF chunks are padded to an even number of bytes + my $len2 = $len + ($len & 0x01); + if ($len2 > 100000000) { + if ($len2 >= 0x80000000 and not $et->Options('LargeFileSupport')) { + $et->Warn('End of processing at large chunk (LargeFileSupport not enabled)'); + last; + } + if ($tagInfo) { + $et->Warn("Skipping large $$tagInfo{Name} chunk (> 100 MB)"); + undef $tagInfo; + } + } + if ($tagInfo) { + if ($$tagInfo{TypeOnly}) { + $len = $len2 = 4; + $page = ($page || 0) + 1; + $et->VPrint(0, $$et{INDENT} . "Page $page:\n"); + } + $raf->Read($buff, $len2) >= $len or $err=1, last; + unless ($$tagInfo{SubDirectory} or $$tagInfo{Binary}) { + $buff =~ s/\0+$//; # remove trailing nulls + } + $et->HandleTag($tagTablePtr, $tag, $buff, + DataPt => \$buff, + DataPos => $pos, + Start => 0, + Size => $len, + ); + } elsif (not $len) { + next if ++$n < 100; + $et->Warn('Aborting scan. Too many empty chunks'); + last; + } elsif ($verbose > 2 and $len2 < 1024000) { + $raf->Read($buff, $len2) == $len2 or $err = 1, last; + $et->VerboseDump(\$buff); + } else { + $raf->Seek($len2, 1) or $err=1, last; + } + $pos += $len2; + $n = 0; + } + $err and $et->Warn("Error reading $type file (corrupted?)"); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::AIFF - Read AIFF meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +information from AIFF (Audio Interchange File Format) audio files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 + +=item L + +=item L + +=back + +=head1 SEE ALSO + +L, +L + +=cut diff --git a/ExifTool/lib/Image/ExifTool/APE.pm b/ExifTool/lib/Image/ExifTool/APE.pm new file mode 100644 index 0000000..8d62dfe --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/APE.pm @@ -0,0 +1,287 @@ +#------------------------------------------------------------------------------ +# File: APE.pm +# +# Description: Read Monkey's Audio meta information +# +# Revisions: 11/13/2006 - P. Harvey Created +# +# References: 1) http://www.monkeysaudio.com/ +# 2) http://www.personal.uni-jena.de/~pfk/mpp/sv8/apetag.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::APE; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.07'; + +# APE metadata blocks +%Image::ExifTool::APE::Main = ( + GROUPS => { 2 => 'Audio' }, + NOTES => q{ + Tags found in Monkey's Audio (APE) information. Only a few common tags are + listed below, but ExifTool will extract any tag found. ExifTool supports + APEv1 and APEv2 tags, as well as ID3 information in APE files, and will also + read APE metadata from MP3 and MPC files. + }, + Album => { }, + Artist => { }, + Genre => { }, + Title => { }, + Track => { }, + Year => { }, + DURATION => { + Name => 'Duration', + ValueConv => '$val += 4294967296 if $val < 0 and $val >= -2147483648; $val * 1e-7', + PrintConv => 'ConvertDuration($val)', + }, + 'Tool Version' => { Name => 'ToolVersion' }, + 'Tool Name' => { Name => 'ToolName' }, +); + +# APE MAC header version 3.97 or earlier +%Image::ExifTool::APE::OldHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 1 => 'MAC', 2 => 'Audio' }, + FORMAT => 'int16u', + NOTES => 'APE MAC audio header for version 3.97 or earlier.', + 0 => { + Name => 'APEVersion', + ValueConv => '$val / 1000', + }, + 1 => 'CompressionLevel', + # 2 => 'FormatFlags', + 3 => 'Channels', + 4 => { Name => 'SampleRate', Format => 'int32u' }, + # 6 => { Name => 'HeaderBytes', Format => 'int32u' }, # WAV header bytes + # 8 => { Name => 'TerminatingBytes', Format => 'int32u' }, + 10 => { Name => 'TotalFrames', Format => 'int32u' }, + 12 => { Name => 'FinalFrameBlocks', Format => 'int32u' }, +); + +# APE MAC header version 3.98 or later +%Image::ExifTool::APE::NewHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 1 => 'MAC', 2 => 'Audio' }, + FORMAT => 'int16u', + NOTES => 'APE MAC audio header for version 3.98 or later.', + 0 => 'CompressionLevel', + # 1 => 'FormatFlags', + 2 => { Name => 'BlocksPerFrame', Format => 'int32u' }, + 4 => { Name => 'FinalFrameBlocks', Format => 'int32u' }, + 6 => { Name => 'TotalFrames', Format => 'int32u' }, + 8 => 'BitsPerSample', + 9 => 'Channels', + 10 => { Name => 'SampleRate', Format => 'int32u' }, +); + +# APE Composite tags +%Image::ExifTool::APE::Composite = ( + GROUPS => { 2 => 'Audio' }, + Duration => { + Require => { + 0 => 'APE:SampleRate', + 1 => 'APE:TotalFrames', + 2 => 'APE:BlocksPerFrame', + 3 => 'APE:FinalFrameBlocks', + }, + RawConv => '($val[0] && $val[1]) ? (($val[1] - 1) * $val[2] + $val[3]) / $val[0]: undef', + PrintConv => 'ConvertDuration($val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::APE'); + +#------------------------------------------------------------------------------ +# Make tag info hash for specified tag +# Inputs: 0) tag name, 1) tag table ref +# - must only call if tag doesn't exist +sub MakeTag($$) +{ + my ($tag, $tagTablePtr) = @_; + my $name = ucfirst(lc($tag)); + # remove invalid characters in tag name and capitalize following letters + $name =~ s/[^\w-]+(.?)/\U$1/sg; + $name =~ s/([a-z0-9])_([a-z])/$1\U$2/g; + my %tagInfo = ( Name => $name ); + $tagInfo{Groups} = { 2 => 'Preview' } if $tag =~ /^Cover Art/ and $tag !~ /Desc$/; + AddTagToTable($tagTablePtr, $tag, \%tagInfo); +} + +#------------------------------------------------------------------------------ +# Extract information from an APE file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# - Just looks for APE trailer if FileType is already set +# Returns: 1 on success, 0 if this wasn't a valid APE file +sub ProcessAPE($$) +{ + my ($et, $dirInfo) = @_; + + # must first check for leading/trailing ID3 information + unless ($$et{DoneID3}) { + require Image::ExifTool::ID3; + Image::ExifTool::ID3::ProcessID3($et, $dirInfo) and return 1; + } + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my ($buff, $i, $header, $tagTablePtr, $dataPos, $oldIndent); + + $$et{DoneAPE} = 1; + + # check APE signature and process audio information + # unless this is some other type of file + unless ($$et{FileType}) { + $raf->Read($buff, 32) == 32 or return 0; + $buff =~ /^(MAC |APETAGEX)/ or return 0; + $et->SetFileType(); + SetByteOrder('II'); + + if ($buff =~ /^APETAGEX/) { + # we already read the APE header + $header = 1; + } else { + # process the MAC header + my $vers = Get16u(\$buff, 4); + my $table; + if ($vers <= 3970) { + $buff = substr($buff, 4); + $table = GetTagTable('Image::ExifTool::APE::OldHeader'); + } else { + my $dlen = Get32u(\$buff, 8); + my $hlen = Get32u(\$buff, 12); + unless ($dlen & 0x80000000 or $hlen & 0x80000000) { + if ($raf->Seek($dlen, 0) and $raf->Read($buff, $hlen) == $hlen) { + $table = GetTagTable('Image::ExifTool::APE::NewHeader'); + } + } + } + $et->ProcessDirectory( { DataPt => \$buff }, $table) if $table; + } + } + # look for APE trailer unless we already found an APE header + unless ($header) { + # look for the APE trailer footer... + my $footPos = -32; + # (...but before the ID3v1 trailer if it exists) + $footPos -= $$et{DoneID3} if $$et{DoneID3} > 1; + $raf->Seek($footPos, 2) or return 1; + $raf->Read($buff, 32) == 32 or return 1; + $buff =~ /^APETAGEX/ or return 1; + SetByteOrder('II'); + } +# +# Read the APE data (we have just read the APE header or footer into $buff) +# + my ($version, $size, $count, $flags) = unpack('x8V4', $buff); + $version /= 1000; + $size -= 32; # get size of data only + if (($size & 0x80000000) == 0 and + ($header or $raf->Seek(-$size-32, 1)) and + $raf->Read($buff, $size) == $size) + { + if ($verbose) { + $oldIndent = $$et{INDENT}; + $$et{INDENT} .= '| '; + $et->VerboseDir("APEv$version", $count, $size); + $et->VerboseDump(\$buff, DataPos => $raf->Tell() - $size); + } + $tagTablePtr = GetTagTable('Image::ExifTool::APE::Main'); + $dataPos = $raf->Tell() - $size; + } else { + $count = -1; + } +# +# Process the APE tags +# + my $pos = 0; + for ($i=0; $i<$count; ++$i) { + # read next APE tag + last if $pos + 8 > $size; + my $len = Get32u(\$buff, $pos); + my $flags = Get32u(\$buff, $pos + 4); + pos($buff) = $pos + 8; + last unless $buff =~ /\G(.*?)\0/sg; + my $tag = $1; + # avoid conflicts with our special table entries + $tag .= '.' if $Image::ExifTool::specialTags{$tag}; + $pos = pos($buff); + last if $pos + $len > $size; + my $val = substr($buff, $pos, $len); + MakeTag($tag, $tagTablePtr) unless $$tagTablePtr{$tag}; + # handle binary-value tags + if (($flags & 0x06) == 0x02) { + my $buf2 = $val; + $val = \$buf2; + # extract cover art description separately (hackitty hack) + if ($tag =~ /^Cover Art/) { + $buf2 =~ s/^([\x20-\x7f]*)\0//; + if ($1) { + my $t = "$tag Desc"; + my $v = $1; + MakeTag($t, $tagTablePtr) unless $$tagTablePtr{$t}; + $et->HandleTag($tagTablePtr, $t, $v); + } + } + } + $et->HandleTag($tagTablePtr, $tag, $val, + Index => $i, + DataPt => \$buff, + DataPos => $dataPos, + Start => $pos, + Size => $len, + ); + $pos += $len; + } + $i == $count or $et->Warn('Bad APE trailer'); + $$et{INDENT} = $oldIndent if defined $oldIndent; + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::APE - Read Monkey's Audio meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Monkey's Audio (APE) audio files. + +=head1 BUGS + +Currently doesn't parse MAC header unless it is at the start of the file. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 + +=item L + +=back + +=head1 SEE ALSO + +L, +L + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/APP12.pm b/ExifTool/lib/Image/ExifTool/APP12.pm new file mode 100644 index 0000000..7c759bf --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/APP12.pm @@ -0,0 +1,322 @@ +#------------------------------------------------------------------------------ +# File: APP12.pm +# +# Description: Read APP12 meta information +# +# Revisions: 10/18/2005 - P. Harvey Created +# +# References: 1) Heinrich Giesen private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::APP12; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.13'; + +sub ProcessAPP12($$$); +sub ProcessDucky($$$); +sub WriteDucky($$$); + +# APP12 tags (ref PH) +%Image::ExifTool::APP12::PictureInfo = ( + PROCESS_PROC => \&ProcessAPP12, + GROUPS => { 0 => 'APP12', 1 => 'PictureInfo', 2 => 'Image' }, + PRIORITY => 0, + NOTES => q{ + The JPEG APP12 "Picture Info" segment was used by some older cameras, and + contains ASCII-based meta information. Below are some tags which have been + observed Agfa and Polaroid images, however ExifTool will extract information + from any tags found in this segment. + }, + FNumber => { + ValueConv => '$val=~s/^[A-Za-z ]*//;$val', # Agfa leads with an 'F' + PrintConv => 'sprintf("%.1f",$val)', + }, + Aperture => { + PrintConv => 'sprintf("%.1f",$val)', + }, + TimeDate => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + ValueConv => '$val=~/^\d+$/ ? ConvertUnixTime($val) : $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + Shutter => { + Name => 'ExposureTime', + ValueConv => '$val * 1e-6', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + shtr => { + Name => 'ExposureTime', + ValueConv => '$val * 1e-6', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 'Serial#' => { + Name => 'SerialNumber', + Groups => { 2 => 'Camera' }, + }, + Flash => { PrintConv => { 0 => 'Off', 1 => 'On' } }, + Macro => { PrintConv => { 0 => 'Off', 1 => 'On' } }, + StrobeTime => { }, + Ytarget => { Name => 'YTarget' }, + ylevel => { Name => 'YLevel' }, + FocusPos => { }, + FocusMode => { }, + Quality => { }, + ExpBias => 'ExposureCompensation', + FWare => 'FirmwareVersion', + StrobeTime => { }, + Resolution => { }, + Protect => { }, + ConTake => { }, + ImageSize => { PrintConv => '$val=~tr/-/x/;$val' }, + ColorMode => { }, + Zoom => { }, + ZoomPos => { }, + LightS => { }, + Type => { + Name => 'CameraType', + Groups => { 2 => 'Camera' }, + DataMember => 'CameraType', + RawConv => '$self->{CameraType} = $val', + }, + Version => { Groups => { 2 => 'Camera' } }, + ID => { Groups => { 2 => 'Camera' } }, +); + +# APP12 segment written in Photoshop "Save For Web" images +# (from tests with Photoshop 7 files - PH/1) +%Image::ExifTool::APP12::Ducky = ( + PROCESS_PROC => \&ProcessDucky, + WRITE_PROC => \&WriteDucky, + GROUPS => { 0 => 'Ducky', 1 => 'Ducky', 2 => 'Image' }, + WRITABLE => 'string', + NOTES => q{ + Photoshop uses the JPEG APP12 "Ducky" segment to store some information in + "Save for Web" images. + }, + 1 => { #PH + Name => 'Quality', + Priority => 0, + Avoid => 1, + Writable => 'int32u', + ValueConv => 'unpack("N",$val)', # 4-byte integer + ValueConvInv => 'pack("N",$val)', + PrintConv => '"$val%"', + PrintConvInv => '$val=~/(\d+)/ ? $1 : undef', + }, + 2 => { #1 + Name => 'Comment', + Priority => 0, + Avoid => 1, + # (ignore 4-byte character count at start of value) + ValueConv => '$self->Decode(substr($val,4),"UCS2","MM")', + ValueConvInv => 'pack("N",length $val) . $self->Encode($val,"UCS2","MM")', + }, + 3 => { #PH + Name => 'Copyright', + Priority => 0, + Avoid => 1, + Groups => { 2 => 'Author' }, + # (ignore 4-byte character count at start of value) + ValueConv => '$self->Decode(substr($val,4),"UCS2","MM")', + ValueConvInv => 'pack("N",length $val) . $self->Encode($val,"UCS2","MM")', + }, +); + +#------------------------------------------------------------------------------ +# Write APP12 Ducky segment +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: New directory data or undefined on error +sub WriteDucky($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $newTags = $et->GetNewTagInfoHash($tagTablePtr); + my @addTags = sort { $a <=> $b } keys(%$newTags); + my ($dirEnd, %doneTags); + if ($dataPt) { + $dirEnd = $pos + $$dirInfo{DirLen}; + } else { + my $tmp = ''; + $dataPt = \$tmp; + $pos = $dirEnd = 0; + } + my $newData = ''; + SetByteOrder('MM'); + # process all data blocks in Ducky segment + for (;;) { + my ($tag, $len, $val); + if ($pos + 4 <= $dirEnd) { + $tag = Get16u($dataPt, $pos); + $len = Get16u($dataPt, $pos + 2); + $pos += 4; + if ($pos + $len > $dirEnd) { + $et->Warn('Invalid Ducky block length'); + return undef; + } + $val = substr($$dataPt, $pos, $len); + $pos += $len; + } else { + last unless @addTags; + $tag = pop @addTags; + next if $doneTags{$tag}; + } + $doneTags{$tag} = 1; + my $tagInfo = $$newTags{$tag}; + if ($tagInfo) { + my $nvHash = $et->GetNewValueHash($tagInfo); + my $isNew; + if (defined $val) { + if ($et->IsOverwriting($nvHash, $val)) { + $et->VerboseValue("- Ducky:$$tagInfo{Name}", $val); + $isNew = 1; + } + } else { + next unless $$nvHash{IsCreating}; + $isNew = 1; + } + if ($isNew) { + $val = $et->GetNewValue($nvHash); + ++$$et{CHANGED}; + next unless defined $val; # next if tag is being deleted + $et->VerboseValue("+ Ducky:$$tagInfo{Name}", $val); + } + } + $newData .= pack('nn', $tag, length $val) . $val; + } + $newData .= "\0\0" if length $newData; + return $newData; +} + +#------------------------------------------------------------------------------ +# Process APP12 Ducky segment (ref PH) +# Inputs: 0) ExifTool object reference, 1) Directory information ref, 2) tag table ref +# Returns: 1 on success, 0 if this wasn't a recognized Ducky segment +# Notes: This segment has the following format: +# 1) 5 bytes: "Ducky" +# 2) multiple data blocks (all integers are big endian): +# a) 2 bytes: block type (0=end, 1=Quality, 2=Comment, 3=Copyright) +# b) 2 bytes: block length (N) +# c) N bytes: block data +sub ProcessDucky($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $dirEnd = $pos + $$dirInfo{DirLen}; + SetByteOrder('MM'); + # process all data blocks in Ducky segment + for (;;) { + last if $pos + 4 > $dirEnd; + my $tag = Get16u($dataPt, $pos); + my $len = Get16u($dataPt, $pos + 2); + $pos += 4; + if ($pos + $len > $dirEnd) { + $et->Warn('Invalid Ducky block length'); + last; + } + my $val = substr($$dataPt, $pos, $len); + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Start => $pos, + Size => $len, + ); + $pos += $len; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process APP12 Picture Info segment (ref PH) +# Inputs: 0) ExifTool object reference, 1) Directory information ref, 2) tag table ref +# Returns: 1 on success, 0 if this wasn't a recognized APP12 +sub ProcessAPP12($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart); + if ($dirLen != $dirStart + length($$dataPt)) { + my $buff = substr($$dataPt, $dirStart, $dirLen); + $dataPt = \$buff; + } else { + pos($$dataPt) = $$dirInfo{DirStart}; + } + my $verbose = $et->Options('Verbose'); + my $success = 0; + my $section = ''; + pos($$dataPt) = 0; + + # this regular expression is a bit complex, but basically we are looking for + # section headers (eg. "[Camera Info]") and tag/value pairs (eg. "tag=value", + # where "value" may contain white space), separated by spaces or CR/LF. + # (APP12 uses CR/LF, but Olympus TextualInfo is similar and uses spaces) + while ($$dataPt =~ /(\[.*?\]|[\w#-]+=[\x20-\x7e]+?(?=\s*([\n\r\0]|[\w#-]+=|\[|$)))/g) { + my $token = $1; + # was this a section name? + if ($token =~ /^\[(.*)\]/) { + $et->VerboseDir($1) if $verbose; + $section = ($token =~ /\[(\S+) ?Info\]/i) ? $1 : ''; + $success = 1; + next; + } + $et->VerboseDir($$dirInfo{DirName}) if $verbose and not $success; + $success = 1; + my ($tag, $val) = ($token =~ /(\S+)=(.+)/); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + $verbose and $et->VerboseInfo($tag, $tagInfo, Value => $val); + unless ($tagInfo) { + # add new tag to table + $tagInfo = { Name => ucfirst $tag }; + # put in Camera group if information in "Camera" section + $$tagInfo{Groups} = { 2 => 'Camera' } if $section =~ /camera/i; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + $et->FoundTag($tagInfo, $val); + } + return $success; +} + + +1; #end + +__END__ + +=head1 NAME + +Image::ExifTool::APP12 - Read APP12 meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +APP12 meta information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Heinrich Giesen for his help decoding APP12 "Ducky" information. + +=head1 SEE ALSO + +L, +L + +=cut diff --git a/ExifTool/lib/Image/ExifTool/ASF.pm b/ExifTool/lib/Image/ExifTool/ASF.pm new file mode 100644 index 0000000..f6964f1 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/ASF.pm @@ -0,0 +1,898 @@ +#------------------------------------------------------------------------------ +# File: ASF.pm +# +# Description: Read ASF/WMA/WMV meta information +# +# Revisions: 12/23/2005 - P. Harvey Created +# +# References: 1) http://www.microsoft.com/windows/windowsmedia/format/asfspec.aspx +# 2) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf (Oct 2008) +#------------------------------------------------------------------------------ + +package Image::ExifTool::ASF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::RIFF; + +$VERSION = '1.25'; + +sub ProcessASF($$;$); +sub ProcessContentDescription($$$); +sub ProcessExtendedContentDescription($$$); +sub ProcessMetadata($$$); +sub ProcessPicture($$$); +sub ProcessCodecList($$$); + +# GUID definitions +my %errorCorrection = ( + '20FB5700-5B55-11CF-A8FD-00805F5C442B' => 'No Error Correction', + 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220' => 'Audio Spread', +); + +my %streamType = ( + 'F8699E40-5B4D-11CF-A8FD-00805F5C442B' => 'Audio', + 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B' => 'Video', + '59DACFC0-59E6-11D0-A3AC-00A0C90348F6' => 'Command', + 'B61BE100-5B4E-11CF-A8FD-00805F5C442B' => 'JFIF', + '35907DE0-E415-11CF-A917-00805F5C442B' => 'Degradable JPEG', + '91BD222C-F21C-497A-8B6D-5AA86BFC0185' => 'File Transfer', + '3AFB65E2-47EF-40F2-AC2C-70A90D71D343' => 'Binary', +); + +my %mutex = ( + 'D6E22A00-35DA-11D1-9034-00A0C90349BE' => 'MutexLanguage', + 'D6E22A01-35DA-11D1-9034-00A0C90349BE' => 'MutexBitrate', + 'D6E22A02-35DA-11D1-9034-00A0C90349BE' => 'MutexUnknown', +); + +my %bandwidthSharing = ( + 'AF6060AA-5197-11D2-B6AF-00C04FD908E9' => 'SharingExclusive', + 'AF6060AB-5197-11D2-B6AF-00C04FD908E9' => 'SharingPartial', +); + +my %typeSpecific = ( + '776257D4-C627-41CB-8F81-7AC7FF1C40CC' => 'WebStreamMediaSubtype', + 'DA1E6B13-8359-4050-B398-388E965BF00C' => 'WebStreamFormat', +); + +my %advancedContentEncryption = ( + '7A079BB6-DAA4-4e12-A5CA-91D38DC11A8D' => 'DRMNetworkDevices', +); + +# ASF top level objects +%Image::ExifTool::ASF::Main = ( + PROCESS_PROC => \&ProcessASF, + NOTES => q{ + The ASF format is used by Windows WMA and WMV files, and DIVX videos. Tag + ID's aren't listed because they are huge 128-bit GUID's that would ruin the + formatting of this table. + }, + '75B22630-668E-11CF-A6D9-00AA0062CE6C' => { + Name => 'Header', + SubDirectory => { TagTable => 'Image::ExifTool::ASF::Header', Size => 6 }, + }, + '75B22636-668E-11CF-A6D9-00AA0062CE6C' => 'Data', + '33000890-E5B1-11CF-89F4-00A0C90349CB' => 'SimpleIndex', + 'D6E229D3-35DA-11D1-9034-00A0C90349BE' => 'Index', + 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C' => 'MediaIndex', + '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C' => 'TimecodeIndex', + 'BE7ACFCB-97A9-42E8-9C71-999491E3AFAC' => { #2 + Name => 'XMP', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, +); + +# ASF header objects +%Image::ExifTool::ASF::Header = ( + PROCESS_PROC => \&ProcessASF, + '8CABDCA1-A947-11CF-8EE4-00C00C205365' => { + Name => 'FileProperties', + SubDirectory => { TagTable => 'Image::ExifTool::ASF::FileProperties' }, + }, + 'B7DC0791-A9B7-11CF-8EE6-00C00C205365' => { + Name => 'StreamProperties', + SubDirectory => { TagTable => 'Image::ExifTool::ASF::StreamProperties' }, + }, + '5FBF03B5-A92E-11CF-8EE3-00C00C205365' => { + Name => 'HeaderExtension', + SubDirectory => { TagTable => 'Image::ExifTool::ASF::HeaderExtension', Size => 22 }, + }, + '86D15240-311D-11D0-A3A4-00A0C90348F6' => { + Name => 'CodecList', + SubDirectory => { TagTable => 'Image::ExifTool::ASF::CodecList' }, + }, + '1EFB1A30-0B62-11D0-A39B-00A0C90348F6' => 'ScriptCommand', + 'F487CD01-A951-11CF-8EE6-00C00C205365' => 'Marker', + 'D6E229DC-35DA-11D1-9034-00A0C90349BE' => 'BitrateMutualExclusion', + '75B22635-668E-11CF-A6D9-00AA0062CE6C' => 'ErrorCorrection', + '75B22633-668E-11CF-A6D9-00AA0062CE6C' => { + Name => 'ContentDescription', + SubDirectory => { TagTable => 'Image::ExifTool::ASF::ContentDescr' }, + }, + '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E' => { + Name => 'ContentBranding', + SubDirectory => { TagTable => 'Image::ExifTool::ASF::ContentBranding' }, + }, + 'D2D0A440-E307-11D2-97F0-00A0C95EA850' => { + Name => 'ExtendedContentDescr', + SubDirectory => { TagTable => 'Image::ExifTool::ASF::ExtendedDescr' }, + }, + '7BF875CE-468D-11D1-8D82-006097C9A2B2' => 'StreamBitrateProps', + '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E' => 'ContentEncryption', + '298AE614-2622-4C17-B935-DAE07EE9289C' => 'ExtendedContentEncryption', + '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E' => 'DigitalSignature', + '1806D474-CADF-4509-A4BA-9AABCB96AAE8' => 'Padding', +); + +%Image::ExifTool::ASF::ContentDescr = ( + PROCESS_PROC => \&ProcessContentDescription, + GROUPS => { 2 => 'Video' }, + 0 => 'Title', + 1 => { Name => 'Author', Groups => { 2 => 'Author' } }, + 2 => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + 3 => 'Description', + 4 => 'Rating', +); + +%Image::ExifTool::ASF::ContentBranding = ( + PROCESS_PROC => \&ProcessContentBranding, + GROUPS => { 2 => 'Author' }, + 0 => { + Name => 'BannerImageType', + PrintConv => { + 0 => 'None', + 1 => 'Bitmap', + 2 => 'JPEG', + 3 => 'GIF', + }, + }, + 1 => { Name => 'BannerImage', Groups => { 2 => 'Preview' }, Binary => 1 }, + 2 => 'BannerImageURL', + 3 => 'CopyrightURL', +); + +# Note: Many of these tags are similar to those in Image::ExifTool::Microsoft::Xtra +# and Image::ExifTool::WTV::Metadata +# (tags in this table may have a leading "WM/" removed) +%Image::ExifTool::ASF::ExtendedDescr = ( + PROCESS_PROC => \&ProcessExtendedContentDescription, + GROUPS => { 2 => 'Video' }, + ASFLeakyBucketPairs => { Binary => 1 }, + AspectRatioX => {}, + AspectRatioY => {}, + Author => { Groups => { 2 => 'Author' } }, + AverageLevel => {}, + BannerImageData => {}, + BannerImageType => {}, + BannerImageURL => {}, + Bitrate => { PrintConv => 'ConvertBitrate($val)' }, + Broadcast => {}, + BufferAverage => {}, + Can_Skip_Backward => {}, + Can_Skip_Forward => {}, + Copyright => { Groups => { 2 => 'Author' } }, + CopyrightURL => { Groups => { 2 => 'Author' } }, + CurrentBitrate => { PrintConv => 'ConvertBitrate($val)' }, + Description => {}, + DRM_ContentID => {}, + DRM_DRMHeader_ContentDistributor => {}, + DRM_DRMHeader_ContentID => {}, + DRM_DRMHeader_IndividualizedVersion => {}, + DRM_DRMHeader_KeyID => {}, + DRM_DRMHeader_LicenseAcqURL => {}, + DRM_DRMHeader_SubscriptionContentID => {}, + DRM_DRMHeader => {}, + DRM_IndividualizedVersion => {}, + DRM_KeyID => {}, + DRM_LASignatureCert => {}, + DRM_LASignatureLicSrvCert => {}, + DRM_LASignaturePrivKey => {}, + DRM_LASignatureRootCert => {}, + DRM_LicenseAcqURL => {}, + DRM_V1LicenseAcqURL => {}, + Duration => { PrintConv => 'ConvertDuration($val)' }, + FileSize => {}, + HasArbitraryDataStream => {}, + HasAttachedImages => {}, + HasAudio => {}, + HasFileTransferStream => {}, + HasImage => {}, + HasScript => {}, + HasVideo => {}, + Is_Protected => {}, + Is_Trusted => {}, + IsVBR => {}, + NSC_Address => {}, + NSC_Description => {}, + NSC_Email => {}, + NSC_Name => {}, + NSC_Phone => {}, + NumberOfFrames => {}, + OptimalBitrate => { PrintConv => 'ConvertBitrate($val)' }, + PeakValue => {}, + Rating => {}, + Seekable => {}, + Signature_Name => {}, + Stridable => {}, + Title => {}, + VBRPeak => {}, + # "WM/" tags... + AlbumArtist => {}, + AlbumCoverURL => {}, + AlbumTitle => {}, + ASFPacketCount => {}, + ASFSecurityObjectsSize => {}, + AudioFileURL => {}, + AudioSourceURL => {}, + AuthorURL => { Groups => { 2 => 'Author' } }, + BeatsPerMinute => {}, + Category => {}, + Codec => {}, + Composer => {}, + Conductor => {}, + ContainerFormat => {}, + ContentDistributor => {}, + ContentGroupDescription => {}, + Director => {}, + DRM => {}, + DVDID => {}, + EncodedBy => {}, + EncodingSettings => {}, + EncodingTime => { Groups => { 2 => 'Time' }, PrintConv => '$self->ConvertDateTime($val)' }, + Genre => {}, + GenreID => {}, + InitialKey => {}, + ISRC => {}, + Language => {}, + Lyrics => {}, + Lyrics_Synchronised => {}, + MCDI => {}, + MediaClassPrimaryID => { ValueConv => 'Image::ExifTool::ASF::GetGUID($val)' }, + MediaClassSecondaryID => { ValueConv => 'Image::ExifTool::ASF::GetGUID($val)' }, + MediaCredits => {}, + MediaIsDelay => {}, + MediaIsFinale => {}, + MediaIsLive => {}, + MediaIsPremiere => {}, + MediaIsRepeat => {}, + MediaIsSAP => {}, + MediaIsStereo => {}, + MediaIsSubtitled => {}, + MediaIsTape => {}, + MediaNetworkAffiliation => {}, + MediaOriginalBroadcastDateTime => { + Groups => { 2 => 'Time' }, + ValueConv => '$val=~tr/-T/: /; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + MediaOriginalChannel => {}, + MediaStationCallSign => {}, + MediaStationName => {}, + ModifiedBy => {}, + Mood => {}, + OriginalAlbumTitle => {}, + OriginalArtist => {}, + OriginalFilename => 'OriginalFileName', + OriginalLyricist => {}, + OriginalReleaseTime => { + Groups => { 2 => 'Time' }, + ValueConv => '$val=~tr/-T/: /; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + OriginalReleaseYear => { Groups => { 2 => 'Time' } }, + ParentalRating => {}, + ParentalRatingReason => {}, + PartOfSet => {}, + PeakBitrate => { PrintConv => 'ConvertBitrate($val)' }, + Period => {}, + Picture => { + SubDirectory => { + TagTable => 'Image::ExifTool::ASF::Picture', + }, + }, + PlaylistDelay => {}, + Producer => {}, + PromotionURL => {}, + ProtectionType => {}, + Provider => {}, + ProviderCopyright => {}, + ProviderRating => {}, + ProviderStyle => {}, + Publisher => {}, + RadioStationName => {}, + RadioStationOwner => {}, + SharedUserRating => {}, + StreamTypeInfo => {}, + SubscriptionContentID => {}, + SubTitle => 'Subtitle', + SubTitleDescription => 'SubtitleDescription', + Text => {}, + ToolName => {}, + ToolVersion => {}, + Track => {}, + TrackNumber => {}, + UniqueFileIdentifier => {}, + UserWebURL => {}, + VideoClosedCaptioning => {}, + VideoFrameRate => {}, + VideoHeight => {}, + VideoWidth => {}, + WMADRCAverageReference => {}, + WMADRCAverageTarget => {}, + WMADRCPeakReference => {}, + WMADRCPeakTarget => {}, + WMCollectionGroupID => {}, + WMCollectionID => {}, + WMContentID => {}, + Writer => { Groups => { 2 => 'Author' } }, + Year => { Groups => { 2 => 'Time' } }, +); + +%Image::ExifTool::ASF::Picture = ( + PROCESS_PROC => \&ProcessPicture, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'PictureType', + PrintConv => { # (Note: Duplicated in ID3, ASF and FLAC modules!) + 0 => 'Other', + 1 => '32x32 PNG Icon', + 2 => 'Other Icon', + 3 => 'Front Cover', + 4 => 'Back Cover', + 5 => 'Leaflet', + 6 => 'Media', + 7 => 'Lead Artist', + 8 => 'Artist', + 9 => 'Conductor', + 10 => 'Band', + 11 => 'Composer', + 12 => 'Lyricist', + 13 => 'Recording Studio or Location', + 14 => 'Recording Session', + 15 => 'Performance', + 16 => 'Capture from Movie or Video', + 17 => 'Bright(ly) Colored Fish', + 18 => 'Illustration', + 19 => 'Band Logo', + 20 => 'Publisher Logo', + }, + }, + 1 => 'PictureMIMEType', + 2 => 'PictureDescription', + 3 => { + Name => 'Picture', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, +); + +%Image::ExifTool::ASF::FileProperties = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + 0 => { + Name => 'FileID', + Format => 'binary[16]', + ValueConv => 'Image::ExifTool::ASF::GetGUID($val)', + }, + 16 => { Name => 'FileLength', Format => 'int64u' }, + 24 => { + Name => 'CreationDate', + Format => 'int64u', + Groups => { 2 => 'Time' }, + # time is in 100 ns intervals since 0:00 UTC Jan 1, 1601 + ValueConv => q{ # (89 leap years between 1601 and 1970) + my $t = $val / 1e7 - (((1970-1601)*365+89)*24*3600); + return Image::ExifTool::ConvertUnixTime($t) . 'Z'; + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 32 => { Name => 'DataPackets', Format => 'int64u' }, + 40 => { + Name => 'Duration', + Format => 'int64u', + Notes => 'called PlayDuration by the ASF spec', + Priority => 0, + ValueConv => '$val / 1e7', + PrintConv => 'ConvertDuration($val)', + }, + 48 => { + Name => 'SendDuration', + Format => 'int64u', + ValueConv => '$val / 1e7', + PrintConv => 'ConvertDuration($val)', + }, + 56 => { Name => 'Preroll', Format => 'int64u' }, + 64 => { Name => 'Flags', Format => 'int32u' }, + 68 => { Name => 'MinPacketSize',Format => 'int32u' }, + 72 => { Name => 'MaxPacketSize',Format => 'int32u' }, + 76 => { Name => 'MaxBitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)' }, +); + +%Image::ExifTool::ASF::StreamProperties = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + NOTES => 'Tags with index 54 and greater are conditional based on the StreamType.', + 0 => { + Name => 'StreamType', + Format => 'binary[16]', + RawConv => sub { # set ASF_STREAM_TYPE for use in conditional tags + my ($val, $et) = @_; + $$et{ASF_STREAM_TYPE} = $streamType{GetGUID($val)} || ''; + return $val; + }, + ValueConv => 'Image::ExifTool::ASF::GetGUID($val)', + PrintConv => \%streamType, + }, + 16 => { + Name => 'ErrorCorrectionType', + Format => 'binary[16]', + ValueConv => 'Image::ExifTool::ASF::GetGUID($val)', + PrintConv => \%errorCorrection, + }, + 32 => { + Name => 'TimeOffset', + Format => 'int64u', + ValueConv => '$val / 1e7', + PrintConv => '"$val s"', + }, + 48 => { + Name => 'StreamNumber', + Format => 'int16u', + PrintConv => '($val & 0x7f) . ($val & 0x8000 ? " (encrypted)" : "")', + }, + 54 => [ + { + Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"', + Name => 'AudioCodecID', + Format => 'int16u', + PrintHex => 1, + SeparateTable => 'RIFF AudioEncoding', + PrintConv => \%Image::ExifTool::RIFF::audioEncoding, + }, + { + Condition => '$self->{ASF_STREAM_TYPE} =~ /^(Video|JFIF|Degradable JPEG)$/', + Name => 'ImageWidth', + Format => 'int32u', + }, + ], + 56 => { + Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"', + Name => 'AudioChannels', + Format => 'int16u', + }, + 58 => [ + { + Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"', + Name => 'AudioSampleRate', + Format => 'int32u', + }, + { + Condition => '$self->{ASF_STREAM_TYPE} =~ /^(Video|JFIF|Degradable JPEG)$/', + Name => 'ImageHeight', + Format => 'int32u', + }, + ], +); + +%Image::ExifTool::ASF::HeaderExtension = ( + PROCESS_PROC => \&ProcessASF, + '14E6A5CB-C672-4332-8399-A96952065B5A' => 'ExtendedStreamProps', + 'A08649CF-4775-4670-8A16-6E35357566CD' => 'AdvancedMutualExcl', + 'D1465A40-5A79-4338-B71B-E36B8FD6C249' => 'GroupMutualExclusion', + 'D4FED15B-88D3-454F-81F0-ED5C45999E24' => 'StreamPrioritization', + 'A69609E6-517B-11D2-B6AF-00C04FD908E9' => 'BandwidthSharing', + '7C4346A9-EFE0-4BFC-B229-393EDE415C85' => 'LanguageList', + 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA' => { + Name => 'Metadata', + SubDirectory => { + # have seen some tags same as ExtendedDescr, so use this table - PH + TagTable => 'Image::ExifTool::ASF::ExtendedDescr', + ProcessProc => \&ProcessMetadata, + }, + }, + '44231C94-9498-49D1-A141-1D134E457054' => { + Name => 'MetadataLibrary', + SubDirectory => { + # have seen some tags same as ExtendedDescr, so use this table - PH + TagTable => 'Image::ExifTool::ASF::ExtendedDescr', + ProcessProc => \&ProcessMetadata, + }, + }, + 'D6E229DF-35DA-11D1-9034-00A0C90349BE' => 'IndexParameters', + '6B203BAD-3F11-48E4-ACA8-D7613DE2CFA7' => 'TimecodeIndexParms', + '75B22630-668E-11CF-A6D9-00AA0062CE6C' => 'Compatibility', + '43058533-6981-49E6-9B74-AD12CB86D58C' => 'AdvancedContentEncryption', + 'ABD3D211-A9BA-11cf-8EE6-00C00C205365' => 'Reserved1', +); + +%Image::ExifTool::ASF::CodecList = ( + PROCESS_PROC => \&ProcessCodecList, + VideoCodecName => {}, + VideoCodecDescription => {}, + AudioCodecName => {}, + AudioCodecDescription => {}, + OtherCodecName => {}, + OtherCodecDescription => {}, +); + +#------------------------------------------------------------------------------ +# Generate GUID from 16 bytes of binary data +# Inputs: 0) data +# Returns: GUID +sub GetGUID($) +{ + # must do some byte swapping + my $val = shift; + return $val unless length($val) == 16; + my $buff = unpack('H*',pack('NnnNN',unpack('VvvNN',$val))); + $buff =~ s/(.{8})(.{4})(.{4})(.{4})/$1-$2-$3-$4-/; + return uc($buff); +} + +#------------------------------------------------------------------------------ +# Process ASF content description +# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference +# Returns: 1 on success +sub ProcessContentDescription($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + return 0 if $dirLen < 10; + my @len = unpack('v5', $$dataPt); + my $pos = 10; + my $tag; + foreach $tag (0..4) { + my $len = shift @len; + next unless $len; + return 0 if $pos + $len > $dirLen; + my $val = $et->Decode(substr($$dataPt,$pos,$len),'UCS2','II'); + $et->HandleTag($tagTablePtr, $tag, $val); + $pos += $len; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process ASF content branding +# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference +# Returns: 1 on success +sub ProcessContentBranding($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + return 0 if $dirLen < 40; + # decode banner image type + $et->HandleTag($tagTablePtr, 0, unpack('V', $$dataPt)); + # decode banner image, banner URL and copyright URL + my $pos = 4; + my $tag; + foreach $tag (1..3) { + return 0 if $pos + 4 > $dirLen; + my $size = unpack("x${pos}V", $$dataPt); + $pos += 4; + next unless $size; + return 0 if $pos + $size > $dirLen; + my $val = substr($$dataPt, $pos, $size); + $et->HandleTag($tagTablePtr, $tag, $val); + $pos += $size; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Read ASF value +# Inputs: 0) ExifTool object ref, 1) data reference, 2) value offset, +# 3) format number, 4) size +# Returns: converted value +sub ReadASF($$$$$) +{ + my ($et, $dataPt, $pos, $format, $size) = @_; + my @vals; + if ($format == 0) { # unicode string + $vals[0] = $et->Decode(substr($$dataPt,$pos,$size),'UCS2','II'); + } elsif ($format == 2) { # 4-byte boolean + @vals = ReadValue($dataPt, $pos, 'int32u', undef, $size); + foreach (@vals) { + $_ = $_ ? 'True' : 'False'; + } + } elsif ($format == 3) { # int32u + @vals = ReadValue($dataPt, $pos, 'int32u', undef, $size); + } elsif ($format == 4) { # int64u + @vals = ReadValue($dataPt, $pos, 'int64u', undef, $size); + } elsif ($format == 5) { # int16u + @vals = ReadValue($dataPt, $pos, 'int16u', undef, $size); + } else { # any other format (including 1, byte array): return raw data + $vals[0] = substr($$dataPt,$pos,$size); + } + return join ' ', @vals; +} + +#------------------------------------------------------------------------------ +# Process extended content description +# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference +# Returns: 1 on success +sub ProcessExtendedContentDescription($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + return 0 if $dirLen < 2; + my $count = Get16u($dataPt, 0); + $et->VerboseDir($dirInfo, $count); + my $pos = 2; + my $i; + for ($i=0; $i<$count; ++$i) { + return 0 if $pos + 6 > $dirLen; + my $nameLen = unpack("x${pos}v", $$dataPt); + $pos += 2; + return 0 if $pos + $nameLen + 4 > $dirLen; + my $tag = Image::ExifTool::Decode(undef,substr($$dataPt,$pos,$nameLen),'UCS2','II','Latin'); + $tag =~ s/^WM\///; # remove leading "WM/" + $pos += $nameLen; + my ($dType, $dLen) = unpack("x${pos}v2", $$dataPt); + $pos += 4; + return 0 if $pos + $dLen > $dirLen; + my $val = ReadASF($et,$dataPt,$pos,$dType,$dLen); + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + Start => $pos, + Size => $dLen, + ); + $pos += $dLen; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process ASF metadata library (similar to ProcessExtendedContentDescription above) +# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference +# Returns: 1 on success +sub ProcessMetadata($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + return 0 if $dirLen < 2; + my $count = Get16u($dataPt, 0); + $et->VerboseDir($dirInfo, $count); + my $pos = 2; + my $i; + for ($i=0; $i<$count; ++$i) { + return 0 if $pos + 12 > $dirLen; + my ($index, $stream, $nameLen, $dType, $dLen) = unpack("x${pos}v4V", $$dataPt); + $pos += 12; + return 0 if $pos + $nameLen + $dLen > $dirLen; + my $tag = Image::ExifTool::Decode(undef,substr($$dataPt,$pos,$nameLen),'UCS2','II','Latin'); + $tag =~ s/^WM\///; # remove leading "WM/" + $pos += $nameLen; + my $val = ReadASF($et,$dataPt,$pos,$dType,$dLen); + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + Start => $pos, + Size => $dLen, + ); + $pos += $dLen; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process WM/Picture preview +# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference +# Returns: 1 on success +sub ProcessPicture($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + return 0 unless $dirLen > 9; + # extract picture type and length + my ($type, $picLen) = unpack("x${dirStart}CV", $$dataPt); + $et->VerboseDir('Picture'); + $et->HandleTag($tagTablePtr, 0, $type); + # extract mime type and description strings (null-terminated unicode strings) + my $n = $dirLen - 5 - $picLen; + return 0 if $n & 0x01 or $n < 4; + my $str = substr($$dataPt, $dirStart+5, $n); + if ($str =~ /^((?:..)*?)\0\0((?:..)*?)\0\0/s) { + my ($mime, $desc) = ($1, $2); + $et->HandleTag($tagTablePtr, 1, $et->Decode($mime,'UCS2','II')); + $et->HandleTag($tagTablePtr, 2, $et->Decode($desc,'UCS2','II')) if length $desc; + } + $et->HandleTag($tagTablePtr, 3, substr($$dataPt, $dirStart+5+$n, $picLen)); + return 1; +} + +#------------------------------------------------------------------------------ +# Process codec list +# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference +# Returns: 1 on success +sub ProcessCodecList($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + return 0 if $dirLen < 20; + my $count = Get32u($dataPt, 16); + $et->VerboseDir($dirInfo, $count); + my $pos = 20; + my $i; + my %codecType = ( 1 => 'Video', 2 => 'Audio' ); + for ($i=0; $i<$count; ++$i) { + return 0 if $pos + 8 > $dirLen; + my $type = ($codecType{Get16u($dataPt, $pos)} || 'Other') . 'Codec'; + # stupid Windows programmers: these lengths are in characters (others are in bytes) + my $nameLen = Get16u($dataPt, $pos + 2) * 2; + $pos += 4; + return 0 if $pos + $nameLen + 2 > $dirLen; + my $name = $et->Decode(substr($$dataPt,$pos,$nameLen),'UCS2','II'); + $et->HandleTag($tagTablePtr, "${type}Name", $name); + my $descLen = Get16u($dataPt, $pos + $nameLen) * 2; + $pos += $nameLen + 2; + return 0 if $pos + $descLen + 2 > $dirLen; + my $desc = $et->Decode(substr($$dataPt,$pos,$descLen),'UCS2','II'); + $et->HandleTag($tagTablePtr, "${type}Description", $desc); + my $infoLen = Get16u($dataPt, $pos + $descLen); + $pos += $descLen + 2 + $infoLen; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from a ASF file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref +# Returns: 1 on success, 0 if this wasn't a valid ASF file +sub ProcessASF($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my $rtnVal = 0; + my $pos = 0; + my ($buff, $err, @parentTable, @childEnd); + + for (;;) { + last unless $raf->Read($buff, 24) == 24; + $pos += 24; + my $tag = GetGUID(substr($buff,0,16)); + unless ($tagTablePtr) { + # verify this is a valid ASF file + last unless $tag eq '75B22630-668E-11CF-A6D9-00AA0062CE6C'; + my $fileType = $$et{FILE_EXT}; + $fileType = 'ASF' unless $fileType and $fileType =~ /^(ASF|WMV|WMA|DIVX)$/; + $et->SetFileType($fileType); + SetByteOrder('II'); + $tagTablePtr = GetTagTable('Image::ExifTool::ASF::Main'); + $rtnVal = 1; + } + my $size = Image::ExifTool::Get64u(\$buff, 16) - 24; + if ($size < 0) { + $err = 'Invalid ASF object size'; + last; + } + if ($size > 0x7fffffff) { + if ($size > 0x7fffffff * 4294967296) { + $err = 'Invalid ASF object size'; + } elsif ($et->Options('LargeFileSupport')) { + if ($raf->Seek($size, 1)) { + $et->VPrint(0, " Skipped large ASF object ($size bytes)\n"); + $pos += $size; + next; + } + $err = 'Error seeking past large ASF object'; + } else { + $err = 'Large ASF objects not supported (LargeFileSupport not set)'; + } + last; + } + # go back to parent tag table if done with previous children + if (@childEnd and $pos >= $childEnd[-1]) { + pop @childEnd; + $tagTablePtr = pop @parentTable; + $$et{INDENT} = substr($$et{INDENT},0,-2); + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + $verbose and $et->VerboseInfo($tag, $tagInfo); + if ($tagInfo) { + my $subdir = $$tagInfo{SubDirectory}; + if ($subdir) { + my $subTable = GetTagTable($$subdir{TagTable}); + if ($$subTable{PROCESS_PROC} eq \&ProcessASF) { + if (defined $$subdir{Size}) { + my $s = $$subdir{Size}; + if ($verbose > 2) { + $raf->Read($buff, $s) == $s or $err = 'Truncated file', last; + $et->VerboseDump(\$buff); + } elsif (not $raf->Seek($s, 1)) { + $err = 'Seek error'; + last; + } + # continue processing linearly using subTable + push @parentTable, $tagTablePtr; + push @childEnd, $pos + $size; + $tagTablePtr = $subTable; + $pos += $$subdir{Size}; + if ($verbose) { + $$et{INDENT} .= '| '; + $et->VerboseDir($$tagInfo{Name}); + } + next; + } + } elsif ($raf->Read($buff, $size) == $size) { + my %subdirInfo = ( + DataPt => \$buff, + DirStart => 0, + DirLen => $size, + DirName => $$tagInfo{Name}, + ); + $et->VerboseDump(\$buff) if $verbose > 2; + unless ($et->ProcessDirectory(\%subdirInfo, $subTable, $$subdir{ProcessProc})) { + $et->Warn("Error processing $$tagInfo{Name} directory"); + } + $pos += $size; + next; + } else { + $err = 'Unexpected end of file'; + last; + } + } + } + if ($verbose > 2) { + $raf->Read($buff, $size) == $size or $err = 'Truncated file', last; + $et->VerboseDump(\$buff); + } elsif (not $raf->Seek($size, 1)) { # skip the block + $err = 'Seek error'; + last; + } + $pos += $size; + } + $err and $et->Warn($err); + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::ASF - Read ASF/WMA/WMV meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +information from Microsoft Advanced Systems Format (ASF) files, including +Windows Media Audio (WMA) and Windows Media Video (WMV) files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 + +=back + +=head1 SEE ALSO + +L, +L + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Apple.pm b/ExifTool/lib/Image/ExifTool/Apple.pm new file mode 100644 index 0000000..5d69ef1 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Apple.pm @@ -0,0 +1,356 @@ +#------------------------------------------------------------------------------ +# File: Apple.pm +# +# Description: Apple EXIF maker notes tags +# +# Revisions: 2013-09-13 - P. Harvey Created +# +# References: 1) http://www.photoinvestigator.co/blog/the-mystery-of-maker-apple-metadata/ +# 2) Frank Rupprecht private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::Apple; + +use strict; +use vars qw($VERSION); +use Image::ExifTool::Exif; +use Image::ExifTool::PLIST; + +$VERSION = '1.11'; + +sub ConvertPLIST($$); + +# Apple iPhone metadata (ref PH) +%Image::ExifTool::Apple::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => 'Tags extracted from the maker notes of iPhone images.', + 0x0001 => { # (Version, ref 2) + Name => 'MakerNoteVersion', + Writable => 'int32s', + }, + 0x0002 => { #2 + Name => 'AEMatrix', + Unknown => 1, + # (not currently writable) + ValueConv => \&ConvertPLIST, + }, + 0x0003 => { # (Timestamp, ref 2) + Name => 'RunTime', # (includes time plugged in, but not when suspended, ref 1) + SubDirectory => { TagTable => 'Image::ExifTool::Apple::RunTime' }, + }, + 0x0004 => { #2 + Name => 'AEStable', + Writable => 'int32s', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x0005 => { #2 + Name => 'AETarget', + Writable => 'int32s', + }, + 0x0006 => { #2 + Name => 'AEAverage', + Writable => 'int32s', + }, + 0x0007 => { #2 + Name => 'AFStable', + Writable => 'int32s', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x0008 => { #1 (FocusAccelerometerVector, ref 2) + Name => 'AccelerationVector', + Groups => { 2 => 'Camera' }, + Writable => 'rational64s', + Count => 3, + # Note: the directions are contrary to the Apple documentation (which have the + # signs of all axes reversed -- apparently the Apple geeks aren't very good + # with basic physics, and don't understand the concept of acceleration. See + # http://nscookbook.com/2013/03/ios-programming-recipe-19-using-core-motion-to-access-gyro-and-accelerometer/ + # for one of the few correct descriptions of this). Note that this leads to + # a left-handed coordinate system for acceleration. + Notes => q{ + XYZ coordinates of the acceleration vector in units of g. As viewed from + the front of the phone, positive X is toward the left side, positive Y is + toward the bottom, and positive Z points into the face of the phone + }, + }, + # 0x0009 - int32s: seen 19,275,531,4371 (SISMethod, ref 2) + 0x000a => { # (HDRMethod, ref 2) + Name => 'HDRImageType', + Writable => 'int32s', + PrintConv => { + # 2 => ? (iPad mini 2) + 3 => 'HDR Image', + 4 => 'Original Image', + }, + }, + 0x000b => { # (BurstUUID, ref 2) + Name => 'BurstUUID', + Writable => 'string', + Notes => 'unique ID for all images in a burst', + }, + 0x000c => { # ref forum13710 (Neal Krawetz) (SphereHealthTrackingError, ref 2) + Name => 'FocusDistanceRange', + Writable => 'rational64s', + Count => 2, + PrintConv => q{ + my @a = split ' ', $val; + sprintf('%.2f - %.2f m', $a[0] <= $a[1] ? @a : reverse @a); + }, + PrintConvInv => '$val =~ s/ - //; $val =~ s/ ?m$//; $val', + }, + # 0x000d - int32s: 0,1,6,20,24,32,40 (SphereHealthAverageCurrent, ref 2) + # 0x000e - int32s: 0,1,4,12 (Orientation? 0=landscape? 4=portrait? ref 1) (SphereMotionDataStatus, ref 2) + 0x000f => { #2 + Name => 'OISMode', + Writable => 'int32s', + # seen: 2,3,5 + }, + # 0x0010 - int32s: 1 (SphereStatus, ref 2) + 0x0011 => { # (if defined, there is a live photo associated with the video, #forum13565) (AssetIdentifier, ref 2) + Name => 'ContentIdentifier', + Notes => 'called MediaGroupUUID when it appears as an XAttr', + # - originally called ContentIdentifier, forum8750 + # - changed in 12.19 to MediaGroupUUID, NealKrawetz private communication + # - changed back to ContentIdentifier since Apple writes this to Keys content.identifier (forum14874) + Writable => 'string', + + }, + # 0x0012 - (QRMOutputType, ref 2) + # 0x0013 - (SphereExternalForceOffset, ref 2) + 0x0014 => { # (StillImageCaptureType, ref 2) + Name => 'ImageCaptureType', + Writable => 'int32s', + # seen: 1,2,3,4,5,10,12 + PrintConv => { #forum15096 + 1 => 'ProRAW', + 2 => 'Portrait', + 10 => 'Photo', + }, + }, + 0x0015 => { # (ImageGroupIdentifier, ref 2) + Name => 'ImageUniqueID', + Writable => 'string', + }, + # 0x0016 - string[29]: "AXZ6pMTOh2L+acSh4Kg630XCScoO\0" (PhotosOriginatingSignature, ref 2) + 0x0017 => { #forum13565 (only valid if MediaGroupUUID/ContentIdentifier exists) (StillImageCaptureFlags, ref 2) + Name => 'LivePhotoVideoIndex', + Notes => 'divide by RunTimeScale to get time in seconds', + }, + # 0x0018 - (PhotosRenderOriginatingSignature, ref 2) + 0x0019 => { # (StillImageProcessingFlags, ref 2) + Name => 'ImageProcessingFlags', + Writable => 'int32s', + Unknown => 1, + PrintConv => { BITMASK => { } }, + }, + 0x001a => { # (PhotoTranscodeQualityHint, ref 2) + Name => 'QualityHint', + Writable => 'string', + Unknown => 1, + # seen: "q825s\0", "q750n\0", "q900n\0" + }, + # 0x001b - (PhotosRenderEffect, ref 2) + # 0x001c - (BracketedCaptureSequenceNumber, ref 2) + 0x001d => { #2 + Name => 'LuminanceNoiseAmplitude', + Writable => 'rational64s', + }, + # 0x001e - (OriginatingAppID, ref 2) + # 0x001f - int32s: 0,1 (PhotosAppFeatureFlags, ref 2) + 0x0020 => { # (ImageCaptureRequestIdentifier, ref 2) + Name => 'ImageCaptureRequestID', + Writable => 'string', + Unknown => 1, + }, + 0x0021 => { # (MeteorHeadroom, ref 2) + Name => 'HDRHeadroom', + Writable => 'rational64s', + }, + # 0x0022 - (ARKitPhoto, ref 2) + # 0x0023 - int32s[2] (AFPerformance, ref 2) + # 0x0024 - (AFExternalOffset, ref 2) + 0x0025 => { # (StillImageSceneFlags, ref 2) + Name => 'SceneFlags', + Writable => 'int32s', + Unknown => 1, + PrintConv => { BITMASK => { } }, + }, + 0x0026 => { # (StillImageSNRType, ref 2) + Name => 'SignalToNoiseRatioType', + Writable => 'int32s', + Unknown => 1, + }, + 0x0027 => { # (StillImageSNR, ref 2) + Name => 'SignalToNoiseRatio', + Writable => 'rational64s', + }, + # 0x0028 - int32s (UBMethod, ref 2) + # 0x0029 - string (SpatialOverCaptureGroupIdentifier, ref 2) + # 0x002A - (iCloudServerSoftwareVersionForDynamicallyGeneratedMedia, ref 2) + 0x002b => { + Name => 'PhotoIdentifier', #2 + Writable => 'string', + }, + # 0x002C - (SpatialOverCaptureImageType, ref 2) + # 0x002D - (CCT, ref 2) + # 0x002E - (ApsMode, ref 2) + 0x002F => { #2 + Name => 'FocusPosition', + Writable => 'int32s', + }, + 0x0030 => { # (MeteorPlusGainMap, ref 2) + Name => 'HDRGain', + Writable => 'rational64s', + }, + # 0x0031 - (StillImageProcessingHomography, ref 2) + # 0x0032 - (IntelligentDistortionCorrection, ref 2) + # 0x0033 - (NRFStatus, ref 2) + # 0x0034 - (NRFInputBracketCount, ref 2) + # 0x0035 - (NRFRegisteredBracketCount, ref 2) + # 0x0036 - (LuxLevel, ref 2) + # 0x0037 - (LastFocusingMethod, ref 2) + 0x0038 => { # (TimeOfFlightAssistedAutoFocusEstimatorMeasuredDepth, ref 2) + Name => 'AFMeasuredDepth', + Notes => 'from the time-of-flight-assisted auto-focus estimator', + Writable => 'int32s', + }, + # 0x0039 - (TimeOfFlightAssistedAutoFocusEstimatorROIType, ref 2) + # 0x003A - (NRFSRLStatus, ref 2) + # 0x003B - (SystemPressureLevel, ref 2) + # 0x003C - (CameraControlsStatisticsMaster, ref 2) + 0x003D => { # (TimeOfFlightAssistedAutoFocusEstimatorSensorConfidence, ref 2) + Name => 'AFConfidence', + Writable => 'int32s', + }, + 0x003E => { # (ColorCorrectionMatrix, ref 2) + Name => 'ColorCorrectionMatrix', + Unknown => 1, + ValueConv => \&ConvertPLIST, + }, + 0x003F => { #2 + Name => 'GreenGhostMitigationStatus', + Writable => 'int32s', + Unknown => 1, + }, + 0x0040 => { #2 + Name => 'SemanticStyle', + ValueConv => \&ConvertPLIST, + }, + 0x0041 => { # (SemanticStyleKey_RenderingVersion, ref 2) + Name => 'SemanticStyleRenderingVer', + ValueConv => \&ConvertPLIST, + }, + 0x0042 => { # (SemanticStyleKey_Preset, ref 2) + Name => 'SemanticStylePreset', + ValueConv => \&ConvertPLIST, + }, + # 0x0043 - (SemanticStyleKey_ToneBias, ref 2) + # 0x0044 - (SemanticStyleKey_WarmthBias, ref 2) + 0x0045 => { # (FrontFacing, ref 2) + Name => 'FrontFacingCamera', + Writable => 'int32s', + PrintConv => { 0 => 'No', 1 => 'Yes' }, #PH (NC) + }, + # 0x0046 - (TimeOfFlightAssistedAutoFocusEstimatorContainsBlindSpot, ref 2) + # 0x0047 - (LeaderFollowerAutoFocusLeaderDepth, ref 2) + # 0x0048 - (LeaderFollowerAutoFocusLeaderFocusMethod, ref 2) + # 0x0049 - (LeaderFollowerAutoFocusLeaderConfidence, ref 2) + # 0x004A - (LeaderFollowerAutoFocusLeaderROIType, ref 2) + # 0x004B - (ZeroShutterLagFailureReason, ref 2) + # 0x004C - (TimeOfFlightAssistedAutoFocusEstimatorMSPMeasuredDepth, ref 2) + # 0x004D - (TimeOfFlightAssistedAutoFocusEstimatorMSPSensorConfidence, ref 2) + # 0x004E - (Camera, ref 2) +); + +# PLIST-format CMTime structure (ref PH) +# (CMTime ref https://developer.apple.com/library/ios/documentation/CoreMedia/Reference/CMTime/Reference/reference.html) +%Image::ExifTool::Apple::RunTime = ( + PROCESS_PROC => \&Image::ExifTool::PLIST::ProcessBinaryPLIST, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => q{ + This PLIST-format information contains the elements of a CMTime structure + representing the amount of time the phone has been running since the last + boot, not including standby time. + }, + timescale => { Name => 'RunTimeScale' }, # (seen 1000000000 --> ns) + epoch => { Name => 'RunTimeEpoch' }, # (seen 0) + value => { Name => 'RunTimeValue' }, # (should divide by RunTimeScale to get seconds) + flags => { + Name => 'RunTimeFlags', + PrintConv => { BITMASK => { + 0 => 'Valid', + 1 => 'Has been rounded', + 2 => 'Positive infinity', + 3 => 'Negative infinity', + 4 => 'Indefinite', + }}, + }, +); + +# Apple composite tags +%Image::ExifTool::Apple::Composite = ( + GROUPS => { 2 => 'Camera' }, + RunTimeSincePowerUp => { + Require => { + 0 => 'Apple:RunTimeValue', + 1 => 'Apple:RunTimeScale', + }, + ValueConv => '$val[1] ? $val[0] / $val[1] : undef', + PrintConv => 'ConvertDuration($val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Apple'); + +#------------------------------------------------------------------------------ +# Convert from binary PLIST format to a tag value we can use +# Inputs: 0) binary plist data, 1) ExifTool ref +# Returns: converted value +sub ConvertPLIST($$) +{ + my ($val, $et) = @_; + my $dirInfo = { DataPt => \$val }; + require Image::ExifTool::PLIST; + Image::ExifTool::PLIST::ProcessBinaryPLIST($et, $dirInfo); + $val = $$dirInfo{Value}; + if (ref $val eq 'HASH' and not $et->Options('Struct')) { + require 'Image/ExifTool/XMPStruct.pl'; + $val = Image::ExifTool::XMP::SerializeStruct($et, $val); + } + return $val; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Apple - Apple 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 +Apple maker notes in EXIF information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L, +L + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Audible.pm b/ExifTool/lib/Image/ExifTool/Audible.pm new file mode 100644 index 0000000..3670b5d --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Audible.pm @@ -0,0 +1,317 @@ +#------------------------------------------------------------------------------ +# File: Audible.pm +# +# Description: Read metadata from Audible audio books +# +# Revisions: 2015/04/05 - P. Harvey Created +# +# References: 1) https://github.com/jteeuwen/audible +# 2) https://code.google.com/p/pyaudibletags/ +# 3) http://wiki.multimedia.cx/index.php?title=Audible_Audio +#------------------------------------------------------------------------------ + +package Image::ExifTool::Audible; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.02'; + +sub ProcessAudible_meta($$$); +sub ProcessAudible_cvrx($$$); + +%Image::ExifTool::Audible::Main = ( + GROUPS => { 2 => 'Audio' }, + NOTES => q{ + ExifTool will extract any information found in the metadata dictionary of + Audible .AA files, even if not listed in the table below. + }, + # tags found in the metadata dictionary (chunk 2) + pubdate => { Name => 'PublishDate', Groups => { 2 => 'Time' } }, + pub_date_start => { Name => 'PublishDateStart', Groups => { 2 => 'Time' } }, + author => { Name => 'Author', Groups => { 2 => 'Author' } }, + copyright => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + # also seen (ref PH): + # product_id, parent_id, title, provider, narrator, price, description, + # long_description, short_title, is_aggregation, title_id, codec, HeaderSeed, + # EncryptedBlocks, HeaderKey, license_list, CPUType, license_count, <12 hex digits>, + # parent_short_title, parent_title, aggregation_id, short_description, user_alias + + # information extracted from other chunks + _chapter_count => { Name => 'ChapterCount' }, # from chunk 6 + _cover_art => { # from chunk 11 + Name => 'CoverArt', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, +); + +# 'tags' atoms observed in Audible .m4b audio books (ref PH) +%Image::ExifTool::Audible::tags = ( + GROUPS => { 0 => 'QuickTime', 2 => 'Audio' }, + NOTES => 'Information found in "tags" atom of Audible M4B audio books.', + meta => { + Name => 'Audible_meta', + SubDirectory => { TagTable => 'Image::ExifTool::Audible::meta' }, + }, + cvrx => { + Name => 'Audible_cvrx', + SubDirectory => { TagTable => 'Image::ExifTool::Audible::cvrx' }, + }, + tseg => { + Name => 'Audible_tseg', + SubDirectory => { TagTable => 'Image::ExifTool::Audible::tseg' }, + }, +); + +# 'meta' information observed in Audible .m4b audio books (ref PH) +%Image::ExifTool::Audible::meta = ( + PROCESS_PROC => \&ProcessAudible_meta, + GROUPS => { 0 => 'QuickTime', 2 => 'Audio' }, + NOTES => 'Information found in Audible M4B "meta" atom.', + Album => 'Album', + ALBUMARTIST => { Name => 'AlbumArtist', Groups => { 2 => 'Author' } }, + Artist => { Name => 'Artist', Groups => { 2 => 'Author' } }, + Comment => 'Comment', + Genre => 'Genre', + itunesmediatype => { Name => 'iTunesMediaType', Description => 'iTunes Media Type' }, + SUBTITLE => 'Subtitle', + Title => 'Title', + TOOL => 'CreatorTool', + Year => { Name => 'Year', Groups => { 2 => 'Time' } }, + track => 'ChapterName', # (found in 'meta' of 'tseg' atom) +); + +# 'cvrx' information observed in Audible .m4b audio books (ref PH) +%Image::ExifTool::Audible::cvrx = ( + PROCESS_PROC => \&ProcessAudible_cvrx, + GROUPS => { 0 => 'QuickTime', 2 => 'Audio' }, + NOTES => 'Audible cover art information in M4B audio books.', + VARS => { NO_ID => 1 }, + CoverArtType => 'CoverArtType', + CoverArt => { + Name => 'CoverArt', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, +); + +# 'tseg' information observed in Audible .m4b audio books (ref PH) +%Image::ExifTool::Audible::tseg = ( + GROUPS => { 0 => 'QuickTime', 2 => 'Audio' }, + tshd => { + Name => 'ChapterNumber', + Format => 'int32u', + ValueConv => '$val + 1', # start counting from 1 + }, + meta => { + Name => 'Audible_meta2', + SubDirectory => { TagTable => 'Image::ExifTool::Audible::meta' }, + }, +); + +#------------------------------------------------------------------------------ +# Process Audible 'meta' tags from M4B files (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessAudible_meta($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $dirLen = length $$dataPt; + return 0 if $dirLen < 4; + my $num = Get32u($dataPt, 0); + $et->VerboseDir('Audible_meta', $num); + my $pos = 4; + my $index; + for ($index=0; $index<$num; ++$index) { + last if $pos + 3 > $dirLen; + my $unk = Get8u($dataPt, $pos); # ? (0x80 or 0x00) + last unless $unk == 0x80 or $unk == 0x00; + my $len = Get16u($dataPt, $pos + 1); # tag length + $pos += 3; + last if $pos + $len + 6 > $dirLen or not $len; + my $tag = substr($$dataPt, $pos, $len); # tag ID + my $ver = Get16u($dataPt, $pos + $len); # version? + last unless $ver == 0x0001; + my $size = Get32u($dataPt, $pos + $len + 2);# data size + $pos += $len + 6; + last if $pos + $size > $dirLen; + my $val = $et->Decode(substr($$dataPt, $pos, $size), 'UTF8'); + unless ($$tagTablePtr{$tag}) { + my $name = Image::ExifTool::MakeTagName(($tag =~ /[a-z]/) ? $tag : lc($tag)); + AddTagToTable($tagTablePtr, $tag, { Name => $name }); + } + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos, + Size => $size, + Index => $index, + ); + $pos += $size; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process Audible 'cvrx' cover art atom from M4B files (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessAudible_cvrx($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $dirLen = length $$dataPt; + return 0 if 0x0a > $dirLen; + my $len = Get16u($dataPt, 0x08); + return 0 if 0x0a + $len + 6 > $dirLen; + my $size = Get32u($dataPt, 0x0a + $len + 2); + return 0 if 0x0a + $len + 6 + $size > $dirLen; + $et->VerboseDir('Audible_cvrx', undef, $dirLen); + $et->HandleTag($tagTablePtr, 'CoverArtType', undef, + DataPt => $dataPt, + DataPos => $dataPos, + Start => 0x0a, + Size => $len, + ); + $et->HandleTag($tagTablePtr, 'CoverArt', undef, + DataPt => $dataPt, + DataPos => $dataPos, + Start => 0x0a + $len + 6, + Size => $size, + ); + return 1; +} + +#------------------------------------------------------------------------------ +# Read information from an Audible .AA file +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid AA file +sub ProcessAA($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $toc, $entry, $i); + + # check magic number + return 0 unless $raf->Read($buff, 16) == 16 and $buff=~/^.{4}\x57\x90\x75\x36/s; + # check file size + if (defined $$et{VALUE}{FileSize}) { + # first 4 bytes of the file should be the filesize + unpack('N', $buff) == $$et{VALUE}{FileSize} or return 0; + } + $et->SetFileType(); + SetByteOrder('MM'); + my $bytes = 12 * Get32u(\$buff, 8); # table of contents size in bytes + $bytes > 0xc00 and $et->Warn('Invalid TOC'), return 1; + # read the table of contents + $raf->Read($toc, $bytes) == $bytes or $et->Warn('Truncated TOC'), return 1; + my $tagTablePtr = GetTagTable('Image::ExifTool::Audible::Main'); + # parse table of contents (in $toc) + for ($entry=0; $entry<$bytes; $entry+=12) { + my $type = Get32u(\$toc, $entry); + next unless $type == 2 or $type == 6 or $type == 11; + my $offset = Get32u(\$toc, $entry + 4); + my $length = Get32u(\$toc, $entry + 8) or next; + $raf->Seek($offset, 0) or $et->Warn("Chunk $type seek error"), last; + if ($type == 6) { # offset table + next if $length < 4 or $raf->Read($buff, 4) != 4; # only read the chapter count + $et->HandleTag($tagTablePtr, '_chapter_count', Get32u(\$buff, 0)); + next; + } + # read the chunk + $length > 100000000 and $et->Warn("Chunk $type too big"), next; + $raf->Read($buff, $length) == $length or $et->Warn("Chunk $type read error"), last; + if ($type == 11) { # cover art + next if $length < 8; + my $len = Get32u(\$buff, 0); + my $off = Get32u(\$buff, 4); + next if $off < $offset + 8 or $off - $offset + $len > $length; + $et->HandleTag($tagTablePtr, '_cover_art', substr($buff, $off-$offset, $len)); + next; + } + # parse metadata dictionary (in $buff) + $length < 4 and $et->Warn('Bad dictionary'), next; + my $num = Get32u(\$buff, 0); + $num > 0x200 and $et->Warn('Bad dictionary count'), next; + my $pos = 4; # dictionary starts immediately after count + require Image::ExifTool::HTML; # (for UnescapeHTML) + $et->VerboseDir('Audible Metadata', $num); + for ($i=0; $i<$num; ++$i) { + my $tagPos = $pos + 9; # position of tag string + $tagPos > $length and $et->Warn('Truncated dictionary'), last; + # (1 unknown byte ignored at start of each dictionary entry) + my $tagLen = Get32u(\$buff, $pos + 1); # tag string length + my $valLen = Get32u(\$buff, $pos + 5); # value string length + my $valPos = $tagPos + $tagLen; # position of value string + my $nxtPos = $valPos + $valLen; # position of next entry + $nxtPos > $length and $et->Warn('Bad dictionary entry'), last; + my $tag = substr($buff, $tagPos, $tagLen); + my $val = substr($buff, $valPos, $valLen); + unless ($$tagTablePtr{$tag}) { + my $name = Image::ExifTool::MakeTagName($tag); + $name =~ s/_(.)/\U$1/g; # change from underscore-separated to mixed case + AddTagToTable($tagTablePtr, $tag, { Name => $name }); + } + # unescape HTML character references and convert from UTF-8 + $val = $et->Decode(Image::ExifTool::HTML::UnescapeHTML($val), 'UTF8'); + $et->HandleTag($tagTablePtr, $tag, $val, + DataPos => $offset, + DataPt => \$buff, + Start => $valPos, + Size => $valLen, + Index => $i, + ); + $pos = $nxtPos; # step to next dictionary entry + } + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Audible - Read meta information from Audible audio books + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read meta +information from Audible audio books. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 + +=item L + +=item L + +=back + +=head1 SEE ALSO + +L, +L + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/BMP.pm b/ExifTool/lib/Image/ExifTool/BMP.pm new file mode 100644 index 0000000..c2e7420 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/BMP.pm @@ -0,0 +1,360 @@ +#------------------------------------------------------------------------------ +# File: BMP.pm +# +# Description: Read BMP meta information +# +# Revisions: 07/16/2005 - P. Harvey Created +# +# References: 1) http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html +# 2) http://www.fourcc.org/rgb.php +# 3) https://msdn.microsoft.com/en-us/library/dd183381(v=vs.85).aspx +#------------------------------------------------------------------------------ + +package Image::ExifTool::BMP; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.09'; + +# conversions for fixed-point 2.30 format values +my %fixed2_30 = ( + ValueConv => q{ + my @a = split ' ', $val; + $_ /= 0x40000000 foreach @a; + "@a"; + }, + PrintConv => q{ + my @a = split ' ', $val; + $_ = sprintf('%.6f', $_) foreach @a; + "@a"; + }, +); + +# BMP chunks +%Image::ExifTool::BMP::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + NOTES => q{ + There really isn't much meta information in a BMP file as such, just a bit + of image related information. + }, + 0 => { + Name => 'BMPVersion', + Format => 'int32u', + Notes => q{ + this is actually the size of the BMP header, but used to determine the BMP + version + }, + RawConv => '$$self{BMPVersion} = $val', + PrintConv => { + 40 => 'Windows V3', + 68 => 'AVI BMP structure?', #PH (seen in AVI movies from some Casio and Nikon cameras) + 108 => 'Windows V4', + 124 => 'Windows V5', + }, + }, + 4 => { + Name => 'ImageWidth', + Format => 'int32u', + }, + 8 => { + Name => 'ImageHeight', + Format => 'int32s', # (negative when stored in top-to-bottom order) + ValueConv => 'abs($val)', + }, + 12 => { + Name => 'Planes', + Format => 'int16u', + # values: 0,1,4,8,16,24,32 + }, + 14 => { + Name => 'BitDepth', + Format => 'int16u', + }, + 16 => { + Name => 'Compression', + Format => 'int32u', + RawConv => '$$self{BMPCompression} = $val', + # (formatted as string[4] for some values in AVI images) + ValueConv => '$val > 256 ? unpack("A4",pack("V",$val)) : $val', + PrintConv => { + 0 => 'None', + 1 => '8-Bit RLE', + 2 => '4-Bit RLE', + 3 => 'Bitfields', + 4 => 'JPEG', #2 + 5 => 'PNG', #2 + # pass through ASCII video compression codec ID's + OTHER => sub { + my $val = shift; + # convert non-ascii characters + $val =~ s/([\0-\x1f\x7f-\xff])/sprintf('\\x%.2x',ord $1)/eg; + return $val; + }, + }, + }, + 20 => { + Name => 'ImageLength', + Format => 'int32u', + RawConv => '$$self{BMPImageLength} = $val', + }, + 24 => { + Name => 'PixelsPerMeterX', + Format => 'int32u', + }, + 28 => { + Name => 'PixelsPerMeterY', + Format => 'int32u', + }, + 32 => { + Name => 'NumColors', + Format => 'int32u', + PrintConv => '$val ? $val : "Use BitDepth"', + }, + 36 => { + Name => 'NumImportantColors', + Format => 'int32u', + Hook => '$varSize += $size if $$self{BMPVersion} == 68', # (the rest is invalid for AVI BMP's) + PrintConv => '$val ? $val : "All"', + }, + 40 => { + Name => 'RedMask', + Format => 'int32u', + PrintConv => 'sprintf("0x%.8x",$val)', + }, + 44 => { + Name => 'GreenMask', + Format => 'int32u', + PrintConv => 'sprintf("0x%.8x",$val)', + }, + 48 => { + Name => 'BlueMask', + Format => 'int32u', + PrintConv => 'sprintf("0x%.8x",$val)', + }, + 52 => { + Name => 'AlphaMask', + Format => 'int32u', + PrintConv => 'sprintf("0x%.8x",$val)', + }, + 56 => { + Name => 'ColorSpace', + Format => 'undef[4]', + RawConv => '$$self{BMPColorSpace} = $val =~ /\0/ ? Get32u(\$val, 0) : pack("N",unpack("V",$val))', + PrintConv => { + 0 => 'Calibrated RGB', + 1 => 'Device RGB', + 2 => 'Device CMYK', + LINK => 'Linked Color Profile', + MBED => 'Embedded Color Profile', + sRGB => 'sRGB', + 'Win ' => 'Windows Color Space', + }, + }, + 60 => { + Name => 'RedEndpoint', + Condition => '$$self{BMPColorSpace} eq "0"', + Format => 'int32u[3]', + %fixed2_30, + }, + 72 => { + Name => 'GreenEndpoint', + Condition => '$$self{BMPColorSpace} eq "0"', + Format => 'int32u[3]', + %fixed2_30, + }, + 84 => { + Name => 'BlueEndpoint', + Condition => '$$self{BMPColorSpace} eq "0"', + Format => 'int32u[3]', + %fixed2_30, + }, + 96 => { + Name => 'GammaRed', + Condition => '$$self{BMPColorSpace} eq "0"', + Format => 'fixed32u', + }, + 100 => { + Name => 'GammaGreen', + Condition => '$$self{BMPColorSpace} eq "0"', + Format => 'fixed32u', + }, + 104 => { + Name => 'GammaBlue', + Condition => '$$self{BMPColorSpace} eq "0"', + Format => 'fixed32u', + }, + 108 => { + Name => 'RenderingIntent', + Format => 'int32u', + PrintConv => { + 1 => 'Graphic (LCS_GM_BUSINESS)', + 2 => 'Proof (LCS_GM_GRAPHICS)', + 4 => 'Picture (LCS_GM_IMAGES)', + 8 => 'Absolute Colorimetric (LCS_GM_ABS_COLORIMETRIC)', + }, + }, + 112 => { + Name => 'ProfileDataOffset', + Condition => '$$self{BMPColorSpace} eq "LINK" or $$self{BMPColorSpace} eq "MBED"', + Format => 'int32u', + RawConv => '$$self{BMPProfileOffset} = $val', + }, + 116 => { + Name => 'ProfileSize', + Condition => '$$self{BMPColorSpace} eq "LINK" or $$self{BMPColorSpace} eq "MBED"', + Format => 'int32u', + RawConv => '$$self{BMPProfileSize} = $val', + }, + # 120 - reserved +); + +# OS/2 12-byte bitmap header (ref http://www.fileformat.info/format/bmp/egff.htm) +%Image::ExifTool::BMP::OS2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + NOTES => 'Information extracted from OS/2-format BMP images.', + 0 => { + Name => 'BMPVersion', + Format => 'int32u', + Notes => 'again, the header size is used to determine the BMP version', + PrintConv => { + 12 => 'OS/2 V1', + 64 => 'OS/2 V2', + }, + }, + 4 => { Name => 'ImageWidth', Format => 'int16u' }, + 6 => { Name => 'ImageHeight', Format => 'int16u' }, + 8 => { Name => 'Planes', Format => 'int16u' }, + 10 => { Name => 'BitDepth', Format => 'int16u' }, +); + +%Image::ExifTool::BMP::Extra = ( + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + NOTES => 'Extra information extracted from some BMP images.', + VARS => { NO_ID => 1 }, + LinkedProfileName => { }, + ICC_Profile => { SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' } }, + EmbeddedJPG => { + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + EmbeddedPNG => { + Groups => { 2 => 'Preview' }, + Binary => 1, + }, +); + +#------------------------------------------------------------------------------ +# Extract EXIF information from a BMP image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid BMP file +sub ProcessBMP($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $tagTablePtr); + + # verify this is a valid BMP file + return 0 unless $raf->Read($buff, 18) == 18; + return 0 unless $buff =~ /^BM/; + SetByteOrder('II'); + my $len = Get32u(\$buff, 14); + # len = v1:12, v4:108, v5:124 + return 0 unless $len == 12 or $len == 16 or ($len >= 40 and $len < 1000000); + return 0 unless $raf->Seek(-4, 1) and $raf->Read($buff, $len) == $len; + $et->SetFileType(); # set the FileType tag +# +# process the BMP header +# + my %dirInfo = ( + DataPt => \$buff, + DirStart => 0, + DirLen => length($buff), + ); + if ($len == 12 or $len == 16 or $len == 64) { # old OS/2 format BMP + $tagTablePtr = GetTagTable('Image::ExifTool::BMP::OS2'); + } else { + $tagTablePtr = GetTagTable('Image::ExifTool::BMP::Main'); + } + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); +# +# extract any embedded images +# + my $extraTable = GetTagTable('Image::ExifTool::BMP::Extra'); + if ($$et{BMPCompression} and $$et{BMPImageLength} and + ($$et{BMPCompression} == 4 or $$et{BMPCompression} == 5)) + { + my $tag = $$et{BMPCompression} == 4 ? 'EmbeddedJPG' : 'EmbeddedPNG'; + my $val = $et->ExtractBinary($raf->Tell(), $$et{BMPImageLength}, $tag); + if ($val) { + $et->HandleTag($extraTable, $tag, $val); + } + } +# +# process profile data if it exists (v5 header only) +# + if ($len == 124 and $$et{BMPProfileOffset}) { + my $pos = $$et{BMPProfileOffset} + 14; # (note the 14-byte shift!) + my $size = $$et{BMPProfileSize}; + if ($raf->Seek($pos, 0) and $raf->Read($buff, $size) == $size) { + my $tag; + if ($$et{BMPColorSpace} eq 'LINK') { + $buff =~ s/\0+$//; # remove null terminator(s) + $buff = $et->Decode($buff, 'Latin'); # convert from Latin + $tag = 'LinkedProfileName'; + } else { + $tag = 'ICC_Profile'; + } + $et->HandleTag($extraTable, $tag => $buff, Size => $size, DataPos => $pos); + } else { + $et->Warn('Error loading profile data', 1); + } + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::BMP - Read BMP meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read BMP +(Windows Bitmap) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 + +=item L + +=item L + +=back + +=head1 SEE ALSO + +L, +L + +=cut diff --git a/ExifTool/lib/Image/ExifTool/BPG.pm b/ExifTool/lib/Image/ExifTool/BPG.pm new file mode 100644 index 0000000..186dd5a --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/BPG.pm @@ -0,0 +1,253 @@ +#------------------------------------------------------------------------------ +# File: BPG.pm +# +# Description: Read BPG meta information +# +# Revisions: 2016-07-05 - P. Harvey Created +# +# References: 1) http://bellard.org/bpg/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::BPG; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.01'; + +# BPG information +%Image::ExifTool::BPG::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + NOTES => q{ + The information listed below is extracted from BPG (Better Portable + Graphics) images. See L for the specification. + }, + 4 => { + Name => 'PixelFormat', + Format => 'int16u', + Mask => 0xe000, + PrintConv => { + 0 => 'Grayscale', + 1 => '4:2:0 (chroma at 0.5, 0.5)', + 2 => '4:2:2 (chroma at 0.5, 0)', + 3 => '4:4:4', + 4 => '4:2:0 (chroma at 0, 0.5)', + 5 => '4:2:2 (chroma at 0, 0)', + }, + }, + 4.1 => { + Name => 'Alpha', + Format => 'int16u', + Mask => 0x1004, + BitShift => 0, + PrintHex => 1, + PrintConv => { + 0x0000 => 'No Alpha Plane', + 0x1000 => 'Alpha Exists (color not premultiplied)', + 0x1004 => 'Alpha Exists (color premultiplied)', + 0x0004 => 'Alpha Exists (W color component)', + }, + }, + 4.2 => { + Name => 'BitDepth', + Format => 'int16u', + Mask => 0x0f00, + ValueConv => '$val + 8', + }, + 4.3 => { + Name => 'ColorSpace', + Format => 'int16u', + Mask => 0x00f0, + PrintConv => { + 0 => 'YCbCr (BT 601)', + 1 => 'RGB', + 2 => 'YCgCo', + 3 => 'YCbCr (BT 709)', + 4 => 'YCbCr (BT 2020)', + 5 => 'BT 2020 Constant Luminance', + }, + }, + 4.4 => { + Name => 'Flags', + Format => 'int16u', + Mask => 0x000b, + PrintConv => { BITMASK => { + 0 => 'Animation', + 1 => 'Limited Range', + 3 => 'Extension Present', + }}, + }, + 6 => { Name => 'ImageWidth', Format => 'var_ue7' }, + 7 => { Name => 'ImageHeight', Format => 'var_ue7' }, + # length of image data or 0 to EOF + # (must be decoded so we know where the extension data starts) + 8 => { Name => 'ImageLength', Format => 'var_ue7' }, +); + +%Image::ExifTool::BPG::Extensions = ( + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + VARS => { ALPHA_FIRST => 1 }, + 1 => { + Name => 'EXIF', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + }, + }, + 2 => { + Name => 'ICC_Profile', + SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' }, + }, + 3 => { + Name => 'XMP', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, + 4 => { + Name => 'ThumbnailBPG', + Binary => 1, + }, + 5 => { + Name => 'AnimationControl', + Binary => 1, + Unknown => 1, + }, +); + +#------------------------------------------------------------------------------ +# Get ue7 integer from binary data (max 32 bits) +# Inputs: 0) data ref, 1) location in data (undef for 0) +# Returns: 0) ue7 as integer or undef on error, 1) length of ue7 in bytes +sub Get_ue7($;$) +{ + my $dataPt = shift; + my $pos = shift || 0; + my $size = length $$dataPt; + my $val = 0; + my $i; + for ($i=0; ; ) { + return() if $pos+$i >= $size or $i >= 5; + my $byte = Get8u($dataPt, $pos + $i); + $val = ($val << 7) | ($byte & 0x7f); + unless ($byte & 0x80) { + return() if $i == 4 and $byte & 0x70; # error if bits 32-34 are set + last; # this was the last byte + } + return() if $i == 0 and $byte == 0x80; # error if first byte is 0x80 + ++$i; # step to the next byte + } + return($val, $i+1); +} + +#------------------------------------------------------------------------------ +# Extract EXIF information from a BPG image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid BPG file +sub ProcessBPG($$) +{ + local $_; + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $size, $n, $len, $pos); + + # verify this is a valid BPG file + return 0 unless $raf->Read($buff, 21) == 21; # (21 bytes is maximum header length) + return 0 unless $buff =~ /^BPG\xfb/; + $et->SetFileType(); # set the FileType tag + + SetByteOrder('MM'); + my %dirInfo = ( + DataPt => \$buff, + DirStart => 0, + DirLen => length($buff), + VarFormatData => [ ], + ); + $et->ProcessDirectory(\%dirInfo, GetTagTable('Image::ExifTool::BPG::Main')); + + return 1 unless $$et{VALUE}{Flags} & 0x0008; # all done unless extension flag is set + + # add varSize from last entry in VarFormatData to determine + # the current read position in the file + my $dataPos = 9 + $dirInfo{VarFormatData}[-1][1]; + # read extension length + unless ($raf->Seek($dataPos, 0) and $raf->Read($buff, 5) == 5) { + $et->Warn('Missing BPG extension data'); + return 1; + } + ($size, $n) = Get_ue7(\$buff); + defined $size or $et->Warn('Corrupted BPG extension length'), return 1; + $dataPos += $n; + $size > 10000000 and $et->Warn('BPG extension is too large'), return 1; + unless ($raf->Seek($dataPos, 0) and $raf->Read($buff, $size) == $size) { + $et->Warn('Truncated BPG extension'); + return 1; + } + my $tagTablePtr = GetTagTable('Image::ExifTool::BPG::Extensions'); + # loop through the individual extensions + for ($pos=0; $pos<$size; $pos+=$len) { + my $type = Get8u(\$buff, $pos); + # get length of this extension + ($len, $n) = Get_ue7(\$buff, ++$pos); + defined $len or $et->Warn('Corrupted BPG extension'), last; + $pos += $n; # point to start of data for this extension + $pos + $len > $size and $et->Warn('Invalid BPG extension size'), last; + $$tagTablePtr{$type} or $et->Warn("Unrecognized BPG extension $type ($len bytes)", 1), next; + # libbpg (in my opinion) incorrectly copies the padding byte after the + # "EXIF\0" APP1 header to the start of the BPG EXIF extension, so issue a + # minor warning and ignore the padding if we find it before the TIFF header + if ($type == 1 and $len > 3 and substr($buff,$pos,3)=~/^.(II|MM)/s) { + $et->Warn("Ignored extra byte at start of EXIF extension", 1); + ++$pos; + --$len; + } + $et->HandleTag($tagTablePtr, $type, undef, + DataPt => \$buff, + DataPos => $dataPos, + Start => $pos, + Size => $len, + Parent => 'BPG', + ); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::BPG - Read BPG meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read BPG +(Better Portable Graphics) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 + +=back + +=head1 SEE ALSO + +L, +L + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/BZZ.pm b/ExifTool/lib/Image/ExifTool/BZZ.pm new file mode 100644 index 0000000..59f8c61 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/BZZ.pm @@ -0,0 +1,472 @@ +#------------------------------------------------------------------------------ +# File: BZZ.pm +# +# Description: Utility to decode BZZ compressed data +# +# Revisions: 09/22/2008 - P. Harvey Created +# +# References: 1) http://djvu.sourceforge.net/ +# 2) http://www.djvu.org/ +# +# Notes: This code based on ZPCodec and BSByteStream of DjVuLibre 3.5.21 +# (see NOTES documentation below for license/copyright details) +#------------------------------------------------------------------------------ + +package Image::ExifTool::BZZ; + +use strict; +use integer; # IMPORTANT!! use integer arithmetic throughout +require Exporter; +use vars qw($VERSION @ISA @EXPORT_OK); + +$VERSION = '1.00'; +@ISA = qw(Exporter); +@EXPORT_OK = qw(Decode); + +# constants +sub FREQMAX { 4 } +sub CTXIDS { 3 } +sub MAXBLOCK { 4096 } + +# This table has been designed for the ZPCoder +# by running the following command in file 'zptable.sn': +# (fast-crude (steady-mat 0.0035 0.0002) 260))) +my @default_ztable_p = ( + 0x8000, 0x8000, 0x8000, 0x6bbd, 0x6bbd, 0x5d45, 0x5d45, 0x51b9, 0x51b9, 0x4813, + 0x4813, 0x3fd5, 0x3fd5, 0x38b1, 0x38b1, 0x3275, 0x3275, 0x2cfd, 0x2cfd, 0x2825, + 0x2825, 0x23ab, 0x23ab, 0x1f87, 0x1f87, 0x1bbb, 0x1bbb, 0x1845, 0x1845, 0x1523, + 0x1523, 0x1253, 0x1253, 0x0fcf, 0x0fcf, 0x0d95, 0x0d95, 0x0b9d, 0x0b9d, 0x09e3, + 0x09e3, 0x0861, 0x0861, 0x0711, 0x0711, 0x05f1, 0x05f1, 0x04f9, 0x04f9, 0x0425, + 0x0425, 0x0371, 0x0371, 0x02d9, 0x02d9, 0x0259, 0x0259, 0x01ed, 0x01ed, 0x0193, + 0x0193, 0x0149, 0x0149, 0x010b, 0x010b, 0x00d5, 0x00d5, 0x00a5, 0x00a5, 0x007b, + 0x007b, 0x0057, 0x0057, 0x003b, 0x003b, 0x0023, 0x0023, 0x0013, 0x0013, 0x0007, + 0x0007, 0x0001, 0x0001, 0x5695, 0x24ee, 0x8000, 0x0d30, 0x481a, 0x0481, 0x3579, + 0x017a, 0x24ef, 0x007b, 0x1978, 0x0028, 0x10ca, 0x000d, 0x0b5d, 0x0034, 0x078a, + 0x00a0, 0x050f, 0x0117, 0x0358, 0x01ea, 0x0234, 0x0144, 0x0173, 0x0234, 0x00f5, + 0x0353, 0x00a1, 0x05c5, 0x011a, 0x03cf, 0x01aa, 0x0285, 0x0286, 0x01ab, 0x03d3, + 0x011a, 0x05c5, 0x00ba, 0x08ad, 0x007a, 0x0ccc, 0x01eb, 0x1302, 0x02e6, 0x1b81, + 0x045e, 0x24ef, 0x0690, 0x2865, 0x09de, 0x3987, 0x0dc8, 0x2c99, 0x10ca, 0x3b5f, + 0x0b5d, 0x5695, 0x078a, 0x8000, 0x050f, 0x24ee, 0x0358, 0x0d30, 0x0234, 0x0481, + 0x0173, 0x017a, 0x00f5, 0x007b, 0x00a1, 0x0028, 0x011a, 0x000d, 0x01aa, 0x0034, + 0x0286, 0x00a0, 0x03d3, 0x0117, 0x05c5, 0x01ea, 0x08ad, 0x0144, 0x0ccc, 0x0234, + 0x1302, 0x0353, 0x1b81, 0x05c5, 0x24ef, 0x03cf, 0x2b74, 0x0285, 0x201d, 0x01ab, + 0x1715, 0x011a, 0x0fb7, 0x00ba, 0x0a67, 0x01eb, 0x06e7, 0x02e6, 0x0496, 0x045e, + 0x030d, 0x0690, 0x0206, 0x09de, 0x0155, 0x0dc8, 0x00e1, 0x2b74, 0x0094, 0x201d, + 0x0188, 0x1715, 0x0252, 0x0fb7, 0x0383, 0x0a67, 0x0547, 0x06e7, 0x07e2, 0x0496, + 0x0bc0, 0x030d, 0x1178, 0x0206, 0x19da, 0x0155, 0x24ef, 0x00e1, 0x320e, 0x0094, + 0x432a, 0x0188, 0x447d, 0x0252, 0x5ece, 0x0383, 0x8000, 0x0547, 0x481a, 0x07e2, + 0x3579, 0x0bc0, 0x24ef, 0x1178, 0x1978, 0x19da, 0x2865, 0x24ef, 0x3987, 0x320e, + 0x2c99, 0x432a, 0x3b5f, 0x447d, 0x5695, 0x5ece, 0x8000, 0x8000, 0x5695, 0x481a, + 0x481a, 0, 0, 0, 0, 0 +); +my @default_ztable_m = ( + 0x0000, 0x0000, 0x0000, 0x10a5, 0x10a5, 0x1f28, 0x1f28, 0x2bd3, 0x2bd3, 0x36e3, + 0x36e3, 0x408c, 0x408c, 0x48fd, 0x48fd, 0x505d, 0x505d, 0x56d0, 0x56d0, 0x5c71, + 0x5c71, 0x615b, 0x615b, 0x65a5, 0x65a5, 0x6962, 0x6962, 0x6ca2, 0x6ca2, 0x6f74, + 0x6f74, 0x71e6, 0x71e6, 0x7404, 0x7404, 0x75d6, 0x75d6, 0x7768, 0x7768, 0x78c2, + 0x78c2, 0x79ea, 0x79ea, 0x7ae7, 0x7ae7, 0x7bbe, 0x7bbe, 0x7c75, 0x7c75, 0x7d0f, + 0x7d0f, 0x7d91, 0x7d91, 0x7dfe, 0x7dfe, 0x7e5a, 0x7e5a, 0x7ea6, 0x7ea6, 0x7ee6, + 0x7ee6, 0x7f1a, 0x7f1a, 0x7f45, 0x7f45, 0x7f6b, 0x7f6b, 0x7f8d, 0x7f8d, 0x7faa, + 0x7faa, 0x7fc3, 0x7fc3, 0x7fd7, 0x7fd7, 0x7fe7, 0x7fe7, 0x7ff2, 0x7ff2, 0x7ffa, + 0x7ffa, 0x7fff, 0x7fff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +); +my @default_ztable_up = ( + 84, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + 82, 81, 82, 9, 86, 5, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 82, 99, 76, 101, 70, 103, 66, 105, 106, 107, 66, 109, 60, 111, 56, 69, + 114, 65, 116, 61, 118, 57, 120, 53, 122, 49, 124, 43, 72, 39, 60, 33, + 56, 29, 52, 23, 48, 23, 42, 137, 38, 21, 140, 15, 142, 9, 144, 141, + 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 70, 157, 66, 81, 62, 75, + 58, 69, 54, 65, 50, 167, 44, 65, 40, 59, 34, 55, 30, 175, 24, 177, + 178, 179, 180, 181, 182, 183, 184, 69, 186, 59, 188, 55, 190, 51, 192, 47, + 194, 41, 196, 37, 198, 199, 72, 201, 62, 203, 58, 205, 54, 207, 50, 209, + 46, 211, 40, 213, 36, 215, 30, 217, 26, 219, 20, 71, 14, 61, 14, 57, + 8, 53, 228, 49, 230, 45, 232, 39, 234, 35, 138, 29, 24, 25, 240, 19, + 22, 13, 16, 13, 10, 7, 244, 249, 10, 89, 230, 0, 0, 0, 0, 0 +); +my @default_ztable_dn = ( + 145, 4, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, 85, 226, 6, 176, 143, 138, 141, 112, 135, 104, 133, 100, 129, + 98, 127, 72, 125, 102, 123, 60, 121, 110, 119, 108, 117, 54, 115, 48, 113, + 134, 59, 132, 55, 130, 51, 128, 47, 126, 41, 62, 37, 66, 31, 54, 25, + 50, 131, 46, 17, 40, 15, 136, 7, 32, 139, 172, 9, 170, 85, 168, 248, + 166, 247, 164, 197, 162, 95, 160, 173, 158, 165, 156, 161, 60, 159, 56, 71, + 52, 163, 48, 59, 42, 171, 38, 169, 32, 53, 26, 47, 174, 193, 18, 191, + 222, 189, 218, 187, 216, 185, 214, 61, 212, 53, 210, 49, 208, 45, 206, 39, + 204, 195, 202, 31, 200, 243, 64, 239, 56, 237, 52, 235, 48, 233, 44, 231, + 38, 229, 34, 227, 28, 225, 22, 223, 16, 221, 220, 63, 8, 55, 224, 51, + 2, 47, 87, 43, 246, 37, 244, 33, 238, 27, 236, 21, 16, 15, 8, 241, + 242, 7, 10, 245, 2, 1, 83, 250, 2, 143, 246, 0, 0, 0, 0, 0 +); + +#------------------------------------------------------------------------------ +# New - create new BZZ object +# Inputs: 0) reference to BZZ object or BZZ class name +# Returns: blessed BZZ object ref +sub new +{ + local $_; + my $that = shift; + my $class = ref($that) || $that || 'Image::ExifTool::BZZ'; + return bless {}, $class; +} + +#------------------------------------------------------------------------------ +# Initialize BZZ object +# Inputs: 0) BZZ object ref, 1) data ref, 2) true for DjVu compatibility +sub Init($$) +{ + my ($self, $dataPt, $djvucompat) = @_; + # Create machine independent ffz table + my $ffzt = $$self{ffzt} = [ ]; + my ($i, $j); + for ($i=0; $i<256; $i++) { + $$ffzt[$i] = 0; + for ($j=$i; $j&0x80; $j<<=1) { + $$ffzt[$i] += 1; + } + } + # Initialize table + $$self{p} = [ @default_ztable_p ]; + $$self{'m'} = [ @default_ztable_m ]; + $$self{up} = [ @default_ztable_up ]; + $$self{dn} = [ @default_ztable_dn ]; + # Patch table (and lose DjVu compatibility) + unless ($djvucompat) { + my ($p, $m, $dn) = ($$self{p}, $$self{'m'}, $$self{dn}); + for ($j=0; $j<256; $j++) { + my $a = (0x10000 - $$p[$j]) & 0xffff; + while ($a >= 0x8000) { $a = ($a<<1) & 0xffff } + if ($$m[$j]>0 && $a+$$p[$j]>=0x8000 && $a>=$$m[$j]) { + $$dn[$j] = $default_ztable_dn[$default_ztable_dn[$j]]; + } + } + } + $$self{ctx} = [ (0) x 300 ]; + $$self{DataPt} = $dataPt; + $$self{Pos} = 0; + $$self{DataLen} = length $$dataPt; + $$self{a} = 0; + $$self{buffer} = 0; + $$self{fence} = 0; + $$self{blocksize} = 0; + # Read first 16 bits of code + if (length($$dataPt) >= 2) { + $$self{code} = unpack('n', $$dataPt); + $$self{Pos} += 2; + } elsif (length($$dataPt) >= 1) { + $$self{code} = (unpack('C', $$dataPt) << 8) | 0xff; + $$self{Pos}++; + } else { + $$self{code} = 0xffff; + } + $$self{byte} = $$self{code} & 0xff; + # Preload buffer + $$self{delay} = 25; + $$self{scount} = 0; + # Compute initial fence + $$self{fence} = $$self{code} >= 0x8000 ? 0x7fff : $$self{code}; +} + +#------------------------------------------------------------------------------ +# Decode data block +# Inputs: 0) optional BZZ object ref, 1) optional data ref +# Returns: decoded data or undefined on error +# Notes: If called without a data ref, an input BZZ object ref must be given and +# the BZZ object must have been initialized by a previous call to Init() +sub Decode($;$) +{ + # Decode input stream + local $_; + my $self; + if (ref $_[0] and UNIVERSAL::isa($_[0],'Image::ExifTool::BZZ')) { + $self = shift; + } else { + $self = new Image::ExifTool::BZZ; + } + my $dataPt = shift; + if ($dataPt) { + $self->Init($dataPt, 1); + } else { + $dataPt = $$self{DataPt} or return undef; + } + # Decode block size + my $n = 1; + my $m = (1 << 24); + while ($n < $m) { + my $b = $self->decode_sub(0x8000 + ($$self{a}>>1)); + $n = ($n<<1) | $b; + } + $$self{size} = $n - $m; + + return '' unless $$self{size}; + return undef if $$self{size} > MAXBLOCK()*1024; + # Allocate + if ($$self{blocksize} < $$self{size}) { + $$self{blocksize} = $$self{size}; + } + # Decode Estimation Speed + my $fshift = 0; + if ($self->decode_sub(0x8000 + ($$self{a}>>1))) { + $fshift += 1; + $fshift += 1 if $self->decode_sub(0x8000 + ($$self{a}>>1)); + } + # Prepare Quasi MTF + my @mtf = (0..255); + my @freq = (0) x FREQMAX(); + my $fadd = 4; + # Decode + my $mtfno = 3; + my $markerpos = -1; + my $cx = $$self{ctx}; + my ($i, @dat); +byte: for ($i=0; $i<$$self{size}; $i++) { + # dummy loop avoids use of "goto" statement +dummy: for (;;) { + my $ctxid = CTXIDS() - 1; + $ctxid = $mtfno if $ctxid > $mtfno; + my $cp = 0; + my ($imtf, $bits); + for ($imtf=0; $imtf<2; ++$imtf) { + if ($self->decoder($$cx[$cp+$ctxid])) { + $mtfno = $imtf; + $dat[$i] = $mtf[$mtfno]; + # (a "goto" here could give a segfault due to a Perl bug) + last dummy; # do rotation + } + $cp += CTXIDS(); + } + for ($bits=1; $bits<8; ++$bits, $imtf<<=1) { + if ($self->decoder($$cx[$cp])) { + my $n = 1; + my $m = (1 << $bits); + while ($n < $m) { + my $b = $self->decoder($$cx[$cp+$n]); + $n = ($n<<1) | $b; + } + $mtfno = $imtf + $n - $m; + $dat[$i] = $mtf[$mtfno]; + last dummy; # do rotation + } + $cp += $imtf; + } + $mtfno=256; + $dat[$i] = 0; + $markerpos=$i; + next byte; # no rotation necessary + } + # Rotate mtf according to empirical frequencies (new!) + # Adjust frequencies for overflow + $fadd = $fadd + ($fadd >> $fshift); + if ($fadd > 0x10000000) { + $fadd >>= 24; + $_ >>= 24 foreach @freq; + } + # Relocate new char according to new freq + my $fc = $fadd; + $fc += $freq[$mtfno] if $mtfno < FREQMAX(); + my $k; + for ($k=$mtfno; $k>=FREQMAX(); $k--) { + $mtf[$k] = $mtf[$k-1]; + } + for (; $k>0 && $fc>=$freq[$k-1]; $k--) { + $mtf[$k] = $mtf[$k-1]; + $freq[$k] = $freq[$k-1]; + } + $mtf[$k] = $dat[$i]; + $freq[$k] = $fc; + # when "goto" was used, Perl 5.8.6 could segfault here + # unless "next" was explicitly stated + } +# +# Reconstruct the string +# + return undef if $markerpos<1 || $markerpos>=$$self{size}; + # Allocate pointers + # Prepare count buffer + my @count = (0) x 256; + my @posn; + # Fill count buffer + no integer; + for ($i=0; $i<$markerpos; $i++) { + my $c = $dat[$i]; + $posn[$i] = ($c<<24) | ($count[$c]++ & 0xffffff); + } + $posn[$i++] = 0; # (initialize marker entry just to be safe) + for ( ; $i<$$self{size}; $i++) { + my $c = $dat[$i]; + $posn[$i] = ($c<<24) | ($count[$c]++ & 0xffffff); + } + use integer; + # Compute sorted char positions + my $last = 1; + for ($i=0; $i<256; $i++) { + my $tmp = $count[$i]; + $count[$i] = $last; + $last += $tmp; + } + # Undo the sort transform + $i = 0; + $last = $$self{size}-1; + while ($last > 0) { + my $n = $posn[$i]; + no integer; + my $c = $n >> 24; + use integer; + $dat[--$last] = $c; + $i = $count[$c] + ($n & 0xffffff); + } + # Final check and return decoded data + return undef if $i != $markerpos; + pop @dat; # (last byte isn't real) + return pack 'C*', @dat; +} + +#------------------------------------------------------------------------------ +# Inputs: 0) BZZ object ref, 1) ctx +# Returns: decoded bit +sub decoder($$) +{ + my ($self, $ctx) = @_; + my $z = $$self{a} + $self->{p}[$ctx]; + if ($z <= $$self{fence}) { + $$self{a} = $z; + return ($ctx & 1); + } + # must pass $_[1] so subroutine can modify value (darned C++ pass-by-reference!) + return $self->decode_sub($z, $_[1]); +} + +#------------------------------------------------------------------------------ +# Inputs: 0) BZZ object ref, 1) z, 2) ctx (or undef) +# Returns: decoded bit +sub decode_sub($$;$) +{ + my ($self, $z, $ctx) = @_; + + # ensure that we have at least 16 bits of encoded data available + if ($$self{scount} < 16) { + # preload byte by byte until we have at least 24 bits + while ($$self{scount} <= 24) { + if ($$self{Pos} < $$self{DataLen}) { + $$self{byte} = ord(substr(${$$self{DataPt}}, $$self{Pos}, 1)); + ++$$self{Pos}; + } else { + $$self{byte} = 0xff; + if (--$$self{delay} < 1) { + # setting size to zero forces error return from Decode() + $$self{size} = 0; + return 0; + } + } + $$self{buffer} = ($$self{buffer}<<8) | $$self{byte}; + $$self{scount} += 8; + } + } + # Save bit + my $a = $$self{a}; + my ($bit, $code); + if (defined $ctx) { + $bit = ($ctx & 1); + # Avoid interval reversion + my $d = 0x6000 + (($z+$a)>>2); + $z = $d if $z > $d; + } else { + $bit = 0; + } + # Test MPS/LPS + if ($z > ($code = $$self{code})) { + $bit ^= 1; + # LPS branch + $z = 0x10000 - $z; + $a += $z; + $code += $z; + # LPS adaptation + $_[2] = $self->{dn}[$ctx] if defined $ctx; + # LPS renormalization + my $sft = $a>=0xff00 ? $self->{ffzt}[$a&0xff] + 8 : $self->{ffzt}[($a>>8)&0xff]; + $$self{scount} -= $sft; + $$self{a} = ($a<<$sft) & 0xffff; + $code = (($code<<$sft) & 0xffff) | (($$self{buffer}>>$$self{scount}) & ((1<<$sft)-1)); + } else { + # MPS adaptation + $_[2] = $self->{up}[$ctx] if defined $ctx and $a >= $self->{'m'}[$ctx]; + # MPS renormalization + $$self{scount} -= 1; + $$self{a} = ($z<<1) & 0xffff; + $code = (($code<<1) & 0xffff) | (($$self{buffer}>>$$self{scount}) & 1); + } + # Adjust fence and save new code + $$self{fence} = $code >= 0x8000 ? 0x7fff : $code; + $$self{code} = $code; + return $bit; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::BZZ - Utility to decode BZZ compressed data + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to decode BZZ +compressed data in DjVu images. + +=head1 NOTES + +This code is based on ZPCodec and BSByteStream of DjVuLibre 3.5.21 (see +additional copyrights and the first reference below), which are covered +under the GNU GPL license. + +This is implemented as Image::ExifTool::BZZ instead of Compress::BZZ because +I am hoping that someone else will write a proper Compress::BZZ module (with +compression ability). + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) +Copyright 2002, Leon Bottou and Yann Le Cun +Copyright 2001, AT&T +Copyright 1999-2001, LizardTech Inc. + +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 + +=item L + +=back + +=head1 SEE ALSO + +L, +L + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/BigTIFF.pm b/ExifTool/lib/Image/ExifTool/BigTIFF.pm new file mode 100644 index 0000000..11c432c --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/BigTIFF.pm @@ -0,0 +1,304 @@ +#------------------------------------------------------------------------------ +# File: BigTIFF.pm +# +# Description: Read Big TIFF meta information +# +# Revisions: 07/03/2007 - P. Harvey Created +# +# References: 1) http://www.awaresystems.be/imaging/tiff/bigtiff.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::BigTIFF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.08'; + +my $maxOffset = 0x7fffffff; # currently supported maximum data offset/size + +#------------------------------------------------------------------------------ +# Process Big IFD directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessBigIFD($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $$et{OPTIONS}{Verbose}; + my $htmlDump = $$et{HTML_DUMP}; + my $dirName = $$dirInfo{DirName}; + my $dirStart = $$dirInfo{DirStart}; + my ($offName, $nextOffName); + + if ($htmlDump) { + $verbose = -1; # mix htmlDump into verbose so we can test for both at once + $offName = $$dirInfo{OffsetName}; + } + + # loop through IFD chain + for (;;) { + if ($dirStart > $maxOffset and not $et->Options('LargeFileSupport')) { + $et->Warn('Huge offsets not supported (LargeFileSupport not set)'); + last; + } + unless ($raf->Seek($dirStart, 0)) { + $et->Warn("Bad $dirName offset"); + return 0; + } + my ($dirBuff, $index); + unless ($raf->Read($dirBuff, 8) == 8) { + $et->Warn("Truncated $dirName count"); + return 0; + } + my $numEntries = Image::ExifTool::Get64u(\$dirBuff, 0); + $verbose > 0 and $et->VerboseDir($dirName, $numEntries); + my $bsize = $numEntries * 20; + if ($bsize > $maxOffset) { + $et->Warn('Huge directory counts not yet supported'); + last; + } + my $bufPos = $raf->Tell(); + unless ($raf->Read($dirBuff, $bsize) == $bsize) { + $et->Warn("Truncated $dirName directory"); + return 0; + } + my $nextIFD; + $raf->Read($nextIFD, 8) == 8 or undef $nextIFD; # try to read next IFD pointer + if ($htmlDump) { + $et->HDump($bufPos-8, 8, "$dirName entries", "Entry count: $numEntries", undef, $offName); + if (defined $nextIFD) { + my $off = Image::ExifTool::Get64u(\$nextIFD, 0); + my $tip = sprintf("Offset: 0x%.8x", $off); + my $id = $offName; + ($nextOffName, $id) = Image::ExifTool::Exif::NextOffsetName($et, $id) if $off; + $et->HDump($bufPos + 20 * $numEntries, 8, "Next IFD", $tip, 0, $id); + } + } + # loop through all entries in this BigTIFF IFD + for ($index=0; $index<$numEntries; ++$index) { + my $entry = 20 * $index; + my $tagID = Get16u(\$dirBuff, $entry); + my $format = Get16u(\$dirBuff, $entry+2); + my $count = Image::ExifTool::Get64u(\$dirBuff, $entry+4); + my $formatSize = $Image::ExifTool::Exif::formatSize[$format]; + unless (defined $formatSize) { + $et->HDump($bufPos+$entry,20,"[invalid IFD entry]", + "Bad format value: $format", 1, $offName); + # warn unless the IFD was just padded with zeros + $et->Warn(sprintf("Unknown format ($format) for $dirName tag 0x%x",$tagID)); + return 0; # assume corrupted IFD + } + my $formatStr = $Image::ExifTool::Exif::formatName[$format]; + my $size = $count * $formatSize; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID); + next unless defined $tagInfo or $verbose; + my $valuePtr = $entry + 12; + my ($valBuff, $valBase, $rational, $subOffName); + if ($size > 8) { + if ($size > $maxOffset) { + $et->Warn("Can't handle $dirName entry $index (huge size)"); + next; + } + $valuePtr = Image::ExifTool::Get64u(\$dirBuff, $valuePtr); + if ($valuePtr > $maxOffset and not $et->Options('LargeFileSupport')) { + $et->Warn("Can't handle $dirName entry $index (LargeFileSupport not set)"); + next; + } + unless ($raf->Seek($valuePtr, 0) and $raf->Read($valBuff, $size) == $size) { + $et->Warn("Error reading $dirName entry $index"); + next; + } + $valBase = 0; + } else { + $valBuff = substr($dirBuff, $valuePtr, $size); + $valBase = $bufPos; + } + if (defined $tagInfo and not $tagInfo) { + # GetTagInfo() required the value for a Condition + $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID, \$valBuff); + } + my $val = ReadValue(\$valBuff, 0, $formatStr, $count, $size, \$rational); + if ($htmlDump) { + my $tval = $val; + # show numerator/denominator separately for rational numbers + $tval .= " ($rational)" if defined $rational; + my ($tagName, $colName); + if ($tagID == 0x927c and $dirName eq 'ExifIFD') { + $tagName = 'MakerNotes'; + } elsif ($tagInfo) { + $tagName = $$tagInfo{Name}; + } else { + $tagName = sprintf("Tag 0x%.4x",$tagID); + } + my $dname = sprintf("$dirName-%.2d", $index); + # build our tool tip + my $tip = sprintf("Tag ID: 0x%.4x\n", $tagID) . + "Format: $formatStr\[$count]\nSize: $size bytes\n"; + if ($size > 8) { + $tip .= sprintf("Value offset: 0x%.8x\n", $valuePtr); + $colName = "$tagName"; + } else { + $colName = $tagName; + } + $tval = substr($tval,0,28) . '[...]' if length($tval) > 32; + if ($formatStr =~ /^(string|undef|binary)/) { + # translate non-printable characters + $tval =~ tr/\x00-\x1f\x7f-\xff/./; + } elsif ($tagInfo and Image::ExifTool::IsInt($tval)) { + if ($$tagInfo{IsOffset}) { + $tval = sprintf('0x%.4x', $tval); + } elsif ($$tagInfo{PrintHex}) { + $tval = sprintf('0x%x', $tval); + } + } + $tip .= "Value: $tval"; + my ($id, $sid); + if ($tagInfo and $$tagInfo{SubIFD}) { + ($subOffName, $id, $sid) = Image::ExifTool::Exif::NextOffsetName($et, $offName); + } else { + $id = $offName; + } + $et->HDump($entry+$bufPos, 20, "$dname $colName", $tip, 1, $id); + if ($size > 8) { + # add value data block + my $flg = ($tagInfo and $$tagInfo{SubDirectory} and $$tagInfo{MakerNotes}) ? 4 : 0; + $et->HDump($valuePtr,$size,"$tagName value",'SAME', $flg, $sid); + } + } + if ($tagInfo and $$tagInfo{SubIFD}) { + # process all SubIFD's as BigTIFF + $verbose > 0 and $et->VerboseInfo($tagID, $tagInfo, + Table => $tagTablePtr, + Index => $index, + Value => $val, + DataPt => \$valBuff, + DataPos => $valBase + $valuePtr, + Start => 0, + Size => $size, + Format => $formatStr, + Count => $count, + ); + my @offsets = split ' ', $val; + my $i; + for ($i=0; $i $raf, + DataPos => 0, + DirStart => $offsets[$i], + DirName => $subdirName, + Parent => $dirName, + OffsetName => $subOffName, + ); + $et->ProcessDirectory(\%subdirInfo, $tagTablePtr, \&ProcessBigIFD); + } + } else { + my $tagKey = $et->HandleTag($tagTablePtr, $tagID, $val, + Index => $index, + DataPt => \$valBuff, + DataPos => $valBase + $valuePtr, + Start => 0, + Size => $size, + Format => $formatStr, + TagInfo => $tagInfo, + RAF => $raf, + ); + $tagKey and $et->SetGroup($tagKey, $dirName); + } + } + last unless $dirName =~ /^(IFD|SubIFD)(\d*)$/; + $dirName = $1 . (($2 || 0) + 1); + defined $nextIFD or $et->Warn("Bad $dirName pointer"), return 0; + $dirStart = Image::ExifTool::Get64u(\$nextIFD, 0); + $dirStart or last; + $offName = $nextOffName; + # protect against infinite loop + if ($$et{PROCESSED}{$dirStart}) { + $et->Warn("$dirName pointer references previous $$et{PROCESSED}{$dirStart} directory"); + last; + } else { + $$et{PROCESSED}{$dirStart} = $dirName; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract meta information from a BigTIFF image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid BigTIFF image +sub ProcessBTF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + + return 0 unless $raf->Read($buff, 16) == 16; + return 0 unless $buff =~ /^(MM\0\x2b\0\x08\0\0|II\x2b\0\x08\0\0\0)/; + if ($$dirInfo{OutFile}) { + $et->Error('ExifTool does not support writing of BigTIFF images'); + return 1; + } + $et->SetFileType('BTF'); # set the FileType tag + SetByteOrder(substr($buff, 0, 2)); + my $offset = Image::ExifTool::Get64u(\$buff, 8); + if ($$et{HTML_DUMP}) { + my $o = (GetByteOrder() eq 'II') ? 'Little' : 'Big'; + $et->HDump(0, 8, "BigTIFF header", "Byte order: $o endian", 0); + $et->HDump(8, 8, "IFD0 pointer", sprintf("Offset: 0x%.8x",$offset), 0); + } + my %dirInfo = ( + RAF => $raf, + DataPos => 0, + DirStart => $offset, + DirName => 'IFD0', + Parent => 'BigTIFF', + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr, \&ProcessBigIFD); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::BigTIFF - Read Big TIFF meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read meta +information in BigTIFF images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 + +=back + +=head1 SEE ALSO + +L, +L + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/BuildTagLookup.pm b/ExifTool/lib/Image/ExifTool/BuildTagLookup.pm new file mode 100644 index 0000000..c042343 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/BuildTagLookup.pm @@ -0,0 +1,2793 @@ +#------------------------------------------------------------------------------ +# File: BuildTagLookup.pm +# +# Description: Utility to build tag lookup tables in Image::ExifTool::TagLookup.pm +# +# Revisions: 12/31/2004 - P. Harvey Created +# 02/15/2005 - PH Added ability to generate TagNames documentation +# +# Notes: Documentation for the tag tables may either be placed in the +# %docs hash below or in a NOTES entry in the table itself, and +# individual tags may have their own Notes entry. +#------------------------------------------------------------------------------ + +package Image::ExifTool::BuildTagLookup; + +use strict; +require Exporter; + +BEGIN { + # prevent ExifTool from loading the user config file + $Image::ExifTool::configFile = ''; + $Image::ExifTool::debug = 1; # enabled debug messages +} + +use vars qw($VERSION @ISA); +use Image::ExifTool qw(:Utils :Vars); +use Image::ExifTool::Exif; +use Image::ExifTool::Shortcuts; +use Image::ExifTool::HTML qw(EscapeHTML); +use Image::ExifTool::IPTC; +use Image::ExifTool::XMP; +use Image::ExifTool::Canon; +use Image::ExifTool::Nikon; +use Image::ExifTool::Sony; +use Image::ExifTool::Validate; +use Image::ExifTool::MacOS; + +$VERSION = '3.52'; +@ISA = qw(Exporter); + +sub NumbersFirst($$); +sub SortedTagTablekeys($); + +my $createDate = 'Feb 15, 2005'; + +# global variables to control sorting order of table entries +my $numbersFirst = 1; # set to -1 to sort numbers last, or 2 to put negative numbers last +my $caseInsensitive; # flag to ignore case when sorting tag names + +# list of all tables in plug-in modules +my @pluginTables = ('Image::ExifTool::MWG::Composite'); + +# colors for html pages +my $noteFont = ""; +my $noteFontSmall = ""; + +my $docType = q{ +}; + +my $homePage = 'https://exiftool.org'; + +# tweak the ordering of tables in the documentation +my %tweakOrder = ( + # this => comes after this + # ------- ----------------- + JPEG => '-', # JPEG comes first + IPTC => 'Exif', # put IPTC after EXIF, + GPS => 'XMP', # etc... + Composite => 'Extra', + CBOR => 'JSON', + GeoTiff => 'GPS', + CanonVRD=> 'CanonCustom', + DJI => 'Casio', + FLIR => 'DJI', + FujiFilm => 'FLIR', + Kodak => 'JVC', + Leaf => 'Kodak', + Minolta => 'Leaf', + Motorola => 'Minolta', + Nikon => 'Motorola', + NikonCustom => 'Nikon', + NikonCapture => 'NikonCustom', + Nintendo => 'NikonCapture', + Pentax => 'Panasonic', + SonyIDC => 'Sony', + Unknown => 'SonyIDC', + DNG => 'Unknown', + PrintIM => 'ICC_Profile', + Vorbis => 'Ogg', + ID3 => 'PostScript', + MinoltaRaw => 'KyoceraRaw', + KyoceraRaw => 'CanonRaw', + SigmaRaw => 'PanasonicRaw', + Lytro => 'SigmaRaw', + PhotoMechanic => 'FotoStation', + Microsoft => 'PhotoMechanic', + 'Microsoft::MP'=> 'Microsoft::MP1', + GIMP => 'Microsoft', + 'Nikon::CameraSettingsD300' => 'Nikon::ShotInfoD300b', + 'Pentax::LensData' => 'Pentax::LensInfo2', + 'Sony::SRF2' => 'Sony::SRF', + DarwinCore => 'AFCP', + 'MWG::Regions' => 'MWG::Composite', + 'MWG::Keywords' => 'MWG::Regions', + 'MWG::Collections' => 'MWG::Keywords', + 'GoPro::fdsc' => 'GoPro::KBAT', +); + +# list of all recognized Format strings +# (not a complete list, but this is all we use so far) +# (also, formats like "var_X[num]" are allowed for any valid X) +my %formatOK = ( + %Image::ExifTool::Exif::formatNumber, + 0 => 1, + 1 => 1, + 2 => 1, # (writable for docs only) + real => 1, + integer => 1, + date => 1, + boolean => 1, + rational => 1, + 'lang-alt' => 1, + fixed16u => 1, + fixed16s => 1, + fixed32u => 1, + fixed32s => 1, + extended => 1, + resize => 1, + digits => 1, + int16uRev => 1, + int32uRev => 1, + rational32u => 1, + rational32s => 1, + pstring => 1, + var_string => 1, + var_int16u => 1, + var_pstr32 => 1, + var_ustr32 => 1, + var_ue7 => 1, # (BPG) + # Matroska + signed => 1, + unsigned => 1, + utf8 => 1, + Unicode => 1, # (Microsoft Xtra) + GUID => 1, # (Microsoft Xtra) + vt_filetime => 1, # (Microsoft Xtra) +); + +# Descriptions for the TagNames documentation +# (descriptions may also be defined in tag table NOTES) +# Note: POD headers in these descriptions start with '~' instead of '=' to keep +# from confusing POD parsers which apparently parse inside quoted strings. +my %docs = ( + PodHeader => q{ +~head1 NAME + +Image::ExifTool::TagNames - ExifTool tag name documentation + +~head1 DESCRIPTION + +This document contains a complete list of ExifTool tag names, organized into +tables based on information type. Tag names are used to reference specific +meta information extracted from or written to a file. + +~head1 TAG TABLES +}, + ExifTool => q{ +The tables listed below give the names of all tags recognized by ExifTool. +They contain a total of $$self{COUNT}{'total tags'} tags, with $$self{COUNT}{'unique tag names'} unique tag names. +}, + ExifTool2 => q{ +B, B or B is given in the first column of each +table. A B is the computer-readable equivalent of a tag name, and +is the identifier that is actually stored in the file. B refers to +the offset of a value when found at a fixed position within a data block +(B<#> is the multiplier for calculating a byte offset: B<1>, B<2>, B<4> or +B<8>). These offsets may have a decimal part which is used only to +differentiate tags with values stored at the same position. (Note that +writable tags within binary data blocks are not individually deletable, +and the usual alternative is to set them to a value of zero.) B +gives the order of values for a serial data stream. + +A B is the handle by which the information is accessed in +ExifTool. In some instances, more than one name may correspond to a single +tag ID. In these cases, the actual name used depends on the context in +which the information is found. Valid characters in a tag name are A-Z, +a-z, 0-9, hyphen (-) and underline (_). Case is not significant. A +question mark (C) after a tag name indicates that the information is +either not understood, not verified, or not very useful -- these tags are +not extracted by ExifTool unless the L (-u) option is enabled. Be +aware that some tag names are different than the descriptions printed out by +default when extracting information with exiftool. To see the tag names +instead of the descriptions, use C. + +The B column indicates whether the tag is writable by ExifTool. +Anything but a C in this column means the tag is writable. A C +indicates writable information that is either unformatted or written using +the existing format. Other expressions give details about the format of the +stored value, and vary depending on the general type of information. The +format name may be followed by a number in square brackets to indicate the +number of values written, or the number of characters in a fixed-length +string (including a null terminator which is added if required). + +A plus sign (C<+>) after an entry in the B column indicates a +I tag which supports multiple values and allows individual values to +be added and deleted. A slash (C) indicates a tag that ExifTool will +I when writing. These will be edited but not created if another +same-named tag may be created instead. To create these tags, the group +should be specified. A tilde (C<~>) indicates a tag this is writable only +when the print conversion is disabled (by setting L to 0, using the +-n option, or suffixing the tag name with a C<#> character). An exclamation +point (C) indicates a tag that is considered I to write under +normal circumstances. These tags are not written unless specified +explicitly (ie. not when wildcards or "all" are used), and care should be +taken when editing them manually since they may affect the way an image is +rendered. An asterisk (C<*>) indicates a I tag which is not +writable directly, but is written automatically by ExifTool (often when a +corresponding L or +L tag is written). A colon +(C<:>) indicates a I tag which may be added automatically when +writing. Normally MakerNotes tags may not be deleted individually, but a +caret (C<^>) indicates a I MakerNotes tag. + +The HTML version of these tables also lists possible B for +discrete-valued tags, as well as B for some tags. The B are +listed with the computer-readable values on the left of the equals sign +(C<=>), and the human-readable values on the right. The human-readable +values are used by default when reading and writing, but the +computer-readable values may be accessed by disabling the value conversion +with the -n option on the command line, by setting the L option to 0 +in the API, or or on a per-tag basis by adding a hash (C<#>) after the tag +name. + +B: If you are familiar with common meta-information tag names, you may +find that some ExifTool tag names are different than expected. The usual +reason for this is to make the tag names more consistent across different +types of meta information. To determine a tag name, either consult this +documentation or run C on a file containing the information in +question. + +I<(This documentation is the result of decades of research, testing and +reverse engineering, and is the most complete metadata tag list available +anywhere on the internet. It is provided not only for ExifTool users, but +more importantly as a public service to help augment the collective +knowledge, and is often used as a primary source of information in the +development of other metadata software. Please help keep this documentation +as accurate and complete as possible, and feed any new discoveries back to +ExifTool. A big thanks to everyone who has helped with this so far!)> +}, + EXIF => q{ +EXIF stands for "Exchangeable Image File Format". This type of information +is formatted according to the TIFF specification, and may be found in JPG, +TIFF, PNG, JP2, PGF, MIFF, HDP, PSP and XCF images, as well as many +TIFF-based RAW images, and even some AVI and MOV videos. + +The EXIF meta information is organized into different Image File Directories +(IFD's) within an image. The names of these IFD's correspond to the +ExifTool family 1 group names. When writing EXIF information, the default +B listed below is used unless another group is specified. + +Mandatory tags (indicated by a colon after the B type) may be +added automatically with default values when creating a new IFD, and the IFD +is removed automatically when deleting tags if only default-valued mandatory +tags remain. + +The table below lists all EXIF tags. Also listed are TIFF, DNG, HDP and +other tags which are not part of the EXIF specification, but may co-exist +with EXIF tags in some images. Tags which are part of the EXIF 2.32 +specification have an underlined B in the HTML version of this +documentation. See +L +for the official EXIF 2.32 specification. +}, + GPS => q{ +These GPS tags are part of the EXIF standard, and are stored in a separate +IFD within the EXIF information. + +ExifTool is very flexible about the input format when writing lat/long +coordinates, and will accept from 1 to 3 floating point numbers (for decimal +degrees, degrees and minutes, or degrees, minutes and seconds) separated by +just about anything, and will format them properly according to the EXIF +specification. + +Some GPS tags have values which are fixed-length strings. For these, the +indicated string lengths include a null terminator which is added +automatically by ExifTool. Remember that the descriptive values are used +when writing (eg. 'Above Sea Level', not '0') unless the print conversion is +disabled (with '-n' on the command line or the L option in the API, +or by suffixing the tag name with a C<#> character). + +When adding GPS information to an image, it is important to set all of the +following tags: GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef, +and GPSAltitude and GPSAltitudeRef if the altitude is known. ExifTool will +write the required GPSVersionID tag automatically if new a GPS IFD is added +to an image. +}, + XMP => q{ +XMP stands for "Extensible Metadata Platform", an XML/RDF-based metadata +format which is being pushed by Adobe. Information in this format can be +embedded in many different image file types including JPG, JP2, TIFF, GIF, +EPS, PDF, PSD, IND, INX, PNG, DJVU, SVG, PGF, MIFF, XCF, CRW, DNG and a +variety of proprietary TIFF-based RAW images, as well as MOV, AVI, ASF, WMV, +FLV, SWF and MP4 videos, and WMA and audio formats supporting ID3v2 +information. + +The XMP B's aren't listed because in most cases they are identical +to the B (aside from differences in case). Tags with different +ID's are mentioned in the B column of the HTML version of this +document. + +All XMP information is stored as character strings. The B column +specifies the information format: C is an unformatted string, +C is a string of digits (possibly beginning with a '+' or '-'), +C is a floating point number, C is entered as a floating +point number but stored as two C strings separated by a '/' +character, C is a date/time string entered in the format "YYYY:mm:dd +HH:MM:SS[.ss][+/-HH:MM]" but some partial date/time formats are also +accepted (see L), C is either +"True" or "False" (but "true" and "false" may be written as a ValueConv +value for compatibility with non-conforming applications), C +indicates a structured tag, and C is a tag that supports alternate +languages. + +When reading, C tags are extracted only if the L (-struct) +option is used. Otherwise the corresponding I tags, indicated by +an underline (C<_>) after the B type, are extracted. When +copying, by default both structured and flattened tags are available, but +the flattened tags are considered "unsafe" so they aren't copied unless +specified explicitly. The L option may be disabled by setting Struct +to 0 via the API or with --struct on the command line to copy only flattened +tags, or enabled by setting Struct to 1 via the API or with -struct on the +command line to copy only as structures. When writing, the L option +has no effect, and both structured and flattened tags may be written. See +L for more details. + +Individual languages for C tags are accessed by suffixing the tag +name with a '-', followed by an RFC 3066 language code (eg. "XMP:Title-fr", +or "Rights-en-US"). (See L for the RFC +3066 specification.) A C tag with no language code accesses the +"x-default" language, but causes other languages for this tag to be deleted +when writing. The "x-default" language code may be specified when writing +to preserve other existing languages (eg. "XMP-dc:Description-x-default"). +When reading, "x-default" is not specified. + +The XMP tags are organized according to schema B in the following +tables. The ExifTool family 1 group names are derived from the namespace +prefixes by adding a leading "XMP-" (eg. "XMP-dc"). A few of the longer +prefixes have been shortened (as mentioned in the documentation below) to +avoid excessively long ExifTool group names. The tags of any namespace may +be deleted as a group by specifying the family 1 group name (eg. +"-XMP-dc:all=" on the command line). This includes namespaces which are not +pre-defined by ExifTool. + +In cases where a tag name exists in more than one namespace, less common +namespaces are avoided when writing. However, a specific namespace may be +written by providing a family 1 group name for the tag (eg. XMP-crs:Contrast +or XMP-exif:Contrast). When deciding on which tags to add to an image, +using standard schemas such as L, L, +L and L is +recommended if possible. + +For structures, the heading of the first column is B. Field +names are very similar to tag names, except they are used to identify fields +inside structures instead of stand-alone tags. See +L for more +details. + +ExifTool will extract XMP information even if it is not listed in these +tables, but other tags are not writable unless added as user-defined tags in +the L. For example, the C namespace doesn't have a +predefined set of tag names because it is used to store application-defined +PDF information, so although this information will be extracted, it is only +writable if the corresponding user-defined tags have been created. + +The tables below list tags from the official XMP specification (with an +underlined B in the HTML version of this documentation), as well +as extensions from various other sources. See +L for the official XMP specification. +}, + IPTC => q{ +The tags listed below are part of the International Press Telecommunications +Council (IPTC) and the Newspaper Association of America (NAA) Information +Interchange Model (IIM). This is an older meta information format, slowly +being phased out in favor of XMP -- the newer IPTCCore specification uses +XMP format. IPTC information may be found in JPG, TIFF, PNG, MIFF, PS, PDF, +PSD, XCF and DNG images. + +IPTC information is separated into different records, each of which has its +own set of tags. See +L for the +official IPTC IIM specification. + +This specification dictates a length for ASCII (C or C) and +binary (C) values. These lengths are given in square brackets after +the B format name. For tags where a range of lengths is allowed, +the minimum and maximum lengths are separated by a comma within the +brackets. When writing, ExifTool issues a minor warning and truncates the +value if it is longer than allowed by the IPTC specification. Minor errors +may be ignored with the L (-m) option, allowing longer +values to be written, but beware that values like this may cause problems +for some other IPTC readers. ExifTool will happily read IPTC values of any +length. + +Separate IPTC date and time tags may be written with a combined date/time +value and ExifTool automagically takes the appropriate part of the date/time +string depending on whether a date or time tag is being written. This is +very useful when copying date/time values to IPTC from other metadata +formats. + +IPTC time values include a timezone offset. If written with a value which +doesn't include a timezone then the current local timezone offset is used +(unless written with a combined date/time, in which case the local timezone +offset at the specified date/time is used, which may be different due to +changes in daylight savings time). + +Note that it is not uncommon for IPTC to be found in non-standard locations +in JPEG and TIFF-based images. When reading, the family 1 group name has a +number added for non-standard IPTC ("IPTC2", "IPTC3", etc), but when writing +only "IPTC" may be specified as the group. To keep the IPTC consistent, +ExifTool updates tags in all existing IPTC locations, but will create a new +IPTC group only in the standard location. +}, + QuickTime => q{ +The QuickTime format is used for many different types of audio, video and +image files (most notably, MOV/MP4 videos and HEIC/CR3 images). ExifTool +extracts standard meta information and a variety of audio, video and image +parameters, as well as proprietary information written by many camera +models. Tags with a question mark after their name are not extracted unless +the L option is set. + +When writing, ExifTool creates both QuickTime and XMP tags by default, but +the group may be specified to write one or the other separately. If no +location is specified, newly created QuickTime tags are added in the +L location if +possible, otherwise in +L, and +finally in L, +but this order may be changed by setting the PREFERRED level of the +appropriate table in the config file (see +L in the full distribution for an +example). Note that some tags with the same name but different ID's may +exist in the same location, but the family 7 group names may be used to +differentiate these. ExifTool currently writes only top-level metadata in +QuickTime-based files; it extracts other track-specific and timed metadata, +but can not yet edit tags in these locations (with the exception of +track-level date/time tags). + +Beware that the Keys tags are actually stored inside the ItemList in the +file, so deleting the ItemList group as a block (ie. C<-ItemList:all=>) also +deletes Keys tags. Instead, to preserve Keys tags the ItemList tags may be +deleted individually with C<-QuickTime:ItemList:all=>. + +Alternate language tags may be accessed for +L and +L tags by adding +a 3-character ISO 639-2 language code and an optional ISO 3166-1 alpha 2 +country code to the tag name (eg. "ItemList:Artist-deu" or +"ItemList::Artist-deu-DE"). Most +L tags support a +language code, but without a country code. If no language code is specified +when writing, the default language is written and alternate languages for +the tag are deleted. Use the "und" language code to write the default +language without deleting alternate languages. Note that "eng" is treated +as a default language when reading, but not when writing. + +According to the specification, integer-format QuickTime date/time tags +should be stored as UTC. Unfortunately, digital cameras often store local +time values instead (presumably because they don't know the time zone). For +this reason, by default ExifTool does not assume a time zone for these +values. However, if the API L option is set, then ExifTool will +assume these values are properly stored as UTC, and will convert them to +local time when extracting. + +When writing string-based date/time tags, the system time zone is added if +the PrintConv option is enabled and no time zone is specified. This is +because Apple software may display crazy values if the time zone is missing +for some tags. + +By default ExifTool will remove null padding from some QuickTime containers +in Canon CR3 files when writing, but the +L option may be used to preserve +the original size by padding with nulls if necessary. + +See +L +for the official specification. +}, + Photoshop => q{ +Photoshop tags are found in PSD and PSB files, as well as inside embedded +Photoshop information in many other file types (JPEG, TIFF, PDF, PNG to name +a few). + +Many Photoshop tags are marked as Unknown (indicated by a question mark +after the tag name) because the information they provide is not very useful +under normal circumstances. These unknown tags are not extracted unless the +L (-u) option is used. See +L for the +official specification + +Photoshop path tags (Tag ID's 0x7d0 to 0xbb5) are not defined by default, +but a config file included in the full ExifTool distribution +(config_files/photoshop_paths.config) contains the tag definitions to allow +access to this information. +}, + PrintIM => q{ +The format of the PrintIM information is known, however no PrintIM tags have +been decoded. Use the L (-u) option to extract PrintIM information. +}, + GeoTiff => q{ +ExifTool extracts the following tags from GeoTIFF images. See +L +for the complete GeoTIFF specification. Also included in the table below +are ChartTIFF tags (see +L). +GeoTIFF tags are not writable individually, but they may be copied en mass +via the block tags GeoTiffDirectory, GeoTiffDoubleParams and +GeoTiffAsciiParams. +}, + JFIF => q{ +The following information is extracted from the JPEG JFIF header. See +L for the JFIF 1.02 +specification. +}, + Kodak => q{ +Many Kodak models don't store the maker notes in standard IFD format, and +these formats vary with different models. Some information has been +decoded, but much of the Kodak information remains unknown. +}, + 'Kodak SpecialEffects' => q{ +The Kodak SpecialEffects and Borders tags are found in sub-IFD's within the +Kodak JPEG APP3 "Meta" segment. +}, + Minolta => q{ +These tags are used by Minolta, Konica/Minolta as well as some Sony cameras. +Minolta doesn't make things easy for decoders because the meaning of some +tags and the location where some information is stored is different for +different camera models. (Take MinoltaQuality for example, which may be +located in 5 different places.) +}, + Olympus => q{ +Tags 0x0000 through 0x0103 are used by some older Olympus cameras, and are +the same as Konica/Minolta tags. These tags are also used for some models +from other brands such as Acer, BenQ, Epson, Hitachi, HP, Maginon, Minolta, +Pentax, Ricoh, Samsung, Sanyo, SeaLife, Sony, Supra and Vivitar. +}, + Panasonic => q{ +These tags are used in Panasonic/Leica cameras. +}, + Pentax => q{ +These tags are used in Pentax/Asahi cameras. +}, + CanonRaw => q{ +These tags apply to CRW-format Canon RAW files and information in the APP0 +"CIFF" segment of JPEG images. When writing CanonRaw/CIFF information, the +length of the information is preserved (and the new information is truncated +or padded as required) unless B is C. Currently, only +JpgFromRaw and ThumbnailImage are allowed to change size. See +L for a description of the Canon CRW +format. + +CRW images also support the addition of a CanonVRD trailer, which in turn +supports XMP. This trailer is created automatically if necessary when +ExifTool is used to write XMP to a CRW image. +}, + NikonCustom => q{ +Unfortunately, the NikonCustom settings are stored in a binary data block +which changes from model to model. This means that significant effort must +be spent in decoding these for each model, usually requiring hundreds of +test images from a dedicated Nikon owner. For this reason, the NikonCustom +settings have not been decoded for all models. The tables below list the +custom settings for the currently supported models. +}, + Unknown => q{ +The following tags are decoded in unsupported maker notes. Use the L +(-u) option to display other unknown tags. +}, + PDF => q{ +The tags listed in the PDF tables below are those which are used by ExifTool +to extract meta information, but they are only a small fraction of the total +number of available PDF tags. See +L for the official PDF +specification. + +ExifTool supports reading and writing PDF documents up to version 2.0, +including support for RC4, AES-128 and AES-256 encryption. A +L option is provided to allow processing +of password-protected PDF files. + +ExifTool may be used to write native PDF and XMP metadata to PDF files. It +uses an incremental update technique that has the advantages of being both +fast and reversible. If ExifTool was used to modify a PDF file, the +original may be recovered by deleting the C pseudo-group (with +C<-PDF-update:all=> on the command line). However, there are two main +disadvantages to this technique: + +1) A linearized PDF file is no longer linearized after the update, so it +must be subsequently re-linearized if this is required. + +2) All metadata edits are reversible. While this would normally be +considered an advantage, it is a potential security problem because old +information is never actually deleted from the file. (However, after +running ExifTool the old information may be removed permanently using the +"qpdf" utility with this command: "qpdf --linearize in.pdf out.pdf".) +}, + DNG => q{ +The main DNG tags are found in the EXIF table. The tables below define only +information found within structures of these main DNG tag values. See +L for the official DNG specification. +}, + MPEG => q{ +The MPEG format doesn't specify any file-level meta information. In lieu of +this, information is extracted from the first audio and video frame headers +in the file. +}, + Real => q{ +ExifTool recognizes three basic types of Real audio/video files: 1) +RealMedia (RM, RV and RMVB), 2) RealAudio (RA), and 3) Real Metafile (RAM +and RPM). +}, + Extra => q{ +The extra tags provide extra features or extra information extracted or +generated by ExifTool that is not directly associated with another tag +group. The B column lists the family 1 group name when reading. +Tags with a "-" in this column are write-only. + +Tags in the family 1 "System" group are referred to as "pseudo" tags because +they don't represent real metadata in the file. Instead, this information +is stored in the directory structure of the filesystem. The B +System "pseudo" tags in this table may be written without modifying the file +itself. The TestName tag is used for dry-run testing before writing +FileName. +}, + Composite => q{ +The values of the composite tags are B the values of other +tags. These are convenience tags which are calculated after all other +information is extracted. Only a few of these tags are writable directly, +the others are changed by writing the corresponding B tags. +User-defined Composite tags, also useful for custom-formatting of tag +values, may created via the L. +}, + Shortcuts => q{ +Shortcut tags are convenience tags that represent one or more other tag +names. They are used like regular tags to read and write the information +for a specified set of tags. + +The shortcut tags below have been pre-defined, but user-defined shortcuts +may be added via the %Image::ExifTool::UserDefined::Shortcuts lookup in the +~/.ExifTool_config file. See the Image::ExifTool::Shortcuts documentation +for more details. +}, + MWG => q{ +The Metadata Working Group (MWG) recommends techniques to allow certain +overlapping EXIF, IPTC and XMP tags to be reconciled when reading, and +synchronized when writing. The MWG Composite tags below are designed to aid +in the implementation of these recommendations. As well, the MWG defines +new XMP tags which are listed in the subsequent tables below. See +L +for the official MWG specification. +}, + MacOS => q{ +On MacOS systems, the there are additional MDItem and XAttr Finder tags that +may be extracted. These tags are not extracted by default -- they must be +specifically requested or enabled via an API option. (Except when reading +MacOS "._" files directly, see below.) + +The tables below list some of the tags that may be extracted, but ExifTool +will extract all available information even for tags not listed. + +Tags in these tables are referred to as "pseudo" tags because their +information is not stored in the file itself. As such, B tags in +these tables may be changed without having to rewrite the file. +}, + PodTrailer => q{ +~head1 NOTES + +This document generated automatically by +L. + +~head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +~head1 SEE ALSO + +L + +~cut +}, +); + +# notes for Shortcuts tags +my %shortcutNotes = ( + AllDates => q{ + contrary to the shortcut name, this represents only the common EXIF + date/time tags. To access all date/time tags, use Time:All instead + }, + MakerNotes => q{ + useful when copying tags between files to either copy the maker notes as a + block or prevent it from being copied + }, + ColorSpaceTags => q{ + standard tags which carry color space information. Useful for preserving + color space when deleting all other metadata + }, + CommonIFD0 => q{ + common metadata tags found in IFD0 of TIFF-format images. Used to simpify + deletion of all metadata from these images. See + L for details + }, + Unsafe => q{ + I tags in JPEG images which are normally not copied. Defined here + as a shortcut to use when rebuilding JPEG EXIF from scratch. See + L for more information + }, + LargeTags => q{ + large binary data tags which may be excluded to reduce memory usage if + memory limitations are a problem + }, + 'ls-l' => q{ + mimics columns shown by Unix "ls -l" command. Includes some tags which are + extracted only if the API L option + is enabled + }, +); + +# lookup for RIFF INFO tags which are found in the EXIF spec +my %riffSpec = ( + IARL => 1, ICRD => 1, IGNR => 1, IPLT => 1, ISRC => 1, + IART => 1, ICRP => 1, IKEY => 1, IPRD => 1, ISRF => 1, + ICMS => 1, IDIM => 1, ILGT => 1, ISBJ => 1, ITCH => 1, + ICMT => 1, IDPI => 1, IMED => 1, ISFT => 1, + ICOP => 1, IENG => 1, INAM => 1, ISHP => 1, +); +# same thing for XMP namespaces +my %xmpSpec = ( + aux => 1, 'x' => 1, + crs => 1, xmp => 1, + dc => 1, xmpBJ => 1, + exif => 1, xmpDM => 1, + pdf => 1, xmpMM => 1, + pdfx => 1, xmpNote => 1, + photoshop => 1, xmpRights => 1, + tiff => 1, xmpTPg => 1, +); + +#------------------------------------------------------------------------------ +# New - create new BuildTagLookup object +# Inputs: 0) reference to BuildTagLookup object or BuildTagLookup class name +sub new +{ + local $_; + my $that = shift; + my $class = ref($that) || $that || 'Image::ExifTool::BuildTagLookup'; + my $self = bless {}, $class; + my (%subdirs, %isShortcut); + my %count = ( + 'unique tag names' => 0, + 'total tags' => 0, + ); +# +# loop through all tables, accumulating TagLookup and TagName information +# + my (%tagNameInfo, %id, %longID, %longName, %shortName, %tableNum, + %tagLookup, %tagExists, %noLookup, %tableWritable, %sepTable, %case, + %structs, %compositeModules, %isPlugin, %flattened, %structLookup, + @writePseudo, %dupXmpTag); + $self->{TAG_NAME_INFO} = \%tagNameInfo; + $self->{ID_LOOKUP} = \%id; + $self->{LONG_ID} = \%longID; + $self->{LONG_NAME} = \%longName; + $self->{SHORT_NAME} = \%shortName; + $self->{TABLE_NUM} = \%tableNum; + $self->{TAG_LOOKUP} = \%tagLookup; + $self->{TAG_EXISTS} = \%tagExists; + $self->{FLATTENED} = \%flattened; + $self->{TABLE_WRITABLE} = \%tableWritable; + $self->{SEPARATE_TABLE} = \%sepTable; + $self->{STRUCTURES} = \%structs; + $self->{STRUCT_LOOKUP} = \%structLookup; # lookup for Struct hash ref based on Struct name + $self->{COMPOSITE_MODULES} = \%compositeModules; + $self->{COUNT} = \%count; + $self->{WRITE_PSEUDO} = \@writePseudo; + + Image::ExifTool::LoadAllTables(); + my @tableNames = sort keys %allTables; + # add Shortcuts after other tables + push @tableNames, 'Image::ExifTool::Shortcuts::Main'; + # add plug-in modules last + foreach (@pluginTables) { + push @tableNames, $_; + $isPlugin{$_} = 1; + } + + my $tableNum = 0; + my $et = new Image::ExifTool; + my ($tableName, $tag); + # create lookup for short table names + foreach $tableName (@tableNames) { + my $short = $tableName; + $short =~ s/^Image::ExifTool:://; + $short =~ s/::Main$//; + $short =~ s/::/ /; + $short =~ s/^(.+)Tags$/\u$1/ unless $short eq 'Nikon AVITags'; + $short =~ s/^Exif\b/EXIF/; + # change underlines to dashes in XMP-mwg group names + # (we used underlines just because Perl variables can't contain dashes) + $short =~ s/^XMP mwg_/XMP mwg-/; + $shortName{$tableName} = $short; # remember short name + $tableNum{$tableName} = $tableNum++; + } + # validate DICOM UID values + foreach (values %Image::ExifTool::DICOM::uid) { + next unless /[\0-\x1f\x7f-\xff]/; + warn "Warning: Special characters in DICOM UID value ($_)\n"; + } + # make lookup table to check for shortcut tags + foreach $tag (keys %Image::ExifTool::Shortcuts::Main) { + my $entry = $Image::ExifTool::Shortcuts::Main{$tag}; + # ignore if shortcut tag name includes itself + next if ref $entry eq 'ARRAY' and grep /^$tag$/, @$entry; + $isShortcut{lc($tag)} = 1; + } + foreach $tableName (@tableNames) { + # create short table name + my $short = $shortName{$tableName}; + my $info = $tagNameInfo{$tableName} = [ ]; + my $isPlugin = $isPlugin{$tableName}; + my ($table, $shortcut, %isOffset, %datamember, %hasSubdir); + if ($short eq 'Shortcuts') { + # can't use GetTagTable() for Shortcuts (not a normal table) + $table = \%Image::ExifTool::Shortcuts::Main; + $shortcut = 1; + } elsif ($isPlugin) { + $table = GetTagTable($tableName); + # don't add to allTables list because this messes our table order + delete $allTables{$tableName}; + pop @tableOrder; + } else { + $table = GetTagTable($tableName); + } + my $tableNum = $tableNum{$tableName}; + my $writeProc = $$table{WRITE_PROC}; + my $vars = $$table{VARS} || { }; + $longID{$tableName} = 0; + $longName{$tableName} = 0; + # save all tag names + my ($tagID, $binaryTable, $noID, $hexID, $isIPTC, $isXMP); + $isIPTC = 1 if $writeProc and $writeProc eq \&Image::ExifTool::IPTC::WriteIPTC; + # generate flattened tag names for structure fields if this is an XMP table + if ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP') { + Image::ExifTool::XMP::AddFlattenedTags($table); + $isXMP = 1; + } + $noID = 1 if $isXMP or $short =~ /^(Shortcuts|ASF.*)$/ or $$vars{NO_ID}; + $hexID = $$vars{HEX_ID}; + my $processBinaryData = ($$table{PROCESS_PROC} and ( + $$table{PROCESS_PROC} eq \&Image::ExifTool::ProcessBinaryData or + $$table{PROCESS_PROC} eq \&Image::ExifTool::Nikon::ProcessNikonEncrypted or + $$table{PROCESS_PROC} eq \&Image::ExifTool::Sony::ProcessEnciphered)); + if ($$vars{ID_LABEL} or $processBinaryData) { + my $s = $$table{FORMAT} ? Image::ExifTool::FormatSize($$table{FORMAT}) || 1 : 1; + $binaryTable = 1; + $id{$tableName} = $$vars{ID_LABEL} || "Index$s"; + } elsif ($isIPTC and $$table{PROCESS_PROC}) { #only the main IPTC table has a PROCESS_PROC + $id{$tableName} = 'Record'; + } elsif (not $noID) { + $id{$tableName} = 'Tag ID'; + } + $caseInsensitive = $isXMP; + $numbersFirst = 2; + $numbersFirst = -1 if $$table{VARS} and $$table{VARS}{ALPHA_FIRST}; + my @keys = SortedTagTableKeys($table); + $numbersFirst = 1; + my $defFormat = $table->{FORMAT}; + # use default format for binary data tables + $defFormat = 'int8u' if not $defFormat and $binaryTable; + +TagID: foreach $tagID (@keys) { + my ($tagInfo, @tagNames, $subdir, @values); + my (@infoArray, @require, @writeGroup, @writable); + if ($shortcut) { + # must build a dummy tagInfo list since Shortcuts is not a normal table + $tagInfo = { + Name => $tagID, + Notes => $shortcutNotes{$tagID}, + Writable => 1, + Require => { }, + }; + my $i; + for ($i=0; $i<@{$$table{$tagID}}; ++$i) { + $tagInfo->{Require}->{$i} = $table->{$tagID}->[$i]; + } + @infoArray = ( $tagInfo ); + } else { + @infoArray = GetTagInfoList($table,$tagID); + } + foreach $tagInfo (@infoArray) { + my $name = $$tagInfo{Name}; + if ($$tagInfo{WritePseudo}) { + push @writePseudo, $name; + warn "Writable pseudo tag $name is not protected!\n" unless $$tagInfo{Protected}; + } + unless ($$tagInfo{SubDirectory} or $$tagInfo{Struct}) { + my $lc = lc $name; + warn "Different case for $tableName $name $case{$lc}\n" if $case{$lc} and $case{$lc} ne $name; + $case{$lc} = $name; + } + my $format = $$tagInfo{Format}; + # check TagID's to make sure they don't start with 'ID-' + my @grps = $et->GetGroup($tagInfo); + foreach (@grps) { + warn "Group name starts with 'ID-' for $short $name\n" if /^ID-/i; + } + if ($isXMP and not $$tagInfo{Avoid} and not $$tagInfo{Struct} and + ($$tagInfo{Writable} or $$table{WRITABLE})) + { + $dupXmpTag{$name} and warn "Duplicate writable XMP tag $name\n"; + $dupXmpTag{$name} = 1; + } + # validate Name (must not start with a digit or else XML output will not be valid; + # must not start with a dash or exiftool command line may get confused) + if ($name !~ /^[_A-Za-z][-\w]+$/ and + # single-character subdirectory names are allowed + (not $$tagInfo{SubDirectory} or $name !~ /^[_A-Za-z]$/)) + { + warn "Warning: Invalid tag name $short '${name}'\n"; + } + # validate list type + if ($$tagInfo{List} and $$tagInfo{List} !~ /^(1|Alt|Bag|Seq|array|string)$/) { + warn "Warning: Unknown List type ($$tagInfo{List}) for $name in $tableName\n"; + } + # accumulate information for consistency check of BinaryData tables + if ($processBinaryData and $$table{WRITABLE}) { + # can't currently write tag if Condition accesses $valPt + if ($$tagInfo{Condition} and not $$tagInfo{SubDirectory} and + $$tagInfo{Condition} =~ /\$valPt/ and + ($$tagInfo{Writable} or not defined $$tagInfo{Writable})) + { + warn "Warning: Tag not writable due to Condition - $short $name\n"; + } + $isOffset{$tagID} = $name if $$tagInfo{IsOffset}; + $hasSubdir{$tagID} = $name if $$tagInfo{SubDirectory}; + # require DATAMEMBER for writable var-format tags, Hook and DataMember tags + if ($format and $format =~ /^var_/) { + $datamember{$tagID} = $name; + unless (defined $$tagInfo{Writable} and not $$tagInfo{Writable}) { + warn "Warning: Var-format tag is writable - $short $name\n" + } + # also need DATAMEMBER for tags used in length of var-sized value + while ($format =~ /\$val\{(.*?)\}/g) { + my $id = $1; + $id = hex($id) if $id =~ /^0x/; # convert from hex if necessary + $datamember{$id} = $$table{$id}{Name} if ref $$table{$id} eq 'HASH'; + unless ($datamember{$id}) { + warn "Warning: Unknown ID ($id) used in Format - $short $name\n"; + } + } + } elsif ($$tagInfo{Hook} or ($$tagInfo{RawConv} and + $$tagInfo{RawConv} =~ /\$self(->)?\{\w+\}\s*=(?!~)/)) + { + $datamember{$tagID} = $name; + } + if ($format and $format =~ /\$val\{/ and + ($$tagInfo{Writable} or not defined $$tagInfo{Writable})) + { + warn "Warning: \$var{} used in Format of writable tag - $short $name\n" + } + } + if ($$tagInfo{Hidden}) { + if ($tagInfo == $infoArray[0]) { + next TagID; # hide all tags with this ID if first tag in list is hidden + } else { + next; # only hide this tag + } + } + my $writable; + if (defined $$tagInfo{Writable}) { + $writable = $$tagInfo{Writable}; + # validate Writable + unless ($formatOK{$writable} or ($writable =~ /(.*)\[/ and $formatOK{$1})) { + warn "Warning: Unknown Writable ($writable) - $short $name\n", + } + } elsif (not $$tagInfo{SubDirectory}) { + $writable = $$table{WRITABLE}; + } + # in general, we can't write unless we have a WRITE_PROC + if ($writable and not ($$table{WRITE_PROC} or $tableName =~ /Shortcuts/ or $writable eq '2')) { + undef $writable; + } + # validate some characteristics of obvious date/time tags + my @g = $et->GetGroup($tagInfo); + if ($$tagInfo{List} and $g[2] eq 'Time' and $writable and not $$tagInfo{Protected} and + not $$tagInfo{PrintConvInv}) + { + # (this is a problem because shifting Time:All would create a new list entry) + warn "Writable List-type Time tag $g[1]:$name has no PrintConvInv and is not Protected!\n"; + } + if ($$tagInfo{PrintConv} and $$tagInfo{PrintConv} eq '$self->ConvertDateTime($val)') { + warn "$short $name should be in 'Time' group!\n" unless $g[2] eq 'Time'; + if ($writable and not defined $$tagInfo{Shift} and $short ne 'PostScript') { + warn "$short $name is not shiftable!\n"; + } + if ($writable and (not $$tagInfo{PrintConvInv} or + $$tagInfo{PrintConvInv} !~ /InverseDateTime/)) + { + warn "$short $name missing InverseDateTime PrintConvInv\n"; + } + } elsif ($name =~ /DateTime(?!Stamp)/ and (not $$tagInfo{Groups}{2} or + $$tagInfo{Groups}{2} ne 'Time') and $short ne 'DICOM') { + warn "$short $name should be in 'Time' group!\n"; + } + if ($name eq 'DateTimeOriginal' and (not $$tagInfo{Description} or + $$tagInfo{Description} ne 'Date/Time Original')) + { + warn "Need Description for $short DateTimeOriginal\n"; + } + # validate Description (can't contain special characters) + if ($$tagInfo{Description} and + $$tagInfo{Description} ne EscapeHTML($$tagInfo{Description})) + { + # this is a problem because the Escape option currently only + # escapes descriptions if the default Lang option isn't default + warn "$name description contains special characters!\n"; + } + # generate structure lookup + my $struct = $$tagInfo{Struct}; + my $strTable; + if (ref $struct) { + $strTable = $struct; + $struct = $$strTable{STRUCT_NAME}; + if ($struct) { + my $oldTable = $structLookup{$struct}; + if ($oldTable and $oldTable ne $strTable) { + warn "Duplicate XMP structure with name $struct\n"; + } else { + $structLookup{$struct} = $strTable; + } + } else { + warn "Missing STRUCT_NAME for structure in $$tagInfo{Name}\n"; + undef $strTable; + } + } elsif ($struct) { + $strTable = $structLookup{$struct}; + unless ($strTable) { + $struct = "XMP $struct" unless $struct =~ / /; + warn "Missing $struct structure!\n"; + undef $struct; + } + } + # validate SubIFD flag + my $subdir = $$tagInfo{SubDirectory}; + my $isSub = ($subdir and $$subdir{Start} and $$subdir{Start} =~ /\$val\b/); + if ($$tagInfo{SubIFD}) { + warn "Warning: Wrong SubDirectory Start for SubIFD tag - $short $name\n" unless $isSub; + } else { + warn "Warning: SubIFD flag not set for $short $name\n" if $isSub and not $processBinaryData; + } + if ($$tagInfo{Notes}) { + my $note = $$tagInfo{Notes}; + # remove leading/trailing blank lines + $note =~ s/^\s+//; $note =~ s/\s+$//; + # remove leading/trailing spaces on each line + $note =~ s/(^[ \t]+|[ \t]+$)//mg; + push @values, "($note)"; + } + if ($isXMP and (lc $tagID ne lc $name or $$tagInfo{NotFlat})) { + my $note; + if ($$tagInfo{NotFlat}) { + $note = 'NOT a flattened tag!'; + } else { + # add note about different XMP Tag ID + $note = $$tagInfo{RootTagInfo} ? $tagID : "called $tagID by the spec"; + } + if ($$tagInfo{Notes}) { + $values[-1] =~ s/^\(/($note; /; + } else { + push @values, "($note)"; + } + } + my $writeGroup; + if ($short eq 'Extra') { + $writeGroup = $$tagInfo{WriteOnly} ? '-' : $g[1]; + } else { + $writeGroup = $$tagInfo{WriteGroup}; + } + unless ($writeGroup) { + $writeGroup = $$table{WRITE_GROUP} if $writable; + $writeGroup = '-' unless $writeGroup; + } + if (defined $format) { + # validate Format + unless ($formatOK{$format} or $short eq 'PICT' or + ($format =~ /^(var_)?(.*)\[/ and $formatOK{$2})) + { + warn "Warning: Unknown Format ($format) for $short $name\n"; + } + } else { + $format = $defFormat; + } + if ($subdir) { + my $subTable = $$subdir{TagTable} || $tableName; + push @values, $shortName{$subTable} + } elsif ($struct) { + push @values, $struct; + $structs{$struct} = 1; + } + my $type; + foreach $type ('Require','Desire') { + my $require = $$tagInfo{$type}; + if (ref $require) { + foreach (sort { $a <=> $b } keys %$require) { + push @require, $$require{$_}; + } + } elsif ($require) { + push @require, $require; + } + } + my $printConv = $$tagInfo{PrintConv}; + if ($$tagInfo{Mask}) { + my $val = $$tagInfo{Mask}; + my $bsh = $$tagInfo{BitShift}; + if ($bsh) { + push @values, sprintf('[val >> %d & 0x%x]',$bsh,$val>>$bsh); + } else { + push @values, sprintf('[val & 0x%x]',$val); + } + # verify that all values are within the mask + if (ref $printConv eq 'HASH') { + $val >>= $$tagInfo{BitShift}; + foreach (keys %$printConv) { + next if $_ !~ /^\d+$/ or ($_ & $val) == $_; + my $hex = sprintf '0x%.2x', $_; + warn "$short $name PrintConv value $hex is not in Mask!\n"; + } + } + } + if (ref($printConv) =~ /^(HASH|ARRAY)$/) { + my (@printConvList, @indexList, $index, $valueConvHash); + if (ref $printConv eq 'ARRAY') { + for ($index=0; $index<@$printConv; ++$index) { + if (ref $$printConv[$index] eq 'HASH') { + next unless %{$$printConv[$index]}; + push @printConvList, $$printConv[$index]; + push @indexList, $index; + } elsif ($$printConv[$index] and $$printConv[$index] eq 'REPEAT' and $index) { + push @printConvList, $$printConv[$index-1]; + push @indexList, 'N'; + } else { + next; + } + # collapse values with identical PrintConv's + if (@printConvList >= 2 and $printConvList[-1] eq $printConvList[-2]) { + if (ref $indexList[-2]) { + push @{$indexList[-2]}, $indexList[-1]; + } else { + $indexList[-2] = [ $indexList[-2], $indexList[-1] ]; + } + pop @printConvList; + pop @indexList; + } + } + $printConv = shift @printConvList; + $index = shift @indexList; + } else { + $valueConvHash = $$tagInfo{ValueConv} if ref $$tagInfo{ValueConv} eq 'HASH'; + } + while (defined $printConv) { + if (defined $index) { + # (print indices of original values if reorganized) + my $s = ''; + my $idx = $$tagInfo{Relist} ? $tagInfo->{Relist}->[$index] : $index; + if (ref $idx) { + $s = 's' if @$idx > 1; + # collapse consecutive number ranges + my ($i, @i, $rngStart); + for ($i=0; $i<@$idx; ++$i) { + if ($i < @$idx - 1 and ($$idx[$i+1] eq 'N' or $$idx[$i+1] == $$idx[$i] + 1)) { + $rngStart = $$idx[$i] unless defined $rngStart; + next; + } + push @i, (defined($rngStart) ? "$rngStart-" : '') . $$idx[$i]; + } + ($idx = join ', ', @i) =~ s/(.*),/$1 and/; + } elsif (not $$tagInfo{Relist}) { + while (@printConvList and $printConv eq $printConvList[0]) { + shift @printConvList; + $index = shift @indexList; + } + if ($idx != $index) { + $idx = "$idx-$index"; + $s = 's'; + } + } + push @values, "[Value$s $idx]"; + } + if ($$tagInfo{SeparateTable}) { + $subdir = 1; + my $s = $$tagInfo{SeparateTable}; + $s = $name if $s eq '1'; + # add module name if not specified + $s =~ / / or ($short =~ /^(\w+)/ and $s = "$1 $s"); + push @values, $s; + $sepTable{$s} = $printConv; + # add PrintHex flag to PrintConv so we can check it later + $$printConv{PrintHex} = 1 if $$tagInfo{PrintHex}; + $$printConv{PrintInt} = 1 if $$tagInfo{PrintInt}; + $$printConv{PrintString} = 1 if $$tagInfo{PrintString}; + } else { + $caseInsensitive = 0; + my @pk; + if ($$tagInfo{PrintSort}) { + @pk = sort { NumbersFirst($$printConv{$a},$$printConv{$b}) } keys %$printConv; + } else { + @pk = sort { NumbersFirst($a,$b) } keys %$printConv; + } + my $n = scalar @values; + my ($bits, $i, $v); + foreach (@pk) { + next if $_ eq '' and $$printConv{$_} eq ''; + $_ eq 'BITMASK' and $bits = $$printConv{$_}, next; + $_ eq 'OTHER' and next; + my $index = $_; + $index =~ s/\.\d+$// if $$tagInfo{PrintInt}; + if (($$tagInfo{PrintHex} or $$printConv{BITMASK}) and $index =~ /^-?\d+$/) { + my $dig = $$tagInfo{PrintHex} || 1; + if ($index >= 0) { + $index = sprintf('0x%.*x', $dig, $index); + } elsif ($format and $format =~ /int(16|32)/) { + # mask off unused bits of signed integer hex value + my $mask = { 16 => 0xffff, 32 => 0xffffffff }->{$1}; + $index = sprintf('0x%.*x', $dig, $index & $mask); + } + } elsif ($$tagInfo{PrintString} or not /^[+-]?(?=\d|\.\d)\d*(\.\d*)?$/) { + # translate unprintable values + if ($index =~ s/([\x00-\x1f\x80-\xff])/sprintf("\\x%.2x",ord $1)/eg) { + $index = qq{"$index"}; + } else { + $index = qq{'${index}'}; + } + } + push @values, "$index = " . $$printConv{$_}; + if ($valueConvHash) { + foreach $v (keys %$valueConvHash) { + next unless $$valueConvHash{$v} eq $_; + $values[-1] = "$v => " . $values[-1]; + last; + } + } + # validate all PrintConv values + if ($$printConv{$_} =~ /[\0-\x1f\x7f-\xff]/) { + warn "Warning: Special characters in $short $name PrintConv ($$printConv{$_})\n"; + } + } + if ($bits) { + my @pk = sort { NumbersFirst($a,$b) } keys %$bits; + foreach (@pk) { + push @values, "Bit $_ = " . $$bits{$_}; + } + } + # organize values into columns if specified + my $cols = $$tagInfo{PrintConvColumns}; + if (not $cols and scalar(@values) - $n >= 6) { + # do columns if more than 6 short entries + my $maxLen = 0; + for ($i=$n; $i<@values; ++$i) { + next unless $maxLen < length $values[$i]; + $maxLen = length $values[$i]; + } + my $num = scalar(@values) - $n; + $cols = int(50 / ($maxLen + 2)); # (50 chars max width) + # have 3 rows minimum + --$cols while $cols and $num / $cols < 3; + } + if ($cols) { + my @new = splice @values, $n; + my $v = '[!HTML]'; + my $rows = int((scalar(@new) + $cols - 1) / $cols); + for ($n=0; ;) { + $v .= "\n '; + last if ($n += $rows) >= @new; + $v .= ''; # add spaces between columns + } + push @values, $v . "
"; + for ($i=0; $i<$rows and $n+$i<@new; ++$i) { + $v .= "\n
" if $i; + $v .= EscapeHTML($new[$n+$i]); + } + $v .= '
  
\n"; + } + } + last unless @printConvList; + $printConv = shift @printConvList; + $index = shift @indexList; + } + # look inside scalar PrintConv for a bit/byte conversion + # (see Photoshop:PrintFlags for use of "$byte" decoding) + } elsif ($printConv and $printConv =~ /DecodeBits\(\$(val|byte),\s*(\\\%[\w:]+|\{.*\})\s*\)/s) { + my $type = $1 eq 'byte' ? 'Byte' : 'Bit'; + $$self{Model} = ''; # needed for Nikon ShootingMode + my $bits = eval $2; + delete $$self{Model}; + if ($@) { + warn $@; + } else { + my @pk = sort { NumbersFirst($a,$b) } keys %$bits; + foreach (@pk) { + push @values, "$type $_ = " . $$bits{$_}; + } + } + } + if ($subdir and not $$tagInfo{SeparateTable}) { + if ($writable) { + # subdirectories are only writable if specified explicitly + my $tw = $$tagInfo{Writable}; + $writable = 'yes' if $tw and $writable eq '1' or $writable eq '2'; + $writable = '-' . ($tw ? $writable : ''); + $writable .= '!' if $tw and ($$tagInfo{Protected} || 0) & 0x01; + $writable .= '+' if $$tagInfo{List}; + if (defined $$tagInfo{Permanent}) { + $writable .= '^' unless $$tagInfo{Permanent}; + } elsif (defined $$table{PERMANENT}) { + $writable .= '^' unless $$table{PERMANENT}; + } + } else { + $writable = '-'; + } + } else { + # not writable if we can't do the inverse conversions + my $noPrintConvInv; + if ($writable) { + foreach ('PrintConv','ValueConv') { + next unless $$tagInfo{$_}; + next if defined $$tagInfo{$_ . 'Inv'}; + # (undefined inverse conversion overrides hash lookup) + unless (exists $$tagInfo{$_ . 'Inv'}) { + next if ref($$tagInfo{$_}) =~ /^(HASH|ARRAY)$/; + next if $$tagInfo{WriteAlso}; + } + if ($_ eq 'ValueConv') { + undef $writable; + } else { + $noPrintConvInv = 1; + } + } + } + if (not $writable) { + $writable = 'no'; + } else { + $writable = $format ? $format : 'yes' if $writable eq '1' or $writable eq '2'; + my $count = $$tagInfo{Count} || 1; + # adjust count to Writable size if different than Format + if ($writable and $format and $writable ne $format and + $writable ne 'string' and $format ne 'string' and + $Image::ExifTool::Exif::formatNumber{$writable} and + $Image::ExifTool::Exif::formatNumber{$format}) + { + my $n1 = $Image::ExifTool::Exif::formatNumber{$format}; + my $n2 = $Image::ExifTool::Exif::formatNumber{$writable}; + $count *= $Image::ExifTool::Exif::formatSize[$n1] / + $Image::ExifTool::Exif::formatSize[$n2]; + } + if ($count != 1) { + $count = 'n' if $count < 0; + $writable .= "[$count]"; + } + $writable .= '~' if $noPrintConvInv; + # add a '*' if this tag is protected or a '!' for unsafe tags + if ($$tagInfo{Protected}) { + $writable .= '*' if $$tagInfo{Protected} & 0x02; + $writable .= '!' if $$tagInfo{Protected} & 0x01; + } + $writable .= '/' if $$tagInfo{Avoid}; + } + $writable = "=struct" if $struct; + $writable .= '_' if defined $$tagInfo{Flat}; + $writable .= '+' if $$tagInfo{List}; + $writable .= ':' if $$tagInfo{Mandatory}; + if (defined $$tagInfo{Permanent}) { + $writable .= '^' unless $$tagInfo{Permanent}; + } elsif (defined $$table{PERMANENT}) { + $writable .= '^' unless $$table{PERMANENT}; + } + # separate tables link like subdirectories (flagged with leading '-') + $writable = "-$writable" if $subdir; + } + # don't duplicate a tag name unless an entry is different + my $lcName = lc($name); + # check for conflicts with shortcut names + if ($isShortcut{$lcName} and $short ne 'Shortcuts' and + ($$tagInfo{Writable} or not $$tagInfo{SubDirectory})) + { + warn "WARNING: $short $name is a shortcut tag!\n"; + } + $name .= '?' if $$tagInfo{Unknown}; + unless (@tagNames and $tagNames[-1] eq $name and + $writeGroup[-1] eq $writeGroup and $writable[-1] eq $writable) + { + push @tagNames, $name; + push @writeGroup, $writeGroup; + push @writable, $writable; + } +# +# add this tag to the tag lookup unless NO_LOOKUP is set or shortcut or plug-in tag +# + next if $shortcut or $isPlugin; + # count tags + if ($$tagInfo{SubDirectory}) { + next if $$vars{NO_LOOKUP}; + $subdirs{$lcName} or $subdirs{$lcName} = 0; + ++$subdirs{$lcName}; + } else { + ++$count{'total tags'}; + unless ($tagExists{$lcName} and + (not $subdirs{$lcName} or $subdirs{$lcName} == $tagExists{$lcName})) + { + ++$count{'unique tag names'} unless $noLookup{$lcName}; + } + # don't add to tag lookup if specified + $$vars{NO_LOOKUP} and $noLookup{$lcName} = 1, next; + } + $tagExists{$lcName} or $tagExists{$lcName} = 0; + ++$tagExists{$lcName}; + # only add writable tags to lookup table (for speed) + my $wflag = $$tagInfo{Writable}; + next unless $writeProc and ($wflag or ($$table{WRITABLE} and + not defined $wflag and not $$tagInfo{SubDirectory})); + $tagLookup{$lcName} or $tagLookup{$lcName} = { }; + # add to lookup for flattened tags if necessary + if ($$tagInfo{RootTagInfo}) { + $flattened{$lcName} or $flattened{$lcName} = { }; + $flattened{$lcName}{$tableNum} = $$tagInfo{RootTagInfo}{TagID}; + } + # remember number for this table + my $tagIDs = $tagLookup{$lcName}->{$tableNum}; + # must allow for duplicate tags with the same name in a single table! + if ($tagIDs) { + if (ref $tagIDs eq 'HASH') { + $$tagIDs{$tagID} = 1; + next; + } elsif ($tagID eq $tagIDs) { + next; + } else { + $tagIDs = { $tagIDs => 1, $tagID => 1 }; + } + } else { + $tagIDs = $tagID; + } + $tableWritable{$tableName} = 1; + $tagLookup{$lcName}->{$tableNum} = $tagIDs; + # keep track of extra modules needed for Composite tags + $compositeModules{$lcName} = $$tagInfo{Module} if $$tagInfo{Module}; + } +# +# save TagName information +# + my $tagIDstr; + if ($tagID =~ /^(-)?\d+(\.\d+)?$/) { + if ($1) { + $tagIDstr = $tagID; + } elsif (defined $hexID) { + $tagIDstr = $hexID ? sprintf('0x%.4x',$tagID) : $tagID; + } elsif (not $2 and not $binaryTable and not $isIPTC and + not ($short =~ /^CanonCustom/ and $tagID < 256)) + { + $tagIDstr = sprintf('0x%.4x',$tagID); + } elsif ($tagID < 0x10000) { + $tagIDstr = $tagID; + } else { + $tagIDstr = sprintf('0x%.8x',$tagID); + } + } elsif ($short eq 'DICOM') { + ($tagIDstr = $tagID) =~ s/_/,/; + } elsif ($tagID =~ /^0x([0-9a-f]+)\.(\d+)$/) { # DR4 tags like '0x20500.0' + $tagIDstr = $tagID; + } else { + # convert non-printable characters to hex escape sequences + if ($tagID =~ s/([\x00-\x1f\x7f-\xff])/'\x'.unpack('H*',$1)/eg) { + $tagID =~ s/\\x00/\\0/g; + next if $tagID eq 'jP\x1a\x1a'; # ignore abnormal JP2 signature tag + $tagIDstr = qq{"$tagID"}; + } else { + $tagIDstr = "'${tagID}'"; + } + } + my $len = length $tagIDstr; + $longID{$tableName} = $len if $longID{$tableName} < $len; + foreach (@tagNames) { + $len = length $_; + $longName{$tableName} = $len if $longName{$tableName} < $len; + } + push @$info, [ $tagIDstr, \@tagNames, \@writable, \@values, \@require, \@writeGroup ]; + } + # do consistency check of writable BinaryData tables + if ($processBinaryData and $$table{WRITABLE}) { + my %lookup = ( + IS_OFFSET => \%isOffset, + IS_SUBDIR => \%hasSubdir, + DATAMEMBER => \%datamember, + ); + my ($var, $tagID); + foreach $var (sort keys %lookup) { + my $hash = $lookup{$var}; + if ($$table{$var}) { + foreach $tagID (@{$$table{$var}}) { + $$hash{$tagID} and delete($$hash{$tagID}), next; + warn sprintf("Warning: Extra %s for %s tag %d (0x%.4x)\n", + $var, $short, $tagID, $tagID); + } + } + foreach $tagID (sort keys %$hash) { + warn sprintf("Warning: Missing %s for %s %s, tag %s (0x%.4x)\n", + $var, $short, $$hash{$tagID}, $tagID, $tagID); + } + } + } + } + # save information about structures + my $strName; + foreach $strName (keys %structs) { + my $struct = $structLookup{$strName}; + my $fullName = ($strName =~ / / ? '' : 'XMP ') . "$strName Struct"; + my $info = $tagNameInfo{$fullName} = [ ]; + my $tag; + foreach $tag (sort keys %$struct) { + my $tagInfo = $$struct{$tag}; + next unless ref $tagInfo eq 'HASH' and $tag ne 'NAMESPACE' and $tag ne 'GROUPS'; + warn "WARNING: $strName Struct containes $tag\n" if $Image::ExifTool::specialTags{$tag}; + my $writable = $$tagInfo{Writable}; + my @vals; + unless ($writable) { + $writable = $$tagInfo{Struct}; + ref $writable and $writable = $$writable{STRUCT_NAME}; + if ($writable) { + push @vals, $writable; + $structs{$writable} = 1; + $writable = "=$writable"; + } else { + $writable = 'string'; + } + } + $writable .= '+' if $$tagInfo{List}; + push @vals, "($$tagInfo{Notes})" if $$tagInfo{Notes}; + # handle PrintConv lookups in Structure elements + my $printConv = $$tagInfo{PrintConv}; + if (ref $printConv eq 'HASH') { + foreach (sort keys %$printConv) { + next if /^(OTHER|BITMASK)$/; + push @vals, "$_ = $$printConv{$_}"; + } + } + push @$info, [ + $tag, + [ $$tagInfo{Name} || ucfirst($tag) ], + [ $writable ], + \@vals, + [], [] + ]; + } + } + return $self; +} + +#------------------------------------------------------------------------------ +# Rewrite this file to build the lookup tables +# Inputs: 0) BuildTagLookup object reference +# 1) output tag lookup module name (eg. 'lib/Image/ExifTool/TagLookup.pm') +# Returns: true on success +sub WriteTagLookup($$) +{ + local ($_, *INFILE, *OUTFILE); + my ($self, $file) = @_; + my $tagLookup = $self->{TAG_LOOKUP}; + my $tagExists = $self->{TAG_EXISTS}; + my $flattened = $self->{FLATTENED}; + my $tableWritable = $self->{TABLE_WRITABLE}; +# +# open/create necessary files and transfer file headers +# + my $tmpFile = "${file}_tmp"; + open(INFILE, $file) or warn("Can't open $file\n"), return 0; + unless (open(OUTFILE, ">$tmpFile")) { + warn "Can't create temporary file $tmpFile\n"; + close(INFILE); + return 0; + } + my $success; + while () { + print OUTFILE $_ or last; + if (/^#\+{4} Begin/) { + $success = 1; + last; + } + } + print OUTFILE "\n# list of tables containing writable tags\n"; + print OUTFILE "my \@tableList = (\n"; + +# +# write table list +# + my @tableNames = sort keys %allTables; + my $tableName; + my %wrNum; # translate from allTables index to writable tables index + my $count = 0; + my $num = 0; + foreach $tableName (@tableNames) { + if ($$tableWritable{$tableName}) { + print OUTFILE "\t'${tableName}',\n"; + $wrNum{$count} = $num++; + } + $count++; + } +# +# write the tag lookup table +# + my $tag; + # verify that certain critical tag names aren't duplicated + foreach $tag (qw{filename directory}) { + next unless $$tagLookup{$tag}; + my $n = scalar keys %{$$tagLookup{$tag}}; + warn "Warning: $n writable '${tag}' tags!\n" if $n > 1; + } + print OUTFILE ");\n\n# lookup for all writable tags\nmy \%tagLookup = (\n"; + foreach $tag (sort keys %$tagLookup) { + print OUTFILE "\t'${tag}' => { "; + my @tableNums = sort { $a <=> $b } keys %{$$tagLookup{$tag}}; + my (@entries, $tableNum); + foreach $tableNum (@tableNums) { + my $tagID = $$tagLookup{$tag}{$tableNum}; + my $rootID = $$flattened{$tag}{$tableNum}; + my $entry; + if (ref $tagID eq 'HASH' or $rootID) { + $tagID = { $tagID => 1 } unless ref $tagID eq 'HASH'; + my @tagIDs = sort keys %$tagID; + foreach (@tagIDs) { + if (/^\d+$/) { + $_ = sprintf('0x%x',$_); + } else { + my $quot = "'"; + # escape non-printable characters in tag ID if necessary + $quot = '"' if s/[\x00-\x1f,\x7f-\xff]/sprintf('\\x%.2x',ord($&))/ge; + $_ = $quot . $_ . $quot; + } + } + # reference to root structure ID must come first in lookup + # (so we can generate the flattened tags just before we need them) + unshift @tagIDs, "\\'${rootID}'" if $rootID; + $entry = '[' . join(',', @tagIDs) . ']'; + } elsif ($tagID =~ /^\d+$/) { + $entry = sprintf('0x%x',$tagID); + } else { + my $quot = "'"; + # escape non-printable characters in tag ID if necessary + $quot = '"' if $tagID =~ s/[\x00-\x1f,\x7f-\xff]/sprintf('\\x%.2x',ord($&))/ge; + $entry = "$quot${tagID}$quot"; + } + my $wrNum = $wrNum{$tableNum}; + push @entries, "$wrNum => $entry"; + } + print OUTFILE join(', ', @entries); + print OUTFILE " },\n"; + } +# +# write tag exists lookup +# + print OUTFILE ");\n\n# lookup for non-writable tags to check if the name exists\n"; + print OUTFILE "my \%tagExists = (\n"; + foreach $tag (sort keys %$tagExists) { + next if $$tagLookup{$tag}; + print OUTFILE "\t'${tag}' => 1,\n"; + } +# +# write module lookup for writable composite tags +# + my $compositeModules = $self->{COMPOSITE_MODULES}; + print OUTFILE ");\n\n# module names for writable Composite tags\n"; + print OUTFILE "my \%compositeModules = (\n"; + foreach (sort keys %$compositeModules) { + print OUTFILE "\t'${_}' => '$$compositeModules{$_}',\n"; + } + print OUTFILE ");\n\n"; +# +# finish writing TagLookup.pm and clean up +# + if ($success) { + $success = 0; + while () { + $success or /^#\+{4} End/ or next; + print OUTFILE $_; + $success = 1; + } + } + close(INFILE); + close(OUTFILE) or $success = 0; +# +# return success code +# + if ($success) { + local (*ORG, *TMP); + # only rename the file if something changed + open ORG, $file or return 0; + open TMP, $tmpFile or return 0; + my ($buff, $buf2, $changed); + for (;;) { + my $n1 = read ORG, $buff, 65536; + my $n2 = read TMP, $buf2, 65536; + $n1 eq $n2 or $changed = 1, last; + last unless $n1; + $buff eq $buf2 or $changed = 1, last; + } + close ORG; + close TMP; + if ($changed) { + rename($tmpFile, $file) or warn("Error renaming $tmpFile\n"), $success = 0; + } else { + unlink($tmpFile); + } + } else { + unlink($tmpFile); + warn "Error rewriting file\n"; + } + return $success; +} + +#------------------------------------------------------------------------------ +# Sort numbers first numerically, then strings alphabetically (case insensitive) +# - two global variables are used to change the sort algorithm: +# $numbersFirst: -1 = put numbers after other strings +# 1 = put numbers before other strings +# 2 = put numbers first, but negative numbers last +# $caseInsensitive: flag set for case-insensitive sorting +sub NumbersFirst($$) +{ + my ($a, $b) = @_; + my $rtnVal; + my ($bNum, $bDec); + ($bNum, $bDec) = ($1, $3) if $b =~ /^(-?[0-9]+)(\.(\d*))?$/; + if ($a =~ /^(-?[0-9]+)(\.(\d*))?$/) { + if (defined $bNum) { + $bNum += 1e9 if $numbersFirst == 2 and $bNum < 0; + my $aInt = $1; + $aInt += 1e9 if $numbersFirst == 2 and $aInt < 0; + # compare integer part as a number + $rtnVal = $aInt <=> $bNum; + unless ($rtnVal) { + my $aDec = $3 || 0; + $bDec or $bDec = 0; + # compare decimal part as an integer too + # (so that "1.10" comes after "1.9") + $rtnVal = $aDec <=> $bDec; + } + } else { + $rtnVal = -$numbersFirst; + } + } elsif (defined $bNum) { + $rtnVal = $numbersFirst; + } else { + my ($a2, $b2) = ($a, $b); + # expand numbers to 3 digits (with restrictions to avoid messing up ascii-hex tags) + $a2 =~ s/(\d+)/sprintf("%.3d",$1)/eg if $a2 =~ /^(APP|DMC-\w+ )?[.0-9 ]*$/ and length($a2)<16; + $b2 =~ s/(\d+)/sprintf("%.3d",$1)/eg if $b2 =~ /^(APP|DMC-\w+ )?[.0-9 ]*$/ and length($b2)<16; + $caseInsensitive and $rtnVal = (lc($a2) cmp lc($b2)); + $rtnVal or $rtnVal = ($a2 cmp $b2); + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Convert our pod-like documentation to pod +# (funny, I know, but the pod headings must be hidden to prevent confusing +# the pod parser) +# Inputs: 0-N) documentation strings +sub Doc2Pod($;@) +{ + my $doc = shift; + local $_; + $doc .= shift while @_; + $doc =~ s/\n~/\n=/g; + $doc =~ s/L<[^>]+?\|(http[^>]+)>/L<$1>/g; # POD doesn't support text for http links + $doc =~ s/L<([^>]+?)\|[^>]+\.html(#\w+)?>/$1/g; # remove relative HTML links + return $doc; +} + +#------------------------------------------------------------------------------ +# Convert our pod-like documentation to html +# Inputs: 0) string +sub Doc2Html($) +{ + my $doc = EscapeHTML(shift); + $doc =~ s/\n\n/<\/p>\n\n

/g; + $doc =~ s/B<(.*?)>/$1<\/b>/sg; + $doc =~ s/C<(.*?)>/$1<\/code>/sg; + $doc =~ s/I<(.*?)>/$1<\/i>/sg; + # L --> some text + $doc =~ s{L<([^&]+?)\|\Q$homePage\E/(.*?)>}{$1<\/a>}sg; + # L --> https://exiftool.org/struct.html + $doc =~ s{L<\Q$homePage\E/(.*?)>}{$1<\/a>}sg; + # L

--> <a href="XMP.html#DICOM">XMP DICOM Tags</a> + # (specify "Image::ExifTool::TagNames" to link to another html file) + $doc =~ s{L&lt;([^&]+?)\|Image::ExifTool::TagNames/(\w+) ([^/&|]+) Tags&gt;}{<a href="$2.html#$3">$1</a>}sg; + # L<DICOM Tags|Image::ExifTool::TagNames/DICOM Tags> --> <a href="DICOM.html">DICOM Tags</a> + $doc =~ s{L&lt;([^&]+?)\|Image::ExifTool::TagNames/(\w+) Tags&gt;}{<a href="$2.html">$1</a>}sg; + # L<dc|/XMP dc Tags> --> <a href="#dc">dc</a> + # (a relative POD link turns into a relative HTML link) + $doc =~ s{L&lt;([^&]+?)\|/\w+ ([^/&|]+) Tags&gt;}{<a href="#$2">$1</a>}sg; + # L<sample config file|../config.html> --> <a href="../config.html">sample config file</a> + $doc =~ s/L&lt;([^&]+?)\|(.+?)&gt;/<a href="$2">$1<\/a>/sg; + # L<http://some.web.site/> --> <a href="http://some.web.site">http://some.web.site</a> + $doc =~ s/L&lt;(http.*?)&gt;/<a href="$1">$1<\/a>/sg; + return $doc; +} + +#------------------------------------------------------------------------------ +# Tweak order of tables +# Inputs: 0) table list ref, 1) reference to tweak hash +sub TweakOrder($$) +{ + local $_; + my ($sortedTables, $tweakOrder) = @_; + my @tweak = sort keys %$tweakOrder; + my (%addedMain, @sorted); + # flag files which have a "Main" table + foreach (@$sortedTables) { + $addedMain{$1} = 0 if /^Image::ExifTool::(\w+)::(\w+)/ and $2 eq 'Main'; + } + # make sure that the main table always comes first in each file + foreach (@$sortedTables) { + if (/^Image::ExifTool::(\w+)::(\w+)/) { + if ($addedMain{$1}) { + next if $2 eq 'Main'; # don't add again + } elsif (defined $addedMain{$1}) { + push @sorted, "Image::ExifTool::${1}::Main" if $2 ne 'Main'; + $addedMain{$1} = 1; + } + } + push @sorted, $_; + } + @$sortedTables = @sorted; + # apply manual tweaks + while (@tweak) { + my $table = shift @tweak; + my $first = $$tweakOrder{$table}; + if ($$tweakOrder{$first}) { + push @tweak, $table; # must defer this till later + next; + } + delete $$tweakOrder{$table}; # because the table won't move again + my @moving = grep /^Image::ExifTool::$table\b/, @$sortedTables; + my @notMoving = grep !/^Image::ExifTool::$table\b/, @$sortedTables; + my @after; + while (@notMoving) { + last if $notMoving[-1] =~ /^Image::ExifTool::$first\b/; + unshift @after, pop @notMoving; + } + @$sortedTables = (@notMoving, @moving, @after); + } +} + +#------------------------------------------------------------------------------ +# Get a list of sorted tag ID's from a table +# Inputs: 0) tag table ref +# Returns: list of sorted keys +sub SortedTagTableKeys($) +{ + my $table = shift; + my $vars = $$table{VARS} || { }; + my @keys = TagTableKeys($table); + if ($$vars{NO_ID}) { + # sort by tag name if ID not shown + my ($key, %name); + foreach $key (@keys) { + my ($tagInfo) = GetTagInfoList($table, $key); + $name{$key} = $$tagInfo{Name}; + } + return sort { $name{$a} cmp $name{$b} or $a cmp $b } @keys; + } else { + my $sortProc = $$vars{SORT_PROC} || \&NumbersFirst; + return sort { &$sortProc($a,$b) } @keys; + } +} + +#------------------------------------------------------------------------------ +# Get the order that we want to print the tables in the documentation +# Inputs: 0-N) Extra tables to add at end +# Returns: tables in the order we want +sub GetTableOrder(@) +{ + my %gotTable; + my @tableNames = @tableOrder; + my (@orderedTables, %mainTables, @outOfOrder); + my $lastTable = ''; + + while (@tableNames) { + my $tableName = shift @tableNames; + next if $gotTable{$tableName}; + if ($tableName =~ /^Image::ExifTool::(\w+)::Main/) { + $mainTables{$1} = 1; + } elsif ($lastTable and not $tableName =~ /^${lastTable}::/) { + push @outOfOrder, $tableName; + } + ($lastTable) = ($tableName =~ /^(Image::ExifTool::\w+)/); + push @orderedTables, $tableName; + $gotTable{$tableName} = 1; + my $table = GetTagTable($tableName); + # recursively scan through tables in subdirectories + my @moreTables; + $caseInsensitive = ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP'); + $numbersFirst = -1 if $$table{VARS} and $$table{VARS}{ALPHA_FIRST}; + my @keys = SortedTagTableKeys($table); + $numbersFirst = 1; + foreach (@keys) { + my @infoArray = GetTagInfoList($table,$_); + my $tagInfo; + foreach $tagInfo (@infoArray) { + my $subdir = $$tagInfo{SubDirectory} or next; + $tableName = $$subdir{TagTable} or next; + next if $gotTable{$tableName}; # next if table already loaded + push @moreTables, $tableName; # must scan this one too + } + } + unshift @tableNames, @moreTables; + } + # clean up the order for tables which are out of order + # (groups all Canon and Kodak tables together) + my %fixOrder; + foreach (@outOfOrder) { + next unless /^Image::ExifTool::(\w+)/; + # only re-order tables which have a corresponding main table + next unless $mainTables{$1}; + $fixOrder{$1} = []; # fix the order of these tables + } + my (@sortedTables, %fixPos, $pos); + foreach (@orderedTables) { + if (/^Image::ExifTool::(\w+)/ and $fixOrder{$1}) { + my $fix = $fixOrder{$1}; + unless (@$fix) { + $pos = @sortedTables; + $fixPos{$pos} or $fixPos{$pos} = []; + push @{$fixPos{$pos}}, $1; + } + push @{$fix}, $_; + } else { + push @sortedTables, $_; + } + } + # insert back in better order + foreach $pos (sort { $b <=> $a } keys %fixPos) { # (reverse sort) + my $fix = $fixPos{$pos}; + foreach (@$fix) { + splice(@sortedTables, $pos, 0, @{$fixOrder{$_}}); + } + } + # add the extra tables + push @sortedTables, @_; + # tweak the table order + TweakOrder(\@sortedTables, \%tweakOrder); + + return @sortedTables; +} + +#------------------------------------------------------------------------------ +# Open HTMLFILE and print header and description +# Inputs: 0) Filename, 1) optional category +# Returns: True on success +my %createdFiles; +sub OpenHtmlFile($;$$) +{ + my ($htmldir, $category, $sepTable) = @_; + my ($htmlFile, $head, $title, $url, $class); + my $top = ''; + + if ($category) { + my @names = split ' ', $category; + $class = shift @names; + $htmlFile = "$htmldir/TagNames/$class.html"; + $head = $category; + if ($head =~ /^\S+ .+ Struct$/) { + pop @names; + } else { + $head .= ($sepTable ? ' Values' : ' Tags'); + } + ($title = $head) =~ s/ .* / /; + @names and $url = join '_', @names; + } else { + $htmlFile = "$htmldir/TagNames/index.html"; + $category = $class = 'ExifTool'; + $head = $title = 'ExifTool Tag Names'; + } + if ($createdFiles{$htmlFile}) { + open(HTMLFILE, ">>${htmlFile}_tmp") or return 0; + } else { + open(HTMLFILE, ">${htmlFile}_tmp") or return 0; + print HTMLFILE "$docType<html>\n"; + print HTMLFILE "<!-- (this file generated automatically by Image::ExifTool::BuildTagLookup) -->\n"; + print HTMLFILE "<head>\n<title>$title</title>\n"; + print HTMLFILE "<link rel=stylesheet type='text/css' href='style.css' title='Style'>\n"; + print HTMLFILE "</head>\n<body>\n"; + if ($category ne $class and $docs{$class}) { + print HTMLFILE "<h2 class=top>$class Tags</h2>\n" or return 0; + print HTMLFILE '<p>',Doc2Html($docs{$class}),"</p>\n" or return 0; + } else { + $top = " class=top"; + } + } + $head = "<a name='${url}'>$head</a>" if $url; + print HTMLFILE "<h2$top>$head</h2>\n" or return 0; + print HTMLFILE '<p>',Doc2Html($docs{$category}),"</p>\n" if $docs{$category}; + $createdFiles{$htmlFile} = 1; + return 1; +} + +#------------------------------------------------------------------------------ +# Close all html files and write trailers +# Returns: true on success +# Inputs: 0) BuildTagLookup object reference +sub CloseHtmlFiles($) +{ + my $self = shift; + my $preserveDate = $$self{PRESERVE_DATE}; + my $success = 1; + # get the date + my ($sec,$min,$hr,$day,$mon,$yr) = localtime; + my @month = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); + $yr += 1900; + my $date = "$month[$mon] $day, $yr"; + my $htmlFile; + my $countNewFiles = 0; + my $countSameFiles = 0; + foreach $htmlFile (keys %createdFiles) { + my $tmpFile = $htmlFile . '_tmp'; + my $fileDate = $date; + if ($preserveDate) { + my @lines = `grep '<i>Last revised' $htmlFile`; + $fileDate = $1 if @lines and $lines[-1] =~ m{<i>Last revised (.*)</i>}; + } + open(HTMLFILE, ">>$tmpFile") or $success = 0, next; + # write the trailers + print HTMLFILE "<hr>\n"; + print HTMLFILE "(This document generated automatically by Image::ExifTool::BuildTagLookup)\n"; + print HTMLFILE "<br><i>Created $createDate</i>\n" if $htmlFile eq 'html/TagNames/index.html'; + print HTMLFILE "<br><i>Last revised $fileDate</i>\n"; + print HTMLFILE "<p class=lf><a href="; + if ($htmlFile =~ /index\.html$/) { + print HTMLFILE "'../index.html'>&lt;-- Back to ExifTool home page</a></p>\n"; + } else { + print HTMLFILE "'index.html'>&lt;-- ExifTool Tag Names</a></p>\n" + } + print HTMLFILE "</body>\n</html>\n" or $success = 0; + close HTMLFILE or $success = 0; + # check for differences and only use new file if it was changed + # (so the date only gets updated if changes were really made) + my $useNewFile; + if ($success) { + open (TEMPFILE, $tmpFile) or $success = 0, last; + if (open (HTMLFILE, $htmlFile)) { + while (<HTMLFILE>) { + my $newLine = <TEMPFILE>; + if (defined $newLine) { + next if /^<br><i>Last revised/; + next if $_ eq $newLine; + } + # files are different -- use the new file + $useNewFile = 1; + last; + } + $useNewFile = 1 if <TEMPFILE>; + close HTMLFILE; + } else { + $useNewFile = 1; + } + close TEMPFILE; + if ($useNewFile) { + ++$countNewFiles; + rename $tmpFile, $htmlFile or warn("Error renaming temporary file\n"), $success = 0; + } else { + ++$countSameFiles; + unlink $tmpFile; # erase new file and use existing file + } + } + last unless $success; + } + # save number of files processed so we can check the results later + $self->{COUNT}->{'HTML files changed'} = $countNewFiles; + $self->{COUNT}->{'HTML files unchanged'} = $countSameFiles; + return $success; +} + +#------------------------------------------------------------------------------ +# Get bitmask for POD documentation +# Inputs: mask string from HTML docs +# Returns: mask string for POD, or '' +sub PodMask($) +{ + my $mask = shift; + return '' unless $mask =~ /^\[val( >> (\d+))? \& (0x[\da-f]+)\]/; + return sprintf(' & 0x%.2x', hex($3) << ($2 || 0)); +} + +#------------------------------------------------------------------------------ +# Write the TagName HTML and POD documentation +# Inputs: 0) BuildTagLookup object reference +# 1) output pod file (eg. 'lib/Image/ExifTool/TagNames.pod') +# 2) output html directory (eg. 'html') +# Returns: true on success +# Notes: My apologies for the patchwork code, but this is only used to generate the docs. +sub WriteTagNames($$) +{ + my ($self, $podFile, $htmldir) = @_; + my ($tableName, $short, $url, @sepTables, @structs); + my $tagNameInfo = $self->{TAG_NAME_INFO} or return 0; + my $idLabel = $self->{ID_LOOKUP}; + my $shortName = $self->{SHORT_NAME}; + my $sepTable = $self->{SEPARATE_TABLE}; + my $structs = $self->{STRUCTURES}; + my $structLookup = $self->{STRUCT_LOOKUP}; + my $success = 1; + my $columns = 6; # number of columns in html index + my $percent = int(100 / $columns); + + # open the file and write the header + open(PODFILE, ">$podFile") or return 0; + $docs{ExifTool} = eval qq{"$docs{ExifTool}"}; + print PODFILE Doc2Pod($docs{PodHeader}, $docs{ExifTool}, $docs{ExifTool2}); + mkdir "$htmldir/TagNames", 0777; + OpenHtmlFile($htmldir) or return 0; + print HTMLFILE "<blockquote>\n"; + print HTMLFILE "<table width='100%' class=frame><tr><td>\n"; + print HTMLFILE "<table width='100%' class=inner cellspacing=1><tr class=h>\n"; + print HTMLFILE "<th colspan=$columns><span class=l>Tag Table Index</span></th></tr>\n"; + print HTMLFILE "<tr class=b><td width='$percent%'>\n"; + + # get the full table list, adding shortcuts and plug-in tables to the end + my @tableNames = GetTableOrder('Image::ExifTool::Shortcuts::Main', @pluginTables); + + # get list of headings and add any missing ones + my $heading = 'xxx'; + my (@tableIndexNames, @headings); + foreach $tableName (@tableNames) { + $short = $$shortName{$tableName}; + my @names = split ' ', $short; + my $class = shift @names; + if (@names and $class ne 'Other') { + # add heading for tables without a Main + unless ($heading eq $class) { + $heading = $class; + push @tableIndexNames, $heading; + push @headings, $heading; + } + } else { + $heading = $short; + push @headings, $heading; + } + push @tableIndexNames, $tableName; + } + @tableNames = @tableIndexNames; + # print html index of headings only + my $count = 0; + my $lines = int((scalar(@headings) + $columns - 1) / $columns); + foreach $tableName (@headings) { + if ($count) { + if ($count % $lines) { + print HTMLFILE "<br>\n"; + } else { + print HTMLFILE "</td><td width='$percent%'>\n"; + } + } + $short = $$shortName{$tableName}; + $short = $tableName unless $short; + $url = "$short.html"; + # handle various tables in "Other.pm" + if ($short =~ /^Other (.*)/) { + $short = $1; + $url = 'Other.html#' . $1; + } else { + $url = "$short.html"; + } + print HTMLFILE "<a href='${url}'>$short</a>"; + ++$count; + } + print HTMLFILE "\n</td></tr></table></td></tr></table></blockquote>\n"; + print HTMLFILE '<p>',Doc2Html($docs{ExifTool2}),"</p>\n"; + + # write all the tag tables + while (@tableNames or @sepTables or @structs) { + while (@sepTables) { + $tableName = shift @sepTables; + my $printConv = $$sepTable{$tableName}; + next unless ref $printConv eq 'HASH'; + $$sepTable{$tableName} = 1; + my $notes = $$printConv{Notes}; + if ($notes) { + # remove unnecessary whitespace + $notes =~ s/^\s+//; $notes =~ s/\s+$//; + $notes =~ s/(^[ \t]+|[ \t]+$)//mg; + } + my $head = $tableName; + $head =~ s/^.* //s; + close HTMLFILE; + if (OpenHtmlFile($htmldir, $tableName, 1)) { + print HTMLFILE '<p>', Doc2Html($notes), "</p>\n" if $notes; + print HTMLFILE "<blockquote>\n"; + print HTMLFILE "<table class=frame><tr><td>\n"; + print HTMLFILE "<table class='inner sep' cellspacing=1>\n"; + my $align = ' class=r'; + my $wid = 0; + my @keys; + foreach (sort { NumbersFirst($a,$b) } keys %$printConv) { + next if /^(Notes|PrintHex|PrintInt|PrintString|OTHER)$/; + $align = '' if $align and /[^\d]/; + my $w = length($_) + length($$printConv{$_}); + $wid = $w if $wid < $w; + push @keys, $_; + if ($$printConv{$_} =~ /[\0-\x1f\x7f-\xff]/) { + warn "Warning: Special characters in $tableName PrintConv ($$printConv{$_})\n"; + } + } + $wid = length($tableName)+7 if $wid < length($tableName)+7; + # print in multiple columns if there is room + my $cols = int(110 / ($wid + 4)); + $cols = 1 if $cols < 1 or $cols > @keys or @keys < 4; + my $rows = int((scalar(@keys) + $cols - 1) / $cols); + my ($r, $c); + print HTMLFILE '<tr class=h>'; + for ($c=0; $c<$cols; ++$c) { + print HTMLFILE "<th>Value</th><th>$head</th>"; + } + print HTMLFILE "</tr>\n"; + for ($r=0; $r<$rows; ++$r) { + print HTMLFILE '<tr>'; + for ($c=0; $c<$cols; ++$c) { + my $key = $keys[$r + $c*$rows]; + my ($index, $prt); + if (defined $key) { + $index = $key; + $prt = '= ' . EscapeHTML($$printConv{$key}); + $index =~ s/\.\d+$// if $$printConv{PrintInt}; + if ($$printConv{PrintHex}) { + $index =~ s/(\.\d+)$//; # remove decimal + $index = sprintf('0x%x',$index); + $index .= $1 if $1; # add back decimal + } elsif ($$printConv{PrintString} or + $index !~ /^[+-]?(?=\d|\.\d)\d*(\.\d*)?$/) + { + $index = "'" . EscapeHTML($index) . "'"; + } + } else { + $index = $prt = '&nbsp;'; + } + my ($ic, $pc); + if ($c & 0x01) { + $pc = ' class=b'; + $ic = $align ? " class='r b'" : $pc; + } else { + $ic = $align; + $pc = ''; + } + print HTMLFILE "<td$ic>$index</td><td$pc>$prt</td>\n"; + } + print HTMLFILE '</tr>'; + } + print HTMLFILE "</table></td></tr></table></blockquote>\n\n"; + } + } + last unless @tableNames or @structs; + my $isStruct; + if (@structs) { + $tableName = shift @structs; + next if $$structs{$tableName} == 2; # only list each structure once + $$structs{$tableName} = 2; + $isStruct = $$structLookup{$tableName}; + $isStruct or warn("Missing structure $tableName\n"), next; + $tableName = "XMP $tableName" unless $tableName =~ / /; + $short = $tableName = "$tableName Struct"; + my $maxLen = 0; + $maxLen < length and $maxLen = length foreach keys %$isStruct; + $$self{LONG_ID}{$tableName} = $maxLen; + } else { + $tableName = shift @tableNames; + $short = $$shortName{$tableName}; + unless ($short) { + # this is just an index heading + print PODFILE "\n=head2 $tableName Tags\n"; + print PODFILE Doc2Pod($docs{$tableName}) if $docs{$tableName}; + next; + } + } + my $isExif = ($tableName eq 'Image::ExifTool::Exif::Main'); + my $isRiff = ($tableName eq 'Image::ExifTool::RIFF::Info'); + my $isXmpMain = ($tableName eq 'Image::ExifTool::XMP::Main'); + my $info = $$tagNameInfo{$tableName}; + my $id = $$idLabel{$tableName}; + my ($hid, $showGrp); + # widths of the different columns in the POD documentation + my ($wID,$wTag,$wReq,$wGrp) = (8,36,24,10); + my ($composite, $derived, $notes, $longTags, $wasLong, $prefix); + if ($short eq 'Shortcuts') { + $derived = '<th>Refers To</th>'; + $composite = 2; + } elsif ($isStruct) { + $derived = ''; + $notes = $$isStruct{NOTES}; + } else { + my $table = GetTagTable($tableName); + $notes = $$table{NOTES}; + if ($$table{NAMESPACE}) { + my $ns = $Image::ExifTool::XMP::stdXlatNS{$$table{NAMESPACE}} || $$table{NAMESPACE}; + my $msg = "These tags belong to the ExifTool XMP-$ns family 1 group."; + if ($notes) { + $notes =~ s/\s+$//; + $notes .= "\n\n" . $msg; + } else { + $notes = $msg; + } + } + $longTags = $$table{VARS}{LONG_TAGS} if $$table{VARS}; + if ($$table{GROUPS}{0} eq 'Composite') { + $composite = 1; + $derived = '<th>Derived From</th>'; + } else { + $composite = 0; + $derived = ''; + } + } + my $podIdLen = $self->{LONG_ID}->{$tableName}; + if ($notes) { + # remove unnecessary whitespace + $notes =~ s/^\s+//; $notes =~ s/\s+$//; + $notes =~ s/(^[ \t]+|[ \t]+$)//mg; + if ($notes =~ /leading '(.*?_)' which/) { + $prefix = $1; + $podIdLen -= length $prefix; + } + } + if ($podIdLen <= $wID) { + $podIdLen = $wID; + } elsif ($short eq 'DICOM') { + $podIdLen = 10; + } else { + # align tag names in secondary columns if possible + my $col = ($podIdLen <= 10) ? 12 : 20; + $podIdLen = $col if $podIdLen < $col; + } + if ($id) { + ($hid = "<th>$id</th>") =~ s/ /&nbsp;/g; + $wTag -= $podIdLen - $wID; + $wID = $podIdLen; + my $longTag = $self->{LONG_NAME}->{$tableName}; + if ($wTag < $longTag) { + if ($wID - $longTag + $wTag >= 6) { # don't let ID column get too narrow + $wID -= $longTag - $wTag; + $wTag = $longTag; + } + $wasLong = 1 if $wID <= $self->{LONG_ID}->{$tableName}; + } + } elsif ($composite) { + $wTag += $wID - $wReq; + $hid = ''; + } else { + $wTag += 9; + $hid = ''; + } + if ($short eq 'EXIF' or $short eq 'Extra') { + $derived = '<th>Group</th>'; + $showGrp = 1; + $wGrp += 8 if $short eq 'Extra'; # tweak Group column width for "Extra" tags + $wTag -= $wGrp + 1; + } + my $head = ($short =~ / /) ? 'head3' : 'head2'; + my $str = $isStruct ? '' : ' Tags'; + print PODFILE "\n=$head $short$str\n"; + print PODFILE Doc2Pod($docs{$short}) if $docs{$short}; + print PODFILE "\n", Doc2Pod($notes), "\n" if $notes; + my $line = "\n"; + if ($id) { + # shift over 'Index' heading by one character for a bit more balance + $id = " $id" if $id eq 'Index'; + $line .= sprintf " %-${wID}s", $id; + } else { + $line .= ' '; + } + my $tagNameHeading = ($short eq 'XMP') ? 'Namespace' : ($isStruct?'Field':'Tag').' Name'; + $line .= sprintf " %-${wTag}s", $tagNameHeading; + $line .= sprintf " %-${wReq}s", $composite == 2 ? 'Refers To' : 'Derived From' if $composite; + $line .= sprintf " %-${wGrp}s", 'Group' if $showGrp; + $line .= ' Writable'; + print PODFILE $line; + $line =~ s/^(\s*\w.{6}\w) /$1\t/; # change space to tab after long ID label (eg. "Sequence") + $line =~ s/\S/-/g; + $line =~ s/- -/---/g; + $line =~ tr/\t/ /; # change tab back to space + print PODFILE $line,"\n"; + close HTMLFILE; + OpenHtmlFile($htmldir, $short) or $success = 0; + print HTMLFILE '<p>',Doc2Html($notes), "</p>\n" if $notes; + print HTMLFILE "<blockquote>\n"; + print HTMLFILE "<table class=frame><tr><td>\n"; + print HTMLFILE "<table class=inner cellspacing=1>\n"; + print HTMLFILE "<tr class=h>$hid<th>$tagNameHeading</th>\n"; + print HTMLFILE "<th>Writable</th>$derived<th>Values / ${noteFont}Notes</span></th></tr>\n"; + my $rowClass = 1; + my $infoCount = 0; + my $infoList; + foreach $infoList (@$info) { + ++$infoCount; + my ($tagIDstr, $tagNames, $writable, $values, $require, $writeGroup) = @$infoList; + my ($align, $idStr, $w, $tip); + my $wTag2 = $wTag; + if (not $id) { + $idStr = ' '; + } elsif ($tagIDstr =~ /^-?\d+(\.\d+)?$/) { + $w = $wID - 3; + $idStr = sprintf " %${w}g ", $tagIDstr; + my $tooLong = length($idStr) - 6 - $w; + if ($tooLong) { + $tooLong = 3 if $tooLong > 3; + $idStr = substr($idStr, 0, -$tooLong); + } + $align = " class=r"; + } else { + $tagIDstr =~ s/^'$prefix/'/ if $prefix; + $w = $wID; + my $over = length($tagIDstr) - $w; + if ($over > 0) { + # shift over tag name if there is room + if ($over <= $wTag - length($$tagNames[0])) { + $wTag2 -= $over; + $w += $over; + } else { + # put tag name on next line if ID is too long + $idStr = " $tagIDstr\n " . (' ' x $w); + if ($longTags) { + --$longTags; + } else { + warn "Notice: Split $$tagNames[0] line\n"; + } + } + } + $idStr = sprintf " %-${w}s ", $tagIDstr unless defined $idStr; + $align = ''; + } + my @reqs; + my @tags = @$tagNames; + my @wGrp = @$writeGroup; + my @vals = @$writable; + my $wrStr = shift @vals; + my $subdir; + my @masks = grep /^\[val( >> \d+)? \& 0x[\da-f]+\]/, @$values; + my $tag = shift @tags; + # if this is a subdirectory or structure, print subdir name (from values) instead of writable + if ($wrStr =~ /^[-=]/) { + $subdir = 1; + if (@masks) { + # combine any mask into the format string + $wrStr .= PodMask($masks[0]); + shift @masks; + @vals = grep !/^\[val( >> \d+)? \& 0x[\da-f]+\]/, @$values; + } else { + @vals = @$values; + } + # remove Notes if subdir has Notes as well + shift @vals if $vals[0] =~ /^\(/ and @vals >= @$writable; + foreach (@vals) { /^\(/ and $_ = '-' } + my $i; # fill in any missing entries from non-directory tags + for ($i=0; $i<@$writable; ++$i) { + $vals[$i] = $$writable[$i] unless defined $vals[$i]; + if (@masks) { + $vals[$i] .= PodMask($masks[0]); + shift @masks; + } + } + if ($$sepTable{$vals[0]}) { + $wrStr =~ s/^[-=]//; + $wrStr = 'no' unless $wrStr; + } elsif ($$structs{$vals[0]}) { + my $flags = $wrStr =~ /([+_]+)$/ ? $1 : ''; + $wrStr = "$vals[0] Struct$flags"; + } else { + $wrStr = $vals[0]; + } + shift @vals; + } elsif ($wrStr and $wrStr ne 'no' and @masks) { + # fill in missing entries if masks are different + my $mask = shift @masks; + while (@masks > @vals) { + last if $masks[@vals] eq $mask; + push @vals, $wrStr; + push @tags, $tag if @tags < @vals; + } + # add Mask to Writable column in POD doc + $wrStr .= PodMask($mask); + } + my $pod = sprintf "%s%-${wTag2}s", $idStr, $tag; + my $tGrp = $wGrp; + if ($id and length($tag) > $wTag2) { + my $madeRoom; + if ($showGrp) { + my $wGrp0 = length($wGrp[0] || '-'); + if (not $composite and $wGrp > $wGrp0) { + $tGrp = $wGrp - (length($tag) - $wTag2); + if ($tGrp < length $wGrp0) { + $tGrp = length $wGrp0; + } else { + $madeRoom = 1; + } + } + } + warn "Warning: Pushed $tag\n" unless $madeRoom; + } + $pod .= sprintf " %-${tGrp}s", shift(@wGrp) || '-' if $showGrp; + if ($composite) { + @reqs = @$require; + $w = $wReq; # Keep writable column in line + length($tag) > $wTag2 and $w -= length($tag) - $wTag2; + $pod .= sprintf " %-${w}s", shift(@reqs) || ''; + } + $pod .= " $wrStr"; + # limit line length to 82 characters (even if it means messing up column alignment) + if (length $pod > 82 and $pod !~ /\n/) { + my $remove = length($pod) - 82; + $pod =~ s/(\w)\s{$remove}/$1/; + } + print PODFILE $pod, "\n"; + my $numTags = scalar @$tagNames; + my $n = 0; + while (@tags or @reqs or @vals) { + my $more = (@tags or @reqs); + $line = ' '; + $line .= ' 'x($wID+1) if $id; + $line .= sprintf("%-${wTag2}s", shift(@tags) || ''); + $line .= sprintf(" %-${wReq}s", shift(@reqs) || '') if $composite; + $line .= sprintf(" %-${wGrp}s", shift(@wGrp) || '-') if $showGrp; + ++$n; + if (@vals) { + my $val = shift @vals; + # use writable if this is a note + my $wrStr = $$writable[$n]; + if ($subdir and ($val =~ /^\(/ or $val =~ /=/ or ($wrStr and $wrStr !~ /^[-=]/))) { + $val = $wrStr; + if (defined $val) { + $val =~ s/^[-=]//; + } else { + # done with tag if nothing else to print + last unless $more; + } + } + if (defined $val) { + $line .= " $val"; + if (@masks) { + $line .= PodMask($masks[0]); + shift @masks; + } + } + } + $line =~ s/\s+$//; # trim trailing white space + print PODFILE "$line\n"; + } + my @htmlTags; + foreach (@$tagNames) { + push @htmlTags, EscapeHTML($_); + } + if (($isExif and $Image::ExifTool::Validate::exifSpec{hex $tagIDstr}) or + ($isRiff and $tagIDstr=~/(\w+)/ and $riffSpec{$1}) or + ($isXmpMain and $tagIDstr=~/([-\w]+)/ and $xmpSpec{$1})) + { + # underline "unknown" makernote tags only + my $n = $tagIDstr eq '0x927c' ? -1 : 0; + $htmlTags[$n] = "<u>$htmlTags[$n]</u>"; + } + $rowClass = $rowClass ? '' : " class=b"; + my $isSubdir; + if ($$writable[0] =~ /^[-=]/) { + $isSubdir = 1; + s/^[-=](.+)/$1/ foreach @$writable; + } + # add tooltip for hex conversion of Tag ID + if ($tagIDstr =~ /^(0x[0-9a-f]+)(\.\d+)?$/i) { + $tip = sprintf(" title='$tagIDstr = %u%s'", hex($1), $2||''); + } elsif ($tagIDstr =~ /^(\d+)(\.\d*)?$/) { + $tip = sprintf(" title='%u = 0x%x'", $1, $1); + } else { + $tip = ''; + # use copyright symbol in QuickTime UserData tags + $tagIDstr =~ s/^"\\xa9/"&copy;/; + # escape necessary characters in html + $tagIDstr =~ s/>/&gt;/g; + $tagIDstr =~ s/</&lt;/g; + } + # add tooltip for special writable attributes + my $wtip = ''; + my %wattr = ( + '_' => 'Flattened', + '+' => 'List', + '/' => 'Avoid', + '~' => 'Writable only with -n', + '!' => 'Unsafe', + '*' => 'Protected', + ':' => 'Mandatory', + '^' => 'Deletable', + ); + my ($wstr, %hasAttr, @hasAttr); + foreach $wstr (@$writable) { + next unless $wstr =~ m{([+/~!*:_^]+)$}; + my @a = split //, $1; + foreach (@a) { + next if $hasAttr{$_}; + push @hasAttr, $_; + $hasAttr{$_} = 1; + } + } + if (@hasAttr) { + $wtip = " title='"; + my $n = 0; + foreach (@hasAttr) { + $wtip .= "\n" if $n; + $wtip .= " $_ = $wattr{$_}"; + ++$n; + } + $wtip .= "'"; + } + # print this row in the tag table + print HTMLFILE "<tr$rowClass>\n"; + print HTMLFILE "<td$align$tip>$tagIDstr</td>\n" if $id; + print HTMLFILE "<td>", join("\n <br>",@htmlTags), "</td>\n"; + print HTMLFILE "<td class=c$wtip>",join('<br>',@$writable),"</td>\n"; + print HTMLFILE '<td class=n>',join("\n <br>",@$require),"</td>\n" if $composite; + print HTMLFILE "<td class=c>",join('<br>',@$writeGroup),"</td>\n" if $showGrp; + print HTMLFILE "<td>"; + if (@$values) { + if ($isSubdir) { + my ($smallNote, @values); + foreach (@$values) { + if (/^\(/) { + $_ = Doc2Html($_); + # set the note font + $smallNote = 1 if $numTags < 2; + push @values, ($smallNote ? $noteFontSmall : $noteFont) . "$_</span>"; + next; + } + # make text in square brackets small + if (/^\[/) { + if (s/^\[!HTML\]//) { + push @values, $_; + } else { + $_ = Doc2Html($_); + push @values, "<span class=s>$_</span>"; + } + next; + } + /=/ and push(@values, $_), next; + my @names = split; + my $suffix = ' Tags'; + if ($$sepTable{$_}) { + push @sepTables, $_; + $suffix = ' Values'; + } + # handle structures specially + if ($$structs{$_}) { + # assume XMP module for this struct unless otherwise specified + unshift @names, 'XMP' unless / /; + push @structs, $_; # list this later + # hack to put Area Struct in with XMP tags, + # even though it is only used by the MWG module + push @structs, 'Area' if $_ eq 'Dimensions'; + $suffix = ' Struct'; + } + $url = (shift @names) . '.html'; + @names and $url .= '#' . join '_', @names; + push @values, "--&gt; <a href='${url}'>$_$suffix</a>"; + } + # put small note last + $smallNote and push @values, shift @values; + print HTMLFILE join("\n <br>",@values); + } else { + my ($close, $br) = ('', ''); + foreach (@$values) { + if (s/^\[!HTML\]//) { + print HTMLFILE $close if $close; + print HTMLFILE $_; + $close = $br = ''; + } else { + if (/^\(/) { + # notes can use POD syntax + $_ = $noteFont . Doc2Html($_) . "</span>"; + } else { + $_ = EscapeHTML($_); + } + $close or $_ = "<span class=s>$_", $close = '</span>'; + print HTMLFILE $br, $_; + $br = "\n <br>"; + } + } + print HTMLFILE $close if $close; + } + } else { + print HTMLFILE '&nbsp;'; + } + print HTMLFILE "</td></tr>\n"; + } + warn "$longTags unaccounted-for long tags in $tableName\n" if $longTags; + if ($wasLong and not defined $longTags) { + warn "Notice: Long tags in $tableName table\n"; + } + unless ($infoCount) { + print PODFILE " [no tags known]\n"; + my $cols = 3; + ++$cols if $hid; + ++$cols if $derived; + print HTMLFILE "<tr><td colspan=$cols class=c><i>[no tags known]</i></td></tr>\n"; + } + print HTMLFILE "</table></td></tr></table></blockquote>\n\n"; + } + close(HTMLFILE) or $success = 0; + CloseHtmlFiles($self) or $success = 0; + print PODFILE Doc2Pod($docs{PodTrailer}) or $success = 0; + close(PODFILE) or $success = 0; + return $success; +} + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::BuildTagLookup - Build ExifTool tag lookup tables + +=head1 DESCRIPTION + +This module is used to generate the tag lookup tables in +Image::ExifTool::TagLookup.pm and tag name documentation in +Image::ExifTool::TagNames.pod, as well as HTML tag name documentation. It +is used before each new ExifTool release to update the lookup tables and +documentation, but it is not used otherwise. It also performs some +validation and consistency checks on the tag tables. + +=head1 SYNOPSIS + + use Image::ExifTool::BuildTagLookup; + + $builder = new Image::ExifTool::BuildTagLookup; + + # update Image::ExifTool::TagLookup + $ok = $builder->WriteTagLookup('lib/Image/ExifTool/TagLookup.pm'); + + # update the tag name documentation + $ok = $builder->WriteTagNames('lib/Image/ExifTool/TagNames.pod','html'); + + # print some statistics + my $count = $$builder{COUNT}; + foreach (sort keys %$count) { + printf "%5d %s\n", $$count{$_}, $_; + } + +=head1 MEMBER VARIABLES + +=over 4 + +=item PRESERVE_DATE + +Flag to preserve "Last revised" date in HTML files. Set before calling +WriteTagNames(). + +=item COUNT + +Reference to hash containing counting statistics. Keys are the +descriptions, and values are the numerical counts. Valid after +BuildTagLookup object is created, but additional statistics are added by +WriteTagNames(). + +=item WRITE_PSEUDO + +Returned list of writable pseudo tags. + +=back + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagLookup(3pm)|Image::ExifTool::TagLookup>, +L<Image::ExifTool::TagNames(3pm)|Image::ExifTool::TagNames> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/CBOR.pm b/ExifTool/lib/Image/ExifTool/CBOR.pm new file mode 100644 index 0000000..08fbf66 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/CBOR.pm @@ -0,0 +1,331 @@ +#------------------------------------------------------------------------------ +# File: CBOR.pm +# +# Description: Read CBOR format metadata +# +# Revisions: 2021-09-30 - P. Harvey Created +# +# References: 1) https://c2pa.org/public-draft/ +# 2) https://datatracker.ietf.org/doc/html/rfc7049 +#------------------------------------------------------------------------------ + +package Image::ExifTool::CBOR; +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::JSON; + +$VERSION = '1.01'; + +sub ProcessCBOR($$$); +sub ReadCBORValue($$$$); + +# optional CBOR type code +my %cborType6 = ( + 0 => 'date/time string', + 1 => 'epoch-based date/time', + 2 => 'positive bignum', + 3 => 'negative bignum', + 4 => 'decimal fraction', + 5 => 'bigfloat', + 21 => 'expected base64url encoding', + 22 => 'expected base64 encoding', + 23 => 'expected base16 encoding', + 24 => 'encoded CBOR data', + 32 => 'URI', + 33 => 'base64url', + 34 => 'base64', + 35 => 'regular expression', + 36 => 'MIME message', + 55799 => 'CBOR magic number', +); + +my %cborType7 = ( + 20 => 'False', + 21 => 'True', + 22 => 'null', + 23 => 'undef', +); + +%Image::ExifTool::CBOR::Main = ( + GROUPS => { 0 => 'JUMBF', 1 => 'CBOR', 2 => 'Other' }, + VARS => { NO_ID => 1 }, + PROCESS_PROC => \&ProcessCBOR, + NOTES => q{ + The tags below are extracted from CBOR (Concise Binary Object + Representation) metadata. The C2PA specification uses this format for some + metadata. As well as these tags, ExifTool will read any existing tags. + }, + 'dc:title' => 'Title', + 'dc:format' => 'Format', + # my sample file has the following 2 tags in CBOR, but they should be JSON + authorName => { Name => 'AuthorName', Groups => { 2 => 'Author' } }, + authorIdentifier=> { Name => 'AuthorIdentifier', Groups => { 2 => 'Author' } }, + documentID => { }, + instanceID => { }, + thumbnailHash => { List => 1 }, + thumbnailUrl => { Name => 'ThumbnailURL' }, + relationship => { } +); + +#------------------------------------------------------------------------------ +# Read CBOR value +# Inputs: 0) ExifTool ref, 1) data ref, 2) position in data, 3) data end +# Returns: 0) value, 1) error string, 2) new data position +sub ReadCBORValue($$$$) +{ + my ($et, $dataPt, $pos, $end) = @_; + return(undef, 'Truncated CBOR data', $pos) if $pos >= $end; + my $verbose = $$et{OPTIONS}{Verbose}; + my $indent = $$et{INDENT}; + my $dumpStart = $pos; + my $fmt = Get8u($dataPt, $pos++); + my $dat = $fmt & 0x1f; + my ($num, $val, $err, $size); + $fmt >>= 5; + if ($dat < 24) { + $num = $dat; + } elsif ($dat == 31) { # indefinite count (not used in C2PA) + $num = -1; # (flag for indefinite count) + $et->VPrint(1, "$$et{INDENT} (indefinite count):\n"); + } else { + my $format = { 24 => 'int8u', 25 => 'int16u', 26 => 'int32u', 27 => 'int64u' }->{$dat}; + return(undef, "Invalid CBOR integer type $dat", $pos) unless $format; + $size = Image::ExifTool::FormatSize($format); + return(undef, 'Truncated CBOR integer value', $pos) if $pos + $size > $end; + $num = ReadValue($dataPt, $pos, $format, 1, $size); + $pos += $size; + } + my $pre = ''; + if (defined $$et{cbor_pre} and $fmt != 6) { + $pre = $$et{cbor_pre}; + delete $$et{cbor_pre}; + } + if ($fmt == 0) { # positive integer + $val = $num; + $et->VPrint(1, "$$et{INDENT} ${pre}int+: $val\n"); + } elsif ($fmt == 1) { # negative integer + $val = -1 * $num; + $et->VPrint(1, "$$et{INDENT} ${pre}int-: $val\n"); + } elsif ($fmt == 2 or $fmt == 3) { # byte/UTF8 string + return(undef, 'Truncated CBOR string value', $pos) if $pos + $num > $end; + if ($num < 0) { # (should not happen in C2PA) + my $string = ''; + $$et{INDENT} .= ' '; + for (;;) { + ($val, $err, $pos) = ReadCBORValue($et, $dataPt, $pos, $end); + return(undef, $err, $pos) if $err; + last if not defined $val; # hit the break? + # (note: strictly we should be checking that this was a string we read) + $string .= $val; + } + $$et{INDENT} = $indent; + return($string, undef, $pos); # return concatenated byte/text string + } else { + $val = substr($$dataPt, $pos, $num); + } + $pos += $num; + if ($fmt == 2) { # (byte string) + $et->VPrint(1, "$$et{INDENT} ${pre}byte: <binary data ".length($val)." bytes>\n"); + my $dat = $val; + $val = \$dat; # use scalar reference for binary data + } else { # (text string) + $val = $et->Decode($val, 'UTF8'); + $et->VPrint(1, "$$et{INDENT} ${pre}text: '${val}'\n"); + } + } elsif ($fmt == 4 or $fmt == 5) { # list/hash + if ($fmt == 4) { + $et->VPrint(1, "$$et{INDENT} ${pre}list: <$num elements>\n"); + } else { + $et->VPrint(1, "$$et{INDENT} ${pre}hash: <$num pairs>\n"); + $num *= 2; + } + $$et{INDENT} .= ' '; + my $i = 0; + my @list; + Image::ExifTool::HexDump($dataPt, $pos - $dumpStart, + Start => $dumpStart, + DataPos => $$et{cbor_datapos}, + Prefix => $$et{INDENT}, + ) if $verbose > 2; + while ($num) { + $$et{cbor_pre} = "$i) "; + if ($fmt == 4) { + ++$i; + } elsif ($num & 0x01) { + $$et{cbor_pre} = ' ' x length($$et{cbor_pre}); + ++$i; + } + ($val, $err, $pos) = ReadCBORValue($et, $dataPt, $pos, $end); + return(undef, $err, $pos) if $err; + if (not defined $val) { + return(undef, 'Unexpected list terminator', $pos) unless $num < 0; + last; + } + push @list, $val; + --$num; + } + $dumpStart = $pos; + $$et{INDENT} = $indent; + if ($fmt == 5) { + my ($i, @keys); + my %hash = ( _ordered_keys_ => \@keys ); + for ($i=0; $i<@list-1; $i+=2) { + $hash{$list[$i]} = $list[$i+1]; + push @keys, $list[$i]; # save ordered list of keys + } + $val = \%hash; + } else { + $val = \@list; + } + } elsif ($fmt == 6) { # optional tag + if ($verbose) { + my $str = "$num (" . ($cborType6{$num} || 'unknown') . ')'; + my $spc = $$et{cbor_pre} ? (' ' x length $$et{cbor_pre}) : ''; + $et->VPrint(1, "$$et{INDENT} $spc<CBOR optional type $str>\n"); + Image::ExifTool::HexDump($dataPt, $pos - $dumpStart, + Start => $dumpStart, + DataPos => $$et{cbor_datapos}, + Prefix => $$et{INDENT} . ' ', + ) if $verbose > 2; + } + # read next value (note: in the case of multiple tags, + # this nesting will apply the tags in the correct order) + ($val, $err, $pos) = ReadCBORValue($et, $dataPt, $pos, $end); + $dumpStart = $pos; + # convert some values according to the optional tag number (untested) + if ($num == 0 and not ref $val) { # date/time string + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::ConvertXMPDate($val); + } elsif ($num == 1 and not ref $val) { # epoch-based date/time + if (Image::ExifTool::IsFloat($val)) { + my $dec = ($val == int($val)) ? undef : 6; + $val = Image::ExifTool::ConvertUnixTime($val, 1, $dec); + } + } elsif (($num == 2 or $num == 3) and ref($val) eq 'SCALAR') { # pos/neg bignum + my $big = 0; + $big = 256 * $big + Get8u($val,$_) foreach 0..(length($$val) - 1); + $val = $num==2 ? $big : -$big; + } elsif (($num == 4 or $num == 5) and # decimal fraction or bigfloat + ref($val) eq 'ARRAY' and @$val == 2 and + Image::ExifTool::IsInt($$val[0]) and Image::ExifTool::IsInt($$val[1])) + { + $val = $$val[1] * ($num == 4 ? 10 : 2) ** $$val[0]; + } + } elsif ($fmt == 7) { + if ($dat == 31) { + undef $val; # "break" = end of indefinite array/hash (not used in C2PA) + } elsif ($dat < 24) { + $val = $cborType7{$num}; + $val = "Unknown ($val)" unless defined $val; + } elsif ($dat == 25) { # half-precision float + my $exp = ($num >> 10) & 0x1f; + my $mant = $num & 0x3ff; + if ($exp == 0) { + $val = $mant ** -24; + $val *= -1 if $num & 0x8000; + } elsif (exp != 31) { + $val = ($mant + 1024) ** ($exp - 25); + $val *= -1 if $num & 0x8000; + } else { + $val = $mant == 0 ? '<inf>' : '<nan>'; + } + } elsif ($dat == 26) { # float + $val = GetFloat($dataPt, $pos - $size); + } elsif ($dat == 27) { # double + $val = GetDouble($dataPt, $pos - $size); + } else { + return(undef, "Invalid CBOR type 7 variant $num", $pos); + } + $et->VPrint(1, "$$et{INDENT} ${pre}typ7: ".(defined $val ? $val : '<break>')."\n"); + } else { + return(undef, "Unknown CBOR format $fmt", $pos); + } + Image::ExifTool::HexDump($dataPt, $pos - $dumpStart, + Start => $dumpStart, + DataPos => $$et{cbor_datapos}, + Prefix => $$et{INDENT} . ' ', + MaxLen => $verbose < 5 ? ($verbose == 3 ? 96 : 2048) : undef, + ) if $verbose > 2; + return($val, $err, $pos); +} + +#------------------------------------------------------------------------------ +# Read CBOR box +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessCBOR($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $end = $pos + $$dirInfo{DirLen}; + my ($val, $err, $tag, $i); + + $et->VerboseDir('CBOR', undef, $$dirInfo{DirLen}); + + $$et{cbor_datapos} = $$dirInfo{DataPos} + $$dirInfo{Base}; + + while ($pos < $end) { + ($val, $err, $pos) = ReadCBORValue($et, $dataPt, $pos, $end); + $err and $et->Warn($err), last; + if (ref $val eq 'HASH') { + foreach $tag (@{$$val{_ordered_keys_}}) { + Image::ExifTool::JSON::ProcessTag($et, $tagTablePtr, $tag, $$val{$tag}); + } + } elsif (ref $val eq 'ARRAY') { + for ($i=0; $i<@$val; ++$i) { + Image::ExifTool::JSON::ProcessTag($et, $tagTablePtr, "Item$i", $$val[$i]); + } + } elsif ($val eq '0') { + $et->VPrint(1, "$$et{INDENT} <CBOR end>\n"); + last; # (treat as padding) + } else { + $et->VPrint(1, "$$et{INDENT} Unknown value: $val\n"); + } + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::CBOR - Read CBOR format metadata + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool read Concise +Binary Object Representation (CBOR) formatted metadata, used by the C2PA +specification. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://c2pa.org/public-draft/> + +=item L<https://datatracker.ietf.org/doc/html/rfc7049> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/CBOR Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Canon.pm b/ExifTool/lib/Image/ExifTool/Canon.pm new file mode 100644 index 0000000..c8e391f --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Canon.pm @@ -0,0 +1,10275 @@ +#------------------------------------------------------------------------------ +# File: Canon.pm +# +# Description: Canon EXIF maker notes tags +# +# Revisions: 11/25/2003 - P. Harvey Created +# 12/03/2003 - P. Harvey Decode lots more tags and add CanonAFInfo +# 02/17/2004 - Michael Rommel Added IxusAFPoint +# 01/27/2005 - P. Harvey Disable validation of CanonAFInfo +# 01/30/2005 - P. Harvey Added a few more tags (ref 4) +# 02/10/2006 - P. Harvey Decode a lot of new tags (ref 12) +# [ongoing] - P. Harvey Constantly decoding new information +# +# Notes: Must check FocalPlaneX/YResolution values for each new model! +# +# References: 1) http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html +# 2) Michael Rommel private communication (Digital Ixus) +# 3) Daniel Pittman private communication (PowerShot S70) +# 4) http://www.wonderland.org/crw/ +# 5) Juha Eskelinen private communication (20D) +# 6) Richard S. Smith private communication (20D) +# 7) Denny Priebe private communication (1DmkII) +# 8) Irwin Poche private communication +# 9) Michael Tiemann private communication (1DmkII) +# 10) Volker Gering private communication (1DmkII) +# 11) "cip" private communication +# 12) Rainer Honle private communication (5D) +# 13) http://www.cybercom.net/~dcoffin/dcraw/ +# 14) (bozi) http://www.cpanforum.com/threads/2476 and /2563 +# 15) http://homepage3.nifty.com/kamisaka/makernote/makernote_canon.htm (2007/11/19) +# + http://homepage3.nifty.com/kamisaka/makernote/CanonLens.htm (2007/11/19) +# 16) Emil Sit private communication (30D) +# 17) http://www.asahi-net.or.jp/~xp8t-ymzk/s10exif.htm +# 18) Samson Tai private communication (G7) +# 19) Warren Stockton private communication +# 20) Bogdan private communication +# 21) Heiko Hinrichs private communication +# 22) Dave Nicholson private communication (PowerShot S30) +# 23) Magne Nilsen private communication (400D) +# 24) Wolfgang Hoffmann private communication (40D) +# 25) Laurent Clevy private communication +# 26) Steve Balcombe private communication +# 27) Chris Huebsch private communication (40D) +# 28) Hal Williamson private communication (XTi) +# 29) Ger Vermeulen private communication +# 30) David Pitcher private communication (1DmkIII) +# 31) Darryl Zurn private communication (A590IS) +# 32) Rich Taylor private communication (5D) +# 33) D.J. Cristi private communication +# 34) Andreas Huggel and Pascal de Bruijn private communication +# 35) Jan Boelsma private communication +# 36) Karl-Heinz Klotz private communication (http://www.dslr-forum.de/showthread.php?t=430900) +# 37) Vesa Kivisto private communication (30D) +# 38) Kurt Garloff private communication (5DmkII) +# 39) Irwin Poche private communication (5DmkII) +# 40) Jose Oliver-Didier private communication +# 41) http://www.cpanforum.com/threads/10730 +# 42) Norbert Wasser private communication +# 43) Karsten Sote private communication +# 44) Hugh Griffiths private communication (5DmkII) +# 45) Mark Berger private communication (5DmkII) +# 46) Dieter Steiner private communication (7D) +# 47) http://www.exiv2.org/ +# 48) Tomasz A. Kawecki private communication (550D, firmware 1.0.6, 1.0.8) +# 49) http://www.listware.net/201101/digikam-users/49795-digikam-users-re-lens-recognition.html +# 50) https://exiftool.org/forum/index.php/topic,3833.0.html +# 51) https://exiftool.org/forum/index.php/topic,4110.0.html +# 52) Kai Harrekilde-Petersen private communication +# 53) Anton Reiser private communication +# 54) https://github.com/lclevy/canon_cr3 +# IB) Iliah Borg private communication (LibRaw) +# JD) Jens Duttke private communication +# JR) Jos Roost private communication +# NJ) Niels Kristian Bech Jensen private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::Canon; + +use strict; +use vars qw($VERSION %canonModelID %canonLensTypes); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +sub WriteCanon($$$); +sub ProcessSerialData($$$); +sub ProcessFilters($$$); +sub ProcessCTMD($$$); +sub ProcessExifInfo($$$); +sub SwapWords($); + +$VERSION = '4.68'; + +# Note: Removed 'USM' from 'L' lenses since it is redundant - PH +# (or is it? Ref 32 shows 5 non-USM L-type lenses) +# --> have relaxed this for new lenses because Canon has been +# consistent about keeping "USM" in the model name +%canonLensTypes = ( #4 + -1 => 'n/a', + 1 => 'Canon EF 50mm f/1.8', + 2 => 'Canon EF 28mm f/2.8 or Sigma Lens', + 2.1 => 'Sigma 24mm f/2.8 Super Wide II', #ClaudeJolicoeur + # (3 removed in current Kamisaka list) + 3 => 'Canon EF 135mm f/2.8 Soft', #15/32 + 4 => 'Canon EF 35-105mm f/3.5-4.5 or Sigma Lens', #28 + 4.1 => 'Sigma UC Zoom 35-135mm f/4-5.6', + 5 => 'Canon EF 35-70mm f/3.5-4.5', #32 + 6 => 'Canon EF 28-70mm f/3.5-4.5 or Sigma or Tokina Lens', #32 + 6.1 => 'Sigma 18-50mm f/3.5-5.6 DC', #23 + 6.2 => 'Sigma 18-125mm f/3.5-5.6 DC IF ASP', + 6.3 => 'Tokina AF 193-2 19-35mm f/3.5-4.5', + 6.4 => 'Sigma 28-80mm f/3.5-5.6 II Macro', #47 + 6.5 => 'Sigma 28-300mm f/3.5-6.3 DG Macro', #IB + 7 => 'Canon EF 100-300mm f/5.6L', #15 + 8 => 'Canon EF 100-300mm f/5.6 or Sigma or Tokina Lens', #32 + 8.1 => 'Sigma 70-300mm f/4-5.6 [APO] DG Macro', #15 (both APO and non-APO, ref forum2947) + 8.2 => 'Tokina AT-X 242 AF 24-200mm f/3.5-5.6', #15 + 9 => 'Canon EF 70-210mm f/4', #32 + 9.1 => 'Sigma 55-200mm f/4-5.6 DC', #34 + 10 => 'Canon EF 50mm f/2.5 Macro or Sigma Lens', #10 (+ LSC Life Size Converter --> 70mm - PH) + 10.1 => 'Sigma 50mm f/2.8 EX', #4 + 10.2 => 'Sigma 28mm f/1.8', + 10.3 => 'Sigma 105mm f/2.8 Macro EX', #15 + 10.4 => 'Sigma 70mm f/2.8 EX DG Macro EF', #Jean-Michel Dubois + 11 => 'Canon EF 35mm f/2', #9 + 13 => 'Canon EF 15mm f/2.8 Fisheye', #9 + 14 => 'Canon EF 50-200mm f/3.5-4.5L', #32 + 15 => 'Canon EF 50-200mm f/3.5-4.5', #32 + 16 => 'Canon EF 35-135mm f/3.5-4.5', #32 + 17 => 'Canon EF 35-70mm f/3.5-4.5A', #32 + 18 => 'Canon EF 28-70mm f/3.5-4.5', #32 + 20 => 'Canon EF 100-200mm f/4.5A', #32 + 21 => 'Canon EF 80-200mm f/2.8L', + 22 => 'Canon EF 20-35mm f/2.8L or Tokina Lens', #32 + 22.1 => 'Tokina AT-X 280 AF Pro 28-80mm f/2.8 Aspherical', #15 + 23 => 'Canon EF 35-105mm f/3.5-4.5', #32 + 24 => 'Canon EF 35-80mm f/4-5.6 Power Zoom', #32 + 25 => 'Canon EF 35-80mm f/4-5.6 Power Zoom', #32 + 26 => 'Canon EF 100mm f/2.8 Macro or Other Lens', + 26.1 => 'Cosina 100mm f/3.5 Macro AF', + 26.2 => 'Tamron SP AF 90mm f/2.8 Di Macro', #15 + 26.3 => 'Tamron SP AF 180mm f/3.5 Di Macro', #15 + 26.4 => 'Carl Zeiss Planar T* 50mm f/1.4', #PH + 26.5 => 'Voigtlander APO Lanthar 125mm F2.5 SL Macro', #JR + 26.6 => 'Carl Zeiss Planar T 85mm f/1.4 ZE', #IB + 27 => 'Canon EF 35-80mm f/4-5.6', #32 + # 27 => 'Carl Zeiss Distagon T* 28mm f/2 ZF', #PH (must be with an adapter, because the ZF version is a Nikon mount) + # 27 => 'EMF adapter for Canon EOS digital cameras', #50 (reports MaxFocalLength of 65535) + # 27 => optix adapter + # 27 => Venus Optics Laowa 12mm f2.8 Zero-D or 105mm f2 (T3.2) Smooth Trans Focus (ref IB) + # 27 => Venus Optics Laowa 105mm f2 STF (ref IB) + 28 => 'Canon EF 80-200mm f/4.5-5.6 or Tamron Lens', #32 + 28.1 => 'Tamron SP AF 28-105mm f/2.8 LD Aspherical IF', #15 + 28.2 => 'Tamron SP AF 28-75mm f/2.8 XR Di LD Aspherical [IF] Macro', #4 + # 28.3 => 'Tamron AF 70-300mm f/4.5-5.6 Di LD 1:2 Macro Zoom', #11 + 28.3 => 'Tamron AF 70-300mm f/4-5.6 Di LD 1:2 Macro', #47 + 28.4 => 'Tamron AF Aspherical 28-200mm f/3.8-5.6', #14 + 29 => 'Canon EF 50mm f/1.8 II', + 30 => 'Canon EF 35-105mm f/4.5-5.6', #32 + 31 => 'Canon EF 75-300mm f/4-5.6 or Tamron Lens', #32 + 31.1 => 'Tamron SP AF 300mm f/2.8 LD IF', #15 + 32 => 'Canon EF 24mm f/2.8 or Sigma Lens', #10 + 32.1 => 'Sigma 15mm f/2.8 EX Fisheye', #11 + 33 => 'Voigtlander or Carl Zeiss Lens', + 33.1 => 'Voigtlander Ultron 40mm f/2 SLII Aspherical', #45 + 33.2 => 'Voigtlander Color Skopar 20mm f/3.5 SLII Aspherical', #50 + 33.3 => 'Voigtlander APO-Lanthar 90mm f/3.5 SLII Close Focus', #50 + 33.4 => 'Carl Zeiss Distagon T* 15mm f/2.8 ZE', #PH + 33.5 => 'Carl Zeiss Distagon T* 18mm f/3.5 ZE', #PH + 33.6 => 'Carl Zeiss Distagon T* 21mm f/2.8 ZE', #PH + 33.7 => 'Carl Zeiss Distagon T* 25mm f/2 ZE', #IB + 33.8 => 'Carl Zeiss Distagon T* 28mm f/2 ZE', #PH + 33.9 => 'Carl Zeiss Distagon T* 35mm f/2 ZE', #PH + '33.10' => 'Carl Zeiss Distagon T* 35mm f/1.4 ZE', #IB + '33.11' => 'Carl Zeiss Planar T* 50mm f/1.4 ZE', #IB + '33.12' => 'Carl Zeiss Makro-Planar T* 50mm f/2 ZE', #IB + '33.13' => 'Carl Zeiss Makro-Planar T* 100mm f/2 ZE', #IB + '33.14' => 'Carl Zeiss Apo-Sonnar T* 135mm f/2 ZE', #JR + 35 => 'Canon EF 35-80mm f/4-5.6', #32 + 36 => 'Canon EF 38-76mm f/4.5-5.6', #32 + 37 => 'Canon EF 35-80mm f/4-5.6 or Tamron Lens', #32 + 37.1 => 'Tamron 70-200mm f/2.8 Di LD IF Macro', #PH + 37.2 => 'Tamron AF 28-300mm f/3.5-6.3 XR Di VC LD Aspherical [IF] Macro (A20)', #38 + 37.3 => 'Tamron SP AF 17-50mm f/2.8 XR Di II VC LD Aspherical [IF]', #34 + 37.4 => 'Tamron AF 18-270mm f/3.5-6.3 Di II VC LD Aspherical [IF] Macro', #forum2937 + 38 => 'Canon EF 80-200mm f/4.5-5.6 II', #32 (II added ref https://github.com/Exiv2/exiv2/issues/1906) + 39 => 'Canon EF 75-300mm f/4-5.6', + 40 => 'Canon EF 28-80mm f/3.5-5.6', + 41 => 'Canon EF 28-90mm f/4-5.6', #32 + 42 => 'Canon EF 28-200mm f/3.5-5.6 or Tamron Lens', #32 + 42.1 => 'Tamron AF 28-300mm f/3.5-6.3 XR Di VC LD Aspherical [IF] Macro (A20)', #15 + 43 => 'Canon EF 28-105mm f/4-5.6', #10 + 44 => 'Canon EF 90-300mm f/4.5-5.6', #32 + 45 => 'Canon EF-S 18-55mm f/3.5-5.6 [II]', #PH (same ID for version II, ref 20) + 46 => 'Canon EF 28-90mm f/4-5.6', #32 + # 46 => 'Tamron 28-300mm f/3.5-6.3 Di VC PZD (A010)', # (also possibly?) + 47 => 'Zeiss Milvus 35mm f/2 or 50mm f/2', #IB + 47.1 => 'Zeiss Milvus 50mm f/2 Makro', #IB + 47.2 => 'Zeiss Milvus 135mm f/2 ZE', #IB + 48 => 'Canon EF-S 18-55mm f/3.5-5.6 IS', #20 + 49 => 'Canon EF-S 55-250mm f/4-5.6 IS', #23 + 50 => 'Canon EF-S 18-200mm f/3.5-5.6 IS', + 51 => 'Canon EF-S 18-135mm f/3.5-5.6 IS', #PH + 52 => 'Canon EF-S 18-55mm f/3.5-5.6 IS II', #PH + 53 => 'Canon EF-S 18-55mm f/3.5-5.6 III', #Jon Charnas + 54 => 'Canon EF-S 55-250mm f/4-5.6 IS II', #47 + 60 => 'Irix 11mm f/4', #50 + 63 => 'Irix 30mm F1.4 Dragonfly', #IB + 80 => 'Canon TS-E 50mm f/2.8L Macro', #42 + 81 => 'Canon TS-E 90mm f/2.8L Macro', #42 + 82 => 'Canon TS-E 135mm f/4L Macro', #42 + 94 => 'Canon TS-E 17mm f/4L', #42 + 95 => 'Canon TS-E 24mm f/3.5L II', #43 + 103 => 'Samyang AF 14mm f/2.8 EF or Rokinon Lens', #IB + 103.1 => 'Rokinon SP 14mm f/2.4', #IB + 103.2 => 'Rokinon AF 14mm f/2.8 EF', #IB + 106 => 'Rokinon SP / Samyang XP 35mm f/1.2', #IB + 112 => 'Sigma 28mm f/1.5 FF High-speed Prime or other Sigma Lens', #IB + 112.1 => 'Sigma 40mm f/1.5 FF High-speed Prime', #IB + 112.2 => 'Sigma 105mm f/1.5 FF High-speed Prime', #IB + 117 => 'Tamron 35-150mm f/2.8-4.0 Di VC OSD (A043) or other Tamron Lens', #IB + 117.1 => 'Tamron SP 35mm f/1.4 Di USD (F045)', #Exiv2#1064 + 124 => 'Canon MP-E 65mm f/2.8 1-5x Macro Photo', #9 + 125 => 'Canon TS-E 24mm f/3.5L', + 126 => 'Canon TS-E 45mm f/2.8', #15 + 127 => 'Canon TS-E 90mm f/2.8 or Tamron Lens', #15 + 127.1 => 'Tamron 18-200mm f/3.5-6.3 Di II VC (B018)', #TomLachecki + 129 => 'Canon EF 300mm f/2.8L USM', #32 + 130 => 'Canon EF 50mm f/1.0L USM', #10/15 + 131 => 'Canon EF 28-80mm f/2.8-4L USM or Sigma Lens', #32 + 131.1 => 'Sigma 8mm f/3.5 EX DG Circular Fisheye', #15 + 131.2 => 'Sigma 17-35mm f/2.8-4 EX DG Aspherical HSM', #15 + 131.3 => 'Sigma 17-70mm f/2.8-4.5 DC Macro', #PH (NC) + 131.4 => 'Sigma APO 50-150mm f/2.8 [II] EX DC HSM', #15 ([II] ref PH) + 131.5 => 'Sigma APO 120-300mm f/2.8 EX DG HSM', #15 + # 'Sigma APO 120-300mm f/2.8 EX DG HSM + 1.4x', #15 + # 'Sigma APO 120-300mm f/2.8 EX DG HSM + 2x', #15 + 131.6 => 'Sigma 4.5mm f/2.8 EX DC HSM Circular Fisheye', #PH + 131.7 => 'Sigma 70-200mm f/2.8 APO EX HSM', #PH (http://www.lensrentals.com/blog/2012/08/canon-illumination-correction-and-third-party-lenses) + 131.8 => 'Sigma 28-70mm f/2.8-4 DG', #IB + 132 => 'Canon EF 1200mm f/5.6L USM', #32 + 134 => 'Canon EF 600mm f/4L IS USM', #15 + 135 => 'Canon EF 200mm f/1.8L USM', + 136 => 'Canon EF 300mm f/2.8L USM', + 136.1 => 'Tamron SP 15-30mm f/2.8 Di VC USD (A012)', #TomLachecki + 137 => 'Canon EF 85mm f/1.2L USM or Sigma or Tamron Lens', #10 + 137.1 => 'Sigma 18-50mm f/2.8-4.5 DC OS HSM', #PH + 137.2 => 'Sigma 50-200mm f/4-5.6 DC OS HSM', #PH + 137.3 => 'Sigma 18-250mm f/3.5-6.3 DC OS HSM', #PH (also Sigma 18-250mm f/3.5-6.3 DC Macro OS HSM) + 137.4 => 'Sigma 24-70mm f/2.8 IF EX DG HSM', #PH + 137.5 => 'Sigma 18-125mm f/3.8-5.6 DC OS HSM', #PH + 137.6 => 'Sigma 17-70mm f/2.8-4 DC Macro OS HSM | C', #forum2819 (Contemporary version has this ID - PH) + 137.7 => 'Sigma 17-50mm f/2.8 OS HSM', #47 + 137.8 => 'Sigma 18-200mm f/3.5-6.3 DC OS HSM [II]', #PH + 137.9 => 'Tamron AF 18-270mm f/3.5-6.3 Di II VC PZD (B008)', #forum3090 + '137.10' => 'Sigma 8-16mm f/4.5-5.6 DC HSM', #50-Zwielicht + '137.11' => 'Tamron SP 17-50mm f/2.8 XR Di II VC (B005)', #50 + '137.12' => 'Tamron SP 60mm f/2 Macro Di II (G005)', #50 + '137.13' => 'Sigma 10-20mm f/3.5 EX DC HSM', #Gerald Erdmann + '137.14' => 'Tamron SP 24-70mm f/2.8 Di VC USD', #PH + '137.15' => 'Sigma 18-35mm f/1.8 DC HSM', #David Monro + '137.16' => 'Sigma 12-24mm f/4.5-5.6 DG HSM II', #IB + '137.17' => 'Sigma 70-300mm f/4-5.6 DG OS', #IB + 138 => 'Canon EF 28-80mm f/2.8-4L', #32 + 139 => 'Canon EF 400mm f/2.8L USM', + 140 => 'Canon EF 500mm f/4.5L USM', #32 + 141 => 'Canon EF 500mm f/4.5L USM', + 142 => 'Canon EF 300mm f/2.8L IS USM', #15 + 143 => 'Canon EF 500mm f/4L IS USM or Sigma Lens', #15 + 143.1 => 'Sigma 17-70mm f/2.8-4 DC Macro OS HSM', #NJ (Exiv2 #1167) + 144 => 'Canon EF 35-135mm f/4-5.6 USM', #26 + 145 => 'Canon EF 100-300mm f/4.5-5.6 USM', #32 + 146 => 'Canon EF 70-210mm f/3.5-4.5 USM', #32 + 147 => 'Canon EF 35-135mm f/4-5.6 USM', #32 + 148 => 'Canon EF 28-80mm f/3.5-5.6 USM', #32 + 149 => 'Canon EF 100mm f/2 USM', #9 + 150 => 'Canon EF 14mm f/2.8L USM or Sigma Lens', #10 + 150.1 => 'Sigma 20mm EX f/1.8', #4 + 150.2 => 'Sigma 30mm f/1.4 DC HSM', #15 + 150.3 => 'Sigma 24mm f/1.8 DG Macro EX', #15 + 150.4 => 'Sigma 28mm f/1.8 DG Macro EX', #IB + 150.5 => 'Sigma 18-35mm f/1.8 DC HSM | A', #IB + 151 => 'Canon EF 200mm f/2.8L USM', + 152 => 'Canon EF 300mm f/4L IS USM or Sigma Lens', #15 + 152.1 => 'Sigma 12-24mm f/4.5-5.6 EX DG ASPHERICAL HSM', #15 + 152.2 => 'Sigma 14mm f/2.8 EX Aspherical HSM', #15 + 152.3 => 'Sigma 10-20mm f/4-5.6', #14 + 152.4 => 'Sigma 100-300mm f/4', # (ref Bozi) + 152.5 => 'Sigma 300-800mm f/5.6 APO EX DG HSM', #IB + 153 => 'Canon EF 35-350mm f/3.5-5.6L USM or Sigma or Tamron Lens', #PH + 153.1 => 'Sigma 50-500mm f/4-6.3 APO HSM EX', #15 + 153.2 => 'Tamron AF 28-300mm f/3.5-6.3 XR LD Aspherical [IF] Macro', + 153.3 => 'Tamron AF 18-200mm f/3.5-6.3 XR Di II LD Aspherical [IF] Macro (A14)', #15 + 153.4 => 'Tamron 18-250mm f/3.5-6.3 Di II LD Aspherical [IF] Macro', #PH + 154 => 'Canon EF 20mm f/2.8 USM or Zeiss Lens', #15 + 154.1 => 'Zeiss Milvus 21mm f/2.8', #IB + 154.2 => 'Zeiss Milvus 15mm f/2.8 ZE', #IB + 154.3 => 'Zeiss Milvus 18mm f/2.8 ZE', #IB + 155 => 'Canon EF 85mm f/1.8 USM or Sigma Lens', + 155.1 => 'Sigma 14mm f/1.8 DG HSM | A', #IB (A017) + 156 => 'Canon EF 28-105mm f/3.5-4.5 USM or Tamron Lens', + 156.1 => 'Tamron SP 70-300mm f/4-5.6 Di VC USD (A005)', #PH + 156.2 => 'Tamron SP AF 28-105mm f/2.8 LD Aspherical IF (176D)', #JR + 160 => 'Canon EF 20-35mm f/3.5-4.5 USM or Tamron or Tokina Lens', + 160.1 => 'Tamron AF 19-35mm f/3.5-4.5', #44 + 160.2 => 'Tokina AT-X 124 AF Pro DX 12-24mm f/4', #49 + 160.3 => 'Tokina AT-X 107 AF DX 10-17mm f/3.5-4.5 Fisheye', #PH (http://osdir.com/ml/digikam-devel/2011-04/msg00275.html) + 160.4 => 'Tokina AT-X 116 AF Pro DX 11-16mm f/2.8', #forum3967 + 160.5 => 'Tokina AT-X 11-20 F2.8 PRO DX Aspherical 11-20mm f/2.8', #NJ (Exiv2 #1166) + 161 => 'Canon EF 28-70mm f/2.8L USM or Other Lens', + 161.1 => 'Sigma 24-70mm f/2.8 EX', + 161.2 => 'Sigma 28-70mm f/2.8 EX', #PH (http://www.breezesys.com/forum/showthread.php?t=3718) + 161.3 => 'Sigma 24-60mm f/2.8 EX DG', #PH (http://www.lensrentals.com/blog/2012/08/canon-illumination-correction-and-third-party-lenses) + 161.4 => 'Tamron AF 17-50mm f/2.8 Di-II LD Aspherical', #40 + 161.5 => 'Tamron 90mm f/2.8', + 161.6 => 'Tamron SP AF 17-35mm f/2.8-4 Di LD Aspherical IF (A05)', #IB + 161.7 => 'Tamron SP AF 28-75mm f/2.8 XR Di LD Aspherical [IF] Macro', #IB/NJ + 161.8 => 'Tokina AT-X 24-70mm f/2.8 PRO FX (IF)', #IB + 162 => 'Canon EF 200mm f/2.8L USM', #32 + 163 => 'Canon EF 300mm f/4L', #32 + 164 => 'Canon EF 400mm f/5.6L', #32 + 165 => 'Canon EF 70-200mm f/2.8L USM', + 166 => 'Canon EF 70-200mm f/2.8L USM + 1.4x', + 167 => 'Canon EF 70-200mm f/2.8L USM + 2x', + 168 => 'Canon EF 28mm f/1.8 USM or Sigma Lens', #15 + 168.1 => 'Sigma 50-100mm f/1.8 DC HSM | A', #IB + 169 => 'Canon EF 17-35mm f/2.8L USM or Sigma Lens', #15 + 169.1 => 'Sigma 18-200mm f/3.5-6.3 DC OS', #23 + 169.2 => 'Sigma 15-30mm f/3.5-4.5 EX DG Aspherical', #4 + 169.3 => 'Sigma 18-50mm f/2.8 Macro', #26 + 169.4 => 'Sigma 50mm f/1.4 EX DG HSM', #PH + 169.5 => 'Sigma 85mm f/1.4 EX DG HSM', #Rolando Ruzic + 169.6 => 'Sigma 30mm f/1.4 EX DC HSM', #Rodolfo Borges + 169.7 => 'Sigma 35mm f/1.4 DG HSM', #PH (also "| A" version, ref 50) + 169.8 => 'Sigma 35mm f/1.5 FF High-Speed Prime | 017', #IB + 169.9 => 'Sigma 70mm f/2.8 Macro EX DG', #IB + 170 => 'Canon EF 200mm f/2.8L II USM or Sigma Lens', #9 + 170.1 => 'Sigma 300mm f/2.8 APO EX DG HSM', #IB + 170.2 => 'Sigma 800mm f/5.6 APO EX DG HSM', #IB + 171 => 'Canon EF 300mm f/4L USM', #15 + 172 => 'Canon EF 400mm f/5.6L USM or Sigma Lens', #32 + 172.1 =>'Sigma 150-600mm f/5-6.3 DG OS HSM | S', #50 + 172.2 => 'Sigma 500mm f/4.5 APO EX DG HSM', #IB + 173 => 'Canon EF 180mm Macro f/3.5L USM or Sigma Lens', #9 + 173.1 => 'Sigma 180mm EX HSM Macro f/3.5', #14 + 173.2 => 'Sigma APO Macro 150mm f/2.8 EX DG HSM', #14 + 173.3 => 'Sigma 10mm f/2.8 EX DC Fisheye', #IB + 173.4 => 'Sigma 15mm f/2.8 EX DG Diagonal Fisheye', #IB + 173.5 => 'Venus Laowa 100mm F2.8 2X Ultra Macro APO', #IB + 174 => 'Canon EF 135mm f/2L USM or Other Lens', #9 + 174.1 => 'Sigma 70-200mm f/2.8 EX DG APO OS HSM', #PH (probably version II of this lens) + 174.2 => 'Sigma 50-500mm f/4.5-6.3 APO DG OS HSM', #forum4031 + 174.3 => 'Sigma 150-500mm f/5-6.3 APO DG OS HSM', #47 + 174.4 => 'Zeiss Milvus 100mm f/2 Makro', #IB + 174.5 => 'Sigma APO 50-150mm f/2.8 EX DC OS HSM', #IB + 174.6 => 'Sigma APO 120-300mm f/2.8 EX DG OS HSM', #IB + 174.7 => 'Sigma 120-300mm f/2.8 DG OS HSM S013', #IB + 174.8 => 'Sigma 120-400mm f/4.5-5.6 APO DG OS HSM', #IB + 174.9 => 'Sigma 200-500mm f/2.8 APO EX DG', #IB + 175 => 'Canon EF 400mm f/2.8L USM', #32 + 176 => 'Canon EF 24-85mm f/3.5-4.5 USM', + 177 => 'Canon EF 300mm f/4L IS USM', #9 + 178 => 'Canon EF 28-135mm f/3.5-5.6 IS', + 179 => 'Canon EF 24mm f/1.4L USM', #20 + 180 => 'Canon EF 35mm f/1.4L USM or Other Lens', #9 + 180.1 => 'Sigma 50mm f/1.4 DG HSM | A', #50 + 180.2 => 'Sigma 24mm f/1.4 DG HSM | A', #NJ + 180.3 => 'Zeiss Milvus 50mm f/1.4', #IB + 180.4 => 'Zeiss Milvus 85mm f/1.4', #IB + 180.5 => 'Zeiss Otus 28mm f/1.4 ZE', #PH + 180.6 => 'Sigma 24mm f/1.5 FF High-Speed Prime | 017', #IB + 180.7 => 'Sigma 50mm f/1.5 FF High-Speed Prime | 017', #IB + 180.8 => 'Sigma 85mm f/1.5 FF High-Speed Prime | 017', #IB + 180.9 => 'Tokina Opera 50mm f/1.4 FF', #IB + '180.10' => 'Sigma 20mm f/1.4 DG HSM | A', #IB (015) + 181 => 'Canon EF 100-400mm f/4.5-5.6L IS USM + 1.4x or Sigma Lens', #15 + 181.1 => 'Sigma 150-600mm f/5-6.3 DG OS HSM | S + 1.4x', #50 + 182 => 'Canon EF 100-400mm f/4.5-5.6L IS USM + 2x or Sigma Lens', + 182.1 => 'Sigma 150-600mm f/5-6.3 DG OS HSM | S + 2x', #PH (NC) + 183 => 'Canon EF 100-400mm f/4.5-5.6L IS USM or Sigma Lens', + 183.1 => 'Sigma 150mm f/2.8 EX DG OS HSM APO Macro', #50 + 183.2 => 'Sigma 105mm f/2.8 EX DG OS HSM Macro', #50 + 183.3 => 'Sigma 180mm f/2.8 EX DG OS HSM APO Macro', #IB + 183.4 => 'Sigma 150-600mm f/5-6.3 DG OS HSM | C', #47 + 183.5 => 'Sigma 150-600mm f/5-6.3 DG OS HSM | S', #forum7109 (Sports 014) + 183.6 => 'Sigma 100-400mm f/5-6.3 DG OS HSM', #PH ("| C" ?) + 183.7 => 'Sigma 180mm f/3.5 APO Macro EX DG IF HSM', #IB + 184 => 'Canon EF 400mm f/2.8L USM + 2x', #15 + 185 => 'Canon EF 600mm f/4L IS USM', #32 + 186 => 'Canon EF 70-200mm f/4L USM', #9 + 187 => 'Canon EF 70-200mm f/4L USM + 1.4x', #26 + 188 => 'Canon EF 70-200mm f/4L USM + 2x', #PH + 189 => 'Canon EF 70-200mm f/4L USM + 2.8x', #32 + 190 => 'Canon EF 100mm f/2.8 Macro USM', # (+USM ref 42) + 191 => 'Canon EF 400mm f/4 DO IS or Sigma Lens', #9 + 191.1 => 'Sigma 500mm f/4 DG OS HSM', #AndrewSheih + 193 => 'Canon EF 35-80mm f/4-5.6 USM', #32 + 194 => 'Canon EF 80-200mm f/4.5-5.6 USM', #32 + 195 => 'Canon EF 35-105mm f/4.5-5.6 USM', #32 + 196 => 'Canon EF 75-300mm f/4-5.6 USM', #15/32 + 197 => 'Canon EF 75-300mm f/4-5.6 IS USM or Sigma Lens', + 197.1 => 'Sigma 18-300mm f/3.5-6.3 DC Macro OS HSM', #50 + 198 => 'Canon EF 50mm f/1.4 USM or Other Lens', + 198.1 => 'Zeiss Otus 55mm f/1.4 ZE', #JR (seen only on Sony camera) + 198.2 => 'Zeiss Otus 85mm f/1.4 ZE', #JR (NC) + 198.3 => 'Zeiss Milvus 25mm f/1.4', #IB + 198.4 => 'Zeiss Otus 100mm f/1.4', #IB + 198.5 => 'Zeiss Milvus 35mm f/1.4 ZE', #IB + 198.6 => 'Yongnuo YN 35mm f/2', #IB + 199 => 'Canon EF 28-80mm f/3.5-5.6 USM', #32 + 200 => 'Canon EF 75-300mm f/4-5.6 USM', #32 + 201 => 'Canon EF 28-80mm f/3.5-5.6 USM', #32 + 202 => 'Canon EF 28-80mm f/3.5-5.6 USM IV', + 208 => 'Canon EF 22-55mm f/4-5.6 USM', #32 + 209 => 'Canon EF 55-200mm f/4.5-5.6', #32 (USM mk I version? ref IB) + 210 => 'Canon EF 28-90mm f/4-5.6 USM', #32 + 211 => 'Canon EF 28-200mm f/3.5-5.6 USM', #15 + 212 => 'Canon EF 28-105mm f/4-5.6 USM', #15 + 213 => 'Canon EF 90-300mm f/4.5-5.6 USM or Tamron Lens', + 213.1 => 'Tamron SP 150-600mm f/5-6.3 Di VC USD (A011)', #forum5565 + 213.2 => 'Tamron 16-300mm f/3.5-6.3 Di II VC PZD Macro (B016)', #PH + 213.3 => 'Tamron SP 35mm f/1.8 Di VC USD (F012)', #PH + 213.4 => 'Tamron SP 45mm f/1.8 Di VC USD (F013)', #PH + 214 => 'Canon EF-S 18-55mm f/3.5-5.6 USM', #PH/34 + 215 => 'Canon EF 55-200mm f/4.5-5.6 II USM', + 217 => 'Tamron AF 18-270mm f/3.5-6.3 Di II VC PZD', #47 + 220 => 'Yongnuo YN 50mm f/1.8', #IB + 224 => 'Canon EF 70-200mm f/2.8L IS USM', #11 + 225 => 'Canon EF 70-200mm f/2.8L IS USM + 1.4x', #11 + 226 => 'Canon EF 70-200mm f/2.8L IS USM + 2x', #14 + 227 => 'Canon EF 70-200mm f/2.8L IS USM + 2.8x', #32 + 228 => 'Canon EF 28-105mm f/3.5-4.5 USM', #32 + 229 => 'Canon EF 16-35mm f/2.8L USM', #PH + 230 => 'Canon EF 24-70mm f/2.8L USM', #9 + 231 => 'Canon EF 17-40mm f/4L USM or Sigma Lens', + 231.1 => 'Sigma 12-24mm f/4 DG HSM A016', #IB + 232 => 'Canon EF 70-300mm f/4.5-5.6 DO IS USM', #15 + 233 => 'Canon EF 28-300mm f/3.5-5.6L IS USM', #PH + 234 => 'Canon EF-S 17-85mm f/4-5.6 IS USM or Tokina Lens', #19 + 234.1 => 'Tokina AT-X 12-28 PRO DX 12-28mm f/4', #50/NJ + 235 => 'Canon EF-S 10-22mm f/3.5-4.5 USM', #15 + 236 => 'Canon EF-S 60mm f/2.8 Macro USM', #15 + 237 => 'Canon EF 24-105mm f/4L IS USM', #15 + 238 => 'Canon EF 70-300mm f/4-5.6 IS USM', #15 (and version II? ref 42) + 239 => 'Canon EF 85mm f/1.2L II USM or Rokinon Lens', #15 + 239.1 => 'Rokinon SP 85mm f/1.2', #IB + 240 => 'Canon EF-S 17-55mm f/2.8 IS USM or Sigma Lens', #15 + 240.1 => 'Sigma 17-50mm f/2.8 EX DC OS HSM', #https://github.com/Exiv2/exiv2/issues/397 + 241 => 'Canon EF 50mm f/1.2L USM', #15 + 242 => 'Canon EF 70-200mm f/4L IS USM', #PH + 243 => 'Canon EF 70-200mm f/4L IS USM + 1.4x', #15 + 244 => 'Canon EF 70-200mm f/4L IS USM + 2x', #PH + 245 => 'Canon EF 70-200mm f/4L IS USM + 2.8x', #32 + 246 => 'Canon EF 16-35mm f/2.8L II USM', #PH + 247 => 'Canon EF 14mm f/2.8L II USM', #32 + 248 => 'Canon EF 200mm f/2L IS USM or Sigma Lens', #42 + 248.1 => 'Sigma 24-35mm f/2 DG HSM | A', #JR + 248.2 => 'Sigma 135mm f/2 FF High-Speed Prime | 017', #IB + 248.3 => 'Sigma 24-35mm f/2.2 FF Zoom | 017', #IB + 248.4 => 'Sigma 135mm f/1.8 DG HSM A017', #IB + 249 => 'Canon EF 800mm f/5.6L IS USM', #35 + 250 => 'Canon EF 24mm f/1.4L II USM or Sigma Lens', #41 + 250.1 => 'Sigma 20mm f/1.4 DG HSM | A', #IB + 250.2 => 'Sigma 20mm f/1.5 FF High-Speed Prime | 017', #IB + 250.3 => 'Tokina Opera 16-28mm f/2.8 FF', #IB + 250.4 => 'Sigma 85mm f/1.4 DG HSM A016', #IB + 251 => 'Canon EF 70-200mm f/2.8L IS II USM', + 251.1 => 'Canon EF 70-200mm f/2.8L IS III USM', #IB + 252 => 'Canon EF 70-200mm f/2.8L IS II USM + 1.4x', #50 (1.4x Mk II) + 252.1 => 'Canon EF 70-200mm f/2.8L IS III USM + 1.4x', #PH (NC) + 253 => 'Canon EF 70-200mm f/2.8L IS II USM + 2x', #PH (NC) + 253.1 => 'Canon EF 70-200mm f/2.8L IS III USM + 2x', #PH (NC) + # 253.2 => 'Tamron SP 70-200mm f/2.8 Di VC USD G2 (A025) + 2x', #forum9367 + 254 => 'Canon EF 100mm f/2.8L Macro IS USM or Tamron Lens', #42 + 254.1 => 'Tamron SP 90mm f/2.8 Di VC USD 1:1 Macro (F017)', #PH + 255 => 'Sigma 24-105mm f/4 DG OS HSM | A or Other Lens', #50 + 255.1 => 'Sigma 180mm f/2.8 EX DG OS HSM APO Macro', #50 + 255.2 => 'Tamron SP 70-200mm f/2.8 Di VC USD', #exiv issue 1202 (A009) + 255.3 => 'Yongnuo YN 50mm f/1.8', #50 + 368 => 'Sigma 14-24mm f/2.8 DG HSM | A or other Sigma Lens', #IB (A018) + 368.1 => 'Sigma 20mm f/1.4 DG HSM | A', #50 (newer firmware) + 368.2 => 'Sigma 50mm f/1.4 DG HSM | A', #50 + 368.3 => 'Sigma 40mm f/1.4 DG HSM | A', #IB (018) + 368.4 => 'Sigma 60-600mm f/4.5-6.3 DG OS HSM | S', #IB (018) + 368.5 => 'Sigma 28mm f/1.4 DG HSM | A', #IB (A019) + 368.6 => 'Sigma 150-600mm f/5-6.3 DG OS HSM | S', #50 + 368.7 => 'Sigma 85mm f/1.4 DG HSM | A', #IB (016) + 368.8 => 'Sigma 105mm f/1.4 DG HSM', #IB (A018) + 368.9 => 'Sigma 14-24mm f/2.8 DG HSM', #IB (A018) + '368.10' => 'Sigma 35mm f/1.4 DG HSM | A', #PH (012) + '368.11' => 'Sigma 70mm f/2.8 DG Macro', #IB (A018) + '368.12' => 'Sigma 18-35mm f/1.8 DC HSM | A', #50 + '368.13' => 'Sigma 24-105mm f/4 DG OS HSM | A', #forum3833 + # Note: LensType 488 (0x1e8) is reported as 232 (0xe8) in 7D CameraSettings + 488 => 'Canon EF-S 15-85mm f/3.5-5.6 IS USM', #PH + 489 => 'Canon EF 70-300mm f/4-5.6L IS USM', #Gerald Kapounek + 490 => 'Canon EF 8-15mm f/4L Fisheye USM', #Klaus Reinfeld (PH added "Fisheye") + 491 => 'Canon EF 300mm f/2.8L IS II USM or Tamron Lens', #42 + 491.1 => 'Tamron SP 70-200mm f/2.8 Di VC USD G2 (A025)', #IB + 491.2 => 'Tamron 18-400mm f/3.5-6.3 Di II VC HLD (B028)', #IB + 491.3 => 'Tamron 100-400mm f/4.5-6.3 Di VC USD (A035)', #IB + 491.4 => 'Tamron 70-210mm f/4 Di VC USD (A034)', #IB + 491.5 => 'Tamron 70-210mm f/4 Di VC USD (A034) + 1.4x', #IB + 491.6 => 'Tamron SP 24-70mm f/2.8 Di VC USD G2 (A032)', + 492 => 'Canon EF 400mm f/2.8L IS II USM', #PH + 493 => 'Canon EF 500mm f/4L IS II USM or EF 24-105mm f4L IS USM', #PH + 493.1 => 'Canon EF 24-105mm f/4L IS USM', #PH (should recheck this) + 494 => 'Canon EF 600mm f/4L IS II USM', #PH + 495 => 'Canon EF 24-70mm f/2.8L II USM or Sigma Lens', #PH + 495.1 => 'Sigma 24-70mm f/2.8 DG OS HSM | A', #IB (017) + 496 => 'Canon EF 200-400mm f/4L IS USM', #PH + 499 => 'Canon EF 200-400mm f/4L IS USM + 1.4x', #50 + 502 => 'Canon EF 28mm f/2.8 IS USM or Tamron Lens', #PH + 502.1 => 'Tamron 35mm f/1.8 Di VC USD (F012)', #forum9757 + 503 => 'Canon EF 24mm f/2.8 IS USM', #PH + 504 => 'Canon EF 24-70mm f/4L IS USM', #PH + 505 => 'Canon EF 35mm f/2 IS USM', #PH + 506 => 'Canon EF 400mm f/4 DO IS II USM', #42 + 507 => 'Canon EF 16-35mm f/4L IS USM', #42 + 508 => 'Canon EF 11-24mm f/4L USM or Tamron Lens', #PH + 508.1 => 'Tamron 10-24mm f/3.5-4.5 Di II VC HLD (B023)', #PH + 624 => 'Sigma 70-200mm f/2.8 DG OS HSM | S or other Sigma Lens', #IB (018) + 624.1 => 'Sigma 150-600mm f/5-6.3 | C', #ChrisSkopec + 747 => 'Canon EF 100-400mm f/4.5-5.6L IS II USM or Tamron Lens', #JR + 747.1 => 'Tamron SP 150-600mm f/5-6.3 Di VC USD G2', #50 + 748 => 'Canon EF 100-400mm f/4.5-5.6L IS II USM + 1.4x or Tamron Lens', #JR (1.4x Mk III) + 748.1 => 'Tamron 100-400mm f/4.5-6.3 Di VC USD A035E + 1.4x', #IB + 748.2 => 'Tamron 70-210mm f/4 Di VC USD (A034) + 2x', #IB + 749 => 'Tamron 100-400mm f/4.5-6.3 Di VC USD A035E + 2x', #IB + 750 => 'Canon EF 35mm f/1.4L II USM or Tamron Lens', #42 + 750.1 => 'Tamron SP 85mm f/1.8 Di VC USD (F016)', #Exiv2#1072 + 750.2 => 'Tamron SP 45mm f/1.8 Di VC USD (F013)', #PH + 751 => 'Canon EF 16-35mm f/2.8L III USM', #42 + 752 => 'Canon EF 24-105mm f/4L IS II USM', #42 + 753 => 'Canon EF 85mm f/1.4L IS USM', #42 + 754 => 'Canon EF 70-200mm f/4L IS II USM', #IB + 757 => 'Canon EF 400mm f/2.8L IS III USM', #IB + 758 => 'Canon EF 600mm f/4L IS III USM', #IB + + 1136 => 'Sigma 24-70mm f/2.8 DG OS HSM | A', #IB (017) + # (STM lenses - 0x10xx) + 4142 => 'Canon EF-S 18-135mm f/3.5-5.6 IS STM', + 4143 => 'Canon EF-M 18-55mm f/3.5-5.6 IS STM or Tamron Lens', + 4143.1 => 'Tamron 18-200mm f/3.5-6.3 Di III VC', #42 + 4144 => 'Canon EF 40mm f/2.8 STM', #50 + 4145 => 'Canon EF-M 22mm f/2 STM', #34 + 4146 => 'Canon EF-S 18-55mm f/3.5-5.6 IS STM', #PH + 4147 => 'Canon EF-M 11-22mm f/4-5.6 IS STM', #42 + 4148 => 'Canon EF-S 55-250mm f/4-5.6 IS STM', #42 + 4149 => 'Canon EF-M 55-200mm f/4.5-6.3 IS STM', #42 + 4150 => 'Canon EF-S 10-18mm f/4.5-5.6 IS STM', #42 + 4152 => 'Canon EF 24-105mm f/3.5-5.6 IS STM', #42 + 4153 => 'Canon EF-M 15-45mm f/3.5-6.3 IS STM', #PH + 4154 => 'Canon EF-S 24mm f/2.8 STM', #IB + 4155 => 'Canon EF-M 28mm f/3.5 Macro IS STM', #42 + 4156 => 'Canon EF 50mm f/1.8 STM', #42 + 4157 => 'Canon EF-M 18-150mm f/3.5-6.3 IS STM', #42 + 4158 => 'Canon EF-S 18-55mm f/4-5.6 IS STM', #PH + 4159 => 'Canon EF-M 32mm f/1.4 STM', #42 + 4160 => 'Canon EF-S 35mm f/2.8 Macro IS STM', #42 + 4208 => 'Sigma 56mm f/1.4 DC DN | C or other Sigma Lens', #forum10603 + 4208.1 => 'Sigma 30mm F1.4 DC DN | C', #git issue#83 (016) + # (Nano USM lenses - 0x90xx) + 36910 => 'Canon EF 70-300mm f/4-5.6 IS II USM', #42 + 36912 => 'Canon EF-S 18-135mm f/3.5-5.6 IS USM', #42 + # (CN-E lenses - 0xf0xx) + 61491 => 'Canon CN-E 14mm T3.1 L F', #PH + 61492 => 'Canon CN-E 24mm T1.5 L F', #PH + # 61493 - missing CN-E 50mm T1.3 L F ? + 61494 => 'Canon CN-E 85mm T1.3 L F', #PH + 61495 => 'Canon CN-E 135mm T2.2 L F', #PH + 61496 => 'Canon CN-E 35mm T1.5 L F', #PH +# +# see RFLensType tag for master list of 61182 RF lenses +# + 61182 => 'Canon RF 50mm F1.2L USM or other Canon RF Lens', + 61182.1 => 'Canon RF 24-105mm F4L IS USM', + 61182.2 => 'Canon RF 28-70mm F2L USM', + 61182.3 => 'Canon RF 35mm F1.8 MACRO IS STM', + 61182.4 => 'Canon RF 85mm F1.2L USM', + 61182.5 => 'Canon RF 85mm F1.2L USM DS', + 61182.6 => 'Canon RF 24-70mm F2.8L IS USM', + 61182.7 => 'Canon RF 15-35mm F2.8L IS USM', + 61182.8 => 'Canon RF 24-240mm F4-6.3 IS USM', + 61182.9 => 'Canon RF 70-200mm F2.8L IS USM', + '61182.10' => 'Canon RF 85mm F2 MACRO IS STM', + '61182.11' => 'Canon RF 600mm F11 IS STM', + '61182.12' => 'Canon RF 600mm F11 IS STM + RF1.4x', + '61182.13' => 'Canon RF 600mm F11 IS STM + RF2x', + '61182.14' => 'Canon RF 800mm F11 IS STM', + '61182.15' => 'Canon RF 800mm F11 IS STM + RF1.4x', + '61182.16' => 'Canon RF 800mm F11 IS STM + RF2x', + '61182.17' => 'Canon RF 24-105mm F4-7.1 IS STM', + '61182.18' => 'Canon RF 100-500mm F4.5-7.1L IS USM', + '61182.19' => 'Canon RF 100-500mm F4.5-7.1L IS USM + RF1.4x', + '61182.20' => 'Canon RF 100-500mm F4.5-7.1L IS USM + RF2x', + '61182.21' => 'Canon RF 70-200mm F4L IS USM', #42 + '61182.22' => 'Canon RF 100mm F2.8L MACRO IS USM', #42 + '61182.23' => 'Canon RF 50mm F1.8 STM', #42 + '61182.24' => 'Canon RF 14-35mm F4L IS USM', #IB + '61182.25' => 'Canon RF-S 18-45mm F4.5-6.3 IS STM', #42 + '61182.26' => 'Canon RF 100-400mm F5.6-8 IS USM', #42 + '61182.27' => 'Canon RF 100-400mm F5.6-8 IS USM + RF1.4x', #42 + '61182.28' => 'Canon RF 100-400mm F5.6-8 IS USM + RF2x', #42 + '61182.29' => 'Canon RF-S 18-150mm F3.5-6.3 IS STM', #42 + '61182.30' => 'Canon RF 24mm F1.8 MACRO IS STM', #42 + '61182.31' => 'Canon RF 16mm F2.8 STM', #42 + '61182.32' => 'Canon RF 400mm F2.8L IS USM', #IB + '61182.33' => 'Canon RF 400mm F2.8L IS USM + RF1.4x', #IB + '61182.34' => 'Canon RF 400mm F2.8L IS USM + RF2x', #IB + '61182.35' => 'Canon RF 600mm F4L IS USM', #GiaZopatti + '61182.36' => 'Canon RF 600mm F4L IS USM + RF1.4x', #42 + '61182.37' => 'Canon RF 600mm F4L IS USM + RF2x', #42 + '61182.38' => 'Canon RF 15-30mm F4.5-6.3 IS STM', #42 + '61182.39' => 'Canon RF 800mm F5.6L IS USM', #42 + '61182.40' => 'Canon RF 800mm F5.6L IS USM + RF1.4x', #42 + '61182.41' => 'Canon RF 800mm F5.6L IS USM + RF2x', #42 + '61182.42' => 'Canon RF 1200mm F8L IS USM', #42 + '61182.43' => 'Canon RF 1200mm F8L IS USM + RF1.4x', #42 + '61182.44' => 'Canon RF 1200mm F8L IS USM + RF2x', #42 + '61182.45' => 'Canon RF 135mm F1.8 L IS USM', #42 + '61182.46' => 'Canon RF 24-50mm F4.5-6.3 IS STM', #42 + '61182.47' => 'Canon RF-S 55-210mm F5-7.1 IS STM', #42 + '61182.48' => 'Canon RF 100-300mm F2.8L IS USM', #42 + '61182.49' => 'Canon RF 100-300mm F2.8L IS USM + RF1.4x', #42 + '61182.50' => 'Canon RF 100-300mm F2.8L IS USM + RF2x', #42 + '61182.51' => 'Canon RF 28mm F2.8 STM', #42 + # we need the RFLensType values for the following... + '61182.52' => 'Canon RF 5.2mm F2.8L Dual Fisheye 3D VR', #PH (NC) + 65535 => 'n/a', +); + +# Canon model ID numbers (PH) +%canonModelID = ( + 0x1010000 => 'PowerShot A30', + 0x1040000 => 'PowerShot S300 / Digital IXUS 300 / IXY Digital 300', + 0x1060000 => 'PowerShot A20', + 0x1080000 => 'PowerShot A10', + 0x1090000 => 'PowerShot S110 / Digital IXUS v / IXY Digital 200', + 0x1100000 => 'PowerShot G2', + 0x1110000 => 'PowerShot S40', + 0x1120000 => 'PowerShot S30', + 0x1130000 => 'PowerShot A40', + 0x1140000 => 'EOS D30', + 0x1150000 => 'PowerShot A100', + 0x1160000 => 'PowerShot S200 / Digital IXUS v2 / IXY Digital 200a', + 0x1170000 => 'PowerShot A200', + 0x1180000 => 'PowerShot S330 / Digital IXUS 330 / IXY Digital 300a', + 0x1190000 => 'PowerShot G3', + 0x1210000 => 'PowerShot S45', + 0x1230000 => 'PowerShot SD100 / Digital IXUS II / IXY Digital 30', + 0x1240000 => 'PowerShot S230 / Digital IXUS v3 / IXY Digital 320', + 0x1250000 => 'PowerShot A70', + 0x1260000 => 'PowerShot A60', + 0x1270000 => 'PowerShot S400 / Digital IXUS 400 / IXY Digital 400', + 0x1290000 => 'PowerShot G5', + 0x1300000 => 'PowerShot A300', + 0x1310000 => 'PowerShot S50', + 0x1340000 => 'PowerShot A80', + 0x1350000 => 'PowerShot SD10 / Digital IXUS i / IXY Digital L', + 0x1360000 => 'PowerShot S1 IS', + 0x1370000 => 'PowerShot Pro1', + 0x1380000 => 'PowerShot S70', + 0x1390000 => 'PowerShot S60', + 0x1400000 => 'PowerShot G6', + 0x1410000 => 'PowerShot S500 / Digital IXUS 500 / IXY Digital 500', + 0x1420000 => 'PowerShot A75', + 0x1440000 => 'PowerShot SD110 / Digital IXUS IIs / IXY Digital 30a', + 0x1450000 => 'PowerShot A400', + 0x1470000 => 'PowerShot A310', + 0x1490000 => 'PowerShot A85', + 0x1520000 => 'PowerShot S410 / Digital IXUS 430 / IXY Digital 450', + 0x1530000 => 'PowerShot A95', + 0x1540000 => 'PowerShot SD300 / Digital IXUS 40 / IXY Digital 50', + 0x1550000 => 'PowerShot SD200 / Digital IXUS 30 / IXY Digital 40', + 0x1560000 => 'PowerShot A520', + 0x1570000 => 'PowerShot A510', + 0x1590000 => 'PowerShot SD20 / Digital IXUS i5 / IXY Digital L2', + 0x1640000 => 'PowerShot S2 IS', + 0x1650000 => 'PowerShot SD430 / Digital IXUS Wireless / IXY Digital Wireless', + 0x1660000 => 'PowerShot SD500 / Digital IXUS 700 / IXY Digital 600', + 0x1668000 => 'EOS D60', + 0x1700000 => 'PowerShot SD30 / Digital IXUS i Zoom / IXY Digital L3', + 0x1740000 => 'PowerShot A430', + 0x1750000 => 'PowerShot A410', + 0x1760000 => 'PowerShot S80', + 0x1780000 => 'PowerShot A620', + 0x1790000 => 'PowerShot A610', + 0x1800000 => 'PowerShot SD630 / Digital IXUS 65 / IXY Digital 80', + 0x1810000 => 'PowerShot SD450 / Digital IXUS 55 / IXY Digital 60', + 0x1820000 => 'PowerShot TX1', + 0x1870000 => 'PowerShot SD400 / Digital IXUS 50 / IXY Digital 55', + 0x1880000 => 'PowerShot A420', + 0x1890000 => 'PowerShot SD900 / Digital IXUS 900 Ti / IXY Digital 1000', + 0x1900000 => 'PowerShot SD550 / Digital IXUS 750 / IXY Digital 700', + 0x1920000 => 'PowerShot A700', + 0x1940000 => 'PowerShot SD700 IS / Digital IXUS 800 IS / IXY Digital 800 IS', + 0x1950000 => 'PowerShot S3 IS', + 0x1960000 => 'PowerShot A540', + 0x1970000 => 'PowerShot SD600 / Digital IXUS 60 / IXY Digital 70', + 0x1980000 => 'PowerShot G7', + 0x1990000 => 'PowerShot A530', + 0x2000000 => 'PowerShot SD800 IS / Digital IXUS 850 IS / IXY Digital 900 IS', + 0x2010000 => 'PowerShot SD40 / Digital IXUS i7 / IXY Digital L4', + 0x2020000 => 'PowerShot A710 IS', + 0x2030000 => 'PowerShot A640', + 0x2040000 => 'PowerShot A630', + 0x2090000 => 'PowerShot S5 IS', + 0x2100000 => 'PowerShot A460', + 0x2120000 => 'PowerShot SD850 IS / Digital IXUS 950 IS / IXY Digital 810 IS', + 0x2130000 => 'PowerShot A570 IS', + 0x2140000 => 'PowerShot A560', + 0x2150000 => 'PowerShot SD750 / Digital IXUS 75 / IXY Digital 90', + 0x2160000 => 'PowerShot SD1000 / Digital IXUS 70 / IXY Digital 10', + 0x2180000 => 'PowerShot A550', + 0x2190000 => 'PowerShot A450', + 0x2230000 => 'PowerShot G9', + 0x2240000 => 'PowerShot A650 IS', + 0x2260000 => 'PowerShot A720 IS', + 0x2290000 => 'PowerShot SX100 IS', + 0x2300000 => 'PowerShot SD950 IS / Digital IXUS 960 IS / IXY Digital 2000 IS', + 0x2310000 => 'PowerShot SD870 IS / Digital IXUS 860 IS / IXY Digital 910 IS', + 0x2320000 => 'PowerShot SD890 IS / Digital IXUS 970 IS / IXY Digital 820 IS', + 0x2360000 => 'PowerShot SD790 IS / Digital IXUS 90 IS / IXY Digital 95 IS', + 0x2370000 => 'PowerShot SD770 IS / Digital IXUS 85 IS / IXY Digital 25 IS', + 0x2380000 => 'PowerShot A590 IS', + 0x2390000 => 'PowerShot A580', + 0x2420000 => 'PowerShot A470', + 0x2430000 => 'PowerShot SD1100 IS / Digital IXUS 80 IS / IXY Digital 20 IS', + 0x2460000 => 'PowerShot SX1 IS', + 0x2470000 => 'PowerShot SX10 IS', + 0x2480000 => 'PowerShot A1000 IS', + 0x2490000 => 'PowerShot G10', + 0x2510000 => 'PowerShot A2000 IS', + 0x2520000 => 'PowerShot SX110 IS', + 0x2530000 => 'PowerShot SD990 IS / Digital IXUS 980 IS / IXY Digital 3000 IS', + 0x2540000 => 'PowerShot SD880 IS / Digital IXUS 870 IS / IXY Digital 920 IS', + 0x2550000 => 'PowerShot E1', + 0x2560000 => 'PowerShot D10', + 0x2570000 => 'PowerShot SD960 IS / Digital IXUS 110 IS / IXY Digital 510 IS', + 0x2580000 => 'PowerShot A2100 IS', + 0x2590000 => 'PowerShot A480', + 0x2600000 => 'PowerShot SX200 IS', + 0x2610000 => 'PowerShot SD970 IS / Digital IXUS 990 IS / IXY Digital 830 IS', + 0x2620000 => 'PowerShot SD780 IS / Digital IXUS 100 IS / IXY Digital 210 IS', + 0x2630000 => 'PowerShot A1100 IS', + 0x2640000 => 'PowerShot SD1200 IS / Digital IXUS 95 IS / IXY Digital 110 IS', + 0x2700000 => 'PowerShot G11', + 0x2710000 => 'PowerShot SX120 IS', + 0x2720000 => 'PowerShot S90', + 0x2750000 => 'PowerShot SX20 IS', + 0x2760000 => 'PowerShot SD980 IS / Digital IXUS 200 IS / IXY Digital 930 IS', + 0x2770000 => 'PowerShot SD940 IS / Digital IXUS 120 IS / IXY Digital 220 IS', + 0x2800000 => 'PowerShot A495', + 0x2810000 => 'PowerShot A490', + 0x2820000 => 'PowerShot A3100/A3150 IS', # (different cameras, same ID) + 0x2830000 => 'PowerShot A3000 IS', + 0x2840000 => 'PowerShot SD1400 IS / IXUS 130 / IXY 400F', + 0x2850000 => 'PowerShot SD1300 IS / IXUS 105 / IXY 200F', + 0x2860000 => 'PowerShot SD3500 IS / IXUS 210 / IXY 10S', + 0x2870000 => 'PowerShot SX210 IS', + 0x2880000 => 'PowerShot SD4000 IS / IXUS 300 HS / IXY 30S', + 0x2890000 => 'PowerShot SD4500 IS / IXUS 1000 HS / IXY 50S', + 0x2920000 => 'PowerShot G12', + 0x2930000 => 'PowerShot SX30 IS', + 0x2940000 => 'PowerShot SX130 IS', + 0x2950000 => 'PowerShot S95', + 0x2980000 => 'PowerShot A3300 IS', + 0x2990000 => 'PowerShot A3200 IS', + 0x3000000 => 'PowerShot ELPH 500 HS / IXUS 310 HS / IXY 31S', + 0x3010000 => 'PowerShot Pro90 IS', + 0x3010001 => 'PowerShot A800', + 0x3020000 => 'PowerShot ELPH 100 HS / IXUS 115 HS / IXY 210F', + 0x3030000 => 'PowerShot SX230 HS', + 0x3040000 => 'PowerShot ELPH 300 HS / IXUS 220 HS / IXY 410F', + 0x3050000 => 'PowerShot A2200', + 0x3060000 => 'PowerShot A1200', + 0x3070000 => 'PowerShot SX220 HS', + 0x3080000 => 'PowerShot G1 X', + 0x3090000 => 'PowerShot SX150 IS', + 0x3100000 => 'PowerShot ELPH 510 HS / IXUS 1100 HS / IXY 51S', + 0x3110000 => 'PowerShot S100 (new)', + 0x3130000 => 'PowerShot SX40 HS', + 0x3120000 => 'PowerShot ELPH 310 HS / IXUS 230 HS / IXY 600F', + # the Canon page lists the IXY 32S as "Japan only", but many other + # sites list the ELPH 500 HS and IXUS 320 HS as being the same model. + # I haven't been able to find an IXUS 320 sample, and the ELPH 500 HS + # is already associated with other IXUS and IXY models - PH + 0x3140000 => 'IXY 32S', # (PowerShot ELPH 500 HS / IXUS 320 HS ??) + 0x3160000 => 'PowerShot A1300', + 0x3170000 => 'PowerShot A810', + 0x3180000 => 'PowerShot ELPH 320 HS / IXUS 240 HS / IXY 420F', + 0x3190000 => 'PowerShot ELPH 110 HS / IXUS 125 HS / IXY 220F', + 0x3200000 => 'PowerShot D20', + 0x3210000 => 'PowerShot A4000 IS', + 0x3220000 => 'PowerShot SX260 HS', + 0x3230000 => 'PowerShot SX240 HS', + 0x3240000 => 'PowerShot ELPH 530 HS / IXUS 510 HS / IXY 1', + 0x3250000 => 'PowerShot ELPH 520 HS / IXUS 500 HS / IXY 3', + 0x3260000 => 'PowerShot A3400 IS', + 0x3270000 => 'PowerShot A2400 IS', + 0x3280000 => 'PowerShot A2300', + 0x3320000 => 'PowerShot S100V', #IB + 0x3330000 => 'PowerShot G15', #25 + 0x3340000 => 'PowerShot SX50 HS', #25/forum8196 + 0x3350000 => 'PowerShot SX160 IS', + 0x3360000 => 'PowerShot S110 (new)', + 0x3370000 => 'PowerShot SX500 IS', + 0x3380000 => 'PowerShot N', + 0x3390000 => 'IXUS 245 HS / IXY 430F', # (no PowerShot) + 0x3400000 => 'PowerShot SX280 HS', + 0x3410000 => 'PowerShot SX270 HS', + 0x3420000 => 'PowerShot A3500 IS', + 0x3430000 => 'PowerShot A2600', + 0x3440000 => 'PowerShot SX275 HS', #forum8199 + 0x3450000 => 'PowerShot A1400', + 0x3460000 => 'PowerShot ELPH 130 IS / IXUS 140 / IXY 110F', + 0x3470000 => 'PowerShot ELPH 115/120 IS / IXUS 132/135 / IXY 90F/100F', + 0x3490000 => 'PowerShot ELPH 330 HS / IXUS 255 HS / IXY 610F', + 0x3510000 => 'PowerShot A2500', + 0x3540000 => 'PowerShot G16', + 0x3550000 => 'PowerShot S120', + 0x3560000 => 'PowerShot SX170 IS', + 0x3580000 => 'PowerShot SX510 HS', + 0x3590000 => 'PowerShot S200 (new)', + 0x3600000 => 'IXY 620F', # (no PowerShot or IXUS?) + 0x3610000 => 'PowerShot N100', + 0x3640000 => 'PowerShot G1 X Mark II', + 0x3650000 => 'PowerShot D30', + 0x3660000 => 'PowerShot SX700 HS', + 0x3670000 => 'PowerShot SX600 HS', + 0x3680000 => 'PowerShot ELPH 140 IS / IXUS 150 / IXY 130', + 0x3690000 => 'PowerShot ELPH 135 / IXUS 145 / IXY 120', + 0x3700000 => 'PowerShot ELPH 340 HS / IXUS 265 HS / IXY 630', + 0x3710000 => 'PowerShot ELPH 150 IS / IXUS 155 / IXY 140', + 0x3740000 => 'EOS M3', #IB + 0x3750000 => 'PowerShot SX60 HS', #IB/NJ + 0x3760000 => 'PowerShot SX520 HS', #IB + 0x3770000 => 'PowerShot SX400 IS', + 0x3780000 => 'PowerShot G7 X', #IB + 0x3790000 => 'PowerShot N2', + 0x3800000 => 'PowerShot SX530 HS', + 0x3820000 => 'PowerShot SX710 HS', + 0x3830000 => 'PowerShot SX610 HS', + 0x3840000 => 'EOS M10', + 0x3850000 => 'PowerShot G3 X', + 0x3860000 => 'PowerShot ELPH 165 HS / IXUS 165 / IXY 160', + 0x3870000 => 'PowerShot ELPH 160 / IXUS 160', + 0x3880000 => 'PowerShot ELPH 350 HS / IXUS 275 HS / IXY 640', + 0x3890000 => 'PowerShot ELPH 170 IS / IXUS 170', + 0x3910000 => 'PowerShot SX410 IS', + 0x3930000 => 'PowerShot G9 X', + 0x3940000 => 'EOS M5', #IB + 0x3950000 => 'PowerShot G5 X', + 0x3970000 => 'PowerShot G7 X Mark II', + 0x3980000 => 'EOS M100', #42 + 0x3990000 => 'PowerShot ELPH 360 HS / IXUS 285 HS / IXY 650', + 0x4010000 => 'PowerShot SX540 HS', + 0x4020000 => 'PowerShot SX420 IS', + 0x4030000 => 'PowerShot ELPH 190 IS / IXUS 180 / IXY 190', + 0x4040000 => 'PowerShot G1', + 0x4040001 => 'PowerShot ELPH 180 IS / IXUS 175 / IXY 180', #forum10402 + 0x4050000 => 'PowerShot SX720 HS', + 0x4060000 => 'PowerShot SX620 HS', + 0x4070000 => 'EOS M6', + 0x4100000 => 'PowerShot G9 X Mark II', + 0x412 => 'EOS M50 / Kiss M', # (yes, no "0000") + 0x4150000 => 'PowerShot ELPH 185 / IXUS 185 / IXY 200', + 0x4160000 => 'PowerShot SX430 IS', + 0x4170000 => 'PowerShot SX730 HS', + 0x4180000 => 'PowerShot G1 X Mark III', #IB + 0x6040000 => 'PowerShot S100 / Digital IXUS / IXY Digital', + 0x801 => 'PowerShot SX740 HS', + 0x804 => 'PowerShot G5 X Mark II', + 0x805 => 'PowerShot SX70 HS', + 0x808 => 'PowerShot G7 X Mark III', + 0x811 => 'EOS M6 Mark II', #IB + 0x812 => 'EOS M200', #25 + +# (see http://cweb.canon.jp/e-support/faq/answer/digitalcamera/10447-1.html for PowerShot/IXUS/IXY names) + + 0x4007d673 => 'DC19/DC21/DC22', + 0x4007d674 => 'XH A1', + 0x4007d675 => 'HV10', + 0x4007d676 => 'MD130/MD140/MD150/MD160/ZR850', + 0x4007d777 => 'DC50', # (iVIS) + 0x4007d778 => 'HV20', # (iVIS) + 0x4007d779 => 'DC211', #29 + 0x4007d77a => 'HG10', + 0x4007d77b => 'HR10', #29 (iVIS) + 0x4007d77d => 'MD255/ZR950', + 0x4007d81c => 'HF11', + 0x4007d878 => 'HV30', + 0x4007d87c => 'XH A1S', + 0x4007d87e => 'DC301/DC310/DC311/DC320/DC330', + 0x4007d87f => 'FS100', + 0x4007d880 => 'HF10', #29 (iVIS/VIXIA) + 0x4007d882 => 'HG20/HG21', # (VIXIA) + 0x4007d925 => 'HF21', # (LEGRIA) + 0x4007d926 => 'HF S11', # (LEGRIA) + 0x4007d978 => 'HV40', # (LEGRIA) + 0x4007d987 => 'DC410/DC411/DC420', + 0x4007d988 => 'FS19/FS20/FS21/FS22/FS200', # (LEGRIA) + 0x4007d989 => 'HF20/HF200', # (LEGRIA) + 0x4007d98a => 'HF S10/S100', # (LEGRIA/VIXIA) + 0x4007da8e => 'HF R10/R16/R17/R18/R100/R106', # (LEGRIA/VIXIA) + 0x4007da8f => 'HF M30/M31/M36/M300/M306', # (LEGRIA/VIXIA) + 0x4007da90 => 'HF S20/S21/S200', # (LEGRIA/VIXIA) + 0x4007da92 => 'FS31/FS36/FS37/FS300/FS305/FS306/FS307', + 0x4007dca0 => 'EOS C300', + 0x4007dda9 => 'HF G25', # (LEGRIA) + 0x4007dfb4 => 'XC10', + 0x4007e1c3 => 'EOS C200', + + # NOTE: some pre-production models may have a model name of + # "Canon EOS Kxxx", where "xxx" is the last 3 digits of the model ID below. + # This has been observed for the 1DSmkIII/K215 and 400D/K236. + 0x80000001 => 'EOS-1D', + 0x80000167 => 'EOS-1DS', + 0x80000168 => 'EOS 10D', + 0x80000169 => 'EOS-1D Mark III', + 0x80000170 => 'EOS Digital Rebel / 300D / Kiss Digital', + 0x80000174 => 'EOS-1D Mark II', + 0x80000175 => 'EOS 20D', + 0x80000176 => 'EOS Digital Rebel XSi / 450D / Kiss X2', + 0x80000188 => 'EOS-1Ds Mark II', + 0x80000189 => 'EOS Digital Rebel XT / 350D / Kiss Digital N', + 0x80000190 => 'EOS 40D', + 0x80000213 => 'EOS 5D', + 0x80000215 => 'EOS-1Ds Mark III', + 0x80000218 => 'EOS 5D Mark II', + 0x80000219 => 'WFT-E1', + 0x80000232 => 'EOS-1D Mark II N', + 0x80000234 => 'EOS 30D', + 0x80000236 => 'EOS Digital Rebel XTi / 400D / Kiss Digital X', + 0x80000241 => 'WFT-E2', + 0x80000246 => 'WFT-E3', + 0x80000250 => 'EOS 7D', + 0x80000252 => 'EOS Rebel T1i / 500D / Kiss X3', + 0x80000254 => 'EOS Rebel XS / 1000D / Kiss F', + 0x80000261 => 'EOS 50D', + 0x80000269 => 'EOS-1D X', + 0x80000270 => 'EOS Rebel T2i / 550D / Kiss X4', + 0x80000271 => 'WFT-E4', + 0x80000273 => 'WFT-E5', + 0x80000281 => 'EOS-1D Mark IV', + 0x80000285 => 'EOS 5D Mark III', + 0x80000286 => 'EOS Rebel T3i / 600D / Kiss X5', + 0x80000287 => 'EOS 60D', + 0x80000288 => 'EOS Rebel T3 / 1100D / Kiss X50', + 0x80000289 => 'EOS 7D Mark II', #IB + 0x80000297 => 'WFT-E2 II', + 0x80000298 => 'WFT-E4 II', + 0x80000301 => 'EOS Rebel T4i / 650D / Kiss X6i', + 0x80000302 => 'EOS 6D', #25 + 0x80000324 => 'EOS-1D C', #(NC) + 0x80000325 => 'EOS 70D', + 0x80000326 => 'EOS Rebel T5i / 700D / Kiss X7i', + 0x80000327 => 'EOS Rebel T5 / 1200D / Kiss X70 / Hi', + 0x80000328 => 'EOS-1D X Mark II', #42 + 0x80000331 => 'EOS M', + 0x80000350 => 'EOS 80D', #42 + 0x80000355 => 'EOS M2', + 0x80000346 => 'EOS Rebel SL1 / 100D / Kiss X7', + 0x80000347 => 'EOS Rebel T6s / 760D / 8000D', + 0x80000349 => 'EOS 5D Mark IV', #42 + 0x80000382 => 'EOS 5DS', + 0x80000393 => 'EOS Rebel T6i / 750D / Kiss X8i', + 0x80000401 => 'EOS 5DS R', + 0x80000404 => 'EOS Rebel T6 / 1300D / Kiss X80', + 0x80000405 => 'EOS Rebel T7i / 800D / Kiss X9i', + 0x80000406 => 'EOS 6D Mark II', #IB/42 + 0x80000408 => 'EOS 77D / 9000D', + 0x80000417 => 'EOS Rebel SL2 / 200D / Kiss X9', #IB/42 + 0x80000421 => 'EOS R5', #PH + 0x80000422 => 'EOS Rebel T100 / 4000D / 3000D', #IB (3000D in China; Kiss? - PH) + 0x80000424 => 'EOS R', #IB + 0x80000428 => 'EOS-1D X Mark III', #IB + 0x80000432 => 'EOS Rebel T7 / 2000D / 1500D / Kiss X90', #IB + 0x80000433 => 'EOS RP', + 0x80000435 => 'EOS Rebel T8i / 850D / X10i', #JR/PH + 0x80000436 => 'EOS SL3 / 250D / Kiss X10', #25 + 0x80000437 => 'EOS 90D', #IB + 0x80000450 => 'EOS R3', #42 + 0x80000453 => 'EOS R6', #PH + 0x80000464 => 'EOS R7', #42 + 0x80000465 => 'EOS R10', #42 + 0x80000467 => 'PowerShot ZOOM', + 0x80000468 => 'EOS M50 Mark II / Kiss M2', #IB + 0x80000480 => 'EOS R50', #42 + 0x80000481 => 'EOS R6 Mark II', #42 + 0x80000487 => 'EOS R8', #42 + 0x80000491 => 'PowerShot V10', #25 + 0x80000498 => 'EOS R100', #25 + 0x80000520 => 'EOS D2000C', #IB + 0x80000560 => 'EOS D6000C', #PH (guess) +); + +my %canonQuality = ( + -1 => 'n/a', # (PH, EOS M MOV video) + 1 => 'Economy', + 2 => 'Normal', + 3 => 'Fine', + 4 => 'RAW', + 5 => 'Superfine', + 7 => 'CRAW', #42 + 130 => 'Light (RAW)', #github#119 + 131 => 'Standard (RAW)', #github#119 +); +my %canonImageSize = ( + -1 => 'n/a', + 0 => 'Large', + 1 => 'Medium', + 2 => 'Small', + 5 => 'Medium 1', #PH + 6 => 'Medium 2', #PH + 7 => 'Medium 3', #PH + 8 => 'Postcard', #PH (SD200 1600x1200 with DateStamp option) + 9 => 'Widescreen', #PH (SD900 3648x2048), 22 (HFS200 3264x1840) + 10 => 'Medium Widescreen', #22 (HFS200 1920x1080) + 14 => 'Small 1', #PH + 15 => 'Small 2', #PH + 16 => 'Small 3', #PH + 128 => '640x480 Movie', #PH (7D 60fps) + 129 => 'Medium Movie', #22 + 130 => 'Small Movie', #22 + 137 => '1280x720 Movie', #PH (S95 24fps; D60 50fps) + 142 => '1920x1080 Movie', #PH (D60 25fps) + 143 => '4096x2160 Movie', #PH (C200) +); +my %canonWhiteBalance = ( + # -1='Click", -2='Pasted' ?? - PH + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Cloudy', + 3 => 'Tungsten', + 4 => 'Fluorescent', + 5 => 'Flash', + 6 => 'Custom', + 7 => 'Black & White', + 8 => 'Shade', + 9 => 'Manual Temperature (Kelvin)', + 10 => 'PC Set1', #PH + 11 => 'PC Set2', #PH + 12 => 'PC Set3', #PH + 14 => 'Daylight Fluorescent', #3 + 15 => 'Custom 1', #PH + 16 => 'Custom 2', #PH + 17 => 'Underwater', #3 + 18 => 'Custom 3', #PH + 19 => 'Custom 4', #PH + 20 => 'PC Set4', #PH + 21 => 'PC Set5', #PH + # 22 - Custom 2? + 23 => 'Auto (ambience priority)', #PH (5DS) (perhaps this needs re-thinking?: forum13295) + # 30 - Click White Balance? + # 31 - Shot Settings? + # 137 - Tungsten? + # 138 - White Fluorescent? + # 139 - Fluorescent H? + # 140 - Manual? +); + +# picture styles used by the 5D +# (styles 0x4X may be downloaded from Canon) +# (called "ColorMatrix" in 1D owner manual) +my %pictureStyles = ( #12 + 0x00 => 'None', #PH + 0x01 => 'Standard', #15 + 0x02 => 'Portrait', #15 + 0x03 => 'High Saturation', #15 + 0x04 => 'Adobe RGB', #15 + 0x05 => 'Low Saturation', #15 + 0x06 => 'CM Set 1', #PH + 0x07 => 'CM Set 2', #PH + # "ColorMatrix" values end here + 0x21 => 'User Def. 1', + 0x22 => 'User Def. 2', + 0x23 => 'User Def. 3', + # "External" styles currently available from Canon are Nostalgia, Clear, + # Twilight and Emerald. The "User Def" styles change to these "External" + # codes when these styles are installed in the camera + 0x41 => 'PC 1', #PH + 0x42 => 'PC 2', #PH + 0x43 => 'PC 3', #PH + 0x81 => 'Standard', + 0x82 => 'Portrait', + 0x83 => 'Landscape', + 0x84 => 'Neutral', + 0x85 => 'Faithful', + 0x86 => 'Monochrome', + 0x87 => 'Auto', #PH + 0x88 => 'Fine Detail', #PH + 0xff => 'n/a', #PH (guess) + 0xffff => 'n/a', #PH (guess) +); +my %userDefStyles = ( #12/48 + Notes => q{ + Base style for user-defined picture styles. PC values represent external + picture styles which may be downloaded from Canon and installed in the + camera. + }, + 0x41 => 'PC 1', + 0x42 => 'PC 2', + 0x43 => 'PC 3', + 0x81 => 'Standard', + 0x82 => 'Portrait', + 0x83 => 'Landscape', + 0x84 => 'Neutral', + 0x85 => 'Faithful', + 0x86 => 'Monochrome', + 0x87 => 'Auto', #PH +); + +# picture style tag information for CameraInfo550D +my %psConv = ( + -559038737 => 'n/a', # = 0xdeadbeef ! LOL + OTHER => sub { shift }, +); +my %psInfo = ( + Format => 'int32s', + PrintHex => 1, + PrintConv => \%psConv, +); + +# ValueConv that makes long values binary type +my %longBin = ( + ValueConv => 'length($val) > 64 ? \$val : $val', + ValueConvInv => '$val', +); + +# conversions, etc for CameraColorCalibration tags +my %cameraColorCalibration = ( + Format => 'int16s[4]', + Unknown => 1, + PrintConv => 'sprintf("%4d %4d %4d (%dK)", split(" ",$val))', + PrintConvInv => '$val=~s/\s+/ /g; $val=~tr/()K//d; $val', +); + +# conversions, etc for PowerShot CameraColorCalibration tags +my %cameraColorCalibration2 = ( + Format => 'int16s[5]', + Unknown => 1, + PrintConv => 'sprintf("%4d %4d %4d %4d (%dK)", split(" ",$val))', + PrintConvInv => '$val=~s/\s+/ /g; $val=~tr/()K//d; $val', +); +# conversions, etc for byte-swapped FocusDistance tags +my %focusDistanceByteSwap = ( + # this is very odd (little-endian number on odd boundary), + # but it does seem to work better with my sample images - PH + Format => 'int16uRev', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => '$val > 655.345 ? "inf" : "$val m"', + PrintConvInv => '$val =~ s/ ?m$//; IsFloat($val) ? $val : 655.35', +); + +# common attributes for writable BinaryData directories +my %binaryDataAttrs = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, +); + +my %offOn = ( 0 => 'Off', 1 => 'On' ); + +#------------------------------------------------------------------------------ +# Canon EXIF Maker Notes +%Image::ExifTool::Canon::Main = ( + WRITE_PROC => \&WriteCanon, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x1 => { + Name => 'CanonCameraSettings', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::CameraSettings', + }, + }, + 0x2 => { + Name => 'CanonFocalLength', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::FocalLength' }, + }, + 0x3 => { + Name => 'CanonFlashInfo', + Unknown => 1, + }, + 0x4 => { + Name => 'CanonShotInfo', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::ShotInfo', + }, + }, + 0x5 => { + Name => 'CanonPanorama', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::Panorama' }, + }, + 0x6 => { + Name => 'CanonImageType', + Writable => 'string', + Groups => { 2 => 'Image' }, + }, + 0x7 => { + Name => 'CanonFirmwareVersion', + Writable => 'string', + }, + 0x8 => { + Name => 'FileNumber', + Writable => 'int32u', + Groups => { 2 => 'Image' }, + PrintConv => '$_=$val,s/(\d+)(\d{4})/$1-$2/,$_', + PrintConvInv => '$val=~s/-//g;$val', + }, + 0x9 => { + Name => 'OwnerName', + Writable => 'string', + # pad to 32 bytes (including null terminator which will be added) + # to avoid bug which crashes DPP if length is 4 bytes + ValueConvInv => '$val .= "\0" x (31 - length $val) if length $val < 31; $val', + }, + 0xa => { + Name => 'UnknownD30', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::UnknownD30', + }, + }, + 0xc => [ # square brackets for a conditional list + { + # D30 + Name => 'SerialNumber', + Condition => '$$self{Model} =~ /EOS D30\b/', + Writable => 'int32u', + PrintConv => 'sprintf("%.4x%.5d",$val>>16,$val&0xffff)', + PrintConvInv => '$val=~/(.*)-?(\d{5})$/ ? (hex($1)<<16)+$2 : undef', + }, + { + # serial number of 1D/1Ds/1D Mark II/1Ds Mark II is usually + # displayed w/o leeding zeros (ref 7) (1D uses 6 digits - PH) + Name => 'SerialNumber', + Condition => '$$self{Model} =~ /EOS-1D/', + Writable => 'int32u', + PrintConv => 'sprintf("%.6u",$val)', + PrintConvInv => '$val', + }, + { + # all other models (D60,300D,350D,REBEL,10D,20D,etc) + Name => 'SerialNumber', + Writable => 'int32u', + PrintConv => 'sprintf("%.10u",$val)', + PrintConvInv => '$val', + }, + ], + 0xd => [ + { + Name => 'CanonCameraInfo1D', + # (save size of this record as "CameraInfoCount" for later tests) + Condition => '($$self{CameraInfoCount} = $count) and $$self{Model} =~ /\b1DS?$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo1D' }, + }, + { + Name => 'CanonCameraInfo1DmkII', + Condition => '$$self{Model} =~ /\b1Ds? Mark II$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo1DmkII' }, + }, + { + Name => 'CanonCameraInfo1DmkIIN', + Condition => '$$self{Model} =~ /\b1Ds? Mark II N$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo1DmkIIN' }, + }, + { + Name => 'CanonCameraInfo1DmkIII', + Condition => '$$self{Model} =~ /\b1Ds? Mark III$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo1DmkIII' }, + }, + { + Name => 'CanonCameraInfo1DmkIV', + Condition => '$$self{Model} =~ /\b1D Mark IV$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo1DmkIV' }, + }, + { + Name => 'CanonCameraInfo1DX', + Condition => '$$self{Model} =~ /EOS-1D X$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo1DX' }, + }, + { + Name => 'CanonCameraInfo5D', + Condition => '$$self{Model} =~ /EOS 5D$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo5D' }, + }, + { + Name => 'CanonCameraInfo5DmkII', + Condition => '$$self{Model} =~ /EOS 5D Mark II$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo5DmkII' }, + }, + { + Name => 'CanonCameraInfo5DmkIII', + Condition => '$$self{Model} =~ /EOS 5D Mark III$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo5DmkIII' }, + }, + { + Name => 'CanonCameraInfo6D', + Condition => '$$self{Model} =~ /EOS 6D$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo6D' }, + }, + { + Name => 'CanonCameraInfo7D', + Condition => '$$self{Model} =~ /EOS 7D$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo7D' }, + }, + { + Name => 'CanonCameraInfo40D', + Condition => '$$self{Model} =~ /EOS 40D$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo40D' }, + }, + { + Name => 'CanonCameraInfo50D', + Condition => '$$self{Model} =~ /EOS 50D$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo50D' }, + }, + { + Name => 'CanonCameraInfo60D', + Condition => '$$self{Model} =~ /EOS 60D$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo60D' }, + }, + { + Name => 'CanonCameraInfo70D', + Condition => '$$self{Model} =~ /EOS 70D$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo70D' }, + }, + { + Name => 'CanonCameraInfo80D', + Condition => '$$self{Model} =~ /EOS 80D$/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo80D' }, + }, + { + Name => 'CanonCameraInfo450D', + Condition => '$$self{Model} =~ /\b(450D|REBEL XSi|Kiss X2)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo450D' }, + }, + { + Name => 'CanonCameraInfo500D', + Condition => '$$self{Model} =~ /\b(500D|REBEL T1i|Kiss X3)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo500D' }, + }, + { + Name => 'CanonCameraInfo550D', + Condition => '$$self{Model} =~ /\b(550D|REBEL T2i|Kiss X4)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo550D' }, + }, + { + Name => 'CanonCameraInfo600D', + Condition => '$$self{Model} =~ /\b(600D|REBEL T3i|Kiss X5)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo600D' }, + }, + { + Name => 'CanonCameraInfo650D', + Condition => '$$self{Model} =~ /\b(650D|REBEL T4i|Kiss X6i)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo650D' }, + }, + { + Name => 'CanonCameraInfo700D', + Condition => '$$self{Model} =~ /\b(700D|REBEL T5i|Kiss X7i)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo650D' }, + }, + { + Name => 'CanonCameraInfo750D', + Condition => '$$self{Model} =~ /\b(750D|Rebel T6i|Kiss X8i)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo750D' }, + }, + { + Name => 'CanonCameraInfo760D', + Condition => '$$self{Model} =~ /\b(760D|Rebel T6s|8000D)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo750D' }, + }, + { + Name => 'CanonCameraInfo1000D', + Condition => '$$self{Model} =~ /\b(1000D|REBEL XS|Kiss F)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo1000D' }, + }, + { + Name => 'CanonCameraInfo1100D', + Condition => '$$self{Model} =~ /\b(1100D|REBEL T3|Kiss X50)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo600D' }, + }, + { + Name => 'CanonCameraInfo1200D', + Condition => '$$self{Model} =~ /\b(1200D|REBEL T5|Kiss X70)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfo60D' }, + }, + { + Name => 'CanonCameraInfoPowerShot', + # valid if format is int32u[138] or int32u[148] + Condition => '$format eq "int32u" and ($count == 138 or $count == 148)', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfoPowerShot' }, + }, + { + Name => 'CanonCameraInfoPowerShot2', + # valid if format is int32u[162], int32u[167], int32u[171] or int32u[264] + Condition => q{ + $format eq "int32u" and ($count == 156 or $count == 162 or + $count == 167 or $count == 171 or $count == 264) + }, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfoPowerShot2' }, + }, + { + Name => 'CanonCameraInfoUnknown32', + Condition => '$format =~ /^int32/', + # (counts of 72, 85, 86, 93, 94, 96, 104) - PH + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfoUnknown32' }, + }, + { + Name => 'CanonCameraInfoUnknown16', + Condition => '$format =~ /^int16/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfoUnknown16' }, + }, + { + Name => 'CanonCameraInfoUnknown', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraInfoUnknown' }, + }, + ], + 0xe => { + Name => 'CanonFileLength', + Writable => 'int32u', + Groups => { 2 => 'Image' }, + }, + 0xf => [ + { # used by 1DmkII, 1DSmkII and 1DmkIIN + Name => 'CustomFunctions1D', + Condition => '$$self{Model} =~ /EOS-1D/', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::Functions1D', + }, + }, + { + Name => 'CustomFunctions5D', + Condition => '$$self{Model} =~ /EOS 5D/', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::Functions5D', + }, + }, + { + Name => 'CustomFunctions10D', + Condition => '$$self{Model} =~ /EOS 10D/', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::Functions10D', + }, + }, + { + Name => 'CustomFunctions20D', + Condition => '$$self{Model} =~ /EOS 20D/', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::Functions20D', + }, + }, + { + Name => 'CustomFunctions30D', + Condition => '$$self{Model} =~ /EOS 30D/', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::Functions30D', + }, + }, + { + Name => 'CustomFunctions350D', + Condition => '$$self{Model} =~ /\b(350D|REBEL XT|Kiss Digital N)\b/', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::Functions350D', + }, + }, + { + Name => 'CustomFunctions400D', + Condition => '$$self{Model} =~ /\b(400D|REBEL XTi|Kiss Digital X|K236)\b/', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::Functions400D', + }, + }, + { + Name => 'CustomFunctionsD30', + Condition => '$$self{Model} =~ /EOS D30\b/', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::FunctionsD30', + }, + }, + { + Name => 'CustomFunctionsD60', + Condition => '$$self{Model} =~ /EOS D60\b/', + SubDirectory => { + # the stored size in the D60 apparently doesn't include the size word: + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size-2,$size)', + # (D60 custom functions are basically the same as D30) + TagTable => 'Image::ExifTool::CanonCustom::FunctionsD30', + }, + }, + { + Name => 'CustomFunctionsUnknown', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::FuncsUnknown', + }, + }, + ], + 0x10 => { #PH + Name => 'CanonModelID', + Writable => 'int32u', + PrintHex => 1, + SeparateTable => 1, + PrintConv => \%canonModelID, + }, + 0x11 => { #PH + Name => 'MovieInfo', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::MovieInfo', + }, + }, + 0x12 => { + Name => 'CanonAFInfo', + # not really a condition -- just need to store the count for later + Condition => '$$self{AFInfoCount} = $count', + SubDirectory => { + # this record does not begin with a length word, so it + # has to be validated differently + Validate => 'Image::ExifTool::Canon::ValidateAFInfo($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::AFInfo', + }, + }, + 0x13 => { #PH + Name => 'ThumbnailImageValidArea', + # left,right,top,bottom edges of image in thumbnail, or all zeros for full frame + Notes => 'all zeros for full frame', + Writable => 'int16u', + Count => 4, + }, + 0x15 => { #PH + # display format for serial number + Name => 'SerialNumberFormat', + Writable => 'int32u', + PrintHex => 1, + PrintConv => { + 0x90000000 => 'Format 1', + 0xa0000000 => 'Format 2', + }, + }, + 0x1a => { #15 + Name => 'SuperMacro', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On (1)', + 2 => 'On (2)', + }, + }, + 0x1c => { #PH (A570IS) + Name => 'DateStampMode', + Writable => 'int16u', + Notes => 'used only in postcard mode', + PrintConv => { + 0 => 'Off', + 1 => 'Date', + 2 => 'Date & Time', + }, + }, + 0x1d => { #PH + Name => 'MyColors', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::MyColors', + }, + }, + 0x1e => { #PH + Name => 'FirmwareRevision', + Writable => 'int32u', + # as a hex number: 0xAVVVRR00, where (a bit of guessing here...) + # A = 'a' for alpha, 'b' for beta? + # V = version? (100,101 for normal releases, 100,110,120,130,170 for alpha/beta) + # R = revision? (01-07, except 00 for alpha/beta releases) + PrintConv => q{ + my $rev = sprintf("%.8x", $val); + my ($rel, $v1, $v2, $r1, $r2) = ($rev =~ /^(.)(.)(..)0?(.+)(..)$/); + my %r = ( a => 'Alpha ', b => 'Beta ', '0' => '' ); + $rel = defined $r{$rel} ? $r{$rel} : "Unknown($rel) "; + return "$rel$v1.$v2 rev $r1.$r2", + }, + PrintConvInv => q{ + $_=$val; s/Alpha ?/a/i; s/Beta ?/b/i; + s/Unknown ?\((.)\)/$1/i; s/ ?rev ?(.)\./0$1/; s/ ?rev ?//; + tr/a-fA-F0-9//dc; return hex $_; + }, + }, + # 0x1f - used for red-eye-corrected images - PH (A570IS) + # 0x22 - values 1 and 2 are 2 and 1 for flash pics, 0 otherwise - PH (A570IS) + 0x23 => { #31 + Name => 'Categories', + Writable => 'int32u', + Format => 'int32u', # (necessary to perform conversion for Condition) + Notes => '2 values: 1. always 8, 2. Categories', + Count => '2', + Condition => '$$valPt =~ /^\x08\0\0\0/', + ValueConv => '$val =~ s/^8 //; $val', + ValueConvInv => '"8 $val"', + PrintConvColumns => 2, + PrintConv => { + 0 => '(none)', + BITMASK => { + 0 => 'People', + 1 => 'Scenery', + 2 => 'Events', + 3 => 'User 1', + 4 => 'User 2', + 5 => 'User 3', + 6 => 'To Do', + }, + }, + }, + 0x24 => { #PH + Name => 'FaceDetect1', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::FaceDetect1', + }, + }, + 0x25 => { #PH + Name => 'FaceDetect2', + SubDirectory => { + TagTable => 'Image::ExifTool::Canon::FaceDetect2', + # (can't validate because this record uses a 1-byte count instead of a 2-byte count) + }, + }, + 0x26 => { #PH (A570IS,1DmkIII) + Name => 'CanonAFInfo2', + Condition => '$$valPt !~ /^\0\0\0\0/', # (data may be all zeros in thumbnail of 60D MOV video) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::AFInfo2', + }, + }, + 0x27 => { #PH + Name => 'ContrastInfo', + Condition => '$$valPt =~ /^\x0a\0/', # (seems to be various versions of this information) + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ContrastInfo' }, + }, + # 0x27 - value 1 is 1 for high ISO pictures, 0 otherwise + # value 4 is 9 for Flexizone and FaceDetect AF, 1 for Centre AF, 0 otherwise (SX10IS) + 0x28 => { #JD + # bytes 0-1=sequence number (encrypted), 2-5=date/time (encrypted) (ref JD) + Name => 'ImageUniqueID', + Format => 'undef', + Writable => 'int8u', + Groups => { 2 => 'Image' }, + RawConv => '$val eq "\0" x 16 ? undef : $val', + ValueConv => 'unpack("H*", $val)', + ValueConvInv => 'pack("H*", $val)', + }, + 0x29 => { #IB (G9) + Name => 'WBInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::WBInfo' }, + }, + # 0x2d - changes with categories (ref 31) + 0x2f => { #PH (G12) + Name => 'FaceDetect3', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::FaceDetect3', + }, + }, + # 0x32 - if length is 768, starting at offset 4 there are 6 RGGB 1/val int16 records: + # daylight,cloudy,tungsten,fluorescent,flash,kelvin (D30 2001, ref IB) + 0x35 => { #PH + Name => 'TimeInfo', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::TimeInfo', + }, + }, + 0x38 => { #PH + Name => 'BatteryType', + Writable => 'undef', + Condition => '$count == 76', + RawConv => '$val=~/^.{4}([^\0]+)/s ? $1 : undef', + RawConvInv => 'substr("\x4c\0\0\0".$val.("\0"x72), 0, 76)', + }, + 0x3c => { #PH (G1XmkII) + Name => 'AFInfo3', + Condition => '$$self{AFInfo3} = 1', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::AFInfo2', + }, + }, + # 0x44 (ShootInfo) + # 0x62 (UserSetting) + 0x81 => { #13 + Name => 'RawDataOffset', + # (can't yet write 1D raw files) + # Writable => 'int32u', + # Protected => 2, + }, + 0x82 => { #github219 (found on 1DS) + Name => 'RawDataLength', + # (can't yet write 1DS raw files) + # Writable => 'int32u', + # Protected => 2, + }, + 0x83 => { #PH + Name => 'OriginalDecisionDataOffset', + Writable => 'int32u', + OffsetPair => 1, # (just used as a flag, since this tag has no pair) + # this is an offset to the original decision data block + # (offset relative to start of file in JPEG images, but NOT DNG images!) + IsOffset => '$val and $$et{FILE_TYPE} ne "JPEG"', + Protected => 2, + DataTag => 'OriginalDecisionData', + }, + 0x90 => { # used by 1D and 1Ds + Name => 'CustomFunctions1D', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::Functions1D', + }, + }, + 0x91 => { #PH + Name => 'PersonalFunctions', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::PersonalFuncs', + }, + }, + 0x92 => { #PH + Name => 'PersonalFunctionValues', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::PersonalFuncValues', + }, + }, + 0x93 => { + Name => 'CanonFileInfo', # (ShootInfoEx) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::FileInfo', + }, + }, + 0x94 => { #PH + # AF points for 1D (45 points in 5 rows) + Name => 'AFPointsInFocus1D', + Notes => 'EOS 1D -- 5 rows: A1-7, B1-10, C1-11, D1-10, E1-7, center point is C6', + PrintConv => 'Image::ExifTool::Canon::PrintAFPoints1D($val)', + }, + 0x95 => { #PH (observed in 5D sample image) + Name => 'LensModel', # (LensName) + Writable => 'string', + }, + 0x96 => [ #PH (CMOSNumber) + { + Name => 'SerialInfo', + Condition => '$$self{Model} =~ /EOS 5D/', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::SerialInfo' }, + }, + { + Name => 'InternalSerialNumber', + Writable => 'string', + # remove trailing 0xff's if they exist (Kiss X3) + ValueConv => '$val=~s/\xff+$//; $val', + ValueConvInv => '$val', + }, + ], + 0x97 => { #PH (also see http://www.freepatentsonline.com/7657116.html) + Name => 'DustRemovalData', # (DustDeleteData) + Writable => 'undef', + Flags => [ 'Binary', 'Protected' ], + # 0x00: int8u - Version (0 or 1) + # 0x01: int8u - LensInfo ? (1) + # 0x02: int8u - AVValue ? (int8u for version 0, int16u for version 1) + # 0x03: int8u - POValue ? (int8u for version 0, int16u for version 1) + # 0x04: int16u - DustCount + # 0x06: int16u - FocalLength ? + # 0x08: int16u - LensID ? + # 0x0a: int16u - Width + # 0x0c: int16u - Height + # 0x0e: int16u - RAW_Width + # 0x10: int16u - RAW_Height + # 0x12: int16u - PixelPitch [um * 1000] + # 0x14: int16u - LpfDistance [mm * 1000] + # 0x16: int8u - TopOffset + # 0x17: int8u - BottomOffset + # 0x18: int8u - LeftOffset + # 0x19: int8u - RightOffset + # 0x1a: int8u - Year [-1900] + # 0x1b: int8u - Month + # 0x1c: int8u - Day + # 0x1d: int8u - Hour + # 0x1e: int8u - Minutes + # 0x1f: int8u - BrightDiff + # Table with DustCount entries: + # 0x22: int16u - DustX + # 0x24: int16u - DustY + # 0x26: int16u - DustSize + }, + 0x98 => { #PH + Name => 'CropInfo', # (ImageSizeOffset) + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CropInfo' }, + }, + 0x99 => { #PH (EOS 1D Mark III, 40D, etc) + Name => 'CustomFunctions2', # (CustomFunEx) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::Functions2', + }, + }, + 0x9a => { #PH + Name => 'AspectInfo', # (AspectRatioInfo) + SubDirectory => { TagTable => 'Image::ExifTool::Canon::AspectInfo' }, + }, + 0xa0 => { + Name => 'ProcessingInfo', # (DevelopParam) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::Processing', + }, + }, + 0xa1 => { Name => 'ToneCurveTable', %longBin }, #PH + 0xa2 => { Name => 'SharpnessTable', %longBin }, #PH + 0xa3 => { Name => 'SharpnessFreqTable', %longBin }, #PH + 0xa4 => { Name => 'WhiteBalanceTable', %longBin }, #PH + 0xa9 => { + Name => 'ColorBalance', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::ColorBalance', + }, + }, + 0xaa => { + Name => 'MeasuredColor', # (PresetWBDS) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::MeasuredColor', + }, + }, + 0xae => { + Name => 'ColorTemperature', + Writable => 'int16u', + }, + 0xb0 => { #PH + Name => 'CanonFlags', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::Flags', + }, + }, + 0xb1 => { #PH + Name => 'ModifiedInfo', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::ModifiedInfo', + }, + }, + 0xb2 => { Name => 'ToneCurveMatching', %longBin }, #PH + 0xb3 => { Name => 'WhiteBalanceMatching', %longBin }, #PH + 0xb4 => { #PH + Name => 'ColorSpace', + Writable => 'int16u', + PrintConv => { + 1 => 'sRGB', + 2 => 'Adobe RGB', + 65535 => 'n/a', + }, + }, + 0xb6 => { + Name => 'PreviewImageInfo', + SubDirectory => { + # Note: the first word of this block gives the correct block size in bytes, but + # the size is wrong by a factor of 2 in the IFD, so we must account for this + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size/2)', + TagTable => 'Image::ExifTool::Canon::PreviewImageInfo', + }, + }, + 0xd0 => { #PH + Name => 'VRDOffset', + Writable => 'int32u', + OffsetPair => 1, # (just used as a flag, since this tag has no pair) + Protected => 2, + DataTag => 'CanonVRD', + Notes => 'offset of VRD "recipe data" if it exists', + }, + 0xe0 => { #12 + Name => 'SensorInfo', # (ImageAreaDesc) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::SensorInfo', + }, + }, + 0x4001 => [ #13 (WBPacket) + { # (int16u[582]) - 20D and 350D + Condition => '$count == 582', + Name => 'ColorData1', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData1' }, + }, + { # (int16u[653]) - 1DmkII and 1DSmkII + Condition => '$count == 653', + Name => 'ColorData2', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData2' }, + }, + { # (int16u[796]) - 1DmkIIN, 5D, 30D, 400D + Condition => '$count == 796', + Name => 'ColorData3', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData3' }, + }, + { # (int16u[692|674|702|1227|1250|1251|1337]) + # 40D (692), 1DmkIII (674), 1DSmkIII (702), 450D/1000D (1227) + # 50D/5DmkII (1250), 500D/7D_pre-prod/1DmkIV_pre-prod (1251), + # 1DmkIV/7D/550D_pre-prod (1337), 550D (1338), 60D/1100D (1346) + Condition => q{ + $count == 692 or $count == 674 or $count == 702 or + $count == 1227 or $count == 1250 or $count == 1251 or + $count == 1337 or $count == 1338 or $count == 1346 + }, + Name => 'ColorData4', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData4' }, + }, + { # (int16u[5120]) - G10, G7X + Condition => '$count == 5120', + Name => 'ColorData5', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData5' }, + }, + { # (int16u[1273|1275]) - 600D (1273), 1200D (1275) + Condition => '$count == 1273 or $count == 1275', + Name => 'ColorData6', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData6' }, + }, + { # (int16u[1312|1313|1316]) + # 1DX/5DmkIII/650D/700D/M (1312), 6D/70D/100D (1313), + # 1DX firmware 1.x (1316), 7DmkII (1506) + Condition => '$count == 1312 or $count == 1313 or $count == 1316 or + $count == 1506', + Name => 'ColorData7', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData7' }, + }, + { # (int16u[1560|1592]) - 5DS/5DSR (1560), 80D (1592), 1300D (1353) ref IB + Condition => '$count == 1560 or $count == 1592 or $count == 1353 or $count == 1602', + Name => 'ColorData8', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData8' }, + }, + { # (int16u[1816|1820|1824]) - M50 (1820) ref PH, EOS R (1824), EOS RP, SX70 (1816) ref IB + Condition => '$count == 1816 or $count == 1820 or $count == 1824', + Name => 'ColorData9', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData9' }, + }, + { # (int16u[2024|3656]) - 1DXmkIII (2024) ref IB, R5/R6 (3656) ref PH + Condition => '$count == 2024 or $count == 3656', + Name => 'ColorData10', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData10' }, + }, + { # (int16u[3973]) - R3 ref IB + Condition => '$count == 3973 or $count == 3778', + Name => 'ColorData11', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorData11' }, + }, + { + Name => 'ColorDataUnknown', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorDataUnknown' }, + }, + ], + 0x4002 => { #PH + # unknown data block in some JPEG and CR2 images + # (5kB for most models, but 22kb for 5D and 30D, and 43kB for 5DmkII so Drop it) + Name => 'CRWParam', + Format => 'undef', + Flags => [ 'Unknown', 'Binary', 'Drop' ], + }, + 0x4003 => { #PH + Name => 'ColorInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorInfo' }, + }, + 0x4005 => { #PH + Name => 'Flavor', + Notes => 'unknown 49kB block, not copied to JPEG images', + # 'Drop' because not found in JPEG images (too large for APP1 anyway) + Flags => [ 'Unknown', 'Binary', 'Drop' ], + }, + 0x4008 => { #53 + Name => 'PictureStyleUserDef', # (BasePictStyleOfUser) + Writable => 'int16u', + Count => 3, # UserDef1, UserDef2, UserDef3 + PrintHex => 1, + SeparateTable => 'PictureStyle', + PrintConv => [\%pictureStyles,\%pictureStyles,\%pictureStyles], + }, + 0x4009 => { #53 + Name => 'PictureStylePC', # (BasePictStyleOfUser) + Writable => 'int16u', + Count => 3, # PC1, PC2, PC3 + PrintHex => 1, + SeparateTable => 'PictureStyle', + PrintConv => [\%pictureStyles,\%pictureStyles,\%pictureStyles], + }, + 0x4010 => { #forum2933 + Name => 'CustomPictureStyleFileName', # (PictStyleCaption) + Writable => 'string', + }, + # 0x4011 (PictStyleAppendInfo) + # 0x4012 (CustomWBCaption) + 0x4013 => { #PH + Name => 'AFMicroAdj', # (AFMicroAdjust) + SubDirectory => { + # Canon DPP 3.13 is known to truncate this data to 0x14 bytes (from 0x2c), + # so specifically check for 0x2c to avoid giving a warning in this case + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size,0x2c)', + TagTable => 'Image::ExifTool::Canon::AFMicroAdj', + }, + }, + # 0x4014 (similar to 0x83?) + 0x4015 => [{ + Name => 'VignettingCorr', # (LensPacket) + Condition => '$$valPt =~ /^\0/ and $$valPt !~ /^\0\0\0\0/', # (data may be all zeros for 60D) + SubDirectory => { + # (the size word is at byte 2 in this structure) + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart+2,$size)', + TagTable => 'Image::ExifTool::Canon::VignettingCorr', + }, + },{ + Name => 'VignettingCorrUnknown1', + Condition => '$$valPt =~ /^[\x01\x02\x10\x20]/ and $$valPt !~ /^\0\0\0\0/', + SubDirectory => { + # (the size word is at byte 2 in this structure) + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart+2,$size)', + TagTable => 'Image::ExifTool::Canon::VignettingCorrUnknown', + }, + },{ + Name => 'VignettingCorrUnknown2', + Condition => '$$valPt !~ /^\0\0\0\0/', + SubDirectory => { + # (the size word is at byte 4 for version 3 of this structure, but not always!) + # Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart+4,$size)', + TagTable => 'Image::ExifTool::Canon::VignettingCorrUnknown', + }, + }], + 0x4016 => { + Name => 'VignettingCorr2', # (ImageCorrectActual) + SubDirectory => { + # (the size word is actually 4 bytes, but it doesn't matter if little-endian) + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::VignettingCorr2', + }, + }, + 0x4018 => { #PH + Name => 'LightingOpt', # (ImageCorrect) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::LightingOpt', + } + }, + 0x4019 => { #20 + Name => 'LensInfo', # (LensInfoForService) + SubDirectory => { + TagTable => 'Image::ExifTool::Canon::LensInfo', + } + }, + 0x4020 => { #PH + Name => 'AmbienceInfo', + Condition => '$$valPt !~ /^\0\0\0\0/', # (data may be all zeros for 60D) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::Ambience', + } + }, + 0x4021 => { #PH + Name => 'MultiExp', # (ExifDSTagMultipleExposure) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::MultiExp', + } + }, + 0x4024 => { #PH + Name => 'FilterInfo', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::FilterInfo', + } + }, + 0x4025 => { #PH + Name => 'HDRInfo', # (HighDynamicRange) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::HDRInfo', + } + }, + 0x4026 => { #github#119 + Name => 'LogInfo', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::LogInfo', + } + }, + 0x4028 => { #PH + Name => 'AFConfig', # (AFTabInfo) + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::AFConfig', + } + }, + # 0x402b - crop information (forum14904) + 0x403f => { #25 + Name => 'RawBurstModeRoll', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::RawBurstInfo', + } + }, +); + +#.............................................................................. +# Canon camera settings (MakerNotes tag 0x01) +# BinaryData (keys are indices into the int16s array) +%Image::ExifTool::Canon::CameraSettings = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + DATAMEMBER => [ 22, 25 ], # necessary for writing + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 1 => { + Name => 'MacroMode', + PrintConv => { + 1 => 'Macro', + 2 => 'Normal', + }, + }, + 2 => { + Name => 'SelfTimer', + # Custom timer mode if bit 0x4000 is set - PH (A570IS) + PrintConv => q{ + return 'Off' unless $val; + return (($val&0xfff) / 10) . ' s' . ($val & 0x4000 ? ', Custom' : ''); + }, + PrintConvInv => q{ + return 0 if $val =~ /^Off/i; + $val =~ s/\s*s(ec)?\b//i; + $val =~ s/,?\s*Custom$//i ? ($val*10) | 0x4000 : $val*10; + }, + }, + 3 => { + Name => 'Quality', + PrintConv => \%canonQuality, + }, + 4 => { + Name => 'CanonFlashMode', + PrintConv => { + -1 => 'n/a', # (PH, EOS M MOV video) + 0 => 'Off', + 1 => 'Auto', + 2 => 'On', + 3 => 'Red-eye reduction', + 4 => 'Slow-sync', + 5 => 'Red-eye reduction (Auto)', + 6 => 'Red-eye reduction (On)', + 16 => 'External flash', # not set in D30 or 300D + }, + }, + 5 => { + Name => 'ContinuousDrive', + PrintConv => { + 0 => 'Single', + 1 => 'Continuous', + 2 => 'Movie', #PH + 3 => 'Continuous, Speed Priority', #PH + 4 => 'Continuous, Low', #PH + 5 => 'Continuous, High', #PH + 6 => 'Silent Single', #PH + 8 => 'Continuous, High+', #WolfgangGulcker + # ref A: https://exiftool.org/forum/index.php/topic,5701.msg27843.html#msg27843 + 9 => 'Single, Silent', #A + 10 => 'Continuous, Silent', #A + # 11 - seen for SX260 + # 32-34 - Self-timer? + }, + }, + 7 => { + Name => 'FocusMode', + PrintConv => { + 0 => 'One-shot AF', + 1 => 'AI Servo AF', + 2 => 'AI Focus AF', + 3 => 'Manual Focus (3)', + 4 => 'Single', + 5 => 'Continuous', + 6 => 'Manual Focus (6)', + 16 => 'Pan Focus', #PH + # 137 - Single? + 256 => 'AF + MF', #PH (NC, EOS M) + 257 => 'Live View', #forum12082 + 512 => 'Movie Snap Focus', #48 + 519 => 'Movie Servo AF', #PH (NC, EOS M) + }, + }, + 9 => { #PH + Name => 'RecordMode', + RawConv => '$val==-1 ? undef : $val', #22 + PrintConv => { + 1 => 'JPEG', + 2 => 'CRW+THM', # (300D,etc) + 3 => 'AVI+THM', # (30D) + 4 => 'TIF', # +THM? (1Ds) (unconfirmed) + 5 => 'TIF+JPEG', # (1D) (unconfirmed) + 6 => 'CR2', # +THM? (1D,30D,350D) + 7 => 'CR2+JPEG', # (S30) + 9 => 'MOV', # (S95 MOV) + 10 => 'MP4', # (SX280 MP4) + 11 => 'CRM', #PH (C200 CRM) + 12 => 'CR3', #PH (EOS R) + 13 => 'CR3+JPEG', #PH (EOS R) + 14 => 'HIF', #PH (NC) + 15 => 'CR3+HIF', #PH (1DXmkIII) + }, + }, + 10 => { + Name => 'CanonImageSize', + PrintConvColumns => 2, + PrintConv => \%canonImageSize, + }, + 11 => { + Name => 'EasyMode', + PrintConvColumns => 3, + PrintConv => { + # references: + # A = http://homepage3.nifty.com/kamisaka/makernote/makernote_canon.htm + # B = http://www.burren.cx/david/canon.html + # C = DPP 3.11.26 + 0 => 'Full auto', + 1 => 'Manual', + 2 => 'Landscape', + 3 => 'Fast shutter', + 4 => 'Slow shutter', + 5 => 'Night', # (C='Night Scene') + 6 => 'Gray Scale', #PH (A/B/C='Black & White') + 7 => 'Sepia', + 8 => 'Portrait', + 9 => 'Sports', + 10 => 'Macro', + 11 => 'Black & White', #PH (A='Black & White', B/C='Pan focus') + 12 => 'Pan focus', # (A='Pan focus', C='Vivid') + 13 => 'Vivid', #PH (A='Vivid', C='Neutral') + 14 => 'Neutral', #PH (A='Natural', C='Black & White') + 15 => 'Flash Off', #8 (C=<none>) + 16 => 'Long Shutter', #PH + 17 => 'Super Macro', #PH (C='Macro') + 18 => 'Foliage', #PH + 19 => 'Indoor', #PH + 20 => 'Fireworks', #PH + 21 => 'Beach', #PH + 22 => 'Underwater', #PH + 23 => 'Snow', #PH + 24 => 'Kids & Pets', #PH + 25 => 'Night Snapshot', #PH + 26 => 'Digital Macro', #PH + 27 => 'My Colors', #PH + 28 => 'Movie Snap', #PH + 29 => 'Super Macro 2', #PH + 30 => 'Color Accent', #18 + 31 => 'Color Swap', #18 + 32 => 'Aquarium', #18 + 33 => 'ISO 3200', #18 + 34 => 'ISO 6400', #PH + 35 => 'Creative Light Effect', #PH + 36 => 'Easy', #PH + 37 => 'Quick Shot', #PH + 38 => 'Creative Auto', #39 + 39 => 'Zoom Blur', #PH + 40 => 'Low Light', #PH + 41 => 'Nostalgic', #PH + 42 => 'Super Vivid', #PH (SD4500) + 43 => 'Poster Effect', #PH (SD4500) + 44 => 'Face Self-timer', #PH + 45 => 'Smile', #PH + 46 => 'Wink Self-timer', #PH + 47 => 'Fisheye Effect', #PH (SX30IS,IXUS240) + 48 => 'Miniature Effect', #PH (SD4500) + 49 => 'High-speed Burst', #PH + 50 => 'Best Image Selection', #PH + 51 => 'High Dynamic Range', #PH (S95) + 52 => 'Handheld Night Scene', #PH + 53 => 'Movie Digest', #PH + 54 => 'Live View Control', #PH + 55 => 'Discreet', #PH + 56 => 'Blur Reduction', #PH + 57 => 'Monochrome', #PH (SX260 B&W,Sepia,Blue tone) + 58 => 'Toy Camera Effect', #51 + 59 => 'Scene Intelligent Auto', #PH (T3i) (C='High-speed Burst HQ' !!) + 60 => 'High-speed Burst HQ', #PH (C='High-speed Burst HQ', same as 59) + 61 => 'Smooth Skin', #51 + 62 => 'Soft Focus', #PH (SX260,IXUS240) + 68 => 'Food', #PH (250D) + # 83 - seen for EOS M200 (ref PH) + 84 => 'HDR Art Standard', #PH (80D) + 85 => 'HDR Art Vivid', #PH (80D) + 93 => 'HDR Art Bold', #PH (80D) + # 83 - seen for EOS M3 night shot (PH) + 257 => 'Spotlight', #PH + 258 => 'Night 2', #PH + 259 => 'Night+', + 260 => 'Super Night', #PH + 261 => 'Sunset', #PH (SX10IS) + 263 => 'Night Scene', #PH + 264 => 'Surface', #PH + 265 => 'Low Light 2', #PH + }, + }, + 12 => { + Name => 'DigitalZoom', + PrintConv => { + 0 => 'None', + 1 => '2x', + 2 => '4x', + 3 => 'Other', # value obtained from 2*$val[37]/$val[36] + }, + }, + 13 => { + Name => 'Contrast', + RawConv => '$val == 0x7fff ? undef : $val', + %Image::ExifTool::Exif::printParameter, + }, + 14 => { + Name => 'Saturation', + RawConv => '$val == 0x7fff ? undef : $val', + %Image::ExifTool::Exif::printParameter, + }, + 15 => { + Name => 'Sharpness', + RawConv => '$val == 0x7fff ? undef : $val', + Notes => q{ + some models use a range of -2 to +2 where 0 is normal sharpening, and + others use a range of 0 to 7 where 0 is no sharpening + }, + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 16 => { + Name => 'CameraISO', + RawConv => '$val == 0x7fff ? undef : $val', + ValueConv => 'Image::ExifTool::Canon::CameraISO($val)', + ValueConvInv => 'Image::ExifTool::Canon::CameraISO($val,1)', + }, + 17 => { + Name => 'MeteringMode', + PrintConv => { + 0 => 'Default', # older Ixus + 1 => 'Spot', + 2 => 'Average', #PH + 3 => 'Evaluative', + 4 => 'Partial', + 5 => 'Center-weighted average', + }, + }, + 18 => { + # this is always 2 for the 300D - PH + Name => 'FocusRange', + PrintConv => { + 0 => 'Manual', + 1 => 'Auto', + 2 => 'Not Known', + 3 => 'Macro', + 4 => 'Very Close', #PH + 5 => 'Close', #PH + 6 => 'Middle Range', #PH + 7 => 'Far Range', + 8 => 'Pan Focus', + 9 => 'Super Macro', #PH + 10=> 'Infinity', #PH + }, + }, + 19 => { + Name => 'AFPoint', + Flags => 'PrintHex', + RawConv => '$val==0 ? undef : $val', + PrintConv => { + 0x2005 => 'Manual AF point selection', + 0x3000 => 'None (MF)', + 0x3001 => 'Auto AF point selection', + 0x3002 => 'Right', + 0x3003 => 'Center', + 0x3004 => 'Left', + 0x4001 => 'Auto AF point selection', + 0x4006 => 'Face Detect', #PH (A570IS) + }, + }, + 20 => { + Name => 'CanonExposureMode', + PrintConv => { + 0 => 'Easy', + 1 => 'Program AE', + 2 => 'Shutter speed priority AE', + 3 => 'Aperture-priority AE', + 4 => 'Manual', + 5 => 'Depth-of-field AE', + 6 => 'M-Dep', #PH + 7 => 'Bulb', #30 + 8 => 'Flexible-priority AE', #ArnoldVanOostrum + }, + }, + 22 => { #4 + Name => 'LensType', + Format => 'int16u', + RawConv => '$val ? $$self{LensType}=$val : undef', # don't use if value is zero + Notes => 'this value is incorrect for EOS 7D images with lenses of type 256 or greater', + SeparateTable => 1, + DataMember => 'LensType', + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 23 => { + Name => 'MaxFocalLength', + Format => 'int16u', + # this is a bit tricky, but we need the FocalUnits to convert this to mm + RawConvInv => '$val * ($$self{FocalUnits} || 1)', + ValueConv => '$val / ($$self{FocalUnits} || 1)', + ValueConvInv => '$val', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', + }, + 24 => { + Name => 'MinFocalLength', + Format => 'int16u', + RawConvInv => '$val * ($$self{FocalUnits} || 1)', + ValueConv => '$val / ($$self{FocalUnits} || 1)', + ValueConvInv => '$val', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', + }, + 25 => { + Name => 'FocalUnits', + # conversion from raw focal length values to mm + DataMember => 'FocalUnits', + RawConv => '$$self{FocalUnits} = $val', + PrintConv => '"$val/mm"', + PrintConvInv => '$val=~s/\s*\/?\s*mm//;$val', + }, + 26 => { #9 + Name => 'MaxAperture', + RawConv => '$val > 0 ? $val : undef', + ValueConv => 'exp(Image::ExifTool::Canon::CanonEv($val)*log(2)/2)', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv(log($val)*2/log(2))', + PrintConv => 'sprintf("%.2g",$val)', + PrintConvInv => '$val', + }, + 27 => { #PH + Name => 'MinAperture', + RawConv => '$val > 0 ? $val : undef', + ValueConv => 'exp(Image::ExifTool::Canon::CanonEv($val)*log(2)/2)', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv(log($val)*2/log(2))', + PrintConv => 'sprintf("%.2g",$val)', + PrintConvInv => '$val', + }, + 28 => { + Name => 'FlashActivity', + RawConv => '$val==-1 ? undef : $val', + }, + 29 => { + Name => 'FlashBits', + PrintConvColumns => 2, + PrintConv => { + 0 => '(none)', + BITMASK => { + 0 => 'Manual', #PH + 1 => 'TTL', #PH + 2 => 'A-TTL', #PH + 3 => 'E-TTL', #PH + 4 => 'FP sync enabled', + 7 => '2nd-curtain sync used', + 11 => 'FP sync used', + 13 => 'Built-in', + 14 => 'External', #(may not be set in manual mode - ref 37) + }, + }, + }, + 32 => { + Name => 'FocusContinuous', + RawConv => '$val==-1 ? undef : $val', + PrintConv => { + 0 => 'Single', + 1 => 'Continuous', + 8 => 'Manual', #22 + }, + }, + 33 => { #PH + Name => 'AESetting', + RawConv => '$val==-1 ? undef : $val', + PrintConv => { + 0 => 'Normal AE', + 1 => 'Exposure Compensation', + 2 => 'AE Lock', + 3 => 'AE Lock + Exposure Comp.', + 4 => 'No AE', + }, + }, + 34 => { #PH + Name => 'ImageStabilization', + RawConv => '$val==-1 ? undef : $val', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'Shoot Only', #15 + 3 => 'Panning', # (A570IS) + 4 => 'Dynamic', # (SX30IS) (was 'On, Video') + # (don't know what bit 0x100 indicates) + 256 => 'Off (2)', + 257 => 'On (2)', + 258 => 'Shoot Only (2)', + 259 => 'Panning (2)', + 260 => 'Dynamic (2)', + }, + }, + 35 => { #PH + Name => 'DisplayAperture', + RawConv => '$val ? $val : undef', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 36 => 'ZoomSourceWidth', #PH + 37 => 'ZoomTargetWidth', #PH + 39 => { #22 + Name => 'SpotMeteringMode', + RawConv => '$val==-1 ? undef : $val', + PrintConv => { + 0 => 'Center', + 1 => 'AF Point', + }, + }, + 40 => { #PH + Name => 'PhotoEffect', + RawConv => '$val==-1 ? undef : $val', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'Vivid', + 2 => 'Neutral', + 3 => 'Smooth', + 4 => 'Sepia', + 5 => 'B&W', + 6 => 'Custom', + 100 => 'My Color Data', + }, + }, + 41 => { #PH (A570IS) + Name => 'ManualFlashOutput', + PrintHex => 1, + PrintConv => { + 0 => 'n/a', + 0x500 => 'Full', + 0x502 => 'Medium', + 0x504 => 'Low', + 0x7fff => 'n/a', # (EOS models) + }, + }, + # 41 => non-zero for manual flash intensity - PH (A570IS) + 42 => { + Name => 'ColorTone', + RawConv => '$val == 0x7fff ? undef : $val', + %Image::ExifTool::Exif::printParameter, + }, + 46 => { #PH + Name => 'SRAWQuality', + RawConv => '$val==-1 ? undef : $val', + PrintConv => { + 0 => 'n/a', + 1 => 'sRAW1 (mRAW)', + 2 => 'sRAW2 (sRAW)', + }, + }, + # 47 - related to aspect ratio: 100=4:3,70=1:1/16:9,90=3:2,60=4:5 (PH G12) + # (roughly image area in percent - 4:3=100%,1:1/16:9=75%,3:2=89%,4:5=60%) +); + +# focal length information (MakerNotes tag 0x02) +%Image::ExifTool::Canon::FocalLength = ( + %binaryDataAttrs, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => { #9 + Name => 'FocalType', + RawConv => '$val ? $val : undef', # don't use if value is zero + PrintConv => { + 1 => 'Fixed', + 2 => 'Zoom', + }, + }, + 1 => { + Name => 'FocalLength', + # the EXIF FocalLength is more reliable, so set this priority to zero + Priority => 0, + RawConv => '$val ? $val : undef', # don't use if value is zero + RawConvInv => q{ + my $focalUnits = $$self{FocalUnits}; + unless ($focalUnits) { + $focalUnits = 1; + # (this happens when writing FocalLength to CRW images) + $self->Warn("FocalUnits not available for FocalLength conversion (1 assumed)"); + } + return $val * $focalUnits; + }, + ValueConv => '$val / ($$self{FocalUnits} || 1)', + ValueConvInv => '$val', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', + }, + 2 => [ #4 + { + Name => 'FocalPlaneXSize', + Notes => q{ + these focal plane sizes are only valid for some models, and are affected by + digital zoom if applied + }, + # this conversion is valid only for PowerShot models and these EOS models: + # D30, D60, 1D, 1DS, 5D, 10D, 20D, 30D, 300D, 350D, and 400D + Condition => q{ + $$self{Model} !~ /EOS/ or + $$self{Model} =~ /\b(1DS?|5D|D30|D60|10D|20D|30D|K236)$/ or + $$self{Model} =~ /\b((300D|350D|400D) DIGITAL|REBEL( XTi?)?|Kiss Digital( [NX])?)$/ + }, + # focal plane image dimensions in 1/1000 inch -- convert to mm + RawConv => '$val < 40 ? undef : $val', # must be reasonable + ValueConv => '$val * 25.4 / 1000', + ValueConvInv => 'int($val * 1000 / 25.4 + 0.5)', + PrintConv => 'sprintf("%.2f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//;$val', + },{ + Name => 'FocalPlaneXUnknown', + Unknown => 1, + }, + ], + 3 => [ #4 + { + Name => 'FocalPlaneYSize', + Condition => q{ + $$self{Model} !~ /EOS/ or + $$self{Model} =~ /\b(1DS?|5D|D30|D60|10D|20D|30D|K236)$/ or + $$self{Model} =~ /\b((300D|350D|400D) DIGITAL|REBEL( XTi?)?|Kiss Digital( [NX])?)$/ + }, + RawConv => '$val < 40 ? undef : $val', # must be reasonable + ValueConv => '$val * 25.4 / 1000', + ValueConvInv => 'int($val * 1000 / 25.4 + 0.5)', + PrintConv => 'sprintf("%.2f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//;$val', + },{ + Name => 'FocalPlaneYUnknown', + Unknown => 1, + }, + ], +); + +# Canon shot information (MakerNotes tag 0x04) +# BinaryData (keys are indices into the int16s array) +%Image::ExifTool::Canon::ShotInfo = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + DATAMEMBER => [ 19 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 1 => { #PH + Name => 'AutoISO', + Notes => 'actual ISO used = BaseISO * AutoISO / 100', + ValueConv => 'exp($val/32*log(2))*100', + ValueConvInv => '32*log($val/100)/log(2)', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 2 => { + Name => 'BaseISO', + Priority => 0, + RawConv => '$val ? $val : undef', + ValueConv => 'exp($val/32*log(2))*100/32', + ValueConvInv => '32*log($val*32/100)/log(2)', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 3 => { #9/PH + Name => 'MeasuredEV', + Notes => q{ + this is the Canon name for what could better be called MeasuredLV, and + should be close to the calculated LightValue for a proper exposure with most + models + }, + # empirical offset of +5 seems to be good for EOS models, but maybe + # the offset should be less by up to 1 EV for some PowerShot models + ValueConv => '$val / 32 + 5', + ValueConvInv => '($val - 5) * 32', + PrintConv => 'sprintf("%.2f",$val)', + PrintConvInv => '$val', + }, + 4 => { #2, 9 + Name => 'TargetAperture', + RawConv => '$val > 0 ? $val : undef', + ValueConv => 'exp(Image::ExifTool::Canon::CanonEv($val)*log(2)/2)', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv(log($val)*2/log(2))', + PrintConv => 'sprintf("%.2g",$val)', + PrintConvInv => '$val', + }, + 5 => { #2 + Name => 'TargetExposureTime', + # ignore obviously bad values (also, -32768 may be used for n/a) + # (note that a few models always write 0: DC211, and video models) + RawConv => '($val > -1000 and ($val or $$self{Model}=~/(EOS|PowerShot|IXUS|IXY)/))? $val : undef', + ValueConv => 'exp(-Image::ExifTool::Canon::CanonEv($val)*log(2))', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv(-log($val)/log(2))', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 6 => { + Name => 'ExposureCompensation', + ValueConv => 'Image::ExifTool::Canon::CanonEv($val)', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv($val)', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 7 => { + Name => 'WhiteBalance', + PrintConv => \%canonWhiteBalance, + SeparateTable => 1, + }, + 8 => { #PH + Name => 'SlowShutter', + PrintConv => { + -1 => 'n/a', + 0 => 'Off', + 1 => 'Night Scene', + 2 => 'On', + 3 => 'None', + }, + }, + 9 => { + Name => 'SequenceNumber', + Description => 'Shot Number In Continuous Burst', + Notes => 'valid only for some models', #PH (eg. not the 5DmkIII) + }, + 10 => { #PH/17 + Name => 'OpticalZoomCode', + Groups => { 2 => 'Camera' }, + Notes => 'for many PowerShot models, a this is 0-6 for wide-tele zoom', + # (for many models, 0-6 represent 0-100% zoom, but it is always 8 for + # EOS models, and I have seen values of 16,20,28,32 and 39 too...) + # - set to 8 for "n/a" by Canon software (ref 22) + PrintConv => '$val == 8 ? "n/a" : $val', + PrintConvInv => '$val =~ /[a-z]/i ? 8 : $val', + }, + # 11 - (8 for all EOS samples, [0,8] for other models - PH) + 12 => { #37 + Name => 'CameraTemperature', + Condition => '$$self{Model} =~ /EOS/ and $$self{Model} !~ /EOS-1DS?$/', + Groups => { 2 => 'Camera' }, + Notes => 'newer EOS models only', + # usually zero if not valid for an EOS model (exceptions: 1D, 1DS) + RawConv => '$val ? $val : undef', + ValueConv => '$val - 128', + ValueConvInv => '$val + 128', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 13 => { #PH + Name => 'FlashGuideNumber', + RawConv => '$val==-1 ? undef : $val', + ValueConv => '$val / 32', + ValueConvInv => '$val * 32', + }, + # AF points for Ixus and IxusV cameras - 02/17/04 M. Rommel (also D30/D60 - PH) + 14 => { #2 + Name => 'AFPointsInFocus', + Notes => 'used by D30, D60 and some PowerShot/Ixus models', + Groups => { 2 => 'Camera' }, + Flags => 'PrintHex', + RawConv => '$val==0 ? undef : $val', + PrintConvColumns => 2, + PrintConv => { + 0x3000 => 'None (MF)', + 0x3001 => 'Right', + 0x3002 => 'Center', + 0x3003 => 'Center+Right', + 0x3004 => 'Left', + 0x3005 => 'Left+Right', + 0x3006 => 'Left+Center', + 0x3007 => 'All', + }, + }, + 15 => { + Name => 'FlashExposureComp', + Description => 'Flash Exposure Compensation', + ValueConv => 'Image::ExifTool::Canon::CanonEv($val)', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv($val)', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 16 => { + Name => 'AutoExposureBracketing', + PrintConv => { + -1 => 'On', + 0 => 'Off', + 1 => 'On (shot 1)', + 2 => 'On (shot 2)', + 3 => 'On (shot 3)', + }, + }, + 17 => { + Name => 'AEBBracketValue', + ValueConv => 'Image::ExifTool::Canon::CanonEv($val)', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv($val)', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 18 => { #22 + Name => 'ControlMode', + PrintConv => { + 0 => 'n/a', + 1 => 'Camera Local Control', + # 2 - have seen this for EOS M studio picture + 3 => 'Computer Remote Control', + }, + }, + 19 => { + Name => 'FocusDistanceUpper', + DataMember => 'FocusDistanceUpper', + Format => 'int16u', + Notes => 'FocusDistance tags are only extracted if FocusDistanceUpper is non-zero', + RawConv => '($$self{FocusDistanceUpper} = $val) || undef', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => '$val > 655.345 ? "inf" : "$val m"', + PrintConvInv => '$val =~ s/ ?m$//; IsFloat($val) ? $val : 655.35', + }, + 20 => { + Name => 'FocusDistanceLower', # (seems to be the upper distance for the 400D) + Condition => '$$self{FocusDistanceUpper}', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => '$val > 655.345 ? "inf" : "$val m"', + PrintConvInv => '$val =~ s/ ?m$//; IsFloat($val) ? $val : 655.35', + }, + 21 => { + Name => 'FNumber', + Priority => 0, + RawConv => '$val ? $val : undef', + # approximate big translation table by simple calculation - PH + ValueConv => 'exp(Image::ExifTool::Canon::CanonEv($val)*log(2)/2)', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv(log($val)*2/log(2))', + PrintConv => 'sprintf("%.2g",$val)', + PrintConvInv => '$val', + }, + 22 => [ + { + Name => 'ExposureTime', + # encoding is different for 20D and 350D (darn!) + # (but note that encoding is the same for TargetExposureTime - PH) + Condition => '$$self{Model} =~ /\b(20D|350D|REBEL XT|Kiss Digital N)\b/', + Priority => 0, + # many models write 0 here in JPEG images (even though 0 is the + # value for an exposure time of 1 sec), but apparently a value of 0 + # is valid in a CRW image (=1s, D60 sample) + RawConv => '($val or $$self{FILE_TYPE} eq "CRW") ? $val : undef', + # approximate big translation table by simple calculation - PH + ValueConv => 'exp(-Image::ExifTool::Canon::CanonEv($val)*log(2))*1000/32', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv(-log($val*32/1000)/log(2))', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + { + Name => 'ExposureTime', + Priority => 0, + # many models write 0 here in JPEG images (even though 0 is the + # value for an exposure time of 1 sec), but apparently a value of 0 + # is valid in a CRW image (=1s, D60 sample) + RawConv => '($val or $$self{FILE_TYPE} eq "CRW") ? $val : undef', + # approximate big translation table by simple calculation - PH + ValueConv => 'exp(-Image::ExifTool::Canon::CanonEv($val)*log(2))', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv(-log($val)/log(2))', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + ], + 23 => { #37 + Name => 'MeasuredEV2', + Description => 'Measured EV 2', + RawConv => '$val ? $val : undef', + ValueConv => '$val / 8 - 6', + ValueConvInv => 'int(($val + 6) * 8 + 0.5)', + }, + 24 => { + Name => 'BulbDuration', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + # 25 - (usually 0, but 1 for 2s timer?, 19 for small AVI, 14 for large + # AVI, and -6 and -10 for shots 1 and 2 with stitch assist - PH) + 26 => { #15 + Name => 'CameraType', + Groups => { 2 => 'Camera' }, + PrintConv => { + 0 => 'n/a', + 248 => 'EOS High-end', + 250 => 'Compact', + 252 => 'EOS Mid-range', + 255 => 'DV Camera', #PH + }, + }, + 27 => { + Name => 'AutoRotate', + RawConv => '$val >= 0 ? $val : undef', + PrintConv => { + -1 => 'n/a', # (set to -1 when rotated by Canon software) + 0 => 'None', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 180', + 3 => 'Rotate 270 CW', + }, + }, + 28 => { #15 + Name => 'NDFilter', + PrintConv => { -1 => 'n/a', 0 => 'Off', 1 => 'On' }, + }, + 29 => { + Name => 'SelfTimer2', + RawConv => '$val >= 0 ? $val : undef', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 33 => { #PH (A570IS) + Name => 'FlashOutput', + RawConv => '($$self{Model}=~/(PowerShot|IXUS|IXY)/ or $val) ? $val : undef', + Notes => q{ + used only for PowerShot models, this has a maximum value of 500 for models + like the A570IS + }, + }, +); + +# Canon panorama information (MakerNotes tag 0x05) +%Image::ExifTool::Canon::Panorama = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + # 0 - values: always 1 + # 1 - values: 0,256,512(3 sequential L->R images); 0,-256(2 R->L images) + 2 => 'PanoramaFrameNumber', #(some models this is always 0) + # 3 - values: 160(SX10IS,A570IS); 871(S30) + # 4 - values: always 0 + 5 => { + Name => 'PanoramaDirection', + PrintConv => { + 0 => 'Left to Right', + 1 => 'Right to Left', + 2 => 'Bottom to Top', + 3 => 'Top to Bottom', + 4 => '2x2 Matrix (Clockwise)', + }, + }, +); + +# D30 color information (MakerNotes tag 0x0a) +%Image::ExifTool::Canon::UnknownD30 = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, +); + +#.............................................................................. +# common CameraInfo tag definitions +my %ciFNumber = ( + Name => 'FNumber', + Format => 'int8u', + Groups => { 2 => 'Image' }, + RawConv => '$val ? $val : undef', + ValueConv => 'exp(($val-8)/16*log(2))', + ValueConvInv => 'log($val)*16/log(2)+8', + PrintConv => 'sprintf("%.2g",$val)', + PrintConvInv => '$val', +); +my %ciExposureTime = ( + Name => 'ExposureTime', + Format => 'int8u', + Groups => { 2 => 'Image' }, + RawConv => '$val ? $val : undef', + ValueConv => 'exp(4*log(2)*(1-Image::ExifTool::Canon::CanonEv($val-24)))', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv(1-log($val)/(4*log(2)))+24', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', +); +my %ciISO = ( + Name => 'ISO', + Format => 'int8u', + Groups => { 2 => 'Image' }, + ValueConv => '100*exp(($val/8-9)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+9)*8', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', +); +my %ciCameraTemperature = ( + Name => 'CameraTemperature', + Format => 'int8u', + ValueConv => '$val - 128', + ValueConvInv => '$val + 128', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', +); +my %ciMacroMagnification = ( + Name => 'MacroMagnification', + Notes => 'currently decoded only for the MP-E 65mm f/2.8 1-5x Macro Photo', + Condition => '$$self{LensType} and $$self{LensType} == 124', + # 75=1x, 44=5x, log relationship + ValueConv => 'exp((75-$val) * log(2) * 3 / 40)', + ValueConvInv => '$val > 0 ? 75 - log($val) / log(2) * 40 / 3 : undef', + PrintConv => 'sprintf("%.1fx",$val)', + PrintConvInv => '$val=~s/\s*x//; $val', +); +my %ciFocalLength = ( + Name => 'FocalLength', + Format => 'int16uRev', # (just to make things confusing, the focal lengths are big-endian) + # ignore if zero + RawConv => '$val ? $val : undef', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', +); +my %ciMinFocal = ( + Name => 'MinFocalLength', + Format => 'int16uRev', # byte order is big-endian + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', +); +my %ciMaxFocal = ( + Name => 'MaxFocalLength', + Format => 'int16uRev', # byte order is big-endian + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', +); + +#.............................................................................. +# Camera information for 1D and 1DS (MakerNotes tag 0x0d) +# (ref 15 unless otherwise noted) +%Image::ExifTool::Canon::CameraInfo1D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, # these tags are not reliable since they change with firmware version + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + Information in the "CameraInfo" records is tricky to decode because the + encodings are very different than in other Canon records (even sometimes + switching endianness between values within a single camera), plus there is + considerable variation in format from model to model. The first table below + lists CameraInfo tags for the 1D and 1DS. + }, + 0x04 => { %ciExposureTime }, #9 + 0x0a => { + Name => 'FocalLength', + Format => 'int16u', + # ignore if zero + RawConv => '$val ? $val : undef', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', + }, + 0x0d => { #9 + Name => 'LensType', + Format => 'int16uRev', # value is little-endian + SeparateTable => 1, + RawConv => '$val ? $val : undef', # don't use if value is zero + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x0e => { + Name => 'MinFocalLength', + Format => 'int16u', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', + }, + 0x10 => { + Name => 'MaxFocalLength', + Format => 'int16u', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', + }, + 0x41 => { + Name => 'SharpnessFrequency', # PatternSharpness? + Condition => '$$self{Model} =~ /\b1D$/', + Notes => '1D only', + PrintConvColumns => 2, + PrintConv => { + 0 => 'n/a', + 1 => 'Lowest', + 2 => 'Low', + 3 => 'Standard', + 4 => 'High', + 5 => 'Highest', + }, + }, + 0x42 => { + Name => 'Sharpness', + Format => 'int8s', + Condition => '$$self{Model} =~ /\b1D$/', + Notes => '1D only', + }, + 0x44 => { + Name => 'WhiteBalance', + Condition => '$$self{Model} =~ /\b1D$/', + Notes => '1D only', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x47 => { + Name => 'SharpnessFrequency', # PatternSharpness? + Condition => '$$self{Model} =~ /\b1DS$/', + Notes => '1DS only', + PrintConvColumns => 2, + PrintConv => { + 0 => 'n/a', + 1 => 'Lowest', + 2 => 'Low', + 3 => 'Standard', + 4 => 'High', + 5 => 'Highest', + }, + }, + 0x48 => [ + { + Name => 'ColorTemperature', + Format => 'int16u', + Condition => '$$self{Model} =~ /\b1D$/', + Notes => '1D only', + }, + { + Name => 'Sharpness', + Format => 'int8s', + Condition => '$$self{Model} =~ /\b1DS$/', + Notes => '1DS only', + }, + ], + 0x4a => { + Name => 'WhiteBalance', + Condition => '$$self{Model} =~ /\b1DS$/', + Notes => '1DS only', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x4b => { + Name => 'PictureStyle', + Condition => '$$self{Model} =~ /\b1D$/', + Notes => "1D only, called 'Color Matrix' in owner's manual", + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0x4e => { + Name => 'ColorTemperature', + Format => 'int16u', + Condition => '$$self{Model} =~ /\b1DS$/', + Notes => '1DS only', + }, + 0x51 => { + Name => 'PictureStyle', + Condition => '$$self{Model} =~ /\b1DS$/', + Notes => '1DS only', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, +); + +# Camera information for 1DmkII and 1DSmkII (MakerNotes tag 0x0d) +# (ref 15 unless otherwise noted) +%Image::ExifTool::Canon::CameraInfo1DmkII = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the 1DmkII and 1DSmkII.', + 0x04 => { %ciExposureTime }, #9 + 0x09 => { %ciFocalLength }, #9 + 0x0c => { #9 + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + RawConv => '$val ? $val : undef', # don't use if value is zero + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x11 => { %ciMinFocal }, #9 + 0x13 => { %ciMaxFocal }, #9 + 0x2d => { #9 + Name => 'FocalType', + PrintConv => { + 0 => 'Fixed', + 2 => 'Zoom', + }, + }, + 0x36 => { + Name => 'WhiteBalance', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x37 => { + Name => 'ColorTemperature', + Format => 'int16uRev', + }, + 0x39 => { + Name => 'CanonImageSize', + Format => 'int16u', + PrintConvColumns => 2, + PrintConv => \%canonImageSize, + }, + 0x66 => { + Name => 'JPEGQuality', + Notes => 'a number from 1 to 10', + }, + 0x6c => { #12 + Name => 'PictureStyle', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0x6e => { + Name => 'Saturation', + Format => 'int8s', + %Image::ExifTool::Exif::printParameter, + }, + 0x6f => { + Name => 'ColorTone', + Format => 'int8s', + %Image::ExifTool::Exif::printParameter, + }, + 0x72 => { + Name => 'Sharpness', + Format => 'int8s', + }, + 0x73 => { + Name => 'Contrast', + Format => 'int8s', + %Image::ExifTool::Exif::printParameter, + }, + 0x75 => { + Name => 'ISO', + Format => 'string[5]', + }, +); + +# Camera information for the 1DmkIIN (MakerNotes tag 0x0d) +# (ref 9 unless otherwise noted) +%Image::ExifTool::Canon::CameraInfo1DmkIIN = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the 1DmkIIN.', + 0x04 => { %ciExposureTime }, + 0x09 => { %ciFocalLength }, + 0x0c => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + RawConv => '$val ? $val : undef', # don't use if value is zero + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x11 => { %ciMinFocal }, + 0x13 => { %ciMaxFocal }, + 0x36 => { #15 + Name => 'WhiteBalance', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x37 => { #15 + Name => 'ColorTemperature', + Format => 'int16uRev', + }, + 0x73 => { #15 + Name => 'PictureStyle', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0x74 => { #15 + Name => 'Sharpness', + Format => 'int8s', + }, + 0x75 => { #15 + Name => 'Contrast', + Format => 'int8s', + %Image::ExifTool::Exif::printParameter, + }, + 0x76 => { #15 + Name => 'Saturation', + Format => 'int8s', + %Image::ExifTool::Exif::printParameter, + }, + 0x77 => { #15 + Name => 'ColorTone', + Format => 'int8s', + %Image::ExifTool::Exif::printParameter, + }, + 0x79 => { #15 + Name => 'ISO', + Format => 'string[5]', + }, +); + +# Canon camera information for 1DmkIII and 1DSmkIII (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo1DmkIII = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x2aa ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the 1DmkIII and 1DSmkIII.', + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, #9 + 0x06 => { %ciISO }, + 0x18 => { %ciCameraTemperature }, #36 + 0x1b => { %ciMacroMagnification }, #(NC) + 0x1d => { %ciFocalLength }, + 0x30 => { # <-- (follows pattern /\xbb\xbb(.{64})?\x01\x01\0\0.{4}/s for all models - Dave Coffin) + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x43 => { #21/24 + Name => 'FocusDistanceUpper', + # (it looks like the focus distances are also odd-byte big-endian) + %focusDistanceByteSwap, + }, + 0x45 => { #21/24 + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x5e => { #15 + Name => 'WhiteBalance', + Format => 'int16u', + PrintConv => \%canonWhiteBalance, + SeparateTable => 1, + }, + 0x62 => { #15 + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0x86 => { + Name => 'PictureStyle', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0x111 => { #15 + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x113 => { %ciMinFocal }, + 0x115 => { %ciMaxFocal }, + 0x136 => { #15 + Name => 'FirmwareVersion', + Format => 'string[6]', + }, + 0x172 => { + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x176 => { + Name => 'ShutterCount', + Notes => 'may be valid only for some 1DmkIII copies, even running the same firmware', + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x17e => { #(NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x2aa => { #48 + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo' }, + }, + 0x45a => { #29 + Name => 'TimeStamp1', + Condition => '$$self{Model} =~ /\b1D Mark III$/', + Format => 'int32u', + Groups => { 2 => 'Time' }, + # observed in 1DmkIII firmware 5.3.1 (pre-production), 1.0.3, 1.0.8 + Notes => 'only valid for some versions of the 1DmkIII firmware', + Shift => 'Time', + RawConv => '$val ? $val : undef', + ValueConv => 'ConvertUnixTime($val)', + ValueConvInv => 'GetUnixTime($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + 0x45e => { + Name => 'TimeStamp', + Format => 'int32u', + Groups => { 2 => 'Time' }, + # observed in 1DmkIII firmware 1.1.0, 1.1.3 and + # 1DSmkIII firmware 1.0.0, 1.0.4, 2.1.2, 2.7.1 + Notes => 'valid for the 1DSmkIII and some versions of the 1DmkIII firmware', + Shift => 'Time', + RawConv => '$val ? $val : undef', + ValueConv => 'ConvertUnixTime($val)', + ValueConvInv => 'GetUnixTime($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, +); + +# Canon camera information for 1DmkIV (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo1DmkIV = ( + %binaryDataAttrs, + FIRST_ENTRY => 0, + PRIORITY => 0, + DATAMEMBER => [ 0x00, 0x56, 0x153 ], + IS_SUBDIR => [ 0x368 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + CameraInfo tags for the EOS 1D Mark IV. Indices shown are for firmware + versions 1.0.x, but they may be different for other firmware versions. + }, + 0x00 => { + Name => 'FirmwareVersionLookAhead', + Hidden => 1, + # look ahead to check location of FirmwareVersion string + Format => 'undef[0x1fd]', + RawConv => q{ + my $t = substr($val, 0x1e8, 6); # 1 = firmware 4.2.1 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 1, return undef; + $t = substr($val, 0x1ed, 6); # 2 = firmware 1.0.4 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 2, return undef; + $self->Warn('Unrecognized CameraInfo1DmkIV firmware version'); + $$self{CanonFirm} = 0; + return undef; # not a real tag + }, + }, + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x07 => { + Name => 'HighlightTonePriority', + PrintConv => \%offOn, + }, + 0x08 => { + Name => 'MeasuredEV2', + Description => 'Measured EV 2', + RawConv => '$val ? $val : undef', + ValueConv => '$val / 8 - 6', + ValueConvInv => 'int(($val + 6) * 8 + 0.5)', + }, + 0x09 => { + Name => 'MeasuredEV3', + Description => 'Measured EV 3', + RawConv => '$val ? $val : undef', + ValueConv => '$val / 8 - 6', + ValueConvInv => 'int(($val + 6) * 8 + 0.5)', + }, + 0x15 => { + Name => 'FlashMeteringMode', + PrintConv => { + 0 => 'E-TTL', + 3 => 'TTL', + 4 => 'External Auto', + 5 => 'External Manual', + 6 => 'Off', + }, + }, + 0x19 => { %ciCameraTemperature }, + 0x1e => { %ciFocalLength }, + 0x35 => { + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x54 => { + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x56 => { + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + Hook => '$varSize += ($$self{CanonFirm} ? -1 : 0x10000) if $$self{CanonFirm} < 2', + }, + 0x78 => { + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x7c => { + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0x14f => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x151 => { %ciMinFocal }, + 0x153 => { %ciMaxFocal, + Hook => '$varSize -= 4 if $$self{CanonFirm} < 2', + }, + 0x1ed => { + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + }, + 0x22c => { #(NC) + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x238 => { #(NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x368 => { + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo' }, + }, +); + +# Camera information for 1D X (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo1DX = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + DATAMEMBER => [ 0x00, 0x1b, 0x8e, 0x1ab ], + IS_SUBDIR => [ 0x3f4 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + CameraInfo tags for the EOS 1D X. Indices shown are for firmware version + 1.0.2, but they may be different for other firmware versions. + }, + 0x00 => { + Name => 'FirmwareVersionLookAhead', + Hidden => 1, + # look ahead to check location of FirmwareVersion string + Format => 'undef[0x28b]', + RawConv => q{ + my $t = substr($val, 0x271, 6); # 1 = firmware 5.7.1 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 1, return undef; + $t = substr($val, 0x279, 6); # 2 = firmware 6.5.1 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 2, return undef; + $t = substr($val, 0x280, 6); # 3 = firmware 0.0.8/1.0.2/1.1.1 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 3, return undef; + $t = substr($val, 0x285, 6); # 4 = firmware 2.1.0 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 4, return undef; + $self->Warn('Unrecognized CameraInfo1DX firmware version'); + $$self{CanonFirm} = 0; + return undef; # not a real tag + }, + }, + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x1b => { %ciCameraTemperature, + Hook => '$varSize -= 3 if $$self{CanonFirm} < 3', + }, + 0x23 => { %ciFocalLength }, + 0x7d => { + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x8c => { + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x8e => { + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + Hook => '$varSize -= 4 if $$self{CanonFirm} < 3; $varSize += 5 if $$self{CanonFirm} == 4', + }, + 0xbc => { + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0xc0 => { + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xf4 => { + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0x1a7 => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x1a9 => { %ciMinFocal }, + 0x1ab => { %ciMaxFocal, + # add another offset of -8 for firmware 5.7.1, and a large offset + # to effectively abort processing for unknown firmware + Hook => '$varSize += ($$self{CanonFirm} ? -8 : 0x10000) if $$self{CanonFirm} < 2', + }, + 0x280 => { + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + }, + 0x2d0 => { # (doesn't seem to work for firmware 2.0.3 - PH) + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x2dc => { #(NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x3f4 => { + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo2' }, + }, +); + +# Camera information for 5D (MakerNotes tag 0x0d) +# (ref 12 unless otherwise noted) +%Image::ExifTool::Canon::CameraInfo5D = ( + %binaryDataAttrs, + FORMAT => 'int8s', + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 5D.', + 0x03 => { %ciFNumber }, #PH + 0x04 => { %ciExposureTime }, #9 + 0x06 => { %ciISO }, #PH + 0x0c => { #9 + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + RawConv => '$val ? $val : undef', # don't use if value is zero + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x17 => { %ciCameraTemperature }, #PH + 0x1b => { %ciMacroMagnification }, #PH + 0x27 => { #PH + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x28 => { %ciFocalLength }, #15 + 0x38 => { + Name => 'AFPointsInFocus5D', + Format => 'int16uRev', + PrintConvColumns => 2, + PrintConv => { 0 => '(none)', + BITMASK => { + 0 => 'Center', + 1 => 'Top', + 2 => 'Bottom', + 3 => 'Upper-left', + 4 => 'Upper-right', + 5 => 'Lower-left', + 6 => 'Lower-right', + 7 => 'Left', + 8 => 'Right', + 9 => 'AI Servo1', + 10 => 'AI Servo2', + 11 => 'AI Servo3', + 12 => 'AI Servo4', + 13 => 'AI Servo5', + 14 => 'AI Servo6', + }, + }, + }, + 0x54 => { #15 + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x58 => { #15 + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0x6c => { + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0x93 => { %ciMinFocal }, #15 + 0x95 => { %ciMaxFocal }, #15 + 0x97 => { #15 + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0xa4 => { #PH + Name => 'FirmwareRevision', + Format => 'string[8]', + }, + 0xac => { #PH + Name => 'ShortOwnerName', + Format => 'string[16]', + }, + 0xcc => { #PH (NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + }, + 0xd0 => { + Name => 'FileIndex', + Format => 'int16u', + Groups => { 2 => 'Image' }, + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0xe8 => 'ContrastStandard', + 0xe9 => 'ContrastPortrait', + 0xea => 'ContrastLandscape', + 0xeb => 'ContrastNeutral', + 0xec => 'ContrastFaithful', + 0xed => 'ContrastMonochrome', + 0xee => 'ContrastUserDef1', + 0xef => 'ContrastUserDef2', + 0xf0 => 'ContrastUserDef3', + # sharpness values are 0-7 + 0xf1 => 'SharpnessStandard', + 0xf2 => 'SharpnessPortrait', + 0xf3 => 'SharpnessLandscape', + 0xf4 => 'SharpnessNeutral', + 0xf5 => 'SharpnessFaithful', + 0xf6 => 'SharpnessMonochrome', + 0xf7 => 'SharpnessUserDef1', + 0xf8 => 'SharpnessUserDef2', + 0xf9 => 'SharpnessUserDef3', + 0xfa => 'SaturationStandard', + 0xfb => 'SaturationPortrait', + 0xfc => 'SaturationLandscape', + 0xfd => 'SaturationNeutral', + 0xfe => 'SaturationFaithful', + 0xff => { + Name => 'FilterEffectMonochrome', + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0x100 => 'SaturationUserDef1', + 0x101 => 'SaturationUserDef2', + 0x102 => 'SaturationUserDef3', + 0x103 => 'ColorToneStandard', + 0x104 => 'ColorTonePortrait', + 0x105 => 'ColorToneLandscape', + 0x106 => 'ColorToneNeutral', + 0x107 => 'ColorToneFaithful', + 0x108 => { + Name => 'ToningEffectMonochrome', + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0x109 => 'ColorToneUserDef1', + 0x10a => 'ColorToneUserDef2', + 0x10b => 'ColorToneUserDef3', + 0x10c => { + Name => 'UserDef1PictureStyle', + Format => 'int16u', + PrintHex => 1, # (only needed for one tag) + SeparateTable => 'UserDefStyle', + PrintConv => \%userDefStyles, + }, + 0x10e => { + Name => 'UserDef2PictureStyle', + Format => 'int16u', + SeparateTable => 'UserDefStyle', + PrintConv => \%userDefStyles, + }, + 0x110 => { + Name => 'UserDef3PictureStyle', + Format => 'int16u', + SeparateTable => 'UserDefStyle', + PrintConv => \%userDefStyles, + }, + 0x11c => { + Name => 'TimeStamp', + Format => 'int32u', + Groups => { 2 => 'Time' }, + Shift => 'Time', + RawConv => '$val ? $val : undef', + ValueConv => 'ConvertUnixTime($val)', + ValueConvInv => 'GetUnixTime($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, +); + +# Camera information for 5D Mark II (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo5DmkII = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + DATAMEMBER => [ 0x00, 0xea ], + IS_SUBDIR => [ 0x2f7 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + CameraInfo tags for the EOS 5D Mark II. Indices shown are for firmware + version 1.0.6, but they may be different for other firmware versions. + }, + 0x00 => { + Name => 'FirmwareVersionLookAhead', + Hidden => 1, + # look ahead to check location of FirmwareVersion string + Format => 'undef[0x184]', + RawConv => q{ + my $t = substr($val, 0x15a, 6); # 1 = firmware 3.4.6/3.6.1 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 1, return undef; + $t = substr($val, 0x17e, 6); # 2 = firmware 4.1.1/1.0.6 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 2, return undef; + $self->Warn('Unrecognized CameraInfo5DmkII firmware version'); + $$self{CanonFirm} = 0; + return undef; # not a real tag + }, + }, + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x07 => { + Name => 'HighlightTonePriority', + PrintConv => \%offOn, + }, + 0x1b => { %ciMacroMagnification }, #PH + 0x15 => { #PH (580 EX II) + Name => 'FlashMeteringMode', + PrintConv => { + 0 => 'E-TTL', + 3 => 'TTL', + 4 => 'External Auto', + 5 => 'External Manual', + 6 => 'Off', + }, + }, + 0x19 => { %ciCameraTemperature }, #36 + # 0x1b, 0x1c, 0x1d - same as FileInfo 0x10 - PH + 0x1e => { %ciFocalLength }, + 0x31 => { + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x50 => { + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x52 => { + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x6f => { + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x73 => { + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xa7 => { + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0xbd => { + Name => 'HighISONoiseReduction', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Off', + }, + }, + 0xbf => { + Name => 'AutoLightingOptimizer', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Off', + }, + }, + 0xe6 => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0xe8 => { %ciMinFocal }, + 0xea => { %ciMaxFocal, + # offset changes after this for different firmware versions + Hook => '$varSize += ($$self{CanonFirm} ? -36 : 0x10000) if $$self{CanonFirm} < 2', + }, + 0x17e => { + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, # not writable for logic reasons + # some firmwares have a null instead of a space after the version number + RawConv => '$val=~/^\d+\.\d+\.\d+\s*$/ ? $val : undef', + }, + 0x1bb => { + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x1c7 => { #(NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x2f7 => { #48 + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo' }, + }, +); + +# Camera information for 5D Mark III (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo5DmkIII = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + DATAMEMBER => [ 0x00, 0x1b, 0x23, 0x8e, 0x157 ], + IS_SUBDIR => [ 0x3b0 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + CameraInfo tags for the EOS 5D Mark III. Indices shown are for firmware + versions 1.0.x, but they may be different for other firmware versions. + }, + 0x00 => { + Name => 'FirmwareVersionLookAhead', + Hidden => 1, + # look ahead to check location of FirmwareVersion string + Format => 'undef[0x24d]', + RawConv => q{ + my $t = substr($val, 0x22c, 6); # 1 = firmware 4.5.4/4.5.6 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 1, return undef; + $t = substr($val, 0x22d, 6); # 2 = firmware 5.2.2/5.3.1/5.4.2 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 2, return undef; + $t = substr($val, 0x23c, 6); # 3 = firmware 1.0.3/1.0.7 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 3, return undef; + $t = substr($val, 0x242, 6); # 4 = firmware 1.2.1 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 4, return undef; + $t = substr($val, 0x247, 6); # 5 = firmware 1.3.5 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 5, return undef; + $self->Warn('Unrecognized CameraInfo5DmkIII firmware version'); + $$self{CanonFirm} = 0; + return undef; # not a real tag + }, + }, + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x1b => { %ciCameraTemperature, + # decrement $varSize for missing byte after this tag with firmware 5.x and earlier + # (and add large offset to effectively abort processing if unknown firmware) + Hook => '$varSize += ($$self{CanonFirm} ? -1 : 0x10000) if $$self{CanonFirm} < 3', + }, + 0x23 => { %ciFocalLength, + Hook => q{ + $varSize -= 3 if $$self{CanonFirm} == 1; + $varSize -= 2 if $$self{CanonFirm} == 2; + $varSize += 6 if $$self{CanonFirm} >= 4; + }, + }, + 0x7d => { + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x8c => { + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x8e => { + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + Hook => q{ + $varSize -= 4 if $$self{CanonFirm} < 3; + $varSize += 5 if $$self{CanonFirm} > 4; + }, + }, + 0xbc => { + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0xc0 => { + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xf4 => { + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0x153 => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x155 => { %ciMinFocal }, + 0x157 => { %ciMaxFocal, + Hook => '$varSize -= 8 if $$self{CanonFirm} < 3', + }, + 0x164 => { + Name => 'LensSerialNumber', + Format => 'undef[5]', + Priority => 0, + ValueConv => 'unpack("H*",$val)', + ValueConvInv => 'length($val) < 10 and $val = 0 x (10-length($val)) . $val; pack("H*",$val)', + }, + 0x23c => { + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + }, + # the 5DmkIII has "User setting1" and "User setting2" file naming options: + # - with "User setting1" 4 characters are selectable + # - with "User setting2", 3 characters are selectable, and the 4th character + # - in the file name corresponds to the image size: + # L=large, M=medium, S=small1, T=small2, U=small3, _=movie + # - as shipped, the first 4 characters of the file name are unique to the camera + 0x28c => { # used for file names like IMG_xxxx.JPG + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x290 => { # used for file names like 2F0Axxxx.JPG and 6T3Cxxxx.JPG + Name => 'FileIndex2', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x298 => { #(NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x29c => { #(NC) + Name => 'DirectoryIndex2', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x3b0 => { + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo2' }, + }, +); + +# Camera information for 6D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo6D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x3c6 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 6D.', + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x1b => { %ciCameraTemperature }, + 0x23 => { %ciFocalLength }, + 0x83 => { # (5DmkIII + 6) + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x92 => { # (5DmkIII + 6) + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x94 => { # (5DmkIII + 6) + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0xc2 => { # (5DmkIII + 6) + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0xc6 => { # (5DmkIII + 6) + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xfa => { # (5DmkIII + 6) + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0x161 => { # (5DmkIII + 0x0e) + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x163 => { %ciMinFocal }, # (5DmkIII + 0x0e) + 0x165 => { %ciMaxFocal }, # (5DmkIII + 0x0e) + 0x256 => { # (5DmkIII + 0x1a) + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + }, + 0x2aa => { # (5DmkIII + 0x16 or 0x1e) + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x2b6 => { #(NC) (5DmkIII + 0x16 or 0x1e) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x3c6 => { # (5DmkIII + 0x16) + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo2' }, + }, +); + +# Camera information for 7D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo7D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + DATAMEMBER => [ 0x00, 0x1e ], + IS_SUBDIR => [ 0x327 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + CameraInfo tags for the EOS 7D. Indices shown are for firmware versions + 1.0.x, but they may be different for other firmware versions. + }, + 0x00 => { + Name => 'FirmwareVersionLookAhead', + Hidden => 1, + # look ahead to check location of FirmwareVersion string + Format => 'undef[0x1b2]', + RawConv => q{ + my $t = substr($val, 0x1a8, 6); # 1 = firmware 3.7.5 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 1, return undef; + $t = substr($val, 0x1ac, 6); # 2 = firmware 1.0.7/1.0.8/1.1.0/1.2.1/1.2.2 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 2, return undef; + $self->Warn('Unrecognized CameraInfo7D firmware version'); + $$self{CanonFirm} = 0; + return undef; # not a real tag + }, + }, + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x07 => { + Name => 'HighlightTonePriority', + PrintConv => \%offOn, + }, + 0x08 => { #37 + Name => 'MeasuredEV2', + Description => 'Measured EV 2', + RawConv => '$val ? $val : undef', + ValueConv => '$val / 8 - 6', + ValueConvInv => 'int(($val + 6) * 8 + 0.5)', + }, + 0x09 => { #37 + Name => 'MeasuredEV', + Description => 'Measured EV', + RawConv => '$val ? $val : undef', + ValueConv => '$val / 8 - 6', + ValueConvInv => 'int(($val + 6) * 8 + 0.5)', + }, + 0x15 => { #PH (580 EX II) + Name => 'FlashMeteringMode', + PrintConv => { + 0 => 'E-TTL', + 3 => 'TTL', + 4 => 'External Auto', + 5 => 'External Manual', + 6 => 'Off', + }, + }, + 0x19 => { %ciCameraTemperature }, + 0x1e => { %ciFocalLength, + Hook => '$varSize += ($$self{CanonFirm} ? -4 : 0x10000) if $$self{CanonFirm} < 2', + }, + 0x35 => { + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x54 => { + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x56 => { + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x77 => { + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x7b => { + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xaf => { + Name => 'CameraPictureStyle', + PrintHex => 1, + PrintConv => { + 0x81 => 'Standard', + 0x82 => 'Portrait', + 0x83 => 'Landscape', + 0x84 => 'Neutral', + 0x85 => 'Faithful', + 0x86 => 'Monochrome', + 0x21 => 'User Defined 1', + 0x22 => 'User Defined 2', + 0x23 => 'User Defined 3', + }, + }, + 0xc9 => { + Name => 'HighISONoiseReduction', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Off', + }, + }, + 0x112 => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x114 => { %ciMinFocal }, + 0x116 => { %ciMaxFocal }, + 0x1ac => { + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, # not writable for logic reasons + # some firmwares have a null instead of a space after the version number + RawConv => '$val=~/^\d+\.\d+\.\d+\s*$/ ? $val : undef', + }, + 0x1eb => { + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x1f7 => { #(NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x327 => { #48 + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo' }, + }, +); + +# Canon camera information for 40D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo40D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x25b ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 40D.', + 0x03 => { %ciFNumber }, #PH + 0x04 => { %ciExposureTime }, #PH + 0x06 => { %ciISO }, #PH + 0x15 => { #PH (580 EX II) + Name => 'FlashMeteringMode', + PrintConv => { + 0 => 'E-TTL', + 3 => 'TTL', + 4 => 'External Auto', + 5 => 'External Manual', + 6 => 'Off', + }, + }, + 0x18 => { %ciCameraTemperature }, #36 + 0x1b => { %ciMacroMagnification }, #PH + 0x1d => { %ciFocalLength }, #PH + 0x30 => { #20 + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x43 => { #21/24 + Name => 'FocusDistanceUpper', + # this is very odd (little-endian number on odd boundary), + # but it does seem to work better with my sample images - PH + %focusDistanceByteSwap, + }, + 0x45 => { #21/24 + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x6f => { #15 + Name => 'WhiteBalance', + Format => 'int16u', + PrintConv => \%canonWhiteBalance, + SeparateTable => 1, + }, + 0x73 => { #15 + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xd6 => { #15 + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0xd8 => { %ciMinFocal }, #15 + 0xda => { %ciMaxFocal }, #15 + 0xff => { #15 + Name => 'FirmwareVersion', + Format => 'string[6]', + }, + 0x133 => { #27 + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + Notes => 'combined with DirectoryIndex to give the Composite FileNumber tag', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x13f => { #27 + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', # yes, minus (opposite to FileIndex) + ValueConvInv => '$val + 1', + }, + 0x25b => { + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo' }, + }, + 0x92b => { #33 + Name => 'LensModel', + Format => 'string[64]', + }, +); + +# Canon camera information for 50D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo50D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + DATAMEMBER => [ 0x00, 0xee ], + IS_SUBDIR => [ 0x2d7 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + CameraInfo tags for the EOS 50D. Indices shown are for firmware versions + 1.0.x, but they may be different for other firmware versions. + }, + 0x00 => { + Name => 'FirmwareVersionLookAhead', + Hidden => 1, + # look ahead to check location of FirmwareVersion string + Format => 'undef[0x164]', + RawConv => q{ + my $t = substr($val, 0x15a, 6); # 1 = firmware 2.6.1 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 1, return undef; + $t = substr($val, 0x15e, 6); # 2 = firmware 2.9.1/3.1.1/1.0.2/1.0.3 + $t =~ /^\d+\.\d+\.\d+/ and $$self{CanonFirm} = 2, return undef; + $self->Warn('Unrecognized CameraInfo50D firmware version'); + $$self{CanonFirm} = 0; + return undef; # not a real tag + }, + }, + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x07 => { + Name => 'HighlightTonePriority', + PrintConv => \%offOn, + }, + 0x15 => { #PH (580 EX II) + Name => 'FlashMeteringMode', + PrintConv => { + 0 => 'E-TTL', + 3 => 'TTL', + 4 => 'External Auto', + 5 => 'External Manual', + 6 => 'Off', + }, + }, + 0x19 => { %ciCameraTemperature }, #36 + 0x1e => { %ciFocalLength }, + 0x31 => { + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x50 => { #33 + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x52 => { #33 + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x6f => { + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x73 => { #33 + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xa7 => { + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0xbd => { + Name => 'HighISONoiseReduction', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Off', + }, + }, + 0xbf => { + Name => 'AutoLightingOptimizer', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Off', + }, + }, + 0xea => { #33 + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0xec => { %ciMinFocal }, + 0xee => { %ciMaxFocal, + Hook => '$varSize += ($$self{CanonFirm} ? -4 : 0x10000) if $$self{CanonFirm} < 2', + }, + 0x15e => { #33 + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + }, + 0x19b => { + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x1a7 => { #(NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x2d7 => { + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo' }, + }, +); + +# Canon camera information for 60D/1200D (MakerNotes tag 0x0d) (ref PH) +# NOTE: Can probably borrow more 50D tags here, possibly with an offset +%Image::ExifTool::Canon::CameraInfo60D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x2f9, 0x321 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 60D and 1200D.', + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x19 => { %ciCameraTemperature }, + 0x1e => { %ciFocalLength }, + 0x36 => { + Name => 'CameraOrientation', + Condition => '$$self{Model} =~ /EOS 60D$/', #(NC) + Notes => '60D only', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x3a => { #IB + Name => 'CameraOrientation', + Condition => '$$self{Model} =~ /\b(1200D|REBEL T5|Kiss X70)\b/', + Notes => '1200D only', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x55 => { + Name => 'FocusDistanceUpper', + Condition => '$$self{Model} =~ /EOS 60D$/', + Notes => '60D only', + %focusDistanceByteSwap, + }, + 0x57 => { + Name => 'FocusDistanceLower', + Condition => '$$self{Model} =~ /EOS 60D$/', + Notes => '60D only', + %focusDistanceByteSwap, + }, + 0x7d => { + Name => 'ColorTemperature', + Condition => '$$self{Model} =~ /EOS 60D$/', + Notes => '60D only', + Format => 'int16u', + }, + 0xe8 => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0xea => { %ciMinFocal }, + 0xec => { %ciMaxFocal }, + 0x199 => { # (at this location for 60D firmware 2.8.1/1.0.5, and 1200D 3.3.1/1.0.0) + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + }, + 0x1d9 => { + Name => 'FileIndex', + Condition => '$$self{Model} =~ /EOS 60D$/', + Notes => '60D only', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x1e5 => { #(NC) + Name => 'DirectoryIndex', + Condition => '$$self{Model} =~ /EOS 60D$/', + Notes => '60D only', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x2f9 => { + Name => 'PictureStyleInfo', + Condition => '$$self{Model} =~ /\b(1200D|REBEL T5|Kiss X70)\b/', + Notes => '1200D', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo2' }, + }, + 0x321 => { + Name => 'PictureStyleInfo', + Condition => '$$self{Model} =~ /EOS 60D$/', + Notes => '60D', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo2' }, + }, +); + +# Canon camera information for 70D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo70D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x3cf ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 70D.', + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x1b => { %ciCameraTemperature }, + 0x23 => { %ciFocalLength }, + # 0x36 - focal length again? + 0x84 => { + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x93 => { + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x95 => { + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0xc7 => { + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0x166 => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x168 => { %ciMinFocal }, + 0x16a => { %ciMaxFocal }, + 0x25e => { # (at this location for firmware 6.1.2, 1.0.4 and 1.1.1) + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + }, + 0x2b3 => { + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x2bf => { #(NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x3cf => { #48 + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo2' }, + }, +); + +# Canon camera information for 80D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo80D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 80D.', + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x1b => { %ciCameraTemperature }, + 0x23 => { %ciFocalLength }, + 0x96 => { + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0xa5 => { + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0xa7 => { + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x13a => { + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0x189 => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x18b => { %ciMinFocal }, + 0x18d => { %ciMaxFocal }, + 0x45a => { # (at this location for firmware 1.0.1) + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + }, + 0x4ae => { + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x4ba => { #(NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, +); + +# Canon camera information for 450D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo450D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x263 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 450D.', + 0x03 => { %ciFNumber }, #PH + 0x04 => { %ciExposureTime }, #PH + 0x06 => { %ciISO }, #PH + 0x15 => { #PH (580 EX II) + Name => 'FlashMeteringMode', + PrintConv => { + 0 => 'E-TTL', + 3 => 'TTL', + 4 => 'External Auto', + 5 => 'External Manual', + 6 => 'Off', + }, + }, + 0x18 => { %ciCameraTemperature }, #36 + 0x1b => { %ciMacroMagnification }, #PH + 0x1d => { %ciFocalLength }, #PH + 0x30 => { #20 + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x43 => { #20 + Name => 'FocusDistanceUpper', + # this is very odd (little-endian number on odd boundary), + # but it does seem to work better with my sample images - PH + %focusDistanceByteSwap, + }, + 0x45 => { #20 + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x6f => { #PH + Name => 'WhiteBalance', + Format => 'int16u', + PrintConv => \%canonWhiteBalance, + SeparateTable => 1, + }, + 0x73 => { #PH + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xde => { #33 + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x107 => { #PH + Name => 'FirmwareVersion', + Format => 'string[6]', + }, + 0x10f => { #20 + Name => 'OwnerName', + Format => 'string[32]', + }, + 0x133 => { #20 + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + }, + 0x13f => { #20 + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x263 => { #PH + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo' }, + }, + 0x933 => { #33 + Name => 'LensModel', + Format => 'string[64]', + }, +); + +# Canon camera information for 500D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo500D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x30b ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 500D.', + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x07 => { + Name => 'HighlightTonePriority', + PrintConv => \%offOn, + }, + 0x15 => { #PH (580 EX II) + Name => 'FlashMeteringMode', + PrintConv => { + 0 => 'E-TTL', + 3 => 'TTL', + 4 => 'External Auto', + 5 => 'External Manual', + 6 => 'Off', + }, + }, + 0x19 => { %ciCameraTemperature }, + 0x1e => { %ciFocalLength }, + 0x31 => { + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x50 => { + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x52 => { + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x73 => { # (50D + 4) + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x77 => { # (50D + 4) + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xab => { # (50D + 4) + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0xbc => { + Name => 'HighISONoiseReduction', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Off', + }, + }, + 0xbe => { + Name => 'AutoLightingOptimizer', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Off', + }, + }, + 0xf6 => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0xf8 => { %ciMinFocal }, + 0xfa => { %ciMaxFocal }, + 0x190 => { + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + RawConv => '$val=~/^\d+\.\d+\.\d+\s*$/ ? $val : undef', + }, + 0x1d3 => { + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x1df => { #(NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x30b => { + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo' }, + }, +); + +# Canon camera information for 550D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo550D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x31c ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 550D.', + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x07 => { #(NC) + Name => 'HighlightTonePriority', + PrintConv => \%offOn, + }, + 0x15 => { #(NC) + Name => 'FlashMeteringMode', + PrintConv => { + 0 => 'E-TTL', + 3 => 'TTL', + 4 => 'External Auto', + 5 => 'External Manual', + 6 => 'Off', + }, + }, + 0x19 => { %ciCameraTemperature }, # (500D + 0) + 0x1e => { %ciFocalLength }, # (500D + 0) + 0x35 => { # (500D + 4) + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x54 => { # (500D + 4) + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x56 => { # (500D + 4) + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x78 => { # (500D + 5) (NC) + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x7c => { # (500D + 5) + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xb0 => { # (500D + 5) + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0xff => { # (500D + 9) + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x101 => { %ciMinFocal }, # (500D + 9) + 0x103 => { %ciMaxFocal }, # (500D + 9) + 0x1a4 => { # (500D + 0x11) + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + RawConv => '$val=~/^\d+\.\d+\.\d+\s*$/ ? $val : undef', + }, + 0x1e4 => { # (500D + 0x11) + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x1f0 => { # (500D + 0x11) (NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x31c => { #48 + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo' }, + }, +); + +# Canon camera information for 600D and 1100D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo600D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x2fb ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 600D and 1100D.', + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x07 => { #(NC) + Name => 'HighlightTonePriority', + PrintConv => \%offOn, + }, + 0x15 => { #(NC) + Name => 'FlashMeteringMode', + PrintConv => { + 0 => 'E-TTL', + 3 => 'TTL', + 4 => 'External Auto', + 5 => 'External Manual', + 6 => 'Off', + }, + }, + 0x19 => { %ciCameraTemperature }, # (60D + 0) + 0x1e => { %ciFocalLength }, # (60D + 0) + 0x38 => { # (60D + 2) + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x57 => { # (60D + 2, 550D + 3) + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x59 => { # (60D + 2, 550D + 3) + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x7b => { # (550D + 3) + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x7f => { # (60D + 2, 550D + 3) + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xb3 => { # (550D + 3) + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0xea => { # (60D + 2, 550D + 3) + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0xec => { %ciMinFocal }, # (60D + 2) + 0xee => { %ciMaxFocal }, # (60D + 2) + 0x19b => { # (60D + 2) + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + RawConv => '$val=~/^\d+\.\d+\.\d+\s*$/ ? $val : undef', + }, + 0x1db => { # (60D + 2) (NC) + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x1e7 => { # (60D + 2) (NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x2fb => { + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo2' }, + }, +); + +# Canon camera information for 650D/700D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo650D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x390 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 650D and 700D.', + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x1b => { %ciCameraTemperature }, # (1DX/5DmkIII + 0) + 0x23 => { %ciFocalLength }, # (1DX/5DmkIII + 3) + # 0x35 - seems to be the same as 0x54 + 0x7d => { # (1DX/5DmkIII + 3) + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x8c => { # (1DX + 3) + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x8e => { # (1DX + 3) + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0xbc => { # (1DX + 7) + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0xc0 => { # (1DX + 7) + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xf4 => { # (1DX + 7) + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0x127 => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x129 => { %ciMinFocal }, + 0x12b => { %ciMaxFocal }, + 0x21b => { # (650D version 1.0.1) + Name => 'FirmwareVersion', + Condition => '$$self{Model} =~ /(650D|REBEL T4i|Kiss X6i)\b/', + Notes => '650D', + Format => 'string[6]', + Writable => 0, + RawConv => '$val=~/^\d+\.\d+\.\d+\s*$/ ? $val : undef', + }, + 0x220 => { # (700D version 1.1.1/2.1.1) + Name => 'FirmwareVersion', + Condition => '$$self{Model} =~ /(700D|REBEL T5i|Kiss X7i)\b/', + Notes => '700D', + Format => 'string[6]', + Writable => 0, + RawConv => '$val=~/^\d+\.\d+\.\d+\s*$/ ? $val : undef', + }, + 0x270 => { #(NC) + Name => 'FileIndex', + Condition => '$$self{Model} =~ /(650D|REBEL T4i|Kiss X6i)\b/', + Notes => '650D', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x274 => { #(NC) + Name => 'FileIndex', + Condition => '$$self{Model} =~ /(700D|REBEL T5i|Kiss X7i)\b/', + Notes => '700D', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x27c => { #(NC) + Name => 'DirectoryIndex', + Condition => '$$self{Model} =~ /(650D|REBEL T4i|Kiss X6i)\b/', + Notes => '650D', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x280 => { #(NC) + Name => 'DirectoryIndex', + Condition => '$$self{Model} =~ /(700D|REBEL T5i|Kiss X7i)\b/', + Notes => '700D', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val - 1', + ValueConvInv => '$val + 1', + }, + 0x390 => { + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo2' }, + }, +); + +# Canon camera information for 750D/760D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo750D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 750D and 760D.', + 0x03 => { %ciFNumber }, + 0x04 => { %ciExposureTime }, + 0x06 => { %ciISO }, + 0x1b => { %ciCameraTemperature }, # (700D + 0) + 0x23 => { %ciFocalLength }, # (700D + 0) + 0x96 => { #IB (700D + 0x19) + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0xa5 => { # (700D + 0x19) + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0xa7 => { # (700D + 0x19) + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x131 => { # (700D + 0x19) + Name => 'WhiteBalance', + Format => 'int16u', + SeparateTable => 1, + PrintConv => \%canonWhiteBalance, + }, + 0x135 => { + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0x169 => { + Name => 'PictureStyle', + Format => 'int8u', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 0x184 => { + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0x186 => { %ciMinFocal }, + 0x188 => { %ciMaxFocal }, + 0x43d => { # (750D/760D firmware 6.7.2) + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + RawConv => '$val=~/^\d+\.\d+\.\d+\s*$/ ? $val : undef', + }, + 0x449 => { # (750D/760D firmware 1.0.0) + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + RawConv => '$val=~/^\d+\.\d+\.\d+\s*$/ ? $val : undef', + }, +); + +# Canon camera information for 1000D (MakerNotes tag 0x0d) (ref PH) +%Image::ExifTool::Canon::CameraInfo1000D = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + PRIORITY => 0, + IS_SUBDIR => [ 0x267 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'CameraInfo tags for the EOS 1000D.', + 0x03 => { %ciFNumber }, #PH + 0x04 => { %ciExposureTime }, #PH + 0x06 => { %ciISO }, #PH + 0x15 => { #PH (580 EX II) + Name => 'FlashMeteringMode', + PrintConv => { + 0 => 'E-TTL', + 3 => 'TTL', + 4 => 'External Auto', + 5 => 'External Manual', + 6 => 'Off', + }, + }, + 0x18 => { %ciCameraTemperature }, #36 + 0x1b => { %ciMacroMagnification }, #PH (NC) + 0x1d => { %ciFocalLength }, #PH + 0x30 => { #20 + Name => 'CameraOrientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + 0x43 => { #20 + Name => 'FocusDistanceUpper', + %focusDistanceByteSwap, + }, + 0x45 => { #20 + Name => 'FocusDistanceLower', + %focusDistanceByteSwap, + }, + 0x6f => { #PH + Name => 'WhiteBalance', + Format => 'int16u', + PrintConv => \%canonWhiteBalance, + SeparateTable => 1, + }, + 0x73 => { #PH + Name => 'ColorTemperature', + Format => 'int16u', + }, + 0xe2 => { #PH + Name => 'LensType', + Format => 'int16uRev', # value is big-endian + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%canonLensTypes, + PrintInt => 1, + }, + 0xe4 => { %ciMinFocal }, #PH + 0xe6 => { %ciMaxFocal }, #PH + 0x10b => { #PH + Name => 'FirmwareVersion', + Format => 'string[6]', + }, + 0x137 => { #PH (NC) + Name => 'DirectoryIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + }, + 0x143 => { #PH + Name => 'FileIndex', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x267 => { #PH + Name => 'PictureStyleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::PSInfo' }, + }, + 0x937 => { #PH + Name => 'LensModel', + Format => 'string[64]', + }, +); + +# Canon camera information for PowerShot models (MakerNotes tag 0x0d) - PH +%Image::ExifTool::Canon::CameraInfoPowerShot = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + CameraInfo tags for PowerShot models such as the A450, A460, A550, A560, + A570, A630, A640, A650, A710, A720, G7, G9, S5, SD40, SD750, SD800, SD850, + SD870, SD900, SD950, SD1000, SX100 and TX1. + }, + 0x00 => { + Name => 'ISO', + Groups => { 2 => 'Image' }, + ValueConv => '100*exp((($val-411)/96)*log(2))', + ValueConvInv => 'log($val/100)/log(2)*96+411', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x05 => { + Name => 'FNumber', + Groups => { 2 => 'Image' }, + ValueConv => 'exp($val/192*log(2))', + ValueConvInv => 'log($val)*192/log(2)', + PrintConv => 'sprintf("%.2g",$val)', + PrintConvInv => '$val', + }, + 0x06 => { + Name => 'ExposureTime', + Groups => { 2 => 'Image' }, + ValueConv => 'exp(-$val/96*log(2))', + ValueConvInv => '-log($val)*96/log(2)', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x17 => 'Rotation', # usually the same as Orientation (but not always! why?) + # 0x25 - flash fired/not fired (ref 37) + # 0x26 - related to flash mode? (ref 37) + # 0x37 - related to flash strength (ref 37) + # 0x38 - pre-flash fired/no fired or flash data collection (ref 37) + 135 => { # [-3] <-- index relative to CameraInfoCount + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 138', + Notes => 'A450, A460, A550, A630, A640 and A710', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 145 => { #37 [-3] + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 148', + Notes => q{ + A560, A570, A650, A720, G7, G9, S5, SD40, SD750, SD800, SD850, SD870, SD900, + SD950, SD1000, SX100 and TX1 + }, + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, +); + +# Canon camera information for some PowerShot models (MakerNotes tag 0x0d) - PH +%Image::ExifTool::Canon::CameraInfoPowerShot2 = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + CameraInfo tags for PowerShot models such as the A470, A480, A490, A495, + A580, A590, A1000, A1100, A2000, A2100, A3000, A3100, D10, E1, G10, G11, + S90, S95, SD770, SD780, SD790, SD880, SD890, SD940, SD960, SD970, SD980, + SD990, SD1100, SD1200, SD1300, SD1400, SD3500, SD4000, SD4500, SX1, SX10, + SX20, SX110, SX120, SX130, SX200 and SX210. + }, + 0x01 => { + Name => 'ISO', + Groups => { 2 => 'Image' }, + ValueConv => '100*exp((($val-411)/96)*log(2))', + ValueConvInv => 'log($val/100)/log(2)*96+411', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x06 => { + Name => 'FNumber', + Groups => { 2 => 'Image' }, + ValueConv => 'exp($val/192*log(2))', + ValueConvInv => 'log($val)*192/log(2)', + PrintConv => 'sprintf("%.2g",$val)', + PrintConvInv => '$val', + }, + 0x07 => { + Name => 'ExposureTime', + Groups => { 2 => 'Image' }, + ValueConv => 'exp(-$val/96*log(2))', + ValueConvInv => '-log($val)*96/log(2)', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x18 => 'Rotation', + 153 => { # [-3] <-- index relative to CameraInfoCount + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 156', + Notes => 'A470, A580, A590, SD770, SD790, SD890 and SD1100', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 159 => { # [-3] + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 162', + Notes => 'A1000, A2000, E1, G10, SD880, SD990, SX1, SX10 and SX110', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 164 => { # [-3] + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 167', + Notes => 'A480, A1100, A2100, D10, SD780, SD960, SD970, SD1200 and SX200', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 168 => { # [-3] + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 171', + Notes => q{ + A490, A495, A3000, A3100, G11, S90, SD940, SD980, SD1300, SD1400, SD3500, + SD4000, SX20, SX120 and SX210 + }, + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 261 => { # [-3] + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 264', + Notes => 'S95, SD4500 and SX130', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, +); + +# unknown Canon camera information (MakerNotes tag 0x0d) - PH +%Image::ExifTool::Canon::CameraInfoUnknown32 = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Unknown CameraInfo tags are divided into 3 tables based on format size.', + # This tag may be uncommented, and is useful for generating + # lists of models in the "Notes" below... + # 0 => { + # Name => 'CameraInfoCount', + # ValueConv => '$$self{CameraInfoCount}', + # }, + 71 => { # [-1] <-- index relative to CameraInfoCount + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 72', + Notes => 'S1', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 83 => { # [-2] + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 85', + Notes => 'S2', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 91 => { # [-2 or -3] + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 93 or $$self{CameraInfoCount} == 94', + Notes => 'A410, A610, A620, S80, SD30, SD400, SD430, SD450, SD500 and SD550', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 92 => { # [-4] + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 96', + Notes => 'S3', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 100 => { # [-4] + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} == 104', + Notes => 'A420, A430, A530, A540, A700, SD600, SD630 and SD700', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + -3 => { + Name => 'CameraTemperature', + Condition => '$$self{CameraInfoCount} > 400', + Notes => '3 entries from end of record for most newer camera models', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, +# 466 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 469', +# Notes => '100HS, 300HS, 500HS, A1200, A2200, A3200 and A3300', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +# 503 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 506', +# Notes => 'A800', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +# 506 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 509', +# Notes => 'SX230HS', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +# 520 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 523', +# Notes => '310HS, 510HS, G1X, S100 (new), SX40HS and SX150', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +# 524 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 527', +# Notes => '110HS, 520HS, A2300, A2400, A3400, A4000, D20 and SX260HS', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +# 532 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 535', +# Notes => 'S110 (new), G15, SX50, SX160IS and SX500IS', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +# 547 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 550', +# Notes => '130IS, A1400, A2500 and A2600', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +# 549 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 552', +# Notes => '115IS, 130IS, SX270, SX280, 330HS and A3500', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +# 552 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 555', +# Notes => 'S200 (new)', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +# 850 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 853', +# Notes => 'N', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +# 895 => { # [-3] +# Name => 'CameraTemperature', +# Condition => '$$self{CameraInfoCount} == 898', +# Notes => 'G1XmkII, N100, SX600HS and SX700HS', +# PrintConv => '"$val C"', +# PrintConvInv => '$val=~s/ ?C//; $val', +# }, +); + +# unknown Canon camera information (MakerNotes tag 0x0d) - PH +%Image::ExifTool::Canon::CameraInfoUnknown16 = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, +); + +# unknown Canon camera information (MakerNotes tag 0x0d) - PH +%Image::ExifTool::Canon::CameraInfoUnknown = ( + %binaryDataAttrs, + FORMAT => 'int8s', + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x16b => { + Name => 'LensSerialNumber', + Condition => '$$self{Model} =~ /^Canon EOS 5DS/', # (good for 5DS and 5DSR) + Format => 'undef[5]', + Priority => 0, + ValueConv => 'unpack("H*",$val)', + ValueConvInv => 'length($val) < 10 and $val = 0 x (10-length($val)) . $val; pack("H*",$val)', + }, + 0x5c1 => { + Name => 'FirmwareVersion', + Format => 'string[6]', + Writable => 0, + Condition => '$$valPt =~ /^\d\.\d\.\d\0/', + Notes => 'M50', # (firmware 1.0.0) + }, +); + +# Picture Style information for various cameras (ref 48) +%Image::ExifTool::Canon::PSInfo = ( + %binaryDataAttrs, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom picture style information for various models.', + # (values expected to be "n/a" are flagged as Unknown) + 0x00 => { Name => 'ContrastStandard', %psInfo }, + 0x04 => { Name => 'SharpnessStandard', %psInfo }, + 0x08 => { Name => 'SaturationStandard', %psInfo }, + 0x0c => { Name => 'ColorToneStandard', %psInfo }, + 0x10 => { Name => 'FilterEffectStandard', %psInfo, Unknown => 1 }, + 0x14 => { Name => 'ToningEffectStandard', %psInfo, Unknown => 1 }, + 0x18 => { Name => 'ContrastPortrait', %psInfo }, + 0x1c => { Name => 'SharpnessPortrait', %psInfo }, + 0x20 => { Name => 'SaturationPortrait', %psInfo }, + 0x24 => { Name => 'ColorTonePortrait', %psInfo }, + 0x28 => { Name => 'FilterEffectPortrait', %psInfo, Unknown => 1 }, + 0x2c => { Name => 'ToningEffectPortrait', %psInfo, Unknown => 1 }, + 0x30 => { Name => 'ContrastLandscape', %psInfo }, + 0x34 => { Name => 'SharpnessLandscape', %psInfo }, + 0x38 => { Name => 'SaturationLandscape', %psInfo }, + 0x3c => { Name => 'ColorToneLandscape', %psInfo }, + 0x40 => { Name => 'FilterEffectLandscape', %psInfo, Unknown => 1 }, + 0x44 => { Name => 'ToningEffectLandscape', %psInfo, Unknown => 1 }, + 0x48 => { Name => 'ContrastNeutral', %psInfo }, + 0x4c => { Name => 'SharpnessNeutral', %psInfo }, + 0x50 => { Name => 'SaturationNeutral', %psInfo }, + 0x54 => { Name => 'ColorToneNeutral', %psInfo }, + 0x58 => { Name => 'FilterEffectNeutral', %psInfo, Unknown => 1 }, + 0x5c => { Name => 'ToningEffectNeutral', %psInfo, Unknown => 1 }, + 0x60 => { Name => 'ContrastFaithful', %psInfo }, + 0x64 => { Name => 'SharpnessFaithful', %psInfo }, + 0x68 => { Name => 'SaturationFaithful', %psInfo }, + 0x6c => { Name => 'ColorToneFaithful', %psInfo }, + 0x70 => { Name => 'FilterEffectFaithful', %psInfo, Unknown => 1 }, + 0x74 => { Name => 'ToningEffectFaithful', %psInfo, Unknown => 1 }, + 0x78 => { Name => 'ContrastMonochrome', %psInfo }, + 0x7c => { Name => 'SharpnessMonochrome', %psInfo }, + 0x80 => { Name => 'SaturationMonochrome', %psInfo, Unknown => 1 }, + 0x84 => { Name => 'ColorToneMonochrome', %psInfo, Unknown => 1 }, + 0x88 => { Name => 'FilterEffectMonochrome',%psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0x8c => { Name => 'ToningEffectMonochrome',%psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0x90 => { Name => 'ContrastUserDef1', %psInfo }, + 0x94 => { Name => 'SharpnessUserDef1', %psInfo }, + 0x98 => { Name => 'SaturationUserDef1', %psInfo }, + 0x9c => { Name => 'ColorToneUserDef1', %psInfo }, + 0xa0 => { Name => 'FilterEffectUserDef1', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xa4 => { Name => 'ToningEffectUserDef1', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xa8 => { Name => 'ContrastUserDef2', %psInfo }, + 0xac => { Name => 'SharpnessUserDef2', %psInfo }, + 0xb0 => { Name => 'SaturationUserDef2', %psInfo }, + 0xb4 => { Name => 'ColorToneUserDef2', %psInfo }, + 0xb8 => { Name => 'FilterEffectUserDef2', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xbc => { Name => 'ToningEffectUserDef2', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xc0 => { Name => 'ContrastUserDef3', %psInfo }, + 0xc4 => { Name => 'SharpnessUserDef3', %psInfo }, + 0xc8 => { Name => 'SaturationUserDef3', %psInfo }, + 0xcc => { Name => 'ColorToneUserDef3', %psInfo }, + 0xd0 => { Name => 'FilterEffectUserDef3', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xd4 => { Name => 'ToningEffectUserDef3', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + # base picture style names: + 0xd8 => { + Name => 'UserDef1PictureStyle', + Format => 'int16u', + SeparateTable => 'UserDefStyle', + PrintConv => \%userDefStyles, + }, + 0xda => { + Name => 'UserDef2PictureStyle', + Format => 'int16u', + SeparateTable => 'UserDefStyle', + PrintConv => \%userDefStyles, + }, + 0xdc => { + Name => 'UserDef3PictureStyle', + Format => 'int16u', + SeparateTable => 'UserDefStyle', + PrintConv => \%userDefStyles, + }, +); + +# Picture Style information for the 60D, etc (ref 48) +%Image::ExifTool::Canon::PSInfo2 = ( + %binaryDataAttrs, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom picture style information for the EOS 5DmkIII, 60D, 600D and 1100D.', + # (values expected to be "n/a" are flagged as Unknown) + 0x00 => { Name => 'ContrastStandard', %psInfo }, + 0x04 => { Name => 'SharpnessStandard', %psInfo }, + 0x08 => { Name => 'SaturationStandard', %psInfo }, + 0x0c => { Name => 'ColorToneStandard', %psInfo }, + 0x10 => { Name => 'FilterEffectStandard', %psInfo, Unknown => 1 }, + 0x14 => { Name => 'ToningEffectStandard', %psInfo, Unknown => 1 }, + 0x18 => { Name => 'ContrastPortrait', %psInfo }, + 0x1c => { Name => 'SharpnessPortrait', %psInfo }, + 0x20 => { Name => 'SaturationPortrait', %psInfo }, + 0x24 => { Name => 'ColorTonePortrait', %psInfo }, + 0x28 => { Name => 'FilterEffectPortrait', %psInfo, Unknown => 1 }, + 0x2c => { Name => 'ToningEffectPortrait', %psInfo, Unknown => 1 }, + 0x30 => { Name => 'ContrastLandscape', %psInfo }, + 0x34 => { Name => 'SharpnessLandscape', %psInfo }, + 0x38 => { Name => 'SaturationLandscape', %psInfo }, + 0x3c => { Name => 'ColorToneLandscape', %psInfo }, + 0x40 => { Name => 'FilterEffectLandscape', %psInfo, Unknown => 1 }, + 0x44 => { Name => 'ToningEffectLandscape', %psInfo, Unknown => 1 }, + 0x48 => { Name => 'ContrastNeutral', %psInfo }, + 0x4c => { Name => 'SharpnessNeutral', %psInfo }, + 0x50 => { Name => 'SaturationNeutral', %psInfo }, + 0x54 => { Name => 'ColorToneNeutral', %psInfo }, + 0x58 => { Name => 'FilterEffectNeutral', %psInfo, Unknown => 1 }, + 0x5c => { Name => 'ToningEffectNeutral', %psInfo, Unknown => 1 }, + 0x60 => { Name => 'ContrastFaithful', %psInfo }, + 0x64 => { Name => 'SharpnessFaithful', %psInfo }, + 0x68 => { Name => 'SaturationFaithful', %psInfo }, + 0x6c => { Name => 'ColorToneFaithful', %psInfo }, + 0x70 => { Name => 'FilterEffectFaithful', %psInfo, Unknown => 1 }, + 0x74 => { Name => 'ToningEffectFaithful', %psInfo, Unknown => 1 }, + 0x78 => { Name => 'ContrastMonochrome', %psInfo }, + 0x7c => { Name => 'SharpnessMonochrome', %psInfo }, + 0x80 => { Name => 'SaturationMonochrome', %psInfo, Unknown => 1 }, + 0x84 => { Name => 'ColorToneMonochrome', %psInfo, Unknown => 1 }, + 0x88 => { Name => 'FilterEffectMonochrome',%psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0x8c => { Name => 'ToningEffectMonochrome',%psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0x90 => { Name => 'ContrastAuto', %psInfo }, + 0x94 => { Name => 'SharpnessAuto', %psInfo }, + 0x98 => { Name => 'SaturationAuto', %psInfo }, + 0x9c => { Name => 'ColorToneAuto', %psInfo }, + 0xa0 => { Name => 'FilterEffectAuto', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xa4 => { Name => 'ToningEffectAuto', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xa8 => { Name => 'ContrastUserDef1', %psInfo }, + 0xac => { Name => 'SharpnessUserDef1', %psInfo }, + 0xb0 => { Name => 'SaturationUserDef1', %psInfo }, + 0xb4 => { Name => 'ColorToneUserDef1', %psInfo }, + 0xb8 => { Name => 'FilterEffectUserDef1', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xbc => { Name => 'ToningEffectUserDef1', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xc0 => { Name => 'ContrastUserDef2', %psInfo }, + 0xc4 => { Name => 'SharpnessUserDef2', %psInfo }, + 0xc8 => { Name => 'SaturationUserDef2', %psInfo }, + 0xcc => { Name => 'ColorToneUserDef2', %psInfo }, + 0xd0 => { Name => 'FilterEffectUserDef2', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xd4 => { Name => 'ToningEffectUserDef2', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xd8 => { Name => 'ContrastUserDef3', %psInfo }, + 0xdc => { Name => 'SharpnessUserDef3', %psInfo }, + 0xe0 => { Name => 'SaturationUserDef3', %psInfo }, + 0xe4 => { Name => 'ColorToneUserDef3', %psInfo }, + 0xe8 => { Name => 'FilterEffectUserDef3', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + 0xec => { Name => 'ToningEffectUserDef3', %psInfo, + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + -559038737 => 'n/a', # (0xdeadbeef) + }, + }, + # base picture style names: + 0xf0 => { + Name => 'UserDef1PictureStyle', + Format => 'int16u', + SeparateTable => 'UserDefStyle', + PrintConv => \%userDefStyles, + }, + 0xf2 => { + Name => 'UserDef2PictureStyle', + Format => 'int16u', + SeparateTable => 'UserDefStyle', + PrintConv => \%userDefStyles, + }, + 0xf4 => { + Name => 'UserDef3PictureStyle', + Format => 'int16u', + SeparateTable => 'UserDefStyle', + PrintConv => \%userDefStyles, + }, +); + +# Movie information (MakerNotes tag 0x11) (ref PH) +%Image::ExifTool::Canon::MovieInfo = ( + %binaryDataAttrs, + FORMAT => 'int16u', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Video' }, + NOTES => 'Tags written by some Canon cameras when recording video.', + 1 => { # (older PowerShot AVI) + Name => 'FrameRate', + RawConv => '$val == 65535 ? undef: $val', + ValueConvInv => '$val > 65535 ? 65535 : $val', + }, + 2 => { # (older PowerShot AVI) + Name => 'FrameCount', + RawConv => '$val == 65535 ? undef: $val', + ValueConvInv => '$val > 65535 ? 65535 : $val', + }, + # 3 - values: 0x0001 (older PowerShot AVI), 0x4004, 0x4005 + 4 => { + Name => 'FrameCount', + Format => 'int32u', + }, + 6 => { + Name => 'FrameRate', + Format => 'rational32u', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + PrintConvInv => '$val', + }, + # 9/10 - same as 6/7 (FrameRate) + 106 => { + Name => 'Duration', + Format => 'int32u', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => 'ConvertDuration($val)', + PrintConvInv => q{ + my @a = ($val =~ /\d+(?:\.\d*)?/g); + $val = pop(@a) || 0; # seconds + $val += pop(@a) * 60 if @a; # minutes + $val += pop(@a) * 3600 if @a; # hours + return $val; + }, + }, + 108 => { + Name => 'AudioBitrate', + Groups => { 2 => 'Audio' }, + Format => 'int32u', + PrintConv => 'ConvertBitrate($val)', + PrintConvInv => q{ + $val =~ /^(\d+(?:\.\d*)?) ?([kMG]?bps)?$/ or return undef; + return $1 * {bps=>1,kbps=>1000,Mbps=>1000000,Gbps=>1000000000}->{$2 || 'bps'}; + }, + }, + 110 => { + Name => 'AudioSampleRate', + Groups => { 2 => 'Audio' }, + Format => 'int32u', + }, + 112 => { # (guess) + Name => 'AudioChannels', + Groups => { 2 => 'Audio' }, + Format => 'int32u', + }, + # 114 - values: 0 (60D), 1 (S95) + 116 => { + Name => 'VideoCodec', + Format => 'undef[4]', + # swap bytes if little endian + RawConv => 'GetByteOrder() eq "MM" ? $val : pack("N",unpack("V",$val))', + RawConvInv => 'GetByteOrder() eq "MM" ? $val : pack("N",unpack("V",$val))', + }, + # 125 - same as 10 +); + +# AF information (MakerNotes tag 0x12) - PH +%Image::ExifTool::Canon::AFInfo = ( + PROCESS_PROC => \&ProcessSerialData, + VARS => { ID_LABEL => 'Sequence' }, + FORMAT => 'int16u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + Auto-focus information used by many older Canon models. The values in this + record are sequential, and some have variable sizes based on the value of + NumAFPoints (which may be 1,5,7,9,15,45 or 53). The AFArea coordinates are + given in a system where the image has dimensions given by AFImageWidth and + AFImageHeight, and 0,0 is the image center. The direction of the Y axis + depends on the camera model, with positive Y upwards for EOS models, but + apparently downwards for PowerShot models. + }, + 0 => { + Name => 'NumAFPoints', + }, + 1 => { + Name => 'ValidAFPoints', + Notes => 'number of AF points valid in the following information', + }, + 2 => { + Name => 'CanonImageWidth', + Groups => { 2 => 'Image' }, + }, + 3 => { + Name => 'CanonImageHeight', + Groups => { 2 => 'Image' }, + }, + 4 => { + Name => 'AFImageWidth', + Notes => 'size of image in AF coordinates', + }, + 5 => 'AFImageHeight', + 6 => 'AFAreaWidth', + 7 => 'AFAreaHeight', + 8 => { + Name => 'AFAreaXPositions', + Format => 'int16s[$val{0}]', + }, + 9 => { + Name => 'AFAreaYPositions', + Format => 'int16s[$val{0}]', + }, + 10 => { + Name => 'AFPointsInFocus', + Format => 'int16s[int(($val{0}+15)/16)]', + PrintConv => 'Image::ExifTool::DecodeBits($val, undef, 16)', + }, + 11 => [ + { + Name => 'PrimaryAFPoint', + Condition => q{ + $$self{Model} !~ /EOS/ and + (not $$self{AFInfoCount} or $$self{AFInfoCount} != 36) + }, + }, + { + # (some PowerShot 9-point systems put PrimaryAFPoint after 8 unknown values) + Name => 'Canon_AFInfo_0x000b', + Condition => '$$self{Model} !~ /EOS/', + Format => 'int16u[8]', + Unknown => 1, + }, + # (serial processing stops here for EOS cameras) + ], + 12 => 'PrimaryAFPoint', +); + +# newer AF information (MakerNotes tag 0x26 and 0x32) - PH (A570IS,1DmkIII,40D and G1XmkII) +# (Note: this tag is out of sequence in A570IS maker notes) +%Image::ExifTool::Canon::AFInfo2 = ( + PROCESS_PROC => \&ProcessSerialData, + VARS => { ID_LABEL => 'Sequence' }, + FORMAT => 'int16u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + Newer version of the AFInfo record containing much of the same information + (and coordinate confusion) as the older version. In this record, NumAFPoints + may be 7, 9, 11, 19, 31, 45 or 61, depending on the camera model. + }, + 0 => { + Name => 'AFInfoSize', + Unknown => 1, # normally don't print this out + }, + 1 => { + Name => 'AFAreaMode', + PrintConv => { + 0 => 'Off (Manual Focus)', + 1 => 'AF Point Expansion (surround)', #PH + 2 => 'Single-point AF', + # 3 - n/a + 4 => 'Auto', #forum6237 (AiAF on A570IS) + 5 => 'Face Detect AF', + 6 => 'Face + Tracking', #PH (NC, EOS M, live view) + 7 => 'Zone AF', #46 + 8 => 'AF Point Expansion (4 point)', #46/PH/forum6237 + 9 => 'Spot AF', #46 + 10 => 'AF Point Expansion (8 point)', #forum6237 + 11 => 'Flexizone Multi (49 point)', #PH (NC, EOS M, live view; 750D 49 points) + 12 => 'Flexizone Multi (9 point)', #PH (750D, 9 points) + 13 => 'Flexizone Single', #PH (EOS M default, live view) + 14 => 'Large Zone AF', #PH/forum6237 (7DmkII) + }, + }, + 2 => { + Name => 'NumAFPoints', + RawConv => '$$self{NumAFPoints} = $val', # save for later + }, + 3 => { + Name => 'ValidAFPoints', + Notes => 'number of AF points valid in the following information', + }, + 4 => { + Name => 'CanonImageWidth', + Groups => { 2 => 'Image' }, + }, + 5 => { + Name => 'CanonImageHeight', + Groups => { 2 => 'Image' }, + }, + 6 => { + Name => 'AFImageWidth', + Notes => 'size of image in AF coordinates', + }, + 7 => 'AFImageHeight', + 8 => { + Name => 'AFAreaWidths', + Format => 'int16s[$val{2}]', + }, + 9 => { + Name => 'AFAreaHeights', + Format => 'int16s[$val{2}]', + }, + 10 => { + Name => 'AFAreaXPositions', + Format => 'int16s[$val{2}]', + }, + 11 => { + Name => 'AFAreaYPositions', + Format => 'int16s[$val{2}]', + }, + 12 => { + Name => 'AFPointsInFocus', + Format => 'int16s[int(($val{2}+15)/16)]', + PrintConv => 'Image::ExifTool::DecodeBits($val, undef, 16)', + }, + 13 => [ + { + Name => 'AFPointsSelected', + Condition => '$$self{Model} =~ /EOS/', + Format => 'int16s[int(($val{2}+15)/16)]', + PrintConv => 'Image::ExifTool::DecodeBits($val, undef, 16)', + }, + { + Name => 'Canon_AFInfo2_0x000d', + Format => 'int16s[int(($val{2}+15)/16)+1]', + Unknown => 1, + }, + ], + 14 => { + # usually, but not always, the lowest number AF point in focus + Name => 'PrimaryAFPoint', + Condition => '$$self{Model} !~ /EOS/ and not $$self{AFInfo3}', # (not valid for G1XmkII) + }, +); + +# contrast information (MakerNotes tag 0x27) - PH +%Image::ExifTool::Canon::ContrastInfo = ( + %binaryDataAttrs, + FORMAT => 'int16u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 4 => { + Name => 'IntelligentContrast', + PrintHex => 1, + PrintConv => { + 0x00 => 'Off', + 0x08 => 'On', + 0xffff => 'n/a', + OTHER => sub { + # DPP shows "On" for any value except 0xffff when bit 0x08 is set + my ($val, $inv) = @_; + if ($inv) { + $val =~ /(0x[0-9a-f]+)/i or $val =~ /(\d+)/ or return undef; + return $1; + } else { + return sprintf("On (0x%.2x)",$val) if $val & 0x08; + return sprintf("Off (0x%.2x)",$val); + } + }, + }, + }, + # 6 - 0=normal, 257=i-Contrast On +); + +# time information (MakerNotes tag 0x35) - PH (1DX, 5DmkIII) +%Image::ExifTool::Canon::TimeInfo = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Time' }, + # 0 - size (16 bytes) + 1 => { + Name => 'TimeZone', + PrintConv => 'Image::ExifTool::TimeZoneString($val)', + PrintConvInv => q{ + $val =~ /Z$/ and return 0; + $val =~ /([-+])(\d{1,2}):?(\d{2})$/ and return $1 . ($2 * 60 + $3); + $val =~ /^(\d{2})(\d{2})$/ and return $1 * 60 + $2; + return undef; + }, + }, + 2 => { + Name => 'TimeZoneCity', + PrintConvColumns => 3, + PrintConv => { + # [square brackets] = actual time zone for each city + # (round brackets) = observed time zone values from sample images + # --> unobserved entries have not been confirmed! + 0 => 'n/a', # (PowerShot models) + 1 => 'Chatham Islands', # [+12:45] + 2 => 'Wellington', # [+12] (+12:00,DST+0) + 3 => 'Solomon Islands', # [+11] + 4 => 'Sydney', # [+10] (+11:00,DST+1) + 5 => 'Adelaide', # [+9:30] + 6 => 'Tokyo', # [+9] (+09:00,DST+0) + 7 => 'Hong Kong', # [+8] (+08:00,DST+0) + 8 => 'Bangkok', # [+7] (+08:00,DST+1) + 9 => 'Yangon', # [+6:30] + 10 => 'Dhaka', # [+6] (Canon uses old "Dacca" spelling) + 11 => 'Kathmandu', # [+5:45] + 12 => 'Delhi', # [+5:30] + 13 => 'Karachi', # [+5] + 14 => 'Kabul', # [+4:30] + 15 => 'Dubai', # [+4] + 16 => 'Tehran', # [+3:30] + 17 => 'Moscow', # [+4] (+03:00,DST+0) (! changed to +4 permanent DST in 2011) + 18 => 'Cairo', # [+2] + 19 => 'Paris', # [+1] (+01:10,DST+0; +02:00,DST+1) + 20 => 'London', # [0] (+00:00,DST+0) + 21 => 'Azores', # [-1] + 22 => 'Fernando de Noronha', # [-2] + 23 => 'Sao Paulo', # [-3] + 24 => 'Newfoundland', # [-3:30] + 25 => 'Santiago', # [-4] + 26 => 'Caracas', # [-4:30] + 27 => 'New York', # [-5] (-05:00,DST+0; -04:00,DST+1) + 28 => 'Chicago', # [-6] + 29 => 'Denver', # [-7] + 30 => 'Los Angeles', # [-8] (-08:00,DST+0; -07:00,DST+1) + 31 => 'Anchorage', # [-9] + 32 => 'Honolulu', # [-10] + 33 => 'Samoa', # [+13] + 32766 => '(not set)', #(NC) + }, + }, + 3 => { + Name => 'DaylightSavings', + PrintConv => { + 0 => 'Off', + 60 => 'On', + }, + }, +); + +# my color mode information (MakerNotes tag 0x1d) - PH (A570IS) +%Image::ExifTool::Canon::MyColors = ( + %binaryDataAttrs, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x02 => { + Name => 'MyColorMode', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'Positive Film', #15 (SD600) + 2 => 'Light Skin Tone', #15 + 3 => 'Dark Skin Tone', #15 + 4 => 'Vivid Blue', #15 + 5 => 'Vivid Green', #15 + 6 => 'Vivid Red', #15 + 7 => 'Color Accent', #15 (A610) (NC) + 8 => 'Color Swap', #15 (A610) + 9 => 'Custom', + 12 => 'Vivid', + 13 => 'Neutral', + 14 => 'Sepia', + 15 => 'B&W', + }, + }, +); + +# face detect information (MakerNotes tag 0x24) - PH (A570IS) +%Image::ExifTool::Canon::FaceDetect1 = ( + %binaryDataAttrs, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + DATAMEMBER => [ 0x02 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x02 => { + Name => 'FacesDetected', + DataMember => 'FacesDetected', + RawConv => '$$self{FacesDetected} = $val', + }, + 0x03 => { + Name => 'FaceDetectFrameSize', + Format => 'int16u[2]', + }, + 0x08 => { + Name => 'Face1Position', + Format => 'int16s[2]', + RawConv => '$$self{FacesDetected} < 1 ? undef: $val', + Notes => q{ + X-Y coordinates for the center of each face in the Face Detect frame at the + time of focus lock. "0 0" is the center, and positive X and Y are to the + right and downwards respectively + }, + }, + 0x0a => { + Name => 'Face2Position', + Format => 'int16s[2]', + RawConv => '$$self{FacesDetected} < 2 ? undef : $val', + }, + 0x0c => { + Name => 'Face3Position', + Format => 'int16s[2]', + RawConv => '$$self{FacesDetected} < 3 ? undef : $val', + }, + 0x0e => { + Name => 'Face4Position', + Format => 'int16s[2]', + RawConv => '$$self{FacesDetected} < 4 ? undef : $val', + }, + 0x10 => { + Name => 'Face5Position', + Format => 'int16s[2]', + RawConv => '$$self{FacesDetected} < 5 ? undef : $val', + }, + 0x12 => { + Name => 'Face6Position', + Format => 'int16s[2]', + RawConv => '$$self{FacesDetected} < 6 ? undef : $val', + }, + 0x14 => { + Name => 'Face7Position', + Format => 'int16s[2]', + RawConv => '$$self{FacesDetected} < 7 ? undef : $val', + }, + 0x16 => { + Name => 'Face8Position', + Format => 'int16s[2]', + RawConv => '$$self{FacesDetected} < 8 ? undef : $val', + }, + 0x18 => { + Name => 'Face9Position', + Format => 'int16s[2]', + RawConv => '$$self{FacesDetected} < 9 ? undef : $val', + }, +); + +# more face detect information (MakerNotes tag 0x25) - PH (A570IS) +%Image::ExifTool::Canon::FaceDetect2 = ( + %binaryDataAttrs, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x01 => 'FaceWidth', + 0x02 => 'FacesDetected', +); + +# G9 white balance information (MakerNotes tag 0x29) (ref IB, changed ref forum13640) +%Image::ExifTool::Canon::WBInfo = ( + %binaryDataAttrs, + NOTES => 'WB tags for the Canon G9.', + FORMAT => 'int32u', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x02 => { Name => 'WB_GRBGLevelsAuto', Format => 'int32s[4]' }, + 0x0a => { Name => 'WB_GRBGLevelsDaylight', Format => 'int32s[4]' }, + 0x12 => { Name => 'WB_GRBGLevelsCloudy', Format => 'int32s[4]' }, + 0x1a => { Name => 'WB_GRBGLevelsTungsten', Format => 'int32s[4]' }, + 0x22 => { Name => 'WB_GRBGLevelsFluorescent', Format => 'int32s[4]' }, + 0x2a => { Name => 'WB_GRBGLevelsFluorHigh', Format => 'int32s[4]' }, + 0x32 => { Name => 'WB_GRBGLevelsFlash', Format => 'int32s[4]' }, + 0x3a => { Name => 'WB_GRBGLevelsUnderwater', Format => 'int32s[4]' }, + 0x42 => { Name => 'WB_GRBGLevelsCustom1', Format => 'int32s[4]' }, + 0x4a => { Name => 'WB_GRBGLevelsCustom2', Format => 'int32s[4]' }, +); + +# yet more face detect information (MakerNotes tag 0x2f) - PH (G12) +%Image::ExifTool::Canon::FaceDetect3 = ( + %binaryDataAttrs, + FORMAT => 'int16u', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + # 0 - size (34 bytes) + # 1 - 1=4:3/16:9,2=1:1/3:2/4:5 + # 2 - normally 1 if faces detected, but sometimes 0 (maybe if face wasn't in captured image?) + 3 => 'FacesDetected', + # 4 - 240=4:3/4:5/1:1,180=16:9,212=3:2 +); + +# File number information (MakerNotes tag 0x93) +%Image::ExifTool::Canon::FileInfo = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 20 ], + 1 => [ + { #5 + Name => 'FileNumber', + Condition => '$$self{Model} =~ /\b(20D|350D|REBEL XT|Kiss Digital N)\b/', + Format => 'int32u', + # Thanks to Juha Eskelinen for figuring this out: + # [this is an odd bit mapping -- it looks like the file number exists as + # a 16-bit integer containing the high bits, followed by an 8-bit integer + # with the low bits. But it is more convenient to have this in a single + # word, so some bit manipulations are necessary... - PH] + # The bit pattern of the 32-bit word is: + # 31....24 23....16 15.....8 7......0 + # 00000000 ffffffff DDDDDDDD ddFFFFFF + # 0 = zero bits (not part of the file number?) + # f/F = low/high bits of file number + # d/D = low/high bits of directory number + # The directory and file number are then converted into decimal + # and separated by a '-' to give the file number used in the 20D + ValueConv => '(($val&0xffc0)>>6)*10000+(($val>>16)&0xff)+(($val&0x3f)<<8)', + ValueConvInv => q{ + my $d = int($val/10000); + my $f = $val - $d * 10000; + return (($d<<6) & 0xffc0) + (($f & 0xff)<<16) + (($f>>8) & 0x3f); + }, + PrintConv => '$_=$val,s/(\d+)(\d{4})/$1-$2/,$_', + PrintConvInv => '$val=~s/-//g;$val', + }, + { #16 + Name => 'FileNumber', + Condition => '$$self{Model} =~ /\b(30D|400D|REBEL XTi|Kiss Digital X|K236)\b/', + Format => 'int32u', + Notes => q{ + the location of the upper 4 bits of the directory number is a mystery for + the EOS 30D, so the reported directory number will be incorrect for original + images with a directory number of 164 or greater + }, + # Thanks to Emil Sit for figuring this out: + # [more insane bit maniplations like the 20D/350D above, but this time we + # appear to have lost the upper 4 bits of the directory number (this was + # verified through tests with directory numbers 100, 222, 801 and 999) - PH] + # The bit pattern for the 30D is: (see 20D notes above for more information) + # 31....24 23....16 15.....8 7......0 + # 00000000 ffff0000 ddddddFF FFFFFFFF + # [NOTE: the 4 high order directory bits don't appear in this record, but + # I have chosen to write them into bits 16-19 since these 4 zero bits look + # very suspicious, and are a convenient place to store this information - PH] + ValueConv => q{ + my $d = ($val & 0xffc00) >> 10; + # we know there are missing bits if directory number is < 100 + $d += 0x40 while $d < 100; # (repair the damage as best we can) + return $d*10000 + (($val&0x3ff)<<4) + (($val>>20)&0x0f); + }, + ValueConvInv => q{ + my $d = int($val/10000); + my $f = $val - $d * 10000; + return ($d << 10) + (($f>>4)&0x3ff) + (($f&0x0f)<<20); + }, + PrintConv => '$_=$val,s/(\d+)(\d{4})/$1-$2/,$_', + PrintConvInv => '$val=~s/-//g;$val', + }, + { #7 (1D, 1Ds) + Name => 'ShutterCount', + Condition => 'GetByteOrder() eq "MM"', + Format => 'int32u', + }, + { #7 (1DmkII, 1DSmkII, 1DSmkIIN) + Name => 'ShutterCount', + # ref http://www.luminous-landscape.com/forum/index.php?topic=36469 : + Notes => q{ + there are reports that the ShutterCount changed when loading a settings file + on the 1DSmkII + }, + Condition => '$$self{Model} =~ /\b1Ds? Mark II\b/', + Format => 'int32u', + ValueConv => '($val>>16)|(($val&0xffff)<<16)', + ValueConvInv => '($val>>16)|(($val&0xffff)<<16)', + }, + # 5D gives a single byte value (unknown) + # 40D stores all zeros + ], + 3 => { #PH + Name => 'BracketMode', + PrintConv => { + 0 => 'Off', + 1 => 'AEB', + 2 => 'FEB', + 3 => 'ISO', + 4 => 'WB', + }, + }, + 4 => 'BracketValue', #PH + 5 => 'BracketShotNumber', #PH + 6 => { #PH + Name => 'RawJpgQuality', + RawConv => '$val<=0 ? undef : $val', + PrintConv => \%canonQuality, + }, + 7 => { #PH + Name => 'RawJpgSize', + RawConv => '$val<0 ? undef : $val', + PrintConv => \%canonImageSize, + }, + 8 => { #PH + Name => 'LongExposureNoiseReduction2', + Notes => q{ + for some modules this gives the long exposure noise reduction applied to the + image, but for other models this just reflects the setting independent of + whether or not it was applied + }, + RawConv => '$val<0 ? undef : $val', + PrintConv => { + 0 => 'Off', + 1 => 'On (1D)', + 3 => 'On', + 4 => 'Auto', + }, + }, + 9 => { #PH + Name => 'WBBracketMode', + PrintConv => { + 0 => 'Off', + 1 => 'On (shift AB)', + 2 => 'On (shift GM)', + }, + }, + 12 => 'WBBracketValueAB', #PH + 13 => 'WBBracketValueGM', #PH + 14 => { #PH + Name => 'FilterEffect', + RawConv => '$val==-1 ? undef : $val', + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + }, + }, + 15 => { #PH + Name => 'ToningEffect', + RawConv => '$val==-1 ? undef : $val', + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + }, + }, + 16 => { #PH + %ciMacroMagnification, + # MP-E 65mm on 5DmkII: 44=5x,52~=3.9x,56~=3.3x,62~=2.6x,75=1x + # ME-E 65mm on 40D/450D: 72 for all samples (not valid) + Condition => q{ + $$self{LensType} and $$self{LensType} == 124 and + $$self{Model} !~ /\b(40D|450D|REBEL XSi|Kiss X2)\b/ + }, + Notes => q{ + currently decoded only for the MP-E 65mm f/2.8 1-5x Macro Photo, and not + valid for all camera models + }, + }, + # 17 - values: 0, 3, 4 + # 18 - same as LiveViewShooting for all my samples (5DmkII, 50D) - PH + 19 => { #PH + # Note: this value is not displayed by Canon ImageBrowser for the following + # models with the live view feature: 1DmkIII, 1DSmkIII, 40D, 450D, 1000D + # (this tag could be valid only for some firmware versions: + # http://www.breezesys.com/forum/showthread.php?p=16980) + Name => 'LiveViewShooting', + PrintConv => \%offOn, + }, + 20 => { #47 + Name => 'FocusDistanceUpper', + DataMember => 'FocusDistanceUpper2', + Format => 'int16u', + RawConv => '($$self{FocusDistanceUpper2} = $val) || undef', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => '$val > 655.345 ? "inf" : "$val m"', + PrintConvInv => '$val =~ s/ ?m$//; IsFloat($val) ? $val : 655.35', + }, + 21 => { #47 + Name => 'FocusDistanceLower', + Condition => '$$self{FocusDistanceUpper2}', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => '$val > 655.345 ? "inf" : "$val m"', + PrintConvInv => '$val =~ s/ ?m$//; IsFloat($val) ? $val : 655.35', + }, + # 22 - values: 0, 1 + 23 => { #JohnMoyer (forum12925) + Name => 'ShutterMode', + PrintConv => { + 0 => 'Mechanical', + 1 => 'Electronic First Curtain', + 2 => 'Electronic', + # 3 => ? + # 21 => ? + # 22 => ? + }, + }, + 25 => { #PH + Name => 'FlashExposureLock', + PrintConv => \%offOn, + }, + 0x3d => { #IB + Name => 'RFLensType', + Format => 'int16u', + PrintConv => { + 0 => 'n/a', + 257 => 'Canon RF 50mm F1.2L USM', + 258 => 'Canon RF 24-105mm F4L IS USM', + 259 => 'Canon RF 28-70mm F2L USM', + 260 => 'Canon RF 35mm F1.8 MACRO IS STM', + 261 => 'Canon RF 85mm F1.2L USM', + 262 => 'Canon RF 85mm F1.2L USM DS', + 263 => 'Canon RF 24-70mm F2.8L IS USM', + 264 => 'Canon RF 15-35mm F2.8L IS USM', + 265 => 'Canon RF 24-240mm F4-6.3 IS USM', + 266 => 'Canon RF 70-200mm F2.8L IS USM', + 267 => 'Canon RF 85mm F2 MACRO IS STM', + 268 => 'Canon RF 600mm F11 IS STM', + 269 => 'Canon RF 600mm F11 IS STM + RF1.4x', + 270 => 'Canon RF 600mm F11 IS STM + RF2x', + 271 => 'Canon RF 800mm F11 IS STM', + 272 => 'Canon RF 800mm F11 IS STM + RF1.4x', + 273 => 'Canon RF 800mm F11 IS STM + RF2x', + 274 => 'Canon RF 24-105mm F4-7.1 IS STM', + 275 => 'Canon RF 100-500mm F4.5-7.1L IS USM', + 276 => 'Canon RF 100-500mm F4.5-7.1L IS USM + RF1.4x', + 277 => 'Canon RF 100-500mm F4.5-7.1L IS USM + RF2x', + 278 => 'Canon RF 70-200mm F4L IS USM', #42 + 279 => 'Canon RF 100mm F2.8L MACRO IS USM', #42 + 280 => 'Canon RF 50mm F1.8 STM', #42 + 281 => 'Canon RF 14-35mm F4L IS USM', #42/IB + 282 => 'Canon RF-S 18-45mm F4.5-6.3 IS STM', #42 + 283 => 'Canon RF 100-400mm F5.6-8 IS USM', #42 + 284 => 'Canon RF 100-400mm F5.6-8 IS USM + RF1.4x', #42 + 285 => 'Canon RF 100-400mm F5.6-8 IS USM + RF2x', #42 + 286 => 'Canon RF-S 18-150mm F3.5-6.3 IS STM', #42 + 287 => 'Canon RF 24mm F1.8 MACRO IS STM', #42 + 288 => 'Canon RF 16mm F2.8 STM', #42 + 289 => 'Canon RF 400mm F2.8L IS USM', #IB + 290 => 'Canon RF 400mm F2.8L IS USM + RF1.4x', #IB + 291 => 'Canon RF 400mm F2.8L IS USM + RF2x', #IB + 292 => 'Canon RF 600mm F4L IS USM', #GiaZopatti + 293 => 'Canon RF 600mm F4L IS USM + RF1.4x', #42 + 294 => 'Canon RF 600mm F4L IS USM + RF2x', #42 + 295 => 'Canon RF 800mm F5.6L IS USM', #42 + 296 => 'Canon RF 800mm F5.6L IS USM + RF1.4x', #42 + 297 => 'Canon RF 800mm F5.6L IS USM + RF2x', #42 + 298 => 'Canon RF 1200mm F8L IS USM', #42 + 299 => 'Canon RF 1200mm F8L IS USM + RF1.4x', #42 + 300 => 'Canon RF 1200mm F8L IS USM + RF2x', #42 + 302 => 'Canon RF 15-30mm F4.5-6.3 IS STM', #42 + 303 => 'Canon RF 135mm F1.8 L IS USM', #42 + 304 => 'Canon RF 24-50mm F4.5-6.3 IS STM', #42 + 305 => 'Canon RF-S 55-210mm F5-7.1 IS STM', #42 + 306 => 'Canon RF 100-300mm F2.8L IS USM', #42 + 307 => 'Canon RF 100-300mm F2.8L IS USM + RF1.4x', #42 + 308 => 'Canon RF 100-300mm F2.8L IS USM + RF2x', #42 + 313 => 'Canon RF 28mm F2.8 STM', #42 + # Note: add new RF lenses to %canonLensTypes with ID 61182 + }, + }, +); + +# Internal serial number information (MakerNotes tag 0x96) (ref PH) +%Image::ExifTool::Canon::SerialInfo = ( + %binaryDataAttrs, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 9 => { + Name => 'InternalSerialNumber', + Format => 'string', + }, +); + +# Cropping information (MakerNotes tag 0x98) (ref PH) +%Image::ExifTool::Canon::CropInfo = ( + %binaryDataAttrs, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => 'CropLeftMargin', # (NC, may be right) + 1 => 'CropRightMargin', + 2 => 'CropTopMargin', # (NC, may be bottom) + 3 => 'CropBottomMargin', +); + +# Aspect ratio information (MakerNotes tag 0x9a) (ref PH) +%Image::ExifTool::Canon::AspectInfo = ( + %binaryDataAttrs, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'AspectRatio', + PrintConv => { + 0 => '3:2', + 1 => '1:1', + 2 => '4:3', + 7 => '16:9', + 8 => '4:5', + 12 => '3:2 (APS-H crop)', #IB + 13 => '3:2 (APS-C crop)', #IB + 258 => '4:3 crop', #PH (NC) + }, + }, + # (could use better names for these, or the Crop tags above, or both) + 1 => 'CroppedImageWidth', + 2 => 'CroppedImageHeight', + 3 => 'CroppedImageLeft', #forum4138 + 4 => 'CroppedImageTop', #ditto +); + +# Color information (MakerNotes tag 0xa0) +%Image::ExifTool::Canon::Processing = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 1 => { #PH + Name => 'ToneCurve', + PrintConv => { + 0 => 'Standard', + 1 => 'Manual', + 2 => 'Custom', + }, + }, + 2 => { #12 + Name => 'Sharpness', + Notes => 'all models except the 20D and 350D', + Condition => '$$self{Model} !~ /\b(20D|350D|REBEL XT|Kiss Digital N)\b/', + Priority => 0, # (maybe not as reliable as other sharpness values) + }, + 3 => { #PH + Name => 'SharpnessFrequency', # PatternSharpness? + PrintConvColumns => 2, + PrintConv => { + 0 => 'n/a', + 1 => 'Lowest', + 2 => 'Low', + 3 => 'Standard', + 4 => 'High', + 5 => 'Highest', + }, + }, + 4 => 'SensorRedLevel', #PH + 5 => 'SensorBlueLevel', #PH + 6 => 'WhiteBalanceRed', #PH + 7 => 'WhiteBalanceBlue', #PH + 8 => { #PH + Name => 'WhiteBalance', + RawConv => '$val < 0 ? undef : $val', + PrintConv => \%canonWhiteBalance, + SeparateTable => 1, + }, + 9 => 'ColorTemperature', #6 + 10 => { #12 + Name => 'PictureStyle', + Flags => ['PrintHex','SeparateTable'], + PrintConv => \%pictureStyles, + }, + 11 => { #PH + Name => 'DigitalGain', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 12 => { #PH + Name => 'WBShiftAB', + Notes => 'positive is a shift toward amber', + }, + 13 => { #PH + Name => 'WBShiftGM', + Notes => 'positive is a shift toward green', + }, +); + +# Color balance information (MakerNotes tag 0xa9) (ref PH) +%Image::ExifTool::Canon::ColorBalance = ( + %binaryDataAttrs, + NOTES => 'These tags are used by the 10D and 300D.', + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # red,green1,green2,blue (ref 2) + 1 => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 5 => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 9 => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 13 => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 17 => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 21 => { Name => 'WB_RGGBLevelsFluorescent',Format => 'int16s[4]' }, + 25 => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 29 => [{ + Name => 'WB_RGGBLevelsCustom', + Notes => 'black levels for the D60', + Condition => '$$self{Model} !~ /EOS D60\b/', + Format => 'int16s[4]', + },{ # (black levels for D60, ref IB) + Name => 'BlackLevels', + Format => 'int16s[4]', + }], + 33 => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 37 => { Name => 'WB_RGGBBlackLevels', Format => 'int16s[4]' }, #IB +); + +# Measured color levels (MakerNotes tag 0xaa) (ref 37) +%Image::ExifTool::Canon::MeasuredColor = ( + %binaryDataAttrs, + FORMAT => 'int16u', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 1 => { + # this is basically the inverse of WB_RGGBLevelsMeasured (ref 37) + Name => 'MeasuredRGGB', + Format => 'int16u[4]', + }, + # 5 - observed values: 0, 1 - PH +); + +# Flags information (MakerNotes tag 0xb0) (ref PH) +%Image::ExifTool::Canon::Flags = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 1 => 'ModifiedParamFlag', +); + +# Modified information (MakerNotes tag 0xb1) (ref PH) +%Image::ExifTool::Canon::ModifiedInfo = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 1 => { + Name => 'ModifiedToneCurve', + PrintConv => { + 0 => 'Standard', + 1 => 'Manual', + 2 => 'Custom', + }, + }, + 2 => { + Name => 'ModifiedSharpness', + Notes => '1D and 5D only', + Condition => '$$self{Model} =~ /\b(1D|5D)/', + }, + 3 => { + Name => 'ModifiedSharpnessFreq', # ModifiedPatternSharpness? + PrintConv => { + 0 => 'n/a', + 1 => 'Lowest', + 2 => 'Low', + 3 => 'Standard', + 4 => 'High', + 5 => 'Highest', + }, + }, + 4 => 'ModifiedSensorRedLevel', + 5 => 'ModifiedSensorBlueLevel', + 6 => 'ModifiedWhiteBalanceRed', + 7 => 'ModifiedWhiteBalanceBlue', + 8 => { + Name => 'ModifiedWhiteBalance', + PrintConv => \%canonWhiteBalance, + SeparateTable => 'WhiteBalance', + }, + 9 => 'ModifiedColorTemp', + 10 => { + Name => 'ModifiedPictureStyle', + PrintHex => 1, + SeparateTable => 'PictureStyle', + PrintConv => \%pictureStyles, + }, + 11 => { + Name => 'ModifiedDigitalGain', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, +); + +# Preview image information (MakerNotes tag 0xb6) +# - The 300D writes a 1536x1024 preview image that is accessed +# through this information - decoded by PH 12/14/03 +%Image::ExifTool::Canon::PreviewImageInfo = ( + %binaryDataAttrs, + FORMAT => 'int32u', + FIRST_ENTRY => 1, + IS_OFFSET => [ 5 ], # tag 5 is 'IsOffset' + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, +# the size of the preview block in 2-byte increments +# 0 => { +# Name => 'PreviewImageInfoWords', +# }, + 1 => { + Name => 'PreviewQuality', + PrintConv => \%canonQuality, + }, + 2 => { + Name => 'PreviewImageLength', + OffsetPair => 5, # point to associated offset + DataTag => 'PreviewImage', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 3 => 'PreviewImageWidth', + 4 => 'PreviewImageHeight', + 5 => { + Name => 'PreviewImageStart', + Flags => 'IsOffset', + OffsetPair => 2, # associated byte count tagID + DataTag => 'PreviewImage', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + # NOTE: The size of the PreviewImageInfo structure is incorrectly + # written as 48 bytes (Count=12, Format=int32u), but only the first + # 6 int32u values actually exist +); + +# Sensor information (MakerNotes tag 0xe0) (ref 12) +%Image::ExifTool::Canon::SensorInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + # Note: Don't make these writable because it confuses Canon decoding software + # if these are changed + 1 => 'SensorWidth', + 2 => 'SensorHeight', + 5 => 'SensorLeftBorder', #2 + 6 => 'SensorTopBorder', #2 + 7 => 'SensorRightBorder', #2 + 8 => 'SensorBottomBorder', #2 + 9 => { #22 + Name => 'BlackMaskLeftBorder', + Notes => q{ + coordinates for the area to the left or right of the image used to calculate + the average black level + }, + }, + 10 => 'BlackMaskTopBorder', #22 + 11 => 'BlackMaskRightBorder', #22 + 12 => 'BlackMaskBottomBorder', #22 +); + +# Color data (MakerNotes tag 0x4001, count=582) (ref 12) +%Image::ExifTool::Canon::ColorData1 = ( + %binaryDataAttrs, + NOTES => 'These tags are used by the 20D and 350D.', + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 0x4b ], + # 0x00: size of record in bytes - PH + # (dcraw 8.81 uses index 0x19 for WB) + 0x19 => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x1d => 'ColorTempAsShot', + 0x1e => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x22 => 'ColorTempAuto', + 0x23 => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0x27 => 'ColorTempDaylight', + 0x28 => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0x2c => 'ColorTempShade', + 0x2d => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0x31 => 'ColorTempCloudy', + 0x32 => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0x36 => 'ColorTempTungsten', + 0x37 => { Name => 'WB_RGGBLevelsFluorescent', Format => 'int16s[4]' }, + 0x3b => 'ColorTempFluorescent', + 0x3c => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0x40 => 'ColorTempFlash', + 0x41 => { Name => 'WB_RGGBLevelsCustom1', Format => 'int16s[4]' }, + 0x45 => 'ColorTempCustom1', + 0x46 => { Name => 'WB_RGGBLevelsCustom2', Format => 'int16s[4]' }, + 0x4a => 'ColorTempCustom2', + 0x4b => { #PH + Name => 'ColorCalib', + Format => 'undef[120]', + Unknown => 1, # (all tags are unknown, so we can avoid processing entire directory) + Notes => 'A, B, C, Temperature', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib' } + }, +); + +# Color data (MakerNotes tag 0x4001, count=653) (ref 12) +%Image::ExifTool::Canon::ColorData2 = ( + %binaryDataAttrs, + NOTES => 'These tags are used by the 1DmkII and 1DSmkII.', + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 0xa4 ], + 0x18 => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x1c => 'ColorTempAuto', + 0x1d => { Name => 'WB_RGGBLevelsUnknown', Format => 'int16s[4]', Unknown => 1 }, + 0x21 => { Name => 'ColorTempUnknown', Unknown => 1 }, + # (dcraw 8.81 uses index 0x22 for WB) + 0x22 => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x26 => 'ColorTempAsShot', + 0x27 => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0x2b => 'ColorTempDaylight', + 0x2c => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0x30 => 'ColorTempShade', + 0x31 => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0x35 => 'ColorTempCloudy', + 0x36 => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0x3a => 'ColorTempTungsten', + 0x3b => { Name => 'WB_RGGBLevelsFluorescent',Format => 'int16s[4]' }, + 0x3f => 'ColorTempFluorescent', + 0x40 => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 0x44 => 'ColorTempKelvin', + 0x45 => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0x49 => 'ColorTempFlash', + 0x4a => { Name => 'WB_RGGBLevelsUnknown2', Format => 'int16s[4]', Unknown => 1 }, + 0x4e => { Name => 'ColorTempUnknown2', Unknown => 1 }, + 0x4f => { Name => 'WB_RGGBLevelsUnknown3', Format => 'int16s[4]', Unknown => 1 }, + 0x53 => { Name => 'ColorTempUnknown3', Unknown => 1 }, + 0x54 => { Name => 'WB_RGGBLevelsUnknown4', Format => 'int16s[4]', Unknown => 1 }, + 0x58 => { Name => 'ColorTempUnknown4', Unknown => 1 }, + 0x59 => { Name => 'WB_RGGBLevelsUnknown5', Format => 'int16s[4]', Unknown => 1 }, + 0x5d => { Name => 'ColorTempUnknown5', Unknown => 1 }, + 0x5e => { Name => 'WB_RGGBLevelsUnknown6', Format => 'int16s[4]', Unknown => 1 }, + 0x62 => { Name => 'ColorTempUnknown6', Unknown => 1 }, + 0x63 => { Name => 'WB_RGGBLevelsUnknown7', Format => 'int16s[4]', Unknown => 1 }, + 0x67 => { Name => 'ColorTempUnknown7', Unknown => 1 }, + 0x68 => { Name => 'WB_RGGBLevelsUnknown8', Format => 'int16s[4]', Unknown => 1 }, + 0x6c => { Name => 'ColorTempUnknown8', Unknown => 1 }, + 0x6d => { Name => 'WB_RGGBLevelsUnknown9', Format => 'int16s[4]', Unknown => 1 }, + 0x71 => { Name => 'ColorTempUnknown9', Unknown => 1 }, + 0x72 => { Name => 'WB_RGGBLevelsUnknown10', Format => 'int16s[4]', Unknown => 1 }, + 0x76 => { Name => 'ColorTempUnknown10', Unknown => 1 }, + 0x77 => { Name => 'WB_RGGBLevelsUnknown11', Format => 'int16s[4]', Unknown => 1 }, + 0x7b => { Name => 'ColorTempUnknown11', Unknown => 1 }, + 0x7c => { Name => 'WB_RGGBLevelsUnknown12', Format => 'int16s[4]', Unknown => 1 }, + 0x80 => { Name => 'ColorTempUnknown12', Unknown => 1 }, + 0x81 => { Name => 'WB_RGGBLevelsUnknown13', Format => 'int16s[4]', Unknown => 1 }, + 0x85 => { Name => 'ColorTempUnknown13', Unknown => 1 }, + 0x86 => { Name => 'WB_RGGBLevelsUnknown14', Format => 'int16s[4]', Unknown => 1 }, + 0x8a => { Name => 'ColorTempUnknown14', Unknown => 1 }, + 0x8b => { Name => 'WB_RGGBLevelsUnknown15', Format => 'int16s[4]', Unknown => 1 }, + 0x8f => { Name => 'ColorTempUnknown15', Unknown => 1 }, + 0x90 => { Name => 'WB_RGGBLevelsPC1', Format => 'int16s[4]' }, + 0x94 => 'ColorTempPC1', + 0x95 => { Name => 'WB_RGGBLevelsPC2', Format => 'int16s[4]' }, + 0x99 => 'ColorTempPC2', + 0x9a => { Name => 'WB_RGGBLevelsPC3', Format => 'int16s[4]' }, + 0x9e => 'ColorTempPC3', + 0x9f => { Name => 'WB_RGGBLevelsUnknown16', Format => 'int16s[4]', Unknown => 1 }, + 0xa3 => { Name => 'ColorTempUnknown16', Unknown => 1 }, + 0xa4 => { #PH + Name => 'ColorCalib', + Format => 'undef[120]', + Unknown => 1, + Notes => 'A, B, C, Temperature', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib' } + }, + 0x26a => { #PH + Name => 'RawMeasuredRGGB', + Format => 'int32u[4]', + Notes => 'raw MeasuredRGGB values, before normalization', + # swap words because the word ordering is big-endian, opposite to the byte ordering + ValueConv => \&SwapWords, + ValueConvInv => \&SwapWords, + }, +); + +# Color data (MakerNotes tag 0x4001, count=796) (ref 12) +%Image::ExifTool::Canon::ColorData3 = ( + %binaryDataAttrs, + NOTES => 'These tags are used by the 1DmkIIN, 5D, 30D and 400D.', + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 0x85 ], + 0x00 => { #PH + Name => 'ColorDataVersion', + PrintConv => { + 1 => '1 (1DmkIIN/5D/30D/400D)', + }, + }, + # 0x01-0x3e: RGGB coefficients, apparently specific to the + # individual camera and possibly used for color calibration (ref 37) + # (dcraw 8.81 uses index 0x3f for WB) + 0x3f => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x43 => 'ColorTempAsShot', + 0x44 => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x48 => 'ColorTempAuto', + # not sure exactly what 'Measured' values mean... + 0x49 => { Name => 'WB_RGGBLevelsMeasured', Format => 'int16s[4]' }, + 0x4d => 'ColorTempMeasured', + 0x4e => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0x52 => 'ColorTempDaylight', + 0x53 => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0x57 => 'ColorTempShade', + 0x58 => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0x5c => 'ColorTempCloudy', + 0x5d => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0x61 => 'ColorTempTungsten', + 0x62 => { Name => 'WB_RGGBLevelsFluorescent', Format => 'int16s[4]' }, + 0x66 => 'ColorTempFluorescent', + 0x67 => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 0x6b => 'ColorTempKelvin', + 0x6c => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0x70 => 'ColorTempFlash', + 0x71 => { Name => 'WB_RGGBLevelsPC1', Format => 'int16s[4]' }, + 0x75 => 'ColorTempPC1', + 0x76 => { Name => 'WB_RGGBLevelsPC2', Format => 'int16s[4]' }, + 0x7a => 'ColorTempPC2', + 0x7b => { Name => 'WB_RGGBLevelsPC3', Format => 'int16s[4]' }, + 0x7f => 'ColorTempPC3', + 0x80 => { Name => 'WB_RGGBLevelsCustom', Format => 'int16s[4]' }, + 0x84 => 'ColorTempCustom', + 0x85 => { #37 + Name => 'ColorCalib', + Format => 'undef[120]', + Unknown => 1, + Notes => 'B, C, A, Temperature', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib' } + }, + 0xc4 => { #IB + Name => 'PerChannelBlackLevel', + Format => 'int16u[4]', + }, + # 0xc8-0x1c7: some sort of color table (ref 37) + 0x248 => { #37 + Name => 'FlashOutput', + ValueConv => '$val >= 255 ? 255 : exp(($val-200)/16*log(2))', + ValueConvInv => '$val == 255 ? 255 : 200 + log($val)*16/log(2)', + PrintConv => '$val == 255 ? "Strobe or Misfire" : sprintf("%.0f%%", $val * 100)', + PrintConvInv => '$val =~ /^(\d(\.?\d*))/ ? $1 / 100 : 255', + }, + 0x249 => { #37 + Name => 'FlashBatteryLevel', + # calibration points for external flash: 144=3.76V (almost empty), 192=5.24V (full) + # - have seen a value of 201 with internal flash + PrintConv => '$val ? sprintf("%.2fV", $val * 5 / 186) : "n/a"', + PrintConvInv => '$val=~/^(\d+\.\d+)\s*V?$/i ? int($val*186/5+0.5) : 0', + }, + 0x24a => { #37 + Name => 'ColorTempFlashData', + # 0 for no external flash, 35980 for 'Strobe or Misfire' + # (lower than ColorTempFlash by up to 200 degrees) + RawConv => '($val < 2000 or $val > 12000) ? undef : $val', + }, + # 0x24b: inverse relationship with flash power (ref 37) + # 0x286: has value 256 for correct exposure, less for under exposure (seen 96 minimum) (ref 37) + 0x287 => { #37 + Name => 'MeasuredRGGBData', + Format => 'int32u[4]', + Notes => 'MeasuredRGGB may be derived from these data values', + # swap words because the word ordering is big-endian, opposite to the byte ordering + ValueConv => \&SwapWords, + ValueConvInv => \&SwapWords, + }, + # 0x297: ranges from -10 to 30, higher for high ISO (ref 37) +); + +# Color data (MakerNotes tag 0x4001, count=674|692|702|1227|1250|1251|1337|1338|1346) (ref PH) +%Image::ExifTool::Canon::ColorData4 = ( + %binaryDataAttrs, + NOTES => q{ + These tags are used by the 1DmkIII, 1DSmkIII, 1DmkIV, 5DmkII, 7D, 40D, 50D, + 60D, 450D, 500D, 550D, 1000D and 1100D. + }, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 0x3f, 0xa8 ], + DATAMEMBER => [ 0x00 ], + 0x00 => { + Name => 'ColorDataVersion', + DataMember => 'ColorDataVersion', + RawConv => '$$self{ColorDataVersion} = $val', + PrintConv => { + 2 => '2 (1DmkIII)', + 3 => '3 (40D)', + 4 => '4 (1DSmkIII)', + 5 => '5 (450D/1000D)', + 6 => '6 (50D/5DmkII)', + 7 => '7 (500D/550D/7D/1DmkIV)', + 9 => '9 (60D/1100D)', + }, + }, + # 0x01-0x18: unknown RGGB coefficients (int16s[4]) (50D) + # (dcraw 8.81 uses index 0x3f for WB) + 0x3f => { + Name => 'ColorCoefs', + Format => 'undef[210]', # ColorTempUnknown11 is last entry + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCoefs' } + }, + 0xa8 => { + Name => 'ColorCalib', + Format => 'undef[120]', + Unknown => 1, + Notes => 'B, C, A, Temperature', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib' } + }, + 0x0e7 => { Name => 'AverageBlackLevel', Format => 'int16u[4]' }, #IB + 0x280 => { #PH + Name => 'RawMeasuredRGGB', + Format => 'int32u[4]', + Notes => 'raw MeasuredRGGB values, before normalization', + # swap words because the word ordering is big-endian, opposite to the byte ordering + ValueConv => \&SwapWords, + ValueConvInv => \&SwapWords, + }, + 0x2b4 => { #IB + Name => 'PerChannelBlackLevel', + Condition => '$$self{ColorDataVersion} == 4 or $$self{ColorDataVersion} == 5', + Format => 'int16u[4]', + }, + 0x2b8 => { #IB + Name => 'NormalWhiteLevel', + Condition => '$$self{ColorDataVersion} == 4 or $$self{ColorDataVersion} == 5', + Format => 'int16u', + RawConv => '$val || undef', + }, + 0x2b9 => { #IB + Name => 'SpecularWhiteLevel', + Condition => '$$self{ColorDataVersion} == 4 or $$self{ColorDataVersion} == 5', + Format => 'int16u', + }, + 0x2ba => { #IB + Name => 'LinearityUpperMargin', + Condition => '$$self{ColorDataVersion} == 4 or $$self{ColorDataVersion} == 5', + Format => 'int16u', + }, + 0x2cb => { #IB + Name => 'PerChannelBlackLevel', + Condition => '$$self{ColorDataVersion} == 6 or $$self{ColorDataVersion} == 7', + Format => 'int16u[4]', + }, + 0x2cf => [{ #IB + Name => 'NormalWhiteLevel', + Condition => '$$self{ColorDataVersion} == 6 or $$self{ColorDataVersion} == 7', + Format => 'int16u', + RawConv => '$val || undef', + },{ + Name => 'PerChannelBlackLevel', + Condition => '$$self{ColorDataVersion} == 9', + Format => 'int16u[4]', + }], + 0x2d0 => { #IB + Name => 'SpecularWhiteLevel', + Condition => '$$self{ColorDataVersion} == 6 or $$self{ColorDataVersion} == 7', + Format => 'int16u', + }, + 0x2d1 => { #IB + Name => 'LinearityUpperMargin', + Condition => '$$self{ColorDataVersion} == 6 or $$self{ColorDataVersion} == 7', + Format => 'int16u', + }, + 0x2d3 => { #IB + Name => 'NormalWhiteLevel', + Condition => '$$self{ColorDataVersion} == 9', + Format => 'int16u', + RawConv => '$val || undef', + }, + 0x2d4 => { #IB + Name => 'SpecularWhiteLevel', + Condition => '$$self{ColorDataVersion} == 9', + Format => 'int16u', + }, + 0x2d5 => { #IB + Name => 'LinearityUpperMargin', + Condition => '$$self{ColorDataVersion} == 9', + Format => 'int16u', + }, +); + +# color coefficients (ref PH) +%Image::ExifTool::Canon::ColorCoefs = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x00 => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x04 => 'ColorTempAsShot', + 0x05 => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x09 => 'ColorTempAuto', + 0x0a => { Name => 'WB_RGGBLevelsMeasured', Format => 'int16s[4]' }, + 0x0e => 'ColorTempMeasured', + # the following Unknown values are set for the 50D and 5DmkII, and the + # SRAW images of the 40D, and affect thumbnail display for the 50D/5DmkII + # and conversion for all modes of the 40D + 0x0f => { Name => 'WB_RGGBLevelsUnknown', Format => 'int16s[4]', Unknown => 1 }, + 0x13 => { Name => 'ColorTempUnknown', Unknown => 1 }, + 0x14 => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0x18 => 'ColorTempDaylight', + 0x19 => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0x1d => 'ColorTempShade', + 0x1e => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0x22 => 'ColorTempCloudy', + 0x23 => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0x27 => 'ColorTempTungsten', + 0x28 => { Name => 'WB_RGGBLevelsFluorescent',Format => 'int16s[4]' }, + 0x2c => 'ColorTempFluorescent', + # (changing the Kelvin values has no effect on image in DPP... why not?) + 0x2d => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 0x31 => 'ColorTempKelvin', + 0x32 => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0x36 => 'ColorTempFlash', + 0x37 => { Name => 'WB_RGGBLevelsUnknown2', Format => 'int16s[4]', Unknown => 1 }, + 0x3b => { Name => 'ColorTempUnknown2', Unknown => 1 }, + 0x3c => { Name => 'WB_RGGBLevelsUnknown3', Format => 'int16s[4]', Unknown => 1 }, + 0x40 => { Name => 'ColorTempUnknown3', Unknown => 1 }, + 0x41 => { Name => 'WB_RGGBLevelsUnknown4', Format => 'int16s[4]', Unknown => 1 }, + 0x45 => { Name => 'ColorTempUnknown4', Unknown => 1 }, + 0x46 => { Name => 'WB_RGGBLevelsUnknown5', Format => 'int16s[4]', Unknown => 1 }, + 0x4a => { Name => 'ColorTempUnknown5', Unknown => 1 }, + 0x4b => { Name => 'WB_RGGBLevelsUnknown6', Format => 'int16s[4]', Unknown => 1 }, + 0x4f => { Name => 'ColorTempUnknown6', Unknown => 1 }, + 0x50 => { Name => 'WB_RGGBLevelsUnknown7', Format => 'int16s[4]', Unknown => 1 }, + 0x54 => { Name => 'ColorTempUnknown7', Unknown => 1 }, + 0x55 => { Name => 'WB_RGGBLevelsUnknown8', Format => 'int16s[4]', Unknown => 1 }, + 0x59 => { Name => 'ColorTempUnknown8', Unknown => 1 }, + 0x5a => { Name => 'WB_RGGBLevelsUnknown9', Format => 'int16s[4]', Unknown => 1 }, + 0x5e => { Name => 'ColorTempUnknown9', Unknown => 1 }, + 0x5f => { Name => 'WB_RGGBLevelsUnknown10', Format => 'int16s[4]', Unknown => 1 }, + 0x63 => { Name => 'ColorTempUnknown10', Unknown => 1 }, + 0x64 => { Name => 'WB_RGGBLevelsUnknown11', Format => 'int16s[4]', Unknown => 1 }, + 0x68 => { Name => 'ColorTempUnknown11', Unknown => 1 }, + 0x69 => { Name => 'WB_RGGBLevelsUnknown12', Format => 'int16s[4]', Unknown => 1 }, + 0x6d => { Name => 'ColorTempUnknown12', Unknown => 1 }, + 0x6e => { Name => 'WB_RGGBLevelsUnknown13', Format => 'int16s[4]', Unknown => 1 }, + 0x72 => { Name => 'ColorTempUnknown13', Unknown => 1 }, +); + +# color coefficients (ref PH/IB) +%Image::ExifTool::Canon::ColorCoefs2 = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x00 => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x07 => 'ColorTempAsShot', + 0x08 => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x0f => 'ColorTempAuto', + 0x10 => { Name => 'WB_RGGBLevelsMeasured', Format => 'int16s[4]' }, + 0x17 => 'ColorTempMeasured', + 0x18 => { Name => 'WB_RGGBLevelsUnknown', Format => 'int16s[4]', Unknown => 1 }, + 0x1f => { Name => 'ColorTempUnknown', Unknown => 1 }, + 0x20 => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0x27 => 'ColorTempDaylight', + 0x28 => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0x2f => 'ColorTempShade', + 0x30 => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0x37 => 'ColorTempCloudy', + 0x38 => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0x3f => 'ColorTempTungsten', + 0x40 => { Name => 'WB_RGGBLevelsFluorescent',Format => 'int16s[4]' }, + 0x47 => 'ColorTempFluorescent', + 0x48 => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 0x4f => 'ColorTempKelvin', + 0x50 => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0x57 => 'ColorTempFlash', + 0x58 => { Name => 'WB_RGGBLevelsUnknown2', Format => 'int16s[4]', Unknown => 1 }, + 0x5f => { Name => 'ColorTempUnknown2', Unknown => 1 }, + 0x60 => { Name => 'WB_RGGBLevelsUnknown3', Format => 'int16s[4]', Unknown => 1 }, + 0x67 => { Name => 'ColorTempUnknown3', Unknown => 1 }, + 0x68 => { Name => 'WB_RGGBLevelsUnknown4', Format => 'int16s[4]', Unknown => 1 }, + 0x6f => { Name => 'ColorTempUnknown4', Unknown => 1 }, + 0x70 => { Name => 'WB_RGGBLevelsUnknown5', Format => 'int16s[4]', Unknown => 1 }, + 0x77 => { Name => 'ColorTempUnknown5', Unknown => 1 }, + 0x78 => { Name => 'WB_RGGBLevelsUnknown6', Format => 'int16s[4]', Unknown => 1 }, + 0x7f => { Name => 'ColorTempUnknown6', Unknown => 1 }, + 0x80 => { Name => 'WB_RGGBLevelsUnknown7', Format => 'int16s[4]', Unknown => 1 }, + 0x87 => { Name => 'ColorTempUnknown7', Unknown => 1 }, + 0x88 => { Name => 'WB_RGGBLevelsUnknown8', Format => 'int16s[4]', Unknown => 1 }, + 0x8f => { Name => 'ColorTempUnknown8', Unknown => 1 }, + 0x90 => { Name => 'WB_RGGBLevelsUnknown9', Format => 'int16s[4]', Unknown => 1 }, + 0x97 => { Name => 'ColorTempUnknown9', Unknown => 1 }, + 0x98 => { Name => 'WB_RGGBLevelsUnknown10', Format => 'int16s[4]', Unknown => 1 }, + 0x9f => { Name => 'ColorTempUnknown10', Unknown => 1 }, + 0xa0 => { Name => 'WB_RGGBLevelsUnknown11', Format => 'int16s[4]', Unknown => 1 }, + 0xa7 => { Name => 'ColorTempUnknown11', Unknown => 1 }, + 0xa8 => { Name => 'WB_RGGBLevelsUnknown12', Format => 'int16s[4]', Unknown => 1 }, + 0xaf => { Name => 'ColorTempUnknown12', Unknown => 1 }, + 0xb0 => { Name => 'WB_RGGBLevelsUnknown13', Format => 'int16s[4]', Unknown => 1 }, + 0xb7 => { Name => 'ColorTempUnknown13', Unknown => 1 }, +); + +# color calibration (ref 37) +%Image::ExifTool::Canon::ColorCalib = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # these coefficients are in a different order compared to older + # models (A,B,C in ColorData1/2 vs. C,A,B in ColorData3/4) - PH + # Coefficient A most closely matches the blue curvature, and + # coefficient B most closely matches the red curvature, but the match + # is not perfect, and I don't know what coefficient C is for (certainly + # not a green coefficient) - PH + NOTES => q{ + Camera color calibration data. For the 20D, 350D, 1DmkII and 1DSmkII the + order of the coefficients is A, B, C, Temperature, but for newer models it + is B, C, A, Temperature. These tags are extracted only when the L<Unknown|../ExifTool.html#Unknown> + option is used. + }, + 0x00 => { Name => 'CameraColorCalibration01', %cameraColorCalibration }, + 0x04 => { Name => 'CameraColorCalibration02', %cameraColorCalibration }, + 0x08 => { Name => 'CameraColorCalibration03', %cameraColorCalibration }, + 0x0c => { Name => 'CameraColorCalibration04', %cameraColorCalibration }, + 0x10 => { Name => 'CameraColorCalibration05', %cameraColorCalibration }, + 0x14 => { Name => 'CameraColorCalibration06', %cameraColorCalibration }, + 0x18 => { Name => 'CameraColorCalibration07', %cameraColorCalibration }, + 0x1c => { Name => 'CameraColorCalibration08', %cameraColorCalibration }, + 0x20 => { Name => 'CameraColorCalibration09', %cameraColorCalibration }, + 0x24 => { Name => 'CameraColorCalibration10', %cameraColorCalibration }, + 0x28 => { Name => 'CameraColorCalibration11', %cameraColorCalibration }, + 0x2c => { Name => 'CameraColorCalibration12', %cameraColorCalibration }, + 0x30 => { Name => 'CameraColorCalibration13', %cameraColorCalibration }, + 0x34 => { Name => 'CameraColorCalibration14', %cameraColorCalibration }, + 0x38 => { Name => 'CameraColorCalibration15', %cameraColorCalibration }, +); + +# color calibration2 +%Image::ExifTool::Canon::ColorCalib2 = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'B, C, A, D, Temperature.', + 0x00 => { Name => 'CameraColorCalibration01', %cameraColorCalibration2 }, + 0x05 => { Name => 'CameraColorCalibration02', %cameraColorCalibration2 }, + 0x0a => { Name => 'CameraColorCalibration03', %cameraColorCalibration2 }, + 0x0f => { Name => 'CameraColorCalibration04', %cameraColorCalibration2 }, + 0x14 => { Name => 'CameraColorCalibration05', %cameraColorCalibration2 }, + 0x19 => { Name => 'CameraColorCalibration06', %cameraColorCalibration2 }, + 0x1e => { Name => 'CameraColorCalibration07', %cameraColorCalibration2 }, + 0x23 => { Name => 'CameraColorCalibration08', %cameraColorCalibration2 }, + 0x28 => { Name => 'CameraColorCalibration09', %cameraColorCalibration2 }, + 0x2d => { Name => 'CameraColorCalibration10', %cameraColorCalibration2 }, + 0x32 => { Name => 'CameraColorCalibration11', %cameraColorCalibration2 }, + 0x37 => { Name => 'CameraColorCalibration12', %cameraColorCalibration2 }, + 0x3c => { Name => 'CameraColorCalibration13', %cameraColorCalibration2 }, + 0x41 => { Name => 'CameraColorCalibration14', %cameraColorCalibration2 }, + 0x46 => { Name => 'CameraColorCalibration15', %cameraColorCalibration2 }, +); + +# Color data (MakerNotes tag 0x4001, count=5120) (ref PH) +%Image::ExifTool::Canon::ColorData5 = ( + %binaryDataAttrs, + NOTES => 'These tags are used by many EOS M and PowerShot models.', + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0x00 ], + IS_SUBDIR => [ 0x47, 0xba, 0xff ], + 0x00 => { + Name => 'ColorDataVersion', + DataMember => 'ColorDataVersion', + RawConv => '$$self{ColorDataVersion} = $val', + PrintConv => { + -3 => '-3 (M10/M3)', # (and PowerShot G1X/G1XmkII/G10/G11/G12/G15/G16/G3X/G5X/G7X/G9X/S100/S110/S120/S90/S95/SX1IS/SX50HS/SX60HS) + -4 => '-4 (M100/M5/M6)', # (and PowerShot G1XmkIII/G7XmkII/G9XmkII) + }, + }, + 0x47 => [{ + Name => 'ColorCoefs', + Condition => '$$self{ColorDataVersion} == -3', + Format => 'undef[230]', # ColorTempUnknown13 is last entry + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCoefs' } + },{ + Name => 'ColorCoefs2', + Condition => '$$self{ColorDataVersion} == -4', + Format => 'undef[368]', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCoefs2' } + }], + 0xba => { + Name => 'ColorCalib2', + Condition => '$$self{ColorDataVersion} == -3', + Format => 'undef[150]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib2' } + }, + 0xff => { + Name => 'ColorCalib2', + Condition => '$$self{ColorDataVersion} == -4', + Format => 'undef[150]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib2' } + }, + 0x108=> { #IB + Name => 'PerChannelBlackLevel', + Condition => '$$self{ColorDataVersion} == -3', + Format => 'int16s[4]', + }, + 0x14d=> { #IB + Name => 'PerChannelBlackLevel', + Condition => '$$self{ColorDataVersion} == -4', + Format => 'int16s[4]', + }, + 0x0569 => { #PH (NC) + Name => 'NormalWhiteLevel', + Condition => '$$self{ColorDataVersion} == -4', + Format => 'int16u', + }, + 0x056a => { #PH (NC) + Name => 'SpecularWhiteLevel', + Condition => '$$self{ColorDataVersion} == -4', + Format => 'int16u', + }, +); + +# Color data (MakerNotes tag 0x4001, count=1273|1275) (ref PH) +%Image::ExifTool::Canon::ColorData6 = ( + %binaryDataAttrs, + NOTES => 'These tags are used by the EOS 600D and 1200D.', + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 0xbc ], + 0x00 => { + Name => 'ColorDataVersion', + PrintConv => { + 10 => '10 (600D/1200D)', + }, + }, + 0x3f => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x43 => 'ColorTempAsShot', + 0x44 => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x48 => 'ColorTempAuto', + 0x49 => { Name => 'WB_RGGBLevelsMeasured', Format => 'int16s[4]' }, + 0x4d => 'ColorTempMeasured', + 0x4e => { Name => 'WB_RGGBLevelsUnknown', Format => 'int16s[4]', Unknown => 1 }, + 0x52 => { Name => 'ColorTempUnknown', Unknown => 1 }, + 0x53 => { Name => 'WB_RGGBLevelsUnknown2', Format => 'int16s[4]', Unknown => 1 }, + 0x57 => { Name => 'ColorTempUnknown2', Unknown => 1 }, + 0x58 => { Name => 'WB_RGGBLevelsUnknown3', Format => 'int16s[4]', Unknown => 1 }, + 0x5c => { Name => 'ColorTempUnknown3', Unknown => 1 }, + 0x5d => { Name => 'WB_RGGBLevelsUnknown4', Format => 'int16s[4]', Unknown => 1 }, + 0x61 => { Name => 'ColorTempUnknown4', Unknown => 1 }, + 0x62 => { Name => 'WB_RGGBLevelsUnknown5', Format => 'int16s[4]', Unknown => 1 }, + 0x66 => { Name => 'ColorTempUnknown5', Unknown => 1 }, + 0x67 => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0x6b => 'ColorTempDaylight', + 0x6c => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0x70 => 'ColorTempShade', + 0x71 => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0x75 => 'ColorTempCloudy', + 0x76 => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0x7a => 'ColorTempTungsten', + 0x7b => { Name => 'WB_RGGBLevelsFluorescent',Format => 'int16s[4]' }, + 0x7f => 'ColorTempFluorescent', + 0x80 => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 0x84 => 'ColorTempKelvin', + 0x85 => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0x89 => 'ColorTempFlash', + 0x8a => { Name => 'WB_RGGBLevelsUnknown6', Format => 'int16s[4]', Unknown => 1 }, + 0x8e => { Name => 'ColorTempUnknown6', Unknown => 1 }, + 0x8f => { Name => 'WB_RGGBLevelsUnknown7', Format => 'int16s[4]', Unknown => 1 }, + 0x93 => { Name => 'ColorTempUnknown7', Unknown => 1 }, + 0x94 => { Name => 'WB_RGGBLevelsUnknown8', Format => 'int16s[4]', Unknown => 1 }, + 0x98 => { Name => 'ColorTempUnknown8', Unknown => 1 }, + 0x99 => { Name => 'WB_RGGBLevelsUnknown9', Format => 'int16s[4]', Unknown => 1 }, + 0x9d => { Name => 'ColorTempUnknown9', Unknown => 1 }, + 0x9e => { Name => 'WB_RGGBLevelsUnknown10', Format => 'int16s[4]', Unknown => 1 }, + 0xa2 => { Name => 'ColorTempUnknown10', Unknown => 1 }, + 0xa3 => { Name => 'WB_RGGBLevelsUnknown11', Format => 'int16s[4]', Unknown => 1 }, + 0xa7 => { Name => 'ColorTempUnknown11', Unknown => 1 }, + 0xa8 => { Name => 'WB_RGGBLevelsUnknown12', Format => 'int16s[4]', Unknown => 1 }, + 0xac => { Name => 'ColorTempUnknown12', Unknown => 1 }, + 0xad => { Name => 'WB_RGGBLevelsUnknown13', Format => 'int16s[4]', Unknown => 1 }, + 0xb1 => { Name => 'ColorTempUnknown13', Unknown => 1 }, + 0xb2 => { Name => 'WB_RGGBLevelsUnknown14', Format => 'int16s[4]', Unknown => 1 }, + 0xb6 => { Name => 'ColorTempUnknown14', Unknown => 1 }, + 0xb7 => { Name => 'WB_RGGBLevelsUnknown15', Format => 'int16s[4]', Unknown => 1 }, + 0xbb => { Name => 'ColorTempUnknown15', Unknown => 1 }, + 0xbc => { + Name => 'ColorCalib', + Format => 'undef[120]', + Unknown => 1, + Notes => 'B, C, A, Temperature', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib' } + }, + 0x0fb => { Name => 'AverageBlackLevel', Format => 'int16u[4]' }, #IB + 0x194 => { #PH + Name => 'RawMeasuredRGGB', + Format => 'int32u[4]', + Notes => 'raw MeasuredRGGB values, before normalization', + # swap words because the word ordering is big-endian, opposite to the byte ordering + ValueConv => \&SwapWords, + ValueConvInv => \&SwapWords, + }, + 0x1df => { Name => 'PerChannelBlackLevel', Format => 'int16u[4]' }, #IB + 0x1e3 => { Name => 'NormalWhiteLevel', Format => 'int16u', RawConv => '$val || undef' }, #IB + 0x1e4 => { Name => 'SpecularWhiteLevel', Format => 'int16u' }, #IB + 0x1e5 => { Name => 'LinearityUpperMargin', Format => 'int16u' }, #IB +); + +# Color data (MakerNotes tag 0x4001, count=1312,1313,1316) (ref PH) +%Image::ExifTool::Canon::ColorData7 = ( + %binaryDataAttrs, + NOTES => q{ + These tags are used by the EOS 1DX, 5DmkIII, 6D, 7DmkII, 100D, 650D, 700D, + 8000D, M and M2. + }, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0x00 ], + IS_SUBDIR => [ 0xd5 ], + 0x00 => { + Name => 'ColorDataVersion', + DataMember => 'ColorDataVersion', + RawConv => '$$self{ColorDataVersion} = $val', + PrintConv => { + 10 => '10 (1DX/5DmkIII/6D/70D/100D/650D/700D/M/M2)', + 11 => '11 (7DmkII/750D/760D/8000D)', + }, + }, + # not really sure about the AsShot, Auto and Measured values any more - PH + 0x3f => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x43 => 'ColorTempAsShot', + 0x44 => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x48 => 'ColorTempAuto', + 0x49 => { Name => 'WB_RGGBLevelsMeasured', Format => 'int16s[4]' }, + 0x4d => 'ColorTempMeasured', + 0x4e => { Name => 'WB_RGGBLevelsUnknown', Format => 'int16s[4]', Unknown => 1 }, + 0x52 => { Name => 'ColorTempUnknown', Unknown => 1 }, + 0x53 => { Name => 'WB_RGGBLevelsUnknown2', Format => 'int16s[4]', Unknown => 1 }, + 0x57 => { Name => 'ColorTempUnknown2', Unknown => 1 }, + 0x58 => { Name => 'WB_RGGBLevelsUnknown3', Format => 'int16s[4]', Unknown => 1 }, + 0x5c => { Name => 'ColorTempUnknown3', Unknown => 1 }, + 0x5d => { Name => 'WB_RGGBLevelsUnknown4', Format => 'int16s[4]', Unknown => 1 }, + 0x61 => { Name => 'ColorTempUnknown4', Unknown => 1 }, + 0x62 => { Name => 'WB_RGGBLevelsUnknown5', Format => 'int16s[4]', Unknown => 1 }, + 0x66 => { Name => 'ColorTempUnknown5', Unknown => 1 }, + 0x67 => { Name => 'WB_RGGBLevelsUnknown6', Format => 'int16s[4]', Unknown => 1 }, + 0x6b => { Name => 'ColorTempUnknown6', Unknown => 1 }, + 0x6c => { Name => 'WB_RGGBLevelsUnknown7', Format => 'int16s[4]', Unknown => 1 }, + 0x70 => { Name => 'ColorTempUnknown7', Unknown => 1 }, + 0x71 => { Name => 'WB_RGGBLevelsUnknown8', Format => 'int16s[4]', Unknown => 1 }, + 0x75 => { Name => 'ColorTempUnknown8', Unknown => 1 }, + 0x76 => { Name => 'WB_RGGBLevelsUnknown9', Format => 'int16s[4]', Unknown => 1 }, + 0x7a => { Name => 'ColorTempUnknown9', Unknown => 1 }, + 0x7b => { Name => 'WB_RGGBLevelsUnknown10', Format => 'int16s[4]', Unknown => 1 }, + 0x7f => { Name => 'ColorTempUnknown10', Unknown => 1 }, + 0x80 => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0x84 => 'ColorTempDaylight', + 0x85 => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0x89 => 'ColorTempShade', + 0x8a => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0x8e => 'ColorTempCloudy', + 0x8f => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0x93 => 'ColorTempTungsten', + 0x94 => { Name => 'WB_RGGBLevelsFluorescent',Format => 'int16s[4]' }, + 0x98 => 'ColorTempFluorescent', + 0x99 => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 0x9d => 'ColorTempKelvin', + 0x9e => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0xa2 => 'ColorTempFlash', + 0xa3 => { Name => 'WB_RGGBLevelsUnknown11', Format => 'int16s[4]', Unknown => 1 }, + 0xa7 => { Name => 'ColorTempUnknown11', Unknown => 1 }, + 0xa8 => { Name => 'WB_RGGBLevelsUnknown12', Format => 'int16s[4]', Unknown => 1 }, + 0xac => { Name => 'ColorTempUnknown12', Unknown => 1 }, + 0xad => { Name => 'WB_RGGBLevelsUnknown13', Format => 'int16s[4]', Unknown => 1 }, + 0xb1 => { Name => 'ColorTempUnknown13', Unknown => 1 }, + 0xb2 => { Name => 'WB_RGGBLevelsUnknown14', Format => 'int16s[4]', Unknown => 1 }, + 0xb6 => { Name => 'ColorTempUnknown14', Unknown => 1 }, + 0xb7 => { Name => 'WB_RGGBLevelsUnknown15', Format => 'int16s[4]', Unknown => 1 }, + 0xbb => { Name => 'ColorTempUnknown15', Unknown => 1 }, + 0xbc => { Name => 'WB_RGGBLevelsUnknown16', Format => 'int16s[4]', Unknown => 1 }, + 0xc0 => { Name => 'ColorTempUnknown16', Unknown => 1 }, + 0xc1 => { Name => 'WB_RGGBLevelsUnknown17', Format => 'int16s[4]', Unknown => 1 }, + 0xc5 => { Name => 'ColorTempUnknown17', Unknown => 1 }, + 0xc6 => { Name => 'WB_RGGBLevelsUnknown18', Format => 'int16s[4]', Unknown => 1 }, + 0xca => { Name => 'ColorTempUnknown18', Unknown => 1 }, + 0xcb => { Name => 'WB_RGGBLevelsUnknown19', Format => 'int16s[4]', Unknown => 1 }, + 0xcf => { Name => 'ColorTempUnknown19', Unknown => 1 }, + 0xd0 => { Name => 'WB_RGGBLevelsUnknown20', Format => 'int16s[4]', Unknown => 1 }, + 0xd4 => { Name => 'ColorTempUnknown20', Unknown => 1 }, + 0xd5 => { + Name => 'ColorCalib', + Format => 'undef[120]', + Unknown => 1, + Notes => 'B, C, A, Temperature', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib' } + }, + 0x114 => { Name => 'AverageBlackLevel', Format => 'int16u[4]' }, #IB + 0x1ad => { + Name => 'RawMeasuredRGGB', + Condition => '$$self{ColorDataVersion} == 10', + Format => 'int32u[4]', + Notes => 'raw MeasuredRGGB values, before normalization', + # swap words because the word ordering is big-endian, opposite to the byte ordering + ValueConv => \&SwapWords, + ValueConvInv => \&SwapWords, + }, + 0x1f8 => { #IB + Name => 'PerChannelBlackLevel', + Condition => '$$self{ColorDataVersion} == 10', + Format => 'int16u[4]', + }, + 0x1fc => { #IB + Name => 'NormalWhiteLevel', + Condition => '$$self{ColorDataVersion} == 10', + Format => 'int16u', + RawConv => '$val || undef', + }, + 0x1fd => { #IB + Name => 'SpecularWhiteLevel', + Condition => '$$self{ColorDataVersion} == 10', + Format => 'int16u', + }, + 0x1fe => { #IB + Name => 'LinearityUpperMargin', + Condition => '$$self{ColorDataVersion} == 10', + Format => 'int16u', + }, + 0x26b => { + Name => 'RawMeasuredRGGB', + Condition => '$$self{ColorDataVersion} == 11', + Format => 'int32u[4]', + ValueConv => \&SwapWords, + ValueConvInv => \&SwapWords, + }, + 0x2d8 => { + Name => 'PerChannelBlackLevel', + Condition => '$$self{ColorDataVersion} == 11', + Format => 'int16u[4]', + }, + 0x2dc => { + Name => 'NormalWhiteLevel', + Condition => '$$self{ColorDataVersion} == 11', + Format => 'int16u', + RawConv => '$val || undef', + }, + 0x2dd => { + Name => 'SpecularWhiteLevel', + Condition => '$$self{ColorDataVersion} == 11', + Format => 'int16u', + }, + 0x2de => { + Name => 'LinearityUpperMargin', + Condition => '$$self{ColorDataVersion} == 11', + Format => 'int16u', + }, +); + +# Color data (MakerNotes tag 0x4001, count=1560,etc) (ref IB) +%Image::ExifTool::Canon::ColorData8 = ( + %binaryDataAttrs, + NOTES => q{ + These tags are used by the EOS 1DXmkII, 5DS, 5DSR, 5DmkIV, 6DmkII, 77D, 80D, + 200D, 800D, 1300D, 2000D, 4000D and 9000D. + }, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0 ], + IS_SUBDIR => [ 0x107 ], + 0x00 => { + Name => 'ColorDataVersion', + DataMember => 'ColorDataVersion', + RawConv => '$$self{ColorDataVersion} = $val', + PrintConv => { + 12 => '12 (1DXmkII/5DS/5DSR)', + 13 => '13 (80D/5DmkIV)', #PH + 14 => '14 (1300D/2000D/4000D)', #IB + 15 => '15 (6DmkII/77D/200D/800D,9000D)', #IB + }, + }, + 0x3f => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x43 => 'ColorTempAsShot', + 0x44 => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x48 => 'ColorTempAuto', + 0x49 => { Name => 'WB_RGGBLevelsMeasured', Format => 'int16s[4]' }, + 0x4d => 'ColorTempMeasured', + 0x4e => { Name => 'WB_RGGBLevelsUnknown', Format => 'int16s[4]', Unknown => 1 }, + 0x52 => { Name => 'ColorTempUnknown', Unknown => 1 }, + 0x53 => { Name => 'WB_RGGBLevelsUnknown2', Format => 'int16s[4]', Unknown => 1 }, + 0x57 => { Name => 'ColorTempUnknown2', Unknown => 1 }, + 0x58 => { Name => 'WB_RGGBLevelsUnknown3', Format => 'int16s[4]', Unknown => 1 }, + 0x5c => { Name => 'ColorTempUnknown3', Unknown => 1 }, + 0x5d => { Name => 'WB_RGGBLevelsUnknown4', Format => 'int16s[4]', Unknown => 1 }, + 0x61 => { Name => 'ColorTempUnknown4', Unknown => 1 }, + 0x62 => { Name => 'WB_RGGBLevelsUnknown5', Format => 'int16s[4]', Unknown => 1 }, + 0x66 => { Name => 'ColorTempUnknown5', Unknown => 1 }, + 0x67 => { Name => 'WB_RGGBLevelsUnknown6', Format => 'int16s[4]', Unknown => 1 }, + 0x6b => { Name => 'ColorTempUnknown6', Unknown => 1 }, + 0x6c => { Name => 'WB_RGGBLevelsUnknown7', Format => 'int16s[4]', Unknown => 1 }, + 0x70 => { Name => 'ColorTempUnknown7', Unknown => 1 }, + 0x71 => { Name => 'WB_RGGBLevelsUnknown8', Format => 'int16s[4]', Unknown => 1 }, + 0x75 => { Name => 'ColorTempUnknown8', Unknown => 1 }, + 0x76 => { Name => 'WB_RGGBLevelsUnknown9', Format => 'int16s[4]', Unknown => 1 }, + 0x7a => { Name => 'ColorTempUnknown9', Unknown => 1 }, + 0x7b => { Name => 'WB_RGGBLevelsUnknown10', Format => 'int16s[4]', Unknown => 1 }, + 0x7f => { Name => 'ColorTempUnknown10', Unknown => 1 }, + 0x80 => { Name => 'WB_RGGBLevelsUnknown11', Format => 'int16s[4]', Unknown => 1 }, + 0x84 => { Name => 'ColorTempUnknown11', Unknown => 1 }, + 0x85 => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0x89 => 'ColorTempDaylight', + 0x8a => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0x8e => 'ColorTempShade', + 0x8f => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0x93 => 'ColorTempCloudy', + 0x94 => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0x98 => 'ColorTempTungsten', + 0x99 => { Name => 'WB_RGGBLevelsFluorescent',Format => 'int16s[4]' }, + 0x9d => 'ColorTempFluorescent', + 0x9e => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 0xa2 => 'ColorTempKelvin', + 0xa3 => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0xa7 => 'ColorTempFlash', + 0xa8 => { Name => 'WB_RGGBLevelsUnknown12', Format => 'int16s[4]', Unknown => 1 }, + 0xac => { Name => 'ColorTempUnknown12', Unknown => 1 }, + 0xad => { Name => 'WB_RGGBLevelsUnknown13', Format => 'int16s[4]', Unknown => 1 }, + 0xb1 => { Name => 'ColorTempUnknown13', Unknown => 1 }, + 0xb2 => { Name => 'WB_RGGBLevelsUnknown14', Format => 'int16s[4]', Unknown => 1 }, + 0xb6 => { Name => 'ColorTempUnknown14', Unknown => 1 }, + 0xb7 => { Name => 'WB_RGGBLevelsUnknown15', Format => 'int16s[4]', Unknown => 1 }, + 0xbb => { Name => 'ColorTempUnknown15', Unknown => 1 }, + 0xbc => { Name => 'WB_RGGBLevelsUnknown16', Format => 'int16s[4]', Unknown => 1 }, + 0xc0 => { Name => 'ColorTempUnknown16', Unknown => 1 }, + 0xc1 => { Name => 'WB_RGGBLevelsUnknown17', Format => 'int16s[4]', Unknown => 1 }, + 0xc5 => { Name => 'ColorTempUnknown17', Unknown => 1 }, + 0xc6 => { Name => 'WB_RGGBLevelsUnknown18', Format => 'int16s[4]', Unknown => 1 }, + 0xca => { Name => 'ColorTempUnknown18', Unknown => 1 }, + 0xcb => { Name => 'WB_RGGBLevelsUnknown19', Format => 'int16s[4]', Unknown => 1 }, + 0xcf => { Name => 'ColorTempUnknown19', Unknown => 1 }, + 0xd0 => { Name => 'WB_RGGBLevelsUnknown20', Format => 'int16s[4]', Unknown => 1 }, + 0xd4 => { Name => 'ColorTempUnknown20', Unknown => 1 }, + 0xd5 => { Name => 'WB_RGGBLevelsUnknown21', Format => 'int16s[4]', Unknown => 1 }, + 0xd9 => { Name => 'ColorTempUnknown21', Unknown => 1 }, + 0xda => { Name => 'WB_RGGBLevelsUnknown22', Format => 'int16s[4]', Unknown => 1 }, + 0xde => { Name => 'ColorTempUnknown22', Unknown => 1 }, + 0xdf => { Name => 'WB_RGGBLevelsUnknown23', Format => 'int16s[4]', Unknown => 1 }, + 0xe3 => { Name => 'ColorTempUnknown23', Unknown => 1 }, + 0xe4 => { Name => 'WB_RGGBLevelsUnknown24', Format => 'int16s[4]', Unknown => 1 }, + 0xe8 => { Name => 'ColorTempUnknown24', Unknown => 1 }, + 0xe9 => { Name => 'WB_RGGBLevelsUnknown25', Format => 'int16s[4]', Unknown => 1 }, + 0xed => { Name => 'ColorTempUnknown25', Unknown => 1 }, + 0xee => { Name => 'WB_RGGBLevelsUnknown26', Format => 'int16s[4]', Unknown => 1 }, + 0xf2 => { Name => 'ColorTempUnknown26', Unknown => 1 }, + 0xf3 => { Name => 'WB_RGGBLevelsUnknown27', Format => 'int16s[4]', Unknown => 1 }, + 0xf7 => { Name => 'ColorTempUnknown27', Unknown => 1 }, + 0xf8 => { Name => 'WB_RGGBLevelsUnknown28', Format => 'int16s[4]', Unknown => 1 }, + 0xfc => { Name => 'ColorTempUnknown28', Unknown => 1 }, + 0xfd => { Name => 'WB_RGGBLevelsUnknown29', Format => 'int16s[4]', Unknown => 1 }, + 0x101 => { Name => 'ColorTempUnknown29', Unknown => 1 }, + 0x102 => { Name => 'WB_RGGBLevelsUnknown30', Format => 'int16s[4]', Unknown => 1 }, + 0x106 => { Name => 'ColorTempUnknown30', Unknown => 1 }, + + 0x107 => { + Name => 'ColorCalib', + Format => 'undef[120]', + Unknown => 1, + Notes => 'B, C, A, Temperature', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib' } + }, + 0x146 => { Name => 'AverageBlackLevel', Format => 'int16u[4]' }, + 0x22c => { + Name => 'PerChannelBlackLevel', + Condition => '$$self{ColorDataVersion} == 14', + Format => 'int16u[4]', + Notes => '1300D', + }, + 0x230 => { + Name => 'NormalWhiteLevel', + Condition => '$$self{ColorDataVersion} == 14', + Format => 'int16u', + Notes => '1300D', + RawConv => '$val || undef', + }, + 0x231 => { + Name => 'SpecularWhiteLevel', + Condition => '$$self{ColorDataVersion} == 14', + Format => 'int16u', + Notes => '1300D', + }, + 0x232 => { + Name => 'LinearityUpperMargin', + Condition => '$$self{ColorDataVersion} == 14', + Format => 'int16u', + Notes => '1300D', + }, + 0x30a => { + Name => 'PerChannelBlackLevel', + Condition => '$$self{ColorDataVersion} < 14 or $$self{ColorDataVersion} == 15', + Format => 'int16u[4]', + Notes => '5DS, 5DS R, 77D, 80D and 800D', + }, + 0x30e => { + Name => 'NormalWhiteLevel', + Condition => '$$self{ColorDataVersion} < 14 or $$self{ColorDataVersion} == 15', + Format => 'int16u', + Notes => '5DS, 5DS R, 77D, 80D and 800D', + RawConv => '$val || undef', + }, + 0x30f => { + Name => 'SpecularWhiteLevel', + Condition => '$$self{ColorDataVersion} < 14 or $$self{ColorDataVersion} == 15', + Format => 'int16u', + Notes => '5DS, 5DS R, 77D, 80D and 800D', + }, + 0x310 => { + Name => 'LinearityUpperMargin', + Condition => '$$self{ColorDataVersion} < 14 or $$self{ColorDataVersion} == 15', + Format => 'int16u', + Notes => '5DS, 5DS R, 77D, 80D and 800D', + }, +); + +# Color data (MakerNotes tag 0x4001, count=1820,etc) (ref PH) +%Image::ExifTool::Canon::ColorData9 = ( + %binaryDataAttrs, + NOTES => 'These tags are used by the M6mkII, M50, M200, EOS R, RP, 90D, 250D and 850D', + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0 ], + IS_SUBDIR => [ 0x10a ], + 0x00 => { + Name => 'ColorDataVersion', + DataMember => 'ColorDataVersion', + RawConv => '$$self{ColorDataVersion} = $val', + PrintConv => { + 16 => '16 (M50)', + 17 => '17 (EOS R)', # (and PowerShot SX740HS) + 18 => '18 (EOS RP/250D)', # (and PowerShot SX70HS) + 19 => '19 (90D/850D/M6mkII/M200)',# (and PowerShot G7XmkIII) + }, + }, + 0x47 => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x4b => 'ColorTempAsShot', + 0x4c => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x50 => 'ColorTempAuto', + 0x51 => { Name => 'WB_RGGBLevelsMeasured', Format => 'int16s[4]' }, + 0x55 => 'ColorTempMeasured', + 0x56 => { Name => 'WB_RGGBLevelsUnknown', Format => 'int16s[4]', Unknown => 1 }, + 0x5a => { Name => 'ColorTempUnknown', Unknown => 1 }, + 0x5b => { Name => 'WB_RGGBLevelsUnknown2', Format => 'int16s[4]', Unknown => 1 }, + 0x5f => { Name => 'ColorTempUnknown2', Unknown => 1 }, + 0x60 => { Name => 'WB_RGGBLevelsUnknown3', Format => 'int16s[4]', Unknown => 1 }, + 0x64 => { Name => 'ColorTempUnknown3', Unknown => 1 }, + 0x65 => { Name => 'WB_RGGBLevelsUnknown4', Format => 'int16s[4]', Unknown => 1 }, + 0x69 => { Name => 'ColorTempUnknown4', Unknown => 1 }, + 0x6a => { Name => 'WB_RGGBLevelsUnknown5', Format => 'int16s[4]', Unknown => 1 }, + 0x6e => { Name => 'ColorTempUnknown5', Unknown => 1 }, + 0x6f => { Name => 'WB_RGGBLevelsUnknown6', Format => 'int16s[4]', Unknown => 1 }, + 0x73 => { Name => 'ColorTempUnknown6', Unknown => 1 }, + 0x74 => { Name => 'WB_RGGBLevelsUnknown7', Format => 'int16s[4]', Unknown => 1 }, + 0x78 => { Name => 'ColorTempUnknown7', Unknown => 1 }, + 0x79 => { Name => 'WB_RGGBLevelsUnknown8', Format => 'int16s[4]', Unknown => 1 }, + 0x7d => { Name => 'ColorTempUnknown8', Unknown => 1 }, + 0x7e => { Name => 'WB_RGGBLevelsUnknown9', Format => 'int16s[4]', Unknown => 1 }, + 0x82 => { Name => 'ColorTempUnknown9', Unknown => 1 }, + 0x83 => { Name => 'WB_RGGBLevelsUnknown10', Format => 'int16s[4]', Unknown => 1 }, + 0x87 => { Name => 'ColorTempUnknown10', Unknown => 1 }, + 0x88 => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0x8c => 'ColorTempDaylight', + 0x8d => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0x91 => 'ColorTempShade', + 0x92 => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0x96 => 'ColorTempCloudy', + 0x97 => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0x9b => 'ColorTempTungsten', + 0x9c => { Name => 'WB_RGGBLevelsFluorescent',Format => 'int16s[4]' }, + 0xa0 => 'ColorTempFluorescent', + 0xa1 => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 0xa5 => 'ColorTempKelvin', + 0xa6 => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0xaa => 'ColorTempFlash', + 0xab => { Name => 'WB_RGGBLevelsUnknown11', Format => 'int16s[4]', Unknown => 1 }, + 0xaf => { Name => 'ColorTempUnknown11', Unknown => 1 }, + 0xb0 => { Name => 'WB_RGGBLevelsUnknown12', Format => 'int16s[4]', Unknown => 1 }, + 0xb4 => { Name => 'ColorTempUnknown12', Unknown => 1 }, + 0xb5 => { Name => 'WB_RGGBLevelsUnknown13', Format => 'int16s[4]', Unknown => 1 }, + 0xb9 => { Name => 'ColorTempUnknown13', Unknown => 1 }, + 0xba => { Name => 'WB_RGGBLevelsUnknown14', Format => 'int16s[4]', Unknown => 1 }, + 0xbe => { Name => 'ColorTempUnknown14', Unknown => 1 }, + 0xbf => { Name => 'WB_RGGBLevelsUnknown15', Format => 'int16s[4]', Unknown => 1 }, + 0xc3 => { Name => 'ColorTempUnknown15', Unknown => 1 }, + 0xc4 => { Name => 'WB_RGGBLevelsUnknown16', Format => 'int16s[4]', Unknown => 1 }, + 0xc8 => { Name => 'ColorTempUnknown16', Unknown => 1 }, + 0xc9 => { Name => 'WB_RGGBLevelsUnknown17', Format => 'int16s[4]', Unknown => 1 }, + 0xcd => { Name => 'ColorTempUnknown17', Unknown => 1 }, + 0xce => { Name => 'WB_RGGBLevelsUnknown18', Format => 'int16s[4]', Unknown => 1 }, + 0xd2 => { Name => 'ColorTempUnknown18', Unknown => 1 }, + 0xd3 => { Name => 'WB_RGGBLevelsUnknown19', Format => 'int16s[4]', Unknown => 1 }, + 0xd7 => { Name => 'ColorTempUnknown19', Unknown => 1 }, + 0xd8 => { Name => 'WB_RGGBLevelsUnknown20', Format => 'int16s[4]', Unknown => 1 }, + 0xdc => { Name => 'ColorTempUnknown20', Unknown => 1 }, + 0xdd => { Name => 'WB_RGGBLevelsUnknown21', Format => 'int16s[4]', Unknown => 1 }, + 0xe1 => { Name => 'ColorTempUnknown21', Unknown => 1 }, + 0xe2 => { Name => 'WB_RGGBLevelsUnknown22', Format => 'int16s[4]', Unknown => 1 }, + 0xe6 => { Name => 'ColorTempUnknown22', Unknown => 1 }, + 0xe7 => { Name => 'WB_RGGBLevelsUnknown23', Format => 'int16s[4]', Unknown => 1 }, + 0xeb => { Name => 'ColorTempUnknown23', Unknown => 1 }, + 0xec => { Name => 'WB_RGGBLevelsUnknown24', Format => 'int16s[4]', Unknown => 1 }, + 0xf0 => { Name => 'ColorTempUnknown24', Unknown => 1 }, + 0xf1 => { Name => 'WB_RGGBLevelsUnknown25', Format => 'int16s[4]', Unknown => 1 }, + 0xf5 => { Name => 'ColorTempUnknown25', Unknown => 1 }, + 0xf6 => { Name => 'WB_RGGBLevelsUnknown26', Format => 'int16s[4]', Unknown => 1 }, + 0xfa => { Name => 'ColorTempUnknown26', Unknown => 1 }, + 0xfb => { Name => 'WB_RGGBLevelsUnknown27', Format => 'int16s[4]', Unknown => 1 }, + 0xff => { Name => 'ColorTempUnknown27', Unknown => 1 }, + 0x100=> { Name => 'WB_RGGBLevelsUnknown28', Format => 'int16s[4]', Unknown => 1 }, + 0x104=> { Name => 'ColorTempUnknown28', Unknown => 1 }, + 0x105=> { Name => 'WB_RGGBLevelsUnknown29', Format => 'int16s[4]', Unknown => 1 }, + 0x109=> { Name => 'ColorTempUnknown29', Unknown => 1 }, + 0x10a => { #IB + Name => 'ColorCalib', + Format => 'undef[120]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib' } + }, + 0x149 => { #IB + Name => 'PerChannelBlackLevel', + Format => 'int16u[4]', + }, + # 0x318 - PerChannelBlackLevel again (ref IB) + 0x31c => { #IB + Name => 'NormalWhiteLevel', + Format => 'int16u', + RawConv => '$val || undef', + }, + 0x31d => { #IB + Name => 'SpecularWhiteLevel', + Format => 'int16u', + }, + 0x31e => { #IB + Name => 'LinearityUpperMargin', + Format => 'int16u', + }, +); + +# Color data (MakerNotes tag 0x4001, count=2024,3656) +# (same as ColorData9 but shifted up by 0x0e, ref PH) +%Image::ExifTool::Canon::ColorData10 = ( + %binaryDataAttrs, + NOTES => 'These tags are used by the R5, R5 and EOS 1DXmkIII.', + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0 ], + IS_SUBDIR => [ 0x118 ], + 0x00 => { + Name => 'ColorDataVersion', + DataMember => 'ColorDataVersion', + RawConv => '$$self{ColorDataVersion} = $val', + PrintConv => { + 32 => '32 (1DXmkIII)', #IB + 33 => '33 (R5/R6)', + }, + }, + 0x55 => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x59 => 'ColorTempAsShot', + 0x5a => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x5e => 'ColorTempAuto', + 0x5f => { Name => 'WB_RGGBLevelsMeasured', Format => 'int16s[4]' }, + 0x63 => 'ColorTempMeasured', + 0x64 => { Name => 'WB_RGGBLevelsUnknown', Format => 'int16s[4]', Unknown => 1 }, + 0x68 => { Name => 'ColorTempUnknown', Unknown => 1 }, + 0x69 => { Name => 'WB_RGGBLevelsUnknown2', Format => 'int16s[4]', Unknown => 1 }, + 0x6d => { Name => 'ColorTempUnknown2', Unknown => 1 }, + 0x6e => { Name => 'WB_RGGBLevelsUnknown3', Format => 'int16s[4]', Unknown => 1 }, + 0x72 => { Name => 'ColorTempUnknown3', Unknown => 1 }, + 0x73 => { Name => 'WB_RGGBLevelsUnknown4', Format => 'int16s[4]', Unknown => 1 }, + 0x77 => { Name => 'ColorTempUnknown4', Unknown => 1 }, + 0x78 => { Name => 'WB_RGGBLevelsUnknown5', Format => 'int16s[4]', Unknown => 1 }, + 0x7c => { Name => 'ColorTempUnknown5', Unknown => 1 }, + 0x7d => { Name => 'WB_RGGBLevelsUnknown6', Format => 'int16s[4]', Unknown => 1 }, + 0x81 => { Name => 'ColorTempUnknown6', Unknown => 1 }, + 0x82 => { Name => 'WB_RGGBLevelsUnknown7', Format => 'int16s[4]', Unknown => 1 }, + 0x86 => { Name => 'ColorTempUnknown7', Unknown => 1 }, + 0x87 => { Name => 'WB_RGGBLevelsUnknown8', Format => 'int16s[4]', Unknown => 1 }, + 0x8b => { Name => 'ColorTempUnknown8', Unknown => 1 }, + 0x8c => { Name => 'WB_RGGBLevelsUnknown9', Format => 'int16s[4]', Unknown => 1 }, + 0x90 => { Name => 'ColorTempUnknown9', Unknown => 1 }, + 0x91 => { Name => 'WB_RGGBLevelsUnknown10', Format => 'int16s[4]', Unknown => 1 }, + 0x95 => { Name => 'ColorTempUnknown10', Unknown => 1 }, + 0x96 => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0x9a => 'ColorTempDaylight', + 0x9b => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0x9f => 'ColorTempShade', + 0xa0 => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0xa4 => 'ColorTempCloudy', + 0xa5 => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0xa9 => 'ColorTempTungsten', + 0xaa => { Name => 'WB_RGGBLevelsFluorescent',Format => 'int16s[4]' }, + 0xae => 'ColorTempFluorescent', + 0xaf => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 0xb3 => 'ColorTempKelvin', + 0xb4 => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0xb8 => 'ColorTempFlash', + 0xb9 => { Name => 'WB_RGGBLevelsUnknown11', Format => 'int16s[4]', Unknown => 1 }, + 0xbd => { Name => 'ColorTempUnknown11', Unknown => 1 }, + 0xbe => { Name => 'WB_RGGBLevelsUnknown12', Format => 'int16s[4]', Unknown => 1 }, + 0xc2 => { Name => 'ColorTempUnknown12', Unknown => 1 }, + 0xc3 => { Name => 'WB_RGGBLevelsUnknown13', Format => 'int16s[4]', Unknown => 1 }, + 0xc7 => { Name => 'ColorTempUnknown13', Unknown => 1 }, + 0xc8 => { Name => 'WB_RGGBLevelsUnknown14', Format => 'int16s[4]', Unknown => 1 }, + 0xcc => { Name => 'ColorTempUnknown14', Unknown => 1 }, + 0xcd => { Name => 'WB_RGGBLevelsUnknown15', Format => 'int16s[4]', Unknown => 1 }, + 0xd1 => { Name => 'ColorTempUnknown15', Unknown => 1 }, + 0xd2 => { Name => 'WB_RGGBLevelsUnknown16', Format => 'int16s[4]', Unknown => 1 }, + 0xd6 => { Name => 'ColorTempUnknown16', Unknown => 1 }, + 0xd7 => { Name => 'WB_RGGBLevelsUnknown17', Format => 'int16s[4]', Unknown => 1 }, + 0xdb => { Name => 'ColorTempUnknown17', Unknown => 1 }, + 0xdc => { Name => 'WB_RGGBLevelsUnknown18', Format => 'int16s[4]', Unknown => 1 }, + 0xe0 => { Name => 'ColorTempUnknown18', Unknown => 1 }, + 0xe1 => { Name => 'WB_RGGBLevelsUnknown19', Format => 'int16s[4]', Unknown => 1 }, + 0xe5 => { Name => 'ColorTempUnknown19', Unknown => 1 }, + 0xe6 => { Name => 'WB_RGGBLevelsUnknown20', Format => 'int16s[4]', Unknown => 1 }, + 0xea => { Name => 'ColorTempUnknown20', Unknown => 1 }, + 0xeb => { Name => 'WB_RGGBLevelsUnknown21', Format => 'int16s[4]', Unknown => 1 }, + 0xef => { Name => 'ColorTempUnknown21', Unknown => 1 }, + 0xf0 => { Name => 'WB_RGGBLevelsUnknown22', Format => 'int16s[4]', Unknown => 1 }, + 0xf4 => { Name => 'ColorTempUnknown22', Unknown => 1 }, + 0xf5 => { Name => 'WB_RGGBLevelsUnknown23', Format => 'int16s[4]', Unknown => 1 }, + 0xf9 => { Name => 'ColorTempUnknown23', Unknown => 1 }, + 0xfa => { Name => 'WB_RGGBLevelsUnknown24', Format => 'int16s[4]', Unknown => 1 }, + 0xfe => { Name => 'ColorTempUnknown24', Unknown => 1 }, + 0xff => { Name => 'WB_RGGBLevelsUnknown25', Format => 'int16s[4]', Unknown => 1 }, + 0x103=> { Name => 'ColorTempUnknown25', Unknown => 1 }, + 0x104=> { Name => 'WB_RGGBLevelsUnknown26', Format => 'int16s[4]', Unknown => 1 }, + 0x108=> { Name => 'ColorTempUnknown26', Unknown => 1 }, + 0x109=> { Name => 'WB_RGGBLevelsUnknown27', Format => 'int16s[4]', Unknown => 1 }, + 0x10d=> { Name => 'ColorTempUnknown27', Unknown => 1 }, + 0x10e=> { Name => 'WB_RGGBLevelsUnknown28', Format => 'int16s[4]', Unknown => 1 }, + 0x112=> { Name => 'ColorTempUnknown28', Unknown => 1 }, + 0x113=> { Name => 'WB_RGGBLevelsUnknown29', Format => 'int16s[4]', Unknown => 1 }, + 0x117=> { Name => 'ColorTempUnknown29', Unknown => 1 }, + 0x118 => { + Name => 'ColorCalib', + Format => 'undef[120]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib' } + }, + 0x157 => { + Name => 'PerChannelBlackLevel', + Format => 'int16u[4]', + }, + # 0x326 - PerChannelBlackLevel again + 0x32a => { + Name => 'NormalWhiteLevel', + Format => 'int16u', + RawConv => '$val || undef', + }, + 0x32b => { + Name => 'SpecularWhiteLevel', + Format => 'int16u', + }, + 0x32c => { + Name => 'LinearityUpperMargin', + Format => 'int16u', + }, +); + +# Color data (MakerNotes tag 0x4001, count=3973, ref IB) +%Image::ExifTool::Canon::ColorData11 = ( + %binaryDataAttrs, + NOTES => 'These tags are used by the EOS R3, R7 and R6mkII', + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0 ], + IS_SUBDIR => [ 0x12c ], + 0x00 => { + Name => 'ColorDataVersion', + DataMember => 'ColorDataVersion', + RawConv => '$$self{ColorDataVersion} = $val', + PrintConv => { + 34 => '34 (R3)', #IB + 48 => '48 (R7, R10, R6 Mark II)', #IB + }, + }, + 0x69 => { Name => 'WB_RGGBLevelsAsShot', Format => 'int16s[4]' }, + 0x6d => 'ColorTempAsShot', + 0x6e => { Name => 'WB_RGGBLevelsAuto', Format => 'int16s[4]' }, + 0x72 => 'ColorTempAuto', + 0x73 => { Name => 'WB_RGGBLevelsMeasured', Format => 'int16s[4]' }, + 0x77 => 'ColorTempMeasured', + 0x78 => { Name => 'WB_RGGBLevelsUnknown', Format => 'int16s[4]', Unknown => 1 }, + 0x7c => { Name => 'ColorTempUnknown', Unknown => 1 }, + 0x7d => { Name => 'WB_RGGBLevelsUnknown2', Format => 'int16s[4]', Unknown => 1 }, + 0x81 => { Name => 'ColorTempUnknown2', Unknown => 1 }, + 0x82 => { Name => 'WB_RGGBLevelsUnknown3', Format => 'int16s[4]', Unknown => 1 }, + 0x86 => { Name => 'ColorTempUnknown3', Unknown => 1 }, + 0x87 => { Name => 'WB_RGGBLevelsUnknown4', Format => 'int16s[4]', Unknown => 1 }, + 0x8b => { Name => 'ColorTempUnknown4', Unknown => 1 }, + 0x8c => { Name => 'WB_RGGBLevelsUnknown5', Format => 'int16s[4]', Unknown => 1 }, + 0x90 => { Name => 'ColorTempUnknown5', Unknown => 1 }, + 0x91 => { Name => 'WB_RGGBLevelsUnknown6', Format => 'int16s[4]', Unknown => 1 }, + 0x95 => { Name => 'ColorTempUnknown6', Unknown => 1 }, + 0x96 => { Name => 'WB_RGGBLevelsUnknown7', Format => 'int16s[4]', Unknown => 1 }, + 0x9a => { Name => 'ColorTempUnknown7', Unknown => 1 }, + 0x9b => { Name => 'WB_RGGBLevelsUnknown8', Format => 'int16s[4]', Unknown => 1 }, + 0x9f => { Name => 'ColorTempUnknown8', Unknown => 1 }, + 0xa0 => { Name => 'WB_RGGBLevelsUnknown9', Format => 'int16s[4]', Unknown => 1 }, + 0xa4 => { Name => 'ColorTempUnknown9', Unknown => 1 }, + 0xa5 => { Name => 'WB_RGGBLevelsUnknown10', Format => 'int16s[4]', Unknown => 1 }, + 0xa9 => { Name => 'ColorTempUnknown10', Unknown => 1 }, + 0xaa => { Name => 'WB_RGGBLevelsUnknown11', Format => 'int16s[4]', Unknown => 1 }, + 0xae => { Name => 'ColorTempUnknown11', Unknown => 1 }, + 0xaf => { Name => 'WB_RGGBLevelsUnknown11', Format => 'int16s[4]', Unknown => 1 }, + 0xb3 => { Name => 'ColorTempUnknown11', Unknown => 1 }, + 0xb4 => { Name => 'WB_RGGBLevelsUnknown12', Format => 'int16s[4]', Unknown => 1 }, + 0xb8 => { Name => 'ColorTempUnknown12', Unknown => 1 }, + 0xb9 => { Name => 'WB_RGGBLevelsUnknown13', Format => 'int16s[4]', Unknown => 1 }, + 0xbd => { Name => 'ColorTempUnknown13', Unknown => 1 }, + 0xbe => { Name => 'WB_RGGBLevelsUnknown14', Format => 'int16s[4]', Unknown => 1 }, + 0xc2 => { Name => 'ColorTempUnknown14', Unknown => 1 }, + 0xc3 => { Name => 'WB_RGGBLevelsUnknown15', Format => 'int16s[4]', Unknown => 1 }, + 0xc7 => { Name => 'ColorTempUnknown15', Unknown => 1 }, + 0xc8 => { Name => 'WB_RGGBLevelsUnknown16', Format => 'int16s[4]', Unknown => 1 }, + 0xcc => { Name => 'ColorTempUnknown16', Unknown => 1 }, + 0xcd => { Name => 'WB_RGGBLevelsDaylight', Format => 'int16s[4]' }, + 0xd1 => 'ColorTempDaylight', + 0xd2 => { Name => 'WB_RGGBLevelsShade', Format => 'int16s[4]' }, + 0xd6 => 'ColorTempShade', + 0xd7 => { Name => 'WB_RGGBLevelsCloudy', Format => 'int16s[4]' }, + 0xdb => 'ColorTempCloudy', + 0xdc => { Name => 'WB_RGGBLevelsTungsten', Format => 'int16s[4]' }, + 0xe0 => 'ColorTempTungsten', + 0xe1 => { Name => 'WB_RGGBLevelsFluorescent',Format => 'int16s[4]' }, + 0xe5 => 'ColorTempFluorescent', + 0xe6 => { Name => 'WB_RGGBLevelsKelvin', Format => 'int16s[4]' }, + 0xea => 'ColorTempKelvin', + 0xeb => { Name => 'WB_RGGBLevelsFlash', Format => 'int16s[4]' }, + 0xef => 'ColorTempFlash', + 0xf0 => { Name => 'WB_RGGBLevelsUnknown17', Format => 'int16s[4]', Unknown => 1 }, + 0xf4 => { Name => 'ColorTempUnknown17', Unknown => 1 }, + 0xf5 => { Name => 'WB_RGGBLevelsUnknown18', Format => 'int16s[4]', Unknown => 1 }, + 0xf9 => { Name => 'ColorTempUnknown18', Unknown => 1 }, + 0xfa => { Name => 'WB_RGGBLevelsUnknown19', Format => 'int16s[4]', Unknown => 1 }, + 0xfe => { Name => 'ColorTempUnknown19', Unknown => 1 }, + 0xff => { Name => 'WB_RGGBLevelsUnknown20', Format => 'int16s[4]', Unknown => 1 }, + 0x103 => { Name => 'ColorTempUnknown20', Unknown => 1 }, + 0x104 => { Name => 'WB_RGGBLevelsUnknown21', Format => 'int16s[4]', Unknown => 1 }, + 0x108 => { Name => 'ColorTempUnknown21', Unknown => 1 }, + 0x109 => { Name => 'WB_RGGBLevelsUnknown22', Format => 'int16s[4]', Unknown => 1 }, + 0x10d => { Name => 'ColorTempUnknown22', Unknown => 1 }, + 0x10e => { Name => 'WB_RGGBLevelsUnknown23', Format => 'int16s[4]', Unknown => 1 }, + 0x112 => { Name => 'ColorTempUnknown23', Unknown => 1 }, + 0x113 => { Name => 'WB_RGGBLevelsUnknown24', Format => 'int16s[4]', Unknown => 1 }, + 0x117 => { Name => 'ColorTempUnknown24', Unknown => 1 }, + 0x118 => { Name => 'WB_RGGBLevelsUnknown25', Format => 'int16s[4]', Unknown => 1 }, + 0x11c => { Name => 'ColorTempUnknown25', Unknown => 1 }, + 0x11d => { Name => 'WB_RGGBLevelsUnknown26', Format => 'int16s[4]', Unknown => 1 }, + 0x121 => { Name => 'ColorTempUnknown26', Unknown => 1 }, + 0x122 => { Name => 'WB_RGGBLevelsUnknown27', Format => 'int16s[4]', Unknown => 1 }, + 0x126 => { Name => 'ColorTempUnknown27', Unknown => 1 }, + 0x12c => { + Name => 'ColorCalib', + Format => 'undef[120]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorCalib' } + }, + 0x16b => { + Name => 'PerChannelBlackLevel', + Format => 'int16u[4]', + }, + # 0x27c - PerChannelBlackLevel again + 0x280 => { + Name => 'NormalWhiteLevel', + Format => 'int16u', + RawConv => '$val || undef', + }, + 0x281 => { + Name => 'SpecularWhiteLevel', + Format => 'int16u', + }, + 0x282 => { + Name => 'LinearityUpperMargin', + Format => 'int16u', + }, +); + +# Unknown color data (MakerNotes tag 0x4001) +%Image::ExifTool::Canon::ColorDataUnknown = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x00 => 'ColorDataVersion', +); + +# Color information (MakerNotes tag 0x4003) (ref PH) +%Image::ExifTool::Canon::ColorInfo = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 1 => { + Condition => '$$self{Model} =~ /EOS-1D/', + Name => 'Saturation', + %Image::ExifTool::Exif::printParameter, + }, + 2 => { + Name => 'ColorTone', + %Image::ExifTool::Exif::printParameter, + }, + 3 => { + Name => 'ColorSpace', + RawConv => '$val ? $val : undef', # ignore tag if zero + PrintConv => { + 1 => 'sRGB', + 2 => 'Adobe RGB', + }, + }, +); + +# AF micro-adjustment information (MakerNotes tag 0x4013) (ref PH) +%Image::ExifTool::Canon::AFMicroAdj = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 1 => { + Name => 'AFMicroAdjMode', + PrintConv => { + 0 => 'Disable', + 1 => 'Adjust all by the same amount', + 2 => 'Adjust by lens', + # 3 - seen this for EOS 77D, which doesn't have an AF Micro Adjust feature - PH + }, + }, + 2 => { + Name => 'AFMicroAdjValue', + Format => 'rational64s', + }, +); + +# Vignetting correction information (MakerNotes tag 0x4015) +%Image::ExifTool::Canon::VignettingCorr = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'This information is found in images from newer EOS models.', + 0 => { + Name => 'VignettingCorrVersion', + Format => 'int8u', + Writable => 0, + }, + 2 => { + Name => 'PeripheralLighting', + PrintConv => \%offOn, + }, + 3 => { + Name => 'DistortionCorrection', + PrintConv => \%offOn, + }, + 4 => { + Name => 'ChromaticAberrationCorr', + PrintConv => \%offOn, + }, + 5 => { + Name => 'ChromaticAberrationCorr', + PrintConv => \%offOn, + }, + 6 => 'PeripheralLightingValue', + 9 => 'DistortionCorrectionValue', + # 10 - flags? + 11 => { + Name => 'OriginalImageWidth', + Notes => 'full size of original image before being rotated or scaled in camera', + }, + 12 => 'OriginalImageHeight', +); + +%Image::ExifTool::Canon::VignettingCorrUnknown = ( + %binaryDataAttrs, + FORMAT => 'int16s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Vignetting correction from PowerShot models.', + 0 => { + Name => 'VignettingCorrVersion', + Format => 'int8u', + Writable => 0, + }, +); + +# More Vignetting correction information (MakerNotes tag 0x4016) +%Image::ExifTool::Canon::VignettingCorr2 = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 5 => { + Name => 'PeripheralLightingSetting', + PrintConv => \%offOn, + }, + 6 => { + Name => 'ChromaticAberrationSetting', + PrintConv => \%offOn, + }, + 7 => { + Name => 'DistortionCorrectionSetting', + PrintConv => \%offOn, + }, + 9 => { #forum14286 + Name => 'DigitalLensOptimizerSetting', + PrintConv => \%offOn, + }, +); + +# Auto Lighting Optimizater information (MakerNotes tag 0x4018) (ref PH) +%Image::ExifTool::Canon::LightingOpt = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'This information is new in images from the EOS 7D.', + 1 => { + Name => 'PeripheralIlluminationCorr', + PrintConv => \%offOn, + }, + 2 => { + Name => 'AutoLightingOptimizer', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Off', + }, + }, + 3 => { + Name => 'HighlightTonePriority', + PrintConv => \%offOn, + }, + 4 => { + Name => 'LongExposureNoiseReduction', + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'On', + }, + }, + 5 => { + Name => 'HighISONoiseReduction', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Off', + }, + }, + # 6 - related to ChromaticAberrationCorr + # 7 - related to DistortionCorrection (0=off, 1=On in a 5DmkIV sample) + # 8 - related to PeripheralIlluminationCorr and ChromaticAberrationCorr + 10 => { #forum14286 + Name => 'DigitalLensOptimizer', + PrintConv => { + 0 => 'Off', + 1 => 'Stanard', + 2 => 'High', + }, + }, +); + +# Lens information (MakerNotes tag 0x4019) (ref 20) +%Image::ExifTool::Canon::LensInfo = ( + %binaryDataAttrs, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { # this doesn't seem to be valid for some models (eg. 550D, 7D?, 1DmkIV?) + Name => 'LensSerialNumber', + Notes => q{ + apparently this is an internal serial number because it doesn't correspond + to the one printed on the lens + }, + Format => 'undef[5]', + Priority => 0, + RawConv => '$val=~/^\0\0\0\0/ ? undef : $val', # (rules out 550D and older lenses) + ValueConv => 'unpack("H*", $val)', + ValueConvInv => 'length($val) < 10 and $val = 0 x (10-length($val)) . $val; pack("H*",$val)', + }, +); + +# Subject mode ambience information (MakerNotes tag 0x4020) (ref PH) +%Image::ExifTool::Canon::Ambience = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 1 => { + Name => 'AmbienceSelection', + PrintConv => { + 0 => 'Standard', + 1 => 'Vivid', + 2 => 'Warm', + 3 => 'Soft', + 4 => 'Cool', + 5 => 'Intense', + 6 => 'Brighter', + 7 => 'Darker', + 8 => 'Monochrome', + }, + }, +); + +# Multi-exposure information (MakerNotes tag 0x4021) (ref PH) +%Image::ExifTool::Canon::MultiExp = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 1 => { + Name => 'MultiExposure', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (RAW)', #IB + }, + }, + 2 => { + Name => 'MultiExposureControl', + PrintConv => { + 0 => 'Additive', + 1 => 'Average', + 2 => 'Bright (comparative)', + 3 => 'Dark (comparative)', + }, + }, + 3 => 'MultiExposureShots', +); + +my %filterConv = ( + PrintConv => { + -1 => 'Off', + OTHER => sub { my $val=shift; return "On ($val)" }, + }, +); +# Creative filter information (MakerNotes tag 0x4024) (ref PH) +%Image::ExifTool::Canon::FilterInfo = ( + PROCESS_PROC => \&ProcessFilters, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Information about creative filter settings.', + 0x101 => { + Name => 'GrainyBWFilter', + Description => 'Grainy B/W Filter', + %filterConv, + }, + 0x201 => { Name => 'SoftFocusFilter', %filterConv }, + 0x301 => { Name => 'ToyCameraFilter', %filterConv }, + 0x401 => { Name => 'MiniatureFilter', %filterConv }, + 0x402 => { + Name => 'MiniatureFilterOrientation', + PrintConv => { + 0 => 'Horizontal', + 1 => 'Vertical', + }, + }, + 0x403 => 'MiniatureFilterPosition', + 0x404 => 'MiniatureFilterParameter', # but what is the meaning? + 0x501 => { Name => 'FisheyeFilter', %filterConv }, # (M2) + 0x601 => { Name => 'PaintingFilter', %filterConv }, # (M2) + 0x701 => { Name => 'WatercolorFilter', %filterConv }, # (M2) +); + +# HDR information (MakerNotes tag 0x4025) (ref PH) +%Image::ExifTool::Canon::HDRInfo = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 1 => { + Name => 'HDR', + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'On', + }, + }, + 2 => { + Name => 'HDREffect', + PrintConv => { + 0 => 'Natural', + 1 => 'Art (standard)', + 2 => 'Art (vivid)', + 3 => 'Art (bold)', + 4 => 'Art (embossed)', + }, + }, + # 3 - maybe related to AutoImageAlign? +); + +# More color information (MakerNotes tag 0x4026) (ref github issue #119) +%Image::ExifTool::Canon::LogInfo = ( + %binaryDataAttrs, + FORMAT => 'int32s', + FIRST_ENTRY => 1, + PRIORITY => 0, + 4 => { + Name => 'CompressionFormat', + PrintConv => { + 0 => 'Editing (ALL-I)', + 1 => 'Standard (IPB)', + 2 => 'Light (IPB)', + 3 => 'Motion JPEG', + 4 => 'RAW', # either Standard or Light, depending on Quality + }, + }, + 6 => { # 0 to 7 + Name => 'Sharpness', + RawConv => '$val == 0x7fffffff ? undef : $val', + }, + 7 => { # -4 to 4 + Name => 'Saturation', + RawConv => '$val == 0x7fffffff ? undef : $val', + %Image::ExifTool::Exif::printParameter, + }, + 8 => { # -4 to 4 + Name => 'ColorTone', + RawConv => '$val == 0x7fffffff ? undef : $val', + %Image::ExifTool::Exif::printParameter, + }, + 9 => { + Name => 'ColorSpace2', + RawConv => '$val == 0x7fffffff ? undef : $val', + PrintConv => { + 0 => 'BT.709', + 1 => 'BT.2020', + 2 => 'CinemaGamut', + }, + }, + 10 => { + Name => 'ColorMatrix', + RawConv => '$val == 0x7fffffff ? undef : $val', + PrintConv => { + 0 => 'EOS Original', + 1 => 'Neutral', + }, + }, + 11 => { + Name => 'CanonLogVersion', # (increases dynamic range of sensor data) + RawConv => '$val == 0x7fffffff ? undef : $val', + PrintConv => { + 0 => 'OFF', + 1 => 'CLogV1', + 2 => 'CLogV2', # (NC) + 3 => 'CLogV3', + }, + }, +); + +# AF configuration info (MakerNotes tag 0x4028) (ref PH) +%Image::ExifTool::Canon::AFConfig = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int32s', + FIRST_ENTRY => 1, + 1 => { + Name => 'AFConfigTool', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + PrintConv => '"Case $val"', + PrintConvInv => '$val=~/(\d+)/ ? $1 : undef', + }, + 2 => 'AFTrackingSensitivity', + 3 => { + Name => 'AFAccelDecelTracking', + Description => 'AF Accel/Decel Tracking', + }, + 4 => 'AFPointSwitching', + 5 => { #52 + Name => 'AIServoFirstImage', + PrintConv => { + 0 => 'Equal Priority', + 1 => 'Release Priority', + 2 => 'Focus Priority', + }, + }, + 6 => { #52 + Name => 'AIServoSecondImage', + PrintConv => { + 0 => 'Equal Priority', + 1 => 'Release Priority', + 2 => 'Focus Priority', + 3 => 'Release High Priority', + 4 => 'Focus High Priority', + }, + }, + 7 => { #52 + Name => 'USMLensElectronicMF', + PrintConv => { + 0 => 'Enable After AF', + 1 => 'Disable After AF', + 2 => 'Disable in AF Mode', + }, + }, + 8 => { #52 + Name => 'AFAssistBeam', + PrintConv => { + 0 => 'Enable', + 1 => 'Disable', + 2 => 'IR AF Assist Beam Only', + }, + }, + 9 => { #52 + Name => 'OneShotAFRelease', + PrintConv => { + 0 => 'Focus Priority', + 1 => 'Release Priority', + }, + }, + 10 => { #52 + Name => 'AutoAFPointSelEOSiTRAF', + Description => 'Auto AF Point Sel EOS iTR AF', + # valid for: 1DX, 1DXmkII, 7DmkII, 5DS, 5DSR + # not valid for: 5DmkIII + Notes => 'only valid for some models', + Condition => '$$self{Model} !~ /5D /', + PrintConv => { + 0 => 'Enable', + 1 => 'Disable', + }, + }, + 11 => { #52 + Name => 'LensDriveWhenAFImpossible', + PrintConv => { + 0 => 'Continue Focus Search', + 1 => 'Stop Focus Search', + }, + }, + 12 => { #52 + Name => 'SelectAFAreaSelectionMode', + PrintConv => { BITMASK => { + 0 => 'Single-point AF', + 1 => 'Auto', # (61 point) + 2 => 'Zone AF', + 3 => 'AF Point Expansion (4 point)', + 4 => 'Spot AF', + 5 => 'AF Point Expansion (8 point)', + }}, + }, + 13 => { #52 + Name => 'AFAreaSelectionMethod', + PrintConv => { + 0 => 'M-Fn Button', + 1 => 'Main Dial', + }, + }, + 14 => { #52 + Name => 'OrientationLinkedAF', + PrintConv => { # Covers both 1Dx (0-2) and 5D3 (0-1) + 0 => 'Same for Vert/Horiz Points', + 1 => 'Separate Vert/Horiz Points', + 2 => 'Separate Area+Points', + }, + }, + 15 => { #52 + Name => 'ManualAFPointSelPattern', + PrintConv => { + 0 => 'Stops at AF Area Edges', + 1 => 'Continuous', + }, + }, + 16 => { #52 + Name => 'AFPointDisplayDuringFocus', + PrintConv => { + 0 => 'Selected (constant)', + 1 => 'All (constant)', + 2 => 'Selected (pre-AF, focused)', + 3 => 'Selected (focused)', + 4 => 'Disabled', + }, + }, + 17 => { #52 + Name => 'VFDisplayIllumination', + PrintConv => { + 0 => 'Auto', + 1 => 'Enable', + 2 => 'Disable', + }, + }, + 18 => { #52 + Name => 'AFStatusViewfinder', + Condition => '$$self{Model} =~ /1D X/', + Notes => '1D X only', + PrintConv => { + 0 => 'Show in Field of View', + 1 => 'Show Outside View', + }, + }, + 19 => { #52 + Name => 'InitialAFPointInServo', + Condition => '$$self{Model} =~ /1D X/', + Notes => '1D X only', + PrintConv => { + 0 => 'Initial AF Point Selected', + 1 => 'Manual AF Point', + 2 => 'Auto', #PH (1DXmkII) + }, + }, +); + +# RAW burst mode info (MakerNotes tag 0x403f) (ref 25) +%Image::ExifTool::Canon::RawBurstInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int32u', + FIRST_ENTRY => 1, + 1 => 'RawBurstImageNum', + 2 => 'RawBurstImageCount', +); + +# Canon UUID atoms (ref PH, SX280) +%Image::ExifTool::Canon::uuid = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Video' }, + WRITE_PROC => 'Image::ExifTool::QuickTime::WriteQuickTime', + NOTES => q{ + Tags extracted from the uuid atom of MP4 videos from cameras such as the + SX280, and CR3 images from cameras such as the EOS M50. + }, + CNCV => { + Name => 'CompressorVersion', + # use this to recognize the specific type of Canon RAW (CR3 or CRM) + RawConv => '$self->OverrideFileType($1) if $val =~ /^Canon(\w{3})/i; $val', + }, + # CNDM - 4 bytes - 0xff,0xd8,0xff,0xd9 + CNTH => { + Name => 'CanonCNTH', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CNTH' }, + }, + CCTP => { # (CR3 files) + Name => 'CanonCCTP', + SubDirectory => { + TagTable => 'Image::ExifTool::Canon::CCTP', + Start => '12', + }, + }, + # CTBO - (CR3 files) int32u entry count N, N x (int32u index, int64u offset, int64u size) + # index: 1=XMP, 2=PRVW, 3=mdat, 4=?, 5=? + # --> ignored when reading, but offsets are updated when writing + CMT1 => { # (CR3 files) + Name => 'IFD0', + PreservePadding => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + }, + }, + CMT2 => { # (CR3 files) + Name => 'ExifIFD', + PreservePadding => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + }, + }, + CMT3 => { # (CR3 files) + Name => 'MakerNoteCanon', + PreservePadding => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Canon::Main', + ProcessProc => \&ProcessCMT3, + WriteProc => \&Image::ExifTool::WriteTIFF, + }, + }, + CMT4 => { # (CR3 files) + Name => 'GPSInfo', + PreservePadding => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::GPS::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + DirName => 'GPS', + }, + }, + THMB => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + PreservePadding => 1, + RawConv => 'substr($val, 16)', + Binary => 1, + }, + CNOP => { #PH (M50) + Name => 'CanonCNOP', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CNOP' }, + }, +); + +# Canon top-level uuid atoms (ref PH, written by DPP4) +%Image::ExifTool::Canon::uuid2 = ( + WRITE_PROC => 'Image::ExifTool::QuickTime::WriteQuickTime', + CNOP => { + Name => 'CanonVRD', + PreservePadding => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::CanonVRD::Main', + WriteProc => 'Image::ExifTool::CanonVRD::WriteCanonDR4', + }, + }, +); + +%Image::ExifTool::Canon::UnknownIFD = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, +); + +# Canon CCTP atoms (ref PH, CR3 files) +%Image::ExifTool::Canon::CCTP = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Video' }, + # CCDT - int32u[3]: 0. 0, 1. decoder type?, 2. 0, 3. index +); + +# 'CMP1' atom information (ref 54, CR3 files) +%Image::ExifTool::Canon::CMP1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Image' }, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + PRIORITY => 0, + 8 => { Name => 'ImageWidth', Format => 'int32u' }, + 10 => { Name => 'ImageHeight', Format => 'int32u' }, + # (the rest of the documented tags don't seem to produced good values with my samples - PH) +); + +# 'CDI1' atom information (ref PH, CR3 files) +%Image::ExifTool::Canon::CDI1 = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Image' }, + IAD1 => { Name => 'IAD1', SubDirectory => { TagTable => 'Image::ExifTool::Canon::IAD1' } }, +); + +# 'IAD1' atom information (ref 54, CR3 files) +%Image::ExifTool::Canon::IAD1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Image' }, + FORMAT => 'int16u', + FIRST_ENTRY => 0, +); + +# Canon Timed MetaData (ref PH, CR3 files) +%Image::ExifTool::Canon::CTMD = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Image' }, + PROCESS_PROC => \&ProcessCTMD, + NOTES => q{ + Canon Timed MetaData tags found in CR3 images. The L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> option + is automatically applied when reading CR3 files to be able to extract this + information. + }, + 1 => { + Name => 'TimeStamp', + Groups => { 2 => 'Time' }, + RawConv => q{ + my $fmt = GetByteOrder() eq 'MM' ? 'x2nCCCCCC' : 'x2vCCCCCC'; + sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d.%.2d', unpack($fmt, $val)); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + # 3 - 4 bytes, seen: ff ff ff ff + 4 => { + Name => 'FocalInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::FocalInfo' }, + }, + 5 => { + Name => 'ExposureInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ExposureInfo' }, + }, + 7 => { + Name => 'ExifInfo7', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ExifInfo' }, + }, + 8 => { + Name => 'ExifInfo8', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ExifInfo' }, + }, + 9 => { + Name => 'ExifInfo9', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ExifInfo' }, + }, + # 10 - 60 bytes: all zeros with a pair of 0xff's at offset 0x02 (C200 CRM) + # 11 - 612 bytes: all zero with pairs of 0xff's at offset 0x6e and 0x116 (C200 CRM) +); + +# Canon Timed MetaData (ref PH, CR3 files) +%Image::ExifTool::Canon::ExifInfo = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Image' }, + PROCESS_PROC => \&ProcessExifInfo, + 0x8769 => { + Name => 'ExifIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + }, + }, + 0x927c => { + Name => 'MakerNoteCanon', + SubDirectory => { + TagTable => 'Image::ExifTool::Canon::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + }, + }, +); + +# timed focal length information (ref PH, CR3 files) +%Image::ExifTool::Canon::FocalInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Image' }, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + 0 => { + Name => 'FocalLength', + Format => 'rational32u', + PrintConv => 'sprintf("%.1f mm",$val)', + }, +); + +# timed exposure information (ref PH, CR3 files) +%Image::ExifTool::Canon::ExposureInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Image' }, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + 0 => { + Name => 'FNumber', + Format => 'rational32u', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + 1 => { + Name => 'ExposureTime', + Format => 'rational32u', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 2 => { + Name => 'ISO', + Format => 'int32u', + ValueConv => '$val & 0x7fffffff', # (not sure what high bit indicates) + }, +); + +%Image::ExifTool::Canon::CNTH = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Video' }, + VARS => { ATOM_COUNT => 1 }, # only one contained atom + WRITABLE => 1, + WRITE_PROC => 'Image::ExifTool::QuickTime::WriteQuickTime', + NOTES => q{ + Canon-specific QuickTime tags found in the CNTH atom of MOV/MP4 videos from + some cameras. + }, + CNDA => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Format => 'undef', + Notes => 'the full THM image, embedded metadata is extracted as the first sub-document', + SetBase => 1, + RawConv => q{ + $$self{DOC_NUM} = ++$$self{DOC_COUNT}; + $self->ExtractInfo(\$val, { ReEntry => 1 }); + $$self{DOC_NUM} = 0; + return \$val; + }, + RawConvInv => '$val', + }, +); + +# Canon CNOP atoms (ref PH) +%Image::ExifTool::Canon::CNOP = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Video' }, + # CNFB - 52 bytes (7DmkII,M50,C200) + # CNMI - 4 bytes: "0x20000001" (C200) + # CNCM - 48 bytes: original file name in bytes 24-31 (C200) +); + +# 'skip' atom of Canon MOV videos (ref PH) +%Image::ExifTool::Canon::Skip = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Canon', 2 => 'Video' }, + NOTES => 'Information found in the "skip" atom of Canon MOV videos.', + CNDB => { Name => 'Unknown_CNDB', Unknown => 1, Binary => 1 }, +); + +# Canon composite tags +%Image::ExifTool::Canon::Composite = ( + GROUPS => { 2 => 'Camera' }, + DriveMode => { + Require => { + 0 => 'ContinuousDrive', + 1 => 'SelfTimer', + }, + ValueConv => '$val[0] ? 0 : ($val[1] ? 1 : 2)', + PrintConv => { + 0 => 'Continuous Shooting', + 1 => 'Self-timer Operation', + 2 => 'Single-frame Shooting', + }, + }, + Lens => { + Require => { + 0 => 'Canon:MinFocalLength', + 1 => 'Canon:MaxFocalLength', + }, + ValueConv => '$val[0]', + PrintConv => 'Image::ExifTool::Canon::PrintFocalRange(@val)', + }, + Lens35efl => { + Description => 'Lens', + Require => { + 0 => 'Canon:MinFocalLength', + 1 => 'Canon:MaxFocalLength', + 3 => 'Lens', + }, + Desire => { + 2 => 'ScaleFactor35efl', + }, + ValueConv => '$val[3] * ($val[2] ? $val[2] : 1)', + PrintConv => '$prt[3] . ($val[2] ? sprintf(" (35 mm equivalent: %s)",Image::ExifTool::Canon::PrintFocalRange(@val)) : "")', + }, + ShootingMode => { + Require => { + 0 => 'CanonExposureMode', + 1 => 'EasyMode', + }, + Desire => { + 2 => 'BulbDuration', + }, + # most Canon models set CanonExposureMode to Manual (4) for Bulb shots, + # but the 1DmkIII uses a value of 7 for Bulb, so use this for other + # models too (Note that Canon DPP reports "Manual Exposure" here) + ValueConv => '$val[0] ? (($val[0] eq "4" and $val[2]) ? 7 : $val[0]) : $val[1] + 10', + PrintConv => '$val eq "7" ? "Bulb" : ($val[0] ? $prt[0] : $prt[1])', + }, + FlashType => { + Notes => q{ + may report "Built-in Flash" for some Canon cameras with external flash in + manual mode + }, + Require => { + 0 => 'FlashBits', + }, + RawConv => '$val[0] ? $val : undef', + ValueConv => '$val[0]&(1<<14)? 1 : 0', + PrintConv => { + 0 => 'Built-In Flash', + 1 => 'External', + }, + }, + RedEyeReduction => { + Require => { + 0 => 'CanonFlashMode', + 1 => 'FlashBits', + }, + RawConv => '$val[1] ? $val : undef', + ValueConv => '($val[0]==3 or $val[0]==4 or $val[0]==6) ? 1 : 0', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + # same as FlashExposureComp, but undefined if no flash + ConditionalFEC => { + Description => 'Flash Exposure Compensation', + Require => { + 0 => 'FlashExposureComp', + 1 => 'FlashBits', + }, + RawConv => '$val[1] ? $val : undef', + ValueConv => '$val[0]', + PrintConv => '$prt[0]', + }, + # hack to assume 1st curtain unless we see otherwise + ShutterCurtainHack => { + Description => 'Shutter Curtain Sync', + Desire => { + 0 => 'ShutterCurtainSync', + }, + Require => { + 1 => 'FlashBits', + }, + RawConv => '$val[1] ? $val : undef', + ValueConv => 'defined($val[0]) ? $val[0] : 0', + PrintConv => { + 0 => '1st-curtain sync', + 1 => '2nd-curtain sync', + }, + }, + WB_RGGBLevels => { + Require => { + 0 => 'Canon:WhiteBalance', + }, + Desire => { + 1 => 'WB_RGGBLevelsAsShot', + # indices of the following entries correspond to Canon:WhiteBalance + 2 + 2 => 'WB_RGGBLevelsAuto', + 3 => 'WB_RGGBLevelsDaylight', + 4 => 'WB_RGGBLevelsCloudy', + 5 => 'WB_RGGBLevelsTungsten', + 6 => 'WB_RGGBLevelsFluorescent', + 7 => 'WB_RGGBLevelsFlash', + 8 => 'WB_RGGBLevelsCustom', + 10 => 'WB_RGGBLevelsShade', + 11 => 'WB_RGGBLevelsKelvin', + }, + ValueConv => '$val[1] ? $val[1] : $val[($val[0] || 0) + 2]', + }, + ISO => { + Priority => 0, # let EXIF:ISO take priority + Desire => { + 0 => 'Canon:CameraISO', + 1 => 'Canon:BaseISO', + 2 => 'Canon:AutoISO', + }, + Notes => 'use CameraISO if numerical, otherwise calculate as BaseISO * AutoISO / 100', + ValueConv => q{ + return $val[0] if $val[0] and $val[0] =~ /^\d+$/; + return undef unless $val[1] and $val[2]; + return $val[1] * $val[2] / 100; + }, + PrintConv => 'sprintf("%.0f",$val)', + }, + DigitalZoom => { + Require => { + 0 => 'Canon:ZoomSourceWidth', + 1 => 'Canon:ZoomTargetWidth', + 2 => 'Canon:DigitalZoom', + }, + RawConv => q{ + ToFloat(@val); + return undef unless $val[2] and $val[2] == 3 and $val[0] and $val[1]; + return $val[1] / $val[0]; + }, + PrintConv => 'sprintf("%.2fx",$val)', + }, + OriginalDecisionData => { + Flags => ['Writable','Protected'], + WriteGroup => 'MakerNotes', + Require => 'OriginalDecisionDataOffset', + RawConv => 'Image::ExifTool::Canon::ReadODD($self,$val[0])', + }, + FileNumber => { + Groups => { 2 => 'Image' }, + Writable => 1, + WriteCheck => '$val=~/\d+-\d+/ ? undef : "Invalid format"', + DelCheck => '"Can\'t delete"', + Require => { + 0 => 'DirectoryIndex', + 1 => 'FileIndex', + }, + WriteAlso => { + DirectoryIndex => '$val=~/(\d+)-(\d+)/; $1', + FileIndex => '$val=~/(\d+)-(\d+)/; $2', + }, + ValueConv => q{ + # fix the funny things that these numbers do when they wrap over 9999 + # (it seems that FileIndex and DirectoryIndex actually store the + # numbers from the previous image, so we need special logic + # to handle the FileIndex wrap properly) + $val[1] == 10000 and $val[1] = 1, ++$val[0]; + return sprintf("%.3d%.4d",@val); + }, + PrintConv => '$_=$val;s/(\d+)(\d{4})/$1-$2/;$_', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Canon'); + +#------------------------------------------------------------------------------ +# Return lens name with teleconverter if applicable +# Inputs: 0) lens name string, 1) short focal length +# Returns: lens string with tc if appropriate +sub LensWithTC($$) +{ + my ($lens, $shortFocal) = @_; + + # add teleconverter multiplication factor if applicable + # (and if the LensType doesn't already include one) + if (not $lens =~ /x$/ and $lens =~ /(\d+)/) { + my $sf = $1; # short focal length + my $tc; + foreach $tc (1, 1.4, 2, 2.8) { + next if abs($shortFocal - $sf * $tc) > 0.9; + $lens .= " + ${tc}x" if $tc > 1; + last; + } + } + return $lens; +} + +#------------------------------------------------------------------------------ +# Attempt to calculate sensor size for Canon cameras +# Inputs: 0/1) rational values for FocalPlaneX/YResolution +# Returns: Sensor diagonal size in mm, or undef +# Notes: This algorithm is fairly reliable, but has been found to give incorrect +# values for some firmware versions of the EOS 20D, A310, SD40 and IXUS 65 +# (ref http://wyw.dcweb.cn/download.asp?path=&file=jhead-2.96-ccdwidth_hack.zip) +sub CalcSensorDiag($$) +{ + my ($xres, $yres) = @_; + # most Canon cameras store the sensor size in the denominator + if ($xres and $yres) { + # assumptions: 1) numerators are image width/height * 1000 + # 2) denominators are sensor width/height in inches * 1000 + my @xres = split /[ \/]/, $xres; + my @yres = split /[ \/]/, $yres; + # verify assumptions as best we can: + # numerators are always divisible by 1000 + if ($xres[0] % 1000 == 0 and $yres[0] % 1000 == 0 and + # at least 640x480 pixels (DC models - PH) + $xres[0] >= 640000 and $yres[0] >= 480000 and + # ... but not too big! + $xres[0] < 10000000 and $yres[0] < 10000000 and + # minimum sensor size is 0.061 inches (DC models - PH) + $xres[1] >= 61 and $xres[1] < 1500 and + $yres[1] >= 61 and $yres[1] < 1000 and + # sensor isn't square (may happen if rationals have been reduced) + $xres[1] != $yres[1]) + { + return sqrt($xres[1]*$xres[1] + $yres[1]*$yres[1]) * 0.0254; + } + } + return undef; +} + +#------------------------------------------------------------------------------ +# Attempt to identify the specific lens if multiple lenses have the same LensType +# Inputs: 0) PrintConv hash ref, 1) LensType, 2) MinFocalLength, 3) MaxFocalLength +# 4) MaxAperture, 5) LensModel +# Notes: PrintConv, LensType, MinFocalLength and MaxFocalLength must be defined. +# Other inputs are optional. +sub PrintLensID(@) +{ + my ($printConv, $lensType, $shortFocal, $longFocal, $maxAperture, $lensModel) = @_; + my $lens; + $lens = $$printConv{$lensType} unless $lensType eq '-1' or $lensType eq '65535'; + if ($lens) { + # return this lens unless other lenses have the same LensType + return LensWithTC($lens, $shortFocal) unless $$printConv{"$lensType.1"}; + $lens =~ s/ or .*//s; # remove everything after "or" + # make list of all possible matching lenses + my @lenses = ( $lens ); + my $i; + for ($i=1; $$printConv{"$lensType.$i"}; ++$i) { + push @lenses, $$printConv{"$lensType.$i"}; + } + my ($tc, @user, @maybe, @likely, @matches); + # look for lens in user-defined lenses + foreach $lens (@lenses) { + push @user, $lens if $Image::ExifTool::userLens{$lens}; + } + # attempt to determine actual lens + foreach $tc (1, 1.4, 2, 2.8) { # loop through teleconverter scaling factors + foreach $lens (@lenses) { + next unless $lens =~ /(\d+)(?:-(\d+))?mm.*?(?:[fF]\/?)(\d+(?:\.\d+)?)(?:-(\d+(?:\.\d+)?))?/; + # ($1=short focal, $2=long focal, $3=max aperture wide, $4=max aperture tele) + my ($sf, $lf, $sa, $la) = ($1, $2, $3, $4); + # see if we can rule out this lens by focal length or aperture + $lf = $sf if $sf and not $lf; + $la = $sa if $sa and not $la; + # account for converter-specific LensType's (ie. end with " + #.#x") + if ($lens =~ / \+ (\d+(\.\d+)?)x$/) { + $sf *= $1; $lf *= $1; + $sa *= $1; $la *= $1; + } + next if abs($shortFocal - $sf * $tc) > 0.9; + my $tclens = $lens; + $tclens .= " + ${tc}x" if $tc > 1; + push @maybe, $tclens; + next if abs($longFocal - $lf * $tc) > 0.9; + push @likely, $tclens; + if ($maxAperture) { + # (not 100% sure that TC affects MaxAperture, but it should!) + # (RF 24-105mm F4L IS USM shows a MaxAperture of 4.177) + next if $maxAperture < $sa * $tc - 0.18; + next if $maxAperture > $la * $tc + 0.18; + } + push @matches, $tclens; + } + last if @maybe; + } + if (@user) { + # choose the best match if we have more than one + if (@user > 1) { + my ($try, @good); + foreach $try (\@matches, \@likely, \@maybe) { + foreach (@$try) { + $Image::ExifTool::userLens{$_} and push(@good, $_), next; + # check for match with TC string removed + next unless /^(.*) \+ \d+(\.\d+)?x$/; + $Image::ExifTool::userLens{$1} and push(@good, $_); + } + return join(' or ', @good) if @good; + } + } + # default to returning the first user-defined lens + return LensWithTC($user[0], $shortFocal); + } + # differentiate Sigma Art/Contemporary/Sports models + if (@matches > 1 and $lensModel and $lensModel =~ /(\| [ACS])/) { + my $type = $1; + my @best; + foreach $lens (@matches) { + push @best, $lens if $lens =~ /\Q$type/; + } + @matches = @best if @best; + } + @matches = @likely unless @matches; + @matches = @maybe unless @matches; + Image::ExifTool::Exif::MatchLensModel(\@matches, $lensModel); + return join(' or ', @matches) if @matches; + } elsif ($lensModel and $lensModel =~ /\d/) { + # use lens model as written by the camera + if ($printConv eq \%canonLensTypes) { + # add "Canon" to the start since the Canon cameras only understand Canon lenses + return "Canon $lensModel"; + } else { + return $lensModel; + } + } + my $str = ''; + if ($shortFocal) { + $str .= sprintf(' %d', $shortFocal); + $str .= sprintf('-%d', $longFocal) if $longFocal and $longFocal != $shortFocal; + $str .= 'mm'; + } + # (careful because Sigma LensType's may not be integer, so use string comparison) + return "Unknown$str" if $lensType eq '-1' or $lensType eq '65535'; + return "Unknown ($lensType)$str"; +} + +#------------------------------------------------------------------------------ +# Swap 16-bit words in 32-bit integers +# Inputs: 0) string of integers +# Returns: string of word-swapped integers +sub SwapWords($) +{ + my @a = split(' ', shift); + $_ = (($_ >> 16) | ($_ << 16)) & 0xffffffff foreach @a; + return "@a"; +} + +#------------------------------------------------------------------------------ +# Validate first word of Canon binary data +# Inputs: 0) data pointer, 1) offset, 2-N) list of valid values +# Returns: true if data value is the same +sub Validate($$@) +{ + my ($dataPt, $offset, @vals) = @_; + # the first 16-bit value is the length of the data in bytes + my $dataVal = Image::ExifTool::Get16u($dataPt, $offset); + my $val; + foreach $val (@vals) { + return 1 if $val == $dataVal; + } + return undef; +} + +#------------------------------------------------------------------------------ +# Validate CanonAFInfo +# Inputs: 0) data pointer, 1) offset, 2) size +# Returns: true if data appears valid +sub ValidateAFInfo($$$) +{ + my ($dataPt, $offset, $size) = @_; + return 0 if $size < 24; # must be at least 24 bytes long (PowerShot Pro1) + my $af = Get16u($dataPt, $offset); + return 0 if $af !~ /^(1|5|7|9|15|45|53)$/; # check NumAFPoints + my $w1 = Get16u($dataPt, $offset + 4); + my $h1 = Get16u($dataPt, $offset + 6); + return 0 unless $h1 and $w1; + my $f1 = $w1 / $h1; + # check for normal aspect ratio + return 1 if abs($f1 - 1.33) < 0.01 or abs($f1 - 1.67) < 0.01; + # ZoomBrowser can modify this for rotated images (ref Joshua Bixby) + return 1 if abs($f1 - 0.75) < 0.01 or abs($f1 - 0.60) < 0.01; + my $w2 = Get16u($dataPt, $offset + 8); + my $h2 = Get16u($dataPt, $offset + 10); + return 0 unless $h2 and $w2; + # compare aspect ratio with AF image size + # (but the Powershot AFImageHeight is odd, hence the test above) + return 0 if $w1 eq $h1; + my $f2 = $w2 / $h2; + return 1 if abs(1-$f1/$f2) < 0.01; + return 1 if abs(1-$f1*$f2) < 0.01; + return 0; +} + +#------------------------------------------------------------------------------ +# Read original decision data from file (variable length) +# Inputs: 0) ExifTool object ref, 1) offset in file +# Returns: reference to original decision data (or undef if no data) +sub ReadODD($$) +{ + my ($et, $offset) = @_; + return undef unless $offset; + my ($raf, $buff, $buf2, $i, $warn); + return undef unless defined($raf = $$et{RAF}); + # the data block is a variable length and starts with 0xffffffff + # followed a 4-byte (int32u) version number + my $pos = $raf->Tell(); + if ($raf->Seek($offset, 0) and $raf->Read($buff, 8)==8 and $buff=~/^\xff{4}.\0\0/s) { + my $err = 1; + # must set byte order in case it is different than current byte order + # (we could be reading this after byte order was changed) + my $oldOrder = GetByteOrder(); + my $version = Get32u(\$buff, 4); + if ($version > 20) { + ToggleByteOrder(); + $version = unpack('N',pack('V',$version)); + } + if ($version == 1 or # 1Ds (big endian) + $version == 2) # 5D/20D (little endian) + { + # this data is structured as follows: + # 4 bytes: all 0xff + # 4 bytes: version number (=1 or 2) + # 20 bytes: sha1 + # 4 bytes: record count + # for each record: + # | 4 bytes: record number (beginning at 0) + # | 4 bytes: block offset + # | 4 bytes: block length + # | 20 bytes: block sha1 + if ($raf->Read($buf2, 24) == 24) { + $buff .= $buf2; + my $count = Get32u(\$buf2, 20); + # read all records if the count is reasonable + if ($count and $count < 20 and + $raf->Read($buf2, $count * 32) == $count * 32) + { + $buff .= $buf2; + undef $err; + } + } + } elsif ($version == 3) { # newer models (little endian) + # this data is structured as follows: + # 4 bytes: all 0xff + # 4 bytes: version number (=3) + # 24 bytes: sha1 A length (=20) + sha1 A + # 24 bytes: sha1 B length (=20) + sha1 B + # 4 bytes: length of remaining data (including this length word!) + # 8 bytes: salt length (=4) + salt ? + # 4 bytes: unknown (=3) + # 4 bytes: size of file + # 4 bytes: unknown (=1 for most models, 2 for 5DmkII) + # 4 bytes: unknown (=1) + # 4 bytes: unknown (always the same for a given firmware version) + # 4 bytes: unknown (random) + # 4 bytes: record count + # for each record: + # | 4 bytes: record number (beginning at 1) + # | 8 bytes: salt length (=4) + salt ? + # | 24 bytes: sha1 length (=20) + sha1 + # | 4 bytes: block count + # | for each block: + # | | 4 bytes: block offset + # | | 4 bytes: block length + # followed by zero padding to end of ODD data (~72 bytes) + for ($i=0; ; ++$i) { + $i == 3 and undef $err, last; # success! + $raf->Read($buf2, 4) == 4 or last; + $buff .= $buf2; + my $len = Get32u(\$buf2, 0); + # (the data length includes the length word itself - doh!) + $len -= 4 if $i == 2 and $len >= 4; + # make sure records are a reasonable size (<= 64kB) + $len <= 0x10000 and $raf->Read($buf2, $len) == $len or last; + $buff .= $buf2; + } + } else { + $warn = "Unsupported original decision data version $version"; + } + SetByteOrder($oldOrder); + unless ($err) { + if ($et->Options('HtmlDump')) { + $et->HDump($offset, length $buff, '[OriginalDecisionData]', undef); + } + $raf->Seek($pos, 0); # restore original file position + return \$buff; + } + } + $et->Warn($warn || 'Invalid original decision data'); + $raf->Seek($pos, 0); # restore original file position + return undef; +} + +#------------------------------------------------------------------------------ +# Convert the CameraISO value +# Inputs: 0) value, 1) set for inverse conversion +sub CameraISO($;$) +{ + my ($val, $inv) = @_; + my $rtnVal; + my %isoLookup = ( + 0 => 'n/a', + 14 => 'Auto High', #PH (S3IS) + 15 => 'Auto', + 16 => 50, + 17 => 100, + 18 => 200, + 19 => 400, + 20 => 800, #PH + ); + if ($inv) { + $rtnVal = Image::ExifTool::ReverseLookup($val, \%isoLookup); + if (not defined $rtnVal and Image::ExifTool::IsInt($val)) { + $rtnVal = ($val & 0x3fff) | 0x4000; + } + } elsif ($val != 0x7fff) { + if ($val & 0x4000) { + $rtnVal = $val & 0x3fff; + } else { + $rtnVal = $isoLookup{$val} || "Unknown ($val)"; + } + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Print range of focal lengths +# Inputs: 0) short focal, 1) long focal, 2) optional scaling factor +sub PrintFocalRange(@) +{ + my ($short, $long, $scale) = @_; + + $scale or $scale = 1; + if ($short == $long) { + return sprintf("%.1f mm", $short * $scale); + } else { + return sprintf("%.1f - %.1f mm", $short * $scale, $long * $scale); + } +} + +#------------------------------------------------------------------------------ +# Process a serial stream of binary data +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# Notes: The tagID's for serial stream tags are consecutive indices beginning +# at 0, and the corresponding values must be contiguous in memory. +# "Unknown" tags must be used to skip padding or unknown values. +# (does not yet extract Rational values) +sub ProcessSerialData($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $offset = $$dirInfo{DirStart}; + my $size = $$dirInfo{DirLen}; + my $base = $$dirInfo{Base} || 0; + my $verbose = $et->Options('Verbose'); + my $dataPos = $$dirInfo{DataPos} || 0; + + # temporarily set Unknown option so GetTagInfo() will return existing unknown tags + # (require to maintain serial data synchronization) + my $unknown = $et->Options(Unknown => 1); + # but disable unknown tag generation (because processing ends when we run out of tags) + $$et{NO_UNKNOWN} = 1; + + $verbose and $et->VerboseDir('SerialData', undef, $size); + + # get default format ('int8u' unless specified) + my $defaultFormat = $$tagTablePtr{FORMAT} || 'int8u'; + + my ($index, %val); + my $pos = 0; + for ($index=0; $$tagTablePtr{$index} and $pos <= $size; ++$index) { + my $tagInfo = $et->GetTagInfo($tagTablePtr, $index) or last; + my $format = $$tagInfo{Format}; + my $count = 1; + if ($format) { + if ($format =~ /(.*)\[(.*)\]/) { + $format = $1; + $count = $2; + # evaluate count to allow count to be based on previous values + #### eval Format (%val, $size) + $count = eval $count; + $@ and warn("Format $$tagInfo{Name}: $@"), last; + } elsif ($format eq 'string') { + # allow string with no specified count to run to end of block + $count = ($size > $pos) ? $size - $pos : 0; + } + } else { + $format = $defaultFormat; + } + my $len = (Image::ExifTool::FormatSize($format) || 1) * $count; + last if $pos + $len > $size; + my $val = ReadValue($dataPt, $pos+$offset, $format, $count, $size-$pos); + last unless defined $val; + if ($verbose) { + $et->VerboseInfo($index, $tagInfo, + Index => $index, + Table => $tagTablePtr, + Value => $val, + DataPt => $dataPt, + Size => $len, + Start => $pos+$offset, + Addr => $pos+$offset+$base+$dataPos, + Format => $format, + Count => $count, + ); + } + $val{$index} = $val; + if ($$tagInfo{SubDirectory}) { + my $subTablePtr = GetTagTable($$tagInfo{SubDirectory}{TagTable}); + my %dirInfo = ( + DataPt => \$val, + DataPos => $dataPos + $pos, + DirStart => 0, + DirLen => length($val), + ); + $et->ProcessDirectory(\%dirInfo, $subTablePtr); + } elsif (not $$tagInfo{Unknown} or $unknown) { + # don't extract zero-length information + $et->FoundTag($tagInfo, $val) if $count; + } + $pos += $len; + } + $et->Options(Unknown => $unknown); # restore Unknown option + delete $$et{NO_UNKNOWN}; + return 1; +} + +#------------------------------------------------------------------------------ +# Print 1D AF points +# Inputs: 0) value to convert +# Focus point pattern: +# A1 A2 A3 A4 A5 A6 A7 +# B1 B2 B3 B4 B5 B6 B7 B8 B9 B10 +# C1 C2 C3 C4 C5 C6 C7 C9 C9 C10 C11 +# D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 +# E1 E2 E3 E4 E5 E6 E7 +sub PrintAFPoints1D($) +{ + my $val = shift; + return 'Unknown' unless length $val == 8; + # list of focus point values for decoding the first byte of the 8-byte record. + # they are the x/y positions of each bit in the AF point mask + # (y is upper 3 bits / x is lower 5 bits) + my @focusPts = (0,0, + 0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10, 0,0, + 0x21,0x23,0x25,0x27,0x29,0x2b,0x2d,0x2f,0x31,0x33, + 0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4d,0x50,0x52,0x54, + 0x61,0x63,0x65,0x67,0x69,0x6b,0x6d,0x6f,0x71,0x73, 0,0, + 0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90, 0,0,0,0,0 + ); + my $focus = unpack('C',$val); + my @bits = split //, unpack('b*',substr($val,1)); + my @rows = split //, ' AAAAAAA BBBBBBBBBBCCCCCCCCCCCDDDDDDDDDD EEEEEEE '; + my ($focusing, $focusPt, @points); + my $lastRow = ''; + my $col = 0; + foreach $focusPt (@focusPts) { + my $row = shift @rows; + $col = ($row eq $lastRow) ? $col + 1 : 1; + $lastRow = $row; + $focusing = "$row$col" if $focus eq $focusPt; + push @points, "$row$col" if shift @bits; + } + $focusing or $focusing = ($focus == 0xff) ? 'Auto' : sprintf('Unknown (0x%.2x)',$focus); + return "$focusing (" . join(',',@points) . ')'; +} + +#------------------------------------------------------------------------------ +# Convert Canon hex-based EV (modulo 0x20) to real number +# Inputs: 0) value to convert +# eg) 0x00 -> 0 +# 0x0c -> 0.33333 +# 0x10 -> 0.5 +# 0x14 -> 0.66666 +# 0x20 -> 1 ... etc +sub CanonEv($) +{ + my $val = shift; + my $sign; + # temporarily make the number positive + if ($val < 0) { + $val = -$val; + $sign = -1; + } else { + $sign = 1; + } + my $frac = $val & 0x1f; + $val -= $frac; # remove fraction + # Convert 1/3 and 2/3 codes + if ($frac == 0x0c) { + $frac = 0x20 / 3; + } elsif ($frac == 0x14) { + $frac = 0x40 / 3; + } + return $sign * ($val + $frac) / 0x20; +} + +#------------------------------------------------------------------------------ +# Convert number to Canon hex-based EV (modulo 0x20) +# Inputs: 0) number +# Returns: Canon EV code +sub CanonEvInv($) +{ + my $num = shift; + my $sign; + # temporarily make the number positive + if ($num < 0) { + $num = -$num; + $sign = -1; + } else { + $sign = 1; + } + my $val = int($num); + my $frac = $num - $val; + if (abs($frac - 0.33) < 0.05) { + $frac = 0x0c + } elsif (abs($frac - 0.67) < 0.05) { + $frac = 0x14; + } else { + $frac = int($frac * 0x20 + 0.5); + } + return $sign * ($val * 0x20 + $frac); +} + +#------------------------------------------------------------------------------ +# Read CMT3 maker notes from CR3 file +# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table ref +# Returns: data block (may be empty if no Exif data) or undef on error +sub ProcessCMT3($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + + # extract the static maker notes to copying to other file types if requested + # Note: this won't copy makernotes in the timed metadata since these are stored + # separately, but the only records they have that aren't in the static maker notes + # (for the M50) are: ColorData9, Flavor, CanonCameraInfoUnknown, + # VignettingCorrUnknown1, Canon_0x4033 and Canon_0x402e + if (($et->Options('MakerNotes') or $$et{REQ_TAG_LOOKUP}{makernotecanon}) and + $$dirInfo{DirLen} > 8) + { + my $dataPt = $$dirInfo{DataPt}; + # remove old (unused) trailer + $$dataPt =~ s/(II\x2a\0|MM\0\x2a)\0{4,10}$//; + # remove TIFF header and append as the Canon makernote trailer + # (so offsets will be interpreted correctly) + my $val = substr($$dataPt,8) . substr($$dataPt,0,8); + $et->FoundTag($Image::ExifTool::Canon::uuid{CMT3}, \$val); + } + return $et->ProcessTIFF($dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Process CTMD EXIF information +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessExifInfo($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $start); + my $dirEnd = $start + $dirLen; + # loop through TIFF-format EXIF/MakerNote records + my ($pos, $len, $tag); + for ($pos = $start; $pos + 8 < $dirEnd; $pos += $len) { + $len = Get32u($dataPt, $pos); + $tag = Get32u($dataPt, $pos + 4); + # test size/tag for valid ExifInfo (not EXIF in CRM files) + last if $len < 8 or $pos + $len > $dirEnd or not $$tagTablePtr{$tag}; + $et->VerboseDir('ExifInfo', undef, $dirLen) if $pos == $start; + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => $dataPt, + Base => $$dirInfo{Base} + $pos + 8, # base for TIFF pointers + DataPos => -($pos + 8), # (relative to Base) + Start => $pos + 8, + Size => $len - 8, + ); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process Canon Timed MetaData (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessCTMD($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $verbose = $et->Options('Verbose'); + my $dirLen = length $$dataPt; + my $pos = 0; + SetByteOrder('II'); + while ($pos + 6 < $dirLen) { + my $size = Get32u($dataPt, $pos); + my $type = Get16u($dataPt, $pos + 4); + # what is the meaning of the 6-byte header of these records?: + # type 1 - 00 00 00 01 zz zz - TimeStamp(CR3/CRM); zz=00(CR3),ff(CRM) + # type 3 - 00 00 00 01 zz zz - ? "ff ff ff ff"; zz=00(CR3),ff(CRM) + # type 4 - 00 00 00 01 ff ff - FocalInfo(CR3/CRM) + # type 5 - 00 00 00 01 ff ff - ExposureInfo(CR3/CRM) + # type 6 - 00 04 00 01 ff ff - ? "03 04 00 80 e0 15 ff ff"(CRM) [0x15e0 = ColorTemperature?] + # type 7 - xx yy 00 01 ff ff - ExifIFD + MakerNotes(CR3), ?(CRM); xxyy=0101(CR3),0004(CRM) + # type 8 - 01 yy 00 01 ff ff - MakerNotes(CR3), ?(CRM); yy=01(CR3),04(CRM) + # type 9 - 01 yy 00 01 ff ff - MakerNotes(CR3), ?(CRM); yy=01(CR3),00(CRM) + # type 10- 01 00 00 01 ff ff - ? (CRM) + # type 11- 01 00 00 01 ff ff - ? (CRM) + # --> maybe yy == 01 for ExifInfo? + $size < 12 and $et->Warn('Short CTMD record'), last; + $pos + $size > $dirLen and $et->Warn('Truncated CTMD record'), last; + $et->VerboseDir("CTMD type $type", undef, $size - 6); + HexDump($dataPt, 6, # dump 6-byte header + Start => $pos + 6, + Addr => $$dirInfo{Base} + $pos + 6, + Prefix => $$et{INDENT}, + ) if $verbose > 2; + if ($$tagTablePtr{$type}) { + $et->HandleTag($tagTablePtr, $type, undef, + DataPt => $dataPt, + Base => $$dirInfo{Base}, + Start => $pos + 12, + Size => $size - 12, + ); + } elsif ($verbose) { + $et->VerboseDump($dataPt, Len=>$size-12, Start=>$pos+12, DataPos=>$$dirInfo{Base}); + } + $pos += $size; + } + $et->Warn('Error parsing Canon CTMD data', 1) if $pos != $dirLen; + return 1; +} + +#------------------------------------------------------------------------------ +# Process a creative filter data +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessFilters($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + my $dataPos = $$dirInfo{DataPos} || 0; + my $end = $pos + $dirLen; + my $verbose = $et->Options('Verbose'); + + return 0 if $dirLen < 8; + my $numFilters = Get32u($dataPt, $pos + 4); + $verbose and $et->VerboseDir('Creative Filter', $numFilters); + $pos += 8; + my ($i, $j, $err); + for ($i=0; $i<$numFilters; ++$i) { + # read filter structure: + # 4 bytes - filter number + # 4 bytes - filter data length + # 4 bytes - number of parameters: + # | 4 bytes - parameter ID + # | 4 bytes - parameter value count + # | 4 bytes * count - parameter values (NC) + $pos + 12 > $end and $err = "Truncated data for filter $i", last; + my $fnum = Get32u($dataPt, $pos); # (is this an index or an ID?) + my $size = Get32u($dataPt, $pos + 4); + my $nparm = Get32u($dataPt, $pos + 8); + my $nxt = $pos + 4 + $size; + $nxt > $end and $err = "Invalid size ($size) for filter $i", last; + $verbose and $et->VerboseDir("Filter $fnum", $nparm, $size); + $pos += 12; + for ($j=0; $j<$nparm; ++$j) { + $pos + 12 > $end and $err = "Truncated data for filter $i param $j", last; + my $tag = Get32u($dataPt, $pos); + my $count = Get32u($dataPt, $pos + 4); + $pos += 8; + $pos + 4 * $count > $end and $err = "Truncated value for filter $i param $j", last; + my $val = ReadValue($dataPt, $pos, 'int32s', $count, 4 * $count); + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos, + Size => 4 * $count, + ); + $pos += 4 * $count; + } + $pos = $nxt; # step to next filter + } + $err and $et->Warn($err, 1); + return 1; +} + +#------------------------------------------------------------------------------ +# Write Canon maker notes +# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table ref +# Returns: data block (may be empty if no Exif data) or undef on error +sub WriteCanon($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + my $dirData = Image::ExifTool::Exif::WriteExif($et, $dirInfo, $tagTablePtr); + # add footer which is written by some Canon models (format of a TIFF header) + if (defined $dirData and length $dirData and $$dirInfo{Fixup}) { + $dirData .= GetByteOrder() . Set16u(42) . Set32u(0); + $$dirInfo{Fixup}->AddFixup(length($dirData) - 4); + } + return $dirData; +} + +#------------------------------------------------------------------------------ +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Canon - Canon 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 +Canon maker notes in EXIF information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html> + +=item L<http://www.wonderland.org/crw/> + +=item L<http://www.cybercom.net/~dcoffin/dcraw/> + +=item L<http://homepage3.nifty.com/kamisaka/makernote/makernote_canon.htm> + +=item (...plus lots of testing with my 300D, A570IS and G12!) + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks Michael Rommel and Daniel Pittman for information they provided about +the Digital Ixus and PowerShot S70 cameras, Juha Eskelinen and Emil Sit for +figuring out the 20D and 30D FileNumber, Denny Priebe for figuring out a +couple of 1D tags, and Michael Tiemann, Rainer Honle, Dave Nicholson, Chris +Huebsch, Ger Vermeulen, Darryl Zurn, D.J. Cristi, Bogdan, Vesa Kivisto and +Kai Harrekilde-Petersen for decoding a number of new tags. Also thanks to +everyone who made contributions to the LensType lookup list or the meanings +of other tag values. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Canon Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/CanonCustom.pm b/ExifTool/lib/Image/ExifTool/CanonCustom.pm new file mode 100644 index 0000000..4f0b894 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/CanonCustom.pm @@ -0,0 +1,2900 @@ +#------------------------------------------------------------------------------ +# File: CanonCustom.pm +# +# Description: Read and write Canon Custom functions +# +# Revisions: 11/25/2003 - P. Harvey Created +# +# References: 1) http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html +# 2) Christian Koller private communication (20D) +# 3) Rainer Honle private communication (5D) +# 4) David Pitcher private communication (1DmkIII firmware upgrade) +#------------------------------------------------------------------------------ + +package Image::ExifTool::CanonCustom; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess); +use Image::ExifTool::Canon; +use Image::ExifTool::Exif; + +$VERSION = '1.58'; + +sub ProcessCanonCustom($$$); +sub ProcessCanonCustom2($$$); +sub WriteCanonCustom($$$); +sub WriteCanonCustom2($$$); +sub CheckCanonCustom($$$); +sub ConvertPFn($); +sub ConvertPFnInv($); + +my %onOff = ( 0 => 'On', 1 => 'Off' ); +my %offOn = ( 0 => 'Off', 1 => 'On' ); +my %disableEnable = ( 0 => 'Disable', 1 => 'Enable' ); +my %enableDisable = ( 0 => 'Enable', 1 => 'Disable' ); +my %convPFn = ( PrintConv => \&ConvertPfn, PrintConvInv => \&ConvertPfnInv ); + +#------------------------------------------------------------------------------ +# Custom functions for the 1D +# CanonCustom (keys are custom function number) +%Image::ExifTool::CanonCustom::Functions1D = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonCustom, + WRITE_PROC => \&WriteCanonCustom, + CHECK_PROC => \&CheckCanonCustom, + WRITABLE => 'int8u', + NOTES => q{ + These custom functions are used by all 1D models up to but not including the + Mark III. + }, + 0 => { + Name => 'FocusingScreen', + PrintConv => { + 0 => 'Ec-N, R', + 1 => 'Ec-A,B,C,CII,CIII,D,H,I,L', + }, + }, + 1 => { + Name => 'FinderDisplayDuringExposure', + PrintConv => \%offOn, + }, + 2 => { + Name => 'ShutterReleaseNoCFCard', + Description => 'Shutter Release W/O CF Card', + PrintConv => { + 0 => 'Yes', + 1 => 'No', + }, + }, + 3 => { + Name => 'ISOSpeedExpansion', + Description => 'ISO Speed Expansion', + PrintConv => { + 0 => 'No', + 1 => 'Yes', + }, + }, + 4 => { + Name => 'ShutterAELButton', + Description => 'Shutter Button/AEL Button', + PrintConv => { + 0 => 'AF/AE lock stop', + 1 => 'AE lock/AF', + 2 => 'AF/AF lock, No AE lock', + 3 => 'AE/AF, No AE lock', + }, + }, + 5 => { + Name => 'ManualTv', + Description => 'Manual Tv/Av For M', + PrintConv => { + 0 => 'Tv=Main/Av=Control', + 1 => 'Tv=Control/Av=Main', + 2 => 'Tv=Main/Av=Main w/o lens', + 3 => 'Tv=Control/Av=Main w/o lens', + }, + }, + 6 => { + Name => 'ExposureLevelIncrements', + PrintConv => { + 0 => '1/3-stop set, 1/3-stop comp.', + 1 => '1-stop set, 1/3-stop comp.', + 2 => '1/2-stop set, 1/2-stop comp.', + }, + }, + 7 => { + Name => 'USMLensElectronicMF', + PrintConv => { + 0 => 'Turns on after one-shot AF', + 1 => 'Turns off after one-shot AF', + 2 => 'Always turned off', + }, + }, + 8 => { + Name => 'LCDPanels', + Description => 'Top/Back LCD Panels', + PrintConv => { + 0 => 'Remain. shots/File no.', + 1 => 'ISO/Remain. shots', + 2 => 'ISO/File no.', + 3 => 'Shots in folder/Remain. shots', + }, + }, + 9 => { + Name => 'AEBSequenceAutoCancel', + Description => 'AEB Sequence/Auto Cancel', + PrintConv => { + 0 => '0,-,+/Enabled', + 1 => '0,-,+/Disabled', + 2 => '-,0,+/Enabled', + 3 => '-,0,+/Disabled', + }, + }, + 10 => { + Name => 'AFPointIllumination', + PrintConv => { + 0 => 'On', + 1 => 'Off', + 2 => 'On without dimming', + 3 => 'Brighter', + }, + }, + 11 => { + Name => 'AFPointSelection', + PrintConv => { + 0 => 'H=AF+Main/V=AF+Command', + 1 => 'H=Comp+Main/V=Comp+Command', + 2 => 'H=Command only/V=Assist+Main', + 3 => 'H=FEL+Main/V=FEL+Command', + }, + }, + 12 => { + Name => 'MirrorLockup', + PrintConv => \%disableEnable, + }, + 13 => { + Name => 'AFPointSpotMetering', + Description => 'No. AF Points/Spot Metering', + PrintConv => { + 0 => '45/Center AF point', + 1 => '11/Active AF point', + 2 => '11/Center AF point', + 3 => '9/Active AF point', + }, + }, + 14 => { + Name => 'FillFlashAutoReduction', + PrintConv => \%enableDisable, + }, + 15 => { + Name => 'ShutterCurtainSync', + PrintConv => { + 0 => '1st-curtain sync', + 1 => '2nd-curtain sync', + }, + }, + 16 => { + Name => 'SafetyShiftInAvOrTv', + PrintConv => \%disableEnable, + }, + 17 => { + Name => 'AFPointActivationArea', + PrintConv => { + 0 => 'Single AF point', + 1 => 'Expanded (TTL. of 7 AF points)', + 2 => 'Automatic expanded (max. 13)', + }, + }, + 18 => { + Name => 'SwitchToRegisteredAFPoint', + PrintConv => { + 0 => 'Assist + AF', + 1 => 'Assist', + 2 => 'Only while pressing assist', + }, + }, + 19 => { + Name => 'LensAFStopButton', + PrintConv => { + 0 => 'AF stop', + 1 => 'AF start', + 2 => 'AE lock while metering', + 3 => 'AF point: M -> Auto / Auto -> Ctr.', + 4 => 'AF mode: ONE SHOT <-> AI SERVO', + 5 => 'IS start', + }, + }, + 20 => { + Name => 'AIServoTrackingSensitivity', + PrintConv => { + 0 => 'Standard', + 1 => 'Slow', + 2 => 'Moderately slow', + 3 => 'Moderately fast', + 4 => 'Fast', + }, + }, + 21 => { + Name => 'AIServoContinuousShooting', + PrintConv => { + 0 => 'Shooting not possible without focus', + 1 => 'Shooting possible without focus', + }, + }, +); + +# Custom functions for the 5D (ref 3) +%Image::ExifTool::CanonCustom::Functions5D = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonCustom, + WRITE_PROC => \&WriteCanonCustom, + CHECK_PROC => \&CheckCanonCustom, + WRITABLE => 'int8u', + 0 => { + Name => 'FocusingScreen', + PrintConv => { + 0 => 'Ee-A', + 1 => 'Ee-D', + 2 => 'Ee-S', + }, + }, + 1 => { + Name => 'SetFunctionWhenShooting', + PrintConv => { + 0 => 'Default (no function)', + 1 => 'Change quality', + 2 => 'Change Parameters', + 3 => 'Menu display', + 4 => 'Image replay', + }, + }, + 2 => { + Name => 'LongExposureNoiseReduction', + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'On', + }, + }, + 3 => { + Name => 'FlashSyncSpeedAv', + PrintConv => { + 0 => 'Auto', + 1 => '1/200 Fixed', + }, + }, + 4 => { + Name => 'Shutter-AELock', + PrintConv => { + 0 => 'AF/AE lock', + 1 => 'AE lock/AF', + 2 => 'AF/AF lock, No AE lock', + 3 => 'AE/AF, No AE lock', + }, + }, + 5 => { + Name => 'AFAssistBeam', + PrintConv => { + 0 => 'Emits', + 1 => 'Does not emit', + }, + }, + 6 => { + Name => 'ExposureLevelIncrements', + PrintConv => { + 0 => '1/3 Stop', + 1 => '1/2 Stop', + }, + }, + 7 => { + Name => 'FlashFiring', + PrintConv => { + 0 => 'Fires', + 1 => 'Does not fire', + }, + }, + 8 => { + Name => 'ISOExpansion', + PrintConv => \%offOn, + }, + 9 => { + Name => 'AEBSequenceAutoCancel', + Description => 'AEB Sequence/Auto Cancel', + PrintConv => { + 0 => '0,-,+/Enabled', + 1 => '0,-,+/Disabled', + 2 => '-,0,+/Enabled', + 3 => '-,0,+/Disabled', + }, + }, + 10 => { + Name => 'SuperimposedDisplay', + PrintConv => \%onOff, + }, + 11 => { + Name => 'MenuButtonDisplayPosition', + PrintConv => { + 0 => 'Previous (top if power off)', + 1 => 'Previous', + 2 => 'Top', + }, + }, + 12 => { + Name => 'MirrorLockup', + PrintConv => \%disableEnable, + }, + 13 => { + Name => 'AFPointSelectionMethod', + PrintConv => { + 0 => 'Normal', + 1 => 'Multi-controller direct', + 2 => 'Quick Control Dial direct', + }, + }, + 14 => { + Name => 'ETTLII', + Description => 'E-TTL II', + PrintConv => { + 0 => 'Evaluative', + 1 => 'Average', + }, + }, + 15 => { + Name => 'ShutterCurtainSync', + PrintConv => { + 0 => '1st-curtain sync', + 1 => '2nd-curtain sync', + }, + }, + 16 => { + Name => 'SafetyShiftInAvOrTv', + PrintConv => \%disableEnable, + }, + 17 => { + Name => 'AFPointActivationArea', + PrintConv => { + 0 => 'Standard', + 1 => 'Expanded', + }, + }, + 18 => { + Name => 'LCDDisplayReturnToShoot', + PrintConv => { + 0 => 'With Shutter Button only', + 1 => 'Also with * etc.', + }, + }, + 19 => { + Name => 'LensAFStopButton', + PrintConv => { + 0 => 'AF stop', + 1 => 'AF start', + 2 => 'AE lock while metering', + 3 => 'AF point: M -> Auto / Auto -> Ctr.', + 4 => 'ONE SHOT <-> AI SERVO', + 5 => 'IS start', + }, + }, + 20 => { + Name => 'AddOriginalDecisionData', + PrintConv => \%offOn, + }, +); + +# Custom functions for 10D +%Image::ExifTool::CanonCustom::Functions10D = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonCustom, + WRITE_PROC => \&WriteCanonCustom, + CHECK_PROC => \&CheckCanonCustom, + WRITABLE => 'int8u', + 1 => { + Name => 'SetButtonWhenShooting', + PrintConv => { + 0 => 'Normal (disabled)', + 1 => 'Image quality', + 2 => 'Change parameters', + 3 => 'Menu display', + 4 => 'Image playback', + }, + }, + 2 => { + Name => 'ShutterReleaseNoCFCard', + Description => 'Shutter Release W/O CF Card', + PrintConv => { + 0 => 'Yes', + 1 => 'No', + }, + }, + 3 => { + Name => 'FlashSyncSpeedAv', + PrintConv => { + 0 => 'Auto', + 1 => '1/200 Fixed', + }, + }, + 4 => { + Name => 'Shutter-AELock', + PrintConv => { + 0 => 'AF/AE lock', + 1 => 'AE lock/AF', + 2 => 'AF/AF lock, No AE lock', + 3 => 'AE/AF, No AE lock', + }, + }, + 5 => { + Name => 'AFAssist', + Description => 'AF Assist/Flash Firing', + PrintConv => { + 0 => 'Emits/Fires', + 1 => 'Does not emit/Fires', + 2 => 'Only ext. flash emits/Fires', + 3 => 'Emits/Does not fire', + }, + }, + 6 => { + Name => 'ExposureLevelIncrements', + PrintConv => { + 0 => '1/2 Stop', + 1 => '1/3 Stop', + }, + }, + 7 => { + Name => 'AFPointRegistration', + PrintConv => { + 0 => 'Center', + 1 => 'Bottom', + 2 => 'Right', + 3 => 'Extreme Right', + 4 => 'Automatic', + 5 => 'Extreme Left', + 6 => 'Left', + 7 => 'Top', + }, + }, + 8 => { + Name => 'RawAndJpgRecording', + PrintConv => { + 0 => 'RAW+Small/Normal', + 1 => 'RAW+Small/Fine', + 2 => 'RAW+Medium/Normal', + 3 => 'RAW+Medium/Fine', + 4 => 'RAW+Large/Normal', + 5 => 'RAW+Large/Fine', + }, + }, + 9 => { + Name => 'AEBSequenceAutoCancel', + Description => 'AEB Sequence/Auto Cancel', + PrintConv => { + 0 => '0,-,+/Enabled', + 1 => '0,-,+/Disabled', + 2 => '-,0,+/Enabled', + 3 => '-,0,+/Disabled', + }, + }, + 10 => { + Name => 'SuperimposedDisplay', + PrintConv => \%onOff, + }, + 11 => { + Name => 'MenuButtonDisplayPosition', + PrintConv => { + 0 => 'Previous (top if power off)', + 1 => 'Previous', + 2 => 'Top', + }, + }, + 12 => { + Name => 'MirrorLockup', + PrintConv => \%disableEnable, + }, + 13 => { + Name => 'AssistButtonFunction', + PrintConv => { + 0 => 'Normal', + 1 => 'Select Home Position', + 2 => 'Select HP (while pressing)', + 3 => 'Av+/- (AF point by QCD)', + 4 => 'FE lock', + }, + }, + 14 => { + Name => 'FillFlashAutoReduction', + PrintConv => \%enableDisable, + }, + 15 => { + Name => 'ShutterCurtainSync', + PrintConv => { + 0 => '1st-curtain sync', + 1 => '2nd-curtain sync', + }, + }, + 16 => { + Name => 'SafetyShiftInAvOrTv', + PrintConv => \%disableEnable, + }, + 17 => { + Name => 'LensAFStopButton', + PrintConv => { + 0 => 'AF stop', + 1 => 'AF start', + 2 => 'AE lock while metering', + 3 => 'AF point: M->Auto/Auto->ctr', + 4 => 'One Shot <-> AI servo', + 5 => 'IS start', + }, + }, +); + +# Custom functions for the 20D (ref 2) +%Image::ExifTool::CanonCustom::Functions20D = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonCustom, + WRITE_PROC => \&WriteCanonCustom, + CHECK_PROC => \&CheckCanonCustom, + WRITABLE => 'int8u', + 0 => { + Name => 'SetFunctionWhenShooting', + PrintConv => { + 0 => 'Default (no function)', + 1 => 'Change quality', + 2 => 'Change Parameters', + 3 => 'Menu display', + 4 => 'Image replay', + }, + }, + 1 => { + Name => 'LongExposureNoiseReduction', + PrintConv => \%offOn, + }, + 2 => { + Name => 'FlashSyncSpeedAv', + PrintConv => { + 0 => 'Auto', + 1 => '1/250 Fixed', + }, + }, + 3 => { + Name => 'Shutter-AELock', + PrintConv => { + 0 => 'AF/AE lock', + 1 => 'AE lock/AF', + 2 => 'AF/AF lock, No AE lock', + 3 => 'AE/AF, No AE lock', + }, + }, + 4 => { + Name => 'AFAssistBeam', + PrintConv => { + 0 => 'Emits', + 1 => 'Does not emit', + 2 => 'Only ext. flash emits', + }, + }, + 5 => { + Name => 'ExposureLevelIncrements', + PrintConv => { + 0 => '1/3 Stop', + 1 => '1/2 Stop', + }, + }, + 6 => { + Name => 'FlashFiring', + PrintConv => { + 0 => 'Fires', + 1 => 'Does not fire', + }, + }, + 7 => { + Name => 'ISOExpansion', + PrintConv => \%offOn, + }, + 8 => { + Name => 'AEBSequenceAutoCancel', + Description => 'AEB Sequence/Auto Cancel', + PrintConv => { + 0 => '0,-,+/Enabled', + 1 => '0,-,+/Disabled', + 2 => '-,0,+/Enabled', + 3 => '-,0,+/Disabled', + }, + }, + 9 => { + Name => 'SuperimposedDisplay', + PrintConv => \%onOff, + }, + 10 => { + Name => 'MenuButtonDisplayPosition', + PrintConv => { + 0 => 'Previous (top if power off)', + 1 => 'Previous', + 2 => 'Top', + }, + }, + 11 => { + Name => 'MirrorLockup', + PrintConv => \%disableEnable, + }, + 12 => { + Name => 'AFPointSelectionMethod', + PrintConv => { + 0 => 'Normal', + 1 => 'Multi-controller direct', + 2 => 'Quick Control Dial direct', + }, + }, + 13 => { + Name => 'ETTLII', + Description => 'E-TTL II', + PrintConv => { + 0 => 'Evaluative', + 1 => 'Average', + }, + }, + 14 => { + Name => 'ShutterCurtainSync', + PrintConv => { + 0 => '1st-curtain sync', + 1 => '2nd-curtain sync', + }, + }, + 15 => { + Name => 'SafetyShiftInAvOrTv', + PrintConv => \%disableEnable, + }, + 16 => { + Name => 'LensAFStopButton', + PrintConv => { + 0 => 'AF stop', + 1 => 'AF start', + 2 => 'AE lock while metering', + 3 => 'AF point: M -> Auto / Auto -> Ctr.', + 4 => 'ONE SHOT <-> AI SERVO', + 5 => 'IS start', + }, + }, + 17 => { + Name => 'AddOriginalDecisionData', + PrintConv => \%offOn, + }, +); + +# Custom functions for the 30D (PH) +%Image::ExifTool::CanonCustom::Functions30D = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonCustom, + WRITE_PROC => \&WriteCanonCustom, + CHECK_PROC => \&CheckCanonCustom, + WRITABLE => 'int8u', + 1 => { + Name => 'SetFunctionWhenShooting', + PrintConv => { + 0 => 'Default (no function)', + 1 => 'Change quality', + 2 => 'Change Picture Style', + 3 => 'Menu display', + 4 => 'Image replay', + }, + }, + 2 => { + Name => 'LongExposureNoiseReduction', + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'On', + }, + }, + 3 => { + Name => 'FlashSyncSpeedAv', + PrintConv => { + 0 => 'Auto', + 1 => '1/250 Fixed', + }, + }, + 4 => { + Name => 'Shutter-AELock', + PrintConv => { + 0 => 'AF/AE lock', + 1 => 'AE lock/AF', + 2 => 'AF/AF lock, No AE lock', + 3 => 'AE/AF, No AE lock', + }, + }, + 5 => { + Name => 'AFAssistBeam', + PrintConv => { + 0 => 'Emits', + 1 => 'Does not emit', + 2 => 'Only ext. flash emits', + }, + }, + 6 => { + Name => 'ExposureLevelIncrements', + PrintConv => { + 0 => '1/3 Stop', + 1 => '1/2 Stop', + }, + }, + 7 => { + Name => 'FlashFiring', + PrintConv => { + 0 => 'Fires', + 1 => 'Does not fire', + }, + }, + 8 => { + Name => 'ISOExpansion', + PrintConv => \%offOn, + }, + 9 => { + Name => 'AEBSequenceAutoCancel', + Description => 'AEB Sequence/Auto Cancel', + PrintConv => { + 0 => '0,-,+/Enabled', + 1 => '0,-,+/Disabled', + 2 => '-,0,+/Enabled', + 3 => '-,0,+/Disabled', + }, + }, + 10 => { + Name => 'SuperimposedDisplay', + PrintConv => \%onOff, + }, + 11 => { + Name => 'MenuButtonDisplayPosition', + PrintConv => { + 0 => 'Previous (top if power off)', + 1 => 'Previous', + 2 => 'Top', + }, + }, + 12 => { + Name => 'MirrorLockup', + PrintConv => \%disableEnable, + }, + 13 => { + Name => 'AFPointSelectionMethod', + PrintConv => { + 0 => 'Normal', + 1 => 'Multi-controller direct', + 2 => 'Quick Control Dial direct', + }, + }, + 14 => { + Name => 'ETTLII', + Description => 'E-TTL II', + PrintConv => { + 0 => 'Evaluative', + 1 => 'Average', + }, + }, + 15 => { + Name => 'ShutterCurtainSync', + PrintConv => { + 0 => '1st-curtain sync', + 1 => '2nd-curtain sync', + }, + }, + 16 => { + Name => 'SafetyShiftInAvOrTv', + PrintConv => \%disableEnable, + }, + 17 => { + Name => 'MagnifiedView', + PrintConv => { + 0 => 'Image playback only', + 1 => 'Image review and playback', + }, + }, + 18 => { + Name => 'LensAFStopButton', + PrintConv => { + 0 => 'AF stop', + 1 => 'AF start', + 2 => 'AE lock while metering', + 3 => 'AF point: M -> Auto / Auto -> Ctr.', + 4 => 'ONE SHOT <-> AI SERVO', + 5 => 'IS start', + }, + }, + 19 => { + Name => 'AddOriginalDecisionData', + PrintConv => \%offOn, + }, +); + +# Custom functions for the 350D (PH) +%Image::ExifTool::CanonCustom::Functions350D = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonCustom, + WRITE_PROC => \&WriteCanonCustom, + CHECK_PROC => \&CheckCanonCustom, + WRITABLE => 'int8u', + 0 => { + Name => 'SetButtonCrossKeysFunc', + PrintConv => { + 0 => 'Normal', + 1 => 'Set: Quality', + 2 => 'Set: Parameter', + 3 => 'Set: Playback', + 4 => 'Cross keys: AF point select', + }, + }, + 1 => { + Name => 'LongExposureNoiseReduction', + PrintConv => \%offOn, + }, + 2 => { + Name => 'FlashSyncSpeedAv', + PrintConv => { + 0 => 'Auto', + 1 => '1/200 Fixed', + }, + }, + 3 => { + Name => 'Shutter-AELock', + PrintConv => { + 0 => 'AF/AE lock', + 1 => 'AE lock/AF', + 2 => 'AF/AF lock, No AE lock', + 3 => 'AE/AF, No AE lock', + }, + }, + 4 => { + Name => 'AFAssistBeam', + PrintConv => { + 0 => 'Emits', + 1 => 'Does not emit', + 2 => 'Only ext. flash emits', + }, + }, + 5 => { + Name => 'ExposureLevelIncrements', + PrintConv => { + 0 => '1/3 Stop', + 1 => '1/2 Stop', + }, + }, + 6 => { + Name => 'MirrorLockup', + PrintConv => \%disableEnable, + }, + 7 => { + Name => 'ETTLII', + Description => 'E-TTL II', + PrintConv => { + 0 => 'Evaluative', + 1 => 'Average', + }, + }, + 8 => { + Name => 'ShutterCurtainSync', + PrintConv => { + 0 => '1st-curtain sync', + 1 => '2nd-curtain sync', + }, + }, +); + +# Custom functions for the 400D (PH) +%Image::ExifTool::CanonCustom::Functions400D = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonCustom, + WRITE_PROC => \&WriteCanonCustom, + CHECK_PROC => \&CheckCanonCustom, + WRITABLE => 'int8u', + 0 => { + Name => 'SetButtonCrossKeysFunc', + PrintConv => { + 0 => 'Set: Picture Style', + 1 => 'Set: Quality', + 2 => 'Set: Flash Exposure Comp', + 3 => 'Set: Playback', + 4 => 'Cross keys: AF point select', + }, + }, + 1 => { + Name => 'LongExposureNoiseReduction', + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'On', + }, + }, + 2 => { + Name => 'FlashSyncSpeedAv', + PrintConv => { + 0 => 'Auto', + 1 => '1/200 Fixed', + }, + }, + 3 => { + Name => 'Shutter-AELock', + PrintConv => { + 0 => 'AF/AE lock', + 1 => 'AE lock/AF', + 2 => 'AF/AF lock, No AE lock', + 3 => 'AE/AF, No AE lock', + }, + }, + 4 => { + Name => 'AFAssistBeam', + PrintConv => { + 0 => 'Emits', + 1 => 'Does not emit', + 2 => 'Only ext. flash emits', + }, + }, + 5 => { + Name => 'ExposureLevelIncrements', + PrintConv => { + 0 => '1/3 Stop', + 1 => '1/2 Stop', + }, + }, + 6 => { + Name => 'MirrorLockup', + PrintConv => \%disableEnable, + }, + 7 => { + Name => 'ETTLII', + Description => 'E-TTL II', + PrintConv => { + 0 => 'Evaluative', + 1 => 'Average', + }, + }, + 8 => { + Name => 'ShutterCurtainSync', + PrintConv => { + 0 => '1st-curtain sync', + 1 => '2nd-curtain sync', + }, + }, + 9 => { + Name => 'MagnifiedView', + PrintConv => { + 0 => 'Image playback only', + 1 => 'Image review and playback', + }, + }, + 10 => { + Name => 'LCDDisplayAtPowerOn', + PrintConv => { + 0 => 'Display', + 1 => 'Retain power off status', + }, + }, +); + +# Custom functions for the D30/D60 +%Image::ExifTool::CanonCustom::FunctionsD30 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonCustom, + WRITE_PROC => \&WriteCanonCustom, + CHECK_PROC => \&CheckCanonCustom, + NOTES => 'Custom functions for the EOS D30 and D60.', + WRITABLE => 'int8u', + 1 => { + Name => 'LongExposureNoiseReduction', + PrintConv => \%offOn, + }, + 2 => { + Name => 'Shutter-AELock', + PrintConv => { + 0 => 'AF/AE lock', + 1 => 'AE lock/AF', + 2 => 'AF/AF lock', + 3 => 'AE+release/AE+AF', + }, + }, + 3 => { + Name => 'MirrorLockup', + PrintConv => \%disableEnable, + }, + 4 => { + Name => 'ExposureLevelIncrements', + PrintConv => { + 0 => '1/2 Stop', + 1 => '1/3 Stop', + }, + }, + 5 => { + Name => 'AFAssist', + PrintConv => { + 0 => 'Emits/Fires', + 1 => 'Does not emit/Fires', + 2 => 'Only ext. flash emits/Fires', + 3 => 'Emits/Does not fire', + }, + }, + 6 => { + Name => 'FlashSyncSpeedAv', + PrintConv => { + 0 => 'Auto', + 1 => '1/200 Fixed', + }, + }, + 7 => { + Name => 'AEBSequenceAutoCancel', + Description => 'AEB Sequence/Auto Cancel', + PrintConv => { + 0 => '0,-,+/Enabled', + 1 => '0,-,+/Disabled', + 2 => '-,0,+/Enabled', + 3 => '-,0,+/Disabled', + }, + }, + 8 => { + Name => 'ShutterCurtainSync', + PrintConv => { + 0 => '1st-curtain sync', + 1 => '2nd-curtain sync', + }, + }, + 9 => { + Name => 'LensAFStopButton', + PrintConv => { + 0 => 'AF Stop', + 1 => 'Operate AF', + 2 => 'Lock AE and start timer', + }, + }, + 10 => { + Name => 'FillFlashAutoReduction', + PrintConv => \%enableDisable, + }, + 11 => { + Name => 'MenuButtonReturn', + PrintConv => { + 0 => 'Top', + 1 => 'Previous (volatile)', + 2 => 'Previous', + }, + }, + 12 => { + Name => 'SetButtonWhenShooting', + PrintConv => { + 0 => 'Default (no function)', + 1 => 'Image quality', + 2 => 'Change ISO speed', + 3 => 'Change parameters', + }, + }, + 13 => { + Name => 'SensorCleaning', + PrintConv => \%disableEnable, + }, + 14 => { + Name => 'SuperimposedDisplay', + PrintConv => \%onOff, + }, + 15 => { + Name => 'ShutterReleaseNoCFCard', + Description => 'Shutter Release W/O CF Card', + PrintConv => { + 0 => 'Yes', + 1 => 'No', + }, + }, +); + +# Custom functions for unknown cameras +%Image::ExifTool::CanonCustom::FuncsUnknown = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonCustom, +); + +# 1D personal function settings (ref PH) +%Image::ExifTool::CanonCustom::PersonalFuncs = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + NOTES => 'Personal function settings for the EOS-1D.', + FORMAT => 'int16u', + FIRST_ENTRY => 1, + 1 => { Name => 'PF0CustomFuncRegistration', %convPFn }, + 2 => { Name => 'PF1DisableShootingModes', %convPFn }, + 3 => { Name => 'PF2DisableMeteringModes', %convPFn }, + 4 => { Name => 'PF3ManualExposureMetering', %convPFn }, + 5 => { Name => 'PF4ExposureTimeLimits', %convPFn }, + 6 => { Name => 'PF5ApertureLimits', %convPFn }, + 7 => { Name => 'PF6PresetShootingModes', %convPFn }, + 8 => { Name => 'PF7BracketContinuousShoot', %convPFn }, + 9 => { Name => 'PF8SetBracketShots', %convPFn }, + 10 => { Name => 'PF9ChangeBracketSequence', %convPFn }, + 11 => { Name => 'PF10RetainProgramShift', %convPFn }, + #12 => { Name => 'PF11Unused', %convPFn }, + #13 => { Name => 'PF12Unused', %convPFn }, + 14 => { Name => 'PF13DrivePriority', %convPFn }, + 15 => { Name => 'PF14DisableFocusSearch', %convPFn }, + 16 => { Name => 'PF15DisableAFAssistBeam', %convPFn }, + 17 => { Name => 'PF16AutoFocusPointShoot', %convPFn }, + 18 => { Name => 'PF17DisableAFPointSel', %convPFn }, + 19 => { Name => 'PF18EnableAutoAFPointSel', %convPFn }, + 20 => { Name => 'PF19ContinuousShootSpeed', %convPFn }, + 21 => { Name => 'PF20LimitContinousShots', %convPFn }, + 22 => { Name => 'PF21EnableQuietOperation', %convPFn }, + #23 => { Name => 'PF22Unused', %convPFn }, + 24 => { Name => 'PF23SetTimerLengths', %convPFn }, + 25 => { Name => 'PF24LightLCDDuringBulb', %convPFn }, + 26 => { Name => 'PF25DefaultClearSettings', %convPFn }, + 27 => { Name => 'PF26ShortenReleaseLag', %convPFn }, + 28 => { Name => 'PF27ReverseDialRotation', %convPFn }, + 29 => { Name => 'PF28NoQuickDialExpComp', %convPFn }, + 30 => { Name => 'PF29QuickDialSwitchOff', %convPFn }, + 31 => { Name => 'PF30EnlargementMode', %convPFn }, + 32 => { Name => 'PF31OriginalDecisionData', %convPFn }, +); + +# 1D personal function values (ref PH) +%Image::ExifTool::CanonCustom::PersonalFuncValues = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int16u', + FIRST_ENTRY => 1, + 1 => 'PF1Value', + 2 => 'PF2Value', + 3 => 'PF3Value', + 4 => { + Name => 'PF4ExposureTimeMin', + RawConv => '$val > 0 ? $val : 0', + ValueConv => 'exp(-Image::ExifTool::Canon::CanonEv($val*4)*log(2))*1000/8', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv(-log($val*8/1000)/log(2))/4', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 5 => { + Name => 'PF4ExposureTimeMax', + RawConv => '$val > 0 ? $val : 0', + ValueConv => 'exp(-Image::ExifTool::Canon::CanonEv($val*4)*log(2))*1000/8', + ValueConvInv => 'Image::ExifTool::Canon::CanonEvInv(-log($val*8/1000)/log(2))/4', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 6 => { + Name => 'PF5ApertureMin', + RawConv => '$val > 0 ? $val : 0', + ValueConv => 'exp(Image::ExifTool::Canon::CanonEv($val*4-32)*log(2)/2)', + ValueConvInv => '(Image::ExifTool::Canon::CanonEvInv(log($val)*2/log(2))+32)/4', + PrintConv => 'sprintf("%.2g",$val)', + PrintConvInv => '$val', + }, + 7 => { + Name => 'PF5ApertureMax', + RawConv => '$val > 0 ? $val : 0', + ValueConv => 'exp(Image::ExifTool::Canon::CanonEv($val*4-32)*log(2)/2)', + ValueConvInv => '(Image::ExifTool::Canon::CanonEvInv(log($val)*2/log(2))+32)/4', + PrintConv => 'sprintf("%.2g",$val)', + PrintConvInv => '$val', + }, + 8 => 'PF8BracketShots', + 9 => 'PF19ShootingSpeedLow', + 10 => 'PF19ShootingSpeedHigh', + 11 => 'PF20MaxContinousShots', + 12 => 'PF23ShutterButtonTime', + 13 => 'PF23FELockTime', + 14 => 'PF23PostReleaseTime', + 15 => 'PF25AEMode', + 16 => 'PF25MeteringMode', + 17 => 'PF25DriveMode', + 18 => 'PF25AFMode', + 19 => 'PF25AFPointSel', + 20 => 'PF25ImageSize', + 21 => 'PF25WBMode', + 22 => 'PF25Parameters', + 23 => 'PF25ColorMatrix', + 24 => 'PF27Value', +); + +# Custom functions used by the 1D Mark III and later models (ref PH) +%Image::ExifTool::CanonCustom::Functions2 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonCustom2, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITE_PROC => \&WriteCanonCustom2, + WRITABLE => 'int32s', + NOTES => q{ + Beginning with the EOS 1D Mark III, Canon finally created a set of custom + function tags which are (reasonably) consistent across models. The EOS 1D + Mark III has 57 custom function tags divided into four main groups: 1. + Exposure (0x0101-0x010f), 2. Image (0x0201-0x0203), Flash Exposure + (0x0304-0x0306) and Display (0x0407-0x0409), 3. Auto Focus (0x0501-0x050e) + and Drive (0x060f-0x0611), and 4. Operation (0x0701-0x070a) and Others + (0x080b-0x0810). The table below lists tags used by the EOS 1D Mark III, as + well as newer tags and values added by later models. + }, + # grouped in 4 groups: + # 1) Exposure + 0x0101 => [ + { + Name => 'ExposureLevelIncrements', + Condition => '$$self{Model} =~ /\b1Ds?\b/', + Notes => '1DmkIII and 1DmkIV', + PrintConv => { + 0 => '1/3-stop set, 1/3-stop comp.', + 1 => '1-stop set, 1/3-stop comp.', + 2 => '1/2-stop set, 1/2-stop comp.', + }, + }, + { + Name => 'ExposureLevelIncrements', + Notes => 'other models', + PrintConv => { + 0 => '1/3 Stop', + 1 => '1/2 Stop', + }, + }, + ], + 0x0102 => { + Name => 'ISOSpeedIncrements', + PrintConv => { + 0 => '1/3 Stop', + 1 => '1 Stop', + }, + }, + 0x0103 => [ + { + Name => 'ISOSpeedRange', + Condition => '$$self{Model} =~ /\b1D/', + Notes => '1DmkIII and 1DmkIV', + Count => 3, + # (this decoding may not be valid for CR2 images?) + ValueConv => [ + undef, + # this may also be set to "H" (Hi6400) -- is this the -1 value I see? - PH + '$val < 2 ? $val : ($val < 1000 ? exp(($val/8-9)*log(2))*100 : 0)', # (educated guess) + # this may also be set to "L" (Lo50) -- is this the 1 value I see? - PH + '$val < 2 ? $val : ($val < 1000 ? exp(($val/8-9)*log(2))*100 : 0)', # (educated guess) + ], + ValueConvInv => [ + undef, + '$val < 2 ? $val : int(8*(log($val/100)/log(2)+9) + 0.5)', + '$val < 2 ? $val : int(8*(log($val/100)/log(2)+9) + 0.5)', + ], + PrintConv => [ + \%disableEnable, + 'sprintf("Max %.0f",$val)', + 'sprintf("Min %.0f",$val)', + ], + PrintConvInv => [ + undef, + '$val=~/(-?[\d.]+)/ ? $1 : 0', + '$val=~/(-?[\d.]+)/ ? $1 : 0', + ], + }, + { + Name => 'ISOExpansion', + Notes => 'other models', + PrintConv => \%offOn, + }, + ], + 0x0104 => { + Name => 'AEBAutoCancel', + PrintConv => \%onOff, + }, + 0x0105 => { + Name => 'AEBSequence', + Notes => 'value of 2 not used by 40D, 50D, 60D, 5DmkII and 7D', + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + 2 => '+,0,-', + }, + }, + 0x0106 => [{ + Name => 'AEBShotCount', + Condition => '$$self{Model} =~ /\b90D\b/', + Notes => 'EOS 90D', # (and who knows what others?) + PrintConv => { + 2 => '2 shots', + 3 => '3 shots', + 5 => '5 shots', + 7 => '7 shots', + }, + },{ + Name => 'AEBShotCount', + Condition => '$count == 1', + Notes => 'other models storing a single value', + PrintConv => { + 0 => '3 shots', + 1 => '2 shots', + 2 => '5 shots', + 3 => '7 shots', + }, + },{ + Name => 'AEBShotCount', + Count => 2, + Notes => 'models storing two values', + PrintConv => { + '3 0' => '3 shots', + '2 1' => '2 shots', + '5 2' => '5 shots', + '7 3' => '7 shots', + }, + }], + 0x0107 => { + Name => 'SpotMeterLinkToAFPoint', + PrintConv => { + 0 => 'Disable (use center AF point)', + 1 => 'Enable (use active AF point)', + }, + }, + 0x0108 => { + Name => 'SafetyShift', + Notes => 'value of 2 not used by some models', # eg. 40D, 50D, 60D, 5DmkII, 7D, 250D + PrintConv => { + 0 => 'Disable', + 1 => 'Enable (Tv/Av)', + 2 => 'Enable (ISO speed)', + }, + }, + 0x0109 => [{ # (1DXmkIII) + Name => 'UsableShootingModes', + Condition => '$count == 1', + Count => 1, + PrintConv => 'sprintf("Flags 0x%x",$val)', + PrintConvInv => '$val=~/0x([\dA-F]+)/i ? hex($1) : undef', + },{ + Name => 'UsableShootingModes', + Count => 2, + PrintConv => [ + \%disableEnable, + 'sprintf("Flags 0x%x",$val)', # (M, Tv, Av, P, Bulb) + ], + PrintConvInv => [ + undef, + '$val=~/0x([\dA-F]+)/i ? hex($1) : undef', + ], + }], + 0x010a => [{ # (1DXmkIII) + Name => 'UsableMeteringModes', + Condition => '$count == 1', + Count => 1, + PrintConv => 'sprintf("Flags 0x%x",$val)', + PrintConvInv => '$val=~/0x([\dA-F]+)/i ? hex($1) : undef', + },{ + Name => 'UsableMeteringModes', + Count => 2, + PrintConv => [ + \%disableEnable, + 'sprintf("Flags 0x%x",$val)', # (evaluative,partial,spot,center-weighted average) + ], + PrintConvInv => [ + undef, + '$val=~/0x([\dA-F]+)/i ? hex($1) : undef', + ], + }], + 0x010b => { + Name => 'ExposureModeInManual', + PrintConv => { + 0 => 'Specified metering mode', + 1 => 'Evaluative metering', + 2 => 'Partial metering', + 3 => 'Spot metering', + 4 => 'Center-weighted average', + }, + }, + 0x010c => [{ + Name => 'ShutterSpeedRange', + Condition => '$count == 3', + Count => 3, + ValueConv => [ + undef, + 'exp(-($val/8-7)*log(2))', + 'exp(-($val/8-7)*log(2))', + ], + ValueConvInv => [ + undef, + 'int(-8*(log($val)/log(2)-7) + 0.5)', + 'int(-8*(log($val)/log(2)-7) + 0.5)', + ], + PrintConv => [ + \%disableEnable, + '"Hi " . Image::ExifTool::Exif::PrintExposureTime($val)', + '"Lo " . Image::ExifTool::Exif::PrintExposureTime($val)', + ], + PrintConvInv => [ + undef, + '$val=~m{([\d./]+)} ? eval $1 : 0', + '$val=~m{([\d./]+)} ? eval $1 : 0', + ], + },{ # (EOS R) + Name => 'ShutterSpeedRange', + Condition => '$count == 4', + Count => 4, + ValueConv => [ # (NC) + 'exp(-$val/(1600*log(2)))', + 'exp(-$val/(1600*log(2)))', + 'exp(-$val/(1600*log(2)))', + 'exp(-$val/(1600*log(2)))', + ], + ValueConvInv => [ + 'int(-log($val)*1600*log(2) + 0.5)', + 'int(-log($val)*1600*log(2) + 0.5)', + 'int(-log($val)*1600*log(2) + 0.5)', + 'int(-log($val)*1600*log(2) + 0.5)', + ], + PrintConv => [ # (NC) + '"Manual: Hi " . Image::ExifTool::Exif::PrintExposureTime($val)', + '"Lo " . Image::ExifTool::Exif::PrintExposureTime($val)', + '"Auto: Hi " . Image::ExifTool::Exif::PrintExposureTime($val)', + '"Lo " . Image::ExifTool::Exif::PrintExposureTime($val)', + ], + PrintConvInv => [ + '$val=~m{([\d./]+)} ? eval $1 : 0', + '$val=~m{([\d./]+)} ? eval $1 : 0', + '$val=~m{([\d./]+)} ? eval $1 : 0', + '$val=~m{([\d./]+)} ? eval $1 : 0', + ], + }], + 0x010d => [{ + Name => 'ApertureRange', + Condition => '$count == 3', + Count => 3, + ValueConv => [ + undef, + 'exp(($val/8-1)*log(2)/2)', + 'exp(($val/8-1)*log(2)/2)', + ], + ValueConvInv => [ + undef, + 'int(8*(log($val)*2/log(2)+1) + 0.5)', + 'int(8*(log($val)*2/log(2)+1) + 0.5)', + ], + PrintConv => [ + \%disableEnable, + 'sprintf("Closed %.2g",$val)', + 'sprintf("Open %.2g",$val)', + ], + PrintConvInv => [ + undef, + '$val=~/([\d.]+)/ ? $1 : 0', + '$val=~/([\d.]+)/ ? $1 : 0', + ], + },{ # (EOS R) + Name => 'ApertureRange', + Condition => '$count == 4', + Count => 4, + ValueConv => [ # (NC) + 'exp($val/2400)', + 'exp($val/2400)', + 'exp($val/2400)', + 'exp($val/2400)', + ], + ValueConvInv => [ + 'int(log($val)*2400) + 0.5)', + 'int(log($val)*2400) + 0.5)', + 'int(log($val)*2400) + 0.5)', + 'int(log($val)*2400) + 0.5)', + ], + PrintConv => [ # (NC) + 'sprintf("Manual: Closed %.2g",$val)', + 'sprintf("Open %.2g",$val)', + 'sprintf("Auto: Closed %.2g",$val)', + 'sprintf("Open %.2g",$val)', + ], + PrintConvInv => [ + '$val=~/([\d.]+)/ ? $1 : 0', + '$val=~/([\d.]+)/ ? $1 : 0', + '$val=~/([\d.]+)/ ? $1 : 0', + '$val=~/([\d.]+)/ ? $1 : 0', + ], + }], + 0x010e => { + Name => 'ApplyShootingMeteringMode', + Count => 8, + PrintConv => [ \%disableEnable ], + }, + 0x010f => [ + { + Name => 'FlashSyncSpeedAv', + Condition => '$$self{Model} =~ /\b(40D|1Ds Mark III)\b/', + Notes => '40D and 1Ds Mark III', + PrintConv => { + 0 => 'Auto', + 1 => '1/250 Fixed', + }, + }, + { + Name => 'FlashSyncSpeedAv', + Condition => '$$self{Model} =~ /\b(50D|60D|7D)\b/', + Notes => '50D, 60D and 7D', + PrintConv => { + 0 => 'Auto', + 1 => '1/250-1/60 Auto', + 2 => '1/250 Fixed', + }, + }, + { + Name => 'FlashSyncSpeedAv', + Condition => '$$self{Model} =~ /\b(450D|XSi|Kiss X2|1000D|XS|Kiss F)\b/', + Notes => '450D and 1000D', + PrintConv => { + 0 => 'Auto', + 1 => '1/200 Fixed', + }, + }, + { + Name => 'FlashSyncSpeedAv', + Condition => '$$self{Model} =~ /\bEOS-1Ds? Mark III\b/', + Notes => '1D Mark III and 1Ds Mark III', + PrintConv => { + 0 => 'Auto', + 1 => '1/300 Fixed', + }, + }, + { + Name => 'FlashSyncSpeedAv', + Condition => '$$self{Model} =~ /\bEOS-1D Mark IV\b/', + Notes => '1D Mark IV', + PrintConv => { + 0 => 'Auto', + 1 => '1/300-1/60 Auto', + 2 => '1/300 Fixed', + }, + }, + { + Name => 'FlashSyncSpeedAv', + Notes => '5D Mark II, 5D Mark III, 500D, 550D, 600D and 1100D', + PrintConv => { + 0 => 'Auto', + 1 => '1/200-1/60 Auto', + 2 => '1/200 Fixed', + }, + }, + ], + 0x0110 => { # new for 1DmkIV + Name => 'AEMicroadjustment', + Count => 3, + PrintConv => [ \%disableEnable ], + }, + 0x0111 => { # new for 1DmkIV + Name => 'FEMicroadjustment', + Count => 3, + PrintConv => [ \%disableEnable ], + }, + 0x0112 => [{ # (5DS) + Name => 'SameExposureForNewAperture', + PrintConv => { + 0 => 'Disable', + 1 => 'ISO Speed', + 2 => 'Shutter Speed', + }, + },{ # (EOS R) + Name => 'SameExposureForNewAperture', + Notes => 'EOS R', + PrintConv => { + 0 => 'Disable', + 1 => 'ISO Speed', + 2 => 'ISO Speed/Shutter Speed', + 3 => 'Shutter Speed', + }, + }], + 0x0113 => { # (200D) + Name => 'ExposureCompAutoCancel', + PrintConv => \%enableDisable, + }, + 0x0114 => { # (R) + Name => 'AELockMeterModeAfterFocus', + # metering modes where AE lock after focus applies: + PrintConv => { BITMASK => { # (NC) + 0 => 'Evaluative', + 1 => 'Partial', + 2 => 'Spot', + 3 => 'Center-weighted', + }}, + }, + #### 2a) Image + 0x0201 => { + Name => 'LongExposureNoiseReduction', + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'On', + }, + }, + 0x0202 => [ + { + Name => 'HighISONoiseReduction', + Condition => q{ + $$self{Model} =~ /\b(50D|60D|5D Mark II|7D|500D|T1i|Kiss X3|550D|T2i|Kiss X4)\b/ or + $$self{Model} =~ /\b(600D|T3i|Kiss X5|1100D|T3|Kiss X50)\b/ + }, + Notes => '50D, 60D, 500D, 550D, 600D, 1100D, 5DmkII and 7D', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Off', + }, + }, + { + Name => 'HighISONoiseReduction', + Notes => 'other models', + PrintConv => \%offOn, + }, + ], + 0x0203 => { + Name => 'HighlightTonePriority', + PrintConv => \%disableEnable + }, + 0x0204 => [ + { + Name => 'AutoLightingOptimizer', + Condition => '$$self{Model} =~ /\b(50D|5D Mark II|500D|T1i|Kiss X3|1D Mark IV)\b/', + Notes => '50D, 500D, 5DmkII and 1DmkIV', + PrintConv => { + 0 => 'Standard', + 1 => 'Low', + 2 => 'Strong', + 3 => 'Disable', + }, + }, + { + Name => 'AutoLightingOptimizer', + Notes => 'other models', + PrintConv => \%enableDisable, + }, + ], + # 0x0205 - Added in 5DmkII firmware update + #### 2b) Flash exposure + 0x0304 => { + Name => 'ETTLII', + Description => 'E-TTL II', + PrintConv => { + 0 => 'Evaluative', + 1 => 'Average', + }, + }, + 0x0305 => { + Name => 'ShutterCurtainSync', + PrintConv => { + 0 => '1st-curtain sync', + 1 => '2nd-curtain sync', + }, + }, + 0x0306 => { + Name => 'FlashFiring', + PrintConv => { + 0 => 'Fires', + 1 => 'Does not fire', + }, + }, + #### 2c) Display + 0x0407 => { + Name => 'ViewInfoDuringExposure', + PrintConv => \%disableEnable, + }, + 0x0408 => { + Name => 'LCDIlluminationDuringBulb', + PrintConv => \%offOn, + }, + 0x0409 => [ + { + Name => 'InfoButtonWhenShooting', + Condition => '$$self{Model} =~ /\b1Ds? Mark III\b/', + Notes => '1D Mark III', + PrintConv => { + 0 => 'Displays camera settings', + 1 => 'Displays shooting functions', + }, + }, + { + Name => 'InfoButtonWhenShooting', + Notes => '1D Mark IV', + PrintConv => { + # reversed from earlier models. grr... + 0 => 'Displays shooting functions', + 1 => 'Displays camera settings', + }, + }, + ], + 0x040a => { # new for 5DmkIII + Name => 'ViewfinderWarnings', + PrintConv => { BITMASK => { #(NC) + 0 => 'Monochrome', # (have seen for: 5DmkII, 6D, 250D, 90D) + 1 => 'WB corrected', # (have seen for: 5DmkII, 6D, 90D) + 2 => 'One-touch image quality', # (have seen for: 5DmkII; doesn't exist for 6D) + 3 => 'ISO expansion', # (have seen for: 5DmkII) + 4 => 'Spot metering', # (have seen for: 5DmkII, 6D) + 6 => 'Noise reduction', # (have seen for: 250D, 90D) + 7 => 'HDR', # (have seen for: 90D) + }}, + }, + 0x040b => { # new for 5DmkIII + Name => 'LVShootingAreaDisplay', + PrintConv => { + 0 => 'Masked', + 1 => 'Outlined', + }, + }, + 0x040c => { # (7DmkII) + Name => 'LVShootingAreaDisplay', + PrintConv => { + 0 => 'Masked', + 1 => 'Outlined', + }, + }, + #### 3a) Auto focus + 0x0501 => { + Name => 'USMLensElectronicMF', + PrintConv => { + 0 => 'Enable after one-shot AF', + 1 => 'Disable after one-shot AF', + 2 => 'Disable in AF mode', + }, + }, + 0x0502 => { + Name => 'AIServoTrackingSensitivity', + PrintConv => { + -2 => 'Slow', # (Locked on -2 for 6D) + -1 => 'Medium Slow', # (Locked on -1 for 6D) + 0 => 'Standard', + 1 => 'Medium Fast', # (Responsive +1 for 6D) + 2 => 'Fast', # (Responsive +2 for 6D) + }, + }, + 0x0503 => { + Name => 'AIServoImagePriority', + PrintConv => { + 0 => '1: AF, 2: Tracking', + 1 => '1: AF, 2: Drive speed', + 2 => '1: Release, 2: Drive speed', + 3 => '1: Release, 2: Tracking', # 7D/1DmkIV + }, + }, + 0x0504 => { + Name => 'AIServoTrackingMethod', + PrintConv => { + 0 => 'Main focus point priority', + 1 => 'Continuous AF track priority', + }, + }, + 0x0505 => { + Name => 'LensDriveNoAF', + PrintConv => { + 0 => 'Focus search on', + 1 => 'Focus search off', + }, + }, + 0x0506 => { + Name => 'LensAFStopButton', + Notes => 'value of 6 not used by 40D, 50D and 5DmkII', + PrintConv => { + 0 => 'AF stop', + 1 => 'AF start', + 2 => 'AE lock', + 3 => 'AF point: M->Auto/Auto->ctr', + 4 => 'One Shot <-> AI servo', + 5 => 'IS start', + 6 => 'Switch to registered AF point', + 7 => 'Spot AF', # 1DmkIV + }, + }, + 0x0507 => { + Name => 'AFMicroadjustment', + Count => 5, + PrintConv => [ + { + 0 => 'Disable', + 1 => 'Adjust all by same amount', + 2 => 'Adjust by lens', + }, + # DECODE OTHER VALUES + ], + }, + 0x0508 => [ + { + Name => 'AFPointAreaExpansion', + Condition => '$$self{Model} =~ /\b5D Mark II\b/', + Notes => '5D Mark II', + PrintConv => { + 0 => 'Disable', + 1 => 'Enable', + }, + }, + { + Name => 'AFPointAreaExpansion', + Condition => '$$self{Model} =~ /\b1Ds Mark III\b/', + Notes => '1Ds Mark III', + PrintConv => { + 0 => 'Disable', + 1 => 'Enable (left/right Assist AF points)', + 2 => 'Enable (surrounding Assist AF points)', + }, + }, + { + Name => 'AFPointAreaExpansion', + Notes => 'other models', + PrintConv => { + 0 => 'Disable', + 1 => 'Left/right AF points', + 2 => 'Surrounding AF points', + 3 => 'All 45 points area', # 1DmkIV + }, + }, + ], + 0x0509 => [ + { + Name => 'SelectableAFPoint', + Condition => '$$self{Model} =~ /\b1D Mark IV\b/', + Notes => '1D Mark IV', + PrintConv => { + 0 => '45 points', + 1 => '19 points', + 2 => '11 points', + 3 => 'Inner 9 points', + 4 => 'Outer 9 points', + }, + }, + { + Name => 'SelectableAFPoint', + Notes => 'other models', + PrintConv => { + 0 => '19 points', + 1 => 'Inner 9 points', + 2 => 'Outer 9 points', + 3 => '19 Points, Multi-controller selectable', #4 + 4 => 'Inner 9 Points, Multi-controller selectable', #4 + 5 => 'Outer 9 Points, Multi-controller selectable', #4 + }, + }, + ], + 0x050a => [ + { + Name => 'SwitchToRegisteredAFPoint', + Condition => '$$self{Model} =~ /\b1D Mark IV\b/', + Notes => '1D Mark IV', + PrintConv => { + 0 => 'Disable', + 1 => 'Switch with multi-controller', + 2 => 'Only while AEL is pressed', + }, + }, + { + Name => 'SwitchToRegisteredAFPoint', + Notes => 'other models', + PrintConv => \%disableEnable, + }, + ], + 0x050b => { + Name => 'AFPointAutoSelection', + PrintConv => { + 0 => 'Control-direct:disable/Main:enable', + 1 => 'Control-direct:disable/Main:disable', + 2 => 'Control-direct:enable/Main:enable', + }, + }, + 0x050c => [ + { + Name => 'AFPointDisplayDuringFocus', + Condition => '$$self{Model} =~ /\b1D\b/', + Notes => '1D models', + PrintConv => { + 0 => 'On', + 1 => 'Off', + 2 => 'On (when focus achieved)', + }, + }, + { + Name => 'AFPointDisplayDuringFocus', + Notes => 'other models', # (7D, 70D, 750D, 760D) + PrintConv => { + 0 => 'Selected (constant)', + 1 => 'All (constant)', + 2 => 'Selected (pre-AF, focused)', + 3 => 'Selected (focused)', + 4 => 'Disable display', + }, + }, + ], + 0x050d => { + Name => 'AFPointBrightness', + PrintConv => { + 0 => 'Normal', + 1 => 'Brighter', + }, + }, + 0x050e => [ + { + Name => 'AFAssistBeam', + Condition => '$$self{Model} =~ /\b(1D Mark IV|6D)\b/', + Notes => '1D Mark IV and 6D', + PrintConv => { + 0 => 'Emits', + 1 => 'Does not emit', + 2 => 'IR AF assist beam only', + }, + }, + { + Name => 'AFAssistBeam', + Notes => 'other models; values 2-3 not used by 1DmkIII or 5DmkII, value 3 new for 7D', + PrintConv => { + 0 => 'Emits', + 1 => 'Does not emit', + 2 => 'Only ext. flash emits', + 3 => 'IR AF assist beam only', # new for 7D + }, + }, + ], + 0x050f => [ # new for 40D + { + Name => 'AFPointSelectionMethod', + Condition => '$$self{Model} !~ /\b60D\b/', + Notes => '40D, 50D and 5DmkII', + PrintConv => { + 0 => 'Normal', + 1 => 'Multi-controller direct', + 2 => 'Quick Control Dial direct', + }, + }, + { + Name => 'AFPointSelectionMethod', + Notes => '60D', + PrintConv => { + 0 => 'AF point button: Activate AF Sel; Rear dial: Select AF points', + 1 => 'AF point button: Auto selection; Rear dial: Manual selection', + }, + }, + # (this is 2 values for 90D,M6mkII, seen: "1046 1046") + ], + 0x0510 => [ # new for 40D + { + Name => 'VFDisplayIllumination', # (7D quirk, or decoded incorrectly?) + Condition => '$$self{Model} =~ /\b7D\b/', + Notes => '7D', + PrintConv => { + 0 => 'Auto', + 1 => 'Enable', + 2 => 'Disable', + }, + }, + { + Name => 'SuperimposedDisplay', + Notes => 'other models', + PrintConv => \%onOff, + }, + ], + 0x0511 => [ # new for 40D + { + Name => 'AFDuringLiveView', + Condition => '$$self{Model} =~ /\b40D\b/', + Notes => '40D', + PrintConv => \%disableEnable, + }, + { + Name => 'AFDuringLiveView', + Notes => '450D and 1000D', + PrintConv => { + 0 => 'Disable', + 1 => 'Quick mode', + 2 => 'Live mode', + }, + }, + ], + 0x0512 => { # new for 7D + Name => 'SelectAFAreaSelectMode', + PrintConv => [ + { + 0 => 'Disable', + 1 => 'Enable', + 2 => 'Register', + 3 => 'Select AF-modes', + # also seen: 87 (90D), 1142 (RP) + }, + 'sprintf("Flags 0x%x",$val)', # (70D=Manual 1pt,Manual zone,Auto 19pt) + ], + PrintConvInv => [ + undef, + '$val=~/0x([\dA-F]+)/i ? hex($1) : undef', + ], + }, + 0x0513 => { # new for 7D + Name => 'ManualAFPointSelectPattern', + PrintConv => { + 0 => 'Stops at AF area edges', + 1 => 'Continuous', + }, + }, + 0x0514 => { # new for 7D + Name => 'DisplayAllAFPoints', + PrintConv => \%enableDisable, + }, + 0x0515 => { # new for 7D + Name => 'FocusDisplayAIServoAndMF', + PrintConv => \%enableDisable, + }, + 0x0516 => { # new for 7D and 1DmkIV + Name => 'OrientationLinkedAFPoint', + PrintConv => { + 0 => 'Same for vertical and horizontal', + 1 => 'Select different AF points', + }, + }, + 0x0517 => { # new for 1DmkIV + Name => 'MultiControllerWhileMetering', + PrintConv => { + 0 => 'Off', + 1 => 'AF point selection', + }, + }, + 0x0518 => { # new for 6D + Name => 'AccelerationTracking', + }, + 0x0519 => { # new for 6D + Name => 'AIServoFirstImagePriority', + PrintConv => { #(NC) + -1 => 'Release priority', + 0 => 'Equal priority', + 1 => 'Focus priority', + }, + }, + 0x051a => { # new for 6D + Name => 'AIServoSecondImagePriority', + PrintConv => { #(NC) + -1 => 'Shooting speed priority', + 0 => 'Equal priority', + 1 => 'Focus priority', + }, + }, + 0x051b => { # (70D) + Name => 'AFAreaSelectMethod', + PrintConv => { + 0 => 'AF area selection button', + 1 => 'Main dial', + }, + }, + 0x051c => { # (750D) + Name => 'AutoAFPointColorTracking', + PrintConv => { + 0 => 'On-Shot AF only', + 1 => 'Disable', + }, + }, + 0x051d => { # (750D/760D) + Name => 'VFDisplayIllumination', + PrintConv => [{ + 0 => 'Auto', + 1 => 'Enable', + 2 => 'Disable', + },{ + 0 => 'Non-illuminated', #(NC) + 1 => 'Illuminated', #(NC) + }], + }, + 0x051e => { # (80D) + Name => 'InitialAFPointAIServoAF', + PrintConv => { + 0 => 'Auto', + 1 => 'Initial AF point selected', + 2 => 'Manual AF point', + }, + }, + #### 3b) Drive + 0x060f => { + Name => 'MirrorLockup', + Notes => 'value of 2 not used by some models', + PrintConv => { + 0 => 'Disable', + 1 => 'Enable', + 2 => 'Enable: Down with Set', + }, + }, + 0x0610 => [{ # (1DXmkIII) + Name => 'ContinuousShootingSpeed', + Condition => '$count == 6', + Count => 6, + PrintConv => [ + \%disableEnable, + '"Hi $val"', + '"Cont $val"', + '"Lo $val"', + '"Soft $val"', + '"Soft LS $val"', + ], + PrintConvInv => [ + undef, + '$val=~/(\d+)/ ? $1 : 0', + '$val=~/(\d+)/ ? $1 : 0', + '$val=~/(\d+)/ ? $1 : 0', + '$val=~/(\d+)/ ? $1 : 0', + '$val=~/(\d+)/ ? $1 : 0', + ], + },{ # (1DXmkIII firmware 1.3) + Name => 'ContinuousShootingSpeed', + Condition => '$count == 5', + Count => 5, + PrintConv => [ + '"Hi $val"', + '"Cont $val"', + '"Lo $val"', + '"Soft $val"', + '"Soft LS $val"', + ], + PrintConvInv => [ + '$val=~/(\d+)/ ? $1 : 0', + '$val=~/(\d+)/ ? $1 : 0', + '$val=~/(\d+)/ ? $1 : 0', + '$val=~/(\d+)/ ? $1 : 0', + '$val=~/(\d+)/ ? $1 : 0', + ], + },{ # others + Name => 'ContinuousShootingSpeed', + Count => 3, + PrintConv => [ + \%disableEnable, + '"Hi $val"', + '"Lo $val"', + ], + PrintConvInv => [ + undef, + '$val=~/(\d+)/ ? $1 : 0', + '$val=~/(\d+)/ ? $1 : 0', + ], + }], + 0x0611 => { + Name => 'ContinuousShotLimit', + Count => 2, + PrintConv => [ + \%disableEnable, + '"$val shots"', + ], + PrintConvInv => [ + undef, + '$val=~/(\d+)/ ? $1 : 0', + ], + }, + 0x0612 => [{ # (1DXmkIII) + Name => 'RestrictDriveModes', + Condition => '$count == 1', + Count => 1, + PrintConv => 'sprintf("Flags 0x%x",$val)', + PrintConvInv => '$val=~/0x([\dA-F]+)/i ? hex($1) : undef', + },{ # (1DX) + Name => 'RestrictDriveModes', + Count => 2, + PrintConv => [ + \%disableEnable, + 'sprintf("Flags 0x%x",$val)', # (Single,Cont Hi,Cont Lo,Timer 10,Timer 2,Silent,Super Hi) + ], + PrintConvInv => [ + undef, + '$val=~/0x([\dA-F]+)/i ? hex($1) : undef', + ], + }], + #### 4a) Operation + 0x0701 => [ + { + Name => 'Shutter-AELock', + Condition => q{ + $$self{Model} =~ /\b(1000D|XS|Kiss F|500D|T1i|Kiss X3|550D|T2i|Kiss X4)\b/ or + $$self{Model} =~ /\b(600D|T3i|Kiss X5|1100D|T3|Kiss X50)\b/ + }, + Notes => '500D, 550D, 600D, 1000D and 1100D', + PrintConv => { + 0 => 'AF/AE lock', + 1 => 'AE lock/AF', + 2 => 'AF/AF lock, No AE lock', + 3 => 'AE/AF, No AE lock', + }, + }, + { + Name => 'Shutter-AELock', + Condition => '$count == 2', + Notes => '250D', + PrintConv => { + '0 0' => 'AF/AE lock', + '1 0' => 'AE lock/AF', + '2 0' => 'AF/AF lock, No AE lock', + '3 0' => 'AE/AF, No AE lock', + }, + }, + { + Name => 'AFAndMeteringButtons', + Condition => '$$self{Model} =~ /\b60D\b/', + Notes => '60D', + PrintConv => { + 0 => 'Metering start', + 1 => 'Metering + AF start', + 2 => 'AE lock', + 3 => 'AF stop', + 4 => 'No function', + }, + }, + { + Name => 'ShutterButtonAFOnButton', + Notes => 'other models', + PrintConv => { + 0 => 'Metering + AF start', + 1 => 'Metering + AF start/AF stop', + 2 => 'Metering start/Meter + AF start', + 3 => 'AE lock/Metering + AF start', + 4 => 'Metering + AF start/disable', + }, + }, + ], + 0x0702 => { + Name => 'AFOnAELockButtonSwitch', + PrintConv => \%disableEnable, + }, + 0x0703 => { + Name => 'QuickControlDialInMeter', + PrintConv => { + 0 => 'Exposure comp/Aperture', + 1 => 'AF point selection', + 2 => 'ISO speed', + 3 => 'AF point selection swapped with Exposure comp', #4 + 4 => 'ISO speed swapped with Exposure comp', #4 + }, + }, + 0x0704 => [ + { + Name => 'SetButtonWhenShooting', + Condition => '$$self{Model} =~ /\b(40D|50D|5D Mark II)\b/', + Notes => '40D, 50D and 5DmkII; value of 5 is new for 50D, and 6 is new for 5DmkII', + PrintConv => { + 0 => 'Normal (disabled)', + 1 => 'Image quality', + 2 => 'Picture style', + 3 => 'Menu display', + 4 => 'Image playback', + 5 => 'Quick control screen', #50D + 6 => 'Record movie (Live View)', #5DmkII + }, + }, + { + Name => 'SetButtonWhenShooting', + Condition => '$$self{Model} =~ /\b60D\b/', + Notes => '60D', + PrintConv => { + 0 => 'Normal (disabled)', + 1 => 'Image quality', + 2 => 'Picture style', + 3 => 'White balance', + 4 => 'Flash exposure compensation', + 5 => 'Viewfinder leveling gauge', + }, + }, + { + Name => 'SetButtonWhenShooting', + Condition => '$$self{Model} =~ /\b(450D|XSi|Kiss X2|550D|T2i|Kiss X4|600D|T3i|Kiss X5)\b/', + Notes => '450D, 550D and 600D; value of 5 is new for 550D', + PrintConv => { + 0 => 'Normal (disabled)', + 1 => 'Image quality', + 2 => 'Flash exposure compensation', + 3 => 'LCD monitor On/Off', + 4 => 'Menu display', + 5 => 'ISO speed', + }, + }, + { + Name => 'SetButtonWhenShooting', + Condition => '$$self{Model} =~ /\b(1100D|T3|Kiss X50)\b/', + Notes => '1100D', + PrintConv => { + 0 => 'Normal (disabled)', + 1 => 'Image quality', + 2 => 'Flash exposure compensation', + 3 => 'LCD monitor On/Off', + 4 => 'Menu display', + 5 => 'Depth-of-field preview', + }, + }, + { + Name => 'SetButtonWhenShooting', + Condition => '$$self{Model} =~ /\b(1000D|XS|Kiss F)\b/', + Notes => '1000D', + PrintConv => { + 0 => 'LCD monitor On/Off', + 1 => 'Image quality', + 2 => 'Flash exposure compensation', + 3 => 'Menu display', + 4 => 'Disabled', + }, + }, + { + Name => 'SetButtonWhenShooting', + Condition => '$$self{Model} =~ /\b(500D|T1i|Kiss X3)\b/', + Notes => '500D', + PrintConv => { + 0 => 'Quick control screen', + 1 => 'Image quality', + 2 => 'Flash exposure compensation', + 3 => 'LCD monitor On/Off', + 4 => 'Menu display', + 5 => 'Disabled', + }, + }, + { + Name => 'SetButtonWhenShooting', + Notes => '250D', + Condition => '$count == 2', + # (not sure how to decode this. seen: "37 0") + PrintConv => { }, + }, + { + Name => 'SetButtonWhenShooting', + Notes => '1DmkIII and 1DmkIV', + PrintConv => { + 0 => 'Normal (disabled)', + 1 => 'White balance', + 2 => 'Image size', + 3 => 'ISO speed', + 4 => 'Picture style', + 5 => 'Record func. + media/folder', + 6 => 'Menu display', + 7 => 'Image playback', + }, + }, + ], + 0x0705 => { + Name => 'ManualTv', + Description => 'Manual Tv/Av For M', + PrintConv => { + 0 => 'Tv=Main/Av=Control', + 1 => 'Tv=Control/Av=Main', + }, + }, + 0x0706 => { + Name => 'DialDirectionTvAv', + PrintConv => { + 0 => 'Normal', + 1 => 'Reversed', + }, + }, + 0x0707 => { + Name => 'AvSettingWithoutLens', + PrintConv => \%disableEnable, + }, + 0x0708 => { + Name => 'WBMediaImageSizeSetting', + PrintConv => { + 0 => 'Rear LCD panel', + 1 => 'LCD monitor', + 2 => 'Off (disable button)', # (1DX) + }, + }, + 0x0709 => { + Name => 'LockMicrophoneButton', + PrintConv => [{ + # called "sound" in 1DmkIII manual, and "memo" in 1DmkIV manual + 0 => 'Protect (hold:record memo)', + 1 => 'Record memo (protect:disable)', + 2 => 'Play memo (hold:record memo)', # new with 1DmkIV + 3 => 'Rating (protect/memo:disable)', # new with 1DX + }], + # (not sure what the 2nd number is -- new for 1DX. Seen a value of 31. + # Memo quality may be set to 48kHz or 8kHz through another option that + # doesn't seem to be stored separately -- is this it?) + }, + 0x070a => { + Name => 'ButtonFunctionControlOff', + PrintConv => { + 0 => 'Normal (enable)', + 1 => 'Disable main, Control, Multi-control', + }, + }, + 0x070b => { # 50D (also, 5DmkII writes this but it isn't in user guide) + Name => 'AssignFuncButton', + PrintConv => { + 0 => 'LCD brightness', + 1 => 'Image quality', + 2 => 'Exposure comp./AEB setting', + 3 => 'Image jump with main dial', + 4 => 'Live view function settings', + }, + }, + 0x070c => { # new for 7D + Name => 'CustomControls', + # (too much stuff to decode) + }, + 0x070d => { # new for 1DmkIV + Name => 'StartMovieShooting', + PrintConv => { + 0 => 'Default (from LV)', + 1 => 'Quick start (FEL button)', + }, + }, + 0x070e => { # new for 1100D + Name => 'FlashButtonFunction', + PrintConv => { + 0 => 'Raise built-in flash', + 1 => 'ISO speed', + }, + }, + 0x070f => { # new for 5DmkIII + Name => 'MultiFunctionLock', + PrintConv => [ + { #(NC) + 0 => 'Off', + 1 => 'On', # "On (main dial)" for 750D/760D? + 2 => 'On (quick control dial)', #(NC) + 3 => 'On (main dial and quick control dial)', #(NC) + }, + { BITMASK => { #(NC) + 0 => 'Main dial', + 1 => 'Quick control dial', + 2 => 'Multi-controller', + }}, + ], + }, + 0x0710 => { # (M) + Name => 'TrashButtonFunction', + PrintConv => { + 0 => 'Normal (set center AF point)', + 1 => 'Depth-of-field preview', + }, + }, + 0x0711 => { # (M) + Name => 'ShutterReleaseWithoutLens', + PrintConv => \%disableEnable, + }, + 0x0712 => { # (R) + Name => 'ControlRingRotation', + PrintConv => { + 0 => 'Normal', + 1 => 'Reversed', + }, + }, + 0x0713 => { # (R) + Name => 'FocusRingRotation', + PrintConv => { + 0 => 'Normal', + 1 => 'Reversed', + }, + }, + 0x0714 => { # (R) + Name => 'RFLensMFFocusRingSensitivity', + PrintConv => { + 0 => 'Varies With Rotation Speed', + 1 => 'Linked To Rotation Angle', + }, + }, + 0x0715 => { # (R) + Name => 'CustomizeDials', # (NC, may be CustomizeM-FnBar) + # (too much stuff to decode) + }, + #### 4b) Others + 0x080b => [ + { + Name => 'FocusingScreen', + Condition => '$$self{Model} =~ /\b(40D|50D|60D)\b/', + Notes => '40D, 50D and 60D', + PrintConv => { + 0 => 'Ef-A', + 1 => 'Ef-D', + 2 => 'Ef-S', + }, + }, + { + Name => 'FocusingScreen', + Condition => '$$self{Model} =~ /\b5D Mark II\b/', + Notes => '5D Mark II', + PrintConv => { + 0 => 'Eg-A', + 1 => 'Eg-D', + 2 => 'Eg-S', + }, + }, + { + Name => 'FocusingScreen', + Condition => '$$self{Model} =~ /\b6D\b/', + Notes => '6D', + PrintConv => { + 0 => 'Eg-A II', + 1 => 'Eg-D', + 2 => 'Eg-S', + }, + }, + { + Name => 'FocusingScreen', + Condition => '$$self{Model} =~ /\b7D Mark II\b/', + Notes => '7D Mark II', + PrintConv => { + 0 => 'Eh-A', + 1 => 'Eh-S', + }, + }, + { + Name => 'FocusingScreen', + Condition => '$$self{Model} =~ /\b1D X\b/', + Notes => '1DX', + PrintConv => { + 0 => 'Ec-CV', + 1 => 'Ec-A,B,D,H,I,L', + }, + }, + { + Name => 'FocusingScreen', + Notes => '1DmkIII, 1DSmkIII and 1DmkIV', + PrintConv => { + 0 => 'Ec-CIV', + 1 => 'Ec-A,B,C,CII,CIII,D,H,I,L', + 2 => 'Ec-S', + 3 => 'Ec-N,R', + }, + }, + ], + 0x080c => [{ # (1DXmkIII) + Name => 'TimerLength', + Condition => '$count == 3', + Count => 3, + PrintConv => [ + '"6 s: $val"', + '"16 s: $val"', + '"After release: $val"', + ], + PrintConvInv => [ + '$val=~/(\d+)$/ ? $1 : 0', + '$val=~/(\d+)$/ ? $1 : 0', + '$val=~/(\d+)$/ ? $1 : 0', + ], + },{ + Name => 'TimerLength', + Count => 4, + PrintConv => [ + \%disableEnable, + '"6 s: $val"', + '"16 s: $val"', + '"After release: $val"', + ], + PrintConvInv => [ + undef, + '$val=~/(\d+)$/ ? $1 : 0', + '$val=~/(\d+)$/ ? $1 : 0', + '$val=~/(\d+)$/ ? $1 : 0', + ], + }], + 0x080d => { + Name => 'ShortReleaseTimeLag', + PrintConv => \%disableEnable, + }, + 0x080e => { + Name => 'AddAspectRatioInfo', + PrintConv => { + 0 => 'Off', + 1 => '6:6', + 2 => '3:4', + 3 => '4:5', + 4 => '6:7', + 5 => '10:12', + 6 => '5:7', + }, + }, + 0x080f => { + Name => 'AddOriginalDecisionData', # called ("image verification" in 1DmkIV manual) + PrintConv => \%offOn, + }, + 0x0810 => { + Name => 'LiveViewExposureSimulation', + PrintConv => { + 0 => 'Disable (LCD auto adjust)', + 1 => 'Enable (simulates exposure)', + }, + }, + 0x0811 => { + Name => 'LCDDisplayAtPowerOn', + PrintConv => { + 0 => 'Display', + 1 => 'Retain power off status', + }, + }, + 0x0812 => { # (1DX) + Name => 'MemoAudioQuality', + PrintConv => { + 0 => 'High (48 kHz)', + 1 => 'Low (8 kHz)', + }, + }, + 0x0813 => { # (5DmkIII) + Name => 'DefaultEraseOption', + PrintConv => { + 0 => 'Cancel selected', + 1 => 'Erase selected', + 2 => 'Erase RAW selected', # (1DXmkIII) + 3 => 'Erase non-RAW selected', # (1DXmkIII) + }, + }, + 0x0814 => { # (5DS) + Name => 'RetractLensOnPowerOff', + PrintConv => \%enableDisable, + }, + 0x0815 => { # (R) + Name => 'AddIPTCInformation', + PrintConv => \%disableEnable, + }, + 0x0816 => { # (90D,RP) + Name => 'AudioCompression', + PrintConv => \%enableDisable, + } +); + +#------------------------------------------------------------------------------ +# Conversion routines +# Inputs: 0) value to convert +sub ConvertPfn($) +{ + my $val = shift; + return $val ? ($val==1 ? 'On' : "On ($val)") : "Off"; +} +sub ConvertPfnInv($) +{ + my $val = shift; + return $1 if $val =~ /(\d+)/; + return 1 if $val =~ /on/i; + return 0 if $val =~ /off/i; + return undef; +} + +#------------------------------------------------------------------------------ +# Read/Write Canon custom 2 directory (new for 1D Mark III) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessCanonCustom2($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $offset = $$dirInfo{DirStart}; + my $size = $$dirInfo{DirLen}; + my $write = $$dirInfo{Write}; + my $verbose = $et->Options('Verbose'); + my $newTags; + + return 0 if $size < 2; + # first entry in array must be the size + my $len = Get16u($dataPt, $offset); + unless ($len == $size and $len >= 8) { + $et->Warn('Invalid CanonCustom2 data'); + return 0; + } + # get group count + my $count = Get32u($dataPt, $offset + 4); + if ($write) { + $newTags = $et->GetNewTagInfoHash($tagTablePtr); + $et->VPrint(0, " Rewriting CanonCustom2\n"); + } elsif ($verbose) { + $et->VerboseDir('CanonCustom2', $count, $len); + } + my $pos = $offset + 8; + my $end = $offset + $size; + # loop through group records + for (; $pos<$end; ) { + last if $pos + 12 > $end; + my $recNum = Get32u($dataPt, $pos); + my $recLen = Get32u($dataPt, $pos + 4); + my $recCount = Get32u($dataPt, $pos + 8); + last if $recLen < 8; # must be at least 8 bytes for recNum and recLen + $pos += 12; + my $recPos = $pos; + my $recEnd = $pos + $recLen - 8; + if ($recEnd > $end) { + $et->Warn("Corrupted CanonCustom2 group $recNum"); + return 0; + } + if ($verbose and not $write) { + $et->VerboseDir("CanonCustom2 group $recNum", $recCount, $recLen); + } + my ($i, $num, $tag); + for ($i=0; $recPos + 8 < $recEnd; ++$i, $recPos+=4*$num) { + $tag = Get32u($dataPt, $recPos); + $num = Get32u($dataPt, $recPos + 4); + my $nextRec = $recPos + 8 + $num * 4; + last if $nextRec > $recEnd; + # patch for EOS-1DXmkIII firmware 1.0.0 bug: + if ($tag == 0x70c and $num == 0x66 and $nextRec + 8 < $recEnd) { + my $tmp = Get32u($dataPt, $nextRec + 4); + if ($tmp == 0x70f) { + $et->Warn('Fixed incorrect CanonCustom tag 0x70c size', 1); + ++$num; # (count should be one greater) + } + } + $recPos += 8; + my $val = ReadValue($dataPt, $recPos, 'int32s', $num, $num * 4); + if ($write) { + # write new value + my $tagInfo = $$newTags{$tag}; + next unless $$newTags{$tag}; + $tagInfo = $et->GetTagInfo($tagTablePtr, $tag, \$val, undef, $num) or next; + my $nvHash = $et->GetNewValueHash($tagInfo) or next; + next unless $et->IsOverwriting($nvHash, $val); + my $newVal = $et->GetNewValue($nvHash); + next unless defined $newVal; # can't delete from a custom table + WriteValue($newVal, 'int32s', $num, $dataPt, $recPos); + $et->VerboseValue("- CanonCustom:$$tagInfo{Name}", $val); + $et->VerboseValue("+ CanonCustom:$$tagInfo{Name}", $newVal); + ++$$et{CHANGED}; + } else { + # save extracted tag + my $oldInfo = $$tagTablePtr{$tag}; + $et->HandleTag($tagTablePtr, $tag, $val, + Index => $i, + Format => 'int32u', + Count => $num, + Size => $num * 4, + ); + my $tagInfo = $$tagTablePtr{$tag}; + # generate properly formatted description if we just added the tag + if ($tagInfo and not $oldInfo) { + ($$tagInfo{Description} = $$tagInfo{Name}) =~ tr/_/ /; + $$tagInfo{Description} =~ s/CanonCustom Functions/Canon Custom Functions /; + } + } + } + if ($i != $recCount) { + $et->Warn('Missing '.($recCount-$i)." entries in CanonCustom2 group $recNum directory", 1); + } + $pos = $recEnd; + } + if ($pos != $end) { + # Note: a firmware bug in the EOS M5 and M6 stores an incorrect + # size for the 2nd CanonCustom2 record, so this message is expected + # for these models... + $et->Warn('Possibly corrupted CanonCustom2 data'); + return 0; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Write Canon custom 2 data +# Inputs: 0) ExifTool object reference, 1) dirInfo hash ref, 2) tag table ref +# Returns: New custom data block or undefined on error +sub WriteCanonCustom2($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + my $dataPt = $$dirInfo{DataPt}; + # edit a copy of the custom function 2 data + my $buff = substr($$dataPt, $$dirInfo{DirStart}, $$dirInfo{DirLen}); + my %dirInfo = ( + DataPt => \$buff, + DirStart => 0, + DirLen => $$dirInfo{DirLen}, + Write => 1, + ); + ProcessCanonCustom2($et, \%dirInfo, $tagTablePtr) or return undef; + return $buff; +} + +#------------------------------------------------------------------------------ +# Process Canon custom directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessCanonCustom($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $offset = $$dirInfo{DirStart}; + my $size = $$dirInfo{DirLen}; + my $verbose = $et->Options('Verbose'); + + # first entry in array must be the size + my $len = Get16u($dataPt,$offset); + unless ($len == $size or ($$et{Model}=~/\bD60\b/ and $len+2 == $size)) { + $et->Warn("Invalid CanonCustom data"); + return 0; + } + $verbose and $et->VerboseDir('CanonCustom', $size/2-1); + my $pos; + for ($pos=2; $pos<$size; $pos+=2) { + # ($pos is position within custom directory) + my $val = Get16u($dataPt,$offset+$pos); + my $tag = ($val >> 8); + $val = ($val & 0xff); + $et->HandleTag($tagTablePtr, $tag, $val, + Index => $pos/2-1, + Format => 'int8u', + Count => 1, + Size => 1, + ); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Check new value for Canon custom data block +# Inputs: 0) ExifTool object reference, 1) tagInfo hash ref, 2) raw value ref +# Returns: error string or undef (and may modify value) on success +sub CheckCanonCustom($$$) +{ + my ($et, $tagInfo, $valPtr) = @_; + return Image::ExifTool::CheckValue($valPtr, 'int8u'); +} + +#------------------------------------------------------------------------------ +# Write Canon custom data +# Inputs: 0) ExifTool object reference, 1) dirInfo hash ref, 2) tag table ref +# Returns: New custom data block or undefined on error +sub WriteCanonCustom($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || length($$dataPt) - $dirStart; + my $dirName = $$dirInfo{DirName}; + my $newData = substr($$dataPt, $dirStart, $dirLen) or return undef; + $dataPt = \$newData; + + # first entry in array must be the size + my $len = Get16u($dataPt, 0); + unless ($len == $dirLen or ($$et{Model}=~/\bD60\b/ and $len+2 == $dirLen)) { + $et->Warn("Invalid CanonCustom data"); + return undef; + } + my $newTags = $et->GetNewTagInfoHash($tagTablePtr); + my $pos; + for ($pos=2; $pos<$dirLen; $pos+=2) { + my $val = Get16u($dataPt, $pos); + my $tag = ($val >> 8); + my $tagInfo = $$newTags{$tag}; + next unless $tagInfo; + my $nvHash = $et->GetNewValueHash($tagInfo); + $val = ($val & 0xff); + next unless $et->IsOverwriting($nvHash, $val); + my $newVal = $et->GetNewValue($nvHash); + next unless defined $newVal; # can't delete from a custom table + Set16u(($newVal & 0xff) + ($tag << 8), $dataPt, $pos); + $et->VerboseValue("- $dirName:$$tagInfo{Name}", $val); + $et->VerboseValue("+ $dirName:$$tagInfo{Name}", $newVal); + ++$$et{CHANGED}; + } + return $newData; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::CanonCustom - Read and Write Canon custom functions + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +The Canon custom functions meta information is very specific to the +camera model, and is found in both the EXIF maker notes and in the +Canon RAW files. This module contains the definitions necessary for +Image::ExifTool to read this information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Christian Koller for his work in decoding the 20D custom +functions, Rainer Honle for decoding the 5D custom functions and David +Pitcher for adding a few undocumented 1DmkIII settings. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Canon Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/CanonRaw.pm b/ExifTool/lib/Image/ExifTool/CanonRaw.pm new file mode 100644 index 0000000..53f619f --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/CanonRaw.pm @@ -0,0 +1,921 @@ +#------------------------------------------------------------------------------ +# File: CanonRaw.pm +# +# Description: Read Canon RAW (CRW) meta information +# +# Revisions: 11/25/2003 - P. Harvey Created +# 12/02/2003 - P. Harvey Completely reworked and figured out many +# more tags +# +# References: 1) http://www.cybercom.net/~dcoffin/dcraw/ +# 2) http://www.wonderland.org/crw/ +# 3) http://xyrion.org/ciff/CIFFspecV1R04.pdf +# 4) Dave Nicholson private communication (PowerShot S30) +#------------------------------------------------------------------------------ + +package Image::ExifTool::CanonRaw; + +use strict; +use vars qw($VERSION $AUTOLOAD %crwTagFormat); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::Canon; + +$VERSION = '1.61'; + +sub WriteCRW($$); +sub ProcessCanonRaw($$$); +sub WriteCanonRaw($$$); +sub CheckCanonRaw($$$); +sub InitMakerNotes($); +sub SaveMakerNotes($); +sub BuildMakerNotes($$$$$$); + +# formats for CRW tag types (($tag >> 8) & 0x38) +# Note: don't define format for undefined types +%crwTagFormat = ( + 0x00 => 'int8u', + 0x08 => 'string', + 0x10 => 'int16u', + 0x18 => 'int32u', + # 0x20 => 'undef', + # 0x28 => 'undef', + # 0x30 => 'undef', +); + +# Canon raw file tag table +# Note: Tag ID's have upper 2 bits set to zero, since these 2 bits +# just specify the location of the information +%Image::ExifTool::CanonRaw::Main = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessCanonRaw, + WRITE_PROC => \&WriteCanonRaw, + CHECK_PROC => \&CheckCanonRaw, + WRITABLE => 1, + 0x0000 => { Name => 'NullRecord', Writable => 'undef' }, #3 + 0x0001 => { #3 + Name => 'FreeBytes', + Format => 'undef', + Binary => 1, + }, + 0x0032 => { Name => 'CanonColorInfo1', Writable => 0 }, + 0x0805 => [ + # this tag is found in more than one directory... + { + Condition => '$self->{DIR_NAME} eq "ImageDescription"', + Name => 'CanonFileDescription', + Writable => 'string[32]', + }, + { + Name => 'UserComment', + Writable => 'string[256]', + }, + ], + 0x080a => { + Name => 'CanonRawMakeModel', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::MakeModel' }, + }, + 0x080b => { Name => 'CanonFirmwareVersion', Writable => 'string[32]' }, + 0x080c => { Name => 'ComponentVersion', Writable => 'string' }, #3 + 0x080d => { Name => 'ROMOperationMode', Writable => 'string[8]' }, #3 + 0x0810 => { Name => 'OwnerName', Writable => 'string[32]' }, + 0x0815 => { Name => 'CanonImageType', Writable => 'string[32]' }, + 0x0816 => { Name => 'OriginalFileName', Writable => 'string[32]' }, + 0x0817 => { Name => 'ThumbnailFileName', Writable => 'string[32]' }, + 0x100a => { #3 + Name => 'TargetImageType', + Writable => 'int16u', + PrintConv => { + 0 => 'Real-world Subject', + 1 => 'Written Document', + }, + }, + 0x1010 => { #3 + Name => 'ShutterReleaseMethod', + Writable => 'int16u', + PrintConv => { + 0 => 'Single Shot', + 2 => 'Continuous Shooting', + }, + }, + 0x1011 => { #3 + Name => 'ShutterReleaseTiming', + Writable => 'int16u', + PrintConv => { + 0 => 'Priority on shutter', + 1 => 'Priority on focus', + }, + }, + 0x1016 => { Name => 'ReleaseSetting', Writable => 'int16u' }, #3 + 0x101c => { Name => 'BaseISO', Writable => 'int16u' }, #3 + 0x1028=> { #PH + Name => 'CanonFlashInfo', + Writable => 'int16u', + Count => 4, + Unknown => 1, + }, + 0x1029 => { + Name => 'CanonFocalLength', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::FocalLength' }, + }, + 0x102a => { + Name => 'CanonShotInfo', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ShotInfo' }, + }, + 0x102c => { + Name => 'CanonColorInfo2', + Writable => 0, + # for the S30, the following information has been decoded: (ref 4) + # offset 66: int32u - shutter half press time in ms + # offset 70: int32u - image capture time in ms + # offset 74: int16u - custom white balance flag (0=Off, 512=On) + }, + 0x102d => { + Name => 'CanonCameraSettings', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraSettings' }, + }, + 0x1030 => { #4 + Name => 'WhiteSample', + Writable => 0, + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonRaw::WhiteSample', + }, + }, + 0x1031 => { + Name => 'SensorInfo', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::SensorInfo' }, + }, + # this tag has only be verified for the 10D in CRW files, but the D30 and D60 + # also produce CRW images and have CustomFunction information in their JPEG's + 0x1033 => [ + { + Name => 'CustomFunctions10D', + Condition => '$self->{Model} =~ /EOS 10D/', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::Functions10D', + }, + }, + { + Name => 'CustomFunctionsD30', + Condition => '$self->{Model} =~ /EOS D30\b/', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::FunctionsD30', + }, + }, + { + Name => 'CustomFunctionsD60', + Condition => '$self->{Model} =~ /EOS D60\b/', + SubDirectory => { + # the stored size in the D60 apparently doesn't include the size word: + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size-2,$size)', + # (D60 custom functions are basically the same as D30) + TagTable => 'Image::ExifTool::CanonCustom::FunctionsD30', + }, + }, + { + Name => 'CustomFunctionsUnknown', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonCustom::FuncsUnknown', + }, + }, + ], + 0x1038 => { + Name => 'CanonAFInfo', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::AFInfo' }, + }, + 0x1093 => { + Name => 'CanonFileInfo', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::Canon::FileInfo', + }, + }, + 0x10a9 => { + Name => 'ColorBalance', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorBalance' }, + }, + 0x10b5 => { #PH + Name => 'RawJpgInfo', + SubDirectory => { + Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)', + TagTable => 'Image::ExifTool::CanonRaw::RawJpgInfo', + }, + }, + 0x10ae => { + Name => 'ColorTemperature', + Writable => 'int16u', + }, + 0x10b4 => { + Name => 'ColorSpace', + Writable => 'int16u', + PrintConv => { + 1 => 'sRGB', + 2 => 'Adobe RGB', + 0xffff => 'Uncalibrated', + }, + }, + 0x1803 => { #3 + Name => 'ImageFormat', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::ImageFormat' }, + }, + 0x1804 => { Name => 'RecordID', Writable => 'int32u' }, #3 + 0x1806 => { #3 + Name => 'SelfTimerTime', + Writable => 'int32u', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => '"$val s"', + PrintConvInv => '$val=~s/\s*s.*//;$val', + }, + 0x1807 => { + Name => 'TargetDistanceSetting', + Format => 'float', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + 0x180b => [ + { + # D30 + Name => 'SerialNumber', + Condition => '$$self{Model} =~ /EOS D30\b/', + Writable => 'int32u', + PrintConv => 'sprintf("%x-%.5d",$val>>16,$val&0xffff)', + PrintConvInv => '$val=~/(.*)-(\d+)/ ? (hex($1)<<16)+$2 : undef', + }, + { + # all EOS models (D30, 10D, 300D) + Name => 'SerialNumber', + Condition => '$$self{Model} =~ /EOS/', + Writable => 'int32u', + PrintConv => 'sprintf("%.10d",$val)', + PrintConvInv => '$val', + }, + { + # this is not SerialNumber for PowerShot models (but what is it?) - PH + Name => 'UnknownNumber', + Unknown => 1, + }, + ], + 0x180e => { + Name => 'TimeStamp', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::CanonRaw::TimeStamp', + }, + }, + 0x1810 => { + Name => 'ImageInfo', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::CanonRaw::ImageInfo', + }, + }, + 0x1813 => { #3 + Name => 'FlashInfo', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::CanonRaw::FlashInfo', + }, + }, + 0x1814 => { #3 + Name => 'MeasuredEV', + Notes => q{ + this is the Canon name for what could better be called MeasuredLV, and + should be close to the calculated LightValue for a proper exposure with most + models + }, + Format => 'float', + ValueConv => '$val + 5', + ValueConvInv => '$val - 5', + }, + 0x1817 => { + Name => 'FileNumber', + Writable => 'int32u', + Groups => { 2 => 'Image' }, + PrintConv => '$_=$val;s/(\d+)(\d{4})/$1-$2/;$_', + PrintConvInv => '$_=$val;s/-//;$_', + }, + 0x1818 => { #3 + Name => 'ExposureInfo', + Groups => { 1 => 'CIFF' }, # (only so CIFF shows up in group lists) + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::ExposureInfo' }, + }, + 0x1834 => { #PH + Name => 'CanonModelID', + Writable => 'int32u', + PrintHex => 1, + Notes => q{ + this is the complete list of model ID numbers, but note that many of these + models do not produce CRW images + }, + SeparateTable => 'Canon CanonModelID', + PrintConv => \%Image::ExifTool::Canon::canonModelID, + }, + 0x1835 => { + Name => 'DecoderTable', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::DecoderTable' }, + }, + 0x183b => { #PH + # display format for serial number + Name => 'SerialNumberFormat', + Writable => 'int32u', + PrintHex => 1, + PrintConv => { + 0x90000000 => 'Format 1', + 0xa0000000 => 'Format 2', + }, + }, + 0x2005 => { + Name => 'RawData', + Writable => 0, + Binary => 1, + }, + 0x2007 => { + Name => 'JpgFromRaw', + Groups => { 2 => 'Preview' }, + Writable => 'resize', # 'resize' allows this value to change size + Permanent => 0, + RawConv => '$self->ValidateImage(\$val,$tag)', + }, + 0x2008 => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Writable => 'resize', # 'resize' allows this value to change size + WriteCheck => '$self->CheckImage(\$val)', + Permanent => 0, + RawConv => '$self->ValidateImage(\$val,$tag)', + }, + # the following entries are subdirectories + # (any 0x28 and 0x30 tag types are handled automatically by the decoding logic) + 0x2804 => { + Name => 'ImageDescription', + SubDirectory => { }, + Writable => 0, + }, + 0x2807 => { #3 + Name => 'CameraObject', + SubDirectory => { }, + Writable => 0, + }, + 0x3002 => { #3 + Name => 'ShootingRecord', + SubDirectory => { }, + Writable => 0, + }, + 0x3003 => { #3 + Name => 'MeasuredInfo', + SubDirectory => { }, + Writable => 0, + }, + 0x3004 => { #3 + Name => 'CameraSpecification', + SubDirectory => { }, + Writable => 0, + }, + 0x300a => { #3 + Name => 'ImageProps', + SubDirectory => { }, + Writable => 0, + }, + 0x300b => { + Name => 'ExifInformation', + SubDirectory => { }, + Writable => 0, + }, +); + +# Canon binary data blocks +%Image::ExifTool::CanonRaw::MakeModel = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + DATAMEMBER => [ 0, 6 ], # indices of data members to extract when writing + WRITABLE => 1, + FORMAT => 'string', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # (can't specify a first entry because this isn't + # a simple binary table with fixed offsets) + 0 => { + Name => 'Make', + Format => 'string[6]', # "Canon\0" + DataMember => 'Make', + RawConv => '$self->{Make} = $val', + }, + 6 => { + Name => 'Model', + Format => 'string', # no size = to the end of the data + Description => 'Camera Model Name', + DataMember => 'Model', + RawConv => '$self->{Model} = $val', + }, +); + +%Image::ExifTool::CanonRaw::TimeStamp = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Time' }, + 0 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Shift => 'Time', + ValueConv => 'ConvertUnixTime($val)', + ValueConvInv => 'GetUnixTime($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + 1 => { #3 + Name => 'TimeZoneCode', + Format => 'int32s', + ValueConv => '$val / 3600', + ValueConvInv => '$val * 3600', + }, + 2 => { #3 + Name => 'TimeZoneInfo', + Notes => 'set to 0x80000000 if TimeZoneCode is valid', + }, +); + +%Image::ExifTool::CanonRaw::ImageFormat = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => { + Name => 'FileFormat', + Flags => 'PrintHex', + PrintConv => { + 0x00010000 => 'JPEG (lossy)', + 0x00010002 => 'JPEG (non-quantization)', + 0x00010003 => 'JPEG (lossy/non-quantization toggled)', + 0x00020001 => 'CRW', + }, + }, + 1 => { + Name => 'TargetCompressionRatio', + Format => 'float', + }, +); + +%Image::ExifTool::CanonRaw::RawJpgInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int16u', + FIRST_ENTRY => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, +# 0 => 'RawJpgInfoSize', + 1 => { #PH + Name => 'RawJpgQuality', + PrintConv => { + 1 => 'Economy', + 2 => 'Normal', + 3 => 'Fine', + 5 => 'Superfine', + }, + }, + 2 => { #PH + Name => 'RawJpgSize', + PrintConv => { + 0 => 'Large', + 1 => 'Medium', + 2 => 'Small', + }, + }, + 3 => 'RawJpgWidth', #PH + 4 => 'RawJpgHeight', #PH +); + +%Image::ExifTool::CanonRaw::FlashInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'float', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => 'FlashGuideNumber', + 1 => 'FlashThreshold', +); + +%Image::ExifTool::CanonRaw::ExposureInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'float', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => 'ExposureCompensation', + 1 => { + Name => 'ShutterSpeedValue', + ValueConv => 'abs($val)<100 ? 1/(2**$val) : 0', + ValueConvInv => '$val>0 ? -log($val)/log(2) : -100', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 2 => { + Name => 'ApertureValue', + ValueConv => '2 ** ($val / 2)', + ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, +); + +%Image::ExifTool::CanonRaw::ImageInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + # Note: Don't make these writable (except rotation) because it confuses + # Canon decoding software if the are changed + 0 => 'ImageWidth', #3 + 1 => 'ImageHeight', #3 + 2 => { #3 + Name => 'PixelAspectRatio', + Format => 'float', + }, + 3 => { + Name => 'Rotation', + Format => 'int32s', + Writable => 'int32s', + }, + 4 => 'ComponentBitDepth', #3 + 5 => 'ColorBitDepth', #3 + 6 => 'ColorBW', #3 +); + +# ref 4 +%Image::ExifTool::CanonRaw::DecoderTable = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + 0 => 'DecoderTableNumber', + 2 => 'CompressedDataOffset', + 3 => 'CompressedDataLength', +); + +# ref 1/4 +%Image::ExifTool::CanonRaw::WhiteSample = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int16u', + FIRST_ENTRY => 1, + 1 => 'WhiteSampleWidth', + 2 => 'WhiteSampleHeight', + 3 => 'WhiteSampleLeftBorder', + 4 => 'WhiteSampleTopBorder', + 5 => 'WhiteSampleBits', + # this is followed by the encrypted white sample values (ref 1) +); + +#------------------------------------------------------------------------------ +# AutoLoad our writer routines when necessary +# +sub AUTOLOAD +{ + return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); +} + +#------------------------------------------------------------------------------ +# Process Raw file directory +# Inputs: 0) ExifTool object reference +# 1) directory information reference, 2) tag table reference +# Returns: 1 on success +sub ProcessCanonRaw($$$) +{ + my ($et, $dirInfo, $rawTagTable) = @_; + my $blockStart = $$dirInfo{DirStart}; + my $blockSize = $$dirInfo{DirLen}; + my $raf = $$dirInfo{RAF} or return 0; + my $buff; + my $verbose = $et->Options('Verbose'); + my $buildMakerNotes = $et->Options('MakerNotes'); + + # 4 bytes at end of block give directory position within block + $raf->Seek($blockStart+$blockSize-4, 0) or return 0; + $raf->Read($buff, 4) == 4 or return 0; + my $dirOffset = Get32u(\$buff,0) + $blockStart; + # avoid infinite recursion + $$et{ProcessedCanonRaw} or $$et{ProcessedCanonRaw} = { }; + if ($$et{ProcessedCanonRaw}{$dirOffset}) { + $et->Warn("Not processing double-referenced $$dirInfo{DirName} directory"); + return 0; + } + $$et{ProcessedCanonRaw}{$dirOffset} = 1; + $raf->Seek($dirOffset, 0) or return 0; + $raf->Read($buff, 2) == 2 or return 0; + my $entries = Get16u(\$buff,0); # get number of entries in directory + # read the directory (10 bytes per entry) + $raf->Read($buff, 10 * $entries) == 10 * $entries or return 0; + + $verbose and $et->VerboseDir('CIFF', $entries); + my $index; + for ($index=0; $index<$entries; ++$index) { + my $pt = 10 * $index; + my $tag = Get16u(\$buff, $pt); + my $size = Get32u(\$buff, $pt+2); + my $valuePtr = Get32u(\$buff, $pt+6); + my $ptr = $valuePtr + $blockStart; # all pointers relative to block start + if ($tag & 0x8000) { + $et->Warn('Bad CRW directory entry'); + return 1; + } + my $tagID = $tag & 0x3fff; # get tag ID + my $tagType = ($tag >> 8) & 0x38; # get tag type + my $valueInDir = ($tag & 0x4000); # flag for value in directory + my $tagInfo = $et->GetTagInfo($rawTagTable, $tagID); + if (($tagType==0x28 or $tagType==0x30) and not $valueInDir) { + # this type of tag specifies a raw subdirectory + my $name; + $tagInfo and $name = $$tagInfo{Name}; + $name or $name = sprintf("CanonRaw_0x%.4x", $tag); + my %subdirInfo = ( + DirName => $name, + DataLen => 0, + DirStart => $ptr, + DirLen => $size, + Nesting => $$dirInfo{Nesting} + 1, + RAF => $raf, + Parent => $$dirInfo{DirName}, + ); + if ($verbose) { + my $fakeInfo = { Name => $name, SubDirectory => { } }; + $et->VerboseInfo($tagID, $fakeInfo, + 'Index' => $index, + 'Size' => $size, + 'Start' => $ptr, + ); + } + $et->ProcessDirectory(\%subdirInfo, $rawTagTable); + next; + } + my ($valueDataPos, $count, $subdir); + my $format = $crwTagFormat{$tagType}; + if ($tagInfo) { + $subdir = $$tagInfo{SubDirectory}; + $format = $$tagInfo{Format} if $$tagInfo{Format}; + $count = $$tagInfo{Count}; + } + # get value data + my ($value, $delRawConv); + if ($valueInDir) { # is the value data in the directory? + # this type of tag stores the value in the 'size' and 'ptr' fields + $valueDataPos = $dirOffset + $pt + 4; # (remember, +2 for the entry count) + $size = 8; + $value = substr($buff, $pt+2, $size); + # set count to 1 by default for normal values in directory + $count = 1 if not defined $count and $format and + $format ne 'string' and not $subdir; + } else { + $valueDataPos = $ptr; + # do hash of image data if requested + if ($$et{ImageDataHash} and $tagID == 0x2005) { + $raf->Seek($ptr, 0) and $et->ImageDataHash($raf, $size, 'raw'); + } + if ($size <= 512 or ($verbose > 2 and $size <= 65536) + or ($tagInfo and ($$tagInfo{SubDirectory} + or grep(/^$$tagInfo{Name}$/i, $et->GetRequestedTags()) ))) + { + # read value if size is small or specifically requested + # or if this is a SubDirectory + unless ($raf->Seek($ptr, 0) and $raf->Read($value, $size) == $size) { + $et->Warn(sprintf("Error reading %d bytes from 0x%x",$size,$ptr)); + next; + } + } else { + $value = "Binary data $size bytes"; + if ($tagInfo) { + if ($et->Options('Binary') or $verbose) { + # read the value anyway + unless ($raf->Seek($ptr, 0) and $raf->Read($value, $size) == $size) { + $et->Warn(sprintf("Error reading %d bytes from 0x%x",$size,$ptr)); + next; + } + } + # force this to be a binary (scalar reference) + $$tagInfo{RawConv} = '\$val'; + $delRawConv = 1; + } + $size = length $value; + undef $format; + } + } + # set count from tagInfo count if necessary + if ($format and not $count) { + # set count according to format and size + my $fnum = $Image::ExifTool::Exif::formatNumber{$format}; + my $fsiz = $Image::ExifTool::Exif::formatSize[$fnum]; + $count = int($size / $fsiz); + } + if ($verbose) { + my $val = $value; + $format and $val = ReadValue(\$val, 0, $format, $count, $size); + $et->VerboseInfo($tagID, $tagInfo, + Table => $rawTagTable, + Index => $index, + Value => $val, + DataPt => \$value, + DataPos => $valueDataPos, + Size => $size, + Format => $format, + Count => $count, + ); + } + if ($buildMakerNotes) { + # build maker notes information if requested + BuildMakerNotes($et, $tagID, $tagInfo, \$value, $format, $count); + } + next unless defined $tagInfo; + + if ($subdir) { + my $name = $$tagInfo{Name}; + my $newTagTable; + if ($$subdir{TagTable}) { + $newTagTable = GetTagTable($$subdir{TagTable}); + unless ($newTagTable) { + warn "Unknown tag table $$subdir{TagTable}\n"; + next; + } + } else { + warn "Must specify TagTable for SubDirectory $name\n"; + next; + } + my $subdirStart = 0; + #### eval Start () + $subdirStart = eval $$subdir{Start} if $$subdir{Start}; + my $dirData = \$value; + my %subdirInfo = ( + Name => $name, + DataPt => $dirData, + DataLen => $size, + DataPos => $valueDataPos, + DirStart => $subdirStart, + DirLen => $size - $subdirStart, + Nesting => $$dirInfo{Nesting} + 1, + RAF => $raf, + DirName => $name, + Parent => $$dirInfo{DirName}, + ); + #### eval Validate ($dirData, $subdirStart, $size) + if (defined $$subdir{Validate} and not eval $$subdir{Validate}) { + $et->Warn("Invalid $name data"); + } else { + $et->ProcessDirectory(\%subdirInfo, $newTagTable, $$subdir{ProcessProc}); + } + } else { + # convert to specified format if necessary + $format and $value = ReadValue(\$value, 0, $format, $count, $size); + # save the information + $et->FoundTag($tagInfo, $value); + delete $$tagInfo{RawConv} if $delRawConv; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# get information from raw file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 if this was a valid Canon RAW file +sub ProcessCRW($$) +{ + my ($et, $dirInfo) = @_; + my ($buff, $sig); + my $raf = $$dirInfo{RAF}; + my $buildMakerNotes = $et->Options('MakerNotes'); + + $raf->Read($buff,2) == 2 or return 0; + SetByteOrder($buff) or return 0; + $raf->Read($buff,4) == 4 or return 0; + $raf->Read($sig,8) == 8 or return 0; # get file signature + $sig =~ /^HEAP(CCDR|JPGM)/ or return 0; # validate signature + my $hlen = Get32u(\$buff, 0); + + $raf->Seek(0, 2) or return 0; # seek to end of file + my $filesize = $raf->Tell() or return 0; + + # initialize maker note data if building maker notes + $buildMakerNotes and InitMakerNotes($et); + + # set the FileType tag unless already done (eg. APP0 CIFF record in JPEG image) + $et->SetFileType(); + + # build directory information for main raw directory + my %dirInfo = ( + DataLen => 0, + DirStart => $hlen, + DirLen => $filesize - $hlen, + Nesting => 0, + RAF => $raf, + Parent => 'CRW', + ); + + # process the raw directory + my $rawTagTable = GetTagTable('Image::ExifTool::CanonRaw::Main'); + my $oldIndent = $$et{INDENT}; + $$et{INDENT} .= '| '; + unless (ProcessCanonRaw($et, \%dirInfo, $rawTagTable)) { + $et->Warn('CRW file format error'); + } + $$et{INDENT} = $oldIndent; + + # finish building maker notes if necessary + $buildMakerNotes and SaveMakerNotes($et); + + # process trailers if they exist in CRW file (not in CIFF information!) + if ($$et{FILE_TYPE} eq 'CRW') { + my $trailInfo = Image::ExifTool::IdentifyTrailer($raf); + $et->ProcessTrailers($trailInfo) if $trailInfo; + } + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::CanonRaw - Read Canon RAW (CRW) meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +meta information from Canon CRW raw files. These files are written directly +by some Canon cameras, and contain meta information similar to that found in +the EXIF Canon maker notes. + +=head1 NOTES + +The CR2 format written by some Canon cameras is very different the CRW +format processed by this module. (CR2 is TIFF-based and uses standard EXIF +tags.) + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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/> + +=item L<http://www.wonderland.org/crw/> + +=item L<http://xyrion.org/ciff/> + +=item L<https://exiftool.org/canon_raw.html> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Dave Nicholson for decoding a number of new tags. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/CanonRaw Tags>, +L<Image::ExifTool::Canon(3pm)|Image::ExifTool::Canon>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/CanonVRD.pm b/ExifTool/lib/Image/ExifTool/CanonVRD.pm new file mode 100644 index 0000000..d135b83 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/CanonVRD.pm @@ -0,0 +1,2284 @@ +#------------------------------------------------------------------------------ +# File: CanonVRD.pm +# +# Description: Read/write Canon VRD and DR4 information +# +# Revisions: 2006/10/30 - P. Harvey Created +# 2007/10/23 - PH Added new VRD 3.0 tags +# 2008/08/29 - PH Added new VRD 3.4 tags +# 2008/12/02 - PH Added new VRD 3.5 tags +# 2010/06/18 - PH Support variable-length CustomPictureStyle data +# 2010/09/14 - PH Added r/w support for XMP in VRD +# 2015/05/16 - PH Added DR4 support (DPP 4.1.50.0) +# 2018/03/13 - PH Update to DPP 4.8.20 +# +# References: 1) Bogdan private communication (Canon DPP v3.4.1.1) +# 2) Gert Kello private communication (DPP 3.8) +#------------------------------------------------------------------------------ + +package Image::ExifTool::CanonVRD; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Canon; + +$VERSION = '1.36'; + +sub ProcessCanonVRD($$;$); +sub WriteCanonVRD($$;$); +sub ProcessEditData($$$); +sub ProcessIHL($$$); +sub ProcessIHLExif($$$); +sub ProcessDR4($$;$); +sub SortDR4($$); + +# map for adding directories to VRD +my %vrdMap = ( + XMP => 'CanonVRD', + CanonVRD => 'VRD', +); + +my %noYes = ( + PrintConvColumns => 2, + PrintConv => { 0 => 'No', 1 => 'Yes' }, +); + +# DR4 format codes +my %vrdFormat = ( + 1 => 'int32u', + 2 => 'string', + 8 => 'int32u', + 9 => 'int32s', + 13 => 'double', + 33 => 'int32u', # (array) + 38 => 'double', # (array) + # 254 => 'undef', ? + 255 => 'undef', +); + +# empty VRD header/footer for creating VRD from scratch +my $blankHeader = "CANON OPTIONAL DATA\0\0\x01\0\0\0\0\0\0"; +my $blankFooter = "CANON OPTIONAL DATA\0" . ("\0" x 42) . "\xff\xd9"; + +# main tag table blocks in CanonVRD trailer (ref PH) +%Image::ExifTool::CanonVRD::Main = ( + WRITE_PROC => \&WriteCanonVRD, + PROCESS_PROC => \&ProcessCanonVRD, + NOTES => q{ + Canon Digital Photo Professional writes VRD (Recipe Data) information as a + trailer record to JPEG, TIFF, CRW and CR2 images, or as stand-alone VRD or + DR4 files. The tags listed below represent information found in these + records. The complete VRD/DR4 data record may be accessed as a block using + the Extra 'CanonVRD' or 'CanonDR4' tag, but this tag is not extracted or + copied unless specified explicitly. + }, + 0xffff00f4 => { + Name => 'EditData', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::Edit' }, + }, + 0xffff00f5 => { + Name => 'IHLData', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::IHL' }, + }, + 0xffff00f6 => { + Name => 'XMP', + Flags => [ 'Binary', 'Protected' ], + Writable => 'undef', # allow writing/deleting as a block + SubDirectory => { + DirName => 'XMP', + TagTable => 'Image::ExifTool::XMP::Main', + }, + }, + 0xffff00f7 => { + Name => 'Edit4Data', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::Edit4' }, + }, +); + +# the VRD edit information is divided into sections +%Image::ExifTool::CanonVRD::Edit = ( + WRITE_PROC => \&ProcessEditData, + PROCESS_PROC => \&ProcessEditData, + VARS => { ID_LABEL => 'Index' }, # change TagID label in documentation + NOTES => 'Canon VRD edit information.', + 0 => { + Name => 'VRD1', + Size => 0x272, # size of version 1.0 edit information in bytes + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::Ver1' }, + }, + 1 => { + Name => 'VRDStampTool', + Size => 0, # size is variable, and obtained from int32u at directory start + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::StampTool' }, + }, + 2 => { + Name => 'VRD2', + Size => undef, # size is the remaining edit data + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::Ver2' }, + }, +); + +# Canon DPP version 4 edit information +%Image::ExifTool::CanonVRD::Edit4 = ( + WRITE_PROC => \&ProcessEditData, + PROCESS_PROC => \&ProcessEditData, + VARS => { ID_LABEL => 'Index' }, # change TagID label in documentation + NOTES => 'Canon DPP version 4 edit information.', + 0 => { + Name => 'DR4', + Size => undef, # size is the remaining edit data + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::DR4' }, + }, +); + +# "IHL Created Optional Item Data" tags (not yet writable) +%Image::ExifTool::CanonVRD::IHL = ( + PROCESS_PROC => \&ProcessIHL, + TAG_PREFIX => 'VRD_IHL', + GROUPS => { 2 => 'Image' }, + 1 => [ + # this contains edited TIFF-format data, with an original IFD at 0x0008 + # and an edited IFD with offset given in the TIFF header. + { + Name => 'IHL_EXIF', + Condition => '$self->Options("ExtractEmbedded")', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&ProcessIHLExif, + }, + },{ + Name => 'IHL_EXIF', + Notes => q{ + extracted as a block if the L<Unknown|../ExifTool.html#Unknown> option is used, or processed as the + first sub-document with the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> option + }, + Binary => 1, + Unknown => 1, + }, + ], + # 2 - written by DPP 3.0.2.6, and it looks something like edit data, + # but I haven't decoded it yet - PH + 3 => { + # (same size as the PreviewImage with DPP 3.0.2.6) + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + 4 => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + 5 => { + Name => 'RawCodecVersion', + ValueConv => '$val =~ s/\0.*//s; $val', # truncate string at null + }, + 6 => { + Name => 'CRCDevelParams', + Binary => 1, + Unknown => 1, + }, +); + +# VRD version 1 tags (ref PH) +%Image::ExifTool::CanonVRD::Ver1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + PERMANENT => 1, # (can't add/delete these individually) + FIRST_ENTRY => 0, + GROUPS => { 2 => 'Image' }, + DATAMEMBER => [ 0x002 ], # necessary for writing +# +# RAW image adjustment +# + 0x002 => { + Name => 'VRDVersion', + Format => 'int16u', + Writable => 0, + DataMember => 'VRDVersion', + RawConv => '$$self{VRDVersion} = $val', + PrintConv => '$val =~ s/^(\d)(\d*)(\d)$/$1.$2.$3/; $val', + }, + 0x006 => { + Name => 'WBAdjRGGBLevels', + Format => 'int16u[4]', + }, + 0x018 => { + Name => 'WhiteBalanceAdj', + Format => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Cloudy', + 3 => 'Tungsten', + 4 => 'Fluorescent', + 5 => 'Flash', + 8 => 'Shade', + 9 => 'Kelvin', + 30 => 'Manual (Click)', + 31 => 'Shot Settings', + }, + }, + 0x01a => { + Name => 'WBAdjColorTemp', + Format => 'int16u', + }, + # 0x01c similar to 0x006 + 0x024 => { + Name => 'WBFineTuneActive', + Format => 'int16u', + %noYes, + }, + 0x028 => { + Name => 'WBFineTuneSaturation', + Format => 'int16u', + }, + 0x02c => { + Name => 'WBFineTuneTone', + Format => 'int16u', + }, + 0x02e => { + Name => 'RawColorAdj', + Format => 'int16u', + PrintConv => { + 0 => 'Shot Settings', + 1 => 'Faithful', + 2 => 'Custom', + }, + }, + 0x030 => { + Name => 'RawCustomSaturation', + Format => 'int32s', + }, + 0x034 => { + Name => 'RawCustomTone', + Format => 'int32s', + }, + 0x038 => { + Name => 'RawBrightnessAdj', + Format => 'int32s', + ValueConv => '$val / 6000', + ValueConvInv => 'int($val * 6000 + ($val < 0 ? -0.5 : 0.5))', + PrintConv => 'sprintf("%.2f",$val)', + PrintConvInv => '$val', + }, + 0x03c => { + Name => 'ToneCurveProperty', + Format => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Shot Settings', + 1 => 'Linear', + 2 => 'Custom 1', + 3 => 'Custom 2', + 4 => 'Custom 3', + 5 => 'Custom 4', + 6 => 'Custom 5', + }, + }, + # 0x040 usually "10 9 2" + 0x07a => { + Name => 'DynamicRangeMin', + Format => 'int16u', + }, + 0x07c => { + Name => 'DynamicRangeMax', + Format => 'int16u', + }, + # 0x0c6 usually "10 9 2" +# +# RGB image adjustment +# + 0x110 => { + Name => 'ToneCurveActive', + Format => 'int16u', + %noYes, + }, + 0x113 => { + Name => 'ToneCurveMode', + PrintConv => { 0 => 'RGB', 1 => 'Luminance' }, + }, + 0x114 => { + Name => 'BrightnessAdj', + Format => 'int8s', + }, + 0x115 => { + Name => 'ContrastAdj', + Format => 'int8s', + }, + 0x116 => { + Name => 'SaturationAdj', + Format => 'int16s', + }, + 0x11e => { + Name => 'ColorToneAdj', + Notes => 'in degrees, so -1 is the same as 359', + Format => 'int32s', + }, + 0x126 => { + Name => 'LuminanceCurvePoints', + Format => 'int16u[21]', + PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', + PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', + }, + 0x150 => { + Name => 'LuminanceCurveLimits', + Notes => '4 numbers: input and output highlight and shadow points', + Format => 'int16u[4]', + }, + 0x159 => { + Name => 'ToneCurveInterpolation', + PrintConv => { 0 => 'Curve', 1 => 'Straight' }, + }, + 0x160 => { + Name => 'RedCurvePoints', + Format => 'int16u[21]', + PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', + PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', + }, + # 0x193 same as 0x159 + 0x19a => { + Name => 'GreenCurvePoints', + Format => 'int16u[21]', + PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', + PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', + }, + # 0x1cd same as 0x159 + 0x1d4 => { + Name => 'BlueCurvePoints', + Format => 'int16u[21]', + PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', + PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', + }, + 0x18a => { + Name => 'RedCurveLimits', + Format => 'int16u[4]', + }, + 0x1c4 => { + Name => 'GreenCurveLimits', + Format => 'int16u[4]', + }, + 0x1fe => { + Name => 'BlueCurveLimits', + Format => 'int16u[4]', + }, + # 0x207 same as 0x159 + 0x20e => { + Name => 'RGBCurvePoints', + Format => 'int16u[21]', + PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', + PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', + }, + 0x238 => { + Name => 'RGBCurveLimits', + Format => 'int16u[4]', + }, + # 0x241 same as 0x159 + 0x244 => { + Name => 'CropActive', + Format => 'int16u', + %noYes, + }, + 0x246 => { + Name => 'CropLeft', + Notes => 'crop coordinates in original unrotated image', + Format => 'int16u', + }, + 0x248 => { + Name => 'CropTop', + Format => 'int16u', + }, + 0x24a => { + Name => 'CropWidth', + Format => 'int16u', + }, + 0x24c => { + Name => 'CropHeight', + Format => 'int16u', + }, + 0x25a => { + Name => 'SharpnessAdj', + Format => 'int16u', + }, + 0x260 => { + Name => 'CropAspectRatio', + Format => 'int16u', + PrintConv => { + 0 => 'Free', + 1 => '3:2', + 2 => '2:3', + 3 => '4:3', + 4 => '3:4', + 5 => 'A-size Landscape', + 6 => 'A-size Portrait', + 7 => 'Letter-size Landscape', + 8 => 'Letter-size Portrait', + 9 => '4:5', + 10 => '5:4', + 11 => '1:1', + 12 => 'Circle', + 65535 => 'Custom', + }, + }, + 0x262 => { + Name => 'ConstrainedCropWidth', + Format => 'float', + PrintConv => 'sprintf("%.7g",$val)', + PrintConvInv => '$val', + }, + 0x266 => { + Name => 'ConstrainedCropHeight', + Format => 'float', + PrintConv => 'sprintf("%.7g",$val)', + PrintConvInv => '$val', + }, + 0x26a => { + Name => 'CheckMark', + Format => 'int16u', + PrintConv => { + 0 => 'Clear', + 1 => 1, + 2 => 2, + 3 => 3, + }, + }, + 0x26e => { + Name => 'Rotation', + Format => 'int16u', + PrintConv => { + 0 => 0, + 1 => 90, + 2 => 180, + 3 => 270, + }, + }, + 0x270 => { + Name => 'WorkColorSpace', + Format => 'int16u', + PrintConv => { + 0 => 'sRGB', + 1 => 'Adobe RGB', + 2 => 'Wide Gamut RGB', + 3 => 'Apple RGB', + 4 => 'ColorMatch RGB', + }, + }, + # (VRD 1.0.0 edit data ends here -- 0x272 bytes) +); + +# VRD Stamp Tool tags (ref PH) +%Image::ExifTool::CanonVRD::StampTool = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0x00 => { + Name => 'StampToolCount', + Format => 'int32u', + }, +); + +# VRD version 2 and 3 tags (ref PH) +%Image::ExifTool::CanonVRD::Ver2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + PERMANENT => 1, # (can't add/delete these individually) + FIRST_ENTRY => 0, + FORMAT => 'int16s', + DATAMEMBER => [ 0x58, 0xdc, 0xdf, 0xe0 ], # (required for DataMember and var-format tags) + IS_SUBDIR => [ 0xe0 ], + GROUPS => { 2 => 'Image' }, + NOTES => 'Tags added in DPP version 2.0 and later.', + 0x02 => { + Name => 'PictureStyle', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Standard', + 1 => 'Portrait', + 2 => 'Landscape', + 3 => 'Neutral', + 4 => 'Faithful', + 5 => 'Monochrome', + 6 => 'Unknown?', # PH (maybe in-camera custom picture style?) + 7 => 'Custom', + }, + }, + 0x03 => { Name => 'IsCustomPictureStyle', %noYes }, + # 0x08: 3 + # 0x09: 4095 + # 0x0a: 0 + # 0x0b: 4095 + # 0x0c: 0 + 0x0d => 'StandardRawColorTone', + 0x0e => 'StandardRawSaturation', + 0x0f => 'StandardRawContrast', + 0x10 => { Name => 'StandardRawLinear', %noYes }, + 0x11 => 'StandardRawSharpness', + 0x12 => 'StandardRawHighlightPoint', + 0x13 => 'StandardRawShadowPoint', + 0x14 => 'StandardOutputHighlightPoint', #2 + 0x15 => 'StandardOutputShadowPoint', #2 + 0x16 => 'PortraitRawColorTone', + 0x17 => 'PortraitRawSaturation', + 0x18 => 'PortraitRawContrast', + 0x19 => { Name => 'PortraitRawLinear', %noYes }, + 0x1a => 'PortraitRawSharpness', + 0x1b => 'PortraitRawHighlightPoint', + 0x1c => 'PortraitRawShadowPoint', + 0x1d => 'PortraitOutputHighlightPoint', + 0x1e => 'PortraitOutputShadowPoint', + 0x1f => 'LandscapeRawColorTone', + 0x20 => 'LandscapeRawSaturation', + 0x21 => 'LandscapeRawContrast', + 0x22 => { Name => 'LandscapeRawLinear', %noYes }, + 0x23 => 'LandscapeRawSharpness', + 0x24 => 'LandscapeRawHighlightPoint', + 0x25 => 'LandscapeRawShadowPoint', + 0x26 => 'LandscapeOutputHighlightPoint', + 0x27 => 'LandscapeOutputShadowPoint', + 0x28 => 'NeutralRawColorTone', + 0x29 => 'NeutralRawSaturation', + 0x2a => 'NeutralRawContrast', + 0x2b => { Name => 'NeutralRawLinear', %noYes }, + 0x2c => 'NeutralRawSharpness', + 0x2d => 'NeutralRawHighlightPoint', + 0x2e => 'NeutralRawShadowPoint', + 0x2f => 'NeutralOutputHighlightPoint', + 0x30 => 'NeutralOutputShadowPoint', + 0x31 => 'FaithfulRawColorTone', + 0x32 => 'FaithfulRawSaturation', + 0x33 => 'FaithfulRawContrast', + 0x34 => { Name => 'FaithfulRawLinear', %noYes }, + 0x35 => 'FaithfulRawSharpness', + 0x36 => 'FaithfulRawHighlightPoint', + 0x37 => 'FaithfulRawShadowPoint', + 0x38 => 'FaithfulOutputHighlightPoint', + 0x39 => 'FaithfulOutputShadowPoint', + 0x3a => { + Name => 'MonochromeFilterEffect', + PrintConv => { + -2 => 'None', + -1 => 'Yellow', + 0 => 'Orange', + 1 => 'Red', + 2 => 'Green', + }, + }, + 0x3b => { + Name => 'MonochromeToningEffect', + PrintConv => { + -2 => 'None', + -1 => 'Sepia', + 0 => 'Blue', + 1 => 'Purple', + 2 => 'Green', + }, + }, + 0x3c => 'MonochromeContrast', + 0x3d => { Name => 'MonochromeLinear', %noYes }, + 0x3e => 'MonochromeSharpness', + 0x3f => 'MonochromeRawHighlightPoint', + 0x40 => 'MonochromeRawShadowPoint', + 0x41 => 'MonochromeOutputHighlightPoint', + 0x42 => 'MonochromeOutputShadowPoint', + 0x45 => { Name => 'UnknownContrast', Unknown => 1 }, + 0x46 => { Name => 'UnknownLinear', %noYes, Unknown => 1 }, + 0x47 => { Name => 'UnknownSharpness', Unknown => 1 }, + 0x48 => { Name => 'UnknownRawHighlightPoint', Unknown => 1 }, + 0x49 => { Name => 'UnknownRawShadowPoint', Unknown => 1 }, + 0x4a => { Name => 'UnknownOutputHighlightPoint',Unknown => 1 }, + 0x4b => { Name => 'UnknownOutputShadowPoint', Unknown => 1 }, + 0x4c => 'CustomColorTone', + 0x4d => 'CustomSaturation', + 0x4e => 'CustomContrast', + 0x4f => { Name => 'CustomLinear', %noYes }, + 0x50 => 'CustomSharpness', + 0x51 => 'CustomRawHighlightPoint', + 0x52 => 'CustomRawShadowPoint', + 0x53 => 'CustomOutputHighlightPoint', + 0x54 => 'CustomOutputShadowPoint', + 0x58 => { + Name => 'CustomPictureStyleData', + Format => 'var_int16u', + Binary => 1, + Notes => 'variable-length data structure', + Writable => 0, + RawConv => 'length($val) == 2 ? undef : $val', # ignore if no data + }, + # (VRD 2.0.0 edit data ends here: 178 bytes, index 0x59) + 0x5e => [{ + Name => 'ChrominanceNoiseReduction', + Condition => '$$self{VRDVersion} < 330', + Notes => 'VRDVersion prior to 3.3.0', + PrintConv => { + 0 => 'Off', + 58 => 'Low', + 100 => 'High', + }, + },{ #1 + Name => 'ChrominanceNoiseReduction', + Notes => 'VRDVersion 3.3.0 or later', + PrintHex => 1, + PrintConvColumns => 4, + PrintConv => { + 0x00 => 0, + 0x10 => 1, + 0x21 => 2, + 0x32 => 3, + 0x42 => 4, + 0x53 => 5, + 0x64 => 6, + 0x74 => 7, + 0x85 => 8, + 0x96 => 9, + 0xa6 => 10, + 0xa7 => 11, + 0xa8 => 12, + 0xa9 => 13, + 0xaa => 14, + 0xab => 15, + 0xac => 16, + 0xad => 17, + 0xae => 18, + 0xaf => 19, + 0xb0 => 20, + }, + }], + 0x5f => [{ + Name => 'LuminanceNoiseReduction', + Condition => '$$self{VRDVersion} < 330', + Notes => 'VRDVersion prior to 3.3.0', + PrintConv => { + 0 => 'Off', + 65 => 'Low', + 100 => 'High', + }, + },{ #1 + Name => 'LuminanceNoiseReduction', + Notes => 'VRDVersion 3.3.0 or later', + PrintHex => 1, + PrintConvColumns => 4, + PrintConv => { + 0x00 => 0, + 0x41 => 1, + 0x64 => 2, + 0x6e => 3, + 0x78 => 4, + 0x82 => 5, + 0x8c => 6, + 0x96 => 7, + 0xa0 => 8, + 0xaa => 9, + 0xb4 => 10, + 0xb5 => 11, + 0xb6 => 12, + 0xb7 => 13, + 0xb8 => 14, + 0xb9 => 15, + 0xba => 16, + 0xbb => 17, + 0xbc => 18, + 0xbd => 19, + 0xbe => 20, + }, + }], + 0x60 => [{ + Name => 'ChrominanceNR_TIFF_JPEG', + Condition => '$$self{VRDVersion} < 330', + Notes => 'VRDVersion prior to 3.3.0', + PrintConv => { + 0 => 'Off', + 33 => 'Low', + 100 => 'High', + }, + },{ #1 + Name => 'ChrominanceNR_TIFF_JPEG', + Notes => 'VRDVersion 3.3.0 or later', + PrintHex => 1, + PrintConvColumns => 4, + PrintConv => { + 0x00 => 0, + 0x10 => 1, + 0x21 => 2, + 0x32 => 3, + 0x42 => 4, + 0x53 => 5, + 0x64 => 6, + 0x74 => 7, + 0x85 => 8, + 0x96 => 9, + 0xa6 => 10, + 0xa7 => 11, + 0xa8 => 12, + 0xa9 => 13, + 0xaa => 14, + 0xab => 15, + 0xac => 16, + 0xad => 17, + 0xae => 18, + 0xaf => 19, + 0xb0 => 20, + }, + }], + # 0x61: 1 + # (VRD 3.0.0 edit data ends here: 196 bytes, index 0x62) + 0x62 => { Name => 'ChromaticAberrationOn', %noYes }, + 0x63 => { Name => 'DistortionCorrectionOn', %noYes }, + 0x64 => { Name => 'PeripheralIlluminationOn', %noYes }, + 0x65 => { Name => 'ColorBlur', %noYes }, + 0x66 => { + Name => 'ChromaticAberration', + ValueConv => '$val / 0x400', + ValueConvInv => 'int($val * 0x400 + 0.5)', + PrintConv => 'sprintf("%.0f%%", $val * 100)', + PrintConvInv => 'ToFloat($val) / 100', + }, + 0x67 => { + Name => 'DistortionCorrection', + ValueConv => '$val / 0x400', + ValueConvInv => 'int($val * 0x400 + 0.5)', + PrintConv => 'sprintf("%.0f%%", $val * 100)', + PrintConvInv => 'ToFloat($val) / 100', + }, + 0x68 => { + Name => 'PeripheralIllumination', + ValueConv => '$val / 0x400', + ValueConvInv => 'int($val * 0x400 + 0.5)', + PrintConv => 'sprintf("%.0f%%", $val * 100)', + PrintConvInv => 'ToFloat($val) / 100', + }, + 0x69 => { + Name => 'AberrationCorrectionDistance', + Notes => '100% = infinity', + RawConv => '$val == 0x7fff ? undef : $val', + ValueConv => '1 - $val / 0x400', + ValueConvInv => 'int((1 - $val) * 0x400 + 0.5)', + PrintConv => 'sprintf("%.0f%%", $val * 100)', + PrintConvInv => 'ToFloat($val) / 100', + }, + 0x6a => 'ChromaticAberrationRed', + 0x6b => 'ChromaticAberrationBlue', + 0x6d => { #1 + Name => 'LuminanceNR_TIFF_JPEG', + Notes => 'val = raw / 10', + ValueConv => '$val / 10', + ValueConvInv => 'int($val * 10 + 0.5)', + }, + # (VRD 3.4.0 edit data ends here: 220 bytes, index 0x6e) + 0x6e => { Name => 'AutoLightingOptimizerOn', %noYes }, + 0x6f => { + Name => 'AutoLightingOptimizer', + PrintConv => { + 100 => 'Low', + 200 => 'Standard', + 300 => 'Strong', + 0x7fff => 'n/a', #1 + }, + }, + # 0x71: 200 + # 0x73: 100 + # (VRD 3.5.0 edit data ends here: 232 bytes, index 0x74) + 0x75 => { + Name => 'StandardRawHighlight', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x76 => { + Name => 'PortraitRawHighlight', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x77 => { + Name => 'LandscapeRawHighlight', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x78 => { + Name => 'NeutralRawHighlight', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x79 => { + Name => 'FaithfulRawHighlight', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x7a => { + Name => 'MonochromeRawHighlight', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x7b => { + Name => 'UnknownRawHighlight', + Unknown => 1, + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x7c => { + Name => 'CustomRawHighlight', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x7e => { + Name => 'StandardRawShadow', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x7f => { + Name => 'PortraitRawShadow', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x80 => { + Name => 'LandscapeRawShadow', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x81 => { + Name => 'NeutralRawShadow', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x82 => { + Name => 'FaithfulRawShadow', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x83 => { + Name => 'MonochromeRawShadow', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x84 => { + Name => 'UnknownRawShadow', + Unknown => 1, + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x85 => { + Name => 'CustomRawShadow', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x8b => { #2 + Name => 'AngleAdj', + Format => 'int32s', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0x8e => { + Name => 'CheckMark2', + Format => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Clear', + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + }, + }, + # (VRD 3.8.0 edit data ends here: 286 bytes, index 0x8f) + 0x90 => { + Name => 'UnsharpMask', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x92 => 'StandardUnsharpMaskStrength', + 0x94 => 'StandardUnsharpMaskFineness', + 0x96 => 'StandardUnsharpMaskThreshold', + 0x98 => 'PortraitUnsharpMaskStrength', + 0x9a => 'PortraitUnsharpMaskFineness', + 0x9c => 'PortraitUnsharpMaskThreshold', + 0x9e => 'LandscapeUnsharpMaskStrength', + 0xa0 => 'LandscapeUnsharpMaskFineness', + 0xa2 => 'LandscapeUnsharpMaskThreshold', + 0xa4 => 'NeutraUnsharpMaskStrength', + 0xa6 => 'NeutralUnsharpMaskFineness', + 0xa8 => 'NeutralUnsharpMaskThreshold', + 0xaa => 'FaithfulUnsharpMaskStrength', + 0xac => 'FaithfulUnsharpMaskFineness', + 0xae => 'FaithfulUnsharpMaskThreshold', + 0xb0 => 'MonochromeUnsharpMaskStrength', + 0xb2 => 'MonochromeUnsharpMaskFineness', + 0xb4 => 'MonochromeUnsharpMaskThreshold', + 0xb6 => 'CustomUnsharpMaskStrength', + 0xb8 => 'CustomUnsharpMaskFineness', + 0xba => 'CustomUnsharpMaskThreshold', + 0xbc => 'CustomDefaultUnsharpStrength', + 0xbe => 'CustomDefaultUnsharpFineness', + 0xc0 => 'CustomDefaultUnsharpThreshold', + # (VRD 3.9.1 edit data ends here: 392 bytes, index 0xc4) + # 0xc9: 3 - some RawSharpness + # 0xca: 4095 - some RawHighlightPoint + # 0xcb: 0 - some RawShadowPoint + # 0xcc: 4095 - some OutputHighlightPoint + # 0xcd: 0 - some OutputShadowPoint + # 0xd1: 3 - some UnsharpMaskStrength + # 0xd3: 7 - some UnsharpMaskFineness + # 0xd5: 3,4 - some UnsharpMaskThreshold + 0xd6 => { Name => 'CropCircleActive', %noYes }, + 0xd7 => 'CropCircleX', + 0xd8 => 'CropCircleY', + 0xd9 => 'CropCircleRadius', + # 0xda: 0, 1 + # 0xdb: 100 + 0xdc => { + Name => 'DLOOn', + DataMember => 'DLOOn', + RawConv => '$$self{DLOOn} = $val', + %noYes, + }, + 0xdd => 'DLOSetting', + # (VRD 3.11.0 edit data ends here: 444 bytes, index 0xde) + 0xde => { + Name => 'DLOShootingDistance', + Notes => '100% = infinity', + RawConv => '$val == 0x7fff ? undef : $val', + ValueConv => '1 - $val / 0x400', + ValueConvInv => 'int((1 - $val) * 0x400 + 0.5)', + PrintConv => 'sprintf("%.0f%%", $val * 100)', + PrintConvInv => 'ToFloat($val) / 100', + }, + 0xdf => { + Name => 'DLODataLength', + DataMember => 'DLODataLength', + Format => 'int32u', + Writable => 0, + RawConv => '$$self{DLODataLength} = $val', + }, + 0xe0 => { # (yes, this overlaps DLODataLength) + Name => 'DLOInfo', + # - have seen DLODataLengths of 65536,64869 when DLO is Off, so must test DLOOn flag + Condition => '$$self{DLOOn}', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::DLOInfo' }, + Hook => '$varSize += $$self{DLODataLength} + 0x16', + }, + 0xe1 => 'CameraRawColorTone', + # (VRD 3.11.2 edit data ends here: 452 bytes, index 0xe2, unless DLO is on) + 0xe2 => 'CameraRawSaturation', + 0xe3 => 'CameraRawContrast', + 0xe4 => { Name => 'CameraRawLinear', %noYes }, + 0xe5 => 'CameraRawSharpness', + 0xe6 => 'CameraRawHighlightPoint', + 0xe7 => 'CameraRawShadowPoint', + 0xe8 => 'CameraRawOutputHighlightPoint', + 0xe9 => 'CameraRawOutputShadowPoint', +); + +# DLO tags (ref PH) +%Image::ExifTool::CanonVRD::DLOInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 1, + FORMAT => 'int16s', + GROUPS => { 2 => 'Image' }, + NOTES => 'Tags added when DLO (Digital Lens Optimizer) is on.', + # 0x01 - seen 3112,3140 + 0x04 => 'DLOSettingApplied', + 0x05 => { + Name => 'DLOVersion', #(NC) + Format => 'string[10]', + }, + 0x0a => { + Name => 'DLOData', + LargeTag => 1, # large tag, so avoid storing unnecessarily + Notes => 'variable-length Digital Lens Optimizer data, stored in JPEG-like format', + Format => 'undef[$$self{DLODataLength}]', + Writable => 0, + Binary => 1, + }, +); + +# VRD version 4 tags (ref PH) +%Image::ExifTool::CanonVRD::DR4 = ( + PROCESS_PROC => \&ProcessDR4, + WRITE_PROC => \&ProcessDR4, + WRITABLE => 1, + PERMANENT => 1, # (can't add/delete these individually) + GROUPS => { 1 => 'CanonDR4', 2 => 'Image' }, + VARS => { HEX_ID => 1, SORT_PROC => \&SortDR4 }, + NOTES => q{ + Tags written by Canon DPP version 4 in CanonVRD trailers and DR4 files. Each + tag has three associated flag words which are stored with the directory + entry, some of which are extracted as a separate tag, indicated in the table + below by a decimal appended to the tag ID (.0, .1 or .2). + }, + header => { + Name => 'DR4Header', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::DR4Header' }, + }, + 0x10002 => 'Rotation', # left/right rotation 90,180,270 + 0x10003 => 'AngleAdj', # crop angle + # 0x10018 - fmt=8: 0 + # 0x10020 - fmt=2: '' + 0x10021 => 'CustomPictureStyle', # (string) + 0x10101 => { + Name => 'CheckMark', + PrintConv => { + 0 => 'Clear', + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + }, + }, + 0x10200 => { + Name => 'WorkColorSpace', + PrintConv => { + 1 => 'sRGB', + 2 => 'Adobe RGB', + 3 => 'Wide Gamut RGB', + 4 => 'Apple RGB', + 5 => 'ColorMatch RGB', + }, + }, + # 0x10201 - fmt=9: 0 + # 0x10f20 - fmt=9: 350 + 0x20001 => 'RawBrightnessAdj', + 0x20101 => { + Name => 'WhiteBalanceAdj', + PrintConvColumns => 2, + PrintConv => { + -1 => 'Manual (Click)', + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Cloudy', + 3 => 'Tungsten', + 4 => 'Fluorescent', + 5 => 'Flash', + 8 => 'Shade', + 9 => 'Kelvin', + 255 => 'Shot Settings', + }, + }, + 0x20102 => 'WBAdjColorTemp', + 0x20105 => 'WBAdjMagentaGreen', + 0x20106 => 'WBAdjBlueAmber', + 0x20125 => { + Name => 'WBAdjRGGBLevels', + PrintConv => '$val =~ s/^\d+ //; $val', # remove first integer (14: what is this for?) + PrintConvInv => '"14 $val"', + }, + 0x20200 => { Name => 'GammaLinear', %noYes }, + 0x20301 => { + Name => 'PictureStyle', + PrintHex => 1, + PrintConv => { + 0x81 => 'Standard', + 0x82 => 'Portrait', + 0x83 => 'Landscape', + 0x84 => 'Neutral', + 0x85 => 'Faithful', + 0x86 => 'Monochrome', + 0x87 => 'Auto', + 0x88 => 'Fine Detail', + 0xf0 => 'Shot Settings', + 0xff => 'Custom', + }, + }, + # 0x20302 - Gamma curve data + 0x20303 => 'ContrastAdj', + 0x20304 => 'ColorToneAdj', + 0x20305 => 'ColorSaturationAdj', + 0x20306 => { + Name => 'MonochromeToningEffect', + PrintConv => { + 0 => 'None', + 1 => 'Sepia', + 2 => 'Blue', + 3 => 'Purple', + 4 => 'Green', + }, + }, + 0x20307 => { + Name => 'MonochromeFilterEffect', + PrintConv => { + 0 => 'None', + 1 => 'Yellow', + 2 => 'Orange', + 3 => 'Red', + 4 => 'Green', + }, + }, + 0x20308 => 'UnsharpMaskStrength', + 0x20309 => 'UnsharpMaskFineness', + 0x2030a => 'UnsharpMaskThreshold', + 0x2030b => 'ShadowAdj', + 0x2030c => 'HighlightAdj', + 0x20310 => { + Name => 'SharpnessAdj', + PrintConv => { + 0 => 'Sharpness', + 1 => 'Unsharp Mask', + }, + }, + '0x20310.0' => { Name => 'SharpnessAdjOn', %noYes }, + 0x20311 => 'SharpnessStrength', + 0x20400 => { + Name => 'ToneCurve', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::ToneCurve' }, + }, + '0x20400.1' => { Name => 'ToneCurveOriginal', %noYes }, + # 0x20401 - fmt=33 (312 bytes) + 0x20410 => 'ToneCurveBrightness', + 0x20411 => 'ToneCurveContrast', + 0x20500 => { + Name => 'AutoLightingOptimizer', + PrintConv => { + 0 => 'Low', + 1 => 'Standard', + 2 => 'Strong', + }, + }, + '0x20500.0' => { + Name => 'AutoLightingOptimizerOn', + Notes => 'ignored if gamma is linear', + %noYes, + }, + # 0x20501 - fmt=13: 0 + # 0x20502 - fmt=13: 0 + 0x20600 => 'LuminanceNoiseReduction', + 0x20601 => 'ChrominanceNoiseReduction', + # 0x20650 - fmt=9: 0 (JPG images) + 0x20670 => 'ColorMoireReduction', + '0x20670.0' => { Name => 'ColorMoireReductionOn', %noYes }, + 0x20701 => { + Name => 'ShootingDistance', + Notes => '100% = infinity', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.0f%%", $val * 100)', + PrintConvInv => 'ToFloat($val) / 100', + }, + 0x20702 => { + Name => 'PeripheralIllumination', + PrintConv => 'sprintf "%g", $val', + PrintConvInv => '$val', + }, + '0x20702.0' => { Name => 'PeripheralIlluminationOn', %noYes }, + 0x20703 => { + Name => 'ChromaticAberration', + PrintConv => 'sprintf "%g", $val', + PrintConvInv => '$val', + }, + '0x20703.0' => { Name => 'ChromaticAberrationOn', %noYes }, + 0x20704 => { Name => 'ColorBlurOn', %noYes }, + 0x20705 => { + Name => 'DistortionCorrection', + PrintConv => 'sprintf "%g", $val', + PrintConvInv => '$val', + }, + '0x20705.0' => { Name => 'DistortionCorrectionOn', %noYes }, + 0x20706 => 'DLOSetting', + '0x20706.0' => { Name => 'DLOOn', %noYes }, + 0x20707 => { + Name => 'ChromaticAberrationRed', + PrintConv => 'sprintf "%g", $val', + PrintConvInv => '$val', + }, + 0x20708 => { + Name => 'ChromaticAberrationBlue', + PrintConv => 'sprintf "%g", $val', + PrintConvInv => '$val', + }, + 0x20709 => { + Name => 'DistortionEffect', + PrintConv => { + 0 => 'Shot Settings', + 1 => 'Emphasize Linearity', + 2 => 'Emphasize Distance', + 3 => 'Emphasize Periphery', + 4 => 'Emphasize Center', + }, + }, + # 0x20800 - fmt=1: 0 + # 0x20801 - fmt=1: 0 + 0x2070b => { Name => 'DiffractionCorrectionOn', %noYes }, + 0x20900 => 'ColorHue', + 0x20901 => 'SaturationAdj', + 0x20910 => 'RedHSL', + 0x20911 => 'OrangeHSL', + 0x20912 => 'YellowHSL', + 0x20913 => 'GreenHSL', + 0x20914 => 'AquaHSL', + 0x20915 => 'BlueHSL', + 0x20916 => 'PurpleHSL', + 0x20917 => 'MagentaHSL', + 0x20a00 => { + Name => 'GammaInfo', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::GammaInfo' }, + }, + # 0x20a01 - Auto picture style settings + # 0x20a02 - Standard picture style settings + # 0x20a03 - Portrait picture style settings + # 0x20a04 - Landscape picture style settings + # 0x20a05 - Neutral picture style settings + # 0x20a06 - Faithful picture style settings + # 0x20a07 - Monochrome picture style settings + # 0x20a08 - (unknown picture style settings) + # 0x20a09 - Custom picture style settings + # 0x20a20 - Fine Detail picture style settings + 0x30101 => { + Name => 'CropAspectRatio', + PrintConv => { + 0 => 'Free', + 1 => 'Custom', + 2 => '1:1', + 3 => '3:2', + 4 => '2:3', + 5 => '4:3', + 6 => '3:4', + 7 => '5:4', + 8 => '4:5', + 9 => '16:9', + 10 => '9:16', + }, + }, + 0x30102 => 'CropAspectRatioCustom', + # 0x30103 - fmt=33: "0 0 8" + 0xf0100 => { + Name => 'CropInfo', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::CropInfo' }, + }, + 0xf0500 => { + Name => 'CustomPictureStyleData', + Binary => 1, + }, + 0xf0510 => { + Name => 'StampInfo', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::StampInfo' }, + }, + 0xf0511 => { + Name => 'DustInfo', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::DustInfo' }, + }, + 0xf0512 => 'LensFocalLength', + # 0xf0521 - DLO data + # 0xf0520 - DLO data + # 0xf0530 - created when dust delete data applied (4 bytes, all zero) + # 0xf0600 - fmt=253 (2308 bytes, JPG images) + # 0xf0601 - fmt=253 (2308 bytes, JPG images) + # 0x1ff52c - values: 129,130,132 (related to custom picture style somehow) + # to do: + # - find 8-15mm CR2 sample and decode linear distortion effect fine-tune +); + +# Version 4 header information (ref PH) +%Image::ExifTool::CanonVRD::DR4Header = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + FORMAT => 'int32u', + GROUPS => { 1 => 'CanonDR4', 2 => 'Image' }, + # 0 - value: 'IIII' (presumably byte order) + # 1 - value: 0x00040004 (currently use this for magic number) + # 2 - value: 6 + 3 => { + Name => 'DR4CameraModel', + Writable => 'int32u', + PrintHex => 1, + SeparateTable => 'Canon CanonModelID', + PrintConv => \%Image::ExifTool::Canon::canonModelID, + }, + # 4 - value: 3 + # 5 - value: 4 + # 6 - value: 5 + # 7 - DR4 directory entry count +); + +# Version 4 RGB tone curve information (ref PH) +%Image::ExifTool::CanonVRD::ToneCurve = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + FORMAT => 'int32u', + GROUPS => { 1 => 'CanonDR4', 2 => 'Image' }, + 0x00 => { + Name => 'ToneCurveColorSpace', + PrintConv => { + 0 => 'RGB', + 1 => 'Luminance', + }, + }, + 0x01 => { + Name => 'ToneCurveShape', + PrintConv => { + 0 => 'Curve', + 1 => 'Straight', + }, + }, + 0x03 => { Name => 'ToneCurveInputRange', Format => 'int32u[2]', Notes => '255 max' }, + 0x05 => { Name => 'ToneCurveOutputRange', Format => 'int32u[2]', Notes => '255 max' }, + 0x07 => { + Name => 'RGBCurvePoints', + Format => 'int32u[21]', + PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', + PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', + }, + 0x0a => 'ToneCurveX', + 0x0b => 'ToneCurveY', + 0x2d => { + Name => 'RedCurvePoints', + Format => 'int32u[21]', + PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', + PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', + }, + 0x53 => { + Name => 'GreenCurvePoints', + Format => 'int32u[21]', + PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', + PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', + }, + 0x79 => { + Name => 'BlueCurvePoints', + Format => 'int32u[21]', + PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', + PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', + }, +); + +# Version 4 gamma curve information (ref PH) +%Image::ExifTool::CanonVRD::GammaInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + FORMAT => 'double', + GROUPS => { 1 => 'CanonDR4', 2 => 'Image' }, + 0x02 => 'GammaContrast', + 0x03 => 'GammaColorTone', + 0x04 => 'GammaSaturation', + 0x05 => 'GammaUnsharpMaskStrength', + 0x06 => 'GammaUnsharpMaskFineness', + 0x07 => 'GammaUnsharpMaskThreshold', + 0x08 => 'GammaSharpnessStrength', + 0x09 => 'GammaShadow', + 0x0a => 'GammaHighlight', + # 0x0b-0x10 are the same as first 6 doubles of tag DR4_0x20302 + # 0x0b - value: 14 + 0x0c => { + Name => 'GammaBlackPoint', + ValueConv => q{ + return 0 if $val <= 0; + $val = log($val / 4.6875) / log(2) + 1; + return abs($val) > 1e-10 ? $val : 0; + }, + ValueConvInv => '$val ? exp(($val - 1) * log(2)) * 4.6876 : 0', + PrintConv => 'sprintf("%+.3f", $val)', + PrintConvInv => '$val', + }, + 0x0d => { + Name => 'GammaWhitePoint', + ValueConv => q{ + return $val if $val <= 0; + $val = log($val / 4.6875) / log(2) - 11.77109325169954; + return abs($val) > 1e-10 ? $val : 0; + }, + ValueConvInv => '$val ? exp(($val + 11.77109325169954) * log(2)) * 4.6875 : 0', + PrintConv => 'sprintf("%+.3f", $val)', + PrintConvInv => '$val', + }, + 0x0e => { + Name => 'GammaMidPoint', + ValueConv => q{ + return $val if $val <= 0; + $val = log($val / 4.6875) / log(2) - 8; + return abs($val) > 1e-10 ? $val : 0; + }, + ValueConvInv => '$val ? exp(($val + 8) * log(2)) * 4.6876 : 0', + PrintConv => 'sprintf("%+.3f", $val)', + PrintConvInv => '$val', + }, + 0x0f => { Name => 'GammaCurveOutputRange', Format => 'double[2]', Notes => '16383 max' }, +); + +# Version 4 crop information (ref PH) +%Image::ExifTool::CanonVRD::CropInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + FORMAT => 'int32s', + GROUPS => { 1 => 'CanonDR4', 2 => 'Image' }, + 0 => { Name => 'CropActive', %noYes }, + 1 => 'CropRotatedOriginalWidth', + 2 => 'CropRotatedOriginalHeight', + 3 => 'CropX', + 4 => 'CropY', + 5 => 'CropWidth', + 6 => 'CropHeight', + 8 => { + Name => 'CropRotation', + Format => 'double', + PrintConv => 'sprintf("%.7g",$val)', + PrintConvInv => '$val', + }, + 0x0a => 'CropOriginalWidth', + 0x0b => 'CropOriginalHeight', + # 0x0c double - value: 100 +); + +# DR4 Stamp Tool tags (ref PH) +%Image::ExifTool::CanonVRD::StampInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 1 => 'CanonDR4', 2 => 'Image' }, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + 0x02 => 'StampToolCount', +); + +# DR4 dust delete information (ref PH) +%Image::ExifTool::CanonVRD::DustInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 1 => 'CanonDR4', 2 => 'Image' }, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + 0x02 => { Name => 'DustDeleteApplied', %noYes }, +); + +#------------------------------------------------------------------------------ +# sort DR4 tag ID's for the documentation +sub SortDR4($$) +{ + my ($a, $b) = @_; + my ($aHex, $aDec, $bHex, $bDec); + ($aHex, $aDec) = ($1, $2) if $a =~ /^(0x[0-9a-f]+)?\.?(\d*?)$/; + ($bHex, $bDec) = ($1, $2) if $b =~ /^(0x[0-9a-f]+)?\.?(\d*?)$/; + if ($aHex) { + return 1 unless defined $bDec; # $b is 'header'; + return hex($aHex) <=> hex($bHex) || $aDec <=> $bDec if $bHex; + return hex($aHex) <=> $bDec || 1; + } elsif ($bHex) { + return -1 unless defined $aDec; + return $aDec <=> hex($bHex) || -1; + } else { + return 1 unless defined $bDec; + return -1 unless defined $aDec; + return $aDec <=> $bDec; + } +} + +#------------------------------------------------------------------------------ +# Tone curve print conversion +sub ToneCurvePrint($) +{ + my $val = shift; + my @vals = split ' ', $val; + return $val unless @vals == 21; + my $n = shift @vals; + return $val unless $n >= 2 and $n <= 10; + $val = ''; + while ($n--) { + $val and $val .= ' '; + $val .= '(' . shift(@vals) . ',' . shift(@vals) . ')'; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Inverse print conversion for tone curve +sub ToneCurvePrintInv($) +{ + my $val = shift; + my @vals = ($val =~ /\((\d+),(\d+)\)/g); + return undef unless @vals >= 4 and @vals <= 20 and not @vals & 0x01; + unshift @vals, scalar(@vals) / 2; + while (@vals < 21) { push @vals, 0 } + return join(' ',@vals); +} + +#------------------------------------------------------------------------------ +# Read/Write VRD edit data +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: Reading: 1 on success; Writing: modified edit data, or undef if nothing changed +sub ProcessEditData($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $dataPos = $$dirInfo{DataPos}; + my $outfile = $$dirInfo{OutFile}; + my $dirLen = $$dirInfo{DirLen}; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my $oldChanged = $$et{CHANGED}; + + $et->VerboseDir('VRD Edit Data', 0, $dirLen) unless $outfile; + + if ($outfile) { + # make a copy for editing in place + my $buff = substr($$dataPt, $pos, $dirLen); + $dataPt = $$dirInfo{DataPt} = \$buff; + $dataPos += $pos; + $pos = $$dirInfo{DirStart} = 0; + } + my $dirEnd = $pos + $dirLen; + + # loop through all records in the edit data + my ($recNum, $recLen, $err); + for ($recNum=0;; ++$recNum, $pos+=$recLen) { + if ($pos + 4 > $dirEnd) { + last if $pos == $dirEnd; # all done if we arrived at end + $recLen = 0; # just reset record size (will exit loop on test below) + } else { + $recLen = Get32u($dataPt, $pos); + # (DR4 has a null terminator) + last if $recLen == 0 and $pos + 4 == $dirEnd; + } + $pos += 4; # move to start of record + if ($pos + $recLen > $dirEnd) { + $et->Warn('Possibly corrupt CanonVRD Edit record'); + $err = 1; + last; + } + my $saveRecLen = $recLen; + if ($verbose > 1 and not $outfile) { + printf $out "$$et{INDENT}CanonVRD Edit record ($recLen bytes at offset 0x%x)\n", + $pos + $dataPos; + $et->VerboseDump($dataPt, Len => $recLen, Start => $pos, Addr => $pos + $dataPos) if $recNum; + } + + # our edit information is the 0th record, so don't process the others + next if $recNum; + + # process VRD edit information + my $subTablePtr = $tagTablePtr; + my $index; + my %subdirInfo = ( + DataPt => $dataPt, + DataPos => $dataPos, + DirStart => $pos, + DirLen => $recLen, + OutFile => $outfile, + ); + my $subStart = 0; + # loop through various sections of the VRD edit data + for ($index=0; ; ++$index) { + my $tagInfo = $$subTablePtr{$index} or last; + my $subLen; + my $maxLen = $recLen - $subStart; + if ($$tagInfo{Size}) { + $subLen = $$tagInfo{Size}; + } elsif (defined $$tagInfo{Size}) { + # get size from int32u at $subStart + last unless $subStart + 4 <= $recLen; + $subLen = Get32u($dataPt, $subStart + $pos); + $subStart += 4; # skip the length word + } else { + $subLen = $maxLen; + } + $subLen > $maxLen and $subLen = $maxLen; + if ($subLen) { + my $subTable = GetTagTable($$tagInfo{SubDirectory}{TagTable}); + my $subName = $$tagInfo{Name}; + $subdirInfo{DirStart} = $subStart + $pos; + $subdirInfo{DirLen} = $subLen; + $subdirInfo{DirName} = $subName; + if ($outfile) { + # rewrite this section of the VRD edit information + $verbose and print $out " Rewriting Canon $subName\n"; + my $newVal = $et->WriteDirectory(\%subdirInfo, $subTable); + if ($newVal) { + my $sizeDiff = length($newVal) - $subLen; + substr($$dataPt, $pos+$subStart, $subLen) = $newVal; + if ($sizeDiff) { + $subLen = length $newVal; + $recLen += $sizeDiff; + $dirEnd += $sizeDiff; + $dirLen += $sizeDiff; + } + } + } else { + $et->VPrint(0, "$$et{INDENT}$subName (SubDirectory) -->\n"); + $et->VerboseDump($dataPt, + Start => $pos + $subStart, + Addr => $dataPos + $pos + $subStart, + Len => $subLen, + ); + # extract tags from this section of the VRD edit information + $et->ProcessDirectory(\%subdirInfo, $subTable); + } + } + # next section starts at the end of this one + $subStart += $subLen; + } + if ($outfile and $saveRecLen ne $recLen) { + # update record length if necessary + Set32u($recLen, $dataPt, $pos - 4) + } + } + if ($outfile) { + return undef if $oldChanged == $$et{CHANGED}; + return substr($$dataPt, $$dirInfo{DirStart}, $dirLen); + } + return $err ? 0 : 1; +} + +#------------------------------------------------------------------------------ +# Process VRD IHL data +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessIHL($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $pos = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + my $dirEnd = $pos + $dirLen; + + $et->VerboseDir('VRD IHL', 0, $dirLen); + + SetByteOrder('II'); # (make up your mind, Canon!) + while ($pos + 48 <= $dirEnd) { + my $hdr = substr($$dataPt, $pos, 48); + unless ($hdr =~ /^IHL Created Optional Item Data\0\0/) { + $et->Warn('Possibly corrupted VRD IHL data'); + last; + } + my $tag = Get32u($dataPt, $pos + 36); + my $size = Get32u($dataPt, $pos + 40); # size of data in IHL record + my $next = Get32u($dataPt, $pos + 44); # size of complete IHL record + if ($size > $next or $pos + 48 + $next > $dirEnd) { + $et->Warn(sprintf('Bad size for VRD IHL tag 0x%.4x', $tag)); + last; + } + $pos += 48; + $et->HandleTag($tagTablePtr, $tag, substr($$dataPt, $pos, $size), + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos, + Size => $size + ); + $pos += $next; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process VRD IHL EXIF data +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessIHLExif($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $$et{DOC_NUM} = 1; + # the IHL-edited maker notes may look messed up, but the offsets should be OK + my $oldFix = $et->Options(FixBase => 0); + my $rtnVal = $et->ProcessTIFF($dirInfo, $tagTablePtr); + $et->Options(FixBase => $oldFix); + delete $$et{DOC_NUM}; + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Wrap DR4 data with the VRD header/footer and edit record +# Inputs: 0) DR4 record +# Returns: VRD[Edit[DR4]] data +sub WrapDR4($) +{ + my $val = shift; + my $n = length $val; + my $oldOrder = GetByteOrder(); + SetByteOrder('MM'); + $val = $blankHeader . "\xff\xff\0\xf7" . Set32u($n+8) . Set32u($n) . + $val . "\0\0\0\0" . $blankFooter; + # update the new VRD length in the header/footer + Set32u($n + 16, \$val, 0x18); # (extra 16 bytes for the edit record wrapper) + Set32u($n + 16, \$val, length($val) - 0x2c); + SetByteOrder($oldOrder); + return $val; +} + +#------------------------------------------------------------------------------ +# Read/Write DPP version 4 edit data or DR4 file +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: +# Reading from memory (not RAF and not IsWriting): 1 on success +# Editing from memory (not RAF and IsWriting): modified edit data, or undef if nothing changed +# Reading file (RAF and not OutFile): 1 if a valid DR4 file, 0 if not +# Writing file (RAF and OutFile): 1 if valid DR4 file, 0 if not, -1 on write error +# (serves me right for not having a consistent interface for the various modes of operation) +sub ProcessDR4($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + my $dataPt = $$dirInfo{DataPt}; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my $isWriting = $outfile || $$dirInfo{IsWriting}; + my $dataPos = $$dirInfo{DataPos} || 0; + my $verbose = $et->Options('Verbose'); + my $unknown = $et->Options('Unknown'); + my ($pos, $dirLen, $numEntries, $err, $newTags); + + # write CanonDR4 as a block if specified + if ($isWriting) { + my $nvHash; + my $newVal = $et->GetNewValue('CanonDR4', \$nvHash); + if ($newVal) { + $et->VPrint(0, " Writing CanonDR4 as a block\n"); + $$et{DidCanonVRD} = 1; # set flag so we don't add this twice + ++$$et{CHANGED}; + if ($outfile) { + Write($$dirInfo{OutFile}, $newVal) or return -1; + return 1; + } else { + return $newVal; + } + } elsif (not $dataPt and ($nvHash or $$et{DEL_GROUP}{CanonVRD})) { + $et->Error("Can't delete all CanonDR4 information from a DR4 file"); + return 1; + } + } + if ($dataPt) { + $pos = $$dirInfo{DirStart} || 0; + $dirLen = $$dirInfo{DirLen} || length($$dataPt) - $pos; + } else { + # load DR4 file into memory + my $buff; + $raf->Read($buff, 8) == 8 and $buff eq "IIII\x04\0\x04\0" or return 0; + $et->SetFileType(); + $raf->Seek(0, 2) or return $err = 1; + $dirLen = $raf->Tell(); + $raf->Seek(0, 0) or return $err = 1; + $raf->Read($buff, $dirLen) == $dirLen or $err = 1; + $err and $et->Warn('Error reading DR4 file'), return 1; + $tagTablePtr = GetTagTable('Image::ExifTool::CanonVRD::DR4'); + $dataPt = \$buff; + $pos = 0; + } + my $dirEnd = $pos + $dirLen; + + if (($$et{TAGS_FROM_FILE} and + not $$et{EXCL_TAG_LOOKUP}{canondr4}) or $$et{REQ_TAG_LOOKUP}{canondr4}) + { + # extract CanonDR4 block if copying tags, or if requested + $et->FoundTag('CanonDR4', substr($$dataPt, $pos, $dirLen)); + } + + # version 4 header is 32 bytes (int32u[8]) + if ($dirLen < 32) { + $err = 1; + } else { + SetByteOrder(substr($$dataPt, $pos, 2)) or $err = 1; + # process the DR4 header + my %hdrInfo = ( + DataPt => $dataPt, + DirStart => $pos, + DirLen => 32, + DirName => 'DR4Header', + ); + my $hdrTable = GetTagTable('Image::ExifTool::CanonVRD::DR4Header'); + if ($outfile) { + my $hdr = $et->WriteDirectory(\%hdrInfo, $hdrTable); + substr($$dataPt, $pos, 32) = $hdr if $hdr and length $hdr == 32; + } else { + $et->VerboseDir('DR4Header', undef, 32); + $et->ProcessDirectory(\%hdrInfo, $hdrTable); + } + # number of entries in the DR4 directory + $numEntries = Get32u($dataPt, $pos + 28); + $err = 1 if $dirLen < 36 + 28 * $numEntries; + } + $err and $et->Warn('Invalid DR4 directory'), return $outfile ? undef : 0; + + if ($outfile) { + $newTags = $et->GetNewTagInfoHash($tagTablePtr); + } else { + $et->VerboseDir('DR4', $numEntries, $dirLen); + } + + my $index; + for ($index=0; $index<$numEntries; ++$index) { + my ($val, @flg, $i); + my $entry = $pos + 36 + 28 * $index; + last if $entry + 28 > $dirEnd; + my $tag = Get32u($dataPt, $entry); + my $fmt = Get32u($dataPt, $entry + 4); + $flg[0] = Get32u($dataPt, $entry + 8); + $flg[1] = Get32u($dataPt, $entry + 12); + $flg[2] = Get32u($dataPt, $entry + 16); + my $off = Get32u($dataPt, $entry + 20) + $pos; + my $len = Get32u($dataPt, $entry + 24); + next if $off + $len >= $dirEnd; + my $format = $vrdFormat{$fmt}; + if (not $format) { + $val = unpack 'H*', substr($$dataPt, $off, $len); + $format = 'undef'; + } elsif ($format eq 'double' and $len == 8) { + # avoid teeny weeny values + $val = ReadValue($dataPt, $off, $format, undef, $len); + $val = 0 if abs($val) < 1e-100; + } + if ($outfile) { + # write (binary data) subdirectory if it exists + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo and $$tagInfo{SubDirectory}) { + my %subdirInfo = ( + DataPt => $dataPt, + DirStart => $off, + DirLen => $len, + DirName => $$tagInfo{Name}, + ); + my $subTablePtr = GetTagTable($$tagInfo{SubDirectory}{TagTable}); + my $saveChanged = $$et{CHANGED}; + my $dat = $et->WriteDirectory(\%subdirInfo, $subTablePtr); + if (defined $dat and length($dat) == $len) { + substr($$dataPt, $off, $len) = $dat; + } else { + $$et{CHANGED} = $saveChanged; # didn't change anything after all + } + } else { + # loop through main tag and flags (don't yet consider flag 2) + for ($i=-1; $i<2; ++$i) { + $tagInfo = $$newTags{$i>=0 ? sprintf('0x%x.%d',$tag,$i) : $tag}; + next unless $tagInfo; + if ($i >= 0) { + $off = $entry + 8 + 4 * $i; + $format = 'int32u'; + $len = 4; + undef $val; + } + $val = ReadValue($dataPt, $off, $format, undef, $len) unless defined $val; + my $nvHash; + my $newVal = $et->GetNewValue($tagInfo, \$nvHash); + if ($et->IsOverwriting($nvHash, $val) and defined $newVal) { + my $count = int($len / Image::ExifTool::FormatSize($format)); + my $rtnVal = WriteValue($newVal, $format, $count, $dataPt, $off); + if (defined $rtnVal) { + $et->VerboseValue("- CanonVRD:$$tagInfo{Name}", $val); + $et->VerboseValue("+ CanonVRD:$$tagInfo{Name}", $newVal); + ++$$et{CHANGED}; + } + } + } + } + next; + } + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $off, + Size => $len, + Index => $index, + Format => $format, + # $flg[0] is on/off flag + # $flg[1] "is default" flag? + # $flg[2] changed to 0 when some unsharp mask settings were changed + Extra => ", fmt=$fmt flags=" . join(',', @flg), + ); + foreach $i (0..2) { + my $flagID = sprintf('0x%x.%d', $tag, $i); + $et->HandleTag($tagTablePtr, $flagID, $flg[$i]) if $$tagTablePtr{$flagID}; + } + } + return 1 unless $isWriting; + return substr($$dataPt, $pos, $dirLen) unless $raf; + return 1 if Write($outfile, substr($$dataPt, $pos, $dirLen)); + return -1; +} + +#------------------------------------------------------------------------------ +# Read/write Canon VRD file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 if this was a Canon VRD file, 0 otherwise, -1 on write error +sub ProcessVRD($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + my $num = $raf->Read($buff, 0x1c); + + # initialize write directories if necessary + $et->InitWriteDirs(\%vrdMap, 'XMP') if $$dirInfo{OutFile}; + + if (not $num and $$dirInfo{OutFile}) { + # create new VRD file from scratch + my $newVal = $et->GetNewValue('CanonVRD'); + if ($newVal) { + $et->VPrint(0, " Writing CanonVRD as a block\n"); + Write($$dirInfo{OutFile}, $newVal) or return -1; + $$et{DidCanonVRD} = 1; + ++$$et{CHANGED}; + } else { + # allow VRD to be created from individual tags + if ($$et{ADD_DIRS}{CanonVRD}) { + my $newVal = ''; + if (ProcessCanonVRD($et, { OutFile => \$newVal }) > 0) { + Write($$dirInfo{OutFile}, $newVal) or return -1; + ++$$et{CHANGED}; + return 1; + } + } + $et->Error('No CanonVRD information to write'); + } + } else { + $num == 0x1c or return 0; + $buff =~ /^CANON OPTIONAL DATA\0/ or return 0; + $et->SetFileType(); + $$dirInfo{DirName} = 'CanonVRD'; # set directory name for verbose output + my $result = ProcessCanonVRD($et, $dirInfo); + return $result if $result < 0; + $result or $et->Warn('Format error in VRD file'); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Write VRD data record as a block +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: VRD data block (may be empty if no VRD data) +# Notes: Increments ExifTool CHANGED flag if changed +sub WriteCanonVRD($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + my $nvHash = $et->GetNewValueHash($Image::ExifTool::Extra{CanonVRD}); + my $val = $et->GetNewValue($nvHash); + $val = '' unless defined $val; + return undef unless $et->IsOverwriting($nvHash, $val); + ++$$et{CHANGED}; + return $val; +} + +#------------------------------------------------------------------------------ +# Write DR4-type CanonVRD edit record +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: VRD data block (may be empty if deleted, of undef on error) +sub WriteCanonDR4($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + my $nvHash = $et->GetNewValueHash($Image::ExifTool::Extra{CanonDR4}); + my $val = $et->GetNewValue($nvHash); + if (defined $val) { + return undef unless $et->IsOverwriting($nvHash, $val); + $et->VPrint(0, " Writing CanonDR4 as a block\n"); + ++$$et{CHANGED}; + return WrapDR4($val); + } + my $buff = ''; + $$dirInfo{OutFile} = \$buff; + return $buff if ProcessCanonVRD($et, $dirInfo, $tagTablePtr) > 0; + return undef; +} + +#------------------------------------------------------------------------------ +# Read/write CanonVRD information (from VRD file or VRD trailer) +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 not valid VRD, or -1 error writing +# - updates DataPos to point to start of CanonVRD information +# - updates DirLen to existing trailer length +sub ProcessCanonVRD($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $offset = $$dirInfo{Offset} || 0; + my $outfile = $$dirInfo{OutFile}; + my $dataPt = $$dirInfo{DataPt}; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my ($buff, $created, $err, $blockLen, $blockType, %didDir, $fromFile); +# +# The CanonVRD trailer has a 0x1c-byte header and a 0x40-byte footer, +# each beginning with "CANON OPTIONAL DATA\0" and containing an int32u +# giving the size of the contained data (at byte 0x18 and 0x14 respectively) +# + if ($raf) { + $fromFile = 1; + } else { + unless ($dataPt) { + return 1 unless $outfile; + # create blank VRD data from scratch + my $blank = $blankHeader . $blankFooter; + $dataPt = \$blank; + $verbose and print $out " Creating CanonVRD trailer\n"; + $created = 1; + } + $raf = new File::RandomAccess($dataPt); + } + # read and validate the footer + $raf->Seek(-0x40-$offset, 2) or return 0; + $raf->Read($buff, 0x40) == 0x40 or return 0; + $buff =~ /^CANON OPTIONAL DATA\0(.{4})/s or return 0; + my $dirLen = unpack('N', $1) + 0x5c; # size including header+footer + + # read and validate the header + unless ($dirLen < 0x80000000 and + $raf->Seek(-$dirLen, 1) and + $raf->Read($buff, 0x1c) == 0x1c and + $buff =~ /^CANON OPTIONAL DATA\0/ and + $raf->Seek(-0x1c, 1)) + { + $et->Warn('Bad CanonVRD trailer'); + return 0; + } + # set variables returned in dirInfo hash + $$dirInfo{DataPos} = $raf->Tell(); + $$dirInfo{DirLen} = $dirLen; + + if ($outfile and ref $outfile eq 'SCALAR' and not length $$outfile) { + # write directly to outfile to avoid duplicating data in memory + $$outfile = $$dataPt unless $fromFile; + # TRICKY! -- copy to outfile memory buffer and edit in place + # (so we must disable all Write() calls for this case) + $dataPt = $outfile; + } + if ($fromFile or $$dirInfo{DirStart}) { + $dataPt = \$buff unless $dataPt; + # read VRD data into memory if necessary + unless ($raf->Read($$dataPt, $dirLen) == $dirLen) { + $$dataPt = '' if $outfile and $outfile eq $dataPt; + $et->Warn('Error reading CanonVRD data'); + return 0; + } + } + my $vrdType = 'VRD'; + + if ($outfile) { + $verbose and not $created and print $out " Rewriting CanonVRD trailer\n"; + # exit quickly if writing and no CanonVRD tags are being edited + unless (exists $$et{EDIT_DIRS}{CanonVRD}) { + print $out "$$et{INDENT} [nothing changed in CanonVRD]\n" if $verbose; + return 1 if $outfile eq $dataPt; + return Write($outfile, $$dataPt) ? 1 : -1; + } + # delete CanonVRD information if specified + my $doDel = $$et{DEL_GROUP}{CanonVRD}; + unless ($doDel) { + $doDel = 1 if $$et{DEL_GROUP}{Trailer} and $$et{FILE_TYPE} ne 'VRD'; + unless ($doDel) { + # also delete if writing as a block (will get added back again later) + if ($$et{NEW_VALUE}{$Image::ExifTool::Extra{CanonVRD}}) { + # delete if this isn't version 4 + $doDel = 1 unless $$dataPt =~ /^.{28}\xff\xff\0\xf7/s; + } + if ($$et{NEW_VALUE}{$Image::ExifTool::Extra{CanonDR4}} and not $doDel) { + # delete if this is version 4 + $doDel = 1 if $$dataPt =~ /^.{28}\xff\xff\0\xf7/s; + } + } + } + if ($doDel) { + if ($$et{FILE_TYPE} eq 'VRD') { + my $newVal = $et->GetNewValue('CanonVRD'); + if ($newVal) { + $verbose and print $out " Writing CanonVRD as a block\n"; + if ($outfile eq $dataPt) { + $$outfile = $newVal; + } else { + Write($outfile, $newVal) or return -1; + } + $$et{DidCanonVRD} = 1; + ++$$et{CHANGED}; + } else { + $et->Error("Can't delete all CanonVRD information from a VRD file"); + } + } else { + $verbose and print $out " Deleting CanonVRD trailer\n"; + $$outfile = '' if $outfile eq $dataPt; + ++$$et{CHANGED}; + } + return 1; + } + # write now and return if CanonVRD was set as a block + my $val = $et->GetNewValue('CanonVRD'); + unless ($val) { + $val = $et->GetNewValue('CanonDR4'); + $vrdType = 'DR4' if $val; + } + if ($val) { + $verbose and print $out " Writing Canon$vrdType as a block\n"; + # must wrap DR4 data with the VRD header/footer and edit record + $val = WrapDR4($val) if $vrdType eq 'DR4'; + if ($outfile eq $dataPt) { + $$outfile = $val; + } else { + Write($outfile, $val) or return -1; + } + $$et{DidCanonVRD} = 1; + ++$$et{CHANGED}; + return 1; + } + } elsif ($verbose or $$et{HTML_DUMP}) { + $et->DumpTrailer($dirInfo) if $$dirInfo{RAF}; + } + + $tagTablePtr = GetTagTable('Image::ExifTool::CanonVRD::Main'); + + # validate VRD trailer and get position and length of edit record + SetByteOrder('MM'); # VRD header/footer is big-endian + my $pos = 0x1c; # start at end of header + + # loop through the VRD blocks + for (;;) { + my $end = $dirLen - 0x40; # end of last VRD block (and start of footer) + if ($pos + 8 > $end) { + last if $pos == $end; + $blockLen = $end; # mark as invalid + } else { + $blockType = Get32u($dataPt, $pos); + $blockLen = Get32u($dataPt, $pos + 4); + } + $vrdType = 'DR4' if $blockType == 0xffff00f7; + $pos += 8; # move to start of block + if ($pos + $blockLen > $end) { + $et->Warn('Possibly corrupt CanonVRD block'); + last; + } + if ($verbose > 1 and not $outfile) { + printf $out " CanonVRD block 0x%.8x ($blockLen bytes at offset 0x%x)\n", + $blockType, $pos + $$dirInfo{DataPos}; + $et->VerboseDump($dataPt, Len => $blockLen, Start => $pos, Addr => $pos + $$dirInfo{DataPos}); + } + my $tagInfo = $$tagTablePtr{$blockType}; + unless ($tagInfo) { + unless ($et->Options('Unknown')) { + $pos += $blockLen; # step to next block + next; + } + my $name = sprintf('CanonVRD_0x%.8x', $blockType); + my $desc = $name; + $desc =~ tr/_/ /; + $tagInfo = { + Name => $name, + Description => $desc, + Binary => 1, + }; + AddTagToTable($tagTablePtr, $blockType, $tagInfo); + } + if ($$tagInfo{SubDirectory}) { + my $subTablePtr = GetTagTable($$tagInfo{SubDirectory}{TagTable}); + my %subdirInfo = ( + DataPt => $dataPt, + DataLen => length $$dataPt, + DataPos => $$dirInfo{DataPos}, + DirStart => $pos, + DirLen => $blockLen, + DirName => $$tagInfo{Name}, + Parent => 'CanonVRD', + OutFile => $outfile, + ); + if ($outfile) { + # set flag indicating we did this directory + $didDir{$$tagInfo{Name}} = 1; + my ($dat, $diff); + if ($$et{NEW_VALUE}{$tagInfo}) { + # write as a block + $et->VPrint(0, "Writing $$tagInfo{Name} as a block\n"); + $dat = $et->GetNewValue($tagInfo); + $dat = '' unless defined $dat; + ++$$et{CHANGED}; + } else { + $dat = $et->WriteDirectory(\%subdirInfo, $subTablePtr); + } + # update data with new directory + if (defined $dat) { + if (length $dat or $$et{FILE_TYPE} !~ /^(CRW|VRD)$/) { + # replace with new block (updating the block length word) + substr($$dataPt, $pos-4, $blockLen+4) = Set32u(length $dat) . $dat; + } else { + # remove block totally (CRW/VRD files only) + substr($$dataPt, $pos-8, $blockLen+8) = ''; + } + # make necessary adjustments if block changes length + if (($diff = length($$dataPt) - $dirLen) != 0) { + $pos += $diff; + $dirLen += $diff; + # update the new VRD length in the header/footer + Set32u($dirLen - 0x5c, $dataPt, 0x18); + Set32u($dirLen - 0x5c, $dataPt, $dirLen - 0x2c); + } + } + } else { + # extract as a block if requested + $et->ProcessDirectory(\%subdirInfo, $subTablePtr); + } + } else { + $et->HandleTag($tagTablePtr, $blockType, substr($$dataPt, $pos, $blockLen)); + } + $pos += $blockLen; # step to next block + } + if ($outfile) { + # create XMP block if necessary (CRW/VRD files only) + if ($$et{ADD_DIRS}{CanonVRD} and not $didDir{XMP}) { + my $subTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); + my $dat = $et->WriteDirectory({ Parent => 'CanonVRD' }, $subTablePtr); + if ($dat) { + my $blockLen = length $dat; + substr($$dataPt, -0x40, 0) = Set32u(0xffff00f6) . Set32u(length $dat) . $dat; + $dirLen = length $$dataPt; + # update the new VRD length in the header/footer + Set32u($dirLen - 0x5c, $dataPt, 0x18); + Set32u($dirLen - 0x5c, $dataPt, $dirLen - 0x2c); + } + } + # write CanonVRD trailer unless it is empty + if (length $$dataPt) { + Write($outfile, $$dataPt) or $err = 1 unless $outfile eq $dataPt; + } else { + $verbose and print $out " Deleting CanonVRD trailer\n"; + } + } elsif ($vrdType eq 'VRD' and (($$et{TAGS_FROM_FILE} and + not $$et{EXCL_TAG_LOOKUP}{canonvrd}) or $$et{REQ_TAG_LOOKUP}{canonvrd})) + { + # extract CanonVRD block if copying tags, or if requested (and not DR4 info) + $et->FoundTag('CanonVRD', $buff); + } + undef $buff; + return $err ? -1 : 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::CanonVRD - Read/write Canon VRD and DR4 information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read and +write VRD and DR4 Recipe Data information as written by the Canon Digital +Photo Professional software. This information is written to VRD and DR4 +files, and as a trailer in JPEG, CRW, CR2 and TIFF images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Bogdan and Gert Kello for decoding some tags. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/CanonVRD Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/CaptureOne.pm b/ExifTool/lib/Image/ExifTool/CaptureOne.pm new file mode 100644 index 0000000..c14cc35 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/CaptureOne.pm @@ -0,0 +1,235 @@ +#------------------------------------------------------------------------------ +# File: CaptureOne.pm +# +# Description: Read Capture One EIP and COS files +# +# Revisions: 2009/11/01 - P. Harvey Created +# +# Notes: The EIP format is a ZIP file containing an image (IIQ or TIFF) +# and some settings files (COS). COS files are XML based. +#------------------------------------------------------------------------------ + +package Image::ExifTool::CaptureOne; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::XMP; +use Image::ExifTool::ZIP; + +$VERSION = '1.04'; + +# CaptureOne COS XML tags +# - tags are added dynamically when encountered +# - this table is not listed in tag name docs +%Image::ExifTool::CaptureOne::Main = ( + GROUPS => { 0 => 'XML', 1 => 'XML', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP, + VARS => { NO_ID => 1 }, + ColorCorrections => { ValueConv => '\$val' }, # (long list of floating point numbers) +); + +#------------------------------------------------------------------------------ +# We found an XMP property name/value +# Inputs: 0) attribute list ref, 1) attr hash ref, +# 2) property name ref, 3) property value ref +# Returns: true if value was changed +sub HandleCOSAttrs($$$$) +{ + my ($attrList, $attrs, $prop, $valPt) = @_; + my $changed; + if (not length $$valPt and defined $$attrs{K} and defined $$attrs{V}) { + $$prop = $$attrs{K}; + $$valPt = $$attrs{V}; + # remove these attributes from the list + my @attrs = @$attrList; + @$attrList = ( ); + my $a; + foreach $a (@attrs) { + if ($a eq 'K' or $a eq 'V') { + delete $$attrs{$a}; + } else { + push @$attrList, $a; + } + } + $changed = 1; + } + return $changed; +} + +#------------------------------------------------------------------------------ +# We found a COS property name/value +# Inputs: 0) ExifTool object ref, 1) tag table ref +# 2) reference to array of XMP property names (last is current property) +# 3) property value, 4) attribute hash ref (not used here) +# Returns: 1 if valid tag was found +sub FoundCOS($$$$;$) +{ + my ($et, $tagTablePtr, $props, $val, $attrs) = @_; + + my $tag = $$props[-1]; + unless ($$tagTablePtr{$tag}) { + $et->VPrint(0, " | [adding $tag]\n"); + my $name = ucfirst $tag; + $name =~ tr/-_a-zA-Z0-9//dc; + return 0 unless length $tag; + my %tagInfo = ( Name => $tag ); + # try formatting any tag with "Date" in the name as a date + # (shouldn't affect non-date tags) + if ($name =~ /Date(?![a-z])/) { + $tagInfo{Groups} = { 2 => 'Time' }; + $tagInfo{ValueConv} = 'Image::ExifTool::XMP::ConvertXMPDate($val,1)'; + $tagInfo{PrintConv} = '$self->ConvertDateTime($val)'; + } + AddTagToTable($tagTablePtr, $tag, \%tagInfo); + } + # convert from UTF8 to ExifTool Charset + $val = $et->Decode($val, "UTF8"); + # un-escape XML character entities + $val = Image::ExifTool::XMP::UnescapeXML($val); + $et->HandleTag($tagTablePtr, $tag, $val); + return 0; +} + +#------------------------------------------------------------------------------ +# Extract information from a COS file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid XML file +sub ProcessCOS($$) +{ + my ($et, $dirInfo) = @_; + + # process using XMP module, but override handling of attributes and tags + $$dirInfo{XMPParseOpts} = { + AttrProc => \&HandleCOSAttrs, + FoundProc => \&FoundCOS, + }; + my $tagTablePtr = GetTagTable('Image::ExifTool::CaptureOne::Main'); + my $success = $et->ProcessDirectory($dirInfo, $tagTablePtr); + delete $$dirInfo{XMLParseArgs}; + return $success; +} + +#------------------------------------------------------------------------------ +# Extract information from a CaptureOne EIP file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 +# Notes: Upon entry to this routine, the file type has already been verified +# and the dirInfo hash contains a ZIP element unique to this process proc: +# ZIP - reference to Archive::Zip object for this file +sub ProcessEIP($$) +{ + my ($et, $dirInfo) = @_; + my $zip = $$dirInfo{ZIP}; + my ($file, $buff, $status, $member, %parseFile); + + $et->SetFileType('EIP'); + + # must catch all Archive::Zip warnings + local $SIG{'__WARN__'} = \&Image::ExifTool::ZIP::WarnProc; + # find all manifest files + my @members = $zip->membersMatching('^manifest\d*.xml$'); + # and choose the one with the highest version number (any better ideas?) + while (@members) { + my $m = shift @members; + my $f = $m->fileName(); + next if $file and $file gt $f; + $member = $m; + $file = $f; + } + # get file names from our chosen manifest file + if ($member) { + ($buff, $status) = $zip->contents($member); + if (not $status) { + my $foundImage; + while ($buff =~ m{<(RawPath|SettingsPath)>(.*?)</\1>}sg) { + $file = $2; + next unless $file =~ /\.(cos|iiq|jpe?g|tiff?)$/i; + $parseFile{$file} = 1; # set flag to parse this file + $foundImage = 1 unless $file =~ /\.cos$/i; + } + # ignore manifest unless it contained a valid image + undef %parseFile unless $foundImage; + } + } + # extract meta information from embedded files + my $docNum = 0; + @members = $zip->members(); # get all members + foreach $member (@members) { + # get filename of this ZIP member + $file = $member->fileName(); + next unless defined $file; + $et->VPrint(0, "File: $file\n"); + # set the document number and extract ZIP tags + $$et{DOC_NUM} = ++$docNum; + Image::ExifTool::ZIP::HandleMember($et, $member); + if (%parseFile) { + next unless $parseFile{$file}; + } else { + # reading the manifest didn't work, so look for image files in the + # root directory and .cos files in the CaptureOne directory + next unless $file =~ m{^([^/]+\.(iiq|jpe?g|tiff?)|CaptureOne/.*\.cos)$}i; + } + # extract the contents of the file + # Note: this could use a LOT of memory here for RAW images... + ($buff, $status) = $zip->contents($member); + $status and $et->Warn("Error extracting $file"), next; + if ($file =~ /\.cos$/i) { + # process Capture One Settings files + my %dirInfo = ( + DataPt => \$buff, + DirLen => length $buff, + DataLen => length $buff, + ); + ProcessCOS($et, \%dirInfo); + } else { + # set HtmlDump error if necessary because it doesn't work with embedded files + if ($$et{HTML_DUMP}) { + $$et{HTML_DUMP}{Error} = "Sorry, can't dump images embedded in ZIP files"; + } + # process IIQ, JPEG and TIFF images + $et->ExtractInfo(\$buff, { ReEntry => 1 }); + } + undef $buff; # (free memory now) + } + delete $$et{DOC_NUM}; + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::CaptureOne - Read Capture One EIP and COS files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Capture One EIP (Enhanced Image Package) and COS (Capture +One Settings) files. + +=head1 NOTES + +The EIP format is a ZIP file containing an image (IIQ or TIFF) and some +settings files (COS). + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/ZIP Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Casio.pm b/ExifTool/lib/Image/ExifTool/Casio.pm new file mode 100644 index 0000000..c840233 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Casio.pm @@ -0,0 +1,2060 @@ +#------------------------------------------------------------------------------ +# File: Casio.pm +# +# Description: Casio EXIF maker notes tags +# +# Revisions: 12/09/2003 - P. Harvey Created +# 09/10/2004 - P. Harvey Added MakerNote2 (thanks to Joachim Loehr) +# +# References: 1) http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html +# 2) Joachim Loehr private communication +# 3) http://homepage3.nifty.com/kamisaka/makernote/makernote_casio.htm +# 4) http://gvsoft.homedns.org/exif/makernote-casio-type1.html +# 5) Robert Chi private communication (EX-F1) +# 6) https://exiftool.org/forum/index.php/topic,3701.html +# JD) Jens Duttke private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::Casio; + +use strict; +use vars qw($VERSION); +use Image::ExifTool::Exif; + +$VERSION = '1.38'; + +# older Casio maker notes (ref 1) +%Image::ExifTool::Casio::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0001 => { + Name => 'RecordingMode' , + Writable => 'int16u', + PrintConv => { + 1 => 'Single Shutter', + 2 => 'Panorama', + 3 => 'Night Scene', + 4 => 'Portrait', + 5 => 'Landscape', + 7 => 'Panorama', #4 + 10 => 'Night Scene', #4 + 15 => 'Portrait', #4 + 16 => 'Landscape', #4 + }, + }, + 0x0002 => { + Name => 'Quality', + Writable => 'int16u', + PrintConv => { 1 => 'Economy', 2 => 'Normal', 3 => 'Fine' }, + }, + 0x0003 => { + Name => 'FocusMode', + Writable => 'int16u', + PrintConv => { + 2 => 'Macro', + 3 => 'Auto', + 4 => 'Manual', + 5 => 'Infinity', + 7 => 'Spot AF', #4 + }, + }, + 0x0004 => [ + { + Name => 'FlashMode', + Condition => '$self->{Model} =~ /^QV-(3500EX|8000SX)/', + Writable => 'int16u', + PrintConv => { + 1 => 'Auto', + 2 => 'On', + 3 => 'Off', + 4 => 'Off', #4 + 5 => 'Red-eye Reduction', #4 + }, + }, + { + Name => 'FlashMode', + Writable => 'int16u', + PrintConv => { + 1 => 'Auto', + 2 => 'On', + 3 => 'Off', + 4 => 'Red-eye Reduction', + }, + }, + ], + 0x0005 => { + Name => 'FlashIntensity', + Writable => 'int16u', + PrintConv => { + 11 => 'Weak', + 12 => 'Low', #4 + 13 => 'Normal', + 14 => 'High', #4 + 15 => 'Strong', + }, + }, + 0x0006 => { + Name => 'ObjectDistance', + Writable => 'int32u', + ValueConv => '$val / 1000', #4 + ValueConvInv => '$val * 1000', + PrintConv => '"$val m"', + PrintConvInv => '$val=~s/\s*m$//;$val', + }, + 0x0007 => { + Name => 'WhiteBalance', + Writable => 'int16u', + PrintConv => { + 1 => 'Auto', + 2 => 'Tungsten', + 3 => 'Daylight', + 4 => 'Fluorescent', + 5 => 'Shade', + 129 => 'Manual', + }, + }, + # 0x0009 Bulb? (ref unknown) + 0x000a => { + Name => 'DigitalZoom', + Writable => 'int32u', + PrintHex => 1, + PrintConv => { + 0x10000 => 'Off', + 0x10001 => '2x', + 0x19999 => '1.6x', #4 + 0x20000 => '2x', #4 + 0x33333 => '3.2x', #4 + 0x40000 => '4x', #4 + }, + }, + 0x000b => { + Name => 'Sharpness', + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'Soft', + 2 => 'Hard', + 16 => 'Normal', #4 + 17 => '+1', #4 + 18 => '-1', #4 + }, + }, + 0x000c => { + Name => 'Contrast', + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'Low', + 2 => 'High', + 16 => 'Normal', #4 + 17 => '+1', #4 + 18 => '-1', #4 + }, + }, + 0x000d => { + Name => 'Saturation', + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'Low', + 2 => 'High', + 16 => 'Normal', #4 + 17 => '+1', #4 + 18 => '-1', #4 + }, + }, + 0x0014 => { + Name => 'ISO', + Writable => 'int16u', + Priority => 0, + }, + 0x0015 => { #JD (Similar to Type2 0x2001) + Name => 'FirmwareDate', + Writable => 'string', + Format => 'undef', # the 'string' contains nulls + Count => 18, + PrintConv => q{ + $_ = $val; + if (/^(\d{2})(\d{2})\0\0(\d{2})(\d{2})\0\0(\d{2})(.{2})\0{2}$/) { + my $yr = $1 + ($1 < 70 ? 2000 : 1900); + my $sec = $6; + $val = "$yr:$2:$3 $4:$5"; + $val .= ":$sec" if $sec=~/^\d{2}$/; + return $val; + } + tr/\0/./; s/\.+$//; + return "Unknown ($_)"; + }, + PrintConvInv => q{ + $_ = $val; + if (/^(19|20)(\d{2}):(\d{2}):(\d{2}) (\d{2}):(\d{2})$/) { + return "$2$3\0\0$4$5\0\0$6\0\0\0\0"; + } elsif (/^Unknown\s*\((.*)\)$/i) { + $_ = $1; tr/./\0/; + return $_; + } else { + return undef; + } + }, + }, + 0x0016 => { #4 + Name => 'Enhancement', + Writable => 'int16u', + PrintConv => { + 1 => 'Off', + 2 => 'Red', + 3 => 'Green', + 4 => 'Blue', + 5 => 'Flesh Tones', + }, + }, + 0x0017 => { #4 + Name => 'ColorFilter', + Writable => 'int16u', + PrintConv => { + 1 => 'Off', + 2 => 'Black & White', + 3 => 'Sepia', + 4 => 'Red', + 5 => 'Green', + 6 => 'Blue', + 7 => 'Yellow', + 8 => 'Pink', + 9 => 'Purple', + }, + }, + 0x0018 => { #4 + Name => 'AFPoint', + Writable => 'int16u', + Notes => 'may not be valid for all models', #JD + PrintConv => { + 1 => 'Center', + 2 => 'Upper Left', + 3 => 'Upper Right', + 4 => 'Near Left/Right of Center', + 5 => 'Far Left/Right of Center', + 6 => 'Far Left/Right of Center/Bottom', + 7 => 'Top Near-left', + 8 => 'Near Upper/Left', + 9 => 'Top Near-right', + 10 => 'Top Left', + 11 => 'Top Center', + 12 => 'Top Right', + 13 => 'Center Left', + 14 => 'Center Right', + 15 => 'Bottom Left', + 16 => 'Bottom Center', + 17 => 'Bottom Right', + }, + }, + 0x0019 => { #4 + Name => 'FlashIntensity', + Writable => 'int16u', + PrintConv => { + 1 => 'Normal', + 2 => 'Weak', + 3 => 'Strong', + }, + }, + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + # crazy I know, but the offset for this value is entry-based + # (QV-2100, QV-2900UX, QV-3500EX and QV-4000) even though the + # offsets for other values isn't + EntryBased => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, +); + +# ref 2: +%Image::ExifTool::Casio::Type2 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0002 => { + Name => 'PreviewImageSize', + Groups => { 2 => 'Image' }, + Writable => 'int16u', + Count => 2, + PrintConv => '$val =~ tr/ /x/; $val', + PrintConvInv => '$val =~ tr/x/ /; $val', + }, + 0x0003 => { + Name => 'PreviewImageLength', + Groups => { 2 => 'Image' }, + OffsetPair => 0x0004, # point to associated offset + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x0004 => { + Name => 'PreviewImageStart', + Groups => { 2 => 'Image' }, + Flags => 'IsOffset', + OffsetPair => 0x0003, # point to associated byte count + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x0008 => { + Name => 'QualityMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Economy', + 1 => 'Normal', + 2 => 'Fine', + }, + }, + 0x0009 => { + Name => 'CasioImageSize', + Groups => { 2 => 'Image' }, + Writable => 'int16u', + PrintConv => { + 0 => '640x480', + 4 => '1600x1200', + 5 => '2048x1536', + 20 => '2288x1712', + 21 => '2592x1944', + 22 => '2304x1728', + 36 => '3008x2008', + }, + }, + 0x000d => { + Name => 'FocusMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'Macro', + }, + }, + 0x0014 => { + Name => 'ISO', + Writable => 'int16u', + Priority => 0, + PrintConv => { + 3 => 50, + 4 => 64, + 6 => 100, + 9 => 200, + }, + }, + 0x0019 => { + Name => 'WhiteBalance', + Writable => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Shade', + 3 => 'Tungsten', + 4 => 'Fluorescent', + 5 => 'Manual', + }, + }, + 0x001d => { + Name => 'FocalLength', + Writable => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + 0x001f => { + Name => 'Saturation', + Writable => 'int16u', + PrintConv => { + 0 => 'Low', + 1 => 'Normal', + 2 => 'High', + }, + }, + 0x0020 => { + Name => 'Contrast', + Writable => 'int16u', + PrintConv => { + 0 => 'Low', + 1 => 'Normal', + 2 => 'High', + }, + }, + 0x0021 => { + Name => 'Sharpness', + Writable => 'int16u', + PrintConv => { + 0 => 'Soft', + 1 => 'Normal', + 2 => 'Hard', + }, + }, + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, + 0x2000 => { + # this image data is also referenced by tags 3 and 4 + # (nasty that they double-reference the image!) + %Image::ExifTool::previewImageTagInfo, + Groups => { 2 => 'Preview' }, + }, + 0x2001 => { #PH + # I downloaded images from 12 different EX-Z50 cameras, and they showed + # only 3 distinct dates here (2004:08:31 18:55, 2004:09:13 14:14, and + # 2004:11:26 17:07), so I'm guessing this is a firmware version date - PH + Name => 'FirmwareDate', + Writable => 'string', + Format => 'undef', # the 'string' contains nulls + Count => 18, + PrintConv => q{ + $_ = $val; + if (/^(\d{2})(\d{2})\0\0(\d{2})(\d{2})\0\0(\d{2})\0{4}$/) { + my $yr = $1 + ($1 < 70 ? 2000 : 1900); + return "$yr:$2:$3 $4:$5"; + } + tr/\0/./; s/\.+$//; + return "Unknown ($_)"; + }, + PrintConvInv => q{ + $_ = $val; + if (/^(19|20)(\d{2}):(\d{2}):(\d{2}) (\d{2}):(\d{2})$/) { + return "$2$3\0\0$4$5\0\0$6\0\0\0\0"; + } elsif (/^Unknown\s*\((.*)\)$/i) { + $_ = $1; tr/./\0/; + return $_; + } else { + return undef; + } + }, + }, + 0x2011 => { + Name => 'WhiteBalanceBias', + Writable => 'int16u', + Count => 2, + }, + 0x2012 => { + Name => 'WhiteBalance', + Writable => 'int16u', + PrintConv => { + 0 => 'Manual', + 1 => 'Daylight', #3 + 2 => 'Cloudy', #PH (EX-ZR20, NC) + 3 => 'Shade', #3 + 4 => 'Flash?', + 6 => 'Fluorescent', #3 + 9 => 'Tungsten?', #PH (EX-Z77) + 10 => 'Tungsten', #3 + 12 => 'Flash', + }, + }, + 0x2021 => { #JD (guess) + Name => 'AFPointPosition', + Writable => 'int16u', + Count => 4, + PrintConv => q{ + my @v = split ' ', $val; + return 'n/a' if $v[0] == 65535 or not $v[1] or not $v[3]; + sprintf "%.2g %.2g", $v[0]/$v[1], $v[2]/$v[3]; + }, + }, + 0x2022 => { + Name => 'ObjectDistance', + Writable => 'int32u', + ValueConv => '$val >= 0x20000000 ? "inf" : $val / 1000', + ValueConvInv => '$val eq "inf" ? 0x20000000 : $val * 1000', + PrintConv => '$val eq "inf" ? $val : "$val m"', + PrintConvInv => '$val=~s/\s*m$//;$val', + }, + # 0x2023 looks interesting (values 0,1,2,3,5 in samples) - PH + # - 1 for makeup mode shots (portrait?) (EX-Z450) + 0x2034 => { + Name => 'FlashDistance', + Writable => 'int16u', + }, + # 0x203e - normally 62000, but 62001 for anti-shake mode - PH + 0x2076 => { #PH (EX-Z450) + # ("Enhancement" was taken already, so call this "SpecialEffect" for lack of a better name) + Name => 'SpecialEffectMode', + Writable => 'int8u', + Count => 3, + PrintConv => { + '0 0 0' => 'Off', + '1 0 0' => 'Makeup', + '2 0 0' => 'Mist Removal', + '3 0 0' => 'Vivid Landscape', + # have also seen '1 1 1', '2 2 4', '4 3 3', '4 4 4' + # '0 0 14' and '0 0 42' - premium auto night shot (EX-Z2300) + # and '0 0 2' for Art HDR + }, + }, + 0x2089 => [ #PH + { + Name => 'FaceInfo1', + Condition => '$$valPt =~ /^(\0\0|.\x02\x80\x01\xe0)/s', # (EX-H5) + SubDirectory => { + TagTable => 'Image::ExifTool::Casio::FaceInfo1', + ByteOrder => 'BigEndian', + }, + },{ + Name => 'FaceInfo2', + Condition => '$$valPt =~ /^\x02\x01/', # (EX-H20G,EX-ZR100) + SubDirectory => { + TagTable => 'Image::ExifTool::Casio::FaceInfo2', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'FaceInfoUnknown', + Unknown => 1, + }, + ], + # 0x208a - also some sort of face detection information - PH + 0x211c => { #PH + Name => 'FacesDetected', + Format => 'int8u', + }, + 0x3000 => { + Name => 'RecordMode', + Writable => 'int16u', + PrintConv => { + 2 => 'Program AE', #3 + 3 => 'Shutter Priority', #3 + 4 => 'Aperture Priority', #3 + 5 => 'Manual', #3 + 6 => 'Best Shot', #3 + 17 => 'Movie', #PH (UHQ?) + 19 => 'Movie (19)', #PH (HQ?, EX-P505) + 20 => 'YouTube Movie', #PH + '2 0' => 'Program AE', #PH (NC) + '3 0' => 'Shutter Priority', #PH (NC) + '4 0' => 'Aperture Priority', #PH (NC) + '5 0' => 'Manual', #PH (NC) + '6 0' => 'Best Shot', #PH (NC) + }, + }, + 0x3001 => { #3 + Name => 'ReleaseMode', + Writable => 'int16u', + PrintConv => { + 1 => 'Normal', + 3 => 'AE Bracketing', + 11 => 'WB Bracketing', + 13 => 'Contrast Bracketing', #(not sure about translation - PH) + 19 => 'High Speed Burst', #PH (EX-FH25, 40fps) + # have also seen: 2, 7(common), 14, 18 - PH + }, + }, + 0x3002 => { + Name => 'Quality', + Writable => 'int16u', + PrintConv => { + 1 => 'Economy', + 2 => 'Normal', + 3 => 'Fine', + }, + }, + 0x3003 => { + Name => 'FocusMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Manual', #(guess at translation) + 1 => 'Focus Lock', #(guess at translation) + 2 => 'Macro', #3 + 3 => 'Single-Area Auto Focus', + 5 => 'Infinity', #PH + 6 => 'Multi-Area Auto Focus', + 8 => 'Super Macro', #PH (EX-Z2300) + }, + }, + 0x3006 => { + Name => 'HometownCity', + Writable => 'string', + }, + # unfortunately the BestShotMode numbers are model-dependent - PH + #http://search.casio-intl.com/search?q=BEST+SHOT+sets+up+the+camera+CASIO+EX+ZR100+BEST+SHOT&btnG=Search&output=xml_no_dtd&oe=UTF-8&ie=UTF-8&site=casio-intl_com&client=search_casio-intl_com&proxystylesheet=search_casio-intl_com + # NOTE: BestShotMode is not used unless RecordMode is "Best Shot" + 0x3007 => [{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-FC100"', + Notes => 'EX-FC100', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Portrait', + 3 => 'Scenery', + 4 => 'Portrait with Scenery', + 5 => 'Children', + 6 => 'Sports', + 7 => 'Pet', + 8 => 'Flower', + 9 => 'Natural Green', + 10 => 'Autumn Leaves', + 11 => 'Sundown', + 12 => 'High Speed Night Scene', + 13 => 'Night Scene Portrait', + 14 => 'Fireworks', + 15 => 'High Speed Anti Shake', + 16 => 'Multi-motion Image', + 17 => 'High Speed Best Selection', + 18 => 'Move Out CS', + 19 => 'Move In CS', + 20 => 'Pre-record Movie', + 21 => 'For YouTube', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-FC150"', + Notes => 'EX-FC150', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Expression CS', + 3 => 'Baby CS', + 4 => 'Child CS', + 5 => 'Pet CS', + 6 => 'Sports CS', + 7 => 'Child High Speed Movie', + 8 => 'Pet High Speed Movie', + 9 => 'Sports High Speed Movie', + 10 => 'Lag Correction', + 11 => 'High Speed Lighting', + 12 => 'High Speed Night Scene', + 13 => 'High Speed Night Scene and Portrait', + 14 => 'High Speed Anti Shake', + 15 => 'High Speed Best Selection', + 16 => 'Portrait', + 17 => 'Scenery', + 18 => 'Portrait with Scenery', + 19 => 'Flower', + 20 => 'Natural Green', + 21 => 'Autumn Leaves', + 22 => 'Sundown', + 23 => 'Fireworks', + 24 => 'Multi-motion Image', + 25 => 'Move Out CS', + 26 => 'Move In CS', + 27 => 'Pre-record Movie', + 28 => 'For YouTube', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-FC200S"', + Notes => 'EX-FC200S', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'Slow Motion Swing (behind)', + 2 => 'Slow Motion Swing (front)', + 3 => 'Self Slow Motion (behind)', + 4 => 'Self Slow Motion (front)', + 5 => 'Swing Burst', + 6 => 'HDR', + 7 => 'HDR Art', + 8 => 'High Speed Night Scene', + 9 => 'High Speed Night Scene and Portrait', + 10 => 'High Speed Anti Shake', + 11 => 'Multi SR Zoom', + 12 => 'Blurred Background', + 13 => 'Wide Shot', + 14 => 'Slide Panorama', + 15 => 'High Speed Best Selection', + 16 => 'Lag Correction', + 17 => 'High Speed CS', + 18 => 'Child CS', + 19 => 'Pet CS', + 20 => 'Sports CS', + 21 => 'Child High Speed Movie', + 22 => 'Pet High Speed Movie', + 23 => 'Sports High Speed Movie', + 24 => 'Portrait', + 25 => 'Scenery', + 26 => 'Portrait with Scenery', + 27 => 'Children', + 28 => 'Sports', + 29 => 'Candlelight Portrait', + 30 => 'Party', + 31 => 'Pet', + 32 => 'Flower', + 33 => 'Natural Green', + 34 => 'Autumn Leaves', + 35 => 'Soft Flowing Water', + 36 => 'Splashing Water', + 37 => 'Sundown', + 38 => 'Fireworks', + 39 => 'Food', + 40 => 'Text', + 41 => 'Collection', + 42 => 'Auction', + 43 => 'Pre-record Movie', + 44 => 'For YouTube', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-FH100"', + Notes => 'EX-FH100', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'Expression CS', + 2 => 'Baby CS', + 3 => 'Child CS', + 4 => 'Pet CS', + 5 => 'Sports CS', + 6 => 'Child High Speed Movie', + 7 => 'Pet High Speed Movie', + 8 => 'Sports High Speed Movie', + 9 => 'Lag Correction', + 10 => 'High Speed Lighting', + 11 => 'High Speed Night Scene', + 12 => 'High Speed Night Scene and Portrait', + 13 => 'High Speed Anti Shake', + 14 => 'High Speed Best Selection', + 15 => 'Portrait', + 16 => 'Scenery', + 17 => 'Portrait with Scenery', + 18 => 'Flower', + 19 => 'Natural Green', + 20 => 'Autumn Leaves', + 21 => 'Sundown', + 22 => 'Fireworks', + 23 => 'Multi-motion Image', + 24 => 'Move Out CS', + 25 => 'Move In CS', + 26 => 'Pre-record Movie', + 27 => 'For YouTube', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-G1"', + Notes => 'EX-G1', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Auto Best Shot', + 3 => 'Dynamic Photo', + 4 => 'Interval Snapshot', + 5 => 'Interval Movie', + 6 => 'Portrait', + 7 => 'Scenery', + 8 => 'Portrait with Scenery', + 9 => 'Underwater', + 10 => 'Beach', + 11 => 'Snow', + 12 => 'Children', + 13 => 'Sports', + 14 => 'Pet', + 15 => 'Flower', + 16 => 'Sundown', + 17 => 'Night Scene', + 18 => 'Night Scene Portrait', + 19 => 'Fireworks', + 20 => 'Food', + 21 => 'For eBay', + 22 => 'Multi-motion Image', + 23 => 'Pre-record Movie', + 24 => 'For YouTube', + 25 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-S10"', + Notes => 'EX-S10', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Portrait', + 3 => 'Scenery', + 4 => 'Portrait with Scenery', + 5 => 'Self-portrait (1 person)', + 6 => 'Self-portrait (2 people)', + 7 => 'Children', + 8 => 'Sports', + 9 => 'Candlelight Portrait', + 10 => 'Party', + 11 => 'Pet', + 12 => 'Flower', + 13 => 'Natural Green', + 14 => 'Autumn Leaves', + 15 => 'Soft Flowing Water', + 16 => 'Splashing Water', + 17 => 'Sundown', + 18 => 'Night Scene', + 19 => 'Night Scene Portrait', + 20 => 'Fireworks', + 21 => 'Food', + 22 => 'Text', + 23 => 'Collection', + 24 => 'Auction', + 25 => 'Backlight', + 26 => 'Anti Shake', + 27 => 'High Sensitivity', + 28 => 'Underwater', + 29 => 'Monochrome', + 30 => 'Retro', + 31 => 'Business Cards', + 32 => 'White Board', + 33 => 'Silent', + 34 => 'Pre-record Movie', + 35 => 'For YouTube', + 36 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-S880"', + Notes => 'EX-S880', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Portrait', + 3 => 'Scenery', + 4 => 'Portrait with Scenery', + 5 => 'Children', + 6 => 'Sports', + 7 => 'Candlelight Portrait', + 8 => 'Party', + 9 => 'Pet', + 10 => 'Flower', + 11 => 'Natural Green', + 12 => 'Autumn Leaves', + 13 => 'Soft Flowing Water', # (wrong in documentation) + 14 => 'Splashing Water', + 15 => 'Sundown', + 16 => 'Night Scene', + 17 => 'Night Scene Portrait', + 18 => 'Fireworks', + 19 => 'Food', + 20 => 'Text', + 21 => 'Collection', + 22 => 'Auction', + 23 => 'Backlight', + 24 => 'Anti Shake', + 25 => 'High Sensitivity', + 26 => 'Monochrome', + 27 => 'Retro', + 28 => 'Twilight', + 29 => 'Layout (2 images)', + 30 => 'Layout (3 images)', + 31 => 'Auto Framing', + 32 => 'Old Photo', + 33 => 'Business Cards', + 34 => 'White Board', + 35 => 'Silent', + 36 => 'Short Movie', + 37 => 'Past Movie', + 38 => 'For YouTube', + 39 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-Z16"', + Notes => 'EX-Z16', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Portrait', + 3 => 'Scenery', + 4 => 'Portrait with Scenery', + 5 => 'Children', + 6 => 'Sports', + 7 => 'Candlelight Portrait', + 8 => 'Party', + 9 => 'Pet', + 10 => 'Flower', + 11 => 'Soft Flowing Water', + 12 => 'Sundown', + 13 => 'Night Scene', + 14 => 'Night Scene Portrait', + 15 => 'Fireworks', + 16 => 'Food', + 17 => 'Text', + 18 => 'For eBay', + 19 => 'Backlight', + 20 => 'Anti Shake', + 21 => 'High Sensitivity', + 22 => 'For YouTube', + 23 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-Z9"', + Notes => 'EX-Z9', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Movie', + 3 => 'Portrait', + 4 => 'Scenery', + 5 => 'Children', + 6 => 'Sports', + 7 => 'Candlelight Portrait', + 8 => 'Party', + 9 => 'Pet', + 10 => 'Flower', + 11 => 'Soft Flowing Water', + 12 => 'Sundown', + 13 => 'Night Scene', + 14 => 'Night Scene Portrait', + 15 => 'Fireworks', + 16 => 'Food', + 17 => 'Text', + 18 => 'Auction', + 19 => 'Backlight', + 20 => 'Anti Shake', + 21 => 'High Sensitivity', + 22 => 'For YouTube', + 23 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-Z80"', + Notes => 'EX-Z80', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Portrait', + 3 => 'Scenery', + 4 => 'Portrait with Scenery', + 5 => 'Pet', + 6 => 'Self-portrait (1 person)', + 7 => 'Self-portrait (2 people)', + 8 => 'Flower', + 9 => 'Food', + 10 => 'Fashion Accessories', + 11 => 'Magazine', + 12 => 'Monochrome', + 13 => 'Retro', + 14 => 'Cross Filter', + 15 => 'Pastel', + 16 => 'Night Scene', + 17 => 'Night Scene Portrait', + 18 => 'Party', + 19 => 'Sports', + 20 => 'Children', + 21 => 'Sundown', + 22 => 'Fireworks', + 23 => 'Underwater', + 24 => 'Backlight', + 25 => 'High Sensitivity', + 26 => 'Auction', + 27 => 'White Board', + 28 => 'Pre-record Movie', + 29 => 'For YouTube', + 30 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} =~ /^EX-Z(100|200)$/', + Notes => 'EX-Z100 and EX-Z200', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Auto Best Shot', + 3 => 'Portrait', + 4 => 'Scenery', + 5 => 'Portrait with Scenery', + 6 => 'Self-portrait (1 person)', + 7 => 'Self-portrait (2 people)', + 8 => 'Children', + 9 => 'Sports', + 10 => 'Candlelight Portrait', + 11 => 'Party', + 12 => 'Pet', + 13 => 'Flower', + 14 => 'Natural Green', + 15 => 'Autumn Leaves', + 16 => 'Soft Flowing Water', + 17 => 'Splashing Water', + 18 => 'Sundown', + 19 => 'Night Scene', + 20 => 'Night Scene Portrait', + 21 => 'Fireworks', + 22 => 'Food', + 23 => 'Text', + 24 => 'Collection', + 25 => 'Auction', + 26 => 'Backlight', + 27 => 'Anti Shake', + 28 => 'High Sensitivity', + 29 => 'Underwater', + 30 => 'Monochrome', + 31 => 'Retro', + 32 => 'Twilight', + 33 => 'ID Photo', + 34 => 'Business Cards', + 35 => 'White Board', + 36 => 'Silent', + 37 => 'Pre-record Movie', + 38 => 'For YouTube', + 39 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + # (Movies have different BestShot numbers for this camera) + Condition => '$$self{Model} eq "EX-Z750" and $$self{FILE_TYPE} eq "JPEG"', + Notes => 'EX-Z750 JPEG images', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Portrait', + 2 => 'Scenery', + 3 => 'Portrait with Scenery', + 4 => 'Children', + 5 => 'Sports', + 6 => 'Candlelight Portrait', + 7 => 'Party', + 8 => 'Pet', + 9 => 'Flower', + 10 => 'Natural Green', + 11 => 'Soft Flowing Water', + 12 => 'Splashing Water', + 13 => 'Sundown', + 14 => 'Night Scene', + 15 => 'Night Scene Portrait', + 16 => 'Fireworks', + 17 => 'Food', + 18 => 'Text', + 19 => 'Collection', + 20 => 'Backlight', + 21 => 'Anti Shake', + 22 => 'Pastel', + 23 => 'Illustration', + 24 => 'Cross Filter', + 25 => 'Monochrome', + 26 => 'Retro', + 27 => 'Twilight', + 28 => 'Old Photo', + 29 => 'ID Photo', + 30 => 'Business Cards', + 31 => 'White Board', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + # (Movies have different BestShot numbers for this camera) + Condition => '$$self{Model} eq "EX-Z750" and $$self{FILE_TYPE} =~ /^(MOV|AVI)$/', + Notes => 'EX-Z750 movies', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Portrait', + 2 => 'Scenery', + 3 => 'Night Scene', + 4 => 'Fireworks', + 5 => 'Backlight', + 6 => 'Silent', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + # (Movies have different BestShot numbers for this camera) + Condition => '$$self{Model} eq "EX-Z850" and $$self{FILE_TYPE} eq "JPEG"', + Notes => 'EX-Z850 JPEG images', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Portrait', + 2 => 'Scenery', + 3 => 'Portrait with Scenery', + 4 => 'Children', + 5 => 'Sports', + 6 => 'Candlelight Portrait', + 7 => 'Party', + 8 => 'Pet', + 9 => 'Flower', + 10 => 'Natural Green', + 11 => 'Autumn Leaves', + 12 => 'Soft Flowing Water', + 13 => 'Splashing Water', + 14 => 'Sundown', + 15 => 'Night Scene', + 16 => 'Night Scene Portrait', + 17 => 'Fireworks', + 18 => 'Food', + 19 => 'Text', + 20 => 'Collection', + 21 => 'For eBay', + 22 => 'Backlight', + 23 => 'Anti Shake', + 24 => 'High Sensitivity', + 25 => 'Pastel', + 26 => 'Illustration', + 27 => 'Cross Filter', + 28 => 'Monochrome', + 29 => 'Retro', + 30 => 'Twilight', + 31 => 'ID Photo', + 32 => 'Old Photo', + 33 => 'Business Cards', + 34 => 'White Board', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + # (Movies have different BestShot numbers for this camera) + Condition => '$$self{Model} eq "EX-Z850" and $$self{FILE_TYPE} =~ /^(MOV|AVI)$/', + Notes => 'EX-Z850 movies', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Portrait', + 2 => 'Scenery', + 3 => 'Night Scene', + 4 => 'Fireworks', + 5 => 'Backlight', + 6 => 'High Sensitivity', + 7 => 'Silent', + 8 => 'Short Movie', + 9 => 'Past Movie', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + # (Movies have different BestShot numbers for this camera) + Condition => '$$self{Model} eq "EX-Z1050"', + Notes => 'EX-Z1050', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Movie', + 3 => 'Portrait', + 4 => 'Scenery', + 5 => 'Portrait with Scenery', + 6 => 'Children', + 7 => 'Sports', + 8 => 'Candlelight Portrait', + 9 => 'Party', + 10 => 'Pet', + 11 => 'Flower', + 12 => 'Natural Green', + 13 => 'Autumn Leaves', + 14 => 'Soft Flowing Water', + 15 => 'Splashing Water', + 16 => 'Sundown', + 17 => 'Night Scene', + 18 => 'Night Scene Portrait', + 19 => 'Fireworks', + 20 => 'Food', + 21 => 'Text', + 22 => 'Collection', + 23 => 'For eBay', + 24 => 'Backlight', + 25 => 'Anti Shake', + 26 => 'High Sensitivity', + 27 => 'Underwater', + 28 => 'Monochrome', + 29 => 'Retro', + 30 => 'Twilight', + 31 => 'Layout (2 images)', + 32 => 'Layout (3 images)', + 33 => 'Auto Framing', + 34 => 'ID Photo', + 35 => 'Old Photo', + 36 => 'Business Cards', + 37 => 'White Board', + 38 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + # (Movies have different BestShot numbers for this camera) + Condition => '$$self{Model} eq "EX-Z1080"', + Notes => 'EX-Z1080', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Movie', + 3 => 'Portrait', + 4 => 'Scenery', + 5 => 'Portrait with Scenery', + 6 => 'Children', + 7 => 'Sports', + 8 => 'Candlelight Portrait', + 9 => 'Party', + 10 => 'Pet', + 11 => 'Flower', + 12 => 'Natural Green', + 13 => 'Autumn Leaves', + 14 => 'Soft Flowing Water', + 15 => 'Splashing Water', + 16 => 'Sundown', + 17 => 'Night Scene', + 18 => 'Night Scene Portrait', + 19 => 'Fireworks', + 20 => 'Food', + 21 => 'Text', + 22 => 'Collection', + 23 => 'For eBay', + 24 => 'Backlight', + 25 => 'Anti Shake', + 26 => 'High Sensitivity', + 27 => 'Underwater', + 28 => 'Monochrome', + 29 => 'Retro', + 30 => 'Twilight', + 31 => 'Layout (2 images)', + 32 => 'Layout (3 images)', + 33 => 'Auto Framing', + 34 => 'ID Photo', + 35 => 'Old Photo', + 36 => 'Business Cards', + 37 => 'White Board', + 38 => 'Short Movie', + 39 => 'Past Movie', + 40 => 'For YouTube', + 41 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + # (Movies have different BestShot numbers for this camera) + Condition => '$$self{Model} eq "EX-Z1200" and $$self{FILE_TYPE} eq "JPEG"', + Notes => 'EX-Z1200 JPEG images', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Portrait', + 2 => 'Scenery', + 3 => 'Portrait with Scenery', + 4 => 'Children', + 5 => 'Sports', + 6 => 'Candlelight Portrait', + 7 => 'Party', + 8 => 'Pet', + 9 => 'Flower', + 10 => 'Natural Green', + 11 => 'Autumn Leaves', + 12 => 'Soft Flowing Water', + 13 => 'Splashing Water', + 14 => 'Sundown', + 15 => 'Night Scene', + 16 => 'Night Scene Portrait', + 17 => 'Fireworks', + 18 => 'Food', + 19 => 'Text', + 20 => 'Collection', + 21 => 'Auction', + 22 => 'Backlight', + 23 => 'High Sensitivity', + 24 => 'Underwater', + 25 => 'Monochrome', + 26 => 'Retro', + 27 => 'Twilight', + 28 => 'Layout (2 images)', + 29 => 'Layout (3 images)', + 30 => 'Auto Framing', + 31 => 'ID Photo', + 32 => 'Old Photo', + 33 => 'Business Cards', + 34 => 'White Board', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + # (Movies have different BestShot numbers for this camera) + Condition => '$$self{Model} eq "EX-Z1200" and $$self{FILE_TYPE} =~ /^(MOV|AVI)$/', + Notes => 'EX-Z1200 movies', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Portrait', + 2 => 'Scenery', + 3 => 'Night Scene', + 4 => 'Fireworks', + 5 => 'Backlight', + 6 => 'High Sensitivity', + 7 => 'Silent', + 8 => 'Short Movie', + 9 => 'Past Movie', + }, + }, + # (the following weren't numbered in the documentation: + # G1, Z300, Z250, Z85, Z19, Z150, F1, FH20) + { + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-Z2000"', + Notes => 'EX-Z2000', + PrintConvColumns => 3, + #http://support.casio.com/download_files/001/faq_pdf/Z2000/EXZ2000_BS_US_a.pdf + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Premium Auto', + 3 => 'Dynamic Photo', + 4 => 'Portrait', + 5 => 'Scenery', + 6 => 'Portrait with Scenery', + 7 => 'Children', + 8 => 'Sports', + 9 => 'Candlelight Portrait', + 10 => 'Party', + 11 => 'Pet', + 12 => 'Flower', + 13 => 'Natural Green', + 14 => 'Autumn Leaves', + 15 => 'Soft Flowing Water', + 16 => 'Splashing Water', + 17 => 'Sundown', + 18 => 'Night Scene', + 19 => 'Night Scene Portrait', + 20 => 'Fireworks', + 21 => 'Food', + 22 => 'Text', + 23 => 'Collection', + 24 => 'For eBay', + 25 => 'Backlight', + 26 => 'High Sensitivity', + 27 => 'Oil Painting', + 28 => 'Crayon', + 29 => 'Water Color', + 30 => 'Monochrome', + 31 => 'Retro', + 32 => 'Twilight', + 33 => 'Multi-motion Image', + 34 => 'ID Photo', + 35 => 'Business Cards', + 36 => 'White Board', + 37 => 'Silent', + 38 => 'Pre-record Movie', + 39 => 'For YouTube', + 40 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + # (Movies have different BestShot numbers for this camera) + Condition => '$$self{Model} eq "EX-Z2300"', + Notes => 'EX-Z2300', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Premium Auto', + 3 => 'Dynamic Photo', + 4 => 'Portrait', + 5 => 'Scenery', + 6 => 'Portrait with Scenery', + 7 => 'Children', + 8 => 'Sports', + 9 => 'Candlelight Portrait', + 10 => 'Party', + 11 => 'Pet', + 12 => 'Flower', + 13 => 'Natural Green', + 14 => 'Autumn Leaves', + 15 => 'Soft Flowing Water', + 16 => 'Splashing Water', + 17 => 'Sundown', + 18 => 'Night Scene', + 19 => 'Night Scene Portrait', + 20 => 'Fireworks', + 21 => 'Food', + 22 => 'Text', + 23 => 'Collection', + 24 => 'Auction', + 25 => 'Backlight', + 26 => 'High Sensitivity', + 27 => 'Oil Painting', + 28 => 'Crayon', + 29 => 'Water Color', + 30 => 'Monochrome', + 31 => 'Retro', + 32 => 'Twilight', + 33 => 'Multi-motion Image', + 34 => 'ID Photo', + 35 => 'Business Cards', + 36 => 'White Board', + 37 => 'Silent', + 38 => 'Pre-record Movie', + 39 => 'For YouTube', + 40 => 'Voice Recording', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-Z3000"', + Notes => 'EX-Z3000', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'Portrait', + 2 => 'Scenery', + 3 => 'Portrait with Scenery', + 4 => 'Children', + 5 => 'Sports', + 6 => 'Night Scene', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-ZR100"', + Notes => 'EX-ZR100', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'Child CS', + 2 => 'Pet CS', + 3 => 'Sports CS', + 4 => 'Child High Speed Movie', + 5 => 'Pet High Speed Movie', + 6 => 'Sports High Speed Movie', + 7 => 'Multi SR Zoom', + 8 => 'Lag Correction', + 9 => 'High Speed Night Scene', + 10 => 'High Speed Night Scene and Portrait', + 11 => 'High Speed Anti Shake', + 12 => 'Portrait', + 13 => 'Scenery', + 14 => 'Portrait with Scenery', + 15 => 'Children', + 16 => 'Sports', + 17 => 'Candlelight Portrait', + 18 => 'Party', + 19 => 'Pet', + 20 => 'Flower', + 21 => 'Natural Green', + 22 => 'Autumn Leaves', + 23 => 'Soft Flowing Water', + 24 => 'Splashing Water', + 25 => 'Sundown', + 26 => 'Fireworks', + 27 => 'Food', + 28 => 'Text', + 29 => 'Collection', + 30 => 'For eBay', + 31 => 'Pre-record Movie', + 32 => 'For YouTube', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-ZR200"', + Notes => 'EX-ZR200', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'High Speed Night Scene', + 2 => 'High Speed Night Scene and Portrait', + 3 => 'High Speed Anti Shake', + 4 => 'Blurred Background', + 5 => 'Wide Shot', + 6 => 'High Speed Best Selection', + 7 => 'Lag Correction', + 8 => 'Child CS', + 9 => 'Pet CS', + 10 => 'Sports CS', + 11 => 'Child High Speed Movie', + 12 => 'Pet High Speed Movie', + 13 => 'Sports High Speed Movie', + 14 => 'Portrait', + 15 => 'Scenery', + 16 => 'Portrait with Scenery', + 17 => 'Children', + 18 => 'Sports', + 19 => 'Candlelight Portrait', + 20 => 'Party', + 21 => 'Pet', + 22 => 'Flower', + 23 => 'Natural Green', + 24 => 'Autumn Leaves', + 25 => 'Soft Flowing Water', + 26 => 'Splashing Water', + 27 => 'Sundown', + 28 => 'Fireworks', + 29 => 'Food', + 30 => 'Text', + 31 => 'Collection', + 32 => 'Auction', + 33 => 'Pre-record Movie', + 34 => 'For YouTube', + }, + },{ #http://ftp.casio.co.jp/pub/world_manual/qv/en/qv_4000/BS.pdf + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "QV-4000"', + Notes => 'QV-4000', + PrintConvColumns => 3, + PrintConv => { + 0 => 'Off', + 1 => 'People', + 2 => 'Scenery', + 3 => 'Flower', + 4 => 'Night Scene', + 5 => 'Soft Focus', + # this camera also supports 100 modes that you can apparently load + # from a CD-ROM, but I don't know how these map into these numbers + }, + },{ #Manfred, email + Name => 'BestShotMode', + Writable => 'int16u', + Condition => '$$self{Model} eq "EX-ZR300"', + Notes => 'EX-ZR300', + PrintConvColumns => 2, + PrintConv => { + 1 => 'High Speed Night Shot', + 2 => 'Blurred Background', + 3 => 'Toy Camera', + 4 => 'Soft Focus', + 5 => 'Light Tone', + 6 => 'Pop', + 7 => 'Sepia', + 8 => 'Monochrome', + 9 => 'Miniature', + 10 => 'Wide Shot', + 11 => 'High Speed Best Selection', + 12 => 'Lag Correction', + 13 => 'High Speed Night Scene', + 14 => 'High Speed Night Scene and Portrait', + 15 => 'High Speed Anti Shake', + 16 => 'Portrait', + 17 => 'Scenery', + 18 => 'Portrait with Scenery', + 19 => 'Children', + 20 => 'Sports', + 21 => 'Candlelight Portrait', + 22 => 'Party', + 23 => 'Pet', + 24 => 'Flower', + 25 => 'Natural Green', + 26 => 'Autumn Leaves', + 27 => 'Soft Flowing Water', + 28 => 'Splashing Water', + 29 => 'Sundown', + 30 => 'Fireworks', + 31 => 'Food', + 32 => 'Text', + 33 => 'Collection', + 34 => 'Auction', + 35 => 'Prerecord (Movie)', + 36 => 'For YouTube', + }, + },{ + Name => 'BestShotMode', + Writable => 'int16u', + Notes => 'other models not yet decoded', + # so we can't use a lookup as usual - PH + PrintConv => '$val ? $val : "Off"', + PrintConvInv => '$val=~/(\d+)/ ? $1 : 0', + }], + 0x3008 => { #3 + Name => 'AutoISO', + Writable => 'int16u', + PrintConv => { + 1 => 'On', + 2 => 'Off', + 7 => 'On (high sensitivity)', #PH + 8 => 'On (anti-shake)', #PH + 10 => 'High Speed', #PH (EX-FC150) + }, + }, + 0x3009 => { #6 + Name => 'AFMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Spot', + 2 => 'Multi', + 3 => 'Face Detection', + 4 => 'Tracking', # (but saw this for "Family First" mode with EX-Z77 - PH) + 5 => 'Intelligent', + }, + }, + 0x3011 => { #3 + Name => 'Sharpness', + Format => 'int16s', + Writable => 'undef', + }, + 0x3012 => { #3 + Name => 'Contrast', + Format => 'int16s', + Writable => 'undef', + }, + 0x3013 => { #3 + Name => 'Saturation', + Format => 'int16s', + Writable => 'undef', + }, + 0x3014 => { + Name => 'ISO', + Writable => 'int16u', + Priority => 0, + }, + 0x3015 => { + Name => 'ColorMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 2 => 'Black & White', #PH (EX-Z400,FH20) + 3 => 'Sepia', #PH (EX-Z400) + }, + }, + 0x3016 => { + Name => 'Enhancement', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Scenery', #PH (NC) (EX-Z77) + 3 => 'Green', #PH (EX-Z77) + 5 => 'Underwater', #PH (NC) (EX-Z77) + 9 => 'Flesh Tones', #PH (EX-Z77) + }, + }, + 0x3017 => { + Name => 'ColorFilter', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Blue', #PH (FH20,Z400) + 3 => 'Green', #PH (FH20) + 4 => 'Yellow', #PH (FH20) + 5 => 'Red', #PH (FH20,Z77) + 6 => 'Purple', #PH (FH20,Z77,Z400) + 7 => 'Pink', #PH (FH20) + }, + }, + 0x301b => { #PH + Name => 'ArtMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 8 => 'Silent Movie', + 39 => 'HDR', # (EX-ZR10) + 45 => 'Premium Auto', # (EX-2300) + 47 => 'Painting', # (EX-2300) + 49 => 'Crayon Drawing', # (EX-2300) + 51 => 'Panorama', # (EX-ZR10) + 52 => 'Art HDR', # (EX-ZR10,EX-Z3000) + 62 => 'High Speed Night Shot', # (EX-ZR20) + 64 => 'Monochrome', # (EX-ZR20) + 67 => 'Toy Camera', # (EX-ZR20) + 68 => 'Pop Art', # (EX-ZR20) + 69 => 'Light Tone', # (EX-ZR20) + }, + }, + 0x301c => { #3 + Name => 'SequenceNumber', # for continuous shooting + Writable => 'int16u', + }, + 0x301d => { #3 + Name => 'BracketSequence', + Writable => 'int16u', + Count => 2, + }, + # 0x301e - MultiBracket ? (ref 3) + 0x3020 => { #3 + Name => 'ImageStabilization', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'Best Shot', + 3 => 'Movie Anti-Shake', # (EX-V7, EX-TR100) + # (newer models write 2 numbers here - PH) + '0 0' => 'Off', #PH + '16 0' => 'Slow Shutter', #PH (EX-Z77) + '18 0' => 'Anti-Shake', #PH (EX-Z77) + '20 0' => 'High Sensitivity', #PH (EX-Z77) + # EX-Z2000 in 'Auto' mode gives '0 3' or '2 3' (ref 6) + '0 1' => 'Off (1)', #6 + '0 3' => 'CCD Shift', #PH/6 ("Camera AS" in EX-Z2000 manual) + '2 1' => 'High Sensitivity', #6 + '2 3' => 'CCD Shift + High Sensitivity', #PH (EX-FC150) + # have also seen: + # '2 0' - EX-Z15 1/60s ISO 200, EX-Z77 1/1000s ISO 50 + # '16 1' - EX-Z2300 1/125s ISO 50 + }, + }, + 0x302a => { #PH (EX-Z450) + Name => 'LightingMode', #(just guessing here) + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'High Dynamic Range', # (EX-Z77 anti-blur shot) + 5 => 'Shadow Enhance Low', #(NC) + 6 => 'Shadow Enhance High', #(NC) + }, + }, + 0x302b => { #PH (EX-Z77) + Name => 'PortraitRefiner', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => '+1', + 2 => '+2', + }, + }, + 0x3030 => { #PH (EX-Z450) + Name => 'SpecialEffectLevel', + Writable => 'int16u', + }, + 0x3031 => { #PH (EX-Z450) + Name => 'SpecialEffectSetting', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Makeup', + 2 => 'Mist Removal', + 3 => 'Vivid Landscape', + 16 => 'Art Shot', # (EX-Z2300) + }, + }, + 0x3103 => { #5 + Name => 'DriveMode', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + OTHER => sub { + # handle new values of future models + my ($val, $inv) = @_; + return $val =~ /(\d+)/ ? $1 : undef if $inv; + return "Continuous ($val fps)"; + }, + 0 => 'Single Shot', #PH (NC) + 1 => 'Continuous Shooting', # (1 fps for the EX-F1) + 2 => 'Continuous (2 fps)', + 3 => 'Continuous (3 fps)', + 4 => 'Continuous (4 fps)', + 5 => 'Continuous (5 fps)', + 6 => 'Continuous (6 fps)', + 7 => 'Continuous (7 fps)', + 10 => 'Continuous (10 fps)', + 12 => 'Continuous (12 fps)', + 15 => 'Continuous (15 fps)', + 20 => 'Continuous (20 fps)', + 30 => 'Continuous (30 fps)', + 40 => 'Continuous (40 fps)', #PH (EX-FH25) + 60 => 'Continuous (60 fps)', + 240 => 'Auto-N', + }, + }, + 0x310b => { #PH (NC) + Name => 'ArtModeParameters', + Writable => 'int8u', + Count => 3, + # "0 1 0" = Toy camera 1 + # "0 2 0" = Toy camera 1 + # "0 3 0" = Toy camera 1 + # Have also seen "0 0 0" and "2 0 0" + }, + 0x4001 => { #PH (AVI videos) + Name => 'CaptureFrameRate', + Writable => 'int16u', + Count => -1, + ValueConv => q{ + my @v=split(" ",$val); + return $val / 1000 if @v == 1; + return $v[1] ? "$v[1]-$v[0]" : ($v[0] > 10000 ? $v[0] / 1000 : $v[0]); + }, + ValueConvInv => '$val <= 60 ? $val * 1000 : int($val) . " 0"', + }, + # 0x4002 - AVI videos, related to video quality or size - PH + 0x4003 => { #PH (AVI and MOV videos) + Name => 'VideoQuality', + Writable => 'int16u', + PrintConv => { + 1 => 'Standard', + # 2 - could this be LP? + 3 => 'HD (720p)', + 4 => 'Full HD (1080p)', # (EX-ZR10, 30fps 1920x1080) + 5 => 'Low', # used in High Speed modes + }, + }, +); + +# face detection information (ref PH) (EX-H5) +%Image::ExifTool::Casio::FaceInfo1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + WRITABLE => 1, + FIRST_ENTRY => 0, + DATAMEMBER => [ 0 ], + NOTES => 'Face-detect tags extracted from models such as the EX-H5.', + 0x00 => { # (NC) + Name => 'FacesDetected', + DataMember => 'FacesDetected', + RawConv => '$$self{FacesDetected} = $val', + }, + 0x01 => { + Name => 'FaceDetectFrameSize', + Condition => '$$self{FacesDetected} >= 1', # (otherwise zeros) + Format => 'int16u[2]', + }, + 0x0d => { + Name => 'Face1Position', + Condition => '$$self{FacesDetected} >= 1', + Format => 'int16u[4]', + Notes => q{ + left, top, right and bottom of detected face in coordinates of + FaceDetectFrameSize, with increasing Y downwards + }, + }, + # decoding NOT CONFIRMED (NC) for faces 2-10! + 0x7c => { + Name => 'Face2Position', + Condition => '$$self{FacesDetected} >= 2', + Format => 'int16u[4]', + }, + 0xeb => { + Name => 'Face3Position', + Condition => '$$self{FacesDetected} >= 3', + Format => 'int16u[4]', + }, + 0x15a => { + Name => 'Face4Position', + Condition => '$$self{FacesDetected} >= 4', + Format => 'int16u[4]', + }, + 0x1c9 => { + Name => 'Face5Position', + Condition => '$$self{FacesDetected} >= 5', + Format => 'int16u[4]', + }, + 0x238 => { + Name => 'Face6Position', + Condition => '$$self{FacesDetected} >= 6', + Format => 'int16u[4]', + }, + 0x2a7 => { + Name => 'Face7Position', + Condition => '$$self{FacesDetected} >= 7', + Format => 'int16u[4]', + }, + 0x316 => { + Name => 'Face8Position', + Condition => '$$self{FacesDetected} >= 8', + Format => 'int16u[4]', + }, + 0x385 => { + Name => 'Face9Position', + Condition => '$$self{FacesDetected} >= 9', + Format => 'int16u[4]', + }, + 0x3f4 => { + Name => 'Face10Position', + Condition => '$$self{FacesDetected} >= 10', + Format => 'int16u[4]', + }, +); + +# face detection information (ref PH) (EX-ZR100) +%Image::ExifTool::Casio::FaceInfo2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + WRITABLE => 1, + FIRST_ENTRY => 0, + DATAMEMBER => [ 2 ], + NOTES => 'Face-detect tags extracted from models such as the EX-H20G and EX-ZR100.', + 0x02 => { + Name => 'FacesDetected', + DataMember => 'FacesDetected', + RawConv => '$$self{FacesDetected} = $val', + }, + 0x04 => { + Name => 'FaceDetectFrameSize', + Condition => '$$self{FacesDetected} >= 1', + Format => 'int16u[2]', + }, + 0x08 => { + Name => 'FaceOrientation', + Condition => '$$self{FacesDetected} >= 1', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + 3 => 'Rotate 180', # (NC) + # (have seen 64 here, but image had no face) + }, + Notes => 'orientation of face relative to unrotated image', + }, + # 0x0a - FaceDetectFrameSize again + # 0x11 - Face1Detected flag (1=detected) + 0x18 => { + Name => 'Face1Position', + Condition => '$$self{FacesDetected} >= 1', + Format => 'int16u[4]', + Notes => q{ + left, top, right and bottom of detected face in coordinates of + FaceDetectFrameSize, with increasing Y downwards + }, + }, + # 0x45 - Face2Detected, etc... + 0x4c => { + Name => 'Face2Position', + Condition => '$$self{FacesDetected} >= 2', + Format => 'int16u[4]', + }, + 0x80 => { + Name => 'Face3Position', + Condition => '$$self{FacesDetected} >= 3', + Format => 'int16u[4]', + }, + 0xb4 => { + Name => 'Face4Position', + Condition => '$$self{FacesDetected} >= 4', + Format => 'int16u[4]', + }, + 0xe8 => { + Name => 'Face5Position', + Condition => '$$self{FacesDetected} >= 5', + Format => 'int16u[4]', + }, + 0x11c => { + Name => 'Face6Position', + Condition => '$$self{FacesDetected} >= 6', + Format => 'int16u[4]', + }, + 0x150 => { + Name => 'Face7Position', + Condition => '$$self{FacesDetected} >= 7', + Format => 'int16u[4]', + }, + 0x184 => { + Name => 'Face8Position', + Condition => '$$self{FacesDetected} >= 8', + Format => 'int16u[4]', + }, + 0x1b8 => { + Name => 'Face9Position', + Condition => '$$self{FacesDetected} >= 9', + Format => 'int16u[4]', + }, + 0x1ec => { + Name => 'Face10Position', + Condition => '$$self{FacesDetected} >= 10', + Format => 'int16u[4]', + }, +); + +# Casio APP1 QVCI segment found in QV-7000SX images (ref PH) +%Image::ExifTool::Casio::QVCI = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => q{ + This information is found in the APP1 QVCI segment of JPEG images from the + Casio QV-7000SX. + }, + 0x2c => { + Name => 'CasioQuality', + PrintConv => { + 1 => 'Economy', + 2 => 'Normal', + 3 => 'Fine', + 4 => 'Super Fine', + }, + }, + 0x37 => { + Name => 'FocalRange', + Unknown => 1, + }, + 0x4d => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Format => 'string[20]', + Groups => { 2 => 'Time' }, + ValueConv => '$val=~tr/./:/; $val=~s/(\d+:\d+:\d+):/$1 /; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x62 => { + Name => 'ModelType', + Format => 'string[7]', + }, + 0x72 => { # could be serial number or manufacture date in form YYMMDDxx ? + Name => 'ManufactureIndex', + Format => 'string[9]', + }, + 0x7c => { + Name => 'ManufactureCode', + Format => 'string[9]', + }, +); + +# tags in Casio AVI videos (ref PH) +%Image::ExifTool::Casio::AVI = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => 'This information is found in Casio GV-10 AVI videos.', + 0 => { + Name => 'Software', # (equivalent to RIFF Software tag) + Format => 'string', + }, +); + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Casio - Casio 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 +Casio maker notes in EXIF information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Joachim Loehr for adding support for the type 2 maker notes, and +Jens Duttke and Robert Chi for decoding some tags. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Casio Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Charset.pm b/ExifTool/lib/Image/ExifTool/Charset.pm new file mode 100644 index 0000000..fdc2eeb --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset.pm @@ -0,0 +1,434 @@ +#------------------------------------------------------------------------------ +# File: Charset.pm +# +# Description: ExifTool character encoding routines +# +# Revisions: 2009/08/28 - P. Harvey created +# 2010/01/20 - P. Harvey complete re-write +# 2010/07/16 - P. Harvey added UTF-16 support +# +# Notes: Charset lookups are generated using my convertCharset script +#------------------------------------------------------------------------------ + +package Image::ExifTool::Charset; + +use strict; +use vars qw($VERSION %csType); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.11'; + +my %charsetTable; # character set tables we've loaded + +# lookup for converting Unicode to 1-byte character sets +my %unicode2byte = ( + Latin => { # pre-load Latin (cp1252) for speed + 0x20ac => 0x80, 0x0160 => 0x8a, 0x2013 => 0x96, + 0x201a => 0x82, 0x2039 => 0x8b, 0x2014 => 0x97, + 0x0192 => 0x83, 0x0152 => 0x8c, 0x02dc => 0x98, + 0x201e => 0x84, 0x017d => 0x8e, 0x2122 => 0x99, + 0x2026 => 0x85, 0x2018 => 0x91, 0x0161 => 0x9a, + 0x2020 => 0x86, 0x2019 => 0x92, 0x203a => 0x9b, + 0x2021 => 0x87, 0x201c => 0x93, 0x0153 => 0x9c, + 0x02c6 => 0x88, 0x201d => 0x94, 0x017e => 0x9e, + 0x2030 => 0x89, 0x2022 => 0x95, 0x0178 => 0x9f, + }, +); + +# bit flags for all supported character sets +# (this number must be correct because it dictates the decoding algorithm!) +# 0x001 = character set requires a translation module +# 0x002 = inverse conversion not yet supported by Recompose() +# 0x080 = some characters with codepoints in the range 0x00-0x7f are remapped +# 0x100 = 1-byte fixed-width characters +# 0x200 = 2-byte fixed-width characters +# 0x400 = 4-byte fixed-width characters +# 0x800 = 1- and 2-byte variable-width characters, or 1-byte +# fixed-width characters that map into multiple codepoints +# Note: In its public interface, ExifTool can currently only support type 0x101 +# and lower character sets because strings are only converted if they +# contain characters above 0x7f and there is no provision for specifying +# the byte order for input/output values +%csType = ( + UTF8 => 0x100, + ASCII => 0x100, # (treated like UTF8) + Arabic => 0x101, + Baltic => 0x101, + Cyrillic => 0x101, + Greek => 0x101, + Hebrew => 0x101, + Latin => 0x101, + Latin2 => 0x101, + DOSLatinUS => 0x101, + DOSLatin1 => 0x101, + DOSCyrillic => 0x101, + MacCroatian => 0x101, + MacCyrillic => 0x101, + MacGreek => 0x101, + MacIceland => 0x101, + MacLatin2 => 0x101, + MacRoman => 0x101, + MacRomanian => 0x101, + MacTurkish => 0x101, + Thai => 0x101, + Turkish => 0x101, + Vietnam => 0x101, + MacArabic => 0x103, # (directional characters not supported) + PDFDoc => 0x181, + Unicode => 0x200, # (UCS2) + UCS2 => 0x200, + UTF16 => 0x200, + Symbol => 0x201, + JIS => 0x201, + UCS4 => 0x400, + MacChineseCN => 0x803, + MacChineseTW => 0x803, + MacHebrew => 0x803, # (directional characters not supported) + MacKorean => 0x803, + MacRSymbol => 0x803, + MacThai => 0x803, + MacJapanese => 0x883, + ShiftJIS => 0x883, +); + +#------------------------------------------------------------------------------ +# Load character set module +# Inputs: 0) Module name +# Returns: Reference to lookup hash, or undef on error +sub LoadCharset($) +{ + my $charset = shift; + my $conv = $charsetTable{$charset}; + unless ($conv) { + # load translation module + my $module = "Image::ExifTool::Charset::$charset"; + no strict 'refs'; + if (%$module or eval "require $module") { + $conv = $charsetTable{$charset} = \%$module; + } + } + return $conv; +} + +#------------------------------------------------------------------------------ +# Does an array contain valid UTF-16 characters? +# Inputs: 0) array reference to list of UCS-2 values +# Returns: 0=invalid UTF-16, 1=valid UTF-16 with no surrogates, 2=valid UTF-16 with surrogates +sub IsUTF16($) +{ + local $_; + my $uni = shift; + my $surrogate; + foreach (@$uni) { + my $hiBits = ($_ & 0xfc00); + if ($hiBits == 0xfc00) { + # check for invalid values in UTF-16 + return 0 if $_ == 0xffff or $_ == 0xfffe or ($_ >= 0xfdd0 and $_ <= 0xfdef); + } elsif ($surrogate) { + return 0 if $hiBits != 0xdc00; + $surrogate = 0; + } else { + return 0 if $hiBits == 0xdc00; + $surrogate = 1 if $hiBits == 0xd800; + } + } + return 1 if not defined $surrogate; + return 2 unless $surrogate; + return 0; +} + +#------------------------------------------------------------------------------ +# Decompose string with specified encoding into an array of integer code points +# Inputs: 0) ExifTool object ref (or undef), 1) string, 2) character set name, +# 3) optional byte order ('II','MM','Unknown' or undef to use ExifTool ordering) +# Returns: Reference to array of Unicode values +# Notes: Accepts any type of character set +# - byte order only used for fixed-width 2-byte and 4-byte character sets +# - byte order mark observed and then removed with UCS2 and UCS4 +# - no warnings are issued if ExifTool object is not provided +# - sets ExifTool WrongByteOrder flag if byte order is Unknown and current order is wrong +sub Decompose($$$;$) +{ + local $_; + my ($et, $val, $charset) = @_; # ($byteOrder assigned later if required) + my $type = $csType{$charset}; + my (@uni, $conv); + + if ($type & 0x001) { + $conv = LoadCharset($charset); + unless ($conv) { + # (shouldn't happen) + $et->Warn("Invalid character set $charset") if $et; + return \@uni; # error! + } + } elsif ($type == 0x100) { + # convert ASCII and UTF8 (treat ASCII as UTF8) + if ($] < 5.006001) { + # do it ourself + @uni = Image::ExifTool::UnpackUTF8($val); + } else { + # handle warnings from malformed UTF-8 + undef $Image::ExifTool::evalWarning; + local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning; + # (somehow the meaning of "U0" was reversed in Perl 5.10.0!) + @uni = unpack($] < 5.010000 ? 'U0U*' : 'C0U*', $val); + # issue warning if we had errors + if ($Image::ExifTool::evalWarning and $et and not $$et{WarnBadUTF8}) { + $et->Warn('Malformed UTF-8 character(s)'); + $$et{WarnBadUTF8} = 1; + } + } + return \@uni; # all done! + } + if ($type & 0x100) { # 1-byte fixed-width characters + @uni = unpack('C*', $val); + foreach (@uni) { + $_ = $$conv{$_} if defined $$conv{$_}; + } + } elsif ($type & 0x600) { # 2-byte or 4-byte fixed-width characters + my $unknown; + my $byteOrder = $_[3]; + if (not $byteOrder) { + $byteOrder = GetByteOrder(); + } elsif ($byteOrder eq 'Unknown') { + $byteOrder = GetByteOrder(); + $unknown = 1; + } + my $fmt = $byteOrder eq 'MM' ? 'n*' : 'v*'; + if ($type & 0x400) { # 4-byte + $fmt = uc $fmt; # unpack as 'N*' or 'V*' + # honour BOM if it exists + $val =~ s/^(\0\0\xfe\xff|\xff\xfe\0\0)// and $fmt = $1 eq "\0\0\xfe\xff" ? 'N*' : 'V*'; + undef $unknown; # (byte order logic applies to 2-byte only) + } elsif ($val =~ s/^(\xfe\xff|\xff\xfe)//) { + $fmt = $1 eq "\xfe\xff" ? 'n*' : 'v*'; + undef $unknown; + } + # convert from UCS2 or UCS4 + @uni = unpack($fmt, $val); + + if (not $conv) { + # no translation necessary + if ($unknown) { + # check the byte order + my (%bh, %bl); + my ($zh, $zl) = (0, 0); + foreach (@uni) { + $bh{$_ >> 8} = 1; + $bl{$_ & 0xff} = 1; + ++$zh unless $_ & 0xff00; + ++$zl unless $_ & 0x00ff; + } + # count the number of unique values in the hi and lo bytes + my ($bh, $bl) = (scalar(keys %bh), scalar(keys %bl)); + # the byte with the greater number of unique values should be + # the low-order byte, otherwise the byte which is zero more + # often is likely the high-order byte + if ($bh > $bl or ($bh == $bl and $zl > $zh)) { + # we guessed wrong, so decode using the other byte order + $fmt =~ tr/nvNV/vnVN/; + @uni = unpack($fmt, $val); + $$et{WrongByteOrder} = 1; + } + } + # handle surrogate pairs of UTF-16 + if ($charset eq 'UTF16') { + my $i; + for ($i=0; $i<$#uni; ++$i) { + next unless ($uni[$i] & 0xfc00) == 0xd800 and + ($uni[$i+1] & 0xfc00) == 0xdc00; + my $cp = 0x10000 + (($uni[$i] & 0x3ff) << 10) + ($uni[$i+1] & 0x3ff); + splice(@uni, $i, 2, $cp); + } + } + } elsif ($unknown) { + # count encoding errors as we do the translation + my $e1 = 0; + foreach (@uni) { + defined $$conv{$_} and $_ = $$conv{$_}, next; + ++$e1; + } + # try the other byte order if we had any errors + if ($e1) { + $fmt = $byteOrder eq 'MM' ? 'v*' : 'n*'; #(reversed) + my @try = unpack($fmt, $val); + my $e2 = 0; + foreach (@try) { + defined $$conv{$_} and $_ = $$conv{$_}, next; + ++$e2; + } + # use this byte order if there are fewer errors + if ($e2 < $e1) { + $$et{WrongByteOrder} = 1; + return \@try; + } + } + } else { + # translate any characters found in the lookup + foreach (@uni) { + $_ = $$conv{$_} if defined $$conv{$_}; + } + } + } else { # variable-width characters + # unpack into bytes + my @bytes = unpack('C*', $val); + while (@bytes) { + my $ch = shift @bytes; + my $cv = $$conv{$ch}; + # pass straight through if no translation + $cv or push(@uni, $ch), next; + # byte translates into single Unicode character + ref $cv or push(@uni, $cv), next; + # byte maps into multiple Unicode characters + ref $cv eq 'ARRAY' and push(@uni, @$cv), next; + # handle 2-byte character codes + $ch = shift @bytes; + if (defined $ch) { + if ($$cv{$ch}) { + $cv = $$cv{$ch}; + ref $cv or push(@uni, $cv), next; + push @uni, @$cv; # multiple Unicode characters + } else { + push @uni, ord('?'); # encoding error + unshift @bytes, $ch; + } + } else { + push @uni, ord('?'); # encoding error + } + } + } + return \@uni; +} + +#------------------------------------------------------------------------------ +# Convert array of code point integers into a string with specified encoding +# Inputs: 0) ExifTool ref (or undef), 1) unicode character array ref, +# 2) character set (note: not all types are supported) +# 3) byte order ('MM' or 'II', multi-byte sets only, defaults to current byte order) +# Returns: converted string (truncated at null character if it exists), empty on error +# Notes: converts elements of input character array to new code points +# - ExifTool ref may be undef provided $charset is defined +sub Recompose($$;$$) +{ + local $_; + my ($et, $uni, $charset) = @_; # ($byteOrder assigned later if required) + my ($outVal, $conv, $inv); + $charset or $charset = $$et{OPTIONS}{Charset}; + my $csType = $csType{$charset}; + if ($csType == 0x100) { # UTF8 (also treat ASCII as UTF8) + if ($] >= 5.006001) { + # let Perl do it + $outVal = pack('C0U*', @$uni); + } else { + # do it ourself + $outVal = Image::ExifTool::PackUTF8(@$uni); + } + $outVal =~ s/\0.*//s; # truncate at null terminator + return $outVal; + } + # get references to forward and inverse lookup tables + if ($csType & 0x801) { + $conv = LoadCharset($charset); + unless ($conv) { + $et->Warn("Missing charset $charset") if $et; + return ''; + } + $inv = $unicode2byte{$charset}; + # generate inverse lookup if necessary + unless ($inv) { + if (not $csType or $csType & 0x802) { + $et->Warn("Invalid destination charset $charset") if $et; + return ''; + } + # prepare table to convert from Unicode to 1-byte characters + my ($char, %inv); + foreach $char (keys %$conv) { + $inv{$$conv{$char}} = $char; + } + $inv = $unicode2byte{$charset} = \%inv; + } + } + if ($csType & 0x100) { # 1-byte fixed-width + # convert to specified character set + foreach (@$uni) { + next if $_ < 0x80; + $$inv{$_} and $_ = $$inv{$_}, next; + # our tables omit 1-byte characters with the same values as Unicode, + # so pass them straight through after making sure there isn't a + # different character with this byte value + next if $_ < 0x100 and not $$conv{$_}; + $_ = ord('?'); # set invalid characters to '?' + if ($et and not $$et{EncodingError}) { + $et->Warn("Some character(s) could not be encoded in $charset"); + $$et{EncodingError} = 1; + } + } + # repack as an 8-bit string and truncate at null + $outVal = pack('C*', @$uni); + $outVal =~ s/\0.*//s; + } else { # 2-byte and 4-byte fixed-width + # convert if required + if ($inv) { + $$inv{$_} and $_ = $$inv{$_} foreach @$uni; + } + # generate surrogate pairs of UTF-16 + if ($charset eq 'UTF16') { + my $i; + for ($i=0; $i<@$uni; ++$i) { + next unless $$uni[$i] >= 0x10000 and $$uni[$i] < 0x10ffff; + my $t = $$uni[$i] - 0x10000; + my $w1 = 0xd800 + (($t >> 10) & 0x3ff); + my $w2 = 0xdc00 + ($t & 0x3ff); + splice(@$uni, $i, 1, $w1, $w2); + ++$i; # skip surrogate pair + } + } + # pack as 2- or 4-byte integer in specified byte order + my $byteOrder = $_[3] || GetByteOrder(); + my $fmt = $byteOrder eq 'MM' ? 'n*' : 'v*'; + $fmt = uc($fmt) if $csType & 0x400; + $outVal = pack($fmt, @$uni); + } + return $outVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Charset - ExifTool character encoding routines + +=head1 SYNOPSIS + +This module is required by Image::ExifTool. + +=head1 DESCRIPTION + +This module contains routines used by ExifTool to translate special +character sets. Currently, the following character sets are supported: + + UTF8, UTF16, UCS2, UCS4, Arabic, Baltic, Cyrillic, Greek, Hebrew, JIS, + Latin, Latin2, DOSLatinUS, DOSLatin1, DOSCyrillic, MacArabic, + MacChineseCN, MacChineseTW, MacCroatian, MacCyrillic, MacGreek, MacHebrew, + MacIceland, MacJapanese, MacKorean, MacLatin2, MacRSymbol, MacRoman, + MacRomanian, MacThai, MacTurkish, PDFDoc, RSymbol, ShiftJIS, Symbol, Thai, + Turkish, Vietnam + +However, only some of these character sets are available to the user via +ExifTool options -- the multi-byte character sets are used only internally +when decoding certain types of information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Charset/Arabic.pm b/ExifTool/lib/Image/ExifTool/Charset/Arabic.pm new file mode 100644 index 0000000..513492f --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Arabic.pm @@ -0,0 +1,39 @@ +#------------------------------------------------------------------------------ +# File: Arabic.pm +# +# Description: cp1256 to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1256.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Arabic = ( + 0x80 => 0x20ac, 0x81 => 0x067e, 0x82 => 0x201a, 0x83 => 0x0192, + 0x84 => 0x201e, 0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, + 0x88 => 0x02c6, 0x89 => 0x2030, 0x8a => 0x0679, 0x8b => 0x2039, + 0x8c => 0x0152, 0x8d => 0x0686, 0x8e => 0x0698, 0x8f => 0x0688, + 0x90 => 0x06af, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c, + 0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014, + 0x98 => 0x06a9, 0x99 => 0x2122, 0x9a => 0x0691, 0x9b => 0x203a, + 0x9c => 0x0153, 0x9d => 0x200c, 0x9e => 0x200d, 0x9f => 0x06ba, + 0xa1 => 0x060c, 0xaa => 0x06be, 0xba => 0x061b, 0xbf => 0x061f, + 0xc0 => 0x06c1, 0xc1 => 0x0621, 0xc2 => 0x0622, 0xc3 => 0x0623, + 0xc4 => 0x0624, 0xc5 => 0x0625, 0xc6 => 0x0626, 0xc7 => 0x0627, + 0xc8 => 0x0628, 0xc9 => 0x0629, 0xca => 0x062a, 0xcb => 0x062b, + 0xcc => 0x062c, 0xcd => 0x062d, 0xce => 0x062e, 0xcf => 0x062f, + 0xd0 => 0x0630, 0xd1 => 0x0631, 0xd2 => 0x0632, 0xd3 => 0x0633, + 0xd4 => 0x0634, 0xd5 => 0x0635, 0xd6 => 0x0636, 0xd8 => 0x0637, + 0xd9 => 0x0638, 0xda => 0x0639, 0xdb => 0x063a, 0xdc => 0x0640, + 0xdd => 0x0641, 0xde => 0x0642, 0xdf => 0x0643, 0xe1 => 0x0644, + 0xe3 => 0x0645, 0xe4 => 0x0646, 0xe5 => 0x0647, 0xe6 => 0x0648, + 0xec => 0x0649, 0xed => 0x064a, 0xf0 => 0x064b, 0xf1 => 0x064c, + 0xf2 => 0x064d, 0xf3 => 0x064e, 0xf5 => 0x064f, 0xf6 => 0x0650, + 0xf8 => 0x0651, 0xfa => 0x0652, 0xfd => 0x200e, 0xfe => 0x200f, + 0xff => 0x06d2, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/Baltic.pm b/ExifTool/lib/Image/ExifTool/Charset/Baltic.pm new file mode 100644 index 0000000..4ed0a10 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Baltic.pm @@ -0,0 +1,35 @@ +#------------------------------------------------------------------------------ +# File: Baltic.pm +# +# Description: cp1257 to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1257.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Baltic = ( + 0x80 => 0x20ac, 0x82 => 0x201a, 0x84 => 0x201e, 0x85 => 0x2026, + 0x86 => 0x2020, 0x87 => 0x2021, 0x89 => 0x2030, 0x8b => 0x2039, 0x8d => 0xa8, + 0x8e => 0x02c7, 0x8f => 0xb8, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c, + 0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014, + 0x99 => 0x2122, 0x9b => 0x203a, 0x9d => 0xaf, 0x9e => 0x02db, 0xa8 => 0xd8, + 0xaa => 0x0156, 0xaf => 0xc6, 0xb8 => 0xf8, 0xba => 0x0157, 0xbf => 0xe6, + 0xc0 => 0x0104, 0xc1 => 0x012e, 0xc2 => 0x0100, 0xc3 => 0x0106, + 0xc6 => 0x0118, 0xc7 => 0x0112, 0xc8 => 0x010c, 0xca => 0x0179, + 0xcb => 0x0116, 0xcc => 0x0122, 0xcd => 0x0136, 0xce => 0x012a, + 0xcf => 0x013b, 0xd0 => 0x0160, 0xd1 => 0x0143, 0xd2 => 0x0145, + 0xd4 => 0x014c, 0xd8 => 0x0172, 0xd9 => 0x0141, 0xda => 0x015a, + 0xdb => 0x016a, 0xdd => 0x017b, 0xde => 0x017d, 0xe0 => 0x0105, + 0xe1 => 0x012f, 0xe2 => 0x0101, 0xe3 => 0x0107, 0xe6 => 0x0119, + 0xe7 => 0x0113, 0xe8 => 0x010d, 0xea => 0x017a, 0xeb => 0x0117, + 0xec => 0x0123, 0xed => 0x0137, 0xee => 0x012b, 0xef => 0x013c, + 0xf0 => 0x0161, 0xf1 => 0x0144, 0xf2 => 0x0146, 0xf4 => 0x014d, + 0xf8 => 0x0173, 0xf9 => 0x0142, 0xfa => 0x015b, 0xfb => 0x016b, + 0xfd => 0x017c, 0xfe => 0x017e, 0xff => 0x02d9, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/Cyrillic.pm b/ExifTool/lib/Image/ExifTool/Charset/Cyrillic.pm new file mode 100644 index 0000000..f7972d6 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Cyrillic.pm @@ -0,0 +1,45 @@ +#------------------------------------------------------------------------------ +# File: Cyrillic.pm +# +# Description: cp1251 to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Cyrillic = ( + 0x80 => 0x0402, 0x81 => 0x0403, 0x82 => 0x201a, 0x83 => 0x0453, + 0x84 => 0x201e, 0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, + 0x88 => 0x20ac, 0x89 => 0x2030, 0x8a => 0x0409, 0x8b => 0x2039, + 0x8c => 0x040a, 0x8d => 0x040c, 0x8e => 0x040b, 0x8f => 0x040f, + 0x90 => 0x0452, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c, + 0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014, + 0x99 => 0x2122, 0x9a => 0x0459, 0x9b => 0x203a, 0x9c => 0x045a, + 0x9d => 0x045c, 0x9e => 0x045b, 0x9f => 0x045f, 0xa1 => 0x040e, + 0xa2 => 0x045e, 0xa3 => 0x0408, 0xa5 => 0x0490, 0xa8 => 0x0401, + 0xaa => 0x0404, 0xaf => 0x0407, 0xb2 => 0x0406, 0xb3 => 0x0456, + 0xb4 => 0x0491, 0xb8 => 0x0451, 0xb9 => 0x2116, 0xba => 0x0454, + 0xbc => 0x0458, 0xbd => 0x0405, 0xbe => 0x0455, 0xbf => 0x0457, + 0xc0 => 0x0410, 0xc1 => 0x0411, 0xc2 => 0x0412, 0xc3 => 0x0413, + 0xc4 => 0x0414, 0xc5 => 0x0415, 0xc6 => 0x0416, 0xc7 => 0x0417, + 0xc8 => 0x0418, 0xc9 => 0x0419, 0xca => 0x041a, 0xcb => 0x041b, + 0xcc => 0x041c, 0xcd => 0x041d, 0xce => 0x041e, 0xcf => 0x041f, + 0xd0 => 0x0420, 0xd1 => 0x0421, 0xd2 => 0x0422, 0xd3 => 0x0423, + 0xd4 => 0x0424, 0xd5 => 0x0425, 0xd6 => 0x0426, 0xd7 => 0x0427, + 0xd8 => 0x0428, 0xd9 => 0x0429, 0xda => 0x042a, 0xdb => 0x042b, + 0xdc => 0x042c, 0xdd => 0x042d, 0xde => 0x042e, 0xdf => 0x042f, + 0xe0 => 0x0430, 0xe1 => 0x0431, 0xe2 => 0x0432, 0xe3 => 0x0433, + 0xe4 => 0x0434, 0xe5 => 0x0435, 0xe6 => 0x0436, 0xe7 => 0x0437, + 0xe8 => 0x0438, 0xe9 => 0x0439, 0xea => 0x043a, 0xeb => 0x043b, + 0xec => 0x043c, 0xed => 0x043d, 0xee => 0x043e, 0xef => 0x043f, + 0xf0 => 0x0440, 0xf1 => 0x0441, 0xf2 => 0x0442, 0xf3 => 0x0443, + 0xf4 => 0x0444, 0xf5 => 0x0445, 0xf6 => 0x0446, 0xf7 => 0x0447, + 0xf8 => 0x0448, 0xf9 => 0x0449, 0xfa => 0x044a, 0xfb => 0x044b, + 0xfc => 0x044c, 0xfd => 0x044d, 0xfe => 0x044e, 0xff => 0x044f, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/DOSCyrillic.pm b/ExifTool/lib/Image/ExifTool/Charset/DOSCyrillic.pm new file mode 100644 index 0000000..1ecc66a --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/DOSCyrillic.pm @@ -0,0 +1,49 @@ +#------------------------------------------------------------------------------ +# File: DOSCyrillic.pm +# +# Description: cp866 to Unicode +# +# Revisions: 2020/03/23 - P. Harvey created +# +# References: 1) https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP866.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::DOSCyrillic = ( + 0x80 => 0x0410, 0x81 => 0x0411, 0x82 => 0x0412, 0x83 => 0x0413, + 0x84 => 0x0414, 0x85 => 0x0415, 0x86 => 0x0416, 0x87 => 0x0417, + 0x88 => 0x0418, 0x89 => 0x0419, 0x8a => 0x041a, 0x8b => 0x041b, + 0x8c => 0x041c, 0x8d => 0x041d, 0x8e => 0x041e, 0x8f => 0x041f, + 0x90 => 0x0420, 0x91 => 0x0421, 0x92 => 0x0422, 0x93 => 0x0423, + 0x94 => 0x0424, 0x95 => 0x0425, 0x96 => 0x0426, 0x97 => 0x0427, + 0x98 => 0x0428, 0x99 => 0x0429, 0x9a => 0x042a, 0x9b => 0x042b, + 0x9c => 0x042c, 0x9d => 0x042d, 0x9e => 0x042e, 0x9f => 0x042f, + 0xa0 => 0x0430, 0xa1 => 0x0431, 0xa2 => 0x0432, 0xa3 => 0x0433, + 0xa4 => 0x0434, 0xa5 => 0x0435, 0xa6 => 0x0436, 0xa7 => 0x0437, + 0xa8 => 0x0438, 0xa9 => 0x0439, 0xaa => 0x043a, 0xab => 0x043b, + 0xac => 0x043c, 0xad => 0x043d, 0xae => 0x043e, 0xaf => 0x043f, + 0xb0 => 0x2591, 0xb1 => 0x2592, 0xb2 => 0x2593, 0xb3 => 0x2502, + 0xb4 => 0x2524, 0xb5 => 0x2561, 0xb6 => 0x2562, 0xb7 => 0x2556, + 0xb8 => 0x2555, 0xb9 => 0x2563, 0xba => 0x2551, 0xbb => 0x2557, + 0xbc => 0x255d, 0xbd => 0x255c, 0xbe => 0x255b, 0xbf => 0x2510, + 0xc0 => 0x2514, 0xc1 => 0x2534, 0xc2 => 0x252c, 0xc3 => 0x251c, + 0xc4 => 0x2500, 0xc5 => 0x253c, 0xc6 => 0x255e, 0xc7 => 0x255f, + 0xc8 => 0x255a, 0xc9 => 0x2554, 0xca => 0x2569, 0xcb => 0x2566, + 0xcc => 0x2560, 0xcd => 0x2550, 0xce => 0x256c, 0xcf => 0x2567, + 0xd0 => 0x2568, 0xd1 => 0x2564, 0xd2 => 0x2565, 0xd3 => 0x2559, + 0xd4 => 0x2558, 0xd5 => 0x2552, 0xd6 => 0x2553, 0xd7 => 0x256b, + 0xd8 => 0x256a, 0xd9 => 0x2518, 0xda => 0x250c, 0xdb => 0x2588, + 0xdc => 0x2584, 0xdd => 0x258c, 0xde => 0x2590, 0xdf => 0x2580, + 0xe0 => 0x0440, 0xe1 => 0x0441, 0xe2 => 0x0442, 0xe3 => 0x0443, + 0xe4 => 0x0444, 0xe5 => 0x0445, 0xe6 => 0x0446, 0xe7 => 0x0447, + 0xe8 => 0x0448, 0xe9 => 0x0449, 0xea => 0x044a, 0xeb => 0x044b, + 0xec => 0x044c, 0xed => 0x044d, 0xee => 0x044e, 0xef => 0x044f, + 0xf0 => 0x0401, 0xf1 => 0x0451, 0xf2 => 0x0404, 0xf3 => 0x0454, + 0xf4 => 0x0407, 0xf5 => 0x0457, 0xf6 => 0x040e, 0xf7 => 0x045e, + 0xf8 => 0x00b0, 0xf9 => 0x2219, 0xfa => 0x00b7, 0xfb => 0x221a, + 0xfc => 0x2116, 0xfd => 0x00a4, 0xfe => 0x25a0, 0xff => 0x00a0, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/DOSLatin1.pm b/ExifTool/lib/Image/ExifTool/Charset/DOSLatin1.pm new file mode 100644 index 0000000..2e3fd15 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/DOSLatin1.pm @@ -0,0 +1,49 @@ +#------------------------------------------------------------------------------ +# File: DOSLatin1.pm +# +# Description: cp850 to Unicode +# +# Revisions: 2017/10/31- P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP850.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::DOSLatin1 = ( + 0x80 => 0x00c7, 0x81 => 0x00fc, 0x82 => 0x00e9, 0x83 => 0x00e2, + 0x84 => 0x00e4, 0x85 => 0x00e0, 0x86 => 0x00e5, 0x87 => 0x00e7, + 0x88 => 0x00ea, 0x89 => 0x00eb, 0x8a => 0x00e8, 0x8b => 0x00ef, + 0x8c => 0x00ee, 0x8d => 0x00ec, 0x8e => 0x00c4, 0x8f => 0x00c5, + 0x90 => 0x00c9, 0x91 => 0x00e6, 0x92 => 0x00c6, 0x93 => 0x00f4, + 0x94 => 0x00f6, 0x95 => 0x00f2, 0x96 => 0x00fb, 0x97 => 0x00f9, + 0x98 => 0x00ff, 0x99 => 0x00d6, 0x9a => 0x00dc, 0x9b => 0x00f8, + 0x9c => 0x00a3, 0x9d => 0x00d8, 0x9e => 0x00d7, 0x9f => 0x0192, + 0xa0 => 0x00e1, 0xa1 => 0x00ed, 0xa2 => 0x00f3, 0xa3 => 0x00fa, + 0xa4 => 0x00f1, 0xa5 => 0x00d1, 0xa6 => 0x00aa, 0xa7 => 0x00ba, + 0xa8 => 0x00bf, 0xa9 => 0x00ae, 0xaa => 0x00ac, 0xab => 0x00bd, + 0xac => 0x00bc, 0xad => 0x00a1, 0xae => 0x00ab, 0xaf => 0x00bb, + 0xb0 => 0x2591, 0xb1 => 0x2592, 0xb2 => 0x2593, 0xb3 => 0x2502, + 0xb4 => 0x2524, 0xb5 => 0x00c1, 0xb6 => 0x00c2, 0xb7 => 0x00c0, + 0xb8 => 0x00a9, 0xb9 => 0x2563, 0xba => 0x2551, 0xbb => 0x2557, + 0xbc => 0x255d, 0xbd => 0x00a2, 0xbe => 0x00a5, 0xbf => 0x2510, + 0xc0 => 0x2514, 0xc1 => 0x2534, 0xc2 => 0x252c, 0xc3 => 0x251c, + 0xc4 => 0x2500, 0xc5 => 0x253c, 0xc6 => 0x00e3, 0xc7 => 0x00c3, + 0xc8 => 0x255a, 0xc9 => 0x2554, 0xca => 0x2569, 0xcb => 0x2566, + 0xcc => 0x2560, 0xcd => 0x2550, 0xce => 0x256c, 0xcf => 0x00a4, + 0xd0 => 0x00f0, 0xd1 => 0x00d0, 0xd2 => 0x00ca, 0xd3 => 0x00cb, + 0xd4 => 0x00c8, 0xd5 => 0x0131, 0xd6 => 0x00cd, 0xd7 => 0x00ce, + 0xd8 => 0x00cf, 0xd9 => 0x2518, 0xda => 0x250c, 0xdb => 0x2588, + 0xdc => 0x2584, 0xdd => 0x00a6, 0xde => 0x00cc, 0xdf => 0x2580, + 0xe0 => 0x00d3, 0xe1 => 0x00df, 0xe2 => 0x00d4, 0xe3 => 0x00d2, + 0xe4 => 0x00f5, 0xe5 => 0x00d5, 0xe6 => 0x00b5, 0xe7 => 0x00fe, + 0xe8 => 0x00de, 0xe9 => 0x00da, 0xea => 0x00db, 0xeb => 0x00d9, + 0xec => 0x00fd, 0xed => 0x00dd, 0xee => 0x00af, 0xef => 0x00b4, + 0xf0 => 0x00ad, 0xf1 => 0x00b1, 0xf2 => 0x2017, 0xf3 => 0x00be, + 0xf4 => 0x00b6, 0xf5 => 0x00a7, 0xf6 => 0x00f7, 0xf7 => 0x00b8, + 0xf8 => 0x00b0, 0xf9 => 0x00a8, 0xfa => 0x00b7, 0xfb => 0x00b9, + 0xfc => 0x00b3, 0xfd => 0x00b2, 0xfe => 0x25a0, 0xff => 0x00a0, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/DOSLatinUS.pm b/ExifTool/lib/Image/ExifTool/Charset/DOSLatinUS.pm new file mode 100644 index 0000000..7f76823 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/DOSLatinUS.pm @@ -0,0 +1,49 @@ +#------------------------------------------------------------------------------ +# File: DOSLatinUS.pm +# +# Description: cp437 to Unicode +# +# Revisions: 2017/10/31- P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP437.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::DOSLatinUS = ( + 0x80 => 0x00c7, 0x81 => 0x00fc, 0x82 => 0x00e9, 0x83 => 0x00e2, + 0x84 => 0x00e4, 0x85 => 0x00e0, 0x86 => 0x00e5, 0x87 => 0x00e7, + 0x88 => 0x00ea, 0x89 => 0x00eb, 0x8a => 0x00e8, 0x8b => 0x00ef, + 0x8c => 0x00ee, 0x8d => 0x00ec, 0x8e => 0x00c4, 0x8f => 0x00c5, + 0x90 => 0x00c9, 0x91 => 0x00e6, 0x92 => 0x00c6, 0x93 => 0x00f4, + 0x94 => 0x00f6, 0x95 => 0x00f2, 0x96 => 0x00fb, 0x97 => 0x00f9, + 0x98 => 0x00ff, 0x99 => 0x00d6, 0x9a => 0x00dc, 0x9b => 0x00a2, + 0x9c => 0x00a3, 0x9d => 0x00a5, 0x9e => 0x20a7, 0x9f => 0x0192, + 0xa0 => 0x00e1, 0xa1 => 0x00ed, 0xa2 => 0x00f3, 0xa3 => 0x00fa, + 0xa4 => 0x00f1, 0xa5 => 0x00d1, 0xa6 => 0x00aa, 0xa7 => 0x00ba, + 0xa8 => 0x00bf, 0xa9 => 0x2310, 0xaa => 0x00ac, 0xab => 0x00bd, + 0xac => 0x00bc, 0xad => 0x00a1, 0xae => 0x00ab, 0xaf => 0x00bb, + 0xb0 => 0x2591, 0xb1 => 0x2592, 0xb2 => 0x2593, 0xb3 => 0x2502, + 0xb4 => 0x2524, 0xb5 => 0x2561, 0xb6 => 0x2562, 0xb7 => 0x2556, + 0xb8 => 0x2555, 0xb9 => 0x2563, 0xba => 0x2551, 0xbb => 0x2557, + 0xbc => 0x255d, 0xbd => 0x255c, 0xbe => 0x255b, 0xbf => 0x2510, + 0xc0 => 0x2514, 0xc1 => 0x2534, 0xc2 => 0x252c, 0xc3 => 0x251c, + 0xc4 => 0x2500, 0xc5 => 0x253c, 0xc6 => 0x255e, 0xc7 => 0x255f, + 0xc8 => 0x255a, 0xc9 => 0x2554, 0xca => 0x2569, 0xcb => 0x2566, + 0xcc => 0x2560, 0xcd => 0x2550, 0xce => 0x256c, 0xcf => 0x2567, + 0xd0 => 0x2568, 0xd1 => 0x2564, 0xd2 => 0x2565, 0xd3 => 0x2559, + 0xd4 => 0x2558, 0xd5 => 0x2552, 0xd6 => 0x2553, 0xd7 => 0x256b, + 0xd8 => 0x256a, 0xd9 => 0x2518, 0xda => 0x250c, 0xdb => 0x2588, + 0xdc => 0x2584, 0xdd => 0x258c, 0xde => 0x2590, 0xdf => 0x2580, + 0xe0 => 0x03b1, 0xe1 => 0x00df, 0xe2 => 0x0393, 0xe3 => 0x03c0, + 0xe4 => 0x03a3, 0xe5 => 0x03c3, 0xe6 => 0x00b5, 0xe7 => 0x03c4, + 0xe8 => 0x03a6, 0xe9 => 0x0398, 0xea => 0x03a9, 0xeb => 0x03b4, + 0xec => 0x221e, 0xed => 0x03c6, 0xee => 0x03b5, 0xef => 0x2229, + 0xf0 => 0x2261, 0xf1 => 0x00b1, 0xf2 => 0x2265, 0xf3 => 0x2264, + 0xf4 => 0x2320, 0xf5 => 0x2321, 0xf6 => 0x00f7, 0xf7 => 0x2248, + 0xf8 => 0x00b0, 0xf9 => 0x2219, 0xfa => 0x00b7, 0xfb => 0x221a, + 0xfc => 0x207f, 0xfd => 0x00b2, 0xfe => 0x25a0, 0xff => 0x00a0, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/Greek.pm b/ExifTool/lib/Image/ExifTool/Charset/Greek.pm new file mode 100644 index 0000000..a1bcb8e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Greek.pm @@ -0,0 +1,40 @@ +#------------------------------------------------------------------------------ +# File: Greek.pm +# +# Description: cp1253 to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1253.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Greek = ( + 0x80 => 0x20ac, 0x82 => 0x201a, 0x83 => 0x0192, 0x84 => 0x201e, + 0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x89 => 0x2030, + 0x8b => 0x2039, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c, + 0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014, + 0x99 => 0x2122, 0x9b => 0x203a, 0xa1 => 0x0385, 0xa2 => 0x0386, + 0xaf => 0x2015, 0xb4 => 0x0384, 0xb8 => 0x0388, 0xb9 => 0x0389, + 0xba => 0x038a, 0xbc => 0x038c, 0xbe => 0x038e, 0xbf => 0x038f, + 0xc0 => 0x0390, 0xc1 => 0x0391, 0xc2 => 0x0392, 0xc3 => 0x0393, + 0xc4 => 0x0394, 0xc5 => 0x0395, 0xc6 => 0x0396, 0xc7 => 0x0397, + 0xc8 => 0x0398, 0xc9 => 0x0399, 0xca => 0x039a, 0xcb => 0x039b, + 0xcc => 0x039c, 0xcd => 0x039d, 0xce => 0x039e, 0xcf => 0x039f, + 0xd0 => 0x03a0, 0xd1 => 0x03a1, 0xd3 => 0x03a3, 0xd4 => 0x03a4, + 0xd5 => 0x03a5, 0xd6 => 0x03a6, 0xd7 => 0x03a7, 0xd8 => 0x03a8, + 0xd9 => 0x03a9, 0xda => 0x03aa, 0xdb => 0x03ab, 0xdc => 0x03ac, + 0xdd => 0x03ad, 0xde => 0x03ae, 0xdf => 0x03af, 0xe0 => 0x03b0, + 0xe1 => 0x03b1, 0xe2 => 0x03b2, 0xe3 => 0x03b3, 0xe4 => 0x03b4, + 0xe5 => 0x03b5, 0xe6 => 0x03b6, 0xe7 => 0x03b7, 0xe8 => 0x03b8, + 0xe9 => 0x03b9, 0xea => 0x03ba, 0xeb => 0x03bb, 0xec => 0x03bc, + 0xed => 0x03bd, 0xee => 0x03be, 0xef => 0x03bf, 0xf0 => 0x03c0, + 0xf1 => 0x03c1, 0xf2 => 0x03c2, 0xf3 => 0x03c3, 0xf4 => 0x03c4, + 0xf5 => 0x03c5, 0xf6 => 0x03c6, 0xf7 => 0x03c7, 0xf8 => 0x03c8, + 0xf9 => 0x03c9, 0xfa => 0x03ca, 0xfb => 0x03cb, 0xfc => 0x03cc, + 0xfd => 0x03cd, 0xfe => 0x03ce, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/Hebrew.pm b/ExifTool/lib/Image/ExifTool/Charset/Hebrew.pm new file mode 100644 index 0000000..6cebced --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Hebrew.pm @@ -0,0 +1,36 @@ +#------------------------------------------------------------------------------ +# File: Hebrew.pm +# +# Description: cp1255 to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1255.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Hebrew = ( + 0x80 => 0x20ac, 0x82 => 0x201a, 0x83 => 0x0192, 0x84 => 0x201e, + 0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02c6, + 0x89 => 0x2030, 0x8b => 0x2039, 0x91 => 0x2018, 0x92 => 0x2019, + 0x93 => 0x201c, 0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, + 0x97 => 0x2014, 0x98 => 0x02dc, 0x99 => 0x2122, 0x9b => 0x203a, + 0xa4 => 0x20aa, 0xaa => 0xd7, 0xba => 0xf7, 0xc0 => 0x05b0, 0xc1 => 0x05b1, + 0xc2 => 0x05b2, 0xc3 => 0x05b3, 0xc4 => 0x05b4, 0xc5 => 0x05b5, + 0xc6 => 0x05b6, 0xc7 => 0x05b7, 0xc8 => 0x05b8, 0xc9 => 0x05b9, + 0xcb => 0x05bb, 0xcc => 0x05bc, 0xcd => 0x05bd, 0xce => 0x05be, + 0xcf => 0x05bf, 0xd0 => 0x05c0, 0xd1 => 0x05c1, 0xd2 => 0x05c2, + 0xd3 => 0x05c3, 0xd4 => 0x05f0, 0xd5 => 0x05f1, 0xd6 => 0x05f2, + 0xd7 => 0x05f3, 0xd8 => 0x05f4, 0xe0 => 0x05d0, 0xe1 => 0x05d1, + 0xe2 => 0x05d2, 0xe3 => 0x05d3, 0xe4 => 0x05d4, 0xe5 => 0x05d5, + 0xe6 => 0x05d6, 0xe7 => 0x05d7, 0xe8 => 0x05d8, 0xe9 => 0x05d9, + 0xea => 0x05da, 0xeb => 0x05db, 0xec => 0x05dc, 0xed => 0x05dd, + 0xee => 0x05de, 0xef => 0x05df, 0xf0 => 0x05e0, 0xf1 => 0x05e1, + 0xf2 => 0x05e2, 0xf3 => 0x05e3, 0xf4 => 0x05e4, 0xf5 => 0x05e5, + 0xf6 => 0x05e6, 0xf7 => 0x05e7, 0xf8 => 0x05e8, 0xf9 => 0x05e9, + 0xfa => 0x05ea, 0xfd => 0x200e, 0xfe => 0x200f, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/JIS.pm b/ExifTool/lib/Image/ExifTool/Charset/JIS.pm new file mode 100644 index 0000000..d4411cb --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/JIS.pm @@ -0,0 +1,1735 @@ +#------------------------------------------------------------------------------ +# File: JIS.pm +# +# Description: JIS X 0208 (1990) to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::JIS = ( + 0x2121 => 0x3000, 0x2122 => 0x3001, 0x2123 => 0x3002, 0x2124 => 0xff0c, + 0x2125 => 0xff0e, 0x2126 => 0x30fb, 0x2127 => 0xff1a, 0x2128 => 0xff1b, + 0x2129 => 0xff1f, 0x212a => 0xff01, 0x212b => 0x309b, 0x212c => 0x309c, + 0x212d => 0x00b4, 0x212e => 0xff40, 0x212f => 0x00a8, 0x2130 => 0xff3e, + 0x2131 => 0xffe3, 0x2132 => 0xff3f, 0x2133 => 0x30fd, 0x2134 => 0x30fe, + 0x2135 => 0x309d, 0x2136 => 0x309e, 0x2137 => 0x3003, 0x2138 => 0x4edd, + 0x2139 => 0x3005, 0x213a => 0x3006, 0x213b => 0x3007, 0x213c => 0x30fc, + 0x213d => 0x2015, 0x213e => 0x2010, 0x213f => 0xff0f, 0x2140 => 0x005c, + 0x2141 => 0x301c, 0x2142 => 0x2016, 0x2143 => 0xff5c, 0x2144 => 0x2026, + 0x2145 => 0x2025, 0x2146 => 0x2018, 0x2147 => 0x2019, 0x2148 => 0x201c, + 0x2149 => 0x201d, 0x214a => 0xff08, 0x214b => 0xff09, 0x214c => 0x3014, + 0x214d => 0x3015, 0x214e => 0xff3b, 0x214f => 0xff3d, 0x2150 => 0xff5b, + 0x2151 => 0xff5d, 0x2152 => 0x3008, 0x2153 => 0x3009, 0x2154 => 0x300a, + 0x2155 => 0x300b, 0x2156 => 0x300c, 0x2157 => 0x300d, 0x2158 => 0x300e, + 0x2159 => 0x300f, 0x215a => 0x3010, 0x215b => 0x3011, 0x215c => 0xff0b, + 0x215d => 0x2212, 0x215e => 0x00b1, 0x215f => 0x00d7, 0x2160 => 0x00f7, + 0x2161 => 0xff1d, 0x2162 => 0x2260, 0x2163 => 0xff1c, 0x2164 => 0xff1e, + 0x2165 => 0x2266, 0x2166 => 0x2267, 0x2167 => 0x221e, 0x2168 => 0x2234, + 0x2169 => 0x2642, 0x216a => 0x2640, 0x216b => 0x00b0, 0x216c => 0x2032, + 0x216d => 0x2033, 0x216e => 0x2103, 0x216f => 0xffe5, 0x2170 => 0xff04, + 0x2171 => 0x00a2, 0x2172 => 0x00a3, 0x2173 => 0xff05, 0x2174 => 0xff03, + 0x2175 => 0xff06, 0x2176 => 0xff0a, 0x2177 => 0xff20, 0x2178 => 0x00a7, + 0x2179 => 0x2606, 0x217a => 0x2605, 0x217b => 0x25cb, 0x217c => 0x25cf, + 0x217d => 0x25ce, 0x217e => 0x25c7, 0x2221 => 0x25c6, 0x2222 => 0x25a1, + 0x2223 => 0x25a0, 0x2224 => 0x25b3, 0x2225 => 0x25b2, 0x2226 => 0x25bd, + 0x2227 => 0x25bc, 0x2228 => 0x203b, 0x2229 => 0x3012, 0x222a => 0x2192, + 0x222b => 0x2190, 0x222c => 0x2191, 0x222d => 0x2193, 0x222e => 0x3013, + 0x223a => 0x2208, 0x223b => 0x220b, 0x223c => 0x2286, 0x223d => 0x2287, + 0x223e => 0x2282, 0x223f => 0x2283, 0x2240 => 0x222a, 0x2241 => 0x2229, + 0x224a => 0x2227, 0x224b => 0x2228, 0x224c => 0x00ac, 0x224d => 0x21d2, + 0x224e => 0x21d4, 0x224f => 0x2200, 0x2250 => 0x2203, 0x225c => 0x2220, + 0x225d => 0x22a5, 0x225e => 0x2312, 0x225f => 0x2202, 0x2260 => 0x2207, + 0x2261 => 0x2261, 0x2262 => 0x2252, 0x2263 => 0x226a, 0x2264 => 0x226b, + 0x2265 => 0x221a, 0x2266 => 0x223d, 0x2267 => 0x221d, 0x2268 => 0x2235, + 0x2269 => 0x222b, 0x226a => 0x222c, 0x2272 => 0x212b, 0x2273 => 0x2030, + 0x2274 => 0x266f, 0x2275 => 0x266d, 0x2276 => 0x266a, 0x2277 => 0x2020, + 0x2278 => 0x2021, 0x2279 => 0x00b6, 0x227e => 0x25ef, 0x2330 => 0xff10, + 0x2331 => 0xff11, 0x2332 => 0xff12, 0x2333 => 0xff13, 0x2334 => 0xff14, + 0x2335 => 0xff15, 0x2336 => 0xff16, 0x2337 => 0xff17, 0x2338 => 0xff18, + 0x2339 => 0xff19, 0x2341 => 0xff21, 0x2342 => 0xff22, 0x2343 => 0xff23, + 0x2344 => 0xff24, 0x2345 => 0xff25, 0x2346 => 0xff26, 0x2347 => 0xff27, + 0x2348 => 0xff28, 0x2349 => 0xff29, 0x234a => 0xff2a, 0x234b => 0xff2b, + 0x234c => 0xff2c, 0x234d => 0xff2d, 0x234e => 0xff2e, 0x234f => 0xff2f, + 0x2350 => 0xff30, 0x2351 => 0xff31, 0x2352 => 0xff32, 0x2353 => 0xff33, + 0x2354 => 0xff34, 0x2355 => 0xff35, 0x2356 => 0xff36, 0x2357 => 0xff37, + 0x2358 => 0xff38, 0x2359 => 0xff39, 0x235a => 0xff3a, 0x2361 => 0xff41, + 0x2362 => 0xff42, 0x2363 => 0xff43, 0x2364 => 0xff44, 0x2365 => 0xff45, + 0x2366 => 0xff46, 0x2367 => 0xff47, 0x2368 => 0xff48, 0x2369 => 0xff49, + 0x236a => 0xff4a, 0x236b => 0xff4b, 0x236c => 0xff4c, 0x236d => 0xff4d, + 0x236e => 0xff4e, 0x236f => 0xff4f, 0x2370 => 0xff50, 0x2371 => 0xff51, + 0x2372 => 0xff52, 0x2373 => 0xff53, 0x2374 => 0xff54, 0x2375 => 0xff55, + 0x2376 => 0xff56, 0x2377 => 0xff57, 0x2378 => 0xff58, 0x2379 => 0xff59, + 0x237a => 0xff5a, 0x2421 => 0x3041, 0x2422 => 0x3042, 0x2423 => 0x3043, + 0x2424 => 0x3044, 0x2425 => 0x3045, 0x2426 => 0x3046, 0x2427 => 0x3047, + 0x2428 => 0x3048, 0x2429 => 0x3049, 0x242a => 0x304a, 0x242b => 0x304b, + 0x242c => 0x304c, 0x242d => 0x304d, 0x242e => 0x304e, 0x242f => 0x304f, + 0x2430 => 0x3050, 0x2431 => 0x3051, 0x2432 => 0x3052, 0x2433 => 0x3053, + 0x2434 => 0x3054, 0x2435 => 0x3055, 0x2436 => 0x3056, 0x2437 => 0x3057, + 0x2438 => 0x3058, 0x2439 => 0x3059, 0x243a => 0x305a, 0x243b => 0x305b, + 0x243c => 0x305c, 0x243d => 0x305d, 0x243e => 0x305e, 0x243f => 0x305f, + 0x2440 => 0x3060, 0x2441 => 0x3061, 0x2442 => 0x3062, 0x2443 => 0x3063, + 0x2444 => 0x3064, 0x2445 => 0x3065, 0x2446 => 0x3066, 0x2447 => 0x3067, + 0x2448 => 0x3068, 0x2449 => 0x3069, 0x244a => 0x306a, 0x244b => 0x306b, + 0x244c => 0x306c, 0x244d => 0x306d, 0x244e => 0x306e, 0x244f => 0x306f, + 0x2450 => 0x3070, 0x2451 => 0x3071, 0x2452 => 0x3072, 0x2453 => 0x3073, + 0x2454 => 0x3074, 0x2455 => 0x3075, 0x2456 => 0x3076, 0x2457 => 0x3077, + 0x2458 => 0x3078, 0x2459 => 0x3079, 0x245a => 0x307a, 0x245b => 0x307b, + 0x245c => 0x307c, 0x245d => 0x307d, 0x245e => 0x307e, 0x245f => 0x307f, + 0x2460 => 0x3080, 0x2461 => 0x3081, 0x2462 => 0x3082, 0x2463 => 0x3083, + 0x2464 => 0x3084, 0x2465 => 0x3085, 0x2466 => 0x3086, 0x2467 => 0x3087, + 0x2468 => 0x3088, 0x2469 => 0x3089, 0x246a => 0x308a, 0x246b => 0x308b, + 0x246c => 0x308c, 0x246d => 0x308d, 0x246e => 0x308e, 0x246f => 0x308f, + 0x2470 => 0x3090, 0x2471 => 0x3091, 0x2472 => 0x3092, 0x2473 => 0x3093, + 0x2521 => 0x30a1, 0x2522 => 0x30a2, 0x2523 => 0x30a3, 0x2524 => 0x30a4, + 0x2525 => 0x30a5, 0x2526 => 0x30a6, 0x2527 => 0x30a7, 0x2528 => 0x30a8, + 0x2529 => 0x30a9, 0x252a => 0x30aa, 0x252b => 0x30ab, 0x252c => 0x30ac, + 0x252d => 0x30ad, 0x252e => 0x30ae, 0x252f => 0x30af, 0x2530 => 0x30b0, + 0x2531 => 0x30b1, 0x2532 => 0x30b2, 0x2533 => 0x30b3, 0x2534 => 0x30b4, + 0x2535 => 0x30b5, 0x2536 => 0x30b6, 0x2537 => 0x30b7, 0x2538 => 0x30b8, + 0x2539 => 0x30b9, 0x253a => 0x30ba, 0x253b => 0x30bb, 0x253c => 0x30bc, + 0x253d => 0x30bd, 0x253e => 0x30be, 0x253f => 0x30bf, 0x2540 => 0x30c0, + 0x2541 => 0x30c1, 0x2542 => 0x30c2, 0x2543 => 0x30c3, 0x2544 => 0x30c4, + 0x2545 => 0x30c5, 0x2546 => 0x30c6, 0x2547 => 0x30c7, 0x2548 => 0x30c8, + 0x2549 => 0x30c9, 0x254a => 0x30ca, 0x254b => 0x30cb, 0x254c => 0x30cc, + 0x254d => 0x30cd, 0x254e => 0x30ce, 0x254f => 0x30cf, 0x2550 => 0x30d0, + 0x2551 => 0x30d1, 0x2552 => 0x30d2, 0x2553 => 0x30d3, 0x2554 => 0x30d4, + 0x2555 => 0x30d5, 0x2556 => 0x30d6, 0x2557 => 0x30d7, 0x2558 => 0x30d8, + 0x2559 => 0x30d9, 0x255a => 0x30da, 0x255b => 0x30db, 0x255c => 0x30dc, + 0x255d => 0x30dd, 0x255e => 0x30de, 0x255f => 0x30df, 0x2560 => 0x30e0, + 0x2561 => 0x30e1, 0x2562 => 0x30e2, 0x2563 => 0x30e3, 0x2564 => 0x30e4, + 0x2565 => 0x30e5, 0x2566 => 0x30e6, 0x2567 => 0x30e7, 0x2568 => 0x30e8, + 0x2569 => 0x30e9, 0x256a => 0x30ea, 0x256b => 0x30eb, 0x256c => 0x30ec, + 0x256d => 0x30ed, 0x256e => 0x30ee, 0x256f => 0x30ef, 0x2570 => 0x30f0, + 0x2571 => 0x30f1, 0x2572 => 0x30f2, 0x2573 => 0x30f3, 0x2574 => 0x30f4, + 0x2575 => 0x30f5, 0x2576 => 0x30f6, 0x2621 => 0x0391, 0x2622 => 0x0392, + 0x2623 => 0x0393, 0x2624 => 0x0394, 0x2625 => 0x0395, 0x2626 => 0x0396, + 0x2627 => 0x0397, 0x2628 => 0x0398, 0x2629 => 0x0399, 0x262a => 0x039a, + 0x262b => 0x039b, 0x262c => 0x039c, 0x262d => 0x039d, 0x262e => 0x039e, + 0x262f => 0x039f, 0x2630 => 0x03a0, 0x2631 => 0x03a1, 0x2632 => 0x03a3, + 0x2633 => 0x03a4, 0x2634 => 0x03a5, 0x2635 => 0x03a6, 0x2636 => 0x03a7, + 0x2637 => 0x03a8, 0x2638 => 0x03a9, 0x2641 => 0x03b1, 0x2642 => 0x03b2, + 0x2643 => 0x03b3, 0x2644 => 0x03b4, 0x2645 => 0x03b5, 0x2646 => 0x03b6, + 0x2647 => 0x03b7, 0x2648 => 0x03b8, 0x2649 => 0x03b9, 0x264a => 0x03ba, + 0x264b => 0x03bb, 0x264c => 0x03bc, 0x264d => 0x03bd, 0x264e => 0x03be, + 0x264f => 0x03bf, 0x2650 => 0x03c0, 0x2651 => 0x03c1, 0x2652 => 0x03c3, + 0x2653 => 0x03c4, 0x2654 => 0x03c5, 0x2655 => 0x03c6, 0x2656 => 0x03c7, + 0x2657 => 0x03c8, 0x2658 => 0x03c9, 0x2721 => 0x0410, 0x2722 => 0x0411, + 0x2723 => 0x0412, 0x2724 => 0x0413, 0x2725 => 0x0414, 0x2726 => 0x0415, + 0x2727 => 0x0401, 0x2728 => 0x0416, 0x2729 => 0x0417, 0x272a => 0x0418, + 0x272b => 0x0419, 0x272c => 0x041a, 0x272d => 0x041b, 0x272e => 0x041c, + 0x272f => 0x041d, 0x2730 => 0x041e, 0x2731 => 0x041f, 0x2732 => 0x0420, + 0x2733 => 0x0421, 0x2734 => 0x0422, 0x2735 => 0x0423, 0x2736 => 0x0424, + 0x2737 => 0x0425, 0x2738 => 0x0426, 0x2739 => 0x0427, 0x273a => 0x0428, + 0x273b => 0x0429, 0x273c => 0x042a, 0x273d => 0x042b, 0x273e => 0x042c, + 0x273f => 0x042d, 0x2740 => 0x042e, 0x2741 => 0x042f, 0x2751 => 0x0430, + 0x2752 => 0x0431, 0x2753 => 0x0432, 0x2754 => 0x0433, 0x2755 => 0x0434, + 0x2756 => 0x0435, 0x2757 => 0x0451, 0x2758 => 0x0436, 0x2759 => 0x0437, + 0x275a => 0x0438, 0x275b => 0x0439, 0x275c => 0x043a, 0x275d => 0x043b, + 0x275e => 0x043c, 0x275f => 0x043d, 0x2760 => 0x043e, 0x2761 => 0x043f, + 0x2762 => 0x0440, 0x2763 => 0x0441, 0x2764 => 0x0442, 0x2765 => 0x0443, + 0x2766 => 0x0444, 0x2767 => 0x0445, 0x2768 => 0x0446, 0x2769 => 0x0447, + 0x276a => 0x0448, 0x276b => 0x0449, 0x276c => 0x044a, 0x276d => 0x044b, + 0x276e => 0x044c, 0x276f => 0x044d, 0x2770 => 0x044e, 0x2771 => 0x044f, + 0x2821 => 0x2500, 0x2822 => 0x2502, 0x2823 => 0x250c, 0x2824 => 0x2510, + 0x2825 => 0x2518, 0x2826 => 0x2514, 0x2827 => 0x251c, 0x2828 => 0x252c, + 0x2829 => 0x2524, 0x282a => 0x2534, 0x282b => 0x253c, 0x282c => 0x2501, + 0x282d => 0x2503, 0x282e => 0x250f, 0x282f => 0x2513, 0x2830 => 0x251b, + 0x2831 => 0x2517, 0x2832 => 0x2523, 0x2833 => 0x2533, 0x2834 => 0x252b, + 0x2835 => 0x253b, 0x2836 => 0x254b, 0x2837 => 0x2520, 0x2838 => 0x252f, + 0x2839 => 0x2528, 0x283a => 0x2537, 0x283b => 0x253f, 0x283c => 0x251d, + 0x283d => 0x2530, 0x283e => 0x2525, 0x283f => 0x2538, 0x2840 => 0x2542, + 0x3021 => 0x4e9c, 0x3022 => 0x5516, 0x3023 => 0x5a03, 0x3024 => 0x963f, + 0x3025 => 0x54c0, 0x3026 => 0x611b, 0x3027 => 0x6328, 0x3028 => 0x59f6, + 0x3029 => 0x9022, 0x302a => 0x8475, 0x302b => 0x831c, 0x302c => 0x7a50, + 0x302d => 0x60aa, 0x302e => 0x63e1, 0x302f => 0x6e25, 0x3030 => 0x65ed, + 0x3031 => 0x8466, 0x3032 => 0x82a6, 0x3033 => 0x9bf5, 0x3034 => 0x6893, + 0x3035 => 0x5727, 0x3036 => 0x65a1, 0x3037 => 0x6271, 0x3038 => 0x5b9b, + 0x3039 => 0x59d0, 0x303a => 0x867b, 0x303b => 0x98f4, 0x303c => 0x7d62, + 0x303d => 0x7dbe, 0x303e => 0x9b8e, 0x303f => 0x6216, 0x3040 => 0x7c9f, + 0x3041 => 0x88b7, 0x3042 => 0x5b89, 0x3043 => 0x5eb5, 0x3044 => 0x6309, + 0x3045 => 0x6697, 0x3046 => 0x6848, 0x3047 => 0x95c7, 0x3048 => 0x978d, + 0x3049 => 0x674f, 0x304a => 0x4ee5, 0x304b => 0x4f0a, 0x304c => 0x4f4d, + 0x304d => 0x4f9d, 0x304e => 0x5049, 0x304f => 0x56f2, 0x3050 => 0x5937, + 0x3051 => 0x59d4, 0x3052 => 0x5a01, 0x3053 => 0x5c09, 0x3054 => 0x60df, + 0x3055 => 0x610f, 0x3056 => 0x6170, 0x3057 => 0x6613, 0x3058 => 0x6905, + 0x3059 => 0x70ba, 0x305a => 0x754f, 0x305b => 0x7570, 0x305c => 0x79fb, + 0x305d => 0x7dad, 0x305e => 0x7def, 0x305f => 0x80c3, 0x3060 => 0x840e, + 0x3061 => 0x8863, 0x3062 => 0x8b02, 0x3063 => 0x9055, 0x3064 => 0x907a, + 0x3065 => 0x533b, 0x3066 => 0x4e95, 0x3067 => 0x4ea5, 0x3068 => 0x57df, + 0x3069 => 0x80b2, 0x306a => 0x90c1, 0x306b => 0x78ef, 0x306c => 0x4e00, + 0x306d => 0x58f1, 0x306e => 0x6ea2, 0x306f => 0x9038, 0x3070 => 0x7a32, + 0x3071 => 0x8328, 0x3072 => 0x828b, 0x3073 => 0x9c2f, 0x3074 => 0x5141, + 0x3075 => 0x5370, 0x3076 => 0x54bd, 0x3077 => 0x54e1, 0x3078 => 0x56e0, + 0x3079 => 0x59fb, 0x307a => 0x5f15, 0x307b => 0x98f2, 0x307c => 0x6deb, + 0x307d => 0x80e4, 0x307e => 0x852d, 0x3121 => 0x9662, 0x3122 => 0x9670, + 0x3123 => 0x96a0, 0x3124 => 0x97fb, 0x3125 => 0x540b, 0x3126 => 0x53f3, + 0x3127 => 0x5b87, 0x3128 => 0x70cf, 0x3129 => 0x7fbd, 0x312a => 0x8fc2, + 0x312b => 0x96e8, 0x312c => 0x536f, 0x312d => 0x9d5c, 0x312e => 0x7aba, + 0x312f => 0x4e11, 0x3130 => 0x7893, 0x3131 => 0x81fc, 0x3132 => 0x6e26, + 0x3133 => 0x5618, 0x3134 => 0x5504, 0x3135 => 0x6b1d, 0x3136 => 0x851a, + 0x3137 => 0x9c3b, 0x3138 => 0x59e5, 0x3139 => 0x53a9, 0x313a => 0x6d66, + 0x313b => 0x74dc, 0x313c => 0x958f, 0x313d => 0x5642, 0x313e => 0x4e91, + 0x313f => 0x904b, 0x3140 => 0x96f2, 0x3141 => 0x834f, 0x3142 => 0x990c, + 0x3143 => 0x53e1, 0x3144 => 0x55b6, 0x3145 => 0x5b30, 0x3146 => 0x5f71, + 0x3147 => 0x6620, 0x3148 => 0x66f3, 0x3149 => 0x6804, 0x314a => 0x6c38, + 0x314b => 0x6cf3, 0x314c => 0x6d29, 0x314d => 0x745b, 0x314e => 0x76c8, + 0x314f => 0x7a4e, 0x3150 => 0x9834, 0x3151 => 0x82f1, 0x3152 => 0x885b, + 0x3153 => 0x8a60, 0x3154 => 0x92ed, 0x3155 => 0x6db2, 0x3156 => 0x75ab, + 0x3157 => 0x76ca, 0x3158 => 0x99c5, 0x3159 => 0x60a6, 0x315a => 0x8b01, + 0x315b => 0x8d8a, 0x315c => 0x95b2, 0x315d => 0x698e, 0x315e => 0x53ad, + 0x315f => 0x5186, 0x3160 => 0x5712, 0x3161 => 0x5830, 0x3162 => 0x5944, + 0x3163 => 0x5bb4, 0x3164 => 0x5ef6, 0x3165 => 0x6028, 0x3166 => 0x63a9, + 0x3167 => 0x63f4, 0x3168 => 0x6cbf, 0x3169 => 0x6f14, 0x316a => 0x708e, + 0x316b => 0x7114, 0x316c => 0x7159, 0x316d => 0x71d5, 0x316e => 0x733f, + 0x316f => 0x7e01, 0x3170 => 0x8276, 0x3171 => 0x82d1, 0x3172 => 0x8597, + 0x3173 => 0x9060, 0x3174 => 0x925b, 0x3175 => 0x9d1b, 0x3176 => 0x5869, + 0x3177 => 0x65bc, 0x3178 => 0x6c5a, 0x3179 => 0x7525, 0x317a => 0x51f9, + 0x317b => 0x592e, 0x317c => 0x5965, 0x317d => 0x5f80, 0x317e => 0x5fdc, + 0x3221 => 0x62bc, 0x3222 => 0x65fa, 0x3223 => 0x6a2a, 0x3224 => 0x6b27, + 0x3225 => 0x6bb4, 0x3226 => 0x738b, 0x3227 => 0x7fc1, 0x3228 => 0x8956, + 0x3229 => 0x9d2c, 0x322a => 0x9d0e, 0x322b => 0x9ec4, 0x322c => 0x5ca1, + 0x322d => 0x6c96, 0x322e => 0x837b, 0x322f => 0x5104, 0x3230 => 0x5c4b, + 0x3231 => 0x61b6, 0x3232 => 0x81c6, 0x3233 => 0x6876, 0x3234 => 0x7261, + 0x3235 => 0x4e59, 0x3236 => 0x4ffa, 0x3237 => 0x5378, 0x3238 => 0x6069, + 0x3239 => 0x6e29, 0x323a => 0x7a4f, 0x323b => 0x97f3, 0x323c => 0x4e0b, + 0x323d => 0x5316, 0x323e => 0x4eee, 0x323f => 0x4f55, 0x3240 => 0x4f3d, + 0x3241 => 0x4fa1, 0x3242 => 0x4f73, 0x3243 => 0x52a0, 0x3244 => 0x53ef, + 0x3245 => 0x5609, 0x3246 => 0x590f, 0x3247 => 0x5ac1, 0x3248 => 0x5bb6, + 0x3249 => 0x5be1, 0x324a => 0x79d1, 0x324b => 0x6687, 0x324c => 0x679c, + 0x324d => 0x67b6, 0x324e => 0x6b4c, 0x324f => 0x6cb3, 0x3250 => 0x706b, + 0x3251 => 0x73c2, 0x3252 => 0x798d, 0x3253 => 0x79be, 0x3254 => 0x7a3c, + 0x3255 => 0x7b87, 0x3256 => 0x82b1, 0x3257 => 0x82db, 0x3258 => 0x8304, + 0x3259 => 0x8377, 0x325a => 0x83ef, 0x325b => 0x83d3, 0x325c => 0x8766, + 0x325d => 0x8ab2, 0x325e => 0x5629, 0x325f => 0x8ca8, 0x3260 => 0x8fe6, + 0x3261 => 0x904e, 0x3262 => 0x971e, 0x3263 => 0x868a, 0x3264 => 0x4fc4, + 0x3265 => 0x5ce8, 0x3266 => 0x6211, 0x3267 => 0x7259, 0x3268 => 0x753b, + 0x3269 => 0x81e5, 0x326a => 0x82bd, 0x326b => 0x86fe, 0x326c => 0x8cc0, + 0x326d => 0x96c5, 0x326e => 0x9913, 0x326f => 0x99d5, 0x3270 => 0x4ecb, + 0x3271 => 0x4f1a, 0x3272 => 0x89e3, 0x3273 => 0x56de, 0x3274 => 0x584a, + 0x3275 => 0x58ca, 0x3276 => 0x5efb, 0x3277 => 0x5feb, 0x3278 => 0x602a, + 0x3279 => 0x6094, 0x327a => 0x6062, 0x327b => 0x61d0, 0x327c => 0x6212, + 0x327d => 0x62d0, 0x327e => 0x6539, 0x3321 => 0x9b41, 0x3322 => 0x6666, + 0x3323 => 0x68b0, 0x3324 => 0x6d77, 0x3325 => 0x7070, 0x3326 => 0x754c, + 0x3327 => 0x7686, 0x3328 => 0x7d75, 0x3329 => 0x82a5, 0x332a => 0x87f9, + 0x332b => 0x958b, 0x332c => 0x968e, 0x332d => 0x8c9d, 0x332e => 0x51f1, + 0x332f => 0x52be, 0x3330 => 0x5916, 0x3331 => 0x54b3, 0x3332 => 0x5bb3, + 0x3333 => 0x5d16, 0x3334 => 0x6168, 0x3335 => 0x6982, 0x3336 => 0x6daf, + 0x3337 => 0x788d, 0x3338 => 0x84cb, 0x3339 => 0x8857, 0x333a => 0x8a72, + 0x333b => 0x93a7, 0x333c => 0x9ab8, 0x333d => 0x6d6c, 0x333e => 0x99a8, + 0x333f => 0x86d9, 0x3340 => 0x57a3, 0x3341 => 0x67ff, 0x3342 => 0x86ce, + 0x3343 => 0x920e, 0x3344 => 0x5283, 0x3345 => 0x5687, 0x3346 => 0x5404, + 0x3347 => 0x5ed3, 0x3348 => 0x62e1, 0x3349 => 0x64b9, 0x334a => 0x683c, + 0x334b => 0x6838, 0x334c => 0x6bbb, 0x334d => 0x7372, 0x334e => 0x78ba, + 0x334f => 0x7a6b, 0x3350 => 0x899a, 0x3351 => 0x89d2, 0x3352 => 0x8d6b, + 0x3353 => 0x8f03, 0x3354 => 0x90ed, 0x3355 => 0x95a3, 0x3356 => 0x9694, + 0x3357 => 0x9769, 0x3358 => 0x5b66, 0x3359 => 0x5cb3, 0x335a => 0x697d, + 0x335b => 0x984d, 0x335c => 0x984e, 0x335d => 0x639b, 0x335e => 0x7b20, + 0x335f => 0x6a2b, 0x3360 => 0x6a7f, 0x3361 => 0x68b6, 0x3362 => 0x9c0d, + 0x3363 => 0x6f5f, 0x3364 => 0x5272, 0x3365 => 0x559d, 0x3366 => 0x6070, + 0x3367 => 0x62ec, 0x3368 => 0x6d3b, 0x3369 => 0x6e07, 0x336a => 0x6ed1, + 0x336b => 0x845b, 0x336c => 0x8910, 0x336d => 0x8f44, 0x336e => 0x4e14, + 0x336f => 0x9c39, 0x3370 => 0x53f6, 0x3371 => 0x691b, 0x3372 => 0x6a3a, + 0x3373 => 0x9784, 0x3374 => 0x682a, 0x3375 => 0x515c, 0x3376 => 0x7ac3, + 0x3377 => 0x84b2, 0x3378 => 0x91dc, 0x3379 => 0x938c, 0x337a => 0x565b, + 0x337b => 0x9d28, 0x337c => 0x6822, 0x337d => 0x8305, 0x337e => 0x8431, + 0x3421 => 0x7ca5, 0x3422 => 0x5208, 0x3423 => 0x82c5, 0x3424 => 0x74e6, + 0x3425 => 0x4e7e, 0x3426 => 0x4f83, 0x3427 => 0x51a0, 0x3428 => 0x5bd2, + 0x3429 => 0x520a, 0x342a => 0x52d8, 0x342b => 0x52e7, 0x342c => 0x5dfb, + 0x342d => 0x559a, 0x342e => 0x582a, 0x342f => 0x59e6, 0x3430 => 0x5b8c, + 0x3431 => 0x5b98, 0x3432 => 0x5bdb, 0x3433 => 0x5e72, 0x3434 => 0x5e79, + 0x3435 => 0x60a3, 0x3436 => 0x611f, 0x3437 => 0x6163, 0x3438 => 0x61be, + 0x3439 => 0x63db, 0x343a => 0x6562, 0x343b => 0x67d1, 0x343c => 0x6853, + 0x343d => 0x68fa, 0x343e => 0x6b3e, 0x343f => 0x6b53, 0x3440 => 0x6c57, + 0x3441 => 0x6f22, 0x3442 => 0x6f97, 0x3443 => 0x6f45, 0x3444 => 0x74b0, + 0x3445 => 0x7518, 0x3446 => 0x76e3, 0x3447 => 0x770b, 0x3448 => 0x7aff, + 0x3449 => 0x7ba1, 0x344a => 0x7c21, 0x344b => 0x7de9, 0x344c => 0x7f36, + 0x344d => 0x7ff0, 0x344e => 0x809d, 0x344f => 0x8266, 0x3450 => 0x839e, + 0x3451 => 0x89b3, 0x3452 => 0x8acc, 0x3453 => 0x8cab, 0x3454 => 0x9084, + 0x3455 => 0x9451, 0x3456 => 0x9593, 0x3457 => 0x9591, 0x3458 => 0x95a2, + 0x3459 => 0x9665, 0x345a => 0x97d3, 0x345b => 0x9928, 0x345c => 0x8218, + 0x345d => 0x4e38, 0x345e => 0x542b, 0x345f => 0x5cb8, 0x3460 => 0x5dcc, + 0x3461 => 0x73a9, 0x3462 => 0x764c, 0x3463 => 0x773c, 0x3464 => 0x5ca9, + 0x3465 => 0x7feb, 0x3466 => 0x8d0b, 0x3467 => 0x96c1, 0x3468 => 0x9811, + 0x3469 => 0x9854, 0x346a => 0x9858, 0x346b => 0x4f01, 0x346c => 0x4f0e, + 0x346d => 0x5371, 0x346e => 0x559c, 0x346f => 0x5668, 0x3470 => 0x57fa, + 0x3471 => 0x5947, 0x3472 => 0x5b09, 0x3473 => 0x5bc4, 0x3474 => 0x5c90, + 0x3475 => 0x5e0c, 0x3476 => 0x5e7e, 0x3477 => 0x5fcc, 0x3478 => 0x63ee, + 0x3479 => 0x673a, 0x347a => 0x65d7, 0x347b => 0x65e2, 0x347c => 0x671f, + 0x347d => 0x68cb, 0x347e => 0x68c4, 0x3521 => 0x6a5f, 0x3522 => 0x5e30, + 0x3523 => 0x6bc5, 0x3524 => 0x6c17, 0x3525 => 0x6c7d, 0x3526 => 0x757f, + 0x3527 => 0x7948, 0x3528 => 0x5b63, 0x3529 => 0x7a00, 0x352a => 0x7d00, + 0x352b => 0x5fbd, 0x352c => 0x898f, 0x352d => 0x8a18, 0x352e => 0x8cb4, + 0x352f => 0x8d77, 0x3530 => 0x8ecc, 0x3531 => 0x8f1d, 0x3532 => 0x98e2, + 0x3533 => 0x9a0e, 0x3534 => 0x9b3c, 0x3535 => 0x4e80, 0x3536 => 0x507d, + 0x3537 => 0x5100, 0x3538 => 0x5993, 0x3539 => 0x5b9c, 0x353a => 0x622f, + 0x353b => 0x6280, 0x353c => 0x64ec, 0x353d => 0x6b3a, 0x353e => 0x72a0, + 0x353f => 0x7591, 0x3540 => 0x7947, 0x3541 => 0x7fa9, 0x3542 => 0x87fb, + 0x3543 => 0x8abc, 0x3544 => 0x8b70, 0x3545 => 0x63ac, 0x3546 => 0x83ca, + 0x3547 => 0x97a0, 0x3548 => 0x5409, 0x3549 => 0x5403, 0x354a => 0x55ab, + 0x354b => 0x6854, 0x354c => 0x6a58, 0x354d => 0x8a70, 0x354e => 0x7827, + 0x354f => 0x6775, 0x3550 => 0x9ecd, 0x3551 => 0x5374, 0x3552 => 0x5ba2, + 0x3553 => 0x811a, 0x3554 => 0x8650, 0x3555 => 0x9006, 0x3556 => 0x4e18, + 0x3557 => 0x4e45, 0x3558 => 0x4ec7, 0x3559 => 0x4f11, 0x355a => 0x53ca, + 0x355b => 0x5438, 0x355c => 0x5bae, 0x355d => 0x5f13, 0x355e => 0x6025, + 0x355f => 0x6551, 0x3560 => 0x673d, 0x3561 => 0x6c42, 0x3562 => 0x6c72, + 0x3563 => 0x6ce3, 0x3564 => 0x7078, 0x3565 => 0x7403, 0x3566 => 0x7a76, + 0x3567 => 0x7aae, 0x3568 => 0x7b08, 0x3569 => 0x7d1a, 0x356a => 0x7cfe, + 0x356b => 0x7d66, 0x356c => 0x65e7, 0x356d => 0x725b, 0x356e => 0x53bb, + 0x356f => 0x5c45, 0x3570 => 0x5de8, 0x3571 => 0x62d2, 0x3572 => 0x62e0, + 0x3573 => 0x6319, 0x3574 => 0x6e20, 0x3575 => 0x865a, 0x3576 => 0x8a31, + 0x3577 => 0x8ddd, 0x3578 => 0x92f8, 0x3579 => 0x6f01, 0x357a => 0x79a6, + 0x357b => 0x9b5a, 0x357c => 0x4ea8, 0x357d => 0x4eab, 0x357e => 0x4eac, + 0x3621 => 0x4f9b, 0x3622 => 0x4fa0, 0x3623 => 0x50d1, 0x3624 => 0x5147, + 0x3625 => 0x7af6, 0x3626 => 0x5171, 0x3627 => 0x51f6, 0x3628 => 0x5354, + 0x3629 => 0x5321, 0x362a => 0x537f, 0x362b => 0x53eb, 0x362c => 0x55ac, + 0x362d => 0x5883, 0x362e => 0x5ce1, 0x362f => 0x5f37, 0x3630 => 0x5f4a, + 0x3631 => 0x602f, 0x3632 => 0x6050, 0x3633 => 0x606d, 0x3634 => 0x631f, + 0x3635 => 0x6559, 0x3636 => 0x6a4b, 0x3637 => 0x6cc1, 0x3638 => 0x72c2, + 0x3639 => 0x72ed, 0x363a => 0x77ef, 0x363b => 0x80f8, 0x363c => 0x8105, + 0x363d => 0x8208, 0x363e => 0x854e, 0x363f => 0x90f7, 0x3640 => 0x93e1, + 0x3641 => 0x97ff, 0x3642 => 0x9957, 0x3643 => 0x9a5a, 0x3644 => 0x4ef0, + 0x3645 => 0x51dd, 0x3646 => 0x5c2d, 0x3647 => 0x6681, 0x3648 => 0x696d, + 0x3649 => 0x5c40, 0x364a => 0x66f2, 0x364b => 0x6975, 0x364c => 0x7389, + 0x364d => 0x6850, 0x364e => 0x7c81, 0x364f => 0x50c5, 0x3650 => 0x52e4, + 0x3651 => 0x5747, 0x3652 => 0x5dfe, 0x3653 => 0x9326, 0x3654 => 0x65a4, + 0x3655 => 0x6b23, 0x3656 => 0x6b3d, 0x3657 => 0x7434, 0x3658 => 0x7981, + 0x3659 => 0x79bd, 0x365a => 0x7b4b, 0x365b => 0x7dca, 0x365c => 0x82b9, + 0x365d => 0x83cc, 0x365e => 0x887f, 0x365f => 0x895f, 0x3660 => 0x8b39, + 0x3661 => 0x8fd1, 0x3662 => 0x91d1, 0x3663 => 0x541f, 0x3664 => 0x9280, + 0x3665 => 0x4e5d, 0x3666 => 0x5036, 0x3667 => 0x53e5, 0x3668 => 0x533a, + 0x3669 => 0x72d7, 0x366a => 0x7396, 0x366b => 0x77e9, 0x366c => 0x82e6, + 0x366d => 0x8eaf, 0x366e => 0x99c6, 0x366f => 0x99c8, 0x3670 => 0x99d2, + 0x3671 => 0x5177, 0x3672 => 0x611a, 0x3673 => 0x865e, 0x3674 => 0x55b0, + 0x3675 => 0x7a7a, 0x3676 => 0x5076, 0x3677 => 0x5bd3, 0x3678 => 0x9047, + 0x3679 => 0x9685, 0x367a => 0x4e32, 0x367b => 0x6adb, 0x367c => 0x91e7, + 0x367d => 0x5c51, 0x367e => 0x5c48, 0x3721 => 0x6398, 0x3722 => 0x7a9f, + 0x3723 => 0x6c93, 0x3724 => 0x9774, 0x3725 => 0x8f61, 0x3726 => 0x7aaa, + 0x3727 => 0x718a, 0x3728 => 0x9688, 0x3729 => 0x7c82, 0x372a => 0x6817, + 0x372b => 0x7e70, 0x372c => 0x6851, 0x372d => 0x936c, 0x372e => 0x52f2, + 0x372f => 0x541b, 0x3730 => 0x85ab, 0x3731 => 0x8a13, 0x3732 => 0x7fa4, + 0x3733 => 0x8ecd, 0x3734 => 0x90e1, 0x3735 => 0x5366, 0x3736 => 0x8888, + 0x3737 => 0x7941, 0x3738 => 0x4fc2, 0x3739 => 0x50be, 0x373a => 0x5211, + 0x373b => 0x5144, 0x373c => 0x5553, 0x373d => 0x572d, 0x373e => 0x73ea, + 0x373f => 0x578b, 0x3740 => 0x5951, 0x3741 => 0x5f62, 0x3742 => 0x5f84, + 0x3743 => 0x6075, 0x3744 => 0x6176, 0x3745 => 0x6167, 0x3746 => 0x61a9, + 0x3747 => 0x63b2, 0x3748 => 0x643a, 0x3749 => 0x656c, 0x374a => 0x666f, + 0x374b => 0x6842, 0x374c => 0x6e13, 0x374d => 0x7566, 0x374e => 0x7a3d, + 0x374f => 0x7cfb, 0x3750 => 0x7d4c, 0x3751 => 0x7d99, 0x3752 => 0x7e4b, + 0x3753 => 0x7f6b, 0x3754 => 0x830e, 0x3755 => 0x834a, 0x3756 => 0x86cd, + 0x3757 => 0x8a08, 0x3758 => 0x8a63, 0x3759 => 0x8b66, 0x375a => 0x8efd, + 0x375b => 0x981a, 0x375c => 0x9d8f, 0x375d => 0x82b8, 0x375e => 0x8fce, + 0x375f => 0x9be8, 0x3760 => 0x5287, 0x3761 => 0x621f, 0x3762 => 0x6483, + 0x3763 => 0x6fc0, 0x3764 => 0x9699, 0x3765 => 0x6841, 0x3766 => 0x5091, + 0x3767 => 0x6b20, 0x3768 => 0x6c7a, 0x3769 => 0x6f54, 0x376a => 0x7a74, + 0x376b => 0x7d50, 0x376c => 0x8840, 0x376d => 0x8a23, 0x376e => 0x6708, + 0x376f => 0x4ef6, 0x3770 => 0x5039, 0x3771 => 0x5026, 0x3772 => 0x5065, + 0x3773 => 0x517c, 0x3774 => 0x5238, 0x3775 => 0x5263, 0x3776 => 0x55a7, + 0x3777 => 0x570f, 0x3778 => 0x5805, 0x3779 => 0x5acc, 0x377a => 0x5efa, + 0x377b => 0x61b2, 0x377c => 0x61f8, 0x377d => 0x62f3, 0x377e => 0x6372, + 0x3821 => 0x691c, 0x3822 => 0x6a29, 0x3823 => 0x727d, 0x3824 => 0x72ac, + 0x3825 => 0x732e, 0x3826 => 0x7814, 0x3827 => 0x786f, 0x3828 => 0x7d79, + 0x3829 => 0x770c, 0x382a => 0x80a9, 0x382b => 0x898b, 0x382c => 0x8b19, + 0x382d => 0x8ce2, 0x382e => 0x8ed2, 0x382f => 0x9063, 0x3830 => 0x9375, + 0x3831 => 0x967a, 0x3832 => 0x9855, 0x3833 => 0x9a13, 0x3834 => 0x9e78, + 0x3835 => 0x5143, 0x3836 => 0x539f, 0x3837 => 0x53b3, 0x3838 => 0x5e7b, + 0x3839 => 0x5f26, 0x383a => 0x6e1b, 0x383b => 0x6e90, 0x383c => 0x7384, + 0x383d => 0x73fe, 0x383e => 0x7d43, 0x383f => 0x8237, 0x3840 => 0x8a00, + 0x3841 => 0x8afa, 0x3842 => 0x9650, 0x3843 => 0x4e4e, 0x3844 => 0x500b, + 0x3845 => 0x53e4, 0x3846 => 0x547c, 0x3847 => 0x56fa, 0x3848 => 0x59d1, + 0x3849 => 0x5b64, 0x384a => 0x5df1, 0x384b => 0x5eab, 0x384c => 0x5f27, + 0x384d => 0x6238, 0x384e => 0x6545, 0x384f => 0x67af, 0x3850 => 0x6e56, + 0x3851 => 0x72d0, 0x3852 => 0x7cca, 0x3853 => 0x88b4, 0x3854 => 0x80a1, + 0x3855 => 0x80e1, 0x3856 => 0x83f0, 0x3857 => 0x864e, 0x3858 => 0x8a87, + 0x3859 => 0x8de8, 0x385a => 0x9237, 0x385b => 0x96c7, 0x385c => 0x9867, + 0x385d => 0x9f13, 0x385e => 0x4e94, 0x385f => 0x4e92, 0x3860 => 0x4f0d, + 0x3861 => 0x5348, 0x3862 => 0x5449, 0x3863 => 0x543e, 0x3864 => 0x5a2f, + 0x3865 => 0x5f8c, 0x3866 => 0x5fa1, 0x3867 => 0x609f, 0x3868 => 0x68a7, + 0x3869 => 0x6a8e, 0x386a => 0x745a, 0x386b => 0x7881, 0x386c => 0x8a9e, + 0x386d => 0x8aa4, 0x386e => 0x8b77, 0x386f => 0x9190, 0x3870 => 0x4e5e, + 0x3871 => 0x9bc9, 0x3872 => 0x4ea4, 0x3873 => 0x4f7c, 0x3874 => 0x4faf, + 0x3875 => 0x5019, 0x3876 => 0x5016, 0x3877 => 0x5149, 0x3878 => 0x516c, + 0x3879 => 0x529f, 0x387a => 0x52b9, 0x387b => 0x52fe, 0x387c => 0x539a, + 0x387d => 0x53e3, 0x387e => 0x5411, 0x3921 => 0x540e, 0x3922 => 0x5589, + 0x3923 => 0x5751, 0x3924 => 0x57a2, 0x3925 => 0x597d, 0x3926 => 0x5b54, + 0x3927 => 0x5b5d, 0x3928 => 0x5b8f, 0x3929 => 0x5de5, 0x392a => 0x5de7, + 0x392b => 0x5df7, 0x392c => 0x5e78, 0x392d => 0x5e83, 0x392e => 0x5e9a, + 0x392f => 0x5eb7, 0x3930 => 0x5f18, 0x3931 => 0x6052, 0x3932 => 0x614c, + 0x3933 => 0x6297, 0x3934 => 0x62d8, 0x3935 => 0x63a7, 0x3936 => 0x653b, + 0x3937 => 0x6602, 0x3938 => 0x6643, 0x3939 => 0x66f4, 0x393a => 0x676d, + 0x393b => 0x6821, 0x393c => 0x6897, 0x393d => 0x69cb, 0x393e => 0x6c5f, + 0x393f => 0x6d2a, 0x3940 => 0x6d69, 0x3941 => 0x6e2f, 0x3942 => 0x6e9d, + 0x3943 => 0x7532, 0x3944 => 0x7687, 0x3945 => 0x786c, 0x3946 => 0x7a3f, + 0x3947 => 0x7ce0, 0x3948 => 0x7d05, 0x3949 => 0x7d18, 0x394a => 0x7d5e, + 0x394b => 0x7db1, 0x394c => 0x8015, 0x394d => 0x8003, 0x394e => 0x80af, + 0x394f => 0x80b1, 0x3950 => 0x8154, 0x3951 => 0x818f, 0x3952 => 0x822a, + 0x3953 => 0x8352, 0x3954 => 0x884c, 0x3955 => 0x8861, 0x3956 => 0x8b1b, + 0x3957 => 0x8ca2, 0x3958 => 0x8cfc, 0x3959 => 0x90ca, 0x395a => 0x9175, + 0x395b => 0x9271, 0x395c => 0x783f, 0x395d => 0x92fc, 0x395e => 0x95a4, + 0x395f => 0x964d, 0x3960 => 0x9805, 0x3961 => 0x9999, 0x3962 => 0x9ad8, + 0x3963 => 0x9d3b, 0x3964 => 0x525b, 0x3965 => 0x52ab, 0x3966 => 0x53f7, + 0x3967 => 0x5408, 0x3968 => 0x58d5, 0x3969 => 0x62f7, 0x396a => 0x6fe0, + 0x396b => 0x8c6a, 0x396c => 0x8f5f, 0x396d => 0x9eb9, 0x396e => 0x514b, + 0x396f => 0x523b, 0x3970 => 0x544a, 0x3971 => 0x56fd, 0x3972 => 0x7a40, + 0x3973 => 0x9177, 0x3974 => 0x9d60, 0x3975 => 0x9ed2, 0x3976 => 0x7344, + 0x3977 => 0x6f09, 0x3978 => 0x8170, 0x3979 => 0x7511, 0x397a => 0x5ffd, + 0x397b => 0x60da, 0x397c => 0x9aa8, 0x397d => 0x72db, 0x397e => 0x8fbc, + 0x3a21 => 0x6b64, 0x3a22 => 0x9803, 0x3a23 => 0x4eca, 0x3a24 => 0x56f0, + 0x3a25 => 0x5764, 0x3a26 => 0x58be, 0x3a27 => 0x5a5a, 0x3a28 => 0x6068, + 0x3a29 => 0x61c7, 0x3a2a => 0x660f, 0x3a2b => 0x6606, 0x3a2c => 0x6839, + 0x3a2d => 0x68b1, 0x3a2e => 0x6df7, 0x3a2f => 0x75d5, 0x3a30 => 0x7d3a, + 0x3a31 => 0x826e, 0x3a32 => 0x9b42, 0x3a33 => 0x4e9b, 0x3a34 => 0x4f50, + 0x3a35 => 0x53c9, 0x3a36 => 0x5506, 0x3a37 => 0x5d6f, 0x3a38 => 0x5de6, + 0x3a39 => 0x5dee, 0x3a3a => 0x67fb, 0x3a3b => 0x6c99, 0x3a3c => 0x7473, + 0x3a3d => 0x7802, 0x3a3e => 0x8a50, 0x3a3f => 0x9396, 0x3a40 => 0x88df, + 0x3a41 => 0x5750, 0x3a42 => 0x5ea7, 0x3a43 => 0x632b, 0x3a44 => 0x50b5, + 0x3a45 => 0x50ac, 0x3a46 => 0x518d, 0x3a47 => 0x6700, 0x3a48 => 0x54c9, + 0x3a49 => 0x585e, 0x3a4a => 0x59bb, 0x3a4b => 0x5bb0, 0x3a4c => 0x5f69, + 0x3a4d => 0x624d, 0x3a4e => 0x63a1, 0x3a4f => 0x683d, 0x3a50 => 0x6b73, + 0x3a51 => 0x6e08, 0x3a52 => 0x707d, 0x3a53 => 0x91c7, 0x3a54 => 0x7280, + 0x3a55 => 0x7815, 0x3a56 => 0x7826, 0x3a57 => 0x796d, 0x3a58 => 0x658e, + 0x3a59 => 0x7d30, 0x3a5a => 0x83dc, 0x3a5b => 0x88c1, 0x3a5c => 0x8f09, + 0x3a5d => 0x969b, 0x3a5e => 0x5264, 0x3a5f => 0x5728, 0x3a60 => 0x6750, + 0x3a61 => 0x7f6a, 0x3a62 => 0x8ca1, 0x3a63 => 0x51b4, 0x3a64 => 0x5742, + 0x3a65 => 0x962a, 0x3a66 => 0x583a, 0x3a67 => 0x698a, 0x3a68 => 0x80b4, + 0x3a69 => 0x54b2, 0x3a6a => 0x5d0e, 0x3a6b => 0x57fc, 0x3a6c => 0x7895, + 0x3a6d => 0x9dfa, 0x3a6e => 0x4f5c, 0x3a6f => 0x524a, 0x3a70 => 0x548b, + 0x3a71 => 0x643e, 0x3a72 => 0x6628, 0x3a73 => 0x6714, 0x3a74 => 0x67f5, + 0x3a75 => 0x7a84, 0x3a76 => 0x7b56, 0x3a77 => 0x7d22, 0x3a78 => 0x932f, + 0x3a79 => 0x685c, 0x3a7a => 0x9bad, 0x3a7b => 0x7b39, 0x3a7c => 0x5319, + 0x3a7d => 0x518a, 0x3a7e => 0x5237, 0x3b21 => 0x5bdf, 0x3b22 => 0x62f6, + 0x3b23 => 0x64ae, 0x3b24 => 0x64e6, 0x3b25 => 0x672d, 0x3b26 => 0x6bba, + 0x3b27 => 0x85a9, 0x3b28 => 0x96d1, 0x3b29 => 0x7690, 0x3b2a => 0x9bd6, + 0x3b2b => 0x634c, 0x3b2c => 0x9306, 0x3b2d => 0x9bab, 0x3b2e => 0x76bf, + 0x3b2f => 0x6652, 0x3b30 => 0x4e09, 0x3b31 => 0x5098, 0x3b32 => 0x53c2, + 0x3b33 => 0x5c71, 0x3b34 => 0x60e8, 0x3b35 => 0x6492, 0x3b36 => 0x6563, + 0x3b37 => 0x685f, 0x3b38 => 0x71e6, 0x3b39 => 0x73ca, 0x3b3a => 0x7523, + 0x3b3b => 0x7b97, 0x3b3c => 0x7e82, 0x3b3d => 0x8695, 0x3b3e => 0x8b83, + 0x3b3f => 0x8cdb, 0x3b40 => 0x9178, 0x3b41 => 0x9910, 0x3b42 => 0x65ac, + 0x3b43 => 0x66ab, 0x3b44 => 0x6b8b, 0x3b45 => 0x4ed5, 0x3b46 => 0x4ed4, + 0x3b47 => 0x4f3a, 0x3b48 => 0x4f7f, 0x3b49 => 0x523a, 0x3b4a => 0x53f8, + 0x3b4b => 0x53f2, 0x3b4c => 0x55e3, 0x3b4d => 0x56db, 0x3b4e => 0x58eb, + 0x3b4f => 0x59cb, 0x3b50 => 0x59c9, 0x3b51 => 0x59ff, 0x3b52 => 0x5b50, + 0x3b53 => 0x5c4d, 0x3b54 => 0x5e02, 0x3b55 => 0x5e2b, 0x3b56 => 0x5fd7, + 0x3b57 => 0x601d, 0x3b58 => 0x6307, 0x3b59 => 0x652f, 0x3b5a => 0x5b5c, + 0x3b5b => 0x65af, 0x3b5c => 0x65bd, 0x3b5d => 0x65e8, 0x3b5e => 0x679d, + 0x3b5f => 0x6b62, 0x3b60 => 0x6b7b, 0x3b61 => 0x6c0f, 0x3b62 => 0x7345, + 0x3b63 => 0x7949, 0x3b64 => 0x79c1, 0x3b65 => 0x7cf8, 0x3b66 => 0x7d19, + 0x3b67 => 0x7d2b, 0x3b68 => 0x80a2, 0x3b69 => 0x8102, 0x3b6a => 0x81f3, + 0x3b6b => 0x8996, 0x3b6c => 0x8a5e, 0x3b6d => 0x8a69, 0x3b6e => 0x8a66, + 0x3b6f => 0x8a8c, 0x3b70 => 0x8aee, 0x3b71 => 0x8cc7, 0x3b72 => 0x8cdc, + 0x3b73 => 0x96cc, 0x3b74 => 0x98fc, 0x3b75 => 0x6b6f, 0x3b76 => 0x4e8b, + 0x3b77 => 0x4f3c, 0x3b78 => 0x4f8d, 0x3b79 => 0x5150, 0x3b7a => 0x5b57, + 0x3b7b => 0x5bfa, 0x3b7c => 0x6148, 0x3b7d => 0x6301, 0x3b7e => 0x6642, + 0x3c21 => 0x6b21, 0x3c22 => 0x6ecb, 0x3c23 => 0x6cbb, 0x3c24 => 0x723e, + 0x3c25 => 0x74bd, 0x3c26 => 0x75d4, 0x3c27 => 0x78c1, 0x3c28 => 0x793a, + 0x3c29 => 0x800c, 0x3c2a => 0x8033, 0x3c2b => 0x81ea, 0x3c2c => 0x8494, + 0x3c2d => 0x8f9e, 0x3c2e => 0x6c50, 0x3c2f => 0x9e7f, 0x3c30 => 0x5f0f, + 0x3c31 => 0x8b58, 0x3c32 => 0x9d2b, 0x3c33 => 0x7afa, 0x3c34 => 0x8ef8, + 0x3c35 => 0x5b8d, 0x3c36 => 0x96eb, 0x3c37 => 0x4e03, 0x3c38 => 0x53f1, + 0x3c39 => 0x57f7, 0x3c3a => 0x5931, 0x3c3b => 0x5ac9, 0x3c3c => 0x5ba4, + 0x3c3d => 0x6089, 0x3c3e => 0x6e7f, 0x3c3f => 0x6f06, 0x3c40 => 0x75be, + 0x3c41 => 0x8cea, 0x3c42 => 0x5b9f, 0x3c43 => 0x8500, 0x3c44 => 0x7be0, + 0x3c45 => 0x5072, 0x3c46 => 0x67f4, 0x3c47 => 0x829d, 0x3c48 => 0x5c61, + 0x3c49 => 0x854a, 0x3c4a => 0x7e1e, 0x3c4b => 0x820e, 0x3c4c => 0x5199, + 0x3c4d => 0x5c04, 0x3c4e => 0x6368, 0x3c4f => 0x8d66, 0x3c50 => 0x659c, + 0x3c51 => 0x716e, 0x3c52 => 0x793e, 0x3c53 => 0x7d17, 0x3c54 => 0x8005, + 0x3c55 => 0x8b1d, 0x3c56 => 0x8eca, 0x3c57 => 0x906e, 0x3c58 => 0x86c7, + 0x3c59 => 0x90aa, 0x3c5a => 0x501f, 0x3c5b => 0x52fa, 0x3c5c => 0x5c3a, + 0x3c5d => 0x6753, 0x3c5e => 0x707c, 0x3c5f => 0x7235, 0x3c60 => 0x914c, + 0x3c61 => 0x91c8, 0x3c62 => 0x932b, 0x3c63 => 0x82e5, 0x3c64 => 0x5bc2, + 0x3c65 => 0x5f31, 0x3c66 => 0x60f9, 0x3c67 => 0x4e3b, 0x3c68 => 0x53d6, + 0x3c69 => 0x5b88, 0x3c6a => 0x624b, 0x3c6b => 0x6731, 0x3c6c => 0x6b8a, + 0x3c6d => 0x72e9, 0x3c6e => 0x73e0, 0x3c6f => 0x7a2e, 0x3c70 => 0x816b, + 0x3c71 => 0x8da3, 0x3c72 => 0x9152, 0x3c73 => 0x9996, 0x3c74 => 0x5112, + 0x3c75 => 0x53d7, 0x3c76 => 0x546a, 0x3c77 => 0x5bff, 0x3c78 => 0x6388, + 0x3c79 => 0x6a39, 0x3c7a => 0x7dac, 0x3c7b => 0x9700, 0x3c7c => 0x56da, + 0x3c7d => 0x53ce, 0x3c7e => 0x5468, 0x3d21 => 0x5b97, 0x3d22 => 0x5c31, + 0x3d23 => 0x5dde, 0x3d24 => 0x4fee, 0x3d25 => 0x6101, 0x3d26 => 0x62fe, + 0x3d27 => 0x6d32, 0x3d28 => 0x79c0, 0x3d29 => 0x79cb, 0x3d2a => 0x7d42, + 0x3d2b => 0x7e4d, 0x3d2c => 0x7fd2, 0x3d2d => 0x81ed, 0x3d2e => 0x821f, + 0x3d2f => 0x8490, 0x3d30 => 0x8846, 0x3d31 => 0x8972, 0x3d32 => 0x8b90, + 0x3d33 => 0x8e74, 0x3d34 => 0x8f2f, 0x3d35 => 0x9031, 0x3d36 => 0x914b, + 0x3d37 => 0x916c, 0x3d38 => 0x96c6, 0x3d39 => 0x919c, 0x3d3a => 0x4ec0, + 0x3d3b => 0x4f4f, 0x3d3c => 0x5145, 0x3d3d => 0x5341, 0x3d3e => 0x5f93, + 0x3d3f => 0x620e, 0x3d40 => 0x67d4, 0x3d41 => 0x6c41, 0x3d42 => 0x6e0b, + 0x3d43 => 0x7363, 0x3d44 => 0x7e26, 0x3d45 => 0x91cd, 0x3d46 => 0x9283, + 0x3d47 => 0x53d4, 0x3d48 => 0x5919, 0x3d49 => 0x5bbf, 0x3d4a => 0x6dd1, + 0x3d4b => 0x795d, 0x3d4c => 0x7e2e, 0x3d4d => 0x7c9b, 0x3d4e => 0x587e, + 0x3d4f => 0x719f, 0x3d50 => 0x51fa, 0x3d51 => 0x8853, 0x3d52 => 0x8ff0, + 0x3d53 => 0x4fca, 0x3d54 => 0x5cfb, 0x3d55 => 0x6625, 0x3d56 => 0x77ac, + 0x3d57 => 0x7ae3, 0x3d58 => 0x821c, 0x3d59 => 0x99ff, 0x3d5a => 0x51c6, + 0x3d5b => 0x5faa, 0x3d5c => 0x65ec, 0x3d5d => 0x696f, 0x3d5e => 0x6b89, + 0x3d5f => 0x6df3, 0x3d60 => 0x6e96, 0x3d61 => 0x6f64, 0x3d62 => 0x76fe, + 0x3d63 => 0x7d14, 0x3d64 => 0x5de1, 0x3d65 => 0x9075, 0x3d66 => 0x9187, + 0x3d67 => 0x9806, 0x3d68 => 0x51e6, 0x3d69 => 0x521d, 0x3d6a => 0x6240, + 0x3d6b => 0x6691, 0x3d6c => 0x66d9, 0x3d6d => 0x6e1a, 0x3d6e => 0x5eb6, + 0x3d6f => 0x7dd2, 0x3d70 => 0x7f72, 0x3d71 => 0x66f8, 0x3d72 => 0x85af, + 0x3d73 => 0x85f7, 0x3d74 => 0x8af8, 0x3d75 => 0x52a9, 0x3d76 => 0x53d9, + 0x3d77 => 0x5973, 0x3d78 => 0x5e8f, 0x3d79 => 0x5f90, 0x3d7a => 0x6055, + 0x3d7b => 0x92e4, 0x3d7c => 0x9664, 0x3d7d => 0x50b7, 0x3d7e => 0x511f, + 0x3e21 => 0x52dd, 0x3e22 => 0x5320, 0x3e23 => 0x5347, 0x3e24 => 0x53ec, + 0x3e25 => 0x54e8, 0x3e26 => 0x5546, 0x3e27 => 0x5531, 0x3e28 => 0x5617, + 0x3e29 => 0x5968, 0x3e2a => 0x59be, 0x3e2b => 0x5a3c, 0x3e2c => 0x5bb5, + 0x3e2d => 0x5c06, 0x3e2e => 0x5c0f, 0x3e2f => 0x5c11, 0x3e30 => 0x5c1a, + 0x3e31 => 0x5e84, 0x3e32 => 0x5e8a, 0x3e33 => 0x5ee0, 0x3e34 => 0x5f70, + 0x3e35 => 0x627f, 0x3e36 => 0x6284, 0x3e37 => 0x62db, 0x3e38 => 0x638c, + 0x3e39 => 0x6377, 0x3e3a => 0x6607, 0x3e3b => 0x660c, 0x3e3c => 0x662d, + 0x3e3d => 0x6676, 0x3e3e => 0x677e, 0x3e3f => 0x68a2, 0x3e40 => 0x6a1f, + 0x3e41 => 0x6a35, 0x3e42 => 0x6cbc, 0x3e43 => 0x6d88, 0x3e44 => 0x6e09, + 0x3e45 => 0x6e58, 0x3e46 => 0x713c, 0x3e47 => 0x7126, 0x3e48 => 0x7167, + 0x3e49 => 0x75c7, 0x3e4a => 0x7701, 0x3e4b => 0x785d, 0x3e4c => 0x7901, + 0x3e4d => 0x7965, 0x3e4e => 0x79f0, 0x3e4f => 0x7ae0, 0x3e50 => 0x7b11, + 0x3e51 => 0x7ca7, 0x3e52 => 0x7d39, 0x3e53 => 0x8096, 0x3e54 => 0x83d6, + 0x3e55 => 0x848b, 0x3e56 => 0x8549, 0x3e57 => 0x885d, 0x3e58 => 0x88f3, + 0x3e59 => 0x8a1f, 0x3e5a => 0x8a3c, 0x3e5b => 0x8a54, 0x3e5c => 0x8a73, + 0x3e5d => 0x8c61, 0x3e5e => 0x8cde, 0x3e5f => 0x91a4, 0x3e60 => 0x9266, + 0x3e61 => 0x937e, 0x3e62 => 0x9418, 0x3e63 => 0x969c, 0x3e64 => 0x9798, + 0x3e65 => 0x4e0a, 0x3e66 => 0x4e08, 0x3e67 => 0x4e1e, 0x3e68 => 0x4e57, + 0x3e69 => 0x5197, 0x3e6a => 0x5270, 0x3e6b => 0x57ce, 0x3e6c => 0x5834, + 0x3e6d => 0x58cc, 0x3e6e => 0x5b22, 0x3e6f => 0x5e38, 0x3e70 => 0x60c5, + 0x3e71 => 0x64fe, 0x3e72 => 0x6761, 0x3e73 => 0x6756, 0x3e74 => 0x6d44, + 0x3e75 => 0x72b6, 0x3e76 => 0x7573, 0x3e77 => 0x7a63, 0x3e78 => 0x84b8, + 0x3e79 => 0x8b72, 0x3e7a => 0x91b8, 0x3e7b => 0x9320, 0x3e7c => 0x5631, + 0x3e7d => 0x57f4, 0x3e7e => 0x98fe, 0x3f21 => 0x62ed, 0x3f22 => 0x690d, + 0x3f23 => 0x6b96, 0x3f24 => 0x71ed, 0x3f25 => 0x7e54, 0x3f26 => 0x8077, + 0x3f27 => 0x8272, 0x3f28 => 0x89e6, 0x3f29 => 0x98df, 0x3f2a => 0x8755, + 0x3f2b => 0x8fb1, 0x3f2c => 0x5c3b, 0x3f2d => 0x4f38, 0x3f2e => 0x4fe1, + 0x3f2f => 0x4fb5, 0x3f30 => 0x5507, 0x3f31 => 0x5a20, 0x3f32 => 0x5bdd, + 0x3f33 => 0x5be9, 0x3f34 => 0x5fc3, 0x3f35 => 0x614e, 0x3f36 => 0x632f, + 0x3f37 => 0x65b0, 0x3f38 => 0x664b, 0x3f39 => 0x68ee, 0x3f3a => 0x699b, + 0x3f3b => 0x6d78, 0x3f3c => 0x6df1, 0x3f3d => 0x7533, 0x3f3e => 0x75b9, + 0x3f3f => 0x771f, 0x3f40 => 0x795e, 0x3f41 => 0x79e6, 0x3f42 => 0x7d33, + 0x3f43 => 0x81e3, 0x3f44 => 0x82af, 0x3f45 => 0x85aa, 0x3f46 => 0x89aa, + 0x3f47 => 0x8a3a, 0x3f48 => 0x8eab, 0x3f49 => 0x8f9b, 0x3f4a => 0x9032, + 0x3f4b => 0x91dd, 0x3f4c => 0x9707, 0x3f4d => 0x4eba, 0x3f4e => 0x4ec1, + 0x3f4f => 0x5203, 0x3f50 => 0x5875, 0x3f51 => 0x58ec, 0x3f52 => 0x5c0b, + 0x3f53 => 0x751a, 0x3f54 => 0x5c3d, 0x3f55 => 0x814e, 0x3f56 => 0x8a0a, + 0x3f57 => 0x8fc5, 0x3f58 => 0x9663, 0x3f59 => 0x976d, 0x3f5a => 0x7b25, + 0x3f5b => 0x8acf, 0x3f5c => 0x9808, 0x3f5d => 0x9162, 0x3f5e => 0x56f3, + 0x3f5f => 0x53a8, 0x3f60 => 0x9017, 0x3f61 => 0x5439, 0x3f62 => 0x5782, + 0x3f63 => 0x5e25, 0x3f64 => 0x63a8, 0x3f65 => 0x6c34, 0x3f66 => 0x708a, + 0x3f67 => 0x7761, 0x3f68 => 0x7c8b, 0x3f69 => 0x7fe0, 0x3f6a => 0x8870, + 0x3f6b => 0x9042, 0x3f6c => 0x9154, 0x3f6d => 0x9310, 0x3f6e => 0x9318, + 0x3f6f => 0x968f, 0x3f70 => 0x745e, 0x3f71 => 0x9ac4, 0x3f72 => 0x5d07, + 0x3f73 => 0x5d69, 0x3f74 => 0x6570, 0x3f75 => 0x67a2, 0x3f76 => 0x8da8, + 0x3f77 => 0x96db, 0x3f78 => 0x636e, 0x3f79 => 0x6749, 0x3f7a => 0x6919, + 0x3f7b => 0x83c5, 0x3f7c => 0x9817, 0x3f7d => 0x96c0, 0x3f7e => 0x88fe, + 0x4021 => 0x6f84, 0x4022 => 0x647a, 0x4023 => 0x5bf8, 0x4024 => 0x4e16, + 0x4025 => 0x702c, 0x4026 => 0x755d, 0x4027 => 0x662f, 0x4028 => 0x51c4, + 0x4029 => 0x5236, 0x402a => 0x52e2, 0x402b => 0x59d3, 0x402c => 0x5f81, + 0x402d => 0x6027, 0x402e => 0x6210, 0x402f => 0x653f, 0x4030 => 0x6574, + 0x4031 => 0x661f, 0x4032 => 0x6674, 0x4033 => 0x68f2, 0x4034 => 0x6816, + 0x4035 => 0x6b63, 0x4036 => 0x6e05, 0x4037 => 0x7272, 0x4038 => 0x751f, + 0x4039 => 0x76db, 0x403a => 0x7cbe, 0x403b => 0x8056, 0x403c => 0x58f0, + 0x403d => 0x88fd, 0x403e => 0x897f, 0x403f => 0x8aa0, 0x4040 => 0x8a93, + 0x4041 => 0x8acb, 0x4042 => 0x901d, 0x4043 => 0x9192, 0x4044 => 0x9752, + 0x4045 => 0x9759, 0x4046 => 0x6589, 0x4047 => 0x7a0e, 0x4048 => 0x8106, + 0x4049 => 0x96bb, 0x404a => 0x5e2d, 0x404b => 0x60dc, 0x404c => 0x621a, + 0x404d => 0x65a5, 0x404e => 0x6614, 0x404f => 0x6790, 0x4050 => 0x77f3, + 0x4051 => 0x7a4d, 0x4052 => 0x7c4d, 0x4053 => 0x7e3e, 0x4054 => 0x810a, + 0x4055 => 0x8cac, 0x4056 => 0x8d64, 0x4057 => 0x8de1, 0x4058 => 0x8e5f, + 0x4059 => 0x78a9, 0x405a => 0x5207, 0x405b => 0x62d9, 0x405c => 0x63a5, + 0x405d => 0x6442, 0x405e => 0x6298, 0x405f => 0x8a2d, 0x4060 => 0x7a83, + 0x4061 => 0x7bc0, 0x4062 => 0x8aac, 0x4063 => 0x96ea, 0x4064 => 0x7d76, + 0x4065 => 0x820c, 0x4066 => 0x8749, 0x4067 => 0x4ed9, 0x4068 => 0x5148, + 0x4069 => 0x5343, 0x406a => 0x5360, 0x406b => 0x5ba3, 0x406c => 0x5c02, + 0x406d => 0x5c16, 0x406e => 0x5ddd, 0x406f => 0x6226, 0x4070 => 0x6247, + 0x4071 => 0x64b0, 0x4072 => 0x6813, 0x4073 => 0x6834, 0x4074 => 0x6cc9, + 0x4075 => 0x6d45, 0x4076 => 0x6d17, 0x4077 => 0x67d3, 0x4078 => 0x6f5c, + 0x4079 => 0x714e, 0x407a => 0x717d, 0x407b => 0x65cb, 0x407c => 0x7a7f, + 0x407d => 0x7bad, 0x407e => 0x7dda, 0x4121 => 0x7e4a, 0x4122 => 0x7fa8, + 0x4123 => 0x817a, 0x4124 => 0x821b, 0x4125 => 0x8239, 0x4126 => 0x85a6, + 0x4127 => 0x8a6e, 0x4128 => 0x8cce, 0x4129 => 0x8df5, 0x412a => 0x9078, + 0x412b => 0x9077, 0x412c => 0x92ad, 0x412d => 0x9291, 0x412e => 0x9583, + 0x412f => 0x9bae, 0x4130 => 0x524d, 0x4131 => 0x5584, 0x4132 => 0x6f38, + 0x4133 => 0x7136, 0x4134 => 0x5168, 0x4135 => 0x7985, 0x4136 => 0x7e55, + 0x4137 => 0x81b3, 0x4138 => 0x7cce, 0x4139 => 0x564c, 0x413a => 0x5851, + 0x413b => 0x5ca8, 0x413c => 0x63aa, 0x413d => 0x66fe, 0x413e => 0x66fd, + 0x413f => 0x695a, 0x4140 => 0x72d9, 0x4141 => 0x758f, 0x4142 => 0x758e, + 0x4143 => 0x790e, 0x4144 => 0x7956, 0x4145 => 0x79df, 0x4146 => 0x7c97, + 0x4147 => 0x7d20, 0x4148 => 0x7d44, 0x4149 => 0x8607, 0x414a => 0x8a34, + 0x414b => 0x963b, 0x414c => 0x9061, 0x414d => 0x9f20, 0x414e => 0x50e7, + 0x414f => 0x5275, 0x4150 => 0x53cc, 0x4151 => 0x53e2, 0x4152 => 0x5009, + 0x4153 => 0x55aa, 0x4154 => 0x58ee, 0x4155 => 0x594f, 0x4156 => 0x723d, + 0x4157 => 0x5b8b, 0x4158 => 0x5c64, 0x4159 => 0x531d, 0x415a => 0x60e3, + 0x415b => 0x60f3, 0x415c => 0x635c, 0x415d => 0x6383, 0x415e => 0x633f, + 0x415f => 0x63bb, 0x4160 => 0x64cd, 0x4161 => 0x65e9, 0x4162 => 0x66f9, + 0x4163 => 0x5de3, 0x4164 => 0x69cd, 0x4165 => 0x69fd, 0x4166 => 0x6f15, + 0x4167 => 0x71e5, 0x4168 => 0x4e89, 0x4169 => 0x75e9, 0x416a => 0x76f8, + 0x416b => 0x7a93, 0x416c => 0x7cdf, 0x416d => 0x7dcf, 0x416e => 0x7d9c, + 0x416f => 0x8061, 0x4170 => 0x8349, 0x4171 => 0x8358, 0x4172 => 0x846c, + 0x4173 => 0x84bc, 0x4174 => 0x85fb, 0x4175 => 0x88c5, 0x4176 => 0x8d70, + 0x4177 => 0x9001, 0x4178 => 0x906d, 0x4179 => 0x9397, 0x417a => 0x971c, + 0x417b => 0x9a12, 0x417c => 0x50cf, 0x417d => 0x5897, 0x417e => 0x618e, + 0x4221 => 0x81d3, 0x4222 => 0x8535, 0x4223 => 0x8d08, 0x4224 => 0x9020, + 0x4225 => 0x4fc3, 0x4226 => 0x5074, 0x4227 => 0x5247, 0x4228 => 0x5373, + 0x4229 => 0x606f, 0x422a => 0x6349, 0x422b => 0x675f, 0x422c => 0x6e2c, + 0x422d => 0x8db3, 0x422e => 0x901f, 0x422f => 0x4fd7, 0x4230 => 0x5c5e, + 0x4231 => 0x8cca, 0x4232 => 0x65cf, 0x4233 => 0x7d9a, 0x4234 => 0x5352, + 0x4235 => 0x8896, 0x4236 => 0x5176, 0x4237 => 0x63c3, 0x4238 => 0x5b58, + 0x4239 => 0x5b6b, 0x423a => 0x5c0a, 0x423b => 0x640d, 0x423c => 0x6751, + 0x423d => 0x905c, 0x423e => 0x4ed6, 0x423f => 0x591a, 0x4240 => 0x592a, + 0x4241 => 0x6c70, 0x4242 => 0x8a51, 0x4243 => 0x553e, 0x4244 => 0x5815, + 0x4245 => 0x59a5, 0x4246 => 0x60f0, 0x4247 => 0x6253, 0x4248 => 0x67c1, + 0x4249 => 0x8235, 0x424a => 0x6955, 0x424b => 0x9640, 0x424c => 0x99c4, + 0x424d => 0x9a28, 0x424e => 0x4f53, 0x424f => 0x5806, 0x4250 => 0x5bfe, + 0x4251 => 0x8010, 0x4252 => 0x5cb1, 0x4253 => 0x5e2f, 0x4254 => 0x5f85, + 0x4255 => 0x6020, 0x4256 => 0x614b, 0x4257 => 0x6234, 0x4258 => 0x66ff, + 0x4259 => 0x6cf0, 0x425a => 0x6ede, 0x425b => 0x80ce, 0x425c => 0x817f, + 0x425d => 0x82d4, 0x425e => 0x888b, 0x425f => 0x8cb8, 0x4260 => 0x9000, + 0x4261 => 0x902e, 0x4262 => 0x968a, 0x4263 => 0x9edb, 0x4264 => 0x9bdb, + 0x4265 => 0x4ee3, 0x4266 => 0x53f0, 0x4267 => 0x5927, 0x4268 => 0x7b2c, + 0x4269 => 0x918d, 0x426a => 0x984c, 0x426b => 0x9df9, 0x426c => 0x6edd, + 0x426d => 0x7027, 0x426e => 0x5353, 0x426f => 0x5544, 0x4270 => 0x5b85, + 0x4271 => 0x6258, 0x4272 => 0x629e, 0x4273 => 0x62d3, 0x4274 => 0x6ca2, + 0x4275 => 0x6fef, 0x4276 => 0x7422, 0x4277 => 0x8a17, 0x4278 => 0x9438, + 0x4279 => 0x6fc1, 0x427a => 0x8afe, 0x427b => 0x8338, 0x427c => 0x51e7, + 0x427d => 0x86f8, 0x427e => 0x53ea, 0x4321 => 0x53e9, 0x4322 => 0x4f46, + 0x4323 => 0x9054, 0x4324 => 0x8fb0, 0x4325 => 0x596a, 0x4326 => 0x8131, + 0x4327 => 0x5dfd, 0x4328 => 0x7aea, 0x4329 => 0x8fbf, 0x432a => 0x68da, + 0x432b => 0x8c37, 0x432c => 0x72f8, 0x432d => 0x9c48, 0x432e => 0x6a3d, + 0x432f => 0x8ab0, 0x4330 => 0x4e39, 0x4331 => 0x5358, 0x4332 => 0x5606, + 0x4333 => 0x5766, 0x4334 => 0x62c5, 0x4335 => 0x63a2, 0x4336 => 0x65e6, + 0x4337 => 0x6b4e, 0x4338 => 0x6de1, 0x4339 => 0x6e5b, 0x433a => 0x70ad, + 0x433b => 0x77ed, 0x433c => 0x7aef, 0x433d => 0x7baa, 0x433e => 0x7dbb, + 0x433f => 0x803d, 0x4340 => 0x80c6, 0x4341 => 0x86cb, 0x4342 => 0x8a95, + 0x4343 => 0x935b, 0x4344 => 0x56e3, 0x4345 => 0x58c7, 0x4346 => 0x5f3e, + 0x4347 => 0x65ad, 0x4348 => 0x6696, 0x4349 => 0x6a80, 0x434a => 0x6bb5, + 0x434b => 0x7537, 0x434c => 0x8ac7, 0x434d => 0x5024, 0x434e => 0x77e5, + 0x434f => 0x5730, 0x4350 => 0x5f1b, 0x4351 => 0x6065, 0x4352 => 0x667a, + 0x4353 => 0x6c60, 0x4354 => 0x75f4, 0x4355 => 0x7a1a, 0x4356 => 0x7f6e, + 0x4357 => 0x81f4, 0x4358 => 0x8718, 0x4359 => 0x9045, 0x435a => 0x99b3, + 0x435b => 0x7bc9, 0x435c => 0x755c, 0x435d => 0x7af9, 0x435e => 0x7b51, + 0x435f => 0x84c4, 0x4360 => 0x9010, 0x4361 => 0x79e9, 0x4362 => 0x7a92, + 0x4363 => 0x8336, 0x4364 => 0x5ae1, 0x4365 => 0x7740, 0x4366 => 0x4e2d, + 0x4367 => 0x4ef2, 0x4368 => 0x5b99, 0x4369 => 0x5fe0, 0x436a => 0x62bd, + 0x436b => 0x663c, 0x436c => 0x67f1, 0x436d => 0x6ce8, 0x436e => 0x866b, + 0x436f => 0x8877, 0x4370 => 0x8a3b, 0x4371 => 0x914e, 0x4372 => 0x92f3, + 0x4373 => 0x99d0, 0x4374 => 0x6a17, 0x4375 => 0x7026, 0x4376 => 0x732a, + 0x4377 => 0x82e7, 0x4378 => 0x8457, 0x4379 => 0x8caf, 0x437a => 0x4e01, + 0x437b => 0x5146, 0x437c => 0x51cb, 0x437d => 0x558b, 0x437e => 0x5bf5, + 0x4421 => 0x5e16, 0x4422 => 0x5e33, 0x4423 => 0x5e81, 0x4424 => 0x5f14, + 0x4425 => 0x5f35, 0x4426 => 0x5f6b, 0x4427 => 0x5fb4, 0x4428 => 0x61f2, + 0x4429 => 0x6311, 0x442a => 0x66a2, 0x442b => 0x671d, 0x442c => 0x6f6e, + 0x442d => 0x7252, 0x442e => 0x753a, 0x442f => 0x773a, 0x4430 => 0x8074, + 0x4431 => 0x8139, 0x4432 => 0x8178, 0x4433 => 0x8776, 0x4434 => 0x8abf, + 0x4435 => 0x8adc, 0x4436 => 0x8d85, 0x4437 => 0x8df3, 0x4438 => 0x929a, + 0x4439 => 0x9577, 0x443a => 0x9802, 0x443b => 0x9ce5, 0x443c => 0x52c5, + 0x443d => 0x6357, 0x443e => 0x76f4, 0x443f => 0x6715, 0x4440 => 0x6c88, + 0x4441 => 0x73cd, 0x4442 => 0x8cc3, 0x4443 => 0x93ae, 0x4444 => 0x9673, + 0x4445 => 0x6d25, 0x4446 => 0x589c, 0x4447 => 0x690e, 0x4448 => 0x69cc, + 0x4449 => 0x8ffd, 0x444a => 0x939a, 0x444b => 0x75db, 0x444c => 0x901a, + 0x444d => 0x585a, 0x444e => 0x6802, 0x444f => 0x63b4, 0x4450 => 0x69fb, + 0x4451 => 0x4f43, 0x4452 => 0x6f2c, 0x4453 => 0x67d8, 0x4454 => 0x8fbb, + 0x4455 => 0x8526, 0x4456 => 0x7db4, 0x4457 => 0x9354, 0x4458 => 0x693f, + 0x4459 => 0x6f70, 0x445a => 0x576a, 0x445b => 0x58f7, 0x445c => 0x5b2c, + 0x445d => 0x7d2c, 0x445e => 0x722a, 0x445f => 0x540a, 0x4460 => 0x91e3, + 0x4461 => 0x9db4, 0x4462 => 0x4ead, 0x4463 => 0x4f4e, 0x4464 => 0x505c, + 0x4465 => 0x5075, 0x4466 => 0x5243, 0x4467 => 0x8c9e, 0x4468 => 0x5448, + 0x4469 => 0x5824, 0x446a => 0x5b9a, 0x446b => 0x5e1d, 0x446c => 0x5e95, + 0x446d => 0x5ead, 0x446e => 0x5ef7, 0x446f => 0x5f1f, 0x4470 => 0x608c, + 0x4471 => 0x62b5, 0x4472 => 0x633a, 0x4473 => 0x63d0, 0x4474 => 0x68af, + 0x4475 => 0x6c40, 0x4476 => 0x7887, 0x4477 => 0x798e, 0x4478 => 0x7a0b, + 0x4479 => 0x7de0, 0x447a => 0x8247, 0x447b => 0x8a02, 0x447c => 0x8ae6, + 0x447d => 0x8e44, 0x447e => 0x9013, 0x4521 => 0x90b8, 0x4522 => 0x912d, + 0x4523 => 0x91d8, 0x4524 => 0x9f0e, 0x4525 => 0x6ce5, 0x4526 => 0x6458, + 0x4527 => 0x64e2, 0x4528 => 0x6575, 0x4529 => 0x6ef4, 0x452a => 0x7684, + 0x452b => 0x7b1b, 0x452c => 0x9069, 0x452d => 0x93d1, 0x452e => 0x6eba, + 0x452f => 0x54f2, 0x4530 => 0x5fb9, 0x4531 => 0x64a4, 0x4532 => 0x8f4d, + 0x4533 => 0x8fed, 0x4534 => 0x9244, 0x4535 => 0x5178, 0x4536 => 0x586b, + 0x4537 => 0x5929, 0x4538 => 0x5c55, 0x4539 => 0x5e97, 0x453a => 0x6dfb, + 0x453b => 0x7e8f, 0x453c => 0x751c, 0x453d => 0x8cbc, 0x453e => 0x8ee2, + 0x453f => 0x985b, 0x4540 => 0x70b9, 0x4541 => 0x4f1d, 0x4542 => 0x6bbf, + 0x4543 => 0x6fb1, 0x4544 => 0x7530, 0x4545 => 0x96fb, 0x4546 => 0x514e, + 0x4547 => 0x5410, 0x4548 => 0x5835, 0x4549 => 0x5857, 0x454a => 0x59ac, + 0x454b => 0x5c60, 0x454c => 0x5f92, 0x454d => 0x6597, 0x454e => 0x675c, + 0x454f => 0x6e21, 0x4550 => 0x767b, 0x4551 => 0x83df, 0x4552 => 0x8ced, + 0x4553 => 0x9014, 0x4554 => 0x90fd, 0x4555 => 0x934d, 0x4556 => 0x7825, + 0x4557 => 0x783a, 0x4558 => 0x52aa, 0x4559 => 0x5ea6, 0x455a => 0x571f, + 0x455b => 0x5974, 0x455c => 0x6012, 0x455d => 0x5012, 0x455e => 0x515a, + 0x455f => 0x51ac, 0x4560 => 0x51cd, 0x4561 => 0x5200, 0x4562 => 0x5510, + 0x4563 => 0x5854, 0x4564 => 0x5858, 0x4565 => 0x5957, 0x4566 => 0x5b95, + 0x4567 => 0x5cf6, 0x4568 => 0x5d8b, 0x4569 => 0x60bc, 0x456a => 0x6295, + 0x456b => 0x642d, 0x456c => 0x6771, 0x456d => 0x6843, 0x456e => 0x68bc, + 0x456f => 0x68df, 0x4570 => 0x76d7, 0x4571 => 0x6dd8, 0x4572 => 0x6e6f, + 0x4573 => 0x6d9b, 0x4574 => 0x706f, 0x4575 => 0x71c8, 0x4576 => 0x5f53, + 0x4577 => 0x75d8, 0x4578 => 0x7977, 0x4579 => 0x7b49, 0x457a => 0x7b54, + 0x457b => 0x7b52, 0x457c => 0x7cd6, 0x457d => 0x7d71, 0x457e => 0x5230, + 0x4621 => 0x8463, 0x4622 => 0x8569, 0x4623 => 0x85e4, 0x4624 => 0x8a0e, + 0x4625 => 0x8b04, 0x4626 => 0x8c46, 0x4627 => 0x8e0f, 0x4628 => 0x9003, + 0x4629 => 0x900f, 0x462a => 0x9419, 0x462b => 0x9676, 0x462c => 0x982d, + 0x462d => 0x9a30, 0x462e => 0x95d8, 0x462f => 0x50cd, 0x4630 => 0x52d5, + 0x4631 => 0x540c, 0x4632 => 0x5802, 0x4633 => 0x5c0e, 0x4634 => 0x61a7, + 0x4635 => 0x649e, 0x4636 => 0x6d1e, 0x4637 => 0x77b3, 0x4638 => 0x7ae5, + 0x4639 => 0x80f4, 0x463a => 0x8404, 0x463b => 0x9053, 0x463c => 0x9285, + 0x463d => 0x5ce0, 0x463e => 0x9d07, 0x463f => 0x533f, 0x4640 => 0x5f97, + 0x4641 => 0x5fb3, 0x4642 => 0x6d9c, 0x4643 => 0x7279, 0x4644 => 0x7763, + 0x4645 => 0x79bf, 0x4646 => 0x7be4, 0x4647 => 0x6bd2, 0x4648 => 0x72ec, + 0x4649 => 0x8aad, 0x464a => 0x6803, 0x464b => 0x6a61, 0x464c => 0x51f8, + 0x464d => 0x7a81, 0x464e => 0x6934, 0x464f => 0x5c4a, 0x4650 => 0x9cf6, + 0x4651 => 0x82eb, 0x4652 => 0x5bc5, 0x4653 => 0x9149, 0x4654 => 0x701e, + 0x4655 => 0x5678, 0x4656 => 0x5c6f, 0x4657 => 0x60c7, 0x4658 => 0x6566, + 0x4659 => 0x6c8c, 0x465a => 0x8c5a, 0x465b => 0x9041, 0x465c => 0x9813, + 0x465d => 0x5451, 0x465e => 0x66c7, 0x465f => 0x920d, 0x4660 => 0x5948, + 0x4661 => 0x90a3, 0x4662 => 0x5185, 0x4663 => 0x4e4d, 0x4664 => 0x51ea, + 0x4665 => 0x8599, 0x4666 => 0x8b0e, 0x4667 => 0x7058, 0x4668 => 0x637a, + 0x4669 => 0x934b, 0x466a => 0x6962, 0x466b => 0x99b4, 0x466c => 0x7e04, + 0x466d => 0x7577, 0x466e => 0x5357, 0x466f => 0x6960, 0x4670 => 0x8edf, + 0x4671 => 0x96e3, 0x4672 => 0x6c5d, 0x4673 => 0x4e8c, 0x4674 => 0x5c3c, + 0x4675 => 0x5f10, 0x4676 => 0x8fe9, 0x4677 => 0x5302, 0x4678 => 0x8cd1, + 0x4679 => 0x8089, 0x467a => 0x8679, 0x467b => 0x5eff, 0x467c => 0x65e5, + 0x467d => 0x4e73, 0x467e => 0x5165, 0x4721 => 0x5982, 0x4722 => 0x5c3f, + 0x4723 => 0x97ee, 0x4724 => 0x4efb, 0x4725 => 0x598a, 0x4726 => 0x5fcd, + 0x4727 => 0x8a8d, 0x4728 => 0x6fe1, 0x4729 => 0x79b0, 0x472a => 0x7962, + 0x472b => 0x5be7, 0x472c => 0x8471, 0x472d => 0x732b, 0x472e => 0x71b1, + 0x472f => 0x5e74, 0x4730 => 0x5ff5, 0x4731 => 0x637b, 0x4732 => 0x649a, + 0x4733 => 0x71c3, 0x4734 => 0x7c98, 0x4735 => 0x4e43, 0x4736 => 0x5efc, + 0x4737 => 0x4e4b, 0x4738 => 0x57dc, 0x4739 => 0x56a2, 0x473a => 0x60a9, + 0x473b => 0x6fc3, 0x473c => 0x7d0d, 0x473d => 0x80fd, 0x473e => 0x8133, + 0x473f => 0x81bf, 0x4740 => 0x8fb2, 0x4741 => 0x8997, 0x4742 => 0x86a4, + 0x4743 => 0x5df4, 0x4744 => 0x628a, 0x4745 => 0x64ad, 0x4746 => 0x8987, + 0x4747 => 0x6777, 0x4748 => 0x6ce2, 0x4749 => 0x6d3e, 0x474a => 0x7436, + 0x474b => 0x7834, 0x474c => 0x5a46, 0x474d => 0x7f75, 0x474e => 0x82ad, + 0x474f => 0x99ac, 0x4750 => 0x4ff3, 0x4751 => 0x5ec3, 0x4752 => 0x62dd, + 0x4753 => 0x6392, 0x4754 => 0x6557, 0x4755 => 0x676f, 0x4756 => 0x76c3, + 0x4757 => 0x724c, 0x4758 => 0x80cc, 0x4759 => 0x80ba, 0x475a => 0x8f29, + 0x475b => 0x914d, 0x475c => 0x500d, 0x475d => 0x57f9, 0x475e => 0x5a92, + 0x475f => 0x6885, 0x4760 => 0x6973, 0x4761 => 0x7164, 0x4762 => 0x72fd, + 0x4763 => 0x8cb7, 0x4764 => 0x58f2, 0x4765 => 0x8ce0, 0x4766 => 0x966a, + 0x4767 => 0x9019, 0x4768 => 0x877f, 0x4769 => 0x79e4, 0x476a => 0x77e7, + 0x476b => 0x8429, 0x476c => 0x4f2f, 0x476d => 0x5265, 0x476e => 0x535a, + 0x476f => 0x62cd, 0x4770 => 0x67cf, 0x4771 => 0x6cca, 0x4772 => 0x767d, + 0x4773 => 0x7b94, 0x4774 => 0x7c95, 0x4775 => 0x8236, 0x4776 => 0x8584, + 0x4777 => 0x8feb, 0x4778 => 0x66dd, 0x4779 => 0x6f20, 0x477a => 0x7206, + 0x477b => 0x7e1b, 0x477c => 0x83ab, 0x477d => 0x99c1, 0x477e => 0x9ea6, + 0x4821 => 0x51fd, 0x4822 => 0x7bb1, 0x4823 => 0x7872, 0x4824 => 0x7bb8, + 0x4825 => 0x8087, 0x4826 => 0x7b48, 0x4827 => 0x6ae8, 0x4828 => 0x5e61, + 0x4829 => 0x808c, 0x482a => 0x7551, 0x482b => 0x7560, 0x482c => 0x516b, + 0x482d => 0x9262, 0x482e => 0x6e8c, 0x482f => 0x767a, 0x4830 => 0x9197, + 0x4831 => 0x9aea, 0x4832 => 0x4f10, 0x4833 => 0x7f70, 0x4834 => 0x629c, + 0x4835 => 0x7b4f, 0x4836 => 0x95a5, 0x4837 => 0x9ce9, 0x4838 => 0x567a, + 0x4839 => 0x5859, 0x483a => 0x86e4, 0x483b => 0x96bc, 0x483c => 0x4f34, + 0x483d => 0x5224, 0x483e => 0x534a, 0x483f => 0x53cd, 0x4840 => 0x53db, + 0x4841 => 0x5e06, 0x4842 => 0x642c, 0x4843 => 0x6591, 0x4844 => 0x677f, + 0x4845 => 0x6c3e, 0x4846 => 0x6c4e, 0x4847 => 0x7248, 0x4848 => 0x72af, + 0x4849 => 0x73ed, 0x484a => 0x7554, 0x484b => 0x7e41, 0x484c => 0x822c, + 0x484d => 0x85e9, 0x484e => 0x8ca9, 0x484f => 0x7bc4, 0x4850 => 0x91c6, + 0x4851 => 0x7169, 0x4852 => 0x9812, 0x4853 => 0x98ef, 0x4854 => 0x633d, + 0x4855 => 0x6669, 0x4856 => 0x756a, 0x4857 => 0x76e4, 0x4858 => 0x78d0, + 0x4859 => 0x8543, 0x485a => 0x86ee, 0x485b => 0x532a, 0x485c => 0x5351, + 0x485d => 0x5426, 0x485e => 0x5983, 0x485f => 0x5e87, 0x4860 => 0x5f7c, + 0x4861 => 0x60b2, 0x4862 => 0x6249, 0x4863 => 0x6279, 0x4864 => 0x62ab, + 0x4865 => 0x6590, 0x4866 => 0x6bd4, 0x4867 => 0x6ccc, 0x4868 => 0x75b2, + 0x4869 => 0x76ae, 0x486a => 0x7891, 0x486b => 0x79d8, 0x486c => 0x7dcb, + 0x486d => 0x7f77, 0x486e => 0x80a5, 0x486f => 0x88ab, 0x4870 => 0x8ab9, + 0x4871 => 0x8cbb, 0x4872 => 0x907f, 0x4873 => 0x975e, 0x4874 => 0x98db, + 0x4875 => 0x6a0b, 0x4876 => 0x7c38, 0x4877 => 0x5099, 0x4878 => 0x5c3e, + 0x4879 => 0x5fae, 0x487a => 0x6787, 0x487b => 0x6bd8, 0x487c => 0x7435, + 0x487d => 0x7709, 0x487e => 0x7f8e, 0x4921 => 0x9f3b, 0x4922 => 0x67ca, + 0x4923 => 0x7a17, 0x4924 => 0x5339, 0x4925 => 0x758b, 0x4926 => 0x9aed, + 0x4927 => 0x5f66, 0x4928 => 0x819d, 0x4929 => 0x83f1, 0x492a => 0x8098, + 0x492b => 0x5f3c, 0x492c => 0x5fc5, 0x492d => 0x7562, 0x492e => 0x7b46, + 0x492f => 0x903c, 0x4930 => 0x6867, 0x4931 => 0x59eb, 0x4932 => 0x5a9b, + 0x4933 => 0x7d10, 0x4934 => 0x767e, 0x4935 => 0x8b2c, 0x4936 => 0x4ff5, + 0x4937 => 0x5f6a, 0x4938 => 0x6a19, 0x4939 => 0x6c37, 0x493a => 0x6f02, + 0x493b => 0x74e2, 0x493c => 0x7968, 0x493d => 0x8868, 0x493e => 0x8a55, + 0x493f => 0x8c79, 0x4940 => 0x5edf, 0x4941 => 0x63cf, 0x4942 => 0x75c5, + 0x4943 => 0x79d2, 0x4944 => 0x82d7, 0x4945 => 0x9328, 0x4946 => 0x92f2, + 0x4947 => 0x849c, 0x4948 => 0x86ed, 0x4949 => 0x9c2d, 0x494a => 0x54c1, + 0x494b => 0x5f6c, 0x494c => 0x658c, 0x494d => 0x6d5c, 0x494e => 0x7015, + 0x494f => 0x8ca7, 0x4950 => 0x8cd3, 0x4951 => 0x983b, 0x4952 => 0x654f, + 0x4953 => 0x74f6, 0x4954 => 0x4e0d, 0x4955 => 0x4ed8, 0x4956 => 0x57e0, + 0x4957 => 0x592b, 0x4958 => 0x5a66, 0x4959 => 0x5bcc, 0x495a => 0x51a8, + 0x495b => 0x5e03, 0x495c => 0x5e9c, 0x495d => 0x6016, 0x495e => 0x6276, + 0x495f => 0x6577, 0x4960 => 0x65a7, 0x4961 => 0x666e, 0x4962 => 0x6d6e, + 0x4963 => 0x7236, 0x4964 => 0x7b26, 0x4965 => 0x8150, 0x4966 => 0x819a, + 0x4967 => 0x8299, 0x4968 => 0x8b5c, 0x4969 => 0x8ca0, 0x496a => 0x8ce6, + 0x496b => 0x8d74, 0x496c => 0x961c, 0x496d => 0x9644, 0x496e => 0x4fae, + 0x496f => 0x64ab, 0x4970 => 0x6b66, 0x4971 => 0x821e, 0x4972 => 0x8461, + 0x4973 => 0x856a, 0x4974 => 0x90e8, 0x4975 => 0x5c01, 0x4976 => 0x6953, + 0x4977 => 0x98a8, 0x4978 => 0x847a, 0x4979 => 0x8557, 0x497a => 0x4f0f, + 0x497b => 0x526f, 0x497c => 0x5fa9, 0x497d => 0x5e45, 0x497e => 0x670d, + 0x4a21 => 0x798f, 0x4a22 => 0x8179, 0x4a23 => 0x8907, 0x4a24 => 0x8986, + 0x4a25 => 0x6df5, 0x4a26 => 0x5f17, 0x4a27 => 0x6255, 0x4a28 => 0x6cb8, + 0x4a29 => 0x4ecf, 0x4a2a => 0x7269, 0x4a2b => 0x9b92, 0x4a2c => 0x5206, + 0x4a2d => 0x543b, 0x4a2e => 0x5674, 0x4a2f => 0x58b3, 0x4a30 => 0x61a4, + 0x4a31 => 0x626e, 0x4a32 => 0x711a, 0x4a33 => 0x596e, 0x4a34 => 0x7c89, + 0x4a35 => 0x7cde, 0x4a36 => 0x7d1b, 0x4a37 => 0x96f0, 0x4a38 => 0x6587, + 0x4a39 => 0x805e, 0x4a3a => 0x4e19, 0x4a3b => 0x4f75, 0x4a3c => 0x5175, + 0x4a3d => 0x5840, 0x4a3e => 0x5e63, 0x4a3f => 0x5e73, 0x4a40 => 0x5f0a, + 0x4a41 => 0x67c4, 0x4a42 => 0x4e26, 0x4a43 => 0x853d, 0x4a44 => 0x9589, + 0x4a45 => 0x965b, 0x4a46 => 0x7c73, 0x4a47 => 0x9801, 0x4a48 => 0x50fb, + 0x4a49 => 0x58c1, 0x4a4a => 0x7656, 0x4a4b => 0x78a7, 0x4a4c => 0x5225, + 0x4a4d => 0x77a5, 0x4a4e => 0x8511, 0x4a4f => 0x7b86, 0x4a50 => 0x504f, + 0x4a51 => 0x5909, 0x4a52 => 0x7247, 0x4a53 => 0x7bc7, 0x4a54 => 0x7de8, + 0x4a55 => 0x8fba, 0x4a56 => 0x8fd4, 0x4a57 => 0x904d, 0x4a58 => 0x4fbf, + 0x4a59 => 0x52c9, 0x4a5a => 0x5a29, 0x4a5b => 0x5f01, 0x4a5c => 0x97ad, + 0x4a5d => 0x4fdd, 0x4a5e => 0x8217, 0x4a5f => 0x92ea, 0x4a60 => 0x5703, + 0x4a61 => 0x6355, 0x4a62 => 0x6b69, 0x4a63 => 0x752b, 0x4a64 => 0x88dc, + 0x4a65 => 0x8f14, 0x4a66 => 0x7a42, 0x4a67 => 0x52df, 0x4a68 => 0x5893, + 0x4a69 => 0x6155, 0x4a6a => 0x620a, 0x4a6b => 0x66ae, 0x4a6c => 0x6bcd, + 0x4a6d => 0x7c3f, 0x4a6e => 0x83e9, 0x4a6f => 0x5023, 0x4a70 => 0x4ff8, + 0x4a71 => 0x5305, 0x4a72 => 0x5446, 0x4a73 => 0x5831, 0x4a74 => 0x5949, + 0x4a75 => 0x5b9d, 0x4a76 => 0x5cf0, 0x4a77 => 0x5cef, 0x4a78 => 0x5d29, + 0x4a79 => 0x5e96, 0x4a7a => 0x62b1, 0x4a7b => 0x6367, 0x4a7c => 0x653e, + 0x4a7d => 0x65b9, 0x4a7e => 0x670b, 0x4b21 => 0x6cd5, 0x4b22 => 0x6ce1, + 0x4b23 => 0x70f9, 0x4b24 => 0x7832, 0x4b25 => 0x7e2b, 0x4b26 => 0x80de, + 0x4b27 => 0x82b3, 0x4b28 => 0x840c, 0x4b29 => 0x84ec, 0x4b2a => 0x8702, + 0x4b2b => 0x8912, 0x4b2c => 0x8a2a, 0x4b2d => 0x8c4a, 0x4b2e => 0x90a6, + 0x4b2f => 0x92d2, 0x4b30 => 0x98fd, 0x4b31 => 0x9cf3, 0x4b32 => 0x9d6c, + 0x4b33 => 0x4e4f, 0x4b34 => 0x4ea1, 0x4b35 => 0x508d, 0x4b36 => 0x5256, + 0x4b37 => 0x574a, 0x4b38 => 0x59a8, 0x4b39 => 0x5e3d, 0x4b3a => 0x5fd8, + 0x4b3b => 0x5fd9, 0x4b3c => 0x623f, 0x4b3d => 0x66b4, 0x4b3e => 0x671b, + 0x4b3f => 0x67d0, 0x4b40 => 0x68d2, 0x4b41 => 0x5192, 0x4b42 => 0x7d21, + 0x4b43 => 0x80aa, 0x4b44 => 0x81a8, 0x4b45 => 0x8b00, 0x4b46 => 0x8c8c, + 0x4b47 => 0x8cbf, 0x4b48 => 0x927e, 0x4b49 => 0x9632, 0x4b4a => 0x5420, + 0x4b4b => 0x982c, 0x4b4c => 0x5317, 0x4b4d => 0x50d5, 0x4b4e => 0x535c, + 0x4b4f => 0x58a8, 0x4b50 => 0x64b2, 0x4b51 => 0x6734, 0x4b52 => 0x7267, + 0x4b53 => 0x7766, 0x4b54 => 0x7a46, 0x4b55 => 0x91e6, 0x4b56 => 0x52c3, + 0x4b57 => 0x6ca1, 0x4b58 => 0x6b86, 0x4b59 => 0x5800, 0x4b5a => 0x5e4c, + 0x4b5b => 0x5954, 0x4b5c => 0x672c, 0x4b5d => 0x7ffb, 0x4b5e => 0x51e1, + 0x4b5f => 0x76c6, 0x4b60 => 0x6469, 0x4b61 => 0x78e8, 0x4b62 => 0x9b54, + 0x4b63 => 0x9ebb, 0x4b64 => 0x57cb, 0x4b65 => 0x59b9, 0x4b66 => 0x6627, + 0x4b67 => 0x679a, 0x4b68 => 0x6bce, 0x4b69 => 0x54e9, 0x4b6a => 0x69d9, + 0x4b6b => 0x5e55, 0x4b6c => 0x819c, 0x4b6d => 0x6795, 0x4b6e => 0x9baa, + 0x4b6f => 0x67fe, 0x4b70 => 0x9c52, 0x4b71 => 0x685d, 0x4b72 => 0x4ea6, + 0x4b73 => 0x4fe3, 0x4b74 => 0x53c8, 0x4b75 => 0x62b9, 0x4b76 => 0x672b, + 0x4b77 => 0x6cab, 0x4b78 => 0x8fc4, 0x4b79 => 0x4fad, 0x4b7a => 0x7e6d, + 0x4b7b => 0x9ebf, 0x4b7c => 0x4e07, 0x4b7d => 0x6162, 0x4b7e => 0x6e80, + 0x4c21 => 0x6f2b, 0x4c22 => 0x8513, 0x4c23 => 0x5473, 0x4c24 => 0x672a, + 0x4c25 => 0x9b45, 0x4c26 => 0x5df3, 0x4c27 => 0x7b95, 0x4c28 => 0x5cac, + 0x4c29 => 0x5bc6, 0x4c2a => 0x871c, 0x4c2b => 0x6e4a, 0x4c2c => 0x84d1, + 0x4c2d => 0x7a14, 0x4c2e => 0x8108, 0x4c2f => 0x5999, 0x4c30 => 0x7c8d, + 0x4c31 => 0x6c11, 0x4c32 => 0x7720, 0x4c33 => 0x52d9, 0x4c34 => 0x5922, + 0x4c35 => 0x7121, 0x4c36 => 0x725f, 0x4c37 => 0x77db, 0x4c38 => 0x9727, + 0x4c39 => 0x9d61, 0x4c3a => 0x690b, 0x4c3b => 0x5a7f, 0x4c3c => 0x5a18, + 0x4c3d => 0x51a5, 0x4c3e => 0x540d, 0x4c3f => 0x547d, 0x4c40 => 0x660e, + 0x4c41 => 0x76df, 0x4c42 => 0x8ff7, 0x4c43 => 0x9298, 0x4c44 => 0x9cf4, + 0x4c45 => 0x59ea, 0x4c46 => 0x725d, 0x4c47 => 0x6ec5, 0x4c48 => 0x514d, + 0x4c49 => 0x68c9, 0x4c4a => 0x7dbf, 0x4c4b => 0x7dec, 0x4c4c => 0x9762, + 0x4c4d => 0x9eba, 0x4c4e => 0x6478, 0x4c4f => 0x6a21, 0x4c50 => 0x8302, + 0x4c51 => 0x5984, 0x4c52 => 0x5b5f, 0x4c53 => 0x6bdb, 0x4c54 => 0x731b, + 0x4c55 => 0x76f2, 0x4c56 => 0x7db2, 0x4c57 => 0x8017, 0x4c58 => 0x8499, + 0x4c59 => 0x5132, 0x4c5a => 0x6728, 0x4c5b => 0x9ed9, 0x4c5c => 0x76ee, + 0x4c5d => 0x6762, 0x4c5e => 0x52ff, 0x4c5f => 0x9905, 0x4c60 => 0x5c24, + 0x4c61 => 0x623b, 0x4c62 => 0x7c7e, 0x4c63 => 0x8cb0, 0x4c64 => 0x554f, + 0x4c65 => 0x60b6, 0x4c66 => 0x7d0b, 0x4c67 => 0x9580, 0x4c68 => 0x5301, + 0x4c69 => 0x4e5f, 0x4c6a => 0x51b6, 0x4c6b => 0x591c, 0x4c6c => 0x723a, + 0x4c6d => 0x8036, 0x4c6e => 0x91ce, 0x4c6f => 0x5f25, 0x4c70 => 0x77e2, + 0x4c71 => 0x5384, 0x4c72 => 0x5f79, 0x4c73 => 0x7d04, 0x4c74 => 0x85ac, + 0x4c75 => 0x8a33, 0x4c76 => 0x8e8d, 0x4c77 => 0x9756, 0x4c78 => 0x67f3, + 0x4c79 => 0x85ae, 0x4c7a => 0x9453, 0x4c7b => 0x6109, 0x4c7c => 0x6108, + 0x4c7d => 0x6cb9, 0x4c7e => 0x7652, 0x4d21 => 0x8aed, 0x4d22 => 0x8f38, + 0x4d23 => 0x552f, 0x4d24 => 0x4f51, 0x4d25 => 0x512a, 0x4d26 => 0x52c7, + 0x4d27 => 0x53cb, 0x4d28 => 0x5ba5, 0x4d29 => 0x5e7d, 0x4d2a => 0x60a0, + 0x4d2b => 0x6182, 0x4d2c => 0x63d6, 0x4d2d => 0x6709, 0x4d2e => 0x67da, + 0x4d2f => 0x6e67, 0x4d30 => 0x6d8c, 0x4d31 => 0x7336, 0x4d32 => 0x7337, + 0x4d33 => 0x7531, 0x4d34 => 0x7950, 0x4d35 => 0x88d5, 0x4d36 => 0x8a98, + 0x4d37 => 0x904a, 0x4d38 => 0x9091, 0x4d39 => 0x90f5, 0x4d3a => 0x96c4, + 0x4d3b => 0x878d, 0x4d3c => 0x5915, 0x4d3d => 0x4e88, 0x4d3e => 0x4f59, + 0x4d3f => 0x4e0e, 0x4d40 => 0x8a89, 0x4d41 => 0x8f3f, 0x4d42 => 0x9810, + 0x4d43 => 0x50ad, 0x4d44 => 0x5e7c, 0x4d45 => 0x5996, 0x4d46 => 0x5bb9, + 0x4d47 => 0x5eb8, 0x4d48 => 0x63da, 0x4d49 => 0x63fa, 0x4d4a => 0x64c1, + 0x4d4b => 0x66dc, 0x4d4c => 0x694a, 0x4d4d => 0x69d8, 0x4d4e => 0x6d0b, + 0x4d4f => 0x6eb6, 0x4d50 => 0x7194, 0x4d51 => 0x7528, 0x4d52 => 0x7aaf, + 0x4d53 => 0x7f8a, 0x4d54 => 0x8000, 0x4d55 => 0x8449, 0x4d56 => 0x84c9, + 0x4d57 => 0x8981, 0x4d58 => 0x8b21, 0x4d59 => 0x8e0a, 0x4d5a => 0x9065, + 0x4d5b => 0x967d, 0x4d5c => 0x990a, 0x4d5d => 0x617e, 0x4d5e => 0x6291, + 0x4d5f => 0x6b32, 0x4d60 => 0x6c83, 0x4d61 => 0x6d74, 0x4d62 => 0x7fcc, + 0x4d63 => 0x7ffc, 0x4d64 => 0x6dc0, 0x4d65 => 0x7f85, 0x4d66 => 0x87ba, + 0x4d67 => 0x88f8, 0x4d68 => 0x6765, 0x4d69 => 0x83b1, 0x4d6a => 0x983c, + 0x4d6b => 0x96f7, 0x4d6c => 0x6d1b, 0x4d6d => 0x7d61, 0x4d6e => 0x843d, + 0x4d6f => 0x916a, 0x4d70 => 0x4e71, 0x4d71 => 0x5375, 0x4d72 => 0x5d50, + 0x4d73 => 0x6b04, 0x4d74 => 0x6feb, 0x4d75 => 0x85cd, 0x4d76 => 0x862d, + 0x4d77 => 0x89a7, 0x4d78 => 0x5229, 0x4d79 => 0x540f, 0x4d7a => 0x5c65, + 0x4d7b => 0x674e, 0x4d7c => 0x68a8, 0x4d7d => 0x7406, 0x4d7e => 0x7483, + 0x4e21 => 0x75e2, 0x4e22 => 0x88cf, 0x4e23 => 0x88e1, 0x4e24 => 0x91cc, + 0x4e25 => 0x96e2, 0x4e26 => 0x9678, 0x4e27 => 0x5f8b, 0x4e28 => 0x7387, + 0x4e29 => 0x7acb, 0x4e2a => 0x844e, 0x4e2b => 0x63a0, 0x4e2c => 0x7565, + 0x4e2d => 0x5289, 0x4e2e => 0x6d41, 0x4e2f => 0x6e9c, 0x4e30 => 0x7409, + 0x4e31 => 0x7559, 0x4e32 => 0x786b, 0x4e33 => 0x7c92, 0x4e34 => 0x9686, + 0x4e35 => 0x7adc, 0x4e36 => 0x9f8d, 0x4e37 => 0x4fb6, 0x4e38 => 0x616e, + 0x4e39 => 0x65c5, 0x4e3a => 0x865c, 0x4e3b => 0x4e86, 0x4e3c => 0x4eae, + 0x4e3d => 0x50da, 0x4e3e => 0x4e21, 0x4e3f => 0x51cc, 0x4e40 => 0x5bee, + 0x4e41 => 0x6599, 0x4e42 => 0x6881, 0x4e43 => 0x6dbc, 0x4e44 => 0x731f, + 0x4e45 => 0x7642, 0x4e46 => 0x77ad, 0x4e47 => 0x7a1c, 0x4e48 => 0x7ce7, + 0x4e49 => 0x826f, 0x4e4a => 0x8ad2, 0x4e4b => 0x907c, 0x4e4c => 0x91cf, + 0x4e4d => 0x9675, 0x4e4e => 0x9818, 0x4e4f => 0x529b, 0x4e50 => 0x7dd1, + 0x4e51 => 0x502b, 0x4e52 => 0x5398, 0x4e53 => 0x6797, 0x4e54 => 0x6dcb, + 0x4e55 => 0x71d0, 0x4e56 => 0x7433, 0x4e57 => 0x81e8, 0x4e58 => 0x8f2a, + 0x4e59 => 0x96a3, 0x4e5a => 0x9c57, 0x4e5b => 0x9e9f, 0x4e5c => 0x7460, + 0x4e5d => 0x5841, 0x4e5e => 0x6d99, 0x4e5f => 0x7d2f, 0x4e60 => 0x985e, + 0x4e61 => 0x4ee4, 0x4e62 => 0x4f36, 0x4e63 => 0x4f8b, 0x4e64 => 0x51b7, + 0x4e65 => 0x52b1, 0x4e66 => 0x5dba, 0x4e67 => 0x601c, 0x4e68 => 0x73b2, + 0x4e69 => 0x793c, 0x4e6a => 0x82d3, 0x4e6b => 0x9234, 0x4e6c => 0x96b7, + 0x4e6d => 0x96f6, 0x4e6e => 0x970a, 0x4e6f => 0x9e97, 0x4e70 => 0x9f62, + 0x4e71 => 0x66a6, 0x4e72 => 0x6b74, 0x4e73 => 0x5217, 0x4e74 => 0x52a3, + 0x4e75 => 0x70c8, 0x4e76 => 0x88c2, 0x4e77 => 0x5ec9, 0x4e78 => 0x604b, + 0x4e79 => 0x6190, 0x4e7a => 0x6f23, 0x4e7b => 0x7149, 0x4e7c => 0x7c3e, + 0x4e7d => 0x7df4, 0x4e7e => 0x806f, 0x4f21 => 0x84ee, 0x4f22 => 0x9023, + 0x4f23 => 0x932c, 0x4f24 => 0x5442, 0x4f25 => 0x9b6f, 0x4f26 => 0x6ad3, + 0x4f27 => 0x7089, 0x4f28 => 0x8cc2, 0x4f29 => 0x8def, 0x4f2a => 0x9732, + 0x4f2b => 0x52b4, 0x4f2c => 0x5a41, 0x4f2d => 0x5eca, 0x4f2e => 0x5f04, + 0x4f2f => 0x6717, 0x4f30 => 0x697c, 0x4f31 => 0x6994, 0x4f32 => 0x6d6a, + 0x4f33 => 0x6f0f, 0x4f34 => 0x7262, 0x4f35 => 0x72fc, 0x4f36 => 0x7bed, + 0x4f37 => 0x8001, 0x4f38 => 0x807e, 0x4f39 => 0x874b, 0x4f3a => 0x90ce, + 0x4f3b => 0x516d, 0x4f3c => 0x9e93, 0x4f3d => 0x7984, 0x4f3e => 0x808b, + 0x4f3f => 0x9332, 0x4f40 => 0x8ad6, 0x4f41 => 0x502d, 0x4f42 => 0x548c, + 0x4f43 => 0x8a71, 0x4f44 => 0x6b6a, 0x4f45 => 0x8cc4, 0x4f46 => 0x8107, + 0x4f47 => 0x60d1, 0x4f48 => 0x67a0, 0x4f49 => 0x9df2, 0x4f4a => 0x4e99, + 0x4f4b => 0x4e98, 0x4f4c => 0x9c10, 0x4f4d => 0x8a6b, 0x4f4e => 0x85c1, + 0x4f4f => 0x8568, 0x4f50 => 0x6900, 0x4f51 => 0x6e7e, 0x4f52 => 0x7897, + 0x4f53 => 0x8155, 0x5021 => 0x5f0c, 0x5022 => 0x4e10, 0x5023 => 0x4e15, + 0x5024 => 0x4e2a, 0x5025 => 0x4e31, 0x5026 => 0x4e36, 0x5027 => 0x4e3c, + 0x5028 => 0x4e3f, 0x5029 => 0x4e42, 0x502a => 0x4e56, 0x502b => 0x4e58, + 0x502c => 0x4e82, 0x502d => 0x4e85, 0x502e => 0x8c6b, 0x502f => 0x4e8a, + 0x5030 => 0x8212, 0x5031 => 0x5f0d, 0x5032 => 0x4e8e, 0x5033 => 0x4e9e, + 0x5034 => 0x4e9f, 0x5035 => 0x4ea0, 0x5036 => 0x4ea2, 0x5037 => 0x4eb0, + 0x5038 => 0x4eb3, 0x5039 => 0x4eb6, 0x503a => 0x4ece, 0x503b => 0x4ecd, + 0x503c => 0x4ec4, 0x503d => 0x4ec6, 0x503e => 0x4ec2, 0x503f => 0x4ed7, + 0x5040 => 0x4ede, 0x5041 => 0x4eed, 0x5042 => 0x4edf, 0x5043 => 0x4ef7, + 0x5044 => 0x4f09, 0x5045 => 0x4f5a, 0x5046 => 0x4f30, 0x5047 => 0x4f5b, + 0x5048 => 0x4f5d, 0x5049 => 0x4f57, 0x504a => 0x4f47, 0x504b => 0x4f76, + 0x504c => 0x4f88, 0x504d => 0x4f8f, 0x504e => 0x4f98, 0x504f => 0x4f7b, + 0x5050 => 0x4f69, 0x5051 => 0x4f70, 0x5052 => 0x4f91, 0x5053 => 0x4f6f, + 0x5054 => 0x4f86, 0x5055 => 0x4f96, 0x5056 => 0x5118, 0x5057 => 0x4fd4, + 0x5058 => 0x4fdf, 0x5059 => 0x4fce, 0x505a => 0x4fd8, 0x505b => 0x4fdb, + 0x505c => 0x4fd1, 0x505d => 0x4fda, 0x505e => 0x4fd0, 0x505f => 0x4fe4, + 0x5060 => 0x4fe5, 0x5061 => 0x501a, 0x5062 => 0x5028, 0x5063 => 0x5014, + 0x5064 => 0x502a, 0x5065 => 0x5025, 0x5066 => 0x5005, 0x5067 => 0x4f1c, + 0x5068 => 0x4ff6, 0x5069 => 0x5021, 0x506a => 0x5029, 0x506b => 0x502c, + 0x506c => 0x4ffe, 0x506d => 0x4fef, 0x506e => 0x5011, 0x506f => 0x5006, + 0x5070 => 0x5043, 0x5071 => 0x5047, 0x5072 => 0x6703, 0x5073 => 0x5055, + 0x5074 => 0x5050, 0x5075 => 0x5048, 0x5076 => 0x505a, 0x5077 => 0x5056, + 0x5078 => 0x506c, 0x5079 => 0x5078, 0x507a => 0x5080, 0x507b => 0x509a, + 0x507c => 0x5085, 0x507d => 0x50b4, 0x507e => 0x50b2, 0x5121 => 0x50c9, + 0x5122 => 0x50ca, 0x5123 => 0x50b3, 0x5124 => 0x50c2, 0x5125 => 0x50d6, + 0x5126 => 0x50de, 0x5127 => 0x50e5, 0x5128 => 0x50ed, 0x5129 => 0x50e3, + 0x512a => 0x50ee, 0x512b => 0x50f9, 0x512c => 0x50f5, 0x512d => 0x5109, + 0x512e => 0x5101, 0x512f => 0x5102, 0x5130 => 0x5116, 0x5131 => 0x5115, + 0x5132 => 0x5114, 0x5133 => 0x511a, 0x5134 => 0x5121, 0x5135 => 0x513a, + 0x5136 => 0x5137, 0x5137 => 0x513c, 0x5138 => 0x513b, 0x5139 => 0x513f, + 0x513a => 0x5140, 0x513b => 0x5152, 0x513c => 0x514c, 0x513d => 0x5154, + 0x513e => 0x5162, 0x513f => 0x7af8, 0x5140 => 0x5169, 0x5141 => 0x516a, + 0x5142 => 0x516e, 0x5143 => 0x5180, 0x5144 => 0x5182, 0x5145 => 0x56d8, + 0x5146 => 0x518c, 0x5147 => 0x5189, 0x5148 => 0x518f, 0x5149 => 0x5191, + 0x514a => 0x5193, 0x514b => 0x5195, 0x514c => 0x5196, 0x514d => 0x51a4, + 0x514e => 0x51a6, 0x514f => 0x51a2, 0x5150 => 0x51a9, 0x5151 => 0x51aa, + 0x5152 => 0x51ab, 0x5153 => 0x51b3, 0x5154 => 0x51b1, 0x5155 => 0x51b2, + 0x5156 => 0x51b0, 0x5157 => 0x51b5, 0x5158 => 0x51bd, 0x5159 => 0x51c5, + 0x515a => 0x51c9, 0x515b => 0x51db, 0x515c => 0x51e0, 0x515d => 0x8655, + 0x515e => 0x51e9, 0x515f => 0x51ed, 0x5160 => 0x51f0, 0x5161 => 0x51f5, + 0x5162 => 0x51fe, 0x5163 => 0x5204, 0x5164 => 0x520b, 0x5165 => 0x5214, + 0x5166 => 0x520e, 0x5167 => 0x5227, 0x5168 => 0x522a, 0x5169 => 0x522e, + 0x516a => 0x5233, 0x516b => 0x5239, 0x516c => 0x524f, 0x516d => 0x5244, + 0x516e => 0x524b, 0x516f => 0x524c, 0x5170 => 0x525e, 0x5171 => 0x5254, + 0x5172 => 0x526a, 0x5173 => 0x5274, 0x5174 => 0x5269, 0x5175 => 0x5273, + 0x5176 => 0x527f, 0x5177 => 0x527d, 0x5178 => 0x528d, 0x5179 => 0x5294, + 0x517a => 0x5292, 0x517b => 0x5271, 0x517c => 0x5288, 0x517d => 0x5291, + 0x517e => 0x8fa8, 0x5221 => 0x8fa7, 0x5222 => 0x52ac, 0x5223 => 0x52ad, + 0x5224 => 0x52bc, 0x5225 => 0x52b5, 0x5226 => 0x52c1, 0x5227 => 0x52cd, + 0x5228 => 0x52d7, 0x5229 => 0x52de, 0x522a => 0x52e3, 0x522b => 0x52e6, + 0x522c => 0x98ed, 0x522d => 0x52e0, 0x522e => 0x52f3, 0x522f => 0x52f5, + 0x5230 => 0x52f8, 0x5231 => 0x52f9, 0x5232 => 0x5306, 0x5233 => 0x5308, + 0x5234 => 0x7538, 0x5235 => 0x530d, 0x5236 => 0x5310, 0x5237 => 0x530f, + 0x5238 => 0x5315, 0x5239 => 0x531a, 0x523a => 0x5323, 0x523b => 0x532f, + 0x523c => 0x5331, 0x523d => 0x5333, 0x523e => 0x5338, 0x523f => 0x5340, + 0x5240 => 0x5346, 0x5241 => 0x5345, 0x5242 => 0x4e17, 0x5243 => 0x5349, + 0x5244 => 0x534d, 0x5245 => 0x51d6, 0x5246 => 0x535e, 0x5247 => 0x5369, + 0x5248 => 0x536e, 0x5249 => 0x5918, 0x524a => 0x537b, 0x524b => 0x5377, + 0x524c => 0x5382, 0x524d => 0x5396, 0x524e => 0x53a0, 0x524f => 0x53a6, + 0x5250 => 0x53a5, 0x5251 => 0x53ae, 0x5252 => 0x53b0, 0x5253 => 0x53b6, + 0x5254 => 0x53c3, 0x5255 => 0x7c12, 0x5256 => 0x96d9, 0x5257 => 0x53df, + 0x5258 => 0x66fc, 0x5259 => 0x71ee, 0x525a => 0x53ee, 0x525b => 0x53e8, + 0x525c => 0x53ed, 0x525d => 0x53fa, 0x525e => 0x5401, 0x525f => 0x543d, + 0x5260 => 0x5440, 0x5261 => 0x542c, 0x5262 => 0x542d, 0x5263 => 0x543c, + 0x5264 => 0x542e, 0x5265 => 0x5436, 0x5266 => 0x5429, 0x5267 => 0x541d, + 0x5268 => 0x544e, 0x5269 => 0x548f, 0x526a => 0x5475, 0x526b => 0x548e, + 0x526c => 0x545f, 0x526d => 0x5471, 0x526e => 0x5477, 0x526f => 0x5470, + 0x5270 => 0x5492, 0x5271 => 0x547b, 0x5272 => 0x5480, 0x5273 => 0x5476, + 0x5274 => 0x5484, 0x5275 => 0x5490, 0x5276 => 0x5486, 0x5277 => 0x54c7, + 0x5278 => 0x54a2, 0x5279 => 0x54b8, 0x527a => 0x54a5, 0x527b => 0x54ac, + 0x527c => 0x54c4, 0x527d => 0x54c8, 0x527e => 0x54a8, 0x5321 => 0x54ab, + 0x5322 => 0x54c2, 0x5323 => 0x54a4, 0x5324 => 0x54be, 0x5325 => 0x54bc, + 0x5326 => 0x54d8, 0x5327 => 0x54e5, 0x5328 => 0x54e6, 0x5329 => 0x550f, + 0x532a => 0x5514, 0x532b => 0x54fd, 0x532c => 0x54ee, 0x532d => 0x54ed, + 0x532e => 0x54fa, 0x532f => 0x54e2, 0x5330 => 0x5539, 0x5331 => 0x5540, + 0x5332 => 0x5563, 0x5333 => 0x554c, 0x5334 => 0x552e, 0x5335 => 0x555c, + 0x5336 => 0x5545, 0x5337 => 0x5556, 0x5338 => 0x5557, 0x5339 => 0x5538, + 0x533a => 0x5533, 0x533b => 0x555d, 0x533c => 0x5599, 0x533d => 0x5580, + 0x533e => 0x54af, 0x533f => 0x558a, 0x5340 => 0x559f, 0x5341 => 0x557b, + 0x5342 => 0x557e, 0x5343 => 0x5598, 0x5344 => 0x559e, 0x5345 => 0x55ae, + 0x5346 => 0x557c, 0x5347 => 0x5583, 0x5348 => 0x55a9, 0x5349 => 0x5587, + 0x534a => 0x55a8, 0x534b => 0x55da, 0x534c => 0x55c5, 0x534d => 0x55df, + 0x534e => 0x55c4, 0x534f => 0x55dc, 0x5350 => 0x55e4, 0x5351 => 0x55d4, + 0x5352 => 0x5614, 0x5353 => 0x55f7, 0x5354 => 0x5616, 0x5355 => 0x55fe, + 0x5356 => 0x55fd, 0x5357 => 0x561b, 0x5358 => 0x55f9, 0x5359 => 0x564e, + 0x535a => 0x5650, 0x535b => 0x71df, 0x535c => 0x5634, 0x535d => 0x5636, + 0x535e => 0x5632, 0x535f => 0x5638, 0x5360 => 0x566b, 0x5361 => 0x5664, + 0x5362 => 0x562f, 0x5363 => 0x566c, 0x5364 => 0x566a, 0x5365 => 0x5686, + 0x5366 => 0x5680, 0x5367 => 0x568a, 0x5368 => 0x56a0, 0x5369 => 0x5694, + 0x536a => 0x568f, 0x536b => 0x56a5, 0x536c => 0x56ae, 0x536d => 0x56b6, + 0x536e => 0x56b4, 0x536f => 0x56c2, 0x5370 => 0x56bc, 0x5371 => 0x56c1, + 0x5372 => 0x56c3, 0x5373 => 0x56c0, 0x5374 => 0x56c8, 0x5375 => 0x56ce, + 0x5376 => 0x56d1, 0x5377 => 0x56d3, 0x5378 => 0x56d7, 0x5379 => 0x56ee, + 0x537a => 0x56f9, 0x537b => 0x5700, 0x537c => 0x56ff, 0x537d => 0x5704, + 0x537e => 0x5709, 0x5421 => 0x5708, 0x5422 => 0x570b, 0x5423 => 0x570d, + 0x5424 => 0x5713, 0x5425 => 0x5718, 0x5426 => 0x5716, 0x5427 => 0x55c7, + 0x5428 => 0x571c, 0x5429 => 0x5726, 0x542a => 0x5737, 0x542b => 0x5738, + 0x542c => 0x574e, 0x542d => 0x573b, 0x542e => 0x5740, 0x542f => 0x574f, + 0x5430 => 0x5769, 0x5431 => 0x57c0, 0x5432 => 0x5788, 0x5433 => 0x5761, + 0x5434 => 0x577f, 0x5435 => 0x5789, 0x5436 => 0x5793, 0x5437 => 0x57a0, + 0x5438 => 0x57b3, 0x5439 => 0x57a4, 0x543a => 0x57aa, 0x543b => 0x57b0, + 0x543c => 0x57c3, 0x543d => 0x57c6, 0x543e => 0x57d4, 0x543f => 0x57d2, + 0x5440 => 0x57d3, 0x5441 => 0x580a, 0x5442 => 0x57d6, 0x5443 => 0x57e3, + 0x5444 => 0x580b, 0x5445 => 0x5819, 0x5446 => 0x581d, 0x5447 => 0x5872, + 0x5448 => 0x5821, 0x5449 => 0x5862, 0x544a => 0x584b, 0x544b => 0x5870, + 0x544c => 0x6bc0, 0x544d => 0x5852, 0x544e => 0x583d, 0x544f => 0x5879, + 0x5450 => 0x5885, 0x5451 => 0x58b9, 0x5452 => 0x589f, 0x5453 => 0x58ab, + 0x5454 => 0x58ba, 0x5455 => 0x58de, 0x5456 => 0x58bb, 0x5457 => 0x58b8, + 0x5458 => 0x58ae, 0x5459 => 0x58c5, 0x545a => 0x58d3, 0x545b => 0x58d1, + 0x545c => 0x58d7, 0x545d => 0x58d9, 0x545e => 0x58d8, 0x545f => 0x58e5, + 0x5460 => 0x58dc, 0x5461 => 0x58e4, 0x5462 => 0x58df, 0x5463 => 0x58ef, + 0x5464 => 0x58fa, 0x5465 => 0x58f9, 0x5466 => 0x58fb, 0x5467 => 0x58fc, + 0x5468 => 0x58fd, 0x5469 => 0x5902, 0x546a => 0x590a, 0x546b => 0x5910, + 0x546c => 0x591b, 0x546d => 0x68a6, 0x546e => 0x5925, 0x546f => 0x592c, + 0x5470 => 0x592d, 0x5471 => 0x5932, 0x5472 => 0x5938, 0x5473 => 0x593e, + 0x5474 => 0x7ad2, 0x5475 => 0x5955, 0x5476 => 0x5950, 0x5477 => 0x594e, + 0x5478 => 0x595a, 0x5479 => 0x5958, 0x547a => 0x5962, 0x547b => 0x5960, + 0x547c => 0x5967, 0x547d => 0x596c, 0x547e => 0x5969, 0x5521 => 0x5978, + 0x5522 => 0x5981, 0x5523 => 0x599d, 0x5524 => 0x4f5e, 0x5525 => 0x4fab, + 0x5526 => 0x59a3, 0x5527 => 0x59b2, 0x5528 => 0x59c6, 0x5529 => 0x59e8, + 0x552a => 0x59dc, 0x552b => 0x598d, 0x552c => 0x59d9, 0x552d => 0x59da, + 0x552e => 0x5a25, 0x552f => 0x5a1f, 0x5530 => 0x5a11, 0x5531 => 0x5a1c, + 0x5532 => 0x5a09, 0x5533 => 0x5a1a, 0x5534 => 0x5a40, 0x5535 => 0x5a6c, + 0x5536 => 0x5a49, 0x5537 => 0x5a35, 0x5538 => 0x5a36, 0x5539 => 0x5a62, + 0x553a => 0x5a6a, 0x553b => 0x5a9a, 0x553c => 0x5abc, 0x553d => 0x5abe, + 0x553e => 0x5acb, 0x553f => 0x5ac2, 0x5540 => 0x5abd, 0x5541 => 0x5ae3, + 0x5542 => 0x5ad7, 0x5543 => 0x5ae6, 0x5544 => 0x5ae9, 0x5545 => 0x5ad6, + 0x5546 => 0x5afa, 0x5547 => 0x5afb, 0x5548 => 0x5b0c, 0x5549 => 0x5b0b, + 0x554a => 0x5b16, 0x554b => 0x5b32, 0x554c => 0x5ad0, 0x554d => 0x5b2a, + 0x554e => 0x5b36, 0x554f => 0x5b3e, 0x5550 => 0x5b43, 0x5551 => 0x5b45, + 0x5552 => 0x5b40, 0x5553 => 0x5b51, 0x5554 => 0x5b55, 0x5555 => 0x5b5a, + 0x5556 => 0x5b5b, 0x5557 => 0x5b65, 0x5558 => 0x5b69, 0x5559 => 0x5b70, + 0x555a => 0x5b73, 0x555b => 0x5b75, 0x555c => 0x5b78, 0x555d => 0x6588, + 0x555e => 0x5b7a, 0x555f => 0x5b80, 0x5560 => 0x5b83, 0x5561 => 0x5ba6, + 0x5562 => 0x5bb8, 0x5563 => 0x5bc3, 0x5564 => 0x5bc7, 0x5565 => 0x5bc9, + 0x5566 => 0x5bd4, 0x5567 => 0x5bd0, 0x5568 => 0x5be4, 0x5569 => 0x5be6, + 0x556a => 0x5be2, 0x556b => 0x5bde, 0x556c => 0x5be5, 0x556d => 0x5beb, + 0x556e => 0x5bf0, 0x556f => 0x5bf6, 0x5570 => 0x5bf3, 0x5571 => 0x5c05, + 0x5572 => 0x5c07, 0x5573 => 0x5c08, 0x5574 => 0x5c0d, 0x5575 => 0x5c13, + 0x5576 => 0x5c20, 0x5577 => 0x5c22, 0x5578 => 0x5c28, 0x5579 => 0x5c38, + 0x557a => 0x5c39, 0x557b => 0x5c41, 0x557c => 0x5c46, 0x557d => 0x5c4e, + 0x557e => 0x5c53, 0x5621 => 0x5c50, 0x5622 => 0x5c4f, 0x5623 => 0x5b71, + 0x5624 => 0x5c6c, 0x5625 => 0x5c6e, 0x5626 => 0x4e62, 0x5627 => 0x5c76, + 0x5628 => 0x5c79, 0x5629 => 0x5c8c, 0x562a => 0x5c91, 0x562b => 0x5c94, + 0x562c => 0x599b, 0x562d => 0x5cab, 0x562e => 0x5cbb, 0x562f => 0x5cb6, + 0x5630 => 0x5cbc, 0x5631 => 0x5cb7, 0x5632 => 0x5cc5, 0x5633 => 0x5cbe, + 0x5634 => 0x5cc7, 0x5635 => 0x5cd9, 0x5636 => 0x5ce9, 0x5637 => 0x5cfd, + 0x5638 => 0x5cfa, 0x5639 => 0x5ced, 0x563a => 0x5d8c, 0x563b => 0x5cea, + 0x563c => 0x5d0b, 0x563d => 0x5d15, 0x563e => 0x5d17, 0x563f => 0x5d5c, + 0x5640 => 0x5d1f, 0x5641 => 0x5d1b, 0x5642 => 0x5d11, 0x5643 => 0x5d14, + 0x5644 => 0x5d22, 0x5645 => 0x5d1a, 0x5646 => 0x5d19, 0x5647 => 0x5d18, + 0x5648 => 0x5d4c, 0x5649 => 0x5d52, 0x564a => 0x5d4e, 0x564b => 0x5d4b, + 0x564c => 0x5d6c, 0x564d => 0x5d73, 0x564e => 0x5d76, 0x564f => 0x5d87, + 0x5650 => 0x5d84, 0x5651 => 0x5d82, 0x5652 => 0x5da2, 0x5653 => 0x5d9d, + 0x5654 => 0x5dac, 0x5655 => 0x5dae, 0x5656 => 0x5dbd, 0x5657 => 0x5d90, + 0x5658 => 0x5db7, 0x5659 => 0x5dbc, 0x565a => 0x5dc9, 0x565b => 0x5dcd, + 0x565c => 0x5dd3, 0x565d => 0x5dd2, 0x565e => 0x5dd6, 0x565f => 0x5ddb, + 0x5660 => 0x5deb, 0x5661 => 0x5df2, 0x5662 => 0x5df5, 0x5663 => 0x5e0b, + 0x5664 => 0x5e1a, 0x5665 => 0x5e19, 0x5666 => 0x5e11, 0x5667 => 0x5e1b, + 0x5668 => 0x5e36, 0x5669 => 0x5e37, 0x566a => 0x5e44, 0x566b => 0x5e43, + 0x566c => 0x5e40, 0x566d => 0x5e4e, 0x566e => 0x5e57, 0x566f => 0x5e54, + 0x5670 => 0x5e5f, 0x5671 => 0x5e62, 0x5672 => 0x5e64, 0x5673 => 0x5e47, + 0x5674 => 0x5e75, 0x5675 => 0x5e76, 0x5676 => 0x5e7a, 0x5677 => 0x9ebc, + 0x5678 => 0x5e7f, 0x5679 => 0x5ea0, 0x567a => 0x5ec1, 0x567b => 0x5ec2, + 0x567c => 0x5ec8, 0x567d => 0x5ed0, 0x567e => 0x5ecf, 0x5721 => 0x5ed6, + 0x5722 => 0x5ee3, 0x5723 => 0x5edd, 0x5724 => 0x5eda, 0x5725 => 0x5edb, + 0x5726 => 0x5ee2, 0x5727 => 0x5ee1, 0x5728 => 0x5ee8, 0x5729 => 0x5ee9, + 0x572a => 0x5eec, 0x572b => 0x5ef1, 0x572c => 0x5ef3, 0x572d => 0x5ef0, + 0x572e => 0x5ef4, 0x572f => 0x5ef8, 0x5730 => 0x5efe, 0x5731 => 0x5f03, + 0x5732 => 0x5f09, 0x5733 => 0x5f5d, 0x5734 => 0x5f5c, 0x5735 => 0x5f0b, + 0x5736 => 0x5f11, 0x5737 => 0x5f16, 0x5738 => 0x5f29, 0x5739 => 0x5f2d, + 0x573a => 0x5f38, 0x573b => 0x5f41, 0x573c => 0x5f48, 0x573d => 0x5f4c, + 0x573e => 0x5f4e, 0x573f => 0x5f2f, 0x5740 => 0x5f51, 0x5741 => 0x5f56, + 0x5742 => 0x5f57, 0x5743 => 0x5f59, 0x5744 => 0x5f61, 0x5745 => 0x5f6d, + 0x5746 => 0x5f73, 0x5747 => 0x5f77, 0x5748 => 0x5f83, 0x5749 => 0x5f82, + 0x574a => 0x5f7f, 0x574b => 0x5f8a, 0x574c => 0x5f88, 0x574d => 0x5f91, + 0x574e => 0x5f87, 0x574f => 0x5f9e, 0x5750 => 0x5f99, 0x5751 => 0x5f98, + 0x5752 => 0x5fa0, 0x5753 => 0x5fa8, 0x5754 => 0x5fad, 0x5755 => 0x5fbc, + 0x5756 => 0x5fd6, 0x5757 => 0x5ffb, 0x5758 => 0x5fe4, 0x5759 => 0x5ff8, + 0x575a => 0x5ff1, 0x575b => 0x5fdd, 0x575c => 0x60b3, 0x575d => 0x5fff, + 0x575e => 0x6021, 0x575f => 0x6060, 0x5760 => 0x6019, 0x5761 => 0x6010, + 0x5762 => 0x6029, 0x5763 => 0x600e, 0x5764 => 0x6031, 0x5765 => 0x601b, + 0x5766 => 0x6015, 0x5767 => 0x602b, 0x5768 => 0x6026, 0x5769 => 0x600f, + 0x576a => 0x603a, 0x576b => 0x605a, 0x576c => 0x6041, 0x576d => 0x606a, + 0x576e => 0x6077, 0x576f => 0x605f, 0x5770 => 0x604a, 0x5771 => 0x6046, + 0x5772 => 0x604d, 0x5773 => 0x6063, 0x5774 => 0x6043, 0x5775 => 0x6064, + 0x5776 => 0x6042, 0x5777 => 0x606c, 0x5778 => 0x606b, 0x5779 => 0x6059, + 0x577a => 0x6081, 0x577b => 0x608d, 0x577c => 0x60e7, 0x577d => 0x6083, + 0x577e => 0x609a, 0x5821 => 0x6084, 0x5822 => 0x609b, 0x5823 => 0x6096, + 0x5824 => 0x6097, 0x5825 => 0x6092, 0x5826 => 0x60a7, 0x5827 => 0x608b, + 0x5828 => 0x60e1, 0x5829 => 0x60b8, 0x582a => 0x60e0, 0x582b => 0x60d3, + 0x582c => 0x60b4, 0x582d => 0x5ff0, 0x582e => 0x60bd, 0x582f => 0x60c6, + 0x5830 => 0x60b5, 0x5831 => 0x60d8, 0x5832 => 0x614d, 0x5833 => 0x6115, + 0x5834 => 0x6106, 0x5835 => 0x60f6, 0x5836 => 0x60f7, 0x5837 => 0x6100, + 0x5838 => 0x60f4, 0x5839 => 0x60fa, 0x583a => 0x6103, 0x583b => 0x6121, + 0x583c => 0x60fb, 0x583d => 0x60f1, 0x583e => 0x610d, 0x583f => 0x610e, + 0x5840 => 0x6147, 0x5841 => 0x613e, 0x5842 => 0x6128, 0x5843 => 0x6127, + 0x5844 => 0x614a, 0x5845 => 0x613f, 0x5846 => 0x613c, 0x5847 => 0x612c, + 0x5848 => 0x6134, 0x5849 => 0x613d, 0x584a => 0x6142, 0x584b => 0x6144, + 0x584c => 0x6173, 0x584d => 0x6177, 0x584e => 0x6158, 0x584f => 0x6159, + 0x5850 => 0x615a, 0x5851 => 0x616b, 0x5852 => 0x6174, 0x5853 => 0x616f, + 0x5854 => 0x6165, 0x5855 => 0x6171, 0x5856 => 0x615f, 0x5857 => 0x615d, + 0x5858 => 0x6153, 0x5859 => 0x6175, 0x585a => 0x6199, 0x585b => 0x6196, + 0x585c => 0x6187, 0x585d => 0x61ac, 0x585e => 0x6194, 0x585f => 0x619a, + 0x5860 => 0x618a, 0x5861 => 0x6191, 0x5862 => 0x61ab, 0x5863 => 0x61ae, + 0x5864 => 0x61cc, 0x5865 => 0x61ca, 0x5866 => 0x61c9, 0x5867 => 0x61f7, + 0x5868 => 0x61c8, 0x5869 => 0x61c3, 0x586a => 0x61c6, 0x586b => 0x61ba, + 0x586c => 0x61cb, 0x586d => 0x7f79, 0x586e => 0x61cd, 0x586f => 0x61e6, + 0x5870 => 0x61e3, 0x5871 => 0x61f6, 0x5872 => 0x61fa, 0x5873 => 0x61f4, + 0x5874 => 0x61ff, 0x5875 => 0x61fd, 0x5876 => 0x61fc, 0x5877 => 0x61fe, + 0x5878 => 0x6200, 0x5879 => 0x6208, 0x587a => 0x6209, 0x587b => 0x620d, + 0x587c => 0x620c, 0x587d => 0x6214, 0x587e => 0x621b, 0x5921 => 0x621e, + 0x5922 => 0x6221, 0x5923 => 0x622a, 0x5924 => 0x622e, 0x5925 => 0x6230, + 0x5926 => 0x6232, 0x5927 => 0x6233, 0x5928 => 0x6241, 0x5929 => 0x624e, + 0x592a => 0x625e, 0x592b => 0x6263, 0x592c => 0x625b, 0x592d => 0x6260, + 0x592e => 0x6268, 0x592f => 0x627c, 0x5930 => 0x6282, 0x5931 => 0x6289, + 0x5932 => 0x627e, 0x5933 => 0x6292, 0x5934 => 0x6293, 0x5935 => 0x6296, + 0x5936 => 0x62d4, 0x5937 => 0x6283, 0x5938 => 0x6294, 0x5939 => 0x62d7, + 0x593a => 0x62d1, 0x593b => 0x62bb, 0x593c => 0x62cf, 0x593d => 0x62ff, + 0x593e => 0x62c6, 0x593f => 0x64d4, 0x5940 => 0x62c8, 0x5941 => 0x62dc, + 0x5942 => 0x62cc, 0x5943 => 0x62ca, 0x5944 => 0x62c2, 0x5945 => 0x62c7, + 0x5946 => 0x629b, 0x5947 => 0x62c9, 0x5948 => 0x630c, 0x5949 => 0x62ee, + 0x594a => 0x62f1, 0x594b => 0x6327, 0x594c => 0x6302, 0x594d => 0x6308, + 0x594e => 0x62ef, 0x594f => 0x62f5, 0x5950 => 0x6350, 0x5951 => 0x633e, + 0x5952 => 0x634d, 0x5953 => 0x641c, 0x5954 => 0x634f, 0x5955 => 0x6396, + 0x5956 => 0x638e, 0x5957 => 0x6380, 0x5958 => 0x63ab, 0x5959 => 0x6376, + 0x595a => 0x63a3, 0x595b => 0x638f, 0x595c => 0x6389, 0x595d => 0x639f, + 0x595e => 0x63b5, 0x595f => 0x636b, 0x5960 => 0x6369, 0x5961 => 0x63be, + 0x5962 => 0x63e9, 0x5963 => 0x63c0, 0x5964 => 0x63c6, 0x5965 => 0x63e3, + 0x5966 => 0x63c9, 0x5967 => 0x63d2, 0x5968 => 0x63f6, 0x5969 => 0x63c4, + 0x596a => 0x6416, 0x596b => 0x6434, 0x596c => 0x6406, 0x596d => 0x6413, + 0x596e => 0x6426, 0x596f => 0x6436, 0x5970 => 0x651d, 0x5971 => 0x6417, + 0x5972 => 0x6428, 0x5973 => 0x640f, 0x5974 => 0x6467, 0x5975 => 0x646f, + 0x5976 => 0x6476, 0x5977 => 0x644e, 0x5978 => 0x652a, 0x5979 => 0x6495, + 0x597a => 0x6493, 0x597b => 0x64a5, 0x597c => 0x64a9, 0x597d => 0x6488, + 0x597e => 0x64bc, 0x5a21 => 0x64da, 0x5a22 => 0x64d2, 0x5a23 => 0x64c5, + 0x5a24 => 0x64c7, 0x5a25 => 0x64bb, 0x5a26 => 0x64d8, 0x5a27 => 0x64c2, + 0x5a28 => 0x64f1, 0x5a29 => 0x64e7, 0x5a2a => 0x8209, 0x5a2b => 0x64e0, + 0x5a2c => 0x64e1, 0x5a2d => 0x62ac, 0x5a2e => 0x64e3, 0x5a2f => 0x64ef, + 0x5a30 => 0x652c, 0x5a31 => 0x64f6, 0x5a32 => 0x64f4, 0x5a33 => 0x64f2, + 0x5a34 => 0x64fa, 0x5a35 => 0x6500, 0x5a36 => 0x64fd, 0x5a37 => 0x6518, + 0x5a38 => 0x651c, 0x5a39 => 0x6505, 0x5a3a => 0x6524, 0x5a3b => 0x6523, + 0x5a3c => 0x652b, 0x5a3d => 0x6534, 0x5a3e => 0x6535, 0x5a3f => 0x6537, + 0x5a40 => 0x6536, 0x5a41 => 0x6538, 0x5a42 => 0x754b, 0x5a43 => 0x6548, + 0x5a44 => 0x6556, 0x5a45 => 0x6555, 0x5a46 => 0x654d, 0x5a47 => 0x6558, + 0x5a48 => 0x655e, 0x5a49 => 0x655d, 0x5a4a => 0x6572, 0x5a4b => 0x6578, + 0x5a4c => 0x6582, 0x5a4d => 0x6583, 0x5a4e => 0x8b8a, 0x5a4f => 0x659b, + 0x5a50 => 0x659f, 0x5a51 => 0x65ab, 0x5a52 => 0x65b7, 0x5a53 => 0x65c3, + 0x5a54 => 0x65c6, 0x5a55 => 0x65c1, 0x5a56 => 0x65c4, 0x5a57 => 0x65cc, + 0x5a58 => 0x65d2, 0x5a59 => 0x65db, 0x5a5a => 0x65d9, 0x5a5b => 0x65e0, + 0x5a5c => 0x65e1, 0x5a5d => 0x65f1, 0x5a5e => 0x6772, 0x5a5f => 0x660a, + 0x5a60 => 0x6603, 0x5a61 => 0x65fb, 0x5a62 => 0x6773, 0x5a63 => 0x6635, + 0x5a64 => 0x6636, 0x5a65 => 0x6634, 0x5a66 => 0x661c, 0x5a67 => 0x664f, + 0x5a68 => 0x6644, 0x5a69 => 0x6649, 0x5a6a => 0x6641, 0x5a6b => 0x665e, + 0x5a6c => 0x665d, 0x5a6d => 0x6664, 0x5a6e => 0x6667, 0x5a6f => 0x6668, + 0x5a70 => 0x665f, 0x5a71 => 0x6662, 0x5a72 => 0x6670, 0x5a73 => 0x6683, + 0x5a74 => 0x6688, 0x5a75 => 0x668e, 0x5a76 => 0x6689, 0x5a77 => 0x6684, + 0x5a78 => 0x6698, 0x5a79 => 0x669d, 0x5a7a => 0x66c1, 0x5a7b => 0x66b9, + 0x5a7c => 0x66c9, 0x5a7d => 0x66be, 0x5a7e => 0x66bc, 0x5b21 => 0x66c4, + 0x5b22 => 0x66b8, 0x5b23 => 0x66d6, 0x5b24 => 0x66da, 0x5b25 => 0x66e0, + 0x5b26 => 0x663f, 0x5b27 => 0x66e6, 0x5b28 => 0x66e9, 0x5b29 => 0x66f0, + 0x5b2a => 0x66f5, 0x5b2b => 0x66f7, 0x5b2c => 0x670f, 0x5b2d => 0x6716, + 0x5b2e => 0x671e, 0x5b2f => 0x6726, 0x5b30 => 0x6727, 0x5b31 => 0x9738, + 0x5b32 => 0x672e, 0x5b33 => 0x673f, 0x5b34 => 0x6736, 0x5b35 => 0x6741, + 0x5b36 => 0x6738, 0x5b37 => 0x6737, 0x5b38 => 0x6746, 0x5b39 => 0x675e, + 0x5b3a => 0x6760, 0x5b3b => 0x6759, 0x5b3c => 0x6763, 0x5b3d => 0x6764, + 0x5b3e => 0x6789, 0x5b3f => 0x6770, 0x5b40 => 0x67a9, 0x5b41 => 0x677c, + 0x5b42 => 0x676a, 0x5b43 => 0x678c, 0x5b44 => 0x678b, 0x5b45 => 0x67a6, + 0x5b46 => 0x67a1, 0x5b47 => 0x6785, 0x5b48 => 0x67b7, 0x5b49 => 0x67ef, + 0x5b4a => 0x67b4, 0x5b4b => 0x67ec, 0x5b4c => 0x67b3, 0x5b4d => 0x67e9, + 0x5b4e => 0x67b8, 0x5b4f => 0x67e4, 0x5b50 => 0x67de, 0x5b51 => 0x67dd, + 0x5b52 => 0x67e2, 0x5b53 => 0x67ee, 0x5b54 => 0x67b9, 0x5b55 => 0x67ce, + 0x5b56 => 0x67c6, 0x5b57 => 0x67e7, 0x5b58 => 0x6a9c, 0x5b59 => 0x681e, + 0x5b5a => 0x6846, 0x5b5b => 0x6829, 0x5b5c => 0x6840, 0x5b5d => 0x684d, + 0x5b5e => 0x6832, 0x5b5f => 0x684e, 0x5b60 => 0x68b3, 0x5b61 => 0x682b, + 0x5b62 => 0x6859, 0x5b63 => 0x6863, 0x5b64 => 0x6877, 0x5b65 => 0x687f, + 0x5b66 => 0x689f, 0x5b67 => 0x688f, 0x5b68 => 0x68ad, 0x5b69 => 0x6894, + 0x5b6a => 0x689d, 0x5b6b => 0x689b, 0x5b6c => 0x6883, 0x5b6d => 0x6aae, + 0x5b6e => 0x68b9, 0x5b6f => 0x6874, 0x5b70 => 0x68b5, 0x5b71 => 0x68a0, + 0x5b72 => 0x68ba, 0x5b73 => 0x690f, 0x5b74 => 0x688d, 0x5b75 => 0x687e, + 0x5b76 => 0x6901, 0x5b77 => 0x68ca, 0x5b78 => 0x6908, 0x5b79 => 0x68d8, + 0x5b7a => 0x6922, 0x5b7b => 0x6926, 0x5b7c => 0x68e1, 0x5b7d => 0x690c, + 0x5b7e => 0x68cd, 0x5c21 => 0x68d4, 0x5c22 => 0x68e7, 0x5c23 => 0x68d5, + 0x5c24 => 0x6936, 0x5c25 => 0x6912, 0x5c26 => 0x6904, 0x5c27 => 0x68d7, + 0x5c28 => 0x68e3, 0x5c29 => 0x6925, 0x5c2a => 0x68f9, 0x5c2b => 0x68e0, + 0x5c2c => 0x68ef, 0x5c2d => 0x6928, 0x5c2e => 0x692a, 0x5c2f => 0x691a, + 0x5c30 => 0x6923, 0x5c31 => 0x6921, 0x5c32 => 0x68c6, 0x5c33 => 0x6979, + 0x5c34 => 0x6977, 0x5c35 => 0x695c, 0x5c36 => 0x6978, 0x5c37 => 0x696b, + 0x5c38 => 0x6954, 0x5c39 => 0x697e, 0x5c3a => 0x696e, 0x5c3b => 0x6939, + 0x5c3c => 0x6974, 0x5c3d => 0x693d, 0x5c3e => 0x6959, 0x5c3f => 0x6930, + 0x5c40 => 0x6961, 0x5c41 => 0x695e, 0x5c42 => 0x695d, 0x5c43 => 0x6981, + 0x5c44 => 0x696a, 0x5c45 => 0x69b2, 0x5c46 => 0x69ae, 0x5c47 => 0x69d0, + 0x5c48 => 0x69bf, 0x5c49 => 0x69c1, 0x5c4a => 0x69d3, 0x5c4b => 0x69be, + 0x5c4c => 0x69ce, 0x5c4d => 0x5be8, 0x5c4e => 0x69ca, 0x5c4f => 0x69dd, + 0x5c50 => 0x69bb, 0x5c51 => 0x69c3, 0x5c52 => 0x69a7, 0x5c53 => 0x6a2e, + 0x5c54 => 0x6991, 0x5c55 => 0x69a0, 0x5c56 => 0x699c, 0x5c57 => 0x6995, + 0x5c58 => 0x69b4, 0x5c59 => 0x69de, 0x5c5a => 0x69e8, 0x5c5b => 0x6a02, + 0x5c5c => 0x6a1b, 0x5c5d => 0x69ff, 0x5c5e => 0x6b0a, 0x5c5f => 0x69f9, + 0x5c60 => 0x69f2, 0x5c61 => 0x69e7, 0x5c62 => 0x6a05, 0x5c63 => 0x69b1, + 0x5c64 => 0x6a1e, 0x5c65 => 0x69ed, 0x5c66 => 0x6a14, 0x5c67 => 0x69eb, + 0x5c68 => 0x6a0a, 0x5c69 => 0x6a12, 0x5c6a => 0x6ac1, 0x5c6b => 0x6a23, + 0x5c6c => 0x6a13, 0x5c6d => 0x6a44, 0x5c6e => 0x6a0c, 0x5c6f => 0x6a72, + 0x5c70 => 0x6a36, 0x5c71 => 0x6a78, 0x5c72 => 0x6a47, 0x5c73 => 0x6a62, + 0x5c74 => 0x6a59, 0x5c75 => 0x6a66, 0x5c76 => 0x6a48, 0x5c77 => 0x6a38, + 0x5c78 => 0x6a22, 0x5c79 => 0x6a90, 0x5c7a => 0x6a8d, 0x5c7b => 0x6aa0, + 0x5c7c => 0x6a84, 0x5c7d => 0x6aa2, 0x5c7e => 0x6aa3, 0x5d21 => 0x6a97, + 0x5d22 => 0x8617, 0x5d23 => 0x6abb, 0x5d24 => 0x6ac3, 0x5d25 => 0x6ac2, + 0x5d26 => 0x6ab8, 0x5d27 => 0x6ab3, 0x5d28 => 0x6aac, 0x5d29 => 0x6ade, + 0x5d2a => 0x6ad1, 0x5d2b => 0x6adf, 0x5d2c => 0x6aaa, 0x5d2d => 0x6ada, + 0x5d2e => 0x6aea, 0x5d2f => 0x6afb, 0x5d30 => 0x6b05, 0x5d31 => 0x8616, + 0x5d32 => 0x6afa, 0x5d33 => 0x6b12, 0x5d34 => 0x6b16, 0x5d35 => 0x9b31, + 0x5d36 => 0x6b1f, 0x5d37 => 0x6b38, 0x5d38 => 0x6b37, 0x5d39 => 0x76dc, + 0x5d3a => 0x6b39, 0x5d3b => 0x98ee, 0x5d3c => 0x6b47, 0x5d3d => 0x6b43, + 0x5d3e => 0x6b49, 0x5d3f => 0x6b50, 0x5d40 => 0x6b59, 0x5d41 => 0x6b54, + 0x5d42 => 0x6b5b, 0x5d43 => 0x6b5f, 0x5d44 => 0x6b61, 0x5d45 => 0x6b78, + 0x5d46 => 0x6b79, 0x5d47 => 0x6b7f, 0x5d48 => 0x6b80, 0x5d49 => 0x6b84, + 0x5d4a => 0x6b83, 0x5d4b => 0x6b8d, 0x5d4c => 0x6b98, 0x5d4d => 0x6b95, + 0x5d4e => 0x6b9e, 0x5d4f => 0x6ba4, 0x5d50 => 0x6baa, 0x5d51 => 0x6bab, + 0x5d52 => 0x6baf, 0x5d53 => 0x6bb2, 0x5d54 => 0x6bb1, 0x5d55 => 0x6bb3, + 0x5d56 => 0x6bb7, 0x5d57 => 0x6bbc, 0x5d58 => 0x6bc6, 0x5d59 => 0x6bcb, + 0x5d5a => 0x6bd3, 0x5d5b => 0x6bdf, 0x5d5c => 0x6bec, 0x5d5d => 0x6beb, + 0x5d5e => 0x6bf3, 0x5d5f => 0x6bef, 0x5d60 => 0x9ebe, 0x5d61 => 0x6c08, + 0x5d62 => 0x6c13, 0x5d63 => 0x6c14, 0x5d64 => 0x6c1b, 0x5d65 => 0x6c24, + 0x5d66 => 0x6c23, 0x5d67 => 0x6c5e, 0x5d68 => 0x6c55, 0x5d69 => 0x6c62, + 0x5d6a => 0x6c6a, 0x5d6b => 0x6c82, 0x5d6c => 0x6c8d, 0x5d6d => 0x6c9a, + 0x5d6e => 0x6c81, 0x5d6f => 0x6c9b, 0x5d70 => 0x6c7e, 0x5d71 => 0x6c68, + 0x5d72 => 0x6c73, 0x5d73 => 0x6c92, 0x5d74 => 0x6c90, 0x5d75 => 0x6cc4, + 0x5d76 => 0x6cf1, 0x5d77 => 0x6cd3, 0x5d78 => 0x6cbd, 0x5d79 => 0x6cd7, + 0x5d7a => 0x6cc5, 0x5d7b => 0x6cdd, 0x5d7c => 0x6cae, 0x5d7d => 0x6cb1, + 0x5d7e => 0x6cbe, 0x5e21 => 0x6cba, 0x5e22 => 0x6cdb, 0x5e23 => 0x6cef, + 0x5e24 => 0x6cd9, 0x5e25 => 0x6cea, 0x5e26 => 0x6d1f, 0x5e27 => 0x884d, + 0x5e28 => 0x6d36, 0x5e29 => 0x6d2b, 0x5e2a => 0x6d3d, 0x5e2b => 0x6d38, + 0x5e2c => 0x6d19, 0x5e2d => 0x6d35, 0x5e2e => 0x6d33, 0x5e2f => 0x6d12, + 0x5e30 => 0x6d0c, 0x5e31 => 0x6d63, 0x5e32 => 0x6d93, 0x5e33 => 0x6d64, + 0x5e34 => 0x6d5a, 0x5e35 => 0x6d79, 0x5e36 => 0x6d59, 0x5e37 => 0x6d8e, + 0x5e38 => 0x6d95, 0x5e39 => 0x6fe4, 0x5e3a => 0x6d85, 0x5e3b => 0x6df9, + 0x5e3c => 0x6e15, 0x5e3d => 0x6e0a, 0x5e3e => 0x6db5, 0x5e3f => 0x6dc7, + 0x5e40 => 0x6de6, 0x5e41 => 0x6db8, 0x5e42 => 0x6dc6, 0x5e43 => 0x6dec, + 0x5e44 => 0x6dde, 0x5e45 => 0x6dcc, 0x5e46 => 0x6de8, 0x5e47 => 0x6dd2, + 0x5e48 => 0x6dc5, 0x5e49 => 0x6dfa, 0x5e4a => 0x6dd9, 0x5e4b => 0x6de4, + 0x5e4c => 0x6dd5, 0x5e4d => 0x6dea, 0x5e4e => 0x6dee, 0x5e4f => 0x6e2d, + 0x5e50 => 0x6e6e, 0x5e51 => 0x6e2e, 0x5e52 => 0x6e19, 0x5e53 => 0x6e72, + 0x5e54 => 0x6e5f, 0x5e55 => 0x6e3e, 0x5e56 => 0x6e23, 0x5e57 => 0x6e6b, + 0x5e58 => 0x6e2b, 0x5e59 => 0x6e76, 0x5e5a => 0x6e4d, 0x5e5b => 0x6e1f, + 0x5e5c => 0x6e43, 0x5e5d => 0x6e3a, 0x5e5e => 0x6e4e, 0x5e5f => 0x6e24, + 0x5e60 => 0x6eff, 0x5e61 => 0x6e1d, 0x5e62 => 0x6e38, 0x5e63 => 0x6e82, + 0x5e64 => 0x6eaa, 0x5e65 => 0x6e98, 0x5e66 => 0x6ec9, 0x5e67 => 0x6eb7, + 0x5e68 => 0x6ed3, 0x5e69 => 0x6ebd, 0x5e6a => 0x6eaf, 0x5e6b => 0x6ec4, + 0x5e6c => 0x6eb2, 0x5e6d => 0x6ed4, 0x5e6e => 0x6ed5, 0x5e6f => 0x6e8f, + 0x5e70 => 0x6ea5, 0x5e71 => 0x6ec2, 0x5e72 => 0x6e9f, 0x5e73 => 0x6f41, + 0x5e74 => 0x6f11, 0x5e75 => 0x704c, 0x5e76 => 0x6eec, 0x5e77 => 0x6ef8, + 0x5e78 => 0x6efe, 0x5e79 => 0x6f3f, 0x5e7a => 0x6ef2, 0x5e7b => 0x6f31, + 0x5e7c => 0x6eef, 0x5e7d => 0x6f32, 0x5e7e => 0x6ecc, 0x5f21 => 0x6f3e, + 0x5f22 => 0x6f13, 0x5f23 => 0x6ef7, 0x5f24 => 0x6f86, 0x5f25 => 0x6f7a, + 0x5f26 => 0x6f78, 0x5f27 => 0x6f81, 0x5f28 => 0x6f80, 0x5f29 => 0x6f6f, + 0x5f2a => 0x6f5b, 0x5f2b => 0x6ff3, 0x5f2c => 0x6f6d, 0x5f2d => 0x6f82, + 0x5f2e => 0x6f7c, 0x5f2f => 0x6f58, 0x5f30 => 0x6f8e, 0x5f31 => 0x6f91, + 0x5f32 => 0x6fc2, 0x5f33 => 0x6f66, 0x5f34 => 0x6fb3, 0x5f35 => 0x6fa3, + 0x5f36 => 0x6fa1, 0x5f37 => 0x6fa4, 0x5f38 => 0x6fb9, 0x5f39 => 0x6fc6, + 0x5f3a => 0x6faa, 0x5f3b => 0x6fdf, 0x5f3c => 0x6fd5, 0x5f3d => 0x6fec, + 0x5f3e => 0x6fd4, 0x5f3f => 0x6fd8, 0x5f40 => 0x6ff1, 0x5f41 => 0x6fee, + 0x5f42 => 0x6fdb, 0x5f43 => 0x7009, 0x5f44 => 0x700b, 0x5f45 => 0x6ffa, + 0x5f46 => 0x7011, 0x5f47 => 0x7001, 0x5f48 => 0x700f, 0x5f49 => 0x6ffe, + 0x5f4a => 0x701b, 0x5f4b => 0x701a, 0x5f4c => 0x6f74, 0x5f4d => 0x701d, + 0x5f4e => 0x7018, 0x5f4f => 0x701f, 0x5f50 => 0x7030, 0x5f51 => 0x703e, + 0x5f52 => 0x7032, 0x5f53 => 0x7051, 0x5f54 => 0x7063, 0x5f55 => 0x7099, + 0x5f56 => 0x7092, 0x5f57 => 0x70af, 0x5f58 => 0x70f1, 0x5f59 => 0x70ac, + 0x5f5a => 0x70b8, 0x5f5b => 0x70b3, 0x5f5c => 0x70ae, 0x5f5d => 0x70df, + 0x5f5e => 0x70cb, 0x5f5f => 0x70dd, 0x5f60 => 0x70d9, 0x5f61 => 0x7109, + 0x5f62 => 0x70fd, 0x5f63 => 0x711c, 0x5f64 => 0x7119, 0x5f65 => 0x7165, + 0x5f66 => 0x7155, 0x5f67 => 0x7188, 0x5f68 => 0x7166, 0x5f69 => 0x7162, + 0x5f6a => 0x714c, 0x5f6b => 0x7156, 0x5f6c => 0x716c, 0x5f6d => 0x718f, + 0x5f6e => 0x71fb, 0x5f6f => 0x7184, 0x5f70 => 0x7195, 0x5f71 => 0x71a8, + 0x5f72 => 0x71ac, 0x5f73 => 0x71d7, 0x5f74 => 0x71b9, 0x5f75 => 0x71be, + 0x5f76 => 0x71d2, 0x5f77 => 0x71c9, 0x5f78 => 0x71d4, 0x5f79 => 0x71ce, + 0x5f7a => 0x71e0, 0x5f7b => 0x71ec, 0x5f7c => 0x71e7, 0x5f7d => 0x71f5, + 0x5f7e => 0x71fc, 0x6021 => 0x71f9, 0x6022 => 0x71ff, 0x6023 => 0x720d, + 0x6024 => 0x7210, 0x6025 => 0x721b, 0x6026 => 0x7228, 0x6027 => 0x722d, + 0x6028 => 0x722c, 0x6029 => 0x7230, 0x602a => 0x7232, 0x602b => 0x723b, + 0x602c => 0x723c, 0x602d => 0x723f, 0x602e => 0x7240, 0x602f => 0x7246, + 0x6030 => 0x724b, 0x6031 => 0x7258, 0x6032 => 0x7274, 0x6033 => 0x727e, + 0x6034 => 0x7282, 0x6035 => 0x7281, 0x6036 => 0x7287, 0x6037 => 0x7292, + 0x6038 => 0x7296, 0x6039 => 0x72a2, 0x603a => 0x72a7, 0x603b => 0x72b9, + 0x603c => 0x72b2, 0x603d => 0x72c3, 0x603e => 0x72c6, 0x603f => 0x72c4, + 0x6040 => 0x72ce, 0x6041 => 0x72d2, 0x6042 => 0x72e2, 0x6043 => 0x72e0, + 0x6044 => 0x72e1, 0x6045 => 0x72f9, 0x6046 => 0x72f7, 0x6047 => 0x500f, + 0x6048 => 0x7317, 0x6049 => 0x730a, 0x604a => 0x731c, 0x604b => 0x7316, + 0x604c => 0x731d, 0x604d => 0x7334, 0x604e => 0x732f, 0x604f => 0x7329, + 0x6050 => 0x7325, 0x6051 => 0x733e, 0x6052 => 0x734e, 0x6053 => 0x734f, + 0x6054 => 0x9ed8, 0x6055 => 0x7357, 0x6056 => 0x736a, 0x6057 => 0x7368, + 0x6058 => 0x7370, 0x6059 => 0x7378, 0x605a => 0x7375, 0x605b => 0x737b, + 0x605c => 0x737a, 0x605d => 0x73c8, 0x605e => 0x73b3, 0x605f => 0x73ce, + 0x6060 => 0x73bb, 0x6061 => 0x73c0, 0x6062 => 0x73e5, 0x6063 => 0x73ee, + 0x6064 => 0x73de, 0x6065 => 0x74a2, 0x6066 => 0x7405, 0x6067 => 0x746f, + 0x6068 => 0x7425, 0x6069 => 0x73f8, 0x606a => 0x7432, 0x606b => 0x743a, + 0x606c => 0x7455, 0x606d => 0x743f, 0x606e => 0x745f, 0x606f => 0x7459, + 0x6070 => 0x7441, 0x6071 => 0x745c, 0x6072 => 0x7469, 0x6073 => 0x7470, + 0x6074 => 0x7463, 0x6075 => 0x746a, 0x6076 => 0x7476, 0x6077 => 0x747e, + 0x6078 => 0x748b, 0x6079 => 0x749e, 0x607a => 0x74a7, 0x607b => 0x74ca, + 0x607c => 0x74cf, 0x607d => 0x74d4, 0x607e => 0x73f1, 0x6121 => 0x74e0, + 0x6122 => 0x74e3, 0x6123 => 0x74e7, 0x6124 => 0x74e9, 0x6125 => 0x74ee, + 0x6126 => 0x74f2, 0x6127 => 0x74f0, 0x6128 => 0x74f1, 0x6129 => 0x74f8, + 0x612a => 0x74f7, 0x612b => 0x7504, 0x612c => 0x7503, 0x612d => 0x7505, + 0x612e => 0x750c, 0x612f => 0x750e, 0x6130 => 0x750d, 0x6131 => 0x7515, + 0x6132 => 0x7513, 0x6133 => 0x751e, 0x6134 => 0x7526, 0x6135 => 0x752c, + 0x6136 => 0x753c, 0x6137 => 0x7544, 0x6138 => 0x754d, 0x6139 => 0x754a, + 0x613a => 0x7549, 0x613b => 0x755b, 0x613c => 0x7546, 0x613d => 0x755a, + 0x613e => 0x7569, 0x613f => 0x7564, 0x6140 => 0x7567, 0x6141 => 0x756b, + 0x6142 => 0x756d, 0x6143 => 0x7578, 0x6144 => 0x7576, 0x6145 => 0x7586, + 0x6146 => 0x7587, 0x6147 => 0x7574, 0x6148 => 0x758a, 0x6149 => 0x7589, + 0x614a => 0x7582, 0x614b => 0x7594, 0x614c => 0x759a, 0x614d => 0x759d, + 0x614e => 0x75a5, 0x614f => 0x75a3, 0x6150 => 0x75c2, 0x6151 => 0x75b3, + 0x6152 => 0x75c3, 0x6153 => 0x75b5, 0x6154 => 0x75bd, 0x6155 => 0x75b8, + 0x6156 => 0x75bc, 0x6157 => 0x75b1, 0x6158 => 0x75cd, 0x6159 => 0x75ca, + 0x615a => 0x75d2, 0x615b => 0x75d9, 0x615c => 0x75e3, 0x615d => 0x75de, + 0x615e => 0x75fe, 0x615f => 0x75ff, 0x6160 => 0x75fc, 0x6161 => 0x7601, + 0x6162 => 0x75f0, 0x6163 => 0x75fa, 0x6164 => 0x75f2, 0x6165 => 0x75f3, + 0x6166 => 0x760b, 0x6167 => 0x760d, 0x6168 => 0x7609, 0x6169 => 0x761f, + 0x616a => 0x7627, 0x616b => 0x7620, 0x616c => 0x7621, 0x616d => 0x7622, + 0x616e => 0x7624, 0x616f => 0x7634, 0x6170 => 0x7630, 0x6171 => 0x763b, + 0x6172 => 0x7647, 0x6173 => 0x7648, 0x6174 => 0x7646, 0x6175 => 0x765c, + 0x6176 => 0x7658, 0x6177 => 0x7661, 0x6178 => 0x7662, 0x6179 => 0x7668, + 0x617a => 0x7669, 0x617b => 0x766a, 0x617c => 0x7667, 0x617d => 0x766c, + 0x617e => 0x7670, 0x6221 => 0x7672, 0x6222 => 0x7676, 0x6223 => 0x7678, + 0x6224 => 0x767c, 0x6225 => 0x7680, 0x6226 => 0x7683, 0x6227 => 0x7688, + 0x6228 => 0x768b, 0x6229 => 0x768e, 0x622a => 0x7696, 0x622b => 0x7693, + 0x622c => 0x7699, 0x622d => 0x769a, 0x622e => 0x76b0, 0x622f => 0x76b4, + 0x6230 => 0x76b8, 0x6231 => 0x76b9, 0x6232 => 0x76ba, 0x6233 => 0x76c2, + 0x6234 => 0x76cd, 0x6235 => 0x76d6, 0x6236 => 0x76d2, 0x6237 => 0x76de, + 0x6238 => 0x76e1, 0x6239 => 0x76e5, 0x623a => 0x76e7, 0x623b => 0x76ea, + 0x623c => 0x862f, 0x623d => 0x76fb, 0x623e => 0x7708, 0x623f => 0x7707, + 0x6240 => 0x7704, 0x6241 => 0x7729, 0x6242 => 0x7724, 0x6243 => 0x771e, + 0x6244 => 0x7725, 0x6245 => 0x7726, 0x6246 => 0x771b, 0x6247 => 0x7737, + 0x6248 => 0x7738, 0x6249 => 0x7747, 0x624a => 0x775a, 0x624b => 0x7768, + 0x624c => 0x776b, 0x624d => 0x775b, 0x624e => 0x7765, 0x624f => 0x777f, + 0x6250 => 0x777e, 0x6251 => 0x7779, 0x6252 => 0x778e, 0x6253 => 0x778b, + 0x6254 => 0x7791, 0x6255 => 0x77a0, 0x6256 => 0x779e, 0x6257 => 0x77b0, + 0x6258 => 0x77b6, 0x6259 => 0x77b9, 0x625a => 0x77bf, 0x625b => 0x77bc, + 0x625c => 0x77bd, 0x625d => 0x77bb, 0x625e => 0x77c7, 0x625f => 0x77cd, + 0x6260 => 0x77d7, 0x6261 => 0x77da, 0x6262 => 0x77dc, 0x6263 => 0x77e3, + 0x6264 => 0x77ee, 0x6265 => 0x77fc, 0x6266 => 0x780c, 0x6267 => 0x7812, + 0x6268 => 0x7926, 0x6269 => 0x7820, 0x626a => 0x792a, 0x626b => 0x7845, + 0x626c => 0x788e, 0x626d => 0x7874, 0x626e => 0x7886, 0x626f => 0x787c, + 0x6270 => 0x789a, 0x6271 => 0x788c, 0x6272 => 0x78a3, 0x6273 => 0x78b5, + 0x6274 => 0x78aa, 0x6275 => 0x78af, 0x6276 => 0x78d1, 0x6277 => 0x78c6, + 0x6278 => 0x78cb, 0x6279 => 0x78d4, 0x627a => 0x78be, 0x627b => 0x78bc, + 0x627c => 0x78c5, 0x627d => 0x78ca, 0x627e => 0x78ec, 0x6321 => 0x78e7, + 0x6322 => 0x78da, 0x6323 => 0x78fd, 0x6324 => 0x78f4, 0x6325 => 0x7907, + 0x6326 => 0x7912, 0x6327 => 0x7911, 0x6328 => 0x7919, 0x6329 => 0x792c, + 0x632a => 0x792b, 0x632b => 0x7940, 0x632c => 0x7960, 0x632d => 0x7957, + 0x632e => 0x795f, 0x632f => 0x795a, 0x6330 => 0x7955, 0x6331 => 0x7953, + 0x6332 => 0x797a, 0x6333 => 0x797f, 0x6334 => 0x798a, 0x6335 => 0x799d, + 0x6336 => 0x79a7, 0x6337 => 0x9f4b, 0x6338 => 0x79aa, 0x6339 => 0x79ae, + 0x633a => 0x79b3, 0x633b => 0x79b9, 0x633c => 0x79ba, 0x633d => 0x79c9, + 0x633e => 0x79d5, 0x633f => 0x79e7, 0x6340 => 0x79ec, 0x6341 => 0x79e1, + 0x6342 => 0x79e3, 0x6343 => 0x7a08, 0x6344 => 0x7a0d, 0x6345 => 0x7a18, + 0x6346 => 0x7a19, 0x6347 => 0x7a20, 0x6348 => 0x7a1f, 0x6349 => 0x7980, + 0x634a => 0x7a31, 0x634b => 0x7a3b, 0x634c => 0x7a3e, 0x634d => 0x7a37, + 0x634e => 0x7a43, 0x634f => 0x7a57, 0x6350 => 0x7a49, 0x6351 => 0x7a61, + 0x6352 => 0x7a62, 0x6353 => 0x7a69, 0x6354 => 0x9f9d, 0x6355 => 0x7a70, + 0x6356 => 0x7a79, 0x6357 => 0x7a7d, 0x6358 => 0x7a88, 0x6359 => 0x7a97, + 0x635a => 0x7a95, 0x635b => 0x7a98, 0x635c => 0x7a96, 0x635d => 0x7aa9, + 0x635e => 0x7ac8, 0x635f => 0x7ab0, 0x6360 => 0x7ab6, 0x6361 => 0x7ac5, + 0x6362 => 0x7ac4, 0x6363 => 0x7abf, 0x6364 => 0x9083, 0x6365 => 0x7ac7, + 0x6366 => 0x7aca, 0x6367 => 0x7acd, 0x6368 => 0x7acf, 0x6369 => 0x7ad5, + 0x636a => 0x7ad3, 0x636b => 0x7ad9, 0x636c => 0x7ada, 0x636d => 0x7add, + 0x636e => 0x7ae1, 0x636f => 0x7ae2, 0x6370 => 0x7ae6, 0x6371 => 0x7aed, + 0x6372 => 0x7af0, 0x6373 => 0x7b02, 0x6374 => 0x7b0f, 0x6375 => 0x7b0a, + 0x6376 => 0x7b06, 0x6377 => 0x7b33, 0x6378 => 0x7b18, 0x6379 => 0x7b19, + 0x637a => 0x7b1e, 0x637b => 0x7b35, 0x637c => 0x7b28, 0x637d => 0x7b36, + 0x637e => 0x7b50, 0x6421 => 0x7b7a, 0x6422 => 0x7b04, 0x6423 => 0x7b4d, + 0x6424 => 0x7b0b, 0x6425 => 0x7b4c, 0x6426 => 0x7b45, 0x6427 => 0x7b75, + 0x6428 => 0x7b65, 0x6429 => 0x7b74, 0x642a => 0x7b67, 0x642b => 0x7b70, + 0x642c => 0x7b71, 0x642d => 0x7b6c, 0x642e => 0x7b6e, 0x642f => 0x7b9d, + 0x6430 => 0x7b98, 0x6431 => 0x7b9f, 0x6432 => 0x7b8d, 0x6433 => 0x7b9c, + 0x6434 => 0x7b9a, 0x6435 => 0x7b8b, 0x6436 => 0x7b92, 0x6437 => 0x7b8f, + 0x6438 => 0x7b5d, 0x6439 => 0x7b99, 0x643a => 0x7bcb, 0x643b => 0x7bc1, + 0x643c => 0x7bcc, 0x643d => 0x7bcf, 0x643e => 0x7bb4, 0x643f => 0x7bc6, + 0x6440 => 0x7bdd, 0x6441 => 0x7be9, 0x6442 => 0x7c11, 0x6443 => 0x7c14, + 0x6444 => 0x7be6, 0x6445 => 0x7be5, 0x6446 => 0x7c60, 0x6447 => 0x7c00, + 0x6448 => 0x7c07, 0x6449 => 0x7c13, 0x644a => 0x7bf3, 0x644b => 0x7bf7, + 0x644c => 0x7c17, 0x644d => 0x7c0d, 0x644e => 0x7bf6, 0x644f => 0x7c23, + 0x6450 => 0x7c27, 0x6451 => 0x7c2a, 0x6452 => 0x7c1f, 0x6453 => 0x7c37, + 0x6454 => 0x7c2b, 0x6455 => 0x7c3d, 0x6456 => 0x7c4c, 0x6457 => 0x7c43, + 0x6458 => 0x7c54, 0x6459 => 0x7c4f, 0x645a => 0x7c40, 0x645b => 0x7c50, + 0x645c => 0x7c58, 0x645d => 0x7c5f, 0x645e => 0x7c64, 0x645f => 0x7c56, + 0x6460 => 0x7c65, 0x6461 => 0x7c6c, 0x6462 => 0x7c75, 0x6463 => 0x7c83, + 0x6464 => 0x7c90, 0x6465 => 0x7ca4, 0x6466 => 0x7cad, 0x6467 => 0x7ca2, + 0x6468 => 0x7cab, 0x6469 => 0x7ca1, 0x646a => 0x7ca8, 0x646b => 0x7cb3, + 0x646c => 0x7cb2, 0x646d => 0x7cb1, 0x646e => 0x7cae, 0x646f => 0x7cb9, + 0x6470 => 0x7cbd, 0x6471 => 0x7cc0, 0x6472 => 0x7cc5, 0x6473 => 0x7cc2, + 0x6474 => 0x7cd8, 0x6475 => 0x7cd2, 0x6476 => 0x7cdc, 0x6477 => 0x7ce2, + 0x6478 => 0x9b3b, 0x6479 => 0x7cef, 0x647a => 0x7cf2, 0x647b => 0x7cf4, + 0x647c => 0x7cf6, 0x647d => 0x7cfa, 0x647e => 0x7d06, 0x6521 => 0x7d02, + 0x6522 => 0x7d1c, 0x6523 => 0x7d15, 0x6524 => 0x7d0a, 0x6525 => 0x7d45, + 0x6526 => 0x7d4b, 0x6527 => 0x7d2e, 0x6528 => 0x7d32, 0x6529 => 0x7d3f, + 0x652a => 0x7d35, 0x652b => 0x7d46, 0x652c => 0x7d73, 0x652d => 0x7d56, + 0x652e => 0x7d4e, 0x652f => 0x7d72, 0x6530 => 0x7d68, 0x6531 => 0x7d6e, + 0x6532 => 0x7d4f, 0x6533 => 0x7d63, 0x6534 => 0x7d93, 0x6535 => 0x7d89, + 0x6536 => 0x7d5b, 0x6537 => 0x7d8f, 0x6538 => 0x7d7d, 0x6539 => 0x7d9b, + 0x653a => 0x7dba, 0x653b => 0x7dae, 0x653c => 0x7da3, 0x653d => 0x7db5, + 0x653e => 0x7dc7, 0x653f => 0x7dbd, 0x6540 => 0x7dab, 0x6541 => 0x7e3d, + 0x6542 => 0x7da2, 0x6543 => 0x7daf, 0x6544 => 0x7ddc, 0x6545 => 0x7db8, + 0x6546 => 0x7d9f, 0x6547 => 0x7db0, 0x6548 => 0x7dd8, 0x6549 => 0x7ddd, + 0x654a => 0x7de4, 0x654b => 0x7dde, 0x654c => 0x7dfb, 0x654d => 0x7df2, + 0x654e => 0x7de1, 0x654f => 0x7e05, 0x6550 => 0x7e0a, 0x6551 => 0x7e23, + 0x6552 => 0x7e21, 0x6553 => 0x7e12, 0x6554 => 0x7e31, 0x6555 => 0x7e1f, + 0x6556 => 0x7e09, 0x6557 => 0x7e0b, 0x6558 => 0x7e22, 0x6559 => 0x7e46, + 0x655a => 0x7e66, 0x655b => 0x7e3b, 0x655c => 0x7e35, 0x655d => 0x7e39, + 0x655e => 0x7e43, 0x655f => 0x7e37, 0x6560 => 0x7e32, 0x6561 => 0x7e3a, + 0x6562 => 0x7e67, 0x6563 => 0x7e5d, 0x6564 => 0x7e56, 0x6565 => 0x7e5e, + 0x6566 => 0x7e59, 0x6567 => 0x7e5a, 0x6568 => 0x7e79, 0x6569 => 0x7e6a, + 0x656a => 0x7e69, 0x656b => 0x7e7c, 0x656c => 0x7e7b, 0x656d => 0x7e83, + 0x656e => 0x7dd5, 0x656f => 0x7e7d, 0x6570 => 0x8fae, 0x6571 => 0x7e7f, + 0x6572 => 0x7e88, 0x6573 => 0x7e89, 0x6574 => 0x7e8c, 0x6575 => 0x7e92, + 0x6576 => 0x7e90, 0x6577 => 0x7e93, 0x6578 => 0x7e94, 0x6579 => 0x7e96, + 0x657a => 0x7e8e, 0x657b => 0x7e9b, 0x657c => 0x7e9c, 0x657d => 0x7f38, + 0x657e => 0x7f3a, 0x6621 => 0x7f45, 0x6622 => 0x7f4c, 0x6623 => 0x7f4d, + 0x6624 => 0x7f4e, 0x6625 => 0x7f50, 0x6626 => 0x7f51, 0x6627 => 0x7f55, + 0x6628 => 0x7f54, 0x6629 => 0x7f58, 0x662a => 0x7f5f, 0x662b => 0x7f60, + 0x662c => 0x7f68, 0x662d => 0x7f69, 0x662e => 0x7f67, 0x662f => 0x7f78, + 0x6630 => 0x7f82, 0x6631 => 0x7f86, 0x6632 => 0x7f83, 0x6633 => 0x7f88, + 0x6634 => 0x7f87, 0x6635 => 0x7f8c, 0x6636 => 0x7f94, 0x6637 => 0x7f9e, + 0x6638 => 0x7f9d, 0x6639 => 0x7f9a, 0x663a => 0x7fa3, 0x663b => 0x7faf, + 0x663c => 0x7fb2, 0x663d => 0x7fb9, 0x663e => 0x7fae, 0x663f => 0x7fb6, + 0x6640 => 0x7fb8, 0x6641 => 0x8b71, 0x6642 => 0x7fc5, 0x6643 => 0x7fc6, + 0x6644 => 0x7fca, 0x6645 => 0x7fd5, 0x6646 => 0x7fd4, 0x6647 => 0x7fe1, + 0x6648 => 0x7fe6, 0x6649 => 0x7fe9, 0x664a => 0x7ff3, 0x664b => 0x7ff9, + 0x664c => 0x98dc, 0x664d => 0x8006, 0x664e => 0x8004, 0x664f => 0x800b, + 0x6650 => 0x8012, 0x6651 => 0x8018, 0x6652 => 0x8019, 0x6653 => 0x801c, + 0x6654 => 0x8021, 0x6655 => 0x8028, 0x6656 => 0x803f, 0x6657 => 0x803b, + 0x6658 => 0x804a, 0x6659 => 0x8046, 0x665a => 0x8052, 0x665b => 0x8058, + 0x665c => 0x805a, 0x665d => 0x805f, 0x665e => 0x8062, 0x665f => 0x8068, + 0x6660 => 0x8073, 0x6661 => 0x8072, 0x6662 => 0x8070, 0x6663 => 0x8076, + 0x6664 => 0x8079, 0x6665 => 0x807d, 0x6666 => 0x807f, 0x6667 => 0x8084, + 0x6668 => 0x8086, 0x6669 => 0x8085, 0x666a => 0x809b, 0x666b => 0x8093, + 0x666c => 0x809a, 0x666d => 0x80ad, 0x666e => 0x5190, 0x666f => 0x80ac, + 0x6670 => 0x80db, 0x6671 => 0x80e5, 0x6672 => 0x80d9, 0x6673 => 0x80dd, + 0x6674 => 0x80c4, 0x6675 => 0x80da, 0x6676 => 0x80d6, 0x6677 => 0x8109, + 0x6678 => 0x80ef, 0x6679 => 0x80f1, 0x667a => 0x811b, 0x667b => 0x8129, + 0x667c => 0x8123, 0x667d => 0x812f, 0x667e => 0x814b, 0x6721 => 0x968b, + 0x6722 => 0x8146, 0x6723 => 0x813e, 0x6724 => 0x8153, 0x6725 => 0x8151, + 0x6726 => 0x80fc, 0x6727 => 0x8171, 0x6728 => 0x816e, 0x6729 => 0x8165, + 0x672a => 0x8166, 0x672b => 0x8174, 0x672c => 0x8183, 0x672d => 0x8188, + 0x672e => 0x818a, 0x672f => 0x8180, 0x6730 => 0x8182, 0x6731 => 0x81a0, + 0x6732 => 0x8195, 0x6733 => 0x81a4, 0x6734 => 0x81a3, 0x6735 => 0x815f, + 0x6736 => 0x8193, 0x6737 => 0x81a9, 0x6738 => 0x81b0, 0x6739 => 0x81b5, + 0x673a => 0x81be, 0x673b => 0x81b8, 0x673c => 0x81bd, 0x673d => 0x81c0, + 0x673e => 0x81c2, 0x673f => 0x81ba, 0x6740 => 0x81c9, 0x6741 => 0x81cd, + 0x6742 => 0x81d1, 0x6743 => 0x81d9, 0x6744 => 0x81d8, 0x6745 => 0x81c8, + 0x6746 => 0x81da, 0x6747 => 0x81df, 0x6748 => 0x81e0, 0x6749 => 0x81e7, + 0x674a => 0x81fa, 0x674b => 0x81fb, 0x674c => 0x81fe, 0x674d => 0x8201, + 0x674e => 0x8202, 0x674f => 0x8205, 0x6750 => 0x8207, 0x6751 => 0x820a, + 0x6752 => 0x820d, 0x6753 => 0x8210, 0x6754 => 0x8216, 0x6755 => 0x8229, + 0x6756 => 0x822b, 0x6757 => 0x8238, 0x6758 => 0x8233, 0x6759 => 0x8240, + 0x675a => 0x8259, 0x675b => 0x8258, 0x675c => 0x825d, 0x675d => 0x825a, + 0x675e => 0x825f, 0x675f => 0x8264, 0x6760 => 0x8262, 0x6761 => 0x8268, + 0x6762 => 0x826a, 0x6763 => 0x826b, 0x6764 => 0x822e, 0x6765 => 0x8271, + 0x6766 => 0x8277, 0x6767 => 0x8278, 0x6768 => 0x827e, 0x6769 => 0x828d, + 0x676a => 0x8292, 0x676b => 0x82ab, 0x676c => 0x829f, 0x676d => 0x82bb, + 0x676e => 0x82ac, 0x676f => 0x82e1, 0x6770 => 0x82e3, 0x6771 => 0x82df, + 0x6772 => 0x82d2, 0x6773 => 0x82f4, 0x6774 => 0x82f3, 0x6775 => 0x82fa, + 0x6776 => 0x8393, 0x6777 => 0x8303, 0x6778 => 0x82fb, 0x6779 => 0x82f9, + 0x677a => 0x82de, 0x677b => 0x8306, 0x677c => 0x82dc, 0x677d => 0x8309, + 0x677e => 0x82d9, 0x6821 => 0x8335, 0x6822 => 0x8334, 0x6823 => 0x8316, + 0x6824 => 0x8332, 0x6825 => 0x8331, 0x6826 => 0x8340, 0x6827 => 0x8339, + 0x6828 => 0x8350, 0x6829 => 0x8345, 0x682a => 0x832f, 0x682b => 0x832b, + 0x682c => 0x8317, 0x682d => 0x8318, 0x682e => 0x8385, 0x682f => 0x839a, + 0x6830 => 0x83aa, 0x6831 => 0x839f, 0x6832 => 0x83a2, 0x6833 => 0x8396, + 0x6834 => 0x8323, 0x6835 => 0x838e, 0x6836 => 0x8387, 0x6837 => 0x838a, + 0x6838 => 0x837c, 0x6839 => 0x83b5, 0x683a => 0x8373, 0x683b => 0x8375, + 0x683c => 0x83a0, 0x683d => 0x8389, 0x683e => 0x83a8, 0x683f => 0x83f4, + 0x6840 => 0x8413, 0x6841 => 0x83eb, 0x6842 => 0x83ce, 0x6843 => 0x83fd, + 0x6844 => 0x8403, 0x6845 => 0x83d8, 0x6846 => 0x840b, 0x6847 => 0x83c1, + 0x6848 => 0x83f7, 0x6849 => 0x8407, 0x684a => 0x83e0, 0x684b => 0x83f2, + 0x684c => 0x840d, 0x684d => 0x8422, 0x684e => 0x8420, 0x684f => 0x83bd, + 0x6850 => 0x8438, 0x6851 => 0x8506, 0x6852 => 0x83fb, 0x6853 => 0x846d, + 0x6854 => 0x842a, 0x6855 => 0x843c, 0x6856 => 0x855a, 0x6857 => 0x8484, + 0x6858 => 0x8477, 0x6859 => 0x846b, 0x685a => 0x84ad, 0x685b => 0x846e, + 0x685c => 0x8482, 0x685d => 0x8469, 0x685e => 0x8446, 0x685f => 0x842c, + 0x6860 => 0x846f, 0x6861 => 0x8479, 0x6862 => 0x8435, 0x6863 => 0x84ca, + 0x6864 => 0x8462, 0x6865 => 0x84b9, 0x6866 => 0x84bf, 0x6867 => 0x849f, + 0x6868 => 0x84d9, 0x6869 => 0x84cd, 0x686a => 0x84bb, 0x686b => 0x84da, + 0x686c => 0x84d0, 0x686d => 0x84c1, 0x686e => 0x84c6, 0x686f => 0x84d6, + 0x6870 => 0x84a1, 0x6871 => 0x8521, 0x6872 => 0x84ff, 0x6873 => 0x84f4, + 0x6874 => 0x8517, 0x6875 => 0x8518, 0x6876 => 0x852c, 0x6877 => 0x851f, + 0x6878 => 0x8515, 0x6879 => 0x8514, 0x687a => 0x84fc, 0x687b => 0x8540, + 0x687c => 0x8563, 0x687d => 0x8558, 0x687e => 0x8548, 0x6921 => 0x8541, + 0x6922 => 0x8602, 0x6923 => 0x854b, 0x6924 => 0x8555, 0x6925 => 0x8580, + 0x6926 => 0x85a4, 0x6927 => 0x8588, 0x6928 => 0x8591, 0x6929 => 0x858a, + 0x692a => 0x85a8, 0x692b => 0x856d, 0x692c => 0x8594, 0x692d => 0x859b, + 0x692e => 0x85ea, 0x692f => 0x8587, 0x6930 => 0x859c, 0x6931 => 0x8577, + 0x6932 => 0x857e, 0x6933 => 0x8590, 0x6934 => 0x85c9, 0x6935 => 0x85ba, + 0x6936 => 0x85cf, 0x6937 => 0x85b9, 0x6938 => 0x85d0, 0x6939 => 0x85d5, + 0x693a => 0x85dd, 0x693b => 0x85e5, 0x693c => 0x85dc, 0x693d => 0x85f9, + 0x693e => 0x860a, 0x693f => 0x8613, 0x6940 => 0x860b, 0x6941 => 0x85fe, + 0x6942 => 0x85fa, 0x6943 => 0x8606, 0x6944 => 0x8622, 0x6945 => 0x861a, + 0x6946 => 0x8630, 0x6947 => 0x863f, 0x6948 => 0x864d, 0x6949 => 0x4e55, + 0x694a => 0x8654, 0x694b => 0x865f, 0x694c => 0x8667, 0x694d => 0x8671, + 0x694e => 0x8693, 0x694f => 0x86a3, 0x6950 => 0x86a9, 0x6951 => 0x86aa, + 0x6952 => 0x868b, 0x6953 => 0x868c, 0x6954 => 0x86b6, 0x6955 => 0x86af, + 0x6956 => 0x86c4, 0x6957 => 0x86c6, 0x6958 => 0x86b0, 0x6959 => 0x86c9, + 0x695a => 0x8823, 0x695b => 0x86ab, 0x695c => 0x86d4, 0x695d => 0x86de, + 0x695e => 0x86e9, 0x695f => 0x86ec, 0x6960 => 0x86df, 0x6961 => 0x86db, + 0x6962 => 0x86ef, 0x6963 => 0x8712, 0x6964 => 0x8706, 0x6965 => 0x8708, + 0x6966 => 0x8700, 0x6967 => 0x8703, 0x6968 => 0x86fb, 0x6969 => 0x8711, + 0x696a => 0x8709, 0x696b => 0x870d, 0x696c => 0x86f9, 0x696d => 0x870a, + 0x696e => 0x8734, 0x696f => 0x873f, 0x6970 => 0x8737, 0x6971 => 0x873b, + 0x6972 => 0x8725, 0x6973 => 0x8729, 0x6974 => 0x871a, 0x6975 => 0x8760, + 0x6976 => 0x875f, 0x6977 => 0x8778, 0x6978 => 0x874c, 0x6979 => 0x874e, + 0x697a => 0x8774, 0x697b => 0x8757, 0x697c => 0x8768, 0x697d => 0x876e, + 0x697e => 0x8759, 0x6a21 => 0x8753, 0x6a22 => 0x8763, 0x6a23 => 0x876a, + 0x6a24 => 0x8805, 0x6a25 => 0x87a2, 0x6a26 => 0x879f, 0x6a27 => 0x8782, + 0x6a28 => 0x87af, 0x6a29 => 0x87cb, 0x6a2a => 0x87bd, 0x6a2b => 0x87c0, + 0x6a2c => 0x87d0, 0x6a2d => 0x96d6, 0x6a2e => 0x87ab, 0x6a2f => 0x87c4, + 0x6a30 => 0x87b3, 0x6a31 => 0x87c7, 0x6a32 => 0x87c6, 0x6a33 => 0x87bb, + 0x6a34 => 0x87ef, 0x6a35 => 0x87f2, 0x6a36 => 0x87e0, 0x6a37 => 0x880f, + 0x6a38 => 0x880d, 0x6a39 => 0x87fe, 0x6a3a => 0x87f6, 0x6a3b => 0x87f7, + 0x6a3c => 0x880e, 0x6a3d => 0x87d2, 0x6a3e => 0x8811, 0x6a3f => 0x8816, + 0x6a40 => 0x8815, 0x6a41 => 0x8822, 0x6a42 => 0x8821, 0x6a43 => 0x8831, + 0x6a44 => 0x8836, 0x6a45 => 0x8839, 0x6a46 => 0x8827, 0x6a47 => 0x883b, + 0x6a48 => 0x8844, 0x6a49 => 0x8842, 0x6a4a => 0x8852, 0x6a4b => 0x8859, + 0x6a4c => 0x885e, 0x6a4d => 0x8862, 0x6a4e => 0x886b, 0x6a4f => 0x8881, + 0x6a50 => 0x887e, 0x6a51 => 0x889e, 0x6a52 => 0x8875, 0x6a53 => 0x887d, + 0x6a54 => 0x88b5, 0x6a55 => 0x8872, 0x6a56 => 0x8882, 0x6a57 => 0x8897, + 0x6a58 => 0x8892, 0x6a59 => 0x88ae, 0x6a5a => 0x8899, 0x6a5b => 0x88a2, + 0x6a5c => 0x888d, 0x6a5d => 0x88a4, 0x6a5e => 0x88b0, 0x6a5f => 0x88bf, + 0x6a60 => 0x88b1, 0x6a61 => 0x88c3, 0x6a62 => 0x88c4, 0x6a63 => 0x88d4, + 0x6a64 => 0x88d8, 0x6a65 => 0x88d9, 0x6a66 => 0x88dd, 0x6a67 => 0x88f9, + 0x6a68 => 0x8902, 0x6a69 => 0x88fc, 0x6a6a => 0x88f4, 0x6a6b => 0x88e8, + 0x6a6c => 0x88f2, 0x6a6d => 0x8904, 0x6a6e => 0x890c, 0x6a6f => 0x890a, + 0x6a70 => 0x8913, 0x6a71 => 0x8943, 0x6a72 => 0x891e, 0x6a73 => 0x8925, + 0x6a74 => 0x892a, 0x6a75 => 0x892b, 0x6a76 => 0x8941, 0x6a77 => 0x8944, + 0x6a78 => 0x893b, 0x6a79 => 0x8936, 0x6a7a => 0x8938, 0x6a7b => 0x894c, + 0x6a7c => 0x891d, 0x6a7d => 0x8960, 0x6a7e => 0x895e, 0x6b21 => 0x8966, + 0x6b22 => 0x8964, 0x6b23 => 0x896d, 0x6b24 => 0x896a, 0x6b25 => 0x896f, + 0x6b26 => 0x8974, 0x6b27 => 0x8977, 0x6b28 => 0x897e, 0x6b29 => 0x8983, + 0x6b2a => 0x8988, 0x6b2b => 0x898a, 0x6b2c => 0x8993, 0x6b2d => 0x8998, + 0x6b2e => 0x89a1, 0x6b2f => 0x89a9, 0x6b30 => 0x89a6, 0x6b31 => 0x89ac, + 0x6b32 => 0x89af, 0x6b33 => 0x89b2, 0x6b34 => 0x89ba, 0x6b35 => 0x89bd, + 0x6b36 => 0x89bf, 0x6b37 => 0x89c0, 0x6b38 => 0x89da, 0x6b39 => 0x89dc, + 0x6b3a => 0x89dd, 0x6b3b => 0x89e7, 0x6b3c => 0x89f4, 0x6b3d => 0x89f8, + 0x6b3e => 0x8a03, 0x6b3f => 0x8a16, 0x6b40 => 0x8a10, 0x6b41 => 0x8a0c, + 0x6b42 => 0x8a1b, 0x6b43 => 0x8a1d, 0x6b44 => 0x8a25, 0x6b45 => 0x8a36, + 0x6b46 => 0x8a41, 0x6b47 => 0x8a5b, 0x6b48 => 0x8a52, 0x6b49 => 0x8a46, + 0x6b4a => 0x8a48, 0x6b4b => 0x8a7c, 0x6b4c => 0x8a6d, 0x6b4d => 0x8a6c, + 0x6b4e => 0x8a62, 0x6b4f => 0x8a85, 0x6b50 => 0x8a82, 0x6b51 => 0x8a84, + 0x6b52 => 0x8aa8, 0x6b53 => 0x8aa1, 0x6b54 => 0x8a91, 0x6b55 => 0x8aa5, + 0x6b56 => 0x8aa6, 0x6b57 => 0x8a9a, 0x6b58 => 0x8aa3, 0x6b59 => 0x8ac4, + 0x6b5a => 0x8acd, 0x6b5b => 0x8ac2, 0x6b5c => 0x8ada, 0x6b5d => 0x8aeb, + 0x6b5e => 0x8af3, 0x6b5f => 0x8ae7, 0x6b60 => 0x8ae4, 0x6b61 => 0x8af1, + 0x6b62 => 0x8b14, 0x6b63 => 0x8ae0, 0x6b64 => 0x8ae2, 0x6b65 => 0x8af7, + 0x6b66 => 0x8ade, 0x6b67 => 0x8adb, 0x6b68 => 0x8b0c, 0x6b69 => 0x8b07, + 0x6b6a => 0x8b1a, 0x6b6b => 0x8ae1, 0x6b6c => 0x8b16, 0x6b6d => 0x8b10, + 0x6b6e => 0x8b17, 0x6b6f => 0x8b20, 0x6b70 => 0x8b33, 0x6b71 => 0x97ab, + 0x6b72 => 0x8b26, 0x6b73 => 0x8b2b, 0x6b74 => 0x8b3e, 0x6b75 => 0x8b28, + 0x6b76 => 0x8b41, 0x6b77 => 0x8b4c, 0x6b78 => 0x8b4f, 0x6b79 => 0x8b4e, + 0x6b7a => 0x8b49, 0x6b7b => 0x8b56, 0x6b7c => 0x8b5b, 0x6b7d => 0x8b5a, + 0x6b7e => 0x8b6b, 0x6c21 => 0x8b5f, 0x6c22 => 0x8b6c, 0x6c23 => 0x8b6f, + 0x6c24 => 0x8b74, 0x6c25 => 0x8b7d, 0x6c26 => 0x8b80, 0x6c27 => 0x8b8c, + 0x6c28 => 0x8b8e, 0x6c29 => 0x8b92, 0x6c2a => 0x8b93, 0x6c2b => 0x8b96, + 0x6c2c => 0x8b99, 0x6c2d => 0x8b9a, 0x6c2e => 0x8c3a, 0x6c2f => 0x8c41, + 0x6c30 => 0x8c3f, 0x6c31 => 0x8c48, 0x6c32 => 0x8c4c, 0x6c33 => 0x8c4e, + 0x6c34 => 0x8c50, 0x6c35 => 0x8c55, 0x6c36 => 0x8c62, 0x6c37 => 0x8c6c, + 0x6c38 => 0x8c78, 0x6c39 => 0x8c7a, 0x6c3a => 0x8c82, 0x6c3b => 0x8c89, + 0x6c3c => 0x8c85, 0x6c3d => 0x8c8a, 0x6c3e => 0x8c8d, 0x6c3f => 0x8c8e, + 0x6c40 => 0x8c94, 0x6c41 => 0x8c7c, 0x6c42 => 0x8c98, 0x6c43 => 0x621d, + 0x6c44 => 0x8cad, 0x6c45 => 0x8caa, 0x6c46 => 0x8cbd, 0x6c47 => 0x8cb2, + 0x6c48 => 0x8cb3, 0x6c49 => 0x8cae, 0x6c4a => 0x8cb6, 0x6c4b => 0x8cc8, + 0x6c4c => 0x8cc1, 0x6c4d => 0x8ce4, 0x6c4e => 0x8ce3, 0x6c4f => 0x8cda, + 0x6c50 => 0x8cfd, 0x6c51 => 0x8cfa, 0x6c52 => 0x8cfb, 0x6c53 => 0x8d04, + 0x6c54 => 0x8d05, 0x6c55 => 0x8d0a, 0x6c56 => 0x8d07, 0x6c57 => 0x8d0f, + 0x6c58 => 0x8d0d, 0x6c59 => 0x8d10, 0x6c5a => 0x9f4e, 0x6c5b => 0x8d13, + 0x6c5c => 0x8ccd, 0x6c5d => 0x8d14, 0x6c5e => 0x8d16, 0x6c5f => 0x8d67, + 0x6c60 => 0x8d6d, 0x6c61 => 0x8d71, 0x6c62 => 0x8d73, 0x6c63 => 0x8d81, + 0x6c64 => 0x8d99, 0x6c65 => 0x8dc2, 0x6c66 => 0x8dbe, 0x6c67 => 0x8dba, + 0x6c68 => 0x8dcf, 0x6c69 => 0x8dda, 0x6c6a => 0x8dd6, 0x6c6b => 0x8dcc, + 0x6c6c => 0x8ddb, 0x6c6d => 0x8dcb, 0x6c6e => 0x8dea, 0x6c6f => 0x8deb, + 0x6c70 => 0x8ddf, 0x6c71 => 0x8de3, 0x6c72 => 0x8dfc, 0x6c73 => 0x8e08, + 0x6c74 => 0x8e09, 0x6c75 => 0x8dff, 0x6c76 => 0x8e1d, 0x6c77 => 0x8e1e, + 0x6c78 => 0x8e10, 0x6c79 => 0x8e1f, 0x6c7a => 0x8e42, 0x6c7b => 0x8e35, + 0x6c7c => 0x8e30, 0x6c7d => 0x8e34, 0x6c7e => 0x8e4a, 0x6d21 => 0x8e47, + 0x6d22 => 0x8e49, 0x6d23 => 0x8e4c, 0x6d24 => 0x8e50, 0x6d25 => 0x8e48, + 0x6d26 => 0x8e59, 0x6d27 => 0x8e64, 0x6d28 => 0x8e60, 0x6d29 => 0x8e2a, + 0x6d2a => 0x8e63, 0x6d2b => 0x8e55, 0x6d2c => 0x8e76, 0x6d2d => 0x8e72, + 0x6d2e => 0x8e7c, 0x6d2f => 0x8e81, 0x6d30 => 0x8e87, 0x6d31 => 0x8e85, + 0x6d32 => 0x8e84, 0x6d33 => 0x8e8b, 0x6d34 => 0x8e8a, 0x6d35 => 0x8e93, + 0x6d36 => 0x8e91, 0x6d37 => 0x8e94, 0x6d38 => 0x8e99, 0x6d39 => 0x8eaa, + 0x6d3a => 0x8ea1, 0x6d3b => 0x8eac, 0x6d3c => 0x8eb0, 0x6d3d => 0x8ec6, + 0x6d3e => 0x8eb1, 0x6d3f => 0x8ebe, 0x6d40 => 0x8ec5, 0x6d41 => 0x8ec8, + 0x6d42 => 0x8ecb, 0x6d43 => 0x8edb, 0x6d44 => 0x8ee3, 0x6d45 => 0x8efc, + 0x6d46 => 0x8efb, 0x6d47 => 0x8eeb, 0x6d48 => 0x8efe, 0x6d49 => 0x8f0a, + 0x6d4a => 0x8f05, 0x6d4b => 0x8f15, 0x6d4c => 0x8f12, 0x6d4d => 0x8f19, + 0x6d4e => 0x8f13, 0x6d4f => 0x8f1c, 0x6d50 => 0x8f1f, 0x6d51 => 0x8f1b, + 0x6d52 => 0x8f0c, 0x6d53 => 0x8f26, 0x6d54 => 0x8f33, 0x6d55 => 0x8f3b, + 0x6d56 => 0x8f39, 0x6d57 => 0x8f45, 0x6d58 => 0x8f42, 0x6d59 => 0x8f3e, + 0x6d5a => 0x8f4c, 0x6d5b => 0x8f49, 0x6d5c => 0x8f46, 0x6d5d => 0x8f4e, + 0x6d5e => 0x8f57, 0x6d5f => 0x8f5c, 0x6d60 => 0x8f62, 0x6d61 => 0x8f63, + 0x6d62 => 0x8f64, 0x6d63 => 0x8f9c, 0x6d64 => 0x8f9f, 0x6d65 => 0x8fa3, + 0x6d66 => 0x8fad, 0x6d67 => 0x8faf, 0x6d68 => 0x8fb7, 0x6d69 => 0x8fda, + 0x6d6a => 0x8fe5, 0x6d6b => 0x8fe2, 0x6d6c => 0x8fea, 0x6d6d => 0x8fef, + 0x6d6e => 0x9087, 0x6d6f => 0x8ff4, 0x6d70 => 0x9005, 0x6d71 => 0x8ff9, + 0x6d72 => 0x8ffa, 0x6d73 => 0x9011, 0x6d74 => 0x9015, 0x6d75 => 0x9021, + 0x6d76 => 0x900d, 0x6d77 => 0x901e, 0x6d78 => 0x9016, 0x6d79 => 0x900b, + 0x6d7a => 0x9027, 0x6d7b => 0x9036, 0x6d7c => 0x9035, 0x6d7d => 0x9039, + 0x6d7e => 0x8ff8, 0x6e21 => 0x904f, 0x6e22 => 0x9050, 0x6e23 => 0x9051, + 0x6e24 => 0x9052, 0x6e25 => 0x900e, 0x6e26 => 0x9049, 0x6e27 => 0x903e, + 0x6e28 => 0x9056, 0x6e29 => 0x9058, 0x6e2a => 0x905e, 0x6e2b => 0x9068, + 0x6e2c => 0x906f, 0x6e2d => 0x9076, 0x6e2e => 0x96a8, 0x6e2f => 0x9072, + 0x6e30 => 0x9082, 0x6e31 => 0x907d, 0x6e32 => 0x9081, 0x6e33 => 0x9080, + 0x6e34 => 0x908a, 0x6e35 => 0x9089, 0x6e36 => 0x908f, 0x6e37 => 0x90a8, + 0x6e38 => 0x90af, 0x6e39 => 0x90b1, 0x6e3a => 0x90b5, 0x6e3b => 0x90e2, + 0x6e3c => 0x90e4, 0x6e3d => 0x6248, 0x6e3e => 0x90db, 0x6e3f => 0x9102, + 0x6e40 => 0x9112, 0x6e41 => 0x9119, 0x6e42 => 0x9132, 0x6e43 => 0x9130, + 0x6e44 => 0x914a, 0x6e45 => 0x9156, 0x6e46 => 0x9158, 0x6e47 => 0x9163, + 0x6e48 => 0x9165, 0x6e49 => 0x9169, 0x6e4a => 0x9173, 0x6e4b => 0x9172, + 0x6e4c => 0x918b, 0x6e4d => 0x9189, 0x6e4e => 0x9182, 0x6e4f => 0x91a2, + 0x6e50 => 0x91ab, 0x6e51 => 0x91af, 0x6e52 => 0x91aa, 0x6e53 => 0x91b5, + 0x6e54 => 0x91b4, 0x6e55 => 0x91ba, 0x6e56 => 0x91c0, 0x6e57 => 0x91c1, + 0x6e58 => 0x91c9, 0x6e59 => 0x91cb, 0x6e5a => 0x91d0, 0x6e5b => 0x91d6, + 0x6e5c => 0x91df, 0x6e5d => 0x91e1, 0x6e5e => 0x91db, 0x6e5f => 0x91fc, + 0x6e60 => 0x91f5, 0x6e61 => 0x91f6, 0x6e62 => 0x921e, 0x6e63 => 0x91ff, + 0x6e64 => 0x9214, 0x6e65 => 0x922c, 0x6e66 => 0x9215, 0x6e67 => 0x9211, + 0x6e68 => 0x925e, 0x6e69 => 0x9257, 0x6e6a => 0x9245, 0x6e6b => 0x9249, + 0x6e6c => 0x9264, 0x6e6d => 0x9248, 0x6e6e => 0x9295, 0x6e6f => 0x923f, + 0x6e70 => 0x924b, 0x6e71 => 0x9250, 0x6e72 => 0x929c, 0x6e73 => 0x9296, + 0x6e74 => 0x9293, 0x6e75 => 0x929b, 0x6e76 => 0x925a, 0x6e77 => 0x92cf, + 0x6e78 => 0x92b9, 0x6e79 => 0x92b7, 0x6e7a => 0x92e9, 0x6e7b => 0x930f, + 0x6e7c => 0x92fa, 0x6e7d => 0x9344, 0x6e7e => 0x932e, 0x6f21 => 0x9319, + 0x6f22 => 0x9322, 0x6f23 => 0x931a, 0x6f24 => 0x9323, 0x6f25 => 0x933a, + 0x6f26 => 0x9335, 0x6f27 => 0x933b, 0x6f28 => 0x935c, 0x6f29 => 0x9360, + 0x6f2a => 0x937c, 0x6f2b => 0x936e, 0x6f2c => 0x9356, 0x6f2d => 0x93b0, + 0x6f2e => 0x93ac, 0x6f2f => 0x93ad, 0x6f30 => 0x9394, 0x6f31 => 0x93b9, + 0x6f32 => 0x93d6, 0x6f33 => 0x93d7, 0x6f34 => 0x93e8, 0x6f35 => 0x93e5, + 0x6f36 => 0x93d8, 0x6f37 => 0x93c3, 0x6f38 => 0x93dd, 0x6f39 => 0x93d0, + 0x6f3a => 0x93c8, 0x6f3b => 0x93e4, 0x6f3c => 0x941a, 0x6f3d => 0x9414, + 0x6f3e => 0x9413, 0x6f3f => 0x9403, 0x6f40 => 0x9407, 0x6f41 => 0x9410, + 0x6f42 => 0x9436, 0x6f43 => 0x942b, 0x6f44 => 0x9435, 0x6f45 => 0x9421, + 0x6f46 => 0x943a, 0x6f47 => 0x9441, 0x6f48 => 0x9452, 0x6f49 => 0x9444, + 0x6f4a => 0x945b, 0x6f4b => 0x9460, 0x6f4c => 0x9462, 0x6f4d => 0x945e, + 0x6f4e => 0x946a, 0x6f4f => 0x9229, 0x6f50 => 0x9470, 0x6f51 => 0x9475, + 0x6f52 => 0x9477, 0x6f53 => 0x947d, 0x6f54 => 0x945a, 0x6f55 => 0x947c, + 0x6f56 => 0x947e, 0x6f57 => 0x9481, 0x6f58 => 0x947f, 0x6f59 => 0x9582, + 0x6f5a => 0x9587, 0x6f5b => 0x958a, 0x6f5c => 0x9594, 0x6f5d => 0x9596, + 0x6f5e => 0x9598, 0x6f5f => 0x9599, 0x6f60 => 0x95a0, 0x6f61 => 0x95a8, + 0x6f62 => 0x95a7, 0x6f63 => 0x95ad, 0x6f64 => 0x95bc, 0x6f65 => 0x95bb, + 0x6f66 => 0x95b9, 0x6f67 => 0x95be, 0x6f68 => 0x95ca, 0x6f69 => 0x6ff6, + 0x6f6a => 0x95c3, 0x6f6b => 0x95cd, 0x6f6c => 0x95cc, 0x6f6d => 0x95d5, + 0x6f6e => 0x95d4, 0x6f6f => 0x95d6, 0x6f70 => 0x95dc, 0x6f71 => 0x95e1, + 0x6f72 => 0x95e5, 0x6f73 => 0x95e2, 0x6f74 => 0x9621, 0x6f75 => 0x9628, + 0x6f76 => 0x962e, 0x6f77 => 0x962f, 0x6f78 => 0x9642, 0x6f79 => 0x964c, + 0x6f7a => 0x964f, 0x6f7b => 0x964b, 0x6f7c => 0x9677, 0x6f7d => 0x965c, + 0x6f7e => 0x965e, 0x7021 => 0x965d, 0x7022 => 0x965f, 0x7023 => 0x9666, + 0x7024 => 0x9672, 0x7025 => 0x966c, 0x7026 => 0x968d, 0x7027 => 0x9698, + 0x7028 => 0x9695, 0x7029 => 0x9697, 0x702a => 0x96aa, 0x702b => 0x96a7, + 0x702c => 0x96b1, 0x702d => 0x96b2, 0x702e => 0x96b0, 0x702f => 0x96b4, + 0x7030 => 0x96b6, 0x7031 => 0x96b8, 0x7032 => 0x96b9, 0x7033 => 0x96ce, + 0x7034 => 0x96cb, 0x7035 => 0x96c9, 0x7036 => 0x96cd, 0x7037 => 0x894d, + 0x7038 => 0x96dc, 0x7039 => 0x970d, 0x703a => 0x96d5, 0x703b => 0x96f9, + 0x703c => 0x9704, 0x703d => 0x9706, 0x703e => 0x9708, 0x703f => 0x9713, + 0x7040 => 0x970e, 0x7041 => 0x9711, 0x7042 => 0x970f, 0x7043 => 0x9716, + 0x7044 => 0x9719, 0x7045 => 0x9724, 0x7046 => 0x972a, 0x7047 => 0x9730, + 0x7048 => 0x9739, 0x7049 => 0x973d, 0x704a => 0x973e, 0x704b => 0x9744, + 0x704c => 0x9746, 0x704d => 0x9748, 0x704e => 0x9742, 0x704f => 0x9749, + 0x7050 => 0x975c, 0x7051 => 0x9760, 0x7052 => 0x9764, 0x7053 => 0x9766, + 0x7054 => 0x9768, 0x7055 => 0x52d2, 0x7056 => 0x976b, 0x7057 => 0x9771, + 0x7058 => 0x9779, 0x7059 => 0x9785, 0x705a => 0x977c, 0x705b => 0x9781, + 0x705c => 0x977a, 0x705d => 0x9786, 0x705e => 0x978b, 0x705f => 0x978f, + 0x7060 => 0x9790, 0x7061 => 0x979c, 0x7062 => 0x97a8, 0x7063 => 0x97a6, + 0x7064 => 0x97a3, 0x7065 => 0x97b3, 0x7066 => 0x97b4, 0x7067 => 0x97c3, + 0x7068 => 0x97c6, 0x7069 => 0x97c8, 0x706a => 0x97cb, 0x706b => 0x97dc, + 0x706c => 0x97ed, 0x706d => 0x9f4f, 0x706e => 0x97f2, 0x706f => 0x7adf, + 0x7070 => 0x97f6, 0x7071 => 0x97f5, 0x7072 => 0x980f, 0x7073 => 0x980c, + 0x7074 => 0x9838, 0x7075 => 0x9824, 0x7076 => 0x9821, 0x7077 => 0x9837, + 0x7078 => 0x983d, 0x7079 => 0x9846, 0x707a => 0x984f, 0x707b => 0x984b, + 0x707c => 0x986b, 0x707d => 0x986f, 0x707e => 0x9870, 0x7121 => 0x9871, + 0x7122 => 0x9874, 0x7123 => 0x9873, 0x7124 => 0x98aa, 0x7125 => 0x98af, + 0x7126 => 0x98b1, 0x7127 => 0x98b6, 0x7128 => 0x98c4, 0x7129 => 0x98c3, + 0x712a => 0x98c6, 0x712b => 0x98e9, 0x712c => 0x98eb, 0x712d => 0x9903, + 0x712e => 0x9909, 0x712f => 0x9912, 0x7130 => 0x9914, 0x7131 => 0x9918, + 0x7132 => 0x9921, 0x7133 => 0x991d, 0x7134 => 0x991e, 0x7135 => 0x9924, + 0x7136 => 0x9920, 0x7137 => 0x992c, 0x7138 => 0x992e, 0x7139 => 0x993d, + 0x713a => 0x993e, 0x713b => 0x9942, 0x713c => 0x9949, 0x713d => 0x9945, + 0x713e => 0x9950, 0x713f => 0x994b, 0x7140 => 0x9951, 0x7141 => 0x9952, + 0x7142 => 0x994c, 0x7143 => 0x9955, 0x7144 => 0x9997, 0x7145 => 0x9998, + 0x7146 => 0x99a5, 0x7147 => 0x99ad, 0x7148 => 0x99ae, 0x7149 => 0x99bc, + 0x714a => 0x99df, 0x714b => 0x99db, 0x714c => 0x99dd, 0x714d => 0x99d8, + 0x714e => 0x99d1, 0x714f => 0x99ed, 0x7150 => 0x99ee, 0x7151 => 0x99f1, + 0x7152 => 0x99f2, 0x7153 => 0x99fb, 0x7154 => 0x99f8, 0x7155 => 0x9a01, + 0x7156 => 0x9a0f, 0x7157 => 0x9a05, 0x7158 => 0x99e2, 0x7159 => 0x9a19, + 0x715a => 0x9a2b, 0x715b => 0x9a37, 0x715c => 0x9a45, 0x715d => 0x9a42, + 0x715e => 0x9a40, 0x715f => 0x9a43, 0x7160 => 0x9a3e, 0x7161 => 0x9a55, + 0x7162 => 0x9a4d, 0x7163 => 0x9a5b, 0x7164 => 0x9a57, 0x7165 => 0x9a5f, + 0x7166 => 0x9a62, 0x7167 => 0x9a65, 0x7168 => 0x9a64, 0x7169 => 0x9a69, + 0x716a => 0x9a6b, 0x716b => 0x9a6a, 0x716c => 0x9aad, 0x716d => 0x9ab0, + 0x716e => 0x9abc, 0x716f => 0x9ac0, 0x7170 => 0x9acf, 0x7171 => 0x9ad1, + 0x7172 => 0x9ad3, 0x7173 => 0x9ad4, 0x7174 => 0x9ade, 0x7175 => 0x9adf, + 0x7176 => 0x9ae2, 0x7177 => 0x9ae3, 0x7178 => 0x9ae6, 0x7179 => 0x9aef, + 0x717a => 0x9aeb, 0x717b => 0x9aee, 0x717c => 0x9af4, 0x717d => 0x9af1, + 0x717e => 0x9af7, 0x7221 => 0x9afb, 0x7222 => 0x9b06, 0x7223 => 0x9b18, + 0x7224 => 0x9b1a, 0x7225 => 0x9b1f, 0x7226 => 0x9b22, 0x7227 => 0x9b23, + 0x7228 => 0x9b25, 0x7229 => 0x9b27, 0x722a => 0x9b28, 0x722b => 0x9b29, + 0x722c => 0x9b2a, 0x722d => 0x9b2e, 0x722e => 0x9b2f, 0x722f => 0x9b32, + 0x7230 => 0x9b44, 0x7231 => 0x9b43, 0x7232 => 0x9b4f, 0x7233 => 0x9b4d, + 0x7234 => 0x9b4e, 0x7235 => 0x9b51, 0x7236 => 0x9b58, 0x7237 => 0x9b74, + 0x7238 => 0x9b93, 0x7239 => 0x9b83, 0x723a => 0x9b91, 0x723b => 0x9b96, + 0x723c => 0x9b97, 0x723d => 0x9b9f, 0x723e => 0x9ba0, 0x723f => 0x9ba8, + 0x7240 => 0x9bb4, 0x7241 => 0x9bc0, 0x7242 => 0x9bca, 0x7243 => 0x9bb9, + 0x7244 => 0x9bc6, 0x7245 => 0x9bcf, 0x7246 => 0x9bd1, 0x7247 => 0x9bd2, + 0x7248 => 0x9be3, 0x7249 => 0x9be2, 0x724a => 0x9be4, 0x724b => 0x9bd4, + 0x724c => 0x9be1, 0x724d => 0x9c3a, 0x724e => 0x9bf2, 0x724f => 0x9bf1, + 0x7250 => 0x9bf0, 0x7251 => 0x9c15, 0x7252 => 0x9c14, 0x7253 => 0x9c09, + 0x7254 => 0x9c13, 0x7255 => 0x9c0c, 0x7256 => 0x9c06, 0x7257 => 0x9c08, + 0x7258 => 0x9c12, 0x7259 => 0x9c0a, 0x725a => 0x9c04, 0x725b => 0x9c2e, + 0x725c => 0x9c1b, 0x725d => 0x9c25, 0x725e => 0x9c24, 0x725f => 0x9c21, + 0x7260 => 0x9c30, 0x7261 => 0x9c47, 0x7262 => 0x9c32, 0x7263 => 0x9c46, + 0x7264 => 0x9c3e, 0x7265 => 0x9c5a, 0x7266 => 0x9c60, 0x7267 => 0x9c67, + 0x7268 => 0x9c76, 0x7269 => 0x9c78, 0x726a => 0x9ce7, 0x726b => 0x9cec, + 0x726c => 0x9cf0, 0x726d => 0x9d09, 0x726e => 0x9d08, 0x726f => 0x9ceb, + 0x7270 => 0x9d03, 0x7271 => 0x9d06, 0x7272 => 0x9d2a, 0x7273 => 0x9d26, + 0x7274 => 0x9daf, 0x7275 => 0x9d23, 0x7276 => 0x9d1f, 0x7277 => 0x9d44, + 0x7278 => 0x9d15, 0x7279 => 0x9d12, 0x727a => 0x9d41, 0x727b => 0x9d3f, + 0x727c => 0x9d3e, 0x727d => 0x9d46, 0x727e => 0x9d48, 0x7321 => 0x9d5d, + 0x7322 => 0x9d5e, 0x7323 => 0x9d64, 0x7324 => 0x9d51, 0x7325 => 0x9d50, + 0x7326 => 0x9d59, 0x7327 => 0x9d72, 0x7328 => 0x9d89, 0x7329 => 0x9d87, + 0x732a => 0x9dab, 0x732b => 0x9d6f, 0x732c => 0x9d7a, 0x732d => 0x9d9a, + 0x732e => 0x9da4, 0x732f => 0x9da9, 0x7330 => 0x9db2, 0x7331 => 0x9dc4, + 0x7332 => 0x9dc1, 0x7333 => 0x9dbb, 0x7334 => 0x9db8, 0x7335 => 0x9dba, + 0x7336 => 0x9dc6, 0x7337 => 0x9dcf, 0x7338 => 0x9dc2, 0x7339 => 0x9dd9, + 0x733a => 0x9dd3, 0x733b => 0x9df8, 0x733c => 0x9de6, 0x733d => 0x9ded, + 0x733e => 0x9def, 0x733f => 0x9dfd, 0x7340 => 0x9e1a, 0x7341 => 0x9e1b, + 0x7342 => 0x9e1e, 0x7343 => 0x9e75, 0x7344 => 0x9e79, 0x7345 => 0x9e7d, + 0x7346 => 0x9e81, 0x7347 => 0x9e88, 0x7348 => 0x9e8b, 0x7349 => 0x9e8c, + 0x734a => 0x9e92, 0x734b => 0x9e95, 0x734c => 0x9e91, 0x734d => 0x9e9d, + 0x734e => 0x9ea5, 0x734f => 0x9ea9, 0x7350 => 0x9eb8, 0x7351 => 0x9eaa, + 0x7352 => 0x9ead, 0x7353 => 0x9761, 0x7354 => 0x9ecc, 0x7355 => 0x9ece, + 0x7356 => 0x9ecf, 0x7357 => 0x9ed0, 0x7358 => 0x9ed4, 0x7359 => 0x9edc, + 0x735a => 0x9ede, 0x735b => 0x9edd, 0x735c => 0x9ee0, 0x735d => 0x9ee5, + 0x735e => 0x9ee8, 0x735f => 0x9eef, 0x7360 => 0x9ef4, 0x7361 => 0x9ef6, + 0x7362 => 0x9ef7, 0x7363 => 0x9ef9, 0x7364 => 0x9efb, 0x7365 => 0x9efc, + 0x7366 => 0x9efd, 0x7367 => 0x9f07, 0x7368 => 0x9f08, 0x7369 => 0x76b7, + 0x736a => 0x9f15, 0x736b => 0x9f21, 0x736c => 0x9f2c, 0x736d => 0x9f3e, + 0x736e => 0x9f4a, 0x736f => 0x9f52, 0x7370 => 0x9f54, 0x7371 => 0x9f63, + 0x7372 => 0x9f5f, 0x7373 => 0x9f60, 0x7374 => 0x9f61, 0x7375 => 0x9f66, + 0x7376 => 0x9f67, 0x7377 => 0x9f6c, 0x7378 => 0x9f6a, 0x7379 => 0x9f77, + 0x737a => 0x9f72, 0x737b => 0x9f76, 0x737c => 0x9f95, 0x737d => 0x9f9c, + 0x737e => 0x9fa0, 0x7421 => 0x582f, 0x7422 => 0x69c7, 0x7423 => 0x9059, + 0x7424 => 0x7464, 0x7425 => 0x51dc, 0x7426 => 0x7199, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/Latin.pm b/ExifTool/lib/Image/ExifTool/Charset/Latin.pm new file mode 100644 index 0000000..13cad9f --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Latin.pm @@ -0,0 +1,24 @@ +#------------------------------------------------------------------------------ +# File: Latin.pm +# +# Description: cp1252 to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Latin = ( + 0x80 => 0x20ac, 0x82 => 0x201a, 0x83 => 0x0192, 0x84 => 0x201e, + 0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02c6, + 0x89 => 0x2030, 0x8a => 0x0160, 0x8b => 0x2039, 0x8c => 0x0152, + 0x8e => 0x017d, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c, + 0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014, + 0x98 => 0x02dc, 0x99 => 0x2122, 0x9a => 0x0161, 0x9b => 0x203a, + 0x9c => 0x0153, 0x9e => 0x017e, 0x9f => 0x0178, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/Latin2.pm b/ExifTool/lib/Image/ExifTool/Charset/Latin2.pm new file mode 100644 index 0000000..1f8432e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Latin2.pm @@ -0,0 +1,36 @@ +#------------------------------------------------------------------------------ +# File: Latin2.pm +# +# Description: cp1250 to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1250.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Latin2 = ( + 0x80 => 0x20ac, 0x82 => 0x201a, 0x84 => 0x201e, 0x85 => 0x2026, + 0x86 => 0x2020, 0x87 => 0x2021, 0x89 => 0x2030, 0x8a => 0x0160, + 0x8b => 0x2039, 0x8c => 0x015a, 0x8d => 0x0164, 0x8e => 0x017d, + 0x8f => 0x0179, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c, + 0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014, + 0x99 => 0x2122, 0x9a => 0x0161, 0x9b => 0x203a, 0x9c => 0x015b, + 0x9d => 0x0165, 0x9e => 0x017e, 0x9f => 0x017a, 0xa1 => 0x02c7, + 0xa2 => 0x02d8, 0xa3 => 0x0141, 0xa5 => 0x0104, 0xaa => 0x015e, + 0xaf => 0x017b, 0xb2 => 0x02db, 0xb3 => 0x0142, 0xb9 => 0x0105, + 0xba => 0x015f, 0xbc => 0x013d, 0xbd => 0x02dd, 0xbe => 0x013e, + 0xbf => 0x017c, 0xc0 => 0x0154, 0xc3 => 0x0102, 0xc5 => 0x0139, + 0xc6 => 0x0106, 0xc8 => 0x010c, 0xca => 0x0118, 0xcc => 0x011a, + 0xcf => 0x010e, 0xd0 => 0x0110, 0xd1 => 0x0143, 0xd2 => 0x0147, + 0xd5 => 0x0150, 0xd8 => 0x0158, 0xd9 => 0x016e, 0xdb => 0x0170, + 0xde => 0x0162, 0xe0 => 0x0155, 0xe3 => 0x0103, 0xe5 => 0x013a, + 0xe6 => 0x0107, 0xe8 => 0x010d, 0xea => 0x0119, 0xec => 0x011b, + 0xef => 0x010f, 0xf0 => 0x0111, 0xf1 => 0x0144, 0xf2 => 0x0148, + 0xf5 => 0x0151, 0xf8 => 0x0159, 0xf9 => 0x016f, 0xfb => 0x0171, + 0xfe => 0x0163, 0xff => 0x02d9, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacArabic.pm b/ExifTool/lib/Image/ExifTool/Charset/MacArabic.pm new file mode 100644 index 0000000..05c22c3 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacArabic.pm @@ -0,0 +1,47 @@ +#------------------------------------------------------------------------------ +# File: MacArabic.pm +# +# Description: Mac Arabic to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/ARABIC.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +# and directional characters are ignored +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacArabic = ( + 0x80 => 0xc4, 0x81 => 0xa0, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1, + 0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2, + 0x8a => 0xe4, 0x8b => 0x06ba, 0x8c => 0xab, 0x8d => 0xe7, 0x8e => 0xe9, + 0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0x2026, + 0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xbb, + 0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf7, 0x9c => 0xfa, 0x9d => 0xf9, + 0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x20, 0xa1 => 0x21, 0xa2 => 0x22, + 0xa3 => 0x23, 0xa4 => 0x24, 0xa5 => 0x066a, 0xa6 => 0x26, 0xa7 => 0x27, + 0xa8 => 0x28, 0xa9 => 0x29, 0xaa => 0x2a, 0xab => 0x2b, 0xac => 0x060c, + 0xad => 0x2d, 0xae => 0x2e, 0xaf => 0x2f, 0xb0 => 0x0660, 0xb1 => 0x0661, + 0xb2 => 0x0662, 0xb3 => 0x0663, 0xb4 => 0x0664, 0xb5 => 0x0665, + 0xb6 => 0x0666, 0xb7 => 0x0667, 0xb8 => 0x0668, 0xb9 => 0x0669, 0xba => 0x3a, + 0xbb => 0x061b, 0xbc => 0x3c, 0xbd => 0x3d, 0xbe => 0x3e, 0xbf => 0x061f, + 0xc0 => 0x274a, 0xc1 => 0x0621, 0xc2 => 0x0622, 0xc3 => 0x0623, + 0xc4 => 0x0624, 0xc5 => 0x0625, 0xc6 => 0x0626, 0xc7 => 0x0627, + 0xc8 => 0x0628, 0xc9 => 0x0629, 0xca => 0x062a, 0xcb => 0x062b, + 0xcc => 0x062c, 0xcd => 0x062d, 0xce => 0x062e, 0xcf => 0x062f, + 0xd0 => 0x0630, 0xd1 => 0x0631, 0xd2 => 0x0632, 0xd3 => 0x0633, + 0xd4 => 0x0634, 0xd5 => 0x0635, 0xd6 => 0x0636, 0xd7 => 0x0637, + 0xd8 => 0x0638, 0xd9 => 0x0639, 0xda => 0x063a, 0xdb => 0x5b, 0xdc => 0x5c, + 0xdd => 0x5d, 0xde => 0x5e, 0xdf => 0x5f, 0xe0 => 0x0640, 0xe1 => 0x0641, + 0xe2 => 0x0642, 0xe3 => 0x0643, 0xe4 => 0x0644, 0xe5 => 0x0645, + 0xe6 => 0x0646, 0xe7 => 0x0647, 0xe8 => 0x0648, 0xe9 => 0x0649, + 0xea => 0x064a, 0xeb => 0x064b, 0xec => 0x064c, 0xed => 0x064d, + 0xee => 0x064e, 0xef => 0x064f, 0xf0 => 0x0650, 0xf1 => 0x0651, + 0xf2 => 0x0652, 0xf3 => 0x067e, 0xf4 => 0x0679, 0xf5 => 0x0686, + 0xf6 => 0x06d5, 0xf7 => 0x06a4, 0xf8 => 0x06af, 0xf9 => 0x0688, + 0xfa => 0x0691, 0xfb => 0x7b, 0xfc => 0x7c, 0xfd => 0x7d, 0xfe => 0x0698, + 0xff => 0x06d2, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacChineseCN.pm b/ExifTool/lib/Image/ExifTool/Charset/MacChineseCN.pm new file mode 100644 index 0000000..5c70e0e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacChineseCN.pm @@ -0,0 +1,2088 @@ +#------------------------------------------------------------------------------ +# File: MacChineseCN.pm +# +# Description: Mac Chinese Simplified to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/CHINSIMP.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacChineseCN = ( + 0x80 => [0xfc,0xf87f], 0x81 => 0xf880, 0x82 => 0xf881, 0xfd => 0xa9, + 0xfe => 0x2122, 0xff => 0x2026, + 0xa1 => { + 0xa1 => 0x3000, 0xa2 => 0x3001, 0xa3 => 0x3002, 0xa4 => 0xb7, + 0xa5 => 0x02c9, 0xa6 => 0x02c7, 0xa7 => 0xa8, 0xa8 => 0x3003, + 0xa9 => 0x3005, 0xaa => 0x2014, 0xab => 0x301c, 0xac => 0x2016, + 0xad => 0x22ef, 0xae => 0x2018, 0xaf => 0x2019, 0xb0 => 0x201c, + 0xb1 => 0x201d, 0xb2 => 0x3014, 0xb3 => 0x3015, 0xb4 => 0x3008, + 0xb5 => 0x3009, 0xb6 => 0x300a, 0xb7 => 0x300b, 0xb8 => 0x300c, + 0xb9 => 0x300d, 0xba => 0x300e, 0xbb => 0x300f, 0xbc => 0x3016, + 0xbd => 0x3017, 0xbe => 0x3010, 0xbf => 0x3011, 0xc0 => 0xb1, 0xc1 => 0xd7, + 0xc2 => 0xf7, 0xc3 => 0x2236, 0xc4 => 0x2227, 0xc5 => 0x2228, + 0xc6 => 0x2211, 0xc7 => 0x220f, 0xc8 => 0x222a, 0xc9 => 0x2229, + 0xca => 0x2208, 0xcb => 0x2237, 0xcc => 0x221a, 0xcd => 0x22a5, + 0xce => 0x2225, 0xcf => 0x2220, 0xd0 => 0x2312, 0xd1 => 0x2299, + 0xd2 => 0x222b, 0xd3 => 0x222e, 0xd4 => 0x2261, 0xd5 => 0x224c, + 0xd6 => 0x2248, 0xd7 => 0x223d, 0xd8 => 0x221d, 0xd9 => 0x2260, + 0xda => 0x226e, 0xdb => 0x226f, 0xdc => 0x2264, 0xdd => 0x2265, + 0xde => 0x221e, 0xdf => 0x2235, 0xe0 => 0x2234, 0xe1 => 0x2642, + 0xe2 => 0x2640, 0xe3 => 0xb0, 0xe4 => 0x2032, 0xe5 => 0x2033, + 0xe6 => 0x2103, 0xe7 => 0xff04, 0xe8 => 0xa4, 0xe9 => 0xa2, 0xea => 0xa3, + 0xeb => 0x2030, 0xec => 0xa7, 0xed => 0x2116, 0xee => 0x2606, + 0xef => 0x2605, 0xf0 => 0x25cb, 0xf1 => 0x25cf, 0xf2 => 0x25ce, + 0xf3 => 0x25c7, 0xf4 => 0x25c6, 0xf5 => 0x25a1, 0xf6 => 0x25a0, + 0xf7 => 0x25b3, 0xf8 => 0x25b2, 0xf9 => 0x203b, 0xfa => 0x2192, + 0xfb => 0x2190, 0xfc => 0x2191, 0xfd => 0x2193, 0xfe => 0x3013, + }, + 0xa2 => { + 0xb1 => 0x2488, 0xb2 => 0x2489, 0xb3 => 0x248a, 0xb4 => 0x248b, + 0xb5 => 0x248c, 0xb6 => 0x248d, 0xb7 => 0x248e, 0xb8 => 0x248f, + 0xb9 => 0x2490, 0xba => 0x2491, 0xbb => 0x2492, 0xbc => 0x2493, + 0xbd => 0x2494, 0xbe => 0x2495, 0xbf => 0x2496, 0xc0 => 0x2497, + 0xc1 => 0x2498, 0xc2 => 0x2499, 0xc3 => 0x249a, 0xc4 => 0x249b, + 0xc5 => 0x2474, 0xc6 => 0x2475, 0xc7 => 0x2476, 0xc8 => 0x2477, + 0xc9 => 0x2478, 0xca => 0x2479, 0xcb => 0x247a, 0xcc => 0x247b, + 0xcd => 0x247c, 0xce => 0x247d, 0xcf => 0x247e, 0xd0 => 0x247f, + 0xd1 => 0x2480, 0xd2 => 0x2481, 0xd3 => 0x2482, 0xd4 => 0x2483, + 0xd5 => 0x2484, 0xd6 => 0x2485, 0xd7 => 0x2486, 0xd8 => 0x2487, + 0xd9 => 0x2460, 0xda => 0x2461, 0xdb => 0x2462, 0xdc => 0x2463, + 0xdd => 0x2464, 0xde => 0x2465, 0xdf => 0x2466, 0xe0 => 0x2467, + 0xe1 => 0x2468, 0xe2 => 0x2469, 0xe5 => 0x3220, 0xe6 => 0x3221, + 0xe7 => 0x3222, 0xe8 => 0x3223, 0xe9 => 0x3224, 0xea => 0x3225, + 0xeb => 0x3226, 0xec => 0x3227, 0xed => 0x3228, 0xee => 0x3229, + 0xf1 => 0x2160, 0xf2 => 0x2161, 0xf3 => 0x2162, 0xf4 => 0x2163, + 0xf5 => 0x2164, 0xf6 => 0x2165, 0xf7 => 0x2166, 0xf8 => 0x2167, + 0xf9 => 0x2168, 0xfa => 0x2169, 0xfb => 0x216a, 0xfc => 0x216b, + }, + 0xa3 => { + 0xa1 => 0xff01, 0xa2 => 0xff02, 0xa3 => 0xff03, 0xa4 => 0xa5, + 0xa5 => 0xff05, 0xa6 => 0xff06, 0xa7 => 0xff07, 0xa8 => 0xff08, + 0xa9 => 0xff09, 0xaa => 0xff0a, 0xab => 0xff0b, 0xac => 0xff0c, + 0xad => 0xff0d, 0xae => 0xff0e, 0xaf => 0xff0f, 0xb0 => 0xff10, + 0xb1 => 0xff11, 0xb2 => 0xff12, 0xb3 => 0xff13, 0xb4 => 0xff14, + 0xb5 => 0xff15, 0xb6 => 0xff16, 0xb7 => 0xff17, 0xb8 => 0xff18, + 0xb9 => 0xff19, 0xba => 0xff1a, 0xbb => 0xff1b, 0xbc => 0xff1c, + 0xbd => 0xff1d, 0xbe => 0xff1e, 0xbf => 0xff1f, 0xc0 => 0xff20, + 0xc1 => 0xff21, 0xc2 => 0xff22, 0xc3 => 0xff23, 0xc4 => 0xff24, + 0xc5 => 0xff25, 0xc6 => 0xff26, 0xc7 => 0xff27, 0xc8 => 0xff28, + 0xc9 => 0xff29, 0xca => 0xff2a, 0xcb => 0xff2b, 0xcc => 0xff2c, + 0xcd => 0xff2d, 0xce => 0xff2e, 0xcf => 0xff2f, 0xd0 => 0xff30, + 0xd1 => 0xff31, 0xd2 => 0xff32, 0xd3 => 0xff33, 0xd4 => 0xff34, + 0xd5 => 0xff35, 0xd6 => 0xff36, 0xd7 => 0xff37, 0xd8 => 0xff38, + 0xd9 => 0xff39, 0xda => 0xff3a, 0xdb => 0xff3b, 0xdc => 0xff3c, + 0xdd => 0xff3d, 0xde => 0xff3e, 0xdf => 0xff3f, 0xe0 => 0xff40, + 0xe1 => 0xff41, 0xe2 => 0xff42, 0xe3 => 0xff43, 0xe4 => 0xff44, + 0xe5 => 0xff45, 0xe6 => 0xff46, 0xe7 => 0xff47, 0xe8 => 0xff48, + 0xe9 => 0xff49, 0xea => 0xff4a, 0xeb => 0xff4b, 0xec => 0xff4c, + 0xed => 0xff4d, 0xee => 0xff4e, 0xef => 0xff4f, 0xf0 => 0xff50, + 0xf1 => 0xff51, 0xf2 => 0xff52, 0xf3 => 0xff53, 0xf4 => 0xff54, + 0xf5 => 0xff55, 0xf6 => 0xff56, 0xf7 => 0xff57, 0xf8 => 0xff58, + 0xf9 => 0xff59, 0xfa => 0xff5a, 0xfb => 0xff5b, 0xfc => 0xff5c, + 0xfd => 0xff5d, 0xfe => 0x203e, + }, + 0xa4 => { + 0xa1 => 0x3041, 0xa2 => 0x3042, 0xa3 => 0x3043, 0xa4 => 0x3044, + 0xa5 => 0x3045, 0xa6 => 0x3046, 0xa7 => 0x3047, 0xa8 => 0x3048, + 0xa9 => 0x3049, 0xaa => 0x304a, 0xab => 0x304b, 0xac => 0x304c, + 0xad => 0x304d, 0xae => 0x304e, 0xaf => 0x304f, 0xb0 => 0x3050, + 0xb1 => 0x3051, 0xb2 => 0x3052, 0xb3 => 0x3053, 0xb4 => 0x3054, + 0xb5 => 0x3055, 0xb6 => 0x3056, 0xb7 => 0x3057, 0xb8 => 0x3058, + 0xb9 => 0x3059, 0xba => 0x305a, 0xbb => 0x305b, 0xbc => 0x305c, + 0xbd => 0x305d, 0xbe => 0x305e, 0xbf => 0x305f, 0xc0 => 0x3060, + 0xc1 => 0x3061, 0xc2 => 0x3062, 0xc3 => 0x3063, 0xc4 => 0x3064, + 0xc5 => 0x3065, 0xc6 => 0x3066, 0xc7 => 0x3067, 0xc8 => 0x3068, + 0xc9 => 0x3069, 0xca => 0x306a, 0xcb => 0x306b, 0xcc => 0x306c, + 0xcd => 0x306d, 0xce => 0x306e, 0xcf => 0x306f, 0xd0 => 0x3070, + 0xd1 => 0x3071, 0xd2 => 0x3072, 0xd3 => 0x3073, 0xd4 => 0x3074, + 0xd5 => 0x3075, 0xd6 => 0x3076, 0xd7 => 0x3077, 0xd8 => 0x3078, + 0xd9 => 0x3079, 0xda => 0x307a, 0xdb => 0x307b, 0xdc => 0x307c, + 0xdd => 0x307d, 0xde => 0x307e, 0xdf => 0x307f, 0xe0 => 0x3080, + 0xe1 => 0x3081, 0xe2 => 0x3082, 0xe3 => 0x3083, 0xe4 => 0x3084, + 0xe5 => 0x3085, 0xe6 => 0x3086, 0xe7 => 0x3087, 0xe8 => 0x3088, + 0xe9 => 0x3089, 0xea => 0x308a, 0xeb => 0x308b, 0xec => 0x308c, + 0xed => 0x308d, 0xee => 0x308e, 0xef => 0x308f, 0xf0 => 0x3090, + 0xf1 => 0x3091, 0xf2 => 0x3092, 0xf3 => 0x3093, + }, + 0xa5 => { + 0xa1 => 0x30a1, 0xa2 => 0x30a2, 0xa3 => 0x30a3, 0xa4 => 0x30a4, + 0xa5 => 0x30a5, 0xa6 => 0x30a6, 0xa7 => 0x30a7, 0xa8 => 0x30a8, + 0xa9 => 0x30a9, 0xaa => 0x30aa, 0xab => 0x30ab, 0xac => 0x30ac, + 0xad => 0x30ad, 0xae => 0x30ae, 0xaf => 0x30af, 0xb0 => 0x30b0, + 0xb1 => 0x30b1, 0xb2 => 0x30b2, 0xb3 => 0x30b3, 0xb4 => 0x30b4, + 0xb5 => 0x30b5, 0xb6 => 0x30b6, 0xb7 => 0x30b7, 0xb8 => 0x30b8, + 0xb9 => 0x30b9, 0xba => 0x30ba, 0xbb => 0x30bb, 0xbc => 0x30bc, + 0xbd => 0x30bd, 0xbe => 0x30be, 0xbf => 0x30bf, 0xc0 => 0x30c0, + 0xc1 => 0x30c1, 0xc2 => 0x30c2, 0xc3 => 0x30c3, 0xc4 => 0x30c4, + 0xc5 => 0x30c5, 0xc6 => 0x30c6, 0xc7 => 0x30c7, 0xc8 => 0x30c8, + 0xc9 => 0x30c9, 0xca => 0x30ca, 0xcb => 0x30cb, 0xcc => 0x30cc, + 0xcd => 0x30cd, 0xce => 0x30ce, 0xcf => 0x30cf, 0xd0 => 0x30d0, + 0xd1 => 0x30d1, 0xd2 => 0x30d2, 0xd3 => 0x30d3, 0xd4 => 0x30d4, + 0xd5 => 0x30d5, 0xd6 => 0x30d6, 0xd7 => 0x30d7, 0xd8 => 0x30d8, + 0xd9 => 0x30d9, 0xda => 0x30da, 0xdb => 0x30db, 0xdc => 0x30dc, + 0xdd => 0x30dd, 0xde => 0x30de, 0xdf => 0x30df, 0xe0 => 0x30e0, + 0xe1 => 0x30e1, 0xe2 => 0x30e2, 0xe3 => 0x30e3, 0xe4 => 0x30e4, + 0xe5 => 0x30e5, 0xe6 => 0x30e6, 0xe7 => 0x30e7, 0xe8 => 0x30e8, + 0xe9 => 0x30e9, 0xea => 0x30ea, 0xeb => 0x30eb, 0xec => 0x30ec, + 0xed => 0x30ed, 0xee => 0x30ee, 0xef => 0x30ef, 0xf0 => 0x30f0, + 0xf1 => 0x30f1, 0xf2 => 0x30f2, 0xf3 => 0x30f3, 0xf4 => 0x30f4, + 0xf5 => 0x30f5, 0xf6 => 0x30f6, + }, + 0xa6 => { + 0xa1 => 0x0391, 0xa2 => 0x0392, 0xa3 => 0x0393, 0xa4 => 0x0394, + 0xa5 => 0x0395, 0xa6 => 0x0396, 0xa7 => 0x0397, 0xa8 => 0x0398, + 0xa9 => 0x0399, 0xaa => 0x039a, 0xab => 0x039b, 0xac => 0x039c, + 0xad => 0x039d, 0xae => 0x039e, 0xaf => 0x039f, 0xb0 => 0x03a0, + 0xb1 => 0x03a1, 0xb2 => 0x03a3, 0xb3 => 0x03a4, 0xb4 => 0x03a5, + 0xb5 => 0x03a6, 0xb6 => 0x03a7, 0xb7 => 0x03a8, 0xb8 => 0x03a9, + 0xc1 => 0x03b1, 0xc2 => 0x03b2, 0xc3 => 0x03b3, 0xc4 => 0x03b4, + 0xc5 => 0x03b5, 0xc6 => 0x03b6, 0xc7 => 0x03b7, 0xc8 => 0x03b8, + 0xc9 => 0x03b9, 0xca => 0x03ba, 0xcb => 0x03bb, 0xcc => 0x03bc, + 0xcd => 0x03bd, 0xce => 0x03be, 0xcf => 0x03bf, 0xd0 => 0x03c0, + 0xd1 => 0x03c1, 0xd2 => 0x03c3, 0xd3 => 0x03c4, 0xd4 => 0x03c5, + 0xd5 => 0x03c6, 0xd6 => 0x03c7, 0xd7 => 0x03c8, 0xd8 => 0x03c9, + 0xd9 => [0xff0c,0xf87e], 0xda => [0x3002,0xf87e], 0xdb => [0x3001,0xf87e], + 0xdc => [0xff1a,0xf87e], 0xdd => [0xff1b,0xf87e], 0xde => [0xff01,0xf87e], + 0xdf => [0xff1f,0xf87e], 0xe0 => 0xfe35, 0xe1 => 0xfe36, 0xe2 => 0xfe39, + 0xe3 => 0xfe3a, 0xe4 => 0xfe3f, 0xe5 => 0xfe40, 0xe6 => 0xfe3d, + 0xe7 => 0xfe3e, 0xe8 => 0xfe41, 0xe9 => 0xfe42, 0xea => 0xfe43, + 0xeb => 0xfe44, 0xec => [0x3016,0xf87e], 0xed => [0x3017,0xf87e], + 0xee => 0xfe3b, 0xef => 0xfe3c, 0xf0 => 0xfe37, 0xf1 => 0xfe38, + 0xf2 => 0xfe31, 0xf3 => [0x22ef,0xf87e], 0xf4 => 0xfe33, 0xf5 => 0xfe34, + }, + 0xa7 => { + 0xa1 => 0x0410, 0xa2 => 0x0411, 0xa3 => 0x0412, 0xa4 => 0x0413, + 0xa5 => 0x0414, 0xa6 => 0x0415, 0xa7 => 0x0401, 0xa8 => 0x0416, + 0xa9 => 0x0417, 0xaa => 0x0418, 0xab => 0x0419, 0xac => 0x041a, + 0xad => 0x041b, 0xae => 0x041c, 0xaf => 0x041d, 0xb0 => 0x041e, + 0xb1 => 0x041f, 0xb2 => 0x0420, 0xb3 => 0x0421, 0xb4 => 0x0422, + 0xb5 => 0x0423, 0xb6 => 0x0424, 0xb7 => 0x0425, 0xb8 => 0x0426, + 0xb9 => 0x0427, 0xba => 0x0428, 0xbb => 0x0429, 0xbc => 0x042a, + 0xbd => 0x042b, 0xbe => 0x042c, 0xbf => 0x042d, 0xc0 => 0x042e, + 0xc1 => 0x042f, 0xd1 => 0x0430, 0xd2 => 0x0431, 0xd3 => 0x0432, + 0xd4 => 0x0433, 0xd5 => 0x0434, 0xd6 => 0x0435, 0xd7 => 0x0451, + 0xd8 => 0x0436, 0xd9 => 0x0437, 0xda => 0x0438, 0xdb => 0x0439, + 0xdc => 0x043a, 0xdd => 0x043b, 0xde => 0x043c, 0xdf => 0x043d, + 0xe0 => 0x043e, 0xe1 => 0x043f, 0xe2 => 0x0440, 0xe3 => 0x0441, + 0xe4 => 0x0442, 0xe5 => 0x0443, 0xe6 => 0x0444, 0xe7 => 0x0445, + 0xe8 => 0x0446, 0xe9 => 0x0447, 0xea => 0x0448, 0xeb => 0x0449, + 0xec => 0x044a, 0xed => 0x044b, 0xee => 0x044c, 0xef => 0x044d, + 0xf0 => 0x044e, 0xf1 => 0x044f, + }, + 0xa8 => { + 0xa1 => 0x0101, 0xa2 => 0xe1, 0xa3 => 0x01ce, 0xa4 => 0xe0, 0xa5 => 0x0113, + 0xa6 => 0xe9, 0xa7 => 0x011b, 0xa8 => 0xe8, 0xa9 => 0x012b, 0xaa => 0xed, + 0xab => 0x01d0, 0xac => 0xec, 0xad => 0x014d, 0xae => 0xf3, 0xaf => 0x01d2, + 0xb0 => 0xf2, 0xb1 => 0x016b, 0xb2 => 0xfa, 0xb3 => 0x01d4, 0xb4 => 0xf9, + 0xb5 => 0x01d6, 0xb6 => 0x01d8, 0xb7 => 0x01da, 0xb8 => 0x01dc, + 0xb9 => 0xfc, 0xba => 0xea, 0xbb => 0x0251, 0xbc => 0x1e3f, 0xbd => 0x0144, + 0xbe => 0x0148, 0xbf => 0x01f9, 0xc0 => 0x0261, 0xc5 => 0x3105, + 0xc6 => 0x3106, 0xc7 => 0x3107, 0xc8 => 0x3108, 0xc9 => 0x3109, + 0xca => 0x310a, 0xcb => 0x310b, 0xcc => 0x310c, 0xcd => 0x310d, + 0xce => 0x310e, 0xcf => 0x310f, 0xd0 => 0x3110, 0xd1 => 0x3111, + 0xd2 => 0x3112, 0xd3 => 0x3113, 0xd4 => 0x3114, 0xd5 => 0x3115, + 0xd6 => 0x3116, 0xd7 => 0x3117, 0xd8 => 0x3118, 0xd9 => 0x3119, + 0xda => 0x311a, 0xdb => 0x311b, 0xdc => 0x311c, 0xdd => 0x311d, + 0xde => 0x311e, 0xdf => 0x311f, 0xe0 => 0x3120, 0xe1 => 0x3121, + 0xe2 => 0x3122, 0xe3 => 0x3123, 0xe4 => 0x3124, 0xe5 => 0x3125, + 0xe6 => 0x3126, 0xe7 => 0x3127, 0xe8 => 0x3128, 0xe9 => 0x3129, + }, + 0xa9 => { + 0xa4 => 0x2500, 0xa5 => 0x2501, 0xa6 => 0x2502, 0xa7 => 0x2503, + 0xa8 => 0x2504, 0xa9 => 0x2505, 0xaa => 0x2506, 0xab => 0x2507, + 0xac => 0x2508, 0xad => 0x2509, 0xae => 0x250a, 0xaf => 0x250b, + 0xb0 => 0x250c, 0xb1 => 0x250d, 0xb2 => 0x250e, 0xb3 => 0x250f, + 0xb4 => 0x2510, 0xb5 => 0x2511, 0xb6 => 0x2512, 0xb7 => 0x2513, + 0xb8 => 0x2514, 0xb9 => 0x2515, 0xba => 0x2516, 0xbb => 0x2517, + 0xbc => 0x2518, 0xbd => 0x2519, 0xbe => 0x251a, 0xbf => 0x251b, + 0xc0 => 0x251c, 0xc1 => 0x251d, 0xc2 => 0x251e, 0xc3 => 0x251f, + 0xc4 => 0x2520, 0xc5 => 0x2521, 0xc6 => 0x2522, 0xc7 => 0x2523, + 0xc8 => 0x2524, 0xc9 => 0x2525, 0xca => 0x2526, 0xcb => 0x2527, + 0xcc => 0x2528, 0xcd => 0x2529, 0xce => 0x252a, 0xcf => 0x252b, + 0xd0 => 0x252c, 0xd1 => 0x252d, 0xd2 => 0x252e, 0xd3 => 0x252f, + 0xd4 => 0x2530, 0xd5 => 0x2531, 0xd6 => 0x2532, 0xd7 => 0x2533, + 0xd8 => 0x2534, 0xd9 => 0x2535, 0xda => 0x2536, 0xdb => 0x2537, + 0xdc => 0x2538, 0xdd => 0x2539, 0xde => 0x253a, 0xdf => 0x253b, + 0xe0 => 0x253c, 0xe1 => 0x253d, 0xe2 => 0x253e, 0xe3 => 0x253f, + 0xe4 => 0x2540, 0xe5 => 0x2541, 0xe6 => 0x2542, 0xe7 => 0x2543, + 0xe8 => 0x2544, 0xe9 => 0x2545, 0xea => 0x2546, 0xeb => 0x2547, + 0xec => 0x2548, 0xed => 0x2549, 0xee => 0x254a, 0xef => 0x254b, + }, + 0xb0 => { + 0xa1 => 0x554a, 0xa2 => 0x963f, 0xa3 => 0x57c3, 0xa4 => 0x6328, + 0xa5 => 0x54ce, 0xa6 => 0x5509, 0xa7 => 0x54c0, 0xa8 => 0x7691, + 0xa9 => 0x764c, 0xaa => 0x853c, 0xab => 0x77ee, 0xac => 0x827e, + 0xad => 0x788d, 0xae => 0x7231, 0xaf => 0x9698, 0xb0 => 0x978d, + 0xb1 => 0x6c28, 0xb2 => 0x5b89, 0xb3 => 0x4ffa, 0xb4 => 0x6309, + 0xb5 => 0x6697, 0xb6 => 0x5cb8, 0xb7 => 0x80fa, 0xb8 => 0x6848, + 0xb9 => 0x80ae, 0xba => 0x6602, 0xbb => 0x76ce, 0xbc => 0x51f9, + 0xbd => 0x6556, 0xbe => 0x71ac, 0xbf => 0x7ff1, 0xc0 => 0x8884, + 0xc1 => 0x50b2, 0xc2 => 0x5965, 0xc3 => 0x61ca, 0xc4 => 0x6fb3, + 0xc5 => 0x82ad, 0xc6 => 0x634c, 0xc7 => 0x6252, 0xc8 => 0x53ed, + 0xc9 => 0x5427, 0xca => 0x7b06, 0xcb => 0x516b, 0xcc => 0x75a4, + 0xcd => 0x5df4, 0xce => 0x62d4, 0xcf => 0x8dcb, 0xd0 => 0x9776, + 0xd1 => 0x628a, 0xd2 => 0x8019, 0xd3 => 0x575d, 0xd4 => 0x9738, + 0xd5 => 0x7f62, 0xd6 => 0x7238, 0xd7 => 0x767d, 0xd8 => 0x67cf, + 0xd9 => 0x767e, 0xda => 0x6446, 0xdb => 0x4f70, 0xdc => 0x8d25, + 0xdd => 0x62dc, 0xde => 0x7a17, 0xdf => 0x6591, 0xe0 => 0x73ed, + 0xe1 => 0x642c, 0xe2 => 0x6273, 0xe3 => 0x822c, 0xe4 => 0x9881, + 0xe5 => 0x677f, 0xe6 => 0x7248, 0xe7 => 0x626e, 0xe8 => 0x62cc, + 0xe9 => 0x4f34, 0xea => 0x74e3, 0xeb => 0x534a, 0xec => 0x529e, + 0xed => 0x7eca, 0xee => 0x90a6, 0xef => 0x5e2e, 0xf0 => 0x6886, + 0xf1 => 0x699c, 0xf2 => 0x8180, 0xf3 => 0x7ed1, 0xf4 => 0x68d2, + 0xf5 => 0x78c5, 0xf6 => 0x868c, 0xf7 => 0x9551, 0xf8 => 0x508d, + 0xf9 => 0x8c24, 0xfa => 0x82de, 0xfb => 0x80de, 0xfc => 0x5305, + 0xfd => 0x8912, 0xfe => 0x5265, + }, + 0xb1 => { + 0xa1 => 0x8584, 0xa2 => 0x96f9, 0xa3 => 0x4fdd, 0xa4 => 0x5821, + 0xa5 => 0x9971, 0xa6 => 0x5b9d, 0xa7 => 0x62b1, 0xa8 => 0x62a5, + 0xa9 => 0x66b4, 0xaa => 0x8c79, 0xab => 0x9c8d, 0xac => 0x7206, + 0xad => 0x676f, 0xae => 0x7891, 0xaf => 0x60b2, 0xb0 => 0x5351, + 0xb1 => 0x5317, 0xb2 => 0x8f88, 0xb3 => 0x80cc, 0xb4 => 0x8d1d, + 0xb5 => 0x94a1, 0xb6 => 0x500d, 0xb7 => 0x72c8, 0xb8 => 0x5907, + 0xb9 => 0x60eb, 0xba => 0x7119, 0xbb => 0x88ab, 0xbc => 0x5954, + 0xbd => 0x82ef, 0xbe => 0x672c, 0xbf => 0x7b28, 0xc0 => 0x5d29, + 0xc1 => 0x7ef7, 0xc2 => 0x752d, 0xc3 => 0x6cf5, 0xc4 => 0x8e66, + 0xc5 => 0x8ff8, 0xc6 => 0x903c, 0xc7 => 0x9f3b, 0xc8 => 0x6bd4, + 0xc9 => 0x9119, 0xca => 0x7b14, 0xcb => 0x5f7c, 0xcc => 0x78a7, + 0xcd => 0x84d6, 0xce => 0x853d, 0xcf => 0x6bd5, 0xd0 => 0x6bd9, + 0xd1 => 0x6bd6, 0xd2 => 0x5e01, 0xd3 => 0x5e87, 0xd4 => 0x75f9, + 0xd5 => 0x95ed, 0xd6 => 0x655d, 0xd7 => 0x5f0a, 0xd8 => 0x5fc5, + 0xd9 => 0x8f9f, 0xda => 0x58c1, 0xdb => 0x81c2, 0xdc => 0x907f, + 0xdd => 0x965b, 0xde => 0x97ad, 0xdf => 0x8fb9, 0xe0 => 0x7f16, + 0xe1 => 0x8d2c, 0xe2 => 0x6241, 0xe3 => 0x4fbf, 0xe4 => 0x53d8, + 0xe5 => 0x535e, 0xe6 => 0x8fa8, 0xe7 => 0x8fa9, 0xe8 => 0x8fab, + 0xe9 => 0x904d, 0xea => 0x6807, 0xeb => 0x5f6a, 0xec => 0x8198, + 0xed => 0x8868, 0xee => 0x9cd6, 0xef => 0x618b, 0xf0 => 0x522b, + 0xf1 => 0x762a, 0xf2 => 0x5f6c, 0xf3 => 0x658c, 0xf4 => 0x6fd2, + 0xf5 => 0x6ee8, 0xf6 => 0x5bbe, 0xf7 => 0x6448, 0xf8 => 0x5175, + 0xf9 => 0x51b0, 0xfa => 0x67c4, 0xfb => 0x4e19, 0xfc => 0x79c9, + 0xfd => 0x997c, 0xfe => 0x70b3, + }, + 0xb2 => { + 0xa1 => 0x75c5, 0xa2 => 0x5e76, 0xa3 => 0x73bb, 0xa4 => 0x83e0, + 0xa5 => 0x64ad, 0xa6 => 0x62e8, 0xa7 => 0x94b5, 0xa8 => 0x6ce2, + 0xa9 => 0x535a, 0xaa => 0x52c3, 0xab => 0x640f, 0xac => 0x94c2, + 0xad => 0x7b94, 0xae => 0x4f2f, 0xaf => 0x5e1b, 0xb0 => 0x8236, + 0xb1 => 0x8116, 0xb2 => 0x818a, 0xb3 => 0x6e24, 0xb4 => 0x6cca, + 0xb5 => 0x9a73, 0xb6 => 0x6355, 0xb7 => 0x535c, 0xb8 => 0x54fa, + 0xb9 => 0x8865, 0xba => 0x57e0, 0xbb => 0x4e0d, 0xbc => 0x5e03, + 0xbd => 0x6b65, 0xbe => 0x7c3f, 0xbf => 0x90e8, 0xc0 => 0x6016, + 0xc1 => 0x64e6, 0xc2 => 0x731c, 0xc3 => 0x88c1, 0xc4 => 0x6750, + 0xc5 => 0x624d, 0xc6 => 0x8d22, 0xc7 => 0x776c, 0xc8 => 0x8e29, + 0xc9 => 0x91c7, 0xca => 0x5f69, 0xcb => 0x83dc, 0xcc => 0x8521, + 0xcd => 0x9910, 0xce => 0x53c2, 0xcf => 0x8695, 0xd0 => 0x6b8b, + 0xd1 => 0x60ed, 0xd2 => 0x60e8, 0xd3 => 0x707f, 0xd4 => 0x82cd, + 0xd5 => 0x8231, 0xd6 => 0x4ed3, 0xd7 => 0x6ca7, 0xd8 => 0x85cf, + 0xd9 => 0x64cd, 0xda => 0x7cd9, 0xdb => 0x69fd, 0xdc => 0x66f9, + 0xdd => 0x8349, 0xde => 0x5395, 0xdf => 0x7b56, 0xe0 => 0x4fa7, + 0xe1 => 0x518c, 0xe2 => 0x6d4b, 0xe3 => 0x5c42, 0xe4 => 0x8e6d, + 0xe5 => 0x63d2, 0xe6 => 0x53c9, 0xe7 => 0x832c, 0xe8 => 0x8336, + 0xe9 => 0x67e5, 0xea => 0x78b4, 0xeb => 0x643d, 0xec => 0x5bdf, + 0xed => 0x5c94, 0xee => 0x5dee, 0xef => 0x8be7, 0xf0 => 0x62c6, + 0xf1 => 0x67f4, 0xf2 => 0x8c7a, 0xf3 => 0x6400, 0xf4 => 0x63ba, + 0xf5 => 0x8749, 0xf6 => 0x998b, 0xf7 => 0x8c17, 0xf8 => 0x7f20, + 0xf9 => 0x94f2, 0xfa => 0x4ea7, 0xfb => 0x9610, 0xfc => 0x98a4, + 0xfd => 0x660c, 0xfe => 0x7316, + }, + 0xb3 => { + 0xa1 => 0x573a, 0xa2 => 0x5c1d, 0xa3 => 0x5e38, 0xa4 => 0x957f, + 0xa5 => 0x507f, 0xa6 => 0x80a0, 0xa7 => 0x5382, 0xa8 => 0x655e, + 0xa9 => 0x7545, 0xaa => 0x5531, 0xab => 0x5021, 0xac => 0x8d85, + 0xad => 0x6284, 0xae => 0x949e, 0xaf => 0x671d, 0xb0 => 0x5632, + 0xb1 => 0x6f6e, 0xb2 => 0x5de2, 0xb3 => 0x5435, 0xb4 => 0x7092, + 0xb5 => 0x8f66, 0xb6 => 0x626f, 0xb7 => 0x64a4, 0xb8 => 0x63a3, + 0xb9 => 0x5f7b, 0xba => 0x6f88, 0xbb => 0x90f4, 0xbc => 0x81e3, + 0xbd => 0x8fb0, 0xbe => 0x5c18, 0xbf => 0x6668, 0xc0 => 0x5ff1, + 0xc1 => 0x6c89, 0xc2 => 0x9648, 0xc3 => 0x8d81, 0xc4 => 0x886c, + 0xc5 => 0x6491, 0xc6 => 0x79f0, 0xc7 => 0x57ce, 0xc8 => 0x6a59, + 0xc9 => 0x6210, 0xca => 0x5448, 0xcb => 0x4e58, 0xcc => 0x7a0b, + 0xcd => 0x60e9, 0xce => 0x6f84, 0xcf => 0x8bda, 0xd0 => 0x627f, + 0xd1 => 0x901e, 0xd2 => 0x9a8b, 0xd3 => 0x79e4, 0xd4 => 0x5403, + 0xd5 => 0x75f4, 0xd6 => 0x6301, 0xd7 => 0x5319, 0xd8 => 0x6c60, + 0xd9 => 0x8fdf, 0xda => 0x5f1b, 0xdb => 0x9a70, 0xdc => 0x803b, + 0xdd => 0x9f7f, 0xde => 0x4f88, 0xdf => 0x5c3a, 0xe0 => 0x8d64, + 0xe1 => 0x7fc5, 0xe2 => 0x65a5, 0xe3 => 0x70bd, 0xe4 => 0x5145, + 0xe5 => 0x51b2, 0xe6 => 0x866b, 0xe7 => 0x5d07, 0xe8 => 0x5ba0, + 0xe9 => 0x62bd, 0xea => 0x916c, 0xeb => 0x7574, 0xec => 0x8e0c, + 0xed => 0x7a20, 0xee => 0x6101, 0xef => 0x7b79, 0xf0 => 0x4ec7, + 0xf1 => 0x7ef8, 0xf2 => 0x7785, 0xf3 => 0x4e11, 0xf4 => 0x81ed, + 0xf5 => 0x521d, 0xf6 => 0x51fa, 0xf7 => 0x6a71, 0xf8 => 0x53a8, + 0xf9 => 0x8e87, 0xfa => 0x9504, 0xfb => 0x96cf, 0xfc => 0x6ec1, + 0xfd => 0x9664, 0xfe => 0x695a, + }, + 0xb4 => { + 0xa1 => 0x7840, 0xa2 => 0x50a8, 0xa3 => 0x77d7, 0xa4 => 0x6410, + 0xa5 => 0x89e6, 0xa6 => 0x5904, 0xa7 => 0x63e3, 0xa8 => 0x5ddd, + 0xa9 => 0x7a7f, 0xaa => 0x693d, 0xab => 0x4f20, 0xac => 0x8239, + 0xad => 0x5598, 0xae => 0x4e32, 0xaf => 0x75ae, 0xb0 => 0x7a97, + 0xb1 => 0x5e62, 0xb2 => 0x5e8a, 0xb3 => 0x95ef, 0xb4 => 0x521b, + 0xb5 => 0x5439, 0xb6 => 0x708a, 0xb7 => 0x6376, 0xb8 => 0x9524, + 0xb9 => 0x5782, 0xba => 0x6625, 0xbb => 0x693f, 0xbc => 0x9187, + 0xbd => 0x5507, 0xbe => 0x6df3, 0xbf => 0x7eaf, 0xc0 => 0x8822, + 0xc1 => 0x6233, 0xc2 => 0x7ef0, 0xc3 => 0x75b5, 0xc4 => 0x8328, + 0xc5 => 0x78c1, 0xc6 => 0x96cc, 0xc7 => 0x8f9e, 0xc8 => 0x6148, + 0xc9 => 0x74f7, 0xca => 0x8bcd, 0xcb => 0x6b64, 0xcc => 0x523a, + 0xcd => 0x8d50, 0xce => 0x6b21, 0xcf => 0x806a, 0xd0 => 0x8471, + 0xd1 => 0x56f1, 0xd2 => 0x5306, 0xd3 => 0x4ece, 0xd4 => 0x4e1b, + 0xd5 => 0x51d1, 0xd6 => 0x7c97, 0xd7 => 0x918b, 0xd8 => 0x7c07, + 0xd9 => 0x4fc3, 0xda => 0x8e7f, 0xdb => 0x7be1, 0xdc => 0x7a9c, + 0xdd => 0x6467, 0xde => 0x5d14, 0xdf => 0x50ac, 0xe0 => 0x8106, + 0xe1 => 0x7601, 0xe2 => 0x7cb9, 0xe3 => 0x6dec, 0xe4 => 0x7fe0, + 0xe5 => 0x6751, 0xe6 => 0x5b58, 0xe7 => 0x5bf8, 0xe8 => 0x78cb, + 0xe9 => 0x64ae, 0xea => 0x6413, 0xeb => 0x63aa, 0xec => 0x632b, + 0xed => 0x9519, 0xee => 0x642d, 0xef => 0x8fbe, 0xf0 => 0x7b54, + 0xf1 => 0x7629, 0xf2 => 0x6253, 0xf3 => 0x5927, 0xf4 => 0x5446, + 0xf5 => 0x6b79, 0xf6 => 0x50a3, 0xf7 => 0x6234, 0xf8 => 0x5e26, + 0xf9 => 0x6b86, 0xfa => 0x4ee3, 0xfb => 0x8d37, 0xfc => 0x888b, + 0xfd => 0x5f85, 0xfe => 0x902e, + }, + 0xb5 => { + 0xa1 => 0x6020, 0xa2 => 0x803d, 0xa3 => 0x62c5, 0xa4 => 0x4e39, + 0xa5 => 0x5355, 0xa6 => 0x90f8, 0xa7 => 0x63b8, 0xa8 => 0x80c6, + 0xa9 => 0x65e6, 0xaa => 0x6c2e, 0xab => 0x4f46, 0xac => 0x60ee, + 0xad => 0x6de1, 0xae => 0x8bde, 0xaf => 0x5f39, 0xb0 => 0x86cb, + 0xb1 => 0x5f53, 0xb2 => 0x6321, 0xb3 => 0x515a, 0xb4 => 0x8361, + 0xb5 => 0x6863, 0xb6 => 0x5200, 0xb7 => 0x6363, 0xb8 => 0x8e48, + 0xb9 => 0x5012, 0xba => 0x5c9b, 0xbb => 0x7977, 0xbc => 0x5bfc, + 0xbd => 0x5230, 0xbe => 0x7a3b, 0xbf => 0x60bc, 0xc0 => 0x9053, + 0xc1 => 0x76d7, 0xc2 => 0x5fb7, 0xc3 => 0x5f97, 0xc4 => 0x7684, + 0xc5 => 0x8e6c, 0xc6 => 0x706f, 0xc7 => 0x767b, 0xc8 => 0x7b49, + 0xc9 => 0x77aa, 0xca => 0x51f3, 0xcb => 0x9093, 0xcc => 0x5824, + 0xcd => 0x4f4e, 0xce => 0x6ef4, 0xcf => 0x8fea, 0xd0 => 0x654c, + 0xd1 => 0x7b1b, 0xd2 => 0x72c4, 0xd3 => 0x6da4, 0xd4 => 0x7fdf, + 0xd5 => 0x5ae1, 0xd6 => 0x62b5, 0xd7 => 0x5e95, 0xd8 => 0x5730, + 0xd9 => 0x8482, 0xda => 0x7b2c, 0xdb => 0x5e1d, 0xdc => 0x5f1f, + 0xdd => 0x9012, 0xde => 0x7f14, 0xdf => 0x98a0, 0xe0 => 0x6382, + 0xe1 => 0x6ec7, 0xe2 => 0x7898, 0xe3 => 0x70b9, 0xe4 => 0x5178, + 0xe5 => 0x975b, 0xe6 => 0x57ab, 0xe7 => 0x7535, 0xe8 => 0x4f43, + 0xe9 => 0x7538, 0xea => 0x5e97, 0xeb => 0x60e6, 0xec => 0x5960, + 0xed => 0x6dc0, 0xee => 0x6bbf, 0xef => 0x7889, 0xf0 => 0x53fc, + 0xf1 => 0x96d5, 0xf2 => 0x51cb, 0xf3 => 0x5201, 0xf4 => 0x6389, + 0xf5 => 0x540a, 0xf6 => 0x9493, 0xf7 => 0x8c03, 0xf8 => 0x8dcc, + 0xf9 => 0x7239, 0xfa => 0x789f, 0xfb => 0x8776, 0xfc => 0x8fed, + 0xfd => 0x8c0d, 0xfe => 0x53e0, + }, + 0xb6 => { + 0xa1 => 0x4e01, 0xa2 => 0x76ef, 0xa3 => 0x53ee, 0xa4 => 0x9489, + 0xa5 => 0x9876, 0xa6 => 0x9f0e, 0xa7 => 0x952d, 0xa8 => 0x5b9a, + 0xa9 => 0x8ba2, 0xaa => 0x4e22, 0xab => 0x4e1c, 0xac => 0x51ac, + 0xad => 0x8463, 0xae => 0x61c2, 0xaf => 0x52a8, 0xb0 => 0x680b, + 0xb1 => 0x4f97, 0xb2 => 0x606b, 0xb3 => 0x51bb, 0xb4 => 0x6d1e, + 0xb5 => 0x515c, 0xb6 => 0x6296, 0xb7 => 0x6597, 0xb8 => 0x9661, + 0xb9 => 0x8c46, 0xba => 0x9017, 0xbb => 0x75d8, 0xbc => 0x90fd, + 0xbd => 0x7763, 0xbe => 0x6bd2, 0xbf => 0x728a, 0xc0 => 0x72ec, + 0xc1 => 0x8bfb, 0xc2 => 0x5835, 0xc3 => 0x7779, 0xc4 => 0x8d4c, + 0xc5 => 0x675c, 0xc6 => 0x9540, 0xc7 => 0x809a, 0xc8 => 0x5ea6, + 0xc9 => 0x6e21, 0xca => 0x5992, 0xcb => 0x7aef, 0xcc => 0x77ed, + 0xcd => 0x953b, 0xce => 0x6bb5, 0xcf => 0x65ad, 0xd0 => 0x7f0e, + 0xd1 => 0x5806, 0xd2 => 0x5151, 0xd3 => 0x961f, 0xd4 => 0x5bf9, + 0xd5 => 0x58a9, 0xd6 => 0x5428, 0xd7 => 0x8e72, 0xd8 => 0x6566, + 0xd9 => 0x987f, 0xda => 0x56e4, 0xdb => 0x949d, 0xdc => 0x76fe, + 0xdd => 0x9041, 0xde => 0x6387, 0xdf => 0x54c6, 0xe0 => 0x591a, + 0xe1 => 0x593a, 0xe2 => 0x579b, 0xe3 => 0x8eb2, 0xe4 => 0x6735, + 0xe5 => 0x8dfa, 0xe6 => 0x8235, 0xe7 => 0x5241, 0xe8 => 0x60f0, + 0xe9 => 0x5815, 0xea => 0x86fe, 0xeb => 0x5ce8, 0xec => 0x9e45, + 0xed => 0x4fc4, 0xee => 0x989d, 0xef => 0x8bb9, 0xf0 => 0x5a25, + 0xf1 => 0x6076, 0xf2 => 0x5384, 0xf3 => 0x627c, 0xf4 => 0x904f, + 0xf5 => 0x9102, 0xf6 => 0x997f, 0xf7 => 0x6069, 0xf8 => 0x800c, + 0xf9 => 0x513f, 0xfa => 0x8033, 0xfb => 0x5c14, 0xfc => 0x9975, + 0xfd => 0x6d31, 0xfe => 0x4e8c, + }, + 0xb7 => { + 0xa1 => 0x8d30, 0xa2 => 0x53d1, 0xa3 => 0x7f5a, 0xa4 => 0x7b4f, + 0xa5 => 0x4f10, 0xa6 => 0x4e4f, 0xa7 => 0x9600, 0xa8 => 0x6cd5, + 0xa9 => 0x73d0, 0xaa => 0x85e9, 0xab => 0x5e06, 0xac => 0x756a, + 0xad => 0x7ffb, 0xae => 0x6a0a, 0xaf => 0x77fe, 0xb0 => 0x9492, + 0xb1 => 0x7e41, 0xb2 => 0x51e1, 0xb3 => 0x70e6, 0xb4 => 0x53cd, + 0xb5 => 0x8fd4, 0xb6 => 0x8303, 0xb7 => 0x8d29, 0xb8 => 0x72af, + 0xb9 => 0x996d, 0xba => 0x6cdb, 0xbb => 0x574a, 0xbc => 0x82b3, + 0xbd => 0x65b9, 0xbe => 0x80aa, 0xbf => 0x623f, 0xc0 => 0x9632, + 0xc1 => 0x59a8, 0xc2 => 0x4eff, 0xc3 => 0x8bbf, 0xc4 => 0x7eba, + 0xc5 => 0x653e, 0xc6 => 0x83f2, 0xc7 => 0x975e, 0xc8 => 0x5561, + 0xc9 => 0x98de, 0xca => 0x80a5, 0xcb => 0x532a, 0xcc => 0x8bfd, + 0xcd => 0x5420, 0xce => 0x80ba, 0xcf => 0x5e9f, 0xd0 => 0x6cb8, + 0xd1 => 0x8d39, 0xd2 => 0x82ac, 0xd3 => 0x915a, 0xd4 => 0x5429, + 0xd5 => 0x6c1b, 0xd6 => 0x5206, 0xd7 => 0x7eb7, 0xd8 => 0x575f, + 0xd9 => 0x711a, 0xda => 0x6c7e, 0xdb => 0x7c89, 0xdc => 0x594b, + 0xdd => 0x4efd, 0xde => 0x5fff, 0xdf => 0x6124, 0xe0 => 0x7caa, + 0xe1 => 0x4e30, 0xe2 => 0x5c01, 0xe3 => 0x67ab, 0xe4 => 0x8702, + 0xe5 => 0x5cf0, 0xe6 => 0x950b, 0xe7 => 0x98ce, 0xe8 => 0x75af, + 0xe9 => 0x70fd, 0xea => 0x9022, 0xeb => 0x51af, 0xec => 0x7f1d, + 0xed => 0x8bbd, 0xee => 0x5949, 0xef => 0x51e4, 0xf0 => 0x4f5b, + 0xf1 => 0x5426, 0xf2 => 0x592b, 0xf3 => 0x6577, 0xf4 => 0x80a4, + 0xf5 => 0x5b75, 0xf6 => 0x6276, 0xf7 => 0x62c2, 0xf8 => 0x8f90, + 0xf9 => 0x5e45, 0xfa => 0x6c1f, 0xfb => 0x7b26, 0xfc => 0x4f0f, + 0xfd => 0x4fd8, 0xfe => 0x670d, + }, + 0xb8 => { + 0xa1 => 0x6d6e, 0xa2 => 0x6daa, 0xa3 => 0x798f, 0xa4 => 0x88b1, + 0xa5 => 0x5f17, 0xa6 => 0x752b, 0xa7 => 0x629a, 0xa8 => 0x8f85, + 0xa9 => 0x4fef, 0xaa => 0x91dc, 0xab => 0x65a7, 0xac => 0x812f, + 0xad => 0x8151, 0xae => 0x5e9c, 0xaf => 0x8150, 0xb0 => 0x8d74, + 0xb1 => 0x526f, 0xb2 => 0x8986, 0xb3 => 0x8d4b, 0xb4 => 0x590d, + 0xb5 => 0x5085, 0xb6 => 0x4ed8, 0xb7 => 0x961c, 0xb8 => 0x7236, + 0xb9 => 0x8179, 0xba => 0x8d1f, 0xbb => 0x5bcc, 0xbc => 0x8ba3, + 0xbd => 0x9644, 0xbe => 0x5987, 0xbf => 0x7f1a, 0xc0 => 0x5490, + 0xc1 => 0x5676, 0xc2 => 0x560e, 0xc3 => 0x8be5, 0xc4 => 0x6539, + 0xc5 => 0x6982, 0xc6 => 0x9499, 0xc7 => 0x76d6, 0xc8 => 0x6e89, + 0xc9 => 0x5e72, 0xca => 0x7518, 0xcb => 0x6746, 0xcc => 0x67d1, + 0xcd => 0x7aff, 0xce => 0x809d, 0xcf => 0x8d76, 0xd0 => 0x611f, + 0xd1 => 0x79c6, 0xd2 => 0x6562, 0xd3 => 0x8d63, 0xd4 => 0x5188, + 0xd5 => 0x521a, 0xd6 => 0x94a2, 0xd7 => 0x7f38, 0xd8 => 0x809b, + 0xd9 => 0x7eb2, 0xda => 0x5c97, 0xdb => 0x6e2f, 0xdc => 0x6760, + 0xdd => 0x7bd9, 0xde => 0x768b, 0xdf => 0x9ad8, 0xe0 => 0x818f, + 0xe1 => 0x7f94, 0xe2 => 0x7cd5, 0xe3 => 0x641e, 0xe4 => 0x9550, + 0xe5 => 0x7a3f, 0xe6 => 0x544a, 0xe7 => 0x54e5, 0xe8 => 0x6b4c, + 0xe9 => 0x6401, 0xea => 0x6208, 0xeb => 0x9e3d, 0xec => 0x80f3, + 0xed => 0x7599, 0xee => 0x5272, 0xef => 0x9769, 0xf0 => 0x845b, + 0xf1 => 0x683c, 0xf2 => 0x86e4, 0xf3 => 0x9601, 0xf4 => 0x9694, + 0xf5 => 0x94ec, 0xf6 => 0x4e2a, 0xf7 => 0x5404, 0xf8 => 0x7ed9, + 0xf9 => 0x6839, 0xfa => 0x8ddf, 0xfb => 0x8015, 0xfc => 0x66f4, + 0xfd => 0x5e9a, 0xfe => 0x7fb9, + }, + 0xb9 => { + 0xa1 => 0x57c2, 0xa2 => 0x803f, 0xa3 => 0x6897, 0xa4 => 0x5de5, + 0xa5 => 0x653b, 0xa6 => 0x529f, 0xa7 => 0x606d, 0xa8 => 0x9f9a, + 0xa9 => 0x4f9b, 0xaa => 0x8eac, 0xab => 0x516c, 0xac => 0x5bab, + 0xad => 0x5f13, 0xae => 0x5de9, 0xaf => 0x6c5e, 0xb0 => 0x62f1, + 0xb1 => 0x8d21, 0xb2 => 0x5171, 0xb3 => 0x94a9, 0xb4 => 0x52fe, + 0xb5 => 0x6c9f, 0xb6 => 0x82df, 0xb7 => 0x72d7, 0xb8 => 0x57a2, + 0xb9 => 0x6784, 0xba => 0x8d2d, 0xbb => 0x591f, 0xbc => 0x8f9c, + 0xbd => 0x83c7, 0xbe => 0x5495, 0xbf => 0x7b8d, 0xc0 => 0x4f30, + 0xc1 => 0x6cbd, 0xc2 => 0x5b64, 0xc3 => 0x59d1, 0xc4 => 0x9f13, + 0xc5 => 0x53e4, 0xc6 => 0x86ca, 0xc7 => 0x9aa8, 0xc8 => 0x8c37, + 0xc9 => 0x80a1, 0xca => 0x6545, 0xcb => 0x987e, 0xcc => 0x56fa, + 0xcd => 0x96c7, 0xce => 0x522e, 0xcf => 0x74dc, 0xd0 => 0x5250, + 0xd1 => 0x5be1, 0xd2 => 0x6302, 0xd3 => 0x8902, 0xd4 => 0x4e56, + 0xd5 => 0x62d0, 0xd6 => 0x602a, 0xd7 => 0x68fa, 0xd8 => 0x5173, + 0xd9 => 0x5b98, 0xda => 0x51a0, 0xdb => 0x89c2, 0xdc => 0x7ba1, + 0xdd => 0x9986, 0xde => 0x7f50, 0xdf => 0x60ef, 0xe0 => 0x704c, + 0xe1 => 0x8d2f, 0xe2 => 0x5149, 0xe3 => 0x5e7f, 0xe4 => 0x901b, + 0xe5 => 0x7470, 0xe6 => 0x89c4, 0xe7 => 0x572d, 0xe8 => 0x7845, + 0xe9 => 0x5f52, 0xea => 0x9f9f, 0xeb => 0x95fa, 0xec => 0x8f68, + 0xed => 0x9b3c, 0xee => 0x8be1, 0xef => 0x7678, 0xf0 => 0x6842, + 0xf1 => 0x67dc, 0xf2 => 0x8dea, 0xf3 => 0x8d35, 0xf4 => 0x523d, + 0xf5 => 0x8f8a, 0xf6 => 0x6eda, 0xf7 => 0x68cd, 0xf8 => 0x9505, + 0xf9 => 0x90ed, 0xfa => 0x56fd, 0xfb => 0x679c, 0xfc => 0x88f9, + 0xfd => 0x8fc7, 0xfe => 0x54c8, + }, + 0xba => { + 0xa1 => 0x9ab8, 0xa2 => 0x5b69, 0xa3 => 0x6d77, 0xa4 => 0x6c26, + 0xa5 => 0x4ea5, 0xa6 => 0x5bb3, 0xa7 => 0x9a87, 0xa8 => 0x9163, + 0xa9 => 0x61a8, 0xaa => 0x90af, 0xab => 0x97e9, 0xac => 0x542b, + 0xad => 0x6db5, 0xae => 0x5bd2, 0xaf => 0x51fd, 0xb0 => 0x558a, + 0xb1 => 0x7f55, 0xb2 => 0x7ff0, 0xb3 => 0x64bc, 0xb4 => 0x634d, + 0xb5 => 0x65f1, 0xb6 => 0x61be, 0xb7 => 0x608d, 0xb8 => 0x710a, + 0xb9 => 0x6c57, 0xba => 0x6c49, 0xbb => 0x592f, 0xbc => 0x676d, + 0xbd => 0x822a, 0xbe => 0x58d5, 0xbf => 0x568e, 0xc0 => 0x8c6a, + 0xc1 => 0x6beb, 0xc2 => 0x90dd, 0xc3 => 0x597d, 0xc4 => 0x8017, + 0xc5 => 0x53f7, 0xc6 => 0x6d69, 0xc7 => 0x5475, 0xc8 => 0x559d, + 0xc9 => 0x8377, 0xca => 0x83cf, 0xcb => 0x6838, 0xcc => 0x79be, + 0xcd => 0x548c, 0xce => 0x4f55, 0xcf => 0x5408, 0xd0 => 0x76d2, + 0xd1 => 0x8c89, 0xd2 => 0x9602, 0xd3 => 0x6cb3, 0xd4 => 0x6db8, + 0xd5 => 0x8d6b, 0xd6 => 0x8910, 0xd7 => 0x9e64, 0xd8 => 0x8d3a, + 0xd9 => 0x563f, 0xda => 0x9ed1, 0xdb => 0x75d5, 0xdc => 0x5f88, + 0xdd => 0x72e0, 0xde => 0x6068, 0xdf => 0x54fc, 0xe0 => 0x4ea8, + 0xe1 => 0x6a2a, 0xe2 => 0x8861, 0xe3 => 0x6052, 0xe4 => 0x8f70, + 0xe5 => 0x54c4, 0xe6 => 0x70d8, 0xe7 => 0x8679, 0xe8 => 0x9e3f, + 0xe9 => 0x6d2a, 0xea => 0x5b8f, 0xeb => 0x5f18, 0xec => 0x7ea2, + 0xed => 0x5589, 0xee => 0x4faf, 0xef => 0x7334, 0xf0 => 0x543c, + 0xf1 => 0x539a, 0xf2 => 0x5019, 0xf3 => 0x540e, 0xf4 => 0x547c, + 0xf5 => 0x4e4e, 0xf6 => 0x5ffd, 0xf7 => 0x745a, 0xf8 => 0x58f6, + 0xf9 => 0x846b, 0xfa => 0x80e1, 0xfb => 0x8774, 0xfc => 0x72d0, + 0xfd => 0x7cca, 0xfe => 0x6e56, + }, + 0xbb => { + 0xa1 => 0x5f27, 0xa2 => 0x864e, 0xa3 => 0x552c, 0xa4 => 0x62a4, + 0xa5 => 0x4e92, 0xa6 => 0x6caa, 0xa7 => 0x6237, 0xa8 => 0x82b1, + 0xa9 => 0x54d7, 0xaa => 0x534e, 0xab => 0x733e, 0xac => 0x6ed1, + 0xad => 0x753b, 0xae => 0x5212, 0xaf => 0x5316, 0xb0 => 0x8bdd, + 0xb1 => 0x69d0, 0xb2 => 0x5f8a, 0xb3 => 0x6000, 0xb4 => 0x6dee, + 0xb5 => 0x574f, 0xb6 => 0x6b22, 0xb7 => 0x73af, 0xb8 => 0x6853, + 0xb9 => 0x8fd8, 0xba => 0x7f13, 0xbb => 0x6362, 0xbc => 0x60a3, + 0xbd => 0x5524, 0xbe => 0x75ea, 0xbf => 0x8c62, 0xc0 => 0x7115, + 0xc1 => 0x6da3, 0xc2 => 0x5ba6, 0xc3 => 0x5e7b, 0xc4 => 0x8352, + 0xc5 => 0x614c, 0xc6 => 0x9ec4, 0xc7 => 0x78fa, 0xc8 => 0x8757, + 0xc9 => 0x7c27, 0xca => 0x7687, 0xcb => 0x51f0, 0xcc => 0x60f6, + 0xcd => 0x714c, 0xce => 0x6643, 0xcf => 0x5e4c, 0xd0 => 0x604d, + 0xd1 => 0x8c0e, 0xd2 => 0x7070, 0xd3 => 0x6325, 0xd4 => 0x8f89, + 0xd5 => 0x5fbd, 0xd6 => 0x6062, 0xd7 => 0x86d4, 0xd8 => 0x56de, + 0xd9 => 0x6bc1, 0xda => 0x6094, 0xdb => 0x6167, 0xdc => 0x5349, + 0xdd => 0x60e0, 0xde => 0x6666, 0xdf => 0x8d3f, 0xe0 => 0x79fd, + 0xe1 => 0x4f1a, 0xe2 => 0x70e9, 0xe3 => 0x6c47, 0xe4 => 0x8bb3, + 0xe5 => 0x8bf2, 0xe6 => 0x7ed8, 0xe7 => 0x8364, 0xe8 => 0x660f, + 0xe9 => 0x5a5a, 0xea => 0x9b42, 0xeb => 0x6d51, 0xec => 0x6df7, + 0xed => 0x8c41, 0xee => 0x6d3b, 0xef => 0x4f19, 0xf0 => 0x706b, + 0xf1 => 0x83b7, 0xf2 => 0x6216, 0xf3 => 0x60d1, 0xf4 => 0x970d, + 0xf5 => 0x8d27, 0xf6 => 0x7978, 0xf7 => 0x51fb, 0xf8 => 0x573e, + 0xf9 => 0x57fa, 0xfa => 0x673a, 0xfb => 0x7578, 0xfc => 0x7a3d, + 0xfd => 0x79ef, 0xfe => 0x7b95, + }, + 0xbc => { + 0xa1 => 0x808c, 0xa2 => 0x9965, 0xa3 => 0x8ff9, 0xa4 => 0x6fc0, + 0xa5 => 0x8ba5, 0xa6 => 0x9e21, 0xa7 => 0x59ec, 0xa8 => 0x7ee9, + 0xa9 => 0x7f09, 0xaa => 0x5409, 0xab => 0x6781, 0xac => 0x68d8, + 0xad => 0x8f91, 0xae => 0x7c4d, 0xaf => 0x96c6, 0xb0 => 0x53ca, + 0xb1 => 0x6025, 0xb2 => 0x75be, 0xb3 => 0x6c72, 0xb4 => 0x5373, + 0xb5 => 0x5ac9, 0xb6 => 0x7ea7, 0xb7 => 0x6324, 0xb8 => 0x51e0, + 0xb9 => 0x810a, 0xba => 0x5df1, 0xbb => 0x84df, 0xbc => 0x6280, + 0xbd => 0x5180, 0xbe => 0x5b63, 0xbf => 0x4f0e, 0xc0 => 0x796d, + 0xc1 => 0x5242, 0xc2 => 0x60b8, 0xc3 => 0x6d4e, 0xc4 => 0x5bc4, + 0xc5 => 0x5bc2, 0xc6 => 0x8ba1, 0xc7 => 0x8bb0, 0xc8 => 0x65e2, + 0xc9 => 0x5fcc, 0xca => 0x9645, 0xcb => 0x5993, 0xcc => 0x7ee7, + 0xcd => 0x7eaa, 0xce => 0x5609, 0xcf => 0x67b7, 0xd0 => 0x5939, + 0xd1 => 0x4f73, 0xd2 => 0x5bb6, 0xd3 => 0x52a0, 0xd4 => 0x835a, + 0xd5 => 0x988a, 0xd6 => 0x8d3e, 0xd7 => 0x7532, 0xd8 => 0x94be, + 0xd9 => 0x5047, 0xda => 0x7a3c, 0xdb => 0x4ef7, 0xdc => 0x67b6, + 0xdd => 0x9a7e, 0xde => 0x5ac1, 0xdf => 0x6b7c, 0xe0 => 0x76d1, + 0xe1 => 0x575a, 0xe2 => 0x5c16, 0xe3 => 0x7b3a, 0xe4 => 0x95f4, + 0xe5 => 0x714e, 0xe6 => 0x517c, 0xe7 => 0x80a9, 0xe8 => 0x8270, + 0xe9 => 0x5978, 0xea => 0x7f04, 0xeb => 0x8327, 0xec => 0x68c0, + 0xed => 0x67ec, 0xee => 0x78b1, 0xef => 0x7877, 0xf0 => 0x62e3, + 0xf1 => 0x6361, 0xf2 => 0x7b80, 0xf3 => 0x4fed, 0xf4 => 0x526a, + 0xf5 => 0x51cf, 0xf6 => 0x8350, 0xf7 => 0x69db, 0xf8 => 0x9274, + 0xf9 => 0x8df5, 0xfa => 0x8d31, 0xfb => 0x89c1, 0xfc => 0x952e, + 0xfd => 0x7bad, 0xfe => 0x4ef6, + }, + 0xbd => { + 0xa1 => 0x5065, 0xa2 => 0x8230, 0xa3 => 0x5251, 0xa4 => 0x996f, + 0xa5 => 0x6e10, 0xa6 => 0x6e85, 0xa7 => 0x6da7, 0xa8 => 0x5efa, + 0xa9 => 0x50f5, 0xaa => 0x59dc, 0xab => 0x5c06, 0xac => 0x6d46, + 0xad => 0x6c5f, 0xae => 0x7586, 0xaf => 0x848b, 0xb0 => 0x6868, + 0xb1 => 0x5956, 0xb2 => 0x8bb2, 0xb3 => 0x5320, 0xb4 => 0x9171, + 0xb5 => 0x964d, 0xb6 => 0x8549, 0xb7 => 0x6912, 0xb8 => 0x7901, + 0xb9 => 0x7126, 0xba => 0x80f6, 0xbb => 0x4ea4, 0xbc => 0x90ca, + 0xbd => 0x6d47, 0xbe => 0x9a84, 0xbf => 0x5a07, 0xc0 => 0x56bc, + 0xc1 => 0x6405, 0xc2 => 0x94f0, 0xc3 => 0x77eb, 0xc4 => 0x4fa5, + 0xc5 => 0x811a, 0xc6 => 0x72e1, 0xc7 => 0x89d2, 0xc8 => 0x997a, + 0xc9 => 0x7f34, 0xca => 0x7ede, 0xcb => 0x527f, 0xcc => 0x6559, + 0xcd => 0x9175, 0xce => 0x8f7f, 0xcf => 0x8f83, 0xd0 => 0x53eb, + 0xd1 => 0x7a96, 0xd2 => 0x63ed, 0xd3 => 0x63a5, 0xd4 => 0x7686, + 0xd5 => 0x79f8, 0xd6 => 0x8857, 0xd7 => 0x9636, 0xd8 => 0x622a, + 0xd9 => 0x52ab, 0xda => 0x8282, 0xdb => 0x6854, 0xdc => 0x6770, + 0xdd => 0x6377, 0xde => 0x776b, 0xdf => 0x7aed, 0xe0 => 0x6d01, + 0xe1 => 0x7ed3, 0xe2 => 0x89e3, 0xe3 => 0x59d0, 0xe4 => 0x6212, + 0xe5 => 0x85c9, 0xe6 => 0x82a5, 0xe7 => 0x754c, 0xe8 => 0x501f, + 0xe9 => 0x4ecb, 0xea => 0x75a5, 0xeb => 0x8beb, 0xec => 0x5c4a, + 0xed => 0x5dfe, 0xee => 0x7b4b, 0xef => 0x65a4, 0xf0 => 0x91d1, + 0xf1 => 0x4eca, 0xf2 => 0x6d25, 0xf3 => 0x895f, 0xf4 => 0x7d27, + 0xf5 => 0x9526, 0xf6 => 0x4ec5, 0xf7 => 0x8c28, 0xf8 => 0x8fdb, + 0xf9 => 0x9773, 0xfa => 0x664b, 0xfb => 0x7981, 0xfc => 0x8fd1, + 0xfd => 0x70ec, 0xfe => 0x6d78, + }, + 0xbe => { + 0xa1 => 0x5c3d, 0xa2 => 0x52b2, 0xa3 => 0x8346, 0xa4 => 0x5162, + 0xa5 => 0x830e, 0xa6 => 0x775b, 0xa7 => 0x6676, 0xa8 => 0x9cb8, + 0xa9 => 0x4eac, 0xaa => 0x60ca, 0xab => 0x7cbe, 0xac => 0x7cb3, + 0xad => 0x7ecf, 0xae => 0x4e95, 0xaf => 0x8b66, 0xb0 => 0x666f, + 0xb1 => 0x9888, 0xb2 => 0x9759, 0xb3 => 0x5883, 0xb4 => 0x656c, + 0xb5 => 0x955c, 0xb6 => 0x5f84, 0xb7 => 0x75c9, 0xb8 => 0x9756, + 0xb9 => 0x7adf, 0xba => 0x7ade, 0xbb => 0x51c0, 0xbc => 0x70af, + 0xbd => 0x7a98, 0xbe => 0x63ea, 0xbf => 0x7a76, 0xc0 => 0x7ea0, + 0xc1 => 0x7396, 0xc2 => 0x97ed, 0xc3 => 0x4e45, 0xc4 => 0x7078, + 0xc5 => 0x4e5d, 0xc6 => 0x9152, 0xc7 => 0x53a9, 0xc8 => 0x6551, + 0xc9 => 0x65e7, 0xca => 0x81fc, 0xcb => 0x8205, 0xcc => 0x548e, + 0xcd => 0x5c31, 0xce => 0x759a, 0xcf => 0x97a0, 0xd0 => 0x62d8, + 0xd1 => 0x72d9, 0xd2 => 0x75bd, 0xd3 => 0x5c45, 0xd4 => 0x9a79, + 0xd5 => 0x83ca, 0xd6 => 0x5c40, 0xd7 => 0x5480, 0xd8 => 0x77e9, + 0xd9 => 0x4e3e, 0xda => 0x6cae, 0xdb => 0x805a, 0xdc => 0x62d2, + 0xdd => 0x636e, 0xde => 0x5de8, 0xdf => 0x5177, 0xe0 => 0x8ddd, + 0xe1 => 0x8e1e, 0xe2 => 0x952f, 0xe3 => 0x4ff1, 0xe4 => 0x53e5, + 0xe5 => 0x60e7, 0xe6 => 0x70ac, 0xe7 => 0x5267, 0xe8 => 0x6350, + 0xe9 => 0x9e43, 0xea => 0x5a1f, 0xeb => 0x5026, 0xec => 0x7737, + 0xed => 0x5377, 0xee => 0x7ee2, 0xef => 0x6485, 0xf0 => 0x652b, + 0xf1 => 0x6289, 0xf2 => 0x6398, 0xf3 => 0x5014, 0xf4 => 0x7235, + 0xf5 => 0x89c9, 0xf6 => 0x51b3, 0xf7 => 0x8bc0, 0xf8 => 0x7edd, + 0xf9 => 0x5747, 0xfa => 0x83cc, 0xfb => 0x94a7, 0xfc => 0x519b, + 0xfd => 0x541b, 0xfe => 0x5cfb, + }, + 0xbf => { + 0xa1 => 0x4fca, 0xa2 => 0x7ae3, 0xa3 => 0x6d5a, 0xa4 => 0x90e1, + 0xa5 => 0x9a8f, 0xa6 => 0x5580, 0xa7 => 0x5496, 0xa8 => 0x5361, + 0xa9 => 0x54af, 0xaa => 0x5f00, 0xab => 0x63e9, 0xac => 0x6977, + 0xad => 0x51ef, 0xae => 0x6168, 0xaf => 0x520a, 0xb0 => 0x582a, + 0xb1 => 0x52d8, 0xb2 => 0x574e, 0xb3 => 0x780d, 0xb4 => 0x770b, + 0xb5 => 0x5eb7, 0xb6 => 0x6177, 0xb7 => 0x7ce0, 0xb8 => 0x625b, + 0xb9 => 0x6297, 0xba => 0x4ea2, 0xbb => 0x7095, 0xbc => 0x8003, + 0xbd => 0x62f7, 0xbe => 0x70e4, 0xbf => 0x9760, 0xc0 => 0x5777, + 0xc1 => 0x82db, 0xc2 => 0x67ef, 0xc3 => 0x68f5, 0xc4 => 0x78d5, + 0xc5 => 0x9897, 0xc6 => 0x79d1, 0xc7 => 0x58f3, 0xc8 => 0x54b3, + 0xc9 => 0x53ef, 0xca => 0x6e34, 0xcb => 0x514b, 0xcc => 0x523b, + 0xcd => 0x5ba2, 0xce => 0x8bfe, 0xcf => 0x80af, 0xd0 => 0x5543, + 0xd1 => 0x57a6, 0xd2 => 0x6073, 0xd3 => 0x5751, 0xd4 => 0x542d, + 0xd5 => 0x7a7a, 0xd6 => 0x6050, 0xd7 => 0x5b54, 0xd8 => 0x63a7, + 0xd9 => 0x62a0, 0xda => 0x53e3, 0xdb => 0x6263, 0xdc => 0x5bc7, + 0xdd => 0x67af, 0xde => 0x54ed, 0xdf => 0x7a9f, 0xe0 => 0x82e6, + 0xe1 => 0x9177, 0xe2 => 0x5e93, 0xe3 => 0x88e4, 0xe4 => 0x5938, + 0xe5 => 0x57ae, 0xe6 => 0x630e, 0xe7 => 0x8de8, 0xe8 => 0x80ef, + 0xe9 => 0x5757, 0xea => 0x7b77, 0xeb => 0x4fa9, 0xec => 0x5feb, + 0xed => 0x5bbd, 0xee => 0x6b3e, 0xef => 0x5321, 0xf0 => 0x7b50, + 0xf1 => 0x72c2, 0xf2 => 0x6846, 0xf3 => 0x77ff, 0xf4 => 0x7736, + 0xf5 => 0x65f7, 0xf6 => 0x51b5, 0xf7 => 0x4e8f, 0xf8 => 0x76d4, + 0xf9 => 0x5cbf, 0xfa => 0x7aa5, 0xfb => 0x8475, 0xfc => 0x594e, + 0xfd => 0x9b41, 0xfe => 0x5080, + }, + 0xc0 => { + 0xa1 => 0x9988, 0xa2 => 0x6127, 0xa3 => 0x6e83, 0xa4 => 0x5764, + 0xa5 => 0x6606, 0xa6 => 0x6346, 0xa7 => 0x56f0, 0xa8 => 0x62ec, + 0xa9 => 0x6269, 0xaa => 0x5ed3, 0xab => 0x9614, 0xac => 0x5783, + 0xad => 0x62c9, 0xae => 0x5587, 0xaf => 0x8721, 0xb0 => 0x814a, + 0xb1 => 0x8fa3, 0xb2 => 0x5566, 0xb3 => 0x83b1, 0xb4 => 0x6765, + 0xb5 => 0x8d56, 0xb6 => 0x84dd, 0xb7 => 0x5a6a, 0xb8 => 0x680f, + 0xb9 => 0x62e6, 0xba => 0x7bee, 0xbb => 0x9611, 0xbc => 0x5170, + 0xbd => 0x6f9c, 0xbe => 0x8c30, 0xbf => 0x63fd, 0xc0 => 0x89c8, + 0xc1 => 0x61d2, 0xc2 => 0x7f06, 0xc3 => 0x70c2, 0xc4 => 0x6ee5, + 0xc5 => 0x7405, 0xc6 => 0x6994, 0xc7 => 0x72fc, 0xc8 => 0x5eca, + 0xc9 => 0x90ce, 0xca => 0x6717, 0xcb => 0x6d6a, 0xcc => 0x635e, + 0xcd => 0x52b3, 0xce => 0x7262, 0xcf => 0x8001, 0xd0 => 0x4f6c, + 0xd1 => 0x59e5, 0xd2 => 0x916a, 0xd3 => 0x70d9, 0xd4 => 0x6d9d, + 0xd5 => 0x52d2, 0xd6 => 0x4e50, 0xd7 => 0x96f7, 0xd8 => 0x956d, + 0xd9 => 0x857e, 0xda => 0x78ca, 0xdb => 0x7d2f, 0xdc => 0x5121, + 0xdd => 0x5792, 0xde => 0x64c2, 0xdf => 0x808b, 0xe0 => 0x7c7b, + 0xe1 => 0x6cea, 0xe2 => 0x68f1, 0xe3 => 0x695e, 0xe4 => 0x51b7, + 0xe5 => 0x5398, 0xe6 => 0x68a8, 0xe7 => 0x7281, 0xe8 => 0x9ece, + 0xe9 => 0x7bf1, 0xea => 0x72f8, 0xeb => 0x79bb, 0xec => 0x6f13, + 0xed => 0x7406, 0xee => 0x674e, 0xef => 0x91cc, 0xf0 => 0x9ca4, + 0xf1 => 0x793c, 0xf2 => 0x8389, 0xf3 => 0x8354, 0xf4 => 0x540f, + 0xf5 => 0x6817, 0xf6 => 0x4e3d, 0xf7 => 0x5389, 0xf8 => 0x52b1, + 0xf9 => 0x783e, 0xfa => 0x5386, 0xfb => 0x5229, 0xfc => 0x5088, + 0xfd => 0x4f8b, 0xfe => 0x4fd0, + }, + 0xc1 => { + 0xa1 => 0x75e2, 0xa2 => 0x7acb, 0xa3 => 0x7c92, 0xa4 => 0x6ca5, + 0xa5 => 0x96b6, 0xa6 => 0x529b, 0xa7 => 0x7483, 0xa8 => 0x54e9, + 0xa9 => 0x4fe9, 0xaa => 0x8054, 0xab => 0x83b2, 0xac => 0x8fde, + 0xad => 0x9570, 0xae => 0x5ec9, 0xaf => 0x601c, 0xb0 => 0x6d9f, + 0xb1 => 0x5e18, 0xb2 => 0x655b, 0xb3 => 0x8138, 0xb4 => 0x94fe, + 0xb5 => 0x604b, 0xb6 => 0x70bc, 0xb7 => 0x7ec3, 0xb8 => 0x7cae, + 0xb9 => 0x51c9, 0xba => 0x6881, 0xbb => 0x7cb1, 0xbc => 0x826f, + 0xbd => 0x4e24, 0xbe => 0x8f86, 0xbf => 0x91cf, 0xc0 => 0x667e, + 0xc1 => 0x4eae, 0xc2 => 0x8c05, 0xc3 => 0x64a9, 0xc4 => 0x804a, + 0xc5 => 0x50da, 0xc6 => 0x7597, 0xc7 => 0x71ce, 0xc8 => 0x5be5, + 0xc9 => 0x8fbd, 0xca => 0x6f66, 0xcb => 0x4e86, 0xcc => 0x6482, + 0xcd => 0x9563, 0xce => 0x5ed6, 0xcf => 0x6599, 0xd0 => 0x5217, + 0xd1 => 0x88c2, 0xd2 => 0x70c8, 0xd3 => 0x52a3, 0xd4 => 0x730e, + 0xd5 => 0x7433, 0xd6 => 0x6797, 0xd7 => 0x78f7, 0xd8 => 0x9716, + 0xd9 => 0x4e34, 0xda => 0x90bb, 0xdb => 0x9cde, 0xdc => 0x6dcb, + 0xdd => 0x51db, 0xde => 0x8d41, 0xdf => 0x541d, 0xe0 => 0x62ce, + 0xe1 => 0x73b2, 0xe2 => 0x83f1, 0xe3 => 0x96f6, 0xe4 => 0x9f84, + 0xe5 => 0x94c3, 0xe6 => 0x4f36, 0xe7 => 0x7f9a, 0xe8 => 0x51cc, + 0xe9 => 0x7075, 0xea => 0x9675, 0xeb => 0x5cad, 0xec => 0x9886, + 0xed => 0x53e6, 0xee => 0x4ee4, 0xef => 0x6e9c, 0xf0 => 0x7409, + 0xf1 => 0x69b4, 0xf2 => 0x786b, 0xf3 => 0x998f, 0xf4 => 0x7559, + 0xf5 => 0x5218, 0xf6 => 0x7624, 0xf7 => 0x6d41, 0xf8 => 0x67f3, + 0xf9 => 0x516d, 0xfa => 0x9f99, 0xfb => 0x804b, 0xfc => 0x5499, + 0xfd => 0x7b3c, 0xfe => 0x7abf, + }, + 0xc2 => { + 0xa1 => 0x9686, 0xa2 => 0x5784, 0xa3 => 0x62e2, 0xa4 => 0x9647, + 0xa5 => 0x697c, 0xa6 => 0x5a04, 0xa7 => 0x6402, 0xa8 => 0x7bd3, + 0xa9 => 0x6f0f, 0xaa => 0x964b, 0xab => 0x82a6, 0xac => 0x5362, + 0xad => 0x9885, 0xae => 0x5e90, 0xaf => 0x7089, 0xb0 => 0x63b3, + 0xb1 => 0x5364, 0xb2 => 0x864f, 0xb3 => 0x9c81, 0xb4 => 0x9e93, + 0xb5 => 0x788c, 0xb6 => 0x9732, 0xb7 => 0x8def, 0xb8 => 0x8d42, + 0xb9 => 0x9e7f, 0xba => 0x6f5e, 0xbb => 0x7984, 0xbc => 0x5f55, + 0xbd => 0x9646, 0xbe => 0x622e, 0xbf => 0x9a74, 0xc0 => 0x5415, + 0xc1 => 0x94dd, 0xc2 => 0x4fa3, 0xc3 => 0x65c5, 0xc4 => 0x5c65, + 0xc5 => 0x5c61, 0xc6 => 0x7f15, 0xc7 => 0x8651, 0xc8 => 0x6c2f, + 0xc9 => 0x5f8b, 0xca => 0x7387, 0xcb => 0x6ee4, 0xcc => 0x7eff, + 0xcd => 0x5ce6, 0xce => 0x631b, 0xcf => 0x5b6a, 0xd0 => 0x6ee6, + 0xd1 => 0x5375, 0xd2 => 0x4e71, 0xd3 => 0x63a0, 0xd4 => 0x7565, + 0xd5 => 0x62a1, 0xd6 => 0x8f6e, 0xd7 => 0x4f26, 0xd8 => 0x4ed1, + 0xd9 => 0x6ca6, 0xda => 0x7eb6, 0xdb => 0x8bba, 0xdc => 0x841d, + 0xdd => 0x87ba, 0xde => 0x7f57, 0xdf => 0x903b, 0xe0 => 0x9523, + 0xe1 => 0x7ba9, 0xe2 => 0x9aa1, 0xe3 => 0x88f8, 0xe4 => 0x843d, + 0xe5 => 0x6d1b, 0xe6 => 0x9a86, 0xe7 => 0x7edc, 0xe8 => 0x5988, + 0xe9 => 0x9ebb, 0xea => 0x739b, 0xeb => 0x7801, 0xec => 0x8682, + 0xed => 0x9a6c, 0xee => 0x9a82, 0xef => 0x561b, 0xf0 => 0x5417, + 0xf1 => 0x57cb, 0xf2 => 0x4e70, 0xf3 => 0x9ea6, 0xf4 => 0x5356, + 0xf5 => 0x8fc8, 0xf6 => 0x8109, 0xf7 => 0x7792, 0xf8 => 0x9992, + 0xf9 => 0x86ee, 0xfa => 0x6ee1, 0xfb => 0x8513, 0xfc => 0x66fc, + 0xfd => 0x6162, 0xfe => 0x6f2b, + }, + 0xc3 => { + 0xa1 => 0x8c29, 0xa2 => 0x8292, 0xa3 => 0x832b, 0xa4 => 0x76f2, + 0xa5 => 0x6c13, 0xa6 => 0x5fd9, 0xa7 => 0x83bd, 0xa8 => 0x732b, + 0xa9 => 0x8305, 0xaa => 0x951a, 0xab => 0x6bdb, 0xac => 0x77db, + 0xad => 0x94c6, 0xae => 0x536f, 0xaf => 0x8302, 0xb0 => 0x5192, + 0xb1 => 0x5e3d, 0xb2 => 0x8c8c, 0xb3 => 0x8d38, 0xb4 => 0x4e48, + 0xb5 => 0x73ab, 0xb6 => 0x679a, 0xb7 => 0x6885, 0xb8 => 0x9176, + 0xb9 => 0x9709, 0xba => 0x7164, 0xbb => 0x6ca1, 0xbc => 0x7709, + 0xbd => 0x5a92, 0xbe => 0x9541, 0xbf => 0x6bcf, 0xc0 => 0x7f8e, + 0xc1 => 0x6627, 0xc2 => 0x5bd0, 0xc3 => 0x59b9, 0xc4 => 0x5a9a, + 0xc5 => 0x95e8, 0xc6 => 0x95f7, 0xc7 => 0x4eec, 0xc8 => 0x840c, + 0xc9 => 0x8499, 0xca => 0x6aac, 0xcb => 0x76df, 0xcc => 0x9530, + 0xcd => 0x731b, 0xce => 0x68a6, 0xcf => 0x5b5f, 0xd0 => 0x772f, + 0xd1 => 0x919a, 0xd2 => 0x9761, 0xd3 => 0x7cdc, 0xd4 => 0x8ff7, + 0xd5 => 0x8c1c, 0xd6 => 0x5f25, 0xd7 => 0x7c73, 0xd8 => 0x79d8, + 0xd9 => 0x89c5, 0xda => 0x6ccc, 0xdb => 0x871c, 0xdc => 0x5bc6, + 0xdd => 0x5e42, 0xde => 0x68c9, 0xdf => 0x7720, 0xe0 => 0x7ef5, + 0xe1 => 0x5195, 0xe2 => 0x514d, 0xe3 => 0x52c9, 0xe4 => 0x5a29, + 0xe5 => 0x7f05, 0xe6 => 0x9762, 0xe7 => 0x82d7, 0xe8 => 0x63cf, + 0xe9 => 0x7784, 0xea => 0x85d0, 0xeb => 0x79d2, 0xec => 0x6e3a, + 0xed => 0x5e99, 0xee => 0x5999, 0xef => 0x8511, 0xf0 => 0x706d, + 0xf1 => 0x6c11, 0xf2 => 0x62bf, 0xf3 => 0x76bf, 0xf4 => 0x654f, + 0xf5 => 0x60af, 0xf6 => 0x95fd, 0xf7 => 0x660e, 0xf8 => 0x879f, + 0xf9 => 0x9e23, 0xfa => 0x94ed, 0xfb => 0x540d, 0xfc => 0x547d, + 0xfd => 0x8c2c, 0xfe => 0x6478, + }, + 0xc4 => { + 0xa1 => 0x6479, 0xa2 => 0x8611, 0xa3 => 0x6a21, 0xa4 => 0x819c, + 0xa5 => 0x78e8, 0xa6 => 0x6469, 0xa7 => 0x9b54, 0xa8 => 0x62b9, + 0xa9 => 0x672b, 0xaa => 0x83ab, 0xab => 0x58a8, 0xac => 0x9ed8, + 0xad => 0x6cab, 0xae => 0x6f20, 0xaf => 0x5bde, 0xb0 => 0x964c, + 0xb1 => 0x8c0b, 0xb2 => 0x725f, 0xb3 => 0x67d0, 0xb4 => 0x62c7, + 0xb5 => 0x7261, 0xb6 => 0x4ea9, 0xb7 => 0x59c6, 0xb8 => 0x6bcd, + 0xb9 => 0x5893, 0xba => 0x66ae, 0xbb => 0x5e55, 0xbc => 0x52df, + 0xbd => 0x6155, 0xbe => 0x6728, 0xbf => 0x76ee, 0xc0 => 0x7766, + 0xc1 => 0x7267, 0xc2 => 0x7a46, 0xc3 => 0x62ff, 0xc4 => 0x54ea, + 0xc5 => 0x5450, 0xc6 => 0x94a0, 0xc7 => 0x90a3, 0xc8 => 0x5a1c, + 0xc9 => 0x7eb3, 0xca => 0x6c16, 0xcb => 0x4e43, 0xcc => 0x5976, + 0xcd => 0x8010, 0xce => 0x5948, 0xcf => 0x5357, 0xd0 => 0x7537, + 0xd1 => 0x96be, 0xd2 => 0x56ca, 0xd3 => 0x6320, 0xd4 => 0x8111, + 0xd5 => 0x607c, 0xd6 => 0x95f9, 0xd7 => 0x6dd6, 0xd8 => 0x5462, + 0xd9 => 0x9981, 0xda => 0x5185, 0xdb => 0x5ae9, 0xdc => 0x80fd, + 0xdd => 0x59ae, 0xde => 0x9713, 0xdf => 0x502a, 0xe0 => 0x6ce5, + 0xe1 => 0x5c3c, 0xe2 => 0x62df, 0xe3 => 0x4f60, 0xe4 => 0x533f, + 0xe5 => 0x817b, 0xe6 => 0x9006, 0xe7 => 0x6eba, 0xe8 => 0x852b, + 0xe9 => 0x62c8, 0xea => 0x5e74, 0xeb => 0x78be, 0xec => 0x64b5, + 0xed => 0x637b, 0xee => 0x5ff5, 0xef => 0x5a18, 0xf0 => 0x917f, + 0xf1 => 0x9e1f, 0xf2 => 0x5c3f, 0xf3 => 0x634f, 0xf4 => 0x8042, + 0xf5 => 0x5b7d, 0xf6 => 0x556e, 0xf7 => 0x954a, 0xf8 => 0x954d, + 0xf9 => 0x6d85, 0xfa => 0x60a8, 0xfb => 0x67e0, 0xfc => 0x72de, + 0xfd => 0x51dd, 0xfe => 0x5b81, + }, + 0xc5 => { + 0xa1 => 0x62e7, 0xa2 => 0x6cde, 0xa3 => 0x725b, 0xa4 => 0x626d, + 0xa5 => 0x94ae, 0xa6 => 0x7ebd, 0xa7 => 0x8113, 0xa8 => 0x6d53, + 0xa9 => 0x519c, 0xaa => 0x5f04, 0xab => 0x5974, 0xac => 0x52aa, + 0xad => 0x6012, 0xae => 0x5973, 0xaf => 0x6696, 0xb0 => 0x8650, + 0xb1 => 0x759f, 0xb2 => 0x632a, 0xb3 => 0x61e6, 0xb4 => 0x7cef, + 0xb5 => 0x8bfa, 0xb6 => 0x54e6, 0xb7 => 0x6b27, 0xb8 => 0x9e25, + 0xb9 => 0x6bb4, 0xba => 0x85d5, 0xbb => 0x5455, 0xbc => 0x5076, + 0xbd => 0x6ca4, 0xbe => 0x556a, 0xbf => 0x8db4, 0xc0 => 0x722c, + 0xc1 => 0x5e15, 0xc2 => 0x6015, 0xc3 => 0x7436, 0xc4 => 0x62cd, + 0xc5 => 0x6392, 0xc6 => 0x724c, 0xc7 => 0x5f98, 0xc8 => 0x6e43, + 0xc9 => 0x6d3e, 0xca => 0x6500, 0xcb => 0x6f58, 0xcc => 0x76d8, + 0xcd => 0x78d0, 0xce => 0x76fc, 0xcf => 0x7554, 0xd0 => 0x5224, + 0xd1 => 0x53db, 0xd2 => 0x4e53, 0xd3 => 0x5e9e, 0xd4 => 0x65c1, + 0xd5 => 0x802a, 0xd6 => 0x80d6, 0xd7 => 0x629b, 0xd8 => 0x5486, + 0xd9 => 0x5228, 0xda => 0x70ae, 0xdb => 0x888d, 0xdc => 0x8dd1, + 0xdd => 0x6ce1, 0xde => 0x5478, 0xdf => 0x80da, 0xe0 => 0x57f9, + 0xe1 => 0x88f4, 0xe2 => 0x8d54, 0xe3 => 0x966a, 0xe4 => 0x914d, + 0xe5 => 0x4f69, 0xe6 => 0x6c9b, 0xe7 => 0x55b7, 0xe8 => 0x76c6, + 0xe9 => 0x7830, 0xea => 0x62a8, 0xeb => 0x70f9, 0xec => 0x6f8e, + 0xed => 0x5f6d, 0xee => 0x84ec, 0xef => 0x68da, 0xf0 => 0x787c, + 0xf1 => 0x7bf7, 0xf2 => 0x81a8, 0xf3 => 0x670b, 0xf4 => 0x9e4f, + 0xf5 => 0x6367, 0xf6 => 0x78b0, 0xf7 => 0x576f, 0xf8 => 0x7812, + 0xf9 => 0x9739, 0xfa => 0x6279, 0xfb => 0x62ab, 0xfc => 0x5288, + 0xfd => 0x7435, 0xfe => 0x6bd7, + }, + 0xc6 => { + 0xa1 => 0x5564, 0xa2 => 0x813e, 0xa3 => 0x75b2, 0xa4 => 0x76ae, + 0xa5 => 0x5339, 0xa6 => 0x75de, 0xa7 => 0x50fb, 0xa8 => 0x5c41, + 0xa9 => 0x8b6c, 0xaa => 0x7bc7, 0xab => 0x504f, 0xac => 0x7247, + 0xad => 0x9a97, 0xae => 0x98d8, 0xaf => 0x6f02, 0xb0 => 0x74e2, + 0xb1 => 0x7968, 0xb2 => 0x6487, 0xb3 => 0x77a5, 0xb4 => 0x62fc, + 0xb5 => 0x9891, 0xb6 => 0x8d2b, 0xb7 => 0x54c1, 0xb8 => 0x8058, + 0xb9 => 0x4e52, 0xba => 0x576a, 0xbb => 0x82f9, 0xbc => 0x840d, + 0xbd => 0x5e73, 0xbe => 0x51ed, 0xbf => 0x74f6, 0xc0 => 0x8bc4, + 0xc1 => 0x5c4f, 0xc2 => 0x5761, 0xc3 => 0x6cfc, 0xc4 => 0x9887, + 0xc5 => 0x5a46, 0xc6 => 0x7834, 0xc7 => 0x9b44, 0xc8 => 0x8feb, + 0xc9 => 0x7c95, 0xca => 0x5256, 0xcb => 0x6251, 0xcc => 0x94fa, + 0xcd => 0x4ec6, 0xce => 0x8386, 0xcf => 0x8461, 0xd0 => 0x83e9, + 0xd1 => 0x84b2, 0xd2 => 0x57d4, 0xd3 => 0x6734, 0xd4 => 0x5703, + 0xd5 => 0x666e, 0xd6 => 0x6d66, 0xd7 => 0x8c31, 0xd8 => 0x66dd, + 0xd9 => 0x7011, 0xda => 0x671f, 0xdb => 0x6b3a, 0xdc => 0x6816, + 0xdd => 0x621a, 0xde => 0x59bb, 0xdf => 0x4e03, 0xe0 => 0x51c4, + 0xe1 => 0x6f06, 0xe2 => 0x67d2, 0xe3 => 0x6c8f, 0xe4 => 0x5176, + 0xe5 => 0x68cb, 0xe6 => 0x5947, 0xe7 => 0x6b67, 0xe8 => 0x7566, + 0xe9 => 0x5d0e, 0xea => 0x8110, 0xeb => 0x9f50, 0xec => 0x65d7, + 0xed => 0x7948, 0xee => 0x7941, 0xef => 0x9a91, 0xf0 => 0x8d77, + 0xf1 => 0x5c82, 0xf2 => 0x4e5e, 0xf3 => 0x4f01, 0xf4 => 0x542f, + 0xf5 => 0x5951, 0xf6 => 0x780c, 0xf7 => 0x5668, 0xf8 => 0x6c14, + 0xf9 => 0x8fc4, 0xfa => 0x5f03, 0xfb => 0x6c7d, 0xfc => 0x6ce3, + 0xfd => 0x8bab, 0xfe => 0x6390, + }, + 0xc7 => { + 0xa1 => 0x6070, 0xa2 => 0x6d3d, 0xa3 => 0x7275, 0xa4 => 0x6266, + 0xa5 => 0x948e, 0xa6 => 0x94c5, 0xa7 => 0x5343, 0xa8 => 0x8fc1, + 0xa9 => 0x7b7e, 0xaa => 0x4edf, 0xab => 0x8c26, 0xac => 0x4e7e, + 0xad => 0x9ed4, 0xae => 0x94b1, 0xaf => 0x94b3, 0xb0 => 0x524d, + 0xb1 => 0x6f5c, 0xb2 => 0x9063, 0xb3 => 0x6d45, 0xb4 => 0x8c34, + 0xb5 => 0x5811, 0xb6 => 0x5d4c, 0xb7 => 0x6b20, 0xb8 => 0x6b49, + 0xb9 => 0x67aa, 0xba => 0x545b, 0xbb => 0x8154, 0xbc => 0x7f8c, + 0xbd => 0x5899, 0xbe => 0x8537, 0xbf => 0x5f3a, 0xc0 => 0x62a2, + 0xc1 => 0x6a47, 0xc2 => 0x9539, 0xc3 => 0x6572, 0xc4 => 0x6084, + 0xc5 => 0x6865, 0xc6 => 0x77a7, 0xc7 => 0x4e54, 0xc8 => 0x4fa8, + 0xc9 => 0x5de7, 0xca => 0x9798, 0xcb => 0x64ac, 0xcc => 0x7fd8, + 0xcd => 0x5ced, 0xce => 0x4fcf, 0xcf => 0x7a8d, 0xd0 => 0x5207, + 0xd1 => 0x8304, 0xd2 => 0x4e14, 0xd3 => 0x602f, 0xd4 => 0x7a83, + 0xd5 => 0x94a6, 0xd6 => 0x4fb5, 0xd7 => 0x4eb2, 0xd8 => 0x79e6, + 0xd9 => 0x7434, 0xda => 0x52e4, 0xdb => 0x82b9, 0xdc => 0x64d2, + 0xdd => 0x79bd, 0xde => 0x5bdd, 0xdf => 0x6c81, 0xe0 => 0x9752, + 0xe1 => 0x8f7b, 0xe2 => 0x6c22, 0xe3 => 0x503e, 0xe4 => 0x537f, + 0xe5 => 0x6e05, 0xe6 => 0x64ce, 0xe7 => 0x6674, 0xe8 => 0x6c30, + 0xe9 => 0x60c5, 0xea => 0x9877, 0xeb => 0x8bf7, 0xec => 0x5e86, + 0xed => 0x743c, 0xee => 0x7a77, 0xef => 0x79cb, 0xf0 => 0x4e18, + 0xf1 => 0x90b1, 0xf2 => 0x7403, 0xf3 => 0x6c42, 0xf4 => 0x56da, + 0xf5 => 0x914b, 0xf6 => 0x6cc5, 0xf7 => 0x8d8b, 0xf8 => 0x533a, + 0xf9 => 0x86c6, 0xfa => 0x66f2, 0xfb => 0x8eaf, 0xfc => 0x5c48, + 0xfd => 0x9a71, 0xfe => 0x6e20, + }, + 0xc8 => { + 0xa1 => 0x53d6, 0xa2 => 0x5a36, 0xa3 => 0x9f8b, 0xa4 => 0x8da3, + 0xa5 => 0x53bb, 0xa6 => 0x5708, 0xa7 => 0x98a7, 0xa8 => 0x6743, + 0xa9 => 0x919b, 0xaa => 0x6cc9, 0xab => 0x5168, 0xac => 0x75ca, + 0xad => 0x62f3, 0xae => 0x72ac, 0xaf => 0x5238, 0xb0 => 0x529d, + 0xb1 => 0x7f3a, 0xb2 => 0x7094, 0xb3 => 0x7638, 0xb4 => 0x5374, + 0xb5 => 0x9e4a, 0xb6 => 0x69b7, 0xb7 => 0x786e, 0xb8 => 0x96c0, + 0xb9 => 0x88d9, 0xba => 0x7fa4, 0xbb => 0x7136, 0xbc => 0x71c3, + 0xbd => 0x5189, 0xbe => 0x67d3, 0xbf => 0x74e4, 0xc0 => 0x58e4, + 0xc1 => 0x6518, 0xc2 => 0x56b7, 0xc3 => 0x8ba9, 0xc4 => 0x9976, + 0xc5 => 0x6270, 0xc6 => 0x7ed5, 0xc7 => 0x60f9, 0xc8 => 0x70ed, + 0xc9 => 0x58ec, 0xca => 0x4ec1, 0xcb => 0x4eba, 0xcc => 0x5fcd, + 0xcd => 0x97e7, 0xce => 0x4efb, 0xcf => 0x8ba4, 0xd0 => 0x5203, + 0xd1 => 0x598a, 0xd2 => 0x7eab, 0xd3 => 0x6254, 0xd4 => 0x4ecd, + 0xd5 => 0x65e5, 0xd6 => 0x620e, 0xd7 => 0x8338, 0xd8 => 0x84c9, + 0xd9 => 0x8363, 0xda => 0x878d, 0xdb => 0x7194, 0xdc => 0x6eb6, + 0xdd => 0x5bb9, 0xde => 0x7ed2, 0xdf => 0x5197, 0xe0 => 0x63c9, + 0xe1 => 0x67d4, 0xe2 => 0x8089, 0xe3 => 0x8339, 0xe4 => 0x8815, + 0xe5 => 0x5112, 0xe6 => 0x5b7a, 0xe7 => 0x5982, 0xe8 => 0x8fb1, + 0xe9 => 0x4e73, 0xea => 0x6c5d, 0xeb => 0x5165, 0xec => 0x8925, + 0xed => 0x8f6f, 0xee => 0x962e, 0xef => 0x854a, 0xf0 => 0x745e, + 0xf1 => 0x9510, 0xf2 => 0x95f0, 0xf3 => 0x6da6, 0xf4 => 0x82e5, + 0xf5 => 0x5f31, 0xf6 => 0x6492, 0xf7 => 0x6d12, 0xf8 => 0x8428, + 0xf9 => 0x816e, 0xfa => 0x9cc3, 0xfb => 0x585e, 0xfc => 0x8d5b, + 0xfd => 0x4e09, 0xfe => 0x53c1, + }, + 0xc9 => { + 0xa1 => 0x4f1e, 0xa2 => 0x6563, 0xa3 => 0x6851, 0xa4 => 0x55d3, + 0xa5 => 0x4e27, 0xa6 => 0x6414, 0xa7 => 0x9a9a, 0xa8 => 0x626b, + 0xa9 => 0x5ac2, 0xaa => 0x745f, 0xab => 0x8272, 0xac => 0x6da9, + 0xad => 0x68ee, 0xae => 0x50e7, 0xaf => 0x838e, 0xb0 => 0x7802, + 0xb1 => 0x6740, 0xb2 => 0x5239, 0xb3 => 0x6c99, 0xb4 => 0x7eb1, + 0xb5 => 0x50bb, 0xb6 => 0x5565, 0xb7 => 0x715e, 0xb8 => 0x7b5b, + 0xb9 => 0x6652, 0xba => 0x73ca, 0xbb => 0x82eb, 0xbc => 0x6749, + 0xbd => 0x5c71, 0xbe => 0x5220, 0xbf => 0x717d, 0xc0 => 0x886b, + 0xc1 => 0x95ea, 0xc2 => 0x9655, 0xc3 => 0x64c5, 0xc4 => 0x8d61, + 0xc5 => 0x81b3, 0xc6 => 0x5584, 0xc7 => 0x6c55, 0xc8 => 0x6247, + 0xc9 => 0x7f2e, 0xca => 0x5892, 0xcb => 0x4f24, 0xcc => 0x5546, + 0xcd => 0x8d4f, 0xce => 0x664c, 0xcf => 0x4e0a, 0xd0 => 0x5c1a, + 0xd1 => 0x88f3, 0xd2 => 0x68a2, 0xd3 => 0x634e, 0xd4 => 0x7a0d, + 0xd5 => 0x70e7, 0xd6 => 0x828d, 0xd7 => 0x52fa, 0xd8 => 0x97f6, + 0xd9 => 0x5c11, 0xda => 0x54e8, 0xdb => 0x90b5, 0xdc => 0x7ecd, + 0xdd => 0x5962, 0xde => 0x8d4a, 0xdf => 0x86c7, 0xe0 => 0x820c, + 0xe1 => 0x820d, 0xe2 => 0x8d66, 0xe3 => 0x6444, 0xe4 => 0x5c04, + 0xe5 => 0x6151, 0xe6 => 0x6d89, 0xe7 => 0x793e, 0xe8 => 0x8bbe, + 0xe9 => 0x7837, 0xea => 0x7533, 0xeb => 0x547b, 0xec => 0x4f38, + 0xed => 0x8eab, 0xee => 0x6df1, 0xef => 0x5a20, 0xf0 => 0x7ec5, + 0xf1 => 0x795e, 0xf2 => 0x6c88, 0xf3 => 0x5ba1, 0xf4 => 0x5a76, + 0xf5 => 0x751a, 0xf6 => 0x80be, 0xf7 => 0x614e, 0xf8 => 0x6e17, + 0xf9 => 0x58f0, 0xfa => 0x751f, 0xfb => 0x7525, 0xfc => 0x7272, + 0xfd => 0x5347, 0xfe => 0x7ef3, + }, + 0xca => { + 0xa1 => 0x7701, 0xa2 => 0x76db, 0xa3 => 0x5269, 0xa4 => 0x80dc, + 0xa5 => 0x5723, 0xa6 => 0x5e08, 0xa7 => 0x5931, 0xa8 => 0x72ee, + 0xa9 => 0x65bd, 0xaa => 0x6e7f, 0xab => 0x8bd7, 0xac => 0x5c38, + 0xad => 0x8671, 0xae => 0x5341, 0xaf => 0x77f3, 0xb0 => 0x62fe, + 0xb1 => 0x65f6, 0xb2 => 0x4ec0, 0xb3 => 0x98df, 0xb4 => 0x8680, + 0xb5 => 0x5b9e, 0xb6 => 0x8bc6, 0xb7 => 0x53f2, 0xb8 => 0x77e2, + 0xb9 => 0x4f7f, 0xba => 0x5c4e, 0xbb => 0x9a76, 0xbc => 0x59cb, + 0xbd => 0x5f0f, 0xbe => 0x793a, 0xbf => 0x58eb, 0xc0 => 0x4e16, + 0xc1 => 0x67ff, 0xc2 => 0x4e8b, 0xc3 => 0x62ed, 0xc4 => 0x8a93, + 0xc5 => 0x901d, 0xc6 => 0x52bf, 0xc7 => 0x662f, 0xc8 => 0x55dc, + 0xc9 => 0x566c, 0xca => 0x9002, 0xcb => 0x4ed5, 0xcc => 0x4f8d, + 0xcd => 0x91ca, 0xce => 0x9970, 0xcf => 0x6c0f, 0xd0 => 0x5e02, + 0xd1 => 0x6043, 0xd2 => 0x5ba4, 0xd3 => 0x89c6, 0xd4 => 0x8bd5, + 0xd5 => 0x6536, 0xd6 => 0x624b, 0xd7 => 0x9996, 0xd8 => 0x5b88, + 0xd9 => 0x5bff, 0xda => 0x6388, 0xdb => 0x552e, 0xdc => 0x53d7, + 0xdd => 0x7626, 0xde => 0x517d, 0xdf => 0x852c, 0xe0 => 0x67a2, + 0xe1 => 0x68b3, 0xe2 => 0x6b8a, 0xe3 => 0x6292, 0xe4 => 0x8f93, + 0xe5 => 0x53d4, 0xe6 => 0x8212, 0xe7 => 0x6dd1, 0xe8 => 0x758f, + 0xe9 => 0x4e66, 0xea => 0x8d4e, 0xeb => 0x5b70, 0xec => 0x719f, + 0xed => 0x85af, 0xee => 0x6691, 0xef => 0x66d9, 0xf0 => 0x7f72, + 0xf1 => 0x8700, 0xf2 => 0x9ecd, 0xf3 => 0x9f20, 0xf4 => 0x5c5e, + 0xf5 => 0x672f, 0xf6 => 0x8ff0, 0xf7 => 0x6811, 0xf8 => 0x675f, + 0xf9 => 0x620d, 0xfa => 0x7ad6, 0xfb => 0x5885, 0xfc => 0x5eb6, + 0xfd => 0x6570, 0xfe => 0x6f31, + }, + 0xcb => { + 0xa1 => 0x6055, 0xa2 => 0x5237, 0xa3 => 0x800d, 0xa4 => 0x6454, + 0xa5 => 0x8870, 0xa6 => 0x7529, 0xa7 => 0x5e05, 0xa8 => 0x6813, + 0xa9 => 0x62f4, 0xaa => 0x971c, 0xab => 0x53cc, 0xac => 0x723d, + 0xad => 0x8c01, 0xae => 0x6c34, 0xaf => 0x7761, 0xb0 => 0x7a0e, + 0xb1 => 0x542e, 0xb2 => 0x77ac, 0xb3 => 0x987a, 0xb4 => 0x821c, + 0xb5 => 0x8bf4, 0xb6 => 0x7855, 0xb7 => 0x6714, 0xb8 => 0x70c1, + 0xb9 => 0x65af, 0xba => 0x6495, 0xbb => 0x5636, 0xbc => 0x601d, + 0xbd => 0x79c1, 0xbe => 0x53f8, 0xbf => 0x4e1d, 0xc0 => 0x6b7b, + 0xc1 => 0x8086, 0xc2 => 0x5bfa, 0xc3 => 0x55e3, 0xc4 => 0x56db, + 0xc5 => 0x4f3a, 0xc6 => 0x4f3c, 0xc7 => 0x9972, 0xc8 => 0x5df3, + 0xc9 => 0x677e, 0xca => 0x8038, 0xcb => 0x6002, 0xcc => 0x9882, + 0xcd => 0x9001, 0xce => 0x5b8b, 0xcf => 0x8bbc, 0xd0 => 0x8bf5, + 0xd1 => 0x641c, 0xd2 => 0x8258, 0xd3 => 0x64de, 0xd4 => 0x55fd, + 0xd5 => 0x82cf, 0xd6 => 0x9165, 0xd7 => 0x4fd7, 0xd8 => 0x7d20, + 0xd9 => 0x901f, 0xda => 0x7c9f, 0xdb => 0x50f3, 0xdc => 0x5851, + 0xdd => 0x6eaf, 0xde => 0x5bbf, 0xdf => 0x8bc9, 0xe0 => 0x8083, + 0xe1 => 0x9178, 0xe2 => 0x849c, 0xe3 => 0x7b97, 0xe4 => 0x867d, + 0xe5 => 0x968b, 0xe6 => 0x968f, 0xe7 => 0x7ee5, 0xe8 => 0x9ad3, + 0xe9 => 0x788e, 0xea => 0x5c81, 0xeb => 0x7a57, 0xec => 0x9042, + 0xed => 0x96a7, 0xee => 0x795f, 0xef => 0x5b59, 0xf0 => 0x635f, + 0xf1 => 0x7b0b, 0xf2 => 0x84d1, 0xf3 => 0x68ad, 0xf4 => 0x5506, + 0xf5 => 0x7f29, 0xf6 => 0x7410, 0xf7 => 0x7d22, 0xf8 => 0x9501, + 0xf9 => 0x6240, 0xfa => 0x584c, 0xfb => 0x4ed6, 0xfc => 0x5b83, + 0xfd => 0x5979, 0xfe => 0x5854, + }, + 0xcc => { + 0xa1 => 0x736d, 0xa2 => 0x631e, 0xa3 => 0x8e4b, 0xa4 => 0x8e0f, + 0xa5 => 0x80ce, 0xa6 => 0x82d4, 0xa7 => 0x62ac, 0xa8 => 0x53f0, + 0xa9 => 0x6cf0, 0xaa => 0x915e, 0xab => 0x592a, 0xac => 0x6001, + 0xad => 0x6c70, 0xae => 0x574d, 0xaf => 0x644a, 0xb0 => 0x8d2a, + 0xb1 => 0x762b, 0xb2 => 0x6ee9, 0xb3 => 0x575b, 0xb4 => 0x6a80, + 0xb5 => 0x75f0, 0xb6 => 0x6f6d, 0xb7 => 0x8c2d, 0xb8 => 0x8c08, + 0xb9 => 0x5766, 0xba => 0x6bef, 0xbb => 0x8892, 0xbc => 0x78b3, + 0xbd => 0x63a2, 0xbe => 0x53f9, 0xbf => 0x70ad, 0xc0 => 0x6c64, + 0xc1 => 0x5858, 0xc2 => 0x642a, 0xc3 => 0x5802, 0xc4 => 0x68e0, + 0xc5 => 0x819b, 0xc6 => 0x5510, 0xc7 => 0x7cd6, 0xc8 => 0x5018, + 0xc9 => 0x8eba, 0xca => 0x6dcc, 0xcb => 0x8d9f, 0xcc => 0x70eb, + 0xcd => 0x638f, 0xce => 0x6d9b, 0xcf => 0x6ed4, 0xd0 => 0x7ee6, + 0xd1 => 0x8404, 0xd2 => 0x6843, 0xd3 => 0x9003, 0xd4 => 0x6dd8, + 0xd5 => 0x9676, 0xd6 => 0x8ba8, 0xd7 => 0x5957, 0xd8 => 0x7279, + 0xd9 => 0x85e4, 0xda => 0x817e, 0xdb => 0x75bc, 0xdc => 0x8a8a, + 0xdd => 0x68af, 0xde => 0x5254, 0xdf => 0x8e22, 0xe0 => 0x9511, + 0xe1 => 0x63d0, 0xe2 => 0x9898, 0xe3 => 0x8e44, 0xe4 => 0x557c, + 0xe5 => 0x4f53, 0xe6 => 0x66ff, 0xe7 => 0x568f, 0xe8 => 0x60d5, + 0xe9 => 0x6d95, 0xea => 0x5243, 0xeb => 0x5c49, 0xec => 0x5929, + 0xed => 0x6dfb, 0xee => 0x586b, 0xef => 0x7530, 0xf0 => 0x751c, + 0xf1 => 0x606c, 0xf2 => 0x8214, 0xf3 => 0x8146, 0xf4 => 0x6311, + 0xf5 => 0x6761, 0xf6 => 0x8fe2, 0xf7 => 0x773a, 0xf8 => 0x8df3, + 0xf9 => 0x8d34, 0xfa => 0x94c1, 0xfb => 0x5e16, 0xfc => 0x5385, + 0xfd => 0x542c, 0xfe => 0x70c3, + }, + 0xcd => { + 0xa1 => 0x6c40, 0xa2 => 0x5ef7, 0xa3 => 0x505c, 0xa4 => 0x4ead, + 0xa5 => 0x5ead, 0xa6 => 0x633a, 0xa7 => 0x8247, 0xa8 => 0x901a, + 0xa9 => 0x6850, 0xaa => 0x916e, 0xab => 0x77b3, 0xac => 0x540c, + 0xad => 0x94dc, 0xae => 0x5f64, 0xaf => 0x7ae5, 0xb0 => 0x6876, + 0xb1 => 0x6345, 0xb2 => 0x7b52, 0xb3 => 0x7edf, 0xb4 => 0x75db, + 0xb5 => 0x5077, 0xb6 => 0x6295, 0xb7 => 0x5934, 0xb8 => 0x900f, + 0xb9 => 0x51f8, 0xba => 0x79c3, 0xbb => 0x7a81, 0xbc => 0x56fe, + 0xbd => 0x5f92, 0xbe => 0x9014, 0xbf => 0x6d82, 0xc0 => 0x5c60, + 0xc1 => 0x571f, 0xc2 => 0x5410, 0xc3 => 0x5154, 0xc4 => 0x6e4d, + 0xc5 => 0x56e2, 0xc6 => 0x63a8, 0xc7 => 0x9893, 0xc8 => 0x817f, + 0xc9 => 0x8715, 0xca => 0x892a, 0xcb => 0x9000, 0xcc => 0x541e, + 0xcd => 0x5c6f, 0xce => 0x81c0, 0xcf => 0x62d6, 0xd0 => 0x6258, + 0xd1 => 0x8131, 0xd2 => 0x9e35, 0xd3 => 0x9640, 0xd4 => 0x9a6e, + 0xd5 => 0x9a7c, 0xd6 => 0x692d, 0xd7 => 0x59a5, 0xd8 => 0x62d3, + 0xd9 => 0x553e, 0xda => 0x6316, 0xdb => 0x54c7, 0xdc => 0x86d9, + 0xdd => 0x6d3c, 0xde => 0x5a03, 0xdf => 0x74e6, 0xe0 => 0x889c, + 0xe1 => 0x6b6a, 0xe2 => 0x5916, 0xe3 => 0x8c4c, 0xe4 => 0x5f2f, + 0xe5 => 0x6e7e, 0xe6 => 0x73a9, 0xe7 => 0x987d, 0xe8 => 0x4e38, + 0xe9 => 0x70f7, 0xea => 0x5b8c, 0xeb => 0x7897, 0xec => 0x633d, + 0xed => 0x665a, 0xee => 0x7696, 0xef => 0x60cb, 0xf0 => 0x5b9b, + 0xf1 => 0x5a49, 0xf2 => 0x4e07, 0xf3 => 0x8155, 0xf4 => 0x6c6a, + 0xf5 => 0x738b, 0xf6 => 0x4ea1, 0xf7 => 0x6789, 0xf8 => 0x7f51, + 0xf9 => 0x5f80, 0xfa => 0x65fa, 0xfb => 0x671b, 0xfc => 0x5fd8, + 0xfd => 0x5984, 0xfe => 0x5a01, + }, + 0xce => { + 0xa1 => 0x5dcd, 0xa2 => 0x5fae, 0xa3 => 0x5371, 0xa4 => 0x97e6, + 0xa5 => 0x8fdd, 0xa6 => 0x6845, 0xa7 => 0x56f4, 0xa8 => 0x552f, + 0xa9 => 0x60df, 0xaa => 0x4e3a, 0xab => 0x6f4d, 0xac => 0x7ef4, + 0xad => 0x82c7, 0xae => 0x840e, 0xaf => 0x59d4, 0xb0 => 0x4f1f, + 0xb1 => 0x4f2a, 0xb2 => 0x5c3e, 0xb3 => 0x7eac, 0xb4 => 0x672a, + 0xb5 => 0x851a, 0xb6 => 0x5473, 0xb7 => 0x754f, 0xb8 => 0x80c3, + 0xb9 => 0x5582, 0xba => 0x9b4f, 0xbb => 0x4f4d, 0xbc => 0x6e2d, + 0xbd => 0x8c13, 0xbe => 0x5c09, 0xbf => 0x6170, 0xc0 => 0x536b, + 0xc1 => 0x761f, 0xc2 => 0x6e29, 0xc3 => 0x868a, 0xc4 => 0x6587, + 0xc5 => 0x95fb, 0xc6 => 0x7eb9, 0xc7 => 0x543b, 0xc8 => 0x7a33, + 0xc9 => 0x7d0a, 0xca => 0x95ee, 0xcb => 0x55e1, 0xcc => 0x7fc1, + 0xcd => 0x74ee, 0xce => 0x631d, 0xcf => 0x8717, 0xd0 => 0x6da1, + 0xd1 => 0x7a9d, 0xd2 => 0x6211, 0xd3 => 0x65a1, 0xd4 => 0x5367, + 0xd5 => 0x63e1, 0xd6 => 0x6c83, 0xd7 => 0x5deb, 0xd8 => 0x545c, + 0xd9 => 0x94a8, 0xda => 0x4e4c, 0xdb => 0x6c61, 0xdc => 0x8bec, + 0xdd => 0x5c4b, 0xde => 0x65e0, 0xdf => 0x829c, 0xe0 => 0x68a7, + 0xe1 => 0x543e, 0xe2 => 0x5434, 0xe3 => 0x6bcb, 0xe4 => 0x6b66, + 0xe5 => 0x4e94, 0xe6 => 0x6342, 0xe7 => 0x5348, 0xe8 => 0x821e, + 0xe9 => 0x4f0d, 0xea => 0x4fae, 0xeb => 0x575e, 0xec => 0x620a, + 0xed => 0x96fe, 0xee => 0x6664, 0xef => 0x7269, 0xf0 => 0x52ff, + 0xf1 => 0x52a1, 0xf2 => 0x609f, 0xf3 => 0x8bef, 0xf4 => 0x6614, + 0xf5 => 0x7199, 0xf6 => 0x6790, 0xf7 => 0x897f, 0xf8 => 0x7852, + 0xf9 => 0x77fd, 0xfa => 0x6670, 0xfb => 0x563b, 0xfc => 0x5438, + 0xfd => 0x9521, 0xfe => 0x727a, + }, + 0xcf => { + 0xa1 => 0x7a00, 0xa2 => 0x606f, 0xa3 => 0x5e0c, 0xa4 => 0x6089, + 0xa5 => 0x819d, 0xa6 => 0x5915, 0xa7 => 0x60dc, 0xa8 => 0x7184, + 0xa9 => 0x70ef, 0xaa => 0x6eaa, 0xab => 0x6c50, 0xac => 0x7280, + 0xad => 0x6a84, 0xae => 0x88ad, 0xaf => 0x5e2d, 0xb0 => 0x4e60, + 0xb1 => 0x5ab3, 0xb2 => 0x559c, 0xb3 => 0x94e3, 0xb4 => 0x6d17, + 0xb5 => 0x7cfb, 0xb6 => 0x9699, 0xb7 => 0x620f, 0xb8 => 0x7ec6, + 0xb9 => 0x778e, 0xba => 0x867e, 0xbb => 0x5323, 0xbc => 0x971e, + 0xbd => 0x8f96, 0xbe => 0x6687, 0xbf => 0x5ce1, 0xc0 => 0x4fa0, + 0xc1 => 0x72ed, 0xc2 => 0x4e0b, 0xc3 => 0x53a6, 0xc4 => 0x590f, + 0xc5 => 0x5413, 0xc6 => 0x6380, 0xc7 => 0x9528, 0xc8 => 0x5148, + 0xc9 => 0x4ed9, 0xca => 0x9c9c, 0xcb => 0x7ea4, 0xcc => 0x54b8, + 0xcd => 0x8d24, 0xce => 0x8854, 0xcf => 0x8237, 0xd0 => 0x95f2, + 0xd1 => 0x6d8e, 0xd2 => 0x5f26, 0xd3 => 0x5acc, 0xd4 => 0x663e, + 0xd5 => 0x9669, 0xd6 => 0x73b0, 0xd7 => 0x732e, 0xd8 => 0x53bf, + 0xd9 => 0x817a, 0xda => 0x9985, 0xdb => 0x7fa1, 0xdc => 0x5baa, + 0xdd => 0x9677, 0xde => 0x9650, 0xdf => 0x7ebf, 0xe0 => 0x76f8, + 0xe1 => 0x53a2, 0xe2 => 0x9576, 0xe3 => 0x9999, 0xe4 => 0x7bb1, + 0xe5 => 0x8944, 0xe6 => 0x6e58, 0xe7 => 0x4e61, 0xe8 => 0x7fd4, + 0xe9 => 0x7965, 0xea => 0x8be6, 0xeb => 0x60f3, 0xec => 0x54cd, + 0xed => 0x4eab, 0xee => 0x9879, 0xef => 0x5df7, 0xf0 => 0x6a61, + 0xf1 => 0x50cf, 0xf2 => 0x5411, 0xf3 => 0x8c61, 0xf4 => 0x8427, + 0xf5 => 0x785d, 0xf6 => 0x9704, 0xf7 => 0x524a, 0xf8 => 0x54ee, + 0xf9 => 0x56a3, 0xfa => 0x9500, 0xfb => 0x6d88, 0xfc => 0x5bb5, + 0xfd => 0x6dc6, 0xfe => 0x6653, + }, + 0xd0 => { + 0xa1 => 0x5c0f, 0xa2 => 0x5b5d, 0xa3 => 0x6821, 0xa4 => 0x8096, + 0xa5 => 0x5578, 0xa6 => 0x7b11, 0xa7 => 0x6548, 0xa8 => 0x6954, + 0xa9 => 0x4e9b, 0xaa => 0x6b47, 0xab => 0x874e, 0xac => 0x978b, + 0xad => 0x534f, 0xae => 0x631f, 0xaf => 0x643a, 0xb0 => 0x90aa, + 0xb1 => 0x659c, 0xb2 => 0x80c1, 0xb3 => 0x8c10, 0xb4 => 0x5199, + 0xb5 => 0x68b0, 0xb6 => 0x5378, 0xb7 => 0x87f9, 0xb8 => 0x61c8, + 0xb9 => 0x6cc4, 0xba => 0x6cfb, 0xbb => 0x8c22, 0xbc => 0x5c51, + 0xbd => 0x85aa, 0xbe => 0x82af, 0xbf => 0x950c, 0xc0 => 0x6b23, + 0xc1 => 0x8f9b, 0xc2 => 0x65b0, 0xc3 => 0x5ffb, 0xc4 => 0x5fc3, + 0xc5 => 0x4fe1, 0xc6 => 0x8845, 0xc7 => 0x661f, 0xc8 => 0x8165, + 0xc9 => 0x7329, 0xca => 0x60fa, 0xcb => 0x5174, 0xcc => 0x5211, + 0xcd => 0x578b, 0xce => 0x5f62, 0xcf => 0x90a2, 0xd0 => 0x884c, + 0xd1 => 0x9192, 0xd2 => 0x5e78, 0xd3 => 0x674f, 0xd4 => 0x6027, + 0xd5 => 0x59d3, 0xd6 => 0x5144, 0xd7 => 0x51f6, 0xd8 => 0x80f8, + 0xd9 => 0x5308, 0xda => 0x6c79, 0xdb => 0x96c4, 0xdc => 0x718a, + 0xdd => 0x4f11, 0xde => 0x4fee, 0xdf => 0x7f9e, 0xe0 => 0x673d, + 0xe1 => 0x55c5, 0xe2 => 0x9508, 0xe3 => 0x79c0, 0xe4 => 0x8896, + 0xe5 => 0x7ee3, 0xe6 => 0x589f, 0xe7 => 0x620c, 0xe8 => 0x9700, + 0xe9 => 0x865a, 0xea => 0x5618, 0xeb => 0x987b, 0xec => 0x5f90, + 0xed => 0x8bb8, 0xee => 0x84c4, 0xef => 0x9157, 0xf0 => 0x53d9, + 0xf1 => 0x65ed, 0xf2 => 0x5e8f, 0xf3 => 0x755c, 0xf4 => 0x6064, + 0xf5 => 0x7d6e, 0xf6 => 0x5a7f, 0xf7 => 0x7eea, 0xf8 => 0x7eed, + 0xf9 => 0x8f69, 0xfa => 0x55a7, 0xfb => 0x5ba3, 0xfc => 0x60ac, + 0xfd => 0x65cb, 0xfe => 0x7384, + }, + 0xd1 => { + 0xa1 => 0x9009, 0xa2 => 0x7663, 0xa3 => 0x7729, 0xa4 => 0x7eda, + 0xa5 => 0x9774, 0xa6 => 0x859b, 0xa7 => 0x5b66, 0xa8 => 0x7a74, + 0xa9 => 0x96ea, 0xaa => 0x8840, 0xab => 0x52cb, 0xac => 0x718f, + 0xad => 0x5faa, 0xae => 0x65ec, 0xaf => 0x8be2, 0xb0 => 0x5bfb, + 0xb1 => 0x9a6f, 0xb2 => 0x5de1, 0xb3 => 0x6b89, 0xb4 => 0x6c5b, + 0xb5 => 0x8bad, 0xb6 => 0x8baf, 0xb7 => 0x900a, 0xb8 => 0x8fc5, + 0xb9 => 0x538b, 0xba => 0x62bc, 0xbb => 0x9e26, 0xbc => 0x9e2d, + 0xbd => 0x5440, 0xbe => 0x4e2b, 0xbf => 0x82bd, 0xc0 => 0x7259, + 0xc1 => 0x869c, 0xc2 => 0x5d16, 0xc3 => 0x8859, 0xc4 => 0x6daf, + 0xc5 => 0x96c5, 0xc6 => 0x54d1, 0xc7 => 0x4e9a, 0xc8 => 0x8bb6, + 0xc9 => 0x7109, 0xca => 0x54bd, 0xcb => 0x9609, 0xcc => 0x70df, + 0xcd => 0x6df9, 0xce => 0x76d0, 0xcf => 0x4e25, 0xd0 => 0x7814, + 0xd1 => 0x8712, 0xd2 => 0x5ca9, 0xd3 => 0x5ef6, 0xd4 => 0x8a00, + 0xd5 => 0x989c, 0xd6 => 0x960e, 0xd7 => 0x708e, 0xd8 => 0x6cbf, + 0xd9 => 0x5944, 0xda => 0x63a9, 0xdb => 0x773c, 0xdc => 0x884d, + 0xdd => 0x6f14, 0xde => 0x8273, 0xdf => 0x5830, 0xe0 => 0x71d5, + 0xe1 => 0x538c, 0xe2 => 0x781a, 0xe3 => 0x96c1, 0xe4 => 0x5501, + 0xe5 => 0x5f66, 0xe6 => 0x7130, 0xe7 => 0x5bb4, 0xe8 => 0x8c1a, + 0xe9 => 0x9a8c, 0xea => 0x6b83, 0xeb => 0x592e, 0xec => 0x9e2f, + 0xed => 0x79e7, 0xee => 0x6768, 0xef => 0x626c, 0xf0 => 0x4f6f, + 0xf1 => 0x75a1, 0xf2 => 0x7f8a, 0xf3 => 0x6d0b, 0xf4 => 0x9633, + 0xf5 => 0x6c27, 0xf6 => 0x4ef0, 0xf7 => 0x75d2, 0xf8 => 0x517b, + 0xf9 => 0x6837, 0xfa => 0x6f3e, 0xfb => 0x9080, 0xfc => 0x8170, + 0xfd => 0x5996, 0xfe => 0x7476, + }, + 0xd2 => { + 0xa1 => 0x6447, 0xa2 => 0x5c27, 0xa3 => 0x9065, 0xa4 => 0x7a91, + 0xa5 => 0x8c23, 0xa6 => 0x59da, 0xa7 => 0x54ac, 0xa8 => 0x8200, + 0xa9 => 0x836f, 0xaa => 0x8981, 0xab => 0x8000, 0xac => 0x6930, + 0xad => 0x564e, 0xae => 0x8036, 0xaf => 0x7237, 0xb0 => 0x91ce, + 0xb1 => 0x51b6, 0xb2 => 0x4e5f, 0xb3 => 0x9875, 0xb4 => 0x6396, + 0xb5 => 0x4e1a, 0xb6 => 0x53f6, 0xb7 => 0x66f3, 0xb8 => 0x814b, + 0xb9 => 0x591c, 0xba => 0x6db2, 0xbb => 0x4e00, 0xbc => 0x58f9, + 0xbd => 0x533b, 0xbe => 0x63d6, 0xbf => 0x94f1, 0xc0 => 0x4f9d, + 0xc1 => 0x4f0a, 0xc2 => 0x8863, 0xc3 => 0x9890, 0xc4 => 0x5937, + 0xc5 => 0x9057, 0xc6 => 0x79fb, 0xc7 => 0x4eea, 0xc8 => 0x80f0, + 0xc9 => 0x7591, 0xca => 0x6c82, 0xcb => 0x5b9c, 0xcc => 0x59e8, + 0xcd => 0x5f5d, 0xce => 0x6905, 0xcf => 0x8681, 0xd0 => 0x501a, + 0xd1 => 0x5df2, 0xd2 => 0x4e59, 0xd3 => 0x77e3, 0xd4 => 0x4ee5, + 0xd5 => 0x827a, 0xd6 => 0x6291, 0xd7 => 0x6613, 0xd8 => 0x9091, + 0xd9 => 0x5c79, 0xda => 0x4ebf, 0xdb => 0x5f79, 0xdc => 0x81c6, + 0xdd => 0x9038, 0xde => 0x8084, 0xdf => 0x75ab, 0xe0 => 0x4ea6, + 0xe1 => 0x88d4, 0xe2 => 0x610f, 0xe3 => 0x6bc5, 0xe4 => 0x5fc6, + 0xe5 => 0x4e49, 0xe6 => 0x76ca, 0xe7 => 0x6ea2, 0xe8 => 0x8be3, + 0xe9 => 0x8bae, 0xea => 0x8c0a, 0xeb => 0x8bd1, 0xec => 0x5f02, + 0xed => 0x7ffc, 0xee => 0x7fcc, 0xef => 0x7ece, 0xf0 => 0x8335, + 0xf1 => 0x836b, 0xf2 => 0x56e0, 0xf3 => 0x6bb7, 0xf4 => 0x97f3, + 0xf5 => 0x9634, 0xf6 => 0x59fb, 0xf7 => 0x541f, 0xf8 => 0x94f6, + 0xf9 => 0x6deb, 0xfa => 0x5bc5, 0xfb => 0x996e, 0xfc => 0x5c39, + 0xfd => 0x5f15, 0xfe => 0x9690, + }, + 0xd3 => { + 0xa1 => 0x5370, 0xa2 => 0x82f1, 0xa3 => 0x6a31, 0xa4 => 0x5a74, + 0xa5 => 0x9e70, 0xa6 => 0x5e94, 0xa7 => 0x7f28, 0xa8 => 0x83b9, + 0xa9 => 0x8424, 0xaa => 0x8425, 0xab => 0x8367, 0xac => 0x8747, + 0xad => 0x8fce, 0xae => 0x8d62, 0xaf => 0x76c8, 0xb0 => 0x5f71, + 0xb1 => 0x9896, 0xb2 => 0x786c, 0xb3 => 0x6620, 0xb4 => 0x54df, + 0xb5 => 0x62e5, 0xb6 => 0x4f63, 0xb7 => 0x81c3, 0xb8 => 0x75c8, + 0xb9 => 0x5eb8, 0xba => 0x96cd, 0xbb => 0x8e0a, 0xbc => 0x86f9, + 0xbd => 0x548f, 0xbe => 0x6cf3, 0xbf => 0x6d8c, 0xc0 => 0x6c38, + 0xc1 => 0x607f, 0xc2 => 0x52c7, 0xc3 => 0x7528, 0xc4 => 0x5e7d, + 0xc5 => 0x4f18, 0xc6 => 0x60a0, 0xc7 => 0x5fe7, 0xc8 => 0x5c24, + 0xc9 => 0x7531, 0xca => 0x90ae, 0xcb => 0x94c0, 0xcc => 0x72b9, + 0xcd => 0x6cb9, 0xce => 0x6e38, 0xcf => 0x9149, 0xd0 => 0x6709, + 0xd1 => 0x53cb, 0xd2 => 0x53f3, 0xd3 => 0x4f51, 0xd4 => 0x91c9, + 0xd5 => 0x8bf1, 0xd6 => 0x53c8, 0xd7 => 0x5e7c, 0xd8 => 0x8fc2, + 0xd9 => 0x6de4, 0xda => 0x4e8e, 0xdb => 0x76c2, 0xdc => 0x6986, + 0xdd => 0x865e, 0xde => 0x611a, 0xdf => 0x8206, 0xe0 => 0x4f59, + 0xe1 => 0x4fde, 0xe2 => 0x903e, 0xe3 => 0x9c7c, 0xe4 => 0x6109, + 0xe5 => 0x6e1d, 0xe6 => 0x6e14, 0xe7 => 0x9685, 0xe8 => 0x4e88, + 0xe9 => 0x5a31, 0xea => 0x96e8, 0xeb => 0x4e0e, 0xec => 0x5c7f, + 0xed => 0x79b9, 0xee => 0x5b87, 0xef => 0x8bed, 0xf0 => 0x7fbd, + 0xf1 => 0x7389, 0xf2 => 0x57df, 0xf3 => 0x828b, 0xf4 => 0x90c1, + 0xf5 => 0x5401, 0xf6 => 0x9047, 0xf7 => 0x55bb, 0xf8 => 0x5cea, + 0xf9 => 0x5fa1, 0xfa => 0x6108, 0xfb => 0x6b32, 0xfc => 0x72f1, + 0xfd => 0x80b2, 0xfe => 0x8a89, + }, + 0xd4 => { + 0xa1 => 0x6d74, 0xa2 => 0x5bd3, 0xa3 => 0x88d5, 0xa4 => 0x9884, + 0xa5 => 0x8c6b, 0xa6 => 0x9a6d, 0xa7 => 0x9e33, 0xa8 => 0x6e0a, + 0xa9 => 0x51a4, 0xaa => 0x5143, 0xab => 0x57a3, 0xac => 0x8881, + 0xad => 0x539f, 0xae => 0x63f4, 0xaf => 0x8f95, 0xb0 => 0x56ed, + 0xb1 => 0x5458, 0xb2 => 0x5706, 0xb3 => 0x733f, 0xb4 => 0x6e90, + 0xb5 => 0x7f18, 0xb6 => 0x8fdc, 0xb7 => 0x82d1, 0xb8 => 0x613f, + 0xb9 => 0x6028, 0xba => 0x9662, 0xbb => 0x66f0, 0xbc => 0x7ea6, + 0xbd => 0x8d8a, 0xbe => 0x8dc3, 0xbf => 0x94a5, 0xc0 => 0x5cb3, + 0xc1 => 0x7ca4, 0xc2 => 0x6708, 0xc3 => 0x60a6, 0xc4 => 0x9605, + 0xc5 => 0x8018, 0xc6 => 0x4e91, 0xc7 => 0x90e7, 0xc8 => 0x5300, + 0xc9 => 0x9668, 0xca => 0x5141, 0xcb => 0x8fd0, 0xcc => 0x8574, + 0xcd => 0x915d, 0xce => 0x6655, 0xcf => 0x97f5, 0xd0 => 0x5b55, + 0xd1 => 0x531d, 0xd2 => 0x7838, 0xd3 => 0x6742, 0xd4 => 0x683d, + 0xd5 => 0x54c9, 0xd6 => 0x707e, 0xd7 => 0x5bb0, 0xd8 => 0x8f7d, + 0xd9 => 0x518d, 0xda => 0x5728, 0xdb => 0x54b1, 0xdc => 0x6512, + 0xdd => 0x6682, 0xde => 0x8d5e, 0xdf => 0x8d43, 0xe0 => 0x810f, + 0xe1 => 0x846c, 0xe2 => 0x906d, 0xe3 => 0x7cdf, 0xe4 => 0x51ff, + 0xe5 => 0x85fb, 0xe6 => 0x67a3, 0xe7 => 0x65e9, 0xe8 => 0x6fa1, + 0xe9 => 0x86a4, 0xea => 0x8e81, 0xeb => 0x566a, 0xec => 0x9020, + 0xed => 0x7682, 0xee => 0x7076, 0xef => 0x71e5, 0xf0 => 0x8d23, + 0xf1 => 0x62e9, 0xf2 => 0x5219, 0xf3 => 0x6cfd, 0xf4 => 0x8d3c, + 0xf5 => 0x600e, 0xf6 => 0x589e, 0xf7 => 0x618e, 0xf8 => 0x66fe, + 0xf9 => 0x8d60, 0xfa => 0x624e, 0xfb => 0x55b3, 0xfc => 0x6e23, + 0xfd => 0x672d, 0xfe => 0x8f67, + }, + 0xd5 => { + 0xa1 => 0x94e1, 0xa2 => 0x95f8, 0xa3 => 0x7728, 0xa4 => 0x6805, + 0xa5 => 0x69a8, 0xa6 => 0x548b, 0xa7 => 0x4e4d, 0xa8 => 0x70b8, + 0xa9 => 0x8bc8, 0xaa => 0x6458, 0xab => 0x658b, 0xac => 0x5b85, + 0xad => 0x7a84, 0xae => 0x503a, 0xaf => 0x5be8, 0xb0 => 0x77bb, + 0xb1 => 0x6be1, 0xb2 => 0x8a79, 0xb3 => 0x7c98, 0xb4 => 0x6cbe, + 0xb5 => 0x76cf, 0xb6 => 0x65a9, 0xb7 => 0x8f97, 0xb8 => 0x5d2d, + 0xb9 => 0x5c55, 0xba => 0x8638, 0xbb => 0x6808, 0xbc => 0x5360, + 0xbd => 0x6218, 0xbe => 0x7ad9, 0xbf => 0x6e5b, 0xc0 => 0x7efd, + 0xc1 => 0x6a1f, 0xc2 => 0x7ae0, 0xc3 => 0x5f70, 0xc4 => 0x6f33, + 0xc5 => 0x5f20, 0xc6 => 0x638c, 0xc7 => 0x6da8, 0xc8 => 0x6756, + 0xc9 => 0x4e08, 0xca => 0x5e10, 0xcb => 0x8d26, 0xcc => 0x4ed7, + 0xcd => 0x80c0, 0xce => 0x7634, 0xcf => 0x969c, 0xd0 => 0x62db, + 0xd1 => 0x662d, 0xd2 => 0x627e, 0xd3 => 0x6cbc, 0xd4 => 0x8d75, + 0xd5 => 0x7167, 0xd6 => 0x7f69, 0xd7 => 0x5146, 0xd8 => 0x8087, + 0xd9 => 0x53ec, 0xda => 0x906e, 0xdb => 0x6298, 0xdc => 0x54f2, + 0xdd => 0x86f0, 0xde => 0x8f99, 0xdf => 0x8005, 0xe0 => 0x9517, + 0xe1 => 0x8517, 0xe2 => 0x8fd9, 0xe3 => 0x6d59, 0xe4 => 0x73cd, + 0xe5 => 0x659f, 0xe6 => 0x771f, 0xe7 => 0x7504, 0xe8 => 0x7827, + 0xe9 => 0x81fb, 0xea => 0x8d1e, 0xeb => 0x9488, 0xec => 0x4fa6, + 0xed => 0x6795, 0xee => 0x75b9, 0xef => 0x8bca, 0xf0 => 0x9707, + 0xf1 => 0x632f, 0xf2 => 0x9547, 0xf3 => 0x9635, 0xf4 => 0x84b8, + 0xf5 => 0x6323, 0xf6 => 0x7741, 0xf7 => 0x5f81, 0xf8 => 0x72f0, + 0xf9 => 0x4e89, 0xfa => 0x6014, 0xfb => 0x6574, 0xfc => 0x62ef, + 0xfd => 0x6b63, 0xfe => 0x653f, + }, + 0xd6 => { + 0xa1 => 0x5e27, 0xa2 => 0x75c7, 0xa3 => 0x90d1, 0xa4 => 0x8bc1, + 0xa5 => 0x829d, 0xa6 => 0x679d, 0xa7 => 0x652f, 0xa8 => 0x5431, + 0xa9 => 0x8718, 0xaa => 0x77e5, 0xab => 0x80a2, 0xac => 0x8102, + 0xad => 0x6c41, 0xae => 0x4e4b, 0xaf => 0x7ec7, 0xb0 => 0x804c, + 0xb1 => 0x76f4, 0xb2 => 0x690d, 0xb3 => 0x6b96, 0xb4 => 0x6267, + 0xb5 => 0x503c, 0xb6 => 0x4f84, 0xb7 => 0x5740, 0xb8 => 0x6307, + 0xb9 => 0x6b62, 0xba => 0x8dbe, 0xbb => 0x53ea, 0xbc => 0x65e8, + 0xbd => 0x7eb8, 0xbe => 0x5fd7, 0xbf => 0x631a, 0xc0 => 0x63b7, + 0xc1 => 0x81f3, 0xc2 => 0x81f4, 0xc3 => 0x7f6e, 0xc4 => 0x5e1c, + 0xc5 => 0x5cd9, 0xc6 => 0x5236, 0xc7 => 0x667a, 0xc8 => 0x79e9, + 0xc9 => 0x7a1a, 0xca => 0x8d28, 0xcb => 0x7099, 0xcc => 0x75d4, + 0xcd => 0x6ede, 0xce => 0x6cbb, 0xcf => 0x7a92, 0xd0 => 0x4e2d, + 0xd1 => 0x76c5, 0xd2 => 0x5fe0, 0xd3 => 0x949f, 0xd4 => 0x8877, + 0xd5 => 0x7ec8, 0xd6 => 0x79cd, 0xd7 => 0x80bf, 0xd8 => 0x91cd, + 0xd9 => 0x4ef2, 0xda => 0x4f17, 0xdb => 0x821f, 0xdc => 0x5468, + 0xdd => 0x5dde, 0xde => 0x6d32, 0xdf => 0x8bcc, 0xe0 => 0x7ca5, + 0xe1 => 0x8f74, 0xe2 => 0x8098, 0xe3 => 0x5e1a, 0xe4 => 0x5492, + 0xe5 => 0x76b1, 0xe6 => 0x5b99, 0xe7 => 0x663c, 0xe8 => 0x9aa4, + 0xe9 => 0x73e0, 0xea => 0x682a, 0xeb => 0x86db, 0xec => 0x6731, + 0xed => 0x732a, 0xee => 0x8bf8, 0xef => 0x8bdb, 0xf0 => 0x9010, + 0xf1 => 0x7af9, 0xf2 => 0x70db, 0xf3 => 0x716e, 0xf4 => 0x62c4, + 0xf5 => 0x77a9, 0xf6 => 0x5631, 0xf7 => 0x4e3b, 0xf8 => 0x8457, + 0xf9 => 0x67f1, 0xfa => 0x52a9, 0xfb => 0x86c0, 0xfc => 0x8d2e, + 0xfd => 0x94f8, 0xfe => 0x7b51, + }, + 0xd7 => { + 0xa1 => 0x4f4f, 0xa2 => 0x6ce8, 0xa3 => 0x795d, 0xa4 => 0x9a7b, + 0xa5 => 0x6293, 0xa6 => 0x722a, 0xa7 => 0x62fd, 0xa8 => 0x4e13, + 0xa9 => 0x7816, 0xaa => 0x8f6c, 0xab => 0x64b0, 0xac => 0x8d5a, + 0xad => 0x7bc6, 0xae => 0x6869, 0xaf => 0x5e84, 0xb0 => 0x88c5, + 0xb1 => 0x5986, 0xb2 => 0x649e, 0xb3 => 0x58ee, 0xb4 => 0x72b6, + 0xb5 => 0x690e, 0xb6 => 0x9525, 0xb7 => 0x8ffd, 0xb8 => 0x8d58, + 0xb9 => 0x5760, 0xba => 0x7f00, 0xbb => 0x8c06, 0xbc => 0x51c6, + 0xbd => 0x6349, 0xbe => 0x62d9, 0xbf => 0x5353, 0xc0 => 0x684c, + 0xc1 => 0x7422, 0xc2 => 0x8301, 0xc3 => 0x914c, 0xc4 => 0x5544, + 0xc5 => 0x7740, 0xc6 => 0x707c, 0xc7 => 0x6d4a, 0xc8 => 0x5179, + 0xc9 => 0x54a8, 0xca => 0x8d44, 0xcb => 0x59ff, 0xcc => 0x6ecb, + 0xcd => 0x6dc4, 0xce => 0x5b5c, 0xcf => 0x7d2b, 0xd0 => 0x4ed4, + 0xd1 => 0x7c7d, 0xd2 => 0x6ed3, 0xd3 => 0x5b50, 0xd4 => 0x81ea, + 0xd5 => 0x6e0d, 0xd6 => 0x5b57, 0xd7 => 0x9b03, 0xd8 => 0x68d5, + 0xd9 => 0x8e2a, 0xda => 0x5b97, 0xdb => 0x7efc, 0xdc => 0x603b, + 0xdd => 0x7eb5, 0xde => 0x90b9, 0xdf => 0x8d70, 0xe0 => 0x594f, + 0xe1 => 0x63cd, 0xe2 => 0x79df, 0xe3 => 0x8db3, 0xe4 => 0x5352, + 0xe5 => 0x65cf, 0xe6 => 0x7956, 0xe7 => 0x8bc5, 0xe8 => 0x963b, + 0xe9 => 0x7ec4, 0xea => 0x94bb, 0xeb => 0x7e82, 0xec => 0x5634, + 0xed => 0x9189, 0xee => 0x6700, 0xef => 0x7f6a, 0xf0 => 0x5c0a, + 0xf1 => 0x9075, 0xf2 => 0x6628, 0xf3 => 0x5de6, 0xf4 => 0x4f50, + 0xf5 => 0x67de, 0xf6 => 0x505a, 0xf7 => 0x4f5c, 0xf8 => 0x5750, + 0xf9 => 0x5ea7, + }, + 0xd8 => { + 0xa1 => 0x4e8d, 0xa2 => 0x4e0c, 0xa3 => 0x5140, 0xa4 => 0x4e10, + 0xa5 => 0x5eff, 0xa6 => 0x5345, 0xa7 => 0x4e15, 0xa8 => 0x4e98, + 0xa9 => 0x4e1e, 0xaa => 0x9b32, 0xab => 0x5b6c, 0xac => 0x5669, + 0xad => 0x4e28, 0xae => 0x79ba, 0xaf => 0x4e3f, 0xb0 => 0x5315, + 0xb1 => 0x4e47, 0xb2 => 0x592d, 0xb3 => 0x723b, 0xb4 => 0x536e, + 0xb5 => 0x6c10, 0xb6 => 0x56df, 0xb7 => 0x80e4, 0xb8 => 0x9997, + 0xb9 => 0x6bd3, 0xba => 0x777e, 0xbb => 0x9f17, 0xbc => 0x4e36, + 0xbd => 0x4e9f, 0xbe => 0x9f10, 0xbf => 0x4e5c, 0xc0 => 0x4e69, + 0xc1 => 0x4e93, 0xc2 => 0x8288, 0xc3 => 0x5b5b, 0xc4 => 0x556c, + 0xc5 => 0x560f, 0xc6 => 0x4ec4, 0xc7 => 0x538d, 0xc8 => 0x539d, + 0xc9 => 0x53a3, 0xca => 0x53a5, 0xcb => 0x53ae, 0xcc => 0x9765, + 0xcd => 0x8d5d, 0xce => 0x531a, 0xcf => 0x53f5, 0xd0 => 0x5326, + 0xd1 => 0x532e, 0xd2 => 0x533e, 0xd3 => 0x8d5c, 0xd4 => 0x5366, + 0xd5 => 0x5363, 0xd6 => 0x5202, 0xd7 => 0x5208, 0xd8 => 0x520e, + 0xd9 => 0x522d, 0xda => 0x5233, 0xdb => 0x523f, 0xdc => 0x5240, + 0xdd => 0x524c, 0xde => 0x525e, 0xdf => 0x5261, 0xe0 => 0x525c, + 0xe1 => 0x84af, 0xe2 => 0x527d, 0xe3 => 0x5282, 0xe4 => 0x5281, + 0xe5 => 0x5290, 0xe6 => 0x5293, 0xe7 => 0x5182, 0xe8 => 0x7f54, + 0xe9 => 0x4ebb, 0xea => 0x4ec3, 0xeb => 0x4ec9, 0xec => 0x4ec2, + 0xed => 0x4ee8, 0xee => 0x4ee1, 0xef => 0x4eeb, 0xf0 => 0x4ede, + 0xf1 => 0x4f1b, 0xf2 => 0x4ef3, 0xf3 => 0x4f22, 0xf4 => 0x4f64, + 0xf5 => 0x4ef5, 0xf6 => 0x4f25, 0xf7 => 0x4f27, 0xf8 => 0x4f09, + 0xf9 => 0x4f2b, 0xfa => 0x4f5e, 0xfb => 0x4f67, 0xfc => 0x6538, + 0xfd => 0x4f5a, 0xfe => 0x4f5d, + }, + 0xd9 => { + 0xa1 => 0x4f5f, 0xa2 => 0x4f57, 0xa3 => 0x4f32, 0xa4 => 0x4f3d, + 0xa5 => 0x4f76, 0xa6 => 0x4f74, 0xa7 => 0x4f91, 0xa8 => 0x4f89, + 0xa9 => 0x4f83, 0xaa => 0x4f8f, 0xab => 0x4f7e, 0xac => 0x4f7b, + 0xad => 0x4faa, 0xae => 0x4f7c, 0xaf => 0x4fac, 0xb0 => 0x4f94, + 0xb1 => 0x4fe6, 0xb2 => 0x4fe8, 0xb3 => 0x4fea, 0xb4 => 0x4fc5, + 0xb5 => 0x4fda, 0xb6 => 0x4fe3, 0xb7 => 0x4fdc, 0xb8 => 0x4fd1, + 0xb9 => 0x4fdf, 0xba => 0x4ff8, 0xbb => 0x5029, 0xbc => 0x504c, + 0xbd => 0x4ff3, 0xbe => 0x502c, 0xbf => 0x500f, 0xc0 => 0x502e, + 0xc1 => 0x502d, 0xc2 => 0x4ffe, 0xc3 => 0x501c, 0xc4 => 0x500c, + 0xc5 => 0x5025, 0xc6 => 0x5028, 0xc7 => 0x507e, 0xc8 => 0x5043, + 0xc9 => 0x5055, 0xca => 0x5048, 0xcb => 0x504e, 0xcc => 0x506c, + 0xcd => 0x507b, 0xce => 0x50a5, 0xcf => 0x50a7, 0xd0 => 0x50a9, + 0xd1 => 0x50ba, 0xd2 => 0x50d6, 0xd3 => 0x5106, 0xd4 => 0x50ed, + 0xd5 => 0x50ec, 0xd6 => 0x50e6, 0xd7 => 0x50ee, 0xd8 => 0x5107, + 0xd9 => 0x510b, 0xda => 0x4edd, 0xdb => 0x6c3d, 0xdc => 0x4f58, + 0xdd => 0x4f65, 0xde => 0x4fce, 0xdf => 0x9fa0, 0xe0 => 0x6c46, + 0xe1 => 0x7c74, 0xe2 => 0x516e, 0xe3 => 0x5dfd, 0xe4 => 0x9ec9, + 0xe5 => 0x9998, 0xe6 => 0x5181, 0xe7 => 0x5914, 0xe8 => 0x52f9, + 0xe9 => 0x530d, 0xea => 0x8a07, 0xeb => 0x5310, 0xec => 0x51eb, + 0xed => 0x5919, 0xee => 0x5155, 0xef => 0x4ea0, 0xf0 => 0x5156, + 0xf1 => 0x4eb3, 0xf2 => 0x886e, 0xf3 => 0x88a4, 0xf4 => 0x4eb5, + 0xf5 => 0x8114, 0xf6 => 0x88d2, 0xf7 => 0x7980, 0xf8 => 0x5b34, + 0xf9 => 0x8803, 0xfa => 0x7fb8, 0xfb => 0x51ab, 0xfc => 0x51b1, + 0xfd => 0x51bd, 0xfe => 0x51bc, + }, + 0xda => { + 0xa1 => 0x51c7, 0xa2 => 0x5196, 0xa3 => 0x51a2, 0xa4 => 0x51a5, + 0xa5 => 0x8ba0, 0xa6 => 0x8ba6, 0xa7 => 0x8ba7, 0xa8 => 0x8baa, + 0xa9 => 0x8bb4, 0xaa => 0x8bb5, 0xab => 0x8bb7, 0xac => 0x8bc2, + 0xad => 0x8bc3, 0xae => 0x8bcb, 0xaf => 0x8bcf, 0xb0 => 0x8bce, + 0xb1 => 0x8bd2, 0xb2 => 0x8bd3, 0xb3 => 0x8bd4, 0xb4 => 0x8bd6, + 0xb5 => 0x8bd8, 0xb6 => 0x8bd9, 0xb7 => 0x8bdc, 0xb8 => 0x8bdf, + 0xb9 => 0x8be0, 0xba => 0x8be4, 0xbb => 0x8be8, 0xbc => 0x8be9, + 0xbd => 0x8bee, 0xbe => 0x8bf0, 0xbf => 0x8bf3, 0xc0 => 0x8bf6, + 0xc1 => 0x8bf9, 0xc2 => 0x8bfc, 0xc3 => 0x8bff, 0xc4 => 0x8c00, + 0xc5 => 0x8c02, 0xc6 => 0x8c04, 0xc7 => 0x8c07, 0xc8 => 0x8c0c, + 0xc9 => 0x8c0f, 0xca => 0x8c11, 0xcb => 0x8c12, 0xcc => 0x8c14, + 0xcd => 0x8c15, 0xce => 0x8c16, 0xcf => 0x8c19, 0xd0 => 0x8c1b, + 0xd1 => 0x8c18, 0xd2 => 0x8c1d, 0xd3 => 0x8c1f, 0xd4 => 0x8c20, + 0xd5 => 0x8c21, 0xd6 => 0x8c25, 0xd7 => 0x8c27, 0xd8 => 0x8c2a, + 0xd9 => 0x8c2b, 0xda => 0x8c2e, 0xdb => 0x8c2f, 0xdc => 0x8c32, + 0xdd => 0x8c33, 0xde => 0x8c35, 0xdf => 0x8c36, 0xe0 => 0x5369, + 0xe1 => 0x537a, 0xe2 => 0x961d, 0xe3 => 0x9622, 0xe4 => 0x9621, + 0xe5 => 0x9631, 0xe6 => 0x962a, 0xe7 => 0x963d, 0xe8 => 0x963c, + 0xe9 => 0x9642, 0xea => 0x9649, 0xeb => 0x9654, 0xec => 0x965f, + 0xed => 0x9667, 0xee => 0x966c, 0xef => 0x9672, 0xf0 => 0x9674, + 0xf1 => 0x9688, 0xf2 => 0x968d, 0xf3 => 0x9697, 0xf4 => 0x96b0, + 0xf5 => 0x9097, 0xf6 => 0x909b, 0xf7 => 0x909d, 0xf8 => 0x9099, + 0xf9 => 0x90ac, 0xfa => 0x90a1, 0xfb => 0x90b4, 0xfc => 0x90b3, + 0xfd => 0x90b6, 0xfe => 0x90ba, + }, + 0xdb => { + 0xa1 => 0x90b8, 0xa2 => 0x90b0, 0xa3 => 0x90cf, 0xa4 => 0x90c5, + 0xa5 => 0x90be, 0xa6 => 0x90d0, 0xa7 => 0x90c4, 0xa8 => 0x90c7, + 0xa9 => 0x90d3, 0xaa => 0x90e6, 0xab => 0x90e2, 0xac => 0x90dc, + 0xad => 0x90d7, 0xae => 0x90db, 0xaf => 0x90eb, 0xb0 => 0x90ef, + 0xb1 => 0x90fe, 0xb2 => 0x9104, 0xb3 => 0x9122, 0xb4 => 0x911e, + 0xb5 => 0x9123, 0xb6 => 0x9131, 0xb7 => 0x912f, 0xb8 => 0x9139, + 0xb9 => 0x9143, 0xba => 0x9146, 0xbb => 0x520d, 0xbc => 0x5942, + 0xbd => 0x52a2, 0xbe => 0x52ac, 0xbf => 0x52ad, 0xc0 => 0x52be, + 0xc1 => 0x54ff, 0xc2 => 0x52d0, 0xc3 => 0x52d6, 0xc4 => 0x52f0, + 0xc5 => 0x53df, 0xc6 => 0x71ee, 0xc7 => 0x77cd, 0xc8 => 0x5ef4, + 0xc9 => 0x51f5, 0xca => 0x51fc, 0xcb => 0x9b2f, 0xcc => 0x53b6, + 0xcd => 0x5f01, 0xce => 0x755a, 0xcf => 0x5def, 0xd0 => 0x574c, + 0xd1 => 0x57a9, 0xd2 => 0x57a1, 0xd3 => 0x587e, 0xd4 => 0x58bc, + 0xd5 => 0x58c5, 0xd6 => 0x58d1, 0xd7 => 0x5729, 0xd8 => 0x572c, + 0xd9 => 0x572a, 0xda => 0x5733, 0xdb => 0x5739, 0xdc => 0x572e, + 0xdd => 0x572f, 0xde => 0x575c, 0xdf => 0x573b, 0xe0 => 0x5742, + 0xe1 => 0x5769, 0xe2 => 0x5785, 0xe3 => 0x576b, 0xe4 => 0x5786, + 0xe5 => 0x577c, 0xe6 => 0x577b, 0xe7 => 0x5768, 0xe8 => 0x576d, + 0xe9 => 0x5776, 0xea => 0x5773, 0xeb => 0x57ad, 0xec => 0x57a4, + 0xed => 0x578c, 0xee => 0x57b2, 0xef => 0x57cf, 0xf0 => 0x57a7, + 0xf1 => 0x57b4, 0xf2 => 0x5793, 0xf3 => 0x57a0, 0xf4 => 0x57d5, + 0xf5 => 0x57d8, 0xf6 => 0x57da, 0xf7 => 0x57d9, 0xf8 => 0x57d2, + 0xf9 => 0x57b8, 0xfa => 0x57f4, 0xfb => 0x57ef, 0xfc => 0x57f8, + 0xfd => 0x57e4, 0xfe => 0x57dd, + }, + 0xdc => { + 0xa1 => 0x580b, 0xa2 => 0x580d, 0xa3 => 0x57fd, 0xa4 => 0x57ed, + 0xa5 => 0x5800, 0xa6 => 0x581e, 0xa7 => 0x5819, 0xa8 => 0x5844, + 0xa9 => 0x5820, 0xaa => 0x5865, 0xab => 0x586c, 0xac => 0x5881, + 0xad => 0x5889, 0xae => 0x589a, 0xaf => 0x5880, 0xb0 => 0x99a8, + 0xb1 => 0x9f19, 0xb2 => 0x61ff, 0xb3 => 0x8279, 0xb4 => 0x827d, + 0xb5 => 0x827f, 0xb6 => 0x828f, 0xb7 => 0x828a, 0xb8 => 0x82a8, + 0xb9 => 0x8284, 0xba => 0x828e, 0xbb => 0x8291, 0xbc => 0x8297, + 0xbd => 0x8299, 0xbe => 0x82ab, 0xbf => 0x82b8, 0xc0 => 0x82be, + 0xc1 => 0x82b0, 0xc2 => 0x82c8, 0xc3 => 0x82ca, 0xc4 => 0x82e3, + 0xc5 => 0x8298, 0xc6 => 0x82b7, 0xc7 => 0x82ae, 0xc8 => 0x82cb, + 0xc9 => 0x82cc, 0xca => 0x82c1, 0xcb => 0x82a9, 0xcc => 0x82b4, + 0xcd => 0x82a1, 0xce => 0x82aa, 0xcf => 0x829f, 0xd0 => 0x82c4, + 0xd1 => 0x82ce, 0xd2 => 0x82a4, 0xd3 => 0x82e1, 0xd4 => 0x8309, + 0xd5 => 0x82f7, 0xd6 => 0x82e4, 0xd7 => 0x830f, 0xd8 => 0x8307, + 0xd9 => 0x82dc, 0xda => 0x82f4, 0xdb => 0x82d2, 0xdc => 0x82d8, + 0xdd => 0x830c, 0xde => 0x82fb, 0xdf => 0x82d3, 0xe0 => 0x8311, + 0xe1 => 0x831a, 0xe2 => 0x8306, 0xe3 => 0x8314, 0xe4 => 0x8315, + 0xe5 => 0x82e0, 0xe6 => 0x82d5, 0xe7 => 0x831c, 0xe8 => 0x8351, + 0xe9 => 0x835b, 0xea => 0x835c, 0xeb => 0x8308, 0xec => 0x8392, + 0xed => 0x833c, 0xee => 0x8334, 0xef => 0x8331, 0xf0 => 0x839b, + 0xf1 => 0x835e, 0xf2 => 0x832f, 0xf3 => 0x834f, 0xf4 => 0x8347, + 0xf5 => 0x8343, 0xf6 => 0x835f, 0xf7 => 0x8340, 0xf8 => 0x8317, + 0xf9 => 0x8360, 0xfa => 0x832d, 0xfb => 0x833a, 0xfc => 0x8333, + 0xfd => 0x8366, 0xfe => 0x8365, + }, + 0xdd => { + 0xa1 => 0x8368, 0xa2 => 0x831b, 0xa3 => 0x8369, 0xa4 => 0x836c, + 0xa5 => 0x836a, 0xa6 => 0x836d, 0xa7 => 0x836e, 0xa8 => 0x83b0, + 0xa9 => 0x8378, 0xaa => 0x83b3, 0xab => 0x83b4, 0xac => 0x83a0, + 0xad => 0x83aa, 0xae => 0x8393, 0xaf => 0x839c, 0xb0 => 0x8385, + 0xb1 => 0x837c, 0xb2 => 0x83b6, 0xb3 => 0x83a9, 0xb4 => 0x837d, + 0xb5 => 0x83b8, 0xb6 => 0x837b, 0xb7 => 0x8398, 0xb8 => 0x839e, + 0xb9 => 0x83a8, 0xba => 0x83ba, 0xbb => 0x83bc, 0xbc => 0x83c1, + 0xbd => 0x8401, 0xbe => 0x83e5, 0xbf => 0x83d8, 0xc0 => 0x5807, + 0xc1 => 0x8418, 0xc2 => 0x840b, 0xc3 => 0x83dd, 0xc4 => 0x83fd, + 0xc5 => 0x83d6, 0xc6 => 0x841c, 0xc7 => 0x8438, 0xc8 => 0x8411, + 0xc9 => 0x8406, 0xca => 0x83d4, 0xcb => 0x83df, 0xcc => 0x840f, + 0xcd => 0x8403, 0xce => 0x83f8, 0xcf => 0x83f9, 0xd0 => 0x83ea, + 0xd1 => 0x83c5, 0xd2 => 0x83c0, 0xd3 => 0x8426, 0xd4 => 0x83f0, + 0xd5 => 0x83e1, 0xd6 => 0x845c, 0xd7 => 0x8451, 0xd8 => 0x845a, + 0xd9 => 0x8459, 0xda => 0x8473, 0xdb => 0x8487, 0xdc => 0x8488, + 0xdd => 0x847a, 0xde => 0x8489, 0xdf => 0x8478, 0xe0 => 0x843c, + 0xe1 => 0x8446, 0xe2 => 0x8469, 0xe3 => 0x8476, 0xe4 => 0x848c, + 0xe5 => 0x848e, 0xe6 => 0x8431, 0xe7 => 0x846d, 0xe8 => 0x84c1, + 0xe9 => 0x84cd, 0xea => 0x84d0, 0xeb => 0x84e6, 0xec => 0x84bd, + 0xed => 0x84d3, 0xee => 0x84ca, 0xef => 0x84bf, 0xf0 => 0x84ba, + 0xf1 => 0x84e0, 0xf2 => 0x84a1, 0xf3 => 0x84b9, 0xf4 => 0x84b4, + 0xf5 => 0x8497, 0xf6 => 0x84e5, 0xf7 => 0x84e3, 0xf8 => 0x850c, + 0xf9 => 0x750d, 0xfa => 0x8538, 0xfb => 0x84f0, 0xfc => 0x8539, + 0xfd => 0x851f, 0xfe => 0x853a, + }, + 0xde => { + 0xa1 => 0x8556, 0xa2 => 0x853b, 0xa3 => 0x84ff, 0xa4 => 0x84fc, + 0xa5 => 0x8559, 0xa6 => 0x8548, 0xa7 => 0x8568, 0xa8 => 0x8564, + 0xa9 => 0x855e, 0xaa => 0x857a, 0xab => 0x77a2, 0xac => 0x8543, + 0xad => 0x8572, 0xae => 0x857b, 0xaf => 0x85a4, 0xb0 => 0x85a8, + 0xb1 => 0x8587, 0xb2 => 0x858f, 0xb3 => 0x8579, 0xb4 => 0x85ae, + 0xb5 => 0x859c, 0xb6 => 0x8585, 0xb7 => 0x85b9, 0xb8 => 0x85b7, + 0xb9 => 0x85b0, 0xba => 0x85d3, 0xbb => 0x85c1, 0xbc => 0x85dc, + 0xbd => 0x85ff, 0xbe => 0x8627, 0xbf => 0x8605, 0xc0 => 0x8629, + 0xc1 => 0x8616, 0xc2 => 0x863c, 0xc3 => 0x5efe, 0xc4 => 0x5f08, + 0xc5 => 0x593c, 0xc6 => 0x5941, 0xc7 => 0x8037, 0xc8 => 0x5955, + 0xc9 => 0x595a, 0xca => 0x5958, 0xcb => 0x530f, 0xcc => 0x5c22, + 0xcd => 0x5c25, 0xce => 0x5c2c, 0xcf => 0x5c34, 0xd0 => 0x624c, + 0xd1 => 0x626a, 0xd2 => 0x629f, 0xd3 => 0x62bb, 0xd4 => 0x62ca, + 0xd5 => 0x62da, 0xd6 => 0x62d7, 0xd7 => 0x62ee, 0xd8 => 0x6322, + 0xd9 => 0x62f6, 0xda => 0x6339, 0xdb => 0x634b, 0xdc => 0x6343, + 0xdd => 0x63ad, 0xde => 0x63f6, 0xdf => 0x6371, 0xe0 => 0x637a, + 0xe1 => 0x638e, 0xe2 => 0x63b4, 0xe3 => 0x636d, 0xe4 => 0x63ac, + 0xe5 => 0x638a, 0xe6 => 0x6369, 0xe7 => 0x63ae, 0xe8 => 0x63bc, + 0xe9 => 0x63f2, 0xea => 0x63f8, 0xeb => 0x63e0, 0xec => 0x63ff, + 0xed => 0x63c4, 0xee => 0x63de, 0xef => 0x63ce, 0xf0 => 0x6452, + 0xf1 => 0x63c6, 0xf2 => 0x63be, 0xf3 => 0x6445, 0xf4 => 0x6441, + 0xf5 => 0x640b, 0xf6 => 0x641b, 0xf7 => 0x6420, 0xf8 => 0x640c, + 0xf9 => 0x6426, 0xfa => 0x6421, 0xfb => 0x645e, 0xfc => 0x6484, + 0xfd => 0x646d, 0xfe => 0x6496, + }, + 0xdf => { + 0xa1 => 0x647a, 0xa2 => 0x64b7, 0xa3 => 0x64b8, 0xa4 => 0x6499, + 0xa5 => 0x64ba, 0xa6 => 0x64c0, 0xa7 => 0x64d0, 0xa8 => 0x64d7, + 0xa9 => 0x64e4, 0xaa => 0x64e2, 0xab => 0x6509, 0xac => 0x6525, + 0xad => 0x652e, 0xae => 0x5f0b, 0xaf => 0x5fd2, 0xb0 => 0x7519, + 0xb1 => 0x5f11, 0xb2 => 0x535f, 0xb3 => 0x53f1, 0xb4 => 0x53fd, + 0xb5 => 0x53e9, 0xb6 => 0x53e8, 0xb7 => 0x53fb, 0xb8 => 0x5412, + 0xb9 => 0x5416, 0xba => 0x5406, 0xbb => 0x544b, 0xbc => 0x5452, + 0xbd => 0x5453, 0xbe => 0x5454, 0xbf => 0x5456, 0xc0 => 0x5443, + 0xc1 => 0x5421, 0xc2 => 0x5457, 0xc3 => 0x5459, 0xc4 => 0x5423, + 0xc5 => 0x5432, 0xc6 => 0x5482, 0xc7 => 0x5494, 0xc8 => 0x5477, + 0xc9 => 0x5471, 0xca => 0x5464, 0xcb => 0x549a, 0xcc => 0x549b, + 0xcd => 0x5484, 0xce => 0x5476, 0xcf => 0x5466, 0xd0 => 0x549d, + 0xd1 => 0x54d0, 0xd2 => 0x54ad, 0xd3 => 0x54c2, 0xd4 => 0x54b4, + 0xd5 => 0x54d2, 0xd6 => 0x54a7, 0xd7 => 0x54a6, 0xd8 => 0x54d3, + 0xd9 => 0x54d4, 0xda => 0x5472, 0xdb => 0x54a3, 0xdc => 0x54d5, + 0xdd => 0x54bb, 0xde => 0x54bf, 0xdf => 0x54cc, 0xe0 => 0x54d9, + 0xe1 => 0x54da, 0xe2 => 0x54dc, 0xe3 => 0x54a9, 0xe4 => 0x54aa, + 0xe5 => 0x54a4, 0xe6 => 0x54dd, 0xe7 => 0x54cf, 0xe8 => 0x54de, + 0xe9 => 0x551b, 0xea => 0x54e7, 0xeb => 0x5520, 0xec => 0x54fd, + 0xed => 0x5514, 0xee => 0x54f3, 0xef => 0x5522, 0xf0 => 0x5523, + 0xf1 => 0x550f, 0xf2 => 0x5511, 0xf3 => 0x5527, 0xf4 => 0x552a, + 0xf5 => 0x5567, 0xf6 => 0x558f, 0xf7 => 0x55b5, 0xf8 => 0x5549, + 0xf9 => 0x556d, 0xfa => 0x5541, 0xfb => 0x5555, 0xfc => 0x553f, + 0xfd => 0x5550, 0xfe => 0x553c, + }, + 0xe0 => { + 0xa1 => 0x5537, 0xa2 => 0x5556, 0xa3 => 0x5575, 0xa4 => 0x5576, + 0xa5 => 0x5577, 0xa6 => 0x5533, 0xa7 => 0x5530, 0xa8 => 0x555c, + 0xa9 => 0x558b, 0xaa => 0x55d2, 0xab => 0x5583, 0xac => 0x55b1, + 0xad => 0x55b9, 0xae => 0x5588, 0xaf => 0x5581, 0xb0 => 0x559f, + 0xb1 => 0x557e, 0xb2 => 0x55d6, 0xb3 => 0x5591, 0xb4 => 0x557b, + 0xb5 => 0x55df, 0xb6 => 0x55bd, 0xb7 => 0x55be, 0xb8 => 0x5594, + 0xb9 => 0x5599, 0xba => 0x55ea, 0xbb => 0x55f7, 0xbc => 0x55c9, + 0xbd => 0x561f, 0xbe => 0x55d1, 0xbf => 0x55eb, 0xc0 => 0x55ec, + 0xc1 => 0x55d4, 0xc2 => 0x55e6, 0xc3 => 0x55dd, 0xc4 => 0x55c4, + 0xc5 => 0x55ef, 0xc6 => 0x55e5, 0xc7 => 0x55f2, 0xc8 => 0x55f3, + 0xc9 => 0x55cc, 0xca => 0x55cd, 0xcb => 0x55e8, 0xcc => 0x55f5, + 0xcd => 0x55e4, 0xce => 0x8f94, 0xcf => 0x561e, 0xd0 => 0x5608, + 0xd1 => 0x560c, 0xd2 => 0x5601, 0xd3 => 0x5624, 0xd4 => 0x5623, + 0xd5 => 0x55fe, 0xd6 => 0x5600, 0xd7 => 0x5627, 0xd8 => 0x562d, + 0xd9 => 0x5658, 0xda => 0x5639, 0xdb => 0x5657, 0xdc => 0x562c, + 0xdd => 0x564d, 0xde => 0x5662, 0xdf => 0x5659, 0xe0 => 0x565c, + 0xe1 => 0x564c, 0xe2 => 0x5654, 0xe3 => 0x5686, 0xe4 => 0x5664, + 0xe5 => 0x5671, 0xe6 => 0x566b, 0xe7 => 0x567b, 0xe8 => 0x567c, + 0xe9 => 0x5685, 0xea => 0x5693, 0xeb => 0x56af, 0xec => 0x56d4, + 0xed => 0x56d7, 0xee => 0x56dd, 0xef => 0x56e1, 0xf0 => 0x56f5, + 0xf1 => 0x56eb, 0xf2 => 0x56f9, 0xf3 => 0x56ff, 0xf4 => 0x5704, + 0xf5 => 0x570a, 0xf6 => 0x5709, 0xf7 => 0x571c, 0xf8 => 0x5e0f, + 0xf9 => 0x5e19, 0xfa => 0x5e14, 0xfb => 0x5e11, 0xfc => 0x5e31, + 0xfd => 0x5e3b, 0xfe => 0x5e3c, + }, + 0xe1 => { + 0xa1 => 0x5e37, 0xa2 => 0x5e44, 0xa3 => 0x5e54, 0xa4 => 0x5e5b, + 0xa5 => 0x5e5e, 0xa6 => 0x5e61, 0xa7 => 0x5c8c, 0xa8 => 0x5c7a, + 0xa9 => 0x5c8d, 0xaa => 0x5c90, 0xab => 0x5c96, 0xac => 0x5c88, + 0xad => 0x5c98, 0xae => 0x5c99, 0xaf => 0x5c91, 0xb0 => 0x5c9a, + 0xb1 => 0x5c9c, 0xb2 => 0x5cb5, 0xb3 => 0x5ca2, 0xb4 => 0x5cbd, + 0xb5 => 0x5cac, 0xb6 => 0x5cab, 0xb7 => 0x5cb1, 0xb8 => 0x5ca3, + 0xb9 => 0x5cc1, 0xba => 0x5cb7, 0xbb => 0x5cc4, 0xbc => 0x5cd2, + 0xbd => 0x5ce4, 0xbe => 0x5ccb, 0xbf => 0x5ce5, 0xc0 => 0x5d02, + 0xc1 => 0x5d03, 0xc2 => 0x5d27, 0xc3 => 0x5d26, 0xc4 => 0x5d2e, + 0xc5 => 0x5d24, 0xc6 => 0x5d1e, 0xc7 => 0x5d06, 0xc8 => 0x5d1b, + 0xc9 => 0x5d58, 0xca => 0x5d3e, 0xcb => 0x5d34, 0xcc => 0x5d3d, + 0xcd => 0x5d6c, 0xce => 0x5d5b, 0xcf => 0x5d6f, 0xd0 => 0x5d5d, + 0xd1 => 0x5d6b, 0xd2 => 0x5d4b, 0xd3 => 0x5d4a, 0xd4 => 0x5d69, + 0xd5 => 0x5d74, 0xd6 => 0x5d82, 0xd7 => 0x5d99, 0xd8 => 0x5d9d, + 0xd9 => 0x8c73, 0xda => 0x5db7, 0xdb => 0x5dc5, 0xdc => 0x5f73, + 0xdd => 0x5f77, 0xde => 0x5f82, 0xdf => 0x5f87, 0xe0 => 0x5f89, + 0xe1 => 0x5f8c, 0xe2 => 0x5f95, 0xe3 => 0x5f99, 0xe4 => 0x5f9c, + 0xe5 => 0x5fa8, 0xe6 => 0x5fad, 0xe7 => 0x5fb5, 0xe8 => 0x5fbc, + 0xe9 => 0x8862, 0xea => 0x5f61, 0xeb => 0x72ad, 0xec => 0x72b0, + 0xed => 0x72b4, 0xee => 0x72b7, 0xef => 0x72b8, 0xf0 => 0x72c3, + 0xf1 => 0x72c1, 0xf2 => 0x72ce, 0xf3 => 0x72cd, 0xf4 => 0x72d2, + 0xf5 => 0x72e8, 0xf6 => 0x72ef, 0xf7 => 0x72e9, 0xf8 => 0x72f2, + 0xf9 => 0x72f4, 0xfa => 0x72f7, 0xfb => 0x7301, 0xfc => 0x72f3, + 0xfd => 0x7303, 0xfe => 0x72fa, + }, + 0xe2 => { + 0xa1 => 0x72fb, 0xa2 => 0x7317, 0xa3 => 0x7313, 0xa4 => 0x7321, + 0xa5 => 0x730a, 0xa6 => 0x731e, 0xa7 => 0x731d, 0xa8 => 0x7315, + 0xa9 => 0x7322, 0xaa => 0x7339, 0xab => 0x7325, 0xac => 0x732c, + 0xad => 0x7338, 0xae => 0x7331, 0xaf => 0x7350, 0xb0 => 0x734d, + 0xb1 => 0x7357, 0xb2 => 0x7360, 0xb3 => 0x736c, 0xb4 => 0x736f, + 0xb5 => 0x737e, 0xb6 => 0x821b, 0xb7 => 0x5925, 0xb8 => 0x98e7, + 0xb9 => 0x5924, 0xba => 0x5902, 0xbb => 0x9963, 0xbc => 0x9967, + 0xbd => 0x9968, 0xbe => 0x9969, 0xbf => 0x996a, 0xc0 => 0x996b, + 0xc1 => 0x996c, 0xc2 => 0x9974, 0xc3 => 0x9977, 0xc4 => 0x997d, + 0xc5 => 0x9980, 0xc6 => 0x9984, 0xc7 => 0x9987, 0xc8 => 0x998a, + 0xc9 => 0x998d, 0xca => 0x9990, 0xcb => 0x9991, 0xcc => 0x9993, + 0xcd => 0x9994, 0xce => 0x9995, 0xcf => 0x5e80, 0xd0 => 0x5e91, + 0xd1 => 0x5e8b, 0xd2 => 0x5e96, 0xd3 => 0x5ea5, 0xd4 => 0x5ea0, + 0xd5 => 0x5eb9, 0xd6 => 0x5eb5, 0xd7 => 0x5ebe, 0xd8 => 0x5eb3, + 0xd9 => 0x8d53, 0xda => 0x5ed2, 0xdb => 0x5ed1, 0xdc => 0x5edb, + 0xdd => 0x5ee8, 0xde => 0x5eea, 0xdf => 0x81ba, 0xe0 => 0x5fc4, + 0xe1 => 0x5fc9, 0xe2 => 0x5fd6, 0xe3 => 0x5fcf, 0xe4 => 0x6003, + 0xe5 => 0x5fee, 0xe6 => 0x6004, 0xe7 => 0x5fe1, 0xe8 => 0x5fe4, + 0xe9 => 0x5ffe, 0xea => 0x6005, 0xeb => 0x6006, 0xec => 0x5fea, + 0xed => 0x5fed, 0xee => 0x5ff8, 0xef => 0x6019, 0xf0 => 0x6035, + 0xf1 => 0x6026, 0xf2 => 0x601b, 0xf3 => 0x600f, 0xf4 => 0x600d, + 0xf5 => 0x6029, 0xf6 => 0x602b, 0xf7 => 0x600a, 0xf8 => 0x603f, + 0xf9 => 0x6021, 0xfa => 0x6078, 0xfb => 0x6079, 0xfc => 0x607b, + 0xfd => 0x607a, 0xfe => 0x6042, + }, + 0xe3 => { + 0xa1 => 0x606a, 0xa2 => 0x607d, 0xa3 => 0x6096, 0xa4 => 0x609a, + 0xa5 => 0x60ad, 0xa6 => 0x609d, 0xa7 => 0x6083, 0xa8 => 0x6092, + 0xa9 => 0x608c, 0xaa => 0x609b, 0xab => 0x60ec, 0xac => 0x60bb, + 0xad => 0x60b1, 0xae => 0x60dd, 0xaf => 0x60d8, 0xb0 => 0x60c6, + 0xb1 => 0x60da, 0xb2 => 0x60b4, 0xb3 => 0x6120, 0xb4 => 0x6126, + 0xb5 => 0x6115, 0xb6 => 0x6123, 0xb7 => 0x60f4, 0xb8 => 0x6100, + 0xb9 => 0x610e, 0xba => 0x612b, 0xbb => 0x614a, 0xbc => 0x6175, + 0xbd => 0x61ac, 0xbe => 0x6194, 0xbf => 0x61a7, 0xc0 => 0x61b7, + 0xc1 => 0x61d4, 0xc2 => 0x61f5, 0xc3 => 0x5fdd, 0xc4 => 0x96b3, + 0xc5 => 0x95e9, 0xc6 => 0x95eb, 0xc7 => 0x95f1, 0xc8 => 0x95f3, + 0xc9 => 0x95f5, 0xca => 0x95f6, 0xcb => 0x95fc, 0xcc => 0x95fe, + 0xcd => 0x9603, 0xce => 0x9604, 0xcf => 0x9606, 0xd0 => 0x9608, + 0xd1 => 0x960a, 0xd2 => 0x960b, 0xd3 => 0x960c, 0xd4 => 0x960d, + 0xd5 => 0x960f, 0xd6 => 0x9612, 0xd7 => 0x9615, 0xd8 => 0x9616, + 0xd9 => 0x9617, 0xda => 0x9619, 0xdb => 0x961a, 0xdc => 0x4e2c, + 0xdd => 0x723f, 0xde => 0x6215, 0xdf => 0x6c35, 0xe0 => 0x6c54, + 0xe1 => 0x6c5c, 0xe2 => 0x6c4a, 0xe3 => 0x6ca3, 0xe4 => 0x6c85, + 0xe5 => 0x6c90, 0xe6 => 0x6c94, 0xe7 => 0x6c8c, 0xe8 => 0x6c68, + 0xe9 => 0x6c69, 0xea => 0x6c74, 0xeb => 0x6c76, 0xec => 0x6c86, + 0xed => 0x6ca9, 0xee => 0x6cd0, 0xef => 0x6cd4, 0xf0 => 0x6cad, + 0xf1 => 0x6cf7, 0xf2 => 0x6cf8, 0xf3 => 0x6cf1, 0xf4 => 0x6cd7, + 0xf5 => 0x6cb2, 0xf6 => 0x6ce0, 0xf7 => 0x6cd6, 0xf8 => 0x6cfa, + 0xf9 => 0x6ceb, 0xfa => 0x6cee, 0xfb => 0x6cb1, 0xfc => 0x6cd3, + 0xfd => 0x6cef, 0xfe => 0x6cfe, + }, + 0xe4 => { + 0xa1 => 0x6d39, 0xa2 => 0x6d27, 0xa3 => 0x6d0c, 0xa4 => 0x6d43, + 0xa5 => 0x6d48, 0xa6 => 0x6d07, 0xa7 => 0x6d04, 0xa8 => 0x6d19, + 0xa9 => 0x6d0e, 0xaa => 0x6d2b, 0xab => 0x6d4d, 0xac => 0x6d2e, + 0xad => 0x6d35, 0xae => 0x6d1a, 0xaf => 0x6d4f, 0xb0 => 0x6d52, + 0xb1 => 0x6d54, 0xb2 => 0x6d33, 0xb3 => 0x6d91, 0xb4 => 0x6d6f, + 0xb5 => 0x6d9e, 0xb6 => 0x6da0, 0xb7 => 0x6d5e, 0xb8 => 0x6d93, + 0xb9 => 0x6d94, 0xba => 0x6d5c, 0xbb => 0x6d60, 0xbc => 0x6d7c, + 0xbd => 0x6d63, 0xbe => 0x6e1a, 0xbf => 0x6dc7, 0xc0 => 0x6dc5, + 0xc1 => 0x6dde, 0xc2 => 0x6e0e, 0xc3 => 0x6dbf, 0xc4 => 0x6de0, + 0xc5 => 0x6e11, 0xc6 => 0x6de6, 0xc7 => 0x6ddd, 0xc8 => 0x6dd9, + 0xc9 => 0x6e16, 0xca => 0x6dab, 0xcb => 0x6e0c, 0xcc => 0x6dae, + 0xcd => 0x6e2b, 0xce => 0x6e6e, 0xcf => 0x6e4e, 0xd0 => 0x6e6b, + 0xd1 => 0x6eb2, 0xd2 => 0x6e5f, 0xd3 => 0x6e86, 0xd4 => 0x6e53, + 0xd5 => 0x6e54, 0xd6 => 0x6e32, 0xd7 => 0x6e25, 0xd8 => 0x6e44, + 0xd9 => 0x6edf, 0xda => 0x6eb1, 0xdb => 0x6e98, 0xdc => 0x6ee0, + 0xdd => 0x6f2d, 0xde => 0x6ee2, 0xdf => 0x6ea5, 0xe0 => 0x6ea7, + 0xe1 => 0x6ebd, 0xe2 => 0x6ebb, 0xe3 => 0x6eb7, 0xe4 => 0x6ed7, + 0xe5 => 0x6eb4, 0xe6 => 0x6ecf, 0xe7 => 0x6e8f, 0xe8 => 0x6ec2, + 0xe9 => 0x6e9f, 0xea => 0x6f62, 0xeb => 0x6f46, 0xec => 0x6f47, + 0xed => 0x6f24, 0xee => 0x6f15, 0xef => 0x6ef9, 0xf0 => 0x6f2f, + 0xf1 => 0x6f36, 0xf2 => 0x6f4b, 0xf3 => 0x6f74, 0xf4 => 0x6f2a, + 0xf5 => 0x6f09, 0xf6 => 0x6f29, 0xf7 => 0x6f89, 0xf8 => 0x6f8d, + 0xf9 => 0x6f8c, 0xfa => 0x6f78, 0xfb => 0x6f72, 0xfc => 0x6f7c, + 0xfd => 0x6f7a, 0xfe => 0x6fd1, + }, + 0xe5 => { + 0xa1 => 0x6fc9, 0xa2 => 0x6fa7, 0xa3 => 0x6fb9, 0xa4 => 0x6fb6, + 0xa5 => 0x6fc2, 0xa6 => 0x6fe1, 0xa7 => 0x6fee, 0xa8 => 0x6fde, + 0xa9 => 0x6fe0, 0xaa => 0x6fef, 0xab => 0x701a, 0xac => 0x7023, + 0xad => 0x701b, 0xae => 0x7039, 0xaf => 0x7035, 0xb0 => 0x704f, + 0xb1 => 0x705e, 0xb2 => 0x5b80, 0xb3 => 0x5b84, 0xb4 => 0x5b95, + 0xb5 => 0x5b93, 0xb6 => 0x5ba5, 0xb7 => 0x5bb8, 0xb8 => 0x752f, + 0xb9 => 0x9a9e, 0xba => 0x6434, 0xbb => 0x5be4, 0xbc => 0x5bee, + 0xbd => 0x8930, 0xbe => 0x5bf0, 0xbf => 0x8e47, 0xc0 => 0x8b07, + 0xc1 => 0x8fb6, 0xc2 => 0x8fd3, 0xc3 => 0x8fd5, 0xc4 => 0x8fe5, + 0xc5 => 0x8fee, 0xc6 => 0x8fe4, 0xc7 => 0x8fe9, 0xc8 => 0x8fe6, + 0xc9 => 0x8ff3, 0xca => 0x8fe8, 0xcb => 0x9005, 0xcc => 0x9004, + 0xcd => 0x900b, 0xce => 0x9026, 0xcf => 0x9011, 0xd0 => 0x900d, + 0xd1 => 0x9016, 0xd2 => 0x9021, 0xd3 => 0x9035, 0xd4 => 0x9036, + 0xd5 => 0x902d, 0xd6 => 0x902f, 0xd7 => 0x9044, 0xd8 => 0x9051, + 0xd9 => 0x9052, 0xda => 0x9050, 0xdb => 0x9068, 0xdc => 0x9058, + 0xdd => 0x9062, 0xde => 0x905b, 0xdf => 0x66b9, 0xe0 => 0x9074, + 0xe1 => 0x907d, 0xe2 => 0x9082, 0xe3 => 0x9088, 0xe4 => 0x9083, + 0xe5 => 0x908b, 0xe6 => 0x5f50, 0xe7 => 0x5f57, 0xe8 => 0x5f56, + 0xe9 => 0x5f58, 0xea => 0x5c3b, 0xeb => 0x54ab, 0xec => 0x5c50, + 0xed => 0x5c59, 0xee => 0x5b71, 0xef => 0x5c63, 0xf0 => 0x5c66, + 0xf1 => 0x7fbc, 0xf2 => 0x5f2a, 0xf3 => 0x5f29, 0xf4 => 0x5f2d, + 0xf5 => 0x8274, 0xf6 => 0x5f3c, 0xf7 => 0x9b3b, 0xf8 => 0x5c6e, + 0xf9 => 0x5981, 0xfa => 0x5983, 0xfb => 0x598d, 0xfc => 0x59a9, + 0xfd => 0x59aa, 0xfe => 0x59a3, + }, + 0xe6 => { + 0xa1 => 0x5997, 0xa2 => 0x59ca, 0xa3 => 0x59ab, 0xa4 => 0x599e, + 0xa5 => 0x59a4, 0xa6 => 0x59d2, 0xa7 => 0x59b2, 0xa8 => 0x59af, + 0xa9 => 0x59d7, 0xaa => 0x59be, 0xab => 0x5a05, 0xac => 0x5a06, + 0xad => 0x59dd, 0xae => 0x5a08, 0xaf => 0x59e3, 0xb0 => 0x59d8, + 0xb1 => 0x59f9, 0xb2 => 0x5a0c, 0xb3 => 0x5a09, 0xb4 => 0x5a32, + 0xb5 => 0x5a34, 0xb6 => 0x5a11, 0xb7 => 0x5a23, 0xb8 => 0x5a13, + 0xb9 => 0x5a40, 0xba => 0x5a67, 0xbb => 0x5a4a, 0xbc => 0x5a55, + 0xbd => 0x5a3c, 0xbe => 0x5a62, 0xbf => 0x5a75, 0xc0 => 0x80ec, + 0xc1 => 0x5aaa, 0xc2 => 0x5a9b, 0xc3 => 0x5a77, 0xc4 => 0x5a7a, + 0xc5 => 0x5abe, 0xc6 => 0x5aeb, 0xc7 => 0x5ab2, 0xc8 => 0x5ad2, + 0xc9 => 0x5ad4, 0xca => 0x5ab8, 0xcb => 0x5ae0, 0xcc => 0x5ae3, + 0xcd => 0x5af1, 0xce => 0x5ad6, 0xcf => 0x5ae6, 0xd0 => 0x5ad8, + 0xd1 => 0x5adc, 0xd2 => 0x5b09, 0xd3 => 0x5b17, 0xd4 => 0x5b16, + 0xd5 => 0x5b32, 0xd6 => 0x5b37, 0xd7 => 0x5b40, 0xd8 => 0x5c15, + 0xd9 => 0x5c1c, 0xda => 0x5b5a, 0xdb => 0x5b65, 0xdc => 0x5b73, + 0xdd => 0x5b51, 0xde => 0x5b53, 0xdf => 0x5b62, 0xe0 => 0x9a75, + 0xe1 => 0x9a77, 0xe2 => 0x9a78, 0xe3 => 0x9a7a, 0xe4 => 0x9a7f, + 0xe5 => 0x9a7d, 0xe6 => 0x9a80, 0xe7 => 0x9a81, 0xe8 => 0x9a85, + 0xe9 => 0x9a88, 0xea => 0x9a8a, 0xeb => 0x9a90, 0xec => 0x9a92, + 0xed => 0x9a93, 0xee => 0x9a96, 0xef => 0x9a98, 0xf0 => 0x9a9b, + 0xf1 => 0x9a9c, 0xf2 => 0x9a9d, 0xf3 => 0x9a9f, 0xf4 => 0x9aa0, + 0xf5 => 0x9aa2, 0xf6 => 0x9aa3, 0xf7 => 0x9aa5, 0xf8 => 0x9aa7, + 0xf9 => 0x7e9f, 0xfa => 0x7ea1, 0xfb => 0x7ea3, 0xfc => 0x7ea5, + 0xfd => 0x7ea8, 0xfe => 0x7ea9, + }, + 0xe7 => { + 0xa1 => 0x7ead, 0xa2 => 0x7eb0, 0xa3 => 0x7ebe, 0xa4 => 0x7ec0, + 0xa5 => 0x7ec1, 0xa6 => 0x7ec2, 0xa7 => 0x7ec9, 0xa8 => 0x7ecb, + 0xa9 => 0x7ecc, 0xaa => 0x7ed0, 0xab => 0x7ed4, 0xac => 0x7ed7, + 0xad => 0x7edb, 0xae => 0x7ee0, 0xaf => 0x7ee1, 0xb0 => 0x7ee8, + 0xb1 => 0x7eeb, 0xb2 => 0x7eee, 0xb3 => 0x7eef, 0xb4 => 0x7ef1, + 0xb5 => 0x7ef2, 0xb6 => 0x7f0d, 0xb7 => 0x7ef6, 0xb8 => 0x7efa, + 0xb9 => 0x7efb, 0xba => 0x7efe, 0xbb => 0x7f01, 0xbc => 0x7f02, + 0xbd => 0x7f03, 0xbe => 0x7f07, 0xbf => 0x7f08, 0xc0 => 0x7f0b, + 0xc1 => 0x7f0c, 0xc2 => 0x7f0f, 0xc3 => 0x7f11, 0xc4 => 0x7f12, + 0xc5 => 0x7f17, 0xc6 => 0x7f19, 0xc7 => 0x7f1c, 0xc8 => 0x7f1b, + 0xc9 => 0x7f1f, 0xca => 0x7f21, 0xcb => 0x7f22, 0xcc => 0x7f23, + 0xcd => 0x7f24, 0xce => 0x7f25, 0xcf => 0x7f26, 0xd0 => 0x7f27, + 0xd1 => 0x7f2a, 0xd2 => 0x7f2b, 0xd3 => 0x7f2c, 0xd4 => 0x7f2d, + 0xd5 => 0x7f2f, 0xd6 => 0x7f30, 0xd7 => 0x7f31, 0xd8 => 0x7f32, + 0xd9 => 0x7f33, 0xda => 0x7f35, 0xdb => 0x5e7a, 0xdc => 0x757f, + 0xdd => 0x5ddb, 0xde => 0x753e, 0xdf => 0x9095, 0xe0 => 0x738e, + 0xe1 => 0x7391, 0xe2 => 0x73ae, 0xe3 => 0x73a2, 0xe4 => 0x739f, + 0xe5 => 0x73cf, 0xe6 => 0x73c2, 0xe7 => 0x73d1, 0xe8 => 0x73b7, + 0xe9 => 0x73b3, 0xea => 0x73c0, 0xeb => 0x73c9, 0xec => 0x73c8, + 0xed => 0x73e5, 0xee => 0x73d9, 0xef => 0x987c, 0xf0 => 0x740a, + 0xf1 => 0x73e9, 0xf2 => 0x73e7, 0xf3 => 0x73de, 0xf4 => 0x73ba, + 0xf5 => 0x73f2, 0xf6 => 0x740f, 0xf7 => 0x742a, 0xf8 => 0x745b, + 0xf9 => 0x7426, 0xfa => 0x7425, 0xfb => 0x7428, 0xfc => 0x7430, + 0xfd => 0x742e, 0xfe => 0x742c, + }, + 0xe8 => { + 0xa1 => 0x741b, 0xa2 => 0x741a, 0xa3 => 0x7441, 0xa4 => 0x745c, + 0xa5 => 0x7457, 0xa6 => 0x7455, 0xa7 => 0x7459, 0xa8 => 0x7477, + 0xa9 => 0x746d, 0xaa => 0x747e, 0xab => 0x749c, 0xac => 0x748e, + 0xad => 0x7480, 0xae => 0x7481, 0xaf => 0x7487, 0xb0 => 0x748b, + 0xb1 => 0x749e, 0xb2 => 0x74a8, 0xb3 => 0x74a9, 0xb4 => 0x7490, + 0xb5 => 0x74a7, 0xb6 => 0x74d2, 0xb7 => 0x74ba, 0xb8 => 0x97ea, + 0xb9 => 0x97eb, 0xba => 0x97ec, 0xbb => 0x674c, 0xbc => 0x6753, + 0xbd => 0x675e, 0xbe => 0x6748, 0xbf => 0x6769, 0xc0 => 0x67a5, + 0xc1 => 0x6787, 0xc2 => 0x676a, 0xc3 => 0x6773, 0xc4 => 0x6798, + 0xc5 => 0x67a7, 0xc6 => 0x6775, 0xc7 => 0x67a8, 0xc8 => 0x679e, + 0xc9 => 0x67ad, 0xca => 0x678b, 0xcb => 0x6777, 0xcc => 0x677c, + 0xcd => 0x67f0, 0xce => 0x6809, 0xcf => 0x67d8, 0xd0 => 0x680a, + 0xd1 => 0x67e9, 0xd2 => 0x67b0, 0xd3 => 0x680c, 0xd4 => 0x67d9, + 0xd5 => 0x67b5, 0xd6 => 0x67da, 0xd7 => 0x67b3, 0xd8 => 0x67dd, + 0xd9 => 0x6800, 0xda => 0x67c3, 0xdb => 0x67b8, 0xdc => 0x67e2, + 0xdd => 0x680e, 0xde => 0x67c1, 0xdf => 0x67fd, 0xe0 => 0x6832, + 0xe1 => 0x6833, 0xe2 => 0x6860, 0xe3 => 0x6861, 0xe4 => 0x684e, + 0xe5 => 0x6862, 0xe6 => 0x6844, 0xe7 => 0x6864, 0xe8 => 0x6883, + 0xe9 => 0x681d, 0xea => 0x6855, 0xeb => 0x6866, 0xec => 0x6841, + 0xed => 0x6867, 0xee => 0x6840, 0xef => 0x683e, 0xf0 => 0x684a, + 0xf1 => 0x6849, 0xf2 => 0x6829, 0xf3 => 0x68b5, 0xf4 => 0x688f, + 0xf5 => 0x6874, 0xf6 => 0x6877, 0xf7 => 0x6893, 0xf8 => 0x686b, + 0xf9 => 0x68c2, 0xfa => 0x696e, 0xfb => 0x68fc, 0xfc => 0x691f, + 0xfd => 0x6920, 0xfe => 0x68f9, + }, + 0xe9 => { + 0xa1 => 0x6924, 0xa2 => 0x68f0, 0xa3 => 0x690b, 0xa4 => 0x6901, + 0xa5 => 0x6957, 0xa6 => 0x68e3, 0xa7 => 0x6910, 0xa8 => 0x6971, + 0xa9 => 0x6939, 0xaa => 0x6960, 0xab => 0x6942, 0xac => 0x695d, + 0xad => 0x6984, 0xae => 0x696b, 0xaf => 0x6980, 0xb0 => 0x6998, + 0xb1 => 0x6978, 0xb2 => 0x6934, 0xb3 => 0x69cc, 0xb4 => 0x6987, + 0xb5 => 0x6988, 0xb6 => 0x69ce, 0xb7 => 0x6989, 0xb8 => 0x6966, + 0xb9 => 0x6963, 0xba => 0x6979, 0xbb => 0x699b, 0xbc => 0x69a7, + 0xbd => 0x69bb, 0xbe => 0x69ab, 0xbf => 0x69ad, 0xc0 => 0x69d4, + 0xc1 => 0x69b1, 0xc2 => 0x69c1, 0xc3 => 0x69ca, 0xc4 => 0x69df, + 0xc5 => 0x6995, 0xc6 => 0x69e0, 0xc7 => 0x698d, 0xc8 => 0x69ff, + 0xc9 => 0x6a2f, 0xca => 0x69ed, 0xcb => 0x6a17, 0xcc => 0x6a18, + 0xcd => 0x6a65, 0xce => 0x69f2, 0xcf => 0x6a44, 0xd0 => 0x6a3e, + 0xd1 => 0x6aa0, 0xd2 => 0x6a50, 0xd3 => 0x6a5b, 0xd4 => 0x6a35, + 0xd5 => 0x6a8e, 0xd6 => 0x6a79, 0xd7 => 0x6a3d, 0xd8 => 0x6a28, + 0xd9 => 0x6a58, 0xda => 0x6a7c, 0xdb => 0x6a91, 0xdc => 0x6a90, + 0xdd => 0x6aa9, 0xde => 0x6a97, 0xdf => 0x6aab, 0xe0 => 0x7337, + 0xe1 => 0x7352, 0xe2 => 0x6b81, 0xe3 => 0x6b82, 0xe4 => 0x6b87, + 0xe5 => 0x6b84, 0xe6 => 0x6b92, 0xe7 => 0x6b93, 0xe8 => 0x6b8d, + 0xe9 => 0x6b9a, 0xea => 0x6b9b, 0xeb => 0x6ba1, 0xec => 0x6baa, + 0xed => 0x8f6b, 0xee => 0x8f6d, 0xef => 0x8f71, 0xf0 => 0x8f72, + 0xf1 => 0x8f73, 0xf2 => 0x8f75, 0xf3 => 0x8f76, 0xf4 => 0x8f78, + 0xf5 => 0x8f77, 0xf6 => 0x8f79, 0xf7 => 0x8f7a, 0xf8 => 0x8f7c, + 0xf9 => 0x8f7e, 0xfa => 0x8f81, 0xfb => 0x8f82, 0xfc => 0x8f84, + 0xfd => 0x8f87, 0xfe => 0x8f8b, + }, + 0xea => { + 0xa1 => 0x8f8d, 0xa2 => 0x8f8e, 0xa3 => 0x8f8f, 0xa4 => 0x8f98, + 0xa5 => 0x8f9a, 0xa6 => 0x8ece, 0xa7 => 0x620b, 0xa8 => 0x6217, + 0xa9 => 0x621b, 0xaa => 0x621f, 0xab => 0x6222, 0xac => 0x6221, + 0xad => 0x6225, 0xae => 0x6224, 0xaf => 0x622c, 0xb0 => 0x81e7, + 0xb1 => 0x74ef, 0xb2 => 0x74f4, 0xb3 => 0x74ff, 0xb4 => 0x750f, + 0xb5 => 0x7511, 0xb6 => 0x7513, 0xb7 => 0x6534, 0xb8 => 0x65ee, + 0xb9 => 0x65ef, 0xba => 0x65f0, 0xbb => 0x660a, 0xbc => 0x6619, + 0xbd => 0x6772, 0xbe => 0x6603, 0xbf => 0x6615, 0xc0 => 0x6600, + 0xc1 => 0x7085, 0xc2 => 0x66f7, 0xc3 => 0x661d, 0xc4 => 0x6634, + 0xc5 => 0x6631, 0xc6 => 0x6636, 0xc7 => 0x6635, 0xc8 => 0x8006, + 0xc9 => 0x665f, 0xca => 0x6654, 0xcb => 0x6641, 0xcc => 0x664f, + 0xcd => 0x6656, 0xce => 0x6661, 0xcf => 0x6657, 0xd0 => 0x6677, + 0xd1 => 0x6684, 0xd2 => 0x668c, 0xd3 => 0x66a7, 0xd4 => 0x669d, + 0xd5 => 0x66be, 0xd6 => 0x66db, 0xd7 => 0x66dc, 0xd8 => 0x66e6, + 0xd9 => 0x66e9, 0xda => 0x8d32, 0xdb => 0x8d33, 0xdc => 0x8d36, + 0xdd => 0x8d3b, 0xde => 0x8d3d, 0xdf => 0x8d40, 0xe0 => 0x8d45, + 0xe1 => 0x8d46, 0xe2 => 0x8d48, 0xe3 => 0x8d49, 0xe4 => 0x8d47, + 0xe5 => 0x8d4d, 0xe6 => 0x8d55, 0xe7 => 0x8d59, 0xe8 => 0x89c7, + 0xe9 => 0x89ca, 0xea => 0x89cb, 0xeb => 0x89cc, 0xec => 0x89ce, + 0xed => 0x89cf, 0xee => 0x89d0, 0xef => 0x89d1, 0xf0 => 0x726e, + 0xf1 => 0x729f, 0xf2 => 0x725d, 0xf3 => 0x7266, 0xf4 => 0x726f, + 0xf5 => 0x727e, 0xf6 => 0x727f, 0xf7 => 0x7284, 0xf8 => 0x728b, + 0xf9 => 0x728d, 0xfa => 0x728f, 0xfb => 0x7292, 0xfc => 0x6308, + 0xfd => 0x6332, 0xfe => 0x63b0, + }, + 0xeb => { + 0xa1 => 0x643f, 0xa2 => 0x64d8, 0xa3 => 0x8004, 0xa4 => 0x6bea, + 0xa5 => 0x6bf3, 0xa6 => 0x6bfd, 0xa7 => 0x6bf5, 0xa8 => 0x6bf9, + 0xa9 => 0x6c05, 0xaa => 0x6c07, 0xab => 0x6c06, 0xac => 0x6c0d, + 0xad => 0x6c15, 0xae => 0x6c18, 0xaf => 0x6c19, 0xb0 => 0x6c1a, + 0xb1 => 0x6c21, 0xb2 => 0x6c29, 0xb3 => 0x6c24, 0xb4 => 0x6c2a, + 0xb5 => 0x6c32, 0xb6 => 0x6535, 0xb7 => 0x6555, 0xb8 => 0x656b, + 0xb9 => 0x724d, 0xba => 0x7252, 0xbb => 0x7256, 0xbc => 0x7230, + 0xbd => 0x8662, 0xbe => 0x5216, 0xbf => 0x809f, 0xc0 => 0x809c, + 0xc1 => 0x8093, 0xc2 => 0x80bc, 0xc3 => 0x670a, 0xc4 => 0x80bd, + 0xc5 => 0x80b1, 0xc6 => 0x80ab, 0xc7 => 0x80ad, 0xc8 => 0x80b4, + 0xc9 => 0x80b7, 0xca => 0x80e7, 0xcb => 0x80e8, 0xcc => 0x80e9, + 0xcd => 0x80ea, 0xce => 0x80db, 0xcf => 0x80c2, 0xd0 => 0x80c4, + 0xd1 => 0x80d9, 0xd2 => 0x80cd, 0xd3 => 0x80d7, 0xd4 => 0x6710, + 0xd5 => 0x80dd, 0xd6 => 0x80eb, 0xd7 => 0x80f1, 0xd8 => 0x80f4, + 0xd9 => 0x80ed, 0xda => 0x810d, 0xdb => 0x810e, 0xdc => 0x80f2, + 0xdd => 0x80fc, 0xde => 0x6715, 0xdf => 0x8112, 0xe0 => 0x8c5a, + 0xe1 => 0x8136, 0xe2 => 0x811e, 0xe3 => 0x812c, 0xe4 => 0x8118, + 0xe5 => 0x8132, 0xe6 => 0x8148, 0xe7 => 0x814c, 0xe8 => 0x8153, + 0xe9 => 0x8174, 0xea => 0x8159, 0xeb => 0x815a, 0xec => 0x8171, + 0xed => 0x8160, 0xee => 0x8169, 0xef => 0x817c, 0xf0 => 0x817d, + 0xf1 => 0x816d, 0xf2 => 0x8167, 0xf3 => 0x584d, 0xf4 => 0x5ab5, + 0xf5 => 0x8188, 0xf6 => 0x8182, 0xf7 => 0x8191, 0xf8 => 0x6ed5, + 0xf9 => 0x81a3, 0xfa => 0x81aa, 0xfb => 0x81cc, 0xfc => 0x6726, + 0xfd => 0x81ca, 0xfe => 0x81bb, + }, + 0xec => { + 0xa1 => 0x81c1, 0xa2 => 0x81a6, 0xa3 => 0x6b24, 0xa4 => 0x6b37, + 0xa5 => 0x6b39, 0xa6 => 0x6b43, 0xa7 => 0x6b46, 0xa8 => 0x6b59, + 0xa9 => 0x98d1, 0xaa => 0x98d2, 0xab => 0x98d3, 0xac => 0x98d5, + 0xad => 0x98d9, 0xae => 0x98da, 0xaf => 0x6bb3, 0xb0 => 0x5f40, + 0xb1 => 0x6bc2, 0xb2 => 0x89f3, 0xb3 => 0x6590, 0xb4 => 0x9f51, + 0xb5 => 0x6593, 0xb6 => 0x65bc, 0xb7 => 0x65c6, 0xb8 => 0x65c4, + 0xb9 => 0x65c3, 0xba => 0x65cc, 0xbb => 0x65ce, 0xbc => 0x65d2, + 0xbd => 0x65d6, 0xbe => 0x7080, 0xbf => 0x709c, 0xc0 => 0x7096, + 0xc1 => 0x709d, 0xc2 => 0x70bb, 0xc3 => 0x70c0, 0xc4 => 0x70b7, + 0xc5 => 0x70ab, 0xc6 => 0x70b1, 0xc7 => 0x70e8, 0xc8 => 0x70ca, + 0xc9 => 0x7110, 0xca => 0x7113, 0xcb => 0x7116, 0xcc => 0x712f, + 0xcd => 0x7131, 0xce => 0x7173, 0xcf => 0x715c, 0xd0 => 0x7168, + 0xd1 => 0x7145, 0xd2 => 0x7172, 0xd3 => 0x714a, 0xd4 => 0x7178, + 0xd5 => 0x717a, 0xd6 => 0x7198, 0xd7 => 0x71b3, 0xd8 => 0x71b5, + 0xd9 => 0x71a8, 0xda => 0x71a0, 0xdb => 0x71e0, 0xdc => 0x71d4, + 0xdd => 0x71e7, 0xde => 0x71f9, 0xdf => 0x721d, 0xe0 => 0x7228, + 0xe1 => 0x706c, 0xe2 => 0x7118, 0xe3 => 0x7166, 0xe4 => 0x71b9, + 0xe5 => 0x623e, 0xe6 => 0x623d, 0xe7 => 0x6243, 0xe8 => 0x6248, + 0xe9 => 0x6249, 0xea => 0x793b, 0xeb => 0x7940, 0xec => 0x7946, + 0xed => 0x7949, 0xee => 0x795b, 0xef => 0x795c, 0xf0 => 0x7953, + 0xf1 => 0x795a, 0xf2 => 0x7962, 0xf3 => 0x7957, 0xf4 => 0x7960, + 0xf5 => 0x796f, 0xf6 => 0x7967, 0xf7 => 0x797a, 0xf8 => 0x7985, + 0xf9 => 0x798a, 0xfa => 0x799a, 0xfb => 0x79a7, 0xfc => 0x79b3, + 0xfd => 0x5fd1, 0xfe => 0x5fd0, + }, + 0xed => { + 0xa1 => 0x603c, 0xa2 => 0x605d, 0xa3 => 0x605a, 0xa4 => 0x6067, + 0xa5 => 0x6041, 0xa6 => 0x6059, 0xa7 => 0x6063, 0xa8 => 0x60ab, + 0xa9 => 0x6106, 0xaa => 0x610d, 0xab => 0x615d, 0xac => 0x61a9, + 0xad => 0x619d, 0xae => 0x61cb, 0xaf => 0x61d1, 0xb0 => 0x6206, + 0xb1 => 0x8080, 0xb2 => 0x807f, 0xb3 => 0x6c93, 0xb4 => 0x6cf6, + 0xb5 => 0x6dfc, 0xb6 => 0x77f6, 0xb7 => 0x77f8, 0xb8 => 0x7800, + 0xb9 => 0x7809, 0xba => 0x7817, 0xbb => 0x7818, 0xbc => 0x7811, + 0xbd => 0x65ab, 0xbe => 0x782d, 0xbf => 0x781c, 0xc0 => 0x781d, + 0xc1 => 0x7839, 0xc2 => 0x783a, 0xc3 => 0x783b, 0xc4 => 0x781f, + 0xc5 => 0x783c, 0xc6 => 0x7825, 0xc7 => 0x782c, 0xc8 => 0x7823, + 0xc9 => 0x7829, 0xca => 0x784e, 0xcb => 0x786d, 0xcc => 0x7856, + 0xcd => 0x7857, 0xce => 0x7826, 0xcf => 0x7850, 0xd0 => 0x7847, + 0xd1 => 0x784c, 0xd2 => 0x786a, 0xd3 => 0x789b, 0xd4 => 0x7893, + 0xd5 => 0x789a, 0xd6 => 0x7887, 0xd7 => 0x789c, 0xd8 => 0x78a1, + 0xd9 => 0x78a3, 0xda => 0x78b2, 0xdb => 0x78b9, 0xdc => 0x78a5, + 0xdd => 0x78d4, 0xde => 0x78d9, 0xdf => 0x78c9, 0xe0 => 0x78ec, + 0xe1 => 0x78f2, 0xe2 => 0x7905, 0xe3 => 0x78f4, 0xe4 => 0x7913, + 0xe5 => 0x7924, 0xe6 => 0x791e, 0xe7 => 0x7934, 0xe8 => 0x9f9b, + 0xe9 => 0x9ef9, 0xea => 0x9efb, 0xeb => 0x9efc, 0xec => 0x76f1, + 0xed => 0x7704, 0xee => 0x770d, 0xef => 0x76f9, 0xf0 => 0x7707, + 0xf1 => 0x7708, 0xf2 => 0x771a, 0xf3 => 0x7722, 0xf4 => 0x7719, + 0xf5 => 0x772d, 0xf6 => 0x7726, 0xf7 => 0x7735, 0xf8 => 0x7738, + 0xf9 => 0x7750, 0xfa => 0x7751, 0xfb => 0x7747, 0xfc => 0x7743, + 0xfd => 0x775a, 0xfe => 0x7768, + }, + 0xee => { + 0xa1 => 0x7762, 0xa2 => 0x7765, 0xa3 => 0x777f, 0xa4 => 0x778d, + 0xa5 => 0x777d, 0xa6 => 0x7780, 0xa7 => 0x778c, 0xa8 => 0x7791, + 0xa9 => 0x779f, 0xaa => 0x77a0, 0xab => 0x77b0, 0xac => 0x77b5, + 0xad => 0x77bd, 0xae => 0x753a, 0xaf => 0x7540, 0xb0 => 0x754e, + 0xb1 => 0x754b, 0xb2 => 0x7548, 0xb3 => 0x755b, 0xb4 => 0x7572, + 0xb5 => 0x7579, 0xb6 => 0x7583, 0xb7 => 0x7f58, 0xb8 => 0x7f61, + 0xb9 => 0x7f5f, 0xba => 0x8a48, 0xbb => 0x7f68, 0xbc => 0x7f74, + 0xbd => 0x7f71, 0xbe => 0x7f79, 0xbf => 0x7f81, 0xc0 => 0x7f7e, + 0xc1 => 0x76cd, 0xc2 => 0x76e5, 0xc3 => 0x8832, 0xc4 => 0x9485, + 0xc5 => 0x9486, 0xc6 => 0x9487, 0xc7 => 0x948b, 0xc8 => 0x948a, + 0xc9 => 0x948c, 0xca => 0x948d, 0xcb => 0x948f, 0xcc => 0x9490, + 0xcd => 0x9494, 0xce => 0x9497, 0xcf => 0x9495, 0xd0 => 0x949a, + 0xd1 => 0x949b, 0xd2 => 0x949c, 0xd3 => 0x94a3, 0xd4 => 0x94a4, + 0xd5 => 0x94ab, 0xd6 => 0x94aa, 0xd7 => 0x94ad, 0xd8 => 0x94ac, + 0xd9 => 0x94af, 0xda => 0x94b0, 0xdb => 0x94b2, 0xdc => 0x94b4, + 0xdd => 0x94b6, 0xde => 0x94b7, 0xdf => 0x94b8, 0xe0 => 0x94b9, + 0xe1 => 0x94ba, 0xe2 => 0x94bc, 0xe3 => 0x94bd, 0xe4 => 0x94bf, + 0xe5 => 0x94c4, 0xe6 => 0x94c8, 0xe7 => 0x94c9, 0xe8 => 0x94ca, + 0xe9 => 0x94cb, 0xea => 0x94cc, 0xeb => 0x94cd, 0xec => 0x94ce, + 0xed => 0x94d0, 0xee => 0x94d1, 0xef => 0x94d2, 0xf0 => 0x94d5, + 0xf1 => 0x94d6, 0xf2 => 0x94d7, 0xf3 => 0x94d9, 0xf4 => 0x94d8, + 0xf5 => 0x94db, 0xf6 => 0x94de, 0xf7 => 0x94df, 0xf8 => 0x94e0, + 0xf9 => 0x94e2, 0xfa => 0x94e4, 0xfb => 0x94e5, 0xfc => 0x94e7, + 0xfd => 0x94e8, 0xfe => 0x94ea, + }, + 0xef => { + 0xa1 => 0x94e9, 0xa2 => 0x94eb, 0xa3 => 0x94ee, 0xa4 => 0x94ef, + 0xa5 => 0x94f3, 0xa6 => 0x94f4, 0xa7 => 0x94f5, 0xa8 => 0x94f7, + 0xa9 => 0x94f9, 0xaa => 0x94fc, 0xab => 0x94fd, 0xac => 0x94ff, + 0xad => 0x9503, 0xae => 0x9502, 0xaf => 0x9506, 0xb0 => 0x9507, + 0xb1 => 0x9509, 0xb2 => 0x950a, 0xb3 => 0x950d, 0xb4 => 0x950e, + 0xb5 => 0x950f, 0xb6 => 0x9512, 0xb7 => 0x9513, 0xb8 => 0x9514, + 0xb9 => 0x9515, 0xba => 0x9516, 0xbb => 0x9518, 0xbc => 0x951b, + 0xbd => 0x951d, 0xbe => 0x951e, 0xbf => 0x951f, 0xc0 => 0x9522, + 0xc1 => 0x952a, 0xc2 => 0x952b, 0xc3 => 0x9529, 0xc4 => 0x952c, + 0xc5 => 0x9531, 0xc6 => 0x9532, 0xc7 => 0x9534, 0xc8 => 0x9536, + 0xc9 => 0x9537, 0xca => 0x9538, 0xcb => 0x953c, 0xcc => 0x953e, + 0xcd => 0x953f, 0xce => 0x9542, 0xcf => 0x9535, 0xd0 => 0x9544, + 0xd1 => 0x9545, 0xd2 => 0x9546, 0xd3 => 0x9549, 0xd4 => 0x954c, + 0xd5 => 0x954e, 0xd6 => 0x954f, 0xd7 => 0x9552, 0xd8 => 0x9553, + 0xd9 => 0x9554, 0xda => 0x9556, 0xdb => 0x9557, 0xdc => 0x9558, + 0xdd => 0x9559, 0xde => 0x955b, 0xdf => 0x955e, 0xe0 => 0x955f, + 0xe1 => 0x955d, 0xe2 => 0x9561, 0xe3 => 0x9562, 0xe4 => 0x9564, + 0xe5 => 0x9565, 0xe6 => 0x9566, 0xe7 => 0x9567, 0xe8 => 0x9568, + 0xe9 => 0x9569, 0xea => 0x956a, 0xeb => 0x956b, 0xec => 0x956c, + 0xed => 0x956f, 0xee => 0x9571, 0xef => 0x9572, 0xf0 => 0x9573, + 0xf1 => 0x953a, 0xf2 => 0x77e7, 0xf3 => 0x77ec, 0xf4 => 0x96c9, + 0xf5 => 0x79d5, 0xf6 => 0x79ed, 0xf7 => 0x79e3, 0xf8 => 0x79eb, + 0xf9 => 0x7a06, 0xfa => 0x5d47, 0xfb => 0x7a03, 0xfc => 0x7a02, + 0xfd => 0x7a1e, 0xfe => 0x7a14, + }, + 0xf0 => { + 0xa1 => 0x7a39, 0xa2 => 0x7a37, 0xa3 => 0x7a51, 0xa4 => 0x9ecf, + 0xa5 => 0x99a5, 0xa6 => 0x7a70, 0xa7 => 0x7688, 0xa8 => 0x768e, + 0xa9 => 0x7693, 0xaa => 0x7699, 0xab => 0x76a4, 0xac => 0x74de, + 0xad => 0x74e0, 0xae => 0x752c, 0xaf => 0x9e20, 0xb0 => 0x9e22, + 0xb1 => 0x9e28, 0xb2 => 0x9e29, 0xb3 => 0x9e2a, 0xb4 => 0x9e2b, + 0xb5 => 0x9e2c, 0xb6 => 0x9e32, 0xb7 => 0x9e31, 0xb8 => 0x9e36, + 0xb9 => 0x9e38, 0xba => 0x9e37, 0xbb => 0x9e39, 0xbc => 0x9e3a, + 0xbd => 0x9e3e, 0xbe => 0x9e41, 0xbf => 0x9e42, 0xc0 => 0x9e44, + 0xc1 => 0x9e46, 0xc2 => 0x9e47, 0xc3 => 0x9e48, 0xc4 => 0x9e49, + 0xc5 => 0x9e4b, 0xc6 => 0x9e4c, 0xc7 => 0x9e4e, 0xc8 => 0x9e51, + 0xc9 => 0x9e55, 0xca => 0x9e57, 0xcb => 0x9e5a, 0xcc => 0x9e5b, + 0xcd => 0x9e5c, 0xce => 0x9e5e, 0xcf => 0x9e63, 0xd0 => 0x9e66, + 0xd1 => 0x9e67, 0xd2 => 0x9e68, 0xd3 => 0x9e69, 0xd4 => 0x9e6a, + 0xd5 => 0x9e6b, 0xd6 => 0x9e6c, 0xd7 => 0x9e71, 0xd8 => 0x9e6d, + 0xd9 => 0x9e73, 0xda => 0x7592, 0xdb => 0x7594, 0xdc => 0x7596, + 0xdd => 0x75a0, 0xde => 0x759d, 0xdf => 0x75ac, 0xe0 => 0x75a3, + 0xe1 => 0x75b3, 0xe2 => 0x75b4, 0xe3 => 0x75b8, 0xe4 => 0x75c4, + 0xe5 => 0x75b1, 0xe6 => 0x75b0, 0xe7 => 0x75c3, 0xe8 => 0x75c2, + 0xe9 => 0x75d6, 0xea => 0x75cd, 0xeb => 0x75e3, 0xec => 0x75e8, + 0xed => 0x75e6, 0xee => 0x75e4, 0xef => 0x75eb, 0xf0 => 0x75e7, + 0xf1 => 0x7603, 0xf2 => 0x75f1, 0xf3 => 0x75fc, 0xf4 => 0x75ff, + 0xf5 => 0x7610, 0xf6 => 0x7600, 0xf7 => 0x7605, 0xf8 => 0x760c, + 0xf9 => 0x7617, 0xfa => 0x760a, 0xfb => 0x7625, 0xfc => 0x7618, + 0xfd => 0x7615, 0xfe => 0x7619, + }, + 0xf1 => { + 0xa1 => 0x761b, 0xa2 => 0x763c, 0xa3 => 0x7622, 0xa4 => 0x7620, + 0xa5 => 0x7640, 0xa6 => 0x762d, 0xa7 => 0x7630, 0xa8 => 0x763f, + 0xa9 => 0x7635, 0xaa => 0x7643, 0xab => 0x763e, 0xac => 0x7633, + 0xad => 0x764d, 0xae => 0x765e, 0xaf => 0x7654, 0xb0 => 0x765c, + 0xb1 => 0x7656, 0xb2 => 0x766b, 0xb3 => 0x766f, 0xb4 => 0x7fca, + 0xb5 => 0x7ae6, 0xb6 => 0x7a78, 0xb7 => 0x7a79, 0xb8 => 0x7a80, + 0xb9 => 0x7a86, 0xba => 0x7a88, 0xbb => 0x7a95, 0xbc => 0x7aa6, + 0xbd => 0x7aa0, 0xbe => 0x7aac, 0xbf => 0x7aa8, 0xc0 => 0x7aad, + 0xc1 => 0x7ab3, 0xc2 => 0x8864, 0xc3 => 0x8869, 0xc4 => 0x8872, + 0xc5 => 0x887d, 0xc6 => 0x887f, 0xc7 => 0x8882, 0xc8 => 0x88a2, + 0xc9 => 0x88c6, 0xca => 0x88b7, 0xcb => 0x88bc, 0xcc => 0x88c9, + 0xcd => 0x88e2, 0xce => 0x88ce, 0xcf => 0x88e3, 0xd0 => 0x88e5, + 0xd1 => 0x88f1, 0xd2 => 0x891a, 0xd3 => 0x88fc, 0xd4 => 0x88e8, + 0xd5 => 0x88fe, 0xd6 => 0x88f0, 0xd7 => 0x8921, 0xd8 => 0x8919, + 0xd9 => 0x8913, 0xda => 0x891b, 0xdb => 0x890a, 0xdc => 0x8934, + 0xdd => 0x892b, 0xde => 0x8936, 0xdf => 0x8941, 0xe0 => 0x8966, + 0xe1 => 0x897b, 0xe2 => 0x758b, 0xe3 => 0x80e5, 0xe4 => 0x76b2, + 0xe5 => 0x76b4, 0xe6 => 0x77dc, 0xe7 => 0x8012, 0xe8 => 0x8014, + 0xe9 => 0x8016, 0xea => 0x801c, 0xeb => 0x8020, 0xec => 0x8022, + 0xed => 0x8025, 0xee => 0x8026, 0xef => 0x8027, 0xf0 => 0x8029, + 0xf1 => 0x8028, 0xf2 => 0x8031, 0xf3 => 0x800b, 0xf4 => 0x8035, + 0xf5 => 0x8043, 0xf6 => 0x8046, 0xf7 => 0x804d, 0xf8 => 0x8052, + 0xf9 => 0x8069, 0xfa => 0x8071, 0xfb => 0x8983, 0xfc => 0x9878, + 0xfd => 0x9880, 0xfe => 0x9883, + }, + 0xf2 => { + 0xa1 => 0x9889, 0xa2 => 0x988c, 0xa3 => 0x988d, 0xa4 => 0x988f, + 0xa5 => 0x9894, 0xa6 => 0x989a, 0xa7 => 0x989b, 0xa8 => 0x989e, + 0xa9 => 0x989f, 0xaa => 0x98a1, 0xab => 0x98a2, 0xac => 0x98a5, + 0xad => 0x98a6, 0xae => 0x864d, 0xaf => 0x8654, 0xb0 => 0x866c, + 0xb1 => 0x866e, 0xb2 => 0x867f, 0xb3 => 0x867a, 0xb4 => 0x867c, + 0xb5 => 0x867b, 0xb6 => 0x86a8, 0xb7 => 0x868d, 0xb8 => 0x868b, + 0xb9 => 0x86ac, 0xba => 0x869d, 0xbb => 0x86a7, 0xbc => 0x86a3, + 0xbd => 0x86aa, 0xbe => 0x8693, 0xbf => 0x86a9, 0xc0 => 0x86b6, + 0xc1 => 0x86c4, 0xc2 => 0x86b5, 0xc3 => 0x86ce, 0xc4 => 0x86b0, + 0xc5 => 0x86ba, 0xc6 => 0x86b1, 0xc7 => 0x86af, 0xc8 => 0x86c9, + 0xc9 => 0x86cf, 0xca => 0x86b4, 0xcb => 0x86e9, 0xcc => 0x86f1, + 0xcd => 0x86f2, 0xce => 0x86ed, 0xcf => 0x86f3, 0xd0 => 0x86d0, + 0xd1 => 0x8713, 0xd2 => 0x86de, 0xd3 => 0x86f4, 0xd4 => 0x86df, + 0xd5 => 0x86d8, 0xd6 => 0x86d1, 0xd7 => 0x8703, 0xd8 => 0x8707, + 0xd9 => 0x86f8, 0xda => 0x8708, 0xdb => 0x870a, 0xdc => 0x870d, + 0xdd => 0x8709, 0xde => 0x8723, 0xdf => 0x873b, 0xe0 => 0x871e, + 0xe1 => 0x8725, 0xe2 => 0x872e, 0xe3 => 0x871a, 0xe4 => 0x873e, + 0xe5 => 0x8748, 0xe6 => 0x8734, 0xe7 => 0x8731, 0xe8 => 0x8729, + 0xe9 => 0x8737, 0xea => 0x873f, 0xeb => 0x8782, 0xec => 0x8722, + 0xed => 0x877d, 0xee => 0x877e, 0xef => 0x877b, 0xf0 => 0x8760, + 0xf1 => 0x8770, 0xf2 => 0x874c, 0xf3 => 0x876e, 0xf4 => 0x878b, + 0xf5 => 0x8753, 0xf6 => 0x8763, 0xf7 => 0x877c, 0xf8 => 0x8764, + 0xf9 => 0x8759, 0xfa => 0x8765, 0xfb => 0x8793, 0xfc => 0x87af, + 0xfd => 0x87a8, 0xfe => 0x87d2, + }, + 0xf3 => { + 0xa1 => 0x87c6, 0xa2 => 0x8788, 0xa3 => 0x8785, 0xa4 => 0x87ad, + 0xa5 => 0x8797, 0xa6 => 0x8783, 0xa7 => 0x87ab, 0xa8 => 0x87e5, + 0xa9 => 0x87ac, 0xaa => 0x87b5, 0xab => 0x87b3, 0xac => 0x87cb, + 0xad => 0x87d3, 0xae => 0x87bd, 0xaf => 0x87d1, 0xb0 => 0x87c0, + 0xb1 => 0x87ca, 0xb2 => 0x87db, 0xb3 => 0x87ea, 0xb4 => 0x87e0, + 0xb5 => 0x87ee, 0xb6 => 0x8816, 0xb7 => 0x8813, 0xb8 => 0x87fe, + 0xb9 => 0x880a, 0xba => 0x881b, 0xbb => 0x8821, 0xbc => 0x8839, + 0xbd => 0x883c, 0xbe => 0x7f36, 0xbf => 0x7f42, 0xc0 => 0x7f44, + 0xc1 => 0x7f45, 0xc2 => 0x8210, 0xc3 => 0x7afa, 0xc4 => 0x7afd, + 0xc5 => 0x7b08, 0xc6 => 0x7b03, 0xc7 => 0x7b04, 0xc8 => 0x7b15, + 0xc9 => 0x7b0a, 0xca => 0x7b2b, 0xcb => 0x7b0f, 0xcc => 0x7b47, + 0xcd => 0x7b38, 0xce => 0x7b2a, 0xcf => 0x7b19, 0xd0 => 0x7b2e, + 0xd1 => 0x7b31, 0xd2 => 0x7b20, 0xd3 => 0x7b25, 0xd4 => 0x7b24, + 0xd5 => 0x7b33, 0xd6 => 0x7b3e, 0xd7 => 0x7b1e, 0xd8 => 0x7b58, + 0xd9 => 0x7b5a, 0xda => 0x7b45, 0xdb => 0x7b75, 0xdc => 0x7b4c, + 0xdd => 0x7b5d, 0xde => 0x7b60, 0xdf => 0x7b6e, 0xe0 => 0x7b7b, + 0xe1 => 0x7b62, 0xe2 => 0x7b72, 0xe3 => 0x7b71, 0xe4 => 0x7b90, + 0xe5 => 0x7ba6, 0xe6 => 0x7ba7, 0xe7 => 0x7bb8, 0xe8 => 0x7bac, + 0xe9 => 0x7b9d, 0xea => 0x7ba8, 0xeb => 0x7b85, 0xec => 0x7baa, + 0xed => 0x7b9c, 0xee => 0x7ba2, 0xef => 0x7bab, 0xf0 => 0x7bb4, + 0xf1 => 0x7bd1, 0xf2 => 0x7bc1, 0xf3 => 0x7bcc, 0xf4 => 0x7bdd, + 0xf5 => 0x7bda, 0xf6 => 0x7be5, 0xf7 => 0x7be6, 0xf8 => 0x7bea, + 0xf9 => 0x7c0c, 0xfa => 0x7bfe, 0xfb => 0x7bfc, 0xfc => 0x7c0f, + 0xfd => 0x7c16, 0xfe => 0x7c0b, + }, + 0xf4 => { + 0xa1 => 0x7c1f, 0xa2 => 0x7c2a, 0xa3 => 0x7c26, 0xa4 => 0x7c38, + 0xa5 => 0x7c41, 0xa6 => 0x7c40, 0xa7 => 0x81fe, 0xa8 => 0x8201, + 0xa9 => 0x8202, 0xaa => 0x8204, 0xab => 0x81ec, 0xac => 0x8844, + 0xad => 0x8221, 0xae => 0x8222, 0xaf => 0x8223, 0xb0 => 0x822d, + 0xb1 => 0x822f, 0xb2 => 0x8228, 0xb3 => 0x822b, 0xb4 => 0x8238, + 0xb5 => 0x823b, 0xb6 => 0x8233, 0xb7 => 0x8234, 0xb8 => 0x823e, + 0xb9 => 0x8244, 0xba => 0x8249, 0xbb => 0x824b, 0xbc => 0x824f, + 0xbd => 0x825a, 0xbe => 0x825f, 0xbf => 0x8268, 0xc0 => 0x887e, + 0xc1 => 0x8885, 0xc2 => 0x8888, 0xc3 => 0x88d8, 0xc4 => 0x88df, + 0xc5 => 0x895e, 0xc6 => 0x7f9d, 0xc7 => 0x7f9f, 0xc8 => 0x7fa7, + 0xc9 => 0x7faf, 0xca => 0x7fb0, 0xcb => 0x7fb2, 0xcc => 0x7c7c, + 0xcd => 0x6549, 0xce => 0x7c91, 0xcf => 0x7c9d, 0xd0 => 0x7c9c, + 0xd1 => 0x7c9e, 0xd2 => 0x7ca2, 0xd3 => 0x7cb2, 0xd4 => 0x7cbc, + 0xd5 => 0x7cbd, 0xd6 => 0x7cc1, 0xd7 => 0x7cc7, 0xd8 => 0x7ccc, + 0xd9 => 0x7ccd, 0xda => 0x7cc8, 0xdb => 0x7cc5, 0xdc => 0x7cd7, + 0xdd => 0x7ce8, 0xde => 0x826e, 0xdf => 0x66a8, 0xe0 => 0x7fbf, + 0xe1 => 0x7fce, 0xe2 => 0x7fd5, 0xe3 => 0x7fe5, 0xe4 => 0x7fe1, + 0xe5 => 0x7fe6, 0xe6 => 0x7fe9, 0xe7 => 0x7fee, 0xe8 => 0x7ff3, + 0xe9 => 0x7cf8, 0xea => 0x7d77, 0xeb => 0x7da6, 0xec => 0x7dae, + 0xed => 0x7e47, 0xee => 0x7e9b, 0xef => 0x9eb8, 0xf0 => 0x9eb4, + 0xf1 => 0x8d73, 0xf2 => 0x8d84, 0xf3 => 0x8d94, 0xf4 => 0x8d91, + 0xf5 => 0x8db1, 0xf6 => 0x8d67, 0xf7 => 0x8d6d, 0xf8 => 0x8c47, + 0xf9 => 0x8c49, 0xfa => 0x914a, 0xfb => 0x9150, 0xfc => 0x914e, + 0xfd => 0x914f, 0xfe => 0x9164, + }, + 0xf5 => { + 0xa1 => 0x9162, 0xa2 => 0x9161, 0xa3 => 0x9170, 0xa4 => 0x9169, + 0xa5 => 0x916f, 0xa6 => 0x917d, 0xa7 => 0x917e, 0xa8 => 0x9172, + 0xa9 => 0x9174, 0xaa => 0x9179, 0xab => 0x918c, 0xac => 0x9185, + 0xad => 0x9190, 0xae => 0x918d, 0xaf => 0x9191, 0xb0 => 0x91a2, + 0xb1 => 0x91a3, 0xb2 => 0x91aa, 0xb3 => 0x91ad, 0xb4 => 0x91ae, + 0xb5 => 0x91af, 0xb6 => 0x91b5, 0xb7 => 0x91b4, 0xb8 => 0x91ba, + 0xb9 => 0x8c55, 0xba => 0x9e7e, 0xbb => 0x8db8, 0xbc => 0x8deb, + 0xbd => 0x8e05, 0xbe => 0x8e59, 0xbf => 0x8e69, 0xc0 => 0x8db5, + 0xc1 => 0x8dbf, 0xc2 => 0x8dbc, 0xc3 => 0x8dba, 0xc4 => 0x8dc4, + 0xc5 => 0x8dd6, 0xc6 => 0x8dd7, 0xc7 => 0x8dda, 0xc8 => 0x8dde, + 0xc9 => 0x8dce, 0xca => 0x8dcf, 0xcb => 0x8ddb, 0xcc => 0x8dc6, + 0xcd => 0x8dec, 0xce => 0x8df7, 0xcf => 0x8df8, 0xd0 => 0x8de3, + 0xd1 => 0x8df9, 0xd2 => 0x8dfb, 0xd3 => 0x8de4, 0xd4 => 0x8e09, + 0xd5 => 0x8dfd, 0xd6 => 0x8e14, 0xd7 => 0x8e1d, 0xd8 => 0x8e1f, + 0xd9 => 0x8e2c, 0xda => 0x8e2e, 0xdb => 0x8e23, 0xdc => 0x8e2f, + 0xdd => 0x8e3a, 0xde => 0x8e40, 0xdf => 0x8e39, 0xe0 => 0x8e35, + 0xe1 => 0x8e3d, 0xe2 => 0x8e31, 0xe3 => 0x8e49, 0xe4 => 0x8e41, + 0xe5 => 0x8e42, 0xe6 => 0x8e51, 0xe7 => 0x8e52, 0xe8 => 0x8e4a, + 0xe9 => 0x8e70, 0xea => 0x8e76, 0xeb => 0x8e7c, 0xec => 0x8e6f, + 0xed => 0x8e74, 0xee => 0x8e85, 0xef => 0x8e8f, 0xf0 => 0x8e94, + 0xf1 => 0x8e90, 0xf2 => 0x8e9c, 0xf3 => 0x8e9e, 0xf4 => 0x8c78, + 0xf5 => 0x8c82, 0xf6 => 0x8c8a, 0xf7 => 0x8c85, 0xf8 => 0x8c98, + 0xf9 => 0x8c94, 0xfa => 0x659b, 0xfb => 0x89d6, 0xfc => 0x89de, + 0xfd => 0x89da, 0xfe => 0x89dc, + }, + 0xf6 => { + 0xa1 => 0x89e5, 0xa2 => 0x89eb, 0xa3 => 0x89ef, 0xa4 => 0x8a3e, + 0xa5 => 0x8b26, 0xa6 => 0x9753, 0xa7 => 0x96e9, 0xa8 => 0x96f3, + 0xa9 => 0x96ef, 0xaa => 0x9706, 0xab => 0x9701, 0xac => 0x9708, + 0xad => 0x970f, 0xae => 0x970e, 0xaf => 0x972a, 0xb0 => 0x972d, + 0xb1 => 0x9730, 0xb2 => 0x973e, 0xb3 => 0x9f80, 0xb4 => 0x9f83, + 0xb5 => 0x9f85, 0xb6 => 0x9f86, 0xb7 => 0x9f87, 0xb8 => 0x9f88, + 0xb9 => 0x9f89, 0xba => 0x9f8a, 0xbb => 0x9f8c, 0xbc => 0x9efe, + 0xbd => 0x9f0b, 0xbe => 0x9f0d, 0xbf => 0x96b9, 0xc0 => 0x96bc, + 0xc1 => 0x96bd, 0xc2 => 0x96ce, 0xc3 => 0x96d2, 0xc4 => 0x77bf, + 0xc5 => 0x96e0, 0xc6 => 0x928e, 0xc7 => 0x92ae, 0xc8 => 0x92c8, + 0xc9 => 0x933e, 0xca => 0x936a, 0xcb => 0x93ca, 0xcc => 0x938f, + 0xcd => 0x943e, 0xce => 0x946b, 0xcf => 0x9c7f, 0xd0 => 0x9c82, + 0xd1 => 0x9c85, 0xd2 => 0x9c86, 0xd3 => 0x9c87, 0xd4 => 0x9c88, + 0xd5 => 0x7a23, 0xd6 => 0x9c8b, 0xd7 => 0x9c8e, 0xd8 => 0x9c90, + 0xd9 => 0x9c91, 0xda => 0x9c92, 0xdb => 0x9c94, 0xdc => 0x9c95, + 0xdd => 0x9c9a, 0xde => 0x9c9b, 0xdf => 0x9c9e, 0xe0 => 0x9c9f, + 0xe1 => 0x9ca0, 0xe2 => 0x9ca1, 0xe3 => 0x9ca2, 0xe4 => 0x9ca3, + 0xe5 => 0x9ca5, 0xe6 => 0x9ca6, 0xe7 => 0x9ca7, 0xe8 => 0x9ca8, + 0xe9 => 0x9ca9, 0xea => 0x9cab, 0xeb => 0x9cad, 0xec => 0x9cae, + 0xed => 0x9cb0, 0xee => 0x9cb1, 0xef => 0x9cb2, 0xf0 => 0x9cb3, + 0xf1 => 0x9cb4, 0xf2 => 0x9cb5, 0xf3 => 0x9cb6, 0xf4 => 0x9cb7, + 0xf5 => 0x9cba, 0xf6 => 0x9cbb, 0xf7 => 0x9cbc, 0xf8 => 0x9cbd, + 0xf9 => 0x9cc4, 0xfa => 0x9cc5, 0xfb => 0x9cc6, 0xfc => 0x9cc7, + 0xfd => 0x9cca, 0xfe => 0x9ccb, + }, + 0xf7 => { + 0xa1 => 0x9ccc, 0xa2 => 0x9ccd, 0xa3 => 0x9cce, 0xa4 => 0x9ccf, + 0xa5 => 0x9cd0, 0xa6 => 0x9cd3, 0xa7 => 0x9cd4, 0xa8 => 0x9cd5, + 0xa9 => 0x9cd7, 0xaa => 0x9cd8, 0xab => 0x9cd9, 0xac => 0x9cdc, + 0xad => 0x9cdd, 0xae => 0x9cdf, 0xaf => 0x9ce2, 0xb0 => 0x977c, + 0xb1 => 0x9785, 0xb2 => 0x9791, 0xb3 => 0x9792, 0xb4 => 0x9794, + 0xb5 => 0x97af, 0xb6 => 0x97ab, 0xb7 => 0x97a3, 0xb8 => 0x97b2, + 0xb9 => 0x97b4, 0xba => 0x9ab1, 0xbb => 0x9ab0, 0xbc => 0x9ab7, + 0xbd => 0x9e58, 0xbe => 0x9ab6, 0xbf => 0x9aba, 0xc0 => 0x9abc, + 0xc1 => 0x9ac1, 0xc2 => 0x9ac0, 0xc3 => 0x9ac5, 0xc4 => 0x9ac2, + 0xc5 => 0x9acb, 0xc6 => 0x9acc, 0xc7 => 0x9ad1, 0xc8 => 0x9b45, + 0xc9 => 0x9b43, 0xca => 0x9b47, 0xcb => 0x9b49, 0xcc => 0x9b48, + 0xcd => 0x9b4d, 0xce => 0x9b51, 0xcf => 0x98e8, 0xd0 => 0x990d, + 0xd1 => 0x992e, 0xd2 => 0x9955, 0xd3 => 0x9954, 0xd4 => 0x9adf, + 0xd5 => 0x9ae1, 0xd6 => 0x9ae6, 0xd7 => 0x9aef, 0xd8 => 0x9aeb, + 0xd9 => 0x9afb, 0xda => 0x9aed, 0xdb => 0x9af9, 0xdc => 0x9b08, + 0xdd => 0x9b0f, 0xde => 0x9b13, 0xdf => 0x9b1f, 0xe0 => 0x9b23, + 0xe1 => 0x9ebd, 0xe2 => 0x9ebe, 0xe3 => 0x7e3b, 0xe4 => 0x9e82, + 0xe5 => 0x9e87, 0xe6 => 0x9e88, 0xe7 => 0x9e8b, 0xe8 => 0x9e92, + 0xe9 => 0x93d6, 0xea => 0x9e9d, 0xeb => 0x9e9f, 0xec => 0x9edb, + 0xed => 0x9edc, 0xee => 0x9edd, 0xef => 0x9ee0, 0xf0 => 0x9edf, + 0xf1 => 0x9ee2, 0xf2 => 0x9ee9, 0xf3 => 0x9ee7, 0xf4 => 0x9ee5, + 0xf5 => 0x9eea, 0xf6 => 0x9eef, 0xf7 => 0x9f22, 0xf8 => 0x9f2c, + 0xf9 => 0x9f2f, 0xfa => 0x9f39, 0xfb => 0x9f37, 0xfc => 0x9f3d, + 0xfd => 0x9f3e, 0xfe => 0x9f44, + }, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacChineseTW.pm b/ExifTool/lib/Image/ExifTool/Charset/MacChineseTW.pm new file mode 100644 index 0000000..a7bb96e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacChineseTW.pm @@ -0,0 +1,3623 @@ +#------------------------------------------------------------------------------ +# File: MacChineseTW.pm +# +# Description: Mac Chinese Traditional to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/CHINTRAD.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacChineseTW = ( + 0x80 => [0x5c,0xf87f], 0x81 => 0xf880, 0x82 => 0xf881, 0xfd => 0xa9, + 0xfe => 0x2122, 0xff => 0x2026, + 0xa1 => { + 0x40 => 0x3000, 0x41 => 0xff0c, 0x42 => 0x3001, 0x43 => 0x3002, + 0x44 => 0xff0e, 0x45 => 0xb7, 0x46 => 0xff1b, 0x47 => 0xff1a, + 0x48 => 0xff1f, 0x49 => 0xff01, 0x4a => 0xfe30, 0x4b => 0x22ef, + 0x4c => 0x2025, 0x4d => [0xff0c,0xf87d], 0x4e => [0x3001,0xf87d], + 0x4f => [0xff0e,0xf87d], 0x50 => [0xff0e,0xf87e], 0x51 => [0xff1b,0xf87d], + 0x52 => [0xff1a,0xf87d], 0x53 => [0xff1f,0xf87d], 0x54 => [0xff01,0xf87d], + 0x55 => 0xff5c, 0x56 => 0x2013, 0x57 => 0xfe31, 0x58 => 0x2014, + 0x59 => 0xfe33, 0x5a => [0xff3f,0xf87f], 0x5b => 0xfe34, 0x5c => 0xfe4f, + 0x5d => 0xff08, 0x5e => 0xff09, 0x5f => 0xfe35, 0x60 => 0xfe36, + 0x61 => 0xff5b, 0x62 => 0xff5d, 0x63 => 0xfe37, 0x64 => 0xfe38, + 0x65 => 0x3014, 0x66 => 0x3015, 0x67 => 0xfe39, 0x68 => 0xfe3a, + 0x69 => 0x3010, 0x6a => 0x3011, 0x6b => 0xfe3b, 0x6c => 0xfe3c, + 0x6d => 0x300a, 0x6e => 0x300b, 0x6f => 0xfe3d, 0x70 => 0xfe3e, + 0x71 => 0x3008, 0x72 => 0x3009, 0x73 => 0xfe3f, 0x74 => 0xfe40, + 0x75 => 0x300c, 0x76 => 0x300d, 0x77 => 0xfe41, 0x78 => 0xfe42, + 0x79 => 0x300e, 0x7a => 0x300f, 0x7b => 0xfe43, 0x7c => 0xfe44, + 0x7d => [0xff08,0xf87f], 0x7e => [0xff09,0xf87f], 0xa1 => [0xff5b,0xf87f], + 0xa2 => [0xff5d,0xf87f], 0xa3 => [0x3014,0xf87f], 0xa4 => [0x3015,0xf87f], + 0xa5 => 0x2018, 0xa6 => 0x2019, 0xa7 => 0x201c, 0xa8 => 0x201d, + 0xa9 => 0x301d, 0xaa => 0x301e, 0xab => 0x2035, 0xac => 0x2032, + 0xad => 0xff03, 0xae => 0xff06, 0xaf => 0xff0a, 0xb0 => 0x203b, + 0xb1 => 0xa7, 0xb2 => 0x3003, 0xb3 => 0x25cb, 0xb4 => 0x25cf, + 0xb5 => 0x25b3, 0xb6 => 0x25b2, 0xb7 => 0x25ce, 0xb8 => 0x2606, + 0xb9 => 0x2605, 0xba => 0x25c7, 0xbb => 0x25c6, 0xbc => 0x25a1, + 0xbd => 0x25a0, 0xbe => 0x25bd, 0xbf => 0x25bc, 0xc0 => 0x32a3, + 0xc1 => 0x2105, 0xc2 => 0x203e, 0xc3 => [0x203e,0xf87c], 0xc4 => 0xff3f, + 0xc5 => [0xff3f,0xf87c], 0xc6 => 0xfe49, 0xc7 => 0xfe4a, 0xc8 => 0xfe4d, + 0xc9 => 0xfe4e, 0xca => 0xfe4b, 0xcb => [0xfe4b,0xf87c], 0xcc => 0xfe5f, + 0xcd => 0xfe60, 0xce => 0xfe61, 0xcf => 0xff0b, 0xd0 => 0xff0d, + 0xd1 => 0xd7, 0xd2 => 0xf7, 0xd3 => 0xb1, 0xd4 => 0x221a, 0xd5 => 0xff1c, + 0xd6 => 0xff1e, 0xd7 => 0xff1d, 0xd8 => 0x2266, 0xd9 => 0x2267, + 0xda => 0x2260, 0xdb => 0x221e, 0xdc => 0x2252, 0xdd => 0x2261, + 0xde => 0xfe62, 0xdf => 0xfe63, 0xe0 => 0xfe64, 0xe1 => 0xfe65, + 0xe2 => 0xfe66, 0xe3 => 0x223c, 0xe4 => 0x2229, 0xe5 => 0x222a, + 0xe6 => 0x22a5, 0xe7 => 0x2220, 0xe8 => 0x221f, 0xe9 => 0x22bf, + 0xea => 0x33d2, 0xeb => 0x33d1, 0xec => 0x222b, 0xed => 0x222e, + 0xee => 0x2235, 0xef => 0x2234, 0xf0 => 0x2640, 0xf1 => 0x2642, + 0xf2 => 0x2295, 0xf3 => 0x2609, 0xf4 => 0x2191, 0xf5 => 0x2193, + 0xf6 => 0x2190, 0xf7 => 0x2192, 0xf8 => 0x2196, 0xf9 => 0x2197, + 0xfa => 0x2199, 0xfb => 0x2198, 0xfc => 0x2225, 0xfd => 0x2223, + 0xfe => [0xff0f,0xf87f], + }, + 0xa2 => { + 0x40 => [0xff3c,0xf87f], 0x41 => 0xff0f, 0x42 => 0xff3c, 0x43 => 0xff04, + 0x44 => 0xa5, 0x45 => 0x3012, 0x46 => 0xa2, 0x47 => 0xa3, 0x48 => 0xff05, + 0x49 => 0xff20, 0x4a => 0x2103, 0x4b => 0x2109, 0x4c => 0xfe69, + 0x4d => 0xfe6a, 0x4e => 0xfe6b, 0x4f => 0x33d5, 0x50 => 0x339c, + 0x51 => 0x339d, 0x52 => 0x339e, 0x53 => 0x33ce, 0x54 => 0x33a1, + 0x55 => 0x338e, 0x56 => 0x338f, 0x57 => 0x33c4, 0x58 => 0xb0, + 0x59 => 0x5159, 0x5a => 0x515b, 0x5b => 0x515e, 0x5c => 0x515d, + 0x5d => 0x5161, 0x5e => 0x5163, 0x5f => 0x55e7, 0x60 => 0x74e9, + 0x61 => 0x7cce, 0x62 => 0x2581, 0x63 => 0x2582, 0x64 => 0x2583, + 0x65 => 0x2584, 0x66 => 0x2585, 0x67 => 0x2586, 0x68 => 0x2587, + 0x69 => 0x2588, 0x6a => 0x258f, 0x6b => 0x258e, 0x6c => 0x258d, + 0x6d => 0x258c, 0x6e => 0x258b, 0x6f => 0x258a, 0x70 => 0x2589, + 0x71 => 0x253c, 0x72 => 0x2534, 0x73 => 0x252c, 0x74 => 0x2524, + 0x75 => 0x251c, 0x76 => 0x2594, 0x77 => 0x2500, 0x78 => 0x2502, + 0x79 => [0x2502,0xf87f], 0x7a => 0x250c, 0x7b => 0x2510, 0x7c => 0x2514, + 0x7d => 0x2518, 0x7e => 0x256d, 0xa1 => 0x256e, 0xa2 => 0x2570, + 0xa3 => 0x256f, 0xa4 => 0x2550, 0xa5 => 0x255e, 0xa6 => 0x256a, + 0xa7 => 0x2561, 0xa8 => 0x25e2, 0xa9 => 0x25e3, 0xaa => 0x25e5, + 0xab => 0x25e4, 0xac => 0x2571, 0xad => 0x2572, 0xae => 0x2573, + 0xaf => 0xff10, 0xb0 => 0xff11, 0xb1 => 0xff12, 0xb2 => 0xff13, + 0xb3 => 0xff14, 0xb4 => 0xff15, 0xb5 => 0xff16, 0xb6 => 0xff17, + 0xb7 => 0xff18, 0xb8 => 0xff19, 0xb9 => 0x2160, 0xba => 0x2161, + 0xbb => 0x2162, 0xbc => 0x2163, 0xbd => 0x2164, 0xbe => 0x2165, + 0xbf => 0x2166, 0xc0 => 0x2167, 0xc1 => 0x2168, 0xc2 => 0x2169, + 0xc3 => 0x3021, 0xc4 => 0x3022, 0xc5 => 0x3023, 0xc6 => 0x3024, + 0xc7 => 0x3025, 0xc8 => 0x3026, 0xc9 => 0x3027, 0xca => 0x3028, + 0xcb => 0x3029, 0xcc => [0x5341,0xf87f], 0xcd => 0x5344, + 0xce => [0x5345,0xf87f], 0xcf => 0xff21, 0xd0 => 0xff22, 0xd1 => 0xff23, + 0xd2 => 0xff24, 0xd3 => 0xff25, 0xd4 => 0xff26, 0xd5 => 0xff27, + 0xd6 => 0xff28, 0xd7 => 0xff29, 0xd8 => 0xff2a, 0xd9 => 0xff2b, + 0xda => 0xff2c, 0xdb => 0xff2d, 0xdc => 0xff2e, 0xdd => 0xff2f, + 0xde => 0xff30, 0xdf => 0xff31, 0xe0 => 0xff32, 0xe1 => 0xff33, + 0xe2 => 0xff34, 0xe3 => 0xff35, 0xe4 => 0xff36, 0xe5 => 0xff37, + 0xe6 => 0xff38, 0xe7 => 0xff39, 0xe8 => 0xff3a, 0xe9 => 0xff41, + 0xea => 0xff42, 0xeb => 0xff43, 0xec => 0xff44, 0xed => 0xff45, + 0xee => 0xff46, 0xef => 0xff47, 0xf0 => 0xff48, 0xf1 => 0xff49, + 0xf2 => 0xff4a, 0xf3 => 0xff4b, 0xf4 => 0xff4c, 0xf5 => 0xff4d, + 0xf6 => 0xff4e, 0xf7 => 0xff4f, 0xf8 => 0xff50, 0xf9 => 0xff51, + 0xfa => 0xff52, 0xfb => 0xff53, 0xfc => 0xff54, 0xfd => 0xff55, + 0xfe => 0xff56, + }, + 0xa3 => { + 0x40 => 0xff57, 0x41 => 0xff58, 0x42 => 0xff59, 0x43 => 0xff5a, + 0x44 => 0x0391, 0x45 => 0x0392, 0x46 => 0x0393, 0x47 => 0x0394, + 0x48 => 0x0395, 0x49 => 0x0396, 0x4a => 0x0397, 0x4b => 0x0398, + 0x4c => 0x0399, 0x4d => 0x039a, 0x4e => 0x039b, 0x4f => 0x039c, + 0x50 => 0x039d, 0x51 => 0x039e, 0x52 => 0x039f, 0x53 => 0x03a0, + 0x54 => 0x03a1, 0x55 => 0x03a3, 0x56 => 0x03a4, 0x57 => 0x03a5, + 0x58 => 0x03a6, 0x59 => 0x03a7, 0x5a => 0x03a8, 0x5b => 0x03a9, + 0x5c => 0x03b1, 0x5d => 0x03b2, 0x5e => 0x03b3, 0x5f => 0x03b4, + 0x60 => 0x03b5, 0x61 => 0x03b6, 0x62 => 0x03b7, 0x63 => 0x03b8, + 0x64 => 0x03b9, 0x65 => 0x03ba, 0x66 => 0x03bb, 0x67 => 0x03bc, + 0x68 => 0x03bd, 0x69 => 0x03be, 0x6a => 0x03bf, 0x6b => 0x03c0, + 0x6c => 0x03c1, 0x6d => 0x03c3, 0x6e => 0x03c4, 0x6f => 0x03c5, + 0x70 => 0x03c6, 0x71 => 0x03c7, 0x72 => 0x03c8, 0x73 => 0x03c9, + 0x74 => 0x3105, 0x75 => 0x3106, 0x76 => 0x3107, 0x77 => 0x3108, + 0x78 => 0x3109, 0x79 => 0x310a, 0x7a => 0x310b, 0x7b => 0x310c, + 0x7c => 0x310d, 0x7d => 0x310e, 0x7e => 0x310f, 0xa1 => 0x3110, + 0xa2 => 0x3111, 0xa3 => 0x3112, 0xa4 => 0x3113, 0xa5 => 0x3114, + 0xa6 => 0x3115, 0xa7 => 0x3116, 0xa8 => 0x3117, 0xa9 => 0x3118, + 0xaa => 0x3119, 0xab => 0x311a, 0xac => 0x311b, 0xad => 0x311c, + 0xae => 0x311d, 0xaf => 0x311e, 0xb0 => 0x311f, 0xb1 => 0x3120, + 0xb2 => 0x3121, 0xb3 => 0x3122, 0xb4 => 0x3123, 0xb5 => 0x3124, + 0xb6 => 0x3125, 0xb7 => 0x3126, 0xb8 => 0x3127, 0xb9 => 0x3128, + 0xba => 0x3129, 0xbb => 0x02d9, 0xbc => 0x02c9, 0xbd => 0x02ca, + 0xbe => 0x02c7, 0xbf => 0x02cb, + }, + 0xa4 => { + 0x40 => 0x4e00, 0x41 => 0x4e59, 0x42 => 0x4e01, 0x43 => 0x4e03, + 0x44 => 0x4e43, 0x45 => 0x4e5d, 0x46 => 0x4e86, 0x47 => 0x4e8c, + 0x48 => 0x4eba, 0x49 => 0x513f, 0x4a => 0x5165, 0x4b => 0x516b, + 0x4c => 0x51e0, 0x4d => 0x5200, 0x4e => 0x5201, 0x4f => 0x529b, + 0x50 => 0x5315, 0x51 => 0x5341, 0x52 => 0x535c, 0x53 => 0x53c8, + 0x54 => 0x4e09, 0x55 => 0x4e0b, 0x56 => 0x4e08, 0x57 => 0x4e0a, + 0x58 => 0x4e2b, 0x59 => 0x4e38, 0x5a => 0x51e1, 0x5b => 0x4e45, + 0x5c => 0x4e48, 0x5d => 0x4e5f, 0x5e => 0x4e5e, 0x5f => 0x4e8e, + 0x60 => 0x4ea1, 0x61 => 0x5140, 0x62 => 0x5203, 0x63 => 0x52fa, + 0x64 => 0x5343, 0x65 => 0x53c9, 0x66 => 0x53e3, 0x67 => 0x571f, + 0x68 => 0x58eb, 0x69 => 0x5915, 0x6a => 0x5927, 0x6b => 0x5973, + 0x6c => 0x5b50, 0x6d => 0x5b51, 0x6e => 0x5b53, 0x6f => 0x5bf8, + 0x70 => 0x5c0f, 0x71 => 0x5c22, 0x72 => 0x5c38, 0x73 => 0x5c71, + 0x74 => 0x5ddd, 0x75 => 0x5de5, 0x76 => 0x5df1, 0x77 => 0x5df2, + 0x78 => 0x5df3, 0x79 => 0x5dfe, 0x7a => 0x5e72, 0x7b => 0x5efe, + 0x7c => 0x5f0b, 0x7d => 0x5f13, 0x7e => 0x624d, 0xa1 => 0x4e11, + 0xa2 => 0x4e10, 0xa3 => 0x4e0d, 0xa4 => 0x4e2d, 0xa5 => 0x4e30, + 0xa6 => 0x4e39, 0xa7 => 0x4e4b, 0xa8 => 0x5c39, 0xa9 => 0x4e88, + 0xaa => 0x4e91, 0xab => 0x4e95, 0xac => 0x4e92, 0xad => 0x4e94, + 0xae => 0x4ea2, 0xaf => 0x4ec1, 0xb0 => 0x4ec0, 0xb1 => 0x4ec3, + 0xb2 => 0x4ec6, 0xb3 => 0x4ec7, 0xb4 => 0x4ecd, 0xb5 => 0x4eca, + 0xb6 => 0x4ecb, 0xb7 => 0x4ec4, 0xb8 => 0x5143, 0xb9 => 0x5141, + 0xba => 0x5167, 0xbb => 0x516d, 0xbc => 0x516e, 0xbd => 0x516c, + 0xbe => 0x5197, 0xbf => 0x51f6, 0xc0 => 0x5206, 0xc1 => 0x5207, + 0xc2 => 0x5208, 0xc3 => 0x52fb, 0xc4 => 0x52fe, 0xc5 => 0x52ff, + 0xc6 => 0x5316, 0xc7 => 0x5339, 0xc8 => 0x5348, 0xc9 => 0x5347, + 0xca => 0x5345, 0xcb => 0x535e, 0xcc => 0x5384, 0xcd => 0x53cb, + 0xce => 0x53ca, 0xcf => 0x53cd, 0xd0 => 0x58ec, 0xd1 => 0x5929, + 0xd2 => 0x592b, 0xd3 => 0x592a, 0xd4 => 0x592d, 0xd5 => 0x5b54, + 0xd6 => 0x5c11, 0xd7 => 0x5c24, 0xd8 => 0x5c3a, 0xd9 => 0x5c6f, + 0xda => 0x5df4, 0xdb => 0x5e7b, 0xdc => 0x5eff, 0xdd => 0x5f14, + 0xde => 0x5f15, 0xdf => 0x5fc3, 0xe0 => 0x6208, 0xe1 => 0x6236, + 0xe2 => 0x624b, 0xe3 => 0x624e, 0xe4 => 0x652f, 0xe5 => 0x6587, + 0xe6 => 0x6597, 0xe7 => 0x65a4, 0xe8 => 0x65b9, 0xe9 => 0x65e5, + 0xea => 0x66f0, 0xeb => 0x6708, 0xec => 0x6728, 0xed => 0x6b20, + 0xee => 0x6b62, 0xef => 0x6b79, 0xf0 => 0x6bcb, 0xf1 => 0x6bd4, + 0xf2 => 0x6bdb, 0xf3 => 0x6c0f, 0xf4 => 0x6c34, 0xf5 => 0x706b, + 0xf6 => 0x722a, 0xf7 => 0x7236, 0xf8 => 0x723b, 0xf9 => 0x7247, + 0xfa => 0x7259, 0xfb => 0x725b, 0xfc => 0x72ac, 0xfd => 0x738b, + 0xfe => 0x4e19, + }, + 0xa5 => { + 0x40 => 0x4e16, 0x41 => 0x4e15, 0x42 => 0x4e14, 0x43 => 0x4e18, + 0x44 => 0x4e3b, 0x45 => 0x4e4d, 0x46 => 0x4e4f, 0x47 => 0x4e4e, + 0x48 => 0x4ee5, 0x49 => 0x4ed8, 0x4a => 0x4ed4, 0x4b => 0x4ed5, + 0x4c => 0x4ed6, 0x4d => 0x4ed7, 0x4e => 0x4ee3, 0x4f => 0x4ee4, + 0x50 => 0x4ed9, 0x51 => 0x4ede, 0x52 => 0x5145, 0x53 => 0x5144, + 0x54 => 0x5189, 0x55 => 0x518a, 0x56 => 0x51ac, 0x57 => 0x51f9, + 0x58 => 0x51fa, 0x59 => 0x51f8, 0x5a => 0x520a, 0x5b => 0x52a0, + 0x5c => 0x529f, 0x5d => 0x5305, 0x5e => 0x5306, 0x5f => 0x5317, + 0x60 => 0x531d, 0x61 => 0x4edf, 0x62 => 0x534a, 0x63 => 0x5349, + 0x64 => 0x5361, 0x65 => 0x5360, 0x66 => 0x536f, 0x67 => 0x536e, + 0x68 => 0x53bb, 0x69 => 0x53ef, 0x6a => 0x53e4, 0x6b => 0x53f3, + 0x6c => 0x53ec, 0x6d => 0x53ee, 0x6e => 0x53e9, 0x6f => 0x53e8, + 0x70 => 0x53fc, 0x71 => 0x53f8, 0x72 => 0x53f5, 0x73 => 0x53eb, + 0x74 => 0x53e6, 0x75 => 0x53ea, 0x76 => 0x53f2, 0x77 => 0x53f1, + 0x78 => 0x53f0, 0x79 => 0x53e5, 0x7a => 0x53ed, 0x7b => 0x53fb, + 0x7c => 0x56db, 0x7d => 0x56da, 0x7e => 0x5916, 0xa1 => 0x592e, + 0xa2 => 0x5931, 0xa3 => 0x5974, 0xa4 => 0x5976, 0xa5 => 0x5b55, + 0xa6 => 0x5b83, 0xa7 => 0x5c3c, 0xa8 => 0x5de8, 0xa9 => 0x5de7, + 0xaa => 0x5de6, 0xab => 0x5e02, 0xac => 0x5e03, 0xad => 0x5e73, + 0xae => 0x5e7c, 0xaf => 0x5f01, 0xb0 => 0x5f18, 0xb1 => 0x5f17, + 0xb2 => 0x5fc5, 0xb3 => 0x620a, 0xb4 => 0x6253, 0xb5 => 0x6254, + 0xb6 => 0x6252, 0xb7 => 0x6251, 0xb8 => 0x65a5, 0xb9 => 0x65e6, + 0xba => 0x672e, 0xbb => 0x672c, 0xbc => 0x672a, 0xbd => 0x672b, + 0xbe => 0x672d, 0xbf => 0x6b63, 0xc0 => 0x6bcd, 0xc1 => 0x6c11, + 0xc2 => 0x6c10, 0xc3 => 0x6c38, 0xc4 => 0x6c41, 0xc5 => 0x6c40, + 0xc6 => 0x6c3e, 0xc7 => 0x72af, 0xc8 => 0x7384, 0xc9 => 0x7389, + 0xca => 0x74dc, 0xcb => 0x74e6, 0xcc => 0x7518, 0xcd => 0x751f, + 0xce => 0x7528, 0xcf => 0x7529, 0xd0 => 0x7530, 0xd1 => 0x7531, + 0xd2 => 0x7532, 0xd3 => 0x7533, 0xd4 => 0x758b, 0xd5 => 0x767d, + 0xd6 => 0x76ae, 0xd7 => 0x76bf, 0xd8 => 0x76ee, 0xd9 => 0x77db, + 0xda => 0x77e2, 0xdb => 0x77f3, 0xdc => 0x793a, 0xdd => 0x79be, + 0xde => 0x7a74, 0xdf => 0x7acb, 0xe0 => 0x4e1e, 0xe1 => 0x4e1f, + 0xe2 => 0x4e52, 0xe3 => 0x4e53, 0xe4 => 0x4e69, 0xe5 => 0x4e99, + 0xe6 => 0x4ea4, 0xe7 => 0x4ea6, 0xe8 => 0x4ea5, 0xe9 => 0x4eff, + 0xea => 0x4f09, 0xeb => 0x4f19, 0xec => 0x4f0a, 0xed => 0x4f15, + 0xee => 0x4f0d, 0xef => 0x4f10, 0xf0 => 0x4f11, 0xf1 => 0x4f0f, + 0xf2 => 0x4ef2, 0xf3 => 0x4ef6, 0xf4 => 0x4efb, 0xf5 => 0x4ef0, + 0xf6 => 0x4ef3, 0xf7 => 0x4efd, 0xf8 => 0x4f01, 0xf9 => 0x4f0b, + 0xfa => 0x5149, 0xfb => 0x5147, 0xfc => 0x5146, 0xfd => 0x5148, + 0xfe => 0x5168, + }, + 0xa6 => { + 0x40 => 0x5171, 0x41 => 0x518d, 0x42 => 0x51b0, 0x43 => 0x5217, + 0x44 => 0x5211, 0x45 => 0x5212, 0x46 => 0x520e, 0x47 => 0x5216, + 0x48 => 0x52a3, 0x49 => 0x5308, 0x4a => 0x5321, 0x4b => 0x5320, + 0x4c => 0x5370, 0x4d => 0x5371, 0x4e => 0x5409, 0x4f => 0x540f, + 0x50 => 0x540c, 0x51 => 0x540a, 0x52 => 0x5410, 0x53 => 0x5401, + 0x54 => 0x540b, 0x55 => 0x5404, 0x56 => 0x5411, 0x57 => 0x540d, + 0x58 => 0x5408, 0x59 => 0x5403, 0x5a => 0x540e, 0x5b => 0x5406, + 0x5c => 0x5412, 0x5d => 0x56e0, 0x5e => 0x56de, 0x5f => 0x56dd, + 0x60 => 0x5733, 0x61 => 0x5730, 0x62 => 0x5728, 0x63 => 0x572d, + 0x64 => 0x572c, 0x65 => 0x572f, 0x66 => 0x5729, 0x67 => 0x5919, + 0x68 => 0x591a, 0x69 => 0x5937, 0x6a => 0x5938, 0x6b => 0x5984, + 0x6c => 0x5978, 0x6d => 0x5983, 0x6e => 0x597d, 0x6f => 0x5979, + 0x70 => 0x5982, 0x71 => 0x5981, 0x72 => 0x5b57, 0x73 => 0x5b58, + 0x74 => 0x5b87, 0x75 => 0x5b88, 0x76 => 0x5b85, 0x77 => 0x5b89, + 0x78 => 0x5bfa, 0x79 => 0x5c16, 0x7a => 0x5c79, 0x7b => 0x5dde, + 0x7c => 0x5e06, 0x7d => 0x5e76, 0x7e => 0x5e74, 0xa1 => 0x5f0f, + 0xa2 => 0x5f1b, 0xa3 => 0x5fd9, 0xa4 => 0x5fd6, 0xa5 => 0x620e, + 0xa6 => 0x620c, 0xa7 => 0x620d, 0xa8 => 0x6210, 0xa9 => 0x6263, + 0xaa => 0x625b, 0xab => 0x6258, 0xac => 0x6536, 0xad => 0x65e9, + 0xae => 0x65e8, 0xaf => 0x65ec, 0xb0 => 0x65ed, 0xb1 => 0x66f2, + 0xb2 => 0x66f3, 0xb3 => 0x6709, 0xb4 => 0x673d, 0xb5 => 0x6734, + 0xb6 => 0x6731, 0xb7 => 0x6735, 0xb8 => 0x6b21, 0xb9 => 0x6b64, + 0xba => 0x6b7b, 0xbb => 0x6c16, 0xbc => 0x6c5d, 0xbd => 0x6c57, + 0xbe => 0x6c59, 0xbf => 0x6c5f, 0xc0 => 0x6c60, 0xc1 => 0x6c50, + 0xc2 => 0x6c55, 0xc3 => 0x6c61, 0xc4 => 0x6c5b, 0xc5 => 0x6c4d, + 0xc6 => 0x6c4e, 0xc7 => 0x7070, 0xc8 => 0x725f, 0xc9 => 0x725d, + 0xca => 0x767e, 0xcb => 0x7af9, 0xcc => 0x7c73, 0xcd => 0x7cf8, + 0xce => 0x7f36, 0xcf => 0x7f8a, 0xd0 => 0x7fbd, 0xd1 => 0x8001, + 0xd2 => 0x8003, 0xd3 => 0x800c, 0xd4 => 0x8012, 0xd5 => 0x8033, + 0xd6 => 0x807f, 0xd7 => 0x8089, 0xd8 => 0x808b, 0xd9 => 0x808c, + 0xda => 0x81e3, 0xdb => 0x81ea, 0xdc => 0x81f3, 0xdd => 0x81fc, + 0xde => 0x820c, 0xdf => 0x821b, 0xe0 => 0x821f, 0xe1 => 0x826e, + 0xe2 => 0x8272, 0xe3 => 0x827e, 0xe4 => 0x866b, 0xe5 => 0x8840, + 0xe6 => 0x884c, 0xe7 => 0x8863, 0xe8 => 0x897f, 0xe9 => 0x9621, + 0xea => 0x4e32, 0xeb => 0x4ea8, 0xec => 0x4f4d, 0xed => 0x4f4f, + 0xee => 0x4f47, 0xef => 0x4f57, 0xf0 => 0x4f5e, 0xf1 => 0x4f34, + 0xf2 => 0x4f5b, 0xf3 => 0x4f55, 0xf4 => 0x4f30, 0xf5 => 0x4f50, + 0xf6 => 0x4f51, 0xf7 => 0x4f3d, 0xf8 => 0x4f3a, 0xf9 => 0x4f38, + 0xfa => 0x4f43, 0xfb => 0x4f54, 0xfc => 0x4f3c, 0xfd => 0x4f46, + 0xfe => 0x4f63, + }, + 0xa7 => { + 0x40 => 0x4f5c, 0x41 => 0x4f60, 0x42 => 0x4f2f, 0x43 => 0x4f4e, + 0x44 => 0x4f36, 0x45 => 0x4f59, 0x46 => 0x4f5d, 0x47 => 0x4f48, + 0x48 => 0x4f5a, 0x49 => 0x514c, 0x4a => 0x514b, 0x4b => 0x514d, + 0x4c => 0x5175, 0x4d => 0x51b6, 0x4e => 0x51b7, 0x4f => 0x5225, + 0x50 => 0x5224, 0x51 => 0x5229, 0x52 => 0x522a, 0x53 => 0x5228, + 0x54 => 0x52ab, 0x55 => 0x52a9, 0x56 => 0x52aa, 0x57 => 0x52ac, + 0x58 => 0x5323, 0x59 => 0x5373, 0x5a => 0x5375, 0x5b => 0x541d, + 0x5c => 0x542d, 0x5d => 0x541e, 0x5e => 0x543e, 0x5f => 0x5426, + 0x60 => 0x544e, 0x61 => 0x5427, 0x62 => 0x5446, 0x63 => 0x5443, + 0x64 => 0x5433, 0x65 => 0x5448, 0x66 => 0x5442, 0x67 => 0x541b, + 0x68 => 0x5429, 0x69 => 0x544a, 0x6a => 0x5439, 0x6b => 0x543b, + 0x6c => 0x5438, 0x6d => 0x542e, 0x6e => 0x5435, 0x6f => 0x5436, + 0x70 => 0x5420, 0x71 => 0x543c, 0x72 => 0x5440, 0x73 => 0x5431, + 0x74 => 0x542b, 0x75 => 0x541f, 0x76 => 0x542c, 0x77 => 0x56ea, + 0x78 => 0x56f0, 0x79 => 0x56e4, 0x7a => 0x56eb, 0x7b => 0x574a, + 0x7c => 0x5751, 0x7d => 0x5740, 0x7e => 0x574d, 0xa1 => 0x5747, + 0xa2 => 0x574e, 0xa3 => 0x573e, 0xa4 => 0x5750, 0xa5 => 0x574f, + 0xa6 => 0x573b, 0xa7 => 0x58ef, 0xa8 => 0x593e, 0xa9 => 0x599d, + 0xaa => 0x5992, 0xab => 0x59a8, 0xac => 0x599e, 0xad => 0x59a3, + 0xae => 0x5999, 0xaf => 0x5996, 0xb0 => 0x598d, 0xb1 => 0x59a4, + 0xb2 => 0x5993, 0xb3 => 0x598a, 0xb4 => 0x59a5, 0xb5 => 0x5b5d, + 0xb6 => 0x5b5c, 0xb7 => 0x5b5a, 0xb8 => 0x5b5b, 0xb9 => 0x5b8c, + 0xba => 0x5b8b, 0xbb => 0x5b8f, 0xbc => 0x5c2c, 0xbd => 0x5c40, + 0xbe => 0x5c41, 0xbf => 0x5c3f, 0xc0 => 0x5c3e, 0xc1 => 0x5c90, + 0xc2 => 0x5c91, 0xc3 => 0x5c94, 0xc4 => 0x5c8c, 0xc5 => 0x5deb, + 0xc6 => 0x5e0c, 0xc7 => 0x5e8f, 0xc8 => 0x5e87, 0xc9 => 0x5e8a, + 0xca => 0x5ef7, 0xcb => 0x5f04, 0xcc => 0x5f1f, 0xcd => 0x5f64, + 0xce => 0x5f62, 0xcf => 0x5f77, 0xd0 => 0x5f79, 0xd1 => 0x5fd8, + 0xd2 => 0x5fcc, 0xd3 => 0x5fd7, 0xd4 => 0x5fcd, 0xd5 => 0x5ff1, + 0xd6 => 0x5feb, 0xd7 => 0x5ff8, 0xd8 => 0x5fea, 0xd9 => 0x6212, + 0xda => 0x6211, 0xdb => 0x6284, 0xdc => 0x6297, 0xdd => 0x6296, + 0xde => 0x6280, 0xdf => 0x6276, 0xe0 => 0x6289, 0xe1 => 0x626d, + 0xe2 => 0x628a, 0xe3 => 0x627c, 0xe4 => 0x627e, 0xe5 => 0x6279, + 0xe6 => 0x6273, 0xe7 => 0x6292, 0xe8 => 0x626f, 0xe9 => 0x6298, + 0xea => 0x626e, 0xeb => 0x6295, 0xec => 0x6293, 0xed => 0x6291, + 0xee => 0x6286, 0xef => 0x6539, 0xf0 => 0x653b, 0xf1 => 0x6538, + 0xf2 => 0x65f1, 0xf3 => 0x66f4, 0xf4 => 0x675f, 0xf5 => 0x674e, + 0xf6 => 0x674f, 0xf7 => 0x6750, 0xf8 => 0x6751, 0xf9 => 0x675c, + 0xfa => 0x6756, 0xfb => 0x675e, 0xfc => 0x6749, 0xfd => 0x6746, + 0xfe => 0x6760, + }, + 0xa8 => { + 0x40 => 0x6753, 0x41 => 0x6757, 0x42 => 0x6b65, 0x43 => 0x6bcf, + 0x44 => 0x6c42, 0x45 => 0x6c5e, 0x46 => 0x6c99, 0x47 => 0x6c81, + 0x48 => 0x6c88, 0x49 => 0x6c89, 0x4a => 0x6c85, 0x4b => 0x6c9b, + 0x4c => 0x6c6a, 0x4d => 0x6c7a, 0x4e => 0x6c90, 0x4f => 0x6c70, + 0x50 => 0x6c8c, 0x51 => 0x6c68, 0x52 => 0x6c96, 0x53 => 0x6c92, + 0x54 => 0x6c7d, 0x55 => 0x6c83, 0x56 => 0x6c72, 0x57 => 0x6c7e, + 0x58 => 0x6c74, 0x59 => 0x6c86, 0x5a => 0x6c76, 0x5b => 0x6c8d, + 0x5c => 0x6c94, 0x5d => 0x6c98, 0x5e => 0x6c82, 0x5f => 0x7076, + 0x60 => 0x707c, 0x61 => 0x707d, 0x62 => 0x7078, 0x63 => 0x7262, + 0x64 => 0x7261, 0x65 => 0x7260, 0x66 => 0x72c4, 0x67 => 0x72c2, + 0x68 => 0x7396, 0x69 => 0x752c, 0x6a => 0x752b, 0x6b => 0x7537, + 0x6c => 0x7538, 0x6d => 0x7682, 0x6e => 0x76ef, 0x6f => 0x77e3, + 0x70 => 0x79c1, 0x71 => 0x79c0, 0x72 => 0x79bf, 0x73 => 0x7a76, + 0x74 => 0x7cfb, 0x75 => 0x7f55, 0x76 => 0x8096, 0x77 => 0x8093, + 0x78 => 0x809d, 0x79 => 0x8098, 0x7a => 0x809b, 0x7b => 0x809a, + 0x7c => 0x80b2, 0x7d => 0x826f, 0x7e => 0x8292, 0xa1 => 0x828b, + 0xa2 => 0x828d, 0xa3 => 0x898b, 0xa4 => 0x89d2, 0xa5 => 0x8a00, + 0xa6 => 0x8c37, 0xa7 => 0x8c46, 0xa8 => 0x8c55, 0xa9 => 0x8c9d, + 0xaa => 0x8d64, 0xab => 0x8d70, 0xac => 0x8db3, 0xad => 0x8eab, + 0xae => 0x8eca, 0xaf => 0x8f9b, 0xb0 => 0x8fb0, 0xb1 => 0x8fc2, + 0xb2 => 0x8fc6, 0xb3 => 0x8fc5, 0xb4 => 0x8fc4, 0xb5 => 0x5de1, + 0xb6 => 0x9091, 0xb7 => 0x90a2, 0xb8 => 0x90aa, 0xb9 => 0x90a6, + 0xba => 0x90a3, 0xbb => 0x9149, 0xbc => 0x91c6, 0xbd => 0x91cc, + 0xbe => 0x9632, 0xbf => 0x962e, 0xc0 => 0x9631, 0xc1 => 0x962a, + 0xc2 => 0x962c, 0xc3 => 0x4e26, 0xc4 => 0x4e56, 0xc5 => 0x4e73, + 0xc6 => 0x4e8b, 0xc7 => 0x4e9b, 0xc8 => 0x4e9e, 0xc9 => 0x4eab, + 0xca => 0x4eac, 0xcb => 0x4f6f, 0xcc => 0x4f9d, 0xcd => 0x4f8d, + 0xce => 0x4f73, 0xcf => 0x4f7f, 0xd0 => 0x4f6c, 0xd1 => 0x4f9b, + 0xd2 => 0x4f8b, 0xd3 => 0x4f86, 0xd4 => 0x4f83, 0xd5 => 0x4f70, + 0xd6 => 0x4f75, 0xd7 => 0x4f88, 0xd8 => 0x4f69, 0xd9 => 0x4f7b, + 0xda => 0x4f96, 0xdb => 0x4f7e, 0xdc => 0x4f8f, 0xdd => 0x4f91, + 0xde => 0x4f7a, 0xdf => 0x5154, 0xe0 => 0x5152, 0xe1 => 0x5155, + 0xe2 => 0x5169, 0xe3 => 0x5177, 0xe4 => 0x5176, 0xe5 => 0x5178, + 0xe6 => 0x51bd, 0xe7 => 0x51fd, 0xe8 => 0x523b, 0xe9 => 0x5238, + 0xea => 0x5237, 0xeb => 0x523a, 0xec => 0x5230, 0xed => 0x522e, + 0xee => 0x5236, 0xef => 0x5241, 0xf0 => 0x52be, 0xf1 => 0x52bb, + 0xf2 => 0x5352, 0xf3 => 0x5354, 0xf4 => 0x5353, 0xf5 => 0x5351, + 0xf6 => 0x5366, 0xf7 => 0x5377, 0xf8 => 0x5378, 0xf9 => 0x5379, + 0xfa => 0x53d6, 0xfb => 0x53d4, 0xfc => 0x53d7, 0xfd => 0x5473, + 0xfe => 0x5475, + }, + 0xa9 => { + 0x40 => 0x5496, 0x41 => 0x5478, 0x42 => 0x5495, 0x43 => 0x5480, + 0x44 => 0x547b, 0x45 => 0x5477, 0x46 => 0x5484, 0x47 => 0x5492, + 0x48 => 0x5486, 0x49 => 0x547c, 0x4a => 0x5490, 0x4b => 0x5471, + 0x4c => 0x5476, 0x4d => 0x548c, 0x4e => 0x549a, 0x4f => 0x5462, + 0x50 => 0x5468, 0x51 => 0x548b, 0x52 => 0x547d, 0x53 => 0x548e, + 0x54 => 0x56fa, 0x55 => 0x5783, 0x56 => 0x5777, 0x57 => 0x576a, + 0x58 => 0x5769, 0x59 => 0x5761, 0x5a => 0x5766, 0x5b => 0x5764, + 0x5c => 0x577c, 0x5d => 0x591c, 0x5e => 0x5949, 0x5f => 0x5947, + 0x60 => 0x5948, 0x61 => 0x5944, 0x62 => 0x5954, 0x63 => 0x59be, + 0x64 => 0x59bb, 0x65 => 0x59d4, 0x66 => 0x59b9, 0x67 => 0x59ae, + 0x68 => 0x59d1, 0x69 => 0x59c6, 0x6a => 0x59d0, 0x6b => 0x59cd, + 0x6c => 0x59cb, 0x6d => 0x59d3, 0x6e => 0x59ca, 0x6f => 0x59af, + 0x70 => 0x59b3, 0x71 => 0x59d2, 0x72 => 0x59c5, 0x73 => 0x5b5f, + 0x74 => 0x5b64, 0x75 => 0x5b63, 0x76 => 0x5b97, 0x77 => 0x5b9a, + 0x78 => 0x5b98, 0x79 => 0x5b9c, 0x7a => 0x5b99, 0x7b => 0x5b9b, + 0x7c => 0x5c1a, 0x7d => 0x5c48, 0x7e => 0x5c45, 0xa1 => 0x5c46, + 0xa2 => 0x5cb7, 0xa3 => 0x5ca1, 0xa4 => 0x5cb8, 0xa5 => 0x5ca9, + 0xa6 => 0x5cab, 0xa7 => 0x5cb1, 0xa8 => 0x5cb3, 0xa9 => 0x5e18, + 0xaa => 0x5e1a, 0xab => 0x5e16, 0xac => 0x5e15, 0xad => 0x5e1b, + 0xae => 0x5e11, 0xaf => 0x5e78, 0xb0 => 0x5e9a, 0xb1 => 0x5e97, + 0xb2 => 0x5e9c, 0xb3 => 0x5e95, 0xb4 => 0x5e96, 0xb5 => 0x5ef6, + 0xb6 => 0x5f26, 0xb7 => 0x5f27, 0xb8 => 0x5f29, 0xb9 => 0x5f80, + 0xba => 0x5f81, 0xbb => 0x5f7f, 0xbc => 0x5f7c, 0xbd => 0x5fdd, + 0xbe => 0x5fe0, 0xbf => 0x5ffd, 0xc0 => 0x5ff5, 0xc1 => 0x5fff, + 0xc2 => 0x600f, 0xc3 => 0x6014, 0xc4 => 0x602f, 0xc5 => 0x6035, + 0xc6 => 0x6016, 0xc7 => 0x602a, 0xc8 => 0x6015, 0xc9 => 0x6021, + 0xca => 0x6027, 0xcb => 0x6029, 0xcc => 0x602b, 0xcd => 0x601b, + 0xce => 0x6216, 0xcf => 0x6215, 0xd0 => 0x623f, 0xd1 => 0x623e, + 0xd2 => 0x6240, 0xd3 => 0x627f, 0xd4 => 0x62c9, 0xd5 => 0x62cc, + 0xd6 => 0x62c4, 0xd7 => 0x62bf, 0xd8 => 0x62c2, 0xd9 => 0x62b9, + 0xda => 0x62d2, 0xdb => 0x62db, 0xdc => 0x62ab, 0xdd => 0x62d3, + 0xde => 0x62d4, 0xdf => 0x62cb, 0xe0 => 0x62c8, 0xe1 => 0x62a8, + 0xe2 => 0x62bd, 0xe3 => 0x62bc, 0xe4 => 0x62d0, 0xe5 => 0x62d9, + 0xe6 => 0x62c7, 0xe7 => 0x62cd, 0xe8 => 0x62b5, 0xe9 => 0x62da, + 0xea => 0x62b1, 0xeb => 0x62d8, 0xec => 0x62d6, 0xed => 0x62d7, + 0xee => 0x62c6, 0xef => 0x62ac, 0xf0 => 0x62ce, 0xf1 => 0x653e, + 0xf2 => 0x65a7, 0xf3 => 0x65bc, 0xf4 => 0x65fa, 0xf5 => 0x6614, + 0xf6 => 0x6613, 0xf7 => 0x660c, 0xf8 => 0x6606, 0xf9 => 0x6602, + 0xfa => 0x660e, 0xfb => 0x6600, 0xfc => 0x660f, 0xfd => 0x6615, + 0xfe => 0x660a, + }, + 0xaa => { + 0x40 => 0x6607, 0x41 => 0x670d, 0x42 => 0x670b, 0x43 => 0x676d, + 0x44 => 0x678b, 0x45 => 0x6795, 0x46 => 0x6771, 0x47 => 0x679c, + 0x48 => 0x6773, 0x49 => 0x6777, 0x4a => 0x6787, 0x4b => 0x679d, + 0x4c => 0x6797, 0x4d => 0x676f, 0x4e => 0x6770, 0x4f => 0x677f, + 0x50 => 0x6789, 0x51 => 0x677e, 0x52 => 0x6790, 0x53 => 0x6775, + 0x54 => 0x679a, 0x55 => 0x6793, 0x56 => 0x677c, 0x57 => 0x676a, + 0x58 => 0x6772, 0x59 => 0x6b23, 0x5a => 0x6b66, 0x5b => 0x6b67, + 0x5c => 0x6b7f, 0x5d => 0x6c13, 0x5e => 0x6c1b, 0x5f => 0x6ce3, + 0x60 => 0x6ce8, 0x61 => 0x6cf3, 0x62 => 0x6cb1, 0x63 => 0x6ccc, + 0x64 => 0x6ce5, 0x65 => 0x6cb3, 0x66 => 0x6cbd, 0x67 => 0x6cbe, + 0x68 => 0x6cbc, 0x69 => 0x6ce2, 0x6a => 0x6cab, 0x6b => 0x6cd5, + 0x6c => 0x6cd3, 0x6d => 0x6cb8, 0x6e => 0x6cc4, 0x6f => 0x6cb9, + 0x70 => 0x6cc1, 0x71 => 0x6cae, 0x72 => 0x6cd7, 0x73 => 0x6cc5, + 0x74 => 0x6cf1, 0x75 => 0x6cbf, 0x76 => 0x6cbb, 0x77 => 0x6ce1, + 0x78 => 0x6cdb, 0x79 => 0x6cca, 0x7a => 0x6cac, 0x7b => 0x6cef, + 0x7c => 0x6cdc, 0x7d => 0x6cd6, 0x7e => 0x6ce0, 0xa1 => 0x7095, + 0xa2 => 0x708e, 0xa3 => 0x7092, 0xa4 => 0x708a, 0xa5 => 0x7099, + 0xa6 => 0x722c, 0xa7 => 0x722d, 0xa8 => 0x7238, 0xa9 => 0x7248, + 0xaa => 0x7267, 0xab => 0x7269, 0xac => 0x72c0, 0xad => 0x72ce, + 0xae => 0x72d9, 0xaf => 0x72d7, 0xb0 => 0x72d0, 0xb1 => 0x73a9, + 0xb2 => 0x73a8, 0xb3 => 0x739f, 0xb4 => 0x73ab, 0xb5 => 0x73a5, + 0xb6 => 0x753d, 0xb7 => 0x759d, 0xb8 => 0x7599, 0xb9 => 0x759a, + 0xba => 0x7684, 0xbb => 0x76c2, 0xbc => 0x76f2, 0xbd => 0x76f4, + 0xbe => 0x77e5, 0xbf => 0x77fd, 0xc0 => 0x793e, 0xc1 => 0x7940, + 0xc2 => 0x7941, 0xc3 => 0x79c9, 0xc4 => 0x79c8, 0xc5 => 0x7a7a, + 0xc6 => 0x7a79, 0xc7 => 0x7afa, 0xc8 => 0x7cfe, 0xc9 => 0x7f54, + 0xca => 0x7f8c, 0xcb => 0x7f8b, 0xcc => 0x8005, 0xcd => 0x80ba, + 0xce => 0x80a5, 0xcf => 0x80a2, 0xd0 => 0x80b1, 0xd1 => 0x80a1, + 0xd2 => 0x80ab, 0xd3 => 0x80a9, 0xd4 => 0x80b4, 0xd5 => 0x80aa, + 0xd6 => 0x80af, 0xd7 => 0x81e5, 0xd8 => 0x81fe, 0xd9 => 0x820d, + 0xda => 0x82b3, 0xdb => 0x829d, 0xdc => 0x8299, 0xdd => 0x82ad, + 0xde => 0x82bd, 0xdf => 0x829f, 0xe0 => 0x82b9, 0xe1 => 0x82b1, + 0xe2 => 0x82ac, 0xe3 => 0x82a5, 0xe4 => 0x82af, 0xe5 => 0x82b8, + 0xe6 => 0x82a3, 0xe7 => 0x82b0, 0xe8 => 0x82be, 0xe9 => 0x82b7, + 0xea => 0x864e, 0xeb => 0x8671, 0xec => 0x521d, 0xed => 0x8868, + 0xee => 0x8ecb, 0xef => 0x8fce, 0xf0 => 0x8fd4, 0xf1 => 0x8fd1, + 0xf2 => 0x90b5, 0xf3 => 0x90b8, 0xf4 => 0x90b1, 0xf5 => 0x90b6, + 0xf6 => 0x91c7, 0xf7 => 0x91d1, 0xf8 => 0x9577, 0xf9 => 0x9580, + 0xfa => 0x961c, 0xfb => 0x9640, 0xfc => 0x963f, 0xfd => 0x963b, + 0xfe => 0x9644, + }, + 0xab => { + 0x40 => 0x9642, 0x41 => 0x96b9, 0x42 => 0x96e8, 0x43 => 0x9752, + 0x44 => 0x975e, 0x45 => 0x4e9f, 0x46 => 0x4ead, 0x47 => 0x4eae, + 0x48 => 0x4fe1, 0x49 => 0x4fb5, 0x4a => 0x4faf, 0x4b => 0x4fbf, + 0x4c => 0x4fe0, 0x4d => 0x4fd1, 0x4e => 0x4fcf, 0x4f => 0x4fdd, + 0x50 => 0x4fc3, 0x51 => 0x4fb6, 0x52 => 0x4fd8, 0x53 => 0x4fdf, + 0x54 => 0x4fca, 0x55 => 0x4fd7, 0x56 => 0x4fae, 0x57 => 0x4fd0, + 0x58 => 0x4fc4, 0x59 => 0x4fc2, 0x5a => 0x4fda, 0x5b => 0x4fce, + 0x5c => 0x4fde, 0x5d => 0x4fb7, 0x5e => 0x5157, 0x5f => 0x5192, + 0x60 => 0x5191, 0x61 => 0x51a0, 0x62 => 0x524e, 0x63 => 0x5243, + 0x64 => 0x524a, 0x65 => 0x524d, 0x66 => 0x524c, 0x67 => 0x524b, + 0x68 => 0x5247, 0x69 => 0x52c7, 0x6a => 0x52c9, 0x6b => 0x52c3, + 0x6c => 0x52c1, 0x6d => 0x530d, 0x6e => 0x5357, 0x6f => 0x537b, + 0x70 => 0x539a, 0x71 => 0x53db, 0x72 => 0x54ac, 0x73 => 0x54c0, + 0x74 => 0x54a8, 0x75 => 0x54ce, 0x76 => 0x54c9, 0x77 => 0x54b8, + 0x78 => 0x54a6, 0x79 => 0x54b3, 0x7a => 0x54c7, 0x7b => 0x54c2, + 0x7c => 0x54bd, 0x7d => 0x54aa, 0x7e => 0x54c1, 0xa1 => 0x54c4, + 0xa2 => 0x54c8, 0xa3 => 0x54af, 0xa4 => 0x54ab, 0xa5 => 0x54b1, + 0xa6 => 0x54bb, 0xa7 => 0x54a9, 0xa8 => 0x54a7, 0xa9 => 0x54bf, + 0xaa => 0x56ff, 0xab => 0x5782, 0xac => 0x578b, 0xad => 0x57a0, + 0xae => 0x57a3, 0xaf => 0x57a2, 0xb0 => 0x57ce, 0xb1 => 0x57ae, + 0xb2 => 0x5793, 0xb3 => 0x5955, 0xb4 => 0x5951, 0xb5 => 0x594f, + 0xb6 => 0x594e, 0xb7 => 0x5950, 0xb8 => 0x59dc, 0xb9 => 0x59d8, + 0xba => 0x59ff, 0xbb => 0x59e3, 0xbc => 0x59e8, 0xbd => 0x5a03, + 0xbe => 0x59e5, 0xbf => 0x59ea, 0xc0 => 0x59da, 0xc1 => 0x59e6, + 0xc2 => 0x5a01, 0xc3 => 0x59fb, 0xc4 => 0x5b69, 0xc5 => 0x5ba3, + 0xc6 => 0x5ba6, 0xc7 => 0x5ba4, 0xc8 => 0x5ba2, 0xc9 => 0x5ba5, + 0xca => 0x5c01, 0xcb => 0x5c4e, 0xcc => 0x5c4f, 0xcd => 0x5c4d, + 0xce => 0x5c4b, 0xcf => 0x5cd9, 0xd0 => 0x5cd2, 0xd1 => 0x5df7, + 0xd2 => 0x5e1d, 0xd3 => 0x5e25, 0xd4 => 0x5e1f, 0xd5 => 0x5e7d, + 0xd6 => 0x5ea0, 0xd7 => 0x5ea6, 0xd8 => 0x5efa, 0xd9 => 0x5f08, + 0xda => 0x5f2d, 0xdb => 0x5f65, 0xdc => 0x5f88, 0xdd => 0x5f85, + 0xde => 0x5f8a, 0xdf => 0x5f8b, 0xe0 => 0x5f87, 0xe1 => 0x5f8c, + 0xe2 => 0x5f89, 0xe3 => 0x6012, 0xe4 => 0x601d, 0xe5 => 0x6020, + 0xe6 => 0x6025, 0xe7 => 0x600e, 0xe8 => 0x6028, 0xe9 => 0x604d, + 0xea => 0x6070, 0xeb => 0x6068, 0xec => 0x6062, 0xed => 0x6046, + 0xee => 0x6043, 0xef => 0x606c, 0xf0 => 0x606b, 0xf1 => 0x606a, + 0xf2 => 0x6064, 0xf3 => 0x6241, 0xf4 => 0x62dc, 0xf5 => 0x6316, + 0xf6 => 0x6309, 0xf7 => 0x62fc, 0xf8 => 0x62ed, 0xf9 => 0x6301, + 0xfa => 0x62ee, 0xfb => 0x62fd, 0xfc => 0x6307, 0xfd => 0x62f1, + 0xfe => 0x62f7, + }, + 0xac => { + 0x40 => 0x62ef, 0x41 => 0x62ec, 0x42 => 0x62fe, 0x43 => 0x62f4, + 0x44 => 0x6311, 0x45 => 0x6302, 0x46 => 0x653f, 0x47 => 0x6545, + 0x48 => 0x65ab, 0x49 => 0x65bd, 0x4a => 0x65e2, 0x4b => 0x6625, + 0x4c => 0x662d, 0x4d => 0x6620, 0x4e => 0x6627, 0x4f => 0x662f, + 0x50 => 0x661f, 0x51 => 0x6628, 0x52 => 0x6631, 0x53 => 0x6624, + 0x54 => 0x66f7, 0x55 => 0x67ff, 0x56 => 0x67d3, 0x57 => 0x67f1, + 0x58 => 0x67d4, 0x59 => 0x67d0, 0x5a => 0x67ec, 0x5b => 0x67b6, + 0x5c => 0x67af, 0x5d => 0x67f5, 0x5e => 0x67e9, 0x5f => 0x67ef, + 0x60 => 0x67c4, 0x61 => 0x67d1, 0x62 => 0x67b4, 0x63 => 0x67da, + 0x64 => 0x67e5, 0x65 => 0x67b8, 0x66 => 0x67cf, 0x67 => 0x67de, + 0x68 => 0x67f3, 0x69 => 0x67b0, 0x6a => 0x67d9, 0x6b => 0x67e2, + 0x6c => 0x67dd, 0x6d => 0x67d2, 0x6e => 0x6b6a, 0x6f => 0x6b83, + 0x70 => 0x6b86, 0x71 => 0x6bb5, 0x72 => 0x6bd2, 0x73 => 0x6bd7, + 0x74 => 0x6c1f, 0x75 => 0x6cc9, 0x76 => 0x6d0b, 0x77 => 0x6d32, + 0x78 => 0x6d2a, 0x79 => 0x6d41, 0x7a => 0x6d25, 0x7b => 0x6d0c, + 0x7c => 0x6d31, 0x7d => 0x6d1e, 0x7e => 0x6d17, 0xa1 => 0x6d3b, + 0xa2 => 0x6d3d, 0xa3 => 0x6d3e, 0xa4 => 0x6d36, 0xa5 => 0x6d1b, + 0xa6 => 0x6cf5, 0xa7 => 0x6d39, 0xa8 => 0x6d27, 0xa9 => 0x6d38, + 0xaa => 0x6d29, 0xab => 0x6d2e, 0xac => 0x6d35, 0xad => 0x6d0e, + 0xae => 0x6d2b, 0xaf => 0x70ab, 0xb0 => 0x70ba, 0xb1 => 0x70b3, + 0xb2 => 0x70ac, 0xb3 => 0x70af, 0xb4 => 0x70ad, 0xb5 => 0x70b8, + 0xb6 => 0x70ae, 0xb7 => 0x70a4, 0xb8 => 0x7230, 0xb9 => 0x7272, + 0xba => 0x726f, 0xbb => 0x7274, 0xbc => 0x72e9, 0xbd => 0x72e0, + 0xbe => 0x72e1, 0xbf => 0x73b7, 0xc0 => 0x73ca, 0xc1 => 0x73bb, + 0xc2 => 0x73b2, 0xc3 => 0x73cd, 0xc4 => 0x73c0, 0xc5 => 0x73b3, + 0xc6 => 0x751a, 0xc7 => 0x752d, 0xc8 => 0x754f, 0xc9 => 0x754c, + 0xca => 0x754e, 0xcb => 0x754b, 0xcc => 0x75ab, 0xcd => 0x75a4, + 0xce => 0x75a5, 0xcf => 0x75a2, 0xd0 => 0x75a3, 0xd1 => 0x7678, + 0xd2 => 0x7686, 0xd3 => 0x7687, 0xd4 => 0x7688, 0xd5 => 0x76c8, + 0xd6 => 0x76c6, 0xd7 => 0x76c3, 0xd8 => 0x76c5, 0xd9 => 0x7701, + 0xda => 0x76f9, 0xdb => 0x76f8, 0xdc => 0x7709, 0xdd => 0x770b, + 0xde => 0x76fe, 0xdf => 0x76fc, 0xe0 => 0x7707, 0xe1 => 0x77dc, + 0xe2 => 0x7802, 0xe3 => 0x7814, 0xe4 => 0x780c, 0xe5 => 0x780d, + 0xe6 => 0x7946, 0xe7 => 0x7949, 0xe8 => 0x7948, 0xe9 => 0x7947, + 0xea => 0x79b9, 0xeb => 0x79ba, 0xec => 0x79d1, 0xed => 0x79d2, + 0xee => 0x79cb, 0xef => 0x7a7f, 0xf0 => 0x7a81, 0xf1 => 0x7aff, + 0xf2 => 0x7afd, 0xf3 => 0x7c7d, 0xf4 => 0x7d02, 0xf5 => 0x7d05, + 0xf6 => 0x7d00, 0xf7 => 0x7d09, 0xf8 => 0x7d07, 0xf9 => 0x7d04, + 0xfa => 0x7d06, 0xfb => 0x7f38, 0xfc => 0x7f8e, 0xfd => 0x7fbf, + 0xfe => 0x8004, + }, + 0xad => { + 0x40 => 0x8010, 0x41 => 0x800d, 0x42 => 0x8011, 0x43 => 0x8036, + 0x44 => 0x80d6, 0x45 => 0x80e5, 0x46 => 0x80da, 0x47 => 0x80c3, + 0x48 => 0x80c4, 0x49 => 0x80cc, 0x4a => 0x80e1, 0x4b => 0x80db, + 0x4c => 0x80ce, 0x4d => 0x80de, 0x4e => 0x80e4, 0x4f => 0x80dd, + 0x50 => 0x81f4, 0x51 => 0x8222, 0x52 => 0x82e7, 0x53 => 0x8303, + 0x54 => 0x8305, 0x55 => 0x82e3, 0x56 => 0x82db, 0x57 => 0x82e6, + 0x58 => 0x8304, 0x59 => 0x82e5, 0x5a => 0x8302, 0x5b => 0x8309, + 0x5c => 0x82d2, 0x5d => 0x82d7, 0x5e => 0x82f1, 0x5f => 0x8301, + 0x60 => 0x82dc, 0x61 => 0x82d4, 0x62 => 0x82d1, 0x63 => 0x82de, + 0x64 => 0x82d3, 0x65 => 0x82df, 0x66 => 0x82ef, 0x67 => 0x8306, + 0x68 => 0x8650, 0x69 => 0x8679, 0x6a => 0x867b, 0x6b => 0x867a, + 0x6c => 0x884d, 0x6d => 0x886b, 0x6e => 0x8981, 0x6f => 0x89d4, + 0x70 => 0x8a08, 0x71 => 0x8a02, 0x72 => 0x8a03, 0x73 => 0x8c9e, + 0x74 => 0x8ca0, 0x75 => 0x8d74, 0x76 => 0x8d73, 0x77 => 0x8db4, + 0x78 => 0x8ecd, 0x79 => 0x8ecc, 0x7a => 0x8ff0, 0x7b => 0x8fe6, + 0x7c => 0x8fe2, 0x7d => 0x8fea, 0x7e => 0x8fe5, 0xa1 => 0x8fed, + 0xa2 => 0x8feb, 0xa3 => 0x8fe4, 0xa4 => 0x8fe8, 0xa5 => 0x90ca, + 0xa6 => 0x90ce, 0xa7 => 0x90c1, 0xa8 => 0x90c3, 0xa9 => 0x914b, + 0xaa => 0x914a, 0xab => 0x91cd, 0xac => 0x9582, 0xad => 0x9650, + 0xae => 0x964b, 0xaf => 0x964c, 0xb0 => 0x964d, 0xb1 => 0x9762, + 0xb2 => 0x9769, 0xb3 => 0x97cb, 0xb4 => 0x97ed, 0xb5 => 0x97f3, + 0xb6 => 0x9801, 0xb7 => 0x98a8, 0xb8 => 0x98db, 0xb9 => 0x98df, + 0xba => 0x9996, 0xbb => 0x9999, 0xbc => 0x4e58, 0xbd => 0x4eb3, + 0xbe => 0x500c, 0xbf => 0x500d, 0xc0 => 0x5023, 0xc1 => 0x4fef, + 0xc2 => 0x5026, 0xc3 => 0x5025, 0xc4 => 0x4ff8, 0xc5 => 0x5029, + 0xc6 => 0x5016, 0xc7 => 0x5006, 0xc8 => 0x503c, 0xc9 => 0x501f, + 0xca => 0x501a, 0xcb => 0x5012, 0xcc => 0x5011, 0xcd => 0x4ffa, + 0xce => 0x5000, 0xcf => 0x5014, 0xd0 => 0x5028, 0xd1 => 0x4ff1, + 0xd2 => 0x5021, 0xd3 => 0x500b, 0xd4 => 0x5019, 0xd5 => 0x5018, + 0xd6 => 0x4ff3, 0xd7 => 0x4fee, 0xd8 => 0x502d, 0xd9 => 0x502a, + 0xda => 0x4ffe, 0xdb => 0x502b, 0xdc => 0x5009, 0xdd => 0x517c, + 0xde => 0x51a4, 0xdf => 0x51a5, 0xe0 => 0x51a2, 0xe1 => 0x51cd, + 0xe2 => 0x51cc, 0xe3 => 0x51c6, 0xe4 => 0x51cb, 0xe5 => 0x5256, + 0xe6 => 0x525c, 0xe7 => 0x5254, 0xe8 => 0x525b, 0xe9 => 0x525d, + 0xea => 0x532a, 0xeb => 0x537f, 0xec => 0x539f, 0xed => 0x539d, + 0xee => 0x53df, 0xef => 0x54e8, 0xf0 => 0x5510, 0xf1 => 0x5501, + 0xf2 => 0x5537, 0xf3 => 0x54fc, 0xf4 => 0x54e5, 0xf5 => 0x54f2, + 0xf6 => 0x5506, 0xf7 => 0x54fa, 0xf8 => 0x5514, 0xf9 => 0x54e9, + 0xfa => 0x54ed, 0xfb => 0x54e1, 0xfc => 0x5509, 0xfd => 0x54ee, + 0xfe => 0x54ea, + }, + 0xae => { + 0x40 => 0x54e6, 0x41 => 0x5527, 0x42 => 0x5507, 0x43 => 0x54fd, + 0x44 => 0x550f, 0x45 => 0x5703, 0x46 => 0x5704, 0x47 => 0x57c2, + 0x48 => 0x57d4, 0x49 => 0x57cb, 0x4a => 0x57c3, 0x4b => 0x5809, + 0x4c => 0x590f, 0x4d => 0x5957, 0x4e => 0x5958, 0x4f => 0x595a, + 0x50 => 0x5a11, 0x51 => 0x5a18, 0x52 => 0x5a1c, 0x53 => 0x5a1f, + 0x54 => 0x5a1b, 0x55 => 0x5a13, 0x56 => 0x59ec, 0x57 => 0x5a20, + 0x58 => 0x5a23, 0x59 => 0x5a29, 0x5a => 0x5a25, 0x5b => 0x5a0c, + 0x5c => 0x5a09, 0x5d => 0x5b6b, 0x5e => 0x5c58, 0x5f => 0x5bb0, + 0x60 => 0x5bb3, 0x61 => 0x5bb6, 0x62 => 0x5bb4, 0x63 => 0x5bae, + 0x64 => 0x5bb5, 0x65 => 0x5bb9, 0x66 => 0x5bb8, 0x67 => 0x5c04, + 0x68 => 0x5c51, 0x69 => 0x5c55, 0x6a => 0x5c50, 0x6b => 0x5ced, + 0x6c => 0x5cfd, 0x6d => 0x5cfb, 0x6e => 0x5cea, 0x6f => 0x5ce8, + 0x70 => 0x5cf0, 0x71 => 0x5cf6, 0x72 => 0x5d01, 0x73 => 0x5cf4, + 0x74 => 0x5dee, 0x75 => 0x5e2d, 0x76 => 0x5e2b, 0x77 => 0x5eab, + 0x78 => 0x5ead, 0x79 => 0x5ea7, 0x7a => 0x5f31, 0x7b => 0x5f92, + 0x7c => 0x5f91, 0x7d => 0x5f90, 0x7e => 0x6059, 0xa1 => 0x6063, + 0xa2 => 0x6065, 0xa3 => 0x6050, 0xa4 => 0x6055, 0xa5 => 0x606d, + 0xa6 => 0x6069, 0xa7 => 0x606f, 0xa8 => 0x6084, 0xa9 => 0x609f, + 0xaa => 0x609a, 0xab => 0x608d, 0xac => 0x6094, 0xad => 0x608c, + 0xae => 0x6085, 0xaf => 0x6096, 0xb0 => 0x6247, 0xb1 => 0x62f3, + 0xb2 => 0x6308, 0xb3 => 0x62ff, 0xb4 => 0x634e, 0xb5 => 0x633e, + 0xb6 => 0x632f, 0xb7 => 0x6355, 0xb8 => 0x6342, 0xb9 => 0x6346, + 0xba => 0x634f, 0xbb => 0x6349, 0xbc => 0x633a, 0xbd => 0x6350, + 0xbe => 0x633d, 0xbf => 0x632a, 0xc0 => 0x632b, 0xc1 => 0x6328, + 0xc2 => 0x634d, 0xc3 => 0x634c, 0xc4 => 0x6548, 0xc5 => 0x6549, + 0xc6 => 0x6599, 0xc7 => 0x65c1, 0xc8 => 0x65c5, 0xc9 => 0x6642, + 0xca => 0x6649, 0xcb => 0x664f, 0xcc => 0x6643, 0xcd => 0x6652, + 0xce => 0x664c, 0xcf => 0x6645, 0xd0 => 0x6641, 0xd1 => 0x66f8, + 0xd2 => 0x6714, 0xd3 => 0x6715, 0xd4 => 0x6717, 0xd5 => 0x6821, + 0xd6 => 0x6838, 0xd7 => 0x6848, 0xd8 => 0x6846, 0xd9 => 0x6853, + 0xda => 0x6839, 0xdb => 0x6842, 0xdc => 0x6854, 0xdd => 0x6829, + 0xde => 0x68b3, 0xdf => 0x6817, 0xe0 => 0x684c, 0xe1 => 0x6851, + 0xe2 => 0x683d, 0xe3 => 0x67f4, 0xe4 => 0x6850, 0xe5 => 0x6840, + 0xe6 => 0x683c, 0xe7 => 0x6843, 0xe8 => 0x682a, 0xe9 => 0x6845, + 0xea => 0x6813, 0xeb => 0x6818, 0xec => 0x6841, 0xed => 0x6b8a, + 0xee => 0x6b89, 0xef => 0x6bb7, 0xf0 => 0x6c23, 0xf1 => 0x6c27, + 0xf2 => 0x6c28, 0xf3 => 0x6c26, 0xf4 => 0x6c24, 0xf5 => 0x6cf0, + 0xf6 => 0x6d6a, 0xf7 => 0x6d95, 0xf8 => 0x6d88, 0xf9 => 0x6d87, + 0xfa => 0x6d66, 0xfb => 0x6d78, 0xfc => 0x6d77, 0xfd => 0x6d59, + 0xfe => 0x6d93, + }, + 0xaf => { + 0x40 => 0x6d6c, 0x41 => 0x6d89, 0x42 => 0x6d6e, 0x43 => 0x6d5a, + 0x44 => 0x6d74, 0x45 => 0x6d69, 0x46 => 0x6d8c, 0x47 => 0x6d8a, + 0x48 => 0x6d79, 0x49 => 0x6d85, 0x4a => 0x6d65, 0x4b => 0x6d94, + 0x4c => 0x70ca, 0x4d => 0x70d8, 0x4e => 0x70e4, 0x4f => 0x70d9, + 0x50 => 0x70c8, 0x51 => 0x70cf, 0x52 => 0x7239, 0x53 => 0x7279, + 0x54 => 0x72fc, 0x55 => 0x72f9, 0x56 => 0x72fd, 0x57 => 0x72f8, + 0x58 => 0x72f7, 0x59 => 0x7386, 0x5a => 0x73ed, 0x5b => 0x7409, + 0x5c => 0x73ee, 0x5d => 0x73e0, 0x5e => 0x73ea, 0x5f => 0x73de, + 0x60 => 0x7554, 0x61 => 0x755d, 0x62 => 0x755c, 0x63 => 0x755a, + 0x64 => 0x7559, 0x65 => 0x75be, 0x66 => 0x75c5, 0x67 => 0x75c7, + 0x68 => 0x75b2, 0x69 => 0x75b3, 0x6a => 0x75bd, 0x6b => 0x75bc, + 0x6c => 0x75b9, 0x6d => 0x75c2, 0x6e => 0x75b8, 0x6f => 0x768b, + 0x70 => 0x76b0, 0x71 => 0x76ca, 0x72 => 0x76cd, 0x73 => 0x76ce, + 0x74 => 0x7729, 0x75 => 0x771f, 0x76 => 0x7720, 0x77 => 0x7728, + 0x78 => 0x77e9, 0x79 => 0x7830, 0x7a => 0x7827, 0x7b => 0x7838, + 0x7c => 0x781d, 0x7d => 0x7834, 0x7e => 0x7837, 0xa1 => 0x7825, + 0xa2 => 0x782d, 0xa3 => 0x7820, 0xa4 => 0x781f, 0xa5 => 0x7832, + 0xa6 => 0x7955, 0xa7 => 0x7950, 0xa8 => 0x7960, 0xa9 => 0x795f, + 0xaa => 0x7956, 0xab => 0x795e, 0xac => 0x795d, 0xad => 0x7957, + 0xae => 0x795a, 0xaf => 0x79e4, 0xb0 => 0x79e3, 0xb1 => 0x79e7, + 0xb2 => 0x79df, 0xb3 => 0x79e6, 0xb4 => 0x79e9, 0xb5 => 0x79d8, + 0xb6 => 0x7a84, 0xb7 => 0x7a88, 0xb8 => 0x7ad9, 0xb9 => 0x7b06, + 0xba => 0x7b11, 0xbb => 0x7c89, 0xbc => 0x7d21, 0xbd => 0x7d17, + 0xbe => 0x7d0b, 0xbf => 0x7d0a, 0xc0 => 0x7d20, 0xc1 => 0x7d22, + 0xc2 => 0x7d14, 0xc3 => 0x7d10, 0xc4 => 0x7d15, 0xc5 => 0x7d1a, + 0xc6 => 0x7d1c, 0xc7 => 0x7d0d, 0xc8 => 0x7d19, 0xc9 => 0x7d1b, + 0xca => 0x7f3a, 0xcb => 0x7f5f, 0xcc => 0x7f94, 0xcd => 0x7fc5, + 0xce => 0x7fc1, 0xcf => 0x8006, 0xd0 => 0x8018, 0xd1 => 0x8015, + 0xd2 => 0x8019, 0xd3 => 0x8017, 0xd4 => 0x803d, 0xd5 => 0x803f, + 0xd6 => 0x80f1, 0xd7 => 0x8102, 0xd8 => 0x80f0, 0xd9 => 0x8105, + 0xda => 0x80ed, 0xdb => 0x80f4, 0xdc => 0x8106, 0xdd => 0x80f8, + 0xde => 0x80f3, 0xdf => 0x8108, 0xe0 => 0x80fd, 0xe1 => 0x810a, + 0xe2 => 0x80fc, 0xe3 => 0x80ef, 0xe4 => 0x81ed, 0xe5 => 0x81ec, + 0xe6 => 0x8200, 0xe7 => 0x8210, 0xe8 => 0x822a, 0xe9 => 0x822b, + 0xea => 0x8228, 0xeb => 0x822c, 0xec => 0x82bb, 0xed => 0x832b, + 0xee => 0x8352, 0xef => 0x8354, 0xf0 => 0x834a, 0xf1 => 0x8338, + 0xf2 => 0x8350, 0xf3 => 0x8349, 0xf4 => 0x8335, 0xf5 => 0x8334, + 0xf6 => 0x834f, 0xf7 => 0x8332, 0xf8 => 0x8339, 0xf9 => 0x8336, + 0xfa => 0x8317, 0xfb => 0x8340, 0xfc => 0x8331, 0xfd => 0x8328, + 0xfe => 0x8343, + }, + 0xb0 => { + 0x40 => 0x8654, 0x41 => 0x868a, 0x42 => 0x86aa, 0x43 => 0x8693, + 0x44 => 0x86a4, 0x45 => 0x86a9, 0x46 => 0x868c, 0x47 => 0x86a3, + 0x48 => 0x869c, 0x49 => 0x8870, 0x4a => 0x8877, 0x4b => 0x8881, + 0x4c => 0x8882, 0x4d => 0x887d, 0x4e => 0x8879, 0x4f => 0x8a18, + 0x50 => 0x8a10, 0x51 => 0x8a0e, 0x52 => 0x8a0c, 0x53 => 0x8a15, + 0x54 => 0x8a0a, 0x55 => 0x8a17, 0x56 => 0x8a13, 0x57 => 0x8a16, + 0x58 => 0x8a0f, 0x59 => 0x8a11, 0x5a => 0x8c48, 0x5b => 0x8c7a, + 0x5c => 0x8c79, 0x5d => 0x8ca1, 0x5e => 0x8ca2, 0x5f => 0x8d77, + 0x60 => 0x8eac, 0x61 => 0x8ed2, 0x62 => 0x8ed4, 0x63 => 0x8ecf, + 0x64 => 0x8fb1, 0x65 => 0x9001, 0x66 => 0x9006, 0x67 => 0x8ff7, + 0x68 => 0x9000, 0x69 => 0x8ffa, 0x6a => 0x8ff4, 0x6b => 0x9003, + 0x6c => 0x8ffd, 0x6d => 0x9005, 0x6e => 0x8ff8, 0x6f => 0x9095, + 0x70 => 0x90e1, 0x71 => 0x90dd, 0x72 => 0x90e2, 0x73 => 0x9152, + 0x74 => 0x914d, 0x75 => 0x914c, 0x76 => 0x91d8, 0x77 => 0x91dd, + 0x78 => 0x91d7, 0x79 => 0x91dc, 0x7a => 0x91d9, 0x7b => 0x9583, + 0x7c => 0x9662, 0x7d => 0x9663, 0x7e => 0x9661, 0xa1 => 0x965b, + 0xa2 => 0x965d, 0xa3 => 0x9664, 0xa4 => 0x9658, 0xa5 => 0x965e, + 0xa6 => 0x96bb, 0xa7 => 0x98e2, 0xa8 => 0x99ac, 0xa9 => 0x9aa8, + 0xaa => 0x9ad8, 0xab => 0x9b25, 0xac => 0x9b32, 0xad => 0x9b3c, + 0xae => 0x4e7e, 0xaf => 0x507a, 0xb0 => 0x507d, 0xb1 => 0x505c, + 0xb2 => 0x5047, 0xb3 => 0x5043, 0xb4 => 0x504c, 0xb5 => 0x505a, + 0xb6 => 0x5049, 0xb7 => 0x5065, 0xb8 => 0x5076, 0xb9 => 0x504e, + 0xba => 0x5055, 0xbb => 0x5075, 0xbc => 0x5074, 0xbd => 0x5077, + 0xbe => 0x504f, 0xbf => 0x500f, 0xc0 => 0x506f, 0xc1 => 0x506d, + 0xc2 => 0x515c, 0xc3 => 0x5195, 0xc4 => 0x51f0, 0xc5 => 0x526a, + 0xc6 => 0x526f, 0xc7 => 0x52d2, 0xc8 => 0x52d9, 0xc9 => 0x52d8, + 0xca => 0x52d5, 0xcb => 0x5310, 0xcc => 0x530f, 0xcd => 0x5319, + 0xce => 0x533f, 0xcf => 0x5340, 0xd0 => 0x533e, 0xd1 => 0x53c3, + 0xd2 => 0x66fc, 0xd3 => 0x5546, 0xd4 => 0x556a, 0xd5 => 0x5566, + 0xd6 => 0x5544, 0xd7 => 0x555e, 0xd8 => 0x5561, 0xd9 => 0x5543, + 0xda => 0x554a, 0xdb => 0x5531, 0xdc => 0x5556, 0xdd => 0x554f, + 0xde => 0x5555, 0xdf => 0x552f, 0xe0 => 0x5564, 0xe1 => 0x5538, + 0xe2 => 0x552e, 0xe3 => 0x555c, 0xe4 => 0x552c, 0xe5 => 0x5563, + 0xe6 => 0x5533, 0xe7 => 0x5541, 0xe8 => 0x5557, 0xe9 => 0x5708, + 0xea => 0x570b, 0xeb => 0x5709, 0xec => 0x57df, 0xed => 0x5805, + 0xee => 0x580a, 0xef => 0x5806, 0xf0 => 0x57e0, 0xf1 => 0x57e4, + 0xf2 => 0x57fa, 0xf3 => 0x5802, 0xf4 => 0x5835, 0xf5 => 0x57f7, + 0xf6 => 0x57f9, 0xf7 => 0x5920, 0xf8 => 0x5962, 0xf9 => 0x5a36, + 0xfa => 0x5a41, 0xfb => 0x5a49, 0xfc => 0x5a66, 0xfd => 0x5a6a, + 0xfe => 0x5a40, + }, + 0xb1 => { + 0x40 => 0x5a3c, 0x41 => 0x5a62, 0x42 => 0x5a5a, 0x43 => 0x5a46, + 0x44 => 0x5a4a, 0x45 => 0x5b70, 0x46 => 0x5bc7, 0x47 => 0x5bc5, + 0x48 => 0x5bc4, 0x49 => 0x5bc2, 0x4a => 0x5bbf, 0x4b => 0x5bc6, + 0x4c => 0x5c09, 0x4d => 0x5c08, 0x4e => 0x5c07, 0x4f => 0x5c60, + 0x50 => 0x5c5c, 0x51 => 0x5c5d, 0x52 => 0x5d07, 0x53 => 0x5d06, + 0x54 => 0x5d0e, 0x55 => 0x5d1b, 0x56 => 0x5d16, 0x57 => 0x5d22, + 0x58 => 0x5d11, 0x59 => 0x5d29, 0x5a => 0x5d14, 0x5b => 0x5d19, + 0x5c => 0x5d24, 0x5d => 0x5d27, 0x5e => 0x5d17, 0x5f => 0x5de2, + 0x60 => 0x5e38, 0x61 => 0x5e36, 0x62 => 0x5e33, 0x63 => 0x5e37, + 0x64 => 0x5eb7, 0x65 => 0x5eb8, 0x66 => 0x5eb6, 0x67 => 0x5eb5, + 0x68 => 0x5ebe, 0x69 => 0x5f35, 0x6a => 0x5f37, 0x6b => 0x5f57, + 0x6c => 0x5f6c, 0x6d => 0x5f69, 0x6e => 0x5f6b, 0x6f => 0x5f97, + 0x70 => 0x5f99, 0x71 => 0x5f9e, 0x72 => 0x5f98, 0x73 => 0x5fa1, + 0x74 => 0x5fa0, 0x75 => 0x5f9c, 0x76 => 0x607f, 0x77 => 0x60a3, + 0x78 => 0x6089, 0x79 => 0x60a0, 0x7a => 0x60a8, 0x7b => 0x60cb, + 0x7c => 0x60b4, 0x7d => 0x60e6, 0x7e => 0x60bd, 0xa1 => 0x60c5, + 0xa2 => 0x60bb, 0xa3 => 0x60b5, 0xa4 => 0x60dc, 0xa5 => 0x60bc, + 0xa6 => 0x60d8, 0xa7 => 0x60d5, 0xa8 => 0x60c6, 0xa9 => 0x60df, + 0xaa => 0x60b8, 0xab => 0x60da, 0xac => 0x60c7, 0xad => 0x621a, + 0xae => 0x621b, 0xaf => 0x6248, 0xb0 => 0x63a0, 0xb1 => 0x63a7, + 0xb2 => 0x6372, 0xb3 => 0x6396, 0xb4 => 0x63a2, 0xb5 => 0x63a5, + 0xb6 => 0x6377, 0xb7 => 0x6367, 0xb8 => 0x6398, 0xb9 => 0x63aa, + 0xba => 0x6371, 0xbb => 0x63a9, 0xbc => 0x6389, 0xbd => 0x6383, + 0xbe => 0x639b, 0xbf => 0x636b, 0xc0 => 0x63a8, 0xc1 => 0x6384, + 0xc2 => 0x6388, 0xc3 => 0x6399, 0xc4 => 0x63a1, 0xc5 => 0x63ac, + 0xc6 => 0x6392, 0xc7 => 0x638f, 0xc8 => 0x6380, 0xc9 => 0x637b, + 0xca => 0x6369, 0xcb => 0x6368, 0xcc => 0x637a, 0xcd => 0x655d, + 0xce => 0x6556, 0xcf => 0x6551, 0xd0 => 0x6559, 0xd1 => 0x6557, + 0xd2 => 0x555f, 0xd3 => 0x654f, 0xd4 => 0x6558, 0xd5 => 0x6555, + 0xd6 => 0x6554, 0xd7 => 0x659c, 0xd8 => 0x659b, 0xd9 => 0x65ac, + 0xda => 0x65cf, 0xdb => 0x65cb, 0xdc => 0x65cc, 0xdd => 0x65ce, + 0xde => 0x665d, 0xdf => 0x665a, 0xe0 => 0x6664, 0xe1 => 0x6668, + 0xe2 => 0x6666, 0xe3 => 0x665e, 0xe4 => 0x66f9, 0xe5 => 0x52d7, + 0xe6 => 0x671b, 0xe7 => 0x6881, 0xe8 => 0x68af, 0xe9 => 0x68a2, + 0xea => 0x6893, 0xeb => 0x68b5, 0xec => 0x687f, 0xed => 0x6876, + 0xee => 0x68b1, 0xef => 0x68a7, 0xf0 => 0x6897, 0xf1 => 0x68b0, + 0xf2 => 0x6883, 0xf3 => 0x68c4, 0xf4 => 0x68ad, 0xf5 => 0x6886, + 0xf6 => 0x6885, 0xf7 => 0x6894, 0xf8 => 0x689d, 0xf9 => 0x68a8, + 0xfa => 0x689f, 0xfb => 0x68a1, 0xfc => 0x6882, 0xfd => 0x6b32, + 0xfe => 0x6bba, + }, + 0xb2 => { + 0x40 => 0x6beb, 0x41 => 0x6bec, 0x42 => 0x6c2b, 0x43 => 0x6d8e, + 0x44 => 0x6dbc, 0x45 => 0x6df3, 0x46 => 0x6dd9, 0x47 => 0x6db2, + 0x48 => 0x6de1, 0x49 => 0x6dcc, 0x4a => 0x6de4, 0x4b => 0x6dfb, + 0x4c => 0x6dfa, 0x4d => 0x6e05, 0x4e => 0x6dc7, 0x4f => 0x6dcb, + 0x50 => 0x6daf, 0x51 => 0x6dd1, 0x52 => 0x6dae, 0x53 => 0x6dde, + 0x54 => 0x6df9, 0x55 => 0x6db8, 0x56 => 0x6df7, 0x57 => 0x6df5, + 0x58 => 0x6dc5, 0x59 => 0x6dd2, 0x5a => 0x6e1a, 0x5b => 0x6db5, + 0x5c => 0x6dda, 0x5d => 0x6deb, 0x5e => 0x6dd8, 0x5f => 0x6dea, + 0x60 => 0x6df1, 0x61 => 0x6dee, 0x62 => 0x6de8, 0x63 => 0x6dc6, + 0x64 => 0x6dc4, 0x65 => 0x6daa, 0x66 => 0x6dec, 0x67 => 0x6dbf, + 0x68 => 0x6de6, 0x69 => 0x70f9, 0x6a => 0x7109, 0x6b => 0x710a, + 0x6c => 0x70fd, 0x6d => 0x70ef, 0x6e => 0x723d, 0x6f => 0x727d, + 0x70 => 0x7281, 0x71 => 0x731c, 0x72 => 0x731b, 0x73 => 0x7316, + 0x74 => 0x7313, 0x75 => 0x7319, 0x76 => 0x7387, 0x77 => 0x7405, + 0x78 => 0x740a, 0x79 => 0x7403, 0x7a => 0x7406, 0x7b => 0x73fe, + 0x7c => 0x740d, 0x7d => 0x74e0, 0x7e => 0x74f6, 0xa1 => 0x74f7, + 0xa2 => 0x751c, 0xa3 => 0x7522, 0xa4 => 0x7565, 0xa5 => 0x7566, + 0xa6 => 0x7562, 0xa7 => 0x7570, 0xa8 => 0x758f, 0xa9 => 0x75d4, + 0xaa => 0x75d5, 0xab => 0x75b5, 0xac => 0x75ca, 0xad => 0x75cd, + 0xae => 0x768e, 0xaf => 0x76d4, 0xb0 => 0x76d2, 0xb1 => 0x76db, + 0xb2 => 0x7737, 0xb3 => 0x773e, 0xb4 => 0x773c, 0xb5 => 0x7736, + 0xb6 => 0x7738, 0xb7 => 0x773a, 0xb8 => 0x786b, 0xb9 => 0x7843, + 0xba => 0x784e, 0xbb => 0x7965, 0xbc => 0x7968, 0xbd => 0x796d, + 0xbe => 0x79fb, 0xbf => 0x7a92, 0xc0 => 0x7a95, 0xc1 => 0x7b20, + 0xc2 => 0x7b28, 0xc3 => 0x7b1b, 0xc4 => 0x7b2c, 0xc5 => 0x7b26, + 0xc6 => 0x7b19, 0xc7 => 0x7b1e, 0xc8 => 0x7b2e, 0xc9 => 0x7c92, + 0xca => 0x7c97, 0xcb => 0x7c95, 0xcc => 0x7d46, 0xcd => 0x7d43, + 0xce => 0x7d71, 0xcf => 0x7d2e, 0xd0 => 0x7d39, 0xd1 => 0x7d3c, + 0xd2 => 0x7d40, 0xd3 => 0x7d30, 0xd4 => 0x7d33, 0xd5 => 0x7d44, + 0xd6 => 0x7d2f, 0xd7 => 0x7d42, 0xd8 => 0x7d32, 0xd9 => 0x7d31, + 0xda => 0x7f3d, 0xdb => 0x7f9e, 0xdc => 0x7f9a, 0xdd => 0x7fcc, + 0xde => 0x7fce, 0xdf => 0x7fd2, 0xe0 => 0x801c, 0xe1 => 0x804a, + 0xe2 => 0x8046, 0xe3 => 0x812f, 0xe4 => 0x8116, 0xe5 => 0x8123, + 0xe6 => 0x812b, 0xe7 => 0x8129, 0xe8 => 0x8130, 0xe9 => 0x8124, + 0xea => 0x8202, 0xeb => 0x8235, 0xec => 0x8237, 0xed => 0x8236, + 0xee => 0x8239, 0xef => 0x838e, 0xf0 => 0x839e, 0xf1 => 0x8398, + 0xf2 => 0x8378, 0xf3 => 0x83a2, 0xf4 => 0x8396, 0xf5 => 0x83bd, + 0xf6 => 0x83ab, 0xf7 => 0x8392, 0xf8 => 0x838a, 0xf9 => 0x8393, + 0xfa => 0x8389, 0xfb => 0x83a0, 0xfc => 0x8377, 0xfd => 0x837b, + 0xfe => 0x837c, + }, + 0xb3 => { + 0x40 => 0x8386, 0x41 => 0x83a7, 0x42 => 0x8655, 0x43 => 0x5f6a, + 0x44 => 0x86c7, 0x45 => 0x86c0, 0x46 => 0x86b6, 0x47 => 0x86c4, + 0x48 => 0x86b5, 0x49 => 0x86c6, 0x4a => 0x86cb, 0x4b => 0x86b1, + 0x4c => 0x86af, 0x4d => 0x86c9, 0x4e => 0x8853, 0x4f => 0x889e, + 0x50 => 0x8888, 0x51 => 0x88ab, 0x52 => 0x8892, 0x53 => 0x8896, + 0x54 => 0x888d, 0x55 => 0x888b, 0x56 => 0x8993, 0x57 => 0x898f, + 0x58 => 0x8a2a, 0x59 => 0x8a1d, 0x5a => 0x8a23, 0x5b => 0x8a25, + 0x5c => 0x8a31, 0x5d => 0x8a2d, 0x5e => 0x8a1f, 0x5f => 0x8a1b, + 0x60 => 0x8a22, 0x61 => 0x8c49, 0x62 => 0x8c5a, 0x63 => 0x8ca9, + 0x64 => 0x8cac, 0x65 => 0x8cab, 0x66 => 0x8ca8, 0x67 => 0x8caa, + 0x68 => 0x8ca7, 0x69 => 0x8d67, 0x6a => 0x8d66, 0x6b => 0x8dbe, + 0x6c => 0x8dba, 0x6d => 0x8edb, 0x6e => 0x8edf, 0x6f => 0x9019, + 0x70 => 0x900d, 0x71 => 0x901a, 0x72 => 0x9017, 0x73 => 0x9023, + 0x74 => 0x901f, 0x75 => 0x901d, 0x76 => 0x9010, 0x77 => 0x9015, + 0x78 => 0x901e, 0x79 => 0x9020, 0x7a => 0x900f, 0x7b => 0x9022, + 0x7c => 0x9016, 0x7d => 0x901b, 0x7e => 0x9014, 0xa1 => 0x90e8, + 0xa2 => 0x90ed, 0xa3 => 0x90fd, 0xa4 => 0x9157, 0xa5 => 0x91ce, + 0xa6 => 0x91f5, 0xa7 => 0x91e6, 0xa8 => 0x91e3, 0xa9 => 0x91e7, + 0xaa => 0x91ed, 0xab => 0x91e9, 0xac => 0x9589, 0xad => 0x966a, + 0xae => 0x9675, 0xaf => 0x9673, 0xb0 => 0x9678, 0xb1 => 0x9670, + 0xb2 => 0x9674, 0xb3 => 0x9676, 0xb4 => 0x9677, 0xb5 => 0x966c, + 0xb6 => 0x96c0, 0xb7 => 0x96ea, 0xb8 => 0x96e9, 0xb9 => 0x7ae0, + 0xba => 0x7adf, 0xbb => 0x9802, 0xbc => 0x9803, 0xbd => 0x9b5a, + 0xbe => 0x9ce5, 0xbf => 0x9e75, 0xc0 => 0x9e7f, 0xc1 => 0x9ea5, + 0xc2 => 0x9ebb, 0xc3 => 0x50a2, 0xc4 => 0x508d, 0xc5 => 0x5085, + 0xc6 => 0x5099, 0xc7 => 0x5091, 0xc8 => 0x5080, 0xc9 => 0x5096, + 0xca => 0x5098, 0xcb => 0x509a, 0xcc => 0x6700, 0xcd => 0x51f1, + 0xce => 0x5272, 0xcf => 0x5274, 0xd0 => 0x5275, 0xd1 => 0x5269, + 0xd2 => 0x52de, 0xd3 => 0x52dd, 0xd4 => 0x52db, 0xd5 => 0x535a, + 0xd6 => 0x53a5, 0xd7 => 0x557b, 0xd8 => 0x5580, 0xd9 => 0x55a7, + 0xda => 0x557c, 0xdb => 0x558a, 0xdc => 0x559d, 0xdd => 0x5598, + 0xde => 0x5582, 0xdf => 0x559c, 0xe0 => 0x55aa, 0xe1 => 0x5594, + 0xe2 => 0x5587, 0xe3 => 0x558b, 0xe4 => 0x5583, 0xe5 => 0x55b3, + 0xe6 => 0x55ae, 0xe7 => 0x559f, 0xe8 => 0x553e, 0xe9 => 0x55b2, + 0xea => 0x559a, 0xeb => 0x55bb, 0xec => 0x55ac, 0xed => 0x55b1, + 0xee => 0x557e, 0xef => 0x5589, 0xf0 => 0x55ab, 0xf1 => 0x5599, + 0xf2 => 0x570d, 0xf3 => 0x582f, 0xf4 => 0x582a, 0xf5 => 0x5834, + 0xf6 => 0x5824, 0xf7 => 0x5830, 0xf8 => 0x5831, 0xf9 => 0x5821, + 0xfa => 0x581d, 0xfb => 0x5820, 0xfc => 0x58f9, 0xfd => 0x58fa, + 0xfe => 0x5960, + }, + 0xb4 => { + 0x40 => 0x5a77, 0x41 => 0x5a9a, 0x42 => 0x5a7f, 0x43 => 0x5a92, + 0x44 => 0x5a9b, 0x45 => 0x5aa7, 0x46 => 0x5b73, 0x47 => 0x5b71, + 0x48 => 0x5bd2, 0x49 => 0x5bcc, 0x4a => 0x5bd3, 0x4b => 0x5bd0, + 0x4c => 0x5c0a, 0x4d => 0x5c0b, 0x4e => 0x5c31, 0x4f => 0x5d4c, + 0x50 => 0x5d50, 0x51 => 0x5d34, 0x52 => 0x5d47, 0x53 => 0x5dfd, + 0x54 => 0x5e45, 0x55 => 0x5e3d, 0x56 => 0x5e40, 0x57 => 0x5e43, + 0x58 => 0x5e7e, 0x59 => 0x5eca, 0x5a => 0x5ec1, 0x5b => 0x5ec2, + 0x5c => 0x5ec4, 0x5d => 0x5f3c, 0x5e => 0x5f6d, 0x5f => 0x5fa9, + 0x60 => 0x5faa, 0x61 => 0x5fa8, 0x62 => 0x60d1, 0x63 => 0x60e1, + 0x64 => 0x60b2, 0x65 => 0x60b6, 0x66 => 0x60e0, 0x67 => 0x611c, + 0x68 => 0x6123, 0x69 => 0x60fa, 0x6a => 0x6115, 0x6b => 0x60f0, + 0x6c => 0x60fb, 0x6d => 0x60f4, 0x6e => 0x6168, 0x6f => 0x60f1, + 0x70 => 0x610e, 0x71 => 0x60f6, 0x72 => 0x6109, 0x73 => 0x6100, + 0x74 => 0x6112, 0x75 => 0x621f, 0x76 => 0x6249, 0x77 => 0x63a3, + 0x78 => 0x638c, 0x79 => 0x63cf, 0x7a => 0x63c0, 0x7b => 0x63e9, + 0x7c => 0x63c9, 0x7d => 0x63c6, 0x7e => 0x63cd, 0xa1 => 0x63d2, + 0xa2 => 0x63e3, 0xa3 => 0x63d0, 0xa4 => 0x63e1, 0xa5 => 0x63d6, + 0xa6 => 0x63ed, 0xa7 => 0x63ee, 0xa8 => 0x6376, 0xa9 => 0x63f4, + 0xaa => 0x63ea, 0xab => 0x63db, 0xac => 0x6452, 0xad => 0x63da, + 0xae => 0x63f9, 0xaf => 0x655e, 0xb0 => 0x6566, 0xb1 => 0x6562, + 0xb2 => 0x6563, 0xb3 => 0x6591, 0xb4 => 0x6590, 0xb5 => 0x65af, + 0xb6 => 0x666e, 0xb7 => 0x6670, 0xb8 => 0x6674, 0xb9 => 0x6676, + 0xba => 0x666f, 0xbb => 0x6691, 0xbc => 0x667a, 0xbd => 0x667e, + 0xbe => 0x6677, 0xbf => 0x66fe, 0xc0 => 0x66ff, 0xc1 => 0x671f, + 0xc2 => 0x671d, 0xc3 => 0x68fa, 0xc4 => 0x68d5, 0xc5 => 0x68e0, + 0xc6 => 0x68d8, 0xc7 => 0x68d7, 0xc8 => 0x6905, 0xc9 => 0x68df, + 0xca => 0x68f5, 0xcb => 0x68ee, 0xcc => 0x68e7, 0xcd => 0x68f9, + 0xce => 0x68d2, 0xcf => 0x68f2, 0xd0 => 0x68e3, 0xd1 => 0x68cb, + 0xd2 => 0x68cd, 0xd3 => 0x690d, 0xd4 => 0x6912, 0xd5 => 0x690e, + 0xd6 => 0x68c9, 0xd7 => 0x68da, 0xd8 => 0x696e, 0xd9 => 0x68fb, + 0xda => 0x6b3e, 0xdb => 0x6b3a, 0xdc => 0x6b3d, 0xdd => 0x6b98, + 0xde => 0x6b96, 0xdf => 0x6bbc, 0xe0 => 0x6bef, 0xe1 => 0x6c2e, + 0xe2 => 0x6c2f, 0xe3 => 0x6c2c, 0xe4 => 0x6e2f, 0xe5 => 0x6e38, + 0xe6 => 0x6e54, 0xe7 => 0x6e21, 0xe8 => 0x6e32, 0xe9 => 0x6e67, + 0xea => 0x6e4a, 0xeb => 0x6e20, 0xec => 0x6e25, 0xed => 0x6e23, + 0xee => 0x6e1b, 0xef => 0x6e5b, 0xf0 => 0x6e58, 0xf1 => 0x6e24, + 0xf2 => 0x6e56, 0xf3 => 0x6e6e, 0xf4 => 0x6e2d, 0xf5 => 0x6e26, + 0xf6 => 0x6e6f, 0xf7 => 0x6e34, 0xf8 => 0x6e4d, 0xf9 => 0x6e3a, + 0xfa => 0x6e2c, 0xfb => 0x6e43, 0xfc => 0x6e1d, 0xfd => 0x6e3e, + 0xfe => 0x6ecb, + }, + 0xb5 => { + 0x40 => 0x6e89, 0x41 => 0x6e19, 0x42 => 0x6e4e, 0x43 => 0x6e63, + 0x44 => 0x6e44, 0x45 => 0x6e72, 0x46 => 0x6e69, 0x47 => 0x6e5f, + 0x48 => 0x7119, 0x49 => 0x711a, 0x4a => 0x7126, 0x4b => 0x7130, + 0x4c => 0x7121, 0x4d => 0x7136, 0x4e => 0x716e, 0x4f => 0x711c, + 0x50 => 0x724c, 0x51 => 0x7284, 0x52 => 0x7280, 0x53 => 0x7336, + 0x54 => 0x7325, 0x55 => 0x7334, 0x56 => 0x7329, 0x57 => 0x743a, + 0x58 => 0x742a, 0x59 => 0x7433, 0x5a => 0x7422, 0x5b => 0x7425, + 0x5c => 0x7435, 0x5d => 0x7436, 0x5e => 0x7434, 0x5f => 0x742f, + 0x60 => 0x741b, 0x61 => 0x7426, 0x62 => 0x7428, 0x63 => 0x7525, + 0x64 => 0x7526, 0x65 => 0x756b, 0x66 => 0x756a, 0x67 => 0x75e2, + 0x68 => 0x75db, 0x69 => 0x75e3, 0x6a => 0x75d9, 0x6b => 0x75d8, + 0x6c => 0x75de, 0x6d => 0x75e0, 0x6e => 0x767b, 0x6f => 0x767c, + 0x70 => 0x7696, 0x71 => 0x7693, 0x72 => 0x76b4, 0x73 => 0x76dc, + 0x74 => 0x774f, 0x75 => 0x77ed, 0x76 => 0x785d, 0x77 => 0x786c, + 0x78 => 0x786f, 0x79 => 0x7a0d, 0x7a => 0x7a08, 0x7b => 0x7a0b, + 0x7c => 0x7a05, 0x7d => 0x7a00, 0x7e => 0x7a98, 0xa1 => 0x7a97, + 0xa2 => 0x7a96, 0xa3 => 0x7ae5, 0xa4 => 0x7ae3, 0xa5 => 0x7b49, + 0xa6 => 0x7b56, 0xa7 => 0x7b46, 0xa8 => 0x7b50, 0xa9 => 0x7b52, + 0xaa => 0x7b54, 0xab => 0x7b4d, 0xac => 0x7b4b, 0xad => 0x7b4f, + 0xae => 0x7b51, 0xaf => 0x7c9f, 0xb0 => 0x7ca5, 0xb1 => 0x7d5e, + 0xb2 => 0x7d50, 0xb3 => 0x7d68, 0xb4 => 0x7d55, 0xb5 => 0x7d2b, + 0xb6 => 0x7d6e, 0xb7 => 0x7d72, 0xb8 => 0x7d61, 0xb9 => 0x7d66, + 0xba => 0x7d62, 0xbb => 0x7d70, 0xbc => 0x7d73, 0xbd => 0x5584, + 0xbe => 0x7fd4, 0xbf => 0x7fd5, 0xc0 => 0x800b, 0xc1 => 0x8052, + 0xc2 => 0x8085, 0xc3 => 0x8155, 0xc4 => 0x8154, 0xc5 => 0x814b, + 0xc6 => 0x8151, 0xc7 => 0x814e, 0xc8 => 0x8139, 0xc9 => 0x8146, + 0xca => 0x813e, 0xcb => 0x814c, 0xcc => 0x8153, 0xcd => 0x8174, + 0xce => 0x8212, 0xcf => 0x821c, 0xd0 => 0x83e9, 0xd1 => 0x8403, + 0xd2 => 0x83f8, 0xd3 => 0x840d, 0xd4 => 0x83e0, 0xd5 => 0x83c5, + 0xd6 => 0x840b, 0xd7 => 0x83c1, 0xd8 => 0x83ef, 0xd9 => 0x83f1, + 0xda => 0x83f4, 0xdb => 0x8457, 0xdc => 0x840a, 0xdd => 0x83f0, + 0xde => 0x840c, 0xdf => 0x83cc, 0xe0 => 0x83fd, 0xe1 => 0x83f2, + 0xe2 => 0x83ca, 0xe3 => 0x8438, 0xe4 => 0x840e, 0xe5 => 0x8404, + 0xe6 => 0x83dc, 0xe7 => 0x8407, 0xe8 => 0x83d4, 0xe9 => 0x83df, + 0xea => 0x865b, 0xeb => 0x86df, 0xec => 0x86d9, 0xed => 0x86ed, + 0xee => 0x86d4, 0xef => 0x86db, 0xf0 => 0x86e4, 0xf1 => 0x86d0, + 0xf2 => 0x86de, 0xf3 => 0x8857, 0xf4 => 0x88c1, 0xf5 => 0x88c2, + 0xf6 => 0x88b1, 0xf7 => 0x8983, 0xf8 => 0x8996, 0xf9 => 0x8a3b, + 0xfa => 0x8a60, 0xfb => 0x8a55, 0xfc => 0x8a5e, 0xfd => 0x8a3c, + 0xfe => 0x8a41, + }, + 0xb6 => { + 0x40 => 0x8a54, 0x41 => 0x8a5b, 0x42 => 0x8a50, 0x43 => 0x8a46, + 0x44 => 0x8a34, 0x45 => 0x8a3a, 0x46 => 0x8a36, 0x47 => 0x8a56, + 0x48 => 0x8c61, 0x49 => 0x8c82, 0x4a => 0x8caf, 0x4b => 0x8cbc, + 0x4c => 0x8cb3, 0x4d => 0x8cbd, 0x4e => 0x8cc1, 0x4f => 0x8cbb, + 0x50 => 0x8cc0, 0x51 => 0x8cb4, 0x52 => 0x8cb7, 0x53 => 0x8cb6, + 0x54 => 0x8cbf, 0x55 => 0x8cb8, 0x56 => 0x8d8a, 0x57 => 0x8d85, + 0x58 => 0x8d81, 0x59 => 0x8dce, 0x5a => 0x8ddd, 0x5b => 0x8dcb, + 0x5c => 0x8dda, 0x5d => 0x8dd1, 0x5e => 0x8dcc, 0x5f => 0x8ddb, + 0x60 => 0x8dc6, 0x61 => 0x8efb, 0x62 => 0x8ef8, 0x63 => 0x8efc, + 0x64 => 0x8f9c, 0x65 => 0x902e, 0x66 => 0x9035, 0x67 => 0x9031, + 0x68 => 0x9038, 0x69 => 0x9032, 0x6a => 0x9036, 0x6b => 0x9102, + 0x6c => 0x90f5, 0x6d => 0x9109, 0x6e => 0x90fe, 0x6f => 0x9163, + 0x70 => 0x9165, 0x71 => 0x91cf, 0x72 => 0x9214, 0x73 => 0x9215, + 0x74 => 0x9223, 0x75 => 0x9209, 0x76 => 0x921e, 0x77 => 0x920d, + 0x78 => 0x9210, 0x79 => 0x9207, 0x7a => 0x9211, 0x7b => 0x9594, + 0x7c => 0x958f, 0x7d => 0x958b, 0x7e => 0x9591, 0xa1 => 0x9593, + 0xa2 => 0x9592, 0xa3 => 0x958e, 0xa4 => 0x968a, 0xa5 => 0x968e, + 0xa6 => 0x968b, 0xa7 => 0x967d, 0xa8 => 0x9685, 0xa9 => 0x9686, + 0xaa => 0x968d, 0xab => 0x9672, 0xac => 0x9684, 0xad => 0x96c1, + 0xae => 0x96c5, 0xaf => 0x96c4, 0xb0 => 0x96c6, 0xb1 => 0x96c7, + 0xb2 => 0x96ef, 0xb3 => 0x96f2, 0xb4 => 0x97cc, 0xb5 => 0x9805, + 0xb6 => 0x9806, 0xb7 => 0x9808, 0xb8 => 0x98e7, 0xb9 => 0x98ea, + 0xba => 0x98ef, 0xbb => 0x98e9, 0xbc => 0x98f2, 0xbd => 0x98ed, + 0xbe => 0x99ae, 0xbf => 0x99ad, 0xc0 => 0x9ec3, 0xc1 => 0x9ecd, + 0xc2 => 0x9ed1, 0xc3 => 0x4e82, 0xc4 => 0x50ad, 0xc5 => 0x50b5, + 0xc6 => 0x50b2, 0xc7 => 0x50b3, 0xc8 => 0x50c5, 0xc9 => 0x50be, + 0xca => 0x50ac, 0xcb => 0x50b7, 0xcc => 0x50bb, 0xcd => 0x50af, + 0xce => 0x50c7, 0xcf => 0x527f, 0xd0 => 0x5277, 0xd1 => 0x527d, + 0xd2 => 0x52df, 0xd3 => 0x52e6, 0xd4 => 0x52e4, 0xd5 => 0x52e2, + 0xd6 => 0x52e3, 0xd7 => 0x532f, 0xd8 => 0x55df, 0xd9 => 0x55e8, + 0xda => 0x55d3, 0xdb => 0x55e6, 0xdc => 0x55ce, 0xdd => 0x55dc, + 0xde => 0x55c7, 0xdf => 0x55d1, 0xe0 => 0x55e3, 0xe1 => 0x55e4, + 0xe2 => 0x55ef, 0xe3 => 0x55da, 0xe4 => 0x55e1, 0xe5 => 0x55c5, + 0xe6 => 0x55c6, 0xe7 => 0x55e5, 0xe8 => 0x55c9, 0xe9 => 0x5712, + 0xea => 0x5713, 0xeb => 0x585e, 0xec => 0x5851, 0xed => 0x5858, + 0xee => 0x5857, 0xef => 0x585a, 0xf0 => 0x5854, 0xf1 => 0x586b, + 0xf2 => 0x584c, 0xf3 => 0x586d, 0xf4 => 0x584a, 0xf5 => 0x5862, + 0xf6 => 0x5852, 0xf7 => 0x584b, 0xf8 => 0x5967, 0xf9 => 0x5ac1, + 0xfa => 0x5ac9, 0xfb => 0x5acc, 0xfc => 0x5abe, 0xfd => 0x5abd, + 0xfe => 0x5abc, + }, + 0xb7 => { + 0x40 => 0x5ab3, 0x41 => 0x5ac2, 0x42 => 0x5ab2, 0x43 => 0x5d69, + 0x44 => 0x5d6f, 0x45 => 0x5e4c, 0x46 => 0x5e79, 0x47 => 0x5ec9, + 0x48 => 0x5ec8, 0x49 => 0x5f12, 0x4a => 0x5f59, 0x4b => 0x5fac, + 0x4c => 0x5fae, 0x4d => 0x611a, 0x4e => 0x610f, 0x4f => 0x6148, + 0x50 => 0x611f, 0x51 => 0x60f3, 0x52 => 0x611b, 0x53 => 0x60f9, + 0x54 => 0x6101, 0x55 => 0x6108, 0x56 => 0x614e, 0x57 => 0x614c, + 0x58 => 0x6144, 0x59 => 0x614d, 0x5a => 0x613e, 0x5b => 0x6134, + 0x5c => 0x6127, 0x5d => 0x610d, 0x5e => 0x6106, 0x5f => 0x6137, + 0x60 => 0x6221, 0x61 => 0x6222, 0x62 => 0x6413, 0x63 => 0x643e, + 0x64 => 0x641e, 0x65 => 0x642a, 0x66 => 0x642d, 0x67 => 0x643d, + 0x68 => 0x642c, 0x69 => 0x640f, 0x6a => 0x641c, 0x6b => 0x6414, + 0x6c => 0x640d, 0x6d => 0x6436, 0x6e => 0x6416, 0x6f => 0x6417, + 0x70 => 0x6406, 0x71 => 0x656c, 0x72 => 0x659f, 0x73 => 0x65b0, + 0x74 => 0x6697, 0x75 => 0x6689, 0x76 => 0x6687, 0x77 => 0x6688, + 0x78 => 0x6696, 0x79 => 0x6684, 0x7a => 0x6698, 0x7b => 0x668d, + 0x7c => 0x6703, 0x7d => 0x6994, 0x7e => 0x696d, 0xa1 => 0x695a, + 0xa2 => 0x6977, 0xa3 => 0x6960, 0xa4 => 0x6954, 0xa5 => 0x6975, + 0xa6 => 0x6930, 0xa7 => 0x6982, 0xa8 => 0x694a, 0xa9 => 0x6968, + 0xaa => 0x696b, 0xab => 0x695e, 0xac => 0x6953, 0xad => 0x6979, + 0xae => 0x6986, 0xaf => 0x695d, 0xb0 => 0x6963, 0xb1 => 0x695b, + 0xb2 => 0x6b47, 0xb3 => 0x6b72, 0xb4 => 0x6bc0, 0xb5 => 0x6bbf, + 0xb6 => 0x6bd3, 0xb7 => 0x6bfd, 0xb8 => 0x6ea2, 0xb9 => 0x6eaf, + 0xba => 0x6ed3, 0xbb => 0x6eb6, 0xbc => 0x6ec2, 0xbd => 0x6e90, + 0xbe => 0x6e9d, 0xbf => 0x6ec7, 0xc0 => 0x6ec5, 0xc1 => 0x6ea5, + 0xc2 => 0x6e98, 0xc3 => 0x6ebc, 0xc4 => 0x6eba, 0xc5 => 0x6eab, + 0xc6 => 0x6ed1, 0xc7 => 0x6e96, 0xc8 => 0x6e9c, 0xc9 => 0x6ec4, + 0xca => 0x6ed4, 0xcb => 0x6eaa, 0xcc => 0x6ea7, 0xcd => 0x6eb4, + 0xce => 0x714e, 0xcf => 0x7159, 0xd0 => 0x7169, 0xd1 => 0x7164, + 0xd2 => 0x7149, 0xd3 => 0x7167, 0xd4 => 0x715c, 0xd5 => 0x716c, + 0xd6 => 0x7166, 0xd7 => 0x714c, 0xd8 => 0x7165, 0xd9 => 0x715e, + 0xda => 0x7146, 0xdb => 0x7168, 0xdc => 0x7156, 0xdd => 0x723a, + 0xde => 0x7252, 0xdf => 0x7337, 0xe0 => 0x7345, 0xe1 => 0x733f, + 0xe2 => 0x733e, 0xe3 => 0x746f, 0xe4 => 0x745a, 0xe5 => 0x7455, + 0xe6 => 0x745f, 0xe7 => 0x745e, 0xe8 => 0x7441, 0xe9 => 0x743f, + 0xea => 0x7459, 0xeb => 0x745b, 0xec => 0x745c, 0xed => 0x7576, + 0xee => 0x7578, 0xef => 0x7600, 0xf0 => 0x75f0, 0xf1 => 0x7601, + 0xf2 => 0x75f2, 0xf3 => 0x75f1, 0xf4 => 0x75fa, 0xf5 => 0x75ff, + 0xf6 => 0x75f4, 0xf7 => 0x75f3, 0xf8 => 0x76de, 0xf9 => 0x76df, + 0xfa => 0x775b, 0xfb => 0x776b, 0xfc => 0x7766, 0xfd => 0x775e, + 0xfe => 0x7763, + }, + 0xb8 => { + 0x40 => 0x7779, 0x41 => 0x776a, 0x42 => 0x776c, 0x43 => 0x775c, + 0x44 => 0x7765, 0x45 => 0x7768, 0x46 => 0x7762, 0x47 => 0x77ee, + 0x48 => 0x788e, 0x49 => 0x78b0, 0x4a => 0x7897, 0x4b => 0x7898, + 0x4c => 0x788c, 0x4d => 0x7889, 0x4e => 0x787c, 0x4f => 0x7891, + 0x50 => 0x7893, 0x51 => 0x787f, 0x52 => 0x797a, 0x53 => 0x797f, + 0x54 => 0x7981, 0x55 => 0x842c, 0x56 => 0x79bd, 0x57 => 0x7a1c, + 0x58 => 0x7a1a, 0x59 => 0x7a20, 0x5a => 0x7a14, 0x5b => 0x7a1f, + 0x5c => 0x7a1e, 0x5d => 0x7a9f, 0x5e => 0x7aa0, 0x5f => 0x7b77, + 0x60 => 0x7bc0, 0x61 => 0x7b60, 0x62 => 0x7b6e, 0x63 => 0x7b67, + 0x64 => 0x7cb1, 0x65 => 0x7cb3, 0x66 => 0x7cb5, 0x67 => 0x7d93, + 0x68 => 0x7d79, 0x69 => 0x7d91, 0x6a => 0x7d81, 0x6b => 0x7d8f, + 0x6c => 0x7d5b, 0x6d => 0x7f6e, 0x6e => 0x7f69, 0x6f => 0x7f6a, + 0x70 => 0x7f72, 0x71 => 0x7fa9, 0x72 => 0x7fa8, 0x73 => 0x7fa4, + 0x74 => 0x8056, 0x75 => 0x8058, 0x76 => 0x8086, 0x77 => 0x8084, + 0x78 => 0x8171, 0x79 => 0x8170, 0x7a => 0x8178, 0x7b => 0x8165, + 0x7c => 0x816e, 0x7d => 0x8173, 0x7e => 0x816b, 0xa1 => 0x8179, + 0xa2 => 0x817a, 0xa3 => 0x8166, 0xa4 => 0x8205, 0xa5 => 0x8247, + 0xa6 => 0x8482, 0xa7 => 0x8477, 0xa8 => 0x843d, 0xa9 => 0x8431, + 0xaa => 0x8475, 0xab => 0x8466, 0xac => 0x846b, 0xad => 0x8449, + 0xae => 0x846c, 0xaf => 0x845b, 0xb0 => 0x843c, 0xb1 => 0x8435, + 0xb2 => 0x8461, 0xb3 => 0x8463, 0xb4 => 0x8469, 0xb5 => 0x846d, + 0xb6 => 0x8446, 0xb7 => 0x865e, 0xb8 => 0x865c, 0xb9 => 0x865f, + 0xba => 0x86f9, 0xbb => 0x8713, 0xbc => 0x8708, 0xbd => 0x8707, + 0xbe => 0x8700, 0xbf => 0x86fe, 0xc0 => 0x86fb, 0xc1 => 0x8702, + 0xc2 => 0x8703, 0xc3 => 0x8706, 0xc4 => 0x870a, 0xc5 => 0x8859, + 0xc6 => 0x88df, 0xc7 => 0x88d4, 0xc8 => 0x88d9, 0xc9 => 0x88dc, + 0xca => 0x88d8, 0xcb => 0x88dd, 0xcc => 0x88e1, 0xcd => 0x88ca, + 0xce => 0x88d5, 0xcf => 0x88d2, 0xd0 => 0x899c, 0xd1 => 0x89e3, + 0xd2 => 0x8a6b, 0xd3 => 0x8a72, 0xd4 => 0x8a73, 0xd5 => 0x8a66, + 0xd6 => 0x8a69, 0xd7 => 0x8a70, 0xd8 => 0x8a87, 0xd9 => 0x8a7c, + 0xda => 0x8a63, 0xdb => 0x8aa0, 0xdc => 0x8a71, 0xdd => 0x8a85, + 0xde => 0x8a6d, 0xdf => 0x8a62, 0xe0 => 0x8a6e, 0xe1 => 0x8a6c, + 0xe2 => 0x8a79, 0xe3 => 0x8a7b, 0xe4 => 0x8a3e, 0xe5 => 0x8a68, + 0xe6 => 0x8c62, 0xe7 => 0x8c8a, 0xe8 => 0x8c89, 0xe9 => 0x8cca, + 0xea => 0x8cc7, 0xeb => 0x8cc8, 0xec => 0x8cc4, 0xed => 0x8cb2, + 0xee => 0x8cc3, 0xef => 0x8cc2, 0xf0 => 0x8cc5, 0xf1 => 0x8de1, + 0xf2 => 0x8ddf, 0xf3 => 0x8de8, 0xf4 => 0x8def, 0xf5 => 0x8df3, + 0xf6 => 0x8dfa, 0xf7 => 0x8dea, 0xf8 => 0x8de4, 0xf9 => 0x8de6, + 0xfa => 0x8eb2, 0xfb => 0x8f03, 0xfc => 0x8f09, 0xfd => 0x8efe, + 0xfe => 0x8f0a, + }, + 0xb9 => { + 0x40 => 0x8f9f, 0x41 => 0x8fb2, 0x42 => 0x904b, 0x43 => 0x904a, + 0x44 => 0x9053, 0x45 => 0x9042, 0x46 => 0x9054, 0x47 => 0x903c, + 0x48 => 0x9055, 0x49 => 0x9050, 0x4a => 0x9047, 0x4b => 0x904f, + 0x4c => 0x904e, 0x4d => 0x904d, 0x4e => 0x9051, 0x4f => 0x903e, + 0x50 => 0x9041, 0x51 => 0x9112, 0x52 => 0x9117, 0x53 => 0x916c, + 0x54 => 0x916a, 0x55 => 0x9169, 0x56 => 0x91c9, 0x57 => 0x9237, + 0x58 => 0x9257, 0x59 => 0x9238, 0x5a => 0x923d, 0x5b => 0x9240, + 0x5c => 0x923e, 0x5d => 0x925b, 0x5e => 0x924b, 0x5f => 0x9264, + 0x60 => 0x9251, 0x61 => 0x9234, 0x62 => 0x9249, 0x63 => 0x924d, + 0x64 => 0x9245, 0x65 => 0x9239, 0x66 => 0x923f, 0x67 => 0x925a, + 0x68 => 0x9598, 0x69 => 0x9698, 0x6a => 0x9694, 0x6b => 0x9695, + 0x6c => 0x96cd, 0x6d => 0x96cb, 0x6e => 0x96c9, 0x6f => 0x96ca, + 0x70 => 0x96f7, 0x71 => 0x96fb, 0x72 => 0x96f9, 0x73 => 0x96f6, + 0x74 => 0x9756, 0x75 => 0x9774, 0x76 => 0x9776, 0x77 => 0x9810, + 0x78 => 0x9811, 0x79 => 0x9813, 0x7a => 0x980a, 0x7b => 0x9812, + 0x7c => 0x980c, 0x7d => 0x98fc, 0x7e => 0x98f4, 0xa1 => 0x98fd, + 0xa2 => 0x98fe, 0xa3 => 0x99b3, 0xa4 => 0x99b1, 0xa5 => 0x99b4, + 0xa6 => 0x9ae1, 0xa7 => 0x9ce9, 0xa8 => 0x9e82, 0xa9 => 0x9f0e, + 0xaa => 0x9f13, 0xab => 0x9f20, 0xac => 0x50e7, 0xad => 0x50ee, + 0xae => 0x50e5, 0xaf => 0x50d6, 0xb0 => 0x50ed, 0xb1 => 0x50da, + 0xb2 => 0x50d5, 0xb3 => 0x50cf, 0xb4 => 0x50d1, 0xb5 => 0x50f1, + 0xb6 => 0x50ce, 0xb7 => 0x50e9, 0xb8 => 0x5162, 0xb9 => 0x51f3, + 0xba => 0x5283, 0xbb => 0x5282, 0xbc => 0x5331, 0xbd => 0x53ad, + 0xbe => 0x55fe, 0xbf => 0x5600, 0xc0 => 0x561b, 0xc1 => 0x5617, + 0xc2 => 0x55fd, 0xc3 => 0x5614, 0xc4 => 0x5606, 0xc5 => 0x5609, + 0xc6 => 0x560d, 0xc7 => 0x560e, 0xc8 => 0x55f7, 0xc9 => 0x5616, + 0xca => 0x561f, 0xcb => 0x5608, 0xcc => 0x5610, 0xcd => 0x55f6, + 0xce => 0x5718, 0xcf => 0x5716, 0xd0 => 0x5875, 0xd1 => 0x587e, + 0xd2 => 0x5883, 0xd3 => 0x5893, 0xd4 => 0x588a, 0xd5 => 0x5879, + 0xd6 => 0x5885, 0xd7 => 0x587d, 0xd8 => 0x58fd, 0xd9 => 0x5925, + 0xda => 0x5922, 0xdb => 0x5924, 0xdc => 0x596a, 0xdd => 0x5969, + 0xde => 0x5ae1, 0xdf => 0x5ae6, 0xe0 => 0x5ae9, 0xe1 => 0x5ad7, + 0xe2 => 0x5ad6, 0xe3 => 0x5ad8, 0xe4 => 0x5ae3, 0xe5 => 0x5b75, + 0xe6 => 0x5bde, 0xe7 => 0x5be7, 0xe8 => 0x5be1, 0xe9 => 0x5be5, + 0xea => 0x5be6, 0xeb => 0x5be8, 0xec => 0x5be2, 0xed => 0x5be4, + 0xee => 0x5bdf, 0xef => 0x5c0d, 0xf0 => 0x5c62, 0xf1 => 0x5d84, + 0xf2 => 0x5d87, 0xf3 => 0x5e5b, 0xf4 => 0x5e63, 0xf5 => 0x5e55, + 0xf6 => 0x5e57, 0xf7 => 0x5e54, 0xf8 => 0x5ed3, 0xf9 => 0x5ed6, + 0xfa => 0x5f0a, 0xfb => 0x5f46, 0xfc => 0x5f70, 0xfd => 0x5fb9, + 0xfe => 0x6147, + }, + 0xba => { + 0x40 => 0x613f, 0x41 => 0x614b, 0x42 => 0x6177, 0x43 => 0x6162, + 0x44 => 0x6163, 0x45 => 0x615f, 0x46 => 0x615a, 0x47 => 0x6158, + 0x48 => 0x6175, 0x49 => 0x622a, 0x4a => 0x6487, 0x4b => 0x6458, + 0x4c => 0x6454, 0x4d => 0x64a4, 0x4e => 0x6478, 0x4f => 0x645f, + 0x50 => 0x647a, 0x51 => 0x6451, 0x52 => 0x6467, 0x53 => 0x6434, + 0x54 => 0x646d, 0x55 => 0x647b, 0x56 => 0x6572, 0x57 => 0x65a1, + 0x58 => 0x65d7, 0x59 => 0x65d6, 0x5a => 0x66a2, 0x5b => 0x66a8, + 0x5c => 0x669d, 0x5d => 0x699c, 0x5e => 0x69a8, 0x5f => 0x6995, + 0x60 => 0x69c1, 0x61 => 0x69ae, 0x62 => 0x69d3, 0x63 => 0x69cb, + 0x64 => 0x699b, 0x65 => 0x69b7, 0x66 => 0x69bb, 0x67 => 0x69ab, + 0x68 => 0x69b4, 0x69 => 0x69d0, 0x6a => 0x69cd, 0x6b => 0x69ad, + 0x6c => 0x69cc, 0x6d => 0x69a6, 0x6e => 0x69c3, 0x6f => 0x69a3, + 0x70 => 0x6b49, 0x71 => 0x6b4c, 0x72 => 0x6c33, 0x73 => 0x6f33, + 0x74 => 0x6f14, 0x75 => 0x6efe, 0x76 => 0x6f13, 0x77 => 0x6ef4, + 0x78 => 0x6f29, 0x79 => 0x6f3e, 0x7a => 0x6f20, 0x7b => 0x6f2c, + 0x7c => 0x6f0f, 0x7d => 0x6f02, 0x7e => 0x6f22, 0xa1 => 0x6eff, + 0xa2 => 0x6eef, 0xa3 => 0x6f06, 0xa4 => 0x6f31, 0xa5 => 0x6f38, + 0xa6 => 0x6f32, 0xa7 => 0x6f23, 0xa8 => 0x6f15, 0xa9 => 0x6f2b, + 0xaa => 0x6f2f, 0xab => 0x6f88, 0xac => 0x6f2a, 0xad => 0x6eec, + 0xae => 0x6f01, 0xaf => 0x6ef2, 0xb0 => 0x6ecc, 0xb1 => 0x6ef7, + 0xb2 => 0x7194, 0xb3 => 0x7199, 0xb4 => 0x717d, 0xb5 => 0x718a, + 0xb6 => 0x7184, 0xb7 => 0x7192, 0xb8 => 0x723e, 0xb9 => 0x7292, + 0xba => 0x7296, 0xbb => 0x7344, 0xbc => 0x7350, 0xbd => 0x7464, + 0xbe => 0x7463, 0xbf => 0x746a, 0xc0 => 0x7470, 0xc1 => 0x746d, + 0xc2 => 0x7504, 0xc3 => 0x7591, 0xc4 => 0x7627, 0xc5 => 0x760d, + 0xc6 => 0x760b, 0xc7 => 0x7609, 0xc8 => 0x7613, 0xc9 => 0x76e1, + 0xca => 0x76e3, 0xcb => 0x7784, 0xcc => 0x777d, 0xcd => 0x777f, + 0xce => 0x7761, 0xcf => 0x78c1, 0xd0 => 0x789f, 0xd1 => 0x78a7, + 0xd2 => 0x78b3, 0xd3 => 0x78a9, 0xd4 => 0x78a3, 0xd5 => 0x798e, + 0xd6 => 0x798f, 0xd7 => 0x798d, 0xd8 => 0x7a2e, 0xd9 => 0x7a31, + 0xda => 0x7aaa, 0xdb => 0x7aa9, 0xdc => 0x7aed, 0xdd => 0x7aef, + 0xde => 0x7ba1, 0xdf => 0x7b95, 0xe0 => 0x7b8b, 0xe1 => 0x7b75, + 0xe2 => 0x7b97, 0xe3 => 0x7b9d, 0xe4 => 0x7b94, 0xe5 => 0x7b8f, + 0xe6 => 0x7bb8, 0xe7 => 0x7b87, 0xe8 => 0x7b84, 0xe9 => 0x7cb9, + 0xea => 0x7cbd, 0xeb => 0x7cbe, 0xec => 0x7dbb, 0xed => 0x7db0, + 0xee => 0x7d9c, 0xef => 0x7dbd, 0xf0 => 0x7dbe, 0xf1 => 0x7da0, + 0xf2 => 0x7dca, 0xf3 => 0x7db4, 0xf4 => 0x7db2, 0xf5 => 0x7db1, + 0xf6 => 0x7dba, 0xf7 => 0x7da2, 0xf8 => 0x7dbf, 0xf9 => 0x7db5, + 0xfa => 0x7db8, 0xfb => 0x7dad, 0xfc => 0x7dd2, 0xfd => 0x7dc7, + 0xfe => 0x7dac, + }, + 0xbb => { + 0x40 => 0x7f70, 0x41 => 0x7fe0, 0x42 => 0x7fe1, 0x43 => 0x7fdf, + 0x44 => 0x805e, 0x45 => 0x805a, 0x46 => 0x8087, 0x47 => 0x8150, + 0x48 => 0x8180, 0x49 => 0x818f, 0x4a => 0x8188, 0x4b => 0x818a, + 0x4c => 0x817f, 0x4d => 0x8182, 0x4e => 0x81e7, 0x4f => 0x81fa, + 0x50 => 0x8207, 0x51 => 0x8214, 0x52 => 0x821e, 0x53 => 0x824b, + 0x54 => 0x84c9, 0x55 => 0x84bf, 0x56 => 0x84c6, 0x57 => 0x84c4, + 0x58 => 0x8499, 0x59 => 0x849e, 0x5a => 0x84b2, 0x5b => 0x849c, + 0x5c => 0x84cb, 0x5d => 0x84b8, 0x5e => 0x84c0, 0x5f => 0x84d3, + 0x60 => 0x8490, 0x61 => 0x84bc, 0x62 => 0x84d1, 0x63 => 0x84ca, + 0x64 => 0x873f, 0x65 => 0x871c, 0x66 => 0x873b, 0x67 => 0x8722, + 0x68 => 0x8725, 0x69 => 0x8734, 0x6a => 0x8718, 0x6b => 0x8755, + 0x6c => 0x8737, 0x6d => 0x8729, 0x6e => 0x88f3, 0x6f => 0x8902, + 0x70 => 0x88f4, 0x71 => 0x88f9, 0x72 => 0x88f8, 0x73 => 0x88fd, + 0x74 => 0x88e8, 0x75 => 0x891a, 0x76 => 0x88ef, 0x77 => 0x8aa6, + 0x78 => 0x8a8c, 0x79 => 0x8a9e, 0x7a => 0x8aa3, 0x7b => 0x8a8d, + 0x7c => 0x8aa1, 0x7d => 0x8a93, 0x7e => 0x8aa4, 0xa1 => 0x8aaa, + 0xa2 => 0x8aa5, 0xa3 => 0x8aa8, 0xa4 => 0x8a98, 0xa5 => 0x8a91, + 0xa6 => 0x8a9a, 0xa7 => 0x8aa7, 0xa8 => 0x8c6a, 0xa9 => 0x8c8d, + 0xaa => 0x8c8c, 0xab => 0x8cd3, 0xac => 0x8cd1, 0xad => 0x8cd2, + 0xae => 0x8d6b, 0xaf => 0x8d99, 0xb0 => 0x8d95, 0xb1 => 0x8dfc, + 0xb2 => 0x8f14, 0xb3 => 0x8f12, 0xb4 => 0x8f15, 0xb5 => 0x8f13, + 0xb6 => 0x8fa3, 0xb7 => 0x9060, 0xb8 => 0x9058, 0xb9 => 0x905c, + 0xba => 0x9063, 0xbb => 0x9059, 0xbc => 0x905e, 0xbd => 0x9062, + 0xbe => 0x905d, 0xbf => 0x905b, 0xc0 => 0x9119, 0xc1 => 0x9118, + 0xc2 => 0x911e, 0xc3 => 0x9175, 0xc4 => 0x9178, 0xc5 => 0x9177, + 0xc6 => 0x9174, 0xc7 => 0x9278, 0xc8 => 0x9280, 0xc9 => 0x9285, + 0xca => 0x9298, 0xcb => 0x9296, 0xcc => 0x927b, 0xcd => 0x9293, + 0xce => 0x929c, 0xcf => 0x92a8, 0xd0 => 0x927c, 0xd1 => 0x9291, + 0xd2 => 0x95a1, 0xd3 => 0x95a8, 0xd4 => 0x95a9, 0xd5 => 0x95a3, + 0xd6 => 0x95a5, 0xd7 => 0x95a4, 0xd8 => 0x9699, 0xd9 => 0x969c, + 0xda => 0x969b, 0xdb => 0x96cc, 0xdc => 0x96d2, 0xdd => 0x9700, + 0xde => 0x977c, 0xdf => 0x9785, 0xe0 => 0x97f6, 0xe1 => 0x9817, + 0xe2 => 0x9818, 0xe3 => 0x98af, 0xe4 => 0x98b1, 0xe5 => 0x9903, + 0xe6 => 0x9905, 0xe7 => 0x990c, 0xe8 => 0x9909, 0xe9 => 0x99c1, + 0xea => 0x9aaf, 0xeb => 0x9ab0, 0xec => 0x9ae6, 0xed => 0x9b41, + 0xee => 0x9b42, 0xef => 0x9cf4, 0xf0 => 0x9cf6, 0xf1 => 0x9cf3, + 0xf2 => 0x9ebc, 0xf3 => 0x9f3b, 0xf4 => 0x9f4a, 0xf5 => 0x5104, + 0xf6 => 0x5100, 0xf7 => 0x50fb, 0xf8 => 0x50f5, 0xf9 => 0x50f9, + 0xfa => 0x5102, 0xfb => 0x5108, 0xfc => 0x5109, 0xfd => 0x5105, + 0xfe => 0x51dc, + }, + 0xbc => { + 0x40 => 0x5287, 0x41 => 0x5288, 0x42 => 0x5289, 0x43 => 0x528d, + 0x44 => 0x528a, 0x45 => 0x52f0, 0x46 => 0x53b2, 0x47 => 0x562e, + 0x48 => 0x563b, 0x49 => 0x5639, 0x4a => 0x5632, 0x4b => 0x563f, + 0x4c => 0x5634, 0x4d => 0x5629, 0x4e => 0x5653, 0x4f => 0x564e, + 0x50 => 0x5657, 0x51 => 0x5674, 0x52 => 0x5636, 0x53 => 0x562f, + 0x54 => 0x5630, 0x55 => 0x5880, 0x56 => 0x589f, 0x57 => 0x589e, + 0x58 => 0x58b3, 0x59 => 0x589c, 0x5a => 0x58ae, 0x5b => 0x58a9, + 0x5c => 0x58a6, 0x5d => 0x596d, 0x5e => 0x5b09, 0x5f => 0x5afb, + 0x60 => 0x5b0b, 0x61 => 0x5af5, 0x62 => 0x5b0c, 0x63 => 0x5b08, + 0x64 => 0x5bee, 0x65 => 0x5bec, 0x66 => 0x5be9, 0x67 => 0x5beb, + 0x68 => 0x5c64, 0x69 => 0x5c65, 0x6a => 0x5d9d, 0x6b => 0x5d94, + 0x6c => 0x5e62, 0x6d => 0x5e5f, 0x6e => 0x5e61, 0x6f => 0x5ee2, + 0x70 => 0x5eda, 0x71 => 0x5edf, 0x72 => 0x5edd, 0x73 => 0x5ee3, + 0x74 => 0x5ee0, 0x75 => 0x5f48, 0x76 => 0x5f71, 0x77 => 0x5fb7, + 0x78 => 0x5fb5, 0x79 => 0x6176, 0x7a => 0x6167, 0x7b => 0x616e, + 0x7c => 0x615d, 0x7d => 0x6155, 0x7e => 0x6182, 0xa1 => 0x617c, + 0xa2 => 0x6170, 0xa3 => 0x616b, 0xa4 => 0x617e, 0xa5 => 0x61a7, + 0xa6 => 0x6190, 0xa7 => 0x61ab, 0xa8 => 0x618e, 0xa9 => 0x61ac, + 0xaa => 0x619a, 0xab => 0x61a4, 0xac => 0x6194, 0xad => 0x61ae, + 0xae => 0x622e, 0xaf => 0x6469, 0xb0 => 0x646f, 0xb1 => 0x6479, + 0xb2 => 0x649e, 0xb3 => 0x64b2, 0xb4 => 0x6488, 0xb5 => 0x6490, + 0xb6 => 0x64b0, 0xb7 => 0x64a5, 0xb8 => 0x6493, 0xb9 => 0x6495, + 0xba => 0x64a9, 0xbb => 0x6492, 0xbc => 0x64ae, 0xbd => 0x64ad, + 0xbe => 0x64ab, 0xbf => 0x649a, 0xc0 => 0x64ac, 0xc1 => 0x6499, + 0xc2 => 0x64a2, 0xc3 => 0x64b3, 0xc4 => 0x6575, 0xc5 => 0x6577, + 0xc6 => 0x6578, 0xc7 => 0x66ae, 0xc8 => 0x66ab, 0xc9 => 0x66b4, + 0xca => 0x66b1, 0xcb => 0x6a23, 0xcc => 0x6a1f, 0xcd => 0x69e8, + 0xce => 0x6a01, 0xcf => 0x6a1e, 0xd0 => 0x6a19, 0xd1 => 0x69fd, + 0xd2 => 0x6a21, 0xd3 => 0x6a13, 0xd4 => 0x6a0a, 0xd5 => 0x69f3, + 0xd6 => 0x6a02, 0xd7 => 0x6a05, 0xd8 => 0x69ed, 0xd9 => 0x6a11, + 0xda => 0x6b50, 0xdb => 0x6b4e, 0xdc => 0x6ba4, 0xdd => 0x6bc5, + 0xde => 0x6bc6, 0xdf => 0x6f3f, 0xe0 => 0x6f7c, 0xe1 => 0x6f84, + 0xe2 => 0x6f51, 0xe3 => 0x6f66, 0xe4 => 0x6f54, 0xe5 => 0x6f86, + 0xe6 => 0x6f6d, 0xe7 => 0x6f5b, 0xe8 => 0x6f78, 0xe9 => 0x6f6e, + 0xea => 0x6f8e, 0xeb => 0x6f7a, 0xec => 0x6f70, 0xed => 0x6f64, + 0xee => 0x6f97, 0xef => 0x6f58, 0xf0 => 0x6ed5, 0xf1 => 0x6f6f, + 0xf2 => 0x6f60, 0xf3 => 0x6f5f, 0xf4 => 0x719f, 0xf5 => 0x71ac, + 0xf6 => 0x71b1, 0xf7 => 0x71a8, 0xf8 => 0x7256, 0xf9 => 0x729b, + 0xfa => 0x734e, 0xfb => 0x7357, 0xfc => 0x7469, 0xfd => 0x748b, + 0xfe => 0x7483, + }, + 0xbd => { + 0x40 => 0x747e, 0x41 => 0x7480, 0x42 => 0x757f, 0x43 => 0x7620, + 0x44 => 0x7629, 0x45 => 0x761f, 0x46 => 0x7624, 0x47 => 0x7626, + 0x48 => 0x7621, 0x49 => 0x7622, 0x4a => 0x769a, 0x4b => 0x76ba, + 0x4c => 0x76e4, 0x4d => 0x778e, 0x4e => 0x7787, 0x4f => 0x778c, + 0x50 => 0x7791, 0x51 => 0x778b, 0x52 => 0x78cb, 0x53 => 0x78c5, + 0x54 => 0x78ba, 0x55 => 0x78ca, 0x56 => 0x78be, 0x57 => 0x78d5, + 0x58 => 0x78bc, 0x59 => 0x78d0, 0x5a => 0x7a3f, 0x5b => 0x7a3c, + 0x5c => 0x7a40, 0x5d => 0x7a3d, 0x5e => 0x7a37, 0x5f => 0x7a3b, + 0x60 => 0x7aaf, 0x61 => 0x7aae, 0x62 => 0x7bad, 0x63 => 0x7bb1, + 0x64 => 0x7bc4, 0x65 => 0x7bb4, 0x66 => 0x7bc6, 0x67 => 0x7bc7, + 0x68 => 0x7bc1, 0x69 => 0x7ba0, 0x6a => 0x7bcc, 0x6b => 0x7cca, + 0x6c => 0x7de0, 0x6d => 0x7df4, 0x6e => 0x7def, 0x6f => 0x7dfb, + 0x70 => 0x7dd8, 0x71 => 0x7dec, 0x72 => 0x7ddd, 0x73 => 0x7de8, + 0x74 => 0x7de3, 0x75 => 0x7dda, 0x76 => 0x7dde, 0x77 => 0x7de9, + 0x78 => 0x7d9e, 0x79 => 0x7dd9, 0x7a => 0x7df2, 0x7b => 0x7df9, + 0x7c => 0x7f75, 0x7d => 0x7f77, 0x7e => 0x7faf, 0xa1 => 0x7fe9, + 0xa2 => 0x8026, 0xa3 => 0x819b, 0xa4 => 0x819c, 0xa5 => 0x819d, + 0xa6 => 0x81a0, 0xa7 => 0x819a, 0xa8 => 0x8198, 0xa9 => 0x8517, + 0xaa => 0x853d, 0xab => 0x851a, 0xac => 0x84ee, 0xad => 0x852c, + 0xae => 0x852d, 0xaf => 0x8513, 0xb0 => 0x8511, 0xb1 => 0x8523, + 0xb2 => 0x8521, 0xb3 => 0x8514, 0xb4 => 0x84ec, 0xb5 => 0x8525, + 0xb6 => 0x84ff, 0xb7 => 0x8506, 0xb8 => 0x8782, 0xb9 => 0x8774, + 0xba => 0x8776, 0xbb => 0x8760, 0xbc => 0x8766, 0xbd => 0x8778, + 0xbe => 0x8768, 0xbf => 0x8759, 0xc0 => 0x8757, 0xc1 => 0x874c, + 0xc2 => 0x8753, 0xc3 => 0x885b, 0xc4 => 0x885d, 0xc5 => 0x8910, + 0xc6 => 0x8907, 0xc7 => 0x8912, 0xc8 => 0x8913, 0xc9 => 0x8915, + 0xca => 0x890a, 0xcb => 0x8abc, 0xcc => 0x8ad2, 0xcd => 0x8ac7, + 0xce => 0x8ac4, 0xcf => 0x8a95, 0xd0 => 0x8acb, 0xd1 => 0x8af8, + 0xd2 => 0x8ab2, 0xd3 => 0x8ac9, 0xd4 => 0x8ac2, 0xd5 => 0x8abf, + 0xd6 => 0x8ab0, 0xd7 => 0x8ad6, 0xd8 => 0x8acd, 0xd9 => 0x8ab6, + 0xda => 0x8ab9, 0xdb => 0x8adb, 0xdc => 0x8c4c, 0xdd => 0x8c4e, + 0xde => 0x8c6c, 0xdf => 0x8ce0, 0xe0 => 0x8cde, 0xe1 => 0x8ce6, + 0xe2 => 0x8ce4, 0xe3 => 0x8cec, 0xe4 => 0x8ced, 0xe5 => 0x8ce2, + 0xe6 => 0x8ce3, 0xe7 => 0x8cdc, 0xe8 => 0x8cea, 0xe9 => 0x8ce1, + 0xea => 0x8d6d, 0xeb => 0x8d9f, 0xec => 0x8da3, 0xed => 0x8e2b, + 0xee => 0x8e10, 0xef => 0x8e1d, 0xf0 => 0x8e22, 0xf1 => 0x8e0f, + 0xf2 => 0x8e29, 0xf3 => 0x8e1f, 0xf4 => 0x8e21, 0xf5 => 0x8e1e, + 0xf6 => 0x8eba, 0xf7 => 0x8f1d, 0xf8 => 0x8f1b, 0xf9 => 0x8f1f, + 0xfa => 0x8f29, 0xfb => 0x8f26, 0xfc => 0x8f2a, 0xfd => 0x8f1c, + 0xfe => 0x8f1e, + }, + 0xbe => { + 0x40 => 0x8f25, 0x41 => 0x9069, 0x42 => 0x906e, 0x43 => 0x9068, + 0x44 => 0x906d, 0x45 => 0x9077, 0x46 => 0x9130, 0x47 => 0x912d, + 0x48 => 0x9127, 0x49 => 0x9131, 0x4a => 0x9187, 0x4b => 0x9189, + 0x4c => 0x918b, 0x4d => 0x9183, 0x4e => 0x92c5, 0x4f => 0x92bb, + 0x50 => 0x92b7, 0x51 => 0x92ea, 0x52 => 0x92ac, 0x53 => 0x92e4, + 0x54 => 0x92c1, 0x55 => 0x92b3, 0x56 => 0x92bc, 0x57 => 0x92d2, + 0x58 => 0x92c7, 0x59 => 0x92f0, 0x5a => 0x92b2, 0x5b => 0x95ad, + 0x5c => 0x95b1, 0x5d => 0x9704, 0x5e => 0x9706, 0x5f => 0x9707, + 0x60 => 0x9709, 0x61 => 0x9760, 0x62 => 0x978d, 0x63 => 0x978b, + 0x64 => 0x978f, 0x65 => 0x9821, 0x66 => 0x982b, 0x67 => 0x981c, + 0x68 => 0x98b3, 0x69 => 0x990a, 0x6a => 0x9913, 0x6b => 0x9912, + 0x6c => 0x9918, 0x6d => 0x99dd, 0x6e => 0x99d0, 0x6f => 0x99df, + 0x70 => 0x99db, 0x71 => 0x99d1, 0x72 => 0x99d5, 0x73 => 0x99d2, + 0x74 => 0x99d9, 0x75 => 0x9ab7, 0x76 => 0x9aee, 0x77 => 0x9aef, + 0x78 => 0x9b27, 0x79 => 0x9b45, 0x7a => 0x9b44, 0x7b => 0x9b77, + 0x7c => 0x9b6f, 0x7d => 0x9d06, 0x7e => 0x9d09, 0xa1 => 0x9d03, + 0xa2 => 0x9ea9, 0xa3 => 0x9ebe, 0xa4 => 0x9ece, 0xa5 => 0x58a8, + 0xa6 => 0x9f52, 0xa7 => 0x5112, 0xa8 => 0x5118, 0xa9 => 0x5114, + 0xaa => 0x5110, 0xab => 0x5115, 0xac => 0x5180, 0xad => 0x51aa, + 0xae => 0x51dd, 0xaf => 0x5291, 0xb0 => 0x5293, 0xb1 => 0x52f3, + 0xb2 => 0x5659, 0xb3 => 0x566b, 0xb4 => 0x5679, 0xb5 => 0x5669, + 0xb6 => 0x5664, 0xb7 => 0x5678, 0xb8 => 0x566a, 0xb9 => 0x5668, + 0xba => 0x5665, 0xbb => 0x5671, 0xbc => 0x566f, 0xbd => 0x566c, + 0xbe => 0x5662, 0xbf => 0x5676, 0xc0 => 0x58c1, 0xc1 => 0x58be, + 0xc2 => 0x58c7, 0xc3 => 0x58c5, 0xc4 => 0x596e, 0xc5 => 0x5b1d, + 0xc6 => 0x5b34, 0xc7 => 0x5b78, 0xc8 => 0x5bf0, 0xc9 => 0x5c0e, + 0xca => 0x5f4a, 0xcb => 0x61b2, 0xcc => 0x6191, 0xcd => 0x61a9, + 0xce => 0x618a, 0xcf => 0x61cd, 0xd0 => 0x61b6, 0xd1 => 0x61be, + 0xd2 => 0x61ca, 0xd3 => 0x61c8, 0xd4 => 0x6230, 0xd5 => 0x64c5, + 0xd6 => 0x64c1, 0xd7 => 0x64cb, 0xd8 => 0x64bb, 0xd9 => 0x64bc, + 0xda => 0x64da, 0xdb => 0x64c4, 0xdc => 0x64c7, 0xdd => 0x64c2, + 0xde => 0x64cd, 0xdf => 0x64bf, 0xe0 => 0x64d2, 0xe1 => 0x64d4, + 0xe2 => 0x64be, 0xe3 => 0x6574, 0xe4 => 0x66c6, 0xe5 => 0x66c9, + 0xe6 => 0x66b9, 0xe7 => 0x66c4, 0xe8 => 0x66c7, 0xe9 => 0x66b8, + 0xea => 0x6a3d, 0xeb => 0x6a38, 0xec => 0x6a3a, 0xed => 0x6a59, + 0xee => 0x6a6b, 0xef => 0x6a58, 0xf0 => 0x6a39, 0xf1 => 0x6a44, + 0xf2 => 0x6a62, 0xf3 => 0x6a61, 0xf4 => 0x6a4b, 0xf5 => 0x6a47, + 0xf6 => 0x6a35, 0xf7 => 0x6a5f, 0xf8 => 0x6a48, 0xf9 => 0x6b59, + 0xfa => 0x6b77, 0xfb => 0x6c05, 0xfc => 0x6fc2, 0xfd => 0x6fb1, + 0xfe => 0x6fa1, + }, + 0xbf => { + 0x40 => 0x6fc3, 0x41 => 0x6fa4, 0x42 => 0x6fc1, 0x43 => 0x6fa7, + 0x44 => 0x6fb3, 0x45 => 0x6fc0, 0x46 => 0x6fb9, 0x47 => 0x6fb6, + 0x48 => 0x6fa6, 0x49 => 0x6fa0, 0x4a => 0x6fb4, 0x4b => 0x71be, + 0x4c => 0x71c9, 0x4d => 0x71d0, 0x4e => 0x71d2, 0x4f => 0x71c8, + 0x50 => 0x71d5, 0x51 => 0x71b9, 0x52 => 0x71ce, 0x53 => 0x71d9, + 0x54 => 0x71dc, 0x55 => 0x71c3, 0x56 => 0x71c4, 0x57 => 0x7368, + 0x58 => 0x749c, 0x59 => 0x74a3, 0x5a => 0x7498, 0x5b => 0x749f, + 0x5c => 0x749e, 0x5d => 0x74e2, 0x5e => 0x750c, 0x5f => 0x750d, + 0x60 => 0x7634, 0x61 => 0x7638, 0x62 => 0x763a, 0x63 => 0x76e7, + 0x64 => 0x76e5, 0x65 => 0x77a0, 0x66 => 0x779e, 0x67 => 0x779f, + 0x68 => 0x77a5, 0x69 => 0x78e8, 0x6a => 0x78da, 0x6b => 0x78ec, + 0x6c => 0x78e7, 0x6d => 0x79a6, 0x6e => 0x7a4d, 0x6f => 0x7a4e, + 0x70 => 0x7a46, 0x71 => 0x7a4c, 0x72 => 0x7a4b, 0x73 => 0x7aba, + 0x74 => 0x7bd9, 0x75 => 0x7c11, 0x76 => 0x7bc9, 0x77 => 0x7be4, + 0x78 => 0x7bdb, 0x79 => 0x7be1, 0x7a => 0x7be9, 0x7b => 0x7be6, + 0x7c => 0x7cd5, 0x7d => 0x7cd6, 0x7e => 0x7e0a, 0xa1 => 0x7e11, + 0xa2 => 0x7e08, 0xa3 => 0x7e1b, 0xa4 => 0x7e23, 0xa5 => 0x7e1e, + 0xa6 => 0x7e1d, 0xa7 => 0x7e09, 0xa8 => 0x7e10, 0xa9 => 0x7f79, + 0xaa => 0x7fb2, 0xab => 0x7ff0, 0xac => 0x7ff1, 0xad => 0x7fee, + 0xae => 0x8028, 0xaf => 0x81b3, 0xb0 => 0x81a9, 0xb1 => 0x81a8, + 0xb2 => 0x81fb, 0xb3 => 0x8208, 0xb4 => 0x8258, 0xb5 => 0x8259, + 0xb6 => 0x854a, 0xb7 => 0x8559, 0xb8 => 0x8548, 0xb9 => 0x8568, + 0xba => 0x8569, 0xbb => 0x8543, 0xbc => 0x8549, 0xbd => 0x856d, + 0xbe => 0x856a, 0xbf => 0x855e, 0xc0 => 0x8783, 0xc1 => 0x879f, + 0xc2 => 0x879e, 0xc3 => 0x87a2, 0xc4 => 0x878d, 0xc5 => 0x8861, + 0xc6 => 0x892a, 0xc7 => 0x8932, 0xc8 => 0x8925, 0xc9 => 0x892b, + 0xca => 0x8921, 0xcb => 0x89aa, 0xcc => 0x89a6, 0xcd => 0x8ae6, + 0xce => 0x8afa, 0xcf => 0x8aeb, 0xd0 => 0x8af1, 0xd1 => 0x8b00, + 0xd2 => 0x8adc, 0xd3 => 0x8ae7, 0xd4 => 0x8aee, 0xd5 => 0x8afe, + 0xd6 => 0x8b01, 0xd7 => 0x8b02, 0xd8 => 0x8af7, 0xd9 => 0x8aed, + 0xda => 0x8af3, 0xdb => 0x8af6, 0xdc => 0x8afc, 0xdd => 0x8c6b, + 0xde => 0x8c6d, 0xdf => 0x8c93, 0xe0 => 0x8cf4, 0xe1 => 0x8e44, + 0xe2 => 0x8e31, 0xe3 => 0x8e34, 0xe4 => 0x8e42, 0xe5 => 0x8e39, + 0xe6 => 0x8e35, 0xe7 => 0x8f3b, 0xe8 => 0x8f2f, 0xe9 => 0x8f38, + 0xea => 0x8f33, 0xeb => 0x8fa8, 0xec => 0x8fa6, 0xed => 0x9075, + 0xee => 0x9074, 0xef => 0x9078, 0xf0 => 0x9072, 0xf1 => 0x907c, + 0xf2 => 0x907a, 0xf3 => 0x9134, 0xf4 => 0x9192, 0xf5 => 0x9320, + 0xf6 => 0x9336, 0xf7 => 0x92f8, 0xf8 => 0x9333, 0xf9 => 0x932f, + 0xfa => 0x9322, 0xfb => 0x92fc, 0xfc => 0x932b, 0xfd => 0x9304, + 0xfe => 0x931a, + }, + 0xc0 => { + 0x40 => 0x9310, 0x41 => 0x9326, 0x42 => 0x9321, 0x43 => 0x9315, + 0x44 => 0x932e, 0x45 => 0x9319, 0x46 => 0x95bb, 0x47 => 0x96a7, + 0x48 => 0x96a8, 0x49 => 0x96aa, 0x4a => 0x96d5, 0x4b => 0x970e, + 0x4c => 0x9711, 0x4d => 0x9716, 0x4e => 0x970d, 0x4f => 0x9713, + 0x50 => 0x970f, 0x51 => 0x975b, 0x52 => 0x975c, 0x53 => 0x9766, + 0x54 => 0x9798, 0x55 => 0x9830, 0x56 => 0x9838, 0x57 => 0x983b, + 0x58 => 0x9837, 0x59 => 0x982d, 0x5a => 0x9839, 0x5b => 0x9824, + 0x5c => 0x9910, 0x5d => 0x9928, 0x5e => 0x991e, 0x5f => 0x991b, + 0x60 => 0x9921, 0x61 => 0x991a, 0x62 => 0x99ed, 0x63 => 0x99e2, + 0x64 => 0x99f1, 0x65 => 0x9ab8, 0x66 => 0x9abc, 0x67 => 0x9afb, + 0x68 => 0x9aed, 0x69 => 0x9b28, 0x6a => 0x9b91, 0x6b => 0x9d15, + 0x6c => 0x9d23, 0x6d => 0x9d26, 0x6e => 0x9d28, 0x6f => 0x9d12, + 0x70 => 0x9d1b, 0x71 => 0x9ed8, 0x72 => 0x9ed4, 0x73 => 0x9f8d, + 0x74 => 0x9f9c, 0x75 => 0x512a, 0x76 => 0x511f, 0x77 => 0x5121, + 0x78 => 0x5132, 0x79 => 0x52f5, 0x7a => 0x568e, 0x7b => 0x5680, + 0x7c => 0x5690, 0x7d => 0x5685, 0x7e => 0x5687, 0xa1 => 0x568f, + 0xa2 => 0x58d5, 0xa3 => 0x58d3, 0xa4 => 0x58d1, 0xa5 => 0x58ce, + 0xa6 => 0x5b30, 0xa7 => 0x5b2a, 0xa8 => 0x5b24, 0xa9 => 0x5b7a, + 0xaa => 0x5c37, 0xab => 0x5c68, 0xac => 0x5dbc, 0xad => 0x5dba, + 0xae => 0x5dbd, 0xaf => 0x5db8, 0xb0 => 0x5e6b, 0xb1 => 0x5f4c, + 0xb2 => 0x5fbd, 0xb3 => 0x61c9, 0xb4 => 0x61c2, 0xb5 => 0x61c7, + 0xb6 => 0x61e6, 0xb7 => 0x61cb, 0xb8 => 0x6232, 0xb9 => 0x6234, + 0xba => 0x64ce, 0xbb => 0x64ca, 0xbc => 0x64d8, 0xbd => 0x64e0, + 0xbe => 0x64f0, 0xbf => 0x64e6, 0xc0 => 0x64ec, 0xc1 => 0x64f1, + 0xc2 => 0x64e2, 0xc3 => 0x64ed, 0xc4 => 0x6582, 0xc5 => 0x6583, + 0xc6 => 0x66d9, 0xc7 => 0x66d6, 0xc8 => 0x6a80, 0xc9 => 0x6a94, + 0xca => 0x6a84, 0xcb => 0x6aa2, 0xcc => 0x6a9c, 0xcd => 0x6adb, + 0xce => 0x6aa3, 0xcf => 0x6a7e, 0xd0 => 0x6a97, 0xd1 => 0x6a90, + 0xd2 => 0x6aa0, 0xd3 => 0x6b5c, 0xd4 => 0x6bae, 0xd5 => 0x6bda, + 0xd6 => 0x6c08, 0xd7 => 0x6fd8, 0xd8 => 0x6ff1, 0xd9 => 0x6fdf, + 0xda => 0x6fe0, 0xdb => 0x6fdb, 0xdc => 0x6fe4, 0xdd => 0x6feb, + 0xde => 0x6fef, 0xdf => 0x6f80, 0xe0 => 0x6fec, 0xe1 => 0x6fe1, + 0xe2 => 0x6fe9, 0xe3 => 0x6fd5, 0xe4 => 0x6fee, 0xe5 => 0x6ff0, + 0xe6 => 0x71e7, 0xe7 => 0x71df, 0xe8 => 0x71ee, 0xe9 => 0x71e6, + 0xea => 0x71e5, 0xeb => 0x71ed, 0xec => 0x71ec, 0xed => 0x71f4, + 0xee => 0x71e0, 0xef => 0x7235, 0xf0 => 0x7246, 0xf1 => 0x7370, + 0xf2 => 0x7372, 0xf3 => 0x74a9, 0xf4 => 0x74b0, 0xf5 => 0x74a6, + 0xf6 => 0x74a8, 0xf7 => 0x7646, 0xf8 => 0x7642, 0xf9 => 0x764c, + 0xfa => 0x76ea, 0xfb => 0x77b3, 0xfc => 0x77aa, 0xfd => 0x77b0, + 0xfe => 0x77ac, + }, + 0xc1 => { + 0x40 => 0x77a7, 0x41 => 0x77ad, 0x42 => 0x77ef, 0x43 => 0x78f7, + 0x44 => 0x78fa, 0x45 => 0x78f4, 0x46 => 0x78ef, 0x47 => 0x7901, + 0x48 => 0x79a7, 0x49 => 0x79aa, 0x4a => 0x7a57, 0x4b => 0x7abf, + 0x4c => 0x7c07, 0x4d => 0x7c0d, 0x4e => 0x7bfe, 0x4f => 0x7bf7, + 0x50 => 0x7c0c, 0x51 => 0x7be0, 0x52 => 0x7ce0, 0x53 => 0x7cdc, + 0x54 => 0x7cde, 0x55 => 0x7ce2, 0x56 => 0x7cdf, 0x57 => 0x7cd9, + 0x58 => 0x7cdd, 0x59 => 0x7e2e, 0x5a => 0x7e3e, 0x5b => 0x7e46, + 0x5c => 0x7e37, 0x5d => 0x7e32, 0x5e => 0x7e43, 0x5f => 0x7e2b, + 0x60 => 0x7e3d, 0x61 => 0x7e31, 0x62 => 0x7e45, 0x63 => 0x7e41, + 0x64 => 0x7e34, 0x65 => 0x7e39, 0x66 => 0x7e48, 0x67 => 0x7e35, + 0x68 => 0x7e3f, 0x69 => 0x7e2f, 0x6a => 0x7f44, 0x6b => 0x7ff3, + 0x6c => 0x7ffc, 0x6d => 0x8071, 0x6e => 0x8072, 0x6f => 0x8070, + 0x70 => 0x806f, 0x71 => 0x8073, 0x72 => 0x81c6, 0x73 => 0x81c3, + 0x74 => 0x81ba, 0x75 => 0x81c2, 0x76 => 0x81c0, 0x77 => 0x81bf, + 0x78 => 0x81bd, 0x79 => 0x81c9, 0x7a => 0x81be, 0x7b => 0x81e8, + 0x7c => 0x8209, 0x7d => 0x8271, 0x7e => 0x85aa, 0xa1 => 0x8584, + 0xa2 => 0x857e, 0xa3 => 0x859c, 0xa4 => 0x8591, 0xa5 => 0x8594, + 0xa6 => 0x85af, 0xa7 => 0x859b, 0xa8 => 0x8587, 0xa9 => 0x85a8, + 0xaa => 0x858a, 0xab => 0x8667, 0xac => 0x87c0, 0xad => 0x87d1, + 0xae => 0x87b3, 0xaf => 0x87d2, 0xb0 => 0x87c6, 0xb1 => 0x87ab, + 0xb2 => 0x87bb, 0xb3 => 0x87ba, 0xb4 => 0x87c8, 0xb5 => 0x87cb, + 0xb6 => 0x893b, 0xb7 => 0x8936, 0xb8 => 0x8944, 0xb9 => 0x8938, + 0xba => 0x893d, 0xbb => 0x89ac, 0xbc => 0x8b0e, 0xbd => 0x8b17, + 0xbe => 0x8b19, 0xbf => 0x8b1b, 0xc0 => 0x8b0a, 0xc1 => 0x8b20, + 0xc2 => 0x8b1d, 0xc3 => 0x8b04, 0xc4 => 0x8b10, 0xc5 => 0x8c41, + 0xc6 => 0x8c3f, 0xc7 => 0x8c73, 0xc8 => 0x8cfa, 0xc9 => 0x8cfd, + 0xca => 0x8cfc, 0xcb => 0x8cf8, 0xcc => 0x8cfb, 0xcd => 0x8da8, + 0xce => 0x8e49, 0xcf => 0x8e4b, 0xd0 => 0x8e48, 0xd1 => 0x8e4a, + 0xd2 => 0x8f44, 0xd3 => 0x8f3e, 0xd4 => 0x8f42, 0xd5 => 0x8f45, + 0xd6 => 0x8f3f, 0xd7 => 0x907f, 0xd8 => 0x907d, 0xd9 => 0x9084, + 0xda => 0x9081, 0xdb => 0x9082, 0xdc => 0x9080, 0xdd => 0x9139, + 0xde => 0x91a3, 0xdf => 0x919e, 0xe0 => 0x919c, 0xe1 => 0x934d, + 0xe2 => 0x9382, 0xe3 => 0x9328, 0xe4 => 0x9375, 0xe5 => 0x934a, + 0xe6 => 0x9365, 0xe7 => 0x934b, 0xe8 => 0x9318, 0xe9 => 0x937e, + 0xea => 0x936c, 0xeb => 0x935b, 0xec => 0x9370, 0xed => 0x935a, + 0xee => 0x9354, 0xef => 0x95ca, 0xf0 => 0x95cb, 0xf1 => 0x95cc, + 0xf2 => 0x95c8, 0xf3 => 0x95c6, 0xf4 => 0x96b1, 0xf5 => 0x96b8, + 0xf6 => 0x96d6, 0xf7 => 0x971c, 0xf8 => 0x971e, 0xf9 => 0x97a0, + 0xfa => 0x97d3, 0xfb => 0x9846, 0xfc => 0x98b6, 0xfd => 0x9935, + 0xfe => 0x9a01, + }, + 0xc2 => { + 0x40 => 0x99ff, 0x41 => 0x9bae, 0x42 => 0x9bab, 0x43 => 0x9baa, + 0x44 => 0x9bad, 0x45 => 0x9d3b, 0x46 => 0x9d3f, 0x47 => 0x9e8b, + 0x48 => 0x9ecf, 0x49 => 0x9ede, 0x4a => 0x9edc, 0x4b => 0x9edd, + 0x4c => 0x9edb, 0x4d => 0x9f3e, 0x4e => 0x9f4b, 0x4f => 0x53e2, + 0x50 => 0x5695, 0x51 => 0x56ae, 0x52 => 0x58d9, 0x53 => 0x58d8, + 0x54 => 0x5b38, 0x55 => 0x5f5d, 0x56 => 0x61e3, 0x57 => 0x6233, + 0x58 => 0x64f4, 0x59 => 0x64f2, 0x5a => 0x64fe, 0x5b => 0x6506, + 0x5c => 0x64fa, 0x5d => 0x64fb, 0x5e => 0x64f7, 0x5f => 0x65b7, + 0x60 => 0x66dc, 0x61 => 0x6726, 0x62 => 0x6ab3, 0x63 => 0x6aac, + 0x64 => 0x6ac3, 0x65 => 0x6abb, 0x66 => 0x6ab8, 0x67 => 0x6ac2, + 0x68 => 0x6aae, 0x69 => 0x6aaf, 0x6a => 0x6b5f, 0x6b => 0x6b78, + 0x6c => 0x6baf, 0x6d => 0x7009, 0x6e => 0x700b, 0x6f => 0x6ffe, + 0x70 => 0x7006, 0x71 => 0x6ffa, 0x72 => 0x7011, 0x73 => 0x700f, + 0x74 => 0x71fb, 0x75 => 0x71fc, 0x76 => 0x71fe, 0x77 => 0x71f8, + 0x78 => 0x7377, 0x79 => 0x7375, 0x7a => 0x74a7, 0x7b => 0x74bf, + 0x7c => 0x7515, 0x7d => 0x7656, 0x7e => 0x7658, 0xa1 => 0x7652, + 0xa2 => 0x77bd, 0xa3 => 0x77bf, 0xa4 => 0x77bb, 0xa5 => 0x77bc, + 0xa6 => 0x790e, 0xa7 => 0x79ae, 0xa8 => 0x7a61, 0xa9 => 0x7a62, + 0xaa => 0x7a60, 0xab => 0x7ac4, 0xac => 0x7ac5, 0xad => 0x7c2b, + 0xae => 0x7c27, 0xaf => 0x7c2a, 0xb0 => 0x7c1e, 0xb1 => 0x7c23, + 0xb2 => 0x7c21, 0xb3 => 0x7ce7, 0xb4 => 0x7e54, 0xb5 => 0x7e55, + 0xb6 => 0x7e5e, 0xb7 => 0x7e5a, 0xb8 => 0x7e61, 0xb9 => 0x7e52, + 0xba => 0x7e59, 0xbb => 0x7f48, 0xbc => 0x7ff9, 0xbd => 0x7ffb, + 0xbe => 0x8077, 0xbf => 0x8076, 0xc0 => 0x81cd, 0xc1 => 0x81cf, + 0xc2 => 0x820a, 0xc3 => 0x85cf, 0xc4 => 0x85a9, 0xc5 => 0x85cd, + 0xc6 => 0x85d0, 0xc7 => 0x85c9, 0xc8 => 0x85b0, 0xc9 => 0x85ba, + 0xca => 0x85b9, 0xcb => 0x85a6, 0xcc => 0x87ef, 0xcd => 0x87ec, + 0xce => 0x87f2, 0xcf => 0x87e0, 0xd0 => 0x8986, 0xd1 => 0x89b2, + 0xd2 => 0x89f4, 0xd3 => 0x8b28, 0xd4 => 0x8b39, 0xd5 => 0x8b2c, + 0xd6 => 0x8b2b, 0xd7 => 0x8c50, 0xd8 => 0x8d05, 0xd9 => 0x8e59, + 0xda => 0x8e63, 0xdb => 0x8e66, 0xdc => 0x8e64, 0xdd => 0x8e5f, + 0xde => 0x8e55, 0xdf => 0x8ec0, 0xe0 => 0x8f49, 0xe1 => 0x8f4d, + 0xe2 => 0x9087, 0xe3 => 0x9083, 0xe4 => 0x9088, 0xe5 => 0x91ab, + 0xe6 => 0x91ac, 0xe7 => 0x91d0, 0xe8 => 0x9394, 0xe9 => 0x938a, + 0xea => 0x9396, 0xeb => 0x93a2, 0xec => 0x93b3, 0xed => 0x93ae, + 0xee => 0x93ac, 0xef => 0x93b0, 0xf0 => 0x9398, 0xf1 => 0x939a, + 0xf2 => 0x9397, 0xf3 => 0x95d4, 0xf4 => 0x95d6, 0xf5 => 0x95d0, + 0xf6 => 0x95d5, 0xf7 => 0x96e2, 0xf8 => 0x96dc, 0xf9 => 0x96d9, + 0xfa => 0x96db, 0xfb => 0x96de, 0xfc => 0x9724, 0xfd => 0x97a3, + 0xfe => 0x97a6, + }, + 0xc3 => { + 0x40 => 0x97ad, 0x41 => 0x97f9, 0x42 => 0x984d, 0x43 => 0x984f, + 0x44 => 0x984c, 0x45 => 0x984e, 0x46 => 0x9853, 0x47 => 0x98ba, + 0x48 => 0x993e, 0x49 => 0x993f, 0x4a => 0x993d, 0x4b => 0x992e, + 0x4c => 0x99a5, 0x4d => 0x9a0e, 0x4e => 0x9ac1, 0x4f => 0x9b03, + 0x50 => 0x9b06, 0x51 => 0x9b4f, 0x52 => 0x9b4e, 0x53 => 0x9b4d, + 0x54 => 0x9bca, 0x55 => 0x9bc9, 0x56 => 0x9bfd, 0x57 => 0x9bc8, + 0x58 => 0x9bc0, 0x59 => 0x9d51, 0x5a => 0x9d5d, 0x5b => 0x9d60, + 0x5c => 0x9ee0, 0x5d => 0x9f15, 0x5e => 0x9f2c, 0x5f => 0x5133, + 0x60 => 0x56a5, 0x61 => 0x58de, 0x62 => 0x58df, 0x63 => 0x58e2, + 0x64 => 0x5bf5, 0x65 => 0x9f90, 0x66 => 0x5eec, 0x67 => 0x61f2, + 0x68 => 0x61f7, 0x69 => 0x61f6, 0x6a => 0x61f5, 0x6b => 0x6500, + 0x6c => 0x650f, 0x6d => 0x66e0, 0x6e => 0x66dd, 0x6f => 0x6ae5, + 0x70 => 0x6add, 0x71 => 0x6ada, 0x72 => 0x6ad3, 0x73 => 0x701b, + 0x74 => 0x701f, 0x75 => 0x7028, 0x76 => 0x701a, 0x77 => 0x701d, + 0x78 => 0x7015, 0x79 => 0x7018, 0x7a => 0x7206, 0x7b => 0x720d, + 0x7c => 0x7258, 0x7d => 0x72a2, 0x7e => 0x7378, 0xa1 => 0x737a, + 0xa2 => 0x74bd, 0xa3 => 0x74ca, 0xa4 => 0x74e3, 0xa5 => 0x7587, + 0xa6 => 0x7586, 0xa7 => 0x765f, 0xa8 => 0x7661, 0xa9 => 0x77c7, + 0xaa => 0x7919, 0xab => 0x79b1, 0xac => 0x7a6b, 0xad => 0x7a69, + 0xae => 0x7c3e, 0xaf => 0x7c3f, 0xb0 => 0x7c38, 0xb1 => 0x7c3d, + 0xb2 => 0x7c37, 0xb3 => 0x7c40, 0xb4 => 0x7e6b, 0xb5 => 0x7e6d, + 0xb6 => 0x7e79, 0xb7 => 0x7e69, 0xb8 => 0x7e6a, 0xb9 => 0x7f85, + 0xba => 0x7e73, 0xbb => 0x7fb6, 0xbc => 0x7fb9, 0xbd => 0x7fb8, + 0xbe => 0x81d8, 0xbf => 0x85e9, 0xc0 => 0x85dd, 0xc1 => 0x85ea, + 0xc2 => 0x85d5, 0xc3 => 0x85e4, 0xc4 => 0x85e5, 0xc5 => 0x85f7, + 0xc6 => 0x87fb, 0xc7 => 0x8805, 0xc8 => 0x880d, 0xc9 => 0x87f9, + 0xca => 0x87fe, 0xcb => 0x8960, 0xcc => 0x895f, 0xcd => 0x8956, + 0xce => 0x895e, 0xcf => 0x8b41, 0xd0 => 0x8b5c, 0xd1 => 0x8b58, + 0xd2 => 0x8b49, 0xd3 => 0x8b5a, 0xd4 => 0x8b4e, 0xd5 => 0x8b4f, + 0xd6 => 0x8b46, 0xd7 => 0x8b59, 0xd8 => 0x8d08, 0xd9 => 0x8d0a, + 0xda => 0x8e7c, 0xdb => 0x8e72, 0xdc => 0x8e87, 0xdd => 0x8e76, + 0xde => 0x8e6c, 0xdf => 0x8e7a, 0xe0 => 0x8e74, 0xe1 => 0x8f54, + 0xe2 => 0x8f4e, 0xe3 => 0x8fad, 0xe4 => 0x908a, 0xe5 => 0x908b, + 0xe6 => 0x91b1, 0xe7 => 0x91ae, 0xe8 => 0x93e1, 0xe9 => 0x93d1, + 0xea => 0x93df, 0xeb => 0x93c3, 0xec => 0x93c8, 0xed => 0x93dc, + 0xee => 0x93dd, 0xef => 0x93d6, 0xf0 => 0x93e2, 0xf1 => 0x93cd, + 0xf2 => 0x93d8, 0xf3 => 0x93e4, 0xf4 => 0x93d7, 0xf5 => 0x93e8, + 0xf6 => 0x95dc, 0xf7 => 0x96b4, 0xf8 => 0x96e3, 0xf9 => 0x972a, + 0xfa => 0x9727, 0xfb => 0x9761, 0xfc => 0x97dc, 0xfd => 0x97fb, + 0xfe => 0x985e, + }, + 0xc4 => { + 0x40 => 0x9858, 0x41 => 0x985b, 0x42 => 0x98bc, 0x43 => 0x9945, + 0x44 => 0x9949, 0x45 => 0x9a16, 0x46 => 0x9a19, 0x47 => 0x9b0d, + 0x48 => 0x9be8, 0x49 => 0x9be7, 0x4a => 0x9bd6, 0x4b => 0x9bdb, + 0x4c => 0x9d89, 0x4d => 0x9d61, 0x4e => 0x9d72, 0x4f => 0x9d6a, + 0x50 => 0x9d6c, 0x51 => 0x9e92, 0x52 => 0x9e97, 0x53 => 0x9e93, + 0x54 => 0x9eb4, 0x55 => 0x52f8, 0x56 => 0x56a8, 0x57 => 0x56b7, + 0x58 => 0x56b6, 0x59 => 0x56b4, 0x5a => 0x56bc, 0x5b => 0x58e4, + 0x5c => 0x5b40, 0x5d => 0x5b43, 0x5e => 0x5b7d, 0x5f => 0x5bf6, + 0x60 => 0x5dc9, 0x61 => 0x61f8, 0x62 => 0x61fa, 0x63 => 0x6518, + 0x64 => 0x6514, 0x65 => 0x6519, 0x66 => 0x66e6, 0x67 => 0x6727, + 0x68 => 0x6aec, 0x69 => 0x703e, 0x6a => 0x7030, 0x6b => 0x7032, + 0x6c => 0x7210, 0x6d => 0x737b, 0x6e => 0x74cf, 0x6f => 0x7662, + 0x70 => 0x7665, 0x71 => 0x7926, 0x72 => 0x792a, 0x73 => 0x792c, + 0x74 => 0x792b, 0x75 => 0x7ac7, 0x76 => 0x7af6, 0x77 => 0x7c4c, + 0x78 => 0x7c43, 0x79 => 0x7c4d, 0x7a => 0x7cef, 0x7b => 0x7cf0, + 0x7c => 0x8fae, 0x7d => 0x7e7d, 0x7e => 0x7e7c, 0xa1 => 0x7e82, + 0xa2 => 0x7f4c, 0xa3 => 0x8000, 0xa4 => 0x81da, 0xa5 => 0x8266, + 0xa6 => 0x85fb, 0xa7 => 0x85f9, 0xa8 => 0x8611, 0xa9 => 0x85fa, + 0xaa => 0x8606, 0xab => 0x860b, 0xac => 0x8607, 0xad => 0x860a, + 0xae => 0x8814, 0xaf => 0x8815, 0xb0 => 0x8964, 0xb1 => 0x89ba, + 0xb2 => 0x89f8, 0xb3 => 0x8b70, 0xb4 => 0x8b6c, 0xb5 => 0x8b66, + 0xb6 => 0x8b6f, 0xb7 => 0x8b5f, 0xb8 => 0x8b6b, 0xb9 => 0x8d0f, + 0xba => 0x8d0d, 0xbb => 0x8e89, 0xbc => 0x8e81, 0xbd => 0x8e85, + 0xbe => 0x8e82, 0xbf => 0x91b4, 0xc0 => 0x91cb, 0xc1 => 0x9418, + 0xc2 => 0x9403, 0xc3 => 0x93fd, 0xc4 => 0x95e1, 0xc5 => 0x9730, + 0xc6 => 0x98c4, 0xc7 => 0x9952, 0xc8 => 0x9951, 0xc9 => 0x99a8, + 0xca => 0x9a2b, 0xcb => 0x9a30, 0xcc => 0x9a37, 0xcd => 0x9a35, + 0xce => 0x9c13, 0xcf => 0x9c0d, 0xd0 => 0x9e79, 0xd1 => 0x9eb5, + 0xd2 => 0x9ee8, 0xd3 => 0x9f2f, 0xd4 => 0x9f5f, 0xd5 => 0x9f63, + 0xd6 => 0x9f61, 0xd7 => 0x5137, 0xd8 => 0x5138, 0xd9 => 0x56c1, + 0xda => 0x56c0, 0xdb => 0x56c2, 0xdc => 0x5914, 0xdd => 0x5c6c, + 0xde => 0x5dcd, 0xdf => 0x61fc, 0xe0 => 0x61fe, 0xe1 => 0x651d, + 0xe2 => 0x651c, 0xe3 => 0x6595, 0xe4 => 0x66e9, 0xe5 => 0x6afb, + 0xe6 => 0x6b04, 0xe7 => 0x6afa, 0xe8 => 0x6bb2, 0xe9 => 0x704c, + 0xea => 0x721b, 0xeb => 0x72a7, 0xec => 0x74d6, 0xed => 0x74d4, + 0xee => 0x7669, 0xef => 0x77d3, 0xf0 => 0x7c50, 0xf1 => 0x7e8f, + 0xf2 => 0x7e8c, 0xf3 => 0x7fbc, 0xf4 => 0x8617, 0xf5 => 0x862d, + 0xf6 => 0x861a, 0xf7 => 0x8823, 0xf8 => 0x8822, 0xf9 => 0x8821, + 0xfa => 0x881f, 0xfb => 0x896a, 0xfc => 0x896c, 0xfd => 0x89bd, + 0xfe => 0x8b74, + }, + 0xc5 => { + 0x40 => 0x8b77, 0x41 => 0x8b7d, 0x42 => 0x8d13, 0x43 => 0x8e8a, + 0x44 => 0x8e8d, 0x45 => 0x8e8b, 0x46 => 0x8f5f, 0x47 => 0x8faf, + 0x48 => 0x91ba, 0x49 => 0x942e, 0x4a => 0x9433, 0x4b => 0x9435, + 0x4c => 0x943a, 0x4d => 0x9438, 0x4e => 0x9432, 0x4f => 0x942b, + 0x50 => 0x95e2, 0x51 => 0x9738, 0x52 => 0x9739, 0x53 => 0x9732, + 0x54 => 0x97ff, 0x55 => 0x9867, 0x56 => 0x9865, 0x57 => 0x9957, + 0x58 => 0x9a45, 0x59 => 0x9a43, 0x5a => 0x9a40, 0x5b => 0x9a3e, + 0x5c => 0x9acf, 0x5d => 0x9b54, 0x5e => 0x9b51, 0x5f => 0x9c2d, + 0x60 => 0x9c25, 0x61 => 0x9daf, 0x62 => 0x9db4, 0x63 => 0x9dc2, + 0x64 => 0x9db8, 0x65 => 0x9e9d, 0x66 => 0x9eef, 0x67 => 0x9f19, + 0x68 => 0x9f5c, 0x69 => 0x9f66, 0x6a => 0x9f67, 0x6b => 0x513c, + 0x6c => 0x513b, 0x6d => 0x56c8, 0x6e => 0x56ca, 0x6f => 0x56c9, + 0x70 => 0x5b7f, 0x71 => 0x5dd4, 0x72 => 0x5dd2, 0x73 => 0x5f4e, + 0x74 => 0x61ff, 0x75 => 0x6524, 0x76 => 0x6b0a, 0x77 => 0x6b61, + 0x78 => 0x7051, 0x79 => 0x7058, 0x7a => 0x7380, 0x7b => 0x74e4, + 0x7c => 0x758a, 0x7d => 0x766e, 0x7e => 0x766c, 0xa1 => 0x79b3, + 0xa2 => 0x7c60, 0xa3 => 0x7c5f, 0xa4 => 0x807e, 0xa5 => 0x807d, + 0xa6 => 0x81df, 0xa7 => 0x8972, 0xa8 => 0x896f, 0xa9 => 0x89fc, + 0xaa => 0x8b80, 0xab => 0x8d16, 0xac => 0x8d17, 0xad => 0x8e91, + 0xae => 0x8e93, 0xaf => 0x8f61, 0xb0 => 0x9148, 0xb1 => 0x9444, + 0xb2 => 0x9451, 0xb3 => 0x9452, 0xb4 => 0x973d, 0xb5 => 0x973e, + 0xb6 => 0x97c3, 0xb7 => 0x97c1, 0xb8 => 0x986b, 0xb9 => 0x9955, + 0xba => 0x9a55, 0xbb => 0x9a4d, 0xbc => 0x9ad2, 0xbd => 0x9b1a, + 0xbe => 0x9c49, 0xbf => 0x9c31, 0xc0 => 0x9c3e, 0xc1 => 0x9c3b, + 0xc2 => 0x9dd3, 0xc3 => 0x9dd7, 0xc4 => 0x9f34, 0xc5 => 0x9f6c, + 0xc6 => 0x9f6a, 0xc7 => 0x9f94, 0xc8 => 0x56cc, 0xc9 => 0x5dd6, + 0xca => 0x6200, 0xcb => 0x6523, 0xcc => 0x652b, 0xcd => 0x652a, + 0xce => 0x66ec, 0xcf => 0x6b10, 0xd0 => 0x74da, 0xd1 => 0x7aca, + 0xd2 => 0x7c64, 0xd3 => 0x7c63, 0xd4 => 0x7c65, 0xd5 => 0x7e93, + 0xd6 => 0x7e96, 0xd7 => 0x7e94, 0xd8 => 0x81e2, 0xd9 => 0x8638, + 0xda => 0x863f, 0xdb => 0x8831, 0xdc => 0x8b8a, 0xdd => 0x9090, + 0xde => 0x908f, 0xdf => 0x9463, 0xe0 => 0x9460, 0xe1 => 0x9464, + 0xe2 => 0x9768, 0xe3 => 0x986f, 0xe4 => 0x995c, 0xe5 => 0x9a5a, + 0xe6 => 0x9a5b, 0xe7 => 0x9a57, 0xe8 => 0x9ad3, 0xe9 => 0x9ad4, + 0xea => 0x9ad1, 0xeb => 0x9c54, 0xec => 0x9c57, 0xed => 0x9c56, + 0xee => 0x9de5, 0xef => 0x9e9f, 0xf0 => 0x9ef4, 0xf1 => 0x56d1, + 0xf2 => 0x58e9, 0xf3 => 0x652c, 0xf4 => 0x705e, 0xf5 => 0x7671, + 0xf6 => 0x7672, 0xf7 => 0x77d7, 0xf8 => 0x7f50, 0xf9 => 0x7f88, + 0xfa => 0x8836, 0xfb => 0x8839, 0xfc => 0x8862, 0xfd => 0x8b93, + 0xfe => 0x8b92, + }, + 0xc6 => { + 0x40 => 0x8b96, 0x41 => 0x8277, 0x42 => 0x8d1b, 0x43 => 0x91c0, + 0x44 => 0x946a, 0x45 => 0x9742, 0x46 => 0x9748, 0x47 => 0x9744, + 0x48 => 0x97c6, 0x49 => 0x9870, 0x4a => 0x9a5f, 0x4b => 0x9b22, + 0x4c => 0x9b58, 0x4d => 0x9c5f, 0x4e => 0x9df9, 0x4f => 0x9dfa, + 0x50 => 0x9e7c, 0x51 => 0x9e7d, 0x52 => 0x9f07, 0x53 => 0x9f77, + 0x54 => 0x9f72, 0x55 => 0x5ef3, 0x56 => 0x6b16, 0x57 => 0x7063, + 0x58 => 0x7c6c, 0x59 => 0x7c6e, 0x5a => 0x883b, 0x5b => 0x89c0, + 0x5c => 0x8ea1, 0x5d => 0x91c1, 0x5e => 0x9472, 0x5f => 0x9470, + 0x60 => 0x9871, 0x61 => 0x995e, 0x62 => 0x9ad6, 0x63 => 0x9b23, + 0x64 => 0x9ecc, 0x65 => 0x7064, 0x66 => 0x77da, 0x67 => 0x8b9a, + 0x68 => 0x9477, 0x69 => 0x97c9, 0x6a => 0x9a62, 0x6b => 0x9a65, + 0x6c => 0x7e9c, 0x6d => 0x8b9c, 0x6e => 0x8eaa, 0x6f => 0x91c5, + 0x70 => 0x947d, 0x71 => 0x947e, 0x72 => 0x947c, 0x73 => 0x9c77, + 0x74 => 0x9c78, 0x75 => 0x9ef7, 0x76 => 0x8c54, 0x77 => 0x947f, + 0x78 => 0x9e1a, 0x79 => 0x7228, 0x7a => 0x9a6a, 0x7b => 0x9b31, + 0x7c => 0x9e1b, 0x7d => 0x9e1e, 0x7e => 0x7c72, + }, + 0xc9 => { + 0x40 => 0x4e42, 0x41 => 0x4e5c, 0x42 => 0x51f5, 0x43 => 0x531a, + 0x44 => 0x5382, 0x45 => 0x4e07, 0x46 => 0x4e0c, 0x47 => 0x4e47, + 0x48 => 0x4e8d, 0x49 => 0x56d7, 0x4a => 0xfa0c, 0x4b => 0x5c6e, + 0x4c => 0x5f73, 0x4d => 0x4e0f, 0x4e => 0x5187, 0x4f => 0x4e0e, + 0x50 => 0x4e2e, 0x51 => 0x4e93, 0x52 => 0x4ec2, 0x53 => 0x4ec9, + 0x54 => 0x4ec8, 0x55 => 0x5198, 0x56 => 0x52fc, 0x57 => 0x536c, + 0x58 => 0x53b9, 0x59 => 0x5720, 0x5a => 0x5903, 0x5b => 0x592c, + 0x5c => 0x5c10, 0x5d => 0x5dff, 0x5e => 0x65e1, 0x5f => 0x6bb3, + 0x60 => 0x6bcc, 0x61 => 0x6c14, 0x62 => 0x723f, 0x63 => 0x4e31, + 0x64 => 0x4e3c, 0x65 => 0x4ee8, 0x66 => 0x4edc, 0x67 => 0x4ee9, + 0x68 => 0x4ee1, 0x69 => 0x4edd, 0x6a => 0x4eda, 0x6b => 0x520c, + 0x6c => 0x531c, 0x6d => 0x534c, 0x6e => 0x5722, 0x6f => 0x5723, + 0x70 => 0x5917, 0x71 => 0x592f, 0x72 => 0x5b81, 0x73 => 0x5b84, + 0x74 => 0x5c12, 0x75 => 0x5c3b, 0x76 => 0x5c74, 0x77 => 0x5c73, + 0x78 => 0x5e04, 0x79 => 0x5e80, 0x7a => 0x5e82, 0x7b => 0x5fc9, + 0x7c => 0x6209, 0x7d => 0x6250, 0x7e => 0x6c15, 0xa1 => 0x6c36, + 0xa2 => 0x6c43, 0xa3 => 0x6c3f, 0xa4 => 0x6c3b, 0xa5 => 0x72ae, + 0xa6 => 0x72b0, 0xa7 => 0x738a, 0xa8 => 0x79b8, 0xa9 => 0x808a, + 0xaa => 0x961e, 0xab => 0x4f0e, 0xac => 0x4f18, 0xad => 0x4f2c, + 0xae => 0x4ef5, 0xaf => 0x4f14, 0xb0 => 0x4ef1, 0xb1 => 0x4f00, + 0xb2 => 0x4ef7, 0xb3 => 0x4f08, 0xb4 => 0x4f1d, 0xb5 => 0x4f02, + 0xb6 => 0x4f05, 0xb7 => 0x4f22, 0xb8 => 0x4f13, 0xb9 => 0x4f04, + 0xba => 0x4ef4, 0xbb => 0x4f12, 0xbc => 0x51b1, 0xbd => 0x5213, + 0xbe => 0x5209, 0xbf => 0x5210, 0xc0 => 0x52a6, 0xc1 => 0x5322, + 0xc2 => 0x531f, 0xc3 => 0x534d, 0xc4 => 0x538a, 0xc5 => 0x5407, + 0xc6 => 0x56e1, 0xc7 => 0x56df, 0xc8 => 0x572e, 0xc9 => 0x572a, + 0xca => 0x5734, 0xcb => 0x593c, 0xcc => 0x5980, 0xcd => 0x597c, + 0xce => 0x5985, 0xcf => 0x597b, 0xd0 => 0x597e, 0xd1 => 0x5977, + 0xd2 => 0x597f, 0xd3 => 0x5b56, 0xd4 => 0x5c15, 0xd5 => 0x5c25, + 0xd6 => 0x5c7c, 0xd7 => 0x5c7a, 0xd8 => 0x5c7b, 0xd9 => 0x5c7e, + 0xda => 0x5ddf, 0xdb => 0x5e75, 0xdc => 0x5e84, 0xdd => 0x5f02, + 0xde => 0x5f1a, 0xdf => 0x5f74, 0xe0 => 0x5fd5, 0xe1 => 0x5fd4, + 0xe2 => 0x5fcf, 0xe3 => 0x625c, 0xe4 => 0x625e, 0xe5 => 0x6264, + 0xe6 => 0x6261, 0xe7 => 0x6266, 0xe8 => 0x6262, 0xe9 => 0x6259, + 0xea => 0x6260, 0xeb => 0x625a, 0xec => 0x6265, 0xed => 0x65ef, + 0xee => 0x65ee, 0xef => 0x673e, 0xf0 => 0x6739, 0xf1 => 0x6738, + 0xf2 => 0x673b, 0xf3 => 0x673a, 0xf4 => 0x673f, 0xf5 => 0x673c, + 0xf6 => 0x6733, 0xf7 => 0x6c18, 0xf8 => 0x6c46, 0xf9 => 0x6c52, + 0xfa => 0x6c5c, 0xfb => 0x6c4f, 0xfc => 0x6c4a, 0xfd => 0x6c54, + 0xfe => 0x6c4b, + }, + 0xca => { + 0x40 => 0x6c4c, 0x41 => 0x7071, 0x42 => 0x725e, 0x43 => 0x72b4, + 0x44 => 0x72b5, 0x45 => 0x738e, 0x46 => 0x752a, 0x47 => 0x767f, + 0x48 => 0x7a75, 0x49 => 0x7f51, 0x4a => 0x8278, 0x4b => 0x827c, + 0x4c => 0x8280, 0x4d => 0x827d, 0x4e => 0x827f, 0x4f => 0x864d, + 0x50 => 0x897e, 0x51 => 0x9099, 0x52 => 0x9097, 0x53 => 0x9098, + 0x54 => 0x909b, 0x55 => 0x9094, 0x56 => 0x9622, 0x57 => 0x9624, + 0x58 => 0x9620, 0x59 => 0x9623, 0x5a => 0x4f56, 0x5b => 0x4f3b, + 0x5c => 0x4f62, 0x5d => 0x4f49, 0x5e => 0x4f53, 0x5f => 0x4f64, + 0x60 => 0x4f3e, 0x61 => 0x4f67, 0x62 => 0x4f52, 0x63 => 0x4f5f, + 0x64 => 0x4f41, 0x65 => 0x4f58, 0x66 => 0x4f2d, 0x67 => 0x4f33, + 0x68 => 0x4f3f, 0x69 => 0x4f61, 0x6a => 0x518f, 0x6b => 0x51b9, + 0x6c => 0x521c, 0x6d => 0x521e, 0x6e => 0x5221, 0x6f => 0x52ad, + 0x70 => 0x52ae, 0x71 => 0x5309, 0x72 => 0x5363, 0x73 => 0x5372, + 0x74 => 0x538e, 0x75 => 0x538f, 0x76 => 0x5430, 0x77 => 0x5437, + 0x78 => 0x542a, 0x79 => 0x5454, 0x7a => 0x5445, 0x7b => 0x5419, + 0x7c => 0x541c, 0x7d => 0x5425, 0x7e => 0x5418, 0xa1 => 0x543d, + 0xa2 => 0x544f, 0xa3 => 0x5441, 0xa4 => 0x5428, 0xa5 => 0x5424, + 0xa6 => 0x5447, 0xa7 => 0x56ee, 0xa8 => 0x56e7, 0xa9 => 0x56e5, + 0xaa => 0x5741, 0xab => 0x5745, 0xac => 0x574c, 0xad => 0x5749, + 0xae => 0x574b, 0xaf => 0x5752, 0xb0 => 0x5906, 0xb1 => 0x5940, + 0xb2 => 0x59a6, 0xb3 => 0x5998, 0xb4 => 0x59a0, 0xb5 => 0x5997, + 0xb6 => 0x598e, 0xb7 => 0x59a2, 0xb8 => 0x5990, 0xb9 => 0x598f, + 0xba => 0x59a7, 0xbb => 0x59a1, 0xbc => 0x5b8e, 0xbd => 0x5b92, + 0xbe => 0x5c28, 0xbf => 0x5c2a, 0xc0 => 0x5c8d, 0xc1 => 0x5c8f, + 0xc2 => 0x5c88, 0xc3 => 0x5c8b, 0xc4 => 0x5c89, 0xc5 => 0x5c92, + 0xc6 => 0x5c8a, 0xc7 => 0x5c86, 0xc8 => 0x5c93, 0xc9 => 0x5c95, + 0xca => 0x5de0, 0xcb => 0x5e0a, 0xcc => 0x5e0e, 0xcd => 0x5e8b, + 0xce => 0x5e89, 0xcf => 0x5e8c, 0xd0 => 0x5e88, 0xd1 => 0x5e8d, + 0xd2 => 0x5f05, 0xd3 => 0x5f1d, 0xd4 => 0x5f78, 0xd5 => 0x5f76, + 0xd6 => 0x5fd2, 0xd7 => 0x5fd1, 0xd8 => 0x5fd0, 0xd9 => 0x5fed, + 0xda => 0x5fe8, 0xdb => 0x5fee, 0xdc => 0x5ff3, 0xdd => 0x5fe1, + 0xde => 0x5fe4, 0xdf => 0x5fe3, 0xe0 => 0x5ffa, 0xe1 => 0x5fef, + 0xe2 => 0x5ff7, 0xe3 => 0x5ffb, 0xe4 => 0x6000, 0xe5 => 0x5ff4, + 0xe6 => 0x623a, 0xe7 => 0x6283, 0xe8 => 0x628c, 0xe9 => 0x628e, + 0xea => 0x628f, 0xeb => 0x6294, 0xec => 0x6287, 0xed => 0x6271, + 0xee => 0x627b, 0xef => 0x627a, 0xf0 => 0x6270, 0xf1 => 0x6281, + 0xf2 => 0x6288, 0xf3 => 0x6277, 0xf4 => 0x627d, 0xf5 => 0x6272, + 0xf6 => 0x6274, 0xf7 => 0x6537, 0xf8 => 0x65f0, 0xf9 => 0x65f4, + 0xfa => 0x65f3, 0xfb => 0x65f2, 0xfc => 0x65f5, 0xfd => 0x6745, + 0xfe => 0x6747, + }, + 0xcb => { + 0x40 => 0x6759, 0x41 => 0x6755, 0x42 => 0x674c, 0x43 => 0x6748, + 0x44 => 0x675d, 0x45 => 0x674d, 0x46 => 0x675a, 0x47 => 0x674b, + 0x48 => 0x6bd0, 0x49 => 0x6c19, 0x4a => 0x6c1a, 0x4b => 0x6c78, + 0x4c => 0x6c67, 0x4d => 0x6c6b, 0x4e => 0x6c84, 0x4f => 0x6c8b, + 0x50 => 0x6c8f, 0x51 => 0x6c71, 0x52 => 0x6c6f, 0x53 => 0x6c69, + 0x54 => 0x6c9a, 0x55 => 0x6c6d, 0x56 => 0x6c87, 0x57 => 0x6c95, + 0x58 => 0x6c9c, 0x59 => 0x6c66, 0x5a => 0x6c73, 0x5b => 0x6c65, + 0x5c => 0x6c7b, 0x5d => 0x6c8e, 0x5e => 0x7074, 0x5f => 0x707a, + 0x60 => 0x7263, 0x61 => 0x72bf, 0x62 => 0x72bd, 0x63 => 0x72c3, + 0x64 => 0x72c6, 0x65 => 0x72c1, 0x66 => 0x72ba, 0x67 => 0x72c5, + 0x68 => 0x7395, 0x69 => 0x7397, 0x6a => 0x7393, 0x6b => 0x7394, + 0x6c => 0x7392, 0x6d => 0x753a, 0x6e => 0x7539, 0x6f => 0x7594, + 0x70 => 0x7595, 0x71 => 0x7681, 0x72 => 0x793d, 0x73 => 0x8034, + 0x74 => 0x8095, 0x75 => 0x8099, 0x76 => 0x8090, 0x77 => 0x8092, + 0x78 => 0x809c, 0x79 => 0x8290, 0x7a => 0x828f, 0x7b => 0x8285, + 0x7c => 0x828e, 0x7d => 0x8291, 0x7e => 0x8293, 0xa1 => 0x828a, + 0xa2 => 0x8283, 0xa3 => 0x8284, 0xa4 => 0x8c78, 0xa5 => 0x8fc9, + 0xa6 => 0x8fbf, 0xa7 => 0x909f, 0xa8 => 0x90a1, 0xa9 => 0x90a5, + 0xaa => 0x909e, 0xab => 0x90a7, 0xac => 0x90a0, 0xad => 0x9630, + 0xae => 0x9628, 0xaf => 0x962f, 0xb0 => 0x962d, 0xb1 => 0x4e33, + 0xb2 => 0x4f98, 0xb3 => 0x4f7c, 0xb4 => 0x4f85, 0xb5 => 0x4f7d, + 0xb6 => 0x4f80, 0xb7 => 0x4f87, 0xb8 => 0x4f76, 0xb9 => 0x4f74, + 0xba => 0x4f89, 0xbb => 0x4f84, 0xbc => 0x4f77, 0xbd => 0x4f4c, + 0xbe => 0x4f97, 0xbf => 0x4f6a, 0xc0 => 0x4f9a, 0xc1 => 0x4f79, + 0xc2 => 0x4f81, 0xc3 => 0x4f78, 0xc4 => 0x4f90, 0xc5 => 0x4f9c, + 0xc6 => 0x4f94, 0xc7 => 0x4f9e, 0xc8 => 0x4f92, 0xc9 => 0x4f82, + 0xca => 0x4f95, 0xcb => 0x4f6b, 0xcc => 0x4f6e, 0xcd => 0x519e, + 0xce => 0x51bc, 0xcf => 0x51be, 0xd0 => 0x5235, 0xd1 => 0x5232, + 0xd2 => 0x5233, 0xd3 => 0x5246, 0xd4 => 0x5231, 0xd5 => 0x52bc, + 0xd6 => 0x530a, 0xd7 => 0x530b, 0xd8 => 0x533c, 0xd9 => 0x5392, + 0xda => 0x5394, 0xdb => 0x5487, 0xdc => 0x547f, 0xdd => 0x5481, + 0xde => 0x5491, 0xdf => 0x5482, 0xe0 => 0x5488, 0xe1 => 0x546b, + 0xe2 => 0x547a, 0xe3 => 0x547e, 0xe4 => 0x5465, 0xe5 => 0x546c, + 0xe6 => 0x5474, 0xe7 => 0x5466, 0xe8 => 0x548d, 0xe9 => 0x546f, + 0xea => 0x5461, 0xeb => 0x5460, 0xec => 0x5498, 0xed => 0x5463, + 0xee => 0x5467, 0xef => 0x5464, 0xf0 => 0x56f7, 0xf1 => 0x56f9, + 0xf2 => 0x576f, 0xf3 => 0x5772, 0xf4 => 0x576d, 0xf5 => 0x576b, + 0xf6 => 0x5771, 0xf7 => 0x5770, 0xf8 => 0x5776, 0xf9 => 0x5780, + 0xfa => 0x5775, 0xfb => 0x577b, 0xfc => 0x5773, 0xfd => 0x5774, + 0xfe => 0x5762, + }, + 0xcc => { + 0x40 => 0x5768, 0x41 => 0x577d, 0x42 => 0x590c, 0x43 => 0x5945, + 0x44 => 0x59b5, 0x45 => 0x59ba, 0x46 => 0x59cf, 0x47 => 0x59ce, + 0x48 => 0x59b2, 0x49 => 0x59cc, 0x4a => 0x59c1, 0x4b => 0x59b6, + 0x4c => 0x59bc, 0x4d => 0x59c3, 0x4e => 0x59d6, 0x4f => 0x59b1, + 0x50 => 0x59bd, 0x51 => 0x59c0, 0x52 => 0x59c8, 0x53 => 0x59b4, + 0x54 => 0x59c7, 0x55 => 0x5b62, 0x56 => 0x5b65, 0x57 => 0x5b93, + 0x58 => 0x5b95, 0x59 => 0x5c44, 0x5a => 0x5c47, 0x5b => 0x5cae, + 0x5c => 0x5ca4, 0x5d => 0x5ca0, 0x5e => 0x5cb5, 0x5f => 0x5caf, + 0x60 => 0x5ca8, 0x61 => 0x5cac, 0x62 => 0x5c9f, 0x63 => 0x5ca3, + 0x64 => 0x5cad, 0x65 => 0x5ca2, 0x66 => 0x5caa, 0x67 => 0x5ca7, + 0x68 => 0x5c9d, 0x69 => 0x5ca5, 0x6a => 0x5cb6, 0x6b => 0x5cb0, + 0x6c => 0x5ca6, 0x6d => 0x5e17, 0x6e => 0x5e14, 0x6f => 0x5e19, + 0x70 => 0x5f28, 0x71 => 0x5f22, 0x72 => 0x5f23, 0x73 => 0x5f24, + 0x74 => 0x5f54, 0x75 => 0x5f82, 0x76 => 0x5f7e, 0x77 => 0x5f7d, + 0x78 => 0x5fde, 0x79 => 0x5fe5, 0x7a => 0x602d, 0x7b => 0x6026, + 0x7c => 0x6019, 0x7d => 0x6032, 0x7e => 0x600b, 0xa1 => 0x6034, + 0xa2 => 0x600a, 0xa3 => 0x6017, 0xa4 => 0x6033, 0xa5 => 0x601a, + 0xa6 => 0x601e, 0xa7 => 0x602c, 0xa8 => 0x6022, 0xa9 => 0x600d, + 0xaa => 0x6010, 0xab => 0x602e, 0xac => 0x6013, 0xad => 0x6011, + 0xae => 0x600c, 0xaf => 0x6009, 0xb0 => 0x601c, 0xb1 => 0x6214, + 0xb2 => 0x623d, 0xb3 => 0x62ad, 0xb4 => 0x62b4, 0xb5 => 0x62d1, + 0xb6 => 0x62be, 0xb7 => 0x62aa, 0xb8 => 0x62b6, 0xb9 => 0x62ca, + 0xba => 0x62ae, 0xbb => 0x62b3, 0xbc => 0x62af, 0xbd => 0x62bb, + 0xbe => 0x62a9, 0xbf => 0x62b0, 0xc0 => 0x62b8, 0xc1 => 0x653d, + 0xc2 => 0x65a8, 0xc3 => 0x65bb, 0xc4 => 0x6609, 0xc5 => 0x65fc, + 0xc6 => 0x6604, 0xc7 => 0x6612, 0xc8 => 0x6608, 0xc9 => 0x65fb, + 0xca => 0x6603, 0xcb => 0x660b, 0xcc => 0x660d, 0xcd => 0x6605, + 0xce => 0x65fd, 0xcf => 0x6611, 0xd0 => 0x6610, 0xd1 => 0x66f6, + 0xd2 => 0x670a, 0xd3 => 0x6785, 0xd4 => 0x676c, 0xd5 => 0x678e, + 0xd6 => 0x6792, 0xd7 => 0x6776, 0xd8 => 0x677b, 0xd9 => 0x6798, + 0xda => 0x6786, 0xdb => 0x6784, 0xdc => 0x6774, 0xdd => 0x678d, + 0xde => 0x678c, 0xdf => 0x677a, 0xe0 => 0x679f, 0xe1 => 0x6791, + 0xe2 => 0x6799, 0xe3 => 0x6783, 0xe4 => 0x677d, 0xe5 => 0x6781, + 0xe6 => 0x6778, 0xe7 => 0x6779, 0xe8 => 0x6794, 0xe9 => 0x6b25, + 0xea => 0x6b80, 0xeb => 0x6b7e, 0xec => 0x6bde, 0xed => 0x6c1d, + 0xee => 0x6c93, 0xef => 0x6cec, 0xf0 => 0x6ceb, 0xf1 => 0x6cee, + 0xf2 => 0x6cd9, 0xf3 => 0x6cb6, 0xf4 => 0x6cd4, 0xf5 => 0x6cad, + 0xf6 => 0x6ce7, 0xf7 => 0x6cb7, 0xf8 => 0x6cd0, 0xf9 => 0x6cc2, + 0xfa => 0x6cba, 0xfb => 0x6cc3, 0xfc => 0x6cc6, 0xfd => 0x6ced, + 0xfe => 0x6cf2, + }, + 0xcd => { + 0x40 => 0x6cd2, 0x41 => 0x6cdd, 0x42 => 0x6cb4, 0x43 => 0x6c8a, + 0x44 => 0x6c9d, 0x45 => 0x6c80, 0x46 => 0x6cde, 0x47 => 0x6cc0, + 0x48 => 0x6d30, 0x49 => 0x6ccd, 0x4a => 0x6cc7, 0x4b => 0x6cb0, + 0x4c => 0x6cf9, 0x4d => 0x6ccf, 0x4e => 0x6ce9, 0x4f => 0x6cd1, + 0x50 => 0x7094, 0x51 => 0x7098, 0x52 => 0x7085, 0x53 => 0x7093, + 0x54 => 0x7086, 0x55 => 0x7084, 0x56 => 0x7091, 0x57 => 0x7096, + 0x58 => 0x7082, 0x59 => 0x709a, 0x5a => 0x7083, 0x5b => 0x726a, + 0x5c => 0x72d6, 0x5d => 0x72cb, 0x5e => 0x72d8, 0x5f => 0x72c9, + 0x60 => 0x72dc, 0x61 => 0x72d2, 0x62 => 0x72d4, 0x63 => 0x72da, + 0x64 => 0x72cc, 0x65 => 0x72d1, 0x66 => 0x73a4, 0x67 => 0x73a1, + 0x68 => 0x73ad, 0x69 => 0x73a6, 0x6a => 0x73a2, 0x6b => 0x73a0, + 0x6c => 0x73ac, 0x6d => 0x739d, 0x6e => 0x74dd, 0x6f => 0x74e8, + 0x70 => 0x753f, 0x71 => 0x7540, 0x72 => 0x753e, 0x73 => 0x758c, + 0x74 => 0x7598, 0x75 => 0x76af, 0x76 => 0x76f3, 0x77 => 0x76f1, + 0x78 => 0x76f0, 0x79 => 0x76f5, 0x7a => 0x77f8, 0x7b => 0x77fc, + 0x7c => 0x77f9, 0x7d => 0x77fb, 0x7e => 0x77fa, 0xa1 => 0x77f7, + 0xa2 => 0x7942, 0xa3 => 0x793f, 0xa4 => 0x79c5, 0xa5 => 0x7a78, + 0xa6 => 0x7a7b, 0xa7 => 0x7afb, 0xa8 => 0x7c75, 0xa9 => 0x7cfd, + 0xaa => 0x8035, 0xab => 0x808f, 0xac => 0x80ae, 0xad => 0x80a3, + 0xae => 0x80b8, 0xaf => 0x80b5, 0xb0 => 0x80ad, 0xb1 => 0x8220, + 0xb2 => 0x82a0, 0xb3 => 0x82c0, 0xb4 => 0x82ab, 0xb5 => 0x829a, + 0xb6 => 0x8298, 0xb7 => 0x829b, 0xb8 => 0x82b5, 0xb9 => 0x82a7, + 0xba => 0x82ae, 0xbb => 0x82bc, 0xbc => 0x829e, 0xbd => 0x82ba, + 0xbe => 0x82b4, 0xbf => 0x82a8, 0xc0 => 0x82a1, 0xc1 => 0x82a9, + 0xc2 => 0x82c2, 0xc3 => 0x82a4, 0xc4 => 0x82c3, 0xc5 => 0x82b6, + 0xc6 => 0x82a2, 0xc7 => 0x8670, 0xc8 => 0x866f, 0xc9 => 0x866d, + 0xca => 0x866e, 0xcb => 0x8c56, 0xcc => 0x8fd2, 0xcd => 0x8fcb, + 0xce => 0x8fd3, 0xcf => 0x8fcd, 0xd0 => 0x8fd6, 0xd1 => 0x8fd5, + 0xd2 => 0x8fd7, 0xd3 => 0x90b2, 0xd4 => 0x90b4, 0xd5 => 0x90af, + 0xd6 => 0x90b3, 0xd7 => 0x90b0, 0xd8 => 0x9639, 0xd9 => 0x963d, + 0xda => 0x963c, 0xdb => 0x963a, 0xdc => 0x9643, 0xdd => 0x4fcd, + 0xde => 0x4fc5, 0xdf => 0x4fd3, 0xe0 => 0x4fb2, 0xe1 => 0x4fc9, + 0xe2 => 0x4fcb, 0xe3 => 0x4fc1, 0xe4 => 0x4fd4, 0xe5 => 0x4fdc, + 0xe6 => 0x4fd9, 0xe7 => 0x4fbb, 0xe8 => 0x4fb3, 0xe9 => 0x4fdb, + 0xea => 0x4fc7, 0xeb => 0x4fd6, 0xec => 0x4fba, 0xed => 0x4fc0, + 0xee => 0x4fb9, 0xef => 0x4fec, 0xf0 => 0x5244, 0xf1 => 0x5249, + 0xf2 => 0x52c0, 0xf3 => 0x52c2, 0xf4 => 0x533d, 0xf5 => 0x537c, + 0xf6 => 0x5397, 0xf7 => 0x5396, 0xf8 => 0x5399, 0xf9 => 0x5398, + 0xfa => 0x54ba, 0xfb => 0x54a1, 0xfc => 0x54ad, 0xfd => 0x54a5, + 0xfe => 0x54cf, + }, + 0xce => { + 0x40 => 0x54c3, 0x41 => 0x830d, 0x42 => 0x54b7, 0x43 => 0x54ae, + 0x44 => 0x54d6, 0x45 => 0x54b6, 0x46 => 0x54c5, 0x47 => 0x54c6, + 0x48 => 0x54a0, 0x49 => 0x5470, 0x4a => 0x54bc, 0x4b => 0x54a2, + 0x4c => 0x54be, 0x4d => 0x5472, 0x4e => 0x54de, 0x4f => 0x54b0, + 0x50 => 0x57b5, 0x51 => 0x579e, 0x52 => 0x579f, 0x53 => 0x57a4, + 0x54 => 0x578c, 0x55 => 0x5797, 0x56 => 0x579d, 0x57 => 0x579b, + 0x58 => 0x5794, 0x59 => 0x5798, 0x5a => 0x578f, 0x5b => 0x5799, + 0x5c => 0x57a5, 0x5d => 0x579a, 0x5e => 0x5795, 0x5f => 0x58f4, + 0x60 => 0x590d, 0x61 => 0x5953, 0x62 => 0x59e1, 0x63 => 0x59de, + 0x64 => 0x59ee, 0x65 => 0x5a00, 0x66 => 0x59f1, 0x67 => 0x59dd, + 0x68 => 0x59fa, 0x69 => 0x59fd, 0x6a => 0x59fc, 0x6b => 0x59f6, + 0x6c => 0x59e4, 0x6d => 0x59f2, 0x6e => 0x59f7, 0x6f => 0x59db, + 0x70 => 0x59e9, 0x71 => 0x59f3, 0x72 => 0x59f5, 0x73 => 0x59e0, + 0x74 => 0x59fe, 0x75 => 0x59f4, 0x76 => 0x59ed, 0x77 => 0x5ba8, + 0x78 => 0x5c4c, 0x79 => 0x5cd0, 0x7a => 0x5cd8, 0x7b => 0x5ccc, + 0x7c => 0x5cd7, 0x7d => 0x5ccb, 0x7e => 0x5cdb, 0xa1 => 0x5cde, + 0xa2 => 0x5cda, 0xa3 => 0x5cc9, 0xa4 => 0x5cc7, 0xa5 => 0x5cca, + 0xa6 => 0x5cd6, 0xa7 => 0x5cd3, 0xa8 => 0x5cd4, 0xa9 => 0x5ccf, + 0xaa => 0x5cc8, 0xab => 0x5cc6, 0xac => 0x5cce, 0xad => 0x5cdf, + 0xae => 0x5cf8, 0xaf => 0x5df9, 0xb0 => 0x5e21, 0xb1 => 0x5e22, + 0xb2 => 0x5e23, 0xb3 => 0x5e20, 0xb4 => 0x5e24, 0xb5 => 0x5eb0, + 0xb6 => 0x5ea4, 0xb7 => 0x5ea2, 0xb8 => 0x5e9b, 0xb9 => 0x5ea3, + 0xba => 0x5ea5, 0xbb => 0x5f07, 0xbc => 0x5f2e, 0xbd => 0x5f56, + 0xbe => 0x5f86, 0xbf => 0x6037, 0xc0 => 0x6039, 0xc1 => 0x6054, + 0xc2 => 0x6072, 0xc3 => 0x605e, 0xc4 => 0x6045, 0xc5 => 0x6053, + 0xc6 => 0x6047, 0xc7 => 0x6049, 0xc8 => 0x605b, 0xc9 => 0x604c, + 0xca => 0x6040, 0xcb => 0x6042, 0xcc => 0x605f, 0xcd => 0x6024, + 0xce => 0x6044, 0xcf => 0x6058, 0xd0 => 0x6066, 0xd1 => 0x606e, + 0xd2 => 0x6242, 0xd3 => 0x6243, 0xd4 => 0x62cf, 0xd5 => 0x630d, + 0xd6 => 0x630b, 0xd7 => 0x62f5, 0xd8 => 0x630e, 0xd9 => 0x6303, + 0xda => 0x62eb, 0xdb => 0x62f9, 0xdc => 0x630f, 0xdd => 0x630c, + 0xde => 0x62f8, 0xdf => 0x62f6, 0xe0 => 0x6300, 0xe1 => 0x6313, + 0xe2 => 0x6314, 0xe3 => 0x62fa, 0xe4 => 0x6315, 0xe5 => 0x62fb, + 0xe6 => 0x62f0, 0xe7 => 0x6541, 0xe8 => 0x6543, 0xe9 => 0x65aa, + 0xea => 0x65bf, 0xeb => 0x6636, 0xec => 0x6621, 0xed => 0x6632, + 0xee => 0x6635, 0xef => 0x661c, 0xf0 => 0x6626, 0xf1 => 0x6622, + 0xf2 => 0x6633, 0xf3 => 0x662b, 0xf4 => 0x663a, 0xf5 => 0x661d, + 0xf6 => 0x6634, 0xf7 => 0x6639, 0xf8 => 0x662e, 0xf9 => 0x670f, + 0xfa => 0x6710, 0xfb => 0x67c1, 0xfc => 0x67f2, 0xfd => 0x67c8, + 0xfe => 0x67ba, + }, + 0xcf => { + 0x40 => 0x67dc, 0x41 => 0x67bb, 0x42 => 0x67f8, 0x43 => 0x67d8, + 0x44 => 0x67c0, 0x45 => 0x67b7, 0x46 => 0x67c5, 0x47 => 0x67eb, + 0x48 => 0x67e4, 0x49 => 0x67df, 0x4a => 0x67b5, 0x4b => 0x67cd, + 0x4c => 0x67b3, 0x4d => 0x67f7, 0x4e => 0x67f6, 0x4f => 0x67ee, + 0x50 => 0x67e3, 0x51 => 0x67c2, 0x52 => 0x67b9, 0x53 => 0x67ce, + 0x54 => 0x67e7, 0x55 => 0x67f0, 0x56 => 0x67b2, 0x57 => 0x67fc, + 0x58 => 0x67c6, 0x59 => 0x67ed, 0x5a => 0x67cc, 0x5b => 0x67ae, + 0x5c => 0x67e6, 0x5d => 0x67db, 0x5e => 0x67fa, 0x5f => 0x67c9, + 0x60 => 0x67ca, 0x61 => 0x67c3, 0x62 => 0x67ea, 0x63 => 0x67cb, + 0x64 => 0x6b28, 0x65 => 0x6b82, 0x66 => 0x6b84, 0x67 => 0x6bb6, + 0x68 => 0x6bd6, 0x69 => 0x6bd8, 0x6a => 0x6be0, 0x6b => 0x6c20, + 0x6c => 0x6c21, 0x6d => 0x6d28, 0x6e => 0x6d34, 0x6f => 0x6d2d, + 0x70 => 0x6d1f, 0x71 => 0x6d3c, 0x72 => 0x6d3f, 0x73 => 0x6d12, + 0x74 => 0x6d0a, 0x75 => 0x6cda, 0x76 => 0x6d33, 0x77 => 0x6d04, + 0x78 => 0x6d19, 0x79 => 0x6d3a, 0x7a => 0x6d1a, 0x7b => 0x6d11, + 0x7c => 0x6d00, 0x7d => 0x6d1d, 0x7e => 0x6d42, 0xa1 => 0x6d01, + 0xa2 => 0x6d18, 0xa3 => 0x6d37, 0xa4 => 0x6d03, 0xa5 => 0x6d0f, + 0xa6 => 0x6d40, 0xa7 => 0x6d07, 0xa8 => 0x6d20, 0xa9 => 0x6d2c, + 0xaa => 0x6d08, 0xab => 0x6d22, 0xac => 0x6d09, 0xad => 0x6d10, + 0xae => 0x70b7, 0xaf => 0x709f, 0xb0 => 0x70be, 0xb1 => 0x70b1, + 0xb2 => 0x70b0, 0xb3 => 0x70a1, 0xb4 => 0x70b4, 0xb5 => 0x70b5, + 0xb6 => 0x70a9, 0xb7 => 0x7241, 0xb8 => 0x7249, 0xb9 => 0x724a, + 0xba => 0x726c, 0xbb => 0x7270, 0xbc => 0x7273, 0xbd => 0x726e, + 0xbe => 0x72ca, 0xbf => 0x72e4, 0xc0 => 0x72e8, 0xc1 => 0x72eb, + 0xc2 => 0x72df, 0xc3 => 0x72ea, 0xc4 => 0x72e6, 0xc5 => 0x72e3, + 0xc6 => 0x7385, 0xc7 => 0x73cc, 0xc8 => 0x73c2, 0xc9 => 0x73c8, + 0xca => 0x73c5, 0xcb => 0x73b9, 0xcc => 0x73b6, 0xcd => 0x73b5, + 0xce => 0x73b4, 0xcf => 0x73eb, 0xd0 => 0x73bf, 0xd1 => 0x73c7, + 0xd2 => 0x73be, 0xd3 => 0x73c3, 0xd4 => 0x73c6, 0xd5 => 0x73b8, + 0xd6 => 0x73cb, 0xd7 => 0x74ec, 0xd8 => 0x74ee, 0xd9 => 0x752e, + 0xda => 0x7547, 0xdb => 0x7548, 0xdc => 0x75a7, 0xdd => 0x75aa, + 0xde => 0x7679, 0xdf => 0x76c4, 0xe0 => 0x7708, 0xe1 => 0x7703, + 0xe2 => 0x7704, 0xe3 => 0x7705, 0xe4 => 0x770a, 0xe5 => 0x76f7, + 0xe6 => 0x76fb, 0xe7 => 0x76fa, 0xe8 => 0x77e7, 0xe9 => 0x77e8, + 0xea => 0x7806, 0xeb => 0x7811, 0xec => 0x7812, 0xed => 0x7805, + 0xee => 0x7810, 0xef => 0x780f, 0xf0 => 0x780e, 0xf1 => 0x7809, + 0xf2 => 0x7803, 0xf3 => 0x7813, 0xf4 => 0x794a, 0xf5 => 0x794c, + 0xf6 => 0x794b, 0xf7 => 0x7945, 0xf8 => 0x7944, 0xf9 => 0x79d5, + 0xfa => 0x79cd, 0xfb => 0x79cf, 0xfc => 0x79d6, 0xfd => 0x79ce, + 0xfe => 0x7a80, + }, + 0xd0 => { + 0x40 => 0x7a7e, 0x41 => 0x7ad1, 0x42 => 0x7b00, 0x43 => 0x7b01, + 0x44 => 0x7c7a, 0x45 => 0x7c78, 0x46 => 0x7c79, 0x47 => 0x7c7f, + 0x48 => 0x7c80, 0x49 => 0x7c81, 0x4a => 0x7d03, 0x4b => 0x7d08, + 0x4c => 0x7d01, 0x4d => 0x7f58, 0x4e => 0x7f91, 0x4f => 0x7f8d, + 0x50 => 0x7fbe, 0x51 => 0x8007, 0x52 => 0x800e, 0x53 => 0x800f, + 0x54 => 0x8014, 0x55 => 0x8037, 0x56 => 0x80d8, 0x57 => 0x80c7, + 0x58 => 0x80e0, 0x59 => 0x80d1, 0x5a => 0x80c8, 0x5b => 0x80c2, + 0x5c => 0x80d0, 0x5d => 0x80c5, 0x5e => 0x80e3, 0x5f => 0x80d9, + 0x60 => 0x80dc, 0x61 => 0x80ca, 0x62 => 0x80d5, 0x63 => 0x80c9, + 0x64 => 0x80cf, 0x65 => 0x80d7, 0x66 => 0x80e6, 0x67 => 0x80cd, + 0x68 => 0x81ff, 0x69 => 0x8221, 0x6a => 0x8294, 0x6b => 0x82d9, + 0x6c => 0x82fe, 0x6d => 0x82f9, 0x6e => 0x8307, 0x6f => 0x82e8, + 0x70 => 0x8300, 0x71 => 0x82d5, 0x72 => 0x833a, 0x73 => 0x82eb, + 0x74 => 0x82d6, 0x75 => 0x82f4, 0x76 => 0x82ec, 0x77 => 0x82e1, + 0x78 => 0x82f2, 0x79 => 0x82f5, 0x7a => 0x830c, 0x7b => 0x82fb, + 0x7c => 0x82f6, 0x7d => 0x82f0, 0x7e => 0x82ea, 0xa1 => 0x82e4, + 0xa2 => 0x82e0, 0xa3 => 0x82fa, 0xa4 => 0x82f3, 0xa5 => 0x82ed, + 0xa6 => 0x8677, 0xa7 => 0x8674, 0xa8 => 0x867c, 0xa9 => 0x8673, + 0xaa => 0x8841, 0xab => 0x884e, 0xac => 0x8867, 0xad => 0x886a, + 0xae => 0x8869, 0xaf => 0x89d3, 0xb0 => 0x8a04, 0xb1 => 0x8a07, + 0xb2 => 0x8d72, 0xb3 => 0x8fe3, 0xb4 => 0x8fe1, 0xb5 => 0x8fee, + 0xb6 => 0x8fe0, 0xb7 => 0x90f1, 0xb8 => 0x90bd, 0xb9 => 0x90bf, + 0xba => 0x90d5, 0xbb => 0x90c5, 0xbc => 0x90be, 0xbd => 0x90c7, + 0xbe => 0x90cb, 0xbf => 0x90c8, 0xc0 => 0x91d4, 0xc1 => 0x91d3, + 0xc2 => 0x9654, 0xc3 => 0x964f, 0xc4 => 0x9651, 0xc5 => 0x9653, + 0xc6 => 0x964a, 0xc7 => 0x964e, 0xc8 => 0x501e, 0xc9 => 0x5005, + 0xca => 0x5007, 0xcb => 0x5013, 0xcc => 0x5022, 0xcd => 0x5030, + 0xce => 0x501b, 0xcf => 0x4ff5, 0xd0 => 0x4ff4, 0xd1 => 0x5033, + 0xd2 => 0x5037, 0xd3 => 0x502c, 0xd4 => 0x4ff6, 0xd5 => 0x4ff7, + 0xd6 => 0x5017, 0xd7 => 0x501c, 0xd8 => 0x5020, 0xd9 => 0x5027, + 0xda => 0x5035, 0xdb => 0x502f, 0xdc => 0x5031, 0xdd => 0x500e, + 0xde => 0x515a, 0xdf => 0x5194, 0xe0 => 0x5193, 0xe1 => 0x51ca, + 0xe2 => 0x51c4, 0xe3 => 0x51c5, 0xe4 => 0x51c8, 0xe5 => 0x51ce, + 0xe6 => 0x5261, 0xe7 => 0x525a, 0xe8 => 0x5252, 0xe9 => 0x525e, + 0xea => 0x525f, 0xeb => 0x5255, 0xec => 0x5262, 0xed => 0x52cd, + 0xee => 0x530e, 0xef => 0x539e, 0xf0 => 0x5526, 0xf1 => 0x54e2, + 0xf2 => 0x5517, 0xf3 => 0x5512, 0xf4 => 0x54e7, 0xf5 => 0x54f3, + 0xf6 => 0x54e4, 0xf7 => 0x551a, 0xf8 => 0x54ff, 0xf9 => 0x5504, + 0xfa => 0x5508, 0xfb => 0x54eb, 0xfc => 0x5511, 0xfd => 0x5505, + 0xfe => 0x54f1, + }, + 0xd1 => { + 0x40 => 0x550a, 0x41 => 0x54fb, 0x42 => 0x54f7, 0x43 => 0x54f8, + 0x44 => 0x54e0, 0x45 => 0x550e, 0x46 => 0x5503, 0x47 => 0x550b, + 0x48 => 0x5701, 0x49 => 0x5702, 0x4a => 0x57cc, 0x4b => 0x5832, + 0x4c => 0x57d5, 0x4d => 0x57d2, 0x4e => 0x57ba, 0x4f => 0x57c6, + 0x50 => 0x57bd, 0x51 => 0x57bc, 0x52 => 0x57b8, 0x53 => 0x57b6, + 0x54 => 0x57bf, 0x55 => 0x57c7, 0x56 => 0x57d0, 0x57 => 0x57b9, + 0x58 => 0x57c1, 0x59 => 0x590e, 0x5a => 0x594a, 0x5b => 0x5a19, + 0x5c => 0x5a16, 0x5d => 0x5a2d, 0x5e => 0x5a2e, 0x5f => 0x5a15, + 0x60 => 0x5a0f, 0x61 => 0x5a17, 0x62 => 0x5a0a, 0x63 => 0x5a1e, + 0x64 => 0x5a33, 0x65 => 0x5b6c, 0x66 => 0x5ba7, 0x67 => 0x5bad, + 0x68 => 0x5bac, 0x69 => 0x5c03, 0x6a => 0x5c56, 0x6b => 0x5c54, + 0x6c => 0x5cec, 0x6d => 0x5cff, 0x6e => 0x5cee, 0x6f => 0x5cf1, + 0x70 => 0x5cf7, 0x71 => 0x5d00, 0x72 => 0x5cf9, 0x73 => 0x5e29, + 0x74 => 0x5e28, 0x75 => 0x5ea8, 0x76 => 0x5eae, 0x77 => 0x5eaa, + 0x78 => 0x5eac, 0x79 => 0x5f33, 0x7a => 0x5f30, 0x7b => 0x5f67, + 0x7c => 0x605d, 0x7d => 0x605a, 0x7e => 0x6067, 0xa1 => 0x6041, + 0xa2 => 0x60a2, 0xa3 => 0x6088, 0xa4 => 0x6080, 0xa5 => 0x6092, + 0xa6 => 0x6081, 0xa7 => 0x609d, 0xa8 => 0x6083, 0xa9 => 0x6095, + 0xaa => 0x609b, 0xab => 0x6097, 0xac => 0x6087, 0xad => 0x609c, + 0xae => 0x608e, 0xaf => 0x6219, 0xb0 => 0x6246, 0xb1 => 0x62f2, + 0xb2 => 0x6310, 0xb3 => 0x6356, 0xb4 => 0x632c, 0xb5 => 0x6344, + 0xb6 => 0x6345, 0xb7 => 0x6336, 0xb8 => 0x6343, 0xb9 => 0x63e4, + 0xba => 0x6339, 0xbb => 0x634b, 0xbc => 0x634a, 0xbd => 0x633c, + 0xbe => 0x6329, 0xbf => 0x6341, 0xc0 => 0x6334, 0xc1 => 0x6358, + 0xc2 => 0x6354, 0xc3 => 0x6359, 0xc4 => 0x632d, 0xc5 => 0x6347, + 0xc6 => 0x6333, 0xc7 => 0x635a, 0xc8 => 0x6351, 0xc9 => 0x6338, + 0xca => 0x6357, 0xcb => 0x6340, 0xcc => 0x6348, 0xcd => 0x654a, + 0xce => 0x6546, 0xcf => 0x65c6, 0xd0 => 0x65c3, 0xd1 => 0x65c4, + 0xd2 => 0x65c2, 0xd3 => 0x664a, 0xd4 => 0x665f, 0xd5 => 0x6647, + 0xd6 => 0x6651, 0xd7 => 0x6712, 0xd8 => 0x6713, 0xd9 => 0x681f, + 0xda => 0x681a, 0xdb => 0x6849, 0xdc => 0x6832, 0xdd => 0x6833, + 0xde => 0x683b, 0xdf => 0x684b, 0xe0 => 0x684f, 0xe1 => 0x6816, + 0xe2 => 0x6831, 0xe3 => 0x681c, 0xe4 => 0x6835, 0xe5 => 0x682b, + 0xe6 => 0x682d, 0xe7 => 0x682f, 0xe8 => 0x684e, 0xe9 => 0x6844, + 0xea => 0x6834, 0xeb => 0x681d, 0xec => 0x6812, 0xed => 0x6814, + 0xee => 0x6826, 0xef => 0x6828, 0xf0 => 0x682e, 0xf1 => 0x684d, + 0xf2 => 0x683a, 0xf3 => 0x6825, 0xf4 => 0x6820, 0xf5 => 0x6b2c, + 0xf6 => 0x6b2f, 0xf7 => 0x6b2d, 0xf8 => 0x6b31, 0xf9 => 0x6b34, + 0xfa => 0x6b6d, 0xfb => 0x8082, 0xfc => 0x6b88, 0xfd => 0x6be6, + 0xfe => 0x6be4, + }, + 0xd2 => { + 0x40 => 0x6be8, 0x41 => 0x6be3, 0x42 => 0x6be2, 0x43 => 0x6be7, + 0x44 => 0x6c25, 0x45 => 0x6d7a, 0x46 => 0x6d63, 0x47 => 0x6d64, + 0x48 => 0x6d76, 0x49 => 0x6d0d, 0x4a => 0x6d61, 0x4b => 0x6d92, + 0x4c => 0x6d58, 0x4d => 0x6d62, 0x4e => 0x6d6d, 0x4f => 0x6d6f, + 0x50 => 0x6d91, 0x51 => 0x6d8d, 0x52 => 0x6def, 0x53 => 0x6d7f, + 0x54 => 0x6d86, 0x55 => 0x6d5e, 0x56 => 0x6d67, 0x57 => 0x6d60, + 0x58 => 0x6d97, 0x59 => 0x6d70, 0x5a => 0x6d7c, 0x5b => 0x6d5f, + 0x5c => 0x6d82, 0x5d => 0x6d98, 0x5e => 0x6d2f, 0x5f => 0x6d68, + 0x60 => 0x6d8b, 0x61 => 0x6d7e, 0x62 => 0x6d80, 0x63 => 0x6d84, + 0x64 => 0x6d16, 0x65 => 0x6d83, 0x66 => 0x6d7b, 0x67 => 0x6d7d, + 0x68 => 0x6d75, 0x69 => 0x6d90, 0x6a => 0x70dc, 0x6b => 0x70d3, + 0x6c => 0x70d1, 0x6d => 0x70dd, 0x6e => 0x70cb, 0x6f => 0x7f39, + 0x70 => 0x70e2, 0x71 => 0x70d7, 0x72 => 0x70d2, 0x73 => 0x70de, + 0x74 => 0x70e0, 0x75 => 0x70d4, 0x76 => 0x70cd, 0x77 => 0x70c5, + 0x78 => 0x70c6, 0x79 => 0x70c7, 0x7a => 0x70da, 0x7b => 0x70ce, + 0x7c => 0x70e1, 0x7d => 0x7242, 0x7e => 0x7278, 0xa1 => 0x7277, + 0xa2 => 0x7276, 0xa3 => 0x7300, 0xa4 => 0x72fa, 0xa5 => 0x72f4, + 0xa6 => 0x72fe, 0xa7 => 0x72f6, 0xa8 => 0x72f3, 0xa9 => 0x72fb, + 0xaa => 0x7301, 0xab => 0x73d3, 0xac => 0x73d9, 0xad => 0x73e5, + 0xae => 0x73d6, 0xaf => 0x73bc, 0xb0 => 0x73e7, 0xb1 => 0x73e3, + 0xb2 => 0x73e9, 0xb3 => 0x73dc, 0xb4 => 0x73d2, 0xb5 => 0x73db, + 0xb6 => 0x73d4, 0xb7 => 0x73dd, 0xb8 => 0x73da, 0xb9 => 0x73d7, + 0xba => 0x73d8, 0xbb => 0x73e8, 0xbc => 0x74de, 0xbd => 0x74df, + 0xbe => 0x74f4, 0xbf => 0x74f5, 0xc0 => 0x7521, 0xc1 => 0x755b, + 0xc2 => 0x755f, 0xc3 => 0x75b0, 0xc4 => 0x75c1, 0xc5 => 0x75bb, + 0xc6 => 0x75c4, 0xc7 => 0x75c0, 0xc8 => 0x75bf, 0xc9 => 0x75b6, + 0xca => 0x75ba, 0xcb => 0x768a, 0xcc => 0x76c9, 0xcd => 0x771d, + 0xce => 0x771b, 0xcf => 0x7710, 0xd0 => 0x7713, 0xd1 => 0x7712, + 0xd2 => 0x7723, 0xd3 => 0x7711, 0xd4 => 0x7715, 0xd5 => 0x7719, + 0xd6 => 0x771a, 0xd7 => 0x7722, 0xd8 => 0x7727, 0xd9 => 0x7823, + 0xda => 0x782c, 0xdb => 0x7822, 0xdc => 0x7835, 0xdd => 0x782f, + 0xde => 0x7828, 0xdf => 0x782e, 0xe0 => 0x782b, 0xe1 => 0x7821, + 0xe2 => 0x7829, 0xe3 => 0x7833, 0xe4 => 0x782a, 0xe5 => 0x7831, + 0xe6 => 0x7954, 0xe7 => 0x795b, 0xe8 => 0x794f, 0xe9 => 0x795c, + 0xea => 0x7953, 0xeb => 0x7952, 0xec => 0x7951, 0xed => 0x79eb, + 0xee => 0x79ec, 0xef => 0x79e0, 0xf0 => 0x79ee, 0xf1 => 0x79ed, + 0xf2 => 0x79ea, 0xf3 => 0x79dc, 0xf4 => 0x79de, 0xf5 => 0x79dd, + 0xf6 => 0x7a86, 0xf7 => 0x7a89, 0xf8 => 0x7a85, 0xf9 => 0x7a8b, + 0xfa => 0x7a8c, 0xfb => 0x7a8a, 0xfc => 0x7a87, 0xfd => 0x7ad8, + 0xfe => 0x7b10, + }, + 0xd3 => { + 0x40 => 0x7b04, 0x41 => 0x7b13, 0x42 => 0x7b05, 0x43 => 0x7b0f, + 0x44 => 0x7b08, 0x45 => 0x7b0a, 0x46 => 0x7b0e, 0x47 => 0x7b09, + 0x48 => 0x7b12, 0x49 => 0x7c84, 0x4a => 0x7c91, 0x4b => 0x7c8a, + 0x4c => 0x7c8c, 0x4d => 0x7c88, 0x4e => 0x7c8d, 0x4f => 0x7c85, + 0x50 => 0x7d1e, 0x51 => 0x7d1d, 0x52 => 0x7d11, 0x53 => 0x7d0e, + 0x54 => 0x7d18, 0x55 => 0x7d16, 0x56 => 0x7d13, 0x57 => 0x7d1f, + 0x58 => 0x7d12, 0x59 => 0x7d0f, 0x5a => 0x7d0c, 0x5b => 0x7f5c, + 0x5c => 0x7f61, 0x5d => 0x7f5e, 0x5e => 0x7f60, 0x5f => 0x7f5d, + 0x60 => 0x7f5b, 0x61 => 0x7f96, 0x62 => 0x7f92, 0x63 => 0x7fc3, + 0x64 => 0x7fc2, 0x65 => 0x7fc0, 0x66 => 0x8016, 0x67 => 0x803e, + 0x68 => 0x8039, 0x69 => 0x80fa, 0x6a => 0x80f2, 0x6b => 0x80f9, + 0x6c => 0x80f5, 0x6d => 0x8101, 0x6e => 0x80fb, 0x6f => 0x8100, + 0x70 => 0x8201, 0x71 => 0x822f, 0x72 => 0x8225, 0x73 => 0x8333, + 0x74 => 0x832d, 0x75 => 0x8344, 0x76 => 0x8319, 0x77 => 0x8351, + 0x78 => 0x8325, 0x79 => 0x8356, 0x7a => 0x833f, 0x7b => 0x8341, + 0x7c => 0x8326, 0x7d => 0x831c, 0x7e => 0x8322, 0xa1 => 0x8342, + 0xa2 => 0x834e, 0xa3 => 0x831b, 0xa4 => 0x832a, 0xa5 => 0x8308, + 0xa6 => 0x833c, 0xa7 => 0x834d, 0xa8 => 0x8316, 0xa9 => 0x8324, + 0xaa => 0x8320, 0xab => 0x8337, 0xac => 0x832f, 0xad => 0x8329, + 0xae => 0x8347, 0xaf => 0x8345, 0xb0 => 0x834c, 0xb1 => 0x8353, + 0xb2 => 0x831e, 0xb3 => 0x832c, 0xb4 => 0x834b, 0xb5 => 0x8327, + 0xb6 => 0x8348, 0xb7 => 0x8653, 0xb8 => 0x8652, 0xb9 => 0x86a2, + 0xba => 0x86a8, 0xbb => 0x8696, 0xbc => 0x868d, 0xbd => 0x8691, + 0xbe => 0x869e, 0xbf => 0x8687, 0xc0 => 0x8697, 0xc1 => 0x8686, + 0xc2 => 0x868b, 0xc3 => 0x869a, 0xc4 => 0x8685, 0xc5 => 0x86a5, + 0xc6 => 0x8699, 0xc7 => 0x86a1, 0xc8 => 0x86a7, 0xc9 => 0x8695, + 0xca => 0x8698, 0xcb => 0x868e, 0xcc => 0x869d, 0xcd => 0x8690, + 0xce => 0x8694, 0xcf => 0x8843, 0xd0 => 0x8844, 0xd1 => 0x886d, + 0xd2 => 0x8875, 0xd3 => 0x8876, 0xd4 => 0x8872, 0xd5 => 0x8880, + 0xd6 => 0x8871, 0xd7 => 0x887f, 0xd8 => 0x886f, 0xd9 => 0x8883, + 0xda => 0x887e, 0xdb => 0x8874, 0xdc => 0x887c, 0xdd => 0x8a12, + 0xde => 0x8c47, 0xdf => 0x8c57, 0xe0 => 0x8c7b, 0xe1 => 0x8ca4, + 0xe2 => 0x8ca3, 0xe3 => 0x8d76, 0xe4 => 0x8d78, 0xe5 => 0x8db5, + 0xe6 => 0x8db7, 0xe7 => 0x8db6, 0xe8 => 0x8ed1, 0xe9 => 0x8ed3, + 0xea => 0x8ffe, 0xeb => 0x8ff5, 0xec => 0x9002, 0xed => 0x8fff, + 0xee => 0x8ffb, 0xef => 0x9004, 0xf0 => 0x8ffc, 0xf1 => 0x8ff6, + 0xf2 => 0x90d6, 0xf3 => 0x90e0, 0xf4 => 0x90d9, 0xf5 => 0x90da, + 0xf6 => 0x90e3, 0xf7 => 0x90df, 0xf8 => 0x90e5, 0xf9 => 0x90d8, + 0xfa => 0x90db, 0xfb => 0x90d7, 0xfc => 0x90dc, 0xfd => 0x90e4, + 0xfe => 0x9150, + }, + 0xd4 => { + 0x40 => 0x914e, 0x41 => 0x914f, 0x42 => 0x91d5, 0x43 => 0x91e2, + 0x44 => 0x91da, 0x45 => 0x965c, 0x46 => 0x965f, 0x47 => 0x96bc, + 0x48 => 0x98e3, 0x49 => 0x9adf, 0x4a => 0x9b2f, 0x4b => 0x4e7f, + 0x4c => 0x5070, 0x4d => 0x506a, 0x4e => 0x5061, 0x4f => 0x505e, + 0x50 => 0x5060, 0x51 => 0x5053, 0x52 => 0x504b, 0x53 => 0x505d, + 0x54 => 0x5072, 0x55 => 0x5048, 0x56 => 0x504d, 0x57 => 0x5041, + 0x58 => 0x505b, 0x59 => 0x504a, 0x5a => 0x5062, 0x5b => 0x5015, + 0x5c => 0x5045, 0x5d => 0x505f, 0x5e => 0x5069, 0x5f => 0x506b, + 0x60 => 0x5063, 0x61 => 0x5064, 0x62 => 0x5046, 0x63 => 0x5040, + 0x64 => 0x506e, 0x65 => 0x5073, 0x66 => 0x5057, 0x67 => 0x5051, + 0x68 => 0x51d0, 0x69 => 0x526b, 0x6a => 0x526d, 0x6b => 0x526c, + 0x6c => 0x526e, 0x6d => 0x52d6, 0x6e => 0x52d3, 0x6f => 0x532d, + 0x70 => 0x539c, 0x71 => 0x5575, 0x72 => 0x5576, 0x73 => 0x553c, + 0x74 => 0x554d, 0x75 => 0x5550, 0x76 => 0x5534, 0x77 => 0x552a, + 0x78 => 0x5551, 0x79 => 0x5562, 0x7a => 0x5536, 0x7b => 0x5535, + 0x7c => 0x5530, 0x7d => 0x5552, 0x7e => 0x5545, 0xa1 => 0x550c, + 0xa2 => 0x5532, 0xa3 => 0x5565, 0xa4 => 0x554e, 0xa5 => 0x5539, + 0xa6 => 0x5548, 0xa7 => 0x552d, 0xa8 => 0x553b, 0xa9 => 0x5540, + 0xaa => 0x554b, 0xab => 0x570a, 0xac => 0x5707, 0xad => 0x57fb, + 0xae => 0x5814, 0xaf => 0x57e2, 0xb0 => 0x57f6, 0xb1 => 0x57dc, + 0xb2 => 0x57f4, 0xb3 => 0x5800, 0xb4 => 0x57ed, 0xb5 => 0x57fd, + 0xb6 => 0x5808, 0xb7 => 0x57f8, 0xb8 => 0x580b, 0xb9 => 0x57f3, + 0xba => 0x57cf, 0xbb => 0x5807, 0xbc => 0x57ee, 0xbd => 0x57e3, + 0xbe => 0x57f2, 0xbf => 0x57e5, 0xc0 => 0x57ec, 0xc1 => 0x57e1, + 0xc2 => 0x580e, 0xc3 => 0x57fc, 0xc4 => 0x5810, 0xc5 => 0x57e7, + 0xc6 => 0x5801, 0xc7 => 0x580c, 0xc8 => 0x57f1, 0xc9 => 0x57e9, + 0xca => 0x57f0, 0xcb => 0x580d, 0xcc => 0x5804, 0xcd => 0x595c, + 0xce => 0x5a60, 0xcf => 0x5a58, 0xd0 => 0x5a55, 0xd1 => 0x5a67, + 0xd2 => 0x5a5e, 0xd3 => 0x5a38, 0xd4 => 0x5a35, 0xd5 => 0x5a6d, + 0xd6 => 0x5a50, 0xd7 => 0x5a5f, 0xd8 => 0x5a65, 0xd9 => 0x5a6c, + 0xda => 0x5a53, 0xdb => 0x5a64, 0xdc => 0x5a57, 0xdd => 0x5a43, + 0xde => 0x5a5d, 0xdf => 0x5a52, 0xe0 => 0x5a44, 0xe1 => 0x5a5b, + 0xe2 => 0x5a48, 0xe3 => 0x5a8e, 0xe4 => 0x5a3e, 0xe5 => 0x5a4d, + 0xe6 => 0x5a39, 0xe7 => 0x5a4c, 0xe8 => 0x5a70, 0xe9 => 0x5a69, + 0xea => 0x5a47, 0xeb => 0x5a51, 0xec => 0x5a56, 0xed => 0x5a42, + 0xee => 0x5a5c, 0xef => 0x5b72, 0xf0 => 0x5b6e, 0xf1 => 0x5bc1, + 0xf2 => 0x5bc0, 0xf3 => 0x5c59, 0xf4 => 0x5d1e, 0xf5 => 0x5d0b, + 0xf6 => 0x5d1d, 0xf7 => 0x5d1a, 0xf8 => 0x5d20, 0xf9 => 0x5d0c, + 0xfa => 0x5d28, 0xfb => 0x5d0d, 0xfc => 0x5d26, 0xfd => 0x5d25, + 0xfe => 0x5d0f, + }, + 0xd5 => { + 0x40 => 0x5d30, 0x41 => 0x5d12, 0x42 => 0x5d23, 0x43 => 0x5d1f, + 0x44 => 0x5d2e, 0x45 => 0x5e3e, 0x46 => 0x5e34, 0x47 => 0x5eb1, + 0x48 => 0x5eb4, 0x49 => 0x5eb9, 0x4a => 0x5eb2, 0x4b => 0x5eb3, + 0x4c => 0x5f36, 0x4d => 0x5f38, 0x4e => 0x5f9b, 0x4f => 0x5f96, + 0x50 => 0x5f9f, 0x51 => 0x608a, 0x52 => 0x6090, 0x53 => 0x6086, + 0x54 => 0x60be, 0x55 => 0x60b0, 0x56 => 0x60ba, 0x57 => 0x60d3, + 0x58 => 0x60d4, 0x59 => 0x60cf, 0x5a => 0x60e4, 0x5b => 0x60d9, + 0x5c => 0x60dd, 0x5d => 0x60c8, 0x5e => 0x60b1, 0x5f => 0x60db, + 0x60 => 0x60b7, 0x61 => 0x60ca, 0x62 => 0x60bf, 0x63 => 0x60c3, + 0x64 => 0x60cd, 0x65 => 0x60c0, 0x66 => 0x6332, 0x67 => 0x6365, + 0x68 => 0x638a, 0x69 => 0x6382, 0x6a => 0x637d, 0x6b => 0x63bd, + 0x6c => 0x639e, 0x6d => 0x63ad, 0x6e => 0x639d, 0x6f => 0x6397, + 0x70 => 0x63ab, 0x71 => 0x638e, 0x72 => 0x636f, 0x73 => 0x6387, + 0x74 => 0x6390, 0x75 => 0x636e, 0x76 => 0x63af, 0x77 => 0x6375, + 0x78 => 0x639c, 0x79 => 0x636d, 0x7a => 0x63ae, 0x7b => 0x637c, + 0x7c => 0x63a4, 0x7d => 0x633b, 0x7e => 0x639f, 0xa1 => 0x6378, + 0xa2 => 0x6385, 0xa3 => 0x6381, 0xa4 => 0x6391, 0xa5 => 0x638d, + 0xa6 => 0x6370, 0xa7 => 0x6553, 0xa8 => 0x65cd, 0xa9 => 0x6665, + 0xaa => 0x6661, 0xab => 0x665b, 0xac => 0x6659, 0xad => 0x665c, + 0xae => 0x6662, 0xaf => 0x6718, 0xb0 => 0x6879, 0xb1 => 0x6887, + 0xb2 => 0x6890, 0xb3 => 0x689c, 0xb4 => 0x686d, 0xb5 => 0x686e, + 0xb6 => 0x68ae, 0xb7 => 0x68ab, 0xb8 => 0x6956, 0xb9 => 0x686f, + 0xba => 0x68a3, 0xbb => 0x68ac, 0xbc => 0x68a9, 0xbd => 0x6875, + 0xbe => 0x6874, 0xbf => 0x68b2, 0xc0 => 0x688f, 0xc1 => 0x6877, + 0xc2 => 0x6892, 0xc3 => 0x687c, 0xc4 => 0x686b, 0xc5 => 0x6872, + 0xc6 => 0x68aa, 0xc7 => 0x6880, 0xc8 => 0x6871, 0xc9 => 0x687e, + 0xca => 0x689b, 0xcb => 0x6896, 0xcc => 0x688b, 0xcd => 0x68a0, + 0xce => 0x6889, 0xcf => 0x68a4, 0xd0 => 0x6878, 0xd1 => 0x687b, + 0xd2 => 0x6891, 0xd3 => 0x688c, 0xd4 => 0x688a, 0xd5 => 0x687d, + 0xd6 => 0x6b36, 0xd7 => 0x6b33, 0xd8 => 0x6b37, 0xd9 => 0x6b38, + 0xda => 0x6b91, 0xdb => 0x6b8f, 0xdc => 0x6b8d, 0xdd => 0x6b8e, + 0xde => 0x6b8c, 0xdf => 0x6c2a, 0xe0 => 0x6dc0, 0xe1 => 0x6dab, + 0xe2 => 0x6db4, 0xe3 => 0x6db3, 0xe4 => 0x6e74, 0xe5 => 0x6dac, + 0xe6 => 0x6de9, 0xe7 => 0x6de2, 0xe8 => 0x6db7, 0xe9 => 0x6df6, + 0xea => 0x6dd4, 0xeb => 0x6e00, 0xec => 0x6dc8, 0xed => 0x6de0, + 0xee => 0x6ddf, 0xef => 0x6dd6, 0xf0 => 0x6dbe, 0xf1 => 0x6de5, + 0xf2 => 0x6ddc, 0xf3 => 0x6ddd, 0xf4 => 0x6ddb, 0xf5 => 0x6df4, + 0xf6 => 0x6dca, 0xf7 => 0x6dbd, 0xf8 => 0x6ded, 0xf9 => 0x6df0, + 0xfa => 0x6dba, 0xfb => 0x6dd5, 0xfc => 0x6dc2, 0xfd => 0x6dcf, + 0xfe => 0x6dc9, + }, + 0xd6 => { + 0x40 => 0x6dd0, 0x41 => 0x6df2, 0x42 => 0x6dd3, 0x43 => 0x6dfd, + 0x44 => 0x6dd7, 0x45 => 0x6dcd, 0x46 => 0x6de3, 0x47 => 0x6dbb, + 0x48 => 0x70fa, 0x49 => 0x710d, 0x4a => 0x70f7, 0x4b => 0x7117, + 0x4c => 0x70f4, 0x4d => 0x710c, 0x4e => 0x70f0, 0x4f => 0x7104, + 0x50 => 0x70f3, 0x51 => 0x7110, 0x52 => 0x70fc, 0x53 => 0x70ff, + 0x54 => 0x7106, 0x55 => 0x7113, 0x56 => 0x7100, 0x57 => 0x70f8, + 0x58 => 0x70f6, 0x59 => 0x710b, 0x5a => 0x7102, 0x5b => 0x710e, + 0x5c => 0x727e, 0x5d => 0x727b, 0x5e => 0x727c, 0x5f => 0x727f, + 0x60 => 0x731d, 0x61 => 0x7317, 0x62 => 0x7307, 0x63 => 0x7311, + 0x64 => 0x7318, 0x65 => 0x730a, 0x66 => 0x7308, 0x67 => 0x72ff, + 0x68 => 0x730f, 0x69 => 0x731e, 0x6a => 0x7388, 0x6b => 0x73f6, + 0x6c => 0x73f8, 0x6d => 0x73f5, 0x6e => 0x7404, 0x6f => 0x7401, + 0x70 => 0x73fd, 0x71 => 0x7407, 0x72 => 0x7400, 0x73 => 0x73fa, + 0x74 => 0x73fc, 0x75 => 0x73ff, 0x76 => 0x740c, 0x77 => 0x740b, + 0x78 => 0x73f4, 0x79 => 0x7408, 0x7a => 0x7564, 0x7b => 0x7563, + 0x7c => 0x75ce, 0x7d => 0x75d2, 0x7e => 0x75cf, 0xa1 => 0x75cb, + 0xa2 => 0x75cc, 0xa3 => 0x75d1, 0xa4 => 0x75d0, 0xa5 => 0x768f, + 0xa6 => 0x7689, 0xa7 => 0x76d3, 0xa8 => 0x7739, 0xa9 => 0x772f, + 0xaa => 0x772d, 0xab => 0x7731, 0xac => 0x7732, 0xad => 0x7734, + 0xae => 0x7733, 0xaf => 0x773d, 0xb0 => 0x7725, 0xb1 => 0x773b, + 0xb2 => 0x7735, 0xb3 => 0x7848, 0xb4 => 0x7852, 0xb5 => 0x7849, + 0xb6 => 0x784d, 0xb7 => 0x784a, 0xb8 => 0x784c, 0xb9 => 0x7826, + 0xba => 0x7845, 0xbb => 0x7850, 0xbc => 0x7964, 0xbd => 0x7967, + 0xbe => 0x7969, 0xbf => 0x796a, 0xc0 => 0x7963, 0xc1 => 0x796b, + 0xc2 => 0x7961, 0xc3 => 0x79bb, 0xc4 => 0x79fa, 0xc5 => 0x79f8, + 0xc6 => 0x79f6, 0xc7 => 0x79f7, 0xc8 => 0x7a8f, 0xc9 => 0x7a94, + 0xca => 0x7a90, 0xcb => 0x7b35, 0xcc => 0x7b47, 0xcd => 0x7b34, + 0xce => 0x7b25, 0xcf => 0x7b30, 0xd0 => 0x7b22, 0xd1 => 0x7b24, + 0xd2 => 0x7b33, 0xd3 => 0x7b18, 0xd4 => 0x7b2a, 0xd5 => 0x7b1d, + 0xd6 => 0x7b31, 0xd7 => 0x7b2b, 0xd8 => 0x7b2d, 0xd9 => 0x7b2f, + 0xda => 0x7b32, 0xdb => 0x7b38, 0xdc => 0x7b1a, 0xdd => 0x7b23, + 0xde => 0x7c94, 0xdf => 0x7c98, 0xe0 => 0x7c96, 0xe1 => 0x7ca3, + 0xe2 => 0x7d35, 0xe3 => 0x7d3d, 0xe4 => 0x7d38, 0xe5 => 0x7d36, + 0xe6 => 0x7d3a, 0xe7 => 0x7d45, 0xe8 => 0x7d2c, 0xe9 => 0x7d29, + 0xea => 0x7d41, 0xeb => 0x7d47, 0xec => 0x7d3e, 0xed => 0x7d3f, + 0xee => 0x7d4a, 0xef => 0x7d3b, 0xf0 => 0x7d28, 0xf1 => 0x7f63, + 0xf2 => 0x7f95, 0xf3 => 0x7f9c, 0xf4 => 0x7f9d, 0xf5 => 0x7f9b, + 0xf6 => 0x7fca, 0xf7 => 0x7fcb, 0xf8 => 0x7fcd, 0xf9 => 0x7fd0, + 0xfa => 0x7fd1, 0xfb => 0x7fc7, 0xfc => 0x7fcf, 0xfd => 0x7fc9, + 0xfe => 0x801f, + }, + 0xd7 => { + 0x40 => 0x801e, 0x41 => 0x801b, 0x42 => 0x8047, 0x43 => 0x8043, + 0x44 => 0x8048, 0x45 => 0x8118, 0x46 => 0x8125, 0x47 => 0x8119, + 0x48 => 0x811b, 0x49 => 0x812d, 0x4a => 0x811f, 0x4b => 0x812c, + 0x4c => 0x811e, 0x4d => 0x8121, 0x4e => 0x8115, 0x4f => 0x8127, + 0x50 => 0x811d, 0x51 => 0x8122, 0x52 => 0x8211, 0x53 => 0x8238, + 0x54 => 0x8233, 0x55 => 0x823a, 0x56 => 0x8234, 0x57 => 0x8232, + 0x58 => 0x8274, 0x59 => 0x8390, 0x5a => 0x83a3, 0x5b => 0x83a8, + 0x5c => 0x838d, 0x5d => 0x837a, 0x5e => 0x8373, 0x5f => 0x83a4, + 0x60 => 0x8374, 0x61 => 0x838f, 0x62 => 0x8381, 0x63 => 0x8395, + 0x64 => 0x8399, 0x65 => 0x8375, 0x66 => 0x8394, 0x67 => 0x83a9, + 0x68 => 0x837d, 0x69 => 0x8383, 0x6a => 0x838c, 0x6b => 0x839d, + 0x6c => 0x839b, 0x6d => 0x83aa, 0x6e => 0x838b, 0x6f => 0x837e, + 0x70 => 0x83a5, 0x71 => 0x83af, 0x72 => 0x8388, 0x73 => 0x8397, + 0x74 => 0x83b0, 0x75 => 0x837f, 0x76 => 0x83a6, 0x77 => 0x8387, + 0x78 => 0x83ae, 0x79 => 0x8376, 0x7a => 0x839a, 0x7b => 0x8659, + 0x7c => 0x8656, 0x7d => 0x86bf, 0x7e => 0x86b7, 0xa1 => 0x86c2, + 0xa2 => 0x86c1, 0xa3 => 0x86c5, 0xa4 => 0x86ba, 0xa5 => 0x86b0, + 0xa6 => 0x86c8, 0xa7 => 0x86b9, 0xa8 => 0x86b3, 0xa9 => 0x86b8, + 0xaa => 0x86cc, 0xab => 0x86b4, 0xac => 0x86bb, 0xad => 0x86bc, + 0xae => 0x86c3, 0xaf => 0x86bd, 0xb0 => 0x86be, 0xb1 => 0x8852, + 0xb2 => 0x8889, 0xb3 => 0x8895, 0xb4 => 0x88a8, 0xb5 => 0x88a2, + 0xb6 => 0x88aa, 0xb7 => 0x889a, 0xb8 => 0x8891, 0xb9 => 0x88a1, + 0xba => 0x889f, 0xbb => 0x8898, 0xbc => 0x88a7, 0xbd => 0x8899, + 0xbe => 0x889b, 0xbf => 0x8897, 0xc0 => 0x88a4, 0xc1 => 0x88ac, + 0xc2 => 0x888c, 0xc3 => 0x8893, 0xc4 => 0x888e, 0xc5 => 0x8982, + 0xc6 => 0x89d6, 0xc7 => 0x89d9, 0xc8 => 0x89d5, 0xc9 => 0x8a30, + 0xca => 0x8a27, 0xcb => 0x8a2c, 0xcc => 0x8a1e, 0xcd => 0x8c39, + 0xce => 0x8c3b, 0xcf => 0x8c5c, 0xd0 => 0x8c5d, 0xd1 => 0x8c7d, + 0xd2 => 0x8ca5, 0xd3 => 0x8d7d, 0xd4 => 0x8d7b, 0xd5 => 0x8d79, + 0xd6 => 0x8dbc, 0xd7 => 0x8dc2, 0xd8 => 0x8db9, 0xd9 => 0x8dbf, + 0xda => 0x8dc1, 0xdb => 0x8ed8, 0xdc => 0x8ede, 0xdd => 0x8edd, + 0xde => 0x8edc, 0xdf => 0x8ed7, 0xe0 => 0x8ee0, 0xe1 => 0x8ee1, + 0xe2 => 0x9024, 0xe3 => 0x900b, 0xe4 => 0x9011, 0xe5 => 0x901c, + 0xe6 => 0x900c, 0xe7 => 0x9021, 0xe8 => 0x90ef, 0xe9 => 0x90ea, + 0xea => 0x90f0, 0xeb => 0x90f4, 0xec => 0x90f2, 0xed => 0x90f3, + 0xee => 0x90d4, 0xef => 0x90eb, 0xf0 => 0x90ec, 0xf1 => 0x90e9, + 0xf2 => 0x9156, 0xf3 => 0x9158, 0xf4 => 0x915a, 0xf5 => 0x9153, + 0xf6 => 0x9155, 0xf7 => 0x91ec, 0xf8 => 0x91f4, 0xf9 => 0x91f1, + 0xfa => 0x91f3, 0xfb => 0x91f8, 0xfc => 0x91e4, 0xfd => 0x91f9, + 0xfe => 0x91ea, + }, + 0xd8 => { + 0x40 => 0x91eb, 0x41 => 0x91f7, 0x42 => 0x91e8, 0x43 => 0x91ee, + 0x44 => 0x957a, 0x45 => 0x9586, 0x46 => 0x9588, 0x47 => 0x967c, + 0x48 => 0x966d, 0x49 => 0x966b, 0x4a => 0x9671, 0x4b => 0x966f, + 0x4c => 0x96bf, 0x4d => 0x976a, 0x4e => 0x9804, 0x4f => 0x98e5, + 0x50 => 0x9997, 0x51 => 0x509b, 0x52 => 0x5095, 0x53 => 0x5094, + 0x54 => 0x509e, 0x55 => 0x508b, 0x56 => 0x50a3, 0x57 => 0x5083, + 0x58 => 0x508c, 0x59 => 0x508e, 0x5a => 0x509d, 0x5b => 0x5068, + 0x5c => 0x509c, 0x5d => 0x5092, 0x5e => 0x5082, 0x5f => 0x5087, + 0x60 => 0x515f, 0x61 => 0x51d4, 0x62 => 0x5312, 0x63 => 0x5311, + 0x64 => 0x53a4, 0x65 => 0x53a7, 0x66 => 0x5591, 0x67 => 0x55a8, + 0x68 => 0x55a5, 0x69 => 0x55ad, 0x6a => 0x5577, 0x6b => 0x5645, + 0x6c => 0x55a2, 0x6d => 0x5593, 0x6e => 0x5588, 0x6f => 0x558f, + 0x70 => 0x55b5, 0x71 => 0x5581, 0x72 => 0x55a3, 0x73 => 0x5592, + 0x74 => 0x55a4, 0x75 => 0x557d, 0x76 => 0x558c, 0x77 => 0x55a6, + 0x78 => 0x557f, 0x79 => 0x5595, 0x7a => 0x55a1, 0x7b => 0x558e, + 0x7c => 0x570c, 0x7d => 0x5829, 0x7e => 0x5837, 0xa1 => 0x5819, + 0xa2 => 0x581e, 0xa3 => 0x5827, 0xa4 => 0x5823, 0xa5 => 0x5828, + 0xa6 => 0x57f5, 0xa7 => 0x5848, 0xa8 => 0x5825, 0xa9 => 0x581c, + 0xaa => 0x581b, 0xab => 0x5833, 0xac => 0x583f, 0xad => 0x5836, + 0xae => 0x582e, 0xaf => 0x5839, 0xb0 => 0x5838, 0xb1 => 0x582d, + 0xb2 => 0x582c, 0xb3 => 0x583b, 0xb4 => 0x5961, 0xb5 => 0x5aaf, + 0xb6 => 0x5a94, 0xb7 => 0x5a9f, 0xb8 => 0x5a7a, 0xb9 => 0x5aa2, + 0xba => 0x5a9e, 0xbb => 0x5a78, 0xbc => 0x5aa6, 0xbd => 0x5a7c, + 0xbe => 0x5aa5, 0xbf => 0x5aac, 0xc0 => 0x5a95, 0xc1 => 0x5aae, + 0xc2 => 0x5a37, 0xc3 => 0x5a84, 0xc4 => 0x5a8a, 0xc5 => 0x5a97, + 0xc6 => 0x5a83, 0xc7 => 0x5a8b, 0xc8 => 0x5aa9, 0xc9 => 0x5a7b, + 0xca => 0x5a7d, 0xcb => 0x5a8c, 0xcc => 0x5a9c, 0xcd => 0x5a8f, + 0xce => 0x5a93, 0xcf => 0x5a9d, 0xd0 => 0x5bea, 0xd1 => 0x5bcd, + 0xd2 => 0x5bcb, 0xd3 => 0x5bd4, 0xd4 => 0x5bd1, 0xd5 => 0x5bca, + 0xd6 => 0x5bce, 0xd7 => 0x5c0c, 0xd8 => 0x5c30, 0xd9 => 0x5d37, + 0xda => 0x5d43, 0xdb => 0x5d6b, 0xdc => 0x5d41, 0xdd => 0x5d4b, + 0xde => 0x5d3f, 0xdf => 0x5d35, 0xe0 => 0x5d51, 0xe1 => 0x5d4e, + 0xe2 => 0x5d55, 0xe3 => 0x5d33, 0xe4 => 0x5d3a, 0xe5 => 0x5d52, + 0xe6 => 0x5d3d, 0xe7 => 0x5d31, 0xe8 => 0x5d59, 0xe9 => 0x5d42, + 0xea => 0x5d39, 0xeb => 0x5d49, 0xec => 0x5d38, 0xed => 0x5d3c, + 0xee => 0x5d32, 0xef => 0x5d36, 0xf0 => 0x5d40, 0xf1 => 0x5d45, + 0xf2 => 0x5e44, 0xf3 => 0x5e41, 0xf4 => 0x5f58, 0xf5 => 0x5fa6, + 0xf6 => 0x5fa5, 0xf7 => 0x5fab, 0xf8 => 0x60c9, 0xf9 => 0x60b9, + 0xfa => 0x60cc, 0xfb => 0x60e2, 0xfc => 0x60ce, 0xfd => 0x60c4, + 0xfe => 0x6114, + }, + 0xd9 => { + 0x40 => 0x60f2, 0x41 => 0x610a, 0x42 => 0x6116, 0x43 => 0x6105, + 0x44 => 0x60f5, 0x45 => 0x6113, 0x46 => 0x60f8, 0x47 => 0x60fc, + 0x48 => 0x60fe, 0x49 => 0x60c1, 0x4a => 0x6103, 0x4b => 0x6118, + 0x4c => 0x611d, 0x4d => 0x6110, 0x4e => 0x60ff, 0x4f => 0x6104, + 0x50 => 0x610b, 0x51 => 0x624a, 0x52 => 0x6394, 0x53 => 0x63b1, + 0x54 => 0x63b0, 0x55 => 0x63ce, 0x56 => 0x63e5, 0x57 => 0x63e8, + 0x58 => 0x63ef, 0x59 => 0x63c3, 0x5a => 0x649d, 0x5b => 0x63f3, + 0x5c => 0x63ca, 0x5d => 0x63e0, 0x5e => 0x63f6, 0x5f => 0x63d5, + 0x60 => 0x63f2, 0x61 => 0x63f5, 0x62 => 0x6461, 0x63 => 0x63df, + 0x64 => 0x63be, 0x65 => 0x63dd, 0x66 => 0x63dc, 0x67 => 0x63c4, + 0x68 => 0x63d8, 0x69 => 0x63d3, 0x6a => 0x63c2, 0x6b => 0x63c7, + 0x6c => 0x63cc, 0x6d => 0x63cb, 0x6e => 0x63c8, 0x6f => 0x63f0, + 0x70 => 0x63d7, 0x71 => 0x63d9, 0x72 => 0x6532, 0x73 => 0x6567, + 0x74 => 0x656a, 0x75 => 0x6564, 0x76 => 0x655c, 0x77 => 0x6568, + 0x78 => 0x6565, 0x79 => 0x658c, 0x7a => 0x659d, 0x7b => 0x659e, + 0x7c => 0x65ae, 0x7d => 0x65d0, 0x7e => 0x65d2, 0xa1 => 0x667c, + 0xa2 => 0x666c, 0xa3 => 0x667b, 0xa4 => 0x6680, 0xa5 => 0x6671, + 0xa6 => 0x6679, 0xa7 => 0x666a, 0xa8 => 0x6672, 0xa9 => 0x6701, + 0xaa => 0x690c, 0xab => 0x68d3, 0xac => 0x6904, 0xad => 0x68dc, + 0xae => 0x692a, 0xaf => 0x68ec, 0xb0 => 0x68ea, 0xb1 => 0x68f1, + 0xb2 => 0x690f, 0xb3 => 0x68d6, 0xb4 => 0x68f7, 0xb5 => 0x68eb, + 0xb6 => 0x68e4, 0xb7 => 0x68f6, 0xb8 => 0x6913, 0xb9 => 0x6910, + 0xba => 0x68f3, 0xbb => 0x68e1, 0xbc => 0x6907, 0xbd => 0x68cc, + 0xbe => 0x6908, 0xbf => 0x6970, 0xc0 => 0x68b4, 0xc1 => 0x6911, + 0xc2 => 0x68ef, 0xc3 => 0x68c6, 0xc4 => 0x6914, 0xc5 => 0x68f8, + 0xc6 => 0x68d0, 0xc7 => 0x68fd, 0xc8 => 0x68fc, 0xc9 => 0x68e8, + 0xca => 0x690b, 0xcb => 0x690a, 0xcc => 0x6917, 0xcd => 0x68ce, + 0xce => 0x68c8, 0xcf => 0x68dd, 0xd0 => 0x68de, 0xd1 => 0x68e6, + 0xd2 => 0x68f4, 0xd3 => 0x68d1, 0xd4 => 0x6906, 0xd5 => 0x68d4, + 0xd6 => 0x68e9, 0xd7 => 0x6915, 0xd8 => 0x6925, 0xd9 => 0x68c7, + 0xda => 0x6b39, 0xdb => 0x6b3b, 0xdc => 0x6b3f, 0xdd => 0x6b3c, + 0xde => 0x6b94, 0xdf => 0x6b97, 0xe0 => 0x6b99, 0xe1 => 0x6b95, + 0xe2 => 0x6bbd, 0xe3 => 0x6bf0, 0xe4 => 0x6bf2, 0xe5 => 0x6bf3, + 0xe6 => 0x6c30, 0xe7 => 0x6dfc, 0xe8 => 0x6e46, 0xe9 => 0x6e47, + 0xea => 0x6e1f, 0xeb => 0x6e49, 0xec => 0x6e88, 0xed => 0x6e3c, + 0xee => 0x6e3d, 0xef => 0x6e45, 0xf0 => 0x6e62, 0xf1 => 0x6e2b, + 0xf2 => 0x6e3f, 0xf3 => 0x6e41, 0xf4 => 0x6e5d, 0xf5 => 0x6e73, + 0xf6 => 0x6e1c, 0xf7 => 0x6e33, 0xf8 => 0x6e4b, 0xf9 => 0x6e40, + 0xfa => 0x6e51, 0xfb => 0x6e3b, 0xfc => 0x6e03, 0xfd => 0x6e2e, + 0xfe => 0x6e5e, + }, + 0xda => { + 0x40 => 0x6e68, 0x41 => 0x6e5c, 0x42 => 0x6e61, 0x43 => 0x6e31, + 0x44 => 0x6e28, 0x45 => 0x6e60, 0x46 => 0x6e71, 0x47 => 0x6e6b, + 0x48 => 0x6e39, 0x49 => 0x6e22, 0x4a => 0x6e30, 0x4b => 0x6e53, + 0x4c => 0x6e65, 0x4d => 0x6e27, 0x4e => 0x6e78, 0x4f => 0x6e64, + 0x50 => 0x6e77, 0x51 => 0x6e55, 0x52 => 0x6e79, 0x53 => 0x6e52, + 0x54 => 0x6e66, 0x55 => 0x6e35, 0x56 => 0x6e36, 0x57 => 0x6e5a, + 0x58 => 0x7120, 0x59 => 0x711e, 0x5a => 0x712f, 0x5b => 0x70fb, + 0x5c => 0x712e, 0x5d => 0x7131, 0x5e => 0x7123, 0x5f => 0x7125, + 0x60 => 0x7122, 0x61 => 0x7132, 0x62 => 0x711f, 0x63 => 0x7128, + 0x64 => 0x713a, 0x65 => 0x711b, 0x66 => 0x724b, 0x67 => 0x725a, + 0x68 => 0x7288, 0x69 => 0x7289, 0x6a => 0x7286, 0x6b => 0x7285, + 0x6c => 0x728b, 0x6d => 0x7312, 0x6e => 0x730b, 0x6f => 0x7330, + 0x70 => 0x7322, 0x71 => 0x7331, 0x72 => 0x7333, 0x73 => 0x7327, + 0x74 => 0x7332, 0x75 => 0x732d, 0x76 => 0x7326, 0x77 => 0x7323, + 0x78 => 0x7335, 0x79 => 0x730c, 0x7a => 0x742e, 0x7b => 0x742c, + 0x7c => 0x7430, 0x7d => 0x742b, 0x7e => 0x7416, 0xa1 => 0x741a, + 0xa2 => 0x7421, 0xa3 => 0x742d, 0xa4 => 0x7431, 0xa5 => 0x7424, + 0xa6 => 0x7423, 0xa7 => 0x741d, 0xa8 => 0x7429, 0xa9 => 0x7420, + 0xaa => 0x7432, 0xab => 0x74fb, 0xac => 0x752f, 0xad => 0x756f, + 0xae => 0x756c, 0xaf => 0x75e7, 0xb0 => 0x75da, 0xb1 => 0x75e1, + 0xb2 => 0x75e6, 0xb3 => 0x75dd, 0xb4 => 0x75df, 0xb5 => 0x75e4, + 0xb6 => 0x75d7, 0xb7 => 0x7695, 0xb8 => 0x7692, 0xb9 => 0x76da, + 0xba => 0x7746, 0xbb => 0x7747, 0xbc => 0x7744, 0xbd => 0x774d, + 0xbe => 0x7745, 0xbf => 0x774a, 0xc0 => 0x774e, 0xc1 => 0x774b, + 0xc2 => 0x774c, 0xc3 => 0x77de, 0xc4 => 0x77ec, 0xc5 => 0x7860, + 0xc6 => 0x7864, 0xc7 => 0x7865, 0xc8 => 0x785c, 0xc9 => 0x786d, + 0xca => 0x7871, 0xcb => 0x786a, 0xcc => 0x786e, 0xcd => 0x7870, + 0xce => 0x7869, 0xcf => 0x7868, 0xd0 => 0x785e, 0xd1 => 0x7862, + 0xd2 => 0x7974, 0xd3 => 0x7973, 0xd4 => 0x7972, 0xd5 => 0x7970, + 0xd6 => 0x7a02, 0xd7 => 0x7a0a, 0xd8 => 0x7a03, 0xd9 => 0x7a0c, + 0xda => 0x7a04, 0xdb => 0x7a99, 0xdc => 0x7ae6, 0xdd => 0x7ae4, + 0xde => 0x7b4a, 0xdf => 0x7b3b, 0xe0 => 0x7b44, 0xe1 => 0x7b48, + 0xe2 => 0x7b4c, 0xe3 => 0x7b4e, 0xe4 => 0x7b40, 0xe5 => 0x7b58, + 0xe6 => 0x7b45, 0xe7 => 0x7ca2, 0xe8 => 0x7c9e, 0xe9 => 0x7ca8, + 0xea => 0x7ca1, 0xeb => 0x7d58, 0xec => 0x7d6f, 0xed => 0x7d63, + 0xee => 0x7d53, 0xef => 0x7d56, 0xf0 => 0x7d67, 0xf1 => 0x7d6a, + 0xf2 => 0x7d4f, 0xf3 => 0x7d6d, 0xf4 => 0x7d5c, 0xf5 => 0x7d6b, + 0xf6 => 0x7d52, 0xf7 => 0x7d54, 0xf8 => 0x7d69, 0xf9 => 0x7d51, + 0xfa => 0x7d5f, 0xfb => 0x7d4e, 0xfc => 0x7f3e, 0xfd => 0x7f3f, + 0xfe => 0x7f65, + }, + 0xdb => { + 0x40 => 0x7f66, 0x41 => 0x7fa2, 0x42 => 0x7fa0, 0x43 => 0x7fa1, + 0x44 => 0x7fd7, 0x45 => 0x8051, 0x46 => 0x804f, 0x47 => 0x8050, + 0x48 => 0x80fe, 0x49 => 0x80d4, 0x4a => 0x8143, 0x4b => 0x814a, + 0x4c => 0x8152, 0x4d => 0x814f, 0x4e => 0x8147, 0x4f => 0x813d, + 0x50 => 0x814d, 0x51 => 0x813a, 0x52 => 0x81e6, 0x53 => 0x81ee, + 0x54 => 0x81f7, 0x55 => 0x81f8, 0x56 => 0x81f9, 0x57 => 0x8204, + 0x58 => 0x823c, 0x59 => 0x823d, 0x5a => 0x823f, 0x5b => 0x8275, + 0x5c => 0x833b, 0x5d => 0x83cf, 0x5e => 0x83f9, 0x5f => 0x8423, + 0x60 => 0x83c0, 0x61 => 0x83e8, 0x62 => 0x8412, 0x63 => 0x83e7, + 0x64 => 0x83e4, 0x65 => 0x83fc, 0x66 => 0x83f6, 0x67 => 0x8410, + 0x68 => 0x83c6, 0x69 => 0x83c8, 0x6a => 0x83eb, 0x6b => 0x83e3, + 0x6c => 0x83bf, 0x6d => 0x8401, 0x6e => 0x83dd, 0x6f => 0x83e5, + 0x70 => 0x83d8, 0x71 => 0x83ff, 0x72 => 0x83e1, 0x73 => 0x83cb, + 0x74 => 0x83ce, 0x75 => 0x83d6, 0x76 => 0x83f5, 0x77 => 0x83c9, + 0x78 => 0x8409, 0x79 => 0x840f, 0x7a => 0x83de, 0x7b => 0x8411, + 0x7c => 0x8406, 0x7d => 0x83c2, 0x7e => 0x83f3, 0xa1 => 0x83d5, + 0xa2 => 0x83fa, 0xa3 => 0x83c7, 0xa4 => 0x83d1, 0xa5 => 0x83ea, + 0xa6 => 0x8413, 0xa7 => 0x83c3, 0xa8 => 0x83ec, 0xa9 => 0x83ee, + 0xaa => 0x83c4, 0xab => 0x83fb, 0xac => 0x83d7, 0xad => 0x83e2, + 0xae => 0x841b, 0xaf => 0x83db, 0xb0 => 0x83fe, 0xb1 => 0x86d8, + 0xb2 => 0x86e2, 0xb3 => 0x86e6, 0xb4 => 0x86d3, 0xb5 => 0x86e3, + 0xb6 => 0x86da, 0xb7 => 0x86ea, 0xb8 => 0x86dd, 0xb9 => 0x86eb, + 0xba => 0x86dc, 0xbb => 0x86ec, 0xbc => 0x86e9, 0xbd => 0x86d7, + 0xbe => 0x86e8, 0xbf => 0x86d1, 0xc0 => 0x8848, 0xc1 => 0x8856, + 0xc2 => 0x8855, 0xc3 => 0x88ba, 0xc4 => 0x88d7, 0xc5 => 0x88b9, + 0xc6 => 0x88b8, 0xc7 => 0x88c0, 0xc8 => 0x88be, 0xc9 => 0x88b6, + 0xca => 0x88bc, 0xcb => 0x88b7, 0xcc => 0x88bd, 0xcd => 0x88b2, + 0xce => 0x8901, 0xcf => 0x88c9, 0xd0 => 0x8995, 0xd1 => 0x8998, + 0xd2 => 0x8997, 0xd3 => 0x89dd, 0xd4 => 0x89da, 0xd5 => 0x89db, + 0xd6 => 0x8a4e, 0xd7 => 0x8a4d, 0xd8 => 0x8a39, 0xd9 => 0x8a59, + 0xda => 0x8a40, 0xdb => 0x8a57, 0xdc => 0x8a58, 0xdd => 0x8a44, + 0xde => 0x8a45, 0xdf => 0x8a52, 0xe0 => 0x8a48, 0xe1 => 0x8a51, + 0xe2 => 0x8a4a, 0xe3 => 0x8a4c, 0xe4 => 0x8a4f, 0xe5 => 0x8c5f, + 0xe6 => 0x8c81, 0xe7 => 0x8c80, 0xe8 => 0x8cba, 0xe9 => 0x8cbe, + 0xea => 0x8cb0, 0xeb => 0x8cb9, 0xec => 0x8cb5, 0xed => 0x8d84, + 0xee => 0x8d80, 0xef => 0x8d89, 0xf0 => 0x8dd8, 0xf1 => 0x8dd3, + 0xf2 => 0x8dcd, 0xf3 => 0x8dc7, 0xf4 => 0x8dd6, 0xf5 => 0x8ddc, + 0xf6 => 0x8dcf, 0xf7 => 0x8dd5, 0xf8 => 0x8dd9, 0xf9 => 0x8dc8, + 0xfa => 0x8dd7, 0xfb => 0x8dc5, 0xfc => 0x8eef, 0xfd => 0x8ef7, + 0xfe => 0x8efa, + }, + 0xdc => { + 0x40 => 0x8ef9, 0x41 => 0x8ee6, 0x42 => 0x8eee, 0x43 => 0x8ee5, + 0x44 => 0x8ef5, 0x45 => 0x8ee7, 0x46 => 0x8ee8, 0x47 => 0x8ef6, + 0x48 => 0x8eeb, 0x49 => 0x8ef1, 0x4a => 0x8eec, 0x4b => 0x8ef4, + 0x4c => 0x8ee9, 0x4d => 0x902d, 0x4e => 0x9034, 0x4f => 0x902f, + 0x50 => 0x9106, 0x51 => 0x912c, 0x52 => 0x9104, 0x53 => 0x90ff, + 0x54 => 0x90fc, 0x55 => 0x9108, 0x56 => 0x90f9, 0x57 => 0x90fb, + 0x58 => 0x9101, 0x59 => 0x9100, 0x5a => 0x9107, 0x5b => 0x9105, + 0x5c => 0x9103, 0x5d => 0x9161, 0x5e => 0x9164, 0x5f => 0x915f, + 0x60 => 0x9162, 0x61 => 0x9160, 0x62 => 0x9201, 0x63 => 0x920a, + 0x64 => 0x9225, 0x65 => 0x9203, 0x66 => 0x921a, 0x67 => 0x9226, + 0x68 => 0x920f, 0x69 => 0x920c, 0x6a => 0x9200, 0x6b => 0x9212, + 0x6c => 0x91ff, 0x6d => 0x91fd, 0x6e => 0x9206, 0x6f => 0x9204, + 0x70 => 0x9227, 0x71 => 0x9202, 0x72 => 0x921c, 0x73 => 0x9224, + 0x74 => 0x9219, 0x75 => 0x9217, 0x76 => 0x9205, 0x77 => 0x9216, + 0x78 => 0x957b, 0x79 => 0x958d, 0x7a => 0x958c, 0x7b => 0x9590, + 0x7c => 0x9687, 0x7d => 0x967e, 0x7e => 0x9688, 0xa1 => 0x9689, + 0xa2 => 0x9683, 0xa3 => 0x9680, 0xa4 => 0x96c2, 0xa5 => 0x96c8, + 0xa6 => 0x96c3, 0xa7 => 0x96f1, 0xa8 => 0x96f0, 0xa9 => 0x976c, + 0xaa => 0x9770, 0xab => 0x976e, 0xac => 0x9807, 0xad => 0x98a9, + 0xae => 0x98eb, 0xaf => 0x9ce6, 0xb0 => 0x9ef9, 0xb1 => 0x4e83, + 0xb2 => 0x4e84, 0xb3 => 0x4eb6, 0xb4 => 0x50bd, 0xb5 => 0x50bf, + 0xb6 => 0x50c6, 0xb7 => 0x50ae, 0xb8 => 0x50c4, 0xb9 => 0x50ca, + 0xba => 0x50b4, 0xbb => 0x50c8, 0xbc => 0x50c2, 0xbd => 0x50b0, + 0xbe => 0x50c1, 0xbf => 0x50ba, 0xc0 => 0x50b1, 0xc1 => 0x50cb, + 0xc2 => 0x50c9, 0xc3 => 0x50b6, 0xc4 => 0x50b8, 0xc5 => 0x51d7, + 0xc6 => 0x527a, 0xc7 => 0x5278, 0xc8 => 0x527b, 0xc9 => 0x527c, + 0xca => 0x55c3, 0xcb => 0x55db, 0xcc => 0x55cc, 0xcd => 0x55d0, + 0xce => 0x55cb, 0xcf => 0x55ca, 0xd0 => 0x55dd, 0xd1 => 0x55c0, + 0xd2 => 0x55d4, 0xd3 => 0x55c4, 0xd4 => 0x55e9, 0xd5 => 0x55bf, + 0xd6 => 0x55d2, 0xd7 => 0x558d, 0xd8 => 0x55cf, 0xd9 => 0x55d5, + 0xda => 0x55e2, 0xdb => 0x55d6, 0xdc => 0x55c8, 0xdd => 0x55f2, + 0xde => 0x55cd, 0xdf => 0x55d9, 0xe0 => 0x55c2, 0xe1 => 0x5714, + 0xe2 => 0x5853, 0xe3 => 0x5868, 0xe4 => 0x5864, 0xe5 => 0x584f, + 0xe6 => 0x584d, 0xe7 => 0x5849, 0xe8 => 0x586f, 0xe9 => 0x5855, + 0xea => 0x584e, 0xeb => 0x585d, 0xec => 0x5859, 0xed => 0x5865, + 0xee => 0x585b, 0xef => 0x583d, 0xf0 => 0x5863, 0xf1 => 0x5871, + 0xf2 => 0x58fc, 0xf3 => 0x5ac7, 0xf4 => 0x5ac4, 0xf5 => 0x5acb, + 0xf6 => 0x5aba, 0xf7 => 0x5ab8, 0xf8 => 0x5ab1, 0xf9 => 0x5ab5, + 0xfa => 0x5ab0, 0xfb => 0x5abf, 0xfc => 0x5ac8, 0xfd => 0x5abb, + 0xfe => 0x5ac6, + }, + 0xdd => { + 0x40 => 0x5ab7, 0x41 => 0x5ac0, 0x42 => 0x5aca, 0x43 => 0x5ab4, + 0x44 => 0x5ab6, 0x45 => 0x5acd, 0x46 => 0x5ab9, 0x47 => 0x5a90, + 0x48 => 0x5bd6, 0x49 => 0x5bd8, 0x4a => 0x5bd9, 0x4b => 0x5c1f, + 0x4c => 0x5c33, 0x4d => 0x5d71, 0x4e => 0x5d63, 0x4f => 0x5d4a, + 0x50 => 0x5d65, 0x51 => 0x5d72, 0x52 => 0x5d6c, 0x53 => 0x5d5e, + 0x54 => 0x5d68, 0x55 => 0x5d67, 0x56 => 0x5d62, 0x57 => 0x5df0, + 0x58 => 0x5e4f, 0x59 => 0x5e4e, 0x5a => 0x5e4a, 0x5b => 0x5e4d, + 0x5c => 0x5e4b, 0x5d => 0x5ec5, 0x5e => 0x5ecc, 0x5f => 0x5ec6, + 0x60 => 0x5ecb, 0x61 => 0x5ec7, 0x62 => 0x5f40, 0x63 => 0x5faf, + 0x64 => 0x5fad, 0x65 => 0x60f7, 0x66 => 0x6149, 0x67 => 0x614a, + 0x68 => 0x612b, 0x69 => 0x6145, 0x6a => 0x6136, 0x6b => 0x6132, + 0x6c => 0x612e, 0x6d => 0x6146, 0x6e => 0x612f, 0x6f => 0x614f, + 0x70 => 0x6129, 0x71 => 0x6140, 0x72 => 0x6220, 0x73 => 0x9168, + 0x74 => 0x6223, 0x75 => 0x6225, 0x76 => 0x6224, 0x77 => 0x63c5, + 0x78 => 0x63f1, 0x79 => 0x63eb, 0x7a => 0x6410, 0x7b => 0x6412, + 0x7c => 0x6409, 0x7d => 0x6420, 0x7e => 0x6424, 0xa1 => 0x6433, + 0xa2 => 0x6443, 0xa3 => 0x641f, 0xa4 => 0x6415, 0xa5 => 0x6418, + 0xa6 => 0x6439, 0xa7 => 0x6437, 0xa8 => 0x6422, 0xa9 => 0x6423, + 0xaa => 0x640c, 0xab => 0x6426, 0xac => 0x6430, 0xad => 0x6428, + 0xae => 0x6441, 0xaf => 0x6435, 0xb0 => 0x642f, 0xb1 => 0x640a, + 0xb2 => 0x641a, 0xb3 => 0x6440, 0xb4 => 0x6425, 0xb5 => 0x6427, + 0xb6 => 0x640b, 0xb7 => 0x63e7, 0xb8 => 0x641b, 0xb9 => 0x642e, + 0xba => 0x6421, 0xbb => 0x640e, 0xbc => 0x656f, 0xbd => 0x6592, + 0xbe => 0x65d3, 0xbf => 0x6686, 0xc0 => 0x668c, 0xc1 => 0x6695, + 0xc2 => 0x6690, 0xc3 => 0x668b, 0xc4 => 0x668a, 0xc5 => 0x6699, + 0xc6 => 0x6694, 0xc7 => 0x6678, 0xc8 => 0x6720, 0xc9 => 0x6966, + 0xca => 0x695f, 0xcb => 0x6938, 0xcc => 0x694e, 0xcd => 0x6962, + 0xce => 0x6971, 0xcf => 0x693f, 0xd0 => 0x6945, 0xd1 => 0x696a, + 0xd2 => 0x6939, 0xd3 => 0x6942, 0xd4 => 0x6957, 0xd5 => 0x6959, + 0xd6 => 0x697a, 0xd7 => 0x6948, 0xd8 => 0x6949, 0xd9 => 0x6935, + 0xda => 0x696c, 0xdb => 0x6933, 0xdc => 0x693d, 0xdd => 0x6965, + 0xde => 0x68f0, 0xdf => 0x6978, 0xe0 => 0x6934, 0xe1 => 0x6969, + 0xe2 => 0x6940, 0xe3 => 0x696f, 0xe4 => 0x6944, 0xe5 => 0x6976, + 0xe6 => 0x6958, 0xe7 => 0x6941, 0xe8 => 0x6974, 0xe9 => 0x694c, + 0xea => 0x693b, 0xeb => 0x694b, 0xec => 0x6937, 0xed => 0x695c, + 0xee => 0x694f, 0xef => 0x6951, 0xf0 => 0x6932, 0xf1 => 0x6952, + 0xf2 => 0x692f, 0xf3 => 0x697b, 0xf4 => 0x693c, 0xf5 => 0x6b46, + 0xf6 => 0x6b45, 0xf7 => 0x6b43, 0xf8 => 0x6b42, 0xf9 => 0x6b48, + 0xfa => 0x6b41, 0xfb => 0x6b9b, 0xfc => 0xfa0d, 0xfd => 0x6bfb, + 0xfe => 0x6bfc, + }, + 0xde => { + 0x40 => 0x6bf9, 0x41 => 0x6bf7, 0x42 => 0x6bf8, 0x43 => 0x6e9b, + 0x44 => 0x6ed6, 0x45 => 0x6ec8, 0x46 => 0x6e8f, 0x47 => 0x6ec0, + 0x48 => 0x6e9f, 0x49 => 0x6e93, 0x4a => 0x6e94, 0x4b => 0x6ea0, + 0x4c => 0x6eb1, 0x4d => 0x6eb9, 0x4e => 0x6ec6, 0x4f => 0x6ed2, + 0x50 => 0x6ebd, 0x51 => 0x6ec1, 0x52 => 0x6e9e, 0x53 => 0x6ec9, + 0x54 => 0x6eb7, 0x55 => 0x6eb0, 0x56 => 0x6ecd, 0x57 => 0x6ea6, + 0x58 => 0x6ecf, 0x59 => 0x6eb2, 0x5a => 0x6ebe, 0x5b => 0x6ec3, + 0x5c => 0x6edc, 0x5d => 0x6ed8, 0x5e => 0x6e99, 0x5f => 0x6e92, + 0x60 => 0x6e8e, 0x61 => 0x6e8d, 0x62 => 0x6ea4, 0x63 => 0x6ea1, + 0x64 => 0x6ebf, 0x65 => 0x6eb3, 0x66 => 0x6ed0, 0x67 => 0x6eca, + 0x68 => 0x6e97, 0x69 => 0x6eae, 0x6a => 0x6ea3, 0x6b => 0x7147, + 0x6c => 0x7154, 0x6d => 0x7152, 0x6e => 0x7163, 0x6f => 0x7160, + 0x70 => 0x7141, 0x71 => 0x715d, 0x72 => 0x7162, 0x73 => 0x7172, + 0x74 => 0x7178, 0x75 => 0x716a, 0x76 => 0x7161, 0x77 => 0x7142, + 0x78 => 0x7158, 0x79 => 0x7143, 0x7a => 0x714b, 0x7b => 0x7170, + 0x7c => 0x715f, 0x7d => 0x7150, 0x7e => 0x7153, 0xa1 => 0x7144, + 0xa2 => 0x714d, 0xa3 => 0x715a, 0xa4 => 0x724f, 0xa5 => 0x728d, + 0xa6 => 0x728c, 0xa7 => 0x7291, 0xa8 => 0x7290, 0xa9 => 0x728e, + 0xaa => 0x733c, 0xab => 0x7342, 0xac => 0x733b, 0xad => 0x733a, + 0xae => 0x7340, 0xaf => 0x734a, 0xb0 => 0x7349, 0xb1 => 0x7444, + 0xb2 => 0x744a, 0xb3 => 0x744b, 0xb4 => 0x7452, 0xb5 => 0x7451, + 0xb6 => 0x7457, 0xb7 => 0x7440, 0xb8 => 0x744f, 0xb9 => 0x7450, + 0xba => 0x744e, 0xbb => 0x7442, 0xbc => 0x7446, 0xbd => 0x744d, + 0xbe => 0x7454, 0xbf => 0x74e1, 0xc0 => 0x74ff, 0xc1 => 0x74fe, + 0xc2 => 0x74fd, 0xc3 => 0x751d, 0xc4 => 0x7579, 0xc5 => 0x7577, + 0xc6 => 0x6983, 0xc7 => 0x75ef, 0xc8 => 0x760f, 0xc9 => 0x7603, + 0xca => 0x75f7, 0xcb => 0x75fe, 0xcc => 0x75fc, 0xcd => 0x75f9, + 0xce => 0x75f8, 0xcf => 0x7610, 0xd0 => 0x75fb, 0xd1 => 0x75f6, + 0xd2 => 0x75ed, 0xd3 => 0x75f5, 0xd4 => 0x75fd, 0xd5 => 0x7699, + 0xd6 => 0x76b5, 0xd7 => 0x76dd, 0xd8 => 0x7755, 0xd9 => 0x775f, + 0xda => 0x7760, 0xdb => 0x7752, 0xdc => 0x7756, 0xdd => 0x775a, + 0xde => 0x7769, 0xdf => 0x7767, 0xe0 => 0x7754, 0xe1 => 0x7759, + 0xe2 => 0x776d, 0xe3 => 0x77e0, 0xe4 => 0x7887, 0xe5 => 0x789a, + 0xe6 => 0x7894, 0xe7 => 0x788f, 0xe8 => 0x7884, 0xe9 => 0x7895, + 0xea => 0x7885, 0xeb => 0x7886, 0xec => 0x78a1, 0xed => 0x7883, + 0xee => 0x7879, 0xef => 0x7899, 0xf0 => 0x7880, 0xf1 => 0x7896, + 0xf2 => 0x787b, 0xf3 => 0x797c, 0xf4 => 0x7982, 0xf5 => 0x797d, + 0xf6 => 0x7979, 0xf7 => 0x7a11, 0xf8 => 0x7a18, 0xf9 => 0x7a19, + 0xfa => 0x7a12, 0xfb => 0x7a17, 0xfc => 0x7a15, 0xfd => 0x7a22, + 0xfe => 0x7a13, + }, + 0xdf => { + 0x40 => 0x7a1b, 0x41 => 0x7a10, 0x42 => 0x7aa3, 0x43 => 0x7aa2, + 0x44 => 0x7a9e, 0x45 => 0x7aeb, 0x46 => 0x7b66, 0x47 => 0x7b64, + 0x48 => 0x7b6d, 0x49 => 0x7b74, 0x4a => 0x7b69, 0x4b => 0x7b72, + 0x4c => 0x7b65, 0x4d => 0x7b73, 0x4e => 0x7b71, 0x4f => 0x7b70, + 0x50 => 0x7b61, 0x51 => 0x7b78, 0x52 => 0x7b76, 0x53 => 0x7b63, + 0x54 => 0x7cb2, 0x55 => 0x7cb4, 0x56 => 0x7caf, 0x57 => 0x7d88, + 0x58 => 0x7d86, 0x59 => 0x7d80, 0x5a => 0x7d8d, 0x5b => 0x7d7f, + 0x5c => 0x7d85, 0x5d => 0x7d7a, 0x5e => 0x7d8e, 0x5f => 0x7d7b, + 0x60 => 0x7d83, 0x61 => 0x7d7c, 0x62 => 0x7d8c, 0x63 => 0x7d94, + 0x64 => 0x7d84, 0x65 => 0x7d7d, 0x66 => 0x7d92, 0x67 => 0x7f6d, + 0x68 => 0x7f6b, 0x69 => 0x7f67, 0x6a => 0x7f68, 0x6b => 0x7f6c, + 0x6c => 0x7fa6, 0x6d => 0x7fa5, 0x6e => 0x7fa7, 0x6f => 0x7fdb, + 0x70 => 0x7fdc, 0x71 => 0x8021, 0x72 => 0x8164, 0x73 => 0x8160, + 0x74 => 0x8177, 0x75 => 0x815c, 0x76 => 0x8169, 0x77 => 0x815b, + 0x78 => 0x8162, 0x79 => 0x8172, 0x7a => 0x6721, 0x7b => 0x815e, + 0x7c => 0x8176, 0x7d => 0x8167, 0x7e => 0x816f, 0xa1 => 0x8144, + 0xa2 => 0x8161, 0xa3 => 0x821d, 0xa4 => 0x8249, 0xa5 => 0x8244, + 0xa6 => 0x8240, 0xa7 => 0x8242, 0xa8 => 0x8245, 0xa9 => 0x84f1, + 0xaa => 0x843f, 0xab => 0x8456, 0xac => 0x8476, 0xad => 0x8479, + 0xae => 0x848f, 0xaf => 0x848d, 0xb0 => 0x8465, 0xb1 => 0x8451, + 0xb2 => 0x8440, 0xb3 => 0x8486, 0xb4 => 0x8467, 0xb5 => 0x8430, + 0xb6 => 0x844d, 0xb7 => 0x847d, 0xb8 => 0x845a, 0xb9 => 0x8459, + 0xba => 0x8474, 0xbb => 0x8473, 0xbc => 0x845d, 0xbd => 0x8507, + 0xbe => 0x845e, 0xbf => 0x8437, 0xc0 => 0x843a, 0xc1 => 0x8434, + 0xc2 => 0x847a, 0xc3 => 0x8443, 0xc4 => 0x8478, 0xc5 => 0x8432, + 0xc6 => 0x8445, 0xc7 => 0x8429, 0xc8 => 0x83d9, 0xc9 => 0x844b, + 0xca => 0x842f, 0xcb => 0x8442, 0xcc => 0x842d, 0xcd => 0x845f, + 0xce => 0x8470, 0xcf => 0x8439, 0xd0 => 0x844e, 0xd1 => 0x844c, + 0xd2 => 0x8452, 0xd3 => 0x846f, 0xd4 => 0x84c5, 0xd5 => 0x848e, + 0xd6 => 0x843b, 0xd7 => 0x8447, 0xd8 => 0x8436, 0xd9 => 0x8433, + 0xda => 0x8468, 0xdb => 0x847e, 0xdc => 0x8444, 0xdd => 0x842b, + 0xde => 0x8460, 0xdf => 0x8454, 0xe0 => 0x846e, 0xe1 => 0x8450, + 0xe2 => 0x870b, 0xe3 => 0x8704, 0xe4 => 0x86f7, 0xe5 => 0x870c, + 0xe6 => 0x86fa, 0xe7 => 0x86d6, 0xe8 => 0x86f5, 0xe9 => 0x874d, + 0xea => 0x86f8, 0xeb => 0x870e, 0xec => 0x8709, 0xed => 0x8701, + 0xee => 0x86f6, 0xef => 0x870d, 0xf0 => 0x8705, 0xf1 => 0x88d6, + 0xf2 => 0x88cb, 0xf3 => 0x88cd, 0xf4 => 0x88ce, 0xf5 => 0x88de, + 0xf6 => 0x88db, 0xf7 => 0x88da, 0xf8 => 0x88cc, 0xf9 => 0x88d0, + 0xfa => 0x8985, 0xfb => 0x899b, 0xfc => 0x89df, 0xfd => 0x89e5, + 0xfe => 0x89e4, + }, + 0xe0 => { + 0x40 => 0x89e1, 0x41 => 0x89e0, 0x42 => 0x89e2, 0x43 => 0x89dc, + 0x44 => 0x89e6, 0x45 => 0x8a76, 0x46 => 0x8a86, 0x47 => 0x8a7f, + 0x48 => 0x8a61, 0x49 => 0x8a3f, 0x4a => 0x8a77, 0x4b => 0x8a82, + 0x4c => 0x8a84, 0x4d => 0x8a75, 0x4e => 0x8a83, 0x4f => 0x8a81, + 0x50 => 0x8a74, 0x51 => 0x8a7a, 0x52 => 0x8c3c, 0x53 => 0x8c4b, + 0x54 => 0x8c4a, 0x55 => 0x8c65, 0x56 => 0x8c64, 0x57 => 0x8c66, + 0x58 => 0x8c86, 0x59 => 0x8c84, 0x5a => 0x8c85, 0x5b => 0x8ccc, + 0x5c => 0x8d68, 0x5d => 0x8d69, 0x5e => 0x8d91, 0x5f => 0x8d8c, + 0x60 => 0x8d8e, 0x61 => 0x8d8f, 0x62 => 0x8d8d, 0x63 => 0x8d93, + 0x64 => 0x8d94, 0x65 => 0x8d90, 0x66 => 0x8d92, 0x67 => 0x8df0, + 0x68 => 0x8de0, 0x69 => 0x8dec, 0x6a => 0x8df1, 0x6b => 0x8dee, + 0x6c => 0x8dd0, 0x6d => 0x8de9, 0x6e => 0x8de3, 0x6f => 0x8de2, + 0x70 => 0x8de7, 0x71 => 0x8df2, 0x72 => 0x8deb, 0x73 => 0x8df4, + 0x74 => 0x8f06, 0x75 => 0x8eff, 0x76 => 0x8f01, 0x77 => 0x8f00, + 0x78 => 0x8f05, 0x79 => 0x8f07, 0x7a => 0x8f08, 0x7b => 0x8f02, + 0x7c => 0x8f0b, 0x7d => 0x9052, 0x7e => 0x903f, 0xa1 => 0x9044, + 0xa2 => 0x9049, 0xa3 => 0x903d, 0xa4 => 0x9110, 0xa5 => 0x910d, + 0xa6 => 0x910f, 0xa7 => 0x9111, 0xa8 => 0x9116, 0xa9 => 0x9114, + 0xaa => 0x910b, 0xab => 0x910e, 0xac => 0x916e, 0xad => 0x916f, + 0xae => 0x9248, 0xaf => 0x9252, 0xb0 => 0x9230, 0xb1 => 0x923a, + 0xb2 => 0x9266, 0xb3 => 0x9233, 0xb4 => 0x9265, 0xb5 => 0x925e, + 0xb6 => 0x9283, 0xb7 => 0x922e, 0xb8 => 0x924a, 0xb9 => 0x9246, + 0xba => 0x926d, 0xbb => 0x926c, 0xbc => 0x924f, 0xbd => 0x9260, + 0xbe => 0x9267, 0xbf => 0x926f, 0xc0 => 0x9236, 0xc1 => 0x9261, + 0xc2 => 0x9270, 0xc3 => 0x9231, 0xc4 => 0x9254, 0xc5 => 0x9263, + 0xc6 => 0x9250, 0xc7 => 0x9272, 0xc8 => 0x924e, 0xc9 => 0x9253, + 0xca => 0x924c, 0xcb => 0x9256, 0xcc => 0x9232, 0xcd => 0x959f, + 0xce => 0x959c, 0xcf => 0x959e, 0xd0 => 0x959b, 0xd1 => 0x9692, + 0xd2 => 0x9693, 0xd3 => 0x9691, 0xd4 => 0x9697, 0xd5 => 0x96ce, + 0xd6 => 0x96fa, 0xd7 => 0x96fd, 0xd8 => 0x96f8, 0xd9 => 0x96f5, + 0xda => 0x9773, 0xdb => 0x9777, 0xdc => 0x9778, 0xdd => 0x9772, + 0xde => 0x980f, 0xdf => 0x980d, 0xe0 => 0x980e, 0xe1 => 0x98ac, + 0xe2 => 0x98f6, 0xe3 => 0x98f9, 0xe4 => 0x99af, 0xe5 => 0x99b2, + 0xe6 => 0x99b0, 0xe7 => 0x99b5, 0xe8 => 0x9aad, 0xe9 => 0x9aab, + 0xea => 0x9b5b, 0xeb => 0x9cea, 0xec => 0x9ced, 0xed => 0x9ce7, + 0xee => 0x9e80, 0xef => 0x9efd, 0xf0 => 0x50e6, 0xf1 => 0x50d4, + 0xf2 => 0x50d7, 0xf3 => 0x50e8, 0xf4 => 0x50f3, 0xf5 => 0x50db, + 0xf6 => 0x50ea, 0xf7 => 0x50dd, 0xf8 => 0x50e4, 0xf9 => 0x50d3, + 0xfa => 0x50ec, 0xfb => 0x50f0, 0xfc => 0x50ef, 0xfd => 0x50e3, + 0xfe => 0x50e0, + }, + 0xe1 => { + 0x40 => 0x51d8, 0x41 => 0x5280, 0x42 => 0x5281, 0x43 => 0x52e9, + 0x44 => 0x52eb, 0x45 => 0x5330, 0x46 => 0x53ac, 0x47 => 0x5627, + 0x48 => 0x5615, 0x49 => 0x560c, 0x4a => 0x5612, 0x4b => 0x55fc, + 0x4c => 0x560f, 0x4d => 0x561c, 0x4e => 0x5601, 0x4f => 0x5613, + 0x50 => 0x5602, 0x51 => 0x55fa, 0x52 => 0x561d, 0x53 => 0x5604, + 0x54 => 0x55ff, 0x55 => 0x55f9, 0x56 => 0x5889, 0x57 => 0x587c, + 0x58 => 0x5890, 0x59 => 0x5898, 0x5a => 0x5886, 0x5b => 0x5881, + 0x5c => 0x587f, 0x5d => 0x5874, 0x5e => 0x588b, 0x5f => 0x587a, + 0x60 => 0x5887, 0x61 => 0x5891, 0x62 => 0x588e, 0x63 => 0x5876, + 0x64 => 0x5882, 0x65 => 0x5888, 0x66 => 0x587b, 0x67 => 0x5894, + 0x68 => 0x588f, 0x69 => 0x58fe, 0x6a => 0x596b, 0x6b => 0x5adc, + 0x6c => 0x5aee, 0x6d => 0x5ae5, 0x6e => 0x5ad5, 0x6f => 0x5aea, + 0x70 => 0x5ada, 0x71 => 0x5aed, 0x72 => 0x5aeb, 0x73 => 0x5af3, + 0x74 => 0x5ae2, 0x75 => 0x5ae0, 0x76 => 0x5adb, 0x77 => 0x5aec, + 0x78 => 0x5ade, 0x79 => 0x5add, 0x7a => 0x5ad9, 0x7b => 0x5ae8, + 0x7c => 0x5adf, 0x7d => 0x5b77, 0x7e => 0x5be0, 0xa1 => 0x5be3, + 0xa2 => 0x5c63, 0xa3 => 0x5d82, 0xa4 => 0x5d80, 0xa5 => 0x5d7d, + 0xa6 => 0x5d86, 0xa7 => 0x5d7a, 0xa8 => 0x5d81, 0xa9 => 0x5d77, + 0xaa => 0x5d8a, 0xab => 0x5d89, 0xac => 0x5d88, 0xad => 0x5d7e, + 0xae => 0x5d7c, 0xaf => 0x5d8d, 0xb0 => 0x5d79, 0xb1 => 0x5d7f, + 0xb2 => 0x5e58, 0xb3 => 0x5e59, 0xb4 => 0x5e53, 0xb5 => 0x5ed8, + 0xb6 => 0x5ed1, 0xb7 => 0x5ed7, 0xb8 => 0x5ece, 0xb9 => 0x5edc, + 0xba => 0x5ed5, 0xbb => 0x5ed9, 0xbc => 0x5ed2, 0xbd => 0x5ed4, + 0xbe => 0x5f44, 0xbf => 0x5f43, 0xc0 => 0x5f6f, 0xc1 => 0x5fb6, + 0xc2 => 0x612c, 0xc3 => 0x6128, 0xc4 => 0x6141, 0xc5 => 0x615e, + 0xc6 => 0x6171, 0xc7 => 0x6173, 0xc8 => 0x6152, 0xc9 => 0x6153, + 0xca => 0x6172, 0xcb => 0x616c, 0xcc => 0x6180, 0xcd => 0x6174, + 0xce => 0x6154, 0xcf => 0x617a, 0xd0 => 0x615b, 0xd1 => 0x6165, + 0xd2 => 0x613b, 0xd3 => 0x616a, 0xd4 => 0x6161, 0xd5 => 0x6156, + 0xd6 => 0x6229, 0xd7 => 0x6227, 0xd8 => 0x622b, 0xd9 => 0x642b, + 0xda => 0x644d, 0xdb => 0x645b, 0xdc => 0x645d, 0xdd => 0x6474, + 0xde => 0x6476, 0xdf => 0x6472, 0xe0 => 0x6473, 0xe1 => 0x647d, + 0xe2 => 0x6475, 0xe3 => 0x6466, 0xe4 => 0x64a6, 0xe5 => 0x644e, + 0xe6 => 0x6482, 0xe7 => 0x645e, 0xe8 => 0x645c, 0xe9 => 0x644b, + 0xea => 0x6453, 0xeb => 0x6460, 0xec => 0x6450, 0xed => 0x647f, + 0xee => 0x643f, 0xef => 0x646c, 0xf0 => 0x646b, 0xf1 => 0x6459, + 0xf2 => 0x6465, 0xf3 => 0x6477, 0xf4 => 0x6573, 0xf5 => 0x65a0, + 0xf6 => 0x66a1, 0xf7 => 0x66a0, 0xf8 => 0x669f, 0xf9 => 0x6705, + 0xfa => 0x6704, 0xfb => 0x6722, 0xfc => 0x69b1, 0xfd => 0x69b6, + 0xfe => 0x69c9, + }, + 0xe2 => { + 0x40 => 0x69a0, 0x41 => 0x69ce, 0x42 => 0x6996, 0x43 => 0x69b0, + 0x44 => 0x69ac, 0x45 => 0x69bc, 0x46 => 0x6991, 0x47 => 0x6999, + 0x48 => 0x698e, 0x49 => 0x69a7, 0x4a => 0x698d, 0x4b => 0x69a9, + 0x4c => 0x69be, 0x4d => 0x69af, 0x4e => 0x69bf, 0x4f => 0x69c4, + 0x50 => 0x69bd, 0x51 => 0x69a4, 0x52 => 0x69d4, 0x53 => 0x69b9, + 0x54 => 0x69ca, 0x55 => 0x699a, 0x56 => 0x69cf, 0x57 => 0x69b3, + 0x58 => 0x6993, 0x59 => 0x69aa, 0x5a => 0x69a1, 0x5b => 0x699e, + 0x5c => 0x69d9, 0x5d => 0x6997, 0x5e => 0x6990, 0x5f => 0x69c2, + 0x60 => 0x69b5, 0x61 => 0x69a5, 0x62 => 0x69c6, 0x63 => 0x6b4a, + 0x64 => 0x6b4d, 0x65 => 0x6b4b, 0x66 => 0x6b9e, 0x67 => 0x6b9f, + 0x68 => 0x6ba0, 0x69 => 0x6bc3, 0x6a => 0x6bc4, 0x6b => 0x6bfe, + 0x6c => 0x6ece, 0x6d => 0x6ef5, 0x6e => 0x6ef1, 0x6f => 0x6f03, + 0x70 => 0x6f25, 0x71 => 0x6ef8, 0x72 => 0x6f37, 0x73 => 0x6efb, + 0x74 => 0x6f2e, 0x75 => 0x6f09, 0x76 => 0x6f4e, 0x77 => 0x6f19, + 0x78 => 0x6f1a, 0x79 => 0x6f27, 0x7a => 0x6f18, 0x7b => 0x6f3b, + 0x7c => 0x6f12, 0x7d => 0x6eed, 0x7e => 0x6f0a, 0xa1 => 0x6f36, + 0xa2 => 0x6f73, 0xa3 => 0x6ef9, 0xa4 => 0x6eee, 0xa5 => 0x6f2d, + 0xa6 => 0x6f40, 0xa7 => 0x6f30, 0xa8 => 0x6f3c, 0xa9 => 0x6f35, + 0xaa => 0x6eeb, 0xab => 0x6f07, 0xac => 0x6f0e, 0xad => 0x6f43, + 0xae => 0x6f05, 0xaf => 0x6efd, 0xb0 => 0x6ef6, 0xb1 => 0x6f39, + 0xb2 => 0x6f1c, 0xb3 => 0x6efc, 0xb4 => 0x6f3a, 0xb5 => 0x6f1f, + 0xb6 => 0x6f0d, 0xb7 => 0x6f1e, 0xb8 => 0x6f08, 0xb9 => 0x6f21, + 0xba => 0x7187, 0xbb => 0x7190, 0xbc => 0x7189, 0xbd => 0x7180, + 0xbe => 0x7185, 0xbf => 0x7182, 0xc0 => 0x718f, 0xc1 => 0x717b, + 0xc2 => 0x7186, 0xc3 => 0x7181, 0xc4 => 0x7197, 0xc5 => 0x7244, + 0xc6 => 0x7253, 0xc7 => 0x7297, 0xc8 => 0x7295, 0xc9 => 0x7293, + 0xca => 0x7343, 0xcb => 0x734d, 0xcc => 0x7351, 0xcd => 0x734c, + 0xce => 0x7462, 0xcf => 0x7473, 0xd0 => 0x7471, 0xd1 => 0x7475, + 0xd2 => 0x7472, 0xd3 => 0x7467, 0xd4 => 0x746e, 0xd5 => 0x7500, + 0xd6 => 0x7502, 0xd7 => 0x7503, 0xd8 => 0x757d, 0xd9 => 0x7590, + 0xda => 0x7616, 0xdb => 0x7608, 0xdc => 0x760c, 0xdd => 0x7615, + 0xde => 0x7611, 0xdf => 0x760a, 0xe0 => 0x7614, 0xe1 => 0x76b8, + 0xe2 => 0x7781, 0xe3 => 0x777c, 0xe4 => 0x7785, 0xe5 => 0x7782, + 0xe6 => 0x776e, 0xe7 => 0x7780, 0xe8 => 0x776f, 0xe9 => 0x777e, + 0xea => 0x7783, 0xeb => 0x78b2, 0xec => 0x78aa, 0xed => 0x78b4, + 0xee => 0x78ad, 0xef => 0x78a8, 0xf0 => 0x787e, 0xf1 => 0x78ab, + 0xf2 => 0x789e, 0xf3 => 0x78a5, 0xf4 => 0x78a0, 0xf5 => 0x78ac, + 0xf6 => 0x78a2, 0xf7 => 0x78a4, 0xf8 => 0x7998, 0xf9 => 0x798a, + 0xfa => 0x798b, 0xfb => 0x7996, 0xfc => 0x7995, 0xfd => 0x7994, + 0xfe => 0x7993, + }, + 0xe3 => { + 0x40 => 0x7997, 0x41 => 0x7988, 0x42 => 0x7992, 0x43 => 0x7990, + 0x44 => 0x7a2b, 0x45 => 0x7a4a, 0x46 => 0x7a30, 0x47 => 0x7a2f, + 0x48 => 0x7a28, 0x49 => 0x7a26, 0x4a => 0x7aa8, 0x4b => 0x7aab, + 0x4c => 0x7aac, 0x4d => 0x7aee, 0x4e => 0x7b88, 0x4f => 0x7b9c, + 0x50 => 0x7b8a, 0x51 => 0x7b91, 0x52 => 0x7b90, 0x53 => 0x7b96, + 0x54 => 0x7b8d, 0x55 => 0x7b8c, 0x56 => 0x7b9b, 0x57 => 0x7b8e, + 0x58 => 0x7b85, 0x59 => 0x7b98, 0x5a => 0x5284, 0x5b => 0x7b99, + 0x5c => 0x7ba4, 0x5d => 0x7b82, 0x5e => 0x7cbb, 0x5f => 0x7cbf, + 0x60 => 0x7cbc, 0x61 => 0x7cba, 0x62 => 0x7da7, 0x63 => 0x7db7, + 0x64 => 0x7dc2, 0x65 => 0x7da3, 0x66 => 0x7daa, 0x67 => 0x7dc1, + 0x68 => 0x7dc0, 0x69 => 0x7dc5, 0x6a => 0x7d9d, 0x6b => 0x7dce, + 0x6c => 0x7dc4, 0x6d => 0x7dc6, 0x6e => 0x7dcb, 0x6f => 0x7dcc, + 0x70 => 0x7daf, 0x71 => 0x7db9, 0x72 => 0x7d96, 0x73 => 0x7dbc, + 0x74 => 0x7d9f, 0x75 => 0x7da6, 0x76 => 0x7dae, 0x77 => 0x7da9, + 0x78 => 0x7da1, 0x79 => 0x7dc9, 0x7a => 0x7f73, 0x7b => 0x7fe2, + 0x7c => 0x7fe3, 0x7d => 0x7fe5, 0x7e => 0x7fde, 0xa1 => 0x8024, + 0xa2 => 0x805d, 0xa3 => 0x805c, 0xa4 => 0x8189, 0xa5 => 0x8186, + 0xa6 => 0x8183, 0xa7 => 0x8187, 0xa8 => 0x818d, 0xa9 => 0x818c, + 0xaa => 0x818b, 0xab => 0x8215, 0xac => 0x8497, 0xad => 0x84a4, + 0xae => 0x84a1, 0xaf => 0x849f, 0xb0 => 0x84ba, 0xb1 => 0x84ce, + 0xb2 => 0x84c2, 0xb3 => 0x84ac, 0xb4 => 0x84ae, 0xb5 => 0x84ab, + 0xb6 => 0x84b9, 0xb7 => 0x84b4, 0xb8 => 0x84c1, 0xb9 => 0x84cd, + 0xba => 0x84aa, 0xbb => 0x849a, 0xbc => 0x84b1, 0xbd => 0x84d0, + 0xbe => 0x849d, 0xbf => 0x84a7, 0xc0 => 0x84bb, 0xc1 => 0x84a2, + 0xc2 => 0x8494, 0xc3 => 0x84c7, 0xc4 => 0x84cc, 0xc5 => 0x849b, + 0xc6 => 0x84a9, 0xc7 => 0x84af, 0xc8 => 0x84a8, 0xc9 => 0x84d6, + 0xca => 0x8498, 0xcb => 0x84b6, 0xcc => 0x84cf, 0xcd => 0x84a0, + 0xce => 0x84d7, 0xcf => 0x84d4, 0xd0 => 0x84d2, 0xd1 => 0x84db, + 0xd2 => 0x84b0, 0xd3 => 0x8491, 0xd4 => 0x8661, 0xd5 => 0x8733, + 0xd6 => 0x8723, 0xd7 => 0x8728, 0xd8 => 0x876b, 0xd9 => 0x8740, + 0xda => 0x872e, 0xdb => 0x871e, 0xdc => 0x8721, 0xdd => 0x8719, + 0xde => 0x871b, 0xdf => 0x8743, 0xe0 => 0x872c, 0xe1 => 0x8741, + 0xe2 => 0x873e, 0xe3 => 0x8746, 0xe4 => 0x8720, 0xe5 => 0x8732, + 0xe6 => 0x872a, 0xe7 => 0x872d, 0xe8 => 0x873c, 0xe9 => 0x8712, + 0xea => 0x873a, 0xeb => 0x8731, 0xec => 0x8735, 0xed => 0x8742, + 0xee => 0x8726, 0xef => 0x8727, 0xf0 => 0x8738, 0xf1 => 0x8724, + 0xf2 => 0x871a, 0xf3 => 0x8730, 0xf4 => 0x8711, 0xf5 => 0x88f7, + 0xf6 => 0x88e7, 0xf7 => 0x88f1, 0xf8 => 0x88f2, 0xf9 => 0x88fa, + 0xfa => 0x88fe, 0xfb => 0x88ee, 0xfc => 0x88fc, 0xfd => 0x88f6, + 0xfe => 0x88fb, + }, + 0xe4 => { + 0x40 => 0x88f0, 0x41 => 0x88ec, 0x42 => 0x88eb, 0x43 => 0x899d, + 0x44 => 0x89a1, 0x45 => 0x899f, 0x46 => 0x899e, 0x47 => 0x89e9, + 0x48 => 0x89eb, 0x49 => 0x89e8, 0x4a => 0x8aab, 0x4b => 0x8a99, + 0x4c => 0x8a8b, 0x4d => 0x8a92, 0x4e => 0x8a8f, 0x4f => 0x8a96, + 0x50 => 0x8c3d, 0x51 => 0x8c68, 0x52 => 0x8c69, 0x53 => 0x8cd5, + 0x54 => 0x8ccf, 0x55 => 0x8cd7, 0x56 => 0x8d96, 0x57 => 0x8e09, + 0x58 => 0x8e02, 0x59 => 0x8dff, 0x5a => 0x8e0d, 0x5b => 0x8dfd, + 0x5c => 0x8e0a, 0x5d => 0x8e03, 0x5e => 0x8e07, 0x5f => 0x8e06, + 0x60 => 0x8e05, 0x61 => 0x8dfe, 0x62 => 0x8e00, 0x63 => 0x8e04, + 0x64 => 0x8f10, 0x65 => 0x8f11, 0x66 => 0x8f0e, 0x67 => 0x8f0d, + 0x68 => 0x9123, 0x69 => 0x911c, 0x6a => 0x9120, 0x6b => 0x9122, + 0x6c => 0x911f, 0x6d => 0x911d, 0x6e => 0x911a, 0x6f => 0x9124, + 0x70 => 0x9121, 0x71 => 0x911b, 0x72 => 0x917a, 0x73 => 0x9172, + 0x74 => 0x9179, 0x75 => 0x9173, 0x76 => 0x92a5, 0x77 => 0x92a4, + 0x78 => 0x9276, 0x79 => 0x929b, 0x7a => 0x927a, 0x7b => 0x92a0, + 0x7c => 0x9294, 0x7d => 0x92aa, 0x7e => 0x928d, 0xa1 => 0x92a6, + 0xa2 => 0x929a, 0xa3 => 0x92ab, 0xa4 => 0x9279, 0xa5 => 0x9297, + 0xa6 => 0x927f, 0xa7 => 0x92a3, 0xa8 => 0x92ee, 0xa9 => 0x928e, + 0xaa => 0x9282, 0xab => 0x9295, 0xac => 0x92a2, 0xad => 0x927d, + 0xae => 0x9288, 0xaf => 0x92a1, 0xb0 => 0x928a, 0xb1 => 0x9286, + 0xb2 => 0x928c, 0xb3 => 0x9299, 0xb4 => 0x92a7, 0xb5 => 0x927e, + 0xb6 => 0x9287, 0xb7 => 0x92a9, 0xb8 => 0x929d, 0xb9 => 0x928b, + 0xba => 0x922d, 0xbb => 0x969e, 0xbc => 0x96a1, 0xbd => 0x96ff, + 0xbe => 0x9758, 0xbf => 0x977d, 0xc0 => 0x977a, 0xc1 => 0x977e, + 0xc2 => 0x9783, 0xc3 => 0x9780, 0xc4 => 0x9782, 0xc5 => 0x977b, + 0xc6 => 0x9784, 0xc7 => 0x9781, 0xc8 => 0x977f, 0xc9 => 0x97ce, + 0xca => 0x97cd, 0xcb => 0x9816, 0xcc => 0x98ad, 0xcd => 0x98ae, + 0xce => 0x9902, 0xcf => 0x9900, 0xd0 => 0x9907, 0xd1 => 0x999d, + 0xd2 => 0x999c, 0xd3 => 0x99c3, 0xd4 => 0x99b9, 0xd5 => 0x99bb, + 0xd6 => 0x99ba, 0xd7 => 0x99c2, 0xd8 => 0x99bd, 0xd9 => 0x99c7, + 0xda => 0x9ab1, 0xdb => 0x9ae3, 0xdc => 0x9ae7, 0xdd => 0x9b3e, + 0xde => 0x9b3f, 0xdf => 0x9b60, 0xe0 => 0x9b61, 0xe1 => 0x9b5f, + 0xe2 => 0x9cf1, 0xe3 => 0x9cf2, 0xe4 => 0x9cf5, 0xe5 => 0x9ea7, + 0xe6 => 0x50ff, 0xe7 => 0x5103, 0xe8 => 0x5130, 0xe9 => 0x50f8, + 0xea => 0x5106, 0xeb => 0x5107, 0xec => 0x50f6, 0xed => 0x50fe, + 0xee => 0x510b, 0xef => 0x510c, 0xf0 => 0x50fd, 0xf1 => 0x510a, + 0xf2 => 0x528b, 0xf3 => 0x528c, 0xf4 => 0x52f1, 0xf5 => 0x52ef, + 0xf6 => 0x5648, 0xf7 => 0x5642, 0xf8 => 0x564c, 0xf9 => 0x5635, + 0xfa => 0x5641, 0xfb => 0x564a, 0xfc => 0x5649, 0xfd => 0x5646, + 0xfe => 0x5658, + }, + 0xe5 => { + 0x40 => 0x565a, 0x41 => 0x5640, 0x42 => 0x5633, 0x43 => 0x563d, + 0x44 => 0x562c, 0x45 => 0x563e, 0x46 => 0x5638, 0x47 => 0x562a, + 0x48 => 0x563a, 0x49 => 0x571a, 0x4a => 0x58ab, 0x4b => 0x589d, + 0x4c => 0x58b1, 0x4d => 0x58a0, 0x4e => 0x58a3, 0x4f => 0x58af, + 0x50 => 0x58ac, 0x51 => 0x58a5, 0x52 => 0x58a1, 0x53 => 0x58ff, + 0x54 => 0x5aff, 0x55 => 0x5af4, 0x56 => 0x5afd, 0x57 => 0x5af7, + 0x58 => 0x5af6, 0x59 => 0x5b03, 0x5a => 0x5af8, 0x5b => 0x5b02, + 0x5c => 0x5af9, 0x5d => 0x5b01, 0x5e => 0x5b07, 0x5f => 0x5b05, + 0x60 => 0x5b0f, 0x61 => 0x5c67, 0x62 => 0x5d99, 0x63 => 0x5d97, + 0x64 => 0x5d9f, 0x65 => 0x5d92, 0x66 => 0x5da2, 0x67 => 0x5d93, + 0x68 => 0x5d95, 0x69 => 0x5da0, 0x6a => 0x5d9c, 0x6b => 0x5da1, + 0x6c => 0x5d9a, 0x6d => 0x5d9e, 0x6e => 0x5e69, 0x6f => 0x5e5d, + 0x70 => 0x5e60, 0x71 => 0x5e5c, 0x72 => 0x7df3, 0x73 => 0x5edb, + 0x74 => 0x5ede, 0x75 => 0x5ee1, 0x76 => 0x5f49, 0x77 => 0x5fb2, + 0x78 => 0x618b, 0x79 => 0x6183, 0x7a => 0x6179, 0x7b => 0x61b1, + 0x7c => 0x61b0, 0x7d => 0x61a2, 0x7e => 0x6189, 0xa1 => 0x619b, + 0xa2 => 0x6193, 0xa3 => 0x61af, 0xa4 => 0x61ad, 0xa5 => 0x619f, + 0xa6 => 0x6192, 0xa7 => 0x61aa, 0xa8 => 0x61a1, 0xa9 => 0x618d, + 0xaa => 0x6166, 0xab => 0x61b3, 0xac => 0x622d, 0xad => 0x646e, + 0xae => 0x6470, 0xaf => 0x6496, 0xb0 => 0x64a0, 0xb1 => 0x6485, + 0xb2 => 0x6497, 0xb3 => 0x649c, 0xb4 => 0x648f, 0xb5 => 0x648b, + 0xb6 => 0x648a, 0xb7 => 0x648c, 0xb8 => 0x64a3, 0xb9 => 0x649f, + 0xba => 0x6468, 0xbb => 0x64b1, 0xbc => 0x6498, 0xbd => 0x6576, + 0xbe => 0x657a, 0xbf => 0x6579, 0xc0 => 0x657b, 0xc1 => 0x65b2, + 0xc2 => 0x65b3, 0xc3 => 0x66b5, 0xc4 => 0x66b0, 0xc5 => 0x66a9, + 0xc6 => 0x66b2, 0xc7 => 0x66b7, 0xc8 => 0x66aa, 0xc9 => 0x66af, + 0xca => 0x6a00, 0xcb => 0x6a06, 0xcc => 0x6a17, 0xcd => 0x69e5, + 0xce => 0x69f8, 0xcf => 0x6a15, 0xd0 => 0x69f1, 0xd1 => 0x69e4, + 0xd2 => 0x6a20, 0xd3 => 0x69ff, 0xd4 => 0x69ec, 0xd5 => 0x69e2, + 0xd6 => 0x6a1b, 0xd7 => 0x6a1d, 0xd8 => 0x69fe, 0xd9 => 0x6a27, + 0xda => 0x69f2, 0xdb => 0x69ee, 0xdc => 0x6a14, 0xdd => 0x69f7, + 0xde => 0x69e7, 0xdf => 0x6a40, 0xe0 => 0x6a08, 0xe1 => 0x69e6, + 0xe2 => 0x69fb, 0xe3 => 0x6a0d, 0xe4 => 0x69fc, 0xe5 => 0x69eb, + 0xe6 => 0x6a09, 0xe7 => 0x6a04, 0xe8 => 0x6a18, 0xe9 => 0x6a25, + 0xea => 0x6a0f, 0xeb => 0x69f6, 0xec => 0x6a26, 0xed => 0x6a07, + 0xee => 0x69f4, 0xef => 0x6a16, 0xf0 => 0x6b51, 0xf1 => 0x6ba5, + 0xf2 => 0x6ba3, 0xf3 => 0x6ba2, 0xf4 => 0x6ba6, 0xf5 => 0x6c01, + 0xf6 => 0x6c00, 0xf7 => 0x6bff, 0xf8 => 0x6c02, 0xf9 => 0x6f41, + 0xfa => 0x6f26, 0xfb => 0x6f7e, 0xfc => 0x6f87, 0xfd => 0x6fc6, + 0xfe => 0x6f92, + }, + 0xe6 => { + 0x40 => 0x6f8d, 0x41 => 0x6f89, 0x42 => 0x6f8c, 0x43 => 0x6f62, + 0x44 => 0x6f4f, 0x45 => 0x6f85, 0x46 => 0x6f5a, 0x47 => 0x6f96, + 0x48 => 0x6f76, 0x49 => 0x6f6c, 0x4a => 0x6f82, 0x4b => 0x6f55, + 0x4c => 0x6f72, 0x4d => 0x6f52, 0x4e => 0x6f50, 0x4f => 0x6f57, + 0x50 => 0x6f94, 0x51 => 0x6f93, 0x52 => 0x6f5d, 0x53 => 0x6f00, + 0x54 => 0x6f61, 0x55 => 0x6f6b, 0x56 => 0x6f7d, 0x57 => 0x6f67, + 0x58 => 0x6f90, 0x59 => 0x6f53, 0x5a => 0x6f8b, 0x5b => 0x6f69, + 0x5c => 0x6f7f, 0x5d => 0x6f95, 0x5e => 0x6f63, 0x5f => 0x6f77, + 0x60 => 0x6f6a, 0x61 => 0x6f7b, 0x62 => 0x71b2, 0x63 => 0x71af, + 0x64 => 0x719b, 0x65 => 0x71b0, 0x66 => 0x71a0, 0x67 => 0x719a, + 0x68 => 0x71a9, 0x69 => 0x71b5, 0x6a => 0x719d, 0x6b => 0x71a5, + 0x6c => 0x719e, 0x6d => 0x71a4, 0x6e => 0x71a1, 0x6f => 0x71aa, + 0x70 => 0x719c, 0x71 => 0x71a7, 0x72 => 0x71b3, 0x73 => 0x7298, + 0x74 => 0x729a, 0x75 => 0x7358, 0x76 => 0x7352, 0x77 => 0x735e, + 0x78 => 0x735f, 0x79 => 0x7360, 0x7a => 0x735d, 0x7b => 0x735b, + 0x7c => 0x7361, 0x7d => 0x735a, 0x7e => 0x7359, 0xa1 => 0x7362, + 0xa2 => 0x7487, 0xa3 => 0x7489, 0xa4 => 0x748a, 0xa5 => 0x7486, + 0xa6 => 0x7481, 0xa7 => 0x747d, 0xa8 => 0x7485, 0xa9 => 0x7488, + 0xaa => 0x747c, 0xab => 0x7479, 0xac => 0x7508, 0xad => 0x7507, + 0xae => 0x757e, 0xaf => 0x7625, 0xb0 => 0x761e, 0xb1 => 0x7619, + 0xb2 => 0x761d, 0xb3 => 0x761c, 0xb4 => 0x7623, 0xb5 => 0x761a, + 0xb6 => 0x7628, 0xb7 => 0x761b, 0xb8 => 0x769c, 0xb9 => 0x769d, + 0xba => 0x769e, 0xbb => 0x769b, 0xbc => 0x778d, 0xbd => 0x778f, + 0xbe => 0x7789, 0xbf => 0x7788, 0xc0 => 0x78cd, 0xc1 => 0x78bb, + 0xc2 => 0x78cf, 0xc3 => 0x78cc, 0xc4 => 0x78d1, 0xc5 => 0x78ce, + 0xc6 => 0x78d4, 0xc7 => 0x78c8, 0xc8 => 0x78c3, 0xc9 => 0x78c4, + 0xca => 0x78c9, 0xcb => 0x799a, 0xcc => 0x79a1, 0xcd => 0x79a0, + 0xce => 0x799c, 0xcf => 0x79a2, 0xd0 => 0x799b, 0xd1 => 0x6b76, + 0xd2 => 0x7a39, 0xd3 => 0x7ab2, 0xd4 => 0x7ab4, 0xd5 => 0x7ab3, + 0xd6 => 0x7bb7, 0xd7 => 0x7bcb, 0xd8 => 0x7bbe, 0xd9 => 0x7bac, + 0xda => 0x7bce, 0xdb => 0x7baf, 0xdc => 0x7bb9, 0xdd => 0x7bca, + 0xde => 0x7bb5, 0xdf => 0x7cc5, 0xe0 => 0x7cc8, 0xe1 => 0x7ccc, + 0xe2 => 0x7ccb, 0xe3 => 0x7df7, 0xe4 => 0x7ddb, 0xe5 => 0x7dea, + 0xe6 => 0x7de7, 0xe7 => 0x7dd7, 0xe8 => 0x7de1, 0xe9 => 0x7e03, + 0xea => 0x7dfa, 0xeb => 0x7de6, 0xec => 0x7df6, 0xed => 0x7df1, + 0xee => 0x7df0, 0xef => 0x7dee, 0xf0 => 0x7ddf, 0xf1 => 0x7f76, + 0xf2 => 0x7fac, 0xf3 => 0x7fb0, 0xf4 => 0x7fad, 0xf5 => 0x7fed, + 0xf6 => 0x7feb, 0xf7 => 0x7fea, 0xf8 => 0x7fec, 0xf9 => 0x7fe6, + 0xfa => 0x7fe8, 0xfb => 0x8064, 0xfc => 0x8067, 0xfd => 0x81a3, + 0xfe => 0x819f, + }, + 0xe7 => { + 0x40 => 0x819e, 0x41 => 0x8195, 0x42 => 0x81a2, 0x43 => 0x8199, + 0x44 => 0x8197, 0x45 => 0x8216, 0x46 => 0x824f, 0x47 => 0x8253, + 0x48 => 0x8252, 0x49 => 0x8250, 0x4a => 0x824e, 0x4b => 0x8251, + 0x4c => 0x8524, 0x4d => 0x853b, 0x4e => 0x850f, 0x4f => 0x8500, + 0x50 => 0x8529, 0x51 => 0x850e, 0x52 => 0x8509, 0x53 => 0x850d, + 0x54 => 0x851f, 0x55 => 0x850a, 0x56 => 0x8527, 0x57 => 0x851c, + 0x58 => 0x84fb, 0x59 => 0x852b, 0x5a => 0x84fa, 0x5b => 0x8508, + 0x5c => 0x850c, 0x5d => 0x84f4, 0x5e => 0x852a, 0x5f => 0x84f2, + 0x60 => 0x8515, 0x61 => 0x84f7, 0x62 => 0x84eb, 0x63 => 0x84f3, + 0x64 => 0x84fc, 0x65 => 0x8512, 0x66 => 0x84ea, 0x67 => 0x84e9, + 0x68 => 0x8516, 0x69 => 0x84fe, 0x6a => 0x8528, 0x6b => 0x851d, + 0x6c => 0x852e, 0x6d => 0x8502, 0x6e => 0x84fd, 0x6f => 0x851e, + 0x70 => 0x84f6, 0x71 => 0x8531, 0x72 => 0x8526, 0x73 => 0x84e7, + 0x74 => 0x84e8, 0x75 => 0x84f0, 0x76 => 0x84ef, 0x77 => 0x84f9, + 0x78 => 0x8518, 0x79 => 0x8520, 0x7a => 0x8530, 0x7b => 0x850b, + 0x7c => 0x8519, 0x7d => 0x852f, 0x7e => 0x8662, 0xa1 => 0x8756, + 0xa2 => 0x8763, 0xa3 => 0x8764, 0xa4 => 0x8777, 0xa5 => 0x87e1, + 0xa6 => 0x8773, 0xa7 => 0x8758, 0xa8 => 0x8754, 0xa9 => 0x875b, + 0xaa => 0x8752, 0xab => 0x8761, 0xac => 0x875a, 0xad => 0x8751, + 0xae => 0x875e, 0xaf => 0x876d, 0xb0 => 0x876a, 0xb1 => 0x8750, + 0xb2 => 0x874e, 0xb3 => 0x875f, 0xb4 => 0x875d, 0xb5 => 0x876f, + 0xb6 => 0x876c, 0xb7 => 0x877a, 0xb8 => 0x876e, 0xb9 => 0x875c, + 0xba => 0x8765, 0xbb => 0x874f, 0xbc => 0x877b, 0xbd => 0x8775, + 0xbe => 0x8762, 0xbf => 0x8767, 0xc0 => 0x8769, 0xc1 => 0x885a, + 0xc2 => 0x8905, 0xc3 => 0x890c, 0xc4 => 0x8914, 0xc5 => 0x890b, + 0xc6 => 0x8917, 0xc7 => 0x8918, 0xc8 => 0x8919, 0xc9 => 0x8906, + 0xca => 0x8916, 0xcb => 0x8911, 0xcc => 0x890e, 0xcd => 0x8909, + 0xce => 0x89a2, 0xcf => 0x89a4, 0xd0 => 0x89a3, 0xd1 => 0x89ed, + 0xd2 => 0x89f0, 0xd3 => 0x89ec, 0xd4 => 0x8acf, 0xd5 => 0x8ac6, + 0xd6 => 0x8ab8, 0xd7 => 0x8ad3, 0xd8 => 0x8ad1, 0xd9 => 0x8ad4, + 0xda => 0x8ad5, 0xdb => 0x8abb, 0xdc => 0x8ad7, 0xdd => 0x8abe, + 0xde => 0x8ac0, 0xdf => 0x8ac5, 0xe0 => 0x8ad8, 0xe1 => 0x8ac3, + 0xe2 => 0x8aba, 0xe3 => 0x8abd, 0xe4 => 0x8ad9, 0xe5 => 0x8c3e, + 0xe6 => 0x8c4d, 0xe7 => 0x8c8f, 0xe8 => 0x8ce5, 0xe9 => 0x8cdf, + 0xea => 0x8cd9, 0xeb => 0x8ce8, 0xec => 0x8cda, 0xed => 0x8cdd, + 0xee => 0x8ce7, 0xef => 0x8da0, 0xf0 => 0x8d9c, 0xf1 => 0x8da1, + 0xf2 => 0x8d9b, 0xf3 => 0x8e20, 0xf4 => 0x8e23, 0xf5 => 0x8e25, + 0xf6 => 0x8e24, 0xf7 => 0x8e2e, 0xf8 => 0x8e15, 0xf9 => 0x8e1b, + 0xfa => 0x8e16, 0xfb => 0x8e11, 0xfc => 0x8e19, 0xfd => 0x8e26, + 0xfe => 0x8e27, + }, + 0xe8 => { + 0x40 => 0x8e14, 0x41 => 0x8e12, 0x42 => 0x8e18, 0x43 => 0x8e13, + 0x44 => 0x8e1c, 0x45 => 0x8e17, 0x46 => 0x8e1a, 0x47 => 0x8f2c, + 0x48 => 0x8f24, 0x49 => 0x8f18, 0x4a => 0x8f1a, 0x4b => 0x8f20, + 0x4c => 0x8f23, 0x4d => 0x8f16, 0x4e => 0x8f17, 0x4f => 0x9073, + 0x50 => 0x9070, 0x51 => 0x906f, 0x52 => 0x9067, 0x53 => 0x906b, + 0x54 => 0x912f, 0x55 => 0x912b, 0x56 => 0x9129, 0x57 => 0x912a, + 0x58 => 0x9132, 0x59 => 0x9126, 0x5a => 0x912e, 0x5b => 0x9185, + 0x5c => 0x9186, 0x5d => 0x918a, 0x5e => 0x9181, 0x5f => 0x9182, + 0x60 => 0x9184, 0x61 => 0x9180, 0x62 => 0x92d0, 0x63 => 0x92c3, + 0x64 => 0x92c4, 0x65 => 0x92c0, 0x66 => 0x92d9, 0x67 => 0x92b6, + 0x68 => 0x92cf, 0x69 => 0x92f1, 0x6a => 0x92df, 0x6b => 0x92d8, + 0x6c => 0x92e9, 0x6d => 0x92d7, 0x6e => 0x92dd, 0x6f => 0x92cc, + 0x70 => 0x92ef, 0x71 => 0x92c2, 0x72 => 0x92e8, 0x73 => 0x92ca, + 0x74 => 0x92c8, 0x75 => 0x92ce, 0x76 => 0x92e6, 0x77 => 0x92cd, + 0x78 => 0x92d5, 0x79 => 0x92c9, 0x7a => 0x92e0, 0x7b => 0x92de, + 0x7c => 0x92e7, 0x7d => 0x92d1, 0x7e => 0x92d3, 0xa1 => 0x92b5, + 0xa2 => 0x92e1, 0xa3 => 0x92c6, 0xa4 => 0x92b4, 0xa5 => 0x957c, + 0xa6 => 0x95ac, 0xa7 => 0x95ab, 0xa8 => 0x95ae, 0xa9 => 0x95b0, + 0xaa => 0x96a4, 0xab => 0x96a2, 0xac => 0x96d3, 0xad => 0x9705, + 0xae => 0x9708, 0xaf => 0x9702, 0xb0 => 0x975a, 0xb1 => 0x978a, + 0xb2 => 0x978e, 0xb3 => 0x9788, 0xb4 => 0x97d0, 0xb5 => 0x97cf, + 0xb6 => 0x981e, 0xb7 => 0x981d, 0xb8 => 0x9826, 0xb9 => 0x9829, + 0xba => 0x9828, 0xbb => 0x9820, 0xbc => 0x981b, 0xbd => 0x9827, + 0xbe => 0x98b2, 0xbf => 0x9908, 0xc0 => 0x98fa, 0xc1 => 0x9911, + 0xc2 => 0x9914, 0xc3 => 0x9916, 0xc4 => 0x9917, 0xc5 => 0x9915, + 0xc6 => 0x99dc, 0xc7 => 0x99cd, 0xc8 => 0x99cf, 0xc9 => 0x99d3, + 0xca => 0x99d4, 0xcb => 0x99ce, 0xcc => 0x99c9, 0xcd => 0x99d6, + 0xce => 0x99d8, 0xcf => 0x99cb, 0xd0 => 0x99d7, 0xd1 => 0x99cc, + 0xd2 => 0x9ab3, 0xd3 => 0x9aec, 0xd4 => 0x9aeb, 0xd5 => 0x9af3, + 0xd6 => 0x9af2, 0xd7 => 0x9af1, 0xd8 => 0x9b46, 0xd9 => 0x9b43, + 0xda => 0x9b67, 0xdb => 0x9b74, 0xdc => 0x9b71, 0xdd => 0x9b66, + 0xde => 0x9b76, 0xdf => 0x9b75, 0xe0 => 0x9b70, 0xe1 => 0x9b68, + 0xe2 => 0x9b64, 0xe3 => 0x9b6c, 0xe4 => 0x9cfc, 0xe5 => 0x9cfa, + 0xe6 => 0x9cfd, 0xe7 => 0x9cff, 0xe8 => 0x9cf7, 0xe9 => 0x9d07, + 0xea => 0x9d00, 0xeb => 0x9cf9, 0xec => 0x9cfb, 0xed => 0x9d08, + 0xee => 0x9d05, 0xef => 0x9d04, 0xf0 => 0x9e83, 0xf1 => 0x9ed3, + 0xf2 => 0x9f0f, 0xf3 => 0x9f10, 0xf4 => 0x511c, 0xf5 => 0x5113, + 0xf6 => 0x5117, 0xf7 => 0x511a, 0xf8 => 0x5111, 0xf9 => 0x51de, + 0xfa => 0x5334, 0xfb => 0x53e1, 0xfc => 0x5670, 0xfd => 0x5660, + 0xfe => 0x566e, + }, + 0xe9 => { + 0x40 => 0x5673, 0x41 => 0x5666, 0x42 => 0x5663, 0x43 => 0x566d, + 0x44 => 0x5672, 0x45 => 0x565e, 0x46 => 0x5677, 0x47 => 0x571c, + 0x48 => 0x571b, 0x49 => 0x58c8, 0x4a => 0x58bd, 0x4b => 0x58c9, + 0x4c => 0x58bf, 0x4d => 0x58ba, 0x4e => 0x58c2, 0x4f => 0x58bc, + 0x50 => 0x58c6, 0x51 => 0x5b17, 0x52 => 0x5b19, 0x53 => 0x5b1b, + 0x54 => 0x5b21, 0x55 => 0x5b14, 0x56 => 0x5b13, 0x57 => 0x5b10, + 0x58 => 0x5b16, 0x59 => 0x5b28, 0x5a => 0x5b1a, 0x5b => 0x5b20, + 0x5c => 0x5b1e, 0x5d => 0x5bef, 0x5e => 0x5dac, 0x5f => 0x5db1, + 0x60 => 0x5da9, 0x61 => 0x5da7, 0x62 => 0x5db5, 0x63 => 0x5db0, + 0x64 => 0x5dae, 0x65 => 0x5daa, 0x66 => 0x5da8, 0x67 => 0x5db2, + 0x68 => 0x5dad, 0x69 => 0x5daf, 0x6a => 0x5db4, 0x6b => 0x5e67, + 0x6c => 0x5e68, 0x6d => 0x5e66, 0x6e => 0x5e6f, 0x6f => 0x5ee9, + 0x70 => 0x5ee7, 0x71 => 0x5ee6, 0x72 => 0x5ee8, 0x73 => 0x5ee5, + 0x74 => 0x5f4b, 0x75 => 0x5fbc, 0x76 => 0x619d, 0x77 => 0x61a8, + 0x78 => 0x6196, 0x79 => 0x61c5, 0x7a => 0x61b4, 0x7b => 0x61c6, + 0x7c => 0x61c1, 0x7d => 0x61cc, 0x7e => 0x61ba, 0xa1 => 0x61bf, + 0xa2 => 0x61b8, 0xa3 => 0x618c, 0xa4 => 0x64d7, 0xa5 => 0x64d6, + 0xa6 => 0x64d0, 0xa7 => 0x64cf, 0xa8 => 0x64c9, 0xa9 => 0x64bd, + 0xaa => 0x6489, 0xab => 0x64c3, 0xac => 0x64db, 0xad => 0x64f3, + 0xae => 0x64d9, 0xaf => 0x6533, 0xb0 => 0x657f, 0xb1 => 0x657c, + 0xb2 => 0x65a2, 0xb3 => 0x66c8, 0xb4 => 0x66be, 0xb5 => 0x66c0, + 0xb6 => 0x66ca, 0xb7 => 0x66cb, 0xb8 => 0x66cf, 0xb9 => 0x66bd, + 0xba => 0x66bb, 0xbb => 0x66ba, 0xbc => 0x66cc, 0xbd => 0x6723, + 0xbe => 0x6a34, 0xbf => 0x6a66, 0xc0 => 0x6a49, 0xc1 => 0x6a67, + 0xc2 => 0x6a32, 0xc3 => 0x6a68, 0xc4 => 0x6a3e, 0xc5 => 0x6a5d, + 0xc6 => 0x6a6d, 0xc7 => 0x6a76, 0xc8 => 0x6a5b, 0xc9 => 0x6a51, + 0xca => 0x6a28, 0xcb => 0x6a5a, 0xcc => 0x6a3b, 0xcd => 0x6a3f, + 0xce => 0x6a41, 0xcf => 0x6a6a, 0xd0 => 0x6a64, 0xd1 => 0x6a50, + 0xd2 => 0x6a4f, 0xd3 => 0x6a54, 0xd4 => 0x6a6f, 0xd5 => 0x6a69, + 0xd6 => 0x6a60, 0xd7 => 0x6a3c, 0xd8 => 0x6a5e, 0xd9 => 0x6a56, + 0xda => 0x6a55, 0xdb => 0x6a4d, 0xdc => 0x6a4e, 0xdd => 0x6a46, + 0xde => 0x6b55, 0xdf => 0x6b54, 0xe0 => 0x6b56, 0xe1 => 0x6ba7, + 0xe2 => 0x6baa, 0xe3 => 0x6bab, 0xe4 => 0x6bc8, 0xe5 => 0x6bc7, + 0xe6 => 0x6c04, 0xe7 => 0x6c03, 0xe8 => 0x6c06, 0xe9 => 0x6fad, + 0xea => 0x6fcb, 0xeb => 0x6fa3, 0xec => 0x6fc7, 0xed => 0x6fbc, + 0xee => 0x6fce, 0xef => 0x6fc8, 0xf0 => 0x6f5e, 0xf1 => 0x6fc4, + 0xf2 => 0x6fbd, 0xf3 => 0x6f9e, 0xf4 => 0x6fca, 0xf5 => 0x6fa8, + 0xf6 => 0x7004, 0xf7 => 0x6fa5, 0xf8 => 0x6fae, 0xf9 => 0x6fba, + 0xfa => 0x6fac, 0xfb => 0x6faa, 0xfc => 0x6fcf, 0xfd => 0x6fbf, + 0xfe => 0x6fb8, + }, + 0xea => { + 0x40 => 0x6fa2, 0x41 => 0x6fc9, 0x42 => 0x6fab, 0x43 => 0x6fcd, + 0x44 => 0x6faf, 0x45 => 0x6fb2, 0x46 => 0x6fb0, 0x47 => 0x71c5, + 0x48 => 0x71c2, 0x49 => 0x71bf, 0x4a => 0x71b8, 0x4b => 0x71d6, + 0x4c => 0x71c0, 0x4d => 0x71c1, 0x4e => 0x71cb, 0x4f => 0x71d4, + 0x50 => 0x71ca, 0x51 => 0x71c7, 0x52 => 0x71cf, 0x53 => 0x71bd, + 0x54 => 0x71d8, 0x55 => 0x71bc, 0x56 => 0x71c6, 0x57 => 0x71da, + 0x58 => 0x71db, 0x59 => 0x729d, 0x5a => 0x729e, 0x5b => 0x7369, + 0x5c => 0x7366, 0x5d => 0x7367, 0x5e => 0x736c, 0x5f => 0x7365, + 0x60 => 0x736b, 0x61 => 0x736a, 0x62 => 0x747f, 0x63 => 0x749a, + 0x64 => 0x74a0, 0x65 => 0x7494, 0x66 => 0x7492, 0x67 => 0x7495, + 0x68 => 0x74a1, 0x69 => 0x750b, 0x6a => 0x7580, 0x6b => 0x762f, + 0x6c => 0x762d, 0x6d => 0x7631, 0x6e => 0x763d, 0x6f => 0x7633, + 0x70 => 0x763c, 0x71 => 0x7635, 0x72 => 0x7632, 0x73 => 0x7630, + 0x74 => 0x76bb, 0x75 => 0x76e6, 0x76 => 0x779a, 0x77 => 0x779d, + 0x78 => 0x77a1, 0x79 => 0x779c, 0x7a => 0x779b, 0x7b => 0x77a2, + 0x7c => 0x77a3, 0x7d => 0x7795, 0x7e => 0x7799, 0xa1 => 0x7797, + 0xa2 => 0x78dd, 0xa3 => 0x78e9, 0xa4 => 0x78e5, 0xa5 => 0x78ea, + 0xa6 => 0x78de, 0xa7 => 0x78e3, 0xa8 => 0x78db, 0xa9 => 0x78e1, + 0xaa => 0x78e2, 0xab => 0x78ed, 0xac => 0x78df, 0xad => 0x78e0, + 0xae => 0x79a4, 0xaf => 0x7a44, 0xb0 => 0x7a48, 0xb1 => 0x7a47, + 0xb2 => 0x7ab6, 0xb3 => 0x7ab8, 0xb4 => 0x7ab5, 0xb5 => 0x7ab1, + 0xb6 => 0x7ab7, 0xb7 => 0x7bde, 0xb8 => 0x7be3, 0xb9 => 0x7be7, + 0xba => 0x7bdd, 0xbb => 0x7bd5, 0xbc => 0x7be5, 0xbd => 0x7bda, + 0xbe => 0x7be8, 0xbf => 0x7bf9, 0xc0 => 0x7bd4, 0xc1 => 0x7bea, + 0xc2 => 0x7be2, 0xc3 => 0x7bdc, 0xc4 => 0x7beb, 0xc5 => 0x7bd8, + 0xc6 => 0x7bdf, 0xc7 => 0x7cd2, 0xc8 => 0x7cd4, 0xc9 => 0x7cd7, + 0xca => 0x7cd0, 0xcb => 0x7cd1, 0xcc => 0x7e12, 0xcd => 0x7e21, + 0xce => 0x7e17, 0xcf => 0x7e0c, 0xd0 => 0x7e1f, 0xd1 => 0x7e20, + 0xd2 => 0x7e13, 0xd3 => 0x7e0e, 0xd4 => 0x7e1c, 0xd5 => 0x7e15, + 0xd6 => 0x7e1a, 0xd7 => 0x7e22, 0xd8 => 0x7e0b, 0xd9 => 0x7e0f, + 0xda => 0x7e16, 0xdb => 0x7e0d, 0xdc => 0x7e14, 0xdd => 0x7e25, + 0xde => 0x7e24, 0xdf => 0x7f43, 0xe0 => 0x7f7b, 0xe1 => 0x7f7c, + 0xe2 => 0x7f7a, 0xe3 => 0x7fb1, 0xe4 => 0x7fef, 0xe5 => 0x802a, + 0xe6 => 0x8029, 0xe7 => 0x806c, 0xe8 => 0x81b1, 0xe9 => 0x81a6, + 0xea => 0x81ae, 0xeb => 0x81b9, 0xec => 0x81b5, 0xed => 0x81ab, + 0xee => 0x81b0, 0xef => 0x81ac, 0xf0 => 0x81b4, 0xf1 => 0x81b2, + 0xf2 => 0x81b7, 0xf3 => 0x81a7, 0xf4 => 0x81f2, 0xf5 => 0x8255, + 0xf6 => 0x8256, 0xf7 => 0x8257, 0xf8 => 0x8556, 0xf9 => 0x8545, + 0xfa => 0x856b, 0xfb => 0x854d, 0xfc => 0x8553, 0xfd => 0x8561, + 0xfe => 0x8558, + }, + 0xeb => { + 0x40 => 0x8540, 0x41 => 0x8546, 0x42 => 0x8564, 0x43 => 0x8541, + 0x44 => 0x8562, 0x45 => 0x8544, 0x46 => 0x8551, 0x47 => 0x8547, + 0x48 => 0x8563, 0x49 => 0x853e, 0x4a => 0x855b, 0x4b => 0x8571, + 0x4c => 0x854e, 0x4d => 0x856e, 0x4e => 0x8575, 0x4f => 0x8555, + 0x50 => 0x8567, 0x51 => 0x8560, 0x52 => 0x858c, 0x53 => 0x8566, + 0x54 => 0x855d, 0x55 => 0x8554, 0x56 => 0x8565, 0x57 => 0x856c, + 0x58 => 0x8663, 0x59 => 0x8665, 0x5a => 0x8664, 0x5b => 0x879b, + 0x5c => 0x878f, 0x5d => 0x8797, 0x5e => 0x8793, 0x5f => 0x8792, + 0x60 => 0x8788, 0x61 => 0x8781, 0x62 => 0x8796, 0x63 => 0x8798, + 0x64 => 0x8779, 0x65 => 0x8787, 0x66 => 0x87a3, 0x67 => 0x8785, + 0x68 => 0x8790, 0x69 => 0x8791, 0x6a => 0x879d, 0x6b => 0x8784, + 0x6c => 0x8794, 0x6d => 0x879c, 0x6e => 0x879a, 0x6f => 0x8789, + 0x70 => 0x891e, 0x71 => 0x8926, 0x72 => 0x8930, 0x73 => 0x892d, + 0x74 => 0x892e, 0x75 => 0x8927, 0x76 => 0x8931, 0x77 => 0x8922, + 0x78 => 0x8929, 0x79 => 0x8923, 0x7a => 0x892f, 0x7b => 0x892c, + 0x7c => 0x891f, 0x7d => 0x89f1, 0x7e => 0x8ae0, 0xa1 => 0x8ae2, + 0xa2 => 0x8af2, 0xa3 => 0x8af4, 0xa4 => 0x8af5, 0xa5 => 0x8add, + 0xa6 => 0x8b14, 0xa7 => 0x8ae4, 0xa8 => 0x8adf, 0xa9 => 0x8af0, + 0xaa => 0x8ac8, 0xab => 0x8ade, 0xac => 0x8ae1, 0xad => 0x8ae8, + 0xae => 0x8aff, 0xaf => 0x8aef, 0xb0 => 0x8afb, 0xb1 => 0x8c91, + 0xb2 => 0x8c92, 0xb3 => 0x8c90, 0xb4 => 0x8cf5, 0xb5 => 0x8cee, + 0xb6 => 0x8cf1, 0xb7 => 0x8cf0, 0xb8 => 0x8cf3, 0xb9 => 0x8d6c, + 0xba => 0x8d6e, 0xbb => 0x8da5, 0xbc => 0x8da7, 0xbd => 0x8e33, + 0xbe => 0x8e3e, 0xbf => 0x8e38, 0xc0 => 0x8e40, 0xc1 => 0x8e45, + 0xc2 => 0x8e36, 0xc3 => 0x8e3c, 0xc4 => 0x8e3d, 0xc5 => 0x8e41, + 0xc6 => 0x8e30, 0xc7 => 0x8e3f, 0xc8 => 0x8ebd, 0xc9 => 0x8f36, + 0xca => 0x8f2e, 0xcb => 0x8f35, 0xcc => 0x8f32, 0xcd => 0x8f39, + 0xce => 0x8f37, 0xcf => 0x8f34, 0xd0 => 0x9076, 0xd1 => 0x9079, + 0xd2 => 0x907b, 0xd3 => 0x9086, 0xd4 => 0x90fa, 0xd5 => 0x9133, + 0xd6 => 0x9135, 0xd7 => 0x9136, 0xd8 => 0x9193, 0xd9 => 0x9190, + 0xda => 0x9191, 0xdb => 0x918d, 0xdc => 0x918f, 0xdd => 0x9327, + 0xde => 0x931e, 0xdf => 0x9308, 0xe0 => 0x931f, 0xe1 => 0x9306, + 0xe2 => 0x930f, 0xe3 => 0x937a, 0xe4 => 0x9338, 0xe5 => 0x933c, + 0xe6 => 0x931b, 0xe7 => 0x9323, 0xe8 => 0x9312, 0xe9 => 0x9301, + 0xea => 0x9346, 0xeb => 0x932d, 0xec => 0x930e, 0xed => 0x930d, + 0xee => 0x92cb, 0xef => 0x931d, 0xf0 => 0x92fa, 0xf1 => 0x9325, + 0xf2 => 0x9313, 0xf3 => 0x92f9, 0xf4 => 0x92f7, 0xf5 => 0x9334, + 0xf6 => 0x9302, 0xf7 => 0x9324, 0xf8 => 0x92ff, 0xf9 => 0x9329, + 0xfa => 0x9339, 0xfb => 0x9335, 0xfc => 0x932a, 0xfd => 0x9314, + 0xfe => 0x930c, + }, + 0xec => { + 0x40 => 0x930b, 0x41 => 0x92fe, 0x42 => 0x9309, 0x43 => 0x9300, + 0x44 => 0x92fb, 0x45 => 0x9316, 0x46 => 0x95bc, 0x47 => 0x95cd, + 0x48 => 0x95be, 0x49 => 0x95b9, 0x4a => 0x95ba, 0x4b => 0x95b6, + 0x4c => 0x95bf, 0x4d => 0x95b5, 0x4e => 0x95bd, 0x4f => 0x96a9, + 0x50 => 0x96d4, 0x51 => 0x970b, 0x52 => 0x9712, 0x53 => 0x9710, + 0x54 => 0x9799, 0x55 => 0x9797, 0x56 => 0x9794, 0x57 => 0x97f0, + 0x58 => 0x97f8, 0x59 => 0x9835, 0x5a => 0x982f, 0x5b => 0x9832, + 0x5c => 0x9924, 0x5d => 0x991f, 0x5e => 0x9927, 0x5f => 0x9929, + 0x60 => 0x999e, 0x61 => 0x99ee, 0x62 => 0x99ec, 0x63 => 0x99e5, + 0x64 => 0x99e4, 0x65 => 0x99f0, 0x66 => 0x99e3, 0x67 => 0x99ea, + 0x68 => 0x99e9, 0x69 => 0x99e7, 0x6a => 0x9ab9, 0x6b => 0x9abf, + 0x6c => 0x9ab4, 0x6d => 0x9abb, 0x6e => 0x9af6, 0x6f => 0x9afa, + 0x70 => 0x9af9, 0x71 => 0x9af7, 0x72 => 0x9b33, 0x73 => 0x9b80, + 0x74 => 0x9b85, 0x75 => 0x9b87, 0x76 => 0x9b7c, 0x77 => 0x9b7e, + 0x78 => 0x9b7b, 0x79 => 0x9b82, 0x7a => 0x9b93, 0x7b => 0x9b92, + 0x7c => 0x9b90, 0x7d => 0x9b7a, 0x7e => 0x9b95, 0xa1 => 0x9b7d, + 0xa2 => 0x9b88, 0xa3 => 0x9d25, 0xa4 => 0x9d17, 0xa5 => 0x9d20, + 0xa6 => 0x9d1e, 0xa7 => 0x9d14, 0xa8 => 0x9d29, 0xa9 => 0x9d1d, + 0xaa => 0x9d18, 0xab => 0x9d22, 0xac => 0x9d10, 0xad => 0x9d19, + 0xae => 0x9d1f, 0xaf => 0x9e88, 0xb0 => 0x9e86, 0xb1 => 0x9e87, + 0xb2 => 0x9eae, 0xb3 => 0x9ead, 0xb4 => 0x9ed5, 0xb5 => 0x9ed6, + 0xb6 => 0x9efa, 0xb7 => 0x9f12, 0xb8 => 0x9f3d, 0xb9 => 0x5126, + 0xba => 0x5125, 0xbb => 0x5122, 0xbc => 0x5124, 0xbd => 0x5120, + 0xbe => 0x5129, 0xbf => 0x52f4, 0xc0 => 0x5693, 0xc1 => 0x568c, + 0xc2 => 0x568d, 0xc3 => 0x5686, 0xc4 => 0x5684, 0xc5 => 0x5683, + 0xc6 => 0x567e, 0xc7 => 0x5682, 0xc8 => 0x567f, 0xc9 => 0x5681, + 0xca => 0x58d6, 0xcb => 0x58d4, 0xcc => 0x58cf, 0xcd => 0x58d2, + 0xce => 0x5b2d, 0xcf => 0x5b25, 0xd0 => 0x5b32, 0xd1 => 0x5b23, + 0xd2 => 0x5b2c, 0xd3 => 0x5b27, 0xd4 => 0x5b26, 0xd5 => 0x5b2f, + 0xd6 => 0x5b2e, 0xd7 => 0x5b7b, 0xd8 => 0x5bf1, 0xd9 => 0x5bf2, + 0xda => 0x5db7, 0xdb => 0x5e6c, 0xdc => 0x5e6a, 0xdd => 0x5fbe, + 0xde => 0x5fbb, 0xdf => 0x61c3, 0xe0 => 0x61b5, 0xe1 => 0x61bc, + 0xe2 => 0x61e7, 0xe3 => 0x61e0, 0xe4 => 0x61e5, 0xe5 => 0x61e4, + 0xe6 => 0x61e8, 0xe7 => 0x61de, 0xe8 => 0x64ef, 0xe9 => 0x64e9, + 0xea => 0x64e3, 0xeb => 0x64eb, 0xec => 0x64e4, 0xed => 0x64e8, + 0xee => 0x6581, 0xef => 0x6580, 0xf0 => 0x65b6, 0xf1 => 0x65da, + 0xf2 => 0x66d2, 0xf3 => 0x6a8d, 0xf4 => 0x6a96, 0xf5 => 0x6a81, + 0xf6 => 0x6aa5, 0xf7 => 0x6a89, 0xf8 => 0x6a9f, 0xf9 => 0x6a9b, + 0xfa => 0x6aa1, 0xfb => 0x6a9e, 0xfc => 0x6a87, 0xfd => 0x6a93, + 0xfe => 0x6a8e, + }, + 0xed => { + 0x40 => 0x6a95, 0x41 => 0x6a83, 0x42 => 0x6aa8, 0x43 => 0x6aa4, + 0x44 => 0x6a91, 0x45 => 0x6a7f, 0x46 => 0x6aa6, 0x47 => 0x6a9a, + 0x48 => 0x6a85, 0x49 => 0x6a8c, 0x4a => 0x6a92, 0x4b => 0x6b5b, + 0x4c => 0x6bad, 0x4d => 0x6c09, 0x4e => 0x6fcc, 0x4f => 0x6fa9, + 0x50 => 0x6ff4, 0x51 => 0x6fd4, 0x52 => 0x6fe3, 0x53 => 0x6fdc, + 0x54 => 0x6fed, 0x55 => 0x6fe7, 0x56 => 0x6fe6, 0x57 => 0x6fde, + 0x58 => 0x6ff2, 0x59 => 0x6fdd, 0x5a => 0x6fe2, 0x5b => 0x6fe8, + 0x5c => 0x71e1, 0x5d => 0x71f1, 0x5e => 0x71e8, 0x5f => 0x71f2, + 0x60 => 0x71e4, 0x61 => 0x71f0, 0x62 => 0x71e2, 0x63 => 0x7373, + 0x64 => 0x736e, 0x65 => 0x736f, 0x66 => 0x7497, 0x67 => 0x74b2, + 0x68 => 0x74ab, 0x69 => 0x7490, 0x6a => 0x74aa, 0x6b => 0x74ad, + 0x6c => 0x74b1, 0x6d => 0x74a5, 0x6e => 0x74af, 0x6f => 0x7510, + 0x70 => 0x7511, 0x71 => 0x7512, 0x72 => 0x750f, 0x73 => 0x7584, + 0x74 => 0x7643, 0x75 => 0x7648, 0x76 => 0x7649, 0x77 => 0x7647, + 0x78 => 0x76a4, 0x79 => 0x76e9, 0x7a => 0x77b5, 0x7b => 0x77ab, + 0x7c => 0x77b2, 0x7d => 0x77b7, 0x7e => 0x77b6, 0xa1 => 0x77b4, + 0xa2 => 0x77b1, 0xa3 => 0x77a8, 0xa4 => 0x77f0, 0xa5 => 0x78f3, + 0xa6 => 0x78fd, 0xa7 => 0x7902, 0xa8 => 0x78fb, 0xa9 => 0x78fc, + 0xaa => 0x78f2, 0xab => 0x7905, 0xac => 0x78f9, 0xad => 0x78fe, + 0xae => 0x7904, 0xaf => 0x79ab, 0xb0 => 0x79a8, 0xb1 => 0x7a5c, + 0xb2 => 0x7a5b, 0xb3 => 0x7a56, 0xb4 => 0x7a58, 0xb5 => 0x7a54, + 0xb6 => 0x7a5a, 0xb7 => 0x7abe, 0xb8 => 0x7ac0, 0xb9 => 0x7ac1, + 0xba => 0x7c05, 0xbb => 0x7c0f, 0xbc => 0x7bf2, 0xbd => 0x7c00, + 0xbe => 0x7bff, 0xbf => 0x7bfb, 0xc0 => 0x7c0e, 0xc1 => 0x7bf4, + 0xc2 => 0x7c0b, 0xc3 => 0x7bf3, 0xc4 => 0x7c02, 0xc5 => 0x7c09, + 0xc6 => 0x7c03, 0xc7 => 0x7c01, 0xc8 => 0x7bf8, 0xc9 => 0x7bfd, + 0xca => 0x7c06, 0xcb => 0x7bf0, 0xcc => 0x7bf1, 0xcd => 0x7c10, + 0xce => 0x7c0a, 0xcf => 0x7ce8, 0xd0 => 0x7e2d, 0xd1 => 0x7e3c, + 0xd2 => 0x7e42, 0xd3 => 0x7e33, 0xd4 => 0x9848, 0xd5 => 0x7e38, + 0xd6 => 0x7e2a, 0xd7 => 0x7e49, 0xd8 => 0x7e40, 0xd9 => 0x7e47, + 0xda => 0x7e29, 0xdb => 0x7e4c, 0xdc => 0x7e30, 0xdd => 0x7e3b, + 0xde => 0x7e36, 0xdf => 0x7e44, 0xe0 => 0x7e3a, 0xe1 => 0x7f45, + 0xe2 => 0x7f7f, 0xe3 => 0x7f7e, 0xe4 => 0x7f7d, 0xe5 => 0x7ff4, + 0xe6 => 0x7ff2, 0xe7 => 0x802c, 0xe8 => 0x81bb, 0xe9 => 0x81c4, + 0xea => 0x81cc, 0xeb => 0x81ca, 0xec => 0x81c5, 0xed => 0x81c7, + 0xee => 0x81bc, 0xef => 0x81e9, 0xf0 => 0x825b, 0xf1 => 0x825a, + 0xf2 => 0x825c, 0xf3 => 0x8583, 0xf4 => 0x8580, 0xf5 => 0x858f, + 0xf6 => 0x85a7, 0xf7 => 0x8595, 0xf8 => 0x85a0, 0xf9 => 0x858b, + 0xfa => 0x85a3, 0xfb => 0x857b, 0xfc => 0x85a4, 0xfd => 0x859a, + 0xfe => 0x859e, + }, + 0xee => { + 0x40 => 0x8577, 0x41 => 0x857c, 0x42 => 0x8589, 0x43 => 0x85a1, + 0x44 => 0x857a, 0x45 => 0x8578, 0x46 => 0x8557, 0x47 => 0x858e, + 0x48 => 0x8596, 0x49 => 0x8586, 0x4a => 0x858d, 0x4b => 0x8599, + 0x4c => 0x859d, 0x4d => 0x8581, 0x4e => 0x85a2, 0x4f => 0x8582, + 0x50 => 0x8588, 0x51 => 0x8585, 0x52 => 0x8579, 0x53 => 0x8576, + 0x54 => 0x8598, 0x55 => 0x8590, 0x56 => 0x859f, 0x57 => 0x8668, + 0x58 => 0x87be, 0x59 => 0x87aa, 0x5a => 0x87ad, 0x5b => 0x87c5, + 0x5c => 0x87b0, 0x5d => 0x87ac, 0x5e => 0x87b9, 0x5f => 0x87b5, + 0x60 => 0x87bc, 0x61 => 0x87ae, 0x62 => 0x87c9, 0x63 => 0x87c3, + 0x64 => 0x87c2, 0x65 => 0x87cc, 0x66 => 0x87b7, 0x67 => 0x87af, + 0x68 => 0x87c4, 0x69 => 0x87ca, 0x6a => 0x87b4, 0x6b => 0x87b6, + 0x6c => 0x87bf, 0x6d => 0x87b8, 0x6e => 0x87bd, 0x6f => 0x87de, + 0x70 => 0x87b2, 0x71 => 0x8935, 0x72 => 0x8933, 0x73 => 0x893c, + 0x74 => 0x893e, 0x75 => 0x8941, 0x76 => 0x8952, 0x77 => 0x8937, + 0x78 => 0x8942, 0x79 => 0x89ad, 0x7a => 0x89af, 0x7b => 0x89ae, + 0x7c => 0x89f2, 0x7d => 0x89f3, 0x7e => 0x8b1e, 0xa1 => 0x8b18, + 0xa2 => 0x8b16, 0xa3 => 0x8b11, 0xa4 => 0x8b05, 0xa5 => 0x8b0b, + 0xa6 => 0x8b22, 0xa7 => 0x8b0f, 0xa8 => 0x8b12, 0xa9 => 0x8b15, + 0xaa => 0x8b07, 0xab => 0x8b0d, 0xac => 0x8b08, 0xad => 0x8b06, + 0xae => 0x8b1c, 0xaf => 0x8b13, 0xb0 => 0x8b1a, 0xb1 => 0x8c4f, + 0xb2 => 0x8c70, 0xb3 => 0x8c72, 0xb4 => 0x8c71, 0xb5 => 0x8c6f, + 0xb6 => 0x8c95, 0xb7 => 0x8c94, 0xb8 => 0x8cf9, 0xb9 => 0x8d6f, + 0xba => 0x8e4e, 0xbb => 0x8e4d, 0xbc => 0x8e53, 0xbd => 0x8e50, + 0xbe => 0x8e4c, 0xbf => 0x8e47, 0xc0 => 0x8f43, 0xc1 => 0x8f40, + 0xc2 => 0x9085, 0xc3 => 0x907e, 0xc4 => 0x9138, 0xc5 => 0x919a, + 0xc6 => 0x91a2, 0xc7 => 0x919b, 0xc8 => 0x9199, 0xc9 => 0x919f, + 0xca => 0x91a1, 0xcb => 0x919d, 0xcc => 0x91a0, 0xcd => 0x93a1, + 0xce => 0x9383, 0xcf => 0x93af, 0xd0 => 0x9364, 0xd1 => 0x9356, + 0xd2 => 0x9347, 0xd3 => 0x937c, 0xd4 => 0x9358, 0xd5 => 0x935c, + 0xd6 => 0x9376, 0xd7 => 0x9349, 0xd8 => 0x9350, 0xd9 => 0x9351, + 0xda => 0x9360, 0xdb => 0x936d, 0xdc => 0x938f, 0xdd => 0x934c, + 0xde => 0x936a, 0xdf => 0x9379, 0xe0 => 0x9357, 0xe1 => 0x9355, + 0xe2 => 0x9352, 0xe3 => 0x934f, 0xe4 => 0x9371, 0xe5 => 0x9377, + 0xe6 => 0x937b, 0xe7 => 0x9361, 0xe8 => 0x935e, 0xe9 => 0x9363, + 0xea => 0x9367, 0xeb => 0x9380, 0xec => 0x934e, 0xed => 0x9359, + 0xee => 0x95c7, 0xef => 0x95c0, 0xf0 => 0x95c9, 0xf1 => 0x95c3, + 0xf2 => 0x95c5, 0xf3 => 0x95b7, 0xf4 => 0x96ae, 0xf5 => 0x96b0, + 0xf6 => 0x96ac, 0xf7 => 0x9720, 0xf8 => 0x971f, 0xf9 => 0x9718, + 0xfa => 0x971d, 0xfb => 0x9719, 0xfc => 0x979a, 0xfd => 0x97a1, + 0xfe => 0x979c, + }, + 0xef => { + 0x40 => 0x979e, 0x41 => 0x979d, 0x42 => 0x97d5, 0x43 => 0x97d4, + 0x44 => 0x97f1, 0x45 => 0x9841, 0x46 => 0x9844, 0x47 => 0x984a, + 0x48 => 0x9849, 0x49 => 0x9845, 0x4a => 0x9843, 0x4b => 0x9925, + 0x4c => 0x992b, 0x4d => 0x992c, 0x4e => 0x992a, 0x4f => 0x9933, + 0x50 => 0x9932, 0x51 => 0x992f, 0x52 => 0x992d, 0x53 => 0x9931, + 0x54 => 0x9930, 0x55 => 0x9998, 0x56 => 0x99a3, 0x57 => 0x99a1, + 0x58 => 0x9a02, 0x59 => 0x99fa, 0x5a => 0x99f4, 0x5b => 0x99f7, + 0x5c => 0x99f9, 0x5d => 0x99f8, 0x5e => 0x99f6, 0x5f => 0x99fb, + 0x60 => 0x99fd, 0x61 => 0x99fe, 0x62 => 0x99fc, 0x63 => 0x9a03, + 0x64 => 0x9abe, 0x65 => 0x9afe, 0x66 => 0x9afd, 0x67 => 0x9b01, + 0x68 => 0x9afc, 0x69 => 0x9b48, 0x6a => 0x9b9a, 0x6b => 0x9ba8, + 0x6c => 0x9b9e, 0x6d => 0x9b9b, 0x6e => 0x9ba6, 0x6f => 0x9ba1, + 0x70 => 0x9ba5, 0x71 => 0x9ba4, 0x72 => 0x9b86, 0x73 => 0x9ba2, + 0x74 => 0x9ba0, 0x75 => 0x9baf, 0x76 => 0x9d33, 0x77 => 0x9d41, + 0x78 => 0x9d67, 0x79 => 0x9d36, 0x7a => 0x9d2e, 0x7b => 0x9d2f, + 0x7c => 0x9d31, 0x7d => 0x9d38, 0x7e => 0x9d30, 0xa1 => 0x9d45, + 0xa2 => 0x9d42, 0xa3 => 0x9d43, 0xa4 => 0x9d3e, 0xa5 => 0x9d37, + 0xa6 => 0x9d40, 0xa7 => 0x9d3d, 0xa8 => 0x7ff5, 0xa9 => 0x9d2d, + 0xaa => 0x9e8a, 0xab => 0x9e89, 0xac => 0x9e8d, 0xad => 0x9eb0, + 0xae => 0x9ec8, 0xaf => 0x9eda, 0xb0 => 0x9efb, 0xb1 => 0x9eff, + 0xb2 => 0x9f24, 0xb3 => 0x9f23, 0xb4 => 0x9f22, 0xb5 => 0x9f54, + 0xb6 => 0x9fa0, 0xb7 => 0x5131, 0xb8 => 0x512d, 0xb9 => 0x512e, + 0xba => 0x5698, 0xbb => 0x569c, 0xbc => 0x5697, 0xbd => 0x569a, + 0xbe => 0x569d, 0xbf => 0x5699, 0xc0 => 0x5970, 0xc1 => 0x5b3c, + 0xc2 => 0x5c69, 0xc3 => 0x5c6a, 0xc4 => 0x5dc0, 0xc5 => 0x5e6d, + 0xc6 => 0x5e6e, 0xc7 => 0x61d8, 0xc8 => 0x61df, 0xc9 => 0x61ed, + 0xca => 0x61ee, 0xcb => 0x61f1, 0xcc => 0x61ea, 0xcd => 0x61f0, + 0xce => 0x61eb, 0xcf => 0x61d6, 0xd0 => 0x61e9, 0xd1 => 0x64ff, + 0xd2 => 0x6504, 0xd3 => 0x64fd, 0xd4 => 0x64f8, 0xd5 => 0x6501, + 0xd6 => 0x6503, 0xd7 => 0x64fc, 0xd8 => 0x6594, 0xd9 => 0x65db, + 0xda => 0x66da, 0xdb => 0x66db, 0xdc => 0x66d8, 0xdd => 0x6ac5, + 0xde => 0x6ab9, 0xdf => 0x6abd, 0xe0 => 0x6ae1, 0xe1 => 0x6ac6, + 0xe2 => 0x6aba, 0xe3 => 0x6ab6, 0xe4 => 0x6ab7, 0xe5 => 0x6ac7, + 0xe6 => 0x6ab4, 0xe7 => 0x6aad, 0xe8 => 0x6b5e, 0xe9 => 0x6bc9, + 0xea => 0x6c0b, 0xeb => 0x7007, 0xec => 0x700c, 0xed => 0x700d, + 0xee => 0x7001, 0xef => 0x7005, 0xf0 => 0x7014, 0xf1 => 0x700e, + 0xf2 => 0x6fff, 0xf3 => 0x7000, 0xf4 => 0x6ffb, 0xf5 => 0x7026, + 0xf6 => 0x6ffc, 0xf7 => 0x6ff7, 0xf8 => 0x700a, 0xf9 => 0x7201, + 0xfa => 0x71ff, 0xfb => 0x71f9, 0xfc => 0x7203, 0xfd => 0x71fd, + 0xfe => 0x7376, + }, + 0xf0 => { + 0x40 => 0x74b8, 0x41 => 0x74c0, 0x42 => 0x74b5, 0x43 => 0x74c1, + 0x44 => 0x74be, 0x45 => 0x74b6, 0x46 => 0x74bb, 0x47 => 0x74c2, + 0x48 => 0x7514, 0x49 => 0x7513, 0x4a => 0x765c, 0x4b => 0x7664, + 0x4c => 0x7659, 0x4d => 0x7650, 0x4e => 0x7653, 0x4f => 0x7657, + 0x50 => 0x765a, 0x51 => 0x76a6, 0x52 => 0x76bd, 0x53 => 0x76ec, + 0x54 => 0x77c2, 0x55 => 0x77ba, 0x56 => 0x78ff, 0x57 => 0x790c, + 0x58 => 0x7913, 0x59 => 0x7914, 0x5a => 0x7909, 0x5b => 0x7910, + 0x5c => 0x7912, 0x5d => 0x7911, 0x5e => 0x79ad, 0x5f => 0x79ac, + 0x60 => 0x7a5f, 0x61 => 0x7c1c, 0x62 => 0x7c29, 0x63 => 0x7c19, + 0x64 => 0x7c20, 0x65 => 0x7c1f, 0x66 => 0x7c2d, 0x67 => 0x7c1d, + 0x68 => 0x7c26, 0x69 => 0x7c28, 0x6a => 0x7c22, 0x6b => 0x7c25, + 0x6c => 0x7c30, 0x6d => 0x7e5c, 0x6e => 0x7e50, 0x6f => 0x7e56, + 0x70 => 0x7e63, 0x71 => 0x7e58, 0x72 => 0x7e62, 0x73 => 0x7e5f, + 0x74 => 0x7e51, 0x75 => 0x7e60, 0x76 => 0x7e57, 0x77 => 0x7e53, + 0x78 => 0x7fb5, 0x79 => 0x7fb3, 0x7a => 0x7ff7, 0x7b => 0x7ff8, + 0x7c => 0x8075, 0x7d => 0x81d1, 0x7e => 0x81d2, 0xa1 => 0x81d0, + 0xa2 => 0x825f, 0xa3 => 0x825e, 0xa4 => 0x85b4, 0xa5 => 0x85c6, + 0xa6 => 0x85c0, 0xa7 => 0x85c3, 0xa8 => 0x85c2, 0xa9 => 0x85b3, + 0xaa => 0x85b5, 0xab => 0x85bd, 0xac => 0x85c7, 0xad => 0x85c4, + 0xae => 0x85bf, 0xaf => 0x85cb, 0xb0 => 0x85ce, 0xb1 => 0x85c8, + 0xb2 => 0x85c5, 0xb3 => 0x85b1, 0xb4 => 0x85b6, 0xb5 => 0x85d2, + 0xb6 => 0x8624, 0xb7 => 0x85b8, 0xb8 => 0x85b7, 0xb9 => 0x85be, + 0xba => 0x8669, 0xbb => 0x87e7, 0xbc => 0x87e6, 0xbd => 0x87e2, + 0xbe => 0x87db, 0xbf => 0x87eb, 0xc0 => 0x87ea, 0xc1 => 0x87e5, + 0xc2 => 0x87df, 0xc3 => 0x87f3, 0xc4 => 0x87e4, 0xc5 => 0x87d4, + 0xc6 => 0x87dc, 0xc7 => 0x87d3, 0xc8 => 0x87ed, 0xc9 => 0x87d8, + 0xca => 0x87e3, 0xcb => 0x87a4, 0xcc => 0x87d7, 0xcd => 0x87d9, + 0xce => 0x8801, 0xcf => 0x87f4, 0xd0 => 0x87e8, 0xd1 => 0x87dd, + 0xd2 => 0x8953, 0xd3 => 0x894b, 0xd4 => 0x894f, 0xd5 => 0x894c, + 0xd6 => 0x8946, 0xd7 => 0x8950, 0xd8 => 0x8951, 0xd9 => 0x8949, + 0xda => 0x8b2a, 0xdb => 0x8b27, 0xdc => 0x8b23, 0xdd => 0x8b33, + 0xde => 0x8b30, 0xdf => 0x8b35, 0xe0 => 0x8b47, 0xe1 => 0x8b2f, + 0xe2 => 0x8b3c, 0xe3 => 0x8b3e, 0xe4 => 0x8b31, 0xe5 => 0x8b25, + 0xe6 => 0x8b37, 0xe7 => 0x8b26, 0xe8 => 0x8b36, 0xe9 => 0x8b2e, + 0xea => 0x8b24, 0xeb => 0x8b3b, 0xec => 0x8b3d, 0xed => 0x8b3a, + 0xee => 0x8c42, 0xef => 0x8c75, 0xf0 => 0x8c99, 0xf1 => 0x8c98, + 0xf2 => 0x8c97, 0xf3 => 0x8cfe, 0xf4 => 0x8d04, 0xf5 => 0x8d02, + 0xf6 => 0x8d00, 0xf7 => 0x8e5c, 0xf8 => 0x8e62, 0xf9 => 0x8e60, + 0xfa => 0x8e57, 0xfb => 0x8e56, 0xfc => 0x8e5e, 0xfd => 0x8e65, + 0xfe => 0x8e67, + }, + 0xf1 => { + 0x40 => 0x8e5b, 0x41 => 0x8e5a, 0x42 => 0x8e61, 0x43 => 0x8e5d, + 0x44 => 0x8e69, 0x45 => 0x8e54, 0x46 => 0x8f46, 0x47 => 0x8f47, + 0x48 => 0x8f48, 0x49 => 0x8f4b, 0x4a => 0x9128, 0x4b => 0x913a, + 0x4c => 0x913b, 0x4d => 0x913e, 0x4e => 0x91a8, 0x4f => 0x91a5, + 0x50 => 0x91a7, 0x51 => 0x91af, 0x52 => 0x91aa, 0x53 => 0x93b5, + 0x54 => 0x938c, 0x55 => 0x9392, 0x56 => 0x93b7, 0x57 => 0x939b, + 0x58 => 0x939d, 0x59 => 0x9389, 0x5a => 0x93a7, 0x5b => 0x938e, + 0x5c => 0x93aa, 0x5d => 0x939e, 0x5e => 0x93a6, 0x5f => 0x9395, + 0x60 => 0x9388, 0x61 => 0x9399, 0x62 => 0x939f, 0x63 => 0x938d, + 0x64 => 0x93b1, 0x65 => 0x9391, 0x66 => 0x93b2, 0x67 => 0x93a4, + 0x68 => 0x93a8, 0x69 => 0x93b4, 0x6a => 0x93a3, 0x6b => 0x93a5, + 0x6c => 0x95d2, 0x6d => 0x95d3, 0x6e => 0x95d1, 0x6f => 0x96b3, + 0x70 => 0x96d7, 0x71 => 0x96da, 0x72 => 0x5dc2, 0x73 => 0x96df, + 0x74 => 0x96d8, 0x75 => 0x96dd, 0x76 => 0x9723, 0x77 => 0x9722, + 0x78 => 0x9725, 0x79 => 0x97ac, 0x7a => 0x97ae, 0x7b => 0x97a8, + 0x7c => 0x97ab, 0x7d => 0x97a4, 0x7e => 0x97aa, 0xa1 => 0x97a2, + 0xa2 => 0x97a5, 0xa3 => 0x97d7, 0xa4 => 0x97d9, 0xa5 => 0x97d6, + 0xa6 => 0x97d8, 0xa7 => 0x97fa, 0xa8 => 0x9850, 0xa9 => 0x9851, + 0xaa => 0x9852, 0xab => 0x98b8, 0xac => 0x9941, 0xad => 0x993c, + 0xae => 0x993a, 0xaf => 0x9a0f, 0xb0 => 0x9a0b, 0xb1 => 0x9a09, + 0xb2 => 0x9a0d, 0xb3 => 0x9a04, 0xb4 => 0x9a11, 0xb5 => 0x9a0a, + 0xb6 => 0x9a05, 0xb7 => 0x9a07, 0xb8 => 0x9a06, 0xb9 => 0x9ac0, + 0xba => 0x9adc, 0xbb => 0x9b08, 0xbc => 0x9b04, 0xbd => 0x9b05, + 0xbe => 0x9b29, 0xbf => 0x9b35, 0xc0 => 0x9b4a, 0xc1 => 0x9b4c, + 0xc2 => 0x9b4b, 0xc3 => 0x9bc7, 0xc4 => 0x9bc6, 0xc5 => 0x9bc3, + 0xc6 => 0x9bbf, 0xc7 => 0x9bc1, 0xc8 => 0x9bb5, 0xc9 => 0x9bb8, + 0xca => 0x9bd3, 0xcb => 0x9bb6, 0xcc => 0x9bc4, 0xcd => 0x9bb9, + 0xce => 0x9bbd, 0xcf => 0x9d5c, 0xd0 => 0x9d53, 0xd1 => 0x9d4f, + 0xd2 => 0x9d4a, 0xd3 => 0x9d5b, 0xd4 => 0x9d4b, 0xd5 => 0x9d59, + 0xd6 => 0x9d56, 0xd7 => 0x9d4c, 0xd8 => 0x9d57, 0xd9 => 0x9d52, + 0xda => 0x9d54, 0xdb => 0x9d5f, 0xdc => 0x9d58, 0xdd => 0x9d5a, + 0xde => 0x9e8e, 0xdf => 0x9e8c, 0xe0 => 0x9edf, 0xe1 => 0x9f01, + 0xe2 => 0x9f00, 0xe3 => 0x9f16, 0xe4 => 0x9f25, 0xe5 => 0x9f2b, + 0xe6 => 0x9f2a, 0xe7 => 0x9f29, 0xe8 => 0x9f28, 0xe9 => 0x9f4c, + 0xea => 0x9f55, 0xeb => 0x5134, 0xec => 0x5135, 0xed => 0x5296, + 0xee => 0x52f7, 0xef => 0x53b4, 0xf0 => 0x56ab, 0xf1 => 0x56ad, + 0xf2 => 0x56a6, 0xf3 => 0x56a7, 0xf4 => 0x56aa, 0xf5 => 0x56ac, + 0xf6 => 0x58da, 0xf7 => 0x58dd, 0xf8 => 0x58db, 0xf9 => 0x5912, + 0xfa => 0x5b3d, 0xfb => 0x5b3e, 0xfc => 0x5b3f, 0xfd => 0x5dc3, + 0xfe => 0x5e70, + }, + 0xf2 => { + 0x40 => 0x5fbf, 0x41 => 0x61fb, 0x42 => 0x6507, 0x43 => 0x6510, + 0x44 => 0x650d, 0x45 => 0x6509, 0x46 => 0x650c, 0x47 => 0x650e, + 0x48 => 0x6584, 0x49 => 0x65de, 0x4a => 0x65dd, 0x4b => 0x66de, + 0x4c => 0x6ae7, 0x4d => 0x6ae0, 0x4e => 0x6acc, 0x4f => 0x6ad1, + 0x50 => 0x6ad9, 0x51 => 0x6acb, 0x52 => 0x6adf, 0x53 => 0x6adc, + 0x54 => 0x6ad0, 0x55 => 0x6aeb, 0x56 => 0x6acf, 0x57 => 0x6acd, + 0x58 => 0x6ade, 0x59 => 0x6b60, 0x5a => 0x6bb0, 0x5b => 0x6c0c, + 0x5c => 0x7019, 0x5d => 0x7027, 0x5e => 0x7020, 0x5f => 0x7016, + 0x60 => 0x702b, 0x61 => 0x7021, 0x62 => 0x7022, 0x63 => 0x7023, + 0x64 => 0x7029, 0x65 => 0x7017, 0x66 => 0x7024, 0x67 => 0x701c, + 0x68 => 0x702a, 0x69 => 0x720c, 0x6a => 0x720a, 0x6b => 0x7207, + 0x6c => 0x7202, 0x6d => 0x7205, 0x6e => 0x72a5, 0x6f => 0x72a6, + 0x70 => 0x72a4, 0x71 => 0x72a3, 0x72 => 0x72a1, 0x73 => 0x74cb, + 0x74 => 0x74c5, 0x75 => 0x74b7, 0x76 => 0x74c3, 0x77 => 0x7516, + 0x78 => 0x7660, 0x79 => 0x77c9, 0x7a => 0x77ca, 0x7b => 0x77c4, + 0x7c => 0x77f1, 0x7d => 0x791d, 0x7e => 0x791b, 0xa1 => 0x7921, + 0xa2 => 0x791c, 0xa3 => 0x7917, 0xa4 => 0x791e, 0xa5 => 0x79b0, + 0xa6 => 0x7a67, 0xa7 => 0x7a68, 0xa8 => 0x7c33, 0xa9 => 0x7c3c, + 0xaa => 0x7c39, 0xab => 0x7c2c, 0xac => 0x7c3b, 0xad => 0x7cec, + 0xae => 0x7cea, 0xaf => 0x7e76, 0xb0 => 0x7e75, 0xb1 => 0x7e78, + 0xb2 => 0x7e70, 0xb3 => 0x7e77, 0xb4 => 0x7e6f, 0xb5 => 0x7e7a, + 0xb6 => 0x7e72, 0xb7 => 0x7e74, 0xb8 => 0x7e68, 0xb9 => 0x7f4b, + 0xba => 0x7f4a, 0xbb => 0x7f83, 0xbc => 0x7f86, 0xbd => 0x7fb7, + 0xbe => 0x7ffd, 0xbf => 0x7ffe, 0xc0 => 0x8078, 0xc1 => 0x81d7, + 0xc2 => 0x81d5, 0xc3 => 0x8264, 0xc4 => 0x8261, 0xc5 => 0x8263, + 0xc6 => 0x85eb, 0xc7 => 0x85f1, 0xc8 => 0x85ed, 0xc9 => 0x85d9, + 0xca => 0x85e1, 0xcb => 0x85e8, 0xcc => 0x85da, 0xcd => 0x85d7, + 0xce => 0x85ec, 0xcf => 0x85f2, 0xd0 => 0x85f8, 0xd1 => 0x85d8, + 0xd2 => 0x85df, 0xd3 => 0x85e3, 0xd4 => 0x85dc, 0xd5 => 0x85d1, + 0xd6 => 0x85f0, 0xd7 => 0x85e6, 0xd8 => 0x85ef, 0xd9 => 0x85de, + 0xda => 0x85e2, 0xdb => 0x8800, 0xdc => 0x87fa, 0xdd => 0x8803, + 0xde => 0x87f6, 0xdf => 0x87f7, 0xe0 => 0x8809, 0xe1 => 0x880c, + 0xe2 => 0x880b, 0xe3 => 0x8806, 0xe4 => 0x87fc, 0xe5 => 0x8808, + 0xe6 => 0x87ff, 0xe7 => 0x880a, 0xe8 => 0x8802, 0xe9 => 0x8962, + 0xea => 0x895a, 0xeb => 0x895b, 0xec => 0x8957, 0xed => 0x8961, + 0xee => 0x895c, 0xef => 0x8958, 0xf0 => 0x895d, 0xf1 => 0x8959, + 0xf2 => 0x8988, 0xf3 => 0x89b7, 0xf4 => 0x89b6, 0xf5 => 0x89f6, + 0xf6 => 0x8b50, 0xf7 => 0x8b48, 0xf8 => 0x8b4a, 0xf9 => 0x8b40, + 0xfa => 0x8b53, 0xfb => 0x8b56, 0xfc => 0x8b54, 0xfd => 0x8b4b, + 0xfe => 0x8b55, + }, + 0xf3 => { + 0x40 => 0x8b51, 0x41 => 0x8b42, 0x42 => 0x8b52, 0x43 => 0x8b57, + 0x44 => 0x8c43, 0x45 => 0x8c77, 0x46 => 0x8c76, 0x47 => 0x8c9a, + 0x48 => 0x8d06, 0x49 => 0x8d07, 0x4a => 0x8d09, 0x4b => 0x8dac, + 0x4c => 0x8daa, 0x4d => 0x8dad, 0x4e => 0x8dab, 0x4f => 0x8e6d, + 0x50 => 0x8e78, 0x51 => 0x8e73, 0x52 => 0x8e6a, 0x53 => 0x8e6f, + 0x54 => 0x8e7b, 0x55 => 0x8ec2, 0x56 => 0x8f52, 0x57 => 0x8f51, + 0x58 => 0x8f4f, 0x59 => 0x8f50, 0x5a => 0x8f53, 0x5b => 0x8fb4, + 0x5c => 0x9140, 0x5d => 0x913f, 0x5e => 0x91b0, 0x5f => 0x91ad, + 0x60 => 0x93de, 0x61 => 0x93c7, 0x62 => 0x93cf, 0x63 => 0x93c2, + 0x64 => 0x93da, 0x65 => 0x93d0, 0x66 => 0x93f9, 0x67 => 0x93ec, + 0x68 => 0x93cc, 0x69 => 0x93d9, 0x6a => 0x93a9, 0x6b => 0x93e6, + 0x6c => 0x93ca, 0x6d => 0x93d4, 0x6e => 0x93ee, 0x6f => 0x93e3, + 0x70 => 0x93d5, 0x71 => 0x93c4, 0x72 => 0x93ce, 0x73 => 0x93c0, + 0x74 => 0x93d2, 0x75 => 0x93e7, 0x76 => 0x957d, 0x77 => 0x95da, + 0x78 => 0x95db, 0x79 => 0x96e1, 0x7a => 0x9729, 0x7b => 0x972b, + 0x7c => 0x972c, 0x7d => 0x9728, 0x7e => 0x9726, 0xa1 => 0x97b3, + 0xa2 => 0x97b7, 0xa3 => 0x97b6, 0xa4 => 0x97dd, 0xa5 => 0x97de, + 0xa6 => 0x97df, 0xa7 => 0x985c, 0xa8 => 0x9859, 0xa9 => 0x985d, + 0xaa => 0x9857, 0xab => 0x98bf, 0xac => 0x98bd, 0xad => 0x98bb, + 0xae => 0x98be, 0xaf => 0x9948, 0xb0 => 0x9947, 0xb1 => 0x9943, + 0xb2 => 0x99a6, 0xb3 => 0x99a7, 0xb4 => 0x9a1a, 0xb5 => 0x9a15, + 0xb6 => 0x9a25, 0xb7 => 0x9a1d, 0xb8 => 0x9a24, 0xb9 => 0x9a1b, + 0xba => 0x9a22, 0xbb => 0x9a20, 0xbc => 0x9a27, 0xbd => 0x9a23, + 0xbe => 0x9a1e, 0xbf => 0x9a1c, 0xc0 => 0x9a14, 0xc1 => 0x9ac2, + 0xc2 => 0x9b0b, 0xc3 => 0x9b0a, 0xc4 => 0x9b0e, 0xc5 => 0x9b0c, + 0xc6 => 0x9b37, 0xc7 => 0x9bea, 0xc8 => 0x9beb, 0xc9 => 0x9be0, + 0xca => 0x9bde, 0xcb => 0x9be4, 0xcc => 0x9be6, 0xcd => 0x9be2, + 0xce => 0x9bf0, 0xcf => 0x9bd4, 0xd0 => 0x9bd7, 0xd1 => 0x9bec, + 0xd2 => 0x9bdc, 0xd3 => 0x9bd9, 0xd4 => 0x9be5, 0xd5 => 0x9bd5, + 0xd6 => 0x9be1, 0xd7 => 0x9bda, 0xd8 => 0x9d77, 0xd9 => 0x9d81, + 0xda => 0x9d8a, 0xdb => 0x9d84, 0xdc => 0x9d88, 0xdd => 0x9d71, + 0xde => 0x9d80, 0xdf => 0x9d78, 0xe0 => 0x9d86, 0xe1 => 0x9d8b, + 0xe2 => 0x9d8c, 0xe3 => 0x9d7d, 0xe4 => 0x9d6b, 0xe5 => 0x9d74, + 0xe6 => 0x9d75, 0xe7 => 0x9d70, 0xe8 => 0x9d69, 0xe9 => 0x9d85, + 0xea => 0x9d73, 0xeb => 0x9d7b, 0xec => 0x9d82, 0xed => 0x9d6f, + 0xee => 0x9d79, 0xef => 0x9d7f, 0xf0 => 0x9d87, 0xf1 => 0x9d68, + 0xf2 => 0x9e94, 0xf3 => 0x9e91, 0xf4 => 0x9ec0, 0xf5 => 0x9efc, + 0xf6 => 0x9f2d, 0xf7 => 0x9f40, 0xf8 => 0x9f41, 0xf9 => 0x9f4d, + 0xfa => 0x9f56, 0xfb => 0x9f57, 0xfc => 0x9f58, 0xfd => 0x5337, + 0xfe => 0x56b2, + }, + 0xf4 => { + 0x40 => 0x56b5, 0x41 => 0x56b3, 0x42 => 0x58e3, 0x43 => 0x5b45, + 0x44 => 0x5dc6, 0x45 => 0x5dc7, 0x46 => 0x5eee, 0x47 => 0x5eef, + 0x48 => 0x5fc0, 0x49 => 0x5fc1, 0x4a => 0x61f9, 0x4b => 0x6517, + 0x4c => 0x6516, 0x4d => 0x6515, 0x4e => 0x6513, 0x4f => 0x65df, + 0x50 => 0x66e8, 0x51 => 0x66e3, 0x52 => 0x66e4, 0x53 => 0x6af3, + 0x54 => 0x6af0, 0x55 => 0x6aea, 0x56 => 0x6ae8, 0x57 => 0x6af9, + 0x58 => 0x6af1, 0x59 => 0x6aee, 0x5a => 0x6aef, 0x5b => 0x703c, + 0x5c => 0x7035, 0x5d => 0x702f, 0x5e => 0x7037, 0x5f => 0x7034, + 0x60 => 0x7031, 0x61 => 0x7042, 0x62 => 0x7038, 0x63 => 0x703f, + 0x64 => 0x703a, 0x65 => 0x7039, 0x66 => 0x7040, 0x67 => 0x703b, + 0x68 => 0x7033, 0x69 => 0x7041, 0x6a => 0x7213, 0x6b => 0x7214, + 0x6c => 0x72a8, 0x6d => 0x737d, 0x6e => 0x737c, 0x6f => 0x74ba, + 0x70 => 0x76ab, 0x71 => 0x76aa, 0x72 => 0x76be, 0x73 => 0x76ed, + 0x74 => 0x77cc, 0x75 => 0x77ce, 0x76 => 0x77cf, 0x77 => 0x77cd, + 0x78 => 0x77f2, 0x79 => 0x7925, 0x7a => 0x7923, 0x7b => 0x7927, + 0x7c => 0x7928, 0x7d => 0x7924, 0x7e => 0x7929, 0xa1 => 0x79b2, + 0xa2 => 0x7a6e, 0xa3 => 0x7a6c, 0xa4 => 0x7a6d, 0xa5 => 0x7af7, + 0xa6 => 0x7c49, 0xa7 => 0x7c48, 0xa8 => 0x7c4a, 0xa9 => 0x7c47, + 0xaa => 0x7c45, 0xab => 0x7cee, 0xac => 0x7e7b, 0xad => 0x7e7e, + 0xae => 0x7e81, 0xaf => 0x7e80, 0xb0 => 0x7fba, 0xb1 => 0x7fff, + 0xb2 => 0x8079, 0xb3 => 0x81db, 0xb4 => 0x81d9, 0xb5 => 0x820b, + 0xb6 => 0x8268, 0xb7 => 0x8269, 0xb8 => 0x8622, 0xb9 => 0x85ff, + 0xba => 0x8601, 0xbb => 0x85fe, 0xbc => 0x861b, 0xbd => 0x8600, + 0xbe => 0x85f6, 0xbf => 0x8604, 0xc0 => 0x8609, 0xc1 => 0x8605, + 0xc2 => 0x860c, 0xc3 => 0x85fd, 0xc4 => 0x8819, 0xc5 => 0x8810, + 0xc6 => 0x8811, 0xc7 => 0x8817, 0xc8 => 0x8813, 0xc9 => 0x8816, + 0xca => 0x8963, 0xcb => 0x8966, 0xcc => 0x89b9, 0xcd => 0x89f7, + 0xce => 0x8b60, 0xcf => 0x8b6a, 0xd0 => 0x8b5d, 0xd1 => 0x8b68, + 0xd2 => 0x8b63, 0xd3 => 0x8b65, 0xd4 => 0x8b67, 0xd5 => 0x8b6d, + 0xd6 => 0x8dae, 0xd7 => 0x8e86, 0xd8 => 0x8e88, 0xd9 => 0x8e84, + 0xda => 0x8f59, 0xdb => 0x8f56, 0xdc => 0x8f57, 0xdd => 0x8f55, + 0xde => 0x8f58, 0xdf => 0x8f5a, 0xe0 => 0x908d, 0xe1 => 0x9143, + 0xe2 => 0x9141, 0xe3 => 0x91b7, 0xe4 => 0x91b5, 0xe5 => 0x91b2, + 0xe6 => 0x91b3, 0xe7 => 0x940b, 0xe8 => 0x9413, 0xe9 => 0x93fb, + 0xea => 0x9420, 0xeb => 0x940f, 0xec => 0x9414, 0xed => 0x93fe, + 0xee => 0x9415, 0xef => 0x9410, 0xf0 => 0x9428, 0xf1 => 0x9419, + 0xf2 => 0x940d, 0xf3 => 0x93f5, 0xf4 => 0x9400, 0xf5 => 0x93f7, + 0xf6 => 0x9407, 0xf7 => 0x940e, 0xf8 => 0x9416, 0xf9 => 0x9412, + 0xfa => 0x93fa, 0xfb => 0x9409, 0xfc => 0x93f8, 0xfd => 0x940a, + 0xfe => 0x93ff, + }, + 0xf5 => { + 0x40 => 0x93fc, 0x41 => 0x940c, 0x42 => 0x93f6, 0x43 => 0x9411, + 0x44 => 0x9406, 0x45 => 0x95de, 0x46 => 0x95e0, 0x47 => 0x95df, + 0x48 => 0x972e, 0x49 => 0x972f, 0x4a => 0x97b9, 0x4b => 0x97bb, + 0x4c => 0x97fd, 0x4d => 0x97fe, 0x4e => 0x9860, 0x4f => 0x9862, + 0x50 => 0x9863, 0x51 => 0x985f, 0x52 => 0x98c1, 0x53 => 0x98c2, + 0x54 => 0x9950, 0x55 => 0x994e, 0x56 => 0x9959, 0x57 => 0x994c, + 0x58 => 0x994b, 0x59 => 0x9953, 0x5a => 0x9a32, 0x5b => 0x9a34, + 0x5c => 0x9a31, 0x5d => 0x9a2c, 0x5e => 0x9a2a, 0x5f => 0x9a36, + 0x60 => 0x9a29, 0x61 => 0x9a2e, 0x62 => 0x9a38, 0x63 => 0x9a2d, + 0x64 => 0x9ac7, 0x65 => 0x9aca, 0x66 => 0x9ac6, 0x67 => 0x9b10, + 0x68 => 0x9b12, 0x69 => 0x9b11, 0x6a => 0x9c0b, 0x6b => 0x9c08, + 0x6c => 0x9bf7, 0x6d => 0x9c05, 0x6e => 0x9c12, 0x6f => 0x9bf8, + 0x70 => 0x9c40, 0x71 => 0x9c07, 0x72 => 0x9c0e, 0x73 => 0x9c06, + 0x74 => 0x9c17, 0x75 => 0x9c14, 0x76 => 0x9c09, 0x77 => 0x9d9f, + 0x78 => 0x9d99, 0x79 => 0x9da4, 0x7a => 0x9d9d, 0x7b => 0x9d92, + 0x7c => 0x9d98, 0x7d => 0x9d90, 0x7e => 0x9d9b, 0xa1 => 0x9da0, + 0xa2 => 0x9d94, 0xa3 => 0x9d9c, 0xa4 => 0x9daa, 0xa5 => 0x9d97, + 0xa6 => 0x9da1, 0xa7 => 0x9d9a, 0xa8 => 0x9da2, 0xa9 => 0x9da8, + 0xaa => 0x9d9e, 0xab => 0x9da3, 0xac => 0x9dbf, 0xad => 0x9da9, + 0xae => 0x9d96, 0xaf => 0x9da6, 0xb0 => 0x9da7, 0xb1 => 0x9e99, + 0xb2 => 0x9e9b, 0xb3 => 0x9e9a, 0xb4 => 0x9ee5, 0xb5 => 0x9ee4, + 0xb6 => 0x9ee7, 0xb7 => 0x9ee6, 0xb8 => 0x9f30, 0xb9 => 0x9f2e, + 0xba => 0x9f5b, 0xbb => 0x9f60, 0xbc => 0x9f5e, 0xbd => 0x9f5d, + 0xbe => 0x9f59, 0xbf => 0x9f91, 0xc0 => 0x513a, 0xc1 => 0x5139, + 0xc2 => 0x5298, 0xc3 => 0x5297, 0xc4 => 0x56c3, 0xc5 => 0x56bd, + 0xc6 => 0x56be, 0xc7 => 0x5b48, 0xc8 => 0x5b47, 0xc9 => 0x5dcb, + 0xca => 0x5dcf, 0xcb => 0x5ef1, 0xcc => 0x61fd, 0xcd => 0x651b, + 0xce => 0x6b02, 0xcf => 0x6afc, 0xd0 => 0x6b03, 0xd1 => 0x6af8, + 0xd2 => 0x6b00, 0xd3 => 0x7043, 0xd4 => 0x7044, 0xd5 => 0x704a, + 0xd6 => 0x7048, 0xd7 => 0x7049, 0xd8 => 0x7045, 0xd9 => 0x7046, + 0xda => 0x721d, 0xdb => 0x721a, 0xdc => 0x7219, 0xdd => 0x737e, + 0xde => 0x7517, 0xdf => 0x766a, 0xe0 => 0x77d0, 0xe1 => 0x792d, + 0xe2 => 0x7931, 0xe3 => 0x792f, 0xe4 => 0x7c54, 0xe5 => 0x7c53, + 0xe6 => 0x7cf2, 0xe7 => 0x7e8a, 0xe8 => 0x7e87, 0xe9 => 0x7e88, + 0xea => 0x7e8b, 0xeb => 0x7e86, 0xec => 0x7e8d, 0xed => 0x7f4d, + 0xee => 0x7fbb, 0xef => 0x8030, 0xf0 => 0x81dd, 0xf1 => 0x8618, + 0xf2 => 0x862a, 0xf3 => 0x8626, 0xf4 => 0x861f, 0xf5 => 0x8623, + 0xf6 => 0x861c, 0xf7 => 0x8619, 0xf8 => 0x8627, 0xf9 => 0x862e, + 0xfa => 0x8621, 0xfb => 0x8620, 0xfc => 0x8629, 0xfd => 0x861e, + 0xfe => 0x8625, + }, + 0xf6 => { + 0x40 => 0x8829, 0x41 => 0x881d, 0x42 => 0x881b, 0x43 => 0x8820, + 0x44 => 0x8824, 0x45 => 0x881c, 0x46 => 0x882b, 0x47 => 0x884a, + 0x48 => 0x896d, 0x49 => 0x8969, 0x4a => 0x896e, 0x4b => 0x896b, + 0x4c => 0x89fa, 0x4d => 0x8b79, 0x4e => 0x8b78, 0x4f => 0x8b45, + 0x50 => 0x8b7a, 0x51 => 0x8b7b, 0x52 => 0x8d10, 0x53 => 0x8d14, + 0x54 => 0x8daf, 0x55 => 0x8e8e, 0x56 => 0x8e8c, 0x57 => 0x8f5e, + 0x58 => 0x8f5b, 0x59 => 0x8f5d, 0x5a => 0x9146, 0x5b => 0x9144, + 0x5c => 0x9145, 0x5d => 0x91b9, 0x5e => 0x943f, 0x5f => 0x943b, + 0x60 => 0x9436, 0x61 => 0x9429, 0x62 => 0x943d, 0x63 => 0x943c, + 0x64 => 0x9430, 0x65 => 0x9439, 0x66 => 0x942a, 0x67 => 0x9437, + 0x68 => 0x942c, 0x69 => 0x9440, 0x6a => 0x9431, 0x6b => 0x95e5, + 0x6c => 0x95e4, 0x6d => 0x95e3, 0x6e => 0x9735, 0x6f => 0x973a, + 0x70 => 0x97bf, 0x71 => 0x97e1, 0x72 => 0x9864, 0x73 => 0x98c9, + 0x74 => 0x98c6, 0x75 => 0x98c0, 0x76 => 0x9958, 0x77 => 0x9956, + 0x78 => 0x9a39, 0x79 => 0x9a3d, 0x7a => 0x9a46, 0x7b => 0x9a44, + 0x7c => 0x9a42, 0x7d => 0x9a41, 0x7e => 0x9a3a, 0xa1 => 0x9a3f, + 0xa2 => 0x9acd, 0xa3 => 0x9b15, 0xa4 => 0x9b17, 0xa5 => 0x9b18, + 0xa6 => 0x9b16, 0xa7 => 0x9b3a, 0xa8 => 0x9b52, 0xa9 => 0x9c2b, + 0xaa => 0x9c1d, 0xab => 0x9c1c, 0xac => 0x9c2c, 0xad => 0x9c23, + 0xae => 0x9c28, 0xaf => 0x9c29, 0xb0 => 0x9c24, 0xb1 => 0x9c21, + 0xb2 => 0x9db7, 0xb3 => 0x9db6, 0xb4 => 0x9dbc, 0xb5 => 0x9dc1, + 0xb6 => 0x9dc7, 0xb7 => 0x9dca, 0xb8 => 0x9dcf, 0xb9 => 0x9dbe, + 0xba => 0x9dc5, 0xbb => 0x9dc3, 0xbc => 0x9dbb, 0xbd => 0x9db5, + 0xbe => 0x9dce, 0xbf => 0x9db9, 0xc0 => 0x9dba, 0xc1 => 0x9dac, + 0xc2 => 0x9dc8, 0xc3 => 0x9db1, 0xc4 => 0x9dad, 0xc5 => 0x9dcc, + 0xc6 => 0x9db3, 0xc7 => 0x9dcd, 0xc8 => 0x9db2, 0xc9 => 0x9e7a, + 0xca => 0x9e9c, 0xcb => 0x9eeb, 0xcc => 0x9eee, 0xcd => 0x9eed, + 0xce => 0x9f1b, 0xcf => 0x9f18, 0xd0 => 0x9f1a, 0xd1 => 0x9f31, + 0xd2 => 0x9f4e, 0xd3 => 0x9f65, 0xd4 => 0x9f64, 0xd5 => 0x9f92, + 0xd6 => 0x4eb9, 0xd7 => 0x56c6, 0xd8 => 0x56c5, 0xd9 => 0x56cb, + 0xda => 0x5971, 0xdb => 0x5b4b, 0xdc => 0x5b4c, 0xdd => 0x5dd5, + 0xde => 0x5dd1, 0xdf => 0x5ef2, 0xe0 => 0x6521, 0xe1 => 0x6520, + 0xe2 => 0x6526, 0xe3 => 0x6522, 0xe4 => 0x6b0b, 0xe5 => 0x6b08, + 0xe6 => 0x6b09, 0xe7 => 0x6c0d, 0xe8 => 0x7055, 0xe9 => 0x7056, + 0xea => 0x7057, 0xeb => 0x7052, 0xec => 0x721e, 0xed => 0x721f, + 0xee => 0x72a9, 0xef => 0x737f, 0xf0 => 0x74d8, 0xf1 => 0x74d5, + 0xf2 => 0x74d9, 0xf3 => 0x74d7, 0xf4 => 0x766d, 0xf5 => 0x76ad, + 0xf6 => 0x7935, 0xf7 => 0x79b4, 0xf8 => 0x7a70, 0xf9 => 0x7a71, + 0xfa => 0x7c57, 0xfb => 0x7c5c, 0xfc => 0x7c59, 0xfd => 0x7c5b, + 0xfe => 0x7c5a, + }, + 0xf7 => { + 0x40 => 0x7cf4, 0x41 => 0x7cf1, 0x42 => 0x7e91, 0x43 => 0x7f4f, + 0x44 => 0x7f87, 0x45 => 0x81de, 0x46 => 0x826b, 0x47 => 0x8634, + 0x48 => 0x8635, 0x49 => 0x8633, 0x4a => 0x862c, 0x4b => 0x8632, + 0x4c => 0x8636, 0x4d => 0x882c, 0x4e => 0x8828, 0x4f => 0x8826, + 0x50 => 0x882a, 0x51 => 0x8825, 0x52 => 0x8971, 0x53 => 0x89bf, + 0x54 => 0x89be, 0x55 => 0x89fb, 0x56 => 0x8b7e, 0x57 => 0x8b84, + 0x58 => 0x8b82, 0x59 => 0x8b86, 0x5a => 0x8b85, 0x5b => 0x8b7f, + 0x5c => 0x8d15, 0x5d => 0x8e95, 0x5e => 0x8e94, 0x5f => 0x8e9a, + 0x60 => 0x8e92, 0x61 => 0x8e90, 0x62 => 0x8e96, 0x63 => 0x8e97, + 0x64 => 0x8f60, 0x65 => 0x8f62, 0x66 => 0x9147, 0x67 => 0x944c, + 0x68 => 0x9450, 0x69 => 0x944a, 0x6a => 0x944b, 0x6b => 0x944f, + 0x6c => 0x9447, 0x6d => 0x9445, 0x6e => 0x9448, 0x6f => 0x9449, + 0x70 => 0x9446, 0x71 => 0x973f, 0x72 => 0x97e3, 0x73 => 0x986a, + 0x74 => 0x9869, 0x75 => 0x98cb, 0x76 => 0x9954, 0x77 => 0x995b, + 0x78 => 0x9a4e, 0x79 => 0x9a53, 0x7a => 0x9a54, 0x7b => 0x9a4c, + 0x7c => 0x9a4f, 0x7d => 0x9a48, 0x7e => 0x9a4a, 0xa1 => 0x9a49, + 0xa2 => 0x9a52, 0xa3 => 0x9a50, 0xa4 => 0x9ad0, 0xa5 => 0x9b19, + 0xa6 => 0x9b2b, 0xa7 => 0x9b3b, 0xa8 => 0x9b56, 0xa9 => 0x9b55, + 0xaa => 0x9c46, 0xab => 0x9c48, 0xac => 0x9c3f, 0xad => 0x9c44, + 0xae => 0x9c39, 0xaf => 0x9c33, 0xb0 => 0x9c41, 0xb1 => 0x9c3c, + 0xb2 => 0x9c37, 0xb3 => 0x9c34, 0xb4 => 0x9c32, 0xb5 => 0x9c3d, + 0xb6 => 0x9c36, 0xb7 => 0x9ddb, 0xb8 => 0x9dd2, 0xb9 => 0x9dde, + 0xba => 0x9dda, 0xbb => 0x9dcb, 0xbc => 0x9dd0, 0xbd => 0x9ddc, + 0xbe => 0x9dd1, 0xbf => 0x9ddf, 0xc0 => 0x9de9, 0xc1 => 0x9dd9, + 0xc2 => 0x9dd8, 0xc3 => 0x9dd6, 0xc4 => 0x9df5, 0xc5 => 0x9dd5, + 0xc6 => 0x9ddd, 0xc7 => 0x9eb6, 0xc8 => 0x9ef0, 0xc9 => 0x9f35, + 0xca => 0x9f33, 0xcb => 0x9f32, 0xcc => 0x9f42, 0xcd => 0x9f6b, + 0xce => 0x9f95, 0xcf => 0x9fa2, 0xd0 => 0x513d, 0xd1 => 0x5299, + 0xd2 => 0x58e8, 0xd3 => 0x58e7, 0xd4 => 0x5972, 0xd5 => 0x5b4d, + 0xd6 => 0x5dd8, 0xd7 => 0x882f, 0xd8 => 0x5f4f, 0xd9 => 0x6201, + 0xda => 0x6203, 0xdb => 0x6204, 0xdc => 0x6529, 0xdd => 0x6525, + 0xde => 0x6596, 0xdf => 0x66eb, 0xe0 => 0x6b11, 0xe1 => 0x6b12, + 0xe2 => 0x6b0f, 0xe3 => 0x6bca, 0xe4 => 0x705b, 0xe5 => 0x705a, + 0xe6 => 0x7222, 0xe7 => 0x7382, 0xe8 => 0x7381, 0xe9 => 0x7383, + 0xea => 0x7670, 0xeb => 0x77d4, 0xec => 0x7c67, 0xed => 0x7c66, + 0xee => 0x7e95, 0xef => 0x826c, 0xf0 => 0x863a, 0xf1 => 0x8640, + 0xf2 => 0x8639, 0xf3 => 0x863c, 0xf4 => 0x8631, 0xf5 => 0x863b, + 0xf6 => 0x863e, 0xf7 => 0x8830, 0xf8 => 0x8832, 0xf9 => 0x882e, + 0xfa => 0x8833, 0xfb => 0x8976, 0xfc => 0x8974, 0xfd => 0x8973, + 0xfe => 0x89fe, + }, + 0xf8 => { + 0x40 => 0x8b8c, 0x41 => 0x8b8e, 0x42 => 0x8b8b, 0x43 => 0x8b88, + 0x44 => 0x8c45, 0x45 => 0x8d19, 0x46 => 0x8e98, 0x47 => 0x8f64, + 0x48 => 0x8f63, 0x49 => 0x91bc, 0x4a => 0x9462, 0x4b => 0x9455, + 0x4c => 0x945d, 0x4d => 0x9457, 0x4e => 0x945e, 0x4f => 0x97c4, + 0x50 => 0x97c5, 0x51 => 0x9800, 0x52 => 0x9a56, 0x53 => 0x9a59, + 0x54 => 0x9b1e, 0x55 => 0x9b1f, 0x56 => 0x9b20, 0x57 => 0x9c52, + 0x58 => 0x9c58, 0x59 => 0x9c50, 0x5a => 0x9c4a, 0x5b => 0x9c4d, + 0x5c => 0x9c4b, 0x5d => 0x9c55, 0x5e => 0x9c59, 0x5f => 0x9c4c, + 0x60 => 0x9c4e, 0x61 => 0x9dfb, 0x62 => 0x9df7, 0x63 => 0x9def, + 0x64 => 0x9de3, 0x65 => 0x9deb, 0x66 => 0x9df8, 0x67 => 0x9de4, + 0x68 => 0x9df6, 0x69 => 0x9de1, 0x6a => 0x9dee, 0x6b => 0x9de6, + 0x6c => 0x9df2, 0x6d => 0x9df0, 0x6e => 0x9de2, 0x6f => 0x9dec, + 0x70 => 0x9df4, 0x71 => 0x9df3, 0x72 => 0x9de8, 0x73 => 0x9ded, + 0x74 => 0x9ec2, 0x75 => 0x9ed0, 0x76 => 0x9ef2, 0x77 => 0x9ef3, + 0x78 => 0x9f06, 0x79 => 0x9f1c, 0x7a => 0x9f38, 0x7b => 0x9f37, + 0x7c => 0x9f36, 0x7d => 0x9f43, 0x7e => 0x9f4f, 0xa1 => 0x9f71, + 0xa2 => 0x9f70, 0xa3 => 0x9f6e, 0xa4 => 0x9f6f, 0xa5 => 0x56d3, + 0xa6 => 0x56cd, 0xa7 => 0x5b4e, 0xa8 => 0x5c6d, 0xa9 => 0x652d, + 0xaa => 0x66ed, 0xab => 0x66ee, 0xac => 0x6b13, 0xad => 0x705f, + 0xae => 0x7061, 0xaf => 0x705d, 0xb0 => 0x7060, 0xb1 => 0x7223, + 0xb2 => 0x74db, 0xb3 => 0x74e5, 0xb4 => 0x77d5, 0xb5 => 0x7938, + 0xb6 => 0x79b7, 0xb7 => 0x79b6, 0xb8 => 0x7c6a, 0xb9 => 0x7e97, + 0xba => 0x7f89, 0xbb => 0x826d, 0xbc => 0x8643, 0xbd => 0x8838, + 0xbe => 0x8837, 0xbf => 0x8835, 0xc0 => 0x884b, 0xc1 => 0x8b94, + 0xc2 => 0x8b95, 0xc3 => 0x8e9e, 0xc4 => 0x8e9f, 0xc5 => 0x8ea0, + 0xc6 => 0x8e9d, 0xc7 => 0x91be, 0xc8 => 0x91bd, 0xc9 => 0x91c2, + 0xca => 0x946b, 0xcb => 0x9468, 0xcc => 0x9469, 0xcd => 0x96e5, + 0xce => 0x9746, 0xcf => 0x9743, 0xd0 => 0x9747, 0xd1 => 0x97c7, + 0xd2 => 0x97e5, 0xd3 => 0x9a5e, 0xd4 => 0x9ad5, 0xd5 => 0x9b59, + 0xd6 => 0x9c63, 0xd7 => 0x9c67, 0xd8 => 0x9c66, 0xd9 => 0x9c62, + 0xda => 0x9c5e, 0xdb => 0x9c60, 0xdc => 0x9e02, 0xdd => 0x9dfe, + 0xde => 0x9e07, 0xdf => 0x9e03, 0xe0 => 0x9e06, 0xe1 => 0x9e05, + 0xe2 => 0x9e00, 0xe3 => 0x9e01, 0xe4 => 0x9e09, 0xe5 => 0x9dff, + 0xe6 => 0x9dfd, 0xe7 => 0x9e04, 0xe8 => 0x9ea0, 0xe9 => 0x9f1e, + 0xea => 0x9f46, 0xeb => 0x9f74, 0xec => 0x9f75, 0xed => 0x9f76, + 0xee => 0x56d4, 0xef => 0x652e, 0xf0 => 0x65b8, 0xf1 => 0x6b18, + 0xf2 => 0x6b19, 0xf3 => 0x6b17, 0xf4 => 0x6b1a, 0xf5 => 0x7062, + 0xf6 => 0x7226, 0xf7 => 0x72aa, 0xf8 => 0x77d8, 0xf9 => 0x77d9, + 0xfa => 0x7939, 0xfb => 0x7c69, 0xfc => 0x7c6b, 0xfd => 0x7cf6, + 0xfe => 0x7e9a, + }, + 0xf9 => { + 0x40 => 0x7e98, 0x41 => 0x7e9b, 0x42 => 0x7e99, 0x43 => 0x81e0, + 0x44 => 0x81e1, 0x45 => 0x8646, 0x46 => 0x8647, 0x47 => 0x8648, + 0x48 => 0x8979, 0x49 => 0x897a, 0x4a => 0x897c, 0x4b => 0x897b, + 0x4c => 0x89ff, 0x4d => 0x8b98, 0x4e => 0x8b99, 0x4f => 0x8ea5, + 0x50 => 0x8ea4, 0x51 => 0x8ea3, 0x52 => 0x946e, 0x53 => 0x946d, + 0x54 => 0x946f, 0x55 => 0x9471, 0x56 => 0x9473, 0x57 => 0x9749, + 0x58 => 0x9872, 0x59 => 0x995f, 0x5a => 0x9c68, 0x5b => 0x9c6e, + 0x5c => 0x9c6d, 0x5d => 0x9e0b, 0x5e => 0x9e0d, 0x5f => 0x9e10, + 0x60 => 0x9e0f, 0x61 => 0x9e12, 0x62 => 0x9e11, 0x63 => 0x9ea1, + 0x64 => 0x9ef5, 0x65 => 0x9f09, 0x66 => 0x9f47, 0x67 => 0x9f78, + 0x68 => 0x9f7b, 0x69 => 0x9f7a, 0x6a => 0x9f79, 0x6b => 0x571e, + 0x6c => 0x7066, 0x6d => 0x7c6f, 0x6e => 0x883c, 0x6f => 0x8db2, + 0x70 => 0x8ea6, 0x71 => 0x91c3, 0x72 => 0x9474, 0x73 => 0x9478, + 0x74 => 0x9476, 0x75 => 0x9475, 0x76 => 0x9a60, 0x77 => 0x9c74, + 0x78 => 0x9c73, 0x79 => 0x9c71, 0x7a => 0x9c75, 0x7b => 0x9e14, + 0x7c => 0x9e13, 0x7d => 0x9ef6, 0x7e => 0x9f0a, 0xa1 => 0x9fa4, + 0xa2 => 0x7068, 0xa3 => 0x7065, 0xa4 => 0x7cf7, 0xa5 => 0x866a, + 0xa6 => 0x883e, 0xa7 => 0x883d, 0xa8 => 0x883f, 0xa9 => 0x8b9e, + 0xaa => 0x8c9c, 0xab => 0x8ea9, 0xac => 0x8ec9, 0xad => 0x974b, + 0xae => 0x9873, 0xaf => 0x9874, 0xb0 => 0x98cc, 0xb1 => 0x9961, + 0xb2 => 0x99ab, 0xb3 => 0x9a64, 0xb4 => 0x9a66, 0xb5 => 0x9a67, + 0xb6 => 0x9b24, 0xb7 => 0x9e15, 0xb8 => 0x9e17, 0xb9 => 0x9f48, + 0xba => 0x6207, 0xbb => 0x6b1e, 0xbc => 0x7227, 0xbd => 0x864c, + 0xbe => 0x8ea8, 0xbf => 0x9482, 0xc0 => 0x9480, 0xc1 => 0x9481, + 0xc2 => 0x9a69, 0xc3 => 0x9a68, 0xc4 => 0x9b2e, 0xc5 => 0x9e19, + 0xc6 => 0x7229, 0xc7 => 0x864b, 0xc8 => 0x8b9f, 0xc9 => 0x9483, + 0xca => 0x9c79, 0xcb => 0x9eb7, 0xcc => 0x7675, 0xcd => 0x9a6b, + 0xce => 0x9c7a, 0xcf => 0x9e1d, 0xd0 => 0x7069, 0xd1 => 0x706a, + 0xd2 => 0x9ea4, 0xd3 => 0x9f7e, 0xd4 => 0x9f49, 0xd5 => 0x9f98, + }, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacCroatian.pm b/ExifTool/lib/Image/ExifTool/Charset/MacCroatian.pm new file mode 100644 index 0000000..f99d066 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacCroatian.pm @@ -0,0 +1,43 @@ +#------------------------------------------------------------------------------ +# File: MacCroatian.pm +# +# Description: Mac Croatian to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/CROATIAN.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacCroatian = ( + 0x80 => 0xc4, 0x81 => 0xc5, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1, + 0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2, + 0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, 0x8e => 0xe9, + 0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0xec, + 0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xf2, + 0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0xf9, + 0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa4 => 0xa7, + 0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, 0xa9 => 0x0160, + 0xaa => 0x2122, 0xab => 0xb4, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0x017d, + 0xaf => 0xd8, 0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0x2206, + 0xb6 => 0x2202, 0xb7 => 0x2211, 0xb8 => 0x220f, 0xb9 => 0x0161, + 0xba => 0x222b, 0xbb => 0xaa, 0xbc => 0xba, 0xbd => 0x03a9, 0xbe => 0x017e, + 0xbf => 0xf8, 0xc0 => 0xbf, 0xc1 => 0xa1, 0xc2 => 0xac, 0xc3 => 0x221a, + 0xc4 => 0x0192, 0xc5 => 0x2248, 0xc6 => 0x0106, 0xc7 => 0xab, 0xc8 => 0x010c, + 0xc9 => 0x2026, 0xca => 0xa0, 0xcb => 0xc0, 0xcc => 0xc3, 0xcd => 0xd5, + 0xce => 0x0152, 0xcf => 0x0153, 0xd0 => 0x0110, 0xd1 => 0x2014, + 0xd2 => 0x201c, 0xd3 => 0x201d, 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, + 0xd7 => 0x25ca, 0xd8 => 0xf8ff, 0xd9 => 0xa9, 0xda => 0x2044, 0xdb => 0x20ac, + 0xdc => 0x2039, 0xdd => 0x203a, 0xde => 0xc6, 0xdf => 0xbb, 0xe0 => 0x2013, + 0xe1 => 0xb7, 0xe2 => 0x201a, 0xe3 => 0x201e, 0xe4 => 0x2030, 0xe5 => 0xc2, + 0xe6 => 0x0107, 0xe7 => 0xc1, 0xe8 => 0x010d, 0xe9 => 0xc8, 0xea => 0xcd, + 0xeb => 0xce, 0xec => 0xcf, 0xed => 0xcc, 0xee => 0xd3, 0xef => 0xd4, + 0xf0 => 0x0111, 0xf1 => 0xd2, 0xf2 => 0xda, 0xf3 => 0xdb, 0xf4 => 0xd9, + 0xf5 => 0x0131, 0xf6 => 0x02c6, 0xf7 => 0x02dc, 0xf8 => 0xaf, 0xf9 => 0x03c0, + 0xfa => 0xcb, 0xfb => 0x02da, 0xfc => 0xb8, 0xfd => 0xca, 0xfe => 0xe6, + 0xff => 0x02c7, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacCyrillic.pm b/ExifTool/lib/Image/ExifTool/Charset/MacCyrillic.pm new file mode 100644 index 0000000..7d5ea75 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacCyrillic.pm @@ -0,0 +1,47 @@ +#------------------------------------------------------------------------------ +# File: MacCyrillic.pm +# +# Description: Mac Cyrillic to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/CYRILLIC.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacCyrillic = ( + 0x80 => 0x0410, 0x81 => 0x0411, 0x82 => 0x0412, 0x83 => 0x0413, + 0x84 => 0x0414, 0x85 => 0x0415, 0x86 => 0x0416, 0x87 => 0x0417, + 0x88 => 0x0418, 0x89 => 0x0419, 0x8a => 0x041a, 0x8b => 0x041b, + 0x8c => 0x041c, 0x8d => 0x041d, 0x8e => 0x041e, 0x8f => 0x041f, + 0x90 => 0x0420, 0x91 => 0x0421, 0x92 => 0x0422, 0x93 => 0x0423, + 0x94 => 0x0424, 0x95 => 0x0425, 0x96 => 0x0426, 0x97 => 0x0427, + 0x98 => 0x0428, 0x99 => 0x0429, 0x9a => 0x042a, 0x9b => 0x042b, + 0x9c => 0x042c, 0x9d => 0x042d, 0x9e => 0x042e, 0x9f => 0x042f, + 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa2 => 0x0490, 0xa4 => 0xa7, 0xa5 => 0x2022, + 0xa6 => 0xb6, 0xa7 => 0x0406, 0xa8 => 0xae, 0xaa => 0x2122, 0xab => 0x0402, + 0xac => 0x0452, 0xad => 0x2260, 0xae => 0x0403, 0xaf => 0x0453, + 0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0x0456, + 0xb6 => 0x0491, 0xb7 => 0x0408, 0xb8 => 0x0404, 0xb9 => 0x0454, + 0xba => 0x0407, 0xbb => 0x0457, 0xbc => 0x0409, 0xbd => 0x0459, + 0xbe => 0x040a, 0xbf => 0x045a, 0xc0 => 0x0458, 0xc1 => 0x0405, 0xc2 => 0xac, + 0xc3 => 0x221a, 0xc4 => 0x0192, 0xc5 => 0x2248, 0xc6 => 0x2206, 0xc7 => 0xab, + 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0, 0xcb => 0x040b, 0xcc => 0x045b, + 0xcd => 0x040c, 0xce => 0x045c, 0xcf => 0x0455, 0xd0 => 0x2013, + 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d, 0xd4 => 0x2018, + 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x201e, 0xd8 => 0x040e, 0xd9 => 0x045e, + 0xda => 0x040f, 0xdb => 0x045f, 0xdc => 0x2116, 0xdd => 0x0401, + 0xde => 0x0451, 0xdf => 0x044f, 0xe0 => 0x0430, 0xe1 => 0x0431, + 0xe2 => 0x0432, 0xe3 => 0x0433, 0xe4 => 0x0434, 0xe5 => 0x0435, + 0xe6 => 0x0436, 0xe7 => 0x0437, 0xe8 => 0x0438, 0xe9 => 0x0439, + 0xea => 0x043a, 0xeb => 0x043b, 0xec => 0x043c, 0xed => 0x043d, + 0xee => 0x043e, 0xef => 0x043f, 0xf0 => 0x0440, 0xf1 => 0x0441, + 0xf2 => 0x0442, 0xf3 => 0x0443, 0xf4 => 0x0444, 0xf5 => 0x0445, + 0xf6 => 0x0446, 0xf7 => 0x0447, 0xf8 => 0x0448, 0xf9 => 0x0449, + 0xfa => 0x044a, 0xfb => 0x044b, 0xfc => 0x044c, 0xfd => 0x044d, + 0xfe => 0x044e, 0xff => 0x20ac, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacGreek.pm b/ExifTool/lib/Image/ExifTool/Charset/MacGreek.pm new file mode 100644 index 0000000..43b2595 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacGreek.pm @@ -0,0 +1,45 @@ +#------------------------------------------------------------------------------ +# File: MacGreek.pm +# +# Description: Mac Greek to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GREEK.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacGreek = ( + 0x80 => 0xc4, 0x81 => 0xb9, 0x82 => 0xb2, 0x83 => 0xc9, 0x84 => 0xb3, + 0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0x0385, 0x88 => 0xe0, 0x89 => 0xe2, + 0x8a => 0xe4, 0x8b => 0x0384, 0x8c => 0xa8, 0x8d => 0xe7, 0x8e => 0xe9, + 0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xa3, 0x93 => 0x2122, + 0x94 => 0xee, 0x95 => 0xef, 0x96 => 0x2022, 0x97 => 0xbd, 0x98 => 0x2030, + 0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xa6, 0x9c => 0x20ac, 0x9d => 0xf9, + 0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0x0393, 0xa2 => 0x0394, + 0xa3 => 0x0398, 0xa4 => 0x039b, 0xa5 => 0x039e, 0xa6 => 0x03a0, 0xa7 => 0xdf, + 0xa8 => 0xae, 0xaa => 0x03a3, 0xab => 0x03aa, 0xac => 0xa7, 0xad => 0x2260, + 0xae => 0xb0, 0xaf => 0xb7, 0xb0 => 0x0391, 0xb2 => 0x2264, 0xb3 => 0x2265, + 0xb4 => 0xa5, 0xb5 => 0x0392, 0xb6 => 0x0395, 0xb7 => 0x0396, 0xb8 => 0x0397, + 0xb9 => 0x0399, 0xba => 0x039a, 0xbb => 0x039c, 0xbc => 0x03a6, + 0xbd => 0x03ab, 0xbe => 0x03a8, 0xbf => 0x03a9, 0xc0 => 0x03ac, + 0xc1 => 0x039d, 0xc2 => 0xac, 0xc3 => 0x039f, 0xc4 => 0x03a1, 0xc5 => 0x2248, + 0xc6 => 0x03a4, 0xc7 => 0xab, 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0, + 0xcb => 0x03a5, 0xcc => 0x03a7, 0xcd => 0x0386, 0xce => 0x0388, + 0xcf => 0x0153, 0xd0 => 0x2013, 0xd1 => 0x2015, 0xd2 => 0x201c, + 0xd3 => 0x201d, 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x0389, + 0xd8 => 0x038a, 0xd9 => 0x038c, 0xda => 0x038e, 0xdb => 0x03ad, + 0xdc => 0x03ae, 0xdd => 0x03af, 0xde => 0x03cc, 0xdf => 0x038f, + 0xe0 => 0x03cd, 0xe1 => 0x03b1, 0xe2 => 0x03b2, 0xe3 => 0x03c8, + 0xe4 => 0x03b4, 0xe5 => 0x03b5, 0xe6 => 0x03c6, 0xe7 => 0x03b3, + 0xe8 => 0x03b7, 0xe9 => 0x03b9, 0xea => 0x03be, 0xeb => 0x03ba, + 0xec => 0x03bb, 0xed => 0x03bc, 0xee => 0x03bd, 0xef => 0x03bf, + 0xf0 => 0x03c0, 0xf1 => 0x03ce, 0xf2 => 0x03c1, 0xf3 => 0x03c3, + 0xf4 => 0x03c4, 0xf5 => 0x03b8, 0xf6 => 0x03c9, 0xf7 => 0x03c2, + 0xf8 => 0x03c7, 0xf9 => 0x03c5, 0xfa => 0x03b6, 0xfb => 0x03ca, + 0xfc => 0x03cb, 0xfd => 0x0390, 0xfe => 0x03b0, 0xff => 0xad, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacHebrew.pm b/ExifTool/lib/Image/ExifTool/Charset/MacHebrew.pm new file mode 100644 index 0000000..86f8f08 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacHebrew.pm @@ -0,0 +1,47 @@ +#------------------------------------------------------------------------------ +# File: MacHebrew.pm +# +# Description: Mac Hebrew to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/HEBREW.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +# and directional characters are ignored +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacHebrew = ( + 0x80 => 0xc4, 0x81 => [0x05f2,0x05b7], 0x82 => 0xc7, 0x83 => 0xc9, + 0x84 => 0xd1, 0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, + 0x89 => 0xe2, 0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, + 0x8e => 0xe9, 0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, + 0x93 => 0xec, 0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, + 0x98 => 0xf2, 0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, + 0x9d => 0xf9, 0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x20, 0xa1 => 0x21, + 0xa2 => 0x22, 0xa3 => 0x23, 0xa4 => 0x24, 0xa5 => 0x25, 0xa6 => 0x20aa, + 0xa7 => 0x27, 0xa8 => 0x29, 0xa9 => 0x28, 0xaa => 0x2a, 0xab => 0x2b, + 0xac => 0x2c, 0xad => 0x2d, 0xae => 0x2e, 0xaf => 0x2f, 0xb0 => 0x30, + 0xb1 => 0x31, 0xb2 => 0x32, 0xb3 => 0x33, 0xb4 => 0x34, 0xb5 => 0x35, + 0xb6 => 0x36, 0xb7 => 0x37, 0xb8 => 0x38, 0xb9 => 0x39, 0xba => 0x3a, + 0xbb => 0x3b, 0xbc => 0x3c, 0xbd => 0x3d, 0xbe => 0x3e, 0xbf => 0x3f, + 0xc0 => [0xf86a,0x05dc,0x05b9], 0xc1 => 0x201e, 0xc2 => 0xf89b, + 0xc3 => 0xf89c, 0xc4 => 0xf89d, 0xc5 => 0xf89e, 0xc6 => 0x05bc, + 0xc7 => 0xfb4b, 0xc8 => 0xfb35, 0xc9 => 0x2026, 0xca => 0xa0, 0xcb => 0x05b8, + 0xcc => 0x05b7, 0xcd => 0x05b5, 0xce => 0x05b6, 0xcf => 0x05b4, + 0xd0 => 0x2013, 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d, + 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xfb2a, 0xd7 => 0xfb2b, + 0xd8 => 0x05bf, 0xd9 => 0x05b0, 0xda => 0x05b2, 0xdb => 0x05b1, + 0xdc => 0x05bb, 0xdd => 0x05b9, 0xde => [0x05b8,0xf87f], 0xdf => 0x05b3, + 0xe0 => 0x05d0, 0xe1 => 0x05d1, 0xe2 => 0x05d2, 0xe3 => 0x05d3, + 0xe4 => 0x05d4, 0xe5 => 0x05d5, 0xe6 => 0x05d6, 0xe7 => 0x05d7, + 0xe8 => 0x05d8, 0xe9 => 0x05d9, 0xea => 0x05da, 0xeb => 0x05db, + 0xec => 0x05dc, 0xed => 0x05dd, 0xee => 0x05de, 0xef => 0x05df, + 0xf0 => 0x05e0, 0xf1 => 0x05e1, 0xf2 => 0x05e2, 0xf3 => 0x05e3, + 0xf4 => 0x05e4, 0xf5 => 0x05e5, 0xf6 => 0x05e6, 0xf7 => 0x05e7, + 0xf8 => 0x05e8, 0xf9 => 0x05e9, 0xfa => 0x05ea, 0xfb => 0x7d, 0xfc => 0x5d, + 0xfd => 0x7b, 0xfe => 0x5b, 0xff => 0x7c, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacIceland.pm b/ExifTool/lib/Image/ExifTool/Charset/MacIceland.pm new file mode 100644 index 0000000..ad4d0c4 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacIceland.pm @@ -0,0 +1,42 @@ +#------------------------------------------------------------------------------ +# File: MacIceland.pm +# +# Description: Mac Icelandic to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/ICELAND.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacIceland = ( + 0x80 => 0xc4, 0x81 => 0xc5, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1, + 0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2, + 0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, 0x8e => 0xe9, + 0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0xec, + 0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xf2, + 0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0xf9, + 0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0xdd, 0xa1 => 0xb0, 0xa4 => 0xa7, + 0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, 0xaa => 0x2122, + 0xab => 0xb4, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0xc6, 0xaf => 0xd8, + 0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0xa5, 0xb6 => 0x2202, + 0xb7 => 0x2211, 0xb8 => 0x220f, 0xb9 => 0x03c0, 0xba => 0x222b, 0xbb => 0xaa, + 0xbc => 0xba, 0xbd => 0x03a9, 0xbe => 0xe6, 0xbf => 0xf8, 0xc0 => 0xbf, + 0xc1 => 0xa1, 0xc2 => 0xac, 0xc3 => 0x221a, 0xc4 => 0x0192, 0xc5 => 0x2248, + 0xc6 => 0x2206, 0xc7 => 0xab, 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0, + 0xcb => 0xc0, 0xcc => 0xc3, 0xcd => 0xd5, 0xce => 0x0152, 0xcf => 0x0153, + 0xd0 => 0x2013, 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d, + 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x25ca, 0xd8 => 0xff, + 0xd9 => 0x0178, 0xda => 0x2044, 0xdb => 0x20ac, 0xdc => 0xd0, 0xdd => 0xf0, + 0xdf => 0xfe, 0xe0 => 0xfd, 0xe1 => 0xb7, 0xe2 => 0x201a, 0xe3 => 0x201e, + 0xe4 => 0x2030, 0xe5 => 0xc2, 0xe6 => 0xca, 0xe7 => 0xc1, 0xe8 => 0xcb, + 0xe9 => 0xc8, 0xea => 0xcd, 0xeb => 0xce, 0xec => 0xcf, 0xed => 0xcc, + 0xee => 0xd3, 0xef => 0xd4, 0xf0 => 0xf8ff, 0xf1 => 0xd2, 0xf2 => 0xda, + 0xf3 => 0xdb, 0xf4 => 0xd9, 0xf5 => 0x0131, 0xf6 => 0x02c6, 0xf7 => 0x02dc, + 0xf8 => 0xaf, 0xf9 => 0x02d8, 0xfa => 0x02d9, 0xfb => 0x02da, 0xfc => 0xb8, + 0xfd => 0x02dd, 0xfe => 0x02db, 0xff => 0x02c7, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacJapanese.pm b/ExifTool/lib/Image/ExifTool/Charset/MacJapanese.pm new file mode 100644 index 0000000..79afe55 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacJapanese.pm @@ -0,0 +1,1933 @@ +#------------------------------------------------------------------------------ +# File: MacJapanese.pm +# +# Description: Mac Japanese to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/JAPANESE.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +# This set re-maps characters with codepoints less than 0x80 +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacJapanese = ( + 0x5c => 0xa5, 0x80 => 0x5c, 0xa1 => 0xff61, 0xa2 => 0xff62, 0xa3 => 0xff63, + 0xa4 => 0xff64, 0xa5 => 0xff65, 0xa6 => 0xff66, 0xa7 => 0xff67, + 0xa8 => 0xff68, 0xa9 => 0xff69, 0xaa => 0xff6a, 0xab => 0xff6b, + 0xac => 0xff6c, 0xad => 0xff6d, 0xae => 0xff6e, 0xaf => 0xff6f, + 0xb0 => 0xff70, 0xb1 => 0xff71, 0xb2 => 0xff72, 0xb3 => 0xff73, + 0xb4 => 0xff74, 0xb5 => 0xff75, 0xb6 => 0xff76, 0xb7 => 0xff77, + 0xb8 => 0xff78, 0xb9 => 0xff79, 0xba => 0xff7a, 0xbb => 0xff7b, + 0xbc => 0xff7c, 0xbd => 0xff7d, 0xbe => 0xff7e, 0xbf => 0xff7f, + 0xc0 => 0xff80, 0xc1 => 0xff81, 0xc2 => 0xff82, 0xc3 => 0xff83, + 0xc4 => 0xff84, 0xc5 => 0xff85, 0xc6 => 0xff86, 0xc7 => 0xff87, + 0xc8 => 0xff88, 0xc9 => 0xff89, 0xca => 0xff8a, 0xcb => 0xff8b, + 0xcc => 0xff8c, 0xcd => 0xff8d, 0xce => 0xff8e, 0xcf => 0xff8f, + 0xd0 => 0xff90, 0xd1 => 0xff91, 0xd2 => 0xff92, 0xd3 => 0xff93, + 0xd4 => 0xff94, 0xd5 => 0xff95, 0xd6 => 0xff96, 0xd7 => 0xff97, + 0xd8 => 0xff98, 0xd9 => 0xff99, 0xda => 0xff9a, 0xdb => 0xff9b, + 0xdc => 0xff9c, 0xdd => 0xff9d, 0xde => 0xff9e, 0xdf => 0xff9f, 0xfd => 0xa9, + 0xfe => 0x2122, 0xff => [0x2026,0xf87f], + 0x81 => { + 0x40 => 0x3000, 0x41 => 0x3001, 0x42 => 0x3002, 0x43 => 0xff0c, + 0x44 => 0xff0e, 0x45 => 0x30fb, 0x46 => 0xff1a, 0x47 => 0xff1b, + 0x48 => 0xff1f, 0x49 => 0xff01, 0x4a => 0x309b, 0x4b => 0x309c, + 0x4c => 0xb4, 0x4d => 0xff40, 0x4e => 0xa8, 0x4f => 0xff3e, 0x50 => 0xffe3, + 0x51 => 0xff3f, 0x52 => 0x30fd, 0x53 => 0x30fe, 0x54 => 0x309d, + 0x55 => 0x309e, 0x56 => 0x3003, 0x57 => 0x4edd, 0x58 => 0x3005, + 0x59 => 0x3006, 0x5a => 0x3007, 0x5b => 0x30fc, 0x5c => 0x2014, + 0x5d => 0x2010, 0x5e => 0xff0f, 0x5f => 0xff3c, 0x60 => 0x301c, + 0x61 => 0x2016, 0x62 => 0xff5c, 0x63 => 0x2026, 0x64 => 0x2025, + 0x65 => 0x2018, 0x66 => 0x2019, 0x67 => 0x201c, 0x68 => 0x201d, + 0x69 => 0xff08, 0x6a => 0xff09, 0x6b => 0x3014, 0x6c => 0x3015, + 0x6d => 0xff3b, 0x6e => 0xff3d, 0x6f => 0xff5b, 0x70 => 0xff5d, + 0x71 => 0x3008, 0x72 => 0x3009, 0x73 => 0x300a, 0x74 => 0x300b, + 0x75 => 0x300c, 0x76 => 0x300d, 0x77 => 0x300e, 0x78 => 0x300f, + 0x79 => 0x3010, 0x7a => 0x3011, 0x7b => 0xff0b, 0x7c => 0x2212, + 0x7d => 0xb1, 0x7e => 0xd7, 0x80 => 0xf7, 0x81 => 0xff1d, 0x82 => 0x2260, + 0x83 => 0xff1c, 0x84 => 0xff1e, 0x85 => 0x2266, 0x86 => 0x2267, + 0x87 => 0x221e, 0x88 => 0x2234, 0x89 => 0x2642, 0x8a => 0x2640, + 0x8b => 0xb0, 0x8c => 0x2032, 0x8d => 0x2033, 0x8e => 0x2103, + 0x8f => 0xffe5, 0x90 => 0xff04, 0x91 => 0xa2, 0x92 => 0xa3, 0x93 => 0xff05, + 0x94 => 0xff03, 0x95 => 0xff06, 0x96 => 0xff0a, 0x97 => 0xff20, + 0x98 => 0xa7, 0x99 => 0x2606, 0x9a => 0x2605, 0x9b => 0x25cb, + 0x9c => 0x25cf, 0x9d => 0x25ce, 0x9e => 0x25c7, 0x9f => 0x25c6, + 0xa0 => 0x25a1, 0xa1 => 0x25a0, 0xa2 => 0x25b3, 0xa3 => 0x25b2, + 0xa4 => 0x25bd, 0xa5 => 0x25bc, 0xa6 => 0x203b, 0xa7 => 0x3012, + 0xa8 => 0x2192, 0xa9 => 0x2190, 0xaa => 0x2191, 0xab => 0x2193, + 0xac => 0x3013, 0xb8 => 0x2208, 0xb9 => 0x220b, 0xba => 0x2286, + 0xbb => 0x2287, 0xbc => 0x2282, 0xbd => 0x2283, 0xbe => 0x222a, + 0xbf => 0x2229, 0xc8 => 0x2227, 0xc9 => 0x2228, 0xca => 0xac, + 0xcb => 0x21d2, 0xcc => 0x21d4, 0xcd => 0x2200, 0xce => 0x2203, + 0xda => 0x2220, 0xdb => 0x22a5, 0xdc => 0x2312, 0xdd => 0x2202, + 0xde => 0x2207, 0xdf => 0x2261, 0xe0 => 0x2252, 0xe1 => 0x226a, + 0xe2 => 0x226b, 0xe3 => 0x221a, 0xe4 => 0x223d, 0xe5 => 0x221d, + 0xe6 => 0x2235, 0xe7 => 0x222b, 0xe8 => 0x222c, 0xf0 => 0x212b, + 0xf1 => 0x2030, 0xf2 => 0x266f, 0xf3 => 0x266d, 0xf4 => 0x266a, + 0xf5 => 0x2020, 0xf6 => 0x2021, 0xf7 => 0xb6, 0xfc => 0x25ef, + }, + 0x82 => { + 0x4f => 0xff10, 0x50 => 0xff11, 0x51 => 0xff12, 0x52 => 0xff13, + 0x53 => 0xff14, 0x54 => 0xff15, 0x55 => 0xff16, 0x56 => 0xff17, + 0x57 => 0xff18, 0x58 => 0xff19, 0x60 => 0xff21, 0x61 => 0xff22, + 0x62 => 0xff23, 0x63 => 0xff24, 0x64 => 0xff25, 0x65 => 0xff26, + 0x66 => 0xff27, 0x67 => 0xff28, 0x68 => 0xff29, 0x69 => 0xff2a, + 0x6a => 0xff2b, 0x6b => 0xff2c, 0x6c => 0xff2d, 0x6d => 0xff2e, + 0x6e => 0xff2f, 0x6f => 0xff30, 0x70 => 0xff31, 0x71 => 0xff32, + 0x72 => 0xff33, 0x73 => 0xff34, 0x74 => 0xff35, 0x75 => 0xff36, + 0x76 => 0xff37, 0x77 => 0xff38, 0x78 => 0xff39, 0x79 => 0xff3a, + 0x81 => 0xff41, 0x82 => 0xff42, 0x83 => 0xff43, 0x84 => 0xff44, + 0x85 => 0xff45, 0x86 => 0xff46, 0x87 => 0xff47, 0x88 => 0xff48, + 0x89 => 0xff49, 0x8a => 0xff4a, 0x8b => 0xff4b, 0x8c => 0xff4c, + 0x8d => 0xff4d, 0x8e => 0xff4e, 0x8f => 0xff4f, 0x90 => 0xff50, + 0x91 => 0xff51, 0x92 => 0xff52, 0x93 => 0xff53, 0x94 => 0xff54, + 0x95 => 0xff55, 0x96 => 0xff56, 0x97 => 0xff57, 0x98 => 0xff58, + 0x99 => 0xff59, 0x9a => 0xff5a, 0x9f => 0x3041, 0xa0 => 0x3042, + 0xa1 => 0x3043, 0xa2 => 0x3044, 0xa3 => 0x3045, 0xa4 => 0x3046, + 0xa5 => 0x3047, 0xa6 => 0x3048, 0xa7 => 0x3049, 0xa8 => 0x304a, + 0xa9 => 0x304b, 0xaa => 0x304c, 0xab => 0x304d, 0xac => 0x304e, + 0xad => 0x304f, 0xae => 0x3050, 0xaf => 0x3051, 0xb0 => 0x3052, + 0xb1 => 0x3053, 0xb2 => 0x3054, 0xb3 => 0x3055, 0xb4 => 0x3056, + 0xb5 => 0x3057, 0xb6 => 0x3058, 0xb7 => 0x3059, 0xb8 => 0x305a, + 0xb9 => 0x305b, 0xba => 0x305c, 0xbb => 0x305d, 0xbc => 0x305e, + 0xbd => 0x305f, 0xbe => 0x3060, 0xbf => 0x3061, 0xc0 => 0x3062, + 0xc1 => 0x3063, 0xc2 => 0x3064, 0xc3 => 0x3065, 0xc4 => 0x3066, + 0xc5 => 0x3067, 0xc6 => 0x3068, 0xc7 => 0x3069, 0xc8 => 0x306a, + 0xc9 => 0x306b, 0xca => 0x306c, 0xcb => 0x306d, 0xcc => 0x306e, + 0xcd => 0x306f, 0xce => 0x3070, 0xcf => 0x3071, 0xd0 => 0x3072, + 0xd1 => 0x3073, 0xd2 => 0x3074, 0xd3 => 0x3075, 0xd4 => 0x3076, + 0xd5 => 0x3077, 0xd6 => 0x3078, 0xd7 => 0x3079, 0xd8 => 0x307a, + 0xd9 => 0x307b, 0xda => 0x307c, 0xdb => 0x307d, 0xdc => 0x307e, + 0xdd => 0x307f, 0xde => 0x3080, 0xdf => 0x3081, 0xe0 => 0x3082, + 0xe1 => 0x3083, 0xe2 => 0x3084, 0xe3 => 0x3085, 0xe4 => 0x3086, + 0xe5 => 0x3087, 0xe6 => 0x3088, 0xe7 => 0x3089, 0xe8 => 0x308a, + 0xe9 => 0x308b, 0xea => 0x308c, 0xeb => 0x308d, 0xec => 0x308e, + 0xed => 0x308f, 0xee => 0x3090, 0xef => 0x3091, 0xf0 => 0x3092, + 0xf1 => 0x3093, + }, + 0x83 => { + 0x40 => 0x30a1, 0x41 => 0x30a2, 0x42 => 0x30a3, 0x43 => 0x30a4, + 0x44 => 0x30a5, 0x45 => 0x30a6, 0x46 => 0x30a7, 0x47 => 0x30a8, + 0x48 => 0x30a9, 0x49 => 0x30aa, 0x4a => 0x30ab, 0x4b => 0x30ac, + 0x4c => 0x30ad, 0x4d => 0x30ae, 0x4e => 0x30af, 0x4f => 0x30b0, + 0x50 => 0x30b1, 0x51 => 0x30b2, 0x52 => 0x30b3, 0x53 => 0x30b4, + 0x54 => 0x30b5, 0x55 => 0x30b6, 0x56 => 0x30b7, 0x57 => 0x30b8, + 0x58 => 0x30b9, 0x59 => 0x30ba, 0x5a => 0x30bb, 0x5b => 0x30bc, + 0x5c => 0x30bd, 0x5d => 0x30be, 0x5e => 0x30bf, 0x5f => 0x30c0, + 0x60 => 0x30c1, 0x61 => 0x30c2, 0x62 => 0x30c3, 0x63 => 0x30c4, + 0x64 => 0x30c5, 0x65 => 0x30c6, 0x66 => 0x30c7, 0x67 => 0x30c8, + 0x68 => 0x30c9, 0x69 => 0x30ca, 0x6a => 0x30cb, 0x6b => 0x30cc, + 0x6c => 0x30cd, 0x6d => 0x30ce, 0x6e => 0x30cf, 0x6f => 0x30d0, + 0x70 => 0x30d1, 0x71 => 0x30d2, 0x72 => 0x30d3, 0x73 => 0x30d4, + 0x74 => 0x30d5, 0x75 => 0x30d6, 0x76 => 0x30d7, 0x77 => 0x30d8, + 0x78 => 0x30d9, 0x79 => 0x30da, 0x7a => 0x30db, 0x7b => 0x30dc, + 0x7c => 0x30dd, 0x7d => 0x30de, 0x7e => 0x30df, 0x80 => 0x30e0, + 0x81 => 0x30e1, 0x82 => 0x30e2, 0x83 => 0x30e3, 0x84 => 0x30e4, + 0x85 => 0x30e5, 0x86 => 0x30e6, 0x87 => 0x30e7, 0x88 => 0x30e8, + 0x89 => 0x30e9, 0x8a => 0x30ea, 0x8b => 0x30eb, 0x8c => 0x30ec, + 0x8d => 0x30ed, 0x8e => 0x30ee, 0x8f => 0x30ef, 0x90 => 0x30f0, + 0x91 => 0x30f1, 0x92 => 0x30f2, 0x93 => 0x30f3, 0x94 => 0x30f4, + 0x95 => 0x30f5, 0x96 => 0x30f6, 0x9f => 0x0391, 0xa0 => 0x0392, + 0xa1 => 0x0393, 0xa2 => 0x0394, 0xa3 => 0x0395, 0xa4 => 0x0396, + 0xa5 => 0x0397, 0xa6 => 0x0398, 0xa7 => 0x0399, 0xa8 => 0x039a, + 0xa9 => 0x039b, 0xaa => 0x039c, 0xab => 0x039d, 0xac => 0x039e, + 0xad => 0x039f, 0xae => 0x03a0, 0xaf => 0x03a1, 0xb0 => 0x03a3, + 0xb1 => 0x03a4, 0xb2 => 0x03a5, 0xb3 => 0x03a6, 0xb4 => 0x03a7, + 0xb5 => 0x03a8, 0xb6 => 0x03a9, 0xbf => 0x03b1, 0xc0 => 0x03b2, + 0xc1 => 0x03b3, 0xc2 => 0x03b4, 0xc3 => 0x03b5, 0xc4 => 0x03b6, + 0xc5 => 0x03b7, 0xc6 => 0x03b8, 0xc7 => 0x03b9, 0xc8 => 0x03ba, + 0xc9 => 0x03bb, 0xca => 0x03bc, 0xcb => 0x03bd, 0xcc => 0x03be, + 0xcd => 0x03bf, 0xce => 0x03c0, 0xcf => 0x03c1, 0xd0 => 0x03c3, + 0xd1 => 0x03c4, 0xd2 => 0x03c5, 0xd3 => 0x03c6, 0xd4 => 0x03c7, + 0xd5 => 0x03c8, 0xd6 => 0x03c9, + }, + 0x84 => { + 0x40 => 0x0410, 0x41 => 0x0411, 0x42 => 0x0412, 0x43 => 0x0413, + 0x44 => 0x0414, 0x45 => 0x0415, 0x46 => 0x0401, 0x47 => 0x0416, + 0x48 => 0x0417, 0x49 => 0x0418, 0x4a => 0x0419, 0x4b => 0x041a, + 0x4c => 0x041b, 0x4d => 0x041c, 0x4e => 0x041d, 0x4f => 0x041e, + 0x50 => 0x041f, 0x51 => 0x0420, 0x52 => 0x0421, 0x53 => 0x0422, + 0x54 => 0x0423, 0x55 => 0x0424, 0x56 => 0x0425, 0x57 => 0x0426, + 0x58 => 0x0427, 0x59 => 0x0428, 0x5a => 0x0429, 0x5b => 0x042a, + 0x5c => 0x042b, 0x5d => 0x042c, 0x5e => 0x042d, 0x5f => 0x042e, + 0x60 => 0x042f, 0x70 => 0x0430, 0x71 => 0x0431, 0x72 => 0x0432, + 0x73 => 0x0433, 0x74 => 0x0434, 0x75 => 0x0435, 0x76 => 0x0451, + 0x77 => 0x0436, 0x78 => 0x0437, 0x79 => 0x0438, 0x7a => 0x0439, + 0x7b => 0x043a, 0x7c => 0x043b, 0x7d => 0x043c, 0x7e => 0x043d, + 0x80 => 0x043e, 0x81 => 0x043f, 0x82 => 0x0440, 0x83 => 0x0441, + 0x84 => 0x0442, 0x85 => 0x0443, 0x86 => 0x0444, 0x87 => 0x0445, + 0x88 => 0x0446, 0x89 => 0x0447, 0x8a => 0x0448, 0x8b => 0x0449, + 0x8c => 0x044a, 0x8d => 0x044b, 0x8e => 0x044c, 0x8f => 0x044d, + 0x90 => 0x044e, 0x91 => 0x044f, 0x9f => 0x2500, 0xa0 => 0x2502, + 0xa1 => 0x250c, 0xa2 => 0x2510, 0xa3 => 0x2518, 0xa4 => 0x2514, + 0xa5 => 0x251c, 0xa6 => 0x252c, 0xa7 => 0x2524, 0xa8 => 0x2534, + 0xa9 => 0x253c, 0xaa => 0x2501, 0xab => 0x2503, 0xac => 0x250f, + 0xad => 0x2513, 0xae => 0x251b, 0xaf => 0x2517, 0xb0 => 0x2523, + 0xb1 => 0x2533, 0xb2 => 0x252b, 0xb3 => 0x253b, 0xb4 => 0x254b, + 0xb5 => 0x2520, 0xb6 => 0x252f, 0xb7 => 0x2528, 0xb8 => 0x2537, + 0xb9 => 0x253f, 0xba => 0x251d, 0xbb => 0x2530, 0xbc => 0x2525, + 0xbd => 0x2538, 0xbe => 0x2542, + }, + 0x85 => { + 0x40 => 0x2460, 0x41 => 0x2461, 0x42 => 0x2462, 0x43 => 0x2463, + 0x44 => 0x2464, 0x45 => 0x2465, 0x46 => 0x2466, 0x47 => 0x2467, + 0x48 => 0x2468, 0x49 => 0x2469, 0x4a => 0x246a, 0x4b => 0x246b, + 0x4c => 0x246c, 0x4d => 0x246d, 0x4e => 0x246e, 0x4f => 0x246f, + 0x50 => 0x2470, 0x51 => 0x2471, 0x52 => 0x2472, 0x53 => 0x2473, + 0x5e => 0x2474, 0x5f => 0x2475, 0x60 => 0x2476, 0x61 => 0x2477, + 0x62 => 0x2478, 0x63 => 0x2479, 0x64 => 0x247a, 0x65 => 0x247b, + 0x66 => 0x247c, 0x67 => 0x247d, 0x68 => 0x247e, 0x69 => 0x247f, + 0x6a => 0x2480, 0x6b => 0x2481, 0x6c => 0x2482, 0x6d => 0x2483, + 0x6e => 0x2484, 0x6f => 0x2485, 0x70 => 0x2486, 0x71 => 0x2487, + 0x7c => 0x2776, 0x7d => 0x2777, 0x7e => 0x2778, 0x80 => 0x2779, + 0x81 => 0x277a, 0x82 => 0x277b, 0x83 => 0x277c, 0x84 => 0x277d, + 0x85 => 0x277e, 0x91 => [0xf860,0x30,0x2e], 0x92 => 0x2488, 0x93 => 0x2489, + 0x94 => 0x248a, 0x95 => 0x248b, 0x96 => 0x248c, 0x97 => 0x248d, + 0x98 => 0x248e, 0x99 => 0x248f, 0x9a => 0x2490, 0x9f => 0x2160, + 0xa0 => 0x2161, 0xa1 => 0x2162, 0xa2 => 0x2163, 0xa3 => 0x2164, + 0xa4 => 0x2165, 0xa5 => 0x2166, 0xa6 => 0x2167, 0xa7 => 0x2168, + 0xa8 => 0x2169, 0xa9 => 0x216a, 0xaa => 0x216b, + 0xab => [0xf862,0x58,0x49,0x49,0x49], 0xac => [0xf861,0x58,0x49,0x56], + 0xad => [0xf860,0x58,0x56], 0xb3 => 0x2170, 0xb4 => 0x2171, 0xb5 => 0x2172, + 0xb6 => 0x2173, 0xb7 => 0x2174, 0xb8 => 0x2175, 0xb9 => 0x2176, + 0xba => 0x2177, 0xbb => 0x2178, 0xbc => 0x2179, 0xbd => 0x217a, + 0xbe => 0x217b, 0xbf => [0xf862,0x78,0x69,0x69,0x69], + 0xc0 => [0xf861,0x78,0x69,0x76], 0xc1 => [0xf860,0x78,0x76], 0xdb => 0x249c, + 0xdc => 0x249d, 0xdd => 0x249e, 0xde => 0x249f, 0xdf => 0x24a0, + 0xe0 => 0x24a1, 0xe1 => 0x24a2, 0xe2 => 0x24a3, 0xe3 => 0x24a4, + 0xe4 => 0x24a5, 0xe5 => 0x24a6, 0xe6 => 0x24a7, 0xe7 => 0x24a8, + 0xe8 => 0x24a9, 0xe9 => 0x24aa, 0xea => 0x24ab, 0xeb => 0x24ac, + 0xec => 0x24ad, 0xed => 0x24ae, 0xee => 0x24af, 0xef => 0x24b0, + 0xf0 => 0x24b1, 0xf1 => 0x24b2, 0xf2 => 0x24b3, 0xf3 => 0x24b4, + 0xf4 => 0x24b5, + }, + 0x86 => { + 0x40 => 0x339c, 0x41 => 0x339f, 0x42 => 0x339d, 0x43 => 0x33a0, + 0x44 => 0x33a4, 0x45 => [0xff4d,0xf87f], 0x46 => 0x33a1, 0x47 => 0x33a5, + 0x48 => 0x339e, 0x49 => 0x33a2, 0x4a => 0x338e, 0x4b => [0xff47,0xf87f], + 0x4c => 0x338f, 0x4d => 0x33c4, 0x4e => 0x3396, 0x4f => 0x3397, + 0x50 => 0x2113, 0x51 => 0x3398, 0x52 => 0x33b3, 0x53 => 0x33b2, + 0x54 => 0x33b1, 0x55 => 0x33b0, 0x56 => 0x2109, 0x57 => 0x33d4, + 0x58 => 0x33cb, 0x59 => 0x3390, 0x5a => 0x3385, 0x5b => 0x3386, + 0x5c => 0x3387, 0x5d => [0xf860,0x54,0x42], 0x9b => 0x2116, 0x9c => 0x33cd, + 0x9d => 0x2121, 0x9e => [0xf861,0x46,0x41,0x58], 0x9f => 0x2664, + 0xa0 => 0x2667, 0xa1 => 0x2661, 0xa2 => 0x2662, 0xa3 => 0x2660, + 0xa4 => 0x2663, 0xa5 => 0x2665, 0xa6 => 0x2666, 0xb3 => 0x3020, + 0xb4 => 0x260e, 0xb5 => 0x3004, 0xc7 => 0x261e, 0xc8 => 0x261c, + 0xc9 => 0x261d, 0xca => 0x261f, 0xcb => 0x21c6, 0xcc => 0x21c4, + 0xcd => 0x21c5, 0xce => [0xf860,0x2193,0x2191], 0xcf => 0x21e8, + 0xd0 => 0x21e6, 0xd1 => 0x21e7, 0xd2 => 0x21e9, 0xd3 => [0x21e8,0xf87a], + 0xd4 => [0x21e6,0xf87a], 0xd5 => [0x21e7,0xf87a], 0xd6 => [0x21e9,0xf87a], + }, + 0x87 => { + 0x40 => 0x3230, 0x41 => 0x322a, 0x42 => 0x322b, 0x43 => 0x322c, + 0x44 => 0x322d, 0x45 => 0x322e, 0x46 => 0x322f, 0x47 => 0x3240, + 0x48 => 0x3237, 0x49 => 0x3242, 0x4a => 0x3243, 0x4b => 0x3239, + 0x4c => 0x323a, 0x4d => 0x3231, 0x4e => 0x323e, 0x4f => 0x3234, + 0x50 => 0x3232, 0x51 => 0x323b, 0x52 => 0x3236, 0x53 => 0x3233, + 0x54 => 0x3235, 0x55 => 0x323c, 0x56 => 0x323d, 0x57 => 0x323f, + 0x58 => 0x3238, 0x91 => [0x5927,0x20dd], 0x92 => [0x5c0f,0x20dd], + 0x93 => 0x32a4, 0x94 => 0x32a5, 0x95 => 0x32a6, 0x96 => 0x32a7, + 0x97 => 0x32a8, 0x98 => 0x32a9, 0x99 => 0x3296, 0x9a => 0x329d, + 0x9b => 0x3298, 0x9c => 0x329e, 0x9d => [0x63a7,0x20dd], 0x9e => 0x3299, + 0x9f => 0x3349, 0xa0 => 0x3322, 0xa1 => 0x334d, 0xa2 => 0x3314, + 0xa3 => 0x3316, 0xa4 => 0x3305, 0xa5 => 0x3333, 0xa6 => 0x334e, + 0xa7 => 0x3303, 0xa8 => 0x3336, 0xa9 => 0x3318, 0xaa => 0x3315, + 0xab => 0x3327, 0xac => 0x3351, 0xad => 0x334a, 0xae => 0x3339, + 0xaf => 0x3357, 0xb0 => 0x330d, 0xb1 => 0x3342, 0xb2 => 0x3323, + 0xb3 => 0x3326, 0xb4 => 0x333b, 0xb5 => 0x332b, 0xbd => 0x3300, + 0xbe => 0x331e, 0xbf => 0x332a, 0xc0 => 0x3331, 0xc1 => 0x3347, + 0xe5 => 0x337e, 0xe6 => 0x337d, 0xe7 => 0x337c, 0xe8 => 0x337b, + 0xfa => 0x337f, 0xfb => [0xf862,0x6709,0x9650,0x4f1a,0x793e], + 0xfc => [0xf862,0x8ca1,0x56e3,0x6cd5,0x4eba], + }, + 0x88 => { + 0x40 => 0x222e, 0x41 => 0x221f, 0x42 => 0x22bf, 0x54 => 0x301d, + 0x55 => 0x301f, 0x68 => 0x3094, 0x6a => 0x30f7, 0x6b => 0x30f8, + 0x6c => 0x30f9, 0x6d => 0x30fa, 0x9f => 0x4e9c, 0xa0 => 0x5516, + 0xa1 => 0x5a03, 0xa2 => 0x963f, 0xa3 => 0x54c0, 0xa4 => 0x611b, + 0xa5 => 0x6328, 0xa6 => 0x59f6, 0xa7 => 0x9022, 0xa8 => 0x8475, + 0xa9 => 0x831c, 0xaa => 0x7a50, 0xab => 0x60aa, 0xac => 0x63e1, + 0xad => 0x6e25, 0xae => 0x65ed, 0xaf => 0x8466, 0xb0 => 0x82a6, + 0xb1 => 0x9bf5, 0xb2 => 0x6893, 0xb3 => 0x5727, 0xb4 => 0x65a1, + 0xb5 => 0x6271, 0xb6 => 0x5b9b, 0xb7 => 0x59d0, 0xb8 => 0x867b, + 0xb9 => 0x98f4, 0xba => 0x7d62, 0xbb => 0x7dbe, 0xbc => 0x9b8e, + 0xbd => 0x6216, 0xbe => 0x7c9f, 0xbf => 0x88b7, 0xc0 => 0x5b89, + 0xc1 => 0x5eb5, 0xc2 => 0x6309, 0xc3 => 0x6697, 0xc4 => 0x6848, + 0xc5 => 0x95c7, 0xc6 => 0x978d, 0xc7 => 0x674f, 0xc8 => 0x4ee5, + 0xc9 => 0x4f0a, 0xca => 0x4f4d, 0xcb => 0x4f9d, 0xcc => 0x5049, + 0xcd => 0x56f2, 0xce => 0x5937, 0xcf => 0x59d4, 0xd0 => 0x5a01, + 0xd1 => 0x5c09, 0xd2 => 0x60df, 0xd3 => 0x610f, 0xd4 => 0x6170, + 0xd5 => 0x6613, 0xd6 => 0x6905, 0xd7 => 0x70ba, 0xd8 => 0x754f, + 0xd9 => 0x7570, 0xda => 0x79fb, 0xdb => 0x7dad, 0xdc => 0x7def, + 0xdd => 0x80c3, 0xde => 0x840e, 0xdf => 0x8863, 0xe0 => 0x8b02, + 0xe1 => 0x9055, 0xe2 => 0x907a, 0xe3 => 0x533b, 0xe4 => 0x4e95, + 0xe5 => 0x4ea5, 0xe6 => 0x57df, 0xe7 => 0x80b2, 0xe8 => 0x90c1, + 0xe9 => 0x78ef, 0xea => 0x4e00, 0xeb => 0x58f1, 0xec => 0x6ea2, + 0xed => 0x9038, 0xee => 0x7a32, 0xef => 0x8328, 0xf0 => 0x828b, + 0xf1 => 0x9c2f, 0xf2 => 0x5141, 0xf3 => 0x5370, 0xf4 => 0x54bd, + 0xf5 => 0x54e1, 0xf6 => 0x56e0, 0xf7 => 0x59fb, 0xf8 => 0x5f15, + 0xf9 => 0x98f2, 0xfa => 0x6deb, 0xfb => 0x80e4, 0xfc => 0x852d, + }, + 0x89 => { + 0x40 => 0x9662, 0x41 => 0x9670, 0x42 => 0x96a0, 0x43 => 0x97fb, + 0x44 => 0x540b, 0x45 => 0x53f3, 0x46 => 0x5b87, 0x47 => 0x70cf, + 0x48 => 0x7fbd, 0x49 => 0x8fc2, 0x4a => 0x96e8, 0x4b => 0x536f, + 0x4c => 0x9d5c, 0x4d => 0x7aba, 0x4e => 0x4e11, 0x4f => 0x7893, + 0x50 => 0x81fc, 0x51 => 0x6e26, 0x52 => 0x5618, 0x53 => 0x5504, + 0x54 => 0x6b1d, 0x55 => 0x851a, 0x56 => 0x9c3b, 0x57 => 0x59e5, + 0x58 => 0x53a9, 0x59 => 0x6d66, 0x5a => 0x74dc, 0x5b => 0x958f, + 0x5c => 0x5642, 0x5d => 0x4e91, 0x5e => 0x904b, 0x5f => 0x96f2, + 0x60 => 0x834f, 0x61 => 0x990c, 0x62 => 0x53e1, 0x63 => 0x55b6, + 0x64 => 0x5b30, 0x65 => 0x5f71, 0x66 => 0x6620, 0x67 => 0x66f3, + 0x68 => 0x6804, 0x69 => 0x6c38, 0x6a => 0x6cf3, 0x6b => 0x6d29, + 0x6c => 0x745b, 0x6d => 0x76c8, 0x6e => 0x7a4e, 0x6f => 0x9834, + 0x70 => 0x82f1, 0x71 => 0x885b, 0x72 => 0x8a60, 0x73 => 0x92ed, + 0x74 => 0x6db2, 0x75 => 0x75ab, 0x76 => 0x76ca, 0x77 => 0x99c5, + 0x78 => 0x60a6, 0x79 => 0x8b01, 0x7a => 0x8d8a, 0x7b => 0x95b2, + 0x7c => 0x698e, 0x7d => 0x53ad, 0x7e => 0x5186, 0x80 => 0x5712, + 0x81 => 0x5830, 0x82 => 0x5944, 0x83 => 0x5bb4, 0x84 => 0x5ef6, + 0x85 => 0x6028, 0x86 => 0x63a9, 0x87 => 0x63f4, 0x88 => 0x6cbf, + 0x89 => 0x6f14, 0x8a => 0x708e, 0x8b => 0x7114, 0x8c => 0x7159, + 0x8d => 0x71d5, 0x8e => 0x733f, 0x8f => 0x7e01, 0x90 => 0x8276, + 0x91 => 0x82d1, 0x92 => 0x8597, 0x93 => 0x9060, 0x94 => 0x925b, + 0x95 => 0x9d1b, 0x96 => 0x5869, 0x97 => 0x65bc, 0x98 => 0x6c5a, + 0x99 => 0x7525, 0x9a => 0x51f9, 0x9b => 0x592e, 0x9c => 0x5965, + 0x9d => 0x5f80, 0x9e => 0x5fdc, 0x9f => 0x62bc, 0xa0 => 0x65fa, + 0xa1 => 0x6a2a, 0xa2 => 0x6b27, 0xa3 => 0x6bb4, 0xa4 => 0x738b, + 0xa5 => 0x7fc1, 0xa6 => 0x8956, 0xa7 => 0x9d2c, 0xa8 => 0x9d0e, + 0xa9 => 0x9ec4, 0xaa => 0x5ca1, 0xab => 0x6c96, 0xac => 0x837b, + 0xad => 0x5104, 0xae => 0x5c4b, 0xaf => 0x61b6, 0xb0 => 0x81c6, + 0xb1 => 0x6876, 0xb2 => 0x7261, 0xb3 => 0x4e59, 0xb4 => 0x4ffa, + 0xb5 => 0x5378, 0xb6 => 0x6069, 0xb7 => 0x6e29, 0xb8 => 0x7a4f, + 0xb9 => 0x97f3, 0xba => 0x4e0b, 0xbb => 0x5316, 0xbc => 0x4eee, + 0xbd => 0x4f55, 0xbe => 0x4f3d, 0xbf => 0x4fa1, 0xc0 => 0x4f73, + 0xc1 => 0x52a0, 0xc2 => 0x53ef, 0xc3 => 0x5609, 0xc4 => 0x590f, + 0xc5 => 0x5ac1, 0xc6 => 0x5bb6, 0xc7 => 0x5be1, 0xc8 => 0x79d1, + 0xc9 => 0x6687, 0xca => 0x679c, 0xcb => 0x67b6, 0xcc => 0x6b4c, + 0xcd => 0x6cb3, 0xce => 0x706b, 0xcf => 0x73c2, 0xd0 => 0x798d, + 0xd1 => 0x79be, 0xd2 => 0x7a3c, 0xd3 => 0x7b87, 0xd4 => 0x82b1, + 0xd5 => 0x82db, 0xd6 => 0x8304, 0xd7 => 0x8377, 0xd8 => 0x83ef, + 0xd9 => 0x83d3, 0xda => 0x8766, 0xdb => 0x8ab2, 0xdc => 0x5629, + 0xdd => 0x8ca8, 0xde => 0x8fe6, 0xdf => 0x904e, 0xe0 => 0x971e, + 0xe1 => 0x868a, 0xe2 => 0x4fc4, 0xe3 => 0x5ce8, 0xe4 => 0x6211, + 0xe5 => 0x7259, 0xe6 => 0x753b, 0xe7 => 0x81e5, 0xe8 => 0x82bd, + 0xe9 => 0x86fe, 0xea => 0x8cc0, 0xeb => 0x96c5, 0xec => 0x9913, + 0xed => 0x99d5, 0xee => 0x4ecb, 0xef => 0x4f1a, 0xf0 => 0x89e3, + 0xf1 => 0x56de, 0xf2 => 0x584a, 0xf3 => 0x58ca, 0xf4 => 0x5efb, + 0xf5 => 0x5feb, 0xf6 => 0x602a, 0xf7 => 0x6094, 0xf8 => 0x6062, + 0xf9 => 0x61d0, 0xfa => 0x6212, 0xfb => 0x62d0, 0xfc => 0x6539, + }, + 0x8a => { + 0x40 => 0x9b41, 0x41 => 0x6666, 0x42 => 0x68b0, 0x43 => 0x6d77, + 0x44 => 0x7070, 0x45 => 0x754c, 0x46 => 0x7686, 0x47 => 0x7d75, + 0x48 => 0x82a5, 0x49 => 0x87f9, 0x4a => 0x958b, 0x4b => 0x968e, + 0x4c => 0x8c9d, 0x4d => 0x51f1, 0x4e => 0x52be, 0x4f => 0x5916, + 0x50 => 0x54b3, 0x51 => 0x5bb3, 0x52 => 0x5d16, 0x53 => 0x6168, + 0x54 => 0x6982, 0x55 => 0x6daf, 0x56 => 0x788d, 0x57 => 0x84cb, + 0x58 => 0x8857, 0x59 => 0x8a72, 0x5a => 0x93a7, 0x5b => 0x9ab8, + 0x5c => 0x6d6c, 0x5d => 0x99a8, 0x5e => 0x86d9, 0x5f => 0x57a3, + 0x60 => 0x67ff, 0x61 => 0x86ce, 0x62 => 0x920e, 0x63 => 0x5283, + 0x64 => 0x5687, 0x65 => 0x5404, 0x66 => 0x5ed3, 0x67 => 0x62e1, + 0x68 => 0x64b9, 0x69 => 0x683c, 0x6a => 0x6838, 0x6b => 0x6bbb, + 0x6c => 0x7372, 0x6d => 0x78ba, 0x6e => 0x7a6b, 0x6f => 0x899a, + 0x70 => 0x89d2, 0x71 => 0x8d6b, 0x72 => 0x8f03, 0x73 => 0x90ed, + 0x74 => 0x95a3, 0x75 => 0x9694, 0x76 => 0x9769, 0x77 => 0x5b66, + 0x78 => 0x5cb3, 0x79 => 0x697d, 0x7a => 0x984d, 0x7b => 0x984e, + 0x7c => 0x639b, 0x7d => 0x7b20, 0x7e => 0x6a2b, 0x80 => 0x6a7f, + 0x81 => 0x68b6, 0x82 => 0x9c0d, 0x83 => 0x6f5f, 0x84 => 0x5272, + 0x85 => 0x559d, 0x86 => 0x6070, 0x87 => 0x62ec, 0x88 => 0x6d3b, + 0x89 => 0x6e07, 0x8a => 0x6ed1, 0x8b => 0x845b, 0x8c => 0x8910, + 0x8d => 0x8f44, 0x8e => 0x4e14, 0x8f => 0x9c39, 0x90 => 0x53f6, + 0x91 => 0x691b, 0x92 => 0x6a3a, 0x93 => 0x9784, 0x94 => 0x682a, + 0x95 => 0x515c, 0x96 => 0x7ac3, 0x97 => 0x84b2, 0x98 => 0x91dc, + 0x99 => 0x938c, 0x9a => 0x565b, 0x9b => 0x9d28, 0x9c => 0x6822, + 0x9d => 0x8305, 0x9e => 0x8431, 0x9f => 0x7ca5, 0xa0 => 0x5208, + 0xa1 => 0x82c5, 0xa2 => 0x74e6, 0xa3 => 0x4e7e, 0xa4 => 0x4f83, + 0xa5 => 0x51a0, 0xa6 => 0x5bd2, 0xa7 => 0x520a, 0xa8 => 0x52d8, + 0xa9 => 0x52e7, 0xaa => 0x5dfb, 0xab => 0x559a, 0xac => 0x582a, + 0xad => 0x59e6, 0xae => 0x5b8c, 0xaf => 0x5b98, 0xb0 => 0x5bdb, + 0xb1 => 0x5e72, 0xb2 => 0x5e79, 0xb3 => 0x60a3, 0xb4 => 0x611f, + 0xb5 => 0x6163, 0xb6 => 0x61be, 0xb7 => 0x63db, 0xb8 => 0x6562, + 0xb9 => 0x67d1, 0xba => 0x6853, 0xbb => 0x68fa, 0xbc => 0x6b3e, + 0xbd => 0x6b53, 0xbe => 0x6c57, 0xbf => 0x6f22, 0xc0 => 0x6f97, + 0xc1 => 0x6f45, 0xc2 => 0x74b0, 0xc3 => 0x7518, 0xc4 => 0x76e3, + 0xc5 => 0x770b, 0xc6 => 0x7aff, 0xc7 => 0x7ba1, 0xc8 => 0x7c21, + 0xc9 => 0x7de9, 0xca => 0x7f36, 0xcb => 0x7ff0, 0xcc => 0x809d, + 0xcd => 0x8266, 0xce => 0x839e, 0xcf => 0x89b3, 0xd0 => 0x8acc, + 0xd1 => 0x8cab, 0xd2 => 0x9084, 0xd3 => 0x9451, 0xd4 => 0x9593, + 0xd5 => 0x9591, 0xd6 => 0x95a2, 0xd7 => 0x9665, 0xd8 => 0x97d3, + 0xd9 => 0x9928, 0xda => 0x8218, 0xdb => 0x4e38, 0xdc => 0x542b, + 0xdd => 0x5cb8, 0xde => 0x5dcc, 0xdf => 0x73a9, 0xe0 => 0x764c, + 0xe1 => 0x773c, 0xe2 => 0x5ca9, 0xe3 => 0x7feb, 0xe4 => 0x8d0b, + 0xe5 => 0x96c1, 0xe6 => 0x9811, 0xe7 => 0x9854, 0xe8 => 0x9858, + 0xe9 => 0x4f01, 0xea => 0x4f0e, 0xeb => 0x5371, 0xec => 0x559c, + 0xed => 0x5668, 0xee => 0x57fa, 0xef => 0x5947, 0xf0 => 0x5b09, + 0xf1 => 0x5bc4, 0xf2 => 0x5c90, 0xf3 => 0x5e0c, 0xf4 => 0x5e7e, + 0xf5 => 0x5fcc, 0xf6 => 0x63ee, 0xf7 => 0x673a, 0xf8 => 0x65d7, + 0xf9 => 0x65e2, 0xfa => 0x671f, 0xfb => 0x68cb, 0xfc => 0x68c4, + }, + 0x8b => { + 0x40 => 0x6a5f, 0x41 => 0x5e30, 0x42 => 0x6bc5, 0x43 => 0x6c17, + 0x44 => 0x6c7d, 0x45 => 0x757f, 0x46 => 0x7948, 0x47 => 0x5b63, + 0x48 => 0x7a00, 0x49 => 0x7d00, 0x4a => 0x5fbd, 0x4b => 0x898f, + 0x4c => 0x8a18, 0x4d => 0x8cb4, 0x4e => 0x8d77, 0x4f => 0x8ecc, + 0x50 => 0x8f1d, 0x51 => 0x98e2, 0x52 => 0x9a0e, 0x53 => 0x9b3c, + 0x54 => 0x4e80, 0x55 => 0x507d, 0x56 => 0x5100, 0x57 => 0x5993, + 0x58 => 0x5b9c, 0x59 => 0x622f, 0x5a => 0x6280, 0x5b => 0x64ec, + 0x5c => 0x6b3a, 0x5d => 0x72a0, 0x5e => 0x7591, 0x5f => 0x7947, + 0x60 => 0x7fa9, 0x61 => 0x87fb, 0x62 => 0x8abc, 0x63 => 0x8b70, + 0x64 => 0x63ac, 0x65 => 0x83ca, 0x66 => 0x97a0, 0x67 => 0x5409, + 0x68 => 0x5403, 0x69 => 0x55ab, 0x6a => 0x6854, 0x6b => 0x6a58, + 0x6c => 0x8a70, 0x6d => 0x7827, 0x6e => 0x6775, 0x6f => 0x9ecd, + 0x70 => 0x5374, 0x71 => 0x5ba2, 0x72 => 0x811a, 0x73 => 0x8650, + 0x74 => 0x9006, 0x75 => 0x4e18, 0x76 => 0x4e45, 0x77 => 0x4ec7, + 0x78 => 0x4f11, 0x79 => 0x53ca, 0x7a => 0x5438, 0x7b => 0x5bae, + 0x7c => 0x5f13, 0x7d => 0x6025, 0x7e => 0x6551, 0x80 => 0x673d, + 0x81 => 0x6c42, 0x82 => 0x6c72, 0x83 => 0x6ce3, 0x84 => 0x7078, + 0x85 => 0x7403, 0x86 => 0x7a76, 0x87 => 0x7aae, 0x88 => 0x7b08, + 0x89 => 0x7d1a, 0x8a => 0x7cfe, 0x8b => 0x7d66, 0x8c => 0x65e7, + 0x8d => 0x725b, 0x8e => 0x53bb, 0x8f => 0x5c45, 0x90 => 0x5de8, + 0x91 => 0x62d2, 0x92 => 0x62e0, 0x93 => 0x6319, 0x94 => 0x6e20, + 0x95 => 0x865a, 0x96 => 0x8a31, 0x97 => 0x8ddd, 0x98 => 0x92f8, + 0x99 => 0x6f01, 0x9a => 0x79a6, 0x9b => 0x9b5a, 0x9c => 0x4ea8, + 0x9d => 0x4eab, 0x9e => 0x4eac, 0x9f => 0x4f9b, 0xa0 => 0x4fa0, + 0xa1 => 0x50d1, 0xa2 => 0x5147, 0xa3 => 0x7af6, 0xa4 => 0x5171, + 0xa5 => 0x51f6, 0xa6 => 0x5354, 0xa7 => 0x5321, 0xa8 => 0x537f, + 0xa9 => 0x53eb, 0xaa => 0x55ac, 0xab => 0x5883, 0xac => 0x5ce1, + 0xad => 0x5f37, 0xae => 0x5f4a, 0xaf => 0x602f, 0xb0 => 0x6050, + 0xb1 => 0x606d, 0xb2 => 0x631f, 0xb3 => 0x6559, 0xb4 => 0x6a4b, + 0xb5 => 0x6cc1, 0xb6 => 0x72c2, 0xb7 => 0x72ed, 0xb8 => 0x77ef, + 0xb9 => 0x80f8, 0xba => 0x8105, 0xbb => 0x8208, 0xbc => 0x854e, + 0xbd => 0x90f7, 0xbe => 0x93e1, 0xbf => 0x97ff, 0xc0 => 0x9957, + 0xc1 => 0x9a5a, 0xc2 => 0x4ef0, 0xc3 => 0x51dd, 0xc4 => 0x5c2d, + 0xc5 => 0x6681, 0xc6 => 0x696d, 0xc7 => 0x5c40, 0xc8 => 0x66f2, + 0xc9 => 0x6975, 0xca => 0x7389, 0xcb => 0x6850, 0xcc => 0x7c81, + 0xcd => 0x50c5, 0xce => 0x52e4, 0xcf => 0x5747, 0xd0 => 0x5dfe, + 0xd1 => 0x9326, 0xd2 => 0x65a4, 0xd3 => 0x6b23, 0xd4 => 0x6b3d, + 0xd5 => 0x7434, 0xd6 => 0x7981, 0xd7 => 0x79bd, 0xd8 => 0x7b4b, + 0xd9 => 0x7dca, 0xda => 0x82b9, 0xdb => 0x83cc, 0xdc => 0x887f, + 0xdd => 0x895f, 0xde => 0x8b39, 0xdf => 0x8fd1, 0xe0 => 0x91d1, + 0xe1 => 0x541f, 0xe2 => 0x9280, 0xe3 => 0x4e5d, 0xe4 => 0x5036, + 0xe5 => 0x53e5, 0xe6 => 0x533a, 0xe7 => 0x72d7, 0xe8 => 0x7396, + 0xe9 => 0x77e9, 0xea => 0x82e6, 0xeb => 0x8eaf, 0xec => 0x99c6, + 0xed => 0x99c8, 0xee => 0x99d2, 0xef => 0x5177, 0xf0 => 0x611a, + 0xf1 => 0x865e, 0xf2 => 0x55b0, 0xf3 => 0x7a7a, 0xf4 => 0x5076, + 0xf5 => 0x5bd3, 0xf6 => 0x9047, 0xf7 => 0x9685, 0xf8 => 0x4e32, + 0xf9 => 0x6adb, 0xfa => 0x91e7, 0xfb => 0x5c51, 0xfc => 0x5c48, + }, + 0x8c => { + 0x40 => 0x6398, 0x41 => 0x7a9f, 0x42 => 0x6c93, 0x43 => 0x9774, + 0x44 => 0x8f61, 0x45 => 0x7aaa, 0x46 => 0x718a, 0x47 => 0x9688, + 0x48 => 0x7c82, 0x49 => 0x6817, 0x4a => 0x7e70, 0x4b => 0x6851, + 0x4c => 0x936c, 0x4d => 0x52f2, 0x4e => 0x541b, 0x4f => 0x85ab, + 0x50 => 0x8a13, 0x51 => 0x7fa4, 0x52 => 0x8ecd, 0x53 => 0x90e1, + 0x54 => 0x5366, 0x55 => 0x8888, 0x56 => 0x7941, 0x57 => 0x4fc2, + 0x58 => 0x50be, 0x59 => 0x5211, 0x5a => 0x5144, 0x5b => 0x5553, + 0x5c => 0x572d, 0x5d => 0x73ea, 0x5e => 0x578b, 0x5f => 0x5951, + 0x60 => 0x5f62, 0x61 => 0x5f84, 0x62 => 0x6075, 0x63 => 0x6176, + 0x64 => 0x6167, 0x65 => 0x61a9, 0x66 => 0x63b2, 0x67 => 0x643a, + 0x68 => 0x656c, 0x69 => 0x666f, 0x6a => 0x6842, 0x6b => 0x6e13, + 0x6c => 0x7566, 0x6d => 0x7a3d, 0x6e => 0x7cfb, 0x6f => 0x7d4c, + 0x70 => 0x7d99, 0x71 => 0x7e4b, 0x72 => 0x7f6b, 0x73 => 0x830e, + 0x74 => 0x834a, 0x75 => 0x86cd, 0x76 => 0x8a08, 0x77 => 0x8a63, + 0x78 => 0x8b66, 0x79 => 0x8efd, 0x7a => 0x981a, 0x7b => 0x9d8f, + 0x7c => 0x82b8, 0x7d => 0x8fce, 0x7e => 0x9be8, 0x80 => 0x5287, + 0x81 => 0x621f, 0x82 => 0x6483, 0x83 => 0x6fc0, 0x84 => 0x9699, + 0x85 => 0x6841, 0x86 => 0x5091, 0x87 => 0x6b20, 0x88 => 0x6c7a, + 0x89 => 0x6f54, 0x8a => 0x7a74, 0x8b => 0x7d50, 0x8c => 0x8840, + 0x8d => 0x8a23, 0x8e => 0x6708, 0x8f => 0x4ef6, 0x90 => 0x5039, + 0x91 => 0x5026, 0x92 => 0x5065, 0x93 => 0x517c, 0x94 => 0x5238, + 0x95 => 0x5263, 0x96 => 0x55a7, 0x97 => 0x570f, 0x98 => 0x5805, + 0x99 => 0x5acc, 0x9a => 0x5efa, 0x9b => 0x61b2, 0x9c => 0x61f8, + 0x9d => 0x62f3, 0x9e => 0x6372, 0x9f => 0x691c, 0xa0 => 0x6a29, + 0xa1 => 0x727d, 0xa2 => 0x72ac, 0xa3 => 0x732e, 0xa4 => 0x7814, + 0xa5 => 0x786f, 0xa6 => 0x7d79, 0xa7 => 0x770c, 0xa8 => 0x80a9, + 0xa9 => 0x898b, 0xaa => 0x8b19, 0xab => 0x8ce2, 0xac => 0x8ed2, + 0xad => 0x9063, 0xae => 0x9375, 0xaf => 0x967a, 0xb0 => 0x9855, + 0xb1 => 0x9a13, 0xb2 => 0x9e78, 0xb3 => 0x5143, 0xb4 => 0x539f, + 0xb5 => 0x53b3, 0xb6 => 0x5e7b, 0xb7 => 0x5f26, 0xb8 => 0x6e1b, + 0xb9 => 0x6e90, 0xba => 0x7384, 0xbb => 0x73fe, 0xbc => 0x7d43, + 0xbd => 0x8237, 0xbe => 0x8a00, 0xbf => 0x8afa, 0xc0 => 0x9650, + 0xc1 => 0x4e4e, 0xc2 => 0x500b, 0xc3 => 0x53e4, 0xc4 => 0x547c, + 0xc5 => 0x56fa, 0xc6 => 0x59d1, 0xc7 => 0x5b64, 0xc8 => 0x5df1, + 0xc9 => 0x5eab, 0xca => 0x5f27, 0xcb => 0x6238, 0xcc => 0x6545, + 0xcd => 0x67af, 0xce => 0x6e56, 0xcf => 0x72d0, 0xd0 => 0x7cca, + 0xd1 => 0x88b4, 0xd2 => 0x80a1, 0xd3 => 0x80e1, 0xd4 => 0x83f0, + 0xd5 => 0x864e, 0xd6 => 0x8a87, 0xd7 => 0x8de8, 0xd8 => 0x9237, + 0xd9 => 0x96c7, 0xda => 0x9867, 0xdb => 0x9f13, 0xdc => 0x4e94, + 0xdd => 0x4e92, 0xde => 0x4f0d, 0xdf => 0x5348, 0xe0 => 0x5449, + 0xe1 => 0x543e, 0xe2 => 0x5a2f, 0xe3 => 0x5f8c, 0xe4 => 0x5fa1, + 0xe5 => 0x609f, 0xe6 => 0x68a7, 0xe7 => 0x6a8e, 0xe8 => 0x745a, + 0xe9 => 0x7881, 0xea => 0x8a9e, 0xeb => 0x8aa4, 0xec => 0x8b77, + 0xed => 0x9190, 0xee => 0x4e5e, 0xef => 0x9bc9, 0xf0 => 0x4ea4, + 0xf1 => 0x4f7c, 0xf2 => 0x4faf, 0xf3 => 0x5019, 0xf4 => 0x5016, + 0xf5 => 0x5149, 0xf6 => 0x516c, 0xf7 => 0x529f, 0xf8 => 0x52b9, + 0xf9 => 0x52fe, 0xfa => 0x539a, 0xfb => 0x53e3, 0xfc => 0x5411, + }, + 0x8d => { + 0x40 => 0x540e, 0x41 => 0x5589, 0x42 => 0x5751, 0x43 => 0x57a2, + 0x44 => 0x597d, 0x45 => 0x5b54, 0x46 => 0x5b5d, 0x47 => 0x5b8f, + 0x48 => 0x5de5, 0x49 => 0x5de7, 0x4a => 0x5df7, 0x4b => 0x5e78, + 0x4c => 0x5e83, 0x4d => 0x5e9a, 0x4e => 0x5eb7, 0x4f => 0x5f18, + 0x50 => 0x6052, 0x51 => 0x614c, 0x52 => 0x6297, 0x53 => 0x62d8, + 0x54 => 0x63a7, 0x55 => 0x653b, 0x56 => 0x6602, 0x57 => 0x6643, + 0x58 => 0x66f4, 0x59 => 0x676d, 0x5a => 0x6821, 0x5b => 0x6897, + 0x5c => 0x69cb, 0x5d => 0x6c5f, 0x5e => 0x6d2a, 0x5f => 0x6d69, + 0x60 => 0x6e2f, 0x61 => 0x6e9d, 0x62 => 0x7532, 0x63 => 0x7687, + 0x64 => 0x786c, 0x65 => 0x7a3f, 0x66 => 0x7ce0, 0x67 => 0x7d05, + 0x68 => 0x7d18, 0x69 => 0x7d5e, 0x6a => 0x7db1, 0x6b => 0x8015, + 0x6c => 0x8003, 0x6d => 0x80af, 0x6e => 0x80b1, 0x6f => 0x8154, + 0x70 => 0x818f, 0x71 => 0x822a, 0x72 => 0x8352, 0x73 => 0x884c, + 0x74 => 0x8861, 0x75 => 0x8b1b, 0x76 => 0x8ca2, 0x77 => 0x8cfc, + 0x78 => 0x90ca, 0x79 => 0x9175, 0x7a => 0x9271, 0x7b => 0x783f, + 0x7c => 0x92fc, 0x7d => 0x95a4, 0x7e => 0x964d, 0x80 => 0x9805, + 0x81 => 0x9999, 0x82 => 0x9ad8, 0x83 => 0x9d3b, 0x84 => 0x525b, + 0x85 => 0x52ab, 0x86 => 0x53f7, 0x87 => 0x5408, 0x88 => 0x58d5, + 0x89 => 0x62f7, 0x8a => 0x6fe0, 0x8b => 0x8c6a, 0x8c => 0x8f5f, + 0x8d => 0x9eb9, 0x8e => 0x514b, 0x8f => 0x523b, 0x90 => 0x544a, + 0x91 => 0x56fd, 0x92 => 0x7a40, 0x93 => 0x9177, 0x94 => 0x9d60, + 0x95 => 0x9ed2, 0x96 => 0x7344, 0x97 => 0x6f09, 0x98 => 0x8170, + 0x99 => 0x7511, 0x9a => 0x5ffd, 0x9b => 0x60da, 0x9c => 0x9aa8, + 0x9d => 0x72db, 0x9e => 0x8fbc, 0x9f => 0x6b64, 0xa0 => 0x9803, + 0xa1 => 0x4eca, 0xa2 => 0x56f0, 0xa3 => 0x5764, 0xa4 => 0x58be, + 0xa5 => 0x5a5a, 0xa6 => 0x6068, 0xa7 => 0x61c7, 0xa8 => 0x660f, + 0xa9 => 0x6606, 0xaa => 0x6839, 0xab => 0x68b1, 0xac => 0x6df7, + 0xad => 0x75d5, 0xae => 0x7d3a, 0xaf => 0x826e, 0xb0 => 0x9b42, + 0xb1 => 0x4e9b, 0xb2 => 0x4f50, 0xb3 => 0x53c9, 0xb4 => 0x5506, + 0xb5 => 0x5d6f, 0xb6 => 0x5de6, 0xb7 => 0x5dee, 0xb8 => 0x67fb, + 0xb9 => 0x6c99, 0xba => 0x7473, 0xbb => 0x7802, 0xbc => 0x8a50, + 0xbd => 0x9396, 0xbe => 0x88df, 0xbf => 0x5750, 0xc0 => 0x5ea7, + 0xc1 => 0x632b, 0xc2 => 0x50b5, 0xc3 => 0x50ac, 0xc4 => 0x518d, + 0xc5 => 0x6700, 0xc6 => 0x54c9, 0xc7 => 0x585e, 0xc8 => 0x59bb, + 0xc9 => 0x5bb0, 0xca => 0x5f69, 0xcb => 0x624d, 0xcc => 0x63a1, + 0xcd => 0x683d, 0xce => 0x6b73, 0xcf => 0x6e08, 0xd0 => 0x707d, + 0xd1 => 0x91c7, 0xd2 => 0x7280, 0xd3 => 0x7815, 0xd4 => 0x7826, + 0xd5 => 0x796d, 0xd6 => 0x658e, 0xd7 => 0x7d30, 0xd8 => 0x83dc, + 0xd9 => 0x88c1, 0xda => 0x8f09, 0xdb => 0x969b, 0xdc => 0x5264, + 0xdd => 0x5728, 0xde => 0x6750, 0xdf => 0x7f6a, 0xe0 => 0x8ca1, + 0xe1 => 0x51b4, 0xe2 => 0x5742, 0xe3 => 0x962a, 0xe4 => 0x583a, + 0xe5 => 0x698a, 0xe6 => 0x80b4, 0xe7 => 0x54b2, 0xe8 => 0x5d0e, + 0xe9 => 0x57fc, 0xea => 0x7895, 0xeb => 0x9dfa, 0xec => 0x4f5c, + 0xed => 0x524a, 0xee => 0x548b, 0xef => 0x643e, 0xf0 => 0x6628, + 0xf1 => 0x6714, 0xf2 => 0x67f5, 0xf3 => 0x7a84, 0xf4 => 0x7b56, + 0xf5 => 0x7d22, 0xf6 => 0x932f, 0xf7 => 0x685c, 0xf8 => 0x9bad, + 0xf9 => 0x7b39, 0xfa => 0x5319, 0xfb => 0x518a, 0xfc => 0x5237, + }, + 0x8e => { + 0x40 => 0x5bdf, 0x41 => 0x62f6, 0x42 => 0x64ae, 0x43 => 0x64e6, + 0x44 => 0x672d, 0x45 => 0x6bba, 0x46 => 0x85a9, 0x47 => 0x96d1, + 0x48 => 0x7690, 0x49 => 0x9bd6, 0x4a => 0x634c, 0x4b => 0x9306, + 0x4c => 0x9bab, 0x4d => 0x76bf, 0x4e => 0x6652, 0x4f => 0x4e09, + 0x50 => 0x5098, 0x51 => 0x53c2, 0x52 => 0x5c71, 0x53 => 0x60e8, + 0x54 => 0x6492, 0x55 => 0x6563, 0x56 => 0x685f, 0x57 => 0x71e6, + 0x58 => 0x73ca, 0x59 => 0x7523, 0x5a => 0x7b97, 0x5b => 0x7e82, + 0x5c => 0x8695, 0x5d => 0x8b83, 0x5e => 0x8cdb, 0x5f => 0x9178, + 0x60 => 0x9910, 0x61 => 0x65ac, 0x62 => 0x66ab, 0x63 => 0x6b8b, + 0x64 => 0x4ed5, 0x65 => 0x4ed4, 0x66 => 0x4f3a, 0x67 => 0x4f7f, + 0x68 => 0x523a, 0x69 => 0x53f8, 0x6a => 0x53f2, 0x6b => 0x55e3, + 0x6c => 0x56db, 0x6d => 0x58eb, 0x6e => 0x59cb, 0x6f => 0x59c9, + 0x70 => 0x59ff, 0x71 => 0x5b50, 0x72 => 0x5c4d, 0x73 => 0x5e02, + 0x74 => 0x5e2b, 0x75 => 0x5fd7, 0x76 => 0x601d, 0x77 => 0x6307, + 0x78 => 0x652f, 0x79 => 0x5b5c, 0x7a => 0x65af, 0x7b => 0x65bd, + 0x7c => 0x65e8, 0x7d => 0x679d, 0x7e => 0x6b62, 0x80 => 0x6b7b, + 0x81 => 0x6c0f, 0x82 => 0x7345, 0x83 => 0x7949, 0x84 => 0x79c1, + 0x85 => 0x7cf8, 0x86 => 0x7d19, 0x87 => 0x7d2b, 0x88 => 0x80a2, + 0x89 => 0x8102, 0x8a => 0x81f3, 0x8b => 0x8996, 0x8c => 0x8a5e, + 0x8d => 0x8a69, 0x8e => 0x8a66, 0x8f => 0x8a8c, 0x90 => 0x8aee, + 0x91 => 0x8cc7, 0x92 => 0x8cdc, 0x93 => 0x96cc, 0x94 => 0x98fc, + 0x95 => 0x6b6f, 0x96 => 0x4e8b, 0x97 => 0x4f3c, 0x98 => 0x4f8d, + 0x99 => 0x5150, 0x9a => 0x5b57, 0x9b => 0x5bfa, 0x9c => 0x6148, + 0x9d => 0x6301, 0x9e => 0x6642, 0x9f => 0x6b21, 0xa0 => 0x6ecb, + 0xa1 => 0x6cbb, 0xa2 => 0x723e, 0xa3 => 0x74bd, 0xa4 => 0x75d4, + 0xa5 => 0x78c1, 0xa6 => 0x793a, 0xa7 => 0x800c, 0xa8 => 0x8033, + 0xa9 => 0x81ea, 0xaa => 0x8494, 0xab => 0x8f9e, 0xac => 0x6c50, + 0xad => 0x9e7f, 0xae => 0x5f0f, 0xaf => 0x8b58, 0xb0 => 0x9d2b, + 0xb1 => 0x7afa, 0xb2 => 0x8ef8, 0xb3 => 0x5b8d, 0xb4 => 0x96eb, + 0xb5 => 0x4e03, 0xb6 => 0x53f1, 0xb7 => 0x57f7, 0xb8 => 0x5931, + 0xb9 => 0x5ac9, 0xba => 0x5ba4, 0xbb => 0x6089, 0xbc => 0x6e7f, + 0xbd => 0x6f06, 0xbe => 0x75be, 0xbf => 0x8cea, 0xc0 => 0x5b9f, + 0xc1 => 0x8500, 0xc2 => 0x7be0, 0xc3 => 0x5072, 0xc4 => 0x67f4, + 0xc5 => 0x829d, 0xc6 => 0x5c61, 0xc7 => 0x854a, 0xc8 => 0x7e1e, + 0xc9 => 0x820e, 0xca => 0x5199, 0xcb => 0x5c04, 0xcc => 0x6368, + 0xcd => 0x8d66, 0xce => 0x659c, 0xcf => 0x716e, 0xd0 => 0x793e, + 0xd1 => 0x7d17, 0xd2 => 0x8005, 0xd3 => 0x8b1d, 0xd4 => 0x8eca, + 0xd5 => 0x906e, 0xd6 => 0x86c7, 0xd7 => 0x90aa, 0xd8 => 0x501f, + 0xd9 => 0x52fa, 0xda => 0x5c3a, 0xdb => 0x6753, 0xdc => 0x707c, + 0xdd => 0x7235, 0xde => 0x914c, 0xdf => 0x91c8, 0xe0 => 0x932b, + 0xe1 => 0x82e5, 0xe2 => 0x5bc2, 0xe3 => 0x5f31, 0xe4 => 0x60f9, + 0xe5 => 0x4e3b, 0xe6 => 0x53d6, 0xe7 => 0x5b88, 0xe8 => 0x624b, + 0xe9 => 0x6731, 0xea => 0x6b8a, 0xeb => 0x72e9, 0xec => 0x73e0, + 0xed => 0x7a2e, 0xee => 0x816b, 0xef => 0x8da3, 0xf0 => 0x9152, + 0xf1 => 0x9996, 0xf2 => 0x5112, 0xf3 => 0x53d7, 0xf4 => 0x546a, + 0xf5 => 0x5bff, 0xf6 => 0x6388, 0xf7 => 0x6a39, 0xf8 => 0x7dac, + 0xf9 => 0x9700, 0xfa => 0x56da, 0xfb => 0x53ce, 0xfc => 0x5468, + }, + 0x8f => { + 0x40 => 0x5b97, 0x41 => 0x5c31, 0x42 => 0x5dde, 0x43 => 0x4fee, + 0x44 => 0x6101, 0x45 => 0x62fe, 0x46 => 0x6d32, 0x47 => 0x79c0, + 0x48 => 0x79cb, 0x49 => 0x7d42, 0x4a => 0x7e4d, 0x4b => 0x7fd2, + 0x4c => 0x81ed, 0x4d => 0x821f, 0x4e => 0x8490, 0x4f => 0x8846, + 0x50 => 0x8972, 0x51 => 0x8b90, 0x52 => 0x8e74, 0x53 => 0x8f2f, + 0x54 => 0x9031, 0x55 => 0x914b, 0x56 => 0x916c, 0x57 => 0x96c6, + 0x58 => 0x919c, 0x59 => 0x4ec0, 0x5a => 0x4f4f, 0x5b => 0x5145, + 0x5c => 0x5341, 0x5d => 0x5f93, 0x5e => 0x620e, 0x5f => 0x67d4, + 0x60 => 0x6c41, 0x61 => 0x6e0b, 0x62 => 0x7363, 0x63 => 0x7e26, + 0x64 => 0x91cd, 0x65 => 0x9283, 0x66 => 0x53d4, 0x67 => 0x5919, + 0x68 => 0x5bbf, 0x69 => 0x6dd1, 0x6a => 0x795d, 0x6b => 0x7e2e, + 0x6c => 0x7c9b, 0x6d => 0x587e, 0x6e => 0x719f, 0x6f => 0x51fa, + 0x70 => 0x8853, 0x71 => 0x8ff0, 0x72 => 0x4fca, 0x73 => 0x5cfb, + 0x74 => 0x6625, 0x75 => 0x77ac, 0x76 => 0x7ae3, 0x77 => 0x821c, + 0x78 => 0x99ff, 0x79 => 0x51c6, 0x7a => 0x5faa, 0x7b => 0x65ec, + 0x7c => 0x696f, 0x7d => 0x6b89, 0x7e => 0x6df3, 0x80 => 0x6e96, + 0x81 => 0x6f64, 0x82 => 0x76fe, 0x83 => 0x7d14, 0x84 => 0x5de1, + 0x85 => 0x9075, 0x86 => 0x9187, 0x87 => 0x9806, 0x88 => 0x51e6, + 0x89 => 0x521d, 0x8a => 0x6240, 0x8b => 0x6691, 0x8c => 0x66d9, + 0x8d => 0x6e1a, 0x8e => 0x5eb6, 0x8f => 0x7dd2, 0x90 => 0x7f72, + 0x91 => 0x66f8, 0x92 => 0x85af, 0x93 => 0x85f7, 0x94 => 0x8af8, + 0x95 => 0x52a9, 0x96 => 0x53d9, 0x97 => 0x5973, 0x98 => 0x5e8f, + 0x99 => 0x5f90, 0x9a => 0x6055, 0x9b => 0x92e4, 0x9c => 0x9664, + 0x9d => 0x50b7, 0x9e => 0x511f, 0x9f => 0x52dd, 0xa0 => 0x5320, + 0xa1 => 0x5347, 0xa2 => 0x53ec, 0xa3 => 0x54e8, 0xa4 => 0x5546, + 0xa5 => 0x5531, 0xa6 => 0x5617, 0xa7 => 0x5968, 0xa8 => 0x59be, + 0xa9 => 0x5a3c, 0xaa => 0x5bb5, 0xab => 0x5c06, 0xac => 0x5c0f, + 0xad => 0x5c11, 0xae => 0x5c1a, 0xaf => 0x5e84, 0xb0 => 0x5e8a, + 0xb1 => 0x5ee0, 0xb2 => 0x5f70, 0xb3 => 0x627f, 0xb4 => 0x6284, + 0xb5 => 0x62db, 0xb6 => 0x638c, 0xb7 => 0x6377, 0xb8 => 0x6607, + 0xb9 => 0x660c, 0xba => 0x662d, 0xbb => 0x6676, 0xbc => 0x677e, + 0xbd => 0x68a2, 0xbe => 0x6a1f, 0xbf => 0x6a35, 0xc0 => 0x6cbc, + 0xc1 => 0x6d88, 0xc2 => 0x6e09, 0xc3 => 0x6e58, 0xc4 => 0x713c, + 0xc5 => 0x7126, 0xc6 => 0x7167, 0xc7 => 0x75c7, 0xc8 => 0x7701, + 0xc9 => 0x785d, 0xca => 0x7901, 0xcb => 0x7965, 0xcc => 0x79f0, + 0xcd => 0x7ae0, 0xce => 0x7b11, 0xcf => 0x7ca7, 0xd0 => 0x7d39, + 0xd1 => 0x8096, 0xd2 => 0x83d6, 0xd3 => 0x848b, 0xd4 => 0x8549, + 0xd5 => 0x885d, 0xd6 => 0x88f3, 0xd7 => 0x8a1f, 0xd8 => 0x8a3c, + 0xd9 => 0x8a54, 0xda => 0x8a73, 0xdb => 0x8c61, 0xdc => 0x8cde, + 0xdd => 0x91a4, 0xde => 0x9266, 0xdf => 0x937e, 0xe0 => 0x9418, + 0xe1 => 0x969c, 0xe2 => 0x9798, 0xe3 => 0x4e0a, 0xe4 => 0x4e08, + 0xe5 => 0x4e1e, 0xe6 => 0x4e57, 0xe7 => 0x5197, 0xe8 => 0x5270, + 0xe9 => 0x57ce, 0xea => 0x5834, 0xeb => 0x58cc, 0xec => 0x5b22, + 0xed => 0x5e38, 0xee => 0x60c5, 0xef => 0x64fe, 0xf0 => 0x6761, + 0xf1 => 0x6756, 0xf2 => 0x6d44, 0xf3 => 0x72b6, 0xf4 => 0x7573, + 0xf5 => 0x7a63, 0xf6 => 0x84b8, 0xf7 => 0x8b72, 0xf8 => 0x91b8, + 0xf9 => 0x9320, 0xfa => 0x5631, 0xfb => 0x57f4, 0xfc => 0x98fe, + }, + 0x90 => { + 0x40 => 0x62ed, 0x41 => 0x690d, 0x42 => 0x6b96, 0x43 => 0x71ed, + 0x44 => 0x7e54, 0x45 => 0x8077, 0x46 => 0x8272, 0x47 => 0x89e6, + 0x48 => 0x98df, 0x49 => 0x8755, 0x4a => 0x8fb1, 0x4b => 0x5c3b, + 0x4c => 0x4f38, 0x4d => 0x4fe1, 0x4e => 0x4fb5, 0x4f => 0x5507, + 0x50 => 0x5a20, 0x51 => 0x5bdd, 0x52 => 0x5be9, 0x53 => 0x5fc3, + 0x54 => 0x614e, 0x55 => 0x632f, 0x56 => 0x65b0, 0x57 => 0x664b, + 0x58 => 0x68ee, 0x59 => 0x699b, 0x5a => 0x6d78, 0x5b => 0x6df1, + 0x5c => 0x7533, 0x5d => 0x75b9, 0x5e => 0x771f, 0x5f => 0x795e, + 0x60 => 0x79e6, 0x61 => 0x7d33, 0x62 => 0x81e3, 0x63 => 0x82af, + 0x64 => 0x85aa, 0x65 => 0x89aa, 0x66 => 0x8a3a, 0x67 => 0x8eab, + 0x68 => 0x8f9b, 0x69 => 0x9032, 0x6a => 0x91dd, 0x6b => 0x9707, + 0x6c => 0x4eba, 0x6d => 0x4ec1, 0x6e => 0x5203, 0x6f => 0x5875, + 0x70 => 0x58ec, 0x71 => 0x5c0b, 0x72 => 0x751a, 0x73 => 0x5c3d, + 0x74 => 0x814e, 0x75 => 0x8a0a, 0x76 => 0x8fc5, 0x77 => 0x9663, + 0x78 => 0x976d, 0x79 => 0x7b25, 0x7a => 0x8acf, 0x7b => 0x9808, + 0x7c => 0x9162, 0x7d => 0x56f3, 0x7e => 0x53a8, 0x80 => 0x9017, + 0x81 => 0x5439, 0x82 => 0x5782, 0x83 => 0x5e25, 0x84 => 0x63a8, + 0x85 => 0x6c34, 0x86 => 0x708a, 0x87 => 0x7761, 0x88 => 0x7c8b, + 0x89 => 0x7fe0, 0x8a => 0x8870, 0x8b => 0x9042, 0x8c => 0x9154, + 0x8d => 0x9310, 0x8e => 0x9318, 0x8f => 0x968f, 0x90 => 0x745e, + 0x91 => 0x9ac4, 0x92 => 0x5d07, 0x93 => 0x5d69, 0x94 => 0x6570, + 0x95 => 0x67a2, 0x96 => 0x8da8, 0x97 => 0x96db, 0x98 => 0x636e, + 0x99 => 0x6749, 0x9a => 0x6919, 0x9b => 0x83c5, 0x9c => 0x9817, + 0x9d => 0x96c0, 0x9e => 0x88fe, 0x9f => 0x6f84, 0xa0 => 0x647a, + 0xa1 => 0x5bf8, 0xa2 => 0x4e16, 0xa3 => 0x702c, 0xa4 => 0x755d, + 0xa5 => 0x662f, 0xa6 => 0x51c4, 0xa7 => 0x5236, 0xa8 => 0x52e2, + 0xa9 => 0x59d3, 0xaa => 0x5f81, 0xab => 0x6027, 0xac => 0x6210, + 0xad => 0x653f, 0xae => 0x6574, 0xaf => 0x661f, 0xb0 => 0x6674, + 0xb1 => 0x68f2, 0xb2 => 0x6816, 0xb3 => 0x6b63, 0xb4 => 0x6e05, + 0xb5 => 0x7272, 0xb6 => 0x751f, 0xb7 => 0x76db, 0xb8 => 0x7cbe, + 0xb9 => 0x8056, 0xba => 0x58f0, 0xbb => 0x88fd, 0xbc => 0x897f, + 0xbd => 0x8aa0, 0xbe => 0x8a93, 0xbf => 0x8acb, 0xc0 => 0x901d, + 0xc1 => 0x9192, 0xc2 => 0x9752, 0xc3 => 0x9759, 0xc4 => 0x6589, + 0xc5 => 0x7a0e, 0xc6 => 0x8106, 0xc7 => 0x96bb, 0xc8 => 0x5e2d, + 0xc9 => 0x60dc, 0xca => 0x621a, 0xcb => 0x65a5, 0xcc => 0x6614, + 0xcd => 0x6790, 0xce => 0x77f3, 0xcf => 0x7a4d, 0xd0 => 0x7c4d, + 0xd1 => 0x7e3e, 0xd2 => 0x810a, 0xd3 => 0x8cac, 0xd4 => 0x8d64, + 0xd5 => 0x8de1, 0xd6 => 0x8e5f, 0xd7 => 0x78a9, 0xd8 => 0x5207, + 0xd9 => 0x62d9, 0xda => 0x63a5, 0xdb => 0x6442, 0xdc => 0x6298, + 0xdd => 0x8a2d, 0xde => 0x7a83, 0xdf => 0x7bc0, 0xe0 => 0x8aac, + 0xe1 => 0x96ea, 0xe2 => 0x7d76, 0xe3 => 0x820c, 0xe4 => 0x8749, + 0xe5 => 0x4ed9, 0xe6 => 0x5148, 0xe7 => 0x5343, 0xe8 => 0x5360, + 0xe9 => 0x5ba3, 0xea => 0x5c02, 0xeb => 0x5c16, 0xec => 0x5ddd, + 0xed => 0x6226, 0xee => 0x6247, 0xef => 0x64b0, 0xf0 => 0x6813, + 0xf1 => 0x6834, 0xf2 => 0x6cc9, 0xf3 => 0x6d45, 0xf4 => 0x6d17, + 0xf5 => 0x67d3, 0xf6 => 0x6f5c, 0xf7 => 0x714e, 0xf8 => 0x717d, + 0xf9 => 0x65cb, 0xfa => 0x7a7f, 0xfb => 0x7bad, 0xfc => 0x7dda, + }, + 0x91 => { + 0x40 => 0x7e4a, 0x41 => 0x7fa8, 0x42 => 0x817a, 0x43 => 0x821b, + 0x44 => 0x8239, 0x45 => 0x85a6, 0x46 => 0x8a6e, 0x47 => 0x8cce, + 0x48 => 0x8df5, 0x49 => 0x9078, 0x4a => 0x9077, 0x4b => 0x92ad, + 0x4c => 0x9291, 0x4d => 0x9583, 0x4e => 0x9bae, 0x4f => 0x524d, + 0x50 => 0x5584, 0x51 => 0x6f38, 0x52 => 0x7136, 0x53 => 0x5168, + 0x54 => 0x7985, 0x55 => 0x7e55, 0x56 => 0x81b3, 0x57 => 0x7cce, + 0x58 => 0x564c, 0x59 => 0x5851, 0x5a => 0x5ca8, 0x5b => 0x63aa, + 0x5c => 0x66fe, 0x5d => 0x66fd, 0x5e => 0x695a, 0x5f => 0x72d9, + 0x60 => 0x758f, 0x61 => 0x758e, 0x62 => 0x790e, 0x63 => 0x7956, + 0x64 => 0x79df, 0x65 => 0x7c97, 0x66 => 0x7d20, 0x67 => 0x7d44, + 0x68 => 0x8607, 0x69 => 0x8a34, 0x6a => 0x963b, 0x6b => 0x9061, + 0x6c => 0x9f20, 0x6d => 0x50e7, 0x6e => 0x5275, 0x6f => 0x53cc, + 0x70 => 0x53e2, 0x71 => 0x5009, 0x72 => 0x55aa, 0x73 => 0x58ee, + 0x74 => 0x594f, 0x75 => 0x723d, 0x76 => 0x5b8b, 0x77 => 0x5c64, + 0x78 => 0x531d, 0x79 => 0x60e3, 0x7a => 0x60f3, 0x7b => 0x635c, + 0x7c => 0x6383, 0x7d => 0x633f, 0x7e => 0x63bb, 0x80 => 0x64cd, + 0x81 => 0x65e9, 0x82 => 0x66f9, 0x83 => 0x5de3, 0x84 => 0x69cd, + 0x85 => 0x69fd, 0x86 => 0x6f15, 0x87 => 0x71e5, 0x88 => 0x4e89, + 0x89 => 0x75e9, 0x8a => 0x76f8, 0x8b => 0x7a93, 0x8c => 0x7cdf, + 0x8d => 0x7dcf, 0x8e => 0x7d9c, 0x8f => 0x8061, 0x90 => 0x8349, + 0x91 => 0x8358, 0x92 => 0x846c, 0x93 => 0x84bc, 0x94 => 0x85fb, + 0x95 => 0x88c5, 0x96 => 0x8d70, 0x97 => 0x9001, 0x98 => 0x906d, + 0x99 => 0x9397, 0x9a => 0x971c, 0x9b => 0x9a12, 0x9c => 0x50cf, + 0x9d => 0x5897, 0x9e => 0x618e, 0x9f => 0x81d3, 0xa0 => 0x8535, + 0xa1 => 0x8d08, 0xa2 => 0x9020, 0xa3 => 0x4fc3, 0xa4 => 0x5074, + 0xa5 => 0x5247, 0xa6 => 0x5373, 0xa7 => 0x606f, 0xa8 => 0x6349, + 0xa9 => 0x675f, 0xaa => 0x6e2c, 0xab => 0x8db3, 0xac => 0x901f, + 0xad => 0x4fd7, 0xae => 0x5c5e, 0xaf => 0x8cca, 0xb0 => 0x65cf, + 0xb1 => 0x7d9a, 0xb2 => 0x5352, 0xb3 => 0x8896, 0xb4 => 0x5176, + 0xb5 => 0x63c3, 0xb6 => 0x5b58, 0xb7 => 0x5b6b, 0xb8 => 0x5c0a, + 0xb9 => 0x640d, 0xba => 0x6751, 0xbb => 0x905c, 0xbc => 0x4ed6, + 0xbd => 0x591a, 0xbe => 0x592a, 0xbf => 0x6c70, 0xc0 => 0x8a51, + 0xc1 => 0x553e, 0xc2 => 0x5815, 0xc3 => 0x59a5, 0xc4 => 0x60f0, + 0xc5 => 0x6253, 0xc6 => 0x67c1, 0xc7 => 0x8235, 0xc8 => 0x6955, + 0xc9 => 0x9640, 0xca => 0x99c4, 0xcb => 0x9a28, 0xcc => 0x4f53, + 0xcd => 0x5806, 0xce => 0x5bfe, 0xcf => 0x8010, 0xd0 => 0x5cb1, + 0xd1 => 0x5e2f, 0xd2 => 0x5f85, 0xd3 => 0x6020, 0xd4 => 0x614b, + 0xd5 => 0x6234, 0xd6 => 0x66ff, 0xd7 => 0x6cf0, 0xd8 => 0x6ede, + 0xd9 => 0x80ce, 0xda => 0x817f, 0xdb => 0x82d4, 0xdc => 0x888b, + 0xdd => 0x8cb8, 0xde => 0x9000, 0xdf => 0x902e, 0xe0 => 0x968a, + 0xe1 => 0x9edb, 0xe2 => 0x9bdb, 0xe3 => 0x4ee3, 0xe4 => 0x53f0, + 0xe5 => 0x5927, 0xe6 => 0x7b2c, 0xe7 => 0x918d, 0xe8 => 0x984c, + 0xe9 => 0x9df9, 0xea => 0x6edd, 0xeb => 0x7027, 0xec => 0x5353, + 0xed => 0x5544, 0xee => 0x5b85, 0xef => 0x6258, 0xf0 => 0x629e, + 0xf1 => 0x62d3, 0xf2 => 0x6ca2, 0xf3 => 0x6fef, 0xf4 => 0x7422, + 0xf5 => 0x8a17, 0xf6 => 0x9438, 0xf7 => 0x6fc1, 0xf8 => 0x8afe, + 0xf9 => 0x8338, 0xfa => 0x51e7, 0xfb => 0x86f8, 0xfc => 0x53ea, + }, + 0x92 => { + 0x40 => 0x53e9, 0x41 => 0x4f46, 0x42 => 0x9054, 0x43 => 0x8fb0, + 0x44 => 0x596a, 0x45 => 0x8131, 0x46 => 0x5dfd, 0x47 => 0x7aea, + 0x48 => 0x8fbf, 0x49 => 0x68da, 0x4a => 0x8c37, 0x4b => 0x72f8, + 0x4c => 0x9c48, 0x4d => 0x6a3d, 0x4e => 0x8ab0, 0x4f => 0x4e39, + 0x50 => 0x5358, 0x51 => 0x5606, 0x52 => 0x5766, 0x53 => 0x62c5, + 0x54 => 0x63a2, 0x55 => 0x65e6, 0x56 => 0x6b4e, 0x57 => 0x6de1, + 0x58 => 0x6e5b, 0x59 => 0x70ad, 0x5a => 0x77ed, 0x5b => 0x7aef, + 0x5c => 0x7baa, 0x5d => 0x7dbb, 0x5e => 0x803d, 0x5f => 0x80c6, + 0x60 => 0x86cb, 0x61 => 0x8a95, 0x62 => 0x935b, 0x63 => 0x56e3, + 0x64 => 0x58c7, 0x65 => 0x5f3e, 0x66 => 0x65ad, 0x67 => 0x6696, + 0x68 => 0x6a80, 0x69 => 0x6bb5, 0x6a => 0x7537, 0x6b => 0x8ac7, + 0x6c => 0x5024, 0x6d => 0x77e5, 0x6e => 0x5730, 0x6f => 0x5f1b, + 0x70 => 0x6065, 0x71 => 0x667a, 0x72 => 0x6c60, 0x73 => 0x75f4, + 0x74 => 0x7a1a, 0x75 => 0x7f6e, 0x76 => 0x81f4, 0x77 => 0x8718, + 0x78 => 0x9045, 0x79 => 0x99b3, 0x7a => 0x7bc9, 0x7b => 0x755c, + 0x7c => 0x7af9, 0x7d => 0x7b51, 0x7e => 0x84c4, 0x80 => 0x9010, + 0x81 => 0x79e9, 0x82 => 0x7a92, 0x83 => 0x8336, 0x84 => 0x5ae1, + 0x85 => 0x7740, 0x86 => 0x4e2d, 0x87 => 0x4ef2, 0x88 => 0x5b99, + 0x89 => 0x5fe0, 0x8a => 0x62bd, 0x8b => 0x663c, 0x8c => 0x67f1, + 0x8d => 0x6ce8, 0x8e => 0x866b, 0x8f => 0x8877, 0x90 => 0x8a3b, + 0x91 => 0x914e, 0x92 => 0x92f3, 0x93 => 0x99d0, 0x94 => 0x6a17, + 0x95 => 0x7026, 0x96 => 0x732a, 0x97 => 0x82e7, 0x98 => 0x8457, + 0x99 => 0x8caf, 0x9a => 0x4e01, 0x9b => 0x5146, 0x9c => 0x51cb, + 0x9d => 0x558b, 0x9e => 0x5bf5, 0x9f => 0x5e16, 0xa0 => 0x5e33, + 0xa1 => 0x5e81, 0xa2 => 0x5f14, 0xa3 => 0x5f35, 0xa4 => 0x5f6b, + 0xa5 => 0x5fb4, 0xa6 => 0x61f2, 0xa7 => 0x6311, 0xa8 => 0x66a2, + 0xa9 => 0x671d, 0xaa => 0x6f6e, 0xab => 0x7252, 0xac => 0x753a, + 0xad => 0x773a, 0xae => 0x8074, 0xaf => 0x8139, 0xb0 => 0x8178, + 0xb1 => 0x8776, 0xb2 => 0x8abf, 0xb3 => 0x8adc, 0xb4 => 0x8d85, + 0xb5 => 0x8df3, 0xb6 => 0x929a, 0xb7 => 0x9577, 0xb8 => 0x9802, + 0xb9 => 0x9ce5, 0xba => 0x52c5, 0xbb => 0x6357, 0xbc => 0x76f4, + 0xbd => 0x6715, 0xbe => 0x6c88, 0xbf => 0x73cd, 0xc0 => 0x8cc3, + 0xc1 => 0x93ae, 0xc2 => 0x9673, 0xc3 => 0x6d25, 0xc4 => 0x589c, + 0xc5 => 0x690e, 0xc6 => 0x69cc, 0xc7 => 0x8ffd, 0xc8 => 0x939a, + 0xc9 => 0x75db, 0xca => 0x901a, 0xcb => 0x585a, 0xcc => 0x6802, + 0xcd => 0x63b4, 0xce => 0x69fb, 0xcf => 0x4f43, 0xd0 => 0x6f2c, + 0xd1 => 0x67d8, 0xd2 => 0x8fbb, 0xd3 => 0x8526, 0xd4 => 0x7db4, + 0xd5 => 0x9354, 0xd6 => 0x693f, 0xd7 => 0x6f70, 0xd8 => 0x576a, + 0xd9 => 0x58f7, 0xda => 0x5b2c, 0xdb => 0x7d2c, 0xdc => 0x722a, + 0xdd => 0x540a, 0xde => 0x91e3, 0xdf => 0x9db4, 0xe0 => 0x4ead, + 0xe1 => 0x4f4e, 0xe2 => 0x505c, 0xe3 => 0x5075, 0xe4 => 0x5243, + 0xe5 => 0x8c9e, 0xe6 => 0x5448, 0xe7 => 0x5824, 0xe8 => 0x5b9a, + 0xe9 => 0x5e1d, 0xea => 0x5e95, 0xeb => 0x5ead, 0xec => 0x5ef7, + 0xed => 0x5f1f, 0xee => 0x608c, 0xef => 0x62b5, 0xf0 => 0x633a, + 0xf1 => 0x63d0, 0xf2 => 0x68af, 0xf3 => 0x6c40, 0xf4 => 0x7887, + 0xf5 => 0x798e, 0xf6 => 0x7a0b, 0xf7 => 0x7de0, 0xf8 => 0x8247, + 0xf9 => 0x8a02, 0xfa => 0x8ae6, 0xfb => 0x8e44, 0xfc => 0x9013, + }, + 0x93 => { + 0x40 => 0x90b8, 0x41 => 0x912d, 0x42 => 0x91d8, 0x43 => 0x9f0e, + 0x44 => 0x6ce5, 0x45 => 0x6458, 0x46 => 0x64e2, 0x47 => 0x6575, + 0x48 => 0x6ef4, 0x49 => 0x7684, 0x4a => 0x7b1b, 0x4b => 0x9069, + 0x4c => 0x93d1, 0x4d => 0x6eba, 0x4e => 0x54f2, 0x4f => 0x5fb9, + 0x50 => 0x64a4, 0x51 => 0x8f4d, 0x52 => 0x8fed, 0x53 => 0x9244, + 0x54 => 0x5178, 0x55 => 0x586b, 0x56 => 0x5929, 0x57 => 0x5c55, + 0x58 => 0x5e97, 0x59 => 0x6dfb, 0x5a => 0x7e8f, 0x5b => 0x751c, + 0x5c => 0x8cbc, 0x5d => 0x8ee2, 0x5e => 0x985b, 0x5f => 0x70b9, + 0x60 => 0x4f1d, 0x61 => 0x6bbf, 0x62 => 0x6fb1, 0x63 => 0x7530, + 0x64 => 0x96fb, 0x65 => 0x514e, 0x66 => 0x5410, 0x67 => 0x5835, + 0x68 => 0x5857, 0x69 => 0x59ac, 0x6a => 0x5c60, 0x6b => 0x5f92, + 0x6c => 0x6597, 0x6d => 0x675c, 0x6e => 0x6e21, 0x6f => 0x767b, + 0x70 => 0x83df, 0x71 => 0x8ced, 0x72 => 0x9014, 0x73 => 0x90fd, + 0x74 => 0x934d, 0x75 => 0x7825, 0x76 => 0x783a, 0x77 => 0x52aa, + 0x78 => 0x5ea6, 0x79 => 0x571f, 0x7a => 0x5974, 0x7b => 0x6012, + 0x7c => 0x5012, 0x7d => 0x515a, 0x7e => 0x51ac, 0x80 => 0x51cd, + 0x81 => 0x5200, 0x82 => 0x5510, 0x83 => 0x5854, 0x84 => 0x5858, + 0x85 => 0x5957, 0x86 => 0x5b95, 0x87 => 0x5cf6, 0x88 => 0x5d8b, + 0x89 => 0x60bc, 0x8a => 0x6295, 0x8b => 0x642d, 0x8c => 0x6771, + 0x8d => 0x6843, 0x8e => 0x68bc, 0x8f => 0x68df, 0x90 => 0x76d7, + 0x91 => 0x6dd8, 0x92 => 0x6e6f, 0x93 => 0x6d9b, 0x94 => 0x706f, + 0x95 => 0x71c8, 0x96 => 0x5f53, 0x97 => 0x75d8, 0x98 => 0x7977, + 0x99 => 0x7b49, 0x9a => 0x7b54, 0x9b => 0x7b52, 0x9c => 0x7cd6, + 0x9d => 0x7d71, 0x9e => 0x5230, 0x9f => 0x8463, 0xa0 => 0x8569, + 0xa1 => 0x85e4, 0xa2 => 0x8a0e, 0xa3 => 0x8b04, 0xa4 => 0x8c46, + 0xa5 => 0x8e0f, 0xa6 => 0x9003, 0xa7 => 0x900f, 0xa8 => 0x9419, + 0xa9 => 0x9676, 0xaa => 0x982d, 0xab => 0x9a30, 0xac => 0x95d8, + 0xad => 0x50cd, 0xae => 0x52d5, 0xaf => 0x540c, 0xb0 => 0x5802, + 0xb1 => 0x5c0e, 0xb2 => 0x61a7, 0xb3 => 0x649e, 0xb4 => 0x6d1e, + 0xb5 => 0x77b3, 0xb6 => 0x7ae5, 0xb7 => 0x80f4, 0xb8 => 0x8404, + 0xb9 => 0x9053, 0xba => 0x9285, 0xbb => 0x5ce0, 0xbc => 0x9d07, + 0xbd => 0x533f, 0xbe => 0x5f97, 0xbf => 0x5fb3, 0xc0 => 0x6d9c, + 0xc1 => 0x7279, 0xc2 => 0x7763, 0xc3 => 0x79bf, 0xc4 => 0x7be4, + 0xc5 => 0x6bd2, 0xc6 => 0x72ec, 0xc7 => 0x8aad, 0xc8 => 0x6803, + 0xc9 => 0x6a61, 0xca => 0x51f8, 0xcb => 0x7a81, 0xcc => 0x6934, + 0xcd => 0x5c4a, 0xce => 0x9cf6, 0xcf => 0x82eb, 0xd0 => 0x5bc5, + 0xd1 => 0x9149, 0xd2 => 0x701e, 0xd3 => 0x5678, 0xd4 => 0x5c6f, + 0xd5 => 0x60c7, 0xd6 => 0x6566, 0xd7 => 0x6c8c, 0xd8 => 0x8c5a, + 0xd9 => 0x9041, 0xda => 0x9813, 0xdb => 0x5451, 0xdc => 0x66c7, + 0xdd => 0x920d, 0xde => 0x5948, 0xdf => 0x90a3, 0xe0 => 0x5185, + 0xe1 => 0x4e4d, 0xe2 => 0x51ea, 0xe3 => 0x8599, 0xe4 => 0x8b0e, + 0xe5 => 0x7058, 0xe6 => 0x637a, 0xe7 => 0x934b, 0xe8 => 0x6962, + 0xe9 => 0x99b4, 0xea => 0x7e04, 0xeb => 0x7577, 0xec => 0x5357, + 0xed => 0x6960, 0xee => 0x8edf, 0xef => 0x96e3, 0xf0 => 0x6c5d, + 0xf1 => 0x4e8c, 0xf2 => 0x5c3c, 0xf3 => 0x5f10, 0xf4 => 0x8fe9, + 0xf5 => 0x5302, 0xf6 => 0x8cd1, 0xf7 => 0x8089, 0xf8 => 0x8679, + 0xf9 => 0x5eff, 0xfa => 0x65e5, 0xfb => 0x4e73, 0xfc => 0x5165, + }, + 0x94 => { + 0x40 => 0x5982, 0x41 => 0x5c3f, 0x42 => 0x97ee, 0x43 => 0x4efb, + 0x44 => 0x598a, 0x45 => 0x5fcd, 0x46 => 0x8a8d, 0x47 => 0x6fe1, + 0x48 => 0x79b0, 0x49 => 0x7962, 0x4a => 0x5be7, 0x4b => 0x8471, + 0x4c => 0x732b, 0x4d => 0x71b1, 0x4e => 0x5e74, 0x4f => 0x5ff5, + 0x50 => 0x637b, 0x51 => 0x649a, 0x52 => 0x71c3, 0x53 => 0x7c98, + 0x54 => 0x4e43, 0x55 => 0x5efc, 0x56 => 0x4e4b, 0x57 => 0x57dc, + 0x58 => 0x56a2, 0x59 => 0x60a9, 0x5a => 0x6fc3, 0x5b => 0x7d0d, + 0x5c => 0x80fd, 0x5d => 0x8133, 0x5e => 0x81bf, 0x5f => 0x8fb2, + 0x60 => 0x8997, 0x61 => 0x86a4, 0x62 => 0x5df4, 0x63 => 0x628a, + 0x64 => 0x64ad, 0x65 => 0x8987, 0x66 => 0x6777, 0x67 => 0x6ce2, + 0x68 => 0x6d3e, 0x69 => 0x7436, 0x6a => 0x7834, 0x6b => 0x5a46, + 0x6c => 0x7f75, 0x6d => 0x82ad, 0x6e => 0x99ac, 0x6f => 0x4ff3, + 0x70 => 0x5ec3, 0x71 => 0x62dd, 0x72 => 0x6392, 0x73 => 0x6557, + 0x74 => 0x676f, 0x75 => 0x76c3, 0x76 => 0x724c, 0x77 => 0x80cc, + 0x78 => 0x80ba, 0x79 => 0x8f29, 0x7a => 0x914d, 0x7b => 0x500d, + 0x7c => 0x57f9, 0x7d => 0x5a92, 0x7e => 0x6885, 0x80 => 0x6973, + 0x81 => 0x7164, 0x82 => 0x72fd, 0x83 => 0x8cb7, 0x84 => 0x58f2, + 0x85 => 0x8ce0, 0x86 => 0x966a, 0x87 => 0x9019, 0x88 => 0x877f, + 0x89 => 0x79e4, 0x8a => 0x77e7, 0x8b => 0x8429, 0x8c => 0x4f2f, + 0x8d => 0x5265, 0x8e => 0x535a, 0x8f => 0x62cd, 0x90 => 0x67cf, + 0x91 => 0x6cca, 0x92 => 0x767d, 0x93 => 0x7b94, 0x94 => 0x7c95, + 0x95 => 0x8236, 0x96 => 0x8584, 0x97 => 0x8feb, 0x98 => 0x66dd, + 0x99 => 0x6f20, 0x9a => 0x7206, 0x9b => 0x7e1b, 0x9c => 0x83ab, + 0x9d => 0x99c1, 0x9e => 0x9ea6, 0x9f => 0x51fd, 0xa0 => 0x7bb1, + 0xa1 => 0x7872, 0xa2 => 0x7bb8, 0xa3 => 0x8087, 0xa4 => 0x7b48, + 0xa5 => 0x6ae8, 0xa6 => 0x5e61, 0xa7 => 0x808c, 0xa8 => 0x7551, + 0xa9 => 0x7560, 0xaa => 0x516b, 0xab => 0x9262, 0xac => 0x6e8c, + 0xad => 0x767a, 0xae => 0x9197, 0xaf => 0x9aea, 0xb0 => 0x4f10, + 0xb1 => 0x7f70, 0xb2 => 0x629c, 0xb3 => 0x7b4f, 0xb4 => 0x95a5, + 0xb5 => 0x9ce9, 0xb6 => 0x567a, 0xb7 => 0x5859, 0xb8 => 0x86e4, + 0xb9 => 0x96bc, 0xba => 0x4f34, 0xbb => 0x5224, 0xbc => 0x534a, + 0xbd => 0x53cd, 0xbe => 0x53db, 0xbf => 0x5e06, 0xc0 => 0x642c, + 0xc1 => 0x6591, 0xc2 => 0x677f, 0xc3 => 0x6c3e, 0xc4 => 0x6c4e, + 0xc5 => 0x7248, 0xc6 => 0x72af, 0xc7 => 0x73ed, 0xc8 => 0x7554, + 0xc9 => 0x7e41, 0xca => 0x822c, 0xcb => 0x85e9, 0xcc => 0x8ca9, + 0xcd => 0x7bc4, 0xce => 0x91c6, 0xcf => 0x7169, 0xd0 => 0x9812, + 0xd1 => 0x98ef, 0xd2 => 0x633d, 0xd3 => 0x6669, 0xd4 => 0x756a, + 0xd5 => 0x76e4, 0xd6 => 0x78d0, 0xd7 => 0x8543, 0xd8 => 0x86ee, + 0xd9 => 0x532a, 0xda => 0x5351, 0xdb => 0x5426, 0xdc => 0x5983, + 0xdd => 0x5e87, 0xde => 0x5f7c, 0xdf => 0x60b2, 0xe0 => 0x6249, + 0xe1 => 0x6279, 0xe2 => 0x62ab, 0xe3 => 0x6590, 0xe4 => 0x6bd4, + 0xe5 => 0x6ccc, 0xe6 => 0x75b2, 0xe7 => 0x76ae, 0xe8 => 0x7891, + 0xe9 => 0x79d8, 0xea => 0x7dcb, 0xeb => 0x7f77, 0xec => 0x80a5, + 0xed => 0x88ab, 0xee => 0x8ab9, 0xef => 0x8cbb, 0xf0 => 0x907f, + 0xf1 => 0x975e, 0xf2 => 0x98db, 0xf3 => 0x6a0b, 0xf4 => 0x7c38, + 0xf5 => 0x5099, 0xf6 => 0x5c3e, 0xf7 => 0x5fae, 0xf8 => 0x6787, + 0xf9 => 0x6bd8, 0xfa => 0x7435, 0xfb => 0x7709, 0xfc => 0x7f8e, + }, + 0x95 => { + 0x40 => 0x9f3b, 0x41 => 0x67ca, 0x42 => 0x7a17, 0x43 => 0x5339, + 0x44 => 0x758b, 0x45 => 0x9aed, 0x46 => 0x5f66, 0x47 => 0x819d, + 0x48 => 0x83f1, 0x49 => 0x8098, 0x4a => 0x5f3c, 0x4b => 0x5fc5, + 0x4c => 0x7562, 0x4d => 0x7b46, 0x4e => 0x903c, 0x4f => 0x6867, + 0x50 => 0x59eb, 0x51 => 0x5a9b, 0x52 => 0x7d10, 0x53 => 0x767e, + 0x54 => 0x8b2c, 0x55 => 0x4ff5, 0x56 => 0x5f6a, 0x57 => 0x6a19, + 0x58 => 0x6c37, 0x59 => 0x6f02, 0x5a => 0x74e2, 0x5b => 0x7968, + 0x5c => 0x8868, 0x5d => 0x8a55, 0x5e => 0x8c79, 0x5f => 0x5edf, + 0x60 => 0x63cf, 0x61 => 0x75c5, 0x62 => 0x79d2, 0x63 => 0x82d7, + 0x64 => 0x9328, 0x65 => 0x92f2, 0x66 => 0x849c, 0x67 => 0x86ed, + 0x68 => 0x9c2d, 0x69 => 0x54c1, 0x6a => 0x5f6c, 0x6b => 0x658c, + 0x6c => 0x6d5c, 0x6d => 0x7015, 0x6e => 0x8ca7, 0x6f => 0x8cd3, + 0x70 => 0x983b, 0x71 => 0x654f, 0x72 => 0x74f6, 0x73 => 0x4e0d, + 0x74 => 0x4ed8, 0x75 => 0x57e0, 0x76 => 0x592b, 0x77 => 0x5a66, + 0x78 => 0x5bcc, 0x79 => 0x51a8, 0x7a => 0x5e03, 0x7b => 0x5e9c, + 0x7c => 0x6016, 0x7d => 0x6276, 0x7e => 0x6577, 0x80 => 0x65a7, + 0x81 => 0x666e, 0x82 => 0x6d6e, 0x83 => 0x7236, 0x84 => 0x7b26, + 0x85 => 0x8150, 0x86 => 0x819a, 0x87 => 0x8299, 0x88 => 0x8b5c, + 0x89 => 0x8ca0, 0x8a => 0x8ce6, 0x8b => 0x8d74, 0x8c => 0x961c, + 0x8d => 0x9644, 0x8e => 0x4fae, 0x8f => 0x64ab, 0x90 => 0x6b66, + 0x91 => 0x821e, 0x92 => 0x8461, 0x93 => 0x856a, 0x94 => 0x90e8, + 0x95 => 0x5c01, 0x96 => 0x6953, 0x97 => 0x98a8, 0x98 => 0x847a, + 0x99 => 0x8557, 0x9a => 0x4f0f, 0x9b => 0x526f, 0x9c => 0x5fa9, + 0x9d => 0x5e45, 0x9e => 0x670d, 0x9f => 0x798f, 0xa0 => 0x8179, + 0xa1 => 0x8907, 0xa2 => 0x8986, 0xa3 => 0x6df5, 0xa4 => 0x5f17, + 0xa5 => 0x6255, 0xa6 => 0x6cb8, 0xa7 => 0x4ecf, 0xa8 => 0x7269, + 0xa9 => 0x9b92, 0xaa => 0x5206, 0xab => 0x543b, 0xac => 0x5674, + 0xad => 0x58b3, 0xae => 0x61a4, 0xaf => 0x626e, 0xb0 => 0x711a, + 0xb1 => 0x596e, 0xb2 => 0x7c89, 0xb3 => 0x7cde, 0xb4 => 0x7d1b, + 0xb5 => 0x96f0, 0xb6 => 0x6587, 0xb7 => 0x805e, 0xb8 => 0x4e19, + 0xb9 => 0x4f75, 0xba => 0x5175, 0xbb => 0x5840, 0xbc => 0x5e63, + 0xbd => 0x5e73, 0xbe => 0x5f0a, 0xbf => 0x67c4, 0xc0 => 0x4e26, + 0xc1 => 0x853d, 0xc2 => 0x9589, 0xc3 => 0x965b, 0xc4 => 0x7c73, + 0xc5 => 0x9801, 0xc6 => 0x50fb, 0xc7 => 0x58c1, 0xc8 => 0x7656, + 0xc9 => 0x78a7, 0xca => 0x5225, 0xcb => 0x77a5, 0xcc => 0x8511, + 0xcd => 0x7b86, 0xce => 0x504f, 0xcf => 0x5909, 0xd0 => 0x7247, + 0xd1 => 0x7bc7, 0xd2 => 0x7de8, 0xd3 => 0x8fba, 0xd4 => 0x8fd4, + 0xd5 => 0x904d, 0xd6 => 0x4fbf, 0xd7 => 0x52c9, 0xd8 => 0x5a29, + 0xd9 => 0x5f01, 0xda => 0x97ad, 0xdb => 0x4fdd, 0xdc => 0x8217, + 0xdd => 0x92ea, 0xde => 0x5703, 0xdf => 0x6355, 0xe0 => 0x6b69, + 0xe1 => 0x752b, 0xe2 => 0x88dc, 0xe3 => 0x8f14, 0xe4 => 0x7a42, + 0xe5 => 0x52df, 0xe6 => 0x5893, 0xe7 => 0x6155, 0xe8 => 0x620a, + 0xe9 => 0x66ae, 0xea => 0x6bcd, 0xeb => 0x7c3f, 0xec => 0x83e9, + 0xed => 0x5023, 0xee => 0x4ff8, 0xef => 0x5305, 0xf0 => 0x5446, + 0xf1 => 0x5831, 0xf2 => 0x5949, 0xf3 => 0x5b9d, 0xf4 => 0x5cf0, + 0xf5 => 0x5cef, 0xf6 => 0x5d29, 0xf7 => 0x5e96, 0xf8 => 0x62b1, + 0xf9 => 0x6367, 0xfa => 0x653e, 0xfb => 0x65b9, 0xfc => 0x670b, + }, + 0x96 => { + 0x40 => 0x6cd5, 0x41 => 0x6ce1, 0x42 => 0x70f9, 0x43 => 0x7832, + 0x44 => 0x7e2b, 0x45 => 0x80de, 0x46 => 0x82b3, 0x47 => 0x840c, + 0x48 => 0x84ec, 0x49 => 0x8702, 0x4a => 0x8912, 0x4b => 0x8a2a, + 0x4c => 0x8c4a, 0x4d => 0x90a6, 0x4e => 0x92d2, 0x4f => 0x98fd, + 0x50 => 0x9cf3, 0x51 => 0x9d6c, 0x52 => 0x4e4f, 0x53 => 0x4ea1, + 0x54 => 0x508d, 0x55 => 0x5256, 0x56 => 0x574a, 0x57 => 0x59a8, + 0x58 => 0x5e3d, 0x59 => 0x5fd8, 0x5a => 0x5fd9, 0x5b => 0x623f, + 0x5c => 0x66b4, 0x5d => 0x671b, 0x5e => 0x67d0, 0x5f => 0x68d2, + 0x60 => 0x5192, 0x61 => 0x7d21, 0x62 => 0x80aa, 0x63 => 0x81a8, + 0x64 => 0x8b00, 0x65 => 0x8c8c, 0x66 => 0x8cbf, 0x67 => 0x927e, + 0x68 => 0x9632, 0x69 => 0x5420, 0x6a => 0x982c, 0x6b => 0x5317, + 0x6c => 0x50d5, 0x6d => 0x535c, 0x6e => 0x58a8, 0x6f => 0x64b2, + 0x70 => 0x6734, 0x71 => 0x7267, 0x72 => 0x7766, 0x73 => 0x7a46, + 0x74 => 0x91e6, 0x75 => 0x52c3, 0x76 => 0x6ca1, 0x77 => 0x6b86, + 0x78 => 0x5800, 0x79 => 0x5e4c, 0x7a => 0x5954, 0x7b => 0x672c, + 0x7c => 0x7ffb, 0x7d => 0x51e1, 0x7e => 0x76c6, 0x80 => 0x6469, + 0x81 => 0x78e8, 0x82 => 0x9b54, 0x83 => 0x9ebb, 0x84 => 0x57cb, + 0x85 => 0x59b9, 0x86 => 0x6627, 0x87 => 0x679a, 0x88 => 0x6bce, + 0x89 => 0x54e9, 0x8a => 0x69d9, 0x8b => 0x5e55, 0x8c => 0x819c, + 0x8d => 0x6795, 0x8e => 0x9baa, 0x8f => 0x67fe, 0x90 => 0x9c52, + 0x91 => 0x685d, 0x92 => 0x4ea6, 0x93 => 0x4fe3, 0x94 => 0x53c8, + 0x95 => 0x62b9, 0x96 => 0x672b, 0x97 => 0x6cab, 0x98 => 0x8fc4, + 0x99 => 0x4fad, 0x9a => 0x7e6d, 0x9b => 0x9ebf, 0x9c => 0x4e07, + 0x9d => 0x6162, 0x9e => 0x6e80, 0x9f => 0x6f2b, 0xa0 => 0x8513, + 0xa1 => 0x5473, 0xa2 => 0x672a, 0xa3 => 0x9b45, 0xa4 => 0x5df3, + 0xa5 => 0x7b95, 0xa6 => 0x5cac, 0xa7 => 0x5bc6, 0xa8 => 0x871c, + 0xa9 => 0x6e4a, 0xaa => 0x84d1, 0xab => 0x7a14, 0xac => 0x8108, + 0xad => 0x5999, 0xae => 0x7c8d, 0xaf => 0x6c11, 0xb0 => 0x7720, + 0xb1 => 0x52d9, 0xb2 => 0x5922, 0xb3 => 0x7121, 0xb4 => 0x725f, + 0xb5 => 0x77db, 0xb6 => 0x9727, 0xb7 => 0x9d61, 0xb8 => 0x690b, + 0xb9 => 0x5a7f, 0xba => 0x5a18, 0xbb => 0x51a5, 0xbc => 0x540d, + 0xbd => 0x547d, 0xbe => 0x660e, 0xbf => 0x76df, 0xc0 => 0x8ff7, + 0xc1 => 0x9298, 0xc2 => 0x9cf4, 0xc3 => 0x59ea, 0xc4 => 0x725d, + 0xc5 => 0x6ec5, 0xc6 => 0x514d, 0xc7 => 0x68c9, 0xc8 => 0x7dbf, + 0xc9 => 0x7dec, 0xca => 0x9762, 0xcb => 0x9eba, 0xcc => 0x6478, + 0xcd => 0x6a21, 0xce => 0x8302, 0xcf => 0x5984, 0xd0 => 0x5b5f, + 0xd1 => 0x6bdb, 0xd2 => 0x731b, 0xd3 => 0x76f2, 0xd4 => 0x7db2, + 0xd5 => 0x8017, 0xd6 => 0x8499, 0xd7 => 0x5132, 0xd8 => 0x6728, + 0xd9 => 0x9ed9, 0xda => 0x76ee, 0xdb => 0x6762, 0xdc => 0x52ff, + 0xdd => 0x9905, 0xde => 0x5c24, 0xdf => 0x623b, 0xe0 => 0x7c7e, + 0xe1 => 0x8cb0, 0xe2 => 0x554f, 0xe3 => 0x60b6, 0xe4 => 0x7d0b, + 0xe5 => 0x9580, 0xe6 => 0x5301, 0xe7 => 0x4e5f, 0xe8 => 0x51b6, + 0xe9 => 0x591c, 0xea => 0x723a, 0xeb => 0x8036, 0xec => 0x91ce, + 0xed => 0x5f25, 0xee => 0x77e2, 0xef => 0x5384, 0xf0 => 0x5f79, + 0xf1 => 0x7d04, 0xf2 => 0x85ac, 0xf3 => 0x8a33, 0xf4 => 0x8e8d, + 0xf5 => 0x9756, 0xf6 => 0x67f3, 0xf7 => 0x85ae, 0xf8 => 0x9453, + 0xf9 => 0x6109, 0xfa => 0x6108, 0xfb => 0x6cb9, 0xfc => 0x7652, + }, + 0x97 => { + 0x40 => 0x8aed, 0x41 => 0x8f38, 0x42 => 0x552f, 0x43 => 0x4f51, + 0x44 => 0x512a, 0x45 => 0x52c7, 0x46 => 0x53cb, 0x47 => 0x5ba5, + 0x48 => 0x5e7d, 0x49 => 0x60a0, 0x4a => 0x6182, 0x4b => 0x63d6, + 0x4c => 0x6709, 0x4d => 0x67da, 0x4e => 0x6e67, 0x4f => 0x6d8c, + 0x50 => 0x7336, 0x51 => 0x7337, 0x52 => 0x7531, 0x53 => 0x7950, + 0x54 => 0x88d5, 0x55 => 0x8a98, 0x56 => 0x904a, 0x57 => 0x9091, + 0x58 => 0x90f5, 0x59 => 0x96c4, 0x5a => 0x878d, 0x5b => 0x5915, + 0x5c => 0x4e88, 0x5d => 0x4f59, 0x5e => 0x4e0e, 0x5f => 0x8a89, + 0x60 => 0x8f3f, 0x61 => 0x9810, 0x62 => 0x50ad, 0x63 => 0x5e7c, + 0x64 => 0x5996, 0x65 => 0x5bb9, 0x66 => 0x5eb8, 0x67 => 0x63da, + 0x68 => 0x63fa, 0x69 => 0x64c1, 0x6a => 0x66dc, 0x6b => 0x694a, + 0x6c => 0x69d8, 0x6d => 0x6d0b, 0x6e => 0x6eb6, 0x6f => 0x7194, + 0x70 => 0x7528, 0x71 => 0x7aaf, 0x72 => 0x7f8a, 0x73 => 0x8000, + 0x74 => 0x8449, 0x75 => 0x84c9, 0x76 => 0x8981, 0x77 => 0x8b21, + 0x78 => 0x8e0a, 0x79 => 0x9065, 0x7a => 0x967d, 0x7b => 0x990a, + 0x7c => 0x617e, 0x7d => 0x6291, 0x7e => 0x6b32, 0x80 => 0x6c83, + 0x81 => 0x6d74, 0x82 => 0x7fcc, 0x83 => 0x7ffc, 0x84 => 0x6dc0, + 0x85 => 0x7f85, 0x86 => 0x87ba, 0x87 => 0x88f8, 0x88 => 0x6765, + 0x89 => 0x83b1, 0x8a => 0x983c, 0x8b => 0x96f7, 0x8c => 0x6d1b, + 0x8d => 0x7d61, 0x8e => 0x843d, 0x8f => 0x916a, 0x90 => 0x4e71, + 0x91 => 0x5375, 0x92 => 0x5d50, 0x93 => 0x6b04, 0x94 => 0x6feb, + 0x95 => 0x85cd, 0x96 => 0x862d, 0x97 => 0x89a7, 0x98 => 0x5229, + 0x99 => 0x540f, 0x9a => 0x5c65, 0x9b => 0x674e, 0x9c => 0x68a8, + 0x9d => 0x7406, 0x9e => 0x7483, 0x9f => 0x75e2, 0xa0 => 0x88cf, + 0xa1 => 0x88e1, 0xa2 => 0x91cc, 0xa3 => 0x96e2, 0xa4 => 0x9678, + 0xa5 => 0x5f8b, 0xa6 => 0x7387, 0xa7 => 0x7acb, 0xa8 => 0x844e, + 0xa9 => 0x63a0, 0xaa => 0x7565, 0xab => 0x5289, 0xac => 0x6d41, + 0xad => 0x6e9c, 0xae => 0x7409, 0xaf => 0x7559, 0xb0 => 0x786b, + 0xb1 => 0x7c92, 0xb2 => 0x9686, 0xb3 => 0x7adc, 0xb4 => 0x9f8d, + 0xb5 => 0x4fb6, 0xb6 => 0x616e, 0xb7 => 0x65c5, 0xb8 => 0x865c, + 0xb9 => 0x4e86, 0xba => 0x4eae, 0xbb => 0x50da, 0xbc => 0x4e21, + 0xbd => 0x51cc, 0xbe => 0x5bee, 0xbf => 0x6599, 0xc0 => 0x6881, + 0xc1 => 0x6dbc, 0xc2 => 0x731f, 0xc3 => 0x7642, 0xc4 => 0x77ad, + 0xc5 => 0x7a1c, 0xc6 => 0x7ce7, 0xc7 => 0x826f, 0xc8 => 0x8ad2, + 0xc9 => 0x907c, 0xca => 0x91cf, 0xcb => 0x9675, 0xcc => 0x9818, + 0xcd => 0x529b, 0xce => 0x7dd1, 0xcf => 0x502b, 0xd0 => 0x5398, + 0xd1 => 0x6797, 0xd2 => 0x6dcb, 0xd3 => 0x71d0, 0xd4 => 0x7433, + 0xd5 => 0x81e8, 0xd6 => 0x8f2a, 0xd7 => 0x96a3, 0xd8 => 0x9c57, + 0xd9 => 0x9e9f, 0xda => 0x7460, 0xdb => 0x5841, 0xdc => 0x6d99, + 0xdd => 0x7d2f, 0xde => 0x985e, 0xdf => 0x4ee4, 0xe0 => 0x4f36, + 0xe1 => 0x4f8b, 0xe2 => 0x51b7, 0xe3 => 0x52b1, 0xe4 => 0x5dba, + 0xe5 => 0x601c, 0xe6 => 0x73b2, 0xe7 => 0x793c, 0xe8 => 0x82d3, + 0xe9 => 0x9234, 0xea => 0x96b7, 0xeb => 0x96f6, 0xec => 0x970a, + 0xed => 0x9e97, 0xee => 0x9f62, 0xef => 0x66a6, 0xf0 => 0x6b74, + 0xf1 => 0x5217, 0xf2 => 0x52a3, 0xf3 => 0x70c8, 0xf4 => 0x88c2, + 0xf5 => 0x5ec9, 0xf6 => 0x604b, 0xf7 => 0x6190, 0xf8 => 0x6f23, + 0xf9 => 0x7149, 0xfa => 0x7c3e, 0xfb => 0x7df4, 0xfc => 0x806f, + }, + 0x98 => { + 0x40 => 0x84ee, 0x41 => 0x9023, 0x42 => 0x932c, 0x43 => 0x5442, + 0x44 => 0x9b6f, 0x45 => 0x6ad3, 0x46 => 0x7089, 0x47 => 0x8cc2, + 0x48 => 0x8def, 0x49 => 0x9732, 0x4a => 0x52b4, 0x4b => 0x5a41, + 0x4c => 0x5eca, 0x4d => 0x5f04, 0x4e => 0x6717, 0x4f => 0x697c, + 0x50 => 0x6994, 0x51 => 0x6d6a, 0x52 => 0x6f0f, 0x53 => 0x7262, + 0x54 => 0x72fc, 0x55 => 0x7bed, 0x56 => 0x8001, 0x57 => 0x807e, + 0x58 => 0x874b, 0x59 => 0x90ce, 0x5a => 0x516d, 0x5b => 0x9e93, + 0x5c => 0x7984, 0x5d => 0x808b, 0x5e => 0x9332, 0x5f => 0x8ad6, + 0x60 => 0x502d, 0x61 => 0x548c, 0x62 => 0x8a71, 0x63 => 0x6b6a, + 0x64 => 0x8cc4, 0x65 => 0x8107, 0x66 => 0x60d1, 0x67 => 0x67a0, + 0x68 => 0x9df2, 0x69 => 0x4e99, 0x6a => 0x4e98, 0x6b => 0x9c10, + 0x6c => 0x8a6b, 0x6d => 0x85c1, 0x6e => 0x8568, 0x6f => 0x6900, + 0x70 => 0x6e7e, 0x71 => 0x7897, 0x72 => 0x8155, 0x9f => 0x5f0c, + 0xa0 => 0x4e10, 0xa1 => 0x4e15, 0xa2 => 0x4e2a, 0xa3 => 0x4e31, + 0xa4 => 0x4e36, 0xa5 => 0x4e3c, 0xa6 => 0x4e3f, 0xa7 => 0x4e42, + 0xa8 => 0x4e56, 0xa9 => 0x4e58, 0xaa => 0x4e82, 0xab => 0x4e85, + 0xac => 0x8c6b, 0xad => 0x4e8a, 0xae => 0x8212, 0xaf => 0x5f0d, + 0xb0 => 0x4e8e, 0xb1 => 0x4e9e, 0xb2 => 0x4e9f, 0xb3 => 0x4ea0, + 0xb4 => 0x4ea2, 0xb5 => 0x4eb0, 0xb6 => 0x4eb3, 0xb7 => 0x4eb6, + 0xb8 => 0x4ece, 0xb9 => 0x4ecd, 0xba => 0x4ec4, 0xbb => 0x4ec6, + 0xbc => 0x4ec2, 0xbd => 0x4ed7, 0xbe => 0x4ede, 0xbf => 0x4eed, + 0xc0 => 0x4edf, 0xc1 => 0x4ef7, 0xc2 => 0x4f09, 0xc3 => 0x4f5a, + 0xc4 => 0x4f30, 0xc5 => 0x4f5b, 0xc6 => 0x4f5d, 0xc7 => 0x4f57, + 0xc8 => 0x4f47, 0xc9 => 0x4f76, 0xca => 0x4f88, 0xcb => 0x4f8f, + 0xcc => 0x4f98, 0xcd => 0x4f7b, 0xce => 0x4f69, 0xcf => 0x4f70, + 0xd0 => 0x4f91, 0xd1 => 0x4f6f, 0xd2 => 0x4f86, 0xd3 => 0x4f96, + 0xd4 => 0x5118, 0xd5 => 0x4fd4, 0xd6 => 0x4fdf, 0xd7 => 0x4fce, + 0xd8 => 0x4fd8, 0xd9 => 0x4fdb, 0xda => 0x4fd1, 0xdb => 0x4fda, + 0xdc => 0x4fd0, 0xdd => 0x4fe4, 0xde => 0x4fe5, 0xdf => 0x501a, + 0xe0 => 0x5028, 0xe1 => 0x5014, 0xe2 => 0x502a, 0xe3 => 0x5025, + 0xe4 => 0x5005, 0xe5 => 0x4f1c, 0xe6 => 0x4ff6, 0xe7 => 0x5021, + 0xe8 => 0x5029, 0xe9 => 0x502c, 0xea => 0x4ffe, 0xeb => 0x4fef, + 0xec => 0x5011, 0xed => 0x5006, 0xee => 0x5043, 0xef => 0x5047, + 0xf0 => 0x6703, 0xf1 => 0x5055, 0xf2 => 0x5050, 0xf3 => 0x5048, + 0xf4 => 0x505a, 0xf5 => 0x5056, 0xf6 => 0x506c, 0xf7 => 0x5078, + 0xf8 => 0x5080, 0xf9 => 0x509a, 0xfa => 0x5085, 0xfb => 0x50b4, + 0xfc => 0x50b2, + }, + 0x99 => { + 0x40 => 0x50c9, 0x41 => 0x50ca, 0x42 => 0x50b3, 0x43 => 0x50c2, + 0x44 => 0x50d6, 0x45 => 0x50de, 0x46 => 0x50e5, 0x47 => 0x50ed, + 0x48 => 0x50e3, 0x49 => 0x50ee, 0x4a => 0x50f9, 0x4b => 0x50f5, + 0x4c => 0x5109, 0x4d => 0x5101, 0x4e => 0x5102, 0x4f => 0x5116, + 0x50 => 0x5115, 0x51 => 0x5114, 0x52 => 0x511a, 0x53 => 0x5121, + 0x54 => 0x513a, 0x55 => 0x5137, 0x56 => 0x513c, 0x57 => 0x513b, + 0x58 => 0x513f, 0x59 => 0x5140, 0x5a => 0x5152, 0x5b => 0x514c, + 0x5c => 0x5154, 0x5d => 0x5162, 0x5e => 0x7af8, 0x5f => 0x5169, + 0x60 => 0x516a, 0x61 => 0x516e, 0x62 => 0x5180, 0x63 => 0x5182, + 0x64 => 0x56d8, 0x65 => 0x518c, 0x66 => 0x5189, 0x67 => 0x518f, + 0x68 => 0x5191, 0x69 => 0x5193, 0x6a => 0x5195, 0x6b => 0x5196, + 0x6c => 0x51a4, 0x6d => 0x51a6, 0x6e => 0x51a2, 0x6f => 0x51a9, + 0x70 => 0x51aa, 0x71 => 0x51ab, 0x72 => 0x51b3, 0x73 => 0x51b1, + 0x74 => 0x51b2, 0x75 => 0x51b0, 0x76 => 0x51b5, 0x77 => 0x51bd, + 0x78 => 0x51c5, 0x79 => 0x51c9, 0x7a => 0x51db, 0x7b => 0x51e0, + 0x7c => 0x8655, 0x7d => 0x51e9, 0x7e => 0x51ed, 0x80 => 0x51f0, + 0x81 => 0x51f5, 0x82 => 0x51fe, 0x83 => 0x5204, 0x84 => 0x520b, + 0x85 => 0x5214, 0x86 => 0x520e, 0x87 => 0x5227, 0x88 => 0x522a, + 0x89 => 0x522e, 0x8a => 0x5233, 0x8b => 0x5239, 0x8c => 0x524f, + 0x8d => 0x5244, 0x8e => 0x524b, 0x8f => 0x524c, 0x90 => 0x525e, + 0x91 => 0x5254, 0x92 => 0x526a, 0x93 => 0x5274, 0x94 => 0x5269, + 0x95 => 0x5273, 0x96 => 0x527f, 0x97 => 0x527d, 0x98 => 0x528d, + 0x99 => 0x5294, 0x9a => 0x5292, 0x9b => 0x5271, 0x9c => 0x5288, + 0x9d => 0x5291, 0x9e => 0x8fa8, 0x9f => 0x8fa7, 0xa0 => 0x52ac, + 0xa1 => 0x52ad, 0xa2 => 0x52bc, 0xa3 => 0x52b5, 0xa4 => 0x52c1, + 0xa5 => 0x52cd, 0xa6 => 0x52d7, 0xa7 => 0x52de, 0xa8 => 0x52e3, + 0xa9 => 0x52e6, 0xaa => 0x98ed, 0xab => 0x52e0, 0xac => 0x52f3, + 0xad => 0x52f5, 0xae => 0x52f8, 0xaf => 0x52f9, 0xb0 => 0x5306, + 0xb1 => 0x5308, 0xb2 => 0x7538, 0xb3 => 0x530d, 0xb4 => 0x5310, + 0xb5 => 0x530f, 0xb6 => 0x5315, 0xb7 => 0x531a, 0xb8 => 0x5323, + 0xb9 => 0x532f, 0xba => 0x5331, 0xbb => 0x5333, 0xbc => 0x5338, + 0xbd => 0x5340, 0xbe => 0x5346, 0xbf => 0x5345, 0xc0 => 0x4e17, + 0xc1 => 0x5349, 0xc2 => 0x534d, 0xc3 => 0x51d6, 0xc4 => 0x535e, + 0xc5 => 0x5369, 0xc6 => 0x536e, 0xc7 => 0x5918, 0xc8 => 0x537b, + 0xc9 => 0x5377, 0xca => 0x5382, 0xcb => 0x5396, 0xcc => 0x53a0, + 0xcd => 0x53a6, 0xce => 0x53a5, 0xcf => 0x53ae, 0xd0 => 0x53b0, + 0xd1 => 0x53b6, 0xd2 => 0x53c3, 0xd3 => 0x7c12, 0xd4 => 0x96d9, + 0xd5 => 0x53df, 0xd6 => 0x66fc, 0xd7 => 0x71ee, 0xd8 => 0x53ee, + 0xd9 => 0x53e8, 0xda => 0x53ed, 0xdb => 0x53fa, 0xdc => 0x5401, + 0xdd => 0x543d, 0xde => 0x5440, 0xdf => 0x542c, 0xe0 => 0x542d, + 0xe1 => 0x543c, 0xe2 => 0x542e, 0xe3 => 0x5436, 0xe4 => 0x5429, + 0xe5 => 0x541d, 0xe6 => 0x544e, 0xe7 => 0x548f, 0xe8 => 0x5475, + 0xe9 => 0x548e, 0xea => 0x545f, 0xeb => 0x5471, 0xec => 0x5477, + 0xed => 0x5470, 0xee => 0x5492, 0xef => 0x547b, 0xf0 => 0x5480, + 0xf1 => 0x5476, 0xf2 => 0x5484, 0xf3 => 0x5490, 0xf4 => 0x5486, + 0xf5 => 0x54c7, 0xf6 => 0x54a2, 0xf7 => 0x54b8, 0xf8 => 0x54a5, + 0xf9 => 0x54ac, 0xfa => 0x54c4, 0xfb => 0x54c8, 0xfc => 0x54a8, + }, + 0x9a => { + 0x40 => 0x54ab, 0x41 => 0x54c2, 0x42 => 0x54a4, 0x43 => 0x54be, + 0x44 => 0x54bc, 0x45 => 0x54d8, 0x46 => 0x54e5, 0x47 => 0x54e6, + 0x48 => 0x550f, 0x49 => 0x5514, 0x4a => 0x54fd, 0x4b => 0x54ee, + 0x4c => 0x54ed, 0x4d => 0x54fa, 0x4e => 0x54e2, 0x4f => 0x5539, + 0x50 => 0x5540, 0x51 => 0x5563, 0x52 => 0x554c, 0x53 => 0x552e, + 0x54 => 0x555c, 0x55 => 0x5545, 0x56 => 0x5556, 0x57 => 0x5557, + 0x58 => 0x5538, 0x59 => 0x5533, 0x5a => 0x555d, 0x5b => 0x5599, + 0x5c => 0x5580, 0x5d => 0x54af, 0x5e => 0x558a, 0x5f => 0x559f, + 0x60 => 0x557b, 0x61 => 0x557e, 0x62 => 0x5598, 0x63 => 0x559e, + 0x64 => 0x55ae, 0x65 => 0x557c, 0x66 => 0x5583, 0x67 => 0x55a9, + 0x68 => 0x5587, 0x69 => 0x55a8, 0x6a => 0x55da, 0x6b => 0x55c5, + 0x6c => 0x55df, 0x6d => 0x55c4, 0x6e => 0x55dc, 0x6f => 0x55e4, + 0x70 => 0x55d4, 0x71 => 0x5614, 0x72 => 0x55f7, 0x73 => 0x5616, + 0x74 => 0x55fe, 0x75 => 0x55fd, 0x76 => 0x561b, 0x77 => 0x55f9, + 0x78 => 0x564e, 0x79 => 0x5650, 0x7a => 0x71df, 0x7b => 0x5634, + 0x7c => 0x5636, 0x7d => 0x5632, 0x7e => 0x5638, 0x80 => 0x566b, + 0x81 => 0x5664, 0x82 => 0x562f, 0x83 => 0x566c, 0x84 => 0x566a, + 0x85 => 0x5686, 0x86 => 0x5680, 0x87 => 0x568a, 0x88 => 0x56a0, + 0x89 => 0x5694, 0x8a => 0x568f, 0x8b => 0x56a5, 0x8c => 0x56ae, + 0x8d => 0x56b6, 0x8e => 0x56b4, 0x8f => 0x56c2, 0x90 => 0x56bc, + 0x91 => 0x56c1, 0x92 => 0x56c3, 0x93 => 0x56c0, 0x94 => 0x56c8, + 0x95 => 0x56ce, 0x96 => 0x56d1, 0x97 => 0x56d3, 0x98 => 0x56d7, + 0x99 => 0x56ee, 0x9a => 0x56f9, 0x9b => 0x5700, 0x9c => 0x56ff, + 0x9d => 0x5704, 0x9e => 0x5709, 0x9f => 0x5708, 0xa0 => 0x570b, + 0xa1 => 0x570d, 0xa2 => 0x5713, 0xa3 => 0x5718, 0xa4 => 0x5716, + 0xa5 => 0x55c7, 0xa6 => 0x571c, 0xa7 => 0x5726, 0xa8 => 0x5737, + 0xa9 => 0x5738, 0xaa => 0x574e, 0xab => 0x573b, 0xac => 0x5740, + 0xad => 0x574f, 0xae => 0x5769, 0xaf => 0x57c0, 0xb0 => 0x5788, + 0xb1 => 0x5761, 0xb2 => 0x577f, 0xb3 => 0x5789, 0xb4 => 0x5793, + 0xb5 => 0x57a0, 0xb6 => 0x57b3, 0xb7 => 0x57a4, 0xb8 => 0x57aa, + 0xb9 => 0x57b0, 0xba => 0x57c3, 0xbb => 0x57c6, 0xbc => 0x57d4, + 0xbd => 0x57d2, 0xbe => 0x57d3, 0xbf => 0x580a, 0xc0 => 0x57d6, + 0xc1 => 0x57e3, 0xc2 => 0x580b, 0xc3 => 0x5819, 0xc4 => 0x581d, + 0xc5 => 0x5872, 0xc6 => 0x5821, 0xc7 => 0x5862, 0xc8 => 0x584b, + 0xc9 => 0x5870, 0xca => 0x6bc0, 0xcb => 0x5852, 0xcc => 0x583d, + 0xcd => 0x5879, 0xce => 0x5885, 0xcf => 0x58b9, 0xd0 => 0x589f, + 0xd1 => 0x58ab, 0xd2 => 0x58ba, 0xd3 => 0x58de, 0xd4 => 0x58bb, + 0xd5 => 0x58b8, 0xd6 => 0x58ae, 0xd7 => 0x58c5, 0xd8 => 0x58d3, + 0xd9 => 0x58d1, 0xda => 0x58d7, 0xdb => 0x58d9, 0xdc => 0x58d8, + 0xdd => 0x58e5, 0xde => 0x58dc, 0xdf => 0x58e4, 0xe0 => 0x58df, + 0xe1 => 0x58ef, 0xe2 => 0x58fa, 0xe3 => 0x58f9, 0xe4 => 0x58fb, + 0xe5 => 0x58fc, 0xe6 => 0x58fd, 0xe7 => 0x5902, 0xe8 => 0x590a, + 0xe9 => 0x5910, 0xea => 0x591b, 0xeb => 0x68a6, 0xec => 0x5925, + 0xed => 0x592c, 0xee => 0x592d, 0xef => 0x5932, 0xf0 => 0x5938, + 0xf1 => 0x593e, 0xf2 => 0x7ad2, 0xf3 => 0x5955, 0xf4 => 0x5950, + 0xf5 => 0x594e, 0xf6 => 0x595a, 0xf7 => 0x5958, 0xf8 => 0x5962, + 0xf9 => 0x5960, 0xfa => 0x5967, 0xfb => 0x596c, 0xfc => 0x5969, + }, + 0x9b => { + 0x40 => 0x5978, 0x41 => 0x5981, 0x42 => 0x599d, 0x43 => 0x4f5e, + 0x44 => 0x4fab, 0x45 => 0x59a3, 0x46 => 0x59b2, 0x47 => 0x59c6, + 0x48 => 0x59e8, 0x49 => 0x59dc, 0x4a => 0x598d, 0x4b => 0x59d9, + 0x4c => 0x59da, 0x4d => 0x5a25, 0x4e => 0x5a1f, 0x4f => 0x5a11, + 0x50 => 0x5a1c, 0x51 => 0x5a09, 0x52 => 0x5a1a, 0x53 => 0x5a40, + 0x54 => 0x5a6c, 0x55 => 0x5a49, 0x56 => 0x5a35, 0x57 => 0x5a36, + 0x58 => 0x5a62, 0x59 => 0x5a6a, 0x5a => 0x5a9a, 0x5b => 0x5abc, + 0x5c => 0x5abe, 0x5d => 0x5acb, 0x5e => 0x5ac2, 0x5f => 0x5abd, + 0x60 => 0x5ae3, 0x61 => 0x5ad7, 0x62 => 0x5ae6, 0x63 => 0x5ae9, + 0x64 => 0x5ad6, 0x65 => 0x5afa, 0x66 => 0x5afb, 0x67 => 0x5b0c, + 0x68 => 0x5b0b, 0x69 => 0x5b16, 0x6a => 0x5b32, 0x6b => 0x5ad0, + 0x6c => 0x5b2a, 0x6d => 0x5b36, 0x6e => 0x5b3e, 0x6f => 0x5b43, + 0x70 => 0x5b45, 0x71 => 0x5b40, 0x72 => 0x5b51, 0x73 => 0x5b55, + 0x74 => 0x5b5a, 0x75 => 0x5b5b, 0x76 => 0x5b65, 0x77 => 0x5b69, + 0x78 => 0x5b70, 0x79 => 0x5b73, 0x7a => 0x5b75, 0x7b => 0x5b78, + 0x7c => 0x6588, 0x7d => 0x5b7a, 0x7e => 0x5b80, 0x80 => 0x5b83, + 0x81 => 0x5ba6, 0x82 => 0x5bb8, 0x83 => 0x5bc3, 0x84 => 0x5bc7, + 0x85 => 0x5bc9, 0x86 => 0x5bd4, 0x87 => 0x5bd0, 0x88 => 0x5be4, + 0x89 => 0x5be6, 0x8a => 0x5be2, 0x8b => 0x5bde, 0x8c => 0x5be5, + 0x8d => 0x5beb, 0x8e => 0x5bf0, 0x8f => 0x5bf6, 0x90 => 0x5bf3, + 0x91 => 0x5c05, 0x92 => 0x5c07, 0x93 => 0x5c08, 0x94 => 0x5c0d, + 0x95 => 0x5c13, 0x96 => 0x5c20, 0x97 => 0x5c22, 0x98 => 0x5c28, + 0x99 => 0x5c38, 0x9a => 0x5c39, 0x9b => 0x5c41, 0x9c => 0x5c46, + 0x9d => 0x5c4e, 0x9e => 0x5c53, 0x9f => 0x5c50, 0xa0 => 0x5c4f, + 0xa1 => 0x5b71, 0xa2 => 0x5c6c, 0xa3 => 0x5c6e, 0xa4 => 0x4e62, + 0xa5 => 0x5c76, 0xa6 => 0x5c79, 0xa7 => 0x5c8c, 0xa8 => 0x5c91, + 0xa9 => 0x5c94, 0xaa => 0x599b, 0xab => 0x5cab, 0xac => 0x5cbb, + 0xad => 0x5cb6, 0xae => 0x5cbc, 0xaf => 0x5cb7, 0xb0 => 0x5cc5, + 0xb1 => 0x5cbe, 0xb2 => 0x5cc7, 0xb3 => 0x5cd9, 0xb4 => 0x5ce9, + 0xb5 => 0x5cfd, 0xb6 => 0x5cfa, 0xb7 => 0x5ced, 0xb8 => 0x5d8c, + 0xb9 => 0x5cea, 0xba => 0x5d0b, 0xbb => 0x5d15, 0xbc => 0x5d17, + 0xbd => 0x5d5c, 0xbe => 0x5d1f, 0xbf => 0x5d1b, 0xc0 => 0x5d11, + 0xc1 => 0x5d14, 0xc2 => 0x5d22, 0xc3 => 0x5d1a, 0xc4 => 0x5d19, + 0xc5 => 0x5d18, 0xc6 => 0x5d4c, 0xc7 => 0x5d52, 0xc8 => 0x5d4e, + 0xc9 => 0x5d4b, 0xca => 0x5d6c, 0xcb => 0x5d73, 0xcc => 0x5d76, + 0xcd => 0x5d87, 0xce => 0x5d84, 0xcf => 0x5d82, 0xd0 => 0x5da2, + 0xd1 => 0x5d9d, 0xd2 => 0x5dac, 0xd3 => 0x5dae, 0xd4 => 0x5dbd, + 0xd5 => 0x5d90, 0xd6 => 0x5db7, 0xd7 => 0x5dbc, 0xd8 => 0x5dc9, + 0xd9 => 0x5dcd, 0xda => 0x5dd3, 0xdb => 0x5dd2, 0xdc => 0x5dd6, + 0xdd => 0x5ddb, 0xde => 0x5deb, 0xdf => 0x5df2, 0xe0 => 0x5df5, + 0xe1 => 0x5e0b, 0xe2 => 0x5e1a, 0xe3 => 0x5e19, 0xe4 => 0x5e11, + 0xe5 => 0x5e1b, 0xe6 => 0x5e36, 0xe7 => 0x5e37, 0xe8 => 0x5e44, + 0xe9 => 0x5e43, 0xea => 0x5e40, 0xeb => 0x5e4e, 0xec => 0x5e57, + 0xed => 0x5e54, 0xee => 0x5e5f, 0xef => 0x5e62, 0xf0 => 0x5e64, + 0xf1 => 0x5e47, 0xf2 => 0x5e75, 0xf3 => 0x5e76, 0xf4 => 0x5e7a, + 0xf5 => 0x9ebc, 0xf6 => 0x5e7f, 0xf7 => 0x5ea0, 0xf8 => 0x5ec1, + 0xf9 => 0x5ec2, 0xfa => 0x5ec8, 0xfb => 0x5ed0, 0xfc => 0x5ecf, + }, + 0x9c => { + 0x40 => 0x5ed6, 0x41 => 0x5ee3, 0x42 => 0x5edd, 0x43 => 0x5eda, + 0x44 => 0x5edb, 0x45 => 0x5ee2, 0x46 => 0x5ee1, 0x47 => 0x5ee8, + 0x48 => 0x5ee9, 0x49 => 0x5eec, 0x4a => 0x5ef1, 0x4b => 0x5ef3, + 0x4c => 0x5ef0, 0x4d => 0x5ef4, 0x4e => 0x5ef8, 0x4f => 0x5efe, + 0x50 => 0x5f03, 0x51 => 0x5f09, 0x52 => 0x5f5d, 0x53 => 0x5f5c, + 0x54 => 0x5f0b, 0x55 => 0x5f11, 0x56 => 0x5f16, 0x57 => 0x5f29, + 0x58 => 0x5f2d, 0x59 => 0x5f38, 0x5a => 0x5f41, 0x5b => 0x5f48, + 0x5c => 0x5f4c, 0x5d => 0x5f4e, 0x5e => 0x5f2f, 0x5f => 0x5f51, + 0x60 => 0x5f56, 0x61 => 0x5f57, 0x62 => 0x5f59, 0x63 => 0x5f61, + 0x64 => 0x5f6d, 0x65 => 0x5f73, 0x66 => 0x5f77, 0x67 => 0x5f83, + 0x68 => 0x5f82, 0x69 => 0x5f7f, 0x6a => 0x5f8a, 0x6b => 0x5f88, + 0x6c => 0x5f91, 0x6d => 0x5f87, 0x6e => 0x5f9e, 0x6f => 0x5f99, + 0x70 => 0x5f98, 0x71 => 0x5fa0, 0x72 => 0x5fa8, 0x73 => 0x5fad, + 0x74 => 0x5fbc, 0x75 => 0x5fd6, 0x76 => 0x5ffb, 0x77 => 0x5fe4, + 0x78 => 0x5ff8, 0x79 => 0x5ff1, 0x7a => 0x5fdd, 0x7b => 0x60b3, + 0x7c => 0x5fff, 0x7d => 0x6021, 0x7e => 0x6060, 0x80 => 0x6019, + 0x81 => 0x6010, 0x82 => 0x6029, 0x83 => 0x600e, 0x84 => 0x6031, + 0x85 => 0x601b, 0x86 => 0x6015, 0x87 => 0x602b, 0x88 => 0x6026, + 0x89 => 0x600f, 0x8a => 0x603a, 0x8b => 0x605a, 0x8c => 0x6041, + 0x8d => 0x606a, 0x8e => 0x6077, 0x8f => 0x605f, 0x90 => 0x604a, + 0x91 => 0x6046, 0x92 => 0x604d, 0x93 => 0x6063, 0x94 => 0x6043, + 0x95 => 0x6064, 0x96 => 0x6042, 0x97 => 0x606c, 0x98 => 0x606b, + 0x99 => 0x6059, 0x9a => 0x6081, 0x9b => 0x608d, 0x9c => 0x60e7, + 0x9d => 0x6083, 0x9e => 0x609a, 0x9f => 0x6084, 0xa0 => 0x609b, + 0xa1 => 0x6096, 0xa2 => 0x6097, 0xa3 => 0x6092, 0xa4 => 0x60a7, + 0xa5 => 0x608b, 0xa6 => 0x60e1, 0xa7 => 0x60b8, 0xa8 => 0x60e0, + 0xa9 => 0x60d3, 0xaa => 0x60b4, 0xab => 0x5ff0, 0xac => 0x60bd, + 0xad => 0x60c6, 0xae => 0x60b5, 0xaf => 0x60d8, 0xb0 => 0x614d, + 0xb1 => 0x6115, 0xb2 => 0x6106, 0xb3 => 0x60f6, 0xb4 => 0x60f7, + 0xb5 => 0x6100, 0xb6 => 0x60f4, 0xb7 => 0x60fa, 0xb8 => 0x6103, + 0xb9 => 0x6121, 0xba => 0x60fb, 0xbb => 0x60f1, 0xbc => 0x610d, + 0xbd => 0x610e, 0xbe => 0x6147, 0xbf => 0x613e, 0xc0 => 0x6128, + 0xc1 => 0x6127, 0xc2 => 0x614a, 0xc3 => 0x613f, 0xc4 => 0x613c, + 0xc5 => 0x612c, 0xc6 => 0x6134, 0xc7 => 0x613d, 0xc8 => 0x6142, + 0xc9 => 0x6144, 0xca => 0x6173, 0xcb => 0x6177, 0xcc => 0x6158, + 0xcd => 0x6159, 0xce => 0x615a, 0xcf => 0x616b, 0xd0 => 0x6174, + 0xd1 => 0x616f, 0xd2 => 0x6165, 0xd3 => 0x6171, 0xd4 => 0x615f, + 0xd5 => 0x615d, 0xd6 => 0x6153, 0xd7 => 0x6175, 0xd8 => 0x6199, + 0xd9 => 0x6196, 0xda => 0x6187, 0xdb => 0x61ac, 0xdc => 0x6194, + 0xdd => 0x619a, 0xde => 0x618a, 0xdf => 0x6191, 0xe0 => 0x61ab, + 0xe1 => 0x61ae, 0xe2 => 0x61cc, 0xe3 => 0x61ca, 0xe4 => 0x61c9, + 0xe5 => 0x61f7, 0xe6 => 0x61c8, 0xe7 => 0x61c3, 0xe8 => 0x61c6, + 0xe9 => 0x61ba, 0xea => 0x61cb, 0xeb => 0x7f79, 0xec => 0x61cd, + 0xed => 0x61e6, 0xee => 0x61e3, 0xef => 0x61f6, 0xf0 => 0x61fa, + 0xf1 => 0x61f4, 0xf2 => 0x61ff, 0xf3 => 0x61fd, 0xf4 => 0x61fc, + 0xf5 => 0x61fe, 0xf6 => 0x6200, 0xf7 => 0x6208, 0xf8 => 0x6209, + 0xf9 => 0x620d, 0xfa => 0x620c, 0xfb => 0x6214, 0xfc => 0x621b, + }, + 0x9d => { + 0x40 => 0x621e, 0x41 => 0x6221, 0x42 => 0x622a, 0x43 => 0x622e, + 0x44 => 0x6230, 0x45 => 0x6232, 0x46 => 0x6233, 0x47 => 0x6241, + 0x48 => 0x624e, 0x49 => 0x625e, 0x4a => 0x6263, 0x4b => 0x625b, + 0x4c => 0x6260, 0x4d => 0x6268, 0x4e => 0x627c, 0x4f => 0x6282, + 0x50 => 0x6289, 0x51 => 0x627e, 0x52 => 0x6292, 0x53 => 0x6293, + 0x54 => 0x6296, 0x55 => 0x62d4, 0x56 => 0x6283, 0x57 => 0x6294, + 0x58 => 0x62d7, 0x59 => 0x62d1, 0x5a => 0x62bb, 0x5b => 0x62cf, + 0x5c => 0x62ff, 0x5d => 0x62c6, 0x5e => 0x64d4, 0x5f => 0x62c8, + 0x60 => 0x62dc, 0x61 => 0x62cc, 0x62 => 0x62ca, 0x63 => 0x62c2, + 0x64 => 0x62c7, 0x65 => 0x629b, 0x66 => 0x62c9, 0x67 => 0x630c, + 0x68 => 0x62ee, 0x69 => 0x62f1, 0x6a => 0x6327, 0x6b => 0x6302, + 0x6c => 0x6308, 0x6d => 0x62ef, 0x6e => 0x62f5, 0x6f => 0x6350, + 0x70 => 0x633e, 0x71 => 0x634d, 0x72 => 0x641c, 0x73 => 0x634f, + 0x74 => 0x6396, 0x75 => 0x638e, 0x76 => 0x6380, 0x77 => 0x63ab, + 0x78 => 0x6376, 0x79 => 0x63a3, 0x7a => 0x638f, 0x7b => 0x6389, + 0x7c => 0x639f, 0x7d => 0x63b5, 0x7e => 0x636b, 0x80 => 0x6369, + 0x81 => 0x63be, 0x82 => 0x63e9, 0x83 => 0x63c0, 0x84 => 0x63c6, + 0x85 => 0x63e3, 0x86 => 0x63c9, 0x87 => 0x63d2, 0x88 => 0x63f6, + 0x89 => 0x63c4, 0x8a => 0x6416, 0x8b => 0x6434, 0x8c => 0x6406, + 0x8d => 0x6413, 0x8e => 0x6426, 0x8f => 0x6436, 0x90 => 0x651d, + 0x91 => 0x6417, 0x92 => 0x6428, 0x93 => 0x640f, 0x94 => 0x6467, + 0x95 => 0x646f, 0x96 => 0x6476, 0x97 => 0x644e, 0x98 => 0x652a, + 0x99 => 0x6495, 0x9a => 0x6493, 0x9b => 0x64a5, 0x9c => 0x64a9, + 0x9d => 0x6488, 0x9e => 0x64bc, 0x9f => 0x64da, 0xa0 => 0x64d2, + 0xa1 => 0x64c5, 0xa2 => 0x64c7, 0xa3 => 0x64bb, 0xa4 => 0x64d8, + 0xa5 => 0x64c2, 0xa6 => 0x64f1, 0xa7 => 0x64e7, 0xa8 => 0x8209, + 0xa9 => 0x64e0, 0xaa => 0x64e1, 0xab => 0x62ac, 0xac => 0x64e3, + 0xad => 0x64ef, 0xae => 0x652c, 0xaf => 0x64f6, 0xb0 => 0x64f4, + 0xb1 => 0x64f2, 0xb2 => 0x64fa, 0xb3 => 0x6500, 0xb4 => 0x64fd, + 0xb5 => 0x6518, 0xb6 => 0x651c, 0xb7 => 0x6505, 0xb8 => 0x6524, + 0xb9 => 0x6523, 0xba => 0x652b, 0xbb => 0x6534, 0xbc => 0x6535, + 0xbd => 0x6537, 0xbe => 0x6536, 0xbf => 0x6538, 0xc0 => 0x754b, + 0xc1 => 0x6548, 0xc2 => 0x6556, 0xc3 => 0x6555, 0xc4 => 0x654d, + 0xc5 => 0x6558, 0xc6 => 0x655e, 0xc7 => 0x655d, 0xc8 => 0x6572, + 0xc9 => 0x6578, 0xca => 0x6582, 0xcb => 0x6583, 0xcc => 0x8b8a, + 0xcd => 0x659b, 0xce => 0x659f, 0xcf => 0x65ab, 0xd0 => 0x65b7, + 0xd1 => 0x65c3, 0xd2 => 0x65c6, 0xd3 => 0x65c1, 0xd4 => 0x65c4, + 0xd5 => 0x65cc, 0xd6 => 0x65d2, 0xd7 => 0x65db, 0xd8 => 0x65d9, + 0xd9 => 0x65e0, 0xda => 0x65e1, 0xdb => 0x65f1, 0xdc => 0x6772, + 0xdd => 0x660a, 0xde => 0x6603, 0xdf => 0x65fb, 0xe0 => 0x6773, + 0xe1 => 0x6635, 0xe2 => 0x6636, 0xe3 => 0x6634, 0xe4 => 0x661c, + 0xe5 => 0x664f, 0xe6 => 0x6644, 0xe7 => 0x6649, 0xe8 => 0x6641, + 0xe9 => 0x665e, 0xea => 0x665d, 0xeb => 0x6664, 0xec => 0x6667, + 0xed => 0x6668, 0xee => 0x665f, 0xef => 0x6662, 0xf0 => 0x6670, + 0xf1 => 0x6683, 0xf2 => 0x6688, 0xf3 => 0x668e, 0xf4 => 0x6689, + 0xf5 => 0x6684, 0xf6 => 0x6698, 0xf7 => 0x669d, 0xf8 => 0x66c1, + 0xf9 => 0x66b9, 0xfa => 0x66c9, 0xfb => 0x66be, 0xfc => 0x66bc, + }, + 0x9e => { + 0x40 => 0x66c4, 0x41 => 0x66b8, 0x42 => 0x66d6, 0x43 => 0x66da, + 0x44 => 0x66e0, 0x45 => 0x663f, 0x46 => 0x66e6, 0x47 => 0x66e9, + 0x48 => 0x66f0, 0x49 => 0x66f5, 0x4a => 0x66f7, 0x4b => 0x670f, + 0x4c => 0x6716, 0x4d => 0x671e, 0x4e => 0x6726, 0x4f => 0x6727, + 0x50 => 0x9738, 0x51 => 0x672e, 0x52 => 0x673f, 0x53 => 0x6736, + 0x54 => 0x6741, 0x55 => 0x6738, 0x56 => 0x6737, 0x57 => 0x6746, + 0x58 => 0x675e, 0x59 => 0x6760, 0x5a => 0x6759, 0x5b => 0x6763, + 0x5c => 0x6764, 0x5d => 0x6789, 0x5e => 0x6770, 0x5f => 0x67a9, + 0x60 => 0x677c, 0x61 => 0x676a, 0x62 => 0x678c, 0x63 => 0x678b, + 0x64 => 0x67a6, 0x65 => 0x67a1, 0x66 => 0x6785, 0x67 => 0x67b7, + 0x68 => 0x67ef, 0x69 => 0x67b4, 0x6a => 0x67ec, 0x6b => 0x67b3, + 0x6c => 0x67e9, 0x6d => 0x67b8, 0x6e => 0x67e4, 0x6f => 0x67de, + 0x70 => 0x67dd, 0x71 => 0x67e2, 0x72 => 0x67ee, 0x73 => 0x67b9, + 0x74 => 0x67ce, 0x75 => 0x67c6, 0x76 => 0x67e7, 0x77 => 0x6a9c, + 0x78 => 0x681e, 0x79 => 0x6846, 0x7a => 0x6829, 0x7b => 0x6840, + 0x7c => 0x684d, 0x7d => 0x6832, 0x7e => 0x684e, 0x80 => 0x68b3, + 0x81 => 0x682b, 0x82 => 0x6859, 0x83 => 0x6863, 0x84 => 0x6877, + 0x85 => 0x687f, 0x86 => 0x689f, 0x87 => 0x688f, 0x88 => 0x68ad, + 0x89 => 0x6894, 0x8a => 0x689d, 0x8b => 0x689b, 0x8c => 0x6883, + 0x8d => 0x6aae, 0x8e => 0x68b9, 0x8f => 0x6874, 0x90 => 0x68b5, + 0x91 => 0x68a0, 0x92 => 0x68ba, 0x93 => 0x690f, 0x94 => 0x688d, + 0x95 => 0x687e, 0x96 => 0x6901, 0x97 => 0x68ca, 0x98 => 0x6908, + 0x99 => 0x68d8, 0x9a => 0x6922, 0x9b => 0x6926, 0x9c => 0x68e1, + 0x9d => 0x690c, 0x9e => 0x68cd, 0x9f => 0x68d4, 0xa0 => 0x68e7, + 0xa1 => 0x68d5, 0xa2 => 0x6936, 0xa3 => 0x6912, 0xa4 => 0x6904, + 0xa5 => 0x68d7, 0xa6 => 0x68e3, 0xa7 => 0x6925, 0xa8 => 0x68f9, + 0xa9 => 0x68e0, 0xaa => 0x68ef, 0xab => 0x6928, 0xac => 0x692a, + 0xad => 0x691a, 0xae => 0x6923, 0xaf => 0x6921, 0xb0 => 0x68c6, + 0xb1 => 0x6979, 0xb2 => 0x6977, 0xb3 => 0x695c, 0xb4 => 0x6978, + 0xb5 => 0x696b, 0xb6 => 0x6954, 0xb7 => 0x697e, 0xb8 => 0x696e, + 0xb9 => 0x6939, 0xba => 0x6974, 0xbb => 0x693d, 0xbc => 0x6959, + 0xbd => 0x6930, 0xbe => 0x6961, 0xbf => 0x695e, 0xc0 => 0x695d, + 0xc1 => 0x6981, 0xc2 => 0x696a, 0xc3 => 0x69b2, 0xc4 => 0x69ae, + 0xc5 => 0x69d0, 0xc6 => 0x69bf, 0xc7 => 0x69c1, 0xc8 => 0x69d3, + 0xc9 => 0x69be, 0xca => 0x69ce, 0xcb => 0x5be8, 0xcc => 0x69ca, + 0xcd => 0x69dd, 0xce => 0x69bb, 0xcf => 0x69c3, 0xd0 => 0x69a7, + 0xd1 => 0x6a2e, 0xd2 => 0x6991, 0xd3 => 0x69a0, 0xd4 => 0x699c, + 0xd5 => 0x6995, 0xd6 => 0x69b4, 0xd7 => 0x69de, 0xd8 => 0x69e8, + 0xd9 => 0x6a02, 0xda => 0x6a1b, 0xdb => 0x69ff, 0xdc => 0x6b0a, + 0xdd => 0x69f9, 0xde => 0x69f2, 0xdf => 0x69e7, 0xe0 => 0x6a05, + 0xe1 => 0x69b1, 0xe2 => 0x6a1e, 0xe3 => 0x69ed, 0xe4 => 0x6a14, + 0xe5 => 0x69eb, 0xe6 => 0x6a0a, 0xe7 => 0x6a12, 0xe8 => 0x6ac1, + 0xe9 => 0x6a23, 0xea => 0x6a13, 0xeb => 0x6a44, 0xec => 0x6a0c, + 0xed => 0x6a72, 0xee => 0x6a36, 0xef => 0x6a78, 0xf0 => 0x6a47, + 0xf1 => 0x6a62, 0xf2 => 0x6a59, 0xf3 => 0x6a66, 0xf4 => 0x6a48, + 0xf5 => 0x6a38, 0xf6 => 0x6a22, 0xf7 => 0x6a90, 0xf8 => 0x6a8d, + 0xf9 => 0x6aa0, 0xfa => 0x6a84, 0xfb => 0x6aa2, 0xfc => 0x6aa3, + }, + 0x9f => { + 0x40 => 0x6a97, 0x41 => 0x8617, 0x42 => 0x6abb, 0x43 => 0x6ac3, + 0x44 => 0x6ac2, 0x45 => 0x6ab8, 0x46 => 0x6ab3, 0x47 => 0x6aac, + 0x48 => 0x6ade, 0x49 => 0x6ad1, 0x4a => 0x6adf, 0x4b => 0x6aaa, + 0x4c => 0x6ada, 0x4d => 0x6aea, 0x4e => 0x6afb, 0x4f => 0x6b05, + 0x50 => 0x8616, 0x51 => 0x6afa, 0x52 => 0x6b12, 0x53 => 0x6b16, + 0x54 => 0x9b31, 0x55 => 0x6b1f, 0x56 => 0x6b38, 0x57 => 0x6b37, + 0x58 => 0x76dc, 0x59 => 0x6b39, 0x5a => 0x98ee, 0x5b => 0x6b47, + 0x5c => 0x6b43, 0x5d => 0x6b49, 0x5e => 0x6b50, 0x5f => 0x6b59, + 0x60 => 0x6b54, 0x61 => 0x6b5b, 0x62 => 0x6b5f, 0x63 => 0x6b61, + 0x64 => 0x6b78, 0x65 => 0x6b79, 0x66 => 0x6b7f, 0x67 => 0x6b80, + 0x68 => 0x6b84, 0x69 => 0x6b83, 0x6a => 0x6b8d, 0x6b => 0x6b98, + 0x6c => 0x6b95, 0x6d => 0x6b9e, 0x6e => 0x6ba4, 0x6f => 0x6baa, + 0x70 => 0x6bab, 0x71 => 0x6baf, 0x72 => 0x6bb2, 0x73 => 0x6bb1, + 0x74 => 0x6bb3, 0x75 => 0x6bb7, 0x76 => 0x6bbc, 0x77 => 0x6bc6, + 0x78 => 0x6bcb, 0x79 => 0x6bd3, 0x7a => 0x6bdf, 0x7b => 0x6bec, + 0x7c => 0x6beb, 0x7d => 0x6bf3, 0x7e => 0x6bef, 0x80 => 0x9ebe, + 0x81 => 0x6c08, 0x82 => 0x6c13, 0x83 => 0x6c14, 0x84 => 0x6c1b, + 0x85 => 0x6c24, 0x86 => 0x6c23, 0x87 => 0x6c5e, 0x88 => 0x6c55, + 0x89 => 0x6c62, 0x8a => 0x6c6a, 0x8b => 0x6c82, 0x8c => 0x6c8d, + 0x8d => 0x6c9a, 0x8e => 0x6c81, 0x8f => 0x6c9b, 0x90 => 0x6c7e, + 0x91 => 0x6c68, 0x92 => 0x6c73, 0x93 => 0x6c92, 0x94 => 0x6c90, + 0x95 => 0x6cc4, 0x96 => 0x6cf1, 0x97 => 0x6cd3, 0x98 => 0x6cbd, + 0x99 => 0x6cd7, 0x9a => 0x6cc5, 0x9b => 0x6cdd, 0x9c => 0x6cae, + 0x9d => 0x6cb1, 0x9e => 0x6cbe, 0x9f => 0x6cba, 0xa0 => 0x6cdb, + 0xa1 => 0x6cef, 0xa2 => 0x6cd9, 0xa3 => 0x6cea, 0xa4 => 0x6d1f, + 0xa5 => 0x884d, 0xa6 => 0x6d36, 0xa7 => 0x6d2b, 0xa8 => 0x6d3d, + 0xa9 => 0x6d38, 0xaa => 0x6d19, 0xab => 0x6d35, 0xac => 0x6d33, + 0xad => 0x6d12, 0xae => 0x6d0c, 0xaf => 0x6d63, 0xb0 => 0x6d93, + 0xb1 => 0x6d64, 0xb2 => 0x6d5a, 0xb3 => 0x6d79, 0xb4 => 0x6d59, + 0xb5 => 0x6d8e, 0xb6 => 0x6d95, 0xb7 => 0x6fe4, 0xb8 => 0x6d85, + 0xb9 => 0x6df9, 0xba => 0x6e15, 0xbb => 0x6e0a, 0xbc => 0x6db5, + 0xbd => 0x6dc7, 0xbe => 0x6de6, 0xbf => 0x6db8, 0xc0 => 0x6dc6, + 0xc1 => 0x6dec, 0xc2 => 0x6dde, 0xc3 => 0x6dcc, 0xc4 => 0x6de8, + 0xc5 => 0x6dd2, 0xc6 => 0x6dc5, 0xc7 => 0x6dfa, 0xc8 => 0x6dd9, + 0xc9 => 0x6de4, 0xca => 0x6dd5, 0xcb => 0x6dea, 0xcc => 0x6dee, + 0xcd => 0x6e2d, 0xce => 0x6e6e, 0xcf => 0x6e2e, 0xd0 => 0x6e19, + 0xd1 => 0x6e72, 0xd2 => 0x6e5f, 0xd3 => 0x6e3e, 0xd4 => 0x6e23, + 0xd5 => 0x6e6b, 0xd6 => 0x6e2b, 0xd7 => 0x6e76, 0xd8 => 0x6e4d, + 0xd9 => 0x6e1f, 0xda => 0x6e43, 0xdb => 0x6e3a, 0xdc => 0x6e4e, + 0xdd => 0x6e24, 0xde => 0x6eff, 0xdf => 0x6e1d, 0xe0 => 0x6e38, + 0xe1 => 0x6e82, 0xe2 => 0x6eaa, 0xe3 => 0x6e98, 0xe4 => 0x6ec9, + 0xe5 => 0x6eb7, 0xe6 => 0x6ed3, 0xe7 => 0x6ebd, 0xe8 => 0x6eaf, + 0xe9 => 0x6ec4, 0xea => 0x6eb2, 0xeb => 0x6ed4, 0xec => 0x6ed5, + 0xed => 0x6e8f, 0xee => 0x6ea5, 0xef => 0x6ec2, 0xf0 => 0x6e9f, + 0xf1 => 0x6f41, 0xf2 => 0x6f11, 0xf3 => 0x704c, 0xf4 => 0x6eec, + 0xf5 => 0x6ef8, 0xf6 => 0x6efe, 0xf7 => 0x6f3f, 0xf8 => 0x6ef2, + 0xf9 => 0x6f31, 0xfa => 0x6eef, 0xfb => 0x6f32, 0xfc => 0x6ecc, + }, + 0xe0 => { + 0x40 => 0x6f3e, 0x41 => 0x6f13, 0x42 => 0x6ef7, 0x43 => 0x6f86, + 0x44 => 0x6f7a, 0x45 => 0x6f78, 0x46 => 0x6f81, 0x47 => 0x6f80, + 0x48 => 0x6f6f, 0x49 => 0x6f5b, 0x4a => 0x6ff3, 0x4b => 0x6f6d, + 0x4c => 0x6f82, 0x4d => 0x6f7c, 0x4e => 0x6f58, 0x4f => 0x6f8e, + 0x50 => 0x6f91, 0x51 => 0x6fc2, 0x52 => 0x6f66, 0x53 => 0x6fb3, + 0x54 => 0x6fa3, 0x55 => 0x6fa1, 0x56 => 0x6fa4, 0x57 => 0x6fb9, + 0x58 => 0x6fc6, 0x59 => 0x6faa, 0x5a => 0x6fdf, 0x5b => 0x6fd5, + 0x5c => 0x6fec, 0x5d => 0x6fd4, 0x5e => 0x6fd8, 0x5f => 0x6ff1, + 0x60 => 0x6fee, 0x61 => 0x6fdb, 0x62 => 0x7009, 0x63 => 0x700b, + 0x64 => 0x6ffa, 0x65 => 0x7011, 0x66 => 0x7001, 0x67 => 0x700f, + 0x68 => 0x6ffe, 0x69 => 0x701b, 0x6a => 0x701a, 0x6b => 0x6f74, + 0x6c => 0x701d, 0x6d => 0x7018, 0x6e => 0x701f, 0x6f => 0x7030, + 0x70 => 0x703e, 0x71 => 0x7032, 0x72 => 0x7051, 0x73 => 0x7063, + 0x74 => 0x7099, 0x75 => 0x7092, 0x76 => 0x70af, 0x77 => 0x70f1, + 0x78 => 0x70ac, 0x79 => 0x70b8, 0x7a => 0x70b3, 0x7b => 0x70ae, + 0x7c => 0x70df, 0x7d => 0x70cb, 0x7e => 0x70dd, 0x80 => 0x70d9, + 0x81 => 0x7109, 0x82 => 0x70fd, 0x83 => 0x711c, 0x84 => 0x7119, + 0x85 => 0x7165, 0x86 => 0x7155, 0x87 => 0x7188, 0x88 => 0x7166, + 0x89 => 0x7162, 0x8a => 0x714c, 0x8b => 0x7156, 0x8c => 0x716c, + 0x8d => 0x718f, 0x8e => 0x71fb, 0x8f => 0x7184, 0x90 => 0x7195, + 0x91 => 0x71a8, 0x92 => 0x71ac, 0x93 => 0x71d7, 0x94 => 0x71b9, + 0x95 => 0x71be, 0x96 => 0x71d2, 0x97 => 0x71c9, 0x98 => 0x71d4, + 0x99 => 0x71ce, 0x9a => 0x71e0, 0x9b => 0x71ec, 0x9c => 0x71e7, + 0x9d => 0x71f5, 0x9e => 0x71fc, 0x9f => 0x71f9, 0xa0 => 0x71ff, + 0xa1 => 0x720d, 0xa2 => 0x7210, 0xa3 => 0x721b, 0xa4 => 0x7228, + 0xa5 => 0x722d, 0xa6 => 0x722c, 0xa7 => 0x7230, 0xa8 => 0x7232, + 0xa9 => 0x723b, 0xaa => 0x723c, 0xab => 0x723f, 0xac => 0x7240, + 0xad => 0x7246, 0xae => 0x724b, 0xaf => 0x7258, 0xb0 => 0x7274, + 0xb1 => 0x727e, 0xb2 => 0x7282, 0xb3 => 0x7281, 0xb4 => 0x7287, + 0xb5 => 0x7292, 0xb6 => 0x7296, 0xb7 => 0x72a2, 0xb8 => 0x72a7, + 0xb9 => 0x72b9, 0xba => 0x72b2, 0xbb => 0x72c3, 0xbc => 0x72c6, + 0xbd => 0x72c4, 0xbe => 0x72ce, 0xbf => 0x72d2, 0xc0 => 0x72e2, + 0xc1 => 0x72e0, 0xc2 => 0x72e1, 0xc3 => 0x72f9, 0xc4 => 0x72f7, + 0xc5 => 0x500f, 0xc6 => 0x7317, 0xc7 => 0x730a, 0xc8 => 0x731c, + 0xc9 => 0x7316, 0xca => 0x731d, 0xcb => 0x7334, 0xcc => 0x732f, + 0xcd => 0x7329, 0xce => 0x7325, 0xcf => 0x733e, 0xd0 => 0x734e, + 0xd1 => 0x734f, 0xd2 => 0x9ed8, 0xd3 => 0x7357, 0xd4 => 0x736a, + 0xd5 => 0x7368, 0xd6 => 0x7370, 0xd7 => 0x7378, 0xd8 => 0x7375, + 0xd9 => 0x737b, 0xda => 0x737a, 0xdb => 0x73c8, 0xdc => 0x73b3, + 0xdd => 0x73ce, 0xde => 0x73bb, 0xdf => 0x73c0, 0xe0 => 0x73e5, + 0xe1 => 0x73ee, 0xe2 => 0x73de, 0xe3 => 0x74a2, 0xe4 => 0x7405, + 0xe5 => 0x746f, 0xe6 => 0x7425, 0xe7 => 0x73f8, 0xe8 => 0x7432, + 0xe9 => 0x743a, 0xea => 0x7455, 0xeb => 0x743f, 0xec => 0x745f, + 0xed => 0x7459, 0xee => 0x7441, 0xef => 0x745c, 0xf0 => 0x7469, + 0xf1 => 0x7470, 0xf2 => 0x7463, 0xf3 => 0x746a, 0xf4 => 0x7476, + 0xf5 => 0x747e, 0xf6 => 0x748b, 0xf7 => 0x749e, 0xf8 => 0x74a7, + 0xf9 => 0x74ca, 0xfa => 0x74cf, 0xfb => 0x74d4, 0xfc => 0x73f1, + }, + 0xe1 => { + 0x40 => 0x74e0, 0x41 => 0x74e3, 0x42 => 0x74e7, 0x43 => 0x74e9, + 0x44 => 0x74ee, 0x45 => 0x74f2, 0x46 => 0x74f0, 0x47 => 0x74f1, + 0x48 => 0x74f8, 0x49 => 0x74f7, 0x4a => 0x7504, 0x4b => 0x7503, + 0x4c => 0x7505, 0x4d => 0x750c, 0x4e => 0x750e, 0x4f => 0x750d, + 0x50 => 0x7515, 0x51 => 0x7513, 0x52 => 0x751e, 0x53 => 0x7526, + 0x54 => 0x752c, 0x55 => 0x753c, 0x56 => 0x7544, 0x57 => 0x754d, + 0x58 => 0x754a, 0x59 => 0x7549, 0x5a => 0x755b, 0x5b => 0x7546, + 0x5c => 0x755a, 0x5d => 0x7569, 0x5e => 0x7564, 0x5f => 0x7567, + 0x60 => 0x756b, 0x61 => 0x756d, 0x62 => 0x7578, 0x63 => 0x7576, + 0x64 => 0x7586, 0x65 => 0x7587, 0x66 => 0x7574, 0x67 => 0x758a, + 0x68 => 0x7589, 0x69 => 0x7582, 0x6a => 0x7594, 0x6b => 0x759a, + 0x6c => 0x759d, 0x6d => 0x75a5, 0x6e => 0x75a3, 0x6f => 0x75c2, + 0x70 => 0x75b3, 0x71 => 0x75c3, 0x72 => 0x75b5, 0x73 => 0x75bd, + 0x74 => 0x75b8, 0x75 => 0x75bc, 0x76 => 0x75b1, 0x77 => 0x75cd, + 0x78 => 0x75ca, 0x79 => 0x75d2, 0x7a => 0x75d9, 0x7b => 0x75e3, + 0x7c => 0x75de, 0x7d => 0x75fe, 0x7e => 0x75ff, 0x80 => 0x75fc, + 0x81 => 0x7601, 0x82 => 0x75f0, 0x83 => 0x75fa, 0x84 => 0x75f2, + 0x85 => 0x75f3, 0x86 => 0x760b, 0x87 => 0x760d, 0x88 => 0x7609, + 0x89 => 0x761f, 0x8a => 0x7627, 0x8b => 0x7620, 0x8c => 0x7621, + 0x8d => 0x7622, 0x8e => 0x7624, 0x8f => 0x7634, 0x90 => 0x7630, + 0x91 => 0x763b, 0x92 => 0x7647, 0x93 => 0x7648, 0x94 => 0x7646, + 0x95 => 0x765c, 0x96 => 0x7658, 0x97 => 0x7661, 0x98 => 0x7662, + 0x99 => 0x7668, 0x9a => 0x7669, 0x9b => 0x766a, 0x9c => 0x7667, + 0x9d => 0x766c, 0x9e => 0x7670, 0x9f => 0x7672, 0xa0 => 0x7676, + 0xa1 => 0x7678, 0xa2 => 0x767c, 0xa3 => 0x7680, 0xa4 => 0x7683, + 0xa5 => 0x7688, 0xa6 => 0x768b, 0xa7 => 0x768e, 0xa8 => 0x7696, + 0xa9 => 0x7693, 0xaa => 0x7699, 0xab => 0x769a, 0xac => 0x76b0, + 0xad => 0x76b4, 0xae => 0x76b8, 0xaf => 0x76b9, 0xb0 => 0x76ba, + 0xb1 => 0x76c2, 0xb2 => 0x76cd, 0xb3 => 0x76d6, 0xb4 => 0x76d2, + 0xb5 => 0x76de, 0xb6 => 0x76e1, 0xb7 => 0x76e5, 0xb8 => 0x76e7, + 0xb9 => 0x76ea, 0xba => 0x862f, 0xbb => 0x76fb, 0xbc => 0x7708, + 0xbd => 0x7707, 0xbe => 0x7704, 0xbf => 0x7729, 0xc0 => 0x7724, + 0xc1 => 0x771e, 0xc2 => 0x7725, 0xc3 => 0x7726, 0xc4 => 0x771b, + 0xc5 => 0x7737, 0xc6 => 0x7738, 0xc7 => 0x7747, 0xc8 => 0x775a, + 0xc9 => 0x7768, 0xca => 0x776b, 0xcb => 0x775b, 0xcc => 0x7765, + 0xcd => 0x777f, 0xce => 0x777e, 0xcf => 0x7779, 0xd0 => 0x778e, + 0xd1 => 0x778b, 0xd2 => 0x7791, 0xd3 => 0x77a0, 0xd4 => 0x779e, + 0xd5 => 0x77b0, 0xd6 => 0x77b6, 0xd7 => 0x77b9, 0xd8 => 0x77bf, + 0xd9 => 0x77bc, 0xda => 0x77bd, 0xdb => 0x77bb, 0xdc => 0x77c7, + 0xdd => 0x77cd, 0xde => 0x77d7, 0xdf => 0x77da, 0xe0 => 0x77dc, + 0xe1 => 0x77e3, 0xe2 => 0x77ee, 0xe3 => 0x77fc, 0xe4 => 0x780c, + 0xe5 => 0x7812, 0xe6 => 0x7926, 0xe7 => 0x7820, 0xe8 => 0x792a, + 0xe9 => 0x7845, 0xea => 0x788e, 0xeb => 0x7874, 0xec => 0x7886, + 0xed => 0x787c, 0xee => 0x789a, 0xef => 0x788c, 0xf0 => 0x78a3, + 0xf1 => 0x78b5, 0xf2 => 0x78aa, 0xf3 => 0x78af, 0xf4 => 0x78d1, + 0xf5 => 0x78c6, 0xf6 => 0x78cb, 0xf7 => 0x78d4, 0xf8 => 0x78be, + 0xf9 => 0x78bc, 0xfa => 0x78c5, 0xfb => 0x78ca, 0xfc => 0x78ec, + }, + 0xe2 => { + 0x40 => 0x78e7, 0x41 => 0x78da, 0x42 => 0x78fd, 0x43 => 0x78f4, + 0x44 => 0x7907, 0x45 => 0x7912, 0x46 => 0x7911, 0x47 => 0x7919, + 0x48 => 0x792c, 0x49 => 0x792b, 0x4a => 0x7940, 0x4b => 0x7960, + 0x4c => 0x7957, 0x4d => 0x795f, 0x4e => 0x795a, 0x4f => 0x7955, + 0x50 => 0x7953, 0x51 => 0x797a, 0x52 => 0x797f, 0x53 => 0x798a, + 0x54 => 0x799d, 0x55 => 0x79a7, 0x56 => 0x9f4b, 0x57 => 0x79aa, + 0x58 => 0x79ae, 0x59 => 0x79b3, 0x5a => 0x79b9, 0x5b => 0x79ba, + 0x5c => 0x79c9, 0x5d => 0x79d5, 0x5e => 0x79e7, 0x5f => 0x79ec, + 0x60 => 0x79e1, 0x61 => 0x79e3, 0x62 => 0x7a08, 0x63 => 0x7a0d, + 0x64 => 0x7a18, 0x65 => 0x7a19, 0x66 => 0x7a20, 0x67 => 0x7a1f, + 0x68 => 0x7980, 0x69 => 0x7a31, 0x6a => 0x7a3b, 0x6b => 0x7a3e, + 0x6c => 0x7a37, 0x6d => 0x7a43, 0x6e => 0x7a57, 0x6f => 0x7a49, + 0x70 => 0x7a61, 0x71 => 0x7a62, 0x72 => 0x7a69, 0x73 => 0x9f9d, + 0x74 => 0x7a70, 0x75 => 0x7a79, 0x76 => 0x7a7d, 0x77 => 0x7a88, + 0x78 => 0x7a97, 0x79 => 0x7a95, 0x7a => 0x7a98, 0x7b => 0x7a96, + 0x7c => 0x7aa9, 0x7d => 0x7ac8, 0x7e => 0x7ab0, 0x80 => 0x7ab6, + 0x81 => 0x7ac5, 0x82 => 0x7ac4, 0x83 => 0x7abf, 0x84 => 0x9083, + 0x85 => 0x7ac7, 0x86 => 0x7aca, 0x87 => 0x7acd, 0x88 => 0x7acf, + 0x89 => 0x7ad5, 0x8a => 0x7ad3, 0x8b => 0x7ad9, 0x8c => 0x7ada, + 0x8d => 0x7add, 0x8e => 0x7ae1, 0x8f => 0x7ae2, 0x90 => 0x7ae6, + 0x91 => 0x7aed, 0x92 => 0x7af0, 0x93 => 0x7b02, 0x94 => 0x7b0f, + 0x95 => 0x7b0a, 0x96 => 0x7b06, 0x97 => 0x7b33, 0x98 => 0x7b18, + 0x99 => 0x7b19, 0x9a => 0x7b1e, 0x9b => 0x7b35, 0x9c => 0x7b28, + 0x9d => 0x7b36, 0x9e => 0x7b50, 0x9f => 0x7b7a, 0xa0 => 0x7b04, + 0xa1 => 0x7b4d, 0xa2 => 0x7b0b, 0xa3 => 0x7b4c, 0xa4 => 0x7b45, + 0xa5 => 0x7b75, 0xa6 => 0x7b65, 0xa7 => 0x7b74, 0xa8 => 0x7b67, + 0xa9 => 0x7b70, 0xaa => 0x7b71, 0xab => 0x7b6c, 0xac => 0x7b6e, + 0xad => 0x7b9d, 0xae => 0x7b98, 0xaf => 0x7b9f, 0xb0 => 0x7b8d, + 0xb1 => 0x7b9c, 0xb2 => 0x7b9a, 0xb3 => 0x7b8b, 0xb4 => 0x7b92, + 0xb5 => 0x7b8f, 0xb6 => 0x7b5d, 0xb7 => 0x7b99, 0xb8 => 0x7bcb, + 0xb9 => 0x7bc1, 0xba => 0x7bcc, 0xbb => 0x7bcf, 0xbc => 0x7bb4, + 0xbd => 0x7bc6, 0xbe => 0x7bdd, 0xbf => 0x7be9, 0xc0 => 0x7c11, + 0xc1 => 0x7c14, 0xc2 => 0x7be6, 0xc3 => 0x7be5, 0xc4 => 0x7c60, + 0xc5 => 0x7c00, 0xc6 => 0x7c07, 0xc7 => 0x7c13, 0xc8 => 0x7bf3, + 0xc9 => 0x7bf7, 0xca => 0x7c17, 0xcb => 0x7c0d, 0xcc => 0x7bf6, + 0xcd => 0x7c23, 0xce => 0x7c27, 0xcf => 0x7c2a, 0xd0 => 0x7c1f, + 0xd1 => 0x7c37, 0xd2 => 0x7c2b, 0xd3 => 0x7c3d, 0xd4 => 0x7c4c, + 0xd5 => 0x7c43, 0xd6 => 0x7c54, 0xd7 => 0x7c4f, 0xd8 => 0x7c40, + 0xd9 => 0x7c50, 0xda => 0x7c58, 0xdb => 0x7c5f, 0xdc => 0x7c64, + 0xdd => 0x7c56, 0xde => 0x7c65, 0xdf => 0x7c6c, 0xe0 => 0x7c75, + 0xe1 => 0x7c83, 0xe2 => 0x7c90, 0xe3 => 0x7ca4, 0xe4 => 0x7cad, + 0xe5 => 0x7ca2, 0xe6 => 0x7cab, 0xe7 => 0x7ca1, 0xe8 => 0x7ca8, + 0xe9 => 0x7cb3, 0xea => 0x7cb2, 0xeb => 0x7cb1, 0xec => 0x7cae, + 0xed => 0x7cb9, 0xee => 0x7cbd, 0xef => 0x7cc0, 0xf0 => 0x7cc5, + 0xf1 => 0x7cc2, 0xf2 => 0x7cd8, 0xf3 => 0x7cd2, 0xf4 => 0x7cdc, + 0xf5 => 0x7ce2, 0xf6 => 0x9b3b, 0xf7 => 0x7cef, 0xf8 => 0x7cf2, + 0xf9 => 0x7cf4, 0xfa => 0x7cf6, 0xfb => 0x7cfa, 0xfc => 0x7d06, + }, + 0xe3 => { + 0x40 => 0x7d02, 0x41 => 0x7d1c, 0x42 => 0x7d15, 0x43 => 0x7d0a, + 0x44 => 0x7d45, 0x45 => 0x7d4b, 0x46 => 0x7d2e, 0x47 => 0x7d32, + 0x48 => 0x7d3f, 0x49 => 0x7d35, 0x4a => 0x7d46, 0x4b => 0x7d73, + 0x4c => 0x7d56, 0x4d => 0x7d4e, 0x4e => 0x7d72, 0x4f => 0x7d68, + 0x50 => 0x7d6e, 0x51 => 0x7d4f, 0x52 => 0x7d63, 0x53 => 0x7d93, + 0x54 => 0x7d89, 0x55 => 0x7d5b, 0x56 => 0x7d8f, 0x57 => 0x7d7d, + 0x58 => 0x7d9b, 0x59 => 0x7dba, 0x5a => 0x7dae, 0x5b => 0x7da3, + 0x5c => 0x7db5, 0x5d => 0x7dc7, 0x5e => 0x7dbd, 0x5f => 0x7dab, + 0x60 => 0x7e3d, 0x61 => 0x7da2, 0x62 => 0x7daf, 0x63 => 0x7ddc, + 0x64 => 0x7db8, 0x65 => 0x7d9f, 0x66 => 0x7db0, 0x67 => 0x7dd8, + 0x68 => 0x7ddd, 0x69 => 0x7de4, 0x6a => 0x7dde, 0x6b => 0x7dfb, + 0x6c => 0x7df2, 0x6d => 0x7de1, 0x6e => 0x7e05, 0x6f => 0x7e0a, + 0x70 => 0x7e23, 0x71 => 0x7e21, 0x72 => 0x7e12, 0x73 => 0x7e31, + 0x74 => 0x7e1f, 0x75 => 0x7e09, 0x76 => 0x7e0b, 0x77 => 0x7e22, + 0x78 => 0x7e46, 0x79 => 0x7e66, 0x7a => 0x7e3b, 0x7b => 0x7e35, + 0x7c => 0x7e39, 0x7d => 0x7e43, 0x7e => 0x7e37, 0x80 => 0x7e32, + 0x81 => 0x7e3a, 0x82 => 0x7e67, 0x83 => 0x7e5d, 0x84 => 0x7e56, + 0x85 => 0x7e5e, 0x86 => 0x7e59, 0x87 => 0x7e5a, 0x88 => 0x7e79, + 0x89 => 0x7e6a, 0x8a => 0x7e69, 0x8b => 0x7e7c, 0x8c => 0x7e7b, + 0x8d => 0x7e83, 0x8e => 0x7dd5, 0x8f => 0x7e7d, 0x90 => 0x8fae, + 0x91 => 0x7e7f, 0x92 => 0x7e88, 0x93 => 0x7e89, 0x94 => 0x7e8c, + 0x95 => 0x7e92, 0x96 => 0x7e90, 0x97 => 0x7e93, 0x98 => 0x7e94, + 0x99 => 0x7e96, 0x9a => 0x7e8e, 0x9b => 0x7e9b, 0x9c => 0x7e9c, + 0x9d => 0x7f38, 0x9e => 0x7f3a, 0x9f => 0x7f45, 0xa0 => 0x7f4c, + 0xa1 => 0x7f4d, 0xa2 => 0x7f4e, 0xa3 => 0x7f50, 0xa4 => 0x7f51, + 0xa5 => 0x7f55, 0xa6 => 0x7f54, 0xa7 => 0x7f58, 0xa8 => 0x7f5f, + 0xa9 => 0x7f60, 0xaa => 0x7f68, 0xab => 0x7f69, 0xac => 0x7f67, + 0xad => 0x7f78, 0xae => 0x7f82, 0xaf => 0x7f86, 0xb0 => 0x7f83, + 0xb1 => 0x7f88, 0xb2 => 0x7f87, 0xb3 => 0x7f8c, 0xb4 => 0x7f94, + 0xb5 => 0x7f9e, 0xb6 => 0x7f9d, 0xb7 => 0x7f9a, 0xb8 => 0x7fa3, + 0xb9 => 0x7faf, 0xba => 0x7fb2, 0xbb => 0x7fb9, 0xbc => 0x7fae, + 0xbd => 0x7fb6, 0xbe => 0x7fb8, 0xbf => 0x8b71, 0xc0 => 0x7fc5, + 0xc1 => 0x7fc6, 0xc2 => 0x7fca, 0xc3 => 0x7fd5, 0xc4 => 0x7fd4, + 0xc5 => 0x7fe1, 0xc6 => 0x7fe6, 0xc7 => 0x7fe9, 0xc8 => 0x7ff3, + 0xc9 => 0x7ff9, 0xca => 0x98dc, 0xcb => 0x8006, 0xcc => 0x8004, + 0xcd => 0x800b, 0xce => 0x8012, 0xcf => 0x8018, 0xd0 => 0x8019, + 0xd1 => 0x801c, 0xd2 => 0x8021, 0xd3 => 0x8028, 0xd4 => 0x803f, + 0xd5 => 0x803b, 0xd6 => 0x804a, 0xd7 => 0x8046, 0xd8 => 0x8052, + 0xd9 => 0x8058, 0xda => 0x805a, 0xdb => 0x805f, 0xdc => 0x8062, + 0xdd => 0x8068, 0xde => 0x8073, 0xdf => 0x8072, 0xe0 => 0x8070, + 0xe1 => 0x8076, 0xe2 => 0x8079, 0xe3 => 0x807d, 0xe4 => 0x807f, + 0xe5 => 0x8084, 0xe6 => 0x8086, 0xe7 => 0x8085, 0xe8 => 0x809b, + 0xe9 => 0x8093, 0xea => 0x809a, 0xeb => 0x80ad, 0xec => 0x5190, + 0xed => 0x80ac, 0xee => 0x80db, 0xef => 0x80e5, 0xf0 => 0x80d9, + 0xf1 => 0x80dd, 0xf2 => 0x80c4, 0xf3 => 0x80da, 0xf4 => 0x80d6, + 0xf5 => 0x8109, 0xf6 => 0x80ef, 0xf7 => 0x80f1, 0xf8 => 0x811b, + 0xf9 => 0x8129, 0xfa => 0x8123, 0xfb => 0x812f, 0xfc => 0x814b, + }, + 0xe4 => { + 0x40 => 0x968b, 0x41 => 0x8146, 0x42 => 0x813e, 0x43 => 0x8153, + 0x44 => 0x8151, 0x45 => 0x80fc, 0x46 => 0x8171, 0x47 => 0x816e, + 0x48 => 0x8165, 0x49 => 0x8166, 0x4a => 0x8174, 0x4b => 0x8183, + 0x4c => 0x8188, 0x4d => 0x818a, 0x4e => 0x8180, 0x4f => 0x8182, + 0x50 => 0x81a0, 0x51 => 0x8195, 0x52 => 0x81a4, 0x53 => 0x81a3, + 0x54 => 0x815f, 0x55 => 0x8193, 0x56 => 0x81a9, 0x57 => 0x81b0, + 0x58 => 0x81b5, 0x59 => 0x81be, 0x5a => 0x81b8, 0x5b => 0x81bd, + 0x5c => 0x81c0, 0x5d => 0x81c2, 0x5e => 0x81ba, 0x5f => 0x81c9, + 0x60 => 0x81cd, 0x61 => 0x81d1, 0x62 => 0x81d9, 0x63 => 0x81d8, + 0x64 => 0x81c8, 0x65 => 0x81da, 0x66 => 0x81df, 0x67 => 0x81e0, + 0x68 => 0x81e7, 0x69 => 0x81fa, 0x6a => 0x81fb, 0x6b => 0x81fe, + 0x6c => 0x8201, 0x6d => 0x8202, 0x6e => 0x8205, 0x6f => 0x8207, + 0x70 => 0x820a, 0x71 => 0x820d, 0x72 => 0x8210, 0x73 => 0x8216, + 0x74 => 0x8229, 0x75 => 0x822b, 0x76 => 0x8238, 0x77 => 0x8233, + 0x78 => 0x8240, 0x79 => 0x8259, 0x7a => 0x8258, 0x7b => 0x825d, + 0x7c => 0x825a, 0x7d => 0x825f, 0x7e => 0x8264, 0x80 => 0x8262, + 0x81 => 0x8268, 0x82 => 0x826a, 0x83 => 0x826b, 0x84 => 0x822e, + 0x85 => 0x8271, 0x86 => 0x8277, 0x87 => 0x8278, 0x88 => 0x827e, + 0x89 => 0x828d, 0x8a => 0x8292, 0x8b => 0x82ab, 0x8c => 0x829f, + 0x8d => 0x82bb, 0x8e => 0x82ac, 0x8f => 0x82e1, 0x90 => 0x82e3, + 0x91 => 0x82df, 0x92 => 0x82d2, 0x93 => 0x82f4, 0x94 => 0x82f3, + 0x95 => 0x82fa, 0x96 => 0x8393, 0x97 => 0x8303, 0x98 => 0x82fb, + 0x99 => 0x82f9, 0x9a => 0x82de, 0x9b => 0x8306, 0x9c => 0x82dc, + 0x9d => 0x8309, 0x9e => 0x82d9, 0x9f => 0x8335, 0xa0 => 0x8334, + 0xa1 => 0x8316, 0xa2 => 0x8332, 0xa3 => 0x8331, 0xa4 => 0x8340, + 0xa5 => 0x8339, 0xa6 => 0x8350, 0xa7 => 0x8345, 0xa8 => 0x832f, + 0xa9 => 0x832b, 0xaa => 0x8317, 0xab => 0x8318, 0xac => 0x8385, + 0xad => 0x839a, 0xae => 0x83aa, 0xaf => 0x839f, 0xb0 => 0x83a2, + 0xb1 => 0x8396, 0xb2 => 0x8323, 0xb3 => 0x838e, 0xb4 => 0x8387, + 0xb5 => 0x838a, 0xb6 => 0x837c, 0xb7 => 0x83b5, 0xb8 => 0x8373, + 0xb9 => 0x8375, 0xba => 0x83a0, 0xbb => 0x8389, 0xbc => 0x83a8, + 0xbd => 0x83f4, 0xbe => 0x8413, 0xbf => 0x83eb, 0xc0 => 0x83ce, + 0xc1 => 0x83fd, 0xc2 => 0x8403, 0xc3 => 0x83d8, 0xc4 => 0x840b, + 0xc5 => 0x83c1, 0xc6 => 0x83f7, 0xc7 => 0x8407, 0xc8 => 0x83e0, + 0xc9 => 0x83f2, 0xca => 0x840d, 0xcb => 0x8422, 0xcc => 0x8420, + 0xcd => 0x83bd, 0xce => 0x8438, 0xcf => 0x8506, 0xd0 => 0x83fb, + 0xd1 => 0x846d, 0xd2 => 0x842a, 0xd3 => 0x843c, 0xd4 => 0x855a, + 0xd5 => 0x8484, 0xd6 => 0x8477, 0xd7 => 0x846b, 0xd8 => 0x84ad, + 0xd9 => 0x846e, 0xda => 0x8482, 0xdb => 0x8469, 0xdc => 0x8446, + 0xdd => 0x842c, 0xde => 0x846f, 0xdf => 0x8479, 0xe0 => 0x8435, + 0xe1 => 0x84ca, 0xe2 => 0x8462, 0xe3 => 0x84b9, 0xe4 => 0x84bf, + 0xe5 => 0x849f, 0xe6 => 0x84d9, 0xe7 => 0x84cd, 0xe8 => 0x84bb, + 0xe9 => 0x84da, 0xea => 0x84d0, 0xeb => 0x84c1, 0xec => 0x84c6, + 0xed => 0x84d6, 0xee => 0x84a1, 0xef => 0x8521, 0xf0 => 0x84ff, + 0xf1 => 0x84f4, 0xf2 => 0x8517, 0xf3 => 0x8518, 0xf4 => 0x852c, + 0xf5 => 0x851f, 0xf6 => 0x8515, 0xf7 => 0x8514, 0xf8 => 0x84fc, + 0xf9 => 0x8540, 0xfa => 0x8563, 0xfb => 0x8558, 0xfc => 0x8548, + }, + 0xe5 => { + 0x40 => 0x8541, 0x41 => 0x8602, 0x42 => 0x854b, 0x43 => 0x8555, + 0x44 => 0x8580, 0x45 => 0x85a4, 0x46 => 0x8588, 0x47 => 0x8591, + 0x48 => 0x858a, 0x49 => 0x85a8, 0x4a => 0x856d, 0x4b => 0x8594, + 0x4c => 0x859b, 0x4d => 0x85ea, 0x4e => 0x8587, 0x4f => 0x859c, + 0x50 => 0x8577, 0x51 => 0x857e, 0x52 => 0x8590, 0x53 => 0x85c9, + 0x54 => 0x85ba, 0x55 => 0x85cf, 0x56 => 0x85b9, 0x57 => 0x85d0, + 0x58 => 0x85d5, 0x59 => 0x85dd, 0x5a => 0x85e5, 0x5b => 0x85dc, + 0x5c => 0x85f9, 0x5d => 0x860a, 0x5e => 0x8613, 0x5f => 0x860b, + 0x60 => 0x85fe, 0x61 => 0x85fa, 0x62 => 0x8606, 0x63 => 0x8622, + 0x64 => 0x861a, 0x65 => 0x8630, 0x66 => 0x863f, 0x67 => 0x864d, + 0x68 => 0x4e55, 0x69 => 0x8654, 0x6a => 0x865f, 0x6b => 0x8667, + 0x6c => 0x8671, 0x6d => 0x8693, 0x6e => 0x86a3, 0x6f => 0x86a9, + 0x70 => 0x86aa, 0x71 => 0x868b, 0x72 => 0x868c, 0x73 => 0x86b6, + 0x74 => 0x86af, 0x75 => 0x86c4, 0x76 => 0x86c6, 0x77 => 0x86b0, + 0x78 => 0x86c9, 0x79 => 0x8823, 0x7a => 0x86ab, 0x7b => 0x86d4, + 0x7c => 0x86de, 0x7d => 0x86e9, 0x7e => 0x86ec, 0x80 => 0x86df, + 0x81 => 0x86db, 0x82 => 0x86ef, 0x83 => 0x8712, 0x84 => 0x8706, + 0x85 => 0x8708, 0x86 => 0x8700, 0x87 => 0x8703, 0x88 => 0x86fb, + 0x89 => 0x8711, 0x8a => 0x8709, 0x8b => 0x870d, 0x8c => 0x86f9, + 0x8d => 0x870a, 0x8e => 0x8734, 0x8f => 0x873f, 0x90 => 0x8737, + 0x91 => 0x873b, 0x92 => 0x8725, 0x93 => 0x8729, 0x94 => 0x871a, + 0x95 => 0x8760, 0x96 => 0x875f, 0x97 => 0x8778, 0x98 => 0x874c, + 0x99 => 0x874e, 0x9a => 0x8774, 0x9b => 0x8757, 0x9c => 0x8768, + 0x9d => 0x876e, 0x9e => 0x8759, 0x9f => 0x8753, 0xa0 => 0x8763, + 0xa1 => 0x876a, 0xa2 => 0x8805, 0xa3 => 0x87a2, 0xa4 => 0x879f, + 0xa5 => 0x8782, 0xa6 => 0x87af, 0xa7 => 0x87cb, 0xa8 => 0x87bd, + 0xa9 => 0x87c0, 0xaa => 0x87d0, 0xab => 0x96d6, 0xac => 0x87ab, + 0xad => 0x87c4, 0xae => 0x87b3, 0xaf => 0x87c7, 0xb0 => 0x87c6, + 0xb1 => 0x87bb, 0xb2 => 0x87ef, 0xb3 => 0x87f2, 0xb4 => 0x87e0, + 0xb5 => 0x880f, 0xb6 => 0x880d, 0xb7 => 0x87fe, 0xb8 => 0x87f6, + 0xb9 => 0x87f7, 0xba => 0x880e, 0xbb => 0x87d2, 0xbc => 0x8811, + 0xbd => 0x8816, 0xbe => 0x8815, 0xbf => 0x8822, 0xc0 => 0x8821, + 0xc1 => 0x8831, 0xc2 => 0x8836, 0xc3 => 0x8839, 0xc4 => 0x8827, + 0xc5 => 0x883b, 0xc6 => 0x8844, 0xc7 => 0x8842, 0xc8 => 0x8852, + 0xc9 => 0x8859, 0xca => 0x885e, 0xcb => 0x8862, 0xcc => 0x886b, + 0xcd => 0x8881, 0xce => 0x887e, 0xcf => 0x889e, 0xd0 => 0x8875, + 0xd1 => 0x887d, 0xd2 => 0x88b5, 0xd3 => 0x8872, 0xd4 => 0x8882, + 0xd5 => 0x8897, 0xd6 => 0x8892, 0xd7 => 0x88ae, 0xd8 => 0x8899, + 0xd9 => 0x88a2, 0xda => 0x888d, 0xdb => 0x88a4, 0xdc => 0x88b0, + 0xdd => 0x88bf, 0xde => 0x88b1, 0xdf => 0x88c3, 0xe0 => 0x88c4, + 0xe1 => 0x88d4, 0xe2 => 0x88d8, 0xe3 => 0x88d9, 0xe4 => 0x88dd, + 0xe5 => 0x88f9, 0xe6 => 0x8902, 0xe7 => 0x88fc, 0xe8 => 0x88f4, + 0xe9 => 0x88e8, 0xea => 0x88f2, 0xeb => 0x8904, 0xec => 0x890c, + 0xed => 0x890a, 0xee => 0x8913, 0xef => 0x8943, 0xf0 => 0x891e, + 0xf1 => 0x8925, 0xf2 => 0x892a, 0xf3 => 0x892b, 0xf4 => 0x8941, + 0xf5 => 0x8944, 0xf6 => 0x893b, 0xf7 => 0x8936, 0xf8 => 0x8938, + 0xf9 => 0x894c, 0xfa => 0x891d, 0xfb => 0x8960, 0xfc => 0x895e, + }, + 0xe6 => { + 0x40 => 0x8966, 0x41 => 0x8964, 0x42 => 0x896d, 0x43 => 0x896a, + 0x44 => 0x896f, 0x45 => 0x8974, 0x46 => 0x8977, 0x47 => 0x897e, + 0x48 => 0x8983, 0x49 => 0x8988, 0x4a => 0x898a, 0x4b => 0x8993, + 0x4c => 0x8998, 0x4d => 0x89a1, 0x4e => 0x89a9, 0x4f => 0x89a6, + 0x50 => 0x89ac, 0x51 => 0x89af, 0x52 => 0x89b2, 0x53 => 0x89ba, + 0x54 => 0x89bd, 0x55 => 0x89bf, 0x56 => 0x89c0, 0x57 => 0x89da, + 0x58 => 0x89dc, 0x59 => 0x89dd, 0x5a => 0x89e7, 0x5b => 0x89f4, + 0x5c => 0x89f8, 0x5d => 0x8a03, 0x5e => 0x8a16, 0x5f => 0x8a10, + 0x60 => 0x8a0c, 0x61 => 0x8a1b, 0x62 => 0x8a1d, 0x63 => 0x8a25, + 0x64 => 0x8a36, 0x65 => 0x8a41, 0x66 => 0x8a5b, 0x67 => 0x8a52, + 0x68 => 0x8a46, 0x69 => 0x8a48, 0x6a => 0x8a7c, 0x6b => 0x8a6d, + 0x6c => 0x8a6c, 0x6d => 0x8a62, 0x6e => 0x8a85, 0x6f => 0x8a82, + 0x70 => 0x8a84, 0x71 => 0x8aa8, 0x72 => 0x8aa1, 0x73 => 0x8a91, + 0x74 => 0x8aa5, 0x75 => 0x8aa6, 0x76 => 0x8a9a, 0x77 => 0x8aa3, + 0x78 => 0x8ac4, 0x79 => 0x8acd, 0x7a => 0x8ac2, 0x7b => 0x8ada, + 0x7c => 0x8aeb, 0x7d => 0x8af3, 0x7e => 0x8ae7, 0x80 => 0x8ae4, + 0x81 => 0x8af1, 0x82 => 0x8b14, 0x83 => 0x8ae0, 0x84 => 0x8ae2, + 0x85 => 0x8af7, 0x86 => 0x8ade, 0x87 => 0x8adb, 0x88 => 0x8b0c, + 0x89 => 0x8b07, 0x8a => 0x8b1a, 0x8b => 0x8ae1, 0x8c => 0x8b16, + 0x8d => 0x8b10, 0x8e => 0x8b17, 0x8f => 0x8b20, 0x90 => 0x8b33, + 0x91 => 0x97ab, 0x92 => 0x8b26, 0x93 => 0x8b2b, 0x94 => 0x8b3e, + 0x95 => 0x8b28, 0x96 => 0x8b41, 0x97 => 0x8b4c, 0x98 => 0x8b4f, + 0x99 => 0x8b4e, 0x9a => 0x8b49, 0x9b => 0x8b56, 0x9c => 0x8b5b, + 0x9d => 0x8b5a, 0x9e => 0x8b6b, 0x9f => 0x8b5f, 0xa0 => 0x8b6c, + 0xa1 => 0x8b6f, 0xa2 => 0x8b74, 0xa3 => 0x8b7d, 0xa4 => 0x8b80, + 0xa5 => 0x8b8c, 0xa6 => 0x8b8e, 0xa7 => 0x8b92, 0xa8 => 0x8b93, + 0xa9 => 0x8b96, 0xaa => 0x8b99, 0xab => 0x8b9a, 0xac => 0x8c3a, + 0xad => 0x8c41, 0xae => 0x8c3f, 0xaf => 0x8c48, 0xb0 => 0x8c4c, + 0xb1 => 0x8c4e, 0xb2 => 0x8c50, 0xb3 => 0x8c55, 0xb4 => 0x8c62, + 0xb5 => 0x8c6c, 0xb6 => 0x8c78, 0xb7 => 0x8c7a, 0xb8 => 0x8c82, + 0xb9 => 0x8c89, 0xba => 0x8c85, 0xbb => 0x8c8a, 0xbc => 0x8c8d, + 0xbd => 0x8c8e, 0xbe => 0x8c94, 0xbf => 0x8c7c, 0xc0 => 0x8c98, + 0xc1 => 0x621d, 0xc2 => 0x8cad, 0xc3 => 0x8caa, 0xc4 => 0x8cbd, + 0xc5 => 0x8cb2, 0xc6 => 0x8cb3, 0xc7 => 0x8cae, 0xc8 => 0x8cb6, + 0xc9 => 0x8cc8, 0xca => 0x8cc1, 0xcb => 0x8ce4, 0xcc => 0x8ce3, + 0xcd => 0x8cda, 0xce => 0x8cfd, 0xcf => 0x8cfa, 0xd0 => 0x8cfb, + 0xd1 => 0x8d04, 0xd2 => 0x8d05, 0xd3 => 0x8d0a, 0xd4 => 0x8d07, + 0xd5 => 0x8d0f, 0xd6 => 0x8d0d, 0xd7 => 0x8d10, 0xd8 => 0x9f4e, + 0xd9 => 0x8d13, 0xda => 0x8ccd, 0xdb => 0x8d14, 0xdc => 0x8d16, + 0xdd => 0x8d67, 0xde => 0x8d6d, 0xdf => 0x8d71, 0xe0 => 0x8d73, + 0xe1 => 0x8d81, 0xe2 => 0x8d99, 0xe3 => 0x8dc2, 0xe4 => 0x8dbe, + 0xe5 => 0x8dba, 0xe6 => 0x8dcf, 0xe7 => 0x8dda, 0xe8 => 0x8dd6, + 0xe9 => 0x8dcc, 0xea => 0x8ddb, 0xeb => 0x8dcb, 0xec => 0x8dea, + 0xed => 0x8deb, 0xee => 0x8ddf, 0xef => 0x8de3, 0xf0 => 0x8dfc, + 0xf1 => 0x8e08, 0xf2 => 0x8e09, 0xf3 => 0x8dff, 0xf4 => 0x8e1d, + 0xf5 => 0x8e1e, 0xf6 => 0x8e10, 0xf7 => 0x8e1f, 0xf8 => 0x8e42, + 0xf9 => 0x8e35, 0xfa => 0x8e30, 0xfb => 0x8e34, 0xfc => 0x8e4a, + }, + 0xe7 => { + 0x40 => 0x8e47, 0x41 => 0x8e49, 0x42 => 0x8e4c, 0x43 => 0x8e50, + 0x44 => 0x8e48, 0x45 => 0x8e59, 0x46 => 0x8e64, 0x47 => 0x8e60, + 0x48 => 0x8e2a, 0x49 => 0x8e63, 0x4a => 0x8e55, 0x4b => 0x8e76, + 0x4c => 0x8e72, 0x4d => 0x8e7c, 0x4e => 0x8e81, 0x4f => 0x8e87, + 0x50 => 0x8e85, 0x51 => 0x8e84, 0x52 => 0x8e8b, 0x53 => 0x8e8a, + 0x54 => 0x8e93, 0x55 => 0x8e91, 0x56 => 0x8e94, 0x57 => 0x8e99, + 0x58 => 0x8eaa, 0x59 => 0x8ea1, 0x5a => 0x8eac, 0x5b => 0x8eb0, + 0x5c => 0x8ec6, 0x5d => 0x8eb1, 0x5e => 0x8ebe, 0x5f => 0x8ec5, + 0x60 => 0x8ec8, 0x61 => 0x8ecb, 0x62 => 0x8edb, 0x63 => 0x8ee3, + 0x64 => 0x8efc, 0x65 => 0x8efb, 0x66 => 0x8eeb, 0x67 => 0x8efe, + 0x68 => 0x8f0a, 0x69 => 0x8f05, 0x6a => 0x8f15, 0x6b => 0x8f12, + 0x6c => 0x8f19, 0x6d => 0x8f13, 0x6e => 0x8f1c, 0x6f => 0x8f1f, + 0x70 => 0x8f1b, 0x71 => 0x8f0c, 0x72 => 0x8f26, 0x73 => 0x8f33, + 0x74 => 0x8f3b, 0x75 => 0x8f39, 0x76 => 0x8f45, 0x77 => 0x8f42, + 0x78 => 0x8f3e, 0x79 => 0x8f4c, 0x7a => 0x8f49, 0x7b => 0x8f46, + 0x7c => 0x8f4e, 0x7d => 0x8f57, 0x7e => 0x8f5c, 0x80 => 0x8f62, + 0x81 => 0x8f63, 0x82 => 0x8f64, 0x83 => 0x8f9c, 0x84 => 0x8f9f, + 0x85 => 0x8fa3, 0x86 => 0x8fad, 0x87 => 0x8faf, 0x88 => 0x8fb7, + 0x89 => 0x8fda, 0x8a => 0x8fe5, 0x8b => 0x8fe2, 0x8c => 0x8fea, + 0x8d => 0x8fef, 0x8e => 0x9087, 0x8f => 0x8ff4, 0x90 => 0x9005, + 0x91 => 0x8ff9, 0x92 => 0x8ffa, 0x93 => 0x9011, 0x94 => 0x9015, + 0x95 => 0x9021, 0x96 => 0x900d, 0x97 => 0x901e, 0x98 => 0x9016, + 0x99 => 0x900b, 0x9a => 0x9027, 0x9b => 0x9036, 0x9c => 0x9035, + 0x9d => 0x9039, 0x9e => 0x8ff8, 0x9f => 0x904f, 0xa0 => 0x9050, + 0xa1 => 0x9051, 0xa2 => 0x9052, 0xa3 => 0x900e, 0xa4 => 0x9049, + 0xa5 => 0x903e, 0xa6 => 0x9056, 0xa7 => 0x9058, 0xa8 => 0x905e, + 0xa9 => 0x9068, 0xaa => 0x906f, 0xab => 0x9076, 0xac => 0x96a8, + 0xad => 0x9072, 0xae => 0x9082, 0xaf => 0x907d, 0xb0 => 0x9081, + 0xb1 => 0x9080, 0xb2 => 0x908a, 0xb3 => 0x9089, 0xb4 => 0x908f, + 0xb5 => 0x90a8, 0xb6 => 0x90af, 0xb7 => 0x90b1, 0xb8 => 0x90b5, + 0xb9 => 0x90e2, 0xba => 0x90e4, 0xbb => 0x6248, 0xbc => 0x90db, + 0xbd => 0x9102, 0xbe => 0x9112, 0xbf => 0x9119, 0xc0 => 0x9132, + 0xc1 => 0x9130, 0xc2 => 0x914a, 0xc3 => 0x9156, 0xc4 => 0x9158, + 0xc5 => 0x9163, 0xc6 => 0x9165, 0xc7 => 0x9169, 0xc8 => 0x9173, + 0xc9 => 0x9172, 0xca => 0x918b, 0xcb => 0x9189, 0xcc => 0x9182, + 0xcd => 0x91a2, 0xce => 0x91ab, 0xcf => 0x91af, 0xd0 => 0x91aa, + 0xd1 => 0x91b5, 0xd2 => 0x91b4, 0xd3 => 0x91ba, 0xd4 => 0x91c0, + 0xd5 => 0x91c1, 0xd6 => 0x91c9, 0xd7 => 0x91cb, 0xd8 => 0x91d0, + 0xd9 => 0x91d6, 0xda => 0x91df, 0xdb => 0x91e1, 0xdc => 0x91db, + 0xdd => 0x91fc, 0xde => 0x91f5, 0xdf => 0x91f6, 0xe0 => 0x921e, + 0xe1 => 0x91ff, 0xe2 => 0x9214, 0xe3 => 0x922c, 0xe4 => 0x9215, + 0xe5 => 0x9211, 0xe6 => 0x925e, 0xe7 => 0x9257, 0xe8 => 0x9245, + 0xe9 => 0x9249, 0xea => 0x9264, 0xeb => 0x9248, 0xec => 0x9295, + 0xed => 0x923f, 0xee => 0x924b, 0xef => 0x9250, 0xf0 => 0x929c, + 0xf1 => 0x9296, 0xf2 => 0x9293, 0xf3 => 0x929b, 0xf4 => 0x925a, + 0xf5 => 0x92cf, 0xf6 => 0x92b9, 0xf7 => 0x92b7, 0xf8 => 0x92e9, + 0xf9 => 0x930f, 0xfa => 0x92fa, 0xfb => 0x9344, 0xfc => 0x932e, + }, + 0xe8 => { + 0x40 => 0x9319, 0x41 => 0x9322, 0x42 => 0x931a, 0x43 => 0x9323, + 0x44 => 0x933a, 0x45 => 0x9335, 0x46 => 0x933b, 0x47 => 0x935c, + 0x48 => 0x9360, 0x49 => 0x937c, 0x4a => 0x936e, 0x4b => 0x9356, + 0x4c => 0x93b0, 0x4d => 0x93ac, 0x4e => 0x93ad, 0x4f => 0x9394, + 0x50 => 0x93b9, 0x51 => 0x93d6, 0x52 => 0x93d7, 0x53 => 0x93e8, + 0x54 => 0x93e5, 0x55 => 0x93d8, 0x56 => 0x93c3, 0x57 => 0x93dd, + 0x58 => 0x93d0, 0x59 => 0x93c8, 0x5a => 0x93e4, 0x5b => 0x941a, + 0x5c => 0x9414, 0x5d => 0x9413, 0x5e => 0x9403, 0x5f => 0x9407, + 0x60 => 0x9410, 0x61 => 0x9436, 0x62 => 0x942b, 0x63 => 0x9435, + 0x64 => 0x9421, 0x65 => 0x943a, 0x66 => 0x9441, 0x67 => 0x9452, + 0x68 => 0x9444, 0x69 => 0x945b, 0x6a => 0x9460, 0x6b => 0x9462, + 0x6c => 0x945e, 0x6d => 0x946a, 0x6e => 0x9229, 0x6f => 0x9470, + 0x70 => 0x9475, 0x71 => 0x9477, 0x72 => 0x947d, 0x73 => 0x945a, + 0x74 => 0x947c, 0x75 => 0x947e, 0x76 => 0x9481, 0x77 => 0x947f, + 0x78 => 0x9582, 0x79 => 0x9587, 0x7a => 0x958a, 0x7b => 0x9594, + 0x7c => 0x9596, 0x7d => 0x9598, 0x7e => 0x9599, 0x80 => 0x95a0, + 0x81 => 0x95a8, 0x82 => 0x95a7, 0x83 => 0x95ad, 0x84 => 0x95bc, + 0x85 => 0x95bb, 0x86 => 0x95b9, 0x87 => 0x95be, 0x88 => 0x95ca, + 0x89 => 0x6ff6, 0x8a => 0x95c3, 0x8b => 0x95cd, 0x8c => 0x95cc, + 0x8d => 0x95d5, 0x8e => 0x95d4, 0x8f => 0x95d6, 0x90 => 0x95dc, + 0x91 => 0x95e1, 0x92 => 0x95e5, 0x93 => 0x95e2, 0x94 => 0x9621, + 0x95 => 0x9628, 0x96 => 0x962e, 0x97 => 0x962f, 0x98 => 0x9642, + 0x99 => 0x964c, 0x9a => 0x964f, 0x9b => 0x964b, 0x9c => 0x9677, + 0x9d => 0x965c, 0x9e => 0x965e, 0x9f => 0x965d, 0xa0 => 0x965f, + 0xa1 => 0x9666, 0xa2 => 0x9672, 0xa3 => 0x966c, 0xa4 => 0x968d, + 0xa5 => 0x9698, 0xa6 => 0x9695, 0xa7 => 0x9697, 0xa8 => 0x96aa, + 0xa9 => 0x96a7, 0xaa => 0x96b1, 0xab => 0x96b2, 0xac => 0x96b0, + 0xad => 0x96b4, 0xae => 0x96b6, 0xaf => 0x96b8, 0xb0 => 0x96b9, + 0xb1 => 0x96ce, 0xb2 => 0x96cb, 0xb3 => 0x96c9, 0xb4 => 0x96cd, + 0xb5 => 0x894d, 0xb6 => 0x96dc, 0xb7 => 0x970d, 0xb8 => 0x96d5, + 0xb9 => 0x96f9, 0xba => 0x9704, 0xbb => 0x9706, 0xbc => 0x9708, + 0xbd => 0x9713, 0xbe => 0x970e, 0xbf => 0x9711, 0xc0 => 0x970f, + 0xc1 => 0x9716, 0xc2 => 0x9719, 0xc3 => 0x9724, 0xc4 => 0x972a, + 0xc5 => 0x9730, 0xc6 => 0x9739, 0xc7 => 0x973d, 0xc8 => 0x973e, + 0xc9 => 0x9744, 0xca => 0x9746, 0xcb => 0x9748, 0xcc => 0x9742, + 0xcd => 0x9749, 0xce => 0x975c, 0xcf => 0x9760, 0xd0 => 0x9764, + 0xd1 => 0x9766, 0xd2 => 0x9768, 0xd3 => 0x52d2, 0xd4 => 0x976b, + 0xd5 => 0x9771, 0xd6 => 0x9779, 0xd7 => 0x9785, 0xd8 => 0x977c, + 0xd9 => 0x9781, 0xda => 0x977a, 0xdb => 0x9786, 0xdc => 0x978b, + 0xdd => 0x978f, 0xde => 0x9790, 0xdf => 0x979c, 0xe0 => 0x97a8, + 0xe1 => 0x97a6, 0xe2 => 0x97a3, 0xe3 => 0x97b3, 0xe4 => 0x97b4, + 0xe5 => 0x97c3, 0xe6 => 0x97c6, 0xe7 => 0x97c8, 0xe8 => 0x97cb, + 0xe9 => 0x97dc, 0xea => 0x97ed, 0xeb => 0x9f4f, 0xec => 0x97f2, + 0xed => 0x7adf, 0xee => 0x97f6, 0xef => 0x97f5, 0xf0 => 0x980f, + 0xf1 => 0x980c, 0xf2 => 0x9838, 0xf3 => 0x9824, 0xf4 => 0x9821, + 0xf5 => 0x9837, 0xf6 => 0x983d, 0xf7 => 0x9846, 0xf8 => 0x984f, + 0xf9 => 0x984b, 0xfa => 0x986b, 0xfb => 0x986f, 0xfc => 0x9870, + }, + 0xe9 => { + 0x40 => 0x9871, 0x41 => 0x9874, 0x42 => 0x9873, 0x43 => 0x98aa, + 0x44 => 0x98af, 0x45 => 0x98b1, 0x46 => 0x98b6, 0x47 => 0x98c4, + 0x48 => 0x98c3, 0x49 => 0x98c6, 0x4a => 0x98e9, 0x4b => 0x98eb, + 0x4c => 0x9903, 0x4d => 0x9909, 0x4e => 0x9912, 0x4f => 0x9914, + 0x50 => 0x9918, 0x51 => 0x9921, 0x52 => 0x991d, 0x53 => 0x991e, + 0x54 => 0x9924, 0x55 => 0x9920, 0x56 => 0x992c, 0x57 => 0x992e, + 0x58 => 0x993d, 0x59 => 0x993e, 0x5a => 0x9942, 0x5b => 0x9949, + 0x5c => 0x9945, 0x5d => 0x9950, 0x5e => 0x994b, 0x5f => 0x9951, + 0x60 => 0x9952, 0x61 => 0x994c, 0x62 => 0x9955, 0x63 => 0x9997, + 0x64 => 0x9998, 0x65 => 0x99a5, 0x66 => 0x99ad, 0x67 => 0x99ae, + 0x68 => 0x99bc, 0x69 => 0x99df, 0x6a => 0x99db, 0x6b => 0x99dd, + 0x6c => 0x99d8, 0x6d => 0x99d1, 0x6e => 0x99ed, 0x6f => 0x99ee, + 0x70 => 0x99f1, 0x71 => 0x99f2, 0x72 => 0x99fb, 0x73 => 0x99f8, + 0x74 => 0x9a01, 0x75 => 0x9a0f, 0x76 => 0x9a05, 0x77 => 0x99e2, + 0x78 => 0x9a19, 0x79 => 0x9a2b, 0x7a => 0x9a37, 0x7b => 0x9a45, + 0x7c => 0x9a42, 0x7d => 0x9a40, 0x7e => 0x9a43, 0x80 => 0x9a3e, + 0x81 => 0x9a55, 0x82 => 0x9a4d, 0x83 => 0x9a5b, 0x84 => 0x9a57, + 0x85 => 0x9a5f, 0x86 => 0x9a62, 0x87 => 0x9a65, 0x88 => 0x9a64, + 0x89 => 0x9a69, 0x8a => 0x9a6b, 0x8b => 0x9a6a, 0x8c => 0x9aad, + 0x8d => 0x9ab0, 0x8e => 0x9abc, 0x8f => 0x9ac0, 0x90 => 0x9acf, + 0x91 => 0x9ad1, 0x92 => 0x9ad3, 0x93 => 0x9ad4, 0x94 => 0x9ade, + 0x95 => 0x9adf, 0x96 => 0x9ae2, 0x97 => 0x9ae3, 0x98 => 0x9ae6, + 0x99 => 0x9aef, 0x9a => 0x9aeb, 0x9b => 0x9aee, 0x9c => 0x9af4, + 0x9d => 0x9af1, 0x9e => 0x9af7, 0x9f => 0x9afb, 0xa0 => 0x9b06, + 0xa1 => 0x9b18, 0xa2 => 0x9b1a, 0xa3 => 0x9b1f, 0xa4 => 0x9b22, + 0xa5 => 0x9b23, 0xa6 => 0x9b25, 0xa7 => 0x9b27, 0xa8 => 0x9b28, + 0xa9 => 0x9b29, 0xaa => 0x9b2a, 0xab => 0x9b2e, 0xac => 0x9b2f, + 0xad => 0x9b32, 0xae => 0x9b44, 0xaf => 0x9b43, 0xb0 => 0x9b4f, + 0xb1 => 0x9b4d, 0xb2 => 0x9b4e, 0xb3 => 0x9b51, 0xb4 => 0x9b58, + 0xb5 => 0x9b74, 0xb6 => 0x9b93, 0xb7 => 0x9b83, 0xb8 => 0x9b91, + 0xb9 => 0x9b96, 0xba => 0x9b97, 0xbb => 0x9b9f, 0xbc => 0x9ba0, + 0xbd => 0x9ba8, 0xbe => 0x9bb4, 0xbf => 0x9bc0, 0xc0 => 0x9bca, + 0xc1 => 0x9bb9, 0xc2 => 0x9bc6, 0xc3 => 0x9bcf, 0xc4 => 0x9bd1, + 0xc5 => 0x9bd2, 0xc6 => 0x9be3, 0xc7 => 0x9be2, 0xc8 => 0x9be4, + 0xc9 => 0x9bd4, 0xca => 0x9be1, 0xcb => 0x9c3a, 0xcc => 0x9bf2, + 0xcd => 0x9bf1, 0xce => 0x9bf0, 0xcf => 0x9c15, 0xd0 => 0x9c14, + 0xd1 => 0x9c09, 0xd2 => 0x9c13, 0xd3 => 0x9c0c, 0xd4 => 0x9c06, + 0xd5 => 0x9c08, 0xd6 => 0x9c12, 0xd7 => 0x9c0a, 0xd8 => 0x9c04, + 0xd9 => 0x9c2e, 0xda => 0x9c1b, 0xdb => 0x9c25, 0xdc => 0x9c24, + 0xdd => 0x9c21, 0xde => 0x9c30, 0xdf => 0x9c47, 0xe0 => 0x9c32, + 0xe1 => 0x9c46, 0xe2 => 0x9c3e, 0xe3 => 0x9c5a, 0xe4 => 0x9c60, + 0xe5 => 0x9c67, 0xe6 => 0x9c76, 0xe7 => 0x9c78, 0xe8 => 0x9ce7, + 0xe9 => 0x9cec, 0xea => 0x9cf0, 0xeb => 0x9d09, 0xec => 0x9d08, + 0xed => 0x9ceb, 0xee => 0x9d03, 0xef => 0x9d06, 0xf0 => 0x9d2a, + 0xf1 => 0x9d26, 0xf2 => 0x9daf, 0xf3 => 0x9d23, 0xf4 => 0x9d1f, + 0xf5 => 0x9d44, 0xf6 => 0x9d15, 0xf7 => 0x9d12, 0xf8 => 0x9d41, + 0xf9 => 0x9d3f, 0xfa => 0x9d3e, 0xfb => 0x9d46, 0xfc => 0x9d48, + }, + 0xea => { + 0x40 => 0x9d5d, 0x41 => 0x9d5e, 0x42 => 0x9d64, 0x43 => 0x9d51, + 0x44 => 0x9d50, 0x45 => 0x9d59, 0x46 => 0x9d72, 0x47 => 0x9d89, + 0x48 => 0x9d87, 0x49 => 0x9dab, 0x4a => 0x9d6f, 0x4b => 0x9d7a, + 0x4c => 0x9d9a, 0x4d => 0x9da4, 0x4e => 0x9da9, 0x4f => 0x9db2, + 0x50 => 0x9dc4, 0x51 => 0x9dc1, 0x52 => 0x9dbb, 0x53 => 0x9db8, + 0x54 => 0x9dba, 0x55 => 0x9dc6, 0x56 => 0x9dcf, 0x57 => 0x9dc2, + 0x58 => 0x9dd9, 0x59 => 0x9dd3, 0x5a => 0x9df8, 0x5b => 0x9de6, + 0x5c => 0x9ded, 0x5d => 0x9def, 0x5e => 0x9dfd, 0x5f => 0x9e1a, + 0x60 => 0x9e1b, 0x61 => 0x9e1e, 0x62 => 0x9e75, 0x63 => 0x9e79, + 0x64 => 0x9e7d, 0x65 => 0x9e81, 0x66 => 0x9e88, 0x67 => 0x9e8b, + 0x68 => 0x9e8c, 0x69 => 0x9e92, 0x6a => 0x9e95, 0x6b => 0x9e91, + 0x6c => 0x9e9d, 0x6d => 0x9ea5, 0x6e => 0x9ea9, 0x6f => 0x9eb8, + 0x70 => 0x9eaa, 0x71 => 0x9ead, 0x72 => 0x9761, 0x73 => 0x9ecc, + 0x74 => 0x9ece, 0x75 => 0x9ecf, 0x76 => 0x9ed0, 0x77 => 0x9ed4, + 0x78 => 0x9edc, 0x79 => 0x9ede, 0x7a => 0x9edd, 0x7b => 0x9ee0, + 0x7c => 0x9ee5, 0x7d => 0x9ee8, 0x7e => 0x9eef, 0x80 => 0x9ef4, + 0x81 => 0x9ef6, 0x82 => 0x9ef7, 0x83 => 0x9ef9, 0x84 => 0x9efb, + 0x85 => 0x9efc, 0x86 => 0x9efd, 0x87 => 0x9f07, 0x88 => 0x9f08, + 0x89 => 0x76b7, 0x8a => 0x9f15, 0x8b => 0x9f21, 0x8c => 0x9f2c, + 0x8d => 0x9f3e, 0x8e => 0x9f4a, 0x8f => 0x9f52, 0x90 => 0x9f54, + 0x91 => 0x9f63, 0x92 => 0x9f5f, 0x93 => 0x9f60, 0x94 => 0x9f61, + 0x95 => 0x9f66, 0x96 => 0x9f67, 0x97 => 0x9f6c, 0x98 => 0x9f6a, + 0x99 => 0x9f77, 0x9a => 0x9f72, 0x9b => 0x9f76, 0x9c => 0x9f95, + 0x9d => 0x9f9c, 0x9e => 0x9fa0, 0x9f => 0x582f, 0xa0 => 0x69c7, + 0xa1 => 0x9059, 0xa2 => 0x7464, 0xa3 => 0x51dc, 0xa4 => 0x7199, + }, + 0xeb => { + 0x41 => [0x3001,0xf87e], 0x42 => [0x3002,0xf87e], 0x50 => [0xffe3,0xf87e], + 0x51 => 0xfe33, 0x5b => [0x30fc,0xf87e], 0x5c => 0xfe31, + 0x5d => [0x2010,0xf87e], 0x60 => [0x301c,0xf87e], 0x61 => [0x2016,0xf87e], + 0x62 => [0xff5c,0xf87e], 0x63 => [0x2026,0xf87e], 0x64 => 0xfe30, + 0x69 => 0xfe35, 0x6a => 0xfe36, 0x6b => 0xfe39, 0x6c => 0xfe3a, + 0x6d => [0xff3b,0xf87e], 0x6e => [0xff3d,0xf87e], 0x6f => 0xfe37, + 0x70 => 0xfe38, 0x71 => 0xfe3f, 0x72 => 0xfe40, 0x73 => 0xfe3d, + 0x74 => 0xfe3e, 0x75 => 0xfe41, 0x76 => 0xfe42, 0x77 => 0xfe43, + 0x78 => 0xfe44, 0x79 => 0xfe3b, 0x7a => 0xfe3c, 0x81 => [0xff1d,0xf87e], + }, + 0xec => { + 0x9f => [0x3041,0xf87e], 0xa1 => [0x3043,0xf87e], 0xa3 => [0x3045,0xf87e], + 0xa5 => [0x3047,0xf87e], 0xa7 => [0x3049,0xf87e], 0xc1 => [0x3063,0xf87e], + 0xe1 => [0x3083,0xf87e], 0xe3 => [0x3085,0xf87e], 0xe5 => [0x3087,0xf87e], + 0xec => [0x308e,0xf87e], + }, + 0xed => { + 0x40 => [0x30a1,0xf87e], 0x42 => [0x30a3,0xf87e], 0x44 => [0x30a5,0xf87e], + 0x46 => [0x30a7,0xf87e], 0x48 => [0x30a9,0xf87e], 0x62 => [0x30c3,0xf87e], + 0x83 => [0x30e3,0xf87e], 0x85 => [0x30e5,0xf87e], 0x87 => [0x30e7,0xf87e], + 0x8e => [0x30ee,0xf87e], 0x95 => [0x30f5,0xf87e], 0x96 => [0x30f6,0xf87e], + }, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacKorean.pm b/ExifTool/lib/Image/ExifTool/Charset/MacKorean.pm new file mode 100644 index 0000000..c8f6e72 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacKorean.pm @@ -0,0 +1,2720 @@ +#------------------------------------------------------------------------------ +# File: MacKorean.pm +# +# Description: Mac Korean to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/KOREAN.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacKorean = ( + 0x80 => 0xa0, 0x81 => 0x20a9, 0x82 => [0x2013,0xf87f], 0x83 => 0xa9, + 0x84 => [0xff3f,0xf87f], 0xff => [0x2026,0xf87f], + 0xa1 => { + 0x41 => [0x300c,0xf87f], 0x42 => [0x300d,0xf87f], 0x43 => [0x300c,0xf87b], + 0x44 => [0x300d,0xf87b], 0x45 => [0x300c,0xf87c], 0x46 => [0x300d,0xf87c], + 0x47 => [0x300e,0xf87c], 0x48 => [0x300f,0xf87c], 0x49 => [0x300a,0xf878], + 0x4a => [0x300b,0xf878], 0x4b => [0x3008,0xf878], 0x4c => [0x3009,0xf878], + 0x4d => 0xfe59, 0x4e => 0xfe5a, 0x4f => [0xfe59,0xf87f], + 0x50 => [0xfe5a,0xf87f], 0x51 => [0x2985,0xf87f], 0x52 => [0x2986,0xf87f], + 0x53 => [0x2985,0xf879], 0x54 => [0x2986,0xf879], 0x55 => [0x2985,0xf87c], + 0x56 => [0x2986,0xf87c], 0x57 => [0x28,0xf87c], 0x58 => [0x29,0xf87c], + 0x59 => 0x2985, 0x5a => 0x2986, 0x5b => [0x3010,0xf87f], + 0x5c => [0x3011,0xf87f], 0x5d => 0x3016, 0x5e => 0x3017, 0x5f => 0x3018, + 0x60 => 0x3019, 0x61 => [0x5b,0xf87b], 0x62 => [0x5d,0xf87b], + 0x63 => [0x5b,0xf87c], 0x64 => [0x5d,0xf87c], 0x65 => [0x2985,0xf87b], + 0x66 => [0x2986,0xf87b], 0x67 => [0x2020,0xf87f], 0x68 => [0x2021,0xf87f], + 0x69 => [0x2020,0xf87b], 0x6a => [0x2021,0xf87c], 0x6b => [0x2020,0xf877], + 0x6c => [0x2a,0xf877], 0x6d => 0x2051, 0x6e => 0xf840, 0x6f => 0x201f, + 0x70 => 0x201b, 0x71 => 0x207a, 0x72 => 0x207b, 0x73 => [0xd7,0xf877], + 0x74 => [0x221e,0xf877], 0x75 => [0x223d,0xf877], 0x76 => [0xb1,0xf877], + 0x77 => [0x2213,0xf877], 0x78 => [0x3e,0xf877], 0x79 => [0x3c,0xf877], + 0x7a => 0x207c, 0x7b => [0x2260,0xf877], 0x7c => 0x207d, 0x7d => 0x207e, + 0x81 => [0x7b,0xf877], 0x82 => [0x7d,0xf877], 0x83 => [0x5b,0xf877], + 0x84 => [0x5d,0xf877], 0x85 => [0x2229,0xf877], 0x86 => [0x222a,0xf877], + 0x87 => [0x2282,0xf877], 0x88 => [0x2208,0xf877], 0x89 => [0x2211,0xf877], + 0x8a => [0x21,0xf877], 0x8b => [0x3007,0xf876], 0x8c => [0x4e00,0xf876], + 0x8d => [0x4e8c,0xf876], 0x8e => [0x4e09,0xf876], 0x8f => [0x56db,0xf876], + 0x90 => [0x4e94,0xf876], 0x91 => [0x516d,0xf876], 0x92 => [0x4e03,0xf876], + 0x93 => [0x516b,0xf876], 0x94 => [0x4e5d,0xf876], 0x95 => [0x5341,0xf876], + 0x96 => 0x204c, 0x97 => 0x204d, 0x98 => 0x02bc, 0x99 => 0x2997, + 0x9a => 0x2998, 0x9c => [0xff0a,0xf874], 0x9d => [0x2051,0xf87c], + 0x9e => [0xff0a,0xf875], 0x9f => [0xff0a,0xf871], 0xa0 => [0x2051,0xf879], + 0xa1 => 0x3000, 0xa2 => 0x3001, 0xa3 => 0x3002, 0xa4 => 0xb7, + 0xa5 => 0x2025, 0xa6 => 0x2026, 0xa7 => 0xa8, 0xa8 => 0x3003, + 0xa9 => 0x2013, 0xaa => 0x2014, 0xab => 0x2016, 0xac => 0xff3c, + 0xad => 0x301c, 0xae => 0x2018, 0xaf => 0x2019, 0xb0 => 0x201c, + 0xb1 => 0x201d, 0xb2 => 0x3014, 0xb3 => 0x3015, 0xb4 => 0x3008, + 0xb5 => 0x3009, 0xb6 => 0x300a, 0xb7 => 0x300b, 0xb8 => 0x300c, + 0xb9 => 0x300d, 0xba => 0x300e, 0xbb => 0x300f, 0xbc => 0x3010, + 0xbd => 0x3011, 0xbe => 0xb1, 0xbf => 0xd7, 0xc0 => 0xf7, 0xc1 => 0x2260, + 0xc2 => 0x2264, 0xc3 => 0x2265, 0xc4 => 0x221e, 0xc5 => 0x2234, + 0xc6 => 0xb0, 0xc7 => 0x2032, 0xc8 => 0x2033, 0xc9 => 0x2103, + 0xca => 0x212b, 0xcb => 0xa2, 0xcc => 0xa3, 0xcd => 0xa5, 0xce => 0x2642, + 0xcf => 0x2640, 0xd0 => 0x2220, 0xd1 => 0x22a5, 0xd2 => 0x2312, + 0xd3 => 0x2202, 0xd4 => 0x2207, 0xd5 => 0x2261, 0xd6 => 0x2252, + 0xd7 => 0xa7, 0xd8 => 0x203b, 0xd9 => 0x2606, 0xda => 0x2605, + 0xdb => 0x25cb, 0xdc => 0x25cf, 0xdd => 0x25ce, 0xde => 0x25c7, + 0xdf => 0x25c6, 0xe0 => 0x25a1, 0xe1 => 0x25a0, 0xe2 => 0x25b3, + 0xe3 => 0x25b2, 0xe4 => 0x25bd, 0xe5 => 0x25bc, 0xe6 => 0x2192, + 0xe7 => 0x2190, 0xe8 => 0x2191, 0xe9 => 0x2193, 0xea => 0x2194, + 0xeb => 0x3013, 0xec => 0x226a, 0xed => 0x226b, 0xee => 0x221a, + 0xef => 0x223d, 0xf0 => 0x221d, 0xf1 => 0x2235, 0xf2 => 0x222b, + 0xf3 => 0x222c, 0xf4 => 0x2208, 0xf5 => 0x220b, 0xf6 => 0x2286, + 0xf7 => 0x2287, 0xf8 => 0x2282, 0xf9 => 0x2283, 0xfa => 0x222a, + 0xfb => 0x2229, 0xfc => 0x2227, 0xfd => 0x2228, 0xfe => 0xac, + }, + 0xa2 => { + 0x41 => [0x2985,0xf878], 0x42 => [0x2986,0xf878], 0x43 => [0x2985,0xf873], + 0x44 => [0x2986,0xf873], 0x45 => [0xfe59,0xf87c], 0x46 => [0xfe5a,0xf87c], + 0x47 => [0x3016,0xf878], 0x48 => [0x3017,0xf878], 0x49 => [0x3010,0xf878], + 0x4a => [0x3011,0xf878], 0x4b => [0x28,0xf87f], 0x4c => [0x29,0xf87f], + 0xa1 => 0x21d2, 0xa2 => 0x21d4, 0xa3 => 0x2200, 0xa4 => 0x2203, + 0xa5 => 0xb4, 0xa6 => 0x02dc, 0xa7 => 0x02c7, 0xa8 => 0x02d8, + 0xa9 => 0x02dd, 0xaa => 0x02da, 0xab => 0x02d9, 0xac => 0xb8, + 0xad => 0x02db, 0xae => 0xa1, 0xaf => 0xbf, 0xb0 => 0x02d0, 0xb1 => 0x222e, + 0xb2 => 0x2211, 0xb3 => 0x220f, 0xb4 => 0xa4, 0xb5 => 0x2109, + 0xb6 => 0x2030, 0xb7 => 0x25c1, 0xb8 => 0x25c0, 0xb9 => 0x25b7, + 0xba => 0x25b6, 0xbb => 0x2664, 0xbc => 0x2660, 0xbd => 0x2661, + 0xbe => 0x2665, 0xbf => 0x2667, 0xc0 => 0x2663, 0xc1 => 0x25c9, + 0xc2 => 0x25c8, 0xc3 => 0x25a3, 0xc4 => 0x25d0, 0xc5 => 0x25d1, + 0xc6 => 0x2592, 0xc7 => 0x25a4, 0xc8 => 0x25a5, 0xc9 => 0x25a8, + 0xca => 0x25a7, 0xcb => 0x25a6, 0xcc => 0x25a9, 0xcd => 0x2668, + 0xce => 0x260f, 0xcf => 0x260e, 0xd0 => 0x261c, 0xd1 => 0x261e, + 0xd2 => 0xb6, 0xd3 => 0x2020, 0xd4 => 0x2021, 0xd5 => 0x2195, + 0xd6 => 0x2197, 0xd7 => 0x2199, 0xd8 => 0x2196, 0xd9 => 0x2198, + 0xda => 0x266d, 0xdb => 0x2669, 0xdc => 0x266a, 0xdd => 0x266c, + 0xde => 0x327f, 0xdf => 0x321c, 0xe0 => 0x2116, 0xe1 => 0x33c7, + 0xe2 => 0x2122, 0xe3 => 0x33c2, 0xe4 => 0x33d8, 0xe5 => 0x2121, + 0xe6 => [0x31,0x20de,0xf87c], 0xe7 => [0x32,0x20de,0xf87c], + 0xe8 => [0x33,0x20de,0xf87c], 0xe9 => [0x34,0x20de,0xf87c], + 0xea => [0x35,0x20de,0xf87c], 0xeb => [0x36,0x20de,0xf87c], + 0xec => [0x37,0x20de,0xf87c], 0xed => [0x38,0x20de,0xf87c], + 0xee => [0x39,0x20de,0xf87c], 0xef => [0xf863,0x5b,0x31,0x30,0x5d], + 0xf0 => [0xf863,0x5b,0x31,0x31,0x5d], 0xf1 => [0xf863,0x5b,0x31,0x32,0x5d], + 0xf2 => [0xf863,0x5b,0x31,0x33,0x5d], 0xf3 => [0xf863,0x5b,0x31,0x34,0x5d], + 0xf4 => [0xf863,0x5b,0x31,0x35,0x5d], 0xf5 => [0xf863,0x5b,0x31,0x36,0x5d], + 0xf6 => [0xf863,0x5b,0x31,0x37,0x5d], 0xf7 => [0xf863,0x5b,0x31,0x38,0x5d], + 0xf8 => [0xf863,0x5b,0x31,0x39,0x5d], 0xf9 => [0xf863,0x5b,0x32,0x30,0x5d], + 0xfa => [0xb6,0xf87f], 0xfb => [0x2016,0xf87b], 0xfc => [0x2016,0xf87c], + 0xfd => 0x22ee, 0xfe => 0x2237, + }, + 0xa3 => { + 0x41 => [0x31,0x20de,0xf87b], 0x42 => [0x32,0x20de,0xf87b], + 0x43 => [0x33,0x20de,0xf87b], 0x44 => [0x34,0x20de,0xf87b], + 0x45 => [0x35,0x20de,0xf87b], 0x46 => [0x36,0x20de,0xf87b], + 0x47 => [0x37,0x20de,0xf87b], 0x48 => [0x38,0x20de,0xf87b], + 0x49 => [0x39,0x20de,0xf87b], 0x4a => [0xf864,0x5b,0x31,0x30,0x5d], + 0x4b => [0xf864,0x5b,0x31,0x31,0x5d], 0x4c => [0xf864,0x5b,0x31,0x32,0x5d], + 0x4d => [0xf864,0x5b,0x31,0x33,0x5d], 0x4e => [0xf864,0x5b,0x31,0x34,0x5d], + 0x4f => [0xf864,0x5b,0x31,0x35,0x5d], 0x50 => [0xf864,0x5b,0x31,0x36,0x5d], + 0x51 => [0xf864,0x5b,0x31,0x37,0x5d], 0x52 => [0xf864,0x5b,0x31,0x38,0x5d], + 0x53 => [0xf864,0x5b,0x31,0x39,0x5d], 0x54 => [0xf864,0x5b,0x32,0x30,0x5d], + 0x55 => 0x278a, 0x56 => 0x278b, 0x57 => 0x278c, 0x58 => 0x278d, + 0x59 => 0x278e, 0x5a => 0x278f, 0x5b => 0x2790, 0x5c => 0x2791, + 0x5d => 0x2792, 0x5e => 0x2793, 0x5f => [0x24eb,0xf87f], + 0x60 => [0x24ec,0xf87f], 0x61 => [0x24ed,0xf87f], 0x62 => [0x24ee,0xf87f], + 0x63 => [0x24ef,0xf87f], 0x64 => [0x24f0,0xf87f], 0x65 => [0x24f1,0xf87f], + 0x66 => [0x24f2,0xf87f], 0x67 => [0x24f3,0xf87f], 0x68 => [0x24f4,0xf87f], + 0x69 => [0xf861,0x28,0x41,0x29], 0x6a => [0xf861,0x28,0x42,0x29], + 0x6b => [0xf861,0x28,0x43,0x29], 0x6c => [0xf861,0x28,0x44,0x29], + 0x6d => [0xf861,0x28,0x45,0x29], 0x6e => [0xf861,0x28,0x46,0x29], + 0x6f => [0xf861,0x28,0x47,0x29], 0x70 => [0xf861,0x28,0x48,0x29], + 0x71 => [0xf861,0x28,0x49,0x29], 0x72 => [0xf861,0x28,0x4a,0x29], + 0x73 => [0xf861,0x28,0x4b,0x29], 0x74 => [0xf861,0x28,0x4c,0x29], + 0x75 => [0xf861,0x28,0x4d,0x29], 0x76 => [0xf861,0x28,0x4e,0x29], + 0x77 => [0xf861,0x28,0x4f,0x29], 0x78 => [0xf861,0x28,0x50,0x29], + 0x79 => [0xf861,0x28,0x51,0x29], 0x7a => [0xf861,0x28,0x52,0x29], + 0x7b => [0xf861,0x28,0x53,0x29], 0x7c => [0xf861,0x28,0x54,0x29], + 0x7d => [0xf861,0x28,0x55,0x29], 0x81 => [0xf861,0x28,0x56,0x29], + 0x82 => [0xf861,0x28,0x57,0x29], 0x83 => [0xf861,0x28,0x58,0x29], + 0x84 => [0xf861,0x28,0x59,0x29], 0x85 => [0xf861,0x28,0x5a,0x29], + 0x86 => 0x24b6, 0x87 => 0x24b7, 0x88 => 0x24b8, 0x89 => 0x24b9, + 0x8a => 0x24ba, 0x8b => 0x24bb, 0x8c => 0x24bc, 0x8d => 0x24bd, + 0x8e => 0x24be, 0x8f => 0x24bf, 0x90 => 0x24c0, 0x91 => 0x24c1, + 0x92 => 0x24c2, 0x93 => 0x24c3, 0x94 => 0x24c4, 0x95 => 0x24c5, + 0x96 => 0x24c6, 0x97 => 0x24c7, 0x98 => 0x24c8, 0x99 => 0x24c9, + 0x9a => 0x24ca, 0x9b => 0x24cb, 0x9c => 0x24cc, 0x9d => 0x24cd, + 0x9e => 0x24ce, 0x9f => 0x24cf, 0xa1 => 0xff01, 0xa2 => 0xff02, + 0xa3 => 0xff03, 0xa4 => 0xff04, 0xa5 => 0xff05, 0xa6 => 0xff06, + 0xa7 => 0xff07, 0xa8 => 0xff08, 0xa9 => 0xff09, 0xaa => 0xff0a, + 0xab => 0xff0b, 0xac => 0xff0c, 0xad => 0xff0d, 0xae => 0xff0e, + 0xaf => 0xff0f, 0xb0 => 0xff10, 0xb1 => 0xff11, 0xb2 => 0xff12, + 0xb3 => 0xff13, 0xb4 => 0xff14, 0xb5 => 0xff15, 0xb6 => 0xff16, + 0xb7 => 0xff17, 0xb8 => 0xff18, 0xb9 => 0xff19, 0xba => 0xff1a, + 0xbb => 0xff1b, 0xbc => 0xff1c, 0xbd => 0xff1d, 0xbe => 0xff1e, + 0xbf => 0xff1f, 0xc0 => 0xff20, 0xc1 => 0xff21, 0xc2 => 0xff22, + 0xc3 => 0xff23, 0xc4 => 0xff24, 0xc5 => 0xff25, 0xc6 => 0xff26, + 0xc7 => 0xff27, 0xc8 => 0xff28, 0xc9 => 0xff29, 0xca => 0xff2a, + 0xcb => 0xff2b, 0xcc => 0xff2c, 0xcd => 0xff2d, 0xce => 0xff2e, + 0xcf => 0xff2f, 0xd0 => 0xff30, 0xd1 => 0xff31, 0xd2 => 0xff32, + 0xd3 => 0xff33, 0xd4 => 0xff34, 0xd5 => 0xff35, 0xd6 => 0xff36, + 0xd7 => 0xff37, 0xd8 => 0xff38, 0xd9 => 0xff39, 0xda => 0xff3a, + 0xdb => 0xff3b, 0xdc => 0xffe6, 0xdd => 0xff3d, 0xde => 0xff3e, + 0xdf => 0xff3f, 0xe0 => 0xff40, 0xe1 => 0xff41, 0xe2 => 0xff42, + 0xe3 => 0xff43, 0xe4 => 0xff44, 0xe5 => 0xff45, 0xe6 => 0xff46, + 0xe7 => 0xff47, 0xe8 => 0xff48, 0xe9 => 0xff49, 0xea => 0xff4a, + 0xeb => 0xff4b, 0xec => 0xff4c, 0xed => 0xff4d, 0xee => 0xff4e, + 0xef => 0xff4f, 0xf0 => 0xff50, 0xf1 => 0xff51, 0xf2 => 0xff52, + 0xf3 => 0xff53, 0xf4 => 0xff54, 0xf5 => 0xff55, 0xf6 => 0xff56, + 0xf7 => 0xff57, 0xf8 => 0xff58, 0xf9 => 0xff59, 0xfa => 0xff5a, + 0xfb => 0xff5b, 0xfc => 0xff5c, 0xfd => 0xff5d, 0xfe => 0x203e, + }, + 0xa4 => { + 0x41 => [0x31,0x20de,0xf87f], 0x42 => [0x32,0x20de,0xf87f], + 0x43 => [0x33,0x20de,0xf87f], 0x44 => [0x34,0x20de,0xf87f], + 0x45 => [0x35,0x20de,0xf87f], 0x46 => [0x36,0x20de,0xf87f], + 0x47 => [0x37,0x20de,0xf87f], 0x48 => [0x38,0x20de,0xf87f], + 0x49 => [0x39,0x20de,0xf87f], 0x4a => [0xf862,0x5b,0x31,0x30,0x5d], + 0x4b => [0xf862,0x5b,0x31,0x31,0x5d], 0x4c => [0xf862,0x5b,0x31,0x32,0x5d], + 0x4d => [0xf862,0x5b,0x31,0x33,0x5d], 0x4e => [0xf862,0x5b,0x31,0x34,0x5d], + 0x4f => [0xf862,0x5b,0x31,0x35,0x5d], 0x50 => [0xf862,0x5b,0x31,0x36,0x5d], + 0x51 => [0xf862,0x5b,0x31,0x37,0x5d], 0x52 => [0xf862,0x5b,0x31,0x38,0x5d], + 0x53 => [0xf862,0x5b,0x31,0x39,0x5d], 0x54 => [0xf862,0x5b,0x32,0x30,0x5d], + 0x55 => [0x31,0x20de,0xf87a], 0x56 => [0x32,0x20de,0xf87a], + 0x57 => [0x33,0x20de,0xf87a], 0x58 => [0x34,0x20de,0xf87a], + 0x59 => [0x35,0x20de,0xf87a], 0x5a => [0x36,0x20de,0xf87a], + 0x5b => [0x37,0x20de,0xf87a], 0x5c => [0x38,0x20de,0xf87a], + 0x5d => [0x39,0x20de,0xf87a], 0x5e => [0xf865,0x5b,0x31,0x30,0x5d], + 0x5f => [0xf865,0x5b,0x31,0x31,0x5d], 0x60 => [0xf865,0x5b,0x31,0x32,0x5d], + 0x61 => [0xf865,0x5b,0x31,0x33,0x5d], 0x62 => [0xf865,0x5b,0x31,0x34,0x5d], + 0x63 => [0xf865,0x5b,0x31,0x35,0x5d], 0x64 => [0xf865,0x5b,0x31,0x36,0x5d], + 0x65 => [0xf865,0x5b,0x31,0x37,0x5d], 0x66 => [0xf865,0x5b,0x31,0x38,0x5d], + 0x67 => [0xf865,0x5b,0x31,0x39,0x5d], 0x68 => [0xf865,0x5b,0x32,0x30,0x5d], + 0x69 => [0x278a,0xf87f], 0x6a => [0x278b,0xf87f], 0x6b => [0x278c,0xf87f], + 0x6c => [0x278d,0xf87f], 0x6d => [0x278e,0xf87f], 0x6e => [0x278f,0xf87f], + 0x6f => [0x2790,0xf87f], 0x70 => [0x2791,0xf87f], 0x71 => [0x2792,0xf87f], + 0x72 => [0x2793,0xf87f], 0x73 => [0x24eb,0xf878], 0x74 => [0x24ec,0xf878], + 0x75 => [0x24ed,0xf878], 0x76 => [0x24ee,0xf878], 0x77 => [0x24ef,0xf878], + 0x78 => [0x24f0,0xf878], 0x79 => [0x24f1,0xf878], 0x7a => [0x24f2,0xf878], + 0x7b => [0x24f3,0xf878], 0x7c => [0x24f4,0xf878], 0x7d => 0x2a26, + 0x81 => 0x227a, 0x82 => 0x227b, 0x83 => 0x22ce, 0x84 => 0x22cf, + 0x85 => 0x2280, 0x86 => 0x2281, 0x87 => 0x2270, 0x88 => 0x2271, + 0x89 => 0x2272, 0x8a => 0x2273, 0x8b => 0x2ac5, 0x8c => 0x2acb, + 0x8d => 0x2ac6, 0x8e => 0x2acc, 0x8f => 0x2276, 0x90 => 0x2277, + 0x91 => 0x2279, 0x92 => 0x22da, 0x93 => 0x22db, 0x94 => 0x2a8b, + 0x95 => 0x2a8c, 0x96 => 0x2a91, 0x97 => 0x2a92, 0x98 => [0x2222,0xf87f], + 0x99 => 0x2245, 0x9a => 0x2243, 0x9b => 0x2248, 0x9c => 0x29a3, + 0x9d => 0x22a4, 0x9e => [0x2225,0x0347], 0x9f => [0x2afd,0x0347], + 0xa1 => 0x3131, 0xa2 => 0x3132, 0xa3 => 0x3133, 0xa4 => 0x3134, + 0xa5 => 0x3135, 0xa6 => 0x3136, 0xa7 => 0x3137, 0xa8 => 0x3138, + 0xa9 => 0x3139, 0xaa => 0x313a, 0xab => 0x313b, 0xac => 0x313c, + 0xad => 0x313d, 0xae => 0x313e, 0xaf => 0x313f, 0xb0 => 0x3140, + 0xb1 => 0x3141, 0xb2 => 0x3142, 0xb3 => 0x3143, 0xb4 => 0x3144, + 0xb5 => 0x3145, 0xb6 => 0x3146, 0xb7 => 0x3147, 0xb8 => 0x3148, + 0xb9 => 0x3149, 0xba => 0x314a, 0xbb => 0x314b, 0xbc => 0x314c, + 0xbd => 0x314d, 0xbe => 0x314e, 0xbf => 0x314f, 0xc0 => 0x3150, + 0xc1 => 0x3151, 0xc2 => 0x3152, 0xc3 => 0x3153, 0xc4 => 0x3154, + 0xc5 => 0x3155, 0xc6 => 0x3156, 0xc7 => 0x3157, 0xc8 => 0x3158, + 0xc9 => 0x3159, 0xca => 0x315a, 0xcb => 0x315b, 0xcc => 0x315c, + 0xcd => 0x315d, 0xce => 0x315e, 0xcf => 0x315f, 0xd0 => 0x3160, + 0xd1 => 0x3161, 0xd2 => 0x3162, 0xd3 => 0x3163, 0xd4 => 0x3164, + 0xd5 => 0x3165, 0xd6 => 0x3166, 0xd7 => 0x3167, 0xd8 => 0x3168, + 0xd9 => 0x3169, 0xda => 0x316a, 0xdb => 0x316b, 0xdc => 0x316c, + 0xdd => 0x316d, 0xde => 0x316e, 0xdf => 0x316f, 0xe0 => 0x3170, + 0xe1 => 0x3171, 0xe2 => 0x3172, 0xe3 => 0x3173, 0xe4 => 0x3174, + 0xe5 => 0x3175, 0xe6 => 0x3176, 0xe7 => 0x3177, 0xe8 => 0x3178, + 0xe9 => 0x3179, 0xea => 0x317a, 0xeb => 0x317b, 0xec => 0x317c, + 0xed => 0x317d, 0xee => 0x317e, 0xef => 0x317f, 0xf0 => 0x3180, + 0xf1 => 0x3181, 0xf2 => 0x3182, 0xf3 => 0x3183, 0xf4 => 0x3184, + 0xf5 => 0x3185, 0xf6 => 0x3186, 0xf7 => 0x3187, 0xf8 => 0x3188, + 0xf9 => 0x3189, 0xfa => 0x318a, 0xfb => 0x318b, 0xfc => 0x318c, + 0xfd => 0x318d, 0xfe => 0x318e, + }, + 0xa5 => { + 0x41 => [0x30,0x20de], 0x42 => [0x31,0x20de], 0x43 => [0x32,0x20de], + 0x44 => [0x33,0x20de], 0x45 => [0x34,0x20de], 0x46 => [0x35,0x20de], + 0x47 => [0x36,0x20de], 0x48 => [0x37,0x20de], 0x49 => [0x38,0x20de], + 0x4a => [0x39,0x20de], 0x4b => [0x24ea,0xf87f], 0x4c => [0x2460,0xf87f], + 0x4d => [0x2461,0xf87f], 0x4e => [0x2462,0xf87f], 0x4f => [0x2463,0xf87f], + 0x50 => [0x2464,0xf87f], 0x51 => [0x2465,0xf87f], 0x52 => [0x2466,0xf87f], + 0x53 => [0x2467,0xf87f], 0x54 => [0x2468,0xf87f], + 0x55 => [0xf860,0x41,0x29], 0x56 => [0xf860,0x42,0x29], + 0x57 => [0xf860,0x43,0x29], 0x58 => [0xf860,0x44,0x29], + 0x59 => [0xf860,0x45,0x29], 0x5a => [0xf860,0x46,0x29], + 0x5b => [0xf860,0x47,0x29], 0x5c => [0xf860,0x48,0x29], + 0x5d => [0xf860,0x49,0x29], 0x5e => [0xf860,0x4a,0x29], + 0x5f => [0xf860,0x4b,0x29], 0x60 => [0xf860,0x4c,0x29], + 0x61 => [0xf860,0x4d,0x29], 0x62 => [0xf860,0x4e,0x29], + 0x63 => [0xf860,0x4f,0x29], 0x64 => [0xf860,0x50,0x29], + 0x65 => [0xf860,0x51,0x29], 0x66 => [0xf860,0x52,0x29], + 0x67 => [0xf860,0x53,0x29], 0x68 => [0xf860,0x54,0x29], + 0x69 => [0xf860,0x55,0x29], 0x6a => [0xf860,0x56,0x29], + 0x6b => [0xf860,0x57,0x29], 0x6c => [0xf860,0x58,0x29], + 0x6d => [0xf860,0x59,0x29], 0x6e => [0xf860,0x5a,0x29], + 0x6f => [0xf860,0x61,0x29], 0x70 => [0xf860,0x62,0x29], + 0x71 => [0xf860,0x63,0x29], 0x72 => [0xf860,0x64,0x29], + 0x73 => [0xf860,0x65,0x29], 0x74 => [0xf860,0x66,0x29], + 0x75 => [0xf860,0x67,0x29], 0x76 => [0xf860,0x68,0x29], + 0x77 => [0xf860,0x69,0x29], 0x78 => [0xf860,0x6a,0x29], + 0x79 => [0xf860,0x6b,0x29], 0x7a => [0xf860,0x6c,0x29], + 0x7b => [0xf860,0x6d,0x29], 0x7c => [0xf860,0x6e,0x29], + 0x7d => [0xf860,0x6f,0x29], 0x81 => [0xf860,0x70,0x29], + 0x82 => [0xf860,0x71,0x29], 0x83 => [0xf860,0x72,0x29], + 0x84 => [0xf860,0x73,0x29], 0x85 => [0xf860,0x74,0x29], + 0x86 => [0xf860,0x75,0x29], 0x87 => [0xf860,0x76,0x29], + 0x88 => [0xf860,0x77,0x29], 0x89 => [0xf860,0x78,0x29], + 0x8a => [0xf860,0x79,0x29], 0x8b => [0xf860,0x7a,0x29], 0xa1 => 0x2170, + 0xa2 => 0x2171, 0xa3 => 0x2172, 0xa4 => 0x2173, 0xa5 => 0x2174, + 0xa6 => 0x2175, 0xa7 => 0x2176, 0xa8 => 0x2177, 0xa9 => 0x2178, + 0xaa => 0x2179, 0xb0 => 0x2160, 0xb1 => 0x2161, 0xb2 => 0x2162, + 0xb3 => 0x2163, 0xb4 => 0x2164, 0xb5 => 0x2165, 0xb6 => 0x2166, + 0xb7 => 0x2167, 0xb8 => 0x2168, 0xb9 => 0x2169, 0xc1 => 0x0391, + 0xc2 => 0x0392, 0xc3 => 0x0393, 0xc4 => 0x0394, 0xc5 => 0x0395, + 0xc6 => 0x0396, 0xc7 => 0x0397, 0xc8 => 0x0398, 0xc9 => 0x0399, + 0xca => 0x039a, 0xcb => 0x039b, 0xcc => 0x039c, 0xcd => 0x039d, + 0xce => 0x039e, 0xcf => 0x039f, 0xd0 => 0x03a0, 0xd1 => 0x03a1, + 0xd2 => 0x03a3, 0xd3 => 0x03a4, 0xd4 => 0x03a5, 0xd5 => 0x03a6, + 0xd6 => 0x03a7, 0xd7 => 0x03a8, 0xd8 => 0x03a9, 0xda => [0xff01,0xf874], + 0xdb => [0x3002,0xf87d], 0xdc => [0x2032,0xf87f], 0xdd => [0x2033,0xf87f], + 0xde => 0x2034, 0xe1 => 0x03b1, 0xe2 => 0x03b2, 0xe3 => 0x03b3, + 0xe4 => 0x03b4, 0xe5 => 0x03b5, 0xe6 => 0x03b6, 0xe7 => 0x03b7, + 0xe8 => 0x03b8, 0xe9 => 0x03b9, 0xea => 0x03ba, 0xeb => 0x03bb, + 0xec => 0x03bc, 0xed => 0x03bd, 0xee => 0x03be, 0xef => 0x03bf, + 0xf0 => 0x03c0, 0xf1 => 0x03c1, 0xf2 => 0x03c3, 0xf3 => 0x03c4, + 0xf4 => 0x03c5, 0xf5 => 0x03c6, 0xf6 => 0x03c7, 0xf7 => 0x03c8, + 0xf8 => 0x03c9, 0xf9 => [0x3257,0xf87a], 0xfa => [0x3258,0xf87a], + 0xfb => [0x3259,0xf87a], 0xfc => [0x325a,0xf87a], + }, + 0xa6 => { + 0x41 => [0xf83d,0xf87f], 0x42 => 0xf83d, 0x43 => [0x2020,0xf87c], + 0x44 => [0xf860,0x2020,0x2020], 0x45 => [0xf860,0x2021,0x2021], + 0x46 => [0xf861,0x2020,0x2020,0x2020], 0x47 => [0xa7,0xf87c], + 0x48 => 0x266f, 0x49 => [0xff0a,0xf87f], 0x4a => [0xff0a,0xf873], + 0x4b => [0x2051,0xf874], 0x4c => [0xf860,0x2a,0x2a], 0x4d => 0x2042, + 0x4e => 0x204e, 0x4f => [0x2051,0xf871], 0x50 => [0xf867,0x2a,0x2a], + 0x51 => [0x2042,0xf879], 0x52 => 0x273d, 0x53 => 0x2731, 0x54 => 0x2747, + 0x55 => 0x2022, 0x56 => [0x25a0,0x20df], 0x57 => [0x25c7,0x20df], + 0x58 => 0xf805, 0x59 => [0x25a1,0x20df], 0x5a => 0x2039, 0x5b => 0x203a, + 0x5c => 0xab, 0x5d => 0xbb, 0x5e => [0x261c,0xf87f], + 0x5f => [0x261e,0xf87f], 0x60 => [0xf806,0x20df], + 0x61 => [0x25c7,0x20df,0x20df], 0x62 => [0x25c7,0x20de], 0x63 => 0xf806, + 0x64 => 0x29c8, 0x65 => [0x25c6,0x20de], 0x66 => [0xf805,0x20de], + 0x67 => [0x29c8,0x20de], 0x68 => 0x29be, 0x69 => [0x25ce,0x20dd], + 0x6a => [0x25b3,0x20dd], 0x6b => [0x25b2,0x20dd], 0x6c => 0x271a, + 0x6d => 0x2716, 0x6e => 0x29bf, 0x6f => 0x25ef, 0x70 => [0x25ef,0xf87c], + 0x71 => [0x2610,0xf87c], 0x72 => 0x2723, 0x73 => 0x2756, 0x74 => 0xf80a, + 0x75 => 0x25cc, 0x76 => [0x2610,0xf87f], 0x77 => 0x2610, 0x78 => 0x25a2, + 0x79 => [0x2723,0xf87a], 0x7a => [0x2756,0xf87a], 0x7b => [0x273f,0xf87a], + 0x7c => 0x273f, 0x7d => [0x3013,0xf87c], 0x81 => 0xf809, + 0x82 => [0x25c9,0x20dd], 0x83 => 0x274d, 0x84 => 0x25cd, + 0x85 => [0x27e1,0x20dd], 0x86 => [0xf80b,0xf87f], 0x87 => [0x2720,0xf87a], + 0x88 => 0x2720, 0x89 => [0x25c8,0xf87f], 0x8a => [0x25a8,0xf87f], + 0x8d => 0x2741, 0x8e => [0x2756,0xf87f], 0x8f => 0xf808, + 0x90 => [0x20a9,0xf87f], 0x91 => [0xf809,0xf87a], 0x92 => [0x534d,0xf87f], + 0x93 => 0x262f, 0x96 => 0xf80b, 0x97 => [0x262f,0xf87a], + 0x98 => [0x262f,0xf876], 0x99 => 0x2740, 0x9a => 0xf80c, + 0x9b => [0x2748,0x20d8], 0x9e => 0x3020, 0x9f => 0xf807, 0xa1 => 0x2500, + 0xa2 => 0x2502, 0xa3 => 0x250c, 0xa4 => 0x2510, 0xa5 => 0x2518, + 0xa6 => 0x2514, 0xa7 => 0x251c, 0xa8 => 0x252c, 0xa9 => 0x2524, + 0xaa => 0x2534, 0xab => 0x253c, 0xac => 0x2501, 0xad => 0x2503, + 0xae => 0x250f, 0xaf => 0x2513, 0xb0 => 0x251b, 0xb1 => 0x2517, + 0xb2 => 0x2523, 0xb3 => 0x2533, 0xb4 => 0x252b, 0xb5 => 0x253b, + 0xb6 => 0x254b, 0xb7 => 0x2520, 0xb8 => 0x252f, 0xb9 => 0x2528, + 0xba => 0x2537, 0xbb => 0x253f, 0xbc => 0x251d, 0xbd => 0x2530, + 0xbe => 0x2525, 0xbf => 0x2538, 0xc0 => 0x2542, 0xc1 => 0x2512, + 0xc2 => 0x2511, 0xc3 => 0x251a, 0xc4 => 0x2519, 0xc5 => 0x2516, + 0xc6 => 0x2515, 0xc7 => 0x250e, 0xc8 => 0x250d, 0xc9 => 0x251e, + 0xca => 0x251f, 0xcb => 0x2521, 0xcc => 0x2522, 0xcd => 0x2526, + 0xce => 0x2527, 0xcf => 0x2529, 0xd0 => 0x252a, 0xd1 => 0x252d, + 0xd2 => 0x252e, 0xd3 => 0x2531, 0xd4 => 0x2532, 0xd5 => 0x2535, + 0xd6 => 0x2536, 0xd7 => 0x2539, 0xd8 => 0x253a, 0xd9 => 0x253d, + 0xda => 0x253e, 0xdb => 0x2540, 0xdc => 0x2541, 0xdd => 0x2543, + 0xde => 0x2544, 0xdf => 0x2545, 0xe0 => 0x2546, 0xe1 => 0x2547, + 0xe2 => 0x2548, 0xe3 => 0x2549, 0xe4 => 0x254a, 0xe5 => 0x2776, + 0xe6 => 0x2777, 0xe7 => 0x2778, 0xe8 => 0x2779, 0xe9 => 0x277a, + 0xea => 0x277b, 0xeb => 0x277c, 0xec => 0x277d, 0xed => 0x277e, + 0xee => 0x277f, 0xef => 0x24eb, 0xf0 => 0x24ec, 0xf1 => 0x24ed, + 0xf2 => 0x24ee, 0xf3 => 0x24ef, 0xf4 => 0x24f0, 0xf5 => 0x24f1, + 0xf6 => 0x24f2, 0xf7 => 0x24f3, 0xf8 => 0x24f4, 0xf9 => [0x3251,0xf87a], + 0xfa => [0x3252,0xf87a], 0xfb => [0x3253,0xf87a], 0xfc => [0x3254,0xf87a], + 0xfd => [0x3255,0xf87a], 0xfe => [0x3256,0xf87a], + }, + 0xa7 => { + 0x41 => [0x2642,0xf87f], 0x42 => 0x3012, 0x43 => 0x3036, + 0x44 => [0x25cb,0xf87f], 0x45 => [0x25b3,0xf87f], 0x46 => 0x25fb, + 0x47 => 0xf84c, 0x48 => [0x2394,0xf876], 0x49 => [0x25ad,0xf878], + 0x4a => 0x25ad, 0x4b => 0xf84d, 0x4c => 0xf84e, 0x4d => 0xf84f, + 0x4e => [0x25c7,0xf87f], 0x4f => [0x51f9,0xf87f], 0x50 => [0x51f8,0xf87f], + 0x51 => 0x2206, 0x52 => [0x2206,0xf87f], 0x53 => 0x221f, + 0x54 => [0x222a,0xf87f], 0x55 => 0x2225, 0x56 => 0x2226, + 0x57 => [0x2229,0xf87f], 0x58 => 0x2253, 0x59 => 0x2251, 0x5a => 0x2266, + 0x5b => 0x2267, 0x5c => 0x2213, 0x5d => 0x2295, 0x5e => 0x2296, + 0x5f => 0x2297, 0x60 => 0x2a38, 0x61 => 0x2314, 0x62 => [0x3d,0x20e5], + 0x63 => [0x2261,0x20e5], 0x64 => 0x2262, 0x65 => [0x3d,0x20d2], + 0x66 => 0x25b1, 0x67 => [0x2d,0x0308], 0x68 => 0x2222, 0x69 => 0x2250, + 0x6a => 0x03d5, 0x6b => 0x2ae8, 0x6c => 0x22a3, 0x6d => [0x22a5,0x0338], + 0x6e => [0x2261,0x20d2], 0x6f => 0x226e, 0x70 => 0x226f, 0x71 => 0x2285, + 0x72 => 0x2284, 0x73 => 0x2209, 0x74 => 0x220c, 0x75 => 0x22bb, + 0x76 => 0x22bc, 0x77 => 0x225a, 0x78 => 0x2306, 0x79 => [0x223d,0x0336], + 0x7a => [0x2314,0xf87f], 0x7b => 0x2a72, 0x7c => [0x88dc,0x20e4], + 0x7d => [0xf862,0xc8fc,0xc2dd,0xd68c,0xc0ac], + 0x81 => [0xf863,0xc8fc,0xc2dd,0xd68c,0xc0ac], 0x82 => 0x329e, + 0x83 => [0x329e,0xf87f], 0x84 => 0x203c, 0x85 => 0x2049, + 0x86 => [0x203c,0xf87f], 0x87 => 0x2047, 0x88 => [0x25c7,0xf87c], + 0x89 => [0x25c7,0xf879], 0x8a => [0x25c7,0xf87b], 0x8b => [0x25c6,0xf879], + 0x8c => [0x25a1,0xf87c], 0x8d => [0x25a1,0xf879], 0x8e => [0x25a1,0xf87b], + 0x8f => 0x2588, 0x90 => 0x25e6, 0x91 => [0x25cb,0xf879], + 0x92 => [0x25cb,0xf87b], 0x93 => [0x25cf,0xf879], 0x94 => 0x25bf, + 0x95 => 0x25b5, 0x96 => 0x25b9, 0x97 => 0x25c3, 0x98 => 0x2666, + 0x99 => 0x2981, 0x9a => 0x25fc, 0x9b => [0x25b4,0x20e4], 0x9c => 0x25ca, + 0x9d => 0x3231, 0x9e => 0x3239, 0x9f => 0x33cb, 0xa1 => 0x3395, + 0xa2 => 0x3396, 0xa3 => 0x3397, 0xa4 => 0x2113, 0xa5 => 0x3398, + 0xa6 => 0x33c4, 0xa7 => 0x33a3, 0xa8 => 0x33a4, 0xa9 => 0x33a5, + 0xaa => 0x33a6, 0xab => 0x3399, 0xac => 0x339a, 0xad => 0x339b, + 0xae => 0x339c, 0xaf => 0x339d, 0xb0 => 0x339e, 0xb1 => 0x339f, + 0xb2 => 0x33a0, 0xb3 => 0x33a1, 0xb4 => 0x33a2, 0xb5 => 0x33ca, + 0xb6 => 0x338d, 0xb7 => 0x338e, 0xb8 => 0x338f, 0xb9 => 0x33cf, + 0xba => 0x3388, 0xbb => 0x3389, 0xbc => 0x33c8, 0xbd => 0x33a7, + 0xbe => 0x33a8, 0xbf => 0x33b0, 0xc0 => 0x33b1, 0xc1 => 0x33b2, + 0xc2 => 0x33b3, 0xc3 => 0x33b4, 0xc4 => 0x33b5, 0xc5 => 0x33b6, + 0xc6 => 0x33b7, 0xc7 => 0x33b8, 0xc8 => 0x33b9, 0xc9 => 0x3380, + 0xca => 0x3381, 0xcb => 0x3382, 0xcc => 0x3383, 0xcd => 0x3384, + 0xce => 0x33ba, 0xcf => 0x33bb, 0xd0 => 0x33bc, 0xd1 => 0x33bd, + 0xd2 => 0x33be, 0xd3 => 0x33bf, 0xd4 => 0x3390, 0xd5 => 0x3391, + 0xd6 => 0x3392, 0xd7 => 0x3393, 0xd8 => 0x3394, 0xd9 => 0x2126, + 0xda => 0x33c0, 0xdb => 0x33c1, 0xdc => 0x338a, 0xdd => 0x338b, + 0xde => 0x338c, 0xdf => 0x33d6, 0xe0 => 0x33c5, 0xe1 => 0x33ad, + 0xe2 => 0x33ae, 0xe3 => 0x33af, 0xe4 => 0x33db, 0xe5 => 0x33a9, + 0xe6 => 0x33aa, 0xe7 => 0x33ab, 0xe8 => 0x33ac, 0xe9 => 0x33dd, + 0xea => 0x33d0, 0xeb => 0x33d3, 0xec => 0x33c3, 0xed => 0x33c9, + 0xee => 0x33dc, 0xef => 0x33c6, 0xf0 => 0x246f, 0xf1 => 0x2470, + 0xf2 => 0x2471, 0xf3 => 0x2472, 0xf4 => 0x2473, 0xf5 => 0x3251, + 0xf6 => 0x3252, 0xf7 => 0x3253, 0xf8 => 0x3254, 0xf9 => 0x3255, + 0xfa => 0x3256, 0xfb => 0x3257, 0xfc => 0x3258, 0xfd => 0x3259, + 0xfe => 0x325a, + }, + 0xa8 => { + 0x41 => [0x2192,0xf87b], 0x42 => [0x2190,0xf87b], 0x43 => [0x2191,0xf87b], + 0x44 => [0x2193,0xf87b], 0x45 => [0x2196,0xf87b], 0x46 => [0x2197,0xf87b], + 0x47 => [0x2198,0xf87b], 0x48 => [0x2199,0xf87b], 0x49 => 0x21d0, + 0x4a => 0x21cf, 0x4b => 0x21cd, 0x4c => [0x21d4,0xf87f], + 0x4d => [0x2192,0xf87c], 0x4e => [0x2190,0xf87c], 0x4f => [0x2191,0xf87c], + 0x50 => [0x2193,0xf87c], 0x51 => [0x2194,0xf87c], 0x52 => [0x2195,0xf87c], + 0x53 => [0x2190,0xf879], 0x54 => [0x2192,0xf879], 0x55 => [0x2191,0xf879], + 0x56 => [0x2193,0xf879], 0x57 => [0x21e6,0x20de], 0x58 => [0x21e8,0x20de], + 0x59 => [0x21e7,0x20de], 0x5a => [0x21e9,0x20de], 0x5b => [0x21e6,0x20dd], + 0x5c => 0x27b2, 0x5d => [0x21e7,0x20dd], 0x5e => [0x21e9,0x20dd], + 0x5f => [0x2190,0xf87f], 0x60 => 0x279c, 0x61 => [0x2191,0xf87f], + 0x62 => [0x2193,0xf87f], 0x63 => [0x2190,0xf875], 0x64 => [0x2192,0xf875], + 0x65 => [0x2191,0xf875], 0x66 => [0x2193,0xf875], 0x67 => 0xf846, + 0x68 => 0xf847, 0x69 => [0x2190,0xf871], 0x6a => 0x279b, + 0x6b => [0x2190,0xf872], 0x6c => [0x2192,0xf872], 0x6d => [0x2191,0xf872], + 0x6e => [0x2193,0xf872], 0x6f => 0x2962, 0x70 => 0x2964, 0x71 => 0x2963, + 0x72 => 0x2965, 0x73 => [0x21e6,0xf87a], 0x74 => 0x27a1, + 0x75 => [0x21e7,0xf87a], 0x76 => [0x21e9,0xf87a], 0x77 => [0x21e6,0xf87b], + 0x78 => 0x279e, 0x79 => [0x21e7,0xf87b], 0x7a => [0x21e9,0xf87b], + 0x7b => 0x21b2, 0x7c => 0x21b1, 0x7d => [0x21bb,0xf87b], 0x81 => 0x21b4, + 0x82 => 0x21b0, 0x83 => 0x21b3, 0x84 => [0x2939,0xf87f], + 0x85 => [0x2934,0xf87f], 0x86 => 0x2936, 0x87 => [0x21b1,0xf87f], + 0x88 => [0x21bb,0xf87f], 0x89 => 0x2935, 0x8a => [0x21b0,0xf87f], + 0x8b => 0x2937, 0x8c => 0x2939, 0x8d => 0x2934, 0x8e => [0x21e6,0xf879], + 0x8f => [0x21e8,0xf879], 0x90 => [0x21e7,0xf879], 0x91 => [0x21e9,0xf879], + 0x92 => 0x21bc, 0x93 => 0x21c0, 0x94 => 0xf841, 0x95 => [0x21d4,0xf879], + 0x96 => [0x21e8,0xf874], 0x97 => [0x21e6,0xf874], 0x98 => [0x21c0,0xf879], + 0x99 => [0x21bc,0xf879], 0x9a => [0x21d2,0xf87c], 0x9b => [0x21d0,0xf87c], + 0x9c => 0xf849, 0x9d => 0xf848, 0x9e => 0x21c4, 0x9f => 0x21c5, + 0xa1 => 0xc6, 0xa2 => 0xd0, 0xa3 => 0xaa, 0xa4 => 0x0126, 0xa6 => 0x0132, + 0xa8 => 0x013f, 0xa9 => 0x0141, 0xaa => 0xd8, 0xab => 0x0152, 0xac => 0xba, + 0xad => 0xde, 0xae => 0x0166, 0xaf => 0x014a, 0xb1 => 0x3260, + 0xb2 => 0x3261, 0xb3 => 0x3262, 0xb4 => 0x3263, 0xb5 => 0x3264, + 0xb6 => 0x3265, 0xb7 => 0x3266, 0xb8 => 0x3267, 0xb9 => 0x3268, + 0xba => 0x3269, 0xbb => 0x326a, 0xbc => 0x326b, 0xbd => 0x326c, + 0xbe => 0x326d, 0xbf => 0x326e, 0xc0 => 0x326f, 0xc1 => 0x3270, + 0xc2 => 0x3271, 0xc3 => 0x3272, 0xc4 => 0x3273, 0xc5 => 0x3274, + 0xc6 => 0x3275, 0xc7 => 0x3276, 0xc8 => 0x3277, 0xc9 => 0x3278, + 0xca => 0x3279, 0xcb => 0x327a, 0xcc => 0x327b, 0xcd => 0x24d0, + 0xce => 0x24d1, 0xcf => 0x24d2, 0xd0 => 0x24d3, 0xd1 => 0x24d4, + 0xd2 => 0x24d5, 0xd3 => 0x24d6, 0xd4 => 0x24d7, 0xd5 => 0x24d8, + 0xd6 => 0x24d9, 0xd7 => 0x24da, 0xd8 => 0x24db, 0xd9 => 0x24dc, + 0xda => 0x24dd, 0xdb => 0x24de, 0xdc => 0x24df, 0xdd => 0x24e0, + 0xde => 0x24e1, 0xdf => 0x24e2, 0xe0 => 0x24e3, 0xe1 => 0x24e4, + 0xe2 => 0x24e5, 0xe3 => 0x24e6, 0xe4 => 0x24e7, 0xe5 => 0x24e8, + 0xe6 => 0x24e9, 0xe7 => 0x2460, 0xe8 => 0x2461, 0xe9 => 0x2462, + 0xea => 0x2463, 0xeb => 0x2464, 0xec => 0x2465, 0xed => 0x2466, + 0xee => 0x2467, 0xef => 0x2468, 0xf0 => 0x2469, 0xf1 => 0x246a, + 0xf2 => 0x246b, 0xf3 => 0x246c, 0xf4 => 0x246d, 0xf5 => 0x246e, + 0xf6 => 0xbd, 0xf7 => 0x2153, 0xf8 => 0x2154, 0xf9 => 0xbc, 0xfa => 0xbe, + 0xfb => 0x215b, 0xfc => 0x215c, 0xfd => 0x215d, 0xfe => 0x215e, + }, + 0xa9 => { + 0x41 => [0xf860,0x41,0x2e], 0x42 => [0xf860,0x42,0x2e], + 0x43 => [0xf860,0x43,0x2e], 0x44 => [0xf860,0x44,0x2e], + 0x45 => [0xf860,0x45,0x2e], 0x46 => [0xf860,0x46,0x2e], + 0x47 => [0xf860,0x47,0x2e], 0x48 => [0xf860,0x48,0x2e], + 0x49 => [0xf860,0x49,0x2e], 0x4a => [0xf860,0x4a,0x2e], + 0x4b => [0xf860,0x4b,0x2e], 0x4c => [0xf860,0x4c,0x2e], + 0x4d => [0xf860,0x4d,0x2e], 0x4e => [0xf860,0x4e,0x2e], + 0x4f => [0xf860,0x4f,0x2e], 0x50 => [0xf860,0x50,0x2e], + 0x51 => [0xf860,0x51,0x2e], 0x52 => [0xf860,0x52,0x2e], + 0x53 => [0xf860,0x53,0x2e], 0x54 => [0xf860,0x54,0x2e], + 0x55 => [0xf860,0x55,0x2e], 0x56 => [0xf860,0x56,0x2e], + 0x57 => [0xf860,0x57,0x2e], 0x58 => [0xf860,0x58,0x2e], + 0x59 => [0xf860,0x59,0x2e], 0x5a => [0xf860,0x5a,0x2e], + 0x5b => [0xf860,0x61,0x2e], 0x5c => [0xf860,0x62,0x2e], + 0x5d => [0xf860,0x63,0x2e], 0x5e => [0xf860,0x64,0x2e], + 0x5f => [0xf860,0x65,0x2e], 0x60 => [0xf860,0x66,0x2e], + 0x61 => [0xf860,0x67,0x2e], 0x62 => [0xf860,0x68,0x2e], + 0x63 => [0xf860,0x69,0x2e], 0x64 => [0xf860,0x6a,0x2e], + 0x65 => [0xf860,0x6b,0x2e], 0x66 => [0xf860,0x6c,0x2e], + 0x67 => [0xf860,0x6d,0x2e], 0x68 => [0xf860,0x6e,0x2e], + 0x69 => [0xf860,0x6f,0x2e], 0x6a => [0xf860,0x70,0x2e], + 0x6b => [0xf860,0x71,0x2e], 0x6c => [0xf860,0x72,0x2e], + 0x6d => [0xf860,0x73,0x2e], 0x6e => [0xf860,0x74,0x2e], + 0x6f => [0xf860,0x75,0x2e], 0x70 => [0xf860,0x76,0x2e], + 0x71 => [0xf860,0x77,0x2e], 0x72 => [0xf860,0x78,0x2e], + 0x73 => [0xf860,0x79,0x2e], 0x74 => [0xf860,0x7a,0x2e], 0xa1 => 0xe6, + 0xa2 => 0x0111, 0xa3 => 0xf0, 0xa4 => 0x0127, 0xa5 => 0x0131, + 0xa6 => 0x0133, 0xa7 => 0x0138, 0xa8 => 0x0140, 0xa9 => 0x0142, + 0xaa => 0xf8, 0xab => 0x0153, 0xac => 0xdf, 0xad => 0xfe, 0xae => 0x0167, + 0xaf => 0x014b, 0xb0 => 0x0149, 0xb1 => 0x3200, 0xb2 => 0x3201, + 0xb3 => 0x3202, 0xb4 => 0x3203, 0xb5 => 0x3204, 0xb6 => 0x3205, + 0xb7 => 0x3206, 0xb8 => 0x3207, 0xb9 => 0x3208, 0xba => 0x3209, + 0xbb => 0x320a, 0xbc => 0x320b, 0xbd => 0x320c, 0xbe => 0x320d, + 0xbf => 0x320e, 0xc0 => 0x320f, 0xc1 => 0x3210, 0xc2 => 0x3211, + 0xc3 => 0x3212, 0xc4 => 0x3213, 0xc5 => 0x3214, 0xc6 => 0x3215, + 0xc7 => 0x3216, 0xc8 => 0x3217, 0xc9 => 0x3218, 0xca => 0x3219, + 0xcb => 0x321a, 0xcc => 0x321b, 0xcd => 0x249c, 0xce => 0x249d, + 0xcf => 0x249e, 0xd0 => 0x249f, 0xd1 => 0x24a0, 0xd2 => 0x24a1, + 0xd3 => 0x24a2, 0xd4 => 0x24a3, 0xd5 => 0x24a4, 0xd6 => 0x24a5, + 0xd7 => 0x24a6, 0xd8 => 0x24a7, 0xd9 => 0x24a8, 0xda => 0x24a9, + 0xdb => 0x24aa, 0xdc => 0x24ab, 0xdd => 0x24ac, 0xde => 0x24ad, + 0xdf => 0x24ae, 0xe0 => 0x24af, 0xe1 => 0x24b0, 0xe2 => 0x24b1, + 0xe3 => 0x24b2, 0xe4 => 0x24b3, 0xe5 => 0x24b4, 0xe6 => 0x24b5, + 0xe7 => 0x2474, 0xe8 => 0x2475, 0xe9 => 0x2476, 0xea => 0x2477, + 0xeb => 0x2478, 0xec => 0x2479, 0xed => 0x247a, 0xee => 0x247b, + 0xef => 0x247c, 0xf0 => 0x247d, 0xf1 => 0x247e, 0xf2 => 0x247f, + 0xf3 => 0x2480, 0xf4 => 0x2481, 0xf5 => 0x2482, 0xf6 => 0xb9, 0xf7 => 0xb2, + 0xf8 => 0xb3, 0xf9 => 0x2074, 0xfa => 0x207f, 0xfb => 0x2081, + 0xfc => 0x2082, 0xfd => 0x2083, 0xfe => 0x2084, + }, + 0xaa => { + 0x41 => [0xc6b4,0x20de], 0x42 => [0xb2f5,0x20de], 0x43 => [0xc8fc,0x20de], + 0x44 => [0xba85,0x20de], 0x45 => [0xb300,0x20de], 0x46 => [0xd615,0x20de], + 0x47 => [0xbd80,0x20de], 0x48 => [0xc804,0x20de], 0x49 => [0xc811,0x20de], + 0x4a => [0xc218,0x20de], 0x4b => [0xb3d9,0x20de], 0x4c => [0xbe44,0x20de], + 0x4d => [0xbc18,0x20de], 0x4e => [0xc790,0x20de], 0x4f => [0xd0c0,0x20de], + 0x50 => [0xac10,0x20de], 0x51 => [0xc57d,0x20de], 0x52 => [0xc778,0x20de], + 0x53 => [0xb73b,0x20de], 0x54 => [0x5370,0x20de], 0x55 => [0x8a3b,0x20de], + 0x56 => [0xc608,0x20de], 0x57 => [0x611f,0x20de], 0x58 => [0x51a0,0x20de], + 0x59 => [0x7b54,0x20de], 0x5a => [0x4ee3,0x20de], 0x5b => [0x982d,0x20de], + 0x5c => [0x52d5,0x20de], 0x5d => [0x540d,0x20de], 0x5e => [0x76ee,0x20de], + 0x5f => [0x53cd,0x20de], 0x60 => [0x88dc,0x20de], 0x61 => [0x672c,0x20de], + 0x62 => [0x526f,0x20de], 0x63 => [0x5e8f,0x20de], 0x64 => [0x9023,0x20de], + 0x65 => [0x5f71,0x20de], 0x66 => [0x4f8b,0x20de], 0x67 => [0x6e90,0x20de], + 0x68 => [0x5b50,0x20de], 0x69 => [0x524d,0x20de], 0x6a => [0x7bc0,0x20de], + 0x6b => [0x63a5,0x20de], 0x6c => [0x52a9,0x20de], 0x6d => [0x6307,0x20de], + 0x6e => [0x4ed6,0x20de], 0x6f => [0x6d3e,0x20de], 0x70 => [0x5f62,0x20de], + 0x71 => [0xc870,0x20de], 0x72 => [0xbb38,0x20de,0xf87a], + 0x73 => [0xb2f5,0x20de,0xf87a], 0x74 => [0xc8fc,0x20de,0xf87a], + 0x75 => [0xb73b,0x20de,0xf87a], 0x76 => [0x8a3b,0x20de,0xf87a], + 0x77 => [0xad50,0x20de,0xf87a], 0x78 => [0xc5ed,0x20de,0xf87a], + 0x79 => [0xc74c,0x20de,0xf87a], 0x7a => [0xc815,0x20de,0xf87a], + 0x7b => [0xd574,0x20de,0xf87a], 0x7c => [0xc608,0x20de,0xf87a], + 0x7d => [0xc874,0x20dd], 0x81 => [0xb77c,0x20dd], 0x82 => [0xb9c8,0x20dd], + 0x83 => [0xbc14,0x20dd], 0x84 => [0xc0ac,0x20dd], 0x85 => [0xc544,0x20dd], + 0x86 => [0xc790,0x20dd], 0x87 => [0xcc28,0x20dd], 0x88 => [0xce74,0x20dd], + 0x89 => [0xd0c0,0x20dd], 0x8a => [0xd30c,0x20dd], 0x8b => [0xb192,0x20dd], + 0x8c => [0xb0ae,0x20dd], 0x8d => [0xba85,0x20dd], 0x8e => [0xb300,0x20dd], + 0x8f => [0xd615,0x20dd], 0x90 => [0xbd80,0x20dd], 0x91 => [0xc804,0x20dd], + 0x92 => [0xc811,0x20dd], 0x93 => [0xc218,0x20dd], 0x94 => [0xb3d9,0x20dd], + 0x95 => [0xbe44,0x20dd], 0x96 => [0xac8c,0x20dd], 0x97 => [0xbc18,0x20dd], + 0x98 => [0xc18d,0x20dd], 0x99 => [0xc778,0x20dd], 0x9a => [0xbcf8,0x20dd], + 0x9b => [0xc57d,0x20dd], 0x9c => [0xc219,0x20dd], 0x9d => [0xc720,0x20dd], + 0x9e => [0xad00,0x20dd], 0x9f => [0x51a0,0x20dd], 0xa1 => 0x3041, + 0xa2 => 0x3042, 0xa3 => 0x3043, 0xa4 => 0x3044, 0xa5 => 0x3045, + 0xa6 => 0x3046, 0xa7 => 0x3047, 0xa8 => 0x3048, 0xa9 => 0x3049, + 0xaa => 0x304a, 0xab => 0x304b, 0xac => 0x304c, 0xad => 0x304d, + 0xae => 0x304e, 0xaf => 0x304f, 0xb0 => 0x3050, 0xb1 => 0x3051, + 0xb2 => 0x3052, 0xb3 => 0x3053, 0xb4 => 0x3054, 0xb5 => 0x3055, + 0xb6 => 0x3056, 0xb7 => 0x3057, 0xb8 => 0x3058, 0xb9 => 0x3059, + 0xba => 0x305a, 0xbb => 0x305b, 0xbc => 0x305c, 0xbd => 0x305d, + 0xbe => 0x305e, 0xbf => 0x305f, 0xc0 => 0x3060, 0xc1 => 0x3061, + 0xc2 => 0x3062, 0xc3 => 0x3063, 0xc4 => 0x3064, 0xc5 => 0x3065, + 0xc6 => 0x3066, 0xc7 => 0x3067, 0xc8 => 0x3068, 0xc9 => 0x3069, + 0xca => 0x306a, 0xcb => 0x306b, 0xcc => 0x306c, 0xcd => 0x306d, + 0xce => 0x306e, 0xcf => 0x306f, 0xd0 => 0x3070, 0xd1 => 0x3071, + 0xd2 => 0x3072, 0xd3 => 0x3073, 0xd4 => 0x3074, 0xd5 => 0x3075, + 0xd6 => 0x3076, 0xd7 => 0x3077, 0xd8 => 0x3078, 0xd9 => 0x3079, + 0xda => 0x307a, 0xdb => 0x307b, 0xdc => 0x307c, 0xdd => 0x307d, + 0xde => 0x307e, 0xdf => 0x307f, 0xe0 => 0x3080, 0xe1 => 0x3081, + 0xe2 => 0x3082, 0xe3 => 0x3083, 0xe4 => 0x3084, 0xe5 => 0x3085, + 0xe6 => 0x3086, 0xe7 => 0x3087, 0xe8 => 0x3088, 0xe9 => 0x3089, + 0xea => 0x308a, 0xeb => 0x308b, 0xec => 0x308c, 0xed => 0x308d, + 0xee => 0x308e, 0xef => 0x308f, 0xf0 => 0x3090, 0xf1 => 0x3091, + 0xf2 => 0x3092, 0xf3 => 0x3093, 0xf4 => 0x2483, 0xf5 => 0x2484, + 0xf6 => 0x2485, 0xf7 => 0x2486, 0xf8 => 0x2487, + 0xf9 => [0xf862,0x28,0x32,0x31,0x29], 0xfa => [0xf862,0x28,0x32,0x32,0x29], + 0xfb => [0xf862,0x28,0x32,0x33,0x29], 0xfc => [0xf862,0x28,0x32,0x34,0x29], + 0xfd => [0xf862,0x28,0x32,0x35,0x29], 0xfe => [0xf862,0x28,0x32,0x36,0x29], + }, + 0xab => { + 0x41 => [0xc870,0x20dd], 0x42 => [0xad6d,0x20dd], 0x43 => [0xac10,0x20dd], + 0x44 => [0x5370,0x20dd], 0x45 => [0x8863,0x20dd], 0x46 => [0x672b,0x20dd], + 0x47 => [0xac70,0x20dd], 0x48 => [0xb2f5,0x20dd], 0x49 => [0xbcc0,0x20dd], + 0x4a => [0xc0c1,0x20dd], 0x4b => [0xc13c,0x20dd], 0x4c => [0xc2e0,0x20dd], + 0x4d => [0xc5ec,0x20dd], 0x4e => [0xc608,0x20dd], 0x4f => [0xc6d0,0x20dd], + 0x50 => [0xc791,0x20dd], 0x51 => [0xc900,0x20dd], 0x52 => [0xd0b9,0x20dd], + 0x53 => [0xc678,0x20dd], 0x54 => [0xd65c,0x20dd], 0x55 => [0xac04,0x20dd], + 0x56 => [0xac19,0x20dd], 0x57 => [0xc2e4,0x20dd], 0x58 => [0x611f,0x20dd], + 0x59 => [0x6163,0x20dd], 0x5a => [0x4ee3,0x20dd], 0x5b => [0x52d5,0x20dd], + 0x5c => 0x3294, 0x5d => [0x53cd,0x20dd], 0x5e => [0x526f,0x20dd], + 0x5f => [0x81ea,0x20dd], 0x60 => [0x524d,0x20dd], 0x61 => [0x96fb,0x20dd], + 0x62 => [0x63a5,0x20dd], 0x63 => [0x52a9,0x20dd], 0x64 => [0x6ce8,0x20dd], + 0x65 => [0x53c3,0x20dd], 0x66 => [0x672c,0x20dd], 0x67 => [0x65b0,0x20dd], + 0x68 => [0x73fe,0x20dd], 0x69 => [0x5f62,0x20dd], 0x6a => [0x9593,0x20dd], + 0x6b => [0x570b,0x20dd], 0x6c => 0x32a5, 0x6d => [0x4ed6,0x20dd], + 0x6e => [0xbe60,0x20dd], 0x6f => [0xc2dc,0x20dd], 0x70 => [0xc785,0x20dd], + 0x71 => [0xc73c,0x20dd], 0x72 => [0xc74c,0x20dd], 0x73 => [0xc9c1,0x20dd], + 0x74 => [0xd45c,0x20dd], 0x75 => [0xac00,0x20dd], 0x76 => [0xb098,0x20dd], + 0x77 => [0xb2e4,0x20dd], 0x78 => [0xd558,0x20dd], + 0x79 => [0xb9c8,0x20dd,0xf87a], 0x7a => [0xbc14,0x20dd,0xf87a], + 0x7b => [0xc0ac,0x20dd,0xf87a], 0x7c => [0xc544,0x20dd,0xf87a], + 0x7d => [0xc790,0x20dd,0xf87a], 0x81 => [0xcc28,0x20dd,0xf87a], + 0x82 => [0xce74,0x20dd,0xf87a], 0x83 => [0xd0c0,0x20dd,0xf87a], + 0x84 => [0xd30c,0x20dd,0xf87a], 0x85 => [0xd558,0x20dd,0xf87a], + 0x86 => [0xbe44,0x20dd,0xf87a], 0x87 => [0xb2f5,0x20dd,0xf87a], + 0x88 => [0xbe60,0x20dd,0xf87a], 0x89 => [0xbcf8,0x20dd,0xf87a], + 0x8a => [0xb2e8,0x20dd,0xf87a], 0x8b => [0xc13c,0x20dd,0xf87a], + 0x8c => [0xc2dc,0x20dd,0xf87a], 0x8d => [0xc5ec,0x20dd,0xf87a], + 0x8e => [0xc608,0x20dd,0xf87a], 0x8f => [0xc73c,0x20dd,0xf87a], + 0x90 => [0xc74c,0x20dd,0xf87a], 0x91 => [0xc785,0x20dd,0xf87a], + 0x92 => [0xc81c,0x20dd,0xf87a], 0x93 => [0xc874,0x20dd,0xf87a], + 0x94 => [0xc900,0x20dd,0xf87a], 0x95 => [0xd45c,0x20dd,0xf87a], + 0x96 => [0xd574,0x20dd,0xf87a], 0x97 => [0xb290,0x20dd,0xf87a], + 0x98 => [0xb192,0x20dd,0xf87a], 0x99 => [0xb0ae,0x20dd,0xf87a], + 0x9a => [0xbc18,0x20dd,0xf87a], 0x9b => [0xac00,0x20dd,0xf87a], + 0x9c => [0xb098,0x20dd,0xf87a], 0x9d => [0xb2e4,0x20dd,0xf87a], + 0x9e => [0xb77c,0x20dd,0xf87a], 0x9f => [0xc678,0x20dd,0xf87a], + 0xa1 => 0x30a1, 0xa2 => 0x30a2, 0xa3 => 0x30a3, 0xa4 => 0x30a4, + 0xa5 => 0x30a5, 0xa6 => 0x30a6, 0xa7 => 0x30a7, 0xa8 => 0x30a8, + 0xa9 => 0x30a9, 0xaa => 0x30aa, 0xab => 0x30ab, 0xac => 0x30ac, + 0xad => 0x30ad, 0xae => 0x30ae, 0xaf => 0x30af, 0xb0 => 0x30b0, + 0xb1 => 0x30b1, 0xb2 => 0x30b2, 0xb3 => 0x30b3, 0xb4 => 0x30b4, + 0xb5 => 0x30b5, 0xb6 => 0x30b6, 0xb7 => 0x30b7, 0xb8 => 0x30b8, + 0xb9 => 0x30b9, 0xba => 0x30ba, 0xbb => 0x30bb, 0xbc => 0x30bc, + 0xbd => 0x30bd, 0xbe => 0x30be, 0xbf => 0x30bf, 0xc0 => 0x30c0, + 0xc1 => 0x30c1, 0xc2 => 0x30c2, 0xc3 => 0x30c3, 0xc4 => 0x30c4, + 0xc5 => 0x30c5, 0xc6 => 0x30c6, 0xc7 => 0x30c7, 0xc8 => 0x30c8, + 0xc9 => 0x30c9, 0xca => 0x30ca, 0xcb => 0x30cb, 0xcc => 0x30cc, + 0xcd => 0x30cd, 0xce => 0x30ce, 0xcf => 0x30cf, 0xd0 => 0x30d0, + 0xd1 => 0x30d1, 0xd2 => 0x30d2, 0xd3 => 0x30d3, 0xd4 => 0x30d4, + 0xd5 => 0x30d5, 0xd6 => 0x30d6, 0xd7 => 0x30d7, 0xd8 => 0x30d8, + 0xd9 => 0x30d9, 0xda => 0x30da, 0xdb => 0x30db, 0xdc => 0x30dc, + 0xdd => 0x30dd, 0xde => 0x30de, 0xdf => 0x30df, 0xe0 => 0x30e0, + 0xe1 => 0x30e1, 0xe2 => 0x30e2, 0xe3 => 0x30e3, 0xe4 => 0x30e4, + 0xe5 => 0x30e5, 0xe6 => 0x30e6, 0xe7 => 0x30e7, 0xe8 => 0x30e8, + 0xe9 => 0x30e9, 0xea => 0x30ea, 0xeb => 0x30eb, 0xec => 0x30ec, + 0xed => 0x30ed, 0xee => 0x30ee, 0xef => 0x30ef, 0xf0 => 0x30f0, + 0xf1 => 0x30f1, 0xf2 => 0x30f2, 0xf3 => 0x30f3, 0xf4 => 0x30f4, + 0xf5 => 0x30f5, 0xf6 => 0x30f6, 0xf7 => [0xf862,0x28,0x32,0x37,0x29], + 0xf8 => [0xf862,0x28,0x32,0x38,0x29], 0xf9 => [0xf862,0x28,0x32,0x39,0x29], + 0xfa => [0xf862,0x28,0x33,0x30,0x29], + }, + 0xac => { + 0x41 => 0x21f0, 0x42 => 0xf843, 0x43 => 0x27b5, 0x44 => [0x2964,0xf87f], + 0x45 => [0x2962,0xf87f], 0x46 => [0x21e8,0xf870], 0x47 => [0x21e6,0xf870], + 0x48 => 0x27a4, 0x49 => 0xf844, 0x4a => 0xf84b, 0x4b => 0xf84a, + 0x4c => [0x21c0,0xf87f], 0x4d => [0x21bc,0xf87f], 0x4e => [0x21e8,0xf87f], + 0x4f => [0x21e6,0xf87f], 0x50 => 0x21b6, 0x51 => 0x21b7, 0x52 => 0x219d, + 0x53 => 0x219c, 0x54 => 0xf842, 0x55 => [0x2190,0xf87a], + 0x56 => [0x2192,0xf87a], 0x57 => [0x2191,0xf87a], 0x58 => [0x2193,0xf87a], + 0x59 => [0x21e6,0xf87c], 0x5a => [0x21e8,0xf87c], 0x5b => [0x21e7,0xf87c], + 0x5c => [0x21e9,0xf87c], 0x5d => [0x2190,0xf873], 0x5e => 0x2794, + 0x5f => 0xf845, 0x60 => [0x2191,0xf873], 0x61 => [0x2193,0xf873], + 0x62 => [0x2190,0xf878], 0x63 => [0x2192,0xf878], 0x64 => [0x2191,0xf878], + 0x65 => [0x2193,0xf878], 0x66 => [0x2190,0xf874], 0x67 => [0x2192,0xf874], + 0x68 => [0x2191,0xf874], 0x69 => [0x2193,0xf874], 0x6a => 0x21e0, + 0x6b => 0x21e2, 0x6c => 0x21e1, 0x6d => 0x21e3, 0x6e => [0x21e6,0xf875], + 0x6f => [0x21e8,0xf875], 0x70 => [0x21e7,0xf875], 0x71 => [0x21e9,0xf875], + 0x72 => 0x21e6, 0x73 => 0x21e8, 0x74 => 0x21e7, 0x75 => 0x21e9, + 0x76 => [0x2936,0xf87a], 0x77 => [0x21b1,0xf87a], 0x78 => [0x21bb,0xf87a], + 0x79 => [0x2935,0xf87a], 0x7a => [0x21b0,0xf87a], 0x7b => [0x2937,0xf87a], + 0x7c => [0x2939,0xf87a], 0x7d => [0x2934,0xf87a], 0x81 => [0x2936,0xf87c], + 0x82 => [0x21b1,0xf87c], 0x83 => [0x21bb,0xf87c], 0x84 => [0x2935,0xf87c], + 0x85 => [0x21b0,0xf87c], 0x86 => [0x2937,0xf87c], 0x87 => [0x2939,0xf87c], + 0x88 => [0x2934,0xf87c], 0x89 => [0x2190,0xf870], 0x8a => [0x2192,0xf870], + 0x8b => [0x2191,0xf870], 0x8c => [0x2193,0xf870], 0x8d => 0x261d, + 0x8e => 0x261f, 0x8f => [0x261d,0xf87f], 0x90 => [0x261f,0xf87f], + 0x91 => [0xb2e8,0x20dd], 0x92 => [0xcc38,0x20dd], 0x93 => [0xc18c,0x20dd], + 0x94 => [0xc911,0x20dd], 0x95 => [0xc77c,0x20dd], 0x96 => [0xc774,0x20dd], + 0x97 => [0xd734,0x20dd], 0xa1 => 0x0410, 0xa2 => 0x0411, 0xa3 => 0x0412, + 0xa4 => 0x0413, 0xa5 => 0x0414, 0xa6 => 0x0415, 0xa7 => 0x0401, + 0xa8 => 0x0416, 0xa9 => 0x0417, 0xaa => 0x0418, 0xab => 0x0419, + 0xac => 0x041a, 0xad => 0x041b, 0xae => 0x041c, 0xaf => 0x041d, + 0xb0 => 0x041e, 0xb1 => 0x041f, 0xb2 => 0x0420, 0xb3 => 0x0421, + 0xb4 => 0x0422, 0xb5 => 0x0423, 0xb6 => 0x0424, 0xb7 => 0x0425, + 0xb8 => 0x0426, 0xb9 => 0x0427, 0xba => 0x0428, 0xbb => 0x0429, + 0xbc => 0x042a, 0xbd => 0x042b, 0xbe => 0x042c, 0xbf => 0x042d, + 0xc0 => 0x042e, 0xc1 => 0x042f, 0xc2 => [0x31,0x20de,0xf875], + 0xc3 => [0x32,0x20de,0xf875], 0xc4 => [0x33,0x20de,0xf875], + 0xc5 => [0x34,0x20de,0xf875], 0xc6 => [0x35,0x20de,0xf875], + 0xc7 => [0x36,0x20de,0xf875], 0xc8 => [0x37,0x20de,0xf875], + 0xc9 => [0x38,0x20de,0xf875], 0xca => [0x39,0x20de,0xf875], + 0xcb => [0xf866,0x5b,0x31,0x30,0x5d], 0xcc => [0xf866,0x5b,0x31,0x31,0x5d], + 0xcd => [0xf866,0x5b,0x31,0x32,0x5d], 0xce => [0xf866,0x5b,0x31,0x33,0x5d], + 0xcf => [0xf866,0x5b,0x31,0x34,0x5d], 0xd0 => [0xf866,0x5b,0x31,0x35,0x5d], + 0xd1 => 0x0430, 0xd2 => 0x0431, 0xd3 => 0x0432, 0xd4 => 0x0433, + 0xd5 => 0x0434, 0xd6 => 0x0435, 0xd7 => 0x0451, 0xd8 => 0x0436, + 0xd9 => 0x0437, 0xda => 0x0438, 0xdb => 0x0439, 0xdc => 0x043a, + 0xdd => 0x043b, 0xde => 0x043c, 0xdf => 0x043d, 0xe0 => 0x043e, + 0xe1 => 0x043f, 0xe2 => 0x0440, 0xe3 => 0x0441, 0xe4 => 0x0442, + 0xe5 => 0x0443, 0xe6 => 0x0444, 0xe7 => 0x0445, 0xe8 => 0x0446, + 0xe9 => 0x0447, 0xea => 0x0448, 0xeb => 0x0449, 0xec => 0x044a, + 0xed => 0x044b, 0xee => 0x044c, 0xef => 0x044d, 0xf0 => 0x044e, + 0xf1 => 0x044f, 0xf2 => [0xf866,0x5b,0x31,0x36,0x5d], + 0xf3 => [0xf866,0x5b,0x31,0x37,0x5d], 0xf4 => [0xf866,0x5b,0x31,0x38,0x5d], + 0xf5 => [0xf866,0x5b,0x31,0x39,0x5d], 0xf6 => [0xf866,0x5b,0x32,0x30,0x5d], + }, + 0xad => { + 0x41 => [0x4e00,0x20de,0xf87a], 0x42 => [0x4e8c,0x20de,0xf87a], + 0x43 => [0x4e09,0x20de,0xf87a], 0x44 => [0x56db,0x20de,0xf87a], + 0x45 => [0x4e94,0x20de,0xf87a], 0x46 => [0x516d,0x20de,0xf87a], + 0x47 => [0x4e03,0x20de,0xf87a], 0x48 => [0x516b,0x20de,0xf87a], + 0x49 => [0x4e5d,0x20de,0xf87a], 0x4a => [0x5341,0x20de,0xf87a], + 0x4b => [0xf863,0x5b,0x5341,0x4e00,0x5d], + 0x4c => [0xf863,0x5b,0x5341,0x4e8c,0x5d], + 0x4d => [0xf863,0x5b,0x5341,0x4e09,0x5d], + 0x4e => [0xf863,0x5b,0x5341,0x56db,0x5d], + 0x4f => [0xf863,0x5b,0x5341,0x4e94,0x5d], + 0x50 => [0xf863,0x5b,0x5341,0x516d,0x5d], + 0x51 => [0xf863,0x5b,0x5341,0x4e03,0x5d], + 0x52 => [0xf863,0x5b,0x5341,0x516b,0x5d], + 0x53 => [0xf863,0x5b,0x5341,0x4e5d,0x5d], + 0x54 => [0xf863,0x5b,0x4e8c,0x5341,0x5d], 0x55 => [0x4e00,0x20de], + 0x56 => [0x4e8c,0x20de], 0x57 => [0x4e09,0x20de], 0x58 => [0x56db,0x20de], + 0x59 => [0x4e94,0x20de], 0x5a => [0x516d,0x20de], 0x5b => [0x4e03,0x20de], + 0x5c => [0x516b,0x20de], 0x5d => [0x4e5d,0x20de], 0x5e => [0x5341,0x20de], + 0x5f => [0xf862,0x5b,0x5341,0x4e00,0x5d], + 0x60 => [0xf862,0x5b,0x5341,0x4e8c,0x5d], + 0x61 => [0xf862,0x5b,0x5341,0x4e09,0x5d], + 0x62 => [0xf862,0x5b,0x5341,0x56db,0x5d], + 0x63 => [0xf862,0x5b,0x5341,0x4e94,0x5d], + 0x64 => [0xf862,0x5b,0x5341,0x516d,0x5d], + 0x65 => [0xf862,0x5b,0x5341,0x4e03,0x5d], + 0x66 => [0xf862,0x5b,0x5341,0x516b,0x5d], + 0x67 => [0xf862,0x5b,0x5341,0x4e5d,0x5d], + 0x68 => [0xf862,0x5b,0x4e8c,0x5341,0x5d], 0x69 => [0x65e5,0x20de], + 0x6a => [0x6708,0x20de], 0x6b => [0x706b,0x20de], 0x6c => [0x6c34,0x20de], + 0x6d => [0x6728,0x20de], 0x6e => [0x91d1,0x20de], 0x6f => [0x571f,0x20de], + 0x70 => 0x3290, 0x71 => 0x328a, 0x72 => 0x328b, 0x73 => 0x328c, + 0x74 => 0x328d, 0x75 => 0x328e, 0x76 => 0x328f, + 0x77 => [0x65e5,0x20de,0xf87c], 0x78 => [0x6708,0x20de,0xf87c], + 0x79 => [0x706b,0x20de,0xf87c], 0x7a => [0x6c34,0x20de,0xf87c], + 0x7b => [0x6728,0x20de,0xf87c], 0x7c => [0x91d1,0x20de,0xf87c], + 0x7d => [0x571f,0x20de,0xf87c], 0xa1 => [0x300c,0xf879], + 0xa2 => [0x300d,0xf879], 0xa3 => [0x300e,0xf879], 0xa4 => [0x300f,0xf879], + 0xa5 => [0x21e8,0xf878], 0xa6 => [0x21e6,0xf878], 0xa7 => [0x21e7,0xf878], + 0xa8 => [0x21e9,0xf878], 0xa9 => 0x301e, 0xaa => 0x301f, 0xab => 0x2036, + 0xac => [0x2033,0xf873], 0xad => 0x2035, 0xae => [0x2032,0xf873], + 0xaf => [0x21e7,0xf87f], 0xb0 => [0x21,0xf87f], + }, + 0xb0 => { + 0xa1 => 0xac00, 0xa2 => 0xac01, 0xa3 => 0xac04, 0xa4 => 0xac07, + 0xa5 => 0xac08, 0xa6 => 0xac09, 0xa7 => 0xac0a, 0xa8 => 0xac10, + 0xa9 => 0xac11, 0xaa => 0xac12, 0xab => 0xac13, 0xac => 0xac14, + 0xad => 0xac15, 0xae => 0xac16, 0xaf => 0xac17, 0xb0 => 0xac19, + 0xb1 => 0xac1a, 0xb2 => 0xac1b, 0xb3 => 0xac1c, 0xb4 => 0xac1d, + 0xb5 => 0xac20, 0xb6 => 0xac24, 0xb7 => 0xac2c, 0xb8 => 0xac2d, + 0xb9 => 0xac2f, 0xba => 0xac30, 0xbb => 0xac31, 0xbc => 0xac38, + 0xbd => 0xac39, 0xbe => 0xac3c, 0xbf => 0xac40, 0xc0 => 0xac4b, + 0xc1 => 0xac4d, 0xc2 => 0xac54, 0xc3 => 0xac58, 0xc4 => 0xac5c, + 0xc5 => 0xac70, 0xc6 => 0xac71, 0xc7 => 0xac74, 0xc8 => 0xac77, + 0xc9 => 0xac78, 0xca => 0xac7a, 0xcb => 0xac80, 0xcc => 0xac81, + 0xcd => 0xac83, 0xce => 0xac84, 0xcf => 0xac85, 0xd0 => 0xac86, + 0xd1 => 0xac89, 0xd2 => 0xac8a, 0xd3 => 0xac8b, 0xd4 => 0xac8c, + 0xd5 => 0xac90, 0xd6 => 0xac94, 0xd7 => 0xac9c, 0xd8 => 0xac9d, + 0xd9 => 0xac9f, 0xda => 0xaca0, 0xdb => 0xaca1, 0xdc => 0xaca8, + 0xdd => 0xaca9, 0xde => 0xacaa, 0xdf => 0xacac, 0xe0 => 0xacaf, + 0xe1 => 0xacb0, 0xe2 => 0xacb8, 0xe3 => 0xacb9, 0xe4 => 0xacbb, + 0xe5 => 0xacbc, 0xe6 => 0xacbd, 0xe7 => 0xacc1, 0xe8 => 0xacc4, + 0xe9 => 0xacc8, 0xea => 0xaccc, 0xeb => 0xacd5, 0xec => 0xacd7, + 0xed => 0xace0, 0xee => 0xace1, 0xef => 0xace4, 0xf0 => 0xace7, + 0xf1 => 0xace8, 0xf2 => 0xacea, 0xf3 => 0xacec, 0xf4 => 0xacef, + 0xf5 => 0xacf0, 0xf6 => 0xacf1, 0xf7 => 0xacf3, 0xf8 => 0xacf5, + 0xf9 => 0xacf6, 0xfa => 0xacfc, 0xfb => 0xacfd, 0xfc => 0xad00, + 0xfd => 0xad04, 0xfe => 0xad06, + }, + 0xb1 => { + 0xa1 => 0xad0c, 0xa2 => 0xad0d, 0xa3 => 0xad0f, 0xa4 => 0xad11, + 0xa5 => 0xad18, 0xa6 => 0xad1c, 0xa7 => 0xad20, 0xa8 => 0xad29, + 0xa9 => 0xad2c, 0xaa => 0xad2d, 0xab => 0xad34, 0xac => 0xad35, + 0xad => 0xad38, 0xae => 0xad3c, 0xaf => 0xad44, 0xb0 => 0xad45, + 0xb1 => 0xad47, 0xb2 => 0xad49, 0xb3 => 0xad50, 0xb4 => 0xad54, + 0xb5 => 0xad58, 0xb6 => 0xad61, 0xb7 => 0xad63, 0xb8 => 0xad6c, + 0xb9 => 0xad6d, 0xba => 0xad70, 0xbb => 0xad73, 0xbc => 0xad74, + 0xbd => 0xad75, 0xbe => 0xad76, 0xbf => 0xad7b, 0xc0 => 0xad7c, + 0xc1 => 0xad7d, 0xc2 => 0xad7f, 0xc3 => 0xad81, 0xc4 => 0xad82, + 0xc5 => 0xad88, 0xc6 => 0xad89, 0xc7 => 0xad8c, 0xc8 => 0xad90, + 0xc9 => 0xad9c, 0xca => 0xad9d, 0xcb => 0xada4, 0xcc => 0xadb7, + 0xcd => 0xadc0, 0xce => 0xadc1, 0xcf => 0xadc4, 0xd0 => 0xadc8, + 0xd1 => 0xadd0, 0xd2 => 0xadd1, 0xd3 => 0xadd3, 0xd4 => 0xaddc, + 0xd5 => 0xade0, 0xd6 => 0xade4, 0xd7 => 0xadf8, 0xd8 => 0xadf9, + 0xd9 => 0xadfc, 0xda => 0xadff, 0xdb => 0xae00, 0xdc => 0xae01, + 0xdd => 0xae08, 0xde => 0xae09, 0xdf => 0xae0b, 0xe0 => 0xae0d, + 0xe1 => 0xae14, 0xe2 => 0xae30, 0xe3 => 0xae31, 0xe4 => 0xae34, + 0xe5 => 0xae37, 0xe6 => 0xae38, 0xe7 => 0xae3a, 0xe8 => 0xae40, + 0xe9 => 0xae41, 0xea => 0xae43, 0xeb => 0xae45, 0xec => 0xae46, + 0xed => 0xae4a, 0xee => 0xae4c, 0xef => 0xae4d, 0xf0 => 0xae4e, + 0xf1 => 0xae50, 0xf2 => 0xae54, 0xf3 => 0xae56, 0xf4 => 0xae5c, + 0xf5 => 0xae5d, 0xf6 => 0xae5f, 0xf7 => 0xae60, 0xf8 => 0xae61, + 0xf9 => 0xae65, 0xfa => 0xae68, 0xfb => 0xae69, 0xfc => 0xae6c, + 0xfd => 0xae70, 0xfe => 0xae78, + }, + 0xb2 => { + 0xa1 => 0xae79, 0xa2 => 0xae7b, 0xa3 => 0xae7c, 0xa4 => 0xae7d, + 0xa5 => 0xae84, 0xa6 => 0xae85, 0xa7 => 0xae8c, 0xa8 => 0xaebc, + 0xa9 => 0xaebd, 0xaa => 0xaebe, 0xab => 0xaec0, 0xac => 0xaec4, + 0xad => 0xaecc, 0xae => 0xaecd, 0xaf => 0xaecf, 0xb0 => 0xaed0, + 0xb1 => 0xaed1, 0xb2 => 0xaed8, 0xb3 => 0xaed9, 0xb4 => 0xaedc, + 0xb5 => 0xaee8, 0xb6 => 0xaeeb, 0xb7 => 0xaeed, 0xb8 => 0xaef4, + 0xb9 => 0xaef8, 0xba => 0xaefc, 0xbb => 0xaf07, 0xbc => 0xaf08, + 0xbd => 0xaf0d, 0xbe => 0xaf10, 0xbf => 0xaf2c, 0xc0 => 0xaf2d, + 0xc1 => 0xaf30, 0xc2 => 0xaf32, 0xc3 => 0xaf34, 0xc4 => 0xaf3c, + 0xc5 => 0xaf3d, 0xc6 => 0xaf3f, 0xc7 => 0xaf41, 0xc8 => 0xaf42, + 0xc9 => 0xaf43, 0xca => 0xaf48, 0xcb => 0xaf49, 0xcc => 0xaf50, + 0xcd => 0xaf5c, 0xce => 0xaf5d, 0xcf => 0xaf64, 0xd0 => 0xaf65, + 0xd1 => 0xaf79, 0xd2 => 0xaf80, 0xd3 => 0xaf84, 0xd4 => 0xaf88, + 0xd5 => 0xaf90, 0xd6 => 0xaf91, 0xd7 => 0xaf95, 0xd8 => 0xaf9c, + 0xd9 => 0xafb8, 0xda => 0xafb9, 0xdb => 0xafbc, 0xdc => 0xafc0, + 0xdd => 0xafc7, 0xde => 0xafc8, 0xdf => 0xafc9, 0xe0 => 0xafcb, + 0xe1 => 0xafcd, 0xe2 => 0xafce, 0xe3 => 0xafd4, 0xe4 => 0xafdc, + 0xe5 => 0xafe8, 0xe6 => 0xafe9, 0xe7 => 0xaff0, 0xe8 => 0xaff1, + 0xe9 => 0xaff4, 0xea => 0xaff8, 0xeb => 0xb000, 0xec => 0xb001, + 0xed => 0xb004, 0xee => 0xb00c, 0xef => 0xb010, 0xf0 => 0xb014, + 0xf1 => 0xb01c, 0xf2 => 0xb01d, 0xf3 => 0xb028, 0xf4 => 0xb044, + 0xf5 => 0xb045, 0xf6 => 0xb048, 0xf7 => 0xb04a, 0xf8 => 0xb04c, + 0xf9 => 0xb04e, 0xfa => 0xb053, 0xfb => 0xb054, 0xfc => 0xb055, + 0xfd => 0xb057, 0xfe => 0xb059, + }, + 0xb3 => { + 0xa1 => 0xb05d, 0xa2 => 0xb07c, 0xa3 => 0xb07d, 0xa4 => 0xb080, + 0xa5 => 0xb084, 0xa6 => 0xb08c, 0xa7 => 0xb08d, 0xa8 => 0xb08f, + 0xa9 => 0xb091, 0xaa => 0xb098, 0xab => 0xb099, 0xac => 0xb09a, + 0xad => 0xb09c, 0xae => 0xb09f, 0xaf => 0xb0a0, 0xb0 => 0xb0a1, + 0xb1 => 0xb0a2, 0xb2 => 0xb0a8, 0xb3 => 0xb0a9, 0xb4 => 0xb0ab, + 0xb5 => 0xb0ac, 0xb6 => 0xb0ad, 0xb7 => 0xb0ae, 0xb8 => 0xb0af, + 0xb9 => 0xb0b1, 0xba => 0xb0b3, 0xbb => 0xb0b4, 0xbc => 0xb0b5, + 0xbd => 0xb0b8, 0xbe => 0xb0bc, 0xbf => 0xb0c4, 0xc0 => 0xb0c5, + 0xc1 => 0xb0c7, 0xc2 => 0xb0c8, 0xc3 => 0xb0c9, 0xc4 => 0xb0d0, + 0xc5 => 0xb0d1, 0xc6 => 0xb0d4, 0xc7 => 0xb0d8, 0xc8 => 0xb0e0, + 0xc9 => 0xb0e5, 0xca => 0xb108, 0xcb => 0xb109, 0xcc => 0xb10b, + 0xcd => 0xb10c, 0xce => 0xb110, 0xcf => 0xb112, 0xd0 => 0xb113, + 0xd1 => 0xb118, 0xd2 => 0xb119, 0xd3 => 0xb11b, 0xd4 => 0xb11c, + 0xd5 => 0xb11d, 0xd6 => 0xb123, 0xd7 => 0xb124, 0xd8 => 0xb125, + 0xd9 => 0xb128, 0xda => 0xb12c, 0xdb => 0xb134, 0xdc => 0xb135, + 0xdd => 0xb137, 0xde => 0xb138, 0xdf => 0xb139, 0xe0 => 0xb140, + 0xe1 => 0xb141, 0xe2 => 0xb144, 0xe3 => 0xb148, 0xe4 => 0xb150, + 0xe5 => 0xb151, 0xe6 => 0xb154, 0xe7 => 0xb155, 0xe8 => 0xb158, + 0xe9 => 0xb15c, 0xea => 0xb160, 0xeb => 0xb178, 0xec => 0xb179, + 0xed => 0xb17c, 0xee => 0xb180, 0xef => 0xb182, 0xf0 => 0xb188, + 0xf1 => 0xb189, 0xf2 => 0xb18b, 0xf3 => 0xb18d, 0xf4 => 0xb192, + 0xf5 => 0xb193, 0xf6 => 0xb194, 0xf7 => 0xb198, 0xf8 => 0xb19c, + 0xf9 => 0xb1a8, 0xfa => 0xb1cc, 0xfb => 0xb1d0, 0xfc => 0xb1d4, + 0xfd => 0xb1dc, 0xfe => 0xb1dd, + }, + 0xb4 => { + 0xa1 => 0xb1df, 0xa2 => 0xb1e8, 0xa3 => 0xb1e9, 0xa4 => 0xb1ec, + 0xa5 => 0xb1f0, 0xa6 => 0xb1f9, 0xa7 => 0xb1fb, 0xa8 => 0xb1fd, + 0xa9 => 0xb204, 0xaa => 0xb205, 0xab => 0xb208, 0xac => 0xb20b, + 0xad => 0xb20c, 0xae => 0xb214, 0xaf => 0xb215, 0xb0 => 0xb217, + 0xb1 => 0xb219, 0xb2 => 0xb220, 0xb3 => 0xb234, 0xb4 => 0xb23c, + 0xb5 => 0xb258, 0xb6 => 0xb25c, 0xb7 => 0xb260, 0xb8 => 0xb268, + 0xb9 => 0xb269, 0xba => 0xb274, 0xbb => 0xb275, 0xbc => 0xb27c, + 0xbd => 0xb284, 0xbe => 0xb285, 0xbf => 0xb289, 0xc0 => 0xb290, + 0xc1 => 0xb291, 0xc2 => 0xb294, 0xc3 => 0xb298, 0xc4 => 0xb299, + 0xc5 => 0xb29a, 0xc6 => 0xb2a0, 0xc7 => 0xb2a1, 0xc8 => 0xb2a3, + 0xc9 => 0xb2a5, 0xca => 0xb2a6, 0xcb => 0xb2aa, 0xcc => 0xb2ac, + 0xcd => 0xb2b0, 0xce => 0xb2b4, 0xcf => 0xb2c8, 0xd0 => 0xb2c9, + 0xd1 => 0xb2cc, 0xd2 => 0xb2d0, 0xd3 => 0xb2d2, 0xd4 => 0xb2d8, + 0xd5 => 0xb2d9, 0xd6 => 0xb2db, 0xd7 => 0xb2dd, 0xd8 => 0xb2e2, + 0xd9 => 0xb2e4, 0xda => 0xb2e5, 0xdb => 0xb2e6, 0xdc => 0xb2e8, + 0xdd => 0xb2eb, 0xde => 0xb2ec, 0xdf => 0xb2ed, 0xe0 => 0xb2ee, + 0xe1 => 0xb2ef, 0xe2 => 0xb2f3, 0xe3 => 0xb2f4, 0xe4 => 0xb2f5, + 0xe5 => 0xb2f7, 0xe6 => 0xb2f8, 0xe7 => 0xb2f9, 0xe8 => 0xb2fa, + 0xe9 => 0xb2fb, 0xea => 0xb2ff, 0xeb => 0xb300, 0xec => 0xb301, + 0xed => 0xb304, 0xee => 0xb308, 0xef => 0xb310, 0xf0 => 0xb311, + 0xf1 => 0xb313, 0xf2 => 0xb314, 0xf3 => 0xb315, 0xf4 => 0xb31c, + 0xf5 => 0xb354, 0xf6 => 0xb355, 0xf7 => 0xb356, 0xf8 => 0xb358, + 0xf9 => 0xb35b, 0xfa => 0xb35c, 0xfb => 0xb35e, 0xfc => 0xb35f, + 0xfd => 0xb364, 0xfe => 0xb365, + }, + 0xb5 => { + 0xa1 => 0xb367, 0xa2 => 0xb369, 0xa3 => 0xb36b, 0xa4 => 0xb36e, + 0xa5 => 0xb370, 0xa6 => 0xb371, 0xa7 => 0xb374, 0xa8 => 0xb378, + 0xa9 => 0xb380, 0xaa => 0xb381, 0xab => 0xb383, 0xac => 0xb384, + 0xad => 0xb385, 0xae => 0xb38c, 0xaf => 0xb390, 0xb0 => 0xb394, + 0xb1 => 0xb3a0, 0xb2 => 0xb3a1, 0xb3 => 0xb3a8, 0xb4 => 0xb3ac, + 0xb5 => 0xb3c4, 0xb6 => 0xb3c5, 0xb7 => 0xb3c8, 0xb8 => 0xb3cb, + 0xb9 => 0xb3cc, 0xba => 0xb3ce, 0xbb => 0xb3d0, 0xbc => 0xb3d4, + 0xbd => 0xb3d5, 0xbe => 0xb3d7, 0xbf => 0xb3d9, 0xc0 => 0xb3db, + 0xc1 => 0xb3dd, 0xc2 => 0xb3e0, 0xc3 => 0xb3e4, 0xc4 => 0xb3e8, + 0xc5 => 0xb3fc, 0xc6 => 0xb410, 0xc7 => 0xb418, 0xc8 => 0xb41c, + 0xc9 => 0xb420, 0xca => 0xb428, 0xcb => 0xb429, 0xcc => 0xb42b, + 0xcd => 0xb434, 0xce => 0xb450, 0xcf => 0xb451, 0xd0 => 0xb454, + 0xd1 => 0xb458, 0xd2 => 0xb460, 0xd3 => 0xb461, 0xd4 => 0xb463, + 0xd5 => 0xb465, 0xd6 => 0xb46c, 0xd7 => 0xb480, 0xd8 => 0xb488, + 0xd9 => 0xb49d, 0xda => 0xb4a4, 0xdb => 0xb4a8, 0xdc => 0xb4ac, + 0xdd => 0xb4b5, 0xde => 0xb4b7, 0xdf => 0xb4b9, 0xe0 => 0xb4c0, + 0xe1 => 0xb4c4, 0xe2 => 0xb4c8, 0xe3 => 0xb4d0, 0xe4 => 0xb4d5, + 0xe5 => 0xb4dc, 0xe6 => 0xb4dd, 0xe7 => 0xb4e0, 0xe8 => 0xb4e3, + 0xe9 => 0xb4e4, 0xea => 0xb4e6, 0xeb => 0xb4ec, 0xec => 0xb4ed, + 0xed => 0xb4ef, 0xee => 0xb4f1, 0xef => 0xb4f8, 0xf0 => 0xb514, + 0xf1 => 0xb515, 0xf2 => 0xb518, 0xf3 => 0xb51b, 0xf4 => 0xb51c, + 0xf5 => 0xb524, 0xf6 => 0xb525, 0xf7 => 0xb527, 0xf8 => 0xb528, + 0xf9 => 0xb529, 0xfa => 0xb52a, 0xfb => 0xb530, 0xfc => 0xb531, + 0xfd => 0xb534, 0xfe => 0xb538, + }, + 0xb6 => { + 0xa1 => 0xb540, 0xa2 => 0xb541, 0xa3 => 0xb543, 0xa4 => 0xb544, + 0xa5 => 0xb545, 0xa6 => 0xb54b, 0xa7 => 0xb54c, 0xa8 => 0xb54d, + 0xa9 => 0xb550, 0xaa => 0xb554, 0xab => 0xb55c, 0xac => 0xb55d, + 0xad => 0xb55f, 0xae => 0xb560, 0xaf => 0xb561, 0xb0 => 0xb5a0, + 0xb1 => 0xb5a1, 0xb2 => 0xb5a4, 0xb3 => 0xb5a8, 0xb4 => 0xb5aa, + 0xb5 => 0xb5ab, 0xb6 => 0xb5b0, 0xb7 => 0xb5b1, 0xb8 => 0xb5b3, + 0xb9 => 0xb5b4, 0xba => 0xb5b5, 0xbb => 0xb5bb, 0xbc => 0xb5bc, + 0xbd => 0xb5bd, 0xbe => 0xb5c0, 0xbf => 0xb5c4, 0xc0 => 0xb5cc, + 0xc1 => 0xb5cd, 0xc2 => 0xb5cf, 0xc3 => 0xb5d0, 0xc4 => 0xb5d1, + 0xc5 => 0xb5d8, 0xc6 => 0xb5ec, 0xc7 => 0xb610, 0xc8 => 0xb611, + 0xc9 => 0xb614, 0xca => 0xb618, 0xcb => 0xb625, 0xcc => 0xb62c, + 0xcd => 0xb634, 0xce => 0xb648, 0xcf => 0xb664, 0xd0 => 0xb668, + 0xd1 => 0xb69c, 0xd2 => 0xb69d, 0xd3 => 0xb6a0, 0xd4 => 0xb6a4, + 0xd5 => 0xb6ab, 0xd6 => 0xb6ac, 0xd7 => 0xb6b1, 0xd8 => 0xb6d4, + 0xd9 => 0xb6f0, 0xda => 0xb6f4, 0xdb => 0xb6f8, 0xdc => 0xb700, + 0xdd => 0xb701, 0xde => 0xb705, 0xdf => 0xb728, 0xe0 => 0xb729, + 0xe1 => 0xb72c, 0xe2 => 0xb72f, 0xe3 => 0xb730, 0xe4 => 0xb738, + 0xe5 => 0xb739, 0xe6 => 0xb73b, 0xe7 => 0xb744, 0xe8 => 0xb748, + 0xe9 => 0xb74c, 0xea => 0xb754, 0xeb => 0xb755, 0xec => 0xb760, + 0xed => 0xb764, 0xee => 0xb768, 0xef => 0xb770, 0xf0 => 0xb771, + 0xf1 => 0xb773, 0xf2 => 0xb775, 0xf3 => 0xb77c, 0xf4 => 0xb77d, + 0xf5 => 0xb780, 0xf6 => 0xb784, 0xf7 => 0xb78c, 0xf8 => 0xb78d, + 0xf9 => 0xb78f, 0xfa => 0xb790, 0xfb => 0xb791, 0xfc => 0xb792, + 0xfd => 0xb796, 0xfe => 0xb797, + }, + 0xb7 => { + 0xa1 => 0xb798, 0xa2 => 0xb799, 0xa3 => 0xb79c, 0xa4 => 0xb7a0, + 0xa5 => 0xb7a8, 0xa6 => 0xb7a9, 0xa7 => 0xb7ab, 0xa8 => 0xb7ac, + 0xa9 => 0xb7ad, 0xaa => 0xb7b4, 0xab => 0xb7b5, 0xac => 0xb7b8, + 0xad => 0xb7c7, 0xae => 0xb7c9, 0xaf => 0xb7ec, 0xb0 => 0xb7ed, + 0xb1 => 0xb7f0, 0xb2 => 0xb7f4, 0xb3 => 0xb7fc, 0xb4 => 0xb7fd, + 0xb5 => 0xb7ff, 0xb6 => 0xb800, 0xb7 => 0xb801, 0xb8 => 0xb807, + 0xb9 => 0xb808, 0xba => 0xb809, 0xbb => 0xb80c, 0xbc => 0xb810, + 0xbd => 0xb818, 0xbe => 0xb819, 0xbf => 0xb81b, 0xc0 => 0xb81d, + 0xc1 => 0xb824, 0xc2 => 0xb825, 0xc3 => 0xb828, 0xc4 => 0xb82c, + 0xc5 => 0xb834, 0xc6 => 0xb835, 0xc7 => 0xb837, 0xc8 => 0xb838, + 0xc9 => 0xb839, 0xca => 0xb840, 0xcb => 0xb844, 0xcc => 0xb851, + 0xcd => 0xb853, 0xce => 0xb85c, 0xcf => 0xb85d, 0xd0 => 0xb860, + 0xd1 => 0xb864, 0xd2 => 0xb86c, 0xd3 => 0xb86d, 0xd4 => 0xb86f, + 0xd5 => 0xb871, 0xd6 => 0xb878, 0xd7 => 0xb87c, 0xd8 => 0xb88d, + 0xd9 => 0xb8a8, 0xda => 0xb8b0, 0xdb => 0xb8b4, 0xdc => 0xb8b8, + 0xdd => 0xb8c0, 0xde => 0xb8c1, 0xdf => 0xb8c3, 0xe0 => 0xb8c5, + 0xe1 => 0xb8cc, 0xe2 => 0xb8d0, 0xe3 => 0xb8d4, 0xe4 => 0xb8dd, + 0xe5 => 0xb8df, 0xe6 => 0xb8e1, 0xe7 => 0xb8e8, 0xe8 => 0xb8e9, + 0xe9 => 0xb8ec, 0xea => 0xb8f0, 0xeb => 0xb8f8, 0xec => 0xb8f9, + 0xed => 0xb8fb, 0xee => 0xb8fd, 0xef => 0xb904, 0xf0 => 0xb918, + 0xf1 => 0xb920, 0xf2 => 0xb93c, 0xf3 => 0xb93d, 0xf4 => 0xb940, + 0xf5 => 0xb944, 0xf6 => 0xb94c, 0xf7 => 0xb94f, 0xf8 => 0xb951, + 0xf9 => 0xb958, 0xfa => 0xb959, 0xfb => 0xb95c, 0xfc => 0xb960, + 0xfd => 0xb968, 0xfe => 0xb969, + }, + 0xb8 => { + 0xa1 => 0xb96b, 0xa2 => 0xb96d, 0xa3 => 0xb974, 0xa4 => 0xb975, + 0xa5 => 0xb978, 0xa6 => 0xb97c, 0xa7 => 0xb984, 0xa8 => 0xb985, + 0xa9 => 0xb987, 0xaa => 0xb989, 0xab => 0xb98a, 0xac => 0xb98d, + 0xad => 0xb98e, 0xae => 0xb9ac, 0xaf => 0xb9ad, 0xb0 => 0xb9b0, + 0xb1 => 0xb9b4, 0xb2 => 0xb9bc, 0xb3 => 0xb9bd, 0xb4 => 0xb9bf, + 0xb5 => 0xb9c1, 0xb6 => 0xb9c8, 0xb7 => 0xb9c9, 0xb8 => 0xb9cc, + 0xb9 => 0xb9ce, 0xba => 0xb9cf, 0xbb => 0xb9d0, 0xbc => 0xb9d1, + 0xbd => 0xb9d2, 0xbe => 0xb9d8, 0xbf => 0xb9d9, 0xc0 => 0xb9db, + 0xc1 => 0xb9dd, 0xc2 => 0xb9de, 0xc3 => 0xb9e1, 0xc4 => 0xb9e3, + 0xc5 => 0xb9e4, 0xc6 => 0xb9e5, 0xc7 => 0xb9e8, 0xc8 => 0xb9ec, + 0xc9 => 0xb9f4, 0xca => 0xb9f5, 0xcb => 0xb9f7, 0xcc => 0xb9f8, + 0xcd => 0xb9f9, 0xce => 0xb9fa, 0xcf => 0xba00, 0xd0 => 0xba01, + 0xd1 => 0xba08, 0xd2 => 0xba15, 0xd3 => 0xba38, 0xd4 => 0xba39, + 0xd5 => 0xba3c, 0xd6 => 0xba40, 0xd7 => 0xba42, 0xd8 => 0xba48, + 0xd9 => 0xba49, 0xda => 0xba4b, 0xdb => 0xba4d, 0xdc => 0xba4e, + 0xdd => 0xba53, 0xde => 0xba54, 0xdf => 0xba55, 0xe0 => 0xba58, + 0xe1 => 0xba5c, 0xe2 => 0xba64, 0xe3 => 0xba65, 0xe4 => 0xba67, + 0xe5 => 0xba68, 0xe6 => 0xba69, 0xe7 => 0xba70, 0xe8 => 0xba71, + 0xe9 => 0xba74, 0xea => 0xba78, 0xeb => 0xba83, 0xec => 0xba84, + 0xed => 0xba85, 0xee => 0xba87, 0xef => 0xba8c, 0xf0 => 0xbaa8, + 0xf1 => 0xbaa9, 0xf2 => 0xbaab, 0xf3 => 0xbaac, 0xf4 => 0xbab0, + 0xf5 => 0xbab2, 0xf6 => 0xbab8, 0xf7 => 0xbab9, 0xf8 => 0xbabb, + 0xf9 => 0xbabd, 0xfa => 0xbac4, 0xfb => 0xbac8, 0xfc => 0xbad8, + 0xfd => 0xbad9, 0xfe => 0xbafc, + }, + 0xb9 => { + 0xa1 => 0xbb00, 0xa2 => 0xbb04, 0xa3 => 0xbb0d, 0xa4 => 0xbb0f, + 0xa5 => 0xbb11, 0xa6 => 0xbb18, 0xa7 => 0xbb1c, 0xa8 => 0xbb20, + 0xa9 => 0xbb29, 0xaa => 0xbb2b, 0xab => 0xbb34, 0xac => 0xbb35, + 0xad => 0xbb36, 0xae => 0xbb38, 0xaf => 0xbb3b, 0xb0 => 0xbb3c, + 0xb1 => 0xbb3d, 0xb2 => 0xbb3e, 0xb3 => 0xbb44, 0xb4 => 0xbb45, + 0xb5 => 0xbb47, 0xb6 => 0xbb49, 0xb7 => 0xbb4d, 0xb8 => 0xbb4f, + 0xb9 => 0xbb50, 0xba => 0xbb54, 0xbb => 0xbb58, 0xbc => 0xbb61, + 0xbd => 0xbb63, 0xbe => 0xbb6c, 0xbf => 0xbb88, 0xc0 => 0xbb8c, + 0xc1 => 0xbb90, 0xc2 => 0xbba4, 0xc3 => 0xbba8, 0xc4 => 0xbbac, + 0xc5 => 0xbbb4, 0xc6 => 0xbbb7, 0xc7 => 0xbbc0, 0xc8 => 0xbbc4, + 0xc9 => 0xbbc8, 0xca => 0xbbd0, 0xcb => 0xbbd3, 0xcc => 0xbbf8, + 0xcd => 0xbbf9, 0xce => 0xbbfc, 0xcf => 0xbbff, 0xd0 => 0xbc00, + 0xd1 => 0xbc02, 0xd2 => 0xbc08, 0xd3 => 0xbc09, 0xd4 => 0xbc0b, + 0xd5 => 0xbc0c, 0xd6 => 0xbc0d, 0xd7 => 0xbc0f, 0xd8 => 0xbc11, + 0xd9 => 0xbc14, 0xda => 0xbc15, 0xdb => 0xbc16, 0xdc => 0xbc17, + 0xdd => 0xbc18, 0xde => 0xbc1b, 0xdf => 0xbc1c, 0xe0 => 0xbc1d, + 0xe1 => 0xbc1e, 0xe2 => 0xbc1f, 0xe3 => 0xbc24, 0xe4 => 0xbc25, + 0xe5 => 0xbc27, 0xe6 => 0xbc29, 0xe7 => 0xbc2d, 0xe8 => 0xbc30, + 0xe9 => 0xbc31, 0xea => 0xbc34, 0xeb => 0xbc38, 0xec => 0xbc40, + 0xed => 0xbc41, 0xee => 0xbc43, 0xef => 0xbc44, 0xf0 => 0xbc45, + 0xf1 => 0xbc49, 0xf2 => 0xbc4c, 0xf3 => 0xbc4d, 0xf4 => 0xbc50, + 0xf5 => 0xbc5d, 0xf6 => 0xbc84, 0xf7 => 0xbc85, 0xf8 => 0xbc88, + 0xf9 => 0xbc8b, 0xfa => 0xbc8c, 0xfb => 0xbc8e, 0xfc => 0xbc94, + 0xfd => 0xbc95, 0xfe => 0xbc97, + }, + 0xba => { + 0xa1 => 0xbc99, 0xa2 => 0xbc9a, 0xa3 => 0xbca0, 0xa4 => 0xbca1, + 0xa5 => 0xbca4, 0xa6 => 0xbca7, 0xa7 => 0xbca8, 0xa8 => 0xbcb0, + 0xa9 => 0xbcb1, 0xaa => 0xbcb3, 0xab => 0xbcb4, 0xac => 0xbcb5, + 0xad => 0xbcbc, 0xae => 0xbcbd, 0xaf => 0xbcc0, 0xb0 => 0xbcc4, + 0xb1 => 0xbccd, 0xb2 => 0xbccf, 0xb3 => 0xbcd0, 0xb4 => 0xbcd1, + 0xb5 => 0xbcd5, 0xb6 => 0xbcd8, 0xb7 => 0xbcdc, 0xb8 => 0xbcf4, + 0xb9 => 0xbcf5, 0xba => 0xbcf6, 0xbb => 0xbcf8, 0xbc => 0xbcfc, + 0xbd => 0xbd04, 0xbe => 0xbd05, 0xbf => 0xbd07, 0xc0 => 0xbd09, + 0xc1 => 0xbd10, 0xc2 => 0xbd14, 0xc3 => 0xbd24, 0xc4 => 0xbd2c, + 0xc5 => 0xbd40, 0xc6 => 0xbd48, 0xc7 => 0xbd49, 0xc8 => 0xbd4c, + 0xc9 => 0xbd50, 0xca => 0xbd58, 0xcb => 0xbd59, 0xcc => 0xbd64, + 0xcd => 0xbd68, 0xce => 0xbd80, 0xcf => 0xbd81, 0xd0 => 0xbd84, + 0xd1 => 0xbd87, 0xd2 => 0xbd88, 0xd3 => 0xbd89, 0xd4 => 0xbd8a, + 0xd5 => 0xbd90, 0xd6 => 0xbd91, 0xd7 => 0xbd93, 0xd8 => 0xbd95, + 0xd9 => 0xbd99, 0xda => 0xbd9a, 0xdb => 0xbd9c, 0xdc => 0xbda4, + 0xdd => 0xbdb0, 0xde => 0xbdb8, 0xdf => 0xbdd4, 0xe0 => 0xbdd5, + 0xe1 => 0xbdd8, 0xe2 => 0xbddc, 0xe3 => 0xbde9, 0xe4 => 0xbdf0, + 0xe5 => 0xbdf4, 0xe6 => 0xbdf8, 0xe7 => 0xbe00, 0xe8 => 0xbe03, + 0xe9 => 0xbe05, 0xea => 0xbe0c, 0xeb => 0xbe0d, 0xec => 0xbe10, + 0xed => 0xbe14, 0xee => 0xbe1c, 0xef => 0xbe1d, 0xf0 => 0xbe1f, + 0xf1 => 0xbe44, 0xf2 => 0xbe45, 0xf3 => 0xbe48, 0xf4 => 0xbe4c, + 0xf5 => 0xbe4e, 0xf6 => 0xbe54, 0xf7 => 0xbe55, 0xf8 => 0xbe57, + 0xf9 => 0xbe59, 0xfa => 0xbe5a, 0xfb => 0xbe5b, 0xfc => 0xbe60, + 0xfd => 0xbe61, 0xfe => 0xbe64, + }, + 0xbb => { + 0xa1 => 0xbe68, 0xa2 => 0xbe6a, 0xa3 => 0xbe70, 0xa4 => 0xbe71, + 0xa5 => 0xbe73, 0xa6 => 0xbe74, 0xa7 => 0xbe75, 0xa8 => 0xbe7b, + 0xa9 => 0xbe7c, 0xaa => 0xbe7d, 0xab => 0xbe80, 0xac => 0xbe84, + 0xad => 0xbe8c, 0xae => 0xbe8d, 0xaf => 0xbe8f, 0xb0 => 0xbe90, + 0xb1 => 0xbe91, 0xb2 => 0xbe98, 0xb3 => 0xbe99, 0xb4 => 0xbea8, + 0xb5 => 0xbed0, 0xb6 => 0xbed1, 0xb7 => 0xbed4, 0xb8 => 0xbed7, + 0xb9 => 0xbed8, 0xba => 0xbee0, 0xbb => 0xbee3, 0xbc => 0xbee4, + 0xbd => 0xbee5, 0xbe => 0xbeec, 0xbf => 0xbf01, 0xc0 => 0xbf08, + 0xc1 => 0xbf09, 0xc2 => 0xbf18, 0xc3 => 0xbf19, 0xc4 => 0xbf1b, + 0xc5 => 0xbf1c, 0xc6 => 0xbf1d, 0xc7 => 0xbf40, 0xc8 => 0xbf41, + 0xc9 => 0xbf44, 0xca => 0xbf48, 0xcb => 0xbf50, 0xcc => 0xbf51, + 0xcd => 0xbf55, 0xce => 0xbf94, 0xcf => 0xbfb0, 0xd0 => 0xbfc5, + 0xd1 => 0xbfcc, 0xd2 => 0xbfcd, 0xd3 => 0xbfd0, 0xd4 => 0xbfd4, + 0xd5 => 0xbfdc, 0xd6 => 0xbfdf, 0xd7 => 0xbfe1, 0xd8 => 0xc03c, + 0xd9 => 0xc051, 0xda => 0xc058, 0xdb => 0xc05c, 0xdc => 0xc060, + 0xdd => 0xc068, 0xde => 0xc069, 0xdf => 0xc090, 0xe0 => 0xc091, + 0xe1 => 0xc094, 0xe2 => 0xc098, 0xe3 => 0xc0a0, 0xe4 => 0xc0a1, + 0xe5 => 0xc0a3, 0xe6 => 0xc0a5, 0xe7 => 0xc0ac, 0xe8 => 0xc0ad, + 0xe9 => 0xc0af, 0xea => 0xc0b0, 0xeb => 0xc0b3, 0xec => 0xc0b4, + 0xed => 0xc0b5, 0xee => 0xc0b6, 0xef => 0xc0bc, 0xf0 => 0xc0bd, + 0xf1 => 0xc0bf, 0xf2 => 0xc0c0, 0xf3 => 0xc0c1, 0xf4 => 0xc0c5, + 0xf5 => 0xc0c8, 0xf6 => 0xc0c9, 0xf7 => 0xc0cc, 0xf8 => 0xc0d0, + 0xf9 => 0xc0d8, 0xfa => 0xc0d9, 0xfb => 0xc0db, 0xfc => 0xc0dc, + 0xfd => 0xc0dd, 0xfe => 0xc0e4, + }, + 0xbc => { + 0xa1 => 0xc0e5, 0xa2 => 0xc0e8, 0xa3 => 0xc0ec, 0xa4 => 0xc0f4, + 0xa5 => 0xc0f5, 0xa6 => 0xc0f7, 0xa7 => 0xc0f9, 0xa8 => 0xc100, + 0xa9 => 0xc104, 0xaa => 0xc108, 0xab => 0xc110, 0xac => 0xc115, + 0xad => 0xc11c, 0xae => 0xc11d, 0xaf => 0xc11e, 0xb0 => 0xc11f, + 0xb1 => 0xc120, 0xb2 => 0xc123, 0xb3 => 0xc124, 0xb4 => 0xc126, + 0xb5 => 0xc127, 0xb6 => 0xc12c, 0xb7 => 0xc12d, 0xb8 => 0xc12f, + 0xb9 => 0xc130, 0xba => 0xc131, 0xbb => 0xc136, 0xbc => 0xc138, + 0xbd => 0xc139, 0xbe => 0xc13c, 0xbf => 0xc140, 0xc0 => 0xc148, + 0xc1 => 0xc149, 0xc2 => 0xc14b, 0xc3 => 0xc14c, 0xc4 => 0xc14d, + 0xc5 => 0xc154, 0xc6 => 0xc155, 0xc7 => 0xc158, 0xc8 => 0xc15c, + 0xc9 => 0xc164, 0xca => 0xc165, 0xcb => 0xc167, 0xcc => 0xc168, + 0xcd => 0xc169, 0xce => 0xc170, 0xcf => 0xc174, 0xd0 => 0xc178, + 0xd1 => 0xc185, 0xd2 => 0xc18c, 0xd3 => 0xc18d, 0xd4 => 0xc18e, + 0xd5 => 0xc190, 0xd6 => 0xc194, 0xd7 => 0xc196, 0xd8 => 0xc19c, + 0xd9 => 0xc19d, 0xda => 0xc19f, 0xdb => 0xc1a1, 0xdc => 0xc1a5, + 0xdd => 0xc1a8, 0xde => 0xc1a9, 0xdf => 0xc1ac, 0xe0 => 0xc1b0, + 0xe1 => 0xc1bd, 0xe2 => 0xc1c4, 0xe3 => 0xc1c8, 0xe4 => 0xc1cc, + 0xe5 => 0xc1d4, 0xe6 => 0xc1d7, 0xe7 => 0xc1d8, 0xe8 => 0xc1e0, + 0xe9 => 0xc1e4, 0xea => 0xc1e8, 0xeb => 0xc1f0, 0xec => 0xc1f1, + 0xed => 0xc1f3, 0xee => 0xc1fc, 0xef => 0xc1fd, 0xf0 => 0xc200, + 0xf1 => 0xc204, 0xf2 => 0xc20c, 0xf3 => 0xc20d, 0xf4 => 0xc20f, + 0xf5 => 0xc211, 0xf6 => 0xc218, 0xf7 => 0xc219, 0xf8 => 0xc21c, + 0xf9 => 0xc21f, 0xfa => 0xc220, 0xfb => 0xc228, 0xfc => 0xc229, + 0xfd => 0xc22b, 0xfe => 0xc22d, + }, + 0xbd => { + 0xa1 => 0xc22f, 0xa2 => 0xc231, 0xa3 => 0xc232, 0xa4 => 0xc234, + 0xa5 => 0xc248, 0xa6 => 0xc250, 0xa7 => 0xc251, 0xa8 => 0xc254, + 0xa9 => 0xc258, 0xaa => 0xc260, 0xab => 0xc265, 0xac => 0xc26c, + 0xad => 0xc26d, 0xae => 0xc270, 0xaf => 0xc274, 0xb0 => 0xc27c, + 0xb1 => 0xc27d, 0xb2 => 0xc27f, 0xb3 => 0xc281, 0xb4 => 0xc288, + 0xb5 => 0xc289, 0xb6 => 0xc290, 0xb7 => 0xc298, 0xb8 => 0xc29b, + 0xb9 => 0xc29d, 0xba => 0xc2a4, 0xbb => 0xc2a5, 0xbc => 0xc2a8, + 0xbd => 0xc2ac, 0xbe => 0xc2ad, 0xbf => 0xc2b4, 0xc0 => 0xc2b5, + 0xc1 => 0xc2b7, 0xc2 => 0xc2b9, 0xc3 => 0xc2dc, 0xc4 => 0xc2dd, + 0xc5 => 0xc2e0, 0xc6 => 0xc2e3, 0xc7 => 0xc2e4, 0xc8 => 0xc2eb, + 0xc9 => 0xc2ec, 0xca => 0xc2ed, 0xcb => 0xc2ef, 0xcc => 0xc2f1, + 0xcd => 0xc2f6, 0xce => 0xc2f8, 0xcf => 0xc2f9, 0xd0 => 0xc2fb, + 0xd1 => 0xc2fc, 0xd2 => 0xc300, 0xd3 => 0xc308, 0xd4 => 0xc309, + 0xd5 => 0xc30c, 0xd6 => 0xc30d, 0xd7 => 0xc313, 0xd8 => 0xc314, + 0xd9 => 0xc315, 0xda => 0xc318, 0xdb => 0xc31c, 0xdc => 0xc324, + 0xdd => 0xc325, 0xde => 0xc328, 0xdf => 0xc329, 0xe0 => 0xc345, + 0xe1 => 0xc368, 0xe2 => 0xc369, 0xe3 => 0xc36c, 0xe4 => 0xc370, + 0xe5 => 0xc372, 0xe6 => 0xc378, 0xe7 => 0xc379, 0xe8 => 0xc37c, + 0xe9 => 0xc37d, 0xea => 0xc384, 0xeb => 0xc388, 0xec => 0xc38c, + 0xed => 0xc3c0, 0xee => 0xc3d8, 0xef => 0xc3d9, 0xf0 => 0xc3dc, + 0xf1 => 0xc3df, 0xf2 => 0xc3e0, 0xf3 => 0xc3e2, 0xf4 => 0xc3e8, + 0xf5 => 0xc3e9, 0xf6 => 0xc3ed, 0xf7 => 0xc3f4, 0xf8 => 0xc3f5, + 0xf9 => 0xc3f8, 0xfa => 0xc408, 0xfb => 0xc410, 0xfc => 0xc424, + 0xfd => 0xc42c, 0xfe => 0xc430, + }, + 0xbe => { + 0xa1 => 0xc434, 0xa2 => 0xc43c, 0xa3 => 0xc43d, 0xa4 => 0xc448, + 0xa5 => 0xc464, 0xa6 => 0xc465, 0xa7 => 0xc468, 0xa8 => 0xc46c, + 0xa9 => 0xc474, 0xaa => 0xc475, 0xab => 0xc479, 0xac => 0xc480, + 0xad => 0xc494, 0xae => 0xc49c, 0xaf => 0xc4b8, 0xb0 => 0xc4bc, + 0xb1 => 0xc4e9, 0xb2 => 0xc4f0, 0xb3 => 0xc4f1, 0xb4 => 0xc4f4, + 0xb5 => 0xc4f8, 0xb6 => 0xc4fa, 0xb7 => 0xc4ff, 0xb8 => 0xc500, + 0xb9 => 0xc501, 0xba => 0xc50c, 0xbb => 0xc510, 0xbc => 0xc514, + 0xbd => 0xc51c, 0xbe => 0xc528, 0xbf => 0xc529, 0xc0 => 0xc52c, + 0xc1 => 0xc530, 0xc2 => 0xc538, 0xc3 => 0xc539, 0xc4 => 0xc53b, + 0xc5 => 0xc53d, 0xc6 => 0xc544, 0xc7 => 0xc545, 0xc8 => 0xc548, + 0xc9 => 0xc549, 0xca => 0xc54a, 0xcb => 0xc54c, 0xcc => 0xc54d, + 0xcd => 0xc54e, 0xce => 0xc553, 0xcf => 0xc554, 0xd0 => 0xc555, + 0xd1 => 0xc557, 0xd2 => 0xc558, 0xd3 => 0xc559, 0xd4 => 0xc55d, + 0xd5 => 0xc55e, 0xd6 => 0xc560, 0xd7 => 0xc561, 0xd8 => 0xc564, + 0xd9 => 0xc568, 0xda => 0xc570, 0xdb => 0xc571, 0xdc => 0xc573, + 0xdd => 0xc574, 0xde => 0xc575, 0xdf => 0xc57c, 0xe0 => 0xc57d, + 0xe1 => 0xc580, 0xe2 => 0xc584, 0xe3 => 0xc587, 0xe4 => 0xc58c, + 0xe5 => 0xc58d, 0xe6 => 0xc58f, 0xe7 => 0xc591, 0xe8 => 0xc595, + 0xe9 => 0xc597, 0xea => 0xc598, 0xeb => 0xc59c, 0xec => 0xc5a0, + 0xed => 0xc5a9, 0xee => 0xc5b4, 0xef => 0xc5b5, 0xf0 => 0xc5b8, + 0xf1 => 0xc5b9, 0xf2 => 0xc5bb, 0xf3 => 0xc5bc, 0xf4 => 0xc5bd, + 0xf5 => 0xc5be, 0xf6 => 0xc5c4, 0xf7 => 0xc5c5, 0xf8 => 0xc5c6, + 0xf9 => 0xc5c7, 0xfa => 0xc5c8, 0xfb => 0xc5c9, 0xfc => 0xc5ca, + 0xfd => 0xc5cc, 0xfe => 0xc5ce, + }, + 0xbf => { + 0xa1 => 0xc5d0, 0xa2 => 0xc5d1, 0xa3 => 0xc5d4, 0xa4 => 0xc5d8, + 0xa5 => 0xc5e0, 0xa6 => 0xc5e1, 0xa7 => 0xc5e3, 0xa8 => 0xc5e5, + 0xa9 => 0xc5ec, 0xaa => 0xc5ed, 0xab => 0xc5ee, 0xac => 0xc5f0, + 0xad => 0xc5f4, 0xae => 0xc5f6, 0xaf => 0xc5f7, 0xb0 => 0xc5fc, + 0xb1 => 0xc5fd, 0xb2 => 0xc5fe, 0xb3 => 0xc5ff, 0xb4 => 0xc600, + 0xb5 => 0xc601, 0xb6 => 0xc605, 0xb7 => 0xc606, 0xb8 => 0xc607, + 0xb9 => 0xc608, 0xba => 0xc60c, 0xbb => 0xc610, 0xbc => 0xc618, + 0xbd => 0xc619, 0xbe => 0xc61b, 0xbf => 0xc61c, 0xc0 => 0xc624, + 0xc1 => 0xc625, 0xc2 => 0xc628, 0xc3 => 0xc62c, 0xc4 => 0xc62d, + 0xc5 => 0xc62e, 0xc6 => 0xc630, 0xc7 => 0xc633, 0xc8 => 0xc634, + 0xc9 => 0xc635, 0xca => 0xc637, 0xcb => 0xc639, 0xcc => 0xc63b, + 0xcd => 0xc640, 0xce => 0xc641, 0xcf => 0xc644, 0xd0 => 0xc648, + 0xd1 => 0xc650, 0xd2 => 0xc651, 0xd3 => 0xc653, 0xd4 => 0xc654, + 0xd5 => 0xc655, 0xd6 => 0xc65c, 0xd7 => 0xc65d, 0xd8 => 0xc660, + 0xd9 => 0xc66c, 0xda => 0xc66f, 0xdb => 0xc671, 0xdc => 0xc678, + 0xdd => 0xc679, 0xde => 0xc67c, 0xdf => 0xc680, 0xe0 => 0xc688, + 0xe1 => 0xc689, 0xe2 => 0xc68b, 0xe3 => 0xc68d, 0xe4 => 0xc694, + 0xe5 => 0xc695, 0xe6 => 0xc698, 0xe7 => 0xc69c, 0xe8 => 0xc6a4, + 0xe9 => 0xc6a5, 0xea => 0xc6a7, 0xeb => 0xc6a9, 0xec => 0xc6b0, + 0xed => 0xc6b1, 0xee => 0xc6b4, 0xef => 0xc6b8, 0xf0 => 0xc6b9, + 0xf1 => 0xc6ba, 0xf2 => 0xc6c0, 0xf3 => 0xc6c1, 0xf4 => 0xc6c3, + 0xf5 => 0xc6c5, 0xf6 => 0xc6cc, 0xf7 => 0xc6cd, 0xf8 => 0xc6d0, + 0xf9 => 0xc6d4, 0xfa => 0xc6dc, 0xfb => 0xc6dd, 0xfc => 0xc6e0, + 0xfd => 0xc6e1, 0xfe => 0xc6e8, + }, + 0xc0 => { + 0xa1 => 0xc6e9, 0xa2 => 0xc6ec, 0xa3 => 0xc6f0, 0xa4 => 0xc6f8, + 0xa5 => 0xc6f9, 0xa6 => 0xc6fd, 0xa7 => 0xc704, 0xa8 => 0xc705, + 0xa9 => 0xc708, 0xaa => 0xc70c, 0xab => 0xc714, 0xac => 0xc715, + 0xad => 0xc717, 0xae => 0xc719, 0xaf => 0xc720, 0xb0 => 0xc721, + 0xb1 => 0xc724, 0xb2 => 0xc728, 0xb3 => 0xc730, 0xb4 => 0xc731, + 0xb5 => 0xc733, 0xb6 => 0xc735, 0xb7 => 0xc737, 0xb8 => 0xc73c, + 0xb9 => 0xc73d, 0xba => 0xc740, 0xbb => 0xc744, 0xbc => 0xc74a, + 0xbd => 0xc74c, 0xbe => 0xc74d, 0xbf => 0xc74f, 0xc0 => 0xc751, + 0xc1 => 0xc752, 0xc2 => 0xc753, 0xc3 => 0xc754, 0xc4 => 0xc755, + 0xc5 => 0xc756, 0xc6 => 0xc757, 0xc7 => 0xc758, 0xc8 => 0xc75c, + 0xc9 => 0xc760, 0xca => 0xc768, 0xcb => 0xc76b, 0xcc => 0xc774, + 0xcd => 0xc775, 0xce => 0xc778, 0xcf => 0xc77c, 0xd0 => 0xc77d, + 0xd1 => 0xc77e, 0xd2 => 0xc783, 0xd3 => 0xc784, 0xd4 => 0xc785, + 0xd5 => 0xc787, 0xd6 => 0xc788, 0xd7 => 0xc789, 0xd8 => 0xc78a, + 0xd9 => 0xc78e, 0xda => 0xc790, 0xdb => 0xc791, 0xdc => 0xc794, + 0xdd => 0xc796, 0xde => 0xc797, 0xdf => 0xc798, 0xe0 => 0xc79a, + 0xe1 => 0xc7a0, 0xe2 => 0xc7a1, 0xe3 => 0xc7a3, 0xe4 => 0xc7a4, + 0xe5 => 0xc7a5, 0xe6 => 0xc7a6, 0xe7 => 0xc7ac, 0xe8 => 0xc7ad, + 0xe9 => 0xc7b0, 0xea => 0xc7b4, 0xeb => 0xc7bc, 0xec => 0xc7bd, + 0xed => 0xc7bf, 0xee => 0xc7c0, 0xef => 0xc7c1, 0xf0 => 0xc7c8, + 0xf1 => 0xc7c9, 0xf2 => 0xc7cc, 0xf3 => 0xc7ce, 0xf4 => 0xc7d0, + 0xf5 => 0xc7d8, 0xf6 => 0xc7dd, 0xf7 => 0xc7e4, 0xf8 => 0xc7e8, + 0xf9 => 0xc7ec, 0xfa => 0xc800, 0xfb => 0xc801, 0xfc => 0xc804, + 0xfd => 0xc808, 0xfe => 0xc80a, + }, + 0xc1 => { + 0xa1 => 0xc810, 0xa2 => 0xc811, 0xa3 => 0xc813, 0xa4 => 0xc815, + 0xa5 => 0xc816, 0xa6 => 0xc81c, 0xa7 => 0xc81d, 0xa8 => 0xc820, + 0xa9 => 0xc824, 0xaa => 0xc82c, 0xab => 0xc82d, 0xac => 0xc82f, + 0xad => 0xc831, 0xae => 0xc838, 0xaf => 0xc83c, 0xb0 => 0xc840, + 0xb1 => 0xc848, 0xb2 => 0xc849, 0xb3 => 0xc84c, 0xb4 => 0xc84d, + 0xb5 => 0xc854, 0xb6 => 0xc870, 0xb7 => 0xc871, 0xb8 => 0xc874, + 0xb9 => 0xc878, 0xba => 0xc87a, 0xbb => 0xc880, 0xbc => 0xc881, + 0xbd => 0xc883, 0xbe => 0xc885, 0xbf => 0xc886, 0xc0 => 0xc887, + 0xc1 => 0xc88b, 0xc2 => 0xc88c, 0xc3 => 0xc88d, 0xc4 => 0xc894, + 0xc5 => 0xc89d, 0xc6 => 0xc89f, 0xc7 => 0xc8a1, 0xc8 => 0xc8a8, + 0xc9 => 0xc8bc, 0xca => 0xc8bd, 0xcb => 0xc8c4, 0xcc => 0xc8c8, + 0xcd => 0xc8cc, 0xce => 0xc8d4, 0xcf => 0xc8d5, 0xd0 => 0xc8d7, + 0xd1 => 0xc8d9, 0xd2 => 0xc8e0, 0xd3 => 0xc8e1, 0xd4 => 0xc8e4, + 0xd5 => 0xc8f5, 0xd6 => 0xc8fc, 0xd7 => 0xc8fd, 0xd8 => 0xc900, + 0xd9 => 0xc904, 0xda => 0xc905, 0xdb => 0xc906, 0xdc => 0xc90c, + 0xdd => 0xc90d, 0xde => 0xc90f, 0xdf => 0xc911, 0xe0 => 0xc918, + 0xe1 => 0xc92c, 0xe2 => 0xc934, 0xe3 => 0xc950, 0xe4 => 0xc951, + 0xe5 => 0xc954, 0xe6 => 0xc958, 0xe7 => 0xc960, 0xe8 => 0xc961, + 0xe9 => 0xc963, 0xea => 0xc96c, 0xeb => 0xc970, 0xec => 0xc974, + 0xed => 0xc97c, 0xee => 0xc988, 0xef => 0xc989, 0xf0 => 0xc98c, + 0xf1 => 0xc990, 0xf2 => 0xc998, 0xf3 => 0xc999, 0xf4 => 0xc99b, + 0xf5 => 0xc99d, 0xf6 => 0xc9c0, 0xf7 => 0xc9c1, 0xf8 => 0xc9c4, + 0xf9 => 0xc9c7, 0xfa => 0xc9c8, 0xfb => 0xc9ca, 0xfc => 0xc9d0, + 0xfd => 0xc9d1, 0xfe => 0xc9d3, + }, + 0xc2 => { + 0xa1 => 0xc9d5, 0xa2 => 0xc9d6, 0xa3 => 0xc9d9, 0xa4 => 0xc9da, + 0xa5 => 0xc9dc, 0xa6 => 0xc9dd, 0xa7 => 0xc9e0, 0xa8 => 0xc9e2, + 0xa9 => 0xc9e4, 0xaa => 0xc9e7, 0xab => 0xc9ec, 0xac => 0xc9ed, + 0xad => 0xc9ef, 0xae => 0xc9f0, 0xaf => 0xc9f1, 0xb0 => 0xc9f8, + 0xb1 => 0xc9f9, 0xb2 => 0xc9fc, 0xb3 => 0xca00, 0xb4 => 0xca08, + 0xb5 => 0xca09, 0xb6 => 0xca0b, 0xb7 => 0xca0c, 0xb8 => 0xca0d, + 0xb9 => 0xca14, 0xba => 0xca18, 0xbb => 0xca29, 0xbc => 0xca4c, + 0xbd => 0xca4d, 0xbe => 0xca50, 0xbf => 0xca54, 0xc0 => 0xca5c, + 0xc1 => 0xca5d, 0xc2 => 0xca5f, 0xc3 => 0xca60, 0xc4 => 0xca61, + 0xc5 => 0xca68, 0xc6 => 0xca7d, 0xc7 => 0xca84, 0xc8 => 0xca98, + 0xc9 => 0xcabc, 0xca => 0xcabd, 0xcb => 0xcac0, 0xcc => 0xcac4, + 0xcd => 0xcacc, 0xce => 0xcacd, 0xcf => 0xcacf, 0xd0 => 0xcad1, + 0xd1 => 0xcad3, 0xd2 => 0xcad8, 0xd3 => 0xcad9, 0xd4 => 0xcae0, + 0xd5 => 0xcaec, 0xd6 => 0xcaf4, 0xd7 => 0xcb08, 0xd8 => 0xcb10, + 0xd9 => 0xcb14, 0xda => 0xcb18, 0xdb => 0xcb20, 0xdc => 0xcb21, + 0xdd => 0xcb41, 0xde => 0xcb48, 0xdf => 0xcb49, 0xe0 => 0xcb4c, + 0xe1 => 0xcb50, 0xe2 => 0xcb58, 0xe3 => 0xcb59, 0xe4 => 0xcb5d, + 0xe5 => 0xcb64, 0xe6 => 0xcb78, 0xe7 => 0xcb79, 0xe8 => 0xcb9c, + 0xe9 => 0xcbb8, 0xea => 0xcbd4, 0xeb => 0xcbe4, 0xec => 0xcbe7, + 0xed => 0xcbe9, 0xee => 0xcc0c, 0xef => 0xcc0d, 0xf0 => 0xcc10, + 0xf1 => 0xcc14, 0xf2 => 0xcc1c, 0xf3 => 0xcc1d, 0xf4 => 0xcc21, + 0xf5 => 0xcc22, 0xf6 => 0xcc27, 0xf7 => 0xcc28, 0xf8 => 0xcc29, + 0xf9 => 0xcc2c, 0xfa => 0xcc2e, 0xfb => 0xcc30, 0xfc => 0xcc38, + 0xfd => 0xcc39, 0xfe => 0xcc3b, + }, + 0xc3 => { + 0xa1 => 0xcc3c, 0xa2 => 0xcc3d, 0xa3 => 0xcc3e, 0xa4 => 0xcc44, + 0xa5 => 0xcc45, 0xa6 => 0xcc48, 0xa7 => 0xcc4c, 0xa8 => 0xcc54, + 0xa9 => 0xcc55, 0xaa => 0xcc57, 0xab => 0xcc58, 0xac => 0xcc59, + 0xad => 0xcc60, 0xae => 0xcc64, 0xaf => 0xcc66, 0xb0 => 0xcc68, + 0xb1 => 0xcc70, 0xb2 => 0xcc75, 0xb3 => 0xcc98, 0xb4 => 0xcc99, + 0xb5 => 0xcc9c, 0xb6 => 0xcca0, 0xb7 => 0xcca8, 0xb8 => 0xcca9, + 0xb9 => 0xccab, 0xba => 0xccac, 0xbb => 0xccad, 0xbc => 0xccb4, + 0xbd => 0xccb5, 0xbe => 0xccb8, 0xbf => 0xccbc, 0xc0 => 0xccc4, + 0xc1 => 0xccc5, 0xc2 => 0xccc7, 0xc3 => 0xccc9, 0xc4 => 0xccd0, + 0xc5 => 0xccd4, 0xc6 => 0xcce4, 0xc7 => 0xccec, 0xc8 => 0xccf0, + 0xc9 => 0xcd01, 0xca => 0xcd08, 0xcb => 0xcd09, 0xcc => 0xcd0c, + 0xcd => 0xcd10, 0xce => 0xcd18, 0xcf => 0xcd19, 0xd0 => 0xcd1b, + 0xd1 => 0xcd1d, 0xd2 => 0xcd24, 0xd3 => 0xcd28, 0xd4 => 0xcd2c, + 0xd5 => 0xcd39, 0xd6 => 0xcd5c, 0xd7 => 0xcd60, 0xd8 => 0xcd64, + 0xd9 => 0xcd6c, 0xda => 0xcd6d, 0xdb => 0xcd6f, 0xdc => 0xcd71, + 0xdd => 0xcd78, 0xde => 0xcd88, 0xdf => 0xcd94, 0xe0 => 0xcd95, + 0xe1 => 0xcd98, 0xe2 => 0xcd9c, 0xe3 => 0xcda4, 0xe4 => 0xcda5, + 0xe5 => 0xcda7, 0xe6 => 0xcda9, 0xe7 => 0xcdb0, 0xe8 => 0xcdc4, + 0xe9 => 0xcdcc, 0xea => 0xcdd0, 0xeb => 0xcde8, 0xec => 0xcdec, + 0xed => 0xcdf0, 0xee => 0xcdf8, 0xef => 0xcdf9, 0xf0 => 0xcdfb, + 0xf1 => 0xcdfd, 0xf2 => 0xce04, 0xf3 => 0xce08, 0xf4 => 0xce0c, + 0xf5 => 0xce14, 0xf6 => 0xce19, 0xf7 => 0xce20, 0xf8 => 0xce21, + 0xf9 => 0xce24, 0xfa => 0xce28, 0xfb => 0xce30, 0xfc => 0xce31, + 0xfd => 0xce33, 0xfe => 0xce35, + }, + 0xc4 => { + 0xa1 => 0xce58, 0xa2 => 0xce59, 0xa3 => 0xce5c, 0xa4 => 0xce5f, + 0xa5 => 0xce60, 0xa6 => 0xce61, 0xa7 => 0xce68, 0xa8 => 0xce69, + 0xa9 => 0xce6b, 0xaa => 0xce6d, 0xab => 0xce74, 0xac => 0xce75, + 0xad => 0xce78, 0xae => 0xce7c, 0xaf => 0xce84, 0xb0 => 0xce85, + 0xb1 => 0xce87, 0xb2 => 0xce89, 0xb3 => 0xce90, 0xb4 => 0xce91, + 0xb5 => 0xce94, 0xb6 => 0xce98, 0xb7 => 0xcea0, 0xb8 => 0xcea1, + 0xb9 => 0xcea3, 0xba => 0xcea4, 0xbb => 0xcea5, 0xbc => 0xceac, + 0xbd => 0xcead, 0xbe => 0xcec1, 0xbf => 0xcee4, 0xc0 => 0xcee5, + 0xc1 => 0xcee8, 0xc2 => 0xceeb, 0xc3 => 0xceec, 0xc4 => 0xcef4, + 0xc5 => 0xcef5, 0xc6 => 0xcef7, 0xc7 => 0xcef8, 0xc8 => 0xcef9, + 0xc9 => 0xcf00, 0xca => 0xcf01, 0xcb => 0xcf04, 0xcc => 0xcf08, + 0xcd => 0xcf10, 0xce => 0xcf11, 0xcf => 0xcf13, 0xd0 => 0xcf15, + 0xd1 => 0xcf1c, 0xd2 => 0xcf20, 0xd3 => 0xcf24, 0xd4 => 0xcf2c, + 0xd5 => 0xcf2d, 0xd6 => 0xcf2f, 0xd7 => 0xcf30, 0xd8 => 0xcf31, + 0xd9 => 0xcf38, 0xda => 0xcf54, 0xdb => 0xcf55, 0xdc => 0xcf58, + 0xdd => 0xcf5c, 0xde => 0xcf64, 0xdf => 0xcf65, 0xe0 => 0xcf67, + 0xe1 => 0xcf69, 0xe2 => 0xcf70, 0xe3 => 0xcf71, 0xe4 => 0xcf74, + 0xe5 => 0xcf78, 0xe6 => 0xcf80, 0xe7 => 0xcf85, 0xe8 => 0xcf8c, + 0xe9 => 0xcfa1, 0xea => 0xcfa8, 0xeb => 0xcfb0, 0xec => 0xcfc4, + 0xed => 0xcfe0, 0xee => 0xcfe1, 0xef => 0xcfe4, 0xf0 => 0xcfe8, + 0xf1 => 0xcff0, 0xf2 => 0xcff1, 0xf3 => 0xcff3, 0xf4 => 0xcff5, + 0xf5 => 0xcffc, 0xf6 => 0xd000, 0xf7 => 0xd004, 0xf8 => 0xd011, + 0xf9 => 0xd018, 0xfa => 0xd02d, 0xfb => 0xd034, 0xfc => 0xd035, + 0xfd => 0xd038, 0xfe => 0xd03c, + }, + 0xc5 => { + 0xa1 => 0xd044, 0xa2 => 0xd045, 0xa3 => 0xd047, 0xa4 => 0xd049, + 0xa5 => 0xd050, 0xa6 => 0xd054, 0xa7 => 0xd058, 0xa8 => 0xd060, + 0xa9 => 0xd06c, 0xaa => 0xd06d, 0xab => 0xd070, 0xac => 0xd074, + 0xad => 0xd07c, 0xae => 0xd07d, 0xaf => 0xd081, 0xb0 => 0xd0a4, + 0xb1 => 0xd0a5, 0xb2 => 0xd0a8, 0xb3 => 0xd0ac, 0xb4 => 0xd0b4, + 0xb5 => 0xd0b5, 0xb6 => 0xd0b7, 0xb7 => 0xd0b9, 0xb8 => 0xd0c0, + 0xb9 => 0xd0c1, 0xba => 0xd0c4, 0xbb => 0xd0c8, 0xbc => 0xd0c9, + 0xbd => 0xd0d0, 0xbe => 0xd0d1, 0xbf => 0xd0d3, 0xc0 => 0xd0d4, + 0xc1 => 0xd0d5, 0xc2 => 0xd0dc, 0xc3 => 0xd0dd, 0xc4 => 0xd0e0, + 0xc5 => 0xd0e4, 0xc6 => 0xd0ec, 0xc7 => 0xd0ed, 0xc8 => 0xd0ef, + 0xc9 => 0xd0f0, 0xca => 0xd0f1, 0xcb => 0xd0f8, 0xcc => 0xd10d, + 0xcd => 0xd130, 0xce => 0xd131, 0xcf => 0xd134, 0xd0 => 0xd138, + 0xd1 => 0xd13a, 0xd2 => 0xd140, 0xd3 => 0xd141, 0xd4 => 0xd143, + 0xd5 => 0xd144, 0xd6 => 0xd145, 0xd7 => 0xd14c, 0xd8 => 0xd14d, + 0xd9 => 0xd150, 0xda => 0xd154, 0xdb => 0xd15c, 0xdc => 0xd15d, + 0xdd => 0xd15f, 0xde => 0xd161, 0xdf => 0xd168, 0xe0 => 0xd16c, + 0xe1 => 0xd17c, 0xe2 => 0xd184, 0xe3 => 0xd188, 0xe4 => 0xd1a0, + 0xe5 => 0xd1a1, 0xe6 => 0xd1a4, 0xe7 => 0xd1a8, 0xe8 => 0xd1b0, + 0xe9 => 0xd1b1, 0xea => 0xd1b3, 0xeb => 0xd1b5, 0xec => 0xd1ba, + 0xed => 0xd1bc, 0xee => 0xd1c0, 0xef => 0xd1d8, 0xf0 => 0xd1f4, + 0xf1 => 0xd1f8, 0xf2 => 0xd207, 0xf3 => 0xd209, 0xf4 => 0xd210, + 0xf5 => 0xd22c, 0xf6 => 0xd22d, 0xf7 => 0xd230, 0xf8 => 0xd234, + 0xf9 => 0xd23c, 0xfa => 0xd23d, 0xfb => 0xd23f, 0xfc => 0xd241, + 0xfd => 0xd248, 0xfe => 0xd25c, + }, + 0xc6 => { + 0xa1 => 0xd264, 0xa2 => 0xd280, 0xa3 => 0xd281, 0xa4 => 0xd284, + 0xa5 => 0xd288, 0xa6 => 0xd290, 0xa7 => 0xd291, 0xa8 => 0xd295, + 0xa9 => 0xd29c, 0xaa => 0xd2a0, 0xab => 0xd2a4, 0xac => 0xd2ac, + 0xad => 0xd2b1, 0xae => 0xd2b8, 0xaf => 0xd2b9, 0xb0 => 0xd2bc, + 0xb1 => 0xd2bf, 0xb2 => 0xd2c0, 0xb3 => 0xd2c2, 0xb4 => 0xd2c8, + 0xb5 => 0xd2c9, 0xb6 => 0xd2cb, 0xb7 => 0xd2d4, 0xb8 => 0xd2d8, + 0xb9 => 0xd2dc, 0xba => 0xd2e4, 0xbb => 0xd2e5, 0xbc => 0xd2f0, + 0xbd => 0xd2f1, 0xbe => 0xd2f4, 0xbf => 0xd2f8, 0xc0 => 0xd300, + 0xc1 => 0xd301, 0xc2 => 0xd303, 0xc3 => 0xd305, 0xc4 => 0xd30c, + 0xc5 => 0xd30d, 0xc6 => 0xd30e, 0xc7 => 0xd310, 0xc8 => 0xd314, + 0xc9 => 0xd316, 0xca => 0xd31c, 0xcb => 0xd31d, 0xcc => 0xd31f, + 0xcd => 0xd320, 0xce => 0xd321, 0xcf => 0xd325, 0xd0 => 0xd328, + 0xd1 => 0xd329, 0xd2 => 0xd32c, 0xd3 => 0xd330, 0xd4 => 0xd338, + 0xd5 => 0xd339, 0xd6 => 0xd33b, 0xd7 => 0xd33c, 0xd8 => 0xd33d, + 0xd9 => 0xd344, 0xda => 0xd345, 0xdb => 0xd37c, 0xdc => 0xd37d, + 0xdd => 0xd380, 0xde => 0xd384, 0xdf => 0xd38c, 0xe0 => 0xd38d, + 0xe1 => 0xd38f, 0xe2 => 0xd390, 0xe3 => 0xd391, 0xe4 => 0xd398, + 0xe5 => 0xd399, 0xe6 => 0xd39c, 0xe7 => 0xd3a0, 0xe8 => 0xd3a8, + 0xe9 => 0xd3a9, 0xea => 0xd3ab, 0xeb => 0xd3ad, 0xec => 0xd3b4, + 0xed => 0xd3b8, 0xee => 0xd3bc, 0xef => 0xd3c4, 0xf0 => 0xd3c5, + 0xf1 => 0xd3c8, 0xf2 => 0xd3c9, 0xf3 => 0xd3d0, 0xf4 => 0xd3d8, + 0xf5 => 0xd3e1, 0xf6 => 0xd3e3, 0xf7 => 0xd3ec, 0xf8 => 0xd3ed, + 0xf9 => 0xd3f0, 0xfa => 0xd3f4, 0xfb => 0xd3fc, 0xfc => 0xd3fd, + 0xfd => 0xd3ff, 0xfe => 0xd401, + }, + 0xc7 => { + 0xa1 => 0xd408, 0xa2 => 0xd41d, 0xa3 => 0xd440, 0xa4 => 0xd444, + 0xa5 => 0xd45c, 0xa6 => 0xd460, 0xa7 => 0xd464, 0xa8 => 0xd46d, + 0xa9 => 0xd46f, 0xaa => 0xd478, 0xab => 0xd479, 0xac => 0xd47c, + 0xad => 0xd47f, 0xae => 0xd480, 0xaf => 0xd482, 0xb0 => 0xd488, + 0xb1 => 0xd489, 0xb2 => 0xd48b, 0xb3 => 0xd48d, 0xb4 => 0xd494, + 0xb5 => 0xd4a9, 0xb6 => 0xd4cc, 0xb7 => 0xd4d0, 0xb8 => 0xd4d4, + 0xb9 => 0xd4dc, 0xba => 0xd4df, 0xbb => 0xd4e8, 0xbc => 0xd4ec, + 0xbd => 0xd4f0, 0xbe => 0xd4f8, 0xbf => 0xd4fb, 0xc0 => 0xd4fd, + 0xc1 => 0xd504, 0xc2 => 0xd508, 0xc3 => 0xd50c, 0xc4 => 0xd514, + 0xc5 => 0xd515, 0xc6 => 0xd517, 0xc7 => 0xd53c, 0xc8 => 0xd53d, + 0xc9 => 0xd540, 0xca => 0xd544, 0xcb => 0xd54c, 0xcc => 0xd54d, + 0xcd => 0xd54f, 0xce => 0xd551, 0xcf => 0xd558, 0xd0 => 0xd559, + 0xd1 => 0xd55c, 0xd2 => 0xd560, 0xd3 => 0xd565, 0xd4 => 0xd568, + 0xd5 => 0xd569, 0xd6 => 0xd56b, 0xd7 => 0xd56d, 0xd8 => 0xd574, + 0xd9 => 0xd575, 0xda => 0xd578, 0xdb => 0xd57c, 0xdc => 0xd584, + 0xdd => 0xd585, 0xde => 0xd587, 0xdf => 0xd588, 0xe0 => 0xd589, + 0xe1 => 0xd590, 0xe2 => 0xd5a5, 0xe3 => 0xd5c8, 0xe4 => 0xd5c9, + 0xe5 => 0xd5cc, 0xe6 => 0xd5d0, 0xe7 => 0xd5d2, 0xe8 => 0xd5d8, + 0xe9 => 0xd5d9, 0xea => 0xd5db, 0xeb => 0xd5dd, 0xec => 0xd5e4, + 0xed => 0xd5e5, 0xee => 0xd5e8, 0xef => 0xd5ec, 0xf0 => 0xd5f4, + 0xf1 => 0xd5f5, 0xf2 => 0xd5f7, 0xf3 => 0xd5f9, 0xf4 => 0xd600, + 0xf5 => 0xd601, 0xf6 => 0xd604, 0xf7 => 0xd608, 0xf8 => 0xd610, + 0xf9 => 0xd611, 0xfa => 0xd613, 0xfb => 0xd614, 0xfc => 0xd615, + 0xfd => 0xd61c, 0xfe => 0xd620, + }, + 0xc8 => { + 0xa1 => 0xd624, 0xa2 => 0xd62d, 0xa3 => 0xd638, 0xa4 => 0xd639, + 0xa5 => 0xd63c, 0xa6 => 0xd640, 0xa7 => 0xd645, 0xa8 => 0xd648, + 0xa9 => 0xd649, 0xaa => 0xd64b, 0xab => 0xd64d, 0xac => 0xd651, + 0xad => 0xd654, 0xae => 0xd655, 0xaf => 0xd658, 0xb0 => 0xd65c, + 0xb1 => 0xd667, 0xb2 => 0xd669, 0xb3 => 0xd670, 0xb4 => 0xd671, + 0xb5 => 0xd674, 0xb6 => 0xd683, 0xb7 => 0xd685, 0xb8 => 0xd68c, + 0xb9 => 0xd68d, 0xba => 0xd690, 0xbb => 0xd694, 0xbc => 0xd69d, + 0xbd => 0xd69f, 0xbe => 0xd6a1, 0xbf => 0xd6a8, 0xc0 => 0xd6ac, + 0xc1 => 0xd6b0, 0xc2 => 0xd6b9, 0xc3 => 0xd6bb, 0xc4 => 0xd6c4, + 0xc5 => 0xd6c5, 0xc6 => 0xd6c8, 0xc7 => 0xd6cc, 0xc8 => 0xd6d1, + 0xc9 => 0xd6d4, 0xca => 0xd6d7, 0xcb => 0xd6d9, 0xcc => 0xd6e0, + 0xcd => 0xd6e4, 0xce => 0xd6e8, 0xcf => 0xd6f0, 0xd0 => 0xd6f5, + 0xd1 => 0xd6fc, 0xd2 => 0xd6fd, 0xd3 => 0xd700, 0xd4 => 0xd704, + 0xd5 => 0xd711, 0xd6 => 0xd718, 0xd7 => 0xd719, 0xd8 => 0xd71c, + 0xd9 => 0xd720, 0xda => 0xd728, 0xdb => 0xd729, 0xdc => 0xd72b, + 0xdd => 0xd72d, 0xde => 0xd734, 0xdf => 0xd735, 0xe0 => 0xd738, + 0xe1 => 0xd73c, 0xe2 => 0xd744, 0xe3 => 0xd747, 0xe4 => 0xd749, + 0xe5 => 0xd750, 0xe6 => 0xd751, 0xe7 => 0xd754, 0xe8 => 0xd756, + 0xe9 => 0xd757, 0xea => 0xd758, 0xeb => 0xd759, 0xec => 0xd760, + 0xed => 0xd761, 0xee => 0xd763, 0xef => 0xd765, 0xf0 => 0xd769, + 0xf1 => 0xd76c, 0xf2 => 0xd770, 0xf3 => 0xd774, 0xf4 => 0xd77c, + 0xf5 => 0xd77d, 0xf6 => 0xd781, 0xf7 => 0xd788, 0xf8 => 0xd789, + 0xf9 => 0xd78c, 0xfa => 0xd790, 0xfb => 0xd798, 0xfc => 0xd799, + 0xfd => 0xd79b, 0xfe => 0xd79d, + }, + 0xca => { + 0xa1 => 0x4f3d, 0xa2 => 0x4f73, 0xa3 => 0x5047, 0xa4 => 0x50f9, + 0xa5 => 0x52a0, 0xa6 => 0x53ef, 0xa7 => 0x5475, 0xa8 => 0x54e5, + 0xa9 => 0x5609, 0xaa => 0x5ac1, 0xab => 0x5bb6, 0xac => 0x6687, + 0xad => 0x67b6, 0xae => 0x67b7, 0xaf => 0x67ef, 0xb0 => 0x6b4c, + 0xb1 => 0x73c2, 0xb2 => 0x75c2, 0xb3 => 0x7a3c, 0xb4 => 0x82db, + 0xb5 => 0x8304, 0xb6 => 0x8857, 0xb7 => 0x8888, 0xb8 => 0x8a36, + 0xb9 => 0x8cc8, 0xba => 0x8dcf, 0xbb => 0x8efb, 0xbc => 0x8fe6, + 0xbd => 0x99d5, 0xbe => 0x523b, 0xbf => 0x5374, 0xc0 => 0x5404, + 0xc1 => 0x606a, 0xc2 => 0x6164, 0xc3 => 0x6bbc, 0xc4 => 0x73cf, + 0xc5 => 0x811a, 0xc6 => 0x89ba, 0xc7 => 0x89d2, 0xc8 => 0x95a3, + 0xc9 => 0x4f83, 0xca => 0x520a, 0xcb => 0x58be, 0xcc => 0x5978, + 0xcd => 0x59e6, 0xce => 0x5e72, 0xcf => 0x5e79, 0xd0 => 0x61c7, + 0xd1 => 0x63c0, 0xd2 => 0x6746, 0xd3 => 0x67ec, 0xd4 => 0x687f, + 0xd5 => 0x6f97, 0xd6 => 0x764e, 0xd7 => 0x770b, 0xd8 => 0x78f5, + 0xd9 => 0x7a08, 0xda => 0x7aff, 0xdb => 0x7c21, 0xdc => 0x809d, + 0xdd => 0x826e, 0xde => 0x8271, 0xdf => 0x8aeb, 0xe0 => 0x9593, + 0xe1 => 0x4e6b, 0xe2 => 0x559d, 0xe3 => 0x66f7, 0xe4 => 0x6e34, + 0xe5 => 0x78a3, 0xe6 => 0x7aed, 0xe7 => 0x845b, 0xe8 => 0x8910, + 0xe9 => 0x874e, 0xea => 0x97a8, 0xeb => 0x52d8, 0xec => 0x574e, + 0xed => 0x582a, 0xee => 0x5d4c, 0xef => 0x611f, 0xf0 => 0x61be, + 0xf1 => 0x6221, 0xf2 => 0x6562, 0xf3 => 0x67d1, 0xf4 => 0x6a44, + 0xf5 => 0x6e1b, 0xf6 => 0x7518, 0xf7 => 0x75b3, 0xf8 => 0x76e3, + 0xf9 => 0x77b0, 0xfa => 0x7d3a, 0xfb => 0x90af, 0xfc => 0x9451, + 0xfd => 0x9452, 0xfe => 0x9f95, + }, + 0xcb => { + 0xa1 => 0x5323, 0xa2 => 0x5cac, 0xa3 => 0x7532, 0xa4 => 0x80db, + 0xa5 => 0x9240, 0xa6 => 0x9598, 0xa7 => 0x525b, 0xa8 => 0x5808, + 0xa9 => 0x59dc, 0xaa => 0x5ca1, 0xab => 0x5d17, 0xac => 0x5eb7, + 0xad => 0x5f3a, 0xae => 0x5f4a, 0xaf => 0x6177, 0xb0 => 0x6c5f, + 0xb1 => 0x757a, 0xb2 => 0x7586, 0xb3 => 0x7ce0, 0xb4 => 0x7d73, + 0xb5 => 0x7db1, 0xb6 => 0x7f8c, 0xb7 => 0x8154, 0xb8 => 0x8221, + 0xb9 => 0x8591, 0xba => 0x8941, 0xbb => 0x8b1b, 0xbc => 0x92fc, + 0xbd => 0x964d, 0xbe => 0x9c47, 0xbf => 0x4ecb, 0xc0 => 0x4ef7, + 0xc1 => 0x500b, 0xc2 => 0x51f1, 0xc3 => 0x584f, 0xc4 => 0x6137, + 0xc5 => 0x613e, 0xc6 => 0x6168, 0xc7 => 0x6539, 0xc8 => 0x69ea, + 0xc9 => 0x6f11, 0xca => 0x75a5, 0xcb => 0x7686, 0xcc => 0x76d6, + 0xcd => 0x7b87, 0xce => 0x82a5, 0xcf => 0x84cb, 0xd0 => 0xf900, + 0xd1 => 0x93a7, 0xd2 => 0x958b, 0xd3 => 0x5580, 0xd4 => 0x5ba2, + 0xd5 => 0x5751, 0xd6 => 0xf901, 0xd7 => 0x7cb3, 0xd8 => 0x7fb9, + 0xd9 => 0x91b5, 0xda => 0x5028, 0xdb => 0x53bb, 0xdc => 0x5c45, + 0xdd => 0x5de8, 0xde => 0x62d2, 0xdf => 0x636e, 0xe0 => 0x64da, + 0xe1 => 0x64e7, 0xe2 => 0x6e20, 0xe3 => 0x70ac, 0xe4 => 0x795b, + 0xe5 => 0x8ddd, 0xe6 => 0x8e1e, 0xe7 => 0xf902, 0xe8 => 0x907d, + 0xe9 => 0x9245, 0xea => 0x92f8, 0xeb => 0x4e7e, 0xec => 0x4ef6, + 0xed => 0x5065, 0xee => 0x5dfe, 0xef => 0x5efa, 0xf0 => 0x6106, + 0xf1 => 0x6957, 0xf2 => 0x8171, 0xf3 => 0x8654, 0xf4 => 0x8e47, + 0xf5 => 0x9375, 0xf6 => 0x9a2b, 0xf7 => 0x4e5e, 0xf8 => 0x5091, + 0xf9 => 0x6770, 0xfa => 0x6840, 0xfb => 0x5109, 0xfc => 0x528d, + 0xfd => 0x5292, 0xfe => 0x6aa2, + }, + 0xcc => { + 0xa1 => 0x77bc, 0xa2 => 0x9210, 0xa3 => 0x9ed4, 0xa4 => 0x52ab, + 0xa5 => 0x602f, 0xa6 => 0x8ff2, 0xa7 => 0x5048, 0xa8 => 0x61a9, + 0xa9 => 0x63ed, 0xaa => 0x64ca, 0xab => 0x683c, 0xac => 0x6a84, + 0xad => 0x6fc0, 0xae => 0x8188, 0xaf => 0x89a1, 0xb0 => 0x9694, + 0xb1 => 0x5805, 0xb2 => 0x727d, 0xb3 => 0x72ac, 0xb4 => 0x7504, + 0xb5 => 0x7d79, 0xb6 => 0x7e6d, 0xb7 => 0x80a9, 0xb8 => 0x898b, + 0xb9 => 0x8b74, 0xba => 0x9063, 0xbb => 0x9d51, 0xbc => 0x6289, + 0xbd => 0x6c7a, 0xbe => 0x6f54, 0xbf => 0x7d50, 0xc0 => 0x7f3a, + 0xc1 => 0x8a23, 0xc2 => 0x517c, 0xc3 => 0x614a, 0xc4 => 0x7b9d, + 0xc5 => 0x8b19, 0xc6 => 0x9257, 0xc7 => 0x938c, 0xc8 => 0x4eac, + 0xc9 => 0x4fd3, 0xca => 0x501e, 0xcb => 0x50be, 0xcc => 0x5106, + 0xcd => 0x52c1, 0xce => 0x52cd, 0xcf => 0x537f, 0xd0 => 0x5770, + 0xd1 => 0x5883, 0xd2 => 0x5e9a, 0xd3 => 0x5f91, 0xd4 => 0x6176, + 0xd5 => 0x61ac, 0xd6 => 0x64ce, 0xd7 => 0x656c, 0xd8 => 0x666f, + 0xd9 => 0x66bb, 0xda => 0x66f4, 0xdb => 0x6897, 0xdc => 0x6d87, + 0xdd => 0x7085, 0xde => 0x70f1, 0xdf => 0x749f, 0xe0 => 0x74a5, + 0xe1 => 0x74ca, 0xe2 => 0x75d9, 0xe3 => 0x786c, 0xe4 => 0x78ec, + 0xe5 => 0x7adf, 0xe6 => 0x7af6, 0xe7 => 0x7d45, 0xe8 => 0x7d93, + 0xe9 => 0x8015, 0xea => 0x803f, 0xeb => 0x811b, 0xec => 0x8396, + 0xed => 0x8b66, 0xee => 0x8f15, 0xef => 0x9015, 0xf0 => 0x93e1, + 0xf1 => 0x9803, 0xf2 => 0x9838, 0xf3 => 0x9a5a, 0xf4 => 0x9be8, + 0xf5 => 0x4fc2, 0xf6 => 0x5553, 0xf7 => 0x583a, 0xf8 => 0x5951, + 0xf9 => 0x5b63, 0xfa => 0x5c46, 0xfb => 0x60b8, 0xfc => 0x6212, + 0xfd => 0x6842, 0xfe => 0x68b0, + }, + 0xcd => { + 0xa1 => 0x68e8, 0xa2 => 0x6eaa, 0xa3 => 0x754c, 0xa4 => 0x7678, + 0xa5 => 0x78ce, 0xa6 => 0x7a3d, 0xa7 => 0x7cfb, 0xa8 => 0x7e6b, + 0xa9 => 0x7e7c, 0xaa => 0x8a08, 0xab => 0x8aa1, 0xac => 0x8c3f, + 0xad => 0x968e, 0xae => 0x9dc4, 0xaf => 0x53e4, 0xb0 => 0x53e9, + 0xb1 => 0x544a, 0xb2 => 0x5471, 0xb3 => 0x56fa, 0xb4 => 0x59d1, + 0xb5 => 0x5b64, 0xb6 => 0x5c3b, 0xb7 => 0x5eab, 0xb8 => 0x62f7, + 0xb9 => 0x6537, 0xba => 0x6545, 0xbb => 0x6572, 0xbc => 0x66a0, + 0xbd => 0x67af, 0xbe => 0x69c1, 0xbf => 0x6cbd, 0xc0 => 0x75fc, + 0xc1 => 0x7690, 0xc2 => 0x777e, 0xc3 => 0x7a3f, 0xc4 => 0x7f94, + 0xc5 => 0x8003, 0xc6 => 0x80a1, 0xc7 => 0x818f, 0xc8 => 0x82e6, + 0xc9 => 0x82fd, 0xca => 0x83f0, 0xcb => 0x85c1, 0xcc => 0x8831, + 0xcd => 0x88b4, 0xce => 0x8aa5, 0xcf => 0xf903, 0xd0 => 0x8f9c, + 0xd1 => 0x932e, 0xd2 => 0x96c7, 0xd3 => 0x9867, 0xd4 => 0x9ad8, + 0xd5 => 0x9f13, 0xd6 => 0x54ed, 0xd7 => 0x659b, 0xd8 => 0x66f2, + 0xd9 => 0x688f, 0xda => 0x7a40, 0xdb => 0x8c37, 0xdc => 0x9d60, + 0xdd => 0x56f0, 0xde => 0x5764, 0xdf => 0x5d11, 0xe0 => 0x6606, + 0xe1 => 0x68b1, 0xe2 => 0x68cd, 0xe3 => 0x6efe, 0xe4 => 0x7428, + 0xe5 => 0x889e, 0xe6 => 0x9be4, 0xe7 => 0x6c68, 0xe8 => 0xf904, + 0xe9 => 0x9aa8, 0xea => 0x4f9b, 0xeb => 0x516c, 0xec => 0x5171, + 0xed => 0x529f, 0xee => 0x5b54, 0xef => 0x5de5, 0xf0 => 0x6050, + 0xf1 => 0x606d, 0xf2 => 0x62f1, 0xf3 => 0x63a7, 0xf4 => 0x653b, + 0xf5 => 0x73d9, 0xf6 => 0x7a7a, 0xf7 => 0x86a3, 0xf8 => 0x8ca2, + 0xf9 => 0x978f, 0xfa => 0x4e32, 0xfb => 0x5be1, 0xfc => 0x6208, + 0xfd => 0x679c, 0xfe => 0x74dc, + }, + 0xce => { + 0xa1 => 0x79d1, 0xa2 => 0x83d3, 0xa3 => 0x8a87, 0xa4 => 0x8ab2, + 0xa5 => 0x8de8, 0xa6 => 0x904e, 0xa7 => 0x934b, 0xa8 => 0x9846, + 0xa9 => 0x5ed3, 0xaa => 0x69e8, 0xab => 0x85ff, 0xac => 0x90ed, + 0xad => 0xf905, 0xae => 0x51a0, 0xaf => 0x5b98, 0xb0 => 0x5bec, + 0xb1 => 0x6163, 0xb2 => 0x68fa, 0xb3 => 0x6b3e, 0xb4 => 0x704c, + 0xb5 => 0x742f, 0xb6 => 0x74d8, 0xb7 => 0x7ba1, 0xb8 => 0x7f50, + 0xb9 => 0x83c5, 0xba => 0x89c0, 0xbb => 0x8cab, 0xbc => 0x95dc, + 0xbd => 0x9928, 0xbe => 0x522e, 0xbf => 0x605d, 0xc0 => 0x62ec, + 0xc1 => 0x9002, 0xc2 => 0x4f8a, 0xc3 => 0x5149, 0xc4 => 0x5321, + 0xc5 => 0x58d9, 0xc6 => 0x5ee3, 0xc7 => 0x66e0, 0xc8 => 0x6d38, + 0xc9 => 0x709a, 0xca => 0x72c2, 0xcb => 0x73d6, 0xcc => 0x7b50, + 0xcd => 0x80f1, 0xce => 0x945b, 0xcf => 0x5366, 0xd0 => 0x639b, + 0xd1 => 0x7f6b, 0xd2 => 0x4e56, 0xd3 => 0x5080, 0xd4 => 0x584a, + 0xd5 => 0x58de, 0xd6 => 0x602a, 0xd7 => 0x6127, 0xd8 => 0x62d0, + 0xd9 => 0x69d0, 0xda => 0x9b41, 0xdb => 0x5b8f, 0xdc => 0x7d18, + 0xdd => 0x80b1, 0xde => 0x8f5f, 0xdf => 0x4ea4, 0xe0 => 0x50d1, + 0xe1 => 0x54ac, 0xe2 => 0x55ac, 0xe3 => 0x5b0c, 0xe4 => 0x5da0, + 0xe5 => 0x5de7, 0xe6 => 0x652a, 0xe7 => 0x654e, 0xe8 => 0x6821, + 0xe9 => 0x6a4b, 0xea => 0x72e1, 0xeb => 0x768e, 0xec => 0x77ef, + 0xed => 0x7d5e, 0xee => 0x7ff9, 0xef => 0x81a0, 0xf0 => 0x854e, + 0xf1 => 0x86df, 0xf2 => 0x8f03, 0xf3 => 0x8f4e, 0xf4 => 0x90ca, + 0xf5 => 0x9903, 0xf6 => 0x9a55, 0xf7 => 0x9bab, 0xf8 => 0x4e18, + 0xf9 => 0x4e45, 0xfa => 0x4e5d, 0xfb => 0x4ec7, 0xfc => 0x4ff1, + 0xfd => 0x5177, 0xfe => 0x52fe, + }, + 0xcf => { + 0xa1 => 0x5340, 0xa2 => 0x53e3, 0xa3 => 0x53e5, 0xa4 => 0x548e, + 0xa5 => 0x5614, 0xa6 => 0x5775, 0xa7 => 0x57a2, 0xa8 => 0x5bc7, + 0xa9 => 0x5d87, 0xaa => 0x5ed0, 0xab => 0x61fc, 0xac => 0x62d8, + 0xad => 0x6551, 0xae => 0x67b8, 0xaf => 0x67e9, 0xb0 => 0x69cb, + 0xb1 => 0x6b50, 0xb2 => 0x6bc6, 0xb3 => 0x6bec, 0xb4 => 0x6c42, + 0xb5 => 0x6e9d, 0xb6 => 0x7078, 0xb7 => 0x72d7, 0xb8 => 0x7396, + 0xb9 => 0x7403, 0xba => 0x77bf, 0xbb => 0x77e9, 0xbc => 0x7a76, + 0xbd => 0x7d7f, 0xbe => 0x8009, 0xbf => 0x81fc, 0xc0 => 0x8205, + 0xc1 => 0x820a, 0xc2 => 0x82df, 0xc3 => 0x8862, 0xc4 => 0x8b33, + 0xc5 => 0x8cfc, 0xc6 => 0x8ec0, 0xc7 => 0x9011, 0xc8 => 0x90b1, + 0xc9 => 0x9264, 0xca => 0x92b6, 0xcb => 0x99d2, 0xcc => 0x9a45, + 0xcd => 0x9ce9, 0xce => 0x9dd7, 0xcf => 0x9f9c, 0xd0 => 0x570b, + 0xd1 => 0x5c40, 0xd2 => 0x83ca, 0xd3 => 0x97a0, 0xd4 => 0x97ab, + 0xd5 => 0x9eb4, 0xd6 => 0x541b, 0xd7 => 0x7a98, 0xd8 => 0x7fa4, + 0xd9 => 0x88d9, 0xda => 0x8ecd, 0xdb => 0x90e1, 0xdc => 0x5800, + 0xdd => 0x5c48, 0xde => 0x6398, 0xdf => 0x7a9f, 0xe0 => 0x5bae, + 0xe1 => 0x5f13, 0xe2 => 0x7a79, 0xe3 => 0x7aae, 0xe4 => 0x828e, + 0xe5 => 0x8eac, 0xe6 => 0x5026, 0xe7 => 0x5238, 0xe8 => 0x52f8, + 0xe9 => 0x5377, 0xea => 0x5708, 0xeb => 0x62f3, 0xec => 0x6372, + 0xed => 0x6b0a, 0xee => 0x6dc3, 0xef => 0x7737, 0xf0 => 0x53a5, + 0xf1 => 0x7357, 0xf2 => 0x8568, 0xf3 => 0x8e76, 0xf4 => 0x95d5, + 0xf5 => 0x673a, 0xf6 => 0x6ac3, 0xf7 => 0x6f70, 0xf8 => 0x8a6d, + 0xf9 => 0x8ecc, 0xfa => 0x994b, 0xfb => 0xf906, 0xfc => 0x6677, + 0xfd => 0x6b78, 0xfe => 0x8cb4, + }, + 0xd0 => { + 0xa1 => 0x9b3c, 0xa2 => 0xf907, 0xa3 => 0x53eb, 0xa4 => 0x572d, + 0xa5 => 0x594e, 0xa6 => 0x63c6, 0xa7 => 0x69fb, 0xa8 => 0x73ea, + 0xa9 => 0x7845, 0xaa => 0x7aba, 0xab => 0x7ac5, 0xac => 0x7cfe, + 0xad => 0x8475, 0xae => 0x898f, 0xaf => 0x8d73, 0xb0 => 0x9035, + 0xb1 => 0x95a8, 0xb2 => 0x52fb, 0xb3 => 0x5747, 0xb4 => 0x7547, + 0xb5 => 0x7b60, 0xb6 => 0x83cc, 0xb7 => 0x921e, 0xb8 => 0xf908, + 0xb9 => 0x6a58, 0xba => 0x514b, 0xbb => 0x524b, 0xbc => 0x5287, + 0xbd => 0x621f, 0xbe => 0x68d8, 0xbf => 0x6975, 0xc0 => 0x9699, + 0xc1 => 0x50c5, 0xc2 => 0x52a4, 0xc3 => 0x52e4, 0xc4 => 0x61c3, + 0xc5 => 0x65a4, 0xc6 => 0x6839, 0xc7 => 0x69ff, 0xc8 => 0x747e, + 0xc9 => 0x7b4b, 0xca => 0x82b9, 0xcb => 0x83eb, 0xcc => 0x89b2, + 0xcd => 0x8b39, 0xce => 0x8fd1, 0xcf => 0x9949, 0xd0 => 0xf909, + 0xd1 => 0x4eca, 0xd2 => 0x5997, 0xd3 => 0x64d2, 0xd4 => 0x6611, + 0xd5 => 0x6a8e, 0xd6 => 0x7434, 0xd7 => 0x7981, 0xd8 => 0x79bd, + 0xd9 => 0x82a9, 0xda => 0x887e, 0xdb => 0x887f, 0xdc => 0x895f, + 0xdd => 0xf90a, 0xde => 0x9326, 0xdf => 0x4f0b, 0xe0 => 0x53ca, + 0xe1 => 0x6025, 0xe2 => 0x6271, 0xe3 => 0x6c72, 0xe4 => 0x7d1a, + 0xe5 => 0x7d66, 0xe6 => 0x4e98, 0xe7 => 0x5162, 0xe8 => 0x77dc, + 0xe9 => 0x80af, 0xea => 0x4f01, 0xeb => 0x4f0e, 0xec => 0x5176, + 0xed => 0x5180, 0xee => 0x55dc, 0xef => 0x5668, 0xf0 => 0x573b, + 0xf1 => 0x57fa, 0xf2 => 0x57fc, 0xf3 => 0x5914, 0xf4 => 0x5947, + 0xf5 => 0x5993, 0xf6 => 0x5bc4, 0xf7 => 0x5c90, 0xf8 => 0x5d0e, + 0xf9 => 0x5df1, 0xfa => 0x5e7e, 0xfb => 0x5fcc, 0xfc => 0x6280, + 0xfd => 0x65d7, 0xfe => 0x65e3, + }, + 0xd1 => { + 0xa1 => 0x671e, 0xa2 => 0x671f, 0xa3 => 0x675e, 0xa4 => 0x68cb, + 0xa5 => 0x68c4, 0xa6 => 0x6a5f, 0xa7 => 0x6b3a, 0xa8 => 0x6c23, + 0xa9 => 0x6c7d, 0xaa => 0x6c82, 0xab => 0x6dc7, 0xac => 0x7398, + 0xad => 0x7426, 0xae => 0x742a, 0xaf => 0x7482, 0xb0 => 0x74a3, + 0xb1 => 0x7578, 0xb2 => 0x757f, 0xb3 => 0x7881, 0xb4 => 0x78ef, + 0xb5 => 0x7941, 0xb6 => 0x7947, 0xb7 => 0x7948, 0xb8 => 0x797a, + 0xb9 => 0x7b95, 0xba => 0x7d00, 0xbb => 0x7dba, 0xbc => 0x7f88, + 0xbd => 0x8006, 0xbe => 0x802d, 0xbf => 0x808c, 0xc0 => 0x8a18, + 0xc1 => 0x8b4f, 0xc2 => 0x8c48, 0xc3 => 0x8d77, 0xc4 => 0x9321, + 0xc5 => 0x9324, 0xc6 => 0x98e2, 0xc7 => 0x9951, 0xc8 => 0x9a0e, + 0xc9 => 0x9a0f, 0xca => 0x9a65, 0xcb => 0x9e92, 0xcc => 0x7dca, + 0xcd => 0x4f76, 0xce => 0x5409, 0xcf => 0x62ee, 0xd0 => 0x6854, + 0xd1 => 0x91d1, 0xd2 => 0x55ab, 0xd3 => 0x513a, 0xd4 => 0xf90b, + 0xd5 => 0xf90c, 0xd6 => 0x5a1c, 0xd7 => 0x61e6, 0xd8 => 0xf90d, + 0xd9 => 0x62cf, 0xda => 0x62ff, 0xdb => 0xf90e, 0xdc => 0xf90f, + 0xdd => 0xf910, 0xde => 0xf911, 0xdf => 0xf912, 0xe0 => 0xf913, + 0xe1 => 0x90a3, 0xe2 => 0xf914, 0xe3 => 0xf915, 0xe4 => 0xf916, + 0xe5 => 0xf917, 0xe6 => 0xf918, 0xe7 => 0x8afe, 0xe8 => 0xf919, + 0xe9 => 0xf91a, 0xea => 0xf91b, 0xeb => 0xf91c, 0xec => 0x6696, + 0xed => 0xf91d, 0xee => 0x7156, 0xef => 0xf91e, 0xf0 => 0xf91f, + 0xf1 => 0x96e3, 0xf2 => 0xf920, 0xf3 => 0x634f, 0xf4 => 0x637a, + 0xf5 => 0x5357, 0xf6 => 0xf921, 0xf7 => 0x678f, 0xf8 => 0x6960, + 0xf9 => 0x6e73, 0xfa => 0xf922, 0xfb => 0x7537, 0xfc => 0xf923, + 0xfd => 0xf924, 0xfe => 0xf925, + }, + 0xd2 => { + 0xa1 => 0x7d0d, 0xa2 => 0xf926, 0xa3 => 0xf927, 0xa4 => 0x8872, + 0xa5 => 0x56ca, 0xa6 => 0x5a18, 0xa7 => 0xf928, 0xa8 => 0xf929, + 0xa9 => 0xf92a, 0xaa => 0xf92b, 0xab => 0xf92c, 0xac => 0x4e43, + 0xad => 0xf92d, 0xae => 0x5167, 0xaf => 0x5948, 0xb0 => 0x67f0, + 0xb1 => 0x8010, 0xb2 => 0xf92e, 0xb3 => 0x5973, 0xb4 => 0x5e74, + 0xb5 => 0x649a, 0xb6 => 0x79ca, 0xb7 => 0x5ff5, 0xb8 => 0x606c, + 0xb9 => 0x62c8, 0xba => 0x637b, 0xbb => 0x5be7, 0xbc => 0x5bd7, + 0xbd => 0x52aa, 0xbe => 0xf92f, 0xbf => 0x5974, 0xc0 => 0x5f29, + 0xc1 => 0x6012, 0xc2 => 0xf930, 0xc3 => 0xf931, 0xc4 => 0xf932, + 0xc5 => 0x7459, 0xc6 => 0xf933, 0xc7 => 0xf934, 0xc8 => 0xf935, + 0xc9 => 0xf936, 0xca => 0xf937, 0xcb => 0xf938, 0xcc => 0x99d1, + 0xcd => 0xf939, 0xce => 0xf93a, 0xcf => 0xf93b, 0xd0 => 0xf93c, + 0xd1 => 0xf93d, 0xd2 => 0xf93e, 0xd3 => 0xf93f, 0xd4 => 0xf940, + 0xd5 => 0xf941, 0xd6 => 0xf942, 0xd7 => 0xf943, 0xd8 => 0x6fc3, + 0xd9 => 0xf944, 0xda => 0xf945, 0xdb => 0x81bf, 0xdc => 0x8fb2, + 0xdd => 0x60f1, 0xde => 0xf946, 0xdf => 0xf947, 0xe0 => 0x8166, + 0xe1 => 0xf948, 0xe2 => 0xf949, 0xe3 => 0x5c3f, 0xe4 => 0xf94a, + 0xe5 => 0xf94b, 0xe6 => 0xf94c, 0xe7 => 0xf94d, 0xe8 => 0xf94e, + 0xe9 => 0xf94f, 0xea => 0xf950, 0xeb => 0xf951, 0xec => 0x5ae9, + 0xed => 0x8a25, 0xee => 0x677b, 0xef => 0x7d10, 0xf0 => 0xf952, + 0xf1 => 0xf953, 0xf2 => 0xf954, 0xf3 => 0xf955, 0xf4 => 0xf956, + 0xf5 => 0xf957, 0xf6 => 0x80fd, 0xf7 => 0xf958, 0xf8 => 0xf959, + 0xf9 => 0x5c3c, 0xfa => 0x6ce5, 0xfb => 0x533f, 0xfc => 0x6eba, + 0xfd => 0x591a, 0xfe => 0x8336, + }, + 0xd3 => { + 0xa1 => 0x4e39, 0xa2 => 0x4eb6, 0xa3 => 0x4f46, 0xa4 => 0x55ae, + 0xa5 => 0x5718, 0xa6 => 0x58c7, 0xa7 => 0x5f56, 0xa8 => 0x65b7, + 0xa9 => 0x65e6, 0xaa => 0x6a80, 0xab => 0x6bb5, 0xac => 0x6e4d, + 0xad => 0x77ed, 0xae => 0x7aef, 0xaf => 0x7c1e, 0xb0 => 0x7dde, + 0xb1 => 0x86cb, 0xb2 => 0x8892, 0xb3 => 0x9132, 0xb4 => 0x935b, + 0xb5 => 0x64bb, 0xb6 => 0x6fbe, 0xb7 => 0x737a, 0xb8 => 0x75b8, + 0xb9 => 0x9054, 0xba => 0x5556, 0xbb => 0x574d, 0xbc => 0x61ba, + 0xbd => 0x64d4, 0xbe => 0x66c7, 0xbf => 0x6de1, 0xc0 => 0x6e5b, + 0xc1 => 0x6f6d, 0xc2 => 0x6fb9, 0xc3 => 0x75f0, 0xc4 => 0x8043, + 0xc5 => 0x81bd, 0xc6 => 0x8541, 0xc7 => 0x8983, 0xc8 => 0x8ac7, + 0xc9 => 0x8b5a, 0xca => 0x931f, 0xcb => 0x6c93, 0xcc => 0x7553, + 0xcd => 0x7b54, 0xce => 0x8e0f, 0xcf => 0x905d, 0xd0 => 0x5510, + 0xd1 => 0x5802, 0xd2 => 0x5858, 0xd3 => 0x5e62, 0xd4 => 0x6207, + 0xd5 => 0x649e, 0xd6 => 0x68e0, 0xd7 => 0x7576, 0xd8 => 0x7cd6, + 0xd9 => 0x87b3, 0xda => 0x9ee8, 0xdb => 0x4ee3, 0xdc => 0x5788, + 0xdd => 0x576e, 0xde => 0x5927, 0xdf => 0x5c0d, 0xe0 => 0x5cb1, + 0xe1 => 0x5e36, 0xe2 => 0x5f85, 0xe3 => 0x6234, 0xe4 => 0x64e1, + 0xe5 => 0x73b3, 0xe6 => 0x81fa, 0xe7 => 0x888b, 0xe8 => 0x8cb8, + 0xe9 => 0x968a, 0xea => 0x9edb, 0xeb => 0x5b85, 0xec => 0x5fb7, + 0xed => 0x60b3, 0xee => 0x5012, 0xef => 0x5200, 0xf0 => 0x5230, + 0xf1 => 0x5716, 0xf2 => 0x5835, 0xf3 => 0x5857, 0xf4 => 0x5c0e, + 0xf5 => 0x5c60, 0xf6 => 0x5cf6, 0xf7 => 0x5d8b, 0xf8 => 0x5ea6, + 0xf9 => 0x5f92, 0xfa => 0x60bc, 0xfb => 0x6311, 0xfc => 0x6389, + 0xfd => 0x6417, 0xfe => 0x6843, + }, + 0xd4 => { + 0xa1 => 0x68f9, 0xa2 => 0x6ac2, 0xa3 => 0x6dd8, 0xa4 => 0x6e21, + 0xa5 => 0x6ed4, 0xa6 => 0x6fe4, 0xa7 => 0x71fe, 0xa8 => 0x76dc, + 0xa9 => 0x7779, 0xaa => 0x79b1, 0xab => 0x7a3b, 0xac => 0x8404, + 0xad => 0x89a9, 0xae => 0x8ced, 0xaf => 0x8df3, 0xb0 => 0x8e48, + 0xb1 => 0x9003, 0xb2 => 0x9014, 0xb3 => 0x9053, 0xb4 => 0x90fd, + 0xb5 => 0x934d, 0xb6 => 0x9676, 0xb7 => 0x97dc, 0xb8 => 0x6bd2, + 0xb9 => 0x7006, 0xba => 0x7258, 0xbb => 0x72a2, 0xbc => 0x7368, + 0xbd => 0x7763, 0xbe => 0x79bf, 0xbf => 0x7be4, 0xc0 => 0x7e9b, + 0xc1 => 0x8b80, 0xc2 => 0x58a9, 0xc3 => 0x60c7, 0xc4 => 0x6566, + 0xc5 => 0x65fd, 0xc6 => 0x66be, 0xc7 => 0x6c8c, 0xc8 => 0x711e, + 0xc9 => 0x71c9, 0xca => 0x8c5a, 0xcb => 0x9813, 0xcc => 0x4e6d, + 0xcd => 0x7a81, 0xce => 0x4edd, 0xcf => 0x51ac, 0xd0 => 0x51cd, + 0xd1 => 0x52d5, 0xd2 => 0x540c, 0xd3 => 0x61a7, 0xd4 => 0x6771, + 0xd5 => 0x6850, 0xd6 => 0x68df, 0xd7 => 0x6d1e, 0xd8 => 0x6f7c, + 0xd9 => 0x75bc, 0xda => 0x77b3, 0xdb => 0x7ae5, 0xdc => 0x80f4, + 0xdd => 0x8463, 0xde => 0x9285, 0xdf => 0x515c, 0xe0 => 0x6597, + 0xe1 => 0x675c, 0xe2 => 0x6793, 0xe3 => 0x75d8, 0xe4 => 0x7ac7, + 0xe5 => 0x8373, 0xe6 => 0xf95a, 0xe7 => 0x8c46, 0xe8 => 0x9017, + 0xe9 => 0x982d, 0xea => 0x5c6f, 0xeb => 0x81c0, 0xec => 0x829a, + 0xed => 0x9041, 0xee => 0x906f, 0xef => 0x920d, 0xf0 => 0x5f97, + 0xf1 => 0x5d9d, 0xf2 => 0x6a59, 0xf3 => 0x71c8, 0xf4 => 0x767b, + 0xf5 => 0x7b49, 0xf6 => 0x85e4, 0xf7 => 0x8b04, 0xf8 => 0x9127, + 0xf9 => 0x9a30, 0xfa => 0x5587, 0xfb => 0x61f6, 0xfc => 0xf95b, + 0xfd => 0x7669, 0xfe => 0x7f85, + }, + 0xd5 => { + 0xa1 => 0x863f, 0xa2 => 0x87ba, 0xa3 => 0x88f8, 0xa4 => 0x908f, + 0xa5 => 0xf95c, 0xa6 => 0x6d1b, 0xa7 => 0x70d9, 0xa8 => 0x73de, + 0xa9 => 0x7d61, 0xaa => 0x843d, 0xab => 0xf95d, 0xac => 0x916a, + 0xad => 0x99f1, 0xae => 0xf95e, 0xaf => 0x4e82, 0xb0 => 0x5375, + 0xb1 => 0x6b04, 0xb2 => 0x6b12, 0xb3 => 0x703e, 0xb4 => 0x721b, + 0xb5 => 0x862d, 0xb6 => 0x9e1e, 0xb7 => 0x524c, 0xb8 => 0x8fa3, + 0xb9 => 0x5d50, 0xba => 0x64e5, 0xbb => 0x652c, 0xbc => 0x6b16, + 0xbd => 0x6feb, 0xbe => 0x7c43, 0xbf => 0x7e9c, 0xc0 => 0x85cd, + 0xc1 => 0x8964, 0xc2 => 0x89bd, 0xc3 => 0x62c9, 0xc4 => 0x81d8, + 0xc5 => 0x881f, 0xc6 => 0x5eca, 0xc7 => 0x6717, 0xc8 => 0x6d6a, + 0xc9 => 0x72fc, 0xca => 0x7405, 0xcb => 0x746f, 0xcc => 0x8782, + 0xcd => 0x90de, 0xce => 0x4f86, 0xcf => 0x5d0d, 0xd0 => 0x5fa0, + 0xd1 => 0x840a, 0xd2 => 0x51b7, 0xd3 => 0x63a0, 0xd4 => 0x7565, + 0xd5 => 0x4eae, 0xd6 => 0x5006, 0xd7 => 0x5169, 0xd8 => 0x51c9, + 0xd9 => 0x6881, 0xda => 0x6a11, 0xdb => 0x7cae, 0xdc => 0x7cb1, + 0xdd => 0x7ce7, 0xde => 0x826f, 0xdf => 0x8ad2, 0xe0 => 0x8f1b, + 0xe1 => 0x91cf, 0xe2 => 0x4fb6, 0xe3 => 0x5137, 0xe4 => 0x52f5, + 0xe5 => 0x5442, 0xe6 => 0x5eec, 0xe7 => 0x616e, 0xe8 => 0x623e, + 0xe9 => 0x65c5, 0xea => 0x6ada, 0xeb => 0x6ffe, 0xec => 0x792a, + 0xed => 0x85dc, 0xee => 0x8823, 0xef => 0x95ad, 0xf0 => 0x9a62, + 0xf1 => 0x9a6a, 0xf2 => 0x9e97, 0xf3 => 0x9ece, 0xf4 => 0x529b, + 0xf5 => 0x66c6, 0xf6 => 0x6b77, 0xf7 => 0x701d, 0xf8 => 0x792b, + 0xf9 => 0x8f62, 0xfa => 0x9742, 0xfb => 0x6190, 0xfc => 0x6200, + 0xfd => 0x6523, 0xfe => 0x6f23, + }, + 0xd6 => { + 0xa1 => 0x7149, 0xa2 => 0x7489, 0xa3 => 0x7df4, 0xa4 => 0x806f, + 0xa5 => 0x84ee, 0xa6 => 0x8f26, 0xa7 => 0x9023, 0xa8 => 0x934a, + 0xa9 => 0x51bd, 0xaa => 0x5217, 0xab => 0x52a3, 0xac => 0x6d0c, + 0xad => 0x70c8, 0xae => 0x88c2, 0xaf => 0x5ec9, 0xb0 => 0x6582, + 0xb1 => 0x6bae, 0xb2 => 0x6fc2, 0xb3 => 0x7c3e, 0xb4 => 0x7375, + 0xb5 => 0x4ee4, 0xb6 => 0x4f36, 0xb7 => 0x56f9, 0xb8 => 0xf95f, + 0xb9 => 0x5cba, 0xba => 0x5dba, 0xbb => 0x601c, 0xbc => 0x73b2, + 0xbd => 0x7b2d, 0xbe => 0x7f9a, 0xbf => 0x7fce, 0xc0 => 0x8046, + 0xc1 => 0x901e, 0xc2 => 0x9234, 0xc3 => 0x96f6, 0xc4 => 0x9748, + 0xc5 => 0x9818, 0xc6 => 0x9f61, 0xc7 => 0x4f8b, 0xc8 => 0x6fa7, + 0xc9 => 0x79ae, 0xca => 0x91b4, 0xcb => 0x96b7, 0xcc => 0x52de, + 0xcd => 0xf960, 0xce => 0x6488, 0xcf => 0x64c4, 0xd0 => 0x6ad3, + 0xd1 => 0x6f5e, 0xd2 => 0x7018, 0xd3 => 0x7210, 0xd4 => 0x76e7, + 0xd5 => 0x8001, 0xd6 => 0x8606, 0xd7 => 0x865c, 0xd8 => 0x8def, + 0xd9 => 0x8f05, 0xda => 0x9732, 0xdb => 0x9b6f, 0xdc => 0x9dfa, + 0xdd => 0x9e75, 0xde => 0x788c, 0xdf => 0x797f, 0xe0 => 0x7da0, + 0xe1 => 0x83c9, 0xe2 => 0x9304, 0xe3 => 0x9e7f, 0xe4 => 0x9e93, + 0xe5 => 0x8ad6, 0xe6 => 0x58df, 0xe7 => 0x5f04, 0xe8 => 0x6727, + 0xe9 => 0x7027, 0xea => 0x74cf, 0xeb => 0x7c60, 0xec => 0x807e, + 0xed => 0x5121, 0xee => 0x7028, 0xef => 0x7262, 0xf0 => 0x78ca, + 0xf1 => 0x8cc2, 0xf2 => 0x8cda, 0xf3 => 0x8cf4, 0xf4 => 0x96f7, + 0xf5 => 0x4e86, 0xf6 => 0x50da, 0xf7 => 0x5bee, 0xf8 => 0x5ed6, + 0xf9 => 0x6599, 0xfa => 0x71ce, 0xfb => 0x7642, 0xfc => 0x77ad, + 0xfd => 0x804a, 0xfe => 0x84fc, + }, + 0xd7 => { + 0xa1 => 0x907c, 0xa2 => 0x9b27, 0xa3 => 0x9f8d, 0xa4 => 0x58d8, + 0xa5 => 0x5a41, 0xa6 => 0x5c62, 0xa7 => 0x6a13, 0xa8 => 0x6dda, + 0xa9 => 0x6f0f, 0xaa => 0x763b, 0xab => 0x7d2f, 0xac => 0x7e37, + 0xad => 0x851e, 0xae => 0x8938, 0xaf => 0x93e4, 0xb0 => 0x964b, + 0xb1 => 0x5289, 0xb2 => 0x65d2, 0xb3 => 0x67f3, 0xb4 => 0x69b4, + 0xb5 => 0x6d41, 0xb6 => 0x6e9c, 0xb7 => 0x700f, 0xb8 => 0x7409, + 0xb9 => 0x7460, 0xba => 0x7559, 0xbb => 0x7624, 0xbc => 0x786b, + 0xbd => 0x8b2c, 0xbe => 0x985e, 0xbf => 0x516d, 0xc0 => 0x622e, + 0xc1 => 0x9678, 0xc2 => 0x4f96, 0xc3 => 0x502b, 0xc4 => 0x5d19, + 0xc5 => 0x6dea, 0xc6 => 0x7db8, 0xc7 => 0x8f2a, 0xc8 => 0x5f8b, + 0xc9 => 0x6144, 0xca => 0x6817, 0xcb => 0xf961, 0xcc => 0x9686, + 0xcd => 0x52d2, 0xce => 0x808b, 0xcf => 0x51dc, 0xd0 => 0x51cc, + 0xd1 => 0x695e, 0xd2 => 0x7a1c, 0xd3 => 0x7dbe, 0xd4 => 0x83f1, + 0xd5 => 0x9675, 0xd6 => 0x4fda, 0xd7 => 0x5229, 0xd8 => 0x5398, + 0xd9 => 0x540f, 0xda => 0x550e, 0xdb => 0x5c65, 0xdc => 0x60a7, + 0xdd => 0x674e, 0xde => 0x68a8, 0xdf => 0x6d6c, 0xe0 => 0x7281, + 0xe1 => 0x72f8, 0xe2 => 0x7406, 0xe3 => 0x7483, 0xe4 => 0xf962, + 0xe5 => 0x75e2, 0xe6 => 0x7c6c, 0xe7 => 0x7f79, 0xe8 => 0x7fb8, + 0xe9 => 0x8389, 0xea => 0x88cf, 0xeb => 0x88e1, 0xec => 0x91cc, + 0xed => 0x91d0, 0xee => 0x96e2, 0xef => 0x9bc9, 0xf0 => 0x541d, + 0xf1 => 0x6f7e, 0xf2 => 0x71d0, 0xf3 => 0x7498, 0xf4 => 0x85fa, + 0xf5 => 0x8eaa, 0xf6 => 0x96a3, 0xf7 => 0x9c57, 0xf8 => 0x9e9f, + 0xf9 => 0x6797, 0xfa => 0x6dcb, 0xfb => 0x7433, 0xfc => 0x81e8, + 0xfd => 0x9716, 0xfe => 0x782c, + }, + 0xd8 => { + 0xa1 => 0x7acb, 0xa2 => 0x7b20, 0xa3 => 0x7c92, 0xa4 => 0x6469, + 0xa5 => 0x746a, 0xa6 => 0x75f2, 0xa7 => 0x78bc, 0xa8 => 0x78e8, + 0xa9 => 0x99ac, 0xaa => 0x9b54, 0xab => 0x9ebb, 0xac => 0x5bde, + 0xad => 0x5e55, 0xae => 0x6f20, 0xaf => 0x819c, 0xb0 => 0x83ab, + 0xb1 => 0x9088, 0xb2 => 0x4e07, 0xb3 => 0x534d, 0xb4 => 0x5a29, + 0xb5 => 0x5dd2, 0xb6 => 0x5f4e, 0xb7 => 0x6162, 0xb8 => 0x633d, + 0xb9 => 0x6669, 0xba => 0x66fc, 0xbb => 0x6eff, 0xbc => 0x6f2b, + 0xbd => 0x7063, 0xbe => 0x779e, 0xbf => 0x842c, 0xc0 => 0x8513, + 0xc1 => 0x883b, 0xc2 => 0x8f13, 0xc3 => 0x9945, 0xc4 => 0x9c3b, + 0xc5 => 0x551c, 0xc6 => 0x62b9, 0xc7 => 0x672b, 0xc8 => 0x6cab, + 0xc9 => 0x8309, 0xca => 0x896a, 0xcb => 0x977a, 0xcc => 0x4ea1, + 0xcd => 0x5984, 0xce => 0x5fd8, 0xcf => 0x5fd9, 0xd0 => 0x671b, + 0xd1 => 0x7db2, 0xd2 => 0x7f54, 0xd3 => 0x8292, 0xd4 => 0x832b, + 0xd5 => 0x83bd, 0xd6 => 0x8f1e, 0xd7 => 0x9099, 0xd8 => 0x57cb, + 0xd9 => 0x59b9, 0xda => 0x5a92, 0xdb => 0x5bd0, 0xdc => 0x6627, + 0xdd => 0x679a, 0xde => 0x6885, 0xdf => 0x6bcf, 0xe0 => 0x7164, + 0xe1 => 0x7f75, 0xe2 => 0x8cb7, 0xe3 => 0x8ce3, 0xe4 => 0x9081, + 0xe5 => 0x9b45, 0xe6 => 0x8108, 0xe7 => 0x8c8a, 0xe8 => 0x964c, + 0xe9 => 0x9a40, 0xea => 0x9ea5, 0xeb => 0x5b5f, 0xec => 0x6c13, + 0xed => 0x731b, 0xee => 0x76f2, 0xef => 0x76df, 0xf0 => 0x840c, + 0xf1 => 0x51aa, 0xf2 => 0x8993, 0xf3 => 0x514d, 0xf4 => 0x5195, + 0xf5 => 0x52c9, 0xf6 => 0x68c9, 0xf7 => 0x6c94, 0xf8 => 0x7704, + 0xf9 => 0x7720, 0xfa => 0x7dbf, 0xfb => 0x7dec, 0xfc => 0x9762, + 0xfd => 0x9eb5, 0xfe => 0x6ec5, + }, + 0xd9 => { + 0xa1 => 0x8511, 0xa2 => 0x51a5, 0xa3 => 0x540d, 0xa4 => 0x547d, + 0xa5 => 0x660e, 0xa6 => 0x669d, 0xa7 => 0x6927, 0xa8 => 0x6e9f, + 0xa9 => 0x76bf, 0xaa => 0x7791, 0xab => 0x8317, 0xac => 0x84c2, + 0xad => 0x879f, 0xae => 0x9169, 0xaf => 0x9298, 0xb0 => 0x9cf4, + 0xb1 => 0x8882, 0xb2 => 0x4fae, 0xb3 => 0x5192, 0xb4 => 0x52df, + 0xb5 => 0x59c6, 0xb6 => 0x5e3d, 0xb7 => 0x6155, 0xb8 => 0x6478, + 0xb9 => 0x6479, 0xba => 0x66ae, 0xbb => 0x67d0, 0xbc => 0x6a21, + 0xbd => 0x6bcd, 0xbe => 0x6bdb, 0xbf => 0x725f, 0xc0 => 0x7261, + 0xc1 => 0x7441, 0xc2 => 0x7738, 0xc3 => 0x77db, 0xc4 => 0x8017, + 0xc5 => 0x82bc, 0xc6 => 0x8305, 0xc7 => 0x8b00, 0xc8 => 0x8b28, + 0xc9 => 0x8c8c, 0xca => 0x6728, 0xcb => 0x6c90, 0xcc => 0x7267, + 0xcd => 0x76ee, 0xce => 0x7766, 0xcf => 0x7a46, 0xd0 => 0x9da9, + 0xd1 => 0x6b7f, 0xd2 => 0x6c92, 0xd3 => 0x5922, 0xd4 => 0x6726, + 0xd5 => 0x8499, 0xd6 => 0x536f, 0xd7 => 0x5893, 0xd8 => 0x5999, + 0xd9 => 0x5edf, 0xda => 0x63cf, 0xdb => 0x6634, 0xdc => 0x6773, + 0xdd => 0x6e3a, 0xde => 0x732b, 0xdf => 0x7ad7, 0xe0 => 0x82d7, + 0xe1 => 0x9328, 0xe2 => 0x52d9, 0xe3 => 0x5deb, 0xe4 => 0x61ae, + 0xe5 => 0x61cb, 0xe6 => 0x620a, 0xe7 => 0x62c7, 0xe8 => 0x64ab, + 0xe9 => 0x65e0, 0xea => 0x6959, 0xeb => 0x6b66, 0xec => 0x6bcb, + 0xed => 0x7121, 0xee => 0x73f7, 0xef => 0x755d, 0xf0 => 0x7e46, + 0xf1 => 0x821e, 0xf2 => 0x8302, 0xf3 => 0x856a, 0xf4 => 0x8aa3, + 0xf5 => 0x8cbf, 0xf6 => 0x9727, 0xf7 => 0x9d61, 0xf8 => 0x58a8, + 0xf9 => 0x9ed8, 0xfa => 0x5011, 0xfb => 0x520e, 0xfc => 0x543b, + 0xfd => 0x554f, 0xfe => 0x6587, + }, + 0xda => { + 0xa1 => 0x6c76, 0xa2 => 0x7d0a, 0xa3 => 0x7d0b, 0xa4 => 0x805e, + 0xa5 => 0x868a, 0xa6 => 0x9580, 0xa7 => 0x96ef, 0xa8 => 0x52ff, + 0xa9 => 0x6c95, 0xaa => 0x7269, 0xab => 0x5473, 0xac => 0x5a9a, + 0xad => 0x5c3e, 0xae => 0x5d4b, 0xaf => 0x5f4c, 0xb0 => 0x5fae, + 0xb1 => 0x672a, 0xb2 => 0x68b6, 0xb3 => 0x6963, 0xb4 => 0x6e3c, + 0xb5 => 0x6e44, 0xb6 => 0x7709, 0xb7 => 0x7c73, 0xb8 => 0x7f8e, + 0xb9 => 0x8587, 0xba => 0x8b0e, 0xbb => 0x8ff7, 0xbc => 0x9761, + 0xbd => 0x9ef4, 0xbe => 0x5cb7, 0xbf => 0x60b6, 0xc0 => 0x610d, + 0xc1 => 0x61ab, 0xc2 => 0x654f, 0xc3 => 0x65fb, 0xc4 => 0x65fc, + 0xc5 => 0x6c11, 0xc6 => 0x6cef, 0xc7 => 0x739f, 0xc8 => 0x73c9, + 0xc9 => 0x7de1, 0xca => 0x9594, 0xcb => 0x5bc6, 0xcc => 0x871c, + 0xcd => 0x8b10, 0xce => 0x525d, 0xcf => 0x535a, 0xd0 => 0x62cd, + 0xd1 => 0x640f, 0xd2 => 0x64b2, 0xd3 => 0x6734, 0xd4 => 0x6a38, + 0xd5 => 0x6cca, 0xd6 => 0x73c0, 0xd7 => 0x749e, 0xd8 => 0x7b94, + 0xd9 => 0x7c95, 0xda => 0x7e1b, 0xdb => 0x818a, 0xdc => 0x8236, + 0xdd => 0x8584, 0xde => 0x8feb, 0xdf => 0x96f9, 0xe0 => 0x99c1, + 0xe1 => 0x4f34, 0xe2 => 0x534a, 0xe3 => 0x53cd, 0xe4 => 0x53db, + 0xe5 => 0x62cc, 0xe6 => 0x642c, 0xe7 => 0x6500, 0xe8 => 0x6591, + 0xe9 => 0x69c3, 0xea => 0x6cee, 0xeb => 0x6f58, 0xec => 0x73ed, + 0xed => 0x7554, 0xee => 0x7622, 0xef => 0x76e4, 0xf0 => 0x76fc, + 0xf1 => 0x78d0, 0xf2 => 0x78fb, 0xf3 => 0x792c, 0xf4 => 0x7d46, + 0xf5 => 0x822c, 0xf6 => 0x87e0, 0xf7 => 0x8fd4, 0xf8 => 0x9812, + 0xf9 => 0x98ef, 0xfa => 0x52c3, 0xfb => 0x62d4, 0xfc => 0x64a5, + 0xfd => 0x6e24, 0xfe => 0x6f51, + }, + 0xdb => { + 0xa1 => 0x767c, 0xa2 => 0x8dcb, 0xa3 => 0x91b1, 0xa4 => 0x9262, + 0xa5 => 0x9aee, 0xa6 => 0x9b43, 0xa7 => 0x5023, 0xa8 => 0x508d, + 0xa9 => 0x574a, 0xaa => 0x59a8, 0xab => 0x5c28, 0xac => 0x5e47, + 0xad => 0x5f77, 0xae => 0x623f, 0xaf => 0x653e, 0xb0 => 0x65b9, + 0xb1 => 0x65c1, 0xb2 => 0x6609, 0xb3 => 0x678b, 0xb4 => 0x699c, + 0xb5 => 0x6ec2, 0xb6 => 0x78c5, 0xb7 => 0x7d21, 0xb8 => 0x80aa, + 0xb9 => 0x8180, 0xba => 0x822b, 0xbb => 0x82b3, 0xbc => 0x84a1, + 0xbd => 0x868c, 0xbe => 0x8a2a, 0xbf => 0x8b17, 0xc0 => 0x90a6, + 0xc1 => 0x9632, 0xc2 => 0x9f90, 0xc3 => 0x500d, 0xc4 => 0x4ff3, + 0xc5 => 0xf963, 0xc6 => 0x57f9, 0xc7 => 0x5f98, 0xc8 => 0x62dc, + 0xc9 => 0x6392, 0xca => 0x676f, 0xcb => 0x6e43, 0xcc => 0x7119, + 0xcd => 0x76c3, 0xce => 0x80cc, 0xcf => 0x80da, 0xd0 => 0x88f4, + 0xd1 => 0x88f5, 0xd2 => 0x8919, 0xd3 => 0x8ce0, 0xd4 => 0x8f29, + 0xd5 => 0x914d, 0xd6 => 0x966a, 0xd7 => 0x4f2f, 0xd8 => 0x4f70, + 0xd9 => 0x5e1b, 0xda => 0x67cf, 0xdb => 0x6822, 0xdc => 0x767d, + 0xdd => 0x767e, 0xde => 0x9b44, 0xdf => 0x5e61, 0xe0 => 0x6a0a, + 0xe1 => 0x7169, 0xe2 => 0x71d4, 0xe3 => 0x756a, 0xe4 => 0xf964, + 0xe5 => 0x7e41, 0xe6 => 0x8543, 0xe7 => 0x85e9, 0xe8 => 0x98dc, + 0xe9 => 0x4f10, 0xea => 0x7b4f, 0xeb => 0x7f70, 0xec => 0x95a5, + 0xed => 0x51e1, 0xee => 0x5e06, 0xef => 0x68b5, 0xf0 => 0x6c3e, + 0xf1 => 0x6c4e, 0xf2 => 0x6cdb, 0xf3 => 0x72af, 0xf4 => 0x7bc4, + 0xf5 => 0x8303, 0xf6 => 0x6cd5, 0xf7 => 0x743a, 0xf8 => 0x50fb, + 0xf9 => 0x5288, 0xfa => 0x58c1, 0xfb => 0x64d8, 0xfc => 0x6a97, + 0xfd => 0x74a7, 0xfe => 0x7656, + }, + 0xdc => { + 0xa1 => 0x78a7, 0xa2 => 0x8617, 0xa3 => 0x95e2, 0xa4 => 0x9739, + 0xa5 => 0xf965, 0xa6 => 0x535e, 0xa7 => 0x5f01, 0xa8 => 0x8b8a, + 0xa9 => 0x8fa8, 0xaa => 0x8faf, 0xab => 0x908a, 0xac => 0x5225, + 0xad => 0x77a5, 0xae => 0x9c49, 0xaf => 0x9f08, 0xb0 => 0x4e19, + 0xb1 => 0x5002, 0xb2 => 0x5175, 0xb3 => 0x5c5b, 0xb4 => 0x5e77, + 0xb5 => 0x661e, 0xb6 => 0x663a, 0xb7 => 0x67c4, 0xb8 => 0x68c5, + 0xb9 => 0x70b3, 0xba => 0x7501, 0xbb => 0x75c5, 0xbc => 0x79c9, + 0xbd => 0x7add, 0xbe => 0x8f27, 0xbf => 0x9920, 0xc0 => 0x9a08, + 0xc1 => 0x4fdd, 0xc2 => 0x5821, 0xc3 => 0x5831, 0xc4 => 0x5bf6, + 0xc5 => 0x666e, 0xc6 => 0x6b65, 0xc7 => 0x6d11, 0xc8 => 0x6e7a, + 0xc9 => 0x6f7d, 0xca => 0x73e4, 0xcb => 0x752b, 0xcc => 0x83e9, + 0xcd => 0x88dc, 0xce => 0x8913, 0xcf => 0x8b5c, 0xd0 => 0x8f14, + 0xd1 => 0x4f0f, 0xd2 => 0x50d5, 0xd3 => 0x5310, 0xd4 => 0x535c, + 0xd5 => 0x5b93, 0xd6 => 0x5fa9, 0xd7 => 0x670d, 0xd8 => 0x798f, + 0xd9 => 0x8179, 0xda => 0x832f, 0xdb => 0x8514, 0xdc => 0x8907, + 0xdd => 0x8986, 0xde => 0x8f39, 0xdf => 0x8f3b, 0xe0 => 0x99a5, + 0xe1 => 0x9c12, 0xe2 => 0x672c, 0xe3 => 0x4e76, 0xe4 => 0x4ff8, + 0xe5 => 0x5949, 0xe6 => 0x5c01, 0xe7 => 0x5cef, 0xe8 => 0x5cf0, + 0xe9 => 0x6367, 0xea => 0x68d2, 0xeb => 0x70fd, 0xec => 0x71a2, + 0xed => 0x742b, 0xee => 0x7e2b, 0xef => 0x84ec, 0xf0 => 0x8702, + 0xf1 => 0x9022, 0xf2 => 0x92d2, 0xf3 => 0x9cf3, 0xf4 => 0x4e0d, + 0xf5 => 0x4ed8, 0xf6 => 0x4fef, 0xf7 => 0x5085, 0xf8 => 0x5256, + 0xf9 => 0x526f, 0xfa => 0x5426, 0xfb => 0x5490, 0xfc => 0x57e0, + 0xfd => 0x592b, 0xfe => 0x5a66, + }, + 0xdd => { + 0xa1 => 0x5b5a, 0xa2 => 0x5b75, 0xa3 => 0x5bcc, 0xa4 => 0x5e9c, + 0xa5 => 0xf966, 0xa6 => 0x6276, 0xa7 => 0x6577, 0xa8 => 0x65a7, + 0xa9 => 0x6d6e, 0xaa => 0x6ea5, 0xab => 0x7236, 0xac => 0x7b26, + 0xad => 0x7c3f, 0xae => 0x7f36, 0xaf => 0x8150, 0xb0 => 0x8151, + 0xb1 => 0x819a, 0xb2 => 0x8240, 0xb3 => 0x8299, 0xb4 => 0x83a9, + 0xb5 => 0x8a03, 0xb6 => 0x8ca0, 0xb7 => 0x8ce6, 0xb8 => 0x8cfb, + 0xb9 => 0x8d74, 0xba => 0x8dba, 0xbb => 0x90e8, 0xbc => 0x91dc, + 0xbd => 0x961c, 0xbe => 0x9644, 0xbf => 0x99d9, 0xc0 => 0x9ce7, + 0xc1 => 0x5317, 0xc2 => 0x5206, 0xc3 => 0x5429, 0xc4 => 0x5674, + 0xc5 => 0x58b3, 0xc6 => 0x5954, 0xc7 => 0x596e, 0xc8 => 0x5fff, + 0xc9 => 0x61a4, 0xca => 0x626e, 0xcb => 0x6610, 0xcc => 0x6c7e, + 0xcd => 0x711a, 0xce => 0x76c6, 0xcf => 0x7c89, 0xd0 => 0x7cde, + 0xd1 => 0x7d1b, 0xd2 => 0x82ac, 0xd3 => 0x8cc1, 0xd4 => 0x96f0, + 0xd5 => 0xf967, 0xd6 => 0x4f5b, 0xd7 => 0x5f17, 0xd8 => 0x5f7f, + 0xd9 => 0x62c2, 0xda => 0x5d29, 0xdb => 0x670b, 0xdc => 0x68da, + 0xdd => 0x787c, 0xde => 0x7e43, 0xdf => 0x9d6c, 0xe0 => 0x4e15, + 0xe1 => 0x5099, 0xe2 => 0x5315, 0xe3 => 0x532a, 0xe4 => 0x5351, + 0xe5 => 0x5983, 0xe6 => 0x5a62, 0xe7 => 0x5e87, 0xe8 => 0x60b2, + 0xe9 => 0x618a, 0xea => 0x6249, 0xeb => 0x6279, 0xec => 0x6590, + 0xed => 0x6787, 0xee => 0x69a7, 0xef => 0x6bd4, 0xf0 => 0x6bd6, + 0xf1 => 0x6bd7, 0xf2 => 0x6bd8, 0xf3 => 0x6cb8, 0xf4 => 0xf968, + 0xf5 => 0x7435, 0xf6 => 0x75fa, 0xf7 => 0x7812, 0xf8 => 0x7891, + 0xf9 => 0x79d5, 0xfa => 0x79d8, 0xfb => 0x7c83, 0xfc => 0x7dcb, + 0xfd => 0x7fe1, 0xfe => 0x80a5, + }, + 0xde => { + 0xa1 => 0x813e, 0xa2 => 0x81c2, 0xa3 => 0x83f2, 0xa4 => 0x871a, + 0xa5 => 0x88e8, 0xa6 => 0x8ab9, 0xa7 => 0x8b6c, 0xa8 => 0x8cbb, + 0xa9 => 0x9119, 0xaa => 0x975e, 0xab => 0x98db, 0xac => 0x9f3b, + 0xad => 0x56ac, 0xae => 0x5b2a, 0xaf => 0x5f6c, 0xb0 => 0x658c, + 0xb1 => 0x6ab3, 0xb2 => 0x6baf, 0xb3 => 0x6d5c, 0xb4 => 0x6ff1, + 0xb5 => 0x7015, 0xb6 => 0x725d, 0xb7 => 0x73ad, 0xb8 => 0x8ca7, + 0xb9 => 0x8cd3, 0xba => 0x983b, 0xbb => 0x6191, 0xbc => 0x6c37, + 0xbd => 0x8058, 0xbe => 0x9a01, 0xbf => 0x4e4d, 0xc0 => 0x4e8b, + 0xc1 => 0x4e9b, 0xc2 => 0x4ed5, 0xc3 => 0x4f3a, 0xc4 => 0x4f3c, + 0xc5 => 0x4f7f, 0xc6 => 0x4fdf, 0xc7 => 0x50ff, 0xc8 => 0x53f2, + 0xc9 => 0x53f8, 0xca => 0x5506, 0xcb => 0x55e3, 0xcc => 0x56db, + 0xcd => 0x58eb, 0xce => 0x5962, 0xcf => 0x5a11, 0xd0 => 0x5beb, + 0xd1 => 0x5bfa, 0xd2 => 0x5c04, 0xd3 => 0x5df3, 0xd4 => 0x5e2b, + 0xd5 => 0x5f99, 0xd6 => 0x601d, 0xd7 => 0x6368, 0xd8 => 0x659c, + 0xd9 => 0x65af, 0xda => 0x67f6, 0xdb => 0x67fb, 0xdc => 0x68ad, + 0xdd => 0x6b7b, 0xde => 0x6c99, 0xdf => 0x6cd7, 0xe0 => 0x6e23, + 0xe1 => 0x7009, 0xe2 => 0x7345, 0xe3 => 0x7802, 0xe4 => 0x793e, + 0xe5 => 0x7940, 0xe6 => 0x7960, 0xe7 => 0x79c1, 0xe8 => 0x7be9, + 0xe9 => 0x7d17, 0xea => 0x7d72, 0xeb => 0x8086, 0xec => 0x820d, + 0xed => 0x838e, 0xee => 0x84d1, 0xef => 0x86c7, 0xf0 => 0x88df, + 0xf1 => 0x8a50, 0xf2 => 0x8a5e, 0xf3 => 0x8b1d, 0xf4 => 0x8cdc, + 0xf5 => 0x8d66, 0xf6 => 0x8fad, 0xf7 => 0x90aa, 0xf8 => 0x98fc, + 0xf9 => 0x99df, 0xfa => 0x9e9d, 0xfb => 0x524a, 0xfc => 0xf969, + 0xfd => 0x6714, 0xfe => 0xf96a, + }, + 0xdf => { + 0xa1 => 0x5098, 0xa2 => 0x522a, 0xa3 => 0x5c71, 0xa4 => 0x6563, + 0xa5 => 0x6c55, 0xa6 => 0x73ca, 0xa7 => 0x7523, 0xa8 => 0x759d, + 0xa9 => 0x7b97, 0xaa => 0x849c, 0xab => 0x9178, 0xac => 0x9730, + 0xad => 0x4e77, 0xae => 0x6492, 0xaf => 0x6bba, 0xb0 => 0x715e, + 0xb1 => 0x85a9, 0xb2 => 0x4e09, 0xb3 => 0xf96b, 0xb4 => 0x6749, + 0xb5 => 0x68ee, 0xb6 => 0x6e17, 0xb7 => 0x829f, 0xb8 => 0x8518, + 0xb9 => 0x886b, 0xba => 0x63f7, 0xbb => 0x6f81, 0xbc => 0x9212, + 0xbd => 0x98af, 0xbe => 0x4e0a, 0xbf => 0x50b7, 0xc0 => 0x50cf, + 0xc1 => 0x511f, 0xc2 => 0x5546, 0xc3 => 0x55aa, 0xc4 => 0x5617, + 0xc5 => 0x5b40, 0xc6 => 0x5c19, 0xc7 => 0x5ce0, 0xc8 => 0x5e38, + 0xc9 => 0x5e8a, 0xca => 0x5ea0, 0xcb => 0x5ec2, 0xcc => 0x60f3, + 0xcd => 0x6851, 0xce => 0x6a61, 0xcf => 0x6e58, 0xd0 => 0x723d, + 0xd1 => 0x7240, 0xd2 => 0x72c0, 0xd3 => 0x76f8, 0xd4 => 0x7965, + 0xd5 => 0x7bb1, 0xd6 => 0x7fd4, 0xd7 => 0x88f3, 0xd8 => 0x89f4, + 0xd9 => 0x8a73, 0xda => 0x8c61, 0xdb => 0x8cde, 0xdc => 0x971c, + 0xdd => 0x585e, 0xde => 0x74bd, 0xdf => 0x8cfd, 0xe0 => 0x55c7, + 0xe1 => 0xf96c, 0xe2 => 0x7a61, 0xe3 => 0x7d22, 0xe4 => 0x8272, + 0xe5 => 0x7272, 0xe6 => 0x751f, 0xe7 => 0x7525, 0xe8 => 0xf96d, + 0xe9 => 0x7b19, 0xea => 0x5885, 0xeb => 0x58fb, 0xec => 0x5dbc, + 0xed => 0x5e8f, 0xee => 0x5eb6, 0xef => 0x5f90, 0xf0 => 0x6055, + 0xf1 => 0x6292, 0xf2 => 0x637f, 0xf3 => 0x654d, 0xf4 => 0x6691, + 0xf5 => 0x66d9, 0xf6 => 0x66f8, 0xf7 => 0x6816, 0xf8 => 0x68f2, + 0xf9 => 0x7280, 0xfa => 0x745e, 0xfb => 0x7b6e, 0xfc => 0x7d6e, + 0xfd => 0x7dd6, 0xfe => 0x7f72, + }, + 0xe0 => { + 0xa1 => 0x80e5, 0xa2 => 0x8212, 0xa3 => 0x85af, 0xa4 => 0x897f, + 0xa5 => 0x8a93, 0xa6 => 0x901d, 0xa7 => 0x92e4, 0xa8 => 0x9ecd, + 0xa9 => 0x9f20, 0xaa => 0x5915, 0xab => 0x596d, 0xac => 0x5e2d, + 0xad => 0x60dc, 0xae => 0x6614, 0xaf => 0x6673, 0xb0 => 0x6790, + 0xb1 => 0x6c50, 0xb2 => 0x6dc5, 0xb3 => 0x6f5f, 0xb4 => 0x77f3, + 0xb5 => 0x78a9, 0xb6 => 0x84c6, 0xb7 => 0x91cb, 0xb8 => 0x932b, + 0xb9 => 0x4ed9, 0xba => 0x50ca, 0xbb => 0x5148, 0xbc => 0x5584, + 0xbd => 0x5b0b, 0xbe => 0x5ba3, 0xbf => 0x6247, 0xc0 => 0x657e, + 0xc1 => 0x65cb, 0xc2 => 0x6e32, 0xc3 => 0x717d, 0xc4 => 0x7401, + 0xc5 => 0x7444, 0xc6 => 0x7487, 0xc7 => 0x74bf, 0xc8 => 0x766c, + 0xc9 => 0x79aa, 0xca => 0x7dda, 0xcb => 0x7e55, 0xcc => 0x7fa8, + 0xcd => 0x817a, 0xce => 0x81b3, 0xcf => 0x8239, 0xd0 => 0x861a, + 0xd1 => 0x87ec, 0xd2 => 0x8a75, 0xd3 => 0x8de3, 0xd4 => 0x9078, + 0xd5 => 0x9291, 0xd6 => 0x9425, 0xd7 => 0x994d, 0xd8 => 0x9bae, + 0xd9 => 0x5368, 0xda => 0x5c51, 0xdb => 0x6954, 0xdc => 0x6cc4, + 0xdd => 0x6d29, 0xde => 0x6e2b, 0xdf => 0x820c, 0xe0 => 0x859b, + 0xe1 => 0x893b, 0xe2 => 0x8a2d, 0xe3 => 0x8aaa, 0xe4 => 0x96ea, + 0xe5 => 0x9f67, 0xe6 => 0x5261, 0xe7 => 0x66b9, 0xe8 => 0x6bb2, + 0xe9 => 0x7e96, 0xea => 0x87fe, 0xeb => 0x8d0d, 0xec => 0x9583, + 0xed => 0x965d, 0xee => 0x651d, 0xef => 0x6d89, 0xf0 => 0x71ee, + 0xf1 => 0xf96e, 0xf2 => 0x57ce, 0xf3 => 0x59d3, 0xf4 => 0x5bac, + 0xf5 => 0x6027, 0xf6 => 0x60fa, 0xf7 => 0x6210, 0xf8 => 0x661f, + 0xf9 => 0x665f, 0xfa => 0x7329, 0xfb => 0x73f9, 0xfc => 0x76db, + 0xfd => 0x7701, 0xfe => 0x7b6c, + }, + 0xe1 => { + 0xa1 => 0x8056, 0xa2 => 0x8072, 0xa3 => 0x8165, 0xa4 => 0x8aa0, + 0xa5 => 0x9192, 0xa6 => 0x4e16, 0xa7 => 0x52e2, 0xa8 => 0x6b72, + 0xa9 => 0x6d17, 0xaa => 0x7a05, 0xab => 0x7b39, 0xac => 0x7d30, + 0xad => 0xf96f, 0xae => 0x8cb0, 0xaf => 0x53ec, 0xb0 => 0x562f, + 0xb1 => 0x5851, 0xb2 => 0x5bb5, 0xb3 => 0x5c0f, 0xb4 => 0x5c11, + 0xb5 => 0x5de2, 0xb6 => 0x6240, 0xb7 => 0x6383, 0xb8 => 0x6414, + 0xb9 => 0x662d, 0xba => 0x68b3, 0xbb => 0x6cbc, 0xbc => 0x6d88, + 0xbd => 0x6eaf, 0xbe => 0x701f, 0xbf => 0x70a4, 0xc0 => 0x71d2, + 0xc1 => 0x7526, 0xc2 => 0x758f, 0xc3 => 0x758e, 0xc4 => 0x7619, + 0xc5 => 0x7b11, 0xc6 => 0x7be0, 0xc7 => 0x7c2b, 0xc8 => 0x7d20, + 0xc9 => 0x7d39, 0xca => 0x852c, 0xcb => 0x856d, 0xcc => 0x8607, + 0xcd => 0x8a34, 0xce => 0x900d, 0xcf => 0x9061, 0xd0 => 0x90b5, + 0xd1 => 0x92b7, 0xd2 => 0x97f6, 0xd3 => 0x9a37, 0xd4 => 0x4fd7, + 0xd5 => 0x5c6c, 0xd6 => 0x675f, 0xd7 => 0x6d91, 0xd8 => 0x7c9f, + 0xd9 => 0x7e8c, 0xda => 0x8b16, 0xdb => 0x8d16, 0xdc => 0x901f, + 0xdd => 0x5b6b, 0xde => 0x5dfd, 0xdf => 0x640d, 0xe0 => 0x84c0, + 0xe1 => 0x905c, 0xe2 => 0x98e1, 0xe3 => 0x7387, 0xe4 => 0x5b8b, + 0xe5 => 0x609a, 0xe6 => 0x677e, 0xe7 => 0x6dde, 0xe8 => 0x8a1f, + 0xe9 => 0x8aa6, 0xea => 0x9001, 0xeb => 0x980c, 0xec => 0x5237, + 0xed => 0xf970, 0xee => 0x7051, 0xef => 0x788e, 0xf0 => 0x9396, + 0xf1 => 0x8870, 0xf2 => 0x91d7, 0xf3 => 0x4fee, 0xf4 => 0x53d7, + 0xf5 => 0x55fd, 0xf6 => 0x56da, 0xf7 => 0x5782, 0xf8 => 0x58fd, + 0xf9 => 0x5ac2, 0xfa => 0x5b88, 0xfb => 0x5cab, 0xfc => 0x5cc0, + 0xfd => 0x5e25, 0xfe => 0x6101, + }, + 0xe2 => { + 0xa1 => 0x620d, 0xa2 => 0x624b, 0xa3 => 0x6388, 0xa4 => 0x641c, + 0xa5 => 0x6536, 0xa6 => 0x6578, 0xa7 => 0x6a39, 0xa8 => 0x6b8a, + 0xa9 => 0x6c34, 0xaa => 0x6d19, 0xab => 0x6f31, 0xac => 0x71e7, + 0xad => 0x72e9, 0xae => 0x7378, 0xaf => 0x7407, 0xb0 => 0x74b2, + 0xb1 => 0x7626, 0xb2 => 0x7761, 0xb3 => 0x79c0, 0xb4 => 0x7a57, + 0xb5 => 0x7aea, 0xb6 => 0x7cb9, 0xb7 => 0x7d8f, 0xb8 => 0x7dac, + 0xb9 => 0x7e61, 0xba => 0x7f9e, 0xbb => 0x8129, 0xbc => 0x8331, + 0xbd => 0x8490, 0xbe => 0x84da, 0xbf => 0x85ea, 0xc0 => 0x8896, + 0xc1 => 0x8ab0, 0xc2 => 0x8b90, 0xc3 => 0x8f38, 0xc4 => 0x9042, + 0xc5 => 0x9083, 0xc6 => 0x916c, 0xc7 => 0x9296, 0xc8 => 0x92b9, + 0xc9 => 0x968b, 0xca => 0x96a7, 0xcb => 0x96a8, 0xcc => 0x96d6, + 0xcd => 0x9700, 0xce => 0x9808, 0xcf => 0x9996, 0xd0 => 0x9ad3, + 0xd1 => 0x9b1a, 0xd2 => 0x53d4, 0xd3 => 0x587e, 0xd4 => 0x5919, + 0xd5 => 0x5b70, 0xd6 => 0x5bbf, 0xd7 => 0x6dd1, 0xd8 => 0x6f5a, + 0xd9 => 0x719f, 0xda => 0x7421, 0xdb => 0x74b9, 0xdc => 0x8085, + 0xdd => 0x83fd, 0xde => 0x5de1, 0xdf => 0x5f87, 0xe0 => 0x5faa, + 0xe1 => 0x6042, 0xe2 => 0x65ec, 0xe3 => 0x6812, 0xe4 => 0x696f, + 0xe5 => 0x6a53, 0xe6 => 0x6b89, 0xe7 => 0x6d35, 0xe8 => 0x6df3, + 0xe9 => 0x73e3, 0xea => 0x76fe, 0xeb => 0x77ac, 0xec => 0x7b4d, + 0xed => 0x7d14, 0xee => 0x8123, 0xef => 0x821c, 0xf0 => 0x8340, + 0xf1 => 0x84f4, 0xf2 => 0x8563, 0xf3 => 0x8a62, 0xf4 => 0x8ac4, + 0xf5 => 0x9187, 0xf6 => 0x931e, 0xf7 => 0x9806, 0xf8 => 0x99b4, + 0xf9 => 0x620c, 0xfa => 0x8853, 0xfb => 0x8ff0, 0xfc => 0x9265, + 0xfd => 0x5d07, 0xfe => 0x5d27, + }, + 0xe3 => { + 0xa1 => 0x5d69, 0xa2 => 0x745f, 0xa3 => 0x819d, 0xa4 => 0x8768, + 0xa5 => 0x6fd5, 0xa6 => 0x62fe, 0xa7 => 0x7fd2, 0xa8 => 0x8936, + 0xa9 => 0x8972, 0xaa => 0x4e1e, 0xab => 0x4e58, 0xac => 0x50e7, + 0xad => 0x52dd, 0xae => 0x5347, 0xaf => 0x627f, 0xb0 => 0x6607, + 0xb1 => 0x7e69, 0xb2 => 0x8805, 0xb3 => 0x965e, 0xb4 => 0x4f8d, + 0xb5 => 0x5319, 0xb6 => 0x5636, 0xb7 => 0x59cb, 0xb8 => 0x5aa4, + 0xb9 => 0x5c38, 0xba => 0x5c4e, 0xbb => 0x5c4d, 0xbc => 0x5e02, + 0xbd => 0x5f11, 0xbe => 0x6043, 0xbf => 0x65bd, 0xc0 => 0x662f, + 0xc1 => 0x6642, 0xc2 => 0x67be, 0xc3 => 0x67f4, 0xc4 => 0x731c, + 0xc5 => 0x77e2, 0xc6 => 0x793a, 0xc7 => 0x7fc5, 0xc8 => 0x8494, + 0xc9 => 0x84cd, 0xca => 0x8996, 0xcb => 0x8a66, 0xcc => 0x8a69, + 0xcd => 0x8ae1, 0xce => 0x8c55, 0xcf => 0x8c7a, 0xd0 => 0x57f4, + 0xd1 => 0x5bd4, 0xd2 => 0x5f0f, 0xd3 => 0x606f, 0xd4 => 0x62ed, + 0xd5 => 0x690d, 0xd6 => 0x6b96, 0xd7 => 0x6e5c, 0xd8 => 0x7184, + 0xd9 => 0x7bd2, 0xda => 0x8755, 0xdb => 0x8b58, 0xdc => 0x8efe, + 0xdd => 0x98df, 0xde => 0x98fe, 0xdf => 0x4f38, 0xe0 => 0x4f81, + 0xe1 => 0x4fe1, 0xe2 => 0x547b, 0xe3 => 0x5a20, 0xe4 => 0x5bb8, + 0xe5 => 0x613c, 0xe6 => 0x65b0, 0xe7 => 0x6668, 0xe8 => 0x71fc, + 0xe9 => 0x7533, 0xea => 0x795e, 0xeb => 0x7d33, 0xec => 0x814e, + 0xed => 0x81e3, 0xee => 0x8398, 0xef => 0x85aa, 0xf0 => 0x85ce, + 0xf1 => 0x8703, 0xf2 => 0x8a0a, 0xf3 => 0x8eab, 0xf4 => 0x8f9b, + 0xf5 => 0xf971, 0xf6 => 0x8fc5, 0xf7 => 0x5931, 0xf8 => 0x5ba4, + 0xf9 => 0x5be6, 0xfa => 0x6089, 0xfb => 0x5be9, 0xfc => 0x5c0b, + 0xfd => 0x5fc3, 0xfe => 0x6c81, + }, + 0xe4 => { + 0xa1 => 0xf972, 0xa2 => 0x6df1, 0xa3 => 0x700b, 0xa4 => 0x751a, + 0xa5 => 0x82af, 0xa6 => 0x8af6, 0xa7 => 0x4ec0, 0xa8 => 0x5341, + 0xa9 => 0xf973, 0xaa => 0x96d9, 0xab => 0x6c0f, 0xac => 0x4e9e, + 0xad => 0x4fc4, 0xae => 0x5152, 0xaf => 0x555e, 0xb0 => 0x5a25, + 0xb1 => 0x5ce8, 0xb2 => 0x6211, 0xb3 => 0x7259, 0xb4 => 0x82bd, + 0xb5 => 0x83aa, 0xb6 => 0x86fe, 0xb7 => 0x8859, 0xb8 => 0x8a1d, + 0xb9 => 0x963f, 0xba => 0x96c5, 0xbb => 0x9913, 0xbc => 0x9d09, + 0xbd => 0x9d5d, 0xbe => 0x580a, 0xbf => 0x5cb3, 0xc0 => 0x5dbd, + 0xc1 => 0x5e44, 0xc2 => 0x60e1, 0xc3 => 0x6115, 0xc4 => 0x63e1, + 0xc5 => 0x6a02, 0xc6 => 0x6e25, 0xc7 => 0x9102, 0xc8 => 0x9354, + 0xc9 => 0x984e, 0xca => 0x9c10, 0xcb => 0x9f77, 0xcc => 0x5b89, + 0xcd => 0x5cb8, 0xce => 0x6309, 0xcf => 0x664f, 0xd0 => 0x6848, + 0xd1 => 0x773c, 0xd2 => 0x96c1, 0xd3 => 0x978d, 0xd4 => 0x9854, + 0xd5 => 0x9b9f, 0xd6 => 0x65a1, 0xd7 => 0x8b01, 0xd8 => 0x8ecb, + 0xd9 => 0x95bc, 0xda => 0x5535, 0xdb => 0x5ca9, 0xdc => 0x5dd6, + 0xdd => 0x5eb5, 0xde => 0x6697, 0xdf => 0x764c, 0xe0 => 0x83f4, + 0xe1 => 0x95c7, 0xe2 => 0x58d3, 0xe3 => 0x62bc, 0xe4 => 0x72ce, + 0xe5 => 0x9d28, 0xe6 => 0x4ef0, 0xe7 => 0x592e, 0xe8 => 0x600f, + 0xe9 => 0x663b, 0xea => 0x6b83, 0xeb => 0x79e7, 0xec => 0x9d26, + 0xed => 0x5393, 0xee => 0x54c0, 0xef => 0x57c3, 0xf0 => 0x5d16, + 0xf1 => 0x611b, 0xf2 => 0x66d6, 0xf3 => 0x6daf, 0xf4 => 0x788d, + 0xf5 => 0x827e, 0xf6 => 0x9698, 0xf7 => 0x9744, 0xf8 => 0x5384, + 0xf9 => 0x627c, 0xfa => 0x6396, 0xfb => 0x6db2, 0xfc => 0x7e0a, + 0xfd => 0x814b, 0xfe => 0x984d, + }, + 0xe5 => { + 0xa1 => 0x6afb, 0xa2 => 0x7f4c, 0xa3 => 0x9daf, 0xa4 => 0x9e1a, + 0xa5 => 0x4e5f, 0xa6 => 0x503b, 0xa7 => 0x51b6, 0xa8 => 0x591c, + 0xa9 => 0x60f9, 0xaa => 0x63f6, 0xab => 0x6930, 0xac => 0x723a, + 0xad => 0x8036, 0xae => 0xf974, 0xaf => 0x91ce, 0xb0 => 0x5f31, + 0xb1 => 0xf975, 0xb2 => 0xf976, 0xb3 => 0x7d04, 0xb4 => 0x82e5, + 0xb5 => 0x846f, 0xb6 => 0x84bb, 0xb7 => 0x85e5, 0xb8 => 0x8e8d, + 0xb9 => 0xf977, 0xba => 0x4f6f, 0xbb => 0xf978, 0xbc => 0xf979, + 0xbd => 0x58e4, 0xbe => 0x5b43, 0xbf => 0x6059, 0xc0 => 0x63da, + 0xc1 => 0x6518, 0xc2 => 0x656d, 0xc3 => 0x6698, 0xc4 => 0xf97a, + 0xc5 => 0x694a, 0xc6 => 0x6a23, 0xc7 => 0x6d0b, 0xc8 => 0x7001, + 0xc9 => 0x716c, 0xca => 0x75d2, 0xcb => 0x760d, 0xcc => 0x79b3, + 0xcd => 0x7a70, 0xce => 0xf97b, 0xcf => 0x7f8a, 0xd0 => 0xf97c, + 0xd1 => 0x8944, 0xd2 => 0xf97d, 0xd3 => 0x8b93, 0xd4 => 0x91c0, + 0xd5 => 0x967d, 0xd6 => 0xf97e, 0xd7 => 0x990a, 0xd8 => 0x5704, + 0xd9 => 0x5fa1, 0xda => 0x65bc, 0xdb => 0x6f01, 0xdc => 0x7600, + 0xdd => 0x79a6, 0xde => 0x8a9e, 0xdf => 0x99ad, 0xe0 => 0x9b5a, + 0xe1 => 0x9f6c, 0xe2 => 0x5104, 0xe3 => 0x61b6, 0xe4 => 0x6291, + 0xe5 => 0x6a8d, 0xe6 => 0x81c6, 0xe7 => 0x5043, 0xe8 => 0x5830, + 0xe9 => 0x5f66, 0xea => 0x7109, 0xeb => 0x8a00, 0xec => 0x8afa, + 0xed => 0x5b7c, 0xee => 0x8616, 0xef => 0x4ffa, 0xf0 => 0x513c, + 0xf1 => 0x56b4, 0xf2 => 0x5944, 0xf3 => 0x63a9, 0xf4 => 0x6df9, + 0xf5 => 0x5daa, 0xf6 => 0x696d, 0xf7 => 0x5186, 0xf8 => 0x4e88, + 0xf9 => 0x4f59, 0xfa => 0xf97f, 0xfb => 0xf980, 0xfc => 0xf981, + 0xfd => 0x5982, 0xfe => 0xf982, + }, + 0xe6 => { + 0xa1 => 0xf983, 0xa2 => 0x6b5f, 0xa3 => 0x6c5d, 0xa4 => 0xf984, + 0xa5 => 0x74b5, 0xa6 => 0x7916, 0xa7 => 0xf985, 0xa8 => 0x8207, + 0xa9 => 0x8245, 0xaa => 0x8339, 0xab => 0x8f3f, 0xac => 0x8f5d, + 0xad => 0xf986, 0xae => 0x9918, 0xaf => 0xf987, 0xb0 => 0xf988, + 0xb1 => 0xf989, 0xb2 => 0x4ea6, 0xb3 => 0xf98a, 0xb4 => 0x57df, + 0xb5 => 0x5f79, 0xb6 => 0x6613, 0xb7 => 0xf98b, 0xb8 => 0xf98c, + 0xb9 => 0x75ab, 0xba => 0x7e79, 0xbb => 0x8b6f, 0xbc => 0xf98d, + 0xbd => 0x9006, 0xbe => 0x9a5b, 0xbf => 0x56a5, 0xc0 => 0x5827, + 0xc1 => 0x59f8, 0xc2 => 0x5a1f, 0xc3 => 0x5bb4, 0xc4 => 0xf98e, + 0xc5 => 0x5ef6, 0xc6 => 0xf98f, 0xc7 => 0xf990, 0xc8 => 0x6350, + 0xc9 => 0x633b, 0xca => 0xf991, 0xcb => 0x693d, 0xcc => 0x6c87, + 0xcd => 0x6cbf, 0xce => 0x6d8e, 0xcf => 0x6d93, 0xd0 => 0x6df5, + 0xd1 => 0x6f14, 0xd2 => 0xf992, 0xd3 => 0x70df, 0xd4 => 0x7136, + 0xd5 => 0x7159, 0xd6 => 0xf993, 0xd7 => 0x71c3, 0xd8 => 0x71d5, + 0xd9 => 0xf994, 0xda => 0x784f, 0xdb => 0x786f, 0xdc => 0xf995, + 0xdd => 0x7b75, 0xde => 0x7de3, 0xdf => 0xf996, 0xe0 => 0x7e2f, + 0xe1 => 0xf997, 0xe2 => 0x884d, 0xe3 => 0x8edf, 0xe4 => 0xf998, + 0xe5 => 0xf999, 0xe6 => 0xf99a, 0xe7 => 0x925b, 0xe8 => 0xf99b, + 0xe9 => 0x9cf6, 0xea => 0xf99c, 0xeb => 0xf99d, 0xec => 0xf99e, + 0xed => 0x6085, 0xee => 0x6d85, 0xef => 0xf99f, 0xf0 => 0x71b1, + 0xf1 => 0xf9a0, 0xf2 => 0xf9a1, 0xf3 => 0x95b1, 0xf4 => 0x53ad, + 0xf5 => 0xf9a2, 0xf6 => 0xf9a3, 0xf7 => 0xf9a4, 0xf8 => 0x67d3, + 0xf9 => 0xf9a5, 0xfa => 0x708e, 0xfb => 0x7130, 0xfc => 0x7430, + 0xfd => 0x8276, 0xfe => 0x82d2, + }, + 0xe7 => { + 0xa1 => 0xf9a6, 0xa2 => 0x95bb, 0xa3 => 0x9ae5, 0xa4 => 0x9e7d, + 0xa5 => 0x66c4, 0xa6 => 0xf9a7, 0xa7 => 0x71c1, 0xa8 => 0x8449, + 0xa9 => 0xf9a8, 0xaa => 0xf9a9, 0xab => 0x584b, 0xac => 0xf9aa, + 0xad => 0xf9ab, 0xae => 0x5db8, 0xaf => 0x5f71, 0xb0 => 0xf9ac, + 0xb1 => 0x6620, 0xb2 => 0x668e, 0xb3 => 0x6979, 0xb4 => 0x69ae, + 0xb5 => 0x6c38, 0xb6 => 0x6cf3, 0xb7 => 0x6e36, 0xb8 => 0x6f41, + 0xb9 => 0x6fda, 0xba => 0x701b, 0xbb => 0x702f, 0xbc => 0x7150, + 0xbd => 0x71df, 0xbe => 0x7370, 0xbf => 0xf9ad, 0xc0 => 0x745b, + 0xc1 => 0xf9ae, 0xc2 => 0x74d4, 0xc3 => 0x76c8, 0xc4 => 0x7a4e, + 0xc5 => 0x7e93, 0xc6 => 0xf9af, 0xc7 => 0xf9b0, 0xc8 => 0x82f1, + 0xc9 => 0x8a60, 0xca => 0x8fce, 0xcb => 0xf9b1, 0xcc => 0x9348, + 0xcd => 0xf9b2, 0xce => 0x9719, 0xcf => 0xf9b3, 0xd0 => 0xf9b4, + 0xd1 => 0x4e42, 0xd2 => 0x502a, 0xd3 => 0xf9b5, 0xd4 => 0x5208, + 0xd5 => 0x53e1, 0xd6 => 0x66f3, 0xd7 => 0x6c6d, 0xd8 => 0x6fca, + 0xd9 => 0x730a, 0xda => 0x777f, 0xdb => 0x7a62, 0xdc => 0x82ae, + 0xdd => 0x85dd, 0xde => 0x8602, 0xdf => 0xf9b6, 0xe0 => 0x88d4, + 0xe1 => 0x8a63, 0xe2 => 0x8b7d, 0xe3 => 0x8c6b, 0xe4 => 0xf9b7, + 0xe5 => 0x92b3, 0xe6 => 0xf9b8, 0xe7 => 0x9713, 0xe8 => 0x9810, + 0xe9 => 0x4e94, 0xea => 0x4f0d, 0xeb => 0x4fc9, 0xec => 0x50b2, + 0xed => 0x5348, 0xee => 0x543e, 0xef => 0x5433, 0xf0 => 0x55da, + 0xf1 => 0x5862, 0xf2 => 0x58ba, 0xf3 => 0x5967, 0xf4 => 0x5a1b, + 0xf5 => 0x5be4, 0xf6 => 0x609f, 0xf7 => 0xf9b9, 0xf8 => 0x61ca, + 0xf9 => 0x6556, 0xfa => 0x65ff, 0xfb => 0x6664, 0xfc => 0x68a7, + 0xfd => 0x6c5a, 0xfe => 0x6fb3, + }, + 0xe8 => { + 0xa1 => 0x70cf, 0xa2 => 0x71ac, 0xa3 => 0x7352, 0xa4 => 0x7b7d, + 0xa5 => 0x8708, 0xa6 => 0x8aa4, 0xa7 => 0x9c32, 0xa8 => 0x9f07, + 0xa9 => 0x5c4b, 0xaa => 0x6c83, 0xab => 0x7344, 0xac => 0x7389, + 0xad => 0x923a, 0xae => 0x6eab, 0xaf => 0x7465, 0xb0 => 0x761f, + 0xb1 => 0x7a69, 0xb2 => 0x7e15, 0xb3 => 0x860a, 0xb4 => 0x5140, + 0xb5 => 0x58c5, 0xb6 => 0x64c1, 0xb7 => 0x74ee, 0xb8 => 0x7515, + 0xb9 => 0x7670, 0xba => 0x7fc1, 0xbb => 0x9095, 0xbc => 0x96cd, + 0xbd => 0x9954, 0xbe => 0x6e26, 0xbf => 0x74e6, 0xc0 => 0x7aa9, + 0xc1 => 0x7aaa, 0xc2 => 0x81e5, 0xc3 => 0x86d9, 0xc4 => 0x8778, + 0xc5 => 0x8a1b, 0xc6 => 0x5a49, 0xc7 => 0x5b8c, 0xc8 => 0x5b9b, + 0xc9 => 0x68a1, 0xca => 0x6900, 0xcb => 0x6d63, 0xcc => 0x73a9, + 0xcd => 0x7413, 0xce => 0x742c, 0xcf => 0x7897, 0xd0 => 0x7de9, + 0xd1 => 0x7feb, 0xd2 => 0x8118, 0xd3 => 0x8155, 0xd4 => 0x839e, + 0xd5 => 0x8c4c, 0xd6 => 0x962e, 0xd7 => 0x9811, 0xd8 => 0x66f0, + 0xd9 => 0x5f80, 0xda => 0x65fa, 0xdb => 0x6789, 0xdc => 0x6c6a, + 0xdd => 0x738b, 0xde => 0x502d, 0xdf => 0x5a03, 0xe0 => 0x6b6a, + 0xe1 => 0x77ee, 0xe2 => 0x5916, 0xe3 => 0x5d6c, 0xe4 => 0x5dcd, + 0xe5 => 0x7325, 0xe6 => 0x754f, 0xe7 => 0xf9ba, 0xe8 => 0xf9bb, + 0xe9 => 0x50e5, 0xea => 0x51f9, 0xeb => 0x582f, 0xec => 0x592d, + 0xed => 0x5996, 0xee => 0x59da, 0xef => 0x5be5, 0xf0 => 0xf9bc, + 0xf1 => 0xf9bd, 0xf2 => 0x5da2, 0xf3 => 0x62d7, 0xf4 => 0x6416, + 0xf5 => 0x6493, 0xf6 => 0x64fe, 0xf7 => 0xf9be, 0xf8 => 0x66dc, + 0xf9 => 0xf9bf, 0xfa => 0x6a48, 0xfb => 0xf9c0, 0xfc => 0x71ff, + 0xfd => 0x7464, 0xfe => 0xf9c1, + }, + 0xe9 => { + 0xa1 => 0x7a88, 0xa2 => 0x7aaf, 0xa3 => 0x7e47, 0xa4 => 0x7e5e, + 0xa5 => 0x8000, 0xa6 => 0x8170, 0xa7 => 0xf9c2, 0xa8 => 0x87ef, + 0xa9 => 0x8981, 0xaa => 0x8b20, 0xab => 0x9059, 0xac => 0xf9c3, + 0xad => 0x9080, 0xae => 0x9952, 0xaf => 0x617e, 0xb0 => 0x6b32, + 0xb1 => 0x6d74, 0xb2 => 0x7e1f, 0xb3 => 0x8925, 0xb4 => 0x8fb1, + 0xb5 => 0x4fd1, 0xb6 => 0x50ad, 0xb7 => 0x5197, 0xb8 => 0x52c7, + 0xb9 => 0x57c7, 0xba => 0x5889, 0xbb => 0x5bb9, 0xbc => 0x5eb8, + 0xbd => 0x6142, 0xbe => 0x6995, 0xbf => 0x6d8c, 0xc0 => 0x6e67, + 0xc1 => 0x6eb6, 0xc2 => 0x7194, 0xc3 => 0x7462, 0xc4 => 0x7528, + 0xc5 => 0x752c, 0xc6 => 0x8073, 0xc7 => 0x8338, 0xc8 => 0x84c9, + 0xc9 => 0x8e0a, 0xca => 0x9394, 0xcb => 0x93de, 0xcc => 0xf9c4, + 0xcd => 0x4e8e, 0xce => 0x4f51, 0xcf => 0x5076, 0xd0 => 0x512a, + 0xd1 => 0x53c8, 0xd2 => 0x53cb, 0xd3 => 0x53f3, 0xd4 => 0x5b87, + 0xd5 => 0x5bd3, 0xd6 => 0x5c24, 0xd7 => 0x611a, 0xd8 => 0x6182, + 0xd9 => 0x65f4, 0xda => 0x725b, 0xdb => 0x7397, 0xdc => 0x7440, + 0xdd => 0x76c2, 0xde => 0x7950, 0xdf => 0x7991, 0xe0 => 0x79b9, + 0xe1 => 0x7d06, 0xe2 => 0x7fbd, 0xe3 => 0x828b, 0xe4 => 0x85d5, + 0xe5 => 0x865e, 0xe6 => 0x8fc2, 0xe7 => 0x9047, 0xe8 => 0x90f5, + 0xe9 => 0x91ea, 0xea => 0x9685, 0xeb => 0x96e8, 0xec => 0x96e9, + 0xed => 0x52d6, 0xee => 0x5f67, 0xef => 0x65ed, 0xf0 => 0x6631, + 0xf1 => 0x682f, 0xf2 => 0x715c, 0xf3 => 0x7a36, 0xf4 => 0x90c1, + 0xf5 => 0x980a, 0xf6 => 0x4e91, 0xf7 => 0xf9c5, 0xf8 => 0x6a52, + 0xf9 => 0x6b9e, 0xfa => 0x6f90, 0xfb => 0x7189, 0xfc => 0x8018, + 0xfd => 0x82b8, 0xfe => 0x8553, + }, + 0xea => { + 0xa1 => 0x904b, 0xa2 => 0x9695, 0xa3 => 0x96f2, 0xa4 => 0x97fb, + 0xa5 => 0x851a, 0xa6 => 0x9b31, 0xa7 => 0x4e90, 0xa8 => 0x718a, + 0xa9 => 0x96c4, 0xaa => 0x5143, 0xab => 0x539f, 0xac => 0x54e1, + 0xad => 0x5713, 0xae => 0x5712, 0xaf => 0x57a3, 0xb0 => 0x5a9b, + 0xb1 => 0x5ac4, 0xb2 => 0x5bc3, 0xb3 => 0x6028, 0xb4 => 0x613f, + 0xb5 => 0x63f4, 0xb6 => 0x6c85, 0xb7 => 0x6d39, 0xb8 => 0x6e72, + 0xb9 => 0x6e90, 0xba => 0x7230, 0xbb => 0x733f, 0xbc => 0x7457, + 0xbd => 0x82d1, 0xbe => 0x8881, 0xbf => 0x8f45, 0xc0 => 0x9060, + 0xc1 => 0xf9c6, 0xc2 => 0x9662, 0xc3 => 0x9858, 0xc4 => 0x9d1b, + 0xc5 => 0x6708, 0xc6 => 0x8d8a, 0xc7 => 0x925e, 0xc8 => 0x4f4d, + 0xc9 => 0x5049, 0xca => 0x50de, 0xcb => 0x5371, 0xcc => 0x570d, + 0xcd => 0x59d4, 0xce => 0x5a01, 0xcf => 0x5c09, 0xd0 => 0x6170, + 0xd1 => 0x6690, 0xd2 => 0x6e2d, 0xd3 => 0x7232, 0xd4 => 0x744b, + 0xd5 => 0x7def, 0xd6 => 0x80c3, 0xd7 => 0x840e, 0xd8 => 0x8466, + 0xd9 => 0x853f, 0xda => 0x875f, 0xdb => 0x885b, 0xdc => 0x8918, + 0xdd => 0x8b02, 0xde => 0x9055, 0xdf => 0x97cb, 0xe0 => 0x9b4f, + 0xe1 => 0x4e73, 0xe2 => 0x4f91, 0xe3 => 0x5112, 0xe4 => 0x516a, + 0xe5 => 0xf9c7, 0xe6 => 0x552f, 0xe7 => 0x55a9, 0xe8 => 0x5b7a, + 0xe9 => 0x5ba5, 0xea => 0x5e7c, 0xeb => 0x5e7d, 0xec => 0x5ebe, + 0xed => 0x60a0, 0xee => 0x60df, 0xef => 0x6108, 0xf0 => 0x6109, + 0xf1 => 0x63c4, 0xf2 => 0x6538, 0xf3 => 0x6709, 0xf4 => 0xf9c8, + 0xf5 => 0x67d4, 0xf6 => 0x67da, 0xf7 => 0xf9c9, 0xf8 => 0x6961, + 0xf9 => 0x6962, 0xfa => 0x6cb9, 0xfb => 0x6d27, 0xfc => 0xf9ca, + 0xfd => 0x6e38, 0xfe => 0xf9cb, + }, + 0xeb => { + 0xa1 => 0x6fe1, 0xa2 => 0x7336, 0xa3 => 0x7337, 0xa4 => 0xf9cc, + 0xa5 => 0x745c, 0xa6 => 0x7531, 0xa7 => 0xf9cd, 0xa8 => 0x7652, + 0xa9 => 0xf9ce, 0xaa => 0xf9cf, 0xab => 0x7dad, 0xac => 0x81fe, + 0xad => 0x8438, 0xae => 0x88d5, 0xaf => 0x8a98, 0xb0 => 0x8adb, + 0xb1 => 0x8aed, 0xb2 => 0x8e30, 0xb3 => 0x8e42, 0xb4 => 0x904a, + 0xb5 => 0x903e, 0xb6 => 0x907a, 0xb7 => 0x9149, 0xb8 => 0x91c9, + 0xb9 => 0x936e, 0xba => 0xf9d0, 0xbb => 0xf9d1, 0xbc => 0x5809, + 0xbd => 0xf9d2, 0xbe => 0x6bd3, 0xbf => 0x8089, 0xc0 => 0x80b2, + 0xc1 => 0xf9d3, 0xc2 => 0xf9d4, 0xc3 => 0x5141, 0xc4 => 0x596b, + 0xc5 => 0x5c39, 0xc6 => 0xf9d5, 0xc7 => 0xf9d6, 0xc8 => 0x6f64, + 0xc9 => 0x73a7, 0xca => 0x80e4, 0xcb => 0x8d07, 0xcc => 0xf9d7, + 0xcd => 0x9217, 0xce => 0x958f, 0xcf => 0xf9d8, 0xd0 => 0xf9d9, + 0xd1 => 0xf9da, 0xd2 => 0xf9db, 0xd3 => 0x807f, 0xd4 => 0x620e, + 0xd5 => 0x701c, 0xd6 => 0x7d68, 0xd7 => 0x878d, 0xd8 => 0xf9dc, + 0xd9 => 0x57a0, 0xda => 0x6069, 0xdb => 0x6147, 0xdc => 0x6bb7, + 0xdd => 0x8abe, 0xde => 0x9280, 0xdf => 0x96b1, 0xe0 => 0x4e59, + 0xe1 => 0x541f, 0xe2 => 0x6deb, 0xe3 => 0x852d, 0xe4 => 0x9670, + 0xe5 => 0x97f3, 0xe6 => 0x98ee, 0xe7 => 0x63d6, 0xe8 => 0x6ce3, + 0xe9 => 0x9091, 0xea => 0x51dd, 0xeb => 0x61c9, 0xec => 0x81ba, + 0xed => 0x9df9, 0xee => 0x4f9d, 0xef => 0x501a, 0xf0 => 0x5100, + 0xf1 => 0x5b9c, 0xf2 => 0x610f, 0xf3 => 0x61ff, 0xf4 => 0x64ec, + 0xf5 => 0x6905, 0xf6 => 0x6bc5, 0xf7 => 0x7591, 0xf8 => 0x77e3, + 0xf9 => 0x7fa9, 0xfa => 0x8264, 0xfb => 0x858f, 0xfc => 0x87fb, + 0xfd => 0x8863, 0xfe => 0x8abc, + }, + 0xec => { + 0xa1 => 0x8b70, 0xa2 => 0x91ab, 0xa3 => 0x4e8c, 0xa4 => 0x4ee5, + 0xa5 => 0x4f0a, 0xa6 => 0xf9dd, 0xa7 => 0xf9de, 0xa8 => 0x5937, + 0xa9 => 0x59e8, 0xaa => 0xf9df, 0xab => 0x5df2, 0xac => 0x5f1b, + 0xad => 0x5f5b, 0xae => 0x6021, 0xaf => 0xf9e0, 0xb0 => 0xf9e1, + 0xb1 => 0xf9e2, 0xb2 => 0xf9e3, 0xb3 => 0x723e, 0xb4 => 0x73e5, + 0xb5 => 0xf9e4, 0xb6 => 0x7570, 0xb7 => 0x75cd, 0xb8 => 0xf9e5, + 0xb9 => 0x79fb, 0xba => 0xf9e6, 0xbb => 0x800c, 0xbc => 0x8033, + 0xbd => 0x8084, 0xbe => 0x82e1, 0xbf => 0x8351, 0xc0 => 0xf9e7, + 0xc1 => 0xf9e8, 0xc2 => 0x8cbd, 0xc3 => 0x8cb3, 0xc4 => 0x9087, + 0xc5 => 0xf9e9, 0xc6 => 0xf9ea, 0xc7 => 0x98f4, 0xc8 => 0x990c, + 0xc9 => 0xf9eb, 0xca => 0xf9ec, 0xcb => 0x7037, 0xcc => 0x76ca, + 0xcd => 0x7fca, 0xce => 0x7fcc, 0xcf => 0x7ffc, 0xd0 => 0x8b1a, + 0xd1 => 0x4eba, 0xd2 => 0x4ec1, 0xd3 => 0x5203, 0xd4 => 0x5370, + 0xd5 => 0xf9ed, 0xd6 => 0x54bd, 0xd7 => 0x56e0, 0xd8 => 0x59fb, + 0xd9 => 0x5bc5, 0xda => 0x5f15, 0xdb => 0x5fcd, 0xdc => 0x6e6e, + 0xdd => 0xf9ee, 0xde => 0xf9ef, 0xdf => 0x7d6a, 0xe0 => 0x8335, + 0xe1 => 0xf9f0, 0xe2 => 0x8693, 0xe3 => 0x8a8d, 0xe4 => 0xf9f1, + 0xe5 => 0x976d, 0xe6 => 0x9777, 0xe7 => 0xf9f2, 0xe8 => 0xf9f3, + 0xe9 => 0x4e00, 0xea => 0x4f5a, 0xeb => 0x4f7e, 0xec => 0x58f9, + 0xed => 0x65e5, 0xee => 0x6ea2, 0xef => 0x9038, 0xf0 => 0x93b0, + 0xf1 => 0x99b9, 0xf2 => 0x4efb, 0xf3 => 0x58ec, 0xf4 => 0x598a, + 0xf5 => 0x59d9, 0xf6 => 0x6041, 0xf7 => 0xf9f4, 0xf8 => 0xf9f5, + 0xf9 => 0x7a14, 0xfa => 0xf9f6, 0xfb => 0x834f, 0xfc => 0x8cc3, + 0xfd => 0x5165, 0xfe => 0x5344, + }, + 0xed => { + 0xa1 => 0xf9f7, 0xa2 => 0xf9f8, 0xa3 => 0xf9f9, 0xa4 => 0x4ecd, + 0xa5 => 0x5269, 0xa6 => 0x5b55, 0xa7 => 0x82bf, 0xa8 => 0x4ed4, + 0xa9 => 0x523a, 0xaa => 0x54a8, 0xab => 0x59c9, 0xac => 0x59ff, + 0xad => 0x5b50, 0xae => 0x5b57, 0xaf => 0x5b5c, 0xb0 => 0x6063, + 0xb1 => 0x6148, 0xb2 => 0x6ecb, 0xb3 => 0x7099, 0xb4 => 0x716e, + 0xb5 => 0x7386, 0xb6 => 0x74f7, 0xb7 => 0x75b5, 0xb8 => 0x78c1, + 0xb9 => 0x7d2b, 0xba => 0x8005, 0xbb => 0x81ea, 0xbc => 0x8328, + 0xbd => 0x8517, 0xbe => 0x85c9, 0xbf => 0x8aee, 0xc0 => 0x8cc7, + 0xc1 => 0x96cc, 0xc2 => 0x4f5c, 0xc3 => 0x52fa, 0xc4 => 0x56bc, + 0xc5 => 0x65ab, 0xc6 => 0x6628, 0xc7 => 0x707c, 0xc8 => 0x70b8, + 0xc9 => 0x7235, 0xca => 0x7dbd, 0xcb => 0x828d, 0xcc => 0x914c, + 0xcd => 0x96c0, 0xce => 0x9d72, 0xcf => 0x5b71, 0xd0 => 0x68e7, + 0xd1 => 0x6b98, 0xd2 => 0x6f7a, 0xd3 => 0x76de, 0xd4 => 0x5c91, + 0xd5 => 0x66ab, 0xd6 => 0x6f5b, 0xd7 => 0x7bb4, 0xd8 => 0x7c2a, + 0xd9 => 0x8836, 0xda => 0x96dc, 0xdb => 0x4e08, 0xdc => 0x4ed7, + 0xdd => 0x5320, 0xde => 0x5834, 0xdf => 0x58bb, 0xe0 => 0x58ef, + 0xe1 => 0x596c, 0xe2 => 0x5c07, 0xe3 => 0x5e33, 0xe4 => 0x5e84, + 0xe5 => 0x5f35, 0xe6 => 0x638c, 0xe7 => 0x66b2, 0xe8 => 0x6756, + 0xe9 => 0x6a1f, 0xea => 0x6aa3, 0xeb => 0x6b0c, 0xec => 0x6f3f, + 0xed => 0x7246, 0xee => 0xf9fa, 0xef => 0x7350, 0xf0 => 0x748b, + 0xf1 => 0x7ae0, 0xf2 => 0x7ca7, 0xf3 => 0x8178, 0xf4 => 0x81df, + 0xf5 => 0x81e7, 0xf6 => 0x838a, 0xf7 => 0x846c, 0xf8 => 0x8523, + 0xf9 => 0x8594, 0xfa => 0x85cf, 0xfb => 0x88dd, 0xfc => 0x8d13, + 0xfd => 0x91ac, 0xfe => 0x9577, + }, + 0xee => { + 0xa1 => 0x969c, 0xa2 => 0x518d, 0xa3 => 0x54c9, 0xa4 => 0x5728, + 0xa5 => 0x5bb0, 0xa6 => 0x624d, 0xa7 => 0x6750, 0xa8 => 0x683d, + 0xa9 => 0x6893, 0xaa => 0x6e3d, 0xab => 0x6ed3, 0xac => 0x707d, + 0xad => 0x7e21, 0xae => 0x88c1, 0xaf => 0x8ca1, 0xb0 => 0x8f09, + 0xb1 => 0x9f4b, 0xb2 => 0x9f4e, 0xb3 => 0x722d, 0xb4 => 0x7b8f, + 0xb5 => 0x8acd, 0xb6 => 0x931a, 0xb7 => 0x4f47, 0xb8 => 0x4f4e, + 0xb9 => 0x5132, 0xba => 0x5480, 0xbb => 0x59d0, 0xbc => 0x5e95, + 0xbd => 0x62b5, 0xbe => 0x6775, 0xbf => 0x696e, 0xc0 => 0x6a17, + 0xc1 => 0x6cae, 0xc2 => 0x6e1a, 0xc3 => 0x72d9, 0xc4 => 0x732a, + 0xc5 => 0x75bd, 0xc6 => 0x7bb8, 0xc7 => 0x7d35, 0xc8 => 0x82e7, + 0xc9 => 0x83f9, 0xca => 0x8457, 0xcb => 0x85f7, 0xcc => 0x8a5b, + 0xcd => 0x8caf, 0xce => 0x8e87, 0xcf => 0x9019, 0xd0 => 0x90b8, + 0xd1 => 0x96ce, 0xd2 => 0x9f5f, 0xd3 => 0x52e3, 0xd4 => 0x540a, + 0xd5 => 0x5ae1, 0xd6 => 0x5bc2, 0xd7 => 0x6458, 0xd8 => 0x6575, + 0xd9 => 0x6ef4, 0xda => 0x72c4, 0xdb => 0xf9fb, 0xdc => 0x7684, + 0xdd => 0x7a4d, 0xde => 0x7b1b, 0xdf => 0x7c4d, 0xe0 => 0x7e3e, + 0xe1 => 0x7fdf, 0xe2 => 0x837b, 0xe3 => 0x8b2b, 0xe4 => 0x8cca, + 0xe5 => 0x8d64, 0xe6 => 0x8de1, 0xe7 => 0x8e5f, 0xe8 => 0x8fea, + 0xe9 => 0x8ff9, 0xea => 0x9069, 0xeb => 0x93d1, 0xec => 0x4f43, + 0xed => 0x4f7a, 0xee => 0x50b3, 0xef => 0x5168, 0xf0 => 0x5178, + 0xf1 => 0x524d, 0xf2 => 0x526a, 0xf3 => 0x5861, 0xf4 => 0x587c, + 0xf5 => 0x5960, 0xf6 => 0x5c08, 0xf7 => 0x5c55, 0xf8 => 0x5edb, + 0xf9 => 0x609b, 0xfa => 0x6230, 0xfb => 0x6813, 0xfc => 0x6bbf, + 0xfd => 0x6c08, 0xfe => 0x6fb1, + }, + 0xef => { + 0xa1 => 0x714e, 0xa2 => 0x7420, 0xa3 => 0x7530, 0xa4 => 0x7538, + 0xa5 => 0x7551, 0xa6 => 0x7672, 0xa7 => 0x7b4c, 0xa8 => 0x7b8b, + 0xa9 => 0x7bad, 0xaa => 0x7bc6, 0xab => 0x7e8f, 0xac => 0x8a6e, + 0xad => 0x8f3e, 0xae => 0x8f49, 0xaf => 0x923f, 0xb0 => 0x9293, + 0xb1 => 0x9322, 0xb2 => 0x942b, 0xb3 => 0x96fb, 0xb4 => 0x985a, + 0xb5 => 0x986b, 0xb6 => 0x991e, 0xb7 => 0x5207, 0xb8 => 0x622a, + 0xb9 => 0x6298, 0xba => 0x6d59, 0xbb => 0x7664, 0xbc => 0x7aca, + 0xbd => 0x7bc0, 0xbe => 0x7d76, 0xbf => 0x5360, 0xc0 => 0x5cbe, + 0xc1 => 0x5e97, 0xc2 => 0x6f38, 0xc3 => 0x70b9, 0xc4 => 0x7c98, + 0xc5 => 0x9711, 0xc6 => 0x9b8e, 0xc7 => 0x9ede, 0xc8 => 0x63a5, + 0xc9 => 0x647a, 0xca => 0x8776, 0xcb => 0x4e01, 0xcc => 0x4e95, + 0xcd => 0x4ead, 0xce => 0x505c, 0xcf => 0x5075, 0xd0 => 0x5448, + 0xd1 => 0x59c3, 0xd2 => 0x5b9a, 0xd3 => 0x5e40, 0xd4 => 0x5ead, + 0xd5 => 0x5ef7, 0xd6 => 0x5f81, 0xd7 => 0x60c5, 0xd8 => 0x633a, + 0xd9 => 0x653f, 0xda => 0x6574, 0xdb => 0x65cc, 0xdc => 0x6676, + 0xdd => 0x6678, 0xde => 0x67fe, 0xdf => 0x6968, 0xe0 => 0x6a89, + 0xe1 => 0x6b63, 0xe2 => 0x6c40, 0xe3 => 0x6dc0, 0xe4 => 0x6de8, + 0xe5 => 0x6e1f, 0xe6 => 0x6e5e, 0xe7 => 0x701e, 0xe8 => 0x70a1, + 0xe9 => 0x738e, 0xea => 0x73fd, 0xeb => 0x753a, 0xec => 0x775b, + 0xed => 0x7887, 0xee => 0x798e, 0xef => 0x7a0b, 0xf0 => 0x7a7d, + 0xf1 => 0x7cbe, 0xf2 => 0x7d8e, 0xf3 => 0x8247, 0xf4 => 0x8a02, + 0xf5 => 0x8aea, 0xf6 => 0x8c9e, 0xf7 => 0x912d, 0xf8 => 0x914a, + 0xf9 => 0x91d8, 0xfa => 0x9266, 0xfb => 0x92cc, 0xfc => 0x9320, + 0xfd => 0x9706, 0xfe => 0x9756, + }, + 0xf0 => { + 0xa1 => 0x975c, 0xa2 => 0x9802, 0xa3 => 0x9f0e, 0xa4 => 0x5236, + 0xa5 => 0x5291, 0xa6 => 0x557c, 0xa7 => 0x5824, 0xa8 => 0x5e1d, + 0xa9 => 0x5f1f, 0xaa => 0x608c, 0xab => 0x63d0, 0xac => 0x68af, + 0xad => 0x6fdf, 0xae => 0x796d, 0xaf => 0x7b2c, 0xb0 => 0x81cd, + 0xb1 => 0x85ba, 0xb2 => 0x88fd, 0xb3 => 0x8af8, 0xb4 => 0x8e44, + 0xb5 => 0x918d, 0xb6 => 0x9664, 0xb7 => 0x969b, 0xb8 => 0x973d, + 0xb9 => 0x984c, 0xba => 0x9f4a, 0xbb => 0x4fce, 0xbc => 0x5146, + 0xbd => 0x51cb, 0xbe => 0x52a9, 0xbf => 0x5632, 0xc0 => 0x5f14, + 0xc1 => 0x5f6b, 0xc2 => 0x63aa, 0xc3 => 0x64cd, 0xc4 => 0x65e9, + 0xc5 => 0x6641, 0xc6 => 0x66fa, 0xc7 => 0x66f9, 0xc8 => 0x671d, + 0xc9 => 0x689d, 0xca => 0x68d7, 0xcb => 0x69fd, 0xcc => 0x6f15, + 0xcd => 0x6f6e, 0xce => 0x7167, 0xcf => 0x71e5, 0xd0 => 0x722a, + 0xd1 => 0x74aa, 0xd2 => 0x773a, 0xd3 => 0x7956, 0xd4 => 0x795a, + 0xd5 => 0x79df, 0xd6 => 0x7a20, 0xd7 => 0x7a95, 0xd8 => 0x7c97, + 0xd9 => 0x7cdf, 0xda => 0x7d44, 0xdb => 0x7e70, 0xdc => 0x8087, + 0xdd => 0x85fb, 0xde => 0x86a4, 0xdf => 0x8a54, 0xe0 => 0x8abf, + 0xe1 => 0x8d99, 0xe2 => 0x8e81, 0xe3 => 0x9020, 0xe4 => 0x906d, + 0xe5 => 0x91e3, 0xe6 => 0x963b, 0xe7 => 0x96d5, 0xe8 => 0x9ce5, + 0xe9 => 0x65cf, 0xea => 0x7c07, 0xeb => 0x8db3, 0xec => 0x93c3, + 0xed => 0x5b58, 0xee => 0x5c0a, 0xef => 0x5352, 0xf0 => 0x62d9, + 0xf1 => 0x731d, 0xf2 => 0x5027, 0xf3 => 0x5b97, 0xf4 => 0x5f9e, + 0xf5 => 0x60b0, 0xf6 => 0x616b, 0xf7 => 0x68d5, 0xf8 => 0x6dd9, + 0xf9 => 0x742e, 0xfa => 0x7a2e, 0xfb => 0x7d42, 0xfc => 0x7d9c, + 0xfd => 0x7e31, 0xfe => 0x816b, + }, + 0xf1 => { + 0xa1 => 0x8e2a, 0xa2 => 0x8e35, 0xa3 => 0x937e, 0xa4 => 0x9418, + 0xa5 => 0x4f50, 0xa6 => 0x5750, 0xa7 => 0x5de6, 0xa8 => 0x5ea7, + 0xa9 => 0x632b, 0xaa => 0x7f6a, 0xab => 0x4e3b, 0xac => 0x4f4f, + 0xad => 0x4f8f, 0xae => 0x505a, 0xaf => 0x59dd, 0xb0 => 0x80c4, + 0xb1 => 0x546a, 0xb2 => 0x5468, 0xb3 => 0x55fe, 0xb4 => 0x594f, + 0xb5 => 0x5b99, 0xb6 => 0x5dde, 0xb7 => 0x5eda, 0xb8 => 0x665d, + 0xb9 => 0x6731, 0xba => 0x67f1, 0xbb => 0x682a, 0xbc => 0x6ce8, + 0xbd => 0x6d32, 0xbe => 0x6e4a, 0xbf => 0x6f8d, 0xc0 => 0x70b7, + 0xc1 => 0x73e0, 0xc2 => 0x7587, 0xc3 => 0x7c4c, 0xc4 => 0x7d02, + 0xc5 => 0x7d2c, 0xc6 => 0x7da2, 0xc7 => 0x821f, 0xc8 => 0x86db, + 0xc9 => 0x8a3b, 0xca => 0x8a85, 0xcb => 0x8d70, 0xcc => 0x8e8a, + 0xcd => 0x8f33, 0xce => 0x9031, 0xcf => 0x914e, 0xd0 => 0x9152, + 0xd1 => 0x9444, 0xd2 => 0x99d0, 0xd3 => 0x7af9, 0xd4 => 0x7ca5, + 0xd5 => 0x4fca, 0xd6 => 0x5101, 0xd7 => 0x51c6, 0xd8 => 0x57c8, + 0xd9 => 0x5bef, 0xda => 0x5cfb, 0xdb => 0x6659, 0xdc => 0x6a3d, + 0xdd => 0x6d5a, 0xde => 0x6e96, 0xdf => 0x6fec, 0xe0 => 0x710c, + 0xe1 => 0x756f, 0xe2 => 0x7ae3, 0xe3 => 0x8822, 0xe4 => 0x9021, + 0xe5 => 0x9075, 0xe6 => 0x96cb, 0xe7 => 0x99ff, 0xe8 => 0x8301, + 0xe9 => 0x4e2d, 0xea => 0x4ef2, 0xeb => 0x8846, 0xec => 0x91cd, + 0xed => 0x537d, 0xee => 0x6adb, 0xef => 0x696b, 0xf0 => 0x6c41, + 0xf1 => 0x847a, 0xf2 => 0x589e, 0xf3 => 0x618e, 0xf4 => 0x66fe, + 0xf5 => 0x62ef, 0xf6 => 0x70dd, 0xf7 => 0x7511, 0xf8 => 0x75c7, + 0xf9 => 0x7e52, 0xfa => 0x84b8, 0xfb => 0x8b49, 0xfc => 0x8d08, + 0xfd => 0x4e4b, 0xfe => 0x53ea, + }, + 0xf2 => { + 0xa1 => 0x54ab, 0xa2 => 0x5730, 0xa3 => 0x5740, 0xa4 => 0x5fd7, + 0xa5 => 0x6301, 0xa6 => 0x6307, 0xa7 => 0x646f, 0xa8 => 0x652f, + 0xa9 => 0x65e8, 0xaa => 0x667a, 0xab => 0x679d, 0xac => 0x67b3, + 0xad => 0x6b62, 0xae => 0x6c60, 0xaf => 0x6c9a, 0xb0 => 0x6f2c, + 0xb1 => 0x77e5, 0xb2 => 0x7825, 0xb3 => 0x7949, 0xb4 => 0x7957, + 0xb5 => 0x7d19, 0xb6 => 0x80a2, 0xb7 => 0x8102, 0xb8 => 0x81f3, + 0xb9 => 0x829d, 0xba => 0x82b7, 0xbb => 0x8718, 0xbc => 0x8a8c, + 0xbd => 0xf9fc, 0xbe => 0x8d04, 0xbf => 0x8dbe, 0xc0 => 0x9072, + 0xc1 => 0x76f4, 0xc2 => 0x7a19, 0xc3 => 0x7a37, 0xc4 => 0x7e54, + 0xc5 => 0x8077, 0xc6 => 0x5507, 0xc7 => 0x55d4, 0xc8 => 0x5875, + 0xc9 => 0x632f, 0xca => 0x6422, 0xcb => 0x6649, 0xcc => 0x664b, + 0xcd => 0x686d, 0xce => 0x699b, 0xcf => 0x6b84, 0xd0 => 0x6d25, + 0xd1 => 0x6eb1, 0xd2 => 0x73cd, 0xd3 => 0x7468, 0xd4 => 0x74a1, + 0xd5 => 0x755b, 0xd6 => 0x75b9, 0xd7 => 0x76e1, 0xd8 => 0x771e, + 0xd9 => 0x778b, 0xda => 0x79e6, 0xdb => 0x7e09, 0xdc => 0x7e1d, + 0xdd => 0x81fb, 0xde => 0x852f, 0xdf => 0x8897, 0xe0 => 0x8a3a, + 0xe1 => 0x8cd1, 0xe2 => 0x8eeb, 0xe3 => 0x8fb0, 0xe4 => 0x9032, + 0xe5 => 0x93ad, 0xe6 => 0x9663, 0xe7 => 0x9673, 0xe8 => 0x9707, + 0xe9 => 0x4f84, 0xea => 0x53f1, 0xeb => 0x59ea, 0xec => 0x5ac9, + 0xed => 0x5e19, 0xee => 0x684e, 0xef => 0x74c6, 0xf0 => 0x75be, + 0xf1 => 0x79e9, 0xf2 => 0x7a92, 0xf3 => 0x81a3, 0xf4 => 0x86ed, + 0xf5 => 0x8cea, 0xf6 => 0x8dcc, 0xf7 => 0x8fed, 0xf8 => 0x659f, + 0xf9 => 0x6715, 0xfa => 0xf9fd, 0xfb => 0x57f7, 0xfc => 0x6f57, + 0xfd => 0x7ddd, 0xfe => 0x8f2f, + }, + 0xf3 => { + 0xa1 => 0x93f6, 0xa2 => 0x96c6, 0xa3 => 0x5fb5, 0xa4 => 0x61f2, + 0xa5 => 0x6f84, 0xa6 => 0x4e14, 0xa7 => 0x4f98, 0xa8 => 0x501f, + 0xa9 => 0x53c9, 0xaa => 0x55df, 0xab => 0x5d6f, 0xac => 0x5dee, + 0xad => 0x6b21, 0xae => 0x6b64, 0xaf => 0x78cb, 0xb0 => 0x7b9a, + 0xb1 => 0xf9fe, 0xb2 => 0x8e49, 0xb3 => 0x8eca, 0xb4 => 0x906e, + 0xb5 => 0x6349, 0xb6 => 0x643e, 0xb7 => 0x7740, 0xb8 => 0x7a84, + 0xb9 => 0x932f, 0xba => 0x947f, 0xbb => 0x9f6a, 0xbc => 0x64b0, + 0xbd => 0x6faf, 0xbe => 0x71e6, 0xbf => 0x74a8, 0xc0 => 0x74da, + 0xc1 => 0x7ac4, 0xc2 => 0x7c12, 0xc3 => 0x7e82, 0xc4 => 0x7cb2, + 0xc5 => 0x7e98, 0xc6 => 0x8b9a, 0xc7 => 0x8d0a, 0xc8 => 0x947d, + 0xc9 => 0x9910, 0xca => 0x994c, 0xcb => 0x5239, 0xcc => 0x5bdf, + 0xcd => 0x64e6, 0xce => 0x672d, 0xcf => 0x7d2e, 0xd0 => 0x50ed, + 0xd1 => 0x53c3, 0xd2 => 0x5879, 0xd3 => 0x6158, 0xd4 => 0x6159, + 0xd5 => 0x61fa, 0xd6 => 0x65ac, 0xd7 => 0x7ad9, 0xd8 => 0x8b92, + 0xd9 => 0x8b96, 0xda => 0x5009, 0xdb => 0x5021, 0xdc => 0x5275, + 0xdd => 0x5531, 0xde => 0x5a3c, 0xdf => 0x5ee0, 0xe0 => 0x5f70, + 0xe1 => 0x6134, 0xe2 => 0x655e, 0xe3 => 0x660c, 0xe4 => 0x6636, + 0xe5 => 0x66a2, 0xe6 => 0x69cd, 0xe7 => 0x6ec4, 0xe8 => 0x6f32, + 0xe9 => 0x7316, 0xea => 0x7621, 0xeb => 0x7a93, 0xec => 0x8139, + 0xed => 0x8259, 0xee => 0x83d6, 0xef => 0x84bc, 0xf0 => 0x50b5, + 0xf1 => 0x57f0, 0xf2 => 0x5bc0, 0xf3 => 0x5be8, 0xf4 => 0x5f69, + 0xf5 => 0x63a1, 0xf6 => 0x7826, 0xf7 => 0x7db5, 0xf8 => 0x83dc, + 0xf9 => 0x8521, 0xfa => 0x91c7, 0xfb => 0x91f5, 0xfc => 0x518a, + 0xfd => 0x67f5, 0xfe => 0x7b56, + }, + 0xf4 => { + 0xa1 => 0x8cac, 0xa2 => 0x51c4, 0xa3 => 0x59bb, 0xa4 => 0x60bd, + 0xa5 => 0x8655, 0xa6 => 0x501c, 0xa7 => 0xf9ff, 0xa8 => 0x5254, + 0xa9 => 0x5c3a, 0xaa => 0x617d, 0xab => 0x621a, 0xac => 0x62d3, + 0xad => 0x64f2, 0xae => 0x65a5, 0xaf => 0x6ecc, 0xb0 => 0x7620, + 0xb1 => 0x810a, 0xb2 => 0x8e60, 0xb3 => 0x965f, 0xb4 => 0x96bb, + 0xb5 => 0x4edf, 0xb6 => 0x5343, 0xb7 => 0x5598, 0xb8 => 0x5929, + 0xb9 => 0x5ddd, 0xba => 0x64c5, 0xbb => 0x6cc9, 0xbc => 0x6dfa, + 0xbd => 0x7394, 0xbe => 0x7a7f, 0xbf => 0x821b, 0xc0 => 0x85a6, + 0xc1 => 0x8ce4, 0xc2 => 0x8e10, 0xc3 => 0x9077, 0xc4 => 0x91e7, + 0xc5 => 0x95e1, 0xc6 => 0x9621, 0xc7 => 0x97c6, 0xc8 => 0x51f8, + 0xc9 => 0x54f2, 0xca => 0x5586, 0xcb => 0x5fb9, 0xcc => 0x64a4, + 0xcd => 0x6f88, 0xce => 0x7db4, 0xcf => 0x8f1f, 0xd0 => 0x8f4d, + 0xd1 => 0x9435, 0xd2 => 0x50c9, 0xd3 => 0x5c16, 0xd4 => 0x6cbe, + 0xd5 => 0x6dfb, 0xd6 => 0x751b, 0xd7 => 0x77bb, 0xd8 => 0x7c3d, + 0xd9 => 0x7c64, 0xda => 0x8a79, 0xdb => 0x8ac2, 0xdc => 0x581e, + 0xdd => 0x59be, 0xde => 0x5e16, 0xdf => 0x6377, 0xe0 => 0x7252, + 0xe1 => 0x758a, 0xe2 => 0x776b, 0xe3 => 0x8adc, 0xe4 => 0x8cbc, + 0xe5 => 0x8f12, 0xe6 => 0x5ef3, 0xe7 => 0x6674, 0xe8 => 0x6df8, + 0xe9 => 0x807d, 0xea => 0x83c1, 0xeb => 0x8acb, 0xec => 0x9751, + 0xed => 0x9bd6, 0xee => 0xfa00, 0xef => 0x5243, 0xf0 => 0x66ff, + 0xf1 => 0x6d95, 0xf2 => 0x6eef, 0xf3 => 0x7de0, 0xf4 => 0x8ae6, + 0xf5 => 0x902e, 0xf6 => 0x905e, 0xf7 => 0x9ad4, 0xf8 => 0x521d, + 0xf9 => 0x527f, 0xfa => 0x54e8, 0xfb => 0x6194, 0xfc => 0x6284, + 0xfd => 0x62db, 0xfe => 0x68a2, + }, + 0xf5 => { + 0xa1 => 0x6912, 0xa2 => 0x695a, 0xa3 => 0x6a35, 0xa4 => 0x7092, + 0xa5 => 0x7126, 0xa6 => 0x785d, 0xa7 => 0x7901, 0xa8 => 0x790e, + 0xa9 => 0x79d2, 0xaa => 0x7a0d, 0xab => 0x8096, 0xac => 0x8278, + 0xad => 0x82d5, 0xae => 0x8349, 0xaf => 0x8549, 0xb0 => 0x8c82, + 0xb1 => 0x8d85, 0xb2 => 0x9162, 0xb3 => 0x918b, 0xb4 => 0x91ae, + 0xb5 => 0x4fc3, 0xb6 => 0x56d1, 0xb7 => 0x71ed, 0xb8 => 0x77d7, + 0xb9 => 0x8700, 0xba => 0x89f8, 0xbb => 0x5bf8, 0xbc => 0x5fd6, + 0xbd => 0x6751, 0xbe => 0x90a8, 0xbf => 0x53e2, 0xc0 => 0x585a, + 0xc1 => 0x5bf5, 0xc2 => 0x60a4, 0xc3 => 0x6181, 0xc4 => 0x6460, + 0xc5 => 0x7e3d, 0xc6 => 0x8070, 0xc7 => 0x8525, 0xc8 => 0x9283, + 0xc9 => 0x64ae, 0xca => 0x50ac, 0xcb => 0x5d14, 0xcc => 0x6700, + 0xcd => 0x589c, 0xce => 0x62bd, 0xcf => 0x63a8, 0xd0 => 0x690e, + 0xd1 => 0x6978, 0xd2 => 0x6a1e, 0xd3 => 0x6e6b, 0xd4 => 0x76ba, + 0xd5 => 0x79cb, 0xd6 => 0x82bb, 0xd7 => 0x8429, 0xd8 => 0x8acf, + 0xd9 => 0x8da8, 0xda => 0x8ffd, 0xdb => 0x9112, 0xdc => 0x914b, + 0xdd => 0x919c, 0xde => 0x9310, 0xdf => 0x9318, 0xe0 => 0x939a, + 0xe1 => 0x96db, 0xe2 => 0x9a36, 0xe3 => 0x9c0d, 0xe4 => 0x4e11, + 0xe5 => 0x755c, 0xe6 => 0x795d, 0xe7 => 0x7afa, 0xe8 => 0x7b51, + 0xe9 => 0x7bc9, 0xea => 0x7e2e, 0xeb => 0x84c4, 0xec => 0x8e59, + 0xed => 0x8e74, 0xee => 0x8ef8, 0xef => 0x9010, 0xf0 => 0x6625, + 0xf1 => 0x693f, 0xf2 => 0x7443, 0xf3 => 0x51fa, 0xf4 => 0x672e, + 0xf5 => 0x9edc, 0xf6 => 0x5145, 0xf7 => 0x5fe0, 0xf8 => 0x6c96, + 0xf9 => 0x87f2, 0xfa => 0x885d, 0xfb => 0x8877, 0xfc => 0x60b4, + 0xfd => 0x81b5, 0xfe => 0x8403, + }, + 0xf6 => { + 0xa1 => 0x8d05, 0xa2 => 0x53d6, 0xa3 => 0x5439, 0xa4 => 0x5634, + 0xa5 => 0x5a36, 0xa6 => 0x5c31, 0xa7 => 0x708a, 0xa8 => 0x7fe0, + 0xa9 => 0x805a, 0xaa => 0x8106, 0xab => 0x81ed, 0xac => 0x8da3, + 0xad => 0x9189, 0xae => 0x9a5f, 0xaf => 0x9df2, 0xb0 => 0x5074, + 0xb1 => 0x4ec4, 0xb2 => 0x53a0, 0xb3 => 0x60fb, 0xb4 => 0x6e2c, + 0xb5 => 0x5c64, 0xb6 => 0x4f88, 0xb7 => 0x5024, 0xb8 => 0x55e4, + 0xb9 => 0x5cd9, 0xba => 0x5e5f, 0xbb => 0x6065, 0xbc => 0x6894, + 0xbd => 0x6cbb, 0xbe => 0x6dc4, 0xbf => 0x71be, 0xc0 => 0x75d4, + 0xc1 => 0x75f4, 0xc2 => 0x7661, 0xc3 => 0x7a1a, 0xc4 => 0x7a49, + 0xc5 => 0x7dc7, 0xc6 => 0x7dfb, 0xc7 => 0x7f6e, 0xc8 => 0x81f4, + 0xc9 => 0x86a9, 0xca => 0x8f1c, 0xcb => 0x96c9, 0xcc => 0x99b3, + 0xcd => 0x9f52, 0xce => 0x5247, 0xcf => 0x52c5, 0xd0 => 0x98ed, + 0xd1 => 0x89aa, 0xd2 => 0x4e03, 0xd3 => 0x67d2, 0xd4 => 0x6f06, + 0xd5 => 0x4fb5, 0xd6 => 0x5be2, 0xd7 => 0x6795, 0xd8 => 0x6c88, + 0xd9 => 0x6d78, 0xda => 0x741b, 0xdb => 0x7827, 0xdc => 0x91dd, + 0xdd => 0x937c, 0xde => 0x87c4, 0xdf => 0x79e4, 0xe0 => 0x7a31, + 0xe1 => 0x5feb, 0xe2 => 0x4ed6, 0xe3 => 0x54a4, 0xe4 => 0x553e, + 0xe5 => 0x58ae, 0xe6 => 0x59a5, 0xe7 => 0x60f0, 0xe8 => 0x6253, + 0xe9 => 0x62d6, 0xea => 0x6736, 0xeb => 0x6955, 0xec => 0x8235, + 0xed => 0x9640, 0xee => 0x99b1, 0xef => 0x99dd, 0xf0 => 0x502c, + 0xf1 => 0x5353, 0xf2 => 0x5544, 0xf3 => 0x577c, 0xf4 => 0xfa01, + 0xf5 => 0x6258, 0xf6 => 0xfa02, 0xf7 => 0x64e2, 0xf8 => 0x666b, + 0xf9 => 0x67dd, 0xfa => 0x6fc1, 0xfb => 0x6fef, 0xfc => 0x7422, + 0xfd => 0x7438, 0xfe => 0x8a17, + }, + 0xf7 => { + 0xa1 => 0x9438, 0xa2 => 0x5451, 0xa3 => 0x5606, 0xa4 => 0x5766, + 0xa5 => 0x5f48, 0xa6 => 0x619a, 0xa7 => 0x6b4e, 0xa8 => 0x7058, + 0xa9 => 0x70ad, 0xaa => 0x7dbb, 0xab => 0x8a95, 0xac => 0x596a, + 0xad => 0x812b, 0xae => 0x63a2, 0xaf => 0x7708, 0xb0 => 0x803d, + 0xb1 => 0x8caa, 0xb2 => 0x5854, 0xb3 => 0x642d, 0xb4 => 0x69bb, + 0xb5 => 0x5b95, 0xb6 => 0x5e11, 0xb7 => 0x6e6f, 0xb8 => 0xfa03, + 0xb9 => 0x8569, 0xba => 0x514c, 0xbb => 0x53f0, 0xbc => 0x592a, + 0xbd => 0x6020, 0xbe => 0x614b, 0xbf => 0x6b86, 0xc0 => 0x6c70, + 0xc1 => 0x6cf0, 0xc2 => 0x7b1e, 0xc3 => 0x80ce, 0xc4 => 0x82d4, + 0xc5 => 0x8dc6, 0xc6 => 0x90b0, 0xc7 => 0x98b1, 0xc8 => 0xfa04, + 0xc9 => 0x64c7, 0xca => 0x6fa4, 0xcb => 0x6491, 0xcc => 0x6504, + 0xcd => 0x514e, 0xce => 0x5410, 0xcf => 0x571f, 0xd0 => 0x8a0e, + 0xd1 => 0x615f, 0xd2 => 0x6876, 0xd3 => 0xfa05, 0xd4 => 0x75db, + 0xd5 => 0x7b52, 0xd6 => 0x7d71, 0xd7 => 0x901a, 0xd8 => 0x5806, + 0xd9 => 0x69cc, 0xda => 0x817f, 0xdb => 0x892a, 0xdc => 0x9000, + 0xdd => 0x9839, 0xde => 0x5078, 0xdf => 0x5957, 0xe0 => 0x59ac, + 0xe1 => 0x6295, 0xe2 => 0x900f, 0xe3 => 0x9b2a, 0xe4 => 0x615d, + 0xe5 => 0x7279, 0xe6 => 0x95d6, 0xe7 => 0x5761, 0xe8 => 0x5a46, + 0xe9 => 0x5df4, 0xea => 0x628a, 0xeb => 0x64ad, 0xec => 0x64fa, + 0xed => 0x6777, 0xee => 0x6ce2, 0xef => 0x6d3e, 0xf0 => 0x722c, + 0xf1 => 0x7436, 0xf2 => 0x7834, 0xf3 => 0x7f77, 0xf4 => 0x82ad, + 0xf5 => 0x8ddb, 0xf6 => 0x9817, 0xf7 => 0x5224, 0xf8 => 0x5742, + 0xf9 => 0x677f, 0xfa => 0x7248, 0xfb => 0x74e3, 0xfc => 0x8ca9, + 0xfd => 0x8fa6, 0xfe => 0x9211, + }, + 0xf8 => { + 0xa1 => 0x962a, 0xa2 => 0x516b, 0xa3 => 0x53ed, 0xa4 => 0x634c, + 0xa5 => 0x4f69, 0xa6 => 0x5504, 0xa7 => 0x6096, 0xa8 => 0x6557, + 0xa9 => 0x6c9b, 0xaa => 0x6d7f, 0xab => 0x724c, 0xac => 0x72fd, + 0xad => 0x7a17, 0xae => 0x8987, 0xaf => 0x8c9d, 0xb0 => 0x5f6d, + 0xb1 => 0x6f8e, 0xb2 => 0x70f9, 0xb3 => 0x81a8, 0xb4 => 0x610e, + 0xb5 => 0x4fbf, 0xb6 => 0x504f, 0xb7 => 0x6241, 0xb8 => 0x7247, + 0xb9 => 0x7bc7, 0xba => 0x7de8, 0xbb => 0x7fe9, 0xbc => 0x904d, + 0xbd => 0x97ad, 0xbe => 0x9a19, 0xbf => 0x8cb6, 0xc0 => 0x576a, + 0xc1 => 0x5e73, 0xc2 => 0x67b0, 0xc3 => 0x840d, 0xc4 => 0x8a55, + 0xc5 => 0x5420, 0xc6 => 0x5b16, 0xc7 => 0x5e63, 0xc8 => 0x5ee2, + 0xc9 => 0x5f0a, 0xca => 0x6583, 0xcb => 0x80ba, 0xcc => 0x853d, + 0xcd => 0x9589, 0xce => 0x965b, 0xcf => 0x4f48, 0xd0 => 0x5305, + 0xd1 => 0x530d, 0xd2 => 0x530f, 0xd3 => 0x5486, 0xd4 => 0x54fa, + 0xd5 => 0x5703, 0xd6 => 0x5e03, 0xd7 => 0x6016, 0xd8 => 0x629b, + 0xd9 => 0x62b1, 0xda => 0x6355, 0xdb => 0xfa06, 0xdc => 0x6ce1, + 0xdd => 0x6d66, 0xde => 0x75b1, 0xdf => 0x7832, 0xe0 => 0x80de, + 0xe1 => 0x812f, 0xe2 => 0x82de, 0xe3 => 0x8461, 0xe4 => 0x84b2, + 0xe5 => 0x888d, 0xe6 => 0x8912, 0xe7 => 0x900b, 0xe8 => 0x92ea, + 0xe9 => 0x98fd, 0xea => 0x9b91, 0xeb => 0x5e45, 0xec => 0x66b4, + 0xed => 0x66dd, 0xee => 0x7011, 0xef => 0x7206, 0xf0 => 0xfa07, + 0xf1 => 0x4ff5, 0xf2 => 0x527d, 0xf3 => 0x5f6a, 0xf4 => 0x6153, + 0xf5 => 0x6753, 0xf6 => 0x6a19, 0xf7 => 0x6f02, 0xf8 => 0x74e2, + 0xf9 => 0x7968, 0xfa => 0x8868, 0xfb => 0x8c79, 0xfc => 0x98c7, + 0xfd => 0x98c4, 0xfe => 0x9a43, + }, + 0xf9 => { + 0xa1 => 0x54c1, 0xa2 => 0x7a1f, 0xa3 => 0x6953, 0xa4 => 0x8af7, + 0xa5 => 0x8c4a, 0xa6 => 0x98a8, 0xa7 => 0x99ae, 0xa8 => 0x5f7c, + 0xa9 => 0x62ab, 0xaa => 0x75b2, 0xab => 0x76ae, 0xac => 0x88ab, + 0xad => 0x907f, 0xae => 0x9642, 0xaf => 0x5339, 0xb0 => 0x5f3c, + 0xb1 => 0x5fc5, 0xb2 => 0x6ccc, 0xb3 => 0x73cc, 0xb4 => 0x7562, + 0xb5 => 0x758b, 0xb6 => 0x7b46, 0xb7 => 0x82fe, 0xb8 => 0x999d, + 0xb9 => 0x4e4f, 0xba => 0x903c, 0xbb => 0x4e0b, 0xbc => 0x4f55, + 0xbd => 0x53a6, 0xbe => 0x590f, 0xbf => 0x5ec8, 0xc0 => 0x6630, + 0xc1 => 0x6cb3, 0xc2 => 0x7455, 0xc3 => 0x8377, 0xc4 => 0x8766, + 0xc5 => 0x8cc0, 0xc6 => 0x9050, 0xc7 => 0x971e, 0xc8 => 0x9c15, + 0xc9 => 0x58d1, 0xca => 0x5b78, 0xcb => 0x8650, 0xcc => 0x8b14, + 0xcd => 0x9db4, 0xce => 0x5bd2, 0xcf => 0x6068, 0xd0 => 0x608d, + 0xd1 => 0x65f1, 0xd2 => 0x6c57, 0xd3 => 0x6f22, 0xd4 => 0x6fa3, + 0xd5 => 0x701a, 0xd6 => 0x7f55, 0xd7 => 0x7ff0, 0xd8 => 0x9591, + 0xd9 => 0x9592, 0xda => 0x9650, 0xdb => 0x97d3, 0xdc => 0x5272, + 0xdd => 0x8f44, 0xde => 0x51fd, 0xdf => 0x542b, 0xe0 => 0x54b8, + 0xe1 => 0x5563, 0xe2 => 0x558a, 0xe3 => 0x6abb, 0xe4 => 0x6db5, + 0xe5 => 0x7dd8, 0xe6 => 0x8266, 0xe7 => 0x929c, 0xe8 => 0x9677, + 0xe9 => 0x9e79, 0xea => 0x5408, 0xeb => 0x54c8, 0xec => 0x76d2, + 0xed => 0x86e4, 0xee => 0x95a4, 0xef => 0x95d4, 0xf0 => 0x965c, + 0xf1 => 0x4ea2, 0xf2 => 0x4f09, 0xf3 => 0x59ee, 0xf4 => 0x5ae6, + 0xf5 => 0x5df7, 0xf6 => 0x6052, 0xf7 => 0x6297, 0xf8 => 0x676d, + 0xf9 => 0x6841, 0xfa => 0x6c86, 0xfb => 0x6e2f, 0xfc => 0x7f38, + 0xfd => 0x809b, 0xfe => 0x822a, + }, + 0xfa => { + 0xa1 => 0xfa08, 0xa2 => 0xfa09, 0xa3 => 0x9805, 0xa4 => 0x4ea5, + 0xa5 => 0x5055, 0xa6 => 0x54b3, 0xa7 => 0x5793, 0xa8 => 0x595a, + 0xa9 => 0x5b69, 0xaa => 0x5bb3, 0xab => 0x61c8, 0xac => 0x6977, + 0xad => 0x6d77, 0xae => 0x7023, 0xaf => 0x87f9, 0xb0 => 0x89e3, + 0xb1 => 0x8a72, 0xb2 => 0x8ae7, 0xb3 => 0x9082, 0xb4 => 0x99ed, + 0xb5 => 0x9ab8, 0xb6 => 0x52be, 0xb7 => 0x6838, 0xb8 => 0x5016, + 0xb9 => 0x5e78, 0xba => 0x674f, 0xbb => 0x8347, 0xbc => 0x884c, + 0xbd => 0x4eab, 0xbe => 0x5411, 0xbf => 0x56ae, 0xc0 => 0x73e6, + 0xc1 => 0x9115, 0xc2 => 0x97ff, 0xc3 => 0x9909, 0xc4 => 0x9957, + 0xc5 => 0x9999, 0xc6 => 0x5653, 0xc7 => 0x589f, 0xc8 => 0x865b, + 0xc9 => 0x8a31, 0xca => 0x61b2, 0xcb => 0x6af6, 0xcc => 0x737b, + 0xcd => 0x8ed2, 0xce => 0x6b47, 0xcf => 0x96aa, 0xd0 => 0x9a57, + 0xd1 => 0x5955, 0xd2 => 0x7200, 0xd3 => 0x8d6b, 0xd4 => 0x9769, + 0xd5 => 0x4fd4, 0xd6 => 0x5cf4, 0xd7 => 0x5f26, 0xd8 => 0x61f8, + 0xd9 => 0x665b, 0xda => 0x6ceb, 0xdb => 0x70ab, 0xdc => 0x7384, + 0xdd => 0x73b9, 0xde => 0x73fe, 0xdf => 0x7729, 0xe0 => 0x774d, + 0xe1 => 0x7d43, 0xe2 => 0x7d62, 0xe3 => 0x7e23, 0xe4 => 0x8237, + 0xe5 => 0x8852, 0xe6 => 0xfa0a, 0xe7 => 0x8ce2, 0xe8 => 0x9249, + 0xe9 => 0x986f, 0xea => 0x5b51, 0xeb => 0x7a74, 0xec => 0x8840, + 0xed => 0x9801, 0xee => 0x5acc, 0xef => 0x4fe0, 0xf0 => 0x5354, + 0xf1 => 0x593e, 0xf2 => 0x5cfd, 0xf3 => 0x633e, 0xf4 => 0x6d79, + 0xf5 => 0x72f9, 0xf6 => 0x8105, 0xf7 => 0x8107, 0xf8 => 0x83a2, + 0xf9 => 0x92cf, 0xfa => 0x9830, 0xfb => 0x4ea8, 0xfc => 0x5144, + 0xfd => 0x5211, 0xfe => 0x578b, + }, + 0xfb => { + 0xa1 => 0x5f62, 0xa2 => 0x6cc2, 0xa3 => 0x6ece, 0xa4 => 0x7005, + 0xa5 => 0x7050, 0xa6 => 0x70af, 0xa7 => 0x7192, 0xa8 => 0x73e9, + 0xa9 => 0x7469, 0xaa => 0x834a, 0xab => 0x87a2, 0xac => 0x8861, + 0xad => 0x9008, 0xae => 0x90a2, 0xaf => 0x93a3, 0xb0 => 0x99a8, + 0xb1 => 0x516e, 0xb2 => 0x5f57, 0xb3 => 0x60e0, 0xb4 => 0x6167, + 0xb5 => 0x66b3, 0xb6 => 0x8559, 0xb7 => 0x8e4a, 0xb8 => 0x91af, + 0xb9 => 0x978b, 0xba => 0x4e4e, 0xbb => 0x4e92, 0xbc => 0x547c, + 0xbd => 0x58d5, 0xbe => 0x58fa, 0xbf => 0x597d, 0xc0 => 0x5cb5, + 0xc1 => 0x5f27, 0xc2 => 0x6236, 0xc3 => 0x6248, 0xc4 => 0x660a, + 0xc5 => 0x6667, 0xc6 => 0x6beb, 0xc7 => 0x6d69, 0xc8 => 0x6dcf, + 0xc9 => 0x6e56, 0xca => 0x6ef8, 0xcb => 0x6f94, 0xcc => 0x6fe0, + 0xcd => 0x6fe9, 0xce => 0x705d, 0xcf => 0x72d0, 0xd0 => 0x7425, + 0xd1 => 0x745a, 0xd2 => 0x74e0, 0xd3 => 0x7693, 0xd4 => 0x795c, + 0xd5 => 0x7cca, 0xd6 => 0x7e1e, 0xd7 => 0x80e1, 0xd8 => 0x82a6, + 0xd9 => 0x846b, 0xda => 0x84bf, 0xdb => 0x864e, 0xdc => 0x865f, + 0xdd => 0x8774, 0xde => 0x8b77, 0xdf => 0x8c6a, 0xe0 => 0x93ac, + 0xe1 => 0x9800, 0xe2 => 0x9865, 0xe3 => 0x60d1, 0xe4 => 0x6216, + 0xe5 => 0x9177, 0xe6 => 0x5a5a, 0xe7 => 0x660f, 0xe8 => 0x6df7, + 0xe9 => 0x6e3e, 0xea => 0x743f, 0xeb => 0x9b42, 0xec => 0x5ffd, + 0xed => 0x60da, 0xee => 0x7b0f, 0xef => 0x54c4, 0xf0 => 0x5f18, + 0xf1 => 0x6c5e, 0xf2 => 0x6cd3, 0xf3 => 0x6d2a, 0xf4 => 0x70d8, + 0xf5 => 0x7d05, 0xf6 => 0x8679, 0xf7 => 0x8a0c, 0xf8 => 0x9d3b, + 0xf9 => 0x5316, 0xfa => 0x548c, 0xfb => 0x5b05, 0xfc => 0x6a3a, + 0xfd => 0x706b, 0xfe => 0x7575, + }, + 0xfc => { + 0xa1 => 0x798d, 0xa2 => 0x79be, 0xa3 => 0x82b1, 0xa4 => 0x83ef, + 0xa5 => 0x8a71, 0xa6 => 0x8b41, 0xa7 => 0x8ca8, 0xa8 => 0x9774, + 0xa9 => 0xfa0b, 0xaa => 0x64f4, 0xab => 0x652b, 0xac => 0x78ba, + 0xad => 0x78bb, 0xae => 0x7a6b, 0xaf => 0x4e38, 0xb0 => 0x559a, + 0xb1 => 0x5950, 0xb2 => 0x5ba6, 0xb3 => 0x5e7b, 0xb4 => 0x60a3, + 0xb5 => 0x63db, 0xb6 => 0x6b61, 0xb7 => 0x6665, 0xb8 => 0x6853, + 0xb9 => 0x6e19, 0xba => 0x7165, 0xbb => 0x74b0, 0xbc => 0x7d08, + 0xbd => 0x9084, 0xbe => 0x9a69, 0xbf => 0x9c25, 0xc0 => 0x6d3b, + 0xc1 => 0x6ed1, 0xc2 => 0x733e, 0xc3 => 0x8c41, 0xc4 => 0x95ca, + 0xc5 => 0x51f0, 0xc6 => 0x5e4c, 0xc7 => 0x5fa8, 0xc8 => 0x604d, + 0xc9 => 0x60f6, 0xca => 0x6130, 0xcb => 0x614c, 0xcc => 0x6643, + 0xcd => 0x6644, 0xce => 0x69a5, 0xcf => 0x6cc1, 0xd0 => 0x6e5f, + 0xd1 => 0x6ec9, 0xd2 => 0x6f62, 0xd3 => 0x714c, 0xd4 => 0x749c, + 0xd5 => 0x7687, 0xd6 => 0x7bc1, 0xd7 => 0x7c27, 0xd8 => 0x8352, + 0xd9 => 0x8757, 0xda => 0x9051, 0xdb => 0x968d, 0xdc => 0x9ec3, + 0xdd => 0x532f, 0xde => 0x56de, 0xdf => 0x5efb, 0xe0 => 0x5f8a, + 0xe1 => 0x6062, 0xe2 => 0x6094, 0xe3 => 0x61f7, 0xe4 => 0x6666, + 0xe5 => 0x6703, 0xe6 => 0x6a9c, 0xe7 => 0x6dee, 0xe8 => 0x6fae, + 0xe9 => 0x7070, 0xea => 0x736a, 0xeb => 0x7e6a, 0xec => 0x81be, + 0xed => 0x8334, 0xee => 0x86d4, 0xef => 0x8aa8, 0xf0 => 0x8cc4, + 0xf1 => 0x5283, 0xf2 => 0x7372, 0xf3 => 0x5b96, 0xf4 => 0x6a6b, + 0xf5 => 0x9404, 0xf6 => 0x54ee, 0xf7 => 0x5686, 0xf8 => 0x5b5d, + 0xf9 => 0x6548, 0xfa => 0x6585, 0xfb => 0x66c9, 0xfc => 0x689f, + 0xfd => 0x6d8d, 0xfe => 0x6dc6, + }, + 0xfd => { + 0xa1 => 0x723b, 0xa2 => 0x80b4, 0xa3 => 0x9175, 0xa4 => 0x9a4d, + 0xa5 => 0x4faf, 0xa6 => 0x5019, 0xa7 => 0x539a, 0xa8 => 0x540e, + 0xa9 => 0x543c, 0xaa => 0x5589, 0xab => 0x55c5, 0xac => 0x5e3f, + 0xad => 0x5f8c, 0xae => 0x673d, 0xaf => 0x7166, 0xb0 => 0x73dd, + 0xb1 => 0x9005, 0xb2 => 0x52db, 0xb3 => 0x52f3, 0xb4 => 0x5864, + 0xb5 => 0x58ce, 0xb6 => 0x7104, 0xb7 => 0x718f, 0xb8 => 0x71fb, + 0xb9 => 0x85b0, 0xba => 0x8a13, 0xbb => 0x6688, 0xbc => 0x85a8, + 0xbd => 0x55a7, 0xbe => 0x6684, 0xbf => 0x714a, 0xc0 => 0x8431, + 0xc1 => 0x5349, 0xc2 => 0x5599, 0xc3 => 0x6bc1, 0xc4 => 0x5f59, + 0xc5 => 0x5fbd, 0xc6 => 0x63ee, 0xc7 => 0x6689, 0xc8 => 0x7147, + 0xc9 => 0x8af1, 0xca => 0x8f1d, 0xcb => 0x9ebe, 0xcc => 0x4f11, + 0xcd => 0x643a, 0xce => 0x70cb, 0xcf => 0x7566, 0xd0 => 0x8667, + 0xd1 => 0x6064, 0xd2 => 0x8b4e, 0xd3 => 0x9df8, 0xd4 => 0x5147, + 0xd5 => 0x51f6, 0xd6 => 0x5308, 0xd7 => 0x6d36, 0xd8 => 0x80f8, + 0xd9 => 0x9ed1, 0xda => 0x6615, 0xdb => 0x6b23, 0xdc => 0x7098, + 0xdd => 0x75d5, 0xde => 0x5403, 0xdf => 0x5c79, 0xe0 => 0x7d07, + 0xe1 => 0x8a16, 0xe2 => 0x6b20, 0xe3 => 0x6b3d, 0xe4 => 0x6b46, + 0xe5 => 0x5438, 0xe6 => 0x6070, 0xe7 => 0x6d3d, 0xe8 => 0x7fd5, + 0xe9 => 0x8208, 0xea => 0x50d6, 0xeb => 0x51de, 0xec => 0x559c, + 0xed => 0x566b, 0xee => 0x56cd, 0xef => 0x59ec, 0xf0 => 0x5b09, + 0xf1 => 0x5e0c, 0xf2 => 0x6199, 0xf3 => 0x6198, 0xf4 => 0x6231, + 0xf5 => 0x665e, 0xf6 => 0x66e6, 0xf7 => 0x7199, 0xf8 => 0x71b9, + 0xf9 => 0x71ba, 0xfa => 0x72a7, 0xfb => 0x79a7, 0xfc => 0x7a00, + 0xfd => 0x7fb2, 0xfe => 0x8a70, + }, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacLatin2.pm b/ExifTool/lib/Image/ExifTool/Charset/MacLatin2.pm new file mode 100644 index 0000000..5b891ec --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacLatin2.pm @@ -0,0 +1,44 @@ +#------------------------------------------------------------------------------ +# File: MacLatin2.pm +# +# Description: Mac Central European to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/CENTEURO.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacLatin2 = ( + 0x80 => 0xc4, 0x81 => 0x0100, 0x82 => 0x0101, 0x83 => 0xc9, 0x84 => 0x0104, + 0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0x0105, 0x89 => 0x010c, + 0x8a => 0xe4, 0x8b => 0x010d, 0x8c => 0x0106, 0x8d => 0x0107, 0x8e => 0xe9, + 0x8f => 0x0179, 0x90 => 0x017a, 0x91 => 0x010e, 0x92 => 0xed, 0x93 => 0x010f, + 0x94 => 0x0112, 0x95 => 0x0113, 0x96 => 0x0116, 0x97 => 0xf3, 0x98 => 0x0117, + 0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0x011a, + 0x9e => 0x011b, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa2 => 0x0118, + 0xa4 => 0xa7, 0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, + 0xaa => 0x2122, 0xab => 0x0119, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0x0123, + 0xaf => 0x012e, 0xb0 => 0x012f, 0xb1 => 0x012a, 0xb2 => 0x2264, + 0xb3 => 0x2265, 0xb4 => 0x012b, 0xb5 => 0x0136, 0xb6 => 0x2202, + 0xb7 => 0x2211, 0xb8 => 0x0142, 0xb9 => 0x013b, 0xba => 0x013c, + 0xbb => 0x013d, 0xbc => 0x013e, 0xbd => 0x0139, 0xbe => 0x013a, + 0xbf => 0x0145, 0xc0 => 0x0146, 0xc1 => 0x0143, 0xc2 => 0xac, 0xc3 => 0x221a, + 0xc4 => 0x0144, 0xc5 => 0x0147, 0xc6 => 0x2206, 0xc7 => 0xab, 0xc8 => 0xbb, + 0xc9 => 0x2026, 0xca => 0xa0, 0xcb => 0x0148, 0xcc => 0x0150, 0xcd => 0xd5, + 0xce => 0x0151, 0xcf => 0x014c, 0xd0 => 0x2013, 0xd1 => 0x2014, + 0xd2 => 0x201c, 0xd3 => 0x201d, 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, + 0xd7 => 0x25ca, 0xd8 => 0x014d, 0xd9 => 0x0154, 0xda => 0x0155, + 0xdb => 0x0158, 0xdc => 0x2039, 0xdd => 0x203a, 0xde => 0x0159, + 0xdf => 0x0156, 0xe0 => 0x0157, 0xe1 => 0x0160, 0xe2 => 0x201a, + 0xe3 => 0x201e, 0xe4 => 0x0161, 0xe5 => 0x015a, 0xe6 => 0x015b, 0xe7 => 0xc1, + 0xe8 => 0x0164, 0xe9 => 0x0165, 0xea => 0xcd, 0xeb => 0x017d, 0xec => 0x017e, + 0xed => 0x016a, 0xee => 0xd3, 0xef => 0xd4, 0xf0 => 0x016b, 0xf1 => 0x016e, + 0xf2 => 0xda, 0xf3 => 0x016f, 0xf4 => 0x0170, 0xf5 => 0x0171, 0xf6 => 0x0172, + 0xf7 => 0x0173, 0xf8 => 0xdd, 0xf9 => 0xfd, 0xfa => 0x0137, 0xfb => 0x017b, + 0xfc => 0x0141, 0xfd => 0x017c, 0xfe => 0x0122, 0xff => 0x02c7, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacRSymbol.pm b/ExifTool/lib/Image/ExifTool/Charset/MacRSymbol.pm new file mode 100644 index 0000000..073c743 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacRSymbol.pm @@ -0,0 +1,2087 @@ +#------------------------------------------------------------------------------ +# File: MacRSymbol.pm +# +# Description: Mac RSymbol (cp10008) to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://www.haible.de/bruno/charsets/conversion-tables/GB2312.tar.bz2 +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacRSymbol = ( + 0x81 => 0xf8d8, 0x82 => 0xf8d9, 0x83 => 0xf8da, 0x84 => 0xf8db, + 0x85 => 0xf8dc, 0x86 => 0xf8dd, 0x87 => 0xf8de, 0x88 => 0xf8df, + 0x89 => 0xf8e0, 0x8a => 0xf8e1, 0x8b => 0xf8e2, 0x8c => 0xf8e3, + 0x8d => 0xf8e4, 0x8e => 0xf8e5, 0x8f => 0xf8e6, 0x90 => 0xf8e7, + 0x91 => 0xf8e8, 0x92 => 0xf8e9, 0x93 => 0xf8ea, 0x94 => 0xf8eb, + 0x95 => 0xf8ec, 0x96 => 0xf8ed, 0x97 => 0xf8ee, 0x98 => 0xf8ef, + 0x99 => 0xf8f0, 0x9a => 0xf8f1, 0x9b => 0xf8f2, 0x9c => 0xf8f3, + 0x9d => 0xf8f4, 0x9e => 0xf8f5, 0x9f => 0xf8f6, 0xa0 => 0xf8f7, + 0xa1 => { + 0xa1 => 0x3000, 0xa2 => 0x3001, 0xa3 => 0x3002, 0xa4 => 0x30fb, + 0xa5 => 0x02c9, 0xa6 => 0x02c7, 0xa7 => 0xa8, 0xa8 => 0x3003, + 0xa9 => 0x3005, 0xaa => 0x2015, 0xab => 0xff5e, 0xad => 0x2026, + 0xae => 0x2018, 0xaf => 0x2019, 0xb0 => 0x201c, 0xb1 => 0x201d, + 0xb2 => 0x3014, 0xb3 => 0x3015, 0xb4 => 0x3008, 0xb5 => 0x3009, + 0xb6 => 0x300a, 0xb7 => 0x300b, 0xb8 => 0x300c, 0xb9 => 0x300d, + 0xba => 0x300e, 0xbb => 0x300f, 0xbc => 0x3016, 0xbd => 0x3017, + 0xbe => 0x3010, 0xbf => 0x3011, 0xc0 => 0xb1, 0xc1 => 0xd7, 0xc2 => 0xf7, + 0xc3 => 0x2236, 0xc4 => 0x2227, 0xc5 => 0x2228, 0xc6 => 0x2211, + 0xc7 => 0x220f, 0xc8 => 0x222a, 0xc9 => 0x2229, 0xca => 0x2208, + 0xcb => 0x2237, 0xcc => 0x221a, 0xcd => 0x22a5, 0xce => 0x2225, + 0xcf => 0x2220, 0xd0 => 0x2312, 0xd1 => 0x2299, 0xd2 => 0x222b, + 0xd3 => 0x222e, 0xd4 => 0x2261, 0xd5 => 0x224c, 0xd6 => 0x2248, + 0xd7 => 0x223d, 0xd8 => 0x221d, 0xd9 => 0x2260, 0xda => 0x226e, + 0xdb => 0x226f, 0xdc => 0x2264, 0xdd => 0x2265, 0xde => 0x221e, + 0xdf => 0x2235, 0xe0 => 0x2234, 0xe1 => 0x2642, 0xe2 => 0x2640, + 0xe3 => 0xb0, 0xe4 => 0x2032, 0xe5 => 0x2033, 0xe6 => 0x2103, + 0xe7 => 0xff04, 0xe8 => 0xa4, 0xe9 => 0xffe0, 0xea => 0xffe1, + 0xeb => 0x2030, 0xec => 0xa7, 0xed => 0x2116, 0xee => 0x2606, + 0xef => 0x2605, 0xf0 => 0x25cb, 0xf1 => 0x25cf, 0xf2 => 0x25ce, + 0xf3 => 0x25c7, 0xf4 => 0x25c6, 0xf5 => 0x25a1, 0xf6 => 0x25a0, + 0xf7 => 0x25b3, 0xf8 => 0x25b2, 0xf9 => 0x203b, 0xfa => 0x2192, + 0xfb => 0x2190, 0xfc => 0x2191, 0xfd => 0x2193, 0xfe => 0x3013, + }, + 0xa2 => { + 0xb1 => 0x2488, 0xb2 => 0x2489, 0xb3 => 0x248a, 0xb4 => 0x248b, + 0xb5 => 0x248c, 0xb6 => 0x248d, 0xb7 => 0x248e, 0xb8 => 0x248f, + 0xb9 => 0x2490, 0xba => 0x2491, 0xbb => 0x2492, 0xbc => 0x2493, + 0xbd => 0x2494, 0xbe => 0x2495, 0xbf => 0x2496, 0xc0 => 0x2497, + 0xc1 => 0x2498, 0xc2 => 0x2499, 0xc3 => 0x249a, 0xc4 => 0x249b, + 0xc5 => 0x2474, 0xc6 => 0x2475, 0xc7 => 0x2476, 0xc8 => 0x2477, + 0xc9 => 0x2478, 0xca => 0x2479, 0xcb => 0x247a, 0xcc => 0x247b, + 0xcd => 0x247c, 0xce => 0x247d, 0xcf => 0x247e, 0xd0 => 0x247f, + 0xd1 => 0x2480, 0xd2 => 0x2481, 0xd3 => 0x2482, 0xd4 => 0x2483, + 0xd5 => 0x2484, 0xd6 => 0x2485, 0xd7 => 0x2486, 0xd8 => 0x2487, + 0xd9 => 0x2460, 0xda => 0x2461, 0xdb => 0x2462, 0xdc => 0x2463, + 0xdd => 0x2464, 0xde => 0x2465, 0xdf => 0x2466, 0xe0 => 0x2467, + 0xe1 => 0x2468, 0xe2 => 0x2469, 0xe5 => 0x3220, 0xe6 => 0x3221, + 0xe7 => 0x3222, 0xe8 => 0x3223, 0xe9 => 0x3224, 0xea => 0x3225, + 0xeb => 0x3226, 0xec => 0x3227, 0xed => 0x3228, 0xee => 0x3229, + 0xf1 => 0x2160, 0xf2 => 0x2161, 0xf3 => 0x2162, 0xf4 => 0x2163, + 0xf5 => 0x2164, 0xf6 => 0x2165, 0xf7 => 0x2166, 0xf8 => 0x2167, + 0xf9 => 0x2168, 0xfa => 0x2169, 0xfb => 0x216a, 0xfc => 0x216b, + }, + 0xa3 => { + 0xa1 => 0xff01, 0xa2 => 0xff02, 0xa3 => 0xff03, 0xa4 => 0xffe5, + 0xa5 => 0xff05, 0xa6 => 0xff06, 0xa7 => 0xff07, 0xa8 => 0xff08, + 0xa9 => 0xff09, 0xaa => 0xff0a, 0xab => 0xff0b, 0xac => 0xff0c, + 0xad => 0xff0d, 0xae => 0xff0e, 0xaf => 0xff0f, 0xb0 => 0xff10, + 0xb1 => 0xff11, 0xb2 => 0xff12, 0xb3 => 0xff13, 0xb4 => 0xff14, + 0xb5 => 0xff15, 0xb6 => 0xff16, 0xb7 => 0xff17, 0xb8 => 0xff18, + 0xb9 => 0xff19, 0xba => 0xff1a, 0xbb => 0xff1b, 0xbc => 0xff1c, + 0xbd => 0xff1d, 0xbe => 0xff1e, 0xbf => 0xff1f, 0xc0 => 0xff20, + 0xc1 => 0xff21, 0xc2 => 0xff22, 0xc3 => 0xff23, 0xc4 => 0xff24, + 0xc5 => 0xff25, 0xc6 => 0xff26, 0xc7 => 0xff27, 0xc8 => 0xff28, + 0xc9 => 0xff29, 0xca => 0xff2a, 0xcb => 0xff2b, 0xcc => 0xff2c, + 0xcd => 0xff2d, 0xce => 0xff2e, 0xcf => 0xff2f, 0xd0 => 0xff30, + 0xd1 => 0xff31, 0xd2 => 0xff32, 0xd3 => 0xff33, 0xd4 => 0xff34, + 0xd5 => 0xff35, 0xd6 => 0xff36, 0xd7 => 0xff37, 0xd8 => 0xff38, + 0xd9 => 0xff39, 0xda => 0xff3a, 0xdb => 0xff3b, 0xdc => 0xff3c, + 0xdd => 0xff3d, 0xde => 0xff3e, 0xdf => 0xff3f, 0xe0 => 0xff40, + 0xe1 => 0xff41, 0xe2 => 0xff42, 0xe3 => 0xff43, 0xe4 => 0xff44, + 0xe5 => 0xff45, 0xe6 => 0xff46, 0xe7 => 0xff47, 0xe8 => 0xff48, + 0xe9 => 0xff49, 0xea => 0xff4a, 0xeb => 0xff4b, 0xec => 0xff4c, + 0xed => 0xff4d, 0xee => 0xff4e, 0xef => 0xff4f, 0xf0 => 0xff50, + 0xf1 => 0xff51, 0xf2 => 0xff52, 0xf3 => 0xff53, 0xf4 => 0xff54, + 0xf5 => 0xff55, 0xf6 => 0xff56, 0xf7 => 0xff57, 0xf8 => 0xff58, + 0xf9 => 0xff59, 0xfa => 0xff5a, 0xfb => 0xff5b, 0xfc => 0xff5c, + 0xfd => 0xff5d, 0xfe => 0xffe3, + }, + 0xa4 => { + 0xa1 => 0x3041, 0xa2 => 0x3042, 0xa3 => 0x3043, 0xa4 => 0x3044, + 0xa5 => 0x3045, 0xa6 => 0x3046, 0xa7 => 0x3047, 0xa8 => 0x3048, + 0xa9 => 0x3049, 0xaa => 0x304a, 0xab => 0x304b, 0xac => 0x304c, + 0xad => 0x304d, 0xae => 0x304e, 0xaf => 0x304f, 0xb0 => 0x3050, + 0xb1 => 0x3051, 0xb2 => 0x3052, 0xb3 => 0x3053, 0xb4 => 0x3054, + 0xb5 => 0x3055, 0xb6 => 0x3056, 0xb7 => 0x3057, 0xb8 => 0x3058, + 0xb9 => 0x3059, 0xba => 0x305a, 0xbb => 0x305b, 0xbc => 0x305c, + 0xbd => 0x305d, 0xbe => 0x305e, 0xbf => 0x305f, 0xc0 => 0x3060, + 0xc1 => 0x3061, 0xc2 => 0x3062, 0xc3 => 0x3063, 0xc4 => 0x3064, + 0xc5 => 0x3065, 0xc6 => 0x3066, 0xc7 => 0x3067, 0xc8 => 0x3068, + 0xc9 => 0x3069, 0xca => 0x306a, 0xcb => 0x306b, 0xcc => 0x306c, + 0xcd => 0x306d, 0xce => 0x306e, 0xcf => 0x306f, 0xd0 => 0x3070, + 0xd1 => 0x3071, 0xd2 => 0x3072, 0xd3 => 0x3073, 0xd4 => 0x3074, + 0xd5 => 0x3075, 0xd6 => 0x3076, 0xd7 => 0x3077, 0xd8 => 0x3078, + 0xd9 => 0x3079, 0xda => 0x307a, 0xdb => 0x307b, 0xdc => 0x307c, + 0xdd => 0x307d, 0xde => 0x307e, 0xdf => 0x307f, 0xe0 => 0x3080, + 0xe1 => 0x3081, 0xe2 => 0x3082, 0xe3 => 0x3083, 0xe4 => 0x3084, + 0xe5 => 0x3085, 0xe6 => 0x3086, 0xe7 => 0x3087, 0xe8 => 0x3088, + 0xe9 => 0x3089, 0xea => 0x308a, 0xeb => 0x308b, 0xec => 0x308c, + 0xed => 0x308d, 0xee => 0x308e, 0xef => 0x308f, 0xf0 => 0x3090, + 0xf1 => 0x3091, 0xf2 => 0x3092, 0xf3 => 0x3093, + }, + 0xa5 => { + 0xa1 => 0x30a1, 0xa2 => 0x30a2, 0xa3 => 0x30a3, 0xa4 => 0x30a4, + 0xa5 => 0x30a5, 0xa6 => 0x30a6, 0xa7 => 0x30a7, 0xa8 => 0x30a8, + 0xa9 => 0x30a9, 0xaa => 0x30aa, 0xab => 0x30ab, 0xac => 0x30ac, + 0xad => 0x30ad, 0xae => 0x30ae, 0xaf => 0x30af, 0xb0 => 0x30b0, + 0xb1 => 0x30b1, 0xb2 => 0x30b2, 0xb3 => 0x30b3, 0xb4 => 0x30b4, + 0xb5 => 0x30b5, 0xb6 => 0x30b6, 0xb7 => 0x30b7, 0xb8 => 0x30b8, + 0xb9 => 0x30b9, 0xba => 0x30ba, 0xbb => 0x30bb, 0xbc => 0x30bc, + 0xbd => 0x30bd, 0xbe => 0x30be, 0xbf => 0x30bf, 0xc0 => 0x30c0, + 0xc1 => 0x30c1, 0xc2 => 0x30c2, 0xc3 => 0x30c3, 0xc4 => 0x30c4, + 0xc5 => 0x30c5, 0xc6 => 0x30c6, 0xc7 => 0x30c7, 0xc8 => 0x30c8, + 0xc9 => 0x30c9, 0xca => 0x30ca, 0xcb => 0x30cb, 0xcc => 0x30cc, + 0xcd => 0x30cd, 0xce => 0x30ce, 0xcf => 0x30cf, 0xd0 => 0x30d0, + 0xd1 => 0x30d1, 0xd2 => 0x30d2, 0xd3 => 0x30d3, 0xd4 => 0x30d4, + 0xd5 => 0x30d5, 0xd6 => 0x30d6, 0xd7 => 0x30d7, 0xd8 => 0x30d8, + 0xd9 => 0x30d9, 0xda => 0x30da, 0xdb => 0x30db, 0xdc => 0x30dc, + 0xdd => 0x30dd, 0xde => 0x30de, 0xdf => 0x30df, 0xe0 => 0x30e0, + 0xe1 => 0x30e1, 0xe2 => 0x30e2, 0xe3 => 0x30e3, 0xe4 => 0x30e4, + 0xe5 => 0x30e5, 0xe6 => 0x30e6, 0xe7 => 0x30e7, 0xe8 => 0x30e8, + 0xe9 => 0x30e9, 0xea => 0x30ea, 0xeb => 0x30eb, 0xec => 0x30ec, + 0xed => 0x30ed, 0xee => 0x30ee, 0xef => 0x30ef, 0xf0 => 0x30f0, + 0xf1 => 0x30f1, 0xf2 => 0x30f2, 0xf3 => 0x30f3, 0xf4 => 0x30f4, + 0xf5 => 0x30f5, 0xf6 => 0x30f6, + }, + 0xa6 => { + 0xa1 => 0x0391, 0xa2 => 0x0392, 0xa3 => 0x0393, 0xa4 => 0x0394, + 0xa5 => 0x0395, 0xa6 => 0x0396, 0xa7 => 0x0397, 0xa8 => 0x0398, + 0xa9 => 0x0399, 0xaa => 0x039a, 0xab => 0x039b, 0xac => 0x039c, + 0xad => 0x039d, 0xae => 0x039e, 0xaf => 0x039f, 0xb0 => 0x03a0, + 0xb1 => 0x03a1, 0xb2 => 0x03a3, 0xb3 => 0x03a4, 0xb4 => 0x03a5, + 0xb5 => 0x03a6, 0xb6 => 0x03a7, 0xb7 => 0x03a8, 0xb8 => 0x03a9, + 0xc1 => 0x03b1, 0xc2 => 0x03b2, 0xc3 => 0x03b3, 0xc4 => 0x03b4, + 0xc5 => 0x03b5, 0xc6 => 0x03b6, 0xc7 => 0x03b7, 0xc8 => 0x03b8, + 0xc9 => 0x03b9, 0xca => 0x03ba, 0xcb => 0x03bb, 0xcc => 0x03bc, + 0xcd => 0x03bd, 0xce => 0x03be, 0xcf => 0x03bf, 0xd0 => 0x03c0, + 0xd1 => 0x03c1, 0xd2 => 0x03c3, 0xd3 => 0x03c4, 0xd4 => 0x03c5, + 0xd5 => 0x03c6, 0xd6 => 0x03c7, 0xd7 => 0x03c8, 0xd8 => 0x03c9, + }, + 0xa7 => { + 0xa1 => 0x0410, 0xa2 => 0x0411, 0xa3 => 0x0412, 0xa4 => 0x0413, + 0xa5 => 0x0414, 0xa6 => 0x0415, 0xa7 => 0x0401, 0xa8 => 0x0416, + 0xa9 => 0x0417, 0xaa => 0x0418, 0xab => 0x0419, 0xac => 0x041a, + 0xad => 0x041b, 0xae => 0x041c, 0xaf => 0x041d, 0xb0 => 0x041e, + 0xb1 => 0x041f, 0xb2 => 0x0420, 0xb3 => 0x0421, 0xb4 => 0x0422, + 0xb5 => 0x0423, 0xb6 => 0x0424, 0xb7 => 0x0425, 0xb8 => 0x0426, + 0xb9 => 0x0427, 0xba => 0x0428, 0xbb => 0x0429, 0xbc => 0x042a, + 0xbd => 0x042b, 0xbe => 0x042c, 0xbf => 0x042d, 0xc0 => 0x042e, + 0xc1 => 0x042f, 0xd1 => 0x0430, 0xd2 => 0x0431, 0xd3 => 0x0432, + 0xd4 => 0x0433, 0xd5 => 0x0434, 0xd6 => 0x0435, 0xd7 => 0x0451, + 0xd8 => 0x0436, 0xd9 => 0x0437, 0xda => 0x0438, 0xdb => 0x0439, + 0xdc => 0x043a, 0xdd => 0x043b, 0xde => 0x043c, 0xdf => 0x043d, + 0xe0 => 0x043e, 0xe1 => 0x043f, 0xe2 => 0x0440, 0xe3 => 0x0441, + 0xe4 => 0x0442, 0xe5 => 0x0443, 0xe6 => 0x0444, 0xe7 => 0x0445, + 0xe8 => 0x0446, 0xe9 => 0x0447, 0xea => 0x0448, 0xeb => 0x0449, + 0xec => 0x044a, 0xed => 0x044b, 0xee => 0x044c, 0xef => 0x044d, + 0xf0 => 0x044e, 0xf1 => 0x044f, + }, + 0xa8 => { + 0xa1 => 0x0101, 0xa2 => 0xe1, 0xa3 => 0x01ce, 0xa4 => 0xe0, 0xa5 => 0x0113, + 0xa6 => 0xe9, 0xa7 => 0x011b, 0xa8 => 0xe8, 0xa9 => 0x012b, 0xaa => 0xed, + 0xab => 0x01d0, 0xac => 0xec, 0xad => 0x014d, 0xae => 0xf3, 0xaf => 0x01d2, + 0xb0 => 0xf2, 0xb1 => 0x016b, 0xb2 => 0xfa, 0xb3 => 0x01d4, 0xb4 => 0xf9, + 0xb5 => 0x01d6, 0xb6 => 0x01d8, 0xb7 => 0x01da, 0xb8 => 0x01dc, + 0xb9 => 0xfc, 0xba => 0xea, 0xc5 => 0x3105, 0xc6 => 0x3106, 0xc7 => 0x3107, + 0xc8 => 0x3108, 0xc9 => 0x3109, 0xca => 0x310a, 0xcb => 0x310b, + 0xcc => 0x310c, 0xcd => 0x310d, 0xce => 0x310e, 0xcf => 0x310f, + 0xd0 => 0x3110, 0xd1 => 0x3111, 0xd2 => 0x3112, 0xd3 => 0x3113, + 0xd4 => 0x3114, 0xd5 => 0x3115, 0xd6 => 0x3116, 0xd7 => 0x3117, + 0xd8 => 0x3118, 0xd9 => 0x3119, 0xda => 0x311a, 0xdb => 0x311b, + 0xdc => 0x311c, 0xdd => 0x311d, 0xde => 0x311e, 0xdf => 0x311f, + 0xe0 => 0x3120, 0xe1 => 0x3121, 0xe2 => 0x3122, 0xe3 => 0x3123, + 0xe4 => 0x3124, 0xe5 => 0x3125, 0xe6 => 0x3126, 0xe7 => 0x3127, + 0xe8 => 0x3128, 0xe9 => 0x3129, + }, + 0xa9 => { + 0xa4 => 0x2500, 0xa5 => 0x2501, 0xa6 => 0x2502, 0xa7 => 0x2503, + 0xa8 => 0x2504, 0xa9 => 0x2505, 0xaa => 0x2506, 0xab => 0x2507, + 0xac => 0x2508, 0xad => 0x2509, 0xae => 0x250a, 0xaf => 0x250b, + 0xb0 => 0x250c, 0xb1 => 0x250d, 0xb2 => 0x250e, 0xb3 => 0x250f, + 0xb4 => 0x2510, 0xb5 => 0x2511, 0xb6 => 0x2512, 0xb7 => 0x2513, + 0xb8 => 0x2514, 0xb9 => 0x2515, 0xba => 0x2516, 0xbb => 0x2517, + 0xbc => 0x2518, 0xbd => 0x2519, 0xbe => 0x251a, 0xbf => 0x251b, + 0xc0 => 0x251c, 0xc1 => 0x251d, 0xc2 => 0x251e, 0xc3 => 0x251f, + 0xc4 => 0x2520, 0xc5 => 0x2521, 0xc6 => 0x2522, 0xc7 => 0x2523, + 0xc8 => 0x2524, 0xc9 => 0x2525, 0xca => 0x2526, 0xcb => 0x2527, + 0xcc => 0x2528, 0xcd => 0x2529, 0xce => 0x252a, 0xcf => 0x252b, + 0xd0 => 0x252c, 0xd1 => 0x252d, 0xd2 => 0x252e, 0xd3 => 0x252f, + 0xd4 => 0x2530, 0xd5 => 0x2531, 0xd6 => 0x2532, 0xd7 => 0x2533, + 0xd8 => 0x2534, 0xd9 => 0x2535, 0xda => 0x2536, 0xdb => 0x2537, + 0xdc => 0x2538, 0xdd => 0x2539, 0xde => 0x253a, 0xdf => 0x253b, + 0xe0 => 0x253c, 0xe1 => 0x253d, 0xe2 => 0x253e, 0xe3 => 0x253f, + 0xe4 => 0x2540, 0xe5 => 0x2541, 0xe6 => 0x2542, 0xe7 => 0x2543, + 0xe8 => 0x2544, 0xe9 => 0x2545, 0xea => 0x2546, 0xeb => 0x2547, + 0xec => 0x2548, 0xed => 0x2549, 0xee => 0x254a, 0xef => 0x254b, + }, + 0xb0 => { + 0xa1 => 0x554a, 0xa2 => 0x963f, 0xa3 => 0x57c3, 0xa4 => 0x6328, + 0xa5 => 0x54ce, 0xa6 => 0x5509, 0xa7 => 0x54c0, 0xa8 => 0x7691, + 0xa9 => 0x764c, 0xaa => 0x853c, 0xab => 0x77ee, 0xac => 0x827e, + 0xad => 0x788d, 0xae => 0x7231, 0xaf => 0x9698, 0xb0 => 0x978d, + 0xb1 => 0x6c28, 0xb2 => 0x5b89, 0xb3 => 0x4ffa, 0xb4 => 0x6309, + 0xb5 => 0x6697, 0xb6 => 0x5cb8, 0xb7 => 0x80fa, 0xb8 => 0x6848, + 0xb9 => 0x80ae, 0xba => 0x6602, 0xbb => 0x76ce, 0xbc => 0x51f9, + 0xbd => 0x6556, 0xbe => 0x71ac, 0xbf => 0x7ff1, 0xc0 => 0x8884, + 0xc1 => 0x50b2, 0xc2 => 0x5965, 0xc3 => 0x61ca, 0xc4 => 0x6fb3, + 0xc5 => 0x82ad, 0xc6 => 0x634c, 0xc7 => 0x6252, 0xc8 => 0x53ed, + 0xc9 => 0x5427, 0xca => 0x7b06, 0xcb => 0x516b, 0xcc => 0x75a4, + 0xcd => 0x5df4, 0xce => 0x62d4, 0xcf => 0x8dcb, 0xd0 => 0x9776, + 0xd1 => 0x628a, 0xd2 => 0x8019, 0xd3 => 0x575d, 0xd4 => 0x9738, + 0xd5 => 0x7f62, 0xd6 => 0x7238, 0xd7 => 0x767d, 0xd8 => 0x67cf, + 0xd9 => 0x767e, 0xda => 0x6446, 0xdb => 0x4f70, 0xdc => 0x8d25, + 0xdd => 0x62dc, 0xde => 0x7a17, 0xdf => 0x6591, 0xe0 => 0x73ed, + 0xe1 => 0x642c, 0xe2 => 0x6273, 0xe3 => 0x822c, 0xe4 => 0x9881, + 0xe5 => 0x677f, 0xe6 => 0x7248, 0xe7 => 0x626e, 0xe8 => 0x62cc, + 0xe9 => 0x4f34, 0xea => 0x74e3, 0xeb => 0x534a, 0xec => 0x529e, + 0xed => 0x7eca, 0xee => 0x90a6, 0xef => 0x5e2e, 0xf0 => 0x6886, + 0xf1 => 0x699c, 0xf2 => 0x8180, 0xf3 => 0x7ed1, 0xf4 => 0x68d2, + 0xf5 => 0x78c5, 0xf6 => 0x868c, 0xf7 => 0x9551, 0xf8 => 0x508d, + 0xf9 => 0x8c24, 0xfa => 0x82de, 0xfb => 0x80de, 0xfc => 0x5305, + 0xfd => 0x8912, 0xfe => 0x5265, + }, + 0xb1 => { + 0xa1 => 0x8584, 0xa2 => 0x96f9, 0xa3 => 0x4fdd, 0xa4 => 0x5821, + 0xa5 => 0x9971, 0xa6 => 0x5b9d, 0xa7 => 0x62b1, 0xa8 => 0x62a5, + 0xa9 => 0x66b4, 0xaa => 0x8c79, 0xab => 0x9c8d, 0xac => 0x7206, + 0xad => 0x676f, 0xae => 0x7891, 0xaf => 0x60b2, 0xb0 => 0x5351, + 0xb1 => 0x5317, 0xb2 => 0x8f88, 0xb3 => 0x80cc, 0xb4 => 0x8d1d, + 0xb5 => 0x94a1, 0xb6 => 0x500d, 0xb7 => 0x72c8, 0xb8 => 0x5907, + 0xb9 => 0x60eb, 0xba => 0x7119, 0xbb => 0x88ab, 0xbc => 0x5954, + 0xbd => 0x82ef, 0xbe => 0x672c, 0xbf => 0x7b28, 0xc0 => 0x5d29, + 0xc1 => 0x7ef7, 0xc2 => 0x752d, 0xc3 => 0x6cf5, 0xc4 => 0x8e66, + 0xc5 => 0x8ff8, 0xc6 => 0x903c, 0xc7 => 0x9f3b, 0xc8 => 0x6bd4, + 0xc9 => 0x9119, 0xca => 0x7b14, 0xcb => 0x5f7c, 0xcc => 0x78a7, + 0xcd => 0x84d6, 0xce => 0x853d, 0xcf => 0x6bd5, 0xd0 => 0x6bd9, + 0xd1 => 0x6bd6, 0xd2 => 0x5e01, 0xd3 => 0x5e87, 0xd4 => 0x75f9, + 0xd5 => 0x95ed, 0xd6 => 0x655d, 0xd7 => 0x5f0a, 0xd8 => 0x5fc5, + 0xd9 => 0x8f9f, 0xda => 0x58c1, 0xdb => 0x81c2, 0xdc => 0x907f, + 0xdd => 0x965b, 0xde => 0x97ad, 0xdf => 0x8fb9, 0xe0 => 0x7f16, + 0xe1 => 0x8d2c, 0xe2 => 0x6241, 0xe3 => 0x4fbf, 0xe4 => 0x53d8, + 0xe5 => 0x535e, 0xe6 => 0x8fa8, 0xe7 => 0x8fa9, 0xe8 => 0x8fab, + 0xe9 => 0x904d, 0xea => 0x6807, 0xeb => 0x5f6a, 0xec => 0x8198, + 0xed => 0x8868, 0xee => 0x9cd6, 0xef => 0x618b, 0xf0 => 0x522b, + 0xf1 => 0x762a, 0xf2 => 0x5f6c, 0xf3 => 0x658c, 0xf4 => 0x6fd2, + 0xf5 => 0x6ee8, 0xf6 => 0x5bbe, 0xf7 => 0x6448, 0xf8 => 0x5175, + 0xf9 => 0x51b0, 0xfa => 0x67c4, 0xfb => 0x4e19, 0xfc => 0x79c9, + 0xfd => 0x997c, 0xfe => 0x70b3, + }, + 0xb2 => { + 0xa1 => 0x75c5, 0xa2 => 0x5e76, 0xa3 => 0x73bb, 0xa4 => 0x83e0, + 0xa5 => 0x64ad, 0xa6 => 0x62e8, 0xa7 => 0x94b5, 0xa8 => 0x6ce2, + 0xa9 => 0x535a, 0xaa => 0x52c3, 0xab => 0x640f, 0xac => 0x94c2, + 0xad => 0x7b94, 0xae => 0x4f2f, 0xaf => 0x5e1b, 0xb0 => 0x8236, + 0xb1 => 0x8116, 0xb2 => 0x818a, 0xb3 => 0x6e24, 0xb4 => 0x6cca, + 0xb5 => 0x9a73, 0xb6 => 0x6355, 0xb7 => 0x535c, 0xb8 => 0x54fa, + 0xb9 => 0x8865, 0xba => 0x57e0, 0xbb => 0x4e0d, 0xbc => 0x5e03, + 0xbd => 0x6b65, 0xbe => 0x7c3f, 0xbf => 0x90e8, 0xc0 => 0x6016, + 0xc1 => 0x64e6, 0xc2 => 0x731c, 0xc3 => 0x88c1, 0xc4 => 0x6750, + 0xc5 => 0x624d, 0xc6 => 0x8d22, 0xc7 => 0x776c, 0xc8 => 0x8e29, + 0xc9 => 0x91c7, 0xca => 0x5f69, 0xcb => 0x83dc, 0xcc => 0x8521, + 0xcd => 0x9910, 0xce => 0x53c2, 0xcf => 0x8695, 0xd0 => 0x6b8b, + 0xd1 => 0x60ed, 0xd2 => 0x60e8, 0xd3 => 0x707f, 0xd4 => 0x82cd, + 0xd5 => 0x8231, 0xd6 => 0x4ed3, 0xd7 => 0x6ca7, 0xd8 => 0x85cf, + 0xd9 => 0x64cd, 0xda => 0x7cd9, 0xdb => 0x69fd, 0xdc => 0x66f9, + 0xdd => 0x8349, 0xde => 0x5395, 0xdf => 0x7b56, 0xe0 => 0x4fa7, + 0xe1 => 0x518c, 0xe2 => 0x6d4b, 0xe3 => 0x5c42, 0xe4 => 0x8e6d, + 0xe5 => 0x63d2, 0xe6 => 0x53c9, 0xe7 => 0x832c, 0xe8 => 0x8336, + 0xe9 => 0x67e5, 0xea => 0x78b4, 0xeb => 0x643d, 0xec => 0x5bdf, + 0xed => 0x5c94, 0xee => 0x5dee, 0xef => 0x8be7, 0xf0 => 0x62c6, + 0xf1 => 0x67f4, 0xf2 => 0x8c7a, 0xf3 => 0x6400, 0xf4 => 0x63ba, + 0xf5 => 0x8749, 0xf6 => 0x998b, 0xf7 => 0x8c17, 0xf8 => 0x7f20, + 0xf9 => 0x94f2, 0xfa => 0x4ea7, 0xfb => 0x9610, 0xfc => 0x98a4, + 0xfd => 0x660c, 0xfe => 0x7316, + }, + 0xb3 => { + 0xa1 => 0x573a, 0xa2 => 0x5c1d, 0xa3 => 0x5e38, 0xa4 => 0x957f, + 0xa5 => 0x507f, 0xa6 => 0x80a0, 0xa7 => 0x5382, 0xa8 => 0x655e, + 0xa9 => 0x7545, 0xaa => 0x5531, 0xab => 0x5021, 0xac => 0x8d85, + 0xad => 0x6284, 0xae => 0x949e, 0xaf => 0x671d, 0xb0 => 0x5632, + 0xb1 => 0x6f6e, 0xb2 => 0x5de2, 0xb3 => 0x5435, 0xb4 => 0x7092, + 0xb5 => 0x8f66, 0xb6 => 0x626f, 0xb7 => 0x64a4, 0xb8 => 0x63a3, + 0xb9 => 0x5f7b, 0xba => 0x6f88, 0xbb => 0x90f4, 0xbc => 0x81e3, + 0xbd => 0x8fb0, 0xbe => 0x5c18, 0xbf => 0x6668, 0xc0 => 0x5ff1, + 0xc1 => 0x6c89, 0xc2 => 0x9648, 0xc3 => 0x8d81, 0xc4 => 0x886c, + 0xc5 => 0x6491, 0xc6 => 0x79f0, 0xc7 => 0x57ce, 0xc8 => 0x6a59, + 0xc9 => 0x6210, 0xca => 0x5448, 0xcb => 0x4e58, 0xcc => 0x7a0b, + 0xcd => 0x60e9, 0xce => 0x6f84, 0xcf => 0x8bda, 0xd0 => 0x627f, + 0xd1 => 0x901e, 0xd2 => 0x9a8b, 0xd3 => 0x79e4, 0xd4 => 0x5403, + 0xd5 => 0x75f4, 0xd6 => 0x6301, 0xd7 => 0x5319, 0xd8 => 0x6c60, + 0xd9 => 0x8fdf, 0xda => 0x5f1b, 0xdb => 0x9a70, 0xdc => 0x803b, + 0xdd => 0x9f7f, 0xde => 0x4f88, 0xdf => 0x5c3a, 0xe0 => 0x8d64, + 0xe1 => 0x7fc5, 0xe2 => 0x65a5, 0xe3 => 0x70bd, 0xe4 => 0x5145, + 0xe5 => 0x51b2, 0xe6 => 0x866b, 0xe7 => 0x5d07, 0xe8 => 0x5ba0, + 0xe9 => 0x62bd, 0xea => 0x916c, 0xeb => 0x7574, 0xec => 0x8e0c, + 0xed => 0x7a20, 0xee => 0x6101, 0xef => 0x7b79, 0xf0 => 0x4ec7, + 0xf1 => 0x7ef8, 0xf2 => 0x7785, 0xf3 => 0x4e11, 0xf4 => 0x81ed, + 0xf5 => 0x521d, 0xf6 => 0x51fa, 0xf7 => 0x6a71, 0xf8 => 0x53a8, + 0xf9 => 0x8e87, 0xfa => 0x9504, 0xfb => 0x96cf, 0xfc => 0x6ec1, + 0xfd => 0x9664, 0xfe => 0x695a, + }, + 0xb4 => { + 0xa1 => 0x7840, 0xa2 => 0x50a8, 0xa3 => 0x77d7, 0xa4 => 0x6410, + 0xa5 => 0x89e6, 0xa6 => 0x5904, 0xa7 => 0x63e3, 0xa8 => 0x5ddd, + 0xa9 => 0x7a7f, 0xaa => 0x693d, 0xab => 0x4f20, 0xac => 0x8239, + 0xad => 0x5598, 0xae => 0x4e32, 0xaf => 0x75ae, 0xb0 => 0x7a97, + 0xb1 => 0x5e62, 0xb2 => 0x5e8a, 0xb3 => 0x95ef, 0xb4 => 0x521b, + 0xb5 => 0x5439, 0xb6 => 0x708a, 0xb7 => 0x6376, 0xb8 => 0x9524, + 0xb9 => 0x5782, 0xba => 0x6625, 0xbb => 0x693f, 0xbc => 0x9187, + 0xbd => 0x5507, 0xbe => 0x6df3, 0xbf => 0x7eaf, 0xc0 => 0x8822, + 0xc1 => 0x6233, 0xc2 => 0x7ef0, 0xc3 => 0x75b5, 0xc4 => 0x8328, + 0xc5 => 0x78c1, 0xc6 => 0x96cc, 0xc7 => 0x8f9e, 0xc8 => 0x6148, + 0xc9 => 0x74f7, 0xca => 0x8bcd, 0xcb => 0x6b64, 0xcc => 0x523a, + 0xcd => 0x8d50, 0xce => 0x6b21, 0xcf => 0x806a, 0xd0 => 0x8471, + 0xd1 => 0x56f1, 0xd2 => 0x5306, 0xd3 => 0x4ece, 0xd4 => 0x4e1b, + 0xd5 => 0x51d1, 0xd6 => 0x7c97, 0xd7 => 0x918b, 0xd8 => 0x7c07, + 0xd9 => 0x4fc3, 0xda => 0x8e7f, 0xdb => 0x7be1, 0xdc => 0x7a9c, + 0xdd => 0x6467, 0xde => 0x5d14, 0xdf => 0x50ac, 0xe0 => 0x8106, + 0xe1 => 0x7601, 0xe2 => 0x7cb9, 0xe3 => 0x6dec, 0xe4 => 0x7fe0, + 0xe5 => 0x6751, 0xe6 => 0x5b58, 0xe7 => 0x5bf8, 0xe8 => 0x78cb, + 0xe9 => 0x64ae, 0xea => 0x6413, 0xeb => 0x63aa, 0xec => 0x632b, + 0xed => 0x9519, 0xee => 0x642d, 0xef => 0x8fbe, 0xf0 => 0x7b54, + 0xf1 => 0x7629, 0xf2 => 0x6253, 0xf3 => 0x5927, 0xf4 => 0x5446, + 0xf5 => 0x6b79, 0xf6 => 0x50a3, 0xf7 => 0x6234, 0xf8 => 0x5e26, + 0xf9 => 0x6b86, 0xfa => 0x4ee3, 0xfb => 0x8d37, 0xfc => 0x888b, + 0xfd => 0x5f85, 0xfe => 0x902e, + }, + 0xb5 => { + 0xa1 => 0x6020, 0xa2 => 0x803d, 0xa3 => 0x62c5, 0xa4 => 0x4e39, + 0xa5 => 0x5355, 0xa6 => 0x90f8, 0xa7 => 0x63b8, 0xa8 => 0x80c6, + 0xa9 => 0x65e6, 0xaa => 0x6c2e, 0xab => 0x4f46, 0xac => 0x60ee, + 0xad => 0x6de1, 0xae => 0x8bde, 0xaf => 0x5f39, 0xb0 => 0x86cb, + 0xb1 => 0x5f53, 0xb2 => 0x6321, 0xb3 => 0x515a, 0xb4 => 0x8361, + 0xb5 => 0x6863, 0xb6 => 0x5200, 0xb7 => 0x6363, 0xb8 => 0x8e48, + 0xb9 => 0x5012, 0xba => 0x5c9b, 0xbb => 0x7977, 0xbc => 0x5bfc, + 0xbd => 0x5230, 0xbe => 0x7a3b, 0xbf => 0x60bc, 0xc0 => 0x9053, + 0xc1 => 0x76d7, 0xc2 => 0x5fb7, 0xc3 => 0x5f97, 0xc4 => 0x7684, + 0xc5 => 0x8e6c, 0xc6 => 0x706f, 0xc7 => 0x767b, 0xc8 => 0x7b49, + 0xc9 => 0x77aa, 0xca => 0x51f3, 0xcb => 0x9093, 0xcc => 0x5824, + 0xcd => 0x4f4e, 0xce => 0x6ef4, 0xcf => 0x8fea, 0xd0 => 0x654c, + 0xd1 => 0x7b1b, 0xd2 => 0x72c4, 0xd3 => 0x6da4, 0xd4 => 0x7fdf, + 0xd5 => 0x5ae1, 0xd6 => 0x62b5, 0xd7 => 0x5e95, 0xd8 => 0x5730, + 0xd9 => 0x8482, 0xda => 0x7b2c, 0xdb => 0x5e1d, 0xdc => 0x5f1f, + 0xdd => 0x9012, 0xde => 0x7f14, 0xdf => 0x98a0, 0xe0 => 0x6382, + 0xe1 => 0x6ec7, 0xe2 => 0x7898, 0xe3 => 0x70b9, 0xe4 => 0x5178, + 0xe5 => 0x975b, 0xe6 => 0x57ab, 0xe7 => 0x7535, 0xe8 => 0x4f43, + 0xe9 => 0x7538, 0xea => 0x5e97, 0xeb => 0x60e6, 0xec => 0x5960, + 0xed => 0x6dc0, 0xee => 0x6bbf, 0xef => 0x7889, 0xf0 => 0x53fc, + 0xf1 => 0x96d5, 0xf2 => 0x51cb, 0xf3 => 0x5201, 0xf4 => 0x6389, + 0xf5 => 0x540a, 0xf6 => 0x9493, 0xf7 => 0x8c03, 0xf8 => 0x8dcc, + 0xf9 => 0x7239, 0xfa => 0x789f, 0xfb => 0x8776, 0xfc => 0x8fed, + 0xfd => 0x8c0d, 0xfe => 0x53e0, + }, + 0xb6 => { + 0xa1 => 0x4e01, 0xa2 => 0x76ef, 0xa3 => 0x53ee, 0xa4 => 0x9489, + 0xa5 => 0x9876, 0xa6 => 0x9f0e, 0xa7 => 0x952d, 0xa8 => 0x5b9a, + 0xa9 => 0x8ba2, 0xaa => 0x4e22, 0xab => 0x4e1c, 0xac => 0x51ac, + 0xad => 0x8463, 0xae => 0x61c2, 0xaf => 0x52a8, 0xb0 => 0x680b, + 0xb1 => 0x4f97, 0xb2 => 0x606b, 0xb3 => 0x51bb, 0xb4 => 0x6d1e, + 0xb5 => 0x515c, 0xb6 => 0x6296, 0xb7 => 0x6597, 0xb8 => 0x9661, + 0xb9 => 0x8c46, 0xba => 0x9017, 0xbb => 0x75d8, 0xbc => 0x90fd, + 0xbd => 0x7763, 0xbe => 0x6bd2, 0xbf => 0x728a, 0xc0 => 0x72ec, + 0xc1 => 0x8bfb, 0xc2 => 0x5835, 0xc3 => 0x7779, 0xc4 => 0x8d4c, + 0xc5 => 0x675c, 0xc6 => 0x9540, 0xc7 => 0x809a, 0xc8 => 0x5ea6, + 0xc9 => 0x6e21, 0xca => 0x5992, 0xcb => 0x7aef, 0xcc => 0x77ed, + 0xcd => 0x953b, 0xce => 0x6bb5, 0xcf => 0x65ad, 0xd0 => 0x7f0e, + 0xd1 => 0x5806, 0xd2 => 0x5151, 0xd3 => 0x961f, 0xd4 => 0x5bf9, + 0xd5 => 0x58a9, 0xd6 => 0x5428, 0xd7 => 0x8e72, 0xd8 => 0x6566, + 0xd9 => 0x987f, 0xda => 0x56e4, 0xdb => 0x949d, 0xdc => 0x76fe, + 0xdd => 0x9041, 0xde => 0x6387, 0xdf => 0x54c6, 0xe0 => 0x591a, + 0xe1 => 0x593a, 0xe2 => 0x579b, 0xe3 => 0x8eb2, 0xe4 => 0x6735, + 0xe5 => 0x8dfa, 0xe6 => 0x8235, 0xe7 => 0x5241, 0xe8 => 0x60f0, + 0xe9 => 0x5815, 0xea => 0x86fe, 0xeb => 0x5ce8, 0xec => 0x9e45, + 0xed => 0x4fc4, 0xee => 0x989d, 0xef => 0x8bb9, 0xf0 => 0x5a25, + 0xf1 => 0x6076, 0xf2 => 0x5384, 0xf3 => 0x627c, 0xf4 => 0x904f, + 0xf5 => 0x9102, 0xf6 => 0x997f, 0xf7 => 0x6069, 0xf8 => 0x800c, + 0xf9 => 0x513f, 0xfa => 0x8033, 0xfb => 0x5c14, 0xfc => 0x9975, + 0xfd => 0x6d31, 0xfe => 0x4e8c, + }, + 0xb7 => { + 0xa1 => 0x8d30, 0xa2 => 0x53d1, 0xa3 => 0x7f5a, 0xa4 => 0x7b4f, + 0xa5 => 0x4f10, 0xa6 => 0x4e4f, 0xa7 => 0x9600, 0xa8 => 0x6cd5, + 0xa9 => 0x73d0, 0xaa => 0x85e9, 0xab => 0x5e06, 0xac => 0x756a, + 0xad => 0x7ffb, 0xae => 0x6a0a, 0xaf => 0x77fe, 0xb0 => 0x9492, + 0xb1 => 0x7e41, 0xb2 => 0x51e1, 0xb3 => 0x70e6, 0xb4 => 0x53cd, + 0xb5 => 0x8fd4, 0xb6 => 0x8303, 0xb7 => 0x8d29, 0xb8 => 0x72af, + 0xb9 => 0x996d, 0xba => 0x6cdb, 0xbb => 0x574a, 0xbc => 0x82b3, + 0xbd => 0x65b9, 0xbe => 0x80aa, 0xbf => 0x623f, 0xc0 => 0x9632, + 0xc1 => 0x59a8, 0xc2 => 0x4eff, 0xc3 => 0x8bbf, 0xc4 => 0x7eba, + 0xc5 => 0x653e, 0xc6 => 0x83f2, 0xc7 => 0x975e, 0xc8 => 0x5561, + 0xc9 => 0x98de, 0xca => 0x80a5, 0xcb => 0x532a, 0xcc => 0x8bfd, + 0xcd => 0x5420, 0xce => 0x80ba, 0xcf => 0x5e9f, 0xd0 => 0x6cb8, + 0xd1 => 0x8d39, 0xd2 => 0x82ac, 0xd3 => 0x915a, 0xd4 => 0x5429, + 0xd5 => 0x6c1b, 0xd6 => 0x5206, 0xd7 => 0x7eb7, 0xd8 => 0x575f, + 0xd9 => 0x711a, 0xda => 0x6c7e, 0xdb => 0x7c89, 0xdc => 0x594b, + 0xdd => 0x4efd, 0xde => 0x5fff, 0xdf => 0x6124, 0xe0 => 0x7caa, + 0xe1 => 0x4e30, 0xe2 => 0x5c01, 0xe3 => 0x67ab, 0xe4 => 0x8702, + 0xe5 => 0x5cf0, 0xe6 => 0x950b, 0xe7 => 0x98ce, 0xe8 => 0x75af, + 0xe9 => 0x70fd, 0xea => 0x9022, 0xeb => 0x51af, 0xec => 0x7f1d, + 0xed => 0x8bbd, 0xee => 0x5949, 0xef => 0x51e4, 0xf0 => 0x4f5b, + 0xf1 => 0x5426, 0xf2 => 0x592b, 0xf3 => 0x6577, 0xf4 => 0x80a4, + 0xf5 => 0x5b75, 0xf6 => 0x6276, 0xf7 => 0x62c2, 0xf8 => 0x8f90, + 0xf9 => 0x5e45, 0xfa => 0x6c1f, 0xfb => 0x7b26, 0xfc => 0x4f0f, + 0xfd => 0x4fd8, 0xfe => 0x670d, + }, + 0xb8 => { + 0xa1 => 0x6d6e, 0xa2 => 0x6daa, 0xa3 => 0x798f, 0xa4 => 0x88b1, + 0xa5 => 0x5f17, 0xa6 => 0x752b, 0xa7 => 0x629a, 0xa8 => 0x8f85, + 0xa9 => 0x4fef, 0xaa => 0x91dc, 0xab => 0x65a7, 0xac => 0x812f, + 0xad => 0x8151, 0xae => 0x5e9c, 0xaf => 0x8150, 0xb0 => 0x8d74, + 0xb1 => 0x526f, 0xb2 => 0x8986, 0xb3 => 0x8d4b, 0xb4 => 0x590d, + 0xb5 => 0x5085, 0xb6 => 0x4ed8, 0xb7 => 0x961c, 0xb8 => 0x7236, + 0xb9 => 0x8179, 0xba => 0x8d1f, 0xbb => 0x5bcc, 0xbc => 0x8ba3, + 0xbd => 0x9644, 0xbe => 0x5987, 0xbf => 0x7f1a, 0xc0 => 0x5490, + 0xc1 => 0x5676, 0xc2 => 0x560e, 0xc3 => 0x8be5, 0xc4 => 0x6539, + 0xc5 => 0x6982, 0xc6 => 0x9499, 0xc7 => 0x76d6, 0xc8 => 0x6e89, + 0xc9 => 0x5e72, 0xca => 0x7518, 0xcb => 0x6746, 0xcc => 0x67d1, + 0xcd => 0x7aff, 0xce => 0x809d, 0xcf => 0x8d76, 0xd0 => 0x611f, + 0xd1 => 0x79c6, 0xd2 => 0x6562, 0xd3 => 0x8d63, 0xd4 => 0x5188, + 0xd5 => 0x521a, 0xd6 => 0x94a2, 0xd7 => 0x7f38, 0xd8 => 0x809b, + 0xd9 => 0x7eb2, 0xda => 0x5c97, 0xdb => 0x6e2f, 0xdc => 0x6760, + 0xdd => 0x7bd9, 0xde => 0x768b, 0xdf => 0x9ad8, 0xe0 => 0x818f, + 0xe1 => 0x7f94, 0xe2 => 0x7cd5, 0xe3 => 0x641e, 0xe4 => 0x9550, + 0xe5 => 0x7a3f, 0xe6 => 0x544a, 0xe7 => 0x54e5, 0xe8 => 0x6b4c, + 0xe9 => 0x6401, 0xea => 0x6208, 0xeb => 0x9e3d, 0xec => 0x80f3, + 0xed => 0x7599, 0xee => 0x5272, 0xef => 0x9769, 0xf0 => 0x845b, + 0xf1 => 0x683c, 0xf2 => 0x86e4, 0xf3 => 0x9601, 0xf4 => 0x9694, + 0xf5 => 0x94ec, 0xf6 => 0x4e2a, 0xf7 => 0x5404, 0xf8 => 0x7ed9, + 0xf9 => 0x6839, 0xfa => 0x8ddf, 0xfb => 0x8015, 0xfc => 0x66f4, + 0xfd => 0x5e9a, 0xfe => 0x7fb9, + }, + 0xb9 => { + 0xa1 => 0x57c2, 0xa2 => 0x803f, 0xa3 => 0x6897, 0xa4 => 0x5de5, + 0xa5 => 0x653b, 0xa6 => 0x529f, 0xa7 => 0x606d, 0xa8 => 0x9f9a, + 0xa9 => 0x4f9b, 0xaa => 0x8eac, 0xab => 0x516c, 0xac => 0x5bab, + 0xad => 0x5f13, 0xae => 0x5de9, 0xaf => 0x6c5e, 0xb0 => 0x62f1, + 0xb1 => 0x8d21, 0xb2 => 0x5171, 0xb3 => 0x94a9, 0xb4 => 0x52fe, + 0xb5 => 0x6c9f, 0xb6 => 0x82df, 0xb7 => 0x72d7, 0xb8 => 0x57a2, + 0xb9 => 0x6784, 0xba => 0x8d2d, 0xbb => 0x591f, 0xbc => 0x8f9c, + 0xbd => 0x83c7, 0xbe => 0x5495, 0xbf => 0x7b8d, 0xc0 => 0x4f30, + 0xc1 => 0x6cbd, 0xc2 => 0x5b64, 0xc3 => 0x59d1, 0xc4 => 0x9f13, + 0xc5 => 0x53e4, 0xc6 => 0x86ca, 0xc7 => 0x9aa8, 0xc8 => 0x8c37, + 0xc9 => 0x80a1, 0xca => 0x6545, 0xcb => 0x987e, 0xcc => 0x56fa, + 0xcd => 0x96c7, 0xce => 0x522e, 0xcf => 0x74dc, 0xd0 => 0x5250, + 0xd1 => 0x5be1, 0xd2 => 0x6302, 0xd3 => 0x8902, 0xd4 => 0x4e56, + 0xd5 => 0x62d0, 0xd6 => 0x602a, 0xd7 => 0x68fa, 0xd8 => 0x5173, + 0xd9 => 0x5b98, 0xda => 0x51a0, 0xdb => 0x89c2, 0xdc => 0x7ba1, + 0xdd => 0x9986, 0xde => 0x7f50, 0xdf => 0x60ef, 0xe0 => 0x704c, + 0xe1 => 0x8d2f, 0xe2 => 0x5149, 0xe3 => 0x5e7f, 0xe4 => 0x901b, + 0xe5 => 0x7470, 0xe6 => 0x89c4, 0xe7 => 0x572d, 0xe8 => 0x7845, + 0xe9 => 0x5f52, 0xea => 0x9f9f, 0xeb => 0x95fa, 0xec => 0x8f68, + 0xed => 0x9b3c, 0xee => 0x8be1, 0xef => 0x7678, 0xf0 => 0x6842, + 0xf1 => 0x67dc, 0xf2 => 0x8dea, 0xf3 => 0x8d35, 0xf4 => 0x523d, + 0xf5 => 0x8f8a, 0xf6 => 0x6eda, 0xf7 => 0x68cd, 0xf8 => 0x9505, + 0xf9 => 0x90ed, 0xfa => 0x56fd, 0xfb => 0x679c, 0xfc => 0x88f9, + 0xfd => 0x8fc7, 0xfe => 0x54c8, + }, + 0xba => { + 0xa1 => 0x9ab8, 0xa2 => 0x5b69, 0xa3 => 0x6d77, 0xa4 => 0x6c26, + 0xa5 => 0x4ea5, 0xa6 => 0x5bb3, 0xa7 => 0x9a87, 0xa8 => 0x9163, + 0xa9 => 0x61a8, 0xaa => 0x90af, 0xab => 0x97e9, 0xac => 0x542b, + 0xad => 0x6db5, 0xae => 0x5bd2, 0xaf => 0x51fd, 0xb0 => 0x558a, + 0xb1 => 0x7f55, 0xb2 => 0x7ff0, 0xb3 => 0x64bc, 0xb4 => 0x634d, + 0xb5 => 0x65f1, 0xb6 => 0x61be, 0xb7 => 0x608d, 0xb8 => 0x710a, + 0xb9 => 0x6c57, 0xba => 0x6c49, 0xbb => 0x592f, 0xbc => 0x676d, + 0xbd => 0x822a, 0xbe => 0x58d5, 0xbf => 0x568e, 0xc0 => 0x8c6a, + 0xc1 => 0x6beb, 0xc2 => 0x90dd, 0xc3 => 0x597d, 0xc4 => 0x8017, + 0xc5 => 0x53f7, 0xc6 => 0x6d69, 0xc7 => 0x5475, 0xc8 => 0x559d, + 0xc9 => 0x8377, 0xca => 0x83cf, 0xcb => 0x6838, 0xcc => 0x79be, + 0xcd => 0x548c, 0xce => 0x4f55, 0xcf => 0x5408, 0xd0 => 0x76d2, + 0xd1 => 0x8c89, 0xd2 => 0x9602, 0xd3 => 0x6cb3, 0xd4 => 0x6db8, + 0xd5 => 0x8d6b, 0xd6 => 0x8910, 0xd7 => 0x9e64, 0xd8 => 0x8d3a, + 0xd9 => 0x563f, 0xda => 0x9ed1, 0xdb => 0x75d5, 0xdc => 0x5f88, + 0xdd => 0x72e0, 0xde => 0x6068, 0xdf => 0x54fc, 0xe0 => 0x4ea8, + 0xe1 => 0x6a2a, 0xe2 => 0x8861, 0xe3 => 0x6052, 0xe4 => 0x8f70, + 0xe5 => 0x54c4, 0xe6 => 0x70d8, 0xe7 => 0x8679, 0xe8 => 0x9e3f, + 0xe9 => 0x6d2a, 0xea => 0x5b8f, 0xeb => 0x5f18, 0xec => 0x7ea2, + 0xed => 0x5589, 0xee => 0x4faf, 0xef => 0x7334, 0xf0 => 0x543c, + 0xf1 => 0x539a, 0xf2 => 0x5019, 0xf3 => 0x540e, 0xf4 => 0x547c, + 0xf5 => 0x4e4e, 0xf6 => 0x5ffd, 0xf7 => 0x745a, 0xf8 => 0x58f6, + 0xf9 => 0x846b, 0xfa => 0x80e1, 0xfb => 0x8774, 0xfc => 0x72d0, + 0xfd => 0x7cca, 0xfe => 0x6e56, + }, + 0xbb => { + 0xa1 => 0x5f27, 0xa2 => 0x864e, 0xa3 => 0x552c, 0xa4 => 0x62a4, + 0xa5 => 0x4e92, 0xa6 => 0x6caa, 0xa7 => 0x6237, 0xa8 => 0x82b1, + 0xa9 => 0x54d7, 0xaa => 0x534e, 0xab => 0x733e, 0xac => 0x6ed1, + 0xad => 0x753b, 0xae => 0x5212, 0xaf => 0x5316, 0xb0 => 0x8bdd, + 0xb1 => 0x69d0, 0xb2 => 0x5f8a, 0xb3 => 0x6000, 0xb4 => 0x6dee, + 0xb5 => 0x574f, 0xb6 => 0x6b22, 0xb7 => 0x73af, 0xb8 => 0x6853, + 0xb9 => 0x8fd8, 0xba => 0x7f13, 0xbb => 0x6362, 0xbc => 0x60a3, + 0xbd => 0x5524, 0xbe => 0x75ea, 0xbf => 0x8c62, 0xc0 => 0x7115, + 0xc1 => 0x6da3, 0xc2 => 0x5ba6, 0xc3 => 0x5e7b, 0xc4 => 0x8352, + 0xc5 => 0x614c, 0xc6 => 0x9ec4, 0xc7 => 0x78fa, 0xc8 => 0x8757, + 0xc9 => 0x7c27, 0xca => 0x7687, 0xcb => 0x51f0, 0xcc => 0x60f6, + 0xcd => 0x714c, 0xce => 0x6643, 0xcf => 0x5e4c, 0xd0 => 0x604d, + 0xd1 => 0x8c0e, 0xd2 => 0x7070, 0xd3 => 0x6325, 0xd4 => 0x8f89, + 0xd5 => 0x5fbd, 0xd6 => 0x6062, 0xd7 => 0x86d4, 0xd8 => 0x56de, + 0xd9 => 0x6bc1, 0xda => 0x6094, 0xdb => 0x6167, 0xdc => 0x5349, + 0xdd => 0x60e0, 0xde => 0x6666, 0xdf => 0x8d3f, 0xe0 => 0x79fd, + 0xe1 => 0x4f1a, 0xe2 => 0x70e9, 0xe3 => 0x6c47, 0xe4 => 0x8bb3, + 0xe5 => 0x8bf2, 0xe6 => 0x7ed8, 0xe7 => 0x8364, 0xe8 => 0x660f, + 0xe9 => 0x5a5a, 0xea => 0x9b42, 0xeb => 0x6d51, 0xec => 0x6df7, + 0xed => 0x8c41, 0xee => 0x6d3b, 0xef => 0x4f19, 0xf0 => 0x706b, + 0xf1 => 0x83b7, 0xf2 => 0x6216, 0xf3 => 0x60d1, 0xf4 => 0x970d, + 0xf5 => 0x8d27, 0xf6 => 0x7978, 0xf7 => 0x51fb, 0xf8 => 0x573e, + 0xf9 => 0x57fa, 0xfa => 0x673a, 0xfb => 0x7578, 0xfc => 0x7a3d, + 0xfd => 0x79ef, 0xfe => 0x7b95, + }, + 0xbc => { + 0xa1 => 0x808c, 0xa2 => 0x9965, 0xa3 => 0x8ff9, 0xa4 => 0x6fc0, + 0xa5 => 0x8ba5, 0xa6 => 0x9e21, 0xa7 => 0x59ec, 0xa8 => 0x7ee9, + 0xa9 => 0x7f09, 0xaa => 0x5409, 0xab => 0x6781, 0xac => 0x68d8, + 0xad => 0x8f91, 0xae => 0x7c4d, 0xaf => 0x96c6, 0xb0 => 0x53ca, + 0xb1 => 0x6025, 0xb2 => 0x75be, 0xb3 => 0x6c72, 0xb4 => 0x5373, + 0xb5 => 0x5ac9, 0xb6 => 0x7ea7, 0xb7 => 0x6324, 0xb8 => 0x51e0, + 0xb9 => 0x810a, 0xba => 0x5df1, 0xbb => 0x84df, 0xbc => 0x6280, + 0xbd => 0x5180, 0xbe => 0x5b63, 0xbf => 0x4f0e, 0xc0 => 0x796d, + 0xc1 => 0x5242, 0xc2 => 0x60b8, 0xc3 => 0x6d4e, 0xc4 => 0x5bc4, + 0xc5 => 0x5bc2, 0xc6 => 0x8ba1, 0xc7 => 0x8bb0, 0xc8 => 0x65e2, + 0xc9 => 0x5fcc, 0xca => 0x9645, 0xcb => 0x5993, 0xcc => 0x7ee7, + 0xcd => 0x7eaa, 0xce => 0x5609, 0xcf => 0x67b7, 0xd0 => 0x5939, + 0xd1 => 0x4f73, 0xd2 => 0x5bb6, 0xd3 => 0x52a0, 0xd4 => 0x835a, + 0xd5 => 0x988a, 0xd6 => 0x8d3e, 0xd7 => 0x7532, 0xd8 => 0x94be, + 0xd9 => 0x5047, 0xda => 0x7a3c, 0xdb => 0x4ef7, 0xdc => 0x67b6, + 0xdd => 0x9a7e, 0xde => 0x5ac1, 0xdf => 0x6b7c, 0xe0 => 0x76d1, + 0xe1 => 0x575a, 0xe2 => 0x5c16, 0xe3 => 0x7b3a, 0xe4 => 0x95f4, + 0xe5 => 0x714e, 0xe6 => 0x517c, 0xe7 => 0x80a9, 0xe8 => 0x8270, + 0xe9 => 0x5978, 0xea => 0x7f04, 0xeb => 0x8327, 0xec => 0x68c0, + 0xed => 0x67ec, 0xee => 0x78b1, 0xef => 0x7877, 0xf0 => 0x62e3, + 0xf1 => 0x6361, 0xf2 => 0x7b80, 0xf3 => 0x4fed, 0xf4 => 0x526a, + 0xf5 => 0x51cf, 0xf6 => 0x8350, 0xf7 => 0x69db, 0xf8 => 0x9274, + 0xf9 => 0x8df5, 0xfa => 0x8d31, 0xfb => 0x89c1, 0xfc => 0x952e, + 0xfd => 0x7bad, 0xfe => 0x4ef6, + }, + 0xbd => { + 0xa1 => 0x5065, 0xa2 => 0x8230, 0xa3 => 0x5251, 0xa4 => 0x996f, + 0xa5 => 0x6e10, 0xa6 => 0x6e85, 0xa7 => 0x6da7, 0xa8 => 0x5efa, + 0xa9 => 0x50f5, 0xaa => 0x59dc, 0xab => 0x5c06, 0xac => 0x6d46, + 0xad => 0x6c5f, 0xae => 0x7586, 0xaf => 0x848b, 0xb0 => 0x6868, + 0xb1 => 0x5956, 0xb2 => 0x8bb2, 0xb3 => 0x5320, 0xb4 => 0x9171, + 0xb5 => 0x964d, 0xb6 => 0x8549, 0xb7 => 0x6912, 0xb8 => 0x7901, + 0xb9 => 0x7126, 0xba => 0x80f6, 0xbb => 0x4ea4, 0xbc => 0x90ca, + 0xbd => 0x6d47, 0xbe => 0x9a84, 0xbf => 0x5a07, 0xc0 => 0x56bc, + 0xc1 => 0x6405, 0xc2 => 0x94f0, 0xc3 => 0x77eb, 0xc4 => 0x4fa5, + 0xc5 => 0x811a, 0xc6 => 0x72e1, 0xc7 => 0x89d2, 0xc8 => 0x997a, + 0xc9 => 0x7f34, 0xca => 0x7ede, 0xcb => 0x527f, 0xcc => 0x6559, + 0xcd => 0x9175, 0xce => 0x8f7f, 0xcf => 0x8f83, 0xd0 => 0x53eb, + 0xd1 => 0x7a96, 0xd2 => 0x63ed, 0xd3 => 0x63a5, 0xd4 => 0x7686, + 0xd5 => 0x79f8, 0xd6 => 0x8857, 0xd7 => 0x9636, 0xd8 => 0x622a, + 0xd9 => 0x52ab, 0xda => 0x8282, 0xdb => 0x6854, 0xdc => 0x6770, + 0xdd => 0x6377, 0xde => 0x776b, 0xdf => 0x7aed, 0xe0 => 0x6d01, + 0xe1 => 0x7ed3, 0xe2 => 0x89e3, 0xe3 => 0x59d0, 0xe4 => 0x6212, + 0xe5 => 0x85c9, 0xe6 => 0x82a5, 0xe7 => 0x754c, 0xe8 => 0x501f, + 0xe9 => 0x4ecb, 0xea => 0x75a5, 0xeb => 0x8beb, 0xec => 0x5c4a, + 0xed => 0x5dfe, 0xee => 0x7b4b, 0xef => 0x65a4, 0xf0 => 0x91d1, + 0xf1 => 0x4eca, 0xf2 => 0x6d25, 0xf3 => 0x895f, 0xf4 => 0x7d27, + 0xf5 => 0x9526, 0xf6 => 0x4ec5, 0xf7 => 0x8c28, 0xf8 => 0x8fdb, + 0xf9 => 0x9773, 0xfa => 0x664b, 0xfb => 0x7981, 0xfc => 0x8fd1, + 0xfd => 0x70ec, 0xfe => 0x6d78, + }, + 0xbe => { + 0xa1 => 0x5c3d, 0xa2 => 0x52b2, 0xa3 => 0x8346, 0xa4 => 0x5162, + 0xa5 => 0x830e, 0xa6 => 0x775b, 0xa7 => 0x6676, 0xa8 => 0x9cb8, + 0xa9 => 0x4eac, 0xaa => 0x60ca, 0xab => 0x7cbe, 0xac => 0x7cb3, + 0xad => 0x7ecf, 0xae => 0x4e95, 0xaf => 0x8b66, 0xb0 => 0x666f, + 0xb1 => 0x9888, 0xb2 => 0x9759, 0xb3 => 0x5883, 0xb4 => 0x656c, + 0xb5 => 0x955c, 0xb6 => 0x5f84, 0xb7 => 0x75c9, 0xb8 => 0x9756, + 0xb9 => 0x7adf, 0xba => 0x7ade, 0xbb => 0x51c0, 0xbc => 0x70af, + 0xbd => 0x7a98, 0xbe => 0x63ea, 0xbf => 0x7a76, 0xc0 => 0x7ea0, + 0xc1 => 0x7396, 0xc2 => 0x97ed, 0xc3 => 0x4e45, 0xc4 => 0x7078, + 0xc5 => 0x4e5d, 0xc6 => 0x9152, 0xc7 => 0x53a9, 0xc8 => 0x6551, + 0xc9 => 0x65e7, 0xca => 0x81fc, 0xcb => 0x8205, 0xcc => 0x548e, + 0xcd => 0x5c31, 0xce => 0x759a, 0xcf => 0x97a0, 0xd0 => 0x62d8, + 0xd1 => 0x72d9, 0xd2 => 0x75bd, 0xd3 => 0x5c45, 0xd4 => 0x9a79, + 0xd5 => 0x83ca, 0xd6 => 0x5c40, 0xd7 => 0x5480, 0xd8 => 0x77e9, + 0xd9 => 0x4e3e, 0xda => 0x6cae, 0xdb => 0x805a, 0xdc => 0x62d2, + 0xdd => 0x636e, 0xde => 0x5de8, 0xdf => 0x5177, 0xe0 => 0x8ddd, + 0xe1 => 0x8e1e, 0xe2 => 0x952f, 0xe3 => 0x4ff1, 0xe4 => 0x53e5, + 0xe5 => 0x60e7, 0xe6 => 0x70ac, 0xe7 => 0x5267, 0xe8 => 0x6350, + 0xe9 => 0x9e43, 0xea => 0x5a1f, 0xeb => 0x5026, 0xec => 0x7737, + 0xed => 0x5377, 0xee => 0x7ee2, 0xef => 0x6485, 0xf0 => 0x652b, + 0xf1 => 0x6289, 0xf2 => 0x6398, 0xf3 => 0x5014, 0xf4 => 0x7235, + 0xf5 => 0x89c9, 0xf6 => 0x51b3, 0xf7 => 0x8bc0, 0xf8 => 0x7edd, + 0xf9 => 0x5747, 0xfa => 0x83cc, 0xfb => 0x94a7, 0xfc => 0x519b, + 0xfd => 0x541b, 0xfe => 0x5cfb, + }, + 0xbf => { + 0xa1 => 0x4fca, 0xa2 => 0x7ae3, 0xa3 => 0x6d5a, 0xa4 => 0x90e1, + 0xa5 => 0x9a8f, 0xa6 => 0x5580, 0xa7 => 0x5496, 0xa8 => 0x5361, + 0xa9 => 0x54af, 0xaa => 0x5f00, 0xab => 0x63e9, 0xac => 0x6977, + 0xad => 0x51ef, 0xae => 0x6168, 0xaf => 0x520a, 0xb0 => 0x582a, + 0xb1 => 0x52d8, 0xb2 => 0x574e, 0xb3 => 0x780d, 0xb4 => 0x770b, + 0xb5 => 0x5eb7, 0xb6 => 0x6177, 0xb7 => 0x7ce0, 0xb8 => 0x625b, + 0xb9 => 0x6297, 0xba => 0x4ea2, 0xbb => 0x7095, 0xbc => 0x8003, + 0xbd => 0x62f7, 0xbe => 0x70e4, 0xbf => 0x9760, 0xc0 => 0x5777, + 0xc1 => 0x82db, 0xc2 => 0x67ef, 0xc3 => 0x68f5, 0xc4 => 0x78d5, + 0xc5 => 0x9897, 0xc6 => 0x79d1, 0xc7 => 0x58f3, 0xc8 => 0x54b3, + 0xc9 => 0x53ef, 0xca => 0x6e34, 0xcb => 0x514b, 0xcc => 0x523b, + 0xcd => 0x5ba2, 0xce => 0x8bfe, 0xcf => 0x80af, 0xd0 => 0x5543, + 0xd1 => 0x57a6, 0xd2 => 0x6073, 0xd3 => 0x5751, 0xd4 => 0x542d, + 0xd5 => 0x7a7a, 0xd6 => 0x6050, 0xd7 => 0x5b54, 0xd8 => 0x63a7, + 0xd9 => 0x62a0, 0xda => 0x53e3, 0xdb => 0x6263, 0xdc => 0x5bc7, + 0xdd => 0x67af, 0xde => 0x54ed, 0xdf => 0x7a9f, 0xe0 => 0x82e6, + 0xe1 => 0x9177, 0xe2 => 0x5e93, 0xe3 => 0x88e4, 0xe4 => 0x5938, + 0xe5 => 0x57ae, 0xe6 => 0x630e, 0xe7 => 0x8de8, 0xe8 => 0x80ef, + 0xe9 => 0x5757, 0xea => 0x7b77, 0xeb => 0x4fa9, 0xec => 0x5feb, + 0xed => 0x5bbd, 0xee => 0x6b3e, 0xef => 0x5321, 0xf0 => 0x7b50, + 0xf1 => 0x72c2, 0xf2 => 0x6846, 0xf3 => 0x77ff, 0xf4 => 0x7736, + 0xf5 => 0x65f7, 0xf6 => 0x51b5, 0xf7 => 0x4e8f, 0xf8 => 0x76d4, + 0xf9 => 0x5cbf, 0xfa => 0x7aa5, 0xfb => 0x8475, 0xfc => 0x594e, + 0xfd => 0x9b41, 0xfe => 0x5080, + }, + 0xc0 => { + 0xa1 => 0x9988, 0xa2 => 0x6127, 0xa3 => 0x6e83, 0xa4 => 0x5764, + 0xa5 => 0x6606, 0xa6 => 0x6346, 0xa7 => 0x56f0, 0xa8 => 0x62ec, + 0xa9 => 0x6269, 0xaa => 0x5ed3, 0xab => 0x9614, 0xac => 0x5783, + 0xad => 0x62c9, 0xae => 0x5587, 0xaf => 0x8721, 0xb0 => 0x814a, + 0xb1 => 0x8fa3, 0xb2 => 0x5566, 0xb3 => 0x83b1, 0xb4 => 0x6765, + 0xb5 => 0x8d56, 0xb6 => 0x84dd, 0xb7 => 0x5a6a, 0xb8 => 0x680f, + 0xb9 => 0x62e6, 0xba => 0x7bee, 0xbb => 0x9611, 0xbc => 0x5170, + 0xbd => 0x6f9c, 0xbe => 0x8c30, 0xbf => 0x63fd, 0xc0 => 0x89c8, + 0xc1 => 0x61d2, 0xc2 => 0x7f06, 0xc3 => 0x70c2, 0xc4 => 0x6ee5, + 0xc5 => 0x7405, 0xc6 => 0x6994, 0xc7 => 0x72fc, 0xc8 => 0x5eca, + 0xc9 => 0x90ce, 0xca => 0x6717, 0xcb => 0x6d6a, 0xcc => 0x635e, + 0xcd => 0x52b3, 0xce => 0x7262, 0xcf => 0x8001, 0xd0 => 0x4f6c, + 0xd1 => 0x59e5, 0xd2 => 0x916a, 0xd3 => 0x70d9, 0xd4 => 0x6d9d, + 0xd5 => 0x52d2, 0xd6 => 0x4e50, 0xd7 => 0x96f7, 0xd8 => 0x956d, + 0xd9 => 0x857e, 0xda => 0x78ca, 0xdb => 0x7d2f, 0xdc => 0x5121, + 0xdd => 0x5792, 0xde => 0x64c2, 0xdf => 0x808b, 0xe0 => 0x7c7b, + 0xe1 => 0x6cea, 0xe2 => 0x68f1, 0xe3 => 0x695e, 0xe4 => 0x51b7, + 0xe5 => 0x5398, 0xe6 => 0x68a8, 0xe7 => 0x7281, 0xe8 => 0x9ece, + 0xe9 => 0x7bf1, 0xea => 0x72f8, 0xeb => 0x79bb, 0xec => 0x6f13, + 0xed => 0x7406, 0xee => 0x674e, 0xef => 0x91cc, 0xf0 => 0x9ca4, + 0xf1 => 0x793c, 0xf2 => 0x8389, 0xf3 => 0x8354, 0xf4 => 0x540f, + 0xf5 => 0x6817, 0xf6 => 0x4e3d, 0xf7 => 0x5389, 0xf8 => 0x52b1, + 0xf9 => 0x783e, 0xfa => 0x5386, 0xfb => 0x5229, 0xfc => 0x5088, + 0xfd => 0x4f8b, 0xfe => 0x4fd0, + }, + 0xc1 => { + 0xa1 => 0x75e2, 0xa2 => 0x7acb, 0xa3 => 0x7c92, 0xa4 => 0x6ca5, + 0xa5 => 0x96b6, 0xa6 => 0x529b, 0xa7 => 0x7483, 0xa8 => 0x54e9, + 0xa9 => 0x4fe9, 0xaa => 0x8054, 0xab => 0x83b2, 0xac => 0x8fde, + 0xad => 0x9570, 0xae => 0x5ec9, 0xaf => 0x601c, 0xb0 => 0x6d9f, + 0xb1 => 0x5e18, 0xb2 => 0x655b, 0xb3 => 0x8138, 0xb4 => 0x94fe, + 0xb5 => 0x604b, 0xb6 => 0x70bc, 0xb7 => 0x7ec3, 0xb8 => 0x7cae, + 0xb9 => 0x51c9, 0xba => 0x6881, 0xbb => 0x7cb1, 0xbc => 0x826f, + 0xbd => 0x4e24, 0xbe => 0x8f86, 0xbf => 0x91cf, 0xc0 => 0x667e, + 0xc1 => 0x4eae, 0xc2 => 0x8c05, 0xc3 => 0x64a9, 0xc4 => 0x804a, + 0xc5 => 0x50da, 0xc6 => 0x7597, 0xc7 => 0x71ce, 0xc8 => 0x5be5, + 0xc9 => 0x8fbd, 0xca => 0x6f66, 0xcb => 0x4e86, 0xcc => 0x6482, + 0xcd => 0x9563, 0xce => 0x5ed6, 0xcf => 0x6599, 0xd0 => 0x5217, + 0xd1 => 0x88c2, 0xd2 => 0x70c8, 0xd3 => 0x52a3, 0xd4 => 0x730e, + 0xd5 => 0x7433, 0xd6 => 0x6797, 0xd7 => 0x78f7, 0xd8 => 0x9716, + 0xd9 => 0x4e34, 0xda => 0x90bb, 0xdb => 0x9cde, 0xdc => 0x6dcb, + 0xdd => 0x51db, 0xde => 0x8d41, 0xdf => 0x541d, 0xe0 => 0x62ce, + 0xe1 => 0x73b2, 0xe2 => 0x83f1, 0xe3 => 0x96f6, 0xe4 => 0x9f84, + 0xe5 => 0x94c3, 0xe6 => 0x4f36, 0xe7 => 0x7f9a, 0xe8 => 0x51cc, + 0xe9 => 0x7075, 0xea => 0x9675, 0xeb => 0x5cad, 0xec => 0x9886, + 0xed => 0x53e6, 0xee => 0x4ee4, 0xef => 0x6e9c, 0xf0 => 0x7409, + 0xf1 => 0x69b4, 0xf2 => 0x786b, 0xf3 => 0x998f, 0xf4 => 0x7559, + 0xf5 => 0x5218, 0xf6 => 0x7624, 0xf7 => 0x6d41, 0xf8 => 0x67f3, + 0xf9 => 0x516d, 0xfa => 0x9f99, 0xfb => 0x804b, 0xfc => 0x5499, + 0xfd => 0x7b3c, 0xfe => 0x7abf, + }, + 0xc2 => { + 0xa1 => 0x9686, 0xa2 => 0x5784, 0xa3 => 0x62e2, 0xa4 => 0x9647, + 0xa5 => 0x697c, 0xa6 => 0x5a04, 0xa7 => 0x6402, 0xa8 => 0x7bd3, + 0xa9 => 0x6f0f, 0xaa => 0x964b, 0xab => 0x82a6, 0xac => 0x5362, + 0xad => 0x9885, 0xae => 0x5e90, 0xaf => 0x7089, 0xb0 => 0x63b3, + 0xb1 => 0x5364, 0xb2 => 0x864f, 0xb3 => 0x9c81, 0xb4 => 0x9e93, + 0xb5 => 0x788c, 0xb6 => 0x9732, 0xb7 => 0x8def, 0xb8 => 0x8d42, + 0xb9 => 0x9e7f, 0xba => 0x6f5e, 0xbb => 0x7984, 0xbc => 0x5f55, + 0xbd => 0x9646, 0xbe => 0x622e, 0xbf => 0x9a74, 0xc0 => 0x5415, + 0xc1 => 0x94dd, 0xc2 => 0x4fa3, 0xc3 => 0x65c5, 0xc4 => 0x5c65, + 0xc5 => 0x5c61, 0xc6 => 0x7f15, 0xc7 => 0x8651, 0xc8 => 0x6c2f, + 0xc9 => 0x5f8b, 0xca => 0x7387, 0xcb => 0x6ee4, 0xcc => 0x7eff, + 0xcd => 0x5ce6, 0xce => 0x631b, 0xcf => 0x5b6a, 0xd0 => 0x6ee6, + 0xd1 => 0x5375, 0xd2 => 0x4e71, 0xd3 => 0x63a0, 0xd4 => 0x7565, + 0xd5 => 0x62a1, 0xd6 => 0x8f6e, 0xd7 => 0x4f26, 0xd8 => 0x4ed1, + 0xd9 => 0x6ca6, 0xda => 0x7eb6, 0xdb => 0x8bba, 0xdc => 0x841d, + 0xdd => 0x87ba, 0xde => 0x7f57, 0xdf => 0x903b, 0xe0 => 0x9523, + 0xe1 => 0x7ba9, 0xe2 => 0x9aa1, 0xe3 => 0x88f8, 0xe4 => 0x843d, + 0xe5 => 0x6d1b, 0xe6 => 0x9a86, 0xe7 => 0x7edc, 0xe8 => 0x5988, + 0xe9 => 0x9ebb, 0xea => 0x739b, 0xeb => 0x7801, 0xec => 0x8682, + 0xed => 0x9a6c, 0xee => 0x9a82, 0xef => 0x561b, 0xf0 => 0x5417, + 0xf1 => 0x57cb, 0xf2 => 0x4e70, 0xf3 => 0x9ea6, 0xf4 => 0x5356, + 0xf5 => 0x8fc8, 0xf6 => 0x8109, 0xf7 => 0x7792, 0xf8 => 0x9992, + 0xf9 => 0x86ee, 0xfa => 0x6ee1, 0xfb => 0x8513, 0xfc => 0x66fc, + 0xfd => 0x6162, 0xfe => 0x6f2b, + }, + 0xc3 => { + 0xa1 => 0x8c29, 0xa2 => 0x8292, 0xa3 => 0x832b, 0xa4 => 0x76f2, + 0xa5 => 0x6c13, 0xa6 => 0x5fd9, 0xa7 => 0x83bd, 0xa8 => 0x732b, + 0xa9 => 0x8305, 0xaa => 0x951a, 0xab => 0x6bdb, 0xac => 0x77db, + 0xad => 0x94c6, 0xae => 0x536f, 0xaf => 0x8302, 0xb0 => 0x5192, + 0xb1 => 0x5e3d, 0xb2 => 0x8c8c, 0xb3 => 0x8d38, 0xb4 => 0x4e48, + 0xb5 => 0x73ab, 0xb6 => 0x679a, 0xb7 => 0x6885, 0xb8 => 0x9176, + 0xb9 => 0x9709, 0xba => 0x7164, 0xbb => 0x6ca1, 0xbc => 0x7709, + 0xbd => 0x5a92, 0xbe => 0x9541, 0xbf => 0x6bcf, 0xc0 => 0x7f8e, + 0xc1 => 0x6627, 0xc2 => 0x5bd0, 0xc3 => 0x59b9, 0xc4 => 0x5a9a, + 0xc5 => 0x95e8, 0xc6 => 0x95f7, 0xc7 => 0x4eec, 0xc8 => 0x840c, + 0xc9 => 0x8499, 0xca => 0x6aac, 0xcb => 0x76df, 0xcc => 0x9530, + 0xcd => 0x731b, 0xce => 0x68a6, 0xcf => 0x5b5f, 0xd0 => 0x772f, + 0xd1 => 0x919a, 0xd2 => 0x9761, 0xd3 => 0x7cdc, 0xd4 => 0x8ff7, + 0xd5 => 0x8c1c, 0xd6 => 0x5f25, 0xd7 => 0x7c73, 0xd8 => 0x79d8, + 0xd9 => 0x89c5, 0xda => 0x6ccc, 0xdb => 0x871c, 0xdc => 0x5bc6, + 0xdd => 0x5e42, 0xde => 0x68c9, 0xdf => 0x7720, 0xe0 => 0x7ef5, + 0xe1 => 0x5195, 0xe2 => 0x514d, 0xe3 => 0x52c9, 0xe4 => 0x5a29, + 0xe5 => 0x7f05, 0xe6 => 0x9762, 0xe7 => 0x82d7, 0xe8 => 0x63cf, + 0xe9 => 0x7784, 0xea => 0x85d0, 0xeb => 0x79d2, 0xec => 0x6e3a, + 0xed => 0x5e99, 0xee => 0x5999, 0xef => 0x8511, 0xf0 => 0x706d, + 0xf1 => 0x6c11, 0xf2 => 0x62bf, 0xf3 => 0x76bf, 0xf4 => 0x654f, + 0xf5 => 0x60af, 0xf6 => 0x95fd, 0xf7 => 0x660e, 0xf8 => 0x879f, + 0xf9 => 0x9e23, 0xfa => 0x94ed, 0xfb => 0x540d, 0xfc => 0x547d, + 0xfd => 0x8c2c, 0xfe => 0x6478, + }, + 0xc4 => { + 0xa1 => 0x6479, 0xa2 => 0x8611, 0xa3 => 0x6a21, 0xa4 => 0x819c, + 0xa5 => 0x78e8, 0xa6 => 0x6469, 0xa7 => 0x9b54, 0xa8 => 0x62b9, + 0xa9 => 0x672b, 0xaa => 0x83ab, 0xab => 0x58a8, 0xac => 0x9ed8, + 0xad => 0x6cab, 0xae => 0x6f20, 0xaf => 0x5bde, 0xb0 => 0x964c, + 0xb1 => 0x8c0b, 0xb2 => 0x725f, 0xb3 => 0x67d0, 0xb4 => 0x62c7, + 0xb5 => 0x7261, 0xb6 => 0x4ea9, 0xb7 => 0x59c6, 0xb8 => 0x6bcd, + 0xb9 => 0x5893, 0xba => 0x66ae, 0xbb => 0x5e55, 0xbc => 0x52df, + 0xbd => 0x6155, 0xbe => 0x6728, 0xbf => 0x76ee, 0xc0 => 0x7766, + 0xc1 => 0x7267, 0xc2 => 0x7a46, 0xc3 => 0x62ff, 0xc4 => 0x54ea, + 0xc5 => 0x5450, 0xc6 => 0x94a0, 0xc7 => 0x90a3, 0xc8 => 0x5a1c, + 0xc9 => 0x7eb3, 0xca => 0x6c16, 0xcb => 0x4e43, 0xcc => 0x5976, + 0xcd => 0x8010, 0xce => 0x5948, 0xcf => 0x5357, 0xd0 => 0x7537, + 0xd1 => 0x96be, 0xd2 => 0x56ca, 0xd3 => 0x6320, 0xd4 => 0x8111, + 0xd5 => 0x607c, 0xd6 => 0x95f9, 0xd7 => 0x6dd6, 0xd8 => 0x5462, + 0xd9 => 0x9981, 0xda => 0x5185, 0xdb => 0x5ae9, 0xdc => 0x80fd, + 0xdd => 0x59ae, 0xde => 0x9713, 0xdf => 0x502a, 0xe0 => 0x6ce5, + 0xe1 => 0x5c3c, 0xe2 => 0x62df, 0xe3 => 0x4f60, 0xe4 => 0x533f, + 0xe5 => 0x817b, 0xe6 => 0x9006, 0xe7 => 0x6eba, 0xe8 => 0x852b, + 0xe9 => 0x62c8, 0xea => 0x5e74, 0xeb => 0x78be, 0xec => 0x64b5, + 0xed => 0x637b, 0xee => 0x5ff5, 0xef => 0x5a18, 0xf0 => 0x917f, + 0xf1 => 0x9e1f, 0xf2 => 0x5c3f, 0xf3 => 0x634f, 0xf4 => 0x8042, + 0xf5 => 0x5b7d, 0xf6 => 0x556e, 0xf7 => 0x954a, 0xf8 => 0x954d, + 0xf9 => 0x6d85, 0xfa => 0x60a8, 0xfb => 0x67e0, 0xfc => 0x72de, + 0xfd => 0x51dd, 0xfe => 0x5b81, + }, + 0xc5 => { + 0xa1 => 0x62e7, 0xa2 => 0x6cde, 0xa3 => 0x725b, 0xa4 => 0x626d, + 0xa5 => 0x94ae, 0xa6 => 0x7ebd, 0xa7 => 0x8113, 0xa8 => 0x6d53, + 0xa9 => 0x519c, 0xaa => 0x5f04, 0xab => 0x5974, 0xac => 0x52aa, + 0xad => 0x6012, 0xae => 0x5973, 0xaf => 0x6696, 0xb0 => 0x8650, + 0xb1 => 0x759f, 0xb2 => 0x632a, 0xb3 => 0x61e6, 0xb4 => 0x7cef, + 0xb5 => 0x8bfa, 0xb6 => 0x54e6, 0xb7 => 0x6b27, 0xb8 => 0x9e25, + 0xb9 => 0x6bb4, 0xba => 0x85d5, 0xbb => 0x5455, 0xbc => 0x5076, + 0xbd => 0x6ca4, 0xbe => 0x556a, 0xbf => 0x8db4, 0xc0 => 0x722c, + 0xc1 => 0x5e15, 0xc2 => 0x6015, 0xc3 => 0x7436, 0xc4 => 0x62cd, + 0xc5 => 0x6392, 0xc6 => 0x724c, 0xc7 => 0x5f98, 0xc8 => 0x6e43, + 0xc9 => 0x6d3e, 0xca => 0x6500, 0xcb => 0x6f58, 0xcc => 0x76d8, + 0xcd => 0x78d0, 0xce => 0x76fc, 0xcf => 0x7554, 0xd0 => 0x5224, + 0xd1 => 0x53db, 0xd2 => 0x4e53, 0xd3 => 0x5e9e, 0xd4 => 0x65c1, + 0xd5 => 0x802a, 0xd6 => 0x80d6, 0xd7 => 0x629b, 0xd8 => 0x5486, + 0xd9 => 0x5228, 0xda => 0x70ae, 0xdb => 0x888d, 0xdc => 0x8dd1, + 0xdd => 0x6ce1, 0xde => 0x5478, 0xdf => 0x80da, 0xe0 => 0x57f9, + 0xe1 => 0x88f4, 0xe2 => 0x8d54, 0xe3 => 0x966a, 0xe4 => 0x914d, + 0xe5 => 0x4f69, 0xe6 => 0x6c9b, 0xe7 => 0x55b7, 0xe8 => 0x76c6, + 0xe9 => 0x7830, 0xea => 0x62a8, 0xeb => 0x70f9, 0xec => 0x6f8e, + 0xed => 0x5f6d, 0xee => 0x84ec, 0xef => 0x68da, 0xf0 => 0x787c, + 0xf1 => 0x7bf7, 0xf2 => 0x81a8, 0xf3 => 0x670b, 0xf4 => 0x9e4f, + 0xf5 => 0x6367, 0xf6 => 0x78b0, 0xf7 => 0x576f, 0xf8 => 0x7812, + 0xf9 => 0x9739, 0xfa => 0x6279, 0xfb => 0x62ab, 0xfc => 0x5288, + 0xfd => 0x7435, 0xfe => 0x6bd7, + }, + 0xc6 => { + 0xa1 => 0x5564, 0xa2 => 0x813e, 0xa3 => 0x75b2, 0xa4 => 0x76ae, + 0xa5 => 0x5339, 0xa6 => 0x75de, 0xa7 => 0x50fb, 0xa8 => 0x5c41, + 0xa9 => 0x8b6c, 0xaa => 0x7bc7, 0xab => 0x504f, 0xac => 0x7247, + 0xad => 0x9a97, 0xae => 0x98d8, 0xaf => 0x6f02, 0xb0 => 0x74e2, + 0xb1 => 0x7968, 0xb2 => 0x6487, 0xb3 => 0x77a5, 0xb4 => 0x62fc, + 0xb5 => 0x9891, 0xb6 => 0x8d2b, 0xb7 => 0x54c1, 0xb8 => 0x8058, + 0xb9 => 0x4e52, 0xba => 0x576a, 0xbb => 0x82f9, 0xbc => 0x840d, + 0xbd => 0x5e73, 0xbe => 0x51ed, 0xbf => 0x74f6, 0xc0 => 0x8bc4, + 0xc1 => 0x5c4f, 0xc2 => 0x5761, 0xc3 => 0x6cfc, 0xc4 => 0x9887, + 0xc5 => 0x5a46, 0xc6 => 0x7834, 0xc7 => 0x9b44, 0xc8 => 0x8feb, + 0xc9 => 0x7c95, 0xca => 0x5256, 0xcb => 0x6251, 0xcc => 0x94fa, + 0xcd => 0x4ec6, 0xce => 0x8386, 0xcf => 0x8461, 0xd0 => 0x83e9, + 0xd1 => 0x84b2, 0xd2 => 0x57d4, 0xd3 => 0x6734, 0xd4 => 0x5703, + 0xd5 => 0x666e, 0xd6 => 0x6d66, 0xd7 => 0x8c31, 0xd8 => 0x66dd, + 0xd9 => 0x7011, 0xda => 0x671f, 0xdb => 0x6b3a, 0xdc => 0x6816, + 0xdd => 0x621a, 0xde => 0x59bb, 0xdf => 0x4e03, 0xe0 => 0x51c4, + 0xe1 => 0x6f06, 0xe2 => 0x67d2, 0xe3 => 0x6c8f, 0xe4 => 0x5176, + 0xe5 => 0x68cb, 0xe6 => 0x5947, 0xe7 => 0x6b67, 0xe8 => 0x7566, + 0xe9 => 0x5d0e, 0xea => 0x8110, 0xeb => 0x9f50, 0xec => 0x65d7, + 0xed => 0x7948, 0xee => 0x7941, 0xef => 0x9a91, 0xf0 => 0x8d77, + 0xf1 => 0x5c82, 0xf2 => 0x4e5e, 0xf3 => 0x4f01, 0xf4 => 0x542f, + 0xf5 => 0x5951, 0xf6 => 0x780c, 0xf7 => 0x5668, 0xf8 => 0x6c14, + 0xf9 => 0x8fc4, 0xfa => 0x5f03, 0xfb => 0x6c7d, 0xfc => 0x6ce3, + 0xfd => 0x8bab, 0xfe => 0x6390, + }, + 0xc7 => { + 0xa1 => 0x6070, 0xa2 => 0x6d3d, 0xa3 => 0x7275, 0xa4 => 0x6266, + 0xa5 => 0x948e, 0xa6 => 0x94c5, 0xa7 => 0x5343, 0xa8 => 0x8fc1, + 0xa9 => 0x7b7e, 0xaa => 0x4edf, 0xab => 0x8c26, 0xac => 0x4e7e, + 0xad => 0x9ed4, 0xae => 0x94b1, 0xaf => 0x94b3, 0xb0 => 0x524d, + 0xb1 => 0x6f5c, 0xb2 => 0x9063, 0xb3 => 0x6d45, 0xb4 => 0x8c34, + 0xb5 => 0x5811, 0xb6 => 0x5d4c, 0xb7 => 0x6b20, 0xb8 => 0x6b49, + 0xb9 => 0x67aa, 0xba => 0x545b, 0xbb => 0x8154, 0xbc => 0x7f8c, + 0xbd => 0x5899, 0xbe => 0x8537, 0xbf => 0x5f3a, 0xc0 => 0x62a2, + 0xc1 => 0x6a47, 0xc2 => 0x9539, 0xc3 => 0x6572, 0xc4 => 0x6084, + 0xc5 => 0x6865, 0xc6 => 0x77a7, 0xc7 => 0x4e54, 0xc8 => 0x4fa8, + 0xc9 => 0x5de7, 0xca => 0x9798, 0xcb => 0x64ac, 0xcc => 0x7fd8, + 0xcd => 0x5ced, 0xce => 0x4fcf, 0xcf => 0x7a8d, 0xd0 => 0x5207, + 0xd1 => 0x8304, 0xd2 => 0x4e14, 0xd3 => 0x602f, 0xd4 => 0x7a83, + 0xd5 => 0x94a6, 0xd6 => 0x4fb5, 0xd7 => 0x4eb2, 0xd8 => 0x79e6, + 0xd9 => 0x7434, 0xda => 0x52e4, 0xdb => 0x82b9, 0xdc => 0x64d2, + 0xdd => 0x79bd, 0xde => 0x5bdd, 0xdf => 0x6c81, 0xe0 => 0x9752, + 0xe1 => 0x8f7b, 0xe2 => 0x6c22, 0xe3 => 0x503e, 0xe4 => 0x537f, + 0xe5 => 0x6e05, 0xe6 => 0x64ce, 0xe7 => 0x6674, 0xe8 => 0x6c30, + 0xe9 => 0x60c5, 0xea => 0x9877, 0xeb => 0x8bf7, 0xec => 0x5e86, + 0xed => 0x743c, 0xee => 0x7a77, 0xef => 0x79cb, 0xf0 => 0x4e18, + 0xf1 => 0x90b1, 0xf2 => 0x7403, 0xf3 => 0x6c42, 0xf4 => 0x56da, + 0xf5 => 0x914b, 0xf6 => 0x6cc5, 0xf7 => 0x8d8b, 0xf8 => 0x533a, + 0xf9 => 0x86c6, 0xfa => 0x66f2, 0xfb => 0x8eaf, 0xfc => 0x5c48, + 0xfd => 0x9a71, 0xfe => 0x6e20, + }, + 0xc8 => { + 0xa1 => 0x53d6, 0xa2 => 0x5a36, 0xa3 => 0x9f8b, 0xa4 => 0x8da3, + 0xa5 => 0x53bb, 0xa6 => 0x5708, 0xa7 => 0x98a7, 0xa8 => 0x6743, + 0xa9 => 0x919b, 0xaa => 0x6cc9, 0xab => 0x5168, 0xac => 0x75ca, + 0xad => 0x62f3, 0xae => 0x72ac, 0xaf => 0x5238, 0xb0 => 0x529d, + 0xb1 => 0x7f3a, 0xb2 => 0x7094, 0xb3 => 0x7638, 0xb4 => 0x5374, + 0xb5 => 0x9e4a, 0xb6 => 0x69b7, 0xb7 => 0x786e, 0xb8 => 0x96c0, + 0xb9 => 0x88d9, 0xba => 0x7fa4, 0xbb => 0x7136, 0xbc => 0x71c3, + 0xbd => 0x5189, 0xbe => 0x67d3, 0xbf => 0x74e4, 0xc0 => 0x58e4, + 0xc1 => 0x6518, 0xc2 => 0x56b7, 0xc3 => 0x8ba9, 0xc4 => 0x9976, + 0xc5 => 0x6270, 0xc6 => 0x7ed5, 0xc7 => 0x60f9, 0xc8 => 0x70ed, + 0xc9 => 0x58ec, 0xca => 0x4ec1, 0xcb => 0x4eba, 0xcc => 0x5fcd, + 0xcd => 0x97e7, 0xce => 0x4efb, 0xcf => 0x8ba4, 0xd0 => 0x5203, + 0xd1 => 0x598a, 0xd2 => 0x7eab, 0xd3 => 0x6254, 0xd4 => 0x4ecd, + 0xd5 => 0x65e5, 0xd6 => 0x620e, 0xd7 => 0x8338, 0xd8 => 0x84c9, + 0xd9 => 0x8363, 0xda => 0x878d, 0xdb => 0x7194, 0xdc => 0x6eb6, + 0xdd => 0x5bb9, 0xde => 0x7ed2, 0xdf => 0x5197, 0xe0 => 0x63c9, + 0xe1 => 0x67d4, 0xe2 => 0x8089, 0xe3 => 0x8339, 0xe4 => 0x8815, + 0xe5 => 0x5112, 0xe6 => 0x5b7a, 0xe7 => 0x5982, 0xe8 => 0x8fb1, + 0xe9 => 0x4e73, 0xea => 0x6c5d, 0xeb => 0x5165, 0xec => 0x8925, + 0xed => 0x8f6f, 0xee => 0x962e, 0xef => 0x854a, 0xf0 => 0x745e, + 0xf1 => 0x9510, 0xf2 => 0x95f0, 0xf3 => 0x6da6, 0xf4 => 0x82e5, + 0xf5 => 0x5f31, 0xf6 => 0x6492, 0xf7 => 0x6d12, 0xf8 => 0x8428, + 0xf9 => 0x816e, 0xfa => 0x9cc3, 0xfb => 0x585e, 0xfc => 0x8d5b, + 0xfd => 0x4e09, 0xfe => 0x53c1, + }, + 0xc9 => { + 0xa1 => 0x4f1e, 0xa2 => 0x6563, 0xa3 => 0x6851, 0xa4 => 0x55d3, + 0xa5 => 0x4e27, 0xa6 => 0x6414, 0xa7 => 0x9a9a, 0xa8 => 0x626b, + 0xa9 => 0x5ac2, 0xaa => 0x745f, 0xab => 0x8272, 0xac => 0x6da9, + 0xad => 0x68ee, 0xae => 0x50e7, 0xaf => 0x838e, 0xb0 => 0x7802, + 0xb1 => 0x6740, 0xb2 => 0x5239, 0xb3 => 0x6c99, 0xb4 => 0x7eb1, + 0xb5 => 0x50bb, 0xb6 => 0x5565, 0xb7 => 0x715e, 0xb8 => 0x7b5b, + 0xb9 => 0x6652, 0xba => 0x73ca, 0xbb => 0x82eb, 0xbc => 0x6749, + 0xbd => 0x5c71, 0xbe => 0x5220, 0xbf => 0x717d, 0xc0 => 0x886b, + 0xc1 => 0x95ea, 0xc2 => 0x9655, 0xc3 => 0x64c5, 0xc4 => 0x8d61, + 0xc5 => 0x81b3, 0xc6 => 0x5584, 0xc7 => 0x6c55, 0xc8 => 0x6247, + 0xc9 => 0x7f2e, 0xca => 0x5892, 0xcb => 0x4f24, 0xcc => 0x5546, + 0xcd => 0x8d4f, 0xce => 0x664c, 0xcf => 0x4e0a, 0xd0 => 0x5c1a, + 0xd1 => 0x88f3, 0xd2 => 0x68a2, 0xd3 => 0x634e, 0xd4 => 0x7a0d, + 0xd5 => 0x70e7, 0xd6 => 0x828d, 0xd7 => 0x52fa, 0xd8 => 0x97f6, + 0xd9 => 0x5c11, 0xda => 0x54e8, 0xdb => 0x90b5, 0xdc => 0x7ecd, + 0xdd => 0x5962, 0xde => 0x8d4a, 0xdf => 0x86c7, 0xe0 => 0x820c, + 0xe1 => 0x820d, 0xe2 => 0x8d66, 0xe3 => 0x6444, 0xe4 => 0x5c04, + 0xe5 => 0x6151, 0xe6 => 0x6d89, 0xe7 => 0x793e, 0xe8 => 0x8bbe, + 0xe9 => 0x7837, 0xea => 0x7533, 0xeb => 0x547b, 0xec => 0x4f38, + 0xed => 0x8eab, 0xee => 0x6df1, 0xef => 0x5a20, 0xf0 => 0x7ec5, + 0xf1 => 0x795e, 0xf2 => 0x6c88, 0xf3 => 0x5ba1, 0xf4 => 0x5a76, + 0xf5 => 0x751a, 0xf6 => 0x80be, 0xf7 => 0x614e, 0xf8 => 0x6e17, + 0xf9 => 0x58f0, 0xfa => 0x751f, 0xfb => 0x7525, 0xfc => 0x7272, + 0xfd => 0x5347, 0xfe => 0x7ef3, + }, + 0xca => { + 0xa1 => 0x7701, 0xa2 => 0x76db, 0xa3 => 0x5269, 0xa4 => 0x80dc, + 0xa5 => 0x5723, 0xa6 => 0x5e08, 0xa7 => 0x5931, 0xa8 => 0x72ee, + 0xa9 => 0x65bd, 0xaa => 0x6e7f, 0xab => 0x8bd7, 0xac => 0x5c38, + 0xad => 0x8671, 0xae => 0x5341, 0xaf => 0x77f3, 0xb0 => 0x62fe, + 0xb1 => 0x65f6, 0xb2 => 0x4ec0, 0xb3 => 0x98df, 0xb4 => 0x8680, + 0xb5 => 0x5b9e, 0xb6 => 0x8bc6, 0xb7 => 0x53f2, 0xb8 => 0x77e2, + 0xb9 => 0x4f7f, 0xba => 0x5c4e, 0xbb => 0x9a76, 0xbc => 0x59cb, + 0xbd => 0x5f0f, 0xbe => 0x793a, 0xbf => 0x58eb, 0xc0 => 0x4e16, + 0xc1 => 0x67ff, 0xc2 => 0x4e8b, 0xc3 => 0x62ed, 0xc4 => 0x8a93, + 0xc5 => 0x901d, 0xc6 => 0x52bf, 0xc7 => 0x662f, 0xc8 => 0x55dc, + 0xc9 => 0x566c, 0xca => 0x9002, 0xcb => 0x4ed5, 0xcc => 0x4f8d, + 0xcd => 0x91ca, 0xce => 0x9970, 0xcf => 0x6c0f, 0xd0 => 0x5e02, + 0xd1 => 0x6043, 0xd2 => 0x5ba4, 0xd3 => 0x89c6, 0xd4 => 0x8bd5, + 0xd5 => 0x6536, 0xd6 => 0x624b, 0xd7 => 0x9996, 0xd8 => 0x5b88, + 0xd9 => 0x5bff, 0xda => 0x6388, 0xdb => 0x552e, 0xdc => 0x53d7, + 0xdd => 0x7626, 0xde => 0x517d, 0xdf => 0x852c, 0xe0 => 0x67a2, + 0xe1 => 0x68b3, 0xe2 => 0x6b8a, 0xe3 => 0x6292, 0xe4 => 0x8f93, + 0xe5 => 0x53d4, 0xe6 => 0x8212, 0xe7 => 0x6dd1, 0xe8 => 0x758f, + 0xe9 => 0x4e66, 0xea => 0x8d4e, 0xeb => 0x5b70, 0xec => 0x719f, + 0xed => 0x85af, 0xee => 0x6691, 0xef => 0x66d9, 0xf0 => 0x7f72, + 0xf1 => 0x8700, 0xf2 => 0x9ecd, 0xf3 => 0x9f20, 0xf4 => 0x5c5e, + 0xf5 => 0x672f, 0xf6 => 0x8ff0, 0xf7 => 0x6811, 0xf8 => 0x675f, + 0xf9 => 0x620d, 0xfa => 0x7ad6, 0xfb => 0x5885, 0xfc => 0x5eb6, + 0xfd => 0x6570, 0xfe => 0x6f31, + }, + 0xcb => { + 0xa1 => 0x6055, 0xa2 => 0x5237, 0xa3 => 0x800d, 0xa4 => 0x6454, + 0xa5 => 0x8870, 0xa6 => 0x7529, 0xa7 => 0x5e05, 0xa8 => 0x6813, + 0xa9 => 0x62f4, 0xaa => 0x971c, 0xab => 0x53cc, 0xac => 0x723d, + 0xad => 0x8c01, 0xae => 0x6c34, 0xaf => 0x7761, 0xb0 => 0x7a0e, + 0xb1 => 0x542e, 0xb2 => 0x77ac, 0xb3 => 0x987a, 0xb4 => 0x821c, + 0xb5 => 0x8bf4, 0xb6 => 0x7855, 0xb7 => 0x6714, 0xb8 => 0x70c1, + 0xb9 => 0x65af, 0xba => 0x6495, 0xbb => 0x5636, 0xbc => 0x601d, + 0xbd => 0x79c1, 0xbe => 0x53f8, 0xbf => 0x4e1d, 0xc0 => 0x6b7b, + 0xc1 => 0x8086, 0xc2 => 0x5bfa, 0xc3 => 0x55e3, 0xc4 => 0x56db, + 0xc5 => 0x4f3a, 0xc6 => 0x4f3c, 0xc7 => 0x9972, 0xc8 => 0x5df3, + 0xc9 => 0x677e, 0xca => 0x8038, 0xcb => 0x6002, 0xcc => 0x9882, + 0xcd => 0x9001, 0xce => 0x5b8b, 0xcf => 0x8bbc, 0xd0 => 0x8bf5, + 0xd1 => 0x641c, 0xd2 => 0x8258, 0xd3 => 0x64de, 0xd4 => 0x55fd, + 0xd5 => 0x82cf, 0xd6 => 0x9165, 0xd7 => 0x4fd7, 0xd8 => 0x7d20, + 0xd9 => 0x901f, 0xda => 0x7c9f, 0xdb => 0x50f3, 0xdc => 0x5851, + 0xdd => 0x6eaf, 0xde => 0x5bbf, 0xdf => 0x8bc9, 0xe0 => 0x8083, + 0xe1 => 0x9178, 0xe2 => 0x849c, 0xe3 => 0x7b97, 0xe4 => 0x867d, + 0xe5 => 0x968b, 0xe6 => 0x968f, 0xe7 => 0x7ee5, 0xe8 => 0x9ad3, + 0xe9 => 0x788e, 0xea => 0x5c81, 0xeb => 0x7a57, 0xec => 0x9042, + 0xed => 0x96a7, 0xee => 0x795f, 0xef => 0x5b59, 0xf0 => 0x635f, + 0xf1 => 0x7b0b, 0xf2 => 0x84d1, 0xf3 => 0x68ad, 0xf4 => 0x5506, + 0xf5 => 0x7f29, 0xf6 => 0x7410, 0xf7 => 0x7d22, 0xf8 => 0x9501, + 0xf9 => 0x6240, 0xfa => 0x584c, 0xfb => 0x4ed6, 0xfc => 0x5b83, + 0xfd => 0x5979, 0xfe => 0x5854, + }, + 0xcc => { + 0xa1 => 0x736d, 0xa2 => 0x631e, 0xa3 => 0x8e4b, 0xa4 => 0x8e0f, + 0xa5 => 0x80ce, 0xa6 => 0x82d4, 0xa7 => 0x62ac, 0xa8 => 0x53f0, + 0xa9 => 0x6cf0, 0xaa => 0x915e, 0xab => 0x592a, 0xac => 0x6001, + 0xad => 0x6c70, 0xae => 0x574d, 0xaf => 0x644a, 0xb0 => 0x8d2a, + 0xb1 => 0x762b, 0xb2 => 0x6ee9, 0xb3 => 0x575b, 0xb4 => 0x6a80, + 0xb5 => 0x75f0, 0xb6 => 0x6f6d, 0xb7 => 0x8c2d, 0xb8 => 0x8c08, + 0xb9 => 0x5766, 0xba => 0x6bef, 0xbb => 0x8892, 0xbc => 0x78b3, + 0xbd => 0x63a2, 0xbe => 0x53f9, 0xbf => 0x70ad, 0xc0 => 0x6c64, + 0xc1 => 0x5858, 0xc2 => 0x642a, 0xc3 => 0x5802, 0xc4 => 0x68e0, + 0xc5 => 0x819b, 0xc6 => 0x5510, 0xc7 => 0x7cd6, 0xc8 => 0x5018, + 0xc9 => 0x8eba, 0xca => 0x6dcc, 0xcb => 0x8d9f, 0xcc => 0x70eb, + 0xcd => 0x638f, 0xce => 0x6d9b, 0xcf => 0x6ed4, 0xd0 => 0x7ee6, + 0xd1 => 0x8404, 0xd2 => 0x6843, 0xd3 => 0x9003, 0xd4 => 0x6dd8, + 0xd5 => 0x9676, 0xd6 => 0x8ba8, 0xd7 => 0x5957, 0xd8 => 0x7279, + 0xd9 => 0x85e4, 0xda => 0x817e, 0xdb => 0x75bc, 0xdc => 0x8a8a, + 0xdd => 0x68af, 0xde => 0x5254, 0xdf => 0x8e22, 0xe0 => 0x9511, + 0xe1 => 0x63d0, 0xe2 => 0x9898, 0xe3 => 0x8e44, 0xe4 => 0x557c, + 0xe5 => 0x4f53, 0xe6 => 0x66ff, 0xe7 => 0x568f, 0xe8 => 0x60d5, + 0xe9 => 0x6d95, 0xea => 0x5243, 0xeb => 0x5c49, 0xec => 0x5929, + 0xed => 0x6dfb, 0xee => 0x586b, 0xef => 0x7530, 0xf0 => 0x751c, + 0xf1 => 0x606c, 0xf2 => 0x8214, 0xf3 => 0x8146, 0xf4 => 0x6311, + 0xf5 => 0x6761, 0xf6 => 0x8fe2, 0xf7 => 0x773a, 0xf8 => 0x8df3, + 0xf9 => 0x8d34, 0xfa => 0x94c1, 0xfb => 0x5e16, 0xfc => 0x5385, + 0xfd => 0x542c, 0xfe => 0x70c3, + }, + 0xcd => { + 0xa1 => 0x6c40, 0xa2 => 0x5ef7, 0xa3 => 0x505c, 0xa4 => 0x4ead, + 0xa5 => 0x5ead, 0xa6 => 0x633a, 0xa7 => 0x8247, 0xa8 => 0x901a, + 0xa9 => 0x6850, 0xaa => 0x916e, 0xab => 0x77b3, 0xac => 0x540c, + 0xad => 0x94dc, 0xae => 0x5f64, 0xaf => 0x7ae5, 0xb0 => 0x6876, + 0xb1 => 0x6345, 0xb2 => 0x7b52, 0xb3 => 0x7edf, 0xb4 => 0x75db, + 0xb5 => 0x5077, 0xb6 => 0x6295, 0xb7 => 0x5934, 0xb8 => 0x900f, + 0xb9 => 0x51f8, 0xba => 0x79c3, 0xbb => 0x7a81, 0xbc => 0x56fe, + 0xbd => 0x5f92, 0xbe => 0x9014, 0xbf => 0x6d82, 0xc0 => 0x5c60, + 0xc1 => 0x571f, 0xc2 => 0x5410, 0xc3 => 0x5154, 0xc4 => 0x6e4d, + 0xc5 => 0x56e2, 0xc6 => 0x63a8, 0xc7 => 0x9893, 0xc8 => 0x817f, + 0xc9 => 0x8715, 0xca => 0x892a, 0xcb => 0x9000, 0xcc => 0x541e, + 0xcd => 0x5c6f, 0xce => 0x81c0, 0xcf => 0x62d6, 0xd0 => 0x6258, + 0xd1 => 0x8131, 0xd2 => 0x9e35, 0xd3 => 0x9640, 0xd4 => 0x9a6e, + 0xd5 => 0x9a7c, 0xd6 => 0x692d, 0xd7 => 0x59a5, 0xd8 => 0x62d3, + 0xd9 => 0x553e, 0xda => 0x6316, 0xdb => 0x54c7, 0xdc => 0x86d9, + 0xdd => 0x6d3c, 0xde => 0x5a03, 0xdf => 0x74e6, 0xe0 => 0x889c, + 0xe1 => 0x6b6a, 0xe2 => 0x5916, 0xe3 => 0x8c4c, 0xe4 => 0x5f2f, + 0xe5 => 0x6e7e, 0xe6 => 0x73a9, 0xe7 => 0x987d, 0xe8 => 0x4e38, + 0xe9 => 0x70f7, 0xea => 0x5b8c, 0xeb => 0x7897, 0xec => 0x633d, + 0xed => 0x665a, 0xee => 0x7696, 0xef => 0x60cb, 0xf0 => 0x5b9b, + 0xf1 => 0x5a49, 0xf2 => 0x4e07, 0xf3 => 0x8155, 0xf4 => 0x6c6a, + 0xf5 => 0x738b, 0xf6 => 0x4ea1, 0xf7 => 0x6789, 0xf8 => 0x7f51, + 0xf9 => 0x5f80, 0xfa => 0x65fa, 0xfb => 0x671b, 0xfc => 0x5fd8, + 0xfd => 0x5984, 0xfe => 0x5a01, + }, + 0xce => { + 0xa1 => 0x5dcd, 0xa2 => 0x5fae, 0xa3 => 0x5371, 0xa4 => 0x97e6, + 0xa5 => 0x8fdd, 0xa6 => 0x6845, 0xa7 => 0x56f4, 0xa8 => 0x552f, + 0xa9 => 0x60df, 0xaa => 0x4e3a, 0xab => 0x6f4d, 0xac => 0x7ef4, + 0xad => 0x82c7, 0xae => 0x840e, 0xaf => 0x59d4, 0xb0 => 0x4f1f, + 0xb1 => 0x4f2a, 0xb2 => 0x5c3e, 0xb3 => 0x7eac, 0xb4 => 0x672a, + 0xb5 => 0x851a, 0xb6 => 0x5473, 0xb7 => 0x754f, 0xb8 => 0x80c3, + 0xb9 => 0x5582, 0xba => 0x9b4f, 0xbb => 0x4f4d, 0xbc => 0x6e2d, + 0xbd => 0x8c13, 0xbe => 0x5c09, 0xbf => 0x6170, 0xc0 => 0x536b, + 0xc1 => 0x761f, 0xc2 => 0x6e29, 0xc3 => 0x868a, 0xc4 => 0x6587, + 0xc5 => 0x95fb, 0xc6 => 0x7eb9, 0xc7 => 0x543b, 0xc8 => 0x7a33, + 0xc9 => 0x7d0a, 0xca => 0x95ee, 0xcb => 0x55e1, 0xcc => 0x7fc1, + 0xcd => 0x74ee, 0xce => 0x631d, 0xcf => 0x8717, 0xd0 => 0x6da1, + 0xd1 => 0x7a9d, 0xd2 => 0x6211, 0xd3 => 0x65a1, 0xd4 => 0x5367, + 0xd5 => 0x63e1, 0xd6 => 0x6c83, 0xd7 => 0x5deb, 0xd8 => 0x545c, + 0xd9 => 0x94a8, 0xda => 0x4e4c, 0xdb => 0x6c61, 0xdc => 0x8bec, + 0xdd => 0x5c4b, 0xde => 0x65e0, 0xdf => 0x829c, 0xe0 => 0x68a7, + 0xe1 => 0x543e, 0xe2 => 0x5434, 0xe3 => 0x6bcb, 0xe4 => 0x6b66, + 0xe5 => 0x4e94, 0xe6 => 0x6342, 0xe7 => 0x5348, 0xe8 => 0x821e, + 0xe9 => 0x4f0d, 0xea => 0x4fae, 0xeb => 0x575e, 0xec => 0x620a, + 0xed => 0x96fe, 0xee => 0x6664, 0xef => 0x7269, 0xf0 => 0x52ff, + 0xf1 => 0x52a1, 0xf2 => 0x609f, 0xf3 => 0x8bef, 0xf4 => 0x6614, + 0xf5 => 0x7199, 0xf6 => 0x6790, 0xf7 => 0x897f, 0xf8 => 0x7852, + 0xf9 => 0x77fd, 0xfa => 0x6670, 0xfb => 0x563b, 0xfc => 0x5438, + 0xfd => 0x9521, 0xfe => 0x727a, + }, + 0xcf => { + 0xa1 => 0x7a00, 0xa2 => 0x606f, 0xa3 => 0x5e0c, 0xa4 => 0x6089, + 0xa5 => 0x819d, 0xa6 => 0x5915, 0xa7 => 0x60dc, 0xa8 => 0x7184, + 0xa9 => 0x70ef, 0xaa => 0x6eaa, 0xab => 0x6c50, 0xac => 0x7280, + 0xad => 0x6a84, 0xae => 0x88ad, 0xaf => 0x5e2d, 0xb0 => 0x4e60, + 0xb1 => 0x5ab3, 0xb2 => 0x559c, 0xb3 => 0x94e3, 0xb4 => 0x6d17, + 0xb5 => 0x7cfb, 0xb6 => 0x9699, 0xb7 => 0x620f, 0xb8 => 0x7ec6, + 0xb9 => 0x778e, 0xba => 0x867e, 0xbb => 0x5323, 0xbc => 0x971e, + 0xbd => 0x8f96, 0xbe => 0x6687, 0xbf => 0x5ce1, 0xc0 => 0x4fa0, + 0xc1 => 0x72ed, 0xc2 => 0x4e0b, 0xc3 => 0x53a6, 0xc4 => 0x590f, + 0xc5 => 0x5413, 0xc6 => 0x6380, 0xc7 => 0x9528, 0xc8 => 0x5148, + 0xc9 => 0x4ed9, 0xca => 0x9c9c, 0xcb => 0x7ea4, 0xcc => 0x54b8, + 0xcd => 0x8d24, 0xce => 0x8854, 0xcf => 0x8237, 0xd0 => 0x95f2, + 0xd1 => 0x6d8e, 0xd2 => 0x5f26, 0xd3 => 0x5acc, 0xd4 => 0x663e, + 0xd5 => 0x9669, 0xd6 => 0x73b0, 0xd7 => 0x732e, 0xd8 => 0x53bf, + 0xd9 => 0x817a, 0xda => 0x9985, 0xdb => 0x7fa1, 0xdc => 0x5baa, + 0xdd => 0x9677, 0xde => 0x9650, 0xdf => 0x7ebf, 0xe0 => 0x76f8, + 0xe1 => 0x53a2, 0xe2 => 0x9576, 0xe3 => 0x9999, 0xe4 => 0x7bb1, + 0xe5 => 0x8944, 0xe6 => 0x6e58, 0xe7 => 0x4e61, 0xe8 => 0x7fd4, + 0xe9 => 0x7965, 0xea => 0x8be6, 0xeb => 0x60f3, 0xec => 0x54cd, + 0xed => 0x4eab, 0xee => 0x9879, 0xef => 0x5df7, 0xf0 => 0x6a61, + 0xf1 => 0x50cf, 0xf2 => 0x5411, 0xf3 => 0x8c61, 0xf4 => 0x8427, + 0xf5 => 0x785d, 0xf6 => 0x9704, 0xf7 => 0x524a, 0xf8 => 0x54ee, + 0xf9 => 0x56a3, 0xfa => 0x9500, 0xfb => 0x6d88, 0xfc => 0x5bb5, + 0xfd => 0x6dc6, 0xfe => 0x6653, + }, + 0xd0 => { + 0xa1 => 0x5c0f, 0xa2 => 0x5b5d, 0xa3 => 0x6821, 0xa4 => 0x8096, + 0xa5 => 0x5578, 0xa6 => 0x7b11, 0xa7 => 0x6548, 0xa8 => 0x6954, + 0xa9 => 0x4e9b, 0xaa => 0x6b47, 0xab => 0x874e, 0xac => 0x978b, + 0xad => 0x534f, 0xae => 0x631f, 0xaf => 0x643a, 0xb0 => 0x90aa, + 0xb1 => 0x659c, 0xb2 => 0x80c1, 0xb3 => 0x8c10, 0xb4 => 0x5199, + 0xb5 => 0x68b0, 0xb6 => 0x5378, 0xb7 => 0x87f9, 0xb8 => 0x61c8, + 0xb9 => 0x6cc4, 0xba => 0x6cfb, 0xbb => 0x8c22, 0xbc => 0x5c51, + 0xbd => 0x85aa, 0xbe => 0x82af, 0xbf => 0x950c, 0xc0 => 0x6b23, + 0xc1 => 0x8f9b, 0xc2 => 0x65b0, 0xc3 => 0x5ffb, 0xc4 => 0x5fc3, + 0xc5 => 0x4fe1, 0xc6 => 0x8845, 0xc7 => 0x661f, 0xc8 => 0x8165, + 0xc9 => 0x7329, 0xca => 0x60fa, 0xcb => 0x5174, 0xcc => 0x5211, + 0xcd => 0x578b, 0xce => 0x5f62, 0xcf => 0x90a2, 0xd0 => 0x884c, + 0xd1 => 0x9192, 0xd2 => 0x5e78, 0xd3 => 0x674f, 0xd4 => 0x6027, + 0xd5 => 0x59d3, 0xd6 => 0x5144, 0xd7 => 0x51f6, 0xd8 => 0x80f8, + 0xd9 => 0x5308, 0xda => 0x6c79, 0xdb => 0x96c4, 0xdc => 0x718a, + 0xdd => 0x4f11, 0xde => 0x4fee, 0xdf => 0x7f9e, 0xe0 => 0x673d, + 0xe1 => 0x55c5, 0xe2 => 0x9508, 0xe3 => 0x79c0, 0xe4 => 0x8896, + 0xe5 => 0x7ee3, 0xe6 => 0x589f, 0xe7 => 0x620c, 0xe8 => 0x9700, + 0xe9 => 0x865a, 0xea => 0x5618, 0xeb => 0x987b, 0xec => 0x5f90, + 0xed => 0x8bb8, 0xee => 0x84c4, 0xef => 0x9157, 0xf0 => 0x53d9, + 0xf1 => 0x65ed, 0xf2 => 0x5e8f, 0xf3 => 0x755c, 0xf4 => 0x6064, + 0xf5 => 0x7d6e, 0xf6 => 0x5a7f, 0xf7 => 0x7eea, 0xf8 => 0x7eed, + 0xf9 => 0x8f69, 0xfa => 0x55a7, 0xfb => 0x5ba3, 0xfc => 0x60ac, + 0xfd => 0x65cb, 0xfe => 0x7384, + }, + 0xd1 => { + 0xa1 => 0x9009, 0xa2 => 0x7663, 0xa3 => 0x7729, 0xa4 => 0x7eda, + 0xa5 => 0x9774, 0xa6 => 0x859b, 0xa7 => 0x5b66, 0xa8 => 0x7a74, + 0xa9 => 0x96ea, 0xaa => 0x8840, 0xab => 0x52cb, 0xac => 0x718f, + 0xad => 0x5faa, 0xae => 0x65ec, 0xaf => 0x8be2, 0xb0 => 0x5bfb, + 0xb1 => 0x9a6f, 0xb2 => 0x5de1, 0xb3 => 0x6b89, 0xb4 => 0x6c5b, + 0xb5 => 0x8bad, 0xb6 => 0x8baf, 0xb7 => 0x900a, 0xb8 => 0x8fc5, + 0xb9 => 0x538b, 0xba => 0x62bc, 0xbb => 0x9e26, 0xbc => 0x9e2d, + 0xbd => 0x5440, 0xbe => 0x4e2b, 0xbf => 0x82bd, 0xc0 => 0x7259, + 0xc1 => 0x869c, 0xc2 => 0x5d16, 0xc3 => 0x8859, 0xc4 => 0x6daf, + 0xc5 => 0x96c5, 0xc6 => 0x54d1, 0xc7 => 0x4e9a, 0xc8 => 0x8bb6, + 0xc9 => 0x7109, 0xca => 0x54bd, 0xcb => 0x9609, 0xcc => 0x70df, + 0xcd => 0x6df9, 0xce => 0x76d0, 0xcf => 0x4e25, 0xd0 => 0x7814, + 0xd1 => 0x8712, 0xd2 => 0x5ca9, 0xd3 => 0x5ef6, 0xd4 => 0x8a00, + 0xd5 => 0x989c, 0xd6 => 0x960e, 0xd7 => 0x708e, 0xd8 => 0x6cbf, + 0xd9 => 0x5944, 0xda => 0x63a9, 0xdb => 0x773c, 0xdc => 0x884d, + 0xdd => 0x6f14, 0xde => 0x8273, 0xdf => 0x5830, 0xe0 => 0x71d5, + 0xe1 => 0x538c, 0xe2 => 0x781a, 0xe3 => 0x96c1, 0xe4 => 0x5501, + 0xe5 => 0x5f66, 0xe6 => 0x7130, 0xe7 => 0x5bb4, 0xe8 => 0x8c1a, + 0xe9 => 0x9a8c, 0xea => 0x6b83, 0xeb => 0x592e, 0xec => 0x9e2f, + 0xed => 0x79e7, 0xee => 0x6768, 0xef => 0x626c, 0xf0 => 0x4f6f, + 0xf1 => 0x75a1, 0xf2 => 0x7f8a, 0xf3 => 0x6d0b, 0xf4 => 0x9633, + 0xf5 => 0x6c27, 0xf6 => 0x4ef0, 0xf7 => 0x75d2, 0xf8 => 0x517b, + 0xf9 => 0x6837, 0xfa => 0x6f3e, 0xfb => 0x9080, 0xfc => 0x8170, + 0xfd => 0x5996, 0xfe => 0x7476, + }, + 0xd2 => { + 0xa1 => 0x6447, 0xa2 => 0x5c27, 0xa3 => 0x9065, 0xa4 => 0x7a91, + 0xa5 => 0x8c23, 0xa6 => 0x59da, 0xa7 => 0x54ac, 0xa8 => 0x8200, + 0xa9 => 0x836f, 0xaa => 0x8981, 0xab => 0x8000, 0xac => 0x6930, + 0xad => 0x564e, 0xae => 0x8036, 0xaf => 0x7237, 0xb0 => 0x91ce, + 0xb1 => 0x51b6, 0xb2 => 0x4e5f, 0xb3 => 0x9875, 0xb4 => 0x6396, + 0xb5 => 0x4e1a, 0xb6 => 0x53f6, 0xb7 => 0x66f3, 0xb8 => 0x814b, + 0xb9 => 0x591c, 0xba => 0x6db2, 0xbb => 0x4e00, 0xbc => 0x58f9, + 0xbd => 0x533b, 0xbe => 0x63d6, 0xbf => 0x94f1, 0xc0 => 0x4f9d, + 0xc1 => 0x4f0a, 0xc2 => 0x8863, 0xc3 => 0x9890, 0xc4 => 0x5937, + 0xc5 => 0x9057, 0xc6 => 0x79fb, 0xc7 => 0x4eea, 0xc8 => 0x80f0, + 0xc9 => 0x7591, 0xca => 0x6c82, 0xcb => 0x5b9c, 0xcc => 0x59e8, + 0xcd => 0x5f5d, 0xce => 0x6905, 0xcf => 0x8681, 0xd0 => 0x501a, + 0xd1 => 0x5df2, 0xd2 => 0x4e59, 0xd3 => 0x77e3, 0xd4 => 0x4ee5, + 0xd5 => 0x827a, 0xd6 => 0x6291, 0xd7 => 0x6613, 0xd8 => 0x9091, + 0xd9 => 0x5c79, 0xda => 0x4ebf, 0xdb => 0x5f79, 0xdc => 0x81c6, + 0xdd => 0x9038, 0xde => 0x8084, 0xdf => 0x75ab, 0xe0 => 0x4ea6, + 0xe1 => 0x88d4, 0xe2 => 0x610f, 0xe3 => 0x6bc5, 0xe4 => 0x5fc6, + 0xe5 => 0x4e49, 0xe6 => 0x76ca, 0xe7 => 0x6ea2, 0xe8 => 0x8be3, + 0xe9 => 0x8bae, 0xea => 0x8c0a, 0xeb => 0x8bd1, 0xec => 0x5f02, + 0xed => 0x7ffc, 0xee => 0x7fcc, 0xef => 0x7ece, 0xf0 => 0x8335, + 0xf1 => 0x836b, 0xf2 => 0x56e0, 0xf3 => 0x6bb7, 0xf4 => 0x97f3, + 0xf5 => 0x9634, 0xf6 => 0x59fb, 0xf7 => 0x541f, 0xf8 => 0x94f6, + 0xf9 => 0x6deb, 0xfa => 0x5bc5, 0xfb => 0x996e, 0xfc => 0x5c39, + 0xfd => 0x5f15, 0xfe => 0x9690, + }, + 0xd3 => { + 0xa1 => 0x5370, 0xa2 => 0x82f1, 0xa3 => 0x6a31, 0xa4 => 0x5a74, + 0xa5 => 0x9e70, 0xa6 => 0x5e94, 0xa7 => 0x7f28, 0xa8 => 0x83b9, + 0xa9 => 0x8424, 0xaa => 0x8425, 0xab => 0x8367, 0xac => 0x8747, + 0xad => 0x8fce, 0xae => 0x8d62, 0xaf => 0x76c8, 0xb0 => 0x5f71, + 0xb1 => 0x9896, 0xb2 => 0x786c, 0xb3 => 0x6620, 0xb4 => 0x54df, + 0xb5 => 0x62e5, 0xb6 => 0x4f63, 0xb7 => 0x81c3, 0xb8 => 0x75c8, + 0xb9 => 0x5eb8, 0xba => 0x96cd, 0xbb => 0x8e0a, 0xbc => 0x86f9, + 0xbd => 0x548f, 0xbe => 0x6cf3, 0xbf => 0x6d8c, 0xc0 => 0x6c38, + 0xc1 => 0x607f, 0xc2 => 0x52c7, 0xc3 => 0x7528, 0xc4 => 0x5e7d, + 0xc5 => 0x4f18, 0xc6 => 0x60a0, 0xc7 => 0x5fe7, 0xc8 => 0x5c24, + 0xc9 => 0x7531, 0xca => 0x90ae, 0xcb => 0x94c0, 0xcc => 0x72b9, + 0xcd => 0x6cb9, 0xce => 0x6e38, 0xcf => 0x9149, 0xd0 => 0x6709, + 0xd1 => 0x53cb, 0xd2 => 0x53f3, 0xd3 => 0x4f51, 0xd4 => 0x91c9, + 0xd5 => 0x8bf1, 0xd6 => 0x53c8, 0xd7 => 0x5e7c, 0xd8 => 0x8fc2, + 0xd9 => 0x6de4, 0xda => 0x4e8e, 0xdb => 0x76c2, 0xdc => 0x6986, + 0xdd => 0x865e, 0xde => 0x611a, 0xdf => 0x8206, 0xe0 => 0x4f59, + 0xe1 => 0x4fde, 0xe2 => 0x903e, 0xe3 => 0x9c7c, 0xe4 => 0x6109, + 0xe5 => 0x6e1d, 0xe6 => 0x6e14, 0xe7 => 0x9685, 0xe8 => 0x4e88, + 0xe9 => 0x5a31, 0xea => 0x96e8, 0xeb => 0x4e0e, 0xec => 0x5c7f, + 0xed => 0x79b9, 0xee => 0x5b87, 0xef => 0x8bed, 0xf0 => 0x7fbd, + 0xf1 => 0x7389, 0xf2 => 0x57df, 0xf3 => 0x828b, 0xf4 => 0x90c1, + 0xf5 => 0x5401, 0xf6 => 0x9047, 0xf7 => 0x55bb, 0xf8 => 0x5cea, + 0xf9 => 0x5fa1, 0xfa => 0x6108, 0xfb => 0x6b32, 0xfc => 0x72f1, + 0xfd => 0x80b2, 0xfe => 0x8a89, + }, + 0xd4 => { + 0xa1 => 0x6d74, 0xa2 => 0x5bd3, 0xa3 => 0x88d5, 0xa4 => 0x9884, + 0xa5 => 0x8c6b, 0xa6 => 0x9a6d, 0xa7 => 0x9e33, 0xa8 => 0x6e0a, + 0xa9 => 0x51a4, 0xaa => 0x5143, 0xab => 0x57a3, 0xac => 0x8881, + 0xad => 0x539f, 0xae => 0x63f4, 0xaf => 0x8f95, 0xb0 => 0x56ed, + 0xb1 => 0x5458, 0xb2 => 0x5706, 0xb3 => 0x733f, 0xb4 => 0x6e90, + 0xb5 => 0x7f18, 0xb6 => 0x8fdc, 0xb7 => 0x82d1, 0xb8 => 0x613f, + 0xb9 => 0x6028, 0xba => 0x9662, 0xbb => 0x66f0, 0xbc => 0x7ea6, + 0xbd => 0x8d8a, 0xbe => 0x8dc3, 0xbf => 0x94a5, 0xc0 => 0x5cb3, + 0xc1 => 0x7ca4, 0xc2 => 0x6708, 0xc3 => 0x60a6, 0xc4 => 0x9605, + 0xc5 => 0x8018, 0xc6 => 0x4e91, 0xc7 => 0x90e7, 0xc8 => 0x5300, + 0xc9 => 0x9668, 0xca => 0x5141, 0xcb => 0x8fd0, 0xcc => 0x8574, + 0xcd => 0x915d, 0xce => 0x6655, 0xcf => 0x97f5, 0xd0 => 0x5b55, + 0xd1 => 0x531d, 0xd2 => 0x7838, 0xd3 => 0x6742, 0xd4 => 0x683d, + 0xd5 => 0x54c9, 0xd6 => 0x707e, 0xd7 => 0x5bb0, 0xd8 => 0x8f7d, + 0xd9 => 0x518d, 0xda => 0x5728, 0xdb => 0x54b1, 0xdc => 0x6512, + 0xdd => 0x6682, 0xde => 0x8d5e, 0xdf => 0x8d43, 0xe0 => 0x810f, + 0xe1 => 0x846c, 0xe2 => 0x906d, 0xe3 => 0x7cdf, 0xe4 => 0x51ff, + 0xe5 => 0x85fb, 0xe6 => 0x67a3, 0xe7 => 0x65e9, 0xe8 => 0x6fa1, + 0xe9 => 0x86a4, 0xea => 0x8e81, 0xeb => 0x566a, 0xec => 0x9020, + 0xed => 0x7682, 0xee => 0x7076, 0xef => 0x71e5, 0xf0 => 0x8d23, + 0xf1 => 0x62e9, 0xf2 => 0x5219, 0xf3 => 0x6cfd, 0xf4 => 0x8d3c, + 0xf5 => 0x600e, 0xf6 => 0x589e, 0xf7 => 0x618e, 0xf8 => 0x66fe, + 0xf9 => 0x8d60, 0xfa => 0x624e, 0xfb => 0x55b3, 0xfc => 0x6e23, + 0xfd => 0x672d, 0xfe => 0x8f67, + }, + 0xd5 => { + 0xa1 => 0x94e1, 0xa2 => 0x95f8, 0xa3 => 0x7728, 0xa4 => 0x6805, + 0xa5 => 0x69a8, 0xa6 => 0x548b, 0xa7 => 0x4e4d, 0xa8 => 0x70b8, + 0xa9 => 0x8bc8, 0xaa => 0x6458, 0xab => 0x658b, 0xac => 0x5b85, + 0xad => 0x7a84, 0xae => 0x503a, 0xaf => 0x5be8, 0xb0 => 0x77bb, + 0xb1 => 0x6be1, 0xb2 => 0x8a79, 0xb3 => 0x7c98, 0xb4 => 0x6cbe, + 0xb5 => 0x76cf, 0xb6 => 0x65a9, 0xb7 => 0x8f97, 0xb8 => 0x5d2d, + 0xb9 => 0x5c55, 0xba => 0x8638, 0xbb => 0x6808, 0xbc => 0x5360, + 0xbd => 0x6218, 0xbe => 0x7ad9, 0xbf => 0x6e5b, 0xc0 => 0x7efd, + 0xc1 => 0x6a1f, 0xc2 => 0x7ae0, 0xc3 => 0x5f70, 0xc4 => 0x6f33, + 0xc5 => 0x5f20, 0xc6 => 0x638c, 0xc7 => 0x6da8, 0xc8 => 0x6756, + 0xc9 => 0x4e08, 0xca => 0x5e10, 0xcb => 0x8d26, 0xcc => 0x4ed7, + 0xcd => 0x80c0, 0xce => 0x7634, 0xcf => 0x969c, 0xd0 => 0x62db, + 0xd1 => 0x662d, 0xd2 => 0x627e, 0xd3 => 0x6cbc, 0xd4 => 0x8d75, + 0xd5 => 0x7167, 0xd6 => 0x7f69, 0xd7 => 0x5146, 0xd8 => 0x8087, + 0xd9 => 0x53ec, 0xda => 0x906e, 0xdb => 0x6298, 0xdc => 0x54f2, + 0xdd => 0x86f0, 0xde => 0x8f99, 0xdf => 0x8005, 0xe0 => 0x9517, + 0xe1 => 0x8517, 0xe2 => 0x8fd9, 0xe3 => 0x6d59, 0xe4 => 0x73cd, + 0xe5 => 0x659f, 0xe6 => 0x771f, 0xe7 => 0x7504, 0xe8 => 0x7827, + 0xe9 => 0x81fb, 0xea => 0x8d1e, 0xeb => 0x9488, 0xec => 0x4fa6, + 0xed => 0x6795, 0xee => 0x75b9, 0xef => 0x8bca, 0xf0 => 0x9707, + 0xf1 => 0x632f, 0xf2 => 0x9547, 0xf3 => 0x9635, 0xf4 => 0x84b8, + 0xf5 => 0x6323, 0xf6 => 0x7741, 0xf7 => 0x5f81, 0xf8 => 0x72f0, + 0xf9 => 0x4e89, 0xfa => 0x6014, 0xfb => 0x6574, 0xfc => 0x62ef, + 0xfd => 0x6b63, 0xfe => 0x653f, + }, + 0xd6 => { + 0xa1 => 0x5e27, 0xa2 => 0x75c7, 0xa3 => 0x90d1, 0xa4 => 0x8bc1, + 0xa5 => 0x829d, 0xa6 => 0x679d, 0xa7 => 0x652f, 0xa8 => 0x5431, + 0xa9 => 0x8718, 0xaa => 0x77e5, 0xab => 0x80a2, 0xac => 0x8102, + 0xad => 0x6c41, 0xae => 0x4e4b, 0xaf => 0x7ec7, 0xb0 => 0x804c, + 0xb1 => 0x76f4, 0xb2 => 0x690d, 0xb3 => 0x6b96, 0xb4 => 0x6267, + 0xb5 => 0x503c, 0xb6 => 0x4f84, 0xb7 => 0x5740, 0xb8 => 0x6307, + 0xb9 => 0x6b62, 0xba => 0x8dbe, 0xbb => 0x53ea, 0xbc => 0x65e8, + 0xbd => 0x7eb8, 0xbe => 0x5fd7, 0xbf => 0x631a, 0xc0 => 0x63b7, + 0xc1 => 0x81f3, 0xc2 => 0x81f4, 0xc3 => 0x7f6e, 0xc4 => 0x5e1c, + 0xc5 => 0x5cd9, 0xc6 => 0x5236, 0xc7 => 0x667a, 0xc8 => 0x79e9, + 0xc9 => 0x7a1a, 0xca => 0x8d28, 0xcb => 0x7099, 0xcc => 0x75d4, + 0xcd => 0x6ede, 0xce => 0x6cbb, 0xcf => 0x7a92, 0xd0 => 0x4e2d, + 0xd1 => 0x76c5, 0xd2 => 0x5fe0, 0xd3 => 0x949f, 0xd4 => 0x8877, + 0xd5 => 0x7ec8, 0xd6 => 0x79cd, 0xd7 => 0x80bf, 0xd8 => 0x91cd, + 0xd9 => 0x4ef2, 0xda => 0x4f17, 0xdb => 0x821f, 0xdc => 0x5468, + 0xdd => 0x5dde, 0xde => 0x6d32, 0xdf => 0x8bcc, 0xe0 => 0x7ca5, + 0xe1 => 0x8f74, 0xe2 => 0x8098, 0xe3 => 0x5e1a, 0xe4 => 0x5492, + 0xe5 => 0x76b1, 0xe6 => 0x5b99, 0xe7 => 0x663c, 0xe8 => 0x9aa4, + 0xe9 => 0x73e0, 0xea => 0x682a, 0xeb => 0x86db, 0xec => 0x6731, + 0xed => 0x732a, 0xee => 0x8bf8, 0xef => 0x8bdb, 0xf0 => 0x9010, + 0xf1 => 0x7af9, 0xf2 => 0x70db, 0xf3 => 0x716e, 0xf4 => 0x62c4, + 0xf5 => 0x77a9, 0xf6 => 0x5631, 0xf7 => 0x4e3b, 0xf8 => 0x8457, + 0xf9 => 0x67f1, 0xfa => 0x52a9, 0xfb => 0x86c0, 0xfc => 0x8d2e, + 0xfd => 0x94f8, 0xfe => 0x7b51, + }, + 0xd7 => { + 0xa1 => 0x4f4f, 0xa2 => 0x6ce8, 0xa3 => 0x795d, 0xa4 => 0x9a7b, + 0xa5 => 0x6293, 0xa6 => 0x722a, 0xa7 => 0x62fd, 0xa8 => 0x4e13, + 0xa9 => 0x7816, 0xaa => 0x8f6c, 0xab => 0x64b0, 0xac => 0x8d5a, + 0xad => 0x7bc6, 0xae => 0x6869, 0xaf => 0x5e84, 0xb0 => 0x88c5, + 0xb1 => 0x5986, 0xb2 => 0x649e, 0xb3 => 0x58ee, 0xb4 => 0x72b6, + 0xb5 => 0x690e, 0xb6 => 0x9525, 0xb7 => 0x8ffd, 0xb8 => 0x8d58, + 0xb9 => 0x5760, 0xba => 0x7f00, 0xbb => 0x8c06, 0xbc => 0x51c6, + 0xbd => 0x6349, 0xbe => 0x62d9, 0xbf => 0x5353, 0xc0 => 0x684c, + 0xc1 => 0x7422, 0xc2 => 0x8301, 0xc3 => 0x914c, 0xc4 => 0x5544, + 0xc5 => 0x7740, 0xc6 => 0x707c, 0xc7 => 0x6d4a, 0xc8 => 0x5179, + 0xc9 => 0x54a8, 0xca => 0x8d44, 0xcb => 0x59ff, 0xcc => 0x6ecb, + 0xcd => 0x6dc4, 0xce => 0x5b5c, 0xcf => 0x7d2b, 0xd0 => 0x4ed4, + 0xd1 => 0x7c7d, 0xd2 => 0x6ed3, 0xd3 => 0x5b50, 0xd4 => 0x81ea, + 0xd5 => 0x6e0d, 0xd6 => 0x5b57, 0xd7 => 0x9b03, 0xd8 => 0x68d5, + 0xd9 => 0x8e2a, 0xda => 0x5b97, 0xdb => 0x7efc, 0xdc => 0x603b, + 0xdd => 0x7eb5, 0xde => 0x90b9, 0xdf => 0x8d70, 0xe0 => 0x594f, + 0xe1 => 0x63cd, 0xe2 => 0x79df, 0xe3 => 0x8db3, 0xe4 => 0x5352, + 0xe5 => 0x65cf, 0xe6 => 0x7956, 0xe7 => 0x8bc5, 0xe8 => 0x963b, + 0xe9 => 0x7ec4, 0xea => 0x94bb, 0xeb => 0x7e82, 0xec => 0x5634, + 0xed => 0x9189, 0xee => 0x6700, 0xef => 0x7f6a, 0xf0 => 0x5c0a, + 0xf1 => 0x9075, 0xf2 => 0x6628, 0xf3 => 0x5de6, 0xf4 => 0x4f50, + 0xf5 => 0x67de, 0xf6 => 0x505a, 0xf7 => 0x4f5c, 0xf8 => 0x5750, + 0xf9 => 0x5ea7, + }, + 0xd8 => { + 0xa1 => 0x4e8d, 0xa2 => 0x4e0c, 0xa3 => 0x5140, 0xa4 => 0x4e10, + 0xa5 => 0x5eff, 0xa6 => 0x5345, 0xa7 => 0x4e15, 0xa8 => 0x4e98, + 0xa9 => 0x4e1e, 0xaa => 0x9b32, 0xab => 0x5b6c, 0xac => 0x5669, + 0xad => 0x4e28, 0xae => 0x79ba, 0xaf => 0x4e3f, 0xb0 => 0x5315, + 0xb1 => 0x4e47, 0xb2 => 0x592d, 0xb3 => 0x723b, 0xb4 => 0x536e, + 0xb5 => 0x6c10, 0xb6 => 0x56df, 0xb7 => 0x80e4, 0xb8 => 0x9997, + 0xb9 => 0x6bd3, 0xba => 0x777e, 0xbb => 0x9f17, 0xbc => 0x4e36, + 0xbd => 0x4e9f, 0xbe => 0x9f10, 0xbf => 0x4e5c, 0xc0 => 0x4e69, + 0xc1 => 0x4e93, 0xc2 => 0x8288, 0xc3 => 0x5b5b, 0xc4 => 0x556c, + 0xc5 => 0x560f, 0xc6 => 0x4ec4, 0xc7 => 0x538d, 0xc8 => 0x539d, + 0xc9 => 0x53a3, 0xca => 0x53a5, 0xcb => 0x53ae, 0xcc => 0x9765, + 0xcd => 0x8d5d, 0xce => 0x531a, 0xcf => 0x53f5, 0xd0 => 0x5326, + 0xd1 => 0x532e, 0xd2 => 0x533e, 0xd3 => 0x8d5c, 0xd4 => 0x5366, + 0xd5 => 0x5363, 0xd6 => 0x5202, 0xd7 => 0x5208, 0xd8 => 0x520e, + 0xd9 => 0x522d, 0xda => 0x5233, 0xdb => 0x523f, 0xdc => 0x5240, + 0xdd => 0x524c, 0xde => 0x525e, 0xdf => 0x5261, 0xe0 => 0x525c, + 0xe1 => 0x84af, 0xe2 => 0x527d, 0xe3 => 0x5282, 0xe4 => 0x5281, + 0xe5 => 0x5290, 0xe6 => 0x5293, 0xe7 => 0x5182, 0xe8 => 0x7f54, + 0xe9 => 0x4ebb, 0xea => 0x4ec3, 0xeb => 0x4ec9, 0xec => 0x4ec2, + 0xed => 0x4ee8, 0xee => 0x4ee1, 0xef => 0x4eeb, 0xf0 => 0x4ede, + 0xf1 => 0x4f1b, 0xf2 => 0x4ef3, 0xf3 => 0x4f22, 0xf4 => 0x4f64, + 0xf5 => 0x4ef5, 0xf6 => 0x4f25, 0xf7 => 0x4f27, 0xf8 => 0x4f09, + 0xf9 => 0x4f2b, 0xfa => 0x4f5e, 0xfb => 0x4f67, 0xfc => 0x6538, + 0xfd => 0x4f5a, 0xfe => 0x4f5d, + }, + 0xd9 => { + 0xa1 => 0x4f5f, 0xa2 => 0x4f57, 0xa3 => 0x4f32, 0xa4 => 0x4f3d, + 0xa5 => 0x4f76, 0xa6 => 0x4f74, 0xa7 => 0x4f91, 0xa8 => 0x4f89, + 0xa9 => 0x4f83, 0xaa => 0x4f8f, 0xab => 0x4f7e, 0xac => 0x4f7b, + 0xad => 0x4faa, 0xae => 0x4f7c, 0xaf => 0x4fac, 0xb0 => 0x4f94, + 0xb1 => 0x4fe6, 0xb2 => 0x4fe8, 0xb3 => 0x4fea, 0xb4 => 0x4fc5, + 0xb5 => 0x4fda, 0xb6 => 0x4fe3, 0xb7 => 0x4fdc, 0xb8 => 0x4fd1, + 0xb9 => 0x4fdf, 0xba => 0x4ff8, 0xbb => 0x5029, 0xbc => 0x504c, + 0xbd => 0x4ff3, 0xbe => 0x502c, 0xbf => 0x500f, 0xc0 => 0x502e, + 0xc1 => 0x502d, 0xc2 => 0x4ffe, 0xc3 => 0x501c, 0xc4 => 0x500c, + 0xc5 => 0x5025, 0xc6 => 0x5028, 0xc7 => 0x507e, 0xc8 => 0x5043, + 0xc9 => 0x5055, 0xca => 0x5048, 0xcb => 0x504e, 0xcc => 0x506c, + 0xcd => 0x507b, 0xce => 0x50a5, 0xcf => 0x50a7, 0xd0 => 0x50a9, + 0xd1 => 0x50ba, 0xd2 => 0x50d6, 0xd3 => 0x5106, 0xd4 => 0x50ed, + 0xd5 => 0x50ec, 0xd6 => 0x50e6, 0xd7 => 0x50ee, 0xd8 => 0x5107, + 0xd9 => 0x510b, 0xda => 0x4edd, 0xdb => 0x6c3d, 0xdc => 0x4f58, + 0xdd => 0x4f65, 0xde => 0x4fce, 0xdf => 0x9fa0, 0xe0 => 0x6c46, + 0xe1 => 0x7c74, 0xe2 => 0x516e, 0xe3 => 0x5dfd, 0xe4 => 0x9ec9, + 0xe5 => 0x9998, 0xe6 => 0x5181, 0xe7 => 0x5914, 0xe8 => 0x52f9, + 0xe9 => 0x530d, 0xea => 0x8a07, 0xeb => 0x5310, 0xec => 0x51eb, + 0xed => 0x5919, 0xee => 0x5155, 0xef => 0x4ea0, 0xf0 => 0x5156, + 0xf1 => 0x4eb3, 0xf2 => 0x886e, 0xf3 => 0x88a4, 0xf4 => 0x4eb5, + 0xf5 => 0x8114, 0xf6 => 0x88d2, 0xf7 => 0x7980, 0xf8 => 0x5b34, + 0xf9 => 0x8803, 0xfa => 0x7fb8, 0xfb => 0x51ab, 0xfc => 0x51b1, + 0xfd => 0x51bd, 0xfe => 0x51bc, + }, + 0xda => { + 0xa1 => 0x51c7, 0xa2 => 0x5196, 0xa3 => 0x51a2, 0xa4 => 0x51a5, + 0xa5 => 0x8ba0, 0xa6 => 0x8ba6, 0xa7 => 0x8ba7, 0xa8 => 0x8baa, + 0xa9 => 0x8bb4, 0xaa => 0x8bb5, 0xab => 0x8bb7, 0xac => 0x8bc2, + 0xad => 0x8bc3, 0xae => 0x8bcb, 0xaf => 0x8bcf, 0xb0 => 0x8bce, + 0xb1 => 0x8bd2, 0xb2 => 0x8bd3, 0xb3 => 0x8bd4, 0xb4 => 0x8bd6, + 0xb5 => 0x8bd8, 0xb6 => 0x8bd9, 0xb7 => 0x8bdc, 0xb8 => 0x8bdf, + 0xb9 => 0x8be0, 0xba => 0x8be4, 0xbb => 0x8be8, 0xbc => 0x8be9, + 0xbd => 0x8bee, 0xbe => 0x8bf0, 0xbf => 0x8bf3, 0xc0 => 0x8bf6, + 0xc1 => 0x8bf9, 0xc2 => 0x8bfc, 0xc3 => 0x8bff, 0xc4 => 0x8c00, + 0xc5 => 0x8c02, 0xc6 => 0x8c04, 0xc7 => 0x8c07, 0xc8 => 0x8c0c, + 0xc9 => 0x8c0f, 0xca => 0x8c11, 0xcb => 0x8c12, 0xcc => 0x8c14, + 0xcd => 0x8c15, 0xce => 0x8c16, 0xcf => 0x8c19, 0xd0 => 0x8c1b, + 0xd1 => 0x8c18, 0xd2 => 0x8c1d, 0xd3 => 0x8c1f, 0xd4 => 0x8c20, + 0xd5 => 0x8c21, 0xd6 => 0x8c25, 0xd7 => 0x8c27, 0xd8 => 0x8c2a, + 0xd9 => 0x8c2b, 0xda => 0x8c2e, 0xdb => 0x8c2f, 0xdc => 0x8c32, + 0xdd => 0x8c33, 0xde => 0x8c35, 0xdf => 0x8c36, 0xe0 => 0x5369, + 0xe1 => 0x537a, 0xe2 => 0x961d, 0xe3 => 0x9622, 0xe4 => 0x9621, + 0xe5 => 0x9631, 0xe6 => 0x962a, 0xe7 => 0x963d, 0xe8 => 0x963c, + 0xe9 => 0x9642, 0xea => 0x9649, 0xeb => 0x9654, 0xec => 0x965f, + 0xed => 0x9667, 0xee => 0x966c, 0xef => 0x9672, 0xf0 => 0x9674, + 0xf1 => 0x9688, 0xf2 => 0x968d, 0xf3 => 0x9697, 0xf4 => 0x96b0, + 0xf5 => 0x9097, 0xf6 => 0x909b, 0xf7 => 0x909d, 0xf8 => 0x9099, + 0xf9 => 0x90ac, 0xfa => 0x90a1, 0xfb => 0x90b4, 0xfc => 0x90b3, + 0xfd => 0x90b6, 0xfe => 0x90ba, + }, + 0xdb => { + 0xa1 => 0x90b8, 0xa2 => 0x90b0, 0xa3 => 0x90cf, 0xa4 => 0x90c5, + 0xa5 => 0x90be, 0xa6 => 0x90d0, 0xa7 => 0x90c4, 0xa8 => 0x90c7, + 0xa9 => 0x90d3, 0xaa => 0x90e6, 0xab => 0x90e2, 0xac => 0x90dc, + 0xad => 0x90d7, 0xae => 0x90db, 0xaf => 0x90eb, 0xb0 => 0x90ef, + 0xb1 => 0x90fe, 0xb2 => 0x9104, 0xb3 => 0x9122, 0xb4 => 0x911e, + 0xb5 => 0x9123, 0xb6 => 0x9131, 0xb7 => 0x912f, 0xb8 => 0x9139, + 0xb9 => 0x9143, 0xba => 0x9146, 0xbb => 0x520d, 0xbc => 0x5942, + 0xbd => 0x52a2, 0xbe => 0x52ac, 0xbf => 0x52ad, 0xc0 => 0x52be, + 0xc1 => 0x54ff, 0xc2 => 0x52d0, 0xc3 => 0x52d6, 0xc4 => 0x52f0, + 0xc5 => 0x53df, 0xc6 => 0x71ee, 0xc7 => 0x77cd, 0xc8 => 0x5ef4, + 0xc9 => 0x51f5, 0xca => 0x51fc, 0xcb => 0x9b2f, 0xcc => 0x53b6, + 0xcd => 0x5f01, 0xce => 0x755a, 0xcf => 0x5def, 0xd0 => 0x574c, + 0xd1 => 0x57a9, 0xd2 => 0x57a1, 0xd3 => 0x587e, 0xd4 => 0x58bc, + 0xd5 => 0x58c5, 0xd6 => 0x58d1, 0xd7 => 0x5729, 0xd8 => 0x572c, + 0xd9 => 0x572a, 0xda => 0x5733, 0xdb => 0x5739, 0xdc => 0x572e, + 0xdd => 0x572f, 0xde => 0x575c, 0xdf => 0x573b, 0xe0 => 0x5742, + 0xe1 => 0x5769, 0xe2 => 0x5785, 0xe3 => 0x576b, 0xe4 => 0x5786, + 0xe5 => 0x577c, 0xe6 => 0x577b, 0xe7 => 0x5768, 0xe8 => 0x576d, + 0xe9 => 0x5776, 0xea => 0x5773, 0xeb => 0x57ad, 0xec => 0x57a4, + 0xed => 0x578c, 0xee => 0x57b2, 0xef => 0x57cf, 0xf0 => 0x57a7, + 0xf1 => 0x57b4, 0xf2 => 0x5793, 0xf3 => 0x57a0, 0xf4 => 0x57d5, + 0xf5 => 0x57d8, 0xf6 => 0x57da, 0xf7 => 0x57d9, 0xf8 => 0x57d2, + 0xf9 => 0x57b8, 0xfa => 0x57f4, 0xfb => 0x57ef, 0xfc => 0x57f8, + 0xfd => 0x57e4, 0xfe => 0x57dd, + }, + 0xdc => { + 0xa1 => 0x580b, 0xa2 => 0x580d, 0xa3 => 0x57fd, 0xa4 => 0x57ed, + 0xa5 => 0x5800, 0xa6 => 0x581e, 0xa7 => 0x5819, 0xa8 => 0x5844, + 0xa9 => 0x5820, 0xaa => 0x5865, 0xab => 0x586c, 0xac => 0x5881, + 0xad => 0x5889, 0xae => 0x589a, 0xaf => 0x5880, 0xb0 => 0x99a8, + 0xb1 => 0x9f19, 0xb2 => 0x61ff, 0xb3 => 0x8279, 0xb4 => 0x827d, + 0xb5 => 0x827f, 0xb6 => 0x828f, 0xb7 => 0x828a, 0xb8 => 0x82a8, + 0xb9 => 0x8284, 0xba => 0x828e, 0xbb => 0x8291, 0xbc => 0x8297, + 0xbd => 0x8299, 0xbe => 0x82ab, 0xbf => 0x82b8, 0xc0 => 0x82be, + 0xc1 => 0x82b0, 0xc2 => 0x82c8, 0xc3 => 0x82ca, 0xc4 => 0x82e3, + 0xc5 => 0x8298, 0xc6 => 0x82b7, 0xc7 => 0x82ae, 0xc8 => 0x82cb, + 0xc9 => 0x82cc, 0xca => 0x82c1, 0xcb => 0x82a9, 0xcc => 0x82b4, + 0xcd => 0x82a1, 0xce => 0x82aa, 0xcf => 0x829f, 0xd0 => 0x82c4, + 0xd1 => 0x82ce, 0xd2 => 0x82a4, 0xd3 => 0x82e1, 0xd4 => 0x8309, + 0xd5 => 0x82f7, 0xd6 => 0x82e4, 0xd7 => 0x830f, 0xd8 => 0x8307, + 0xd9 => 0x82dc, 0xda => 0x82f4, 0xdb => 0x82d2, 0xdc => 0x82d8, + 0xdd => 0x830c, 0xde => 0x82fb, 0xdf => 0x82d3, 0xe0 => 0x8311, + 0xe1 => 0x831a, 0xe2 => 0x8306, 0xe3 => 0x8314, 0xe4 => 0x8315, + 0xe5 => 0x82e0, 0xe6 => 0x82d5, 0xe7 => 0x831c, 0xe8 => 0x8351, + 0xe9 => 0x835b, 0xea => 0x835c, 0xeb => 0x8308, 0xec => 0x8392, + 0xed => 0x833c, 0xee => 0x8334, 0xef => 0x8331, 0xf0 => 0x839b, + 0xf1 => 0x835e, 0xf2 => 0x832f, 0xf3 => 0x834f, 0xf4 => 0x8347, + 0xf5 => 0x8343, 0xf6 => 0x835f, 0xf7 => 0x8340, 0xf8 => 0x8317, + 0xf9 => 0x8360, 0xfa => 0x832d, 0xfb => 0x833a, 0xfc => 0x8333, + 0xfd => 0x8366, 0xfe => 0x8365, + }, + 0xdd => { + 0xa1 => 0x8368, 0xa2 => 0x831b, 0xa3 => 0x8369, 0xa4 => 0x836c, + 0xa5 => 0x836a, 0xa6 => 0x836d, 0xa7 => 0x836e, 0xa8 => 0x83b0, + 0xa9 => 0x8378, 0xaa => 0x83b3, 0xab => 0x83b4, 0xac => 0x83a0, + 0xad => 0x83aa, 0xae => 0x8393, 0xaf => 0x839c, 0xb0 => 0x8385, + 0xb1 => 0x837c, 0xb2 => 0x83b6, 0xb3 => 0x83a9, 0xb4 => 0x837d, + 0xb5 => 0x83b8, 0xb6 => 0x837b, 0xb7 => 0x8398, 0xb8 => 0x839e, + 0xb9 => 0x83a8, 0xba => 0x83ba, 0xbb => 0x83bc, 0xbc => 0x83c1, + 0xbd => 0x8401, 0xbe => 0x83e5, 0xbf => 0x83d8, 0xc0 => 0x5807, + 0xc1 => 0x8418, 0xc2 => 0x840b, 0xc3 => 0x83dd, 0xc4 => 0x83fd, + 0xc5 => 0x83d6, 0xc6 => 0x841c, 0xc7 => 0x8438, 0xc8 => 0x8411, + 0xc9 => 0x8406, 0xca => 0x83d4, 0xcb => 0x83df, 0xcc => 0x840f, + 0xcd => 0x8403, 0xce => 0x83f8, 0xcf => 0x83f9, 0xd0 => 0x83ea, + 0xd1 => 0x83c5, 0xd2 => 0x83c0, 0xd3 => 0x8426, 0xd4 => 0x83f0, + 0xd5 => 0x83e1, 0xd6 => 0x845c, 0xd7 => 0x8451, 0xd8 => 0x845a, + 0xd9 => 0x8459, 0xda => 0x8473, 0xdb => 0x8487, 0xdc => 0x8488, + 0xdd => 0x847a, 0xde => 0x8489, 0xdf => 0x8478, 0xe0 => 0x843c, + 0xe1 => 0x8446, 0xe2 => 0x8469, 0xe3 => 0x8476, 0xe4 => 0x848c, + 0xe5 => 0x848e, 0xe6 => 0x8431, 0xe7 => 0x846d, 0xe8 => 0x84c1, + 0xe9 => 0x84cd, 0xea => 0x84d0, 0xeb => 0x84e6, 0xec => 0x84bd, + 0xed => 0x84d3, 0xee => 0x84ca, 0xef => 0x84bf, 0xf0 => 0x84ba, + 0xf1 => 0x84e0, 0xf2 => 0x84a1, 0xf3 => 0x84b9, 0xf4 => 0x84b4, + 0xf5 => 0x8497, 0xf6 => 0x84e5, 0xf7 => 0x84e3, 0xf8 => 0x850c, + 0xf9 => 0x750d, 0xfa => 0x8538, 0xfb => 0x84f0, 0xfc => 0x8539, + 0xfd => 0x851f, 0xfe => 0x853a, + }, + 0xde => { + 0xa1 => 0x8556, 0xa2 => 0x853b, 0xa3 => 0x84ff, 0xa4 => 0x84fc, + 0xa5 => 0x8559, 0xa6 => 0x8548, 0xa7 => 0x8568, 0xa8 => 0x8564, + 0xa9 => 0x855e, 0xaa => 0x857a, 0xab => 0x77a2, 0xac => 0x8543, + 0xad => 0x8572, 0xae => 0x857b, 0xaf => 0x85a4, 0xb0 => 0x85a8, + 0xb1 => 0x8587, 0xb2 => 0x858f, 0xb3 => 0x8579, 0xb4 => 0x85ae, + 0xb5 => 0x859c, 0xb6 => 0x8585, 0xb7 => 0x85b9, 0xb8 => 0x85b7, + 0xb9 => 0x85b0, 0xba => 0x85d3, 0xbb => 0x85c1, 0xbc => 0x85dc, + 0xbd => 0x85ff, 0xbe => 0x8627, 0xbf => 0x8605, 0xc0 => 0x8629, + 0xc1 => 0x8616, 0xc2 => 0x863c, 0xc3 => 0x5efe, 0xc4 => 0x5f08, + 0xc5 => 0x593c, 0xc6 => 0x5941, 0xc7 => 0x8037, 0xc8 => 0x5955, + 0xc9 => 0x595a, 0xca => 0x5958, 0xcb => 0x530f, 0xcc => 0x5c22, + 0xcd => 0x5c25, 0xce => 0x5c2c, 0xcf => 0x5c34, 0xd0 => 0x624c, + 0xd1 => 0x626a, 0xd2 => 0x629f, 0xd3 => 0x62bb, 0xd4 => 0x62ca, + 0xd5 => 0x62da, 0xd6 => 0x62d7, 0xd7 => 0x62ee, 0xd8 => 0x6322, + 0xd9 => 0x62f6, 0xda => 0x6339, 0xdb => 0x634b, 0xdc => 0x6343, + 0xdd => 0x63ad, 0xde => 0x63f6, 0xdf => 0x6371, 0xe0 => 0x637a, + 0xe1 => 0x638e, 0xe2 => 0x63b4, 0xe3 => 0x636d, 0xe4 => 0x63ac, + 0xe5 => 0x638a, 0xe6 => 0x6369, 0xe7 => 0x63ae, 0xe8 => 0x63bc, + 0xe9 => 0x63f2, 0xea => 0x63f8, 0xeb => 0x63e0, 0xec => 0x63ff, + 0xed => 0x63c4, 0xee => 0x63de, 0xef => 0x63ce, 0xf0 => 0x6452, + 0xf1 => 0x63c6, 0xf2 => 0x63be, 0xf3 => 0x6445, 0xf4 => 0x6441, + 0xf5 => 0x640b, 0xf6 => 0x641b, 0xf7 => 0x6420, 0xf8 => 0x640c, + 0xf9 => 0x6426, 0xfa => 0x6421, 0xfb => 0x645e, 0xfc => 0x6484, + 0xfd => 0x646d, 0xfe => 0x6496, + }, + 0xdf => { + 0xa1 => 0x647a, 0xa2 => 0x64b7, 0xa3 => 0x64b8, 0xa4 => 0x6499, + 0xa5 => 0x64ba, 0xa6 => 0x64c0, 0xa7 => 0x64d0, 0xa8 => 0x64d7, + 0xa9 => 0x64e4, 0xaa => 0x64e2, 0xab => 0x6509, 0xac => 0x6525, + 0xad => 0x652e, 0xae => 0x5f0b, 0xaf => 0x5fd2, 0xb0 => 0x7519, + 0xb1 => 0x5f11, 0xb2 => 0x535f, 0xb3 => 0x53f1, 0xb4 => 0x53fd, + 0xb5 => 0x53e9, 0xb6 => 0x53e8, 0xb7 => 0x53fb, 0xb8 => 0x5412, + 0xb9 => 0x5416, 0xba => 0x5406, 0xbb => 0x544b, 0xbc => 0x5452, + 0xbd => 0x5453, 0xbe => 0x5454, 0xbf => 0x5456, 0xc0 => 0x5443, + 0xc1 => 0x5421, 0xc2 => 0x5457, 0xc3 => 0x5459, 0xc4 => 0x5423, + 0xc5 => 0x5432, 0xc6 => 0x5482, 0xc7 => 0x5494, 0xc8 => 0x5477, + 0xc9 => 0x5471, 0xca => 0x5464, 0xcb => 0x549a, 0xcc => 0x549b, + 0xcd => 0x5484, 0xce => 0x5476, 0xcf => 0x5466, 0xd0 => 0x549d, + 0xd1 => 0x54d0, 0xd2 => 0x54ad, 0xd3 => 0x54c2, 0xd4 => 0x54b4, + 0xd5 => 0x54d2, 0xd6 => 0x54a7, 0xd7 => 0x54a6, 0xd8 => 0x54d3, + 0xd9 => 0x54d4, 0xda => 0x5472, 0xdb => 0x54a3, 0xdc => 0x54d5, + 0xdd => 0x54bb, 0xde => 0x54bf, 0xdf => 0x54cc, 0xe0 => 0x54d9, + 0xe1 => 0x54da, 0xe2 => 0x54dc, 0xe3 => 0x54a9, 0xe4 => 0x54aa, + 0xe5 => 0x54a4, 0xe6 => 0x54dd, 0xe7 => 0x54cf, 0xe8 => 0x54de, + 0xe9 => 0x551b, 0xea => 0x54e7, 0xeb => 0x5520, 0xec => 0x54fd, + 0xed => 0x5514, 0xee => 0x54f3, 0xef => 0x5522, 0xf0 => 0x5523, + 0xf1 => 0x550f, 0xf2 => 0x5511, 0xf3 => 0x5527, 0xf4 => 0x552a, + 0xf5 => 0x5567, 0xf6 => 0x558f, 0xf7 => 0x55b5, 0xf8 => 0x5549, + 0xf9 => 0x556d, 0xfa => 0x5541, 0xfb => 0x5555, 0xfc => 0x553f, + 0xfd => 0x5550, 0xfe => 0x553c, + }, + 0xe0 => { + 0xa1 => 0x5537, 0xa2 => 0x5556, 0xa3 => 0x5575, 0xa4 => 0x5576, + 0xa5 => 0x5577, 0xa6 => 0x5533, 0xa7 => 0x5530, 0xa8 => 0x555c, + 0xa9 => 0x558b, 0xaa => 0x55d2, 0xab => 0x5583, 0xac => 0x55b1, + 0xad => 0x55b9, 0xae => 0x5588, 0xaf => 0x5581, 0xb0 => 0x559f, + 0xb1 => 0x557e, 0xb2 => 0x55d6, 0xb3 => 0x5591, 0xb4 => 0x557b, + 0xb5 => 0x55df, 0xb6 => 0x55bd, 0xb7 => 0x55be, 0xb8 => 0x5594, + 0xb9 => 0x5599, 0xba => 0x55ea, 0xbb => 0x55f7, 0xbc => 0x55c9, + 0xbd => 0x561f, 0xbe => 0x55d1, 0xbf => 0x55eb, 0xc0 => 0x55ec, + 0xc1 => 0x55d4, 0xc2 => 0x55e6, 0xc3 => 0x55dd, 0xc4 => 0x55c4, + 0xc5 => 0x55ef, 0xc6 => 0x55e5, 0xc7 => 0x55f2, 0xc8 => 0x55f3, + 0xc9 => 0x55cc, 0xca => 0x55cd, 0xcb => 0x55e8, 0xcc => 0x55f5, + 0xcd => 0x55e4, 0xce => 0x8f94, 0xcf => 0x561e, 0xd0 => 0x5608, + 0xd1 => 0x560c, 0xd2 => 0x5601, 0xd3 => 0x5624, 0xd4 => 0x5623, + 0xd5 => 0x55fe, 0xd6 => 0x5600, 0xd7 => 0x5627, 0xd8 => 0x562d, + 0xd9 => 0x5658, 0xda => 0x5639, 0xdb => 0x5657, 0xdc => 0x562c, + 0xdd => 0x564d, 0xde => 0x5662, 0xdf => 0x5659, 0xe0 => 0x565c, + 0xe1 => 0x564c, 0xe2 => 0x5654, 0xe3 => 0x5686, 0xe4 => 0x5664, + 0xe5 => 0x5671, 0xe6 => 0x566b, 0xe7 => 0x567b, 0xe8 => 0x567c, + 0xe9 => 0x5685, 0xea => 0x5693, 0xeb => 0x56af, 0xec => 0x56d4, + 0xed => 0x56d7, 0xee => 0x56dd, 0xef => 0x56e1, 0xf0 => 0x56f5, + 0xf1 => 0x56eb, 0xf2 => 0x56f9, 0xf3 => 0x56ff, 0xf4 => 0x5704, + 0xf5 => 0x570a, 0xf6 => 0x5709, 0xf7 => 0x571c, 0xf8 => 0x5e0f, + 0xf9 => 0x5e19, 0xfa => 0x5e14, 0xfb => 0x5e11, 0xfc => 0x5e31, + 0xfd => 0x5e3b, 0xfe => 0x5e3c, + }, + 0xe1 => { + 0xa1 => 0x5e37, 0xa2 => 0x5e44, 0xa3 => 0x5e54, 0xa4 => 0x5e5b, + 0xa5 => 0x5e5e, 0xa6 => 0x5e61, 0xa7 => 0x5c8c, 0xa8 => 0x5c7a, + 0xa9 => 0x5c8d, 0xaa => 0x5c90, 0xab => 0x5c96, 0xac => 0x5c88, + 0xad => 0x5c98, 0xae => 0x5c99, 0xaf => 0x5c91, 0xb0 => 0x5c9a, + 0xb1 => 0x5c9c, 0xb2 => 0x5cb5, 0xb3 => 0x5ca2, 0xb4 => 0x5cbd, + 0xb5 => 0x5cac, 0xb6 => 0x5cab, 0xb7 => 0x5cb1, 0xb8 => 0x5ca3, + 0xb9 => 0x5cc1, 0xba => 0x5cb7, 0xbb => 0x5cc4, 0xbc => 0x5cd2, + 0xbd => 0x5ce4, 0xbe => 0x5ccb, 0xbf => 0x5ce5, 0xc0 => 0x5d02, + 0xc1 => 0x5d03, 0xc2 => 0x5d27, 0xc3 => 0x5d26, 0xc4 => 0x5d2e, + 0xc5 => 0x5d24, 0xc6 => 0x5d1e, 0xc7 => 0x5d06, 0xc8 => 0x5d1b, + 0xc9 => 0x5d58, 0xca => 0x5d3e, 0xcb => 0x5d34, 0xcc => 0x5d3d, + 0xcd => 0x5d6c, 0xce => 0x5d5b, 0xcf => 0x5d6f, 0xd0 => 0x5d5d, + 0xd1 => 0x5d6b, 0xd2 => 0x5d4b, 0xd3 => 0x5d4a, 0xd4 => 0x5d69, + 0xd5 => 0x5d74, 0xd6 => 0x5d82, 0xd7 => 0x5d99, 0xd8 => 0x5d9d, + 0xd9 => 0x8c73, 0xda => 0x5db7, 0xdb => 0x5dc5, 0xdc => 0x5f73, + 0xdd => 0x5f77, 0xde => 0x5f82, 0xdf => 0x5f87, 0xe0 => 0x5f89, + 0xe1 => 0x5f8c, 0xe2 => 0x5f95, 0xe3 => 0x5f99, 0xe4 => 0x5f9c, + 0xe5 => 0x5fa8, 0xe6 => 0x5fad, 0xe7 => 0x5fb5, 0xe8 => 0x5fbc, + 0xe9 => 0x8862, 0xea => 0x5f61, 0xeb => 0x72ad, 0xec => 0x72b0, + 0xed => 0x72b4, 0xee => 0x72b7, 0xef => 0x72b8, 0xf0 => 0x72c3, + 0xf1 => 0x72c1, 0xf2 => 0x72ce, 0xf3 => 0x72cd, 0xf4 => 0x72d2, + 0xf5 => 0x72e8, 0xf6 => 0x72ef, 0xf7 => 0x72e9, 0xf8 => 0x72f2, + 0xf9 => 0x72f4, 0xfa => 0x72f7, 0xfb => 0x7301, 0xfc => 0x72f3, + 0xfd => 0x7303, 0xfe => 0x72fa, + }, + 0xe2 => { + 0xa1 => 0x72fb, 0xa2 => 0x7317, 0xa3 => 0x7313, 0xa4 => 0x7321, + 0xa5 => 0x730a, 0xa6 => 0x731e, 0xa7 => 0x731d, 0xa8 => 0x7315, + 0xa9 => 0x7322, 0xaa => 0x7339, 0xab => 0x7325, 0xac => 0x732c, + 0xad => 0x7338, 0xae => 0x7331, 0xaf => 0x7350, 0xb0 => 0x734d, + 0xb1 => 0x7357, 0xb2 => 0x7360, 0xb3 => 0x736c, 0xb4 => 0x736f, + 0xb5 => 0x737e, 0xb6 => 0x821b, 0xb7 => 0x5925, 0xb8 => 0x98e7, + 0xb9 => 0x5924, 0xba => 0x5902, 0xbb => 0x9963, 0xbc => 0x9967, + 0xbd => 0x9968, 0xbe => 0x9969, 0xbf => 0x996a, 0xc0 => 0x996b, + 0xc1 => 0x996c, 0xc2 => 0x9974, 0xc3 => 0x9977, 0xc4 => 0x997d, + 0xc5 => 0x9980, 0xc6 => 0x9984, 0xc7 => 0x9987, 0xc8 => 0x998a, + 0xc9 => 0x998d, 0xca => 0x9990, 0xcb => 0x9991, 0xcc => 0x9993, + 0xcd => 0x9994, 0xce => 0x9995, 0xcf => 0x5e80, 0xd0 => 0x5e91, + 0xd1 => 0x5e8b, 0xd2 => 0x5e96, 0xd3 => 0x5ea5, 0xd4 => 0x5ea0, + 0xd5 => 0x5eb9, 0xd6 => 0x5eb5, 0xd7 => 0x5ebe, 0xd8 => 0x5eb3, + 0xd9 => 0x8d53, 0xda => 0x5ed2, 0xdb => 0x5ed1, 0xdc => 0x5edb, + 0xdd => 0x5ee8, 0xde => 0x5eea, 0xdf => 0x81ba, 0xe0 => 0x5fc4, + 0xe1 => 0x5fc9, 0xe2 => 0x5fd6, 0xe3 => 0x5fcf, 0xe4 => 0x6003, + 0xe5 => 0x5fee, 0xe6 => 0x6004, 0xe7 => 0x5fe1, 0xe8 => 0x5fe4, + 0xe9 => 0x5ffe, 0xea => 0x6005, 0xeb => 0x6006, 0xec => 0x5fea, + 0xed => 0x5fed, 0xee => 0x5ff8, 0xef => 0x6019, 0xf0 => 0x6035, + 0xf1 => 0x6026, 0xf2 => 0x601b, 0xf3 => 0x600f, 0xf4 => 0x600d, + 0xf5 => 0x6029, 0xf6 => 0x602b, 0xf7 => 0x600a, 0xf8 => 0x603f, + 0xf9 => 0x6021, 0xfa => 0x6078, 0xfb => 0x6079, 0xfc => 0x607b, + 0xfd => 0x607a, 0xfe => 0x6042, + }, + 0xe3 => { + 0xa1 => 0x606a, 0xa2 => 0x607d, 0xa3 => 0x6096, 0xa4 => 0x609a, + 0xa5 => 0x60ad, 0xa6 => 0x609d, 0xa7 => 0x6083, 0xa8 => 0x6092, + 0xa9 => 0x608c, 0xaa => 0x609b, 0xab => 0x60ec, 0xac => 0x60bb, + 0xad => 0x60b1, 0xae => 0x60dd, 0xaf => 0x60d8, 0xb0 => 0x60c6, + 0xb1 => 0x60da, 0xb2 => 0x60b4, 0xb3 => 0x6120, 0xb4 => 0x6126, + 0xb5 => 0x6115, 0xb6 => 0x6123, 0xb7 => 0x60f4, 0xb8 => 0x6100, + 0xb9 => 0x610e, 0xba => 0x612b, 0xbb => 0x614a, 0xbc => 0x6175, + 0xbd => 0x61ac, 0xbe => 0x6194, 0xbf => 0x61a7, 0xc0 => 0x61b7, + 0xc1 => 0x61d4, 0xc2 => 0x61f5, 0xc3 => 0x5fdd, 0xc4 => 0x96b3, + 0xc5 => 0x95e9, 0xc6 => 0x95eb, 0xc7 => 0x95f1, 0xc8 => 0x95f3, + 0xc9 => 0x95f5, 0xca => 0x95f6, 0xcb => 0x95fc, 0xcc => 0x95fe, + 0xcd => 0x9603, 0xce => 0x9604, 0xcf => 0x9606, 0xd0 => 0x9608, + 0xd1 => 0x960a, 0xd2 => 0x960b, 0xd3 => 0x960c, 0xd4 => 0x960d, + 0xd5 => 0x960f, 0xd6 => 0x9612, 0xd7 => 0x9615, 0xd8 => 0x9616, + 0xd9 => 0x9617, 0xda => 0x9619, 0xdb => 0x961a, 0xdc => 0x4e2c, + 0xdd => 0x723f, 0xde => 0x6215, 0xdf => 0x6c35, 0xe0 => 0x6c54, + 0xe1 => 0x6c5c, 0xe2 => 0x6c4a, 0xe3 => 0x6ca3, 0xe4 => 0x6c85, + 0xe5 => 0x6c90, 0xe6 => 0x6c94, 0xe7 => 0x6c8c, 0xe8 => 0x6c68, + 0xe9 => 0x6c69, 0xea => 0x6c74, 0xeb => 0x6c76, 0xec => 0x6c86, + 0xed => 0x6ca9, 0xee => 0x6cd0, 0xef => 0x6cd4, 0xf0 => 0x6cad, + 0xf1 => 0x6cf7, 0xf2 => 0x6cf8, 0xf3 => 0x6cf1, 0xf4 => 0x6cd7, + 0xf5 => 0x6cb2, 0xf6 => 0x6ce0, 0xf7 => 0x6cd6, 0xf8 => 0x6cfa, + 0xf9 => 0x6ceb, 0xfa => 0x6cee, 0xfb => 0x6cb1, 0xfc => 0x6cd3, + 0xfd => 0x6cef, 0xfe => 0x6cfe, + }, + 0xe4 => { + 0xa1 => 0x6d39, 0xa2 => 0x6d27, 0xa3 => 0x6d0c, 0xa4 => 0x6d43, + 0xa5 => 0x6d48, 0xa6 => 0x6d07, 0xa7 => 0x6d04, 0xa8 => 0x6d19, + 0xa9 => 0x6d0e, 0xaa => 0x6d2b, 0xab => 0x6d4d, 0xac => 0x6d2e, + 0xad => 0x6d35, 0xae => 0x6d1a, 0xaf => 0x6d4f, 0xb0 => 0x6d52, + 0xb1 => 0x6d54, 0xb2 => 0x6d33, 0xb3 => 0x6d91, 0xb4 => 0x6d6f, + 0xb5 => 0x6d9e, 0xb6 => 0x6da0, 0xb7 => 0x6d5e, 0xb8 => 0x6d93, + 0xb9 => 0x6d94, 0xba => 0x6d5c, 0xbb => 0x6d60, 0xbc => 0x6d7c, + 0xbd => 0x6d63, 0xbe => 0x6e1a, 0xbf => 0x6dc7, 0xc0 => 0x6dc5, + 0xc1 => 0x6dde, 0xc2 => 0x6e0e, 0xc3 => 0x6dbf, 0xc4 => 0x6de0, + 0xc5 => 0x6e11, 0xc6 => 0x6de6, 0xc7 => 0x6ddd, 0xc8 => 0x6dd9, + 0xc9 => 0x6e16, 0xca => 0x6dab, 0xcb => 0x6e0c, 0xcc => 0x6dae, + 0xcd => 0x6e2b, 0xce => 0x6e6e, 0xcf => 0x6e4e, 0xd0 => 0x6e6b, + 0xd1 => 0x6eb2, 0xd2 => 0x6e5f, 0xd3 => 0x6e86, 0xd4 => 0x6e53, + 0xd5 => 0x6e54, 0xd6 => 0x6e32, 0xd7 => 0x6e25, 0xd8 => 0x6e44, + 0xd9 => 0x6edf, 0xda => 0x6eb1, 0xdb => 0x6e98, 0xdc => 0x6ee0, + 0xdd => 0x6f2d, 0xde => 0x6ee2, 0xdf => 0x6ea5, 0xe0 => 0x6ea7, + 0xe1 => 0x6ebd, 0xe2 => 0x6ebb, 0xe3 => 0x6eb7, 0xe4 => 0x6ed7, + 0xe5 => 0x6eb4, 0xe6 => 0x6ecf, 0xe7 => 0x6e8f, 0xe8 => 0x6ec2, + 0xe9 => 0x6e9f, 0xea => 0x6f62, 0xeb => 0x6f46, 0xec => 0x6f47, + 0xed => 0x6f24, 0xee => 0x6f15, 0xef => 0x6ef9, 0xf0 => 0x6f2f, + 0xf1 => 0x6f36, 0xf2 => 0x6f4b, 0xf3 => 0x6f74, 0xf4 => 0x6f2a, + 0xf5 => 0x6f09, 0xf6 => 0x6f29, 0xf7 => 0x6f89, 0xf8 => 0x6f8d, + 0xf9 => 0x6f8c, 0xfa => 0x6f78, 0xfb => 0x6f72, 0xfc => 0x6f7c, + 0xfd => 0x6f7a, 0xfe => 0x6fd1, + }, + 0xe5 => { + 0xa1 => 0x6fc9, 0xa2 => 0x6fa7, 0xa3 => 0x6fb9, 0xa4 => 0x6fb6, + 0xa5 => 0x6fc2, 0xa6 => 0x6fe1, 0xa7 => 0x6fee, 0xa8 => 0x6fde, + 0xa9 => 0x6fe0, 0xaa => 0x6fef, 0xab => 0x701a, 0xac => 0x7023, + 0xad => 0x701b, 0xae => 0x7039, 0xaf => 0x7035, 0xb0 => 0x704f, + 0xb1 => 0x705e, 0xb2 => 0x5b80, 0xb3 => 0x5b84, 0xb4 => 0x5b95, + 0xb5 => 0x5b93, 0xb6 => 0x5ba5, 0xb7 => 0x5bb8, 0xb8 => 0x752f, + 0xb9 => 0x9a9e, 0xba => 0x6434, 0xbb => 0x5be4, 0xbc => 0x5bee, + 0xbd => 0x8930, 0xbe => 0x5bf0, 0xbf => 0x8e47, 0xc0 => 0x8b07, + 0xc1 => 0x8fb6, 0xc2 => 0x8fd3, 0xc3 => 0x8fd5, 0xc4 => 0x8fe5, + 0xc5 => 0x8fee, 0xc6 => 0x8fe4, 0xc7 => 0x8fe9, 0xc8 => 0x8fe6, + 0xc9 => 0x8ff3, 0xca => 0x8fe8, 0xcb => 0x9005, 0xcc => 0x9004, + 0xcd => 0x900b, 0xce => 0x9026, 0xcf => 0x9011, 0xd0 => 0x900d, + 0xd1 => 0x9016, 0xd2 => 0x9021, 0xd3 => 0x9035, 0xd4 => 0x9036, + 0xd5 => 0x902d, 0xd6 => 0x902f, 0xd7 => 0x9044, 0xd8 => 0x9051, + 0xd9 => 0x9052, 0xda => 0x9050, 0xdb => 0x9068, 0xdc => 0x9058, + 0xdd => 0x9062, 0xde => 0x905b, 0xdf => 0x66b9, 0xe0 => 0x9074, + 0xe1 => 0x907d, 0xe2 => 0x9082, 0xe3 => 0x9088, 0xe4 => 0x9083, + 0xe5 => 0x908b, 0xe6 => 0x5f50, 0xe7 => 0x5f57, 0xe8 => 0x5f56, + 0xe9 => 0x5f58, 0xea => 0x5c3b, 0xeb => 0x54ab, 0xec => 0x5c50, + 0xed => 0x5c59, 0xee => 0x5b71, 0xef => 0x5c63, 0xf0 => 0x5c66, + 0xf1 => 0x7fbc, 0xf2 => 0x5f2a, 0xf3 => 0x5f29, 0xf4 => 0x5f2d, + 0xf5 => 0x8274, 0xf6 => 0x5f3c, 0xf7 => 0x9b3b, 0xf8 => 0x5c6e, + 0xf9 => 0x5981, 0xfa => 0x5983, 0xfb => 0x598d, 0xfc => 0x59a9, + 0xfd => 0x59aa, 0xfe => 0x59a3, + }, + 0xe6 => { + 0xa1 => 0x5997, 0xa2 => 0x59ca, 0xa3 => 0x59ab, 0xa4 => 0x599e, + 0xa5 => 0x59a4, 0xa6 => 0x59d2, 0xa7 => 0x59b2, 0xa8 => 0x59af, + 0xa9 => 0x59d7, 0xaa => 0x59be, 0xab => 0x5a05, 0xac => 0x5a06, + 0xad => 0x59dd, 0xae => 0x5a08, 0xaf => 0x59e3, 0xb0 => 0x59d8, + 0xb1 => 0x59f9, 0xb2 => 0x5a0c, 0xb3 => 0x5a09, 0xb4 => 0x5a32, + 0xb5 => 0x5a34, 0xb6 => 0x5a11, 0xb7 => 0x5a23, 0xb8 => 0x5a13, + 0xb9 => 0x5a40, 0xba => 0x5a67, 0xbb => 0x5a4a, 0xbc => 0x5a55, + 0xbd => 0x5a3c, 0xbe => 0x5a62, 0xbf => 0x5a75, 0xc0 => 0x80ec, + 0xc1 => 0x5aaa, 0xc2 => 0x5a9b, 0xc3 => 0x5a77, 0xc4 => 0x5a7a, + 0xc5 => 0x5abe, 0xc6 => 0x5aeb, 0xc7 => 0x5ab2, 0xc8 => 0x5ad2, + 0xc9 => 0x5ad4, 0xca => 0x5ab8, 0xcb => 0x5ae0, 0xcc => 0x5ae3, + 0xcd => 0x5af1, 0xce => 0x5ad6, 0xcf => 0x5ae6, 0xd0 => 0x5ad8, + 0xd1 => 0x5adc, 0xd2 => 0x5b09, 0xd3 => 0x5b17, 0xd4 => 0x5b16, + 0xd5 => 0x5b32, 0xd6 => 0x5b37, 0xd7 => 0x5b40, 0xd8 => 0x5c15, + 0xd9 => 0x5c1c, 0xda => 0x5b5a, 0xdb => 0x5b65, 0xdc => 0x5b73, + 0xdd => 0x5b51, 0xde => 0x5b53, 0xdf => 0x5b62, 0xe0 => 0x9a75, + 0xe1 => 0x9a77, 0xe2 => 0x9a78, 0xe3 => 0x9a7a, 0xe4 => 0x9a7f, + 0xe5 => 0x9a7d, 0xe6 => 0x9a80, 0xe7 => 0x9a81, 0xe8 => 0x9a85, + 0xe9 => 0x9a88, 0xea => 0x9a8a, 0xeb => 0x9a90, 0xec => 0x9a92, + 0xed => 0x9a93, 0xee => 0x9a96, 0xef => 0x9a98, 0xf0 => 0x9a9b, + 0xf1 => 0x9a9c, 0xf2 => 0x9a9d, 0xf3 => 0x9a9f, 0xf4 => 0x9aa0, + 0xf5 => 0x9aa2, 0xf6 => 0x9aa3, 0xf7 => 0x9aa5, 0xf8 => 0x9aa7, + 0xf9 => 0x7e9f, 0xfa => 0x7ea1, 0xfb => 0x7ea3, 0xfc => 0x7ea5, + 0xfd => 0x7ea8, 0xfe => 0x7ea9, + }, + 0xe7 => { + 0xa1 => 0x7ead, 0xa2 => 0x7eb0, 0xa3 => 0x7ebe, 0xa4 => 0x7ec0, + 0xa5 => 0x7ec1, 0xa6 => 0x7ec2, 0xa7 => 0x7ec9, 0xa8 => 0x7ecb, + 0xa9 => 0x7ecc, 0xaa => 0x7ed0, 0xab => 0x7ed4, 0xac => 0x7ed7, + 0xad => 0x7edb, 0xae => 0x7ee0, 0xaf => 0x7ee1, 0xb0 => 0x7ee8, + 0xb1 => 0x7eeb, 0xb2 => 0x7eee, 0xb3 => 0x7eef, 0xb4 => 0x7ef1, + 0xb5 => 0x7ef2, 0xb6 => 0x7f0d, 0xb7 => 0x7ef6, 0xb8 => 0x7efa, + 0xb9 => 0x7efb, 0xba => 0x7efe, 0xbb => 0x7f01, 0xbc => 0x7f02, + 0xbd => 0x7f03, 0xbe => 0x7f07, 0xbf => 0x7f08, 0xc0 => 0x7f0b, + 0xc1 => 0x7f0c, 0xc2 => 0x7f0f, 0xc3 => 0x7f11, 0xc4 => 0x7f12, + 0xc5 => 0x7f17, 0xc6 => 0x7f19, 0xc7 => 0x7f1c, 0xc8 => 0x7f1b, + 0xc9 => 0x7f1f, 0xca => 0x7f21, 0xcb => 0x7f22, 0xcc => 0x7f23, + 0xcd => 0x7f24, 0xce => 0x7f25, 0xcf => 0x7f26, 0xd0 => 0x7f27, + 0xd1 => 0x7f2a, 0xd2 => 0x7f2b, 0xd3 => 0x7f2c, 0xd4 => 0x7f2d, + 0xd5 => 0x7f2f, 0xd6 => 0x7f30, 0xd7 => 0x7f31, 0xd8 => 0x7f32, + 0xd9 => 0x7f33, 0xda => 0x7f35, 0xdb => 0x5e7a, 0xdc => 0x757f, + 0xdd => 0x5ddb, 0xde => 0x753e, 0xdf => 0x9095, 0xe0 => 0x738e, + 0xe1 => 0x7391, 0xe2 => 0x73ae, 0xe3 => 0x73a2, 0xe4 => 0x739f, + 0xe5 => 0x73cf, 0xe6 => 0x73c2, 0xe7 => 0x73d1, 0xe8 => 0x73b7, + 0xe9 => 0x73b3, 0xea => 0x73c0, 0xeb => 0x73c9, 0xec => 0x73c8, + 0xed => 0x73e5, 0xee => 0x73d9, 0xef => 0x987c, 0xf0 => 0x740a, + 0xf1 => 0x73e9, 0xf2 => 0x73e7, 0xf3 => 0x73de, 0xf4 => 0x73ba, + 0xf5 => 0x73f2, 0xf6 => 0x740f, 0xf7 => 0x742a, 0xf8 => 0x745b, + 0xf9 => 0x7426, 0xfa => 0x7425, 0xfb => 0x7428, 0xfc => 0x7430, + 0xfd => 0x742e, 0xfe => 0x742c, + }, + 0xe8 => { + 0xa1 => 0x741b, 0xa2 => 0x741a, 0xa3 => 0x7441, 0xa4 => 0x745c, + 0xa5 => 0x7457, 0xa6 => 0x7455, 0xa7 => 0x7459, 0xa8 => 0x7477, + 0xa9 => 0x746d, 0xaa => 0x747e, 0xab => 0x749c, 0xac => 0x748e, + 0xad => 0x7480, 0xae => 0x7481, 0xaf => 0x7487, 0xb0 => 0x748b, + 0xb1 => 0x749e, 0xb2 => 0x74a8, 0xb3 => 0x74a9, 0xb4 => 0x7490, + 0xb5 => 0x74a7, 0xb6 => 0x74d2, 0xb7 => 0x74ba, 0xb8 => 0x97ea, + 0xb9 => 0x97eb, 0xba => 0x97ec, 0xbb => 0x674c, 0xbc => 0x6753, + 0xbd => 0x675e, 0xbe => 0x6748, 0xbf => 0x6769, 0xc0 => 0x67a5, + 0xc1 => 0x6787, 0xc2 => 0x676a, 0xc3 => 0x6773, 0xc4 => 0x6798, + 0xc5 => 0x67a7, 0xc6 => 0x6775, 0xc7 => 0x67a8, 0xc8 => 0x679e, + 0xc9 => 0x67ad, 0xca => 0x678b, 0xcb => 0x6777, 0xcc => 0x677c, + 0xcd => 0x67f0, 0xce => 0x6809, 0xcf => 0x67d8, 0xd0 => 0x680a, + 0xd1 => 0x67e9, 0xd2 => 0x67b0, 0xd3 => 0x680c, 0xd4 => 0x67d9, + 0xd5 => 0x67b5, 0xd6 => 0x67da, 0xd7 => 0x67b3, 0xd8 => 0x67dd, + 0xd9 => 0x6800, 0xda => 0x67c3, 0xdb => 0x67b8, 0xdc => 0x67e2, + 0xdd => 0x680e, 0xde => 0x67c1, 0xdf => 0x67fd, 0xe0 => 0x6832, + 0xe1 => 0x6833, 0xe2 => 0x6860, 0xe3 => 0x6861, 0xe4 => 0x684e, + 0xe5 => 0x6862, 0xe6 => 0x6844, 0xe7 => 0x6864, 0xe8 => 0x6883, + 0xe9 => 0x681d, 0xea => 0x6855, 0xeb => 0x6866, 0xec => 0x6841, + 0xed => 0x6867, 0xee => 0x6840, 0xef => 0x683e, 0xf0 => 0x684a, + 0xf1 => 0x6849, 0xf2 => 0x6829, 0xf3 => 0x68b5, 0xf4 => 0x688f, + 0xf5 => 0x6874, 0xf6 => 0x6877, 0xf7 => 0x6893, 0xf8 => 0x686b, + 0xf9 => 0x68c2, 0xfa => 0x696e, 0xfb => 0x68fc, 0xfc => 0x691f, + 0xfd => 0x6920, 0xfe => 0x68f9, + }, + 0xe9 => { + 0xa1 => 0x6924, 0xa2 => 0x68f0, 0xa3 => 0x690b, 0xa4 => 0x6901, + 0xa5 => 0x6957, 0xa6 => 0x68e3, 0xa7 => 0x6910, 0xa8 => 0x6971, + 0xa9 => 0x6939, 0xaa => 0x6960, 0xab => 0x6942, 0xac => 0x695d, + 0xad => 0x6984, 0xae => 0x696b, 0xaf => 0x6980, 0xb0 => 0x6998, + 0xb1 => 0x6978, 0xb2 => 0x6934, 0xb3 => 0x69cc, 0xb4 => 0x6987, + 0xb5 => 0x6988, 0xb6 => 0x69ce, 0xb7 => 0x6989, 0xb8 => 0x6966, + 0xb9 => 0x6963, 0xba => 0x6979, 0xbb => 0x699b, 0xbc => 0x69a7, + 0xbd => 0x69bb, 0xbe => 0x69ab, 0xbf => 0x69ad, 0xc0 => 0x69d4, + 0xc1 => 0x69b1, 0xc2 => 0x69c1, 0xc3 => 0x69ca, 0xc4 => 0x69df, + 0xc5 => 0x6995, 0xc6 => 0x69e0, 0xc7 => 0x698d, 0xc8 => 0x69ff, + 0xc9 => 0x6a2f, 0xca => 0x69ed, 0xcb => 0x6a17, 0xcc => 0x6a18, + 0xcd => 0x6a65, 0xce => 0x69f2, 0xcf => 0x6a44, 0xd0 => 0x6a3e, + 0xd1 => 0x6aa0, 0xd2 => 0x6a50, 0xd3 => 0x6a5b, 0xd4 => 0x6a35, + 0xd5 => 0x6a8e, 0xd6 => 0x6a79, 0xd7 => 0x6a3d, 0xd8 => 0x6a28, + 0xd9 => 0x6a58, 0xda => 0x6a7c, 0xdb => 0x6a91, 0xdc => 0x6a90, + 0xdd => 0x6aa9, 0xde => 0x6a97, 0xdf => 0x6aab, 0xe0 => 0x7337, + 0xe1 => 0x7352, 0xe2 => 0x6b81, 0xe3 => 0x6b82, 0xe4 => 0x6b87, + 0xe5 => 0x6b84, 0xe6 => 0x6b92, 0xe7 => 0x6b93, 0xe8 => 0x6b8d, + 0xe9 => 0x6b9a, 0xea => 0x6b9b, 0xeb => 0x6ba1, 0xec => 0x6baa, + 0xed => 0x8f6b, 0xee => 0x8f6d, 0xef => 0x8f71, 0xf0 => 0x8f72, + 0xf1 => 0x8f73, 0xf2 => 0x8f75, 0xf3 => 0x8f76, 0xf4 => 0x8f78, + 0xf5 => 0x8f77, 0xf6 => 0x8f79, 0xf7 => 0x8f7a, 0xf8 => 0x8f7c, + 0xf9 => 0x8f7e, 0xfa => 0x8f81, 0xfb => 0x8f82, 0xfc => 0x8f84, + 0xfd => 0x8f87, 0xfe => 0x8f8b, + }, + 0xea => { + 0xa1 => 0x8f8d, 0xa2 => 0x8f8e, 0xa3 => 0x8f8f, 0xa4 => 0x8f98, + 0xa5 => 0x8f9a, 0xa6 => 0x8ece, 0xa7 => 0x620b, 0xa8 => 0x6217, + 0xa9 => 0x621b, 0xaa => 0x621f, 0xab => 0x6222, 0xac => 0x6221, + 0xad => 0x6225, 0xae => 0x6224, 0xaf => 0x622c, 0xb0 => 0x81e7, + 0xb1 => 0x74ef, 0xb2 => 0x74f4, 0xb3 => 0x74ff, 0xb4 => 0x750f, + 0xb5 => 0x7511, 0xb6 => 0x7513, 0xb7 => 0x6534, 0xb8 => 0x65ee, + 0xb9 => 0x65ef, 0xba => 0x65f0, 0xbb => 0x660a, 0xbc => 0x6619, + 0xbd => 0x6772, 0xbe => 0x6603, 0xbf => 0x6615, 0xc0 => 0x6600, + 0xc1 => 0x7085, 0xc2 => 0x66f7, 0xc3 => 0x661d, 0xc4 => 0x6634, + 0xc5 => 0x6631, 0xc6 => 0x6636, 0xc7 => 0x6635, 0xc8 => 0x8006, + 0xc9 => 0x665f, 0xca => 0x6654, 0xcb => 0x6641, 0xcc => 0x664f, + 0xcd => 0x6656, 0xce => 0x6661, 0xcf => 0x6657, 0xd0 => 0x6677, + 0xd1 => 0x6684, 0xd2 => 0x668c, 0xd3 => 0x66a7, 0xd4 => 0x669d, + 0xd5 => 0x66be, 0xd6 => 0x66db, 0xd7 => 0x66dc, 0xd8 => 0x66e6, + 0xd9 => 0x66e9, 0xda => 0x8d32, 0xdb => 0x8d33, 0xdc => 0x8d36, + 0xdd => 0x8d3b, 0xde => 0x8d3d, 0xdf => 0x8d40, 0xe0 => 0x8d45, + 0xe1 => 0x8d46, 0xe2 => 0x8d48, 0xe3 => 0x8d49, 0xe4 => 0x8d47, + 0xe5 => 0x8d4d, 0xe6 => 0x8d55, 0xe7 => 0x8d59, 0xe8 => 0x89c7, + 0xe9 => 0x89ca, 0xea => 0x89cb, 0xeb => 0x89cc, 0xec => 0x89ce, + 0xed => 0x89cf, 0xee => 0x89d0, 0xef => 0x89d1, 0xf0 => 0x726e, + 0xf1 => 0x729f, 0xf2 => 0x725d, 0xf3 => 0x7266, 0xf4 => 0x726f, + 0xf5 => 0x727e, 0xf6 => 0x727f, 0xf7 => 0x7284, 0xf8 => 0x728b, + 0xf9 => 0x728d, 0xfa => 0x728f, 0xfb => 0x7292, 0xfc => 0x6308, + 0xfd => 0x6332, 0xfe => 0x63b0, + }, + 0xeb => { + 0xa1 => 0x643f, 0xa2 => 0x64d8, 0xa3 => 0x8004, 0xa4 => 0x6bea, + 0xa5 => 0x6bf3, 0xa6 => 0x6bfd, 0xa7 => 0x6bf5, 0xa8 => 0x6bf9, + 0xa9 => 0x6c05, 0xaa => 0x6c07, 0xab => 0x6c06, 0xac => 0x6c0d, + 0xad => 0x6c15, 0xae => 0x6c18, 0xaf => 0x6c19, 0xb0 => 0x6c1a, + 0xb1 => 0x6c21, 0xb2 => 0x6c29, 0xb3 => 0x6c24, 0xb4 => 0x6c2a, + 0xb5 => 0x6c32, 0xb6 => 0x6535, 0xb7 => 0x6555, 0xb8 => 0x656b, + 0xb9 => 0x724d, 0xba => 0x7252, 0xbb => 0x7256, 0xbc => 0x7230, + 0xbd => 0x8662, 0xbe => 0x5216, 0xbf => 0x809f, 0xc0 => 0x809c, + 0xc1 => 0x8093, 0xc2 => 0x80bc, 0xc3 => 0x670a, 0xc4 => 0x80bd, + 0xc5 => 0x80b1, 0xc6 => 0x80ab, 0xc7 => 0x80ad, 0xc8 => 0x80b4, + 0xc9 => 0x80b7, 0xca => 0x80e7, 0xcb => 0x80e8, 0xcc => 0x80e9, + 0xcd => 0x80ea, 0xce => 0x80db, 0xcf => 0x80c2, 0xd0 => 0x80c4, + 0xd1 => 0x80d9, 0xd2 => 0x80cd, 0xd3 => 0x80d7, 0xd4 => 0x6710, + 0xd5 => 0x80dd, 0xd6 => 0x80eb, 0xd7 => 0x80f1, 0xd8 => 0x80f4, + 0xd9 => 0x80ed, 0xda => 0x810d, 0xdb => 0x810e, 0xdc => 0x80f2, + 0xdd => 0x80fc, 0xde => 0x6715, 0xdf => 0x8112, 0xe0 => 0x8c5a, + 0xe1 => 0x8136, 0xe2 => 0x811e, 0xe3 => 0x812c, 0xe4 => 0x8118, + 0xe5 => 0x8132, 0xe6 => 0x8148, 0xe7 => 0x814c, 0xe8 => 0x8153, + 0xe9 => 0x8174, 0xea => 0x8159, 0xeb => 0x815a, 0xec => 0x8171, + 0xed => 0x8160, 0xee => 0x8169, 0xef => 0x817c, 0xf0 => 0x817d, + 0xf1 => 0x816d, 0xf2 => 0x8167, 0xf3 => 0x584d, 0xf4 => 0x5ab5, + 0xf5 => 0x8188, 0xf6 => 0x8182, 0xf7 => 0x8191, 0xf8 => 0x6ed5, + 0xf9 => 0x81a3, 0xfa => 0x81aa, 0xfb => 0x81cc, 0xfc => 0x6726, + 0xfd => 0x81ca, 0xfe => 0x81bb, + }, + 0xec => { + 0xa1 => 0x81c1, 0xa2 => 0x81a6, 0xa3 => 0x6b24, 0xa4 => 0x6b37, + 0xa5 => 0x6b39, 0xa6 => 0x6b43, 0xa7 => 0x6b46, 0xa8 => 0x6b59, + 0xa9 => 0x98d1, 0xaa => 0x98d2, 0xab => 0x98d3, 0xac => 0x98d5, + 0xad => 0x98d9, 0xae => 0x98da, 0xaf => 0x6bb3, 0xb0 => 0x5f40, + 0xb1 => 0x6bc2, 0xb2 => 0x89f3, 0xb3 => 0x6590, 0xb4 => 0x9f51, + 0xb5 => 0x6593, 0xb6 => 0x65bc, 0xb7 => 0x65c6, 0xb8 => 0x65c4, + 0xb9 => 0x65c3, 0xba => 0x65cc, 0xbb => 0x65ce, 0xbc => 0x65d2, + 0xbd => 0x65d6, 0xbe => 0x7080, 0xbf => 0x709c, 0xc0 => 0x7096, + 0xc1 => 0x709d, 0xc2 => 0x70bb, 0xc3 => 0x70c0, 0xc4 => 0x70b7, + 0xc5 => 0x70ab, 0xc6 => 0x70b1, 0xc7 => 0x70e8, 0xc8 => 0x70ca, + 0xc9 => 0x7110, 0xca => 0x7113, 0xcb => 0x7116, 0xcc => 0x712f, + 0xcd => 0x7131, 0xce => 0x7173, 0xcf => 0x715c, 0xd0 => 0x7168, + 0xd1 => 0x7145, 0xd2 => 0x7172, 0xd3 => 0x714a, 0xd4 => 0x7178, + 0xd5 => 0x717a, 0xd6 => 0x7198, 0xd7 => 0x71b3, 0xd8 => 0x71b5, + 0xd9 => 0x71a8, 0xda => 0x71a0, 0xdb => 0x71e0, 0xdc => 0x71d4, + 0xdd => 0x71e7, 0xde => 0x71f9, 0xdf => 0x721d, 0xe0 => 0x7228, + 0xe1 => 0x706c, 0xe2 => 0x7118, 0xe3 => 0x7166, 0xe4 => 0x71b9, + 0xe5 => 0x623e, 0xe6 => 0x623d, 0xe7 => 0x6243, 0xe8 => 0x6248, + 0xe9 => 0x6249, 0xea => 0x793b, 0xeb => 0x7940, 0xec => 0x7946, + 0xed => 0x7949, 0xee => 0x795b, 0xef => 0x795c, 0xf0 => 0x7953, + 0xf1 => 0x795a, 0xf2 => 0x7962, 0xf3 => 0x7957, 0xf4 => 0x7960, + 0xf5 => 0x796f, 0xf6 => 0x7967, 0xf7 => 0x797a, 0xf8 => 0x7985, + 0xf9 => 0x798a, 0xfa => 0x799a, 0xfb => 0x79a7, 0xfc => 0x79b3, + 0xfd => 0x5fd1, 0xfe => 0x5fd0, + }, + 0xed => { + 0xa1 => 0x603c, 0xa2 => 0x605d, 0xa3 => 0x605a, 0xa4 => 0x6067, + 0xa5 => 0x6041, 0xa6 => 0x6059, 0xa7 => 0x6063, 0xa8 => 0x60ab, + 0xa9 => 0x6106, 0xaa => 0x610d, 0xab => 0x615d, 0xac => 0x61a9, + 0xad => 0x619d, 0xae => 0x61cb, 0xaf => 0x61d1, 0xb0 => 0x6206, + 0xb1 => 0x8080, 0xb2 => 0x807f, 0xb3 => 0x6c93, 0xb4 => 0x6cf6, + 0xb5 => 0x6dfc, 0xb6 => 0x77f6, 0xb7 => 0x77f8, 0xb8 => 0x7800, + 0xb9 => 0x7809, 0xba => 0x7817, 0xbb => 0x7818, 0xbc => 0x7811, + 0xbd => 0x65ab, 0xbe => 0x782d, 0xbf => 0x781c, 0xc0 => 0x781d, + 0xc1 => 0x7839, 0xc2 => 0x783a, 0xc3 => 0x783b, 0xc4 => 0x781f, + 0xc5 => 0x783c, 0xc6 => 0x7825, 0xc7 => 0x782c, 0xc8 => 0x7823, + 0xc9 => 0x7829, 0xca => 0x784e, 0xcb => 0x786d, 0xcc => 0x7856, + 0xcd => 0x7857, 0xce => 0x7826, 0xcf => 0x7850, 0xd0 => 0x7847, + 0xd1 => 0x784c, 0xd2 => 0x786a, 0xd3 => 0x789b, 0xd4 => 0x7893, + 0xd5 => 0x789a, 0xd6 => 0x7887, 0xd7 => 0x789c, 0xd8 => 0x78a1, + 0xd9 => 0x78a3, 0xda => 0x78b2, 0xdb => 0x78b9, 0xdc => 0x78a5, + 0xdd => 0x78d4, 0xde => 0x78d9, 0xdf => 0x78c9, 0xe0 => 0x78ec, + 0xe1 => 0x78f2, 0xe2 => 0x7905, 0xe3 => 0x78f4, 0xe4 => 0x7913, + 0xe5 => 0x7924, 0xe6 => 0x791e, 0xe7 => 0x7934, 0xe8 => 0x9f9b, + 0xe9 => 0x9ef9, 0xea => 0x9efb, 0xeb => 0x9efc, 0xec => 0x76f1, + 0xed => 0x7704, 0xee => 0x770d, 0xef => 0x76f9, 0xf0 => 0x7707, + 0xf1 => 0x7708, 0xf2 => 0x771a, 0xf3 => 0x7722, 0xf4 => 0x7719, + 0xf5 => 0x772d, 0xf6 => 0x7726, 0xf7 => 0x7735, 0xf8 => 0x7738, + 0xf9 => 0x7750, 0xfa => 0x7751, 0xfb => 0x7747, 0xfc => 0x7743, + 0xfd => 0x775a, 0xfe => 0x7768, + }, + 0xee => { + 0xa1 => 0x7762, 0xa2 => 0x7765, 0xa3 => 0x777f, 0xa4 => 0x778d, + 0xa5 => 0x777d, 0xa6 => 0x7780, 0xa7 => 0x778c, 0xa8 => 0x7791, + 0xa9 => 0x779f, 0xaa => 0x77a0, 0xab => 0x77b0, 0xac => 0x77b5, + 0xad => 0x77bd, 0xae => 0x753a, 0xaf => 0x7540, 0xb0 => 0x754e, + 0xb1 => 0x754b, 0xb2 => 0x7548, 0xb3 => 0x755b, 0xb4 => 0x7572, + 0xb5 => 0x7579, 0xb6 => 0x7583, 0xb7 => 0x7f58, 0xb8 => 0x7f61, + 0xb9 => 0x7f5f, 0xba => 0x8a48, 0xbb => 0x7f68, 0xbc => 0x7f74, + 0xbd => 0x7f71, 0xbe => 0x7f79, 0xbf => 0x7f81, 0xc0 => 0x7f7e, + 0xc1 => 0x76cd, 0xc2 => 0x76e5, 0xc3 => 0x8832, 0xc4 => 0x9485, + 0xc5 => 0x9486, 0xc6 => 0x9487, 0xc7 => 0x948b, 0xc8 => 0x948a, + 0xc9 => 0x948c, 0xca => 0x948d, 0xcb => 0x948f, 0xcc => 0x9490, + 0xcd => 0x9494, 0xce => 0x9497, 0xcf => 0x9495, 0xd0 => 0x949a, + 0xd1 => 0x949b, 0xd2 => 0x949c, 0xd3 => 0x94a3, 0xd4 => 0x94a4, + 0xd5 => 0x94ab, 0xd6 => 0x94aa, 0xd7 => 0x94ad, 0xd8 => 0x94ac, + 0xd9 => 0x94af, 0xda => 0x94b0, 0xdb => 0x94b2, 0xdc => 0x94b4, + 0xdd => 0x94b6, 0xde => 0x94b7, 0xdf => 0x94b8, 0xe0 => 0x94b9, + 0xe1 => 0x94ba, 0xe2 => 0x94bc, 0xe3 => 0x94bd, 0xe4 => 0x94bf, + 0xe5 => 0x94c4, 0xe6 => 0x94c8, 0xe7 => 0x94c9, 0xe8 => 0x94ca, + 0xe9 => 0x94cb, 0xea => 0x94cc, 0xeb => 0x94cd, 0xec => 0x94ce, + 0xed => 0x94d0, 0xee => 0x94d1, 0xef => 0x94d2, 0xf0 => 0x94d5, + 0xf1 => 0x94d6, 0xf2 => 0x94d7, 0xf3 => 0x94d9, 0xf4 => 0x94d8, + 0xf5 => 0x94db, 0xf6 => 0x94de, 0xf7 => 0x94df, 0xf8 => 0x94e0, + 0xf9 => 0x94e2, 0xfa => 0x94e4, 0xfb => 0x94e5, 0xfc => 0x94e7, + 0xfd => 0x94e8, 0xfe => 0x94ea, + }, + 0xef => { + 0xa1 => 0x94e9, 0xa2 => 0x94eb, 0xa3 => 0x94ee, 0xa4 => 0x94ef, + 0xa5 => 0x94f3, 0xa6 => 0x94f4, 0xa7 => 0x94f5, 0xa8 => 0x94f7, + 0xa9 => 0x94f9, 0xaa => 0x94fc, 0xab => 0x94fd, 0xac => 0x94ff, + 0xad => 0x9503, 0xae => 0x9502, 0xaf => 0x9506, 0xb0 => 0x9507, + 0xb1 => 0x9509, 0xb2 => 0x950a, 0xb3 => 0x950d, 0xb4 => 0x950e, + 0xb5 => 0x950f, 0xb6 => 0x9512, 0xb7 => 0x9513, 0xb8 => 0x9514, + 0xb9 => 0x9515, 0xba => 0x9516, 0xbb => 0x9518, 0xbc => 0x951b, + 0xbd => 0x951d, 0xbe => 0x951e, 0xbf => 0x951f, 0xc0 => 0x9522, + 0xc1 => 0x952a, 0xc2 => 0x952b, 0xc3 => 0x9529, 0xc4 => 0x952c, + 0xc5 => 0x9531, 0xc6 => 0x9532, 0xc7 => 0x9534, 0xc8 => 0x9536, + 0xc9 => 0x9537, 0xca => 0x9538, 0xcb => 0x953c, 0xcc => 0x953e, + 0xcd => 0x953f, 0xce => 0x9542, 0xcf => 0x9535, 0xd0 => 0x9544, + 0xd1 => 0x9545, 0xd2 => 0x9546, 0xd3 => 0x9549, 0xd4 => 0x954c, + 0xd5 => 0x954e, 0xd6 => 0x954f, 0xd7 => 0x9552, 0xd8 => 0x9553, + 0xd9 => 0x9554, 0xda => 0x9556, 0xdb => 0x9557, 0xdc => 0x9558, + 0xdd => 0x9559, 0xde => 0x955b, 0xdf => 0x955e, 0xe0 => 0x955f, + 0xe1 => 0x955d, 0xe2 => 0x9561, 0xe3 => 0x9562, 0xe4 => 0x9564, + 0xe5 => 0x9565, 0xe6 => 0x9566, 0xe7 => 0x9567, 0xe8 => 0x9568, + 0xe9 => 0x9569, 0xea => 0x956a, 0xeb => 0x956b, 0xec => 0x956c, + 0xed => 0x956f, 0xee => 0x9571, 0xef => 0x9572, 0xf0 => 0x9573, + 0xf1 => 0x953a, 0xf2 => 0x77e7, 0xf3 => 0x77ec, 0xf4 => 0x96c9, + 0xf5 => 0x79d5, 0xf6 => 0x79ed, 0xf7 => 0x79e3, 0xf8 => 0x79eb, + 0xf9 => 0x7a06, 0xfa => 0x5d47, 0xfb => 0x7a03, 0xfc => 0x7a02, + 0xfd => 0x7a1e, 0xfe => 0x7a14, + }, + 0xf0 => { + 0xa1 => 0x7a39, 0xa2 => 0x7a37, 0xa3 => 0x7a51, 0xa4 => 0x9ecf, + 0xa5 => 0x99a5, 0xa6 => 0x7a70, 0xa7 => 0x7688, 0xa8 => 0x768e, + 0xa9 => 0x7693, 0xaa => 0x7699, 0xab => 0x76a4, 0xac => 0x74de, + 0xad => 0x74e0, 0xae => 0x752c, 0xaf => 0x9e20, 0xb0 => 0x9e22, + 0xb1 => 0x9e28, 0xb2 => 0x9e29, 0xb3 => 0x9e2a, 0xb4 => 0x9e2b, + 0xb5 => 0x9e2c, 0xb6 => 0x9e32, 0xb7 => 0x9e31, 0xb8 => 0x9e36, + 0xb9 => 0x9e38, 0xba => 0x9e37, 0xbb => 0x9e39, 0xbc => 0x9e3a, + 0xbd => 0x9e3e, 0xbe => 0x9e41, 0xbf => 0x9e42, 0xc0 => 0x9e44, + 0xc1 => 0x9e46, 0xc2 => 0x9e47, 0xc3 => 0x9e48, 0xc4 => 0x9e49, + 0xc5 => 0x9e4b, 0xc6 => 0x9e4c, 0xc7 => 0x9e4e, 0xc8 => 0x9e51, + 0xc9 => 0x9e55, 0xca => 0x9e57, 0xcb => 0x9e5a, 0xcc => 0x9e5b, + 0xcd => 0x9e5c, 0xce => 0x9e5e, 0xcf => 0x9e63, 0xd0 => 0x9e66, + 0xd1 => 0x9e67, 0xd2 => 0x9e68, 0xd3 => 0x9e69, 0xd4 => 0x9e6a, + 0xd5 => 0x9e6b, 0xd6 => 0x9e6c, 0xd7 => 0x9e71, 0xd8 => 0x9e6d, + 0xd9 => 0x9e73, 0xda => 0x7592, 0xdb => 0x7594, 0xdc => 0x7596, + 0xdd => 0x75a0, 0xde => 0x759d, 0xdf => 0x75ac, 0xe0 => 0x75a3, + 0xe1 => 0x75b3, 0xe2 => 0x75b4, 0xe3 => 0x75b8, 0xe4 => 0x75c4, + 0xe5 => 0x75b1, 0xe6 => 0x75b0, 0xe7 => 0x75c3, 0xe8 => 0x75c2, + 0xe9 => 0x75d6, 0xea => 0x75cd, 0xeb => 0x75e3, 0xec => 0x75e8, + 0xed => 0x75e6, 0xee => 0x75e4, 0xef => 0x75eb, 0xf0 => 0x75e7, + 0xf1 => 0x7603, 0xf2 => 0x75f1, 0xf3 => 0x75fc, 0xf4 => 0x75ff, + 0xf5 => 0x7610, 0xf6 => 0x7600, 0xf7 => 0x7605, 0xf8 => 0x760c, + 0xf9 => 0x7617, 0xfa => 0x760a, 0xfb => 0x7625, 0xfc => 0x7618, + 0xfd => 0x7615, 0xfe => 0x7619, + }, + 0xf1 => { + 0xa1 => 0x761b, 0xa2 => 0x763c, 0xa3 => 0x7622, 0xa4 => 0x7620, + 0xa5 => 0x7640, 0xa6 => 0x762d, 0xa7 => 0x7630, 0xa8 => 0x763f, + 0xa9 => 0x7635, 0xaa => 0x7643, 0xab => 0x763e, 0xac => 0x7633, + 0xad => 0x764d, 0xae => 0x765e, 0xaf => 0x7654, 0xb0 => 0x765c, + 0xb1 => 0x7656, 0xb2 => 0x766b, 0xb3 => 0x766f, 0xb4 => 0x7fca, + 0xb5 => 0x7ae6, 0xb6 => 0x7a78, 0xb7 => 0x7a79, 0xb8 => 0x7a80, + 0xb9 => 0x7a86, 0xba => 0x7a88, 0xbb => 0x7a95, 0xbc => 0x7aa6, + 0xbd => 0x7aa0, 0xbe => 0x7aac, 0xbf => 0x7aa8, 0xc0 => 0x7aad, + 0xc1 => 0x7ab3, 0xc2 => 0x8864, 0xc3 => 0x8869, 0xc4 => 0x8872, + 0xc5 => 0x887d, 0xc6 => 0x887f, 0xc7 => 0x8882, 0xc8 => 0x88a2, + 0xc9 => 0x88c6, 0xca => 0x88b7, 0xcb => 0x88bc, 0xcc => 0x88c9, + 0xcd => 0x88e2, 0xce => 0x88ce, 0xcf => 0x88e3, 0xd0 => 0x88e5, + 0xd1 => 0x88f1, 0xd2 => 0x891a, 0xd3 => 0x88fc, 0xd4 => 0x88e8, + 0xd5 => 0x88fe, 0xd6 => 0x88f0, 0xd7 => 0x8921, 0xd8 => 0x8919, + 0xd9 => 0x8913, 0xda => 0x891b, 0xdb => 0x890a, 0xdc => 0x8934, + 0xdd => 0x892b, 0xde => 0x8936, 0xdf => 0x8941, 0xe0 => 0x8966, + 0xe1 => 0x897b, 0xe2 => 0x758b, 0xe3 => 0x80e5, 0xe4 => 0x76b2, + 0xe5 => 0x76b4, 0xe6 => 0x77dc, 0xe7 => 0x8012, 0xe8 => 0x8014, + 0xe9 => 0x8016, 0xea => 0x801c, 0xeb => 0x8020, 0xec => 0x8022, + 0xed => 0x8025, 0xee => 0x8026, 0xef => 0x8027, 0xf0 => 0x8029, + 0xf1 => 0x8028, 0xf2 => 0x8031, 0xf3 => 0x800b, 0xf4 => 0x8035, + 0xf5 => 0x8043, 0xf6 => 0x8046, 0xf7 => 0x804d, 0xf8 => 0x8052, + 0xf9 => 0x8069, 0xfa => 0x8071, 0xfb => 0x8983, 0xfc => 0x9878, + 0xfd => 0x9880, 0xfe => 0x9883, + }, + 0xf2 => { + 0xa1 => 0x9889, 0xa2 => 0x988c, 0xa3 => 0x988d, 0xa4 => 0x988f, + 0xa5 => 0x9894, 0xa6 => 0x989a, 0xa7 => 0x989b, 0xa8 => 0x989e, + 0xa9 => 0x989f, 0xaa => 0x98a1, 0xab => 0x98a2, 0xac => 0x98a5, + 0xad => 0x98a6, 0xae => 0x864d, 0xaf => 0x8654, 0xb0 => 0x866c, + 0xb1 => 0x866e, 0xb2 => 0x867f, 0xb3 => 0x867a, 0xb4 => 0x867c, + 0xb5 => 0x867b, 0xb6 => 0x86a8, 0xb7 => 0x868d, 0xb8 => 0x868b, + 0xb9 => 0x86ac, 0xba => 0x869d, 0xbb => 0x86a7, 0xbc => 0x86a3, + 0xbd => 0x86aa, 0xbe => 0x8693, 0xbf => 0x86a9, 0xc0 => 0x86b6, + 0xc1 => 0x86c4, 0xc2 => 0x86b5, 0xc3 => 0x86ce, 0xc4 => 0x86b0, + 0xc5 => 0x86ba, 0xc6 => 0x86b1, 0xc7 => 0x86af, 0xc8 => 0x86c9, + 0xc9 => 0x86cf, 0xca => 0x86b4, 0xcb => 0x86e9, 0xcc => 0x86f1, + 0xcd => 0x86f2, 0xce => 0x86ed, 0xcf => 0x86f3, 0xd0 => 0x86d0, + 0xd1 => 0x8713, 0xd2 => 0x86de, 0xd3 => 0x86f4, 0xd4 => 0x86df, + 0xd5 => 0x86d8, 0xd6 => 0x86d1, 0xd7 => 0x8703, 0xd8 => 0x8707, + 0xd9 => 0x86f8, 0xda => 0x8708, 0xdb => 0x870a, 0xdc => 0x870d, + 0xdd => 0x8709, 0xde => 0x8723, 0xdf => 0x873b, 0xe0 => 0x871e, + 0xe1 => 0x8725, 0xe2 => 0x872e, 0xe3 => 0x871a, 0xe4 => 0x873e, + 0xe5 => 0x8748, 0xe6 => 0x8734, 0xe7 => 0x8731, 0xe8 => 0x8729, + 0xe9 => 0x8737, 0xea => 0x873f, 0xeb => 0x8782, 0xec => 0x8722, + 0xed => 0x877d, 0xee => 0x877e, 0xef => 0x877b, 0xf0 => 0x8760, + 0xf1 => 0x8770, 0xf2 => 0x874c, 0xf3 => 0x876e, 0xf4 => 0x878b, + 0xf5 => 0x8753, 0xf6 => 0x8763, 0xf7 => 0x877c, 0xf8 => 0x8764, + 0xf9 => 0x8759, 0xfa => 0x8765, 0xfb => 0x8793, 0xfc => 0x87af, + 0xfd => 0x87a8, 0xfe => 0x87d2, + }, + 0xf3 => { + 0xa1 => 0x87c6, 0xa2 => 0x8788, 0xa3 => 0x8785, 0xa4 => 0x87ad, + 0xa5 => 0x8797, 0xa6 => 0x8783, 0xa7 => 0x87ab, 0xa8 => 0x87e5, + 0xa9 => 0x87ac, 0xaa => 0x87b5, 0xab => 0x87b3, 0xac => 0x87cb, + 0xad => 0x87d3, 0xae => 0x87bd, 0xaf => 0x87d1, 0xb0 => 0x87c0, + 0xb1 => 0x87ca, 0xb2 => 0x87db, 0xb3 => 0x87ea, 0xb4 => 0x87e0, + 0xb5 => 0x87ee, 0xb6 => 0x8816, 0xb7 => 0x8813, 0xb8 => 0x87fe, + 0xb9 => 0x880a, 0xba => 0x881b, 0xbb => 0x8821, 0xbc => 0x8839, + 0xbd => 0x883c, 0xbe => 0x7f36, 0xbf => 0x7f42, 0xc0 => 0x7f44, + 0xc1 => 0x7f45, 0xc2 => 0x8210, 0xc3 => 0x7afa, 0xc4 => 0x7afd, + 0xc5 => 0x7b08, 0xc6 => 0x7b03, 0xc7 => 0x7b04, 0xc8 => 0x7b15, + 0xc9 => 0x7b0a, 0xca => 0x7b2b, 0xcb => 0x7b0f, 0xcc => 0x7b47, + 0xcd => 0x7b38, 0xce => 0x7b2a, 0xcf => 0x7b19, 0xd0 => 0x7b2e, + 0xd1 => 0x7b31, 0xd2 => 0x7b20, 0xd3 => 0x7b25, 0xd4 => 0x7b24, + 0xd5 => 0x7b33, 0xd6 => 0x7b3e, 0xd7 => 0x7b1e, 0xd8 => 0x7b58, + 0xd9 => 0x7b5a, 0xda => 0x7b45, 0xdb => 0x7b75, 0xdc => 0x7b4c, + 0xdd => 0x7b5d, 0xde => 0x7b60, 0xdf => 0x7b6e, 0xe0 => 0x7b7b, + 0xe1 => 0x7b62, 0xe2 => 0x7b72, 0xe3 => 0x7b71, 0xe4 => 0x7b90, + 0xe5 => 0x7ba6, 0xe6 => 0x7ba7, 0xe7 => 0x7bb8, 0xe8 => 0x7bac, + 0xe9 => 0x7b9d, 0xea => 0x7ba8, 0xeb => 0x7b85, 0xec => 0x7baa, + 0xed => 0x7b9c, 0xee => 0x7ba2, 0xef => 0x7bab, 0xf0 => 0x7bb4, + 0xf1 => 0x7bd1, 0xf2 => 0x7bc1, 0xf3 => 0x7bcc, 0xf4 => 0x7bdd, + 0xf5 => 0x7bda, 0xf6 => 0x7be5, 0xf7 => 0x7be6, 0xf8 => 0x7bea, + 0xf9 => 0x7c0c, 0xfa => 0x7bfe, 0xfb => 0x7bfc, 0xfc => 0x7c0f, + 0xfd => 0x7c16, 0xfe => 0x7c0b, + }, + 0xf4 => { + 0xa1 => 0x7c1f, 0xa2 => 0x7c2a, 0xa3 => 0x7c26, 0xa4 => 0x7c38, + 0xa5 => 0x7c41, 0xa6 => 0x7c40, 0xa7 => 0x81fe, 0xa8 => 0x8201, + 0xa9 => 0x8202, 0xaa => 0x8204, 0xab => 0x81ec, 0xac => 0x8844, + 0xad => 0x8221, 0xae => 0x8222, 0xaf => 0x8223, 0xb0 => 0x822d, + 0xb1 => 0x822f, 0xb2 => 0x8228, 0xb3 => 0x822b, 0xb4 => 0x8238, + 0xb5 => 0x823b, 0xb6 => 0x8233, 0xb7 => 0x8234, 0xb8 => 0x823e, + 0xb9 => 0x8244, 0xba => 0x8249, 0xbb => 0x824b, 0xbc => 0x824f, + 0xbd => 0x825a, 0xbe => 0x825f, 0xbf => 0x8268, 0xc0 => 0x887e, + 0xc1 => 0x8885, 0xc2 => 0x8888, 0xc3 => 0x88d8, 0xc4 => 0x88df, + 0xc5 => 0x895e, 0xc6 => 0x7f9d, 0xc7 => 0x7f9f, 0xc8 => 0x7fa7, + 0xc9 => 0x7faf, 0xca => 0x7fb0, 0xcb => 0x7fb2, 0xcc => 0x7c7c, + 0xcd => 0x6549, 0xce => 0x7c91, 0xcf => 0x7c9d, 0xd0 => 0x7c9c, + 0xd1 => 0x7c9e, 0xd2 => 0x7ca2, 0xd3 => 0x7cb2, 0xd4 => 0x7cbc, + 0xd5 => 0x7cbd, 0xd6 => 0x7cc1, 0xd7 => 0x7cc7, 0xd8 => 0x7ccc, + 0xd9 => 0x7ccd, 0xda => 0x7cc8, 0xdb => 0x7cc5, 0xdc => 0x7cd7, + 0xdd => 0x7ce8, 0xde => 0x826e, 0xdf => 0x66a8, 0xe0 => 0x7fbf, + 0xe1 => 0x7fce, 0xe2 => 0x7fd5, 0xe3 => 0x7fe5, 0xe4 => 0x7fe1, + 0xe5 => 0x7fe6, 0xe6 => 0x7fe9, 0xe7 => 0x7fee, 0xe8 => 0x7ff3, + 0xe9 => 0x7cf8, 0xea => 0x7d77, 0xeb => 0x7da6, 0xec => 0x7dae, + 0xed => 0x7e47, 0xee => 0x7e9b, 0xef => 0x9eb8, 0xf0 => 0x9eb4, + 0xf1 => 0x8d73, 0xf2 => 0x8d84, 0xf3 => 0x8d94, 0xf4 => 0x8d91, + 0xf5 => 0x8db1, 0xf6 => 0x8d67, 0xf7 => 0x8d6d, 0xf8 => 0x8c47, + 0xf9 => 0x8c49, 0xfa => 0x914a, 0xfb => 0x9150, 0xfc => 0x914e, + 0xfd => 0x914f, 0xfe => 0x9164, + }, + 0xf5 => { + 0xa1 => 0x9162, 0xa2 => 0x9161, 0xa3 => 0x9170, 0xa4 => 0x9169, + 0xa5 => 0x916f, 0xa6 => 0x917d, 0xa7 => 0x917e, 0xa8 => 0x9172, + 0xa9 => 0x9174, 0xaa => 0x9179, 0xab => 0x918c, 0xac => 0x9185, + 0xad => 0x9190, 0xae => 0x918d, 0xaf => 0x9191, 0xb0 => 0x91a2, + 0xb1 => 0x91a3, 0xb2 => 0x91aa, 0xb3 => 0x91ad, 0xb4 => 0x91ae, + 0xb5 => 0x91af, 0xb6 => 0x91b5, 0xb7 => 0x91b4, 0xb8 => 0x91ba, + 0xb9 => 0x8c55, 0xba => 0x9e7e, 0xbb => 0x8db8, 0xbc => 0x8deb, + 0xbd => 0x8e05, 0xbe => 0x8e59, 0xbf => 0x8e69, 0xc0 => 0x8db5, + 0xc1 => 0x8dbf, 0xc2 => 0x8dbc, 0xc3 => 0x8dba, 0xc4 => 0x8dc4, + 0xc5 => 0x8dd6, 0xc6 => 0x8dd7, 0xc7 => 0x8dda, 0xc8 => 0x8dde, + 0xc9 => 0x8dce, 0xca => 0x8dcf, 0xcb => 0x8ddb, 0xcc => 0x8dc6, + 0xcd => 0x8dec, 0xce => 0x8df7, 0xcf => 0x8df8, 0xd0 => 0x8de3, + 0xd1 => 0x8df9, 0xd2 => 0x8dfb, 0xd3 => 0x8de4, 0xd4 => 0x8e09, + 0xd5 => 0x8dfd, 0xd6 => 0x8e14, 0xd7 => 0x8e1d, 0xd8 => 0x8e1f, + 0xd9 => 0x8e2c, 0xda => 0x8e2e, 0xdb => 0x8e23, 0xdc => 0x8e2f, + 0xdd => 0x8e3a, 0xde => 0x8e40, 0xdf => 0x8e39, 0xe0 => 0x8e35, + 0xe1 => 0x8e3d, 0xe2 => 0x8e31, 0xe3 => 0x8e49, 0xe4 => 0x8e41, + 0xe5 => 0x8e42, 0xe6 => 0x8e51, 0xe7 => 0x8e52, 0xe8 => 0x8e4a, + 0xe9 => 0x8e70, 0xea => 0x8e76, 0xeb => 0x8e7c, 0xec => 0x8e6f, + 0xed => 0x8e74, 0xee => 0x8e85, 0xef => 0x8e8f, 0xf0 => 0x8e94, + 0xf1 => 0x8e90, 0xf2 => 0x8e9c, 0xf3 => 0x8e9e, 0xf4 => 0x8c78, + 0xf5 => 0x8c82, 0xf6 => 0x8c8a, 0xf7 => 0x8c85, 0xf8 => 0x8c98, + 0xf9 => 0x8c94, 0xfa => 0x659b, 0xfb => 0x89d6, 0xfc => 0x89de, + 0xfd => 0x89da, 0xfe => 0x89dc, + }, + 0xf6 => { + 0xa1 => 0x89e5, 0xa2 => 0x89eb, 0xa3 => 0x89ef, 0xa4 => 0x8a3e, + 0xa5 => 0x8b26, 0xa6 => 0x9753, 0xa7 => 0x96e9, 0xa8 => 0x96f3, + 0xa9 => 0x96ef, 0xaa => 0x9706, 0xab => 0x9701, 0xac => 0x9708, + 0xad => 0x970f, 0xae => 0x970e, 0xaf => 0x972a, 0xb0 => 0x972d, + 0xb1 => 0x9730, 0xb2 => 0x973e, 0xb3 => 0x9f80, 0xb4 => 0x9f83, + 0xb5 => 0x9f85, 0xb6 => 0x9f86, 0xb7 => 0x9f87, 0xb8 => 0x9f88, + 0xb9 => 0x9f89, 0xba => 0x9f8a, 0xbb => 0x9f8c, 0xbc => 0x9efe, + 0xbd => 0x9f0b, 0xbe => 0x9f0d, 0xbf => 0x96b9, 0xc0 => 0x96bc, + 0xc1 => 0x96bd, 0xc2 => 0x96ce, 0xc3 => 0x96d2, 0xc4 => 0x77bf, + 0xc5 => 0x96e0, 0xc6 => 0x928e, 0xc7 => 0x92ae, 0xc8 => 0x92c8, + 0xc9 => 0x933e, 0xca => 0x936a, 0xcb => 0x93ca, 0xcc => 0x938f, + 0xcd => 0x943e, 0xce => 0x946b, 0xcf => 0x9c7f, 0xd0 => 0x9c82, + 0xd1 => 0x9c85, 0xd2 => 0x9c86, 0xd3 => 0x9c87, 0xd4 => 0x9c88, + 0xd5 => 0x7a23, 0xd6 => 0x9c8b, 0xd7 => 0x9c8e, 0xd8 => 0x9c90, + 0xd9 => 0x9c91, 0xda => 0x9c92, 0xdb => 0x9c94, 0xdc => 0x9c95, + 0xdd => 0x9c9a, 0xde => 0x9c9b, 0xdf => 0x9c9e, 0xe0 => 0x9c9f, + 0xe1 => 0x9ca0, 0xe2 => 0x9ca1, 0xe3 => 0x9ca2, 0xe4 => 0x9ca3, + 0xe5 => 0x9ca5, 0xe6 => 0x9ca6, 0xe7 => 0x9ca7, 0xe8 => 0x9ca8, + 0xe9 => 0x9ca9, 0xea => 0x9cab, 0xeb => 0x9cad, 0xec => 0x9cae, + 0xed => 0x9cb0, 0xee => 0x9cb1, 0xef => 0x9cb2, 0xf0 => 0x9cb3, + 0xf1 => 0x9cb4, 0xf2 => 0x9cb5, 0xf3 => 0x9cb6, 0xf4 => 0x9cb7, + 0xf5 => 0x9cba, 0xf6 => 0x9cbb, 0xf7 => 0x9cbc, 0xf8 => 0x9cbd, + 0xf9 => 0x9cc4, 0xfa => 0x9cc5, 0xfb => 0x9cc6, 0xfc => 0x9cc7, + 0xfd => 0x9cca, 0xfe => 0x9ccb, + }, + 0xf7 => { + 0xa1 => 0x9ccc, 0xa2 => 0x9ccd, 0xa3 => 0x9cce, 0xa4 => 0x9ccf, + 0xa5 => 0x9cd0, 0xa6 => 0x9cd3, 0xa7 => 0x9cd4, 0xa8 => 0x9cd5, + 0xa9 => 0x9cd7, 0xaa => 0x9cd8, 0xab => 0x9cd9, 0xac => 0x9cdc, + 0xad => 0x9cdd, 0xae => 0x9cdf, 0xaf => 0x9ce2, 0xb0 => 0x977c, + 0xb1 => 0x9785, 0xb2 => 0x9791, 0xb3 => 0x9792, 0xb4 => 0x9794, + 0xb5 => 0x97af, 0xb6 => 0x97ab, 0xb7 => 0x97a3, 0xb8 => 0x97b2, + 0xb9 => 0x97b4, 0xba => 0x9ab1, 0xbb => 0x9ab0, 0xbc => 0x9ab7, + 0xbd => 0x9e58, 0xbe => 0x9ab6, 0xbf => 0x9aba, 0xc0 => 0x9abc, + 0xc1 => 0x9ac1, 0xc2 => 0x9ac0, 0xc3 => 0x9ac5, 0xc4 => 0x9ac2, + 0xc5 => 0x9acb, 0xc6 => 0x9acc, 0xc7 => 0x9ad1, 0xc8 => 0x9b45, + 0xc9 => 0x9b43, 0xca => 0x9b47, 0xcb => 0x9b49, 0xcc => 0x9b48, + 0xcd => 0x9b4d, 0xce => 0x9b51, 0xcf => 0x98e8, 0xd0 => 0x990d, + 0xd1 => 0x992e, 0xd2 => 0x9955, 0xd3 => 0x9954, 0xd4 => 0x9adf, + 0xd5 => 0x9ae1, 0xd6 => 0x9ae6, 0xd7 => 0x9aef, 0xd8 => 0x9aeb, + 0xd9 => 0x9afb, 0xda => 0x9aed, 0xdb => 0x9af9, 0xdc => 0x9b08, + 0xdd => 0x9b0f, 0xde => 0x9b13, 0xdf => 0x9b1f, 0xe0 => 0x9b23, + 0xe1 => 0x9ebd, 0xe2 => 0x9ebe, 0xe3 => 0x7e3b, 0xe4 => 0x9e82, + 0xe5 => 0x9e87, 0xe6 => 0x9e88, 0xe7 => 0x9e8b, 0xe8 => 0x9e92, + 0xe9 => 0x93d6, 0xea => 0x9e9d, 0xeb => 0x9e9f, 0xec => 0x9edb, + 0xed => 0x9edc, 0xee => 0x9edd, 0xef => 0x9ee0, 0xf0 => 0x9edf, + 0xf1 => 0x9ee2, 0xf2 => 0x9ee9, 0xf3 => 0x9ee7, 0xf4 => 0x9ee5, + 0xf5 => 0x9eea, 0xf6 => 0x9eef, 0xf7 => 0x9f22, 0xf8 => 0x9f2c, + 0xf9 => 0x9f2f, 0xfa => 0x9f39, 0xfb => 0x9f37, 0xfc => 0x9f3d, + 0xfd => 0x9f3e, 0xfe => 0x9f44, + }, + 0xf8 => 0xf8f8, 0xf9 => 0xf8f9, 0xfa => 0xf8fa, 0xfb => 0xf8fb, + 0xfc => 0xf8fc, 0xfd => 0xf8fd, 0xfe => 0xf8fe, 0xff => 0xf8ff, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacRoman.pm b/ExifTool/lib/Image/ExifTool/Charset/MacRoman.pm new file mode 100644 index 0000000..a672b26 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacRoman.pm @@ -0,0 +1,42 @@ +#------------------------------------------------------------------------------ +# File: MacRoman.pm +# +# Description: Mac Roman to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacRoman = ( + 0x80 => 0xc4, 0x81 => 0xc5, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1, + 0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2, + 0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, 0x8e => 0xe9, + 0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0xec, + 0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xf2, + 0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0xf9, + 0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa4 => 0xa7, + 0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, 0xaa => 0x2122, + 0xab => 0xb4, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0xc6, 0xaf => 0xd8, + 0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0xa5, 0xb6 => 0x2202, + 0xb7 => 0x2211, 0xb8 => 0x220f, 0xb9 => 0x03c0, 0xba => 0x222b, 0xbb => 0xaa, + 0xbc => 0xba, 0xbd => 0x03a9, 0xbe => 0xe6, 0xbf => 0xf8, 0xc0 => 0xbf, + 0xc1 => 0xa1, 0xc2 => 0xac, 0xc3 => 0x221a, 0xc4 => 0x0192, 0xc5 => 0x2248, + 0xc6 => 0x2206, 0xc7 => 0xab, 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0, + 0xcb => 0xc0, 0xcc => 0xc3, 0xcd => 0xd5, 0xce => 0x0152, 0xcf => 0x0153, + 0xd0 => 0x2013, 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d, + 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x25ca, 0xd8 => 0xff, + 0xd9 => 0x0178, 0xda => 0x2044, 0xdb => 0x20ac, 0xdc => 0x2039, + 0xdd => 0x203a, 0xde => 0xfb01, 0xdf => 0xfb02, 0xe0 => 0x2021, 0xe1 => 0xb7, + 0xe2 => 0x201a, 0xe3 => 0x201e, 0xe4 => 0x2030, 0xe5 => 0xc2, 0xe6 => 0xca, + 0xe7 => 0xc1, 0xe8 => 0xcb, 0xe9 => 0xc8, 0xea => 0xcd, 0xeb => 0xce, + 0xec => 0xcf, 0xed => 0xcc, 0xee => 0xd3, 0xef => 0xd4, 0xf0 => 0xf8ff, + 0xf1 => 0xd2, 0xf2 => 0xda, 0xf3 => 0xdb, 0xf4 => 0xd9, 0xf5 => 0x0131, + 0xf6 => 0x02c6, 0xf7 => 0x02dc, 0xf8 => 0xaf, 0xf9 => 0x02d8, 0xfa => 0x02d9, + 0xfb => 0x02da, 0xfc => 0xb8, 0xfd => 0x02dd, 0xfe => 0x02db, 0xff => 0x02c7, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacRomanian.pm b/ExifTool/lib/Image/ExifTool/Charset/MacRomanian.pm new file mode 100644 index 0000000..69c9396 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacRomanian.pm @@ -0,0 +1,42 @@ +#------------------------------------------------------------------------------ +# File: MacRomanian.pm +# +# Description: Mac Romanian to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMANIAN.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacRomanian = ( + 0x80 => 0xc4, 0x81 => 0xc5, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1, + 0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2, + 0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, 0x8e => 0xe9, + 0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0xec, + 0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xf2, + 0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0xf9, + 0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa4 => 0xa7, + 0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, 0xaa => 0x2122, + 0xab => 0xb4, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0x0102, 0xaf => 0x0218, + 0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0xa5, 0xb6 => 0x2202, + 0xb7 => 0x2211, 0xb8 => 0x220f, 0xb9 => 0x03c0, 0xba => 0x222b, 0xbb => 0xaa, + 0xbc => 0xba, 0xbd => 0x03a9, 0xbe => 0x0103, 0xbf => 0x0219, 0xc0 => 0xbf, + 0xc1 => 0xa1, 0xc2 => 0xac, 0xc3 => 0x221a, 0xc4 => 0x0192, 0xc5 => 0x2248, + 0xc6 => 0x2206, 0xc7 => 0xab, 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0, + 0xcb => 0xc0, 0xcc => 0xc3, 0xcd => 0xd5, 0xce => 0x0152, 0xcf => 0x0153, + 0xd0 => 0x2013, 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d, + 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x25ca, 0xd8 => 0xff, + 0xd9 => 0x0178, 0xda => 0x2044, 0xdb => 0x20ac, 0xdc => 0x2039, + 0xdd => 0x203a, 0xde => 0x021a, 0xdf => 0x021b, 0xe0 => 0x2021, 0xe1 => 0xb7, + 0xe2 => 0x201a, 0xe3 => 0x201e, 0xe4 => 0x2030, 0xe5 => 0xc2, 0xe6 => 0xca, + 0xe7 => 0xc1, 0xe8 => 0xcb, 0xe9 => 0xc8, 0xea => 0xcd, 0xeb => 0xce, + 0xec => 0xcf, 0xed => 0xcc, 0xee => 0xd3, 0xef => 0xd4, 0xf0 => 0xf8ff, + 0xf1 => 0xd2, 0xf2 => 0xda, 0xf3 => 0xdb, 0xf4 => 0xd9, 0xf5 => 0x0131, + 0xf6 => 0x02c6, 0xf7 => 0x02dc, 0xf8 => 0xaf, 0xf9 => 0x02d8, 0xfa => 0x02d9, + 0xfb => 0x02da, 0xfc => 0xb8, 0xfd => 0x02dd, 0xfe => 0x02db, 0xff => 0x02c7, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacThai.pm b/ExifTool/lib/Image/ExifTool/Charset/MacThai.pm new file mode 100644 index 0000000..7292255 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacThai.pm @@ -0,0 +1,49 @@ +#------------------------------------------------------------------------------ +# File: MacThai.pm +# +# Description: Mac Thai to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/THAI.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacThai = ( + 0x80 => 0xab, 0x81 => 0xbb, 0x82 => 0x2026, 0x83 => [0x0e48,0xf875], + 0x84 => [0x0e49,0xf875], 0x85 => [0x0e4a,0xf875], 0x86 => [0x0e4b,0xf875], + 0x87 => [0x0e4c,0xf875], 0x88 => [0x0e48,0xf873], 0x89 => [0x0e49,0xf873], + 0x8a => [0x0e4a,0xf873], 0x8b => [0x0e4b,0xf873], 0x8c => [0x0e4c,0xf873], + 0x8d => 0x201c, 0x8e => 0x201d, 0x8f => [0x0e4d,0xf874], 0x91 => 0x2022, + 0x92 => [0x0e31,0xf874], 0x93 => [0x0e47,0xf874], 0x94 => [0x0e34,0xf874], + 0x95 => [0x0e35,0xf874], 0x96 => [0x0e36,0xf874], 0x97 => [0x0e37,0xf874], + 0x98 => [0x0e48,0xf874], 0x99 => [0x0e49,0xf874], 0x9a => [0x0e4a,0xf874], + 0x9b => [0x0e4b,0xf874], 0x9c => [0x0e4c,0xf874], 0x9d => 0x2018, + 0x9e => 0x2019, 0xa1 => 0x0e01, 0xa2 => 0x0e02, 0xa3 => 0x0e03, + 0xa4 => 0x0e04, 0xa5 => 0x0e05, 0xa6 => 0x0e06, 0xa7 => 0x0e07, + 0xa8 => 0x0e08, 0xa9 => 0x0e09, 0xaa => 0x0e0a, 0xab => 0x0e0b, + 0xac => 0x0e0c, 0xad => 0x0e0d, 0xae => 0x0e0e, 0xaf => 0x0e0f, + 0xb0 => 0x0e10, 0xb1 => 0x0e11, 0xb2 => 0x0e12, 0xb3 => 0x0e13, + 0xb4 => 0x0e14, 0xb5 => 0x0e15, 0xb6 => 0x0e16, 0xb7 => 0x0e17, + 0xb8 => 0x0e18, 0xb9 => 0x0e19, 0xba => 0x0e1a, 0xbb => 0x0e1b, + 0xbc => 0x0e1c, 0xbd => 0x0e1d, 0xbe => 0x0e1e, 0xbf => 0x0e1f, + 0xc0 => 0x0e20, 0xc1 => 0x0e21, 0xc2 => 0x0e22, 0xc3 => 0x0e23, + 0xc4 => 0x0e24, 0xc5 => 0x0e25, 0xc6 => 0x0e26, 0xc7 => 0x0e27, + 0xc8 => 0x0e28, 0xc9 => 0x0e29, 0xca => 0x0e2a, 0xcb => 0x0e2b, + 0xcc => 0x0e2c, 0xcd => 0x0e2d, 0xce => 0x0e2e, 0xcf => 0x0e2f, + 0xd0 => 0x0e30, 0xd1 => 0x0e31, 0xd2 => 0x0e32, 0xd3 => 0x0e33, + 0xd4 => 0x0e34, 0xd5 => 0x0e35, 0xd6 => 0x0e36, 0xd7 => 0x0e37, + 0xd8 => 0x0e38, 0xd9 => 0x0e39, 0xda => 0x0e3a, 0xdb => 0x2060, + 0xdc => 0x200b, 0xdd => 0x2013, 0xde => 0x2014, 0xdf => 0x0e3f, + 0xe0 => 0x0e40, 0xe1 => 0x0e41, 0xe2 => 0x0e42, 0xe3 => 0x0e43, + 0xe4 => 0x0e44, 0xe5 => 0x0e45, 0xe6 => 0x0e46, 0xe7 => 0x0e47, + 0xe8 => 0x0e48, 0xe9 => 0x0e49, 0xea => 0x0e4a, 0xeb => 0x0e4b, + 0xec => 0x0e4c, 0xed => 0x0e4d, 0xee => 0x2122, 0xef => 0x0e4f, + 0xf0 => 0x0e50, 0xf1 => 0x0e51, 0xf2 => 0x0e52, 0xf3 => 0x0e53, + 0xf4 => 0x0e54, 0xf5 => 0x0e55, 0xf6 => 0x0e56, 0xf7 => 0x0e57, + 0xf8 => 0x0e58, 0xf9 => 0x0e59, 0xfa => 0xae, 0xfb => 0xa9, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/MacTurkish.pm b/ExifTool/lib/Image/ExifTool/Charset/MacTurkish.pm new file mode 100644 index 0000000..d95cb35 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/MacTurkish.pm @@ -0,0 +1,42 @@ +#------------------------------------------------------------------------------ +# File: MacTurkish.pm +# +# Description: Mac Turkish to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/TURKISH.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::MacTurkish = ( + 0x80 => 0xc4, 0x81 => 0xc5, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1, + 0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2, + 0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, 0x8e => 0xe9, + 0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0xec, + 0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xf2, + 0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0xf9, + 0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa4 => 0xa7, + 0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, 0xaa => 0x2122, + 0xab => 0xb4, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0xc6, 0xaf => 0xd8, + 0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0xa5, 0xb6 => 0x2202, + 0xb7 => 0x2211, 0xb8 => 0x220f, 0xb9 => 0x03c0, 0xba => 0x222b, 0xbb => 0xaa, + 0xbc => 0xba, 0xbd => 0x03a9, 0xbe => 0xe6, 0xbf => 0xf8, 0xc0 => 0xbf, + 0xc1 => 0xa1, 0xc2 => 0xac, 0xc3 => 0x221a, 0xc4 => 0x0192, 0xc5 => 0x2248, + 0xc6 => 0x2206, 0xc7 => 0xab, 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0, + 0xcb => 0xc0, 0xcc => 0xc3, 0xcd => 0xd5, 0xce => 0x0152, 0xcf => 0x0153, + 0xd0 => 0x2013, 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d, + 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x25ca, 0xd8 => 0xff, + 0xd9 => 0x0178, 0xda => 0x011e, 0xdb => 0x011f, 0xdc => 0x0130, + 0xdd => 0x0131, 0xde => 0x015e, 0xdf => 0x015f, 0xe0 => 0x2021, 0xe1 => 0xb7, + 0xe2 => 0x201a, 0xe3 => 0x201e, 0xe4 => 0x2030, 0xe5 => 0xc2, 0xe6 => 0xca, + 0xe7 => 0xc1, 0xe8 => 0xcb, 0xe9 => 0xc8, 0xea => 0xcd, 0xeb => 0xce, + 0xec => 0xcf, 0xed => 0xcc, 0xee => 0xd3, 0xef => 0xd4, 0xf0 => 0xf8ff, + 0xf1 => 0xd2, 0xf2 => 0xda, 0xf3 => 0xdb, 0xf4 => 0xd9, 0xf5 => 0xf8a0, + 0xf6 => 0x02c6, 0xf7 => 0x02dc, 0xf8 => 0xaf, 0xf9 => 0x02d8, 0xfa => 0x02d9, + 0xfb => 0x02da, 0xfc => 0xb8, 0xfd => 0x02dd, 0xfe => 0x02db, 0xff => 0x02c7, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/PDFDoc.pm b/ExifTool/lib/Image/ExifTool/Charset/PDFDoc.pm new file mode 100644 index 0000000..83fa0e7 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/PDFDoc.pm @@ -0,0 +1,28 @@ +#------------------------------------------------------------------------------ +# File: PDFDoc.pm +# +# Description: PDFDocEncoding to Unicode +# +# Revisions: 2010/10/16 - P. Harvey created +# +# References: 1) http://www.adobe.com/devnet/pdf/pdf_reference.html +# +# Notes: The table omits 1-byte characters with the same values as Unicode +# This set re-maps characters with codepoints less than 0x80 +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::PDFDoc = ( + 0x18 => 0x02d8, 0x82 => 0x2021, 0x8c => 0x201e, 0x96 => 0x0152, + 0x19 => 0x02c7, 0x83 => 0x2026, 0x8d => 0x201c, 0x97 => 0x0160, + 0x1a => 0x02c6, 0x84 => 0x2014, 0x8e => 0x201d, 0x98 => 0x0178, + 0x1b => 0x02d9, 0x85 => 0x2013, 0x8f => 0x2018, 0x99 => 0x017d, + 0x1c => 0x02dd, 0x86 => 0x0192, 0x90 => 0x2019, 0x9a => 0x0131, + 0x1d => 0x02db, 0x87 => 0x2044, 0x91 => 0x201a, 0x9b => 0x0142, + 0x1e => 0x02da, 0x88 => 0x2039, 0x92 => 0x2122, 0x9c => 0x0153, + 0x1f => 0x02dc, 0x89 => 0x203a, 0x93 => 0xfb01, 0x9d => 0x0161, + 0x80 => 0x2022, 0x8a => 0x2212, 0x94 => 0xfb02, 0x9e => 0x017e, + 0x81 => 0x2020, 0x8b => 0x2030, 0x95 => 0x0141, 0xa0 => 0x20ac, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/ShiftJIS.pm b/ExifTool/lib/Image/ExifTool/Charset/ShiftJIS.pm new file mode 100644 index 0000000..d2a99d3 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/ShiftJIS.pm @@ -0,0 +1,1835 @@ +#------------------------------------------------------------------------------ +# File: ShiftJIS.pm +# +# Description: Shift-JIS to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# 2010/07/30 - P. Harvey fixed to use variable-width characters +# +# References: 1) http://unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/SHIFTJIS.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::ShiftJIS = ( + 0x5c => 0xa5, 0x7e => 0x203e, + 0x81 => { + 0x40 => 0x3000, 0x41 => 0x3001, 0x42 => 0x3002, 0x43 => 0xff0c, + 0x44 => 0xff0e, 0x45 => 0x30fb, 0x46 => 0xff1a, 0x47 => 0xff1b, + 0x48 => 0xff1f, 0x49 => 0xff01, 0x4a => 0x309b, 0x4b => 0x309c, + 0x4c => 0xb4, 0x4d => 0xff40, 0x4e => 0xa8, 0x4f => 0xff3e, 0x50 => 0xffe3, + 0x51 => 0xff3f, 0x52 => 0x30fd, 0x53 => 0x30fe, 0x54 => 0x309d, + 0x55 => 0x309e, 0x56 => 0x3003, 0x57 => 0x4edd, 0x58 => 0x3005, + 0x59 => 0x3006, 0x5a => 0x3007, 0x5b => 0x30fc, 0x5c => 0x2015, + 0x5d => 0x2010, 0x5e => 0xff0f, 0x5f => 0x5c, 0x60 => 0x301c, + 0x61 => 0x2016, 0x62 => 0xff5c, 0x63 => 0x2026, 0x64 => 0x2025, + 0x65 => 0x2018, 0x66 => 0x2019, 0x67 => 0x201c, 0x68 => 0x201d, + 0x69 => 0xff08, 0x6a => 0xff09, 0x6b => 0x3014, 0x6c => 0x3015, + 0x6d => 0xff3b, 0x6e => 0xff3d, 0x6f => 0xff5b, 0x70 => 0xff5d, + 0x71 => 0x3008, 0x72 => 0x3009, 0x73 => 0x300a, 0x74 => 0x300b, + 0x75 => 0x300c, 0x76 => 0x300d, 0x77 => 0x300e, 0x78 => 0x300f, + 0x79 => 0x3010, 0x7a => 0x3011, 0x7b => 0xff0b, 0x7c => 0x2212, + 0x7d => 0xb1, 0x7e => 0xd7, 0x80 => 0xf7, 0x81 => 0xff1d, 0x82 => 0x2260, + 0x83 => 0xff1c, 0x84 => 0xff1e, 0x85 => 0x2266, 0x86 => 0x2267, + 0x87 => 0x221e, 0x88 => 0x2234, 0x89 => 0x2642, 0x8a => 0x2640, + 0x8b => 0xb0, 0x8c => 0x2032, 0x8d => 0x2033, 0x8e => 0x2103, + 0x8f => 0xffe5, 0x90 => 0xff04, 0x91 => 0xa2, 0x92 => 0xa3, 0x93 => 0xff05, + 0x94 => 0xff03, 0x95 => 0xff06, 0x96 => 0xff0a, 0x97 => 0xff20, + 0x98 => 0xa7, 0x99 => 0x2606, 0x9a => 0x2605, 0x9b => 0x25cb, + 0x9c => 0x25cf, 0x9d => 0x25ce, 0x9e => 0x25c7, 0x9f => 0x25c6, + 0xa0 => 0x25a1, 0xa1 => 0x25a0, 0xa2 => 0x25b3, 0xa3 => 0x25b2, + 0xa4 => 0x25bd, 0xa5 => 0x25bc, 0xa6 => 0x203b, 0xa7 => 0x3012, + 0xa8 => 0x2192, 0xa9 => 0x2190, 0xaa => 0x2191, 0xab => 0x2193, + 0xac => 0x3013, 0xb8 => 0x2208, 0xb9 => 0x220b, 0xba => 0x2286, + 0xbb => 0x2287, 0xbc => 0x2282, 0xbd => 0x2283, 0xbe => 0x222a, + 0xbf => 0x2229, 0xc8 => 0x2227, 0xc9 => 0x2228, 0xca => 0xac, + 0xcb => 0x21d2, 0xcc => 0x21d4, 0xcd => 0x2200, 0xce => 0x2203, + 0xda => 0x2220, 0xdb => 0x22a5, 0xdc => 0x2312, 0xdd => 0x2202, + 0xde => 0x2207, 0xdf => 0x2261, 0xe0 => 0x2252, 0xe1 => 0x226a, + 0xe2 => 0x226b, 0xe3 => 0x221a, 0xe4 => 0x223d, 0xe5 => 0x221d, + 0xe6 => 0x2235, 0xe7 => 0x222b, 0xe8 => 0x222c, 0xf0 => 0x212b, + 0xf1 => 0x2030, 0xf2 => 0x266f, 0xf3 => 0x266d, 0xf4 => 0x266a, + 0xf5 => 0x2020, 0xf6 => 0x2021, 0xf7 => 0xb6, 0xfc => 0x25ef, + }, + 0x82 => { + 0x4f => 0xff10, 0x50 => 0xff11, 0x51 => 0xff12, 0x52 => 0xff13, + 0x53 => 0xff14, 0x54 => 0xff15, 0x55 => 0xff16, 0x56 => 0xff17, + 0x57 => 0xff18, 0x58 => 0xff19, 0x60 => 0xff21, 0x61 => 0xff22, + 0x62 => 0xff23, 0x63 => 0xff24, 0x64 => 0xff25, 0x65 => 0xff26, + 0x66 => 0xff27, 0x67 => 0xff28, 0x68 => 0xff29, 0x69 => 0xff2a, + 0x6a => 0xff2b, 0x6b => 0xff2c, 0x6c => 0xff2d, 0x6d => 0xff2e, + 0x6e => 0xff2f, 0x6f => 0xff30, 0x70 => 0xff31, 0x71 => 0xff32, + 0x72 => 0xff33, 0x73 => 0xff34, 0x74 => 0xff35, 0x75 => 0xff36, + 0x76 => 0xff37, 0x77 => 0xff38, 0x78 => 0xff39, 0x79 => 0xff3a, + 0x81 => 0xff41, 0x82 => 0xff42, 0x83 => 0xff43, 0x84 => 0xff44, + 0x85 => 0xff45, 0x86 => 0xff46, 0x87 => 0xff47, 0x88 => 0xff48, + 0x89 => 0xff49, 0x8a => 0xff4a, 0x8b => 0xff4b, 0x8c => 0xff4c, + 0x8d => 0xff4d, 0x8e => 0xff4e, 0x8f => 0xff4f, 0x90 => 0xff50, + 0x91 => 0xff51, 0x92 => 0xff52, 0x93 => 0xff53, 0x94 => 0xff54, + 0x95 => 0xff55, 0x96 => 0xff56, 0x97 => 0xff57, 0x98 => 0xff58, + 0x99 => 0xff59, 0x9a => 0xff5a, 0x9f => 0x3041, 0xa0 => 0x3042, + 0xa1 => 0x3043, 0xa2 => 0x3044, 0xa3 => 0x3045, 0xa4 => 0x3046, + 0xa5 => 0x3047, 0xa6 => 0x3048, 0xa7 => 0x3049, 0xa8 => 0x304a, + 0xa9 => 0x304b, 0xaa => 0x304c, 0xab => 0x304d, 0xac => 0x304e, + 0xad => 0x304f, 0xae => 0x3050, 0xaf => 0x3051, 0xb0 => 0x3052, + 0xb1 => 0x3053, 0xb2 => 0x3054, 0xb3 => 0x3055, 0xb4 => 0x3056, + 0xb5 => 0x3057, 0xb6 => 0x3058, 0xb7 => 0x3059, 0xb8 => 0x305a, + 0xb9 => 0x305b, 0xba => 0x305c, 0xbb => 0x305d, 0xbc => 0x305e, + 0xbd => 0x305f, 0xbe => 0x3060, 0xbf => 0x3061, 0xc0 => 0x3062, + 0xc1 => 0x3063, 0xc2 => 0x3064, 0xc3 => 0x3065, 0xc4 => 0x3066, + 0xc5 => 0x3067, 0xc6 => 0x3068, 0xc7 => 0x3069, 0xc8 => 0x306a, + 0xc9 => 0x306b, 0xca => 0x306c, 0xcb => 0x306d, 0xcc => 0x306e, + 0xcd => 0x306f, 0xce => 0x3070, 0xcf => 0x3071, 0xd0 => 0x3072, + 0xd1 => 0x3073, 0xd2 => 0x3074, 0xd3 => 0x3075, 0xd4 => 0x3076, + 0xd5 => 0x3077, 0xd6 => 0x3078, 0xd7 => 0x3079, 0xd8 => 0x307a, + 0xd9 => 0x307b, 0xda => 0x307c, 0xdb => 0x307d, 0xdc => 0x307e, + 0xdd => 0x307f, 0xde => 0x3080, 0xdf => 0x3081, 0xe0 => 0x3082, + 0xe1 => 0x3083, 0xe2 => 0x3084, 0xe3 => 0x3085, 0xe4 => 0x3086, + 0xe5 => 0x3087, 0xe6 => 0x3088, 0xe7 => 0x3089, 0xe8 => 0x308a, + 0xe9 => 0x308b, 0xea => 0x308c, 0xeb => 0x308d, 0xec => 0x308e, + 0xed => 0x308f, 0xee => 0x3090, 0xef => 0x3091, 0xf0 => 0x3092, + 0xf1 => 0x3093, + }, + 0x83 => { + 0x40 => 0x30a1, 0x41 => 0x30a2, 0x42 => 0x30a3, 0x43 => 0x30a4, + 0x44 => 0x30a5, 0x45 => 0x30a6, 0x46 => 0x30a7, 0x47 => 0x30a8, + 0x48 => 0x30a9, 0x49 => 0x30aa, 0x4a => 0x30ab, 0x4b => 0x30ac, + 0x4c => 0x30ad, 0x4d => 0x30ae, 0x4e => 0x30af, 0x4f => 0x30b0, + 0x50 => 0x30b1, 0x51 => 0x30b2, 0x52 => 0x30b3, 0x53 => 0x30b4, + 0x54 => 0x30b5, 0x55 => 0x30b6, 0x56 => 0x30b7, 0x57 => 0x30b8, + 0x58 => 0x30b9, 0x59 => 0x30ba, 0x5a => 0x30bb, 0x5b => 0x30bc, + 0x5c => 0x30bd, 0x5d => 0x30be, 0x5e => 0x30bf, 0x5f => 0x30c0, + 0x60 => 0x30c1, 0x61 => 0x30c2, 0x62 => 0x30c3, 0x63 => 0x30c4, + 0x64 => 0x30c5, 0x65 => 0x30c6, 0x66 => 0x30c7, 0x67 => 0x30c8, + 0x68 => 0x30c9, 0x69 => 0x30ca, 0x6a => 0x30cb, 0x6b => 0x30cc, + 0x6c => 0x30cd, 0x6d => 0x30ce, 0x6e => 0x30cf, 0x6f => 0x30d0, + 0x70 => 0x30d1, 0x71 => 0x30d2, 0x72 => 0x30d3, 0x73 => 0x30d4, + 0x74 => 0x30d5, 0x75 => 0x30d6, 0x76 => 0x30d7, 0x77 => 0x30d8, + 0x78 => 0x30d9, 0x79 => 0x30da, 0x7a => 0x30db, 0x7b => 0x30dc, + 0x7c => 0x30dd, 0x7d => 0x30de, 0x7e => 0x30df, 0x80 => 0x30e0, + 0x81 => 0x30e1, 0x82 => 0x30e2, 0x83 => 0x30e3, 0x84 => 0x30e4, + 0x85 => 0x30e5, 0x86 => 0x30e6, 0x87 => 0x30e7, 0x88 => 0x30e8, + 0x89 => 0x30e9, 0x8a => 0x30ea, 0x8b => 0x30eb, 0x8c => 0x30ec, + 0x8d => 0x30ed, 0x8e => 0x30ee, 0x8f => 0x30ef, 0x90 => 0x30f0, + 0x91 => 0x30f1, 0x92 => 0x30f2, 0x93 => 0x30f3, 0x94 => 0x30f4, + 0x95 => 0x30f5, 0x96 => 0x30f6, 0x9f => 0x0391, 0xa0 => 0x0392, + 0xa1 => 0x0393, 0xa2 => 0x0394, 0xa3 => 0x0395, 0xa4 => 0x0396, + 0xa5 => 0x0397, 0xa6 => 0x0398, 0xa7 => 0x0399, 0xa8 => 0x039a, + 0xa9 => 0x039b, 0xaa => 0x039c, 0xab => 0x039d, 0xac => 0x039e, + 0xad => 0x039f, 0xae => 0x03a0, 0xaf => 0x03a1, 0xb0 => 0x03a3, + 0xb1 => 0x03a4, 0xb2 => 0x03a5, 0xb3 => 0x03a6, 0xb4 => 0x03a7, + 0xb5 => 0x03a8, 0xb6 => 0x03a9, 0xbf => 0x03b1, 0xc0 => 0x03b2, + 0xc1 => 0x03b3, 0xc2 => 0x03b4, 0xc3 => 0x03b5, 0xc4 => 0x03b6, + 0xc5 => 0x03b7, 0xc6 => 0x03b8, 0xc7 => 0x03b9, 0xc8 => 0x03ba, + 0xc9 => 0x03bb, 0xca => 0x03bc, 0xcb => 0x03bd, 0xcc => 0x03be, + 0xcd => 0x03bf, 0xce => 0x03c0, 0xcf => 0x03c1, 0xd0 => 0x03c3, + 0xd1 => 0x03c4, 0xd2 => 0x03c5, 0xd3 => 0x03c6, 0xd4 => 0x03c7, + 0xd5 => 0x03c8, 0xd6 => 0x03c9, + }, + 0x84 => { + 0x40 => 0x0410, 0x41 => 0x0411, 0x42 => 0x0412, 0x43 => 0x0413, + 0x44 => 0x0414, 0x45 => 0x0415, 0x46 => 0x0401, 0x47 => 0x0416, + 0x48 => 0x0417, 0x49 => 0x0418, 0x4a => 0x0419, 0x4b => 0x041a, + 0x4c => 0x041b, 0x4d => 0x041c, 0x4e => 0x041d, 0x4f => 0x041e, + 0x50 => 0x041f, 0x51 => 0x0420, 0x52 => 0x0421, 0x53 => 0x0422, + 0x54 => 0x0423, 0x55 => 0x0424, 0x56 => 0x0425, 0x57 => 0x0426, + 0x58 => 0x0427, 0x59 => 0x0428, 0x5a => 0x0429, 0x5b => 0x042a, + 0x5c => 0x042b, 0x5d => 0x042c, 0x5e => 0x042d, 0x5f => 0x042e, + 0x60 => 0x042f, 0x70 => 0x0430, 0x71 => 0x0431, 0x72 => 0x0432, + 0x73 => 0x0433, 0x74 => 0x0434, 0x75 => 0x0435, 0x76 => 0x0451, + 0x77 => 0x0436, 0x78 => 0x0437, 0x79 => 0x0438, 0x7a => 0x0439, + 0x7b => 0x043a, 0x7c => 0x043b, 0x7d => 0x043c, 0x7e => 0x043d, + 0x80 => 0x043e, 0x81 => 0x043f, 0x82 => 0x0440, 0x83 => 0x0441, + 0x84 => 0x0442, 0x85 => 0x0443, 0x86 => 0x0444, 0x87 => 0x0445, + 0x88 => 0x0446, 0x89 => 0x0447, 0x8a => 0x0448, 0x8b => 0x0449, + 0x8c => 0x044a, 0x8d => 0x044b, 0x8e => 0x044c, 0x8f => 0x044d, + 0x90 => 0x044e, 0x91 => 0x044f, 0x9f => 0x2500, 0xa0 => 0x2502, + 0xa1 => 0x250c, 0xa2 => 0x2510, 0xa3 => 0x2518, 0xa4 => 0x2514, + 0xa5 => 0x251c, 0xa6 => 0x252c, 0xa7 => 0x2524, 0xa8 => 0x2534, + 0xa9 => 0x253c, 0xaa => 0x2501, 0xab => 0x2503, 0xac => 0x250f, + 0xad => 0x2513, 0xae => 0x251b, 0xaf => 0x2517, 0xb0 => 0x2523, + 0xb1 => 0x2533, 0xb2 => 0x252b, 0xb3 => 0x253b, 0xb4 => 0x254b, + 0xb5 => 0x2520, 0xb6 => 0x252f, 0xb7 => 0x2528, 0xb8 => 0x2537, + 0xb9 => 0x253f, 0xba => 0x251d, 0xbb => 0x2530, 0xbc => 0x2525, + 0xbd => 0x2538, 0xbe => 0x2542, + }, + 0x88 => { + 0x9f => 0x4e9c, 0xa0 => 0x5516, 0xa1 => 0x5a03, 0xa2 => 0x963f, + 0xa3 => 0x54c0, 0xa4 => 0x611b, 0xa5 => 0x6328, 0xa6 => 0x59f6, + 0xa7 => 0x9022, 0xa8 => 0x8475, 0xa9 => 0x831c, 0xaa => 0x7a50, + 0xab => 0x60aa, 0xac => 0x63e1, 0xad => 0x6e25, 0xae => 0x65ed, + 0xaf => 0x8466, 0xb0 => 0x82a6, 0xb1 => 0x9bf5, 0xb2 => 0x6893, + 0xb3 => 0x5727, 0xb4 => 0x65a1, 0xb5 => 0x6271, 0xb6 => 0x5b9b, + 0xb7 => 0x59d0, 0xb8 => 0x867b, 0xb9 => 0x98f4, 0xba => 0x7d62, + 0xbb => 0x7dbe, 0xbc => 0x9b8e, 0xbd => 0x6216, 0xbe => 0x7c9f, + 0xbf => 0x88b7, 0xc0 => 0x5b89, 0xc1 => 0x5eb5, 0xc2 => 0x6309, + 0xc3 => 0x6697, 0xc4 => 0x6848, 0xc5 => 0x95c7, 0xc6 => 0x978d, + 0xc7 => 0x674f, 0xc8 => 0x4ee5, 0xc9 => 0x4f0a, 0xca => 0x4f4d, + 0xcb => 0x4f9d, 0xcc => 0x5049, 0xcd => 0x56f2, 0xce => 0x5937, + 0xcf => 0x59d4, 0xd0 => 0x5a01, 0xd1 => 0x5c09, 0xd2 => 0x60df, + 0xd3 => 0x610f, 0xd4 => 0x6170, 0xd5 => 0x6613, 0xd6 => 0x6905, + 0xd7 => 0x70ba, 0xd8 => 0x754f, 0xd9 => 0x7570, 0xda => 0x79fb, + 0xdb => 0x7dad, 0xdc => 0x7def, 0xdd => 0x80c3, 0xde => 0x840e, + 0xdf => 0x8863, 0xe0 => 0x8b02, 0xe1 => 0x9055, 0xe2 => 0x907a, + 0xe3 => 0x533b, 0xe4 => 0x4e95, 0xe5 => 0x4ea5, 0xe6 => 0x57df, + 0xe7 => 0x80b2, 0xe8 => 0x90c1, 0xe9 => 0x78ef, 0xea => 0x4e00, + 0xeb => 0x58f1, 0xec => 0x6ea2, 0xed => 0x9038, 0xee => 0x7a32, + 0xef => 0x8328, 0xf0 => 0x828b, 0xf1 => 0x9c2f, 0xf2 => 0x5141, + 0xf3 => 0x5370, 0xf4 => 0x54bd, 0xf5 => 0x54e1, 0xf6 => 0x56e0, + 0xf7 => 0x59fb, 0xf8 => 0x5f15, 0xf9 => 0x98f2, 0xfa => 0x6deb, + 0xfb => 0x80e4, 0xfc => 0x852d, + }, + 0x89 => { + 0x40 => 0x9662, 0x41 => 0x9670, 0x42 => 0x96a0, 0x43 => 0x97fb, + 0x44 => 0x540b, 0x45 => 0x53f3, 0x46 => 0x5b87, 0x47 => 0x70cf, + 0x48 => 0x7fbd, 0x49 => 0x8fc2, 0x4a => 0x96e8, 0x4b => 0x536f, + 0x4c => 0x9d5c, 0x4d => 0x7aba, 0x4e => 0x4e11, 0x4f => 0x7893, + 0x50 => 0x81fc, 0x51 => 0x6e26, 0x52 => 0x5618, 0x53 => 0x5504, + 0x54 => 0x6b1d, 0x55 => 0x851a, 0x56 => 0x9c3b, 0x57 => 0x59e5, + 0x58 => 0x53a9, 0x59 => 0x6d66, 0x5a => 0x74dc, 0x5b => 0x958f, + 0x5c => 0x5642, 0x5d => 0x4e91, 0x5e => 0x904b, 0x5f => 0x96f2, + 0x60 => 0x834f, 0x61 => 0x990c, 0x62 => 0x53e1, 0x63 => 0x55b6, + 0x64 => 0x5b30, 0x65 => 0x5f71, 0x66 => 0x6620, 0x67 => 0x66f3, + 0x68 => 0x6804, 0x69 => 0x6c38, 0x6a => 0x6cf3, 0x6b => 0x6d29, + 0x6c => 0x745b, 0x6d => 0x76c8, 0x6e => 0x7a4e, 0x6f => 0x9834, + 0x70 => 0x82f1, 0x71 => 0x885b, 0x72 => 0x8a60, 0x73 => 0x92ed, + 0x74 => 0x6db2, 0x75 => 0x75ab, 0x76 => 0x76ca, 0x77 => 0x99c5, + 0x78 => 0x60a6, 0x79 => 0x8b01, 0x7a => 0x8d8a, 0x7b => 0x95b2, + 0x7c => 0x698e, 0x7d => 0x53ad, 0x7e => 0x5186, 0x80 => 0x5712, + 0x81 => 0x5830, 0x82 => 0x5944, 0x83 => 0x5bb4, 0x84 => 0x5ef6, + 0x85 => 0x6028, 0x86 => 0x63a9, 0x87 => 0x63f4, 0x88 => 0x6cbf, + 0x89 => 0x6f14, 0x8a => 0x708e, 0x8b => 0x7114, 0x8c => 0x7159, + 0x8d => 0x71d5, 0x8e => 0x733f, 0x8f => 0x7e01, 0x90 => 0x8276, + 0x91 => 0x82d1, 0x92 => 0x8597, 0x93 => 0x9060, 0x94 => 0x925b, + 0x95 => 0x9d1b, 0x96 => 0x5869, 0x97 => 0x65bc, 0x98 => 0x6c5a, + 0x99 => 0x7525, 0x9a => 0x51f9, 0x9b => 0x592e, 0x9c => 0x5965, + 0x9d => 0x5f80, 0x9e => 0x5fdc, 0x9f => 0x62bc, 0xa0 => 0x65fa, + 0xa1 => 0x6a2a, 0xa2 => 0x6b27, 0xa3 => 0x6bb4, 0xa4 => 0x738b, + 0xa5 => 0x7fc1, 0xa6 => 0x8956, 0xa7 => 0x9d2c, 0xa8 => 0x9d0e, + 0xa9 => 0x9ec4, 0xaa => 0x5ca1, 0xab => 0x6c96, 0xac => 0x837b, + 0xad => 0x5104, 0xae => 0x5c4b, 0xaf => 0x61b6, 0xb0 => 0x81c6, + 0xb1 => 0x6876, 0xb2 => 0x7261, 0xb3 => 0x4e59, 0xb4 => 0x4ffa, + 0xb5 => 0x5378, 0xb6 => 0x6069, 0xb7 => 0x6e29, 0xb8 => 0x7a4f, + 0xb9 => 0x97f3, 0xba => 0x4e0b, 0xbb => 0x5316, 0xbc => 0x4eee, + 0xbd => 0x4f55, 0xbe => 0x4f3d, 0xbf => 0x4fa1, 0xc0 => 0x4f73, + 0xc1 => 0x52a0, 0xc2 => 0x53ef, 0xc3 => 0x5609, 0xc4 => 0x590f, + 0xc5 => 0x5ac1, 0xc6 => 0x5bb6, 0xc7 => 0x5be1, 0xc8 => 0x79d1, + 0xc9 => 0x6687, 0xca => 0x679c, 0xcb => 0x67b6, 0xcc => 0x6b4c, + 0xcd => 0x6cb3, 0xce => 0x706b, 0xcf => 0x73c2, 0xd0 => 0x798d, + 0xd1 => 0x79be, 0xd2 => 0x7a3c, 0xd3 => 0x7b87, 0xd4 => 0x82b1, + 0xd5 => 0x82db, 0xd6 => 0x8304, 0xd7 => 0x8377, 0xd8 => 0x83ef, + 0xd9 => 0x83d3, 0xda => 0x8766, 0xdb => 0x8ab2, 0xdc => 0x5629, + 0xdd => 0x8ca8, 0xde => 0x8fe6, 0xdf => 0x904e, 0xe0 => 0x971e, + 0xe1 => 0x868a, 0xe2 => 0x4fc4, 0xe3 => 0x5ce8, 0xe4 => 0x6211, + 0xe5 => 0x7259, 0xe6 => 0x753b, 0xe7 => 0x81e5, 0xe8 => 0x82bd, + 0xe9 => 0x86fe, 0xea => 0x8cc0, 0xeb => 0x96c5, 0xec => 0x9913, + 0xed => 0x99d5, 0xee => 0x4ecb, 0xef => 0x4f1a, 0xf0 => 0x89e3, + 0xf1 => 0x56de, 0xf2 => 0x584a, 0xf3 => 0x58ca, 0xf4 => 0x5efb, + 0xf5 => 0x5feb, 0xf6 => 0x602a, 0xf7 => 0x6094, 0xf8 => 0x6062, + 0xf9 => 0x61d0, 0xfa => 0x6212, 0xfb => 0x62d0, 0xfc => 0x6539, + }, + 0x8a => { + 0x40 => 0x9b41, 0x41 => 0x6666, 0x42 => 0x68b0, 0x43 => 0x6d77, + 0x44 => 0x7070, 0x45 => 0x754c, 0x46 => 0x7686, 0x47 => 0x7d75, + 0x48 => 0x82a5, 0x49 => 0x87f9, 0x4a => 0x958b, 0x4b => 0x968e, + 0x4c => 0x8c9d, 0x4d => 0x51f1, 0x4e => 0x52be, 0x4f => 0x5916, + 0x50 => 0x54b3, 0x51 => 0x5bb3, 0x52 => 0x5d16, 0x53 => 0x6168, + 0x54 => 0x6982, 0x55 => 0x6daf, 0x56 => 0x788d, 0x57 => 0x84cb, + 0x58 => 0x8857, 0x59 => 0x8a72, 0x5a => 0x93a7, 0x5b => 0x9ab8, + 0x5c => 0x6d6c, 0x5d => 0x99a8, 0x5e => 0x86d9, 0x5f => 0x57a3, + 0x60 => 0x67ff, 0x61 => 0x86ce, 0x62 => 0x920e, 0x63 => 0x5283, + 0x64 => 0x5687, 0x65 => 0x5404, 0x66 => 0x5ed3, 0x67 => 0x62e1, + 0x68 => 0x64b9, 0x69 => 0x683c, 0x6a => 0x6838, 0x6b => 0x6bbb, + 0x6c => 0x7372, 0x6d => 0x78ba, 0x6e => 0x7a6b, 0x6f => 0x899a, + 0x70 => 0x89d2, 0x71 => 0x8d6b, 0x72 => 0x8f03, 0x73 => 0x90ed, + 0x74 => 0x95a3, 0x75 => 0x9694, 0x76 => 0x9769, 0x77 => 0x5b66, + 0x78 => 0x5cb3, 0x79 => 0x697d, 0x7a => 0x984d, 0x7b => 0x984e, + 0x7c => 0x639b, 0x7d => 0x7b20, 0x7e => 0x6a2b, 0x80 => 0x6a7f, + 0x81 => 0x68b6, 0x82 => 0x9c0d, 0x83 => 0x6f5f, 0x84 => 0x5272, + 0x85 => 0x559d, 0x86 => 0x6070, 0x87 => 0x62ec, 0x88 => 0x6d3b, + 0x89 => 0x6e07, 0x8a => 0x6ed1, 0x8b => 0x845b, 0x8c => 0x8910, + 0x8d => 0x8f44, 0x8e => 0x4e14, 0x8f => 0x9c39, 0x90 => 0x53f6, + 0x91 => 0x691b, 0x92 => 0x6a3a, 0x93 => 0x9784, 0x94 => 0x682a, + 0x95 => 0x515c, 0x96 => 0x7ac3, 0x97 => 0x84b2, 0x98 => 0x91dc, + 0x99 => 0x938c, 0x9a => 0x565b, 0x9b => 0x9d28, 0x9c => 0x6822, + 0x9d => 0x8305, 0x9e => 0x8431, 0x9f => 0x7ca5, 0xa0 => 0x5208, + 0xa1 => 0x82c5, 0xa2 => 0x74e6, 0xa3 => 0x4e7e, 0xa4 => 0x4f83, + 0xa5 => 0x51a0, 0xa6 => 0x5bd2, 0xa7 => 0x520a, 0xa8 => 0x52d8, + 0xa9 => 0x52e7, 0xaa => 0x5dfb, 0xab => 0x559a, 0xac => 0x582a, + 0xad => 0x59e6, 0xae => 0x5b8c, 0xaf => 0x5b98, 0xb0 => 0x5bdb, + 0xb1 => 0x5e72, 0xb2 => 0x5e79, 0xb3 => 0x60a3, 0xb4 => 0x611f, + 0xb5 => 0x6163, 0xb6 => 0x61be, 0xb7 => 0x63db, 0xb8 => 0x6562, + 0xb9 => 0x67d1, 0xba => 0x6853, 0xbb => 0x68fa, 0xbc => 0x6b3e, + 0xbd => 0x6b53, 0xbe => 0x6c57, 0xbf => 0x6f22, 0xc0 => 0x6f97, + 0xc1 => 0x6f45, 0xc2 => 0x74b0, 0xc3 => 0x7518, 0xc4 => 0x76e3, + 0xc5 => 0x770b, 0xc6 => 0x7aff, 0xc7 => 0x7ba1, 0xc8 => 0x7c21, + 0xc9 => 0x7de9, 0xca => 0x7f36, 0xcb => 0x7ff0, 0xcc => 0x809d, + 0xcd => 0x8266, 0xce => 0x839e, 0xcf => 0x89b3, 0xd0 => 0x8acc, + 0xd1 => 0x8cab, 0xd2 => 0x9084, 0xd3 => 0x9451, 0xd4 => 0x9593, + 0xd5 => 0x9591, 0xd6 => 0x95a2, 0xd7 => 0x9665, 0xd8 => 0x97d3, + 0xd9 => 0x9928, 0xda => 0x8218, 0xdb => 0x4e38, 0xdc => 0x542b, + 0xdd => 0x5cb8, 0xde => 0x5dcc, 0xdf => 0x73a9, 0xe0 => 0x764c, + 0xe1 => 0x773c, 0xe2 => 0x5ca9, 0xe3 => 0x7feb, 0xe4 => 0x8d0b, + 0xe5 => 0x96c1, 0xe6 => 0x9811, 0xe7 => 0x9854, 0xe8 => 0x9858, + 0xe9 => 0x4f01, 0xea => 0x4f0e, 0xeb => 0x5371, 0xec => 0x559c, + 0xed => 0x5668, 0xee => 0x57fa, 0xef => 0x5947, 0xf0 => 0x5b09, + 0xf1 => 0x5bc4, 0xf2 => 0x5c90, 0xf3 => 0x5e0c, 0xf4 => 0x5e7e, + 0xf5 => 0x5fcc, 0xf6 => 0x63ee, 0xf7 => 0x673a, 0xf8 => 0x65d7, + 0xf9 => 0x65e2, 0xfa => 0x671f, 0xfb => 0x68cb, 0xfc => 0x68c4, + }, + 0x8b => { + 0x40 => 0x6a5f, 0x41 => 0x5e30, 0x42 => 0x6bc5, 0x43 => 0x6c17, + 0x44 => 0x6c7d, 0x45 => 0x757f, 0x46 => 0x7948, 0x47 => 0x5b63, + 0x48 => 0x7a00, 0x49 => 0x7d00, 0x4a => 0x5fbd, 0x4b => 0x898f, + 0x4c => 0x8a18, 0x4d => 0x8cb4, 0x4e => 0x8d77, 0x4f => 0x8ecc, + 0x50 => 0x8f1d, 0x51 => 0x98e2, 0x52 => 0x9a0e, 0x53 => 0x9b3c, + 0x54 => 0x4e80, 0x55 => 0x507d, 0x56 => 0x5100, 0x57 => 0x5993, + 0x58 => 0x5b9c, 0x59 => 0x622f, 0x5a => 0x6280, 0x5b => 0x64ec, + 0x5c => 0x6b3a, 0x5d => 0x72a0, 0x5e => 0x7591, 0x5f => 0x7947, + 0x60 => 0x7fa9, 0x61 => 0x87fb, 0x62 => 0x8abc, 0x63 => 0x8b70, + 0x64 => 0x63ac, 0x65 => 0x83ca, 0x66 => 0x97a0, 0x67 => 0x5409, + 0x68 => 0x5403, 0x69 => 0x55ab, 0x6a => 0x6854, 0x6b => 0x6a58, + 0x6c => 0x8a70, 0x6d => 0x7827, 0x6e => 0x6775, 0x6f => 0x9ecd, + 0x70 => 0x5374, 0x71 => 0x5ba2, 0x72 => 0x811a, 0x73 => 0x8650, + 0x74 => 0x9006, 0x75 => 0x4e18, 0x76 => 0x4e45, 0x77 => 0x4ec7, + 0x78 => 0x4f11, 0x79 => 0x53ca, 0x7a => 0x5438, 0x7b => 0x5bae, + 0x7c => 0x5f13, 0x7d => 0x6025, 0x7e => 0x6551, 0x80 => 0x673d, + 0x81 => 0x6c42, 0x82 => 0x6c72, 0x83 => 0x6ce3, 0x84 => 0x7078, + 0x85 => 0x7403, 0x86 => 0x7a76, 0x87 => 0x7aae, 0x88 => 0x7b08, + 0x89 => 0x7d1a, 0x8a => 0x7cfe, 0x8b => 0x7d66, 0x8c => 0x65e7, + 0x8d => 0x725b, 0x8e => 0x53bb, 0x8f => 0x5c45, 0x90 => 0x5de8, + 0x91 => 0x62d2, 0x92 => 0x62e0, 0x93 => 0x6319, 0x94 => 0x6e20, + 0x95 => 0x865a, 0x96 => 0x8a31, 0x97 => 0x8ddd, 0x98 => 0x92f8, + 0x99 => 0x6f01, 0x9a => 0x79a6, 0x9b => 0x9b5a, 0x9c => 0x4ea8, + 0x9d => 0x4eab, 0x9e => 0x4eac, 0x9f => 0x4f9b, 0xa0 => 0x4fa0, + 0xa1 => 0x50d1, 0xa2 => 0x5147, 0xa3 => 0x7af6, 0xa4 => 0x5171, + 0xa5 => 0x51f6, 0xa6 => 0x5354, 0xa7 => 0x5321, 0xa8 => 0x537f, + 0xa9 => 0x53eb, 0xaa => 0x55ac, 0xab => 0x5883, 0xac => 0x5ce1, + 0xad => 0x5f37, 0xae => 0x5f4a, 0xaf => 0x602f, 0xb0 => 0x6050, + 0xb1 => 0x606d, 0xb2 => 0x631f, 0xb3 => 0x6559, 0xb4 => 0x6a4b, + 0xb5 => 0x6cc1, 0xb6 => 0x72c2, 0xb7 => 0x72ed, 0xb8 => 0x77ef, + 0xb9 => 0x80f8, 0xba => 0x8105, 0xbb => 0x8208, 0xbc => 0x854e, + 0xbd => 0x90f7, 0xbe => 0x93e1, 0xbf => 0x97ff, 0xc0 => 0x9957, + 0xc1 => 0x9a5a, 0xc2 => 0x4ef0, 0xc3 => 0x51dd, 0xc4 => 0x5c2d, + 0xc5 => 0x6681, 0xc6 => 0x696d, 0xc7 => 0x5c40, 0xc8 => 0x66f2, + 0xc9 => 0x6975, 0xca => 0x7389, 0xcb => 0x6850, 0xcc => 0x7c81, + 0xcd => 0x50c5, 0xce => 0x52e4, 0xcf => 0x5747, 0xd0 => 0x5dfe, + 0xd1 => 0x9326, 0xd2 => 0x65a4, 0xd3 => 0x6b23, 0xd4 => 0x6b3d, + 0xd5 => 0x7434, 0xd6 => 0x7981, 0xd7 => 0x79bd, 0xd8 => 0x7b4b, + 0xd9 => 0x7dca, 0xda => 0x82b9, 0xdb => 0x83cc, 0xdc => 0x887f, + 0xdd => 0x895f, 0xde => 0x8b39, 0xdf => 0x8fd1, 0xe0 => 0x91d1, + 0xe1 => 0x541f, 0xe2 => 0x9280, 0xe3 => 0x4e5d, 0xe4 => 0x5036, + 0xe5 => 0x53e5, 0xe6 => 0x533a, 0xe7 => 0x72d7, 0xe8 => 0x7396, + 0xe9 => 0x77e9, 0xea => 0x82e6, 0xeb => 0x8eaf, 0xec => 0x99c6, + 0xed => 0x99c8, 0xee => 0x99d2, 0xef => 0x5177, 0xf0 => 0x611a, + 0xf1 => 0x865e, 0xf2 => 0x55b0, 0xf3 => 0x7a7a, 0xf4 => 0x5076, + 0xf5 => 0x5bd3, 0xf6 => 0x9047, 0xf7 => 0x9685, 0xf8 => 0x4e32, + 0xf9 => 0x6adb, 0xfa => 0x91e7, 0xfb => 0x5c51, 0xfc => 0x5c48, + }, + 0x8c => { + 0x40 => 0x6398, 0x41 => 0x7a9f, 0x42 => 0x6c93, 0x43 => 0x9774, + 0x44 => 0x8f61, 0x45 => 0x7aaa, 0x46 => 0x718a, 0x47 => 0x9688, + 0x48 => 0x7c82, 0x49 => 0x6817, 0x4a => 0x7e70, 0x4b => 0x6851, + 0x4c => 0x936c, 0x4d => 0x52f2, 0x4e => 0x541b, 0x4f => 0x85ab, + 0x50 => 0x8a13, 0x51 => 0x7fa4, 0x52 => 0x8ecd, 0x53 => 0x90e1, + 0x54 => 0x5366, 0x55 => 0x8888, 0x56 => 0x7941, 0x57 => 0x4fc2, + 0x58 => 0x50be, 0x59 => 0x5211, 0x5a => 0x5144, 0x5b => 0x5553, + 0x5c => 0x572d, 0x5d => 0x73ea, 0x5e => 0x578b, 0x5f => 0x5951, + 0x60 => 0x5f62, 0x61 => 0x5f84, 0x62 => 0x6075, 0x63 => 0x6176, + 0x64 => 0x6167, 0x65 => 0x61a9, 0x66 => 0x63b2, 0x67 => 0x643a, + 0x68 => 0x656c, 0x69 => 0x666f, 0x6a => 0x6842, 0x6b => 0x6e13, + 0x6c => 0x7566, 0x6d => 0x7a3d, 0x6e => 0x7cfb, 0x6f => 0x7d4c, + 0x70 => 0x7d99, 0x71 => 0x7e4b, 0x72 => 0x7f6b, 0x73 => 0x830e, + 0x74 => 0x834a, 0x75 => 0x86cd, 0x76 => 0x8a08, 0x77 => 0x8a63, + 0x78 => 0x8b66, 0x79 => 0x8efd, 0x7a => 0x981a, 0x7b => 0x9d8f, + 0x7c => 0x82b8, 0x7d => 0x8fce, 0x7e => 0x9be8, 0x80 => 0x5287, + 0x81 => 0x621f, 0x82 => 0x6483, 0x83 => 0x6fc0, 0x84 => 0x9699, + 0x85 => 0x6841, 0x86 => 0x5091, 0x87 => 0x6b20, 0x88 => 0x6c7a, + 0x89 => 0x6f54, 0x8a => 0x7a74, 0x8b => 0x7d50, 0x8c => 0x8840, + 0x8d => 0x8a23, 0x8e => 0x6708, 0x8f => 0x4ef6, 0x90 => 0x5039, + 0x91 => 0x5026, 0x92 => 0x5065, 0x93 => 0x517c, 0x94 => 0x5238, + 0x95 => 0x5263, 0x96 => 0x55a7, 0x97 => 0x570f, 0x98 => 0x5805, + 0x99 => 0x5acc, 0x9a => 0x5efa, 0x9b => 0x61b2, 0x9c => 0x61f8, + 0x9d => 0x62f3, 0x9e => 0x6372, 0x9f => 0x691c, 0xa0 => 0x6a29, + 0xa1 => 0x727d, 0xa2 => 0x72ac, 0xa3 => 0x732e, 0xa4 => 0x7814, + 0xa5 => 0x786f, 0xa6 => 0x7d79, 0xa7 => 0x770c, 0xa8 => 0x80a9, + 0xa9 => 0x898b, 0xaa => 0x8b19, 0xab => 0x8ce2, 0xac => 0x8ed2, + 0xad => 0x9063, 0xae => 0x9375, 0xaf => 0x967a, 0xb0 => 0x9855, + 0xb1 => 0x9a13, 0xb2 => 0x9e78, 0xb3 => 0x5143, 0xb4 => 0x539f, + 0xb5 => 0x53b3, 0xb6 => 0x5e7b, 0xb7 => 0x5f26, 0xb8 => 0x6e1b, + 0xb9 => 0x6e90, 0xba => 0x7384, 0xbb => 0x73fe, 0xbc => 0x7d43, + 0xbd => 0x8237, 0xbe => 0x8a00, 0xbf => 0x8afa, 0xc0 => 0x9650, + 0xc1 => 0x4e4e, 0xc2 => 0x500b, 0xc3 => 0x53e4, 0xc4 => 0x547c, + 0xc5 => 0x56fa, 0xc6 => 0x59d1, 0xc7 => 0x5b64, 0xc8 => 0x5df1, + 0xc9 => 0x5eab, 0xca => 0x5f27, 0xcb => 0x6238, 0xcc => 0x6545, + 0xcd => 0x67af, 0xce => 0x6e56, 0xcf => 0x72d0, 0xd0 => 0x7cca, + 0xd1 => 0x88b4, 0xd2 => 0x80a1, 0xd3 => 0x80e1, 0xd4 => 0x83f0, + 0xd5 => 0x864e, 0xd6 => 0x8a87, 0xd7 => 0x8de8, 0xd8 => 0x9237, + 0xd9 => 0x96c7, 0xda => 0x9867, 0xdb => 0x9f13, 0xdc => 0x4e94, + 0xdd => 0x4e92, 0xde => 0x4f0d, 0xdf => 0x5348, 0xe0 => 0x5449, + 0xe1 => 0x543e, 0xe2 => 0x5a2f, 0xe3 => 0x5f8c, 0xe4 => 0x5fa1, + 0xe5 => 0x609f, 0xe6 => 0x68a7, 0xe7 => 0x6a8e, 0xe8 => 0x745a, + 0xe9 => 0x7881, 0xea => 0x8a9e, 0xeb => 0x8aa4, 0xec => 0x8b77, + 0xed => 0x9190, 0xee => 0x4e5e, 0xef => 0x9bc9, 0xf0 => 0x4ea4, + 0xf1 => 0x4f7c, 0xf2 => 0x4faf, 0xf3 => 0x5019, 0xf4 => 0x5016, + 0xf5 => 0x5149, 0xf6 => 0x516c, 0xf7 => 0x529f, 0xf8 => 0x52b9, + 0xf9 => 0x52fe, 0xfa => 0x539a, 0xfb => 0x53e3, 0xfc => 0x5411, + }, + 0x8d => { + 0x40 => 0x540e, 0x41 => 0x5589, 0x42 => 0x5751, 0x43 => 0x57a2, + 0x44 => 0x597d, 0x45 => 0x5b54, 0x46 => 0x5b5d, 0x47 => 0x5b8f, + 0x48 => 0x5de5, 0x49 => 0x5de7, 0x4a => 0x5df7, 0x4b => 0x5e78, + 0x4c => 0x5e83, 0x4d => 0x5e9a, 0x4e => 0x5eb7, 0x4f => 0x5f18, + 0x50 => 0x6052, 0x51 => 0x614c, 0x52 => 0x6297, 0x53 => 0x62d8, + 0x54 => 0x63a7, 0x55 => 0x653b, 0x56 => 0x6602, 0x57 => 0x6643, + 0x58 => 0x66f4, 0x59 => 0x676d, 0x5a => 0x6821, 0x5b => 0x6897, + 0x5c => 0x69cb, 0x5d => 0x6c5f, 0x5e => 0x6d2a, 0x5f => 0x6d69, + 0x60 => 0x6e2f, 0x61 => 0x6e9d, 0x62 => 0x7532, 0x63 => 0x7687, + 0x64 => 0x786c, 0x65 => 0x7a3f, 0x66 => 0x7ce0, 0x67 => 0x7d05, + 0x68 => 0x7d18, 0x69 => 0x7d5e, 0x6a => 0x7db1, 0x6b => 0x8015, + 0x6c => 0x8003, 0x6d => 0x80af, 0x6e => 0x80b1, 0x6f => 0x8154, + 0x70 => 0x818f, 0x71 => 0x822a, 0x72 => 0x8352, 0x73 => 0x884c, + 0x74 => 0x8861, 0x75 => 0x8b1b, 0x76 => 0x8ca2, 0x77 => 0x8cfc, + 0x78 => 0x90ca, 0x79 => 0x9175, 0x7a => 0x9271, 0x7b => 0x783f, + 0x7c => 0x92fc, 0x7d => 0x95a4, 0x7e => 0x964d, 0x80 => 0x9805, + 0x81 => 0x9999, 0x82 => 0x9ad8, 0x83 => 0x9d3b, 0x84 => 0x525b, + 0x85 => 0x52ab, 0x86 => 0x53f7, 0x87 => 0x5408, 0x88 => 0x58d5, + 0x89 => 0x62f7, 0x8a => 0x6fe0, 0x8b => 0x8c6a, 0x8c => 0x8f5f, + 0x8d => 0x9eb9, 0x8e => 0x514b, 0x8f => 0x523b, 0x90 => 0x544a, + 0x91 => 0x56fd, 0x92 => 0x7a40, 0x93 => 0x9177, 0x94 => 0x9d60, + 0x95 => 0x9ed2, 0x96 => 0x7344, 0x97 => 0x6f09, 0x98 => 0x8170, + 0x99 => 0x7511, 0x9a => 0x5ffd, 0x9b => 0x60da, 0x9c => 0x9aa8, + 0x9d => 0x72db, 0x9e => 0x8fbc, 0x9f => 0x6b64, 0xa0 => 0x9803, + 0xa1 => 0x4eca, 0xa2 => 0x56f0, 0xa3 => 0x5764, 0xa4 => 0x58be, + 0xa5 => 0x5a5a, 0xa6 => 0x6068, 0xa7 => 0x61c7, 0xa8 => 0x660f, + 0xa9 => 0x6606, 0xaa => 0x6839, 0xab => 0x68b1, 0xac => 0x6df7, + 0xad => 0x75d5, 0xae => 0x7d3a, 0xaf => 0x826e, 0xb0 => 0x9b42, + 0xb1 => 0x4e9b, 0xb2 => 0x4f50, 0xb3 => 0x53c9, 0xb4 => 0x5506, + 0xb5 => 0x5d6f, 0xb6 => 0x5de6, 0xb7 => 0x5dee, 0xb8 => 0x67fb, + 0xb9 => 0x6c99, 0xba => 0x7473, 0xbb => 0x7802, 0xbc => 0x8a50, + 0xbd => 0x9396, 0xbe => 0x88df, 0xbf => 0x5750, 0xc0 => 0x5ea7, + 0xc1 => 0x632b, 0xc2 => 0x50b5, 0xc3 => 0x50ac, 0xc4 => 0x518d, + 0xc5 => 0x6700, 0xc6 => 0x54c9, 0xc7 => 0x585e, 0xc8 => 0x59bb, + 0xc9 => 0x5bb0, 0xca => 0x5f69, 0xcb => 0x624d, 0xcc => 0x63a1, + 0xcd => 0x683d, 0xce => 0x6b73, 0xcf => 0x6e08, 0xd0 => 0x707d, + 0xd1 => 0x91c7, 0xd2 => 0x7280, 0xd3 => 0x7815, 0xd4 => 0x7826, + 0xd5 => 0x796d, 0xd6 => 0x658e, 0xd7 => 0x7d30, 0xd8 => 0x83dc, + 0xd9 => 0x88c1, 0xda => 0x8f09, 0xdb => 0x969b, 0xdc => 0x5264, + 0xdd => 0x5728, 0xde => 0x6750, 0xdf => 0x7f6a, 0xe0 => 0x8ca1, + 0xe1 => 0x51b4, 0xe2 => 0x5742, 0xe3 => 0x962a, 0xe4 => 0x583a, + 0xe5 => 0x698a, 0xe6 => 0x80b4, 0xe7 => 0x54b2, 0xe8 => 0x5d0e, + 0xe9 => 0x57fc, 0xea => 0x7895, 0xeb => 0x9dfa, 0xec => 0x4f5c, + 0xed => 0x524a, 0xee => 0x548b, 0xef => 0x643e, 0xf0 => 0x6628, + 0xf1 => 0x6714, 0xf2 => 0x67f5, 0xf3 => 0x7a84, 0xf4 => 0x7b56, + 0xf5 => 0x7d22, 0xf6 => 0x932f, 0xf7 => 0x685c, 0xf8 => 0x9bad, + 0xf9 => 0x7b39, 0xfa => 0x5319, 0xfb => 0x518a, 0xfc => 0x5237, + }, + 0x8e => { + 0x40 => 0x5bdf, 0x41 => 0x62f6, 0x42 => 0x64ae, 0x43 => 0x64e6, + 0x44 => 0x672d, 0x45 => 0x6bba, 0x46 => 0x85a9, 0x47 => 0x96d1, + 0x48 => 0x7690, 0x49 => 0x9bd6, 0x4a => 0x634c, 0x4b => 0x9306, + 0x4c => 0x9bab, 0x4d => 0x76bf, 0x4e => 0x6652, 0x4f => 0x4e09, + 0x50 => 0x5098, 0x51 => 0x53c2, 0x52 => 0x5c71, 0x53 => 0x60e8, + 0x54 => 0x6492, 0x55 => 0x6563, 0x56 => 0x685f, 0x57 => 0x71e6, + 0x58 => 0x73ca, 0x59 => 0x7523, 0x5a => 0x7b97, 0x5b => 0x7e82, + 0x5c => 0x8695, 0x5d => 0x8b83, 0x5e => 0x8cdb, 0x5f => 0x9178, + 0x60 => 0x9910, 0x61 => 0x65ac, 0x62 => 0x66ab, 0x63 => 0x6b8b, + 0x64 => 0x4ed5, 0x65 => 0x4ed4, 0x66 => 0x4f3a, 0x67 => 0x4f7f, + 0x68 => 0x523a, 0x69 => 0x53f8, 0x6a => 0x53f2, 0x6b => 0x55e3, + 0x6c => 0x56db, 0x6d => 0x58eb, 0x6e => 0x59cb, 0x6f => 0x59c9, + 0x70 => 0x59ff, 0x71 => 0x5b50, 0x72 => 0x5c4d, 0x73 => 0x5e02, + 0x74 => 0x5e2b, 0x75 => 0x5fd7, 0x76 => 0x601d, 0x77 => 0x6307, + 0x78 => 0x652f, 0x79 => 0x5b5c, 0x7a => 0x65af, 0x7b => 0x65bd, + 0x7c => 0x65e8, 0x7d => 0x679d, 0x7e => 0x6b62, 0x80 => 0x6b7b, + 0x81 => 0x6c0f, 0x82 => 0x7345, 0x83 => 0x7949, 0x84 => 0x79c1, + 0x85 => 0x7cf8, 0x86 => 0x7d19, 0x87 => 0x7d2b, 0x88 => 0x80a2, + 0x89 => 0x8102, 0x8a => 0x81f3, 0x8b => 0x8996, 0x8c => 0x8a5e, + 0x8d => 0x8a69, 0x8e => 0x8a66, 0x8f => 0x8a8c, 0x90 => 0x8aee, + 0x91 => 0x8cc7, 0x92 => 0x8cdc, 0x93 => 0x96cc, 0x94 => 0x98fc, + 0x95 => 0x6b6f, 0x96 => 0x4e8b, 0x97 => 0x4f3c, 0x98 => 0x4f8d, + 0x99 => 0x5150, 0x9a => 0x5b57, 0x9b => 0x5bfa, 0x9c => 0x6148, + 0x9d => 0x6301, 0x9e => 0x6642, 0x9f => 0x6b21, 0xa0 => 0x6ecb, + 0xa1 => 0x6cbb, 0xa2 => 0x723e, 0xa3 => 0x74bd, 0xa4 => 0x75d4, + 0xa5 => 0x78c1, 0xa6 => 0x793a, 0xa7 => 0x800c, 0xa8 => 0x8033, + 0xa9 => 0x81ea, 0xaa => 0x8494, 0xab => 0x8f9e, 0xac => 0x6c50, + 0xad => 0x9e7f, 0xae => 0x5f0f, 0xaf => 0x8b58, 0xb0 => 0x9d2b, + 0xb1 => 0x7afa, 0xb2 => 0x8ef8, 0xb3 => 0x5b8d, 0xb4 => 0x96eb, + 0xb5 => 0x4e03, 0xb6 => 0x53f1, 0xb7 => 0x57f7, 0xb8 => 0x5931, + 0xb9 => 0x5ac9, 0xba => 0x5ba4, 0xbb => 0x6089, 0xbc => 0x6e7f, + 0xbd => 0x6f06, 0xbe => 0x75be, 0xbf => 0x8cea, 0xc0 => 0x5b9f, + 0xc1 => 0x8500, 0xc2 => 0x7be0, 0xc3 => 0x5072, 0xc4 => 0x67f4, + 0xc5 => 0x829d, 0xc6 => 0x5c61, 0xc7 => 0x854a, 0xc8 => 0x7e1e, + 0xc9 => 0x820e, 0xca => 0x5199, 0xcb => 0x5c04, 0xcc => 0x6368, + 0xcd => 0x8d66, 0xce => 0x659c, 0xcf => 0x716e, 0xd0 => 0x793e, + 0xd1 => 0x7d17, 0xd2 => 0x8005, 0xd3 => 0x8b1d, 0xd4 => 0x8eca, + 0xd5 => 0x906e, 0xd6 => 0x86c7, 0xd7 => 0x90aa, 0xd8 => 0x501f, + 0xd9 => 0x52fa, 0xda => 0x5c3a, 0xdb => 0x6753, 0xdc => 0x707c, + 0xdd => 0x7235, 0xde => 0x914c, 0xdf => 0x91c8, 0xe0 => 0x932b, + 0xe1 => 0x82e5, 0xe2 => 0x5bc2, 0xe3 => 0x5f31, 0xe4 => 0x60f9, + 0xe5 => 0x4e3b, 0xe6 => 0x53d6, 0xe7 => 0x5b88, 0xe8 => 0x624b, + 0xe9 => 0x6731, 0xea => 0x6b8a, 0xeb => 0x72e9, 0xec => 0x73e0, + 0xed => 0x7a2e, 0xee => 0x816b, 0xef => 0x8da3, 0xf0 => 0x9152, + 0xf1 => 0x9996, 0xf2 => 0x5112, 0xf3 => 0x53d7, 0xf4 => 0x546a, + 0xf5 => 0x5bff, 0xf6 => 0x6388, 0xf7 => 0x6a39, 0xf8 => 0x7dac, + 0xf9 => 0x9700, 0xfa => 0x56da, 0xfb => 0x53ce, 0xfc => 0x5468, + }, + 0x8f => { + 0x40 => 0x5b97, 0x41 => 0x5c31, 0x42 => 0x5dde, 0x43 => 0x4fee, + 0x44 => 0x6101, 0x45 => 0x62fe, 0x46 => 0x6d32, 0x47 => 0x79c0, + 0x48 => 0x79cb, 0x49 => 0x7d42, 0x4a => 0x7e4d, 0x4b => 0x7fd2, + 0x4c => 0x81ed, 0x4d => 0x821f, 0x4e => 0x8490, 0x4f => 0x8846, + 0x50 => 0x8972, 0x51 => 0x8b90, 0x52 => 0x8e74, 0x53 => 0x8f2f, + 0x54 => 0x9031, 0x55 => 0x914b, 0x56 => 0x916c, 0x57 => 0x96c6, + 0x58 => 0x919c, 0x59 => 0x4ec0, 0x5a => 0x4f4f, 0x5b => 0x5145, + 0x5c => 0x5341, 0x5d => 0x5f93, 0x5e => 0x620e, 0x5f => 0x67d4, + 0x60 => 0x6c41, 0x61 => 0x6e0b, 0x62 => 0x7363, 0x63 => 0x7e26, + 0x64 => 0x91cd, 0x65 => 0x9283, 0x66 => 0x53d4, 0x67 => 0x5919, + 0x68 => 0x5bbf, 0x69 => 0x6dd1, 0x6a => 0x795d, 0x6b => 0x7e2e, + 0x6c => 0x7c9b, 0x6d => 0x587e, 0x6e => 0x719f, 0x6f => 0x51fa, + 0x70 => 0x8853, 0x71 => 0x8ff0, 0x72 => 0x4fca, 0x73 => 0x5cfb, + 0x74 => 0x6625, 0x75 => 0x77ac, 0x76 => 0x7ae3, 0x77 => 0x821c, + 0x78 => 0x99ff, 0x79 => 0x51c6, 0x7a => 0x5faa, 0x7b => 0x65ec, + 0x7c => 0x696f, 0x7d => 0x6b89, 0x7e => 0x6df3, 0x80 => 0x6e96, + 0x81 => 0x6f64, 0x82 => 0x76fe, 0x83 => 0x7d14, 0x84 => 0x5de1, + 0x85 => 0x9075, 0x86 => 0x9187, 0x87 => 0x9806, 0x88 => 0x51e6, + 0x89 => 0x521d, 0x8a => 0x6240, 0x8b => 0x6691, 0x8c => 0x66d9, + 0x8d => 0x6e1a, 0x8e => 0x5eb6, 0x8f => 0x7dd2, 0x90 => 0x7f72, + 0x91 => 0x66f8, 0x92 => 0x85af, 0x93 => 0x85f7, 0x94 => 0x8af8, + 0x95 => 0x52a9, 0x96 => 0x53d9, 0x97 => 0x5973, 0x98 => 0x5e8f, + 0x99 => 0x5f90, 0x9a => 0x6055, 0x9b => 0x92e4, 0x9c => 0x9664, + 0x9d => 0x50b7, 0x9e => 0x511f, 0x9f => 0x52dd, 0xa0 => 0x5320, + 0xa1 => 0x5347, 0xa2 => 0x53ec, 0xa3 => 0x54e8, 0xa4 => 0x5546, + 0xa5 => 0x5531, 0xa6 => 0x5617, 0xa7 => 0x5968, 0xa8 => 0x59be, + 0xa9 => 0x5a3c, 0xaa => 0x5bb5, 0xab => 0x5c06, 0xac => 0x5c0f, + 0xad => 0x5c11, 0xae => 0x5c1a, 0xaf => 0x5e84, 0xb0 => 0x5e8a, + 0xb1 => 0x5ee0, 0xb2 => 0x5f70, 0xb3 => 0x627f, 0xb4 => 0x6284, + 0xb5 => 0x62db, 0xb6 => 0x638c, 0xb7 => 0x6377, 0xb8 => 0x6607, + 0xb9 => 0x660c, 0xba => 0x662d, 0xbb => 0x6676, 0xbc => 0x677e, + 0xbd => 0x68a2, 0xbe => 0x6a1f, 0xbf => 0x6a35, 0xc0 => 0x6cbc, + 0xc1 => 0x6d88, 0xc2 => 0x6e09, 0xc3 => 0x6e58, 0xc4 => 0x713c, + 0xc5 => 0x7126, 0xc6 => 0x7167, 0xc7 => 0x75c7, 0xc8 => 0x7701, + 0xc9 => 0x785d, 0xca => 0x7901, 0xcb => 0x7965, 0xcc => 0x79f0, + 0xcd => 0x7ae0, 0xce => 0x7b11, 0xcf => 0x7ca7, 0xd0 => 0x7d39, + 0xd1 => 0x8096, 0xd2 => 0x83d6, 0xd3 => 0x848b, 0xd4 => 0x8549, + 0xd5 => 0x885d, 0xd6 => 0x88f3, 0xd7 => 0x8a1f, 0xd8 => 0x8a3c, + 0xd9 => 0x8a54, 0xda => 0x8a73, 0xdb => 0x8c61, 0xdc => 0x8cde, + 0xdd => 0x91a4, 0xde => 0x9266, 0xdf => 0x937e, 0xe0 => 0x9418, + 0xe1 => 0x969c, 0xe2 => 0x9798, 0xe3 => 0x4e0a, 0xe4 => 0x4e08, + 0xe5 => 0x4e1e, 0xe6 => 0x4e57, 0xe7 => 0x5197, 0xe8 => 0x5270, + 0xe9 => 0x57ce, 0xea => 0x5834, 0xeb => 0x58cc, 0xec => 0x5b22, + 0xed => 0x5e38, 0xee => 0x60c5, 0xef => 0x64fe, 0xf0 => 0x6761, + 0xf1 => 0x6756, 0xf2 => 0x6d44, 0xf3 => 0x72b6, 0xf4 => 0x7573, + 0xf5 => 0x7a63, 0xf6 => 0x84b8, 0xf7 => 0x8b72, 0xf8 => 0x91b8, + 0xf9 => 0x9320, 0xfa => 0x5631, 0xfb => 0x57f4, 0xfc => 0x98fe, + }, + 0x90 => { + 0x40 => 0x62ed, 0x41 => 0x690d, 0x42 => 0x6b96, 0x43 => 0x71ed, + 0x44 => 0x7e54, 0x45 => 0x8077, 0x46 => 0x8272, 0x47 => 0x89e6, + 0x48 => 0x98df, 0x49 => 0x8755, 0x4a => 0x8fb1, 0x4b => 0x5c3b, + 0x4c => 0x4f38, 0x4d => 0x4fe1, 0x4e => 0x4fb5, 0x4f => 0x5507, + 0x50 => 0x5a20, 0x51 => 0x5bdd, 0x52 => 0x5be9, 0x53 => 0x5fc3, + 0x54 => 0x614e, 0x55 => 0x632f, 0x56 => 0x65b0, 0x57 => 0x664b, + 0x58 => 0x68ee, 0x59 => 0x699b, 0x5a => 0x6d78, 0x5b => 0x6df1, + 0x5c => 0x7533, 0x5d => 0x75b9, 0x5e => 0x771f, 0x5f => 0x795e, + 0x60 => 0x79e6, 0x61 => 0x7d33, 0x62 => 0x81e3, 0x63 => 0x82af, + 0x64 => 0x85aa, 0x65 => 0x89aa, 0x66 => 0x8a3a, 0x67 => 0x8eab, + 0x68 => 0x8f9b, 0x69 => 0x9032, 0x6a => 0x91dd, 0x6b => 0x9707, + 0x6c => 0x4eba, 0x6d => 0x4ec1, 0x6e => 0x5203, 0x6f => 0x5875, + 0x70 => 0x58ec, 0x71 => 0x5c0b, 0x72 => 0x751a, 0x73 => 0x5c3d, + 0x74 => 0x814e, 0x75 => 0x8a0a, 0x76 => 0x8fc5, 0x77 => 0x9663, + 0x78 => 0x976d, 0x79 => 0x7b25, 0x7a => 0x8acf, 0x7b => 0x9808, + 0x7c => 0x9162, 0x7d => 0x56f3, 0x7e => 0x53a8, 0x80 => 0x9017, + 0x81 => 0x5439, 0x82 => 0x5782, 0x83 => 0x5e25, 0x84 => 0x63a8, + 0x85 => 0x6c34, 0x86 => 0x708a, 0x87 => 0x7761, 0x88 => 0x7c8b, + 0x89 => 0x7fe0, 0x8a => 0x8870, 0x8b => 0x9042, 0x8c => 0x9154, + 0x8d => 0x9310, 0x8e => 0x9318, 0x8f => 0x968f, 0x90 => 0x745e, + 0x91 => 0x9ac4, 0x92 => 0x5d07, 0x93 => 0x5d69, 0x94 => 0x6570, + 0x95 => 0x67a2, 0x96 => 0x8da8, 0x97 => 0x96db, 0x98 => 0x636e, + 0x99 => 0x6749, 0x9a => 0x6919, 0x9b => 0x83c5, 0x9c => 0x9817, + 0x9d => 0x96c0, 0x9e => 0x88fe, 0x9f => 0x6f84, 0xa0 => 0x647a, + 0xa1 => 0x5bf8, 0xa2 => 0x4e16, 0xa3 => 0x702c, 0xa4 => 0x755d, + 0xa5 => 0x662f, 0xa6 => 0x51c4, 0xa7 => 0x5236, 0xa8 => 0x52e2, + 0xa9 => 0x59d3, 0xaa => 0x5f81, 0xab => 0x6027, 0xac => 0x6210, + 0xad => 0x653f, 0xae => 0x6574, 0xaf => 0x661f, 0xb0 => 0x6674, + 0xb1 => 0x68f2, 0xb2 => 0x6816, 0xb3 => 0x6b63, 0xb4 => 0x6e05, + 0xb5 => 0x7272, 0xb6 => 0x751f, 0xb7 => 0x76db, 0xb8 => 0x7cbe, + 0xb9 => 0x8056, 0xba => 0x58f0, 0xbb => 0x88fd, 0xbc => 0x897f, + 0xbd => 0x8aa0, 0xbe => 0x8a93, 0xbf => 0x8acb, 0xc0 => 0x901d, + 0xc1 => 0x9192, 0xc2 => 0x9752, 0xc3 => 0x9759, 0xc4 => 0x6589, + 0xc5 => 0x7a0e, 0xc6 => 0x8106, 0xc7 => 0x96bb, 0xc8 => 0x5e2d, + 0xc9 => 0x60dc, 0xca => 0x621a, 0xcb => 0x65a5, 0xcc => 0x6614, + 0xcd => 0x6790, 0xce => 0x77f3, 0xcf => 0x7a4d, 0xd0 => 0x7c4d, + 0xd1 => 0x7e3e, 0xd2 => 0x810a, 0xd3 => 0x8cac, 0xd4 => 0x8d64, + 0xd5 => 0x8de1, 0xd6 => 0x8e5f, 0xd7 => 0x78a9, 0xd8 => 0x5207, + 0xd9 => 0x62d9, 0xda => 0x63a5, 0xdb => 0x6442, 0xdc => 0x6298, + 0xdd => 0x8a2d, 0xde => 0x7a83, 0xdf => 0x7bc0, 0xe0 => 0x8aac, + 0xe1 => 0x96ea, 0xe2 => 0x7d76, 0xe3 => 0x820c, 0xe4 => 0x8749, + 0xe5 => 0x4ed9, 0xe6 => 0x5148, 0xe7 => 0x5343, 0xe8 => 0x5360, + 0xe9 => 0x5ba3, 0xea => 0x5c02, 0xeb => 0x5c16, 0xec => 0x5ddd, + 0xed => 0x6226, 0xee => 0x6247, 0xef => 0x64b0, 0xf0 => 0x6813, + 0xf1 => 0x6834, 0xf2 => 0x6cc9, 0xf3 => 0x6d45, 0xf4 => 0x6d17, + 0xf5 => 0x67d3, 0xf6 => 0x6f5c, 0xf7 => 0x714e, 0xf8 => 0x717d, + 0xf9 => 0x65cb, 0xfa => 0x7a7f, 0xfb => 0x7bad, 0xfc => 0x7dda, + }, + 0x91 => { + 0x40 => 0x7e4a, 0x41 => 0x7fa8, 0x42 => 0x817a, 0x43 => 0x821b, + 0x44 => 0x8239, 0x45 => 0x85a6, 0x46 => 0x8a6e, 0x47 => 0x8cce, + 0x48 => 0x8df5, 0x49 => 0x9078, 0x4a => 0x9077, 0x4b => 0x92ad, + 0x4c => 0x9291, 0x4d => 0x9583, 0x4e => 0x9bae, 0x4f => 0x524d, + 0x50 => 0x5584, 0x51 => 0x6f38, 0x52 => 0x7136, 0x53 => 0x5168, + 0x54 => 0x7985, 0x55 => 0x7e55, 0x56 => 0x81b3, 0x57 => 0x7cce, + 0x58 => 0x564c, 0x59 => 0x5851, 0x5a => 0x5ca8, 0x5b => 0x63aa, + 0x5c => 0x66fe, 0x5d => 0x66fd, 0x5e => 0x695a, 0x5f => 0x72d9, + 0x60 => 0x758f, 0x61 => 0x758e, 0x62 => 0x790e, 0x63 => 0x7956, + 0x64 => 0x79df, 0x65 => 0x7c97, 0x66 => 0x7d20, 0x67 => 0x7d44, + 0x68 => 0x8607, 0x69 => 0x8a34, 0x6a => 0x963b, 0x6b => 0x9061, + 0x6c => 0x9f20, 0x6d => 0x50e7, 0x6e => 0x5275, 0x6f => 0x53cc, + 0x70 => 0x53e2, 0x71 => 0x5009, 0x72 => 0x55aa, 0x73 => 0x58ee, + 0x74 => 0x594f, 0x75 => 0x723d, 0x76 => 0x5b8b, 0x77 => 0x5c64, + 0x78 => 0x531d, 0x79 => 0x60e3, 0x7a => 0x60f3, 0x7b => 0x635c, + 0x7c => 0x6383, 0x7d => 0x633f, 0x7e => 0x63bb, 0x80 => 0x64cd, + 0x81 => 0x65e9, 0x82 => 0x66f9, 0x83 => 0x5de3, 0x84 => 0x69cd, + 0x85 => 0x69fd, 0x86 => 0x6f15, 0x87 => 0x71e5, 0x88 => 0x4e89, + 0x89 => 0x75e9, 0x8a => 0x76f8, 0x8b => 0x7a93, 0x8c => 0x7cdf, + 0x8d => 0x7dcf, 0x8e => 0x7d9c, 0x8f => 0x8061, 0x90 => 0x8349, + 0x91 => 0x8358, 0x92 => 0x846c, 0x93 => 0x84bc, 0x94 => 0x85fb, + 0x95 => 0x88c5, 0x96 => 0x8d70, 0x97 => 0x9001, 0x98 => 0x906d, + 0x99 => 0x9397, 0x9a => 0x971c, 0x9b => 0x9a12, 0x9c => 0x50cf, + 0x9d => 0x5897, 0x9e => 0x618e, 0x9f => 0x81d3, 0xa0 => 0x8535, + 0xa1 => 0x8d08, 0xa2 => 0x9020, 0xa3 => 0x4fc3, 0xa4 => 0x5074, + 0xa5 => 0x5247, 0xa6 => 0x5373, 0xa7 => 0x606f, 0xa8 => 0x6349, + 0xa9 => 0x675f, 0xaa => 0x6e2c, 0xab => 0x8db3, 0xac => 0x901f, + 0xad => 0x4fd7, 0xae => 0x5c5e, 0xaf => 0x8cca, 0xb0 => 0x65cf, + 0xb1 => 0x7d9a, 0xb2 => 0x5352, 0xb3 => 0x8896, 0xb4 => 0x5176, + 0xb5 => 0x63c3, 0xb6 => 0x5b58, 0xb7 => 0x5b6b, 0xb8 => 0x5c0a, + 0xb9 => 0x640d, 0xba => 0x6751, 0xbb => 0x905c, 0xbc => 0x4ed6, + 0xbd => 0x591a, 0xbe => 0x592a, 0xbf => 0x6c70, 0xc0 => 0x8a51, + 0xc1 => 0x553e, 0xc2 => 0x5815, 0xc3 => 0x59a5, 0xc4 => 0x60f0, + 0xc5 => 0x6253, 0xc6 => 0x67c1, 0xc7 => 0x8235, 0xc8 => 0x6955, + 0xc9 => 0x9640, 0xca => 0x99c4, 0xcb => 0x9a28, 0xcc => 0x4f53, + 0xcd => 0x5806, 0xce => 0x5bfe, 0xcf => 0x8010, 0xd0 => 0x5cb1, + 0xd1 => 0x5e2f, 0xd2 => 0x5f85, 0xd3 => 0x6020, 0xd4 => 0x614b, + 0xd5 => 0x6234, 0xd6 => 0x66ff, 0xd7 => 0x6cf0, 0xd8 => 0x6ede, + 0xd9 => 0x80ce, 0xda => 0x817f, 0xdb => 0x82d4, 0xdc => 0x888b, + 0xdd => 0x8cb8, 0xde => 0x9000, 0xdf => 0x902e, 0xe0 => 0x968a, + 0xe1 => 0x9edb, 0xe2 => 0x9bdb, 0xe3 => 0x4ee3, 0xe4 => 0x53f0, + 0xe5 => 0x5927, 0xe6 => 0x7b2c, 0xe7 => 0x918d, 0xe8 => 0x984c, + 0xe9 => 0x9df9, 0xea => 0x6edd, 0xeb => 0x7027, 0xec => 0x5353, + 0xed => 0x5544, 0xee => 0x5b85, 0xef => 0x6258, 0xf0 => 0x629e, + 0xf1 => 0x62d3, 0xf2 => 0x6ca2, 0xf3 => 0x6fef, 0xf4 => 0x7422, + 0xf5 => 0x8a17, 0xf6 => 0x9438, 0xf7 => 0x6fc1, 0xf8 => 0x8afe, + 0xf9 => 0x8338, 0xfa => 0x51e7, 0xfb => 0x86f8, 0xfc => 0x53ea, + }, + 0x92 => { + 0x40 => 0x53e9, 0x41 => 0x4f46, 0x42 => 0x9054, 0x43 => 0x8fb0, + 0x44 => 0x596a, 0x45 => 0x8131, 0x46 => 0x5dfd, 0x47 => 0x7aea, + 0x48 => 0x8fbf, 0x49 => 0x68da, 0x4a => 0x8c37, 0x4b => 0x72f8, + 0x4c => 0x9c48, 0x4d => 0x6a3d, 0x4e => 0x8ab0, 0x4f => 0x4e39, + 0x50 => 0x5358, 0x51 => 0x5606, 0x52 => 0x5766, 0x53 => 0x62c5, + 0x54 => 0x63a2, 0x55 => 0x65e6, 0x56 => 0x6b4e, 0x57 => 0x6de1, + 0x58 => 0x6e5b, 0x59 => 0x70ad, 0x5a => 0x77ed, 0x5b => 0x7aef, + 0x5c => 0x7baa, 0x5d => 0x7dbb, 0x5e => 0x803d, 0x5f => 0x80c6, + 0x60 => 0x86cb, 0x61 => 0x8a95, 0x62 => 0x935b, 0x63 => 0x56e3, + 0x64 => 0x58c7, 0x65 => 0x5f3e, 0x66 => 0x65ad, 0x67 => 0x6696, + 0x68 => 0x6a80, 0x69 => 0x6bb5, 0x6a => 0x7537, 0x6b => 0x8ac7, + 0x6c => 0x5024, 0x6d => 0x77e5, 0x6e => 0x5730, 0x6f => 0x5f1b, + 0x70 => 0x6065, 0x71 => 0x667a, 0x72 => 0x6c60, 0x73 => 0x75f4, + 0x74 => 0x7a1a, 0x75 => 0x7f6e, 0x76 => 0x81f4, 0x77 => 0x8718, + 0x78 => 0x9045, 0x79 => 0x99b3, 0x7a => 0x7bc9, 0x7b => 0x755c, + 0x7c => 0x7af9, 0x7d => 0x7b51, 0x7e => 0x84c4, 0x80 => 0x9010, + 0x81 => 0x79e9, 0x82 => 0x7a92, 0x83 => 0x8336, 0x84 => 0x5ae1, + 0x85 => 0x7740, 0x86 => 0x4e2d, 0x87 => 0x4ef2, 0x88 => 0x5b99, + 0x89 => 0x5fe0, 0x8a => 0x62bd, 0x8b => 0x663c, 0x8c => 0x67f1, + 0x8d => 0x6ce8, 0x8e => 0x866b, 0x8f => 0x8877, 0x90 => 0x8a3b, + 0x91 => 0x914e, 0x92 => 0x92f3, 0x93 => 0x99d0, 0x94 => 0x6a17, + 0x95 => 0x7026, 0x96 => 0x732a, 0x97 => 0x82e7, 0x98 => 0x8457, + 0x99 => 0x8caf, 0x9a => 0x4e01, 0x9b => 0x5146, 0x9c => 0x51cb, + 0x9d => 0x558b, 0x9e => 0x5bf5, 0x9f => 0x5e16, 0xa0 => 0x5e33, + 0xa1 => 0x5e81, 0xa2 => 0x5f14, 0xa3 => 0x5f35, 0xa4 => 0x5f6b, + 0xa5 => 0x5fb4, 0xa6 => 0x61f2, 0xa7 => 0x6311, 0xa8 => 0x66a2, + 0xa9 => 0x671d, 0xaa => 0x6f6e, 0xab => 0x7252, 0xac => 0x753a, + 0xad => 0x773a, 0xae => 0x8074, 0xaf => 0x8139, 0xb0 => 0x8178, + 0xb1 => 0x8776, 0xb2 => 0x8abf, 0xb3 => 0x8adc, 0xb4 => 0x8d85, + 0xb5 => 0x8df3, 0xb6 => 0x929a, 0xb7 => 0x9577, 0xb8 => 0x9802, + 0xb9 => 0x9ce5, 0xba => 0x52c5, 0xbb => 0x6357, 0xbc => 0x76f4, + 0xbd => 0x6715, 0xbe => 0x6c88, 0xbf => 0x73cd, 0xc0 => 0x8cc3, + 0xc1 => 0x93ae, 0xc2 => 0x9673, 0xc3 => 0x6d25, 0xc4 => 0x589c, + 0xc5 => 0x690e, 0xc6 => 0x69cc, 0xc7 => 0x8ffd, 0xc8 => 0x939a, + 0xc9 => 0x75db, 0xca => 0x901a, 0xcb => 0x585a, 0xcc => 0x6802, + 0xcd => 0x63b4, 0xce => 0x69fb, 0xcf => 0x4f43, 0xd0 => 0x6f2c, + 0xd1 => 0x67d8, 0xd2 => 0x8fbb, 0xd3 => 0x8526, 0xd4 => 0x7db4, + 0xd5 => 0x9354, 0xd6 => 0x693f, 0xd7 => 0x6f70, 0xd8 => 0x576a, + 0xd9 => 0x58f7, 0xda => 0x5b2c, 0xdb => 0x7d2c, 0xdc => 0x722a, + 0xdd => 0x540a, 0xde => 0x91e3, 0xdf => 0x9db4, 0xe0 => 0x4ead, + 0xe1 => 0x4f4e, 0xe2 => 0x505c, 0xe3 => 0x5075, 0xe4 => 0x5243, + 0xe5 => 0x8c9e, 0xe6 => 0x5448, 0xe7 => 0x5824, 0xe8 => 0x5b9a, + 0xe9 => 0x5e1d, 0xea => 0x5e95, 0xeb => 0x5ead, 0xec => 0x5ef7, + 0xed => 0x5f1f, 0xee => 0x608c, 0xef => 0x62b5, 0xf0 => 0x633a, + 0xf1 => 0x63d0, 0xf2 => 0x68af, 0xf3 => 0x6c40, 0xf4 => 0x7887, + 0xf5 => 0x798e, 0xf6 => 0x7a0b, 0xf7 => 0x7de0, 0xf8 => 0x8247, + 0xf9 => 0x8a02, 0xfa => 0x8ae6, 0xfb => 0x8e44, 0xfc => 0x9013, + }, + 0x93 => { + 0x40 => 0x90b8, 0x41 => 0x912d, 0x42 => 0x91d8, 0x43 => 0x9f0e, + 0x44 => 0x6ce5, 0x45 => 0x6458, 0x46 => 0x64e2, 0x47 => 0x6575, + 0x48 => 0x6ef4, 0x49 => 0x7684, 0x4a => 0x7b1b, 0x4b => 0x9069, + 0x4c => 0x93d1, 0x4d => 0x6eba, 0x4e => 0x54f2, 0x4f => 0x5fb9, + 0x50 => 0x64a4, 0x51 => 0x8f4d, 0x52 => 0x8fed, 0x53 => 0x9244, + 0x54 => 0x5178, 0x55 => 0x586b, 0x56 => 0x5929, 0x57 => 0x5c55, + 0x58 => 0x5e97, 0x59 => 0x6dfb, 0x5a => 0x7e8f, 0x5b => 0x751c, + 0x5c => 0x8cbc, 0x5d => 0x8ee2, 0x5e => 0x985b, 0x5f => 0x70b9, + 0x60 => 0x4f1d, 0x61 => 0x6bbf, 0x62 => 0x6fb1, 0x63 => 0x7530, + 0x64 => 0x96fb, 0x65 => 0x514e, 0x66 => 0x5410, 0x67 => 0x5835, + 0x68 => 0x5857, 0x69 => 0x59ac, 0x6a => 0x5c60, 0x6b => 0x5f92, + 0x6c => 0x6597, 0x6d => 0x675c, 0x6e => 0x6e21, 0x6f => 0x767b, + 0x70 => 0x83df, 0x71 => 0x8ced, 0x72 => 0x9014, 0x73 => 0x90fd, + 0x74 => 0x934d, 0x75 => 0x7825, 0x76 => 0x783a, 0x77 => 0x52aa, + 0x78 => 0x5ea6, 0x79 => 0x571f, 0x7a => 0x5974, 0x7b => 0x6012, + 0x7c => 0x5012, 0x7d => 0x515a, 0x7e => 0x51ac, 0x80 => 0x51cd, + 0x81 => 0x5200, 0x82 => 0x5510, 0x83 => 0x5854, 0x84 => 0x5858, + 0x85 => 0x5957, 0x86 => 0x5b95, 0x87 => 0x5cf6, 0x88 => 0x5d8b, + 0x89 => 0x60bc, 0x8a => 0x6295, 0x8b => 0x642d, 0x8c => 0x6771, + 0x8d => 0x6843, 0x8e => 0x68bc, 0x8f => 0x68df, 0x90 => 0x76d7, + 0x91 => 0x6dd8, 0x92 => 0x6e6f, 0x93 => 0x6d9b, 0x94 => 0x706f, + 0x95 => 0x71c8, 0x96 => 0x5f53, 0x97 => 0x75d8, 0x98 => 0x7977, + 0x99 => 0x7b49, 0x9a => 0x7b54, 0x9b => 0x7b52, 0x9c => 0x7cd6, + 0x9d => 0x7d71, 0x9e => 0x5230, 0x9f => 0x8463, 0xa0 => 0x8569, + 0xa1 => 0x85e4, 0xa2 => 0x8a0e, 0xa3 => 0x8b04, 0xa4 => 0x8c46, + 0xa5 => 0x8e0f, 0xa6 => 0x9003, 0xa7 => 0x900f, 0xa8 => 0x9419, + 0xa9 => 0x9676, 0xaa => 0x982d, 0xab => 0x9a30, 0xac => 0x95d8, + 0xad => 0x50cd, 0xae => 0x52d5, 0xaf => 0x540c, 0xb0 => 0x5802, + 0xb1 => 0x5c0e, 0xb2 => 0x61a7, 0xb3 => 0x649e, 0xb4 => 0x6d1e, + 0xb5 => 0x77b3, 0xb6 => 0x7ae5, 0xb7 => 0x80f4, 0xb8 => 0x8404, + 0xb9 => 0x9053, 0xba => 0x9285, 0xbb => 0x5ce0, 0xbc => 0x9d07, + 0xbd => 0x533f, 0xbe => 0x5f97, 0xbf => 0x5fb3, 0xc0 => 0x6d9c, + 0xc1 => 0x7279, 0xc2 => 0x7763, 0xc3 => 0x79bf, 0xc4 => 0x7be4, + 0xc5 => 0x6bd2, 0xc6 => 0x72ec, 0xc7 => 0x8aad, 0xc8 => 0x6803, + 0xc9 => 0x6a61, 0xca => 0x51f8, 0xcb => 0x7a81, 0xcc => 0x6934, + 0xcd => 0x5c4a, 0xce => 0x9cf6, 0xcf => 0x82eb, 0xd0 => 0x5bc5, + 0xd1 => 0x9149, 0xd2 => 0x701e, 0xd3 => 0x5678, 0xd4 => 0x5c6f, + 0xd5 => 0x60c7, 0xd6 => 0x6566, 0xd7 => 0x6c8c, 0xd8 => 0x8c5a, + 0xd9 => 0x9041, 0xda => 0x9813, 0xdb => 0x5451, 0xdc => 0x66c7, + 0xdd => 0x920d, 0xde => 0x5948, 0xdf => 0x90a3, 0xe0 => 0x5185, + 0xe1 => 0x4e4d, 0xe2 => 0x51ea, 0xe3 => 0x8599, 0xe4 => 0x8b0e, + 0xe5 => 0x7058, 0xe6 => 0x637a, 0xe7 => 0x934b, 0xe8 => 0x6962, + 0xe9 => 0x99b4, 0xea => 0x7e04, 0xeb => 0x7577, 0xec => 0x5357, + 0xed => 0x6960, 0xee => 0x8edf, 0xef => 0x96e3, 0xf0 => 0x6c5d, + 0xf1 => 0x4e8c, 0xf2 => 0x5c3c, 0xf3 => 0x5f10, 0xf4 => 0x8fe9, + 0xf5 => 0x5302, 0xf6 => 0x8cd1, 0xf7 => 0x8089, 0xf8 => 0x8679, + 0xf9 => 0x5eff, 0xfa => 0x65e5, 0xfb => 0x4e73, 0xfc => 0x5165, + }, + 0x94 => { + 0x40 => 0x5982, 0x41 => 0x5c3f, 0x42 => 0x97ee, 0x43 => 0x4efb, + 0x44 => 0x598a, 0x45 => 0x5fcd, 0x46 => 0x8a8d, 0x47 => 0x6fe1, + 0x48 => 0x79b0, 0x49 => 0x7962, 0x4a => 0x5be7, 0x4b => 0x8471, + 0x4c => 0x732b, 0x4d => 0x71b1, 0x4e => 0x5e74, 0x4f => 0x5ff5, + 0x50 => 0x637b, 0x51 => 0x649a, 0x52 => 0x71c3, 0x53 => 0x7c98, + 0x54 => 0x4e43, 0x55 => 0x5efc, 0x56 => 0x4e4b, 0x57 => 0x57dc, + 0x58 => 0x56a2, 0x59 => 0x60a9, 0x5a => 0x6fc3, 0x5b => 0x7d0d, + 0x5c => 0x80fd, 0x5d => 0x8133, 0x5e => 0x81bf, 0x5f => 0x8fb2, + 0x60 => 0x8997, 0x61 => 0x86a4, 0x62 => 0x5df4, 0x63 => 0x628a, + 0x64 => 0x64ad, 0x65 => 0x8987, 0x66 => 0x6777, 0x67 => 0x6ce2, + 0x68 => 0x6d3e, 0x69 => 0x7436, 0x6a => 0x7834, 0x6b => 0x5a46, + 0x6c => 0x7f75, 0x6d => 0x82ad, 0x6e => 0x99ac, 0x6f => 0x4ff3, + 0x70 => 0x5ec3, 0x71 => 0x62dd, 0x72 => 0x6392, 0x73 => 0x6557, + 0x74 => 0x676f, 0x75 => 0x76c3, 0x76 => 0x724c, 0x77 => 0x80cc, + 0x78 => 0x80ba, 0x79 => 0x8f29, 0x7a => 0x914d, 0x7b => 0x500d, + 0x7c => 0x57f9, 0x7d => 0x5a92, 0x7e => 0x6885, 0x80 => 0x6973, + 0x81 => 0x7164, 0x82 => 0x72fd, 0x83 => 0x8cb7, 0x84 => 0x58f2, + 0x85 => 0x8ce0, 0x86 => 0x966a, 0x87 => 0x9019, 0x88 => 0x877f, + 0x89 => 0x79e4, 0x8a => 0x77e7, 0x8b => 0x8429, 0x8c => 0x4f2f, + 0x8d => 0x5265, 0x8e => 0x535a, 0x8f => 0x62cd, 0x90 => 0x67cf, + 0x91 => 0x6cca, 0x92 => 0x767d, 0x93 => 0x7b94, 0x94 => 0x7c95, + 0x95 => 0x8236, 0x96 => 0x8584, 0x97 => 0x8feb, 0x98 => 0x66dd, + 0x99 => 0x6f20, 0x9a => 0x7206, 0x9b => 0x7e1b, 0x9c => 0x83ab, + 0x9d => 0x99c1, 0x9e => 0x9ea6, 0x9f => 0x51fd, 0xa0 => 0x7bb1, + 0xa1 => 0x7872, 0xa2 => 0x7bb8, 0xa3 => 0x8087, 0xa4 => 0x7b48, + 0xa5 => 0x6ae8, 0xa6 => 0x5e61, 0xa7 => 0x808c, 0xa8 => 0x7551, + 0xa9 => 0x7560, 0xaa => 0x516b, 0xab => 0x9262, 0xac => 0x6e8c, + 0xad => 0x767a, 0xae => 0x9197, 0xaf => 0x9aea, 0xb0 => 0x4f10, + 0xb1 => 0x7f70, 0xb2 => 0x629c, 0xb3 => 0x7b4f, 0xb4 => 0x95a5, + 0xb5 => 0x9ce9, 0xb6 => 0x567a, 0xb7 => 0x5859, 0xb8 => 0x86e4, + 0xb9 => 0x96bc, 0xba => 0x4f34, 0xbb => 0x5224, 0xbc => 0x534a, + 0xbd => 0x53cd, 0xbe => 0x53db, 0xbf => 0x5e06, 0xc0 => 0x642c, + 0xc1 => 0x6591, 0xc2 => 0x677f, 0xc3 => 0x6c3e, 0xc4 => 0x6c4e, + 0xc5 => 0x7248, 0xc6 => 0x72af, 0xc7 => 0x73ed, 0xc8 => 0x7554, + 0xc9 => 0x7e41, 0xca => 0x822c, 0xcb => 0x85e9, 0xcc => 0x8ca9, + 0xcd => 0x7bc4, 0xce => 0x91c6, 0xcf => 0x7169, 0xd0 => 0x9812, + 0xd1 => 0x98ef, 0xd2 => 0x633d, 0xd3 => 0x6669, 0xd4 => 0x756a, + 0xd5 => 0x76e4, 0xd6 => 0x78d0, 0xd7 => 0x8543, 0xd8 => 0x86ee, + 0xd9 => 0x532a, 0xda => 0x5351, 0xdb => 0x5426, 0xdc => 0x5983, + 0xdd => 0x5e87, 0xde => 0x5f7c, 0xdf => 0x60b2, 0xe0 => 0x6249, + 0xe1 => 0x6279, 0xe2 => 0x62ab, 0xe3 => 0x6590, 0xe4 => 0x6bd4, + 0xe5 => 0x6ccc, 0xe6 => 0x75b2, 0xe7 => 0x76ae, 0xe8 => 0x7891, + 0xe9 => 0x79d8, 0xea => 0x7dcb, 0xeb => 0x7f77, 0xec => 0x80a5, + 0xed => 0x88ab, 0xee => 0x8ab9, 0xef => 0x8cbb, 0xf0 => 0x907f, + 0xf1 => 0x975e, 0xf2 => 0x98db, 0xf3 => 0x6a0b, 0xf4 => 0x7c38, + 0xf5 => 0x5099, 0xf6 => 0x5c3e, 0xf7 => 0x5fae, 0xf8 => 0x6787, + 0xf9 => 0x6bd8, 0xfa => 0x7435, 0xfb => 0x7709, 0xfc => 0x7f8e, + }, + 0x95 => { + 0x40 => 0x9f3b, 0x41 => 0x67ca, 0x42 => 0x7a17, 0x43 => 0x5339, + 0x44 => 0x758b, 0x45 => 0x9aed, 0x46 => 0x5f66, 0x47 => 0x819d, + 0x48 => 0x83f1, 0x49 => 0x8098, 0x4a => 0x5f3c, 0x4b => 0x5fc5, + 0x4c => 0x7562, 0x4d => 0x7b46, 0x4e => 0x903c, 0x4f => 0x6867, + 0x50 => 0x59eb, 0x51 => 0x5a9b, 0x52 => 0x7d10, 0x53 => 0x767e, + 0x54 => 0x8b2c, 0x55 => 0x4ff5, 0x56 => 0x5f6a, 0x57 => 0x6a19, + 0x58 => 0x6c37, 0x59 => 0x6f02, 0x5a => 0x74e2, 0x5b => 0x7968, + 0x5c => 0x8868, 0x5d => 0x8a55, 0x5e => 0x8c79, 0x5f => 0x5edf, + 0x60 => 0x63cf, 0x61 => 0x75c5, 0x62 => 0x79d2, 0x63 => 0x82d7, + 0x64 => 0x9328, 0x65 => 0x92f2, 0x66 => 0x849c, 0x67 => 0x86ed, + 0x68 => 0x9c2d, 0x69 => 0x54c1, 0x6a => 0x5f6c, 0x6b => 0x658c, + 0x6c => 0x6d5c, 0x6d => 0x7015, 0x6e => 0x8ca7, 0x6f => 0x8cd3, + 0x70 => 0x983b, 0x71 => 0x654f, 0x72 => 0x74f6, 0x73 => 0x4e0d, + 0x74 => 0x4ed8, 0x75 => 0x57e0, 0x76 => 0x592b, 0x77 => 0x5a66, + 0x78 => 0x5bcc, 0x79 => 0x51a8, 0x7a => 0x5e03, 0x7b => 0x5e9c, + 0x7c => 0x6016, 0x7d => 0x6276, 0x7e => 0x6577, 0x80 => 0x65a7, + 0x81 => 0x666e, 0x82 => 0x6d6e, 0x83 => 0x7236, 0x84 => 0x7b26, + 0x85 => 0x8150, 0x86 => 0x819a, 0x87 => 0x8299, 0x88 => 0x8b5c, + 0x89 => 0x8ca0, 0x8a => 0x8ce6, 0x8b => 0x8d74, 0x8c => 0x961c, + 0x8d => 0x9644, 0x8e => 0x4fae, 0x8f => 0x64ab, 0x90 => 0x6b66, + 0x91 => 0x821e, 0x92 => 0x8461, 0x93 => 0x856a, 0x94 => 0x90e8, + 0x95 => 0x5c01, 0x96 => 0x6953, 0x97 => 0x98a8, 0x98 => 0x847a, + 0x99 => 0x8557, 0x9a => 0x4f0f, 0x9b => 0x526f, 0x9c => 0x5fa9, + 0x9d => 0x5e45, 0x9e => 0x670d, 0x9f => 0x798f, 0xa0 => 0x8179, + 0xa1 => 0x8907, 0xa2 => 0x8986, 0xa3 => 0x6df5, 0xa4 => 0x5f17, + 0xa5 => 0x6255, 0xa6 => 0x6cb8, 0xa7 => 0x4ecf, 0xa8 => 0x7269, + 0xa9 => 0x9b92, 0xaa => 0x5206, 0xab => 0x543b, 0xac => 0x5674, + 0xad => 0x58b3, 0xae => 0x61a4, 0xaf => 0x626e, 0xb0 => 0x711a, + 0xb1 => 0x596e, 0xb2 => 0x7c89, 0xb3 => 0x7cde, 0xb4 => 0x7d1b, + 0xb5 => 0x96f0, 0xb6 => 0x6587, 0xb7 => 0x805e, 0xb8 => 0x4e19, + 0xb9 => 0x4f75, 0xba => 0x5175, 0xbb => 0x5840, 0xbc => 0x5e63, + 0xbd => 0x5e73, 0xbe => 0x5f0a, 0xbf => 0x67c4, 0xc0 => 0x4e26, + 0xc1 => 0x853d, 0xc2 => 0x9589, 0xc3 => 0x965b, 0xc4 => 0x7c73, + 0xc5 => 0x9801, 0xc6 => 0x50fb, 0xc7 => 0x58c1, 0xc8 => 0x7656, + 0xc9 => 0x78a7, 0xca => 0x5225, 0xcb => 0x77a5, 0xcc => 0x8511, + 0xcd => 0x7b86, 0xce => 0x504f, 0xcf => 0x5909, 0xd0 => 0x7247, + 0xd1 => 0x7bc7, 0xd2 => 0x7de8, 0xd3 => 0x8fba, 0xd4 => 0x8fd4, + 0xd5 => 0x904d, 0xd6 => 0x4fbf, 0xd7 => 0x52c9, 0xd8 => 0x5a29, + 0xd9 => 0x5f01, 0xda => 0x97ad, 0xdb => 0x4fdd, 0xdc => 0x8217, + 0xdd => 0x92ea, 0xde => 0x5703, 0xdf => 0x6355, 0xe0 => 0x6b69, + 0xe1 => 0x752b, 0xe2 => 0x88dc, 0xe3 => 0x8f14, 0xe4 => 0x7a42, + 0xe5 => 0x52df, 0xe6 => 0x5893, 0xe7 => 0x6155, 0xe8 => 0x620a, + 0xe9 => 0x66ae, 0xea => 0x6bcd, 0xeb => 0x7c3f, 0xec => 0x83e9, + 0xed => 0x5023, 0xee => 0x4ff8, 0xef => 0x5305, 0xf0 => 0x5446, + 0xf1 => 0x5831, 0xf2 => 0x5949, 0xf3 => 0x5b9d, 0xf4 => 0x5cf0, + 0xf5 => 0x5cef, 0xf6 => 0x5d29, 0xf7 => 0x5e96, 0xf8 => 0x62b1, + 0xf9 => 0x6367, 0xfa => 0x653e, 0xfb => 0x65b9, 0xfc => 0x670b, + }, + 0x96 => { + 0x40 => 0x6cd5, 0x41 => 0x6ce1, 0x42 => 0x70f9, 0x43 => 0x7832, + 0x44 => 0x7e2b, 0x45 => 0x80de, 0x46 => 0x82b3, 0x47 => 0x840c, + 0x48 => 0x84ec, 0x49 => 0x8702, 0x4a => 0x8912, 0x4b => 0x8a2a, + 0x4c => 0x8c4a, 0x4d => 0x90a6, 0x4e => 0x92d2, 0x4f => 0x98fd, + 0x50 => 0x9cf3, 0x51 => 0x9d6c, 0x52 => 0x4e4f, 0x53 => 0x4ea1, + 0x54 => 0x508d, 0x55 => 0x5256, 0x56 => 0x574a, 0x57 => 0x59a8, + 0x58 => 0x5e3d, 0x59 => 0x5fd8, 0x5a => 0x5fd9, 0x5b => 0x623f, + 0x5c => 0x66b4, 0x5d => 0x671b, 0x5e => 0x67d0, 0x5f => 0x68d2, + 0x60 => 0x5192, 0x61 => 0x7d21, 0x62 => 0x80aa, 0x63 => 0x81a8, + 0x64 => 0x8b00, 0x65 => 0x8c8c, 0x66 => 0x8cbf, 0x67 => 0x927e, + 0x68 => 0x9632, 0x69 => 0x5420, 0x6a => 0x982c, 0x6b => 0x5317, + 0x6c => 0x50d5, 0x6d => 0x535c, 0x6e => 0x58a8, 0x6f => 0x64b2, + 0x70 => 0x6734, 0x71 => 0x7267, 0x72 => 0x7766, 0x73 => 0x7a46, + 0x74 => 0x91e6, 0x75 => 0x52c3, 0x76 => 0x6ca1, 0x77 => 0x6b86, + 0x78 => 0x5800, 0x79 => 0x5e4c, 0x7a => 0x5954, 0x7b => 0x672c, + 0x7c => 0x7ffb, 0x7d => 0x51e1, 0x7e => 0x76c6, 0x80 => 0x6469, + 0x81 => 0x78e8, 0x82 => 0x9b54, 0x83 => 0x9ebb, 0x84 => 0x57cb, + 0x85 => 0x59b9, 0x86 => 0x6627, 0x87 => 0x679a, 0x88 => 0x6bce, + 0x89 => 0x54e9, 0x8a => 0x69d9, 0x8b => 0x5e55, 0x8c => 0x819c, + 0x8d => 0x6795, 0x8e => 0x9baa, 0x8f => 0x67fe, 0x90 => 0x9c52, + 0x91 => 0x685d, 0x92 => 0x4ea6, 0x93 => 0x4fe3, 0x94 => 0x53c8, + 0x95 => 0x62b9, 0x96 => 0x672b, 0x97 => 0x6cab, 0x98 => 0x8fc4, + 0x99 => 0x4fad, 0x9a => 0x7e6d, 0x9b => 0x9ebf, 0x9c => 0x4e07, + 0x9d => 0x6162, 0x9e => 0x6e80, 0x9f => 0x6f2b, 0xa0 => 0x8513, + 0xa1 => 0x5473, 0xa2 => 0x672a, 0xa3 => 0x9b45, 0xa4 => 0x5df3, + 0xa5 => 0x7b95, 0xa6 => 0x5cac, 0xa7 => 0x5bc6, 0xa8 => 0x871c, + 0xa9 => 0x6e4a, 0xaa => 0x84d1, 0xab => 0x7a14, 0xac => 0x8108, + 0xad => 0x5999, 0xae => 0x7c8d, 0xaf => 0x6c11, 0xb0 => 0x7720, + 0xb1 => 0x52d9, 0xb2 => 0x5922, 0xb3 => 0x7121, 0xb4 => 0x725f, + 0xb5 => 0x77db, 0xb6 => 0x9727, 0xb7 => 0x9d61, 0xb8 => 0x690b, + 0xb9 => 0x5a7f, 0xba => 0x5a18, 0xbb => 0x51a5, 0xbc => 0x540d, + 0xbd => 0x547d, 0xbe => 0x660e, 0xbf => 0x76df, 0xc0 => 0x8ff7, + 0xc1 => 0x9298, 0xc2 => 0x9cf4, 0xc3 => 0x59ea, 0xc4 => 0x725d, + 0xc5 => 0x6ec5, 0xc6 => 0x514d, 0xc7 => 0x68c9, 0xc8 => 0x7dbf, + 0xc9 => 0x7dec, 0xca => 0x9762, 0xcb => 0x9eba, 0xcc => 0x6478, + 0xcd => 0x6a21, 0xce => 0x8302, 0xcf => 0x5984, 0xd0 => 0x5b5f, + 0xd1 => 0x6bdb, 0xd2 => 0x731b, 0xd3 => 0x76f2, 0xd4 => 0x7db2, + 0xd5 => 0x8017, 0xd6 => 0x8499, 0xd7 => 0x5132, 0xd8 => 0x6728, + 0xd9 => 0x9ed9, 0xda => 0x76ee, 0xdb => 0x6762, 0xdc => 0x52ff, + 0xdd => 0x9905, 0xde => 0x5c24, 0xdf => 0x623b, 0xe0 => 0x7c7e, + 0xe1 => 0x8cb0, 0xe2 => 0x554f, 0xe3 => 0x60b6, 0xe4 => 0x7d0b, + 0xe5 => 0x9580, 0xe6 => 0x5301, 0xe7 => 0x4e5f, 0xe8 => 0x51b6, + 0xe9 => 0x591c, 0xea => 0x723a, 0xeb => 0x8036, 0xec => 0x91ce, + 0xed => 0x5f25, 0xee => 0x77e2, 0xef => 0x5384, 0xf0 => 0x5f79, + 0xf1 => 0x7d04, 0xf2 => 0x85ac, 0xf3 => 0x8a33, 0xf4 => 0x8e8d, + 0xf5 => 0x9756, 0xf6 => 0x67f3, 0xf7 => 0x85ae, 0xf8 => 0x9453, + 0xf9 => 0x6109, 0xfa => 0x6108, 0xfb => 0x6cb9, 0xfc => 0x7652, + }, + 0x97 => { + 0x40 => 0x8aed, 0x41 => 0x8f38, 0x42 => 0x552f, 0x43 => 0x4f51, + 0x44 => 0x512a, 0x45 => 0x52c7, 0x46 => 0x53cb, 0x47 => 0x5ba5, + 0x48 => 0x5e7d, 0x49 => 0x60a0, 0x4a => 0x6182, 0x4b => 0x63d6, + 0x4c => 0x6709, 0x4d => 0x67da, 0x4e => 0x6e67, 0x4f => 0x6d8c, + 0x50 => 0x7336, 0x51 => 0x7337, 0x52 => 0x7531, 0x53 => 0x7950, + 0x54 => 0x88d5, 0x55 => 0x8a98, 0x56 => 0x904a, 0x57 => 0x9091, + 0x58 => 0x90f5, 0x59 => 0x96c4, 0x5a => 0x878d, 0x5b => 0x5915, + 0x5c => 0x4e88, 0x5d => 0x4f59, 0x5e => 0x4e0e, 0x5f => 0x8a89, + 0x60 => 0x8f3f, 0x61 => 0x9810, 0x62 => 0x50ad, 0x63 => 0x5e7c, + 0x64 => 0x5996, 0x65 => 0x5bb9, 0x66 => 0x5eb8, 0x67 => 0x63da, + 0x68 => 0x63fa, 0x69 => 0x64c1, 0x6a => 0x66dc, 0x6b => 0x694a, + 0x6c => 0x69d8, 0x6d => 0x6d0b, 0x6e => 0x6eb6, 0x6f => 0x7194, + 0x70 => 0x7528, 0x71 => 0x7aaf, 0x72 => 0x7f8a, 0x73 => 0x8000, + 0x74 => 0x8449, 0x75 => 0x84c9, 0x76 => 0x8981, 0x77 => 0x8b21, + 0x78 => 0x8e0a, 0x79 => 0x9065, 0x7a => 0x967d, 0x7b => 0x990a, + 0x7c => 0x617e, 0x7d => 0x6291, 0x7e => 0x6b32, 0x80 => 0x6c83, + 0x81 => 0x6d74, 0x82 => 0x7fcc, 0x83 => 0x7ffc, 0x84 => 0x6dc0, + 0x85 => 0x7f85, 0x86 => 0x87ba, 0x87 => 0x88f8, 0x88 => 0x6765, + 0x89 => 0x83b1, 0x8a => 0x983c, 0x8b => 0x96f7, 0x8c => 0x6d1b, + 0x8d => 0x7d61, 0x8e => 0x843d, 0x8f => 0x916a, 0x90 => 0x4e71, + 0x91 => 0x5375, 0x92 => 0x5d50, 0x93 => 0x6b04, 0x94 => 0x6feb, + 0x95 => 0x85cd, 0x96 => 0x862d, 0x97 => 0x89a7, 0x98 => 0x5229, + 0x99 => 0x540f, 0x9a => 0x5c65, 0x9b => 0x674e, 0x9c => 0x68a8, + 0x9d => 0x7406, 0x9e => 0x7483, 0x9f => 0x75e2, 0xa0 => 0x88cf, + 0xa1 => 0x88e1, 0xa2 => 0x91cc, 0xa3 => 0x96e2, 0xa4 => 0x9678, + 0xa5 => 0x5f8b, 0xa6 => 0x7387, 0xa7 => 0x7acb, 0xa8 => 0x844e, + 0xa9 => 0x63a0, 0xaa => 0x7565, 0xab => 0x5289, 0xac => 0x6d41, + 0xad => 0x6e9c, 0xae => 0x7409, 0xaf => 0x7559, 0xb0 => 0x786b, + 0xb1 => 0x7c92, 0xb2 => 0x9686, 0xb3 => 0x7adc, 0xb4 => 0x9f8d, + 0xb5 => 0x4fb6, 0xb6 => 0x616e, 0xb7 => 0x65c5, 0xb8 => 0x865c, + 0xb9 => 0x4e86, 0xba => 0x4eae, 0xbb => 0x50da, 0xbc => 0x4e21, + 0xbd => 0x51cc, 0xbe => 0x5bee, 0xbf => 0x6599, 0xc0 => 0x6881, + 0xc1 => 0x6dbc, 0xc2 => 0x731f, 0xc3 => 0x7642, 0xc4 => 0x77ad, + 0xc5 => 0x7a1c, 0xc6 => 0x7ce7, 0xc7 => 0x826f, 0xc8 => 0x8ad2, + 0xc9 => 0x907c, 0xca => 0x91cf, 0xcb => 0x9675, 0xcc => 0x9818, + 0xcd => 0x529b, 0xce => 0x7dd1, 0xcf => 0x502b, 0xd0 => 0x5398, + 0xd1 => 0x6797, 0xd2 => 0x6dcb, 0xd3 => 0x71d0, 0xd4 => 0x7433, + 0xd5 => 0x81e8, 0xd6 => 0x8f2a, 0xd7 => 0x96a3, 0xd8 => 0x9c57, + 0xd9 => 0x9e9f, 0xda => 0x7460, 0xdb => 0x5841, 0xdc => 0x6d99, + 0xdd => 0x7d2f, 0xde => 0x985e, 0xdf => 0x4ee4, 0xe0 => 0x4f36, + 0xe1 => 0x4f8b, 0xe2 => 0x51b7, 0xe3 => 0x52b1, 0xe4 => 0x5dba, + 0xe5 => 0x601c, 0xe6 => 0x73b2, 0xe7 => 0x793c, 0xe8 => 0x82d3, + 0xe9 => 0x9234, 0xea => 0x96b7, 0xeb => 0x96f6, 0xec => 0x970a, + 0xed => 0x9e97, 0xee => 0x9f62, 0xef => 0x66a6, 0xf0 => 0x6b74, + 0xf1 => 0x5217, 0xf2 => 0x52a3, 0xf3 => 0x70c8, 0xf4 => 0x88c2, + 0xf5 => 0x5ec9, 0xf6 => 0x604b, 0xf7 => 0x6190, 0xf8 => 0x6f23, + 0xf9 => 0x7149, 0xfa => 0x7c3e, 0xfb => 0x7df4, 0xfc => 0x806f, + }, + 0x98 => { + 0x40 => 0x84ee, 0x41 => 0x9023, 0x42 => 0x932c, 0x43 => 0x5442, + 0x44 => 0x9b6f, 0x45 => 0x6ad3, 0x46 => 0x7089, 0x47 => 0x8cc2, + 0x48 => 0x8def, 0x49 => 0x9732, 0x4a => 0x52b4, 0x4b => 0x5a41, + 0x4c => 0x5eca, 0x4d => 0x5f04, 0x4e => 0x6717, 0x4f => 0x697c, + 0x50 => 0x6994, 0x51 => 0x6d6a, 0x52 => 0x6f0f, 0x53 => 0x7262, + 0x54 => 0x72fc, 0x55 => 0x7bed, 0x56 => 0x8001, 0x57 => 0x807e, + 0x58 => 0x874b, 0x59 => 0x90ce, 0x5a => 0x516d, 0x5b => 0x9e93, + 0x5c => 0x7984, 0x5d => 0x808b, 0x5e => 0x9332, 0x5f => 0x8ad6, + 0x60 => 0x502d, 0x61 => 0x548c, 0x62 => 0x8a71, 0x63 => 0x6b6a, + 0x64 => 0x8cc4, 0x65 => 0x8107, 0x66 => 0x60d1, 0x67 => 0x67a0, + 0x68 => 0x9df2, 0x69 => 0x4e99, 0x6a => 0x4e98, 0x6b => 0x9c10, + 0x6c => 0x8a6b, 0x6d => 0x85c1, 0x6e => 0x8568, 0x6f => 0x6900, + 0x70 => 0x6e7e, 0x71 => 0x7897, 0x72 => 0x8155, 0x9f => 0x5f0c, + 0xa0 => 0x4e10, 0xa1 => 0x4e15, 0xa2 => 0x4e2a, 0xa3 => 0x4e31, + 0xa4 => 0x4e36, 0xa5 => 0x4e3c, 0xa6 => 0x4e3f, 0xa7 => 0x4e42, + 0xa8 => 0x4e56, 0xa9 => 0x4e58, 0xaa => 0x4e82, 0xab => 0x4e85, + 0xac => 0x8c6b, 0xad => 0x4e8a, 0xae => 0x8212, 0xaf => 0x5f0d, + 0xb0 => 0x4e8e, 0xb1 => 0x4e9e, 0xb2 => 0x4e9f, 0xb3 => 0x4ea0, + 0xb4 => 0x4ea2, 0xb5 => 0x4eb0, 0xb6 => 0x4eb3, 0xb7 => 0x4eb6, + 0xb8 => 0x4ece, 0xb9 => 0x4ecd, 0xba => 0x4ec4, 0xbb => 0x4ec6, + 0xbc => 0x4ec2, 0xbd => 0x4ed7, 0xbe => 0x4ede, 0xbf => 0x4eed, + 0xc0 => 0x4edf, 0xc1 => 0x4ef7, 0xc2 => 0x4f09, 0xc3 => 0x4f5a, + 0xc4 => 0x4f30, 0xc5 => 0x4f5b, 0xc6 => 0x4f5d, 0xc7 => 0x4f57, + 0xc8 => 0x4f47, 0xc9 => 0x4f76, 0xca => 0x4f88, 0xcb => 0x4f8f, + 0xcc => 0x4f98, 0xcd => 0x4f7b, 0xce => 0x4f69, 0xcf => 0x4f70, + 0xd0 => 0x4f91, 0xd1 => 0x4f6f, 0xd2 => 0x4f86, 0xd3 => 0x4f96, + 0xd4 => 0x5118, 0xd5 => 0x4fd4, 0xd6 => 0x4fdf, 0xd7 => 0x4fce, + 0xd8 => 0x4fd8, 0xd9 => 0x4fdb, 0xda => 0x4fd1, 0xdb => 0x4fda, + 0xdc => 0x4fd0, 0xdd => 0x4fe4, 0xde => 0x4fe5, 0xdf => 0x501a, + 0xe0 => 0x5028, 0xe1 => 0x5014, 0xe2 => 0x502a, 0xe3 => 0x5025, + 0xe4 => 0x5005, 0xe5 => 0x4f1c, 0xe6 => 0x4ff6, 0xe7 => 0x5021, + 0xe8 => 0x5029, 0xe9 => 0x502c, 0xea => 0x4ffe, 0xeb => 0x4fef, + 0xec => 0x5011, 0xed => 0x5006, 0xee => 0x5043, 0xef => 0x5047, + 0xf0 => 0x6703, 0xf1 => 0x5055, 0xf2 => 0x5050, 0xf3 => 0x5048, + 0xf4 => 0x505a, 0xf5 => 0x5056, 0xf6 => 0x506c, 0xf7 => 0x5078, + 0xf8 => 0x5080, 0xf9 => 0x509a, 0xfa => 0x5085, 0xfb => 0x50b4, + 0xfc => 0x50b2, + }, + 0x99 => { + 0x40 => 0x50c9, 0x41 => 0x50ca, 0x42 => 0x50b3, 0x43 => 0x50c2, + 0x44 => 0x50d6, 0x45 => 0x50de, 0x46 => 0x50e5, 0x47 => 0x50ed, + 0x48 => 0x50e3, 0x49 => 0x50ee, 0x4a => 0x50f9, 0x4b => 0x50f5, + 0x4c => 0x5109, 0x4d => 0x5101, 0x4e => 0x5102, 0x4f => 0x5116, + 0x50 => 0x5115, 0x51 => 0x5114, 0x52 => 0x511a, 0x53 => 0x5121, + 0x54 => 0x513a, 0x55 => 0x5137, 0x56 => 0x513c, 0x57 => 0x513b, + 0x58 => 0x513f, 0x59 => 0x5140, 0x5a => 0x5152, 0x5b => 0x514c, + 0x5c => 0x5154, 0x5d => 0x5162, 0x5e => 0x7af8, 0x5f => 0x5169, + 0x60 => 0x516a, 0x61 => 0x516e, 0x62 => 0x5180, 0x63 => 0x5182, + 0x64 => 0x56d8, 0x65 => 0x518c, 0x66 => 0x5189, 0x67 => 0x518f, + 0x68 => 0x5191, 0x69 => 0x5193, 0x6a => 0x5195, 0x6b => 0x5196, + 0x6c => 0x51a4, 0x6d => 0x51a6, 0x6e => 0x51a2, 0x6f => 0x51a9, + 0x70 => 0x51aa, 0x71 => 0x51ab, 0x72 => 0x51b3, 0x73 => 0x51b1, + 0x74 => 0x51b2, 0x75 => 0x51b0, 0x76 => 0x51b5, 0x77 => 0x51bd, + 0x78 => 0x51c5, 0x79 => 0x51c9, 0x7a => 0x51db, 0x7b => 0x51e0, + 0x7c => 0x8655, 0x7d => 0x51e9, 0x7e => 0x51ed, 0x80 => 0x51f0, + 0x81 => 0x51f5, 0x82 => 0x51fe, 0x83 => 0x5204, 0x84 => 0x520b, + 0x85 => 0x5214, 0x86 => 0x520e, 0x87 => 0x5227, 0x88 => 0x522a, + 0x89 => 0x522e, 0x8a => 0x5233, 0x8b => 0x5239, 0x8c => 0x524f, + 0x8d => 0x5244, 0x8e => 0x524b, 0x8f => 0x524c, 0x90 => 0x525e, + 0x91 => 0x5254, 0x92 => 0x526a, 0x93 => 0x5274, 0x94 => 0x5269, + 0x95 => 0x5273, 0x96 => 0x527f, 0x97 => 0x527d, 0x98 => 0x528d, + 0x99 => 0x5294, 0x9a => 0x5292, 0x9b => 0x5271, 0x9c => 0x5288, + 0x9d => 0x5291, 0x9e => 0x8fa8, 0x9f => 0x8fa7, 0xa0 => 0x52ac, + 0xa1 => 0x52ad, 0xa2 => 0x52bc, 0xa3 => 0x52b5, 0xa4 => 0x52c1, + 0xa5 => 0x52cd, 0xa6 => 0x52d7, 0xa7 => 0x52de, 0xa8 => 0x52e3, + 0xa9 => 0x52e6, 0xaa => 0x98ed, 0xab => 0x52e0, 0xac => 0x52f3, + 0xad => 0x52f5, 0xae => 0x52f8, 0xaf => 0x52f9, 0xb0 => 0x5306, + 0xb1 => 0x5308, 0xb2 => 0x7538, 0xb3 => 0x530d, 0xb4 => 0x5310, + 0xb5 => 0x530f, 0xb6 => 0x5315, 0xb7 => 0x531a, 0xb8 => 0x5323, + 0xb9 => 0x532f, 0xba => 0x5331, 0xbb => 0x5333, 0xbc => 0x5338, + 0xbd => 0x5340, 0xbe => 0x5346, 0xbf => 0x5345, 0xc0 => 0x4e17, + 0xc1 => 0x5349, 0xc2 => 0x534d, 0xc3 => 0x51d6, 0xc4 => 0x535e, + 0xc5 => 0x5369, 0xc6 => 0x536e, 0xc7 => 0x5918, 0xc8 => 0x537b, + 0xc9 => 0x5377, 0xca => 0x5382, 0xcb => 0x5396, 0xcc => 0x53a0, + 0xcd => 0x53a6, 0xce => 0x53a5, 0xcf => 0x53ae, 0xd0 => 0x53b0, + 0xd1 => 0x53b6, 0xd2 => 0x53c3, 0xd3 => 0x7c12, 0xd4 => 0x96d9, + 0xd5 => 0x53df, 0xd6 => 0x66fc, 0xd7 => 0x71ee, 0xd8 => 0x53ee, + 0xd9 => 0x53e8, 0xda => 0x53ed, 0xdb => 0x53fa, 0xdc => 0x5401, + 0xdd => 0x543d, 0xde => 0x5440, 0xdf => 0x542c, 0xe0 => 0x542d, + 0xe1 => 0x543c, 0xe2 => 0x542e, 0xe3 => 0x5436, 0xe4 => 0x5429, + 0xe5 => 0x541d, 0xe6 => 0x544e, 0xe7 => 0x548f, 0xe8 => 0x5475, + 0xe9 => 0x548e, 0xea => 0x545f, 0xeb => 0x5471, 0xec => 0x5477, + 0xed => 0x5470, 0xee => 0x5492, 0xef => 0x547b, 0xf0 => 0x5480, + 0xf1 => 0x5476, 0xf2 => 0x5484, 0xf3 => 0x5490, 0xf4 => 0x5486, + 0xf5 => 0x54c7, 0xf6 => 0x54a2, 0xf7 => 0x54b8, 0xf8 => 0x54a5, + 0xf9 => 0x54ac, 0xfa => 0x54c4, 0xfb => 0x54c8, 0xfc => 0x54a8, + }, + 0x9a => { + 0x40 => 0x54ab, 0x41 => 0x54c2, 0x42 => 0x54a4, 0x43 => 0x54be, + 0x44 => 0x54bc, 0x45 => 0x54d8, 0x46 => 0x54e5, 0x47 => 0x54e6, + 0x48 => 0x550f, 0x49 => 0x5514, 0x4a => 0x54fd, 0x4b => 0x54ee, + 0x4c => 0x54ed, 0x4d => 0x54fa, 0x4e => 0x54e2, 0x4f => 0x5539, + 0x50 => 0x5540, 0x51 => 0x5563, 0x52 => 0x554c, 0x53 => 0x552e, + 0x54 => 0x555c, 0x55 => 0x5545, 0x56 => 0x5556, 0x57 => 0x5557, + 0x58 => 0x5538, 0x59 => 0x5533, 0x5a => 0x555d, 0x5b => 0x5599, + 0x5c => 0x5580, 0x5d => 0x54af, 0x5e => 0x558a, 0x5f => 0x559f, + 0x60 => 0x557b, 0x61 => 0x557e, 0x62 => 0x5598, 0x63 => 0x559e, + 0x64 => 0x55ae, 0x65 => 0x557c, 0x66 => 0x5583, 0x67 => 0x55a9, + 0x68 => 0x5587, 0x69 => 0x55a8, 0x6a => 0x55da, 0x6b => 0x55c5, + 0x6c => 0x55df, 0x6d => 0x55c4, 0x6e => 0x55dc, 0x6f => 0x55e4, + 0x70 => 0x55d4, 0x71 => 0x5614, 0x72 => 0x55f7, 0x73 => 0x5616, + 0x74 => 0x55fe, 0x75 => 0x55fd, 0x76 => 0x561b, 0x77 => 0x55f9, + 0x78 => 0x564e, 0x79 => 0x5650, 0x7a => 0x71df, 0x7b => 0x5634, + 0x7c => 0x5636, 0x7d => 0x5632, 0x7e => 0x5638, 0x80 => 0x566b, + 0x81 => 0x5664, 0x82 => 0x562f, 0x83 => 0x566c, 0x84 => 0x566a, + 0x85 => 0x5686, 0x86 => 0x5680, 0x87 => 0x568a, 0x88 => 0x56a0, + 0x89 => 0x5694, 0x8a => 0x568f, 0x8b => 0x56a5, 0x8c => 0x56ae, + 0x8d => 0x56b6, 0x8e => 0x56b4, 0x8f => 0x56c2, 0x90 => 0x56bc, + 0x91 => 0x56c1, 0x92 => 0x56c3, 0x93 => 0x56c0, 0x94 => 0x56c8, + 0x95 => 0x56ce, 0x96 => 0x56d1, 0x97 => 0x56d3, 0x98 => 0x56d7, + 0x99 => 0x56ee, 0x9a => 0x56f9, 0x9b => 0x5700, 0x9c => 0x56ff, + 0x9d => 0x5704, 0x9e => 0x5709, 0x9f => 0x5708, 0xa0 => 0x570b, + 0xa1 => 0x570d, 0xa2 => 0x5713, 0xa3 => 0x5718, 0xa4 => 0x5716, + 0xa5 => 0x55c7, 0xa6 => 0x571c, 0xa7 => 0x5726, 0xa8 => 0x5737, + 0xa9 => 0x5738, 0xaa => 0x574e, 0xab => 0x573b, 0xac => 0x5740, + 0xad => 0x574f, 0xae => 0x5769, 0xaf => 0x57c0, 0xb0 => 0x5788, + 0xb1 => 0x5761, 0xb2 => 0x577f, 0xb3 => 0x5789, 0xb4 => 0x5793, + 0xb5 => 0x57a0, 0xb6 => 0x57b3, 0xb7 => 0x57a4, 0xb8 => 0x57aa, + 0xb9 => 0x57b0, 0xba => 0x57c3, 0xbb => 0x57c6, 0xbc => 0x57d4, + 0xbd => 0x57d2, 0xbe => 0x57d3, 0xbf => 0x580a, 0xc0 => 0x57d6, + 0xc1 => 0x57e3, 0xc2 => 0x580b, 0xc3 => 0x5819, 0xc4 => 0x581d, + 0xc5 => 0x5872, 0xc6 => 0x5821, 0xc7 => 0x5862, 0xc8 => 0x584b, + 0xc9 => 0x5870, 0xca => 0x6bc0, 0xcb => 0x5852, 0xcc => 0x583d, + 0xcd => 0x5879, 0xce => 0x5885, 0xcf => 0x58b9, 0xd0 => 0x589f, + 0xd1 => 0x58ab, 0xd2 => 0x58ba, 0xd3 => 0x58de, 0xd4 => 0x58bb, + 0xd5 => 0x58b8, 0xd6 => 0x58ae, 0xd7 => 0x58c5, 0xd8 => 0x58d3, + 0xd9 => 0x58d1, 0xda => 0x58d7, 0xdb => 0x58d9, 0xdc => 0x58d8, + 0xdd => 0x58e5, 0xde => 0x58dc, 0xdf => 0x58e4, 0xe0 => 0x58df, + 0xe1 => 0x58ef, 0xe2 => 0x58fa, 0xe3 => 0x58f9, 0xe4 => 0x58fb, + 0xe5 => 0x58fc, 0xe6 => 0x58fd, 0xe7 => 0x5902, 0xe8 => 0x590a, + 0xe9 => 0x5910, 0xea => 0x591b, 0xeb => 0x68a6, 0xec => 0x5925, + 0xed => 0x592c, 0xee => 0x592d, 0xef => 0x5932, 0xf0 => 0x5938, + 0xf1 => 0x593e, 0xf2 => 0x7ad2, 0xf3 => 0x5955, 0xf4 => 0x5950, + 0xf5 => 0x594e, 0xf6 => 0x595a, 0xf7 => 0x5958, 0xf8 => 0x5962, + 0xf9 => 0x5960, 0xfa => 0x5967, 0xfb => 0x596c, 0xfc => 0x5969, + }, + 0x9b => { + 0x40 => 0x5978, 0x41 => 0x5981, 0x42 => 0x599d, 0x43 => 0x4f5e, + 0x44 => 0x4fab, 0x45 => 0x59a3, 0x46 => 0x59b2, 0x47 => 0x59c6, + 0x48 => 0x59e8, 0x49 => 0x59dc, 0x4a => 0x598d, 0x4b => 0x59d9, + 0x4c => 0x59da, 0x4d => 0x5a25, 0x4e => 0x5a1f, 0x4f => 0x5a11, + 0x50 => 0x5a1c, 0x51 => 0x5a09, 0x52 => 0x5a1a, 0x53 => 0x5a40, + 0x54 => 0x5a6c, 0x55 => 0x5a49, 0x56 => 0x5a35, 0x57 => 0x5a36, + 0x58 => 0x5a62, 0x59 => 0x5a6a, 0x5a => 0x5a9a, 0x5b => 0x5abc, + 0x5c => 0x5abe, 0x5d => 0x5acb, 0x5e => 0x5ac2, 0x5f => 0x5abd, + 0x60 => 0x5ae3, 0x61 => 0x5ad7, 0x62 => 0x5ae6, 0x63 => 0x5ae9, + 0x64 => 0x5ad6, 0x65 => 0x5afa, 0x66 => 0x5afb, 0x67 => 0x5b0c, + 0x68 => 0x5b0b, 0x69 => 0x5b16, 0x6a => 0x5b32, 0x6b => 0x5ad0, + 0x6c => 0x5b2a, 0x6d => 0x5b36, 0x6e => 0x5b3e, 0x6f => 0x5b43, + 0x70 => 0x5b45, 0x71 => 0x5b40, 0x72 => 0x5b51, 0x73 => 0x5b55, + 0x74 => 0x5b5a, 0x75 => 0x5b5b, 0x76 => 0x5b65, 0x77 => 0x5b69, + 0x78 => 0x5b70, 0x79 => 0x5b73, 0x7a => 0x5b75, 0x7b => 0x5b78, + 0x7c => 0x6588, 0x7d => 0x5b7a, 0x7e => 0x5b80, 0x80 => 0x5b83, + 0x81 => 0x5ba6, 0x82 => 0x5bb8, 0x83 => 0x5bc3, 0x84 => 0x5bc7, + 0x85 => 0x5bc9, 0x86 => 0x5bd4, 0x87 => 0x5bd0, 0x88 => 0x5be4, + 0x89 => 0x5be6, 0x8a => 0x5be2, 0x8b => 0x5bde, 0x8c => 0x5be5, + 0x8d => 0x5beb, 0x8e => 0x5bf0, 0x8f => 0x5bf6, 0x90 => 0x5bf3, + 0x91 => 0x5c05, 0x92 => 0x5c07, 0x93 => 0x5c08, 0x94 => 0x5c0d, + 0x95 => 0x5c13, 0x96 => 0x5c20, 0x97 => 0x5c22, 0x98 => 0x5c28, + 0x99 => 0x5c38, 0x9a => 0x5c39, 0x9b => 0x5c41, 0x9c => 0x5c46, + 0x9d => 0x5c4e, 0x9e => 0x5c53, 0x9f => 0x5c50, 0xa0 => 0x5c4f, + 0xa1 => 0x5b71, 0xa2 => 0x5c6c, 0xa3 => 0x5c6e, 0xa4 => 0x4e62, + 0xa5 => 0x5c76, 0xa6 => 0x5c79, 0xa7 => 0x5c8c, 0xa8 => 0x5c91, + 0xa9 => 0x5c94, 0xaa => 0x599b, 0xab => 0x5cab, 0xac => 0x5cbb, + 0xad => 0x5cb6, 0xae => 0x5cbc, 0xaf => 0x5cb7, 0xb0 => 0x5cc5, + 0xb1 => 0x5cbe, 0xb2 => 0x5cc7, 0xb3 => 0x5cd9, 0xb4 => 0x5ce9, + 0xb5 => 0x5cfd, 0xb6 => 0x5cfa, 0xb7 => 0x5ced, 0xb8 => 0x5d8c, + 0xb9 => 0x5cea, 0xba => 0x5d0b, 0xbb => 0x5d15, 0xbc => 0x5d17, + 0xbd => 0x5d5c, 0xbe => 0x5d1f, 0xbf => 0x5d1b, 0xc0 => 0x5d11, + 0xc1 => 0x5d14, 0xc2 => 0x5d22, 0xc3 => 0x5d1a, 0xc4 => 0x5d19, + 0xc5 => 0x5d18, 0xc6 => 0x5d4c, 0xc7 => 0x5d52, 0xc8 => 0x5d4e, + 0xc9 => 0x5d4b, 0xca => 0x5d6c, 0xcb => 0x5d73, 0xcc => 0x5d76, + 0xcd => 0x5d87, 0xce => 0x5d84, 0xcf => 0x5d82, 0xd0 => 0x5da2, + 0xd1 => 0x5d9d, 0xd2 => 0x5dac, 0xd3 => 0x5dae, 0xd4 => 0x5dbd, + 0xd5 => 0x5d90, 0xd6 => 0x5db7, 0xd7 => 0x5dbc, 0xd8 => 0x5dc9, + 0xd9 => 0x5dcd, 0xda => 0x5dd3, 0xdb => 0x5dd2, 0xdc => 0x5dd6, + 0xdd => 0x5ddb, 0xde => 0x5deb, 0xdf => 0x5df2, 0xe0 => 0x5df5, + 0xe1 => 0x5e0b, 0xe2 => 0x5e1a, 0xe3 => 0x5e19, 0xe4 => 0x5e11, + 0xe5 => 0x5e1b, 0xe6 => 0x5e36, 0xe7 => 0x5e37, 0xe8 => 0x5e44, + 0xe9 => 0x5e43, 0xea => 0x5e40, 0xeb => 0x5e4e, 0xec => 0x5e57, + 0xed => 0x5e54, 0xee => 0x5e5f, 0xef => 0x5e62, 0xf0 => 0x5e64, + 0xf1 => 0x5e47, 0xf2 => 0x5e75, 0xf3 => 0x5e76, 0xf4 => 0x5e7a, + 0xf5 => 0x9ebc, 0xf6 => 0x5e7f, 0xf7 => 0x5ea0, 0xf8 => 0x5ec1, + 0xf9 => 0x5ec2, 0xfa => 0x5ec8, 0xfb => 0x5ed0, 0xfc => 0x5ecf, + }, + 0x9c => { + 0x40 => 0x5ed6, 0x41 => 0x5ee3, 0x42 => 0x5edd, 0x43 => 0x5eda, + 0x44 => 0x5edb, 0x45 => 0x5ee2, 0x46 => 0x5ee1, 0x47 => 0x5ee8, + 0x48 => 0x5ee9, 0x49 => 0x5eec, 0x4a => 0x5ef1, 0x4b => 0x5ef3, + 0x4c => 0x5ef0, 0x4d => 0x5ef4, 0x4e => 0x5ef8, 0x4f => 0x5efe, + 0x50 => 0x5f03, 0x51 => 0x5f09, 0x52 => 0x5f5d, 0x53 => 0x5f5c, + 0x54 => 0x5f0b, 0x55 => 0x5f11, 0x56 => 0x5f16, 0x57 => 0x5f29, + 0x58 => 0x5f2d, 0x59 => 0x5f38, 0x5a => 0x5f41, 0x5b => 0x5f48, + 0x5c => 0x5f4c, 0x5d => 0x5f4e, 0x5e => 0x5f2f, 0x5f => 0x5f51, + 0x60 => 0x5f56, 0x61 => 0x5f57, 0x62 => 0x5f59, 0x63 => 0x5f61, + 0x64 => 0x5f6d, 0x65 => 0x5f73, 0x66 => 0x5f77, 0x67 => 0x5f83, + 0x68 => 0x5f82, 0x69 => 0x5f7f, 0x6a => 0x5f8a, 0x6b => 0x5f88, + 0x6c => 0x5f91, 0x6d => 0x5f87, 0x6e => 0x5f9e, 0x6f => 0x5f99, + 0x70 => 0x5f98, 0x71 => 0x5fa0, 0x72 => 0x5fa8, 0x73 => 0x5fad, + 0x74 => 0x5fbc, 0x75 => 0x5fd6, 0x76 => 0x5ffb, 0x77 => 0x5fe4, + 0x78 => 0x5ff8, 0x79 => 0x5ff1, 0x7a => 0x5fdd, 0x7b => 0x60b3, + 0x7c => 0x5fff, 0x7d => 0x6021, 0x7e => 0x6060, 0x80 => 0x6019, + 0x81 => 0x6010, 0x82 => 0x6029, 0x83 => 0x600e, 0x84 => 0x6031, + 0x85 => 0x601b, 0x86 => 0x6015, 0x87 => 0x602b, 0x88 => 0x6026, + 0x89 => 0x600f, 0x8a => 0x603a, 0x8b => 0x605a, 0x8c => 0x6041, + 0x8d => 0x606a, 0x8e => 0x6077, 0x8f => 0x605f, 0x90 => 0x604a, + 0x91 => 0x6046, 0x92 => 0x604d, 0x93 => 0x6063, 0x94 => 0x6043, + 0x95 => 0x6064, 0x96 => 0x6042, 0x97 => 0x606c, 0x98 => 0x606b, + 0x99 => 0x6059, 0x9a => 0x6081, 0x9b => 0x608d, 0x9c => 0x60e7, + 0x9d => 0x6083, 0x9e => 0x609a, 0x9f => 0x6084, 0xa0 => 0x609b, + 0xa1 => 0x6096, 0xa2 => 0x6097, 0xa3 => 0x6092, 0xa4 => 0x60a7, + 0xa5 => 0x608b, 0xa6 => 0x60e1, 0xa7 => 0x60b8, 0xa8 => 0x60e0, + 0xa9 => 0x60d3, 0xaa => 0x60b4, 0xab => 0x5ff0, 0xac => 0x60bd, + 0xad => 0x60c6, 0xae => 0x60b5, 0xaf => 0x60d8, 0xb0 => 0x614d, + 0xb1 => 0x6115, 0xb2 => 0x6106, 0xb3 => 0x60f6, 0xb4 => 0x60f7, + 0xb5 => 0x6100, 0xb6 => 0x60f4, 0xb7 => 0x60fa, 0xb8 => 0x6103, + 0xb9 => 0x6121, 0xba => 0x60fb, 0xbb => 0x60f1, 0xbc => 0x610d, + 0xbd => 0x610e, 0xbe => 0x6147, 0xbf => 0x613e, 0xc0 => 0x6128, + 0xc1 => 0x6127, 0xc2 => 0x614a, 0xc3 => 0x613f, 0xc4 => 0x613c, + 0xc5 => 0x612c, 0xc6 => 0x6134, 0xc7 => 0x613d, 0xc8 => 0x6142, + 0xc9 => 0x6144, 0xca => 0x6173, 0xcb => 0x6177, 0xcc => 0x6158, + 0xcd => 0x6159, 0xce => 0x615a, 0xcf => 0x616b, 0xd0 => 0x6174, + 0xd1 => 0x616f, 0xd2 => 0x6165, 0xd3 => 0x6171, 0xd4 => 0x615f, + 0xd5 => 0x615d, 0xd6 => 0x6153, 0xd7 => 0x6175, 0xd8 => 0x6199, + 0xd9 => 0x6196, 0xda => 0x6187, 0xdb => 0x61ac, 0xdc => 0x6194, + 0xdd => 0x619a, 0xde => 0x618a, 0xdf => 0x6191, 0xe0 => 0x61ab, + 0xe1 => 0x61ae, 0xe2 => 0x61cc, 0xe3 => 0x61ca, 0xe4 => 0x61c9, + 0xe5 => 0x61f7, 0xe6 => 0x61c8, 0xe7 => 0x61c3, 0xe8 => 0x61c6, + 0xe9 => 0x61ba, 0xea => 0x61cb, 0xeb => 0x7f79, 0xec => 0x61cd, + 0xed => 0x61e6, 0xee => 0x61e3, 0xef => 0x61f6, 0xf0 => 0x61fa, + 0xf1 => 0x61f4, 0xf2 => 0x61ff, 0xf3 => 0x61fd, 0xf4 => 0x61fc, + 0xf5 => 0x61fe, 0xf6 => 0x6200, 0xf7 => 0x6208, 0xf8 => 0x6209, + 0xf9 => 0x620d, 0xfa => 0x620c, 0xfb => 0x6214, 0xfc => 0x621b, + }, + 0x9d => { + 0x40 => 0x621e, 0x41 => 0x6221, 0x42 => 0x622a, 0x43 => 0x622e, + 0x44 => 0x6230, 0x45 => 0x6232, 0x46 => 0x6233, 0x47 => 0x6241, + 0x48 => 0x624e, 0x49 => 0x625e, 0x4a => 0x6263, 0x4b => 0x625b, + 0x4c => 0x6260, 0x4d => 0x6268, 0x4e => 0x627c, 0x4f => 0x6282, + 0x50 => 0x6289, 0x51 => 0x627e, 0x52 => 0x6292, 0x53 => 0x6293, + 0x54 => 0x6296, 0x55 => 0x62d4, 0x56 => 0x6283, 0x57 => 0x6294, + 0x58 => 0x62d7, 0x59 => 0x62d1, 0x5a => 0x62bb, 0x5b => 0x62cf, + 0x5c => 0x62ff, 0x5d => 0x62c6, 0x5e => 0x64d4, 0x5f => 0x62c8, + 0x60 => 0x62dc, 0x61 => 0x62cc, 0x62 => 0x62ca, 0x63 => 0x62c2, + 0x64 => 0x62c7, 0x65 => 0x629b, 0x66 => 0x62c9, 0x67 => 0x630c, + 0x68 => 0x62ee, 0x69 => 0x62f1, 0x6a => 0x6327, 0x6b => 0x6302, + 0x6c => 0x6308, 0x6d => 0x62ef, 0x6e => 0x62f5, 0x6f => 0x6350, + 0x70 => 0x633e, 0x71 => 0x634d, 0x72 => 0x641c, 0x73 => 0x634f, + 0x74 => 0x6396, 0x75 => 0x638e, 0x76 => 0x6380, 0x77 => 0x63ab, + 0x78 => 0x6376, 0x79 => 0x63a3, 0x7a => 0x638f, 0x7b => 0x6389, + 0x7c => 0x639f, 0x7d => 0x63b5, 0x7e => 0x636b, 0x80 => 0x6369, + 0x81 => 0x63be, 0x82 => 0x63e9, 0x83 => 0x63c0, 0x84 => 0x63c6, + 0x85 => 0x63e3, 0x86 => 0x63c9, 0x87 => 0x63d2, 0x88 => 0x63f6, + 0x89 => 0x63c4, 0x8a => 0x6416, 0x8b => 0x6434, 0x8c => 0x6406, + 0x8d => 0x6413, 0x8e => 0x6426, 0x8f => 0x6436, 0x90 => 0x651d, + 0x91 => 0x6417, 0x92 => 0x6428, 0x93 => 0x640f, 0x94 => 0x6467, + 0x95 => 0x646f, 0x96 => 0x6476, 0x97 => 0x644e, 0x98 => 0x652a, + 0x99 => 0x6495, 0x9a => 0x6493, 0x9b => 0x64a5, 0x9c => 0x64a9, + 0x9d => 0x6488, 0x9e => 0x64bc, 0x9f => 0x64da, 0xa0 => 0x64d2, + 0xa1 => 0x64c5, 0xa2 => 0x64c7, 0xa3 => 0x64bb, 0xa4 => 0x64d8, + 0xa5 => 0x64c2, 0xa6 => 0x64f1, 0xa7 => 0x64e7, 0xa8 => 0x8209, + 0xa9 => 0x64e0, 0xaa => 0x64e1, 0xab => 0x62ac, 0xac => 0x64e3, + 0xad => 0x64ef, 0xae => 0x652c, 0xaf => 0x64f6, 0xb0 => 0x64f4, + 0xb1 => 0x64f2, 0xb2 => 0x64fa, 0xb3 => 0x6500, 0xb4 => 0x64fd, + 0xb5 => 0x6518, 0xb6 => 0x651c, 0xb7 => 0x6505, 0xb8 => 0x6524, + 0xb9 => 0x6523, 0xba => 0x652b, 0xbb => 0x6534, 0xbc => 0x6535, + 0xbd => 0x6537, 0xbe => 0x6536, 0xbf => 0x6538, 0xc0 => 0x754b, + 0xc1 => 0x6548, 0xc2 => 0x6556, 0xc3 => 0x6555, 0xc4 => 0x654d, + 0xc5 => 0x6558, 0xc6 => 0x655e, 0xc7 => 0x655d, 0xc8 => 0x6572, + 0xc9 => 0x6578, 0xca => 0x6582, 0xcb => 0x6583, 0xcc => 0x8b8a, + 0xcd => 0x659b, 0xce => 0x659f, 0xcf => 0x65ab, 0xd0 => 0x65b7, + 0xd1 => 0x65c3, 0xd2 => 0x65c6, 0xd3 => 0x65c1, 0xd4 => 0x65c4, + 0xd5 => 0x65cc, 0xd6 => 0x65d2, 0xd7 => 0x65db, 0xd8 => 0x65d9, + 0xd9 => 0x65e0, 0xda => 0x65e1, 0xdb => 0x65f1, 0xdc => 0x6772, + 0xdd => 0x660a, 0xde => 0x6603, 0xdf => 0x65fb, 0xe0 => 0x6773, + 0xe1 => 0x6635, 0xe2 => 0x6636, 0xe3 => 0x6634, 0xe4 => 0x661c, + 0xe5 => 0x664f, 0xe6 => 0x6644, 0xe7 => 0x6649, 0xe8 => 0x6641, + 0xe9 => 0x665e, 0xea => 0x665d, 0xeb => 0x6664, 0xec => 0x6667, + 0xed => 0x6668, 0xee => 0x665f, 0xef => 0x6662, 0xf0 => 0x6670, + 0xf1 => 0x6683, 0xf2 => 0x6688, 0xf3 => 0x668e, 0xf4 => 0x6689, + 0xf5 => 0x6684, 0xf6 => 0x6698, 0xf7 => 0x669d, 0xf8 => 0x66c1, + 0xf9 => 0x66b9, 0xfa => 0x66c9, 0xfb => 0x66be, 0xfc => 0x66bc, + }, + 0x9e => { + 0x40 => 0x66c4, 0x41 => 0x66b8, 0x42 => 0x66d6, 0x43 => 0x66da, + 0x44 => 0x66e0, 0x45 => 0x663f, 0x46 => 0x66e6, 0x47 => 0x66e9, + 0x48 => 0x66f0, 0x49 => 0x66f5, 0x4a => 0x66f7, 0x4b => 0x670f, + 0x4c => 0x6716, 0x4d => 0x671e, 0x4e => 0x6726, 0x4f => 0x6727, + 0x50 => 0x9738, 0x51 => 0x672e, 0x52 => 0x673f, 0x53 => 0x6736, + 0x54 => 0x6741, 0x55 => 0x6738, 0x56 => 0x6737, 0x57 => 0x6746, + 0x58 => 0x675e, 0x59 => 0x6760, 0x5a => 0x6759, 0x5b => 0x6763, + 0x5c => 0x6764, 0x5d => 0x6789, 0x5e => 0x6770, 0x5f => 0x67a9, + 0x60 => 0x677c, 0x61 => 0x676a, 0x62 => 0x678c, 0x63 => 0x678b, + 0x64 => 0x67a6, 0x65 => 0x67a1, 0x66 => 0x6785, 0x67 => 0x67b7, + 0x68 => 0x67ef, 0x69 => 0x67b4, 0x6a => 0x67ec, 0x6b => 0x67b3, + 0x6c => 0x67e9, 0x6d => 0x67b8, 0x6e => 0x67e4, 0x6f => 0x67de, + 0x70 => 0x67dd, 0x71 => 0x67e2, 0x72 => 0x67ee, 0x73 => 0x67b9, + 0x74 => 0x67ce, 0x75 => 0x67c6, 0x76 => 0x67e7, 0x77 => 0x6a9c, + 0x78 => 0x681e, 0x79 => 0x6846, 0x7a => 0x6829, 0x7b => 0x6840, + 0x7c => 0x684d, 0x7d => 0x6832, 0x7e => 0x684e, 0x80 => 0x68b3, + 0x81 => 0x682b, 0x82 => 0x6859, 0x83 => 0x6863, 0x84 => 0x6877, + 0x85 => 0x687f, 0x86 => 0x689f, 0x87 => 0x688f, 0x88 => 0x68ad, + 0x89 => 0x6894, 0x8a => 0x689d, 0x8b => 0x689b, 0x8c => 0x6883, + 0x8d => 0x6aae, 0x8e => 0x68b9, 0x8f => 0x6874, 0x90 => 0x68b5, + 0x91 => 0x68a0, 0x92 => 0x68ba, 0x93 => 0x690f, 0x94 => 0x688d, + 0x95 => 0x687e, 0x96 => 0x6901, 0x97 => 0x68ca, 0x98 => 0x6908, + 0x99 => 0x68d8, 0x9a => 0x6922, 0x9b => 0x6926, 0x9c => 0x68e1, + 0x9d => 0x690c, 0x9e => 0x68cd, 0x9f => 0x68d4, 0xa0 => 0x68e7, + 0xa1 => 0x68d5, 0xa2 => 0x6936, 0xa3 => 0x6912, 0xa4 => 0x6904, + 0xa5 => 0x68d7, 0xa6 => 0x68e3, 0xa7 => 0x6925, 0xa8 => 0x68f9, + 0xa9 => 0x68e0, 0xaa => 0x68ef, 0xab => 0x6928, 0xac => 0x692a, + 0xad => 0x691a, 0xae => 0x6923, 0xaf => 0x6921, 0xb0 => 0x68c6, + 0xb1 => 0x6979, 0xb2 => 0x6977, 0xb3 => 0x695c, 0xb4 => 0x6978, + 0xb5 => 0x696b, 0xb6 => 0x6954, 0xb7 => 0x697e, 0xb8 => 0x696e, + 0xb9 => 0x6939, 0xba => 0x6974, 0xbb => 0x693d, 0xbc => 0x6959, + 0xbd => 0x6930, 0xbe => 0x6961, 0xbf => 0x695e, 0xc0 => 0x695d, + 0xc1 => 0x6981, 0xc2 => 0x696a, 0xc3 => 0x69b2, 0xc4 => 0x69ae, + 0xc5 => 0x69d0, 0xc6 => 0x69bf, 0xc7 => 0x69c1, 0xc8 => 0x69d3, + 0xc9 => 0x69be, 0xca => 0x69ce, 0xcb => 0x5be8, 0xcc => 0x69ca, + 0xcd => 0x69dd, 0xce => 0x69bb, 0xcf => 0x69c3, 0xd0 => 0x69a7, + 0xd1 => 0x6a2e, 0xd2 => 0x6991, 0xd3 => 0x69a0, 0xd4 => 0x699c, + 0xd5 => 0x6995, 0xd6 => 0x69b4, 0xd7 => 0x69de, 0xd8 => 0x69e8, + 0xd9 => 0x6a02, 0xda => 0x6a1b, 0xdb => 0x69ff, 0xdc => 0x6b0a, + 0xdd => 0x69f9, 0xde => 0x69f2, 0xdf => 0x69e7, 0xe0 => 0x6a05, + 0xe1 => 0x69b1, 0xe2 => 0x6a1e, 0xe3 => 0x69ed, 0xe4 => 0x6a14, + 0xe5 => 0x69eb, 0xe6 => 0x6a0a, 0xe7 => 0x6a12, 0xe8 => 0x6ac1, + 0xe9 => 0x6a23, 0xea => 0x6a13, 0xeb => 0x6a44, 0xec => 0x6a0c, + 0xed => 0x6a72, 0xee => 0x6a36, 0xef => 0x6a78, 0xf0 => 0x6a47, + 0xf1 => 0x6a62, 0xf2 => 0x6a59, 0xf3 => 0x6a66, 0xf4 => 0x6a48, + 0xf5 => 0x6a38, 0xf6 => 0x6a22, 0xf7 => 0x6a90, 0xf8 => 0x6a8d, + 0xf9 => 0x6aa0, 0xfa => 0x6a84, 0xfb => 0x6aa2, 0xfc => 0x6aa3, + }, + 0x9f => { + 0x40 => 0x6a97, 0x41 => 0x8617, 0x42 => 0x6abb, 0x43 => 0x6ac3, + 0x44 => 0x6ac2, 0x45 => 0x6ab8, 0x46 => 0x6ab3, 0x47 => 0x6aac, + 0x48 => 0x6ade, 0x49 => 0x6ad1, 0x4a => 0x6adf, 0x4b => 0x6aaa, + 0x4c => 0x6ada, 0x4d => 0x6aea, 0x4e => 0x6afb, 0x4f => 0x6b05, + 0x50 => 0x8616, 0x51 => 0x6afa, 0x52 => 0x6b12, 0x53 => 0x6b16, + 0x54 => 0x9b31, 0x55 => 0x6b1f, 0x56 => 0x6b38, 0x57 => 0x6b37, + 0x58 => 0x76dc, 0x59 => 0x6b39, 0x5a => 0x98ee, 0x5b => 0x6b47, + 0x5c => 0x6b43, 0x5d => 0x6b49, 0x5e => 0x6b50, 0x5f => 0x6b59, + 0x60 => 0x6b54, 0x61 => 0x6b5b, 0x62 => 0x6b5f, 0x63 => 0x6b61, + 0x64 => 0x6b78, 0x65 => 0x6b79, 0x66 => 0x6b7f, 0x67 => 0x6b80, + 0x68 => 0x6b84, 0x69 => 0x6b83, 0x6a => 0x6b8d, 0x6b => 0x6b98, + 0x6c => 0x6b95, 0x6d => 0x6b9e, 0x6e => 0x6ba4, 0x6f => 0x6baa, + 0x70 => 0x6bab, 0x71 => 0x6baf, 0x72 => 0x6bb2, 0x73 => 0x6bb1, + 0x74 => 0x6bb3, 0x75 => 0x6bb7, 0x76 => 0x6bbc, 0x77 => 0x6bc6, + 0x78 => 0x6bcb, 0x79 => 0x6bd3, 0x7a => 0x6bdf, 0x7b => 0x6bec, + 0x7c => 0x6beb, 0x7d => 0x6bf3, 0x7e => 0x6bef, 0x80 => 0x9ebe, + 0x81 => 0x6c08, 0x82 => 0x6c13, 0x83 => 0x6c14, 0x84 => 0x6c1b, + 0x85 => 0x6c24, 0x86 => 0x6c23, 0x87 => 0x6c5e, 0x88 => 0x6c55, + 0x89 => 0x6c62, 0x8a => 0x6c6a, 0x8b => 0x6c82, 0x8c => 0x6c8d, + 0x8d => 0x6c9a, 0x8e => 0x6c81, 0x8f => 0x6c9b, 0x90 => 0x6c7e, + 0x91 => 0x6c68, 0x92 => 0x6c73, 0x93 => 0x6c92, 0x94 => 0x6c90, + 0x95 => 0x6cc4, 0x96 => 0x6cf1, 0x97 => 0x6cd3, 0x98 => 0x6cbd, + 0x99 => 0x6cd7, 0x9a => 0x6cc5, 0x9b => 0x6cdd, 0x9c => 0x6cae, + 0x9d => 0x6cb1, 0x9e => 0x6cbe, 0x9f => 0x6cba, 0xa0 => 0x6cdb, + 0xa1 => 0x6cef, 0xa2 => 0x6cd9, 0xa3 => 0x6cea, 0xa4 => 0x6d1f, + 0xa5 => 0x884d, 0xa6 => 0x6d36, 0xa7 => 0x6d2b, 0xa8 => 0x6d3d, + 0xa9 => 0x6d38, 0xaa => 0x6d19, 0xab => 0x6d35, 0xac => 0x6d33, + 0xad => 0x6d12, 0xae => 0x6d0c, 0xaf => 0x6d63, 0xb0 => 0x6d93, + 0xb1 => 0x6d64, 0xb2 => 0x6d5a, 0xb3 => 0x6d79, 0xb4 => 0x6d59, + 0xb5 => 0x6d8e, 0xb6 => 0x6d95, 0xb7 => 0x6fe4, 0xb8 => 0x6d85, + 0xb9 => 0x6df9, 0xba => 0x6e15, 0xbb => 0x6e0a, 0xbc => 0x6db5, + 0xbd => 0x6dc7, 0xbe => 0x6de6, 0xbf => 0x6db8, 0xc0 => 0x6dc6, + 0xc1 => 0x6dec, 0xc2 => 0x6dde, 0xc3 => 0x6dcc, 0xc4 => 0x6de8, + 0xc5 => 0x6dd2, 0xc6 => 0x6dc5, 0xc7 => 0x6dfa, 0xc8 => 0x6dd9, + 0xc9 => 0x6de4, 0xca => 0x6dd5, 0xcb => 0x6dea, 0xcc => 0x6dee, + 0xcd => 0x6e2d, 0xce => 0x6e6e, 0xcf => 0x6e2e, 0xd0 => 0x6e19, + 0xd1 => 0x6e72, 0xd2 => 0x6e5f, 0xd3 => 0x6e3e, 0xd4 => 0x6e23, + 0xd5 => 0x6e6b, 0xd6 => 0x6e2b, 0xd7 => 0x6e76, 0xd8 => 0x6e4d, + 0xd9 => 0x6e1f, 0xda => 0x6e43, 0xdb => 0x6e3a, 0xdc => 0x6e4e, + 0xdd => 0x6e24, 0xde => 0x6eff, 0xdf => 0x6e1d, 0xe0 => 0x6e38, + 0xe1 => 0x6e82, 0xe2 => 0x6eaa, 0xe3 => 0x6e98, 0xe4 => 0x6ec9, + 0xe5 => 0x6eb7, 0xe6 => 0x6ed3, 0xe7 => 0x6ebd, 0xe8 => 0x6eaf, + 0xe9 => 0x6ec4, 0xea => 0x6eb2, 0xeb => 0x6ed4, 0xec => 0x6ed5, + 0xed => 0x6e8f, 0xee => 0x6ea5, 0xef => 0x6ec2, 0xf0 => 0x6e9f, + 0xf1 => 0x6f41, 0xf2 => 0x6f11, 0xf3 => 0x704c, 0xf4 => 0x6eec, + 0xf5 => 0x6ef8, 0xf6 => 0x6efe, 0xf7 => 0x6f3f, 0xf8 => 0x6ef2, + 0xf9 => 0x6f31, 0xfa => 0x6eef, 0xfb => 0x6f32, 0xfc => 0x6ecc, + }, + 0xa1 => 0xff61, 0xa2 => 0xff62, 0xa3 => 0xff63, 0xa4 => 0xff64, + 0xa5 => 0xff65, 0xa6 => 0xff66, 0xa7 => 0xff67, 0xa8 => 0xff68, + 0xa9 => 0xff69, 0xaa => 0xff6a, 0xab => 0xff6b, 0xac => 0xff6c, + 0xad => 0xff6d, 0xae => 0xff6e, 0xaf => 0xff6f, 0xb0 => 0xff70, + 0xb1 => 0xff71, 0xb2 => 0xff72, 0xb3 => 0xff73, 0xb4 => 0xff74, + 0xb5 => 0xff75, 0xb6 => 0xff76, 0xb7 => 0xff77, 0xb8 => 0xff78, + 0xb9 => 0xff79, 0xba => 0xff7a, 0xbb => 0xff7b, 0xbc => 0xff7c, + 0xbd => 0xff7d, 0xbe => 0xff7e, 0xbf => 0xff7f, 0xc0 => 0xff80, + 0xc1 => 0xff81, 0xc2 => 0xff82, 0xc3 => 0xff83, 0xc4 => 0xff84, + 0xc5 => 0xff85, 0xc6 => 0xff86, 0xc7 => 0xff87, 0xc8 => 0xff88, + 0xc9 => 0xff89, 0xca => 0xff8a, 0xcb => 0xff8b, 0xcc => 0xff8c, + 0xcd => 0xff8d, 0xce => 0xff8e, 0xcf => 0xff8f, 0xd0 => 0xff90, + 0xd1 => 0xff91, 0xd2 => 0xff92, 0xd3 => 0xff93, 0xd4 => 0xff94, + 0xd5 => 0xff95, 0xd6 => 0xff96, 0xd7 => 0xff97, 0xd8 => 0xff98, + 0xd9 => 0xff99, 0xda => 0xff9a, 0xdb => 0xff9b, 0xdc => 0xff9c, + 0xdd => 0xff9d, 0xde => 0xff9e, 0xdf => 0xff9f, + 0xe0 => { + 0x40 => 0x6f3e, 0x41 => 0x6f13, 0x42 => 0x6ef7, 0x43 => 0x6f86, + 0x44 => 0x6f7a, 0x45 => 0x6f78, 0x46 => 0x6f81, 0x47 => 0x6f80, + 0x48 => 0x6f6f, 0x49 => 0x6f5b, 0x4a => 0x6ff3, 0x4b => 0x6f6d, + 0x4c => 0x6f82, 0x4d => 0x6f7c, 0x4e => 0x6f58, 0x4f => 0x6f8e, + 0x50 => 0x6f91, 0x51 => 0x6fc2, 0x52 => 0x6f66, 0x53 => 0x6fb3, + 0x54 => 0x6fa3, 0x55 => 0x6fa1, 0x56 => 0x6fa4, 0x57 => 0x6fb9, + 0x58 => 0x6fc6, 0x59 => 0x6faa, 0x5a => 0x6fdf, 0x5b => 0x6fd5, + 0x5c => 0x6fec, 0x5d => 0x6fd4, 0x5e => 0x6fd8, 0x5f => 0x6ff1, + 0x60 => 0x6fee, 0x61 => 0x6fdb, 0x62 => 0x7009, 0x63 => 0x700b, + 0x64 => 0x6ffa, 0x65 => 0x7011, 0x66 => 0x7001, 0x67 => 0x700f, + 0x68 => 0x6ffe, 0x69 => 0x701b, 0x6a => 0x701a, 0x6b => 0x6f74, + 0x6c => 0x701d, 0x6d => 0x7018, 0x6e => 0x701f, 0x6f => 0x7030, + 0x70 => 0x703e, 0x71 => 0x7032, 0x72 => 0x7051, 0x73 => 0x7063, + 0x74 => 0x7099, 0x75 => 0x7092, 0x76 => 0x70af, 0x77 => 0x70f1, + 0x78 => 0x70ac, 0x79 => 0x70b8, 0x7a => 0x70b3, 0x7b => 0x70ae, + 0x7c => 0x70df, 0x7d => 0x70cb, 0x7e => 0x70dd, 0x80 => 0x70d9, + 0x81 => 0x7109, 0x82 => 0x70fd, 0x83 => 0x711c, 0x84 => 0x7119, + 0x85 => 0x7165, 0x86 => 0x7155, 0x87 => 0x7188, 0x88 => 0x7166, + 0x89 => 0x7162, 0x8a => 0x714c, 0x8b => 0x7156, 0x8c => 0x716c, + 0x8d => 0x718f, 0x8e => 0x71fb, 0x8f => 0x7184, 0x90 => 0x7195, + 0x91 => 0x71a8, 0x92 => 0x71ac, 0x93 => 0x71d7, 0x94 => 0x71b9, + 0x95 => 0x71be, 0x96 => 0x71d2, 0x97 => 0x71c9, 0x98 => 0x71d4, + 0x99 => 0x71ce, 0x9a => 0x71e0, 0x9b => 0x71ec, 0x9c => 0x71e7, + 0x9d => 0x71f5, 0x9e => 0x71fc, 0x9f => 0x71f9, 0xa0 => 0x71ff, + 0xa1 => 0x720d, 0xa2 => 0x7210, 0xa3 => 0x721b, 0xa4 => 0x7228, + 0xa5 => 0x722d, 0xa6 => 0x722c, 0xa7 => 0x7230, 0xa8 => 0x7232, + 0xa9 => 0x723b, 0xaa => 0x723c, 0xab => 0x723f, 0xac => 0x7240, + 0xad => 0x7246, 0xae => 0x724b, 0xaf => 0x7258, 0xb0 => 0x7274, + 0xb1 => 0x727e, 0xb2 => 0x7282, 0xb3 => 0x7281, 0xb4 => 0x7287, + 0xb5 => 0x7292, 0xb6 => 0x7296, 0xb7 => 0x72a2, 0xb8 => 0x72a7, + 0xb9 => 0x72b9, 0xba => 0x72b2, 0xbb => 0x72c3, 0xbc => 0x72c6, + 0xbd => 0x72c4, 0xbe => 0x72ce, 0xbf => 0x72d2, 0xc0 => 0x72e2, + 0xc1 => 0x72e0, 0xc2 => 0x72e1, 0xc3 => 0x72f9, 0xc4 => 0x72f7, + 0xc5 => 0x500f, 0xc6 => 0x7317, 0xc7 => 0x730a, 0xc8 => 0x731c, + 0xc9 => 0x7316, 0xca => 0x731d, 0xcb => 0x7334, 0xcc => 0x732f, + 0xcd => 0x7329, 0xce => 0x7325, 0xcf => 0x733e, 0xd0 => 0x734e, + 0xd1 => 0x734f, 0xd2 => 0x9ed8, 0xd3 => 0x7357, 0xd4 => 0x736a, + 0xd5 => 0x7368, 0xd6 => 0x7370, 0xd7 => 0x7378, 0xd8 => 0x7375, + 0xd9 => 0x737b, 0xda => 0x737a, 0xdb => 0x73c8, 0xdc => 0x73b3, + 0xdd => 0x73ce, 0xde => 0x73bb, 0xdf => 0x73c0, 0xe0 => 0x73e5, + 0xe1 => 0x73ee, 0xe2 => 0x73de, 0xe3 => 0x74a2, 0xe4 => 0x7405, + 0xe5 => 0x746f, 0xe6 => 0x7425, 0xe7 => 0x73f8, 0xe8 => 0x7432, + 0xe9 => 0x743a, 0xea => 0x7455, 0xeb => 0x743f, 0xec => 0x745f, + 0xed => 0x7459, 0xee => 0x7441, 0xef => 0x745c, 0xf0 => 0x7469, + 0xf1 => 0x7470, 0xf2 => 0x7463, 0xf3 => 0x746a, 0xf4 => 0x7476, + 0xf5 => 0x747e, 0xf6 => 0x748b, 0xf7 => 0x749e, 0xf8 => 0x74a7, + 0xf9 => 0x74ca, 0xfa => 0x74cf, 0xfb => 0x74d4, 0xfc => 0x73f1, + }, + 0xe1 => { + 0x40 => 0x74e0, 0x41 => 0x74e3, 0x42 => 0x74e7, 0x43 => 0x74e9, + 0x44 => 0x74ee, 0x45 => 0x74f2, 0x46 => 0x74f0, 0x47 => 0x74f1, + 0x48 => 0x74f8, 0x49 => 0x74f7, 0x4a => 0x7504, 0x4b => 0x7503, + 0x4c => 0x7505, 0x4d => 0x750c, 0x4e => 0x750e, 0x4f => 0x750d, + 0x50 => 0x7515, 0x51 => 0x7513, 0x52 => 0x751e, 0x53 => 0x7526, + 0x54 => 0x752c, 0x55 => 0x753c, 0x56 => 0x7544, 0x57 => 0x754d, + 0x58 => 0x754a, 0x59 => 0x7549, 0x5a => 0x755b, 0x5b => 0x7546, + 0x5c => 0x755a, 0x5d => 0x7569, 0x5e => 0x7564, 0x5f => 0x7567, + 0x60 => 0x756b, 0x61 => 0x756d, 0x62 => 0x7578, 0x63 => 0x7576, + 0x64 => 0x7586, 0x65 => 0x7587, 0x66 => 0x7574, 0x67 => 0x758a, + 0x68 => 0x7589, 0x69 => 0x7582, 0x6a => 0x7594, 0x6b => 0x759a, + 0x6c => 0x759d, 0x6d => 0x75a5, 0x6e => 0x75a3, 0x6f => 0x75c2, + 0x70 => 0x75b3, 0x71 => 0x75c3, 0x72 => 0x75b5, 0x73 => 0x75bd, + 0x74 => 0x75b8, 0x75 => 0x75bc, 0x76 => 0x75b1, 0x77 => 0x75cd, + 0x78 => 0x75ca, 0x79 => 0x75d2, 0x7a => 0x75d9, 0x7b => 0x75e3, + 0x7c => 0x75de, 0x7d => 0x75fe, 0x7e => 0x75ff, 0x80 => 0x75fc, + 0x81 => 0x7601, 0x82 => 0x75f0, 0x83 => 0x75fa, 0x84 => 0x75f2, + 0x85 => 0x75f3, 0x86 => 0x760b, 0x87 => 0x760d, 0x88 => 0x7609, + 0x89 => 0x761f, 0x8a => 0x7627, 0x8b => 0x7620, 0x8c => 0x7621, + 0x8d => 0x7622, 0x8e => 0x7624, 0x8f => 0x7634, 0x90 => 0x7630, + 0x91 => 0x763b, 0x92 => 0x7647, 0x93 => 0x7648, 0x94 => 0x7646, + 0x95 => 0x765c, 0x96 => 0x7658, 0x97 => 0x7661, 0x98 => 0x7662, + 0x99 => 0x7668, 0x9a => 0x7669, 0x9b => 0x766a, 0x9c => 0x7667, + 0x9d => 0x766c, 0x9e => 0x7670, 0x9f => 0x7672, 0xa0 => 0x7676, + 0xa1 => 0x7678, 0xa2 => 0x767c, 0xa3 => 0x7680, 0xa4 => 0x7683, + 0xa5 => 0x7688, 0xa6 => 0x768b, 0xa7 => 0x768e, 0xa8 => 0x7696, + 0xa9 => 0x7693, 0xaa => 0x7699, 0xab => 0x769a, 0xac => 0x76b0, + 0xad => 0x76b4, 0xae => 0x76b8, 0xaf => 0x76b9, 0xb0 => 0x76ba, + 0xb1 => 0x76c2, 0xb2 => 0x76cd, 0xb3 => 0x76d6, 0xb4 => 0x76d2, + 0xb5 => 0x76de, 0xb6 => 0x76e1, 0xb7 => 0x76e5, 0xb8 => 0x76e7, + 0xb9 => 0x76ea, 0xba => 0x862f, 0xbb => 0x76fb, 0xbc => 0x7708, + 0xbd => 0x7707, 0xbe => 0x7704, 0xbf => 0x7729, 0xc0 => 0x7724, + 0xc1 => 0x771e, 0xc2 => 0x7725, 0xc3 => 0x7726, 0xc4 => 0x771b, + 0xc5 => 0x7737, 0xc6 => 0x7738, 0xc7 => 0x7747, 0xc8 => 0x775a, + 0xc9 => 0x7768, 0xca => 0x776b, 0xcb => 0x775b, 0xcc => 0x7765, + 0xcd => 0x777f, 0xce => 0x777e, 0xcf => 0x7779, 0xd0 => 0x778e, + 0xd1 => 0x778b, 0xd2 => 0x7791, 0xd3 => 0x77a0, 0xd4 => 0x779e, + 0xd5 => 0x77b0, 0xd6 => 0x77b6, 0xd7 => 0x77b9, 0xd8 => 0x77bf, + 0xd9 => 0x77bc, 0xda => 0x77bd, 0xdb => 0x77bb, 0xdc => 0x77c7, + 0xdd => 0x77cd, 0xde => 0x77d7, 0xdf => 0x77da, 0xe0 => 0x77dc, + 0xe1 => 0x77e3, 0xe2 => 0x77ee, 0xe3 => 0x77fc, 0xe4 => 0x780c, + 0xe5 => 0x7812, 0xe6 => 0x7926, 0xe7 => 0x7820, 0xe8 => 0x792a, + 0xe9 => 0x7845, 0xea => 0x788e, 0xeb => 0x7874, 0xec => 0x7886, + 0xed => 0x787c, 0xee => 0x789a, 0xef => 0x788c, 0xf0 => 0x78a3, + 0xf1 => 0x78b5, 0xf2 => 0x78aa, 0xf3 => 0x78af, 0xf4 => 0x78d1, + 0xf5 => 0x78c6, 0xf6 => 0x78cb, 0xf7 => 0x78d4, 0xf8 => 0x78be, + 0xf9 => 0x78bc, 0xfa => 0x78c5, 0xfb => 0x78ca, 0xfc => 0x78ec, + }, + 0xe2 => { + 0x40 => 0x78e7, 0x41 => 0x78da, 0x42 => 0x78fd, 0x43 => 0x78f4, + 0x44 => 0x7907, 0x45 => 0x7912, 0x46 => 0x7911, 0x47 => 0x7919, + 0x48 => 0x792c, 0x49 => 0x792b, 0x4a => 0x7940, 0x4b => 0x7960, + 0x4c => 0x7957, 0x4d => 0x795f, 0x4e => 0x795a, 0x4f => 0x7955, + 0x50 => 0x7953, 0x51 => 0x797a, 0x52 => 0x797f, 0x53 => 0x798a, + 0x54 => 0x799d, 0x55 => 0x79a7, 0x56 => 0x9f4b, 0x57 => 0x79aa, + 0x58 => 0x79ae, 0x59 => 0x79b3, 0x5a => 0x79b9, 0x5b => 0x79ba, + 0x5c => 0x79c9, 0x5d => 0x79d5, 0x5e => 0x79e7, 0x5f => 0x79ec, + 0x60 => 0x79e1, 0x61 => 0x79e3, 0x62 => 0x7a08, 0x63 => 0x7a0d, + 0x64 => 0x7a18, 0x65 => 0x7a19, 0x66 => 0x7a20, 0x67 => 0x7a1f, + 0x68 => 0x7980, 0x69 => 0x7a31, 0x6a => 0x7a3b, 0x6b => 0x7a3e, + 0x6c => 0x7a37, 0x6d => 0x7a43, 0x6e => 0x7a57, 0x6f => 0x7a49, + 0x70 => 0x7a61, 0x71 => 0x7a62, 0x72 => 0x7a69, 0x73 => 0x9f9d, + 0x74 => 0x7a70, 0x75 => 0x7a79, 0x76 => 0x7a7d, 0x77 => 0x7a88, + 0x78 => 0x7a97, 0x79 => 0x7a95, 0x7a => 0x7a98, 0x7b => 0x7a96, + 0x7c => 0x7aa9, 0x7d => 0x7ac8, 0x7e => 0x7ab0, 0x80 => 0x7ab6, + 0x81 => 0x7ac5, 0x82 => 0x7ac4, 0x83 => 0x7abf, 0x84 => 0x9083, + 0x85 => 0x7ac7, 0x86 => 0x7aca, 0x87 => 0x7acd, 0x88 => 0x7acf, + 0x89 => 0x7ad5, 0x8a => 0x7ad3, 0x8b => 0x7ad9, 0x8c => 0x7ada, + 0x8d => 0x7add, 0x8e => 0x7ae1, 0x8f => 0x7ae2, 0x90 => 0x7ae6, + 0x91 => 0x7aed, 0x92 => 0x7af0, 0x93 => 0x7b02, 0x94 => 0x7b0f, + 0x95 => 0x7b0a, 0x96 => 0x7b06, 0x97 => 0x7b33, 0x98 => 0x7b18, + 0x99 => 0x7b19, 0x9a => 0x7b1e, 0x9b => 0x7b35, 0x9c => 0x7b28, + 0x9d => 0x7b36, 0x9e => 0x7b50, 0x9f => 0x7b7a, 0xa0 => 0x7b04, + 0xa1 => 0x7b4d, 0xa2 => 0x7b0b, 0xa3 => 0x7b4c, 0xa4 => 0x7b45, + 0xa5 => 0x7b75, 0xa6 => 0x7b65, 0xa7 => 0x7b74, 0xa8 => 0x7b67, + 0xa9 => 0x7b70, 0xaa => 0x7b71, 0xab => 0x7b6c, 0xac => 0x7b6e, + 0xad => 0x7b9d, 0xae => 0x7b98, 0xaf => 0x7b9f, 0xb0 => 0x7b8d, + 0xb1 => 0x7b9c, 0xb2 => 0x7b9a, 0xb3 => 0x7b8b, 0xb4 => 0x7b92, + 0xb5 => 0x7b8f, 0xb6 => 0x7b5d, 0xb7 => 0x7b99, 0xb8 => 0x7bcb, + 0xb9 => 0x7bc1, 0xba => 0x7bcc, 0xbb => 0x7bcf, 0xbc => 0x7bb4, + 0xbd => 0x7bc6, 0xbe => 0x7bdd, 0xbf => 0x7be9, 0xc0 => 0x7c11, + 0xc1 => 0x7c14, 0xc2 => 0x7be6, 0xc3 => 0x7be5, 0xc4 => 0x7c60, + 0xc5 => 0x7c00, 0xc6 => 0x7c07, 0xc7 => 0x7c13, 0xc8 => 0x7bf3, + 0xc9 => 0x7bf7, 0xca => 0x7c17, 0xcb => 0x7c0d, 0xcc => 0x7bf6, + 0xcd => 0x7c23, 0xce => 0x7c27, 0xcf => 0x7c2a, 0xd0 => 0x7c1f, + 0xd1 => 0x7c37, 0xd2 => 0x7c2b, 0xd3 => 0x7c3d, 0xd4 => 0x7c4c, + 0xd5 => 0x7c43, 0xd6 => 0x7c54, 0xd7 => 0x7c4f, 0xd8 => 0x7c40, + 0xd9 => 0x7c50, 0xda => 0x7c58, 0xdb => 0x7c5f, 0xdc => 0x7c64, + 0xdd => 0x7c56, 0xde => 0x7c65, 0xdf => 0x7c6c, 0xe0 => 0x7c75, + 0xe1 => 0x7c83, 0xe2 => 0x7c90, 0xe3 => 0x7ca4, 0xe4 => 0x7cad, + 0xe5 => 0x7ca2, 0xe6 => 0x7cab, 0xe7 => 0x7ca1, 0xe8 => 0x7ca8, + 0xe9 => 0x7cb3, 0xea => 0x7cb2, 0xeb => 0x7cb1, 0xec => 0x7cae, + 0xed => 0x7cb9, 0xee => 0x7cbd, 0xef => 0x7cc0, 0xf0 => 0x7cc5, + 0xf1 => 0x7cc2, 0xf2 => 0x7cd8, 0xf3 => 0x7cd2, 0xf4 => 0x7cdc, + 0xf5 => 0x7ce2, 0xf6 => 0x9b3b, 0xf7 => 0x7cef, 0xf8 => 0x7cf2, + 0xf9 => 0x7cf4, 0xfa => 0x7cf6, 0xfb => 0x7cfa, 0xfc => 0x7d06, + }, + 0xe3 => { + 0x40 => 0x7d02, 0x41 => 0x7d1c, 0x42 => 0x7d15, 0x43 => 0x7d0a, + 0x44 => 0x7d45, 0x45 => 0x7d4b, 0x46 => 0x7d2e, 0x47 => 0x7d32, + 0x48 => 0x7d3f, 0x49 => 0x7d35, 0x4a => 0x7d46, 0x4b => 0x7d73, + 0x4c => 0x7d56, 0x4d => 0x7d4e, 0x4e => 0x7d72, 0x4f => 0x7d68, + 0x50 => 0x7d6e, 0x51 => 0x7d4f, 0x52 => 0x7d63, 0x53 => 0x7d93, + 0x54 => 0x7d89, 0x55 => 0x7d5b, 0x56 => 0x7d8f, 0x57 => 0x7d7d, + 0x58 => 0x7d9b, 0x59 => 0x7dba, 0x5a => 0x7dae, 0x5b => 0x7da3, + 0x5c => 0x7db5, 0x5d => 0x7dc7, 0x5e => 0x7dbd, 0x5f => 0x7dab, + 0x60 => 0x7e3d, 0x61 => 0x7da2, 0x62 => 0x7daf, 0x63 => 0x7ddc, + 0x64 => 0x7db8, 0x65 => 0x7d9f, 0x66 => 0x7db0, 0x67 => 0x7dd8, + 0x68 => 0x7ddd, 0x69 => 0x7de4, 0x6a => 0x7dde, 0x6b => 0x7dfb, + 0x6c => 0x7df2, 0x6d => 0x7de1, 0x6e => 0x7e05, 0x6f => 0x7e0a, + 0x70 => 0x7e23, 0x71 => 0x7e21, 0x72 => 0x7e12, 0x73 => 0x7e31, + 0x74 => 0x7e1f, 0x75 => 0x7e09, 0x76 => 0x7e0b, 0x77 => 0x7e22, + 0x78 => 0x7e46, 0x79 => 0x7e66, 0x7a => 0x7e3b, 0x7b => 0x7e35, + 0x7c => 0x7e39, 0x7d => 0x7e43, 0x7e => 0x7e37, 0x80 => 0x7e32, + 0x81 => 0x7e3a, 0x82 => 0x7e67, 0x83 => 0x7e5d, 0x84 => 0x7e56, + 0x85 => 0x7e5e, 0x86 => 0x7e59, 0x87 => 0x7e5a, 0x88 => 0x7e79, + 0x89 => 0x7e6a, 0x8a => 0x7e69, 0x8b => 0x7e7c, 0x8c => 0x7e7b, + 0x8d => 0x7e83, 0x8e => 0x7dd5, 0x8f => 0x7e7d, 0x90 => 0x8fae, + 0x91 => 0x7e7f, 0x92 => 0x7e88, 0x93 => 0x7e89, 0x94 => 0x7e8c, + 0x95 => 0x7e92, 0x96 => 0x7e90, 0x97 => 0x7e93, 0x98 => 0x7e94, + 0x99 => 0x7e96, 0x9a => 0x7e8e, 0x9b => 0x7e9b, 0x9c => 0x7e9c, + 0x9d => 0x7f38, 0x9e => 0x7f3a, 0x9f => 0x7f45, 0xa0 => 0x7f4c, + 0xa1 => 0x7f4d, 0xa2 => 0x7f4e, 0xa3 => 0x7f50, 0xa4 => 0x7f51, + 0xa5 => 0x7f55, 0xa6 => 0x7f54, 0xa7 => 0x7f58, 0xa8 => 0x7f5f, + 0xa9 => 0x7f60, 0xaa => 0x7f68, 0xab => 0x7f69, 0xac => 0x7f67, + 0xad => 0x7f78, 0xae => 0x7f82, 0xaf => 0x7f86, 0xb0 => 0x7f83, + 0xb1 => 0x7f88, 0xb2 => 0x7f87, 0xb3 => 0x7f8c, 0xb4 => 0x7f94, + 0xb5 => 0x7f9e, 0xb6 => 0x7f9d, 0xb7 => 0x7f9a, 0xb8 => 0x7fa3, + 0xb9 => 0x7faf, 0xba => 0x7fb2, 0xbb => 0x7fb9, 0xbc => 0x7fae, + 0xbd => 0x7fb6, 0xbe => 0x7fb8, 0xbf => 0x8b71, 0xc0 => 0x7fc5, + 0xc1 => 0x7fc6, 0xc2 => 0x7fca, 0xc3 => 0x7fd5, 0xc4 => 0x7fd4, + 0xc5 => 0x7fe1, 0xc6 => 0x7fe6, 0xc7 => 0x7fe9, 0xc8 => 0x7ff3, + 0xc9 => 0x7ff9, 0xca => 0x98dc, 0xcb => 0x8006, 0xcc => 0x8004, + 0xcd => 0x800b, 0xce => 0x8012, 0xcf => 0x8018, 0xd0 => 0x8019, + 0xd1 => 0x801c, 0xd2 => 0x8021, 0xd3 => 0x8028, 0xd4 => 0x803f, + 0xd5 => 0x803b, 0xd6 => 0x804a, 0xd7 => 0x8046, 0xd8 => 0x8052, + 0xd9 => 0x8058, 0xda => 0x805a, 0xdb => 0x805f, 0xdc => 0x8062, + 0xdd => 0x8068, 0xde => 0x8073, 0xdf => 0x8072, 0xe0 => 0x8070, + 0xe1 => 0x8076, 0xe2 => 0x8079, 0xe3 => 0x807d, 0xe4 => 0x807f, + 0xe5 => 0x8084, 0xe6 => 0x8086, 0xe7 => 0x8085, 0xe8 => 0x809b, + 0xe9 => 0x8093, 0xea => 0x809a, 0xeb => 0x80ad, 0xec => 0x5190, + 0xed => 0x80ac, 0xee => 0x80db, 0xef => 0x80e5, 0xf0 => 0x80d9, + 0xf1 => 0x80dd, 0xf2 => 0x80c4, 0xf3 => 0x80da, 0xf4 => 0x80d6, + 0xf5 => 0x8109, 0xf6 => 0x80ef, 0xf7 => 0x80f1, 0xf8 => 0x811b, + 0xf9 => 0x8129, 0xfa => 0x8123, 0xfb => 0x812f, 0xfc => 0x814b, + }, + 0xe4 => { + 0x40 => 0x968b, 0x41 => 0x8146, 0x42 => 0x813e, 0x43 => 0x8153, + 0x44 => 0x8151, 0x45 => 0x80fc, 0x46 => 0x8171, 0x47 => 0x816e, + 0x48 => 0x8165, 0x49 => 0x8166, 0x4a => 0x8174, 0x4b => 0x8183, + 0x4c => 0x8188, 0x4d => 0x818a, 0x4e => 0x8180, 0x4f => 0x8182, + 0x50 => 0x81a0, 0x51 => 0x8195, 0x52 => 0x81a4, 0x53 => 0x81a3, + 0x54 => 0x815f, 0x55 => 0x8193, 0x56 => 0x81a9, 0x57 => 0x81b0, + 0x58 => 0x81b5, 0x59 => 0x81be, 0x5a => 0x81b8, 0x5b => 0x81bd, + 0x5c => 0x81c0, 0x5d => 0x81c2, 0x5e => 0x81ba, 0x5f => 0x81c9, + 0x60 => 0x81cd, 0x61 => 0x81d1, 0x62 => 0x81d9, 0x63 => 0x81d8, + 0x64 => 0x81c8, 0x65 => 0x81da, 0x66 => 0x81df, 0x67 => 0x81e0, + 0x68 => 0x81e7, 0x69 => 0x81fa, 0x6a => 0x81fb, 0x6b => 0x81fe, + 0x6c => 0x8201, 0x6d => 0x8202, 0x6e => 0x8205, 0x6f => 0x8207, + 0x70 => 0x820a, 0x71 => 0x820d, 0x72 => 0x8210, 0x73 => 0x8216, + 0x74 => 0x8229, 0x75 => 0x822b, 0x76 => 0x8238, 0x77 => 0x8233, + 0x78 => 0x8240, 0x79 => 0x8259, 0x7a => 0x8258, 0x7b => 0x825d, + 0x7c => 0x825a, 0x7d => 0x825f, 0x7e => 0x8264, 0x80 => 0x8262, + 0x81 => 0x8268, 0x82 => 0x826a, 0x83 => 0x826b, 0x84 => 0x822e, + 0x85 => 0x8271, 0x86 => 0x8277, 0x87 => 0x8278, 0x88 => 0x827e, + 0x89 => 0x828d, 0x8a => 0x8292, 0x8b => 0x82ab, 0x8c => 0x829f, + 0x8d => 0x82bb, 0x8e => 0x82ac, 0x8f => 0x82e1, 0x90 => 0x82e3, + 0x91 => 0x82df, 0x92 => 0x82d2, 0x93 => 0x82f4, 0x94 => 0x82f3, + 0x95 => 0x82fa, 0x96 => 0x8393, 0x97 => 0x8303, 0x98 => 0x82fb, + 0x99 => 0x82f9, 0x9a => 0x82de, 0x9b => 0x8306, 0x9c => 0x82dc, + 0x9d => 0x8309, 0x9e => 0x82d9, 0x9f => 0x8335, 0xa0 => 0x8334, + 0xa1 => 0x8316, 0xa2 => 0x8332, 0xa3 => 0x8331, 0xa4 => 0x8340, + 0xa5 => 0x8339, 0xa6 => 0x8350, 0xa7 => 0x8345, 0xa8 => 0x832f, + 0xa9 => 0x832b, 0xaa => 0x8317, 0xab => 0x8318, 0xac => 0x8385, + 0xad => 0x839a, 0xae => 0x83aa, 0xaf => 0x839f, 0xb0 => 0x83a2, + 0xb1 => 0x8396, 0xb2 => 0x8323, 0xb3 => 0x838e, 0xb4 => 0x8387, + 0xb5 => 0x838a, 0xb6 => 0x837c, 0xb7 => 0x83b5, 0xb8 => 0x8373, + 0xb9 => 0x8375, 0xba => 0x83a0, 0xbb => 0x8389, 0xbc => 0x83a8, + 0xbd => 0x83f4, 0xbe => 0x8413, 0xbf => 0x83eb, 0xc0 => 0x83ce, + 0xc1 => 0x83fd, 0xc2 => 0x8403, 0xc3 => 0x83d8, 0xc4 => 0x840b, + 0xc5 => 0x83c1, 0xc6 => 0x83f7, 0xc7 => 0x8407, 0xc8 => 0x83e0, + 0xc9 => 0x83f2, 0xca => 0x840d, 0xcb => 0x8422, 0xcc => 0x8420, + 0xcd => 0x83bd, 0xce => 0x8438, 0xcf => 0x8506, 0xd0 => 0x83fb, + 0xd1 => 0x846d, 0xd2 => 0x842a, 0xd3 => 0x843c, 0xd4 => 0x855a, + 0xd5 => 0x8484, 0xd6 => 0x8477, 0xd7 => 0x846b, 0xd8 => 0x84ad, + 0xd9 => 0x846e, 0xda => 0x8482, 0xdb => 0x8469, 0xdc => 0x8446, + 0xdd => 0x842c, 0xde => 0x846f, 0xdf => 0x8479, 0xe0 => 0x8435, + 0xe1 => 0x84ca, 0xe2 => 0x8462, 0xe3 => 0x84b9, 0xe4 => 0x84bf, + 0xe5 => 0x849f, 0xe6 => 0x84d9, 0xe7 => 0x84cd, 0xe8 => 0x84bb, + 0xe9 => 0x84da, 0xea => 0x84d0, 0xeb => 0x84c1, 0xec => 0x84c6, + 0xed => 0x84d6, 0xee => 0x84a1, 0xef => 0x8521, 0xf0 => 0x84ff, + 0xf1 => 0x84f4, 0xf2 => 0x8517, 0xf3 => 0x8518, 0xf4 => 0x852c, + 0xf5 => 0x851f, 0xf6 => 0x8515, 0xf7 => 0x8514, 0xf8 => 0x84fc, + 0xf9 => 0x8540, 0xfa => 0x8563, 0xfb => 0x8558, 0xfc => 0x8548, + }, + 0xe5 => { + 0x40 => 0x8541, 0x41 => 0x8602, 0x42 => 0x854b, 0x43 => 0x8555, + 0x44 => 0x8580, 0x45 => 0x85a4, 0x46 => 0x8588, 0x47 => 0x8591, + 0x48 => 0x858a, 0x49 => 0x85a8, 0x4a => 0x856d, 0x4b => 0x8594, + 0x4c => 0x859b, 0x4d => 0x85ea, 0x4e => 0x8587, 0x4f => 0x859c, + 0x50 => 0x8577, 0x51 => 0x857e, 0x52 => 0x8590, 0x53 => 0x85c9, + 0x54 => 0x85ba, 0x55 => 0x85cf, 0x56 => 0x85b9, 0x57 => 0x85d0, + 0x58 => 0x85d5, 0x59 => 0x85dd, 0x5a => 0x85e5, 0x5b => 0x85dc, + 0x5c => 0x85f9, 0x5d => 0x860a, 0x5e => 0x8613, 0x5f => 0x860b, + 0x60 => 0x85fe, 0x61 => 0x85fa, 0x62 => 0x8606, 0x63 => 0x8622, + 0x64 => 0x861a, 0x65 => 0x8630, 0x66 => 0x863f, 0x67 => 0x864d, + 0x68 => 0x4e55, 0x69 => 0x8654, 0x6a => 0x865f, 0x6b => 0x8667, + 0x6c => 0x8671, 0x6d => 0x8693, 0x6e => 0x86a3, 0x6f => 0x86a9, + 0x70 => 0x86aa, 0x71 => 0x868b, 0x72 => 0x868c, 0x73 => 0x86b6, + 0x74 => 0x86af, 0x75 => 0x86c4, 0x76 => 0x86c6, 0x77 => 0x86b0, + 0x78 => 0x86c9, 0x79 => 0x8823, 0x7a => 0x86ab, 0x7b => 0x86d4, + 0x7c => 0x86de, 0x7d => 0x86e9, 0x7e => 0x86ec, 0x80 => 0x86df, + 0x81 => 0x86db, 0x82 => 0x86ef, 0x83 => 0x8712, 0x84 => 0x8706, + 0x85 => 0x8708, 0x86 => 0x8700, 0x87 => 0x8703, 0x88 => 0x86fb, + 0x89 => 0x8711, 0x8a => 0x8709, 0x8b => 0x870d, 0x8c => 0x86f9, + 0x8d => 0x870a, 0x8e => 0x8734, 0x8f => 0x873f, 0x90 => 0x8737, + 0x91 => 0x873b, 0x92 => 0x8725, 0x93 => 0x8729, 0x94 => 0x871a, + 0x95 => 0x8760, 0x96 => 0x875f, 0x97 => 0x8778, 0x98 => 0x874c, + 0x99 => 0x874e, 0x9a => 0x8774, 0x9b => 0x8757, 0x9c => 0x8768, + 0x9d => 0x876e, 0x9e => 0x8759, 0x9f => 0x8753, 0xa0 => 0x8763, + 0xa1 => 0x876a, 0xa2 => 0x8805, 0xa3 => 0x87a2, 0xa4 => 0x879f, + 0xa5 => 0x8782, 0xa6 => 0x87af, 0xa7 => 0x87cb, 0xa8 => 0x87bd, + 0xa9 => 0x87c0, 0xaa => 0x87d0, 0xab => 0x96d6, 0xac => 0x87ab, + 0xad => 0x87c4, 0xae => 0x87b3, 0xaf => 0x87c7, 0xb0 => 0x87c6, + 0xb1 => 0x87bb, 0xb2 => 0x87ef, 0xb3 => 0x87f2, 0xb4 => 0x87e0, + 0xb5 => 0x880f, 0xb6 => 0x880d, 0xb7 => 0x87fe, 0xb8 => 0x87f6, + 0xb9 => 0x87f7, 0xba => 0x880e, 0xbb => 0x87d2, 0xbc => 0x8811, + 0xbd => 0x8816, 0xbe => 0x8815, 0xbf => 0x8822, 0xc0 => 0x8821, + 0xc1 => 0x8831, 0xc2 => 0x8836, 0xc3 => 0x8839, 0xc4 => 0x8827, + 0xc5 => 0x883b, 0xc6 => 0x8844, 0xc7 => 0x8842, 0xc8 => 0x8852, + 0xc9 => 0x8859, 0xca => 0x885e, 0xcb => 0x8862, 0xcc => 0x886b, + 0xcd => 0x8881, 0xce => 0x887e, 0xcf => 0x889e, 0xd0 => 0x8875, + 0xd1 => 0x887d, 0xd2 => 0x88b5, 0xd3 => 0x8872, 0xd4 => 0x8882, + 0xd5 => 0x8897, 0xd6 => 0x8892, 0xd7 => 0x88ae, 0xd8 => 0x8899, + 0xd9 => 0x88a2, 0xda => 0x888d, 0xdb => 0x88a4, 0xdc => 0x88b0, + 0xdd => 0x88bf, 0xde => 0x88b1, 0xdf => 0x88c3, 0xe0 => 0x88c4, + 0xe1 => 0x88d4, 0xe2 => 0x88d8, 0xe3 => 0x88d9, 0xe4 => 0x88dd, + 0xe5 => 0x88f9, 0xe6 => 0x8902, 0xe7 => 0x88fc, 0xe8 => 0x88f4, + 0xe9 => 0x88e8, 0xea => 0x88f2, 0xeb => 0x8904, 0xec => 0x890c, + 0xed => 0x890a, 0xee => 0x8913, 0xef => 0x8943, 0xf0 => 0x891e, + 0xf1 => 0x8925, 0xf2 => 0x892a, 0xf3 => 0x892b, 0xf4 => 0x8941, + 0xf5 => 0x8944, 0xf6 => 0x893b, 0xf7 => 0x8936, 0xf8 => 0x8938, + 0xf9 => 0x894c, 0xfa => 0x891d, 0xfb => 0x8960, 0xfc => 0x895e, + }, + 0xe6 => { + 0x40 => 0x8966, 0x41 => 0x8964, 0x42 => 0x896d, 0x43 => 0x896a, + 0x44 => 0x896f, 0x45 => 0x8974, 0x46 => 0x8977, 0x47 => 0x897e, + 0x48 => 0x8983, 0x49 => 0x8988, 0x4a => 0x898a, 0x4b => 0x8993, + 0x4c => 0x8998, 0x4d => 0x89a1, 0x4e => 0x89a9, 0x4f => 0x89a6, + 0x50 => 0x89ac, 0x51 => 0x89af, 0x52 => 0x89b2, 0x53 => 0x89ba, + 0x54 => 0x89bd, 0x55 => 0x89bf, 0x56 => 0x89c0, 0x57 => 0x89da, + 0x58 => 0x89dc, 0x59 => 0x89dd, 0x5a => 0x89e7, 0x5b => 0x89f4, + 0x5c => 0x89f8, 0x5d => 0x8a03, 0x5e => 0x8a16, 0x5f => 0x8a10, + 0x60 => 0x8a0c, 0x61 => 0x8a1b, 0x62 => 0x8a1d, 0x63 => 0x8a25, + 0x64 => 0x8a36, 0x65 => 0x8a41, 0x66 => 0x8a5b, 0x67 => 0x8a52, + 0x68 => 0x8a46, 0x69 => 0x8a48, 0x6a => 0x8a7c, 0x6b => 0x8a6d, + 0x6c => 0x8a6c, 0x6d => 0x8a62, 0x6e => 0x8a85, 0x6f => 0x8a82, + 0x70 => 0x8a84, 0x71 => 0x8aa8, 0x72 => 0x8aa1, 0x73 => 0x8a91, + 0x74 => 0x8aa5, 0x75 => 0x8aa6, 0x76 => 0x8a9a, 0x77 => 0x8aa3, + 0x78 => 0x8ac4, 0x79 => 0x8acd, 0x7a => 0x8ac2, 0x7b => 0x8ada, + 0x7c => 0x8aeb, 0x7d => 0x8af3, 0x7e => 0x8ae7, 0x80 => 0x8ae4, + 0x81 => 0x8af1, 0x82 => 0x8b14, 0x83 => 0x8ae0, 0x84 => 0x8ae2, + 0x85 => 0x8af7, 0x86 => 0x8ade, 0x87 => 0x8adb, 0x88 => 0x8b0c, + 0x89 => 0x8b07, 0x8a => 0x8b1a, 0x8b => 0x8ae1, 0x8c => 0x8b16, + 0x8d => 0x8b10, 0x8e => 0x8b17, 0x8f => 0x8b20, 0x90 => 0x8b33, + 0x91 => 0x97ab, 0x92 => 0x8b26, 0x93 => 0x8b2b, 0x94 => 0x8b3e, + 0x95 => 0x8b28, 0x96 => 0x8b41, 0x97 => 0x8b4c, 0x98 => 0x8b4f, + 0x99 => 0x8b4e, 0x9a => 0x8b49, 0x9b => 0x8b56, 0x9c => 0x8b5b, + 0x9d => 0x8b5a, 0x9e => 0x8b6b, 0x9f => 0x8b5f, 0xa0 => 0x8b6c, + 0xa1 => 0x8b6f, 0xa2 => 0x8b74, 0xa3 => 0x8b7d, 0xa4 => 0x8b80, + 0xa5 => 0x8b8c, 0xa6 => 0x8b8e, 0xa7 => 0x8b92, 0xa8 => 0x8b93, + 0xa9 => 0x8b96, 0xaa => 0x8b99, 0xab => 0x8b9a, 0xac => 0x8c3a, + 0xad => 0x8c41, 0xae => 0x8c3f, 0xaf => 0x8c48, 0xb0 => 0x8c4c, + 0xb1 => 0x8c4e, 0xb2 => 0x8c50, 0xb3 => 0x8c55, 0xb4 => 0x8c62, + 0xb5 => 0x8c6c, 0xb6 => 0x8c78, 0xb7 => 0x8c7a, 0xb8 => 0x8c82, + 0xb9 => 0x8c89, 0xba => 0x8c85, 0xbb => 0x8c8a, 0xbc => 0x8c8d, + 0xbd => 0x8c8e, 0xbe => 0x8c94, 0xbf => 0x8c7c, 0xc0 => 0x8c98, + 0xc1 => 0x621d, 0xc2 => 0x8cad, 0xc3 => 0x8caa, 0xc4 => 0x8cbd, + 0xc5 => 0x8cb2, 0xc6 => 0x8cb3, 0xc7 => 0x8cae, 0xc8 => 0x8cb6, + 0xc9 => 0x8cc8, 0xca => 0x8cc1, 0xcb => 0x8ce4, 0xcc => 0x8ce3, + 0xcd => 0x8cda, 0xce => 0x8cfd, 0xcf => 0x8cfa, 0xd0 => 0x8cfb, + 0xd1 => 0x8d04, 0xd2 => 0x8d05, 0xd3 => 0x8d0a, 0xd4 => 0x8d07, + 0xd5 => 0x8d0f, 0xd6 => 0x8d0d, 0xd7 => 0x8d10, 0xd8 => 0x9f4e, + 0xd9 => 0x8d13, 0xda => 0x8ccd, 0xdb => 0x8d14, 0xdc => 0x8d16, + 0xdd => 0x8d67, 0xde => 0x8d6d, 0xdf => 0x8d71, 0xe0 => 0x8d73, + 0xe1 => 0x8d81, 0xe2 => 0x8d99, 0xe3 => 0x8dc2, 0xe4 => 0x8dbe, + 0xe5 => 0x8dba, 0xe6 => 0x8dcf, 0xe7 => 0x8dda, 0xe8 => 0x8dd6, + 0xe9 => 0x8dcc, 0xea => 0x8ddb, 0xeb => 0x8dcb, 0xec => 0x8dea, + 0xed => 0x8deb, 0xee => 0x8ddf, 0xef => 0x8de3, 0xf0 => 0x8dfc, + 0xf1 => 0x8e08, 0xf2 => 0x8e09, 0xf3 => 0x8dff, 0xf4 => 0x8e1d, + 0xf5 => 0x8e1e, 0xf6 => 0x8e10, 0xf7 => 0x8e1f, 0xf8 => 0x8e42, + 0xf9 => 0x8e35, 0xfa => 0x8e30, 0xfb => 0x8e34, 0xfc => 0x8e4a, + }, + 0xe7 => { + 0x40 => 0x8e47, 0x41 => 0x8e49, 0x42 => 0x8e4c, 0x43 => 0x8e50, + 0x44 => 0x8e48, 0x45 => 0x8e59, 0x46 => 0x8e64, 0x47 => 0x8e60, + 0x48 => 0x8e2a, 0x49 => 0x8e63, 0x4a => 0x8e55, 0x4b => 0x8e76, + 0x4c => 0x8e72, 0x4d => 0x8e7c, 0x4e => 0x8e81, 0x4f => 0x8e87, + 0x50 => 0x8e85, 0x51 => 0x8e84, 0x52 => 0x8e8b, 0x53 => 0x8e8a, + 0x54 => 0x8e93, 0x55 => 0x8e91, 0x56 => 0x8e94, 0x57 => 0x8e99, + 0x58 => 0x8eaa, 0x59 => 0x8ea1, 0x5a => 0x8eac, 0x5b => 0x8eb0, + 0x5c => 0x8ec6, 0x5d => 0x8eb1, 0x5e => 0x8ebe, 0x5f => 0x8ec5, + 0x60 => 0x8ec8, 0x61 => 0x8ecb, 0x62 => 0x8edb, 0x63 => 0x8ee3, + 0x64 => 0x8efc, 0x65 => 0x8efb, 0x66 => 0x8eeb, 0x67 => 0x8efe, + 0x68 => 0x8f0a, 0x69 => 0x8f05, 0x6a => 0x8f15, 0x6b => 0x8f12, + 0x6c => 0x8f19, 0x6d => 0x8f13, 0x6e => 0x8f1c, 0x6f => 0x8f1f, + 0x70 => 0x8f1b, 0x71 => 0x8f0c, 0x72 => 0x8f26, 0x73 => 0x8f33, + 0x74 => 0x8f3b, 0x75 => 0x8f39, 0x76 => 0x8f45, 0x77 => 0x8f42, + 0x78 => 0x8f3e, 0x79 => 0x8f4c, 0x7a => 0x8f49, 0x7b => 0x8f46, + 0x7c => 0x8f4e, 0x7d => 0x8f57, 0x7e => 0x8f5c, 0x80 => 0x8f62, + 0x81 => 0x8f63, 0x82 => 0x8f64, 0x83 => 0x8f9c, 0x84 => 0x8f9f, + 0x85 => 0x8fa3, 0x86 => 0x8fad, 0x87 => 0x8faf, 0x88 => 0x8fb7, + 0x89 => 0x8fda, 0x8a => 0x8fe5, 0x8b => 0x8fe2, 0x8c => 0x8fea, + 0x8d => 0x8fef, 0x8e => 0x9087, 0x8f => 0x8ff4, 0x90 => 0x9005, + 0x91 => 0x8ff9, 0x92 => 0x8ffa, 0x93 => 0x9011, 0x94 => 0x9015, + 0x95 => 0x9021, 0x96 => 0x900d, 0x97 => 0x901e, 0x98 => 0x9016, + 0x99 => 0x900b, 0x9a => 0x9027, 0x9b => 0x9036, 0x9c => 0x9035, + 0x9d => 0x9039, 0x9e => 0x8ff8, 0x9f => 0x904f, 0xa0 => 0x9050, + 0xa1 => 0x9051, 0xa2 => 0x9052, 0xa3 => 0x900e, 0xa4 => 0x9049, + 0xa5 => 0x903e, 0xa6 => 0x9056, 0xa7 => 0x9058, 0xa8 => 0x905e, + 0xa9 => 0x9068, 0xaa => 0x906f, 0xab => 0x9076, 0xac => 0x96a8, + 0xad => 0x9072, 0xae => 0x9082, 0xaf => 0x907d, 0xb0 => 0x9081, + 0xb1 => 0x9080, 0xb2 => 0x908a, 0xb3 => 0x9089, 0xb4 => 0x908f, + 0xb5 => 0x90a8, 0xb6 => 0x90af, 0xb7 => 0x90b1, 0xb8 => 0x90b5, + 0xb9 => 0x90e2, 0xba => 0x90e4, 0xbb => 0x6248, 0xbc => 0x90db, + 0xbd => 0x9102, 0xbe => 0x9112, 0xbf => 0x9119, 0xc0 => 0x9132, + 0xc1 => 0x9130, 0xc2 => 0x914a, 0xc3 => 0x9156, 0xc4 => 0x9158, + 0xc5 => 0x9163, 0xc6 => 0x9165, 0xc7 => 0x9169, 0xc8 => 0x9173, + 0xc9 => 0x9172, 0xca => 0x918b, 0xcb => 0x9189, 0xcc => 0x9182, + 0xcd => 0x91a2, 0xce => 0x91ab, 0xcf => 0x91af, 0xd0 => 0x91aa, + 0xd1 => 0x91b5, 0xd2 => 0x91b4, 0xd3 => 0x91ba, 0xd4 => 0x91c0, + 0xd5 => 0x91c1, 0xd6 => 0x91c9, 0xd7 => 0x91cb, 0xd8 => 0x91d0, + 0xd9 => 0x91d6, 0xda => 0x91df, 0xdb => 0x91e1, 0xdc => 0x91db, + 0xdd => 0x91fc, 0xde => 0x91f5, 0xdf => 0x91f6, 0xe0 => 0x921e, + 0xe1 => 0x91ff, 0xe2 => 0x9214, 0xe3 => 0x922c, 0xe4 => 0x9215, + 0xe5 => 0x9211, 0xe6 => 0x925e, 0xe7 => 0x9257, 0xe8 => 0x9245, + 0xe9 => 0x9249, 0xea => 0x9264, 0xeb => 0x9248, 0xec => 0x9295, + 0xed => 0x923f, 0xee => 0x924b, 0xef => 0x9250, 0xf0 => 0x929c, + 0xf1 => 0x9296, 0xf2 => 0x9293, 0xf3 => 0x929b, 0xf4 => 0x925a, + 0xf5 => 0x92cf, 0xf6 => 0x92b9, 0xf7 => 0x92b7, 0xf8 => 0x92e9, + 0xf9 => 0x930f, 0xfa => 0x92fa, 0xfb => 0x9344, 0xfc => 0x932e, + }, + 0xe8 => { + 0x40 => 0x9319, 0x41 => 0x9322, 0x42 => 0x931a, 0x43 => 0x9323, + 0x44 => 0x933a, 0x45 => 0x9335, 0x46 => 0x933b, 0x47 => 0x935c, + 0x48 => 0x9360, 0x49 => 0x937c, 0x4a => 0x936e, 0x4b => 0x9356, + 0x4c => 0x93b0, 0x4d => 0x93ac, 0x4e => 0x93ad, 0x4f => 0x9394, + 0x50 => 0x93b9, 0x51 => 0x93d6, 0x52 => 0x93d7, 0x53 => 0x93e8, + 0x54 => 0x93e5, 0x55 => 0x93d8, 0x56 => 0x93c3, 0x57 => 0x93dd, + 0x58 => 0x93d0, 0x59 => 0x93c8, 0x5a => 0x93e4, 0x5b => 0x941a, + 0x5c => 0x9414, 0x5d => 0x9413, 0x5e => 0x9403, 0x5f => 0x9407, + 0x60 => 0x9410, 0x61 => 0x9436, 0x62 => 0x942b, 0x63 => 0x9435, + 0x64 => 0x9421, 0x65 => 0x943a, 0x66 => 0x9441, 0x67 => 0x9452, + 0x68 => 0x9444, 0x69 => 0x945b, 0x6a => 0x9460, 0x6b => 0x9462, + 0x6c => 0x945e, 0x6d => 0x946a, 0x6e => 0x9229, 0x6f => 0x9470, + 0x70 => 0x9475, 0x71 => 0x9477, 0x72 => 0x947d, 0x73 => 0x945a, + 0x74 => 0x947c, 0x75 => 0x947e, 0x76 => 0x9481, 0x77 => 0x947f, + 0x78 => 0x9582, 0x79 => 0x9587, 0x7a => 0x958a, 0x7b => 0x9594, + 0x7c => 0x9596, 0x7d => 0x9598, 0x7e => 0x9599, 0x80 => 0x95a0, + 0x81 => 0x95a8, 0x82 => 0x95a7, 0x83 => 0x95ad, 0x84 => 0x95bc, + 0x85 => 0x95bb, 0x86 => 0x95b9, 0x87 => 0x95be, 0x88 => 0x95ca, + 0x89 => 0x6ff6, 0x8a => 0x95c3, 0x8b => 0x95cd, 0x8c => 0x95cc, + 0x8d => 0x95d5, 0x8e => 0x95d4, 0x8f => 0x95d6, 0x90 => 0x95dc, + 0x91 => 0x95e1, 0x92 => 0x95e5, 0x93 => 0x95e2, 0x94 => 0x9621, + 0x95 => 0x9628, 0x96 => 0x962e, 0x97 => 0x962f, 0x98 => 0x9642, + 0x99 => 0x964c, 0x9a => 0x964f, 0x9b => 0x964b, 0x9c => 0x9677, + 0x9d => 0x965c, 0x9e => 0x965e, 0x9f => 0x965d, 0xa0 => 0x965f, + 0xa1 => 0x9666, 0xa2 => 0x9672, 0xa3 => 0x966c, 0xa4 => 0x968d, + 0xa5 => 0x9698, 0xa6 => 0x9695, 0xa7 => 0x9697, 0xa8 => 0x96aa, + 0xa9 => 0x96a7, 0xaa => 0x96b1, 0xab => 0x96b2, 0xac => 0x96b0, + 0xad => 0x96b4, 0xae => 0x96b6, 0xaf => 0x96b8, 0xb0 => 0x96b9, + 0xb1 => 0x96ce, 0xb2 => 0x96cb, 0xb3 => 0x96c9, 0xb4 => 0x96cd, + 0xb5 => 0x894d, 0xb6 => 0x96dc, 0xb7 => 0x970d, 0xb8 => 0x96d5, + 0xb9 => 0x96f9, 0xba => 0x9704, 0xbb => 0x9706, 0xbc => 0x9708, + 0xbd => 0x9713, 0xbe => 0x970e, 0xbf => 0x9711, 0xc0 => 0x970f, + 0xc1 => 0x9716, 0xc2 => 0x9719, 0xc3 => 0x9724, 0xc4 => 0x972a, + 0xc5 => 0x9730, 0xc6 => 0x9739, 0xc7 => 0x973d, 0xc8 => 0x973e, + 0xc9 => 0x9744, 0xca => 0x9746, 0xcb => 0x9748, 0xcc => 0x9742, + 0xcd => 0x9749, 0xce => 0x975c, 0xcf => 0x9760, 0xd0 => 0x9764, + 0xd1 => 0x9766, 0xd2 => 0x9768, 0xd3 => 0x52d2, 0xd4 => 0x976b, + 0xd5 => 0x9771, 0xd6 => 0x9779, 0xd7 => 0x9785, 0xd8 => 0x977c, + 0xd9 => 0x9781, 0xda => 0x977a, 0xdb => 0x9786, 0xdc => 0x978b, + 0xdd => 0x978f, 0xde => 0x9790, 0xdf => 0x979c, 0xe0 => 0x97a8, + 0xe1 => 0x97a6, 0xe2 => 0x97a3, 0xe3 => 0x97b3, 0xe4 => 0x97b4, + 0xe5 => 0x97c3, 0xe6 => 0x97c6, 0xe7 => 0x97c8, 0xe8 => 0x97cb, + 0xe9 => 0x97dc, 0xea => 0x97ed, 0xeb => 0x9f4f, 0xec => 0x97f2, + 0xed => 0x7adf, 0xee => 0x97f6, 0xef => 0x97f5, 0xf0 => 0x980f, + 0xf1 => 0x980c, 0xf2 => 0x9838, 0xf3 => 0x9824, 0xf4 => 0x9821, + 0xf5 => 0x9837, 0xf6 => 0x983d, 0xf7 => 0x9846, 0xf8 => 0x984f, + 0xf9 => 0x984b, 0xfa => 0x986b, 0xfb => 0x986f, 0xfc => 0x9870, + }, + 0xe9 => { + 0x40 => 0x9871, 0x41 => 0x9874, 0x42 => 0x9873, 0x43 => 0x98aa, + 0x44 => 0x98af, 0x45 => 0x98b1, 0x46 => 0x98b6, 0x47 => 0x98c4, + 0x48 => 0x98c3, 0x49 => 0x98c6, 0x4a => 0x98e9, 0x4b => 0x98eb, + 0x4c => 0x9903, 0x4d => 0x9909, 0x4e => 0x9912, 0x4f => 0x9914, + 0x50 => 0x9918, 0x51 => 0x9921, 0x52 => 0x991d, 0x53 => 0x991e, + 0x54 => 0x9924, 0x55 => 0x9920, 0x56 => 0x992c, 0x57 => 0x992e, + 0x58 => 0x993d, 0x59 => 0x993e, 0x5a => 0x9942, 0x5b => 0x9949, + 0x5c => 0x9945, 0x5d => 0x9950, 0x5e => 0x994b, 0x5f => 0x9951, + 0x60 => 0x9952, 0x61 => 0x994c, 0x62 => 0x9955, 0x63 => 0x9997, + 0x64 => 0x9998, 0x65 => 0x99a5, 0x66 => 0x99ad, 0x67 => 0x99ae, + 0x68 => 0x99bc, 0x69 => 0x99df, 0x6a => 0x99db, 0x6b => 0x99dd, + 0x6c => 0x99d8, 0x6d => 0x99d1, 0x6e => 0x99ed, 0x6f => 0x99ee, + 0x70 => 0x99f1, 0x71 => 0x99f2, 0x72 => 0x99fb, 0x73 => 0x99f8, + 0x74 => 0x9a01, 0x75 => 0x9a0f, 0x76 => 0x9a05, 0x77 => 0x99e2, + 0x78 => 0x9a19, 0x79 => 0x9a2b, 0x7a => 0x9a37, 0x7b => 0x9a45, + 0x7c => 0x9a42, 0x7d => 0x9a40, 0x7e => 0x9a43, 0x80 => 0x9a3e, + 0x81 => 0x9a55, 0x82 => 0x9a4d, 0x83 => 0x9a5b, 0x84 => 0x9a57, + 0x85 => 0x9a5f, 0x86 => 0x9a62, 0x87 => 0x9a65, 0x88 => 0x9a64, + 0x89 => 0x9a69, 0x8a => 0x9a6b, 0x8b => 0x9a6a, 0x8c => 0x9aad, + 0x8d => 0x9ab0, 0x8e => 0x9abc, 0x8f => 0x9ac0, 0x90 => 0x9acf, + 0x91 => 0x9ad1, 0x92 => 0x9ad3, 0x93 => 0x9ad4, 0x94 => 0x9ade, + 0x95 => 0x9adf, 0x96 => 0x9ae2, 0x97 => 0x9ae3, 0x98 => 0x9ae6, + 0x99 => 0x9aef, 0x9a => 0x9aeb, 0x9b => 0x9aee, 0x9c => 0x9af4, + 0x9d => 0x9af1, 0x9e => 0x9af7, 0x9f => 0x9afb, 0xa0 => 0x9b06, + 0xa1 => 0x9b18, 0xa2 => 0x9b1a, 0xa3 => 0x9b1f, 0xa4 => 0x9b22, + 0xa5 => 0x9b23, 0xa6 => 0x9b25, 0xa7 => 0x9b27, 0xa8 => 0x9b28, + 0xa9 => 0x9b29, 0xaa => 0x9b2a, 0xab => 0x9b2e, 0xac => 0x9b2f, + 0xad => 0x9b32, 0xae => 0x9b44, 0xaf => 0x9b43, 0xb0 => 0x9b4f, + 0xb1 => 0x9b4d, 0xb2 => 0x9b4e, 0xb3 => 0x9b51, 0xb4 => 0x9b58, + 0xb5 => 0x9b74, 0xb6 => 0x9b93, 0xb7 => 0x9b83, 0xb8 => 0x9b91, + 0xb9 => 0x9b96, 0xba => 0x9b97, 0xbb => 0x9b9f, 0xbc => 0x9ba0, + 0xbd => 0x9ba8, 0xbe => 0x9bb4, 0xbf => 0x9bc0, 0xc0 => 0x9bca, + 0xc1 => 0x9bb9, 0xc2 => 0x9bc6, 0xc3 => 0x9bcf, 0xc4 => 0x9bd1, + 0xc5 => 0x9bd2, 0xc6 => 0x9be3, 0xc7 => 0x9be2, 0xc8 => 0x9be4, + 0xc9 => 0x9bd4, 0xca => 0x9be1, 0xcb => 0x9c3a, 0xcc => 0x9bf2, + 0xcd => 0x9bf1, 0xce => 0x9bf0, 0xcf => 0x9c15, 0xd0 => 0x9c14, + 0xd1 => 0x9c09, 0xd2 => 0x9c13, 0xd3 => 0x9c0c, 0xd4 => 0x9c06, + 0xd5 => 0x9c08, 0xd6 => 0x9c12, 0xd7 => 0x9c0a, 0xd8 => 0x9c04, + 0xd9 => 0x9c2e, 0xda => 0x9c1b, 0xdb => 0x9c25, 0xdc => 0x9c24, + 0xdd => 0x9c21, 0xde => 0x9c30, 0xdf => 0x9c47, 0xe0 => 0x9c32, + 0xe1 => 0x9c46, 0xe2 => 0x9c3e, 0xe3 => 0x9c5a, 0xe4 => 0x9c60, + 0xe5 => 0x9c67, 0xe6 => 0x9c76, 0xe7 => 0x9c78, 0xe8 => 0x9ce7, + 0xe9 => 0x9cec, 0xea => 0x9cf0, 0xeb => 0x9d09, 0xec => 0x9d08, + 0xed => 0x9ceb, 0xee => 0x9d03, 0xef => 0x9d06, 0xf0 => 0x9d2a, + 0xf1 => 0x9d26, 0xf2 => 0x9daf, 0xf3 => 0x9d23, 0xf4 => 0x9d1f, + 0xf5 => 0x9d44, 0xf6 => 0x9d15, 0xf7 => 0x9d12, 0xf8 => 0x9d41, + 0xf9 => 0x9d3f, 0xfa => 0x9d3e, 0xfb => 0x9d46, 0xfc => 0x9d48, + }, + 0xea => { + 0x40 => 0x9d5d, 0x41 => 0x9d5e, 0x42 => 0x9d64, 0x43 => 0x9d51, + 0x44 => 0x9d50, 0x45 => 0x9d59, 0x46 => 0x9d72, 0x47 => 0x9d89, + 0x48 => 0x9d87, 0x49 => 0x9dab, 0x4a => 0x9d6f, 0x4b => 0x9d7a, + 0x4c => 0x9d9a, 0x4d => 0x9da4, 0x4e => 0x9da9, 0x4f => 0x9db2, + 0x50 => 0x9dc4, 0x51 => 0x9dc1, 0x52 => 0x9dbb, 0x53 => 0x9db8, + 0x54 => 0x9dba, 0x55 => 0x9dc6, 0x56 => 0x9dcf, 0x57 => 0x9dc2, + 0x58 => 0x9dd9, 0x59 => 0x9dd3, 0x5a => 0x9df8, 0x5b => 0x9de6, + 0x5c => 0x9ded, 0x5d => 0x9def, 0x5e => 0x9dfd, 0x5f => 0x9e1a, + 0x60 => 0x9e1b, 0x61 => 0x9e1e, 0x62 => 0x9e75, 0x63 => 0x9e79, + 0x64 => 0x9e7d, 0x65 => 0x9e81, 0x66 => 0x9e88, 0x67 => 0x9e8b, + 0x68 => 0x9e8c, 0x69 => 0x9e92, 0x6a => 0x9e95, 0x6b => 0x9e91, + 0x6c => 0x9e9d, 0x6d => 0x9ea5, 0x6e => 0x9ea9, 0x6f => 0x9eb8, + 0x70 => 0x9eaa, 0x71 => 0x9ead, 0x72 => 0x9761, 0x73 => 0x9ecc, + 0x74 => 0x9ece, 0x75 => 0x9ecf, 0x76 => 0x9ed0, 0x77 => 0x9ed4, + 0x78 => 0x9edc, 0x79 => 0x9ede, 0x7a => 0x9edd, 0x7b => 0x9ee0, + 0x7c => 0x9ee5, 0x7d => 0x9ee8, 0x7e => 0x9eef, 0x80 => 0x9ef4, + 0x81 => 0x9ef6, 0x82 => 0x9ef7, 0x83 => 0x9ef9, 0x84 => 0x9efb, + 0x85 => 0x9efc, 0x86 => 0x9efd, 0x87 => 0x9f07, 0x88 => 0x9f08, + 0x89 => 0x76b7, 0x8a => 0x9f15, 0x8b => 0x9f21, 0x8c => 0x9f2c, + 0x8d => 0x9f3e, 0x8e => 0x9f4a, 0x8f => 0x9f52, 0x90 => 0x9f54, + 0x91 => 0x9f63, 0x92 => 0x9f5f, 0x93 => 0x9f60, 0x94 => 0x9f61, + 0x95 => 0x9f66, 0x96 => 0x9f67, 0x97 => 0x9f6c, 0x98 => 0x9f6a, + 0x99 => 0x9f77, 0x9a => 0x9f72, 0x9b => 0x9f76, 0x9c => 0x9f95, + 0x9d => 0x9f9c, 0x9e => 0x9fa0, 0x9f => 0x582f, 0xa0 => 0x69c7, + 0xa1 => 0x9059, 0xa2 => 0x7464, 0xa3 => 0x51dc, 0xa4 => 0x7199, + }, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/Symbol.pm b/ExifTool/lib/Image/ExifTool/Charset/Symbol.pm new file mode 100644 index 0000000..cb240ef --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Symbol.pm @@ -0,0 +1,54 @@ +#------------------------------------------------------------------------------ +# File: Symbol.pm +# +# Description: Symbol to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://blogs.msdn.com/michkap/archive/2005/11/08/490495.aspx +# +# Notes: The table omits 1-byte characters with the same values as Unicode. +# This set re-maps characters with codepoints less than 0x80 +# (Although all bytes >= 0x20 should be mapped according to the +# reference, I didn't map chars below 0x80 because I have some +# samples where these are regular ASCII characters, even though +# I think the encoding is probably incorrect for these samples) +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Symbol = ( + 0x80 => 0xf080, 0x81 => 0xf081, 0x82 => 0xf082, 0x83 => 0xf083, + 0x84 => 0xf084, 0x85 => 0xf085, 0x86 => 0xf086, 0x87 => 0xf087, + 0x88 => 0xf088, 0x89 => 0xf089, 0x8a => 0xf08a, 0x8b => 0xf08b, + 0x8c => 0xf08c, 0x8d => 0xf08d, 0x8e => 0xf08e, 0x8f => 0xf08f, + 0x90 => 0xf090, 0x91 => 0xf091, 0x92 => 0xf092, 0x93 => 0xf093, + 0x94 => 0xf094, 0x95 => 0xf095, 0x96 => 0xf096, 0x97 => 0xf097, + 0x98 => 0xf098, 0x99 => 0xf099, 0x9a => 0xf09a, 0x9b => 0xf09b, + 0x9c => 0xf09c, 0x9d => 0xf09d, 0x9e => 0xf09e, 0x9f => 0xf09f, + 0xa0 => 0xf0a0, 0xa1 => 0xf0a1, 0xa2 => 0xf0a2, 0xa3 => 0xf0a3, + 0xa4 => 0xf0a4, 0xa5 => 0xf0a5, 0xa6 => 0xf0a6, 0xa7 => 0xf0a7, + 0xa8 => 0xf0a8, 0xa9 => 0xf0a9, 0xaa => 0xf0aa, 0xab => 0xf0ab, + 0xac => 0xf0ac, 0xad => 0xf0ad, 0xae => 0xf0ae, 0xaf => 0xf0af, + 0xb0 => 0xf0b0, 0xb1 => 0xf0b1, 0xb2 => 0xf0b2, 0xb3 => 0xf0b3, + 0xb4 => 0xf0b4, 0xb5 => 0xf0b5, 0xb6 => 0xf0b6, 0xb7 => 0xf0b7, + 0xb8 => 0xf0b8, 0xb9 => 0xf0b9, 0xba => 0xf0ba, 0xbb => 0xf0bb, + 0xbc => 0xf0bc, 0xbd => 0xf0bd, 0xbe => 0xf0be, 0xbf => 0xf0bf, + 0xc0 => 0xf0c0, 0xc1 => 0xf0c1, 0xc2 => 0xf0c2, 0xc3 => 0xf0c3, + 0xc4 => 0xf0c4, 0xc5 => 0xf0c5, 0xc6 => 0xf0c6, 0xc7 => 0xf0c7, + 0xc8 => 0xf0c8, 0xc9 => 0xf0c9, 0xca => 0xf0ca, 0xcb => 0xf0cb, + 0xcc => 0xf0cc, 0xcd => 0xf0cd, 0xce => 0xf0ce, 0xcf => 0xf0cf, + 0xd0 => 0xf0d0, 0xd1 => 0xf0d1, 0xd2 => 0xf0d2, 0xd3 => 0xf0d3, + 0xd4 => 0xf0d4, 0xd5 => 0xf0d5, 0xd6 => 0xf0d6, 0xd7 => 0xf0d7, + 0xd8 => 0xf0d8, 0xd9 => 0xf0d9, 0xda => 0xf0da, 0xdb => 0xf0db, + 0xdc => 0xf0dc, 0xdd => 0xf0dd, 0xde => 0xf0de, 0xdf => 0xf0df, + 0xe0 => 0xf0e0, 0xe1 => 0xf0e1, 0xe2 => 0xf0e2, 0xe3 => 0xf0e3, + 0xe4 => 0xf0e4, 0xe5 => 0xf0e5, 0xe6 => 0xf0e6, 0xe7 => 0xf0e7, + 0xe8 => 0xf0e8, 0xe9 => 0xf0e9, 0xea => 0xf0ea, 0xeb => 0xf0eb, + 0xec => 0xf0ec, 0xed => 0xf0ed, 0xee => 0xf0ee, 0xef => 0xf0ef, + 0xf0 => 0xf0f0, 0xf1 => 0xf0f1, 0xf2 => 0xf0f2, 0xf3 => 0xf0f3, + 0xf4 => 0xf0f4, 0xf5 => 0xf0f5, 0xf6 => 0xf0f6, 0xf7 => 0xf0f7, + 0xf8 => 0xf0f8, 0xf9 => 0xf0f9, 0xfa => 0xf0fa, 0xfb => 0xf0fb, + 0xfc => 0xf0fc, 0xfd => 0xf0fd, 0xfe => 0xf0fe, 0xff => 0xf0ff, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/Thai.pm b/ExifTool/lib/Image/ExifTool/Charset/Thai.pm new file mode 100644 index 0000000..068018e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Thai.pm @@ -0,0 +1,41 @@ +#------------------------------------------------------------------------------ +# File: Thai.pm +# +# Description: cp874 to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP874.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Thai = ( + 0x80 => 0x20ac, 0x85 => 0x2026, 0x91 => 0x2018, 0x92 => 0x2019, + 0x93 => 0x201c, 0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, + 0x97 => 0x2014, 0xa1 => 0x0e01, 0xa2 => 0x0e02, 0xa3 => 0x0e03, + 0xa4 => 0x0e04, 0xa5 => 0x0e05, 0xa6 => 0x0e06, 0xa7 => 0x0e07, + 0xa8 => 0x0e08, 0xa9 => 0x0e09, 0xaa => 0x0e0a, 0xab => 0x0e0b, + 0xac => 0x0e0c, 0xad => 0x0e0d, 0xae => 0x0e0e, 0xaf => 0x0e0f, + 0xb0 => 0x0e10, 0xb1 => 0x0e11, 0xb2 => 0x0e12, 0xb3 => 0x0e13, + 0xb4 => 0x0e14, 0xb5 => 0x0e15, 0xb6 => 0x0e16, 0xb7 => 0x0e17, + 0xb8 => 0x0e18, 0xb9 => 0x0e19, 0xba => 0x0e1a, 0xbb => 0x0e1b, + 0xbc => 0x0e1c, 0xbd => 0x0e1d, 0xbe => 0x0e1e, 0xbf => 0x0e1f, + 0xc0 => 0x0e20, 0xc1 => 0x0e21, 0xc2 => 0x0e22, 0xc3 => 0x0e23, + 0xc4 => 0x0e24, 0xc5 => 0x0e25, 0xc6 => 0x0e26, 0xc7 => 0x0e27, + 0xc8 => 0x0e28, 0xc9 => 0x0e29, 0xca => 0x0e2a, 0xcb => 0x0e2b, + 0xcc => 0x0e2c, 0xcd => 0x0e2d, 0xce => 0x0e2e, 0xcf => 0x0e2f, + 0xd0 => 0x0e30, 0xd1 => 0x0e31, 0xd2 => 0x0e32, 0xd3 => 0x0e33, + 0xd4 => 0x0e34, 0xd5 => 0x0e35, 0xd6 => 0x0e36, 0xd7 => 0x0e37, + 0xd8 => 0x0e38, 0xd9 => 0x0e39, 0xda => 0x0e3a, 0xdf => 0x0e3f, + 0xe0 => 0x0e40, 0xe1 => 0x0e41, 0xe2 => 0x0e42, 0xe3 => 0x0e43, + 0xe4 => 0x0e44, 0xe5 => 0x0e45, 0xe6 => 0x0e46, 0xe7 => 0x0e47, + 0xe8 => 0x0e48, 0xe9 => 0x0e49, 0xea => 0x0e4a, 0xeb => 0x0e4b, + 0xec => 0x0e4c, 0xed => 0x0e4d, 0xee => 0x0e4e, 0xef => 0x0e4f, + 0xf0 => 0x0e50, 0xf1 => 0x0e51, 0xf2 => 0x0e52, 0xf3 => 0x0e53, + 0xf4 => 0x0e54, 0xf5 => 0x0e55, 0xf6 => 0x0e56, 0xf7 => 0x0e57, + 0xf8 => 0x0e58, 0xf9 => 0x0e59, 0xfa => 0x0e5a, 0xfb => 0x0e5b, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/Turkish.pm b/ExifTool/lib/Image/ExifTool/Charset/Turkish.pm new file mode 100644 index 0000000..7f0a784 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Turkish.pm @@ -0,0 +1,25 @@ +#------------------------------------------------------------------------------ +# File: Turkish.pm +# +# Description: cp1254 to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1254.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Turkish = ( + 0x80 => 0x20ac, 0x82 => 0x201a, 0x83 => 0x0192, 0x84 => 0x201e, + 0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02c6, + 0x89 => 0x2030, 0x8a => 0x0160, 0x8b => 0x2039, 0x8c => 0x0152, + 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c, 0x94 => 0x201d, + 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014, 0x98 => 0x02dc, + 0x99 => 0x2122, 0x9a => 0x0161, 0x9b => 0x203a, 0x9c => 0x0153, + 0x9f => 0x0178, 0xd0 => 0x011e, 0xdd => 0x0130, 0xde => 0x015e, + 0xf0 => 0x011f, 0xfd => 0x0131, 0xfe => 0x015f, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/Charset/Vietnam.pm b/ExifTool/lib/Image/ExifTool/Charset/Vietnam.pm new file mode 100644 index 0000000..6275ea5 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Charset/Vietnam.pm @@ -0,0 +1,27 @@ +#------------------------------------------------------------------------------ +# File: Vietnam.pm +# +# Description: cp1258 to Unicode +# +# Revisions: 2010/01/20 - P. Harvey created +# +# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1258.TXT +# +# Notes: The table omits 1-byte characters with the same values as Unicode +#------------------------------------------------------------------------------ +use strict; + +%Image::ExifTool::Charset::Vietnam = ( + 0x80 => 0x20ac, 0x82 => 0x201a, 0x83 => 0x0192, 0x84 => 0x201e, + 0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02c6, + 0x89 => 0x2030, 0x8b => 0x2039, 0x8c => 0x0152, 0x91 => 0x2018, + 0x92 => 0x2019, 0x93 => 0x201c, 0x94 => 0x201d, 0x95 => 0x2022, + 0x96 => 0x2013, 0x97 => 0x2014, 0x98 => 0x02dc, 0x99 => 0x2122, + 0x9b => 0x203a, 0x9c => 0x0153, 0x9f => 0x0178, 0xc3 => 0x0102, + 0xcc => 0x0300, 0xd0 => 0x0110, 0xd2 => 0x0309, 0xd5 => 0x01a0, + 0xdd => 0x01af, 0xde => 0x0303, 0xe3 => 0x0103, 0xec => 0x0301, + 0xf0 => 0x0111, 0xf2 => 0x0323, 0xf5 => 0x01a1, 0xfd => 0x01b0, + 0xfe => 0x20ab, +); + +1; # end diff --git a/ExifTool/lib/Image/ExifTool/DICOM.pm b/ExifTool/lib/Image/ExifTool/DICOM.pm new file mode 100644 index 0000000..973cc7a --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/DICOM.pm @@ -0,0 +1,3881 @@ +#------------------------------------------------------------------------------ +# File: DICOM.pm +# +# Description: Read DICOM and ACR-NEMA medical images +# +# Revisions: 2005/11/09 - P. Harvey Created +# 2009/11/19 - P. Harvey Added private GE tags from ref 4 +# 2009/12/11 - P. Harvey Updated to DICOM 2008 spec +# 2010/04/08 - P. Harvey Updated to DICOM 2009 spec +# +# References: 1) http://medical.nema.org/ +# 2) http://www.sph.sc.edu/comd/rorden/dicom.html +# 3) http://www.dclunie.com/ +# 4) http://www.gehealthcare.com/usen/interoperability/dicom/docs/2258357r3.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::DICOM; + +use strict; +use vars qw($VERSION %uid); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.22'; + +# DICOM VR (Value Representation) format conversions +my %dicomFormat = ( + FD => 'double', + FL => 'float', + OB => 'int8u', + OF => 'float', + OW => 'int16u', + SL => 'int32s', + SS => 'int16s', + UL => 'int32u', + US => 'int16u', +); + +# VR elements with 32-bit length in explicit VR syntax +my %vr32 = ( OB=>1, OW=>1, OF=>1, SQ=>1, UT=>1, UN=>1 ); + +# data elements with implicit VR regardless of syntax +my %implicitVR = ( + 'FFFE,E000' => 1, + 'FFFE,E00D' => 1, + 'FFFE,E0DD' => 1, +); + +# DICOM tags +# Note: "XxxGroupLength" tags are generated automatically if they don't exist +%Image::ExifTool::DICOM::Main = ( + GROUPS => { 2 => 'Image' }, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup (way too many!) + NOTES => q{ + The DICOM format is based on the ACR-NEMA specification, but adds a file + header and a number of new tags. ExifTool will extract information from + either type of file. The Tag ID's in the following table are the tag group + and element numbers in hexadecimal, as given in the DICOM specification (see + L<http://medical.nema.org/>). The table below contains tags from the DICOM + 2009 and earlier specifications plus some vendor-specific private tags. + + Note that DICOM information may be saved in other file formats using the + L<XMP DICOM Tags|Image::ExifTool::TagNames/XMP DICOM Tags>. + }, + # file meta information group (names end with VR) + '0002,0000' => { VR => 'UL', Name => 'FileMetaInfoGroupLength' }, + '0002,0001' => { VR => 'OB', Name => 'FileMetaInfoVersion' }, + '0002,0002' => { VR => 'UI', Name => 'MediaStorageSOPClassUID' }, + '0002,0003' => { VR => 'UI', Name => 'MediaStorageSOPInstanceUID' }, + '0002,0010' => { VR => 'UI', Name => 'TransferSyntaxUID' }, + '0002,0012' => { VR => 'UI', Name => 'ImplementationClassUID' }, + '0002,0013' => { VR => 'SH', Name => 'ImplementationVersionName' }, + '0002,0016' => { VR => 'AE', Name => 'SourceApplicationEntityTitle' }, + '0002,0100' => { VR => 'UI', Name => 'PrivateInformationCreatorUID' }, + '0002,0102' => { VR => 'OB', Name => 'PrivateInformation' }, + # directory structuring group + '0004,1130' => { VR => 'CS', Name => 'FileSetID' }, + '0004,1141' => { VR => 'CS', Name => 'FileSetDescriptorFileID' }, + '0004,1142' => { VR => 'CS', Name => 'SpecificCharacterSetOfFile' }, + '0004,1200' => { VR => 'UL', Name => 'FirstDirectoryRecordOffset' }, + '0004,1202' => { VR => 'UL', Name => 'LastDirectoryRecordOffset' }, + '0004,1212' => { VR => 'US', Name => 'FileSetConsistencyFlag' }, + '0004,1220' => { VR => 'SQ', Name => 'DirectoryRecordSequence' }, + '0004,1400' => { VR => 'UL', Name => 'OffsetOfNextDirectoryRecord' }, + '0004,1410' => { VR => 'US', Name => 'RecordInUseFlag' }, + '0004,1420' => { VR => 'UL', Name => 'LowerLevelDirectoryEntityOffset' }, + '0004,1430' => { VR => 'CS', Name => 'DirectoryRecordType' }, + '0004,1432' => { VR => 'UI', Name => 'PrivateRecordUID' }, + '0004,1500' => { VR => 'CS', Name => 'ReferencedFileID' }, + '0004,1504' => { VR => 'UL', Name => 'MRDRDirectoryRecordOffset' }, + '0004,1510' => { VR => 'UI', Name => 'ReferencedSOPClassUIDInFile' }, + '0004,1511' => { VR => 'UI', Name => 'ReferencedSOPInstanceUIDInFile' }, + '0004,1512' => { VR => 'UI', Name => 'ReferencedTransferSyntaxUIDInFile' }, + '0004,151A' => { VR => 'UI', Name => 'ReferencedRelatedSOPClassUIDInFile' }, + '0004,1600' => { VR => 'UL', Name => 'NumberOfReferences' }, + # identifying group + '0008,0000' => { VR => 'UL', Name => 'IdentifyingGroupLength' }, + '0008,0001' => { VR => 'UL', Name => 'LengthToEnd' }, + '0008,0005' => { VR => 'CS', Name => 'SpecificCharacterSet' }, + '0008,0006' => { VR => 'SQ', Name => 'LanguageCodeSequence' }, + '0008,0008' => { VR => 'CS', Name => 'ImageType' }, + '0008,0010' => { VR => 'CS', Name => 'RecognitionCode' }, + '0008,0012' => { VR => 'DA', Name => 'InstanceCreationDate' }, + '0008,0013' => { VR => 'TM', Name => 'InstanceCreationTime' }, + '0008,0014' => { VR => 'UI', Name => 'InstanceCreatorUID' }, + '0008,0016' => { VR => 'UI', Name => 'SOPClassUID' }, + '0008,0018' => { VR => 'UI', Name => 'SOPInstanceUID' }, + '0008,001A' => { VR => 'UI', Name => 'RelatedGeneralSOPClassUID' }, + '0008,001B' => { VR => 'UI', Name => 'OriginalSpecializedSOPClassUID' }, + '0008,0020' => { VR => 'DA', Name => 'StudyDate' }, + '0008,0021' => { VR => 'DA', Name => 'SeriesDate' }, + '0008,0022' => { VR => 'DA', Name => 'AcquisitionDate' }, + '0008,0023' => { VR => 'DA', Name => 'ContentDate' }, + '0008,0024' => { VR => 'DA', Name => 'OverlayDate' }, + '0008,0025' => { VR => 'DA', Name => 'CurveDate' }, + '0008,002A' => { VR => 'DT', Name => 'AcquisitionDateTime' }, + '0008,0030' => { VR => 'TM', Name => 'StudyTime' }, + '0008,0031' => { VR => 'TM', Name => 'SeriesTime' }, + '0008,0032' => { VR => 'TM', Name => 'AcquisitionTime' }, + '0008,0033' => { VR => 'TM', Name => 'ContentTime' }, + '0008,0034' => { VR => 'TM', Name => 'OverlayTime' }, + '0008,0035' => { VR => 'TM', Name => 'CurveTime' }, + '0008,0040' => { VR => 'US', Name => 'DataSetType' }, + '0008,0041' => { VR => 'LO', Name => 'DataSetSubtype' }, + '0008,0042' => { VR => 'CS', Name => 'NuclearMedicineSeriesType' }, + '0008,0050' => { VR => 'SH', Name => 'AccessionNumber' }, + '0008,0052' => { VR => 'CS', Name => 'QueryRetrieveLevel' }, + '0008,0054' => { VR => 'AE', Name => 'RetrieveAETitle' }, + '0008,0056' => { VR => 'CS', Name => 'InstanceAvailability' }, + '0008,0058' => { VR => 'UI', Name => 'FailedSOPInstanceUIDList' }, + '0008,0060' => { VR => 'CS', Name => 'Modality' }, + '0008,0061' => { VR => 'CS', Name => 'ModalitiesInStudy' }, + '0008,0062' => { VR => 'UI', Name => 'SOPClassesInStudy' }, + '0008,0064' => { VR => 'CS', Name => 'ConversionType' }, + '0008,0068' => { VR => 'CS', Name => 'PresentationIntentType' }, + '0008,0070' => { VR => 'LO', Name => 'Manufacturer' }, + '0008,0080' => { VR => 'LO', Name => 'InstitutionName' }, + '0008,0081' => { VR => 'ST', Name => 'InstitutionAddress' }, + '0008,0082' => { VR => 'SQ', Name => 'InstitutionCodeSequence' }, + '0008,0090' => { VR => 'PN', Name => 'ReferringPhysicianName' }, + '0008,0092' => { VR => 'ST', Name => 'ReferringPhysicianAddress' }, + '0008,0094' => { VR => 'SH', Name => 'ReferringPhysicianTelephoneNumber' }, + '0008,0096' => { VR => 'SQ', Name => 'ReferringPhysicianIDSequence' }, + '0008,0100' => { VR => 'SH', Name => 'CodeValue' }, + '0008,0102' => { VR => 'SH', Name => 'CodingSchemeDesignator' }, + '0008,0103' => { VR => 'SH', Name => 'CodingSchemeVersion' }, + '0008,0104' => { VR => 'LO', Name => 'CodeMeaning' }, + '0008,0105' => { VR => 'CS', Name => 'MappingResource' }, + '0008,0106' => { VR => 'DT', Name => 'ContextGroupVersion' }, + '0008,0107' => { VR => 'DT', Name => 'ContextGroupLocalVersion' }, + '0008,010B' => { VR => 'CS', Name => 'ContextGroupExtensionFlag' }, + '0008,010C' => { VR => 'UI', Name => 'CodingSchemeUID' }, + '0008,010D' => { VR => 'UI', Name => 'ContextGroupExtensionCreatorUID' }, + '0008,010F' => { VR => 'CS', Name => 'ContextIdentifier' }, + '0008,0110' => { VR => 'SQ', Name => 'CodingSchemeIDSequence' }, + '0008,0112' => { VR => 'LO', Name => 'CodingSchemeRegistry' }, + '0008,0114' => { VR => 'ST', Name => 'CodingSchemeExternalID' }, + '0008,0115' => { VR => 'ST', Name => 'CodingSchemeName' }, + '0008,0116' => { VR => 'ST', Name => 'ResponsibleOrganization' }, + '0008,0117' => { VR => 'UI', Name => 'ContextUID' }, + '0008,0201' => { VR => 'SH', Name => 'TimezoneOffsetFromUTC' }, + '0008,1000' => { VR => 'AE', Name => 'NetworkID' }, + '0008,1010' => { VR => 'SH', Name => 'StationName' }, + '0008,1030' => { VR => 'LO', Name => 'StudyDescription' }, + '0008,1032' => { VR => 'SQ', Name => 'ProcedureCodeSequence' }, + '0008,103E' => { VR => 'LO', Name => 'SeriesDescription' }, + '0008,1040' => { VR => 'LO', Name => 'InstitutionalDepartmentName' }, + '0008,1048' => { VR => 'PN', Name => 'PhysiciansOfRecord' }, + '0008,1049' => { VR => 'SQ', Name => 'PhysiciansOfRecordIDSequence' }, + '0008,1050' => { VR => 'PN', Name => 'PerformingPhysicianName' }, + '0008,1052' => { VR => 'SQ', Name => 'PerformingPhysicianIDSequence' }, + '0008,1060' => { VR => 'PN', Name => 'NameOfPhysicianReadingStudy' }, + '0008,1062' => { VR => 'SQ', Name => 'PhysicianReadingStudyIDSequence' }, + '0008,1070' => { VR => 'PN', Name => 'OperatorsName' }, + '0008,1072' => { VR => 'SQ', Name => 'OperatorIDSequence' }, + '0008,1080' => { VR => 'LO', Name => 'AdmittingDiagnosesDescription' }, + '0008,1084' => { VR => 'SQ', Name => 'AdmittingDiagnosesCodeSequence' }, + '0008,1090' => { VR => 'LO', Name => 'ManufacturersModelName' }, + '0008,1100' => { VR => 'SQ', Name => 'ReferencedResultsSequence' }, + '0008,1110' => { VR => 'SQ', Name => 'ReferencedStudySequence' }, + '0008,1111' => { VR => 'SQ', Name => 'ReferencedProcedureStepSequence' }, + '0008,1115' => { VR => 'SQ', Name => 'ReferencedSeriesSequence' }, + '0008,1120' => { VR => 'SQ', Name => 'ReferencedPatientSequence' }, + '0008,1125' => { VR => 'SQ', Name => 'ReferencedVisitSequence' }, + '0008,1130' => { VR => 'SQ', Name => 'ReferencedOverlaySequence' }, + '0008,113A' => { VR => 'SQ', Name => 'ReferencedWaveformSequence' }, + '0008,1140' => { VR => 'SQ', Name => 'ReferencedImageSequence' }, + '0008,1145' => { VR => 'SQ', Name => 'ReferencedCurveSequence' }, + '0008,114A' => { VR => 'SQ', Name => 'ReferencedInstanceSequence' }, + '0008,1150' => { VR => 'UI', Name => 'ReferencedSOPClassUID' }, + '0008,1155' => { VR => 'UI', Name => 'ReferencedSOPInstanceUID' }, + '0008,115A' => { VR => 'UI', Name => 'SOPClassesSupported' }, + '0008,1160' => { VR => 'IS', Name => 'ReferencedFrameNumber' }, + '0008,1161' => { VR => 'UL', Name => 'SimpleFrameList' }, + '0008,1162' => { VR => 'UL', Name => 'CalculatedFrameList' }, + '0008,1163' => { VR => 'FD', Name => 'TimeRange' }, + '0008,1164' => { VR => 'SQ', Name => 'FrameExtractionSequence' }, + '0008,1195' => { VR => 'UI', Name => 'TransactionUID' }, + '0008,1197' => { VR => 'US', Name => 'FailureReason' }, + '0008,1198' => { VR => 'SQ', Name => 'FailedSOPSequence' }, + '0008,1199' => { VR => 'SQ', Name => 'ReferencedSOPSequence' }, + '0008,1200' => { VR => 'SQ', Name => 'OtherReferencedStudiesSequence' }, + '0008,1250' => { VR => 'SQ', Name => 'RelatedSeriesSequence' }, + '0008,2110' => { VR => 'CS', Name => 'LossyImageCompression' }, + '0008,2111' => { VR => 'ST', Name => 'DerivationDescription' }, + '0008,2112' => { VR => 'SQ', Name => 'SourceImageSequence' }, + '0008,2120' => { VR => 'SH', Name => 'StageName' }, + '0008,2122' => { VR => 'IS', Name => 'StageNumber' }, + '0008,2124' => { VR => 'IS', Name => 'NumberOfStages' }, + '0008,2127' => { VR => 'SH', Name => 'ViewName' }, + '0008,2128' => { VR => 'IS', Name => 'ViewNumber' }, + '0008,2129' => { VR => 'IS', Name => 'NumberOfEventTimers' }, + '0008,212A' => { VR => 'IS', Name => 'NumberOfViewsInStage' }, + '0008,2130' => { VR => 'DS', Name => 'EventElapsedTimes' }, + '0008,2132' => { VR => 'LO', Name => 'EventTimerNames' }, + '0008,2133' => { VR => 'SQ', Name => 'EventTimerSequence' }, + '0008,2134' => { VR => 'FD', Name => 'EventTimeOffset' }, + '0008,2135' => { VR => 'SQ', Name => 'EventCodeSequence' }, + '0008,2142' => { VR => 'IS', Name => 'StartTrim' }, + '0008,2143' => { VR => 'IS', Name => 'StopTrim' }, + '0008,2144' => { VR => 'IS', Name => 'RecommendedDisplayFrameRate' }, + '0008,2200' => { VR => 'CS', Name => 'TransducerPosition' }, + '0008,2204' => { VR => 'CS', Name => 'TransducerOrientation' }, + '0008,2208' => { VR => 'CS', Name => 'AnatomicStructure' }, + '0008,2218' => { VR => 'SQ', Name => 'AnatomicRegionSequence' }, + '0008,2220' => { VR => 'SQ', Name => 'AnatomicRegionModifierSequence' }, + '0008,2228' => { VR => 'SQ', Name => 'PrimaryAnatomicStructureSequence' }, + '0008,2229' => { VR => 'SQ', Name => 'AnatomicStructureOrRegionSequence' }, + '0008,2230' => { VR => 'SQ', Name => 'AnatomicStructureModifierSequence' }, + '0008,2240' => { VR => 'SQ', Name => 'TransducerPositionSequence' }, + '0008,2242' => { VR => 'SQ', Name => 'TransducerPositionModifierSequence' }, + '0008,2244' => { VR => 'SQ', Name => 'TransducerOrientationSequence' }, + '0008,2246' => { VR => 'SQ', Name => 'TransducerOrientationModifierSeq' }, + '0008,2253' => { VR => 'SQ', Name => 'AnatomicEntrancePortalCodeSeqTrial' }, + '0008,2255' => { VR => 'SQ', Name => 'AnatomicApproachDirCodeSeqTrial' }, + '0008,2256' => { VR => 'ST', Name => 'AnatomicPerspectiveDescrTrial' }, + '0008,2257' => { VR => 'SQ', Name => 'AnatomicPerspectiveCodeSeqTrial' }, + '0008,3001' => { VR => 'SQ', Name => 'AlternateRepresentationSequence' }, + '0008,3010' => { VR => 'UI', Name => 'IrradiationEventUID' }, + '0008,4000' => { VR => 'LT', Name => 'IdentifyingComments' }, + '0008,9007' => { VR => 'CS', Name => 'FrameType' }, + '0008,9092' => { VR => 'SQ', Name => 'ReferencedImageEvidenceSequence' }, + '0008,9121' => { VR => 'SQ', Name => 'ReferencedRawDataSequence' }, + '0008,9123' => { VR => 'UI', Name => 'CreatorVersionUID' }, + '0008,9124' => { VR => 'SQ', Name => 'DerivationImageSequence' }, + '0008,9154' => { VR => 'SQ', Name => 'SourceImageEvidenceSequence' }, + '0008,9205' => { VR => 'CS', Name => 'PixelPresentation' }, + '0008,9206' => { VR => 'CS', Name => 'VolumetricProperties' }, + '0008,9207' => { VR => 'CS', Name => 'VolumeBasedCalculationTechnique' }, + '0008,9208' => { VR => 'CS', Name => 'ComplexImageComponent' }, + '0008,9209' => { VR => 'CS', Name => 'AcquisitionContrast' }, + '0008,9215' => { VR => 'SQ', Name => 'DerivationCodeSequence' }, + '0008,9237' => { VR => 'SQ', Name => 'GrayscalePresentationStateSequence' }, + '0008,9410' => { VR => 'SQ', Name => 'ReferencedOtherPlaneSequence' }, + '0008,9458' => { VR => 'SQ', Name => 'FrameDisplaySequence' }, + '0008,9459' => { VR => 'FL', Name => 'RecommendedDisplayFrameRateInFloat' }, + '0008,9460' => { VR => 'CS', Name => 'SkipFrameRangeFlag' }, + # GEMS_IDEN_01 (ref 4) + '0009,1001' => { VR => 'LO', Name => 'FullFidelity' }, + '0009,1002' => { VR => 'SH', Name => 'SuiteID' }, + '0009,1004' => { VR => 'SH', Name => 'ProductID' }, + '0009,1027' => { VR => 'SL', Name => 'ImageActualDate' }, + '0009,1030' => { VR => 'SH', Name => 'ServiceID' }, + '0009,1031' => { VR => 'SH', Name => 'MobileLocationNumber' }, + '0009,10E3' => { VR => 'UI', Name => 'EquipmentUID' }, + '0009,10E6' => { VR => 'SH', Name => 'GenesisVersionNow' }, + '0009,10E7' => { VR => 'UL', Name => 'ExamRecordChecksum' }, + '0009,10E9' => { VR => 'SL', Name => 'ActualSeriesDataTimeStamp' }, + # patient group + '0010,0000' => { VR => 'UL', Name => 'PatientGroupLength' }, + '0010,0010' => { VR => 'PN', Name => 'PatientName' }, + '0010,0020' => { VR => 'LO', Name => 'PatientID' }, + '0010,0021' => { VR => 'LO', Name => 'IssuerOfPatientID' }, + '0010,0022' => { VR => 'CS', Name => 'TypeOfPatientID' }, + '0010,0030' => { VR => 'DA', Name => 'PatientBirthDate' }, + '0010,0032' => { VR => 'TM', Name => 'PatientBirthTime' }, + '0010,0040' => { VR => 'CS', Name => 'PatientSex' }, + '0010,0050' => { VR => 'SQ', Name => 'PatientInsurancePlanCodeSequence' }, + '0010,0101' => { VR => 'SQ', Name => 'PatientPrimaryLanguageCodeSeq' }, + '0010,0102' => { VR => 'SQ', Name => 'PatientPrimaryLanguageCodeModSeq' }, + '0010,1000' => { VR => 'LO', Name => 'OtherPatientIDs' }, + '0010,1001' => { VR => 'PN', Name => 'OtherPatientNames' }, + '0010,1002' => { VR => 'SQ', Name => 'OtherPatientIDsSequence' }, + '0010,1005' => { VR => 'PN', Name => 'PatientBirthName' }, + '0010,1010' => { VR => 'AS', Name => 'PatientAge' }, + '0010,1020' => { VR => 'DS', Name => 'PatientSize' }, + '0010,1030' => { VR => 'DS', Name => 'PatientWeight' }, + '0010,1040' => { VR => 'LO', Name => 'PatientAddress' }, + '0010,1050' => { VR => 'LO', Name => 'InsurancePlanIdentification' }, + '0010,1060' => { VR => 'PN', Name => 'PatientMotherBirthName' }, + '0010,1080' => { VR => 'LO', Name => 'MilitaryRank' }, + '0010,1081' => { VR => 'LO', Name => 'BranchOfService' }, + '0010,1090' => { VR => 'LO', Name => 'MedicalRecordLocator' }, + '0010,2000' => { VR => 'LO', Name => 'MedicalAlerts' }, + '0010,2110' => { VR => 'LO', Name => 'Allergies' }, + '0010,2150' => { VR => 'LO', Name => 'CountryOfResidence' }, + '0010,2152' => { VR => 'LO', Name => 'RegionOfResidence' }, + '0010,2154' => { VR => 'SH', Name => 'PatientTelephoneNumbers' }, + '0010,2160' => { VR => 'SH', Name => 'EthnicGroup' }, + '0010,2180' => { VR => 'SH', Name => 'Occupation' }, + '0010,21A0' => { VR => 'CS', Name => 'SmokingStatus' }, + '0010,21B0' => { VR => 'LT', Name => 'AdditionalPatientHistory' }, + '0010,21C0' => { VR => 'US', Name => 'PregnancyStatus' }, + '0010,21D0' => { VR => 'DA', Name => 'LastMenstrualDate' }, + '0010,21F0' => { VR => 'LO', Name => 'PatientReligiousPreference' }, + '0010,2201' => { VR => 'LO', Name => 'PatientSpeciesDescription' }, + '0010,2202' => { VR => 'SQ', Name => 'PatientSpeciesCodeSequence' }, + '0010,2203' => { VR => 'CS', Name => 'PatientSexNeutered' }, + '0010,2210' => { VR => 'CS', Name => 'AnatomicalOrientationType' }, + '0010,2292' => { VR => 'LO', Name => 'PatientBreedDescription' }, + '0010,2293' => { VR => 'SQ', Name => 'PatientBreedCodeSequence' }, + '0010,2294' => { VR => 'SQ', Name => 'BreedRegistrationSequence' }, + '0010,2295' => { VR => 'LO', Name => 'BreedRegistrationNumber' }, + '0010,2296' => { VR => 'SQ', Name => 'BreedRegistryCodeSequence' }, + '0010,2297' => { VR => 'PN', Name => 'ResponsiblePerson' }, + '0010,2298' => { VR => 'CS', Name => 'ResponsiblePersonRole' }, + '0010,2299' => { VR => 'LO', Name => 'ResponsibleOrganization' }, + '0010,4000' => { VR => 'LT', Name => 'PatientComments' }, + '0010,9431' => { VR => 'FL', Name => 'ExaminedBodyThickness' }, + # GEMS_PATI_01 (ref 4) + '0011,1010' => { VR => 'SS', Name => 'PatientStatus' }, + # clinical trial group + '0012,0010' => { VR => 'LO', Name => 'ClinicalTrialSponsorName' }, + '0012,0020' => { VR => 'LO', Name => 'ClinicalTrialProtocolID' }, + '0012,0021' => { VR => 'LO', Name => 'ClinicalTrialProtocolName' }, + '0012,0030' => { VR => 'LO', Name => 'ClinicalTrialSiteID' }, + '0012,0031' => { VR => 'LO', Name => 'ClinicalTrialSiteName' }, + '0012,0040' => { VR => 'LO', Name => 'ClinicalTrialSubjectID' }, + '0012,0042' => { VR => 'LO', Name => 'ClinicalTrialSubjectReadingID' }, + '0012,0050' => { VR => 'LO', Name => 'ClinicalTrialTimePointID' }, + '0012,0051' => { VR => 'ST', Name => 'ClinicalTrialTimePointDescription' }, + '0012,0060' => { VR => 'LO', Name => 'ClinicalTrialCoordinatingCenter' }, + '0012,0062' => { VR => 'CS', Name => 'PatientIdentityRemoved' }, + '0012,0063' => { VR => 'LO', Name => 'DeidentificationMethod' }, + '0012,0064' => { VR => 'SQ', Name => 'DeidentificationMethodCodeSequence' }, + '0012,0071' => { VR => 'LO', Name => 'ClinicalTrialSeriesID' }, + '0012,0072' => { VR => 'LO', Name => 'ClinicalTrialSeriesDescription' }, + '0012,0084' => { VR => 'CS', Name => 'DistributionType' }, + '0012,0085' => { VR => 'CS', Name => 'ConsentForDistributionFlag' }, + # acquisition group + '0018,0000' => { VR => 'UL', Name => 'AcquisitionGroupLength' }, + '0018,0010' => { VR => 'LO', Name => 'ContrastBolusAgent' }, + '0018,0012' => { VR => 'SQ', Name => 'ContrastBolusAgentSequence' }, + '0018,0014' => { VR => 'SQ', Name => 'ContrastBolusAdministrationRoute' }, + '0018,0015' => { VR => 'CS', Name => 'BodyPartExamined' }, + '0018,0020' => { VR => 'CS', Name => 'ScanningSequence' }, + '0018,0021' => { VR => 'CS', Name => 'SequenceVariant' }, + '0018,0022' => { VR => 'CS', Name => 'ScanOptions' }, + '0018,0023' => { VR => 'CS', Name => 'MRAcquisitionType' }, + '0018,0024' => { VR => 'SH', Name => 'SequenceName' }, + '0018,0025' => { VR => 'CS', Name => 'AngioFlag' }, + '0018,0026' => { VR => 'SQ', Name => 'InterventionDrugInformationSeq' }, + '0018,0027' => { VR => 'TM', Name => 'InterventionDrugStopTime' }, + '0018,0028' => { VR => 'DS', Name => 'InterventionDrugDose' }, + '0018,0029' => { VR => 'SQ', Name => 'InterventionDrugSequence' }, + '0018,002A' => { VR => 'SQ', Name => 'AdditionalDrugSequence' }, + '0018,0030' => { VR => 'LO', Name => 'Radionuclide' }, + '0018,0031' => { VR => 'LO', Name => 'Radiopharmaceutical' }, + '0018,0032' => { VR => 'DS', Name => 'EnergyWindowCenterline' }, + '0018,0033' => { VR => 'DS', Name => 'EnergyWindowTotalWidth' }, + '0018,0034' => { VR => 'LO', Name => 'InterventionDrugName' }, + '0018,0035' => { VR => 'TM', Name => 'InterventionDrugStartTime' }, + '0018,0036' => { VR => 'SQ', Name => 'InterventionSequence' }, + '0018,0037' => { VR => 'CS', Name => 'TherapyType' }, + '0018,0038' => { VR => 'CS', Name => 'InterventionStatus' }, + '0018,0039' => { VR => 'CS', Name => 'TherapyDescription' }, + '0018,003A' => { VR => 'ST', Name => 'InterventionDescription' }, + '0018,0040' => { VR => 'IS', Name => 'CineRate' }, + '0018,0042' => { VR => 'CS', Name => 'InitialCineRunState' }, + '0018,0050' => { VR => 'DS', Name => 'SliceThickness' }, + '0018,0060' => { VR => 'DS', Name => 'KVP' }, + '0018,0070' => { VR => 'IS', Name => 'CountsAccumulated' }, + '0018,0071' => { VR => 'CS', Name => 'AcquisitionTerminationCondition' }, + '0018,0072' => { VR => 'DS', Name => 'EffectiveDuration' }, + '0018,0073' => { VR => 'CS', Name => 'AcquisitionStartCondition' }, + '0018,0074' => { VR => 'IS', Name => 'AcquisitionStartConditionData' }, + '0018,0075' => { VR => 'IS', Name => 'AcquisitionEndConditionData' }, + '0018,0080' => { VR => 'DS', Name => 'RepetitionTime' }, + '0018,0081' => { VR => 'DS', Name => 'EchoTime' }, + '0018,0082' => { VR => 'DS', Name => 'InversionTime' }, + '0018,0083' => { VR => 'DS', Name => 'NumberOfAverages' }, + '0018,0084' => { VR => 'DS', Name => 'ImagingFrequency' }, + '0018,0085' => { VR => 'SH', Name => 'ImagedNucleus' }, + '0018,0086' => { VR => 'IS', Name => 'EchoNumber' }, + '0018,0087' => { VR => 'DS', Name => 'MagneticFieldStrength' }, + '0018,0088' => { VR => 'DS', Name => 'SpacingBetweenSlices' }, + '0018,0089' => { VR => 'IS', Name => 'NumberOfPhaseEncodingSteps' }, + '0018,0090' => { VR => 'DS', Name => 'DataCollectionDiameter' }, + '0018,0091' => { VR => 'IS', Name => 'EchoTrainLength' }, + '0018,0093' => { VR => 'DS', Name => 'PercentSampling' }, + '0018,0094' => { VR => 'DS', Name => 'PercentPhaseFieldOfView' }, + '0018,0095' => { VR => 'DS', Name => 'PixelBandwidth' }, + '0018,1000' => { VR => 'LO', Name => 'DeviceSerialNumber' }, + '0018,1002' => { VR => 'UI', Name => 'DeviceUID' }, + '0018,1003' => { VR => 'LO', Name => 'DeviceID' }, + '0018,1004' => { VR => 'LO', Name => 'PlateID' }, + '0018,1005' => { VR => 'LO', Name => 'GeneratorID' }, + '0018,1006' => { VR => 'LO', Name => 'GridID' }, + '0018,1007' => { VR => 'LO', Name => 'CassetteID' }, + '0018,1008' => { VR => 'LO', Name => 'GantryID' }, + '0018,1010' => { VR => 'LO', Name => 'SecondaryCaptureDeviceID' }, + '0018,1011' => { VR => 'LO', Name => 'HardcopyCreationDeviceID' }, + '0018,1012' => { VR => 'DA', Name => 'DateOfSecondaryCapture' }, + '0018,1014' => { VR => 'TM', Name => 'TimeOfSecondaryCapture' }, + '0018,1016' => { VR => 'LO', Name => 'SecondaryCaptureDeviceManufacturer' }, + '0018,1017' => { VR => 'LO', Name => 'HardcopyDeviceManufacturer' }, + '0018,1018' => { VR => 'LO', Name => 'SecondaryCaptureDeviceModelName' }, + '0018,1019' => { VR => 'LO', Name => 'SecondaryCaptureDeviceSoftwareVers' }, + '0018,101A' => { VR => 'LO', Name => 'HardcopyDeviceSoftwareVersion' }, + '0018,101B' => { VR => 'LO', Name => 'HardcopyDeviceModelName' }, + '0018,1020' => { VR => 'LO', Name => 'SoftwareVersion' }, + '0018,1022' => { VR => 'SH', Name => 'VideoImageFormatAcquired' }, + '0018,1023' => { VR => 'LO', Name => 'DigitalImageFormatAcquired' }, + '0018,1030' => { VR => 'LO', Name => 'ProtocolName' }, + '0018,1040' => { VR => 'LO', Name => 'ContrastBolusRoute' }, + '0018,1041' => { VR => 'DS', Name => 'ContrastBolusVolume' }, + '0018,1042' => { VR => 'TM', Name => 'ContrastBolusStartTime' }, + '0018,1043' => { VR => 'TM', Name => 'ContrastBolusStopTime' }, + '0018,1044' => { VR => 'DS', Name => 'ContrastBolusTotalDose' }, + '0018,1045' => { VR => 'IS', Name => 'SyringeCounts' }, + '0018,1046' => { VR => 'DS', Name => 'ContrastFlowRate' }, + '0018,1047' => { VR => 'DS', Name => 'ContrastFlowDuration' }, + '0018,1048' => { VR => 'CS', Name => 'ContrastBolusIngredient' }, + '0018,1049' => { VR => 'DS', Name => 'ContrastBolusConcentration' }, + '0018,1050' => { VR => 'DS', Name => 'SpatialResolution' }, + '0018,1060' => { VR => 'DS', Name => 'TriggerTime' }, + '0018,1061' => { VR => 'LO', Name => 'TriggerSourceOrType' }, + '0018,1062' => { VR => 'IS', Name => 'NominalInterval' }, + '0018,1063' => { VR => 'DS', Name => 'FrameTime' }, + '0018,1064' => { VR => 'LO', Name => 'CardiacFramingType' }, + '0018,1065' => { VR => 'DS', Name => 'FrameTimeVector' }, + '0018,1066' => { VR => 'DS', Name => 'FrameDelay' }, + '0018,1067' => { VR => 'DS', Name => 'ImageTriggerDelay' }, + '0018,1068' => { VR => 'DS', Name => 'MultiplexGroupTimeOffset' }, + '0018,1069' => { VR => 'DS', Name => 'TriggerTimeOffset' }, + '0018,106A' => { VR => 'CS', Name => 'SynchronizationTrigger' }, + '0018,106C' => { VR => 'US', Name => 'SynchronizationChannel' }, + '0018,106E' => { VR => 'UL', Name => 'TriggerSamplePosition' }, + '0018,1070' => { VR => 'LO', Name => 'RadiopharmaceuticalRoute' }, + '0018,1071' => { VR => 'DS', Name => 'RadiopharmaceuticalVolume' }, + '0018,1072' => { VR => 'TM', Name => 'RadiopharmaceuticalStartTime' }, + '0018,1073' => { VR => 'TM', Name => 'RadiopharmaceuticalStopTime' }, + '0018,1074' => { VR => 'DS', Name => 'RadionuclideTotalDose' }, + '0018,1075' => { VR => 'DS', Name => 'RadionuclideHalfLife' }, + '0018,1076' => { VR => 'DS', Name => 'RadionuclidePositronFraction' }, + '0018,1077' => { VR => 'DS', Name => 'RadiopharmaceuticalSpecActivity' }, + '0018,1078' => { VR => 'DT', Name => 'RadiopharmaceuticalStartDateTime' }, + '0018,1079' => { VR => 'DT', Name => 'RadiopharmaceuticalStopDateTime' }, + '0018,1080' => { VR => 'CS', Name => 'BeatRejectionFlag' }, + '0018,1081' => { VR => 'IS', Name => 'LowRRValue' }, + '0018,1082' => { VR => 'IS', Name => 'HighRRValue' }, + '0018,1083' => { VR => 'IS', Name => 'IntervalsAcquired' }, + '0018,1084' => { VR => 'IS', Name => 'IntervalsRejected' }, + '0018,1085' => { VR => 'LO', Name => 'PVCRejection' }, + '0018,1086' => { VR => 'IS', Name => 'SkipBeats' }, + '0018,1088' => { VR => 'IS', Name => 'HeartRate' }, + '0018,1090' => { VR => 'IS', Name => 'CardiacNumberOfImages' }, + '0018,1094' => { VR => 'IS', Name => 'TriggerWindow' }, + '0018,1100' => { VR => 'DS', Name => 'ReconstructionDiameter' }, + '0018,1110' => { VR => 'DS', Name => 'DistanceSourceToDetector' }, + '0018,1111' => { VR => 'DS', Name => 'DistanceSourceToPatient' }, + '0018,1114' => { VR => 'DS', Name => 'EstimatedRadiographicMagnification' }, + '0018,1120' => { VR => 'DS', Name => 'GantryDetectorTilt' }, + '0018,1121' => { VR => 'DS', Name => 'GantryDetectorSlew' }, + '0018,1130' => { VR => 'DS', Name => 'TableHeight' }, + '0018,1131' => { VR => 'DS', Name => 'TableTraverse' }, + '0018,1134' => { VR => 'CS', Name => 'TableMotion' }, + '0018,1135' => { VR => 'DS', Name => 'TableVerticalIncrement' }, + '0018,1136' => { VR => 'DS', Name => 'TableLateralIncrement' }, + '0018,1137' => { VR => 'DS', Name => 'TableLongitudinalIncrement' }, + '0018,1138' => { VR => 'DS', Name => 'TableAngle' }, + '0018,113A' => { VR => 'CS', Name => 'TableType' }, + '0018,1140' => { VR => 'CS', Name => 'RotationDirection' }, + '0018,1141' => { VR => 'DS', Name => 'AngularPosition' }, + '0018,1142' => { VR => 'DS', Name => 'RadialPosition' }, + '0018,1143' => { VR => 'DS', Name => 'ScanArc' }, + '0018,1144' => { VR => 'DS', Name => 'AngularStep' }, + '0018,1145' => { VR => 'DS', Name => 'CenterOfRotationOffset' }, + '0018,1146' => { VR => 'DS', Name => 'RotationOffset' }, + '0018,1147' => { VR => 'CS', Name => 'FieldOfViewShape' }, + '0018,1149' => { VR => 'IS', Name => 'FieldOfViewDimensions' }, + '0018,1150' => { VR => 'IS', Name => 'ExposureTime' }, + '0018,1151' => { VR => 'IS', Name => 'XRayTubeCurrent' }, + '0018,1152' => { VR => 'IS', Name => 'Exposure' }, + '0018,1153' => { VR => 'IS', Name => 'ExposureInMicroAmpSec' }, + '0018,1154' => { VR => 'DS', Name => 'AveragePulseWidth' }, + '0018,1155' => { VR => 'CS', Name => 'RadiationSetting' }, + '0018,1156' => { VR => 'CS', Name => 'RectificationType' }, + '0018,115A' => { VR => 'CS', Name => 'RadiationMode' }, + '0018,115E' => { VR => 'DS', Name => 'ImageAreaDoseProduct' }, + '0018,1160' => { VR => 'SH', Name => 'FilterType' }, + '0018,1161' => { VR => 'LO', Name => 'TypeOfFilters' }, + '0018,1162' => { VR => 'DS', Name => 'IntensifierSize' }, + '0018,1164' => { VR => 'DS', Name => 'ImagerPixelSpacing' }, + '0018,1166' => { VR => 'CS', Name => 'Grid' }, + '0018,1170' => { VR => 'IS', Name => 'GeneratorPower' }, + '0018,1180' => { VR => 'SH', Name => 'CollimatorGridName' }, + '0018,1181' => { VR => 'CS', Name => 'CollimatorType' }, + '0018,1182' => { VR => 'IS', Name => 'FocalDistance' }, + '0018,1183' => { VR => 'DS', Name => 'XFocusCenter' }, + '0018,1184' => { VR => 'DS', Name => 'YFocusCenter' }, + '0018,1190' => { VR => 'DS', Name => 'FocalSpots' }, + '0018,1191' => { VR => 'CS', Name => 'AnodeTargetMaterial' }, + '0018,11A0' => { VR => 'DS', Name => 'BodyPartThickness' }, + '0018,11A2' => { VR => 'DS', Name => 'CompressionForce' }, + '0018,1200' => { VR => 'DA', Name => 'DateOfLastCalibration' }, + '0018,1201' => { VR => 'TM', Name => 'TimeOfLastCalibration' }, + '0018,1210' => { VR => 'SH', Name => 'ConvolutionKernel' }, + '0018,1240' => { VR => 'IS', Name => 'UpperLowerPixelValues' }, + '0018,1242' => { VR => 'IS', Name => 'ActualFrameDuration' }, + '0018,1243' => { VR => 'IS', Name => 'CountRate' }, + '0018,1244' => { VR => 'US', Name => 'PreferredPlaybackSequencing' }, + '0018,1250' => { VR => 'SH', Name => 'ReceiveCoilName' }, + '0018,1251' => { VR => 'SH', Name => 'TransmitCoilName' }, + '0018,1260' => { VR => 'SH', Name => 'PlateType' }, + '0018,1261' => { VR => 'LO', Name => 'PhosphorType' }, + '0018,1300' => { VR => 'DS', Name => 'ScanVelocity' }, + '0018,1301' => { VR => 'CS', Name => 'WholeBodyTechnique' }, + '0018,1302' => { VR => 'IS', Name => 'ScanLength' }, + '0018,1310' => { VR => 'US', Name => 'AcquisitionMatrix' }, + '0018,1312' => { VR => 'CS', Name => 'InPlanePhaseEncodingDirection' }, + '0018,1314' => { VR => 'DS', Name => 'FlipAngle' }, + '0018,1315' => { VR => 'CS', Name => 'VariableFlipAngleFlag' }, + '0018,1316' => { VR => 'DS', Name => 'SAR' }, + '0018,1318' => { VR => 'DS', Name => 'DB-Dt' }, + '0018,1400' => { VR => 'LO', Name => 'AcquisitionDeviceProcessingDescr' }, + '0018,1401' => { VR => 'LO', Name => 'AcquisitionDeviceProcessingCode' }, + '0018,1402' => { VR => 'CS', Name => 'CassetteOrientation' }, + '0018,1403' => { VR => 'CS', Name => 'CassetteSize' }, + '0018,1404' => { VR => 'US', Name => 'ExposuresOnPlate' }, + '0018,1405' => { VR => 'IS', Name => 'RelativeXRayExposure' }, + '0018,1450' => { VR => 'DS', Name => 'ColumnAngulation' }, + '0018,1460' => { VR => 'DS', Name => 'TomoLayerHeight' }, + '0018,1470' => { VR => 'DS', Name => 'TomoAngle' }, + '0018,1480' => { VR => 'DS', Name => 'TomoTime' }, + '0018,1490' => { VR => 'CS', Name => 'TomoType' }, + '0018,1491' => { VR => 'CS', Name => 'TomoClass' }, + '0018,1495' => { VR => 'IS', Name => 'NumberOfTomosynthesisSourceImages' }, + '0018,1500' => { VR => 'CS', Name => 'PositionerMotion' }, + '0018,1508' => { VR => 'CS', Name => 'PositionerType' }, + '0018,1510' => { VR => 'DS', Name => 'PositionerPrimaryAngle' }, + '0018,1511' => { VR => 'DS', Name => 'PositionerSecondaryAngle' }, + '0018,1520' => { VR => 'DS', Name => 'PositionerPrimaryAngleIncrement' }, + '0018,1521' => { VR => 'DS', Name => 'PositionerSecondaryAngleIncrement' }, + '0018,1530' => { VR => 'DS', Name => 'DetectorPrimaryAngle' }, + '0018,1531' => { VR => 'DS', Name => 'DetectorSecondaryAngle' }, + '0018,1600' => { VR => 'CS', Name => 'ShutterShape' }, + '0018,1602' => { VR => 'IS', Name => 'ShutterLeftVerticalEdge' }, + '0018,1604' => { VR => 'IS', Name => 'ShutterRightVerticalEdge' }, + '0018,1606' => { VR => 'IS', Name => 'ShutterUpperHorizontalEdge' }, + '0018,1608' => { VR => 'IS', Name => 'ShutterLowerHorizontalEdge' }, + '0018,1610' => { VR => 'IS', Name => 'CenterOfCircularShutter' }, + '0018,1612' => { VR => 'IS', Name => 'RadiusOfCircularShutter' }, + '0018,1620' => { VR => 'IS', Name => 'VerticesOfPolygonalShutter' }, + '0018,1622' => { VR => 'US', Name => 'ShutterPresentationValue' }, + '0018,1623' => { VR => 'US', Name => 'ShutterOverlayGroup' }, + '0018,1624' => { VR => 'US', Name => 'ShutterPresentationColorCIELabVal' }, + '0018,1700' => { VR => 'CS', Name => 'CollimatorShape' }, + '0018,1702' => { VR => 'IS', Name => 'CollimatorLeftVerticalEdge' }, + '0018,1704' => { VR => 'IS', Name => 'CollimatorRightVerticalEdge' }, + '0018,1706' => { VR => 'IS', Name => 'CollimatorUpperHorizontalEdge' }, + '0018,1708' => { VR => 'IS', Name => 'CollimatorLowerHorizontalEdge' }, + '0018,1710' => { VR => 'IS', Name => 'CenterOfCircularCollimator' }, + '0018,1712' => { VR => 'IS', Name => 'RadiusOfCircularCollimator' }, + '0018,1720' => { VR => 'IS', Name => 'VerticesOfPolygonalCollimator' }, + '0018,1800' => { VR => 'CS', Name => 'AcquisitionTimeSynchronized' }, + '0018,1801' => { VR => 'SH', Name => 'TimeSource' }, + '0018,1802' => { VR => 'CS', Name => 'TimeDistributionProtocol' }, + '0018,1803' => { VR => 'LO', Name => 'NTPSourceAddress' }, + '0018,2001' => { VR => 'IS', Name => 'PageNumberVector' }, + '0018,2002' => { VR => 'SH', Name => 'FrameLabelVector' }, + '0018,2003' => { VR => 'DS', Name => 'FramePrimaryAngleVector' }, + '0018,2004' => { VR => 'DS', Name => 'FrameSecondaryAngleVector' }, + '0018,2005' => { VR => 'DS', Name => 'SliceLocationVector' }, + '0018,2006' => { VR => 'SH', Name => 'DisplayWindowLabelVector' }, + '0018,2010' => { VR => 'DS', Name => 'NominalScannedPixelSpacing' }, + '0018,2020' => { VR => 'CS', Name => 'DigitizingDeviceTransportDirection' }, + '0018,2030' => { VR => 'DS', Name => 'RotationOfScannedFilm' }, + '0018,3100' => { VR => 'CS', Name => 'IVUSAcquisition' }, + '0018,3101' => { VR => 'DS', Name => 'IVUSPullbackRate' }, + '0018,3102' => { VR => 'DS', Name => 'IVUSGatedRate' }, + '0018,3103' => { VR => 'IS', Name => 'IVUSPullbackStartFrameNumber' }, + '0018,3104' => { VR => 'IS', Name => 'IVUSPullbackStopFrameNumber' }, + '0018,3105' => { VR => 'IS', Name => 'LesionNumber' }, + '0018,4000' => { VR => 'LT', Name => 'AcquisitionComments' }, + '0018,5000' => { VR => 'SH', Name => 'OutputPower' }, + '0018,5010' => { VR => 'LO', Name => 'TransducerData' }, + '0018,5012' => { VR => 'DS', Name => 'FocusDepth' }, + '0018,5020' => { VR => 'LO', Name => 'ProcessingFunction' }, + '0018,5021' => { VR => 'LO', Name => 'PostprocessingFunction' }, + '0018,5022' => { VR => 'DS', Name => 'MechanicalIndex' }, + '0018,5024' => { VR => 'DS', Name => 'BoneThermalIndex' }, + '0018,5026' => { VR => 'DS', Name => 'CranialThermalIndex' }, + '0018,5027' => { VR => 'DS', Name => 'SoftTissueThermalIndex' }, + '0018,5028' => { VR => 'DS', Name => 'SoftTissueFocusThermalIndex' }, + '0018,5029' => { VR => 'DS', Name => 'SoftTissueSurfaceThermalIndex' }, + '0018,5030' => { VR => 'DS', Name => 'DynamicRange' }, + '0018,5040' => { VR => 'DS', Name => 'TotalGain' }, + '0018,5050' => { VR => 'IS', Name => 'DepthOfScanField' }, + '0018,5100' => { VR => 'CS', Name => 'PatientPosition' }, + '0018,5101' => { VR => 'CS', Name => 'ViewPosition' }, + '0018,5104' => { VR => 'SQ', Name => 'ProjectionEponymousNameCodeSeq' }, + '0018,5210' => { VR => 'DS', Name => 'ImageTransformationMatrix' }, + '0018,5212' => { VR => 'DS', Name => 'ImageTranslationVector' }, + '0018,6000' => { VR => 'DS', Name => 'Sensitivity' }, + '0018,6011' => { VR => 'SQ', Name => 'SequenceOfUltrasoundRegions' }, + '0018,6012' => { VR => 'US', Name => 'RegionSpatialFormat' }, + '0018,6014' => { VR => 'US', Name => 'RegionDataType' }, + '0018,6016' => { VR => 'UL', Name => 'RegionFlags' }, + '0018,6018' => { VR => 'UL', Name => 'RegionLocationMinX0' }, + '0018,601A' => { VR => 'UL', Name => 'RegionLocationMinY0' }, + '0018,601C' => { VR => 'UL', Name => 'RegionLocationMaxX1' }, + '0018,601E' => { VR => 'UL', Name => 'RegionLocationMaxY1' }, + '0018,6020' => { VR => 'SL', Name => 'ReferencePixelX0' }, + '0018,6022' => { VR => 'SL', Name => 'ReferencePixelY0' }, + '0018,6024' => { VR => 'US', Name => 'PhysicalUnitsXDirection' }, + '0018,6026' => { VR => 'US', Name => 'PhysicalUnitsYDirection' }, + '0018,6028' => { VR => 'FD', Name => 'ReferencePixelPhysicalValueX' }, + '0018,602A' => { VR => 'FD', Name => 'ReferencePixelPhysicalValueY' }, + '0018,602C' => { VR => 'FD', Name => 'PhysicalDeltaX' }, + '0018,602E' => { VR => 'FD', Name => 'PhysicalDeltaY' }, + '0018,6030' => { VR => 'UL', Name => 'TransducerFrequency' }, + '0018,6031' => { VR => 'CS', Name => 'TransducerType' }, + '0018,6032' => { VR => 'UL', Name => 'PulseRepetitionFrequency' }, + '0018,6034' => { VR => 'FD', Name => 'DopplerCorrectionAngle' }, + '0018,6036' => { VR => 'FD', Name => 'SteeringAngle' }, + '0018,6038' => { VR => 'UL', Name => 'DopplerSampleVolumeXPosRetired' }, + '0018,6039' => { VR => 'SL', Name => 'DopplerSampleVolumeXPosition' }, + '0018,603A' => { VR => 'UL', Name => 'DopplerSampleVolumeYPosRetired' }, + '0018,603B' => { VR => 'SL', Name => 'DopplerSampleVolumeYPosition' }, + '0018,603C' => { VR => 'UL', Name => 'TMLinePositionX0Retired' }, + '0018,603D' => { VR => 'SL', Name => 'TMLinePositionX0' }, + '0018,603E' => { VR => 'UL', Name => 'TMLinePositionY0Retired' }, + '0018,603F' => { VR => 'SL', Name => 'TMLinePositionY0' }, + '0018,6040' => { VR => 'UL', Name => 'TMLinePositionX1Retired' }, + '0018,6041' => { VR => 'SL', Name => 'TMLinePositionX1' }, + '0018,6042' => { VR => 'UL', Name => 'TMLinePositionY1Retired' }, + '0018,6043' => { VR => 'SL', Name => 'TMLinePositionY1' }, + '0018,6044' => { VR => 'US', Name => 'PixelComponentOrganization' }, + '0018,6046' => { VR => 'UL', Name => 'PixelComponentMask' }, + '0018,6048' => { VR => 'UL', Name => 'PixelComponentRangeStart' }, + '0018,604A' => { VR => 'UL', Name => 'PixelComponentRangeStop' }, + '0018,604C' => { VR => 'US', Name => 'PixelComponentPhysicalUnits' }, + '0018,604E' => { VR => 'US', Name => 'PixelComponentDataType' }, + '0018,6050' => { VR => 'UL', Name => 'NumberOfTableBreakPoints' }, + '0018,6052' => { VR => 'UL', Name => 'TableOfXBreakPoints' }, + '0018,6054' => { VR => 'FD', Name => 'TableOfYBreakPoints' }, + '0018,6056' => { VR => 'UL', Name => 'NumberOfTableEntries' }, + '0018,6058' => { VR => 'UL', Name => 'TableOfPixelValues' }, + '0018,605A' => { VR => 'FL', Name => 'TableOfParameterValues' }, + '0018,6060' => { VR => 'FL', Name => 'RWaveTimeVector' }, + '0018,7000' => { VR => 'CS', Name => 'DetectorConditionsNominalFlag' }, + '0018,7001' => { VR => 'DS', Name => 'DetectorTemperature' }, + '0018,7004' => { VR => 'CS', Name => 'DetectorType' }, + '0018,7005' => { VR => 'CS', Name => 'DetectorConfiguration' }, + '0018,7006' => { VR => 'LT', Name => 'DetectorDescription' }, + '0018,7008' => { VR => 'LT', Name => 'DetectorMode' }, + '0018,700A' => { VR => 'SH', Name => 'DetectorID' }, + '0018,700C' => { VR => 'DA', Name => 'DateOfLastDetectorCalibration' }, + '0018,700E' => { VR => 'TM', Name => 'TimeOfLastDetectorCalibration' }, + '0018,7010' => { VR => 'IS', Name => 'DetectorExposuresSinceCalibration' }, + '0018,7011' => { VR => 'IS', Name => 'DetectorExposuresSinceManufactured' }, + '0018,7012' => { VR => 'DS', Name => 'DetectorTimeSinceLastExposure' }, + '0018,7014' => { VR => 'DS', Name => 'DetectorActiveTime' }, + '0018,7016' => { VR => 'DS', Name => 'DetectorActiveOffsetFromExposure' }, + '0018,701A' => { VR => 'DS', Name => 'DetectorBinning' }, + '0018,7020' => { VR => 'DS', Name => 'DetectorElementPhysicalSize' }, + '0018,7022' => { VR => 'DS', Name => 'DetectorElementSpacing' }, + '0018,7024' => { VR => 'CS', Name => 'DetectorActiveShape' }, + '0018,7026' => { VR => 'DS', Name => 'DetectorActiveDimensions' }, + '0018,7028' => { VR => 'DS', Name => 'DetectorActiveOrigin' }, + '0018,702A' => { VR => 'LO', Name => 'DetectorManufacturerName' }, + '0018,702B' => { VR => 'LO', Name => 'DetectorManufacturersModelName' }, + '0018,7030' => { VR => 'DS', Name => 'FieldOfViewOrigin' }, + '0018,7032' => { VR => 'DS', Name => 'FieldOfViewRotation' }, + '0018,7034' => { VR => 'CS', Name => 'FieldOfViewHorizontalFlip' }, + '0018,7040' => { VR => 'LT', Name => 'GridAbsorbingMaterial' }, + '0018,7041' => { VR => 'LT', Name => 'GridSpacingMaterial' }, + '0018,7042' => { VR => 'DS', Name => 'GridThickness' }, + '0018,7044' => { VR => 'DS', Name => 'GridPitch' }, + '0018,7046' => { VR => 'IS', Name => 'GridAspectRatio' }, + '0018,7048' => { VR => 'DS', Name => 'GridPeriod' }, + '0018,704C' => { VR => 'DS', Name => 'GridFocalDistance' }, + '0018,7050' => { VR => 'CS', Name => 'FilterMaterial' }, + '0018,7052' => { VR => 'DS', Name => 'FilterThicknessMinimum' }, + '0018,7054' => { VR => 'DS', Name => 'FilterThicknessMaximum' }, + '0018,7060' => { VR => 'CS', Name => 'ExposureControlMode' }, + '0018,7062' => { VR => 'LT', Name => 'ExposureControlModeDescription' }, + '0018,7064' => { VR => 'CS', Name => 'ExposureStatus' }, + '0018,7065' => { VR => 'DS', Name => 'PhototimerSetting' }, + '0018,8150' => { VR => 'DS', Name => 'ExposureTimeInMicroSec' }, + '0018,8151' => { VR => 'DS', Name => 'XRayTubeCurrentInMicroAmps' }, + '0018,9004' => { VR => 'CS', Name => 'ContentQualification' }, + '0018,9005' => { VR => 'SH', Name => 'PulseSequenceName' }, + '0018,9006' => { VR => 'SQ', Name => 'MRImagingModifierSequence' }, + '0018,9008' => { VR => 'CS', Name => 'EchoPulseSequence' }, + '0018,9009' => { VR => 'CS', Name => 'InversionRecovery' }, + '0018,9010' => { VR => 'CS', Name => 'FlowCompensation' }, + '0018,9011' => { VR => 'CS', Name => 'MultipleSpinEcho' }, + '0018,9012' => { VR => 'CS', Name => 'MultiPlanarExcitation' }, + '0018,9014' => { VR => 'CS', Name => 'PhaseContrast' }, + '0018,9015' => { VR => 'CS', Name => 'TimeOfFlightContrast' }, + '0018,9016' => { VR => 'CS', Name => 'Spoiling' }, + '0018,9017' => { VR => 'CS', Name => 'SteadyStatePulseSequence' }, + '0018,9018' => { VR => 'CS', Name => 'EchoPlanarPulseSequence' }, + '0018,9019' => { VR => 'FD', Name => 'TagAngleFirstAxis' }, + '0018,9020' => { VR => 'CS', Name => 'MagnetizationTransfer' }, + '0018,9021' => { VR => 'CS', Name => 'T2Preparation' }, + '0018,9022' => { VR => 'CS', Name => 'BloodSignalNulling' }, + '0018,9022' => { VR => 'CS', Name => 'BloodSignalNulling' }, + '0018,9024' => { VR => 'CS', Name => 'SaturationRecovery' }, + '0018,9025' => { VR => 'CS', Name => 'SpectrallySelectedSuppression' }, + '0018,9026' => { VR => 'CS', Name => 'SpectrallySelectedExcitation' }, + '0018,9027' => { VR => 'CS', Name => 'SpatialPresaturation' }, + '0018,9028' => { VR => 'CS', Name => 'Tagging' }, + '0018,9029' => { VR => 'CS', Name => 'OversamplingPhase' }, + '0018,9030' => { VR => 'FD', Name => 'TagSpacingFirstDimension' }, + '0018,9032' => { VR => 'CS', Name => 'GeometryOfKSpaceTraversal' }, + '0018,9033' => { VR => 'CS', Name => 'SegmentedKSpaceTraversal' }, + '0018,9034' => { VR => 'CS', Name => 'RectilinearPhaseEncodeReordering' }, + '0018,9035' => { VR => 'FD', Name => 'TagThickness' }, + '0018,9036' => { VR => 'CS', Name => 'PartialFourierDirection' }, + '0018,9037' => { VR => 'CS', Name => 'CardiacSynchronizationTechnique' }, + '0018,9041' => { VR => 'LO', Name => 'ReceiveCoilManufacturerName' }, + '0018,9042' => { VR => 'SQ', Name => 'MRReceiveCoilSequence' }, + '0018,9043' => { VR => 'CS', Name => 'ReceiveCoilType' }, + '0018,9044' => { VR => 'CS', Name => 'QuadratureReceiveCoil' }, + '0018,9045' => { VR => 'SQ', Name => 'MultiCoilDefinitionSequence' }, + '0018,9046' => { VR => 'LO', Name => 'MultiCoilConfiguration' }, + '0018,9047' => { VR => 'SH', Name => 'MultiCoilElementName' }, + '0018,9048' => { VR => 'CS', Name => 'MultiCoilElementUsed' }, + '0018,9049' => { VR => 'SQ', Name => 'MRTransmitCoilSequence' }, + '0018,9050' => { VR => 'LO', Name => 'TransmitCoilManufacturerName' }, + '0018,9051' => { VR => 'CS', Name => 'TransmitCoilType' }, + '0018,9052' => { VR => 'FD', Name => 'SpectralWidth' }, + '0018,9053' => { VR => 'FD', Name => 'ChemicalShiftReference' }, + '0018,9054' => { VR => 'CS', Name => 'VolumeLocalizationTechnique' }, + '0018,9058' => { VR => 'US', Name => 'MRAcquisitionFrequencyEncodeSteps' }, + '0018,9059' => { VR => 'CS', Name => 'Decoupling' }, + '0018,9060' => { VR => 'CS', Name => 'DecoupledNucleus' }, + '0018,9061' => { VR => 'FD', Name => 'DecouplingFrequency' }, + '0018,9062' => { VR => 'CS', Name => 'DecouplingMethod' }, + '0018,9063' => { VR => 'FD', Name => 'DecouplingChemicalShiftReference' }, + '0018,9064' => { VR => 'CS', Name => 'KSpaceFiltering' }, + '0018,9065' => { VR => 'CS', Name => 'TimeDomainFiltering' }, + '0018,9066' => { VR => 'US', Name => 'NumberOfZeroFills' }, + '0018,9067' => { VR => 'CS', Name => 'BaselineCorrection' }, + '0018,9069' => { VR => 'FD', Name => 'ParallelReductionFactorInPlane' }, + '0018,9070' => { VR => 'FD', Name => 'CardiacRRIntervalSpecified' }, + '0018,9073' => { VR => 'FD', Name => 'AcquisitionDuration' }, + '0018,9074' => { VR => 'DT', Name => 'FrameAcquisitionDateTime' }, + '0018,9075' => { VR => 'CS', Name => 'DiffusionDirectionality' }, + '0018,9076' => { VR => 'SQ', Name => 'DiffusionGradientDirectionSequence' }, + '0018,9077' => { VR => 'CS', Name => 'ParallelAcquisition' }, + '0018,9078' => { VR => 'CS', Name => 'ParallelAcquisitionTechnique' }, + '0018,9079' => { VR => 'FD', Name => 'InversionTimes' }, + '0018,9080' => { VR => 'ST', Name => 'MetaboliteMapDescription' }, + '0018,9081' => { VR => 'CS', Name => 'PartialFourier' }, + '0018,9082' => { VR => 'FD', Name => 'EffectiveEchoTime' }, + '0018,9083' => { VR => 'SQ', Name => 'MetaboliteMapCodeSequence' }, + '0018,9084' => { VR => 'SQ', Name => 'ChemicalShiftSequence' }, + '0018,9085' => { VR => 'CS', Name => 'CardiacSignalSource' }, + '0018,9087' => { VR => 'FD', Name => 'DiffusionBValue' }, + '0018,9089' => { VR => 'FD', Name => 'DiffusionGradientOrientation' }, + '0018,9090' => { VR => 'FD', Name => 'VelocityEncodingDirection' }, + '0018,9091' => { VR => 'FD', Name => 'VelocityEncodingMinimumValue' }, + '0018,9093' => { VR => 'US', Name => 'NumberOfKSpaceTrajectories' }, + '0018,9094' => { VR => 'CS', Name => 'CoverageOfKSpace' }, + '0018,9095' => { VR => 'UL', Name => 'SpectroscopyAcquisitionPhaseRows' }, + '0018,9096' => { VR => 'FD', Name => 'ParallelReductFactorInPlaneRetired' }, + '0018,9098' => { VR => 'FD', Name => 'TransmitterFrequency' }, + '0018,9100' => { VR => 'CS', Name => 'ResonantNucleus' }, + '0018,9101' => { VR => 'CS', Name => 'FrequencyCorrection' }, + '0018,9103' => { VR => 'SQ', Name => 'MRSpectroscopyFOV-GeometrySequence' }, + '0018,9104' => { VR => 'FD', Name => 'SlabThickness' }, + '0018,9105' => { VR => 'FD', Name => 'SlabOrientation' }, + '0018,9106' => { VR => 'FD', Name => 'MidSlabPosition' }, + '0018,9107' => { VR => 'SQ', Name => 'MRSpatialSaturationSequence' }, + '0018,9112' => { VR => 'SQ', Name => 'MRTimingAndRelatedParametersSeq' }, + '0018,9114' => { VR => 'SQ', Name => 'MREchoSequence' }, + '0018,9115' => { VR => 'SQ', Name => 'MRModifierSequence' }, + '0018,9117' => { VR => 'SQ', Name => 'MRDiffusionSequence' }, + '0018,9118' => { VR => 'SQ', Name => 'CardiacTriggerSequence' }, + '0018,9119' => { VR => 'SQ', Name => 'MRAveragesSequence' }, + '0018,9125' => { VR => 'SQ', Name => 'MRFOV-GeometrySequence' }, + '0018,9126' => { VR => 'SQ', Name => 'VolumeLocalizationSequence' }, + '0018,9127' => { VR => 'UL', Name => 'SpectroscopyAcquisitionDataColumns' }, + '0018,9147' => { VR => 'CS', Name => 'DiffusionAnisotropyType' }, + '0018,9151' => { VR => 'DT', Name => 'FrameReferenceDateTime' }, + '0018,9152' => { VR => 'SQ', Name => 'MRMetaboliteMapSequence' }, + '0018,9155' => { VR => 'FD', Name => 'ParallelReductionFactorOutOfPlane' }, + '0018,9159' => { VR => 'UL', Name => 'SpectroscopyOutOfPlanePhaseSteps' }, + '0018,9166' => { VR => 'CS', Name => 'BulkMotionStatus' }, + '0018,9168' => { VR => 'FD', Name => 'ParallelReductionFactSecondInPlane' }, + '0018,9169' => { VR => 'CS', Name => 'CardiacBeatRejectionTechnique' }, + '0018,9170' => { VR => 'CS', Name => 'RespiratoryMotionCompTechnique' }, + '0018,9171' => { VR => 'CS', Name => 'RespiratorySignalSource' }, + '0018,9172' => { VR => 'CS', Name => 'BulkMotionCompensationTechnique' }, + '0018,9173' => { VR => 'CS', Name => 'BulkMotionSignalSource' }, + '0018,9174' => { VR => 'CS', Name => 'ApplicableSafetyStandardAgency' }, + '0018,9175' => { VR => 'LO', Name => 'ApplicableSafetyStandardDescr' }, + '0018,9176' => { VR => 'SQ', Name => 'OperatingModeSequence' }, + '0018,9177' => { VR => 'CS', Name => 'OperatingModeType' }, + '0018,9178' => { VR => 'CS', Name => 'OperatingMode' }, + '0018,9179' => { VR => 'CS', Name => 'SpecificAbsorptionRateDefinition' }, + '0018,9180' => { VR => 'CS', Name => 'GradientOutputType' }, + '0018,9181' => { VR => 'FD', Name => 'SpecificAbsorptionRateValue' }, + '0018,9182' => { VR => 'FD', Name => 'GradientOutput' }, + '0018,9183' => { VR => 'CS', Name => 'FlowCompensationDirection' }, + '0018,9184' => { VR => 'FD', Name => 'TaggingDelay' }, + '0018,9185' => { VR => 'ST', Name => 'RespiratoryMotionCompTechDescr' }, + '0018,9186' => { VR => 'SH', Name => 'RespiratorySignalSourceID' }, + '0018,9195' => { VR => 'FD', Name => 'ChemicalShiftsMinIntegrateLimitHz' }, + '0018,9196' => { VR => 'FD', Name => 'ChemicalShiftsMaxIntegrateLimitHz' }, + '0018,9197' => { VR => 'SQ', Name => 'MRVelocityEncodingSequence' }, + '0018,9198' => { VR => 'CS', Name => 'FirstOrderPhaseCorrection' }, + '0018,9199' => { VR => 'CS', Name => 'WaterReferencedPhaseCorrection' }, + '0018,9200' => { VR => 'CS', Name => 'MRSpectroscopyAcquisitionType' }, + '0018,9214' => { VR => 'CS', Name => 'RespiratoryCyclePosition' }, + '0018,9217' => { VR => 'FD', Name => 'VelocityEncodingMaximumValue' }, + '0018,9218' => { VR => 'FD', Name => 'TagSpacingSecondDimension' }, + '0018,9219' => { VR => 'SS', Name => 'TagAngleSecondAxis' }, + '0018,9220' => { VR => 'FD', Name => 'FrameAcquisitionDuration' }, + '0018,9226' => { VR => 'SQ', Name => 'MRImageFrameTypeSequence' }, + '0018,9227' => { VR => 'SQ', Name => 'MRSpectroscopyFrameTypeSequence' }, + '0018,9231' => { VR => 'US', Name => 'MRAcqPhaseEncodingStepsInPlane' }, + '0018,9232' => { VR => 'US', Name => 'MRAcqPhaseEncodingStepsOutOfPlane' }, + '0018,9234' => { VR => 'UL', Name => 'SpectroscopyAcqPhaseColumns' }, + '0018,9236' => { VR => 'CS', Name => 'CardiacCyclePosition' }, + '0018,9239' => { VR => 'SQ', Name => 'SpecificAbsorptionRateSequence' }, + '0018,9240' => { VR => 'US', Name => 'RFEchoTrainLength' }, + '0018,9241' => { VR => 'US', Name => 'GradientEchoTrainLength' }, + '0018,9295' => { VR => 'FD', Name => 'ChemicalShiftsMinIntegrateLimitPPM' }, + '0018,9296' => { VR => 'FD', Name => 'ChemicalShiftsMaxIntegrateLimitPPM' }, + '0018,9301' => { VR => 'SQ', Name => 'CTAcquisitionTypeSequence' }, + '0018,9302' => { VR => 'CS', Name => 'AcquisitionType' }, + '0018,9303' => { VR => 'FD', Name => 'TubeAngle' }, + '0018,9304' => { VR => 'SQ', Name => 'CTAcquisitionDetailsSequence' }, + '0018,9305' => { VR => 'FD', Name => 'RevolutionTime' }, + '0018,9306' => { VR => 'FD', Name => 'SingleCollimationWidth' }, + '0018,9307' => { VR => 'FD', Name => 'TotalCollimationWidth' }, + '0018,9308' => { VR => 'SQ', Name => 'CTTableDynamicsSequence' }, + '0018,9309' => { VR => 'FD', Name => 'TableSpeed' }, + '0018,9310' => { VR => 'FD', Name => 'TableFeedPerRotation' }, + '0018,9311' => { VR => 'FD', Name => 'SpiralPitchFactor' }, + '0018,9312' => { VR => 'SQ', Name => 'CTGeometrySequence' }, + '0018,9313' => { VR => 'FD', Name => 'DataCollectionCenterPatient' }, + '0018,9314' => { VR => 'SQ', Name => 'CTReconstructionSequence' }, + '0018,9315' => { VR => 'CS', Name => 'ReconstructionAlgorithm' }, + '0018,9316' => { VR => 'CS', Name => 'ConvolutionKernelGroup' }, + '0018,9317' => { VR => 'FD', Name => 'ReconstructionFieldOfView' }, + '0018,9318' => { VR => 'FD', Name => 'ReconstructionTargetCenterPatient' }, + '0018,9319' => { VR => 'FD', Name => 'ReconstructionAngle' }, + '0018,9320' => { VR => 'SH', Name => 'ImageFilter' }, + '0018,9321' => { VR => 'SQ', Name => 'CTExposureSequence' }, + '0018,9322' => { VR => 'FD', Name => 'ReconstructionPixelSpacing' }, + '0018,9323' => { VR => 'CS', Name => 'ExposureModulationType' }, + '0018,9324' => { VR => 'FD', Name => 'EstimatedDoseSaving' }, + '0018,9325' => { VR => 'SQ', Name => 'CTXRayDetailsSequence' }, + '0018,9326' => { VR => 'SQ', Name => 'CTPositionSequence' }, + '0018,9327' => { VR => 'FD', Name => 'TablePosition' }, + '0018,9328' => { VR => 'FD', Name => 'ExposureTimeInMilliSec' }, + '0018,9329' => { VR => 'SQ', Name => 'CTImageFrameTypeSequence' }, + '0018,9330' => { VR => 'FD', Name => 'XRayTubeCurrentInMilliAmps' }, + '0018,9332' => { VR => 'FD', Name => 'ExposureInMilliAmpSec' }, + '0018,9333' => { VR => 'CS', Name => 'ConstantVolumeFlag' }, + '0018,9334' => { VR => 'CS', Name => 'FluoroscopyFlag' }, + '0018,9335' => { VR => 'FD', Name => 'SourceToDataCollectionCenterDist' }, + '0018,9337' => { VR => 'US', Name => 'ContrastBolusAgentNumber' }, + '0018,9338' => { VR => 'SQ', Name => 'ContrastBolusIngredientCodeSeq' }, + '0018,9340' => { VR => 'SQ', Name => 'ContrastAdministrationProfileSeq' }, + '0018,9341' => { VR => 'SQ', Name => 'ContrastBolusUsageSequence' }, + '0018,9342' => { VR => 'CS', Name => 'ContrastBolusAgentAdministered' }, + '0018,9343' => { VR => 'CS', Name => 'ContrastBolusAgentDetected' }, + '0018,9344' => { VR => 'CS', Name => 'ContrastBolusAgentPhase' }, + '0018,9345' => { VR => 'FD', Name => 'CTDIvol' }, + '0018,9346' => { VR => 'SQ', Name => 'CTDIPhantomTypeCodeSequence' }, + '0018,9351' => { VR => 'FL', Name => 'CalciumScoringMassFactorPatient' }, + '0018,9352' => { VR => 'FL', Name => 'CalciumScoringMassFactorDevice' }, + '0018,9353' => { VR => 'FL', Name => 'EnergyWeightingFactor' }, + '0018,9360' => { VR => 'SQ', Name => 'CTAdditionalXRaySourceSequence' }, + '0018,9401' => { VR => 'SQ', Name => 'ProjectionPixelCalibrationSequence' }, + '0018,9402' => { VR => 'FL', Name => 'DistanceSourceToIsocenter' }, + '0018,9403' => { VR => 'FL', Name => 'DistanceObjectToTableTop' }, + '0018,9404' => { VR => 'FL', Name => 'ObjectPixelSpacingInCenterOfBeam' }, + '0018,9405' => { VR => 'SQ', Name => 'PositionerPositionSequence' }, + '0018,9406' => { VR => 'SQ', Name => 'TablePositionSequence' }, + '0018,9407' => { VR => 'SQ', Name => 'CollimatorShapeSequence' }, + '0018,9412' => { VR => 'SQ', Name => 'XA-XRFFrameCharacteristicsSequence' }, + '0018,9417' => { VR => 'SQ', Name => 'FrameAcquisitionSequence' }, + '0018,9420' => { VR => 'CS', Name => 'XRayReceptorType' }, + '0018,9423' => { VR => 'LO', Name => 'AcquisitionProtocolName' }, + '0018,9424' => { VR => 'LT', Name => 'AcquisitionProtocolDescription' }, + '0018,9425' => { VR => 'CS', Name => 'ContrastBolusIngredientOpaque' }, + '0018,9426' => { VR => 'FL', Name => 'DistanceReceptorPlaneToDetHousing' }, + '0018,9427' => { VR => 'CS', Name => 'IntensifierActiveShape' }, + '0018,9428' => { VR => 'FL', Name => 'IntensifierActiveDimensions' }, + '0018,9429' => { VR => 'FL', Name => 'PhysicalDetectorSize' }, + '0018,9430' => { VR => 'US', Name => 'PositionOfIsocenterProjection' }, + '0018,9432' => { VR => 'SQ', Name => 'FieldOfViewSequence' }, + '0018,9433' => { VR => 'LO', Name => 'FieldOfViewDescription' }, + '0018,9434' => { VR => 'SQ', Name => 'ExposureControlSensingRegionsSeq' }, + '0018,9435' => { VR => 'CS', Name => 'ExposureControlSensingRegionShape' }, + '0018,9436' => { VR => 'SS', Name => 'ExposureControlSensRegionLeftEdge' }, + '0018,9437' => { VR => 'SS', Name => 'ExposureControlSensRegionRightEdge' }, + '0018,9440' => { VR => 'SS', Name => 'CenterOfCircExposControlSensRegion' }, + '0018,9441' => { VR => 'US', Name => 'RadiusOfCircExposControlSensRegion' }, + '0018,9447' => { VR => 'FL', Name => 'ColumnAngulationPatient' }, + '0018,9449' => { VR => 'FL', Name => 'BeamAngle' }, + '0018,9451' => { VR => 'SQ', Name => 'FrameDetectorParametersSequence' }, + '0018,9452' => { VR => 'FL', Name => 'CalculatedAnatomyThickness' }, + '0018,9455' => { VR => 'SQ', Name => 'CalibrationSequence' }, + '0018,9456' => { VR => 'SQ', Name => 'ObjectThicknessSequence' }, + '0018,9457' => { VR => 'CS', Name => 'PlaneIdentification' }, + '0018,9461' => { VR => 'FL', Name => 'FieldOfViewDimensionsInFloat' }, + '0018,9462' => { VR => 'SQ', Name => 'IsocenterReferenceSystemSequence' }, + '0018,9463' => { VR => 'FL', Name => 'PositionerIsocenterPrimaryAngle' }, + '0018,9464' => { VR => 'FL', Name => 'PositionerIsocenterSecondaryAngle' }, + '0018,9465' => { VR => 'FL', Name => 'PositionerIsocenterDetRotAngle' }, + '0018,9466' => { VR => 'FL', Name => 'TableXPositionToIsocenter' }, + '0018,9467' => { VR => 'FL', Name => 'TableYPositionToIsocenter' }, + '0018,9468' => { VR => 'FL', Name => 'TableZPositionToIsocenter' }, + '0018,9469' => { VR => 'FL', Name => 'TableHorizontalRotationAngle' }, + '0018,9470' => { VR => 'FL', Name => 'TableHeadTiltAngle' }, + '0018,9471' => { VR => 'FL', Name => 'TableCradleTiltAngle' }, + '0018,9472' => { VR => 'SQ', Name => 'FrameDisplayShutterSequence' }, + '0018,9473' => { VR => 'FL', Name => 'AcquiredImageAreaDoseProduct' }, + '0018,9474' => { VR => 'CS', Name => 'CArmPositionerTabletopRelationship' }, + '0018,9476' => { VR => 'SQ', Name => 'XRayGeometrySequence' }, + '0018,9477' => { VR => 'SQ', Name => 'IrradiationEventIDSequence' }, + '0018,9504' => { VR => 'SQ', Name => 'XRay3DFrameTypeSequence' }, + '0018,9506' => { VR => 'SQ', Name => 'ContributingSourcesSequence' }, + '0018,9507' => { VR => 'SQ', Name => 'XRay3DAcquisitionSequence' }, + '0018,9508' => { VR => 'FL', Name => 'PrimaryPositionerScanArc' }, + '0018,9509' => { VR => 'FL', Name => 'SecondaryPositionerScanArc' }, + '0018,9510' => { VR => 'FL', Name => 'PrimaryPositionerScanStartAngle' }, + '0018,9511' => { VR => 'FL', Name => 'SecondaryPositionerScanStartAngle' }, + '0018,9514' => { VR => 'FL', Name => 'PrimaryPositionerIncrement' }, + '0018,9515' => { VR => 'FL', Name => 'SecondaryPositionerIncrement' }, + '0018,9516' => { VR => 'DT', Name => 'StartAcquisitionDateTime' }, + '0018,9517' => { VR => 'DT', Name => 'EndAcquisitionDateTime' }, + '0018,9524' => { VR => 'LO', Name => 'ApplicationName' }, + '0018,9525' => { VR => 'LO', Name => 'ApplicationVersion' }, + '0018,9526' => { VR => 'LO', Name => 'ApplicationManufacturer' }, + '0018,9527' => { VR => 'CS', Name => 'AlgorithmType' }, + '0018,9528' => { VR => 'LO', Name => 'AlgorithmDescription' }, + '0018,9530' => { VR => 'SQ', Name => 'XRay3DReconstructionSequence' }, + '0018,9531' => { VR => 'LO', Name => 'ReconstructionDescription' }, + '0018,9538' => { VR => 'SQ', Name => 'PerProjectionAcquisitionSequence' }, + '0018,9601' => { VR => 'SQ', Name => 'DiffusionBMatrixSequence' }, + '0018,9602' => { VR => 'FD', Name => 'DiffusionBValueXX' }, + '0018,9603' => { VR => 'FD', Name => 'DiffusionBValueXY' }, + '0018,9604' => { VR => 'FD', Name => 'DiffusionBValueXZ' }, + '0018,9605' => { VR => 'FD', Name => 'DiffusionBValueYY' }, + '0018,9606' => { VR => 'FD', Name => 'DiffusionBValueYZ' }, + '0018,9607' => { VR => 'FD', Name => 'DiffusionBValueZZ' }, + '0018,9701' => { VR => 'DT', Name => 'DecayCorrectionDateTime' }, + '0018,9715' => { VR => 'FD', Name => 'StartDensityThreshold' }, + '0018,9722' => { VR => 'FD', Name => 'TerminationTimeThreshold' }, + '0018,9725' => { VR => 'CS', Name => 'DetectorGeometry' }, + '0018,9727' => { VR => 'FD', Name => 'AxialDetectorDimension' }, + '0018,9735' => { VR => 'SQ', Name => 'PETPositionSequence' }, + '0018,9739' => { VR => 'US', Name => 'NumberOfIterations' }, + '0018,9740' => { VR => 'US', Name => 'NumberOfSubsets' }, + '0018,9751' => { VR => 'SQ', Name => 'PETFrameTypeSequence' }, + '0018,9756' => { VR => 'CS', Name => 'ReconstructionType' }, + '0018,9758' => { VR => 'CS', Name => 'DecayCorrected' }, + '0018,9759' => { VR => 'CS', Name => 'AttenuationCorrected' }, + '0018,9760' => { VR => 'CS', Name => 'ScatterCorrected' }, + '0018,9761' => { VR => 'CS', Name => 'DeadTimeCorrected' }, + '0018,9762' => { VR => 'CS', Name => 'GantryMotionCorrected' }, + '0018,9763' => { VR => 'CS', Name => 'PatientMotionCorrected' }, + '0018,9765' => { VR => 'CS', Name => 'RandomsCorrected' }, + '0018,9767' => { VR => 'CS', Name => 'SensitivityCalibrated' }, + '0018,9801' => { VR => 'FD', Name => 'DepthsOfFocus' }, + '0018,9804' => { VR => 'DT', Name => 'ExclusionStartDatetime' }, + '0018,9805' => { VR => 'FD', Name => 'ExclusionDuration' }, + '0018,9807' => { VR => 'SQ', Name => 'ImageDataTypeSequence' }, + '0018,9808' => { VR => 'CS', Name => 'DataType' }, + '0018,980B' => { VR => 'CS', Name => 'AliasedDataType' }, + '0018,A001' => { VR => 'SQ', Name => 'ContributingEquipmentSequence' }, + '0018,A002' => { VR => 'DT', Name => 'ContributionDateTime' }, + '0018,A003' => { VR => 'ST', Name => 'ContributionDescription' }, + # GEMS_ACQU_01 (ref 4) + '0019,1002' => { VR => 'SL', Name => 'NumberOfCellsIInDetector' }, + '0019,1003' => { VR => 'DS', Name => 'CellNumberAtTheta' }, + '0019,1004' => { VR => 'DS', Name => 'CellSpacing' }, + '0019,100F' => { VR => 'DS', Name => 'HorizFrameOfRef' }, + '0019,1011' => { VR => 'SS', Name => 'SeriesContrast' }, + '0019,1012' => { VR => 'SS', Name => 'LastPseq' }, + '0019,1013' => { VR => 'SS', Name => 'StartNumberForBaseline' }, + '0019,1014' => { VR => 'SS', Name => 'EndNumberForBaseline' }, + '0019,1015' => { VR => 'SS', Name => 'StartNumberForEnhancedScans' }, + '0019,1016' => { VR => 'SS', Name => 'EndNumberForEnhancedScans' }, + '0019,1017' => { VR => 'SS', Name => 'SeriesPlane' }, + '0019,1018' => { VR => 'LO', Name => 'FirstScanRas' }, + '0019,1019' => { VR => 'DS', Name => 'FirstScanLocation' }, + '0019,101A' => { VR => 'LO', Name => 'LastScanRas' }, + '0019,101B' => { VR => 'DS', Name => 'LastScanLoc' }, + '0019,101E' => { VR => 'DS', Name => 'DisplayFieldOfView' }, + '0019,1023' => { VR => 'DS', Name => 'TableSpeed' }, + '0019,1024' => { VR => 'DS', Name => 'MidScanTime' }, + '0019,1025' => { VR => 'SS', Name => 'MidScanFlag' }, + '0019,1026' => { VR => 'SL', Name => 'DegreesOfAzimuth' }, + '0019,1027' => { VR => 'DS', Name => 'GantryPeriod' }, + '0019,102A' => { VR => 'DS', Name => 'XRayOnPosition' }, + '0019,102B' => { VR => 'DS', Name => 'XRayOffPosition' }, + '0019,102C' => { VR => 'SL', Name => 'NumberOfTriggers' }, + '0019,102E' => { VR => 'DS', Name => 'AngleOfFirstView' }, + '0019,102F' => { VR => 'DS', Name => 'TriggerFrequency' }, + '0019,1039' => { VR => 'SS', Name => 'ScanFOVType' }, + '0019,1040' => { VR => 'SS', Name => 'StatReconFlag' }, + '0019,1041' => { VR => 'SS', Name => 'ComputeType' }, + '0019,1042' => { VR => 'SS', Name => 'SegmentNumber' }, + '0019,1043' => { VR => 'SS', Name => 'TotalSegmentsRequested' }, + '0019,1044' => { VR => 'DS', Name => 'InterscanDelay' }, + '0019,1047' => { VR => 'SS', Name => 'ViewCompressionFactor' }, + '0019,104A' => { VR => 'SS', Name => 'TotalNoOfRefChannels' }, + '0019,104B' => { VR => 'SL', Name => 'DataSizeForScanData' }, + '0019,1052' => { VR => 'SS', Name => 'ReconPostProcflag' }, + '0019,1057' => { VR => 'SS', Name => 'CTWaterNumber' }, + '0019,1058' => { VR => 'SS', Name => 'CTBoneNumber' }, + '0019,105A' => { VR => 'FL', Name => 'AcquisitionDuration' }, + '0019,105E' => { VR => 'SL', Name => 'NumberOfChannels' }, + '0019,105F' => { VR => 'SL', Name => 'IncrementBetweenChannels' }, + '0019,1060' => { VR => 'SL', Name => 'StartingView' }, + '0019,1061' => { VR => 'SL', Name => 'NumberOfViews' }, + '0019,1062' => { VR => 'SL', Name => 'IncrementBetweenViews' }, + '0019,106A' => { VR => 'SS', Name => 'DependantOnNoViewsProcessed' }, + '0019,106B' => { VR => 'SS', Name => 'FieldOfViewInDetectorCells' }, + '0019,1070' => { VR => 'SS', Name => 'ValueOfBackProjectionButton' }, + '0019,1071' => { VR => 'SS', Name => 'SetIfFatqEstimatesWereUsed' }, + '0019,1072' => { VR => 'DS', Name => 'ZChanAvgOverViews' }, + '0019,1073' => { VR => 'DS', Name => 'AvgOfLeftRefChansOverViews' }, + '0019,1074' => { VR => 'DS', Name => 'MaxLeftChanOverViews' }, + '0019,1075' => { VR => 'DS', Name => 'AvgOfRightRefChansOverViews' }, + '0019,1076' => { VR => 'DS', Name => 'MaxRightChanOverViews' }, + '0019,107D' => { VR => 'DS', Name => 'SecondEcho' }, + '0019,107E' => { VR => 'SS', Name => 'NumberOfEchoes' }, + '0019,107F' => { VR => 'DS', Name => 'TableDelta' }, + '0019,1081' => { VR => 'SS', Name => 'Contiguous' }, + '0019,1084' => { VR => 'DS', Name => 'PeakSAR' }, + '0019,1085' => { VR => 'SS', Name => 'MonitorSAR' }, + '0019,1087' => { VR => 'DS', Name => 'CardiacRepetitionTime' }, + '0019,1088' => { VR => 'SS', Name => 'ImagesPerCardiacCycle' }, + '0019,108A' => { VR => 'SS', Name => 'ActualReceiveGainAnalog' }, + '0019,108B' => { VR => 'SS', Name => 'ActualReceiveGainDigital' }, + '0019,108D' => { VR => 'DS', Name => 'DelayAfterTrigger' }, + '0019,108F' => { VR => 'SS', Name => 'Swappf' }, + '0019,1090' => { VR => 'SS', Name => 'PauseInterval' }, + '0019,1091' => { VR => 'DS', Name => 'PulseTime' }, + '0019,1092' => { VR => 'SL', Name => 'SliceOffsetOnFreqAxis' }, + '0019,1093' => { VR => 'DS', Name => 'CenterFrequency' }, + '0019,1094' => { VR => 'SS', Name => 'TransmitGain' }, + '0019,1095' => { VR => 'SS', Name => 'AnalogReceiverGain' }, + '0019,1096' => { VR => 'SS', Name => 'DigitalReceiverGain' }, + '0019,1097' => { VR => 'SL', Name => 'BitmapDefiningCVs' }, + '0019,1098' => { VR => 'SS', Name => 'CenterFreqMethod' }, + '0019,109B' => { VR => 'SS', Name => 'PulseSeqMode' }, + '0019,109C' => { VR => 'LO', Name => 'PulseSeqName' }, + '0019,109D' => { VR => 'DT', Name => 'PulseSeqDate' }, + '0019,109E' => { VR => 'LO', Name => 'InternalPulseSeqName' }, + '0019,109F' => { VR => 'SS', Name => 'TransmittingCoil' }, + '0019,10A0' => { VR => 'SS', Name => 'SurfaceCoilType' }, + '0019,10A1' => { VR => 'SS', Name => 'ExtremityCoilFlag' }, + '0019,10A2' => { VR => 'SL', Name => 'RawDataRunNumber' }, + '0019,10A3' => { VR => 'UL', Name => 'CalibratedFieldStrength' }, + '0019,10A4' => { VR => 'SS', Name => 'SATFatWaterBone' }, + '0019,10A5' => { VR => 'DS', Name => 'ReceiveBandwidth' }, + '0019,10A7' => { VR => 'DS', Name => 'UserData01' }, + '0019,10A8' => { VR => 'DS', Name => 'UserData02' }, + '0019,10A9' => { VR => 'DS', Name => 'UserData03' }, + '0019,10AA' => { VR => 'DS', Name => 'UserData04' }, + '0019,10AB' => { VR => 'DS', Name => 'UserData05' }, + '0019,10AC' => { VR => 'DS', Name => 'UserData06' }, + '0019,10AD' => { VR => 'DS', Name => 'UserData07' }, + '0019,10AE' => { VR => 'DS', Name => 'UserData08' }, + '0019,10AF' => { VR => 'DS', Name => 'UserData09' }, + '0019,10B0' => { VR => 'DS', Name => 'UserData10' }, + '0019,10B1' => { VR => 'DS', Name => 'UserData11' }, + '0019,10B2' => { VR => 'DS', Name => 'UserData12' }, + '0019,10B3' => { VR => 'DS', Name => 'UserData13' }, + '0019,10B4' => { VR => 'DS', Name => 'UserData14' }, + '0019,10B5' => { VR => 'DS', Name => 'UserData15' }, + '0019,10B6' => { VR => 'DS', Name => 'UserData16' }, + '0019,10B7' => { VR => 'DS', Name => 'UserData17' }, + '0019,10B8' => { VR => 'DS', Name => 'UserData18' }, + '0019,10B9' => { VR => 'DS', Name => 'UserData19' }, + '0019,10BA' => { VR => 'DS', Name => 'UserData20' }, + '0019,10BB' => { VR => 'DS', Name => 'UserData21' }, + '0019,10BC' => { VR => 'DS', Name => 'UserData22' }, + '0019,10BD' => { VR => 'DS', Name => 'UserData23' }, + '0019,10BE' => { VR => 'DS', Name => 'ProjectionAngle' }, + '0019,10C0' => { VR => 'SS', Name => 'SaturationPlanes' }, + '0019,10C1' => { VR => 'SS', Name => 'SurfaceCoilIntensity' }, + '0019,10C2' => { VR => 'SS', Name => 'SATLocationR' }, + '0019,10C3' => { VR => 'SS', Name => 'SATLocationL' }, + '0019,10C4' => { VR => 'SS', Name => 'SATLocationA' }, + '0019,10C5' => { VR => 'SS', Name => 'SATLocationP' }, + '0019,10C6' => { VR => 'SS', Name => 'SATLocationH' }, + '0019,10C7' => { VR => 'SS', Name => 'SATLocationF' }, + '0019,10C8' => { VR => 'SS', Name => 'SATThicknessR-L' }, + '0019,10C9' => { VR => 'SS', Name => 'SATThicknessA-P' }, + '0019,10CA' => { VR => 'SS', Name => 'SATThicknessH-F' }, + '0019,10CB' => { VR => 'SS', Name => 'PrescribedFlowAxis' }, + '0019,10CC' => { VR => 'SS', Name => 'VelocityEncoding' }, + '0019,10CD' => { VR => 'SS', Name => 'ThicknessDisclaimer' }, + '0019,10CE' => { VR => 'SS', Name => 'PrescanType' }, + '0019,10CF' => { VR => 'SS', Name => 'PrescanStatus' }, + '0019,10D0' => { VR => 'SH', Name => 'RawDataType' }, + '0019,10D2' => { VR => 'SS', Name => 'ProjectionAlgorithm' }, + '0019,10D3' => { VR => 'SH', Name => 'ProjectionAlgorithm' }, + '0019,10D5' => { VR => 'SS', Name => 'FractionalEcho' }, + '0019,10D6' => { VR => 'SS', Name => 'PrepPulse' }, + '0019,10D7' => { VR => 'SS', Name => 'CardiacPhases' }, + '0019,10D8' => { VR => 'SS', Name => 'VariableEchoflag' }, + '0019,10D9' => { VR => 'DS', Name => 'ConcatenatedSAT' }, + '0019,10DA' => { VR => 'SS', Name => 'ReferenceChannelUsed' }, + '0019,10DB' => { VR => 'DS', Name => 'BackProjectorCoefficient' }, + '0019,10DC' => { VR => 'SS', Name => 'PrimarySpeedCorrectionUsed' }, + '0019,10DD' => { VR => 'SS', Name => 'OverrangeCorrectionUsed' }, + '0019,10DE' => { VR => 'DS', Name => 'DynamicZAlphaValue' }, + '0019,10DF' => { VR => 'DS', Name => 'UserData' }, + '0019,10E0' => { VR => 'DS', Name => 'UserData' }, + '0019,10E2' => { VR => 'DS', Name => 'VelocityEncodeScale' }, + '0019,10F2' => { VR => 'SS', Name => 'FastPhases' }, + '0019,10F9' => { VR => 'DS', Name => 'TransmissionGain' }, + # relationship group + '0020,0000' => { VR => 'UL', Name => 'RelationshipGroupLength' }, + '0020,000D' => { VR => 'UI', Name => 'StudyInstanceUID' }, + '0020,000E' => { VR => 'UI', Name => 'SeriesInstanceUID' }, + '0020,0010' => { VR => 'SH', Name => 'StudyID' }, + '0020,0011' => { VR => 'IS', Name => 'SeriesNumber' }, + '0020,0012' => { VR => 'IS', Name => 'AcquisitionNumber' }, + '0020,0013' => { VR => 'IS', Name => 'InstanceNumber' }, + '0020,0014' => { VR => 'IS', Name => 'IsotopeNumber' }, + '0020,0015' => { VR => 'IS', Name => 'PhaseNumber' }, + '0020,0016' => { VR => 'IS', Name => 'IntervalNumber' }, + '0020,0017' => { VR => 'IS', Name => 'TimeSlotNumber' }, + '0020,0018' => { VR => 'IS', Name => 'AngleNumber' }, + '0020,0019' => { VR => 'IS', Name => 'ItemNumber' }, + '0020,0020' => { VR => 'CS', Name => 'PatientOrientation' }, + '0020,0022' => { VR => 'IS', Name => 'OverlayNumber' }, + '0020,0024' => { VR => 'IS', Name => 'CurveNumber' }, + '0020,0026' => { VR => 'IS', Name => 'LookupTableNumber' }, + '0020,0030' => { VR => 'DS', Name => 'ImagePosition' }, + '0020,0032' => { VR => 'DS', Name => 'ImagePositionPatient' }, + '0020,0035' => { VR => 'DS', Name => 'ImageOrientation' }, + '0020,0037' => { VR => 'DS', Name => 'ImageOrientationPatient' }, + '0020,0050' => { VR => 'DS', Name => 'Location' }, + '0020,0052' => { VR => 'UI', Name => 'FrameOfReferenceUID' }, + '0020,0060' => { VR => 'CS', Name => 'Laterality' }, + '0020,0062' => { VR => 'CS', Name => 'ImageLaterality' }, + '0020,0070' => { VR => 'LO', Name => 'ImageGeometryType' }, + '0020,0080' => { VR => 'CS', Name => 'MaskingImage' }, + '0020,0100' => { VR => 'IS', Name => 'TemporalPositionIdentifier' }, + '0020,0105' => { VR => 'IS', Name => 'NumberOfTemporalPositions' }, + '0020,0110' => { VR => 'DS', Name => 'TemporalResolution' }, + '0020,0200' => { VR => 'UI', Name => 'SynchronizationFrameOfReferenceUID' }, + '0020,1000' => { VR => 'IS', Name => 'SeriesInStudy' }, + '0020,1001' => { VR => 'IS', Name => 'AcquisitionsInSeries' }, + '0020,1002' => { VR => 'IS', Name => 'ImagesInAcquisition' }, + '0020,1003' => { VR => 'IS', Name => 'ImagesInSeries' }, + '0020,1004' => { VR => 'IS', Name => 'AcquisitionsInStudy' }, + '0020,1005' => { VR => 'IS', Name => 'ImagesInStudy' }, + '0020,1020' => { VR => 'CS', Name => 'Reference' }, + '0020,1040' => { VR => 'LO', Name => 'PositionReferenceIndicator' }, + '0020,1041' => { VR => 'DS', Name => 'SliceLocation' }, + '0020,1070' => { VR => 'IS', Name => 'OtherStudyNumbers' }, + '0020,1200' => { VR => 'IS', Name => 'NumberOfPatientRelatedStudies' }, + '0020,1202' => { VR => 'IS', Name => 'NumberOfPatientRelatedSeries' }, + '0020,1204' => { VR => 'IS', Name => 'NumberOfPatientRelatedInstances' }, + '0020,1206' => { VR => 'IS', Name => 'NumberOfStudyRelatedSeries' }, + '0020,1208' => { VR => 'IS', Name => 'NumberOfStudyRelatedInstances' }, + '0020,1209' => { VR => 'IS', Name => 'NumberOfSeriesRelatedInstances' }, + '0020,31xx' => { VR => 'CS', Name => 'SourceImageIDs' }, + '0020,3401' => { VR => 'CS', Name => 'ModifyingDeviceID' }, + '0020,3402' => { VR => 'CS', Name => 'ModifiedImageID' }, + '0020,3403' => { VR => 'DA', Name => 'ModifiedImageDate' }, + '0020,3404' => { VR => 'LO', Name => 'ModifyingDeviceManufacturer' }, + '0020,3405' => { VR => 'TM', Name => 'ModifiedImageTime' }, + '0020,3406' => { VR => 'LO', Name => 'ModifiedImageDescription' }, + '0020,4000' => { VR => 'LT', Name => 'ImageComments' }, + '0020,5000' => { VR => 'AT', Name => 'OriginalImageIdentification' }, + '0020,5002' => { VR => 'CS', Name => 'OriginalImageIdentNomenclature' }, + '0020,9056' => { VR => 'SH', Name => 'StackID' }, + '0020,9057' => { VR => 'UL', Name => 'InStackPositionNumber' }, + '0020,9071' => { VR => 'SQ', Name => 'FrameAnatomySequence' }, + '0020,9072' => { VR => 'CS', Name => 'FrameLaterality' }, + '0020,9111' => { VR => 'SQ', Name => 'FrameContentSequence' }, + '0020,9113' => { VR => 'SQ', Name => 'PlanePositionSequence' }, + '0020,9116' => { VR => 'SQ', Name => 'PlaneOrientationSequence' }, + '0020,9128' => { VR => 'UL', Name => 'TemporalPositionIndex' }, + '0020,9153' => { VR => 'FD', Name => 'TriggerDelayTime' }, + '0020,9156' => { VR => 'US', Name => 'FrameAcquisitionNumber' }, + '0020,9157' => { VR => 'UL', Name => 'DimensionIndexValues' }, + '0020,9158' => { VR => 'LT', Name => 'FrameComments' }, + '0020,9161' => { VR => 'UI', Name => 'ConcatenationUID' }, + '0020,9162' => { VR => 'US', Name => 'InConcatenationNumber' }, + '0020,9163' => { VR => 'US', Name => 'InConcatenationTotalNumber' }, + '0020,9164' => { VR => 'UI', Name => 'DimensionOrganizationUID' }, + '0020,9165' => { VR => 'AT', Name => 'DimensionIndexPointer' }, + '0020,9167' => { VR => 'AT', Name => 'FunctionalGroupPointer' }, + '0020,9213' => { VR => 'LO', Name => 'DimensionIndexPrivateCreator' }, + '0020,9221' => { VR => 'SQ', Name => 'DimensionOrganizationSequence' }, + '0020,9222' => { VR => 'SQ', Name => 'DimensionIndexSequence' }, + '0020,9228' => { VR => 'UL', Name => 'ConcatenationFrameOffsetNumber' }, + '0020,9238' => { VR => 'LO', Name => 'FunctionalGroupPrivateCreator' }, + '0020,9241' => { VR => 'FL', Name => 'NominalPercentageOfCardiacPhase' }, + '0020,9245' => { VR => 'FL', Name => 'NominalPercentOfRespiratoryPhase' }, + '0020,9246' => { VR => 'FL', Name => 'StartingRespiratoryAmplitude' }, + '0020,9247' => { VR => 'CS', Name => 'StartingRespiratoryPhase' }, + '0020,9248' => { VR => 'FL', Name => 'EndingRespiratoryAmplitude' }, + '0020,9249' => { VR => 'CS', Name => 'EndingRespiratoryPhase' }, + '0020,9250' => { VR => 'CS', Name => 'RespiratoryTriggerType' }, + '0020,9251' => { VR => 'FD', Name => 'RRIntervalTimeNominal' }, + '0020,9252' => { VR => 'FD', Name => 'ActualCardiacTriggerDelayTime' }, + '0020,9253' => { VR => 'SQ', Name => 'RespiratorySynchronizationSequence' }, + '0020,9254' => { VR => 'FD', Name => 'RespiratoryIntervalTime' }, + '0020,9255' => { VR => 'FD', Name => 'NominalRespiratoryTriggerDelayTime' }, + '0020,9256' => { VR => 'FD', Name => 'RespiratoryTriggerDelayThreshold' }, + '0020,9257' => { VR => 'FD', Name => 'ActualRespiratoryTriggerDelayTime' }, + '0020,9301' => { VR => 'FD', Name => 'ImagePositionVolume' }, + '0020,9302' => { VR => 'FD', Name => 'ImageOrientationVolume' }, + '0020,9308' => { VR => 'FD', Name => 'ApexPosition' }, + '0020,9421' => { VR => 'LO', Name => 'DimensionDescriptionLabel' }, + '0020,9450' => { VR => 'SQ', Name => 'PatientOrientationInFrameSequence' }, + '0020,9453' => { VR => 'LO', Name => 'FrameLabel' }, + '0020,9518' => { VR => 'US', Name => 'AcquisitionIndex' }, + '0020,9529' => { VR => 'SQ', Name => 'ContributingSOPInstancesRefSeq' }, + '0020,9536' => { VR => 'US', Name => 'ReconstructionIndex' }, + # GEMS_RELA_01 (ref 4) + '0021,1003' => { VR => 'SS', Name => 'SeriesFromWhichPrescribed' }, + '0021,1005' => { VR => 'SH', Name => 'GenesisVersionNow' }, + '0021,1005' => { VR => 'SH', Name => 'GenesisVersionNow' }, + '0021,1007' => { VR => 'UL', Name => 'SeriesRecordChecksum' }, + '0021,1018' => { VR => 'SH', Name => 'GenesisVersionNow' }, + '0021,1018' => { VR => 'SH', Name => 'GenesisVersionNow' }, + '0021,1019' => { VR => 'UL', Name => 'AcqReconRecordChecksum' }, + '0021,1019' => { VR => 'UL', Name => 'AcqreconRecordChecksum' }, + '0021,1020' => { VR => 'DS', Name => 'TableStartLocation' }, + '0021,1035' => { VR => 'SS', Name => 'SeriesFromWhichPrescribed' }, + '0021,1036' => { VR => 'SS', Name => 'ImageFromWhichPrescribed' }, + '0021,1037' => { VR => 'SS', Name => 'ScreenFormat' }, + '0021,104A' => { VR => 'LO', Name => 'AnatomicalReferenceForScout' }, + '0021,104F' => { VR => 'SS', Name => 'LocationsInAcquisition' }, + '0021,1050' => { VR => 'SS', Name => 'GraphicallyPrescribed' }, + '0021,1051' => { VR => 'DS', Name => 'RotationFromSourceXRot' }, + '0021,1052' => { VR => 'DS', Name => 'RotationFromSourceYRot' }, + '0021,1053' => { VR => 'DS', Name => 'RotationFromSourceZRot' }, + '0021,1054' => { VR => 'SH', Name => 'ImagePosition' }, + '0021,1055' => { VR => 'SH', Name => 'ImageOrientation' }, + '0021,1056' => { VR => 'SL', Name => 'IntegerSlop' }, + '0021,1057' => { VR => 'SL', Name => 'IntegerSlop' }, + '0021,1058' => { VR => 'SL', Name => 'IntegerSlop' }, + '0021,1059' => { VR => 'SL', Name => 'IntegerSlop' }, + '0021,105A' => { VR => 'SL', Name => 'IntegerSlop' }, + '0021,105B' => { VR => 'DS', Name => 'FloatSlop' }, + '0021,105C' => { VR => 'DS', Name => 'FloatSlop' }, + '0021,105D' => { VR => 'DS', Name => 'FloatSlop' }, + '0021,105E' => { VR => 'DS', Name => 'FloatSlop' }, + '0021,105F' => { VR => 'DS', Name => 'FloatSlop' }, + '0021,1081' => { VR => 'DS', Name => 'AutoWindowLevelAlpha' }, + '0021,1082' => { VR => 'DS', Name => 'AutoWindowLevelBeta' }, + '0021,1083' => { VR => 'DS', Name => 'AutoWindowLevelWindow' }, + '0021,1084' => { VR => 'DS', Name => 'ToWindowLevelLevel' }, + '0021,1090' => { VR => 'SS', Name => 'TubeFocalSpotPosition' }, + '0021,1091' => { VR => 'SS', Name => 'BiopsyPosition' }, + '0021,1092' => { VR => 'FL', Name => 'BiopsyTLocation' }, + '0021,1093' => { VR => 'FL', Name => 'BiopsyRefLocation' }, + # ? + '0022,0001' => { VR => 'US', Name => 'LightPathFilterPassThroughWavelen' }, + '0022,0002' => { VR => 'US', Name => 'LightPathFilterPassBand' }, + '0022,0003' => { VR => 'US', Name => 'ImagePathFilterPassThroughWavelen' }, + '0022,0004' => { VR => 'US', Name => 'ImagePathFilterPassBand' }, + '0022,0005' => { VR => 'CS', Name => 'PatientEyeMovementCommanded' }, + '0022,0006' => { VR => 'SQ', Name => 'PatientEyeMovementCommandCodeSeq' }, + '0022,0007' => { VR => 'FL', Name => 'SphericalLensPower' }, + '0022,0008' => { VR => 'FL', Name => 'CylinderLensPower' }, + '0022,0009' => { VR => 'FL', Name => 'CylinderAxis' }, + '0022,000A' => { VR => 'FL', Name => 'EmmetropicMagnification' }, + '0022,000B' => { VR => 'FL', Name => 'IntraOcularPressure' }, + '0022,000C' => { VR => 'FL', Name => 'HorizontalFieldOfView' }, + '0022,000D' => { VR => 'CS', Name => 'PupilDilated' }, + '0022,000E' => { VR => 'FL', Name => 'DegreeOfDilation' }, + '0022,0010' => { VR => 'FL', Name => 'StereoBaselineAngle' }, + '0022,0011' => { VR => 'FL', Name => 'StereoBaselineDisplacement' }, + '0022,0012' => { VR => 'FL', Name => 'StereoHorizontalPixelOffset' }, + '0022,0013' => { VR => 'FL', Name => 'StereoVerticalPixelOffset' }, + '0022,0014' => { VR => 'FL', Name => 'StereoRotation' }, + '0022,0015' => { VR => 'SQ', Name => 'AcquisitionDeviceTypeCodeSequence' }, + '0022,0016' => { VR => 'SQ', Name => 'IlluminationTypeCodeSequence' }, + '0022,0017' => { VR => 'SQ', Name => 'LightPathFilterTypeStackCodeSeq' }, + '0022,0018' => { VR => 'SQ', Name => 'ImagePathFilterTypeStackCodeSeq' }, + '0022,0019' => { VR => 'SQ', Name => 'LensesCodeSequence' }, + '0022,001A' => { VR => 'SQ', Name => 'ChannelDescriptionCodeSequence' }, + '0022,001B' => { VR => 'SQ', Name => 'RefractiveStateSequence' }, + '0022,001C' => { VR => 'SQ', Name => 'MydriaticAgentCodeSequence' }, + '0022,001D' => { VR => 'SQ', Name => 'RelativeImagePositionCodeSequence' }, + '0022,0020' => { VR => 'SQ', Name => 'StereoPairsSequence' }, + '0022,0021' => { VR => 'SQ', Name => 'LeftImageSequence' }, + '0022,0022' => { VR => 'SQ', Name => 'RightImageSequence' }, + '0022,0030' => { VR => 'FL', Name => 'AxialLengthOfTheEye' }, + '0022,0031' => { VR => 'SQ', Name => 'OphthalmicFrameLocationSequence' }, + '0022,0032' => { VR => 'FL', Name => 'ReferenceCoordinates' }, + '0022,0035' => { VR => 'FL', Name => 'DepthSpatialResolution' }, + '0022,0036' => { VR => 'FL', Name => 'MaximumDepthDistortion' }, + '0022,0037' => { VR => 'FL', Name => 'AlongScanSpatialResolution' }, + '0022,0038' => { VR => 'FL', Name => 'MaximumAlongScanDistortion' }, + '0022,0039' => { VR => 'CS', Name => 'OphthalmicImageOrientation' }, + '0022,0041' => { VR => 'FL', Name => 'DepthOfTransverseImage' }, + '0022,0042' => { VR => 'SQ', Name => 'MydriaticAgentConcUnitsSeq' }, + '0022,0048' => { VR => 'FL', Name => 'AcrossScanSpatialResolution' }, + '0022,0049' => { VR => 'FL', Name => 'MaximumAcrossScanDistortion' }, + '0022,004E' => { VR => 'DS', Name => 'MydriaticAgentConcentration' }, + '0022,0055' => { VR => 'FL', Name => 'IlluminationWaveLength' }, + '0022,0056' => { VR => 'FL', Name => 'IlluminationPower' }, + '0022,0057' => { VR => 'FL', Name => 'IlluminationBandwidth' }, + '0022,0058' => { VR => 'SQ', Name => 'MydriaticAgentSequence' }, + # GEMS_STDY_01 (ref 4) + '0023,1001' => { VR => 'SL', Name => 'NumberOfSeriesInStudy' }, + '0023,1002' => { VR => 'SL', Name => 'NumberOfUnarchivedSeries' }, + '0023,1010' => { VR => 'SS', Name => 'ReferenceImageField' }, + '0023,1050' => { VR => 'SS', Name => 'SummaryImage' }, + '0023,1070' => { VR => 'FD', Name => 'StartTimeSecsInFirstAxial' }, + '0023,1074' => { VR => 'SL', Name => 'NoofUpdatesToHeader' }, + '0023,107D' => { VR => 'SS', Name => 'IndicatesIfStudyHasCompleteInfo' }, + '0023,107D' => { VR => 'SS', Name => 'IndicatesIfTheStudyHasCompleteInfo' }, + # GEMS_SERS_01 (ref 4) + '0025,1006' => { VR => 'SS', Name => 'LastPulseSequenceUsed' }, + '0025,1007' => { VR => 'SL', Name => 'ImagesInSeries' }, + '0025,1010' => { VR => 'SL', Name => 'LandmarkCounter' }, + '0025,1011' => { VR => 'SS', Name => 'NumberOfAcquisitions' }, + '0025,1014' => { VR => 'SL', Name => 'IndicatesNoofUpdatesToHeader' }, + '0025,1017' => { VR => 'SL', Name => 'SeriesCompleteFlag' }, + '0025,1018' => { VR => 'SL', Name => 'NumberOfImagesArchived' }, + '0025,1019' => { VR => 'SL', Name => 'LastImageNumberUsed' }, + '0025,101A' => { VR => 'SH', Name => 'PrimaryReceiverSuiteAndHost' }, + # GEMS_IMAG_01 (ref 4) + '0027,1006' => { VR => 'SL', Name => 'ImageArchiveFlag' }, + '0027,1010' => { VR => 'SS', Name => 'ScoutType' }, + '0027,101C' => { VR => 'SL', Name => 'VmaMamp' }, + '0027,101D' => { VR => 'SS', Name => 'VmaPhase' }, + '0027,101E' => { VR => 'SL', Name => 'VmaMod' }, + '0027,101F' => { VR => 'SL', Name => 'VmaClip' }, + '0027,1020' => { VR => 'SS', Name => 'SmartScanOnOffFlag' }, + '0027,1030' => { VR => 'SH', Name => 'ForeignImageRevision' }, + '0027,1031' => { VR => 'SS', Name => 'ImagingMode' }, + '0027,1032' => { VR => 'SS', Name => 'PulseSequence' }, + '0027,1033' => { VR => 'SL', Name => 'ImagingOptions' }, + '0027,1035' => { VR => 'SS', Name => 'PlaneType' }, + '0027,1036' => { VR => 'SL', Name => 'ObliquePlane' }, + '0027,1040' => { VR => 'SH', Name => 'RASLetterOfImageLocation' }, + '0027,1041' => { VR => 'FL', Name => 'ImageLocation' }, + '0027,1042' => { VR => 'FL', Name => 'CenterRCoordOfPlaneImage' }, + '0027,1043' => { VR => 'FL', Name => 'CenterACoordOfPlaneImage' }, + '0027,1044' => { VR => 'FL', Name => 'CenterSCoordOfPlaneImage' }, + '0027,1045' => { VR => 'FL', Name => 'NormalRCoord' }, + '0027,1046' => { VR => 'FL', Name => 'NormalACoord' }, + '0027,1047' => { VR => 'FL', Name => 'NormalSCoord' }, + '0027,1048' => { VR => 'FL', Name => 'RCoordOfTopRightCorner' }, + '0027,1049' => { VR => 'FL', Name => 'ACoordOfTopRightCorner' }, + '0027,104A' => { VR => 'FL', Name => 'SCoordOfTopRightCorner' }, + '0027,104B' => { VR => 'FL', Name => 'RCoordOfBottomRightCorner' }, + '0027,104C' => { VR => 'FL', Name => 'ACoordOfBottomRightCorner' }, + '0027,104D' => { VR => 'FL', Name => 'SCoordOfBottomRightCorner' }, + '0027,1050' => { VR => 'FL', Name => 'TableStartLocation' }, + '0027,1051' => { VR => 'FL', Name => 'TableEndLocation' }, + '0027,1052' => { VR => 'SH', Name => 'RASLetterForSideOfImage' }, + '0027,1053' => { VR => 'SH', Name => 'RASLetterForAnteriorPosterior' }, + '0027,1054' => { VR => 'SH', Name => 'RASLetterForScoutStartLoc' }, + '0027,1055' => { VR => 'SH', Name => 'RASLetterForScoutEndLoc' }, + '0027,1060' => { VR => 'FL', Name => 'ImageDimensionX' }, + '0027,1061' => { VR => 'FL', Name => 'ImageDimensionY' }, + '0027,1062' => { VR => 'FL', Name => 'NumberOfExcitations' }, + # image presentation group + '0028,0000' => { VR => 'UL', Name => 'ImagePresentationGroupLength' }, + '0028,0002' => { VR => 'US', Name => 'SamplesPerPixel' }, + '0028,0003' => { VR => 'US', Name => 'SamplesPerPixelUsed' }, + '0028,0004' => { VR => 'CS', Name => 'PhotometricInterpretation' }, + '0028,0005' => { VR => 'US', Name => 'ImageDimensions' }, + '0028,0006' => { VR => 'US', Name => 'PlanarConfiguration' }, + '0028,0008' => { VR => 'IS', Name => 'NumberOfFrames' }, + '0028,0009' => { VR => 'AT', Name => 'FrameIncrementPointer' }, + '0028,000A' => { VR => 'AT', Name => 'FrameDimensionPointer' }, + '0028,0010' => { VR => 'US', Name => 'Rows' }, + '0028,0011' => { VR => 'US', Name => 'Columns' }, + '0028,0012' => { VR => 'US', Name => 'Planes' }, + '0028,0014' => { VR => 'US', Name => 'UltrasoundColorDataPresent' }, + '0028,0030' => { VR => 'DS', Name => 'PixelSpacing' }, + '0028,0031' => { VR => 'DS', Name => 'ZoomFactor' }, + '0028,0032' => { VR => 'DS', Name => 'ZoomCenter' }, + '0028,0034' => { VR => 'IS', Name => 'PixelAspectRatio' }, + '0028,0040' => { VR => 'CS', Name => 'ImageFormat' }, + '0028,0050' => { VR => 'LO', Name => 'ManipulatedImage' }, + '0028,0051' => { VR => 'CS', Name => 'CorrectedImage' }, + '0028,005F' => { VR => 'LO', Name => 'CompressionRecognitionCode' }, + '0028,0060' => { VR => 'CS', Name => 'CompressionCode' }, + '0028,0061' => { VR => 'SH', Name => 'CompressionOriginator' }, + '0028,0062' => { VR => 'LO', Name => 'CompressionLabel' }, + '0028,0063' => { VR => 'SH', Name => 'CompressionDescription' }, + '0028,0065' => { VR => 'CS', Name => 'CompressionSequence' }, + '0028,0066' => { VR => 'AT', Name => 'CompressionStepPointers' }, + '0028,0068' => { VR => 'US', Name => 'RepeatInterval' }, + '0028,0069' => { VR => 'US', Name => 'BitsGrouped' }, + '0028,0070' => { VR => 'US', Name => 'PerimeterTable' }, + '0028,0071' => { VR => 'US', Name => 'PerimeterValue' }, + '0028,0080' => { VR => 'US', Name => 'PredictorRows' }, + '0028,0081' => { VR => 'US', Name => 'PredictorColumns' }, + '0028,0082' => { VR => 'US', Name => 'PredictorConstants' }, + '0028,0090' => { VR => 'CS', Name => 'BlockedPixels' }, + '0028,0091' => { VR => 'US', Name => 'BlockRows' }, + '0028,0092' => { VR => 'US', Name => 'BlockColumns' }, + '0028,0093' => { VR => 'US', Name => 'RowOverlap' }, + '0028,0094' => { VR => 'US', Name => 'ColumnOverlap' }, + '0028,0100' => { VR => 'US', Name => 'BitsAllocated' }, + '0028,0101' => { VR => 'US', Name => 'BitsStored' }, + '0028,0102' => { VR => 'US', Name => 'HighBit' }, + '0028,0103' => { VR => 'US', Name => 'PixelRepresentation', PrintConv => { 0 => 'Unsigned', 1 => 'Signed' } }, + '0028,0104' => { VR => 'US', Name => 'SmallestValidPixelValue' }, + '0028,0105' => { VR => 'US', Name => 'LargestValidPixelValue' }, + '0028,0106' => { VR => 'US', Name => 'SmallestImagePixelValue' }, + '0028,0107' => { VR => 'US', Name => 'LargestImagePixelValue' }, + '0028,0108' => { VR => 'US', Name => 'SmallestPixelValueInSeries' }, + '0028,0109' => { VR => 'US', Name => 'LargestPixelValueInSeries' }, + '0028,0110' => { VR => 'US', Name => 'SmallestImagePixelValueInPlane' }, + '0028,0111' => { VR => 'US', Name => 'LargestImagePixelValueInPlane' }, + '0028,0120' => { VR => 'US', Name => 'PixelPaddingValue' }, + '0028,0121' => { VR => 'US', Name => 'PixelPaddingRangeLimit' }, + '0028,0200' => { VR => 'US', Name => 'ImageLocation' }, + '0028,0300' => { VR => 'CS', Name => 'QualityControlImage' }, + '0028,0301' => { VR => 'CS', Name => 'BurnedInAnnotation' }, + '0028,0400' => { VR => 'LO', Name => 'TransformLabel' }, + '0028,0401' => { VR => 'LO', Name => 'TransformVersionNumber' }, + '0028,0402' => { VR => 'US', Name => 'NumberOfTransformSteps' }, + '0028,0403' => { VR => 'LO', Name => 'SequenceOfCompressedData' }, + '0028,0404' => { VR => 'AT', Name => 'DetailsOfCoefficients' }, + '0028,04x2' => { VR => 'LO', Name => 'CoefficientCoding' }, + '0028,04x3' => { VR => 'AT', Name => 'CoefficientCodingPointers' }, + '0028,0700' => { VR => 'LO', Name => 'DCTLabel' }, + '0028,0701' => { VR => 'CS', Name => 'DataBlockDescription' }, + '0028,0702' => { VR => 'AT', Name => 'DataBlock' }, + '0028,0710' => { VR => 'US', Name => 'NormalizationFactorFormat' }, + '0028,0720' => { VR => 'US', Name => 'ZonalMapNumberFormat' }, + '0028,0721' => { VR => 'AT', Name => 'ZonalMapLocation' }, + '0028,0722' => { VR => 'US', Name => 'ZonalMapFormat' }, + '0028,0730' => { VR => 'US', Name => 'AdaptiveMapFormat' }, + '0028,0740' => { VR => 'US', Name => 'CodeNumberFormat' }, + '0028,08x0' => { VR => 'CS', Name => 'CodeLabel' }, + '0028,08x2' => { VR => 'US', Name => 'NumberOfTables' }, + '0028,08x3' => { VR => 'AT', Name => 'CodeTableLocation' }, + '0028,08x4' => { VR => 'US', Name => 'BitsForCodeWord' }, + '0028,08x8' => { VR => 'AT', Name => 'ImageDataLocation' }, + '0028,1040' => { VR => 'CS', Name => 'PixelIntensityRelationship' }, + '0028,0A02' => { VR => 'CS', Name => 'PixelSpacingCalibrationType' }, + '0028,0A04' => { VR => 'LO', Name => 'PixelSpacingCalibrationDescription' }, + '0028,1040' => { VR => 'CS', Name => 'PixelIntensityRelationship' }, + '0028,1041' => { VR => 'SS', Name => 'PixelIntensityRelationshipSign' }, + '0028,1050' => { VR => 'DS', Name => 'WindowCenter' }, + '0028,1051' => { VR => 'DS', Name => 'WindowWidth' }, + '0028,1052' => { VR => 'DS', Name => 'RescaleIntercept' }, + '0028,1053' => { VR => 'DS', Name => 'RescaleSlope' }, + '0028,1054' => { VR => 'LO', Name => 'RescaleType' }, + '0028,1055' => { VR => 'LO', Name => 'WindowCenterAndWidthExplanation' }, + '0028,1056' => { VR => 'CS', Name => 'VOI_LUTFunction' }, + '0028,1080' => { VR => 'CS', Name => 'GrayScale' }, + '0028,1090' => { VR => 'CS', Name => 'RecommendedViewingMode' }, + '0028,1100' => { VR => 'SS', Name => 'GrayLookupTableDescriptor' }, + '0028,1101' => { VR => 'SS', Name => 'RedPaletteColorTableDescriptor' }, + '0028,1102' => { VR => 'SS', Name => 'GreenPaletteColorTableDescriptor' }, + '0028,1103' => { VR => 'SS', Name => 'BluePaletteColorTableDescriptor' }, + '0028,1111' => { VR => 'SS', Name => 'LargeRedPaletteColorTableDescr' }, + '0028,1112' => { VR => 'SS', Name => 'LargeGreenPaletteColorTableDescr' }, + '0028,1113' => { VR => 'SS', Name => 'LargeBluePaletteColorTableDescr' }, + '0028,1199' => { VR => 'UI', Name => 'PaletteColorTableUID' }, + '0028,1200' => { VR => 'US', Name => 'GrayLookupTableData' }, + '0028,1201' => { VR => 'OW', Name => 'RedPaletteColorTableData' }, + '0028,1202' => { VR => 'OW', Name => 'GreenPaletteColorTableData' }, + '0028,1203' => { VR => 'OW', Name => 'BluePaletteColorTableData' }, + '0028,1211' => { VR => 'OW', Name => 'LargeRedPaletteColorTableData', Binary => 1 }, + '0028,1212' => { VR => 'OW', Name => 'LargeGreenPaletteColorTableData', Binary => 1 }, + '0028,1213' => { VR => 'OW', Name => 'LargeBluePaletteColorTableData', Binary => 1 }, + '0028,1214' => { VR => 'UI', Name => 'LargePaletteColorLookupTableUID' }, + '0028,1221' => { VR => 'OW', Name => 'SegmentedRedColorTableData' }, + '0028,1222' => { VR => 'OW', Name => 'SegmentedGreenColorTableData' }, + '0028,1223' => { VR => 'OW', Name => 'SegmentedBlueColorTableData' }, + '0028,1300' => { VR => 'CS', Name => 'BreastImplantPresent' }, + '0028,1350' => { VR => 'CS', Name => 'PartialView' }, + '0028,1351' => { VR => 'ST', Name => 'PartialViewDescription' }, + '0028,1352' => { VR => 'SQ', Name => 'PartialViewCodeSequence' }, + '0028,135A' => { VR => 'CS', Name => 'SpatialLocationsPreserved' }, + '0028,1402' => { VR => 'CS', Name => 'DataPathAssignment' }, + '0028,1404' => { VR => 'SQ', Name => 'BlendingLUT1Sequence' }, + '0028,1406' => { VR => 'FD', Name => 'BlendingWeightConstant' }, + '0028,1408' => { VR => 'OW', Name => 'BlendingLookupTableData' }, + '0028,140C' => { VR => 'SQ', Name => 'BlendingLUT2Sequence' }, + '0028,140E' => { VR => 'CS', Name => 'DataPathID' }, + '0028,140F' => { VR => 'CS', Name => 'RGBLUTTransferFunction' }, + '0028,1410' => { VR => 'CS', Name => 'AlphaLUTTransferFunction' }, + '0028,2000' => { VR => 'OB', Name => 'ICCProfile' }, + '0028,2110' => { VR => 'CS', Name => 'LossyImageCompression' }, + '0028,2112' => { VR => 'DS', Name => 'LossyImageCompressionRatio' }, + '0028,2114' => { VR => 'CS', Name => 'LossyImageCompressionMethod' }, + '0028,3000' => { VR => 'SQ', Name => 'ModalityLUTSequence' }, + '0028,3002' => { VR => 'US', Name => 'LUTDescriptor' }, + '0028,3003' => { VR => 'LO', Name => 'LUTExplanation' }, + '0028,3004' => { VR => 'LO', Name => 'ModalityLUTType' }, + '0028,3006' => { VR => 'SS', Name => 'LUTData' }, + '0028,3010' => { VR => 'SQ', Name => 'VOILUTSequence' }, + '0028,3110' => { VR => 'SQ', Name => 'SoftcopyVOILUTSequence' }, + '0028,4000' => { VR => 'LT', Name => 'ImagePresentationComments' }, + '0028,5000' => { VR => 'SQ', Name => 'BiPlaneAcquisitionSequence' }, + '0028,6010' => { VR => 'US', Name => 'RepresentativeFrameNumber' }, + '0028,6020' => { VR => 'US', Name => 'FrameNumbersOfInterest' }, + '0028,6022' => { VR => 'LO', Name => 'FrameOfInterestDescription' }, + '0028,6023' => { VR => 'CS', Name => 'FrameOfInterestType' }, + '0028,6030' => { VR => 'US', Name => 'MaskPointers' }, + '0028,6040' => { VR => 'US', Name => 'RWavePointer' }, + '0028,6100' => { VR => 'SQ', Name => 'MaskSubtractionSequence' }, + '0028,6101' => { VR => 'CS', Name => 'MaskOperation' }, + '0028,6102' => { VR => 'US', Name => 'ApplicableFrameRange' }, + '0028,6110' => { VR => 'US', Name => 'MaskFrameNumbers' }, + '0028,6112' => { VR => 'US', Name => 'ContrastFrameAveraging' }, + '0028,6114' => { VR => 'FL', Name => 'MaskSubPixelShift' }, + '0028,6120' => { VR => 'SS', Name => 'TIDOffset' }, + '0028,6190' => { VR => 'ST', Name => 'MaskOperationExplanation' }, + '0028,7FE0' => { VR => 'UT', Name => 'PixelDataProviderURL' }, + '0028,9001' => { VR => 'UL', Name => 'DataPointRows' }, + '0028,9002' => { VR => 'UL', Name => 'DataPointColumns' }, + '0028,9003' => { VR => 'CS', Name => 'SignalDomainColumns' }, + '0028,9099' => { VR => 'US', Name => 'LargestMonochromePixelValue' }, + '0028,9108' => { VR => 'CS', Name => 'DataRepresentation' }, + '0028,9110' => { VR => 'SQ', Name => 'PixelMeasuresSequence' }, + '0028,9132' => { VR => 'SQ', Name => 'FrameVOILUTSequence' }, + '0028,9145' => { VR => 'SQ', Name => 'PixelValueTransformationSequence' }, + '0028,9235' => { VR => 'CS', Name => 'SignalDomainRows' }, + '0028,9411' => { VR => 'FL', Name => 'DisplayFilterPercentage' }, + '0028,9415' => { VR => 'SQ', Name => 'FramePixelShiftSequence' }, + '0028,9416' => { VR => 'US', Name => 'SubtractionItemID' }, + '0028,9422' => { VR => 'SQ', Name => 'PixelIntensityRelationshipLUTSeq' }, + '0028,9443' => { VR => 'SQ', Name => 'FramePixelDataPropertiesSequence' }, + '0028,9444' => { VR => 'CS', Name => 'GeometricalProperties' }, + '0028,9445' => { VR => 'FL', Name => 'GeometricMaximumDistortion' }, + '0028,9446' => { VR => 'CS', Name => 'ImageProcessingApplied' }, + '0028,9454' => { VR => 'CS', Name => 'MaskSelectionMode' }, + '0028,9474' => { VR => 'CS', Name => 'LUTFunction' }, + '0028,9478' => { VR => 'FL', Name => 'MaskVisibilityPercentage' }, + '0028,9501' => { VR => 'SQ', Name => 'PixelShiftSequence' }, + '0028,9502' => { VR => 'SQ', Name => 'RegionPixelShiftSequence' }, + '0028,9503' => { VR => 'SS', Name => 'VerticesOfTheRegion' }, + '0028,9506' => { VR => 'US', Name => 'PixelShiftFrameRange' }, + '0028,9507' => { VR => 'US', Name => 'LUTFrameRange' }, + '0028,9520' => { VR => 'DS', Name => 'ImageToEquipmentMappingMatrix' }, + '0028,9537' => { VR => 'CS', Name => 'EquipmentCoordinateSystemID' }, + # GEMS_IMPS_01 (ref 4) + '0029,1004' => { VR => 'SL', Name => 'LowerRangeOfPixels1a' }, + '0029,1005' => { VR => 'DS', Name => 'LowerRangeOfPixels1b' }, + '0029,1006' => { VR => 'DS', Name => 'LowerRangeOfPixels1c' }, + '0029,1007' => { VR => 'SL', Name => 'LowerRangeOfPixels1d' }, + '0029,1008' => { VR => 'SH', Name => 'LowerRangeOfPixels1e' }, + '0029,1009' => { VR => 'SH', Name => 'LowerRangeOfPixels1f' }, + '0029,100A' => { VR => 'SS', Name => 'LowerRangeOfPixels1g' }, + '0029,1015' => { VR => 'SL', Name => 'LowerRangeOfPixels1h' }, + '0029,1016' => { VR => 'SL', Name => 'LowerRangeOfPixels1i' }, + '0029,1017' => { VR => 'SL', Name => 'LowerRangeOfPixels2' }, + '0029,1018' => { VR => 'SL', Name => 'UpperRangeOfPixels2' }, + '0029,101A' => { VR => 'SL', Name => 'LenOfTotHdrInBytes' }, + '0029,1026' => { VR => 'SS', Name => 'VersionOfTheHdrStruct' }, + '0029,1034' => { VR => 'SL', Name => 'AdvantageCompOverflow' }, + '0029,1035' => { VR => 'SL', Name => 'AdvantageCompUnderflow' }, + # study group + '0032,0000' => { VR => 'UL', Name => 'StudyGroupLength' }, + '0032,000A' => { VR => 'CS', Name => 'StudyStatusID' }, + '0032,000C' => { VR => 'CS', Name => 'StudyPriorityID' }, + '0032,0012' => { VR => 'LO', Name => 'StudyIDIssuer' }, + '0032,0032' => { VR => 'DA', Name => 'StudyVerifiedDate' }, + '0032,0033' => { VR => 'TM', Name => 'StudyVerifiedTime' }, + '0032,0034' => { VR => 'DA', Name => 'StudyReadDate' }, + '0032,0035' => { VR => 'TM', Name => 'StudyReadTime' }, + '0032,1000' => { VR => 'DA', Name => 'ScheduledStudyStartDate' }, + '0032,1001' => { VR => 'TM', Name => 'ScheduledStudyStartTime' }, + '0032,1010' => { VR => 'DA', Name => 'ScheduledStudyStopDate' }, + '0032,1011' => { VR => 'TM', Name => 'ScheduledStudyStopTime' }, + '0032,1020' => { VR => 'LO', Name => 'ScheduledStudyLocation' }, + '0032,1021' => { VR => 'AE', Name => 'ScheduledStudyLocationAETitle' }, + '0032,1030' => { VR => 'LO', Name => 'ReasonForStudy' }, + '0032,1031' => { VR => 'SQ', Name => 'RequestingPhysicianIDSequence' }, + '0032,1032' => { VR => 'PN', Name => 'RequestingPhysician' }, + '0032,1033' => { VR => 'LO', Name => 'RequestingService' }, + '0032,1040' => { VR => 'DA', Name => 'StudyArrivalDate' }, + '0032,1041' => { VR => 'TM', Name => 'StudyArrivalTime' }, + '0032,1050' => { VR => 'DA', Name => 'StudyCompletionDate' }, + '0032,1051' => { VR => 'TM', Name => 'StudyCompletionTime' }, + '0032,1055' => { VR => 'CS', Name => 'StudyComponentStatusID' }, + '0032,1060' => { VR => 'LO', Name => 'RequestedProcedureDescription' }, + '0032,1064' => { VR => 'SQ', Name => 'RequestedProcedureCodeSequence' }, + '0032,1070' => { VR => 'LO', Name => 'RequestedContrastAgent' }, + '0032,4000' => { VR => 'LT', Name => 'StudyComments' }, + # visit group + '0038,0004' => { VR => 'SQ', Name => 'ReferencedPatientAliasSequence' }, + '0038,0008' => { VR => 'CS', Name => 'VisitStatusID' }, + '0038,0010' => { VR => 'LO', Name => 'AdmissionID' }, + '0038,0011' => { VR => 'LO', Name => 'IssuerOfAdmissionID' }, + '0038,0016' => { VR => 'LO', Name => 'RouteOfAdmissions' }, + '0038,001A' => { VR => 'DA', Name => 'ScheduledAdmissionDate' }, + '0038,001B' => { VR => 'TM', Name => 'ScheduledAdmissionTime' }, + '0038,001C' => { VR => 'DA', Name => 'ScheduledDischargeDate' }, + '0038,001D' => { VR => 'TM', Name => 'ScheduledDischargeTime' }, + '0038,001E' => { VR => 'LO', Name => 'ScheduledPatientInstitResidence' }, + '0038,0020' => { VR => 'DA', Name => 'AdmittingDate' }, + '0038,0021' => { VR => 'TM', Name => 'AdmittingTime' }, + '0038,0030' => { VR => 'DA', Name => 'DischargeDate' }, + '0038,0032' => { VR => 'TM', Name => 'DischargeTime' }, + '0038,0040' => { VR => 'LO', Name => 'DischargeDiagnosisDescription' }, + '0038,0044' => { VR => 'SQ', Name => 'DischargeDiagnosisCodeSequence' }, + '0038,0050' => { VR => 'LO', Name => 'SpecialNeeds' }, + '0038,0060' => { VR => 'LO', Name => 'ServiceEpisodeID' }, + '0038,0061' => { VR => 'LO', Name => 'IssuerOfServiceEpisodeID' }, + '0038,0062' => { VR => 'LO', Name => 'ServiceEpisodeDescription' }, + '0038,0100' => { VR => 'SQ', Name => 'PertinentDocumentsSequence' }, + '0038,0300' => { VR => 'LO', Name => 'CurrentPatientLocation' }, + '0038,0400' => { VR => 'LO', Name => 'PatientInstitutionResidence' }, + '0038,0500' => { VR => 'LO', Name => 'PatientState' }, + '0038,0502' => { VR => 'SQ', Name => 'PatientClinicalTrialParticipSeq' }, + '0038,4000' => { VR => 'LT', Name => 'VisitComments' }, + '003A,0004' => { VR => 'CS', Name => 'WaveformOriginality' }, + '003A,0005' => { VR => 'US', Name => 'NumberOfWaveformChannels' }, + '003A,0010' => { VR => 'UL', Name => 'NumberOfWaveformSamples' }, + '003A,001A' => { VR => 'DS', Name => 'SamplingFrequency' }, + '003A,0020' => { VR => 'SH', Name => 'MultiplexGroupLabel' }, + '003A,0200' => { VR => 'SQ', Name => 'ChannelDefinitionSequence' }, + '003A,0202' => { VR => 'IS', Name => 'WaveformChannelNumber' }, + '003A,0203' => { VR => 'SH', Name => 'ChannelLabel' }, + '003A,0205' => { VR => 'CS', Name => 'ChannelStatus' }, + '003A,0208' => { VR => 'SQ', Name => 'ChannelSourceSequence' }, + '003A,0209' => { VR => 'SQ', Name => 'ChannelSourceModifiersSequence' }, + '003A,020A' => { VR => 'SQ', Name => 'SourceWaveformSequence' }, + '003A,020C' => { VR => 'LO', Name => 'ChannelDerivationDescription' }, + '003A,0210' => { VR => 'DS', Name => 'ChannelSensitivity' }, + '003A,0211' => { VR => 'SQ', Name => 'ChannelSensitivityUnitsSequence' }, + '003A,0212' => { VR => 'DS', Name => 'ChannelSensitivityCorrectionFactor' }, + '003A,0213' => { VR => 'DS', Name => 'ChannelBaseline' }, + '003A,0214' => { VR => 'DS', Name => 'ChannelTimeSkew' }, + '003A,0215' => { VR => 'DS', Name => 'ChannelSampleSkew' }, + '003A,0218' => { VR => 'DS', Name => 'ChannelOffset' }, + '003A,021A' => { VR => 'US', Name => 'WaveformBitsStored' }, + '003A,0220' => { VR => 'DS', Name => 'FilterLowFrequency' }, + '003A,0221' => { VR => 'DS', Name => 'FilterHighFrequency' }, + '003A,0222' => { VR => 'DS', Name => 'NotchFilterFrequency' }, + '003A,0223' => { VR => 'DS', Name => 'NotchFilterBandwidth' }, + '003A,0230' => { VR => 'FL', Name => 'WaveformDataDisplayScale' }, + '003A,0231' => { VR => 'US', Name => 'WaveformDisplayBkgCIELabValue' }, + '003A,0240' => { VR => 'SQ', Name => 'WaveformPresentationGroupSequence' }, + '003A,0241' => { VR => 'US', Name => 'PresentationGroupNumber' }, + '003A,0242' => { VR => 'SQ', Name => 'ChannelDisplaySequence' }, + '003A,0244' => { VR => 'US', Name => 'ChannelRecommendDisplayCIELabValue' }, + '003A,0245' => { VR => 'FL', Name => 'ChannelPosition' }, + '003A,0246' => { VR => 'CS', Name => 'DisplayShadingFlag' }, + '003A,0247' => { VR => 'FL', Name => 'FractionalChannelDisplayScale' }, + '003A,0248' => { VR => 'FL', Name => 'AbsoluteChannelDisplayScale' }, + '003A,0300' => { VR => 'SQ', Name => 'MultiplexAudioChannelsDescrCodeSeq' }, + '003A,0301' => { VR => 'IS', Name => 'ChannelIdentificationCode' }, + '003A,0302' => { VR => 'CS', Name => 'ChannelMode' }, + '0040,0001' => { VR => 'AE', Name => 'ScheduledStationAETitle' }, + '0040,0002' => { VR => 'DA', Name => 'ScheduledProcedureStepStartDate' }, + '0040,0003' => { VR => 'TM', Name => 'ScheduledProcedureStepStartTime' }, + '0040,0004' => { VR => 'DA', Name => 'ScheduledProcedureStepEndDate' }, + '0040,0005' => { VR => 'TM', Name => 'ScheduledProcedureStepEndTime' }, + '0040,0006' => { VR => 'PN', Name => 'ScheduledPerformingPhysiciansName' }, + '0040,0007' => { VR => 'LO', Name => 'ScheduledProcedureStepDescription' }, + '0040,0008' => { VR => 'SQ', Name => 'ScheduledProtocolCodeSequence' }, + '0040,0009' => { VR => 'SH', Name => 'ScheduledProcedureStepID' }, + '0040,000A' => { VR => 'SQ', Name => 'StageCodeSequence' }, + '0040,000B' => { VR => 'SQ', Name => 'ScheduledPerformingPhysicianIDSeq' }, + '0040,0010' => { VR => 'SH', Name => 'ScheduledStationName' }, + '0040,0011' => { VR => 'SH', Name => 'ScheduledProcedureStepLocation' }, + '0040,0012' => { VR => 'LO', Name => 'PreMedication' }, + '0040,0020' => { VR => 'CS', Name => 'ScheduledProcedureStepStatus' }, + '0040,0031' => { VR => 'UT', Name => 'LocalNamespaceEntityID' }, + '0040,0032' => { VR => 'UT', Name => 'UniversalEntityID' }, + '0040,0033' => { VR => 'CS', Name => 'UniversalEntityIDType' }, + '0040,0035' => { VR => 'CS', Name => 'IdentifierTypeCode' }, + '0040,0036' => { VR => 'SQ', Name => 'AssigningFacilitySequence' }, + '0040,0100' => { VR => 'SQ', Name => 'ScheduledProcedureStepSequence' }, + '0040,0220' => { VR => 'SQ', Name => 'ReferencedNonImageCompositeSOPSeq' }, + '0040,0241' => { VR => 'AE', Name => 'PerformedStationAETitle' }, + '0040,0242' => { VR => 'SH', Name => 'PerformedStationName' }, + '0040,0243' => { VR => 'SH', Name => 'PerformedLocation' }, + '0040,0244' => { VR => 'DA', Name => 'PerformedProcedureStepStartDate' }, + '0040,0245' => { VR => 'TM', Name => 'PerformedProcedureStepStartTime' }, + '0040,0250' => { VR => 'DA', Name => 'PerformedProcedureStepEndDate' }, + '0040,0251' => { VR => 'TM', Name => 'PerformedProcedureStepEndTime' }, + '0040,0252' => { VR => 'CS', Name => 'PerformedProcedureStepStatus' }, + '0040,0253' => { VR => 'SH', Name => 'PerformedProcedureStepID' }, + '0040,0254' => { VR => 'LO', Name => 'PerformedProcedureStepDescription' }, + '0040,0255' => { VR => 'LO', Name => 'PerformedProcedureTypeDescription' }, + '0040,0260' => { VR => 'SQ', Name => 'PerformedProtocolCodeSequence' }, + '0040,0261' => { VR => 'CS', Name => 'PerformedProtocolType' }, + '0040,0270' => { VR => 'SQ', Name => 'ScheduledStepAttributesSequence' }, + '0040,0275' => { VR => 'SQ', Name => 'RequestAttributesSequence' }, + '0040,0280' => { VR => 'ST', Name => 'CommentsOnPerformedProcedureStep' }, + '0040,0281' => { VR => 'SQ', Name => 'ProcStepDiscontinueReasonCodeSeq' }, + '0040,0293' => { VR => 'SQ', Name => 'QuantitySequence' }, + '0040,0294' => { VR => 'DS', Name => 'Quantity' }, + '0040,0295' => { VR => 'SQ', Name => 'MeasuringUnitsSequence' }, + '0040,0296' => { VR => 'SQ', Name => 'BillingItemSequence' }, + '0040,0300' => { VR => 'US', Name => 'TotalTimeOfFluoroscopy' }, + '0040,0301' => { VR => 'US', Name => 'TotalNumberOfExposures' }, + '0040,0302' => { VR => 'US', Name => 'EntranceDose' }, + '0040,0303' => { VR => 'US', Name => 'ExposedArea' }, + '0040,0306' => { VR => 'DS', Name => 'DistanceSourceToEntrance' }, + '0040,0307' => { VR => 'DS', Name => 'DistanceSourceToSupport' }, + '0040,030E' => { VR => 'SQ', Name => 'ExposureDoseSequence' }, + '0040,0310' => { VR => 'ST', Name => 'CommentsOnRadiationDose' }, + '0040,0312' => { VR => 'DS', Name => 'XRayOutput' }, + '0040,0314' => { VR => 'DS', Name => 'HalfValueLayer' }, + '0040,0316' => { VR => 'DS', Name => 'OrganDose' }, + '0040,0318' => { VR => 'CS', Name => 'OrganExposed' }, + '0040,0320' => { VR => 'SQ', Name => 'BillingProcedureStepSequence' }, + '0040,0321' => { VR => 'SQ', Name => 'FilmConsumptionSequence' }, + '0040,0324' => { VR => 'SQ', Name => 'BillingSuppliesAndDevicesSequence' }, + '0040,0330' => { VR => 'SQ', Name => 'ReferencedProcedureStepSequence' }, + '0040,0340' => { VR => 'SQ', Name => 'PerformedSeriesSequence' }, + '0040,0400' => { VR => 'LT', Name => 'CommentsOnScheduledProcedureStep' }, + '0040,0440' => { VR => 'SQ', Name => 'ProtocolContextSequence' }, + '0040,0441' => { VR => 'SQ', Name => 'ContentItemModifierSequence' }, + '0040,050A' => { VR => 'LO', Name => 'SpecimenAccessionNumber' }, + '0040,0512' => { VR => 'LO', Name => 'ContainerIdentifier' }, + '0040,051A' => { VR => 'LO', Name => 'ContainerDescription' }, + '0040,0550' => { VR => 'SQ', Name => 'SpecimenSequence' }, + '0040,0551' => { VR => 'LO', Name => 'SpecimenIdentifier' }, + '0040,0552' => { VR => 'SQ', Name => 'SpecimenDescriptionSequenceTrial' }, + '0040,0553' => { VR => 'ST', Name => 'SpecimenDescriptionTrial' }, + '0040,0554' => { VR => 'UI', Name => 'SpecimenUID' }, + '0040,0555' => { VR => 'SQ', Name => 'AcquisitionContextSequence' }, + '0040,0556' => { VR => 'ST', Name => 'AcquisitionContextDescription' }, + '0040,059A' => { VR => 'SQ', Name => 'SpecimenTypeCodeSequence' }, + '0040,0600' => { VR => 'LO', Name => 'SpecimenShortDescription' }, + '0040,06FA' => { VR => 'LO', Name => 'SlideIdentifier' }, + '0040,071A' => { VR => 'SQ', Name => 'ImageCenterPointCoordinatesSeq' }, + '0040,072A' => { VR => 'DS', Name => 'XOffsetInSlideCoordinateSystem' }, + '0040,073A' => { VR => 'DS', Name => 'YOffsetInSlideCoordinateSystem' }, + '0040,074A' => { VR => 'DS', Name => 'ZOffsetInSlideCoordinateSystem' }, + '0040,08D8' => { VR => 'SQ', Name => 'PixelSpacingSequence' }, + '0040,08DA' => { VR => 'SQ', Name => 'CoordinateSystemAxisCodeSequence' }, + '0040,08EA' => { VR => 'SQ', Name => 'MeasurementUnitsCodeSequence' }, + '0040,09F8' => { VR => 'SQ', Name => 'VitalStainCodeSequenceTrial' }, + '0040,1001' => { VR => 'SH', Name => 'RequestedProcedureID' }, + '0040,1002' => { VR => 'LO', Name => 'ReasonForRequestedProcedure' }, + '0040,1003' => { VR => 'SH', Name => 'RequestedProcedurePriority' }, + '0040,1004' => { VR => 'LO', Name => 'PatientTransportArrangements' }, + '0040,1005' => { VR => 'LO', Name => 'RequestedProcedureLocation' }, + '0040,1006' => { VR => 'SH', Name => 'PlacerOrderNumber-Procedure' }, + '0040,1007' => { VR => 'SH', Name => 'FillerOrderNumber-Procedure' }, + '0040,1008' => { VR => 'LO', Name => 'ConfidentialityCode' }, + '0040,1009' => { VR => 'SH', Name => 'ReportingPriority' }, + '0040,100A' => { VR => 'SQ', Name => 'ReasonForRequestedProcedureCodeSeq' }, + '0040,1010' => { VR => 'PN', Name => 'NamesOfIntendedRecipientsOfResults' }, + '0040,1011' => { VR => 'SQ', Name => 'IntendedRecipientsOfResultsIDSeq' }, + '0040,1101' => { VR => 'SQ', Name => 'PersonIdentificationCodeSequence' }, + '0040,1102' => { VR => 'ST', Name => 'PersonAddress' }, + '0040,1103' => { VR => 'LO', Name => 'PersonTelephoneNumbers' }, + '0040,1400' => { VR => 'LT', Name => 'RequestedProcedureComments' }, + '0040,2001' => { VR => 'LO', Name => 'ReasonForImagingServiceRequest' }, + '0040,2004' => { VR => 'DA', Name => 'IssueDateOfImagingServiceRequest' }, + '0040,2005' => { VR => 'TM', Name => 'IssueTimeOfImagingServiceRequest' }, + '0040,2006' => { VR => 'SH', Name => 'PlacerOrderNum-ImagingServiceReq' }, + '0040,2007' => { VR => 'SH', Name => 'FillerOrderNum-ImagingServiceReq' }, + '0040,2008' => { VR => 'PN', Name => 'OrderEnteredBy' }, + '0040,2009' => { VR => 'SH', Name => 'OrderEntererLocation' }, + '0040,2010' => { VR => 'SH', Name => 'OrderCallbackPhoneNumber' }, + '0040,2016' => { VR => 'LO', Name => 'PlacerOrderNum-ImagingServiceReq' }, + '0040,2017' => { VR => 'LO', Name => 'FillerOrderNum-ImagingServiceReq' }, + '0040,2400' => { VR => 'LT', Name => 'ImagingServiceRequestComments' }, + '0040,3001' => { VR => 'LO', Name => 'ConfidentialityOnPatientDataDescr' }, + '0040,4001' => { VR => 'CS', Name => 'GenPurposeScheduledProcStepStatus' }, + '0040,4002' => { VR => 'CS', Name => 'GenPurposePerformedProcStepStatus' }, + '0040,4003' => { VR => 'CS', Name => 'GenPurposeSchedProcStepPriority' }, + '0040,4004' => { VR => 'SQ', Name => 'SchedProcessingApplicationsCodeSeq' }, + '0040,4005' => { VR => 'DT', Name => 'SchedProcedureStepStartDateAndTime' }, + '0040,4006' => { VR => 'CS', Name => 'MultipleCopiesFlag' }, + '0040,4007' => { VR => 'SQ', Name => 'PerformedProcessingAppsCodeSeq' }, + '0040,4009' => { VR => 'SQ', Name => 'HumanPerformerCodeSequence' }, + '0040,4010' => { VR => 'DT', Name => 'SchedProcStepModificationDateTime' }, + '0040,4011' => { VR => 'DT', Name => 'ExpectedCompletionDateAndTime' }, + '0040,4015' => { VR => 'SQ', Name => 'ResultingGenPurposePerfProcStepSeq' }, + '0040,4016' => { VR => 'SQ', Name => 'RefGenPurposeSchedProcStepSeq' }, + '0040,4018' => { VR => 'SQ', Name => 'ScheduledWorkitemCodeSequence' }, + '0040,4019' => { VR => 'SQ', Name => 'PerformedWorkitemCodeSequence' }, + '0040,4020' => { VR => 'CS', Name => 'InputAvailabilityFlag' }, + '0040,4021' => { VR => 'SQ', Name => 'InputInformationSequence' }, + '0040,4022' => { VR => 'SQ', Name => 'RelevantInformationSequence' }, + '0040,4023' => { VR => 'UI', Name => 'RefGenPurSchedProcStepTransUID' }, + '0040,4025' => { VR => 'SQ', Name => 'ScheduledStationNameCodeSequence' }, + '0040,4026' => { VR => 'SQ', Name => 'ScheduledStationClassCodeSequence' }, + '0040,4027' => { VR => 'SQ', Name => 'SchedStationGeographicLocCodeSeq' }, + '0040,4028' => { VR => 'SQ', Name => 'PerformedStationNameCodeSequence' }, + '0040,4029' => { VR => 'SQ', Name => 'PerformedStationClassCodeSequence' }, + '0040,4030' => { VR => 'SQ', Name => 'PerformedStationGeogLocCodeSeq' }, + '0040,4031' => { VR => 'SQ', Name => 'RequestedSubsequentWorkItemCodeSeq' }, + '0040,4032' => { VR => 'SQ', Name => 'NonDICOMOutputCodeSequence' }, + '0040,4033' => { VR => 'SQ', Name => 'OutputInformationSequence' }, + '0040,4034' => { VR => 'SQ', Name => 'ScheduledHumanPerformersSequence' }, + '0040,4035' => { VR => 'SQ', Name => 'ActualHumanPerformersSequence' }, + '0040,4036' => { VR => 'LO', Name => 'HumanPerformersOrganization' }, + '0040,4037' => { VR => 'PN', Name => 'HumanPerformerName' }, + '0040,4040' => { VR => 'CS', Name => 'RawDataHandling' }, + '0040,8302' => { VR => 'DS', Name => 'EntranceDoseInMilliGy' }, + '0040,9094' => { VR => 'SQ', Name => 'RefImageRealWorldValueMappingSeq' }, + '0040,9096' => { VR => 'SQ', Name => 'RealWorldValueMappingSequence' }, + '0040,9098' => { VR => 'SQ', Name => 'PixelValueMappingCodeSequence' }, + '0040,9210' => { VR => 'SH', Name => 'LUTLabel' }, + '0040,9211' => { VR => 'SS', Name => 'RealWorldValueLastValueMapped' }, + '0040,9212' => { VR => 'FD', Name => 'RealWorldValueLUTData' }, + '0040,9216' => { VR => 'SS', Name => 'RealWorldValueFirstValueMapped' }, + '0040,9224' => { VR => 'FD', Name => 'RealWorldValueIntercept' }, + '0040,9225' => { VR => 'FD', Name => 'RealWorldValueSlope' }, + '0040,A010' => { VR => 'CS', Name => 'RelationshipType' }, + '0040,A027' => { VR => 'LO', Name => 'VerifyingOrganization' }, + '0040,A030' => { VR => 'DT', Name => 'VerificationDateTime' }, + '0040,A032' => { VR => 'DT', Name => 'ObservationDateTime' }, + '0040,A040' => { VR => 'CS', Name => 'ValueType' }, + '0040,A043' => { VR => 'SQ', Name => 'ConceptNameCodeSequence' }, + '0040,A050' => { VR => 'CS', Name => 'ContinuityOfContent' }, + '0040,A073' => { VR => 'SQ', Name => 'VerifyingObserverSequence' }, + '0040,A075' => { VR => 'PN', Name => 'VerifyingObserverName' }, + '0040,A078' => { VR => 'SQ', Name => 'AuthorObserverSequence' }, + '0040,A07A' => { VR => 'SQ', Name => 'ParticipantSequence' }, + '0040,A07C' => { VR => 'SQ', Name => 'CustodialOrganizationSequence' }, + '0040,A080' => { VR => 'CS', Name => 'ParticipationType' }, + '0040,A082' => { VR => 'DT', Name => 'ParticipationDateTime' }, + '0040,A084' => { VR => 'CS', Name => 'ObserverType' }, + '0040,A088' => { VR => 'SQ', Name => 'VerifyingObserverIdentCodeSequence' }, + '0040,A090' => { VR => 'SQ', Name => 'EquivalentCDADocumentSequence' }, + '0040,A0B0' => { VR => 'US', Name => 'ReferencedWaveformChannels' }, + '0040,A120' => { VR => 'DT', Name => 'DateTime' }, + '0040,A121' => { VR => 'DA', Name => 'Date' }, + '0040,A122' => { VR => 'TM', Name => 'Time' }, + '0040,A123' => { VR => 'PN', Name => 'PersonName' }, + '0040,A124' => { VR => 'UI', Name => 'UID' }, + '0040,A130' => { VR => 'CS', Name => 'TemporalRangeType' }, + '0040,A132' => { VR => 'UL', Name => 'ReferencedSamplePositions' }, + '0040,A136' => { VR => 'US', Name => 'ReferencedFrameNumbers' }, + '0040,A138' => { VR => 'DS', Name => 'ReferencedTimeOffsets' }, + '0040,A13A' => { VR => 'DT', Name => 'ReferencedDateTime' }, + '0040,A160' => { VR => 'UT', Name => 'TextValue' }, + '0040,A168' => { VR => 'SQ', Name => 'ConceptCodeSequence' }, + '0040,A170' => { VR => 'SQ', Name => 'PurposeOfReferenceCodeSequence' }, + '0040,A180' => { VR => 'US', Name => 'AnnotationGroupNumber' }, + '0040,A195' => { VR => 'SQ', Name => 'ModifierCodeSequence' }, + '0040,A300' => { VR => 'SQ', Name => 'MeasuredValueSequence' }, + '0040,A301' => { VR => 'SQ', Name => 'NumericValueQualifierCodeSequence' }, + '0040,A30A' => { VR => 'DS', Name => 'NumericValue' }, + '0040,A353' => { VR => 'ST', Name => 'AddressTrial' }, + '0040,A354' => { VR => 'LO', Name => 'TelephoneNumberTrial' }, + '0040,A360' => { VR => 'SQ', Name => 'PredecessorDocumentsSequence' }, + '0040,A370' => { VR => 'SQ', Name => 'ReferencedRequestSequence' }, + '0040,A372' => { VR => 'SQ', Name => 'PerformedProcedureCodeSequence' }, + '0040,A375' => { VR => 'SQ', Name => 'CurrentRequestedProcEvidenceSeq' }, + '0040,A385' => { VR => 'SQ', Name => 'PertinentOtherEvidenceSequence' }, + '0040,A390' => { VR => 'SQ', Name => 'HL7StructuredDocumentRefSeq' }, + '0040,A491' => { VR => 'CS', Name => 'CompletionFlag' }, + '0040,A492' => { VR => 'LO', Name => 'CompletionFlagDescription' }, + '0040,A493' => { VR => 'CS', Name => 'VerificationFlag' }, + '0040,A494' => { VR => 'CS', Name => 'ArchiveRequested' }, + '0040,A496' => { VR => 'CS', Name => 'PreliminaryFlag' }, + '0040,A504' => { VR => 'SQ', Name => 'ContentTemplateSequence' }, + '0040,A525' => { VR => 'SQ', Name => 'IdenticalDocumentsSequence' }, + '0040,A730' => { VR => 'SQ', Name => 'ContentSequence' }, + '0040,B020' => { VR => 'SQ', Name => 'AnnotationSequence' }, + '0040,DB00' => { VR => 'CS', Name => 'TemplateIdentifier' }, + '0040,DB06' => { VR => 'DT', Name => 'TemplateVersion' }, + '0040,DB07' => { VR => 'DT', Name => 'TemplateLocalVersion' }, + '0040,DB0B' => { VR => 'CS', Name => 'TemplateExtensionFlag' }, + '0040,DB0C' => { VR => 'UI', Name => 'TemplateExtensionOrganizationUID' }, + '0040,DB0D' => { VR => 'UI', Name => 'TemplateExtensionCreatorUID' }, + '0040,DB73' => { VR => 'UL', Name => 'ReferencedContentItemIdentifier' }, + '0040,E001' => { VR => 'ST', Name => 'HL7InstanceIdentifier' }, + '0040,E004' => { VR => 'DT', Name => 'HL7DocumentEffectiveTime' }, + '0040,E006' => { VR => 'SQ', Name => 'HL7DocumentTypeCodeSequence' }, + '0040,E010' => { VR => 'UT', Name => 'RetrieveURI' }, + '0040,E011' => { VR => 'UI', Name => 'RetrieveLocationUID' }, + '0042,0010' => { VR => 'ST', Name => 'DocumentTitle' }, + '0042,0011' => { VR => 'OB', Name => 'EncapsulatedDocument' }, + '0042,0012' => { VR => 'LO', Name => 'MIMETypeOfEncapsulatedDocument' }, + '0042,0013' => { VR => 'SQ', Name => 'SourceInstanceSequence' }, + '0042,0014' => { VR => 'LO', Name => 'ListOfMIMETypes' }, + # GEMS_PARM_01 (ref 4) + '0043,1001' => { VR => 'SS', Name => 'BitmapOfPrescanOptions' }, + '0043,1002' => { VR => 'SS', Name => 'GradientOffsetInX' }, + '0043,1003' => { VR => 'SS', Name => 'GradientOffsetInY' }, + '0043,1004' => { VR => 'SS', Name => 'GradientOffsetInZ' }, + '0043,1005' => { VR => 'SS', Name => 'ImgIsOriginalOrUnoriginal' }, + '0043,1006' => { VR => 'SS', Name => 'NumberOfEPIShots' }, + '0043,1007' => { VR => 'SS', Name => 'ViewsPerSegment' }, + '0043,1008' => { VR => 'SS', Name => 'RespiratoryRateBpm' }, + '0043,1009' => { VR => 'SS', Name => 'RespiratoryTriggerPoint' }, + '0043,100A' => { VR => 'SS', Name => 'TypeOfReceiverUsed' }, + '0043,100B' => { VR => 'DS', Name => 'PeakRateOfChangeOfGradientField' }, + '0043,100C' => { VR => 'DS', Name => 'LimitsInUnitsOfPercent' }, + '0043,100D' => { VR => 'DS', Name => 'PSDEstimatedLimit' }, + '0043,100E' => { VR => 'DS', Name => 'PSDEstimatedLimitInTeslaPerSecond' }, + '0043,100F' => { VR => 'DS', Name => 'Saravghead' }, + '0043,1010' => { VR => 'US', Name => 'WindowValue' }, + '0043,1011' => { VR => 'US', Name => 'TotalInputViews' }, + '0043,1012' => { VR => 'SS', Name => 'X-RayChain' }, + '0043,1013' => { VR => 'SS', Name => 'DeconKernelParameters' }, + '0043,1014' => { VR => 'SS', Name => 'CalibrationParameters' }, + '0043,1015' => { VR => 'SS', Name => 'TotalOutputViews' }, + '0043,1016' => { VR => 'SS', Name => 'NumberOfOverranges' }, + '0043,1017' => { VR => 'DS', Name => 'IBHImageScaleFactors' }, + '0043,1018' => { VR => 'DS', Name => 'BBHCoefficients' }, + '0043,1019' => { VR => 'SS', Name => 'NumberOfBBHChainsToBlend' }, + '0043,101A' => { VR => 'SL', Name => 'StartingChannelNumber' }, + '0043,101B' => { VR => 'SS', Name => 'PpscanParameters' }, + '0043,101C' => { VR => 'SS', Name => 'GEImageIntegrity' }, + '0043,101D' => { VR => 'SS', Name => 'LevelValue' }, + '0043,101E' => { VR => 'DS', Name => 'DeltaStartTime' }, + '0043,101F' => { VR => 'SL', Name => 'MaxOverrangesInAView' }, + '0043,1020' => { VR => 'DS', Name => 'AvgOverrangesAllViews' }, + '0043,1021' => { VR => 'SS', Name => 'CorrectedAfterGlowTerms' }, + '0043,1025' => { VR => 'SS', Name => 'ReferenceChannels' }, + '0043,1026' => { VR => 'US', Name => 'NoViewsRefChansBlocked' }, + '0043,1027' => { VR => 'SH', Name => 'ScanPitchRatio' }, + '0043,1028' => { VR => 'OB', Name => 'UniqueImageIden' }, + '0043,1029' => { VR => 'OB', Name => 'HistogramTables' }, + '0043,102A' => { VR => 'OB', Name => 'UserDefinedData' }, + '0043,102B' => { VR => 'SS', Name => 'PrivateScanOptions' }, + '0043,102C' => { VR => 'SS', Name => 'EffectiveEchoSpacing' }, + '0043,102D' => { VR => 'SH', Name => 'StringSlopField1' }, + '0043,102E' => { VR => 'SH', Name => 'StringSlopField2' }, + '0043,102F' => { VR => 'SS', Name => 'RawDataType' }, + '0043,1030' => { VR => 'SS', Name => 'RawDataType' }, + '0043,1031' => { VR => 'DS', Name => 'RACordOfTargetReconCenter' }, + '0043,1032' => { VR => 'SS', Name => 'RawDataType' }, + '0043,1033' => { VR => 'FL', Name => 'NegScanspacing' }, + '0043,1034' => { VR => 'IS', Name => 'OffsetFrequency' }, + '0043,1035' => { VR => 'UL', Name => 'UserUsageTag' }, + '0043,1036' => { VR => 'UL', Name => 'UserFillMapMSW' }, + '0043,1037' => { VR => 'UL', Name => 'UserFillMapLSW' }, + '0043,1038' => { VR => 'FL', Name => 'User25-48' }, + '0043,1039' => { VR => 'IS', Name => 'SlopInt6-9' }, + '0043,1040' => { VR => 'FL', Name => 'TriggerOnPosition' }, + '0043,1041' => { VR => 'FL', Name => 'DegreeOfRotation' }, + '0043,1042' => { VR => 'SL', Name => 'DASTriggerSource' }, + '0043,1043' => { VR => 'SL', Name => 'DASFpaGain' }, + '0043,1044' => { VR => 'SL', Name => 'DASOutputSource' }, + '0043,1045' => { VR => 'SL', Name => 'DASAdInput' }, + '0043,1046' => { VR => 'SL', Name => 'DASCalMode' }, + '0043,1047' => { VR => 'SL', Name => 'DASCalFrequency' }, + '0043,1048' => { VR => 'SL', Name => 'DASRegXm' }, + '0043,1049' => { VR => 'SL', Name => 'DASAutoZero' }, + '0043,104A' => { VR => 'SS', Name => 'StartingChannelOfView' }, + '0043,104B' => { VR => 'SL', Name => 'DASXmPattern' }, + '0043,104C' => { VR => 'SS', Name => 'TGGCTriggerMode' }, + '0043,104D' => { VR => 'FL', Name => 'StartScanToXrayOnDelay' }, + '0043,104E' => { VR => 'FL', Name => 'DurationOfXrayOn' }, + '0043,1060' => { VR => 'IS', Name => 'SlopInt10-17' }, + '0043,1061' => { VR => 'UI', Name => 'ScannerStudyEntityUID' }, + '0043,1062' => { VR => 'SH', Name => 'ScannerStudyID' }, + '0043,106f' => { VR => 'DS', Name => 'ScannerTableEntry' }, + # ? + '0044,0001' => { VR => 'ST', Name => 'ProductPackageIdentifier' }, + '0044,0002' => { VR => 'CS', Name => 'SubstanceAdministrationApproval' }, + '0044,0003' => { VR => 'LT', Name => 'ApprovalStatusFurtherDescription' }, + '0044,0004' => { VR => 'DT', Name => 'ApprovalStatusDateTime' }, + '0044,0007' => { VR => 'SQ', Name => 'ProductTypeCodeSequence' }, + '0044,0008' => { VR => 'LO', Name => 'ProductName' }, + '0044,0009' => { VR => 'LT', Name => 'ProductDescription' }, + '0044,000A' => { VR => 'LO', Name => 'ProductLotIdentifier' }, + '0044,000B' => { VR => 'DT', Name => 'ProductExpirationDateTime' }, + '0044,0010' => { VR => 'DT', Name => 'SubstanceAdministrationDateTime' }, + '0044,0011' => { VR => 'LO', Name => 'SubstanceAdministrationNotes' }, + '0044,0012' => { VR => 'LO', Name => 'SubstanceAdministrationDeviceID' }, + '0044,0013' => { VR => 'SQ', Name => 'ProductParameterSequence' }, + '0044,0019' => { VR => 'SQ', Name => 'SubstanceAdminParameterSeq' }, + # GEMS_HELIOS_01 (ref 4) + '0045,1001' => { VR => 'LO', Name => 'NumberOfMacroRowsInDetector' }, + '0045,1002' => { VR => 'FL', Name => 'MacroWidthAtISOCenter' }, + '0045,1003' => { VR => 'SS', Name => 'DASType' }, + '0045,1004' => { VR => 'SS', Name => 'DASGain' }, + '0045,1005' => { VR => 'SS', Name => 'DASTemperature' }, + '0045,1006' => { VR => 'CS', Name => 'TableDirectionInOrOut' }, + '0045,1007' => { VR => 'FL', Name => 'ZSmoothingFactor' }, + '0045,1008' => { VR => 'SS', Name => 'ViewWeightingMode' }, + '0045,1009' => { VR => 'SS', Name => 'SigmaRowNumberWhichRowsWereUsed' }, + '0045,100A' => { VR => 'FL', Name => 'MinimumDasValueFoundInTheScanData' }, + '0045,100B' => { VR => 'FL', Name => 'MaximumOffsetShiftValueUsed' }, + '0045,100C' => { VR => 'SS', Name => 'NumberOfViewsShifted' }, + '0045,100D' => { VR => 'SS', Name => 'ZTrackingFlag' }, + '0045,100E' => { VR => 'FL', Name => 'MeanZError' }, + '0045,100F' => { VR => 'FL', Name => 'ZTrackingMaximumError' }, + '0045,1010' => { VR => 'SS', Name => 'StartingViewForRow2a' }, + '0045,1011' => { VR => 'SS', Name => 'NumberOfViewsInRow2a' }, + '0045,1012' => { VR => 'SS', Name => 'StartingViewForRow1a' }, + '0045,1013' => { VR => 'SS', Name => 'SigmaMode' }, + '0045,1014' => { VR => 'SS', Name => 'NumberOfViewsInRow1a' }, + '0045,1015' => { VR => 'SS', Name => 'StartingViewForRow2b' }, + '0045,1016' => { VR => 'SS', Name => 'NumberOfViewsInRow2b' }, + '0045,1017' => { VR => 'SS', Name => 'StartingViewForRow1b' }, + '0045,1018' => { VR => 'SS', Name => 'NumberOfViewsInRow1b' }, + '0045,1019' => { VR => 'SS', Name => 'AirFilterCalibrationDate' }, + '0045,101A' => { VR => 'SS', Name => 'AirFilterCalibrationTime' }, + '0045,101B' => { VR => 'SS', Name => 'PhantomCalibrationDate' }, + '0045,101C' => { VR => 'SS', Name => 'PhantomCalibrationTime' }, + '0045,101D' => { VR => 'SS', Name => 'ZSlopeCalibrationDate' }, + '0045,101E' => { VR => 'SS', Name => 'ZSlopeCalibrationTime' }, + '0045,101F' => { VR => 'SS', Name => 'CrosstalkCalibrationDate' }, + '0045,1020' => { VR => 'SS', Name => 'CrosstalkCalibrationTime' }, + '0045,1021' => { VR => 'SS', Name => 'IterboneOptionFlag' }, + '0045,1022' => { VR => 'SS', Name => 'PeristalticFlagOption' }, + '0046,0012' => { VR => 'LO', Name => 'LensDescription' }, + '0046,0014' => { VR => 'SQ', Name => 'RightLensSequence' }, + '0046,0015' => { VR => 'SQ', Name => 'LeftLensSequence' }, + '0046,0018' => { VR => 'SQ', Name => 'CylinderSequence' }, + '0046,0028' => { VR => 'SQ', Name => 'PrismSequence' }, + '0046,0030' => { VR => 'FD', Name => 'HorizontalPrismPower' }, + '0046,0032' => { VR => 'CS', Name => 'HorizontalPrismBase' }, + '0046,0034' => { VR => 'FD', Name => 'VerticalPrismPower' }, + '0046,0036' => { VR => 'CS', Name => 'VerticalPrismBase' }, + '0046,0038' => { VR => 'CS', Name => 'LensSegmentType' }, + '0046,0040' => { VR => 'FD', Name => 'OpticalTransmittance' }, + '0046,0042' => { VR => 'FD', Name => 'ChannelWidth' }, + '0046,0044' => { VR => 'FD', Name => 'PupilSize' }, + '0046,0046' => { VR => 'FD', Name => 'CornealSize' }, + '0046,0060' => { VR => 'FD', Name => 'DistancePupillaryDistance' }, + '0046,0062' => { VR => 'FD', Name => 'NearPupillaryDistance' }, + '0046,0064' => { VR => 'FD', Name => 'OtherPupillaryDistance' }, + '0046,0075' => { VR => 'FD', Name => 'RadiusOfCurvature' }, + '0046,0076' => { VR => 'FD', Name => 'KeratometricPower' }, + '0046,0077' => { VR => 'FD', Name => 'KeratometricAxis' }, + '0046,0092' => { VR => 'CS', Name => 'BackgroundColor' }, + '0046,0094' => { VR => 'CS', Name => 'Optotype' }, + '0046,0095' => { VR => 'CS', Name => 'OptotypePresentation' }, + '0046,0100' => { VR => 'SQ', Name => 'AddNearSequence' }, + '0046,0101' => { VR => 'SQ', Name => 'AddIntermediateSequence' }, + '0046,0102' => { VR => 'SQ', Name => 'AddOtherSequence' }, + '0046,0104' => { VR => 'FD', Name => 'AddPower' }, + '0046,0106' => { VR => 'FD', Name => 'ViewingDistance' }, + '0046,0125' => { VR => 'CS', Name => 'ViewingDistanceType' }, + '0046,0135' => { VR => 'SS', Name => 'VisualAcuityModifiers' }, + '0046,0137' => { VR => 'FD', Name => 'DecimalVisualAcuity' }, + '0046,0139' => { VR => 'LO', Name => 'OptotypeDetailedDefinition' }, + '0046,0146' => { VR => 'FD', Name => 'SpherePower' }, + '0046,0147' => { VR => 'FD', Name => 'CylinderPower' }, + # calibration group + '0050,0004' => { VR => 'CS', Name => 'CalibrationImage' }, + '0050,0010' => { VR => 'SQ', Name => 'DeviceSequence' }, + '0050,0014' => { VR => 'DS', Name => 'DeviceLength' }, + '0050,0015' => { VR => 'FD', Name => 'ContainerComponentWidth' }, + '0050,0016' => { VR => 'DS', Name => 'DeviceDiameter' }, + '0050,0017' => { VR => 'CS', Name => 'DeviceDiameterUnits' }, + '0050,0018' => { VR => 'DS', Name => 'DeviceVolume' }, + '0050,0019' => { VR => 'DS', Name => 'InterMarkerDistance' }, + '0050,001B' => { VR => 'LO', Name => 'ContainerComponentID' }, + '0050,0020' => { VR => 'LO', Name => 'DeviceDescription' }, + # nuclear acquisition group + '0054,0010' => { VR => 'US', Name => 'EnergyWindowVector' }, + '0054,0011' => { VR => 'US', Name => 'NumberOfEnergyWindows' }, + '0054,0012' => { VR => 'SQ', Name => 'EnergyWindowInformationSequence' }, + '0054,0013' => { VR => 'SQ', Name => 'EnergyWindowRangeSequence' }, + '0054,0014' => { VR => 'DS', Name => 'EnergyWindowLowerLimit' }, + '0054,0015' => { VR => 'DS', Name => 'EnergyWindowUpperLimit' }, + '0054,0016' => { VR => 'SQ', Name => 'RadiopharmaceuticalInformationSeq' }, + '0054,0017' => { VR => 'IS', Name => 'ResidualSyringeCounts' }, + '0054,0018' => { VR => 'SH', Name => 'EnergyWindowName' }, + '0054,0020' => { VR => 'US', Name => 'DetectorVector' }, + '0054,0021' => { VR => 'US', Name => 'NumberOfDetectors' }, + '0054,0022' => { VR => 'SQ', Name => 'DetectorInformationSequence' }, + '0054,0030' => { VR => 'US', Name => 'PhaseVector' }, + '0054,0031' => { VR => 'US', Name => 'NumberOfPhases' }, + '0054,0032' => { VR => 'SQ', Name => 'PhaseInformationSequence' }, + '0054,0033' => { VR => 'US', Name => 'NumberOfFramesInPhase' }, + '0054,0036' => { VR => 'IS', Name => 'PhaseDelay' }, + '0054,0038' => { VR => 'IS', Name => 'PauseBetweenFrames' }, + '0054,0039' => { VR => 'CS', Name => 'PhaseDescription' }, + '0054,0050' => { VR => 'US', Name => 'RotationVector' }, + '0054,0051' => { VR => 'US', Name => 'NumberOfRotations' }, + '0054,0052' => { VR => 'SQ', Name => 'RotationInformationSequence' }, + '0054,0053' => { VR => 'US', Name => 'NumberOfFramesInRotation' }, + '0054,0060' => { VR => 'US', Name => 'RRIntervalVector' }, + '0054,0061' => { VR => 'US', Name => 'NumberOfRRIntervals' }, + '0054,0062' => { VR => 'SQ', Name => 'GatedInformationSequence' }, + '0054,0063' => { VR => 'SQ', Name => 'DataInformationSequence' }, + '0054,0070' => { VR => 'US', Name => 'TimeSlotVector' }, + '0054,0071' => { VR => 'US', Name => 'NumberOfTimeSlots' }, + '0054,0072' => { VR => 'SQ', Name => 'TimeSlotInformationSequence' }, + '0054,0073' => { VR => 'DS', Name => 'TimeSlotTime' }, + '0054,0080' => { VR => 'US', Name => 'SliceVector' }, + '0054,0081' => { VR => 'US', Name => 'NumberOfSlices' }, + '0054,0090' => { VR => 'US', Name => 'AngularViewVector' }, + '0054,0100' => { VR => 'US', Name => 'TimeSliceVector' }, + '0054,0101' => { VR => 'US', Name => 'NumberOfTimeSlices' }, + '0054,0200' => { VR => 'DS', Name => 'StartAngle' }, + '0054,0202' => { VR => 'CS', Name => 'TypeOfDetectorMotion' }, + '0054,0210' => { VR => 'IS', Name => 'TriggerVector' }, + '0054,0211' => { VR => 'US', Name => 'NumberOfTriggersInPhase' }, + '0054,0220' => { VR => 'SQ', Name => 'ViewCodeSequence' }, + '0054,0222' => { VR => 'SQ', Name => 'ViewModifierCodeSequence' }, + '0054,0300' => { VR => 'SQ', Name => 'RadionuclideCodeSequence' }, + '0054,0302' => { VR => 'SQ', Name => 'AdministrationRouteCodeSequence' }, + '0054,0304' => { VR => 'SQ', Name => 'RadiopharmaceuticalCodeSequence' }, + '0054,0306' => { VR => 'SQ', Name => 'CalibrationDataSequence' }, + '0054,0308' => { VR => 'US', Name => 'EnergyWindowNumber' }, + '0054,0400' => { VR => 'SH', Name => 'ImageID' }, + '0054,0410' => { VR => 'SQ', Name => 'PatientOrientationCodeSequence' }, + '0054,0412' => { VR => 'SQ', Name => 'PatientOrientationModifierCodeSeq' }, + '0054,0414' => { VR => 'SQ', Name => 'PatientGantryRelationshipCodeSeq' }, + '0054,0500' => { VR => 'CS', Name => 'SliceProgressionDirection' }, + '0054,1000' => { VR => 'CS', Name => 'SeriesType' }, + '0054,1001' => { VR => 'CS', Name => 'Units' }, + '0054,1002' => { VR => 'CS', Name => 'CountsSource' }, + '0054,1004' => { VR => 'CS', Name => 'ReprojectionMethod' }, + '0054,1100' => { VR => 'CS', Name => 'RandomsCorrectionMethod' }, + '0054,1101' => { VR => 'LO', Name => 'AttenuationCorrectionMethod' }, + '0054,1102' => { VR => 'CS', Name => 'DecayCorrection' }, + '0054,1103' => { VR => 'LO', Name => 'ReconstructionMethod' }, + '0054,1104' => { VR => 'LO', Name => 'DetectorLinesOfResponseUsed' }, + '0054,1105' => { VR => 'LO', Name => 'ScatterCorrectionMethod' }, + '0054,1200' => { VR => 'DS', Name => 'AxialAcceptance' }, + '0054,1201' => { VR => 'IS', Name => 'AxialMash' }, + '0054,1202' => { VR => 'IS', Name => 'TransverseMash' }, + '0054,1203' => { VR => 'DS', Name => 'DetectorElementSize' }, + '0054,1210' => { VR => 'DS', Name => 'CoincidenceWindowWidth' }, + '0054,1220' => { VR => 'CS', Name => 'SecondaryCountsType' }, + '0054,1300' => { VR => 'DS', Name => 'FrameReferenceTime' }, + '0054,1310' => { VR => 'IS', Name => 'PrimaryCountsAccumulated' }, + '0054,1311' => { VR => 'IS', Name => 'SecondaryCountsAccumulated' }, + '0054,1320' => { VR => 'DS', Name => 'SliceSensitivityFactor' }, + '0054,1321' => { VR => 'DS', Name => 'DecayFactor' }, + '0054,1322' => { VR => 'DS', Name => 'DoseCalibrationFactor' }, + '0054,1323' => { VR => 'DS', Name => 'ScatterFractionFactor' }, + '0054,1324' => { VR => 'DS', Name => 'DeadTimeFactor' }, + '0054,1330' => { VR => 'US', Name => 'ImageIndex' }, + '0054,1400' => { VR => 'CS', Name => 'CountsIncluded' }, + '0054,1401' => { VR => 'CS', Name => 'DeadTimeCorrectionFlag' }, + '0060,3000' => { VR => 'SQ', Name => 'HistogramSequence' }, + '0060,3002' => { VR => 'US', Name => 'HistogramNumberOfBins' }, + '0060,3004' => { VR => 'US', Name => 'HistogramFirstBinValue' }, + '0060,3006' => { VR => 'US', Name => 'HistogramLastBinValue' }, + '0060,3008' => { VR => 'US', Name => 'HistogramBinWidth' }, + '0060,3010' => { VR => 'LO', Name => 'HistogramExplanation' }, + '0060,3020' => { VR => 'UL', Name => 'HistogramData' }, + '0062,0001' => { VR => 'CS', Name => 'SegmentationType' }, + '0062,0002' => { VR => 'SQ', Name => 'SegmentSequence' }, + '0062,0003' => { VR => 'SQ', Name => 'SegmentedPropertyCategoryCodeSeq' }, + '0062,0004' => { VR => 'US', Name => 'SegmentNumber' }, + '0062,0005' => { VR => 'LO', Name => 'SegmentLabel' }, + '0062,0006' => { VR => 'ST', Name => 'SegmentDescription' }, + '0062,0008' => { VR => 'CS', Name => 'SegmentAlgorithmType' }, + '0062,0009' => { VR => 'LO', Name => 'SegmentAlgorithmName' }, + '0062,000A' => { VR => 'SQ', Name => 'SegmentIdentificationSequence' }, + '0062,000B' => { VR => 'US', Name => 'ReferencedSegmentNumber' }, + '0062,000C' => { VR => 'US', Name => 'RecommendedDisplayGrayscaleValue' }, + '0062,000D' => { VR => 'US', Name => 'RecommendedDisplayCIELabValue' }, + '0062,000E' => { VR => 'US', Name => 'MaximumFractionalValue' }, + '0062,000F' => { VR => 'SQ', Name => 'SegmentedPropertyTypeCodeSequence' }, + '0062,0010' => { VR => 'CS', Name => 'SegmentationFractionalType' }, + '0064,0002' => { VR => 'SQ', Name => 'DeformableRegistrationSequence' }, + '0064,0003' => { VR => 'UI', Name => 'SourceFrameOfReferenceUID' }, + '0064,0005' => { VR => 'SQ', Name => 'DeformableRegistrationGridSequence' }, + '0064,0007' => { VR => 'UL', Name => 'GridDimensions' }, + '0064,0008' => { VR => 'FD', Name => 'GridResolution' }, + '0064,0009' => { VR => 'OF', Name => 'VectorGridData' }, + '0064,000F' => { VR => 'SQ', Name => 'PreDeformationMatrixRegistSeq' }, + '0064,0010' => { VR => 'SQ', Name => 'PostDeformationMatrixRegistSeq' }, + '0066,0001' => { VR => 'UL', Name => 'NumberOfSurfaces' }, + '0066,0002' => { VR => 'SQ', Name => 'SurfaceSequence' }, + '0066,0003' => { VR => 'UL', Name => 'SurfaceNumber' }, + '0066,0004' => { VR => 'LT', Name => 'SurfaceComments' }, + '0066,0009' => { VR => 'CS', Name => 'SurfaceProcessing' }, + '0066,000A' => { VR => 'FL', Name => 'SurfaceProcessingRatio' }, + '0066,000E' => { VR => 'CS', Name => 'FiniteVolume' }, + '0066,0010' => { VR => 'CS', Name => 'Manifold' }, + '0066,0011' => { VR => 'SQ', Name => 'SurfacePointsSequence' }, + '0066,0015' => { VR => 'UL', Name => 'NumberOfSurfacePoints' }, + '0066,0016' => { VR => 'OF', Name => 'PointCoordinatesData' }, + '0066,0017' => { VR => 'FL', Name => 'PointPositionAccuracy' }, + '0066,0018' => { VR => 'FL', Name => 'MeanPointDistance' }, + '0066,0019' => { VR => 'FL', Name => 'MaximumPointDistance' }, + '0066,001B' => { VR => 'FL', Name => 'AxisOfRotation' }, + '0066,001C' => { VR => 'FL', Name => 'CenterOfRotation' }, + '0066,001E' => { VR => 'UL', Name => 'NumberOfVectors' }, + '0066,001F' => { VR => 'US', Name => 'VectorDimensionality' }, + '0066,0020' => { VR => 'FL', Name => 'VectorAccuracy' }, + '0066,0021' => { VR => 'OF', Name => 'VectorCoordinateData' }, + '0066,0023' => { VR => 'OW', Name => 'TrianglePointIndexList' }, + '0066,0024' => { VR => 'OW', Name => 'EdgePointIndexList' }, + '0066,0025' => { VR => 'OW', Name => 'VertexPointIndexList' }, + '0066,0026' => { VR => 'SQ', Name => 'TriangleStripSequence' }, + '0066,0027' => { VR => 'SQ', Name => 'TriangleFanSequence' }, + '0066,0028' => { VR => 'SQ', Name => 'LineSequence' }, + '0066,0029' => { VR => 'OW', Name => 'PrimitivePointIndexList' }, + '0066,002A' => { VR => 'UL', Name => 'SurfaceCount' }, + '0066,002F' => { VR => 'SQ', Name => 'AlgorithmFamilyCodeSequ' }, + '0066,0031' => { VR => 'LO', Name => 'AlgorithmVersion' }, + '0066,0032' => { VR => 'LT', Name => 'AlgorithmParameters' }, + '0066,0034' => { VR => 'SQ', Name => 'FacetSequence' }, + '0066,0036' => { VR => 'LO', Name => 'AlgorithmName' }, + '0070,0001' => { VR => 'SQ', Name => 'GraphicAnnotationSequence' }, + '0070,0002' => { VR => 'CS', Name => 'GraphicLayer' }, + '0070,0003' => { VR => 'CS', Name => 'BoundingBoxAnnotationUnits' }, + '0070,0004' => { VR => 'CS', Name => 'AnchorPointAnnotationUnits' }, + '0070,0005' => { VR => 'CS', Name => 'GraphicAnnotationUnits' }, + '0070,0006' => { VR => 'ST', Name => 'UnformattedTextValue' }, + '0070,0008' => { VR => 'SQ', Name => 'TextObjectSequence' }, + '0070,0009' => { VR => 'SQ', Name => 'GraphicObjectSequence' }, + '0070,0010' => { VR => 'FL', Name => 'BoundingBoxTopLeftHandCorner' }, + '0070,0011' => { VR => 'FL', Name => 'BoundingBoxBottomRightHandCorner' }, + '0070,0012' => { VR => 'CS', Name => 'BoundingBoxTextHorizJustification' }, + '0070,0014' => { VR => 'FL', Name => 'AnchorPoint' }, + '0070,0015' => { VR => 'CS', Name => 'AnchorPointVisibility' }, + '0070,0020' => { VR => 'US', Name => 'GraphicDimensions' }, + '0070,0021' => { VR => 'US', Name => 'NumberOfGraphicPoints' }, + '0070,0022' => { VR => 'FL', Name => 'GraphicData' }, + '0070,0023' => { VR => 'CS', Name => 'GraphicType' }, + '0070,0024' => { VR => 'CS', Name => 'GraphicFilled' }, + '0070,0040' => { VR => 'IS', Name => 'ImageRotationRetired' }, + '0070,0041' => { VR => 'CS', Name => 'ImageHorizontalFlip' }, + '0070,0042' => { VR => 'US', Name => 'ImageRotation' }, + '0070,0050' => { VR => 'US', Name => 'DisplayedAreaTopLeftTrial' }, + '0070,0051' => { VR => 'US', Name => 'DisplayedAreaBottomRightTrial' }, + '0070,0052' => { VR => 'SL', Name => 'DisplayedAreaTopLeft' }, + '0070,0053' => { VR => 'SL', Name => 'DisplayedAreaBottomRight' }, + '0070,005A' => { VR => 'SQ', Name => 'DisplayedAreaSelectionSequence' }, + '0070,0060' => { VR => 'SQ', Name => 'GraphicLayerSequence' }, + '0070,0062' => { VR => 'IS', Name => 'GraphicLayerOrder' }, + '0070,0066' => { VR => 'US', Name => 'GraphicLayerRecDisplayGraysclValue' }, + '0070,0067' => { VR => 'US', Name => 'GraphicLayerRecDisplayRGBValue' }, + '0070,0068' => { VR => 'LO', Name => 'GraphicLayerDescription' }, + '0070,0080' => { VR => 'CS', Name => 'ContentLabel' }, + '0070,0081' => { VR => 'LO', Name => 'ContentDescription' }, + '0070,0082' => { VR => 'DA', Name => 'PresentationCreationDate' }, + '0070,0083' => { VR => 'TM', Name => 'PresentationCreationTime' }, + '0070,0084' => { VR => 'PN', Name => 'ContentCreatorName' }, + '0070,0086' => { VR => 'SQ', Name => 'ContentCreatorIDCodeSequence' }, + '0070,0100' => { VR => 'CS', Name => 'PresentationSizeMode' }, + '0070,0101' => { VR => 'DS', Name => 'PresentationPixelSpacing' }, + '0070,0102' => { VR => 'IS', Name => 'PresentationPixelAspectRatio' }, + '0070,0103' => { VR => 'FL', Name => 'PresentationPixelMagRatio' }, + '0070,0306' => { VR => 'CS', Name => 'ShapeType' }, + '0070,0308' => { VR => 'SQ', Name => 'RegistrationSequence' }, + '0070,0309' => { VR => 'SQ', Name => 'MatrixRegistrationSequence' }, + '0070,030A' => { VR => 'SQ', Name => 'MatrixSequence' }, + '0070,030C' => { VR => 'CS', Name => 'FrameOfRefTransformationMatrixType' }, + '0070,030D' => { VR => 'SQ', Name => 'RegistrationTypeCodeSequence' }, + '0070,030F' => { VR => 'ST', Name => 'FiducialDescription' }, + '0070,0310' => { VR => 'SH', Name => 'FiducialIdentifier' }, + '0070,0311' => { VR => 'SQ', Name => 'FiducialIdentifierCodeSequence' }, + '0070,0312' => { VR => 'FD', Name => 'ContourUncertaintyRadius' }, + '0070,0314' => { VR => 'SQ', Name => 'UsedFiducialsSequence' }, + '0070,0318' => { VR => 'SQ', Name => 'GraphicCoordinatesDataSequence' }, + '0070,031A' => { VR => 'UI', Name => 'FiducialUID' }, + '0070,031C' => { VR => 'SQ', Name => 'FiducialSetSequence' }, + '0070,031E' => { VR => 'SQ', Name => 'FiducialSequence' }, + '0070,0401' => { VR => 'US', Name => 'GraphicLayerRecomDisplayCIELabVal' }, + '0070,0402' => { VR => 'SQ', Name => 'BlendingSequence' }, + '0070,0403' => { VR => 'FL', Name => 'RelativeOpacity' }, + '0070,0404' => { VR => 'SQ', Name => 'ReferencedSpatialRegistrationSeq' }, + '0070,0405' => { VR => 'CS', Name => 'BlendingPosition' }, + '0072,0002' => { VR => 'SH', Name => 'HangingProtocolName' }, + '0072,0004' => { VR => 'LO', Name => 'HangingProtocolDescription' }, + '0072,0006' => { VR => 'CS', Name => 'HangingProtocolLevel' }, + '0072,0008' => { VR => 'LO', Name => 'HangingProtocolCreator' }, + '0072,000A' => { VR => 'DT', Name => 'HangingProtocolCreationDateTime' }, + '0072,000C' => { VR => 'SQ', Name => 'HangingProtocolDefinitionSequence' }, + '0072,000E' => { VR => 'SQ', Name => 'HangingProtocolUserIDCodeSequence' }, + '0072,0010' => { VR => 'LO', Name => 'HangingProtocolUserGroupName' }, + '0072,0012' => { VR => 'SQ', Name => 'SourceHangingProtocolSequence' }, + '0072,0014' => { VR => 'US', Name => 'NumberOfPriorsReferenced' }, + '0072,0020' => { VR => 'SQ', Name => 'ImageSetsSequence' }, + '0072,0022' => { VR => 'SQ', Name => 'ImageSetSelectorSequence' }, + '0072,0024' => { VR => 'CS', Name => 'ImageSetSelectorUsageFlag' }, + '0072,0026' => { VR => 'AT', Name => 'SelectorAttribute' }, + '0072,0028' => { VR => 'US', Name => 'SelectorValueNumber' }, + '0072,0030' => { VR => 'SQ', Name => 'TimeBasedImageSetsSequence' }, + '0072,0032' => { VR => 'US', Name => 'ImageSetNumber' }, + '0072,0034' => { VR => 'CS', Name => 'ImageSetSelectorCategory' }, + '0072,0038' => { VR => 'US', Name => 'RelativeTime' }, + '0072,003A' => { VR => 'CS', Name => 'RelativeTimeUnits' }, + '0072,003C' => { VR => 'SS', Name => 'AbstractPriorValue' }, + '0072,003E' => { VR => 'SQ', Name => 'AbstractPriorCodeSequence' }, + '0072,0040' => { VR => 'LO', Name => 'ImageSetLabel' }, + '0072,0050' => { VR => 'CS', Name => 'SelectorAttributeVR' }, + '0072,0052' => { VR => 'AT', Name => 'SelectorSequencePointer' }, + '0072,0054' => { VR => 'LO', Name => 'SelectorSeqPointerPrivateCreator' }, + '0072,0056' => { VR => 'LO', Name => 'SelectorAttributePrivateCreator' }, + '0072,0060' => { VR => 'AT', Name => 'SelectorATValue' }, + '0072,0062' => { VR => 'CS', Name => 'SelectorCSValue' }, + '0072,0064' => { VR => 'IS', Name => 'SelectorISValue' }, + '0072,0066' => { VR => 'LO', Name => 'SelectorLOValue' }, + '0072,0068' => { VR => 'LT', Name => 'SelectorLTValue' }, + '0072,006A' => { VR => 'PN', Name => 'SelectorPNValue' }, + '0072,006C' => { VR => 'SH', Name => 'SelectorSHValue' }, + '0072,006E' => { VR => 'ST', Name => 'SelectorSTValue' }, + '0072,0070' => { VR => 'UT', Name => 'SelectorUTValue' }, + '0072,0072' => { VR => 'DS', Name => 'SelectorDSValue' }, + '0072,0074' => { VR => 'FD', Name => 'SelectorFDValue' }, + '0072,0076' => { VR => 'FL', Name => 'SelectorFLValue' }, + '0072,0078' => { VR => 'UL', Name => 'SelectorULValue' }, + '0072,007A' => { VR => 'US', Name => 'SelectorUSValue' }, + '0072,007C' => { VR => 'SL', Name => 'SelectorSLValue' }, + '0072,007E' => { VR => 'SS', Name => 'SelectorSSValue' }, + '0072,0080' => { VR => 'SQ', Name => 'SelectorCodeSequenceValue' }, + '0072,0100' => { VR => 'US', Name => 'NumberOfScreens' }, + '0072,0102' => { VR => 'SQ', Name => 'NominalScreenDefinitionSequence' }, + '0072,0104' => { VR => 'US', Name => 'NumberOfVerticalPixels' }, + '0072,0106' => { VR => 'US', Name => 'NumberOfHorizontalPixels' }, + '0072,0108' => { VR => 'FD', Name => 'DisplayEnvironmentSpatialPosition' }, + '0072,010A' => { VR => 'US', Name => 'ScreenMinimumGrayscaleBitDepth' }, + '0072,010C' => { VR => 'US', Name => 'ScreenMinimumColorBitDepth' }, + '0072,010E' => { VR => 'US', Name => 'ApplicationMaximumRepaintTime' }, + '0072,0200' => { VR => 'SQ', Name => 'DisplaySetsSequence' }, + '0072,0202' => { VR => 'US', Name => 'DisplaySetNumber' }, + '0072,0203' => { VR => 'LO', Name => 'DisplaySetLabel' }, + '0072,0204' => { VR => 'US', Name => 'DisplaySetPresentationGroup' }, + '0072,0206' => { VR => 'LO', Name => 'DisplaySetPresentationGroupDescr' }, + '0072,0208' => { VR => 'CS', Name => 'PartialDataDisplayHandling' }, + '0072,0210' => { VR => 'SQ', Name => 'SynchronizedScrollingSequence' }, + '0072,0212' => { VR => 'US', Name => 'DisplaySetScrollingGroup' }, + '0072,0214' => { VR => 'SQ', Name => 'NavigationIndicatorSequence' }, + '0072,0216' => { VR => 'US', Name => 'NavigationDisplaySet' }, + '0072,0218' => { VR => 'US', Name => 'ReferenceDisplaySets' }, + '0072,0300' => { VR => 'SQ', Name => 'ImageBoxesSequence' }, + '0072,0302' => { VR => 'US', Name => 'ImageBoxNumber' }, + '0072,0304' => { VR => 'CS', Name => 'ImageBoxLayoutType' }, + '0072,0306' => { VR => 'US', Name => 'ImageBoxTileHorizontalDimension' }, + '0072,0308' => { VR => 'US', Name => 'ImageBoxTileVerticalDimension' }, + '0072,0310' => { VR => 'CS', Name => 'ImageBoxScrollDirection' }, + '0072,0312' => { VR => 'CS', Name => 'ImageBoxSmallScrollType' }, + '0072,0314' => { VR => 'US', Name => 'ImageBoxSmallScrollAmount' }, + '0072,0316' => { VR => 'CS', Name => 'ImageBoxLargeScrollType' }, + '0072,0318' => { VR => 'US', Name => 'ImageBoxLargeScrollAmount' }, + '0072,0320' => { VR => 'US', Name => 'ImageBoxOverlapPriority' }, + '0072,0330' => { VR => 'FD', Name => 'CineRelativeToRealTime' }, + '0072,0400' => { VR => 'SQ', Name => 'FilterOperationsSequence' }, + '0072,0402' => { VR => 'CS', Name => 'FilterByCategory' }, + '0072,0404' => { VR => 'CS', Name => 'FilterByAttributePresence' }, + '0072,0406' => { VR => 'CS', Name => 'FilterByOperator' }, + '0072,0432' => { VR => 'US', Name => 'SynchronizedImageBoxList' }, + '0072,0434' => { VR => 'CS', Name => 'TypeOfSynchronization' }, + '0072,0500' => { VR => 'CS', Name => 'BlendingOperationType' }, + '0072,0510' => { VR => 'CS', Name => 'ReformattingOperationType' }, + '0072,0512' => { VR => 'FD', Name => 'ReformattingThickness' }, + '0072,0514' => { VR => 'FD', Name => 'ReformattingInterval' }, + '0072,0516' => { VR => 'CS', Name => 'ReformattingOpInitialViewDir' }, + '0072,0520' => { VR => 'CS', Name => 'RenderingType3D' }, + '0072,0600' => { VR => 'SQ', Name => 'SortingOperationsSequence' }, + '0072,0602' => { VR => 'CS', Name => 'SortByCategory' }, + '0072,0604' => { VR => 'CS', Name => 'SortingDirection' }, + '0072,0700' => { VR => 'CS', Name => 'DisplaySetPatientOrientation' }, + '0072,0702' => { VR => 'CS', Name => 'VOIType' }, + '0072,0704' => { VR => 'CS', Name => 'PseudoColorType' }, + '0072,0706' => { VR => 'CS', Name => 'ShowGrayscaleInverted' }, + '0072,0710' => { VR => 'CS', Name => 'ShowImageTrueSizeFlag' }, + '0072,0712' => { VR => 'CS', Name => 'ShowGraphicAnnotationFlag' }, + '0072,0714' => { VR => 'CS', Name => 'ShowPatientDemographicsFlag' }, + '0072,0716' => { VR => 'CS', Name => 'ShowAcquisitionTechniquesFlag' }, + '0072,0717' => { VR => 'CS', Name => 'DisplaySetHorizontalJustification' }, + '0072,0718' => { VR => 'CS', Name => 'DisplaySetVerticalJustification' }, + '0074,1000' => { VR => 'CS', Name => 'UnifiedProcedureStepState' }, + '0074,1002' => { VR => 'SQ', Name => 'UPSProgressInformationSequence' }, + '0074,1004' => { VR => 'DS', Name => 'UnifiedProcedureStepProgress' }, + '0074,1006' => { VR => 'ST', Name => 'UnifiedProcedureStepProgressDescr' }, + '0074,1008' => { VR => 'SQ', Name => 'UnifiedProcedureStepComURISeq' }, + '0074,100a' => { VR => 'ST', Name => 'ContactURI' }, + '0074,100c' => { VR => 'LO', Name => 'ContactDisplayName' }, + '0074,1020' => { VR => 'SQ', Name => 'BeamTaskSequence' }, + '0074,1022' => { VR => 'CS', Name => 'BeamTaskType' }, + '0074,1024' => { VR => 'IS', Name => 'BeamOrderIndex' }, + '0074,1030' => { VR => 'SQ', Name => 'DeliveryVerificationImageSequence' }, + '0074,1032' => { VR => 'CS', Name => 'VerificationImageTiming' }, + '0074,1034' => { VR => 'CS', Name => 'DoubleExposureFlag' }, + '0074,1036' => { VR => 'CS', Name => 'DoubleExposureOrdering' }, + '0074,1038' => { VR => 'DS', Name => 'DoubleExposureMeterset' }, + '0074,103A' => { VR => 'DS', Name => 'DoubleExposureFieldDelta' }, + '0074,1040' => { VR => 'SQ', Name => 'RelatedReferenceRTImageSequence' }, + '0074,1042' => { VR => 'SQ', Name => 'GeneralMachineVerificationSequence' }, + '0074,1044' => { VR => 'SQ', Name => 'ConventionalMachineVerificationSeq' }, + '0074,1046' => { VR => 'SQ', Name => 'IonMachineVerificationSequence' }, + '0074,1048' => { VR => 'SQ', Name => 'FailedAttributesSequence' }, + '0074,104A' => { VR => 'SQ', Name => 'OverriddenAttributesSequence' }, + '0074,104C' => { VR => 'SQ', Name => 'ConventionalControlPointVerifySeq' }, + '0074,104E' => { VR => 'SQ', Name => 'IonControlPointVerificationSeq' }, + '0074,1050' => { VR => 'SQ', Name => 'AttributeOccurrenceSequence' }, + '0074,1052' => { VR => 'AT', Name => 'AttributeOccurrencePointer' }, + '0074,1054' => { VR => 'UL', Name => 'AttributeItemSelector' }, + '0074,1056' => { VR => 'LO', Name => 'AttributeOccurrencePrivateCreator' }, + '0074,1200' => { VR => 'CS', Name => 'ScheduledProcedureStepPriority' }, + '0074,1202' => { VR => 'LO', Name => 'WorklistLabel' }, + '0074,1204' => { VR => 'LO', Name => 'ProcedureStepLabel' }, + '0074,1210' => { VR => 'SQ', Name => 'ScheduledProcessingParametersSeq' }, + '0074,1212' => { VR => 'SQ', Name => 'PerformedProcessingParametersSeq' }, + '0074,1216' => { VR => 'SQ', Name => 'UPSPerformedProcedureSequence' }, + '0074,1220' => { VR => 'SQ', Name => 'RelatedProcedureStepSequence' }, + '0074,1222' => { VR => 'LO', Name => 'ProcedureStepRelationshipType' }, + '0074,1230' => { VR => 'LO', Name => 'DeletionLock' }, + '0074,1234' => { VR => 'AE', Name => 'ReceivingAE' }, + '0074,1236' => { VR => 'AE', Name => 'RequestingAE' }, + '0074,1238' => { VR => 'LT', Name => 'ReasonForCancellation' }, + '0074,1242' => { VR => 'CS', Name => 'SCPStatus' }, + '0074,1244' => { VR => 'CS', Name => 'SubscriptionListStatus' }, + '0074,1246' => { VR => 'CS', Name => 'UPSListStatus' }, + # storage group + '0088,0130' => { VR => 'SH', Name => 'StorageMediaFileSetID' }, + '0088,0140' => { VR => 'UI', Name => 'StorageMediaFileSetUID' }, + '0088,0200' => { VR => 'SQ', Name => 'IconImageSequence' }, + '0088,0904' => { VR => 'LO', Name => 'TopicTitle' }, + '0088,0906' => { VR => 'ST', Name => 'TopicSubject' }, + '0088,0910' => { VR => 'LO', Name => 'TopicAuthor' }, + '0088,0912' => { VR => 'LO', Name => 'TopicKeywords' }, + '0100,0410' => { VR => 'CS', Name => 'SOPInstanceStatus' }, + '0100,0420' => { VR => 'DT', Name => 'SOPAuthorizationDateAndTime' }, + '0100,0424' => { VR => 'LT', Name => 'SOPAuthorizationComment' }, + '0100,0426' => { VR => 'LO', Name => 'AuthorizationEquipmentCertNumber' }, + '0400,0005' => { VR => 'US', Name => 'MACIDNumber' }, + '0400,0010' => { VR => 'UI', Name => 'MACCalculationTransferSyntaxUID' }, + '0400,0015' => { VR => 'CS', Name => 'MACAlgorithm' }, + '0400,0020' => { VR => 'AT', Name => 'DataElementsSigned' }, + '0400,0100' => { VR => 'UI', Name => 'DigitalSignatureUID' }, + '0400,0105' => { VR => 'DT', Name => 'DigitalSignatureDateTime' }, + '0400,0110' => { VR => 'CS', Name => 'CertificateType' }, + '0400,0115' => { VR => 'OB', Name => 'CertificateOfSigner' }, + '0400,0120' => { VR => 'OB', Name => 'Signature' }, + '0400,0305' => { VR => 'CS', Name => 'CertifiedTimestampType' }, + '0400,0310' => { VR => 'OB', Name => 'CertifiedTimestamp' }, + '0400,0401' => { VR => 'SQ', Name => 'DigitalSignaturePurposeCodeSeq' }, + '0400,0402' => { VR => 'SQ', Name => 'ReferencedDigitalSignatureSeq' }, + '0400,0403' => { VR => 'SQ', Name => 'ReferencedSOPInstanceMACSeq' }, + '0400,0404' => { VR => 'OB', Name => 'MAC' }, + '0400,0500' => { VR => 'SQ', Name => 'EncryptedAttributesSequence' }, + '0400,0510' => { VR => 'UI', Name => 'EncryptedContentTransferSyntaxUID' }, + '0400,0520' => { VR => 'OB', Name => 'EncryptedContent' }, + '0400,0550' => { VR => 'SQ', Name => 'ModifiedAttributesSequence' }, + '0400,0561' => { VR => 'SQ', Name => 'OriginalAttributesSequence' }, + '0400,0562' => { VR => 'DT', Name => 'AttributeModificationDateTime' }, + '0400,0563' => { VR => 'LO', Name => 'ModifyingSystem' }, + '0400,0564' => { VR => 'LO', Name => 'SourceOfPreviousValues' }, + '0400,0565' => { VR => 'CS', Name => 'ReasonForTheAttributeModification' }, + '1000,xxx0' => { VR => 'US', Name => 'EscapeTriplet' }, + '1000,xxx1' => { VR => 'US', Name => 'RunLengthTriplet' }, + '1000,xxx2' => { VR => 'US', Name => 'HuffmanTableSize' }, + '1000,xxx3' => { VR => 'US', Name => 'HuffmanTableTriplet' }, + '1000,xxx4' => { VR => 'US', Name => 'ShiftTableSize' }, + '1000,xxx5' => { VR => 'US', Name => 'ShiftTableTriplet' }, + '1010,xxxx' => { VR => 'US', Name => 'ZonalMap' }, + '2000,0010' => { VR => 'IS', Name => 'NumberOfCopies' }, + '2000,001E' => { VR => 'SQ', Name => 'PrinterConfigurationSequence' }, + '2000,0020' => { VR => 'CS', Name => 'PrintPriority' }, + '2000,0030' => { VR => 'CS', Name => 'MediumType' }, + '2000,0040' => { VR => 'CS', Name => 'FilmDestination' }, + '2000,0050' => { VR => 'LO', Name => 'FilmSessionLabel' }, + '2000,0060' => { VR => 'IS', Name => 'MemoryAllocation' }, + '2000,0061' => { VR => 'IS', Name => 'MaximumMemoryAllocation' }, + '2000,0062' => { VR => 'CS', Name => 'ColorImagePrintingFlag' }, + '2000,0063' => { VR => 'CS', Name => 'CollationFlag' }, + '2000,0065' => { VR => 'CS', Name => 'AnnotationFlag' }, + '2000,0067' => { VR => 'CS', Name => 'ImageOverlayFlag' }, + '2000,0069' => { VR => 'CS', Name => 'PresentationLUTFlag' }, + '2000,006A' => { VR => 'CS', Name => 'ImageBoxPresentationLUTFlag' }, + '2000,00A0' => { VR => 'US', Name => 'MemoryBitDepth' }, + '2000,00A1' => { VR => 'US', Name => 'PrintingBitDepth' }, + '2000,00A2' => { VR => 'SQ', Name => 'MediaInstalledSequence' }, + '2000,00A4' => { VR => 'SQ', Name => 'OtherMediaAvailableSequence' }, + '2000,00A8' => { VR => 'SQ', Name => 'SupportedImageDisplayFormatSeq' }, + '2000,0500' => { VR => 'SQ', Name => 'ReferencedFilmBoxSequence' }, + '2000,0510' => { VR => 'SQ', Name => 'ReferencedStoredPrintSequence' }, + # film box group + '2010,0010' => { VR => 'ST', Name => 'ImageDisplayFormat' }, + '2010,0030' => { VR => 'CS', Name => 'AnnotationDisplayFormatID' }, + '2010,0040' => { VR => 'CS', Name => 'FilmOrientation' }, + '2010,0050' => { VR => 'CS', Name => 'FilmSizeID' }, + '2010,0052' => { VR => 'CS', Name => 'PrinterResolutionID' }, + '2010,0054' => { VR => 'CS', Name => 'DefaultPrinterResolutionID' }, + '2010,0060' => { VR => 'CS', Name => 'MagnificationType' }, + '2010,0080' => { VR => 'CS', Name => 'SmoothingType' }, + '2010,00A6' => { VR => 'CS', Name => 'DefaultMagnificationType' }, + '2010,00A7' => { VR => 'CS', Name => 'OtherMagnificationTypesAvailable' }, + '2010,00A8' => { VR => 'CS', Name => 'DefaultSmoothingType' }, + '2010,00A9' => { VR => 'CS', Name => 'OtherSmoothingTypesAvailable' }, + '2010,0100' => { VR => 'CS', Name => 'BorderDensity' }, + '2010,0110' => { VR => 'CS', Name => 'EmptyImageDensity' }, + '2010,0120' => { VR => 'US', Name => 'MinDensity' }, + '2010,0130' => { VR => 'US', Name => 'MaxDensity' }, + '2010,0140' => { VR => 'CS', Name => 'Trim' }, + '2010,0150' => { VR => 'ST', Name => 'ConfigurationInformation' }, + '2010,0152' => { VR => 'LT', Name => 'ConfigurationInformationDescr' }, + '2010,0154' => { VR => 'IS', Name => 'MaximumCollatedFilms' }, + '2010,015E' => { VR => 'US', Name => 'Illumination' }, + '2010,0160' => { VR => 'US', Name => 'ReflectedAmbientLight' }, + '2010,0376' => { VR => 'DS', Name => 'PrinterPixelSpacing' }, + '2010,0500' => { VR => 'SQ', Name => 'ReferencedFilmSessionSequence' }, + '2010,0510' => { VR => 'SQ', Name => 'ReferencedImageBoxSequence' }, + '2010,0520' => { VR => 'SQ', Name => 'ReferencedBasicAnnotationBoxSeq' }, + # image box group + '2020,0010' => { VR => 'US', Name => 'ImageBoxPosition' }, + '2020,0020' => { VR => 'CS', Name => 'Polarity' }, + '2020,0030' => { VR => 'DS', Name => 'RequestedImageSize' }, + '2020,0040' => { VR => 'CS', Name => 'RequestedDecimate-CropBehavior' }, + '2020,0050' => { VR => 'CS', Name => 'RequestedResolutionID' }, + '2020,00A0' => { VR => 'CS', Name => 'RequestedImageSizeFlag' }, + '2020,00A2' => { VR => 'CS', Name => 'DecimateCropResult' }, + '2020,0110' => { VR => 'SQ', Name => 'BasicGrayscaleImageSequence' }, + '2020,0111' => { VR => 'SQ', Name => 'BasicColorImageSequence' }, + '2020,0130' => { VR => 'SQ', Name => 'ReferencedImageOverlayBoxSequence' }, + '2020,0140' => { VR => 'SQ', Name => 'ReferencedVOILUTBoxSequence' }, + # annotation group + '2030,0010' => { VR => 'US', Name => 'AnnotationPosition' }, + '2030,0020' => { VR => 'LO', Name => 'TextString' }, + # overlay box group + '2040,0010' => { VR => 'SQ', Name => 'ReferencedOverlayPlaneSequence' }, + '2040,0011' => { VR => 'US', Name => 'ReferencedOverlayPlaneGroups' }, + '2040,0020' => { VR => 'SQ', Name => 'OverlayPixelDataSequence' }, + '2040,0060' => { VR => 'CS', Name => 'OverlayMagnificationType' }, + '2040,0070' => { VR => 'CS', Name => 'OverlaySmoothingType' }, + '2040,0072' => { VR => 'CS', Name => 'OverlayOrImageMagnification' }, + '2040,0074' => { VR => 'US', Name => 'MagnifyToNumberOfColumns' }, + '2040,0080' => { VR => 'CS', Name => 'OverlayForegroundDensity' }, + '2040,0082' => { VR => 'CS', Name => 'OverlayBackgroundDensity' }, + '2040,0090' => { VR => 'CS', Name => 'OverlayMode' }, + '2040,0100' => { VR => 'CS', Name => 'ThresholdDensity' }, + '2040,0500' => { VR => 'SQ', Name => 'ReferencedImageBoxSequence' }, + '2050,0010' => { VR => 'SQ', Name => 'PresentationLUTSequence' }, + '2050,0020' => { VR => 'CS', Name => 'PresentationLUTShape' }, + '2050,0500' => { VR => 'SQ', Name => 'ReferencedPresentationLUTSequence' }, + '2100,0010' => { VR => 'SH', Name => 'PrintJobID' }, + '2100,0020' => { VR => 'CS', Name => 'ExecutionStatus' }, + '2100,0030' => { VR => 'CS', Name => 'ExecutionStatusInfo' }, + '2100,0040' => { VR => 'DA', Name => 'CreationDate' }, + '2100,0050' => { VR => 'TM', Name => 'CreationTime' }, + '2100,0070' => { VR => 'AE', Name => 'Originator' }, + '2100,0140' => { VR => 'AE', Name => 'DestinationAE' }, + '2100,0160' => { VR => 'SH', Name => 'OwnerID' }, + '2100,0170' => { VR => 'IS', Name => 'NumberOfFilms' }, + '2100,0500' => { VR => 'SQ', Name => 'ReferencedPrintJobSequence' }, + # printer group + '2110,0010' => { VR => 'CS', Name => 'PrinterStatus' }, + '2110,0020' => { VR => 'CS', Name => 'PrinterStatusInfo' }, + '2110,0030' => { VR => 'LO', Name => 'PrinterName' }, + '2110,0099' => { VR => 'SH', Name => 'PrintQueueID' }, + '2120,0010' => { VR => 'CS', Name => 'QueueStatus' }, + # print job group + '2120,0050' => { VR => 'SQ', Name => 'PrintJobDescriptionSequence' }, + '2120,0070' => { VR => 'SQ', Name => 'ReferencedPrintJobSequence' }, + '2130,0010' => { VR => 'SQ', Name => 'PrintManagementCapabilitiesSeq' }, + '2130,0015' => { VR => 'SQ', Name => 'PrinterCharacteristicsSequence' }, + '2130,0030' => { VR => 'SQ', Name => 'FilmBoxContentSequence' }, + '2130,0040' => { VR => 'SQ', Name => 'ImageBoxContentSequence' }, + '2130,0050' => { VR => 'SQ', Name => 'AnnotationContentSequence' }, + '2130,0060' => { VR => 'SQ', Name => 'ImageOverlayBoxContentSequence' }, + '2130,0080' => { VR => 'SQ', Name => 'PresentationLUTContentSequence' }, + '2130,00A0' => { VR => 'SQ', Name => 'ProposedStudySequence' }, + '2130,00C0' => { VR => 'SQ', Name => 'OriginalImageSequence' }, + '2200,0001' => { VR => 'CS', Name => 'LabelFromInfoExtractedFromInstance' }, + '2200,0002' => { VR => 'UT', Name => 'LabelText' }, + '2200,0003' => { VR => 'CS', Name => 'LabelStyleSelection' }, + '2200,0004' => { VR => 'LT', Name => 'MediaDisposition' }, + '2200,0005' => { VR => 'LT', Name => 'BarcodeValue' }, + '2200,0006' => { VR => 'CS', Name => 'BarcodeSymbology' }, + '2200,0007' => { VR => 'CS', Name => 'AllowMediaSplitting' }, + '2200,0008' => { VR => 'CS', Name => 'IncludeNonDICOMObjects' }, + '2200,0009' => { VR => 'CS', Name => 'IncludeDisplayApplication' }, + '2200,000A' => { VR => 'CS', Name => 'SaveCompInstancesAfterMediaCreate' }, + '2200,000B' => { VR => 'US', Name => 'TotalNumberMediaPiecesCreated' }, + '2200,000C' => { VR => 'LO', Name => 'RequestedMediaApplicationProfile' }, + '2200,000D' => { VR => 'SQ', Name => 'ReferencedStorageMediaSequence' }, + '2200,000E' => { VR => 'AT', Name => 'FailureAttributes' }, + '2200,000F' => { VR => 'CS', Name => 'AllowLossyCompression' }, + '2200,0020' => { VR => 'CS', Name => 'RequestPriority' }, + '3002,0002' => { VR => 'SH', Name => 'RTImageLabel' }, + '3002,0003' => { VR => 'LO', Name => 'RTImageName' }, + '3002,0004' => { VR => 'ST', Name => 'RTImageDescription' }, + '3002,000A' => { VR => 'CS', Name => 'ReportedValuesOrigin' }, + '3002,000C' => { VR => 'CS', Name => 'RTImagePlane' }, + '3002,000D' => { VR => 'DS', Name => 'XRayImageReceptorTranslation' }, + '3002,000E' => { VR => 'DS', Name => 'XRayImageReceptorAngle' }, + '3002,0010' => { VR => 'DS', Name => 'RTImageOrientation' }, + '3002,0011' => { VR => 'DS', Name => 'ImagePlanePixelSpacing' }, + '3002,0012' => { VR => 'DS', Name => 'RTImagePosition' }, + '3002,0020' => { VR => 'SH', Name => 'RadiationMachineName' }, + '3002,0022' => { VR => 'DS', Name => 'RadiationMachineSAD' }, + '3002,0024' => { VR => 'DS', Name => 'RadiationMachineSSD' }, + '3002,0026' => { VR => 'DS', Name => 'RTImageSID' }, + '3002,0028' => { VR => 'DS', Name => 'SourceToReferenceObjectDistance' }, + '3002,0029' => { VR => 'IS', Name => 'FractionNumber' }, + '3002,0030' => { VR => 'SQ', Name => 'ExposureSequence' }, + '3002,0032' => { VR => 'DS', Name => 'MetersetExposure' }, + '3002,0034' => { VR => 'DS', Name => 'DiaphragmPosition' }, + '3002,0040' => { VR => 'SQ', Name => 'FluenceMapSequence' }, + '3002,0041' => { VR => 'CS', Name => 'FluenceDataSource' }, + '3002,0042' => { VR => 'DS', Name => 'FluenceDataScale' }, + '3002,0051' => { VR => 'CS', Name => 'FluenceMode' }, + '3002,0052' => { VR => 'SH', Name => 'FluenceModeID' }, + '3004,0001' => { VR => 'CS', Name => 'DVHType' }, + '3004,0002' => { VR => 'CS', Name => 'DoseUnits' }, + '3004,0004' => { VR => 'CS', Name => 'DoseType' }, + '3004,0006' => { VR => 'LO', Name => 'DoseComment' }, + '3004,0008' => { VR => 'DS', Name => 'NormalizationPoint' }, + '3004,000A' => { VR => 'CS', Name => 'DoseSummationType' }, + '3004,000C' => { VR => 'DS', Name => 'GridFrameOffsetVector' }, + '3004,000E' => { VR => 'DS', Name => 'DoseGridScaling' }, + '3004,0010' => { VR => 'SQ', Name => 'RTDoseROISequence' }, + '3004,0012' => { VR => 'DS', Name => 'DoseValue' }, + '3004,0014' => { VR => 'CS', Name => 'TissueHeterogeneityCorrection' }, + '3004,0040' => { VR => 'DS', Name => 'DVHNormalizationPoint' }, + '3004,0042' => { VR => 'DS', Name => 'DVHNormalizationDoseValue' }, + '3004,0050' => { VR => 'SQ', Name => 'DVHSequence' }, + '3004,0052' => { VR => 'DS', Name => 'DVHDoseScaling' }, + '3004,0054' => { VR => 'CS', Name => 'DVHVolumeUnits' }, + '3004,0056' => { VR => 'IS', Name => 'DVHNumberOfBins' }, + '3004,0058' => { VR => 'DS', Name => 'DVHData' }, + '3004,0060' => { VR => 'SQ', Name => 'DVHReferencedROISequence' }, + '3004,0062' => { VR => 'CS', Name => 'DVHROIContributionType' }, + '3004,0070' => { VR => 'DS', Name => 'DVHMinimumDose' }, + '3004,0072' => { VR => 'DS', Name => 'DVHMaximumDose' }, + '3004,0074' => { VR => 'DS', Name => 'DVHMeanDose' }, + '3006,0002' => { VR => 'SH', Name => 'StructureSetLabel' }, + '3006,0004' => { VR => 'LO', Name => 'StructureSetName' }, + '3006,0006' => { VR => 'ST', Name => 'StructureSetDescription' }, + '3006,0008' => { VR => 'DA', Name => 'StructureSetDate' }, + '3006,0009' => { VR => 'TM', Name => 'StructureSetTime' }, + '3006,0010' => { VR => 'SQ', Name => 'ReferencedFrameOfReferenceSequence' }, + '3006,0012' => { VR => 'SQ', Name => 'RTReferencedStudySequence' }, + '3006,0014' => { VR => 'SQ', Name => 'RTReferencedSeriesSequence' }, + '3006,0016' => { VR => 'SQ', Name => 'ContourImageSequence' }, + '3006,0020' => { VR => 'SQ', Name => 'StructureSetROISequence' }, + '3006,0022' => { VR => 'IS', Name => 'ROINumber' }, + '3006,0024' => { VR => 'UI', Name => 'ReferencedFrameOfReferenceUID' }, + '3006,0026' => { VR => 'LO', Name => 'ROIName' }, + '3006,0028' => { VR => 'ST', Name => 'ROIDescription' }, + '3006,002A' => { VR => 'IS', Name => 'ROIDisplayColor' }, + '3006,002C' => { VR => 'DS', Name => 'ROIVolume' }, + '3006,0030' => { VR => 'SQ', Name => 'RTRelatedROISequence' }, + '3006,0033' => { VR => 'CS', Name => 'RTROIRelationship' }, + '3006,0036' => { VR => 'CS', Name => 'ROIGenerationAlgorithm' }, + '3006,0038' => { VR => 'LO', Name => 'ROIGenerationDescription' }, + '3006,0039' => { VR => 'SQ', Name => 'ROIContourSequence' }, + '3006,0040' => { VR => 'SQ', Name => 'ContourSequence' }, + '3006,0042' => { VR => 'CS', Name => 'ContourGeometricType' }, + '3006,0044' => { VR => 'DS', Name => 'ContourSlabThickness' }, + '3006,0045' => { VR => 'DS', Name => 'ContourOffsetVector' }, + '3006,0046' => { VR => 'IS', Name => 'NumberOfContourPoints' }, + '3006,0048' => { VR => 'IS', Name => 'ContourNumber' }, + '3006,0049' => { VR => 'IS', Name => 'AttachedContours' }, + '3006,0050' => { VR => 'DS', Name => 'ContourData' }, + '3006,0080' => { VR => 'SQ', Name => 'RTROIObservationsSequence' }, + '3006,0082' => { VR => 'IS', Name => 'ObservationNumber' }, + '3006,0084' => { VR => 'IS', Name => 'ReferencedROINumber' }, + '3006,0085' => { VR => 'SH', Name => 'ROIObservationLabel' }, + '3006,0086' => { VR => 'SQ', Name => 'RTROIIdentificationCodeSequence' }, + '3006,0088' => { VR => 'ST', Name => 'ROIObservationDescription' }, + '3006,00A0' => { VR => 'SQ', Name => 'RelatedRTROIObservationsSequence' }, + '3006,00A4' => { VR => 'CS', Name => 'RTROIInterpretedType' }, + '3006,00A6' => { VR => 'PN', Name => 'ROIInterpreter' }, + '3006,00B0' => { VR => 'SQ', Name => 'ROIPhysicalPropertiesSequence' }, + '3006,00B2' => { VR => 'CS', Name => 'ROIPhysicalProperty' }, + '3006,00B4' => { VR => 'DS', Name => 'ROIPhysicalPropertyValue' }, + '3006,00B6' => { VR => 'SQ', Name => 'ROIElementalCompositionSequence' }, + '3006,00B7' => { VR => 'US', Name => 'ROIElementalCompAtomicNumber' }, + '3006,00B8' => { VR => 'FL', Name => 'ROIElementalCompAtomicMassFraction' }, + '3006,00C0' => { VR => 'SQ', Name => 'FrameOfReferenceRelationshipSeq' }, + '3006,00C2' => { VR => 'UI', Name => 'RelatedFrameOfReferenceUID' }, + '3006,00C4' => { VR => 'CS', Name => 'FrameOfReferenceTransformType' }, + '3006,00C6' => { VR => 'DS', Name => 'FrameOfReferenceTransformMatrix' }, + '3006,00C8' => { VR => 'LO', Name => 'FrameOfReferenceTransformComment' }, + '3008,0010' => { VR => 'SQ', Name => 'MeasuredDoseReferenceSequence' }, + '3008,0012' => { VR => 'ST', Name => 'MeasuredDoseDescription' }, + '3008,0014' => { VR => 'CS', Name => 'MeasuredDoseType' }, + '3008,0016' => { VR => 'DS', Name => 'MeasuredDoseValue' }, + '3008,0020' => { VR => 'SQ', Name => 'TreatmentSessionBeamSequence' }, + '3008,0021' => { VR => 'SQ', Name => 'TreatmentSessionIonBeamSequence' }, + '3008,0022' => { VR => 'IS', Name => 'CurrentFractionNumber' }, + '3008,0024' => { VR => 'DA', Name => 'TreatmentControlPointDate' }, + '3008,0025' => { VR => 'TM', Name => 'TreatmentControlPointTime' }, + '3008,002A' => { VR => 'CS', Name => 'TreatmentTerminationStatus' }, + '3008,002B' => { VR => 'SH', Name => 'TreatmentTerminationCode' }, + '3008,002C' => { VR => 'CS', Name => 'TreatmentVerificationStatus' }, + '3008,0030' => { VR => 'SQ', Name => 'ReferencedTreatmentRecordSequence' }, + '3008,0032' => { VR => 'DS', Name => 'SpecifiedPrimaryMeterset' }, + '3008,0033' => { VR => 'DS', Name => 'SpecifiedSecondaryMeterset' }, + '3008,0036' => { VR => 'DS', Name => 'DeliveredPrimaryMeterset' }, + '3008,0037' => { VR => 'DS', Name => 'DeliveredSecondaryMeterset' }, + '3008,003A' => { VR => 'DS', Name => 'SpecifiedTreatmentTime' }, + '3008,003B' => { VR => 'DS', Name => 'DeliveredTreatmentTime' }, + '3008,0040' => { VR => 'SQ', Name => 'ControlPointDeliverySequence' }, + '3008,0041' => { VR => 'SQ', Name => 'IonControlPointDeliverySequence' }, + '3008,0042' => { VR => 'DS', Name => 'SpecifiedMeterset' }, + '3008,0044' => { VR => 'DS', Name => 'DeliveredMeterset' }, + '3008,0045' => { VR => 'FL', Name => 'MetersetRateSet' }, + '3008,0046' => { VR => 'FL', Name => 'MetersetRateDelivered' }, + '3008,0047' => { VR => 'FL', Name => 'ScanSpotMetersetsDelivered' }, + '3008,0048' => { VR => 'DS', Name => 'DoseRateDelivered' }, + '3008,0050' => { VR => 'SQ', Name => 'TreatmentSummaryCalcDoseRefSeq' }, + '3008,0052' => { VR => 'DS', Name => 'CumulativeDoseToDoseReference' }, + '3008,0054' => { VR => 'DA', Name => 'FirstTreatmentDate' }, + '3008,0056' => { VR => 'DA', Name => 'MostRecentTreatmentDate' }, + '3008,005A' => { VR => 'IS', Name => 'NumberOfFractionsDelivered' }, + '3008,0060' => { VR => 'SQ', Name => 'OverrideSequence' }, + '3008,0061' => { VR => 'AT', Name => 'ParameterSequencePointer' }, + '3008,0062' => { VR => 'AT', Name => 'OverrideParameterPointer' }, + '3008,0063' => { VR => 'IS', Name => 'ParameterItemIndex' }, + '3008,0064' => { VR => 'IS', Name => 'MeasuredDoseReferenceNumber' }, + '3008,0065' => { VR => 'AT', Name => 'ParameterPointer' }, + '3008,0066' => { VR => 'ST', Name => 'OverrideReason' }, + '3008,0068' => { VR => 'SQ', Name => 'CorrectedParameterSequence' }, + '3008,006A' => { VR => 'FL', Name => 'CorrectionValue' }, + '3008,0070' => { VR => 'SQ', Name => 'CalculatedDoseReferenceSequence' }, + '3008,0072' => { VR => 'IS', Name => 'CalculatedDoseReferenceNumber' }, + '3008,0074' => { VR => 'ST', Name => 'CalculatedDoseReferenceDescription' }, + '3008,0076' => { VR => 'DS', Name => 'CalculatedDoseReferenceDoseValue' }, + '3008,0078' => { VR => 'DS', Name => 'StartMeterset' }, + '3008,007A' => { VR => 'DS', Name => 'EndMeterset' }, + '3008,0080' => { VR => 'SQ', Name => 'ReferencedMeasuredDoseReferenceSeq' }, + '3008,0082' => { VR => 'IS', Name => 'ReferencedMeasuredDoseReferenceNum' }, + '3008,0090' => { VR => 'SQ', Name => 'ReferencedCalculatedDoseRefSeq' }, + '3008,0092' => { VR => 'IS', Name => 'ReferencedCalculatedDoseRefNumber' }, + '3008,00A0' => { VR => 'SQ', Name => 'BeamLimitingDeviceLeafPairsSeq' }, + '3008,00B0' => { VR => 'SQ', Name => 'RecordedWedgeSequence' }, + '3008,00C0' => { VR => 'SQ', Name => 'RecordedCompensatorSequence' }, + '3008,00D0' => { VR => 'SQ', Name => 'RecordedBlockSequence' }, + '3008,00E0' => { VR => 'SQ', Name => 'TreatmentSummaryMeasuredDoseRefSeq' }, + '3008,00F0' => { VR => 'SQ', Name => 'RecordedSnoutSequence' }, + '3008,00F2' => { VR => 'SQ', Name => 'RecordedRangeShifterSequence' }, + '3008,00F4' => { VR => 'SQ', Name => 'RecordedLateralSpreadingDeviceSeq' }, + '3008,00F6' => { VR => 'SQ', Name => 'RecordedRangeModulatorSequence' }, + '3008,0100' => { VR => 'SQ', Name => 'RecordedSourceSequence' }, + '3008,0105' => { VR => 'LO', Name => 'SourceSerialNumber' }, + '3008,0110' => { VR => 'SQ', Name => 'TreatmentSessionAppSetupSeq' }, + '3008,0116' => { VR => 'CS', Name => 'ApplicationSetupCheck' }, + '3008,0120' => { VR => 'SQ', Name => 'RecordedBrachyAccessoryDeviceSeq' }, + '3008,0122' => { VR => 'IS', Name => 'ReferencedBrachyAccessoryDeviceNum' }, + '3008,0130' => { VR => 'SQ', Name => 'RecordedChannelSequence' }, + '3008,0132' => { VR => 'DS', Name => 'SpecifiedChannelTotalTime' }, + '3008,0134' => { VR => 'DS', Name => 'DeliveredChannelTotalTime' }, + '3008,0136' => { VR => 'IS', Name => 'SpecifiedNumberOfPulses' }, + '3008,0138' => { VR => 'IS', Name => 'DeliveredNumberOfPulses' }, + '3008,013A' => { VR => 'DS', Name => 'SpecifiedPulseRepetitionInterval' }, + '3008,013C' => { VR => 'DS', Name => 'DeliveredPulseRepetitionInterval' }, + '3008,0140' => { VR => 'SQ', Name => 'RecordedSourceApplicatorSequence' }, + '3008,0142' => { VR => 'IS', Name => 'ReferencedSourceApplicatorNumber' }, + '3008,0150' => { VR => 'SQ', Name => 'RecordedChannelShieldSequence' }, + '3008,0152' => { VR => 'IS', Name => 'ReferencedChannelShieldNumber' }, + '3008,0160' => { VR => 'SQ', Name => 'BrachyControlPointDeliveredSeq' }, + '3008,0162' => { VR => 'DA', Name => 'SafePositionExitDate' }, + '3008,0164' => { VR => 'TM', Name => 'SafePositionExitTime' }, + '3008,0166' => { VR => 'DA', Name => 'SafePositionReturnDate' }, + '3008,0168' => { VR => 'TM', Name => 'SafePositionReturnTime' }, + '3008,0200' => { VR => 'CS', Name => 'CurrentTreatmentStatus' }, + '3008,0202' => { VR => 'ST', Name => 'TreatmentStatusComment' }, + '3008,0220' => { VR => 'SQ', Name => 'FractionGroupSummarySequence' }, + '3008,0223' => { VR => 'IS', Name => 'ReferencedFractionNumber' }, + '3008,0224' => { VR => 'CS', Name => 'FractionGroupType' }, + '3008,0230' => { VR => 'CS', Name => 'BeamStopperPosition' }, + '3008,0240' => { VR => 'SQ', Name => 'FractionStatusSummarySequence' }, + '3008,0250' => { VR => 'DA', Name => 'TreatmentDate' }, + '3008,0251' => { VR => 'TM', Name => 'TreatmentTime' }, + '300A,0002' => { VR => 'SH', Name => 'RTPlanLabel' }, + '300A,0003' => { VR => 'LO', Name => 'RTPlanName' }, + '300A,0004' => { VR => 'ST', Name => 'RTPlanDescription' }, + '300A,0006' => { VR => 'DA', Name => 'RTPlanDate' }, + '300A,0007' => { VR => 'TM', Name => 'RTPlanTime' }, + '300A,0009' => { VR => 'LO', Name => 'TreatmentProtocols' }, + '300A,000A' => { VR => 'CS', Name => 'PlanIntent' }, + '300A,000B' => { VR => 'LO', Name => 'TreatmentSites' }, + '300A,000C' => { VR => 'CS', Name => 'RTPlanGeometry' }, + '300A,000E' => { VR => 'ST', Name => 'PrescriptionDescription' }, + '300A,0010' => { VR => 'SQ', Name => 'DoseReferenceSequence' }, + '300A,0012' => { VR => 'IS', Name => 'DoseReferenceNumber' }, + '300A,0013' => { VR => 'UI', Name => 'DoseReferenceUID' }, + '300A,0014' => { VR => 'CS', Name => 'DoseReferenceStructureType' }, + '300A,0015' => { VR => 'CS', Name => 'NominalBeamEnergyUnit' }, + '300A,0016' => { VR => 'LO', Name => 'DoseReferenceDescription' }, + '300A,0018' => { VR => 'DS', Name => 'DoseReferencePointCoordinates' }, + '300A,001A' => { VR => 'DS', Name => 'NominalPriorDose' }, + '300A,0020' => { VR => 'CS', Name => 'DoseReferenceType' }, + '300A,0021' => { VR => 'DS', Name => 'ConstraintWeight' }, + '300A,0022' => { VR => 'DS', Name => 'DeliveryWarningDose' }, + '300A,0023' => { VR => 'DS', Name => 'DeliveryMaximumDose' }, + '300A,0025' => { VR => 'DS', Name => 'TargetMinimumDose' }, + '300A,0026' => { VR => 'DS', Name => 'TargetPrescriptionDose' }, + '300A,0027' => { VR => 'DS', Name => 'TargetMaximumDose' }, + '300A,0028' => { VR => 'DS', Name => 'TargetUnderdoseVolumeFraction' }, + '300A,002A' => { VR => 'DS', Name => 'OrganAtRiskFullVolumeDose' }, + '300A,002B' => { VR => 'DS', Name => 'OrganAtRiskLimitDose' }, + '300A,002C' => { VR => 'DS', Name => 'OrganAtRiskMaximumDose' }, + '300A,002D' => { VR => 'DS', Name => 'OrganAtRiskOverdoseVolumeFraction' }, + '300A,0040' => { VR => 'SQ', Name => 'ToleranceTableSequence' }, + '300A,0042' => { VR => 'IS', Name => 'ToleranceTableNumber' }, + '300A,0043' => { VR => 'SH', Name => 'ToleranceTableLabel' }, + '300A,0044' => { VR => 'DS', Name => 'GantryAngleTolerance' }, + '300A,0046' => { VR => 'DS', Name => 'BeamLimitingDeviceAngleTolerance' }, + '300A,0048' => { VR => 'SQ', Name => 'BeamLimitingDeviceToleranceSeq' }, + '300A,004A' => { VR => 'DS', Name => 'BeamLimitingDevicePositionTol' }, + '300A,004B' => { VR => 'FL', Name => 'SnoutPositionTolerance' }, + '300A,004C' => { VR => 'DS', Name => 'PatientSupportAngleTolerance' }, + '300A,004E' => { VR => 'DS', Name => 'TableTopEccentricAngleTolerance' }, + '300A,004F' => { VR => 'FL', Name => 'TableTopPitchAngleTolerance' }, + '300A,0050' => { VR => 'FL', Name => 'TableTopRollAngleTolerance' }, + '300A,0051' => { VR => 'DS', Name => 'TableTopVerticalPositionTolerance' }, + '300A,0052' => { VR => 'DS', Name => 'TableTopLongitudinalPositionTol' }, + '300A,0053' => { VR => 'DS', Name => 'TableTopLateralPositionTolerance' }, + '300A,0055' => { VR => 'CS', Name => 'RTPlanRelationship' }, + '300A,0070' => { VR => 'SQ', Name => 'FractionGroupSequence' }, + '300A,0071' => { VR => 'IS', Name => 'FractionGroupNumber' }, + '300A,0072' => { VR => 'LO', Name => 'FractionGroupDescription' }, + '300A,0078' => { VR => 'IS', Name => 'NumberOfFractionsPlanned' }, + '300A,0079' => { VR => 'IS', Name => 'NumberFractionPatternDigitsPerDay' }, + '300A,007A' => { VR => 'IS', Name => 'RepeatFractionCycleLength' }, + '300A,007B' => { VR => 'LT', Name => 'FractionPattern' }, + '300A,0080' => { VR => 'IS', Name => 'NumberOfBeams' }, + '300A,0082' => { VR => 'DS', Name => 'BeamDoseSpecificationPoint' }, + '300A,0084' => { VR => 'DS', Name => 'BeamDose' }, + '300A,0086' => { VR => 'DS', Name => 'BeamMeterset' }, + '300A,0088' => { VR => 'FL', Name => 'BeamDosePointDepth' }, + '300A,0089' => { VR => 'FL', Name => 'BeamDosePointEquivalentDepth' }, + '300A,008A' => { VR => 'FL', Name => 'BeamDosePointSSD' }, + '300A,00A0' => { VR => 'IS', Name => 'NumberOfBrachyApplicationSetups' }, + '300A,00A2' => { VR => 'DS', Name => 'BrachyAppSetupDoseSpecPoint' }, + '300A,00A4' => { VR => 'DS', Name => 'BrachyApplicationSetupDose' }, + '300A,00B0' => { VR => 'SQ', Name => 'BeamSequence' }, + '300A,00B2' => { VR => 'SH', Name => 'TreatmentMachineName' }, + '300A,00B3' => { VR => 'CS', Name => 'PrimaryDosimeterUnit' }, + '300A,00B4' => { VR => 'DS', Name => 'SourceAxisDistance' }, + '300A,00B6' => { VR => 'SQ', Name => 'BeamLimitingDeviceSequence' }, + '300A,00B8' => { VR => 'CS', Name => 'RTBeamLimitingDeviceType' }, + '300A,00BA' => { VR => 'DS', Name => 'SourceToBeamLimitingDeviceDistance' }, + '300A,00BB' => { VR => 'FL', Name => 'IsocenterToBeamLimitingDeviceDist' }, + '300A,00BC' => { VR => 'IS', Name => 'NumberOfLeafJawPairs' }, + '300A,00BE' => { VR => 'DS', Name => 'LeafPositionBoundaries' }, + '300A,00C0' => { VR => 'IS', Name => 'BeamNumber' }, + '300A,00C2' => { VR => 'LO', Name => 'BeamName' }, + '300A,00C3' => { VR => 'ST', Name => 'BeamDescription' }, + '300A,00C4' => { VR => 'CS', Name => 'BeamType' }, + '300A,00C6' => { VR => 'CS', Name => 'RadiationType' }, + '300A,00C7' => { VR => 'CS', Name => 'HighDoseTechniqueType' }, + '300A,00C8' => { VR => 'IS', Name => 'ReferenceImageNumber' }, + '300A,00CA' => { VR => 'SQ', Name => 'PlannedVerificationImageSequence' }, + '300A,00CC' => { VR => 'LO', Name => 'ImagingDeviceSpecificAcqParams' }, + '300A,00CE' => { VR => 'CS', Name => 'TreatmentDeliveryType' }, + '300A,00D0' => { VR => 'IS', Name => 'NumberOfWedges' }, + '300A,00D1' => { VR => 'SQ', Name => 'WedgeSequence' }, + '300A,00D2' => { VR => 'IS', Name => 'WedgeNumber' }, + '300A,00D3' => { VR => 'CS', Name => 'WedgeType' }, + '300A,00D4' => { VR => 'SH', Name => 'WedgeID' }, + '300A,00D5' => { VR => 'IS', Name => 'WedgeAngle' }, + '300A,00D6' => { VR => 'DS', Name => 'WedgeFactor' }, + '300A,00D7' => { VR => 'FL', Name => 'TotalWedgeTrayWaterEquivThickness' }, + '300A,00D8' => { VR => 'DS', Name => 'WedgeOrientation' }, + '300A,00D9' => { VR => 'FL', Name => 'IsocenterToWedgeTrayDistance' }, + '300A,00DA' => { VR => 'DS', Name => 'SourceToWedgeTrayDistance' }, + '300A,00DB' => { VR => 'FL', Name => 'WedgeThinEdgePosition' }, + '300A,00DC' => { VR => 'SH', Name => 'BolusID' }, + '300A,00DD' => { VR => 'ST', Name => 'BolusDescription' }, + '300A,00E0' => { VR => 'IS', Name => 'NumberOfCompensators' }, + '300A,00E1' => { VR => 'SH', Name => 'MaterialID' }, + '300A,00E2' => { VR => 'DS', Name => 'TotalCompensatorTrayFactor' }, + '300A,00E3' => { VR => 'SQ', Name => 'CompensatorSequence' }, + '300A,00E4' => { VR => 'IS', Name => 'CompensatorNumber' }, + '300A,00E5' => { VR => 'SH', Name => 'CompensatorID' }, + '300A,00E6' => { VR => 'DS', Name => 'SourceToCompensatorTrayDistance' }, + '300A,00E7' => { VR => 'IS', Name => 'CompensatorRows' }, + '300A,00E8' => { VR => 'IS', Name => 'CompensatorColumns' }, + '300A,00E9' => { VR => 'DS', Name => 'CompensatorPixelSpacing' }, + '300A,00EA' => { VR => 'DS', Name => 'CompensatorPosition' }, + '300A,00EB' => { VR => 'DS', Name => 'CompensatorTransmissionData' }, + '300A,00EC' => { VR => 'DS', Name => 'CompensatorThicknessData' }, + '300A,00ED' => { VR => 'IS', Name => 'NumberOfBoli' }, + '300A,00EE' => { VR => 'CS', Name => 'CompensatorType' }, + '300A,00F0' => { VR => 'IS', Name => 'NumberOfBlocks' }, + '300A,00F2' => { VR => 'DS', Name => 'TotalBlockTrayFactor' }, + '300A,00F3' => { VR => 'FL', Name => 'TotalBlockTrayWaterEquivThickness' }, + '300A,00F4' => { VR => 'SQ', Name => 'BlockSequence' }, + '300A,00F5' => { VR => 'SH', Name => 'BlockTrayID' }, + '300A,00F6' => { VR => 'DS', Name => 'SourceToBlockTrayDistance' }, + '300A,00F7' => { VR => 'FL', Name => 'IsocenterToBlockTrayDistance' }, + '300A,00F8' => { VR => 'CS', Name => 'BlockType' }, + '300A,00F9' => { VR => 'LO', Name => 'AccessoryCode' }, + '300A,00FA' => { VR => 'CS', Name => 'BlockDivergence' }, + '300A,00FB' => { VR => 'CS', Name => 'BlockMountingPosition' }, + '300A,00FC' => { VR => 'IS', Name => 'BlockNumber' }, + '300A,00FE' => { VR => 'LO', Name => 'BlockName' }, + '300A,0100' => { VR => 'DS', Name => 'BlockThickness' }, + '300A,0102' => { VR => 'DS', Name => 'BlockTransmission' }, + '300A,0104' => { VR => 'IS', Name => 'BlockNumberOfPoints' }, + '300A,0106' => { VR => 'DS', Name => 'BlockData' }, + '300A,0107' => { VR => 'SQ', Name => 'ApplicatorSequence' }, + '300A,0108' => { VR => 'SH', Name => 'ApplicatorID' }, + '300A,0109' => { VR => 'CS', Name => 'ApplicatorType' }, + '300A,010A' => { VR => 'LO', Name => 'ApplicatorDescription' }, + '300A,010C' => { VR => 'DS', Name => 'CumulativeDoseReferenceCoefficient' }, + '300A,010E' => { VR => 'DS', Name => 'FinalCumulativeMetersetWeight' }, + '300A,0110' => { VR => 'IS', Name => 'NumberOfControlPoints' }, + '300A,0111' => { VR => 'SQ', Name => 'ControlPointSequence' }, + '300A,0112' => { VR => 'IS', Name => 'ControlPointIndex' }, + '300A,0114' => { VR => 'DS', Name => 'NominalBeamEnergy' }, + '300A,0115' => { VR => 'DS', Name => 'DoseRateSet' }, + '300A,0116' => { VR => 'SQ', Name => 'WedgePositionSequence' }, + '300A,0118' => { VR => 'CS', Name => 'WedgePosition' }, + '300A,011A' => { VR => 'SQ', Name => 'BeamLimitingDevicePositionSequence' }, + '300A,011C' => { VR => 'DS', Name => 'LeafJawPositions' }, + '300A,011E' => { VR => 'DS', Name => 'GantryAngle' }, + '300A,011F' => { VR => 'CS', Name => 'GantryRotationDirection' }, + '300A,0120' => { VR => 'DS', Name => 'BeamLimitingDeviceAngle' }, + '300A,0121' => { VR => 'CS', Name => 'BeamLimitingDeviceRotateDirection' }, + '300A,0122' => { VR => 'DS', Name => 'PatientSupportAngle' }, + '300A,0123' => { VR => 'CS', Name => 'PatientSupportRotationDirection' }, + '300A,0124' => { VR => 'DS', Name => 'TableTopEccentricAxisDistance' }, + '300A,0125' => { VR => 'DS', Name => 'TableTopEccentricAngle' }, + '300A,0126' => { VR => 'CS', Name => 'TableTopEccentricRotateDirection' }, + '300A,0128' => { VR => 'DS', Name => 'TableTopVerticalPosition' }, + '300A,0129' => { VR => 'DS', Name => 'TableTopLongitudinalPosition' }, + '300A,012A' => { VR => 'DS', Name => 'TableTopLateralPosition' }, + '300A,012C' => { VR => 'DS', Name => 'IsocenterPosition' }, + '300A,012E' => { VR => 'DS', Name => 'SurfaceEntryPoint' }, + '300A,0130' => { VR => 'DS', Name => 'SourceToSurfaceDistance' }, + '300A,0134' => { VR => 'DS', Name => 'CumulativeMetersetWeight' }, + '300A,0140' => { VR => 'FL', Name => 'TableTopPitchAngle' }, + '300A,0142' => { VR => 'CS', Name => 'TableTopPitchRotationDirection' }, + '300A,0144' => { VR => 'FL', Name => 'TableTopRollAngle' }, + '300A,0146' => { VR => 'CS', Name => 'TableTopRollRotationDirection' }, + '300A,0148' => { VR => 'FL', Name => 'HeadFixationAngle' }, + '300A,014A' => { VR => 'FL', Name => 'GantryPitchAngle' }, + '300A,014C' => { VR => 'CS', Name => 'GantryPitchRotationDirection' }, + '300A,014E' => { VR => 'FL', Name => 'GantryPitchAngleTolerance' }, + '300A,0180' => { VR => 'SQ', Name => 'PatientSetupSequence' }, + '300A,0182' => { VR => 'IS', Name => 'PatientSetupNumber' }, + '300A,0183' => { VR => 'LO', Name => 'PatientSetupLabel' }, + '300A,0184' => { VR => 'LO', Name => 'PatientAdditionalPosition' }, + '300A,0190' => { VR => 'SQ', Name => 'FixationDeviceSequence' }, + '300A,0192' => { VR => 'CS', Name => 'FixationDeviceType' }, + '300A,0194' => { VR => 'SH', Name => 'FixationDeviceLabel' }, + '300A,0196' => { VR => 'ST', Name => 'FixationDeviceDescription' }, + '300A,0198' => { VR => 'SH', Name => 'FixationDevicePosition' }, + '300A,0199' => { VR => 'FL', Name => 'FixationDevicePitchAngle' }, + '300A,019A' => { VR => 'FL', Name => 'FixationDeviceRollAngle' }, + '300A,01A0' => { VR => 'SQ', Name => 'ShieldingDeviceSequence' }, + '300A,01A2' => { VR => 'CS', Name => 'ShieldingDeviceType' }, + '300A,01A4' => { VR => 'SH', Name => 'ShieldingDeviceLabel' }, + '300A,01A6' => { VR => 'ST', Name => 'ShieldingDeviceDescription' }, + '300A,01A8' => { VR => 'SH', Name => 'ShieldingDevicePosition' }, + '300A,01B0' => { VR => 'CS', Name => 'SetupTechnique' }, + '300A,01B2' => { VR => 'ST', Name => 'SetupTechniqueDescription' }, + '300A,01B4' => { VR => 'SQ', Name => 'SetupDeviceSequence' }, + '300A,01B6' => { VR => 'CS', Name => 'SetupDeviceType' }, + '300A,01B8' => { VR => 'SH', Name => 'SetupDeviceLabel' }, + '300A,01BA' => { VR => 'ST', Name => 'SetupDeviceDescription' }, + '300A,01BC' => { VR => 'DS', Name => 'SetupDeviceParameter' }, + '300A,01D0' => { VR => 'ST', Name => 'SetupReferenceDescription' }, + '300A,01D2' => { VR => 'DS', Name => 'TableTopVerticalSetupDisplacement' }, + '300A,01D4' => { VR => 'DS', Name => 'TableTopLongitudinalSetupDisplace' }, + '300A,01D6' => { VR => 'DS', Name => 'TableTopLateralSetupDisplacement' }, + '300A,0200' => { VR => 'CS', Name => 'BrachyTreatmentTechnique' }, + '300A,0202' => { VR => 'CS', Name => 'BrachyTreatmentType' }, + '300A,0206' => { VR => 'SQ', Name => 'TreatmentMachineSequence' }, + '300A,0210' => { VR => 'SQ', Name => 'SourceSequence' }, + '300A,0212' => { VR => 'IS', Name => 'SourceNumber' }, + '300A,0214' => { VR => 'CS', Name => 'SourceType' }, + '300A,0216' => { VR => 'LO', Name => 'SourceManufacturer' }, + '300A,0218' => { VR => 'DS', Name => 'ActiveSourceDiameter' }, + '300A,021A' => { VR => 'DS', Name => 'ActiveSourceLength' }, + '300A,0222' => { VR => 'DS', Name => 'SourceEncapsulationNomThickness' }, + '300A,0224' => { VR => 'DS', Name => 'SourceEncapsulationNomTransmission' }, + '300A,0226' => { VR => 'LO', Name => 'SourceIsotopeName' }, + '300A,0228' => { VR => 'DS', Name => 'SourceIsotopeHalfLife' }, + '300A,0229' => { VR => 'CS', Name => 'SourceStrengthUnits' }, + '300A,022A' => { VR => 'DS', Name => 'ReferenceAirKermaRate' }, + '300A,022B' => { VR => 'DS', Name => 'SourceStrength' }, + '300A,022C' => { VR => 'DA', Name => 'SourceStrengthReferenceDate' }, + '300A,022E' => { VR => 'TM', Name => 'SourceStrengthReferenceTime' }, + '300A,0230' => { VR => 'SQ', Name => 'ApplicationSetupSequence' }, + '300A,0232' => { VR => 'CS', Name => 'ApplicationSetupType' }, + '300A,0234' => { VR => 'IS', Name => 'ApplicationSetupNumber' }, + '300A,0236' => { VR => 'LO', Name => 'ApplicationSetupName' }, + '300A,0238' => { VR => 'LO', Name => 'ApplicationSetupManufacturer' }, + '300A,0240' => { VR => 'IS', Name => 'TemplateNumber' }, + '300A,0242' => { VR => 'SH', Name => 'TemplateType' }, + '300A,0244' => { VR => 'LO', Name => 'TemplateName' }, + '300A,0250' => { VR => 'DS', Name => 'TotalReferenceAirKerma' }, + '300A,0260' => { VR => 'SQ', Name => 'BrachyAccessoryDeviceSequence' }, + '300A,0262' => { VR => 'IS', Name => 'BrachyAccessoryDeviceNumber' }, + '300A,0263' => { VR => 'SH', Name => 'BrachyAccessoryDeviceID' }, + '300A,0264' => { VR => 'CS', Name => 'BrachyAccessoryDeviceType' }, + '300A,0266' => { VR => 'LO', Name => 'BrachyAccessoryDeviceName' }, + '300A,026A' => { VR => 'DS', Name => 'BrachyAccessoryDeviceNomThickness' }, + '300A,026C' => { VR => 'DS', Name => 'BrachyAccessoryDevNomTransmission' }, + '300A,0280' => { VR => 'SQ', Name => 'ChannelSequence' }, + '300A,0282' => { VR => 'IS', Name => 'ChannelNumber' }, + '300A,0284' => { VR => 'DS', Name => 'ChannelLength' }, + '300A,0286' => { VR => 'DS', Name => 'ChannelTotalTime' }, + '300A,0288' => { VR => 'CS', Name => 'SourceMovementType' }, + '300A,028A' => { VR => 'IS', Name => 'NumberOfPulses' }, + '300A,028C' => { VR => 'DS', Name => 'PulseRepetitionInterval' }, + '300A,0290' => { VR => 'IS', Name => 'SourceApplicatorNumber' }, + '300A,0291' => { VR => 'SH', Name => 'SourceApplicatorID' }, + '300A,0292' => { VR => 'CS', Name => 'SourceApplicatorType' }, + '300A,0294' => { VR => 'LO', Name => 'SourceApplicatorName' }, + '300A,0296' => { VR => 'DS', Name => 'SourceApplicatorLength' }, + '300A,0298' => { VR => 'LO', Name => 'SourceApplicatorManufacturer' }, + '300A,029C' => { VR => 'DS', Name => 'SourceApplicatorWallNomThickness' }, + '300A,029E' => { VR => 'DS', Name => 'SourceApplicatorWallNomTrans' }, + '300A,02A0' => { VR => 'DS', Name => 'SourceApplicatorStepSize' }, + '300A,02A2' => { VR => 'IS', Name => 'TransferTubeNumber' }, + '300A,02A4' => { VR => 'DS', Name => 'TransferTubeLength' }, + '300A,02B0' => { VR => 'SQ', Name => 'ChannelShieldSequence' }, + '300A,02B2' => { VR => 'IS', Name => 'ChannelShieldNumber' }, + '300A,02B3' => { VR => 'SH', Name => 'ChannelShieldID' }, + '300A,02B4' => { VR => 'LO', Name => 'ChannelShieldName' }, + '300A,02B8' => { VR => 'DS', Name => 'ChannelShieldNominalThickness' }, + '300A,02BA' => { VR => 'DS', Name => 'ChannelShieldNominalTransmission' }, + '300A,02C8' => { VR => 'DS', Name => 'FinalCumulativeTimeWeight' }, + '300A,02D0' => { VR => 'SQ', Name => 'BrachyControlPointSequence' }, + '300A,02D2' => { VR => 'DS', Name => 'ControlPointRelativePosition' }, + '300A,02D4' => { VR => 'DS', Name => 'ControlPoint3DPosition' }, + '300A,02D6' => { VR => 'DS', Name => 'CumulativeTimeWeight' }, + '300A,02E0' => { VR => 'CS', Name => 'CompensatorDivergence' }, + '300A,02E1' => { VR => 'CS', Name => 'CompensatorMountingPosition' }, + '300A,02E2' => { VR => 'DS', Name => 'SourceToCompensatorDistance' }, + '300A,02E3' => { VR => 'FL', Name => 'TotalCompTrayWaterEquivThickness' }, + '300A,02E4' => { VR => 'FL', Name => 'IsocenterToCompensatorTrayDistance' }, + '300A,02E5' => { VR => 'FL', Name => 'CompensatorColumnOffset' }, + '300A,02E6' => { VR => 'FL', Name => 'IsocenterToCompensatorDistances' }, + '300A,02E7' => { VR => 'FL', Name => 'CompensatorRelStoppingPowerRatio' }, + '300A,02E8' => { VR => 'FL', Name => 'CompensatorMillingToolDiameter' }, + '300A,02EA' => { VR => 'SQ', Name => 'IonRangeCompensatorSequence' }, + '300A,02EB' => { VR => 'LT', Name => 'CompensatorDescription' }, + '300A,0302' => { VR => 'IS', Name => 'RadiationMassNumber' }, + '300A,0304' => { VR => 'IS', Name => 'RadiationAtomicNumber' }, + '300A,0306' => { VR => 'SS', Name => 'RadiationChargeState' }, + '300A,0308' => { VR => 'CS', Name => 'ScanMode' }, + '300A,030A' => { VR => 'FL', Name => 'VirtualSourceAxisDistances' }, + '300A,030C' => { VR => 'SQ', Name => 'SnoutSequence' }, + '300A,030D' => { VR => 'FL', Name => 'SnoutPosition' }, + '300A,030F' => { VR => 'SH', Name => 'SnoutID' }, + '300A,0312' => { VR => 'IS', Name => 'NumberOfRangeShifters' }, + '300A,0314' => { VR => 'SQ', Name => 'RangeShifterSequence' }, + '300A,0316' => { VR => 'IS', Name => 'RangeShifterNumber' }, + '300A,0318' => { VR => 'SH', Name => 'RangeShifterID' }, + '300A,0320' => { VR => 'CS', Name => 'RangeShifterType' }, + '300A,0322' => { VR => 'LO', Name => 'RangeShifterDescription' }, + '300A,0330' => { VR => 'IS', Name => 'NumberOfLateralSpreadingDevices' }, + '300A,0332' => { VR => 'SQ', Name => 'LateralSpreadingDeviceSequence' }, + '300A,0334' => { VR => 'IS', Name => 'LateralSpreadingDeviceNumber' }, + '300A,0336' => { VR => 'SH', Name => 'LateralSpreadingDeviceID' }, + '300A,0338' => { VR => 'CS', Name => 'LateralSpreadingDeviceType' }, + '300A,033A' => { VR => 'LO', Name => 'LateralSpreadingDeviceDescription' }, + '300A,033C' => { VR => 'FL', Name => 'LateralSpreadingDevWaterEquivThick' }, + '300A,0340' => { VR => 'IS', Name => 'NumberOfRangeModulators' }, + '300A,0342' => { VR => 'SQ', Name => 'RangeModulatorSequence' }, + '300A,0344' => { VR => 'IS', Name => 'RangeModulatorNumber' }, + '300A,0346' => { VR => 'SH', Name => 'RangeModulatorID' }, + '300A,0348' => { VR => 'CS', Name => 'RangeModulatorType' }, + '300A,034A' => { VR => 'LO', Name => 'RangeModulatorDescription' }, + '300A,034C' => { VR => 'SH', Name => 'BeamCurrentModulationID' }, + '300A,0350' => { VR => 'CS', Name => 'PatientSupportType' }, + '300A,0352' => { VR => 'SH', Name => 'PatientSupportID' }, + '300A,0354' => { VR => 'LO', Name => 'PatientSupportAccessoryCode' }, + '300A,0356' => { VR => 'FL', Name => 'FixationLightAzimuthalAngle' }, + '300A,0358' => { VR => 'FL', Name => 'FixationLightPolarAngle' }, + '300A,035A' => { VR => 'FL', Name => 'MetersetRate' }, + '300A,0360' => { VR => 'SQ', Name => 'RangeShifterSettingsSequence' }, + '300A,0362' => { VR => 'LO', Name => 'RangeShifterSetting' }, + '300A,0364' => { VR => 'FL', Name => 'IsocenterToRangeShifterDistance' }, + '300A,0366' => { VR => 'FL', Name => 'RangeShifterWaterEquivThickness' }, + '300A,0370' => { VR => 'SQ', Name => 'LateralSpreadingDeviceSettingsSeq' }, + '300A,0372' => { VR => 'LO', Name => 'LateralSpreadingDeviceSetting' }, + '300A,0374' => { VR => 'FL', Name => 'IsocenterToLateralSpreadingDevDist' }, + '300A,0380' => { VR => 'SQ', Name => 'RangeModulatorSettingsSequence' }, + '300A,0382' => { VR => 'FL', Name => 'RangeModulatorGatingStartValue' }, + '300A,0384' => { VR => 'FL', Name => 'RangeModulatorGatingStopValue' }, + '300A,038A' => { VR => 'FL', Name => 'IsocenterToRangeModulatorDistance' }, + '300A,0390' => { VR => 'SH', Name => 'ScanSpotTuneID' }, + '300A,0392' => { VR => 'IS', Name => 'NumberOfScanSpotPositions' }, + '300A,0394' => { VR => 'FL', Name => 'ScanSpotPositionMap' }, + '300A,0396' => { VR => 'FL', Name => 'ScanSpotMetersetWeights' }, + '300A,0398' => { VR => 'FL', Name => 'ScanningSpotSize' }, + '300A,039A' => { VR => 'IS', Name => 'NumberOfPaintings' }, + '300A,03A0' => { VR => 'SQ', Name => 'IonToleranceTableSequence' }, + '300A,03A2' => { VR => 'SQ', Name => 'IonBeamSequence' }, + '300A,03A4' => { VR => 'SQ', Name => 'IonBeamLimitingDeviceSequence' }, + '300A,03A6' => { VR => 'SQ', Name => 'IonBlockSequence' }, + '300A,03A8' => { VR => 'SQ', Name => 'IonControlPointSequence' }, + '300A,03AA' => { VR => 'SQ', Name => 'IonWedgeSequence' }, + '300A,03AC' => { VR => 'SQ', Name => 'IonWedgePositionSequence' }, + '300A,0401' => { VR => 'SQ', Name => 'ReferencedSetupImageSequence' }, + '300A,0402' => { VR => 'ST', Name => 'SetupImageComment' }, + '300A,0410' => { VR => 'SQ', Name => 'MotionSynchronizationSequence' }, + '300A,0412' => { VR => 'FL', Name => 'ControlPointOrientation' }, + '300A,0420' => { VR => 'SQ', Name => 'GeneralAccessorySequence' }, + '300A,0421' => { VR => 'SH', Name => 'GeneralAccessoryID' }, + '300A,0422' => { VR => 'ST', Name => 'GeneralAccessoryDescription' }, + '300A,0423' => { VR => 'CS', Name => 'GeneralAccessoryType' }, + '300A,0424' => { VR => 'IS', Name => 'GeneralAccessoryNumber' }, + '300C,0002' => { VR => 'SQ', Name => 'ReferencedRTPlanSequence' }, + '300C,0004' => { VR => 'SQ', Name => 'ReferencedBeamSequence' }, + '300C,0006' => { VR => 'IS', Name => 'ReferencedBeamNumber' }, + '300C,0007' => { VR => 'IS', Name => 'ReferencedReferenceImageNumber' }, + '300C,0008' => { VR => 'DS', Name => 'StartCumulativeMetersetWeight' }, + '300C,0009' => { VR => 'DS', Name => 'EndCumulativeMetersetWeight' }, + '300C,000A' => { VR => 'SQ', Name => 'ReferencedBrachyAppSetupSeq' }, + '300C,000C' => { VR => 'IS', Name => 'ReferencedBrachyAppSetupNumber' }, + '300C,000E' => { VR => 'IS', Name => 'ReferencedSourceNumber' }, + '300C,0020' => { VR => 'SQ', Name => 'ReferencedFractionGroupSequence' }, + '300C,0022' => { VR => 'IS', Name => 'ReferencedFractionGroupNumber' }, + '300C,0040' => { VR => 'SQ', Name => 'ReferencedVerificationImageSeq' }, + '300C,0042' => { VR => 'SQ', Name => 'ReferencedReferenceImageSequence' }, + '300C,0050' => { VR => 'SQ', Name => 'ReferencedDoseReferenceSequence' }, + '300C,0051' => { VR => 'IS', Name => 'ReferencedDoseReferenceNumber' }, + '300C,0055' => { VR => 'SQ', Name => 'BrachyReferencedDoseReferenceSeq' }, + '300C,0060' => { VR => 'SQ', Name => 'ReferencedStructureSetSequence' }, + '300C,006A' => { VR => 'IS', Name => 'ReferencedPatientSetupNumber' }, + '300C,0080' => { VR => 'SQ', Name => 'ReferencedDoseSequence' }, + '300C,00A0' => { VR => 'IS', Name => 'ReferencedToleranceTableNumber' }, + '300C,00B0' => { VR => 'SQ', Name => 'ReferencedBolusSequence' }, + '300C,00C0' => { VR => 'IS', Name => 'ReferencedWedgeNumber' }, + '300C,00D0' => { VR => 'IS', Name => 'ReferencedCompensatorNumber' }, + '300C,00E0' => { VR => 'IS', Name => 'ReferencedBlockNumber' }, + '300C,00F0' => { VR => 'IS', Name => 'ReferencedControlPointIndex' }, + '300C,00F2' => { VR => 'SQ', Name => 'ReferencedControlPointSequence' }, + '300C,00F4' => { VR => 'IS', Name => 'ReferencedStartControlPointIndex' }, + '300C,00F6' => { VR => 'IS', Name => 'ReferencedStopControlPointIndex' }, + '300C,0100' => { VR => 'IS', Name => 'ReferencedRangeShifterNumber' }, + '300C,0102' => { VR => 'IS', Name => 'ReferencedLateralSpreadingDevNum' }, + '300C,0104' => { VR => 'IS', Name => 'ReferencedRangeModulatorNumber' }, + '300E,0002' => { VR => 'CS', Name => 'ApprovalStatus' }, + '300E,0004' => { VR => 'DA', Name => 'ReviewDate' }, + '300E,0005' => { VR => 'TM', Name => 'ReviewTime' }, + '300E,0008' => { VR => 'PN', Name => 'ReviewerName' }, + # text group + '4000,0000' => { VR => 'UL', Name => 'TextGroupLength' }, + '4000,0010' => { VR => 'LT', Name => 'Arbitrary' }, + '4000,4000' => { VR => 'LT', Name => 'TextComments' }, + # results group + '4008,0040' => { VR => 'SH', Name => 'ResultsID' }, + '4008,0042' => { VR => 'LO', Name => 'ResultsIDIssuer' }, + '4008,0050' => { VR => 'SQ', Name => 'ReferencedInterpretationSequence' }, + '4008,0100' => { VR => 'DA', Name => 'InterpretationRecordedDate' }, + '4008,0101' => { VR => 'TM', Name => 'InterpretationRecordedTime' }, + '4008,0102' => { VR => 'PN', Name => 'InterpretationRecorder' }, + '4008,0103' => { VR => 'LO', Name => 'ReferenceToRecordedSound' }, + '4008,0108' => { VR => 'DA', Name => 'InterpretationTranscriptionDate' }, + '4008,0109' => { VR => 'TM', Name => 'InterpretationTranscriptionTime' }, + '4008,010A' => { VR => 'PN', Name => 'InterpretationTranscriber' }, + '4008,010B' => { VR => 'ST', Name => 'InterpretationText' }, + '4008,010C' => { VR => 'PN', Name => 'InterpretationAuthor' }, + '4008,0111' => { VR => 'SQ', Name => 'InterpretationApproverSequence' }, + '4008,0112' => { VR => 'DA', Name => 'InterpretationApprovalDate' }, + '4008,0113' => { VR => 'TM', Name => 'InterpretationApprovalTime' }, + '4008,0114' => { VR => 'PN', Name => 'PhysicianApprovingInterpretation' }, + '4008,0115' => { VR => 'LT', Name => 'InterpretationDiagnosisDescription' }, + '4008,0117' => { VR => 'SQ', Name => 'InterpretationDiagnosisCodeSeq' }, + '4008,0118' => { VR => 'SQ', Name => 'ResultsDistributionListSequence' }, + '4008,0119' => { VR => 'PN', Name => 'DistributionName' }, + '4008,011A' => { VR => 'LO', Name => 'DistributionAddress' }, + '4008,0200' => { VR => 'SH', Name => 'InterpretationID' }, + '4008,0202' => { VR => 'LO', Name => 'InterpretationIDIssuer' }, + '4008,0210' => { VR => 'CS', Name => 'InterpretationTypeID' }, + '4008,0212' => { VR => 'CS', Name => 'InterpretationStatusID' }, + '4008,0300' => { VR => 'ST', Name => 'Impressions' }, + '4008,4000' => { VR => 'ST', Name => 'ResultsComments' }, + '4FFE,0001' => { VR => 'SQ', Name => 'MACParametersSequence' }, + # curve group + '50xx,0005' => { VR => 'US', Name => 'CurveDimensions' }, + '50xx,0010' => { VR => 'US', Name => 'NumberOfPoints' }, + '50xx,0020' => { VR => 'CS', Name => 'TypeOfData' }, + '50xx,0022' => { VR => 'LO', Name => 'CurveDescription' }, + '50xx,0030' => { VR => 'SH', Name => 'AxisUnits' }, + '50xx,0040' => { VR => 'SH', Name => 'AxisLabels' }, + '50xx,0103' => { VR => 'US', Name => 'DataValueRepresentation' }, + '50xx,0104' => { VR => 'US', Name => 'MinimumCoordinateValue' }, + '50xx,0105' => { VR => 'US', Name => 'MaximumCoordinateValue' }, + '50xx,0106' => { VR => 'SH', Name => 'CurveRange' }, + '50xx,0110' => { VR => 'US', Name => 'CurveDataDescriptor' }, + '50xx,0112' => { VR => 'US', Name => 'CoordinateStartValue' }, + '50xx,0114' => { VR => 'US', Name => 'CoordinateStepValue' }, + '50xx,1001' => { VR => 'CS', Name => 'CurveActivationLayer' }, + '50xx,2000' => { VR => 'US', Name => 'AudioType' }, + '50xx,2002' => { VR => 'US', Name => 'AudioSampleFormat' }, + '50xx,2004' => { VR => 'US', Name => 'NumberOfChannels' }, + '50xx,2006' => { VR => 'UL', Name => 'NumberOfSamples' }, + '50xx,2008' => { VR => 'UL', Name => 'SampleRate' }, + '50xx,200A' => { VR => 'UL', Name => 'TotalTime' }, + '50xx,200C' => { VR => 'OW', Name => 'AudioSampleData' }, + '50xx,200E' => { VR => 'LT', Name => 'AudioComments' }, + '50xx,2500' => { VR => 'LO', Name => 'CurveLabel' }, + '50xx,2600' => { VR => 'SQ', Name => 'ReferencedOverlaySequence' }, + '50xx,2610' => { VR => 'US', Name => 'ReferencedOverlayGroup' }, + '50xx,3000' => { VR => 'OW', Name => 'CurveData' }, + '5200,9229' => { VR => 'SQ', Name => 'SharedFunctionalGroupsSequence' }, + '5200,9230' => { VR => 'SQ', Name => 'PerFrameFunctionalGroupsSequence' }, + '5400,0100' => { VR => 'SQ', Name => 'WaveformSequence' }, + '5400,0110' => { VR => 'OB', Name => 'ChannelMinimumValue' }, + '5400,0112' => { VR => 'OB', Name => 'ChannelMaximumValue' }, + '5400,1004' => { VR => 'US', Name => 'WaveformBitsAllocated' }, + '5400,1006' => { VR => 'CS', Name => 'WaveformSampleInterpretation' }, + '5400,100A' => { VR => 'OB', Name => 'WaveformPaddingValue' }, + '5400,1010' => { VR => 'OB', Name => 'WaveformData' }, + '5600,0010' => { VR => 'OF', Name => 'FirstOrderPhaseCorrectionAngle' }, + '5600,0020' => { VR => 'OF', Name => 'SpectroscopyData' }, + # overlay group + '6000,0000' => { VR => 'UL', Name => 'OverlayGroupLength' }, + '60xx,0010' => { VR => 'US', Name => 'OverlayRows' }, + '60xx,0011' => { VR => 'US', Name => 'OverlayColumns' }, + '60xx,0012' => { VR => 'US', Name => 'OverlayPlanes' }, + '60xx,0015' => { VR => 'IS', Name => 'NumberOfFramesInOverlay' }, + '60xx,0022' => { VR => 'LO', Name => 'OverlayDescription' }, + '60xx,0040' => { VR => 'CS', Name => 'OverlayType' }, + '60xx,0045' => { VR => 'LO', Name => 'OverlaySubtype' }, + '60xx,0050' => { VR => 'SS', Name => 'OverlayOrigin' }, + '60xx,0051' => { VR => 'US', Name => 'ImageFrameOrigin' }, + '60xx,0052' => { VR => 'US', Name => 'OverlayPlaneOrigin' }, + '60xx,0060' => { VR => 'CS', Name => 'OverlayCompressionCode' }, + '60xx,0061' => { VR => 'SH', Name => 'OverlayCompressionOriginator' }, + '60xx,0062' => { VR => 'SH', Name => 'OverlayCompressionLabel' }, + '60xx,0063' => { VR => 'CS', Name => 'OverlayCompressionDescription' }, + '60xx,0066' => { VR => 'AT', Name => 'OverlayCompressionStepPointers' }, + '60xx,0068' => { VR => 'US', Name => 'OverlayRepeatInterval' }, + '60xx,0069' => { VR => 'US', Name => 'OverlayBitsGrouped' }, + '60xx,0100' => { VR => 'US', Name => 'OverlayBitsAllocated' }, + '60xx,0102' => { VR => 'US', Name => 'OverlayBitPosition' }, + '60xx,0110' => { VR => 'CS', Name => 'OverlayFormat' }, + '60xx,0200' => { VR => 'US', Name => 'OverlayLocation' }, + '60xx,0800' => { VR => 'CS', Name => 'OverlayCodeLabel' }, + '60xx,0802' => { VR => 'US', Name => 'OverlayNumberOfTables' }, + '60xx,0803' => { VR => 'AT', Name => 'OverlayCodeTableLocation' }, + '60xx,0804' => { VR => 'US', Name => 'OverlayBitsForCodeWord' }, + '60xx,1001' => { VR => 'CS', Name => 'OverlayActivationLayer' }, + '60xx,1100' => { VR => 'US', Name => 'OverlayDescriptorGray' }, + '60xx,1101' => { VR => 'US', Name => 'OverlayDescriptorRed' }, + '60xx,1102' => { VR => 'US', Name => 'OverlayDescriptorGreen' }, + '60xx,1103' => { VR => 'US', Name => 'OverlayDescriptorBlue' }, + '60xx,1200' => { VR => 'US', Name => 'OverlaysGray' }, + '60xx,1201' => { VR => 'US', Name => 'OverlaysRed' }, + '60xx,1202' => { VR => 'US', Name => 'OverlaysGreen' }, + '60xx,1203' => { VR => 'US', Name => 'OverlaysBlue' }, + '60xx,1301' => { VR => 'IS', Name => 'ROIArea' }, + '60xx,1302' => { VR => 'DS', Name => 'ROIMean' }, + '60xx,1303' => { VR => 'DS', Name => 'ROIStandardDeviation' }, + '60xx,1500' => { VR => 'LO', Name => 'OverlayLabel' }, + '60xx,3000' => { VR => 'OB', Name => 'OverlayData' }, + '60xx,4000' => { VR => 'LT',Name => 'OverlayComments' }, + # pixel data group + '7Fxx,0000' => { VR => 'UL', Name => 'PixelDataGroupLength' }, + '7Fxx,0010' => { VR => 'OB', Name => 'PixelData', Binary => 1 }, + '7Fxx,0011' => { VR => 'US', Name => 'VariableNextDataGroup' }, + '7Fxx,0020' => { VR => 'OW', Name => 'VariableCoefficientsSDVN' }, + '7Fxx,0030' => { VR => 'OW', Name => 'VariableCoefficientsSDHN' }, + '7Fxx,0040' => { VR => 'OW', Name => 'VariableCoefficientsSDDN' }, + 'FFFA,FFFA' => { VR => 'SQ', Name => 'DigitalSignaturesSequence' }, + 'FFFC,FFFC' => { VR => 'OB', Name => 'DataSetTrailingPadding', Binary => 1 }, + # the sequence delimiters have no VR: + 'FFFE,E000' => 'StartOfItem', + 'FFFE,E00D' => 'EndOfItems', + 'FFFE,E0DD' => 'EndOfSequence', +); + +# table to translate registered UID values to readable strings +# (the PrintConv is added dynamically when a 'UI' format tag is extracted) +%uid = ( + '1.2.840.10008.1.1' => 'Verification SOP Class', + '1.2.840.10008.1.2' => 'Implicit VR Little Endian', + '1.2.840.10008.1.2.1' => 'Explicit VR Little Endian', + '1.2.840.10008.1.2.1.99' => 'Deflated Explicit VR Little Endian', + '1.2.840.10008.1.2.2' => 'Explicit VR Big Endian', + '1.2.840.10008.1.2.4.50' => 'JPEG Baseline (Process 1)', + '1.2.840.10008.1.2.4.51' => 'JPEG Extended (Process 2 & 4)', + '1.2.840.10008.1.2.4.52' => 'JPEG Extended (Process 3 & 5)', + '1.2.840.10008.1.2.4.53' => 'JPEG Spectral Selection, Non-Hierarchical (Process 6 & 8)', + '1.2.840.10008.1.2.4.54' => 'JPEG Spectral Selection, Non-Hierarchical (Process 7 & 9)', + '1.2.840.10008.1.2.4.55' => 'JPEG Full Progression, Non-Hierarchical (Process 10 & 12)', + '1.2.840.10008.1.2.4.56' => 'JPEG Full Progression, Non-Hierarchical (Process 11 & 13)', + '1.2.840.10008.1.2.4.57' => 'JPEG Lossless, Non-Hierarchical (Process 14)', + '1.2.840.10008.1.2.4.58' => 'JPEG Lossless, Non-Hierarchical (Process 15) ', + '1.2.840.10008.1.2.4.59' => 'JPEG Extended, Hierarchical (Process 16 & 18) ', + '1.2.840.10008.1.2.4.60' => 'JPEG Extended, Hierarchical (Process 17 & 19) ', + '1.2.840.10008.1.2.4.61' => 'JPEG Spectral Selection, Hierarchical (Process 20 & 22)', + '1.2.840.10008.1.2.4.62' => 'JPEG Spectral Selection, Hierarchical (Process 21 & 23)', + '1.2.840.10008.1.2.4.63' => 'JPEG Full Progression, Hierarchical (Process 24 & 26)', + '1.2.840.10008.1.2.4.64' => 'JPEG Full Progression, Hierarchical (Process 25 & 27)', + '1.2.840.10008.1.2.4.65' => 'JPEG Lossless, Hierarchical (Process 28) ', + '1.2.840.10008.1.2.4.66' => 'JPEG Lossless, Hierarchical (Process 29) ', + '1.2.840.10008.1.2.4.70' => 'JPEG Lossless, Non-Hierarchical, First-Order Prediction (Process 14-1)', + '1.2.840.10008.1.2.4.80' => 'JPEG-LS Lossless Image Compression', + '1.2.840.10008.1.2.4.81' => 'JPEG-LS Lossy (Near-Lossless) Image Compression', + '1.2.840.10008.1.2.4.90' => 'JPEG 2000 Image Compression (Lossless Only)', + '1.2.840.10008.1.2.4.91' => 'JPEG 2000 Image Compression', + '1.2.840.10008.1.2.4.92' => 'JPEG 2000 Part 2 Multi-component Image Compression (Lossless Only)', + '1.2.840.10008.1.2.4.93' => 'JPEG 2000 Part 2 Multi-component Image Compression', + '1.2.840.10008.1.2.4.94' => 'JPIP Referenced', + '1.2.840.10008.1.2.4.95' => 'JPIP Referenced Deflate', + '1.2.840.10008.1.2.4.100' => 'MPEG2 Main Profile @ Main Level', + '1.2.840.10008.1.2.5' => 'RLE Lossless', + '1.2.840.10008.1.2.6.1' => 'RFC 2557 MIME encapsulation', + '1.2.840.10008.1.2.6.2' => 'XML Encoding', + '1.2.840.10008.1.3.10' => 'Media Storage Directory Storage', + '1.2.840.10008.1.4.1.1' => 'Talairach Brain Atlas Frame of Reference', + '1.2.840.10008.1.4.1.2' => 'SPM2 T1 Frame of Reference', + '1.2.840.10008.1.4.1.3' => 'SPM2 T2 Frame of Reference', + '1.2.840.10008.1.4.1.4' => 'SPM2 PD Frame of Reference', + '1.2.840.10008.1.4.1.5' => 'SPM2 EPI Frame of Reference', + '1.2.840.10008.1.4.1.6' => 'SPM2 FIL T1 Frame of Reference', + '1.2.840.10008.1.4.1.7' => 'SPM2 PET Frame of Reference', + '1.2.840.10008.1.4.1.8' => 'SPM2 TRANSM Frame of Reference', + '1.2.840.10008.1.4.1.9' => 'SPM2 SPECT Frame of Reference', + '1.2.840.10008.1.4.1.10' => 'SPM2 GRAY Frame of Reference', + '1.2.840.10008.1.4.1.11' => 'SPM2 WHITE Frame of Reference', + '1.2.840.10008.1.4.1.12' => 'SPM2 CSF Frame of Reference', + '1.2.840.10008.1.4.1.13' => 'SPM2 BRAINMASK Frame of Reference', + '1.2.840.10008.1.4.1.14' => 'SPM2 AVG305T1 Frame of Reference', + '1.2.840.10008.1.4.1.15' => 'SPM2 AVG152T1 Frame of Reference', + '1.2.840.10008.1.4.1.16' => 'SPM2 AVG152T2 Frame of Reference', + '1.2.840.10008.1.4.1.17' => 'SPM2 AVG152PD Frame of Reference', + '1.2.840.10008.1.4.1.18' => 'SPM2 SINGLESUBJT1 Frame of Reference', + '1.2.840.10008.1.4.2.1' => 'ICBM 452 T1 Frame of Reference', + '1.2.840.10008.1.4.2.2' => 'ICBM Single Subject MRI Frame of Reference', + '1.2.840.10008.1.9' => 'Basic Study Content Notification SOP Class', + '1.2.840.10008.1.20.1' => 'Storage Commitment Push Model SOP Class', + '1.2.840.10008.1.20.1.1' => 'Storage Commitment Push Model SOP Instance', + '1.2.840.10008.1.20.2' => 'Storage Commitment Pull Model SOP Class ', + '1.2.840.10008.1.20.2.1' => 'Storage Commitment Pull Model SOP Instance ', + '1.2.840.10008.1.40' => 'Procedural Event Logging SOP Class', + '1.2.840.10008.1.40.1' => 'Procedural Event Logging SOP Instance', + '1.2.840.10008.1.42' => 'Substance Administration Logging SOP Class', + '1.2.840.10008.1.42.1' => 'Substance Administration Logging SOP Instance', + '1.2.840.10008.2.6.1' => 'DICOM UID Registry', + '1.2.840.10008.2.16.4' => 'DICOM Controlled Terminology', + '1.2.840.10008.3.1.1.1' => 'DICOM Application Context Name', + '1.2.840.10008.3.1.2.1.1' => 'Detached Patient Management SOP Class', + '1.2.840.10008.3.1.2.1.4' => 'Detached Patient Management Meta SOP Class', + '1.2.840.10008.3.1.2.2.1' => 'Detached Visit Management SOP Class', + '1.2.840.10008.3.1.2.3.1' => 'Detached Study Management SOP Class', + '1.2.840.10008.3.1.2.3.2' => 'Study Component Management SOP Class', + '1.2.840.10008.3.1.2.3.3' => 'Modality Performed Procedure Step SOP Class', + '1.2.840.10008.3.1.2.3.4' => 'Modality Performed Procedure Step Retrieve SOP Class', + '1.2.840.10008.3.1.2.3.5' => 'Modality Performed Procedure Step Notification SOP Class', + '1.2.840.10008.3.1.2.5.1' => 'Detached Results Management SOP Class', + '1.2.840.10008.3.1.2.5.4' => 'Detached Results Management Meta SOP Class', + '1.2.840.10008.3.1.2.5.5' => 'Detached Study Management Meta SOP Class', + '1.2.840.10008.3.1.2.6.1' => 'Detached Interpretation Management SOP Class', + '1.2.840.10008.4.2' => 'Storage Service Class Service Class PS 3.4', + '1.2.840.10008.5.1.1.1' => 'Basic Film Session SOP Class', + '1.2.840.10008.5.1.1.2' => 'Basic Film Box SOP Class', + '1.2.840.10008.5.1.1.4' => 'Basic Grayscale Image Box SOP Class', + '1.2.840.10008.5.1.1.4.1' => 'Basic Color Image Box SOP Class', + '1.2.840.10008.5.1.1.4.2' => 'Referenced Image Box SOP Class', + '1.2.840.10008.5.1.1.9' => 'Basic Grayscale Print ManagementMeta SOP Class', + '1.2.840.10008.5.1.1.9.1' => 'Referenced Grayscale Print Management Meta SOP Class', + '1.2.840.10008.5.1.1.14' => 'Print Job SOP Class', + '1.2.840.10008.5.1.1.15' => 'Basic Annotation Box SOP Class', + '1.2.840.10008.5.1.1.16' => 'Printer SOP Class', + '1.2.840.10008.5.1.1.16.376' => 'Printer Configuration Retrieval SOP Class', + '1.2.840.10008.5.1.1.17' => 'Printer SOP Instance', + '1.2.840.10008.5.1.1.17.376' => 'Printer Configuration RetrievalSOP Instance', + '1.2.840.10008.5.1.1.18' => 'Basic Color Print Management Meta SOP Class', + '1.2.840.10008.5.1.1.18.1' => 'Referenced Color Print Management Meta SOP Class', + '1.2.840.10008.5.1.1.22' => 'VOI LUT Box SOP Class', + '1.2.840.10008.5.1.1.23' => 'Presentation LUT SOP Class', + '1.2.840.10008.5.1.1.24' => 'Image Overlay Box SOP Class', + '1.2.840.10008.5.1.1.24.1' => 'Basic Print Image Overlay Box SOP Class', + '1.2.840.10008.5.1.1.25' => 'Print Queue SOP Instance', + '1.2.840.10008.5.1.1.26' => 'Print Queue Management SOP Class', + '1.2.840.10008.5.1.1.27' => 'Stored Print Storage SOP Class', + '1.2.840.10008.5.1.1.29' => 'Hardcopy Grayscale Image', + '1.2.840.10008.5.1.1.30' => 'Hardcopy Color Image Storage SOP Class', + '1.2.840.10008.5.1.1.31' => 'Pull Print Request SOP Class', + '1.2.840.10008.5.1.1.32' => 'Pull Stored Print Management Meta SOP Class', + '1.2.840.10008.5.1.1.33' => 'Media Creation Management SOP Class', + '1.2.840.10008.5.1.4.1.1.1' => 'Computed Radiography Image Storage', + '1.2.840.10008.5.1.4.1.1.1.1' => 'Digital X-Ray Image Storage - For Presentation', + '1.2.840.10008.5.1.4.1.1.1.1.1' => 'Digital X-Ray Image Storage - For Processing', + '1.2.840.10008.5.1.4.1.1.1.2' => 'Digital Mammography X-Ray Image Storage - For Presentation', + '1.2.840.10008.5.1.4.1.1.1.2.1' => 'Digital Mammography X-Ray Image Storage - For Processing', + '1.2.840.10008.5.1.4.1.1.1.3' => 'Digital Intra-oral X-Ray Image Storage - For Presentation', + '1.2.840.10008.5.1.4.1.1.1.3.1' => 'Digital Intra-oral X-Ray Image Storage - For Processing', + '1.2.840.10008.5.1.4.1.1.2' => 'CT Image Storage', + '1.2.840.10008.5.1.4.1.1.2.1' => 'Enhanced CT Image Storage', + '1.2.840.10008.5.1.4.1.1.3' => 'Ultrasound Multi-frame Image Storage ', + '1.2.840.10008.5.1.4.1.1.3.1' => 'Ultrasound Multi-frame Image Storage', + '1.2.840.10008.5.1.4.1.1.4' => 'MR Image Storage', + '1.2.840.10008.5.1.4.1.1.4.1' => 'Enhanced MR Image Storage', + '1.2.840.10008.5.1.4.1.1.4.2' => 'MR Spectroscopy Storage', + '1.2.840.10008.5.1.4.1.1.5' => 'Nuclear Medicine Image Storage', + '1.2.840.10008.5.1.4.1.1.6' => 'Ultrasound Image Storage', + '1.2.840.10008.5.1.4.1.1.6.1' => 'Ultrasound Image Storage', + '1.2.840.10008.5.1.4.1.1.7' => 'Secondary Capture Image Storage', + '1.2.840.10008.5.1.4.1.1.7.1' => 'Multi-frame Single Bit Secondary', + '1.2.840.10008.5.1.4.1.1.7.2' => 'Multi-frame Grayscale Byte Secondary Capture Image Storage', + '1.2.840.10008.5.1.4.1.1.7.3' => 'Multi-frame Grayscale Word Secondary Capture Image Storage', + '1.2.840.10008.5.1.4.1.1.7.4' => 'Multi-frame True Color Secondary Capture Image Storage', + '1.2.840.10008.5.1.4.1.1.8' => 'Standalone Overlay Storage', + '1.2.840.10008.5.1.4.1.1.9' => 'Standalone Curve Storage', + '1.2.840.10008.5.1.4.1.1.9.1' => 'Waveform Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.9.1.1' => '12-lead ECG Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.1.2' => 'General ECG Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.1.3' => 'Ambulatory ECG Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.2.1' => 'Hemodynamic Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.3.1' => 'Cardiac Electrophysiology Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.4.1' => 'Basic Voice Audio Waveform Storage', + '1.2.840.10008.5.1.4.1.1.10' => 'Standalone Modality LUT Storage', + '1.2.840.10008.5.1.4.1.1.11' => 'Standalone VOI LUT Storage', + '1.2.840.10008.5.1.4.1.1.11.1' => 'Grayscale Softcopy Presentation State Storage SOP Class', + '1.2.840.10008.5.1.4.1.1.11.2' => 'Color Softcopy Presentation State Storage SOP Class', + '1.2.840.10008.5.1.4.1.1.11.3' => 'Pseudo-Color Softcopy Presentation State Storage SOP Class', + '1.2.840.10008.5.1.4.1.1.11.4' => 'Blending Softcopy Presentation State Storage SOP Class', + '1.2.840.10008.5.1.4.1.1.12.1' => 'X-Ray Angiographic Image Storage', + '1.2.840.10008.5.1.4.1.1.12.1.1' => 'Enhanced XA Image Storage', + '1.2.840.10008.5.1.4.1.1.12.2' => 'X-Ray Radiofluoroscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.12.2.1' => 'Enhanced XRF Image Storage', + '1.2.840.10008.5.1.4.1.1.12.3' => 'X-Ray Angiographic Bi-Plane Image Storage ', + '1.2.840.10008.5.1.4.1.1.13.1.1' => 'X-Ray 3D Angiographic Image Storage', + '1.2.840.10008.5.1.4.1.1.13.1.2' => 'X-Ray 3D Craniofacial Image Storage', + '1.2.840.10008.5.1.4.1.1.13.1.3' => 'Breast Tomosynthesis Image Storage', + '1.2.840.10008.5.1.4.1.1.14.1' => 'Intravascular Optical Coherence Tomography Image Storage - For Presentation', + '1.2.840.10008.5.1.4.1.1.14.2' => 'Intravascular Optical Coherence Tomography Image Storage - For Processing', + '1.2.840.10008.5.1.4.1.1.20' => 'Nuclear Medicine Image Storage', + '1.2.840.10008.5.1.4.1.1.66' => 'Raw Data Storage', + '1.2.840.10008.5.1.4.1.1.66.1' => 'Spatial Registration Storage', + '1.2.840.10008.5.1.4.1.1.66.2' => 'Spatial Fiducials Storage', + '1.2.840.10008.5.1.4.1.1.66.3' => 'Deformable Spatial Registration Storage', + '1.2.840.10008.5.1.4.1.1.66.4' => 'Segmentation Storage', + '1.2.840.10008.5.1.4.1.1.67' => 'Real World Value Mapping Storage', + '1.2.840.10008.5.1.4.1.1.77.1' => 'VL Image Storage ', + '1.2.840.10008.5.1.4.1.1.77.2' => 'VL Multi-frame Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.1' => 'VL Endoscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.1.1' => 'Video Endoscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.2' => 'VL Microscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.2.1' => 'Video Microscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.3' => 'VL Slide-Coordinates Microscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.4' => 'VL Photographic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.4.1' => 'Video Photographic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.5.1' => 'Ophthalmic Photography 8 Bit Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.5.2' => 'Ophthalmic Photography 16 Bit Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.5.3' => 'Stereometric Relationship Storage', + '1.2.840.10008.5.1.4.1.1.77.1.5.4' => 'Ophthalmic Tomography Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.6' => 'VL Whole Slide Microscopy Image Storage', + '1.2.840.10008.5.1.4.1.1.78.1' => 'Lensometry Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.2' => 'Autorefraction Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.3' => 'Keratometry Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.4' => 'Subjective Refraction Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.5' => 'Visual Acuity Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.6' => 'Spectacle Prescription Report Storage', + '1.2.840.10008.5.1.4.1.1.78.7' => 'Ophthalmic Axial Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.8' => 'Intraocular Lens Calculations Storage', + '1.2.840.10008.5.1.4.1.1.79.1' => 'Macular Grid Thickness and Volume Report Storage SOP Class', + '1.2.840.10008.5.1.4.1.1.80.1' => 'Ophthalmic Visual Field Static Perimetry Measurements Storage', + '1.2.840.10008.5.1.4.1.1.88.1' => 'Text SR Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.88.2' => 'Audio SR Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.88.3' => 'Detail SR Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.88.4' => 'Comprehensive SR Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.88.11' => 'Basic Text SR', + '1.2.840.10008.5.1.4.1.1.88.22' => 'Enhanced SR', + '1.2.840.10008.5.1.4.1.1.88.33' => 'Comprehensive SR', + '1.2.840.10008.5.1.4.1.1.88.40' => 'Procedure Log Storage', + '1.2.840.10008.5.1.4.1.1.88.50' => 'Mammography CAD SR', + '1.2.840.10008.5.1.4.1.1.88.59' => 'Key Object Selection Document', + '1.2.840.10008.5.1.4.1.1.88.65' => 'Chest CAD SR', + '1.2.840.10008.5.1.4.1.1.88.67' => 'X-Ray Radiation Dose SR Storage', + '1.2.840.10008.5.1.4.1.1.88.69' => 'Colon CAD SR', + '1.2.840.10008.5.1.4.1.1.88.70' => 'Implantation Plan SR Document Storage', + '1.2.840.10008.5.1.4.1.1.104.1' => 'Encapsulated PDF Storage', + '1.2.840.10008.5.1.4.1.1.104.2' => 'Encapsulated CDA Storage', + '1.2.840.10008.5.1.4.1.1.128' => 'Positron Emission Tomography Image Storage', + '1.2.840.10008.5.1.4.1.1.129' => 'Standalone PET Curve Storage', + '1.2.840.10008.5.1.4.1.1.481.1' => 'RT Image Storage', + '1.2.840.10008.5.1.4.1.1.481.2' => 'RT Dose Storage', + '1.2.840.10008.5.1.4.1.1.481.3' => 'RT Structure Set Storage', + '1.2.840.10008.5.1.4.1.1.481.4' => 'RT Beams Treatment Record Storage', + '1.2.840.10008.5.1.4.1.1.481.5' => 'RT Plan Storage', + '1.2.840.10008.5.1.4.1.1.481.6' => 'RT Brachy Treatment Record Storage', + '1.2.840.10008.5.1.4.1.1.481.7' => 'RT Treatment Summary Record Storage', + '1.2.840.10008.5.1.4.1.1.481.8' => 'RT Ion Plan Storage', + '1.2.840.10008.5.1.4.1.1.481.9' => 'RT Ion Beams Treatment Record Storage', + '1.2.840.10008.5.1.4.1.2.1.1' => 'Patient Root Query/Retrieve Information Model - FIND', + '1.2.840.10008.5.1.4.1.2.1.2' => 'Patient Root Query/Retrieve Information Model - MOVE', + '1.2.840.10008.5.1.4.1.2.1.3' => 'Patient Root Query/Retrieve Information Model - GET', + '1.2.840.10008.5.1.4.1.2.2.1' => 'Study Root Query/Retrieve Information Model - FIND', + '1.2.840.10008.5.1.4.1.2.2.2' => 'Study Root Query/Retrieve Information Model - MOVE', + '1.2.840.10008.5.1.4.1.2.2.3' => 'Study Root Query/Retrieve Information Model - GET', + '1.2.840.10008.5.1.4.1.2.3.1' => 'Patient/Study Only Query/Retrieve Information Model - FIND', + '1.2.840.10008.5.1.4.1.2.3.2' => 'Patient/Study Only Query/Retrieve Information Model - MOVE', + '1.2.840.10008.5.1.4.1.2.3.3' => 'Patient/Study Only Query/Retrieve Information Model - GET', + '1.2.840.10008.5.1.4.31' => 'Modality Worklist Information Model - FIND', + '1.2.840.10008.5.1.4.32.1' => 'General Purpose Worklist Information Model - FIND', + '1.2.840.10008.5.1.4.32.2' => 'General Purpose Scheduled Procedure Step SOP Class', + '1.2.840.10008.5.1.4.32.3' => 'General Purpose Performed Procedure Step SOP Class', + '1.2.840.10008.5.1.4.32' => 'General Purpose Worklist Management Meta SOP Class', + '1.2.840.10008.5.1.4.33' => 'Instance Availability Notification SOP Class', + '1.2.840.10008.5.1.4.34.1' => 'RT Beams Delivery Instruction Storage', + '1.2.840.10008.5.1.4.34.2' => 'RT Conventional Machine Verification', + '1.2.840.10008.5.1.4.34.3' => 'RT Ion Machine Verification', + '1.2.840.10008.5.1.4.34.4' => 'Unified Worklist and Procedure Step Service Class', + '1.2.840.10008.5.1.4.34.4.1' => 'Unified Procedure Step - Push SOP Class', + '1.2.840.10008.5.1.4.34.4.2' => 'Unified Procedure Step - Watch SOP Class', + '1.2.840.10008.5.1.4.34.4.3' => 'Unified Procedure Step - Pull SOP Class', + '1.2.840.10008.5.1.4.34.4.4' => 'Unified Procedure Step - Event SOP Class', + '1.2.840.10008.5.1.4.34.5' => 'Unified Worklist and Procedure Step SOP Instance', + '1.2.840.10008.5.1.4.34.6.1' => 'Unified Procedure Step - Push SOP Class', + '1.2.840.10008.5.1.4.34.6.2' => 'Unified Procedure Step - Watch SOP Class', + '1.2.840.10008.5.1.4.34.6.3' => 'Unified Procedure Step - Pull SOP Class', + '1.2.840.10008.5.1.4.34.6.4' => 'Unified Procedure Step - Event SOP Class', + '1.2.840.10008.5.1.4.34.7' => 'RT Beams Delivery Instruction Storage', + '1.2.840.10008.5.1.4.34.8' => 'RT Conventional Machine Verification', + '1.2.840.10008.5.1.4.34.9' => 'RT Ion Machine Verification', + '1.2.840.10008.5.1.4.37.1' => 'General Relevant Patient Information Query', + '1.2.840.10008.5.1.4.37.2' => 'Breast Imaging Relevant Patient Information Query', + '1.2.840.10008.5.1.4.37.3' => 'Cardiac Relevant Patient Information Query', + '1.2.840.10008.5.1.4.38.1' => 'Hanging Protocol Storage', + '1.2.840.10008.5.1.4.38.2' => 'Hanging Protocol Information Model - FIND', + '1.2.840.10008.5.1.4.38.3' => 'Hanging Protocol Information Model - MOVE', + '1.2.840.10008.5.1.4.39.1' => 'Color Palette Storage', + '1.2.840.10008.5.1.4.39.2' => 'Color Palette Information Model - FIND', + '1.2.840.10008.5.1.4.39.3' => 'Color Palette Information Model - MOVE', + '1.2.840.10008.5.1.4.39.4' => 'Color Palette Information Model - GET', + '1.2.840.10008.5.1.4.41' => 'Product Characteristics Query SOP Class', + '1.2.840.10008.5.1.4.42' => 'Substance Approval Query SOP Class', + '1.2.840.10008.5.1.4.43.1' => 'Generic Implant Template Storage', + '1.2.840.10008.5.1.4.43.2' => 'Generic Implant Template Information Model - FIND', + '1.2.840.10008.5.1.4.43.3' => 'Generic Implant Template Information Model - MOVE', + '1.2.840.10008.5.1.4.43.4' => 'Generic Implant Template Information Model - GET', + '1.2.840.10008.5.1.4.44.1' => 'Implant Assembly Template Storage', + '1.2.840.10008.5.1.4.44.2' => 'Implant Assembly Template Information Model - FIND', + '1.2.840.10008.5.1.4.44.3' => 'Implant Assembly Template Information Model - MOVE', + '1.2.840.10008.5.1.4.44.4' => 'Implant Assembly Template Information Model - GET', + '1.2.840.10008.5.1.4.45.1' => 'Implant Template Group Storage', + '1.2.840.10008.5.1.4.45.2' => 'Implant Template Group Information Model - FIND', + '1.2.840.10008.5.1.4.45.3' => 'Implant Template Group Information Model - MOVE', + '1.2.840.10008.5.1.4.45.4' => 'Implant Template Group Information Model - GET', + '1.2.840.10008.15.0.3.1' => 'dicomDeviceName', + '1.2.840.10008.15.0.3.2' => 'dicomDescription', + '1.2.840.10008.15.0.3.3' => 'dicomManufacturer', + '1.2.840.10008.15.0.3.4' => 'dicomManufacturerModelName', + '1.2.840.10008.15.0.3.5' => 'dicomSoftwareVersion', + '1.2.840.10008.15.0.3.6' => 'dicomVendorData', + '1.2.840.10008.15.0.3.7' => 'dicomAETitle', + '1.2.840.10008.15.0.3.8' => 'dicomNetworkConnectionReference', + '1.2.840.10008.15.0.3.9' => 'dicomApplicationCluster', + '1.2.840.10008.15.0.3.10' => 'dicomAssociationInitiator', + '1.2.840.10008.15.0.3.11' => 'dicomAssociationAcceptor', + '1.2.840.10008.15.0.3.12' => 'dicomHostname', + '1.2.840.10008.15.0.3.13' => 'dicomPort', + '1.2.840.10008.15.0.3.14' => 'dicomSOPClass', + '1.2.840.10008.15.0.3.15' => 'dicomTransferRole', + '1.2.840.10008.15.0.3.16' => 'dicomTransferSyntax', + '1.2.840.10008.15.0.3.17' => 'dicomPrimaryDeviceType', + '1.2.840.10008.15.0.3.18' => 'dicomRelatedDeviceReference', + '1.2.840.10008.15.0.3.19' => 'dicomPreferredCalledAETitle', + '1.2.840.10008.15.0.3.20' => 'dicomTLSCyphersuite', + '1.2.840.10008.15.0.3.21' => 'dicomAuthorizedNodeCertificateReference', + '1.2.840.10008.15.0.3.22' => 'dicomThisNodeCertificateReference', + '1.2.840.10008.15.0.3.23' => 'dicomInstalled', + '1.2.840.10008.15.0.3.24' => 'dicomStationName', + '1.2.840.10008.15.0.3.25' => 'dicomDeviceSerialNumber', + '1.2.840.10008.15.0.3.26' => 'dicomInstitutionName', + '1.2.840.10008.15.0.3.27' => 'dicomInstitutionAddress', + '1.2.840.10008.15.0.3.28' => 'dicomInstitutionDepartmentName', + '1.2.840.10008.15.0.3.29' => 'dicomIssuerOfPatientID', + '1.2.840.10008.15.0.3.30' => 'dicomPreferredCallingAETitle', + '1.2.840.10008.15.0.3.31' => 'dicomSupportedCharacterSet', + '1.2.840.10008.15.0.4.1' => 'dicomConfigurationRoot', + '1.2.840.10008.15.0.4.2' => 'dicomDevicesRoot', + '1.2.840.10008.15.0.4.3' => 'dicomUniqueAETitlesRegistryRoot', + '1.2.840.10008.15.0.4.4' => 'dicomDevice', + '1.2.840.10008.15.0.4.5' => 'dicomNetworkAE', + '1.2.840.10008.15.0.4.6' => 'dicomNetworkConnection', + '1.2.840.10008.15.0.4.7' => 'dicomUniqueAETitle', + '1.2.840.10008.15.0.4.8' => 'dicomTransferCapability', +); + +#------------------------------------------------------------------------------ +# Extract information from a DICOM (DCM) image +# Inputs: 0) ExifTool object reference, 1) DirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid DICOM file +sub ProcessDICOM($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $unknown = $et->Options('Unknown'); + my $verbose = $et->Options('Verbose'); + my ($hdr, $buff, $implicit, $vr, $len); +# +# identify the DICOM or ACR-NEMA file +# + $raf->Read($hdr, 12) == 12 or return 0; # save for ACR identification later + $raf->Seek(128, 0) or return 0; # skip to end of DICM header + $raf->Read($buff, 4) == 4 or return 0; # read signature + if ($buff eq 'DICM') { + # file meta information transfer syntax is explicit little endian + SetByteOrder('II'); + $et->SetFileType('DICOM'); + } else { + # test for a RAW DCM image (ACR-NEMA format, ie. no header) + foreach ('II','MM','') { + return 0 unless $_; # no luck identifying the syntax + SetByteOrder($_); + my $g = Get16u(\$hdr, 0); + # expect group number to be small and even + next if $g < 2 or $g > 8 or $g & 0x01; + my $e = Get16u(\$hdr, 2); + next if $e > 0x20; # expect a low element number at start + $vr = substr($hdr, 4, 2); # look for explicit VR + if ($vr =~ /^[A-Z]{2}$/) { + $implicit = 0; + if ($vr32{$vr}) { + next unless Get16u(\$hdr, 6) == 0; # must be 2 zero bytes + $len = Get32u(\$hdr, 8); + } else { + next if $e == 0 and $vr ne 'UL'; # group length must be UL + $len = Get16u(\$hdr, 6); + } + } else { + $implicit = 1; + $len = Get32u(\$hdr, 4); + } + next if $e == 0 and $len != 4; # group length value must be 4 bytes + next if $len > 64; # first element shouldn't be too long + last; # success! + } + $raf->Seek(0, 0) or return 0; # rewind to start of file + $et->SetFileType('ACR'); + } +# +# process the meta information +# + my $tagTablePtr = GetTagTable('Image::ExifTool::DICOM::Main'); + my $pos = $raf->Tell(); + my $err = 1; + my ($transferSyntax, $group2end); + for (;;) { + $raf->Read($buff, 8) == 8 or $err = 0, last; + $pos += 8; + my $group = Get16u(\$buff, 0); + # implement the transfer syntax at the end of the group 2 data + if ($transferSyntax and ($group != 0x0002 or + ($group2end and $pos > $group2end))) + { + # 1.2.840.10008.1.2 = implicit VR little endian + # 1.2.840.10008.1.2.2 = explicit VR big endian + # 1.2.840.10008.1.2.x = explicit VR little endian + # 1.2.840.10008.1.2.1.99 = deflated + unless ($transferSyntax =~ /^1\.2\.840\.10008\.1\.2(\.\d+)?(\.\d+)?/) { + $et->Warn("Unrecognized transfer syntax $transferSyntax"); + last; + } + if (not $1) { + $implicit = 1; + } elsif ($1 eq '.2') { + SetByteOrder('MM'); + $group = Get16u(\$buff, 0); # must get group again + } elsif ($1 eq '.1' and $2 and $2 eq '.99') { + # inflate compressed data stream + if (eval { require Compress::Zlib }) { + # must use undocumented zlib feature to disable zlib header information + # because DICOM deflated data doesn't have the zlib header (ref 3) + my $wbits = -Compress::Zlib::MAX_WBITS(); + my $inflate = Compress::Zlib::inflateInit(-WindowBits => $wbits); + if ($inflate) { + $raf->Seek(-8, 1) or last; + my $data = ''; + while ($raf->Read($buff, 65536)) { + my ($buf, $stat) = $inflate->inflate($buff); + if ($stat == Compress::Zlib::Z_OK() or + $stat == Compress::Zlib::Z_STREAM_END()) + { + $data .= $buf; + last if $stat == Compress::Zlib::Z_STREAM_END(); + } else { + $et->Warn('Error inflating compressed data stream'); + return 1; + } + } + last if length $data < 8; + # create new RAF object from inflated data stream + $raf = new File::RandomAccess(\$data); + # re-read start of stream (now decompressed) + $raf->Read($buff, 8) == 8 or last; + $group = Get16u(\$buff, 0); + } else { + $et->Warn('Error initializing inflation'); + return 1; + } + } else { + $et->Warn('Install Compress::Zlib to decode compressed data stream'); + return 1; + } + } + undef $transferSyntax; + } + my $element = Get16u(\$buff,2); + my $tag = sprintf('%.4X,%.4X', $group, $element); + + if ($implicit or $implicitVR{$tag}) { + # treat everything as string if implicit VR because it + # isn't worth it to generate the necessary VR lookup tables + # for the thousands of defined data elements + $vr = ''; # no VR (treat everything as string) + $len = Get32u(\$buff, 4); + } else { + $vr = substr($buff,4,2); + last unless $vr =~ /^[A-Z]{2}$/; + if ($vr32{$vr}) { + $raf->Read($buff, 4) == 4 or last; + $pos += 4; + $len = Get32u(\$buff, 0); + $len = 0 if $vr eq 'SQ'; # just recurse into sequences + } else { + $len = Get16u(\$buff, 6); + } + } + if ($len == 0xffffffff) { + $len = 0; # don't read value if undefined length + if ($verbose) { + # start list of items in verbose output + $et->VPrint(0, "$$et{INDENT}+ [List of items]\n"); + $$et{INDENT} .= '| '; + } + } + # read the element value + if ($len) { + $raf->Read($buff, $len) == $len or last; + $pos += $len; + } else { + $buff = ''; + } + + # handle tags not found in the table + my $tagInfo = $$tagTablePtr{$tag}; + unless ($tagInfo) { + # accept tag ID's with "x" for a wildcard in the following patterns: + # '60xx,1203', '0020,31xx', '0028,04x2', '1000,xxx0', '1010,xxxx' + my $xx; + if ((($xx = $tag) =~ s/^(..)../$1xx/ and $$tagTablePtr{$xx}) or + (($xx = $tag) =~ s/..$/xx/ and $$tagTablePtr{$xx}) or + (($xx = $tag) =~ s/.(.)$/x$1/ and $$tagTablePtr{$xx}) or + (($xx = $tag) =~ s/...(.)$/xxx$1/ and $$tagTablePtr{$xx}) or + (($xx = $tag) =~ s/....$/xxxx/ and $$tagTablePtr{$xx})) + { + $tag = $xx; + $tagInfo = $$tagTablePtr{$xx}; + } elsif ($unknown) { + # create tag info hash for unknown elements + if ($element == 0) { # element zero is group length + $tagInfo = { + Name => sprintf("Group%.4X_Length", $group), + Description => sprintf("Group %.4X Length", $group), + }; + } else { + $tagInfo = { + Name => sprintf("DICOM_%.4X_%.4X", $group, $element), + Description => sprintf("DICOM %.4X,%.4X", $group, $element), + }; + } + $$tagInfo{Unknown} = 1; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + } + # get VR from our tag information if implicit + $vr = $$tagInfo{VR} || ' ' if $tagInfo and not $vr; + + if ($element == 0) { + $vr = 'UL'; # group length element is always unsigned long + } + my $val; + my $format = $dicomFormat{$vr}; + # remove trailing space used to pad to an even number of characters + $buff =~ s/ $// unless $format or length($buff) & 0x01; + if ($len > 1024) { + # treat large data elements as binary data + my $binData; + my $lcTag = $tagInfo ? lc($$tagInfo{Name}) : 'unknown'; + if ($$et{REQ_TAG_LOOKUP}{$lcTag} or + ($$et{OPTIONS}{Binary} and not $$et{EXCL_TAG_LOOKUP}{$lcTag})) + { + $binData = $buff; # must make a copy + } else { + $binData = "Binary data $len bytes"; + } + $val = \$binData; + } elsif ($format) { + $val = ReadValue(\$buff, 0, $format, undef, $len); + } else { + $val = $buff; + $format = 'string'; + if ($vr eq 'DA') { + # format date values + $val =~ s/^ *(\d{4})(\d{2})(\d{2})/$1:$2:$3/; + } elsif ($vr eq 'TM') { + # format time values + $val =~ s/^ *(\d{2})(\d{2})(\d{2}[^ ]*)/$1:$2:$3/; + } elsif ($vr eq 'DT') { + # format date/time values + $val =~ s/^ *(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2}[^ ]*)/$1:$2:$3 $4:$5:$6/; + } elsif ($vr eq 'AT' and $len == 4) { + # convert attribute tag ID to hex format + my ($g, $e) = (Get16u(\$buff,0), Get16u(\$buff,2)); + $val = sprintf('%.4X,%.4X', $g, $e); + } elsif ($vr eq 'UI') { + # add PrintConv to translate registered UID's + $val =~ s/\0.*//s; # truncate at null + $$tagInfo{PrintConv} = \%uid if $uid{$val} and $tagInfo; + } elsif ($vr =~ /^(AE|CS|DS|IS|LO|PN|SH)$/) { + $val =~ s/ +$//; # leading/trailing spaces not significant + $val =~ s/^ +//; + } elsif ($vr =~ /^(LT|ST|UT)$/) { + $val =~ s/ +$//; # trailing spaces not significant + } + } + # save the group 2 end position and transfer syntax + if ($group == 0x0002) { + $element == 0x0000 and $group2end = $pos + $val; + $element == 0x0010 and $transferSyntax = $val; + } + + # handle the new tag information + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => \$buff, + DataPos => $pos - $len, + Format => $format, + Start => 0, + Size => $len, + Extra => " ($vr)", + ); + + # stop indenting for list if we reached EndOfItems tag + $$et{INDENT} =~ s/..$// if $verbose and $tag eq 'FFFE,E00D'; + } + $err and $et->Warn('Error reading DICOM file (corrupted?)'); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::DICOM - Read DICOM and ACR-NEMA medical images + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract meta +information from DICOM (Digital Imaging and Communications in Medicine) DCM +and ACR-NEMA (American College of Radiology - National Electrical +Manufacturer's Association) ACR medical images. + +=head1 NOTES + +Images compressed using the DICOM deflated transfer syntax will be decoded +if Compress::Zlib is installed. + +No translation of special characters sets is done. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://medical.nema.org/> + +=item L<http://www.sph.sc.edu/comd/rorden/dicom.html> + +=item L<http://www.dclunie.com/> + +=item L<http://www.gehealthcare.com/usen/interoperability/dicom/docs/2258357r3.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/DICOM Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/DJI.pm b/ExifTool/lib/Image/ExifTool/DJI.pm new file mode 100644 index 0000000..4316fc0 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/DJI.pm @@ -0,0 +1,249 @@ +#------------------------------------------------------------------------------ +# File: DJI.pm +# +# Description: DJI Phantom maker notes tags +# +# Revisions: 2016-07-25 - P. Harvey Created +# 2017-06-23 - PH Added XMP tags +#------------------------------------------------------------------------------ + +package Image::ExifTool::DJI; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::XMP; +use Image::ExifTool::GPS; + +$VERSION = '1.08'; + +sub ProcessDJIInfo($$$); + +my %convFloat2 = ( + PrintConv => 'sprintf("%+.2f", $val)', + PrintConvInv => '$val', +); + +# DJI maker notes (ref PH, mostly educated guesses based on DJI QuickTime::UserData tags) +%Image::ExifTool::DJI::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + This table lists tags found in the maker notes of images from some DJI + Phantom drones. + }, + 0x01 => { Name => 'Make', Writable => 'string' }, + # 0x02 - int8u[4]: "1 0 0 0", "1 1 0 0" + 0x03 => { Name => 'SpeedX', Writable => 'float', %convFloat2 }, # (guess) + 0x04 => { Name => 'SpeedY', Writable => 'float', %convFloat2 }, # (guess) + 0x05 => { Name => 'SpeedZ', Writable => 'float', %convFloat2 }, # (guess) + 0x06 => { Name => 'Pitch', Writable => 'float', %convFloat2 }, + 0x07 => { Name => 'Yaw', Writable => 'float', %convFloat2 }, + 0x08 => { Name => 'Roll', Writable => 'float', %convFloat2 }, + 0x09 => { Name => 'CameraPitch',Writable => 'float', %convFloat2 }, + 0x0a => { Name => 'CameraYaw', Writable => 'float', %convFloat2 }, + 0x0b => { Name => 'CameraRoll', Writable => 'float', %convFloat2 }, +); + +# DJI debug maker notes +%Image::ExifTool::DJI::Info = ( + PROCESS_PROC => \&ProcessDJIInfo, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Tags written by some DJI drones.', + VARS => { LONG_TAGS => 2 }, + ae_dbg_info => { Name => 'AEDebugInfo' }, + ae_histogram_info => { Name => 'AEHistogramInfo' }, + ae_local_histogram => { Name => 'AELocalHistogram' }, + ae_liveview_histogram_info => { Name => 'AELiveViewHistogramInfo' }, + ae_liveview_local_histogram => { Name => 'AELiveViewLocalHistogram' }, + awb_dbg_info => { Name => 'AWBDebugInfo' }, + af_dbg_info => { Name => 'AFDebugInfo' }, + hiso => { Name => 'Histogram' }, + xidiri => { Name => 'Xidiri' }, + 'GimbalDegree(Y,P,R)'=> { Name => 'GimbalDegree' }, + 'FlightDegree(Y,P,R)'=> { Name => 'FlightDegree' }, + adj_dbg_info => { Name => 'ADJDebugInfo' }, + sensor_id => { Name => 'SensorID' }, + 'FlightSpeed(X,Y,Z)' => { Name => 'FlightSpeed' }, + hyperlapse_dbg_info => { Name => 'HyperlapsDebugInfo' }, +); + +# thermal parameters in APP4 of DJI ZH20T images (ref forum11401) +%Image::ExifTool::DJI::ThermalParams = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'APP4', 2 => 'Image' }, + NOTES => 'Thermal parameters extracted from APP4 of DJI RJPEG files from the ZH20T.', + # 0x00 - 0xaa551206 - temperature header magic number + 0x24 => { Name => 'K1', Format => 'float' }, + 0x28 => { Name => 'K2', Format => 'float' }, + 0x2c => { Name => 'K3', Format => 'float' }, + 0x30 => { Name => 'K4', Format => 'float' }, + 0x34 => { Name => 'KF', Format => 'float' }, + 0x38 => { Name => 'B1', Format => 'float' }, + 0x3c => { Name => 'B2', Format => 'float' }, + 0x44 => { Name => 'ObjectDistance', Format => 'int16u' }, + 0x46 => { Name => 'RelativeHumidity', Format => 'int16u' }, + 0x48 => { Name => 'Emissivity', Format => 'int16u' }, + 0x4a => { Name => 'Reflection', Format => 'int16u', }, + 0x4c => { Name => 'AmbientTemperature', Format => 'int16u' }, # (aka D1) + 0x50 => { Name => 'D2', Format => 'int32s' }, + 0x54 => { Name => 'KJ', Format => 'int16u' }, + 0x56 => { Name => 'DB', Format => 'int16u' }, + 0x58 => { Name => 'KK', Format => 'int16u' }, + # 0x500 - 0x55aa1206 - device header magic number + # (nothing yet decoded from device header) +); + +# thermal parameters in APP4 of DJI M3T, H20N, M2EA and some M30T images (ref PH/forum11401) +%Image::ExifTool::DJI::ThermalParams2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'APP4', 2 => 'Image' }, + NOTES => 'Thermal parameters extracted from APP4 of DJI M3T RJPEG files.', + 0x00 => { Name => 'AmbientTemperature', Format => 'float', PrintConv => 'sprintf("%.1f C",$val)' }, # (NC) + 0x04 => { Name => 'ObjectDistance', Format => 'float', PrintConv => 'sprintf("%.1f m",$val)' }, + 0x08 => { Name => 'Emissivity', Format => 'float', PrintConv => 'sprintf("%.2f",$val)' }, + 0x0c => { Name => 'RelativeHumidity', Format => 'float', PrintConv => 'sprintf("%g %%",$val*100)' }, + 0x10 => { Name => 'ReflectedTemperature',Format => 'float', PrintConv => 'sprintf("%.1f C",$val)' }, + 0x65 => { Name => 'IDString', Format => 'string[16]' }, # (NC) +); + +# thermal parameters in APP4 of some DJI M30T images (ref PH) +%Image::ExifTool::DJI::ThermalParams3 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'APP4', 2 => 'Image' }, + NOTES => 'Thermal parameters extracted from APP4 of some DJI RJPEG files.', + # 0x00 - 0xaa553800 - params3 magic number + 0x04 => { Name => 'RelativeHumidity', Format => 'int16u' }, + 0x06 => { Name => 'ObjectDistance', Format => 'int16u', ValueConv => '$val / 10' }, + 0x08 => { Name => 'Emissivity', Format => 'int16u', ValueConv => '$val / 100' }, + 0x0a => { Name => 'ReflectedTemperature',Format => 'int16u', ValueConv => '$val / 10' }, +); + +%Image::ExifTool::DJI::XMP = ( + %Image::ExifTool::XMP::xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-drone-dji', 2 => 'Location' }, + NAMESPACE => 'drone-dji', + TABLE_DESC => 'XMP DJI', + VARS => { NO_ID => 1 }, + NOTES => 'XMP tags used by DJI for images from drones.', + AbsoluteAltitude => { Writable => 'real' }, + RelativeAltitude => { Writable => 'real' }, + GimbalRollDegree => { Writable => 'real' }, + GimbalYawDegree => { Writable => 'real' }, + GimbalPitchDegree => { Writable => 'real' }, + FlightRollDegree => { Writable => 'real' }, + FlightYawDegree => { Writable => 'real' }, + FlightPitchDegree => { Writable => 'real' }, + GpsLatitude => { + Name => 'GPSLatitude', + Writable => 'real', + Avoid => 1, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")', + }, + GpsLongtitude => { # (sic) + Name => 'GPSLongtitude', + Writable => 'real', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")', + }, + GpsLongitude => { #PH (NC) + Name => 'GPSLongitude', + Writable => 'real', + Avoid => 1, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")', + }, + FlightXSpeed => { Writable => 'real' }, + FlightYSpeed => { Writable => 'real' }, + FlightZSpeed => { Writable => 'real' }, + CamReverse => { }, # integer? + GimbalReverse => { }, # integer? + SelfData => { Groups => { 2 => 'Image' } }, + CalibratedFocalLength => { Writable => 'real', Groups => { 2 => 'Image' } }, + CalibratedOpticalCenterX => { Writable => 'real', Groups => { 2 => 'Image' } }, + CalibratedOpticalCenterY => { Writable => 'real', Groups => { 2 => 'Image' } }, + RtkFlag => { }, # integer? + RtkStdLon => { Writable => 'real' }, + RtkStdLat => { Writable => 'real' }, + RtkStdHgt => { Writable => 'real' }, + DewarpData => { Groups => { 2 => 'Image' } }, + DewarpFlag => { Groups => { 2 => 'Image' } }, # integer? + Latitude => { + Name => 'Latitude', + Writable => 'real', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")', + }, + Longitude => { + Name => 'Longitude', + Writable => 'real', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")', + }, +); + +#------------------------------------------------------------------------------ +# Process DJI info (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessDJIInfo($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart); + if ($dirStart) { + my $buff = substr($$dataPt, $dirStart, $dirLen); + $dataPt = \$buff; + } + $et->VerboseDir('DJIInfo', undef, length $$dataPt); + while ($$dataPt =~ /\G\[(.*?)\](?=(\[|$))/sg) { + my ($tag, $val) = split /:/, $1, 2; + next unless defined $tag and defined $val; + if ($val =~ /^([\x20-\x7f]+)\0*$/) { + $val = $1; + } else { + my $buff = $val; + $val = \$buff; + } + if (not $$tagTbl{$tag} and $tag=~ /^[-_a-zA-Z0-9]+$/) { + my $name = $tag; + $name =~ s/_([a-z])/_\U$1/g; + AddTagToTable($tagTbl, $tag, { Name => Image::ExifTool::MakeTagName($name) }); + } + $et->HandleTag($tagTbl, $tag, $val); + } + return 1; +} + +__END__ + +=head1 NAME + +Image::ExifTool::DJI - DJI Phantom 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 +the maker notes in images from some DJI Phantom drones. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/DJI Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/DNG.pm b/ExifTool/lib/Image/ExifTool/DNG.pm new file mode 100644 index 0000000..ff2c469 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/DNG.pm @@ -0,0 +1,848 @@ +#------------------------------------------------------------------------------ +# File: DNG.pm +# +# Description: Read DNG-specific information +# +# Revisions: 01/09/2006 - P. Harvey Created +# +# References: 1) http://www.adobe.com/products/dng/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::DNG; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::MakerNotes; +use Image::ExifTool::CanonRaw; + +$VERSION = '1.23'; + +sub ProcessOriginalRaw($$$); +sub ProcessAdobeData($$$); +sub ProcessAdobeMakN($$$); +sub ProcessAdobeCRW($$$); +sub ProcessAdobeRAF($$$); +sub ProcessAdobeMRW($$$); +sub ProcessAdobeSR2($$$); +sub ProcessAdobeIFD($$$); +sub WriteAdobeStuff($$$); + +# data in OriginalRawFileData +%Image::ExifTool::DNG::OriginalRaw = ( + GROUPS => { 2 => 'Image' }, + PROCESS_PROC => \&ProcessOriginalRaw, + NOTES => q{ + This table defines tags extracted from the DNG OriginalRawFileData + information. + }, + 0 => { Name => 'OriginalRawImage', Binary => 1 }, + 1 => { Name => 'OriginalRawResource', Binary => 1 }, + 2 => 'OriginalRawFileType', + 3 => 'OriginalRawCreator', + 4 => { Name => 'OriginalTHMImage', Binary => 1 }, + 5 => { Name => 'OriginalTHMResource', Binary => 1 }, + 6 => 'OriginalTHMFileType', + 7 => 'OriginalTHMCreator', +); + +%Image::ExifTool::DNG::AdobeData = ( #PH + GROUPS => { 0 => 'MakerNotes', 1 => 'AdobeDNG', 2 => 'Image' }, + PROCESS_PROC => \&ProcessAdobeData, + WRITE_PROC => \&WriteAdobeStuff, + NOTES => q{ + This information is found in the "Adobe" DNGPrivateData. + + The maker notes ('MakN') are processed by ExifTool, but some information may + have been lost by the Adobe DNG Converter. This is because the Adobe DNG + Converter (as of version 6.3) doesn't properly handle information referenced + from inside the maker notes that lies outside the original maker notes + block. This information is lost when only the maker note block is copied to + the DNG image. While this doesn't effect all makes of cameras, it is a + problem for some major brands such as Olympus and Sony. + + Other entries in this table represent proprietary information that is + extracted from the original RAW image and restructured to a different (but + still proprietary) Adobe format. + }, + MakN => [ ], # (filled in later) + 'CRW ' => { + Name => 'AdobeCRW', + SubDirectory => { + TagTable => 'Image::ExifTool::CanonRaw::Main', + ProcessProc => \&ProcessAdobeCRW, + WriteProc => \&WriteAdobeStuff, + }, + }, + 'MRW ' => { + Name => 'AdobeMRW', + SubDirectory => { + TagTable => 'Image::ExifTool::MinoltaRaw::Main', + ProcessProc => \&ProcessAdobeMRW, + WriteProc => \&WriteAdobeStuff, + }, + }, + 'SR2 ' => { + Name => 'AdobeSR2', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::SR2Private', + ProcessProc => \&ProcessAdobeSR2, + }, + }, + 'RAF ' => { + Name => 'AdobeRAF', + SubDirectory => { + TagTable => 'Image::ExifTool::FujiFilm::RAF', + ProcessProc => \&ProcessAdobeRAF, + }, + }, + 'Pano' => { + Name => 'AdobePano', + SubDirectory => { + TagTable => 'Image::ExifTool::PanasonicRaw::Main', + ProcessProc => \&ProcessAdobeIFD, + }, + }, + 'Koda' => { + Name => 'AdobeKoda', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::IFD', + ProcessProc => \&ProcessAdobeIFD, + }, + }, + 'Leaf' => { + Name => 'AdobeLeaf', + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::SubIFD', + ProcessProc => \&ProcessAdobeIFD, + }, + }, +); + +# fill in maker notes +{ + my $tagInfo; + my $list = $Image::ExifTool::DNG::AdobeData{MakN}; + foreach $tagInfo (@Image::ExifTool::MakerNotes::Main) { + unless (ref $tagInfo eq 'HASH') { + push @$list, $tagInfo; + next; + } + my %copy = %$tagInfo; + delete $copy{Groups}; + delete $copy{GotGroups}; + delete $copy{Table}; + push @$list, \%copy; + } +} + +#------------------------------------------------------------------------------ +# Process DNG OriginalRawFileData information +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessOriginalRaw($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart}; + my $end = $start + $$dirInfo{DirLen}; + my $pos = $start; + my ($index, $err); + + SetByteOrder('MM'); # pointers are always big-endian in this structure + for ($index=0; $index<8; ++$index) { + last if $pos + 4 > $end; + my $val = Get32u($dataPt, $pos); + $val or $pos += 4, next; # ignore zero values + my $tagInfo = $et->GetTagInfo($tagTablePtr, $index); + $tagInfo or $err = "Missing DNG tag $index", last; + if ($index & 0x02) { + # extract a simple file type (tags 2, 3, 6 and 7) + $val = substr($$dataPt, $pos, 4); + $pos += 4; + } else { + # extract a compressed data block (tags 0, 1, 4 and 5) + my $n = int(($val + 65535) / 65536); + my $hdrLen = 4 * ($n + 2); + $pos + $hdrLen > $end and $err = '', last; + my $tag = $$tagInfo{Name}; + # only extract this information if requested (because it takes time) + my $lcTag = lc $tag; + if (($$et{OPTIONS}{Binary} and not $$et{EXCL_TAG_LOOKUP}{$lcTag}) or + $$et{REQ_TAG_LOOKUP}{$lcTag}) + { + unless (eval { require Compress::Zlib }) { + $err = 'Install Compress::Zlib to extract compressed images'; + last; + } + my $i; + $val = ''; + my $p2 = $pos + Get32u($dataPt, $pos + 4); + for ($i=0; $i<$n; ++$i) { + # inflate this compressed block + my $p1 = $p2; + $p2 = $pos + Get32u($dataPt, $pos + ($i + 2) * 4); + if ($p1 >= $p2 or $p2 > $end) { + $err = 'Bad compressed RAW image'; + last; + } + my $buff = substr($$dataPt, $p1, $p2 - $p1); + my ($v2, $stat); + my $inflate = Compress::Zlib::inflateInit(); + $inflate and ($v2, $stat) = $inflate->inflate($buff); + if ($inflate and $stat == Compress::Zlib::Z_STREAM_END()) { + $val .= $v2; + } else { + $err = 'Error inflating compressed RAW image'; + last; + } + } + $pos = $p2; + } else { + $pos + $hdrLen > $end and $err = '', last; + my $len = Get32u($dataPt, $pos + $hdrLen - 4); + $pos + $len > $end and $err = '', last; + $val = substr($$dataPt, $pos + $hdrLen, $len - $hdrLen); + $val = "Binary data $len bytes"; + $pos += $len; # skip over this block + } + } + $et->FoundTag($tagInfo, $val); + } + $et->Warn($err || 'Bad OriginalRawFileData') if defined $err; + return 1; +} + +#------------------------------------------------------------------------------ +# Process Adobe DNGPrivateData directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessAdobeData($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $pos = $$dirInfo{DirStart}; + my $end = $$dirInfo{DirLen} + $pos; + my $outfile = $$dirInfo{OutFile}; + my $verbose = $et->Options('Verbose'); + my $htmlDump = $et->Options('HtmlDump'); + + return 0 unless $$dataPt =~ /^Adobe\0/; + unless ($outfile) { + $et->VerboseDir($dirInfo); + # don't parse makernotes if FastScan > 1 + my $fast = $et->Options('FastScan'); + return 1 if $fast and $fast > 1; + } + $htmlDump and $et->HDump($dataPos, 6, 'Adobe DNGPrivateData header'); + SetByteOrder('MM'); # always big endian + $pos += 6; + while ($pos + 8 <= $end) { + my ($tag, $size) = unpack("x${pos}a4N", $$dataPt); + $pos += 8; + last if $pos + $size > $end; + my $tagInfo = $$tagTablePtr{$tag}; + if ($htmlDump) { + my $name = "Adobe$tag"; + $name =~ tr/ //d; + $et->HDump($dataPos + $pos - 8, 8, "$name header", "Data Size: $size bytes"); + # dump non-EXIF format data + unless ($tag =~ /^(MakN|SR2 )$/) { + $et->HDump($dataPos + $pos, $size, "$name data"); + } + } + if ($verbose and not $outfile) { + $tagInfo or $et->VPrint(0, "$$et{INDENT}Unsupported DNGAdobeData record: ($tag)\n"); + $et->VerboseInfo($tag, + ref $tagInfo eq 'HASH' ? $tagInfo : undef, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos, + Size => $size, + ); + } + my $value; + while ($tagInfo) { + my ($subTable, $subName, $processProc); + if (ref $tagInfo eq 'HASH') { + unless ($$tagInfo{SubDirectory}) { + if ($outfile) { + # copy value across to outfile + $value = substr($$dataPt, $pos, $size); + } else { + $et->HandleTag($tagTablePtr, $tag, substr($$dataPt, $pos, $size)); + } + last; + } + $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + $subName = $$tagInfo{Name}; + $processProc = $tagInfo->{SubDirectory}->{ProcessProc}; + } else { + $subTable = $tagTablePtr; + $subName = 'AdobeMakN'; + $processProc = \&ProcessAdobeMakN; + } + my %dirInfo = ( + Base => $$dirInfo{Base}, + DataPt => $dataPt, + DataPos => $dataPos, + DataLen => $$dirInfo{DataLen}, + DirStart => $pos, + DirLen => $size, + DirName => $subName, + ); + if ($outfile) { + $dirInfo{Proc} = $processProc; # WriteAdobeStuff() calls this to do the actual writing + $value = $et->WriteDirectory(\%dirInfo, $subTable, \&WriteAdobeStuff); + # use old directory if an error occurred + defined $value or $value = substr($$dataPt, $pos, $size); + } else { + # override process proc for MakN + $et->ProcessDirectory(\%dirInfo, $subTable, $processProc); + } + last; + } + if (defined $value and length $value) { + # add "Adobe" header if necessary + $$outfile = "Adobe\0" unless $$outfile and length $$outfile; + $$outfile .= $tag . pack('N', length $value) . $value; + $$outfile .= "\0" if length($value) & 0x01; # pad if necessary + } + $pos += $size; + ++$pos if $size & 0x01; # (darn padding) + } + $pos == $end or $et->Warn("$pos $end Adobe private data is corrupt"); + return 1; +} + +#------------------------------------------------------------------------------ +# Process Adobe CRW directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +# Notes: data has 4 byte header (2 for byte order and 2 for entry count) +# - this routine would be as simple as ProcessAdobeMRW() below if Adobe hadn't +# pulled the bonehead move of reformatting the CRW information +sub ProcessAdobeCRW($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart}; + my $end = $start + $$dirInfo{DirLen}; + my $verbose = $et->Options('Verbose'); + my $buildMakerNotes = $et->Options('MakerNotes'); + my $outfile = $$dirInfo{OutFile}; + my ($newTags, $oldChanged); + + SetByteOrder('MM'); # always big endian + return 0 if $$dirInfo{DirLen} < 4; + my $byteOrder = substr($$dataPt, $start, 2); + return 0 unless $byteOrder =~ /^(II|MM)$/; + + # initialize maker note data if building maker notes + $buildMakerNotes and Image::ExifTool::CanonRaw::InitMakerNotes($et); + + my $entries = Get16u($dataPt, $start + 2); + my $pos = $start + 4; + $et->VerboseDir($dirInfo, $entries) unless $outfile; + if ($outfile) { + # get hash of new tags + $newTags = $et->GetNewTagInfoHash($tagTablePtr); + $$outfile = substr($$dataPt, $start, 4); + $oldChanged = $$et{CHANGED}; + } + # loop through entries in Adobe CRW information + my $index; + for ($index=0; $index<$entries; ++$index) { + last if $pos + 6 > $end; + my $tag = Get16u($dataPt, $pos); + my $size = Get32u($dataPt, $pos + 2); + $pos += 6; + last if $pos + $size > $end; + my $value = substr($$dataPt, $pos, $size); + my $tagID = $tag & 0x3fff; + my $tagType = ($tag >> 8) & 0x38; # get tag type + my $format = $Image::ExifTool::CanonRaw::crwTagFormat{$tagType}; + my $count; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID, \$value); + if ($tagInfo) { + $format = $$tagInfo{Format} if $$tagInfo{Format}; + $count = $$tagInfo{Count}; + } + # set count to 1 by default for values that were in the directory entry + if (not defined $count and $tag & 0x4000 and $format and $format ne 'string') { + $count = 1; + } + # set count from tagInfo count if necessary + if ($format and not $count) { + # set count according to format and size + my $fnum = $Image::ExifTool::Exif::formatNumber{$format}; + my $fsiz = $Image::ExifTool::Exif::formatSize[$fnum]; + $count = int($size / $fsiz); + } + $format or $format = 'undef'; + SetByteOrder($byteOrder); + my $val = ReadValue(\$value, 0, $format, $count, $size); + if ($outfile) { + if ($tagInfo) { + my $subdir = $$tagInfo{SubDirectory}; + if ($subdir and $$subdir{TagTable}) { + my $name = $$tagInfo{Name}; + my $newTagTable = GetTagTable($$subdir{TagTable}); + return 0 unless $newTagTable; + my $subdirStart = 0; + #### eval Start () + $subdirStart = eval $$subdir{Start} if $$subdir{Start}; + my $dirData = \$value; + my %subdirInfo = ( + Name => $name, + DataPt => $dirData, + DataLen => $size, + DirStart => $subdirStart, + DirLen => $size - $subdirStart, + Parent => $$dirInfo{DirName}, + ); + #### eval Validate ($dirData, $subdirStart, $size) + if (defined $$subdir{Validate} and not eval $$subdir{Validate}) { + $et->Warn("Invalid $name data"); + } else { + $subdir = $et->WriteDirectory(\%subdirInfo, $newTagTable); + if (defined $subdir and length $subdir) { + if ($subdirStart) { + # add header before data directory + $value = substr($value, 0, $subdirStart) . $subdir; + } else { + $value = $subdir; + } + } + } + } elsif ($$newTags{$tagID}) { + my $nvHash = $et->GetNewValueHash($tagInfo); + if ($et->IsOverwriting($nvHash, $val)) { + my $newVal = $et->GetNewValue($nvHash); + my $verboseVal; + $verboseVal = $newVal if $verbose > 1; + # convert to specified format if necessary + if (defined $newVal and $format) { + $newVal = WriteValue($newVal, $format, $count); + } + if (defined $newVal) { + $et->VerboseValue("- CanonRaw:$$tagInfo{Name}", $value); + $et->VerboseValue("+ CanonRaw:$$tagInfo{Name}", $verboseVal); + $value = $newVal; + ++$$et{CHANGED}; + } + } + } + } + # write out new value (always big-endian) + SetByteOrder('MM'); + # (verified that there is no padding here) + $$outfile .= Set16u($tag) . Set32u(length($value)) . $value; + } else { + $et->HandleTag($tagTablePtr, $tagID, $val, + Index => $index, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Start => $pos, + Size => $size, + TagInfo => $tagInfo, + ); + if ($buildMakerNotes) { + # build maker notes information if requested + Image::ExifTool::CanonRaw::BuildMakerNotes($et, $tagID, $tagInfo, + \$value, $format, $count); + } + } + # (we lost the directory structure, but the second tag 0x0805 + # should be in the ImageDescription directory) + $$et{DIR_NAME} = 'ImageDescription' if $tagID == 0x0805; + SetByteOrder('MM'); + $pos += $size; + } + if ($outfile and (not defined $$outfile or $index != $entries or + $$et{CHANGED} == $oldChanged)) + { + $$et{CHANGED} = $oldChanged; # nothing changed + undef $$outfile; # rewrite old directory + } + if ($index != $entries) { + $et->Warn('Truncated CRW notes'); + } elsif ($pos < $end) { + $et->Warn($end-$pos . ' extra bytes at end of CRW notes'); + } + # finish building maker notes if necessary + if ($buildMakerNotes) { + SetByteOrder($byteOrder); + Image::ExifTool::CanonRaw::SaveMakerNotes($et); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process Adobe MRW directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +# Notes: data has 4 byte header (2 for byte order and 2 for entry count) +sub ProcessAdobeMRW($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + my $dirStart = $$dirInfo{DirStart}; + my $outfile = $$dirInfo{OutFile}; + + # construct fake MRW file + my $buff = "\0MRM" . pack('N', $dirLen - 4); + # ignore leading byte order and directory count words + $buff .= substr($$dataPt, $dirStart + 4, $dirLen - 4); + my $raf = new File::RandomAccess(\$buff); + my %dirInfo = ( RAF => $raf, OutFile => $outfile ); + my $rtnVal = Image::ExifTool::MinoltaRaw::ProcessMRW($et, \%dirInfo); + if ($outfile and defined $$outfile and length $$outfile) { + # remove MRW header and add Adobe header + $$outfile = substr($$dataPt, $dirStart, 4) . substr($$outfile, 8); + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Process Adobe RAF directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessAdobeRAF($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + return 0 if $$dirInfo{OutFile}; # (can't write this yet) + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $dirEnd = $$dirInfo{DirLen} + $pos; + my ($readIt, $warn); + + # set byte order according to first 2 bytes of Adobe RAF data + if ($pos + 2 <= $dirEnd and SetByteOrder(substr($$dataPt, $pos, 2))) { + $pos += 2; + } else { + $et->Warn('Invalid DNG RAF data'); + return 0; + } + $et->VerboseDir($dirInfo); + # make fake RAF object for processing (same acronym, different meaning) + my $raf = new File::RandomAccess($dataPt); + my $num = ''; + # loop through all records in Adobe RAF data: + # 0 - RAF table (not processed) + # 1 - first RAF directory + # 2 - second RAF directory (if available) + for (;;) { + last if $pos + 4 > $dirEnd; + my $len = Get32u($dataPt, $pos); + $pos += 4 + $len; # step to next entry in Adobe RAF record + $len or last; # ends with an empty entry + $readIt or $readIt = 1, next; # ignore first entry (RAF table) + my %dirInfo = ( + RAF => $raf, + DirStart => $pos - $len, + ); + $$et{SET_GROUP1} = "RAF$num"; + $et->ProcessDirectory(\%dirInfo, $tagTablePtr) or $warn = 1; + delete $$et{SET_GROUP1}; + $num = ($num || 1) + 1; + } + $warn and $et->Warn('Possibly corrupt RAF information'); + return 1; +} + +#------------------------------------------------------------------------------ +# Process Adobe SR2 directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +# Notes: data has 6 byte header (2 for byte order and 4 for original offset) +sub ProcessAdobeSR2($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + return 0 if $$dirInfo{OutFile}; # (can't write this yet) + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart}; + my $len = $$dirInfo{DirLen}; + + return 0 if $len < 6; + SetByteOrder('MM'); + my $originalPos = Get32u($dataPt, $start + 2); + return 0 unless SetByteOrder(substr($$dataPt, $start, 2)); + + $et->VerboseDir($dirInfo); + my $dataPos = $$dirInfo{DataPos}; + my $dirStart = $start + 6; # pointer to maker note directory + my $dirLen = $len - 6; + + # initialize subdirectory information + my $fix = $dataPos + $dirStart - $originalPos; + my %subdirInfo = ( + DirName => 'AdobeSR2', + Base => $$dirInfo{Base} + $fix, + DataPt => $dataPt, + DataPos => $dataPos - $fix, + DataLen => $$dirInfo{DataLen}, + DirStart => $dirStart, + DirLen => $dirLen, + Parent => $$dirInfo{DirName}, + ); + if ($et->Options('HtmlDump')) { + $et->HDump($dataPos + $start, 6, 'Adobe SR2 data'); + } + # parse the SR2 directory + $et->ProcessDirectory(\%subdirInfo, $tagTablePtr); + return 1; +} + +#------------------------------------------------------------------------------ +# Process Adobe-mutilated IFD directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +# Notes: data has 2 byte header (byte order of the data) +sub ProcessAdobeIFD($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + return 0 if $$dirInfo{OutFile}; # (can't write this yet) + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $dataPos = $$dirInfo{DataPos}; + + return 0 if $$dirInfo{DirLen} < 4; + my $dataOrder = substr($$dataPt, $pos, 2); + return 0 unless SetByteOrder($dataOrder); # validate byte order of data + + # parse the mutilated IFD. This is similar to a TIFF IFD, except: + # - data follows directly after Count entry in IFD + # - byte order of IFD entries is always big-endian, but byte order of data changes + SetByteOrder('MM'); # IFD structure is always big-endian + my $entries = Get16u($dataPt, $pos + 2); + $et->VerboseDir($dirInfo, $entries); + $pos += 4; + + my $end = $pos + $$dirInfo{DirLen}; + my $index; + for ($index=0; $index<$entries; ++$index) { + last if $pos + 8 > $end; + SetByteOrder('MM'); # directory entries always big-endian (doh!) + my $tagID = Get16u($dataPt, $pos); + my $format = Get16u($dataPt, $pos+2); + my $count = Get32u($dataPt, $pos+4); + if ($format < 1 or $format > 13) { + # warn unless the IFD was just padded with zeros + $format and $et->Warn( + sprintf("Unknown format ($format) for $$dirInfo{DirName} tag 0x%x",$tagID)); + return 0; # must be corrupted + } + my $size = $Image::ExifTool::Exif::formatSize[$format] * $count; + last if $pos + 8 + $size > $end; + my $formatStr = $Image::ExifTool::Exif::formatName[$format]; + SetByteOrder($dataOrder); # data stored in native order + my $val = ReadValue($dataPt, $pos + 8, $formatStr, $count, $size); + $et->HandleTag($tagTablePtr, $tagID, $val, + Index => $index, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos + 8, + Size => $size + ); + $pos += 8 + $size; + } + if ($index < $entries) { + $et->Warn("Truncated $$dirInfo{DirName} directory"); + return 0; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process Adobe MakerNotes directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +# Notes: data has 6 byte header (2 for byte order and 4 for original offset) +# --> or 18 bytes for DNG converted from JPG by Adobe Camera Raw! +sub ProcessAdobeMakN($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart}; + my $len = $$dirInfo{DirLen}; + my $outfile = $$dirInfo{OutFile}; + + return 0 if $len < 6; + SetByteOrder('MM'); + my $originalPos = Get32u($dataPt, $start + 2); + return 0 unless SetByteOrder(substr($$dataPt, $start, 2)); + + $et->VerboseDir($dirInfo) unless $outfile; + my $dataPos = $$dirInfo{DataPos}; + my $hdrLen = 6; + + # 2018-09-27: hack for extra 12 bytes in MakN header of JPEG converted to DNG + # by Adobe Camera Raw (4 bytes "00 00 00 01" followed by 8 unknown bytes) + # - this is because CameraRaw copies the maker notes from the wrong location + # in a JPG image (off by 12 bytes presumably due to the JPEG headers) + # - this hack won't work in most cases because the extra bytes are not consistent + # since they are just the data that existed in the JPG before the maker notes + # - also, the last 12 bytes of the maker notes will be missing + # - 2022-04-26: this bug still exists in Camera Raw 14.3 + $hdrLen += 12 if $len >= 18 and substr($$dataPt, $start+6, 4) eq "\0\0\0\x01"; + + my $dirStart = $start + $hdrLen; # pointer to maker note directory + my $dirLen = $len - $hdrLen; + + my $hdr = substr($$dataPt, $dirStart, $dirLen < 48 ? $dirLen : 48); + my $tagInfo = $et->GetTagInfo($tagTablePtr, 'MakN', \$hdr); + return 0 unless $tagInfo and $$tagInfo{SubDirectory}; + my $subdir = $$tagInfo{SubDirectory}; + my $subTable = GetTagTable($$subdir{TagTable}); + # initialize subdirectory information + my %subdirInfo = ( + DirName => 'MakerNotes', + Name => $$tagInfo{Name}, # needed for maker notes verbose dump + Base => $$dirInfo{Base}, + DataPt => $dataPt, + DataPos => $dataPos, + DataLen => $$dirInfo{DataLen}, + DirStart => $dirStart, + DirLen => $dirLen, + TagInfo => $tagInfo, + FixBase => $$subdir{FixBase}, + EntryBased=> $$subdir{EntryBased}, + Parent => $$dirInfo{DirName}, + ); + # look for start of maker notes IFD + my $loc = Image::ExifTool::MakerNotes::LocateIFD($et,\%subdirInfo); + unless (defined $loc) { + $et->Warn('Maker notes could not be parsed'); + return 0; + } + if ($et->Options('HtmlDump')) { + $et->HDump($dataPos + $start, $hdrLen, 'Adobe MakN data'); + $et->HDump($dataPos + $dirStart, $loc, "$$tagInfo{Name} header") if $loc; + } + + my $fix = 0; + unless ($$subdir{Base}) { + # adjust base offset for current maker note position + $fix = $dataPos + $dirStart - $originalPos; + $subdirInfo{Base} += $fix; + $subdirInfo{DataPos} -= $fix; + } + if ($outfile) { + # rewrite the maker notes directory + my $fixup = $subdirInfo{Fixup} = new Image::ExifTool::Fixup; + my $oldChanged = $$et{CHANGED}; + my $buff = $et->WriteDirectory(\%subdirInfo, $subTable); + # nothing to do if error writing directory or nothing changed + unless (defined $buff and $$et{CHANGED} != $oldChanged) { + $$et{CHANGED} = $oldChanged; + return 1; + } + # deleting maker notes if directory is empty + unless (length $buff) { + $$outfile = ''; + return 1; + } + # apply a one-time fixup to offsets + if ($subdirInfo{Relative}) { + # shift all offsets to be relative to new base + my $baseShift = $dataPos + $dirStart + $$dirInfo{Base} - $subdirInfo{Base}; + $fixup->{Shift} += $baseShift; + } else { + # shift offsets to position of original maker notes + $fixup->{Shift} += $originalPos; + } + # if we wrote the directory as a block the header is already included + $loc = 0 if $subdirInfo{BlockWrite}; + $fixup->{Shift} += $loc; # adjust for makernotes header + $fixup->ApplyFixup(\$buff); # fix up pointer offsets + # get copy of original Adobe header (6 or 18) and makernotes header ($loc) + my $header = substr($$dataPt, $start, $hdrLen + $loc); + # add Adobe and makernotes headers to new directory + $$outfile = $header . $buff; + } else { + # parse the maker notes directory + $et->ProcessDirectory(\%subdirInfo, $subTable, $$subdir{ProcessProc}); + # extract maker notes as a block if specified + if ($et->Options('MakerNotes') or + $$et{REQ_TAG_LOOKUP}{lc($$tagInfo{Name})}) + { + my $val; + if ($$tagInfo{MakerNotes}) { + $subdirInfo{Base} = $$dirInfo{Base} + $fix; + $subdirInfo{DataPos} = $dataPos - $fix; + $subdirInfo{DirStart} = $dirStart; + $subdirInfo{DirLen} = $dirLen; + # rebuild the maker notes to identify all offsets that require fixing up + $val = Image::ExifTool::Exif::RebuildMakerNotes($et, \%subdirInfo, $subTable); + if (not defined $val and $dirLen > 4) { + $et->Warn('Error rebuilding maker notes (may be corrupt)'); + } + } else { + # extract this directory as a block if specified + return 1 unless $$tagInfo{Writable}; + } + $val = substr($$dataPt, 20) unless defined $val; + $et->FoundTag($tagInfo, $val); + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Write Adobe information (calls appropriate ProcessProc to do the actual work) +# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref +# Returns: new data block (may be empty if directory is deleted) or undef on error +sub WriteAdobeStuff($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + my $proc = $$dirInfo{Proc} || \&ProcessAdobeData; + my $buff; + $$dirInfo{OutFile} = \$buff; + &$proc($et, $dirInfo, $tagTablePtr) or undef $buff; + return $buff; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::DNG.pm - Read DNG-specific information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to process +information in DNG (Digital Negative) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.adobe.com/products/dng/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/DNG Tags>, +L<Image::ExifTool::TagNames/EXIF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/DPX.pm b/ExifTool/lib/Image/ExifTool/DPX.pm new file mode 100644 index 0000000..7a61848 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/DPX.pm @@ -0,0 +1,247 @@ +#------------------------------------------------------------------------------ +# File: DPX.pm +# +# Description: Read DPX meta information +# +# Revisions: 2013-09-19 - P. Harvey created +# +# References: 1) http://www.cineon.com/ff_draft.php +# 2) Harry Mallon private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::DPX; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.06'; + +# DPX tags +%Image::ExifTool::DPX::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + NOTES => 'Tags extracted from DPX (Digital Picture Exchange) images.', + 0 => { Name => 'ByteOrder', Format => 'undef[4]', PrintConv => { SDPX => 'Big-endian', XPDS => 'Little-endian' } }, + 8 => { Name => 'HeaderVersion', Format => 'string[8]' }, + # 24 => { Name => 'GenericHeaderSize', Format => 'int32u' }, # = 1664 + # 28 => { Name => 'IndustryStandardHeaderSize', Format => 'int32u' }, # = 384 + 16 => { Name => 'DPXFileSize', Format => 'int32u' }, + 20 => { Name => 'DittoKey', Format => 'int32u', PrintConv => { 0 => 'Same', 1 => 'New' } }, + 36 => { Name => 'ImageFileName', Format => 'string[100]' }, + 136 => { + Name => 'CreateDate', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{4}:\d{2}:\d{2}):/$1 /; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + 160 => { Name => 'Creator', Format => 'string[100]', Groups => { 2 => 'Author' } }, + 260 => { Name => 'Project', Format => 'string[200]' }, + 460 => { Name => 'Copyright', Format => 'string[200]', Groups => { 2 => 'Author' } }, + 660 => { Name => 'EncryptionKey', Format => 'int32u', PrintConv => 'sprintf("%.8x",$val)' }, + 768 => { + Name => 'Orientation', + Format => 'int16u', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Mirror vertical', + 2 => 'Mirror horizontal', + 3 => 'Rotate 180', + 4 => 'Mirror horizontal and rotate 270 CW', + 5 => 'Rotate 90 CW', + 6 => 'Rotate 270 CW', + 7 => 'Mirror horizontal and rotate 90 CW', + }, + }, + 770 => { Name => 'ImageElements', Format => 'int16u' }, + 772 => { Name => 'ImageWidth', Format => 'int32u' }, + 776 => { Name => 'ImageHeight', Format => 'int32u' }, + 780 => { Name => 'DataSign', Format => 'int32u', PrintConv => { 0 => 'Unsigned', 1 => 'Signed' } }, + 800 => { + Name => 'ComponentsConfiguration', + Format => 'int8u', + PrintConv => { + 0 => 'User-defined single component', + 1 => 'Red (R)', + 2 => 'Green (G)', + 3 => 'Blue (B)', + 4 => 'Alpha (matte)', + 6 => 'Luminance (Y)', + 7 => 'Chrominance (Cb, Cr, subsampled by two)', + 8 => 'Depth (Z)', + 9 => 'Composite video', + 50 => 'R, G, B', + 51 => 'R, G, B, Alpha', + 52 => 'Alpha, B, G, R', + 100 => 'Cb, Y, Cr, Y (4:2:2)', + 101 => 'Cb, Y, A, Cr, Y, A (4:2:2:4)', + 102 => 'Cb, Y, Cr (4:4:4)', + 103 => 'Cb, Y, Cr, A (4:4:4:4)', + 150 => 'User-defined 2 component element', + 151 => 'User-defined 3 component element', + 152 => 'User-defined 4 component element', + 153 => 'User-defined 5 component element', + 154 => 'User-defined 6 component element', + 155 => 'User-defined 7 component element', + 156 => 'User-defined 8 component element', + }, + }, + 801 => { #2 + Name => 'TransferCharacteristic', + Format => 'int8u', + PrintConv => { + 0 => 'User-defined', + 1 => 'Printing density', + 2 => 'Linear', + 3 => 'Logarithmic', + 4 => 'Unspecified video', + 5 => 'SMPTE 274M', + 6 => 'ITU-R 704-4', + 7 => 'ITU-R 601-5 system B or G (625)', + 8 => 'ITU-R 601-5 system M (525)', + 9 => 'Composite video (NTSC)', + 10 => 'Composite video (PAL)', + 11 => 'Z (depth) - linear', + 12 => 'Z (depth) - homogeneous', + 13 => 'SMPTE ADX', + 14 => 'ITU-R 2020 NCL', + 15 => 'ITU-R 2020 CL', + 16 => 'IEC 61966-2-4 xvYCC', + 17 => 'ITU-R 2100 NCL/PQ', + 18 => 'ITU-R 2100 ICtCp/PQ', + 19 => 'ITU-R 2100 NCL/HLG', + 20 => 'ITU-R 2100 ICtCp/HLG', + 21 => 'RP 431-2:2011 Gama 2.6', + 22 => 'IEC 61966-2-1 sRGB', + }, + }, + 802 => { #2 + Name => 'ColorimetricSpecification', + Format => 'int8u', + PrintConv => { + 0 => 'User-defined', + 1 => 'Printing density', + 4 => 'Unspecified video', + 5 => 'SMPTE 274M', + 6 => 'ITU-R 704-4', + 7 => 'ITU-R 601-5 system B or G (625)', + 8 => 'ITU-R 601-5 system M (525)', + 9 => 'Composite video (NTSC)', + 10 => 'Composite video (PAL)', + 13 => 'SMPTE ADX', + 14 => 'ITU-R 2020', + 15 => 'P3D65', + 16 => 'P3DCI', + 17 => 'P3D60', + 18 => 'ACES', + }, + }, + 803 => { Name => 'BitDepth', Format => 'int8u' }, + 820 => { Name => 'ImageDescription', Format => 'string[32]' }, + 892 => { Name => 'Image2Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' }, + 964 => { Name => 'Image3Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' }, + 1036=> { Name => 'Image4Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' }, + 1108=> { Name => 'Image5Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' }, + 1180=> { Name => 'Image6Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' }, + 1252=> { Name => 'Image7Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' }, + 1324=> { Name => 'Image8Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' }, + # 1408=> { Name => 'XOffset', Format => 'int32u' }, + # 1412=> { Name => 'YOffset', Format => 'int32u' }, + # 1416=> { Name => 'XCenter', Format => 'float' }, + # 1420=> { Name => 'YCenter', Format => 'float' }, + # 1424=> { Name => 'XOriginalSize', Format => 'int32u' }, + # 1428=> { Name => 'YOriginalSize', Format => 'int32u' }, + 1432=> { Name => 'SourceFileName', Format => 'string[100]' }, + 1532=> { Name => 'SourceCreateDate', Format => 'string[24]' }, + 1556=> { Name => 'InputDeviceName', Format => 'string[32]' }, + 1588=> { Name => 'InputDeviceSerialNumber', Format => 'string[32]' }, + # 1620 => { Name => 'Border', Format => 'int16u[4]' }, + 1628 => { + Name => 'AspectRatio', + Format => 'int32u[2]', + RawConv => '$val =~ /4294967295/ ? undef : $val', # ignore undefined values + PrintConv => q{ + return 'undef' if $val eq '0 0'; + return 'inf' if $val=~/ 0$/; + my @a=split(' ',$val); + return join(':', Rationalize($a[0]/$a[1])); + }, + }, + 1724 => { Name => 'OriginalFrameRate',Format => 'float' }, + 1728 => { Name => 'ShutterAngle', Format => 'float', RawConv => '($val =~ /\d/ and $val !~ /nan/i) ? $val : undef' }, #2 + 1732 => { Name => 'FrameID', Format => 'string[32]' }, + 1764 => { Name => 'SlateInformation', Format => 'string[100]' }, + 1920 => { Name => 'TimeCode', Format => 'int32u' }, #2 + 1940 => { Name => 'FrameRate', Format => 'float', RawConv => '($val =~ /\d/ and $val !~ /nan/i) ? $val : undef' }, #2 + 1972 => { Name => 'Reserved5', Format => 'string[76]', Unknown => 1 }, + 2048 => { Name => 'UserID', Format => 'string[32]' }, +); + +#------------------------------------------------------------------------------ +# Extract EXIF information from a DPX image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid DPX file +sub ProcessDPX($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + + # verify this is a valid DPX file + return 0 unless $raf->Read($buff, 2080) == 2080; + return 0 unless $buff =~ /^(SDPX|XPDS)/; + SetByteOrder($1 eq 'SDPX' ? 'MM' : 'II'); + $et->SetFileType(); # set the FileType tag + my $hdrLen = Get32u(\$buff,24) + Get32u(\$buff,28); + $hdrLen == 2048 or $et->Warn("Unexpected DPX header length ($hdrLen)"); + my %dirInfo = ( + DataPt => \$buff, + DirStart => 0, + DirLen => length($buff), + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::DPX::Main'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::DPX - Read DPX meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +metadata from DPX (Digital Picture Exchange) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.cineon.com/ff_draft.php> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/DPX Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/DV.pm b/ExifTool/lib/Image/ExifTool/DV.pm new file mode 100644 index 0000000..4af6317 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/DV.pm @@ -0,0 +1,315 @@ +#------------------------------------------------------------------------------ +# File: DV.pm +# +# Description: Read DV meta information +# +# Revisions: 2010/12/24 - P. Harvey Created +# +# References: 1) http://www.ffmpeg.org/ +# 2) http://dvswitch.alioth.debian.org/wiki/DV_format/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::DV; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.02'; + +# DV profiles (ref 1) +my @dvProfiles = ( + { + DSF => 0, + VideoSType => 0x0, + FrameSize => 120000, + VideoFormat => 'IEC 61834, SMPTE-314M - 525/60 (NTSC)', + Colorimetry => '4:1:1', + FrameRate => 30000/1001, + ImageHeight => 480, + ImageWidth => 720, + },{ + DSF => 1, + VideoSType => 0x0, + FrameSize => 144000, + VideoFormat => 'IEC 61834 - 625/50 (PAL)', + Colorimetry => '4:2:0', + FrameRate => 25/1, + ImageHeight => 576, + ImageWidth => 720, + },{ + DSF => 1, + VideoSType => 0x0, + FrameSize => 144000, + VideoFormat => 'SMPTE-314M - 625/50 (PAL)', + Colorimetry => '4:1:1', + FrameRate => 25/1, + ImageHeight => 576, + ImageWidth => 720, + },{ + DSF => 0, + VideoSType => 0x4, + FrameSize => 240000, + VideoFormat => 'DVCPRO50: SMPTE-314M - 525/60 (NTSC) 50 Mbps', + Colorimetry => '4:2:2', + FrameRate => 30000/1001, + ImageHeight => 480, + ImageWidth => 720, + },{ + DSF => 1, + VideoSType => 0x4, + FrameSize => 288000, + VideoFormat => 'DVCPRO50: SMPTE-314M - 625/50 (PAL) 50 Mbps', + Colorimetry => '4:2:2', + FrameRate => 25/1, + ImageHeight => 576, + ImageWidth => 720, + },{ + DSF => 0, + VideoSType => 0x14, + FrameSize => 480000, + VideoFormat => 'DVCPRO HD: SMPTE-370M - 1080i60 100 Mbps', + Colorimetry => '4:2:2', + FrameRate => 30000/1001, + ImageHeight => 1080, + ImageWidth => 1280, + },{ + DSF => 1, + VideoSType => 0x14, + FrameSize => 576000, + VideoFormat => 'DVCPRO HD: SMPTE-370M - 1080i50 100 Mbps', + Colorimetry => '4:2:2', + FrameRate => 25/1, + ImageHeight => 1080, + ImageWidth => 1440, + },{ + DSF => 0, + VideoSType => 0x18, + FrameSize => 240000, + VideoFormat => 'DVCPRO HD: SMPTE-370M - 720p60 100 Mbps', + Colorimetry => '4:2:2', + FrameRate => 60000/1001, + ImageHeight => 720, + ImageWidth => 960, + },{ + DSF => 1, + VideoSType => 0x18, + FrameSize => 288000, + VideoFormat => 'DVCPRO HD: SMPTE-370M - 720p50 100 Mbps', + Colorimetry => '4:2:2', + FrameRate => 50/1, + ImageHeight => 720, + ImageWidth => 960, + },{ + DSF => 1, + VideoSType => 0x1, + FrameSize => 144000, + VideoFormat => 'IEC 61883-5 - 625/50 (PAL)', + Colorimetry => '4:2:0', + FrameRate => 25/1, + ImageHeight => 576, + ImageWidth => 720, + }, +); + +# tags to extract, in the order we want to extract them +my @dvTags = ( + 'DateTimeOriginal', 'ImageWidth', 'ImageHeight', 'Duration', + 'TotalBitrate', 'VideoFormat', 'VideoScanType', 'FrameRate', + 'AspectRatio', 'Colorimetry', 'AudioChannels', 'AudioSampleRate', + 'AudioBitsPerSample', +); + +# DV tags +%Image::ExifTool::DV::Main = ( + GROUPS => { 2 => 'Video' }, + VARS => { NO_ID => 1 }, + NOTES => 'The following tags are extracted from DV videos.', + DateTimeOriginal => { + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + ImageWidth => { }, + ImageHeight => { }, + Duration => { PrintConv => 'ConvertDuration($val)' }, + TotalBitrate => { PrintConv => 'ConvertBitrate($val)' }, + VideoFormat => { }, + VideoScanType => { }, + FrameRate => { PrintConv => 'int($val * 1000 + 0.5) / 1000' }, + AspectRatio => { }, + Colorimetry => { }, + AudioChannels => { Groups => { 2 => 'Audio' } }, + AudioSampleRate => { Groups => { 2 => 'Audio' } }, + AudioBitsPerSample => { Groups => { 2 => 'Audio' } }, +); + +#------------------------------------------------------------------------------ +# Read information in a DV file (ref 1) +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid DV file +sub ProcessDV($$) +{ + my ($et, $dirInfo) = @_; + local $_; + my $raf = $$dirInfo{RAF}; + my ($buff, $start, $profile, $tag, $i, $j); + + $raf->Read($buff, 12000) or return 0; + if ($buff =~ /\x1f\x07\0[\x3f\xbf]/sg) { + $start = pos($buff) - 4; + } else { + while ($buff =~ /[\0\xff]\x3f\x07\0.{76}\xff\x3f\x07\x01/sg) { + next if pos($buff) - 163 < 0; + $start = pos($buff) - 163; + last; + } + return 0 unless defined $start; + } + my $len = length $buff; + # must at least have a full DIF header + return 0 if $start + 80 * 6 > $len; + + $et->SetFileType(); + + my $pos = $start; + my $dsf = (Get8u(\$buff, $pos + 3) & 0x80) >> 7; + my $stype = Get8u(\$buff, $pos + 80*5 + 48 + 3) & 0x1f; + + # 576i50 25Mbps 4:1:1 is a special case + if ($dsf == 1 && $stype == 0 && Get8u(\$buff, 4) & 0x07) { + $profile = $dvProfiles[2]; + } else { + foreach (@dvProfiles) { + next unless $dsf == $$_{DSF} and $stype == $$_{VideoSType}; + $profile = $_; + last; + } + $profile or $et->Warn("Unrecognized DV profile"), return 1; + } + my $tagTablePtr = GetTagTable('Image::ExifTool::DV::Main'); + + # calculate total bit rate and duration + my $byteRate = $$profile{FrameSize} * $$profile{FrameRate}; + my $fileSize = $$et{VALUE}{FileSize}; + $$profile{TotalBitrate} = 8 * $byteRate; + $$profile{Duration} = $fileSize / $byteRate if defined $fileSize; + + # read DVPack metadata from the VAUX DIF's to extract video tags + delete $$profile{DateTimeOriginal}; + delete $$profile{AspectRatio}; + delete $$profile{VideoScanType}; + my ($date, $time, $is16_9, $interlace); + for ($i=1; $i<6; ++$i) { + $pos += 80; + my $type = Get8u(\$buff, $pos); + next unless ($type & 0xf0) == 0x50; # look for VAUX types + for ($j=0; $j<15; ++$j) { + my $p = $pos + $j * 5 + 3; + $type = Get8u(\$buff, $p); + if ($type == 0x61) { # video control + my $apt = Get8u(\$buff, $start + 4) & 0x07; + my $t = Get8u(\$buff, $p + 2); + $is16_9 = (($t & 0x07) == 0x02 or (not $apt and ($t & 0x07) == 0x07)); + $interlace = Get8u(\$buff, $p + 3) & 0x10; # (ref 2) + } elsif ($type == 0x62) { # date + # mask off unused bits + my @d = unpack('C*', substr($buff, $p + 1, 4)); + # (ignore timezone in byte 0 until we can test this properly - see ref 2) + $date = sprintf('%.2x:%.2x:%.2x', $d[3], $d[2] & 0x1f, $d[1] & 0x3f); + if ($date =~ /[a-f]/) { + undef $date; # invalid date + } else { + # add century (this will work until 2089) + $date = ($date lt '9' ? '20' : '19') . $date; + } + undef $time; + } elsif ($type == 0x63 and $date) { # time + # (ignore frames past second in byte 0 for now - see ref 2) + my $val = Get32u(\$buff, $p + 1) & 0x007f7f3f; + my @t = unpack('C*', substr($buff, $p + 1, 4)); + $time = sprintf('%.2x:%.2x:%.2x', $t[3] & 0x3f, $t[2] & 0x7f, $t[1] & 0x7f); + last; + } else { + undef $time; # must be consecutive + } + } + } + if ($date and $time) { + $$profile{DateTimeOriginal} = "$date $time"; + if (defined $is16_9) { + $$profile{AspectRatio} = $is16_9 ? '16:9' : '4:3'; + $$profile{VideoScanType} = $interlace ? 'Interlaced' : 'Progressive'; + } + } + + # read audio tags if available + delete $$profile{AudioSampleRate}; + delete $$profile{AudioBitsPerSample}; + delete $$profile{AudioChannels}; + $pos = $start + 80*6 + 80*16*3 + 3; + if ($pos + 4 < $len and Get8u(\$buff, $pos) == 0x50) { + my $smpls = Get8u(\$buff, $pos + 1); + my $freq = (Get8u(\$buff, $pos + 4) >> 3) & 0x07; + my $stype = Get8u(\$buff, $pos + 3) & 0x1f; + my $quant = Get8u(\$buff, $pos + 4) & 0x07; + if ($freq < 3) { + $$profile{AudioSampleRate} = {0=>48000, 1=>44100, 2=>32000}->{$freq}; + } + if ($stype < 3) { + $stype = 2 if $stype == 0 and $quant and $freq == 2; + $$profile{AudioChannels} = {0=>2, 1=>0, 2=>4, 3=>8}->{$stype}; + } + $$profile{AudioBitsPerSample} = $quant ? 12 : 16; + } + + # save our metadata + foreach $tag (@dvTags) { + next unless defined $$profile{$tag}; + $et->HandleTag($tagTablePtr, $tag, $$profile{$tag}); + } + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::DV - Read DV meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read meta +information from DV (raw Digital Video) files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://ffmpeg.org/> + +=item L<http://dvswitch.alioth.debian.org/wiki/DV_format/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/DV Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/DarwinCore.pm b/ExifTool/lib/Image/ExifTool/DarwinCore.pm new file mode 100644 index 0000000..47c0a8a --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/DarwinCore.pm @@ -0,0 +1,393 @@ +#------------------------------------------------------------------------------ +# File: DarwinCore.pm +# +# Description: Darwin Core XMP tags +# +# Revisions: 2013-01-28 - P. Harvey Created +# +# References: 1) http://rs.tdwg.org/dwc/index.htm +# 2) https://exiftool.org/forum/index.php/topic,4442.0/all.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::DarwinCore; + +use strict; +use vars qw($VERSION); +use Image::ExifTool::XMP; + +$VERSION = '1.07'; + +my %dateTimeInfo = ( + # NOTE: Do NOT put "Groups" here because Groups hash must not be common! + Writable => 'date', + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', +); + +my %materialSample = ( + STRUCT_NAME => 'DarwinCore MaterialSample', + NAMESPACE => 'dwc', + materialSampleID => { }, +); + +my %event = ( + STRUCT_NAME => 'DarwinCore Event', + NAMESPACE => 'dwc', + day => { Writable => 'integer', Groups => { 2 => 'Time' } }, + earliestDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + endDayOfYear => { Writable => 'integer', Groups => { 2 => 'Time' } }, + eventDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + eventID => { Avoid => 1, Notes => 'avoided in favor of XMP-iptcExt:EventID' }, + eventRemarks => { Writable => 'lang-alt' }, + eventTime => { + Groups => { 2 => 'Time' }, + Writable => 'string', # (so we can format this ourself) + Shift => 'Time', + # (allow date/time or just time value) + ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + ValueConvInv => 'Image::ExifTool::XMP::FormatXMPDate($val) or $val', + PrintConvInv => q{ + my $v = $self->InverseDateTime($val,undef,1); + undef $Image::ExifTool::evalWarning; + return $v if $v; + # allow time-only values by adding dummy date (thanks Herb) + my $v = $self->InverseDateTime("2000:01:01 $val",undef,1); + undef $Image::ExifTool::evalWarning; + return $v if $v and $v =~ s/.* //; # strip off dummy date + $Image::ExifTool::evalWarning = 'Invalid date/time or time-only value (use HH:MM:SS[.ss][+/-HH:MM|Z])'; + return undef; + }, + }, + fieldNotes => { }, + fieldNumber => { }, + habitat => { }, + latestDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + month => { Writable => 'integer', Groups => { 2 => 'Time' } }, + parentEventID => { }, + samplingEffort => { }, + samplingProtocol => { }, + sampleSizeValue => { }, + sampleSizeUnit => { }, + startDayOfYear => { Writable => 'integer', Groups => { 2 => 'Time' } }, + verbatimEventDate => { Groups => { 2 => 'Time' } }, + year => { Writable => 'integer', Groups => { 2 => 'Time' } }, +); + +# Darwin Core tags +%Image::ExifTool::DarwinCore::Main = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-dwc', 2 => 'Other' }, + NAMESPACE => 'dwc', + WRITABLE => 'string', + NOTES => q{ + Tags defined in the Darwin Core (dwc) XMP namespace. See + L<http://rs.tdwg.org/dwc/index.htm> for the official specification. + }, + Event => { + Name => 'DCEvent', # (avoid conflict with XMP-iptcExt:Event) + FlatName => 'Event', + Struct => \%event, + }, + # tweak a few of the flattened tag names + EventEventDate => { Name => 'EventDate', Flat => 1 }, + EventEventID => { Name => 'EventID', Flat => 1 }, + EventEventRemarks => { Name => 'EventRemarks', Flat => 1 }, + EventEventTime => { Name => 'EventTime', Flat => 1 }, + FossilSpecimen => { Struct => \%materialSample }, + GeologicalContext => { + FlatName => '', # ('GeologicalContext' is too long) + Struct => { + STRUCT_NAME => 'DarwinCore GeologicalContext', + NAMESPACE => 'dwc', + bed => { }, + earliestAgeOrLowestStage => { }, + earliestEonOrLowestEonothem => { }, + earliestEpochOrLowestSeries => { }, + earliestEraOrLowestErathem => { }, + earliestPeriodOrLowestSystem=> { }, + formation => { }, + geologicalContextID => { }, + group => { }, + highestBiostratigraphicZone => { }, + latestAgeOrHighestStage => { }, + latestEonOrHighestEonothem => { }, + latestEpochOrHighestSeries => { }, + latestEraOrHighestErathem => { }, + latestPeriodOrHighestSystem => { }, + lithostratigraphicTerms => { }, + lowestBiostratigraphicZone => { }, + member => { }, + }, + }, + GeologicalContextBed => { Name => 'GeologicalContextBed', Flat => 1 }, + GeologicalContextFormation => { Name => 'GeologicalContextFormation', Flat => 1 }, + GeologicalContextGroup => { Name => 'GeologicalContextGroup', Flat => 1 }, + GeologicalContextMember => { Name => 'GeologicalContextMember', Flat => 1 }, + HumanObservation => { Struct => \%event }, + Identification => { + FlatName => '', # ('Identification' is redundant) + Struct => { + STRUCT_NAME => 'DarwinCore Identification', + NAMESPACE => 'dwc', + dateIdentified => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + identificationID => { }, + identificationQualifier => { }, + identificationReferences => { }, + identificationRemarks => { }, + identificationVerificationStatus => { }, + identifiedBy => { }, + typeStatus => { }, + # new, ref forum13707 + identifiedByID => { }, + verbatimIdentification => { }, + }, + }, + LivingSpecimen => { Struct => \%materialSample }, + MachineObservation => { Struct => \%event }, + MaterialSample => { Struct => \%materialSample }, + MaterialSampleMaterialSampleID => { Name => 'MaterialSampleID', Flat => 1 }, + MeasurementOrFact => { + FlatName => '', # ('MeasurementOrFact' is redundant and too long) + Struct => { + STRUCT_NAME => 'DarwinCore MeasurementOrFact', + NAMESPACE => 'dwc', + measurementAccuracy => { Format => 'real' }, + measurementDeterminedBy => { }, + measurementDeterminedDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + measurementID => { }, + measurementMethod => { }, + measurementRemarks => { }, + measurementType => { }, + measurementUnit => { }, + measurementValue => { }, + }, + }, + Occurrence => { + Struct => { + STRUCT_NAME => 'DarwinCore Occurrence', + NAMESPACE => 'dwc', + associatedMedia => { }, + associatedOccurrences => { }, + associatedReferences => { }, + associatedSequences => { }, + associatedTaxa => { }, + behavior => { }, + catalogNumber => { }, + disposition => { }, + establishmentMeans => { }, + individualCount => { }, + individualID => { }, + lifeStage => { }, + occurrenceDetails => { }, + occurrenceID => { }, + occurrenceRemarks => { }, + occurrenceStatus => { }, + organismQuantity => { }, + organismQuantityType => { }, + otherCatalogNumbers => { }, + preparations => { }, + previousIdentifications => { }, + recordedBy => { }, + recordNumber => { }, + reproductiveCondition => { }, + sex => { }, + # new, ref forum13707 + degreeOfEstablishment => { }, + georeferenceVerificationStatus => { }, + pathway => { }, + recordedByID => { }, + }, + }, + OccurrenceOccurrenceDetails => { Name => 'OccurrenceDetails', Flat => 1 }, + OccurrenceOccurrenceID => { Name => 'OccurrenceID', Flat => 1 }, + OccurrenceOccurrenceRemarks => { Name => 'OccurrenceRemarks', Flat => 1 }, + OccurrenceOccurrenceStatus => { Name => 'OccurrenceStatus', Flat => 1 }, + Organism => { + Struct => { + STRUCT_NAME => 'DarwinCore Organism', + NAMESPACE => 'dwc', + associatedOccurrences => { }, + associatedOrganisms => { }, + organismID => { }, + organismName => { }, + organismRemarks => { }, + organismScope => { }, + previousIdentifications => { }, + }, + }, + OrganismOrganismID => { Name => 'OrganismID', Flat => 1 }, + OrganismOrganismName => { Name => 'OrganismName', Flat => 1 }, + OrganismOrganismRemarks => { Name => 'OrganismRemarks', Flat => 1 }, + OrganismOrganismScope => { Name => 'OrganismScope', Flat => 1 }, + PreservedSpecimen => { Struct => \%materialSample }, + Record => { + Struct => { + STRUCT_NAME => 'DarwinCore Record', + NAMESPACE => 'dwc', + basisOfRecord => { }, + collectionCode => { }, + collectionID => { }, + dataGeneralizations => { }, + datasetID => { }, + datasetName => { }, + dynamicProperties => { }, + informationWithheld => { }, + institutionCode => { }, + institutionID => { }, + ownerInstitutionCode => { }, + }, + }, + ResourceRelationship => { + FlatName => '', # ('ResourceRelationship' is redundant and too long) + Struct => { + STRUCT_NAME => 'DarwinCore ResourceRelationship', + NAMESPACE => 'dwc', + relatedResourceID => { }, + relationshipAccordingTo => { }, + relationshipEstablishedDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + relationshipOfResource => { }, + relationshipRemarks => { }, + resourceID => { }, + resourceRelationshipID => { }, + relationshipOfResourceID => { }, # new, ref forum13707 + }, + }, + Taxon => { + Struct => { + STRUCT_NAME => 'DarwinCore Taxon', + NAMESPACE => 'dwc', + acceptedNameUsage => { }, + acceptedNameUsageID => { }, + class => { }, + family => { }, + genus => { }, + higherClassification => { }, + infraspecificEpithet => { }, + cultivarEpithet => { }, # new, ref forum13707 + kingdom => { }, + nameAccordingTo => { }, + nameAccordingToID => { }, + namePublishedIn => { }, + namePublishedInID => { }, + namePublishedInYear => { }, + nomenclaturalCode => { }, + nomenclaturalStatus => { }, + order => { }, + originalNameUsage => { }, + originalNameUsageID => { }, + parentNameUsage => { }, + parentNameUsageID => { }, + phylum => { }, + scientificName => { }, + scientificNameAuthorship => { }, + scientificNameID => { }, + specificEpithet => { }, + subgenus => { }, + taxonConceptID => { }, + taxonID => { }, + taxonRank => { }, + taxonRemarks => { }, + taxonomicStatus => { }, + verbatimTaxonRank => { }, + vernacularName => { Writable => 'lang-alt' }, + }, + }, + TaxonTaxonConceptID => { Name => 'TaxonConceptID', Flat => 1 }, + TaxonTaxonID => { Name => 'TaxonID', Flat => 1 }, + TaxonTaxonRank => { Name => 'TaxonRank', Flat => 1 }, + TaxonTaxonRemarks => { Name => 'TaxonRemarks', Flat => 1 }, + dctermsLocation => { + Name => 'DCTermsLocation', + Groups => { 2 => 'Location' }, + FlatName => 'DC', # ('dctermsLocation' is too long) + Struct => { + STRUCT_NAME => 'DarwinCore DCTermsLocation', + NAMESPACE => 'dwc', + continent => { }, + coordinatePrecision => { }, + coordinateUncertaintyInMeters => { }, + country => { }, + countryCode => { }, + county => { }, + decimalLatitude => { }, + decimalLongitude => { }, + footprintSpatialFit => { }, + footprintSRS => { }, + footprintWKT => { }, + geodeticDatum => { }, + georeferencedBy => { }, + georeferencedDate => { }, + georeferenceProtocol => { }, + georeferenceRemarks => { }, + georeferenceSources => { }, + georeferenceVerificationStatus => { }, + higherGeography => { }, + higherGeographyID => { }, + island => { }, + islandGroup => { }, + locality => { }, + locationAccordingTo => { }, + locationID => { }, + locationRemarks => { }, + maximumDepthInMeters => { }, + maximumDistanceAboveSurfaceInMeters => { }, + maximumElevationInMeters => { }, + minimumDepthInMeters => { }, + minimumDistanceAboveSurfaceInMeters => { }, + minimumElevationInMeters => { }, + municipality => { }, + pointRadiusSpatialFit => { }, + stateProvince => { }, + verbatimCoordinates => { }, + verbatimCoordinateSystem => { }, + verbatimDepth => { }, + verbatimElevation => { }, + verbatimLatitude => { }, + verbatimLocality => { }, + verbatimLongitude => { }, + verbatimSRS => { }, + waterBody => { }, + # new, ref forum13707 + verticalDatum => { }, + }, + }, +); + +1; #end + +__END__ + +=head1 NAME + +Image::ExifTool::DarwinCore - Darwin Core XMP tags + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This file contains tag definitions for the Darwin Core XMP namespace. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://rs.tdwg.org/dwc/index.htm> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/XMP Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/DjVu.pm b/ExifTool/lib/Image/ExifTool/DjVu.pm new file mode 100644 index 0000000..e901d4c --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/DjVu.pm @@ -0,0 +1,378 @@ +#------------------------------------------------------------------------------ +# File: DjVu.pm +# +# Description: Read DjVu archive meta information +# +# Revisions: 09/25/2008 - P. Harvey Created +# +# References: 1) http://djvu.sourceforge.net/ (DjVu v3 specification, Nov 2005) +# 2) http://www.djvu.org/ +# +# Notes: DjVu files are recognized and the IFF structure is processed +# by Image::ExifTool::AIFF +#------------------------------------------------------------------------------ + +package Image::ExifTool::DjVu; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.07'; + +sub ParseAnt($); +sub ProcessAnt($$$); +sub ProcessMeta($$$); +sub ProcessBZZ($$$); + +# DjVu chunks that we parse (ref 4) +%Image::ExifTool::DjVu::Main = ( + GROUPS => { 2 => 'Image' }, + NOTES => q{ + Information is extracted from the following chunks in DjVu images. See + L<http://www.djvu.org/> for the DjVu specification. + }, + INFO => { + SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Info' }, + }, + FORM => { + TypeOnly => 1, # extract chunk type only, then descend into chunk + SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Form' }, + }, + ANTa => { + SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Ant' }, + }, + ANTz => { + Name => 'CompressedAnnotation', + SubDirectory => { + TagTable => 'Image::ExifTool::DjVu::Ant', + ProcessProc => \&ProcessBZZ, + } + }, + INCL => 'IncludedFileID', +); + +# information in the DjVu INFO chunk +%Image::ExifTool::DjVu::Info = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int8u', + PRIORITY => 0, # first INFO block takes priority + 0 => { + Name => 'ImageWidth', + Format => 'int16u', + }, + 2 => { + Name => 'ImageHeight', + Format => 'int16u', + }, + 4 => { + Name => 'DjVuVersion', + Description => 'DjVu Version', + Format => 'int8u[2]', + # (this may be just one byte as with version 0.16) + ValueConv => '$val=~/(\d+) (\d+)/ ? "$2.$1" : "0.$val"', + }, + 6 => { + Name => 'SpatialResolution', + Format => 'int16u', + ValueConv => '(($val & 0xff)<<8) + ($val>>8)', # (little-endian!) + }, + 8 => { + Name => 'Gamma', + ValueConv => '$val / 10', + }, + 9 => { + Name => 'Orientation', + Mask => 0x07, # (upper 5 bits reserved) + PrintConv => { + 1 => 'Horizontal (normal)', + 2 => 'Rotate 180', + 5 => 'Rotate 90 CW', + 6 => 'Rotate 270 CW', + }, + }, +); + +# information in the DjVu FORM chunk +%Image::ExifTool::DjVu::Form = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'SubfileType', + Format => 'undef[4]', + Priority => 0, + PrintConv => { + DJVU => 'Single-page image', + DJVM => 'Multi-page document', + PM44 => 'Color IW44', + BM44 => 'Grayscale IW44', + DJVI => 'Shared component', + THUM => 'Thumbnail image', + }, + }, +); + +# tags found in the DjVu annotation chunk (ANTz or ANTa) +%Image::ExifTool::DjVu::Ant = ( + PROCESS_PROC => \&Image::ExifTool::DjVu::ProcessAnt, + GROUPS => { 2 => 'Image' }, + NOTES => 'Information extracted from annotation chunks.', + # Note: For speed, ProcessAnt() pre-scans for known tag ID's, so if any + # new tags are added here they must also be added to the pre-scan check + metadata => { + SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Meta' } + }, + xmp => { + Name => 'XMP', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' } + }, +); + +# tags found in the DjVu annotation metadata +%Image::ExifTool::DjVu::Meta = ( + PROCESS_PROC => \&Image::ExifTool::DjVu::ProcessMeta, + GROUPS => { 1 => 'DjVu-Meta', 2 => 'Image' }, + NOTES => q{ + This table lists the standard DjVu metadata tags, but ExifTool will extract + any tags that exist even if they don't appear here. The DjVu v3 + documentation endorses tags borrowed from two standards: 1) BibTeX + bibliography system tags (all lowercase Tag ID's in the table below), and 2) + PDF DocInfo tags (capitalized Tag ID's). + }, + # BibTeX tags (ref http://en.wikipedia.org/wiki/BibTeX) + address => { Groups => { 2 => 'Location' } }, + annote => { Name => 'Annotation' }, + author => { Groups => { 2 => 'Author' } }, + booktitle => { Name => 'BookTitle' }, + chapter => { }, + crossref => { Name => 'CrossRef' }, + edition => { }, + eprint => { Name => 'EPrint' }, + howpublished=> { Name => 'HowPublished' }, + institution => { }, + journal => { }, + key => { }, + month => { Groups => { 2 => 'Time' } }, + note => { }, + number => { }, + organization=> { }, + pages => { }, + publisher => { }, + school => { }, + series => { }, + title => { }, + type => { }, + url => { Name => 'URL' }, + volume => { }, + year => { Groups => { 2 => 'Time' } }, + # PDF tags (same as Image::ExifTool::PDF::Info) + Title => { }, + Author => { Groups => { 2 => 'Author' } }, + Subject => { }, + Keywords => { }, + Creator => { }, + Producer => { }, + CreationDate => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + # RFC 3339 date/time format + ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + ModDate => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + Trapped => { + # remove leading '/' from '/True' or '/False' + ValueConv => '$val=~s{^/}{}; $val', + }, +); + +#------------------------------------------------------------------------------ +# Parse DjVu annotation "s-expression" syntax (recursively) +# Inputs: 0) data ref (with pos($$dataPt) set to start of annotation) +# Returns: reference to list of tokens/references, or undef if no tokens, +# and the position in $$dataPt is set to end of last token +# Notes: The DjVu annotation syntax is not well documented, so I make +# a number of assumptions here! +sub ParseAnt($) +{ + my $dataPt = shift; + my (@toks, $tok, $more); + # (the DjVu annotation syntax really sucks, and requires that every + # single token be parsed in order to properly scan through the items) +Tok: for (;;) { + # find the next token + last unless $$dataPt =~ /(\S)/sg; # get next non-space character + if ($1 eq '(') { # start of list + $tok = ParseAnt($dataPt); + } elsif ($1 eq ')') { # end of list + $more = 1; + last; + } elsif ($1 eq '"') { # quoted string + $tok = ''; + for (;;) { + # get string up to the next quotation mark + # this doesn't work in perl 5.6.2! grrrr + # last Tok unless $$dataPt =~ /(.*?)"/sg; + # $tok .= $1; + my $pos = pos($$dataPt); + last Tok unless $$dataPt =~ /"/sg; + $tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos); + # we're good unless quote was escaped by odd number of backslashes + last unless $tok =~ /(\\+)$/ and length($1) & 0x01; + $tok .= '"'; # quote is part of the string + } + # convert C escape sequences, allowed in quoted text + # (note: this only converts a few of them!) + my %esc = ( a => "\a", b => "\b", f => "\f", n => "\n", + r => "\r", t => "\t", '"' => '"', '\\' => '\\' ); + $tok =~ s/\\(.)/$esc{$1}||'\\'.$1/egs; + } else { # key name + pos($$dataPt) = pos($$dataPt) - 1; + # allow anything in key but whitespace, braces and double quotes + # (this is one of those assumptions I mentioned) + $tok = $$dataPt =~ /([^\s()"]+)/sg ? $1 : undef; + } + push @toks, $tok if defined $tok; + } + # prevent further parsing unless more after this + pos($$dataPt) = length $$dataPt unless $more; + return @toks ? \@toks : undef; +} + +#------------------------------------------------------------------------------ +# Process DjVu annotation chunk (ANTa or decoded ANTz) +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref +# Returns: 1 on success +sub ProcessAnt($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + + # quick pre-scan to check for metadata or XMP + return 1 unless $$dataPt =~ /\(\s*(metadata|xmp)[\s("]/s; + + # parse annotations into a tree structure + pos($$dataPt) = 0; + my $toks = ParseAnt($dataPt) or return 0; + + # process annotations individually + my $ant; + foreach $ant (@$toks) { + next unless ref $ant eq 'ARRAY' and @$ant >= 2; + my $tag = shift @$ant; + next if ref $tag or not defined $$tagTablePtr{$tag}; + if ($tag eq 'metadata') { + # ProcessMeta() takes array reference + $et->HandleTag($tagTablePtr, $tag, $ant); + } else { + next if ref $$ant[0]; # only process simple values + $et->HandleTag($tagTablePtr, $tag, $$ant[0]); + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process DjVu metadata +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref +# Returns: 1 on success +# Notes: input dirInfo DataPt is a reference to a list of pre-parsed metadata entries +sub ProcessMeta($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + return 0 unless ref $$dataPt eq 'ARRAY'; + $et->VerboseDir('Metadata', scalar @$$dataPt); + my ($item, $err); + foreach $item (@$$dataPt) { + # make sure item is a simple tag/value pair + $err=1, next unless ref $item eq 'ARRAY' and @$item >= 2 and + not ref $$item[0] and not ref $$item[1]; + # add any new tags to the table + unless ($$tagTablePtr{$$item[0]}) { + my $name = $$item[0]; + $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters + length $name or $err = 1, next; + AddTagToTable($tagTablePtr, $$item[0], { Name => ucfirst($name) }); + } + $et->HandleTag($tagTablePtr, $$item[0], $$item[1]); + } + $err and $et->Warn('Ignored invalid metadata entry(s)'); + return 1; +} + +#------------------------------------------------------------------------------ +# Process BZZ-compressed data (in DjVu images) +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref +# Returns: 1 on success +sub ProcessBZZ($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + require Image::ExifTool::BZZ; + my $buff = Image::ExifTool::BZZ::Decode($$dirInfo{DataPt}); + unless (defined $buff) { + $et->Warn("Error decoding $$dirInfo{DirName}"); + return 0; + } + my $verbose = $et->Options('Verbose'); + if ($verbose >= 3) { + # dump the decoded data in very verbose mode + $et->VerboseDir("Decoded $$dirInfo{DirName}", 0, length $buff); + $et->VerboseDump(\$buff); + } + $$dirInfo{DataPt} = \$buff; + $$dirInfo{DataLen} = $$dirInfo{DirLen} = length $buff; + # process the data using the default process proc for this table + my $processProc = $$tagTablePtr{PROCESS_PROC} or return 0; + return &$processProc($et, $dirInfo, $tagTablePtr); +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::DjVu - Read DjVu meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from DjVu images. Parsing of the DjVu IFF structure is done by +Image::ExifTool::AIFF. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://djvu.sourceforge.net/> + +=item L<http://www.djvu.org/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/DjVu Tags>, +L<Image::ExifTool::AIFF(3pm)|Image::ExifTool::AIFF>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/EXE.pm b/ExifTool/lib/Image/ExifTool/EXE.pm new file mode 100644 index 0000000..8172eaf --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/EXE.pm @@ -0,0 +1,1440 @@ +#------------------------------------------------------------------------------ +# File: EXE.pm +# +# Description: Read meta information of various executable file formats +# +# Revisions: 2008/08/28 - P. Harvey Created +# 2011/07/12 - P. Harvey Added CHM (ok, not EXE, but it fits here) +# +# References: 1) http://www.openwatcom.org/ftp/devel/docs/pecoff.pdf +# 2) http://support.microsoft.com/kb/65122 +# 3) http://www.opensource.apple.com +# 4) http://www.skyfree.org/linux/references/ELF_Format.pdf +# 5) http://msdn.microsoft.com/en-us/library/ms809762.aspx +# 6) http://code.google.com/p/pefile/ +# 7) http://www.codeproject.com/KB/DLL/showver.aspx +#------------------------------------------------------------------------------ + +package Image::ExifTool::EXE; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.18'; + +sub ProcessPEResources($$); +sub ProcessPEVersion($$); + +# PE file resource types (ref 6) +my %resourceType = ( + 1 => 'Cursor', + 2 => 'Bitmap', + 3 => 'Icon', + 4 => 'Menu', + 5 => 'Dialog', + 6 => 'String', + 7 => 'Font Dir', + 8 => 'Font', + 9 => 'Accelerator', + 10 => 'RC Data', + 11 => 'Message Table', + 12 => 'Group Cursor', + 14 => 'Group Icon', + 16 => 'Version', + 17 => 'Dialog Include', + 19 => 'Plug-n-Play', + 20 => 'VxD', + 21 => 'Animated Cursor', + 22 => 'Animated Icon', + 23 => 'HTML', + 24 => 'Manifest', +); + +my %languageCode = ( + Notes => q{ + See L<https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid> + for the full list of Microsoft language codes. + }, + '0000' => 'Neutral', + '007F' => 'Invariant', + '0400' => 'Process default', + '0401' => 'Arabic', + '0402' => 'Bulgarian', + '0403' => 'Catalan', + '0404' => 'Chinese (Traditional)', + '0405' => 'Czech', + '0406' => 'Danish', + '0407' => 'German', + '0408' => 'Greek', + '0409' => 'English (U.S.)', + '040A' => 'Spanish (Castilian)', + '040B' => 'Finnish', + '040C' => 'French', + '040D' => 'Hebrew', + '040E' => 'Hungarian', + '040F' => 'Icelandic', + '0410' => 'Italian', + '0411' => 'Japanese', + '0412' => 'Korean', + '0413' => 'Dutch', + '0414' => 'Norwegian (Bokml)', + '0415' => 'Polish', + '0416' => 'Portuguese (Brazilian)', + '0417' => 'Rhaeto-Romanic', + '0418' => 'Romanian', + '0419' => 'Russian', + '041A' => 'Croato-Serbian (Latin)', + '041B' => 'Slovak', + '041C' => 'Albanian', + '041D' => 'Swedish', + '041E' => 'Thai', + '041F' => 'Turkish', + '0420' => 'Urdu', + # 0421-0493 ref 6 + '0421' => 'Indonesian', + '0422' => 'Ukrainian', + '0423' => 'Belarusian', + '0424' => 'Slovenian', + '0425' => 'Estonian', + '0426' => 'Latvian', + '0427' => 'Lithuanian', + '0428' => 'Maori', + '0429' => 'Farsi', + '042a' => 'Vietnamese', + '042b' => 'Armenian', + '042c' => 'Azeri', + '042d' => 'Basque', + '042e' => 'Sorbian', + '042f' => 'Macedonian', + '0430' => 'Sutu', + '0431' => 'Tsonga', + '0432' => 'Tswana', + '0433' => 'Venda', + '0434' => 'Xhosa', + '0435' => 'Zulu', + '0436' => 'Afrikaans', + '0437' => 'Georgian', + '0438' => 'Faeroese', + '0439' => 'Hindi', + '043a' => 'Maltese', + '043b' => 'Saami', + '043c' => 'Gaelic', + '043e' => 'Malay', + '043f' => 'Kazak', + '0440' => 'Kyrgyz', + '0441' => 'Swahili', + '0443' => 'Uzbek', + '0444' => 'Tatar', + '0445' => 'Bengali', + '0446' => 'Punjabi', + '0447' => 'Gujarati', + '0448' => 'Oriya', + '0449' => 'Tamil', + '044a' => 'Telugu', + '044b' => 'Kannada', + '044c' => 'Malayalam', + '044d' => 'Assamese', + '044e' => 'Marathi', + '044f' => 'Sanskrit', + '0450' => 'Mongolian', + '0456' => 'Galician', + '0457' => 'Konkani', + '0458' => 'Manipuri', + '0459' => 'Sindhi', + '045a' => 'Syriac', + '0460' => 'Kashmiri', + '0461' => 'Nepali', + '0465' => 'Divehi', + '047f' => 'Invariant', + '048f' => 'Esperanto', + '0490' => 'Walon', + '0491' => 'Cornish', + '0492' => 'Welsh', + '0493' => 'Breton', + '0800' => 'Neutral 2', + '0804' => 'Chinese (Simplified)', + '0807' => 'German (Swiss)', + '0809' => 'English (British)', + '080A' => 'Spanish (Mexican)', + '080C' => 'French (Belgian)', + '0810' => 'Italian (Swiss)', + '0813' => 'Dutch (Belgian)', + '0814' => 'Norwegian (Nynorsk)', + '0816' => 'Portuguese', + '081A' => 'Serbo-Croatian (Cyrillic)', + '0C07' => 'German (Austrian)', + '0C09' => 'English (Australian)', + '0C0A' => 'Spanish (Modern)', + '0C0C' => 'French (Canadian)', + '1009' => 'English (Canadian)', + '100C' => 'French (Swiss)', +); + +# Information extracted from PE COFF (Windows EXE) file header +%Image::ExifTool::EXE::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + FORMAT => 'int16u', + NOTES => q{ + This module extracts information from various types of Windows, MacOS and + Unix executable and library files. The first table below lists information + extracted from the header of Windows PE (Portable Executable) EXE files and + DLL libraries. + }, + 0 => { + Name => 'MachineType', + PrintHex => 1, + PrintConv => { + 0x014c => 'Intel 386 or later, and compatibles', + 0x014d => 'Intel i860', #5 + 0x0162 => 'MIPS R3000', + 0x0166 => 'MIPS little endian (R4000)', + 0x0168 => 'MIPS R10000', + 0x0169 => 'MIPS little endian WCI v2', + 0x0183 => 'Alpha AXP (old)', #5 + 0x0184 => 'Alpha AXP', + 0x01a2 => 'Hitachi SH3', + 0x01a3 => 'Hitachi SH3 DSP', + 0x01a6 => 'Hitachi SH4', + 0x01a8 => 'Hitachi SH5', + 0x01c0 => 'ARM little endian', + 0x01c2 => 'Thumb', + 0x01d3 => 'Matsushita AM33', + 0x01f0 => 'PowerPC little endian', + 0x01f1 => 'PowerPC with floating point support', + 0x0200 => 'Intel IA64', + 0x0266 => 'MIPS16', + 0x0268 => 'Motorola 68000 series', + 0x0284 => 'Alpha AXP 64-bit', + 0x0366 => 'MIPS with FPU', + 0x0466 => 'MIPS16 with FPU', + 0x0ebc => 'EFI Byte Code', + 0x8664 => 'AMD AMD64', + 0x9041 => 'Mitsubishi M32R little endian', + 0xc0ee => 'clr pure MSIL', + }, + }, + 2 => { + Name => 'TimeStamp', + Format => 'int32u', + Groups => { 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 9 => { + Name => 'ImageFileCharacteristics', + # ref https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_image_file_header + PrintConv => { BITMASK => { + 0 => 'No relocs', + 1 => 'Executable', + 2 => 'No line numbers', + 3 => 'No symbols', + 4 => 'Aggressive working-set trim', + 5 => 'Large address aware', + 7 => 'Bytes reversed lo', + 8 => '32-bit', + 9 => 'No debug', + 10 => 'Removable run from swap', + 11 => 'Net run from swap', + 12 => 'System file', + 13 => 'DLL', + 14 => 'Uniprocessor only', + 15 => 'Bytes reversed hi', + }}, + }, + 10 => { + Name => 'PEType', + PrintHex => 1, + PrintConv => { + 0x107 => 'ROM Image', + 0x10b => 'PE32', + 0x20b => 'PE32+', + }, + }, + 11 => { + Name => 'LinkerVersion', + Format => 'int8u[2]', + ValueConv => '$val=~tr/ /./; $val', + }, + 12 => { + Name => 'CodeSize', + Format => 'int32u', + }, + 14 => { + Name => 'InitializedDataSize', + Format => 'int32u', + }, + 16 => { + Name => 'UninitializedDataSize', + Format => 'int32u', + }, + 18 => { + Name => 'EntryPoint', + Format => 'int32u', + PrintConv => 'sprintf("0x%.4x", $val)', + }, + 30 => { + Name => 'OSVersion', + Format => 'int16u[2]', + ValueConv => '$val=~tr/ /./; $val', + }, + 32 => { + Name => 'ImageVersion', + Format => 'int16u[2]', + ValueConv => '$val=~tr/ /./; $val', + }, + 34 => { + Name => 'SubsystemVersion', + Format => 'int16u[2]', + ValueConv => '$val=~tr/ /./; $val', + }, + 44 => { + Name => 'Subsystem', + PrintConv => { + 0 => 'Unknown', + 1 => 'Native', + 2 => 'Windows GUI', + 3 => 'Windows command line', + 5 => 'OS/2 command line', #5 + 7 => 'POSIX command line', + 9 => 'Windows CE GUI', + 10 => 'EFI application', + 11 => 'EFI boot service', + 12 => 'EFI runtime driver', + 13 => 'EFI ROM', #6 + 14 => 'XBOX', #6 + }, + }, +); + +# PE file version information (ref 6) +%Image::ExifTool::EXE::PEVersion = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + FORMAT => 'int32u', + NOTES => q{ + Information extracted from the VS_VERSION_INFO structure of Windows PE + files. + }, + # (boring -- always 0xfeef04bd) + #0 => { + # Name => 'Signature', + # PrintConv => 'sprintf("0x%.4x",$val)', + #}, + # (boring -- always 1.0) + #1 => { + # Name => 'StructVersion', + # Format => 'int16u[2]', + # ValueConv => 'my @a=split(" ",$val); "$a[1].$a[0]"', + #}, + 2 => { + Name => 'FileVersionNumber', + Format => 'int16u[4]', + ValueConv => 'my @a=split(" ",$val); "$a[1].$a[0].$a[3].$a[2]"', + }, + 4 => { + Name => 'ProductVersionNumber', + Format => 'int16u[4]', + ValueConv => 'my @a=split(" ",$val); "$a[1].$a[0].$a[3].$a[2]"', + }, + 6 => { + Name => 'FileFlagsMask', + PrintConv => 'sprintf("0x%.4x",$val)', + }, + 7 => { # ref Cygwin /usr/include/w32api/winver.h + Name => 'FileFlags', + PrintConv => { BITMASK => { + 0 => 'Debug', + 1 => 'Pre-release', + 2 => 'Patched', + 3 => 'Private build', + 4 => 'Info inferred', + 5 => 'Special build', + }}, + }, + 8 => { + Name => 'FileOS', + PrintHex => 1, + PrintConv => { # ref Cygwin /usr/include/w32api/winver.h + 0x00001 => 'Win16', + 0x00002 => 'PM-16', + 0x00003 => 'PM-32', + 0x00004 => 'Win32', + 0x10000 => 'DOS', + 0x20000 => 'OS/2 16-bit', + 0x30000 => 'OS/2 32-bit', + 0x40000 => 'Windows NT', + 0x10001 => 'Windows 16-bit', + 0x10004 => 'Windows 32-bit', + 0x20002 => 'OS/2 16-bit PM-16', + 0x30003 => 'OS/2 32-bit PM-32', + 0x40004 => 'Windows NT 32-bit', + }, + }, + 9 => { # ref Cygwin /usr/include/w32api/winver.h + Name => 'ObjectFileType', + PrintConv => { + 0 => 'Unknown', + 1 => 'Executable application', + 2 => 'Dynamic link library', + 3 => 'Driver', + 4 => 'Font', + 5 => 'VxD', + 7 => 'Static library', + }, + }, + 10 => 'FileSubtype', + # (these are usually zero, so ignore them) + # 11 => 'FileDateMS', + # 12 => 'FileDateLS', +); + +# Windows PE StringFileInfo resource strings +# (see http://msdn.microsoft.com/en-us/library/aa381049.aspx) +%Image::ExifTool::EXE::PEString = ( + GROUPS => { 2 => 'Other' }, + VARS => { NO_ID => 1 }, + NOTES => q{ + Resource strings found in Windows PE files. The B<TagID>'s are not shown + because they are the same as the B<Tag Name>. ExifTool will extract any + existing StringFileInfo tags even if not listed in this table. + }, + LanguageCode => { + Notes => 'Windows code page; extracted from the StringFileInfo value', + # ref http://techsupt.winbatch.com/TS/T000001050F49.html + # (also see http://support.bigfix.com/fixlet/documents/WinInspectors-2006-08-10.pdf) + # (also see ftp://ftp.dyu.edu.tw/pub/cpatch/faq/tech/tech_nlsnt.txt) + # (not a complete set) + PrintString => 1, + SeparateTable => 1, + PrintConv => \%languageCode, + }, + CharacterSet => { + Notes => 'extracted from the StringFileInfo value', + # ref http://techsupt.winbatch.com/TS/T000001050F49.html + # (also see http://blog.chinaunix.net/u1/41189/showart_345768.html) + PrintString => 1, + PrintConv => { + '0000' => 'ASCII', + '03A4' => 'Windows, Japan (Shift - JIS X-0208)', # cp932 + '03A8' => 'Windows, Chinese (Simplified)', # cp936 + '03B5' => 'Windows, Korea (Shift - KSC 5601)', # cp949 + '03B6' => 'Windows, Taiwan (Big5)', # cp950 + '04B0' => 'Unicode', # UCS-2 + '04E2' => 'Windows, Latin2 (Eastern European)', + '04E3' => 'Windows, Cyrillic', + '04E4' => 'Windows, Latin1', + '04E5' => 'Windows, Greek', + '04E6' => 'Windows, Turkish', + '04E7' => 'Windows, Hebrew', + '04E8' => 'Windows, Arabic', + }, + }, + BuildDate => { Groups => { 2 => 'Time' } }, # (non-standard) + BuildVersion => { }, # (non-standard) + Comments => { }, + CompanyName => { }, + Copyright => { }, # (non-standard) + FileDescription => { }, + FileVersion => { }, + InternalName => { }, + LegalCopyright => { }, + LegalTrademarks => { }, + OriginalFilename=> { Name => 'OriginalFileName' }, + PrivateBuild => { }, + ProductName => { }, + ProductVersion => { }, + SpecialBuild => { }, +); + +# Information extracted from Mach-O (Mac OS X) file header +%Image::ExifTool::EXE::MachO = ( + GROUPS => { 2 => 'Other' }, + VARS => { ID_LABEL => 'Index' }, + NOTES => q{ + Information extracted from Mach-O (Mac OS X) executable files and DYLIB + libraries. + }, + # ref http://www.opensource.apple.com/darwinsource/DevToolsOct2007/cctools-622.9/include/mach/machine.h + 0 => 'CPUArchitecture', + 1 => 'CPUByteOrder', + 2 => 'CPUCount', + # ref /System/Library/Frameworks/Kernel.framework/Versions/A/Headers/mach/machine.h + 3 => { + Name => 'CPUType', + List => 1, + PrintConv => { + # handle 64-bit flag (0x1000000) + OTHER => sub { + my ($val, $inv, $conv) = @_; + my $v = $val & 0xfeffffff; + return $$conv{$v} ? "$$conv{$v} 64-bit" : "Unknown ($val)"; + }, + -1 => 'Any', + 1 => 'VAX', + 2 => 'ROMP', + 4 => 'NS32032', + 5 => 'NS32332', + 6 => 'MC680x0', + 7 => 'x86', + 8 => 'MIPS', + 9 => 'NS32532', + 10 => 'MC98000', + 11 => 'HPPA', + 12 => 'ARM', + 13 => 'MC88000', + 14 => 'SPARC', + 15 => 'i860 big endian', + 16 => 'i860 little endian', + 17 => 'RS6000', + 18 => 'PowerPC', + 255 => 'VEO', + }, + }, + # ref /System/Library/Frameworks/Kernel.framework/Versions/A/Headers/mach/machine.h + 4 => { + Name => 'CPUSubtype', + List => 1, + PrintConv => { + # handle 64-bit flags on CPUType (0x1000000) and CPUSubtype (0x80000000) + OTHER => sub { + my ($val, $inv, $conv) = @_; + my @v = split ' ', $val; + my $v = ($v[0] & 0xfeffffff) . ' ' . ($v[1] & 0x7fffffff); + return $$conv{$v} ? "$$conv{$v} 64-bit" : "Unknown ($val)"; + }, + # in theory, subtype can be -1 for multiple CPU types, + # but in practice I'm not sure anyone uses this - PH + '1 0' => 'VAX (all)', + '1 1' => 'VAX780', + '1 2' => 'VAX785', + '1 3' => 'VAX750', + '1 4' => 'VAX730', + '1 5' => 'UVAXI', + '1 6' => 'UVAXII', + '1 7' => 'VAX8200', + '1 8' => 'VAX8500', + '1 9' => 'VAX8600', + '1 10' => 'VAX8650', + '1 11' => 'VAX8800', + '1 12' => 'UVAXIII', + '2 0' => 'RT (all)', + '2 1' => 'RT PC', + '2 2' => 'RT APC', + '2 3' => 'RT 135', + # 32032/32332/32532 subtypes. + '4 0' => 'NS32032 (all)', + '4 1' => 'NS32032 DPC (032 CPU)', + '4 2' => 'NS32032 SQT', + '4 3' => 'NS32032 APC FPU (32081)', + '4 4' => 'NS32032 APC FPA (Weitek)', + '4 5' => 'NS32032 XPC (532)', + '5 0' => 'NS32332 (all)', + '5 1' => 'NS32332 DPC (032 CPU)', + '5 2' => 'NS32332 SQT', + '5 3' => 'NS32332 APC FPU (32081)', + '5 4' => 'NS32332 APC FPA (Weitek)', + '5 5' => 'NS32332 XPC (532)', + '6 1' => 'MC680x0 (all)', + '6 2' => 'MC68040', + '6 3' => 'MC68030', + '7 3' => 'i386 (all)', + '7 4' => 'i486', + '7 132' => 'i486SX', + '7 5' => 'i586', + '7 22' => 'Pentium Pro', + '7 54' => 'Pentium II M3', + '7 86' => 'Pentium II M5', + '7 103' => 'Celeron', + '7 119' => 'Celeron Mobile', + '7 8' => 'Pentium III', + '7 24' => 'Pentium III M', + '7 40' => 'Pentium III Xeon', + '7 9' => 'Pentium M', + '7 10' => 'Pentium 4', + '7 26' => 'Pentium 4 M', + '7 11' => 'Itanium', + '7 27' => 'Itanium 2', + '7 12' => 'Xeon', + '7 28' => 'Xeon MP', + '8 0' => 'MIPS (all)', + '8 1' => 'MIPS R2300', + '8 2' => 'MIPS R2600', + '8 3' => 'MIPS R2800', + '8 4' => 'MIPS R2000a', + '8 5' => 'MIPS R2000', + '8 6' => 'MIPS R3000a', + '8 7' => 'MIPS R3000', + '10 0' => 'MC98000 (all)', + '10 1' => 'MC98601', + '11 0' => 'HPPA (all)', + '11 1' => 'HPPA 7100LC', + '12 0' => 'ARM (all)', + '12 1' => 'ARM A500 ARCH', + '12 2' => 'ARM A500', + '12 3' => 'ARM A440', + '12 4' => 'ARM M4', + '12 5' => 'ARM A680/V4T', + '12 6' => 'ARM V6', + '12 7' => 'ARM V5TEJ', + '12 8' => 'ARM XSCALE', + '12 9' => 'ARM V7', + '13 0' => 'MC88000 (all)', + '13 1' => 'MC88100', + '13 2' => 'MC88110', + '14 0' => 'SPARC (all)', + '14 1' => 'SUN 4/260', + '14 2' => 'SUN 4/110', + '15 0' => 'i860 (all)', + '15 1' => 'i860 860', + '16 0' => 'i860 little (all)', + '16 1' => 'i860 little', + '17 0' => 'RS6000 (all)', + '17 1' => 'RS6000', + '18 0' => 'PowerPC (all)', + '18 1' => 'PowerPC 601', + '18 2' => 'PowerPC 602', + '18 3' => 'PowerPC 603', + '18 4' => 'PowerPC 603e', + '18 5' => 'PowerPC 603ev', + '18 6' => 'PowerPC 604', + '18 7' => 'PowerPC 604e', + '18 8' => 'PowerPC 620', + '18 9' => 'PowerPC 750', + '18 10' => 'PowerPC 7400', + '18 11' => 'PowerPC 7450', + '18 100' => 'PowerPC 970', + '255 1' => 'VEO 1', + '255 2' => 'VEO 2', + }, + }, + 5 => { + Name => 'ObjectFileType', + PrintHex => 1, + # ref https://svn.red-bean.com/pyobjc/branches/pyobjc-20x-branch/macholib/macholib/mach_o.py + PrintConv => { + -1 => 'Static library', #PH (internal use only) + 1 => 'Relocatable object', + 2 => 'Demand paged executable', + 3 => 'Fixed VM shared library', + 4 => 'Core', + 5 => 'Preloaded executable', + 6 => 'Dynamically bound shared library', + 7 => 'Dynamic link editor', + 8 => 'Dynamically bound bundle', + 9 => 'Shared library stub for static linking', + # (the following from Apple loader.h header file) + 10 => 'Debug information', + 11 => 'x86_64 kexts', + }, + }, + 6 => { + Name => 'ObjectFlags', + PrintHex => 1, + # ref Apple loader.h header file + PrintConv => { BITMASK => { + 0 => 'No undefs', + 1 => 'Incrementa link', + 2 => 'Dyld link', + 3 => 'Bind at load', + 4 => 'Prebound', + 5 => 'Split segs', + 6 => 'Lazy init', + 7 => 'Two level', + 8 => 'Force flat', + 9 => 'No multi defs', + 10 => 'No fix prebinding', + 11 => 'Prebindable', + 12 => 'All mods bound', + 13 => 'Subsections via symbols', + 14 => 'Canonical', + 15 => 'Weak defines', + 16 => 'Binds to weak', + 17 => 'Allow stack execution', + 18 => 'Dead strippable dylib', + 19 => 'Root safe', + 20 => 'No reexported dylibs', + 21 => 'Random address', + }}, + }, +); + +# Information extracted from PEF (Classic MacOS executable) file header +%Image::ExifTool::EXE::PEF = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + NOTES => q{ + Information extracted from PEF (Classic MacOS) executable files and + libraries. + }, + FORMAT => 'int32u', + 2 => { + Name => 'CPUArchitecture', + Format => 'undef[4]', + PrintConv => { + pwpc => 'PowerPC', + m68k => '68000', + }, + }, + 3 => 'PEFVersion', + 4 => { + Name => 'TimeStamp', + Groups => { 2 => 'Time' }, + # timestamp is relative to Jan 1, 1904 + ValueConv => 'ConvertUnixTime($val - ((66 * 365 + 17) * 24 * 3600))', + PrintConv => '$self->ConvertDateTime($val)', + }, + #5 => 'OldDefVersion', + #6 => 'OldImpVersion', + #7 => 'CurrentVersion', +); + +# Information extracted from ELF (Unix executable) file header +%Image::ExifTool::EXE::ELF = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + NOTES => q{ + Information extracted from ELF (Unix) executable files and SO libraries. + }, + 4 => { + Name => 'CPUArchitecture', + PrintConv => { + 1 => '32 bit', + 2 => '64 bit', + }, + }, + 5 => { + Name => 'CPUByteOrder', + PrintConv => { + 1 => 'Little endian', + 2 => 'Big endian', + }, + }, + 16 => { + Name => 'ObjectFileType', + Format => 'int16u', + PrintConv => { + 0 => 'None', + 1 => 'Relocatable file', + 2 => 'Executable file', + 3 => 'Shared object file', + 4 => 'Core file', + }, + }, + 18 => { + Name => 'CPUType', + Format => 'int16u', + # ref /usr/include/linux/elf-em.h + PrintConv => { + 0 => 'None', + 1 => 'AT&T WE 32100', + 2 => 'SPARC', + 3 => 'i386', + 4 => 'Motorola 68000', + 5 => 'Motorola 88000', + 6 => 'i486', + 7 => 'i860', + 8 => 'MIPS R3000', + 10 => 'MIPS R4000', + 15 => 'HPPA', + 18 => 'Sun v8plus', + 20 => 'PowerPC', + 21 => 'PowerPC 64-bit', + 22 => 'IBM S/390', + 23 => 'Cell BE SPU', + 42 => 'SuperH', + 43 => 'SPARC v9 64-bit', + 46 => 'Renesas H8/300,300H,H8S', + 50 => 'HP/Intel IA-64', + 62 => 'AMD x86-64', + 76 => 'Axis Communications 32-bit embedded processor', + 87 => 'NEC v850', + 88 => 'Renesas M32R', + 0x5441 => 'Fujitsu FR-V', + 0x9026 => 'Alpha', # (interim value) + 0x9041 => 'm32r (old)', + 0x9080 => 'v850 (old)', + 0xa390 => 'S/390 (old)', + }, + }, +); + +# Information extracted from static library archives +# (ref http://opensource.apple.com//source/xnu/xnu-1456.1.26/EXTERNAL_HEADERS/ar.h) +%Image::ExifTool::EXE::AR = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + NOTES => q{ + Information extracted from static libraries. + }, + # 0 string[16] ar_name + 16 => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + Format => 'string[12]', + ValueConv => 'ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + # 28 string[6] ar_uid + # 34 string[6] ar_gid + # 40 string[8] ar_mode + # 48 string[10] ar_size + # 58 string[2] terminator "`\n" +); + +# Microsoft compiled help format (ref http://www.russotto.net/chm/chmformat.html) +%Image::ExifTool::EXE::CHM = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + NOTES => 'Tags extracted from Microsoft Compiled HTML files.', + FORMAT => 'int32u', + 1 => { Name => 'CHMVersion' }, + # 2 - total header length + # 3 - 1 + # 4 - low bits of date/time value plus 42 (ref http://www.nongnu.org/chmspec/latest/ITSF.html) + 5 => { + Name => 'LanguageCode', + SeparateTable => 1, + ValueConv => 'sprintf("%.4X", $val)', + PrintConv => \%languageCode, + }, +); + +#------------------------------------------------------------------------------ +# Extract information from a CHM file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid CHM file +sub ProcessCHM($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + + return 0 unless $raf->Read($buff, 56) == 56 and + $buff =~ /^ITSF.{20}\x10\xfd\x01\x7c\xaa\x7b\xd0\x11\x9e\x0c\0\xa0\xc9\x22\xe6\xec/s; + my $tagTablePtr = GetTagTable('Image::ExifTool::EXE::CHM'); + $et->SetFileType(); + SetByteOrder('II'); + $et->ProcessDirectory({ DataPt => \$buff }, $tagTablePtr); + return 1; +} + +#------------------------------------------------------------------------------ +# Read Unicode string (null terminated) from resource +# Inputs: 0) data ref, 1) start offset, 2) data end, 3) optional ExifTool object ref +# Returns: 0) Unicode string translated to UTF8, or current CharSet with ExifTool ref +# 1) end pos (rounded up to nearest 4 bytes) +sub ReadUnicodeStr($$$;$) +{ + my ($dataPt, $pos, $end, $et) = @_; + $end = length $$dataPt if $end > length $$dataPt; # (be safe) + my $str = ''; + while ($pos + 2 <= $end) { + my $ch = substr($$dataPt, $pos, 2); + $pos += 2; + last if $ch eq "\0\0"; + $str .= $ch; + } + $pos += 2 if $pos & 0x03; + my $to = $et ? $et->Options('Charset') : 'UTF8'; + return (Image::ExifTool::Decode(undef,$str,'UCS2','II',$to), $pos); +} + +#------------------------------------------------------------------------------ +# Process Windows PE Version Resource +# Inputs: 0) ExifTool object ref, 1) dirInfo ref +# Returns: true on success +sub ProcessPEVersion($$) +{ + my ($et, $dirInfo) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $end = $pos + $$dirInfo{DirLen}; + my ($index, $len, $valLen, $type, $string, $strEnd); + + # get VS_VERSION_INFO + for ($index = 0; ; ++$index) { + $pos = ($pos + 3) & 0xfffffffc; # align on a 4-byte boundary + last if $pos + 6 > $end; + $len = Get16u($dataPt, $pos); + $valLen = Get16u($dataPt, $pos + 2); + $type = Get16u($dataPt, $pos + 4); + return 0 unless $len or $valLen; # prevent possible infinite loop + ($string, $strEnd) = ReadUnicodeStr($dataPt, $pos + 6, $pos + $len); + return 0 if $strEnd + $valLen > $end; + unless ($index or $string eq 'VS_VERSION_INFO') { + $et->Warn('Invalid Version Info block'); + return 0; + } + if ($string eq 'VS_VERSION_INFO') { + # parse the fixed version info + $$dirInfo{DirStart} = $strEnd; + $$dirInfo{DirLen} = $valLen; + my $subTablePtr = GetTagTable('Image::ExifTool::EXE::PEVersion'); + $et->ProcessDirectory($dirInfo, $subTablePtr); + $pos = $strEnd + $valLen; + } elsif ($string eq 'StringFileInfo' and $valLen == 0) { + $pos += $len; + my $pt = $strEnd; + # parse string table + my $tagTablePtr = GetTagTable('Image::ExifTool::EXE::PEString'); + for ($index = 0; $pt + 6 < $pos; ++$index) { + $len = Get16u($dataPt, $pt); + $valLen = Get16u($dataPt, $pt + 2); + # $type = Get16u($dataPt, $pt + 4); + my $entryEnd = $pt + $len; + # get tag ID (converted to UTF8) + ($string, $pt) = ReadUnicodeStr($dataPt, $pt + 6, $entryEnd); + unless ($index) { + # separate the language code and character set + # (not sure what the CharacterSet tag is for, but the string + # values stored here are UCS-2 in all my files even if the + # CharacterSet is otherwise) + my $char; + if (length($string) > 4) { + $char = substr($string, 4); + $string = substr($string, 0, 4); + } + $et->HandleTag($tagTablePtr, 'LanguageCode', uc $string); + $et->HandleTag($tagTablePtr, 'CharacterSet', uc $char) if $char; + next; + } + my $tag = $string; + # create entry in tag table if it doesn't already exist + unless ($$tagTablePtr{$tag}) { + my $name = $tag; + $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters + next unless length $name; + AddTagToTable($tagTablePtr, $tag, { Name => $name }); + } + # get tag value (converted to current Charset) + if ($valLen) { + ($string, $pt) = ReadUnicodeStr($dataPt, $pt, $entryEnd, $et); + } else { + $string = ''; + } + $et->HandleTag($tagTablePtr, $tag, $string); + # step to next entry (padded to an even word) + $pt = ($entryEnd + 3) & 0xfffffffc; + } + } else { + $pos += $len + $valLen; + # ignore other information (for now) + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process Windows PE Resources +# Inputs: 0) ExifTool object ref, 1) dirInfo ref +# Returns: true on success +sub ProcessPEResources($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $base = $$dirInfo{Base}; + my $dirStart = $$dirInfo{DirStart} + $base; + my $level = $$dirInfo{Level} || 0; + my $verbose = $et->Options('Verbose'); + my ($buff, $buf2, $item); + + return 0 if $level > 10; # protect against deep recursion + # read the resource header + $raf->Seek($dirStart, 0) and $raf->Read($buff, 16) == 16 or return 0; + my $nameEntries = Get16u(\$buff, 12); + my $idEntries = Get16u(\$buff, 14); + my $count = $nameEntries + $idEntries; + $raf->Read($buff, $count * 8) == $count * 8 or return 0; + # loop through all resource entries + for ($item=0; $item<$count; ++$item) { + my $pos = $item * 8; + my $name = Get32u(\$buff, $pos); + my $entryPos = Get32u(\$buff, $pos + 4); + unless ($level) { + # set resource type if this is the 0th level directory + my $resType = $resourceType{$name} || sprintf('Unknown (0x%x)', $name); + # ignore everything but the Version resource unless verbose + if ($verbose) { + $et->VPrint(0, "$resType resource:\n"); + } else { + next unless $resType eq 'Version'; + } + $$dirInfo{ResType} = $resType; + } + if ($entryPos & 0x80000000) { # is this a directory? + # descend into next directory level + $$dirInfo{DirStart} = $entryPos & 0x7fffffff; + $$dirInfo{Level} = $level + 1; + ProcessPEResources($et, $dirInfo) or return 0; + --$$dirInfo{Level}; + } elsif ($$dirInfo{ResType} eq 'Version' and $level == 2 and + not $$dirInfo{GotVersion}) # (only process first Version resource) + { + # get position of this resource in the file + my $buf2; + $raf->Seek($entryPos + $base, 0) and $raf->Read($buf2, 16) == 16 or return 0; + my $off = Get32u(\$buf2, 0); + my $len = Get32u(\$buf2, 4); + # determine which section this is in so we can convert the virtual address + my ($section, $filePos); + foreach $section (@{$$dirInfo{Sections}}) { + next unless $off >= $$section{VirtualAddress} and + $off < $$section{VirtualAddress} + $$section{Size}; + $filePos = $off + $$section{Base} - $$section{VirtualAddress}; + last; + } + return 0 unless $filePos; + $raf->Seek($filePos, 0) and $raf->Read($buf2, $len) == $len or return 0; + ProcessPEVersion($et, { + DataPt => \$buf2, + DataLen => $len, + DirStart => 0, + DirLen => $len, + }) or $et->Warn('Possibly corrupt Version resource'); + $$dirInfo{GotVersion} = 1; # set flag so we don't do this again + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process Windows PE file data dictionary +# Inputs: 0) ExifTool object ref, 1) dirInfo ref +# Returns: true on success +sub ProcessPEDict($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = length($$dataPt); + my ($pos, @sections, %dirInfo, $rsrcFound); + + # loop through all sections + for ($pos=0; $pos+40<=$dirLen; $pos+=40) { + my $name = substr($$dataPt, $pos, 8); + my $va = Get32u($dataPt, $pos + 12); + my $size = Get32u($dataPt, $pos + 16); + my $offset = Get32u($dataPt, $pos + 20); + # remember the section offsets for the VirtualAddress lookup later + push @sections, { Base => $offset, Size => $size, VirtualAddress => $va }; + # save details of the first resource section (or .text if .rsrc not found, ref forum11465) + next unless ($name eq ".rsrc\0\0\0" and not $rsrcFound and defined($rsrcFound = 1)) or + ($name eq ".text\0\0\0" and not %dirInfo); + %dirInfo = ( + RAF => $raf, + Base => $offset, + DirStart => 0, # (relative to Base) + DirLen => $size, + Sections => \@sections, + ); + } + # process the first resource section + ProcessPEResources($et, \%dirInfo) or return 0 if %dirInfo; + return 1; +} + +#------------------------------------------------------------------------------ +# Override file type if necessary for Mach object files and libraries +# Inputs: 0) ExifTool ref, 1) ObjectFileType number, 2) flag for fat binary +my %machOverride = ( + 1 => [ 'object file', 'O' ], + 6 => [ 'dynamic link library', 'DYLIB' ], + 8 => [ 'dynamic bound bundle', 'DYLIB' ], + 9 => [ 'dynamic link library stub', 'DYLIB' ], +); +sub MachOverride($$;$) +{ + my ($et, $objType, $fat) = @_; + my $override = $machOverride{$objType}; + if ($override) { + my $desc = 'Mach-O ' . ($fat ? 'fat ' : '') . $$override[0]; + $et->OverrideFileType($desc, undef, $$override[1]); + } +} + +#------------------------------------------------------------------------------ +# Extract tags from Mach header +# Inputs: 0) ExifTool ref, 1) data ref, 2) flag to extract object type +# Returns: true if Mach header was found +# Mach type based on magic number +# [bit depth, byte order starting with "Little" or "Big"] +my %machType = ( + "\xfe\xed\xfa\xce" => ['32 bit', 'Big endian'], + "\xce\xfa\xed\xfe" => ['32 bit', 'Little endian'], + "\xfe\xed\xfa\xcf" => ['64 bit', 'Big endian'], + "\xcf\xfa\xed\xfe" => ['64 bit', 'Little endian'], +); +sub ExtractMachTags($$;$) +{ + my ($et, $dataPt, $doObj) = @_; + # get information about mach header based on the magic number (first 4 bytes) + my $info = $machType{substr($$dataPt, 0, 4)}; + if ($info) { + # Mach header structure: + # 0 int32u magic + # 4 int32u cputype + # 8 int32u cpusubtype + # 12 int32u filetype + # 16 int32u ncmds + # 20 int32u sizeofcmds + # 24 int32u flags + my $tagTablePtr = GetTagTable('Image::ExifTool::EXE::MachO'); + SetByteOrder($$info[1]); + my $cpuType = Get32s($dataPt, 4); + my $subType = Get32s($dataPt, 8); + $et->HandleTag($tagTablePtr, 0, $$info[0]); + $et->HandleTag($tagTablePtr, 1, $$info[1]); + $et->HandleTag($tagTablePtr, 3, $cpuType); + $et->HandleTag($tagTablePtr, 4, "$cpuType $subType"); + if ($doObj) { + my $objType = Get32u($dataPt, 12); + my $flags = Get32u($dataPt, 24); + $et->HandleTag($tagTablePtr, 5, $objType); + $et->HandleTag($tagTablePtr, 6, $flags); + # override file type if this is an object file or library + MachOverride($et, $objType); + } else { # otherwise this was a static library + $et->OverrideFileType('Mach-O static library', undef, 'A'); + } + return 1; + } + return 0; +} + +#------------------------------------------------------------------------------ +# Extract information from an EXE file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid EXE file +sub ProcessEXE($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $buf2, $type, $mime, $ext, $tagTablePtr, %dirInfo); + + my $size = $raf->Read($buff, 0x40) or return 0; + my $fast3 = $$et{OPTIONS}{FastScan} && $$et{OPTIONS}{FastScan} == 3; +# +# DOS and Windows EXE +# + if ($buff =~ /^MZ/ and $size == 0x40) { + # DOS/Windows executable + # validate DOS header + # (ref http://www.delphidabbler.com/articles?article=8&part=2) + # 0 int16u magic - Magic number ("MZ") + # 2 int16u cblp - Bytes on last page of file + # 4 int16u cp - Pages in file + # 6 int16u crlc - Relocations + # 8 int16u cparhdr - Size of header in paragraphs + # 10 int16u minalloc - Minimum extra paragraphs needed + # 12 int16u maxalloc - Maximum extra paragraphs needed + # 14 int16u ss - Initial (relative) SS value + # 16 int16u sp - Initial SP value + # 18 int16u csum - Checksum + # 20 int16u ip - Initial IP value + # 22 int16u cs - Initial (relative) CS value + # 24 int16u lfarlc - Address of relocation table + # 26 int16u ovno - Overlay number + # 28 int16u[4] res - Reserved words + # 36 int16u oemid - OEM identifier (for oeminfo) + # 38 int16u oeminfo - OEM info; oemid specific + # 40 int16u[10] res2 - Reserved words + # 60 int32u; lfanew - File address of new exe header + SetByteOrder('II'); + my ($cblp, $cp, $lfarlc, $lfanew) = unpack('x2v2x18vx34V', $buff); + my $fileSize = ($cp - ($cblp ? 1 : 0)) * 512 + $cblp; + #(patch to accommodate observed 64-bit files) + #return 0 if $fileSize < 0x40 or $fileSize < $lfarlc; + #return 0 if $fileSize < 0x40; (changed to warning in ExifTool 12.08) + $et->Warn('Invalid file size in DOS header') if $fileSize < 0x40; + # read the Windows NE, PE or LE (virtual device driver) header + #if ($lfarlc == 0x40 and $fileSize > $lfanew + 2 and ... + if ($raf->Seek($lfanew, 0) and $raf->Read($buff, 0x40) and $buff =~ /^(NE|PE|LE)/) { + if ($1 eq 'NE') { + if ($size >= 0x40) { # NE header is 64 bytes (ref 2) + # check for DLL + my $appFlags = Get16u(\$buff, 0x0c); + $ext = $appFlags & 0x80 ? 'DLL' : 'EXE'; + $type = "Win16 $ext"; + # offset 0x02 is 2 bytes with linker version and revision numbers + # offset 0x36 is executable type (2 = Windows) + } + } elsif ($1 eq 'PE') { + # PE header comes at byte 4 in buff: + # 4 int16u Machine + # 6 int16u NumberOfSections + # 8 int32u TimeDateStamp + # 12 int32u PointerToSymbolTable + # 16 int32u NumberOfSymbols + # 20 int16u SizeOfOptionalHeader + # 22 int16u Characteristics + if ($size >= 24) { # PE header is 24 bytes (plus optional header) + my $mach = Get16u(\$buff, 4); # MachineType + my $flags = Get16u(\$buff, 22); # ImageFileCharacteristics + my $machine = $Image::ExifTool::EXE::Main{0}{PrintConv}{$mach} || ''; + my $winType = $machine =~ /64/ ? 'Win64' : 'Win32'; + $ext = $flags & 0x2000 ? 'DLL' : 'EXE'; + $et->SetFileType("$winType $ext", undef, $ext); + return 1 if $fast3; + # read the rest of the optional header if necessary + my $optSize = Get16u(\$buff, 20); + my $more = $optSize + 24 - $size; + if ($more > 0) { + if ($raf->Read($buf2, $more) == $more) { + $buff .= $buf2; + $size += $more; + my $magic = Get16u(\$buff, 24); + # verify PE magic number + unless ($magic == 0x107 or $magic == 0x10b or $magic == 0x20b) { + $et->Warn('Unknown PE magic number'); + return 1; + } + # --> 64-bit if $magic is 0x20b ???? + } else { + $et->Warn('Error reading optional header'); + } + } + # process PE COFF file header + $tagTablePtr = GetTagTable('Image::ExifTool::EXE::Main'); + %dirInfo = ( + DataPt => \$buff, + DataPos => $raf->Tell() - $size, + DataLen => $size, + DirStart => 4, + DirLen => $size - 4, + ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + # process data dictionary + my $num = Get16u(\$buff, 6); # NumberOfSections + if ($raf->Read($buff, 40 * $num) == 40 * $num) { + %dirInfo = ( + RAF => $raf, + DataPt => \$buff, + ); + ProcessPEDict($et, \%dirInfo) or $et->Warn('Error processing PE data dictionary'); + } + return 1; + } + } else { + $type = 'Virtual Device Driver'; + $ext = '386'; + } + } else { + $type = 'DOS EXE'; + $ext = 'exe'; + } +# +# Mach-O (Mac OS X) +# + } elsif ($buff =~ /^(\xca\xfe\xba\xbe|\xfe\xed\xfa(\xce|\xcf)|(\xce|\xcf)\xfa\xed\xfe)/ and $size > 12) { + # Mach-O executable + # (ref http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html) + $tagTablePtr = GetTagTable('Image::ExifTool::EXE::MachO'); + if ($1 eq "\xca\xfe\xba\xbe") { + SetByteOrder('MM'); + my $ver = Get32u(\$buff, 4); + # Java bytecode .class files have the same magic number, so we need to look deeper + # (ref https://github.com/file/file/blob/master/magic/Magdir/cafebabe#L6-L15) + if ($ver > 30) { + # this is Java bytecode + $et->SetFileType('Java bytecode', 'application/java-byte-code', 'class'); + return 1; + } + $et->SetFileType('Mach-O fat binary executable', undef, ''); + return 1 if $fast3; + my $count = Get32u(\$buff, 4); # get architecture count + my $more = $count * 20 - ($size - 8); + if ($more > 0) { + unless ($raf->Read($buf2, $more) == $more) { + $et->Warn('Error reading fat-arch headers'); + return 1; + } + $buff .= $buf2; + $size += $more; + } + $et->HandleTag($tagTablePtr, 2, $count); + my $i; + for ($i=0; $i<$count; ++$i) { + my $cpuType = Get32s(\$buff, 8 + $i * 20); + my $subType = Get32s(\$buff, 12 + $i * 20); + $et->HandleTag($tagTablePtr, 3, $cpuType); + $et->HandleTag($tagTablePtr, 4, "$cpuType $subType"); + } + # load first Mach-O header to get the object file type + my $offset = Get32u(\$buff, 16); + if ($raf->Seek($offset, 0) and $raf->Read($buf2, 16) == 16) { + if ($buf2 =~ /^(\xfe\xed\xfa(\xce|\xcf)|(\xce|\xcf)\xfa\xed\xfe)/) { + SetByteOrder($buf2 =~ /^\xfe\xed/ ? 'MM' : 'II'); + my $objType = Get32u(\$buf2, 12); + $et->HandleTag($tagTablePtr, 5, $objType); + # override file type if this is a library or object file + MachOverride($et, $objType, 'fat'); + } elsif ($buf2 =~ /^!<arch>\x0a/) { + # .a libraries use this magic number + $et->HandleTag($tagTablePtr, 5, -1); + # override file type since this is a library + $et->OverrideFileType('Mach-O fat static library', undef, 'A'); + } else { + $et->Warn('Unrecognized object file type'); + } + } else { + $et->Warn('Error reading file'); + } + } elsif ($size >= 16) { + $et->SetFileType('Mach-O executable', undef, ''); + return 1 if $fast3; + ExtractMachTags($et, \$buff, 1); + } + return 1; +# +# PEF (classic MacOS) +# + } elsif ($buff =~ /^Joy!peff/ and $size > 12) { + # ref http://developer.apple.com/documentation/mac/pdf/MacOS_RT_Architectures.pdf + $et->SetFileType('Classic MacOS executable', undef, ''); + return 1 if $fast3; + SetByteOrder('MM'); + $tagTablePtr = GetTagTable('Image::ExifTool::EXE::PEF'); + %dirInfo = ( + DataPt => \$buff, + DataPos => 0, + DataLen => $size, + DirStart => 0, + DirLen => $size, + ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + return 1; +# +# ELF (Unix) +# + } elsif ($buff =~ /^\x7fELF/ and $size >= 16) { + $et->SetFileType('ELF executable', undef, ''); + return 1 if $fast3; + SetByteOrder(Get8u(\$buff,5) == 1 ? 'II' : 'MM'); + $tagTablePtr = GetTagTable('Image::ExifTool::EXE::ELF'); + %dirInfo = ( + DataPt => \$buff, + DataPos => 0, + DataLen => $size, + DirLen => $size, + ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + # override file type if this is a library or object file + my $override = { + 1 => [ 'ELF object file', 'O' ], + 3 => [ 'ELF shared library', 'SO' ], + }->{$$et{VALUE}{ObjectFileType} || 0}; + $et->OverrideFileType($$override[0], undef, $$override[1]) if $override; + return 1; +# +# .a libraries +# + } elsif ($buff =~ /^!<arch>\x0a/) { + $et->SetFileType('Static library', undef, 'A'); + return 1 if $fast3; + my $pos = 8; # current file position + my $max = 10; # maximum number of archive files to check + # read into list of ar structures (each 60 bytes long): + while ($max-- > 0) { + # seek to start of the ar structure and read it + $raf->Seek($pos, 0) and $raf->Read($buff, 60) == 60 or last; + substr($buff, 58, 2) eq "`\n" or $et->Warn('Invalid archive header'), last; + unless ($tagTablePtr) { + # extract some information from first file in archive + $tagTablePtr = GetTagTable('Image::ExifTool::EXE::AR'); + %dirInfo = ( + DataPt => \$buff, + DataPos => $pos, + ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + my $name = substr($buff, 0, 16); + if ($name =~ m{^#1/(\d+) *$}) { # check for extended archive (BSD variant) + my $len = $1; + $len > 256 and $et->Warn('Invalid extended archive name length'), last; + # (we read the name here just to move the file pointer) + $raf->Read($name, $len) == $len or $et->Warn('Error reading archive name'), last; + } + my $arSize = substr($buff, 48, 10); + $arSize =~ s/^(\d+).*/$1/s or last; # make sure archive size is a number + $raf->Read($buff, 28) == 28 or last; # read (possible) Mach header + ExtractMachTags($et, \$buff) and last; # try to extract tags + $pos += 60 + $arSize; # step to next entry + ++$pos if $pos & 0x01; # padded to an even byte + } + return 1; +# +# various scripts (perl, sh, etc...) +# + } elsif ($buff =~ m{^#!\s*/\S*bin/(\w+)}) { + my $prog = $1; + $prog = $1 if $prog eq 'env' and $buff =~ /\b(perl|python|ruby|php)\b/; + $type = "$prog script"; + $mime = "text/x-$prog"; + $ext = { + perl => 'pl', + python => 'py', + ruby => 'rb', + php => 'php', + }->{$prog}; + # use '.sh' for extension of all shell scripts + $ext = $prog =~ /sh$/ ? 'sh' : '' unless defined $ext; + } + return 0 unless $type; + $et->SetFileType($type, $mime, $ext); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::EXE - Read executable file meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from various types of Windows, MacOS and Unix executable and +library files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.openwatcom.org/ftp/devel/docs/pecoff.pdf> + +=item L<http://support.microsoft.com/kb/65122> + +=item L<http://www.opensource.apple.com> + +=item L<http://www.skyfree.org/linux/references/ELF_Format.pdf> + +=item L<http://msdn.microsoft.com/en-us/library/ms809762.aspx> + +=item L<http://code.google.com/p/pefile/> + +=item L<http://www.codeproject.com/KB/DLL/showver.aspx> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/EXE Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Exif.pm b/ExifTool/lib/Image/ExifTool/Exif.pm new file mode 100644 index 0000000..55b4115 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Exif.pm @@ -0,0 +1,6948 @@ +#------------------------------------------------------------------------------ +# File: Exif.pm +# +# Description: Read EXIF/TIFF meta information +# +# Revisions: 11/25/2003 - P. Harvey Created +# 02/06/2004 - P. Harvey Moved processing functions from ExifTool +# 03/19/2004 - P. Harvey Check PreviewImage for validity +# 11/11/2004 - P. Harvey Split off maker notes into MakerNotes.pm +# 12/13/2004 - P. Harvey Added AUTOLOAD to load write routines +# +# References: 0) http://www.exif.org/Exif2-2.PDF +# 1) http://partners.adobe.com/asn/developer/pdfs/tn/TIFF6.pdf +# 2) http://www.adobe.com/products/dng/pdfs/dng_spec_1_3_0_0.pdf +# 3) http://www.awaresystems.be/imaging/tiff/tifftags.html +# 4) http://www.remotesensing.org/libtiff/TIFFTechNote2.html +# 5) http://www.exif.org/dcf.PDF +# 6) http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html +# 7) http://www.fine-view.com/jp/lab/doc/ps6ffspecsv2.pdf +# 8) http://www.ozhiker.com/electronics/pjmt/jpeg_info/meta.html +# 9) http://hul.harvard.edu/jhove/tiff-tags.html +# 10) http://partners.adobe.com/public/developer/en/tiff/TIFFPM6.pdf +# 11) Robert Mucke private communication +# 12) http://www.broomscloset.com/closet/photo/exif/TAG2000-22_DIS12234-2.PDF +# 13) http://www.microsoft.com/whdc/xps/wmphoto.mspx +# 14) http://www.asmail.be/msg0054681802.html +# 15) http://crousseau.free.fr/imgfmt_raw.htm +# 16) http://www.cybercom.net/~dcoffin/dcraw/ +# 17) http://www.digitalpreservation.gov/formats/content/tiff_tags.shtml +# 18) http://www.asmail.be/msg0055568584.html +# 19) http://libpsd.graphest.com/files/Photoshop%20File%20Formats.pdf +# 20) http://tiki-lounge.com/~raf/tiff/fields.html +# 21) http://community.roxen.com/developers/idocs/rfc/rfc3949.html +# 22) http://tools.ietf.org/html/draft-ietf-fax-tiff-fx-extension1-01 +# 23) MetaMorph Stack (STK) Image File Format: +# --> ftp://ftp.meta.moleculardevices.com/support/stack/STK.doc +# 24) http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf (Exif 2.3) +# 25) Vesa Kivisto private communication (7D) +# 26) Jeremy Brown private communication +# 27) Gregg Lee private communication +# 28) http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/cinemadng/pdfs/CinemaDNG_Format_Specification_v1_1.pdf +# 29) http://www.libtiff.org +# 30) http://geotiff.maptools.org/spec/geotiffhome.html +# 31) https://android.googlesource.com/platform/external/dng_sdk/+/refs/heads/master/source/dng_tag_codes.h +# 32) Jeffry Friedl private communication +# 33) https://www.cipa.jp/std/documents/download_e.html?DC-008-Translation-2023-E (Exif 3.0) +# IB) Iliah Borg private communication (LibRaw) +# JD) Jens Duttke private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::Exif; + +use strict; +use vars qw($VERSION $AUTOLOAD @formatSize @formatName %formatNumber %intFormat + %lightSource %flash %compression %photometricInterpretation %orientation + %subfileType %saveForValidate); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::MakerNotes; + +$VERSION = '4.44'; + +sub ProcessExif($$$); +sub WriteExif($$$); +sub CheckExif($$$); +sub RebuildMakerNotes($$$); +sub EncodeExifText($$); +sub ValidateIFD($;$); +sub ValidateImageData($$$;$); +sub AddImageDataHash($$$); +sub ProcessTiffIFD($$$); +sub PrintParameter($$$); +sub GetOffList($$$$$); +sub PrintOpcode($$$); +sub PrintLensInfo($); +sub InverseOffsetTime($$); +sub ConvertLensInfo($); + +# size limit for loading binary data block into memory +sub BINARY_DATA_LIMIT { return 10 * 1024 * 1024; } + +# byte sizes for the various EXIF format types below +@formatSize = (undef,1,1,2,4,8,1,1,2,4,8,4,8,4,2,8,8,8,8); +$formatSize[129] = 1; # (Exif 3.0) + +@formatName = ( + undef, 'int8u', 'string', 'int16u', + 'int32u', 'rational64u','int8s', 'undef', + 'int16s', 'int32s', 'rational64s','float', + 'double', 'ifd', 'unicode', 'complex', + 'int64u', 'int64s', 'ifd64', # (new BigTIFF formats) +); +$formatName[129] = 'utf8'; # (Exif 3.0) + +# hash to look up EXIF format numbers by name +# (format types are all lower case) +%formatNumber = ( + 'int8u' => 1, # BYTE + 'string' => 2, # ASCII + 'int16u' => 3, # SHORT + 'int32u' => 4, # LONG + 'rational64u' => 5, # RATIONAL + 'int8s' => 6, # SBYTE + 'undef' => 7, # UNDEFINED + 'binary' => 7, # (same as undef) + 'int16s' => 8, # SSHORT + 'int32s' => 9, # SLONG + 'rational64s' => 10, # SRATIONAL + 'float' => 11, # FLOAT + 'double' => 12, # DOUBLE + 'ifd' => 13, # IFD (with int32u format) + 'unicode' => 14, # UNICODE [see Note below] + 'complex' => 15, # COMPLEX [see Note below] + 'int64u' => 16, # LONG8 [BigTIFF] + 'int64s' => 17, # SLONG8 [BigTIFF] + 'ifd64' => 18, # IFD8 (with int64u format) [BigTIFF] + 'utf8' => 129,# UTF-8 (Exif 3.0) + # Note: unicode and complex types are not yet properly supported by ExifTool. + # These are types which have been observed in the Adobe DNG SDK code, but + # aren't fully supported there either. We know the sizes, but that's about it. + # We don't know if the unicode is null terminated, or the format for complex + # (although I suspect it would be two 4-byte floats, real and imaginary). +); + +# lookup for integer format strings +%intFormat = ( + 'int8u' => 1, + 'int16u' => 3, + 'int32u' => 4, + 'int8s' => 6, + 'int16s' => 8, + 'int32s' => 9, + 'ifd' => 13, + 'int64u' => 16, + 'int64s' => 17, + 'ifd64' => 18, +); + +# EXIF LightSource PrintConv values +%lightSource = ( + 0 => 'Unknown', + 1 => 'Daylight', + 2 => 'Fluorescent', + 3 => 'Tungsten (Incandescent)', + 4 => 'Flash', + 9 => 'Fine Weather', + 10 => 'Cloudy', + 11 => 'Shade', + 12 => 'Daylight Fluorescent', # (D 5700 - 7100K) + 13 => 'Day White Fluorescent', # (N 4600 - 5500K) + 14 => 'Cool White Fluorescent', # (W 3800 - 4500K) + 15 => 'White Fluorescent', # (WW 3250 - 3800K) + 16 => 'Warm White Fluorescent', # (L 2600 - 3250K) + 17 => 'Standard Light A', + 18 => 'Standard Light B', + 19 => 'Standard Light C', + 20 => 'D55', + 21 => 'D65', + 22 => 'D75', + 23 => 'D50', + 24 => 'ISO Studio Tungsten', + 255 => 'Other', +); + +# EXIF Flash values +%flash = ( + OTHER => sub { + # translate "Off" and "On" when writing + my ($val, $inv) = @_; + return undef unless $inv and $val =~ /^(off|on)$/i; + return lc $val eq 'off' ? 0x00 : 0x01; + }, + 0x00 => 'No Flash', + 0x01 => 'Fired', + 0x05 => 'Fired, Return not detected', + 0x07 => 'Fired, Return detected', + 0x08 => 'On, Did not fire', # not charged up? + 0x09 => 'On, Fired', + 0x0d => 'On, Return not detected', + 0x0f => 'On, Return detected', + 0x10 => 'Off, Did not fire', + 0x14 => 'Off, Did not fire, Return not detected', + 0x18 => 'Auto, Did not fire', + 0x19 => 'Auto, Fired', + 0x1d => 'Auto, Fired, Return not detected', + 0x1f => 'Auto, Fired, Return detected', + 0x20 => 'No flash function', + 0x30 => 'Off, No flash function', + 0x41 => 'Fired, Red-eye reduction', + 0x45 => 'Fired, Red-eye reduction, Return not detected', + 0x47 => 'Fired, Red-eye reduction, Return detected', + 0x49 => 'On, Red-eye reduction', + 0x4d => 'On, Red-eye reduction, Return not detected', + 0x4f => 'On, Red-eye reduction, Return detected', + 0x50 => 'Off, Red-eye reduction', + 0x58 => 'Auto, Did not fire, Red-eye reduction', + 0x59 => 'Auto, Fired, Red-eye reduction', + 0x5d => 'Auto, Fired, Red-eye reduction, Return not detected', + 0x5f => 'Auto, Fired, Red-eye reduction, Return detected', +); + +# TIFF Compression values +# (values with format "Xxxxx XXX Compressed" are used to identify RAW file types) +%compression = ( + 1 => 'Uncompressed', + 2 => 'CCITT 1D', + 3 => 'T4/Group 3 Fax', + 4 => 'T6/Group 4 Fax', + 5 => 'LZW', + 6 => 'JPEG (old-style)', #3 + 7 => 'JPEG', #4 + 8 => 'Adobe Deflate', #3 + 9 => 'JBIG B&W', #3 + 10 => 'JBIG Color', #3 + 99 => 'JPEG', #16 + 262 => 'Kodak 262', #16 + 32766 => 'Next', #3 + 32767 => 'Sony ARW Compressed', #16 + 32769 => 'Packed RAW', #PH (used by Epson, Nikon, Samsung) + 32770 => 'Samsung SRW Compressed', #PH + 32771 => 'CCIRLEW', #3 + 32772 => 'Samsung SRW Compressed 2', #PH (NX3000,NXmini) + 32773 => 'PackBits', + 32809 => 'Thunderscan', #3 + 32867 => 'Kodak KDC Compressed', #PH + 32895 => 'IT8CTPAD', #3 + 32896 => 'IT8LW', #3 + 32897 => 'IT8MP', #3 + 32898 => 'IT8BL', #3 + 32908 => 'PixarFilm', #3 + 32909 => 'PixarLog', #3 + # 32910,32911 - Pixar reserved + 32946 => 'Deflate', #3 + 32947 => 'DCS', #3 + 33003 => 'Aperio JPEG 2000 YCbCr', #https://openslide.org/formats/aperio/ + 33005 => 'Aperio JPEG 2000 RGB', #https://openslide.org/formats/aperio/ + 34661 => 'JBIG', #3 + 34676 => 'SGILog', #3 + 34677 => 'SGILog24', #3 + 34712 => 'JPEG 2000', #3 + 34713 => 'Nikon NEF Compressed', #PH + 34715 => 'JBIG2 TIFF FX', #20 + 34718 => 'Microsoft Document Imaging (MDI) Binary Level Codec', #18 + 34719 => 'Microsoft Document Imaging (MDI) Progressive Transform Codec', #18 + 34720 => 'Microsoft Document Imaging (MDI) Vector', #18 + 34887 => 'ESRI Lerc', #LibTiff + # 34888,34889 - ESRI reserved + 34892 => 'Lossy JPEG', # (DNG 1.4) + 34925 => 'LZMA2', #LibTiff + 34926 => 'Zstd', #LibTiff + 34927 => 'WebP', #LibTiff + 34933 => 'PNG', # (TIFF mail list) + 34934 => 'JPEG XR', # (TIFF mail list) + 65000 => 'Kodak DCR Compressed', #PH + 65535 => 'Pentax PEF Compressed', #Jens +); + +%photometricInterpretation = ( + 0 => 'WhiteIsZero', + 1 => 'BlackIsZero', + 2 => 'RGB', + 3 => 'RGB Palette', + 4 => 'Transparency Mask', + 5 => 'CMYK', + 6 => 'YCbCr', + 8 => 'CIELab', + 9 => 'ICCLab', #3 + 10 => 'ITULab', #3 + 32803 => 'Color Filter Array', #2 + 32844 => 'Pixar LogL', #3 + 32845 => 'Pixar LogLuv', #3 + 32892 => 'Sequential Color Filter', #JR (Sony ARQ) + 34892 => 'Linear Raw', #2 + 51177 => 'Depth Map', # (DNG 1.5) + 52527 => 'Semantic Mask', # (DNG 1.6) +); + +%orientation = ( + 1 => 'Horizontal (normal)', + 2 => 'Mirror horizontal', + 3 => 'Rotate 180', + 4 => 'Mirror vertical', + 5 => 'Mirror horizontal and rotate 270 CW', + 6 => 'Rotate 90 CW', + 7 => 'Mirror horizontal and rotate 90 CW', + 8 => 'Rotate 270 CW', +); + +%subfileType = ( + 0 => 'Full-resolution image', + 1 => 'Reduced-resolution image', + 2 => 'Single page of multi-page image', + 3 => 'Single page of multi-page reduced-resolution image', + 4 => 'Transparency mask', + 5 => 'Transparency mask of reduced-resolution image', + 6 => 'Transparency mask of multi-page image', + 7 => 'Transparency mask of reduced-resolution multi-page image', + 8 => 'Depth map', # (DNG 1.5) + 9 => 'Depth map of reduced-resolution image', # (DNG 1.5) + 16 => 'Enhanced image data', # (DNG 1.5) + 0x10001 => 'Alternate reduced-resolution image', # (DNG 1.2) + 0x10004 => 'Semantic Mask', # (DNG 1.6) + 0xffffffff => 'invalid', #(found in E5700 NEF's) + BITMASK => { + 0 => 'Reduced resolution', + 1 => 'Single page', + 2 => 'Transparency mask', + 3 => 'TIFF/IT final page', #20 (repurposed as DepthMap repurposes by DNG 1.5) + 4 => 'TIFF-FX mixed raster content', #20 (repurposed as EnhancedImageData by DNG 1.5) + }, +); + +# PrintConv for parameter tags +%Image::ExifTool::Exif::printParameter = ( + PrintConv => { + 0 => 'Normal', + OTHER => \&Image::ExifTool::Exif::PrintParameter, + }, +); + +# convert DNG UTF-8 string values (may be string or int8u format) +my %utf8StringConv = ( + Writable => 'string', + Format => 'string', + ValueConv => '$self->Decode($val, "UTF8")', + ValueConvInv => '$self->Encode($val,"UTF8")', +); + +# ValueConv that makes long values binary type +my %longBin = ( + ValueConv => 'length($val) > 64 ? \$val : $val', + ValueConvInv => '$val', + LongBinary => 1, # flag to avoid decoding values of a large array +); + +# PrintConv for SampleFormat (0x153) +my %sampleFormat = ( + 1 => 'Unsigned', # unsigned integer + 2 => 'Signed', # two's complement signed integer + 3 => 'Float', # IEEE floating point + 4 => 'Undefined', + 5 => 'Complex int', # complex integer (ref 3) + 6 => 'Complex float', # complex IEEE floating point (ref 3) +); + +# save the values of these tags for additional validation checks +%saveForValidate = ( + 0x100 => 1, # ImageWidth + 0x101 => 1, # ImageHeight + 0x102 => 1, # BitsPerSample + 0x103 => 1, # Compression + 0x115 => 1, # SamplesPerPixel +); + +# conversions for DNG OpcodeList tags +my %opcodeInfo = ( + Writable => 'undef', + WriteGroup => 'SubIFD', + Protected => 1, + Binary => 1, + ConvertBinary => 1, # needed because the ValueConv value is binary + PrintConvColumns => 2, + PrintConv => { + OTHER => \&PrintOpcode, + 1 => 'WarpRectilinear', + 2 => 'WarpFisheye', + 3 => 'FixVignetteRadial', + 4 => 'FixBadPixelsConstant', + 5 => 'FixBadPixelsList', + 6 => 'TrimBounds', + 7 => 'MapTable', + 8 => 'MapPolynomial', + 9 => 'GainMap', + 10 => 'DeltaPerRow', + 11 => 'DeltaPerColumn', + 12 => 'ScalePerRow', + 13 => 'ScalePerColumn', + 14 => 'WarpRectilinear2', # (DNG 1.6) + }, + PrintConvInv => undef, # (so the inverse conversion is not performed) +); + +# main EXIF tag table +%Image::ExifTool::Exif::Main = ( + GROUPS => { 0 => 'EXIF', 1 => 'IFD0', 2 => 'Image'}, + WRITE_PROC => \&WriteExif, + CHECK_PROC => \&CheckExif, + WRITE_GROUP => 'ExifIFD', # default write group + SET_GROUP1 => 1, # set group1 name to directory name for all tags in table + 0x1 => { + Name => 'InteropIndex', + Description => 'Interoperability Index', + Protected => 1, + Writable => 'string', + WriteGroup => 'InteropIFD', + PrintConv => { + R98 => 'R98 - DCF basic file (sRGB)', + R03 => 'R03 - DCF option file (Adobe RGB)', + THM => 'THM - DCF thumbnail file', + }, + }, + 0x2 => { #5 + Name => 'InteropVersion', + Description => 'Interoperability Version', + Protected => 1, + Writable => 'undef', + Mandatory => 1, + WriteGroup => 'InteropIFD', + RawConv => '$val=~s/\0+$//; $val', # (some idiots add null terminators) + }, + 0x0b => { #PH + Name => 'ProcessingSoftware', + Writable => 'string', + WriteGroup => 'IFD0', + Notes => 'used by ACD Systems Digital Imaging', + }, + 0xfe => { + Name => 'SubfileType', + Notes => 'called NewSubfileType by the TIFF specification', + Protected => 1, + Writable => 'int32u', + WriteGroup => 'IFD0', + # set priority directory if this is the full resolution image + DataMember => 'SubfileType', + RawConv => q{ + if ($val == ($val & 0x02)) { + $self->SetPriorityDir() if $val == 0; + $$self{PageCount} = ($$self{PageCount} || 0) + 1; + $$self{MultiPage} = 1 if $val == 2 or $$self{PageCount} > 1; + } + $$self{SubfileType} = $val; + }, + PrintConv => \%subfileType, + }, + 0xff => { + Name => 'OldSubfileType', + Notes => 'called SubfileType by the TIFF specification', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + # set priority directory if this is the full resolution image + RawConv => q{ + if ($val == 1 or $val == 3) { + $self->SetPriorityDir() if $val == 1; + $$self{PageCount} = ($$self{PageCount} || 0) + 1; + $$self{MultiPage} = 1 if $val == 3 or $$self{PageCount} > 1; + } + $val; + }, + PrintConv => { + 1 => 'Full-resolution image', + 2 => 'Reduced-resolution image', + 3 => 'Single page of multi-page image', + }, + }, + 0x100 => { + Name => 'ImageWidth', + # even though Group 1 is set dynamically we need to register IFD1 once + # so it will show up in the group lists + Groups => { 1 => 'IFD1' }, + Protected => 1, + Writable => 'int32u', + WriteGroup => 'IFD0', + # Note: priority 0 tags automatically have their priority increased for the + # priority directory (the directory with a SubfileType of "Full-resolution image") + Priority => 0, + }, + 0x101 => { + Name => 'ImageHeight', + Notes => 'called ImageLength by the EXIF spec.', + Protected => 1, + Writable => 'int32u', + WriteGroup => 'IFD0', + Priority => 0, + }, + 0x102 => { + Name => 'BitsPerSample', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + Count => -1, # can be 1 or 3: -1 means 'variable' + Priority => 0, + }, + 0x103 => { + Name => 'Compression', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + Mandatory => 1, + DataMember => 'Compression', + SeparateTable => 'Compression', + RawConv => q{ + Image::ExifTool::Exif::IdentifyRawFile($self, $val); + return $$self{Compression} = $val; + }, + PrintConv => \%compression, + Priority => 0, + }, + 0x106 => { + Name => 'PhotometricInterpretation', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + PrintConv => \%photometricInterpretation, + Priority => 0, + }, + 0x107 => { + Name => 'Thresholding', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + PrintConv => { + 1 => 'No dithering or halftoning', + 2 => 'Ordered dither or halftone', + 3 => 'Randomized dither', + }, + }, + 0x108 => { + Name => 'CellWidth', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + }, + 0x109 => { + Name => 'CellLength', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + }, + 0x10a => { + Name => 'FillOrder', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + PrintConv => { + 1 => 'Normal', + 2 => 'Reversed', + }, + }, + 0x10d => { + Name => 'DocumentName', + Writable => 'string', + WriteGroup => 'IFD0', + }, + 0x10e => { + Name => 'ImageDescription', + Writable => 'string', + WriteGroup => 'IFD0', + Priority => 0, + }, + 0x10f => { + Name => 'Make', + Groups => { 2 => 'Camera' }, + Writable => 'string', + WriteGroup => 'IFD0', + DataMember => 'Make', + # remove trailing blanks and save as an ExifTool member variable + RawConv => '$val =~ s/\s+$//; $$self{Make} = $val', + # NOTE: trailing "blanks" (spaces) are removed from all EXIF tags which + # may be "unknown" (filled with spaces) according to the EXIF spec. + # This allows conditional replacement with "exiftool -TAG-= -TAG=VALUE". + # - also removed are any other trailing whitespace characters + }, + 0x110 => { + Name => 'Model', + Description => 'Camera Model Name', + Groups => { 2 => 'Camera' }, + Writable => 'string', + WriteGroup => 'IFD0', + DataMember => 'Model', + # remove trailing blanks and save as an ExifTool member variable + RawConv => '$val =~ s/\s+$//; $$self{Model} = $val', + }, + 0x111 => [ + { + Condition => q[ + $$self{TIFF_TYPE} eq 'MRW' and $$self{DIR_NAME} eq 'IFD0' and + $$self{Model} =~ /^DiMAGE A200/ + ], + Name => 'StripOffsets', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0x117, # point to associated byte counts + # A200 stores this information in the wrong byte order!! + ValueConv => '$val=join(" ",unpack("N*",pack("V*",split(" ",$val))));\$val', + ByteOrder => 'LittleEndian', + }, + { + Condition => '$$self{Compression} and $$self{Compression} eq "34892"', # DNG Lossy JPEG + Name => 'OtherImageStart', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0x117, # point to associated byte counts + DataTag => 'OtherImage', + }, + { + # (APP1 IFD2 is for Leica JPEG preview) + Condition => q[ + not ($$self{TIFF_TYPE} eq 'CR2' and $$self{DIR_NAME} eq 'IFD0') and + not ($$self{TIFF_TYPE} =~ /^(DNG|TIFF)$/ and $$self{Compression} eq '7' and $$self{SubfileType} ne '0') and + not ($$self{TIFF_TYPE} eq 'APP1' and $$self{DIR_NAME} eq 'IFD2') + ], + Name => 'StripOffsets', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0x117, # point to associated byte counts + ValueConv => 'length($val) > 32 ? \$val : $val', + }, + { + # PreviewImageStart in IFD0 of CR2 images + Condition => '$$self{TIFF_TYPE} eq "CR2"', + Name => 'PreviewImageStart', + IsOffset => 1, + OffsetPair => 0x117, + Notes => q{ + called StripOffsets in most locations, but it is PreviewImageStart in IFD0 + of CR2 images and various IFD's of DNG images except for SubIFD2 where it is + JpgFromRawStart + }, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 2, + Permanent => 1, + }, + { + # PreviewImageStart in various IFD's of DNG images except SubIFD2 + Condition => '$$self{DIR_NAME} ne "SubIFD2"', + Name => 'PreviewImageStart', + IsOffset => 1, + OffsetPair => 0x117, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'All', # (writes to specific group of associated Composite tag) + Protected => 2, + Permanent => 1, + }, + { + # JpgFromRawStart in various IFD's of DNG images except SubIFD2 + Name => 'JpgFromRawStart', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0x117, + DataTag => 'JpgFromRaw', + Writable => 'int32u', + WriteGroup => 'SubIFD2', + Protected => 2, + Permanent => 1, + }, + ], + 0x112 => { + Name => 'Orientation', + Writable => 'int16u', + WriteGroup => 'IFD0', + PrintConv => \%orientation, + Priority => 0, # so PRIORITY_DIR takes precedence + }, + 0x115 => { + Name => 'SamplesPerPixel', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + Priority => 0, + }, + 0x116 => { + Name => 'RowsPerStrip', + Protected => 1, + Writable => 'int32u', + WriteGroup => 'IFD0', + Priority => 0, + }, + 0x117 => [ + { + Condition => q[ + $$self{TIFF_TYPE} eq 'MRW' and $$self{DIR_NAME} eq 'IFD0' and + $$self{Model} =~ /^DiMAGE A200/ + ], + Name => 'StripByteCounts', + OffsetPair => 0x111, # point to associated offset + # A200 stores this information in the wrong byte order!! + ValueConv => '$val=join(" ",unpack("N*",pack("V*",split(" ",$val))));\$val', + ByteOrder => 'LittleEndian', + }, + { + Condition => '$$self{Compression} and $$self{Compression} eq "34892"', # DNG Lossy JPEG + Name => 'OtherImageLength', + OffsetPair => 0x111, # point to associated offset + DataTag => 'OtherImage', + }, + { + # (APP1 IFD2 is for Leica JPEG preview) + Condition => q[ + not ($$self{TIFF_TYPE} eq 'CR2' and $$self{DIR_NAME} eq 'IFD0') and + not ($$self{TIFF_TYPE} =~ /^(DNG|TIFF)$/ and $$self{Compression} eq '7' and $$self{SubfileType} ne '0') and + not ($$self{TIFF_TYPE} eq 'APP1' and $$self{DIR_NAME} eq 'IFD2') + ], + Name => 'StripByteCounts', + OffsetPair => 0x111, # point to associated offset + ValueConv => 'length($val) > 32 ? \$val : $val', + }, + { + # PreviewImageLength in IFD0 of CR2 images + Condition => '$$self{TIFF_TYPE} eq "CR2"', + Name => 'PreviewImageLength', + OffsetPair => 0x111, + Notes => q{ + called StripByteCounts in most locations, but it is PreviewImageLength in + IFD0 of CR2 images and various IFD's of DNG images except for SubIFD2 where + it is JpgFromRawLength + }, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 2, + Permanent => 1, + }, + { + # PreviewImageLength in various IFD's of DNG images except SubIFD2 + Condition => '$$self{DIR_NAME} ne "SubIFD2"', + Name => 'PreviewImageLength', + OffsetPair => 0x111, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'All', # (writes to specific group of associated Composite tag) + Protected => 2, + Permanent => 1, + }, + { + # JpgFromRawLength in SubIFD2 of DNG images + Name => 'JpgFromRawLength', + OffsetPair => 0x111, + DataTag => 'JpgFromRaw', + Writable => 'int32u', + WriteGroup => 'SubIFD2', + Protected => 2, + Permanent => 1, + }, + ], + 0x118 => { + Name => 'MinSampleValue', + Writable => 'int16u', + WriteGroup => 'IFD0', + }, + 0x119 => { + Name => 'MaxSampleValue', + Writable => 'int16u', + WriteGroup => 'IFD0', + }, + 0x11a => { + Name => 'XResolution', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Mandatory => 1, + Priority => 0, # so PRIORITY_DIR takes precedence + }, + 0x11b => { + Name => 'YResolution', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Mandatory => 1, + Priority => 0, + }, + 0x11c => { + Name => 'PlanarConfiguration', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + PrintConv => { + 1 => 'Chunky', + 2 => 'Planar', + }, + Priority => 0, + }, + 0x11d => { + Name => 'PageName', + Writable => 'string', + WriteGroup => 'IFD0', + }, + 0x11e => { + Name => 'XPosition', + Writable => 'rational64u', + WriteGroup => 'IFD0', + }, + 0x11f => { + Name => 'YPosition', + Writable => 'rational64u', + WriteGroup => 'IFD0', + }, + # FreeOffsets/FreeByteCounts are used by Ricoh for RMETA information + # in TIFF images (not yet supported) + 0x120 => { + Name => 'FreeOffsets', + IsOffset => 1, + OffsetPair => 0x121, + ValueConv => 'length($val) > 32 ? \$val : $val', + }, + 0x121 => { + Name => 'FreeByteCounts', + OffsetPair => 0x120, + ValueConv => 'length($val) > 32 ? \$val : $val', + }, + 0x122 => { + Name => 'GrayResponseUnit', + Writable => 'int16u', + WriteGroup => 'IFD0', + PrintConv => { #3 + 1 => 0.1, + 2 => 0.001, + 3 => 0.0001, + 4 => 0.00001, + 5 => 0.000001, + }, + }, + 0x123 => { + Name => 'GrayResponseCurve', + Binary => 1, + }, + 0x124 => { + Name => 'T4Options', + PrintConv => { BITMASK => { + 0 => '2-Dimensional encoding', + 1 => 'Uncompressed', + 2 => 'Fill bits added', + } }, #3 + }, + 0x125 => { + Name => 'T6Options', + PrintConv => { BITMASK => { + 1 => 'Uncompressed', + } }, #3 + }, + 0x128 => { + Name => 'ResolutionUnit', + Notes => 'the value 1 is not standard EXIF', + Writable => 'int16u', + WriteGroup => 'IFD0', + Mandatory => 1, + PrintConv => { + 1 => 'None', + 2 => 'inches', + 3 => 'cm', + }, + Priority => 0, + }, + 0x129 => { + Name => 'PageNumber', + Writable => 'int16u', + WriteGroup => 'IFD0', + Count => 2, + }, + 0x12c => 'ColorResponseUnit', #9 + 0x12d => { + Name => 'TransferFunction', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + Count => 768, + Binary => 1, + }, + 0x131 => { + Name => 'Software', + Writable => 'string', + WriteGroup => 'IFD0', + DataMember => 'Software', + RawConv => '$val =~ s/\s+$//; $$self{Software} = $val', # trim trailing blanks + }, + 0x132 => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + Notes => 'called DateTime by the EXIF spec.', + Writable => 'string', + Shift => 'Time', + WriteGroup => 'IFD0', + Validate => 'ValidateExifDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,0)', + }, + 0x13b => { + Name => 'Artist', + Groups => { 2 => 'Author' }, + Notes => 'becomes a list-type tag when the MWG module is loaded', + Writable => 'string', + WriteGroup => 'IFD0', + RawConv => '$val =~ s/\s+$//; $val', # trim trailing blanks + }, + 0x13c => { + Name => 'HostComputer', + Writable => 'string', + WriteGroup => 'IFD0', + }, + 0x13d => { + Name => 'Predictor', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + PrintConv => { + 1 => 'None', + 2 => 'Horizontal differencing', + 3 => 'Floating point', # (DNG 1.5) + 34892 => 'Horizontal difference X2', # (DNG 1.5) + 34893 => 'Horizontal difference X4', # (DNG 1.5) + 34894 => 'Floating point X2', # (DNG 1.5) + 34895 => 'Floating point X4', # (DNG 1.5) + }, + }, + 0x13e => { + Name => 'WhitePoint', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + WriteGroup => 'IFD0', + Count => 2, + }, + 0x13f => { + Name => 'PrimaryChromaticities', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Count => 6, + Priority => 0, + }, + 0x140 => { + Name => 'ColorMap', + Format => 'binary', + Binary => 1, + }, + 0x141 => { + Name => 'HalftoneHints', + Writable => 'int16u', + WriteGroup => 'IFD0', + Count => 2, + }, + 0x142 => { + Name => 'TileWidth', + Protected => 1, + Writable => 'int32u', + WriteGroup => 'IFD0', + }, + 0x143 => { + Name => 'TileLength', + Protected => 1, + Writable => 'int32u', + WriteGroup => 'IFD0', + }, + 0x144 => { + Name => 'TileOffsets', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0x145, + ValueConv => 'length($val) > 32 ? \$val : $val', + }, + 0x145 => { + Name => 'TileByteCounts', + OffsetPair => 0x144, + ValueConv => 'length($val) > 32 ? \$val : $val', + }, + 0x146 => 'BadFaxLines', #3 + 0x147 => { #3 + Name => 'CleanFaxData', + PrintConv => { + 0 => 'Clean', + 1 => 'Regenerated', + 2 => 'Unclean', + }, + }, + 0x148 => 'ConsecutiveBadFaxLines', #3 + 0x14a => [ + { + Name => 'SubIFD', + # use this opportunity to identify an ARW image, and if so we + # must decide if this is a SubIFD or the A100 raw data + # (use SubfileType, Compression and FILE_TYPE to identify ARW/SR2, + # then call SetARW to finish the job) + Condition => q{ + $$self{DIR_NAME} ne 'IFD0' or $$self{FILE_TYPE} ne 'TIFF' or + $$self{Make} !~ /^SONY/ or + not $$self{SubfileType} or $$self{SubfileType} != 1 or + not $$self{Compression} or $$self{Compression} != 6 or + not require Image::ExifTool::Sony or + Image::ExifTool::Sony::SetARW($self, $valPt) + }, + Groups => { 1 => 'SubIFD' }, + Flags => 'SubIFD', + SubDirectory => { + Start => '$val', + MaxSubdirs => 10, # (have seen 5 in a DNG 1.4 image) + }, + }, + { #16 + Name => 'A100DataOffset', + Notes => 'the data offset in original Sony DSLR-A100 ARW images', + DataMember => 'A100DataOffset', + RawConv => '$$self{A100DataOffset} = $val', + WriteGroup => 'IFD0', # (only for Validate) + IsOffset => 1, + Protected => 2, + }, + ], + 0x14c => { + Name => 'InkSet', + Writable => 'int16u', + WriteGroup => 'IFD0', + PrintConv => { #3 + 1 => 'CMYK', + 2 => 'Not CMYK', + }, + }, + 0x14d => 'InkNames', #3 + 0x14e => 'NumberofInks', #3 + 0x150 => 'DotRange', + 0x151 => { + Name => 'TargetPrinter', + Writable => 'string', + WriteGroup => 'IFD0', + }, + 0x152 => { + Name => 'ExtraSamples', + PrintConv => { #20 + 0 => 'Unspecified', + 1 => 'Associated Alpha', + 2 => 'Unassociated Alpha', + }, + }, + 0x153 => { + Name => 'SampleFormat', + Notes => 'SamplesPerPixel values', + WriteGroup => 'SubIFD', # (only for Validate) + PrintConvColumns => 2, + PrintConv => [ \%sampleFormat, \%sampleFormat, \%sampleFormat, \%sampleFormat ], + }, + 0x154 => 'SMinSampleValue', + 0x155 => 'SMaxSampleValue', + 0x156 => 'TransferRange', + 0x157 => 'ClipPath', #3 + 0x158 => 'XClipPathUnits', #3 + 0x159 => 'YClipPathUnits', #3 + 0x15a => { #3 + Name => 'Indexed', + PrintConv => { 0 => 'Not indexed', 1 => 'Indexed' }, + }, + 0x15b => { + Name => 'JPEGTables', + Binary => 1, + }, + 0x15f => { #10 + Name => 'OPIProxy', + PrintConv => { + 0 => 'Higher resolution image does not exist', + 1 => 'Higher resolution image exists', + }, + }, + # 0x181 => 'Decode', #20 (typo! - should be 0x1b1, ref 21) + # 0x182 => 'DefaultImageColor', #20 (typo! - should be 0x1b2, ref 21) + 0x190 => { #3 + Name => 'GlobalParametersIFD', + Groups => { 1 => 'GlobParamIFD' }, + Flags => 'SubIFD', + SubDirectory => { + DirName => 'GlobParamIFD', + Start => '$val', + MaxSubdirs => 1, + }, + }, + 0x191 => { #3 + Name => 'ProfileType', + PrintConv => { 0 => 'Unspecified', 1 => 'Group 3 FAX' }, + }, + 0x192 => { #3 + Name => 'FaxProfile', + PrintConv => { + 0 => 'Unknown', + 1 => 'Minimal B&W lossless, S', + 2 => 'Extended B&W lossless, F', + 3 => 'Lossless JBIG B&W, J', + 4 => 'Lossy color and grayscale, C', + 5 => 'Lossless color and grayscale, L', + 6 => 'Mixed raster content, M', + 7 => 'Profile T', #20 + 255 => 'Multi Profiles', #20 + }, + }, + 0x193 => { #3 + Name => 'CodingMethods', + PrintConv => { BITMASK => { + 0 => 'Unspecified compression', + 1 => 'Modified Huffman', + 2 => 'Modified Read', + 3 => 'Modified MR', + 4 => 'JBIG', + 5 => 'Baseline JPEG', + 6 => 'JBIG color', + } }, + }, + 0x194 => 'VersionYear', #3 + 0x195 => 'ModeNumber', #3 + 0x1b1 => 'Decode', #3 + 0x1b2 => 'DefaultImageColor', #3 (changed to ImageBaseColor, ref 21) + 0x1b3 => 'T82Options', #20 + 0x1b5 => { #19 + Name => 'JPEGTables', + Binary => 1, + }, + 0x200 => { + Name => 'JPEGProc', + PrintConv => { + 1 => 'Baseline', + 14 => 'Lossless', + }, + }, + 0x201 => [ + { + Name => 'ThumbnailOffset', + Notes => q{ + called JPEGInterchangeFormat in the specification, this is ThumbnailOffset + in IFD1 of JPEG and some TIFF-based images, IFD0 of MRW images and AVI and + MOV videos, and the SubIFD in IFD1 of SRW images; PreviewImageStart in + MakerNotes and IFD0 of ARW and SR2 images; JpgFromRawStart in SubIFD of NEF + images and IFD2 of PEF images; and OtherImageStart in everything else + }, + # thumbnail is found in IFD1 of JPEG and TIFF images, and + # IFD0 of EXIF information in FujiFilm AVI (RIFF) and MOV videos + Condition => q{ + # recognize NRW file from a JPEG-compressed thumbnail in IFD0 + if ($$self{TIFF_TYPE} eq 'NEF' and $$self{DIR_NAME} eq 'IFD0' and $$self{Compression} == 6) { + $self->OverrideFileType($$self{TIFF_TYPE} = 'NRW'); + } + $$self{DIR_NAME} eq 'IFD1' or + ($$self{DIR_NAME} eq 'IFD0' and $$self{FILE_TYPE} =~ /^(RIFF|MOV)$/) + }, + IsOffset => 1, + OffsetPair => 0x202, + DataTag => 'ThumbnailImage', + Writable => 'int32u', + WriteGroup => 'IFD1', + # according to the EXIF spec. a JPEG-compressed thumbnail image may not + # be stored in a TIFF file, but these TIFF-based RAW image formats + # use IFD1 for a JPEG-compressed thumbnail: CR2, ARW, SR2 and PEF. + # (SRF also stores a JPEG image in IFD1, but it is actually a preview + # and we don't yet write SRF anyway) + WriteCondition => q{ + $$self{FILE_TYPE} ne "TIFF" or + $$self{TIFF_TYPE} =~ /^(CR2|ARW|SR2|PEF)$/ + }, + Protected => 2, + }, + { + Name => 'ThumbnailOffset', + # thumbnail in IFD0 of MRW images (Minolta A200) + # and IFD0 of NRW images (Nikon Coolpix P6000,P7000,P7100) + Condition => '$$self{DIR_NAME} eq "IFD0" and $$self{TIFF_TYPE} =~ /^(MRW|NRW)$/', + IsOffset => 1, + OffsetPair => 0x202, + # A200 uses the wrong base offset for this pointer!! + WrongBase => '$$self{Model} =~ /^DiMAGE A200/ ? $$self{MRW_WrongBase} : undef', + DataTag => 'ThumbnailImage', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 2, + Permanent => 1, + }, + { + Name => 'ThumbnailOffset', + # in SubIFD of IFD1 in Samsung SRW images + Condition => q{ + $$self{TIFF_TYPE} eq 'SRW' and $$self{DIR_NAME} eq 'SubIFD' and + $$self{PATH}[-2] eq 'IFD1' + }, + IsOffset => 1, + OffsetPair => 0x202, + DataTag => 'ThumbnailImage', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Protected => 2, + Permanent => 1, + }, + { + Name => 'PreviewImageStart', + Condition => '$$self{DIR_NAME} eq "MakerNotes"', + IsOffset => 1, + OffsetPair => 0x202, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + Permanent => 1, + }, + { + Name => 'PreviewImageStart', + # PreviewImage in IFD0 of ARW and SR2 files for all models + Condition => '$$self{DIR_NAME} eq "IFD0" and $$self{TIFF_TYPE} =~ /^(ARW|SR2)$/', + IsOffset => 1, + OffsetPair => 0x202, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 2, + Permanent => 1, + }, + { + Name => 'JpgFromRawStart', + Condition => '$$self{DIR_NAME} eq "SubIFD"', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0x202, + DataTag => 'JpgFromRaw', + Writable => 'int32u', + WriteGroup => 'SubIFD', + # JpgFromRaw is in SubIFD of NEF, NRW and SRW files + Protected => 2, + Permanent => 1, + }, + { + Name => 'JpgFromRawStart', + Condition => '$$self{DIR_NAME} eq "IFD2"', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0x202, + DataTag => 'JpgFromRaw', + Writable => 'int32u', + WriteGroup => 'IFD2', + # JpgFromRaw is in IFD2 of PEF files + Protected => 2, + Permanent => 1, + }, + { + Name => 'OtherImageStart', + Condition => '$$self{DIR_NAME} eq "SubIFD1"', + IsImageData => 1, + IsOffset => 1, + OffsetPair => 0x202, + DataTag => 'OtherImage', + Writable => 'int32u', + WriteGroup => 'SubIFD1', + Protected => 2, + Permanent => 1, + }, + { + Name => 'OtherImageStart', + Condition => '$$self{DIR_NAME} eq "SubIFD2"', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0x202, + DataTag => 'OtherImage', + Writable => 'int32u', + WriteGroup => 'SubIFD2', + Protected => 2, + Permanent => 1, + }, + { + Name => 'OtherImageStart', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0x202, + }, + ], + 0x202 => [ + { + Name => 'ThumbnailLength', + Notes => q{ + called JPEGInterchangeFormatLength in the specification, this is + ThumbnailLength in IFD1 of JPEG and some TIFF-based images, IFD0 of MRW + images and AVI and MOV videos, and the SubIFD in IFD1 of SRW images; + PreviewImageLength in MakerNotes and IFD0 of ARW and SR2 images; + JpgFromRawLength in SubIFD of NEF images, and IFD2 of PEF images; and + OtherImageLength in everything else + }, + Condition => q{ + $$self{DIR_NAME} eq 'IFD1' or + ($$self{DIR_NAME} eq 'IFD0' and $$self{FILE_TYPE} =~ /^(RIFF|MOV)$/) + }, + OffsetPair => 0x201, + DataTag => 'ThumbnailImage', + Writable => 'int32u', + WriteGroup => 'IFD1', + WriteCondition => q{ + $$self{FILE_TYPE} ne "TIFF" or + $$self{TIFF_TYPE} =~ /^(CR2|ARW|SR2|PEF)$/ + }, + Protected => 2, + }, + { + Name => 'ThumbnailLength', + # thumbnail in IFD0 of MRW images (Minolta A200) + # and IFD0 of NRW images (Nikon Coolpix P6000,P7000,P7100) + Condition => '$$self{DIR_NAME} eq "IFD0" and $$self{TIFF_TYPE} =~ /^(MRW|NRW)$/', + OffsetPair => 0x201, + DataTag => 'ThumbnailImage', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 2, + Permanent => 1, + }, + { + Name => 'ThumbnailLength', + # in SubIFD of IFD1 in Samsung SRW images + Condition => q{ + $$self{TIFF_TYPE} eq 'SRW' and $$self{DIR_NAME} eq 'SubIFD' and + $$self{PATH}[-2] eq 'IFD1' + }, + OffsetPair => 0x201, + DataTag => 'ThumbnailImage', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Protected => 2, + Permanent => 1, + }, + { + Name => 'PreviewImageLength', + Condition => '$$self{DIR_NAME} eq "MakerNotes"', + OffsetPair => 0x201, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + Permanent => 1, + }, + { + Name => 'PreviewImageLength', + # PreviewImage in IFD0 of ARW and SR2 files for all models + Condition => '$$self{DIR_NAME} eq "IFD0" and $$self{TIFF_TYPE} =~ /^(ARW|SR2)$/', + OffsetPair => 0x201, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 2, + Permanent => 1, + }, + { + Name => 'JpgFromRawLength', + Condition => '$$self{DIR_NAME} eq "SubIFD"', + OffsetPair => 0x201, + DataTag => 'JpgFromRaw', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Protected => 2, + Permanent => 1, + }, + { + Name => 'JpgFromRawLength', + Condition => '$$self{DIR_NAME} eq "IFD2"', + OffsetPair => 0x201, + DataTag => 'JpgFromRaw', + Writable => 'int32u', + WriteGroup => 'IFD2', + Protected => 2, + Permanent => 1, + }, + { + Name => 'OtherImageLength', + Condition => '$$self{DIR_NAME} eq "SubIFD1"', + OffsetPair => 0x201, + DataTag => 'OtherImage', + Writable => 'int32u', + WriteGroup => 'SubIFD1', + Protected => 2, + Permanent => 1, + }, + { + Name => 'OtherImageLength', + Condition => '$$self{DIR_NAME} eq "SubIFD2"', + OffsetPair => 0x201, + DataTag => 'OtherImage', + Writable => 'int32u', + WriteGroup => 'SubIFD2', + Protected => 2, + Permanent => 1, + }, + { + Name => 'OtherImageLength', + OffsetPair => 0x201, + }, + ], + 0x203 => 'JPEGRestartInterval', + 0x205 => 'JPEGLosslessPredictors', + 0x206 => 'JPEGPointTransforms', + 0x207 => { + Name => 'JPEGQTables', + IsOffset => 1, + # this tag is not supported for writing, so define an + # invalid offset pair to cause a "No size tag" error to be + # generated if we try to write a file containing this tag + OffsetPair => -1, + }, + 0x208 => { + Name => 'JPEGDCTables', + IsOffset => 1, + OffsetPair => -1, # (see comment for JPEGQTables) + }, + 0x209 => { + Name => 'JPEGACTables', + IsOffset => 1, + OffsetPair => -1, # (see comment for JPEGQTables) + }, + 0x211 => { + Name => 'YCbCrCoefficients', + Protected => 1, + Writable => 'rational64u', + WriteGroup => 'IFD0', + Count => 3, + Priority => 0, + }, + 0x212 => { + Name => 'YCbCrSubSampling', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + Count => 2, + PrintConvColumns => 2, + PrintConv => \%Image::ExifTool::JPEG::yCbCrSubSampling, + Priority => 0, + }, + 0x213 => { + Name => 'YCbCrPositioning', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'IFD0', + Mandatory => 1, + PrintConv => { + 1 => 'Centered', + 2 => 'Co-sited', + }, + Priority => 0, + }, + 0x214 => { + Name => 'ReferenceBlackWhite', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Count => 6, + Priority => 0, + }, + # 0x220 - int32u: 0 (IFD0, Xaiomi Redmi models) + # 0x221 - int32u: 0 (IFD0, Xaiomi Redmi models) + # 0x222 - int32u: 0 (IFD0, Xaiomi Redmi models) + # 0x223 - int32u: 0 (IFD0, Xaiomi Redmi models) + # 0x224 - int32u: 0,1 (IFD0, Xaiomi Redmi models) + # 0x225 - string: "" (IFD0, Xaiomi Redmi models) + 0x22f => 'StripRowCounts', + 0x2bc => { + Name => 'ApplicationNotes', # (writable directory!) + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', # (only for Validate) + Flags => [ 'Binary', 'Protected' ], + # this could be an XMP block + SubDirectory => { + DirName => 'XMP', + TagTable => 'Image::ExifTool::XMP::Main', + }, + }, + 0x3e7 => 'USPTOMiscellaneous', #20 + 0x1000 => { #5 + Name => 'RelatedImageFileFormat', + Protected => 1, + Writable => 'string', + WriteGroup => 'InteropIFD', + }, + 0x1001 => { #5 + Name => 'RelatedImageWidth', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'InteropIFD', + }, + 0x1002 => { #5 + Name => 'RelatedImageHeight', + Notes => 'called RelatedImageLength by the DCF spec.', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'InteropIFD', + }, + # (0x474x tags written by MicrosoftPhoto) + 0x4746 => { #PH + Name => 'Rating', + Writable => 'int16u', + WriteGroup => 'IFD0', + Avoid => 1, + }, + 0x4747 => { # (written by Digital Image Pro) + Name => 'XP_DIP_XML', + Format => 'undef', + # the following reference indicates this is Unicode: + # http://social.msdn.microsoft.com/Forums/en-US/isvvba/thread/ce6edcbb-8fc2-40c6-ad98-85f5d835ddfb + ValueConv => '$self->Decode($val,"UCS2","II")', + }, + 0x4748 => { + Name => 'StitchInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Microsoft::Stitch', + ByteOrder => 'LittleEndian', #PH (NC) + }, + }, + 0x4749 => { #PH + Name => 'RatingPercent', + Writable => 'int16u', + WriteGroup => 'IFD0', + Avoid => 1, + }, + 0x7000 => { #JR + Name => 'SonyRawFileType', + # (only valid if Sony:FileFormat >= ARW 2.0, ref IB) + # Writable => 'int16u', (don't allow writes for now) + PrintConv => { + 0 => 'Sony Uncompressed 14-bit RAW', + 1 => 'Sony Uncompressed 12-bit RAW', #IB + 2 => 'Sony Compressed RAW', # (lossy, ref IB) + 3 => 'Sony Lossless Compressed RAW', #IB + 4 => 'Sony Lossless Compressed RAW 2', #JR (ILCE-1) + }, + }, + # 0x7001 - int16u[1] (in SubIFD of Sony ARW images) - values: 0,1 + 0x7010 => { #IB + Name => 'SonyToneCurve', + # int16u[4] (in SubIFD of Sony ARW images -- don't allow writes for now) + # - only the middle 4 points are stored (lower comes from black level, + # and upper from data maximum) + }, + # 0x7011 - int16u[4] (in SubIFD of Sony ARW images) - values: "0 4912 8212 12287","4000 7200 10050 12075" + # 0x7020 - int32u[1] (in SubIFD of Sony ARW images) - values: 0,3 + 0x7031 => { + Name => 'VignettingCorrection', + Notes => 'found in Sony ARW images', + Writable => 'int16s', + WriteGroup => 'SubIFD', + Permanent => 1, + Protected => 1, + PrintConv => { + 256 => 'Off', + 257 => 'Auto', + 272 => 'Auto (ILCE-1)', #JR + 511 => 'No correction params available', + }, + }, + 0x7032 => { + Name => 'VignettingCorrParams', #forum7640 + Notes => 'found in Sony ARW images', + Writable => 'int16s', + WriteGroup => 'SubIFD', + Count => 17, + Permanent => 1, + Protected => 1, + }, + 0x7034 => { + Name => 'ChromaticAberrationCorrection', + Notes => 'found in Sony ARW images', + Writable => 'int16s', + WriteGroup => 'SubIFD', + Permanent => 1, + Protected => 1, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 255 => 'No correction params available', + }, + }, + 0x7035 => { + Name => 'ChromaticAberrationCorrParams', #forum6509 + Notes => 'found in Sony ARW images', + Writable => 'int16s', + WriteGroup => 'SubIFD', + Count => 33, + Permanent => 1, + Protected => 1, + }, + 0x7036 => { + Name => 'DistortionCorrection', + Notes => 'found in Sony ARW images', + Writable => 'int16s', + WriteGroup => 'SubIFD', + Permanent => 1, + Protected => 1, + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 17 => 'Auto fixed by lens', + 255 => 'No correction params available', + }, + }, + 0x7037 => { + Name => 'DistortionCorrParams', #forum6509 + Notes => 'found in Sony ARW images', + Writable => 'int16s', + WriteGroup => 'SubIFD', + Count => 17, + Permanent => 1, + Protected => 1, + }, + 0x7038 => { #github#195 (Sony ARW) + Name => 'SonyRawImageSize', + Notes => 'size of actual image in Sony ARW files', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Count => 2, + Permanent => 1, + Protected => 1, + }, + 0x7310 => { #github#195 (Sony ARW) + Name => 'BlackLevel', + Notes => 'found in Sony ARW images', + Writable => 'int16u', + WriteGroup => 'SubIFD', + Count => 4, + Permanent => 1, + Protected => 1, + }, + 0x7313 => { #github#195 (Sony ARW) + Name => 'WB_RGGBLevels', + Notes => 'found in Sony ARW images', + Writable => 'int16s', + WriteGroup => 'SubIFD', + Count => 4, + Permanent => 1, + Protected => 1, + }, + 0x74c7 => { #IB (in ARW images from some Sony cameras) + Name => 'SonyCropTopLeft', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Count => 2, + Permanent => 1, + Protected => 1, + }, + 0x74c8 => { #IB (in ARW images from some Sony cameras) + Name => 'SonyCropSize', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Count => 2, + Permanent => 1, + Protected => 1, + }, + 0x800d => 'ImageID', #10 + 0x80a3 => { Name => 'WangTag1', Binary => 1 }, #20 + 0x80a4 => { Name => 'WangAnnotation', Binary => 1 }, + 0x80a5 => { Name => 'WangTag3', Binary => 1 }, #20 + 0x80a6 => { #20 + Name => 'WangTag4', + PrintConv => 'length($val) <= 64 ? $val : \$val', + }, + # tags 0x80b8-0x80bc are registered to Island Graphics + 0x80b9 => 'ImageReferencePoints', #29 + 0x80ba => 'RegionXformTackPoint', #29 + 0x80bb => 'WarpQuadrilateral', #29 + 0x80bc => 'AffineTransformMat', #29 + 0x80e3 => 'Matteing', #9 + 0x80e4 => 'DataType', #9 + 0x80e5 => 'ImageDepth', #9 + 0x80e6 => 'TileDepth', #9 + # tags 0x8214-0x8219 are registered to Pixar + 0x8214 => 'ImageFullWidth', #29 + 0x8215 => 'ImageFullHeight', #29 + 0x8216 => 'TextureFormat', #29 + 0x8217 => 'WrapModes', #29 + 0x8218 => 'FovCot', #29 + 0x8219 => 'MatrixWorldToScreen', #29 + 0x821a => 'MatrixWorldToCamera', #29 + 0x827d => 'Model2', #29 (Eastman Kodak) + 0x828d => { #12 + Name => 'CFARepeatPatternDim', + Protected => 1, + Writable => 'int16u', + WriteGroup => 'SubIFD', + Count => 2, + }, + 0x828e => { + Name => 'CFAPattern2', #12 + Format => 'int8u', # (written incorrectly as 'undef' in Nikon NRW images) + Protected => 1, + Writable => 'int8u', + WriteGroup => 'SubIFD', + Count => -1, + }, + 0x828f => { #12 + Name => 'BatteryLevel', + Groups => { 2 => 'Camera' }, + }, + 0x8290 => { + Name => 'KodakIFD', + Groups => { 1 => 'KodakIFD' }, + Flags => 'SubIFD', + Notes => 'used in various types of Kodak images', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::IFD', + DirName => 'KodakIFD', + Start => '$val', + MaxSubdirs => 1, + }, + }, + 0x8298 => { + Name => 'Copyright', + Groups => { 2 => 'Author' }, + Format => 'undef', + Writable => 'string', + WriteGroup => 'IFD0', + Notes => q{ + may contain copyright notices for photographer and editor, separated by a + newline. As per the EXIF specification, the newline is replaced by a null + byte when writing to file, but this may be avoided by disabling the print + conversion + }, + # internally the strings are separated by a null character in this format: + # Photographer only: photographer + NULL + # Both: photographer + NULL + editor + NULL + # Editor only: SPACE + NULL + editor + NULL + # (this is done as a RawConv so conditional replaces will work properly) + RawConv => sub { + my ($val, $self) = @_; + $val =~ s/ *\0/\n/; # translate first NULL to a newline, removing trailing blanks + $val =~ s/ *\0.*//s; # truncate at second NULL and remove trailing blanks + $val =~ s/\n$//; # remove trailing newline if it exists + # decode if necessary (note: this is the only non-'string' EXIF value like this) + my $enc = $self->Options('CharsetEXIF'); + $val = $self->Decode($val,$enc) if $enc; + return $val; + }, + RawConvInv => '$val . "\0"', + PrintConvInv => sub { + my ($val, $self) = @_; + # encode if necessary (not automatic because Format is 'undef') + my $enc = $self->Options('CharsetEXIF'); + $val = $self->Encode($val,$enc) if $enc and $val !~ /\0/; + if ($val =~ /(.*?)\s*[\n\r]+\s*(.*)/s) { + return $1 unless length $2; + # photographer copyright set to ' ' if it doesn't exist, according to spec. + return((length($1) ? $1 : ' ') . "\0" . $2); + } + return $val; + }, + }, + 0x829a => { + Name => 'ExposureTime', + Writable => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => '$val', + }, + 0x829d => { + Name => 'FNumber', + Writable => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }, + 0x82a5 => { #3 + Name => 'MDFileTag', + Notes => 'tags 0x82a5-0x82ac are used in Molecular Dynamics GEL files', + }, + 0x82a6 => 'MDScalePixel', #3 + 0x82a7 => 'MDColorTable', #3 + 0x82a8 => 'MDLabName', #3 + 0x82a9 => 'MDSampleInfo', #3 + 0x82aa => 'MDPrepDate', #3 + 0x82ab => 'MDPrepTime', #3 + 0x82ac => 'MDFileUnits', #3 + 0x830e => { #30 (GeoTiff) + Name => 'PixelScale', + Writable => 'double', + WriteGroup => 'IFD0', + Count => 3, + }, + 0x8335 => 'AdventScale', #20 + 0x8336 => 'AdventRevision', #20 + 0x835c => 'UIC1Tag', #23 + 0x835d => 'UIC2Tag', #23 + 0x835e => 'UIC3Tag', #23 + 0x835f => 'UIC4Tag', #23 + 0x83bb => { #12 + Name => 'IPTC-NAA', # (writable directory! -- but see note below) + # this should actually be written as 'undef' (see + # http://www.awaresystems.be/imaging/tiff/tifftags/iptc.html), + # but Photoshop writes it as int32u and Nikon Capture won't read + # anything else, so we do the same thing here... Doh! + Format => 'undef', # convert binary values as undef + Writable => 'int32u', # but write int32u format code in IFD + WriteGroup => 'IFD0', + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { + DirName => 'IPTC', + TagTable => 'Image::ExifTool::IPTC::Main', + }, + # Note: This directory may be written as a block via the IPTC-NAA tag, + # but this technique is not recommended. Instead, it is better to + # write the Extra IPTC tag and let ExifTool decide where it should go. + }, + 0x847e => 'IntergraphPacketData', #3 + 0x847f => 'IntergraphFlagRegisters', #3 + 0x8480 => { #30 (GeoTiff, obsolete) + Name => 'IntergraphMatrix', + Writable => 'double', + WriteGroup => 'IFD0', + Count => -1, + }, + 0x8481 => 'INGRReserved', #20 + 0x8482 => { #30 (GeoTiff) + Name => 'ModelTiePoint', + Groups => { 2 => 'Location' }, + Writable => 'double', + WriteGroup => 'IFD0', + Count => -1, + }, + 0x84e0 => 'Site', #9 + 0x84e1 => 'ColorSequence', #9 + 0x84e2 => 'IT8Header', #9 + 0x84e3 => { #9 + Name => 'RasterPadding', + PrintConv => { #20 + 0 => 'Byte', + 1 => 'Word', + 2 => 'Long Word', + 9 => 'Sector', + 10 => 'Long Sector', + }, + }, + 0x84e4 => 'BitsPerRunLength', #9 + 0x84e5 => 'BitsPerExtendedRunLength', #9 + 0x84e6 => 'ColorTable', #9 + 0x84e7 => { #9 + Name => 'ImageColorIndicator', + PrintConv => { #20 + 0 => 'Unspecified Image Color', + 1 => 'Specified Image Color', + }, + }, + 0x84e8 => { #9 + Name => 'BackgroundColorIndicator', + PrintConv => { #20 + 0 => 'Unspecified Background Color', + 1 => 'Specified Background Color', + }, + }, + 0x84e9 => 'ImageColorValue', #9 + 0x84ea => 'BackgroundColorValue', #9 + 0x84eb => 'PixelIntensityRange', #9 + 0x84ec => 'TransparencyIndicator', #9 + 0x84ed => 'ColorCharacterization', #9 + 0x84ee => { #9 + Name => 'HCUsage', + PrintConv => { #20 + 0 => 'CT', + 1 => 'Line Art', + 2 => 'Trap', + }, + }, + 0x84ef => 'TrapIndicator', #17 + 0x84f0 => 'CMYKEquivalent', #17 + 0x8546 => { #11 + Name => 'SEMInfo', + Notes => 'found in some scanning electron microscope images', + Writable => 'string', + WriteGroup => 'IFD0', + }, + 0x8568 => { + Name => 'AFCP_IPTC', + SubDirectory => { + # must change directory name so we don't create this directory + DirName => 'AFCP_IPTC', + TagTable => 'Image::ExifTool::IPTC::Main', + }, + }, + 0x85b8 => 'PixelMagicJBIGOptions', #20 + 0x85d7 => 'JPLCartoIFD', #exifprobe (NC) + 0x85d8 => { #30 (GeoTiff) + Name => 'ModelTransform', + Groups => { 2 => 'Location' }, + Writable => 'double', + WriteGroup => 'IFD0', + Count => 16, + }, + 0x8602 => { #16 + Name => 'WB_GRGBLevels', + Notes => 'found in IFD0 of Leaf MOS images', + }, + # 0x8603 - Leaf CatchLight color matrix (ref 16) + 0x8606 => { + Name => 'LeafData', + Format => 'undef', # avoid converting huge block to string of int8u's! + SubDirectory => { + DirName => 'LeafIFD', + TagTable => 'Image::ExifTool::Leaf::Main', + }, + }, + 0x8649 => { #19 + Name => 'PhotoshopSettings', + Format => 'binary', + WriteGroup => 'IFD0', # (only for Validate) + SubDirectory => { + DirName => 'Photoshop', + TagTable => 'Image::ExifTool::Photoshop::Main', + }, + }, + 0x8769 => { + Name => 'ExifOffset', + Groups => { 1 => 'ExifIFD' }, + WriteGroup => 'IFD0', # (only for Validate) + SubIFD => 2, + SubDirectory => { + DirName => 'ExifIFD', + Start => '$val', + }, + }, + 0x8773 => { + Name => 'ICC_Profile', + WriteGroup => 'IFD0', # (only for Validate) + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + }, + 0x877f => { #20 + Name => 'TIFF_FXExtensions', + PrintConv => { BITMASK => { + 0 => 'Resolution/Image Width', + 1 => 'N Layer Profile M', + 2 => 'Shared Data', + 3 => 'B&W JBIG2', + 4 => 'JBIG2 Profile M', + }}, + }, + 0x8780 => { #20 + Name => 'MultiProfiles', + PrintConv => { BITMASK => { + 0 => 'Profile S', + 1 => 'Profile F', + 2 => 'Profile J', + 3 => 'Profile C', + 4 => 'Profile L', + 5 => 'Profile M', + 6 => 'Profile T', + 7 => 'Resolution/Image Width', + 8 => 'N Layer Profile M', + 9 => 'Shared Data', + 10 => 'JBIG2 Profile M', + }}, + }, + 0x8781 => { #22 + Name => 'SharedData', + IsOffset => 1, + # this tag is not supported for writing, so define an + # invalid offset pair to cause a "No size tag" error to be + # generated if we try to write a file containing this tag + OffsetPair => -1, + }, + 0x8782 => 'T88Options', #20 + 0x87ac => 'ImageLayer', + 0x87af => { #30 + Name => 'GeoTiffDirectory', + Format => 'undef', + Writable => 'int16u', + Notes => q{ + these "GeoTiff" tags may read and written as a block, but they aren't + extracted unless specifically requested. Byte order changes are handled + automatically when copying between TIFF images with different byte order + }, + WriteGroup => 'IFD0', + Binary => 1, + RawConv => '$val . GetByteOrder()', # save byte order + # swap byte order if necessary + RawConvInv => q{ + return $val if length $val < 2; + my $order = substr($val, -2); + return $val unless $order eq 'II' or $order eq 'MM'; + $val = substr($val, 0, -2); + return $val if $order eq GetByteOrder(); + return pack('v*',unpack('n*',$val)); + }, + }, + 0x87b0 => { #30 + Name => 'GeoTiffDoubleParams', + Format => 'undef', + Writable => 'double', + WriteGroup => 'IFD0', + Binary => 1, + RawConv => '$val . GetByteOrder()', # save byte order + # swap byte order if necessary + RawConvInv => q{ + return $val if length $val < 2; + my $order = substr($val, -2); + return $val unless $order eq 'II' or $order eq 'MM'; + $val = substr($val, 0, -2); + return $val if $order eq GetByteOrder(); + $val =~ s/(.{4})(.{4})/$2$1/sg; # swap words + return pack('V*',unpack('N*',$val)); + }, + }, + 0x87b1 => { #30 + Name => 'GeoTiffAsciiParams', + Format => 'undef', + Writable => 'string', + WriteGroup => 'IFD0', + Binary => 1, + }, + 0x87be => 'JBIGOptions', #29 + 0x8822 => { + Name => 'ExposureProgram', + Groups => { 2 => 'Camera' }, + Notes => 'the value of 9 is not standard EXIF, but is used by the Canon EOS 7D', + Writable => 'int16u', + PrintConv => { + 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', + 9 => 'Bulb', #25 + }, + }, + 0x8824 => { + Name => 'SpectralSensitivity', + Groups => { 2 => 'Camera' }, + Writable => 'string', + }, + 0x8825 => { + Name => 'GPSInfo', + Groups => { 1 => 'GPS' }, + WriteGroup => 'IFD0', # (only for Validate) + Flags => 'SubIFD', + SubDirectory => { + DirName => 'GPS', + TagTable => 'Image::ExifTool::GPS::Main', + Start => '$val', + MaxSubdirs => 1, + }, + }, + 0x8827 => { + Name => 'ISO', + Notes => q{ + called ISOSpeedRatings by EXIF 2.2, then PhotographicSensitivity by the EXIF + 2.3 spec. + }, + Writable => 'int16u', + Count => -1, + PrintConv => '$val=~s/\s+/, /g; $val', + PrintConvInv => '$val=~tr/,//d; $val', + }, + 0x8828 => { + Name => 'Opto-ElectricConvFactor', + Notes => 'called OECF by the EXIF spec.', + Binary => 1, + }, + 0x8829 => 'Interlace', #12 + 0x882a => { #12 + Name => 'TimeZoneOffset', + Writable => 'int16s', + Count => -1, # can be 1 or 2 + Notes => q{ + 1 or 2 values: 1. The time zone offset of DateTimeOriginal from GMT in + hours, 2. If present, the time zone offset of ModifyDate + }, + }, + 0x882b => { #12 + Name => 'SelfTimerMode', + Writable => 'int16u', + }, + 0x8830 => { #24 + Name => 'SensitivityType', + Notes => 'applies to EXIF:ISO tag', + Writable => 'int16u', + PrintConv => { + 0 => 'Unknown', + 1 => 'Standard Output Sensitivity', + 2 => 'Recommended Exposure Index', + 3 => 'ISO Speed', + 4 => 'Standard Output Sensitivity and Recommended Exposure Index', + 5 => 'Standard Output Sensitivity and ISO Speed', + 6 => 'Recommended Exposure Index and ISO Speed', + 7 => 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed', + }, + }, + 0x8831 => { #24 + Name => 'StandardOutputSensitivity', + Writable => 'int32u', + }, + 0x8832 => { #24 + Name => 'RecommendedExposureIndex', + Writable => 'int32u', + }, + 0x8833 => { #24 + Name => 'ISOSpeed', + Writable => 'int32u', + }, + 0x8834 => { #24 + Name => 'ISOSpeedLatitudeyyy', + Description => 'ISO Speed Latitude yyy', + Writable => 'int32u', + }, + 0x8835 => { #24 + Name => 'ISOSpeedLatitudezzz', + Description => 'ISO Speed Latitude zzz', + Writable => 'int32u', + }, + 0x885c => 'FaxRecvParams', #9 + 0x885d => 'FaxSubAddress', #9 + 0x885e => 'FaxRecvTime', #9 + 0x8871 => 'FedexEDR', #exifprobe (NC) + # 0x8889 - string: "portrait" (ExifIFD, Xiaomi POCO F1) + 0x888a => { #PH + Name => 'LeafSubIFD', + Format => 'int32u', # Leaf incorrectly uses 'undef' format! + Groups => { 1 => 'LeafSubIFD' }, + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::SubIFD', + Start => '$val', + }, + }, + # 0x8891 - int16u: 35 (ExifIFD, Xiaomi POCO F1) + # 0x8894 - int16u: 0 (ExifIFD, Xiaomi POCO F1) + # 0x8895 - int16u: 0 (ExifIFD, Xiaomi POCO F1) + # 0x889a - int16u: 0 (ExifIFD, Xiaomi POCO F1) + # 0x89ab - seen "11 100 130 16 0 0 0 0" in IFD0 of TIFF image from IR scanner (forum8470) + 0x9000 => { + Name => 'ExifVersion', + Writable => 'undef', + Mandatory => 1, + RawConv => '$val=~s/\0+$//; $val', # (some idiots add null terminators) + # (allow strings like "2.31" when writing) + PrintConvInv => '$val=~tr/.//d; $val=~/^\d{4}$/ ? $val : $val =~ /^\d{3}$/ ? "0$val" : undef', + }, + 0x9003 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Notes => 'date/time when original image was taken', + Writable => 'string', + Shift => 'Time', + Validate => 'ValidateExifDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,0)', + }, + 0x9004 => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + Notes => 'called DateTimeDigitized by the EXIF spec.', + Writable => 'string', + Shift => 'Time', + Validate => 'ValidateExifDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,0)', + }, + 0x9009 => { # undef[44] (or undef[11]) written by Google Plus uploader - PH + Name => 'GooglePlusUploadCode', + Format => 'int8u', + Writable => 'undef', + Count => -1, + }, + 0x9010 => { + Name => 'OffsetTime', + Groups => { 2 => 'Time' }, + Notes => 'time zone for ModifyDate', + Writable => 'string', + Shift => 'Time', + PrintConvInv => \&InverseOffsetTime, + }, + 0x9011 => { + Name => 'OffsetTimeOriginal', + Groups => { 2 => 'Time' }, + Notes => 'time zone for DateTimeOriginal', + Writable => 'string', + Shift => 'Time', + PrintConvInv => \&InverseOffsetTime, + }, + 0x9012 => { + Name => 'OffsetTimeDigitized', + Groups => { 2 => 'Time' }, + Notes => 'time zone for CreateDate', + Writable => 'string', + Shift => 'Time', + PrintConvInv => \&InverseOffsetTime, + }, + 0x9101 => { + Name => 'ComponentsConfiguration', + Format => 'int8u', + Protected => 1, + Writable => 'undef', + Count => 4, + Mandatory => 1, + ValueConvInv => '$val=~tr/,//d; $val', # (so we can copy from XMP with -n) + PrintConvColumns => 2, + PrintConv => { + 0 => '-', + 1 => 'Y', + 2 => 'Cb', + 3 => 'Cr', + 4 => 'R', + 5 => 'G', + 6 => 'B', + OTHER => sub { + my ($val, $inv, $conv) = @_; + my @a = split /,?\s+/, $val; + if ($inv) { + my %invConv; + $invConv{lc $$conv{$_}} = $_ foreach keys %$conv; + # strings like "YCbCr" and "RGB" still work for writing + @a = $a[0] =~ /(Y|Cb|Cr|R|G|B)/g if @a == 1; + foreach (@a) { + $_ = $invConv{lc $_}; + return undef unless defined $_; + } + push @a, 0 while @a < 4; + } else { + foreach (@a) { + $_ = $$conv{$_} || "Err ($_)"; + } + } + return join ', ', @a; + }, + }, + }, + 0x9102 => { + Name => 'CompressedBitsPerPixel', + Protected => 1, + Writable => 'rational64u', + }, + # 0x9103 - int16u: 1 (found in Pentax XG-1 samples) + 0x9201 => { + Name => 'ShutterSpeedValue', + Notes => 'displayed in seconds, but stored as an APEX value', + Format => 'rational64s', # Leica M8 patch (incorrectly written as rational64u) + Writable => 'rational64s', + ValueConv => 'IsFloat($val) && abs($val)<100 ? 2**(-$val) : 0', + ValueConvInv => '$val>0 ? -log($val)/log(2) : -100', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x9202 => { + Name => 'ApertureValue', + Notes => 'displayed as an F number, but stored as an APEX value', + Writable => 'rational64u', + ValueConv => '2 ** ($val / 2)', + ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + # Wikipedia: BrightnessValue = Bv = Av + Tv - Sv + # ExifTool: LightValue = LV = Av + Tv - Sv + 5 (5 is the Sv for ISO 100 in Exif usage) + 0x9203 => { + Name => 'BrightnessValue', + Writable => 'rational64s', + }, + 0x9204 => { + Name => 'ExposureCompensation', + Format => 'rational64s', # Leica M8 patch (incorrectly written as rational64u) + Notes => 'called ExposureBiasValue by the EXIF spec.', + Writable => 'rational64s', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => '$val', + }, + 0x9205 => { + Name => 'MaxApertureValue', + Notes => 'displayed as an F number, but stored as an APEX value', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + ValueConv => '2 ** ($val / 2)', + ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x9206 => { + Name => 'SubjectDistance', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "${val} m"', + PrintConvInv => '$val=~s/\s*m$//;$val', + }, + 0x9207 => { + Name => 'MeteringMode', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + PrintConv => { + 0 => 'Unknown', + 1 => 'Average', + 2 => 'Center-weighted average', + 3 => 'Spot', + 4 => 'Multi-spot', + 5 => 'Multi-segment', + 6 => 'Partial', + 255 => 'Other', + }, + }, + 0x9208 => { + Name => 'LightSource', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + SeparateTable => 'LightSource', + PrintConv => \%lightSource, + }, + 0x9209 => { + Name => 'Flash', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + Flags => 'PrintHex', + SeparateTable => 'Flash', + PrintConv => \%flash, + }, + 0x920a => { + Name => 'FocalLength', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + # Note: tags 0x920b-0x9217 are duplicates of 0xa20b-0xa217 + # (The EXIF standard uses 0xa2xx, but you'll find both in images) + 0x920b => { #12 + Name => 'FlashEnergy', + Groups => { 2 => 'Camera' }, + }, + 0x920c => 'SpatialFrequencyResponse', #12 (not in Fuji images - PH) + 0x920d => 'Noise', #12 + 0x920e => 'FocalPlaneXResolution', #12 + 0x920f => 'FocalPlaneYResolution', #12 + 0x9210 => { #12 + Name => 'FocalPlaneResolutionUnit', + Groups => { 2 => 'Camera' }, + PrintConv => { + 1 => 'None', + 2 => 'inches', + 3 => 'cm', + 4 => 'mm', + 5 => 'um', + }, + }, + 0x9211 => { #12 + Name => 'ImageNumber', + Writable => 'int32u', + }, + 0x9212 => { #12 + Name => 'SecurityClassification', + Writable => 'string', + PrintConv => { + T => 'Top Secret', + S => 'Secret', + C => 'Confidential', + R => 'Restricted', + U => 'Unclassified', + }, + }, + 0x9213 => { #12 + Name => 'ImageHistory', + Writable => 'string', + }, + 0x9214 => { + Name => 'SubjectArea', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + Count => -1, # 2, 3 or 4 values + }, + 0x9215 => 'ExposureIndex', #12 + 0x9216 => 'TIFF-EPStandardID', #12 + 0x9217 => { #12 + Name => 'SensingMethod', + Groups => { 2 => 'Camera' }, + PrintConv => { + # (values 1 and 6 are not used by corresponding EXIF tag 0xa217) + 1 => 'Monochrome area', + 2 => 'One-chip color area', + 3 => 'Two-chip color area', + 4 => 'Three-chip color area', + 5 => 'Color sequential area', + 6 => 'Monochrome linear', + 7 => 'Trilinear', + 8 => 'Color sequential linear', + }, + }, + 0x923a => 'CIP3DataFile', #20 + 0x923b => 'CIP3Sheet', #20 + 0x923c => 'CIP3Side', #20 + 0x923f => 'StoNits', #9 + # handle maker notes as a conditional list + 0x927c => \@Image::ExifTool::MakerNotes::Main, + 0x9286 => { + Name => 'UserComment', + # I have seen other applications write it incorrectly as 'string' or 'int8u' + Format => 'undef', + Writable => 'undef', + RawConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,1,$tag)', + # (starts with "ASCII\0\0\0", "UNICODE\0", "JIS\0\0\0\0\0" or "\0\0\0\0\0\0\0\0") + RawConvInv => 'Image::ExifTool::Exif::EncodeExifText($self,$val)', + # SHOULD ADD SPECIAL LOGIC TO ALLOW CONDITIONAL OVERWRITE OF + # "UNKNOWN" VALUES FILLED WITH SPACES + }, + 0x9290 => { + Name => 'SubSecTime', + Groups => { 2 => 'Time' }, + Notes => 'fractional seconds for ModifyDate', + Writable => 'string', + ValueConv => '$val=~s/ +$//; $val', # trim trailing blanks + # extract fractional seconds from a full date/time value + ValueConvInv => '$val=~/^(\d+)\s*$/ ? $1 : ($val=~/\.(\d+)/ ? $1 : undef)', + }, + 0x9291 => { + Name => 'SubSecTimeOriginal', + Groups => { 2 => 'Time' }, + Notes => 'fractional seconds for DateTimeOriginal', + Writable => 'string', + ValueConv => '$val=~s/ +$//; $val', # trim trailing blanks + ValueConvInv => '$val=~/^(\d+)\s*$/ ? $1 : ($val=~/\.(\d+)/ ? $1 : undef)', + }, + 0x9292 => { + Name => 'SubSecTimeDigitized', + Groups => { 2 => 'Time' }, + Notes => 'fractional seconds for CreateDate', + Writable => 'string', + ValueConv => '$val=~s/ +$//; $val', # trim trailing blanks + ValueConvInv => '$val=~/^(\d+)\s*$/ ? $1 : ($val=~/\.(\d+)/ ? $1 : undef)', + }, + # The following 3 tags are found in MSOffice TIFF images + # References: + # http://social.msdn.microsoft.com/Forums/en-US/os_standocs/thread/03086d55-294a-49d5-967a-5303d34c40f8/ + # http://blogs.msdn.com/openspecification/archive/2009/12/08/details-of-three-tiff-tag-extensions-that-microsoft-office-document-imaging-modi-software-may-write-into-the-tiff-files-it-generates.aspx + # http://www.microsoft.com/downloads/details.aspx?FamilyID=0dbc435d-3544-4f4b-9092-2f2643d64a39&displaylang=en#filelist + 0x932f => 'MSDocumentText', + 0x9330 => { + Name => 'MSPropertySetStorage', + Binary => 1, + }, + 0x9331 => { + Name => 'MSDocumentTextPosition', + Binary => 1, # (just in case -- don't know what format this is) + }, + 0x935c => { #3/19 + Name => 'ImageSourceData', # (writable directory!) + Writable => 'undef', + WriteGroup => 'IFD0', + SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::DocumentData' }, + Binary => 1, + Protected => 1, # (because this can be hundreds of megabytes) + ReadFromRAF => 1, # don't load into memory when reading + }, + 0x9400 => { + Name => 'AmbientTemperature', + Notes => 'ambient temperature in degrees C, called Temperature by the EXIF spec.', + Writable => 'rational64s', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 0x9401 => { + Name => 'Humidity', + Notes => 'ambient relative humidity in percent', + Writable => 'rational64u', + }, + 0x9402 => { + Name => 'Pressure', + Notes => 'air pressure in hPa or mbar', + Writable => 'rational64u', + }, + 0x9403 => { + Name => 'WaterDepth', + Notes => 'depth under water in metres, negative for above water', + Writable => 'rational64s', + }, + 0x9404 => { + Name => 'Acceleration', + Notes => 'directionless camera acceleration in units of mGal, or 10-5 m/s2', + Writable => 'rational64u', + }, + 0x9405 => { + Name => 'CameraElevationAngle', + Writable => 'rational64s', + }, + # 0x9999 - string: camera settings (ExifIFD, Xiaomi POCO F1) + # 0x9aaa - int8u[2176]: ? (ExifIFD, Xiaomi POCO F1) + 0x9c9b => { + Name => 'XPTitle', + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + Notes => q{ + tags 0x9c9b-0x9c9f are used by Windows Explorer; special characters + in these values are converted to UTF-8 by default, or Windows Latin1 + with the -L option. XPTitle is ignored by Windows Explorer if + ImageDescription exists + }, + ValueConv => '$self->Decode($val,"UCS2","II")', + ValueConvInv => '$self->Encode($val,"UCS2","II") . "\0\0"', + }, + 0x9c9c => { + Name => 'XPComment', + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + ValueConv => '$self->Decode($val,"UCS2","II")', + ValueConvInv => '$self->Encode($val,"UCS2","II") . "\0\0"', + }, + 0x9c9d => { + Name => 'XPAuthor', + Groups => { 2 => 'Author' }, + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + Notes => 'ignored by Windows Explorer if Artist exists', + ValueConv => '$self->Decode($val,"UCS2","II")', + ValueConvInv => '$self->Encode($val,"UCS2","II") . "\0\0"', + }, + 0x9c9e => { + Name => 'XPKeywords', + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + ValueConv => '$self->Decode($val,"UCS2","II")', + ValueConvInv => '$self->Encode($val,"UCS2","II") . "\0\0"', + }, + 0x9c9f => { + Name => 'XPSubject', + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + ValueConv => '$self->Decode($val,"UCS2","II")', + ValueConvInv => '$self->Encode($val,"UCS2","II") . "\0\0"', + }, + 0xa000 => { + Name => 'FlashpixVersion', + Writable => 'undef', + Mandatory => 1, + RawConv => '$val=~s/\0+$//; $val', # (some idiots add null terminators) + PrintConvInv => '$val=~tr/.//d; $val=~/^\d{4}$/ ? $val : undef', + }, + 0xa001 => { + Name => 'ColorSpace', + Notes => q{ + the value of 0x2 is not standard EXIF. Instead, an Adobe RGB image is + indicated by "Uncalibrated" with an InteropIndex of "R03". The values + 0xfffd and 0xfffe are also non-standard, and are used by some Sony cameras + }, + Writable => 'int16u', + Mandatory => 1, + PrintHex => 1, + PrintConv => { + 1 => 'sRGB', + 2 => 'Adobe RGB', + 0xffff => 'Uncalibrated', + # Sony uses these definitions: (ref JD) + # 0xffff => 'Adobe RGB', (conflicts with Uncalibrated) + 0xfffe => 'ICC Profile', + 0xfffd => 'Wide Gamut RGB', + }, + }, + 0xa002 => { + Name => 'ExifImageWidth', + Notes => 'called PixelXDimension by the EXIF spec.', + Writable => 'int16u', + Mandatory => 1, + }, + 0xa003 => { + Name => 'ExifImageHeight', + Notes => 'called PixelYDimension by the EXIF spec.', + Writable => 'int16u', + Mandatory => 1, + }, + 0xa004 => { + Name => 'RelatedSoundFile', + Writable => 'string', + }, + 0xa005 => { + Name => 'InteropOffset', + Groups => { 1 => 'InteropIFD' }, + Flags => 'SubIFD', + Description => 'Interoperability Offset', + SubDirectory => { + DirName => 'InteropIFD', + Start => '$val', + MaxSubdirs => 1, + }, + }, + # the following 4 tags found in SubIFD1 of some Samsung SRW images + 0xa010 => { + Name => 'SamsungRawPointersOffset', + IsOffset => 1, + OffsetPair => 0xa011, # point to associated byte count + }, + 0xa011 => { + Name => 'SamsungRawPointersLength', + OffsetPair => 0xa010, # point to associated offset + }, + 0xa101 => { + Name => 'SamsungRawByteOrder', + Format => 'undef', + # this is written incorrectly as string[1], but is "\0\0MM" or "II\0\0" + FixedSize => 4, + Count => 1, + }, + 0xa102 => { + Name => 'SamsungRawUnknown', + Unknown => 1, + }, + 0xa20b => { + Name => 'FlashEnergy', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + }, + 0xa20c => { + Name => 'SpatialFrequencyResponse', + PrintConv => 'Image::ExifTool::Exif::PrintSFR($val)', + }, + 0xa20d => 'Noise', + 0xa20e => { + Name => 'FocalPlaneXResolution', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + }, + 0xa20f => { + Name => 'FocalPlaneYResolution', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + }, + 0xa210 => { + Name => 'FocalPlaneResolutionUnit', + Groups => { 2 => 'Camera' }, + Notes => 'values 1, 4 and 5 are not standard EXIF', + Writable => 'int16u', + PrintConv => { + 1 => 'None', # (not standard EXIF) + 2 => 'inches', + 3 => 'cm', + 4 => 'mm', # (not standard EXIF) + 5 => 'um', # (not standard EXIF) + }, + }, + 0xa211 => 'ImageNumber', + 0xa212 => 'SecurityClassification', + 0xa213 => 'ImageHistory', + 0xa214 => { + Name => 'SubjectLocation', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + Count => 2, + }, + 0xa215 => { Name => 'ExposureIndex', Writable => 'rational64u' }, + 0xa216 => 'TIFF-EPStandardID', + 0xa217 => { + Name => 'SensingMethod', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + PrintConv => { + 1 => 'Not defined', + 2 => 'One-chip color area', + 3 => 'Two-chip color area', + 4 => 'Three-chip color area', + 5 => 'Color sequential area', + 7 => 'Trilinear', + 8 => 'Color sequential linear', + # 15 - used by DJI XT2 + }, + }, + 0xa300 => { + Name => 'FileSource', + Writable => 'undef', + ValueConvInv => '($val=~/^\d+$/ and $val < 256) ? chr($val) : $val', + PrintConv => { + 1 => 'Film Scanner', + 2 => 'Reflection Print Scanner', + 3 => 'Digital Camera', + # handle the case where Sigma incorrectly gives this tag a count of 4 + "\3\0\0\0" => 'Sigma Digital Camera', + }, + }, + 0xa301 => { + Name => 'SceneType', + Writable => 'undef', + ValueConvInv => 'chr($val & 0xff)', + PrintConv => { + 1 => 'Directly photographed', + }, + }, + 0xa302 => { + Name => 'CFAPattern', + Writable => 'undef', + RawConv => 'Image::ExifTool::Exif::DecodeCFAPattern($self, $val)', + RawConvInv => q{ + my @a = split ' ', $val; + return $val if @a <= 2; # also accept binary data for backward compatibility + return pack(GetByteOrder() eq 'II' ? 'v2C*' : 'n2C*', @a); + }, + PrintConv => 'Image::ExifTool::Exif::PrintCFAPattern($val)', + PrintConvInv => 'Image::ExifTool::Exif::GetCFAPattern($val)', + }, + 0xa401 => { + Name => 'CustomRendered', + Writable => 'int16u', + Notes => q{ + only 0 and 1 are standard EXIF, but other values are used by Apple iOS + devices + }, + PrintConv => { + 0 => 'Normal', + 1 => 'Custom', + 2 => 'HDR (no original saved)', #32 non-standard (Apple iOS) + 3 => 'HDR (original saved)', #32 non-standard (Apple iOS) + 4 => 'Original (for HDR)', #32 non-standard (Apple iOS) + 6 => 'Panorama', # non-standard (Apple iOS, horizontal or vertical) + 7 => 'Portrait HDR', #32 non-standard (Apple iOS) + 8 => 'Portrait', # non-standard (Apple iOS, blurred background) + # 9 - also seen (Apple iOS) (HDR Portrait?) + }, + }, + 0xa402 => { + Name => 'ExposureMode', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Manual', + 2 => 'Auto bracket', + # have seen 3 from Samsung EX1, NX30, NX200 - PH + }, + }, + 0xa403 => { + Name => 'WhiteBalance', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + # set Priority to zero to keep this WhiteBalance from overriding the + # MakerNotes WhiteBalance, since the MakerNotes WhiteBalance and is more + # accurate and contains more information (if it exists) + Priority => 0, + PrintConv => { + 0 => 'Auto', + 1 => 'Manual', + }, + }, + 0xa404 => { + Name => 'DigitalZoomRatio', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + }, + 0xa405 => { + Name => 'FocalLengthIn35mmFormat', + Notes => 'called FocalLengthIn35mmFilm by the EXIF spec.', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + 0xa406 => { + Name => 'SceneCaptureType', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + Notes => 'the value of 4 is non-standard, and used by some Samsung models', + PrintConv => { + 0 => 'Standard', + 1 => 'Landscape', + 2 => 'Portrait', + 3 => 'Night', + 4 => 'Other', # (non-standard Samsung, ref forum 5724) + }, + }, + 0xa407 => { + Name => 'GainControl', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + PrintConv => { + 0 => 'None', + 1 => 'Low gain up', + 2 => 'High gain up', + 3 => 'Low gain down', + 4 => 'High gain down', + }, + }, + 0xa408 => { + Name => 'Contrast', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'Low', + 2 => 'High', + }, + PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)', + }, + 0xa409 => { + Name => 'Saturation', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'Low', + 2 => 'High', + }, + PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)', + }, + 0xa40a => { + Name => 'Sharpness', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'Soft', + 2 => 'Hard', + }, + PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)', + }, + 0xa40b => { + Name => 'DeviceSettingDescription', + Groups => { 2 => 'Camera' }, + Binary => 1, + }, + 0xa40c => { + Name => 'SubjectDistanceRange', + Groups => { 2 => 'Camera' }, + Writable => 'int16u', + PrintConv => { + 0 => 'Unknown', + 1 => 'Macro', + 2 => 'Close', + 3 => 'Distant', + }, + }, + # 0xa40d - int16u: 0 (GE E1486 TW) + # 0xa40e - int16u: 1 (GE E1486 TW) + 0xa420 => { Name => 'ImageUniqueID', Writable => 'string' }, + 0xa430 => { #24 + Name => 'OwnerName', + Notes => 'called CameraOwnerName by the EXIF spec.', + Writable => 'string', + }, + 0xa431 => { #24 + Name => 'SerialNumber', + Notes => 'called BodySerialNumber by the EXIF spec.', + Writable => 'string', + }, + 0xa432 => { #24 + Name => 'LensInfo', + Notes => q{ + 4 rational values giving focal and aperture ranges, called LensSpecification + by the EXIF spec. + }, + Writable => 'rational64u', + Count => 4, + # convert to the form "12-20mm f/3.8-4.5" or "50mm f/1.4" + PrintConv => \&PrintLensInfo, + PrintConvInv => \&ConvertLensInfo, + }, + 0xa433 => { Name => 'LensMake', Writable => 'string' }, #24 + 0xa434 => { Name => 'LensModel', Writable => 'string' }, #24 + 0xa435 => { Name => 'LensSerialNumber', Writable => 'string' }, #24 + 0xa436 => { Name => 'Title', Writable => 'string', Avoid => 1 }, #33 + 0xa437 => { Name => 'Photographer', Writable => 'string' }, #33 + 0xa438 => { Name => 'ImageEditor', Writable => 'string' }, #33 + 0xa439 => { Name => 'CameraFirmware', Writable => 'string' }, #33 + 0xa43a => { Name => 'RAWDevelopingSoftware', Writable => 'string' }, #33 + 0xa43b => { Name => 'ImageEditingSoftware', Writable => 'string' }, #33 + 0xa43c => { Name => 'MetadataEditingSoftware', Writable => 'string' }, #33 + 0xa460 => { #Exif2.32 + Name => 'CompositeImage', + Writable => 'int16u', + PrintConv => { + 0 => 'Unknown', + 1 => 'Not a Composite Image', + 2 => 'General Composite Image', + 3 => 'Composite Image Captured While Shooting', + }, + }, + 0xa461 => { #Exif2.32 + Name => 'CompositeImageCount', + Notes => q{ + 2 values: 1. Number of source images, 2. Number of images used. Called + SourceImageNumberOfCompositeImage by the EXIF spec. + }, + Writable => 'int16u', + Count => 2, + }, + 0xa462 => { #Exif2.32 + Name => 'CompositeImageExposureTimes', + Notes => q{ + 11 or more values: 1. Total exposure time period, 2. Total exposure of all + source images, 3. Total exposure of all used images, 4. Max exposure time of + source images, 5. Max exposure time of used images, 6. Min exposure time of + source images, 7. Min exposure of used images, 8. Number of sequences, 9. + Number of source images in sequence. 10-N. Exposure times of each source + image. Called SourceExposureTimesOfCompositeImage by the EXIF spec. + }, + Writable => 'undef', + RawConv => sub { + my $val = shift; + my @v; + my $i = 0; + for (;;) { + if ($i == 56 or $i == 58) { + last if $i + 2 > length $val; + push @v, Get16u(\$val, $i); + $i += 2; + } else { + last if $i + 8 > length $val; + push @v, Image::ExifTool::GetRational64u(\$val, $i); + $i += 8; + } + } + return join ' ', @v; + }, + RawConvInv => sub { + my $val = shift; + my @v = split ' ', $val; + my $i; + for ($i=0; ; ++$i) { + last unless defined $v[$i]; + $v[$i] = ($i == 7 or $i == 8) ? Set16u($v[$i]) : Image::ExifTool::SetRational64u($v[$i]); + } + return join '', @v; + }, + PrintConv => sub { + my $val = shift; + my @v = split ' ', $val; + my $i; + for ($i=0; ; ++$i) { + last unless defined $v[$i]; + $v[$i] = PrintExposureTime($v[$i]) unless $i == 7 or $i == 8; + } + return join ' ', @v; + }, + PrintConvInv => '$val', + }, + 0xa480 => { Name => 'GDALMetadata', Writable => 'string', WriteGroup => 'IFD0' }, #3 + 0xa481 => { Name => 'GDALNoData', Writable => 'string', WriteGroup => 'IFD0' }, #3 + 0xa500 => { Name => 'Gamma', Writable => 'rational64u' }, + 0xafc0 => 'ExpandSoftware', #JD (Opanda) + 0xafc1 => 'ExpandLens', #JD (Opanda) + 0xafc2 => 'ExpandFilm', #JD (Opanda) + 0xafc3 => 'ExpandFilterLens', #JD (Opanda) + 0xafc4 => 'ExpandScanner', #JD (Opanda) + 0xafc5 => 'ExpandFlashLamp', #JD (Opanda) + 0xb4c3 => { Name => 'HasselbladRawImage', Format => 'undef', Binary => 1 }, #IB +# +# Windows Media Photo / HD Photo (WDP/HDP) tags +# + 0xbc01 => { #13 + Name => 'PixelFormat', + PrintHex => 1, + Format => 'undef', + Notes => q{ + tags 0xbc** are used in Windows HD Photo (HDP and WDP) images. The actual + PixelFormat values are 16-byte GUID's but the leading 15 bytes, + '6fddc324-4e03-4bfe-b1853-d77768dc9', have been removed below to avoid + unnecessary clutter + }, + ValueConv => q{ + require Image::ExifTool::ASF; + $val = Image::ExifTool::ASF::GetGUID($val); + # GUID's are too long, so remove redundant information + $val =~ s/^6fddc324-4e03-4bfe-b185-3d77768dc9//i and $val = hex($val); + return $val; + }, + PrintConv => { + 0x0d => '24-bit RGB', + 0x0c => '24-bit BGR', + 0x0e => '32-bit BGR', + 0x15 => '48-bit RGB', + 0x12 => '48-bit RGB Fixed Point', + 0x3b => '48-bit RGB Half', + 0x18 => '96-bit RGB Fixed Point', + 0x1b => '128-bit RGB Float', + 0x0f => '32-bit BGRA', + 0x16 => '64-bit RGBA', + 0x1d => '64-bit RGBA Fixed Point', + 0x3a => '64-bit RGBA Half', + 0x1e => '128-bit RGBA Fixed Point', + 0x19 => '128-bit RGBA Float', + 0x10 => '32-bit PBGRA', + 0x17 => '64-bit PRGBA', + 0x1a => '128-bit PRGBA Float', + 0x1c => '32-bit CMYK', + 0x2c => '40-bit CMYK Alpha', + 0x1f => '64-bit CMYK', + 0x2d => '80-bit CMYK Alpha', + 0x20 => '24-bit 3 Channels', + 0x21 => '32-bit 4 Channels', + 0x22 => '40-bit 5 Channels', + 0x23 => '48-bit 6 Channels', + 0x24 => '56-bit 7 Channels', + 0x25 => '64-bit 8 Channels', + 0x2e => '32-bit 3 Channels Alpha', + 0x2f => '40-bit 4 Channels Alpha', + 0x30 => '48-bit 5 Channels Alpha', + 0x31 => '56-bit 6 Channels Alpha', + 0x32 => '64-bit 7 Channels Alpha', + 0x33 => '72-bit 8 Channels Alpha', + 0x26 => '48-bit 3 Channels', + 0x27 => '64-bit 4 Channels', + 0x28 => '80-bit 5 Channels', + 0x29 => '96-bit 6 Channels', + 0x2a => '112-bit 7 Channels', + 0x2b => '128-bit 8 Channels', + 0x34 => '64-bit 3 Channels Alpha', + 0x35 => '80-bit 4 Channels Alpha', + 0x36 => '96-bit 5 Channels Alpha', + 0x37 => '112-bit 6 Channels Alpha', + 0x38 => '128-bit 7 Channels Alpha', + 0x39 => '144-bit 8 Channels Alpha', + 0x08 => '8-bit Gray', + 0x0b => '16-bit Gray', + 0x13 => '16-bit Gray Fixed Point', + 0x3e => '16-bit Gray Half', + 0x3f => '32-bit Gray Fixed Point', + 0x11 => '32-bit Gray Float', + 0x05 => 'Black & White', + 0x09 => '16-bit BGR555', + 0x0a => '16-bit BGR565', + 0x13 => '32-bit BGR101010', + 0x3d => '32-bit RGBE', + }, + }, + 0xbc02 => { #13 + Name => 'Transformation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Mirror vertical', + 2 => 'Mirror horizontal', + 3 => 'Rotate 180', + 4 => 'Rotate 90 CW', + 5 => 'Mirror horizontal and rotate 90 CW', + 6 => 'Mirror horizontal and rotate 270 CW', + 7 => 'Rotate 270 CW', + }, + }, + 0xbc03 => { #13 + Name => 'Uncompressed', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0xbc04 => { #13 + Name => 'ImageType', + PrintConv => { BITMASK => { + 0 => 'Preview', + 1 => 'Page', + } }, + }, + 0xbc80 => 'ImageWidth', #13 + 0xbc81 => 'ImageHeight', #13 + 0xbc82 => 'WidthResolution', #13 + 0xbc83 => 'HeightResolution', #13 + 0xbcc0 => { #13 + Name => 'ImageOffset', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0xbcc1, # point to associated byte count + }, + 0xbcc1 => { #13 + Name => 'ImageByteCount', + OffsetPair => 0xbcc0, # point to associated offset + }, + 0xbcc2 => { #13 + Name => 'AlphaOffset', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0xbcc3, # point to associated byte count + }, + 0xbcc3 => { #13 + Name => 'AlphaByteCount', + OffsetPair => 0xbcc2, # point to associated offset + }, + 0xbcc4 => { #13 + Name => 'ImageDataDiscard', + PrintConv => { + 0 => 'Full Resolution', + 1 => 'Flexbits Discarded', + 2 => 'HighPass Frequency Data Discarded', + 3 => 'Highpass and LowPass Frequency Data Discarded', + }, + }, + 0xbcc5 => { #13 + Name => 'AlphaDataDiscard', + PrintConv => { + 0 => 'Full Resolution', + 1 => 'Flexbits Discarded', + 2 => 'HighPass Frequency Data Discarded', + 3 => 'Highpass and LowPass Frequency Data Discarded', + }, + }, +# + 0xc427 => 'OceScanjobDesc', #3 + 0xc428 => 'OceApplicationSelector', #3 + 0xc429 => 'OceIDNumber', #3 + 0xc42a => 'OceImageLogic', #3 + 0xc44f => { Name => 'Annotations', Binary => 1 }, #7/19 + 0xc4a5 => { + Name => 'PrintIM', # (writable directory!) + # must set Writable here so this tag will be saved with MakerNotes option + Writable => 'undef', + WriteGroup => 'IFD0', + Binary => 1, + # (don't make Binary/Protected because we can't copy individual PrintIM tags anyway) + Description => 'Print Image Matching', + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + PrintConvInv => '$val =~ /^PrintIM/ ? $val : undef', # quick validation + }, + 0xc51b => { # (Hasselblad H3D) + Name => 'HasselbladExif', + Format => 'undef', + RawConv => q{ + $$self{DOC_NUM} = ++$$self{DOC_COUNT}; + $self->ExtractInfo(\$val, { ReEntry => 1 }); + $$self{DOC_NUM} = 0; + return undef; + }, + }, + 0xc573 => { #PH + Name => 'OriginalFileName', + Notes => 'used by some obscure software', # (possibly Swizzy Photosmacker?) + # (it is a 'string', but obscure, so don't make it writable) + }, + 0xc580 => { #20 + Name => 'USPTOOriginalContentType', + PrintConv => { + 0 => 'Text or Drawing', + 1 => 'Grayscale', + 2 => 'Color', + }, + }, + # 0xc5d8 - found in CR2 images + # 0xc5d9 - found in CR2 images + 0xc5e0 => { #forum8153 (CR2 images) + Name => 'CR2CFAPattern', + ValueConv => { + 1 => '0 1 1 2', + 2 => '2 1 1 0', + 3 => '1 2 0 1', + 4 => '1 0 2 1', + }, + PrintConv => { + '0 1 1 2' => '[Red,Green][Green,Blue]', + '2 1 1 0' => '[Blue,Green][Green,Red]', + '1 2 0 1' => '[Green,Blue][Red,Green]', + '1 0 2 1' => '[Green,Red][Blue,Green]', + }, + }, +# +# DNG tags 0xc6XX, 0xc7XX and 0xcdXX (ref 2 unless otherwise stated) +# + 0xc612 => { + Name => 'DNGVersion', + Notes => q{ + tags 0xc612-0xcd3b are defined by the DNG specification unless otherwise + noted. See L<https://helpx.adobe.com/photoshop/digital-negative.html> for + the specification + }, + Writable => 'int8u', + WriteGroup => 'IFD0', + Count => 4, + Protected => 1, # (confuses Apple Preview if written to a TIFF image) + DataMember => 'DNGVersion', + RawConv => '$$self{DNGVersion} = $val', + PrintConv => '$val =~ tr/ /./; $val', + PrintConvInv => '$val =~ tr/./ /; $val', + }, + 0xc613 => { + Name => 'DNGBackwardVersion', + Writable => 'int8u', + WriteGroup => 'IFD0', + Count => 4, + Protected => 1, + PrintConv => '$val =~ tr/ /./; $val', + PrintConvInv => '$val =~ tr/./ /; $val', + }, + 0xc614 => { + Name => 'UniqueCameraModel', + Writable => 'string', + WriteGroup => 'IFD0', + }, + 0xc615 => { + Name => 'LocalizedCameraModel', + WriteGroup => 'IFD0', + %utf8StringConv, + PrintConv => '$self->Printable($val, 0)', + PrintConvInv => '$val', + }, + 0xc616 => { + Name => 'CFAPlaneColor', + WriteGroup => 'SubIFD', # (only for Validate) + PrintConv => q{ + my @cols = qw(Red Green Blue Cyan Magenta Yellow White); + my @vals = map { $cols[$_] || "Unknown($_)" } split(' ', $val); + return join(',', @vals); + }, + }, + 0xc617 => { + Name => 'CFALayout', + WriteGroup => 'SubIFD', # (only for Validate) + PrintConv => { + 1 => 'Rectangular', + 2 => 'Even columns offset down 1/2 row', + 3 => 'Even columns offset up 1/2 row', + 4 => 'Even rows offset right 1/2 column', + 5 => 'Even rows offset left 1/2 column', + # the following are new for DNG 1.3: + 6 => 'Even rows offset up by 1/2 row, even columns offset left by 1/2 column', + 7 => 'Even rows offset up by 1/2 row, even columns offset right by 1/2 column', + 8 => 'Even rows offset down by 1/2 row, even columns offset left by 1/2 column', + 9 => 'Even rows offset down by 1/2 row, even columns offset right by 1/2 column', + }, + }, + 0xc618 => { + Name => 'LinearizationTable', + Writable => 'int16u', + WriteGroup => 'SubIFD', + Count => -1, + Protected => 1, + Binary => 1, + }, + 0xc619 => { + Name => 'BlackLevelRepeatDim', + Writable => 'int16u', + WriteGroup => 'SubIFD', + Count => 2, + Protected => 1, + }, + 0xc61a => { + Name => 'BlackLevel', + Writable => 'rational64u', + WriteGroup => 'SubIFD', + Count => -1, + Protected => 1, + }, + 0xc61b => { + Name => 'BlackLevelDeltaH', + %longBin, + Writable => 'rational64s', + WriteGroup => 'SubIFD', + Count => -1, + Protected => 1, + }, + 0xc61c => { + Name => 'BlackLevelDeltaV', + %longBin, + Writable => 'rational64s', + WriteGroup => 'SubIFD', + Count => -1, + Protected => 1, + }, + 0xc61d => { + Name => 'WhiteLevel', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Count => -1, + Protected => 1, + }, + 0xc61e => { + Name => 'DefaultScale', + Writable => 'rational64u', + WriteGroup => 'SubIFD', + Count => 2, + Protected => 1, + }, + 0xc61f => { + Name => 'DefaultCropOrigin', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Count => 2, + Protected => 1, + }, + 0xc620 => { + Name => 'DefaultCropSize', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Count => 2, + Protected => 1, + }, + 0xc621 => { + Name => 'ColorMatrix1', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc622 => { + Name => 'ColorMatrix2', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc623 => { + Name => 'CameraCalibration1', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc624 => { + Name => 'CameraCalibration2', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc625 => { + Name => 'ReductionMatrix1', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc626 => { + Name => 'ReductionMatrix2', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc627 => { + Name => 'AnalogBalance', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc628 => { + Name => 'AsShotNeutral', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc629 => { + Name => 'AsShotWhiteXY', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Count => 2, + Protected => 1, + }, + 0xc62a => { + Name => 'BaselineExposure', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xc62b => { + Name => 'BaselineNoise', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xc62c => { + Name => 'BaselineSharpness', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xc62d => { + Name => 'BayerGreenSplit', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Protected => 1, + }, + 0xc62e => { + Name => 'LinearResponseLimit', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xc62f => { + Name => 'CameraSerialNumber', + Groups => { 2 => 'Camera' }, + Writable => 'string', + WriteGroup => 'IFD0', + }, + 0xc630 => { + Name => 'DNGLensInfo', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + WriteGroup => 'IFD0', + Count => 4, + PrintConv =>\&PrintLensInfo, + PrintConvInv => \&ConvertLensInfo, + }, + 0xc631 => { + Name => 'ChromaBlurRadius', + Writable => 'rational64u', + WriteGroup => 'SubIFD', + Protected => 1, + }, + 0xc632 => { + Name => 'AntiAliasStrength', + Writable => 'rational64u', + WriteGroup => 'SubIFD', + Protected => 1, + }, + 0xc633 => { + Name => 'ShadowScale', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xc634 => [ + { + Condition => '$$self{TIFF_TYPE} =~ /^(ARW|SR2)$/', + Name => 'SR2Private', + Groups => { 1 => 'SR2' }, + Flags => 'SubIFD', + Format => 'int32u', + # some utilities have problems unless this is int8u format: + # - Adobe Camera Raw 5.3 gives an error + # - Apple Preview 10.5.8 gets the wrong white balance + FixFormat => 'int8u', # (stupid Sony) + WriteGroup => 'IFD0', # (for Validate) + SubDirectory => { + DirName => 'SR2Private', + TagTable => 'Image::ExifTool::Sony::SR2Private', + Start => '$val', + }, + }, + { + Condition => '$$valPt =~ /^Adobe\0/', + Name => 'DNGAdobeData', + Flags => [ 'Binary', 'Protected' ], + Writable => 'undef', # (writable directory!) (to make it possible to delete this mess) + WriteGroup => 'IFD0', + NestedHtmlDump => 1, + SubDirectory => { TagTable => 'Image::ExifTool::DNG::AdobeData' }, + Format => 'undef', # but written as int8u (change to undef for speed) + }, + { + # Pentax/Samsung models that write AOC maker notes in JPG images: + # K-5,K-7,K-m,K-x,K-r,K10D,K20D,K100D,K110D,K200D,K2000,GX10,GX20 + # (Note: the following expression also appears in WriteExif.pl) + Condition => q{ + $$valPt =~ /^(PENTAX |SAMSUNG)\0/ and + $$self{Model} =~ /\b(K(-[57mrx]|(10|20|100|110|200)D|2000)|GX(10|20))\b/ + }, + Name => 'MakerNotePentax', + MakerNotes => 1, # (causes "MakerNotes header" to be identified in HtmlDump output) + Binary => 1, + WriteGroup => 'IFD0', # (for Validate) + # Note: Don't make this block-writable for a few reasons: + # 1) It would be dangerous (possibly confusing Pentax software) + # 2) It is a different format from the JPEG version of MakerNotePentax + # 3) It is converted to JPEG format by RebuildMakerNotes() when copying + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + Start => '$valuePtr + 10', + Base => '$start - 10', + ByteOrder => 'Unknown', # easier to do this than read byteorder word + }, + Format => 'undef', # but written as int8u (change to undef for speed) + }, + { + # must duplicate the above tag with a different name for more recent + # Pentax models which use the "PENTAX" instead of the "AOC" maker notes + # in JPG images (needed when copying maker notes from DNG to JPG) + Condition => '$$valPt =~ /^(PENTAX |SAMSUNG)\0/', + Name => 'MakerNotePentax5', + MakerNotes => 1, + Binary => 1, + WriteGroup => 'IFD0', # (for Validate) + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + Start => '$valuePtr + 10', + Base => '$start - 10', + ByteOrder => 'Unknown', + }, + Format => 'undef', + }, + { + # Ricoh models such as the GR III + Condition => '$$valPt =~ /^RICOH\0(II|MM)/', + Name => 'MakerNoteRicohPentax', + MakerNotes => 1, + Binary => 1, + WriteGroup => 'IFD0', # (for Validate) + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + Start => '$valuePtr + 8', + Base => '$start - 8', + ByteOrder => 'Unknown', + }, + Format => 'undef', + }, + # the DJI FC2103 writes some interesting stuff here (with sections labelled + # awb_dbg_info, ae_dbg_info, ae_histogram_info, af_dbg_info, hiso, xidiri) - PH + { + Name => 'MakerNoteDJIInfo', + Condition => '$$valPt =~ /^\[ae_dbg_info:/', + MakerNotes => 1, + Binary => 1, + WriteGroup => 'IFD0', # (for Validate) + SubDirectory => { TagTable => 'Image::ExifTool::DJI::Info' }, + Format => 'undef', + }, + { + Name => 'DNGPrivateData', + Flags => [ 'Binary', 'Protected' ], + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + }, + ], + 0xc635 => { + Name => 'MakerNoteSafety', + Writable => 'int16u', + WriteGroup => 'IFD0', + PrintConv => { + 0 => 'Unsafe', + 1 => 'Safe', + }, + }, + 0xc640 => { #15 + Name => 'RawImageSegmentation', + # (int16u[3], not writable) + Notes => q{ + used in segmented Canon CR2 images. 3 numbers: 1. Number of segments minus + one; 2. Pixel width of segments except last; 3. Pixel width of last segment + }, + }, + 0xc65a => { + Name => 'CalibrationIlluminant1', + Writable => 'int16u', + WriteGroup => 'IFD0', + Protected => 1, + SeparateTable => 'LightSource', + PrintConv => \%lightSource, + }, + 0xc65b => { + Name => 'CalibrationIlluminant2', + Writable => 'int16u', + WriteGroup => 'IFD0', + Protected => 1, + SeparateTable => 'LightSource', + PrintConv => \%lightSource, + }, + 0xc65c => { + Name => 'BestQualityScale', + Writable => 'rational64u', + WriteGroup => 'SubIFD', + Protected => 1, + }, + 0xc65d => { + Name => 'RawDataUniqueID', + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + Count => 16, + Protected => 1, + ValueConv => 'uc(unpack("H*",$val))', + ValueConvInv => 'pack("H*", $val)', + }, + 0xc660 => { #3 + Name => 'AliasLayerMetadata', + Notes => 'used by Alias Sketchbook Pro', + }, + 0xc68b => { + Name => 'OriginalRawFileName', + WriteGroup => 'IFD0', + Protected => 1, + %utf8StringConv, + }, + 0xc68c => { + Name => 'OriginalRawFileData', # (writable directory!) + Writable => 'undef', # must be defined here so tag will be extracted if specified + WriteGroup => 'IFD0', + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { + TagTable => 'Image::ExifTool::DNG::OriginalRaw', + }, + }, + 0xc68d => { + Name => 'ActiveArea', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Count => 4, + Protected => 1, + }, + 0xc68e => { + Name => 'MaskedAreas', + Writable => 'int32u', + WriteGroup => 'SubIFD', + Count => -1, + Protected => 1, + }, + 0xc68f => { + Name => 'AsShotICCProfile', # (writable directory) + Binary => 1, + Writable => 'undef', # must be defined here so tag will be extracted if specified + WriteGroup => 'IFD0', + Protected => 1, + WriteCheck => q{ + require Image::ExifTool::ICC_Profile; + return Image::ExifTool::ICC_Profile::ValidateICC(\$val); + }, + SubDirectory => { + DirName => 'AsShotICCProfile', + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + }, + 0xc690 => { + Name => 'AsShotPreProfileMatrix', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc691 => { + Name => 'CurrentICCProfile', # (writable directory) + Binary => 1, + Writable => 'undef', # must be defined here so tag will be extracted if specified + SubDirectory => { + DirName => 'CurrentICCProfile', + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + Writable => 'undef', + WriteGroup => 'IFD0', + Protected => 1, + WriteCheck => q{ + require Image::ExifTool::ICC_Profile; + return Image::ExifTool::ICC_Profile::ValidateICC(\$val); + }, + }, + 0xc692 => { + Name => 'CurrentPreProfileMatrix', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc6bf => { + Name => 'ColorimetricReference', + Writable => 'int16u', + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xc6c5 => { Name => 'SRawType', Description => 'SRaw Type', WriteGroup => 'IFD0' }, #exifprobe (CR2 proprietary) + 0xc6d2 => { #JD (Panasonic DMC-TZ5) + # this text is UTF-8 encoded (hooray!) - PH (TZ5) + Name => 'PanasonicTitle', + Format => 'string', # written incorrectly as 'undef' + Notes => 'proprietary Panasonic tag used for baby/pet name, etc', + Writable => 'undef', + WriteGroup => 'IFD0', + # panasonic always records this tag (64 zero bytes), + # so ignore it unless it contains valid information + RawConv => 'length($val) ? $val : undef', + ValueConv => '$self->Decode($val, "UTF8")', + ValueConvInv => '$self->Encode($val,"UTF8")', + }, + 0xc6d3 => { #PH (Panasonic DMC-FS7) + Name => 'PanasonicTitle2', + Format => 'string', # written incorrectly as 'undef' + Notes => 'proprietary Panasonic tag used for baby/pet name with age', + Writable => 'undef', + WriteGroup => 'IFD0', + # panasonic always records this tag (128 zero bytes), + # so ignore it unless it contains valid information + RawConv => 'length($val) ? $val : undef', + ValueConv => '$self->Decode($val, "UTF8")', + ValueConvInv => '$self->Encode($val,"UTF8")', + }, + # 0xc6dc - int32u[4]: found in CR2 images (PH, 7DmkIII) + # 0xc6dd - int16u[256]: found in CR2 images (PH, 5DmkIV) + 0xc6f3 => { + Name => 'CameraCalibrationSig', + WriteGroup => 'IFD0', + Protected => 1, + %utf8StringConv, + }, + 0xc6f4 => { + Name => 'ProfileCalibrationSig', + WriteGroup => 'IFD0', + Protected => 1, + %utf8StringConv, + }, + 0xc6f5 => { + Name => 'ProfileIFD', # (ExtraCameraProfiles) + Groups => { 1 => 'ProfileIFD' }, + Flags => 'SubIFD', + WriteGroup => 'IFD0', # (only for Validate) + SubDirectory => { + ProcessProc => \&ProcessTiffIFD, + WriteProc => \&ProcessTiffIFD, + DirName => 'ProfileIFD', + Start => '$val', + Base => '$start', # offsets relative to start of TIFF-like header + MaxSubdirs => 10, + Magic => 0x4352, # magic number for TIFF-like header + }, + }, + 0xc6f6 => { + Name => 'AsShotProfileName', + WriteGroup => 'IFD0', + Protected => 1, + %utf8StringConv, + }, + 0xc6f7 => { + Name => 'NoiseReductionApplied', + Writable => 'rational64u', + WriteGroup => 'SubIFD', + Protected => 1, + }, + 0xc6f8 => { + Name => 'ProfileName', + WriteGroup => 'IFD0', + Protected => 1, + %utf8StringConv, + }, + 0xc6f9 => { + Name => 'ProfileHueSatMapDims', + Writable => 'int32u', + WriteGroup => 'IFD0', + Count => 3, + Protected => 1, + }, + 0xc6fa => { + Name => 'ProfileHueSatMapData1', + %longBin, + Writable => 'float', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc6fb => { + Name => 'ProfileHueSatMapData2', + %longBin, + Writable => 'float', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc6fc => { + Name => 'ProfileToneCurve', + %longBin, + Writable => 'float', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc6fd => { + Name => 'ProfileEmbedPolicy', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 1, + PrintConv => { + 0 => 'Allow Copying', + 1 => 'Embed if Used', + 2 => 'Never Embed', + 3 => 'No Restrictions', + }, + }, + 0xc6fe => { + Name => 'ProfileCopyright', + WriteGroup => 'IFD0', + Protected => 1, + %utf8StringConv, + }, + 0xc714 => { + Name => 'ForwardMatrix1', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc715 => { + Name => 'ForwardMatrix2', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc716 => { + Name => 'PreviewApplicationName', + WriteGroup => 'IFD0', + Protected => 1, + %utf8StringConv, + }, + 0xc717 => { + Name => 'PreviewApplicationVersion', + Writable => 'string', + WriteGroup => 'IFD0', + Protected => 1, + %utf8StringConv, + }, + 0xc718 => { + Name => 'PreviewSettingsName', + Writable => 'string', + WriteGroup => 'IFD0', + Protected => 1, + %utf8StringConv, + }, + 0xc719 => { + Name => 'PreviewSettingsDigest', + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + Protected => 1, + ValueConv => 'unpack("H*", $val)', + ValueConvInv => 'pack("H*", $val)', + }, + 0xc71a => { + Name => 'PreviewColorSpace', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 1, + PrintConv => { + 0 => 'Unknown', + 1 => 'Gray Gamma 2.2', + 2 => 'sRGB', + 3 => 'Adobe RGB', + 4 => 'ProPhoto RGB', + }, + }, + 0xc71b => { + Name => 'PreviewDateTime', + Groups => { 2 => 'Time' }, + Writable => 'string', + Shift => 'Time', + WriteGroup => 'IFD0', + Protected => 1, + ValueConv => q{ + require Image::ExifTool::XMP; + return Image::ExifTool::XMP::ConvertXMPDate($val); + }, + ValueConvInv => q{ + require Image::ExifTool::XMP; + return Image::ExifTool::XMP::FormatXMPDate($val); + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,1,1)', + }, + 0xc71c => { + Name => 'RawImageDigest', + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + Count => 16, + Protected => 1, + ValueConv => 'unpack("H*", $val)', + ValueConvInv => 'pack("H*", $val)', + }, + 0xc71d => { + Name => 'OriginalRawFileDigest', + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + Count => 16, + Protected => 1, + ValueConv => 'unpack("H*", $val)', + ValueConvInv => 'pack("H*", $val)', + }, + 0xc71e => 'SubTileBlockSize', + 0xc71f => 'RowInterleaveFactor', + 0xc725 => { + Name => 'ProfileLookTableDims', + Writable => 'int32u', + WriteGroup => 'IFD0', + Count => 3, + Protected => 1, + }, + 0xc726 => { + Name => 'ProfileLookTableData', + %longBin, + Writable => 'float', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xc740 => { Name => 'OpcodeList1', %opcodeInfo }, # DNG 1.3 + 0xc741 => { Name => 'OpcodeList2', %opcodeInfo }, # DNG 1.3 + 0xc74e => { Name => 'OpcodeList3', %opcodeInfo }, # DNG 1.3 + 0xc761 => { # DNG 1.3 + Name => 'NoiseProfile', + Writable => 'double', + WriteGroup => 'SubIFD', + Count => -1, + Protected => 1, + }, + 0xc763 => { #28 + Name => 'TimeCodes', + Writable => 'int8u', + WriteGroup => 'IFD0', + Count => -1, # (8 * number of time codes, max 10) + ValueConv => q{ + my @a = split ' ', $val; + my @v; + push @v, join('.', map { sprintf('%.2x',$_) } splice(@a,0,8)) while @a >= 8; + join ' ', @v; + }, + ValueConvInv => q{ + my @a = map hex, split /[. ]+/, $val; + join ' ', @a; + }, + # Note: Currently ignore the flags: + # byte 0 0x80 - color frame + # byte 0 0x40 - drop frame + # byte 1 0x80 - field phase + PrintConv => q{ + my @a = map hex, split /[. ]+/, $val; + my @v; + while (@a >= 8) { + my $str = sprintf("%.2x:%.2x:%.2x.%.2x", $a[3]&0x3f, + $a[2]&0x7f, $a[1]&0x7f, $a[0]&0x3f); + if ($a[3] & 0x80) { # date+timezone exist if BGF2 is set + my $tz = $a[7] & 0x3f; + my $bz = sprintf('%.2x', $tz); + $bz = 100 if $bz =~ /[a-f]/i; # not BCD + if ($bz < 26) { + $tz = ($bz < 13 ? 0 : 26) - $bz; + } elsif ($bz == 32) { + $tz = 12.75; + } elsif ($bz >= 28 and $bz <= 31) { + $tz = 0; # UTC + } elsif ($bz < 100) { + undef $tz; # undefined or user-defined + } elsif ($tz < 0x20) { + $tz = (($tz < 0x10 ? 10 : 20) - $tz) - 0.5; + } else { + $tz = (($tz < 0x30 ? 53 : 63) - $tz) + 0.5; + } + if ($a[7] & 0x80) { # MJD format (/w UTC time) + my ($h,$m,$s,$f) = split /[:.]/, $str; + my $jday = sprintf('%x%.2x%.2x', reverse @a[4..6]); + $str = ConvertUnixTime(($jday - 40587) * 24 * 3600 + + ((($h+$tz) * 60) + $m) * 60 + $s) . ".$f"; + $str =~ s/^(\d+):(\d+):(\d+) /$1-$2-${3}T/; + } else { # YYMMDD (Note: CinemaDNG 1.1 example seems wrong) + my $yr = sprintf('%.2x',$a[6]) + 1900; + $yr += 100 if $yr < 1970; + $str = sprintf('%d-%.2x-%.2xT%s',$yr,$a[5],$a[4],$str); + } + $str .= TimeZoneString($tz*60) if defined $tz; + } + push @v, $str; + splice @a, 0, 8; + } + join ' ', @v; + }, + PrintConvInv => q{ + my @a = split ' ', $val; + my @v; + foreach (@a) { + my @td = reverse split /T/; + my $tz = 0x39; # default to unknown timezone + if ($td[0] =~ s/([-+])(\d+):(\d+)$//) { + if ($3 == 0) { + $tz = hex(($1 eq '-') ? $2 : 0x26 - $2); + } elsif ($3 == 30) { + if ($1 eq '-') { + $tz = $2 + 0x0a; + $tz += 0x0a if $tz > 0x0f; + } else { + $tz = 0x3f - $2; + $tz -= 0x0a if $tz < 0x3a; + } + } elsif ($3 == 45) { + $tz = 0x32 if $1 eq '+' and $2 == 12; + } + } + my @t = split /[:.]/, $td[0]; + push @t, '00' while @t < 4; + my $bg; + if ($td[1]) { + # date was specified: fill in date & timezone + my @d = split /[-]/, $td[1]; + next if @d < 3; + $bg = sprintf('.%.2d.%.2d.%.2d.%.2x', $d[2], $d[1], $d[0]%100, $tz); + $t[0] = sprintf('%.2x', hex($t[0]) + 0xc0); # set BGF1+BGF2 + } else { # time only + $bg = '.00.00.00.00'; + } + push @v, join('.', reverse(@t[0..3])) . $bg; + } + join ' ', @v; + }, + }, + 0xc764 => { #28 + Name => 'FrameRate', + Writable => 'rational64s', + WriteGroup => 'IFD0', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + PrintConvInv => '$val', + }, + 0xc772 => { #28 + Name => 'TStop', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Count => -1, # (1 or 2) + PrintConv => 'join("-", map { sprintf("%.2f",$_) } split " ", $val)', + PrintConvInv => '$val=~tr/-/ /; $val', + }, + 0xc789 => { #28 + Name => 'ReelName', + Writable => 'string', + WriteGroup => 'IFD0', + }, + 0xc791 => { # DNG 1.4 + Name => 'OriginalDefaultFinalSize', + Writable => 'int32u', + WriteGroup => 'IFD0', + Count => 2, + Protected => 1, + }, + 0xc792 => { # DNG 1.4 + Name => 'OriginalBestQualitySize', + Notes => 'called OriginalBestQualityFinalSize by the DNG spec', + Writable => 'int32u', + WriteGroup => 'IFD0', + Count => 2, + Protected => 1, + }, + 0xc793 => { # DNG 1.4 + Name => 'OriginalDefaultCropSize', + Writable => 'rational64u', + WriteGroup => 'IFD0', + Count => 2, + Protected => 1, + }, + 0xc7a1 => { #28 + Name => 'CameraLabel', + Writable => 'string', + WriteGroup => 'IFD0', + }, + 0xc7a3 => { # DNG 1.4 + Name => 'ProfileHueSatMapEncoding', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 1, + PrintConv => { + 0 => 'Linear', + 1 => 'sRGB', + }, + }, + 0xc7a4 => { # DNG 1.4 + Name => 'ProfileLookTableEncoding', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 1, + PrintConv => { + 0 => 'Linear', + 1 => 'sRGB', + }, + }, + 0xc7a5 => { # DNG 1.4 + Name => 'BaselineExposureOffset', + Writable => 'rational64s', # (incorrectly "RATIONAL" in DNG 1.4 spec) + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xc7a6 => { # DNG 1.4 + Name => 'DefaultBlackRender', + Writable => 'int32u', + WriteGroup => 'IFD0', + Protected => 1, + PrintConv => { + 0 => 'Auto', + 1 => 'None', + }, + }, + 0xc7a7 => { # DNG 1.4 + Name => 'NewRawImageDigest', + Format => 'undef', + Writable => 'int8u', + WriteGroup => 'IFD0', + Count => 16, + Protected => 1, + ValueConv => 'unpack("H*", $val)', + ValueConvInv => 'pack("H*", $val)', + }, + 0xc7a8 => { # DNG 1.4 + Name => 'RawToPreviewGain', + Writable => 'double', + WriteGroup => 'IFD0', + Protected => 1, + }, + # 0xc7a9 - CacheBlob (ref 31) + 0xc7aa => { #31 undocumented DNG tag written by LR4 (val=256, related to fast load data?) + Name => 'CacheVersion', + Writable => 'int32u', + WriteGroup => 'SubIFD2', + Format => 'int8u', + Count => 4, + Protected => 1, + PrintConv => '$val =~ tr/ /./; $val', + PrintConvInv => '$val =~ tr/./ /; $val', + }, + 0xc7b5 => { # DNG 1.4 + Name => 'DefaultUserCrop', + Writable => 'rational64u', + WriteGroup => 'SubIFD', + Count => 4, + Protected => 1, + }, + 0xc7d5 => { #PH (in SubIFD1 of Nikon Z6/Z7 NEF images) + Name => 'NikonNEFInfo', + Condition => '$$valPt =~ /^Nikon\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::NEFInfo', + Start => '$valuePtr + 18', + Base => '$start - 8', + ByteOrder => 'Unknown', + }, + }, + # 0xc7d6 - int8u: 1 (SubIFD1 of Nikon Z6/Z7 NEF) + 0xc7e9 => { # DNG 1.5 + Name => 'DepthFormat', + Writable => 'int16u', + Notes => 'tags 0xc7e9-0xc7ee added by DNG 1.5.0.0', + Protected => 1, + WriteGroup => 'IFD0', + PrintConv => { + 0 => 'Unknown', + 1 => 'Linear', + 2 => 'Inverse', + }, + }, + 0xc7ea => { # DNG 1.5 + Name => 'DepthNear', + Writable => 'rational64u', + Protected => 1, + WriteGroup => 'IFD0', + }, + 0xc7eb => { # DNG 1.5 + Name => 'DepthFar', + Writable => 'rational64u', + Protected => 1, + WriteGroup => 'IFD0', + }, + 0xc7ec => { # DNG 1.5 + Name => 'DepthUnits', + Writable => 'int16u', + Protected => 1, + WriteGroup => 'IFD0', + PrintConv => { + 0 => 'Unknown', + 1 => 'Meters', + }, + }, + 0xc7ed => { # DNG 1.5 + Name => 'DepthMeasureType', + Writable => 'int16u', + Protected => 1, + WriteGroup => 'IFD0', + PrintConv => { + 0 => 'Unknown', + 1 => 'Optical Axis', + 2 => 'Optical Ray', + }, + }, + 0xc7ee => { # DNG 1.5 + Name => 'EnhanceParams', + Writable => 'string', + Protected => 1, + WriteGroup => 'IFD0', + }, + 0xcd2d => { # DNG 1.6 + Name => 'ProfileGainTableMap', + Writable => 'undef', + WriteGroup => 'SubIFD', + Protected => 1, + Binary => 1, + }, + 0xcd2e => { # DNG 1.6 + Name => 'SemanticName', + # Writable => 'string', + WriteGroup => 'SubIFD' #? (NC) Semantic Mask IFD (only for Validate) + }, + 0xcd30 => { # DNG 1.6 + Name => 'SemanticInstanceID', + # Writable => 'string', + WriteGroup => 'SubIFD' #? (NC) Semantic Mask IFD (only for Validate) + }, + 0xcd31 => { # DNG 1.6 + Name => 'CalibrationIlluminant3', + Writable => 'int16u', + WriteGroup => 'IFD0', + Protected => 1, + SeparateTable => 'LightSource', + PrintConv => \%lightSource, + }, + 0xcd32 => { # DNG 1.6 + Name => 'CameraCalibration3', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xcd33 => { # DNG 1.6 + Name => 'ColorMatrix3', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xcd34 => { # DNG 1.6 + Name => 'ForwardMatrix3', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xcd35 => { # DNG 1.6 + Name => 'IlluminantData1', + Writable => 'undef', + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xcd36 => { # DNG 1.6 + Name => 'IlluminantData2', + Writable => 'undef', + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xcd37 => { # DNG 1.6 + Name => 'IlluminantData3', + Writable => 'undef', + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xcd38 => { # DNG 1.6 + Name => 'MaskSubArea', + # Writable => 'int32u', + WriteGroup => 'SubIFD', #? (NC) Semantic Mask IFD (only for Validate) + Count => 4, + }, + 0xcd39 => { # DNG 1.6 + Name => 'ProfileHueSatMapData3', + %longBin, + Writable => 'float', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xcd3a => { # DNG 1.6 + Name => 'ReductionMatrix3', + Writable => 'rational64s', + WriteGroup => 'IFD0', + Count => -1, + Protected => 1, + }, + 0xcd3b => { # DNG 1.6 + Name => 'RGBTables', + Writable => 'undef', + WriteGroup => 'IFD0', + Protected => 1, + }, + 0xea1c => { #13 + Name => 'Padding', + Binary => 1, + Protected => 1, + Writable => 'undef', + # must start with 0x1c 0xea by the WM Photo specification + # (not sure what should happen if padding is only 1 byte) + # (why does MicrosoftPhoto write "1c ea 00 00 00 08"?) + RawConvInv => '$val=~s/^../\x1c\xea/s; $val', + }, + 0xea1d => { + Name => 'OffsetSchema', + Notes => "Microsoft's ill-conceived maker note offset difference", + Protected => 1, + Writable => 'int32s', + # From the Microsoft documentation: + # + # Any time the "Maker Note" is relocated by Windows, the Exif MakerNote + # tag (37500) is updated automatically to reference the new location. In + # addition, Windows records the offset (or difference) between the old and + # new locations in the Exif OffsetSchema tag (59933). If the "Maker Note" + # contains relative references, the developer can add the value in + # OffsetSchema to the original references to find the correct information. + # + # My recommendation is for other developers to ignore this tag because the + # information it contains is unreliable. It will be wrong if the image has + # been subsequently edited by another application that doesn't recognize the + # new Microsoft tag. + # + # The new tag unfortunately only gives the difference between the new maker + # note offset and the original offset. Instead, it should have been designed + # to store the original offset. The new offset may change if the image is + # edited, which will invalidate the tag as currently written. If instead the + # original offset had been stored, the new difference could be easily + # calculated because the new maker note offset is known. + # + # I exchanged emails with a Microsoft technical representative, pointing out + # this problem shortly after they released the update (Feb 2007), but so far + # they have taken no steps to address this. + }, + # 0xefee - int16u: 0 - seen this from a WIC-scanned image + + # tags in the range 0xfde8-0xfe58 have been observed in PS7 files + # generated from RAW images. They are all strings with the + # tag name at the start of the string. To accommodate these types + # of tags, all tags with values above 0xf000 are handled specially + # by ProcessExif(). + 0xfde8 => { + Name => 'OwnerName', + Condition => '$$self{TIFF_TYPE} ne "DCR"', # (used for another purpose in Kodak DCR images) + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Owner's Name: $val"}, + Notes => q{ + tags 0xfde8-0xfdea and 0xfe4c-0xfe58 are generated by Photoshop Camera RAW. + Some names are the same as other EXIF tags, but ExifTool will avoid writing + these unless they already exist in the file + }, + }, + 0xfde9 => { + Name => 'SerialNumber', + Condition => '$$self{TIFF_TYPE} ne "DCR"', # (used for another purpose in Kodak DCR SubIFD) + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Serial Number: $val"}, + }, + 0xfdea => { + Name => 'Lens', + Condition => '$$self{TIFF_TYPE} ne "DCR"', # (used for another purpose in Kodak DCR SubIFD) + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Lens: $val"}, + }, + 0xfe4c => { + Name => 'RawFile', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Raw File: $val"}, + }, + 0xfe4d => { + Name => 'Converter', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Converter: $val"}, + }, + 0xfe4e => { + Name => 'WhiteBalance', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"White Balance: $val"}, + }, + 0xfe51 => { + Name => 'Exposure', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Exposure: $val"}, + }, + 0xfe52 => { + Name => 'Shadows', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Shadows: $val"}, + }, + 0xfe53 => { + Name => 'Brightness', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Brightness: $val"}, + }, + 0xfe54 => { + Name => 'Contrast', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Contrast: $val"}, + }, + 0xfe55 => { + Name => 'Saturation', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Saturation: $val"}, + }, + 0xfe56 => { + Name => 'Sharpness', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Sharpness: $val"}, + }, + 0xfe57 => { + Name => 'Smoothness', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Smoothness: $val"}, + }, + 0xfe58 => { + Name => 'MoireFilter', + Avoid => 1, + PSRaw => 1, + Writable => 'string', + ValueConv => '$val=~s/^.*: //;$val', + ValueConvInv => q{"Moire Filter: $val"}, + }, + + #------------- + 0xfe00 => { + Name => 'KDC_IFD', + Groups => { 1 => 'KDC_IFD' }, + Flags => 'SubIFD', + Notes => 'used in some Kodak KDC images', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::KDC_IFD', + DirName => 'KDC_IFD', + Start => '$val', + }, + }, +); + +# conversions for Composite SubSec date/time tags +my %subSecConv = ( + # @val array: 0) date/time, 1) sub-seconds, 2) time zone offset + RawConv => q{ + my $v; + if (defined $val[1] and $val[1]=~/^(\d+)/) { + my $subSec = $1; + # be careful here just in case the time already contains sub-seconds or a timezone (contrary to spec) + undef $v unless ($v = $val[0]) =~ s/( \d{2}:\d{2}:\d{2})(?!\.\d+)/$1\.$subSec/; + } + if (defined $val[2] and $val[0]!~/[-+]/ and $val[2]=~/^([-+])(\d{1,2}):(\d{2})/) { + $v = ($v || $val[0]) . sprintf('%s%.2d:%.2d', $1, $2, $3); + } + return $v; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', +); + +# EXIF Composite tags (plus other more general Composite tags) +%Image::ExifTool::Exif::Composite = ( + GROUPS => { 2 => 'Image' }, + ImageSize => { + Require => { + 0 => 'ImageWidth', + 1 => 'ImageHeight', + }, + Desire => { + 2 => 'ExifImageWidth', + 3 => 'ExifImageHeight', + 4 => 'RawImageCroppedSize', # (FujiFilm RAF images) + }, + # use ExifImageWidth/Height only for Canon and Phase One TIFF-base RAW images + ValueConv => q{ + return $val[4] if $val[4]; + return "$val[2] $val[3]" if $val[2] and $val[3] and + $$self{TIFF_TYPE} =~ /^(CR2|Canon 1D RAW|IIQ|EIP)$/; + return "$val[0] $val[1]" if IsFloat($val[0]) and IsFloat($val[1]); + return undef; + }, + PrintConv => '$val =~ tr/ /x/; $val', + }, + Megapixels => { + Require => 'ImageSize', + ValueConv => 'my @d = ($val =~ /\d+/g); $d[0] * $d[1] / 1000000', + PrintConv => 'sprintf("%.*f", ($val >= 1 ? 1 : ($val >= 0.001 ? 3 : 6)), $val)', + }, + # pick the best shutter speed value + ShutterSpeed => { + Desire => { + 0 => 'ExposureTime', + 1 => 'ShutterSpeedValue', + 2 => 'BulbDuration', + }, + ValueConv => '($val[2] and $val[2]>0) ? $val[2] : (defined($val[0]) ? $val[0] : $val[1])', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + Aperture => { + Desire => { + 0 => 'FNumber', + 1 => 'ApertureValue', + }, + RawConv => '($val[0] || $val[1]) ? $val : undef', + ValueConv => '$val[0] || $val[1]', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + LightValue => { + Notes => q{ + calculated LV = 2 * log2(Aperture) - log2(ShutterSpeed) - log2(ISO/100); + similar to exposure value but normalized to ISO 100 + }, + Require => { + 0 => 'Aperture', + 1 => 'ShutterSpeed', + 2 => 'ISO', + }, + ValueConv => 'Image::ExifTool::Exif::CalculateLV($val[0],$val[1],$prt[2])', + PrintConv => 'sprintf("%.1f",$val)', + }, + FocalLength35efl => { #26/PH + Description => 'Focal Length', + Notes => 'this value may be incorrect if the image has been resized', + Groups => { 2 => 'Camera' }, + Require => { + 0 => 'FocalLength', + }, + Desire => { + 1 => 'ScaleFactor35efl', + }, + ValueConv => 'ToFloat(@val); ($val[0] || 0) * ($val[1] || 1)', + PrintConv => '$val[1] ? sprintf("%.1f mm (35 mm equivalent: %.1f mm)", $val[0], $val) : sprintf("%.1f mm", $val)', + }, + ScaleFactor35efl => { #26/PH + Description => 'Scale Factor To 35 mm Equivalent', + Notes => q{ + this value and any derived values may be incorrect if the image has been + resized + }, + Groups => { 2 => 'Camera' }, + Desire => { + 0 => 'FocalLength', + 1 => 'FocalLengthIn35mmFormat', + 2 => 'Composite:DigitalZoom', + 3 => 'FocalPlaneDiagonal', + 4 => 'SensorSize', + 5 => 'FocalPlaneXSize', + 6 => 'FocalPlaneYSize', + 7 => 'FocalPlaneResolutionUnit', + 8 => 'FocalPlaneXResolution', + 9 => 'FocalPlaneYResolution', + 10 => 'ExifImageWidth', + 11 => 'ExifImageHeight', + 12 => 'CanonImageWidth', + 13 => 'CanonImageHeight', + 14 => 'ImageWidth', + 15 => 'ImageHeight', + }, + ValueConv => 'Image::ExifTool::Exif::CalcScaleFactor35efl($self, @val)', + PrintConv => 'sprintf("%.1f", $val)', + }, + CircleOfConfusion => { + Notes => q{ + calculated as D/1440, where D is the focal plane diagonal in mm. This value + may be incorrect if the image has been resized + }, + Groups => { 2 => 'Camera' }, + Require => 'ScaleFactor35efl', + ValueConv => 'sqrt(24*24+36*36) / ($val * 1440)', + PrintConv => 'sprintf("%.3f mm",$val)', + }, + HyperfocalDistance => { + Notes => 'this value may be incorrect if the image has been resized', + Groups => { 2 => 'Camera' }, + Require => { + 0 => 'FocalLength', + 1 => 'Aperture', + 2 => 'CircleOfConfusion', + }, + ValueConv => q{ + ToFloat(@val); + return 'inf' unless $val[1] and $val[2]; + return $val[0] * $val[0] / ($val[1] * $val[2] * 1000); + }, + PrintConv => 'sprintf("%.2f m", $val)', + }, + DOF => { + Description => 'Depth Of Field', + Notes => 'this value may be incorrect if the image has been resized', + Require => { + 0 => 'FocalLength', + 1 => 'Aperture', + 2 => 'CircleOfConfusion', + }, + Desire => { + 3 => 'FocusDistance', # focus distance in metres (0 is infinity) + 4 => 'SubjectDistance', + 5 => 'ObjectDistance', + 6 => 'ApproximateFocusDistance', + 7 => 'FocusDistanceLower', + 8 => 'FocusDistanceUpper', + }, + ValueConv => q{ + ToFloat(@val); + my ($d, $f) = ($val[3], $val[0]); + if (defined $d) { + $d or $d = 1e10; # (use large number for infinity) + } else { + $d = $val[4] || $val[5] || $val[6]; + unless (defined $d) { + return undef unless defined $val[7] and defined $val[8]; + $d = ($val[7] + $val[8]) / 2; + } + } + return 0 unless $f and $val[2]; + my $t = $val[1] * $val[2] * ($d * 1000 - $f) / ($f * $f); + my @v = ($d / (1 + $t), $d / (1 - $t)); + $v[1] < 0 and $v[1] = 0; # 0 means 'inf' + return join(' ',@v); + }, + PrintConv => q{ + $val =~ tr/,/./; # in case locale is whacky + my @v = split ' ', $val; + $v[1] or return sprintf("inf (%.2f m - inf)", $v[0]); + my $dof = $v[1] - $v[0]; + my $fmt = ($dof>0 and $dof<0.02) ? "%.3f" : "%.2f"; + return sprintf("$fmt m ($fmt - $fmt m)",$dof,$v[0],$v[1]); + }, + }, + FOV => { + Description => 'Field Of View', + Notes => q{ + calculated for the long image dimension. This value may be incorrect for + fisheye lenses, or if the image has been resized + }, + Require => { + 0 => 'FocalLength', + 1 => 'ScaleFactor35efl', + }, + Desire => { + 2 => 'FocusDistance', # (multiply by 1000 to convert to mm) + }, + # ref http://www.bobatkins.com/photography/technical/field_of_view.html + # (calculations below apply to rectilinear lenses only, not fisheye) + ValueConv => q{ + ToFloat(@val); + return undef unless $val[0] and $val[1]; + my $corr = 1; + if ($val[2]) { + my $d = 1000 * $val[2] - $val[0]; + $corr += $val[0]/$d if $d > 0; + } + my $fd2 = atan2(36, 2*$val[0]*$val[1]*$corr); + my @fov = ( $fd2 * 360 / 3.14159 ); + if ($val[2] and $val[2] > 0 and $val[2] < 10000) { + push @fov, 2 * $val[2] * sin($fd2) / cos($fd2); + } + return join(' ', @fov); + }, + PrintConv => q{ + my @v = split(' ',$val); + my $str = sprintf("%.1f deg", $v[0]); + $str .= sprintf(" (%.2f m)", $v[1]) if $v[1]; + return $str; + }, + }, + # generate DateTimeOriginal from Date and Time Created if not extracted already + DateTimeOriginal => { + Condition => 'not defined $$self{VALUE}{DateTimeOriginal}', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Desire => { + 0 => 'DateTimeCreated', + 1 => 'DateCreated', + 2 => 'TimeCreated', + }, + RawConv => '($val[1] and $val[2]) ? $val : undef', + ValueConv => q{ + return $val[0] if $val[0] and $val[0]=~/ /; + return "$val[1] $val[2]"; + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + ThumbnailImage => { + Groups => { 0 => 'EXIF', 1 => 'IFD1', 2 => 'Preview' }, + Writable => 1, + WriteGroup => 'All', + WriteCheck => '$self->CheckImage(\$val)', + WriteAlso => { + # (the 0xfeedfeed values are translated in the Exif write routine) + ThumbnailOffset => 'defined $val ? 0xfeedfeed : undef', + ThumbnailLength => 'defined $val ? 0xfeedfeed : undef', + }, + Require => { + 0 => 'ThumbnailOffset', + 1 => 'ThumbnailLength', + }, + Notes => q{ + this tag is writable, and may be used to update existing thumbnails, but may + only create a thumbnail in IFD1 of certain types of files. Note that for + this and other Composite embedded-image tags the family 0 and 1 groups match + those of the originating tags + }, + # retrieve the thumbnail from our EXIF data + RawConv => q{ + @grps = $self->GetGroup($$val{0}); # set groups from ThumbnailOffsets + Image::ExifTool::Exif::ExtractImage($self,$val[0],$val[1],"ThumbnailImage"); + }, + }, + ThumbnailTIFF => { + Groups => { 2 => 'Preview' }, + Require => { + 0 => 'SubfileType', + 1 => 'Compression', + 2 => 'ImageWidth', + 3 => 'ImageHeight', + 4 => 'BitsPerSample', + 5 => 'PhotometricInterpretation', + 6 => 'StripOffsets', + 7 => 'SamplesPerPixel', + 8 => 'RowsPerStrip', + 9 => 'StripByteCounts', + }, + Desire => { + 10 => 'PlanarConfiguration', + 11 => 'Orientation', + }, + # rebuild the TIFF thumbnail from our EXIF data + RawConv => q{ + my $tiff; + ($tiff, @grps) = Image::ExifTool::Exif::RebuildTIFF($self, @val); + return $tiff; + }, + }, + PreviewImage => { + Groups => { 0 => 'EXIF', 1 => 'SubIFD', 2 => 'Preview' }, + Writable => 1, + WriteGroup => 'All', + WriteCheck => '$self->CheckImage(\$val)', + DelCheck => '$val = ""; return undef', # can't delete, so set to empty string + WriteAlso => { + PreviewImageStart => 'defined $val ? 0xfeedfeed : undef', + PreviewImageLength => 'defined $val ? 0xfeedfeed : undef', + PreviewImageValid => 'defined $val and length $val ? 1 : 0', # (for Olympus) + }, + Require => { + 0 => 'PreviewImageStart', + 1 => 'PreviewImageLength', + }, + Desire => { + 2 => 'PreviewImageValid', + # (DNG and A100 ARW may be have 2 preview images) + 3 => 'PreviewImageStart (1)', + 4 => 'PreviewImageLength (1)', + }, + Notes => q{ + this tag is writable, and may be used to update existing embedded images, + but not create or delete them + }, + # note: extract 2nd preview, but ignore double-referenced preview + # (in A100 ARW images, the 2nd PreviewImageLength from IFD0 may be wrong anyway) + RawConv => q{ + if ($val[3] and $val[4] and $val[0] ne $val[3]) { + my %val = ( + 0 => 'PreviewImageStart (1)', + 1 => 'PreviewImageLength (1)', + 2 => 'PreviewImageValid', + ); + $self->FoundTag($tagInfo, \%val); + } + return undef if defined $val[2] and not $val[2]; + @grps = $self->GetGroup($$val{0}); + return Image::ExifTool::Exif::ExtractImage($self,$val[0],$val[1],'PreviewImage'); + }, + }, + JpgFromRaw => { + Groups => { 0 => 'EXIF', 1 => 'SubIFD', 2 => 'Preview' }, + Writable => 1, + WriteGroup => 'All', + WriteCheck => '$self->CheckImage(\$val)', + # Note: ExifTool 10.38 had disabled the ability to delete this -- why? + # --> added the DelCheck in 10.61 to re-enable this + DelCheck => '$val = ""; return undef', # can't delete, so set to empty string + WriteAlso => { + JpgFromRawStart => 'defined $val ? 0xfeedfeed : undef', + JpgFromRawLength => 'defined $val ? 0xfeedfeed : undef', + }, + Require => { + 0 => 'JpgFromRawStart', + 1 => 'JpgFromRawLength', + }, + Notes => q{ + this tag is writable, and may be used to update existing embedded images, + but not create or delete them + }, + RawConv => q{ + @grps = $self->GetGroup($$val{0}); + return Image::ExifTool::Exif::ExtractImage($self,$val[0],$val[1],"JpgFromRaw"); + }, + }, + OtherImage => { + Groups => { 0 => 'EXIF', 1 => 'SubIFD', 2 => 'Preview' }, + Writable => 1, + WriteGroup => 'All', + WriteCheck => '$self->CheckImage(\$val)', + DelCheck => '$val = ""; return undef', # can't delete, so set to empty string + WriteAlso => { + OtherImageStart => 'defined $val ? 0xfeedfeed : undef', + OtherImageLength => 'defined $val ? 0xfeedfeed : undef', + }, + Require => { + 0 => 'OtherImageStart', + 1 => 'OtherImageLength', + }, + Desire => { + 2 => 'OtherImageStart (1)', + 3 => 'OtherImageLength (1)', + }, + Notes => q{ + this tag is writable, and may be used to update existing embedded images, + but not create or delete them + }, + # retrieve all other images + RawConv => q{ + if ($val[2] and $val[3]) { + my $i = 1; + for (;;) { + my %val = ( 0 => $$val{2}, 1 => $$val{3} ); + $self->FoundTag($tagInfo, \%val); + ++$i; + $$val{2} = "$$val{0} ($i)"; + last unless defined $$self{VALUE}{$$val{2}}; + $$val{3} = "$$val{1} ($i)"; + last unless defined $$self{VALUE}{$$val{3}}; + } + } + @grps = $self->GetGroup($$val{0}); + Image::ExifTool::Exif::ExtractImage($self,$val[0],$val[1],"OtherImage"); + }, + }, + PreviewImageSize => { + Require => { + 0 => 'PreviewImageWidth', + 1 => 'PreviewImageHeight', + }, + ValueConv => '"$val[0]x$val[1]"', + }, + SubSecDateTimeOriginal => { + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Writable => 1, + Shift => 0, # don't shift this tag + Require => { + 0 => 'EXIF:DateTimeOriginal', + }, + Desire => { + 1 => 'SubSecTimeOriginal', + 2 => 'OffsetTimeOriginal', + }, + WriteAlso => { + 'EXIF:DateTimeOriginal' => '($val and $val=~/^(\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2})/) ? $1 : undef', + 'EXIF:SubSecTimeOriginal' => '($val and $val=~/\.(\d+)/) ? $1 : undef', + 'EXIF:OffsetTimeOriginal' => '($val and $val=~/([-+]\d{2}:\d{2}|Z)$/) ? ($1 eq "Z" ? "+00:00" : $1) : undef', + }, + %subSecConv, + }, + SubSecCreateDate => { + Description => 'Create Date', + Groups => { 2 => 'Time' }, + Writable => 1, + Shift => 0, # don't shift this tag + Require => { + 0 => 'EXIF:CreateDate', + }, + Desire => { + 1 => 'SubSecTimeDigitized', + 2 => 'OffsetTimeDigitized', + }, + WriteAlso => { + 'EXIF:CreateDate' => '($val and $val=~/^(\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2})/) ? $1 : undef', + 'EXIF:SubSecTimeDigitized' => '($val and $val=~/\.(\d+)/) ? $1 : undef', + 'EXIF:OffsetTimeDigitized' => '($val and $val=~/([-+]\d{2}:\d{2}|Z)$/) ? ($1 eq "Z" ? "+00:00" : $1) : undef', + }, + %subSecConv, + }, + SubSecModifyDate => { + Description => 'Modify Date', + Groups => { 2 => 'Time' }, + Writable => 1, + Shift => 0, # don't shift this tag + Require => { + 0 => 'EXIF:ModifyDate', + }, + Desire => { + 1 => 'SubSecTime', + 2 => 'OffsetTime', + }, + WriteAlso => { + 'EXIF:ModifyDate' => '($val and $val=~/^(\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2})/) ? $1 : undef', + 'EXIF:SubSecTime' => '($val and $val=~/\.(\d+)/) ? $1 : undef', + 'EXIF:OffsetTime' => '($val and $val=~/([-+]\d{2}:\d{2}|Z)$/) ? ($1 eq "Z" ? "+00:00" : $1) : undef', + }, + %subSecConv, + }, + CFAPattern => { + Require => { + 0 => 'CFARepeatPatternDim', + 1 => 'CFAPattern2', + }, + # generate CFAPattern + ValueConv => q{ + my @a = split / /, $val[0]; + my @b = split / /, $val[1]; + return '?' unless @a==2 and @b==$a[0]*$a[1]; + return "$a[0] $a[1] @b"; + }, + PrintConv => 'Image::ExifTool::Exif::PrintCFAPattern($val)', + }, + RedBalance => { + Groups => { 2 => 'Camera' }, + Desire => { + 0 => 'WB_RGGBLevels', + 1 => 'WB_RGBGLevels', + 2 => 'WB_RBGGLevels', + 3 => 'WB_GRBGLevels', + 4 => 'WB_GRGBLevels', + 5 => 'WB_GBRGLevels', + 6 => 'WB_RGBLevels', + 7 => 'WB_GRBLevels', + 8 => 'WB_RBLevels', + 9 => 'WBRedLevel', # red + 10 => 'WBGreenLevel', + }, + ValueConv => 'Image::ExifTool::Exif::RedBlueBalance(0,@val)', + PrintConv => 'int($val * 1e6 + 0.5) * 1e-6', + }, + BlueBalance => { + Groups => { 2 => 'Camera' }, + Desire => { + 0 => 'WB_RGGBLevels', + 1 => 'WB_RGBGLevels', + 2 => 'WB_RBGGLevels', + 3 => 'WB_GRBGLevels', + 4 => 'WB_GRGBLevels', + 5 => 'WB_GBRGLevels', + 6 => 'WB_RGBLevels', + 7 => 'WB_GRBLevels', + 8 => 'WB_RBLevels', + 9 => 'WBBlueLevel', # blue + 10 => 'WBGreenLevel', + }, + ValueConv => 'Image::ExifTool::Exif::RedBlueBalance(1,@val)', + PrintConv => 'int($val * 1e6 + 0.5) * 1e-6', + }, + GPSPosition => { + Groups => { 2 => 'Location' }, + Writable => 1, + Protected => 1, + WriteAlso => { + GPSLatitude => '(defined $val and $val =~ /(.*) /) ? $1 : undef', + GPSLatitudeRef => '(defined $val and $val =~ /(-?)(.*?) /) ? ($1 ? "S" : "N") : undef', + GPSLongitude => '(defined $val and $val =~ / (.*)$/) ? $1 : undef', + GPSLongitudeRef => '(defined $val and $val =~ / (-?)/) ? ($1 ? "W" : "E") : undef', + }, + PrintConvInv => q{ + return undef unless $val =~ /(.*? ?[NS]?), ?(.*? ?[EW]?)$/; + my ($lat, $lon) = ($1, $2); + require Image::ExifTool::GPS; + $lat = Image::ExifTool::GPS::ToDegrees($lat, 1, "lat"); + $lon = Image::ExifTool::GPS::ToDegrees($lon, 1, "lon"); + return "$lat $lon"; + }, + Require => { + 0 => 'GPSLatitude', + 1 => 'GPSLongitude', + }, + Priority => 0, + Notes => q{ + when written, writes GPSLatitude, GPSLatitudeRef, GPSLongitude and + GPSLongitudeRef. This tag may be written using the same coordinate + format as provided by Google Maps when right-clicking on a location + }, + ValueConv => '(length($val[0]) or length($val[1])) ? "$val[0] $val[1]" : undef', + PrintConv => '"$prt[0], $prt[1]"', + }, + LensID => { + Groups => { 2 => 'Camera' }, + Require => 'LensType', + Desire => { + 1 => 'FocalLength', + 2 => 'MaxAperture', + 3 => 'MaxApertureValue', + 4 => 'MinFocalLength', + 5 => 'MaxFocalLength', + 6 => 'LensModel', + 7 => 'LensFocalRange', + 8 => 'LensSpec', + 9 => 'LensType2', + 10 => 'LensType3', + 11 => 'LensFocalLength', # (for Pentax to check for converter) + 12 => 'RFLensType', + }, + Notes => q{ + attempt to identify the actual lens from all lenses with a given LensType. + Applies only to LensType values with a lookup table. May be configured + by adding user-defined lenses + }, + # this LensID is only valid if the LensType has a PrintConv or is a model name + RawConv => q{ + my $printConv = $$self{TAG_INFO}{LensType}{PrintConv}; + return $val if ref $printConv eq 'HASH' or (ref $printConv eq 'ARRAY' and + ref $$printConv[0] eq 'HASH') or $val[0] =~ /(mm|\d\/F)/; + return undef; + }, + ValueConv => '$val', + PrintConv => q{ + my $pcv; + # use LensType2 instead of LensType if available and valid (Sony E-mount lenses) + # (0x8000 or greater; 0 for several older/3rd-party E-mount lenses) + if (defined $val[9] and ($val[9] & 0x8000 or $val[9] == 0)) { + $val[0] = $val[9]; + $prt[0] = $prt[9]; + # Particularly GM lenses: often LensType2=0 but LensType3 is available and valid: use LensType3. + if ($val[9] == 0 and $val[10] & 0x8000) { + $val[0] = $val[10]; + $prt[0] = $prt[10]; + } + $pcv = $$self{TAG_INFO}{LensType2}{PrintConv}; + } + # use Canon RFLensType if available + if ($val[12]) { + $val[0] = $val[12]; + $prt[0] = $prt[12]; + $pcv = $$self{TAG_INFO}{RFLensType}{PrintConv}; + } + my $lens = Image::ExifTool::Exif::PrintLensID($self, $prt[0], $pcv, $prt[8], @val); + # check for use of lens converter (Pentax K-3) + if ($val[11] and $val[1] and $lens) { + my $conv = $val[1] / $val[11]; + $lens .= sprintf(' + %.1fx converter', $conv) if $conv > 1.1; + } + return $lens; + }, + }, + 'LensID-2' => { + Name => 'LensID', + Groups => { 2 => 'Camera' }, + Desire => { + 0 => 'LensModel', + 1 => 'Lens', + 2 => 'XMP-aux:LensID', + 3 => 'Make', + }, + Inhibit => { + 4 => 'Composite:LensID', + }, + RawConv => q{ + return undef if defined $val[2] and defined $val[3]; + return $val if defined $val[0] and $val[0] =~ /(mm|\d\/F)/; + return $val if defined $val[1] and $val[1] =~ /(mm|\d\/F)/; + return undef; + }, + ValueConv => q{ + return $val[0] if defined $val[0] and $val[0] =~ /(mm|\d\/F)/; + return $val[1]; + }, + PrintConv => '$_=$val; s/(\d)\/F/$1mm F/; s/mmF/mm F/; s/(\d) mm/${1}mm/; s/ - /-/; $_', + }, +); + +# table for unknown IFD entries +%Image::ExifTool::Exif::Unknown = ( + GROUPS => { 0 => 'EXIF', 1 => 'UnknownIFD', 2 => 'Image'}, + WRITE_PROC => \&WriteExif, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Exif'); + + +#------------------------------------------------------------------------------ +# AutoLoad our writer routines when necessary +# +sub AUTOLOAD +{ + return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); +} + +#------------------------------------------------------------------------------ +# Identify RAW file type for some TIFF-based formats using Compression value +# Inputs: 0) ExifTool object reference, 1) Compression value +# - sets TIFF_TYPE and FileType if identified +sub IdentifyRawFile($$) +{ + my ($et, $comp) = @_; + if ($$et{FILE_TYPE} eq 'TIFF' and not $$et{IdentifiedRawFile}) { + if ($compression{$comp} and $compression{$comp} =~ /^\w+ ([A-Z]{3}) Compressed$/) { + $et->OverrideFileType($$et{TIFF_TYPE} = $1); + $$et{IdentifiedRawFile} = 1; + } + } +} + +#------------------------------------------------------------------------------ +# Calculate LV (Light Value) +# Inputs: 0) Aperture, 1) ShutterSpeed, 2) ISO +# Returns: LV value (and converts input values to floating point if necessary) +sub CalculateLV($$$) +{ + local $_; + # do validity checks on arguments + return undef unless @_ >= 3; + foreach (@_) { + return undef unless $_ and /([+-]?(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?)/ and $1 > 0; + $_ = $1; # extract float from any other garbage + } + # (A light value of 0 is defined as f/1.0 at 1 second with ISO 100) + return log($_[0] * $_[0] * 100 / ($_[1] * $_[2])) / log(2); +} + +#------------------------------------------------------------------------------ +# Calculate scale factor for 35mm effective focal length (ref 26/PH) +# Inputs: 0) ExifTool object ref +# 1) Focal length +# 2) Focal length in 35mm format +# 3) Canon digital zoom factor +# 4) Focal plane diagonal size (in mm) +# 5) Sensor size (X and Y in mm) +# 6/7) Focal plane X/Y size (in mm) +# 8) focal plane resolution units (1=None,2=inches,3=cm,4=mm,5=um) +# 9/10) Focal plane X/Y resolution +# 11/12,13/14...) Image width/height in order of precedence (first valid pair is used) +# Returns: 35mm conversion factor (or undefined if it can't be calculated) +sub CalcScaleFactor35efl +{ + my $et = shift; + my $res = $_[7]; # save resolution units (in case they have been converted to string) + my $sensXY = $_[4]; + Image::ExifTool::ToFloat(@_); + my $focal = shift; + my $foc35 = shift; + + return $foc35 / $focal if $focal and $foc35; + + my $digz = shift || 1; + my $diag = shift; + my $sens = shift; + # calculate Canon sensor size using a dedicated algorithm + if ($$et{Make} eq 'Canon') { + require Image::ExifTool::Canon; + my $canonDiag = Image::ExifTool::Canon::CalcSensorDiag( + $$et{RATIONAL}{FocalPlaneXResolution}, + $$et{RATIONAL}{FocalPlaneYResolution}, + ); + $diag = $canonDiag if $canonDiag; + } + unless ($diag and Image::ExifTool::IsFloat($diag)) { + if ($sens and $sensXY =~ / (\d+(\.?\d*)?)$/) { + $diag = sqrt($sens * $sens + $1 * $1); + } else { + undef $diag; + my $xsize = shift; + my $ysize = shift; + if ($xsize and $ysize) { + # validate by checking aspect ratio because FocalPlaneX/YSize is not reliable + my $a = $xsize / $ysize; + if (abs($a-1.3333) < .1 or abs($a-1.5) < .1) { + $diag = sqrt($xsize * $xsize + $ysize * $ysize); + } + } + } + unless ($diag) { + # get number of mm in units (assume inches unless otherwise specified) + my %lkup = ( 3=>10, 4=>1, 5=>0.001 , cm=>10, mm=>1, um=>0.001 ); + my $units = $lkup{ shift() || $res || '' } || 25.4; + my $x_res = shift || return undef; + my $y_res = shift || $x_res; + Image::ExifTool::IsFloat($x_res) and $x_res != 0 or return undef; + Image::ExifTool::IsFloat($y_res) and $y_res != 0 or return undef; + my ($w, $h); + for (;;) { + @_ < 2 and return undef; + $w = shift; + $h = shift; + next unless $w and $h; + my $a = $w / $h; + last if $a > 0.5 and $a < 2; # stop if we get a reasonable value + } + # calculate focal plane size in mm + $w *= $units / $x_res; + $h *= $units / $y_res; + $diag = sqrt($w*$w+$h*$h); + # make sure size is reasonable + return undef unless $diag > 1 and $diag < 100; + } + } + return sqrt(36*36+24*24) * $digz / $diag; +} + +#------------------------------------------------------------------------------ +# Print exposure compensation fraction +sub PrintFraction($) +{ + my $val = shift; + my $str; + if (defined $val) { + $val *= 1.00001; # avoid round-off errors + if (not $val) { + $str = '0'; + } elsif (int($val)/$val > 0.999) { + $str = sprintf("%+d", int($val)); + } elsif ((int($val*2))/($val*2) > 0.999) { + $str = sprintf("%+d/2", int($val * 2)); + } elsif ((int($val*3))/($val*3) > 0.999) { + $str = sprintf("%+d/3", int($val * 3)); + } else { + $str = sprintf("%+.3g", $val); + } + } + return $str; +} + +#------------------------------------------------------------------------------ +# Convert fraction or number to floating point value (or 'undef' or 'inf') +sub ConvertFraction($) +{ + my $val = shift; + if ($val =~ m{([-+]?\d+)/(\d+)}) { + $val = $2 ? $1 / $2 : ($1 ? 'inf' : 'undef'); + } + return $val; +} + +#------------------------------------------------------------------------------ +# Convert EXIF text to something readable +# Inputs: 0) ExifTool object reference, 1) EXIF text, +# 2) [optional] 1 to apply CharsetEXIF to ASCII text, +# 3) tag name for warning message (may be argument 2) +# Returns: text encoded according to Charset option (with trailing spaces removed) +sub ConvertExifText($$;$$) +{ + my ($et, $val, $asciiFlex, $tag) = @_; + return $val if length($val) < 8; + my $id = substr($val, 0, 8); + my $str = substr($val, 8); + my $type; + + delete $$et{WrongByteOrder}; + if ($$et{OPTIONS}{Validate} and $id =~ /^(ASCII|UNICODE|JIS)?\0* \0*$/) { + $et->Warn(($1 || 'Undefined') . ' text header' . ($tag ? " for $tag" : '') . ' has spaces instead of nulls'); + } + # Note: allow spaces instead of nulls in the ID codes because + # it is fairly common for camera manufacturers to get this wrong + # (also handle Canon ZoomBrowser EX 4.5 null followed by 7 bytes of garbage) + if ($id =~ /^(ASCII)?(\0|[\0 ]+$)/) { + # truncate at null terminator (shouldn't have a null based on the + # EXIF spec, but it seems that few people actually read the spec) + $str =~ s/\0.*//s; + # allow ASCII text to contain any other specified encoding + if ($asciiFlex and $asciiFlex eq '1') { + my $enc = $et->Options('CharsetEXIF'); + $str = $et->Decode($str, $enc) if $enc; + } + # by the EXIF spec, the following string should be "UNICODE\0", but + # apparently Kodak sometimes uses "Unicode\0" in the APP3 "Meta" information. + # However, unfortunately Ricoh uses "Unicode\0" in the RR30 EXIF UserComment + # when the text is actually ASCII, so only recognize uppercase "UNICODE\0". + } elsif ($id =~ /^(UNICODE)[\0 ]$/) { + $type = $1; + # MicrosoftPhoto writes as little-endian even in big-endian EXIF, + # so we must guess at the true byte ordering + $str = $et->Decode($str, 'UTF16', 'Unknown'); + } elsif ($id =~ /^(JIS)[\0 ]{5}$/) { + $type = $1; + $str = $et->Decode($str, 'JIS', 'Unknown'); + } else { + $tag = $asciiFlex if $asciiFlex and $asciiFlex ne '1'; + $et->Warn('Invalid EXIF text encoding' . ($tag ? " for $tag" : '')); + $str = $id . $str; + } + if ($$et{WrongByteOrder} and $$et{OPTIONS}{Validate}) { + $et->Warn('Wrong byte order for EXIF' . ($tag ? " $tag" : '') . + ($type ? " $type" : '') . ' text'); + } + $str =~ s/ +$//; # trim trailing blanks + return $str; +} + +#------------------------------------------------------------------------------ +# Print conversion for SpatialFrequencyResponse +sub PrintSFR($) +{ + my $val = shift; + return $val unless length $val > 4; + my ($n, $m) = (Get16u(\$val, 0), Get16u(\$val, 2)); + my @cols = split /\0/, substr($val, 4), $n+1; + my $pos = length($val) - 8 * $n * $m; + return $val unless @cols == $n+1 and $pos >= 4; + pop @cols; + my ($i, $j); + for ($i=0; $i<$n; ++$i) { + my @rows; + for ($j=0; $j<$m; ++$j) { + push @rows, Image::ExifTool::GetRational64u(\$val, $pos + 8*($i+$j*$n)); + } + $cols[$i] .= '=' . join(',',@rows) . ''; + } + return join '; ', @cols; +} + +#------------------------------------------------------------------------------ +# Print numerical parameter value (with sign, or 'Normal' for zero) +# Inputs: 0) value, 1) flag for inverse conversion, 2) conversion hash reference +sub PrintParameter($$$) +{ + my ($val, $inv, $conv) = @_; + return $val if $inv; + if ($val > 0) { + if ($val > 0xfff0) { # a negative value in disguise? + $val = $val - 0x10000; + } else { + $val = "+$val"; + } + } + return $val; +} + +#------------------------------------------------------------------------------ +# Convert parameter back to standard EXIF value +# 0,0.00,etc or "Normal" => 0 +# -1,-2,etc or "Soft" or "Low" => 1 +# +1,+2,1,2,etc or "Hard" or "High" => 2 +sub ConvertParameter($) +{ + my $val = shift; + my $isFloat = Image::ExifTool::IsFloat($val); + # normal is a value of zero + return 0 if $val =~ /\bn/i or ($isFloat and $val == 0); + # "soft", "low" or any negative number is a value of 1 + return 1 if $val =~ /\b(s|l)/i or ($isFloat and $val < 0); + # "hard", "high" or any positive number is a value of 2 + return 2 if $val =~ /\bh/i or $isFloat; + return undef; +} + +#------------------------------------------------------------------------------ +# Calculate Red/BlueBalance +# Inputs: 0) 0=red, 1=blue, 1-8) WB_RGGB/RGBG/RBGG/GRBG/GRGB/RGB/GRB/RBLevels, +# 8) red or blue level, 9) green level +my @rggbLookup = ( + # indices for R, G, G and B components in input value + [ 0, 1, 2, 3 ], # 0 RGGB + [ 0, 1, 3, 2 ], # 1 RGBG + [ 0, 2, 3, 1 ], # 2 RBGG + [ 1, 0, 3, 2 ], # 3 GRBG + [ 1, 0, 2, 3 ], # 4 GRGB + [ 2, 3, 0, 1 ], # 5 GBRG + [ 0, 1, 1, 2 ], # 6 RGB + [ 1, 0, 0, 2 ], # 7 GRB + [ 0, 256, 256, 1 ], # 8 RB (green level is 256) +); +sub RedBlueBalance($@) +{ + my $blue = shift; + my ($i, $val, $levels); + for ($i=0; $i<@rggbLookup; ++$i) { + $levels = shift or next; + my @levels = split ' ', $levels; + next if @levels < 2; + my $lookup = $rggbLookup[$i]; + my $g = $$lookup[1]; # get green level or index + if ($g < 4) { + next if @levels < 3; + $g = ($levels[$g] + $levels[$$lookup[2]]) / 2 or next; + } elsif ($levels[$$lookup[$blue * 3]] < 4) { + $g = 1; # Some Nikon cameras use a scaling factor of 1 (E5700) + } + $val = $levels[$$lookup[$blue * 3]] / $g; + last; + } + $val = $_[0] / $_[1] if not defined $val and ($_[0] and $_[1]); + return $val; +} + +#------------------------------------------------------------------------------ +# Print exposure time as a fraction +sub PrintExposureTime($) +{ + my $secs = shift; + return $secs unless Image::ExifTool::IsFloat($secs); + if ($secs < 0.25001 and $secs > 0) { + return sprintf("1/%d",int(0.5 + 1/$secs)); + } + $_ = sprintf("%.1f",$secs); + s/\.0$//; + return $_; +} + +#------------------------------------------------------------------------------ +# Print FNumber +sub PrintFNumber($) +{ + my $val = shift; + if (Image::ExifTool::IsFloat($val) and $val > 0) { + # round to 1 decimal place, or 2 for values < 1.0 + $val = sprintf(($val<1 ? "%.2f" : "%.1f"), $val); + } + return $val; +} + +#------------------------------------------------------------------------------ +# Decode raw CFAPattern value +# Inputs: 0) ExifTool ref, 1) binary value +# Returns: string of numbers +sub DecodeCFAPattern($$) +{ + my ($self, $val) = @_; + # some panasonic cameras (SV-AS3, SV-AS30) write this in ascii (very odd) + if ($val =~ /^[0-6]+$/) { + $self->Warn('Incorrectly formatted CFAPattern', 1); + $val =~ tr/0-6/\x00-\x06/; + } + return $val unless length($val) >= 4; + my @a = unpack(GetByteOrder() eq 'II' ? 'v2C*' : 'n2C*', $val); + my $end = 2 + $a[0] * $a[1]; + if ($end > @a) { + # try swapping byte order (I have seen this order different than in EXIF) + my ($x, $y) = unpack('n2',pack('v2',$a[0],$a[1])); + if (@a < 2 + $x * $y) { + $self->Warn('Invalid CFAPattern', 1); + } else { + ($a[0], $a[1]) = ($x, $y); + # (can't technically be wrong because the order isn't well defined by the EXIF spec) + # $self->Warn('Wrong byte order for CFAPattern'); + } + } + return "@a"; +} + +#------------------------------------------------------------------------------ +# Print CFA Pattern +sub PrintCFAPattern($) +{ + my $val = shift; + my @a = split ' ', $val; + return '<truncated data>' unless @a >= 2; + return '<zero pattern size>' unless $a[0] and $a[1]; + my $end = 2 + $a[0] * $a[1]; + return '<invalid pattern size>' if $end > @a; + my @cfaColor = qw(Red Green Blue Cyan Magenta Yellow White); + my ($pos, $rtnVal) = (2, '['); + for (;;) { + $rtnVal .= $cfaColor[$a[$pos]] || 'Unknown'; + last if ++$pos >= $end; + ($pos - 2) % $a[1] and $rtnVal .= ',', next; + $rtnVal .= ']['; + } + return $rtnVal . ']'; +} + +#------------------------------------------------------------------------------ +# Print Opcode List +# Inputs: 0) value, 1) flag for inverse conversion, 2) conversion hash reference +# Returns: converted value +sub PrintOpcode($$$) +{ + my ($val, $inv, $conv) = @_; + return undef if $inv; # (can't do inverse conversion) + return '' unless length $$val > 4; + my $num = unpack('N', $$val); + my $pos = 4; + my ($i, @ops); + for ($i=0; $i<$num; ++$i) { + $pos + 16 <= length $$val or push(@ops, '<err>'), last; + my ($op, $ver, $flags, $len) = unpack("x${pos}N4", $$val); + push @ops, $$conv{$op} || "[opcode $op]"; + $pos += 16 + $len; + } + return join ', ', @ops; +} + +#------------------------------------------------------------------------------ +# Print conversion for lens info +# Inputs: 0) string of values (min focal, max focal, min F, max F) +# Returns: string in the form "12-20mm f/3.8-4.5" or "50mm f/1.4" +sub PrintLensInfo($) +{ + my $val = shift; + my @vals = split ' ', $val; + return $val unless @vals == 4; + my $c = 0; + foreach (@vals) { + Image::ExifTool::IsFloat($_) and ++$c, next; + $_ eq 'inf' and $_ = '?', ++$c, next; + $_ eq 'undef' and $_ = '?', ++$c, next; + } + return $val unless $c == 4; + $val = $vals[0]; + # (the Pentax Q writes zero for upper value of fixed-focal-length lenses) + $val .= "-$vals[1]" if $vals[1] and $vals[1] ne $vals[0]; + $val .= "mm f/$vals[2]"; + $val .= "-$vals[3]" if $vals[3] and $vals[3] ne $vals[2]; + return $val; +} + +#------------------------------------------------------------------------------ +# Get lens info from lens model string +# Inputs: 0) lens string, 1) flag to allow unknown "?" values +# Returns: 0) min focal, 1) max focal, 2) min aperture, 3) max aperture +# Notes: returns empty list if lens string could not be parsed +sub GetLensInfo($;$) +{ + my ($lens, $unk) = @_; + # extract focal length and aperture ranges for this lens + my $pat = '\\d+(?:\\.\\d+)?'; + $pat .= '|\\?' if $unk; + return () unless $lens =~ /($pat)(?:-($pat))?\s*mm.*?(?:[fF]\/?\s*)($pat)(?:-($pat))?/; + # ($1=short focal, $2=long focal, $3=max aperture wide, $4=max aperture tele) + my @a = ($1, $2, $3, $4); + $a[1] or $a[1] = $a[0]; + $a[3] or $a[3] = $a[2]; + if ($unk) { + local $_; + $_ eq '?' and $_ = 'undef' foreach @a; + } + return @a; +} + +#------------------------------------------------------------------------------ +# Match lens in list of possbilities based on value of LensModel +# Inputs: 0) reference to list of possible models, 1) LensModel string +# - updates list on return; guaranteed not to remove all list entries +sub MatchLensModel($$) +{ + my ($try, $lensModel) = @_; + if (@$try > 1 and $lensModel) { + my (@filt, $pat); + # filter by focal length + if ($lensModel =~ /((\d+-)?\d+mm)/) { + my $focal = $1; + @filt = grep /$focal/, @$try; + @$try = @filt if @filt and @filt < @$try; + } + # filter by aperture + if (@$try > 1 and $lensModel =~ m{(?:F/?|1:)(\d+(\.\d+)?)}i) { + my $fnum = $1; + @filt = grep m{(F/?|1:)$fnum(\b|[A-Z])}i, @$try; + @$try = @filt if @filt and @filt < @$try; + } + # filter by model version, and other lens parameters + foreach $pat ('I+', 'USM') { + next unless @$try > 1 and $lensModel =~ /\b($pat)\b/; + my $val = $1; + @filt = grep /\b$val\b/, @$try; + @$try = @filt if @filt and @filt < @$try; + } + } +} + +#------------------------------------------------------------------------------ +# Attempt to identify the specific lens if multiple lenses have the same LensType +# Inputs: 0) ExifTool object ref, 1) LensType print value, 2) PrintConv hash ref, +# 3) LensSpec print value, 4) LensType numerical value, 5) FocalLength, +# 6) MaxAperture, 7) MaxApertureValue, 8) MinFocalLength, 9) MaxFocalLength, +# 10) LensModel, 11) LensFocalRange, 12) LensSpec +my %sonyEtype; +sub PrintLensID($$@) +{ + my ($et, $lensTypePrt, $printConv, $lensSpecPrt, $lensType, $focalLength, + $maxAperture, $maxApertureValue, $shortFocal, $longFocal, $lensModel, + $lensFocalRange, $lensSpec) = @_; + # this logic relies on the LensType lookup: + return undef unless defined $lensType; + # get print conversion hash if necessary + $printConv or $printConv = $$et{TAG_INFO}{LensType}{PrintConv}; + # just copy LensType PrintConv value if it was a lens name + # (Olympus or Panasonic -- just exclude things like Nikon and Leaf LensType) + unless (ref $printConv eq 'HASH') { + if (ref $printConv eq 'ARRAY' and ref $$printConv[0] eq 'HASH') { + $printConv = $$printConv[0]; + $lensTypePrt =~ s/;.*//; + $lensType =~ s/ .*//; + } else { + return $lensTypePrt if $lensTypePrt =~ /mm/; + return $lensTypePrt if $lensTypePrt =~ s/(\d)\/F/$1mm F/; + return undef; + } + } + # get LensSpec information if available (Sony) + my ($sf0, $lf0, $sa0, $la0); + if ($lensSpecPrt) { + ($sf0, $lf0, $sa0, $la0) = GetLensInfo($lensSpecPrt); + undef $sf0 unless $sa0; # (make sure aperture isn't zero) + } + # use MaxApertureValue if MaxAperture is not available + $maxAperture = $maxApertureValue unless $maxAperture; + if ($lensFocalRange and $lensFocalRange =~ /^(\d+)(?: (?:to )?(\d+))?$/) { + ($shortFocal, $longFocal) = ($1, $2 || $1); + } + if ($$et{Make} eq 'SONY') { + if ($lensType eq 65535) { + # handle Sony E-type lenses when LensType2 isn't valid (NEX/ILCE models only) + if ($$et{Model} =~ /NEX|ILCE/) { + unless (%sonyEtype) { + my ($index, $i, %did, $lens); + require Image::ExifTool::Sony; + foreach (sort keys %Image::ExifTool::Sony::sonyLensTypes2) { + ($lens = $Image::ExifTool::Sony::sonyLensTypes2{$_}) =~ s/ or .*//; + next if $did{$lens}; + ($i, $index) = $index ? ("65535.$index", $index + 1) : (65535, 1); + $did{$sonyEtype{$i} = $lens} = 1; + } + } + $printConv = \%sonyEtype; + } + } elsif ($lensType != 0xff00) { + # Patch for Metabones or other adapters on Sony E-mount cameras (ref Jos Roost) + # Metabones Canon EF to E-mount adapters add 0xef00, 0xbc00 or 0x7700 to the + # high byte for 2-byte Canon LensType values, so we need to adjust for these. + # Offset 0xef00 is also used by Sigma MC-11, Fotodiox and Viltrox EF-E adapters. + # Have to exclude A-mount Sigma Filtermatic with 'odd' LensType=0xff00. + require Image::ExifTool::Minolta; + if ($Image::ExifTool::Minolta::metabonesID{$lensType & 0xff00}) { + $lensType -= ($lensType >= 0xef00 ? 0xef00 : $lensType >= 0xbc00 ? 0xbc00 : 0x7700); + require Image::ExifTool::Canon; + $printConv = \%Image::ExifTool::Canon::canonLensTypes; + $lensTypePrt = $$printConv{$lensType} if $$printConv{$lensType}; + # Test for Sigma MC-11 SA-E adapter with Sigma SA lens using 0x4900 offset. + # (upper limit of test cuts off two highest Sigma lenses, but prevents + # conflict with old Minolta 25xxx and higher ID's) + } elsif ($lensType >= 0x4900 and $lensType <= 0x590a) { + require Image::ExifTool::Sigma; + $lensType -= 0x4900; + $printConv = \%Image::ExifTool::Sigma::sigmaLensTypes; + $lensTypePrt = $$printConv{$lensType} if $$printConv{$lensType}; + } + } + # (Min/MaxFocalLength may report the current focal length for Tamron zoom lenses) + } elsif ($shortFocal and $longFocal and (not $lensModel or $lensModel !~ /^TAMRON.*-\d+mm/)) { + # Canon (and some other makes) include makernote information + # which allows better lens identification + require Image::ExifTool::Canon; + return Image::ExifTool::Canon::PrintLensID($printConv, $lensType, + $shortFocal, $longFocal, $maxAperture, $lensModel); + } + my $lens = $$printConv{$lensType}; + return ($lensModel || $lensTypePrt) unless $lens; + return $lens unless $$printConv{"$lensType.1"}; + $lens =~ s/ or .*//s; # remove everything after "or" + # make list of all possible matching lenses + my @lenses = ( $lens ); + my $i; + for ($i=1; $$printConv{"$lensType.$i"}; ++$i) { + push @lenses, $$printConv{"$lensType.$i"}; + } + # attempt to determine actual lens + my (@matches, @best, @user, $diff); + foreach $lens (@lenses) { + push @user, $lens if $Image::ExifTool::userLens{$lens}; + # sf = short focal + # lf = long focal + # sa = max aperture at short focal + # la = max aperture at long focal + my ($sf, $lf, $sa, $la) = GetLensInfo($lens); + next unless $sf; + # check against LensSpec parameters if available + if ($sf0) { + next if abs($sf - $sf0) > 0.5 or abs($sa - $sa0) > 0.15 or + abs($lf - $lf0) > 0.5 or abs($la - $la0) > 0.15; + # the basic parameters match, but also check against additional lens features: + # for Sony A and E lenses, the full LensSpec string should match with end of LensType, + # excluding any part between () at the end, and preceded by a space (the space + # ensures that e.g. Zeiss Loxia 21mm having LensSpec "E 21mm F2.8" will not be + # identified as "Sony FE 21mm F2.8 (SEL28F20 + SEL075UWC)") + $lensSpecPrt and $lens =~ / \Q$lensSpecPrt\E( \(| GM$|$)/ and @best = ( $lens ), last; + # exactly-matching Sony lens should have been found above, so only add non-Sony lenses + push @best, $lens unless $lens =~ /^Sony /; + next; + } + # adjust focal length and aperture if teleconverter is attached (Minolta) + if ($lens =~ / \+ .*? (\d+(\.\d+)?)x( |$)/) { + $sf *= $1; $lf *= $1; + $sa *= $1; $la *= $1; + } + # see if we can rule out this lens using FocalLength and MaxAperture + if ($focalLength) { + next if $focalLength < $sf - 0.5; + next if $focalLength > $lf + 0.5; + } + if ($maxAperture) { + # it seems that most manufacturers set MaxAperture and MaxApertureValue + # to the maximum aperture (smallest F number) for the current focal length + # of the lens, so assume that MaxAperture varies with focal length and find + # the closest match (this is somewhat contrary to the EXIF specification which + # states "The smallest F number of the lens", without mention of focal length) + next if $maxAperture < $sa - 0.15; # (0.15 is arbitrary) + next if $maxAperture > $la + 0.15; + # now determine the best match for this aperture + my $aa; # approximate maximum aperture at this focal length + if ($sf == $lf or $sa == $la or $focalLength <= $sf) { + # either 1) prime lens, 2) fixed-aperture zoom, or 3) zoom at min focal + $aa = $sa; + } elsif ($focalLength >= $lf) { + $aa = $la; + } else { + # assume a log-log variation of max aperture with focal length + # (see http://regex.info/blog/2006-10-05/263) + $aa = exp(log($sa) + (log($la)-log($sa)) / (log($lf)-log($sf)) * + (log($focalLength)-log($sf))); + # a linear relationship between 1/FocalLength and 1/MaxAperture fits Sony better (ref 27) + #$aa = 1 / (1/$sa + (1/$focalLength - 1/$sf) * (1/$la - 1/$sa) / (1/$lf - 1/$sf)); + } + my $d = abs($maxAperture - $aa); + if (defined $diff) { + $d > $diff + 0.15 and next; # (0.15 is arbitrary) + $d < $diff - 0.15 and undef @best; + } + $diff = $d; + push @best, $lens; + } + push @matches, $lens; + } + # return the user-defined lens if it exists + if (@user) { + # choose the best match if we have more than one + if (@user > 1) { + my ($try, @good); + foreach $try (\@best, \@matches) { + $Image::ExifTool::userLens{$_} and push @good, $_ foreach @$try; + return join(' or ', @good) if @good; + } + } + return join(' or ', @user); + } + # return the best match(es) from the possible lenses, after checking against LensModel + @best = @matches unless @best; + if (@best) { + MatchLensModel(\@best, $lensModel); + return join(' or ', @best); + } + $lens = $$printConv{$lensType}; + return $lensModel if $lensModel and $lens =~ / or /; # (eg. Sony NEX-5N) + return $lens; +} + +#------------------------------------------------------------------------------ +# Translate date into standard EXIF format +# Inputs: 0) date +# Returns: date in format '2003:10:22' +# - bad formats recognized: '2003-10-22','2003/10/22','2003 10 22','20031022' +# - removes null terminator if it exists +sub ExifDate($) +{ + my $date = shift; + $date =~ s/\0$//; # remove any null terminator + # separate year:month:day with colons + # (have seen many other characters, including nulls, used erroneously) + $date =~ s/(\d{4})[^\d]*(\d{2})[^\d]*(\d{2})$/$1:$2:$3/; + return $date; +} + +#------------------------------------------------------------------------------ +# Translate time into standard EXIF format +# Inputs: 0) time +# Returns: time in format '10:30:55' +# - bad formats recognized: '10 30 55', '103055', '103055+0500' +# - removes null terminator if it exists +# - leaves time zone intact if specified (eg. '10:30:55+05:00') +sub ExifTime($) +{ + my $time = shift; + $time =~ tr/ /:/; # use ':' (not ' ') as a separator + $time =~ s/\0$//; # remove any null terminator + # add separators if they don't exist + $time =~ s/^(\d{2})(\d{2})(\d{2})/$1:$2:$3/; + $time =~ s/([+-]\d{2})(\d{2})\s*$/$1:$2/; # to timezone too + return $time; +} + +#------------------------------------------------------------------------------ +# Generate TIFF file from scratch (in current byte order) +# Inputs: 0) hash of IFD entries (TagID => Value; multiple values space-delimited) +# 1) raw image data reference +# Returns: TIFF image data, or undef on error +sub GenerateTIFF($$) +{ + my ($entries, $dataPt) = @_; + my ($rtnVal, $tag, $offsetPos); + + my $num = scalar keys %$entries; + my $ifdBuff = GetByteOrder() . Set16u(42) . Set32u(8) . Set16u($num); + my $valBuff = ''; + my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); + foreach $tag (sort { $a <=> $b } keys %$entries) { + my $tagInfo = $$tagTablePtr{$tag}; + my $fmt = ref $tagInfo eq 'HASH' ? $$tagInfo{Writable} : 'int32u'; + return undef unless defined $fmt; + my $val = Image::ExifTool::WriteValue($$entries{$tag}, $fmt, -1); + return undef unless defined $val; + my $format = $formatNumber{$fmt}; + $ifdBuff .= Set16u($tag) . Set16u($format) . Set32u(length($val)/$formatSize[$format]); + $offsetPos = length($ifdBuff) if $tag == 0x111; # (remember StripOffsets position) + if (length $val > 4) { + $ifdBuff .= Set32u(10 + 12 * $num + 4 + length($valBuff)); + $valBuff .= $val; + } else { + $val .= "\0" x (4 - length($val)) if length $val < 4; + $ifdBuff .= $val; + } + } + $ifdBuff .= "\0\0\0\0"; # (no IFD1) + return undef unless $offsetPos; + Set32u(length($ifdBuff) + length($valBuff), \$ifdBuff, $offsetPos); + return $ifdBuff . $valBuff . $$dataPt; +} + +#------------------------------------------------------------------------------ +# Rebuild TIFF thumbnail(s)/preview(s) into stand-alone files with current byte order +# Inputs: 0) ExifTool ref, 1) SubfileType, 2) Compression, 3) ImageWidth, 4) ImageHeight, +# 5) BitsPerSample, 6) PhotometricInterpretation, 7) StripOffsets, 8) SamplesPerPixel, +# 9) RowsPerStrip, 10) StripByteCounts, 10) PlanarConfiguration, 11) Orientation +# Returns: 0) TIFF image or undef, 1/2) Family 0/1 groups for TIFF preview IFD +sub RebuildTIFF($;@) +{ + local $_; + my $et = $_[0]; + my $value = $$et{VALUE}; + my ($i, $j, $rtn, $grp0, $grp1); + return undef if $$et{FILE_TYPE} eq 'RWZ'; +SubFile: + for ($i=0; ; ++$i) { + my $key = 'SubfileType' . ($i ? " ($i)" : ''); + last unless defined $$value{$key}; + next unless $$value{$key} == 1; # (reduced-resolution image) + my $grp = $et->GetGroup($key, 1); + my $cmp = $et->FindValue('Compression', $grp); + next unless $cmp == 1; # (no compression) + my %vals = (Compression=>$cmp, PlanarConfiguration=>1, Orientation=>1); + foreach (qw(ImageWidth ImageHeight BitsPerSample PhotometricInterpretation + StripOffsets SamplesPerPixel RowsPerStrip StripByteCounts + PlanarConfiguration Orientation)) + { + my $val = $et->FindValue($_, $grp); + defined $val and $vals{$_} = $val, next; + next SubFile unless defined $vals{$_}; + } + my ($w, $h) = @vals{'ImageWidth', 'ImageHeight'}; + my @bits = split ' ', $vals{BitsPerSample}; + my $rowBytes = 0; + $rowBytes += $w * int(($_+7)/8) foreach @bits; + my $dat = ''; + my @off = split ' ', $vals{StripOffsets}; + my @len = split ' ', $vals{StripByteCounts}; + # read the image data + for ($j=0; $j<@off; ++$j) { + next SubFile unless $len[$j] == $rowBytes * $vals{RowsPerStrip}; + my $tmp = $et->ExtractBinary($off[$j], $len[$j]); + next SubFile unless defined $tmp; + $dat .= $tmp; + } + # generate the TIFF image + my %entries = ( + 0x0fe => 0, # SubfileType = 0 + 0x100 => $w, # ImageWidth + 0x101 => $h, # ImageHeight + 0x102 => $vals{BitsPerSample},# BitsPerSample + 0x103 => $vals{Compression},# Compression + 0x106 => $vals{PhotometricInterpretation}, # PhotometricInterpretation + 0x111 => 0, # StripOffsets (will be adjusted later) + 0x112 => $vals{Orientation},# Orientation + 0x115 => $vals{SamplesPerPixel}, # SamplesPerPixel + 0x116 => $h, # RowsPerStrip + 0x117 => $h * $rowBytes, # StripByteCounts + 0x11a => 72, # XResolution = 72 + 0x11b => 72, # YResolution = 72 + 0x11c => $vals{PlanarConfiguration}, # PlanarConfiguration + 0x128 => 2, # ResolutionUnit = 2 + ); + my $img = GenerateTIFF(\%entries, \$dat); + + if (not defined $img) { + $et->Warn('Invalid ' . ($w > 256 ? 'Preview' : 'Thumbnail') . 'TIFF data'); + } elsif ($rtn or $w > 256) { # (call it a preview if larger than 256 pixels) + $et->FoundTag('PreviewTIFF', \$img, $et->GetGroup($key)); + } else { + $rtn = \$img; + ($grp0, $grp1) = $et->GetGroup($key); + } + } + return $rtn unless wantarray; + return ($rtn, $grp0, $grp1); +} + +#------------------------------------------------------------------------------ +# Extract image from file +# Inputs: 0) ExifTool object reference, 1) data offset (in file), 2) data length +# 3) [optional] tag name +# Returns: Reference to Image if specifically requested or "Binary data" message +# Returns undef if there was an error loading the image +sub ExtractImage($$$$) +{ + my ($et, $offset, $len, $tag) = @_; + my $dataPt = \$$et{EXIF_DATA}; + my $dataPos = $$et{EXIF_POS}; + my $image; + + # no image if length is zero, and don't try to extract binary from XMP file + return undef if not $len or $$et{FILE_TYPE} eq 'XMP'; + + # take data from EXIF block if possible + if (defined $dataPos and $offset>=$dataPos and $offset+$len<=$dataPos+length($$dataPt)) { + $image = substr($$dataPt, $offset-$dataPos, $len); + } else { + $image = $et->ExtractBinary($offset, $len, $tag); + return undef unless defined $image; + # patch for incorrect ThumbnailOffset in some Sony DSLR-A100 ARW images + if ($tag and $tag eq 'ThumbnailImage' and $$et{TIFF_TYPE} eq 'ARW' and + $$et{Model} eq 'DSLR-A100' and $offset < 0x10000 and + $image !~ /^(Binary data|\xff\xd8\xff)/) + { + my $try = $et->ExtractBinary($offset + 0x10000, $len, $tag); + if (defined $try and $try =~ /^\xff\xd8\xff/) { + $image = $try; + $$et{VALUE}{ThumbnailOffset} += 0x10000; + $et->Warn('Adjusted incorrect A100 ThumbnailOffset', 1); + } + } + } + return $et->ValidateImage(\$image, $tag); +} + +#------------------------------------------------------------------------------ +# Utility routine to return tag ID string for warnings +# Inputs: 0) Tag ID, 1) [optional] TagInfo ref +# Returns: "tag 0xXXXX NAME" +sub TagName($;$) +{ + my ($tagID, $tagInfo) = @_; + my $tagName = $tagInfo ? ' '.$$tagInfo{Name} : ''; + return sprintf('tag 0x%.4x%s', $tagID, $tagName); +} + +#------------------------------------------------------------------------------ +# Get class name of next IFD offset for HtmlDump output +# Inputs: 0) ExifTool ref, 1) current class ID +# Returns: 0) new IFD offset name, 1) new class ID including "Offset_" for new offset +# 2) new "Offset_" ID +sub NextOffsetName($;$) +{ + my ($et, $id) = @_; + $$et{OffsetNum} = defined $$et{OffsetNum} ? $$et{OffsetNum} + 1 : 0; + my $offName = 'o' . $$et{OffsetNum}; + my $sid = "Offset_$offName"; + $id = (defined $id ? "$id " : '') . $sid; + return ($offName, $id, $sid); +} + +#------------------------------------------------------------------------------ +# Process EXIF directory +# Inputs: 0) ExifTool object reference +# 1) Reference to directory information hash +# 2) Pointer to tag table for this directory +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessExif($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos} || 0; + my $dataLen = $$dirInfo{DataLen}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || $dataLen - $dirStart; + my $dirName = $$dirInfo{DirName}; + my $base = $$dirInfo{Base} || 0; + my $firstBase = $base; + my $raf = $$dirInfo{RAF}; + my ($verbose,$validate,$saveFormat) = @{$$et{OPTIONS}}{qw(Verbose Validate SaveFormat)}; + my $htmlDump = $$et{HTML_DUMP}; + my $success = 1; + my ($tagKey, $dirSize, $makerAddr, $strEnc, %offsetInfo, $offName, $nextOffName, $doHash); + my $inMakerNotes = $$tagTablePtr{GROUPS}{0} eq 'MakerNotes'; + my $isExif = ($tagTablePtr eq \%Image::ExifTool::Exif::Main); + + # set flag to calculate image data hash if requested + $doHash = 1 if $$et{ImageDataHash} and (($$et{FILE_TYPE} eq 'TIFF' and not $base and not $inMakerNotes) or + ($$et{FILE_TYPE} eq 'RAF' and $dirName eq 'FujiIFD')); + + # set encoding to assume for strings + $strEnc = $et->Options('CharsetEXIF') if $$tagTablePtr{GROUPS}{0} eq 'EXIF'; + + # ignore non-standard EXIF while in strict MWG compatibility mode + if (($validate or $Image::ExifTool::MWG::strict) and $dirName eq 'IFD0' and + $isExif and $$et{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/) + { + my $path = $et->MetadataPath(); + if ($path =~ /^(JPEG-APP1-IFD0|TIFF-IFD0|PSD-EXIFInfo-IFD0)$/) { + unless ($$et{DOC_NUM}) { + $et->Warn("Duplicate EXIF at $path") if $$et{HasExif}; + $$et{HasExif} = 1; + } + } else { + if ($Image::ExifTool::MWG::strict) { + $et->Warn("Ignored non-standard EXIF at $path"); + return 0; + } else { + $et->Warn("Non-standard EXIF at $path", 1); + } + } + } + # mix htmlDump and Validate into verbose so we can test for all at once + $verbose = -1 if $htmlDump; + $verbose = -2 if $validate and not $verbose; + $dirName eq 'EXIF' and $dirName = $$dirInfo{DirName} = 'IFD0'; + $$dirInfo{Multi} = 1 if $dirName =~ /^(IFD0|SubIFD)$/ and not defined $$dirInfo{Multi}; + # get a more descriptive name for MakerNote sub-directories + my $dir = $$dirInfo{Name}; + $dir = $dirName unless $dir and $inMakerNotes and $dir !~ /^MakerNote/; + + my ($numEntries, $dirEnd); + if ($dirStart >= 0 and $dirStart <= $dataLen-2) { + # make sure data is large enough (patches bug in Olympus subdirectory lengths) + $numEntries = Get16u($dataPt, $dirStart); + $dirSize = 2 + 12 * $numEntries; + $dirEnd = $dirStart + $dirSize; + if ($dirSize > $dirLen) { + if (($verbose > 0 or $validate) and not $$dirInfo{SubIFD}) { + my $short = $dirSize - $dirLen; + $$et{INDENT} =~ s/..$//; # keep indent the same + $et->Warn("Short directory size for $dir (missing $short bytes)"); + $$et{INDENT} .= '| '; + } + undef $dirSize if $dirEnd > $dataLen; # read from file if necessary + } + } + # read IFD from file if necessary + unless ($dirSize) { + $success = 0; + if ($raf) { + # read the count of entries in this IFD + my $offset = $dirStart + $dataPos; + my ($buff, $buf2); + if ($raf->Seek($offset + $base, 0) and $raf->Read($buff,2) == 2) { + my $len = 12 * Get16u(\$buff,0); + # also read next IFD pointer if available + if ($raf->Read($buf2, $len+4) >= $len) { + $buff .= $buf2; + $dataPt = $$dirInfo{DataPt} = \$buff; + $dataPos = $$dirInfo{DataPos} = $offset; + $dataLen = $$dirInfo{DataLen} = length $buff; + $dirStart = $$dirInfo{DirStart} = 0; + $dirLen = $$dirInfo{DirLen} = length $buff; + $success = 1; + } + } + } + if ($success) { + $numEntries = Get16u($dataPt, $dirStart); + } else { + $et->Warn("Bad $dir directory", $inMakerNotes); + return 0 unless $inMakerNotes and $dirLen >= 14 and $dirStart >= 0 and + $dirStart + $dirLen <= length($$dataPt); + $dirSize = $dirLen; + $numEntries = int(($dirSize - 2) / 12); # read what we can + Set16u($numEntries, $dataPt, $dirStart); + } + $dirSize = 2 + 12 * $numEntries; + $dirEnd = $dirStart + $dirSize; + } + $verbose > 0 and $et->VerboseDir($dirName, $numEntries); + my $bytesFromEnd = $dataLen - $dirEnd; + if ($bytesFromEnd < 4) { + unless ($bytesFromEnd==2 or $bytesFromEnd==0) { + $et->Warn("Illegal $dir directory size ($numEntries entries)"); + return 0; + } + } + # fix base offset for maker notes if necessary + if (defined $$dirInfo{MakerNoteAddr}) { + $makerAddr = $$dirInfo{MakerNoteAddr}; + delete $$dirInfo{MakerNoteAddr}; + if (Image::ExifTool::MakerNotes::FixBase($et, $dirInfo)) { + $base = $$dirInfo{Base}; + $dataPos = $$dirInfo{DataPos}; + } + } + if ($htmlDump) { + $offName = $$dirInfo{OffsetName}; + my $longName = $dir eq 'MakerNotes' ? ($$dirInfo{Name} || $dir) : $dir; + if (defined $makerAddr) { + my $hdrLen = $dirStart + $dataPos + $base - $makerAddr; + $et->HDump($makerAddr, $hdrLen, "MakerNotes header", $longName) if $hdrLen > 0; + } + unless ($$dirInfo{NoDumpEntryCount}) { + $et->HDump($dirStart + $dataPos + $base, 2, "$longName entries", + "Entry count: $numEntries", undef, $offName); + } + my $tip; + my $id = $offName; + if ($bytesFromEnd >= 4) { + my $nxt = ($dir =~ /^(.*?)(\d+)$/) ? $1 . ($2 + 1) : 'Next IFD'; + my $off = Get32u($dataPt, $dirEnd); + $tip = sprintf("$nxt offset: 0x%.4x", $off); + ($nextOffName, $id) = NextOffsetName($et, $offName) if $off; + } + $et->HDump($dirEnd + $dataPos + $base, 4, "Next IFD", $tip, 0, $id); + } + + # patch for Canon EOS 40D firmware 1.0.4 bug (incorrect directory counts) + # (must do this before parsing directory or CameraSettings offset will be suspicious) + if ($inMakerNotes and $$et{Model} eq 'Canon EOS 40D' and $numEntries) { + my $entry = $dirStart + 2 + 12 * ($numEntries - 1); + my $fmt = Get16u($dataPt, $entry + 2); + if ($fmt < 1 or $fmt > 13) { + $et->HDump($entry+$dataPos+$base,12,"[invalid IFD entry]", + "Bad format type: $fmt", 1, $offName); + # adjust the number of directory entries + --$numEntries; + $dirEnd -= 12; + } + } + + # make sure that Compression and SubfileType are defined for this IFD (for Condition's) + $$et{Compression} = $$et{SubfileType} = ''; + + # loop through all entries in an EXIF directory (IFD) + my ($index, $valEnd, $offList, $offHash, $mapFmt, @valPos); + $mapFmt = $$tagTablePtr{VARS}{MAP_FORMAT} if $$tagTablePtr{VARS}; + + my ($warnCount, $lastID) = (0, -1); + for ($index=0; $index<$numEntries; ++$index) { + if ($warnCount > 10) { + $et->Warn("Too many warnings -- $dir parsing aborted", 2) and return 0; + } + my $entry = $dirStart + 2 + 12 * $index; + my $tagID = Get16u($dataPt, $entry); + my $format = Get16u($dataPt, $entry+2); + my $count = Get32u($dataPt, $entry+4); + # (Apple uses the BigTIFF format code 16 in the maker notes of their ProRaw DNG files) + if (($format < 1 or $format > 13) and $format != 129 and not ($format == 16 and $$et{Make} eq 'Apple' and $inMakerNotes)) { + if ($mapFmt and $$mapFmt{$format}) { + $format = $$mapFmt{$format}; + } else { + $et->HDump($entry+$dataPos+$base,12,"[invalid IFD entry]", + "Bad format type: $format", 1, $offName); + # warn unless the IFD was just padded with zeros + if ($format or $validate) { + $et->Warn("Bad format ($format) for $dir entry $index", $inMakerNotes); + ++$warnCount; + } + # assume corrupted IFD if this is our first entry (except Sony ILCE-7M2 firmware 1.21) + return 0 unless $index or $$et{Model} eq 'ILCE-7M2'; + next; + } + } + my $formatStr = $formatName[$format]; # get name of this format + my $valueDataPt = $dataPt; + my $valueDataPos = $dataPos; + my $valueDataLen = $dataLen; + my $valuePtr = $entry + 8; # pointer to value within $$dataPt + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID); + my ($origFormStr, $bad, $rational, $subOffName); + # save the EXIF format codes if requested + $$et{SaveFormat}{$saveFormat = $formatStr} = 1 if $saveFormat; + # hack to patch incorrect count in Kodak SubIFD3 tags + if ($count < 2 and ref $$tagTablePtr{$tagID} eq 'HASH' and $$tagTablePtr{$tagID}{FixCount}) { + $offList or ($offList, $offHash) = GetOffList($dataPt, $dirStart, $dataPos, + $numEntries, $tagTablePtr); + my $i = $$offHash{Get32u($dataPt, $valuePtr)}; + if (defined $i and $i < $#$offList) { + my $oldCount = $count; + $count = int(($$offList[$i+1] - $$offList[$i]) / $formatSize[$format]); + $origFormStr = $formatName[$format] . '[' . $oldCount . ']' if $oldCount != $count; + } + } + $validate and not $inMakerNotes and Image::ExifTool::Validate::ValidateExif( + $et, $tagTablePtr, $tagID, $tagInfo, $lastID, $dir, $count, $formatStr); + my $size = $count * $formatSize[$format]; + my $readSize = $size; + if ($size > 4) { + if ($size > 0x7fffffff and (not $tagInfo or not $$tagInfo{ReadFromRAF})) { + $et->Warn(sprintf("Invalid size (%u) for %s %s",$size,$dir,TagName($tagID,$tagInfo)), $inMakerNotes); + ++$warnCount; + next; + } + $valuePtr = Get32u($dataPt, $valuePtr); + if ($validate and not $inMakerNotes) { + my $tagName = TagName($tagID, $tagInfo); + $et->Warn("Odd offset for $dir $tagName", 1) if $valuePtr & 0x01; + if ($valuePtr < 8 || ($valuePtr + $size > length($$dataPt) and + $valuePtr + $size > $$et{VALUE}{FileSize})) + { + $et->Warn("Invalid offset for $dir $tagName"); + ++$warnCount; + next; + } + if ($valuePtr + $size > $dirStart + $dataPos and $valuePtr < $dirEnd + $dataPos + 4) { + $et->Warn("Value for $dir $tagName overlaps IFD"); + } + foreach (@valPos) { + next if $$_[0] >= $valuePtr + $size or $$_[0] + $$_[1] <= $valuePtr; + $et->Warn("Value for $dir $tagName overlaps $$_[2]"); + } + push @valPos, [ $valuePtr, $size, $tagName ]; + } + # fix valuePtr if necessary + if ($$dirInfo{FixOffsets}) { + my $wFlag; + $valEnd or $valEnd = $dataPos + $dirEnd + 4; + #### eval FixOffsets ($valuePtr, $valEnd, $size, $tagID, $wFlag) + eval $$dirInfo{FixOffsets}; + } + my $suspect; + # offset shouldn't point into TIFF header + $valuePtr < 8 and not $$dirInfo{ZeroOffsetOK} and $suspect = $warnCount; + # convert offset to pointer in $$dataPt + if ($$dirInfo{EntryBased} or (ref $$tagTablePtr{$tagID} eq 'HASH' and + $$tagTablePtr{$tagID}{EntryBased})) + { + $valuePtr += $entry; + } else { + $valuePtr -= $dataPos; + } + # value shouldn't overlap our directory + $suspect = $warnCount if $valuePtr < $dirEnd and $valuePtr+$size > $dirStart; + # load value from file if necessary + if ($valuePtr < 0 or $valuePtr+$size > $dataLen) { + # get value by seeking in file if we are allowed + my $buff; + if ($raf) { + # avoid loading large binary data unless necessary + while ($size > BINARY_DATA_LIMIT) { + if ($tagInfo) { + # make large unknown blocks binary data + $$tagInfo{Binary} = 1 if $$tagInfo{Unknown}; + last unless $$tagInfo{Binary}; # must read non-binary data + last if $$tagInfo{SubDirectory}; + my $lcTag = lc($$tagInfo{Name}); + if ($$et{OPTIONS}{Binary} and + not $$et{EXCL_TAG_LOOKUP}{$lcTag}) + { + # read binary data if specified unless tagsFromFile won't use it + last unless $$et{TAGS_FROM_FILE} and $$tagInfo{Protected}; + } + # must read if tag is specified by name + last if $$et{REQ_TAG_LOOKUP}{$lcTag}; + } else { + # must read value if needed for a condition + last if defined $tagInfo; + } + # (note: changing the value without changing $size will cause + # a warning in the verbose output, but we need to maintain the + # proper size for the htmlDump, so we can't change this) + $buff = "Binary data $size bytes"; + $readSize = length $buff; + last; + } + # read from file if necessary + unless (defined $buff) { + my $wrn; + my $readFromRAF = ($tagInfo and $$tagInfo{ReadFromRAF}); + if (not $raf->Seek($base + $valuePtr + $dataPos, 0)) { + $wrn = "Invalid offset for $dir entry $index"; + } elsif ($readFromRAF and $size > BINARY_DATA_LIMIT and + not $$et{REQ_TAG_LOOKUP}{lc $$tagInfo{Name}}) + { + $buff = "$$tagInfo{Name} data $size bytes"; + $readSize = length $buff; + } elsif ($raf->Read($buff,$size) != $size) { + $wrn = "Error reading value for $dir entry $index"; + } elsif ($readFromRAF) { + # seek back to the start of the value + $raf->Seek($base + $valuePtr + $dataPos, 0); + } + if ($wrn) { + $et->Warn($wrn, $inMakerNotes); + return 0 unless $inMakerNotes or $htmlDump; + ++$warnCount; + $buff = '' unless defined $buff; + $readSize = length $buff; + $bad = 1; + } + } + $valueDataLen = length $buff; + $valueDataPt = \$buff; + $valueDataPos = $valuePtr + $dataPos; + $valuePtr = 0; + } else { + my ($tagStr, $tmpInfo, $leicaTrailer); + if ($tagInfo) { + $tagStr = $$tagInfo{Name}; + $leicaTrailer = $$tagInfo{LeicaTrailer}; + } elsif (defined $tagInfo) { + $tmpInfo = $et->GetTagInfo($tagTablePtr, $tagID, \ '', $formatStr, $count); + if ($tmpInfo) { + $tagStr = $$tmpInfo{Name}; + $leicaTrailer = $$tmpInfo{LeicaTrailer}; + } + } + if ($tagInfo and $$tagInfo{ChangeBase}) { + # adjust base offset for this tag only + #### eval ChangeBase ($dirStart,$dataPos) + my $newBase = eval $$tagInfo{ChangeBase}; + $valuePtr += $newBase; + } + $tagStr or $tagStr = sprintf("tag 0x%.4x",$tagID); + # allow PreviewImage to run outside EXIF data + if ($tagStr eq 'PreviewImage' and $$et{RAF}) { + my $pos = $$et{RAF}->Tell(); + $buff = $et->ExtractBinary($base + $valuePtr + $dataPos, $size, 'PreviewImage'); + $$et{RAF}->Seek($pos, 0); + $valueDataPt = \$buff; + $valueDataPos = $valuePtr + $dataPos; + $valueDataLen = $size; + $valuePtr = 0; + } elsif ($leicaTrailer and $$et{RAF}) { + if ($verbose > 0) { + $et->VPrint(0, "$$et{INDENT}$index) $tagStr --> (outside APP1 segment)\n"); + } + if ($et->Options('FastScan')) { + $et->Warn('Ignored Leica MakerNote trailer'); + } else { + require Image::ExifTool::Fixup; + $$et{LeicaTrailer} = { + TagInfo => $tagInfo || $tmpInfo, + Offset => $base + $valuePtr + $dataPos, + Size => $size, + Fixup => new Image::ExifTool::Fixup, + }; + } + } else { + $et->Warn("Bad offset for $dir $tagStr", $inMakerNotes); + ++$warnCount; + } + unless (defined $buff) { + $valueDataPt = ''; + $valueDataPos = $valuePtr + $dataPos; + $valueDataLen = 0; + $valuePtr = 0; + $bad = 1; + } + } + } + # warn about suspect offsets if they didn't already cause another warning + if (defined $suspect and $suspect == $warnCount) { + my $tagStr = $tagInfo ? $$tagInfo{Name} : sprintf('tag 0x%.4x', $tagID); + if ($et->Warn("Suspicious $dir offset for $tagStr", $inMakerNotes)) { + ++$warnCount; + next unless $verbose; + } + } + } + # treat single unknown byte as int8u + $formatStr = 'int8u' if $format == 7 and $count == 1; + + my ($val, $subdir, $wrongFormat); + if ($tagID > 0xf000 and $isExif) { + my $oldInfo = $$tagTablePtr{$tagID}; + if ((not $oldInfo or (ref $oldInfo eq 'HASH' and $$oldInfo{Condition} and + not $$oldInfo{PSRaw})) and not $bad) + { + # handle special case of Photoshop RAW tags (0xfde8-0xfe58) + # --> generate tags from the value if possible + $val = ReadValue($valueDataPt,$valuePtr,$formatStr,$count,$readSize); + if (defined $val and $val =~ /(.*): (.*)/) { + my $tag = $1; + $val = $2; + $tag =~ s/'s//; # remove 's (so "Owner's Name" becomes "OwnerName") + $tag =~ tr/a-zA-Z0-9_//cd; # remove unknown characters + if ($tag) { + $tagInfo = { + Name => $tag, + Condition => '$$self{TIFF_TYPE} ne "DCR"', + ValueConv => '$_=$val;s/^.*: //;$_', # remove descr + PSRaw => 1, # (just as flag to avoid adding this again) + }; + AddTagToTable($tagTablePtr, $tagID, $tagInfo); + # generate conditional list if a conditional tag already existed + $$tagTablePtr{$tagID} = [ $oldInfo, $tagInfo ] if $oldInfo; + } + } + } + } + if (defined $tagInfo and not $tagInfo) { + if ($bad) { + undef $tagInfo; + } else { + # GetTagInfo() required the value for a Condition + my $tmpVal = substr($$valueDataPt, $valuePtr, $readSize < 128 ? $readSize : 128); + # (use original format name in this call -- $formatStr may have been changed to int8u) + $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID, \$tmpVal, + $formatName[$format], $count); + } + } + # make sure we are handling the 'ifd' format properly + if (($format == 13 or $format == 18) and (not $tagInfo or not $$tagInfo{SubIFD})) { + my $str = sprintf('%s tag 0x%.4x IFD format not handled', $dirName, $tagID); + $et->Warn($str, $inMakerNotes); + } + if (defined $tagInfo) { + my $readFormat = $$tagInfo{Format}; + $subdir = $$tagInfo{SubDirectory}; + # unless otherwise specified, all SubDirectory data except + # EXIF SubIFD offsets should be unformatted + $readFormat = 'undef' if $subdir and not $$tagInfo{SubIFD} and not $readFormat; + # override EXIF format if specified + if ($readFormat) { + $formatStr = $readFormat; + my $newNum = $formatNumber{$formatStr}; + if ($newNum and $newNum != $format) { + $origFormStr = $formatName[$format] . '[' . $count . ']'; + $format = $newNum; + $size = $readSize = $$tagInfo{FixedSize} if $$tagInfo{FixedSize}; + # adjust number of items for new format size + $count = int($size / $formatSize[$format]); + } + } + # verify that offset-type values are integral + if (($$tagInfo{IsOffset} or $$tagInfo{SubIFD}) and not $intFormat{$formatStr}) { + $et->Warn(sprintf('Wrong format (%s) for %s 0x%.4x %s',$formatStr,$dir,$tagID,$$tagInfo{Name})); + if ($validate) { + $$et{WrongFormat}{"$dir:$$tagInfo{Name}"} = 1; + $offsetInfo{$tagID} = [ $tagInfo, '' ]; + } + next unless $verbose; + $wrongFormat = 1; + } + } else { + next unless $verbose; + } + unless ($bad) { + # limit maximum length of data to reformat + # (avoids long delays when processing some corrupted files) + my $warned; + if ($count > 100000 and $formatStr !~ /^(undef|string|binary)$/) { + my $tagName = $tagInfo ? $$tagInfo{Name} : sprintf('tag 0x%.4x', $tagID); + # (count of 196608 is typical for ColorMap) + if ($tagName ne 'TransferFunction' or $count != 196608) { + my $minor = $count > 2000000 ? 0 : 2; + if ($et->Warn("Ignoring $dirName $tagName with excessive count", $minor)) { + next unless $$et{OPTIONS}{HtmlDump}; + $warned = 1; + } + } + } + if ($count > 500 and $formatStr !~ /^(undef|string|binary)$/ and + (not $tagInfo or $$tagInfo{LongBinary} or $warned) and not $$et{OPTIONS}{IgnoreMinorErrors}) + { + $et->WarnOnce('Not decoding some large array(s). Ignore minor errors to decode', 2) unless $warned; + next if $$et{TAGS_FROM_FILE}; # don't generate bogus value when copying tags + $val = "(large array of $count $formatStr values)"; + } else { + # convert according to specified format + $val = ReadValue($valueDataPt,$valuePtr,$formatStr,$count,$readSize,\$rational); + # re-code if necessary + if (defined $val) { + if ($formatStr eq 'utf8') { + $val = $et->Decode($val, 'UTF8'); + } elsif ($strEnc and $formatStr eq 'string') { + $val = $et->Decode($val, $strEnc); + } + } + } + } + + if ($verbose) { + my $tval = $val; + # also show as a rational + $tval .= " ($rational)" if defined $rational; + if ($htmlDump) { + my ($tagName, $colName); + if ($tagInfo) { + $tagName = $$tagInfo{Name}; + } elsif ($tagID == 0x927c and $dirName eq 'ExifIFD') { + $tagName = 'MakerNotes'; + } else { + $tagName = sprintf("Tag 0x%.4x",$tagID); + } + my $dname = sprintf("${dir}-%.2d", $index); + # build our tool tip + $size < 0 and $size = $count * $formatSize[$format]; + my $fstr = "$formatName[$format]\[$count]"; + $fstr = "$origFormStr read as $fstr" if $origFormStr and $origFormStr ne $fstr; + $fstr .= ' <-- WRONG' if $wrongFormat; + my $tip = sprintf("Tag ID: 0x%.4x\n", $tagID) . + "Format: $fstr\nSize: $size bytes\n"; + if ($size > 4) { + my $offPt = Get32u($dataPt,$entry+8); + # (test this with ../pics/{CanonEOS-1D_XMarkIII.hif,PanasonicDC-G9.rw2}) + my $actPt = $valuePtr + $valueDataPos + $base - ($$et{EXIF_POS} || 0) + ($$et{BASE_FUDGE} || 0); + $tip .= sprintf("Value offset: 0x%.4x\n", $offPt); + # highlight tag name (red for bad size) + my $style = ($bad or not defined $tval) ? 'V' : 'H'; + if ($actPt != $offPt) { + $tip .= sprintf("Actual offset: 0x%.4x\n", $actPt); + my $sign = $actPt < $offPt ? '-' : ''; + $tip .= sprintf("Offset base: ${sign}0x%.4x\n", abs($actPt - $offPt)); + $style = 'F' if $style eq 'H'; # purple for different offsets + } + if ($$et{EXIF_POS} and not $$et{BASE_FUDGE}) { + $tip .= sprintf("File offset: 0x%.4x\n", $actPt + $$et{EXIF_POS}) + } + $colName = "<span class=$style>$tagName</span>"; + $colName .= ' <span class=V>(odd)</span>' if $offPt & 0x01; + } else { + $colName = $tagName; + } + $colName .= ' <span class=V>(err)</span>' if $wrongFormat; + $colName .= ' <span class=V>(seq)</span>' if $tagID <= $lastID and not $inMakerNotes; + $lastID = $tagID; + if (not defined $tval) { + $tval = '<bad size/offset>'; + } else { + $tval = substr($tval,0,28) . '[...]' if length($tval) > 32; + if ($formatStr =~ /^(string|undef|binary)/) { + # translate non-printable characters + $tval =~ tr/\x00-\x1f\x7f-\xff/./; + } elsif ($tagInfo and Image::ExifTool::IsInt($tval)) { + if ($$tagInfo{IsOffset} or $$tagInfo{SubIFD}) { + $tval = sprintf('0x%.4x', $tval); + my $actPt = $val + $base - ($$et{EXIF_POS} || 0) + ($$et{BASE_FUDGE} || 0); + if ($actPt != $val) { + $tval .= sprintf("\nActual offset: 0x%.4x", $actPt); + my $sign = $actPt < $val ? '-' : ''; + $tval .= sprintf("\nOffset base: ${sign}0x%.4x", abs($actPt - $val)); + } + } elsif ($$tagInfo{PrintHex}) { + $tval = sprintf('0x%x', $tval); + } + } + } + $tip .= "Value: $tval"; + my $id = $offName; + my $sid; + ($subOffName, $id, $sid) = NextOffsetName($et, $offName) if $tagInfo and $$tagInfo{SubIFD}; + $et->HDump($entry+$dataPos+$base, 12, "$dname $colName", $tip, 1, $id); + next if $valueDataLen < 0; # don't process bad pointer entry + if ($size > 4) { + my $exifDumpPos = $valuePtr + $valueDataPos + $base; + my $flag = 0; + if ($subdir) { + if ($$tagInfo{MakerNotes}) { + $flag = 0x04; + } elsif ($$tagInfo{NestedHtmlDump}) { + $flag = $$tagInfo{NestedHtmlDump} == 2 ? 0x10 : 0x04; + } + } + # add value data block (underlining maker notes data) + $et->HDump($exifDumpPos,$size,"$tagName value",'SAME', $flag, $sid); + if ($subdir and $$tagInfo{MakerNotes} and $$tagInfo{NotIFD}) { + $et->HDump($exifDumpPos,$size,"$tagName value",undef,undef,$$dirInfo{OffsetName}); + } + } + } else { + if ($tagID <= $lastID and not $inMakerNotes) { + my $str = $tagInfo ? ' '.$$tagInfo{Name} : ''; + if ($tagID == $lastID) { + $et->Warn(sprintf('Duplicate tag 0x%.4x%s in %s', $tagID, $str, $dirName)); + } else { + $et->Warn(sprintf('Tag ID 0x%.4x%s out of sequence in %s', $tagID, $str, $dirName)); + } + } + $lastID = $tagID; + if ($verbose > 0) { + my $fstr = $formatName[$format]; + $fstr = "$origFormStr read as $fstr" if $origFormStr; + $et->VerboseInfo($tagID, $tagInfo, + Table => $tagTablePtr, + Index => $index, + Value => $tval, + DataPt => $valueDataPt, + DataPos => $valueDataPos + $base, + Size => $size, + Start => $valuePtr, + Format => $fstr, + Count => $count, + ); + } + } + next if not $tagInfo or $wrongFormat; + } + next unless defined $val; +#.............................................................................. +# Handle SubDirectory tag types +# + if ($subdir) { + # don't process empty subdirectories + unless ($size) { + unless ($$tagInfo{MakerNotes} or $inMakerNotes) { + $et->Warn("Empty $$tagInfo{Name} data", 1); + } + next; + } + my (@values, $newTagTable, $dirNum, $newByteOrder, $invalid); + my $tagStr = $$tagInfo{Name}; + if ($$subdir{MaxSubdirs}) { + @values = split ' ', $val; + # limit the number of subdirectories we parse + my $over = @values - $$subdir{MaxSubdirs}; + if ($over > 0) { + $et->Warn("Ignoring $over $tagStr directories"); + splice @values, $$subdir{MaxSubdirs}; + } + $val = shift @values; + } + if ($$subdir{TagTable}) { + $newTagTable = GetTagTable($$subdir{TagTable}); + $newTagTable or warn("Unknown tag table $$subdir{TagTable}"), next; + } else { + $newTagTable = $tagTablePtr; # use existing table + } + # loop through all sub-directories specified by this tag + for ($dirNum=0; ; ++$dirNum) { + my $subdirBase = $base; + my $subdirDataPt = $valueDataPt; + my $subdirDataPos = $valueDataPos; + my $subdirDataLen = $valueDataLen; + my $subdirStart = $valuePtr; + if (defined $$subdir{Start}) { + # set local $valuePtr relative to file $base for eval + my $valuePtr = $subdirStart + $subdirDataPos; + #### eval Start ($valuePtr, $val) + my $newStart = eval($$subdir{Start}); + unless (Image::ExifTool::IsInt($newStart)) { + $et->Warn("Bad value for $tagStr"); + last; + } + # convert back to relative to $subdirDataPt + $newStart -= $subdirDataPos; + # adjust directory size if necessary + unless ($$tagInfo{SubIFD} or $$subdir{BadOffset}) { + $size -= $newStart - $subdirStart; + } + $subdirStart = $newStart; + } + # this is a pain, but some maker notes are always a specific + # byte order, regardless of the byte order of the file + my $oldByteOrder = GetByteOrder(); + $newByteOrder = $$subdir{ByteOrder}; + if ($newByteOrder) { + if ($newByteOrder =~ /^Little/i) { + $newByteOrder = 'II'; + } elsif ($newByteOrder =~ /^Big/i) { + $newByteOrder = 'MM'; + } elsif ($$subdir{OffsetPt}) { + undef $newByteOrder; + warn "Can't have variable byte ordering for SubDirectories using OffsetPt"; + last; + } elsif ($subdirStart + 2 <= $subdirDataLen) { + # attempt to determine the byte ordering by checking + # the number of directory entries. This is an int16u + # that should be a reasonable value. + my $num = Get16u($subdirDataPt, $subdirStart); + if ($num & 0xff00 and ($num>>8) > ($num&0xff)) { + # This looks wrong, we shouldn't have this many entries + my %otherOrder = ( II=>'MM', MM=>'II' ); + $newByteOrder = $otherOrder{$oldByteOrder}; + } else { + $newByteOrder = $oldByteOrder; + } + } + } else { + $newByteOrder = $oldByteOrder; + } + # set base offset if necessary + if ($$subdir{Base}) { + # calculate subdirectory start relative to $base for eval + my $start = $subdirStart + $subdirDataPos; + #### eval Base ($start,$base) + $subdirBase = eval($$subdir{Base}) + $base; + } + # add offset to the start of the directory if necessary + if ($$subdir{OffsetPt}) { + #### eval OffsetPt ($valuePtr) + my $pos = eval $$subdir{OffsetPt}; + if ($pos + 4 > $subdirDataLen) { + $et->Warn("Bad $tagStr OffsetPt"); + last; + } + SetByteOrder($newByteOrder); + $subdirStart += Get32u($subdirDataPt, $pos); + SetByteOrder($oldByteOrder); + } + if ($subdirStart < 0 or $subdirStart + 2 > $subdirDataLen) { + # convert $subdirStart back to a file offset + if ($raf) { + # reset SubDirectory buffer (we will load it later) + my $buff = ''; + $subdirDataPt = \$buff; + $subdirDataLen = $size = length $buff; + } else { + my $msg = "Bad $tagStr SubDirectory start"; + if ($verbose > 0) { + if ($subdirStart < 0) { + $msg .= " (directory start $subdirStart is before EXIF start)"; + } else { + my $end = $subdirStart + $size; + $msg .= " (directory end is $end but EXIF size is only $subdirDataLen)"; + } + } + $et->Warn($msg, $inMakerNotes); + last; + } + } + + # must update subdirDataPos if $base changes for this subdirectory + $subdirDataPos += $base - $subdirBase; + + # build information hash for new directory + my %subdirInfo = ( + Name => $tagStr, + Base => $subdirBase, + DataPt => $subdirDataPt, + DataPos => $subdirDataPos, + DataLen => $subdirDataLen, + DirStart => $subdirStart, + DirLen => $size, + RAF => $raf, + Parent => $dirName, + DirName => $$subdir{DirName}, + FixBase => $$subdir{FixBase}, + FixOffsets => $$subdir{FixOffsets}, + EntryBased => $$subdir{EntryBased}, + TagInfo => $tagInfo, + SubIFD => $$tagInfo{SubIFD}, + Subdir => $subdir, + OffsetName => $subOffName, + ); + # (remember: some cameras incorrectly write maker notes in IFD0) + if ($$tagInfo{MakerNotes}) { + # don't parse makernotes if FastScan > 1 + my $fast = $et->Options('FastScan'); + last if $fast and $fast > 1; + $subdirInfo{MakerNoteAddr} = $valuePtr + $valueDataPos + $base; + $subdirInfo{NoFixBase} = 1 if defined $$subdir{Base}; + } + # set directory IFD name from group name of family 1 if it exists, + # unless the tag is writable as a block in which case group 1 may + # have been set automatically + if ($$tagInfo{Groups} and not $$tagInfo{Writable}) { + $subdirInfo{DirName} = $$tagInfo{Groups}{1}; + # number multiple subdirectories + $subdirInfo{DirName} =~ s/\d*$/$dirNum/ if $dirNum; + } + SetByteOrder($newByteOrder); # set byte order for this subdir + # validate the subdirectory if necessary + my $dirData = $subdirDataPt; # set data pointer to be used in eval + #### eval Validate ($val, $dirData, $subdirStart, $size) + my $ok = 0; + if (defined $$subdir{Validate} and not eval $$subdir{Validate}) { + $et->Warn("Invalid $tagStr data", $inMakerNotes); + $invalid = 1; + } else { + if (not $subdirInfo{DirName} and $inMakerNotes) { + $subdirInfo{DirName} = $$tagInfo{Name}; + } + # process the subdirectory + $ok = $et->ProcessDirectory(\%subdirInfo, $newTagTable, $$subdir{ProcessProc}); + } + # print debugging information if there were errors + if (not $ok and $verbose > 1 and $subdirStart != $valuePtr) { + my $out = $et->Options('TextOut'); + printf $out "%s (SubDirectory start = 0x%x)\n", $$et{INDENT}, $subdirStart; + } + SetByteOrder($oldByteOrder); # restore original byte swapping + + @values or last; + $val = shift @values; # continue with next subdir + } + my $doMaker = $et->Options('MakerNotes'); + next unless $doMaker or $$et{REQ_TAG_LOOKUP}{lc($tagStr)} or $$tagInfo{BlockExtract}; + # extract as a block if specified + if ($$tagInfo{MakerNotes}) { + # save maker note byte order (if it was significant and valid) + if ($$subdir{ByteOrder} and not $invalid) { + $$et{MAKER_NOTE_BYTE_ORDER} = + defined ($$et{UnknownByteOrder}) ? + $$et{UnknownByteOrder} : $newByteOrder; + } + if ($doMaker and $doMaker eq '2') { + # extract maker notes without rebuilding (no fixup information) + delete $$et{MAKER_NOTE_FIXUP}; + } elsif (not $$tagInfo{NotIFD} or $$tagInfo{IsPhaseOne}) { + # this is a pain, but we must rebuild EXIF-type maker notes to + # include all the value data if data was outside the maker notes + my %makerDirInfo = ( + Name => $tagStr, + Base => $base, + DataPt => $valueDataPt, + DataPos => $valueDataPos, + DataLen => $valueDataLen, + DirStart => $valuePtr, + DirLen => $size, + RAF => $raf, + Parent => $dirName, + DirName => 'MakerNotes', + FixOffsets => $$subdir{FixOffsets}, + TagInfo => $tagInfo, + ); + my $val2; + if ($$tagInfo{IsPhaseOne}) { + $$et{DropTags} = 1; + $val2 = Image::ExifTool::PhaseOne::WritePhaseOne($et, \%makerDirInfo, $newTagTable); + delete $$et{DropTags}; + } else { + $makerDirInfo{FixBase} = 1 if $$subdir{FixBase}; + # rebuild maker notes (creates $$et{MAKER_NOTE_FIXUP}) + $val2 = RebuildMakerNotes($et, \%makerDirInfo, $newTagTable); + } + if (defined $val2) { + $val = $val2; + } elsif ($size > 4) { + $et->Warn('Error rebuilding maker notes (may be corrupt)'); + } + } + } else { + # extract this directory as a block if specified + next unless $$tagInfo{Writable}; + } + } + #.............................................................................. + # convert to absolute offsets if this tag is an offset + #### eval IsOffset ($val, $et) + if ($$tagInfo{IsOffset} and eval $$tagInfo{IsOffset}) { + my $offsetBase = $$tagInfo{IsOffset} eq '2' ? $firstBase : $base; + $offsetBase += $$et{BASE}; + # handle offsets which use a wrong base (Minolta A200) + if ($$tagInfo{WrongBase}) { + my $self = $et; + #### eval WrongBase ($self) + $offsetBase += eval $$tagInfo{WrongBase} || 0; + } + my @vals = split(' ',$val); + foreach $val (@vals) { + $val += $offsetBase; + } + $val = join(' ', @vals); + } + if ($validate or $doHash) { + if ($$tagInfo{OffsetPair}) { + $offsetInfo{$tagID} = [ $tagInfo, $val ]; + } elsif ($saveForValidate{$tagID} and $isExif) { + $offsetInfo{$tagID} = $val; + } + } + # save the value of this tag + $tagKey = $et->FoundTag($tagInfo, $val); + if (defined $tagKey) { + # set the group 1 name for tags in specified tables + $et->SetGroup($tagKey, $dirName) if $$tagTablePtr{SET_GROUP1}; + # save original components of rational numbers (used when copying) + $$et{RATIONAL}{$tagKey} = $rational if defined $rational; + $$et{TAG_EXTRA}{$tagKey}{G6} = $saveFormat if $saveFormat; + } + } + + if (%offsetInfo) { + # calculate image data hash if requested + AddImageDataHash($et, $dirInfo, \%offsetInfo) if $doHash; + # validate image data offsets for this IFD (note: modifies %offsetInfo) + Image::ExifTool::Validate::ValidateOffsetInfo($et, \%offsetInfo, $dirName, $inMakerNotes) if $validate; + } + + # scan for subsequent IFD's if specified + if ($$dirInfo{Multi} and $bytesFromEnd >= 4) { + # use same directory information for trailing directory, + # but change the start location (ProcessDirectory will + # test to make sure we don't reprocess the same dir twice) + my %newDirInfo = %$dirInfo; + $newDirInfo{Multi} = 0; # prevent recursion + $newDirInfo{OffsetName} = $nextOffName; + $$et{INDENT} =~ s/..$//; # keep indent the same + for (;;) { + my $offset = Get32u($dataPt, $dirEnd) or last; + $newDirInfo{DirStart} = $offset - $dataPos; + # increment IFD number + my $ifdNum = $newDirInfo{DirName} =~ s/(\d+)$// ? $1 : 0; + $newDirInfo{DirName} .= $ifdNum + 1; + # must validate SubIFD1 because the nextIFD pointer is invalid for some RAW formats + if ($newDirInfo{DirName} ne 'SubIFD1' or ValidateIFD(\%newDirInfo)) { + my $cur = pop @{$$et{PATH}}; + $et->ProcessDirectory(\%newDirInfo, $tagTablePtr) or $success = 0; + push @{$$et{PATH}}, $cur; + if ($success and $newDirInfo{BytesFromEnd} >= 4) { + $dataPt = $newDirInfo{DataPt}; + $dataPos = $newDirInfo{DataPos}; + $dirEnd = $newDirInfo{DirEnd}; + next; + } + } elsif ($verbose or $$et{TIFF_TYPE} eq 'TIFF') { + $et->Warn('Ignored bad IFD linked from SubIFD'); + } + last; + } + } elsif (defined $$dirInfo{Multi}) { + # return necessary parameters for parsing next IFD + $$dirInfo{DirEnd} = $dirEnd; + $$dirInfo{OffsetName} = $nextOffName; + $$dirInfo{BytesFromEnd} = $bytesFromEnd; + } + return $success; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Exif - Read EXIF/TIFF meta information + +=head1 SYNOPSIS + +This module is required by Image::ExifTool. + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool for processing +EXIF and TIFF meta information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.exif.org/Exif2-2.PDF> + +=item L<http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf> + +=item L<http://partners.adobe.com/asn/developer/pdfs/tn/TIFF6.pdf> + +=item L<http://partners.adobe.com/public/developer/en/tiff/TIFFPM6.pdf> + +=item L<http://www.adobe.com/products/dng/pdfs/dng_spec.pdf> + +=item L<http://www.awaresystems.be/imaging/tiff/tifftags.html> + +=item L<http://www.remotesensing.org/libtiff/TIFFTechNote2.html> + +=item L<http://www.exif.org/dcf.PDF> + +=item L<http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html> + +=item L<http://www.fine-view.com/jp/lab/doc/ps6ffspecsv2.pdf> + +=item L<http://www.ozhiker.com/electronics/pjmt/jpeg_info/meta.html> + +=item L<http://hul.harvard.edu/jhove/tiff-tags.html> + +=item L<http://www.microsoft.com/whdc/xps/wmphoto.mspx> + +=item L<http://www.asmail.be/msg0054681802.html> + +=item L<http://crousseau.free.fr/imgfmt_raw.htm> + +=item L<http://www.cybercom.net/~dcoffin/dcraw/> + +=item L<http://www.digitalpreservation.gov/formats/content/tiff_tags.shtml> + +=item L<http://community.roxen.com/developers/idocs/rfc/rfc3949.html> + +=item L<http://tools.ietf.org/html/draft-ietf-fax-tiff-fx-extension1-01> + +=item L<http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/cinemadng/pdfs/CinemaDNG_Format_Specification_v1_1.pdf> + +=item L<http://geotiff.maptools.org/spec/geotiffhome.html> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jeremy Brown for the 35efl tags, and Matt Madrid for his help with +the XP character code conversions. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/EXIF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/FITS.pm b/ExifTool/lib/Image/ExifTool/FITS.pm new file mode 100644 index 0000000..62791e9 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/FITS.pm @@ -0,0 +1,159 @@ +#------------------------------------------------------------------------------ +# File: FITS.pm +# +# Description: Read Flexible Image Transport System metadata +# +# Revisions: 2018/03/07 - P. Harvey Created +# +# References: 1) https://fits.gsfc.nasa.gov/fits_standard.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::FITS; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.02'; + +# FITS tags (ref 1) +%Image::ExifTool::FITS::Main = ( + GROUPS => { 2 => 'Image' }, + NOTES => q{ + This table lists some standard Flexible Image Transport System (FITS) tags, + but ExifTool will extract any other tags found. See + L<https://fits.gsfc.nasa.gov/fits_standard.html> for the specification. + }, + TELESCOP => 'Telescope', + BACKGRND => 'Background', + INSTRUME => 'Instrument', + OBJECT => 'Object', + OBSERVER => 'Observer', + DATE => { Name => 'CreateDate', Groups => { 2 => 'Time' } }, + AUTHOR => { Name => 'Author', Groups => { 2 => 'Author' } }, + REFERENC => 'Reference', + 'DATE-OBS'=> { Name => 'ObservationDate', Groups => { 2 => 'Time' } }, + 'TIME-OBS'=> { Name => 'ObservationTime', Groups => { 2 => 'Time' } }, + 'DATE-END'=> { Name => 'ObservationDateEnd', Groups => { 2 => 'Time' } }, + 'TIME-END'=> { Name => 'ObservationTimeEnd', Groups => { 2 => 'Time' } }, + COMMENT => { Name => 'Comment', PrintConv => '$val =~ s/^ +//; $val', + Notes => 'leading spaces are removed if L<PrintConv|../ExifTool.html#PrintConv> is enabled' }, + HISTORY => { Name => 'History', PrintConv => '$val =~ s/^ +//; $val', + Notes => 'leading spaces are removed if L<PrintConv|../ExifTool.html#PrintConv> is enabled' }, +); + +#------------------------------------------------------------------------------ +# Read information in a FITS document +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid FITS file +sub ProcessFITS($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $tag, $continue); + + return 0 unless $raf->Read($buff, 80) == 80 and $buff =~ /^SIMPLE = {20}T/; + $et->SetFileType(); + my $tagTablePtr = GetTagTable('Image::ExifTool::FITS::Main'); + + for (;;) { + $raf->Read($buff, 80) == 80 or $et->Warn('Truncated FITS header'), last; + my $key = substr($buff, 0, 8); + $key =~ s/ +$//; # remove trailing space from key + if ($key eq 'CONTINUE') { + defined $continue or $et->WarnOnce('Unexpected FITS CONTINUE keyword'), next; + } else { + if (defined $continue) { + # the previous value wasn't continued, so store with the trailing '&' + $et->HandleTag($tagTablePtr, $tag, $continue . '&'); + undef $continue; + } + last if $key eq 'END'; + # make sure the key is valid + $key =~ /^[-_A-Z0-9]*$/ or $et->Warn('Format error in FITS header'), last; + if ($key eq 'COMMENT' or $key eq 'HISTORY') { + my $val = substr($buff, 8); # comments start in column 9 + $val =~ s/ +$//; # remove trailing spaces + $et->HandleTag($tagTablePtr, $key, $val); + next; + } + # ignore other lines that aren't tags + next unless substr($buff,8,2) eq '= '; + # save tag name (avoiding potential conflict with ExifTool variables) + $tag = $Image::ExifTool::specialTags{$key} ? "_$key" : $key; + # add to tag table if necessary + unless ($$tagTablePtr{$tag}) { + my $name = ucfirst lc $tag; # make tag name lower case with leading capital + $name =~ s/_(.)/\U$1/g; # remove all '_' and capitalize subsequent letter + AddTagToTable($tagTablePtr, $tag, { Name => $name }); + } + } + my $val = substr($buff, 10); + # parse quoted values + if ($val =~ /^'(.*?)'(.*)/) { + ($val, $buff) = ($1, $2); + while ($buff =~ /^('.*?)'(.*)/) { # handle escaped quotes + $val .= $1; + $buff = $2; + } + $val =~ s/ +$//; # remove trailing spaces + if (defined $continue) { + $val = $continue . $val; + undef $continue; + } + # check for possible continuation, removing trailing '&' + $val =~ s/\&$// and $continue = $val, next; + } elsif (defined $continue) { + $et->WarnOnce('Invalid FITS CONTINUE value'); + next; + } else { + $val =~ s/ *(\/.*)?$//; # remove trailing spaces and comment + next unless length $val; # ignore undefined values + $val =~ s/^ +//; # remove leading spaces + # re-format floating-point values to use 'e' + $val =~ tr/DE/e/ if $val =~ /^[+-]?(?=\d|\.\d)\d*(\.\d*)?([ED]([+-]?\d+))?$/; + } + $et->HandleTag($tagTablePtr, $tag, $val); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::FITS - Read Flexible Image Transport System metadata + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read meta +information from FITS (Flexible Image Transport System) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://fits.gsfc.nasa.gov/fits_standard.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/FITS Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/FLAC.pm b/ExifTool/lib/Image/ExifTool/FLAC.pm new file mode 100644 index 0000000..6caaebc --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/FLAC.pm @@ -0,0 +1,321 @@ +#------------------------------------------------------------------------------ +# File: FLAC.pm +# +# Description: Read Free Lossless Audio Codec information +# +# Revisions: 11/13/2006 - P. Harvey Created +# +# References: 1) http://flac.sourceforge.net/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::FLAC; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.09'; + +sub ProcessBitStream($$$); + +# FLAC metadata blocks +%Image::ExifTool::FLAC::Main = ( + NOTES => q{ + Free Lossless Audio Codec (FLAC) meta information. ExifTool also extracts + ID3 information from these files. + }, + 0 => { + Name => 'StreamInfo', + SubDirectory => { TagTable => 'Image::ExifTool::FLAC::StreamInfo' }, + }, + 1 => { Name => 'Padding', Binary => 1, Unknown => 1 }, + 2 => [{ # (see forum14064) + Name => 'Application_riff', + Condition => '$$valPt =~ /^riff(?!RIFF)/', # (all "riff" blocks but header) + SubDirectory => { + TagTable => 'Image::ExifTool::RIFF::Main', + ByteOrder => 'LittleEndian', + Start => 4, + }, + },{ + Name => 'ApplicationUnknown', + Binary => 1, + Unknown => 1, + }], + 3 => { Name => 'SeekTable', Binary => 1, Unknown => 1 }, + 4 => { + Name => 'VorbisComment', + SubDirectory => { TagTable => 'Image::ExifTool::Vorbis::Comments' }, + }, + 5 => { Name => 'CueSheet', Binary => 1, Unknown => 1 }, + 6 => { + Name => 'Picture', + SubDirectory => { TagTable => 'Image::ExifTool::FLAC::Picture' }, + }, + # 7-126 - Reserved + # 127 - Invalid +); + +%Image::ExifTool::FLAC::StreamInfo = ( + PROCESS_PROC => \&ProcessBitStream, + NOTES => 'FLAC is big-endian, so bit 0 is the high-order bit in this table.', + GROUPS => { 2 => 'Audio' }, + 'Bit000-015' => 'BlockSizeMin', + 'Bit016-031' => 'BlockSizeMax', + 'Bit032-055' => 'FrameSizeMin', + 'Bit056-079' => 'FrameSizeMax', + 'Bit080-099' => 'SampleRate', + 'Bit100-102' => { + Name => 'Channels', + ValueConv => '$val + 1', + }, + 'Bit103-107' => { + Name => 'BitsPerSample', + ValueConv => '$val + 1', + }, + 'Bit108-143' => 'TotalSamples', + 'Bit144-271' => { #Tim Eliseo + Name => 'MD5Signature', + Format => 'undef', + ValueConv => 'unpack("H*",$val)', + }, +); + +%Image::ExifTool::FLAC::Picture = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int32u', + 0 => { + Name => 'PictureType', + PrintConv => { # (Note: Duplicated in ID3, ASF and FLAC modules!) + 0 => 'Other', + 1 => '32x32 PNG Icon', + 2 => 'Other Icon', + 3 => 'Front Cover', + 4 => 'Back Cover', + 5 => 'Leaflet', + 6 => 'Media', + 7 => 'Lead Artist', + 8 => 'Artist', + 9 => 'Conductor', + 10 => 'Band', + 11 => 'Composer', + 12 => 'Lyricist', + 13 => 'Recording Studio or Location', + 14 => 'Recording Session', + 15 => 'Performance', + 16 => 'Capture from Movie or Video', + 17 => 'Bright(ly) Colored Fish', + 18 => 'Illustration', + 19 => 'Band Logo', + 20 => 'Publisher Logo', + }, + }, + 1 => { + Name => 'PictureMIMEType', + Format => 'var_pstr32', + }, + 2 => { + Name => 'PictureDescription', + Format => 'var_pstr32', + ValueConv => '$self->Decode($val, "UTF8")', + }, + 3 => 'PictureWidth', + 4 => 'PictureHeight', + 5 => 'PictureBitsPerPixel', + 6 => 'PictureIndexedColors', + 7 => 'PictureLength', + 8 => { + Name => 'Picture', + Groups => { 2 => 'Preview' }, + Format => 'undef[$val{7}]', + Binary => 1, + }, +); + +# FLAC composite tags +%Image::ExifTool::FLAC::Composite = ( + Duration => { + Require => { + 0 => 'FLAC:SampleRate', + 1 => 'FLAC:TotalSamples', + }, + ValueConv => '($val[0] and $val[1]) ? $val[1] / $val[0] : undef', + PrintConv => 'ConvertDuration($val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::FLAC'); + + +#------------------------------------------------------------------------------ +# Process information in a bit stream +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Notes: Byte order is used to determine the ordering of bits in the stream: +# 'MM' = bit 0 is most significant, 'II' = bit 0 is least significant +# - can handle arbitrarily wide values (eg. 8-byte or larger integers) +sub ProcessBitStream($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart); + my $verbose = $et->Options('Verbose'); + my $byteOrder = GetByteOrder(); + my $tag; + + if ($verbose) { + $et->VPrint(0, " + [BitStream directory, $dirLen bytes, '${byteOrder}' order]\n"); + } + foreach $tag (sort keys %$tagTablePtr) { + next unless $tag =~ /^Bit(\d+)-?(\d+)?/; + my ($b1, $b2) = ($1, $2 || $1); # start/end bit numbers in stream + my ($i1, $i2) = (int($b1 / 8), int($b2 / 8)); # start/end byte numbers + my ($f1, $f2) = ($b1 % 8, $b2 % 8); # start/end bit numbers within each byte + last if $i2 >= $dirLen; + my ($val, $extra); + # if Format is unspecified, convert the specified number of bits to an unsigned integer, + # otherwise allow HandleTag to convert whole bytes the normal way (via undefined $val) + if (ref $$tagTablePtr{$tag} ne 'HASH' or not $$tagTablePtr{$tag}{Format}) { + my ($i, $mask); + $val = 0; + $extra = ', Mask=0x' if $verbose and ($f1 != 0 or $f2 != 7); + if ($byteOrder eq 'MM') { + # loop from high byte to low byte + for ($i=$i1; $i<=$i2; ++$i) { + $mask = 0xff; + if ($i == $i1 and $f1) { + # mask off high bits in first word (0 is high bit) + foreach ((8-$f1) .. 7) { $mask ^= (1 << $_) } + } + if ($i == $i2 and $f2 < 7) { + # mask off low bits in last word (7 is low bit) + foreach (0 .. (6-$f2)) { $mask ^= (1 << $_) } + } + $val = $val * 256 + ($mask & Get8u($dataPt, $i + $dirStart)); + $extra .= sprintf('%.2x', $mask) if $extra; + } + } else { + # (FLAC is big-endian, but support little-endian bit streams + # so this routine can be used by other modules) + # loop from high byte to low byte + for ($i=$i2; $i>=$i1; --$i) { + $mask = 0xff; + if ($i == $i1 and $f1) { + # mask off low bits in first word (0 is low bit) + foreach (0 .. ($f1-1)) { $mask ^= (1 << $_) } + } + if ($i == $i2 and $f2 < 7) { + # mask off high bits in last word (7 is high bit) + foreach (($f2+1) .. 7) { $mask ^= (1 << $_) } + } + $val = $val * 256 + ($mask & Get8u($dataPt, $i + $dirStart)); + $extra .= sprintf('%.2x', $mask) if $extra; + } + } + # shift word down until low bit is in position 0 + until ($mask & 0x01) { + $val /= 2; + $mask >>= 1; + } + } + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $dirStart + $i1, + Size => $i2 - $i1 + 1, + Extra => $extra, + ); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from an Ogg FLAC file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid Ogg FLAC file +sub ProcessFLAC($$) +{ + my ($et, $dirInfo) = @_; + + # must first check for leading/trailing ID3 information + unless ($$et{DoneID3}) { + require Image::ExifTool::ID3; + Image::ExifTool::ID3::ProcessID3($et, $dirInfo) and return 1; + } + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my ($buff, $err); + + # check FLAC signature + $raf->Read($buff, 4) == 4 and $buff eq 'fLaC' or return 0; + $et->SetFileType(); + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::FLAC::Main'); + for (;;) { + # read next metadata block header + $raf->Read($buff, 4) == 4 or last; + my $flag = unpack('C', $buff); + my $size = unpack('N', $buff) & 0x00ffffff; + $raf->Read($buff, $size) == $size or $err = 1, last; + my $last = $flag & 0x80; # last-metadata-block flag + my $tag = $flag & 0x7f; # tag bits + if ($verbose) { + print $out "FLAC metadata block, type $tag:\n"; + $et->VerboseDump(\$buff, DataPos => $raf->Tell() - $size); + } + $et->HandleTag($tagTablePtr, $tag, $buff, + DataPt => \$buff, + DataPos => $raf->Tell() - $size, + Start => 0, + Size => $size, + ); + last if $last; # all done if is set + } + $err and $et->Warn('Format error in FLAC file'); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::FLAC - Read Free Lossless Audio Codec information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Free Lossless Audio Codec (FLAC) audio files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://flac.sourceforge.net/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/FLAC Tags>, +L<Image::ExifTool::TagNames/Ogg Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/FLIF.pm b/ExifTool/lib/Image/ExifTool/FLIF.pm new file mode 100644 index 0000000..5778e21 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/FLIF.pm @@ -0,0 +1,353 @@ +#------------------------------------------------------------------------------ +# File: FLIF.pm +# +# Description: Read/write FLIF meta information +# +# Revisions: 2016/10/11 - P. Harvey Created +# 2016/10/14 - PH Added write support +# +# References: 1) http://flif.info/ +# 2) https://github.com/FLIF-hub/FLIF/blob/master/doc/metadata +#------------------------------------------------------------------------------ + +package Image::ExifTool::FLIF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.02'; + +my %flifMap = ( + EXIF => 'FLIF', + XMP => 'FLIF', + ICC_Profile => 'FLIF', + IFD0 => 'EXIF', + IFD1 => 'IFD0', + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', +); + +# FLIF tags +%Image::ExifTool::FLIF::Main = ( + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + VARS => { HEX_ID => 0 }, + NOTES => q{ + Information extracted from Free Lossless Image Format files. See + L<http://flif.info/> for more information. + }, +# +# header information +# + 0 => { + Name => 'ImageType', + PrintConv => { + '1' => 'Grayscale (non-interlaced)', + '3' => 'RGB (non-interlaced)', + '4' => 'RGBA (non-interlaced)', + 'A' => 'Grayscale (interlaced)', + 'C' => 'RGB (interlaced)', + 'D' => 'RGBA (interlaced)', + 'Q' => 'Grayscale Animation (non-interlaced)', + 'S' => 'RGB Animation (non-interlaced)', + 'T' => 'RGBA Animation (non-interlaced)', + 'a' => 'Grayscale Animation (interlaced)', + 'c' => 'RGB Animation (interlaced)', + 'd' => 'RGBA Animation (interlaced)', + }, + }, + 1 => { + Name => 'BitDepth', + PrintConv => { + '0' => 'Custom', + '1' => 8, + '2' => 16, + }, + }, + 2 => 'ImageWidth', + 3 => 'ImageHeight', + 4 => 'AnimationFrames', + 5 => { + Name => 'Encoding', + PrintConv => { + 0 => 'FLIF16', + }, + }, +# +# metadata chunks +# + iCCP => { + Name => 'ICC_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + }, + eXif => { + Name => 'EXIF', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + Start => 6, # (skip "Exif\0\0" header) + Header => "Exif\0\0", + }, + }, + eXmp => { + Name => 'XMP', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + }, + }, + # tRko - list of truncation offsets + # \0 - FLIF16-format image data +); + +#------------------------------------------------------------------------------ +# Read variable-length FLIF integer +# Inputs: 0) raf reference, 1) number to add to returned value +# Returns: integer, or undef on EOF +sub GetVarInt($;$) +{ + my ($raf, $add) = @_; + my ($val, $buff); + for ($val=0; ; $val<<=7) { + $raf->Read($buff, 1) or return undef; + my $byte = ord($buff); + $val |= ($byte & 0x7f); + last unless $byte & 0x80; + } + return $val + ($add || 0); +} + +#------------------------------------------------------------------------------ +# Construct variable-length FLIF integer +# Inputs: 0) integer +# Returns: FLIF variable-length integer byte stream +sub SetVarInt($) +{ + my $val = shift; + my $buff = ''; + my $high = 0; + for (;;) { + $buff = chr(($val & 0x7f) | $high) . $buff; + last unless $val >>= 7; + $high = 0x80; + } + return $buff; +} + +#------------------------------------------------------------------------------ +# Read FLIF header +# Inputs: 0) RAF ref +# Returns: Scalar context: binary header block +# List context: header values (4 or 5 elements: type,depth,width,height[,frames]) +# or undef if this isn't a valid FLIF file header +sub ReadFLIFHeader($) +{ + my $raf = shift; + my ($buff, @vals); + + # verify this is a valid FLIF file + return () unless $raf->Read($buff, 6) == 6 and $buff =~ /^FLIF([0-\x6f])([0-2])/; + + # decode header information ("FLIF" chunk) + push @vals, $1, $2; # type, depth + push @vals, GetVarInt($raf,+1), GetVarInt($raf,+1); # width, height (+1 each) + push @vals, GetVarInt($raf,+2) if $vals[0] gt 'H'; # frames (+2) + + return () unless defined $vals[-1]; + return @vals if wantarray; # return the decoded header values + + # return the binary header block + my $hdrLen = $raf->Tell(); + return () unless $raf->Seek(0,0) and $raf->Read($buff, $hdrLen) == $hdrLen; + return $buff; +} + +#------------------------------------------------------------------------------ +# WriteFLIF : Write FLIF image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid FLIF file, or -1 if +# an output file was specified and a write error occurred +sub WriteFLIF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $soi, @addTags, %doneTag); + + # verify FLIF header and copy it to the output file + $buff = ReadFLIFHeader($raf) or return 0; + my $outfile = $$dirInfo{OutFile}; + Write($outfile, $buff) or return -1; + + $et->InitWriteDirs(\%flifMap); + my $tagTablePtr = GetTagTable('Image::ExifTool::FLIF::Main'); + + # loop through the FLIF chunks + for (;;) { + my ($tag, $size, $inflated); + # read new tag (or soi) unless we already hit the soi (start of image) + if (not defined $soi) { + $raf->Read($buff, 4) == 4 or $et->Error('Unexpected EOF'), last; + if ($buff lt ' ') { + $soi = $buff; # we have hit the start of image (no more metadata) + # make list of new tags to add + foreach $tag ('eXif', 'eXmp', 'iCCP') { + push @addTags, $tag if $$et{ADD_DIRS}{$$tagTablePtr{$tag}{Name}} and not $doneTag{$tag}; + } + } + } + if (not defined $soi) { + $tag = $buff; + $size = GetVarInt($raf); # read the data size + } elsif (@addTags) { + $tag = shift @addTags; + ($buff, $size) = ('', 0); # create metadata from scratch + } else { + # finish copying file (no more metadata to add) + Write($outfile, $soi) or return -1; + Write($outfile, $buff) or return -1 while $raf->Read($buff, 65536); + last; # all done! + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo and $$tagInfo{SubDirectory} and $$et{EDIT_DIRS}{$$tagInfo{Name}}) { + $doneTag{$tag} = 1; # prevent adding this back again later + unless (defined $soi) { + $raf->Read($buff, $size) == $size or $et->Error("Truncated FLIF $tag chunk"), last; + } + # rewrite the compressed data + if (eval { require IO::Uncompress::RawInflate } and eval { require IO::Compress::RawDeflate } ) { + if (length $buff == 0) { + $inflated = $buff; # (creating from scratch, so no need to inflate) + } elsif (not IO::Uncompress::RawInflate::rawinflate(\$buff => \$inflated)) { + $et->Error("Error inflating FLIF $tag chunk"), last; + } + my $subdir = $$tagInfo{SubDirectory}; + my %subdirInfo = ( + DirName => $$tagInfo{Name}, + DataPt => \$inflated, + DirStart => length($inflated) ? $$subdir{Start} : undef, + ReadOnly => 1, # (used only by WriteXMP) + ); + my $subTable = GetTagTable($$subdir{TagTable}); + $inflated = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc}); + if (defined $inflated) { + next unless length $inflated; # (delete directory if length is zero) + $inflated = $$subdir{Header} . $inflated if $$subdir{Header}; # (add back header if necessary) + unless (IO::Compress::RawDeflate::rawdeflate(\$inflated => \$buff)) { + $et->Error("Error deflating FLIF $tag chunk"), last; + } + } + } else { + $et->WarnOnce('Install IO::Compress::RawDeflate to write FLIF metadata'); + } + Write($outfile, $tag, SetVarInt(length $buff), $buff) or return -1; + } elsif (not defined $soi) { + Write($outfile, $tag, SetVarInt($size)) or return -1; + Image::ExifTool::CopyBlock($raf, $outfile, $size) or return -1; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from an FLIF file +# Inputs: 0) ExifTool object reference, 1) DirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid FLIF file +sub ProcessFLIF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $tag, $inflated); + + # verify this is a valid FLIF file and read the header + my @vals = ReadFLIFHeader($raf) or return 0; + + $et->SetFileType(); + my $tagTablePtr = GetTagTable('Image::ExifTool::FLIF::Main'); + my $verbose = $et->Options('Verbose'); + + # save the header information + $et->VPrint(0, "FLIF header:\n") if $verbose; + for ($tag=0; defined $vals[$tag]; ++$tag) { + $et->HandleTag($tagTablePtr, $tag, $vals[$tag]); + } + + # loop through the FLIF chunks + for (;;) { + $raf->Read($tag, 4) == 4 or $et->Warn('Unexpected EOF'), last; + my $byte = ord substr($tag, 0, 1); + # all done if we arrived at the image chunk + $byte < 32 and $et->HandleTag($tagTablePtr, 5, $byte), last; + my $size = GetVarInt($raf); + $et->VPrint(0, "FLIF $tag ($size bytes):\n") if $verbose; + if ($$tagTablePtr{$tag}) { + $raf->Read($buff, $size) == $size or $et->Warn("Truncated FLIF $tag chunk"), last; + $et->VerboseDump(\$buff, Addr => $raf->Tell() - $size) if $verbose > 2; + # inflate the compressed data + if (eval { require IO::Uncompress::RawInflate }) { + if (IO::Uncompress::RawInflate::rawinflate(\$buff => \$inflated)) { + $et->HandleTag($tagTablePtr, $tag, $inflated, + DataPt => \$inflated, + Size => length $inflated, + Extra => ' inflated', + ); + } else { + $et->Warn("Error inflating FLIF $tag chunk"); + } + } else { + $et->WarnOnce('Install IO::Uncompress::RawInflate to decode FLIF metadata'); + } + } else { + $raf->Seek($size, 1) or $et->Warn('Seek error'), last; + } + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::FLIF - Read/write FLIF meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read and write +meta information in FLIF (Free Lossless Image Format) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://flif.info/> + +=item L<https://github.com/FLIF-hub/FLIF/blob/master/doc/metadata> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/FLIF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/FLIR.pm b/ExifTool/lib/Image/ExifTool/FLIR.pm new file mode 100644 index 0000000..c110150 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/FLIR.pm @@ -0,0 +1,1669 @@ +#------------------------------------------------------------------------------ +# File: FLIR.pm +# +# Description: Read FLIR meta information +# +# Revisions: 2013/03/28 - P. Harvey Created +# +# References: 1) https://exiftool.org/forum/index.php/topic,4898.0.html +# 2) http://www.nuage.ch/site/flir-i7-some-analysis/ +# 3) http://www.workswell.cz/manuals/flir/hardware/A3xx_and_A6xx_models/Streaming_format_ThermoVision.pdf +# 4) http://support.flir.com/DocDownload/Assets/62/English/1557488%24A.pdf +# 5) http://code.google.com/p/dvelib/source/browse/trunk/flirPublicFormat/fpfConverter/Fpfimg.h?spec=svn3&r=3 +# 6) https://exiftool.org/forum/index.php/topic,5538.0.html +# JD) Jens Duttke private communication +# +# Glossary: FLIR = Forward Looking Infra Red +#------------------------------------------------------------------------------ + +package Image::ExifTool::FLIR; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::GPS; + +$VERSION = '1.22'; + +sub ProcessFLIR($$;$); +sub ProcessFLIRText($$$); +sub ProcessMeasInfo($$$); +sub GetImageType($$$); + +my %temperatureInfo = ( + Writable => 'rational64u', + Format => 'rational64s', # (have seen negative values) +); + +# tag information for floating point Kelvin tag +my %floatKelvin = ( + Format => 'float', + ValueConv => '$val - 273.15', + PrintConv => 'sprintf("%.1f C",$val)', +); + +# commonly used tag information elements +my %float1f = ( Format => 'float', PrintConv => 'sprintf("%.1f",$val)' ); +my %float2f = ( Format => 'float', PrintConv => 'sprintf("%.2f",$val)' ); +my %float6f = ( Format => 'float', PrintConv => 'sprintf("%.6f",$val)' ); +my %float8g = ( Format => 'float', PrintConv => 'sprintf("%.8g",$val)' ); + +# FLIR makernotes tags (ref PH) +%Image::ExifTool::FLIR::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + WRITABLE => 1, + PRIORITY => 0, # (unreliable) + NOTES => q{ + Information extracted from the maker notes of JPEG images from thermal + imaging cameras by FLIR Systems Inc. + }, + 0x01 => { #2 + Name => 'ImageTemperatureMax', + %temperatureInfo, + Notes => q{ + these temperatures may be in Celsius, Kelvin or Fahrenheit, but there is no + way to tell which + }, + }, + 0x02 => { Name => 'ImageTemperatureMin', %temperatureInfo }, #2 + 0x03 => { #1 + Name => 'Emissivity', + Writable => 'rational64u', + PrintConv => 'sprintf("%.2f",$val)', + PrintConvInv => '$val', + }, + # 0x04 does not change with temperature units; often 238, 250 or 457 + 0x04 => { Name => 'UnknownTemperature', %temperatureInfo, Unknown => 1 }, + # 0x05,0x06 are unreliable. As written by FLIR tools, these are the + # CameraTemperatureRangeMax/Min, but the units vary depending on the + # options settings. But as written by some cameras, the values are different. + 0x05 => { Name => 'CameraTemperatureRangeMax', %temperatureInfo, Unknown => 1 }, + 0x06 => { Name => 'CameraTemperatureRangeMin', %temperatureInfo, Unknown => 1 }, + # 0x07 - string[33] (some sort of image ID?) + # 0x08 - string[33] + # 0x09 - undef (tool info) + # 0x0a - int32u: 1 + # 0x0f - rational64u: 0/1000 + # 0x10,0x11,0x12 - int32u: 0 + # 0x13 - rational64u: 0/1000 +); + +# FLIR FFF tag table (ref PH) +%Image::ExifTool::FLIR::FFF = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&ProcessFLIR, + VARS => { ALPHA_FIRST => 1 }, + NOTES => q{ + Information extracted from FLIR FFF images and the APP1 FLIR segment of JPEG + images. These tags may also be extracted from the first frame of an FLIR + SEQ file, or all frames if the ExtractEmbedded option is used. Setting + ExtractEmbedded to 2 also the raw thermal data from all frames. + }, + "_header" => { + Name => 'FFFHeader', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::Header' }, + }, + # 0 = free (ref 3) + 0x01 => { + Name => 'RawData', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::RawData' }, + }, + # 2 = GainMap (ref 3) + # 3 = OffsMap (ref 3) + # 4 = DeadMap (ref 3) + 0x05 => { #6 + Name => 'GainDeadData', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::GainDeadData' }, + }, + 0x06 => { #6 + Name => 'CoarseData', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::CoarseData' }, + }, + # 7 = ImageMap (ref 3) + 0x0e => { + Name => 'EmbeddedImage', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::EmbeddedImage' }, + }, + 0x20 => { + Name => 'CameraInfo', # (BasicData - ref 3) + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::CameraInfo' }, + }, + 0x21 => { #6 + Name => 'MeasurementInfo', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::MeasInfo' }, + }, + 0x22 => { + Name => 'PaletteInfo', # (ColorPal - ref 3) + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::PaletteInfo' }, + }, + 0x23 => { + Name => 'TextInfo', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::TextInfo' }, + }, + 0x24 => { + Name => 'EmbeddedAudioFile', + # (sometimes has an unknown 8-byte header) + RawConv => q{ + return \$val if $val =~ s/^.{0,16}?RIFF/RIFF/s; + $self->Warn('Unknown EmbeddedAudioFile format'); + return undef; + }, + }, + # 0x27: 01 00 08 00 10 00 00 00 + 0x28 => { + Name => 'PaintData', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::PaintData' }, + }, + 0x2a => { + Name => 'PiP', + SubDirectory => { + TagTable => 'Image::ExifTool::FLIR::PiP', + ByteOrder => 'LittleEndian', + }, + }, + 0x2b => { + Name => 'GPSInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::FLIR::GPSInfo', + ByteOrder => 'LittleEndian', + }, + }, + 0x2c => { + Name => 'MeterLink', + SubDirectory => { + TagTable => 'Image::ExifTool::FLIR::MeterLink' , + ByteOrder => 'LittleEndian' + }, + }, + 0x2e => { + Name => 'ParameterInfo', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::ParamInfo' }, + }, +); + +# FFF file header (ref PH) +%Image::ExifTool::FLIR::Header = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + NOTES => 'Tags extracted from the FLIR FFF/AFF header.', + 4 => { Name => 'CreatorSoftware', Format => 'string[16]' }, +); + +# FLIR raw data record (ref PH) +%Image::ExifTool::FLIR::RawData = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + NOTES => q{ + The thermal image data may be stored either as raw data, or in PNG format. + If stored as raw data, ExifTool adds a TIFF header to allow the data to be + viewed as a TIFF image. If stored in PNG format, the PNG image is extracted + as-is. Note that most FLIR cameras using the PNG format seem to write the + 16-bit raw image data in the wrong byte order. + }, + 0x00 => { + # use this tag only to determine the byte order of the raw data + # (the value should be 0x0002 if the byte order is correct) + # - always "II" when RawThermalImageType is "TIFF" + # - seen both "II" and "MM" when RawThermalImageType is "PNG" + Name => 'RawDataByteOrder', + Hidden => 1, + RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef', + }, + 0x01 => { + Name => 'RawThermalImageWidth', + RawConv => '$$self{RawThermalImageWidth} = $val', + }, + 0x02 => { + Name => 'RawThermalImageHeight', + RawConv => '$$self{RawThermalImageHeight} = $val', + }, + # 0x03-0x05: 0 + # 0x06: raw image width - 1 + # 0x07: 0 + # 0x08: raw image height - 1 + # 0x09: 0,15,16 + # 0x0a: 0,2,3,11,12,13,30 + # 0x0b: 0,2 + # 0x0c: 0 or a large number + # 0x0d: 0,3,4,6 + # 0x0e-0x0f: 0 + 16 => { + Name => 'RawThermalImageType', + Format => 'undef[$size-0x20]', + RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "RawThermalImage")', + }, + 16.1 => { + Name => 'RawThermalImage', + Groups => { 2 => 'Preview' }, + # make a copy in case we want to extract more of them with -ee2 + RawConv => 'my $copy = $$self{RawThermalImage}; \$copy', + }, +); + +# GainDeadMap record (ref 6) (see RawData above) +%Image::ExifTool::FLIR::GainDeadData = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + NOTES => 'Information found in FFF-format .GAN calibration image files.', + 0x00 => { + Name => 'GainDeadMapByteOrder', + Hidden => 1, + RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef', + }, + 0x01 => { + Name => 'GainDeadMapImageWidth', + RawConv => '$$self{GainDeadMapImageWidth} = $val', + }, + 0x02 => { + Name => 'GainDeadMapImageHeight', + RawConv => '$$self{GainDeadMapImageHeight} = $val', + }, + 16 => { + Name => 'GainDeadMapImageType', + Format => 'undef[$size-0x20]', + RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "GainDeadMapImage")', + }, + 16.1 => { + Name => 'GainDeadMapImage', + RawConv => 'my $copy = \$$self{GainDeadMapImage}; \$copy', + }, +); + +# CoarseMap record (ref 6) (see RawData above) +%Image::ExifTool::FLIR::CoarseData = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + NOTES => 'Information found in FFF-format .CRS correction image files.', + 0x00 => { + Name => 'CoarseMapByteOrder', + Hidden => 1, + RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef', + }, + 0x01 => { + Name => 'CoarseMapImageWidth', + RawConv => '$$self{CoarseMapImageWidth} = $val', + }, + 0x02 => { + Name => 'CoarseMapImageHeight', + RawConv => '$$self{CoarseMapImageHeight} = $val', + }, + 16 => { + Name => 'CoarseMapImageType', + Format => 'undef[$size-0x20]', + RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "CoarseMapImage")', + }, + 16.1 => { + Name => 'CoarseMapImage', + RawConv => 'my $copy = \$$self{CoarseMapImage}; \$copy', + }, +); + +# "Paint colors" record (ref PH) +%Image::ExifTool::FLIR::PaintData = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + NOTES => 'Information generated by FLIR Tools "Paint colors" tool.', + 0x01 => { + Name => 'PaintByteOrder', + Hidden => 1, + RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef', + }, + 0x05 => { + Name => 'PaintImageWidth', + RawConv => '$$self{PaintImageWidth} = $val', + }, + 0x06 => { + Name => 'PaintImageHeight', + RawConv => '$$self{PaintImageHeight} = $val', + }, + 20 => { + Name => 'PaintImageType', + Format => 'undef[$size-0x28]', + RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "PaintImage")', + }, + 20.1 => { + Name => 'PaintImage', + RawConv => 'my $copy = \$$self{PaintImage}; \$copy', + }, +); + +# FLIR embedded image (ref 1) +%Image::ExifTool::FLIR::EmbeddedImage = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + 0 => { + # use this tag only to determine the byte order + # (the value should be 0x0003 if the byte order is correct) + Name => 'EmbeddedImageByteOrder', + Format => 'int16u', + Hidden => 1, + RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef', + }, + 1 => 'EmbeddedImageWidth', + 2 => 'EmbeddedImageHeight', + 16 => { + Name => 'EmbeddedImageType', + Format => 'undef[4]', + RawConv => '$val =~ /^\x89PNG/s ? "PNG" : ($val =~ /^\xff\xd8\xff/ ? "JPG" : "DAT")', + Notes => q{ + "PNG" for PNG image in Y Cb Cr colors, "JPG" for a JPEG image, or "DAT" for + other image data + }, + }, + 16.1 => { + Name => 'EmbeddedImage', + Groups => { 2 => 'Preview' }, + Format => 'undef[$size-0x20]', + Binary => 1, + }, +); + +# FLIR camera record (ref PH) +%Image::ExifTool::FLIR::CameraInfo = ( + GROUPS => { 0 => 'APP1', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + NOTES => q{ + FLIR camera information. The Planck tags are variables used in the + temperature calculation. See + L<https://exiftool.org/forum/index.php?topic=4898.msg23972#msg23972> + for details. + }, + 0x00 => { + # use this tag only to determine the byte order + # (the value should be 0x0002 if the byte order is correct) + Name => 'CameraInfoByteOrder', + Format => 'int16u', + Hidden => 1, + RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef', + }, + # 0x02 - int16u: image width + # 0x04 - int16u: image height + # 0x0c - int32u: image width - 1 + # 0x10 - int32u: image height - 1 + 0x20 => { Name => 'Emissivity', %float2f }, + 0x24 => { Name => 'ObjectDistance', Format => 'float', PrintConv => 'sprintf("%.2f m",$val)' }, + 0x28 => { Name => 'ReflectedApparentTemperature', %floatKelvin }, + 0x2c => { Name => 'AtmosphericTemperature', %floatKelvin }, + 0x30 => { Name => 'IRWindowTemperature', %floatKelvin }, + 0x34 => { Name => 'IRWindowTransmission', %float2f }, + # 0x38: 0 + 0x3c => { + Name => 'RelativeHumidity', + Format => 'float', + ValueConv => '$val > 2 ? $val / 100 : $val', # have seen value expressed as percent in FFF file + PrintConv => 'sprintf("%.1f %%",$val*100)', + }, + # 0x40 - float: 0,6 + # 0x44,0x48,0x4c: 0 + # 0x50 - int32u: 1 + # 0x54: 0 + 0x58 => { Name => 'PlanckR1', %float8g }, #1 + 0x5c => { Name => 'PlanckB', %float8g }, #1 + 0x60 => { Name => 'PlanckF', %float8g }, #1 + # 0x64,0x68,0x6c: 0 + 0x070 => { Name => 'AtmosphericTransAlpha1', %float6f }, #1 (value: 0.006569) + 0x074 => { Name => 'AtmosphericTransAlpha2', %float6f }, #1 (value: 0.012620) + 0x078 => { Name => 'AtmosphericTransBeta1', %float6f }, #1 (value: -0.002276) + 0x07c => { Name => 'AtmosphericTransBeta2', %float6f }, #1 (value: -0.006670) + 0x080 => { Name => 'AtmosphericTransX', %float6f }, #1 (value: 1.900000) + # 0x84,0x88: 0 + # 0x8c - float: 0,4,6 + 0x90 => { Name => 'CameraTemperatureRangeMax', %floatKelvin }, + 0x94 => { Name => 'CameraTemperatureRangeMin', %floatKelvin }, + 0x98 => { Name => 'CameraTemperatureMaxClip', %floatKelvin }, # 50 degrees over camera max + 0x9c => { Name => 'CameraTemperatureMinClip', %floatKelvin }, # usually 10 or 20 degrees below camera min + 0xa0 => { Name => 'CameraTemperatureMaxWarn', %floatKelvin }, # same as camera max + 0xa4 => { Name => 'CameraTemperatureMinWarn', %floatKelvin }, # same as camera min + 0xa8 => { Name => 'CameraTemperatureMaxSaturated', %floatKelvin }, # usually 50 or 88 degrees over camera max + 0xac => { Name => 'CameraTemperatureMinSaturated', %floatKelvin }, # usually 10, 20 or 40 degrees below camera min + 0xd4 => { Name => 'CameraModel', Format => 'string[32]' }, + 0xf4 => { Name => 'CameraPartNumber', Format => 'string[16]' }, #1 + 0x104 => { Name => 'CameraSerialNumber',Format => 'string[16]' }, #1 + 0x114 => { Name => 'CameraSoftware', Format => 'string[16]' }, #1/PH (NC) + 0x170 => { Name => 'LensModel', Format => 'string[32]' }, + # note: it seems that FLIR updated their lenses at some point, so lenses with the same + # name may have different part numbers (eg. the FOL38 is either 1196456 or T197089) + 0x190 => { Name => 'LensPartNumber', Format => 'string[16]' }, + 0x1a0 => { Name => 'LensSerialNumber', Format => 'string[16]' }, + 0x1b4 => { Name => 'FieldOfView', Format => 'float', PrintConv => 'sprintf("%.1f deg", $val)' }, #1 + # 0x1d0 - int16u: 0,12,24,25,46 + # 0x1d2 - int16u: 170,180,190,380,760,52320 + 0x1ec => { Name => 'FilterModel', Format => 'string[16]' }, + 0x1fc => { Name => 'FilterPartNumber', Format => 'string[32]' }, + 0x21c => { Name => 'FilterSerialNumber',Format => 'string[32]' }, + 0x308 => { Name => 'PlanckO', Format => 'int32s' }, #1 + 0x30c => { Name => 'PlanckR2', %float8g }, #1 + 0x310 => { Name => 'RawValueRangeMin', Format => 'int16u', Groups => { 2 => 'Image' } }, #forum10060 + 0x312 => { Name => 'RawValueRangeMax', Format => 'int16u', Groups => { 2 => 'Image' } }, #forum10060 + 0x338 => { Name => 'RawValueMedian', Format => 'int16u', Groups => { 2 => 'Image' } }, + 0x33c => { Name => 'RawValueRange', Format => 'int16u', Groups => { 2 => 'Image' } }, + 0x384 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Format => 'undef[10]', + Groups => { 2 => 'Time' }, + RawConv => q{ + my $tm = Get32u(\$val, 0); + my $ss = Get32u(\$val, 4) & 0xffff; + my $tz = Get16s(\$val, 8); + ConvertUnixTime($tm - $tz * 60) . sprintf('.%.3d', $ss) . TimeZoneString(-$tz); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x390 => { Name => 'FocusStepCount', Format => 'int16u' }, + 0x45c => { Name => 'FocusDistance', Format => 'float', PrintConv => 'sprintf("%.1f m",$val)' }, + # 0x43c - string: either "Live" or the file name + 0x464 => { Name => 'FrameRate', Format => 'int16u' }, #SebastianHani +); + +# FLIR measurement tools record (ref 6) +%Image::ExifTool::FLIR::MeasInfo = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&ProcessMeasInfo, + FORMAT => 'int16u', + VARS => { NO_ID => 1 }, + NOTES => q{ + Tags listed below are only for the first measurement tool, however multiple + measurements may be added, and information is extracted for all of them. + Tags for subsequent measurements are generated as required with the prefixes + "Meas2", "Meas3", etc. + }, + Meas1Type => { + PrintConv => { + 1 => 'Spot', + 2 => 'Area', + 3 => 'Ellipse', + 4 => 'Line', + 5 => 'Endpoint', #PH (NC, FLIR Tools v2.0 for Mac generates an empty one of these after each Line) + 6 => 'Alarm', #PH seen params: "0 1 0 1 9142 0 9142 0" (called "Isotherm" by Mac version) + 7 => 'Unused', #PH (NC) (or maybe "Free"?) + 8 => 'Difference', + }, + }, + Meas1Params => { + Notes => 'Spot=X,Y; Area=X1,Y1,W,H; Ellipse=XC,YC,X1,Y1,X2,Y2; Line=X1,Y1,X2,Y2', + }, + Meas1Label => { }, +); + +# FLIR palette record (ref PH/JD) +%Image::ExifTool::FLIR::PaletteInfo = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + 0x00 => { #JD + Name => 'PaletteColors', + RawConv => '$$self{PaletteColors} = $val', + }, + 0x06 => { Name => 'AboveColor', Format => 'int8u[3]', Notes => 'Y Cr Cb color components' }, #JD + 0x09 => { Name => 'BelowColor', Format => 'int8u[3]' }, #JD + 0x0c => { Name => 'OverflowColor', Format => 'int8u[3]' }, #JD + 0x0f => { Name => 'UnderflowColor', Format => 'int8u[3]' }, #JD + 0x12 => { Name => 'Isotherm1Color', Format => 'int8u[3]' }, #JD + 0x15 => { Name => 'Isotherm2Color', Format => 'int8u[3]' }, #JD + 0x1a => { Name => 'PaletteMethod' }, #JD + 0x1b => { Name => 'PaletteStretch' }, #JD + 0x30 => { + Name => 'PaletteFileName', + Format => 'string[32]', + # (not valid for all images) + RawConv => q{ + $val =~ s/\0.*//; + $val =~ /^[\x20-\x7e]{3,31}$/ ? $val : undef; + }, + }, + 0x50 => { + Name => 'PaletteName', + Format => 'string[32]', + # (not valid for all images) + RawConv => q{ + $val =~ s/\0.*//; + $val =~ /^[\x20-\x7e]{3,31}$/ ? $val : undef; + }, + }, + 0x70 => { + Name => 'Palette', + Format => 'undef[3*$$self{PaletteColors}]', + Notes => 'Y Cr Cb byte values for each palette color', + Binary => 1, + }, +); + +# FLIR text information record (ref PH) +%Image::ExifTool::FLIR::TextInfo = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&ProcessFLIRText, + VARS => { NO_ID => 1 }, + Label0 => { }, + Value0 => { }, + Label1 => { }, + Value1 => { }, + Label2 => { }, + Value2 => { }, + Label3 => { }, + Value3 => { }, + # (there could be more, and we will generate these on the fly if necessary) +); + +# FLIR parameter information record (ref PH) +%Image::ExifTool::FLIR::ParamInfo = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&ProcessFLIRText, + VARS => { NO_ID => 1 }, + Generated => { + Name => 'DateTimeGenerated', + Description => 'Date/Time Generated', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ tr/-/:/; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + Param0 => { }, + Param1 => { }, + Param2 => { }, + Param3 => { }, + # (there could be more, and we will generate these on the fly if necessary) +); + +# FLIR Picture in Picture record (ref 1) +%Image::ExifTool::FLIR::PiP = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + NOTES => 'FLIR Picture in Picture tags.', + FORMAT => 'int16s', + 0x00 => { + Name => 'Real2IR', + Format => 'float', + }, + 2 => { + Name => 'OffsetX', + Notes => 'offset from of insertion point from center', + PrintConv => 'sprintf("%+d",$val)', # (add sign for direct use with IM convert) + }, + 3 => { + Name => 'OffsetY', + PrintConv => 'sprintf("%+d",$val)', + }, + 4 => { + Name => 'PiPX1', + Description => 'PiP X1', + Notes => 'crop size for radiometric image', + }, + 5 => { Name => 'PiPX2', Description => 'PiP X2' }, + 6 => { Name => 'PiPY1', Description => 'PiP Y1' }, + 7 => { Name => 'PiPY2', Description => 'PiP Y2' }, +); + +# FLIR GPS record (ref PH/JD/forum9615) +%Image::ExifTool::FLIR::GPSInfo = ( + GROUPS => { 0 => 'APP1', 2 => 'Location' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + 0x00 => { + Name => 'GPSValid', + Format => 'int32u', + RawConv => '$$self{GPSValid} = $val', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x04 => { + Name => 'GPSVersionID', + Format => 'undef[4]', + RawConv => '$val eq "\0\0\0\0" ? undef : $val', + PrintConv => 'join ".", split //, $val', + }, + 0x08 => { + Name => 'GPSLatitudeRef', + Format => 'string[2]', + RawConv => 'length($val) ? $val : undef', + PrintConv => { + N => 'North', + S => 'South', + }, + }, + 0x0a => { + Name => 'GPSLongitudeRef', + Format => 'string[2]', + RawConv => 'length($val) ? $val : undef', + PrintConv => { + E => 'East', + W => 'West', + }, + }, + # 0x0c - 4 unknown bytes + 0x10 => { + Name => 'GPSLatitude', + Condition => '$$self{GPSValid}', # valid only if GPSValid is 1 + Format => 'double', # (signed) + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 0x18 => { + Name => 'GPSLongitude', + Condition => '$$self{GPSValid}', # valid only if GPSValid is 1 + Format => 'double', # (signed) + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 0x20 => { + Name => 'GPSAltitude', + Condition => '$$self{GPSValid}', # valid only if GPSValid is 1 + Format => 'float', + # (have seen likely invalid value of -1 when GPSValid is 1) + PrintConv => 'sprintf("%.2f m", $val)', + }, + # 0x24 - 28 unknown bytes: + # 0x28 - int8u: seen 0,49,51,55,57 (ASCII "1","3","7","9") + # 0x29 - int8u: seen 0,48 (ASCII "0") + 0x40 => { + Name => 'GPSDOP', + Description => 'GPS Dilution Of Precision', + Format => 'float', + RawConv => '$val > 0 ? $val : undef', # (have also seen likely invalid value of 1) + PrintConv => 'sprintf("%.2f", $val)', + }, + 0x44 => { + Name => 'GPSSpeedRef', + Format => 'string[2]', + RawConv => 'length($val) ? $val : undef', + PrintConv => { + K => 'km/h', + M => 'mph', + N => 'knots', + }, + }, + 0x46 => { + Name => 'GPSTrackRef', + Format => 'string[2]', + RawConv => 'length($val) ? $val : undef', + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + 0x48 => { #PH (NC) + Name => 'GPSImgDirectionRef', + Format => 'string[2]', + RawConv => 'length($val) ? $val : undef', + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + 0x4c => { + Name => 'GPSSpeed', + %float2f, + RawConv => '$val < 0 ? undef : $val', + }, + 0x50 => { + Name => 'GPSTrack', + %float2f, + RawConv => '$val < 0 ? undef : $val', + }, + 0x54 => { + Name => 'GPSImgDirection', + %float2f, + RawConv => '$val < 0 ? undef : $val', + }, + 0x58 => { + Name => 'GPSMapDatum', + Format => 'string[16]', + RawConv => 'length($val) ? $val : undef', + }, + # 0xa4 - string[6]: seen 000208,081210,020409,000608,010408,020808,091011 + # 0x78 - double[2]: seen "-1 -1","0 0" + # 0x78 - float[2]: seen "-1 -1","0 0" + # 0xb2 - string[2]?: seen "5\0" +); + +# humidity meter information +# (ref https://exiftool.org/forum/index.php/topic,5325.0.html) +# The %Image::ExifTool::UserDefined hash defines new tags to be added to existing tables. +%Image::ExifTool::FLIR::MeterLink = ( + GROUPS => { 0 => 'APP1', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + NOTES => 'Tags containing Meterlink humidity meter information.', + 26 => { + Name => 'Reading1Units', + DataMember => 'Reading1Units', + RawConv => '$$self{Reading1Units} = $val', + PrintHex => 1, + PrintConv => { + 0x0d => 'C', + 0x1b => '%', + 0x1d => 'Relative', + 0x24 => 'g/kg', + }, + }, + 28 => { + Name => 'Reading1Description', + DataMember => 'Reading1Description', + RawConv => '$$self{Reading1Description} = $val', + PrintConv => { + 0 => 'Humidity', + 3 => 'Moisture', # Pinless Moisture Readings with INTernal sensor + 7 => 'Dew Point', + 8 => 'Air Temperature', + 9 => 'IR Temperature', + 11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint + }, + }, + 32 => { + Name => 'Reading1Device', + Format => 'string[16]', + }, + 96 => { + Name => 'Reading1Value', + Format => 'double', + # convert Kelvin -> Celsius and kg/kg -> g/kg + ValueConv => q{ + return $val - 273.15 if $$self{Reading1Units} == 0x0d and $$self{Reading1Description} != 11; + return $val *= 1000 if $$self{Reading1Units} == 0x24; + return $val; + }, + }, + # add 100 for subsequent readings + 126 => { + Name => 'Reading2Units', + DataMember => 'Reading2Units', + RawConv => '$$self{Reading2Units} = $val', + PrintHex => 1, + PrintConv => { + 0x0d => 'C', + 0x1b => '%', + 0x1d => 'rel', + 0x24 => 'g/kg', + }, + }, + 128 => { + Name => 'Reading2Description', + DataMember => 'Reading2Description', + RawConv => '$$self{Reading2Description} = $val', + PrintConv => { + 0 => 'Humidity', + 3 => 'Moisture', + 7 => 'Dew Point', + 8 => 'Air Temperature', + 9 => 'IR Temperature', + 11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint + }, + }, + 132 => { + Name => 'Reading2Device', + Format => 'string[16]', + }, + 196 => { + Name => 'Reading2Value', + Format => 'double', + # convert Kelvin -> Celsius and kg/kg -> g/kg + ValueConv => q{ + return $val - 273.15 if $$self{Reading2Units} == 0x0d and $$self{Reading2Description} != 11; + return $val *= 1000 if $$self{Reading2Units} == 0x24; + return $val; + }, + }, + 226 => { + Name => 'Reading3Units', + DataMember => 'Reading3Units', + RawConv => '$$self{Reading3Units} = $val', + PrintHex => 1, + PrintConv => { + 0x0d => 'C', + 0x1b => '%', + 0x1d => 'rel', + 0x24 => 'g/kg', + }, + }, + 228 => { + Name => 'Reading3Description', + DataMember => 'Reading3Description', + RawConv => '$$self{Reading3Description} = $val', + PrintConv => { + 0 => 'Humidity', + 3 => 'Moisture', + 7 => 'Dew Point', + 8 => 'Air Temperature', + 9 => 'IR Temperature', + 11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint + }, + }, + 232 => { + Name => 'Reading3Device', + Format => 'string[16]', + }, + 296 => { + Name => 'Reading3Value', + Format => 'double', + # convert Kelvin -> Celsius and kg/kg -> g/kg + ValueConv => q{ + return $val - 273.15 if $$self{Reading3Units} == 0x0d and $$self{Reading3Description} != 11; + return $val *= 1000 if $$self{Reading3Units} == 0x24; + return $val; + }, + }, + + 326 => { + Name => 'Reading4Units', + DataMember => 'Reading4Units', + RawConv => '$$self{Reading4Units} = $val', + PrintHex => 1, + PrintConv => { + 0x0d => 'C', + 0x1b => '%', + 0x1d => 'rel', + 0x24 => 'g/kg', + }, + }, + 328 => { + Name => 'Reading4Description', + DataMember => 'Reading4Description', + RawConv => '$$self{Reading4Description} = $val', + PrintConv => { + 0 => 'Humidity', + 3 => 'Moisture', + 7 => 'Dew Point', + 8 => 'Air Temperature', + 9 => 'IR Temperature', + 11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint + }, + }, + 332 => { + Name => 'Reading4Device', + Format => 'string[16]', + }, + 396 => { + Name => 'Reading4Value', + Format => 'double', + # convert Kelvin -> Celsius and kg/kg -> g/kg + ValueConv => q{ + return $val - 273.15 if $$self{Reading4Units} == 0x0d and $$self{Reading4Description} != 11; + return $val *= 1000 if $$self{Reading4Units} == 0x24; + return $val; + }, + }, +); + +# FLIR public image format (ref 4/5) +%Image::ExifTool::FLIR::FPF = ( + GROUPS => { 0 => 'FLIR', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + NOTES => 'Tags extracted from FLIR Public image Format (FPF) files.', + 0x20 => { Name => 'FPFVersion', Format => 'int32u' }, + 0x24 => { Name => 'ImageDataOffset', Format => 'int32u' }, + 0x28 => { + Name => 'ImageType', + Format => 'int16u', + PrintConv => { + 0 => 'Temperature', + 1 => 'Temperature Difference', + 2 => 'Object Signal', + 3 => 'Object Signal Difference', + }, + }, + 0x2a => { + Name => 'ImagePixelFormat', + Format => 'int16u', + PrintConv => { + 0 => '2-byte short integer', + 1 => '4-byte long integer', + 2 => '4-byte float', + 3 => '8-byte double', + }, + }, + 0x2c => { Name => 'ImageWidth', Format => 'int16u' }, + 0x2e => { Name => 'ImageHeight', Format => 'int16u' }, + 0x30 => { Name => 'ExternalTriggerCount',Format => 'int32u' }, + 0x34 => { Name => 'SequenceFrameNumber',Format => 'int32u' }, + 0x78 => { Name => 'CameraModel', Format => 'string[32]', Groups => { 2 => 'Camera' } }, + 0x98 => { Name => 'CameraPartNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } }, + 0xb8 => { Name => 'CameraSerialNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } }, + 0xd8 => { Name => 'CameraTemperatureRangeMin', %floatKelvin, Groups => { 2 => 'Camera' } }, + 0xdc => { Name => 'CameraTemperatureRangeMax', %floatKelvin, Groups => { 2 => 'Camera' } }, + 0xe0 => { Name => 'LensModel', Format => 'string[32]', Groups => { 2 => 'Camera' } }, + 0x100 => { Name => 'LensPartNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } }, + 0x120 => { Name => 'LensSerialNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } }, + 0x140 => { Name => 'FilterModel', Format => 'string[32]', Groups => { 2 => 'Camera' } }, + 0x150 => { Name => 'FilterPartNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } }, + 0x180 => { Name => 'FilterSerialNumber',Format => 'string[32]', Groups => { 2 => 'Camera' } }, + 0x1e0 => { Name => 'Emissivity', %float2f }, + 0x1e4 => { Name => 'ObjectDistance', Format => 'float', PrintConv => 'sprintf("%.2f m",$val)' }, + 0x1e8 => { Name => 'ReflectedApparentTemperature', %floatKelvin }, + 0x1ec => { Name => 'AtmosphericTemperature', %floatKelvin }, + 0x1f0 => { Name => 'RelativeHumidity', Format => 'float', PrintConv => 'sprintf("%.1f %%",$val*100)' }, + 0x1f4 => { Name => 'ComputedAtmosphericTrans', %float2f }, + 0x1f8 => { Name => 'EstimatedAtmosphericTrans',%float2f }, + 0x1fc => { Name => 'ReferenceTemperature', %floatKelvin }, + 0x200 => { Name => 'IRWindowTemperature', %floatKelvin, Groups => { 2 => 'Camera' } }, + 0x204 => { Name => 'IRWindowTransmission', %float2f, Groups => { 2 => 'Camera' } }, + 0x248 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Format => 'int32u[7]', + ValueConv => 'sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d.%.3d",split(" ",$val))', + PrintConv => '$self->ConvertDateTime($val)', + }, + # Notes (based on ref 4): + # 1) The above date/time structure is documented to be 32 bytes for FPFVersion 1, but in + # fact it is only 28. Maybe this is why the full header length of my FPFVersion 2 + # sample is 892 bytes instead of 896. If this was a documentation error, we are OK, + # but if the alignment was really different in version 1, then the temperatures below + # will be mis-aligned. I don't have any version 1 samples to check this. + # 2) The following temperatures may not always be in Kelvin + 0x2a4 => { Name => 'CameraScaleMin', %float1f }, + 0x2a8 => { Name => 'CameraScaleMax', %float1f }, + 0x2ac => { Name => 'CalculatedScaleMin',%float1f }, + 0x2b0 => { Name => 'CalculatedScaleMax',%float1f }, + 0x2b4 => { Name => 'ActualScaleMin', %float1f }, + 0x2b8 => { Name => 'ActualScaleMax', %float1f }, +); + +# top-level user data written by FLIR cameras in MP4 videos +%Image::ExifTool::FLIR::UserData = ( + GROUPS => { 1 => 'FLIR', 2 => 'Camera' }, + NOTES => q{ + Tags written by some FLIR cameras in a top-level (!) "udta" atom of MP4 + videos. + }, + uuid => [ + { + Name => 'FLIR_Parts', + Condition => '$$valPt=~/^\x43\xc3\x99\x3b\x0f\x94\x42\x4b\x82\x05\x6b\x66\x51\x3f\x48\x5d/s', + SubDirectory => { + TagTable => 'Image::ExifTool::FLIR::Parts', + Start => 16, + }, + }, + { + Name => 'FLIR_Serial', + Condition => '$$valPt=~/^\x57\xf5\xb9\x3e\x51\xe4\x48\xaf\xa0\xd9\xc3\xef\x1b\x37\xf7\x12/s', + SubDirectory => { + TagTable => 'Image::ExifTool::FLIR::SerialNums', + Start => 16, + }, + }, + { + Name => 'FLIR_Params', + Condition => '$$valPt=~/^\x41\xe5\xdc\xf9\xe8\x0a\x41\xce\xad\xfe\x7f\x0c\x58\x08\x2c\x19/s', + SubDirectory => { + TagTable => 'Image::ExifTool::FLIR::Params', + Start => 16, + }, + }, + { + Name => 'FLIR_UnknownUUID', + Condition => '$$valPt=~/^\x57\x45\x20\x50\x2c\xbb\x44\xad\xae\x54\x15\xe9\xb8\x39\xd9\x03/s', + SubDirectory => { + TagTable => 'Image::ExifTool::FLIR::UnknownUUID', + Start => 16, + }, + }, + { + Name => 'FLIR_GPS', + Condition => '$$valPt=~/^\x7f\x2e\x21\x00\x8b\x46\x49\x18\xaf\xb1\xde\x70\x9a\x74\xf6\xf5/s', + SubDirectory => { + TagTable => 'Image::ExifTool::FLIR::GPS_UUID', + Start => 16, + }, + }, + { + Name => 'FLIR_MoreInfo', + Condition => '$$valPt=~/^\x2b\x45\x2f\xdc\x74\x35\x40\x94\xba\xee\x22\xa6\xb2\x3a\x7c\xf8/s', + SubDirectory => { + TagTable => 'Image::ExifTool::FLIR::MoreInfo', + Start => 16, + }, + }, + { + Name => 'SoftwareComponents', + Condition => '$$valPt=~/^\x78\x3f\xc7\x83\x0c\x95\x4b\x00\x8c\xc7\xac\xf1\xec\xb4\xd3\x9a/s', + Unknown => 1, + ValueConv => 'join " ", unpack "x20N4xZ*", $val', + }, + { + Name => 'FLIR_Unknown', + Condition => '$$valPt=~/^\x52\xae\xda\x45\x17\x1e\x48\xb1\x92\x47\x93\xa4\x21\x4e\x43\xf5/s', + Unknown => 1, + ValueConv => 'unpack "x20C*", $val', + }, + { + Name => 'Units', + Condition => '$$valPt=~/^\xf8\xab\x72\x1e\x84\x73\x44\xa0\xb8\xc8\x1b\x04\x82\x6e\x07\x24/s', + List => 1, + RawConv => 'my @a = split "\0", substr($val, 20); \@a', + }, + { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Condition => '$$valPt=~/^\x91\xaf\x9b\x93\x45\x9b\x44\x56\x98\xd1\x5e\x76\xea\x01\x04\xac....\xff\xd8\xff/s', + RawConv => 'substr($val, 20)', + Binary => 1, + }, + ], +); + +# uuid 43c3993b0f94424b82056b66513f485d box of MP4 videos (ref PH) +%Image::ExifTool::FLIR::Parts = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'undef', + NOTES => q{ + Tags extracted from the "uuid" box with ID 43c3993b0f94424b82056b66513f485d + in FLIR MP4 videos. + }, + 4 => [ + { + Name => 'BAHPVer', + Condition => '$$valPt =~ /^bahpver\0/', + Format => 'undef[$size]', + RawConv => 'join " ", split "\0", substr($val, 8)', + }, + { + Name => 'BALPVer', + Condition => '$$valPt =~ /^balpver\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 8)', + }, + { + Name => 'Battery', + Condition => '$$valPt =~ /^battery\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 8)', + }, + { + Name => 'BAVPVer', + Condition => '$$valPt =~ /^bavpver\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 8)', + # (the first string corresponds with a lens part number) + }, + { + Name => 'CamCore', + Condition => '$$valPt =~ /^camcore\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 8)', + }, + { + Name => 'DetectorBoard', + Condition => '$$valPt =~ /^det_board\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 10)', + }, + { + Name => 'Detector', + Condition => '$$valPt =~ /^detector\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 9)', + }, + { + Name => 'GIDCVer', + Condition => '$$valPt =~ /^gidcver\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 8)', + }, + { + Name => 'GIDPVer', + Condition => '$$valPt =~ /^gidpver\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 8)', + }, + { + Name => 'GIPC_CPLD', + Condition => '$$valPt =~ /^gipccpld\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 9)', + }, + { + Name => 'GIPCVer', + Condition => '$$valPt =~ /^gipcver\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 8)', + }, + { + Name => 'GIXIVer', + Condition => '$$valPt =~ /^gixiver\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 8)', + }, + { + Name => 'MainBoard', + Condition => '$$valPt =~ /^mainboard\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 10)', + }, + { + Name => 'Optics', + Condition => '$$valPt =~ /^optics\0/', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", substr($val, 7)', + }, + { + Name => 'PartNumber', + Format => 'undef[$size]', + ValueConv => 'join " ", split "\0", $val', + }, + ], +); + +# uuid 57f5b93e51e448afa0d9c3ef1b37f712 box of MP4 videos (ref PH) +%Image::ExifTool::FLIR::SerialNums = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + NOTES => q{ + Tags extracted from the "uuid" box with ID 57f5b93e51e448afa0d9c3ef1b37f712 + in FLIR MP4 videos. + }, + # (not sure if these offsets are constant) + 0x0c => { Name => 'UnknownSerial1', Format => 'string[33]', Unknown => 1 }, + 0x2d => { Name => 'UnknownSerial2', Format => 'string[33]', Unknown => 1 }, + 0x4e => { Name => 'UnknownSerial3', Format => 'string[33]', Unknown => 1 }, + 0x6f => { Name => 'UnknownSerial4', Format => 'string[11]', Unknown => 1 }, + 0x7b => { Name => 'UnknownNumber', Format => 'string[3]', Unknown => 1 }, + 0x7e => { Name => 'CameraSerialNumber', Format => 'string[9]' }, +); + +# uuid 41e5dcf9e80a41ceadfe7f0c58082c19 box of MP4 videos (ref PH) +%Image::ExifTool::FLIR::Params = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'float', + FIRST_ENTRY => 0, + NOTES => q{ + Tags extracted from the "uuid" box with ID 41e5dcf9e80a41ceadfe7f0c58082c19 + in FLIR MP4 videos. + }, + 1 => { Name => 'ReflectedApparentTemperature', %floatKelvin }, + 2 => { Name => 'AtmosphericTemperature', %floatKelvin }, + 3 => { Name => 'Emissivity', %float2f }, + 4 => { Name => 'ObjectDistance', PrintConv => 'sprintf("%.2f m",$val)' }, + 5 => { Name => 'RelativeHumidity', PrintConv => 'sprintf("%.1f %%",$val*100)' }, + 6 => { Name => 'EstimatedAtmosphericTrans', %float2f }, + 7 => { Name => 'IRWindowTemperature', %floatKelvin }, + 8 => { Name => 'IRWindowTransmission', %float2f }, +); + +# uuid 574520502cbb44adae5415e9b839d903 box of MP4 videos (ref PH) +%Image::ExifTool::FLIR::UnknownUUID = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'float', + FIRST_ENTRY => 0, + NOTES => q{ + Tags extracted from the "uuid" box with ID 574520502cbb44adae5415e9b839d903 + in FLIR MP4 videos. + }, + # 1 - 1 + # 2 - 0 + # 3 - 0 +); + +# uuid 7f2e21008b464918afb1de709a74f6f5 box of MP4 videos (ref PH) +%Image::ExifTool::FLIR::GPS_UUID = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Location' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'float', + FIRST_ENTRY => 0, + NOTES => q{ + Tags extracted from the "uuid" box with ID 7f2e21008b464918afb1de709a74f6f5 + in FLIR MP4 videos. + }, + 1 => { + Name => 'GPSLatitude', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 2 => { + Name => 'GPSLongitude', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 3 => { + Name => 'GPSAltitude', + PrintConv => '$val=int($val*100+0.5)/100;"$val m"', + }, + # 4 - int32u: 0x0001bf74 + # 5 - int32u: 0 + # 6 - int32u: 1 +); + +# uuid 2b452fdc74354094baee22a6b23a7cf8 box of MP4 videos (ref PH) +%Image::ExifTool::FLIR::MoreInfo = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + NOTES => q{ + Tags extracted from the "uuid" box with ID 2b452fdc74354094baee22a6b23a7cf8 + in FLIR MP4 videos. + }, + 5 => { Name => 'LensModel', Format => 'string[6]' }, + 11 => { Name => 'UnknownTemperature1', %floatKelvin, Unknown => 1 }, # (-14.9 C) + 15 => { Name => 'UnknownTemperature2', %floatKelvin, Unknown => 1 }, # (60.0 C) +); + +# FLIR AFF tag table (ref PH) +%Image::ExifTool::FLIR::AFF = ( + GROUPS => { 0 => 'FLIR', 1 => 'FLIR', 2 => 'Image' }, + NOTES => 'Tags extracted from FLIR "AFF" SEQ images.', + VARS => { ALPHA_FIRST => 1 }, + "_header" => { + Name => 'AFFHeader', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::Header' }, + }, + 0x01 => { + Name => 'AFF1', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::AFF1' }, + }, + 0x05 => { + Name => 'AFF5', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::AFF5' }, + }, +); + +# AFF record type 1 (ref forum?topic=4898.msg27627) +%Image::ExifTool::FLIR::AFF1 = ( + GROUPS => { 0 => 'FLIR', 1 => 'FLIR', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + 0x00 => { + # use this tag only to determine the byte order of the raw data + # (the value should be 0x0002 if the byte order is correct) + Name => 'RawDataByteOrder', + Hidden => 1, + RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef', + }, + 0x01 => { Name => 'SensorWidth', Format => 'int16u' }, + 0x02 => { Name => 'SensorHeight', Format => 'int16u' }, +); + +# AFF record type 5 (ref forum?topic=4898.msg27628) +%Image::ExifTool::FLIR::AFF5 = ( + GROUPS => { 0 => 'FLIR', 1 => 'FLIR', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + 0x12 => { + # use this tag only to determine the byte order of the raw data + # (the value should be 0x0002 if the byte order is correct) + Name => 'RawDataByteOrder', + Hidden => 1, + RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef', + }, + 0x13 => { Name => 'SensorWidth', Format => 'int16u' }, + 0x14 => { Name => 'SensorHeight', Format => 'int16u' }, +); + +# FLIR composite tags (ref 1) +%Image::ExifTool::FLIR::Composite = ( + GROUPS => { 1 => 'FLIR', 2 => 'Camera' }, + PeakSpectralSensitivity => { + Require => 'FLIR:PlanckB', + ValueConv => '14387.6515/$val', + PrintConv => 'sprintf("%.1f um", $val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::FLIR'); + +#------------------------------------------------------------------------------ +# Get image type from raw image data +# Inputs: 0) ExifTool ref, 1) image data, 2) tag name +# Returns: image type (PNG, JPG, TIFF or undef) +# - image itself is stored in $$et{$tag} +sub GetImageType($$$) +{ + my ($et, $val, $tag) = @_; + my ($w, $h) = @$et{"${tag}Width","${tag}Height"}; + my $type = 'DAT'; + # add TIFF header only if this looks like 16-bit raw data + # (note: MakeTiffHeader currently works only for little-endian, + # and I haven't seen any big-endian samples, but check anwyay) + if ($val =~ /^\x89PNG\r\n\x1a\n/) { + $type = 'PNG'; + } elsif ($val =~ /^\xff\xd8\xff/) { # (haven't seen this, but just in case - PH) + $type = 'JPG'; + } elsif (length $val != $w * $h * 2) { + $et->Warn("Unrecognized FLIR $tag data format"); + } elsif (GetByteOrder() eq 'II') { + $val = Image::ExifTool::MakeTiffHeader($w,$h,1,16) . $val; + $type = 'TIFF'; + } else { + $et->Warn("Don't yet support big-endian TIFF $tag"); + } + # save image data + $$et{$tag} = $val; + return $type; +} + +#------------------------------------------------------------------------------ +# Unescape FLIR Unicode character +# Inputs: 0) escaped character code +# Returns: UTF8 character +sub UnescapeFLIR($) +{ + my $char = shift; + return $char unless length $char == 4; # escaped ASCII char (eg. '\\') + my $val = hex $char; + return chr($val) if $val < 0x80; # simple ASCII + return pack('C0U', $val) if $] >= 5.006001; + return Image::ExifTool::PackUTF8($val); +} + +#------------------------------------------------------------------------------ +# Process FLIR text info record (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessFLIRText($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen}; + + return 0 if $dirLen < 12; + + $et->VerboseDir('FLIR Text'); + + my $dat = substr($$dataPt, $dirStart+12, $dirLen-12); + $dat =~ s/\0.*//s; # truncate at null + + # the parameter text contains an additional header entry... + if ($tagTablePtr eq \%Image::ExifTool::FLIR::ParamInfo and + $dat =~ /# (Generated) at (.*?)[\n\r]/) + { + $et->HandleTag($tagTablePtr, $1, $2); + } + + for (;;) { + $dat =~ /.(\d+).(label|value|param) (unicode|text) "(.*)"/g or last; + my ($tag, $val) = (ucfirst($2) . $1, $4); + if ($3 eq 'unicode' and $val =~ /\\/) { + # convert escaped Unicode characters (backslash followed by 4 hex digits) + $val =~ s/\\([0-9a-fA-F]{4}|.)/UnescapeFLIR($1)/sge; + $et->Decode($val, 'UTF8'); + } + $$tagTablePtr{$tag} or AddTagToTable($tagTablePtr, $tag, { Name => $tag }); + $et->HandleTag($tagTablePtr, $tag, $val); + } + + return 1; +} + +#------------------------------------------------------------------------------ +# Process FLIR measurement tool record (ref 6) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# (code-driven decoding isn't pretty, but sometimes it is necessary) +sub ProcessMeasInfo($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dataPos = $$dirInfo{DataPos}; + my $dirEnd = $dirStart + $$dirInfo{DirLen}; + + my $pos = $dirStart + 12; + return 0 if $pos > $dirEnd; + ToggleByteOrder() if Get16u($dataPt, $dirStart) >= 0x100; + my ($i, $t, $p); + for ($i=1; ; ++$i) { + last if $pos + 2 > $dirEnd; + my $recLen = Get16u($dataPt, $pos); + last if $recLen < 0x28 or $pos + $recLen > $dirEnd; + my $pre = 'Meas' . $i; + $et->VerboseDir("MeasInfo $i", undef, $recLen); + $et->VerboseDump($dataPt, Len => $recLen, Start=>$pos, DataPos=>$dataPos); + my $coordLen = Get16u($dataPt, $pos+4); + # generate tag table entries for this tool if necessary + foreach $t ('Type', 'Params', 'Label') { + my $tag = $pre . $t; + last if $$tagTablePtr{$tag}; + my $tagInfo = { Name => $tag }; + $$tagInfo{PrintConv} = $$tagTablePtr{"Meas1$t"}{PrintConv}; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + # extract measurement tool type + $et->HandleTag($tagTablePtr, "${pre}Type", undef, + DataPt=>$dataPt, DataPos=>$dataPos, Start=>$pos+0x0a, Size=>2); + last if $pos + 0x24 + $coordLen > $dirEnd; + # extract measurement parameters + $et->HandleTag($tagTablePtr, "${pre}Params", undef, + DataPt=>$dataPt, DataPos=>$dataPos, Start=>$pos+0x24, Size=>$coordLen); + my @uni; + # extract label (sometimes-null-terminated Unicode) + for ($p=0x24+$coordLen; $p<$recLen-1; $p+=2) { + my $ch = Get16u($dataPt, $p+$pos); + # FLIR Tools v2.0 for Mac doesn't properly null-terminate these strings, + # so end the string at any funny character + last if $ch < 0x20 or $ch > 0x7f; + push @uni, $ch; + } + # convert to the ExifTool character set + require Image::ExifTool::Charset; + my $val = Image::ExifTool::Charset::Recompose($et, \@uni); + $et->HandleTag($tagTablePtr, "${pre}Label", $val, + DataPt=>$dataPt, DataPos=>$dataPos, Start=>$pos+0x24+$coordLen, Size=>2*scalar(@uni)); + $pos += $recLen; # step to next record + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process FLIR FFF record (ref PH/1/3) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 if this was a valid FFF record +sub ProcessFLIR($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF} || new File::RandomAccess($$dirInfo{DataPt}); + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my $base = $raf->Tell(); + my ($i, $hdr, $buff, $rec); + + # read and verify FFF header + $raf->Read($hdr, 0x40) == 0x40 and $hdr =~ /^([AF]FF)\0/ or return 0; + + my $type = $1; + + # set file type if reading from FFF or SEQ file ($tagTablePtr will not be defined) + $et->SetFileType($type eq 'FFF' ? 'FLIR' : 'SEQ') unless $tagTablePtr; + + # FLIR file header (ref 3) + # 0x00 - string[4] file format ID = "FFF\0" + # 0x04 - string[16] file creator: seen "\0","MTX IR\0","CAMCTRL\0" + # 0x14 - int32u file format version = 100 + # 0x18 - int32u offset to record directory + # 0x1c - int32u number of entries in record directory + # 0x20 - int32u next free index ID = 2 + # 0x24 - int16u swap pattern = 0 (?) + # 0x28 - int16u[7] spares + # 0x34 - int32u[2] reserved + # 0x3c - int32u checksum + + # determine byte ordering by validating version number + # (in my samples FLIR APP1 is big-endian, FFF files are little-endian) + for ($i=0; ; ++$i) { + my $ver = Get32u(\$hdr, 0x14); + last if $ver >= 100 and $ver < 200; # (have seen 100 and 101 - PH) + ToggleByteOrder(); + next unless $i; + return 0 if $$et{DOC_NUM}; + $et->Warn("Unsupported FLIR $type version"); + return 1; + } + + # read the FLIR record directory + my $pos = Get32u(\$hdr, 0x18); + my $num = Get32u(\$hdr, 0x1c); + unless ($raf->Seek($base+$pos) and $raf->Read($buff, $num * 0x20) == $num * 0x20) { + $et->Warn('Truncated FLIR FFF directory'); + return $$et{DOC_NUM} ? 0 : 1; + } + + unless ($tagTablePtr) { + $tagTablePtr = GetTagTable("Image::ExifTool::FLIR::$type"); + $$et{SET_GROUP0} = 'FLIR'; # (set group 0 to 'FLIR' for FFF files) + } + + # process the header data + $et->HandleTag($tagTablePtr, '_header', $hdr); + + my $success = 1; + my $oldIndent = $$et{INDENT}; + $$et{INDENT} .= '| '; + $et->VerboseDir($type, $num); + + for ($i=0; $i<$num; ++$i) { + + # FLIR record entry (ref 3): + # 0x00 - int16u record type + # 0x02 - int16u record subtype: RawData 1=BE, 2=LE, 3=PNG; 1 for other record types + # 0x04 - int32u record version: seen 0x64,0x66,0x67,0x68,0x6f,0x104 + # 0x08 - int32u index id = 1 + # 0x0c - int32u record offset from start of FLIR data + # 0x10 - int32u record length + # 0x14 - int32u parent = 0 (?) + # 0x18 - int32u object number = 0 (?) + # 0x1c - int32u checksum: 0 for no checksum + + my $entry = $i * 0x20; + my $recType = Get16u(\$buff, $entry); + if ($recType == 0) { + $verbose and print $out "$$et{INDENT}$i) FLIR Record 0x00 (empty)\n"; + next; + } + my $recPos = Get32u(\$buff, $entry + 0x0c); + my $recLen = Get32u(\$buff, $entry + 0x10); + + $verbose and printf $out "%s%d) FLIR Record 0x%.2x, offset 0x%.4x, length 0x%.4x\n", + $$et{INDENT}, $i, $recType, $recPos, $recLen; + + # skip RawData records for embedded documents + if ($recType == 1 and $$et{DOC_NUM} and $et->Options('ExtractEmbedded') < 2) { + $raf->Seek($base+$recPos+$recLen) or $success = 0, last; + next; + } + unless ($raf->Seek($base+$recPos) and $raf->Read($rec, $recLen) == $recLen) { + if ($$et{DOC_NUM}) { + $success = 0; # abort processing more documents + } else { + $et->Warn('Invalid FLIR record'); + } + last; + } + if ($$tagTablePtr{$recType}) { + $et->HandleTag($tagTablePtr, $recType, undef, + Base => $base, + DataPt => \$rec, + DataPos => $recPos, + Start => 0, + Size => $recLen, + ); + } elsif ($verbose > 2) { + $et->VerboseDump(\$rec, Len => $recLen, DataPos => $recPos); + } + } + delete $$et{SET_GROUP0}; + $$et{INDENT} = $oldIndent; + + # extract information from subsequent frames in SEQ file if ExtractEmbedded is used + if ($$dirInfo{RAF} and $et->Options('ExtractEmbedded') and not $$et{DOC_NUM}) { + for (;;) { + $$et{DOC_NUM} = $$et{DOC_COUNT} + 1; + last unless ProcessFLIR($et, $dirInfo, $tagTablePtr); + # (DOC_COUNT will be incremented automatically if we extracted any tags) + } + delete $$et{DOC_NUM}; + } + return $success; +} + +#------------------------------------------------------------------------------ +# Process FLIR public image format (FPF) file (ref PH/4) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 if this was a valid FFF file +sub ProcessFPF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + + $raf->Read($buff, 892) == 892 and $buff =~ /^FPF Public Image Format\0/ or return 0; + + # I think these are always little-endian, but check FPFVersion just in case + SetByteOrder('II'); + ToggleByteOrder() unless Get32u(\$buff, 0x20) & 0xffff; + + my $tagTablePtr = GetTagTable('Image::ExifTool::FLIR::FPF'); + $et->SetFileType(); + $et->ProcessDirectory( { DataPt => \$buff, Parent => 'FPF' }, $tagTablePtr); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::FLIR - Read FLIR meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains the definitions to read meta information from FLIR +Systems Inc. thermal image files (FFF, FPF and JPEG format). + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://exiftool.org/forum/index.php/topic,4898.0.html> + +=item L<http://www.nuage.ch/site/flir-i7-some-analysis/> + +=item L<http://www.workswell.cz/manuals/flir/hardware/A3xx_and_A6xx_models/Streaming_format_ThermoVision.pdf> + +=item L<http://support.flir.com/DocDownload/Assets/62/English/1557488%24A.pdf> + +=item L<http://code.google.com/p/dvelib/source/browse/trunk/flirPublicFormat/fpfConverter/Fpfimg.h?spec=svn3&r=3> + +=item L<https://exiftool.org/forum/index.php/topic,5538.0.html> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Tomas for his hard work in decoding much of this information, and +to Jens Duttke for getting me started on this format. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/FLIR Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Fixup.pm b/ExifTool/lib/Image/ExifTool/Fixup.pm new file mode 100644 index 0000000..6a4efce --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Fixup.pm @@ -0,0 +1,366 @@ +#------------------------------------------------------------------------------ +# File: Fixup.pm +# +# Description: Utility to handle pointer fixups +# +# Revisions: 01/19/2005 - P. Harvey Created +# 04/11/2005 - P. Harvey Allow fixups to be tagged with a marker, +# and add new marker-related routines +# 06/21/2006 - P. Harvey Patch to work with negative offsets +# 07/07/2006 - P. Harvey Added support for 16-bit pointers +# 02/19/2013 - P. Harvey Added IsEmpty() +# +# Data Members: +# +# Start - Position in data where a zero pointer points to. +# Shift - Amount to shift offsets (relative to Start). +# Fixups - List of Fixup object references to to shift relative to this Fixup. +# Pointers - Hash of references to fixup pointer arrays, keyed by ByteOrder +# string (with "2" added if pointer is 16-bit [default is 32-bit], +# plus "_$marker" suffix if tagged with a marker name). +# +# Procedure: +# +# 1. Create a Fixup object for each data block containing pointers +# 2. Call AddFixup with the offset of each pointer in the block +# - pointer is assumed int32u with the current byte order +# - may also be called with a fixup reference for contained blocks +# 3. Add the necessary pointer offset to $$fixup{Shift} +# 4. Add data size to $$fixup{Start} if data is added before the block +# - automatically also shifts pointers by this amount +# 5. Call ApplyFixup to apply the fixup to all pointers +# - resets Shift and Start to 0 after applying fixup +#------------------------------------------------------------------------------ + +package Image::ExifTool::Fixup; + +use strict; +use Image::ExifTool qw(GetByteOrder SetByteOrder Get32u Get32s Set32u + Get16u Get16s Set16u); +use vars qw($VERSION); + +$VERSION = '1.05'; + +sub AddFixup($$;$$); +sub ApplyFixup($$); +sub Dump($;$); + +#------------------------------------------------------------------------------ +# New - create new Fixup object +# Inputs: 0) reference to Fixup object or Fixup class name +sub new +{ + local $_; + my $that = shift; + my $class = ref($that) || $that || 'Image::ExifTool::Fixup'; + my $self = bless {}, $class; + + # initialize required members + $self->{Start} = 0; + $self->{Shift} = 0; + + return $self; +} + +#------------------------------------------------------------------------------ +# Clone this object +# Inputs: 0) reference to Fixup object or Fixup class name +# Returns: reference to new Fixup object +sub Clone($) +{ + my $self = shift; + my $clone = new Image::ExifTool::Fixup; + $clone->{Start} = $self->{Start}; + $clone->{Shift} = $self->{Shift}; + my $phash = $self->{Pointers}; + if ($phash) { + $clone->{Pointers} = { }; + my $byteOrder; + foreach $byteOrder (keys %$phash) { + my @pointers = @{$phash->{$byteOrder}}; + $clone->{Pointers}->{$byteOrder} = \@pointers; + } + } + if ($self->{Fixups}) { + $clone->{Fixups} = [ ]; + my $subFixup; + foreach $subFixup (@{$self->{Fixups}}) { + push @{$clone->{Fixups}}, $subFixup->Clone(); + } + } + return $clone; +} + +#------------------------------------------------------------------------------ +# Add fixup pointer or another fixup object below this one +# Inputs: 0) Fixup object reference +# 1) Scalar for pointer offset, or reference to Fixup object +# 2) Optional marker name for the pointer +# 3) Optional pointer format ('int16u' or 'int32u', defaults to 'int32u') +# Notes: Byte ordering must be set properly for the pointer being added (must keep +# track of the byte order of each offset since MakerNotes may have different byte order!) +sub AddFixup($$;$$) +{ + my ($self, $pointer, $marker, $format) = @_; + if (ref $pointer) { + $self->{Fixups} or $self->{Fixups} = [ ]; + push @{$self->{Fixups}}, $pointer; + } else { + my $byteOrder = GetByteOrder(); + if (defined $format) { + if ($format eq 'int16u') { + $byteOrder .= '2'; + } elsif ($format ne 'int32u') { + warn "Bad Fixup pointer format $format\n"; + } + } + $byteOrder .= "_$marker" if defined $marker; + my $phash = $self->{Pointers}; + $phash or $phash = $self->{Pointers} = { }; + $phash->{$byteOrder} or $phash->{$byteOrder} = [ ]; + push @{$phash->{$byteOrder}}, $pointer; + } +} + +#------------------------------------------------------------------------------ +# fix up pointer offsets +# Inputs: 0) Fixup object reference, 1) data reference +# Outputs: Collapses fixup hierarchy into linear lists of fixup pointers +sub ApplyFixup($$) +{ + my ($self, $dataPt) = @_; + + my $start = $self->{Start}; + my $shift = $self->{Shift} + $start; # make shift relative to start + my $phash = $self->{Pointers}; + + # fix up pointers in this fixup + if ($phash and ($start or $shift)) { + my $saveOrder = GetByteOrder(); # save original byte ordering + my ($byteOrder, $ptr); + foreach $byteOrder (keys %$phash) { + SetByteOrder(substr($byteOrder,0,2)); + # apply the fixup offset shift (must get as signed integer + # to avoid overflow in case it was negative before) + my ($get, $set) = ($byteOrder =~ /^(II2|MM2)/) ? + (\&Get16s, \&Set16u) : (\&Get32s, \&Set32u); + foreach $ptr (@{$phash->{$byteOrder}}) { + $ptr += $start; # update pointer to new start location + next unless $shift; + &$set(&$get($dataPt, $ptr) + $shift, $dataPt, $ptr); + } + } + SetByteOrder($saveOrder); # restore original byte ordering + } + # recurse into contained fixups + if ($self->{Fixups}) { + # create our pointer hash if it doesn't exist + $phash or $phash = $self->{Pointers} = { }; + # loop through all contained fixups + my $subFixup; + foreach $subFixup (@{$self->{Fixups}}) { + # adjust the subfixup start and shift + $subFixup->{Start} += $start; + $subFixup->{Shift} += $shift - $start; + # recursively apply contained fixups + ApplyFixup($subFixup, $dataPt); + my $shash = $subFixup->{Pointers} or next; + # add all pointers to our collapsed lists + my $byteOrder; + foreach $byteOrder (keys %$shash) { + $phash->{$byteOrder} or $phash->{$byteOrder} = [ ]; + push @{$phash->{$byteOrder}}, @{$shash->{$byteOrder}}; + delete $shash->{$byteOrder}; + } + delete $subFixup->{Pointers}; + } + delete $self->{Fixups}; # remove our contained fixups + } + # reset our Start/Shift for the collapsed fixup + $self->{Start} = $self->{Shift} = 0; +} + +#------------------------------------------------------------------------------ +# Is this Fixup empty? +# Inputs: 0) Fixup object ref +# Returns: True if there are no offsets to fix +sub IsEmpty($) +{ + my $self = shift; + my $phash = $self->{Pointers}; + if ($phash) { + my $key; + foreach $key (keys %$phash) { + next unless ref $$phash{$key} eq 'ARRAY'; + return 0 if @{$$phash{$key}}; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Does specified marker exist? +# Inputs: 0) Fixup object reference, 1) marker name +# Returns: True if fixup contains specified marker name +sub HasMarker($$) +{ + my ($self, $marker) = @_; + my $phash = $self->{Pointers}; + return 0 unless $phash; + return 1 if grep /_$marker$/, keys %$phash; + return 0 unless $self->{Fixups}; + my $subFixup; + foreach $subFixup (@{$self->{Fixups}}) { + return 1 if $subFixup->HasMarker($marker); + } + return 0; +} + +#------------------------------------------------------------------------------ +# Set all marker pointers to specified value +# Inputs: 0) Fixup object reference, 1) data reference +# 2) marker name, 3) pointer value, 4) offset to start of data +sub SetMarkerPointers($$$$;$) +{ + my ($self, $dataPt, $marker, $value, $startOffset) = @_; + my $start = $self->{Start} + ($startOffset || 0); + my $phash = $self->{Pointers}; + + if ($phash) { + my $saveOrder = GetByteOrder(); # save original byte ordering + my ($byteOrder, $ptr); + foreach $byteOrder (keys %$phash) { + next unless $byteOrder =~ /^(II|MM)(2?)_$marker$/; + SetByteOrder($1); + my $set = $2 ? \&Set16u : \&Set32u; + foreach $ptr (@{$phash->{$byteOrder}}) { + &$set($value, $dataPt, $ptr + $start); + } + } + SetByteOrder($saveOrder); # restore original byte ordering + } + if ($self->{Fixups}) { + my $subFixup; + foreach $subFixup (@{$self->{Fixups}}) { + $subFixup->SetMarkerPointers($dataPt, $marker, $value, $start); + } + } +} + +#------------------------------------------------------------------------------ +# Get pointer values for specified marker +# Inputs: 0) Fixup object reference, 1) data reference, +# 2) marker name, 3) offset to start of data +# Returns: List of marker pointers in list context, or first marker pointer otherwise +sub GetMarkerPointers($$$;$) +{ + my ($self, $dataPt, $marker, $startOffset) = @_; + my $start = $self->{Start} + ($startOffset || 0); + my $phash = $self->{Pointers}; + my @pointers; + + if ($phash) { + my $saveOrder = GetByteOrder(); + my ($byteOrder, $ptr); + foreach $byteOrder (grep /_$marker$/, keys %$phash) { + SetByteOrder(substr($byteOrder,0,2)); + my $get = ($byteOrder =~ /^(II2|MM2)/) ? \&Get16u : \&Get32u; + foreach $ptr (@{$phash->{$byteOrder}}) { + push @pointers, &$get($dataPt, $ptr + $start); + } + } + SetByteOrder($saveOrder); # restore original byte ordering + } + if ($self->{Fixups}) { + my $subFixup; + foreach $subFixup (@{$self->{Fixups}}) { + push @pointers, $subFixup->GetMarkerPointers($dataPt, $marker, $start); + } + } + return @pointers if wantarray; + return $pointers[0]; +} + +#------------------------------------------------------------------------------ +# Dump fixup to console for debugging +# Inputs: 0) Fixup object reference, 1) optional initial indent string +sub Dump($;$) +{ + my ($self, $indent) = @_; + $indent or $indent = ''; + printf "${indent}Fixup start=0x%x shift=0x%x\n", $self->{Start}, $self->{Shift}; + my $phash = $self->{Pointers}; + if ($phash) { + my $byteOrder; + foreach $byteOrder (sort keys %$phash) { + print "$indent $byteOrder: ", join(' ',@{$phash->{$byteOrder}}),"\n"; + } + } + if ($self->{Fixups}) { + my $subFixup; + foreach $subFixup (@{$self->{Fixups}}) { + Dump($subFixup, $indent . ' '); + } + } +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Fixup - Utility to handle pointer fixups + +=head1 SYNOPSIS + + use Image::ExifTool::Fixup; + + $fixup = new Image::ExifTool::Fixup; + + # add a new fixup to a pointer at the specified offset in data + $fixup->AddFixup($offset); + + # add a new Fixup object to the tree + $fixup->AddFixup($subFixup); + + $fixup->{Start} += $shift1; # shift pointer offsets and values + + $fixup->{Shift} += $shift2; # shift pointer values only + + # recursively apply fixups to the specified data + $fixup->ApplyFixups(\$data); + + $fixup->Dump(); # dump debugging information + + $fixup->IsEmpty(); # return true if no offsets to fix + +=head1 DESCRIPTION + +This module contains the code to keep track of pointers in memory and to +shift these pointers as required. It is used by ExifTool to maintain the +pointers in image file directories (IFD's). + +=head1 NOTES + +Keeps track of pointers with different byte ordering, and relies on +Image::ExifTool::GetByteOrder() to determine the current byte ordering +when adding new pointers to a fixup. + +Maintains a hierarchical list of fixups so that the whole hierarchy can +be shifted by a simple shift at the base. Hierarchy is collapsed to a +linear list when ApplyFixups() is called. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Flash.pm b/ExifTool/lib/Image/ExifTool/Flash.pm new file mode 100644 index 0000000..6358cbd --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Flash.pm @@ -0,0 +1,755 @@ +#------------------------------------------------------------------------------ +# File: Flash.pm +# +# Description: Read Shockwave Flash meta information +# +# Revisions: 05/16/2006 - P. Harvey Created +# 06/07/2007 - PH Added support for FLV (Flash Video) files +# 10/23/2008 - PH Added support for XMP in FLV and SWF +# +# References: 1) http://www.the-labs.com/MacromediaFlash/SWF-Spec/SWFfileformat.html +# 2) http://sswf.sourceforge.net/SWFalexref.html +# 3) http://osflash.org/flv/ +# 4) http://www.irisa.fr/texmex/people/dufouil/ffmpegdoxy/flv_8h.html +# 5) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf (Oct 2008) +# 6) http://www.adobe.com/devnet/swf/pdf/swf_file_format_spec_v9.pdf +# 7) http://help.adobe.com/en_US/FlashMediaServer/3.5_Deving/WS5b3ccc516d4fbf351e63e3d11a0773d56e-7ff6.html +# 8) http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf +# +# Notes: I'll add AMF3 support if someone sends me a FLV with AMF3 data +#------------------------------------------------------------------------------ + +package Image::ExifTool::Flash; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::FLAC; + +$VERSION = '1.12'; + +sub ProcessMeta($$$;$); + +# Meta packets that we process +my %processMetaPacket = ( onMetaData => 1, onXMPData => 1 ); + +# information extracted from SWF header +%Image::ExifTool::Flash::Main = ( + GROUPS => { 2 => 'Video' }, + VARS => { ALPHA_FIRST => 1 }, + NOTES => q{ + The information below is extracted from SWF (Shockwave Flash) files. Tags + with string ID's represent information extracted from the file header. + }, + FlashVersion => { }, + Compressed => { PrintConv => { 0 => 'False', 1 => 'True' } }, + ImageWidth => { }, + ImageHeight => { }, + FrameRate => { }, + FrameCount => { }, + Duration => { + Notes => 'calculated from FrameRate and FrameCount', + PrintConv => 'ConvertDuration($val)', + }, + 69 => { + Name => 'FlashAttributes', + PrintConv => { BITMASK => { + 0 => 'UseNetwork', + 3 => 'ActionScript3', + 4 => 'HasMetadata', + } }, + }, + 77 => { + Name => 'XMP', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, +); + +# packets in Flash Video files +%Image::ExifTool::Flash::FLV = ( + NOTES => q{ + Information is extracted from the following packets in FLV (Flash Video) + files. + }, + 0x08 => { + Name => 'Audio', + BitMask => 0x04, + SubDirectory => { TagTable => 'Image::ExifTool::Flash::Audio' }, + }, + 0x09 => { + Name => 'Video', + BitMask => 0x01, + SubDirectory => { TagTable => 'Image::ExifTool::Flash::Video' }, + }, + 0x12 => { + Name => 'Meta', + SubDirectory => { TagTable => 'Image::ExifTool::Flash::Meta' }, + }, +); + +# tags in Flash Video packet header +%Image::ExifTool::Flash::Audio = ( + PROCESS_PROC => \&Image::ExifTool::FLAC::ProcessBitStream, + GROUPS => { 2 => 'Audio' }, + NOTES => 'Information extracted from the Flash Audio header.', + 'Bit0-3' => { + Name => 'AudioEncoding', + PrintConv => { + 0 => 'PCM-BE (uncompressed)', # PCM-BE according to ref 4 + 1 => 'ADPCM', + 2 => 'MP3', + 3 => 'PCM-LE (uncompressed)', #4 + 4 => 'Nellymoser 16kHz Mono', #8 + 5 => 'Nellymoser 8kHz Mono', + 6 => 'Nellymoser', + 7 => 'G.711 A-law logarithmic PCM', #8 + 8 => 'G.711 mu-law logarithmic PCM', #8 + # (9 is reserved, ref 8) + 10 => 'AAC', #8 + 11 => 'Speex', #8 + 13 => 'MP3 8-Khz', #8 + 15 => 'Device-specific sound', #8 + }, + }, + 'Bit4-5' => { + Name => 'AudioSampleRate', + ValueConv => { + 0 => 5512, + 1 => 11025, + 2 => 22050, + 3 => 44100, + }, + }, + 'Bit6' => { + Name => 'AudioBitsPerSample', + ValueConv => '8 * ($val + 1)', + }, + 'Bit7' => { + Name => 'AudioChannels', + ValueConv => '$val + 1', + PrintConv => { + 1 => '1 (mono)', + 2 => '2 (stereo)', + }, + }, +); + +# tags in Flash Video packet header +%Image::ExifTool::Flash::Video = ( + PROCESS_PROC => \&Image::ExifTool::FLAC::ProcessBitStream, + GROUPS => { 2 => 'Video' }, + NOTES => 'Information extracted from the Flash Video header.', + 'Bit4-7' => { + Name => 'VideoEncoding', + PrintConv => { + 1 => 'JPEG', #8 + 2 => 'Sorensen H.263', + 3 => 'Screen Video', + 4 => 'On2 VP6', + 5 => 'On2 VP6 Alpha', #3 + 6 => 'Screen Video 2', #3 + 7 => 'H.264', #7 (called "AVC" by ref 8) + }, + }, +); + +# tags in Flash META packet (in ActionScript Message Format) +%Image::ExifTool::Flash::Meta = ( + PROCESS_PROC => \&ProcessMeta, + GROUPS => { 2 => 'Video' }, + NOTES => q{ + Below are a few observed FLV Meta tags, but ExifTool will attempt to extract + information from any tag found. + }, + 'audiocodecid' => { Name => 'AudioCodecID', Groups => { 2 => 'Audio' } }, + 'audiodatarate' => { + Name => 'AudioBitrate', + Groups => { 2 => 'Audio' }, + ValueConv => '$val * 1000', + PrintConv => 'ConvertBitrate($val)', + }, + 'audiodelay' => { Name => 'AudioDelay', Groups => { 2 => 'Audio' } }, + 'audiosamplerate'=>{ Name => 'AudioSampleRate', Groups => { 2 => 'Audio' } }, + 'audiosamplesize'=>{ Name => 'AudioSampleSize', Groups => { 2 => 'Audio' } }, + 'audiosize' => { Name => 'AudioSize', Groups => { 2 => 'Audio' } }, + 'bytelength' => 'ByteLength', # (youtube) + 'canseekontime' => 'CanSeekOnTime', # (youtube) + 'canSeekToEnd' => 'CanSeekToEnd', + 'creationdate' => { + # (not an AMF date type in my sample) + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + ValueConv => '$val=~s/\s+$//; $val', # trim trailing whitespace + }, + 'createdby' => 'CreatedBy', #7 + 'cuePoints' => { + Name => 'CuePoint', + SubDirectory => { TagTable => 'Image::ExifTool::Flash::CuePoint' }, + }, + 'datasize' => 'DataSize', + 'duration' => { + Name => 'Duration', + PrintConv => 'ConvertDuration($val)', + }, + 'filesize' => 'FileSizeBytes', + 'framerate' => { + Name => 'FrameRate', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + 'hasAudio' => { Name => 'HasAudio', Groups => { 2 => 'Audio' } }, + 'hasCuePoints' => 'HasCuePoints', + 'hasKeyframes' => 'HasKeyFrames', + 'hasMetadata' => 'HasMetadata', + 'hasVideo' => 'HasVideo', + 'height' => 'ImageHeight', + 'httphostheader'=> 'HTTPHostHeader', # (youtube) + 'keyframesTimes'=> 'KeyFramesTimes', + 'keyframesFilepositions' => 'KeyFramePositions', + 'lasttimestamp' => 'LastTimeStamp', + 'lastkeyframetimestamp' => 'LastKeyFrameTime', + 'metadatacreator'=>'MetadataCreator', + 'metadatadate' => { + Name => 'MetadataDate', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 'purl' => 'URL', # (youtube) (what does P mean?) + 'pmsg' => 'Message', # (youtube) (what does P mean?) + 'sourcedata' => 'SourceData', # (youtube) + 'starttime' => { # (youtube) + Name => 'StartTime', + PrintConv => 'ConvertDuration($val)', + }, + 'stereo' => { Name => 'Stereo', Groups => { 2 => 'Audio' } }, + 'totalduration' => { # (youtube) + Name => 'TotalDuration', + PrintConv => 'ConvertDuration($val)', + }, + 'totaldatarate' => { # (youtube) + Name => 'TotalDataRate', + ValueConv => '$val * 1000', + PrintConv => 'int($val + 0.5)', + }, + 'totalduration' => 'TotalDuration', + 'videocodecid' => 'VideoCodecID', + 'videodatarate' => { + Name => 'VideoBitrate', + ValueConv => '$val * 1000', + PrintConv => 'ConvertBitrate($val)', + }, + 'videosize' => 'VideoSize', + 'width' => 'ImageWidth', + # tags in 'onXMPData' packets + 'liveXML' => { #5 + Name => 'XMP', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, +); + +# tags in Flash META CuePoint structure +%Image::ExifTool::Flash::CuePoint = ( + PROCESS_PROC => \&ProcessMeta, + GROUPS => { 2 => 'Video' }, + NOTES => q{ + These tag names are added to the CuePoint name to generate complete tag + names like "CuePoint0Name". + }, + 'name' => 'Name', + 'type' => 'Type', + 'time' => 'Time', + 'parameters' => { + Name => 'Parameter', + SubDirectory => { TagTable => 'Image::ExifTool::Flash::Parameter' }, + }, +); + +# tags in Flash META CuePoint Parameter structure +%Image::ExifTool::Flash::Parameter = ( + PROCESS_PROC => \&ProcessMeta, + GROUPS => { 2 => 'Video' }, + NOTES => q{ + There are no pre-defined parameter tags, but ExifTool will extract any + existing parameters, with tag names like "CuePoint0ParameterXxx". + }, +); + +# name lookup for known AMF data types +my @amfType = qw(double boolean string object movieClip null undefined reference + mixedArray objectEnd array date longString unsupported recordSet + XML typedObject AMF3data); + +# test for AMF structure types (object, mixed array or typed object) +my %isStruct = ( 0x03 => 1, 0x08 => 1, 0x10 => 1 ); + +#------------------------------------------------------------------------------ +# Process Flash Video AMF Meta packet (ref 3) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# 3) Set to extract single type/value only +# Returns: 1 on success, (or type/value if extracting single value) +# Notes: Updates DataPos in dirInfo if extracting single value +sub ProcessMeta($$$;$) +{ + my ($et, $dirInfo, $tagTablePtr, $single) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $dirLen = $$dirInfo{DirLen} || length($$dataPt); + my $pos = $$dirInfo{Pos} || 0; + my ($type, $val, $rec); + + $et->VerboseDir('Meta') unless $single; + +Record: for ($rec=0; ; ++$rec) { + last if $pos >= $dirLen; + $type = ord(substr($$dataPt, $pos)); + ++$pos; + if ($type == 0x00 or $type == 0x0b) { # double or date + last if $pos + 8 > $dirLen; + $val = GetDouble($dataPt, $pos); + $pos += 8; + if ($type == 0x0b) { # date + $val /= 1000; # convert to seconds + my $frac = $val - int($val); # fractional seconds + # get time zone + last if $pos + 2 > $dirLen; + my $tz = Get16s($dataPt, $pos); + $pos += 2; + # construct date/time string + $val = Image::ExifTool::ConvertUnixTime(int($val)); + if ($frac) { + $frac = sprintf('%.6f', $frac); + $frac =~ s/(^0|0+$)//g; + $val .= $frac; + } + # add timezone + if ($tz < 0) { + $val .= '-'; + $tz *= -1; + } else { + $val .= '+'; + } + $val .= sprintf('%.2d:%.2d', int($tz/60), $tz%60); + } + } elsif ($type == 0x01) { # boolean + last if $pos + 1 > $dirLen; + $val = Get8u($dataPt, $pos); + $val = { 0 => 'No', 1 => 'Yes' }->{$val} if $val < 2; + ++$pos; + } elsif ($type == 0x02) { # string + last if $pos + 2 > $dirLen; + my $len = Get16u($dataPt, $pos); + last if $pos + 2 + $len > $dirLen; + $val = substr($$dataPt, $pos + 2, $len); + $pos += 2 + $len; + } elsif ($isStruct{$type}) { # object, mixed array or typed object + $et->VPrint(1, " + [$amfType[$type]]\n"); + my $getName; + $val = ''; # dummy value + if ($type == 0x08) { # mixed array + # skip last array index for mixed array + last if $pos + 4 > $dirLen; + $pos += 4; + } elsif ($type == 0x10) { # typed object + $getName = 1; + } + for (;;) { + # get tag ID (or typed object name) + last Record if $pos + 2 > $dirLen; + my $len = Get16u($dataPt, $pos); + if ($pos + 2 + $len > $dirLen) { + $et->Warn("Truncated $amfType[$type] record"); + last Record; + } + my $tag = substr($$dataPt, $pos + 2, $len); + $pos += 2 + $len; + # first string of a typed object is the object name + if ($getName) { + $et->VPrint(1," | (object name '${tag}')\n"); + undef $getName; + next; # (ignore name for now) + } + my $subTablePtr = $tagTablePtr; + my $tagInfo = $$subTablePtr{$tag}; + # switch to subdirectory table if necessary + if ($tagInfo and $$tagInfo{SubDirectory}) { + my $subTable = $tagInfo->{SubDirectory}->{TagTable}; + # descend into Flash SubDirectory + if ($subTable =~ /^Image::ExifTool::Flash::/) { + $tag = $$tagInfo{Name}; # use our name for the tag + $subTablePtr = GetTagTable($subTable); + } + } + # get object value + my $valPos = $pos + 1; + $$dirInfo{Pos} = $pos; + my $structName = $$dirInfo{StructName}; + # add structure name to start of tag name + $tag = $structName . ucfirst($tag) if defined $structName; + $$dirInfo{StructName} = $tag; # set new structure name + my ($t, $v) = ProcessMeta($et, $dirInfo, $subTablePtr, 1); + $$dirInfo{StructName} = $structName;# restore original structure name + $pos = $$dirInfo{Pos}; # update to new position in packet + # all done if this value contained tags + last Record unless defined $t and defined $v; + next if $isStruct{$t}; # already handled tags in sub-structures + next if ref($v) eq 'ARRAY' and not @$v; # ignore empty arrays + last if $t == 0x09; # (end of object) + if (not $$subTablePtr{$tag} and $tag =~ /^\w+$/) { + AddTagToTable($subTablePtr, $tag, { Name => ucfirst($tag) }); + $et->VPrint(1, " | (adding $tag)\n"); + } + $et->HandleTag($subTablePtr, $tag, $v, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $valPos, + Size => $pos - $valPos, + Format => $amfType[$t] || sprintf('0x%x',$t), + ); + } + # } elsif ($type == 0x04) { # movie clip (not supported) + } elsif ($type == 0x05 or $type == 0x06 or $type == 0x09 or $type == 0x0d) { + # null, undefined, dirLen of object, or unsupported + $val = ''; + } elsif ($type == 0x07) { # reference + last if $pos + 2 > $dirLen; + $val = Get16u($dataPt, $pos); + $pos += 2; + } elsif ($type == 0x0a) { # array + last if $pos + 4 > $dirLen; + my $num = Get32u($dataPt, $pos); + $$dirInfo{Pos} = $pos + 4; + my ($i, @vals); + # add array index to compound tag name + my $structName = $$dirInfo{StructName}; + for ($i=0; $i<$num; ++$i) { + $$dirInfo{StructName} = $structName . $i if defined $structName; + my ($t, $v) = ProcessMeta($et, $dirInfo, $tagTablePtr, 1); + last Record unless defined $v; + # save value unless contained in a sub-structure + push @vals, $v unless $isStruct{$t}; + } + $$dirInfo{StructName} = $structName; + $pos = $$dirInfo{Pos}; + $val = \@vals; + } elsif ($type == 0x0c or $type == 0x0f) { # long string or XML + last if $pos + 4 > $dirLen; + my $len = Get32u($dataPt, $pos); + last if $pos + 4 + $len > $dirLen; + $val = substr($$dataPt, $pos + 4, $len); + $pos += 4 + $len; + # } elsif ($type == 0x0e) { # record set (not supported) + # } elsif ($type == 0x11) { # AMF3 data (can't add support for this without a test sample) + } else { + my $t = $amfType[$type] || sprintf('type 0x%x',$type); + $et->Warn("AMF $t record not yet supported"); + undef $type; # (so we don't print another warning) + last; # can't continue + } + last if $single; # all done if extracting single value + unless ($isStruct{$type}) { + # only process certain Meta packets + if ($type == 0x02 and not $rec) { + my $verb = $processMetaPacket{$val} ? 'processing' : 'ignoring'; + $et->VPrint(0, " | ($verb $val information)\n"); + last unless $processMetaPacket{$val}; + } else { + # give verbose indication if we ignore a lone value + my $t = $amfType[$type] || sprintf('type 0x%x',$type); + $et->VPrint(1, " | (ignored lone $t value '${val}')\n"); + } + } + } + if (not defined $val and defined $type) { + $et->Warn(sprintf("Truncated AMF record 0x%x",$type)); + } + return 1 unless $single; # all done + $$dirInfo{Pos} = $pos; # update position + return($type,$val); # return single type/value pair +} + +#------------------------------------------------------------------------------ +# Read information frame a Flash Video file +# Inputs: 0) ExifTool object reference, 1) Directory information reference +# Returns: 1 on success, 0 if this wasn't a valid Flash Video file +sub ProcessFLV($$) +{ + my ($et, $dirInfo) = @_; + my $verbose = $et->Options('Verbose'); + my $raf = $$dirInfo{RAF}; + my $buff; + + $raf->Read($buff, 9) == 9 or return 0; + $buff =~ /^FLV\x01/ or return 0; + SetByteOrder('MM'); + $et->SetFileType(); + my ($flags, $offset) = unpack('x4CN', $buff); + $raf->Seek($offset-9, 1) or return 1 if $offset > 9; + $flags &= 0x05; # only look for audio/video + my $found = 0; + my $tagTablePtr = GetTagTable('Image::ExifTool::Flash::FLV'); + for (;;) { + $raf->Read($buff, 15) == 15 or last; + my $len = unpack('x4N', $buff); + my $type = $len >> 24; + $len &= 0x00ffffff; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $type); + if ($verbose > 1) { + my $name = $tagInfo ? $$tagInfo{Name} : "type $type"; + $et->VPrint(1, "FLV $name packet, len $len\n"); + } + undef $buff; + if ($tagInfo and $$tagInfo{SubDirectory}) { + my $mask = $$tagInfo{BitMask}; + if ($mask) { + # handle audio or video packet + unless ($found & $mask) { + $found |= $mask; + $flags &= ~$mask; + if ($len>=1 and $raf->Read($buff, 1) == 1) { + $len -= 1; + } else { + $et->Warn("Bad $$tagInfo{Name} packet"); + last; + } + } + } elsif ($raf->Read($buff, $len) == $len) { + $len = 0; + } else { + $et->Warn('Truncated Meta packet'); + last; + } + } + if (defined $buff) { + $et->HandleTag($tagTablePtr, $type, undef, + DataPt => \$buff, + DataPos => $raf->Tell() - length($buff), + ); + } + last unless $flags; + $raf->Seek($len, 1) or last if $len; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Found a Flash tag +# Inputs: 0) ExifTool object ref, 1) tag name, 2) tag value +sub FoundFlashTag($$$) +{ + my ($et, $tag, $val) = @_; + $et->HandleTag(\%Image::ExifTool::Flash::Main, $tag, $val); +} + +#------------------------------------------------------------------------------ +# Read data from possibly compressed file +# Inputs: 0) RAF reference, 1) data buffer, 2) bytes to read, 2) compressed flag +# Returns: number of bytes read (may be greater than requested bytes if compressed) +# - concatenates data to current buffer +# - updates compressed flag with reference to inflate object for future calls +# (or sets to error message and returns zero on error) +sub ReadCompressed($$$$) +{ + my ($raf, $len, $inflate) = ($_[0], $_[2], $_[3]); + my $buff; + unless ($raf->Read($buff, $len)) { + $_[3] = 'Error reading file'; + return 0; + } + # uncompress if necessary + if ($inflate) { + unless (ref $inflate) { + unless (eval { require Compress::Zlib }) { + $_[3] = 'Install Compress::Zlib to extract compressed information'; + return 0; + } + $inflate = Compress::Zlib::inflateInit(); + unless ($inflate) { + $_[3] = 'Error initializing inflate for Flash data'; + return 0; + } + $_[3] = $inflate; # pass inflate object back to caller + } + my $tmp = $buff; + $buff = ''; + # read 64 more bytes at a time and inflate until we get enough uncompressed data + for (;;) { + my ($dat, $stat) = $inflate->inflate($tmp); + if ($stat == Compress::Zlib::Z_STREAM_END() or + $stat == Compress::Zlib::Z_OK()) + { + $buff .= $dat; # add inflated data to buffer + last if length $buff >= $len or $stat == Compress::Zlib::Z_STREAM_END(); + $raf->Read($tmp,64) or last; # must read a bit more data + } else { + $buff = ''; + last; + } + } + $_[3] = 'Error inflating compressed Flash data' unless length $buff; + } + $_[1] = defined $_[1] ? $_[1] . $buff : $buff; + return length $buff; +} + +#------------------------------------------------------------------------------ +# Read information frame a Flash file +# Inputs: 0) ExifTool object reference, 1) Directory information reference +# Returns: 1 on success, 0 if this wasn't a valid Flash file +sub ProcessSWF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $hasMeta); + + $raf->Read($buff, 8) == 8 or return 0; + $buff =~ /^(F|C)WS([^\0])/ or return 0; + my ($compressed, $vers) = ($1 eq 'C' ? 1 : 0, ord($2)); + + SetByteOrder('II'); + $et->SetFileType(); + GetTagTable('Image::ExifTool::Flash::Main'); # make sure table is initialized + + FoundFlashTag($et, FlashVersion => $vers); + FoundFlashTag($et, Compressed => $compressed); + + # read the next 64 bytes of the file (and inflate if necessary) + $buff = ''; + unless (ReadCompressed($raf, $buff, 64, $compressed)) { + $et->Warn($compressed) if $compressed; + return 1; + } + + # unpack elements of bit-packed Flash Rect structure + my $nBits = unpack('C', $buff) >> 3; # bits in x1,x2,y1,y2 elements + my $totBits = 5 + $nBits * 4; # total bits in Rect structure + my $nBytes = int(($totBits + 7) / 8); # byte length of Rect structure + if (length $buff < $nBytes + 4) { + $et->Warn('Truncated Flash file'); + return 1; + } + my $bits = unpack("B$totBits", $buff); + # isolate Rect elements and convert from ASCII bit strings to integers + my @vals = unpack('x5' . "a$nBits" x 4, $bits); + # (do conversion the hard way because oct("0b$val") requires Perl 5.6) + map { $_ = unpack('N', pack('B32', '0' x (32 - length $_) . $_)) } @vals; + + # calculate and store ImageWidth/Height + FoundFlashTag($et, ImageWidth => ($vals[1] - $vals[0]) / 20); + FoundFlashTag($et, ImageHeight => ($vals[3] - $vals[2]) / 20); + + # get frame rate and count + @vals = unpack("x${nBytes}v2", $buff); + FoundFlashTag($et, FrameRate => $vals[0] / 256); + FoundFlashTag($et, FrameCount => $vals[1]); + FoundFlashTag($et, Duration => $vals[1] * 256 / $vals[0]) if $vals[0]; + + # scan through the tags to find FlashAttributes and XMP + $buff = substr($buff, $nBytes + 4); + for (;;) { + my $buffLen = length $buff; + last if $buffLen < 2; + my $code = Get16u(\$buff, 0); + my $pos = 2; + my $tag = $code >> 6; + my $size = $code & 0x3f; + $et->VPrint(1, "SWF tag $tag ($size bytes):\n"); + last unless $tag == 69 or $tag == 77 or $hasMeta; + # read enough to get a complete short record + if ($pos + $size > $buffLen) { + # (read 2 extra bytes if available to get next tag word) + unless (ReadCompressed($raf, $buff, $size + 2, $compressed)) { + $et->Warn($compressed) if $compressed; + return 1; + } + $buffLen = length $buff; + last if $pos + $size > $buffLen; + } + # read extended record if necessary + if ($size == 0x3f) { + last if $pos + 4 > $buffLen; + $size = Get32u(\$buff, $pos); + $pos += 4; + last if $size > 1000000; # don't read anything huge + if ($pos + $size > $buffLen) { + unless (ReadCompressed($raf, $buff, $size + 2, $compressed)) { + $et->Warn($compressed) if $compressed; + return 1; + } + $buffLen = length $buff; + last if $pos + $size > $buffLen; + } + $et->VPrint(1, " [extended size $size bytes]\n"); + } + if ($tag == 69) { # FlashAttributes + last unless $size; + my $flags = Get8u(\$buff, $pos); + FoundFlashTag($et, $tag => $flags); + last unless $flags & 0x10; # only continue if we have metadata (XMP) + $hasMeta = 1; + } elsif ($tag == 77) { # Metadata + my $val = substr($buff, $pos, $size); + FoundFlashTag($et, $tag => $val); + last; + } + last if $pos + 2 > $buffLen; + $buff = substr($buff, $pos); # remove everything before the next tag + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Flash - Read Shockwave Flash meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read SWF +(Shockwave Flash) and FLV (Flash Video) files. + +=head1 NOTES + +Flash Video AMF3 support has not yet been added because I haven't yet found +a FLV file containing AMF3 information. If someone sends me a sample then I +will add AMF3 support. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.the-labs.com/MacromediaFlash/SWF-Spec/SWFfileformat.html> + +=item L<http://sswf.sourceforge.net/SWFalexref.html> + +=item L<http://osflash.org/flv/> + +=item L<http://www.irisa.fr/texmex/people/dufouil/ffmpegdoxy/flv_8h.html> + +=item L<http://help.adobe.com/en_US/FlashMediaServer/3.5_Deving/WS5b3ccc516d4fbf351e63e3d11a0773d56e-7ff6.html> + +=item L<http://www.adobe.com/devnet/swf/pdf/swf_file_format_spec_v9.pdf> + +=item L<http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Flash Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/FlashPix.pm b/ExifTool/lib/Image/ExifTool/FlashPix.pm new file mode 100644 index 0000000..6ca1f95 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/FlashPix.pm @@ -0,0 +1,2537 @@ +#------------------------------------------------------------------------------ +# File: FlashPix.pm +# +# Description: Read FlashPix meta information +# +# Revisions: 05/29/2006 - P. Harvey Created +# +# References: 1) http://www.exif.org/Exif2-2.PDF +# 2) http://www.graphcomp.com/info/specs/livepicture/fpx.pdf +# 3) http://search.cpan.org/~jdb/libwin32/ +# 4) http://msdn.microsoft.com/en-us/library/aa380374.aspx +# 5) http://www.cpan.org/modules/by-authors/id/H/HC/HCARVEY/File-MSWord-0.1.zip +# 6) https://msdn.microsoft.com/en-us/library/cc313153(v=office.12).aspx +# 7) https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-oshared/3ef02e83-afef-4b6c-9585-c109edd24e07 +#------------------------------------------------------------------------------ + +package Image::ExifTool::FlashPix; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::ASF; # for GetGUID() + +$VERSION = '1.46'; + +sub ProcessFPX($$); +sub ProcessFPXR($$$); +sub ProcessProperties($$$); +sub ReadFPXValue($$$$$;$$); +sub ProcessHyperlinks($$); +sub ProcessContents($$$); +sub ProcessWordDocument($$$); +sub ProcessDocumentTable($); +sub ProcessCommentBy($$$); +sub ProcessLastSavedBy($$$); +sub SetDocNum($$;$$$); +sub ConvertDTTM($); + +# sector type constants +sub HDR_SIZE () { 512; } +sub DIF_SECT () { 0xfffffffc; } +sub FAT_SECT () { 0xfffffffd; } +sub END_OF_CHAIN () { 0xfffffffe; } +sub FREE_SECT () { 0xffffffff; } + +# format flags +sub VT_VECTOR () { 0x1000; } +sub VT_ARRAY () { 0x2000; } +sub VT_BYREF () { 0x4000; } +sub VT_RESERVED () { 0x8000; } + +# other constants +sub VT_VARIANT () { 12; } +sub VT_LPSTR () { 30; } + +# list of OLE format codes (unsupported codes commented out) +my %oleFormat = ( + 0 => undef, # VT_EMPTY + 1 => undef, # VT_NULL + 2 => 'int16s', # VT_I2 + 3 => 'int32s', # VT_I4 + 4 => 'float', # VT_R4 + 5 => 'double', # VT_R8 + 6 => undef, # VT_CY + 7 => 'VT_DATE', # VT_DATE (double, number of days since Dec 30, 1899) + 8 => 'VT_BSTR', # VT_BSTR (int32u count, followed by binary string) +# 9 => 'VT_DISPATCH', + 10 => 'int32s', # VT_ERROR + 11 => 'int16s', # VT_BOOL + 12 => 'VT_VARIANT', # VT_VARIANT +# 13 => 'VT_UNKNOWN', +# 14 => 'VT_DECIMAL', + 16 => 'int8s', # VT_I1 + 17 => 'int8u', # VT_UI1 + 18 => 'int16u', # VT_UI2 + 19 => 'int32u', # VT_UI4 + 20 => 'int64s', # VT_I8 + 21 => 'int64u', # VT_UI8 +# 22 => 'VT_INT', +# 23 => 'VT_UINT', +# 24 => 'VT_VOID', +# 25 => 'VT_HRESULT', +# 26 => 'VT_PTR', +# 27 => 'VT_SAFEARRAY', +# 28 => 'VT_CARRAY', +# 29 => 'VT_USERDEFINED', + 30 => 'VT_LPSTR', # VT_LPSTR (int32u count, followed by string) + 31 => 'VT_LPWSTR', # VT_LPWSTR (int32u word count, followed by Unicode string) + 64 => 'VT_FILETIME',# VT_FILETIME (int64u, 100 ns increments since Jan 1, 1601) + 65 => 'VT_BLOB', # VT_BLOB +# 66 => 'VT_STREAM', +# 67 => 'VT_STORAGE', +# 68 => 'VT_STREAMED_OBJECT', +# 69 => 'VT_STORED_OBJECT', +# 70 => 'VT_BLOB_OBJECT', + 71 => 'VT_CF', # VT_CF + 72 => 'VT_CLSID', # VT_CLSID +); + +# OLE flag codes (high nibble of property type) +my %oleFlags = ( + 0x1000 => 'VT_VECTOR', + 0x2000 => 'VT_ARRAY', # not yet supported + 0x4000 => 'VT_BYREF', # ditto + 0x8000 => 'VT_RESERVED', +); + +# byte sizes for supported VT_* format and flag types +my %oleFormatSize = ( + VT_DATE => 8, + VT_BSTR => 4, # (+ string length) + VT_VARIANT => 4, # (+ data length) + VT_LPSTR => 4, # (+ string length) + VT_LPWSTR => 4, # (+ string character length) + VT_FILETIME => 8, + VT_BLOB => 4, # (+ data length) + VT_CF => 4, # (+ data length) + VT_CLSID => 16, + VT_VECTOR => 4, # (+ vector elements) +); + +# names for each type of directory entry +my @dirEntryType = qw(INVALID STORAGE STREAM LOCKBYTES PROPERTY ROOT); + +# list of code pages used by Microsoft +# (ref http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx) +my %codePage = ( + 37 => 'IBM EBCDIC US-Canada', + 437 => 'DOS United States', + 500 => 'IBM EBCDIC International', + 708 => 'Arabic (ASMO 708)', + 709 => 'Arabic (ASMO-449+, BCON V4)', + 710 => 'Arabic - Transparent Arabic', + 720 => 'DOS Arabic (Transparent ASMO)', + 737 => 'DOS Greek (formerly 437G)', + 775 => 'DOS Baltic', + 850 => 'DOS Latin 1 (Western European)', + 852 => 'DOS Latin 2 (Central European)', + 855 => 'DOS Cyrillic (primarily Russian)', + 857 => 'DOS Turkish', + 858 => 'DOS Multilingual Latin 1 with Euro', + 860 => 'DOS Portuguese', + 861 => 'DOS Icelandic', + 862 => 'DOS Hebrew', + 863 => 'DOS French Canadian', + 864 => 'DOS Arabic', + 865 => 'DOS Nordic', + 866 => 'DOS Russian (Cyrillic)', + 869 => 'DOS Modern Greek', + 870 => 'IBM EBCDIC Multilingual/ROECE (Latin 2)', + 874 => 'Windows Thai (same as 28605, ISO 8859-15)', + 875 => 'IBM EBCDIC Greek Modern', + 932 => 'Windows Japanese (Shift-JIS)', + 936 => 'Windows Simplified Chinese (PRC, Singapore)', + 949 => 'Windows Korean (Unified Hangul Code)', + 950 => 'Windows Traditional Chinese (Taiwan)', + 1026 => 'IBM EBCDIC Turkish (Latin 5)', + 1047 => 'IBM EBCDIC Latin 1/Open System', + 1140 => 'IBM EBCDIC US-Canada with Euro', + 1141 => 'IBM EBCDIC Germany with Euro', + 1142 => 'IBM EBCDIC Denmark-Norway with Euro', + 1143 => 'IBM EBCDIC Finland-Sweden with Euro', + 1144 => 'IBM EBCDIC Italy with Euro', + 1145 => 'IBM EBCDIC Latin America-Spain with Euro', + 1146 => 'IBM EBCDIC United Kingdom with Euro', + 1147 => 'IBM EBCDIC France with Euro', + 1148 => 'IBM EBCDIC International with Euro', + 1149 => 'IBM EBCDIC Icelandic with Euro', + 1200 => 'Unicode UTF-16, little endian', + 1201 => 'Unicode UTF-16, big endian', + 1250 => 'Windows Latin 2 (Central European)', + 1251 => 'Windows Cyrillic', + 1252 => 'Windows Latin 1 (Western European)', + 1253 => 'Windows Greek', + 1254 => 'Windows Turkish', + 1255 => 'Windows Hebrew', + 1256 => 'Windows Arabic', + 1257 => 'Windows Baltic', + 1258 => 'Windows Vietnamese', + 1361 => 'Korean (Johab)', + 10000 => 'Mac Roman (Western European)', + 10001 => 'Mac Japanese', + 10002 => 'Mac Traditional Chinese', + 10003 => 'Mac Korean', + 10004 => 'Mac Arabic', + 10005 => 'Mac Hebrew', + 10006 => 'Mac Greek', + 10007 => 'Mac Cyrillic', + 10008 => 'Mac Simplified Chinese', + 10010 => 'Mac Romanian', + 10017 => 'Mac Ukrainian', + 10021 => 'Mac Thai', + 10029 => 'Mac Latin 2 (Central European)', + 10079 => 'Mac Icelandic', + 10081 => 'Mac Turkish', + 10082 => 'Mac Croatian', + 12000 => 'Unicode UTF-32, little endian', + 12001 => 'Unicode UTF-32, big endian', + 20000 => 'CNS Taiwan', + 20001 => 'TCA Taiwan', + 20002 => 'Eten Taiwan', + 20003 => 'IBM5550 Taiwan', + 20004 => 'TeleText Taiwan', + 20005 => 'Wang Taiwan', + 20105 => 'IA5 (IRV International Alphabet No. 5, 7-bit)', + 20106 => 'IA5 German (7-bit)', + 20107 => 'IA5 Swedish (7-bit)', + 20108 => 'IA5 Norwegian (7-bit)', + 20127 => 'US-ASCII (7-bit)', + 20261 => 'T.61', + 20269 => 'ISO 6937 Non-Spacing Accent', + 20273 => 'IBM EBCDIC Germany', + 20277 => 'IBM EBCDIC Denmark-Norway', + 20278 => 'IBM EBCDIC Finland-Sweden', + 20280 => 'IBM EBCDIC Italy', + 20284 => 'IBM EBCDIC Latin America-Spain', + 20285 => 'IBM EBCDIC United Kingdom', + 20290 => 'IBM EBCDIC Japanese Katakana Extended', + 20297 => 'IBM EBCDIC France', + 20420 => 'IBM EBCDIC Arabic', + 20423 => 'IBM EBCDIC Greek', + 20424 => 'IBM EBCDIC Hebrew', + 20833 => 'IBM EBCDIC Korean Extended', + 20838 => 'IBM EBCDIC Thai', + 20866 => 'Russian/Cyrillic (KOI8-R)', + 20871 => 'IBM EBCDIC Icelandic', + 20880 => 'IBM EBCDIC Cyrillic Russian', + 20905 => 'IBM EBCDIC Turkish', + 20924 => 'IBM EBCDIC Latin 1/Open System with Euro', + 20932 => 'Japanese (JIS 0208-1990 and 0121-1990)', + 20936 => 'Simplified Chinese (GB2312)', + 20949 => 'Korean Wansung', + 21025 => 'IBM EBCDIC Cyrillic Serbian-Bulgarian', + 21027 => 'Extended Alpha Lowercase (deprecated)', + 21866 => 'Ukrainian/Cyrillic (KOI8-U)', + 28591 => 'ISO 8859-1 Latin 1 (Western European)', + 28592 => 'ISO 8859-2 (Central European)', + 28593 => 'ISO 8859-3 Latin 3', + 28594 => 'ISO 8859-4 Baltic', + 28595 => 'ISO 8859-5 Cyrillic', + 28596 => 'ISO 8859-6 Arabic', + 28597 => 'ISO 8859-7 Greek', + 28598 => 'ISO 8859-8 Hebrew (Visual)', + 28599 => 'ISO 8859-9 Turkish', + 28603 => 'ISO 8859-13 Estonian', + 28605 => 'ISO 8859-15 Latin 9', + 29001 => 'Europa 3', + 38598 => 'ISO 8859-8 Hebrew (Logical)', + 50220 => 'ISO 2022 Japanese with no halfwidth Katakana (JIS)', + 50221 => 'ISO 2022 Japanese with halfwidth Katakana (JIS-Allow 1 byte Kana)', + 50222 => 'ISO 2022 Japanese JIS X 0201-1989 (JIS-Allow 1 byte Kana - SO/SI)', + 50225 => 'ISO 2022 Korean', + 50227 => 'ISO 2022 Simplified Chinese', + 50229 => 'ISO 2022 Traditional Chinese', + 50930 => 'EBCDIC Japanese (Katakana) Extended', + 50931 => 'EBCDIC US-Canada and Japanese', + 50933 => 'EBCDIC Korean Extended and Korean', + 50935 => 'EBCDIC Simplified Chinese Extended and Simplified Chinese', + 50936 => 'EBCDIC Simplified Chinese', + 50937 => 'EBCDIC US-Canada and Traditional Chinese', + 50939 => 'EBCDIC Japanese (Latin) Extended and Japanese', + 51932 => 'EUC Japanese', + 51936 => 'EUC Simplified Chinese', + 51949 => 'EUC Korean', + 51950 => 'EUC Traditional Chinese', + 52936 => 'HZ-GB2312 Simplified Chinese', + 54936 => 'Windows XP and later: GB18030 Simplified Chinese (4 byte)', + 57002 => 'ISCII Devanagari', + 57003 => 'ISCII Bengali', + 57004 => 'ISCII Tamil', + 57005 => 'ISCII Telugu', + 57006 => 'ISCII Assamese', + 57007 => 'ISCII Oriya', + 57008 => 'ISCII Kannada', + 57009 => 'ISCII Malayalam', + 57010 => 'ISCII Gujarati', + 57011 => 'ISCII Punjabi', + 65000 => 'Unicode (UTF-7)', + 65001 => 'Unicode (UTF-8)', +); + +# test for file extensions which may be variants of the FPX format +# (have seen one password-protected DOCX file that is FPX-like, so assume +# that all the rest could be as well) +my %fpxFileType = ( + DOC => 1, DOCX => 1, DOCM => 1, + DOT => 1, DOTX => 1, DOTM => 1, + POT => 1, POTX => 1, POTM => 1, + PPS => 1, PPSX => 1, PPSM => 1, + PPT => 1, PPTX => 1, PPTM => 1, THMX => 1, + XLA => 1, XLAM => 1, + XLS => 1, XLSX => 1, XLSM => 1, XLSB => 1, + XLT => 1, XLTX => 1, XLTM => 1, + # non MSOffice types + FLA => 1, VSD => 1, +); + +%Image::ExifTool::FlashPix::Main = ( + PROCESS_PROC => \&ProcessFPXR, + GROUPS => { 2 => 'Image' }, + VARS => { LONG_TAGS => 0 }, + NOTES => q{ + The FlashPix file format, introduced in 1996, was developed by Kodak, + Hewlett-Packard and Microsoft. Internally the FPX file structure mimics + that of an old DOS disk with fixed-sized "sectors" (usually 512 bytes) and a + "file allocation table" (FAT). No wonder this image format never became + popular. However, some of the structures used in FlashPix streams are part + of the EXIF specification, and are still being used in the APP2 FPXR segment + of JPEG images by some digital cameras from manufacturers such as FujiFilm, + Hewlett-Packard, Kodak and Sanyo. + + ExifTool extracts FlashPix information from both FPX images and the APP2 + FPXR segment of JPEG images. As well, FlashPix information is extracted + from DOC, PPT, XLS (Microsoft Word, PowerPoint and Excel) documents, VSD + (Microsoft Visio) drawings, and FLA (Macromedia/Adobe Flash project) files + since these are based on the same file format as FlashPix (the Windows + Compound Binary File format). Note that ExifTool identifies any + unrecognized Windows Compound Binary file as a FlashPix (FPX) file. See + L<http://graphcomp.com/info/specs/livepicture/fpx.pdf> for the FlashPix + specification. + + Note that Microsoft is not consistent with the time zone used for some + date/time tags, and it may be either UTC or local time depending on the + software used to create the file. + }, + "\x05SummaryInformation" => { + Name => 'SummaryInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::SummaryInfo', + }, + }, + "\x05DocumentSummaryInformation" => { + Name => 'DocumentInfo', + Multi => 1, # flag to process UserDefined information after this + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::DocumentInfo', + }, + }, + "\x01CompObj" => { + Name => 'CompObj', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::CompObj', + DirStart => 0x1c, # skip stream header + }, + }, + "\x05Image Info" => { + Name => 'ImageInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::ImageInfo', + }, + }, + "\x05Image Contents" => { + Name => 'Image', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::Image', + }, + }, + "Contents" => { + Name => 'Contents', + Notes => 'found in FLA files; may contain XMP', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + ProcessProc => \&ProcessContents, + }, + }, + "ICC Profile 0001" => { + Name => 'ICC_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + DirStart => 0x1c, # skip stream header + }, + }, + "\x05Extension List" => { + Name => 'Extensions', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::Extensions', + }, + }, + 'Subimage 0000 Header' => { + Name => 'SubimageHdr', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::SubimageHdr', + DirStart => 0x1c, # skip stream header + }, + }, +# 'Subimage 0000 Data' + "\x05Data Object" => { # plus instance number (eg. " 000000") + Name => 'DataObject', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::DataObject', + }, + }, +# "\x05Data Object Store" => { # plus instance number (eg. " 000000") + "\x05Transform" => { # plus instance number (eg. " 000000") + Name => 'Transform', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::Transform', + }, + }, + "\x05Operation" => { # plus instance number (eg. " 000000") + Name => 'Operation', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::Operation', + }, + }, + "\x05Global Info" => { + Name => 'GlobalInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::GlobalInfo', + }, + }, + "\x05Screen Nail" => { # plus class ID (eg. "_bd0100609719a180") + Name => 'ScreenNail', + Groups => { 2 => 'Other' }, + # strip off stream header + ValueConv => 'length($val) > 0x1c and $val = substr($val, 0x1c); \$val', + }, + "\x05Audio Info" => { + Name => 'AudioInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::AudioInfo', + }, + }, + 'Audio Stream' => { # plus instance number (eg. " 000000") + Name => 'AudioStream', + Groups => { 2 => 'Audio' }, + # strip off stream header + ValueConv => 'length($val) > 0x1c and $val = substr($val, 0x1c); \$val', + }, + 'Current User' => { #PH + Name => 'CurrentUser', + # not sure what the rest of this data is, but extract ASCII name from it - PH + ValueConv => q{ + return undef if length $val < 12; + my ($size,$pos) = unpack('x4VV', $val); + my $len = $size - $pos - 4; + return undef if $len < 0 or length $val < $size + 8; + return substr($val, 8 + $pos, $len); + }, + }, + 'WordDocument' => { + Name => 'WordDocument', + SubDirectory => { TagTable => 'Image::ExifTool::FlashPix::WordDocument' }, + }, + # save these tables until after the WordDocument was processed + '0Table' => { + Name => 'Table0', + Hidden => 1, # (used only as temporary storage until table is processed) + Binary => 1, + }, + '1Table' => { + Name => 'Table1', + Hidden => 1, # (used only as temporary storage until table is processed) + Binary => 1, + }, + Preview => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + Notes => 'written by some FujiFilm models', + # skip 47-byte Fuji header + RawConv => q{ + return undef unless length $val > 47; + $val = substr($val, 47); + return $val =~ /^\xff\xd8\xff/ ? $val : undef; + }, + }, + Property => { + Name => 'PreviewInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::PreviewInfo', + ByteOrder => 'BigEndian', + }, + }, + # recognize Autodesk Revit files by looking at BasicFileInfo + # (but don't yet support reading their metatdata) + BasicFileInfo => { + Name => 'BasicFileInfo', + Binary => 1, + RawConv => q{ + $val =~ tr/\0//d; # brute force conversion to ASCII + if ($val =~ /\.(rfa|rft|rte|rvt)/) { + $self->OverrideFileType(uc($1), "application/$1", $1); + } + return $val; + }, + }, + IeImg => { + Name => 'EmbeddedImage', + Notes => q{ + embedded images in Scene7 vignette VNT files. The EmbeddedImage Class and + Rectangle are also extracted for applicable images, and may be associated + with the corresponding EmbeddedImage via the family 3 group name + }, + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + IeImg_class => { + Name => 'EmbeddedImageClass', + Notes => q{ + not a real tag. This information is extracted if available for the + corresponding EmbeddedImage from the Contents of a VNT file + }, + # eg. "Cache", "Mask" + }, + IeImg_rect => { # + Name => 'EmbeddedImageRectangle', + Notes => q{ + not a real tag. This information is extracted if available for the + corresponding EmbeddedImage from the Contents of a VNT file + }, + }, +); + +# Summary Information properties +%Image::ExifTool::FlashPix::SummaryInfo = ( + PROCESS_PROC => \&ProcessProperties, + GROUPS => { 2 => 'Document' }, + NOTES => q{ + The Dictionary, CodePage and LocalIndicator tags are common to all FlashPix + property tables, even though they are only listed in the SummaryInfo table. + }, + 0x00 => { Name => 'Dictionary', Groups => { 2 => 'Other' }, Binary => 1 }, + 0x01 => { + Name => 'CodePage', + Groups => { 2 => 'Other' }, + PrintConv => \%codePage, + }, + 0x02 => 'Title', + 0x03 => 'Subject', + 0x04 => { Name => 'Author', Groups => { 2 => 'Author' } }, + 0x05 => 'Keywords', + 0x06 => 'Comments', + 0x07 => 'Template', + 0x08 => { Name => 'LastModifiedBy', Groups => { 2 => 'Author' } }, + 0x09 => 'RevisionNumber', + 0x0a => { Name => 'TotalEditTime', PrintConv => 'ConvertTimeSpan($val)' }, # (in sec) + 0x0b => { Name => 'LastPrinted', Groups => { 2 => 'Time' } }, + 0x0c => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x0d => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x0e => 'Pages', + 0x0f => 'Words', + 0x10 => 'Characters', + 0x11 => { + Name => 'ThumbnailClip', + # (not a displayable format, so not in the "Preview" group) + Binary => 1, + }, + 0x12 => { + Name => 'Software', + RawConv => '$$self{Software} = $val', # (use to determine file type) + }, + 0x13 => { + Name => 'Security', + # see http://msdn.microsoft.com/en-us/library/aa379255(VS.85).aspx + PrintConv => { + 0 => 'None', + BITMASK => { + 0 => 'Password protected', + 1 => 'Read-only recommended', + 2 => 'Read-only enforced', + 3 => 'Locked for annotations', + }, + }, + }, + 0x22 => { Name => 'CreatedBy', Groups => { 2 => 'Author' } }, #PH (guess) (MAX files) + 0x23 => 'DocumentID', # PH (guess) (MAX files) + # 0x25 ? seen values 1.0-1.97 (MAX files) + 0x80000000 => { Name => 'LocaleIndicator', Groups => { 2 => 'Other' } }, +); + +# Document Summary Information properties (ref 4) +%Image::ExifTool::FlashPix::DocumentInfo = ( + PROCESS_PROC => \&ProcessProperties, + GROUPS => { 2 => 'Document' }, + NOTES => q{ + The DocumentSummaryInformation property set includes a UserDefined property + set for which only the Hyperlinks and HyperlinkBase tags are pre-defined. + However, ExifTool will also extract any other information found in the + UserDefined properties. + }, + # 0x01 => 'CodePage', #7 + 0x02 => 'Category', + 0x03 => 'PresentationTarget', + 0x04 => 'Bytes', + 0x05 => 'Lines', + 0x06 => 'Paragraphs', + 0x07 => 'Slides', + 0x08 => 'Notes', + 0x09 => 'HiddenSlides', + 0x0a => 'MMClips', + 0x0b => { + Name => 'ScaleCrop', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x0c => 'HeadingPairs', + 0x0d => { + Name => 'TitleOfParts', + # look for "3ds Max" software name at beginning of TitleOfParts + RawConv => q{ + (ref $val eq 'ARRAY' ? $$val[0] : $val) =~ /^(3ds Max)/ and $$self{Software} = $1; + return $val; + } + }, + 0x0e => 'Manager', + 0x0f => 'Company', + 0x10 => { + Name => 'LinksUpToDate', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x11 => 'CharCountWithSpaces', + # 0x12 ? seen -32.1850395202637,-386.220672607422,-9.8100004196167,-9810,... + 0x13 => { #PH (unconfirmed) + Name => 'SharedDoc', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + # 0x14 ? seen -1 + # 0x15 ? seen 1 + 0x16 => { + Name => 'HyperlinksChanged', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x17 => { #PH (unconfirmed handling of lower 16 bits, not valid for MAX files) + Name => 'AppVersion', + ValueConv => 'sprintf("%d.%.4d",$val >> 16, $val & 0xffff)', + }, + # 0x18 ? seen -1 (DigitalSignature, VtDigSig format, ref 7) + # 0x19 ? seen 0 + # 0x1a ? seen 0 + # 0x1b ? seen 0 + # 0x1c ? seen 0,1 + # 0x1d ? seen 1 + 0x1a => 'ContentType', #7, github#217 + 0x1b => 'ContentStatus', #7, github#217 + 0x1c => 'Language', #7, github#217 + 0x1d => 'DocVersion', #7, github#217 + # 0x1e ? seen 1 + # 0x1f ? seen 1,5 + # 0x20 ? seen 0,5 + # 0x21 ? seen -1 + # 0x22 ? seen 0 + '_PID_LINKBASE' => { + Name => 'HyperlinkBase', + ValueConv => '$self->Decode($val, "UCS2","II")', + }, + '_PID_HLINKS' => { + Name => 'Hyperlinks', + RawConv => \&ProcessHyperlinks, + }, +); + +# Image Information properties +%Image::ExifTool::FlashPix::ImageInfo = ( + PROCESS_PROC => \&ProcessProperties, + GROUPS => { 2 => 'Image' }, + 0x21000000 => { + Name => 'FileSource', + PrintConv => { + 1 => 'Film Scanner', + 2 => 'Reflection Print Scanner', + 3 => 'Digital Camera', + 4 => 'Video Capture', + 5 => 'Computer Graphics', + }, + }, + 0x21000001 => { + Name => 'SceneType', + PrintConv => { + 1 => 'Original Scene', + 2 => 'Second Generation Scene', + 3 => 'Digital Scene Generation', + }, + }, + 0x21000002 => 'CreationPathVector', + 0x21000003 => 'SoftwareRelease', + 0x21000004 => 'UserDefinedID', + 0x21000005 => 'SharpnessApproximation', + 0x22000000 => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + 0x22000001 => { Name => 'OriginalImageBroker', Groups => { 2 => 'Author' } }, + 0x22000002 => { Name => 'DigitalImageBroker', Groups => { 2 => 'Author' } }, + 0x22000003 => { Name => 'Authorship', Groups => { 2 => 'Author' } }, + 0x22000004 => { Name => 'IntellectualPropertyNotes', Groups => { 2 => 'Author' } }, + 0x23000000 => { + Name => 'TestTarget', + PrintConv => { + 1 => 'Color Chart', + 2 => 'Gray Card', + 3 => 'Grayscale', + 4 => 'Resolution Chart', + 5 => 'Inch Scale', + 6 => 'Centimeter Scale', + 7 => 'Millimeter Scale', + 8 => 'Micrometer Scale', + }, + }, + 0x23000002 => 'GroupCaption', + 0x23000003 => 'CaptionText', + 0x23000004 => 'People', + 0x23000007 => 'Things', + 0x2300000A => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x2300000B => 'Events', + 0x2300000C => 'Places', + 0x2300000F => 'ContentDescriptionNotes', + 0x24000000 => { Name => 'Make', Groups => { 2 => 'Camera' } }, + 0x24000001 => { + Name => 'Model', + Description => 'Camera Model Name', + Groups => { 2 => 'Camera' }, + }, + 0x24000002 => { Name => 'SerialNumber', Groups => { 2 => 'Camera' } }, + 0x25000000 => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x25000001 => { + Name => 'ExposureTime', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x25000002 => { + Name => 'FNumber', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x25000003 => { + Name => 'ExposureProgram', + Groups => { 2 => 'Camera' }, + # use PrintConv of corresponding EXIF tag + PrintConv => $Image::ExifTool::Exif::Main{0x8822}->{PrintConv}, + }, + 0x25000004 => 'BrightnessValue', + 0x25000005 => 'ExposureCompensation', + 0x25000006 => { + Name => 'SubjectDistance', + Groups => { 2 => 'Camera' }, + PrintConv => 'sprintf("%.3f m", $val)', + }, + 0x25000007 => { + Name => 'MeteringMode', + Groups => { 2 => 'Camera' }, + PrintConv => $Image::ExifTool::Exif::Main{0x9207}->{PrintConv}, + }, + 0x25000008 => { + Name => 'LightSource', + Groups => { 2 => 'Camera' }, + PrintConv => $Image::ExifTool::Exif::Main{0x9208}->{PrintConv}, + }, + 0x25000009 => { + Name => 'FocalLength', + Groups => { 2 => 'Camera' }, + PrintConv => 'sprintf("%.1f mm",$val)', + }, + 0x2500000A => { + Name => 'MaxApertureValue', + Groups => { 2 => 'Camera' }, + ValueConv => '2 ** ($val / 2)', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x2500000B => { + Name => 'Flash', + Groups => { 2 => 'Camera' }, + PrintConv => { + 1 => 'No Flash', + 2 => 'Flash Fired', + }, + }, + 0x2500000C => { + Name => 'FlashEnergy', + Groups => { 2 => 'Camera' }, + }, + 0x2500000D => { + Name => 'FlashReturn', + Groups => { 2 => 'Camera' }, + PrintConv => { + 1 => 'Subject Outside Flash Range', + 2 => 'Subject Inside Flash Range', + }, + }, + 0x2500000E => { + Name => 'BackLight', + PrintConv => { + 1 => 'Front Lit', + 2 => 'Back Lit 1', + 3 => 'Back Lit 2', + }, + }, + 0x2500000F => { Name => 'SubjectLocation', Groups => { 2 => 'Camera' } }, + 0x25000010 => 'ExposureIndex', + 0x25000011 => { + Name => 'SpecialEffectsOpticalFilter', + PrintConv => { + 1 => 'None', + 2 => 'Colored', + 3 => 'Diffusion', + 4 => 'Multi-image', + 5 => 'Polarizing', + 6 => 'Split-field', + 7 => 'Star', + }, + }, + 0x25000012 => 'PerPictureNotes', + 0x26000000 => { + Name => 'SensingMethod', + Groups => { 2 => 'Camera' }, + PrintConv => $Image::ExifTool::Exif::Main{0x9217}->{PrintConv}, + }, + 0x26000001 => { Name => 'FocalPlaneXResolution', Groups => { 2 => 'Camera' } }, + 0x26000002 => { Name => 'FocalPlaneYResolution', Groups => { 2 => 'Camera' } }, + 0x26000003 => { + Name => 'FocalPlaneResolutionUnit', + Groups => { 2 => 'Camera' }, + PrintConv => $Image::ExifTool::Exif::Main{0xa210}->{PrintConv}, + }, + 0x26000004 => 'SpatialFrequencyResponse', + 0x26000005 => 'CFAPattern', + 0x27000001 => { + Name => 'FilmCategory', + PrintConv => { + 1 => 'Negative B&W', + 2 => 'Negative Color', + 3 => 'Reversal B&W', + 4 => 'Reversal Color', + 5 => 'Chromagenic', + 6 => 'Internegative B&W', + 7 => 'Internegative Color', + }, + }, + 0x26000007 => 'ISO', + 0x26000008 => 'Opto-ElectricConvFactor', + 0x27000000 => 'FilmBrand', + 0x27000001 => 'FilmCategory', + 0x27000002 => 'FilmSize', + 0x27000003 => 'FilmRollNumber', + 0x27000004 => 'FilmFrameNumber', + 0x29000000 => 'OriginalScannedImageSize', + 0x29000001 => 'OriginalDocumentSize', + 0x29000002 => { + Name => 'OriginalMedium', + PrintConv => { + 1 => 'Continuous Tone Image', + 2 => 'Halftone Image', + 3 => 'Line Art', + }, + }, + 0x29000003 => { + Name => 'TypeOfOriginal', + PrintConv => { + 1 => 'B&W Print', + 2 => 'Color Print', + 3 => 'B&W Document', + 4 => 'Color Document', + }, + }, + 0x28000000 => 'ScannerMake', + 0x28000001 => 'ScannerModel', + 0x28000002 => 'ScannerSerialNumber', + 0x28000003 => 'ScanSoftware', + 0x28000004 => { Name => 'ScanSoftwareRevisionDate', Groups => { 2 => 'Time' } }, + 0x28000005 => 'ServiceOrganizationName', + 0x28000006 => 'ScanOperatorID', + 0x28000008 => { + Name => 'ScanDate', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x28000009 => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x2800000A => 'ScannerPixelSize', +); + +# Image Contents properties +%Image::ExifTool::FlashPix::Image = ( + PROCESS_PROC => \&ProcessProperties, + GROUPS => { 2 => 'Image' }, + # VARS storage is used as a hash lookup for tagID's which aren't constant. + # The key is a mask for significant bits of the tagID, and the value + # is a lookup for tagID's for which this mask is valid. + VARS => { + # ID's are different for each subimage + 0xff00ffff => { + 0x02000000=>1, 0x02000001=>1, 0x02000002=>1, 0x02000003=>1, + 0x02000004=>1, 0x02000005=>1, 0x02000006=>1, 0x02000007=>1, + 0x03000001=>1, + }, + }, + 0x01000000 => 'NumberOfResolutions', + 0x01000002 => 'ImageWidth', # width of highest resolution image + 0x01000003 => 'ImageHeight', + 0x01000004 => 'DefaultDisplayHeight', + 0x01000005 => 'DefaultDisplayWidth', + 0x01000006 => { + Name => 'DisplayUnits', + PrintConv => { + 0 => 'inches', + 1 => 'meters', + 2 => 'cm', + 3 => 'mm', + }, + }, + 0x02000000 => 'SubimageWidth', + 0x02000001 => 'SubimageHeight', + 0x02000002 => { + Name => 'SubimageColor', + # decode only component count and color space of first component + ValueConv => 'sprintf("%.2x %.4x", unpack("x4vx4v",$val))', + PrintConv => { + '01 0000' => 'Opacity Only', + '01 8000' => 'Opacity Only (uncalibrated)', + '01 0001' => 'Monochrome', + '01 8001' => 'Monochrome (uncalibrated)', + '03 0002' => 'YCbCr', + '03 8002' => 'YCbCr (uncalibrated)', + '03 0003' => 'RGB', + '03 8003' => 'RGB (uncalibrated)', + '04 0002' => 'YCbCr with Opacity', + '04 8002' => 'YCbCr with Opacity (uncalibrated)', + '04 0003' => 'RGB with Opacity', + '04 8003' => 'RGB with Opacity (uncalibrated)', + }, + }, + 0x02000003 => { + Name => 'SubimageNumericalFormat', + PrintConv => { + 17 => '8-bit, Unsigned', + 18 => '16-bit, Unsigned', + 19 => '32-bit, Unsigned', + }, + }, + 0x02000004 => { + Name => 'DecimationMethod', + PrintConv => { + 0 => 'None (Full-sized Image)', + 8 => '8-point Prefilter', + }, + }, + 0x02000005 => 'DecimationPrefilterWidth', + 0x02000007 => 'SubimageICC_Profile', + 0x03000001 => { Name => 'JPEGTables', Binary => 1 }, + 0x03000002 => 'MaxJPEGTableIndex', +); + +# Extension List properties +%Image::ExifTool::FlashPix::Extensions = ( + PROCESS_PROC => \&ProcessProperties, + GROUPS => { 2 => 'Other' }, + VARS => { + # ID's are different for each extension type + 0x0000ffff => { + 0x0001=>1, 0x0002=>1, 0x0003=>1, 0x0004=>1, + 0x0005=>1, 0x0006=>1, 0x0007=>1, 0x1000=>1, + 0x2000=>1, 0x2001=>1, 0x3000=>1, 0x4000=>1, + }, + 0x0000f00f => { 0x3001=>1, 0x3002=>1 }, + }, + 0x10000000 => 'UsedExtensionNumbers', + 0x0001 => 'ExtensionName', + 0x0002 => 'ExtensionClassID', + 0x0003 => { + Name => 'ExtensionPersistence', + PrintConv => { + 0 => 'Always Valid', + 1 => 'Invalidated By Modification', + 2 => 'Potentially Invalidated By Modification', + }, + }, + 0x0004 => { Name => 'ExtensionCreateDate', Groups => { 2 => 'Time' } }, + 0x0005 => { Name => 'ExtensionModifyDate', Groups => { 2 => 'Time' } }, + 0x0006 => 'CreatingApplication', + 0x0007 => 'ExtensionDescription', + 0x1000 => 'Storage-StreamPathname', + 0x2000 => 'FlashPixStreamPathname', + 0x2001 => 'FlashPixStreamFieldOffset', + 0x3000 => 'PropertySetPathname', + 0x3001 => 'PropertySetIDCodes', + 0x3002 => 'PropertyVectorElements', + 0x4000 => 'SubimageResolutions', +); + +# Subimage Header tags +%Image::ExifTool::FlashPix::SubimageHdr = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int32u', +# 0 => 'HeaderLength', + 1 => 'SubimageWidth', + 2 => 'SubimageHeight', + 3 => 'SubimageTileCount', + 4 => 'SubimageTileWidth', + 5 => 'SubimageTileHeight', + 6 => 'NumChannels', +# 7 => 'TileHeaderOffset', +# 8 => 'TileHeaderLength', + # ... followed by tile header table +); + +# Data Object properties +%Image::ExifTool::FlashPix::DataObject = ( + PROCESS_PROC => \&ProcessProperties, + GROUPS => { 2 => 'Other' }, + 0x00010000 => 'DataObjectID', + 0x00010002 => 'LockedPropertyList', + 0x00010003 => 'DataObjectTitle', + 0x00010004 => 'LastModifier', + 0x00010005 => 'RevisionNumber', + 0x00010006 => { Name => 'DataCreateDate', Groups => { 2 => 'Time' } }, + 0x00010007 => { Name => 'DataModifyDate', Groups => { 2 => 'Time' } }, + 0x00010008 => 'CreatingApplication', + 0x00010100 => { + Name => 'DataObjectStatus', + PrintConv => q{ + ($val & 0x0000ffff ? 'Exists' : 'Does Not Exist') . + ', ' . ($val & 0xffff0000 ? 'Not ' : '') . 'Purgeable' + }, + }, + 0x00010101 => { + Name => 'CreatingTransform', + PrintConv => '$val ? $val : "Source Image"', + }, + 0x00010102 => 'UsingTransforms', + 0x10000000 => 'CachedImageHeight', + 0x10000001 => 'CachedImageWidth', +); + +# Transform properties +%Image::ExifTool::FlashPix::Transform = ( + PROCESS_PROC => \&ProcessProperties, + GROUPS => { 2 => 'Other' }, + 0x00010000 => 'TransformNodeID', + 0x00010001 => 'OperationClassID', + 0x00010002 => 'LockedPropertyList', + 0x00010003 => 'TransformTitle', + 0x00010004 => 'LastModifier', + 0x00010005 => 'RevisionNumber', + 0x00010006 => { Name => 'TransformCreateDate', Groups => { 2 => 'Time' } }, + 0x00010007 => { Name => 'TransformModifyDate', Groups => { 2 => 'Time' } }, + 0x00010008 => 'CreatingApplication', + 0x00010100 => 'InputDataObjectList', + 0x00010101 => 'OutputDataObjectList', + 0x00010102 => 'OperationNumber', + 0x10000000 => 'ResultAspectRatio', + 0x10000001 => 'RectangleOfInterest', + 0x10000002 => 'Filtering', + 0x10000003 => 'SpatialOrientation', + 0x10000004 => 'ColorTwistMatrix', + 0x10000005 => 'ContrastAdjustment', +); + +# Operation properties +%Image::ExifTool::FlashPix::Operation = ( + PROCESS_PROC => \&ProcessProperties, + 0x00010000 => 'OperationID', +); + +# Global Info properties +%Image::ExifTool::FlashPix::GlobalInfo = ( + PROCESS_PROC => \&ProcessProperties, + 0x00010002 => 'LockedPropertyList', + 0x00010003 => 'TransformedImageTitle', + 0x00010004 => 'LastModifier', + 0x00010100 => 'VisibleOutputs', + 0x00010101 => 'MaximumImageIndex', + 0x00010102 => 'MaximumTransformIndex', + 0x00010103 => 'MaximumOperationIndex', +); + +# Audio Info properties +%Image::ExifTool::FlashPix::AudioInfo = ( + PROCESS_PROC => \&ProcessProperties, + GROUPS => { 2 => 'Audio' }, +); + +# MacroMedia flash contents +%Image::ExifTool::FlashPix::Contents = ( + PROCESS_PROC => \&ProcessProperties, + GROUPS => { 2 => 'Image' }, + OriginalFileName => { Name => 'OriginalFileName', Hidden => 1 }, # (not a real tag -- extracted from Contents of VNT file) +); + +# CompObj tags +%Image::ExifTool::FlashPix::CompObj = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + FORMAT => 'int32u', + 0 => { Name => 'CompObjUserTypeLen' }, + 1 => { + Name => 'CompObjUserType', + Format => 'string[$val{0}]', + RawConv => '$$self{CompObjUserType} = $val', # (use to determine file type) + }, +); + +# decode Word document FIB header (ref [MS-DOC].pdf) +%Image::ExifTool::FlashPix::WordDocument = ( + PROCESS_PROC => \&ProcessWordDocument, + GROUPS => { 2 => 'Other' }, + FORMAT => 'int16u', + NOTES => 'Tags extracted from the Microsoft Word document stream.', + 0 => { + Name => 'Identification', + PrintHex => 1, + PrintConv => { + 0x6a62 => 'MS Word 97', + 0x626a => 'Word 98 Mac', + 0xa5dc => 'Word 6.0/7.0', + 0xa5ec => 'Word 8.0', + }, + }, + 3 => { + Name => 'LanguageCode', + PrintHex => 1, + PrintConv => { + 0x0400 => 'None', + 0x0401 => 'Arabic', + 0x0402 => 'Bulgarian', + 0x0403 => 'Catalan', + 0x0404 => 'Traditional Chinese', + 0x0804 => 'Simplified Chinese', + 0x0405 => 'Czech', + 0x0406 => 'Danish', + 0x0407 => 'German', + 0x0807 => 'German (Swiss)', + 0x0408 => 'Greek', + 0x0409 => 'English (US)', + 0x0809 => 'English (British)', + 0x0c09 => 'English (Australian)', + 0x040a => 'Spanish (Castilian)', + 0x080a => 'Spanish (Mexican)', + 0x040b => 'Finnish', + 0x040c => 'French', + 0x080c => 'French (Belgian)', + 0x0c0c => 'French (Canadian)', + 0x100c => 'French (Swiss)', + 0x040d => 'Hebrew', + 0x040e => 'Hungarian', + 0x040f => 'Icelandic', + 0x0410 => 'Italian', + 0x0810 => 'Italian (Swiss)', + 0x0411 => 'Japanese', + 0x0412 => 'Korean', + 0x0413 => 'Dutch', + 0x0813 => 'Dutch (Belgian)', + 0x0414 => 'Norwegian (Bokmal)', + 0x0814 => 'Norwegian (Nynorsk)', + 0x0415 => 'Polish', + 0x0416 => 'Portuguese (Brazilian)', + 0x0816 => 'Portuguese', + 0x0417 => 'Rhaeto-Romanic', + 0x0418 => 'Romanian', + 0x0419 => 'Russian', + 0x041a => 'Croato-Serbian (Latin)', + 0x081a => 'Serbo-Croatian (Cyrillic)', + 0x041b => 'Slovak', + 0x041c => 'Albanian', + 0x041d => 'Swedish', + 0x041e => 'Thai', + 0x041f => 'Turkish', + 0x0420 => 'Urdu', + 0x0421 => 'Bahasa', + 0x0422 => 'Ukrainian', + 0x0423 => 'Byelorussian', + 0x0424 => 'Slovenian', + 0x0425 => 'Estonian', + 0x0426 => 'Latvian', + 0x0427 => 'Lithuanian', + 0x0429 => 'Farsi', + 0x042d => 'Basque', + 0x042f => 'Macedonian', + 0x0436 => 'Afrikaans', + 0x043e => 'Malaysian', + }, + }, + 5 => { + Name => 'DocFlags', + Mask => 0xff0f, # ignore save count + RawConv => '$$self{DocFlags} = $val', + PrintConv => { BITMASK => { + 0 => 'Template', + 1 => 'AutoText only', + 2 => 'Complex', + 3 => 'Has picture', + # 4-7 = number of incremental saves + 8 => 'Encrypted', + 9 => '1Table', + 10 => 'Read only', + 11 => 'Passworded', + 12 => 'ExtChar', + 13 => 'Load override', + 14 => 'Far east', + 15 => 'Obfuscated', + }}, + }, + 9.1 => { + Name => 'System', + Mask => 0x0001, + PrintConv => { + 0x0000 => 'Windows', + 0x0001 => 'Macintosh', + }, + }, + 9.2 => { + Name => 'Word97', + Mask => 0x0010, + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, +); + +# tags decoded from Word document table +%Image::ExifTool::FlashPix::DocTable = ( + GROUPS => { 1 => 'MS-DOC', 2 => 'Document' }, + NOTES => 'Tags extracted from the Microsoft Word document table.', + VARS => { NO_ID => 1 }, + CommentBy => { + Groups => { 2 => 'Author' }, + Notes => 'enable L<Duplicates|../ExifTool.html#Duplicates> option to extract all entries', + }, + LastSavedBy => { + Groups => { 2 => 'Author' }, + Notes => 'enable L<Duplicates|../ExifTool.html#Duplicates> option to extract history of up to 10 entries', + }, + DOP => { SubDirectory => { TagTable => 'Image::ExifTool::FlashPix::DOP' } }, + ModifyDate => { + Groups => { 2 => 'Time' }, + Format => 'int64u', + Priority => 0, + RawConv => q{ + $val = $val * 1e-7 - 11644473600; # convert to seconds since 1970 + return $val > 0 ? $val : undef; + }, + ValueConv => 'ConvertUnixTime($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, +# +# tags below are used internally in intermediate steps to extract the tags above +# + TableOffsets => { Hidden => 1 }, # stores offsets to extract data from document table + CommentByBlock => { # entire block of CommentBy entries + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::DocTable', + ProcessProc => \&ProcessCommentBy, + }, + Hidden => 1, + }, + LastSavedByBlock => { # entire block of LastSavedBy entries + SubDirectory => { + TagTable => 'Image::ExifTool::FlashPix::DocTable', + ProcessProc => \&ProcessLastSavedBy, + }, + Hidden => 1, + }, +); + +# Microsoft Office Document Properties (ref [MS-DOC].pdf) +%Image::ExifTool::FlashPix::DOP = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 1 => 'MS-DOC', 2 => 'Document' }, + NOTES => 'Microsoft office document properties.', + 20 => { + Name => 'CreateDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + Priority => 0, + RawConv => \&ConvertDTTM, + PrintConv => '$self->ConvertDateTime($val)', + }, + 24 => { + Name => 'ModifyDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + Priority => 0, + RawConv => \&ConvertDTTM, + PrintConv => '$self->ConvertDateTime($val)', + }, + 28 => { + Name => 'LastPrinted', + Format => 'int32u', + Groups => { 2 => 'Time' }, + RawConv => \&ConvertDTTM, + PrintConv => '$self->ConvertDateTime($val)', + }, + 32 => { Name => 'RevisionNumber', Format => 'int16u' }, + 34 => { + Name => 'TotalEditTime', + Format => 'int32u', + PrintConv => 'ConvertTimeSpan($val,60)', + }, + # (according to the MS-DOC specification, the following are accurate only if + # flag 'X' is set, and flag 'u' specifies whether the main or subdoc tags are + # used, but in my tests it seems that both are filled in with reasonable values, + # so just extract the main counts and ignore the subdoc counts for now - PH) + 38 => { Name => 'Words', Format => 'int32u' }, + 42 => { Name => 'Characters', Format => 'int32u' }, + 46 => { Name => 'Pages', Format => 'int16u' }, + 48 => { Name => 'Paragraphs', Format => 'int32u' }, + 56 => { Name => 'Lines', Format => 'int32u' }, + #60 => { Name => 'WordsWithSubdocs', Format => 'int32u' }, + #64 => { Name => 'CharactersWithSubdocs', Format => 'int32u' }, + #68 => { Name => 'PagesWithSubdocs', Format => 'int16u' }, + #70 => { Name => 'ParagraphsWithSubdocs', Format => 'int32u' }, + #74 => { Name => 'LinesWithSubdocs', Format => 'int32u' }, +); + +# FujiFilm "Property" information (ref PH) +%Image::ExifTool::FlashPix::PreviewInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + NOTES => 'Preview information written by some FujiFilm models.', + FIRST_ENTRY => 0, + # values are all constant for for my samples except the two decoded tags + # 0x0000: 01 01 00 00 02 01 00 00 00 00 00 00 00 xx xx 01 + # 0x0010: 01 00 00 00 00 00 00 xx xx 00 00 00 00 00 00 00 + # 0x0020: 00 00 00 00 00 + 0x0d => { + Name => 'PreviewImageWidth', + Format => 'int16u', + }, + 0x17 => { + Name => 'PreviewImageHeight', + Format => 'int16u', + }, +); + +# composite FlashPix tags +%Image::ExifTool::FlashPix::Composite = ( + GROUPS => { 2 => 'Image' }, + PreviewImage => { + Groups => { 2 => 'Preview' }, + # extract JPEG preview from ScreenNail if possible + Require => { + 0 => 'ScreenNail', + }, + Binary => 1, + RawConv => q{ + return undef unless $val[0] =~ /\xff\xd8\xff/g; + @grps = $self->GetGroup($$val{0}); # set groups from ScreenNail + return substr($val[0], pos($val[0])-3); + }, + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::FlashPix'); + +#------------------------------------------------------------------------------ +# Convert Microsoft DTTM structure to date/time +# Inputs: 0) DTTM value +# Returns: EXIF-format date/time string ("0000:00:00 00:00:00" for zero date/time) +sub ConvertDTTM($) +{ + my $val = shift; + my $yr = ($val >> 20) & 0x1ff; + my $mon = ($val >> 16) & 0x0f; + my $day = ($val >> 11) & 0x1f; + my $hr = ($val >> 6) & 0x1f; + my $min = ($val & 0x3f); + $yr += 1900 if $val; + # ExifTool 12.48 dropped the "Z" on the time here because a test .doc + # file written by Word 2011 on Mac certainly used local time here + return sprintf("%.4d:%.2d:%.2d %.2d:%.2d:00",$yr,$mon,$day,$hr,$min); +} + +#------------------------------------------------------------------------------ +# Process hyperlinks from PID_HYPERLINKS array +# (ref http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnaro97ta/html/msdn_hyper97.asp) +# Inputs: 0) value, 1) ExifTool ref +# Returns: list of hyperlinks +sub ProcessHyperlinks($$) +{ + my ($val, $et) = @_; + + # process as an array of VT_VARIANT's + my $dirEnd = length $val; + return undef if $dirEnd < 4; + my $num = Get32u(\$val, 0); + my $valPos = 4; + my ($i, @vals); + for ($i=0; $i<$num; ++$i) { + # read VT_BLOB entries as an array of VT_VARIANT's + my $value = ReadFPXValue($et, \$val, $valPos, VT_VARIANT, $dirEnd); + last unless defined $value; + push @vals, $value; + } + # filter values to extract only the links + my @links; + for ($i=0; $i<@vals; $i+=6) { + push @links, $vals[$i+4]; # get address + $links[-1] .= '#' . $vals[$i+5] if length $vals[$i+5]; # add subaddress + } + return \@links; +} + +#------------------------------------------------------------------------------ +# Read FlashPix value +# Inputs: 0) ExifTool ref, 1) data ref, 2) value offset, 3) FPX format number, +# 4) end offset, 5) flag for no padding, 6) code page +# Returns: converted value (or list of values in list context) and updates +# value offset to end of value if successful, or returns undef on error +sub ReadFPXValue($$$$$;$$) +{ + my ($et, $dataPt, $valPos, $type, $dirEnd, $noPad, $codePage) = @_; + my @vals; + + my $format = $oleFormat{$type & 0x0fff}; + while ($format) { + my $count = 1; + # handle VT_VECTOR types + my $flags = $type & 0xf000; + if ($flags) { + if ($flags == VT_VECTOR) { + $noPad = 1; # values sometimes aren't padded inside vectors!! + my $size = $oleFormatSize{VT_VECTOR}; + if ($valPos + $size > $dirEnd) { + $et->WarnOnce('Incorrect FPX VT_VECTOR size'); + last; + } + $count = Get32u($dataPt, $valPos); + push @vals, '' if $count == 0; # allow zero-element vector + $valPos += 4; + } else { + # can't yet handle this property flag + $et->WarnOnce('Unknown FPX property'); + last; + } + } + unless ($format =~ /^VT_/) { + my $size = Image::ExifTool::FormatSize($format) * $count; + if ($valPos + $size > $dirEnd) { + $et->WarnOnce("Incorrect FPX $format size"); + last; + } + @vals = ReadValue($dataPt, $valPos, $format, $count, $size); + # update position to end of value plus padding + $valPos += ($count * $size + 3) & 0xfffffffc; + last; + } + my $size = $oleFormatSize{$format}; + my ($item, $val, $len); + for ($item=0; $item<$count; ++$item) { + if ($valPos + $size > $dirEnd) { + $et->WarnOnce("Truncated FPX $format value"); + last; + } + # sometimes VT_VECTOR items are padded to even 4-byte boundaries, and sometimes they aren't + if ($noPad and defined $len and $len & 0x03) { + my $pad = 4 - ($len & 0x03); + if ($valPos + $pad + $size <= $dirEnd) { + # skip padding if all zeros + $valPos += $pad if substr($$dataPt, $valPos, $pad) eq "\0" x $pad; + } + } + undef $len; + if ($format eq 'VT_VARIANT') { + my $subType = Get32u($dataPt, $valPos); + $valPos += $size; + $val = ReadFPXValue($et, $dataPt, $valPos, $subType, $dirEnd, $noPad, $codePage); + last unless defined $val; + push @vals, $val; + next; # avoid adding $size to $valPos again + } elsif ($format eq 'VT_FILETIME') { + # convert from time in 100 ns increments to time in seconds + $val = 1e-7 * Image::ExifTool::Get64u($dataPt, $valPos); + # print as date/time if value is greater than one year (PH hack) + my $secDay = 24 * 3600; + if ($val > 365 * $secDay) { + # shift from Jan 1, 1601 to Jan 1, 1970 + my $unixTimeZero = 134774 * $secDay; + $val -= $unixTimeZero; + # there are a lot of bad programmers out there... + my $sec100yr = 100 * 365 * $secDay; + if ($val < 0 || $val > $sec100yr) { + # some software writes the wrong byte order (but the proper word order) + my @w = unpack("x${valPos}NN", $$dataPt); + my $v2 = ($w[0] + $w[1] * 4294967296) * 1e-7 - $unixTimeZero; + if ($v2 > 0 && $v2 < $sec100yr) { + $val = $v2; + # also check for wrong time base + } elsif ($val < 0 && $val + $unixTimeZero > 0) { + $val += $unixTimeZero; + } + } + $val = Image::ExifTool::ConvertUnixTime($val); + } + } elsif ($format eq 'VT_DATE') { + $val = Image::ExifTool::GetDouble($dataPt, $valPos); + # shift zero from Dec 30, 1899 to Jan 1, 1970 and convert to secs + $val = ($val - 25569) * 24 * 3600 if $val != 0; + $val = Image::ExifTool::ConvertUnixTime($val); + } elsif ($format =~ /STR$/) { + $len = Get32u($dataPt, $valPos); + $len *= 2 if $format eq 'VT_LPWSTR'; # convert to byte count + if ($valPos + $len + 4 > $dirEnd) { + $et->WarnOnce("Truncated $format value"); + last; + } + $val = substr($$dataPt, $valPos + 4, $len); + if ($format eq 'VT_LPWSTR') { + # convert wide string from Unicode + $val = $et->Decode($val, 'UCS2'); + } elsif ($codePage) { + my $charset = $Image::ExifTool::charsetName{"cp$codePage"}; + if ($charset) { + $val = $et->Decode($val, $charset); + } elsif ($codePage == 1200) { # UTF-16, little endian + $val = $et->Decode($val, 'UCS2', 'II'); + } + } + $val =~ s/\0.*//s; # truncate at null terminator + # update position for string length + # (the spec states that strings should be padded to align + # on even 32-bit boundaries, but this isn't always the case) + $valPos += $noPad ? $len : ($len + 3) & 0xfffffffc; + } elsif ($format eq 'VT_BLOB' or $format eq 'VT_CF') { + my $len = Get32u($dataPt, $valPos); # (use local $len because we always expect padding) + if ($valPos + $len + 4 > $dirEnd) { + $et->WarnOnce("Truncated $format value"); + last; + } + $val = substr($$dataPt, $valPos + 4, $len); + # update position for data length plus padding + # (does this padding disappear in arrays too?) + $valPos += ($len + 3) & 0xfffffffc; + } elsif ($format eq 'VT_CLSID') { + $val = Image::ExifTool::ASF::GetGUID(substr($$dataPt, $valPos, $size)); + } + $valPos += $size; # update value pointer to end of value + push @vals, $val; + } + # join VT_ values with commas unless we want an array + @vals = ( join $et->Options('ListSep'), @vals ) if @vals > 1 and not wantarray; + last; # didn't really want to loop + } + $_[2] = $valPos; # return updated value position + + push @vals, '' if $type eq 0; # (VT_EMPTY) + if (wantarray) { + return @vals; + } elsif (@vals > 1) { + return join(' ', @vals); + } else { + return $vals[0]; + } +} + +#------------------------------------------------------------------------------ +# Scan for XMP in FLA Contents (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# Notes: FLA format is proprietary and I couldn't find any documentation, +# so this routine is entirely based on observations from sample files +sub ProcessContents($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $isFLA; + + # all of my FLA samples contain "Contents" data, and no other FPX-like samples have + # this (except Scene7 VNT viles), but check the data for a familiar pattern to be + # sure this is FLA: the Contents of all of my FLA samples start with two bytes + # (0x29,0x38,0x3f,0x43 or 0x47, then 0x01) followed by a number of zero bytes + # (from 0x18 to 0x26 of them, related somehow to the value of the first byte), + # followed by the string "DocumentPage" + if ($$dataPt =~ /^..\0+\xff\xff\x01\0\x0d\0CDocumentPage/s) { + $isFLA = 1; + } elsif ($$dataPt =~ /^\0{4}.(.{1,255})\x60\xa1\x3f\x22\0{5}(.{8})/sg) { + # this looks like a VNT file + $et->OverrideFileType('VNT', 'image/x-vignette'); + # hack to set proper file description (extension is the same for V-Note files) + $Image::ExifTool::static_vars{OverrideFileDescription}{VNT} = 'Scene7 Vignette', + my $name = $1; + my ($w, $h) = unpack('V2',$2); + $et->FoundTag(ImageWidth => $w); + $et->FoundTag(ImageHeight => $h); + $et->HandleTag($tagTablePtr, OriginalFileName => $name); + if ($$dataPt =~ /\G\x01\0{4}(.{12})/sg) { + # (first 4 bytes seem to be number of objects, next 4 bytes are zero, then ICC size) + my $size = unpack('x8V', $1); + # (not useful?) $et->FoundTag(NumObjects => $num); + if ($size and pos($$dataPt) + $size < length($$dataPt)) { + my $dat = substr($$dataPt, pos($$dataPt), $size); + $et->FoundTag(ICC_Profile => $dat); + pos($$dataPt) += $size; + } + $$et{IeImg_lkup} = { }; + $$et{IeImg_class} = { }; + # - the byte before \x80 is 0x0d, 0x11 or 0x1f for separate images in my samples, + # and 0x1c or 0x23 for inline masks + # - the byte after \xff\xff is 0x3b in my samples for $1 containing 'VnMask' or 'VnCache' + while ($$dataPt =~ /\x0bTargetRole1(?:.\x80|\xff\xff.\0.\0Vn(\w+))\0\0\x01.{4}(.{24})/sg) { + my ($index, @coords) = unpack('Vx4V4', $2); + next if $index == 0xffffffff; + $$et{IeImg_lkup}{$index} and $et->WarnOnce('Duplicate image index'); + $$et{IeImg_lkup}{$index} = "@coords"; + $$et{IeImg_class}{$index} = $1 if $1; + } + } + } + + # do a brute-force scan of the "Contents" for UTF-16 XMP + # (this may always be little-endian, but allow for either endianness) + if ($$dataPt =~ /<\0\?\0x\0p\0a\0c\0k\0e\0t\0 \0b\0e\0g\0i\0n\0=\0['"](\0\xff\xfe|\xfe\xff)/g) { + $$dirInfo{DirStart} = pos($$dataPt) - 36; + if ($$dataPt =~ /<\0\?\0x\0p\0a\0c\0k\0e\0t\0 \0e\0n\0d\0=\0['"]\0[wr]\0['"]\0\?\0>\0?/g) { + $$dirInfo{DirLen} = pos($$dataPt) - $$dirInfo{DirStart}; + Image::ExifTool::XMP::ProcessXMP($et, $dirInfo, $tagTablePtr); + # override format if not already FLA but XMP-dc:Format indicates it is + $isFLA = 1 if $$et{FILE_TYPE} ne 'FLA' and $$et{VALUE}{Format} and + $$et{VALUE}{Format} eq 'application/vnd.adobe.fla'; + } + } + $et->OverrideFileType('FLA') if $isFLA; + return 1; +} + +#------------------------------------------------------------------------------ +# Process WordDocument stream of MSWord doc file (ref 6) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessWordDocument($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt} or return 0; + my $dirLen = length $$dataPt; + # validate the FIB signature + unless ($dirLen > 2 and Get16u($dataPt,0) == 0xa5ec) { + $et->WarnOnce('Invalid FIB signature', 1); + return 0; + } + $et->ProcessBinaryData($dirInfo, $tagTablePtr); # process FIB + # continue parsing the WordDocument stream until we find the FibRgFcLcb + my $pos = 32; + return 0 if $pos + 2 > $dirLen; + my $n = Get16u($dataPt, $pos); # read csw + $pos += 2 + $n * 2; # skip fibRgW + return 0 if $pos + 2 > $dirLen; + $n = Get16u($dataPt, $pos); # read cslw + $pos += 2 + $n * 4; # skip fibRgLw + return 0 if $pos + 2 > $dirLen; + $n = Get16u($dataPt, $pos); # read cbRgFcLcb + $pos += 2; # point to start of fibRgFcLcbBlob + return 0 if $pos + $n * 8 > $dirLen; + my ($off, @tableOffsets); + # save necessary entries for later processing of document table + # (DOP, CommentBy, LastSavedBy) + foreach $off (0xf8, 0x120, 0x238) { + last if $off + 8 > $n * 8; + push @tableOffsets, Get32u($dataPt, $pos + $off); + push @tableOffsets, Get32u($dataPt, $pos + $off + 4); + } + my $tbl = GetTagTable('Image::ExifTool::FlashPix::DocTable'); + # extract ModifyDate if it exists + $et->HandleTag($tbl, 'ModifyDate', undef, + DataPt => $dataPt, + Start => $pos + 0x2b8, + Size => 8, + ); + $et->HandleTag($tbl, TableOffsets => \@tableOffsets); # save for later + # $pos += $n * 8; # skip fibRgFcLcbBlob + # return 0 if $pos + 2 > $dirLen; + # $n = Get16u($dataPt, $pos); # read cswNew + # return 0 if $pos + 2 + $n * 2 > $dirLen; + # my $nFib = Get16u($dataPt, 2 + ($n ? $pos : 0)); + # $pos += 2 + $n * 2; # skip fibRgCswNew + return 1; +} + +#------------------------------------------------------------------------------ +# Process Microsoft Word Document Table +# Inputs: 0) ExifTool object ref +sub ProcessDocumentTable($) +{ + my $et = shift; + my $value = $$et{VALUE}; + my $extra = $$et{TAG_EXTRA}; + my ($i, $j, $tag); + # loop through TableOffsets for each sub-document + for ($i=0; ; ++$i) { + my $key = 'TableOffsets' . ($i ? " ($i)" : ''); + my $offsets = $$value{$key}; + last unless defined $offsets; + my $doc; + $doc = $$extra{$key}{G3} if $$extra{$key}; + $doc = '' unless $doc; + # get DocFlags for this sub-document + my ($docFlags, $docTable); + for ($j=0; ; ++$j) { + my $key = 'DocFlags' . ($j ? " ($j)" : ''); + last unless defined $$value{$key}; + my $tmp; + $tmp = $$extra{$key}{G3} if $$extra{$key}; + $tmp = '' unless $tmp; + if ($tmp eq $doc) { + $docFlags = $$value{$key}; + last; + } + } + next unless defined $docFlags; + $tag = $docFlags & 0x200 ? 'Table1' : 'Table0'; + # get table for this sub-document + for ($j=0; ; ++$j) { + my $key = $tag . ($j ? " ($j)" : ''); + last unless defined $$value{$key}; + my $tmp; + $tmp = $$extra{$key}{G3} if $$extra{$key}; + $tmp = '' unless $tmp; + if ($tmp eq $doc) { + $docTable = \$$value{$key}; + last; + } + } + next unless defined $docTable; + # extract DOP and LastSavedBy information from document table + $$et{DOC_NUM} = $doc; # use same document number + my $tagTablePtr = GetTagTable('Image::ExifTool::FlashPix::DocTable'); + foreach $tag (qw(DOP CommentByBlock LastSavedByBlock)) { + last unless @$offsets; + my $off = shift @$offsets; + my $len = shift @$offsets; + next unless $len and $off + $len <= length $$docTable; + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => $docTable, + Start => $off, + Size => $len, + ); + } + delete $$et{DOC_NUM}; + } + # delete intermediate tags + foreach $tag (qw(TableOffsets Table0 Table1)) { + for ($i=0; ; ++$i) { + my $key = $tag . ($i ? " ($i)" : ''); + last unless defined $$value{$key}; + $et->DeleteTag($key); + } + } +} + +#------------------------------------------------------------------------------ +# Extract names of comment authors (ref 6) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessCommentBy($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $end = $$dirInfo{DirLen} + $pos; + $et->VerboseDir($$dirInfo{DirName}); + while ($pos + 2 < $end) { + my $len = Get16u($dataPt, $pos); + $pos += 2; + last if $pos + $len * 2 > $end; + my $author = $et->Decode(substr($$dataPt, $pos, $len*2), 'UCS2'); + $pos += $len * 2; + $et->HandleTag($tagTablePtr, CommentBy => $author); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract last-saved-by names (ref 5) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessLastSavedBy($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $end = $$dirInfo{DirLen} + $pos; + return 0 if $pos + 6 > $end; + $et->VerboseDir($$dirInfo{DirName}); + my $num = Get16u($dataPt, $pos+2); + $pos += 6; + while ($num >= 2) { + last if $pos + 2 > $end; + my $len = Get16u($dataPt, $pos); + $pos += 2; + last if $pos + $len * 2 > $end; + my $author = $et->Decode(substr($$dataPt, $pos, $len*2), 'UCS2'); + $pos += $len * 2; + last if $pos + 2 > $end; + $len = Get16u($dataPt, $pos); + $pos += 2; + last if $pos + $len * 2 > $end; + my $path = $et->Decode(substr($$dataPt, $pos, $len*2), 'UCS2'); + $pos += $len * 2; + $et->HandleTag($tagTablePtr, LastSavedBy => "$author ($path)"); + $num -= 2; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Check FPX byte order mark (BOM) and set byte order appropriately +# Inputs: 0) data ref, 1) offset to BOM +# Returns: true on success +sub CheckBOM($$) +{ + my ($dataPt, $pos) = @_; + my $bom = Get16u($dataPt, $pos); + return 1 if $bom == 0xfffe; + return 0 unless $bom == 0xfeff; + ToggleByteOrder(); + return 1; +} + +#------------------------------------------------------------------------------ +# Process FlashPix properties +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessProperties($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || length($$dataPt) - $pos; + my $dirEnd = $pos + $dirLen; + my $verbose = $et->Options('Verbose'); + my $n; + + if ($dirLen < 48) { + $et->Warn('Truncated FPX properties'); + return 0; + } + # check and set our byte order if necessary + unless (CheckBOM($dataPt, $pos)) { + $et->Warn('Bad FPX property byte order mark'); + return 0; + } + # get position of start of section + $pos = Get32u($dataPt, $pos + 44); + if ($pos < 48) { + $et->Warn('Bad FPX property section offset'); + return 0; + } + for ($n=0; $n<2; ++$n) { + my %dictionary; # dictionary to translate user-defined properties + my $codePage; + last if $pos + 8 > $dirEnd; + # read property section header + my $size = Get32u($dataPt, $pos); + last unless $size; + my $numEntries = Get32u($dataPt, $pos + 4); + $verbose and $et->VerboseDir('Property Info', $numEntries, $size); + if ($pos + 8 + 8 * $numEntries > $dirEnd) { + $et->Warn('Truncated property list'); + last; + } + my $index; + for ($index=0; $index<$numEntries; ++$index) { + my $entry = $pos + 8 + 8 * $index; + my $tag = Get32u($dataPt, $entry); + my $offset = Get32u($dataPt, $entry + 4); + my $valStart = $pos + 4 + $offset; + last if $valStart >= $dirEnd; + my $valPos = $valStart; + my $type = Get32u($dataPt, $pos + $offset); + if ($tag == 0) { + # read dictionary to get tag name lookup for this property set + my $i; + for ($i=0; $i<$type; ++$i) { + last if $valPos + 8 > $dirEnd; + $tag = Get32u($dataPt, $valPos); + my $len = Get32u($dataPt, $valPos + 4); + $valPos += 8 + $len; + last if $valPos > $dirEnd; + my $name = substr($$dataPt, $valPos - $len, $len); + $name =~ s/\0.*//s; + next unless length $name; + $dictionary{$tag} = $name; + next if $$tagTablePtr{$name}; + $tag = $name; + $name =~ s/(^| )([a-z])/\U$2/g; # start with uppercase + $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters + next unless length $name; + $et->VPrint(0, "$$et{INDENT}\[adding $name]\n") if $verbose; + AddTagToTable($tagTablePtr, $tag, { Name => $name }); + } + next; + } + # use tag name from dictionary if available + my ($custom, $val); + if (defined $dictionary{$tag}) { + $tag = $dictionary{$tag}; + $custom = 1; + } + my @vals = ReadFPXValue($et, $dataPt, $valPos, $type, $dirEnd, undef, $codePage); + @vals or $et->Warn('Error reading property value'); + $val = @vals > 1 ? \@vals : $vals[0]; + my $format = $type & 0x0fff; + my $flags = $type & 0xf000; + my $formStr = $oleFormat{$format} || "Type $format"; + $formStr .= '|' . ($oleFlags{$flags} || sprintf("0x%x",$flags)) if $flags; + my $tagInfo; + # check for common tag ID's: Dictionary, CodePage and LocaleIndicator + # (must be done before masking because masked tags may overlap these ID's) + if (not $custom and ($tag == 1 or $tag == 0x80000000)) { + # get tagInfo from SummaryInfo table + my $summaryTable = GetTagTable('Image::ExifTool::FlashPix::SummaryInfo'); + $tagInfo = $et->GetTagInfo($summaryTable, $tag); + if ($tag == 1) { + $val += 0x10000 if $val < 0; # (may be incorrectly stored as int16s) + $codePage = $val; # save code page for translating values + } + } elsif ($$tagTablePtr{$tag}) { + $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + } elsif ($$tagTablePtr{VARS} and not $custom) { + # mask off insignificant bits of tag ID if necessary + my $masked = $$tagTablePtr{VARS}; + my $mask; + foreach $mask (keys %$masked) { + if ($masked->{$mask}->{$tag & $mask}) { + $tagInfo = $et->GetTagInfo($tagTablePtr, $tag & $mask); + last; + } + } + } + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + Start => $valStart, + Size => $valPos - $valStart, + Format => $formStr, + Index => $index, + TagInfo => $tagInfo, + Extra => ", type=$type", + ); + } + # issue warning if we hit end of property section prematurely + $et->Warn('Truncated property data') if $index < $numEntries; + last unless $$dirInfo{Multi}; + $pos += $size; + } + + return 1; +} + +#------------------------------------------------------------------------------ +# Load chain of sectors from file +# Inputs: 0) RAF ref, 1) first sector number, 2) FAT ref, 3) sector size, 4) header size +sub LoadChain($$$$$) +{ + my ($raf, $sect, $fatPt, $sectSize, $hdrSize) = @_; + return undef unless $raf; + my $chain = ''; + my ($buff, %loadedSect); + for (;;) { + last if $sect >= END_OF_CHAIN; + return undef if $loadedSect{$sect}; # avoid infinite loop + $loadedSect{$sect} = 1; + my $offset = $sect * $sectSize + $hdrSize; + return undef unless ($offset <= 0x7fffffff or $$raf{LargeFileSupport}) and + $raf->Seek($offset, 0) and + $raf->Read($buff, $sectSize) == $sectSize; + $chain .= $buff; + # step to next sector in chain + return undef if $sect * 4 > length($$fatPt) - 4; + $sect = Get32u($fatPt, $sect * 4); + } + return $chain; +} + +#------------------------------------------------------------------------------ +# Extract information from a JPEG APP2 FPXR segment +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessFPXR($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + my $verbose = $et->Options('Verbose'); + + if ($dirLen < 13) { + $et->Warn('FPXR segment too small'); + return 0; + } + + # get version and segment type (version is 0 in all my samples) + my ($vers, $type) = unpack('x5C2', $$dataPt); + + if ($type == 1) { # a "Contents List" segment + + $vers != 0 and $et->Warn("Untested FPXR version $vers"); + if ($$et{FPXR}) { + $et->Warn('Multiple FPXR contents lists'); + delete $$et{FPXR}; + } + my $numEntries = unpack('x7n', $$dataPt); + my @contents; + $verbose and $et->VerboseDir('Contents List', $numEntries); + my $pos = 9; + my $entry; + for ($entry = 0; $entry < $numEntries; ++$entry) { + if ($pos + 4 > $dirLen) { + $et->Warn('Truncated FPXR contents'); + return 0; + } + my ($size, $default) = unpack("x${pos}Na", $$dataPt); + pos($$dataPt) = $pos + 5; + # according to the spec, this string is little-endian + # (very odd, since the size word is big-endian), + # and the first char must be '/' + unless ($$dataPt =~ m{\G(/\0(..)*?)\0\0}sg) { + $et->Warn('Invalid FPXR stream name'); + return 0; + } + # convert stream pathname to ascii + my $name = Image::ExifTool::Decode(undef, $1, 'UCS2', 'II', 'Latin'); + if ($verbose) { + my $psize = ($size == 0xffffffff) ? 'storage' : "$size bytes"; + $et->VPrint(0," | $entry) Name: '${name}' [$psize]\n"); + } + # remove directory specification + $name =~ s{.*/}{}s; + # read storage class ID if necessary + my $classID; + if ($size == 0xffffffff) { + unless ($$dataPt =~ m{(.{16})}sg) { + $et->Warn('Truncated FPXR storage class ID'); + return 0; + } + # unpack class ID in case we want to use it sometime + $classID = Image::ExifTool::ASF::GetGUID($1); + } + # find the tagInfo if available + my $tagInfo; + unless ($$tagTablePtr{$name}) { + # remove instance number or class ID from tag if necessary + $tagInfo = $et->GetTagInfo($tagTablePtr, $1) if + ($name =~ /(.*) \d{6}$/s and $$tagTablePtr{$1}) or + ($name =~ /(.*)_[0-9a-f]{16}$/s and $$tagTablePtr{$1}); + } + # update position in list + $pos = pos($$dataPt); + # add to our contents list + push @contents, { + Name => $name, + Size => $size, + Default => $default, + ClassID => $classID, + TagInfo => $tagInfo, + }; + } + # save contents list as $et member variable + # (must do this last so we don't save list on error) + $$et{FPXR} = \@contents; + + } elsif ($type == 2) { # a "Stream Data" segment + + # get the contents list index and stream data offset + my ($index, $offset) = unpack('x7nN', $$dataPt); + my $fpxr = $$et{FPXR}; + if ($fpxr and $$fpxr[$index]) { + my $obj = $$fpxr[$index]; + # extract stream data (after 13-byte header) + if (not defined $$obj{Stream}) { + # ignore offset for first segment of this type + # (in my sample images, this isn't always zero as one would expect) + $$obj{Stream} = substr($$dataPt, $dirStart+13); + } else { + # add data at the proper offset to the stream + my $overlap = length($$obj{Stream}) - $offset; + my $start = $dirStart + 13; + if ($overlap < 0 or $dirLen - $overlap < 13) { + $et->WarnOnce("Bad FPXR stream $index offset",1); + } else { + # ignore any overlapping data in this segment + # (this seems to be the convention) + $start += $overlap; + } + # concatenate data with this stream + $$obj{Stream} .= substr($$dataPt, $start); + } + # save value for this tag if stream is complete + my $len = length $$obj{Stream}; + if ($len >= $$obj{Size}) { + $et->VPrint(0, " + [FPXR stream $index, $len bytes]\n") if $verbose; + if ($len > $$obj{Size}) { + $et->Warn('Extra data in FPXR segment (truncated)'); + $$obj{Stream} = substr($$obj{Stream}, 0, $$obj{Size}); + } + # handle this tag + $et->HandleTag($tagTablePtr, $$obj{Name}, $$obj{Stream}, + DataPt => \$$obj{Stream}, + TagInfo => $$obj{TagInfo}, + ); + delete $$obj{Stream}; # done with this stream + } + # hack for improperly stored FujiFilm PreviewImage (stored with no contents list) + } elsif ($index == 512 and $dirLen > 60 and ($$et{FujiPreview} or + ($dirLen > 64 and substr($$dataPt, $dirStart+60, 4) eq "\xff\xd8\xff\xdb"))) + { + $$et{FujiPreview} = '' unless defined $$et{FujiPreview}; + # recombine PreviewImage, skipping 13-byte FPXR header + 47-byte Fuji header + $$et{FujiPreview} .= substr($$dataPt, $dirStart+60); + } else { + # (Kodak uses index 255 for a free segment in images from some cameras) + $et->Warn("Unlisted FPXR segment (index $index)") if $index != 255; + } + + } elsif ($type != 3) { # not a "Reserved" segment + + $et->Warn("Unknown FPXR segment (type $type)"); + + } + + # clean up if this was the last FPXR segment + if ($$dirInfo{LastFPXR}) { + if ($$et{FPXR}) { + my $obj; + foreach $obj (@{$$et{FPXR}}) { + next unless defined $$obj{Stream} and length $$obj{Stream}; + # parse it even though it isn't the proper length + $et->HandleTag($tagTablePtr, $$obj{Name}, $$obj{Stream}, + DataPt => \$$obj{Stream}, + TagInfo => $$obj{TagInfo}, + ); + } + delete $$et{FPXR}; # delete our temporary variables + } + if ($$et{FujiPreview}) { + $et->FoundTag('PreviewImage', $$et{FujiPreview}); + delete $$et{FujiPreview}; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Set document number for objects +# Inputs: 0) object hierarchy hash ref, 1) object index, 2) doc number list ref, +# 3) doc numbers used at each level, 4) flag set for metadata levels +sub SetDocNum($$;$$$) +{ + my ($hier, $index, $doc, $used, $meta) = @_; + my $obj = $$hier{$index} or return; + return if exists $$obj{DocNum}; + $$obj{DocNum} = $doc; + SetDocNum($hier, $$obj{Left}, $doc, $used, $meta) if $$obj{Left}; + SetDocNum($hier, $$obj{Right}, $doc, $used, $meta) if $$obj{Right}; + if (defined $$obj{Child}) { + $used or $used = [ ]; + my @subDoc; + push @subDoc, @$doc if $doc; + # we must dive down 2 levels for each sub-document, so use the + # $meta flag to add a sub-document level only for every 2nd generation + if ($meta) { + my $subNum = ($$used[scalar @subDoc] || 0); + $$used[scalar @subDoc] = $subNum; + push @subDoc, $subNum; + } elsif (@subDoc) { + $subDoc[-1] = ++$$used[$#subDoc]; + } + SetDocNum($hier, $$obj{Child}, \@subDoc, $used, not $meta); + } +} + +#------------------------------------------------------------------------------ +# Extract information from a FlashPix (FPX) file +# Inputs: 0) ExifTool object ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid FPX-format file +sub ProcessFPX($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $out, $oldIndent, $miniStreamBuff); + my ($tag, %hier, %objIndex, %loadedDifSect); + + # read header + return 0 unless $raf->Read($buff,HDR_SIZE) == HDR_SIZE; + # check signature + return 0 unless $buff =~ /^\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1/; + + # set FileType initially based on file extension (we may override this later) + my $fileType = $$et{FILE_EXT}; + $fileType = 'FPX' unless $fileType and $fpxFileType{$fileType}; + $et->SetFileType($fileType); + SetByteOrder(substr($buff, 0x1c, 2) eq "\xff\xfe" ? 'MM' : 'II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::FlashPix::Main'); + my $verbose = $et->Options('Verbose'); + # copy LargeFileSupport option to RAF for use in LoadChain + $$raf{LargeFileSupport} = $et->Options('LargeFileSupport'); + + my $sectSize = 1 << Get16u(\$buff, 0x1e); + my $miniSize = 1 << Get16u(\$buff, 0x20); + my $fatCount = Get32u(\$buff, 0x2c); # number of FAT sectors + my $dirStart = Get32u(\$buff, 0x30); # first directory sector + my $miniCutoff = Get32u(\$buff, 0x38); # minimum size for big-FAT streams + my $miniStart = Get32u(\$buff, 0x3c); # first sector of mini-FAT + my $miniCount = Get32u(\$buff, 0x40); # number of mini-FAT sectors + my $difStart = Get32u(\$buff, 0x44); # first sector of DIF chain + my $difCount = Get32u(\$buff, 0x48); # number of DIF sectors + + if ($verbose) { + $out = $et->Options('TextOut'); + print $out " Sector size=$sectSize\n FAT: Count=$fatCount\n"; + print $out " DIR: Start=$dirStart\n"; + print $out " MiniFAT: Mini-sector size=$miniSize Start=$miniStart Count=$miniCount Cutoff=$miniCutoff\n"; + print $out " DIF FAT: Start=$difStart Count=$difCount\n"; + } +# +# load the FAT +# + my $pos = 0x4c; + my $endPos = length($buff); + my $fat = ''; + my $fatCountCheck = 0; + my $difCountCheck = 0; + my $hdrSize = $sectSize > HDR_SIZE ? $sectSize : HDR_SIZE; + + for (;;) { + while ($pos <= $endPos - 4) { + my $sect = Get32u(\$buff, $pos); + $pos += 4; + next if $sect == FREE_SECT; + my $offset = $sect * $sectSize + $hdrSize; + my $fatSect; + unless ($raf->Seek($offset, 0) and + $raf->Read($fatSect, $sectSize) == $sectSize) + { + $et->Error("Error reading FAT from sector $sect"); + return 1; + } + $fat .= $fatSect; + ++$fatCountCheck; + } + last if $difStart >= END_OF_CHAIN; + # read next DIF (Dual Indirect FAT) sector + if (++$difCountCheck > $difCount) { + $et->Warn('Unterminated DIF FAT'); + last; + } + if ($loadedDifSect{$difStart}) { + $et->Warn('Cyclical reference in DIF FAT'); + last; + } + my $offset = $difStart * $sectSize + $hdrSize; + unless ($raf->Seek($offset, 0) and $raf->Read($buff, $sectSize) == $sectSize) { + $et->Error("Error reading DIF sector $difStart"); + return 1; + } + $loadedDifSect{$difStart} = 1; + # set end of sector information in this DIF + $pos = 0; + $endPos = $sectSize - 4; + # next time around we want to read next DIF in chain + $difStart = Get32u(\$buff, $endPos); + } + if ($fatCountCheck != $fatCount) { + $et->Warn("Bad number of FAT sectors (expected $fatCount but found $fatCountCheck)"); + } +# +# load the mini-FAT and the directory +# + my $miniFat = LoadChain($raf, $miniStart, \$fat, $sectSize, $hdrSize); + my $dir = LoadChain($raf, $dirStart, \$fat, $sectSize, $hdrSize); + unless (defined $miniFat and defined $dir) { + $et->Error('Error reading mini-FAT or directory stream'); + return 1; + } + if ($verbose) { + print $out " FAT [",length($fat)," bytes]:\n"; + $et->VerboseDump(\$fat); + print $out " Mini-FAT [",length($miniFat)," bytes]:\n"; + $et->VerboseDump(\$miniFat); + print $out " Directory [",length($dir)," bytes]:\n"; + $et->VerboseDump(\$dir); + } +# +# process the directory +# + if ($verbose) { + $oldIndent = $$et{INDENT}; + $$et{INDENT} .= '| '; + $et->VerboseDir('FPX', undef, length $dir); + } + my $miniStream; + $endPos = length($dir); + my $index = 0; + + for ($pos=0; $pos<=$endPos-128; $pos+=128, ++$index) { + + # get directory entry type + # (0=invalid, 1=storage, 2=stream, 3=lockbytes, 4=property, 5=root) + my $type = Get8u(\$dir, $pos + 0x42); + next if $type == 0; # skip invalid entries + if ($type > 5) { + $et->Warn("Invalid directory entry type $type"); + last; # rest of directory is probably garbage + } + # get entry name (note: this is supposed to be length in 2-byte + # characters but this isn't what is done in my sample FPX file, so + # be very tolerant of this count -- it's null terminated anyway) + my $len = Get16u(\$dir, $pos + 0x40); + $len > 32 and $len = 32; + $tag = Image::ExifTool::Decode(undef, substr($dir,$pos,$len*2), 'UCS2', 'II', 'Latin'); + $tag =~ s/\0.*//s; # truncate at null (in case length was wrong) + + my $sect = Get32u(\$dir, $pos + 0x74); # start sector number + my $size = Get32u(\$dir, $pos + 0x78); # stream length + + # load Ministream (referenced from first directory entry) + unless ($miniStream) { + $miniStreamBuff = LoadChain($raf, $sect, \$fat, $sectSize, $hdrSize); + unless (defined $miniStreamBuff) { + $et->Warn('Error loading Mini-FAT stream'); + last; + } + $miniStream = new File::RandomAccess(\$miniStreamBuff); + } + + my $tagInfo; + if ($$tagTablePtr{$tag}) { + $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + } else { + # remove instance number or class ID from tag if necessary + $tagInfo = $et->GetTagInfo($tagTablePtr, $1) if + ($tag =~ /(.*) \d{6}$/s and $$tagTablePtr{$1}) or + ($tag =~ /(.*)_[0-9a-f]{16}$/s and $$tagTablePtr{$1}) or + ($tag =~ /(.*)_[0-9]{4}$/s and $$tagTablePtr{$1}); # IeImg instances + } + + my $lSib = Get32u(\$dir, $pos + 0x44); # left sibling + my $rSib = Get32u(\$dir, $pos + 0x48); # right sibling + my $chld = Get32u(\$dir, $pos + 0x4c); # child directory + + # save information about object hierarchy + my ($obj, $sub); + $obj = $hier{$index} or $obj = $hier{$index} = { }; + $$obj{Left} = $lSib unless $lSib == FREE_SECT; + $$obj{Right} = $rSib unless $rSib == FREE_SECT; + unless ($chld == FREE_SECT) { + $$obj{Child} = $chld; + $sub = $hier{$chld} or $sub = $hier{$chld} = { }; + $$sub{Parent} = $index; + } + + next unless $tagInfo or $verbose; + + # load the data for stream types + my $extra = ''; + my $typeStr = $dirEntryType[$type] || $type; + if ($typeStr eq 'STREAM') { + if ($size >= $miniCutoff) { + # stream is in the main FAT + $buff = LoadChain($raf, $sect, \$fat, $sectSize, $hdrSize); + } elsif ($size) { + # stream is in the mini-FAT + $buff = LoadChain($miniStream, $sect, \$miniFat, $miniSize, 0); + } else { + $buff = ''; # an empty stream + } + unless (defined $buff) { + my $name = $tagInfo ? $$tagInfo{Name} : 'unknown'; + $et->Warn("Error reading $name stream"); + $buff = ''; + } + } elsif ($typeStr eq 'ROOT') { + $buff = $miniStreamBuff; + $extra .= ' (Ministream)'; + } else { + $buff = ''; + undef $size; + } + if ($verbose) { + my $flags = Get8u(\$dir, $pos + 0x43); # 0=red, 1=black + my $col = { 0 => 'Red', 1 => 'Black' }->{$flags} || $flags; + $extra .= " Type=$typeStr Flags=$col"; + $extra .= " Left=$lSib" unless $lSib == FREE_SECT; + $extra .= " Right=$rSib" unless $rSib == FREE_SECT; + $extra .= " Child=$chld" unless $chld == FREE_SECT; + $et->VerboseInfo($tag, $tagInfo, + Index => $index, + Value => $buff, + DataPt => \$buff, + Extra => $extra, + Size => $size, + ); + } + if ($tagInfo and $buff) { + my $num = $$et{NUM_FOUND}; + my $subdir = $$tagInfo{SubDirectory}; + if ($subdir) { + my %dirInfo = ( + DataPt => \$buff, + DirStart => $$subdir{DirStart}, + DirLen => length $buff, + Multi => $$tagInfo{Multi}, + ); + my $subTablePtr = GetTagTable($$subdir{TagTable}); + $et->ProcessDirectory(\%dirInfo, $subTablePtr, $$subdir{ProcessProc}); + } elsif (defined $size and $size > length($buff)) { + $et->WarnOnce('Truncated object'); + } else { + $buff = substr($buff, 0, $size) if defined $size and $size < length($buff); + if ($tag =~ /^IeImg_0*(\d+)$/) { + # set document number for embedded images and their positions (if available, VNT files) + my $num = $1; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $et->FoundTag($tagInfo, $buff); + if ($$et{IeImg_lkup} and $$et{IeImg_lkup}{$num}) { + # save position of this image + $et->HandleTag($tagTablePtr, IeImg_rect => $$et{IeImg_lkup}{$num}); + delete $$et{IeImg_lkup}{$num}; + if ($$et{IeImg_class} and $$et{IeImg_class}{$num}) { + $et->HandleTag($tagTablePtr, IeImg_class => $$et{IeImg_class}{$num}); + delete $$et{IeImg_class}{$num}; + } + } + delete $$et{DOC_NUM}; + } else { + $et->FoundTag($tagInfo, $buff); + } + } + # save object index number for all found tags + my $num2 = $$et{NUM_FOUND}; + $objIndex{++$num} = $index while $num < $num2; + } + } + # set document numbers for tags extracted from embedded documents + unless ($$et{DOC_NUM}) { + # initialize document number for all objects, beginning at root (index 0) + SetDocNum(\%hier, 0); + # set family 3 group name for all tags in embedded documents + my $order = $$et{FILE_ORDER}; + my (@pri, $copy, $member); + foreach $tag (keys %$order) { + my $num = $$order{$tag}; + next unless defined $num and $objIndex{$num}; + my $obj = $hier{$objIndex{$num}} or next; + my $docNums = $$obj{DocNum}; + next unless $docNums and @$docNums; + $$et{TAG_EXTRA}{$tag}{G3} = join '-', @$docNums; + push @pri, $tag unless $tag =~ / /; # save keys for priority sub-doc tags + } + # swap priority sub-document tags with main document tags if they exist + foreach $tag (@pri) { + for ($copy=1; ;++$copy) { + my $key = "$tag ($copy)"; + last unless defined $$et{VALUE}{$key}; + my $extra = $$et{TAG_EXTRA}{$key}; + next if $extra and $$extra{G3}; # not Main if family 3 group is set + foreach $member ('PRIORITY','VALUE','FILE_ORDER','TAG_INFO','TAG_EXTRA') { + my $pHash = $$et{$member}; + my $t = $$pHash{$tag}; + $$pHash{$tag} = $$pHash{$key}; + $$pHash{$key} = $t; + } + last; + } + } + } + $$et{INDENT} = $oldIndent if $verbose; + # try to better identify the file type + if ($$et{FileType} eq 'FPX') { + my $val = $$et{CompObjUserType} || $$et{Software}; + if ($val) { + my %type = ( '^3ds Max' => 'MAX', Word => 'DOC', PowerPoint => 'PPT', Excel => 'XLS' ); + my $pat; + foreach $pat (sort keys %type) { + next unless $val =~ /$pat/; + $et->OverrideFileType($type{$pat}); + last; + } + } + } + # process Word document table + ProcessDocumentTable($et); + + if ($$et{IeImg_lkup} and %{$$et{IeImg_lkup}}) { + $et->Warn('Image positions exist without corresponding images'); + } + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::FlashPix - Read FlashPix meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +FlashPix meta information from FPX images, and from the APP2 FPXR segment of +JPEG images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.exif.org/Exif2-2.PDF> + +=item L<http://www.graphcomp.com/info/specs/livepicture/fpx.pdf> + +=item L<http://search.cpan.org/~jdb/libwin32/> + +=item L<http://msdn.microsoft.com/en-us/library/aa380374.aspx> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/FlashPix Tags>, +L<Image::ExifTool::TagNames/OOXML Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Font.pm b/ExifTool/lib/Image/ExifTool/Font.pm new file mode 100644 index 0000000..d8fd244 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Font.pm @@ -0,0 +1,654 @@ +#------------------------------------------------------------------------------ +# File: Font.pm +# +# Description: Read meta information from font files +# +# Revisions: 2010/01/15 - P. Harvey Created +# +# References: 1) http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6.html +# 2) http://www.microsoft.com/typography/otspec/otff.htm +# 3) http://partners.adobe.com/public/developer/opentype/index_font_file.html +# 4) http://partners.adobe.com/public/developer/en/font/5178.PFM.pdf +# 5) http://opensource.adobe.com/svn/opensource/flex/sdk/trunk/modules/compiler/src/java/flex2/compiler/util/MimeMappings.java +# 6) http://www.adobe.com/devnet/font/pdfs/5004.AFM_Spec.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::Font; + +use strict; +use vars qw($VERSION %ttLang); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.10'; + +sub ProcessOTF($$); + +# TrueType 'name' platform codes +my %ttPlatform = ( + 0 => 'Unicode', + 1 => 'Macintosh', + 2 => 'ISO', + 3 => 'Windows', + 4 => 'Custom', +); + +# convert TrueType 'name' character encoding to ExifTool Charset (ref 1/2) +my %ttCharset = ( + Macintosh => { + 0 => 'MacRoman', 17 => 'MacMalayalam', + 1 => 'MacJapanese', 18 => 'MacSinhalese', + 2 => 'MacChineseTW', 19 => 'MacBurmese', + 3 => 'MacKorean', 20 => 'MacKhmer', + 4 => 'MacArabic', 21 => 'MacThai', + 5 => 'MacHebrew', 22 => 'MacLaotian', + 6 => 'MacGreek', 23 => 'MacGeorgian', + 7 => 'MacCyrillic', 24 => 'MacArmenian', # 7=Russian + 8 => 'MacRSymbol', 25 => 'MacChineseCN', + 9 => 'MacDevanagari', 26 => 'MacTibetan', + 10 => 'MacGurmukhi', 27 => 'MacMongolian', + 11 => 'MacGujarati', 28 => 'MacGeez', + 12 => 'MacOriya', 29 => 'MacCyrillic', # 29=Slavic + 13 => 'MacBengali', 30 => 'MacVietnam', + 14 => 'MacTamil', 31 => 'MacSindhi', + 15 => 'MacTelugu', 32 => '', # 32=uninterpreted + 16 => 'MacKannada', + }, + Windows => { + 0 => 'Symbol', 4 => 'Big5', + 1 => 'UCS2', 5 => 'Wansung', + 2 => 'ShiftJIS', 6 => 'Johab', + 3 => 'PRC', 10 => 'UCS4', + }, + Unicode => { + # (we don't currently handle the various Unicode flavours) + 0 => 'UCS2', # Unicode 1.0 semantics + 1 => 'UCS2', # Unicode 1.1 semantics + 2 => 'UCS2', # ISO 10646 semantics + 3 => 'UCS2', # Unicode 2.0 and onwards semantics, Unicode BMP only. + 4 => 'UCS2', # Unicode 2.0 and onwards semantics, Unicode full repertoire. + # 5 => Unicode Variation Sequences (not used in Naming table) + }, + ISO => { # (deprecated) + 0 => 'UTF8', # (7-bit ASCII) + 1 => 'UCS2', # ISO 10646 + 2 => 'Latin', # ISO 8859-1 + }, + Custom => { }, +); + +# convert TrueType 'name' language code to ExifTool language code +%ttLang = ( + # Macintosh language codes (also used by QuickTime.pm) + # oddities: + # 49 - Cyrillic version 83 - Roman + # 50 - Arabic version 84 - Arabic + # 146 - with dot above + Macintosh => { + 0 => 'en', 24 => 'lt', 48 => 'kk', 72 => 'ml', 129 => 'eu', + 1 => 'fr', 25 => 'pl', 49 => 'az', 73 => 'kn', 130 => 'ca', + 2 => 'de', 26 => 'hu', 50 => 'az', 74 => 'ta', 131 => 'la', + 3 => 'it', 27 => 'et', 51 => 'hy', 75 => 'te', 132 => 'qu', + 4 => 'nl-NL', 28 => 'lv', 52 => 'ka', 76 => 'si', 133 => 'gn', + 5 => 'sv', 29 => 'smi', 53 => 'ro', 77 => 'my', 134 => 'ay', + 6 => 'es', 30 => 'fo', 54 => 'ky', 78 => 'km', 135 => 'tt', + 7 => 'da', 31 => 'fa', 55 => 'tg', 79 => 'lo', 136 => 'ug', + 8 => 'pt', 32 => 'ru', 56 => 'tk', 80 => 'vi', 137 => 'dz', + 9 => 'no', 33 => 'zh-CN', 57 => 'mn-MN', 81 => 'id', 138 => 'jv', + 10 => 'he', 34 => 'nl-BE', 58 => 'mn-CN', 82 => 'tl', 139 => 'su', + 11 => 'ja', 35 => 'ga', 59 => 'ps', 83 => 'ms-MY', 140 => 'gl', + 12 => 'ar', 36 => 'sq', 60 => 'ku', 84 => 'ms-BN', 141 => 'af', + 13 => 'fi', 37 => 'ro', 61 => 'ks', 85 => 'am', 142 => 'br', + 14 => 'el', 38 => 'cs', 62 => 'sd', 86 => 'ti', 144 => 'gd', + 15 => 'is', 39 => 'sk', 63 => 'bo', 87 => 'om', 145 => 'gv', + 16 => 'mt', 40 => 'sl', 64 => 'ne', 88 => 'so', 146 => 'ga', + 17 => 'tr', 41 => 'yi', 65 => 'sa', 89 => 'sw', 147 => 'to', + 18 => 'hr', 42 => 'sr', 66 => 'mr', 90 => 'rw', 148 => 'el', + 19 => 'zh-TW', 43 => 'mk', 67 => 'bn', 91 => 'rn', 149 => 'kl', + 20 => 'ur', 44 => 'bg', 68 => 'as', 92 => 'ny', 150 => 'az', + 21 => 'hi', 45 => 'uk', 69 => 'gu', 93 => 'mg', + 22 => 'th', 46 => 'be', 70 => 'pa', 94 => 'eo', + 23 => 'ko', 47 => 'uz', 71 => 'or', 128 => 'cy', + }, + # Windows language codes (http://msdn.microsoft.com/en-us/library/0h88fahh(VS.85).aspx) + # Notes: This isn't an exact science. The reference above gives language codes + # which are different from some ISO 639-1 numbers. Also, some Windows language + # codes don't appear to have ISO 639-1 equivalents. + # 0x0428 - fa by ref above + # 0x048c - no ISO equivalent + # 0x081a/0x83c - sr-SP + # 0x0c0a - modern? + # 0x2409 - Caribbean country code not found in ISO 3166-1 + Windows => { + 0x0401 => 'ar-SA', 0x0438 => 'fo', 0x0481 => 'mi', 0x1409 => 'en-NZ', + 0x0402 => 'bg', 0x0439 => 'hi', 0x0482 => 'oc', 0x140a => 'es-CR', + 0x0403 => 'ca', 0x043a => 'mt', 0x0483 => 'co', 0x140c => 'fr-LU', + 0x0404 => 'zh-TW', 0x043b => 'se-NO', 0x0484 => 'gsw', 0x141a => 'bs-BA', + 0x0405 => 'cs', 0x043c => 'gd', 0x0485 => 'sah', 0x143b => 'smj-SE', + 0x0406 => 'da', 0x043d => 'yi', 0x0486 => 'ny', 0x1801 => 'ar-MA', + 0x0407 => 'de-DE', 0x043e => 'ms-MY', 0x0487 => 'rw', 0x1809 => 'en-IE', + 0x0408 => 'el', 0x043f => 'kk', 0x048c => 'Dari', 0x180a => 'es-PA', + 0x0409 => 'en-US', 0x0440 => 'ky', 0x0801 => 'ar-IQ', 0x180c => 'fr-MC', + 0x040a => 'es-ES', 0x0441 => 'sw', 0x0804 => 'zh-CN', 0x181a => 'sr-BA', + 0x040b => 'fi', 0x0442 => 'tk', 0x0807 => 'de-CH', 0x183b => 'sma-NO', + 0x040c => 'fr-FR', 0x0443 => 'uz-UZ', 0x0809 => 'en-GB', 0x1c01 => 'ar-TN', + 0x040d => 'he', 0x0444 => 'tt', 0x080a => 'es-MX', 0x1c09 => 'en-ZA', + 0x040e => 'hu', 0x0445 => 'bn-IN', 0x080c => 'fr-BE', 0x1c0a => 'es-DO', + 0x040f => 'is', 0x0446 => 'pa', 0x0810 => 'it-CH', 0x1c1a => 'sr-BA', + 0x0410 => 'it-IT', 0x0447 => 'gu', 0x0813 => 'nl-BE', 0x1c3b => 'sma-SE', + 0x0411 => 'ja', 0x0448 => 'wo', 0x0814 => 'nn', 0x2001 => 'ar-OM', + 0x0412 => 'ko', 0x0449 => 'ta', 0x0816 => 'pt-PT', 0x2009 => 'en-JM', + 0x0413 => 'nl-NL', 0x044a => 'te', 0x0818 => 'ro-MO', 0x200a => 'es-VE', + 0x0414 => 'no-NO', 0x044b => 'kn', 0x0819 => 'ru-MO', 0x201a => 'bs-BA', + 0x0415 => 'pl', 0x044c => 'ml', 0x081a => 'sr-RS', 0x203b => 'sms', + 0x0416 => 'pt-BR', 0x044d => 'as', 0x081d => 'sv-FI', 0x2401 => 'ar-YE', + 0x0417 => 'rm', 0x044e => 'mr', 0x082c => 'az-AZ', 0x2409 => 'en-CB', + 0x0418 => 'ro', 0x044f => 'sa', 0x082e => 'dsb', 0x240a => 'es-CO', + 0x0419 => 'ru', 0x0450 => 'mn-MN', 0x083b => 'se-SE', 0x243b => 'smn', + 0x041a => 'hr', 0x0451 => 'bo', 0x083c => 'ga', 0x2801 => 'ar-SY', + 0x041b => 'sk', 0x0452 => 'cy', 0x083e => 'ms-BN', 0x2809 => 'en-BZ', + 0x041c => 'sq', 0x0453 => 'km', 0x0843 => 'uz-UZ', 0x280a => 'es-PE', + 0x041d => 'sv-SE', 0x0454 => 'lo', 0x0845 => 'bn-BD', 0x2c01 => 'ar-JO', + 0x041e => 'th', 0x0456 => 'gl', 0x0850 => 'mn-CN', 0x2c09 => 'en-TT', + 0x041f => 'tr', 0x0457 => 'kok', 0x085d => 'iu-CA', 0x2c0a => 'es-AR', + 0x0420 => 'ur', 0x045a => 'syr', 0x085f => 'tmh', 0x3001 => 'ar-LB', + 0x0421 => 'id', 0x045b => 'si', 0x086b => 'qu-EC', 0x3009 => 'en-ZW', + 0x0422 => 'uk', 0x045d => 'iu-CA', 0x0c01 => 'ar-EG', 0x300a => 'es-EC', + 0x0423 => 'be', 0x045e => 'am', 0x0c04 => 'zh-HK', 0x3401 => 'ar-KW', + 0x0424 => 'sl', 0x0461 => 'ne', 0x0c07 => 'de-AT', 0x3409 => 'en-PH', + 0x0425 => 'et', 0x0462 => 'fy', 0x0c09 => 'en-AU', 0x340a => 'es-CL', + 0x0426 => 'lv', 0x0463 => 'ps', 0x0c0a => 'es-ES', 0x3801 => 'ar-AE', + 0x0427 => 'lt', 0x0464 => 'fil', 0x0c0c => 'fr-CA', 0x380a => 'es-UY', + 0x0428 => 'tg', 0x0465 => 'dv', 0x0c1a => 'sr-RS', 0x3c01 => 'ar-BH', + 0x042a => 'vi', 0x0468 => 'ha', 0x0c3b => 'se-FI', 0x3c0a => 'es-PY', + 0x042b => 'hy', 0x046a => 'yo', 0x0c6b => 'qu-PE', 0x4001 => 'ar-QA', + 0x042c => 'az-AZ', 0x046b => 'qu-BO', 0x1001 => 'ar-LY', 0x4009 => 'en-IN', + 0x042d => 'eu', 0x046c => 'st', 0x1004 => 'zh-SG', 0x400a => 'es-BO', + 0x042e => 'hsb', 0x046d => 'ba', 0x1007 => 'de-LU', 0x4409 => 'en-MY', + 0x042f => 'mk', 0x046e => 'lb', 0x1009 => 'en-CA', 0x440a => 'es-SV', + 0x0430 => 'st', 0x046f => 'kl', 0x100a => 'es-GT', 0x4809 => 'en-SG', + 0x0431 => 'ts', 0x0470 => 'ig', 0x100c => 'fr-CH', 0x480a => 'es-HN', + 0x0432 => 'tn', 0x0478 => 'yi', 0x101a => 'hr-BA', 0x4c0a => 'es-NI', + 0x0434 => 'xh', 0x047a => 'arn', 0x103b => 'smj-NO',0x500a => 'es-PR', + 0x0435 => 'zu', 0x047c => 'moh', 0x1401 => 'ar-DZ', 0x540a => 'es-US', + 0x0436 => 'af', 0x047e => 'br', 0x1404 => 'zh-MO', + 0x0437 => 'ka', 0x0480 => 'ug', 0x1407 => 'de-LI', + }, + Unicode => { }, + ISO => { }, + Custom => { }, +); + +# eclectic table of tags for various format font files +%Image::ExifTool::Font::Main = ( + GROUPS => { 2 => 'Document' }, + NOTES => q{ + This table contains a collection of tags found in font files of various + formats. ExifTool current recognizes OTF, TTF, TTC, DFONT, PFA, PFB, PFM, + AFM, ACFM and AMFM font files. + }, + name => { + SubDirectory => { TagTable => 'Image::ExifTool::Font::Name' }, + }, + PFM => { + Name => 'PFMHeader', + SubDirectory => { TagTable => 'Image::ExifTool::Font::PFM' }, + }, + PSInfo => { + Name => 'PSFontInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Font::PSInfo' }, + }, + AFM => { + Name => 'AFM', + SubDirectory => { TagTable => 'Image::ExifTool::Font::AFM' }, + }, + numfonts => 'NumFonts', + fontname => 'FontName', + postfont => { + Name => 'PostScriptFontName', + Description => 'PostScript Font Name', + }, +); + +# TrueType name tags (ref 1/2) +%Image::ExifTool::Font::Name = ( + GROUPS => { 2 => 'Document' }, + NOTES => q{ + The following tags are extracted from the TrueType font "name" table found + in OTF, TTF, TTC and DFONT files. These tags support localized languages by + adding a hyphen followed by a language code to the end of the tag name (eg. + "Copyright-fr" or "License-en-US"). Tags with no language code use the + default language of "en". + }, + 0 => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + 1 => 'FontFamily', + 2 => 'FontSubfamily', + 3 => 'FontSubfamilyID', + 4 => 'FontName', # full name + 5 => 'NameTableVersion', + 6 => { Name => 'PostScriptFontName', Description => 'PostScript Font Name' }, + 7 => 'Trademark', + 8 => 'Manufacturer', + 9 => 'Designer', + 10 => 'Description', + 11 => 'VendorURL', + 12 => 'DesignerURL', + 13 => 'License', + 14 => 'LicenseInfoURL', + 16 => 'PreferredFamily', + 17 => 'PreferredSubfamily', + 18 => 'CompatibleFontName', + 19 => 'SampleText', + 20 => { + Name => 'PostScriptFontName', + Description => 'PostScript Font Name', + }, + 21 => 'WWSFamilyName', + 22 => 'WWSSubfamilyName', +); + +# PostScript Font Metric file header (ref 4) +%Image::ExifTool::Font::PFM = ( + GROUPS => { 2 => 'Document' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + NOTES => 'Tags extracted from the PFM file header.', + 0 => { + Name => 'PFMVersion', + Format => 'int16u', + PrintConv => 'sprintf("%x.%.2x",$val>>8,$val&0xff)', + }, + 6 => { Name => 'Copyright', Format => 'string[60]', Groups => { 2 => 'Author' } }, + 66 => { Name => 'FontType', Format => 'int16u' }, + 68 => { Name => 'PointSize', Format => 'int16u' }, + 70 => { Name => 'YResolution', Format => 'int16u' }, + 72 => { Name => 'XResolution', Format => 'int16u' }, + 74 => { Name => 'Ascent', Format => 'int16u' }, + 76 => { Name => 'InternalLeading', Format => 'int16u' }, + 78 => { Name => 'ExternalLeading', Format => 'int16u' }, + 80 => { Name => 'Italic' }, + 81 => { Name => 'Underline' }, + 82 => { Name => 'Strikeout' }, + 83 => { Name => 'Weight', Format => 'int16u' }, + 85 => { Name => 'CharacterSet' }, + 86 => { Name => 'PixWidth', Format => 'int16u' }, + 88 => { Name => 'PixHeight', Format => 'int16u' }, + 90 => { Name => 'PitchAndFamily' }, + 91 => { Name => 'AvgWidth', Format => 'int16u' }, + 93 => { Name => 'MaxWidth', Format => 'int16u' }, + 95 => { Name => 'FirstChar' }, + 96 => { Name => 'LastChar' }, + 97 => { Name => 'DefaultChar' }, + 98 => { Name => 'BreakChar' }, + 99 => { Name => 'WidthBytes', Format => 'int16u' }, + # 101 => { Name => 'DeviceTypeOffset', Format => 'int32u' }, + # 105 => { Name => 'FontNameOffset', Format => 'int32u' }, + # 109 => { Name => 'BitsPointer', Format => 'int32u' }, + # 113 => { Name => 'BitsOffset', Format => 'int32u' }, +); + +# PostScript FontInfo attributes (PFA, PFB) (ref PH) +%Image::ExifTool::Font::PSInfo = ( + GROUPS => { 2 => 'Document' }, + NOTES => 'Tags extracted from PostScript font files (PFA and PFB).', + FullName => { }, + FamilyName => { Name => 'FontFamily' }, + Weight => { }, + ItalicAngle => { }, + isFixedPitch=> { }, + UnderlinePosition => { }, + UnderlineThickness => { }, + Copyright => { Groups => { 2 => 'Author' } }, + Notice => { Groups => { 2 => 'Author' } }, + version => { }, + FontName => { }, + FontType => { }, + FSType => { }, +); + +# Adobe Font Metrics tags (AFM) (ref 6) +%Image::ExifTool::Font::AFM = ( + GROUPS => { 2 => 'Document' }, + NOTES => 'Tags extracted from Adobe Font Metrics files (AFM, ACFM and AMFM).', + 'Creation Date' => { Name => 'CreateDate', Groups => { 2 => 'Time' } }, + FontName => { }, + FullName => { }, + FamilyName => { Name => 'FontFamily' }, + Weight => { }, + Version => { }, + Notice => { Groups => { 2 => 'Author' } }, + EncodingScheme => { }, + MappingScheme => { }, + EscChar => { }, + CharacterSet=> { }, + Characters => { }, + IsBaseFont => { }, + # VVector => { }, + IsFixedV => { }, + CapHeight => { }, + XHeight => { }, + Ascender => { }, + Descender => { }, +); + +#------------------------------------------------------------------------------ +# Read information from a TrueType font collection (TTC) (refs 2,3) +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid TrueType font collection +sub ProcessTTC($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $i); + + return 0 unless $raf->Read($buff, 12) == 12; + return 0 unless $buff =~ /^ttcf\0[\x01\x02]\0\0/; + SetByteOrder('MM'); + my $num = Get32u(\$buff, 8); + # might as well put a limit on the number of fonts we will parse (< 256) + return 0 unless $num < 0x100 and $raf->Read($buff, $num * 4) == $num * 4; + $et->SetFileType('TTC'); + return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3; + my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Main'); + $et->HandleTag($tagTablePtr, 'numfonts', $num); + # loop through all fonts in the collection + for ($i=0; $i<$num; ++$i) { + my $n = $i + 1; + $et->VPrint(0, "Font $n:\n"); + $$et{SET_GROUP1} = "+$n"; + my $offset = Get32u(\$buff, $i * 4); + $raf->Seek($offset, 0) or last; + ProcessOTF($et, $dirInfo) or last; + } + delete $$et{SET_GROUP1}; + return 1; +} + +#------------------------------------------------------------------------------ +# Read information from a TrueType font file (OTF or TTF) (refs 1,2) +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid TrueType font file +sub ProcessOTF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($tbl, $buff, $pos, $i); + my $base = $$dirInfo{Base} || 0; + + return 0 unless $raf->Read($buff, 12) == 12; + return 0 unless $buff =~ /^(\0\x01\0\0|OTTO|true|typ1|\xa5(kbd|lst))[\0\x01]/; + + $et->SetFileType($1 eq 'OTTO' ? 'OTF' : 'TTF'); + return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3; + SetByteOrder('MM'); + my $numTables = Get16u(\$buff, 4); + return 0 unless $numTables > 0 and $numTables < 0x200; + my $len = $numTables * 16; + return 0 unless $raf->Read($tbl, $len) == $len; + + my $verbose = $et->Options('Verbose'); + my $oldIndent = $$et{INDENT}; + $$et{INDENT} .= '| '; + $et->VerboseDir('TrueType', $numTables) if $verbose; + + for ($pos=0; $pos<$len; $pos+=16) { + # look for 'name' table + my $tag = substr($tbl, $pos, 4); + next unless $tag eq 'name' or $verbose; + my $offset = Get32u(\$tbl, $pos + 8); + my $size = Get32u(\$tbl, $pos + 12); + unless ($raf->Seek($offset+$base, 0) and $raf->Read($buff, $size) == $size) { + $et->Warn("Error reading '${tag}' data"); + next; + } + if ($verbose) { + $tag =~ s/([\0-\x1f\x80-\xff])/sprintf('\x%.2x',ord $1)/ge; + my $str = sprintf("%s%d) Tag '%s' (offset 0x%.4x, %d bytes)\n", + $$et{INDENT}, $pos/16, $tag, $offset, $size); + $et->VPrint(0, $str); + $et->VerboseDump(\$buff, Addr => $offset) if $verbose > 2; + next unless $tag eq 'name'; + } + next unless $size >= 8; + my $entries = Get16u(\$buff, 2); + my $recEnd = 6 + $entries * 12; + if ($recEnd > $size) { + $et->Warn('Truncated name record'); + last; + } + my $strStart = Get16u(\$buff, 4); + if ($strStart < $recEnd or $strStart > $size) { + $et->Warn('Invalid string offset'); + last; + } + # parse language-tag record (in format 1 Naming table only) (ref 2) + my %langTag; + if (Get16u(\$buff, 0) == 1 and $recEnd + 2 <= $size) { + my $langTags = Get16u(\$buff, $recEnd); + if ($langTags and $recEnd + 2 + $langTags * 4 < $size) { + for ($i=0; $i<$langTags; ++$i) { + my $pt = $recEnd + 2 + $i * 4; + my $langLen = Get16u(\$buff, $pt); + # make sure the language string length is reasonable (UTF-16BE) + last if $langLen == 0 or $langLen & 0x01 or $langLen > 40; + my $langPt = Get16u(\$buff, $pt + 2) + $strStart; + last if $langPt + $langLen > $size; + my $lang = substr($buff, $langPt, $langLen); + $lang = $et->Decode($lang,'UCS2','MM','UTF8'); + $lang =~ tr/-_a-zA-Z0-9//dc; # remove naughty characters + $langTag{$i + 0x8000} = $lang; + } + } + } + my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Name'); + $$et{INDENT} .= '| '; + $et->VerboseDir('Name', $entries) if $verbose; + for ($i=0; $i<$entries; ++$i) { + my $pt = 6 + $i * 12; + my $platform = Get16u(\$buff, $pt); + my $encoding = Get16u(\$buff, $pt + 2); + my $langID = Get16u(\$buff, $pt + 4); + my $nameID = Get16u(\$buff, $pt + 6); + my $strLen = Get16u(\$buff, $pt + 8); + my $strPt = Get16u(\$buff, $pt + 10) + $strStart; + if ($strPt + $strLen <= $size) { + my $val = substr($buff, $strPt, $strLen); + my ($lang, $charset, $extra); + my $sys = $ttPlatform{$platform}; + # translate from specified encoding + if ($sys) { + $lang = $ttLang{$sys}{$langID} || $langTag{$langID}; + $charset = $ttCharset{$sys}{$encoding}; + if (not $charset) { + if (not defined $charset and not $$et{FontWarn}) { + $et->Warn("Unknown $sys character set ($encoding)"); + $$et{FontWarn} = 1; + } + } else { + # translate to ExifTool character set + $val = $et->Decode($val, $charset); + } + } else { + $et->Warn("Unknown platform ($platform) for name $nameID"); + } + # get the tagInfo for our specific language (use 'en' for default) + my $tagInfo = $et->GetTagInfo($tagTablePtr, $nameID); + if ($tagInfo and $lang and $lang ne 'en') { + my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang); + $tagInfo = $langInfo if $langInfo; + } + if ($verbose) { + $langID > 0x400 and $langID = sprintf('0x%x', $langID); + $extra = ", Plat=$platform/" . ($sys || 'Unknown') . ', ' . + "Enc=$encoding/" . ($charset || 'Unknown') . ', ' . + "Lang=$langID/" . ($lang || 'Unknown'); + } + $et->HandleTag($tagTablePtr, $nameID, $val, + TagInfo => $tagInfo, + DataPt => \$buff, + DataPos => $offset, + Start => $strPt, + Size => $strLen, + Index => $i, + Extra => $extra, + ); + } + } + $$et{INDENT} = $oldIndent . '| '; + last unless $verbose; + } + $$et{INDENT} = $oldIndent; + return 1; +} + +#------------------------------------------------------------------------------ +# Read information from an Adobe Font Metrics file (AFM, ACFM, AMFM) (ref 6) +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a recognized AFM-type file +sub ProcessAFM($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $comment); + + require Image::ExifTool::PostScript; + local $/ = Image::ExifTool::PostScript::GetInputRecordSeparator($raf); + $raf->ReadLine($buff); + return 0 unless $buff =~ /^Start(Comp|Master)?FontMetrics\s+\d+/; + my $ftyp = $1 ? ($1 eq 'Comp' ? 'ACFM' : 'AMFM') : 'AFM'; + $et->SetFileType($ftyp, 'application/x-font-afm'); + return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3; + my $tagTablePtr = GetTagTable('Image::ExifTool::Font::AFM'); + + for (;;) { + $raf->ReadLine($buff) or last; + if (defined $comment and $buff !~ /^Comment\s/) { + $et->FoundTag('Comment', $comment); + undef $comment; + } + $buff =~ /^(\w+)\s+(.*?)[\x0d\x0a]/ or next; + my ($tag, $val) = ($1, $2); + if ($tag eq 'Comment' and $val =~ /^(Creation Date):\s+(.*)/) { + ($tag, $val) = ($1, $2); + } + $val =~ s/^\((.*)\)$/$1/; # (some values may be in brackets) + if ($tag eq 'Comment') { + # concatinate all comments into a single value + $comment = defined($comment) ? "$comment\n$val" : $val; + next; + } + unless ($et->HandleTag($tagTablePtr, $tag, $val)) { + # end parsing if we start any subsection + last if $tag =~ /^Start/ and $tag ne 'StartDirection'; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Read information from various format font files +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a recognized Font file +sub ProcessFont($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $buf2, $rtnVal); + return 0 unless $raf->Read($buff, 24) and $raf->Seek(0,0); + if ($buff =~ /^(\0\x01\0\0|OTTO|true|typ1)[\0\x01]/) { # OTF, TTF + $rtnVal = ProcessOTF($et, $dirInfo); + } elsif ($buff =~ /^ttcf\0[\x01\x02]\0\0/) { # TTC + $rtnVal = ProcessTTC($et, $dirInfo); + } elsif ($buff =~ /^Start(Comp|Master)?FontMetrics\s+\d+/s) { # AFM + $rtnVal = ProcessAFM($et, $dirInfo); + } elsif ($buff =~ /^(.{6})?%!(PS-(AdobeFont-|Bitstream )|FontType1-)/s) {# PFA, PFB + $raf->Seek(6,0) and $et->SetFileType('PFB') if $1; + require Image::ExifTool::PostScript; + $rtnVal = Image::ExifTool::PostScript::ProcessPS($et, $dirInfo); + } elsif ($buff =~ /^\0[\x01\x02]/ and $raf->Seek(0, 2) and # PFM + # validate file size + $raf->Tell() > 117 and $raf->Tell() == unpack('x2V',$buff) and + # read PFM header + $raf->Seek(0,0) and $raf->Read($buff,117) == 117 and + # validate "DeviceType" string (must be "PostScript\0") + SetByteOrder('II') and $raf->Seek(Get32u(\$buff, 101), 0) and + # the DeviceType should be "PostScript\0", but FontForge + # incorrectly writes "Postscript\0", so ignore case + $raf->Read($buf2, 11) == 11 and lc($buf2) eq "postscript\0") + { + $et->SetFileType('PFM'); + return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3; + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Main'); + # process the PFM header + $et->HandleTag($tagTablePtr, 'PFM', $buff); + # extract the font names + my $nameOff = Get32u(\$buff, 105); + if ($raf->Seek($nameOff, 0) and $raf->Read($buff, 256) and + $buff =~ /^([\x20-\xff]+)\0([\x20-\xff]+)\0/) + { + $et->HandleTag($tagTablePtr, 'fontname', $1); + $et->HandleTag($tagTablePtr, 'postfont', $2); + } + $rtnVal = 1; + } elsif ($buff =~ /^(wOF[F2])/) { + my $type = $1 eq 'wOFF' ? 'woff' : 'woff2'; + $et->SetFileType(uc($type), "font/$type"); + # (don't yet extract metadata from these files) + $rtnVal = 1; + } else { + $rtnVal = 0; + } + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Font - Read meta information from font files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains the routines required by Image::ExifTool to read meta +information from various format font files. Currently recognized font file +types are OTF, TTF, TTC, DFONT, PFA, PFB, PFM, AFM, ACFM and AMFM. As well, +WOFF and WOFF2 font files are identified, but metadata is not currently +extracted from these formats. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://developer.apple.com/textfonts/TTRefMan/RM06/Chap6.html> + +=item L<http://www.microsoft.com/typography/otspec/otff.htm> + +=item L<http://partners.adobe.com/public/developer/opentype/index_font_file.html> + +=item L<http://partners.adobe.com/public/developer/en/font/5178.PFM.pdf> + +=item L<http://opensource.adobe.com/svn/opensource/flex/sdk/trunk/modules/compiler/src/java/flex2/compiler/util/MimeMappings.java> + +=item L<http://www.adobe.com/devnet/font/pdfs/5004.AFM_Spec.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Font Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/FotoStation.pm b/ExifTool/lib/Image/ExifTool/FotoStation.pm new file mode 100644 index 0000000..8a7c21d --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/FotoStation.pm @@ -0,0 +1,261 @@ +#------------------------------------------------------------------------------ +# File: FotoStation.pm +# +# Description: Read/write FotoWare FotoStation trailer +# +# Revisions: 10/28/2006 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::FotoStation; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.04'; + +sub ProcessFotoStation($$); + +%Image::ExifTool::FotoStation::Main = ( + PROCESS_PROC => \&ProcessFotoStation, + WRITE_PROC => \&ProcessFotoStation, + GROUPS => { 2 => 'Image' }, + NOTES => q{ + The following tables define information found in the FotoWare FotoStation + trailer. + }, + 0x01 => { + Name => 'IPTC', + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::Main', + }, + }, + 0x02 => { + Name => 'SoftEdit', + SubDirectory => { + TagTable => 'Image::ExifTool::FotoStation::SoftEdit', + }, + }, + 0x03 => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Writable => 1, + RawConv => '$self->ValidateImage(\$val,$tag)', + }, + 0x04 => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Writable => 1, + RawConv => '$self->ValidateImage(\$val,$tag)', + }, +); + +# crop coordinate conversions +my %cropConv = ( + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => '"$val%"', + PrintConvInv => '$val=~tr/ %//d; $val', +); + +# soft crop record +%Image::ExifTool::FotoStation::SoftEdit = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int32s', + FIRST_ENTRY => 0, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'OriginalImageWidth', + }, + 1 => 'OriginalImageHeight', + 2 => 'ColorPlanes', + 3 => { + Name => 'XYResolution', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + }, + 4 => { + Name => 'Rotation', + Notes => q{ + rotations are stored as degrees CCW * 100, but converted to degrees CW by + ExifTool + }, + # raw value is 0, 9000, 18000 or 27000 + ValueConv => '$val ? 360 - $val / 100 : 0', + ValueConvInv => '$val ? (360 - $val) * 100 : 0', + }, + # 5 Validity Check (0x11222211) + 6 => { + Name => 'CropLeft', + %cropConv, + }, + 7 => { + Name => 'CropTop', + %cropConv, + }, + 8 => { + Name => 'CropRight', + %cropConv, + }, + 9 => { + Name => 'CropBottom', + %cropConv, + }, + 11 => { + Name => 'CropRotation', + # raw value in the range -4500 to 4500 + ValueConv => '-$val / 100', + ValueConvInv => '-$val * 100', + }, +); + +#------------------------------------------------------------------------------ +# Read/write FotoStation information in a file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this file didn't contain FotoStation information +# - updates DataPos to point to start of FotoStation information +# - updates DirLen to trailer length +sub ProcessFotoStation($$) +{ + my ($et, $dirInfo) = @_; + $et or return 1; # allow dummy access to autoload this package + my ($buff, $footer, $dirBuff, $tagTablePtr); + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my $offset = $$dirInfo{Offset} || 0; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my $rtnVal = 0; + + $$dirInfo{DirLen} = 0; # initialize returned trailer length + $raf->Seek(-$offset, 2); # seek to specified offset from end of file + + # loop through FotoStation records + for (;;) { + # look for trailer signature + last unless $raf->Seek(-10, 1) and $raf->Read($footer, 10) == 10; + my ($tag, $size, $sig) = unpack('nNN', $footer); + last unless $sig == 0xa1b2c3d4 and $size >= 10 and $raf->Seek(-$size, 1); + $size -= 10; # size of data only + last unless $raf->Read($buff, $size) == $size; + $raf->Seek(-$size, 1); + # set variables returned in dirInfo hash + $$dirInfo{DataPos} = $raf->Tell(); + $$dirInfo{DirLen} += $size + 10; + + unless ($tagTablePtr) { + $tagTablePtr = GetTagTable('Image::ExifTool::FotoStation::Main'); + SetByteOrder('MM'); # necessary for the binary data + $rtnVal = 1; # we found a valid FotoStation trailer + } + unless ($outfile) { + # print verbose trailer information + if ($verbose or $$et{HTML_DUMP}) { + $et->DumpTrailer({ + RAF => $raf, + DataPos => $$dirInfo{DataPos}, + DirLen => $size + 10, + DirName => "FotoStation_$tag", + }); + } + # extract information for this tag + $et->HandleTag($tagTablePtr, $tag, $buff, + DataPt => \$buff, + Start => 0, + Size => $size, + DataPos => $$dirInfo{DataPos}, + ); + next; + } + if ($$et{DEL_GROUP}{FotoStation}) { + $verbose and print $out " Deleting FotoStation trailer\n"; + $verbose = 0; # no more verbose messages after this + ++$$et{CHANGED}; + next; + } + # rewrite this information + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo) { + my $newVal; + my $tagName = $$tagInfo{Name}; + if ($$tagInfo{SubDirectory}) { + my %subdirInfo = ( + DataPt => \$buff, + DirStart => 0, + DirLen => $size, + DataPos => $$dirInfo{DataPos}, + DirName => $tagName, + Parent => 'FotoStation', + ); + my $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + $newVal = $et->WriteDirectory(\%subdirInfo, $subTable); + } else { + my $nvHash = $et->GetNewValueHash($tagInfo); + if ($et->IsOverwriting($nvHash) > 0) { + $newVal = $et->GetNewValue($nvHash); + $newVal = '' unless defined $newVal; + if ($verbose > 1) { + my $n = length $newVal; + print $out " - FotoStation:$tagName ($size bytes)\n" if $size; + print $out " + FotoStation:$tagName ($n bytes)\n" if $n; + } + ++$$et{CHANGED}; + } + } + if (defined $newVal) { + # note: length may be 0 here, but we write the empty record anyway + $buff = $newVal; + $size = length($newVal) + 10; + $footer = pack('nNN', $tag, $size, $sig); + } + } + if (defined $dirBuff) { + # maintain original record order + $dirBuff = $buff . $footer . $dirBuff; + } else { + $dirBuff = $buff . $footer; + } + } + # write the modified FotoStation trailer + Write($outfile, $dirBuff) or $rtnVal = -1 if $dirBuff; + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::FotoStation - Read/write FotoWare FotoStation trailer + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read and +write information from the FotoWare FotoStation trailer. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Mark Tate for information about the FotoStation data format. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/FotoStation Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/FujiFilm.pm b/ExifTool/lib/Image/ExifTool/FujiFilm.pm new file mode 100644 index 0000000..a1e5720 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/FujiFilm.pm @@ -0,0 +1,1765 @@ +#------------------------------------------------------------------------------ +# File: FujiFilm.pm +# +# Description: Read/write FujiFilm maker notes and RAF images +# +# Revisions: 11/25/2003 - P. Harvey Created +# 11/14/2007 - PH Added ability to write RAF images +# +# References: 1) http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html +# 2) http://homepage3.nifty.com/kamisaka/makernote/makernote_fuji.htm (2007/09/11) +# 3) Michael Meissner private communication +# 4) Paul Samuelson private communication (S5) +# 5) http://www.cybercom.net/~dcoffin/dcraw/ +# 6) http://forums.dpreview.com/forums/readflat.asp?forum=1012&thread=31350384 +# and http://forum.photome.de/viewtopic.php?f=2&t=353&p=742#p740 +# 7) Kai Lappalainen private communication +# 8) https://exiftool.org/forum/index.php/topic,5223.0.html +# 9) Zilvinas Brobliauskas private communication +# 10) Albert Shan private communication +# 11) https://exiftool.org/forum/index.php/topic,8377.0.html +# 12) https://exiftool.org/forum/index.php/topic,9607.0.html +# 13) https://exiftool.org/forum/index.php/topic=10481.0.html +# IB) Iliah Borg private communication (LibRaw) +# JD) Jens Duttke private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::FujiFilm; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.89'; + +sub ProcessFujiDir($$$); +sub ProcessFaceRec($$$); + +# the following RAF version numbers have been tested for writing: +# (as of ExifTool 11.70, this lookup is no longer used if the version number is numerical) +my %testedRAF = ( + '0100' => 'E550, E900, F770, S5600, S6000fd, S6500fd, HS10/HS11, HS30, S200EXR, X100, XF1, X-Pro1, X-S1, XQ2 Ver1.00, X-T100, GFX 50R, XF10', + '0101' => 'X-E1, X20 Ver1.01, X-T3', + '0102' => 'S100FS, X10 Ver1.02', + '0103' => 'IS Pro Ver1.03', + '0104' => 'S5Pro Ver1.04', + '0106' => 'S5Pro Ver1.06', + '0111' => 'S5Pro Ver1.11', + '0114' => 'S9600 Ver1.00', + '0132' => 'X-T2 Ver1.32', + '0144' => 'X100T Ver1.44', + '0159' => 'S2Pro Ver1.00', + '0200' => 'X10 Ver2.00', + '0201' => 'X-H1 Ver2.01', + '0212' => 'S3Pro Ver2.12', + '0216' => 'S3Pro Ver2.16', # (NC) + '0218' => 'S3Pro Ver2.18', + '0240' => 'X-E1 Ver2.40', + '0264' => 'F700 Ver2.00', + '0266' => 'S9500 Ver1.01', + '0261' => 'X-E1 Ver2.61', + '0269' => 'S9500 Ver1.02', + '0271' => 'S3Pro Ver2.71', # UV/IR model? + '0300' => 'X-E2', + # 0400 - expect to see this for X-T1 + '0540' => 'X-T1 Ver5.40', + '0712' => 'S5000 Ver3.00', + '0716' => 'S5000 Ver3.00', # (yes, 2 RAF versions with the same Software version) + '0Dgi' => 'X-A10 Ver1.01 and X-A3 Ver1.02', # (yes, non-digits in the firmware number) +); + +my %faceCategories = ( + Format => 'int8u', + PrintConv => { BITMASK => { + 1 => 'Partner', + 2 => 'Family', + 3 => 'Friend', + }}, +); + +# FujiFilm MakerNotes tags +%Image::ExifTool::FujiFilm::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0 => { + Name => 'Version', + Writable => 'undef', + }, + 0x0010 => { #PH/IB + Name => 'InternalSerialNumber', + Writable => 'string', + Notes => q{ + this number is unique for most models, and contains the camera model ID and + the date of manufacture + }, + # eg) "FPX20017035 592D31313034060427796060110384" + # "FPX 20495643 592D313335310701318AD010110047" (F40fd) + # HHHHHHHHHHHHyymmdd + # HHHHHHHHHHHH = camera body number in hex + # yymmdd = date of manufacture + PrintConv => q{ + if ($val =~ /^(.*?\s*)([0-9a-fA-F]*)(\d{2})(\d{2})(\d{2})(.{12})\s*\0*$/s + and $4 >= 1 and $4 <= 12 and $5 >= 1 and $5 <= 31) + { + my $yr = $3 + ($3 < 70 ? 2000 : 1900); + my $sn = pack 'H*', $2; + return "$1$sn $yr:$4:$5 $6"; + } else { + # handle a couple of models which use a slightly different format + $val =~ s/\b(592D(3[0-9])+)/pack("H*",$1).' '/e; + } + return $val; + }, + # (this inverse conversion doesn't work in all cases, so it is best to write + # the ValueConv value if an authentic internal serial number is required) + PrintConvInv => '$_=$val; s/(\S+) (19|20)(\d{2}):(\d{2}):(\d{2}) /unpack("H*",$1)."$3$4$5"/e; $_', + }, + 0x1000 => { + Name => 'Quality', + Writable => 'string', + }, + 0x1001 => { + Name => 'Sharpness', + Flags => 'PrintHex', + Writable => 'int16u', + PrintConv => { + 0x00 => '-4 (softest)', #10 + 0x01 => '-3 (very soft)', + 0x02 => '-2 (soft)', + 0x03 => '0 (normal)', + 0x04 => '+2 (hard)', + 0x05 => '+3 (very hard)', + 0x06 => '+4 (hardest)', + 0x82 => '-1 (medium soft)', #2 + 0x84 => '+1 (medium hard)', #2 + 0x8000 => 'Film Simulation', #2 + 0xffff => 'n/a', #2 + }, + }, + 0x1002 => { + Name => 'WhiteBalance', + Flags => 'PrintHex', + Writable => 'int16u', + PrintConv => { + 0x0 => 'Auto', + 0x1 => 'Auto (white priority)', #forum10890 + 0x2 => 'Auto (ambiance priority)', #forum10890 + 0x100 => 'Daylight', + 0x200 => 'Cloudy', + 0x300 => 'Daylight Fluorescent', + 0x301 => 'Day White Fluorescent', + 0x302 => 'White Fluorescent', + 0x303 => 'Warm White Fluorescent', #2/PH (S5) + 0x304 => 'Living Room Warm White Fluorescent', #2/PH (S5) + 0x400 => 'Incandescent', + 0x500 => 'Flash', #4 + 0x600 => 'Underwater', #forum6109 + 0xf00 => 'Custom', + 0xf01 => 'Custom2', #2 + 0xf02 => 'Custom3', #2 + 0xf03 => 'Custom4', #2 + 0xf04 => 'Custom5', #2 + # 0xfe0 => 'Gray Point?', #2 + 0xff0 => 'Kelvin', #4 + }, + }, + 0x1003 => { + Name => 'Saturation', + Flags => 'PrintHex', + Writable => 'int16u', + PrintConv => { + 0x0 => '0 (normal)', # # ("Color 0", ref 8) + 0x080 => '+1 (medium high)', #2 ("Color +1", ref 8) + 0x100 => '+2 (high)', # ("Color +2", ref 8) + 0x0c0 => '+3 (very high)', + 0x0e0 => '+4 (highest)', + 0x180 => '-1 (medium low)', #2 ("Color -1", ref 8) + 0x200 => 'Low', + 0x300 => 'None (B&W)', #2 + 0x301 => 'B&W Red Filter', #PH/8 + 0x302 => 'B&W Yellow Filter', #PH (X100) + 0x303 => 'B&W Green Filter', #PH/8 + 0x310 => 'B&W Sepia', #PH (X100) + 0x400 => '-2 (low)', #8 ("Color -2") + 0x4c0 => '-3 (very low)', + 0x4e0 => '-4 (lowest)', + 0x500 => 'Acros', #PH (X-Pro2) + 0x501 => 'Acros Red Filter', #PH (X-Pro2) + 0x502 => 'Acros Yellow Filter', #PH (X-Pro2) + 0x503 => 'Acros Green Filter', #PH (X-Pro2) + 0x8000 => 'Film Simulation', #2 + }, + }, + 0x1004 => { + Name => 'Contrast', + Flags => 'PrintHex', + Writable => 'int16u', + PrintConv => { + 0x0 => 'Normal', + 0x080 => 'Medium High', #2 + 0x100 => 'High', + 0x180 => 'Medium Low', #2 + 0x200 => 'Low', + 0x8000 => 'Film Simulation', #2 + }, + }, + 0x1005 => { #4 + Name => 'ColorTemperature', + Writable => 'int16u', + }, + 0x1006 => { #JD + Name => 'Contrast', + Flags => 'PrintHex', + Writable => 'int16u', + PrintConv => { + 0x0 => 'Normal', + 0x100 => 'High', + 0x300 => 'Low', + }, + }, + 0x100a => { #2 + Name => 'WhiteBalanceFineTune', + Notes => 'newer cameras should divide these values by 20', #forum10800 + Writable => 'int32s', + Count => 2, + PrintConv => 'sprintf("Red %+d, Blue %+d", split(" ", $val))', + PrintConvInv => 'my @v=($val=~/-?\d+/g);"@v"', + }, + 0x100b => { #2 + Name => 'NoiseReduction', + Flags => 'PrintHex', + Writable => 'int16u', + RawConv => '$val == 0x100 ? undef : $val', + PrintConv => { + 0x40 => 'Low', + 0x80 => 'Normal', + 0x100 => 'n/a', #PH (NC) (all X100 samples) + }, + }, + 0x100e => { #PH (X100) + Name => 'NoiseReduction', + Flags => 'PrintHex', + Writable => 'int16u', + PrintConv => { + 0x000 => '0 (normal)', # ("NR 0, ref 8) + 0x100 => '+2 (strong)', # ("NR+2, ref 8) + 0x180 => '+1 (medium strong)', #8 ("NR+1") + 0x1c0 => '+3 (very strong)', + 0x1e0 => '+4 (strongest)', + 0x200 => '-2 (weak)', # ("NR-2, ref 8) + 0x280 => '-1 (medium weak)', #8 ("NR-1") + 0x2c0 => '-3 (very weak)', #10 (-3) + 0x2e0 => '-4 (weakest)', #10 (-4) + }, + }, + 0x100f => { #PR158 + Name => 'Clarity', + Writable => 'int32s', #PH + PrintConv => { + -5000 => '-5', + -4000 => '-4', + -3000 => '-3', + -2000 => '-2', + -1000 => '-1', + 0 => '0', + 1000 => '1', + 2000 => '2', + 3000 => '3', + 4000 => '4', + 5000 => '5', + }, + }, + 0x1010 => { + Name => 'FujiFlashMode', + Writable => 'int16u', + PrintHex => 1, + PrintConv => { + 0 => 'Auto', + 1 => 'On', + 2 => 'Off', + 3 => 'Red-eye reduction', + 4 => 'External', #JD + 16 => 'Commander', + 0x8000 => 'Not Attached', #10 (X-T2) (or external flash off) + 0x8120 => 'TTL', #10 (X-T2) + 0x8320 => 'TTL Auto - Did not fire', + 0x9840 => 'Manual', #10 (X-T2) + 0x9860 => 'Flash Commander', #13 + 0x9880 => 'Multi-flash', #10 (X-T2) + 0xa920 => '1st Curtain (front)', #10 (EF-X500 flash) + 0xaa20 => 'TTL Slow - 1st Curtain (front)', #13 + 0xab20 => 'TTL Auto - 1st Curtain (front)', #13 + 0xad20 => 'TTL - Red-eye Flash - 1st Curtain (front)', #13 + 0xae20 => 'TTL Slow - Red-eye Flash - 1st Curtain (front)', #13 + 0xaf20 => 'TTL Auto - Red-eye Flash - 1st Curtain (front)', #13 + 0xc920 => '2nd Curtain (rear)', #10 + 0xca20 => 'TTL Slow - 2nd Curtain (rear)', #13 + 0xcb20 => 'TTL Auto - 2nd Curtain (rear)', #13 + 0xcd20 => 'TTL - Red-eye Flash - 2nd Curtain (rear)', #13 + 0xce20 => 'TTL Slow - Red-eye Flash - 2nd Curtain (rear)', #13 + 0xcf20 => 'TTL Auto - Red-eye Flash - 2nd Curtain (rear)', #13 + 0xe920 => 'High Speed Sync (HSS)', #10 + }, + }, + 0x1011 => { + Name => 'FlashExposureComp', #JD + Writable => 'rational64s', + }, + 0x1020 => { + Name => 'Macro', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x1021 => { + Name => 'FocusMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Manual', + 65535 => 'Movie', #forum10766 + }, + }, + 0x1022 => { #8/forum6579 + Name => 'AFMode', + Writable => 'int16u', + Notes => '"No" for manual and some AF-multi focus modes', + PrintConv => { + 0 => 'No', + 1 => 'Single Point', + 256 => 'Zone', + 512 => 'Wide/Tracking', + }, + }, + 0x102b => { + Name => 'PrioritySettings', + SubDirectory => { TagTable => 'Image::ExifTool::FujiFilm::PrioritySettings' }, + }, + 0x102d => { + Name => 'FocusSettings', + SubDirectory => { TagTable => 'Image::ExifTool::FujiFilm::FocusSettings' }, + }, + 0x102e => { + Name => 'AFCSettings', + SubDirectory => { TagTable => 'Image::ExifTool::FujiFilm::AFCSettings' }, + }, + 0x1023 => { #2 + Name => 'FocusPixel', + Writable => 'int16u', + Count => 2, + }, + 0x1030 => { + Name => 'SlowSync', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x1031 => { + Name => 'PictureMode', + Flags => 'PrintHex', + Writable => 'int16u', + PrintConv => { + 0x0 => 'Auto', # (or 'SR+' if SceneRecognition present, ref 11) + 0x1 => 'Portrait', + 0x2 => 'Landscape', + 0x3 => 'Macro', #JD + 0x4 => 'Sports', + 0x5 => 'Night Scene', + 0x6 => 'Program AE', + 0x7 => 'Natural Light', #3 + 0x8 => 'Anti-blur', #3 + 0x9 => 'Beach & Snow', #JD + 0xa => 'Sunset', #3 + 0xb => 'Museum', #3 + 0xc => 'Party', #3 + 0xd => 'Flower', #3 + 0xe => 'Text', #3 + 0xf => 'Natural Light & Flash', #3 + 0x10 => 'Beach', #3 + 0x11 => 'Snow', #3 + 0x12 => 'Fireworks', #3 + 0x13 => 'Underwater', #3 + 0x14 => 'Portrait with Skin Correction', #7 + 0x16 => 'Panorama', #PH (X100) + 0x17 => 'Night (tripod)', #7 + 0x18 => 'Pro Low-light', #7 + 0x19 => 'Pro Focus', #7 + 0x1a => 'Portrait 2', #PH (NC, T500, maybe "Smile & Shoot"?) + 0x1b => 'Dog Face Detection', #7 + 0x1c => 'Cat Face Detection', #7 + 0x30 => 'HDR', #forum10799 + 0x40 => 'Advanced Filter', + 0x100 => 'Aperture-priority AE', + 0x200 => 'Shutter speed priority AE', + 0x300 => 'Manual', + }, + }, + 0x1032 => { #8 + Name => 'ExposureCount', + Writable => 'int16u', + Notes => 'number of exposures used for this image', + }, + 0x1033 => { #6 + Name => 'EXRAuto', + Writable => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Manual', + }, + }, + 0x1034 => { #6 + Name => 'EXRMode', + Writable => 'int16u', + PrintHex => 1, + PrintConv => { + 0x100 => 'HR (High Resolution)', + 0x200 => 'SN (Signal to Noise priority)', + 0x300 => 'DR (Dynamic Range priority)', + }, + }, + 0x1040 => { #8 + Name => 'ShadowTone', + Writable => 'int32s', + PrintConv => { + -64 => '+4 (hardest)', + -48 => '+3 (very hard)', + -32 => '+2 (hard)', + -16 => '+1 (medium hard)', + 0 => '0 (normal)', + 16 => '-1 (medium soft)', + 32 => '-2 (soft)', + }, + }, + 0x1041 => { #8 + Name => 'HighlightTone', + Writable => 'int32s', + PrintConv => { + -64 => '+4 (hardest)', + -48 => '+3 (very hard)', + -32 => '+2 (hard)', + -16 => '+1 (medium hard)', + 0 => '0 (normal)', + 16 => '-1 (medium soft)', + 32 => '-2 (soft)', + }, + }, + 0x1044 => { #forum7668 + Name => 'DigitalZoom', + Writable => 'int32u', + ValueConv => '$val / 8', + ValueConvInv => '$val * 8', + }, + 0x1045 => { #12 + Name => 'LensModulationOptimizer', + Writable => 'int32u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x1047 => { #12 + Name => 'GrainEffectRoughness', + Writable => 'int32s', + PrintConv => { + 0 => 'Off', + 32 => 'Weak', + 64 => 'Strong', + }, + }, + 0x1048 => { #12 + Name => 'ColorChromeEffect', + Writable => 'int32s', + PrintConv => { + 0 => 'Off', + 32 => 'Weak', + 64 => 'Strong', + }, + }, + 0x1049 => { #12,forum14319 + Name => 'BWAdjustment', + Notes => 'positive values are warm, negative values are cool', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val + 0', + }, + 0x104b => { #forum10800,forum14319 + Name => 'BWMagentaGreen', + Notes => 'positive values are green, negative values are magenta', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val + 0', + }, + 0x104c => { #PR158 + Name => "GrainEffectSize", + Writable => 'int16u', #PH + PrintConv => { + 0 => 'Off', + 16 => 'Small', + 32 => 'Large', + }, + }, + 0x104d => { #forum9634 + Name => 'CropMode', + Writable => 'int16u', + PrintConv => { # (perhaps this is a bit mask?) + 0 => 'n/a', + 1 => 'Full-frame on GFX', #IB + 2 => 'Sports Finder Mode', # (mechanical shutter) + 4 => 'Electronic Shutter 1.25x Crop', # (continuous high) + }, + }, + 0x104e => { #forum10800 (X-Pro3) + Name => 'ColorChromeFXBlue', + Writable => 'int32s', + PrintConv => { + 0 => 'Off', + 32 => 'Weak', # (NC) + 64 => 'Strong', + }, + }, + 0x1050 => { #forum6109 + Name => 'ShutterType', + Writable => 'int16u', + PrintConv => { + 0 => 'Mechanical', + 1 => 'Electronic', + 2 => 'Electronic (long shutter speed)', #12 + 3 => 'Electronic Front Curtain', #10 + }, + }, + # 0x1100 - This may not work well for newer cameras (ref forum12682) + 0x1100 => [{ + Name => 'AutoBracketing', + Condition => '$$self{Model} eq "X-T3"', + Notes => 'X-T3 only', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'Pre-shot', #12 (Electronic Shutter and Continuous High drive mode only) + }, + },{ + Name => 'AutoBracketing', + Notes => 'other models', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'No flash & flash', #3 + 6 => 'Pixel Shift', #IB (GFX100S) + }, + }], + 0x1101 => { + Name => 'SequenceNumber', + Writable => 'int16u', + }, + 0x1103 => { + Name => 'DriveSettings', + SubDirectory => { TagTable => 'Image::ExifTool::FujiFilm::DriveSettings' }, + }, + 0x1105 => { Name => 'PixelShiftShots', Writable => 'int16u' }, #IB + 0x1106 => { Name => 'PixelShiftOffset', Writable => 'rational64s', Count => 2 }, #IB + # (0x1150-0x1152 exist only for Pro Low-light and Pro Focus PictureModes) + # 0x1150 - Pro Low-light - val=1; Pro Focus - val=2 (ref 7); HDR - val=128 (forum10799) + # 0x1151 - Pro Low-light - val=4 (number of pictures taken?); Pro Focus - val=2,3 (ref 7); HDR - val=3 (forum10799) + # 0x1152 - Pro Low-light - val=1,3,4 (stacked pictures used?); Pro Focus - val=1,2 (ref 7); HDR - val=3 (forum10799) + 0x1153 => { #forum7668 + Name => 'PanoramaAngle', + Writable => 'int16u', + }, + 0x1154 => { #forum7668 + Name => 'PanoramaDirection', + Writable => 'int16u', + PrintConv => { + 1 => 'Right', + 2 => 'Up', + 3 => 'Left', + 4 => 'Down', + }, + }, + 0x1201 => { #forum6109 + Name => 'AdvancedFilter', + Writable => 'int32u', + PrintHex => 1, + PrintConv => { + 0x10000 => 'Pop Color', + 0x20000 => 'Hi Key', + 0x30000 => 'Toy Camera', + 0x40000 => 'Miniature', + 0x50000 => 'Dynamic Tone', + 0x60001 => 'Partial Color Red', + 0x60002 => 'Partial Color Yellow', + 0x60003 => 'Partial Color Green', + 0x60004 => 'Partial Color Blue', + 0x60005 => 'Partial Color Orange', + 0x60006 => 'Partial Color Purple', + 0x70000 => 'Soft Focus', + 0x90000 => 'Low Key', + }, + }, + 0x1210 => { #2 + Name => 'ColorMode', + Writable => 'int16u', + PrintHex => 1, + PrintConv => { + 0x00 => 'Standard', + 0x10 => 'Chrome', + 0x30 => 'B & W', + }, + }, + 0x1300 => { + Name => 'BlurWarning', + Writable => 'int16u', + PrintConv => { + 0 => 'None', + 1 => 'Blur Warning', + }, + }, + 0x1301 => { + Name => 'FocusWarning', + Writable => 'int16u', + PrintConv => { + 0 => 'Good', + 1 => 'Out of focus', + }, + }, + 0x1302 => { + Name => 'ExposureWarning', + Writable => 'int16u', + PrintConv => { + 0 => 'Good', + 1 => 'Bad exposure', + }, + }, + 0x1304 => { #PH + Name => 'GEImageSize', + Condition => '$$self{Make} =~ /^GENERAL IMAGING/', + Writable => 'string', + Notes => 'GE models only', + }, + 0x1400 => { #2 + Name => 'DynamicRange', + Writable => 'int16u', + PrintConv => { + 1 => 'Standard', + 3 => 'Wide', + # the S5Pro has 100%(STD),130%,170%,230%(W1),300%,400%(W2) - PH + }, + }, + 0x1401 => { #2 (this doesn't seem to work for the X100 - PH) + Name => 'FilmMode', + Writable => 'int16u', + PrintHex => 1, + PrintConv => { + 0x000 => 'F0/Standard (Provia)', # X-Pro2 "Provia/Standard" + 0x100 => 'F1/Studio Portrait', + 0x110 => 'F1a/Studio Portrait Enhanced Saturation', + 0x120 => 'F1b/Studio Portrait Smooth Skin Tone (Astia)', # X-Pro2 "Astia/Soft" + 0x130 => 'F1c/Studio Portrait Increased Sharpness', + 0x200 => 'F2/Fujichrome (Velvia)', # X-Pro2 "Velvia/Vivid" + 0x300 => 'F3/Studio Portrait Ex', + 0x400 => 'F4/Velvia', + 0x500 => 'Pro Neg. Std', #PH (X-Pro1) + 0x501 => 'Pro Neg. Hi', #PH (X-Pro1) + 0x600 => 'Classic Chrome', #forum6109 + 0x700 => 'Eterna', #12 + 0x800 => 'Classic Negative', #forum10536 + 0x900 => 'Bleach Bypass', #forum10890 + 0xa00 => 'Nostalgic Neg', #forum12085 + 0xb00 => 'Reala ACE', #forum15190 + }, + }, + 0x1402 => { #2 + Name => 'DynamicRangeSetting', + Writable => 'int16u', + PrintHex => 1, + PrintConv => { + 0x000 => 'Auto', + 0x001 => 'Manual', #(ref http://forum.photome.de/viewtopic.php?f=2&t=353) + 0x100 => 'Standard (100%)', + 0x200 => 'Wide1 (230%)', + 0x201 => 'Wide2 (400%)', + 0x8000 => 'Film Simulation', + }, + }, + 0x1403 => { #2 (only valid for manual DR, ref 6) + Name => 'DevelopmentDynamicRange', + Writable => 'int16u', + # (shows 200, 400 or 800 for HDR200,HDR400,HDR800, ref forum10799) + }, + 0x1404 => { #2 + Name => 'MinFocalLength', + Writable => 'rational64s', + }, + 0x1405 => { #2 + Name => 'MaxFocalLength', + Writable => 'rational64s', + }, + 0x1406 => { #2 + Name => 'MaxApertureAtMinFocal', + Writable => 'rational64s', + }, + 0x1407 => { #2 + Name => 'MaxApertureAtMaxFocal', + Writable => 'rational64s', + }, + # 0x1408 - values: '0100', 'S100', 'VQ10' + # 0x1409 - values: same as 0x1408 + # 0x140a - values: 0, 1, 3, 5, 7 (bit 2=red-eye detection, ref 11/13) + 0x140b => { #6 + Name => 'AutoDynamicRange', + Writable => 'int16u', + PrintConv => '"$val%"', + PrintConvInv => '$val=~s/\s*\%$//; $val', + }, + 0x1422 => { #8 + Name => 'ImageStabilization', + Writable => 'int16u', + Count => 3, + PrintConv => [{ + 0 => 'None', + 1 => 'Optical', #PH + 2 => 'Sensor-shift', #PH (now IBIS/OIS, ref forum13708) + 3 => 'OIS Lens', #forum9815 (optical+sensor?) + 258 => 'IBIS/OIS + DIS', #forum13708 (digital on top of IBIS/OIS) + 512 => 'Digital', #PH + },{ + 0 => 'Off', + 1 => 'On (mode 1, continuous)', + 2 => 'On (mode 2, shooting only)', + }], + }, + 0x1425 => { # if present and 0x1031 PictureMode is zero, then PictureMode is SR+, not Auto (ref 11) + Name => 'SceneRecognition', + Writable => 'int16u', + PrintHex => 1, + PrintConv => { + 0 => 'Unrecognized', + 0x100 => 'Portrait Image', + 0x103 => 'Night Portrait', #forum10651 + 0x105 => 'Backlit Portrait', #forum10651 + 0x200 => 'Landscape Image', + 0x300 => 'Night Scene', + 0x400 => 'Macro', + }, + }, + 0x1431 => { #forum6109 + Name => 'Rating', + Groups => { 2 => 'Image' }, + Writable => 'int32u', + Priority => 0, + }, + 0x1436 => { #8 + Name => 'ImageGeneration', + Writable => 'int16u', + PrintConv => { + 0 => 'Original Image', + 1 => 'Re-developed from RAW', + }, + }, + 0x1438 => { #forum6579 (X-T1 firmware version 3) + Name => 'ImageCount', + Notes => 'may reset to 0 when new firmware is installed', + Writable => 'int16u', + ValueConv => '$val & 0x7fff', + ValueConvInv => '$val | 0x8000', + }, + 0x1443 => { #12 (X-T3) + Name => 'DRangePriority', + Writable => 'int16u', + PrintConv => { 0 => 'Auto', 1 => 'Fixed' }, + }, + 0x1444 => { #12 (X-T3, only exists if DRangePriority is 'Auto') + Name => 'DRangePriorityAuto', + Writable => 'int16u', + PrintConv => { + 1 => 'Weak', + 2 => 'Strong', + 3 => 'Plus', #forum10799 + }, + }, + 0x1445 => { #12 (X-T3, only exists if DRangePriority is 'Fixed') + Name => 'DRangePriorityFixed', + Writable => 'int16u', + PrintConv => { 1 => 'Weak', 2 => 'Strong' }, + }, + 0x1446 => { #12 + Name => 'FlickerReduction', + Writable => 'int32u', + # seen values: Off=0x0000, On=0x2100,0x3100 + PrintConv => q{ + my $on = ((($val >> 8) & 0x0f) == 1) ? 'On' : 'Off'; + return sprintf('%s (0x%.4x)', $on, $val); + }, + PrintConvInv => '$val=~/(0x[0-9a-f]+)/i; hex $1', + }, + 0x1447 => { Name => 'FujiModel', Writable => 'string' }, + 0x1448 => { Name => 'FujiModel2', Writable => 'string' }, + 0x144d => { Name => 'RollAngle', Writable => 'rational64s' }, #forum14319 + 0x3803 => { #forum10037 + Name => 'VideoRecordingMode', + Groups => { 2 => 'Video' }, + Writable => 'int32u', + PrintHex => 1, + PrintConv => { + 0x00 => 'Normal', + 0x10 => 'F-log', + 0x20 => 'HLG', + 0x30 => 'F-log2', #forum14384 + }, + }, + 0x3804 => { #forum10037 + Name => 'PeripheralLighting', + Groups => { 2 => 'Video' }, + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + # 0x3805 - int16u: seen 1 + 0x3806 => { #forum10037 + Name => 'VideoCompression', + Groups => { 2 => 'Video' }, + Writable => 'int16u', + PrintConv => { + 1 => 'Log GOP', + 2 => 'All Intra', + }, + }, + # 0x3810 - int32u: related to video codec (ref forum10037) + 0x3820 => { #PH (HS20EXR MOV) + Name => 'FrameRate', + Writable => 'int16u', + Groups => { 2 => 'Video' }, + }, + 0x3821 => { #PH (HS20EXR MOV) + Name => 'FrameWidth', + Writable => 'int16u', + Groups => { 2 => 'Video' }, + }, + 0x3822 => { #PH (HS20EXR MOV) + Name => 'FrameHeight', + Writable => 'int16u', + Groups => { 2 => 'Video' }, + }, + 0x3824 => { #forum10480 (X series) + Name => 'FullHDHighSpeedRec', + Writable => 'int32u', + Groups => { 2 => 'Video' }, + PrintConv => { 1 => 'Off', 2 => 'On' }, + }, + 0x4005 => { #forum9634 + Name => 'FaceElementSelected', # (could be face or eye) + Writable => 'int16u', + Count => 4, + }, + 0x4100 => { #PH + Name => 'FacesDetected', + Writable => 'int16u', + }, + 0x4103 => { #PH + Name => 'FacePositions', + Writable => 'int16u', + Count => -1, + Notes => q{ + left, top, right and bottom coordinates in full-sized image for each face + detected + }, + }, + 0x4200 => { #11 + Name => 'NumFaceElements', + Writable => 'int16u', + }, + 0x4201 => { #11 + Name => 'FaceElementTypes', + Writable => 'int8u', + Count => -1, + PrintConv => [{ + 1 => 'Face', + 2 => 'Left Eye', + 3 => 'Right Eye', + 7 => 'Body', + 8 => 'Head', + 11 => 'Bike', + 12 => 'Body of Car', + 13 => 'Front of Car', + 14 => 'Animal Body', + 15 => 'Animal Head', + 16 => 'Animal Face', + 17 => 'Animal Left Eye', + 18 => 'Animal Right Eye', + 19 => 'Bird Body', + 20 => 'Bird Head', + 21 => 'Bird Left Eye', + 22 => 'Bird Right Eye', + 23 => 'Aircraft Body', + 25 => 'Aircraft Cockpit', + 26 => 'Train Front', + 27 => 'Train Cockpit', + 28 => 'Animal Head (28)', #forum15192 + 29 => 'Animal Body (29)', #forum15192 + },'REPEAT'], + }, + # 0x4202 int8u[-1] - number of cooredinates in each rectangle? (ref 11) + 0x4203 => { #11 + Name => 'FaceElementPositions', + Writable => 'int16u', + Count => -1, + Notes => q{ + left, top, right and bottom coordinates in full-sized image for each face + element + }, + }, + # 0x4101-0x4105 - exist only if face detection active + # 0x4104 - also related to face detection (same number of entries as FacePositions) + # 0x4200 - same as 0x4100? + # 0x4203 - same as 0x4103 + # 0x4204 - same as 0x4104 + 0x4282 => { #PH + Name => 'FaceRecInfo', + SubDirectory => { TagTable => 'Image::ExifTool::FujiFilm::FaceRecInfo' }, + }, + 0x8000 => { #2 + Name => 'FileSource', + Writable => 'string', + }, + 0x8002 => { #2 + Name => 'OrderNumber', + Writable => 'int32u', + }, + 0x8003 => { #2 + Name => 'FrameNumber', + Writable => 'int16u', + }, + 0xb211 => { #PH + Name => 'Parallax', + # (value set in camera is -0.5 times this value in MPImage2... why?) + Writable => 'rational64s', + Notes => 'only found in MPImage2 of .MPO images', + }, + # 0xb212 - also found in MPIMage2 images - PH +); + +# Focus Priority settings, tag 0x102b (X-T3, ref forum 9607) +%Image::ExifTool::FujiFilm::PrioritySettings = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int16u', + WRITABLE => 1, + 0.1 => { + Name => 'AF-SPriority', + Mask => 0x000f, + PrintConv => { + 1 => 'Release', + 2 => 'Focus', + }, + }, + 0.2 => { + Name => 'AF-CPriority', + Mask => 0x00f0, + PrintConv => { + 1 => 'Release', + 2 => 'Focus', + }, + }, +); + +# Focus settings, tag 0x102d (X-T3, ref forum 9607) +%Image::ExifTool::FujiFilm::FocusSettings = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int32u', + WRITABLE => 1, + 0.1 => { + Name => 'FocusMode2', + Mask => 0x0000000f, + PrintConv => { + 0x0 => 'AF-M', + 0x1 => 'AF-S', + 0x2 => 'AF-C', + }, + }, + 0.2 => { + Name => 'PreAF', + Mask => 0x00f0, + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0.3 => { + Name => 'AFAreaMode', + Mask => 0x0f00, + PrintConv => { + 0 => 'Single Point', + 1 => 'Zone', + 2 => 'Wide/Tracking', + }, + }, + 0.4 => { + Name => 'AFAreaPointSize', + Mask => 0xf000, + PrintConv => { + 0 => 'n/a', + OTHER => sub { return $_[0] }, + }, + }, + 0.5 => { + Name => 'AFAreaZoneSize', + Mask => 0xf0000, + PrintConv => { + 0 => 'n/a', + OTHER => sub { + my ($val, $inv) = @_; + return "$val x $val" unless $inv; + $val =~ s/ ?x.*//; + return $val; + }, + }, + }, +); + +# AF-C settings, tag 0x102e (ref forum 9607) +%Image::ExifTool::FujiFilm::AFCSettings = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int32u', + WRITABLE => 1, + 0 => { + Name => 'AF-CSetting', + PrintHex => 3, + PrintSort => 1, # sort PrintConv by value + # decode in-camera preset values (X-T3) + PrintConv => { + 0x102 => 'Set 1 (multi-purpose)', # (2,0,Auto) + 0x203 => 'Set 2 (ignore obstacles)', # (3,0,Center) + 0x122 => 'Set 3 (accelerating subject)', # (2,2,Auto) + 0x010 => 'Set 4 (suddenly appearing subject)', # (0,1,Front) + 0x123 => 'Set 5 (erratic motion)', # (3,2,Auto) + OTHER => sub { + my ($val, $inv) = @_; + return $val =~ /(0x\w+)/ ? hex $1 : undef if $inv; + return sprintf 'Set 6 (custom 0x%.3x)', $val; + }, + }, + }, + 0.1 => { + Name => 'AF-CTrackingSensitivity', + Mask => 0x000f, # (values 0-4) + }, + 0.2 => { + Name => 'AF-CSpeedTrackingSensitivity', + Mask => 0x00f0, + # (values 0-2) + }, + 0.3 => { + Name => 'AF-CZoneAreaSwitching', + Mask => 0x0f00, + PrintConv => { + 0 => 'Front', + 1 => 'Auto', + 2 => 'Center', + }, + }, +); + +# DriveMode settings, tag 0x1103 (X-T3, ref forum 9607) +%Image::ExifTool::FujiFilm::DriveSettings = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int32u', + WRITABLE => 1, + 0.1 => { + Name => 'DriveMode', + Mask => 0x000000ff, + PrintConv => { + 0 => 'Single', + 1 => 'Continuous Low', # not used by X-H2S? (see forum13777) + 2 => 'Continuous High', + }, + }, + 0.2 => { + Name => 'DriveSpeed', + Mask => 0xff000000, + PrintConv => { + 0 => 'n/a', + OTHER => sub { + my ($val, $inv) = @_; + return "$val fps" unless $inv; + $val =~ s/ ?fps$//; + return $val; + }, + }, + }, +); + +# Face recognition information from FinePix F550EXR (ref PH) +%Image::ExifTool::FujiFilm::FaceRecInfo = ( + PROCESS_PROC => \&ProcessFaceRec, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + VARS => { NO_ID => 1 }, + NOTES => 'Face recognition information.', + Face1Name => { }, + Face2Name => { }, + Face3Name => { }, + Face4Name => { }, + Face5Name => { }, + Face6Name => { }, + Face7Name => { }, + Face8Name => { }, + Face1Category => { %faceCategories }, + Face2Category => { %faceCategories }, + Face3Category => { %faceCategories }, + Face4Category => { %faceCategories }, + Face5Category => { %faceCategories }, + Face6Category => { %faceCategories }, + Face7Category => { %faceCategories }, + Face8Category => { %faceCategories }, + Face1Birthday => { }, + Face2Birthday => { }, + Face3Birthday => { }, + Face4Birthday => { }, + Face5Birthday => { }, + Face6Birthday => { }, + Face7Birthday => { }, + Face8Birthday => { }, +); + +# tags in RAF images (ref 5) +%Image::ExifTool::FujiFilm::RAF = ( + PROCESS_PROC => \&ProcessFujiDir, + GROUPS => { 0 => 'RAF', 1 => 'RAF', 2 => 'Image' }, + PRIORITY => 0, # so the first RAF directory takes precedence + NOTES => q{ + FujiFilm RAF images contain meta information stored in a proprietary + FujiFilm RAF format, as well as EXIF information stored inside an embedded + JPEG preview image. The table below lists tags currently decoded from the + RAF-format information. + }, + 0x100 => { + Name => 'RawImageFullSize', + Format => 'int16u', + Groups => { 1 => 'RAF2' }, # (so RAF2 shows up in family 1 list) + Count => 2, + Notes => 'including borders', + ValueConv => 'my @v=reverse split(" ",$val);"@v"', # reverse to show width first + PrintConv => '$val=~tr/ /x/; $val', + }, + 0x110 => { + Name => 'RawImageCropTopLeft', + Format => 'int16u', + Count => 2, + Notes => 'top margin first, then left margin', + }, + 0x111 => { + Name => 'RawImageCroppedSize', + Format => 'int16u', + Count => 2, + Notes => 'including borders', + ValueConv => 'my @v=reverse split(" ",$val);"@v"', # reverse to show width first + PrintConv => '$val=~tr/ /x/; $val', + }, + 0x115 => { + Name => 'RawImageAspectRatio', + Format => 'int16u', + Count => 2, + ValueConv => 'my @v=reverse split(" ",$val);"@v"', # reverse to show width first + PrintConv => '$val=~tr/ /:/; $val', + }, + 0x121 => [ + { + Name => 'RawImageSize', + Condition => '$$self{Model} eq "FinePixS2Pro"', + Format => 'int16u', + Count => 2, + ValueConv => q{ + my @v=split(" ",$val); + $v[0]*=2, $v[1]/=2; + return "@v"; + }, + PrintConv => '$val=~tr/ /x/; $val', + }, + { + Name => 'RawImageSize', + Format => 'int16u', + Count => 2, + # values are height then width, adjusted for the layout + ValueConv => q{ + my @v=reverse split(" ",$val); + $$self{FujiLayout} and $v[0]/=2, $v[1]*=2; + return "@v"; + }, + PrintConv => '$val=~tr/ /x/; $val', + }, + ], + 0x130 => { + Name => 'FujiLayout', + Format => 'int8u', + RawConv => q{ + my ($v) = split ' ', $val; + $$self{FujiLayout} = $v & 0x80 ? 1 : 0; + return $val; + }, + }, + 0x131 => { #5 + Name => 'XTransLayout', + Description => 'X-Trans Layout', + Format => 'int8u', + Count => 36, + PrintConv => '$val =~ tr/012 /RGB/d; join " ", $val =~ /....../g', + }, + 0x2000 => { #IB + Name => 'WB_GRGBLevelsAuto', + Format => 'int16u', + Count => 4, # (ignore the duplicate values) + }, + 0x2100 => { #IB + Name => 'WB_GRGBLevelsDaylight', + Format => 'int16u', + Count => 4, + }, + 0x2200 => { #IB + Name => 'WB_GRGBLevelsCloudy', + Format => 'int16u', + Count => 4, + }, + 0x2300 => { #IB + Name => 'WB_GRGBLevelsDaylightFluor', + Format => 'int16u', + Count => 4, + }, + 0x2301 => { #IB + Name => 'WB_GRGBLevelsDayWhiteFluor', + Format => 'int16u', + Count => 4, + }, + 0x2302 => { #IB + Name => 'WB_GRGBLevelsWhiteFluorescent', + Format => 'int16u', + Count => 4, + }, + 0x2310 => { #IB + Name => 'WB_GRGBLevelsWarmWhiteFluor', + Format => 'int16u', + Count => 4, + }, + 0x2311 => { #IB + Name => 'WB_GRGBLevelsLivingRoomWarmWhiteFluor', + Format => 'int16u', + Count => 4, + }, + 0x2400 => { #IB + Name => 'WB_GRGBLevelsTungsten', + Format => 'int16u', + Count => 4, + }, + # 0x2f00 => WB_GRGBLevelsCustom: int32u count, then count * (int16u GRGBGRGB), ref IB + 0x2ff0 => { + Name => 'WB_GRGBLevels', + Format => 'int16u', + Count => 4, + }, + 0x9200 => { #Frank Markesteijn + Name => 'RelativeExposure', + Format => 'rational32s', + ValueConv => 'log($val) / log(2)', + ValueConvInv => 'exp($val * log(2))', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + # 0x9200 - relative exposure? (ref Frank Markesteijn) + 0x9650 => { #Frank Markesteijn + Name => 'RawExposureBias', + Format => 'rational32s', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 0xc000 => { + Name => 'RAFData', + SubDirectory => { + TagTable => 'Image::ExifTool::FujiFilm::RAFData', + ByteOrder => 'Little-endian', + } + }, +); + +%Image::ExifTool::FujiFilm::RAFData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0, 4, 8 ], + FIRST_ENTRY => 0, + # (FujiFilm image dimensions are REALLY confusing) + # --> this needs some cleaning up + # [Note to self: See email from Iliah Borg for more information about WB settings in this data] + 0 => { + Name => 'RawImageWidth', + Format => 'int32u', + DataMember => 'FujiWidth', + RawConv => '$val < 10000 ? $$self{FujiWidth} = $val : undef', #5 + ValueConv => '$$self{FujiLayout} ? ($val / 2) : $val', + }, + 4 => [ + { + Name => 'RawImageWidth', + Condition => 'not $$self{FujiWidth}', + Format => 'int32u', + DataMember => 'FujiWidth', + RawConv => '$val < 10000 ? $$self{FujiWidth} = $val : undef', #PH + ValueConv => '$$self{FujiLayout} ? ($val / 2) : $val', + }, + { + Name => 'RawImageHeight', + Format => 'int32u', + DataMember => 'FujiHeight', + RawConv => '$$self{FujiHeight} = $val', + ValueConv => '$$self{FujiLayout} ? ($val * 2) : $val', + }, + ], + 8 => [ + { + Name => 'RawImageWidth', + Condition => 'not $$self{FujiWidth}', + Format => 'int32u', + DataMember => 'FujiWidth', + RawConv => '$val < 10000 ? $$self{FujiWidth} = $val : undef', #PH + ValueConv => '$$self{FujiLayout} ? ($val / 2) : $val', + }, + { + Name => 'RawImageHeight', + Condition => 'not $$self{FujiHeight}', + Format => 'int32u', + DataMember => 'FujiHeight', + RawConv => '$$self{FujiHeight} = $val', + ValueConv => '$$self{FujiLayout} ? ($val * 2) : $val', + }, + ], + 12 => { + Name => 'RawImageHeight', + Condition => 'not $$self{FujiHeight}', + Format => 'int32u', + ValueConv => '$$self{FujiLayout} ? ($val * 2) : $val', + }, +); + +# TIFF IFD-format information stored in FujiFilm RAF images (ref 5) +%Image::ExifTool::FujiFilm::IFD = ( + PROCESS_PROC => \&Image::ExifTool::Exif::ProcessExif, + GROUPS => { 0 => 'RAF', 1 => 'FujiIFD', 2 => 'Image' }, + NOTES => 'Tags found in the FujiIFD information of RAF images from some models.', + 0xf000 => { + Name => 'FujiIFD', + Groups => { 1 => 'FujiIFD' }, + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::FujiFilm::IFD', + DirName => 'FujiSubIFD', + Start => '$val', + }, + }, + 0xf001 => 'RawImageFullWidth', + 0xf002 => 'RawImageFullHeight', + 0xf003 => 'BitsPerSample', + # 0xf004 - values: 4 + # 0xf005 - values: 1374, 1668 + # 0xf006 - some sort of flag indicating packed format? + 0xf007 => { + Name => 'StripOffsets', + IsOffset => 1, + IsImageData => 1, + OffsetPair => 0xf008, # point to associated byte counts + }, + 0xf008 => { + Name => 'StripByteCounts', + OffsetPair => 0xf007, # point to associated offsets + }, + # 0xf009 - values: 0, 3 + 0xf00a => 'BlackLevel', #IB + 0xf00b => 'GeometricDistortionParams', #9 (rational64s[23, 35 or 43]) + 0xf00c => 'WB_GRBLevelsStandard', #IB (GRBXGRBX; X=17 is standard illuminant A, X=21 is D65) + 0xf00d => 'WB_GRBLevelsAuto', #IB + 0xf00e => 'WB_GRBLevels', + 0xf00f => 'ChromaticAberrationParams', # (rational64s[23]) + 0xf010 => 'VignettingParams', #9 (rational64s[31 or 64]) +); + +# information found in FFMV atom of MOV videos +%Image::ExifTool::FujiFilm::FFMV = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => 'Information found in the FFMV atom of MOV videos.', + 0 => { + Name => 'MovieStreamName', + Format => 'string[34]', + }, +); + +# tags in FujiFilm QuickTime videos (ref PH) +# (similar information in Kodak,Minolta,Nikon,Olympus,Pentax and Sanyo videos) +%Image::ExifTool::FujiFilm::MOV = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => 'This information is found in MOV videos from some FujiFilm cameras.', + 0x00 => { + Name => 'Make', + Format => 'string[24]', + }, + 0x18 => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[16]', + }, + 0x2e => { # (NC) + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val ? 1 / $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', + }, +); + +#------------------------------------------------------------------------------ +# decode information from FujiFilm face recognition information +# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref +# Returns: 1 +sub ProcessFaceRec($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos} + ($$dirInfo{Base} || 0); + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + my $pos = $dirStart; + my $end = $dirStart + $dirLen; + my ($i, $n, $p, $val); + $et->VerboseDir('FaceRecInfo'); + for ($i=1; ; ++$i) { + last if $pos + 8 > $end; + my $off = Get32u($dataPt, $pos) + $dirStart; + my $len = Get32u($dataPt, $pos + 4); + last if $len==0 or $off>$end or $off+$len>$end or $len < 62; + # values observed for each offset (always zero if not listed): + # 0=5; 3=1; 4=4; 6=1; 10-13=numbers(constant for a given registered face) + # 15=16; 16=3; 18=1; 22=nameLen; 26=1; 27=16; 28=7; 30-33=nameLen(int32u) + # 34-37=nameOffset(int32u); 38=32; 39=16; 40=4; 42=1; 46=0,2,4,8(category) + # 50=33; 51=16; 52=7; 54-57=dateLen(int32u); 58-61=dateOffset(int32u) + $n = Get32u($dataPt, $off + 30); + $p = Get32u($dataPt, $off + 34) + $dirStart; + last if $p < $dirStart or $p + $n > $end; + $val = substr($$dataPt, $p, $n); + $et->HandleTag($tagTablePtr, "Face${i}Name", $val, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $p, + Size => $n, + ); + $n = Get32u($dataPt, $off + 54); + $p = Get32u($dataPt, $off + 58) + $dirStart; + last if $p < $dirStart or $p + $n > $end; + $val = substr($$dataPt, $p, $n); + $val =~ s/(\d{4})(\d{2})(\d{2})/$1:$2:$2/; + $et->HandleTag($tagTablePtr, "Face${i}Birthday", $val, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $p, + Size => $n, + ); + $et->HandleTag($tagTablePtr, "Face${i}Category", undef, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $off + 46, + Size => 1, + ); + $pos += 8; + } + return 1; +} + +#------------------------------------------------------------------------------ +# get information from FujiFilm RAF directory +# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref +# Returns: 1 if this was a valid FujiFilm directory +sub ProcessFujiDir($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $offset = $$dirInfo{DirStart}; + $raf->Seek($offset, 0) or return 0; + my ($buff, $index); + $raf->Read($buff, 4) or return 0; + my $entries = unpack 'N', $buff; + $entries < 256 or return 0; + $et->Options('Verbose') and $et->VerboseDir('Fuji', $entries); + SetByteOrder('MM'); + my $pos = $offset + 4; + for ($index=0; $index<$entries; ++$index) { + $raf->Read($buff,4) or return 0; + $pos += 4; + my ($tag, $len) = unpack 'nn', $buff; + my ($val, $vbuf); + $raf->Read($vbuf, $len) or return 0; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo and $$tagInfo{Format}) { + $val = ReadValue(\$vbuf, 0, $$tagInfo{Format}, $$tagInfo{Count}, $len); + next unless defined $val; + } elsif ($len == 4) { + # interpret unknown 4-byte values as int32u + $val = Get32u(\$vbuf, 0); + } else { + # treat other unknown values as binary data + $val = \$vbuf; + } + $et->HandleTag($tagTablePtr, $tag, $val, + Index => $index, + DataPt => \$vbuf, + DataPos => $pos, + Size => $len, + TagInfo => $tagInfo, + ); + $pos += $len; + } + return 1; +} + +#------------------------------------------------------------------------------ +# write information to FujiFilm RAW file (RAF) +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid RAF file, or -1 on write error +sub WriteRAF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($hdr, $jpeg, $outJpeg, $offset, $err, $buff); + + $raf->Read($hdr,0x94) == 0x94 or return 0; + $hdr =~ /^FUJIFILM/ or return 0; + my $ver = substr($hdr, 0x3c, 4); + $ver =~ /^\d{4}$/ or $testedRAF{$ver} or return 0; + + # get the position and size of embedded JPEG + my ($jpos, $jlen) = unpack('x84NN', $hdr); + # check to be sure the JPEG starts in the expected location + if ($jpos > 0x94 or $jpos < 0x68 or $jpos & 0x03) { + $et->Error("Unsupported or corrupted RAF image (version $ver)"); + return 1; + } + # check to make sure this version of RAF has been tested + #(removed in ExifTool 11.70) + #unless ($testedRAF{$ver}) { + # $et->Warn("RAF version $ver not yet tested", 1); + #} + # read the embedded JPEG + unless ($raf->Seek($jpos, 0) and $raf->Read($jpeg, $jlen) == $jlen) { + $et->Error('Error reading RAF meta information'); + return 1; + } + # use same write directories as JPEG + $et->InitWriteDirs('JPEG'); + # rewrite the embedded JPEG in memory + my %jpegInfo = ( + Parent => 'RAF', + RAF => new File::RandomAccess(\$jpeg), + OutFile => \$outJpeg, + ); + $$et{FILE_TYPE} = 'JPEG'; + my $success = $et->WriteJPEG(\%jpegInfo); + $$et{FILE_TYPE} = 'RAF'; + unless ($success and $outJpeg) { + $et->Error("Invalid RAF format"); + return 1; + } + return -1 if $success < 0; + + # rewrite the RAF image + SetByteOrder('MM'); + my $jpegLen = length $outJpeg; + # pad JPEG to an even 4 bytes (ALWAYS use padding as Fuji does) + my $pad = "\0" x (4 - ($jpegLen % 4)); + # update JPEG size in header (size without padding) + Set32u(length($outJpeg), \$hdr, 0x58); + # get pointer to start of the next RAF block + my $nextPtr = Get32u(\$hdr, 0x5c); + # determine the length of padding at the end of the original JPEG + my $oldPadLen = $nextPtr - ($jpos + $jlen); + if ($oldPadLen) { + if ($oldPadLen > 1000000 or $oldPadLen < 0 or + not $raf->Seek($jpos+$jlen, 0) or + $raf->Read($buff, $oldPadLen) != $oldPadLen) + { + $et->Error('Bad RAF pointer at 0x5c'); + return 1; + } + # make sure padding is only zero bytes (can be >100k for HS10) + # (have seen non-null padding in X-Pro1) + if ($buff =~ /[^\0]/) { + return 1 if $et->Error('Non-null bytes found in padding', 2); + } + } + # calculate offset difference due to change in JPEG size + my $ptrDiff = length($outJpeg) + length($pad) - ($jlen + $oldPadLen); + # update necessary pointers in header + foreach $offset (0x5c, 0x64, 0x78, 0x80) { + last if $offset >= $jpos; # some versions have a short header + my $oldPtr = Get32u(\$hdr, $offset); + next unless $oldPtr; # don't update if pointer is zero + Set32u($oldPtr + $ptrDiff, \$hdr, $offset); + } + # write the new header + my $outfile = $$dirInfo{OutFile}; + Write($outfile, substr($hdr, 0, $jpos)) or $err = 1; + # write the updated JPEG plus padding + Write($outfile, $outJpeg, $pad) or $err = 1; + # copy over the rest of the RAF image + unless ($raf->Seek($nextPtr, 0)) { + $et->Error('Error reading RAF image'); + return 1; + } + while ($raf->Read($buff, 65536)) { + Write($outfile, $buff) or $err = 1, last; + } + return $err ? -1 : 1; +} + +#------------------------------------------------------------------------------ +# get information from FujiFilm RAW file (RAF) +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 if this was a valid RAF file +sub ProcessRAF($$) +{ + my ($et, $dirInfo) = @_; + my ($buff, $jpeg, $warn, $offset); + + my $raf = $$dirInfo{RAF}; + $raf->Read($buff,0x5c) == 0x5c or return 0; + $buff =~ /^FUJIFILM/ or return 0; + my ($jpos, $jlen) = unpack('x84NN', $buff); + $jpos & 0x8000 and return 0; + $raf->Seek($jpos, 0) or return 0; + $raf->Read($jpeg, $jlen) == $jlen or return 0; + + $et->SetFileType(); + $et->FoundTag('RAFVersion', substr($buff, 0x3c, 4)); + + # extract information from embedded JPEG + my %dirInfo = ( + Parent => 'RAF', + RAF => new File::RandomAccess(\$jpeg), + ); + $$et{BASE} += $jpos; + my $rtnVal = $et->ProcessJPEG(\%dirInfo); + $$et{BASE} -= $jpos; + $et->FoundTag('PreviewImage', \$jpeg) if $rtnVal; + + # extract information from Fuji RAF and TIFF directories + my ($rafNum, $ifdNum) = ('',''); + foreach $offset (0x5c, 0x64, 0x78, 0x80) { + last if $offset >= $jpos; + unless ($raf->Seek($offset, 0) and $raf->Read($buff, 8)) { + $warn = 1; + last; + } + my ($start, $len) = unpack('N2',$buff); + next unless $start; + if ($offset == 0x64 or $offset == 0x80) { + # parse FujiIFD directory + %dirInfo = ( + RAF => $raf, + Base => $start, + ); + $$et{SET_GROUP1} = "FujiIFD$ifdNum"; + my $tagTablePtr = GetTagTable('Image::ExifTool::FujiFilm::IFD'); + # this is TIFF-format data only for some models, so no warning if it fails + unless ($et->ProcessTIFF(\%dirInfo, $tagTablePtr, \&Image::ExifTool::ProcessTIFF)) { + # do hash of image data if necessary + $et->ImageDataHash($raf, $len, 'raw') if $$et{ImageDataHash} and $raf->Seek($start,0); + } + delete $$et{SET_GROUP1}; + $ifdNum = ($ifdNum || 1) + 1; + } else { + # parse RAF directory + %dirInfo = ( + RAF => $raf, + DirStart => $start, + ); + $$et{SET_GROUP1} = "RAF$rafNum"; + my $tagTablePtr = GetTagTable('Image::ExifTool::FujiFilm::RAF'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr) or $warn = 1; + delete $$et{SET_GROUP1}; + $rafNum = ($rafNum || 1) + 1; + } + } + $warn and $et->Warn('Possibly corrupt RAF information'); + + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::FujiFilm - Read/write FujiFilm maker notes and RAF images + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +FujiFilm maker notes in EXIF information, and to read/write FujiFilm RAW +(RAF) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html> + +=item L<http://homepage3.nifty.com/kamisaka/makernote/makernote_fuji.htm> + +=item L<http://www.cybercom.net/~dcoffin/dcraw/> + +=item (...plus testing with my own FinePix 2400 Zoom) + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Michael Meissner, Paul Samuelson and Jens Duttke for help decoding +some FujiFilm information. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/FujiFilm Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/GE.pm b/ExifTool/lib/Image/ExifTool/GE.pm new file mode 100644 index 0000000..137155a --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/GE.pm @@ -0,0 +1,81 @@ +#------------------------------------------------------------------------------ +# File: GE.pm +# +# Description: General Imaging maker notes tags +# +# Revisions: 2010-12-14 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::GE; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.00'; + +sub ProcessGE2($$$); + +# GE type 1 maker notes (ref PH) +# (similar to Kodak::Type11 and Ricoh::Type2) +%Image::ExifTool::GE::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + This table lists tags found in the maker notes of some General Imaging + camera models. + }, + # 0x0104 - int32u + # 0x0200 - int32u[3] (with invalid offset of 0) + 0x0202 => { + Name => 'Macro', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + # 0x0203 - int16u: 0 + # 0x0204 - rational64u: 10/10 + # 0x0205 - rational64u: 7.249,7.34,9.47 (changes with camera model) + # 0x0206 - int16u[6] (with invalid offset of 0) + 0x0207 => { + Name => 'GEModel', + Format => 'string', + }, + 0x0300 => { + Name => 'GEMake', + Format => 'string', + }, + # 0x0500 - int16u: 0 + # 0x0600 - int32u: 0 +); + +__END__ + +=head1 NAME + +Image::ExifTool::GE - General Imaging 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 +General Imaging maker notes. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/GE Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/GIF.pm b/ExifTool/lib/Image/ExifTool/GIF.pm new file mode 100644 index 0000000..20b770c --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/GIF.pm @@ -0,0 +1,569 @@ +#------------------------------------------------------------------------------ +# File: GIF.pm +# +# Description: Read and write GIF meta information +# +# Revisions: 10/18/2005 - P. Harvey Separated from ExifTool.pm +# 05/23/2008 - P. Harvey Added ability to read/write XMP +# 10/28/2011 - P. Harvey Added ability to read/write ICC_Profile +# +# References: 1) http://www.w3.org/Graphics/GIF/spec-gif89a.txt +# 2) http://www.adobe.com/devnet/xmp/ +# 3) http://graphcomp.com/info/specs/ani_gif.html +# 4) http://www.color.org/icc_specs2.html +# 5) http://www.midiox.com/mmgif.htm +#------------------------------------------------------------------------------ + +package Image::ExifTool::GIF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.19'; + +# road map of directory locations in GIF images +my %gifMap = ( + XMP => 'GIF', + ICC_Profile => 'GIF', +); + +%Image::ExifTool::GIF::Main = ( + GROUPS => { 2 => 'Image' }, + VARS => { NO_ID => 1 }, + NOTES => q{ + This table lists information extracted from GIF images. See + L<http://www.w3.org/Graphics/GIF/spec-gif89a.txt> for the official GIF89a + specification. + }, + GIFVersion => { }, + FrameCount => { Notes => 'number of animated images' }, + Text => { Notes => 'text displayed in image' }, + Comment => { + # for documentation only -- flag as writable for the docs, but + # it won't appear in the TagLookup because there is no WRITE_PROC + Writable => 2, + }, + Duration => { + Notes => 'duration of a single animation iteration', + PrintConv => 'sprintf("%.2f s",$val)', + }, + ScreenDescriptor => { + SubDirectory => { TagTable => 'Image::ExifTool::GIF::Screen' }, + }, + Extensions => { # (for documentation only) + SubDirectory => { TagTable => 'Image::ExifTool::GIF::Extensions' }, + }, + TransparentColor => { }, +); + +# GIF89a application extensions: +%Image::ExifTool::GIF::Extensions = ( + GROUPS => { 2 => 'Image' }, + NOTES => 'Tags extracted from GIF89a application extensions.', + 'NETSCAPE/2.0' => { #3 + Name => 'Animation', + SubDirectory => { TagTable => 'Image::ExifTool::GIF::Animation' }, + }, + 'XMP Data/XMP' => { #2 + Name => 'XMP', + IncludeLengthBytes => 1, # length bytes are included in the data + Writable => 2, + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, + 'ICCRGBG1/012' => { #4 + Name => 'ICC_Profile', + Writable => 2, + SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' }, + }, + 'MIDICTRL/Jon' => { #5 + Name => 'MIDIControl', + SubDirectory => { TagTable => 'Image::ExifTool::GIF::MIDIControl' }, + }, + 'MIDISONG/Dm7' => { #5 + Name => 'MIDISong', + Groups => { 2 => 'Audio' }, + Binary => 1, + }, +); + +# GIF locical screen descriptor +%Image::ExifTool::GIF::Screen = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + NOTES => 'Information extracted from the GIF logical screen descriptor.', + 0 => { + Name => 'ImageWidth', + Format => 'int16u', + }, + 2 => { + Name => 'ImageHeight', + Format => 'int16u', + }, + 4.1 => { + Name => 'HasColorMap', + Mask => 0x80, + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 4.2 => { + Name => 'ColorResolutionDepth', + Mask => 0x70, + ValueConv => '$val + 1', + }, + 4.3 => { + Name => 'BitsPerPixel', + Mask => 0x07, + ValueConv => '$val + 1', + }, + 5 => 'BackgroundColor', + 6 => { + Name => 'PixelAspectRatio', + RawConv => '$val ? $val : undef', + ValueConv => '($val + 15) / 64', + }, +); + +# GIF Netscape 2.0 animation extension (ref 3) +%Image::ExifTool::GIF::Animation = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + NOTES => 'Information extracted from the "NETSCAPE2.0" animation extension.', + 1 => { + Name => 'AnimationIterations', + Format => 'int16u', + PrintConv => '$val ? $val : "Infinite"', + }, +); + +# GIF MIDICTRL extension (ref 5) +%Image::ExifTool::GIF::MIDIControl = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + NOTES => 'Information extracted from the MIDI control block extension.', + 0 => 'MIDIControlVersion', + 1 => 'SequenceNumber', + 2 => 'MelodicPolyphony', + 3 => 'PercussivePolyphony', + 4 => { + Name => 'ChannelUsage', + Format => 'int16u', + PrintConv => 'sprintf("0x%.4x", $val)', + }, + 6 => { + Name => 'DelayTime', + Format => 'int16u', + ValueConv => '$val / 100', + PrintConv => '$val . " s"', + }, +); + +#------------------------------------------------------------------------------ +# Process meta information in GIF image +# Inputs: 0) ExifTool object reference, 1) Directory information ref +# Returns: 1 on success, 0 if this wasn't a valid GIF file, or -1 if +# an output file was specified and a write error occurred +sub ProcessGIF($$) +{ + my ($et, $dirInfo) = @_; + my $outfile = $$dirInfo{OutFile}; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my ($a, $s, $ch, $length, $buff); + my ($err, $newComment, $setComment, $nvComment); + my ($addDirs, %doneDir); + my ($frameCount, $delayTime) = (0, 0); + + # verify this is a valid GIF file + return 0 unless $raf->Read($buff, 6) == 6 + and $buff =~ /^GIF(8[79]a)$/ + and $raf->Read($s, 7) == 7; + + my $ver = $1; + my $rtnVal = 0; + my $tagTablePtr = GetTagTable('Image::ExifTool::GIF::Main'); + SetByteOrder('II'); + + if ($outfile) { + $et->InitWriteDirs(\%gifMap, 'XMP'); # make XMP the preferred group for GIF + $addDirs = $$et{ADD_DIRS}; + # determine if we are editing the File:Comment tag + my $delGroup = $$et{DEL_GROUP}; + $newComment = $et->GetNewValue('Comment', \$nvComment); + $setComment = 1 if $nvComment or $$delGroup{File}; + # change to GIF 89a if adding comment, XMP or ICC_Profile + $buff = 'GIF89a' if $$addDirs{XMP} or $$addDirs{ICC_Profile} or defined $newComment; + Write($outfile, $buff, $s) or $err = 1; + } else { + $et->SetFileType(); # set file type + $et->HandleTag($tagTablePtr, 'GIFVersion', $ver); + $et->HandleTag($tagTablePtr, 'ScreenDescriptor', $s); + } + my $flags = Get8u(\$s, 4); + if ($flags & 0x80) { # does this image contain a color table? + # calculate color table size + $length = 3 * (2 << ($flags & 0x07)); + $raf->Read($buff, $length) == $length or return 0; # skip color table + Write($outfile, $buff) or $err = 1 if $outfile; + } +# +# loop through GIF blocks +# +Block: + for (;;) { + last unless $raf->Read($ch, 1); + # write out any new metadata now if this isn't an extension block + if ($outfile and ord($ch) != 0x21) { + # write the comment first if necessary + if (defined $newComment and $$nvComment{IsCreating}) { + # write comment marker + Write($outfile, "\x21\xfe") or $err = 1; + $verbose and print $out " + Comment = $newComment\n"; + my $len = length($newComment); + # write out the comment in 255-byte chunks, each + # chunk beginning with a length byte + my $n; + for ($n=0; $n<$len; $n+=255) { + my $size = $len - $n; + $size > 255 and $size = 255; + my $str = substr($newComment,$n,$size); + Write($outfile, pack('C',$size), $str) or $err = 1; + } + Write($outfile, "\0") or $err = 1; # empty chunk as terminator + undef $newComment; + undef $nvComment; # delete any other extraneous comments + ++$$et{CHANGED}; # increment file changed flag + } + # add application extension containing XMP block if necessary + # (this will place XMP before the first non-extension block) + if (exists $$addDirs{XMP} and not defined $doneDir{XMP}) { + $doneDir{XMP} = 1; + # write new XMP data + my $xmpTable = GetTagTable('Image::ExifTool::XMP::Main'); + my %dirInfo = ( Parent => 'GIF' ); + $verbose and print $out "Creating XMP application extension block:\n"; + $buff = $et->WriteDirectory(\%dirInfo, $xmpTable); + if (defined $buff and length $buff) { + my $lz = pack('C*',1,reverse(0..255),0); + Write($outfile, "\x21\xff\x0bXMP DataXMP", $buff, $lz) or $err = 1; + ++$doneDir{XMP}; # set to 2 to indicate we added XMP + } else { + $verbose and print $out " -> no XMP to add\n"; + } + } + # add application extension containing ICC_Profile if necessary + if (exists $$addDirs{ICC_Profile} and not defined $doneDir{ICC_Profile}) { + $doneDir{ICC_Profile} = 1; + # write new ICC_Profile + my $iccTable = GetTagTable('Image::ExifTool::ICC_Profile::Main'); + my %dirInfo = ( Parent => 'GIF' ); + $verbose and print $out "Creating ICC_Profile application extension block:\n"; + $buff = $et->WriteDirectory(\%dirInfo, $iccTable); + if (defined $buff and length $buff) { + my $pos = 0; + Write($outfile, "\x21\xff\x0bICCRGBG1012") or $err = 1; + my $len = length $buff; + while ($pos < $len) { + my $n = $len - $pos; + $n = 255 if $n > 255; + Write($outfile, chr($n), substr($buff, $pos, $n)) or $err = 1; + $pos += $n; + } + Write($outfile, "\0") or $err = 1; # write null terminator + ++$doneDir{ICC_Profile}; # set to 2 to indicate we added a new profile + } else { + $verbose and print $out " -> no ICC_Profile to add\n"; + } + } + } + if (ord($ch) == 0x2c) { + ++$frameCount; + Write($outfile, $ch) or $err = 1 if $outfile; + # image descriptor + last unless $raf->Read($buff, 8) == 8 and $raf->Read($ch, 1); + Write($outfile, $buff, $ch) or $err = 1 if $outfile; + if ($verbose) { + my ($left, $top, $w, $h) = unpack('v*', $buff); + print $out "Image: left=$left top=$top width=$w height=$h\n"; + } + if (ord($ch) & 0x80) { # does color table exist? + $length = 3 * (2 << (ord($ch) & 0x07)); + # skip the color table + last unless $raf->Read($buff, $length) == $length; + Write($outfile, $buff) or $err = 1 if $outfile; + } + # skip "LZW Minimum Code Size" byte + last unless $raf->Read($buff, 1); + Write($outfile,$buff) or $err = 1 if $outfile; + # skip image blocks + for (;;) { + last unless $raf->Read($ch, 1); + Write($outfile, $ch) or $err = 1 if $outfile; + last unless ord($ch); + last unless $raf->Read($buff, ord($ch)); + Write($outfile,$buff) or $err = 1 if $outfile; + } + next; # continue with next field + } +# last if ord($ch) == 0x3b; # normal end of GIF marker + unless (ord($ch) == 0x21) { + if ($outfile) { + Write($outfile, $ch) or $err = 1; + # copy the rest of the file + while ($raf->Read($buff, 65536)) { + Write($outfile, $buff) or $err = 1; + } + } + $rtnVal = 1; + last; + } + # get extension block type/size + last unless $raf->Read($s, 2) == 2; + # get marker and block size + ($a,$length) = unpack("C"x2, $s); + + if ($a == 0xfe) { # comment extension + + my $comment = ''; + while ($length) { + last unless $raf->Read($buff, $length) == $length; + $et->VerboseDump(\$buff) unless $outfile; + # add buffer to comment string + $comment .= $buff; + last unless $raf->Read($ch, 1); # read next block header + $length = ord($ch); # get next block size + } + last if $length; # was a read error if length isn't zero + if ($outfile) { + my $isOverwriting; + if ($setComment) { + if ($nvComment) { + $isOverwriting = $et->IsOverwriting($nvComment,$comment); + # get new comment again (may have been shifted) + $newComment = $et->GetNewValue($nvComment) if defined $newComment; + } else { + # group delete, or deleting additional comments after writing one + $isOverwriting = 1; + } + } + if ($isOverwriting) { + ++$$et{CHANGED}; # increment file changed flag + $et->VerboseValue('- Comment', $comment); + $comment = $newComment; + $et->VerboseValue('+ Comment', $comment) if defined $comment; + undef $nvComment; # just delete remaining comments + } else { + undef $setComment; # leave remaining comments alone + } + if (defined $comment) { + # write comment marker + Write($outfile, "\x21\xfe") or $err = 1; + my $len = length($comment); + # write out the comment in 255-byte chunks, each + # chunk beginning with a length byte + my $n; + for ($n=0; $n<$len; $n+=255) { + my $size = $len - $n; + $size > 255 and $size = 255; + my $str = substr($comment,$n,$size); + Write($outfile, pack('C',$size), $str) or $err = 1; + } + Write($outfile, "\0") or $err = 1; # empty chunk as terminator + } + undef $newComment; # don't write the new comment again + } else { + $rtnVal = 1; + $et->FoundTag('Comment', $comment) if $comment; + undef $comment; + # assume no more than one comment in FastScan mode + last if $et->Options('FastScan'); + } + next; + + } elsif ($a == 0xff and $length == 0x0b) { # application extension + + last unless $raf->Read($buff, $length) == $length; + my $hdr = "$ch$s$buff"; + # add "/" for readability + my $tag = substr($buff, 0, 8) . '/' . substr($buff, 8); + $tag =~ tr/\0-\x1f//d; # remove nulls and control characters + $verbose and print $out "Application Extension: $tag\n"; + + my $extTable = GetTagTable('Image::ExifTool::GIF::Extensions'); + my $extInfo = $$extTable{$tag}; + my ($subdir, $inclLen, $justCopy); + if ($extInfo) { + $subdir = $$extInfo{SubDirectory}; + $inclLen = $$extInfo{IncludeLengthBytes}; + # rewrite as-is unless this is a writable subdirectory + $justCopy = 1 if $outfile and (not $subdir or not $$extInfo{Writable}); + } else { + $justCopy = 1 if $outfile; + } + Write($outfile, $hdr) or $err = 1 if $justCopy; + + # read the extension data + my $dat = ''; + for (;;) { + $raf->Read($ch, 1) or last Block; # read next block header + $length = ord($ch) or last; # get next block size + $raf->Read($buff, $length) == $length or last Block; + Write($outfile, $ch, $buff) or $err = 1 if $justCopy; + $dat .= $inclLen ? $ch . $buff : $buff; + } + Write($outfile, "\0") if $justCopy; + + if ($subdir) { + my $dirLen = length $dat; + my $name = $$extInfo{Name}; + if ($name eq 'XMP') { + # get length of XMP without landing zone data + # (note that LZ data may not be exactly the same as what we use) + $dirLen = pos($dat) if $dat =~ /<\?xpacket end=['"][wr]['"]\?>/g; + } + my %dirInfo = ( + DataPt => \$dat, + DataLen => length $dat, + DirLen => $dirLen, + DirName => $name, + Parent => 'GIF', + ); + my $subTable = GetTagTable($$subdir{TagTable}); + if (not $outfile) { + $et->ProcessDirectory(\%dirInfo, $subTable); + } elsif ($$extInfo{Writable}) { + if ($doneDir{$name} and $doneDir{$name} > 1) { + $et->Warn("Duplicate $name block created"); + } + $buff = $et->WriteDirectory(\%dirInfo, $subTable); + if (defined $buff) { + next unless length $buff; # delete this extension if length is zero + # check for null just to be safe + $et->Error("$name contained NULL character") if $buff =~ /\0/; + $dat = $buff; + # add landing zone (without terminator, which will be added later) + $dat .= pack('C*',1,reverse(0..255)) if $$extInfo{IncludeLengthBytes}; + } # (else rewrite original data) + + $doneDir{$name} = 1; + + if ($$extInfo{IncludeLengthBytes}) { + # write data and landing zone + Write($outfile, $hdr, $dat) or $err = 1; + } else { + # write as sub-blocks + Write($outfile, $hdr) or $err = 1; + my $pos = 0; + my $len = length $dat; + while ($pos < $len) { + my $n = $len - $pos; + $n = 255 if $n > 255; + Write($outfile, chr($n), substr($dat, $pos, $n)) or $err = 1; + $pos += $n; + } + } + Write($outfile, "\0") or $err = 1; # write null terminator + } + } elsif (not $outfile) { + $et->HandleTag($extTable, $tag, $dat); + } + next; + + } elsif ($a == 0xf9 and $length == 4) { # graphic control extension + + last unless $raf->Read($buff, $length) == $length; + # sum the individual delay times + my $delay = Get16u(\$buff, 1); + $delayTime += $delay; + $verbose and printf $out "Graphic Control: delay=%.2f\n", $delay / 100; + # get transparent colour + my $bits = Get8u(\$buff, 0); + $et->HandleTag($tagTablePtr, 'TransparentColor', Get8u(\$buff,3)) if $bits & 0x01; + $raf->Seek(-$length, 1) or last; + + } elsif ($a == 0x01 and $length == 12) { # plain text extension + + last unless $raf->Read($buff, $length) == $length; + Write($outfile, $ch, $s, $buff) or $err = 1 if $outfile; + if ($verbose) { + my ($left, $top, $w, $h) = unpack('v4', $buff); + print $out "Text: left=$left top=$top width=$w height=$h\n"; + } + my $text = ''; + for (;;) { + last unless $raf->Read($ch, 1); + $length = ord($ch) or last; + last unless $raf->Read($buff, $length) == $length; + Write($outfile, $ch, $buff) or $err = 1 if $outfile; # write block + $text .= $buff; + } + Write($outfile, "\0") or $err = 1 if $outfile; # write terminator block + $et->HandleTag($tagTablePtr, 'Text', $text); + next; + } + Write($outfile, $ch, $s) or $err = 1 if $outfile; + # skip the block + while ($length) { + last unless $raf->Read($buff, $length) == $length; + Write($outfile, $buff) or $err = 1 if $outfile; + last unless $raf->Read($ch, 1); # read next block header + Write($outfile, $ch) or $err = 1 if $outfile; + $length = ord($ch); # get next block size + } + } + unless ($outfile) { + $et->HandleTag($tagTablePtr, 'FrameCount', $frameCount) if $frameCount > 1; + $et->HandleTag($tagTablePtr, 'Duration', $delayTime/100) if $delayTime; + } + + # set return value to -1 if we only had a write error + $rtnVal = -1 if $rtnVal and $err; + return $rtnVal; +} + + +1; #end + +__END__ + +=head1 NAME + +Image::ExifTool::GIF - Read and write GIF meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read and +write GIF meta information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.w3.org/Graphics/GIF/spec-gif89a.txt> + +=item L<http://www.adobe.com/devnet/xmp/> + +=item L<http://graphcomp.com/info/specs/ani_gif.html> + +=item L<http://www.color.org/icc_specs2.html> + +=item L<http://www.midiox.com/mmgif.htm> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/GIMP.pm b/ExifTool/lib/Image/ExifTool/GIMP.pm new file mode 100644 index 0000000..3ad302c --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/GIMP.pm @@ -0,0 +1,294 @@ +#------------------------------------------------------------------------------ +# File: GIMP.pm +# +# Description: Read meta information from GIMP XCF images +# +# Revisions: 2010/10/05 - P. Harvey Created +# 2018/08/21 - PH Updated to current XCF specification (v013) +# +# References: 1) GIMP source code +# 2) https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/xcf.txt +#------------------------------------------------------------------------------ + +package Image::ExifTool::GIMP; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.03'; + +sub ProcessParasites($$$); + +# GIMP XCF properties (ref 2) +%Image::ExifTool::GIMP::Main = ( + GROUPS => { 2 => 'Image' }, + VARS => { ALPHA_FIRST => 1 }, + NOTES => q{ + The GNU Image Manipulation Program (GIMP) writes these tags in its native + XCF (eXperimental Computing Facilty) images. + }, + header => { SubDirectory => { TagTable => 'Image::ExifTool::GIMP::Header' } }, + # recognized properties + # 1 - ColorMap + # 17 - SamplePoints? (doc says 17 is also "PROP_SAMPLE_POINTS"??) + 17 => { + Name => 'Compression', + Format => 'int8u', + PrintConv => { + 0 => 'None', + 1 => 'RLE Encoding', + 2 => 'Zlib', + 3 => 'Fractal', + }, + }, + # 18 - Guides + 19 => { + Name => 'Resolution', + SubDirectory => { TagTable => 'Image::ExifTool::GIMP::Resolution' }, + }, + 20 => { + Name => 'Tattoo', + Format => 'int32u', + }, + 21 => { + Name => 'Parasites', + SubDirectory => { TagTable => 'Image::ExifTool::GIMP::Parasite' }, + }, + 22 => { + Name => 'Units', + Format => 'int32u', + PrintConv => { + 1 => 'Inches', + 2 => 'mm', + 3 => 'Points', + 4 => 'Picas', + }, + }, + # 23 Paths + # 24 UserUnit + # 25 Vectors +); + +# information extracted from the XCF file header (ref 2) +%Image::ExifTool::GIMP::Header = ( + GROUPS => { 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + 9 => { + Name => 'XCFVersion', + Format => 'string[5]', + DataMember => 'XCFVersion', + RawConv => '$$self{XCFVersion} = $val', + PrintConv => { + 'file' => '0', + 'v001' => '1', + 'v002' => '2', + OTHER => sub { my $val = shift; $val =~ s/^v0*//; return $val }, + }, + }, + 14 => { Name => 'ImageWidth', Format => 'int32u' }, + 18 => { Name => 'ImageHeight', Format => 'int32u' }, + 22 => { + Name => 'ColorMode', + Format => 'int32u', + PrintConv => { + 0 => 'RGB Color', + 1 => 'Grayscale', + 2 => 'Indexed Color', + }, + }, + # 26 - [XCF 4 or later] Precision +); + +# XCF resolution data (property type 19) (ref 2) +%Image::ExifTool::GIMP::Resolution = ( + GROUPS => { 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'float', + 0 => 'XResolution', + 1 => 'YResolution', +); + +# XCF "Parasite" data (property type 21) (ref 1/PH) +%Image::ExifTool::GIMP::Parasite = ( + GROUPS => { 2 => 'Image' }, + PROCESS_PROC => \&ProcessParasites, + 'gimp-comment' => { + Name => 'Comment', + Format => 'string', + }, + 'exif-data' => { + Name => 'ExifData', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + Start => 6, # starts after "Exif\0\0" header + }, + }, + 'jpeg-exif-data' => { # (deprecated, untested) + Name => 'JPEGExifData', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + Start => 6, + }, + }, + 'iptc-data' => { # (untested) + Name => 'IPTCData', + SubDirectory => { TagTable => 'Image::ExifTool::IPTC::Main' }, + }, + 'icc-profile' => { + Name => 'ICC_Profile', + SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' }, + }, + 'icc-profile-name' => { + Name => 'ICCProfileName', + Format => 'string', + }, + 'gimp-metadata' => { + Name => 'XMP', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + Start => 10, # starts after "GIMP_XMP_1" header + }, + }, + 'gimp-image-metadata' => { + Name => 'XML', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' }, + }, + # Seen, but not yet decoded: + # gimp-image-grid + # jpeg-settings +); + +#------------------------------------------------------------------------------ +# Read information in a GIMP XCF parasite data (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessParasites($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $unknown = $et->Options('Unknown') || $et->Options('Verbose'); + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart} || 0; + my $end = length $$dataPt; + $et->VerboseDir('Parasites', undef, $end); + for (;;) { + last if $pos + 4 > $end; + my $size = Get32u($dataPt, $pos); # length of tag string + $pos += 4; + last if $pos + $size + 8 > $end; + my $tag = substr($$dataPt, $pos, $size); + $pos += $size; + $tag =~ s/\0.*//s; # trim at null terminator + # my $flags = Get32u($dataPt, $pos); (ignore flags) + $size = Get32u($dataPt, $pos + 4); # length of data + $pos += 8; + last if $pos + $size > $end; + if (not $$tagTablePtr{$tag} and $unknown) { + my $name = $tag; + $name =~ tr/-_A-Za-z0-9//dc; + $name =~ s/^gimp-//; + next unless length $name; + $name = ucfirst $name; + $name =~ s/([a-z])-([a-z])/$1\u$2/g; + $name = "GIMP-$name" unless length($name) > 1; + AddTagToTable($tagTablePtr, $tag, { Name => $name, Unknown => 1 }); + } + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => $dataPt, + Start => $pos, + Size => $size, + ); + $pos += $size; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Read information in a GIMP XCF document +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid XCF file +sub ProcessXCF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + + return 0 unless $raf->Read($buff, 26) == 26; + return 0 unless $buff =~ /^gimp xcf /; + + my $tagTablePtr = GetTagTable('Image::ExifTool::GIMP::Main'); + my $verbose = $et->Options('Verbose'); + $et->SetFileType(); + SetByteOrder('MM'); + + # process the XCF header + $et->HandleTag($tagTablePtr, 'header', $buff); + + # skip over precision for XCV version 4 or later + $raf->Seek(4, 1) if $$et{XCFVersion} =~ /^v0*(\d+)/ and $1 >= 4; + + # loop through image properties + for (;;) { + $raf->Read($buff, 8) == 8 or last; + my $tag = Get32u(\$buff, 0) or last; + my $size = Get32u(\$buff, 4); + $verbose and $et->VPrint(0, "XCF property $tag ($size bytes):\n"); + unless ($$tagTablePtr{$tag}) { + $raf->Seek($size, 1); + next; + } + $raf->Read($buff, $size) == $size or last; + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => \$buff, + DataPos => $raf->Tell() - $size, + Size => $size, + ); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::GIMP - Read meta information from GIMP XCF images + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read meta +information from GIMP (GNU Image Manipulation Program) XCF (eXperimental +Computing Facility) images. This is the native image format used by the +GIMP software. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<GIMP source code> + +=item L<http://svn.gnome.org/viewvc/gimp/trunk/devel-docs/xcf.txt?view=markup> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/GIMP Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/GPS.pm b/ExifTool/lib/Image/ExifTool/GPS.pm new file mode 100644 index 0000000..1804aa8 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/GPS.pm @@ -0,0 +1,635 @@ +#------------------------------------------------------------------------------ +# File: GPS.pm +# +# Description: EXIF GPS meta information tags +# +# Revisions: 12/09/2003 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::GPS; + +use strict; +use vars qw($VERSION); +use Image::ExifTool::Exif; + +$VERSION = '1.55'; + +my %coordConv = ( + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val)', + ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', +); + +%Image::ExifTool::GPS::Main = ( + GROUPS => { 0 => 'EXIF', 1 => 'GPS', 2 => 'Location' }, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + WRITE_GROUP => 'GPS', + 0x0000 => { + Name => 'GPSVersionID', + Writable => 'int8u', + Mandatory => 1, + Count => 4, + PrintConv => '$val =~ tr/ /./; $val', + PrintConvInv => '$val =~ tr/./ /; $val', + }, + 0x0001 => { + Name => 'GPSLatitudeRef', + Writable => 'string', + Notes => q{ + tags 0x0001-0x0006 used for camera location according to MWG 2.0. ExifTool + will also accept a number when writing GPSLatitudeRef, positive for north + latitudes or negative for south, or a string containing N, North, S or South + }, + Count => 2, + PrintConv => { + # extract N/S if written from Composite:GPSLatitude + # (also allow writing from a signed number) + OTHER => sub { + my ($val, $inv) = @_; + return undef unless $inv; + return uc $2 if $val =~ /(^|[^A-Z])([NS])(orth|outh)?\b/i; + return $1 eq '-' ? 'S' : 'N' if $val =~ /([-+]?)\d+/; + return undef; + }, + N => 'North', + S => 'South', + }, + }, + 0x0002 => { + Name => 'GPSLatitude', + Writable => 'rational64u', + Count => 3, + %coordConv, + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lat")', + }, + 0x0003 => { + Name => 'GPSLongitudeRef', + Writable => 'string', + Count => 2, + Notes => q{ + ExifTool will also accept a number when writing this tag, positive for east + longitudes or negative for west, or a string containing E, East, W or West + }, + PrintConv => { + # extract E/W if written from Composite:GPSLongitude + # (also allow writing from a signed number) + OTHER => sub { + my ($val, $inv) = @_; + return undef unless $inv; + return uc $2 if $val =~ /(^|[^A-Z])([EW])(ast|est)?\b/i; + return $1 eq '-' ? 'W' : 'E' if $val =~ /([-+]?)\d+/; + return undef; + }, + E => 'East', + W => 'West', + }, + }, + 0x0004 => { + Name => 'GPSLongitude', + Writable => 'rational64u', + Count => 3, + %coordConv, + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lon")', + }, + 0x0005 => { + Name => 'GPSAltitudeRef', + Writable => 'int8u', + Notes => q{ + ExifTool will also accept number when writing this tag, with negative + numbers indicating below sea level + }, + PrintConv => { + OTHER => sub { + my ($val, $inv) = @_; + return undef unless $inv and $val =~ /^([-+0-9])/; + return($1 eq '-' ? 1 : 0); + }, + 0 => 'Above Sea Level', # (ellipsoidal surface, Exif 3.0) + 1 => 'Below Sea Level', # (ellipsoidal surface, Exif 3.0) + # 2 => 'Above Sea Level', # (Exif 3.0) + # 3 => 'Below Sea Level', # (Exif 3.0) + }, + }, + 0x0006 => { + Name => 'GPSAltitude', + Writable => 'rational64u', + # extricate unsigned decimal number from string + ValueConvInv => '$val=~/((?=\d|\.\d)\d*(?:\.\d*)?)/ ? $1 : undef', + PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"', + PrintConvInv => '$val=~s/\s*m$//;$val', + }, + 0x0007 => { + Name => 'GPSTimeStamp', + Groups => { 2 => 'Time' }, + Writable => 'rational64u', + Count => 3, + Shift => 'Time', + Notes => q{ + UTC time of GPS fix. When writing, date is stripped off if present, and + time is adjusted to UTC if it includes a timezone + }, + ValueConv => 'Image::ExifTool::GPS::ConvertTimeStamp($val)', + ValueConvInv => '$val=~tr/:/ /;$val', + PrintConv => 'Image::ExifTool::GPS::PrintTimeStamp($val)', + # pull time out of any format date/time string + # (converting to UTC if a timezone is given) + PrintConvInv => sub { + my ($v, $et) = @_; + $v = $et->TimeNow() if lc($v) eq 'now'; + my @tz; + if ($v =~ s/([-+])(\d{1,2}):?(\d{2})\s*(DST)?$//i) { # remove timezone + my $s = $1 eq '-' ? 1 : -1; # opposite sign to convert back to UTC + my $t = $2; + @tz = ($s*$2, $s*$3); + } + # (note: we must allow '.' as a time separator, eg. '10.30.00', with is tricky due to decimal seconds) + # YYYYmmddHHMMSS[.ss] format + my @a = ($v =~ /^[^\d]*\d{4}[^\d]*\d{1,2}[^\d]*\d{1,2}[^\d]*(\d{1,2})[^\d]*(\d{2})[^\d]*(\d{2}(?:\.\d+)?)[^\d]*$/); + # HHMMSS[.ss] format + @a or @a = ($v =~ /^[^\d]*(\d{1,2})[^\d]*(\d{2})[^\d]*(\d{2}(?:\.\d+)?)[^\d]*$/); + @a or warn('Invalid time (use HH:MM:SS[.ss][+/-HH:MM|Z])'), return undef; + if (@tz) { + # adjust to UTC + $a[1] += $tz[1]; + $a[0] += $tz[0]; + while ($a[1] >= 60) { $a[1] -= 60; ++$a[0] } + while ($a[1] < 0) { $a[1] += 60; --$a[0] } + $a[0] = ($a[0] + 24) % 24; + } + return join(':', @a); + }, + }, + 0x0008 => { + Name => 'GPSSatellites', + Writable => 'string', + }, + 0x0009 => { + Name => 'GPSStatus', + Writable => 'string', + Count => 2, + PrintConv => { + A => 'Measurement Active', # Exif2.2 "Measurement in progress" + V => 'Measurement Void', # Exif2.2 "Measurement Interoperability" (WTF?) + # (meaning for 'V' taken from status code in NMEA GLL and RMC sentences) + }, + }, + 0x000a => { + Name => 'GPSMeasureMode', + Writable => 'string', + Count => 2, + PrintConv => { + 2 => '2-Dimensional Measurement', + 3 => '3-Dimensional Measurement', + }, + }, + 0x000b => { + Name => 'GPSDOP', + Description => 'GPS Dilution Of Precision', + Writable => 'rational64u', + }, + 0x000c => { + Name => 'GPSSpeedRef', + Writable => 'string', + Count => 2, + PrintConv => { + K => 'km/h', + M => 'mph', + N => 'knots', + }, + }, + 0x000d => { + Name => 'GPSSpeed', + Writable => 'rational64u', + }, + 0x000e => { + Name => 'GPSTrackRef', + Writable => 'string', + Count => 2, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + 0x000f => { + Name => 'GPSTrack', + Writable => 'rational64u', + }, + 0x0010 => { + Name => 'GPSImgDirectionRef', + Writable => 'string', + Count => 2, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + 0x0011 => { + Name => 'GPSImgDirection', + Writable => 'rational64u', + }, + 0x0012 => { + Name => 'GPSMapDatum', + Writable => 'string', + }, + 0x0013 => { + Name => 'GPSDestLatitudeRef', + Writable => 'string', + Notes => 'tags 0x0013-0x001a used for subject location according to MWG 2.0', + Count => 2, + PrintConv => { N => 'North', S => 'South' }, + }, + 0x0014 => { + Name => 'GPSDestLatitude', + Writable => 'rational64u', + Count => 3, + %coordConv, + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lat")', + }, + 0x0015 => { + Name => 'GPSDestLongitudeRef', + Writable => 'string', + Count => 2, + PrintConv => { E => 'East', W => 'West' }, + }, + 0x0016 => { + Name => 'GPSDestLongitude', + Writable => 'rational64u', + Count => 3, + %coordConv, + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lon")', + }, + 0x0017 => { + Name => 'GPSDestBearingRef', + Writable => 'string', + Count => 2, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + 0x0018 => { + Name => 'GPSDestBearing', + Writable => 'rational64u', + }, + 0x0019 => { + Name => 'GPSDestDistanceRef', + Writable => 'string', + Count => 2, + PrintConv => { + K => 'Kilometers', + M => 'Miles', + N => 'Nautical Miles', + }, + }, + 0x001a => { + Name => 'GPSDestDistance', + Writable => 'rational64u', + }, + 0x001b => { + Name => 'GPSProcessingMethod', + Writable => 'undef', + Notes => 'values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec.', + # (or QZZSS, GALILEO, GLONASS, BEIDOU or NAVIC in Exif 3.0) + RawConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,1,$tag)', + RawConvInv => 'Image::ExifTool::Exif::EncodeExifText($self,$val)', + }, + 0x001c => { + Name => 'GPSAreaInformation', + Writable => 'undef', + RawConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,1,$tag)', + RawConvInv => 'Image::ExifTool::Exif::EncodeExifText($self,$val)', + }, + 0x001d => { + Name => 'GPSDateStamp', + Groups => { 2 => 'Time' }, + Writable => 'string', + Format => 'undef', # (Casio EX-H20G uses "\0" instead of ":" as a separator) + Count => 11, + Shift => 'Time', + Notes => q{ + when writing, time is stripped off if present, after adjusting date/time to + UTC if time includes a timezone. Format is YYYY:mm:dd + }, + RawConv => '$val =~ s/\0+$//; $val', + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + ValueConvInv => '$val', + # pull date out of any format date/time string + # (and adjust to UTC if this is a full date/time/timezone value) + PrintConvInv => q{ + my $secs; + $val = $self->TimeNow() if lc($val) eq 'now'; + if ($val =~ /[-+]/ and ($secs = Image::ExifTool::GetUnixTime($val, 1))) { + $val = Image::ExifTool::ConvertUnixTime($secs); + } + return $val =~ /(\d{4}).*?(\d{2}).*?(\d{2})/ ? "$1:$2:$3" : undef; + }, + }, + 0x001e => { + Name => 'GPSDifferential', + Writable => 'int16u', + PrintConv => { + 0 => 'No Correction', + 1 => 'Differential Corrected', + }, + }, + 0x001f => { + Name => 'GPSHPositioningError', + Description => 'GPS Horizontal Positioning Error', + PrintConv => '"$val m"', + PrintConvInv => '$val=~s/\s*m$//; $val', + Writable => 'rational64u', + }, + # 0xea1c - Nokia Lumina 1020, Samsung GT-I8750, and other Windows 8 + # phones write this (padding) in GPS IFD - PH +); + +# Composite GPS tags +%Image::ExifTool::GPS::Composite = ( + GROUPS => { 2 => 'Location' }, + GPSDateTime => { + Description => 'GPS Date/Time', + Groups => { 2 => 'Time' }, + SubDoc => 1, # generate for all sub-documents + Require => { + 0 => 'GPS:GPSDateStamp', + 1 => 'GPS:GPSTimeStamp', + }, + ValueConv => '"$val[0] $val[1]Z"', + PrintConv => '$self->ConvertDateTime($val)', + }, + # Note: The following tags are used by other modules + # which must therefore require this module as necessary + GPSLatitude => { + SubDoc => 1, # generate for all sub-documents + Writable => 1, + Avoid => 1, + Priority => 1, # (necessary because Avoid sets default Priority to 0) + Require => { + 0 => 'GPS:GPSLatitude', + 1 => 'GPS:GPSLatitudeRef', + }, + WriteAlso => { + 'GPS:GPSLatitude' => '$val', + 'GPS:GPSLatitudeRef' => '(defined $val and $val < 0) ? "S" : "N"', + }, + ValueConv => '$val[1] =~ /^S/i ? -$val[0] : $val[0]', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")', + }, + GPSLongitude => { + SubDoc => 1, # generate for all sub-documents + Writable => 1, + Avoid => 1, + Priority => 1, + Require => { + 0 => 'GPS:GPSLongitude', + 1 => 'GPS:GPSLongitudeRef', + }, + WriteAlso => { + 'GPS:GPSLongitude' => '$val', + 'GPS:GPSLongitudeRef' => '(defined $val and $val < 0) ? "W" : "E"', + }, + Require => { + 0 => 'GPS:GPSLongitude', + 1 => 'GPS:GPSLongitudeRef', + }, + ValueConv => '$val[1] =~ /^W/i ? -$val[0] : $val[0]', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")', + }, + GPSAltitude => { + SubDoc => [1,3], # generate for sub-documents if Desire 1 or 3 has a chance to exist + Desire => { + 0 => 'GPS:GPSAltitude', + 1 => 'GPS:GPSAltitudeRef', + 2 => 'XMP:GPSAltitude', + 3 => 'XMP:GPSAltitudeRef', + }, + # Require either GPS:GPSAltitudeRef or XMP:GPSAltitudeRef + RawConv => '(defined $val[1] or defined $val[3]) ? $val : undef', + ValueConv => q{ + foreach (0,2) { + next unless defined $val[$_] and IsFloat($val[$_]) and defined $val[$_+1]; + return $val[$_+1] ? -abs($val[$_]) : $val[$_]; + } + return undef; + }, + PrintConv => q{ + foreach (0,2) { + next unless defined $val[$_] and IsFloat($val[$_]); + next unless defined $prt[$_+1] and $prt[$_+1] =~ /Sea/; + return((int($val[$_]*10)/10) . ' m ' . $prt[$_+1]); + } + $val = int($val * 10) / 10; + return(($val =~ s/^-// ? "$val m Below" : "$val m Above") . " Sea Level"); + }, + }, + GPSDestLatitude => { + Require => { + 0 => 'GPS:GPSDestLatitude', + 1 => 'GPS:GPSDestLatitudeRef', + }, + ValueConv => '$val[1] =~ /^S/i ? -$val[0] : $val[0]', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + GPSDestLongitude => { + SubDoc => 1, # generate for all sub-documents + Require => { + 0 => 'GPS:GPSDestLongitude', + 1 => 'GPS:GPSDestLongitudeRef', + }, + ValueConv => '$val[1] =~ /^W/i ? -$val[0] : $val[0]', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::GPS'); + +#------------------------------------------------------------------------------ +# Convert GPS timestamp value +# Inputs: 0) raw timestamp value string +# Returns: EXIF-formatted time string +sub ConvertTimeStamp($) +{ + my $val = shift; + my ($h,$m,$s) = split ' ', $val; + my $f = (($h || 0) * 60 + ($m || 0)) * 60 + ($s || 0); + $h = int($f / 3600); $f -= $h * 3600; + $m = int($f / 60); $f -= $m * 60; + my $ss = sprintf('%012.9f', $f); + if ($ss >= 60) { + $ss = '00'; + ++$m >= 60 and $m -= 60, ++$h; + } else { + $ss =~ s/\.?0+$//; # trim trailing zeros + decimal + } + return sprintf("%.2d:%.2d:%s",$h,$m,$ss); +} + +#------------------------------------------------------------------------------ +# Print GPS timestamp +# Inputs: 0) EXIF-formatted time string +# Returns: time rounded to the nearest microsecond +sub PrintTimeStamp($) +{ + my $val = shift; + return $val unless $val =~ s/:(\d{2}\.\d+)$//; + my $s = int($1 * 1000000 + 0.5) / 1000000; + $s = "0$s" if $s < 10; + return "${val}:$s"; +} + +#------------------------------------------------------------------------------ +# Convert degrees to DMS, or whatever the current settings are +# Inputs: 0) ExifTool reference, 1) Value in degrees, +# 2) format code (0=no format, 1=CoordFormat, 2=XMP format, 3=signed unformatted) +# 3) 'N' or 'E' if sign is significant and N/S/E/W should be added +# Returns: DMS string +sub ToDMS($$;$$) +{ + my ($et, $val, $doPrintConv, $ref) = @_; + my ($fmt, @fmt, $num, $sign, $rtnVal, $neg); + + unless (length $val) { + # don't convert an empty value + return $val if $doPrintConv and $doPrintConv eq '1'; # avoid hiding existing tag when extracting + return undef; # avoid writing empty value + } + if ($ref) { + if ($val < 0) { + $val = -$val; + $ref = {N => 'S', E => 'W'}->{$ref}; + $sign = '-'; + } else { + $sign = '+'; + } + $ref = " $ref" unless $doPrintConv and $doPrintConv eq '2'; + } else { + if ($doPrintConv and $doPrintConv eq '3') { + $neg = 1 if $val < 0; + $doPrintConv = 0; + } + $val = abs($val); + $ref = ''; + } + if ($doPrintConv) { + if ($doPrintConv eq '1') { + $fmt = $et->Options('CoordFormat'); + if (not $fmt) { + $fmt = q{%d deg %d' %.2f"} . $ref; + } elsif ($ref) { + # use signed value instead of reference direction if specified + $fmt =~ s/%\+/$sign%/g or $fmt .= $ref; + } else { + $fmt =~ s/%\+/%/g; # don't know sign, so don't print it + } + } else { + $fmt = "%d,%.8f$ref"; # use XMP format with 8 decimal minutes + } + # count (and capture) the format specifiers (max 3) + while ($fmt =~ /(%(%|[^%]*?[diouxXDOUeEfFgGcs]))/g) { + next if $1 eq '%%'; + push @fmt, $1; + last if @fmt >= 3; + } + $num = scalar @fmt; + } else { + $num = 3; + } + my @c; # coordinates (D) or (D,M) or (D,M,S) + $c[0] = $val; + if ($num > 1) { + $c[0] = int($c[0]); + $c[1] = ($val - $c[0]) * 60; + if ($num > 2) { + $c[1] = int($c[1]); + $c[2] = ($val - $c[0] - $c[1] / 60) * 3600; + } + # handle round-off errors to ensure minutes and seconds are + # less than 60 (eg. convert "72 59 60.00" to "73 0 0.00") + $c[-1] = $doPrintConv ? sprintf($fmt[-1], $c[-1]) : ($c[-1] . ''); + if ($c[-1] >= 60) { + $c[-1] -= 60; + ($c[-2] += 1) >= 60 and $num > 2 and $c[-2] -= 60, $c[-3] += 1; + } + } + if ($doPrintConv) { + $rtnVal = sprintf($fmt, @c); + # trim trailing zeros in XMP + $rtnVal =~ s/(\d)0+$ref$/$1$ref/ if $doPrintConv eq '2'; + } else { + $neg and map { $_ *= -1 } @c; + $rtnVal = "@c$ref"; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Convert to decimal degrees +# Inputs: 0) a string containing 1-3 decimal numbers and any amount of other garbage +# 1) true if value should be negative if coordinate ends in 'S' or 'W', +# 2) 'lat' or 'lon' to extract lat or lon from GPSCoordinates string +# Returns: Coordinate in degrees, or '' on error +sub ToDegrees($;$$) +{ + my ($val, $doSign, $coord) = @_; + return '' if $val =~ /\b(inf|undef)\b/; # ignore invalid values + # use only lat or lon part of combined GPSCoordinates inputs + if ($coord and ($coord eq 'lat' or $coord eq 'lon') and + # (two formatted coordinate values with cardinal directions, separated by a comma) + $val =~ /^(.*(?:N(?:orth)?|S(?:outh)?)),\s*(.*(?:E(?:ast)?|W(?:est)?))$/i) + { + $val = $coord eq 'lat' ? $1 : $2; + } + # extract decimal or floating point values out of any other garbage + my ($d, $m, $s) = ($val =~ /((?:[+-]?)(?=\d|\.\d)\d*(?:\.\d*)?(?:[Ee][+-]\d+)?)/g); + return '' unless defined $d; + my $deg = $d + (($m || 0) + ($s || 0)/60) / 60; + # make negative if S or W coordinate + $deg = -$deg if $doSign ? $val =~ /[^A-Z](S(outh)?|W(est)?)\s*$/i : $deg < 0; + return $deg; +} + + +1; #end + +__END__ + +=head1 NAME + +Image::ExifTool::GPS - EXIF GPS meta information 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 +GPS (Global Positioning System) meta information in EXIF data. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<Image::Info|Image::Info> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/GPS Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::Info(3pm)|Image::Info> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/GeoTiff.pm b/ExifTool/lib/Image/ExifTool/GeoTiff.pm new file mode 100644 index 0000000..cd55cef --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/GeoTiff.pm @@ -0,0 +1,2265 @@ +#------------------------------------------------------------------------------ +# File: GeoTiff.pm +# +# Description: Read GeoTiff meta information +# +# Revisions: 02/23/2004 - P. Harvey Created +# 02/25/2004 - PH Added new codes from libgeotiff-1.2.1 +# 02/01/2007 - PH Added new codes from libgeotiff-1.2.3 +# 01/22/2014 - PH Added new code from libgeotiff-1.4.0 +# 01/19/2015 - PH Added ChartTIFF tags +# +# References: 1) ftp://ftp.remotesensing.org/geotiff/libgeotiff/libgeotiff-1.1.4.tar.gz +# 2) http://www.charttiff.com/whitepapers.shtml +#------------------------------------------------------------------------------ + +package Image::ExifTool::GeoTiff; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.12'; + +# format codes for geoTiff directory entries +my %geoTiffFormat = ( + 0 => 'int16u', # (value is stored in offset, and count is 1) + 0x87af => 'int16u', # (value is stored after directory) + 0x87b0 => 'double', + 0x87b1 => 'string', +); + +my %epsg_units = ( + 9001 => 'Linear Meter', + 9002 => 'Linear Foot', + 9003 => 'Linear Foot US Survey', + 9004 => 'Linear Foot Modified American', + 9005 => 'Linear Foot Clarke', + 9006 => 'Linear Foot Indian', + 9007 => 'Linear Link', + 9008 => 'Linear Link Benoit', + 9009 => 'Linear Link Sears', + 9010 => 'Linear Chain Benoit', + 9011 => 'Linear Chain Sears', + 9012 => 'Linear Yard Sears', + 9013 => 'Linear Yard Indian', + 9014 => 'Linear Fathom', + 9015 => 'Linear Mile International Nautical', + 9101 => 'Angular Radian', + 9102 => 'Angular Degree', + 9103 => 'Angular Arc Minute', + 9104 => 'Angular Arc Second', + 9105 => 'Angular Grad', + 9106 => 'Angular Gon', + 9107 => 'Angular DMS', + 9108 => 'Angular DMS Hemisphere', + 32767 => 'User Defined', +); + +my %epsg_vertcs = ( + 0 => 'Undefined', + 5001 => 'Airy 1830 ellipsoid', + 5002 => 'Airy Modified 1849 ellipsoid', + 5003 => 'ANS ellipsoid', + 5004 => 'Bessel 1841 ellipsoid', + 5005 => 'Bessel Modified ellipsoid', + 5006 => 'Bessel Namibia ellipsoid', + 5007 => 'Clarke 1858 ellipsoid', + 5008 => 'Clarke 1866 ellipsoid', + 5010 => 'Clarke 1880 Benoit ellipsoid', + 5011 => 'Clarke 1880 IGN ellipsoid', + 5012 => 'Clarke 1880 RGS ellipsoid', + 5013 => 'Clarke 1880 Arc ellipsoid', + 5014 => 'Clarke 1880 SGA 1922 ellipsoid', + 5015 => 'Everest 1830 1937 Adjustment ellipsoid', + 5016 => 'Everest 1830 1967 Definition ellipsoid', + 5017 => 'Everest 1830 1975 Definition ellipsoid', + 5018 => 'Everest 1830 Modified ellipsoid', + 5019 => 'GRS 1980 ellipsoid', + 5020 => 'Helmert 1906 ellipsoid', + 5021 => 'INS ellipsoid', + 5022 => 'International 1924 ellipsoid', + 5023 => 'International 1967 ellipsoid', + 5024 => 'Krassowsky 1940 ellipsoid', + 5025 => 'NWL 9D ellipsoid', + 5026 => 'NWL 10D ellipsoid', + 5027 => 'Plessis 1817 ellipsoid', + 5028 => 'Struve 1860 ellipsoid', + 5029 => 'War Office ellipsoid', + 5030 => 'WGS 84 ellipsoid', + 5031 => 'GEM 10C ellipsoid', + 5032 => 'OSU86F ellipsoid', + 5033 => 'OSU91A ellipsoid', + 5101 => 'Newlyn', + 5102 => 'North American Vertical Datum 1929', + 5103 => 'North American Vertical Datum 1988', + 5104 => 'Yellow Sea 1956', + 5105 => 'Baltic Sea', + 5106 => 'Caspian Sea', + 32767 => 'User Defined', +); + +%Image::ExifTool::GeoTiff::Main = ( + GROUPS => { 2 => 'Location' }, + 1 => { + # this isn't a real GeoTiff key, but put it here + # so it will appear in tag lists since we generate it below + Name => 'GeoTiffVersion', + }, + 1024 => { + Name => 'GTModelType', + PrintConv => { + 1 => 'Projected', + 2 => 'Geographic', + 3 => 'Geocentric', + 32767 => 'User Defined', + }, + }, + 1025 => { + Name => 'GTRasterType', + PrintConv => { + 1 => 'Pixel Is Area', + 2 => 'Pixel Is Point', + 32767 => 'User Defined', + }, + }, + 1026 => 'GTCitation', + 2048 => { + Name => 'GeographicType', + PrintConv => { + # epsg_gcs + 4001 => 'Airy 1830', + 4002 => 'Airy Modified 1849', + 4003 => 'Australian National Spheroid', + 4004 => 'Bessel 1841', + 4005 => 'Bessel Modified', + 4006 => 'Bessel Namibia', + 4007 => 'Clarke 1858', + 4008 => 'Clarke 1866', + 4009 => 'Clarke 1866 Michigan', + 4010 => 'Clarke 1880 Benoit', + 4011 => 'Clarke 1880 IGN', + 4012 => 'Clarke 1880 RGS', + 4013 => 'Clarke 1880 Arc', + 4014 => 'Clarke 1880 SGA 1922', + 4015 => 'Everest 1830 1937 Adjustment', + 4016 => 'Everest 1830 1967 Definition', + 4017 => 'Everest 1830 1975 Definition', + 4018 => 'Everest 1830 Modified', + 4019 => 'GRS 1980', + 4020 => 'Helmert 1906', + 4021 => 'Indonesian National Spheroid', + 4022 => 'International 1924', + 4023 => 'International 1967', + 4024 => 'Krassowsky 1940', + 4025 => 'NWL9D', + 4026 => 'NWL10D', + 4027 => 'Plessis 1817', + 4028 => 'Struve 1860', + 4029 => 'War Office', + 4030 => 'WGS84', + 4031 => 'GEM10C', + 4032 => 'OSU86F', + 4033 => 'OSU91A', + 4034 => 'Clarke 1880', + 4035 => 'Sphere', + 4120 => 'Greek', + 4121 => 'GGRS87', + 4123 => 'KKJ', + 4124 => 'RT90', + 4133 => 'EST92', + 4815 => 'Greek Athens', + 4201 => 'Adindan', + 4202 => 'AGD66', + 4203 => 'AGD84', + 4204 => 'Ain el Abd', + 4205 => 'Afgooye', + 4206 => 'Agadez', + 4207 => 'Lisbon', + 4208 => 'Aratu', + 4209 => 'Arc 1950', + 4210 => 'Arc 1960', + 4211 => 'Batavia', + 4212 => 'Barbados', + 4213 => 'Beduaram', + 4214 => 'Beijing 1954', + 4215 => 'Belge 1950', + 4216 => 'Bermuda 1957', + 4217 => 'Bern 1898', + 4218 => 'Bogota', + 4219 => 'Bukit Rimpah', + 4220 => 'Camacupa', + 4221 => 'Campo Inchauspe', + 4222 => 'Cape', + 4223 => 'Carthage', + 4224 => 'Chua', + 4225 => 'Corrego Alegre', + 4226 => 'Cote d Ivoire', + 4227 => 'Deir ez Zor', + 4228 => 'Douala', + 4229 => 'Egypt 1907', + 4230 => 'ED50', + 4231 => 'ED87', + 4232 => 'Fahud', + 4233 => 'Gandajika 1970', + 4234 => 'Garoua', + 4235 => 'Guyane Francaise', + 4236 => 'Hu Tzu Shan', + 4237 => 'HD72', + 4238 => 'ID74', + 4239 => 'Indian 1954', + 4240 => 'Indian 1975', + 4241 => 'Jamaica 1875', + 4242 => 'JAD69', + 4243 => 'Kalianpur', + 4244 => 'Kandawala', + 4245 => 'Kertau', + 4246 => 'KOC', + 4247 => 'La Canoa', + 4248 => 'PSAD56', + 4249 => 'Lake', + 4250 => 'Leigon', + 4251 => 'Liberia 1964', + 4252 => 'Lome', + 4253 => 'Luzon 1911', + 4254 => 'Hito XVIII 1963', + 4255 => 'Herat North', + 4256 => 'Mahe 1971', + 4257 => 'Makassar', + 4258 => 'EUREF89', + 4259 => 'Malongo 1987', + 4260 => 'Manoca', + 4261 => 'Merchich', + 4262 => 'Massawa', + 4263 => 'Minna', + 4264 => 'Mhast', + 4265 => 'Monte Mario', + 4266 => 'M poraloko', + 4267 => 'NAD27', + 4268 => 'NAD Michigan', + 4269 => 'NAD83', + 4270 => 'Nahrwan 1967', + 4271 => 'Naparima 1972', + 4272 => 'GD49', + 4273 => 'NGO 1948', + 4274 => '73', + 4275 => 'NTF', + 4276 => 'NSWC 9Z 2', + 4277 => 'OSGB 1936', + 4278 => 'OSGB70', + 4279 => 'OS SN80', + 4280 => 'Padang', + 4281 => 'Palestine 1923', + 4282 => 'Pointe Noire', + 4283 => 'GDA94', + 4284 => 'Pulkovo 1942', + 4285 => 'Qatar', + 4286 => 'Qatar 1948', + 4287 => 'Qornoq', + 4288 => 'Loma Quintana', + 4289 => 'Amersfoort', + 4290 => 'RT38', + 4291 => 'SAD69', + 4292 => 'Sapper Hill 1943', + 4293 => 'Schwarzeck', + 4294 => 'Segora', + 4295 => 'Serindung', + 4296 => 'Sudan', + 4297 => 'Tananarive', + 4298 => 'Timbalai 1948', + 4299 => 'TM65', + 4300 => 'TM75', + 4301 => 'Tokyo', + 4302 => 'Trinidad 1903', + 4303 => 'TC 1948', + 4304 => 'Voirol 1875', + 4305 => 'Voirol Unifie', + 4306 => 'Bern 1938', + 4307 => 'Nord Sahara 1959', + 4308 => 'Stockholm 1938', + 4309 => 'Yacare', + 4310 => 'Yoff', + 4311 => 'Zanderij', + 4312 => 'MGI', + 4313 => 'Belge 1972', + 4314 => 'DHDN', + 4315 => 'Conakry 1905', + 4317 => 'Dealul Piscului 1970', + 4322 => 'WGS 72', + 4324 => 'WGS 72BE', + 4326 => 'WGS 84', + 4801 => 'Bern 1898 Bern', + 4802 => 'Bogota Bogota', + 4803 => 'Lisbon Lisbon', + 4804 => 'Makassar Jakarta', + 4805 => 'MGI Ferro', + 4806 => 'Monte Mario Rome', + 4807 => 'NTF Paris', + 4808 => 'Padang Jakarta', + 4809 => 'Belge 1950 Brussels', + 4810 => 'Tananarive Paris', + 4811 => 'Voirol 1875 Paris', + 4812 => 'Voirol Unifie Paris', + 4813 => 'Batavia Jakarta', + 4901 => 'ATF Paris', + 4902 => 'NDG Paris', + 32767 => 'User Defined', + }, + }, + 2049 => 'GeogCitation', + 2050 => { + Name => 'GeogGeodeticDatum', + PrintConv => { + # epsg_datum, + 6001 => 'Airy 1830', + 6002 => 'Airy Modified 1849', + 6003 => 'Australian National Spheroid', + 6004 => 'Bessel 1841', + 6005 => 'Bessel Modified', + 6006 => 'Bessel Namibia', + 6007 => 'Clarke 1858', + 6008 => 'Clarke 1866', + 6009 => 'Clarke 1866 Michigan', + 6010 => 'Clarke 1880 Benoit', + 6011 => 'Clarke 1880 IGN', + 6012 => 'Clarke 1880 RGS', + 6013 => 'Clarke 1880 Arc', + 6014 => 'Clarke 1880 SGA 1922', + 6015 => 'Everest 1830 1937 Adjustment', + 6016 => 'Everest 1830 1967 Definition', + 6017 => 'Everest 1830 1975 Definition', + 6018 => 'Everest 1830 Modified', + 6019 => 'GRS 1980', + 6020 => 'Helmert 1906', + 6021 => 'Indonesian National Spheroid', + 6022 => 'International 1924', + 6023 => 'International 1967', + 6024 => 'Krassowsky 1960', + 6025 => 'NWL9D', + 6026 => 'NWL10D', + 6027 => 'Plessis 1817', + 6028 => 'Struve 1860', + 6029 => 'War Office', + 6030 => 'WGS84', + 6031 => 'GEM10C', + 6032 => 'OSU86F', + 6033 => 'OSU91A', + 6034 => 'Clarke 1880', + 6035 => 'Sphere', + 6201 => 'Adindan', + 6202 => 'Australian Geodetic Datum 1966', + 6203 => 'Australian Geodetic Datum 1984', + 6204 => 'Ain el Abd 1970', + 6205 => 'Afgooye', + 6206 => 'Agadez', + 6207 => 'Lisbon', + 6208 => 'Aratu', + 6209 => 'Arc 1950', + 6210 => 'Arc 1960', + 6211 => 'Batavia', + 6212 => 'Barbados', + 6213 => 'Beduaram', + 6214 => 'Beijing 1954', + 6215 => 'Reseau National Belge 1950', + 6216 => 'Bermuda 1957', + 6217 => 'Bern 1898', + 6218 => 'Bogota', + 6219 => 'Bukit Rimpah', + 6220 => 'Camacupa', + 6221 => 'Campo Inchauspe', + 6222 => 'Cape', + 6223 => 'Carthage', + 6224 => 'Chua', + 6225 => 'Corrego Alegre', + 6226 => 'Cote d Ivoire', + 6227 => 'Deir ez Zor', + 6228 => 'Douala', + 6229 => 'Egypt 1907', + 6230 => 'European Datum 1950', + 6231 => 'European Datum 1987', + 6232 => 'Fahud', + 6233 => 'Gandajika 1970', + 6234 => 'Garoua', + 6235 => 'Guyane Francaise', + 6236 => 'Hu Tzu Shan', + 6237 => 'Hungarian Datum 1972', + 6238 => 'Indonesian Datum 1974', + 6239 => 'Indian 1954', + 6240 => 'Indian 1975', + 6241 => 'Jamaica 1875', + 6242 => 'Jamaica 1969', + 6243 => 'Kalianpur', + 6244 => 'Kandawala', + 6245 => 'Kertau', + 6246 => 'Kuwait Oil Company', + 6247 => 'La Canoa', + 6248 => 'Provisional S American Datum 1956', + 6249 => 'Lake', + 6250 => 'Leigon', + 6251 => 'Liberia 1964', + 6252 => 'Lome', + 6253 => 'Luzon 1911', + 6254 => 'Hito XVIII 1963', + 6255 => 'Herat North', + 6256 => 'Mahe 1971', + 6257 => 'Makassar', + 6258 => 'European Reference System 1989', + 6259 => 'Malongo 1987', + 6260 => 'Manoca', + 6261 => 'Merchich', + 6262 => 'Massawa', + 6263 => 'Minna', + 6264 => 'Mhast', + 6265 => 'Monte Mario', + 6266 => 'M poraloko', + 6267 => 'North American Datum 1927', + 6268 => 'NAD Michigan', + 6269 => 'North American Datum 1983', + 6270 => 'Nahrwan 1967', + 6271 => 'Naparima 1972', + 6272 => 'New Zealand Geodetic Datum 1949', + 6273 => 'NGO 1948', + 6274 => 'Datum 73', + 6275 => 'Nouvelle Triangulation Francaise', + 6276 => 'NSWC 9Z 2', + 6277 => 'OSGB 1936', + 6278 => 'OSGB 1970 SN', + 6279 => 'OS SN 1980', + 6280 => 'Padang 1884', + 6281 => 'Palestine 1923', + 6282 => 'Pointe Noire', + 6283 => 'Geocentric Datum of Australia 1994', + 6284 => 'Pulkovo 1942', + 6285 => 'Qatar', + 6286 => 'Qatar 1948', + 6287 => 'Qornoq', + 6288 => 'Loma Quintana', + 6289 => 'Amersfoort', + 6290 => 'RT38', + 6291 => 'South American Datum 1969', + 6292 => 'Sapper Hill 1943', + 6293 => 'Schwarzeck', + 6294 => 'Segora', + 6295 => 'Serindung', + 6296 => 'Sudan', + 6297 => 'Tananarive 1925', + 6298 => 'Timbalai 1948', + 6299 => 'TM65', + 6300 => 'TM75', + 6301 => 'Tokyo', + 6302 => 'Trinidad 1903', + 6303 => 'Trucial Coast 1948', + 6304 => 'Voirol 1875', + 6305 => 'Voirol Unifie 1960', + 6306 => 'Bern 1938', + 6307 => 'Nord Sahara 1959', + 6308 => 'Stockholm 1938', + 6309 => 'Yacare', + 6310 => 'Yoff', + 6311 => 'Zanderij', + 6312 => 'Militar Geographische Institut', + 6313 => 'Reseau National Belge 1972', + 6314 => 'Deutsche Hauptdreiecksnetz', + 6315 => 'Conakry 1905', + 6317 => 'Dealul Piscului 1970', + 6322 => 'WGS72', + 6324 => 'WGS72 Transit Broadcast Ephemeris', + 6326 => 'WGS84', + 6901 => 'Ancienne Triangulation Francaise', + 6902 => 'Nord de Guerre', + 32767 => 'User Defined', + }, + }, + 2051 => { + Name => 'GeogPrimeMeridian', + PrintConv => { + # epsg_pm + 8901 => 'Greenwich', + 8902 => 'Lisbon', + 8903 => 'Paris', + 8904 => 'Bogota', + 8905 => 'Madrid', + 8906 => 'Rome', + 8907 => 'Bern', + 8908 => 'Jakarta', + 8909 => 'Ferro', + 8910 => 'Brussels', + 8911 => 'Stockholm', + 32767 => 'User Defined', + }, + }, + 2052 => { + Name => 'GeogLinearUnits', + SeparateTable => 'Units', + PrintConv => \%epsg_units, + }, + 2053 => 'GeogLinearUnitSize', + 2054 => { + Name => 'GeogAngularUnits', + SeparateTable => 'Units', + PrintConv => \%epsg_units, + }, + 2055 => 'GeogAngularUnitSize', + 2056 => { + Name => 'GeogEllipsoid', + PrintConv => { + # epsg_ellipse + 7001 => 'Airy 1830', + 7002 => 'Airy Modified 1849', + 7003 => 'Australian National Spheroid', + 7004 => 'Bessel 1841', + 7005 => 'Bessel Modified', + 7006 => 'Bessel Namibia', + 7007 => 'Clarke 1858', + 7008 => 'Clarke 1866', + 7009 => 'Clarke 1866 Michigan', + 7010 => 'Clarke 1880 Benoit', + 7011 => 'Clarke 1880 IGN', + 7012 => 'Clarke 1880 RGS', + 7013 => 'Clarke 1880 Arc', + 7014 => 'Clarke 1880 SGA 1922', + 7015 => 'Everest 1830 1937 Adjustment', + 7016 => 'Everest 1830 1967 Definition', + 7017 => 'Everest 1830 1975 Definition', + 7018 => 'Everest 1830 Modified', + 7019 => 'GRS 1980', + 7020 => 'Helmert 1906', + 7021 => 'Indonesian National Spheroid', + 7022 => 'International 1924', + 7023 => 'International 1967', + 7024 => 'Krassowsky 1940', + 7025 => 'NWL 9D', + 7026 => 'NWL 10D', + 7027 => 'Plessis 1817', + 7028 => 'Struve 1860', + 7029 => 'War Office', + 7030 => 'WGS 84', + 7031 => 'GEM 10C', + 7032 => 'OSU86F', + 7033 => 'OSU91A', + 7034 => 'Clarke 1880', + 7035 => 'Sphere', + 32767 => 'User Defined', + }, + }, + 2057 => 'GeogSemiMajorAxis', + 2058 => 'GeogSemiMinorAxis', + 2059 => 'GeogInvFlattening', + 2060 => { + Name => 'GeogAzimuthUnits', + SeparateTable => 'Units', + PrintConv => \%epsg_units, + }, + 2061 => 'GeogPrimeMeridianLong', + 2062 => 'GeogToWGS84', + 3072 => { + Name => 'ProjectedCSType', + PrintConv => { + # epsg_pcs + 2100 => 'GGRS87 Greek Grid', + 2176 => 'ETRS89 Poland CS2000 zone 5', + 2177 => 'ETRS89 Poland CS2000 zone 6', + 2177 => 'ETRS89 Poland CS2000 zone 7', + 2178 => 'ETRS89 Poland CS2000 zone 8', + 2180 => 'ETRS89 Poland CS92', + 2204 => 'NAD27 Tennessee', + 2205 => 'NAD83 Kentucky North', + 2391 => 'KKJ Finland zone 1', + 2392 => 'KKJ Finland zone 2', + 2393 => 'KKJ Finland zone 3', + 2394 => 'KKJ Finland zone 4', + 2400 => 'RT90 2 5 gon W', + 2600 => 'Lietuvos Koordinoei Sistema 1994', + 3053 => 'Hjorsey 1955 Lambert', + 3057 => 'ISN93 Lambert 1993', + 3300 => 'Estonian Coordinate System of 1992', + 3786 => 'Popular Visualisation CRS / Mercator', #PH (NC) + 3857 => 'WGS 84 / Pseudo-Mercator', #PH (NC) + 20137 => 'Adindan UTM zone 37N', + 20138 => 'Adindan UTM zone 38N', + 20248 => 'AGD66 AMG zone 48', + 20249 => 'AGD66 AMG zone 49', + 20250 => 'AGD66 AMG zone 50', + 20251 => 'AGD66 AMG zone 51', + 20252 => 'AGD66 AMG zone 52', + 20253 => 'AGD66 AMG zone 53', + 20254 => 'AGD66 AMG zone 54', + 20255 => 'AGD66 AMG zone 55', + 20256 => 'AGD66 AMG zone 56', + 20257 => 'AGD66 AMG zone 57', + 20258 => 'AGD66 AMG zone 58', + 20348 => 'AGD84 AMG zone 48', + 20349 => 'AGD84 AMG zone 49', + 20350 => 'AGD84 AMG zone 50', + 20351 => 'AGD84 AMG zone 51', + 20352 => 'AGD84 AMG zone 52', + 20353 => 'AGD84 AMG zone 53', + 20354 => 'AGD84 AMG zone 54', + 20355 => 'AGD84 AMG zone 55', + 20356 => 'AGD84 AMG zone 56', + 20357 => 'AGD84 AMG zone 57', + 20358 => 'AGD84 AMG zone 58', + 20437 => 'Ain el Abd UTM zone 37N', + 20438 => 'Ain el Abd UTM zone 38N', + 20439 => 'Ain el Abd UTM zone 39N', + 20499 => 'Ain el Abd Bahrain Grid', + 20538 => 'Afgooye UTM zone 38N', + 20539 => 'Afgooye UTM zone 39N', + 20700 => 'Lisbon Portuguese Grid', + 20822 => 'Aratu UTM zone 22S', + 20823 => 'Aratu UTM zone 23S', + 20824 => 'Aratu UTM zone 24S', + 20973 => 'Arc 1950 Lo13', + 20975 => 'Arc 1950 Lo15', + 20977 => 'Arc 1950 Lo17', + 20979 => 'Arc 1950 Lo19', + 20981 => 'Arc 1950 Lo21', + 20983 => 'Arc 1950 Lo23', + 20985 => 'Arc 1950 Lo25', + 20987 => 'Arc 1950 Lo27', + 20989 => 'Arc 1950 Lo29', + 20991 => 'Arc 1950 Lo31', + 20993 => 'Arc 1950 Lo33', + 20995 => 'Arc 1950 Lo35', + 21100 => 'Batavia NEIEZ', + 21148 => 'Batavia UTM zone 48S', + 21149 => 'Batavia UTM zone 49S', + 21150 => 'Batavia UTM zone 50S', + 21413 => 'Beijing Gauss zone 13', + 21414 => 'Beijing Gauss zone 14', + 21415 => 'Beijing Gauss zone 15', + 21416 => 'Beijing Gauss zone 16', + 21417 => 'Beijing Gauss zone 17', + 21418 => 'Beijing Gauss zone 18', + 21419 => 'Beijing Gauss zone 19', + 21420 => 'Beijing Gauss zone 20', + 21421 => 'Beijing Gauss zone 21', + 21422 => 'Beijing Gauss zone 22', + 21423 => 'Beijing Gauss zone 23', + 21473 => 'Beijing Gauss 13N', + 21474 => 'Beijing Gauss 14N', + 21475 => 'Beijing Gauss 15N', + 21476 => 'Beijing Gauss 16N', + 21477 => 'Beijing Gauss 17N', + 21478 => 'Beijing Gauss 18N', + 21479 => 'Beijing Gauss 19N', + 21480 => 'Beijing Gauss 20N', + 21481 => 'Beijing Gauss 21N', + 21482 => 'Beijing Gauss 22N', + 21483 => 'Beijing Gauss 23N', + 21500 => 'Belge Lambert 50', + 21790 => 'Bern 1898 Swiss Old', + 21817 => 'Bogota UTM zone 17N', + 21818 => 'Bogota UTM zone 18N', + 21891 => 'Bogota Colombia 3W', + 21892 => 'Bogota Colombia Bogota', + 21893 => 'Bogota Colombia 3E', + 21894 => 'Bogota Colombia 6E', + 22032 => 'Camacupa UTM 32S', + 22033 => 'Camacupa UTM 33S', + 22191 => 'C Inchauspe Argentina 1', + 22192 => 'C Inchauspe Argentina 2', + 22193 => 'C Inchauspe Argentina 3', + 22194 => 'C Inchauspe Argentina 4', + 22195 => 'C Inchauspe Argentina 5', + 22196 => 'C Inchauspe Argentina 6', + 22197 => 'C Inchauspe Argentina 7', + 22332 => 'Carthage UTM zone 32N', + 22391 => 'Carthage Nord Tunisie', + 22392 => 'Carthage Sud Tunisie', + 22523 => 'Corrego Alegre UTM 23S', + 22524 => 'Corrego Alegre UTM 24S', + 22832 => 'Douala UTM zone 32N', + 22992 => 'Egypt 1907 Red Belt', + 22993 => 'Egypt 1907 Purple Belt', + 22994 => 'Egypt 1907 Ext Purple', + 23028 => 'ED50 UTM zone 28N', + 23029 => 'ED50 UTM zone 29N', + 23030 => 'ED50 UTM zone 30N', + 23031 => 'ED50 UTM zone 31N', + 23032 => 'ED50 UTM zone 32N', + 23033 => 'ED50 UTM zone 33N', + 23034 => 'ED50 UTM zone 34N', + 23035 => 'ED50 UTM zone 35N', + 23036 => 'ED50 UTM zone 36N', + 23037 => 'ED50 UTM zone 37N', + 23038 => 'ED50 UTM zone 38N', + 23239 => 'Fahud UTM zone 39N', + 23240 => 'Fahud UTM zone 40N', + 23433 => 'Garoua UTM zone 33N', + 23700 => 'HD72 EOV', + 23846 => 'ID74 UTM zone 46N', + 23847 => 'ID74 UTM zone 47N', + 23848 => 'ID74 UTM zone 48N', + 23849 => 'ID74 UTM zone 49N', + 23850 => 'ID74 UTM zone 50N', + 23851 => 'ID74 UTM zone 51N', + 23852 => 'ID74 UTM zone 52N', + 23853 => 'ID74 UTM zone 53N', + 23886 => 'ID74 UTM zone 46S', + 23887 => 'ID74 UTM zone 47S', + 23888 => 'ID74 UTM zone 48S', + 23889 => 'ID74 UTM zone 49S', + 23890 => 'ID74 UTM zone 50S', + 23891 => 'ID74 UTM zone 51S', + 23892 => 'ID74 UTM zone 52S', + 23893 => 'ID74 UTM zone 53S', + 23894 => 'ID74 UTM zone 54S', + 23947 => 'Indian 1954 UTM 47N', + 23948 => 'Indian 1954 UTM 48N', + 24047 => 'Indian 1975 UTM 47N', + 24048 => 'Indian 1975 UTM 48N', + 24100 => 'Jamaica 1875 Old Grid', + 24200 => 'JAD69 Jamaica Grid', + 24370 => 'Kalianpur India 0', + 24371 => 'Kalianpur India I', + 24372 => 'Kalianpur India IIa', + 24373 => 'Kalianpur India IIIa', + 24374 => 'Kalianpur India IVa', + 24382 => 'Kalianpur India IIb', + 24383 => 'Kalianpur India IIIb', + 24384 => 'Kalianpur India IVb', + 24500 => 'Kertau Singapore Grid', + 24547 => 'Kertau UTM zone 47N', + 24548 => 'Kertau UTM zone 48N', + 24720 => 'La Canoa UTM zone 20N', + 24721 => 'La Canoa UTM zone 21N', + 24818 => 'PSAD56 UTM zone 18N', + 24819 => 'PSAD56 UTM zone 19N', + 24820 => 'PSAD56 UTM zone 20N', + 24821 => 'PSAD56 UTM zone 21N', + 24877 => 'PSAD56 UTM zone 17S', + 24878 => 'PSAD56 UTM zone 18S', + 24879 => 'PSAD56 UTM zone 19S', + 24880 => 'PSAD56 UTM zone 20S', + 24891 => 'PSAD56 Peru west zone', + 24892 => 'PSAD56 Peru central', + 24893 => 'PSAD56 Peru east zone', + 25000 => 'Leigon Ghana Grid', + 25231 => 'Lome UTM zone 31N', + 25391 => 'Luzon Philippines I', + 25392 => 'Luzon Philippines II', + 25393 => 'Luzon Philippines III', + 25394 => 'Luzon Philippines IV', + 25395 => 'Luzon Philippines V', + 25700 => 'Makassar NEIEZ', + 25932 => 'Malongo 1987 UTM 32S', + 26191 => 'Merchich Nord Maroc', + 26192 => 'Merchich Sud Maroc', + 26193 => 'Merchich Sahara', + 26237 => 'Massawa UTM zone 37N', + 26331 => 'Minna UTM zone 31N', + 26332 => 'Minna UTM zone 32N', + 26391 => 'Minna Nigeria West', + 26392 => 'Minna Nigeria Mid Belt', + 26393 => 'Minna Nigeria East', + 26432 => 'Mhast UTM zone 32S', + 26591 => 'Monte Mario Italy 1', + 26592 => 'Monte Mario Italy 2', + 26632 => 'M poraloko UTM 32N', + 26692 => 'M poraloko UTM 32S', + 26703 => 'NAD27 UTM zone 3N', + 26704 => 'NAD27 UTM zone 4N', + 26705 => 'NAD27 UTM zone 5N', + 26706 => 'NAD27 UTM zone 6N', + 26707 => 'NAD27 UTM zone 7N', + 26708 => 'NAD27 UTM zone 8N', + 26709 => 'NAD27 UTM zone 9N', + 26710 => 'NAD27 UTM zone 10N', + 26711 => 'NAD27 UTM zone 11N', + 26712 => 'NAD27 UTM zone 12N', + 26713 => 'NAD27 UTM zone 13N', + 26714 => 'NAD27 UTM zone 14N', + 26715 => 'NAD27 UTM zone 15N', + 26716 => 'NAD27 UTM zone 16N', + 26717 => 'NAD27 UTM zone 17N', + 26718 => 'NAD27 UTM zone 18N', + 26719 => 'NAD27 UTM zone 19N', + 26720 => 'NAD27 UTM zone 20N', + 26721 => 'NAD27 UTM zone 21N', + 26722 => 'NAD27 UTM zone 22N', + 26729 => 'NAD27 Alabama East', + 26730 => 'NAD27 Alabama West', + 26731 => 'NAD27 Alaska zone 1', + 26732 => 'NAD27 Alaska zone 2', + 26733 => 'NAD27 Alaska zone 3', + 26734 => 'NAD27 Alaska zone 4', + 26735 => 'NAD27 Alaska zone 5', + 26736 => 'NAD27 Alaska zone 6', + 26737 => 'NAD27 Alaska zone 7', + 26738 => 'NAD27 Alaska zone 8', + 26739 => 'NAD27 Alaska zone 9', + 26740 => 'NAD27 Alaska zone 10', + 26741 => 'NAD27 California I', + 26742 => 'NAD27 California II', + 26743 => 'NAD27 California III', + 26744 => 'NAD27 California IV', + 26745 => 'NAD27 California V', + 26746 => 'NAD27 California VI', + 26747 => 'NAD27 California VII', + 26748 => 'NAD27 Arizona East', + 26749 => 'NAD27 Arizona Central', + 26750 => 'NAD27 Arizona West', + 26751 => 'NAD27 Arkansas North', + 26752 => 'NAD27 Arkansas South', + 26753 => 'NAD27 Colorado North', + 26754 => 'NAD27 Colorado Central', + 26755 => 'NAD27 Colorado South', + 26756 => 'NAD27 Connecticut', + 26757 => 'NAD27 Delaware', + 26758 => 'NAD27 Florida East', + 26759 => 'NAD27 Florida West', + 26760 => 'NAD27 Florida North', + 26761 => 'NAD27 Hawaii zone 1', + 26762 => 'NAD27 Hawaii zone 2', + 26763 => 'NAD27 Hawaii zone 3', + 26764 => 'NAD27 Hawaii zone 4', + 26765 => 'NAD27 Hawaii zone 5', + 26766 => 'NAD27 Georgia East', + 26767 => 'NAD27 Georgia West', + 26768 => 'NAD27 Idaho East', + 26769 => 'NAD27 Idaho Central', + 26770 => 'NAD27 Idaho West', + 26771 => 'NAD27 Illinois East', + 26772 => 'NAD27 Illinois West', + 26773 => 'NAD27 Indiana East', + 26774 => 'NAD27 BLM 14N feet', + 26774 => 'NAD27 Indiana West', + 26775 => 'NAD27 BLM 15N feet', + 26775 => 'NAD27 Iowa North', + 26776 => 'NAD27 BLM 16N feet', + 26776 => 'NAD27 Iowa South', + 26777 => 'NAD27 BLM 17N feet', + 26777 => 'NAD27 Kansas North', + 26778 => 'NAD27 Kansas South', + 26779 => 'NAD27 Kentucky North', + 26780 => 'NAD27 Kentucky South', + 26781 => 'NAD27 Louisiana North', + 26782 => 'NAD27 Louisiana South', + 26783 => 'NAD27 Maine East', + 26784 => 'NAD27 Maine West', + 26785 => 'NAD27 Maryland', + 26786 => 'NAD27 Massachusetts', + 26787 => 'NAD27 Massachusetts Is', + 26788 => 'NAD27 Michigan North', + 26789 => 'NAD27 Michigan Central', + 26790 => 'NAD27 Michigan South', + 26791 => 'NAD27 Minnesota North', + 26792 => 'NAD27 Minnesota Cent', + 26793 => 'NAD27 Minnesota South', + 26794 => 'NAD27 Mississippi East', + 26795 => 'NAD27 Mississippi West', + 26796 => 'NAD27 Missouri East', + 26797 => 'NAD27 Missouri Central', + 26798 => 'NAD27 Missouri West', + 26801 => 'NAD Michigan Michigan East', + 26802 => 'NAD Michigan Michigan Old Central', + 26803 => 'NAD Michigan Michigan West', + 26903 => 'NAD83 UTM zone 3N', + 26904 => 'NAD83 UTM zone 4N', + 26905 => 'NAD83 UTM zone 5N', + 26906 => 'NAD83 UTM zone 6N', + 26907 => 'NAD83 UTM zone 7N', + 26908 => 'NAD83 UTM zone 8N', + 26909 => 'NAD83 UTM zone 9N', + 26910 => 'NAD83 UTM zone 10N', + 26911 => 'NAD83 UTM zone 11N', + 26912 => 'NAD83 UTM zone 12N', + 26913 => 'NAD83 UTM zone 13N', + 26914 => 'NAD83 UTM zone 14N', + 26915 => 'NAD83 UTM zone 15N', + 26916 => 'NAD83 UTM zone 16N', + 26917 => 'NAD83 UTM zone 17N', + 26918 => 'NAD83 UTM zone 18N', + 26919 => 'NAD83 UTM zone 19N', + 26920 => 'NAD83 UTM zone 20N', + 26921 => 'NAD83 UTM zone 21N', + 26922 => 'NAD83 UTM zone 22N', + 26923 => 'NAD83 UTM zone 23N', + 26929 => 'NAD83 Alabama East', + 26930 => 'NAD83 Alabama West', + 26931 => 'NAD83 Alaska zone 1', + 26932 => 'NAD83 Alaska zone 2', + 26933 => 'NAD83 Alaska zone 3', + 26934 => 'NAD83 Alaska zone 4', + 26935 => 'NAD83 Alaska zone 5', + 26936 => 'NAD83 Alaska zone 6', + 26937 => 'NAD83 Alaska zone 7', + 26938 => 'NAD83 Alaska zone 8', + 26939 => 'NAD83 Alaska zone 9', + 26940 => 'NAD83 Alaska zone 10', + 26941 => 'NAD83 California 1', + 26942 => 'NAD83 California 2', + 26943 => 'NAD83 California 3', + 26944 => 'NAD83 California 4', + 26945 => 'NAD83 California 5', + 26946 => 'NAD83 California 6', + 26948 => 'NAD83 Arizona East', + 26949 => 'NAD83 Arizona Central', + 26950 => 'NAD83 Arizona West', + 26951 => 'NAD83 Arkansas North', + 26952 => 'NAD83 Arkansas South', + 26953 => 'NAD83 Colorado North', + 26954 => 'NAD83 Colorado Central', + 26955 => 'NAD83 Colorado South', + 26956 => 'NAD83 Connecticut', + 26957 => 'NAD83 Delaware', + 26958 => 'NAD83 Florida East', + 26959 => 'NAD83 Florida West', + 26960 => 'NAD83 Florida North', + 26961 => 'NAD83 Hawaii zone 1', + 26962 => 'NAD83 Hawaii zone 2', + 26963 => 'NAD83 Hawaii zone 3', + 26964 => 'NAD83 Hawaii zone 4', + 26965 => 'NAD83 Hawaii zone 5', + 26966 => 'NAD83 Georgia East', + 26967 => 'NAD83 Georgia West', + 26968 => 'NAD83 Idaho East', + 26969 => 'NAD83 Idaho Central', + 26970 => 'NAD83 Idaho West', + 26971 => 'NAD83 Illinois East', + 26972 => 'NAD83 Illinois West', + 26973 => 'NAD83 Indiana East', + 26974 => 'NAD83 Indiana West', + 26975 => 'NAD83 Iowa North', + 26976 => 'NAD83 Iowa South', + 26977 => 'NAD83 Kansas North', + 26978 => 'NAD83 Kansas South', + 26979 => 'NAD83 Kentucky North', + 26980 => 'NAD83 Kentucky South', + 26981 => 'NAD83 Louisiana North', + 26982 => 'NAD83 Louisiana South', + 26983 => 'NAD83 Maine East', + 26984 => 'NAD83 Maine West', + 26985 => 'NAD83 Maryland', + 26986 => 'NAD83 Massachusetts', + 26987 => 'NAD83 Massachusetts Is', + 26988 => 'NAD83 Michigan North', + 26989 => 'NAD83 Michigan Central', + 26990 => 'NAD83 Michigan South', + 26991 => 'NAD83 Minnesota North', + 26992 => 'NAD83 Minnesota Cent', + 26993 => 'NAD83 Minnesota South', + 26994 => 'NAD83 Mississippi East', + 26995 => 'NAD83 Mississippi West', + 26996 => 'NAD83 Missouri East', + 26997 => 'NAD83 Missouri Central', + 26998 => 'NAD83 Missouri West', + 27038 => 'Nahrwan 1967 UTM 38N', + 27039 => 'Nahrwan 1967 UTM 39N', + 27040 => 'Nahrwan 1967 UTM 40N', + 27120 => 'Naparima UTM 20N', + 27200 => 'GD49 NZ Map Grid', + 27291 => 'GD49 North Island Grid', + 27292 => 'GD49 South Island Grid', + 27429 => 'Datum 73 UTM zone 29N', + 27500 => 'ATF Nord de Guerre', + 27581 => 'NTF France I', + 27582 => 'NTF France II', + 27583 => 'NTF France III', + 27591 => 'NTF Nord France', + 27592 => 'NTF Centre France', + 27593 => 'NTF Sud France', + 27700 => 'British National Grid', + 28232 => 'Point Noire UTM 32S', + 28348 => 'GDA94 MGA zone 48', + 28349 => 'GDA94 MGA zone 49', + 28350 => 'GDA94 MGA zone 50', + 28351 => 'GDA94 MGA zone 51', + 28352 => 'GDA94 MGA zone 52', + 28353 => 'GDA94 MGA zone 53', + 28354 => 'GDA94 MGA zone 54', + 28355 => 'GDA94 MGA zone 55', + 28356 => 'GDA94 MGA zone 56', + 28357 => 'GDA94 MGA zone 57', + 28358 => 'GDA94 MGA zone 58', + 28404 => 'Pulkovo Gauss zone 4', + 28405 => 'Pulkovo Gauss zone 5', + 28406 => 'Pulkovo Gauss zone 6', + 28407 => 'Pulkovo Gauss zone 7', + 28408 => 'Pulkovo Gauss zone 8', + 28409 => 'Pulkovo Gauss zone 9', + 28410 => 'Pulkovo Gauss zone 10', + 28411 => 'Pulkovo Gauss zone 11', + 28412 => 'Pulkovo Gauss zone 12', + 28413 => 'Pulkovo Gauss zone 13', + 28414 => 'Pulkovo Gauss zone 14', + 28415 => 'Pulkovo Gauss zone 15', + 28416 => 'Pulkovo Gauss zone 16', + 28417 => 'Pulkovo Gauss zone 17', + 28418 => 'Pulkovo Gauss zone 18', + 28419 => 'Pulkovo Gauss zone 19', + 28420 => 'Pulkovo Gauss zone 20', + 28421 => 'Pulkovo Gauss zone 21', + 28422 => 'Pulkovo Gauss zone 22', + 28423 => 'Pulkovo Gauss zone 23', + 28424 => 'Pulkovo Gauss zone 24', + 28425 => 'Pulkovo Gauss zone 25', + 28426 => 'Pulkovo Gauss zone 26', + 28427 => 'Pulkovo Gauss zone 27', + 28428 => 'Pulkovo Gauss zone 28', + 28429 => 'Pulkovo Gauss zone 29', + 28430 => 'Pulkovo Gauss zone 30', + 28431 => 'Pulkovo Gauss zone 31', + 28432 => 'Pulkovo Gauss zone 32', + 28464 => 'Pulkovo Gauss 4N', + 28465 => 'Pulkovo Gauss 5N', + 28466 => 'Pulkovo Gauss 6N', + 28467 => 'Pulkovo Gauss 7N', + 28468 => 'Pulkovo Gauss 8N', + 28469 => 'Pulkovo Gauss 9N', + 28470 => 'Pulkovo Gauss 10N', + 28471 => 'Pulkovo Gauss 11N', + 28472 => 'Pulkovo Gauss 12N', + 28473 => 'Pulkovo Gauss 13N', + 28474 => 'Pulkovo Gauss 14N', + 28475 => 'Pulkovo Gauss 15N', + 28476 => 'Pulkovo Gauss 16N', + 28477 => 'Pulkovo Gauss 17N', + 28478 => 'Pulkovo Gauss 18N', + 28479 => 'Pulkovo Gauss 19N', + 28480 => 'Pulkovo Gauss 20N', + 28481 => 'Pulkovo Gauss 21N', + 28482 => 'Pulkovo Gauss 22N', + 28483 => 'Pulkovo Gauss 23N', + 28484 => 'Pulkovo Gauss 24N', + 28485 => 'Pulkovo Gauss 25N', + 28486 => 'Pulkovo Gauss 26N', + 28487 => 'Pulkovo Gauss 27N', + 28488 => 'Pulkovo Gauss 28N', + 28489 => 'Pulkovo Gauss 29N', + 28490 => 'Pulkovo Gauss 30N', + 28491 => 'Pulkovo Gauss 31N', + 28492 => 'Pulkovo Gauss 32N', + 28600 => 'Qatar National Grid', + 28991 => 'RD Netherlands Old', + 28992 => 'RD Netherlands New', + 29118 => 'SAD69 UTM zone 18N', + 29119 => 'SAD69 UTM zone 19N', + 29120 => 'SAD69 UTM zone 20N', + 29121 => 'SAD69 UTM zone 21N', + 29122 => 'SAD69 UTM zone 22N', + 29177 => 'SAD69 UTM zone 17S', + 29178 => 'SAD69 UTM zone 18S', + 29179 => 'SAD69 UTM zone 19S', + 29180 => 'SAD69 UTM zone 20S', + 29181 => 'SAD69 UTM zone 21S', + 29182 => 'SAD69 UTM zone 22S', + 29183 => 'SAD69 UTM zone 23S', + 29184 => 'SAD69 UTM zone 24S', + 29185 => 'SAD69 UTM zone 25S', + 29220 => 'Sapper Hill UTM 20S', + 29221 => 'Sapper Hill UTM 21S', + 29333 => 'Schwarzeck UTM 33S', + 29635 => 'Sudan UTM zone 35N', + 29636 => 'Sudan UTM zone 36N', + 29700 => 'Tananarive Laborde', + 29738 => 'Tananarive UTM 38S', + 29739 => 'Tananarive UTM 39S', + 29800 => 'Timbalai 1948 Borneo', + 29849 => 'Timbalai 1948 UTM 49N', + 29850 => 'Timbalai 1948 UTM 50N', + 29900 => 'TM65 Irish Nat Grid', + 30200 => 'Trinidad 1903 Trinidad', + 30339 => 'TC 1948 UTM zone 39N', + 30340 => 'TC 1948 UTM zone 40N', + 30491 => 'Voirol N Algerie ancien', + 30492 => 'Voirol S Algerie ancien', + 30591 => 'Voirol Unifie N Algerie', + 30592 => 'Voirol Unifie S Algerie', + 30600 => 'Bern 1938 Swiss New', + 30729 => 'Nord Sahara UTM 29N', + 30730 => 'Nord Sahara UTM 30N', + 30731 => 'Nord Sahara UTM 31N', + 30732 => 'Nord Sahara UTM 32N', + 31028 => 'Yoff UTM zone 28N', + 31121 => 'Zanderij UTM zone 21N', + 31291 => 'MGI Austria West', + 31292 => 'MGI Austria Central', + 31293 => 'MGI Austria East', + 31300 => 'Belge Lambert 72', + 31491 => 'DHDN Germany zone 1', + 31492 => 'DHDN Germany zone 2', + 31493 => 'DHDN Germany zone 3', + 31494 => 'DHDN Germany zone 4', + 31495 => 'DHDN Germany zone 5', + 31700 => 'Dealul Piscului 1970 Stereo 70', + 32001 => 'NAD27 Montana North', + 32002 => 'NAD27 Montana Central', + 32003 => 'NAD27 Montana South', + 32005 => 'NAD27 Nebraska North', + 32006 => 'NAD27 Nebraska South', + 32007 => 'NAD27 Nevada East', + 32008 => 'NAD27 Nevada Central', + 32009 => 'NAD27 Nevada West', + 32010 => 'NAD27 New Hampshire', + 32011 => 'NAD27 New Jersey', + 32012 => 'NAD27 New Mexico East', + 32013 => 'NAD27 New Mexico Cent', + 32014 => 'NAD27 New Mexico West', + 32015 => 'NAD27 New York East', + 32016 => 'NAD27 New York Central', + 32017 => 'NAD27 New York West', + 32018 => 'NAD27 New York Long Is', + 32019 => 'NAD27 North Carolina', + 32020 => 'NAD27 North Dakota N', + 32021 => 'NAD27 North Dakota S', + 32022 => 'NAD27 Ohio North', + 32023 => 'NAD27 Ohio South', + 32024 => 'NAD27 Oklahoma North', + 32025 => 'NAD27 Oklahoma South', + 32026 => 'NAD27 Oregon North', + 32027 => 'NAD27 Oregon South', + 32028 => 'NAD27 Pennsylvania N', + 32029 => 'NAD27 Pennsylvania S', + 32030 => 'NAD27 Rhode Island', + 32031 => 'NAD27 South Carolina N', + 32033 => 'NAD27 South Carolina S', + 32034 => 'NAD27 South Dakota N', + 32035 => 'NAD27 South Dakota S', + 32036 => 'NAD27 Tennessee', + 32037 => 'NAD27 Texas North', + 32038 => 'NAD27 Texas North Cen', + 32039 => 'NAD27 Texas Central', + 32040 => 'NAD27 Texas South Cen', + 32041 => 'NAD27 Texas South', + 32042 => 'NAD27 Utah North', + 32043 => 'NAD27 Utah Central', + 32044 => 'NAD27 Utah South', + 32045 => 'NAD27 Vermont', + 32046 => 'NAD27 Virginia North', + 32047 => 'NAD27 Virginia South', + 32048 => 'NAD27 Washington North', + 32049 => 'NAD27 Washington South', + 32050 => 'NAD27 West Virginia N', + 32051 => 'NAD27 West Virginia S', + 32052 => 'NAD27 Wisconsin North', + 32053 => 'NAD27 Wisconsin Cen', + 32054 => 'NAD27 Wisconsin South', + 32055 => 'NAD27 Wyoming East', + 32056 => 'NAD27 Wyoming E Cen', + 32057 => 'NAD27 Wyoming W Cen', + 32058 => 'NAD27 Wyoming West', + 32059 => 'NAD27 Puerto Rico', + 32060 => 'NAD27 St Croix', + 32100 => 'NAD83 Montana', + 32104 => 'NAD83 Nebraska', + 32107 => 'NAD83 Nevada East', + 32108 => 'NAD83 Nevada Central', + 32109 => 'NAD83 Nevada West', + 32110 => 'NAD83 New Hampshire', + 32111 => 'NAD83 New Jersey', + 32112 => 'NAD83 New Mexico East', + 32113 => 'NAD83 New Mexico Cent', + 32114 => 'NAD83 New Mexico West', + 32115 => 'NAD83 New York East', + 32116 => 'NAD83 New York Central', + 32117 => 'NAD83 New York West', + 32118 => 'NAD83 New York Long Is', + 32119 => 'NAD83 North Carolina', + 32120 => 'NAD83 North Dakota N', + 32121 => 'NAD83 North Dakota S', + 32122 => 'NAD83 Ohio North', + 32123 => 'NAD83 Ohio South', + 32124 => 'NAD83 Oklahoma North', + 32125 => 'NAD83 Oklahoma South', + 32126 => 'NAD83 Oregon North', + 32127 => 'NAD83 Oregon South', + 32128 => 'NAD83 Pennsylvania N', + 32129 => 'NAD83 Pennsylvania S', + 32130 => 'NAD83 Rhode Island', + 32133 => 'NAD83 South Carolina', + 32134 => 'NAD83 South Dakota N', + 32135 => 'NAD83 South Dakota S', + 32136 => 'NAD83 Tennessee', + 32137 => 'NAD83 Texas North', + 32138 => 'NAD83 Texas North Cen', + 32139 => 'NAD83 Texas Central', + 32140 => 'NAD83 Texas South Cen', + 32141 => 'NAD83 Texas South', + 32142 => 'NAD83 Utah North', + 32143 => 'NAD83 Utah Central', + 32144 => 'NAD83 Utah South', + 32145 => 'NAD83 Vermont', + 32146 => 'NAD83 Virginia North', + 32147 => 'NAD83 Virginia South', + 32148 => 'NAD83 Washington North', + 32149 => 'NAD83 Washington South', + 32150 => 'NAD83 West Virginia N', + 32151 => 'NAD83 West Virginia S', + 32152 => 'NAD83 Wisconsin North', + 32153 => 'NAD83 Wisconsin Cen', + 32154 => 'NAD83 Wisconsin South', + 32155 => 'NAD83 Wyoming East', + 32156 => 'NAD83 Wyoming E Cen', + 32157 => 'NAD83 Wyoming W Cen', + 32158 => 'NAD83 Wyoming West', + 32161 => 'NAD83 Puerto Rico Virgin Is', + 32201 => 'WGS72 UTM zone 1N', + 32202 => 'WGS72 UTM zone 2N', + 32203 => 'WGS72 UTM zone 3N', + 32204 => 'WGS72 UTM zone 4N', + 32205 => 'WGS72 UTM zone 5N', + 32206 => 'WGS72 UTM zone 6N', + 32207 => 'WGS72 UTM zone 7N', + 32208 => 'WGS72 UTM zone 8N', + 32209 => 'WGS72 UTM zone 9N', + 32210 => 'WGS72 UTM zone 10N', + 32211 => 'WGS72 UTM zone 11N', + 32212 => 'WGS72 UTM zone 12N', + 32213 => 'WGS72 UTM zone 13N', + 32214 => 'WGS72 UTM zone 14N', + 32215 => 'WGS72 UTM zone 15N', + 32216 => 'WGS72 UTM zone 16N', + 32217 => 'WGS72 UTM zone 17N', + 32218 => 'WGS72 UTM zone 18N', + 32219 => 'WGS72 UTM zone 19N', + 32220 => 'WGS72 UTM zone 20N', + 32221 => 'WGS72 UTM zone 21N', + 32222 => 'WGS72 UTM zone 22N', + 32223 => 'WGS72 UTM zone 23N', + 32224 => 'WGS72 UTM zone 24N', + 32225 => 'WGS72 UTM zone 25N', + 32226 => 'WGS72 UTM zone 26N', + 32227 => 'WGS72 UTM zone 27N', + 32228 => 'WGS72 UTM zone 28N', + 32229 => 'WGS72 UTM zone 29N', + 32230 => 'WGS72 UTM zone 30N', + 32231 => 'WGS72 UTM zone 31N', + 32232 => 'WGS72 UTM zone 32N', + 32233 => 'WGS72 UTM zone 33N', + 32234 => 'WGS72 UTM zone 34N', + 32235 => 'WGS72 UTM zone 35N', + 32236 => 'WGS72 UTM zone 36N', + 32237 => 'WGS72 UTM zone 37N', + 32238 => 'WGS72 UTM zone 38N', + 32239 => 'WGS72 UTM zone 39N', + 32240 => 'WGS72 UTM zone 40N', + 32241 => 'WGS72 UTM zone 41N', + 32242 => 'WGS72 UTM zone 42N', + 32243 => 'WGS72 UTM zone 43N', + 32244 => 'WGS72 UTM zone 44N', + 32245 => 'WGS72 UTM zone 45N', + 32246 => 'WGS72 UTM zone 46N', + 32247 => 'WGS72 UTM zone 47N', + 32248 => 'WGS72 UTM zone 48N', + 32249 => 'WGS72 UTM zone 49N', + 32250 => 'WGS72 UTM zone 50N', + 32251 => 'WGS72 UTM zone 51N', + 32252 => 'WGS72 UTM zone 52N', + 32253 => 'WGS72 UTM zone 53N', + 32254 => 'WGS72 UTM zone 54N', + 32255 => 'WGS72 UTM zone 55N', + 32256 => 'WGS72 UTM zone 56N', + 32257 => 'WGS72 UTM zone 57N', + 32258 => 'WGS72 UTM zone 58N', + 32259 => 'WGS72 UTM zone 59N', + 32260 => 'WGS72 UTM zone 60N', + 32301 => 'WGS72 UTM zone 1S', + 32302 => 'WGS72 UTM zone 2S', + 32303 => 'WGS72 UTM zone 3S', + 32304 => 'WGS72 UTM zone 4S', + 32305 => 'WGS72 UTM zone 5S', + 32306 => 'WGS72 UTM zone 6S', + 32307 => 'WGS72 UTM zone 7S', + 32308 => 'WGS72 UTM zone 8S', + 32309 => 'WGS72 UTM zone 9S', + 32310 => 'WGS72 UTM zone 10S', + 32311 => 'WGS72 UTM zone 11S', + 32312 => 'WGS72 UTM zone 12S', + 32313 => 'WGS72 UTM zone 13S', + 32314 => 'WGS72 UTM zone 14S', + 32315 => 'WGS72 UTM zone 15S', + 32316 => 'WGS72 UTM zone 16S', + 32317 => 'WGS72 UTM zone 17S', + 32318 => 'WGS72 UTM zone 18S', + 32319 => 'WGS72 UTM zone 19S', + 32320 => 'WGS72 UTM zone 20S', + 32321 => 'WGS72 UTM zone 21S', + 32322 => 'WGS72 UTM zone 22S', + 32323 => 'WGS72 UTM zone 23S', + 32324 => 'WGS72 UTM zone 24S', + 32325 => 'WGS72 UTM zone 25S', + 32326 => 'WGS72 UTM zone 26S', + 32327 => 'WGS72 UTM zone 27S', + 32328 => 'WGS72 UTM zone 28S', + 32329 => 'WGS72 UTM zone 29S', + 32330 => 'WGS72 UTM zone 30S', + 32331 => 'WGS72 UTM zone 31S', + 32332 => 'WGS72 UTM zone 32S', + 32333 => 'WGS72 UTM zone 33S', + 32334 => 'WGS72 UTM zone 34S', + 32335 => 'WGS72 UTM zone 35S', + 32336 => 'WGS72 UTM zone 36S', + 32337 => 'WGS72 UTM zone 37S', + 32338 => 'WGS72 UTM zone 38S', + 32339 => 'WGS72 UTM zone 39S', + 32340 => 'WGS72 UTM zone 40S', + 32341 => 'WGS72 UTM zone 41S', + 32342 => 'WGS72 UTM zone 42S', + 32343 => 'WGS72 UTM zone 43S', + 32344 => 'WGS72 UTM zone 44S', + 32345 => 'WGS72 UTM zone 45S', + 32346 => 'WGS72 UTM zone 46S', + 32347 => 'WGS72 UTM zone 47S', + 32348 => 'WGS72 UTM zone 48S', + 32349 => 'WGS72 UTM zone 49S', + 32350 => 'WGS72 UTM zone 50S', + 32351 => 'WGS72 UTM zone 51S', + 32352 => 'WGS72 UTM zone 52S', + 32353 => 'WGS72 UTM zone 53S', + 32354 => 'WGS72 UTM zone 54S', + 32355 => 'WGS72 UTM zone 55S', + 32356 => 'WGS72 UTM zone 56S', + 32357 => 'WGS72 UTM zone 57S', + 32358 => 'WGS72 UTM zone 58S', + 32359 => 'WGS72 UTM zone 59S', + 32360 => 'WGS72 UTM zone 60S', + 32401 => 'WGS72BE UTM zone 1N', + 32402 => 'WGS72BE UTM zone 2N', + 32403 => 'WGS72BE UTM zone 3N', + 32404 => 'WGS72BE UTM zone 4N', + 32405 => 'WGS72BE UTM zone 5N', + 32406 => 'WGS72BE UTM zone 6N', + 32407 => 'WGS72BE UTM zone 7N', + 32408 => 'WGS72BE UTM zone 8N', + 32409 => 'WGS72BE UTM zone 9N', + 32410 => 'WGS72BE UTM zone 10N', + 32411 => 'WGS72BE UTM zone 11N', + 32412 => 'WGS72BE UTM zone 12N', + 32413 => 'WGS72BE UTM zone 13N', + 32414 => 'WGS72BE UTM zone 14N', + 32415 => 'WGS72BE UTM zone 15N', + 32416 => 'WGS72BE UTM zone 16N', + 32417 => 'WGS72BE UTM zone 17N', + 32418 => 'WGS72BE UTM zone 18N', + 32419 => 'WGS72BE UTM zone 19N', + 32420 => 'WGS72BE UTM zone 20N', + 32421 => 'WGS72BE UTM zone 21N', + 32422 => 'WGS72BE UTM zone 22N', + 32423 => 'WGS72BE UTM zone 23N', + 32424 => 'WGS72BE UTM zone 24N', + 32425 => 'WGS72BE UTM zone 25N', + 32426 => 'WGS72BE UTM zone 26N', + 32427 => 'WGS72BE UTM zone 27N', + 32428 => 'WGS72BE UTM zone 28N', + 32429 => 'WGS72BE UTM zone 29N', + 32430 => 'WGS72BE UTM zone 30N', + 32431 => 'WGS72BE UTM zone 31N', + 32432 => 'WGS72BE UTM zone 32N', + 32433 => 'WGS72BE UTM zone 33N', + 32434 => 'WGS72BE UTM zone 34N', + 32435 => 'WGS72BE UTM zone 35N', + 32436 => 'WGS72BE UTM zone 36N', + 32437 => 'WGS72BE UTM zone 37N', + 32438 => 'WGS72BE UTM zone 38N', + 32439 => 'WGS72BE UTM zone 39N', + 32440 => 'WGS72BE UTM zone 40N', + 32441 => 'WGS72BE UTM zone 41N', + 32442 => 'WGS72BE UTM zone 42N', + 32443 => 'WGS72BE UTM zone 43N', + 32444 => 'WGS72BE UTM zone 44N', + 32445 => 'WGS72BE UTM zone 45N', + 32446 => 'WGS72BE UTM zone 46N', + 32447 => 'WGS72BE UTM zone 47N', + 32448 => 'WGS72BE UTM zone 48N', + 32449 => 'WGS72BE UTM zone 49N', + 32450 => 'WGS72BE UTM zone 50N', + 32451 => 'WGS72BE UTM zone 51N', + 32452 => 'WGS72BE UTM zone 52N', + 32453 => 'WGS72BE UTM zone 53N', + 32454 => 'WGS72BE UTM zone 54N', + 32455 => 'WGS72BE UTM zone 55N', + 32456 => 'WGS72BE UTM zone 56N', + 32457 => 'WGS72BE UTM zone 57N', + 32458 => 'WGS72BE UTM zone 58N', + 32459 => 'WGS72BE UTM zone 59N', + 32460 => 'WGS72BE UTM zone 60N', + 32501 => 'WGS72BE UTM zone 1S', + 32502 => 'WGS72BE UTM zone 2S', + 32503 => 'WGS72BE UTM zone 3S', + 32504 => 'WGS72BE UTM zone 4S', + 32505 => 'WGS72BE UTM zone 5S', + 32506 => 'WGS72BE UTM zone 6S', + 32507 => 'WGS72BE UTM zone 7S', + 32508 => 'WGS72BE UTM zone 8S', + 32509 => 'WGS72BE UTM zone 9S', + 32510 => 'WGS72BE UTM zone 10S', + 32511 => 'WGS72BE UTM zone 11S', + 32512 => 'WGS72BE UTM zone 12S', + 32513 => 'WGS72BE UTM zone 13S', + 32514 => 'WGS72BE UTM zone 14S', + 32515 => 'WGS72BE UTM zone 15S', + 32516 => 'WGS72BE UTM zone 16S', + 32517 => 'WGS72BE UTM zone 17S', + 32518 => 'WGS72BE UTM zone 18S', + 32519 => 'WGS72BE UTM zone 19S', + 32520 => 'WGS72BE UTM zone 20S', + 32521 => 'WGS72BE UTM zone 21S', + 32522 => 'WGS72BE UTM zone 22S', + 32523 => 'WGS72BE UTM zone 23S', + 32524 => 'WGS72BE UTM zone 24S', + 32525 => 'WGS72BE UTM zone 25S', + 32526 => 'WGS72BE UTM zone 26S', + 32527 => 'WGS72BE UTM zone 27S', + 32528 => 'WGS72BE UTM zone 28S', + 32529 => 'WGS72BE UTM zone 29S', + 32530 => 'WGS72BE UTM zone 30S', + 32531 => 'WGS72BE UTM zone 31S', + 32532 => 'WGS72BE UTM zone 32S', + 32533 => 'WGS72BE UTM zone 33S', + 32534 => 'WGS72BE UTM zone 34S', + 32535 => 'WGS72BE UTM zone 35S', + 32536 => 'WGS72BE UTM zone 36S', + 32537 => 'WGS72BE UTM zone 37S', + 32538 => 'WGS72BE UTM zone 38S', + 32539 => 'WGS72BE UTM zone 39S', + 32540 => 'WGS72BE UTM zone 40S', + 32541 => 'WGS72BE UTM zone 41S', + 32542 => 'WGS72BE UTM zone 42S', + 32543 => 'WGS72BE UTM zone 43S', + 32544 => 'WGS72BE UTM zone 44S', + 32545 => 'WGS72BE UTM zone 45S', + 32546 => 'WGS72BE UTM zone 46S', + 32547 => 'WGS72BE UTM zone 47S', + 32548 => 'WGS72BE UTM zone 48S', + 32549 => 'WGS72BE UTM zone 49S', + 32550 => 'WGS72BE UTM zone 50S', + 32551 => 'WGS72BE UTM zone 51S', + 32552 => 'WGS72BE UTM zone 52S', + 32553 => 'WGS72BE UTM zone 53S', + 32554 => 'WGS72BE UTM zone 54S', + 32555 => 'WGS72BE UTM zone 55S', + 32556 => 'WGS72BE UTM zone 56S', + 32557 => 'WGS72BE UTM zone 57S', + 32558 => 'WGS72BE UTM zone 58S', + 32559 => 'WGS72BE UTM zone 59S', + 32560 => 'WGS72BE UTM zone 60S', + 32601 => 'WGS84 UTM zone 1N', + 32602 => 'WGS84 UTM zone 2N', + 32603 => 'WGS84 UTM zone 3N', + 32604 => 'WGS84 UTM zone 4N', + 32605 => 'WGS84 UTM zone 5N', + 32606 => 'WGS84 UTM zone 6N', + 32607 => 'WGS84 UTM zone 7N', + 32608 => 'WGS84 UTM zone 8N', + 32609 => 'WGS84 UTM zone 9N', + 32610 => 'WGS84 UTM zone 10N', + 32611 => 'WGS84 UTM zone 11N', + 32612 => 'WGS84 UTM zone 12N', + 32613 => 'WGS84 UTM zone 13N', + 32614 => 'WGS84 UTM zone 14N', + 32615 => 'WGS84 UTM zone 15N', + 32616 => 'WGS84 UTM zone 16N', + 32617 => 'WGS84 UTM zone 17N', + 32618 => 'WGS84 UTM zone 18N', + 32619 => 'WGS84 UTM zone 19N', + 32620 => 'WGS84 UTM zone 20N', + 32621 => 'WGS84 UTM zone 21N', + 32622 => 'WGS84 UTM zone 22N', + 32623 => 'WGS84 UTM zone 23N', + 32624 => 'WGS84 UTM zone 24N', + 32625 => 'WGS84 UTM zone 25N', + 32626 => 'WGS84 UTM zone 26N', + 32627 => 'WGS84 UTM zone 27N', + 32628 => 'WGS84 UTM zone 28N', + 32629 => 'WGS84 UTM zone 29N', + 32630 => 'WGS84 UTM zone 30N', + 32631 => 'WGS84 UTM zone 31N', + 32632 => 'WGS84 UTM zone 32N', + 32633 => 'WGS84 UTM zone 33N', + 32634 => 'WGS84 UTM zone 34N', + 32635 => 'WGS84 UTM zone 35N', + 32636 => 'WGS84 UTM zone 36N', + 32637 => 'WGS84 UTM zone 37N', + 32638 => 'WGS84 UTM zone 38N', + 32639 => 'WGS84 UTM zone 39N', + 32640 => 'WGS84 UTM zone 40N', + 32641 => 'WGS84 UTM zone 41N', + 32642 => 'WGS84 UTM zone 42N', + 32643 => 'WGS84 UTM zone 43N', + 32644 => 'WGS84 UTM zone 44N', + 32645 => 'WGS84 UTM zone 45N', + 32646 => 'WGS84 UTM zone 46N', + 32647 => 'WGS84 UTM zone 47N', + 32648 => 'WGS84 UTM zone 48N', + 32649 => 'WGS84 UTM zone 49N', + 32650 => 'WGS84 UTM zone 50N', + 32651 => 'WGS84 UTM zone 51N', + 32652 => 'WGS84 UTM zone 52N', + 32653 => 'WGS84 UTM zone 53N', + 32654 => 'WGS84 UTM zone 54N', + 32655 => 'WGS84 UTM zone 55N', + 32656 => 'WGS84 UTM zone 56N', + 32657 => 'WGS84 UTM zone 57N', + 32658 => 'WGS84 UTM zone 58N', + 32659 => 'WGS84 UTM zone 59N', + 32660 => 'WGS84 UTM zone 60N', + 32701 => 'WGS84 UTM zone 1S', + 32702 => 'WGS84 UTM zone 2S', + 32703 => 'WGS84 UTM zone 3S', + 32704 => 'WGS84 UTM zone 4S', + 32705 => 'WGS84 UTM zone 5S', + 32706 => 'WGS84 UTM zone 6S', + 32707 => 'WGS84 UTM zone 7S', + 32708 => 'WGS84 UTM zone 8S', + 32709 => 'WGS84 UTM zone 9S', + 32710 => 'WGS84 UTM zone 10S', + 32711 => 'WGS84 UTM zone 11S', + 32712 => 'WGS84 UTM zone 12S', + 32713 => 'WGS84 UTM zone 13S', + 32714 => 'WGS84 UTM zone 14S', + 32715 => 'WGS84 UTM zone 15S', + 32716 => 'WGS84 UTM zone 16S', + 32717 => 'WGS84 UTM zone 17S', + 32718 => 'WGS84 UTM zone 18S', + 32719 => 'WGS84 UTM zone 19S', + 32720 => 'WGS84 UTM zone 20S', + 32721 => 'WGS84 UTM zone 21S', + 32722 => 'WGS84 UTM zone 22S', + 32723 => 'WGS84 UTM zone 23S', + 32724 => 'WGS84 UTM zone 24S', + 32725 => 'WGS84 UTM zone 25S', + 32726 => 'WGS84 UTM zone 26S', + 32727 => 'WGS84 UTM zone 27S', + 32728 => 'WGS84 UTM zone 28S', + 32729 => 'WGS84 UTM zone 29S', + 32730 => 'WGS84 UTM zone 30S', + 32731 => 'WGS84 UTM zone 31S', + 32732 => 'WGS84 UTM zone 32S', + 32733 => 'WGS84 UTM zone 33S', + 32734 => 'WGS84 UTM zone 34S', + 32735 => 'WGS84 UTM zone 35S', + 32736 => 'WGS84 UTM zone 36S', + 32737 => 'WGS84 UTM zone 37S', + 32738 => 'WGS84 UTM zone 38S', + 32739 => 'WGS84 UTM zone 39S', + 32740 => 'WGS84 UTM zone 40S', + 32741 => 'WGS84 UTM zone 41S', + 32742 => 'WGS84 UTM zone 42S', + 32743 => 'WGS84 UTM zone 43S', + 32744 => 'WGS84 UTM zone 44S', + 32745 => 'WGS84 UTM zone 45S', + 32746 => 'WGS84 UTM zone 46S', + 32747 => 'WGS84 UTM zone 47S', + 32748 => 'WGS84 UTM zone 48S', + 32749 => 'WGS84 UTM zone 49S', + 32750 => 'WGS84 UTM zone 50S', + 32751 => 'WGS84 UTM zone 51S', + 32752 => 'WGS84 UTM zone 52S', + 32753 => 'WGS84 UTM zone 53S', + 32754 => 'WGS84 UTM zone 54S', + 32755 => 'WGS84 UTM zone 55S', + 32756 => 'WGS84 UTM zone 56S', + 32757 => 'WGS84 UTM zone 57S', + 32758 => 'WGS84 UTM zone 58S', + 32759 => 'WGS84 UTM zone 59S', + 32760 => 'WGS84 UTM zone 60S', + 32767 => 'User Defined', + }, + }, + 3073 => 'PCSCitation', + 3074 => { + Name => 'Projection', + PrintConv => { + # epsg_proj + 10101 => 'Alabama CS27 East', + 10102 => 'Alabama CS27 West', + 10131 => 'Alabama CS83 East', + 10132 => 'Alabama CS83 West', + 10201 => 'Arizona Coordinate System east', + 10202 => 'Arizona Coordinate System Central', + 10203 => 'Arizona Coordinate System west', + 10231 => 'Arizona CS83 east', + 10232 => 'Arizona CS83 Central', + 10233 => 'Arizona CS83 west', + 10301 => 'Arkansas CS27 North', + 10302 => 'Arkansas CS27 South', + 10331 => 'Arkansas CS83 North', + 10332 => 'Arkansas CS83 South', + 10401 => 'California CS27 I', + 10402 => 'California CS27 II', + 10403 => 'California CS27 III', + 10404 => 'California CS27 IV', + 10405 => 'California CS27 V', + 10406 => 'California CS27 VI', + 10407 => 'California CS27 VII', + 10431 => 'California CS83 1', + 10432 => 'California CS83 2', + 10433 => 'California CS83 3', + 10434 => 'California CS83 4', + 10435 => 'California CS83 5', + 10436 => 'California CS83 6', + 10501 => 'Colorado CS27 North', + 10502 => 'Colorado CS27 Central', + 10503 => 'Colorado CS27 South', + 10531 => 'Colorado CS83 North', + 10532 => 'Colorado CS83 Central', + 10533 => 'Colorado CS83 South', + 10600 => 'Connecticut CS27', + 10630 => 'Connecticut CS83', + 10700 => 'Delaware CS27', + 10730 => 'Delaware CS83', + 10901 => 'Florida CS27 East', + 10902 => 'Florida CS27 West', + 10903 => 'Florida CS27 North', + 10931 => 'Florida CS83 East', + 10932 => 'Florida CS83 West', + 10933 => 'Florida CS83 North', + 11001 => 'Georgia CS27 East', + 11002 => 'Georgia CS27 West', + 11031 => 'Georgia CS83 East', + 11032 => 'Georgia CS83 West', + 11101 => 'Idaho CS27 East', + 11102 => 'Idaho CS27 Central', + 11103 => 'Idaho CS27 West', + 11131 => 'Idaho CS83 East', + 11132 => 'Idaho CS83 Central', + 11133 => 'Idaho CS83 West', + 11201 => 'Illinois CS27 East', + 11202 => 'Illinois CS27 West', + 11231 => 'Illinois CS83 East', + 11232 => 'Illinois CS83 West', + 11301 => 'Indiana CS27 East', + 11302 => 'Indiana CS27 West', + 11331 => 'Indiana CS83 East', + 11332 => 'Indiana CS83 West', + 11401 => 'Iowa CS27 North', + 11402 => 'Iowa CS27 South', + 11431 => 'Iowa CS83 North', + 11432 => 'Iowa CS83 South', + 11501 => 'Kansas CS27 North', + 11502 => 'Kansas CS27 South', + 11531 => 'Kansas CS83 North', + 11532 => 'Kansas CS83 South', + 11601 => 'Kentucky CS27 North', + 11602 => 'Kentucky CS27 South', + 11631 => 'Kentucky CS83 North', + 11632 => 'Kentucky CS83 South', + 11701 => 'Louisiana CS27 North', + 11702 => 'Louisiana CS27 South', + 11731 => 'Louisiana CS83 North', + 11732 => 'Louisiana CS83 South', + 11801 => 'Maine CS27 East', + 11802 => 'Maine CS27 West', + 11831 => 'Maine CS83 East', + 11832 => 'Maine CS83 West', + 11900 => 'Maryland CS27', + 11930 => 'Maryland CS83', + 12001 => 'Massachusetts CS27 Mainland', + 12002 => 'Massachusetts CS27 Island', + 12031 => 'Massachusetts CS83 Mainland', + 12032 => 'Massachusetts CS83 Island', + 12101 => 'Michigan State Plane East', + 12102 => 'Michigan State Plane Old Central', + 12103 => 'Michigan State Plane West', + 12111 => 'Michigan CS27 North', + 12112 => 'Michigan CS27 Central', + 12113 => 'Michigan CS27 South', + 12141 => 'Michigan CS83 North', + 12142 => 'Michigan CS83 Central', + 12143 => 'Michigan CS83 South', + 12201 => 'Minnesota CS27 North', + 12202 => 'Minnesota CS27 Central', + 12203 => 'Minnesota CS27 South', + 12231 => 'Minnesota CS83 North', + 12232 => 'Minnesota CS83 Central', + 12233 => 'Minnesota CS83 South', + 12301 => 'Mississippi CS27 East', + 12302 => 'Mississippi CS27 West', + 12331 => 'Mississippi CS83 East', + 12332 => 'Mississippi CS83 West', + 12401 => 'Missouri CS27 East', + 12402 => 'Missouri CS27 Central', + 12403 => 'Missouri CS27 West', + 12431 => 'Missouri CS83 East', + 12432 => 'Missouri CS83 Central', + 12433 => 'Missouri CS83 West', + 12501 => 'Montana CS27 North', + 12502 => 'Montana CS27 Central', + 12503 => 'Montana CS27 South', + 12530 => 'Montana CS83', + 12601 => 'Nebraska CS27 North', + 12602 => 'Nebraska CS27 South', + 12630 => 'Nebraska CS83', + 12701 => 'Nevada CS27 East', + 12702 => 'Nevada CS27 Central', + 12703 => 'Nevada CS27 West', + 12731 => 'Nevada CS83 East', + 12732 => 'Nevada CS83 Central', + 12733 => 'Nevada CS83 West', + 12800 => 'New Hampshire CS27', + 12830 => 'New Hampshire CS83', + 12900 => 'New Jersey CS27', + 12930 => 'New Jersey CS83', + 13001 => 'New Mexico CS27 East', + 13002 => 'New Mexico CS27 Central', + 13003 => 'New Mexico CS27 West', + 13031 => 'New Mexico CS83 East', + 13032 => 'New Mexico CS83 Central', + 13033 => 'New Mexico CS83 West', + 13101 => 'New York CS27 East', + 13102 => 'New York CS27 Central', + 13103 => 'New York CS27 West', + 13104 => 'New York CS27 Long Island', + 13131 => 'New York CS83 East', + 13132 => 'New York CS83 Central', + 13133 => 'New York CS83 West', + 13134 => 'New York CS83 Long Island', + 13200 => 'North Carolina CS27', + 13230 => 'North Carolina CS83', + 13301 => 'North Dakota CS27 North', + 13302 => 'North Dakota CS27 South', + 13331 => 'North Dakota CS83 North', + 13332 => 'North Dakota CS83 South', + 13401 => 'Ohio CS27 North', + 13402 => 'Ohio CS27 South', + 13431 => 'Ohio CS83 North', + 13432 => 'Ohio CS83 South', + 13501 => 'Oklahoma CS27 North', + 13502 => 'Oklahoma CS27 South', + 13531 => 'Oklahoma CS83 North', + 13532 => 'Oklahoma CS83 South', + 13601 => 'Oregon CS27 North', + 13602 => 'Oregon CS27 South', + 13631 => 'Oregon CS83 North', + 13632 => 'Oregon CS83 South', + 13701 => 'Pennsylvania CS27 North', + 13702 => 'Pennsylvania CS27 South', + 13731 => 'Pennsylvania CS83 North', + 13732 => 'Pennsylvania CS83 South', + 13800 => 'Rhode Island CS27', + 13830 => 'Rhode Island CS83', + 13901 => 'South Carolina CS27 North', + 13902 => 'South Carolina CS27 South', + 13930 => 'South Carolina CS83', + 14001 => 'South Dakota CS27 North', + 14002 => 'South Dakota CS27 South', + 14031 => 'South Dakota CS83 North', + 14032 => 'South Dakota CS83 South', + 14100 => 'Tennessee CS27', + 14130 => 'Tennessee CS83', + 14201 => 'Texas CS27 North', + 14202 => 'Texas CS27 North Central', + 14203 => 'Texas CS27 Central', + 14204 => 'Texas CS27 South Central', + 14205 => 'Texas CS27 South', + 14231 => 'Texas CS83 North', + 14232 => 'Texas CS83 North Central', + 14233 => 'Texas CS83 Central', + 14234 => 'Texas CS83 South Central', + 14235 => 'Texas CS83 South', + 14301 => 'Utah CS27 North', + 14302 => 'Utah CS27 Central', + 14303 => 'Utah CS27 South', + 14331 => 'Utah CS83 North', + 14332 => 'Utah CS83 Central', + 14333 => 'Utah CS83 South', + 14400 => 'Vermont CS27', + 14430 => 'Vermont CS83', + 14501 => 'Virginia CS27 North', + 14502 => 'Virginia CS27 South', + 14531 => 'Virginia CS83 North', + 14532 => 'Virginia CS83 South', + 14601 => 'Washington CS27 North', + 14602 => 'Washington CS27 South', + 14631 => 'Washington CS83 North', + 14632 => 'Washington CS83 South', + 14701 => 'West Virginia CS27 North', + 14702 => 'West Virginia CS27 South', + 14731 => 'West Virginia CS83 North', + 14732 => 'West Virginia CS83 South', + 14801 => 'Wisconsin CS27 North', + 14802 => 'Wisconsin CS27 Central', + 14803 => 'Wisconsin CS27 South', + 14831 => 'Wisconsin CS83 North', + 14832 => 'Wisconsin CS83 Central', + 14833 => 'Wisconsin CS83 South', + 14901 => 'Wyoming CS27 East', + 14902 => 'Wyoming CS27 East Central', + 14903 => 'Wyoming CS27 West Central', + 14904 => 'Wyoming CS27 West', + 14931 => 'Wyoming CS83 East', + 14932 => 'Wyoming CS83 East Central', + 14933 => 'Wyoming CS83 West Central', + 14934 => 'Wyoming CS83 West', + 15001 => 'Alaska CS27 1', + 15002 => 'Alaska CS27 2', + 15003 => 'Alaska CS27 3', + 15004 => 'Alaska CS27 4', + 15005 => 'Alaska CS27 5', + 15006 => 'Alaska CS27 6', + 15007 => 'Alaska CS27 7', + 15008 => 'Alaska CS27 8', + 15009 => 'Alaska CS27 9', + 15010 => 'Alaska CS27 10', + 15031 => 'Alaska CS83 1', + 15032 => 'Alaska CS83 2', + 15033 => 'Alaska CS83 3', + 15034 => 'Alaska CS83 4', + 15035 => 'Alaska CS83 5', + 15036 => 'Alaska CS83 6', + 15037 => 'Alaska CS83 7', + 15038 => 'Alaska CS83 8', + 15039 => 'Alaska CS83 9', + 15040 => 'Alaska CS83 10', + 15101 => 'Hawaii CS27 1', + 15102 => 'Hawaii CS27 2', + 15103 => 'Hawaii CS27 3', + 15104 => 'Hawaii CS27 4', + 15105 => 'Hawaii CS27 5', + 15131 => 'Hawaii CS83 1', + 15132 => 'Hawaii CS83 2', + 15133 => 'Hawaii CS83 3', + 15134 => 'Hawaii CS83 4', + 15135 => 'Hawaii CS83 5', + 15201 => 'Puerto Rico CS27', + 15202 => 'St Croix', + 15230 => 'Puerto Rico Virgin Is', + 15302 => 'Kentucky CS27', + 15303 => 'Kentucky CS83 North', + 15914 => 'BLM 14N feet', + 15915 => 'BLM 15N feet', + 15916 => 'BLM 16N feet', + 15917 => 'BLM 17N feet', + 16001 => 'UTM zone 1N', + 16002 => 'UTM zone 2N', + 16003 => 'UTM zone 3N', + 16004 => 'UTM zone 4N', + 16005 => 'UTM zone 5N', + 16006 => 'UTM zone 6N', + 16007 => 'UTM zone 7N', + 16008 => 'UTM zone 8N', + 16009 => 'UTM zone 9N', + 16010 => 'UTM zone 10N', + 16011 => 'UTM zone 11N', + 16012 => 'UTM zone 12N', + 16013 => 'UTM zone 13N', + 16014 => 'UTM zone 14N', + 16015 => 'UTM zone 15N', + 16016 => 'UTM zone 16N', + 16017 => 'UTM zone 17N', + 16018 => 'UTM zone 18N', + 16019 => 'UTM zone 19N', + 16020 => 'UTM zone 20N', + 16021 => 'UTM zone 21N', + 16022 => 'UTM zone 22N', + 16023 => 'UTM zone 23N', + 16024 => 'UTM zone 24N', + 16025 => 'UTM zone 25N', + 16026 => 'UTM zone 26N', + 16027 => 'UTM zone 27N', + 16028 => 'UTM zone 28N', + 16029 => 'UTM zone 29N', + 16030 => 'UTM zone 30N', + 16031 => 'UTM zone 31N', + 16032 => 'UTM zone 32N', + 16033 => 'UTM zone 33N', + 16034 => 'UTM zone 34N', + 16035 => 'UTM zone 35N', + 16036 => 'UTM zone 36N', + 16037 => 'UTM zone 37N', + 16038 => 'UTM zone 38N', + 16039 => 'UTM zone 39N', + 16040 => 'UTM zone 40N', + 16041 => 'UTM zone 41N', + 16042 => 'UTM zone 42N', + 16043 => 'UTM zone 43N', + 16044 => 'UTM zone 44N', + 16045 => 'UTM zone 45N', + 16046 => 'UTM zone 46N', + 16047 => 'UTM zone 47N', + 16048 => 'UTM zone 48N', + 16049 => 'UTM zone 49N', + 16050 => 'UTM zone 50N', + 16051 => 'UTM zone 51N', + 16052 => 'UTM zone 52N', + 16053 => 'UTM zone 53N', + 16054 => 'UTM zone 54N', + 16055 => 'UTM zone 55N', + 16056 => 'UTM zone 56N', + 16057 => 'UTM zone 57N', + 16058 => 'UTM zone 58N', + 16059 => 'UTM zone 59N', + 16060 => 'UTM zone 60N', + 16101 => 'UTM zone 1S', + 16102 => 'UTM zone 2S', + 16103 => 'UTM zone 3S', + 16104 => 'UTM zone 4S', + 16105 => 'UTM zone 5S', + 16106 => 'UTM zone 6S', + 16107 => 'UTM zone 7S', + 16108 => 'UTM zone 8S', + 16109 => 'UTM zone 9S', + 16110 => 'UTM zone 10S', + 16111 => 'UTM zone 11S', + 16112 => 'UTM zone 12S', + 16113 => 'UTM zone 13S', + 16114 => 'UTM zone 14S', + 16115 => 'UTM zone 15S', + 16116 => 'UTM zone 16S', + 16117 => 'UTM zone 17S', + 16118 => 'UTM zone 18S', + 16119 => 'UTM zone 19S', + 16120 => 'UTM zone 20S', + 16121 => 'UTM zone 21S', + 16122 => 'UTM zone 22S', + 16123 => 'UTM zone 23S', + 16124 => 'UTM zone 24S', + 16125 => 'UTM zone 25S', + 16126 => 'UTM zone 26S', + 16127 => 'UTM zone 27S', + 16128 => 'UTM zone 28S', + 16129 => 'UTM zone 29S', + 16130 => 'UTM zone 30S', + 16131 => 'UTM zone 31S', + 16132 => 'UTM zone 32S', + 16133 => 'UTM zone 33S', + 16134 => 'UTM zone 34S', + 16135 => 'UTM zone 35S', + 16136 => 'UTM zone 36S', + 16137 => 'UTM zone 37S', + 16138 => 'UTM zone 38S', + 16139 => 'UTM zone 39S', + 16140 => 'UTM zone 40S', + 16141 => 'UTM zone 41S', + 16142 => 'UTM zone 42S', + 16143 => 'UTM zone 43S', + 16144 => 'UTM zone 44S', + 16145 => 'UTM zone 45S', + 16146 => 'UTM zone 46S', + 16147 => 'UTM zone 47S', + 16148 => 'UTM zone 48S', + 16149 => 'UTM zone 49S', + 16150 => 'UTM zone 50S', + 16151 => 'UTM zone 51S', + 16152 => 'UTM zone 52S', + 16153 => 'UTM zone 53S', + 16154 => 'UTM zone 54S', + 16155 => 'UTM zone 55S', + 16156 => 'UTM zone 56S', + 16157 => 'UTM zone 57S', + 16158 => 'UTM zone 58S', + 16159 => 'UTM zone 59S', + 16160 => 'UTM zone 60S', + 16200 => 'Gauss Kruger zone 0', + 16201 => 'Gauss Kruger zone 1', + 16202 => 'Gauss Kruger zone 2', + 16203 => 'Gauss Kruger zone 3', + 16204 => 'Gauss Kruger zone 4', + 16205 => 'Gauss Kruger zone 5', + 17348 => 'Map Grid of Australia 48', + 17349 => 'Map Grid of Australia 49', + 17350 => 'Map Grid of Australia 50', + 17351 => 'Map Grid of Australia 51', + 17352 => 'Map Grid of Australia 52', + 17353 => 'Map Grid of Australia 53', + 17354 => 'Map Grid of Australia 54', + 17355 => 'Map Grid of Australia 55', + 17356 => 'Map Grid of Australia 56', + 17357 => 'Map Grid of Australia 57', + 17358 => 'Map Grid of Australia 58', + 17448 => 'Australian Map Grid 48', + 17449 => 'Australian Map Grid 49', + 17450 => 'Australian Map Grid 50', + 17451 => 'Australian Map Grid 51', + 17452 => 'Australian Map Grid 52', + 17453 => 'Australian Map Grid 53', + 17454 => 'Australian Map Grid 54', + 17455 => 'Australian Map Grid 55', + 17456 => 'Australian Map Grid 56', + 17457 => 'Australian Map Grid 57', + 17458 => 'Australian Map Grid 58', + 18031 => 'Argentina 1', + 18032 => 'Argentina 2', + 18033 => 'Argentina 3', + 18034 => 'Argentina 4', + 18035 => 'Argentina 5', + 18036 => 'Argentina 6', + 18037 => 'Argentina 7', + 18051 => 'Colombia 3W', + 18052 => 'Colombia Bogota', + 18053 => 'Colombia 3E', + 18054 => 'Colombia 6E', + 18072 => 'Egypt Red Belt', + 18073 => 'Egypt Purple Belt', + 18074 => 'Extended Purple Belt', + 18141 => 'New Zealand North Island Nat Grid', + 18142 => 'New Zealand South Island Nat Grid', + 19900 => 'Bahrain Grid', + 19905 => 'Netherlands E Indies Equatorial', + 19912 => 'RSO Borneo', + 19926 => 'Stereo 70', + 32767 => 'User Defined', + }, + }, + 3075 => { + Name => 'ProjCoordTrans', + PrintConv => { + # geo_ctrans + 1 => 'Transverse Mercator', + 2 => 'Transverse Mercator Modified Alaska', + 3 => 'Oblique Mercator', + 4 => 'Oblique Mercator Laborde', + 5 => 'Oblique Mercator Rosenmund', + 6 => 'Oblique Mercator Spherical', # not advisable + 7 => 'Mercator', + 8 => 'Lambert Conf Conic 2SP', + 9 => 'Lambert Conf Conic 1SP', + 10 => 'Lambert Azim Equal Area', + 11 => 'Albers Equal Area', + 12 => 'Azimuthal Equidistant', + 13 => 'Equidistant Conic', + 14 => 'Stereographic', + 15 => 'Polar Stereographic', + 16 => 'Oblique Stereographic', # not advisable + 17 => 'Equirectangular', + 18 => 'Cassini Soldner', + 19 => 'Gnomonic', + 20 => 'Miller Cylindrical', + 21 => 'Orthographic', + 22 => 'Polyconic', + 23 => 'Robinson', + 24 => 'Sinusoidal', + 25 => 'VanDerGrinten', + 26 => 'New Zealand Map Grid', + 27 => 'Transverse Mercator South Orientated', + 28 => 'Cylindrical Equal Area', + 32767 => 'User Defined', + }, + }, + 3076 => { + Name => 'ProjLinearUnits', + SeparateTable => 'Units', + PrintConv => \%epsg_units, + }, + 3077 => 'ProjLinearUnitSize', + 3078 => 'ProjStdParallel1', + 3079 => 'ProjStdParallel2', + 3080 => 'ProjNatOriginLong', + 3081 => 'ProjNatOriginLat', + 3082 => 'ProjFalseEasting', + 3083 => 'ProjFalseNorthing', + 3084 => 'ProjFalseOriginLong', + 3085 => 'ProjFalseOriginLat', + 3086 => 'ProjFalseOriginEasting', + 3087 => 'ProjFalseOriginNorthing', + 3088 => 'ProjCenterLong', + 3089 => 'ProjCenterLat', + 3090 => 'ProjCenterEasting', + 3091 => 'ProjCenterNorthing', + 3092 => 'ProjScaleAtNatOrigin', + 3093 => 'ProjScaleAtCenter', + 3094 => 'ProjAzimuthAngle', + 3095 => 'ProjStraightVertPoleLong', + 3096 => 'ProjRectifiedGridAngle', + 4096 => { + Name => 'VerticalCSType', + SeparateTable => 'VerticalCS', + PrintConv => \%epsg_vertcs, + }, + 4097 => 'VerticalCitation', + 4098 => { + Name => 'VerticalDatum', + SeparateTable => 'VerticalCS', + PrintConv => \%epsg_vertcs, + }, + 4099 => { + Name => 'VerticalUnits', + SeparateTable => 'Units', + PrintConv => \%epsg_units, + }, +# +# ChartTiff extensions (ref 2) +# + 47001 => { + Name => 'ChartFormat', + PrintConv => { + 47500 => 'General', + 47501 => 'Coastal', + 47502 => 'Harbor', + 47503 => 'SailingInternational', + 47504 => 'SmallCraft Route', + 47505 => 'SmallCraftArea', + 47506 => 'SmallCraftFolio', + 47507 => 'Topographic', + 47508 => 'Recreation', + 47509 => 'Index', + 47510 => 'Inset', + }, + }, + 47002 => 'ChartSource', + 47003 => 'ChartSourceEdition', + 47004 => 'ChartSourceDate', + 47005 => 'ChartCorrDate', + 47006 => 'ChartCountryOrigin', + 47007 => 'ChartRasterEdition', + 47008 => { + Name => 'ChartSoundingDatum', + PrintConv => { + 47600 => 'Equatorial Spring Low Water', + 47601 => 'Indian Spring Low Water', + 47602 => 'Lowest Astronomical Tide', + 47603 => 'Lowest Low Water', + 47604 => 'Lowest Normal Low Water', + 47605 => 'Mean Higher High Water', + 47606 => 'Mean High Water', + 47607 => 'Mean High Water Springs', + 47608 => 'Mean Lower Low Water', + 47609 => 'Mean Lower Low Water Springs', + 47610 => 'Mean Low Water', + 47611 => 'Mean Sea Level', + 47612 => 'Tropic Higher High Water', + 47613 => 'Tropic Lower Low Water', + }, + }, + 47009 => { + Name => 'ChartDepthUnits', + SeparateTable => 'Units', + PrintConv => \%epsg_units, + }, + 47010 => 'ChartMagVar', + 47011 => 'ChartMagVarYear', + 47012 => 'ChartMagVarAnnChange', + 47013 => 'ChartWGSNSShift', + 47015 => 'InsetNWPixelX', + 47016 => 'InsetNWPixelY', + 47017 => 'ChartContourInterval', +); + +#------------------------------------------------------------------------------ +# Inputs: 0) ExifTool object ref +# Notes: byte order must be set before calling this routine +sub ProcessGeoTiff($) +{ + my $et = shift; + my $dirData = $et->GetValue('GeoTiffDirectory', 'ValueConv') or return; + + # avoid re-processing if another EXIF directory is found + $$et{DidGeoTiff} and $$et{DidGeoTiff} eq $dirData and return; + $$et{DidGeoTiff} = $dirData; + + my $doubleData = $et->GetValue('GeoTiffDoubleParams', 'ValueConv'); + my $asciiData = $et->GetValue('GeoTiffAsciiParams', 'ValueConv'); + my $verbose = $et->Options('Verbose'); + + if (length($$dirData) >= 8 and + length($$dirData) >= 8 * (Get16u($dirData,6) + 1)) + { + my $version = Get16u($dirData,0); + my $revision = Get16u($dirData,2); + my $minorRev = Get16u($dirData,4); + my $numEntries = Get16u($dirData,6); + + if ($verbose) { + $$et{INDENT} .= '| '; + $et->VerboseDir('GeoTiff',$numEntries); + } + # generate version number tag (not a real GeoTiff tag) + my $tagTable = GetTagTable("Image::ExifTool::GeoTiff::Main"); + my $tagInfo = $et->GetTagInfo($tagTable, 1); + $tagInfo and $et->FoundTag($tagInfo,"$version.$revision.$minorRev"); + + my $i; + for ($i=0; $i<$numEntries; ++$i) { + my $pt = 8 * ($i + 1); + my $tag = Get16u($dirData, $pt); + $tagInfo = $et->GetTagInfo($tagTable, $tag) or next; + my $loc = Get16u($dirData, $pt+2); + my $count = Get16u($dirData, $pt+4); + my $offset = Get16u($dirData, $pt+6); + my $format = $geoTiffFormat{$loc}; + my ($val, $dataPt); + if (not $format) { + $et->Warn("Unknown GeoTiff location ($loc) for $$tagInfo{Name}"); + next; + } elsif ($format eq 'double') { # in the double parms + $dataPt = $doubleData; + } elsif ($format eq 'string') { # in the ASCII parms + $dataPt = $asciiData; + } elsif ($format eq 'int16u') { # in the GeoTiffDirectory data + $dataPt = $dirData; + unless ($loc) { # is value is stored in offset? + $count = 1; # (implied by location of 0) + $offset = ($pt + 6) / 2; # offset of the "offset" value + } + } + my $size = Image::ExifTool::FormatSize($format); + if (not $dataPt or length($$dataPt) < $size*($offset+$count)) { + $et->Warn("Missing $format data for $$tagInfo{Name}"); + next; + } + $offset *= $size; + $val = Image::ExifTool::ReadValue($dataPt, $offset, $format, + $count, length($$dataPt)-$offset); + # remove trailing terminator (NULL or '|') from string value + $val =~ s/(\0|\|)$// if $format eq 'string'; + $verbose and $et->VerboseInfo($tag, $tagInfo, + 'Table' => $tagTable, + 'Index' => $i, + 'Value' => $val, + 'DataPt' => $dataPt, + 'Start' => $offset, + 'Format' => $format, + 'Count' => $count, + 'Size' => $count * $size, + ); + $et->FoundTag($tagInfo, $val); + } + if ($verbose) { + $$et{INDENT} =~ s/..$//; + } + } else { + $et->Warn('Bad GeoTIFF directory'); + } + # extract block tags only if requested + unless ($$et{OPTIONS}{RequestAll}) { + $et->DeleteTag('GeoTiffDirectory') unless $$et{REQ_TAG_LOOKUP}{geotiffdirectory}; + $et->DeleteTag('GeoTiffDoubleParams') unless $$et{REQ_TAG_LOOKUP}{geotiffdoubleparams}; + $et->DeleteTag('GeoTiffAsciiParams') unless $$et{REQ_TAG_LOOKUP}{geotiffasciiparams}; + } +} + + +1; #end + +__END__ + +=head1 NAME + +Image::ExifTool::GeoTiff - Read GeoTiff meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +GeoTiff meta information. GeoTiff information is used in images to specify +exact geometric mappings used to transform the image to real world +coordinates. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 ftp://ftp.remotesensing.org/geotiff/libgeotiff/libgeotiff-1.1.4.tar.gz + +=item http://www.charttiff.com/whitepapers.shtml + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/GeoTiff Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Geotag.pm b/ExifTool/lib/Image/ExifTool/Geotag.pm new file mode 100644 index 0000000..ae78a1c --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Geotag.pm @@ -0,0 +1,1518 @@ +#------------------------------------------------------------------------------ +# File: Geotag.pm +# +# Description: Geotagging utility routines +# +# Revisions: 2009/04/01 - P. Harvey Created +# 2009/09/27 - PH Added Geosync feature +# 2009/06/25 - PH Read Garmin TCX track logs +# 2009/09/11 - PH Read ITC GPS track logs +# 2012/01/08 - PH Extract orientation information from PTNTHPR +# 2012/05/08 - PH Read Winplus Beacon .TXT files +# 2015/05/30 - PH Read Bramor gEO log files +# 2016/07/13 - PH Added ability to geotag date/time only +# 2019/07/02 - PH Added ability to read IMU CSV files +# 2019/11/10 - PH Also write pitch to CameraElevationAngle +# 2020/12/01 - PH Added ability to read DJI CSV log files +# 2022/06/21 - PH Added ability to read Google Takeout JSON files +# +# References: 1) http://www.topografix.com/GPX/1/1/ +# 2) http://www.gpsinformation.org/dale/nmea.htm#GSA +# 3) http://code.google.com/apis/kml/documentation/kmlreference.html +# 4) http://www.fai.org/gliding/system/files/tech_spec_gnss.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::Geotag; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:Public); +use Image::ExifTool::GPS; + +$VERSION = '1.72'; + +sub JITTER() { return 2 } # maximum time jitter + +sub GetTime($); +sub SetGeoValues($$;$); +sub PrintFixTime($); +sub PrintFix($@); + +# XML tags that we recognize (keys are forced to lower case) +my %xmlTag = ( + lat => 'lat', # GPX + latitude => 'lat', # Garmin + latitudedegrees => 'lat', # Garmin TCX + lon => 'lon', # GPX + longitude => 'lon', # Garmin + longitudedegrees => 'lon', # Garmin TCX + ele => 'alt', # GPX + elevation => 'alt', # PH + alt => 'alt', # PH + altitude => 'alt', # Garmin + altitudemeters => 'alt', # Garmin TCX + 'time' => 'time', # GPX/Garmin + fix => 'fixtype', # GPX + hdop => 'hdop', # GPX + vdop => 'vdop', # GPX + pdop => 'pdop', # GPX + sat => 'nsats', # GPX + atemp => 'atemp', # GPX (Garmin 550t) + when => 'time', # KML + coordinates => 'coords', # KML + coord => 'coords', # KML, as written by Google Location History + begin => 'begin', # KML TimeSpan + end => 'time', # KML TimeSpan + course => 'dir', # (written by Arduino) + pitch => 'pitch', # (written by Arduino) + roll => 'roll', # (written by Arduino) + # XML containers (fix is reset at the opening tag of these properties) + wpt => '', # GPX + trkpt => '', # GPX + rtept => '', # GPX + trackpoint => '', # Garmin + placemark => '', # KML +); + +# fix information keys which must be interpolated around a circle +my %cyclical = (lon => 1, track => 1, dir => 1, pitch => 1, roll => 1); +my %cyc180 = (lon => 1, pitch => 1, roll => 1); # wraps from 180 to -180 + +# fix information keys for each of our general categories +my %fixInfoKeys = ( + 'pos' => [ 'lat', 'lon' ], + track => [ 'track', 'speed' ], + alt => [ 'alt' ], + orient => [ 'dir', 'pitch', 'roll' ], + atemp => [ 'atemp' ], +); + +my %isOrient = ( dir => 1, pitch => 1, roll => 1 ); # test for orientation key + +# tags which may exist separately in some formats (eg. CSV) +my %sepTags = ( dir => 1, pitch => 1, roll => 1, track => 1, speed => 1 ); + +# conversion factors for GPSSpeed (standard EXIF units only) +my %speedConv = ( + 'K' => 1.852, # km/h per knot + 'M' => 1.150779448, # mph per knot + 'k' => 'K', # (allow lower case) + 'm' => 'M', + 'km/h' => 'K', # (allow other formats) + 'mph' => 'M', +); + +# all recognized speed conversion factors (non-EXIF included) +my %otherConv = ( + 'km/h' => 1.852, + 'mph' => 1.150779448, + 'm/s' => 0.514444, +); + +my $secPerDay = 24 * 3600; # a useful constant + +#------------------------------------------------------------------------------ +# Load GPS track log file +# Inputs: 0) ExifTool ref, 1) track log data or file name +# Returns: geotag hash data reference or error string +# - the geotag hash has the following members: +# Points - hash of GPS fix information hashes keyed by Unix time +# Times - list of sorted Unix times (keys of Points hash) +# NoDate - flag if some points have no date (ie. referenced to 1970:01:01) +# IsDate - flag if some points have date +# Has - hash of flags for available information (track, orient, alt) +# - the fix information hash may contain: +# lat - signed latitude (required) +# lon - signed longitude (required) +# alt - signed altitude +# time - fix time in UTC as XML string +# fixtype- type of fix ('none'|'2d'|'3d'|'dgps'|'pps') +# pdop - dilution of precision +# hdop - horizontal DOP +# vdop - vertical DOP +# sats - comma-separated list of active satellites +# nsats - number of active satellites +# track - track heading (deg true) +# dir - image direction (deg true) +# pitch - pitch angle (deg) +# roll - roll angle (deg) +# speed - speed (knots) +# first - flag set for first fix of track +# - concatenates new data with existing track data stored in ExifTool NEW_VALUE +# for the Geotag tag +sub LoadTrackLog($$;$) +{ + local ($_, $/, *EXIFTOOL_TRKFILE); + my ($et, $val) = @_; + my ($raf, $from, $time, $isDate, $noDate, $noDateChanged, $lastDate, $dateFlarm); + my ($nmeaStart, $fixSecs, @fixTimes, $lastFix, %nmea, @csvHeadings, $sortFixes); + my ($canCut, $cutPDOP, $cutHDOP, $cutSats, $e0, $e1, @tmp, $trackFile, $trackTime); + my $scaleSpeed; + + unless (eval { require Time::Local }) { + return 'Geotag feature requires Time::Local installed'; + } + # add data to existing track + my $geotag = $et->GetNewValue('Geotag') || { }; + + # initialize track points lookup + my $points = $$geotag{Points}; + $points or $points = $$geotag{Points} = { }; + + # get lookup for available information types + my $has = $$geotag{Has}; + $has or $has = $$geotag{Has} = { 'pos' => 1 }; + + my $format = ''; + # is $val track log data? + if ($val =~ /^(\xef\xbb\xbf)?<(\?xml|gpx)[\s>]/) { + $format = 'XML'; + $/ = '>'; # set input record separator to '>' for XML/GPX data + } elsif ($val =~ /(\x0d\x0a|\x0d|\x0a)/) { + $/ = $1; + } else { + # $val is track file name + if ($et->Open(\*EXIFTOOL_TRKFILE, $val)) { + $trackFile = $val; + $raf = new File::RandomAccess(\*EXIFTOOL_TRKFILE); + unless ($raf->Read($_, 256)) { + close EXIFTOOL_TRKFILE; + return "Empty track file '${val}'"; + } + # look for XML or GPX header (might as well allow UTF-8 BOM) + if (/^(\xef\xbb\xbf)?<(\?xml|gpx)[\s>]/) { + $format = 'XML'; + $/ = '>'; # set input record separator to '>' for XML/GPX data + } elsif (/(\x0d\x0a|\x0d|\x0a)/) { + $/ = $1; + } else { + close EXIFTOOL_TRKFILE; + return "Invalid track file '${val}'"; + } + $raf->Seek(0,0); + $from = "file '${val}'"; + } elsif ($val eq 'DATETIMEONLY') { + $$geotag{DateTimeOnly} = 1; + $$geotag{IsDate} = 1; + $et->VPrint(0, 'Geotagging date/time only'); + return $geotag; + } else { + return "Error opening GPS file '${val}'"; + } + } + unless ($from) { + # set up RAF for reading log file in memory + $raf = new File::RandomAccess(\$val); + $from = 'data'; + } + + # initialize cuts + my $maxHDOP = $et->Options('GeoMaxHDOP'); + my $maxPDOP = $et->Options('GeoMaxPDOP'); + my $minSats = $et->Options('GeoMinSats'); + my $isCut = $maxHDOP || $maxPDOP || $minSats; + + my $numPoints = 0; + my $skipped = 0; + my $lastSecs = 0; + my $fix = { }; + my $csvDelim = $et->Options('CSVDelim'); + $csvDelim = ',' unless defined $csvDelim; + my (@saveFix, @saveTime, $timeSpan); + for (;;) { + $raf->ReadLine($_) or last; + # determine file format + if (not $format) { + s/^\xef\xbb\xbf//; # remove leading BOM if it exists + if (/^\xff\xfe|\xfe\xff/) { + return "ExifTool doesn't yet read UTF16-format track logs"; + } + if (/^<(\?xml|gpx)[\s>]/) { # look for XML or GPX header + $format = 'XML'; + # check for NMEA sentence + # (must ONLY start with ones that have timestamps! eg. not GSA or PTNTHPR!) + } elsif (/^.*\$([A-Z]{2}(RMC|GGA|GLL|ZDA)|PMGNTRK),/) { + $format = 'NMEA'; + $nmeaStart = $2 || $1; # save type of first sentence + } elsif (/^A(FLA|XSY|FIL)/) { + # (don't set format yet because we want to read HFDTE first) + $nmeaStart = 'B' ; + next; + } elsif (/^HFDTE(?:DATE:)?(\d{2})(\d{2})(\d{2})/) { + my $year = $3 + ($3 >= 70 ? 1900 : 2000); + $dateFlarm = Time::Local::timegm(0,0,0,$1,$2-1,$year); + $nmeaStart = 'B' ; + $format = 'IGC'; + next; + } elsif ($nmeaStart and /^B/) { + # parse IGC fixes without a date + $format = 'IGC'; + } elsif (/^TP,D,/) { + $format = 'Winplus'; + } elsif (/^\s*\d+\s+.*\sypr\s*$/ and (@tmp=split) == 12) { + $format = 'Bramor'; + } elsif (((/\b(GPS)?Date/i and /\b(GPS)?(Date)?Time/i) or /\bTime\(seconds\)/i) and /\Q$csvDelim/) { + chomp; + @csvHeadings = split /\Q$csvDelim/; + $format = 'CSV'; + # convert recognized headings to our parameter names + foreach (@csvHeadings) { + my $head = $_; + my $param; + my $xtra = ''; + s/^GPS ?//; # remove leading "GPS" to simplify regex patterns + if (/^Time ?\(seconds\)$/i) { # DJI + # DJI CSV log files have a column "Time(seconds)" which is seconds since + # the start of the flight. The date/time is obtained from the file name. + $param = 'runtime'; + if ($trackFile and $trackFile =~ /(\d{4})-(\d{2})-(\d{2})[^\/]+(\d{2})-(\d{2})-(\d{2})[^\/]*$/) { + $trackTime = Image::ExifTool::TimeLocal($6,$5,$4,$3,$2-1,$1); + my $utc = PrintFixTime($trackTime); + my $tzs = Image::ExifTool::TimeZoneString([$6,$5,$4,$3,$2-1,$1-1900],$trackTime); + $et->VPrint(2, " DJI start time: $utc (local timezone is $tzs)\n"); + } else { + return 'Error getting start time from file name for DJI CSV track file'; + } + } elsif (/^Date ?Time/i) { # ExifTool addition + $param = 'datetime'; + } elsif (/^Date/i) { + $param = 'date'; + } elsif (/^Time(?! ?\(text\))/i) { # (ignore DJI "Time(text)" column) + $param = 'time'; + } elsif (/^(Pos)?Lat/i) { + $param = 'lat'; + /ref$/i and $param .= 'ref'; + } elsif (/^(Pos)?Lon/i) { + $param = 'lon'; + /ref$/i and $param .= 'ref'; + } elsif (/^(Pos)?Alt/i) { + $param = 'alt'; + } elsif (/^Speed/i) { + $param = 'speed'; + # (recognize units in brackets) + if (m{\((mph|km/h|m/s)\)}) { + $scaleSpeed = $otherConv{$1}; + $xtra = " in $1"; + } else { + $xtra = ' in knots'; + } + } elsif (/^(Angle)?(Heading|Track|Bearing)/i) { + $param = 'track'; + } elsif (/^(Angle)?Pitch/i or /^Camera ?Elevation ?Angle/i) { + $param = 'pitch'; + } elsif (/^(Angle)?Roll/i) { + $param = 'roll'; + } elsif (/^Img ?Dir/i) { + $param = 'dir'; + } + if ($param) { + $et->VPrint(2, "CSV column '${head}' is $param$xtra\n"); + $_ = $param; + } else { + $et->VPrint(2, "CSV column '${head}' ignored\n"); + $_ = ''; # ignore this column + } + } + next; + } elsif (/"(timelineObjects|placeVisit|activitySegment|latitudeE7)":/) { + # Google Takeout JSON format + $format = 'JSON'; + $sortFixes = 1; # (fixes are not all in order for this format) + } else { + # search only first 50 lines of file for a valid fix + last if ++$skipped > 50; + next; + } + } +# +# XML format (GPX, KML, Garmin XML/TCX etc) +# + if ($format eq 'XML') { + my ($arg, $tok, $td); + s/\s*=\s*(['"])\s*/=$1/g; # remove unnecessary white space in attributes + # Workaround for KML generated by Google Location History: + # lat/lon/alt are space-separated; we want commas. + s{(\S+)\s+(\S+)\s+(\S+)(</gx:coord>)}{$1,$2,$3$4}; + foreach $arg (split) { + # parse attributes (eg. GPX 'lat' and 'lon') + # (note: ignore namespace prefixes if they exist) + if ($arg =~ /^(\w+:)?(\w+)=(['"])(.*?)\3/g) { + my $tag = $xmlTag{lc $2}; + if ($tag) { + $$fix{$tag} = $4; + if ($isOrient{$tag}) { + $$has{orient} = 1; + } elsif ($tag eq 'alt') { + # validate altitude + undef $$fix{alt} if defined $$fix{alt} and $$fix{alt} !~ /^[+-]?\d+\.?\d*/; + $$has{alt} = 1 if $$fix{alt}; # set "has altitude" flag if appropriate + } elsif ($tag eq 'atemp') { + $$has{atemp} = 1; + } + } + } + # loop through XML elements + while ($arg =~ m{([^<>]*)<(/)?(\w+:)?(\w+)(>|$)}g) { + my $tag = $xmlTag{$tok = lc $4}; + # parse as a simple property if this element has a value + if (defined $tag and not $tag) { + # a containing property was opened or closed + if (not $2) { + # opened: start a new fix + $lastFix = $fix = { }; + undef @saveFix; + next; + } elsif ($fix and $lastFix and %$fix) { + # closed: transfer additional tags from current fix + foreach (keys %$fix) { + $$lastFix{$_} = $$fix{$_} unless defined $$lastFix{$_}; + } + undef $lastFix; + } + } + if (length $1) { + if ($tag) { + if ($tag eq 'coords') { + # save other fixes if there are more than one + if (defined $$fix{lon} and defined $$fix{lat} and defined $$fix{alt}) { + push @saveFix, [ @$fix{'lon','lat','alt'} ]; + } + # read KML "Point" coordinates + @$fix{'lon','lat','alt'} = split ',', $1; + $$has{alt} = 1 if $$fix{alt}; + } else { + if ($tok eq 'when' and $$fix{'time'}) { + push @saveTime, $1; # flightaware KML stores times in array + } else { + $$fix{$tag} = $1; + } + if ($isOrient{$tag}) { + $$has{orient} = 1; + } elsif ($tag eq 'alt') { + # validate altitude + undef $$fix{alt} if defined $$fix{alt} and $$fix{alt} !~ /^[+-]?\d+\.?\d*/; + $$has{alt} = 1 if $$fix{alt}; # set "has altitude" flag if appropriate + } elsif ($tag eq 'atemp') { + $$has{atemp} = 1; + } + } + } + next; + } elsif ($tok eq 'td') { + $td = 1; + } + # validate and store GPS fix + next unless defined $$fix{lat} and defined $$fix{lon}; + unless (defined $$fix{'time'}) { + next unless @saveTime; + $$fix{'time'} = shift @saveTime; # get next time in flightaware KML list + } + unless ($$fix{lat} =~ /^[+-]?\d+\.?\d*/ and $$fix{lon} =~ /^[+-]?\d+\.?\d*/) { + $e0 or $et->VPrint(0, "Coordinate format error in $from\n"), $e0 = 1; + next; + } + unless (defined($time = GetTime($$fix{'time'}))) { + $e1 or $et->VPrint(0, "Timestamp format error in $from\n"), $e1 = 1; + next; + } + $isDate = 1; + $canCut= 1 if defined $$fix{pdop} or defined $$fix{hdop} or defined $$fix{nsats}; + # generate extra fixes assuming an equally spaced track + if ($$fix{begin}) { + my $begin = GetTime($$fix{begin}); + undef $$fix{begin}; + if (defined $begin and $begin < $time) { + $$fix{span} = $timeSpan = ($timeSpan || 0) + 1; + my $i; + # duplicate the fix if there is only one so we will have + # a fix and the start and end of the TimeSpan + @saveFix or push @saveFix, [ @$fix{'lon','lat','alt'} ]; + for ($i=0; $i<@saveFix; ++$i) { + my $t = $begin + ($time - $begin) * ($i / scalar(@saveFix)); + my %f; + @f{'lon','lat','alt'} = @{$saveFix[$i]}; + $t += 0.001 if not $i and $$points{$t}; # (avoid dupicates) + $f{span} = $timeSpan; + $$points{$t} = \%f; + push @fixTimes, $t; + } + } + } + $$points{$time} = $fix; + push @fixTimes, $time; # save times of all fixes in order + $fix = { }; + undef @saveFix; + ++$numPoints; + } + } + # last ditch check KML description for timestamp (assume it is UTC) + $$fix{'time'} = "$1T$2Z" if $td and not $$fix{'time'} and + /[\s>](\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}:\d{2}(\.\d+)?)/; + next; +# +# Winplus Beacon text file +# + } elsif ($format eq 'Winplus') { + # TP,D, 44.933666667, -93.186555556, 10/26/2011, 19:07:28, 0 + # latitude longitude date time + /^TP,D,\s*([-+]?\d+\.\d*),\s*([-+]?\d+\.\d*),\s*(\d+)\/(\d+)\/(\d{4}),\s*(\d+):(\d+):(\d+)/ or next; + $$fix{lat} = $1; + $$fix{lon} = $2; + $time = Time::Local::timegm($8,$7,$6,$4,$3-1,$5); +DoneFix: $isDate = 1; + $$points{$time} = $fix; + push @fixTimes, $time; + $fix = { }; + ++$numPoints; + next; +# +# Bramor gEO log file +# + } elsif ($format eq 'Bramor') { + # 1 0015 18.723675 50.672752 149 169.31 22/04/2015 07:06:55 169.31 8.88 28.07 ypr + # ? index latitude longitude alt track date time dir pitch roll + my @parts = split ' ', $_; + next unless @parts == 12 and $parts[11] eq 'ypr'; + my @d = split m{/}, $parts[6]; # date (dd/mm/YYYY) + my @t = split m{:}, $parts[7]; # time (HH:MM:SS) + next unless @d == 3 and @t == 3; + @$fix{qw(lat lon alt track dir pitch roll)} = @parts[2,3,4,5,8,9,10]; + # (add the seconds afterwards in case some models have decimal seconds) + $time = Time::Local::timegm(0,$t[1],$t[0],$d[0],$d[1]-1,$d[2]) + $t[2]; + # set necessary flags for extra available information + @$has{qw(alt track orient)} = (1,1,1); + goto DoneFix; # save this fix + } elsif ($format eq 'CSV') { + chomp; + my @vals = split /\Q$csvDelim/; +# +# CSV format output of GPS/IMU POS system +# Date* - date in DD/MM/YYYY format +# Time* - time in HH:MM:SS.SSS format +# [Pos]Lat* - latitude in decimal degrees +# [Pos]Lon* - longitude in decimal degrees +# [Pos]Alt* - altitude in m relative to sea level +# [Angle]Heading* - GPSTrack in degrees true +# [Angle]Pitch* - pitch angle in degrees +# [Angle]Roll* - roll angle in degrees +# (ExifTool enhancements allow for standard tag names or descriptions as the column headings, +# add support for time zones and flexible coordinates, and allow new DateTime and Shift columns) +# + my ($param, $date, $secs, %neg); + foreach $param (@csvHeadings) { + my $val = shift @vals; + last unless defined $val and length($val); + next unless $param; + if ($param eq 'datetime') { + # (fix formats like "24.07.2016 13:47:30") + $val =~ s/^(\d{2})[^\d](\d{2})[^\d](\d{4}) /$3:$2:$1 /; + local $SIG{'__WARN__'} = sub { }; + my $dateTime = $et->InverseDateTime($val); + if ($dateTime) { + $date = Image::ExifTool::GetUnixTime($val, 2); + $secs = 0; + } + } elsif ($param eq 'date') { + if ($val =~ m{^(\d{2})/(\d{2})/(\d{4})$}) { + $date = Time::Local::timegm(0,0,0,$1,$2-1,$3); + } elsif ($val =~ /(\d{4}).*?(\d{2}).*?(\d{2})/) { + $date = Time::Local::timegm(0,0,0,$3,$2-1,$1); + } + } elsif ($param eq 'time') { + if ($val =~ /^(\d{1,2}):(\d{2}):(\d{2}(\.\d+)?).*?(([-+])(\d{1,2}):?(\d{2}))?/) { + $secs = (($1 * 60) + $2) * 60 + $3; + # adjust for time zone if specified + $secs += ($7 * 60 + $8) * ($6 eq '-' ? 60 : -60) if $5; + } + } elsif ($param eq 'lat' or $param eq 'lon') { + $$fix{$param} = Image::ExifTool::GPS::ToDegrees($val, 1); + } elsif ($param eq 'latref') { + $neg{lat} = 1 if $val =~ /^S/i; + } elsif ($param eq 'lonref') { + $neg{lon} = 1 if $val =~ /^W/i; + } elsif ($param eq 'runtime') { + $date = $trackTime; + $secs = $val; + } else { + $val /= $scaleSpeed if $scaleSpeed and $param eq 'speed'; + $$fix{$param} = $val; + $$has{$param} = 1 if $sepTags{$param}; + } + } + # make coordinate negative according to reference direction if necessary + foreach $param (keys %neg) { + next unless defined $$fix{$param}; + $$fix{$param} = -abs($$fix{$param}); + } + if ($date and defined $secs and defined $$fix{lat} and defined $$fix{lon}) { + $time = $date + $secs; + $$has{alt} = 1 if defined $$fix{alt}; + $$has{track} = 1 if defined $$fix{track}; + $$has{orient} = 1 if defined $$fix{pitch}; + goto DoneFix; + } + next; + } elsif ($format eq 'JSON') { + # Google Takeout JSON format + if (/"(latitudeE7|longitudeE7|latE7|lngE7|timestamp)":\s*"?(.*?)"?,?\s*[\x0d\x0a]/) { + if ($1 eq 'timestamp') { + $time = GetTime($2); + goto DoneFix if $time and $$fix{lat} and $$fix{lon}; + } elsif ($1 eq 'latitudeE7' or $1 eq 'latE7') { + $$fix{lat} = $2 * 1e-7; + } else { + $$fix{lon} = $2 * 1e-7; + } + } + next; + } + my (%fix, $secs, $date, $nmea); + if ($format eq 'NMEA') { + # ignore unrecognized NMEA sentences + # (first 2 characters: GP=GPS, GL=GLONASS, GA=Gallileo, GN=combined, BD=Beidou) + next unless /^(.*)\$([A-Z]{2}(RMC|GGA|GLL|GSA|ZDA)|PMGNTRK|PTNTHPR),/; + $nmea = $3 || $2; + $_ = substr($_, length($1)) if length($1); + } +# +# IGC (flarm) (ref 4) +# + if ($format eq 'IGC') { + # B0939564531208N00557021EA007670089100207 + # BHHMMSSDDMMmmmNDDDMMmmmEAaaaaaAAAAAxxyy + # HH MM SS DD MM mmm DDD MM mmm aaaaa AAAAA + # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + /^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{3})([NS])(\d{3})(\d{2})(\d{3})([EW])([AV])(\d{5})(\d{5})/ or next; + $fix{lat} = ($4 + ($5 + $6/1000)/60) * ($7 eq 'N' ? 1 : -1); + $fix{lon} = ($8 + ($9 +$10/1000)/60) * ($11 eq 'E' ? 1 : -1); + $fix{alt} = $12 eq 'A' ? $14 : undef; + $secs = (($1 * 60) + $2) * 60 + $3; + # wrap to next day if necessary + if ($dateFlarm) { + $dateFlarm += $secPerDay if $secs < $lastSecs - JITTER(); + $date = $dateFlarm; + } + $nmea = 'B'; +# +# NMEA RMC sentence (contains date) +# + } elsif ($nmea eq 'RMC') { + # $GPRMC,092204.999,A,4250.5589,S,14718.5084,E,0.00,89.68,211200,,*25 + # $GPRMC,093657.007,,3652.835020,N,01053.104094,E,1.642,,290913,,,A*0F + # $GPRMC,hhmmss.sss,A/V,ddmm.mmmm,N/S,dddmm.mmmm,E/W,spd(knots),dir(deg),DDMMYY,,*cs + /^\$[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/ or next; + next if $13 > 31 or $14 > 12 or $15 > 99; # validate day/month/year + $fix{lat} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1); + $fix{lon} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1); + $fix{speed} = $11 if length $11; + $fix{track} = $12 if length $12; + my $year = $15 + ($15 >= 70 ? 1900 : 2000); + $secs = (($1 * 60) + $2) * 60 + $3; + $date = Time::Local::timegm(0,0,0,$13,$14-1,$year); +# +# NMEA GGA sentence (no date) +# + } elsif ($nmea eq 'GGA') { + # $GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F + # $GPGGA,093657.000,3652.835020,N,01053.104094,E,,8,,166.924,M,40.9,M,,*77 + # $GPGGA,hhmmss.sss,ddmm.mmmm,N/S,dddmm.mmmm,E/W,0=invalid,sats,hdop,alt,M,... + /^\$[A-Z]{2}GGA,(\d{2})(\d{2})(\d+(\.\d*)?),(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),[1-6]?,(\d+)?,(\.\d+|\d+\.?\d*)?,(-?\d+\.?\d*)?,M?/ or next; + $fix{lat} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1); + $fix{lon} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1); + @fix{qw(nsats hdop alt)} = ($11,$12,$13); + $secs = (($1 * 60) + $2) * 60 + $3; + $canCut = 1; +# +# NMEA GLL sentence (no date) +# + } elsif ($nmea eq 'GLL') { + # $GPGLL,4250.5589,S,14718.5084,E,092204.999,A*2D + # $GPGLL,ddmm.mmmm,N/S,dddmm.mmmm,E/W,hhmmss.sss,A/V*cs + /^\$[A-Z]{2}GLL,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d{2})(\d{2})(\d+(\.\d*)?),A/ or next; + $fix{lat} = (($1 || 0) + $2/60) * ($3 eq 'N' ? 1 : -1); + $fix{lon} = (($4 || 0) + $5/60) * ($6 eq 'E' ? 1 : -1); + $secs = (($7 * 60) + $8) * 60 + $9; +# +# NMEA GSA sentence (satellite status, no date) +# + } elsif ($nmea eq 'GSA') { + # $GPGSA,A,3,04,05,,,,,,,,,,,pdop,hdop,vdop*HH + /^\$[A-Z]{2}GSA,[AM],([23]),((?:\d*,){11}(?:\d*)),(\d+\.?\d*|\.\d+)?,(\d+\.?\d*|\.\d+)?,(\d+\.?\d*|\.\d+)?\*/ or next; + @fix{qw(fixtype sats pdop hdop vdop)} = ($1.'d',$2,$3,$4,$5); + # count the number of acquired satellites + my @a = ($fix{sats} =~ /\d+/g); + $fix{nsats} = scalar @a; + $canCut = 1; +# +# NMEA ZDA sentence (date/time, contains date) +# + } elsif ($nmea eq 'ZDA') { + # $GPZDA,093655.000,29,09,2013,,*58 + # $GPZDA,hhmmss.ss,DD,MM,YYYY,tzh,tzm (hhmmss in UTC) + /^\$[A-Z]{2}ZDA,(\d{2})(\d{2})(\d{2}(\.\d*)?),(\d+),(\d+),(\d+)/ or next; + $secs = (($1 * 60) + $2) * 60 + $3; + $date = Time::Local::timegm(0,0,0,$5,$6-1,$7); +# +# Magellan eXplorist PMGNTRK (Proprietary MaGellaN TRacK) sentence (optional date) +# + } elsif ($nmea eq 'PMGNTRK') { + # $PMGNTRK,4415.026,N,07631.091,W,00092,M,185031.06,A,,020409*65 + # $PMGNTRK,ddmm.mmm,N/S,dddmm.mmm,E/W,alt,F/M,hhmmss.ss,A/V,trkname,DDMMYY*cs + /^\$PMGNTRK,(\d+)(\d{2}\.\d+),([NS]),(\d+)(\d{2}\.\d+),([EW]),(-?\d+\.?\d*),([MF]),(\d{2})(\d{2})(\d+(\.\d*)?),A,(?:[^,]*,(\d{2})(\d{2})(\d+))?/ or next; + $fix{lat} = ($1 + $2/60) * ($3 eq 'N' ? 1 : -1); + $fix{lon} = ($4 + $5/60) * ($6 eq 'E' ? 1 : -1); + $fix{alt} = $8 eq 'M' ? $7 : $7 * 12 * 0.0254; + $secs = (($9 * 60) + $10) * 60 + $11; + if (defined $15) { + next if $13 > 31 or $14 > 12 or $15 > 99; # validate day/month/year + # optional date is available in PMGNTRK sentence + my $year = $15 + ($15 >= 70 ? 1900 : 2000); + $date = Time::Local::timegm(0,0,0,$13,$14-1,$year); + } +# +# Honeywell HMR3000 PTNTHPR (Heading Pitch Roll) sentence (no date) +# (ref http://www.gpsarea.com/uploadfile/download/introduce/hmr3000_manual.pdf) +# + } elsif ($nmea eq 'PTNTHPR') { + # $PTNTHPR,85.9,N,-0.9,N,0.8,N*HH + # $PTNTHPR,heading,heading status,pitch,pitch status,roll,roll status,*cs + # status: L=low alarm, M=low warning, N=normal, O=high warning + # P=high alarm, C=tuning analog circuit + # (ignore this information on any alarm status) + /^\$PTNTHPR,(-?[\d.]+),[MNO],(-?[\d.]+),[MNO],(-?[\d.]+),[MNO]/ or next; + @fix{qw(dir pitch roll)} = ($1,$2,$3); + + } else { + next; # this shouldn't happen + } + # remember the NMEA formats we successfully read + $nmea{$nmea} = 1; + # use last date if necessary (and appropriate) + if (defined $secs and not defined $date and defined $lastDate) { + # wrap to next day if necessary + if ($secs < $lastSecs - JITTER()) { + $lastSecs -= $secPerDay; + $lastDate += $secPerDay; + } + # use earlier date only if we are within 10 seconds + if ($secs - $lastSecs < 10) { + # last date is close, use it for this fix + $date = $lastDate; + } else { + # last date is old, discard it + undef $lastDate; + undef $lastSecs; + } + } + # save our last date/time + if (defined $date) { + $lastDate = $date; + $lastSecs = $secs; + } +# +# Add NMEA/IGC fix to our lookup +# (this is much more complicated than it needs to be because +# the stupid NMEA format provides no end-of-fix indication) +# + # assumptions for each NMEA sentence: + # - we only parse a time if we get a lat/lon + # - we always get a time if we have a date + if ($nmea eq $nmeaStart or (defined $secs and (not defined $fixSecs or + # don't combine sentences that are outside 10 seconds apart + ($secs >= $fixSecs and $secs - $fixSecs >= 10) or + ($secs < $fixSecs and $secs + $secPerDay - $fixSecs >= 10)))) + { + # start a new fix + $fix = \%fix; + $fixSecs = $secs; + undef $noDateChanged; + # does this fix have a date/time or time stamp? + if (defined $date) { + $fix{isDate} = $isDate = 1; + $time = $date + $secs; + } elsif (defined $secs) { + $time = $secs; + $noDate = $noDateChanged = 1; + } else { + next; # wait until we have a time before adding to lookup + } + } else { + # add new data to existing fix (but don't overwrite earlier values to + # keep the coordinates in sync with the fix time) + foreach (keys %fix) { + $$fix{$_} = $fix{$_} unless defined $$fix{$_}; + } + if (defined $date) { + next if $$fix{isDate}; + # move this fix to the proper date + if (defined $fixSecs) { + delete $$points{$fixSecs}; + pop @fixTimes if @fixTimes and $fixTimes[-1] == $fixSecs; + --$numPoints; + # if we wrapped to the next day since the start of this fix, + # we must shift the date back to the day of $fixSecs + $date -= $secPerDay if $secs < $fixSecs; + } else { + $fixSecs = $secs; + } + $time = $date + $fixSecs; + $$fix{isDate} = $isDate = 1; + # revert noDate flag if it was set for this fix + $noDate = 0 if $noDateChanged; + } elsif (defined $secs and not defined $fixSecs) { + $time = $fixSecs = $secs; + $noDate = $noDateChanged = 1; + } else { + next; # wait until we have a time + } + } + # add fix to our lookup + $$points{$time} = $fix; + push @fixTimes, $time; # save time of all fixes in order + ++$numPoints; + } + $raf->Close(); + + # set date flags + if ($noDate and not $$geotag{NoDate}) { + if ($isDate) { + $et->Warn('Fixes are date-less -- will use time-only interpolation'); + } else { + $et->Warn('Some fixes are date-less -- may use time-only interpolation'); + } + $$geotag{NoDate} = 1; + } + $$geotag{IsDate} = 1 if $isDate; + + # cut bad fixes if necessary + if ($isCut and $canCut) { + $cutPDOP = $cutHDOP = $cutSats = 0; + my @goodTimes; + foreach (@fixTimes) { + $fix = $$points{$_} or next; + if ($maxPDOP and $$fix{pdop} and $$fix{pdop} > $maxPDOP) { + delete $$points{$_}; + ++$cutPDOP; + } elsif ($maxHDOP and $$fix{hdop} and $$fix{hdop} > $maxHDOP) { + delete $$points{$_}; + ++$cutHDOP; + } elsif ($minSats and defined $$fix{nsats} and $$fix{nsats} ne '' and + $$fix{nsats} < $minSats) + { + delete $$points{$_}; + ++$cutSats; + } else { + push @goodTimes, $_; + } + } + @fixTimes = @goodTimes; # update fix times + $numPoints -= $cutPDOP; + $numPoints -= $cutHDOP; + $numPoints -= $cutSats; + } + # sort fixes if necessary + @fixTimes = sort { $a <=> $b } @fixTimes if $sortFixes; + # mark first fix of the track + while (@fixTimes) { + $fix = $$points{$fixTimes[0]} or shift(@fixTimes), next; + $$fix{first} = 1; + last; + } + my $verbose = $et->Options('Verbose'); + if ($verbose) { + my $out = $et->Options('TextOut'); + $format or $format = 'unknown'; + print $out "Loaded $numPoints points from $format-format GPS track log $from\n"; + print $out "Ignored $cutPDOP points due to GeoMaxPDOP cut\n" if $cutPDOP; + print $out "Ignored $cutHDOP points due to GeoMaxHDOP cut\n" if $cutHDOP; + print $out "Ignored $cutSats points due to GeoMinSats cut\n" if $cutSats; + if ($numPoints and $verbose > 1) { + my @lbl = ('start:', 'end: '); + # (fixes may be in reverse order in GPX files) + @lbl = reverse @lbl if $fixTimes[0] > $fixTimes[-1]; + print $out " GPS track $lbl[0] " . PrintFixTime($fixTimes[0]) . "\n"; + if ($verbose > 3) { + print $out PrintFix($points, $_) foreach @fixTimes; + } + print $out " GPS track $lbl[1] " . PrintFixTime($fixTimes[-1]) . "\n"; + } + } + if ($numPoints) { + # reset timestamp list to force it to be regenerated + delete $$geotag{Times}; + # set flags for available information + $$has{alt} = 1 if $nmea{GGA} or $nmea{PMGNTRK} or $nmea{B}; # alt + $$has{track} = 1 if $nmea{RMC}; # track, speed + $$has{orient} = 1 if $nmea{PTNTHPR}; # dir, pitch, roll + return $geotag; # success! + } + return "No track points found in GPS $from"; +} + + +#------------------------------------------------------------------------------ +# Get floating point UTC time +# Inputs: 0) XML time string +# Returns: floating point time or undef on error +sub GetTime($) +{ + my $timeStr = shift; + $timeStr =~ /^(\d{4})-(\d+)-(\d+)T(\d+):(\d+):(\d+)(\.\d+)?(.*)/ or return undef; + my $time = Time::Local::timegm($6,$5,$4,$3,$2-1,$1); + $time += $7 if $7; # add fractional seconds + my $tz = $8; + # adjust for time zone (otherwise assume UTC) + # - allow timezone of +-HH:MM, +-H:MM, +-HHMM or +-HH since + # the spec is unclear about timezone format + if ($tz =~ /^([-+])(\d+):(\d{2})\b/ or $tz =~ /^([-+])(\d{2})(\d{2})?\b/) { + $tz = ($2 * 60 + ($3 || 0)) * 60; + $tz *= -1 if $1 eq '+'; # opposite sign to change back to UTC + $time += $tz; + } + return $time; +} + +#------------------------------------------------------------------------------ +# Apply Geosync time correction +# Inputs: 0) ExifTool ref, 1) Unix UTC time value +# Returns: sync time difference (and updates input time), or undef if no sync +sub ApplySyncCorr($$) +{ + my ($et, $time) = @_; + my $sync = $et->GetNewValue('Geosync'); + if (ref $sync eq 'HASH') { + my $syncTimes = $$sync{Times}; + if ($syncTimes) { + # find the nearest 2 sync points + my ($i0, $i1) = (0, scalar(@$syncTimes) - 1); + while ($i1 > $i0 + 1) { + my $pt = int(($i0 + $i1) / 2); + ($time < $$syncTimes[$pt] ? $i1 : $i0) = $pt; + } + my ($t0, $t1) = ($$syncTimes[$i0], $$syncTimes[$i1]); + # interpolate/extrapolate to account for linear camera clock drift + my $syncPoints = $$sync{Points}; + my $f = $t1 == $t0 ? 0 : ($time - $t0) / ($t1 - $t0); + $sync = $$syncPoints{$t1} * $f + $$syncPoints{$t0} * (1 - $f); + } else { + $sync = $$sync{Offset}; # use fixed time offset + } + $_[1] += $sync; + } else { + undef $sync; + } + return $sync; +} + +#------------------------------------------------------------------------------ +# Scan outwards for a fix containing the requested parameter +# Inputs: 0) name of fix parameter, 1) reference to list of fix times, +# 2) reference to fix points hash, 3) index of starting time, +# 4) direction to scan (-1 or +1), 5) maximum time difference +# Returns: 0) time for fix containing requested information (or undef) +# 1) the corresponding fix, 2) the value of the requested fix parameter +sub ScanOutwards($$$$$$) +{ + my ($key, $times, $points, $i, $dir, $maxSecs) = @_; + my $t0 = $$times[$i]; + for (;;) { + $i += $dir; + last if $i < 0 or $i >= scalar @$times; + my $t = $$times[$i]; + last if abs($t - $t0) > $maxSecs; # don't look too far + my $p = $$points{$t}; + my $v = $$p{$key}; + return($t,$p,$v) if defined $v; + } + return(); +} + +#------------------------------------------------------------------------------ +# Find nearest fix containing the specified parameter +# Inputs: 0) ExifTool ref, 1) name of fix parameter, 2) reference to list of fix times, +# 3) reference to fix points hash, 4) index of starting time, +# 5) direction to scan (-1, +1 or undef), 6) maximum time difference +# Returns: reference to fix hash or undef +sub FindFix($$$$$$$) +{ + my ($et, $key, $times, $points, $i, $dir, $maxSecs) = @_; + my ($t,$p); + if ($dir) { + ($t,$p) = ScanOutwards($key, $times, $points, $i, $dir, $maxSecs); + } else { + my ($t1, $p1) = ScanOutwards($key, $times, $points, $i, -1, $maxSecs); + my ($t2, $p2) = ScanOutwards($key, $times, $points, $i, 1, $maxSecs); + if (defined $t1) { + if (defined $t2) { + # both surrounding points are valid, so take the closest one + ($t, $p) = ($t - $t1 < $t2 - $t) ? ($t1, $p1) : ($t2, $p2); + } else { + ($t, $p) = ($t1, $p1); + } + } elsif (defined $t2) { + ($t, $p) = ($t2, $p2); + } + } + if (defined $p and $$et{OPTIONS}{Verbose} > 2) { + $et->VPrint(2, " Taking $key from fix:\n", PrintFix($points, $t)) + } + return $p; +} + +#------------------------------------------------------------------------------ +# Set new geotagging values according to date/time +# Inputs: 0) ExifTool object ref, 1) date/time value (or undef to delete tags) +# 2) optional write group +# Returns: error string, or '' on success +# Notes: Uses track data stored in ExifTool NEW_VALUE for Geotag tag +sub SetGeoValues($$;$) +{ + local $_; + my ($et, $val, $writeGroup) = @_; + my $geotag = $et->GetNewValue('Geotag'); + my $verbose = $et->Options('Verbose'); + my ($fix, $time, $fsec, $noDate, $secondTry, $iExt, $iDir); + + # remove date if none of our fixes had date information + $val =~ s/^\S+\s+// if $val and $geotag and not $$geotag{IsDate}; + + # maximum time (sec) from nearest GPS fix when position is still considered valid + my $geoMaxIntSecs = $et->Options('GeoMaxIntSecs'); + my $geoMaxExtSecs = $et->Options('GeoMaxExtSecs'); + + # use 30 minutes for a default + defined $geoMaxIntSecs or $geoMaxIntSecs = 1800; + defined $geoMaxExtSecs or $geoMaxExtSecs = 1800; + + my $times = $$geotag{Times}; + my $points = $$geotag{Points}; + my $has = $$geotag{Has}; + my $err = ''; + # loop to try date/time value first, then time-only value + while (defined $val) { + unless (defined $geotag) { + $err = 'No GPS track loaded'; + last; + } + unless ($times) { + # generate sorted timestamp list for binary search + my @times = sort { $a <=> $b } keys %$points; + $times = $$geotag{Times} = \@times; + } + unless ($times and @$times or $$geotag{DateTimeOnly}) { + $err = 'GPS track is empty'; + last; + } + unless (eval { require Time::Local }) { + $err = 'Geotag feature requires Time::Local installed'; + last; + } + # convert date/time to UTC + my ($year,$mon,$day,$hr,$min,$sec,$fs,$tz,$t0,$t1,$t2); + if ($val =~ /^(\d{4}):(\d+):(\d+)\s+(\d+):(\d+):(\d+)(\.\d*)?(Z|([-+])(\d+):(\d+))?/) { + # valid date/time value + ($year,$mon,$day,$hr,$min,$sec,$fs,$tz,$t0,$t1,$t2) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11); + } elsif ($val =~ /^(\d{2}):(\d+):(\d+)(\.\d*)?(Z|([-+])(\d+):(\d+))?/) { + # valid time-only value + ($hr,$min,$sec,$fs,$tz,$t0,$t1,$t2) = ($1,$2,$3,$4,$5,$6,$7,$8); + # use Jan. 2 to avoid going negative after tz adjustment + ($year,$mon,$day) = (1970,1,2); + $noDate = 1; + } else { + $err = 'Invalid date/time (use YYYY:mm:dd HH:MM:SS[.ss][+/-HH:MM|Z])'; + last; + } + if ($tz) { + $time = Time::Local::timegm($sec,$min,$hr,$day,$mon-1,$year); + # use timezone from date/time value + if ($tz ne 'Z') { + my $tzmin = $t1 * 60 + $t2; + $time -= ($t0 eq '-' ? -$tzmin : $tzmin) * 60; + } + } else { + # assume local timezone + $time = Image::ExifTool::TimeLocal($sec,$min,$hr,$day,$mon-1,$year); + } + # add fractional seconds + $time += $fs if $fs and $fs ne '.'; + + # bring UTC time back to Jan. 1 if no date is given + # (don't use '%' operator here because it drops fractional seconds) + $time -= int($time / $secPerDay) * $secPerDay if $noDate; + + # apply time synchronization if available + my $sync = ApplySyncCorr($et, $time); + + # save fractional seconds string + $fsec = ($time =~ /(\.\d+)$/) ? $1 : ''; + + if ($et->Options('Verbose') > 1 and not $secondTry) { + my $out = $et->Options('TextOut'); + my $str = ''; + $str .= sprintf(" (incl. Geosync offset of %+.3f sec)", $sync) if defined $sync; + unless ($tz) { + my $tzs = Image::ExifTool::TimeZoneString([$sec,$min,$hr,$day,$mon-1,$year-1900],$time); + $str .= " (local timezone is $tzs)"; + } + print $out ' Geotime value: ' . PrintFixTime($time) . "$str\n"; + } + if (not $times or not @$times) { + $fix = { }; # dummy fix to geotag date/time only + # interpolate GPS track at $time + } elsif ($time < $$times[0]) { + if ($time < $$times[0] - $geoMaxExtSecs) { + $err or $err = 'Time is too far before track'; + $et->VPrint(2, ' Track start: ', PrintFixTime($$times[0]), "\n") if $verbose > 2; + $fix = { } if $$geotag{DateTimeOnly}; + } else { + $fix = $$points{$$times[0]}; + $iExt = 0; $iDir = 1; + $et->VPrint(2, " Taking pos from fix:\n", + PrintFix($points, $$times[0])) if $verbose > 2; + } + } elsif ($time > $$times[-1]) { + if ($time > $$times[-1] + $geoMaxExtSecs) { + $err or $err = 'Time is too far beyond track'; + $et->VPrint(2, ' Track end: ', PrintFixTime($$times[-1]), "\n") if $verbose > 2; + $fix = { } if $$geotag{DateTimeOnly}; + } else { + $fix = $$points{$$times[-1]}; + $iExt = $#$times; $iDir = -1; + $et->VPrint(2, " Taking pos from fix:\n", + PrintFix($points, $$times[-1])) if $verbose > 2; + } + } else { + # find nearest 2 points in time + my ($i0, $i1) = (0, scalar(@$times) - 1); + while ($i1 > $i0 + 1) { + my $pt = int(($i0 + $i1) / 2); + ($time < $$times[$pt] ? $i1 : $i0) = $pt; + } + # do linear interpolation for position + my $t0 = $$times[$i0]; + my $t1 = $$times[$i1]; + my $p1 = $$points{$t1}; + # check to see if we are extrapolating before the first entry in a track + my $maxSecs = ($$p1{first} and $geoMaxIntSecs) ? $geoMaxExtSecs : $geoMaxIntSecs; + # don't interpolate if fixes are too far apart + # (but always interpolate fixes inside the same TimeSpan) + if ($t1 - $t0 > $maxSecs and (not $$p1{span} or not $$points{$t0}{span} or + $$p1{span} != $$points{$t0}{span})) + { + # treat as an extrapolation -- use nearest fix if close enough + my $tn; + if ($time - $t0 < $t1 - $time) { + $tn = $t0; + $iExt = $i0; + } else { + $tn = $t1; + $iExt = $i1; + } + if (abs($time - $tn) > $geoMaxExtSecs) { + $err or $err = 'Time is too far from nearest GPS fix'.' '.abs($time-$tn).' '.$geoMaxExtSecs; + $et->VPrint(2, ' Nearest fix: ', PrintFixTime($tn), "\n") if $verbose > 2; + $fix = { } if $$geotag{DateTimeOnly}; + } else { + $fix = $$points{$tn}; + $et->VPrint(2, " Taking pos from fix:\n", + PrintFix($points, $tn)) if $verbose > 2; + } + } else { + my $f0 = $t1 == $t0 ? 0 : ($time - $t0) / ($t1 - $t0); + my $p0 = $$points{$t0}; + $et->VPrint(2, " Interpolating between fixes (f=$f0):\n", + PrintFix($points, $t0, $t1)) if $verbose > 2; + $fix = { }; + # loop through available fix information categories + # (pos, track, alt, orient) + my ($category, $key); +Category: foreach $category (qw{pos track alt orient atemp}) { + next unless $$has{$category}; + my ($f, $p0b, $p1b, $f0b); + # loop through specific fix information keys + # (lat, lon, alt, track, speed, dir, pitch, roll) + foreach $key (@{$fixInfoKeys{$category}}) { + my $v0 = $$p0{$key}; + my $v1 = $$p1{$key}; + if (defined $v0 and defined $v1) { + $f = $f0; + } elsif (defined $f0b) { + $v0 = $$p0b{$key}; + $v1 = $$p1b{$key}; + next unless defined $v0 and defined $v1; + $f = $f0b; + } else { + next if $sepTags{$key}; # (don't scan outwards for some formats, eg. CSV) + # scan outwards looking for fixes with the required information + # (NOTE: SHOULD EVENTUALLY DO THIS FOR EXTRAPOLATION TOO!) + my ($t0b, $t1b); + if (defined $v0) { + $t0b = $t0; $p0b = $p0; + } else { + ($t0b,$p0b,$v0) = ScanOutwards($key,$times,$points,$i0,-1,$maxSecs); + next Category unless defined $t0b; + } + if (defined $v1) { + $t1b = $t1; $p1b = $p1; + } else { + ($t1b,$p1b,$v1) = ScanOutwards($key,$times,$points,$i1,1,$maxSecs); + next Category unless defined $t1b; + } + # re-calculate the interpolation factor + $f = $f0b = $t1b == $t0b ? 0 : ($time - $t0b) / ($t1b - $t0b); + $et->VPrint(2, " Interpolating $category between fixes (f=$f):\n", + PrintFix($points, $t0b, $t1b)) if $verbose > 2; + } + # must interpolate cyclical values differently + if ($cyclical{$key} and abs($v1 - $v0) > 180) { + # the acute angle spans the discontinuity, so add + # 360 degrees to the smaller angle before interpolating + $v0 < $v1 ? $v0 += 360 : $v1 += 360; + $$fix{$key} = $v1 * $f + $v0 * (1 - $f); + # some ranges are -180 to 180, others are 0 to 360 + my $max = $cyc180{$key} ? 180 : 360; + $$fix{$key} -= 360 if $$fix{$key} >= $max; + } else { + # simple linear interpolation + $$fix{$key} = $v1 * $f + $v0 * (1 - $f); + } + } + } + } + } + if ($fix) { + $err = ''; # success! + } elsif ($$geotag{NoDate} and not $noDate and $val =~ s/^\S+\s+//) { + # try again with no date since some of our track points are date-less + $secondTry = 1; + next; + } + last; + } + if ($fix) { + my ($gpsDate, $gpsAlt, $gpsAltRef); + my @t = gmtime(int $time); + my $gpsTime = sprintf('%.2d:%.2d:%.2d', $t[2], $t[1], $t[0]) . $fsec; + # write GPSDateStamp if date included in track log, otherwise delete it + $gpsDate = sprintf('%.2d:%.2d:%.2d', $t[5]+1900, $t[4]+1, $t[3]) unless $noDate; + # write GPSAltitude tags if altitude included in track log, otherwise delete them + if (defined $$fix{alt}) { + $gpsAlt = abs $$fix{alt}; + $gpsAltRef = ($$fix{alt} < 0 ? 1 : 0); + } elsif ($$has{alt} and defined $iExt) { + my $tFix = FindFix($et,'alt',$times,$points,$iExt,$iDir,$geoMaxExtSecs); + if ($tFix) { + $gpsAlt = abs $$tFix{alt}; + $gpsAltRef = ($$tFix{alt} < 0 ? 1 : 0); + } + } + # set new GPS tag values (EXIF, or XMP if write group is 'xmp') + my ($xmp, $exif, @r); + my %opts = ( Type => 'ValueConv' ); # write ValueConv values + if ($writeGroup) { + $opts{Group} = $writeGroup; + $xmp = ($writeGroup =~ /xmp/i); + $exif = ($writeGroup =~ /^(exif|gps)$/i); + } + # (capture error messages by calling SetNewValue in list context) + @r = $et->SetNewValue(GPSLatitude => $$fix{lat}, %opts); + @r = $et->SetNewValue(GPSLongitude => $$fix{lon}, %opts); + @r = $et->SetNewValue(GPSAltitude => $gpsAlt, %opts); + @r = $et->SetNewValue(GPSAltitudeRef => $gpsAltRef, %opts); + if ($$has{track}) { + my $tFix = $fix; + if (not defined $$fix{track} and defined $iExt) { + my $p = FindFix($et,'track',$times,$points,$iExt,$iDir,$geoMaxExtSecs); + $tFix = $p if $p; + } + @r = $et->SetNewValue(GPSTrack => $$tFix{track}, %opts); + @r = $et->SetNewValue(GPSTrackRef => (defined $$tFix{track} ? 'T' : undef), %opts); + my ($spd, $ref); + if (defined($spd = $$tFix{speed})) { + # convert to specified units if necessary + $ref = $$et{OPTIONS}{GeoSpeedRef}; + if ($ref and defined $speedConv{$ref}) { + $ref = $speedConv{$ref} if $speedConv{$speedConv{$ref}}; + $spd *= $speedConv{$ref}; + } else { + $ref = 'N'; # knots by default + } + } + @r = $et->SetNewValue(GPSSpeed => $spd, %opts); + @r = $et->SetNewValue(GPSSpeedRef => $ref, %opts); + } + if ($$has{orient}) { + my $tFix = $fix; + if (not defined $$fix{dir} and defined $iExt) { + my $p = FindFix($et,'dir',$times,$points,$iExt,$iDir,$geoMaxExtSecs); + $tFix = $p if $p; + } + @r = $et->SetNewValue(GPSImgDirection => $$tFix{dir}, %opts); + @r = $et->SetNewValue(GPSImgDirectionRef => (defined $$tFix{dir} ? 'T' : undef), %opts); + @r = $et->SetNewValue(CameraElevationAngle => $$tFix{pitch}, %opts); + # Note: GPSPitch and GPSRoll are non-standard, and must be user-defined + @r = $et->SetNewValue(GPSPitch => $$tFix{pitch}, %opts); + @r = $et->SetNewValue(GPSRoll => $$tFix{roll}, %opts); + } + if ($$has{atemp}) { + my $tFix = $fix; + if (not defined $$fix{atemp} and defined $iExt) { + # (not all fixes have atemp, so try interpolating specifically for this) + my $p = FindFix($et,'atemp',$times,$points,$iExt,$iDir,$geoMaxExtSecs); + $tFix = $p if $p; + } + @r = $et->SetNewValue(AmbientTemperature => $$tFix{atemp}, %opts); + } + unless ($xmp) { + my ($latRef, $lonRef); + $latRef = ($$fix{lat} > 0 ? 'N' : 'S') if defined $$fix{lat}; + $lonRef = ($$fix{lon} > 0 ? 'E' : 'W') if defined $$fix{lon}; + @r = $et->SetNewValue(GPSLatitudeRef => $latRef, %opts); + @r = $et->SetNewValue(GPSLongitudeRef => $lonRef, %opts); + @r = $et->SetNewValue(GPSDateStamp => $gpsDate, %opts); + @r = $et->SetNewValue(GPSTimeStamp => $gpsTime, %opts); + # set options to edit XMP:GPSDateTime only if it already exists + $opts{EditOnly} = 1; + $opts{Group} = 'XMP'; + } + unless ($exif) { + @r = $et->SetNewValue(GPSDateTime => "$gpsDate $gpsTime", %opts); + } + } else { + my %opts = ( IgnorePermanent => 1 ); + $opts{Replace} = 2 if defined $val; # remove existing new values + $opts{Group} = $writeGroup if $writeGroup; + + # reset any GPS values we might have already set + foreach (qw(GPSLatitude GPSLatitudeRef GPSLongitude GPSLongitudeRef + GPSAltitude GPSAltitudeRef GPSDateStamp GPSTimeStamp GPSDateTime + GPSTrack GPSTrackRef GPSSpeed GPSSpeedRef GPSImgDirection + GPSImgDirectionRef GPSPitch GPSRoll CameraElevationAngle + AmbientTemperature)) + { + my @r = $et->SetNewValue($_, undef, %opts); + } + } + return $err; +} + +#------------------------------------------------------------------------------ +# Convert Geotagging time synchronization value +# Inputs: 0) exiftool object ref, +# 1) time difference string ("[+-]DD MM:HH:SS.ss"), geosync'd file name, +# "GPSTIME@IMAGETIME", or "GPSTIME@FILENAME" +# Returns: geosync hash: +# Offset = Offset in seconds for latest synchronization (GPS - image time) +# Points = hash of all sync offsets keyed by image times in seconds +# Times = sorted list of image synchronization times (keys in Points hash) +# Notes: calling this routine with more than one geosync'd file causes time drift +# correction to be implemented +sub ConvertGeosync($$) +{ + my ($et, $val) = @_; + my $sync = $et->GetNewValue('Geosync') || { }; + my ($syncFile, $gpsTime, $imgTime); + + if ($val =~ /(.*?)\@(.*)/) { + $gpsTime = $1; + (-f $2 ? $syncFile : $imgTime) = $2; + # (take care because "-f '1:30'" crashes ActivePerl 5.10) + } elsif ($val !~ /^\d/ or $val !~ /:/) { + $syncFile = $val if -f $val; + } + if ($gpsTime or defined $syncFile) { + # (this is a time synchronization vector) + if (defined $syncFile) { + # check the following tags in order to obtain the image timestamp + my @timeTags = qw(SubSecDateTimeOriginal SubSecCreateDate SubSecModifyDate + DateTimeOriginal CreateDate ModifyDate FileModifyDate); + my $info = ImageInfo($syncFile, { PrintConv => 0 }, @timeTags, + 'GPSDateTime', 'GPSTimeStamp'); + $$info{Error} and warn("$$info{Err}\n"), return undef; + unless ($gpsTime) { + $gpsTime = $$info{GPSDateTime} || $$info{GPSTimeStamp}; + $gpsTime .= 'Z' if $gpsTime and not $$info{GPSDateTime}; + } + $gpsTime or warn("No GPSTimeStamp in '$syncFile\n"), return undef; + my $tag; + foreach $tag (@timeTags) { + if ($$info{$tag}) { + $imgTime = $$info{$tag}; + $et->VPrint(2, "Geosyncing with $tag from '${syncFile}'\n"); + last; + } + } + $imgTime or warn("No image timestamp in '${syncFile}'\n"), return undef; + } + # add date to date-less timestamps + my ($imgDateTime, $gpsDateTime, $noDate); + if ($imgTime =~ /^(\d+:\d+:\d+)\s+\d+/) { + $imgDateTime = $imgTime; + my $date = $1; + if ($gpsTime =~ /^\d+:\d+:\d+\s+\d+/) { + $gpsDateTime = $gpsTime; + } else { + $gpsDateTime = "$date $gpsTime"; + } + } elsif ($gpsTime =~ /^(\d+:\d+:\d+)\s+\d+/) { + $imgDateTime = "$1 $imgTime"; + $gpsDateTime = $gpsTime; + } else { + # use a today's date (so hopefully the DST setting will be intuitive) + my @tm = localtime; + my $date = sprintf('%.4d:%.2d:%.2d', $tm[5]+1900, $tm[4]+1, $tm[3]); + $gpsDateTime = "$date $gpsTime"; + $imgDateTime = "$date $imgTime"; + $noDate = 1; + } + # calculate Unix seconds since the epoch + my $imgSecs = Image::ExifTool::GetUnixTime($imgDateTime, 1); + defined $imgSecs or warn("Invalid image time '${imgTime}'\n"), return undef; + my $gpsSecs = Image::ExifTool::GetUnixTime($gpsDateTime, 1); + defined $gpsSecs or warn("Invalid GPS time '${gpsTime}'\n"), return undef; + # add fractional seconds + $gpsSecs += $1 if $gpsTime =~ /(\.\d+)/; + $imgSecs += $1 if $imgTime =~ /(\.\d+)/; + # shift dates within 12 hours of each other if either timestamp was date-less + if ($gpsDateTime ne $gpsTime or $imgDateTime ne $imgTime) { + my $diff = ($imgSecs - $gpsSecs) % (24 * 3600); + $diff -= 24 * 3600 if $diff > 12 * 3600; + $diff += 24 * 3600 if $diff < -12 * 3600; + if ($gpsDateTime ne $gpsTime) { + $gpsSecs = $imgSecs - $diff; + } else { + $imgSecs = $gpsSecs + $diff; + } + } + # save the synchronization offset + $$sync{Offset} = $gpsSecs - $imgSecs; + # save this synchronization point if either timestamp had a date + unless ($noDate) { + $$sync{Points} or $$sync{Points} = { }; + $$sync{Points}{$imgSecs} = $$sync{Offset}; + # print verbose output + if ($et->Options('Verbose') > 1) { + # print GPS and image timestamps in UTC + $et->VPrint(1, "Added Geosync point:\n", + ' GPS time stamp: ', PrintFixTime($gpsSecs), "\n", + ' Image date/time: ', PrintFixTime($imgSecs), "\n"); + } + # save sorted list of image sync times if we have more than one + my @times = keys %{$$sync{Points}}; + if (@times > 1) { + @times = sort { $a <=> $b } @times; + $$sync{Times} = \@times; + } + } + } else { + # (this is a simple time difference) + my @vals = $val =~ /(?=\d|\.\d)\d*(?:\.\d*)?/g; # (allow decimal values too) + @vals or warn("Invalid value (please refer to geotag documentation)\n"), return undef; + my $secs = 0; + my $mult; + foreach $mult (1, 60, 3600, $secPerDay) { + $secs += $mult * pop(@vals); + last unless @vals; + } + # set constant sync offset + $$sync{Offset} = $val =~ /^\s*-/ ? -$secs : $secs; + } + return $sync; +} + +#------------------------------------------------------------------------------ +# Print fix time +# Inputs: 0) time since the epoch +# Returns: UTC time string with fractional seconds +sub PrintFixTime($) +{ + my $time = $_[0] + 0.0005; # round off to nearest ms + my $fsec = int(($time - int($time)) * 1000); + return sprintf('%s.%.3d UTC', Image::ExifTool::ConvertUnixTime($time), $fsec); +} + +#------------------------------------------------------------------------------ +# Print fix information +# Inputs: 0) lookup for all fix points, 1-n) list of fix times +# Returns: fix string (including leading indent and trailing newline) +sub PrintFix($@) +{ + local $_; + my $points = shift; + my $str = ''; + while (@_) { + my $time = shift; + $str .= ' ' . PrintFixTime($time) . ' -'; + my $fix = $$points{$time}; + if ($fix) { + foreach (sort keys %$fix) { + $str .= " $_=$$fix{$_}" unless $_ eq 'time' or not defined $$fix{$_}; + } + } + $str .= "\n"; + } + return $str; +} + +#------------------------------------------------------------------------------ +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Geotag - Geotagging utility routines + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module loads GPS track logs, interpolates to determine position based +on time, and sets new GPS values for geotagging images. Currently supported +formats are GPX, NMEA RMC/GGA/GLL, KML, IGC, Garmin XML and TCX, Magellan +PMGNTRK, Honeywell PTNTHPR, Bramor gEO, Winplus Beacon text, Google Takeout +JSON, GPS/IMU CSV, DJI CSV, ExifTool CSV log files. + +Methods in this module should not be called directly. Instead, the Geotag +feature is accessed by writing the values of the ExifTool Geotag, Geosync +and Geotime tags (see the L<Extra Tags|Image::ExifTool::TagNames/Extra Tags> +in the tag name documentation). + +=head1 NOTES + +To take advantage of attitude information in the PTNTHPR NMEA sentence, the +user-defined tag GPSRoll, must be active. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.topografix.com/GPX/1/1/> + +=item L<http://www.gpsinformation.org/dale/nmea.htm#GSA> + +=item L<http://code.google.com/apis/kml/documentation/kmlreference.html> + +=item L<http://www.fai.org/gliding/system/files/tech_spec_gnss.pdf> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Lionel Genet for the ability to read IGC format track logs. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Extra Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/GoPro.pm b/ExifTool/lib/Image/ExifTool/GoPro.pm new file mode 100644 index 0000000..9386ad7 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/GoPro.pm @@ -0,0 +1,757 @@ +#------------------------------------------------------------------------------ +# File: GoPro.pm +# +# Description: Read information from GoPro videos +# +# Revisions: 2018/01/12 - P. Harvey Created +# +# References: 1) https://github.com/gopro/gpmf-parser +# 2) https://github.com/stilldavid/gopro-utils +#------------------------------------------------------------------------------ + +package Image::ExifTool::GoPro; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::QuickTime; + +$VERSION = '1.07'; + +sub ProcessGoPro($$$); +sub ProcessString($$$); +sub ScaleValues($$); +sub AddUnits($$$); +sub ConvertSystemTime($$); + +# GoPro data types that have ExifTool equivalents (ref 1) +my %goProFmt = ( # format codes + # 0x00 - container (subdirectory) + 0x62 => 'int8s', # 'b' + 0x42 => 'int8u', # 'B' + 0x63 => 'string', # 'c' (possibly null terminated) + 0x73 => 'int16s', # 's' + 0x53 => 'int16u', # 'S' + 0x6c => 'int32s', # 'l' + 0x4c => 'int32u', # 'L' + 0x66 => 'float', # 'f' + 0x64 => 'double', # 'd' + 0x46 => 'undef', # 'F' (4-char ID) + 0x47 => 'undef', # 'G' (16-byte uuid) + 0x6a => 'int64s', # 'j' + 0x4a => 'int64u', # 'J' + 0x71 => 'fixed32s', # 'q' + 0x51 => 'fixed64s', # 'Q' + 0x55 => 'undef', # 'U' (16-byte date) + 0x3f => 'undef', # '?' (complex structure) +); + +# sizes of format codes if different than what FormatSize() would return +my %goProSize = ( + 0x46 => 4, + 0x47 => 16, + 0x55 => 16, +); + +# tagInfo elements to add units to PrintConv value +my %addUnits = ( + AddUnits => 1, + PrintConv => 'Image::ExifTool::GoPro::AddUnits($self, $val, $tag)', +); + +# Tags found in the GPMF box of Hero6 mp4 videos (ref PH), and +# the gpmd-format timed metadata of Hero5 and Hero6 videos (ref 1) +%Image::ExifTool::GoPro::GPMF = ( + PROCESS_PROC => \&ProcessGoPro, + GROUPS => { 2 => 'Camera' }, + NOTES => q{ + Tags extracted from the GPMF box of GoPro MP4 videos, the APP6 "GoPro" + segment of JPEG files, and from the "gpmd" timed metadata if the + L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> (-ee) option is enabled. Many more tags exist, but are + currently unknown and extracted only with the L<Unknown|../ExifTool.html#Unknown> (-u) option. Please + let me know if you discover the meaning of any of these unknown tags. See + L<https://github.com/gopro/gpmf-parser> for details about this format. + }, + ACCL => { #2 (gpmd) + Name => 'Accelerometer', + Notes => 'accelerator readings in m/s2', + Binary => 1, + }, + # ANGX (GPMF-GEOC) - seen -0.05 (fmt d, Max) + # ANGY (GPMF-GEOC) - seen 179.9 (fmt d, Max) + # ANGZ (GPMF-GEOC) - seen 0.152 (fmt d, Max) + ALLD => 'AutoLowLightDuration', #1 (gpmd) (untested) + # APTO (GPMF) - seen: 'RAW', 'DYNM' (fmt c) + ATTD => { #PH (Karma) + Name => 'Attitude', + # UNIT=s,rad,rad,rad,rad/s,rad/s,rad/s, + # TYPE=LffffffB + # SCAL=1000 1 1 1 1 1 1 1 + Binary => 1, + }, + ATTR => { #PH (Karma) + Name => 'AttitudeTarget', + # UNIT=s,rad,rad,rad, + # TYPE=Jffff + # SCAL=1000 1 1 1 1 + Binary => 1, + }, + AUDO => 'AudioSetting', #PH (GPMF - seen: 'WIND', fmt c) + # AUPT (GPMF) - seen: 'N','Y' (fmt c) + BPOS => { #PH (Karma) + Name => 'Controller', + Unknown => 1, + # UNIT=deg,deg,m,deg,deg,m,m,m + # TYPE=lllfffff + # SCAL=10000000 10000000 1000 1 1 1 1 1 + %addUnits, + }, + # BRID (GPMF) - seen: 0 (fmt B) + # BROD (GPMF) - seen: 'ASK','' (fmt c) + # CALH (GPMF-GEOC) - seen 3040 (fmt L, Max) + # CALW (GPMF-GEOC) - seen 4056 (fmt L, Max) + CASN => 'CameraSerialNumber', #PH (GPMF - seen: 'C3221324545448', fmt c) + # CINF (GPMF) - seen: 0x67376be7709bc8876a8baf3940908618, 0xe230988539b30cf5f016627ae8fc5395, + # 0x8bcbe424acc5b37d7d77001635198b3b (fmt B) (Camera INFormation?) + # CMOD (GPMF) - seen: 12,13,17 [12 360 video, 13 time-laps video, 17 JPEG] (fmt B) + # CRTX (GPMF-BACK/FRNT) - double[1] + # CRTY (GPMF-BACK/FRNT) - double[1] + CSEN => { #PH (Karma) + Name => 'CoyoteSense', + # UNIT=s,rad/s,rad/s,rad/s,g,g,g,,,, + # TYPE=LffffffLLLL + # SCAL=1000 1 1 1 1 1 1 1 1 1 1 + Binary => 1, + }, + CYTS => { #PH (Karma) + Name => 'CoyoteStatus', + # UNIT=s,,,,,rad,rad,rad,, + # TYPE=LLLLLfffBB + # SCAL=1000 1 1 1 1 1 1 1 1 1 + Binary => 1, + }, + DEVC => { #PH (gpmd,GPMF, fmt \0) + Name => 'DeviceContainer', + SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' }, + # (Max) DVID=1,DVNM='Global Settings',VERS,FMWR,LINF,CINF,CASN,MINF,MUID,CMOD,MTYP,OREN, + # DZOM,DZST,SMTR,PRTN,PTWB,PTSH,PTCL,EXPT,PIMX,PIMN,PTEV,RATE,SROT,ZFOV,VLTE,VLTA, + # EISE,EISA,AUPT,AUDO,BROD,BRID,PVUL,PRJT,SOFF + # (Max) DVID='GEOC',DVNM='Geometry Calibrations',SHFX,SHFY,SHFZ,ANGX,ANGY,ANGZ,CALW,CALH + # (Max) DVID='BACK',DVNM='Back Lens',KLNS,CTRX,CTRY,MFOV,SFTR + # (Max) DVID='FRNT',DVNM='Front Lens',KLNS,CTRX,CTRY,MFOV,SFTR + # (Max) DVID='HLMT',DVNM='Highlights' + }, + # DVID (GPMF) - DeviceID; seen: 1 (fmt L), HLMT (fmt F), GEOC (fmt F), 'BACK' (fmt F, Max) + DVID => { Name => 'DeviceID', Unknown => 1 }, #2 (gpmd) + # DVNM (GPMF) seen: 'Video Global Settings' (fmt c), 'Highlights' (fmt c), 'Geometry Calibrations' (Max) + # DVNM (gpmd) seen: 'Camera' (Hero5), 'Hero6 Black' (Hero6), 'GoPro Karma v1.0' (Karma) + DVNM => 'DeviceName', #PH (n/c) + DZOM => { #PH (GPMF - seen: 'Y', fmt c) + Name => 'DigitalZoom', + PrintConv => { N => 'No', Y => 'Yes' }, + }, + # DZST (GPMF) - seen: 0 (fmt L) (something to do with digital zoom maybe?) + EISA => { #PH (GPMF) - seen: 'Y','N','HS EIS','N/A' (fmt c) [N was for a time-lapse video] + Name => 'ElectronicImageStabilization', + }, + # EISE (GPMF) - seen: 'Y','N' (fmt c) + EMPT => { Name => 'Empty', Unknown => 1 }, #2 (gpmd) + ESCS => { #PH (Karma) + Name => 'EscapeStatus', + # UNIT=s,rpm,rpm,rpm,rpm,rpm,rpm,rpm,rpm,degC,degC,degC,degC,V,V,V,V,A,A,A,A,,,,,,,,, + # TYPE=JSSSSSSSSssssSSSSSSSSSSSSSSSSB + # (no SCAL!) + Unknown => 1, + %addUnits, + }, + # EXPT (GPMF) - seen: '', 'AUTO' (fmt c) + FACE => 'FaceDetected', #PH (gpmd) + FCNM => 'FaceNumbers', #PH (gpmd) (faces counted per frame, ref 1) + FMWR => 'FirmwareVersion', #PH (GPMF - seen: HD6.01.01.51.00, fmt c) + FWVS => 'OtherFirmware', #PH (NC) (gpmd - seen: '1.1.11.0', Karma) + GLPI => { #PH (gpmd, Karma) + Name => 'GPSPos', + # UNIT=s,deg,deg,m,m,m/s,m/s,m/s,deg + # TYPE=LllllsssS + # SCAL=1000 10000000 10000000 1000 1000 100 100 100 100 + RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data + SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GLPI' }, + }, + GPRI => { #PH (gpmd, Karma) + Name => 'GPSRaw', + # UNIT=s,deg,deg,m,m,m,m/s,deg,, + # TYPE=JlllSSSSBB + # SCAL=1000000,10000000,10000000,1000,100,100,100,100,1,1 + Unknown => 1, + RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data + SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPRI' }, + }, + GPS5 => { #2 (gpmd) + Name => 'GPSInfo', + # SCAL=10000000,10000000,1000,1000,100 + RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data + SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPS5' }, + }, + GPSF => { #2 (gpmd) + Name => 'GPSMeasureMode', + PrintConv => { + 2 => '2-Dimensional Measurement', + 3 => '3-Dimensional Measurement', + }, + }, + GPSP => { #2 (gpmd) + Name => 'GPSHPositioningError', + Description => 'GPS Horizontal Positioning Error', + ValueConv => '$val / 100', # convert from cm to m + }, + GPSU => { #2 (gpmd) + Name => 'GPSDateTime', + Groups => { 2 => 'Time' }, + # (HERO5 writes this in 'c' format, HERO6 writes 'U') + ValueConv => '$val =~ s/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/20$1:$2:$3 $4:$5:/; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + GYRO => { #2 (gpmd) + Name => 'Gyroscope', + Notes => 'gyroscope readings in rad/s', + Binary => 1, + }, + # HFLG (APP6) - seen: 0 + ISOE => 'ISOSpeeds', #PH (gpmd) + ISOG => { #2 (gpmd) + Name => 'ImageSensorGain', + Binary => 1, + }, + KBAT => { #PH (gpmd) (Karma) + Name => 'BatteryStatus', + # UNIT=A,Ah,J,degC,V,V,V,V,s,%,,,,,% + # TYPE=lLlsSSSSSSSBBBb + # SCAL=1000,1000,0.00999999977648258,100,1000,1000,1000,1000,0.0166666675359011,1,1,1,1,1,1 + RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data + SubDirectory => { TagTable => 'Image::ExifTool::GoPro::KBAT' }, + }, + # KLNS (GPMF-BACK/FRNT) - double[5] (fmt d, Max) + # LINF (GPMF) - seen: LAJ7061916601668,C3341326002180,C33632245450981 (fmt c) (Lens INFormation?) + LNED => { #PH (Karma) + Name => 'LocalPositionNED', + # UNIT=s,m,m,m,m/s,m/s,m/s + # TYPE=Lffffff + # SCAL=1000 1 1 1 1 1 1 + Binary => 1, + }, + MAGN => 'Magnetometer', #1 (gpmd) (units of uT) + # MFOV (GPMF-BACK/FRNT) - seen: 100 (fmt d, Max) + MINF => { #PH (GPMF - seen: HERO6 Black, fmt c) + Name => 'Model', + Groups => { 2 => 'Camera' }, + Description => 'Camera Model Name', + }, + # MTYP (GPMF) - seen: 0,1,5,11 [1 for time-lapse video, 5 for 360 video, 11 for JPEG] (fmt B) + # MUID (GPMF) - seen: 3882563431 2278071152 967805802 411471936 0 0 0 0 (fmt L) + OREN => { #PH (GPMF - seen: 'U', fmt c) + Name => 'AutoRotation', + PrintConv => { + U => 'Up', + D => 'Down', # (NC) + A => 'Auto', # (NC) + }, + }, + # (most of the "P" tags are ProTune settings - PH) + PHDR => 'HDRSetting', #PH (APP6 - seen: 0) + PIMN => 'AutoISOMin', #PH (GPMF - seen: 100, fmt L) + PIMX => 'AutoISOMax', #PH (GPMF - seen: 1600, fmt L) + # PRAW (APP6) - seen: 0, 'N', 'Y' (fmt c) + PRES => 'PhotoResolution', #PH (APP6 - seen: '12MP_W') + # PRJT (APP6) - seen: 'GPRO','EACO' (fmt F, Hero8, Max) + PRTN => { #PH (GPMF - seen: 'N', fmt c) + Name => 'ProTune', + PrintConv => { + N => 'Off', + Y => 'On', # (NC) + }, + }, + PTCL => 'ColorMode', #PH (GPMF - seen: 'GOPRO', fmt c' APP6: 'FLAT') + PTEV => 'ExposureCompensation', #PH (GPMF - seen: '0.0', fmt c) + PTSH => 'Sharpness', #PH (GPMF - seen: 'HIGH', fmt c) + PTWB => 'WhiteBalance', #PH (GPMF - seen: 'AUTO', fmt c) + # PVUL (APP6) - seen: 'F' (fmt c, Hero8, Max) + RATE => 'Rate', #PH (GPMF - seen: '0_5SEC', fmt c; APP6 - seen: '4_1SEC') + RMRK => { #2 (gpmd) + Name => 'Comments', + ValueConv => '$self->Decode($val, "Latin")', + }, + SCAL => { #2 (gpmd) scale factor for subsequent data + Name => 'ScaleFactor', + Unknown => 1, + }, + SCPR => { #PH (Karma) [stream was empty] + Name => 'ScaledPressure', + # UNIT=s,Pa,Pa,degC + # TYPE=Lffs + # SCAL=1000 0.00999999977648258 0.00999999977648258 100 + %addUnits, + }, + # SFTR (GPMF-BACK/FRNT) - seen 0.999,1.00004 (fmt d, Max) + # SHFX (GPMF-GEOC) - seen 22.92 (fmt d, Max) + # SHFY (GPMF-GEOC) - seen 0.123 (fmt d, Max) + # SHFZ (GPMF-GEOC) - seen 36.06 (fmt d, Max) + SHUT => { #2 (gpmd) + Name => 'ExposureTimes', + PrintConv => q{ + my @a = split ' ', $val; + $_ = Image::ExifTool::Exif::PrintExposureTime($_) foreach @a; + return join ' ', @a; + }, + }, + SIMU => { #PH (Karma) + Name => 'ScaledIMU', + # UNIT=s,g,g,g,rad/s,rad/s,rad/s,T,T,T + # TYPE=Lsssssssss + # SCAL=1000 1000 1000 1000 1000 1000 1000 1000 1000 1000 + %addUnits, + }, + SIUN => { #2 (gpmd - seen : 'm/s2','rad/s') + Name => 'SIUnits', + Unknown => 1, + ValueConv => '$self->Decode($val, "Latin")', + }, + # SMTR (GPMF) - seen: 'N' (fmt c) + # SOFF (APP6) - seen: 0 (fmt L, Hero8, Max) + # SROT (GPMF) - seen 20.60 (fmt f, Max) + STMP => { #1 (gpmd) + Name => 'TimeStamp', + ValueConv => '$val / 1e6', + }, + STRM => { #2 (gpmd,GPMF, fmt \0) + Name => 'NestedSignalStream', + SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' }, + }, + STNM => { #2 (gpmd) + Name => 'StreamName', + Unknown => 1, + ValueConv => '$self->Decode($val, "Latin")', + }, + SYST => { #PH (Karma) + Name => 'SystemTime', + # UNIT=s,s + # TYPE=JJ + # SCAL=1000000 1000 + # save system time calibrations for later + RawConv => q{ + my @v = split ' ', $val; + if (@v == 2) { + my $s = $$self{SystemTimeList}; + $s or $s = $$self{SystemTimeList} = [ ]; + push @$s, \@v; + } + return $val; + }, + }, + # TICK => { Name => 'InTime', Unknown => 1, ValueConv => '$val/1000' }, #1 (gpmd) + TMPC => { #2 (gpmd) + Name => 'CameraTemperature', + PrintConv => '"$val C"', + }, + # TOCK => { Name => 'OutTime', Unknown => 1, ValueConv => '$val/1000' }, #1 (gpmd) + TSMP => { Name => 'TotalSamples', Unknown => 1 }, #2 (gpmd) + TYPE => { Name => 'StructureType', Unknown => 1 }, #2 (gpmd,GPMF - eg 'LLLllfFff', fmt c) + UNIT => { #2 (gpmd) alternative units + Name => 'Units', + Unknown => 1, + ValueConv => '$self->Decode($val, "Latin")', + }, + VERS => { + Name => 'MetadataVersion', + PrintConv => '$val =~ tr/ /./; $val', + }, + VFOV => { #PH (GPMF - seen: 'W', fmt c) + Name => 'FieldOfView', + PrintConv => { + W => 'Wide', + S => 'Super View', # (NC, not seen) + L => 'Linear', # (NC, not seen) + }, + }, + # VLTA (GPMF) - seen: 78 ('N') (fmt B -- wrong format?) + VFRH => { #PH (Karma) + Name => 'VisualFlightRulesHUD', + BinaryData => 1, + # UNIT=m/s,m/s,m,m/s,deg,% + # TYPE=ffffsS + }, + # VLTE (GPMF) - seen: 'Y','N' (fmt c) + WBAL => 'ColorTemperatures', #PH (gpmd) + WRGB => { #PH (gpmd) + Name => 'WhiteBalanceRGB', + Binary => 1, + }, + # ZFOV (APP6,GPMF) - seen: 148.34, 0 (fmt f, Hero8, Max) + # the following ref forum12825 + MUID => { + Name => 'MediaUniqueID', + PrintConv => q{ + my @a = split ' ', $val; + $_ = sprintf('%.8x',$_) foreach @a; + return join('', @a); + }, + }, + EXPT => 'MaximumShutterAngle', + MTRX => 'AccelerometerMatrix', + ORIN => 'InputOrientation', + ORIO => 'OutputOrientation', + UNIF => 'InputUniformity', + SROT => 'SensorReadoutTime', +); + +# GoPro GPS5 tags (ref 2) (Hero5,Hero6) +%Image::ExifTool::GoPro::GPS5 = ( + PROCESS_PROC => \&ProcessString, + GROUPS => { 1 => 'GoPro', 2 => 'Location' }, + VARS => { HEX_ID => 0, ID_LABEL => 'Index' }, + 0 => { # (unit='deg') + Name => 'GPSLatitude', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 1 => { # (unit='deg') + Name => 'GPSLongitude', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 2 => { # (unit='m') + Name => 'GPSAltitude', + PrintConv => '"$val m"', + }, + 3 => 'GPSSpeed', # (unit='m/s') + 4 => 'GPSSpeed3D', # (unit='m/s') +); + +# GoPro GPRI tags (ref PH) (Karma) +%Image::ExifTool::GoPro::GPRI = ( + PROCESS_PROC => \&ProcessString, + GROUPS => { 1 => 'GoPro', 2 => 'Location' }, + VARS => { HEX_ID => 0, ID_LABEL => 'Index' }, + 0 => { # (unit='s') + Name => 'GPSDateTimeRaw', + Groups => { 2 => 'Time' }, + ValueConv => \&ConvertSystemTime, # convert to date/time based on SystemTime clock + PrintConv => '$self->ConvertDateTime($val)', + }, + 1 => { # (unit='deg') + Name => 'GPSLatitudeRaw', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 2 => { # (unit='deg') + Name => 'GPSLongitudeRaw', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 3 => { + Name => 'GPSAltitudeRaw', # (NC) + PrintConv => '"$val m"', + }, + # (unknown tags must be defined so that ProcessString() will iterate through all values) + 4 => { Name => 'GPRI_Unknown4', Unknown => 1, Hidden => 1, PrintConv => '"$val m"' }, + 5 => { Name => 'GPRI_Unknown5', Unknown => 1, Hidden => 1, PrintConv => '"$val m"' }, + 6 => 'GPSSpeedRaw', # (NC) # (unit='m/s' -- should convert to other units?) + 7 => 'GPSTrackRaw', # (NC) # (unit='deg') + 8 => { Name => 'GPRI_Unknown8', Unknown => 1, Hidden => 1 }, # (no units) + 9 => { Name => 'GPRI_Unknown9', Unknown => 1, Hidden => 1 }, # (no units) +); + +# GoPro GLPI tags (ref PH) (Karma) +%Image::ExifTool::GoPro::GLPI = ( + PROCESS_PROC => \&ProcessString, + GROUPS => { 1 => 'GoPro', 2 => 'Location' }, + VARS => { HEX_ID => 0, ID_LABEL => 'Index' }, + 0 => { # (unit='s') + Name => 'GPSDateTime', + Groups => { 2 => 'Time' }, + ValueConv => \&ConvertSystemTime, # convert to date/time based on SystemTime clock + PrintConv => '$self->ConvertDateTime($val)', + }, + 1 => { # (unit='deg') + Name => 'GPSLatitude', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 2 => { # (unit='deg') + Name => 'GPSLongitude', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 3 => { # (unit='m') + Name => 'GPSAltitude', # (NC) + PrintConv => '"$val m"', + }, + # (unknown tags must be defined so that ProcessString() will iterate through all values) + 4 => { Name => 'GLPI_Unknown4', Unknown => 1, Hidden => 1, PrintConv => '"$val m"' }, + 5 => { Name => 'GPSSpeedX', PrintConv => '"$val m/s"' }, # (NC) + 6 => { Name => 'GPSSpeedY', PrintConv => '"$val m/s"' }, # (NC) + 7 => { Name => 'GPSSpeedZ', PrintConv => '"$val m/s"' }, # (NC) + 8 => { Name => 'GPSTrack' }, # (unit='deg') +); + +# GoPro KBAT tags (ref PH) +%Image::ExifTool::GoPro::KBAT = ( + PROCESS_PROC => \&ProcessString, + GROUPS => { 1 => 'GoPro', 2 => 'Camera' }, + VARS => { HEX_ID => 0, ID_LABEL => 'Index' }, + NOTES => 'Battery status information found in GoPro Karma videos.', + 0 => { Name => 'BatteryCurrent', PrintConv => '"$val A"' }, + 1 => { Name => 'BatteryCapacity', PrintConv => '"$val Ah"' }, + 2 => { Name => 'KBAT_Unknown2', PrintConv => '"$val J"', Unknown => 1, Hidden => 1 }, + 3 => { Name => 'BatteryTemperature', PrintConv => '"$val C"' }, + 4 => { Name => 'BatteryVoltage1', PrintConv => '"$val V"' }, + 5 => { Name => 'BatteryVoltage2', PrintConv => '"$val V"' }, + 6 => { Name => 'BatteryVoltage3', PrintConv => '"$val V"' }, + 7 => { Name => 'BatteryVoltage4', PrintConv => '"$val V"' }, + 8 => { Name => 'BatteryTime', PrintConv => 'ConvertDuration(int($val + 0.5))' }, # (NC) + 9 => { Name => 'KBAT_Unknown9', PrintConv => '"$val %"', Unknown => 1, Hidden => 1, }, + 10 => { Name => 'KBAT_Unknown10', Unknown => 1, Hidden => 1 }, # (no units) + 11 => { Name => 'KBAT_Unknown11', Unknown => 1, Hidden => 1 }, # (no units) + 12 => { Name => 'KBAT_Unknown12', Unknown => 1, Hidden => 1 }, # (no units) + 13 => { Name => 'KBAT_Unknown13', Unknown => 1, Hidden => 1 }, # (no units) + 14 => { Name => 'BatteryLevel', PrintConv => '"$val %"' }, +); + +# GoPro fdsc tags written by the Hero5 and Hero6 (ref PH) +%Image::ExifTool::GoPro::fdsc = ( + GROUPS => { 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + NOTES => q{ + Tags extracted from the MP4 "fdsc" timed metadata when the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> + (-ee) option is used. + }, + 0x08 => { Name => 'FirmwareVersion', Format => 'string[15]' }, + 0x17 => { Name => 'SerialNumber', Format => 'string[16]' }, + 0x57 => { Name => 'OtherSerialNumber', Format => 'string[15]' }, # (NC) + 0x66 => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[16]', + }, + # ... + # after this there are lots of interesting values also found in the GPMF box, + # but this block is lacking tag ID's and any directory structure, so the + # value offsets are therefore presumably firmware dependent :( +); + +#------------------------------------------------------------------------------ +# Convert system time to date/time string +# Inputs: 0) system time value, 1) ExifTool ref +# Returns: EXIF-format date/time string with milliseconds +sub ConvertSystemTime($$) +{ + my ($val, $et) = @_; + my $s = $$et{SystemTimeList} or return '<uncalibrated>'; + unless ($$et{SystemTimeListSorted}) { + $s = $$et{SystemTimeList} = [ sort { $$a[0] <=> $$b[0] } @$s ]; + $$et{SystemTimeListSorted} = 1; + } + my ($i, $j) = (0, $#$s); + # perform binary search to find this system time value + while ($j - $i > 1) { + my $t = int(($i + $j) / 2); + ($val < $$s[$t][0] ? $j : $i) = $t; + } + if ($i == $j or $$s[$j][0] == $$s[$i][0]) { + $val = $$s[$i][1]; + } else { + # interpolate between values + $val = $$s[$i][1] + ($$s[$j][1] - $$s[$i][1]) * ($val - $$s[$i][0]) / ($$s[$j][0] - $$s[$i][0]); + } + # (a bit tricky to remove fractional seconds then add them back again after + # the date/time conversion while avoiding round-off errors which could + # put the seconds out by 1...) + my ($t, $f) = ("$val" =~ /^(\d+)(\.\d+)/); + return Image::ExifTool::ConvertUnixTime($t, $$et{OPTIONS}{QuickTimeUTC}) . $f; +} + +#------------------------------------------------------------------------------ +# Scale values by last 'SCAL' constants +# Inputs: 0) value or list of values, 1) string of scale factors +# Returns: nothing, but updates values +sub ScaleValues($$) +{ + my ($val, $scl) = @_; + return unless $val and $scl; + my @scl = split ' ', $scl or return; + my @scaled; + my $v = (ref $val eq 'ARRAY') ? $val : [ $val ]; + foreach $val (@$v) { + my @a = split ' ', $val; + $a[$_] /= $scl[$_ % @scl] foreach 0..$#a; + push @scaled, join(' ', @a); + } + $_[0] = @scaled > 1 ? \@scaled : $scaled[0]; +} + +#------------------------------------------------------------------------------ +# Add units to values for human-readable output +# Inputs: 0) ExifTool ref, 1) value, 2) tag key +# Returns: converted value +sub AddUnits($$$) +{ + my ($et, $val, $tag) = @_; + if ($et and $$et{TAG_EXTRA}{$tag} and $$et{TAG_EXTRA}{$tag}{Units}) { + my $u = $$et{TAG_EXTRA}{$tag}{Units}; + $u = [ $u ] unless ref $u eq 'ARRAY'; + my @a = split ' ', $val; + if (@$u == @a) { + my $i; + for ($i=0; $i<@a; ++$i) { + $a[$i] .= ' ' . $$u[$i] if $$u[$i]; + } + $val = join ' ', @a; + } + } + return $val; +} + +#------------------------------------------------------------------------------ +# Process string of values (or array of strings) to extract as separate tags +# Inputs: 0) ExifTool object ref, 1) directory information ref, 2) tag table ref +# Returns: 1 on success +sub ProcessString($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my @list = ref $$dataPt eq 'ARRAY' ? @{$$dataPt} : ( $$dataPt ); + my ($string, $val); + $et->VerboseDir('GoPro structure'); + foreach $string (@list) { + my @val = split ' ', $string; + my $i = 0; + foreach $val (@val) { + $et->HandleTag($tagTablePtr, $i, $val); + $$tagTablePtr{++$i} or $i = 0; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process GoPro metadata (gpmd samples, GPMF box, or APP6) (ref PH/1/2) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# - with hack to check for encrypted text in gpmd data (Rove Stealth 4K) +sub ProcessGoPro($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $base = $$dirInfo{Base}; + my $pos = $$dirInfo{DirStart} || 0; + my $dirEnd = $pos + ($$dirInfo{DirLen} || (length($$dataPt) - $pos)); + my $verbose = $et->Options('Verbose'); + my $unknown = $verbose || $et->Options('Unknown'); + my ($size, $type, $unit, $scal, $setGroup0); + + $et->VerboseDir($$dirInfo{DirName} || 'GPMF', undef, $dirEnd-$pos) if $verbose; + if ($pos) { + my $parent = $$dirInfo{Parent}; + $setGroup0 = $$et{SET_GROUP0} = 'APP6' if $parent and $parent eq 'APP6'; + } else { + # set group0 to "QuickTime" unless group1 is being changed (to Track#) + $setGroup0 = $$et{SET_GROUP0} = 'QuickTime' unless $$et{SET_GROUP1}; + } + + for (; $pos+8<=$dirEnd; $pos+=($size+3)&0xfffffffc) { + my ($tag,$fmt,$len,$count) = unpack("x${pos}a4CCn", $$dataPt); + $size = $len * $count; + $pos += 8; + last if $pos + $size > $dirEnd; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + last if $tag eq "\0\0\0\0"; # stop at null tag + next unless $size or $verbose; # don't save empty values unless verbose + my $format = $goProFmt{$fmt} || 'undef'; + my ($val, $i, $j, $p, @v); + if ($fmt == 0x3f and defined $type) { + # decode structure with format given by previous 'TYPE' + for ($i=0; $i<$count; ++$i) { + my (@s, $l); + for ($j=0, $p=0; $j<length($type); ++$j, $p+=$l) { + my $b = Get8u(\$type, $j); + my $f = $goProFmt{$b} or last; + $l = $goProSize{$b} || Image::ExifTool::FormatSize($f) or last; + last if $p + $l > $len; + my $s = ReadValue($dataPt, $pos+$i*$len+$p, $f, undef, $l); + last unless defined $s; + push @s, $s; + } + push @v, join ' ', @s if @s; + } + $val = @v > 1 ? \@v : $v[0]; + } elsif (($format eq 'undef' or $format eq 'string') and $count > 1 and $len > 1) { + # unpack multiple undef/string values as a list + my $a = $format eq 'undef' ? 'a' : 'A'; + $val = [ unpack("x${pos}".("$a$len" x $count), $$dataPt) ]; + } else { + $val = ReadValue($dataPt, $pos, $format, undef, $size); + } + # save TYPE, UNIT/SIUN and SCAL values for later + $type = $val if $tag eq 'TYPE'; + $unit = $val if $tag eq 'UNIT' or $tag eq 'SIUN'; + $scal = $val if $tag eq 'SCAL'; + + unless ($tagInfo) { + next unless $unknown; + my $name = Image::ExifTool::QuickTime::PrintableTagID($tag); + $tagInfo = { Name => "GoPro_$name", Description => "GoPro $name", Unknown => 1 }; + $$tagInfo{SubDirectory} = { TagTable => 'Image::ExifTool::GoPro::GPMF' } if not $fmt; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + # apply scaling if available to last tag in this container + ScaleValues($val, $scal) if $scal and $tag ne 'SCAL' and $pos+$size+3>=$dirEnd; + my $key = $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + Base => $base, + Start => $pos, + Size => $size, + TagInfo => $tagInfo, + Format => $format, + Extra => $verbose ? ", type='".($fmt ? chr($fmt) : '\0')."' size=$len count=$count" : undef, + ); + # save units for adding in print conversion if specified + $$et{TAG_EXTRA}{$key}{Units} = $unit if $$tagInfo{AddUnits} and $key; + } + delete $$et{SET_GROUP0} if $setGroup0; + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::GoPro - Read information from GoPro videos + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to decode +metadata from GoPro MP4 videos. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://github.com/gopro/gpmf-parser> + +=item L<https://github.com/stilldavid/gopro-utils> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/GoPro Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/H264.pm b/ExifTool/lib/Image/ExifTool/H264.pm new file mode 100644 index 0000000..e734383 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/H264.pm @@ -0,0 +1,1149 @@ +#------------------------------------------------------------------------------ +# File: H264.pm +# +# Description: Read meta information from H.264 video +# +# Revisions: 2010/01/31 - P. Harvey Created +# +# References: 1) http://www.itu.int/rec/T-REC-H.264/e (T-REC-H.264-200305-S!!PDF-E.pdf) +# 2) http://miffteevee.co.uk/documentation/development/H264Parser_8cpp-source.html +# 3) http://ffmpeg.org/ +# 4) US Patent 2009/0052875 A1 +# 5) European Patent (EP2 051 528A1) application no. 07792522.0 filed 08.08.2007 +# 6) Dave Nicholson private communication +# 7) http://www.freepatentsonline.com/20050076039.pdf +# 8) Michael Reitinger private communication (RX100) +# +# Glossary: RBSP = Raw Byte Sequence Payload +#------------------------------------------------------------------------------ + +package Image::ExifTool::H264; + +use strict; +use vars qw($VERSION %convMake); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::GPS; + +$VERSION = '1.17'; + +sub ProcessSEI($$); + +my $parsePictureTiming; # flag to enable parsing of picture timing information (test only) + +# lookup for camera manufacturer name +%convMake = ( + 0x0103 => 'Panasonic', + 0x0108 => 'Sony', + 0x1011 => 'Canon', + 0x1104 => 'JVC', #Rob Lewis +); + +# information extracted from H.264 video streams +%Image::ExifTool::H264::Main = ( + GROUPS => { 2 => 'Video' }, + VARS => { NO_ID => 1 }, + NOTES => q{ + Tags extracted from H.264 video streams. The metadata for AVCHD videos is + stored in this stream. + }, + ImageWidth => { }, + ImageHeight => { }, + MDPM => { SubDirectory => { TagTable => 'Image::ExifTool::H264::MDPM' } }, +); + +# H.264 Supplemental Enhancement Information User Data (ref PH/4) +%Image::ExifTool::H264::MDPM = ( + GROUPS => { 2 => 'Camera' }, + PROCESS_PROC => \&ProcessSEI, + TAG_PREFIX => 'MDPM', + NOTES => q{ + The following tags are decoded from the Modified Digital Video Pack Metadata + (MDPM) of the unregistered user data with UUID + 17ee8c60f84d11d98cd60800200c9a66 in the H.264 Supplemental Enhancement + Information (SEI). I<[Yes, this description is confusing, but nothing + compared to the challenge of actually decoding the data!]> This information + may exist at regular intervals through the entire video, but only the first + occurrence is extracted unless the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> (-ee) option is used (in + which case subsequent occurrences are extracted as sub-documents). + }, + # (Note: all these are explained in IEC 61834-4, but it costs money so it is useless to me) + # 0x00 - ControlCassetteID (ref 7) + # 0x01 - ControlTapeLength (ref 7) + # 0x02 - ControlTimerActDate (ref 7) + # 0x03 - ControlTimerACS_S_S (ref 7) + # 0x04-0x05 - ControlPR_StartPoint (ref 7) + # 0x06 - ControlTagIDNoGenre (ref 7) + # 0x07 - ControlTopicPageHeader (ref 7) + # 0x08 - ControlTextHeader (ref 7) + # 0x09 - ControlText (ref 7) + # 0x0a-0x0b - ControlTag (ref 7) + # 0x0c - ControlTeletextInfo (ref 7) + # 0x0d - ControlKey (ref 7) + # 0x0e-0x0f - ControlZoneEnd (ref 7) + # 0x10 - TitleTotalTime (ref 7) + # 0x11 - TitleRemainTime (ref 7) + # 0x12 - TitleChapterTotalNo (ref 7) + 0x13 => { + Name => 'TimeCode', + Notes => 'hours:minutes:seconds:frames', + ValueConv => 'sprintf("%.2x:%.2x:%.2x:%.2x",reverse unpack("C*",$val))', + }, + # 0x14 - TitleBinaryGroup - val: 0x00000000,0x14200130 + # 0x15 - TitleCassetteNo (ref 7) + # 0x16-0x17 - TitleSoftID (ref 7) + # (0x18,0x19 listed as TitleTextHeader/TitleText by ref 7) + 0x18 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Notes => 'combined with tag 0x19', + Combine => 1, # the next tag (0x19) contains the rest of the date + # first byte is timezone information: + # 0x80 - unused + # 0x40 - DST flag + # 0x20 - TimeZoneSign + # 0x1e - TimeZoneValue + # 0x01 - half-hour flag + ValueConv => q{ + my ($tz, @a) = unpack('C*',$val); + return sprintf('%.2x%.2x:%.2x:%.2x %.2x:%.2x:%.2x%s%.2d:%s%s', @a, + $tz & 0x20 ? '-' : '+', ($tz >> 1) & 0x0f, + $tz & 0x01 ? '30' : '00', + $tz & 0x40 ? ' DST' : ''); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + # 0x1a-0x1b - TitleStart (ref 7) + # 0x1c-0x1d - TitleReelID (ref 7) + # 0x1e-0x1f - TitleEnd (ref 7) + # 0x20 - ChapterTotalTime (ref 7) + # 0x42 - ProgramRecDTime (ref 7) + # 0x50/0x60 - (AAUX/VAUX)Source (ref 7) + # 0x51/0x61 - (AAUX/VAUX)SourceControl (ref 7) + # 0x52/0x62 - (AAUX/VAUX)RecDate (ref 7) + # 0x53/0x63 - (AAUX/VAUX)RecTime (ref 7) + # 0x54/0x64 - (AAUX/VAUX)BinaryGroup (ref 7) + # 0x55/0x65 - (AAUX/VAUX)ClosedCaption (ref 7) + # 0x56/0x66 - (AAUX/VAUX)TR (ref 7) + 0x70 => { # ConsumerCamera1 + Name => 'Camera1', + SubDirectory => { TagTable => 'Image::ExifTool::H264::Camera1' }, + }, + 0x71 => { # ConsumerCamera2 + Name => 'Camera2', + SubDirectory => { TagTable => 'Image::ExifTool::H264::Camera2' }, + }, + # 0x73 Lens - val: 0x04ffffd3,0x0effffd3,0x15ffffd3,0x41ffffd3,0x52ffffd3,0x59ffffd3,0x65ffffd3,0x71ffffd3,0x75ffffd3,0x79ffffd3,0x7fffffd3,0xffffffd3... + # 0x74 Gain - val: 0xb8ffff0f + # 0x75 Pedestal + # 0x76 Gamma + # 0x77 Detail + # 0x7b CameraPreset + # 0x7c Flare + # 0x7d Shading + # 0x7e Knee + 0x7f => { # Shutter + Name => 'Shutter', + SubDirectory => { + TagTable => 'Image::ExifTool::H264::Shutter', + ByteOrder => 'LittleEndian', # weird + }, + }, + 0xa0 => { + Name => 'ExposureTime', + Format => 'rational32u', + Groups => { 2 => 'Image' }, + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0xa1 => { + Name => 'FNumber', + Format => 'rational32u', + Groups => { 2 => 'Image' }, + }, + 0xa2 => { + Name => 'ExposureProgram', + Format => 'int32u', # (guess) + PrintConv => { + 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', + }, + }, + 0xa3 => { + Name => 'BrightnessValue', + Format => 'rational32s', + Groups => { 2 => 'Image' }, + }, + 0xa4 => { + Name => 'ExposureCompensation', + Format => 'rational32s', + Groups => { 2 => 'Image' }, + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + 0xa5 => { + Name => 'MaxApertureValue', + Format => 'rational32u', + ValueConv => '2 ** ($val / 2)', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0xa6 => { + Name => 'Flash', + Format => 'int32u', # (guess) + Flags => 'PrintHex', + SeparateTable => 'EXIF Flash', + PrintConv => \%Image::ExifTool::Exif::flash, + }, + 0xa7 => { + Name => 'CustomRendered', + Format => 'int32u', # (guess) + Groups => { 2 => 'Image' }, + PrintConv => { + 0 => 'Normal', + 1 => 'Custom', + }, + }, + 0xa8 => { + Name => 'WhiteBalance', + Format => 'int32u', # (guess) + Priority => 0, + PrintConv => { + 0 => 'Auto', + 1 => 'Manual', + }, + }, + 0xa9 => { + Name => 'FocalLengthIn35mmFormat', + Format => 'rational32u', + PrintConv => '"$val mm"', + }, + 0xaa => { + Name => 'SceneCaptureType', + Format => 'int32u', # (guess) + PrintConv => { + 0 => 'Standard', + 1 => 'Landscape', + 2 => 'Portrait', + 3 => 'Night', + }, + }, + # 0xab-0xaf ExifOption + 0xb0 => { + Name => 'GPSVersionID', + Format => 'int8u', + Count => 4, + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => '$val =~ tr/ /./; $val', + }, + 0xb1 => { + Name => 'GPSLatitudeRef', + Format => 'string', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + N => 'North', + S => 'South', + }, + }, + 0xb2 => { + Name => 'GPSLatitude', + Format => 'rational32u', + Groups => { 1 => 'GPS', 2 => 'Location' }, + Notes => 'combined with tags 0xb3 and 0xb4', + Combine => 2, # combine the next 2 tags (0xb2=deg, 0xb3=min, 0xb4=sec) + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', + }, + 0xb5 => { + Name => 'GPSLongitudeRef', + Format => 'string', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + E => 'East', + W => 'West', + }, + }, + 0xb6 => { + Name => 'GPSLongitude', + Format => 'rational32u', + Groups => { 1 => 'GPS', 2 => 'Location' }, + Combine => 2, # combine the next 2 tags (0xb6=deg, 0xb7=min, 0xb8=sec) + Notes => 'combined with tags 0xb7 and 0xb8', + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', + }, + 0xb9 => { + Name => 'GPSAltitudeRef', + Format => 'int32u', # (guess) + Groups => { 1 => 'GPS', 2 => 'Location' }, + ValueConv => '$val ? 1 : 0', # because I'm not sure about the Format + PrintConv => { + 0 => 'Above Sea Level', + 1 => 'Below Sea Level', + }, + }, + 0xba => { + Name => 'GPSAltitude', + Format => 'rational32u', + Groups => { 1 => 'GPS', 2 => 'Location' }, + }, + 0xbb => { + Name => 'GPSTimeStamp', + Format => 'rational32u', + Groups => { 1 => 'GPS', 2 => 'Time' }, + Combine => 2, # the next tags (0xbc/0xbd) contain the minutes/seconds + Notes => 'combined with tags 0xbc and 0xbd', + ValueConv => 'Image::ExifTool::GPS::ConvertTimeStamp($val)', + PrintConv => 'Image::ExifTool::GPS::PrintTimeStamp($val)', + }, + 0xbe => { + Name => 'GPSStatus', + Format => 'string', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + A => 'Measurement Active', + V => 'Measurement Void', + }, + }, + 0xbf => { + Name => 'GPSMeasureMode', + Format => 'string', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + 2 => '2-Dimensional Measurement', + 3 => '3-Dimensional Measurement', + }, + }, + 0xc0 => { + Name => 'GPSDOP', + Description => 'GPS Dilution Of Precision', + Format => 'rational32u', + Groups => { 1 => 'GPS', 2 => 'Location' }, + }, + 0xc1 => { + Name => 'GPSSpeedRef', + Format => 'string', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + K => 'km/h', + M => 'mph', + N => 'knots', + }, + }, + 0xc2 => { + Name => 'GPSSpeed', + Format => 'rational32u', + Groups => { 1 => 'GPS', 2 => 'Location' }, + }, + 0xc3 => { + Name => 'GPSTrackRef', + Format => 'string', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + 0xc4 => { + Name => 'GPSTrack', + Format => 'rational32u', + Groups => { 1 => 'GPS', 2 => 'Location' }, + }, + 0xc5 => { + Name => 'GPSImgDirectionRef', + Format => 'string', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + 0xc6 => { + Name => 'GPSImgDirection', + Format => 'rational32u', + Groups => { 1 => 'GPS', 2 => 'Location' }, + }, + 0xc7 => { + Name => 'GPSMapDatum', + Format => 'string', + Groups => { 1 => 'GPS', 2 => 'Location' }, + Combine => 1, # the next tag (0xc8) contains the rest of the string + Notes => 'combined with tag 0xc8', + }, + # 0xc9-0xcf - GPSOption + # 0xc9 - val: 0x001d0203 + 0xca => { #PH (Sony DSC-HX7V) + Name => 'GPSDateStamp', + Format => 'string', + Groups => { 1 => 'GPS', 2 => 'Time' }, + Combine => 2, # the next 2 tags contain the rest of the string + Notes => 'combined with tags 0xcb and 0xcc', + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + }, + 0xe0 => { + Name => 'MakeModel', + SubDirectory => { TagTable => 'Image::ExifTool::H264::MakeModel' }, + }, + # 0xe1-0xef - MakerOption + # 0xe1 - val: 0x01000670,0x01000678,0x06ffffff,0x01ffffff,0x01000020,0x01000400... + 0xe1 => { #6 + Name => 'RecInfo', + Condition => '$$self{Make} eq "Canon"', + Notes => 'Canon only', + SubDirectory => { TagTable => 'Image::ExifTool::H264::RecInfo' }, + }, + # 0xe2-0xe8 - val: 0x00000000 in many samples + # 0xe2 - val: 0x00000000,0x01000000,0x01010000,0x8080900c,0x8080a074 + # 0xe3 - val: 0x00801f89,0x00801f8b,0x00c01f89,0xc9c01f80 + 0xe4 => { #PH + Name => 'Model', + Condition => '$$self{Make} eq "Sony"', # (possibly also Canon models?) + Description => 'Camera Model Name', + Notes => 'Sony only, combined with tags 0xe5 and 0xe6', + Format => 'string', + Combine => 2, # (not sure about 0xe6, but include it just in case) + RawConv => '$val eq "" ? undef : $val', + }, + # 0xeb - val: 0x008a0a00,0x0a300000,0x508a0a00,0x52880a00,0x528a0a00 + # 0xec - val: 0x0b700000 + # 0xed - val: 0x0ce0f819 + 0xee => { #6 (HFS200) + Name => 'FrameInfo', + Condition => '$$self{Make} eq "Canon"', + Notes => 'Canon only', + SubDirectory => { TagTable => 'Image::ExifTool::H264::FrameInfo' }, + }, + # 0xef - val: 0x01c00000,0x0e00000c +); + +# ConsumerCamera1 information (ref PH) +%Image::ExifTool::H264::Camera1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Camera' }, + TAG_PREFIX => 'Camera1', + PRINT_CONV => 'sprintf("0x%.2x",$val)', + FIRST_ENTRY => 0, + 0 => { + Name => 'ApertureSetting', + PrintHex => 1, + PrintConv => { + 0xff => 'Auto', + 0xfe => 'Closed', + OTHER => sub { sprintf('%.1f', 2 ** (($_[0] & 0x3f) / 8)) }, + }, + }, + 1 => { + Name => 'Gain', + Mask => 0x0f, + # (0x0f would translate to 42 dB, but this value is used by the Sony + # HXR-NX5U for any out-of-range value such as -6 dB or "hyper gain" - PH) + ValueConv => '($val - 1) * 3', + PrintConv => '$val==42 ? "Out of range" : "$val dB"', + }, + 1.1 => { + Name => 'ExposureProgram', + Mask => 0xf0, + ValueConv => '$val == 15 ? undef : $val', + PrintConv => { + 0 => 'Program AE', + 1 => 'Gain', #? + 2 => 'Shutter speed priority AE', + 3 => 'Aperture-priority AE', + 4 => 'Manual', + }, + }, + 2.1 => { + Name => 'WhiteBalance', + Mask => 0xe0, + ValueConv => '$val == 7 ? undef : $val', + PrintConv => { + 0 => 'Auto', + 1 => 'Hold', + 2 => '1-Push', + 3 => 'Daylight', + }, + }, + 3 => { + Name => 'Focus', + ValueConv => '$val == 0xff ? undef : $val', + PrintConv => q{ + my $foc = ($val & 0x7e) / (($val & 0x01) ? 40 : 400); + return(($val & 0x80 ? 'Manual' : 'Auto') . " ($foc)"); + }, + }, +); + +# ConsumerCamera2 information (ref PH) +%Image::ExifTool::H264::Camera2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Camera' }, + TAG_PREFIX => 'Camera2', + PRINT_CONV => 'sprintf("0x%.2x",$val)', + FIRST_ENTRY => 0, + 1 => { + Name => 'ImageStabilization', + PrintHex => 1, + PrintConv => { + 0 => 'Off', + 0x3f => 'On (0x3f)', #8 + 0xbf => 'Off (0xbf)', #8 + 0xff => 'n/a', + OTHER => sub { + my $val = shift; + sprintf("%s (0x%.2x)", $val & 0x10 ? "On" : "Off", $val); + }, + }, + }, +); + +# camera info 0x7f (ref PH) +%Image::ExifTool::H264::Shutter = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + TAG_PREFIX => 'Shutter', + PRINT_CONV => 'sprintf("0x%.2x",$val)', + FIRST_ENTRY => 0, + FORMAT => 'int16u', + 1.1 => { #6 + Name => 'ExposureTime', + Mask => 0x7fff, # (what is bit 0x8000 for?) + RawConv => '$val == 0x7fff ? undef : $val', #7 + ValueConv => '$val / 28125', #PH (Vixia HF G30, ref forum5588) (was $val/33640 until 9.49) + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, +); + +# camera info 0xe0 (ref PH) +%Image::ExifTool::H264::MakeModel = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Camera' }, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + 0 => { + Name => 'Make', + PrintHex => 1, + RawConv => '$$self{Make} = ($Image::ExifTool::H264::convMake{$val} || "Unknown"); $val', + PrintConv => \%convMake, + }, + # 1 => ModelIDCode according to ref 4/5 (I think not - PH) + # 1 => { Name => 'ModelIDCode', PrintConv => 'sprintf("%.4x",$val)' }, + # vals: 0x0313 - various Pansonic HDC models + # 0x0345 - Panasonic HC-V7272 + # 0x0414 - Panasonic AG-AF100 + # 0x0591 - various Panasonic DMC models + # 0x0802 - Panasonic DMC-TZ60 with GPS information off + # 0x0803 - Panasonic DMC-TZ60 with GPS information on + # 0x3001 - various Sony DSC, HDR, NEX and SLT models + # 0x3003 - various Sony DSC models + # 0x3100 - various Sony DSC, ILCE, NEX and SLT models + # 0x1000 - Sony HDR-UX1 + # 0x2000 - Canon HF100 (60i) + # 0x3000 - Canon HF100 (30p) + # 0x3101 - Canon HFM300 (PH, all qualities and frame rates) + # 0x3102 - Canon HFS200 + # 0x4300 - Canon HFG30 +); + +# camera info 0xe1 (ref 6) +%Image::ExifTool::H264::RecInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Camera' }, + FORMAT => 'int8u', + NOTES => 'Recording information stored by some Canon video cameras.', + FIRST_ENTRY => 0, + 0 => { + Name => 'RecordingMode', + PrintConv => { + 0x02 => 'XP+', # High Quality 12 Mbps + 0x04 => 'SP', # Standard Play 7 Mbps + 0x05 => 'LP', # Long Play 5 Mbps + 0x06 => 'FXP', # High Quality 17 Mbps + 0x07 => 'MXP', # High Quality 24 Mbps + }, + }, +); + +# camera info 0xee (ref 6) +%Image::ExifTool::H264::FrameInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int8u', + NOTES => 'Frame rate information stored by some Canon video cameras.', + FIRST_ENTRY => 0, + 0 => 'CaptureFrameRate', + 1 => 'VideoFrameRate', + # 2 - 8=60i, 10=PF30, 74=PF24 (PH, HFM300) +); + +#============================================================================== +# Bitstream functions (used for H264 video) +# +# Member variables: +# Mask = mask for next bit to read (0 when all data has been read) +# Pos = byte offset of next word to read +# Word = current data word +# Len = total data length in bytes +# DataPt = data pointer +#.............................................................................. + +#------------------------------------------------------------------------------ +# Read next word from bitstream +# Inputs: 0) BitStream ref +# Returns: true if there is more data (and updates +# Mask, Pos and Word for first bit in next word) +sub ReadNextWord($) +{ + my $bstr = shift; + my $pos = $$bstr{Pos}; + if ($pos + 4 <= $$bstr{Len}) { + $$bstr{Word} = unpack("x$pos N", ${$$bstr{DataPt}}); + $$bstr{Mask} = 0x80000000; + $$bstr{Pos} += 4; + } elsif ($pos < $$bstr{Len}) { + my @bytes = unpack("x$pos C*", ${$$bstr{DataPt}}); + my ($word, $mask) = (shift(@bytes), 0x80); + while (@bytes) { + $word = ($word << 8) | shift(@bytes); + $mask <<= 8; + } + $$bstr{Word} = $word; + $$bstr{Mask} = $mask; + $$bstr{Pos} = $$bstr{Len}; + } else { + return 0; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Create a new BitStream object +# Inputs: 0) data ref +# Returns: BitStream ref, or null if data is empty +sub NewBitStream($) +{ + my $dataPt = shift; + my $bstr = { + DataPt => $dataPt, + Len => length($$dataPt), + Pos => 0, + Mask => 0, + }; + ReadNextWord($bstr) or undef $bstr; + return $bstr; +} + +#------------------------------------------------------------------------------ +# Get number of bits remaining in bit stream +# Inputs: 0) BitStream ref +# Returns: number of bits remaining +sub BitsLeft($) +{ + my $bstr = shift; + my $bits = 0; + my $mask = $$bstr{Mask}; + while ($mask) { + ++$bits; + $mask >>= 1; + } + return $bits + 8 * ($$bstr{Len} - $$bstr{Pos}); +} + +#------------------------------------------------------------------------------ +# Get integer from bitstream +# Inputs: 0) BitStream ref, 1) number of bits +# Returns: integer (and increments position in bitstream) +sub GetIntN($$) +{ + my ($bstr, $bits) = @_; + my $val = 0; + while ($bits--) { + $val <<= 1; + ++$val if $$bstr{Mask} & $$bstr{Word}; + $$bstr{Mask} >>= 1 and next; + ReadNextWord($bstr) or last; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Get Exp-Golomb integer from bitstream +# Inputs: 0) BitStream ref +# Returns: integer (and increments position in bitstream) +sub GetGolomb($) +{ + my $bstr = shift; + # first, count the number of zero bits to get the integer bit width + my $count = 0; + until ($$bstr{Mask} & $$bstr{Word}) { + ++$count; + $$bstr{Mask} >>= 1 and next; + ReadNextWord($bstr) or last; + } + # then return the adjusted integer + return GetIntN($bstr, $count + 1) - 1; +} + +#------------------------------------------------------------------------------ +# Get signed Exp-Golomb integer from bitstream +# Inputs: 0) BitStream ref +# Returns: integer (and increments position in bitstream) +sub GetGolombS($) +{ + my $bstr = shift; + my $val = GetGolomb($bstr) + 1; + return ($val & 1) ? -($val >> 1) : ($val >> 1); +} + +# end bitstream functions +#============================================================================== + +#------------------------------------------------------------------------------ +# Decode H.264 scaling matrices +# Inputs: 0) BitStream ref +# Reference: http://ffmpeg.org/ +sub DecodeScalingMatrices($) +{ + my $bstr = shift; + if (GetIntN($bstr, 1)) { + my ($i, $j); + for ($i=0; $i<8; ++$i) { + my $size = $i<6 ? 16 : 64; + next unless GetIntN($bstr, 1); + my ($last, $next) = (8, 8); + for ($j=0; $j<$size; ++$j) { + $next = ($last + GetGolombS($bstr)) & 0xff if $next; + last unless $j or $next; + } + } + } +} + +#------------------------------------------------------------------------------ +# Parse H.264 sequence parameter set RBSP (ref 1) +# Inputs: 0) ExifTool ref, 1) tag table ref, 2) data ref +# Notes: All this just to get the image size! +sub ParseSeqParamSet($$$) +{ + my ($et, $tagTablePtr, $dataPt) = @_; + # initialize our bitstream object + my $bstr = NewBitStream($dataPt) or return; + my ($t, $i, $j, $n); + # the messy nature of H.264 encoding makes it difficult to use + # data-driven structure parsing, so I code it explicitly (yuck!) + $t = GetIntN($bstr, 8); # profile_idc + GetIntN($bstr, 16); # constraints and level_idc + GetGolomb($bstr); # seq_parameter_set_id + if ($t >= 100) { # (ref b) + $t = GetGolomb($bstr); # chroma_format_idc + if ($t == 3) { + GetIntN($bstr, 1); # separate_colour_plane_flag + $n = 12; + } else { + $n = 8; + } + GetGolomb($bstr); # bit_depth_luma_minus8 + GetGolomb($bstr); # bit_depth_chroma_minus8 + GetIntN($bstr, 1); # qpprime_y_zero_transform_bypass_flag + DecodeScalingMatrices($bstr); + } + GetGolomb($bstr); # log2_max_frame_num_minus4 + $t = GetGolomb($bstr); # pic_order_cnt_type + if ($t == 0) { + GetGolomb($bstr); # log2_max_pic_order_cnt_lsb_minus4 + } elsif ($t == 1) { + GetIntN($bstr, 1); # delta_pic_order_always_zero_flag + GetGolomb($bstr); # offset_for_non_ref_pic + GetGolomb($bstr); # offset_for_top_to_bottom_field + $n = GetGolomb($bstr); # num_ref_frames_in_pic_order_cnt_cycle + for ($i=0; $i<$n; ++$i) { + GetGolomb($bstr); # offset_for_ref_frame[i] + } + } + GetGolomb($bstr); # num_ref_frames + GetIntN($bstr, 1); # gaps_in_frame_num_value_allowed_flag + my $w = GetGolomb($bstr); # pic_width_in_mbs_minus1 + my $h = GetGolomb($bstr); # pic_height_in_map_units_minus1 + my $f = GetIntN($bstr, 1); # frame_mbs_only_flag + $f or GetIntN($bstr, 1); # mb_adaptive_frame_field_flag + GetIntN($bstr, 1); # direct_8x8_inference_flag + # convert image size to pixels + $w = ($w + 1) * 16; + $h = (2 - $f) * ($h + 1) * 16; + # account for cropping (if any) + $t = GetIntN($bstr, 1); # frame_cropping_flag + if ($t) { + my $m = 4 - $f * 2; + $w -= 4 * GetGolomb($bstr);# frame_crop_left_offset + $w -= 4 * GetGolomb($bstr);# frame_crop_right_offset + $h -= $m * GetGolomb($bstr);# frame_crop_top_offset + $h -= $m * GetGolomb($bstr);# frame_crop_bottom_offset + } + # quick validity checks (just in case) + return unless $$bstr{Mask}; + if ($w>=160 and $w<=4096 and $h>=120 and $h<=3072) { + $et->HandleTag($tagTablePtr, ImageWidth => $w); + $et->HandleTag($tagTablePtr, ImageHeight => $h); + # (whew! -- so much work just to get ImageSize!!) + } + # return now unless interested in picture timing information + return unless $parsePictureTiming; + + # parse vui parameters if they exist + GetIntN($bstr, 1) or return; # vui_parameters_present_flag + $t = GetIntN($bstr, 1); # aspect_ratio_info_present_flag + if ($t) { + $t = GetIntN($bstr, 8); # aspect_ratio_idc + if ($t == 255) { # Extended_SAR ? + GetIntN($bstr, 32); # sar_width/sar_height + } + } + $t = GetIntN($bstr, 1); # overscan_info_present_flag + GetIntN($bstr, 1) if $t; # overscan_appropriate_flag + $t = GetIntN($bstr, 1); # video_signal_type_present_flag + if ($t) { + GetIntN($bstr, 4); # video_format/video_full_range_flag + $t = GetIntN($bstr, 1); # colour_description_present_flag + GetIntN($bstr, 24) if $t; # colour_primaries/transfer_characteristics/matrix_coefficients + } + $t = GetIntN($bstr, 1); # chroma_loc_info_present_flag + if ($t) { + GetGolomb($bstr); # chroma_sample_loc_type_top_field + GetGolomb($bstr); # chroma_sample_loc_type_bottom_field + } + $t = GetIntN($bstr, 1); # timing_info_present_flag + if ($t) { + return if BitsLeft($bstr) < 65; + $$et{VUI_units} = GetIntN($bstr, 32); # num_units_in_tick + $$et{VUI_scale} = GetIntN($bstr, 32); # time_scale + GetIntN($bstr, 1); # fixed_frame_rate_flag + } + my $hard; + for ($j=0; $j<2; ++$j) { + $t = GetIntN($bstr, 1); # nal_/vcl_hrd_parameters_present_flag + if ($t) { + $$et{VUI_hard} = 1; + $hard = 1; + $n = GetGolomb($bstr); # cpb_cnt_minus1 + GetIntN($bstr, 8); # bit_rate_scale/cpb_size_scale + for ($i=0; $i<=$n; ++$i) { + GetGolomb($bstr); # bit_rate_value_minus1[SchedSelIdx] + GetGolomb($bstr); # cpb_size_value_minus1[SchedSelIdx] + GetIntN($bstr, 1); # cbr_flag[SchedSelIdx] + } + GetIntN($bstr, 5); # initial_cpb_removal_delay_length_minus1 + $$et{VUI_clen} = GetIntN($bstr, 5); # cpb_removal_delay_length_minus1 + $$et{VUI_dlen} = GetIntN($bstr, 5); # dpb_output_delay_length_minus1 + $$et{VUI_toff} = GetIntN($bstr, 5); # time_offset_length + } + } + GetIntN($bstr, 1) if $hard; # low_delay_hrd_flag + $$et{VUI_pic} = GetIntN($bstr, 1); # pic_struct_present_flag + # (don't yet decode the rest of the vui data) +} + +#------------------------------------------------------------------------------ +# Parse H.264 picture timing SEI message (payload type 1) (ref 1) +# Inputs: 0) ExifTool ref, 1) data ref +# Notes: this routine is for test purposes only, and not called unless the +# $parsePictureTiming flag is set +sub ParsePictureTiming($$) +{ + my ($et, $dataPt) = @_; + my $bstr = NewBitStream($dataPt) or return; + my ($i, $t, $n); + # the specification is very odd on this point: the following delays + # exist if the VUI hardware parameters are present, or if + # "determined by the application, by some means not specified" -- WTF?? + if ($$et{VUI_hard}) { + GetIntN($bstr, $$et{VUI_clen} + 1); # cpb_removal_delay + GetIntN($bstr, $$et{VUI_dlen} + 1); # dpb_output_delay + } + if ($$et{VUI_pic}) { + $t = GetIntN($bstr, 4); # pic_struct + # determine NumClockTS ($n) + $n = { 0=>1, 1=>1, 2=>1, 3=>2, 4=>2, 5=>3, 6=>3, 7=>2, 8=>3 }->{$t}; + $n or return; + for ($i=0; $i<$n; ++$i) { + $t = GetIntN($bstr, 1); # clock_timestamp_flag[i] + next unless $t; + my ($nu, $s, $m, $h, $o); + GetIntN($bstr, 2); # ct_type + $nu = GetIntN($bstr, 1);# nuit_field_based_flag + GetIntN($bstr, 5); # counting_type + $t = GetIntN($bstr, 1); # full_timestamp_flag + GetIntN($bstr, 1); # discontinuity_flag + GetIntN($bstr, 1); # cnt_dropped_flag + GetIntN($bstr, 8); # n_frames + if ($t) { + $s = GetIntN($bstr, 6); # seconds_value + $m = GetIntN($bstr, 6); # minutes_value + $h = GetIntN($bstr, 5); # hours_value + } else { + $t = GetIntN($bstr, 1); # seconds_flag + if ($t) { + $s = GetIntN($bstr, 6); # seconds_value + $t = GetIntN($bstr, 1); # minutes_flag + if ($t) { + $m = GetIntN($bstr, 6); # minutes_value + $t = GetIntN($bstr, 1); # hours_flag + $h = GetIntN($bstr, 5) if $t; # hours_value + } + } + } + if ($$et{VUI_toff}) { + $o = GetIntN($bstr, $$et{VUI_toff}); # time_offset + } + last; # only parse the first clock timestamp found + } + } +} + +#------------------------------------------------------------------------------ +# Process H.264 Supplementary Enhancement Information (ref 1/PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 if we processed payload type 5 +# Payload types: +# 0 - buffer period +# 1 - pic timing +# 2 - pan scan rect +# 3 - filler payload +# 4 - user data registered itu t t35 +# 5 - user data unregistered +# 6 - recovery point +# 7 - dec ref pic marking repetition +# 8 - spare pic +# 9 - sene info +# 10 - sub seq info +# 11 - sub seq layer characteristics +# 12 - sub seq characteristics +# 13 - full frame freeze +# 14 - full frame freeze release +# 15 - full frame snapshot +# 16 - progressive refinement segment start +# 17 - progressive refinement segment end +# 18 - motion constrained slice group set +sub ProcessSEI($$) +{ + my ($et, $dirInfo) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $end = length($$dataPt); + my $pos = 0; + my ($type, $size, $index, $t); + + # scan through SEI payload for type 5 (the unregistered user data) + for (;;) { + $type = 0; + for (;;) { + return 0 if $pos >= $end; + $t = Get8u($dataPt, $pos++); # payload type + $type += $t; + last unless $t == 255; + } + return 0 if $type == 0x80; # terminator (ref PH - maybe byte alignment bits?) + $size = 0; + for (;;) { + return 0 if $pos >= $end; + $t = Get8u($dataPt, $pos++); # payload data length + $size += $t; + last unless $t == 255; + } + return 0 if $pos + $size > $end; + $et->VPrint(1," (SEI type $type)\n"); + if ($type == 1) { # picture timing information + if ($parsePictureTiming) { + my $buff = substr($$dataPt, $pos, $size); + ParsePictureTiming($et, $dataPt); + } + } elsif ($type == 5) { # unregistered user data + last; # exit loop to process user data now + } + $pos += $size; + } + + # look for our 16-byte UUID + # - plus "MDPM" for "ModifiedDVPackMeta" + # - plus "GA94" for closed-caption data (currently not decoded) + return 0 unless $size > 20 and substr($$dataPt, $pos, 20) eq + "\x17\xee\x8c\x60\xf8\x4d\x11\xd9\x8c\xd6\x08\0\x20\x0c\x9a\x66MDPM"; +# +# parse the MDPM records in the UUID 17ee8c60f84d11d98cd60800200c9a66 +# unregistered user data payload (ref PH) +# + my $tagTablePtr = GetTagTable('Image::ExifTool::H264::MDPM'); + my $oldIndent = $$et{INDENT}; + $$et{INDENT} .= '| '; + $end = $pos + $size; # end of payload + $pos += 20; # skip UUID + "MDPM" + my $num = Get8u($dataPt, $pos++); # get entry count + my $lastTag = 0; + $et->VerboseDir('MDPM', $num) if $et->Options('Verbose'); + # walk through entries in the MDPM payload + for ($index=0; $index<$num and $pos<$end; ++$index) { + my $tag = Get8u($dataPt, $pos); + if ($tag <= $lastTag) { # should be in numerical order (PH) + $et->Warn('Entries in MDPM directory are out of sequence'); + last; + } + $lastTag = $tag; + my $buff = substr($$dataPt, $pos + 1, 4); + my $from; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo) { + # use our own print conversion for Unknown tags + if ($$tagInfo{Unknown} and not $$tagInfo{SetPrintConv}) { + $$tagInfo{PrintConv} = 'sprintf("0x%.8x", unpack("N", $val))'; + $$tagInfo{SetPrintConv} = 1; + } + # combine with next value(s) if necessary + my $combine = $$tagTablePtr{$tag}{Combine}; + while ($combine) { + last if $pos + 5 >= $end; + my $t = Get8u($dataPt, $pos + 5); + last if $t != $lastTag + 1; # must be consecutive tag ID's + $pos += 5; + $buff .= substr($$dataPt, $pos + 1, 4); + $from = $index unless defined $from; + ++$index; + ++$lastTag; + --$combine; + } + $et->HandleTag($tagTablePtr, $tag, undef, + TagInfo => $tagInfo, + DataPt => \$buff, + Size => length($buff), + Index => defined $from ? "$from-$index" : $index, + ); + } + $pos += 5; + } + $$et{INDENT} = $oldIndent; + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from H.264 video stream +# Inputs: 0) ExifTool ref, 1) data ref +# Returns: 0 = done parsing, 1 = we want to parse more of these +sub ParseH264Video($$) +{ + my ($et, $dataPt) = @_; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my $tagTablePtr = GetTagTable('Image::ExifTool::H264::Main'); + my %parseNalUnit = ( 0x06 => 1, 0x07 => 1 ); # NAL unit types to parse + my $foundUserData; + my $len = length $$dataPt; + my $pos = 0; + while ($pos < $len) { + my ($nextPos, $end); + # find start of next NAL unit + if ($$dataPt =~ /(\0{2,3}\x01)/g) { + $nextPos = pos $$dataPt; + $end = $nextPos - length $1; + $pos or $pos = $nextPos, next; + } else { + last unless $pos; + $nextPos = $end = $len; + } + last if $pos >= $len; + # parse NAL unit from $pos to $end + my $nal_unit_type = Get8u($dataPt, $pos); + ++$pos; + # check forbidden_zero_bit + $nal_unit_type & 0x80 and $et->Warn('H264 forbidden bit error'), last; + $nal_unit_type &= 0x1f; + # ignore this NAL unit unless we will parse it + $parseNalUnit{$nal_unit_type} or $verbose or $pos = $nextPos, next; + # read NAL unit (and convert all 0x000003's to 0x0000 as per spec.) + my $buff = ''; + pos($$dataPt) = $pos + 1; + while ($$dataPt =~ /\0\0\x03/g) { + last if pos $$dataPt > $end; + $buff .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos); + $pos = pos $$dataPt; + } + $buff .= substr($$dataPt, $pos, $end - $pos); + if ($verbose > 1) { + printf $out " NAL Unit Type: 0x%x (%d bytes)\n",$nal_unit_type, length $buff; + $et->VerboseDump(\$buff); + } + pos($$dataPt) = $pos = $nextPos; + + if ($nal_unit_type == 0x06) { # sei_rbsp (supplemental enhancement info) + + if ($$et{GotNAL06}) { + # process only the first SEI unless ExtractEmbedded is set + next unless $et->Options('ExtractEmbedded'); + $$et{DOC_NUM} = $$et{GotNAL06}; + } + $foundUserData = ProcessSEI($et, { DataPt => \$buff } ); + delete $$et{DOC_NUM}; + # keep parsing SEI's until we find the user data + next unless $foundUserData; + $$et{GotNAL06} = ($$et{GotNAL06} || 0) + 1; + + } elsif ($nal_unit_type == 0x07) { # sequence_parameter_set_rbsp + + # process this NAL unit type only once + next if $$et{GotNAL07}; + $$et{GotNAL07} = 1; + ParseSeqParamSet($et, $tagTablePtr, \$buff); + } + # we were successful, so don't parse this NAL unit type again + delete $parseNalUnit{$nal_unit_type}; + } + # parse one extra H264 frame if we didn't find the user data in this one + # (Panasonic cameras don't put the SEI in the first frame) + return 0 if $foundUserData or $$et{ParsedH264}; + $$et{ParsedH264} = 1; + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::H264 - Read meta information from H.264 video + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +information from H.264 video streams. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.itu.int/rec/T-REC-H.264/e> + +=item L<http://miffteevee.co.uk/documentation/development/H264Parser_8cpp-source.html> + +=item L<http://ffmpeg.org/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/H264 Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/HP.pm b/ExifTool/lib/Image/ExifTool/HP.pm new file mode 100644 index 0000000..d368e30 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/HP.pm @@ -0,0 +1,263 @@ +#------------------------------------------------------------------------------ +# File: HP.pm +# +# Description: Hewlett-Packard maker notes tags +# +# Revisions: 2007-05-03 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::HP; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.04'; + +sub ProcessHP($$$); +sub ProcessTDHD($$$); + +# HP EXIF-format maker notes (or is it Vivitar?) +%Image::ExifTool::HP::Main = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tables list tags found in the maker notes of some Hewlett-Packard + camera models. + + The first table lists tags found in the EXIF-format maker notes of the + PhotoSmart 720 (also used by the Vivitar ViviCam 3705, 3705B and 3715). + }, + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, +); + +# other types of HP maker notes +%Image::ExifTool::HP::Type2 = ( + PROCESS_PROC => \&ProcessHP, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are used by the PhotoSmart E427.', + 'PreviewImage' => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + RawConv => '$self->ValidateImage(\$val,$tag)', + }, + 'Serial Number' => 'SerialNumber', + 'Lens Shading' => 'LensShading', +); + +%Image::ExifTool::HP::Type4 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are used by the PhotoSmart M627.', + 0x0c => { + Name => 'MaxAperture', + Format => 'int16u', + ValueConv => '$val / 10', + }, + 0x10 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val / 1e6', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x14 => { + Name => 'CameraDateTime', + Groups => { 2 => 'Time' }, + Format => 'string[20]', + }, + 0x34 => { + Name => 'ISO', + Format => 'int16u', + }, + 0x5c => { + Name => 'SerialNumber', + Format => 'string[26]', + RawConv => '$val =~ s/^SERIAL NUMBER:// ? $val : undef', + }, +); + +%Image::ExifTool::HP::Type6 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are used by the PhotoSmart M425, M525 and M527.', + 0x0c => { + Name => 'FNumber', + Format => 'int16u', + ValueConv => '$val / 10', + }, + 0x10 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val / 1e6', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x14 => { + Name => 'CameraDateTime', + Groups => { 2 => 'Time' }, + Format => 'string[20]', + }, + 0x34 => { + Name => 'ISO', + Format => 'int16u', + }, + 0x58 => { + Name => 'SerialNumber', + Format => 'string[26]', + RawConv => '$val =~ s/^SERIAL NUMBER:// ? $val : undef', + }, +); + +# proprietary format TDHD data written by Photosmart R837 (ref PH) +%Image::ExifTool::HP::TDHD = ( + PROCESS_PROC => \&ProcessTDHD, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from the APP6 "TDHD" segment of Photosmart R837 + JPEG images. Many other unknown tags exist in is data, and can be seen with + the L<Unknown|../ExifTool.html#Unknown> (-u) option. + }, + # (all subdirectories except TDHD and LSLV are automatically recognized + # by their "type" word of 0x10001) + TDHD => { + Name => 'TDHD', + SubDirectory => { TagTable => 'Image::ExifTool::HP::TDHD' }, + }, + LSLV => { + Name => 'LSLV', + SubDirectory => { TagTable => 'Image::ExifTool::HP::TDHD' }, + }, + FWRV => 'FirmwareVersion', + CMSN => 'SerialNumber', # (unverified) + # LTEM - some temperature? +); + +#------------------------------------------------------------------------------ +# Process HP APP6 TDHD metadata (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessTDHD($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $pos = $$dirInfo{DirStart}; + my $dirEnd = $pos + $$dirInfo{DirLen}; + my $unknown = $et->Options('Unknown') || $et->Options('Verbose'); + $et->VerboseDir('TDHD', undef, $$dirInfo{DirLen}); + SetByteOrder('II'); + while ($pos + 12 < $dirEnd) { + my $tag = substr($$dataPt, $pos, 4); + my $type = Get32u($dataPt, $pos + 4); + my $size = Get32u($dataPt, $pos + 8); + $pos += 12; + last if $size < 0 or $pos + $size > $dirEnd; + if ($type == 0x10001) { + # this is a subdirectory containing more tags + my %dirInfo = ( + DataPt => $dataPt, + DataPos => $dataPos, + DirStart => $pos, + DirLen => $size, + ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } else { + if (not $$tagTablePtr{$tag} and $unknown) { + my $name = $tag; + $name =~ tr/-_A-Za-z0-9//dc; # remove invalid characters + my %tagInfo = ( + Name => "HP_TDHD_$name", + Unknown => 1, + ); + # guess format based on data size + if ($size == 1) { + $tagInfo{Format} = 'int8u'; + } elsif ($size == 2) { + $tagInfo{Format} = 'int16u'; + } elsif ($size == 4) { + $tagInfo{Format} = 'int32s'; + } elsif ($size > 80) { + $tagInfo{Binary} = 1; + } + AddTagToTable($tagTablePtr, $tag, \%tagInfo); + } + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos, + Size => $size, + ); + } + $pos += $size; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process HP maker notes +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessHP($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = $$dirInfo{DataLen}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || $dataLen - $dirStart; + + # look for known text-type tags + if ($dirStart or $dirLen != length($$dataPt)) { + my $buff = substr($$dataPt, $dirStart, $dirLen); + $dataPt = \$buff; + } + my $tagID; + # brute-force scan for PreviewImage + if ($$tagTablePtr{PreviewImage} and $$dataPt =~ /(\xff\xd8\xff\xdb.*\xff\xd9)/gs) { + $et->HandleTag($tagTablePtr, 'PreviewImage', $1); + # truncate preview to speed subsequent tag scans + my $buff = substr($$dataPt, 0, pos($$dataPt)-length($1)); + $dataPt = \$buff; + } + # scan for other tag ID's + foreach $tagID (sort(TagTableKeys($tagTablePtr))) { + next if $tagID eq 'PreviewImage'; + next unless $$dataPt =~ /$tagID:\s*([\x20-\x7f]+)/i; + $et->HandleTag($tagTablePtr, $tagID, $1); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::HP - Hewlett-Packard 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 +Hewlett-Packard maker notes. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/HP Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/HTML.pm b/ExifTool/lib/Image/ExifTool/HTML.pm new file mode 100644 index 0000000..05f6dc3 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/HTML.pm @@ -0,0 +1,583 @@ +#------------------------------------------------------------------------------ +# File: HTML.pm +# +# Description: Read HTML meta information +# +# Revisions: 01/30/2007 - P. Harvey Created +# +# References: 1) http://www.w3.org/TR/html4/ +# 2) http://www.daisy.org/publications/specifications/daisy_202.html +# 3) http://vancouver-webpages.com/META/metatags.detail.html +# 4) http://www.html-reference.com/META.htm +#------------------------------------------------------------------------------ + +package Image::ExifTool::HTML; + +use strict; +use vars qw($VERSION @ISA @EXPORT_OK); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::PostScript; +use Image::ExifTool::XMP qw(EscapeXML UnescapeXML); +require Exporter; + +$VERSION = '1.16'; +@ISA = qw(Exporter); +@EXPORT_OK = qw(EscapeHTML UnescapeHTML); + +sub SetHTMLCharset($$); + +# convert HTML charset (lower case) to ExifTool Charset name +my %htmlCharset = ( + macintosh => 'MacRoman', + 'iso-8859-1' => 'Latin', + 'utf-8' => 'UTF8', + 'windows-1252' => 'Latin', +); + +# HTML 4 character entity references +my %entityNum = ( + 'quot' => 34, 'eth' => 240, 'lsquo' => 8216, + 'amp' => 38, 'ntilde' => 241, 'rsquo' => 8217, + 'apos' => 39, 'ograve' => 242, 'sbquo' => 8218, + 'lt' => 60, 'oacute' => 243, 'ldquo' => 8220, + 'gt' => 62, 'ocirc' => 244, 'rdquo' => 8221, + 'nbsp' => 160, 'otilde' => 245, 'bdquo' => 8222, + 'iexcl' => 161, 'ouml' => 246, 'dagger' => 8224, + 'cent' => 162, 'divide' => 247, 'Dagger' => 8225, + 'pound' => 163, 'oslash' => 248, 'bull' => 8226, + 'curren' => 164, 'ugrave' => 249, 'hellip' => 8230, + 'yen' => 165, 'uacute' => 250, 'permil' => 8240, + 'brvbar' => 166, 'ucirc' => 251, 'prime' => 8242, + 'sect' => 167, 'uuml' => 252, 'Prime' => 8243, + 'uml' => 168, 'yacute' => 253, 'lsaquo' => 8249, + 'copy' => 169, 'thorn' => 254, 'rsaquo' => 8250, + 'ordf' => 170, 'yuml' => 255, 'oline' => 8254, + 'laquo' => 171, 'OElig' => 338, 'frasl' => 8260, + 'not' => 172, 'oelig' => 339, 'euro' => 8364, + 'shy' => 173, 'Scaron' => 352, 'image' => 8465, + 'reg' => 174, 'scaron' => 353, 'weierp' => 8472, + 'macr' => 175, 'Yuml' => 376, 'real' => 8476, + 'deg' => 176, 'fnof' => 402, 'trade' => 8482, + 'plusmn' => 177, 'circ' => 710, 'alefsym'=> 8501, + 'sup2' => 178, 'tilde' => 732, 'larr' => 8592, + 'sup3' => 179, 'Alpha' => 913, 'uarr' => 8593, + 'acute' => 180, 'Beta' => 914, 'rarr' => 8594, + 'micro' => 181, 'Gamma' => 915, 'darr' => 8595, + 'para' => 182, 'Delta' => 916, 'harr' => 8596, + 'middot' => 183, 'Epsilon'=> 917, 'crarr' => 8629, + 'cedil' => 184, 'Zeta' => 918, 'lArr' => 8656, + 'sup1' => 185, 'Eta' => 919, 'uArr' => 8657, + 'ordm' => 186, 'Theta' => 920, 'rArr' => 8658, + 'raquo' => 187, 'Iota' => 921, 'dArr' => 8659, + 'frac14' => 188, 'Kappa' => 922, 'hArr' => 8660, + 'frac12' => 189, 'Lambda' => 923, 'forall' => 8704, + 'frac34' => 190, 'Mu' => 924, 'part' => 8706, + 'iquest' => 191, 'Nu' => 925, 'exist' => 8707, + 'Agrave' => 192, 'Xi' => 926, 'empty' => 8709, + 'Aacute' => 193, 'Omicron'=> 927, 'nabla' => 8711, + 'Acirc' => 194, 'Pi' => 928, 'isin' => 8712, + 'Atilde' => 195, 'Rho' => 929, 'notin' => 8713, + 'Auml' => 196, 'Sigma' => 931, 'ni' => 8715, + 'Aring' => 197, 'Tau' => 932, 'prod' => 8719, + 'AElig' => 198, 'Upsilon'=> 933, 'sum' => 8721, + 'Ccedil' => 199, 'Phi' => 934, 'minus' => 8722, + 'Egrave' => 200, 'Chi' => 935, 'lowast' => 8727, + 'Eacute' => 201, 'Psi' => 936, 'radic' => 8730, + 'Ecirc' => 202, 'Omega' => 937, 'prop' => 8733, + 'Euml' => 203, 'alpha' => 945, 'infin' => 8734, + 'Igrave' => 204, 'beta' => 946, 'ang' => 8736, + 'Iacute' => 205, 'gamma' => 947, 'and' => 8743, + 'Icirc' => 206, 'delta' => 948, 'or' => 8744, + 'Iuml' => 207, 'epsilon'=> 949, 'cap' => 8745, + 'ETH' => 208, 'zeta' => 950, 'cup' => 8746, + 'Ntilde' => 209, 'eta' => 951, 'int' => 8747, + 'Ograve' => 210, 'theta' => 952, 'there4' => 8756, + 'Oacute' => 211, 'iota' => 953, 'sim' => 8764, + 'Ocirc' => 212, 'kappa' => 954, 'cong' => 8773, + 'Otilde' => 213, 'lambda' => 955, 'asymp' => 8776, + 'Ouml' => 214, 'mu' => 956, 'ne' => 8800, + 'times' => 215, 'nu' => 957, 'equiv' => 8801, + 'Oslash' => 216, 'xi' => 958, 'le' => 8804, + 'Ugrave' => 217, 'omicron'=> 959, 'ge' => 8805, + 'Uacute' => 218, 'pi' => 960, 'sub' => 8834, + 'Ucirc' => 219, 'rho' => 961, 'sup' => 8835, + 'Uuml' => 220, 'sigmaf' => 962, 'nsub' => 8836, + 'Yacute' => 221, 'sigma' => 963, 'sube' => 8838, + 'THORN' => 222, 'tau' => 964, 'supe' => 8839, + 'szlig' => 223, 'upsilon'=> 965, 'oplus' => 8853, + 'agrave' => 224, 'phi' => 966, 'otimes' => 8855, + 'aacute' => 225, 'chi' => 967, 'perp' => 8869, + 'acirc' => 226, 'psi' => 968, 'sdot' => 8901, + 'atilde' => 227, 'omega' => 969, 'lceil' => 8968, + 'auml' => 228, 'thetasym'=>977, 'rceil' => 8969, + 'aring' => 229, 'upsih' => 978, 'lfloor' => 8970, + 'aelig' => 230, 'piv' => 982, 'rfloor' => 8971, + 'ccedil' => 231, 'ensp' => 8194, 'lang' => 9001, + 'egrave' => 232, 'emsp' => 8195, 'rang' => 9002, + 'eacute' => 233, 'thinsp' => 8201, 'loz' => 9674, + 'ecirc' => 234, 'zwnj' => 8204, 'spades' => 9824, + 'euml' => 235, 'zwj' => 8205, 'clubs' => 9827, + 'igrave' => 236, 'lrm' => 8206, 'hearts' => 9829, + 'iacute' => 237, 'rlm' => 8207, 'diams' => 9830, + 'icirc' => 238, 'ndash' => 8211, + 'iuml' => 239, 'mdash' => 8212, +); +my %entityName; # look up entity names by number (built as necessary) + +# HTML info +# (tag ID's are case insensitive and must be all lower case in tables) +%Image::ExifTool::HTML::Main = ( + GROUPS => { 2 => 'Document' }, + NOTES => q{ + Meta information extracted from the header of HTML and XHTML files. This is + a mix of information found in the C<META> elements, C<XML> element, and the + C<TITLE> element. + }, + dc => { + Name => 'DC', + SubDirectory => { TagTable => 'Image::ExifTool::HTML::dc' }, + }, + ncc => { + Name => 'NCC', + SubDirectory => { TagTable => 'Image::ExifTool::HTML::ncc' }, + }, + prod => { + Name => 'Prod', + SubDirectory => { TagTable => 'Image::ExifTool::HTML::prod' }, + }, + vw96 => { + Name => 'VW96', + SubDirectory => { TagTable => 'Image::ExifTool::HTML::vw96' }, + }, + 'http-equiv' => { + Name => 'HTTP-equiv', + SubDirectory => { TagTable => 'Image::ExifTool::HTML::equiv' }, + }, + o => { + Name => 'Office', + SubDirectory => { TagTable => 'Image::ExifTool::HTML::Office' }, + }, + abstract => { }, + author => { }, + classification => { }, + 'content-language'=>{ Name => 'ContentLanguage' }, + copyright => { }, + description => { }, + distribution => { }, + 'doc-class' => { Name => 'DocClass' }, + 'doc-rights' => { Name => 'DocRights' }, + 'doc-type' => { Name => 'DocType' }, + formatter => { }, + generator => { }, + generatorversion=> { Name => 'GeneratorVersion' }, + googlebot => { Name => 'GoogleBot' }, + keywords => { List => 1 }, + mssmarttagspreventparsing => { Name => 'NoMSSmartTags' }, + originator => { }, + owner => { }, + progid => { Name => 'ProgID' }, + rating => { }, + refresh => { }, + 'resource-type' => { Name => 'ResourceType' }, + 'revisit-after' => { Name => 'RevisitAfter' }, + robots => { List => 1 }, + title => { Notes => "the only extracted tag which isn't from an HTML META element" }, +); + +# ref 2 +%Image::ExifTool::HTML::dc = ( + GROUPS => { 1 => 'HTML-dc', 2 => 'Document' }, + NOTES => 'Dublin Core schema tags (also used in XMP).', + contributor => { Groups => { 2 => 'Author' }, List => 'Bag' }, + coverage => { }, + creator => { Groups => { 2 => 'Author' }, List => 'Seq' }, + date => { + Groups => { 2 => 'Time' }, + List => 'Seq', + PrintConv => '$self->ConvertDateTime($val)', + }, + description => { }, + 'format' => { }, + identifier => { }, + language => { List => 'Bag' }, + publisher => { Groups => { 2 => 'Author' }, List => 'Bag' }, + relation => { List => 'Bag' }, + rights => { Groups => { 2 => 'Author' } }, + source => { Groups => { 2 => 'Author' } }, + subject => { List => 'Bag' }, + title => { }, + type => { List => 'Bag' }, +); + +# ref 2 +%Image::ExifTool::HTML::ncc = ( + GROUPS => { 1 => 'HTML-ncc', 2 => 'Document' }, + charset => { Name => 'CharacterSet' }, # name changed to avoid conflict with -charset option + depth => { }, + files => { }, + footnotes => { }, + generator => { }, + kbytesize => { Name => 'KByteSize' }, + maxpagenormal => { Name => 'MaxPageNormal' }, + multimediatype => { Name => 'MultimediaType' }, + narrator => { }, + pagefront => { Name => 'PageFront' }, + pagenormal => { Name => 'PageNormal' }, + pagespecial => { Name => 'PageSpecial' }, + prodnotes => { Name => 'ProdNotes' }, + producer => { }, + produceddate => { Name => 'ProducedDate', Groups => { 2 => 'Time' } }, # YYYY-mm-dd + revision => { }, + revisiondate => { Name => 'RevisionDate', Groups => { 2 => 'Time' } }, + setinfo => { Name => 'SetInfo' }, + sidebars => { }, + sourcedate => { Name => 'SourceDate', Groups => { 2 => 'Time' } }, + sourceedition => { Name => 'SourceEdition' }, + sourcepublisher => { Name => 'SourcePublisher' }, + sourcerights => { Name => 'SourceRights' }, + sourcetitle => { Name => 'SourceTitle' }, + tocitems => { Name => 'TOCItems' }, + totaltime => { Name => 'Duration' }, # HH:MM:SS +); + +# ref 3 +%Image::ExifTool::HTML::vw96 = ( + GROUPS => { 1 => 'HTML-vw96', 2 => 'Document' }, + objecttype => { Name => 'ObjectType' }, +); + +# ref 2 +%Image::ExifTool::HTML::prod = ( + GROUPS => { 1 => 'HTML-prod', 2 => 'Document' }, + reclocation => { Name => 'RecLocation' }, + recengineer => { Name => 'RecEngineer' }, +); + +# ref 3/4 +%Image::ExifTool::HTML::equiv = ( + GROUPS => { 1 => 'HTTP-equiv', 2 => 'Document' }, + NOTES => 'These tags have a family 1 group name of "HTTP-equiv".', + 'cache-control' => { Name => 'CacheControl' }, + 'content-disposition' => { Name => 'ContentDisposition' }, + 'content-language' => { Name => 'ContentLanguage' }, + 'content-script-type' => { Name => 'ContentScriptType' }, + 'content-style-type' => { Name => 'ContentStyleType' }, + # note: setting the HTMLCharset like this will miss any tags which come earlier + 'content-type' => { Name => 'ContentType', RawConv => \&SetHTMLCharset }, + 'default-style' => { Name => 'DefaultStyle' }, + expires => { }, + 'ext-cache' => { Name => 'ExtCache' }, + imagetoolbar => { Name => 'ImageToolbar' }, + lotus => { }, + 'page-enter' => { Name => 'PageEnter' }, + 'page-exit' => { Name => 'PageExit' }, + 'pics-label' => { Name => 'PicsLabel' }, + pragma => { }, + refresh => { }, + 'reply-to' => { Name => 'ReplyTo' }, + 'set-cookie' => { Name => 'SetCookie' }, + 'site-enter' => { Name => 'SiteEnter' }, + 'site-exit' => { Name => 'SiteExit' }, + vary => { }, + 'window-target' => { Name => 'WindowTarget' }, +); + +# MS Office namespace (ref PH) +%Image::ExifTool::HTML::Office = ( + GROUPS => { 1 => 'HTML-office', 2 => 'Document' }, + NOTES => 'Tags written by Microsoft Office applications.', + Subject => { }, + Author => { Groups => { 2 => 'Author' } }, + Keywords => { }, + Description => { }, + Template => { }, + LastAuthor => { Groups => { 2 => 'Author' } }, + Revision => { Name => 'RevisionNumber' }, + TotalTime => { Name => 'TotalEditTime', PrintConv => 'ConvertTimeSpan($val, 60)' }, + Created => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + LastSaved => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + LastSaved => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + LastPrinted => { + Name => 'LastPrinted', + Groups => { 2 => 'Time' }, + ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + Pages => { }, + Words => { }, + Characters => { }, + Category => { }, + Manager => { }, + Company => { }, + Lines => { }, + Paragraphs => { }, + CharactersWithSpaces => { }, + Version => { Name => 'RevisionNumber' }, +); + +#------------------------------------------------------------------------------ +# Set HTMLCharset member based on content type +# Inputs: 0) content type string, 1) ExifTool ref +# Returns: original string +sub SetHTMLCharset($$) +{ + my ($val, $et) = @_; + $$et{HTMLCharset} = $htmlCharset{lc $1} if $val =~ /charset=['"]?([-\w]+)/; + return $val; +} + +#------------------------------------------------------------------------------ +# Convert single UTF-8 character to HTML character reference +# Inputs: 0) UTF-8 character sequence +# Returns: HTML character reference (eg. "&quot;"); +# Note: Must be called via EscapeHTML to load name lookup +sub EscapeChar($) +{ + my $ch = shift; + my $val; + if ($] < 5.006001) { + ($val) = Image::ExifTool::UnpackUTF8($ch); + } else { + # the meaning of "U0" is reversed as of Perl 5.10.0! + ($val) = unpack($] < 5.010000 ? 'U0U' : 'C0U', $ch); + } + return '?' unless defined $val; + return "&$entityName{$val};" if $entityName{$val}; + return sprintf('&#x%x;',$val); +} + +#------------------------------------------------------------------------------ +# Escape any special characters for HTML +# Inputs: 0) string to be escaped, 1) optional string encoding (default 'UTF8') +# Returns: escaped string +sub EscapeHTML($;$) +{ + my ($str, $enc) = @_; + # escape XML characters + $str = EscapeXML($str); + # escape other special characters if they exist + if ($str =~ /[\x80-\xff]/) { + # generate entity name lookup if necessary + unless (%entityName) { + local $_; + foreach (keys %entityNum) { + $entityName{$entityNum{$_}} = $_; + } + delete $entityName{39}; # 'apos' is not valid HTML + } + # suppress warnings + local $SIG{'__WARN__'} = sub { 1 }; + if ($enc and $enc ne 'UTF8') { + $str = Image::ExifTool::Decode(undef, $str, $enc, undef, 'UTF8'); + $str =~ s/([\xc2-\xf7][\x80-\xbf]+)/EscapeChar($1)/sge; + $str = Image::ExifTool::Decode(undef, $str, 'UTF8', undef, $enc); + } else { + # escape any non-ascii characters for HTML + $str =~ s/([\xc2-\xf7][\x80-\xbf]+)/EscapeChar($1)/sge; + } + } + return $str; +} + +#------------------------------------------------------------------------------ +# Unescape all HTML character references +# Inputs: 0) string to be unescaped, 1) optional string encoding (default 'UTF8') +# Returns: unescaped string +sub UnescapeHTML($;$) +{ + my ($str, $enc) = @_; + return UnescapeXML($str, \%entityNum, $enc); +} + +#------------------------------------------------------------------------------ +# Extract information from a HTML file +# Inputs: 0) ExifTool object reference, 1) DirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid HTML file +sub ProcessHTML($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + + # validate HTML or XHTML file + $raf->Read($buff, 256) or return 0; + $buff =~ /^(\xef\xbb\xbf)?\s*<(!DOCTYPE\s+HTML|HTML|\?xml)/i or return 0; + $buff =~ /<(!DOCTYPE\s+)?HTML/i or return 0 if $2 eq '?xml'; + $et->SetFileType(); + + $raf->Seek(0,0) or $et->Warn('Seek error'), return 1; + + local $/ = Image::ExifTool::PostScript::GetInputRecordSeparator($raf); + $/ or $et->Warn('Invalid HTML data'), return 1; + + # extract header information + my $doc; + while ($raf->ReadLine($buff)) { + if (not defined $doc) { + # look for 'head' element + next unless $buff =~ /<head\b/ig; + $doc = substr($buff, pos($buff)); + next; + } + $doc .= $buff; + last if $buff =~ m{</head>}i; + } + return 1 unless defined $doc; + + # process all elements in header + my $tagTablePtr = GetTagTable('Image::ExifTool::HTML::Main'); + for (;;) { + last unless $doc =~ m{<([\w:.-]+)(.*?)>}sg; + my ($tagName, $attrs) = ($1, $2); + my $tag = lc($tagName); + my ($val, $grp); + if ($attrs =~ m{/$}) { # self-contained XHTML tags end in '/>' + $val = ''; + } else { + # look for element close + my $pos = pos($doc); + my $close = "</$tagName>"; + # the following doesn't work on Solaris Perl 5.6.1 due to Perl bug: + # if ($doc =~ m{(.*?)</$tagName>}sg) { + # $val = $1; + if ($doc =~ m{$close}sg) { + $val = substr($doc, $pos, pos($doc)-$pos-length($close)); + } else { + pos($doc) = $pos; + next unless $tag eq 'meta'; # META tags don't need to be closed + $val = ''; + } + } + my $table = $tagTablePtr; + if ($tag eq 'meta') { + # parse HTML META element + undef $tag; + # tag name is in NAME or HTTP-EQUIV attribute + if ($attrs =~ /\bname\s*=\s*['"]?([\w:.-]+)/si) { + $tagName = $1; + } elsif ($attrs =~ /\bhttp-equiv\s*=\s*['"]?([\w:.-]+)/si) { + $tagName = "HTTP-equiv.$1"; + } else { + next; # no name + } + $tag = lc($tagName) or next; + # tag value is in CONTENT attribute + if ($attrs =~ /\bcontent\s*=\s*(['"])(.*?)\1/si or + $attrs =~ /\bcontent\s*=\s*(['"]?)([\w:.-]+)/si) + { + $val = $2; + } else { + next unless length $val; + } + # isolate group name (separator is '.' in HTML, but ':' in ref 2) + if ($tag =~ /^([\w-]+)[:.]([\w-]+)/) { + ($grp, $tag) = ($1, $2); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $grp); + if ($tagInfo and $$tagInfo{SubDirectory}) { + $table = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + } else { + $tag = "$grp.$tag"; + } + } + } elsif ($tag eq 'xml') { + $et->VPrint(0, "Parsing XML\n"); + # parse XML tags (quick-and-dirty) + my $xml = $val; + while ($xml =~ /<([\w-]+):([\w-]+)(\s.*?)?>([^<]*?)<\/\1:\2>/g) { + ($grp, $tag, $val) = ($1, $2, $4); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $grp); + next unless $tagInfo and $$tagInfo{SubDirectory}; + $table = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + unless ($$table{$tag}) { + my $name = ucfirst $tag; + $name =~ s/_x([0-9a-f]{4})_/chr(hex($1))/gie; # convert hex codes + $name =~ s/\s(.)/\U$1/g; # capitalize all words in tag name + $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters (also hex code wide chars) + AddTagToTable($table, $tag, { Name => $name }); + $et->VPrint(0, " [adding $tag '${name}']\n"); + } + $val = $et->Decode($val, $$et{HTMLCharset}) if $$et{HTMLCharset}; + $et->HandleTag($table, $tag, UnescapeXML($val)); + } + next; + } else { + # the only other element we process is TITLE + next unless $tag eq 'title'; + } + unless ($$table{$tag}) { + my $name = $tagName; + $name =~ s/\W+(\w)/\u$1/sg; + my $info = { Name => $name, Groups => { 0 => 'HTML' } }; + $info->{Groups}->{1} = ($grp eq 'http-equiv' ? 'HTTP-equiv' : "HTML-$grp") if $grp; + AddTagToTable($table, $tag, $info); + $et->VPrint(0, " [adding $tag '${tagName}']\n"); + } + # recode if necessary + $val = $et->Decode($val, $$et{HTMLCharset}) if $$et{HTMLCharset}; + $val =~ s{\s*$/\s*}{ }sg; # replace linefeeds and indenting spaces + $val = UnescapeHTML($val); # unescape HTML character references + $et->HandleTag($table, $tag, $val); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::HTML - Read HTML meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +meta information from HTML documents. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.w3.org/TR/html4/> + +=item L<http://www.daisy.org/publications/specifications/daisy_202.html> + +=item L<http://vancouver-webpages.com/META/metatags.detail.html> + +=item L<http://www.html-reference.com/META.htm> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/HTML Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/HtmlDump.pm b/ExifTool/lib/Image/ExifTool/HtmlDump.pm new file mode 100644 index 0000000..1a250df --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/HtmlDump.pm @@ -0,0 +1,930 @@ +#------------------------------------------------------------------------------ +# File: HtmlDump.pm +# +# Description: Dump information in hex to HTML page +# +# Revisions: 12/05/2005 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::HtmlDump; + +use strict; +use vars qw($VERSION); +use Image::ExifTool; # only for FinishTiffDump() +use Image::ExifTool::HTML qw(EscapeHTML); + +$VERSION = '1.39'; + +sub DumpTable($$$;$$$$$$); +sub Open($$$;@); +sub Write($@); + +my ($bkgStart, $bkgEnd, @bkgSpan); + +my $htmlHeader1 = <<_END_PART_1_; +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" + "http://www.w3.org/TR/1998/REC-html40-19980424/loose.dtd"> +<html> +<head> +<title> +_END_PART_1_ + +# Note: Don't change font-weight style because it can affect line height +my $htmlHeader2 = <<_END_PART_2_; +</title> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<style type="text/css"> +<!-- +/* character style ID's */ +.D { color: #000000 } /* default color */ +.V { color: #ff0000 } /* duplicate block 1 */ +.W { color: #004400 } /* normal block 1 */ +.X { color: #ff4488 } /* duplicate block 2 */ +.Y { color: #448844 } /* normal block 2 */ +.U { color: #cc8844 } /* unused data block */ +.H { color: #0000ff } /* highlighted tag name */ +.F { color: #aa00dd } /* actual offset differs */ +.M { text-decoration: underline } /* maker notes data */ +.tt { /* tooltip text */ + visibility: hidden; + position: absolute; + white-space: nowrap; + top: 0; + left: 0; + font-family: Verdana, sans-serif; + font-size: .7em; + padding: 2px 4px; + border: 1px solid gray; + z-index: 3; +} +.tb { /* tooltip background */ + visibility: hidden; + position: absolute; + background: #ffffdd; + zoom: 1; + -moz-opacity: 0.8; + -khtml-opacity: 0.8; + -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; + filter: alpha(opacity=80); + opacity: 0.8; + z-index: 2; +} +/* table styles */ +table.dump { + border-top: 1px solid gray; + border-bottom: 1px solid gray; +} +table.dump td { padding: .2em .3em } +td.c2 { + border-left: 1px solid gray; + border-right: 1px solid gray; +} +pre { margin: 0 } +table { font-size: .9em } +body { color: black; background: white } +--> +</style> +<script language="JavaScript" type="text/JavaScript"> +<!-- Begin +// tooltip positioning constants +var TMAR = 4; // top/left margins +var BMAR = 16; // bottom/right margins (scrollbars may overhang inner dimensions) +var XOFF = 10; // x offset from cursor +var YOFF = 40; // y offset +var YMIN = 10; // minimum y offset +var YTOP = 20; // y offset when above cursor +// common variables +var safari1 = navigator.userAgent.indexOf("Safari/312.6") >= 0; +var ie6 = navigator.userAgent.toLowerCase().indexOf('msie 6') >= 0; +var mspan = new Array; +var clicked = 0; +var hlist, tt, tb, firstOutEvt, lastInEvt; + +function GetElementsByClass(classname, tagname) { + var found = new Array(); + var list = document.getElementsByTagName(tagname); + var len = list.length; + for (var i=0, j=0; i<len; ++i) { + var classes = list[i].className.split(' '); + for (var k=0; k<classes.length; ++k) { + if (classes[k] == classname) { + found[j++] = list[i]; + break; + } + } + } + return found; +} + +// click mouse +function doClick(e) +{ + if (!clicked) { + firstOutEvt = lastInEvt = undefined; + high(e, 2); + if (hlist) clicked = 1; + } else { + clicked = 0; + if (firstOutEvt) high(firstOutEvt, 0); + if (lastInEvt) high(lastInEvt, 1); + } +} + +// move tooltip +function move(e) +{ + if (!tt) return; + if (ie6 && (tt.style.top == '' || tt.style.top == 0) && + (tt.style.left == '' || tt.style.left == 0)) + { + tt.style.width = tt.offsetWidth + 'px'; + tt.style.height = tt.offsetHeight + 'px'; + } + var w, h; + // browser inconsistencies make getting window size more complex than it should be, + // and even then we don't know if it is smaller due to scrollbar width + if (typeof(window.innerWidth) == 'number') { + w = window.innerWidth; + h = window.innerHeight; + } else if (document.documentElement && document.documentElement.clientWidth) { + w = document.documentElement.clientWidth; + h = document.documentElement.clientHeight; + } else { + w = document.body.clientWidth; + h = document.body.clientHeight; + } + var x = e.clientX + XOFF; + var y = e.clientY + YOFF; + if (safari1) { // patch for people still using OS X 10.3.9 + x -= document.body.scrollLeft + document.documentElement.scrollLeft; + y -= document.body.scrollTop + document.documentElement.scrollTop; + } + var mx = w - BMAR - tt.offsetWidth; + var my = h - BMAR - tt.offsetHeight; + if (y > my + YOFF - YMIN) y = e.clientY - YTOP - tt.offsetHeight; + if (x > mx) x = mx; + if (y > my) y = my; + if (x < TMAR) x = TMAR; + if (y < TMAR) y = TMAR; + x += document.body.scrollLeft + document.documentElement.scrollLeft; + y += document.body.scrollTop + document.documentElement.scrollTop; + tb.style.width = tt.offsetWidth + 'px'; + tb.style.height = tt.offsetHeight + 'px'; + tt.style.top = tb.style.top = y + 'px'; + tt.style.left = tb.style.left = x + 'px'; + tt.style.visibility = tb.style.visibility = 'visible'; +} + +// highlight/unhighlight text +function high(e,on) { + if (on) { + lastInEvt = e; + } else { + if (!firstOutEvt) firstOutEvt = e; + } + if (clicked) return; + var targ; + if (e.target) targ = e.target; + else if (e.srcElement) targ = e.srcElement; + if (targ.nodeType == 3) targ = targ.parentNode; // defeat Safari bug + if (!targ.name) targ = targ.parentNode; // go up another level if necessary + if (targ.name && document.getElementsByName) { + // un-highlight current objects + if (hlist) { + for (var i=0; i<hlist.length; ++i) { + for (var j=0; j<hlist[i].length; ++j) { + hlist[i][j].style.background = 'transparent'; + } + } + hlist = null; + } + if (tt) { + // hide old tooltip + tt.style.visibility = tb.style.visibility = 'hidden'; + tt = null; + } + if (on) { + if (targ.name.substring(0,1) == 't') { + // show our tooltip (ID is different than name to avoid confusing IE) + tt = document.getElementById('p' + targ.name.substring(1)); + if (tt) { + tb = document.getElementById('tb'); + move(e); + } + } + // highlight anchor elements with the same name + hlist = new Array; + hlist.push(document.getElementsByName(targ.name)); + // is this an IFD pointer? + var pos = targ.className.indexOf('Offset_'); + if (pos > 0) { + // add elements from this IFD to our highlight list + hlist.push(document.getElementsByClassName(targ.className.substr(pos+7))); + } + // use class name to highlight span elements if necessary + for (var i=0; i<mspan.length; ++i) { + if (mspan[i] != targ.name) continue; + // add these span elements to our highlight list + hlist.push(GetElementsByClass(targ.name, 'span')); + break; + } + for (var i=0; i<hlist.length; ++i) { + for (var j=0; j<hlist[i].length; ++j) { + hlist[i][j].style.background = on == 2 ? '#ffbbbb' : '#ffcc99'; + } + } + } + } +} +_END_PART_2_ + +my $htmlHeader3 = q[ +// End ---> +</script></head> +<body><noscript><b class=V>--&gt; +Enable JavaScript for active highlighting and information tool tips! +</b></noscript> +<table class=dump cellspacing=0 cellpadding=2> +<tr><td valign='top'><pre>]; + +my $preMouse = q(<pre onmouseover="high(event,1)" onmouseout="high(event,0)" onmousemove="move(event)" onmousedown="doClick(event)">); + +#------------------------------------------------------------------------------ +# New - create new HtmlDump object +# Inputs: 0) reference to HtmlDump object or HtmlDump class name +sub new +{ + local $_; + my $that = shift; + my $class = ref($that) || $that || 'Image::ExifTool::HtmlDump'; + return bless { Block => {}, TipNum => 0 }, $class; +} + +#------------------------------------------------------------------------------ +# Add information to dump +# Inputs: 0) HTML dump hash ref, 1) absolute offset in file, 2) data size, +# 3) comment string, 4) tool tip (or SAME to use previous tip), +# 5) bit flags (see below), 6) IFD name +# Bits: 0x01 - print at start of line +# 0x02 - print red address +# 0x04 - maker notes data ('M'-class span) +# 0x08 - limit block length +# 0x10 - allow double references +# 0x100 - (reserved) +# Notes: Block will be shown in 'unused' color if comment string begins with '[' +sub Add($$$$;$$) +{ + my ($self, $start, $size, $msg, $tip, $flag, $ifd) = @_; + my $block = $$self{Block}; + $$block{$start} or $$block{$start} = [ ]; + my $htip; + if ($tip and $tip eq 'SAME') { + $htip = ''; + } else { + # use message as first line of tip, and make bold unless in brackets + $htip = ($msg =~ /^[[(]/) ? $msg : "<b>$msg</b>"; + if (defined $tip) { + ($tip = EscapeHTML($tip)) =~ s/\n/<br>/g; # HTML-ize tooltip text + $htip .= '<br>' . $tip; + } + # add size if not already done + $htip .= "<br>($size bytes)" unless $htip =~ /<br>Size:/; + ++$self->{TipNum}; + } + push @{$$block{$start}}, [ $size, $msg, $htip, $flag, $self->{TipNum}, $ifd ]; +} + +#------------------------------------------------------------------------------ +# Print dump information to HTML page +# Inputs: 0) Dump information hash reference, 1) source file RAF reference, +# 2) data pointer, 3) data position, 4) output file or scalar reference, +# 5) limit level (1-3), 6) title +# Returns: non-zero if useful output was generated, +# or -1 on error loading data and "ERROR" is set to offending data name +# Note: The "Error" member may be set externally to print a specific error +# message instead of doing the dump. +sub Print($$;$$$$$) +{ + local $_; + my ($self, $raf, $dataPt, $dataPos, $outfile, $level, $title) = @_; + my ($i, $buff, $rtnVal, $limit, $err); + my $block = $$self{Block}; + $dataPos = 0 unless $dataPos; + $outfile = \*STDOUT unless ref $outfile; + $title = 'HtmlDump' unless $title; + $level or $level = 0; + my $tell = $raf->Tell(); + my $pos = 0; + my $dataEnd = $dataPos + ($dataPt ? length($$dataPt) : 0); + # initialize member variables + $$self{Open} = []; + $$self{Closed} = []; + $$self{TipList} = []; + $$self{MSpanList} = []; + $$self{Cols} = [ '', '', '', '' ]; # text columns + # set dump size limits (limits are 4x smaller if bit 0x08 set in flags) + if ($level <= 1) { + $limit = 1024; + } elsif ($level <= 2) { + $limit = 16384; + } else { + $limit = 256 * 1024 * 1024; # never dump bigger than 256 MB + } + $$self{Limit} = $limit; + # pre-initialize open/closed hashes for all columns + for ($i=0; $i<4; ++$i) { + $self->{Open}->[$i] = { ID => [ ], Element => { } }; + $self->{Closed}->[$i] = { ID => [ ], Element => { } }; + } + $bkgStart = $bkgEnd = 0; + undef @bkgSpan; + my $index = 0; # initialize tooltip index + my (@names, $wasUnused, @starts); + # only do dump if we didn't have a serious error + @starts = sort { $a <=> $b } keys %$block unless $$self{Error}; + for ($i=0; $i<=@starts; ++$i) { + my $start = $starts[$i]; + my $parmList; + if (defined $start) { + $parmList = $$block{$start}; + } elsif ($bkgEnd and $pos < $bkgEnd and not defined $wasUnused) { + $start = $bkgEnd; # finish last bkg block + } else { + last; + } + my $len = $start - $pos; + if ($len > 0 and not $wasUnused) { + # we have a unused bytes before this data block + --$i; # dump the data block next time around + # split unused data into 2 blocks if it spans end of a bkg block + my ($nextBkgEnd, $bkg); + if (not defined $wasUnused and $bkgEnd) { + foreach $bkg (@bkgSpan) { + next if $pos >= $$bkg{End} + $dataPos or $pos + $len <= $$bkg{End} + $dataPos; + $nextBkgEnd = $$bkg{End} unless $nextBkgEnd and $nextBkgEnd < $$bkg{End}; + } + } + if ($nextBkgEnd) { + $start = $pos; + $len = $nextBkgEnd + $dataPos - $pos; + $wasUnused = 0; + } else { + $start = $pos; # dump the unused bytes now + $wasUnused = 1; # avoid re-dumping unused bytes if we get a read error + } + my $str = ($len > 1) ? "unused $len bytes" : 'pad byte'; + $parmList = [ [ $len, "[$str]", undef, 0x108 ] ]; + } else { + undef $wasUnused; + } + my $parms; + foreach $parms (@$parmList) { + my ($len, $msg, $tip, $flag, $tipNum, $ifd) = @$parms; + next unless $len > 0; + $flag = 0 unless defined $flag; + # generate same name for all blocks indexed by this tooltip + my $name; + $name = $names[$tipNum] if defined $tipNum; + my $idx = $index; + if ($name) { + # get index from existing ID + $idx = substr($name, 1); + } else { + $name = "t$index"; + $names[$tipNum] = $name if defined $tipNum; + ++$index; + } + if ($flag & 0x14) { + my $class = $flag & 0x04 ? "$name M" : $name; + $class .= " $ifd" if $ifd; + my %bkg = ( + Class => $class, + Start => $start - $dataPos, + End => $start - $dataPos + $len, + ); + push @bkgSpan, \%bkg; + $bkgStart = $bkg{Start} unless $bkgStart and $bkgStart < $bkg{Start}; + $bkgEnd = $bkg{End} unless $bkgEnd and $bkgEnd > $bkg{End}; + push @{$self->{MSpanList}}, $name; + next; + } + # loop until we read the value properly + my ($end, $try); + for ($try=0; $try<2; ++$try) { + $end = $start + $len; + # only load as much of the block as we are going to dump + # (read 32 more bytes than necessary just in case there + # is only one skipped line that we decide to print) + my $size = ($len > $limit + 32) ? $limit / 2 + 16 : $len; + if ($start >= $dataPos and $end <= $dataEnd) { + $buff = substr($$dataPt, $start-$dataPos, $size); + if ($len != $size) { + $buff .= substr($$dataPt, $start-$dataPos+$len-$size, $size); + } + } else { + $buff = ''; + if ($raf->Seek($start, 0) and $raf->Read($buff, $size) == $size) { + # read end of block + if ($len != $size) { + my $buf2 = ''; + unless ($raf->Seek($start+$len-$size, 0) and + $raf->Read($buf2, $size) == $size) + { + $err = $msg; + # reset $len to the actual length of available data + $raf->Seek(0, 2); + $len = $raf->Tell() - $start; + $tip .= "<br>Error: Only $len bytes available!" if $tip; + next; + } + $buff .= $buf2; + undef $buf2; + } + } else { + $err = $msg; + $len = length $buff; + $tip .= "<br>Error: Only $len bytes available!" if $tip; + } + } + last; + } + $tip and $self->{TipList}->[$idx] = $tip; + next unless length $buff; + # set flag to continue this line if next block is contiguous + if ($i+1 < @starts and $parms eq $$parmList[-1] and + ($end == $starts[$i+1] or ($end < $starts[$i+1] and $end >= $pos))) + { + my $nextFlag = $block->{$starts[$i+1]}->[0]->[3] || 0; + $flag |= 0x100 unless $flag & 0x01 or $nextFlag & 0x01; + } + $self->DumpTable($start-$dataPos, \$buff, $msg, $name, + $flag, $len, $pos-$dataPos, $ifd); + undef $buff; + $pos = $end if $pos < $end; + } + } + $self->Open('',''); # close all open elements + $raf->Seek($tell,0); + + # write output HTML file + Write($outfile, $htmlHeader1, $title); + if ($self->{Cols}->[0]) { + Write($outfile, $htmlHeader2); + my $mspan = \@{$$self{MSpanList}}; + for ($i=0; $i<@$mspan; ++$i) { + Write($outfile, qq(mspan[$i] = "$$mspan[$i]";\n)); + } + Write($outfile, $htmlHeader3, $self->{Cols}->[0]); + Write($outfile, '</pre></td><td valign="top">', + $preMouse, $self->{Cols}->[1]); + Write($outfile, '</pre></td><td class=c2 valign="top">', + $preMouse, $self->{Cols}->[2]); + Write($outfile, '</pre></td><td valign="top">', + $preMouse, $self->{Cols}->[3]); + Write($outfile, "</pre></td></tr></table>\n<div id=tb class=tb> </div>\n"); + my $tips = \@{$$self{TipList}}; + for ($i=0; $i<@$tips; ++$i) { + my $tip = $$tips[$i]; + Write($outfile, "<div id=p$i class=tt>$tip</div>\n") if defined $tip; + } + delete $$self{TipList}; + $rtnVal = 1; + } else { + my $err = $$self{Error} || 'No EXIF or TIFF information found in image'; + Write($outfile, "$title</title></head><body>\n$err\n"); + $rtnVal = 0; + } + Write($outfile, "</body></html>\n"); + for ($i=0; $i<4; ++$i) { + $self->{Cols}->[$i] = ''; # free memory + } + if ($err) { + $err =~ tr/()//d; + $$self{ERROR} = $err; + return -1; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Open or close a specified html element +# Inputs: 0) HtmlDump object ref, 1) element id, 2) element string, +# 3-N) list of column numbers (empty for all columns) +# - element id may be '' to close all elements +# - element string may be '' to close element by ID (or 0 to close without reopening) +# - element id and string may both be 1 to reopen temporarily closed elements +sub Open($$$;@) +{ + my ($self, $id, $element, @colNums) = @_; + + # loop through specified columns + @colNums or @colNums = (0 .. $#{$self->{Open}}); + my $col; + foreach $col (@colNums) { + # get information about open elements in this column + my $opHash = $self->{Open}->[$col]; + my $opElem = $$opHash{Element}; + if ($element) { + # next if already open + next if $$opElem{$id} and $$opElem{$id} eq $element; + } elsif ($id and not $$opElem{$id}) { + # next if already closed and nothing to reopen + next unless $element eq '' and @{$self->{Closed}->[$col]->{ID}}; + } + my $opID = $$opHash{ID}; + my $clHash = $self->{Closed}->[$col]; + my $clID = $$clHash{ID}; + my $clElem = $$clHash{Element}; + # get reference to output column list (use temp list if available) + my $cols = $$self{TmpCols} || $$self{Cols}; + # close everything down to this element if necessary + if ($$opElem{$id} or not $id) { + while (@$opID) { + my $tid = pop @$opID; + my $e = $$opElem{$tid}; + $e =~ s/^<(\S+).*/<\/$1>/s; + $$cols[$col] .= $e; + if ($id eq $tid or not $id) { + delete $$opElem{$tid}; + last if $id; + next; + } + # add this to the temporarily closed list + # (because we really didn't want to close it) + push @$clID, $tid; + $$clElem{$tid} = $$opElem{$tid}; + delete $$opElem{$tid}; + } + unless ($id) { + # forget all temporarily closed elements + $clID = $$clHash{ID} = [ ]; + $clElem = $$clHash{Element} = { }; + } + } elsif ($$clElem{$id}) { + # delete from the list of temporarily closed elements + delete $$clElem{$id}; + @$clID = grep !/^$id$/, @$clID; + } + next if $element eq '0'; # 0 = don't reopen temporarily closed elements + + # re-open temporarily closed elements + while (@$clID) { + my $tid = pop @$clID; + $$cols[$col] .= $$clElem{$tid}; + push @$opID, $tid; + $$opElem{$tid} = $$clElem{$tid}; + delete $$clElem{$tid}; + } + # open specified element + if ($element and $element ne '1') { + $$cols[$col] .= $element; + push @$opID, $id; + $$opElem{$id} = $element; + } + } +} + +#------------------------------------------------------------------------------ +# Dump a block of data in HTML table form +# Inputs: 0) HtmlDump object ref, 1) data position, 2) block pointer, +# 3) message, 4) object name, 5) flag, 6) full block length (actual +# data may be shorter), 7) data end position, 8) IFD name +sub DumpTable($$$;$$$$$$) +{ + my ($self, $pos, $blockPt, $msg, $name, $flag, $len, $endPos, $ifd) = @_; + $len = length $$blockPt unless defined $len; + $endPos = 0 unless $endPos; + my ($f0, $dblRef, $id); + my $skipped = 0; + if (($endPos and $pos < $endPos) or $flag & 0x02) { + # display double-reference addresses in red + $f0 = "<span class=V>"; + $dblRef = 1 if $endPos and $pos < $endPos; + } else { + $f0 = ''; + } + my @c = ('','','',''); + $$self{TmpCols} = \@c; + if ($name) { + if ($msg and $msg =~ /^\[/) { + $id = 'U'; + } else { + if ($$self{A}) { + $id = 'X'; + $$self{A} = 0; + } else { + $id = 'V'; + $$self{A} = 1; + } + ++$id unless $dblRef; + } + my $class = $ifd ? "'$id $ifd'" : $id; + $name = "<a name=$name class=$class>"; + $msg and $msg = "$name$msg</a>"; + } else { + $name = ''; + } + # use base-relative offsets from now on + my $cols = 0; + my $p = $pos; + if ($$self{Cont}) { + $cols = $pos & 0x0f; + $c[1] .= ($cols == 8) ? ' ' : ' '; + } else { + my $addr = $pos < 0 ? sprintf("-%.4x",-$pos) : sprintf("%5.4x",$pos); + $self->Open('fgd', $f0, 0); + $self->Open('fgd', '', 3); + $c[0] .= "$addr"; + $p -= $pos & 0x0f unless $flag & 0x01; + if ($p < $pos) { + $self->Open('bkg', '', 1, 2); # don't underline white space + $cols = $pos - $p; + my $n = 3 * $cols; + ++$n if $cols > 7; + $c[1] .= ' ' x $n; + $c[2] .= ' ' x $cols; + $p = $pos; + } + } + # loop through each column of hex numbers + for (;;) { + my (@spanClass, @spanCont, $spanClose, $bkg); + if ($p >= $bkgStart and $p < $bkgEnd) { + foreach $bkg (@bkgSpan) { + next unless $p >= $$bkg{Start} and $p < $$bkg{End}; + push @spanClass, $$bkg{Class}; + if ($p + 1 == $$bkg{End}) { + $spanClose = 1; + } else { + push @spanCont, $$bkg{Class}; # this span continues + } + } + $self->Open('bkg', @spanClass ? "<span class='@spanClass'>" : '', 1, 2); + } else { + $self->Open('bkg', '', 1, 2); + } + $self->Open('a', $name, 1, 2); + my $ch = substr($$blockPt,$p-$pos-$skipped,1); + $c[1] .= sprintf("%.2x", ord($ch)); + # make the character HTML-friendly + $ch =~ tr/\x00-\x1f\x7f-\xff/./; + $ch =~ s/&/&amp;/g; + $ch =~ s/>/&gt;/g; + $ch =~ s/</&lt;/g; + $c[2] .= $ch; + ++$p; + ++$cols; + # close necessary elements + if ($spanClose) { + my $spanCont = @spanCont ? "<span class='@spanCont'>" : ''; + # close without reopening if closing anchor later + my $arg = ($p - $pos >= $len) ? 0 : $spanCont; + $self->Open('bkg', $arg, 1, 2); + } + if ($dblRef and $p >= $endPos) { + $dblRef = 0; + ++$id; + my $class = $ifd ? "'$id $ifd'" : $id; + $name =~ s/class=\w\b/class=$class/; + $f0 = ''; + $self->Open('fgd', $f0, 0); + } + if ($p - $pos >= $len) { + $self->Open('a', '', 1, 2); # close our anchor + last; + } + if ($cols < 16) { + $c[1] .= ($cols == 8 ? ' ' : ' '); + next; + } elsif ($flag & 0x01 and $cols < $len) { + $c[1] .= ' '; + next; # put it all on one line + } + unless ($$self{Msg}) { + $c[3] .= $msg; + $msg = ''; + } + $_ .= "\n" foreach @c; # add CR to all lines + $$self{Msg} = 0; + # limit data length if specified + if ($$self{Limit}) { + my $div = ($flag & 0x08) ? 4 : 1; + my $lim = $$self{Limit} / (2 * $div) - 16; + if ($p - $pos > $lim and $len - $p + $pos > $lim) { + my $n = ($len - $p + $pos - $lim) & ~0x0f; + if ($n > 16) { # (no use just cutting out one line) + $self->Open('bkg', '', 1, 2); # no underline + my $note = sprintf "[snip %d lines]", $n / 16; + $note = (' ' x (24-length($note)/2)) . $note; + $c[0] .= " ...\n"; + $c[1] .= $note . (' ' x (48-length($note))) . "\n"; + $c[2] .= " [snip] \n"; + $c[3] .= "\n"; + $p += $n; + $skipped += $len - length $$blockPt; + } + } + } + $c[0] .= ($p < 0 ? sprintf("-%.4x",-$p) : sprintf("%5.4x",$p)); + $cols = 0; + } + if ($msg) { + $msg = " $msg" if $$self{Msg}; + $c[3] .= $msg; + } + if ($flag & 0x100 and $cols < 16) { # continue on same line? + $$self{Cont} = 1; + $$self{Msg} = 1 if $msg; + } else { + $_ .= "\n" foreach @c; + $$self{Msg} = 0; + $$self{Cont} = 0; + } + # add temporary column data to our real columns + my $i; + for ($i=0; $i<4; ++$i) { + $self->{Cols}->[$i] .= $c[$i]; + } + delete $$self{TmpCols}; +} + +#------------------------------------------------------------------------------ +# Finish dumping of TIFF image data +# Inputs: 0) HtmlDump object ref, 1) ExifTool object ref, 2) length of file +# (this really belongs in Image::ExifTool::Exif, but is placed here so it +# is only compiled when needed) +sub FinishTiffDump($$$) +{ + my ($self, $et, $size) = @_; + my ($tag, $key, $start, $blockInfo, $i); + + # list of all indirectly referenced TIFF data tags + my %offsetPair = ( + StripOffsets => 'StripByteCounts', + TileOffsets => 'TileByteCounts', + FreeOffsets => 'FreeByteCounts', + ThumbnailOffset => 'ThumbnailLength', + PreviewImageStart => 'PreviewImageLength', + JpgFromRawStart => 'JpgFromRawLength', + OtherImageStart => 'OtherImageLength', + ImageOffset => 'ImageByteCount', + AlphaOffset => 'AlphaByteCount', + MPImageStart => 'MPImageLength', + IDCPreviewStart => 'IDCPreviewLength', + SamsungRawPointersOffset => 'SamsungRawPointersLength', + ); + + # add TIFF data to html dump + foreach $tag (keys %offsetPair) { + my $info = $et->GetInfo($tag); + next unless %$info; + # Panasonic hack: StripOffsets is not valid for Panasonic RW2 files, + # and StripRowBytes is not valid for some RAW images + if ($tag eq 'StripOffsets' and $$et{TAG_INFO}{$tag}{PanasonicHack}) { + # use RawDataOffset instead if available since it is valid in RW2 + my $info2 = $et->GetInfo('RawDataOffset'); + $info2 = $info unless %$info2; + my @keys = keys %$info2; + my $offset = $$info2{$keys[0]}; + my $raf = $$et{RAF}; + # ignore StripByteCounts and assume raw data runs to the end of file + if (@keys == 1 and $offset =~ /^\d+$/ and $raf) { + my $pos = $raf->Tell(); + $raf->Seek(0, 2); # seek to end + my $len = $raf->Tell() - $offset; + $raf->Seek($pos, 0); + if ($len > 0) { + $self->Add($offset, $len, "(Panasonic raw data)", "Size: $len bytes", 0x08); + next; + } + } + } + # loop through all offsets tags + foreach $key (keys %$info) { + my $name = Image::ExifTool::GetTagName($key); + my $grp1 = $et->GetGroup($key, 1); + my $info2 = $et->GetInfo($offsetPair{$tag}, { Group1 => $grp1 }); + my $key2 = $offsetPair{$tag}; + $key2 .= $1 if $key =~ /( .*)/; # use same instance number as $tag + next unless $$info2{$key2}; + my $offsets = $$info{$key}; + my $byteCounts = $$info2{$key2}; + # ignore primary MPImage (this is the whole JPEG) + next if $tag eq 'MPImageStart' and $offsets eq '0'; + # (long lists may be SCALAR references) + my @offsets = split ' ', (ref $offsets ? $$offsets : $offsets); + my @byteCounts = split ' ', (ref $byteCounts ? $$byteCounts : $byteCounts); + my $num = scalar @offsets; + my $li = 0; + my $padBytes = 0; + for ($i=0; @offsets and @byteCounts; ++$i) { + my $offset = shift @offsets; + my $byteCount = shift @byteCounts; + my $end = $offset + $byteCount; + if (@offsets and @byteCounts) { + # show data as contiguous if only normal pad bytes between blocks + if ($end & 0x01 and $end + 1 == $offsets[0]) { + $end += 1; + ++$padBytes; # count them + } + if ($end == $offsets[0]) { + # combine these two blocks + $byteCounts[0] += $offsets[0] - $offset; + $offsets[0] = $offset; + next; + } + } + my $msg = $et->GetGroup($key, 1) . ':' . $tag; + $msg =~ s/(Offsets?|Start)$/ /; + if ($num > 1) { + $msg .= "$li-" if $li != $i; + $msg .= "$i "; + $li = $i + 1; + } + $msg .= "data"; + my $tip = "Size: $byteCount bytes"; + $tip .= ", incl. $padBytes pad bytes" if $padBytes; + $self->Add($offset, $byteCount, "($msg)", $tip, 0x08); + } + } + } + # find offset of last dumped information, and dump any unknown trailer + my $last = 0; + my $block = $$self{Block}; + foreach $start (keys %$block) { + foreach $blockInfo (@{$$block{$start}}) { + my $end = $start + $$blockInfo[0]; + $last = $end if $last < $end; + } + } + my $diff = $size - $last; + if ($diff > 0 and ($last or $et->Options('Unknown'))) { + if ($diff > 1 or $size & 0x01) { + $self->Add($last, $diff, "[unknown data]", "Size: $diff bytes", 0x08); + } else { + $self->Add($last, $diff, "[trailing pad byte]", undef, 0x08); + } + } +} + +#------------------------------------------------------------------------------ +# utility routine to write to file or memory +# Inputs: 0) file or scalar reference, 1-N) list of stuff to write +# Returns: true on success +sub Write($@) +{ + my $outfile = shift; + if (UNIVERSAL::isa($outfile,'GLOB')) { + return print $outfile @_; + } elsif (ref $outfile eq 'SCALAR') { + $$outfile .= join('', @_); + return 1; + } + return 0; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::HtmlDump - Dump information in hex to HTML page + +=head1 SYNOPSIS + + use Image::ExifTool::HtmlDump; + my $dump = new Image::ExifTool::HtmlDump; + $dump->Add($start, $size, $comment); + $dump->Print($dumpInfo, $raf, $dataPt, $dataPos, $outfile); + +=head1 DESCRIPTION + +This module contains code used to generate an HTML-based hex dump of +information for debugging purposes. This is code is called when the +ExifTool 'HtmlDump' option is used. + +Currently, only EXIF/TIFF and JPEG information is dumped. + +=head1 BUGS + +Due to a memory allocation bug in ActivePerl 5.8.x for Windows, this code +may run extremely slowly when processing large files with this version of +Perl. + +An HTML 4 compliant browser is needed to properly display the generated HTML +page. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/ICC_Profile.pm b/ExifTool/lib/Image/ExifTool/ICC_Profile.pm new file mode 100644 index 0000000..c63b110 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/ICC_Profile.pm @@ -0,0 +1,1382 @@ +#------------------------------------------------------------------------------ +# File: ICC_Profile.pm +# +# Description: Read ICC Profile meta information +# +# Revisions: 11/16/2004 - P. Harvey Created +# +# References: 1) http://www.color.org/icc_specs2.html (ICC.1:2003-09) +# 2) http://www.color.org/icc_specs2.html (ICC.1:2001-04) +# 3) http://developer.apple.com/documentation/GraphicsImaging/Reference/ColorSync_Manager/ColorSync_Manager.pdf +# 4) http://www.color.org/privatetag2007-01.pdf +# 5) http://www.color.org/icc_specs2.xalter (approved revisions, 2010-07-16) +# 6) Eef Vreeland private communication +# 7) https://color.org/specification/ICC.2-2019.pdf +# +# Notes: The ICC profile information is different: the format of each +# tag is embedded in the information instead of in the directory +# structure. This makes things a bit more complex because I need +# an extra level of logic to decode the variable-format tags. +#------------------------------------------------------------------------------ + +package Image::ExifTool::ICC_Profile; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.40'; + +sub ProcessICC($$); +sub ProcessICC_Profile($$$); +sub WriteICC_Profile($$;$); +sub ProcessMetadata($$$); +sub ValidateICC($); + +# illuminant type definitions +my %illuminantType = ( + 1 => 'D50', + 2 => 'D65', + 3 => 'D93', + 4 => 'F2', + 5 => 'D55', + 6 => 'A', + 7 => 'Equi-Power (E)', + 8 => 'F8', +); +my %profileClass = ( + scnr => 'Input Device Profile', + mntr => 'Display Device Profile', + prtr => 'Output Device Profile', + 'link'=> 'DeviceLink Profile', + spac => 'ColorSpace Conversion Profile', + abst => 'Abstract Profile', + nmcl => 'NamedColor Profile', + nkpf => 'Nikon Input Device Profile (NON-STANDARD!)', # (written by Nikon utilities) + # additions in v5 (ref 7) + cenc => 'ColorEncodingSpace Profile', + 'mid '=> 'MultiplexIdentification Profile', + mlnk => 'MultiplexLink Profile', + mvis => 'MultiplexVisualization Profile', +); +my %manuSig = ( #6 + 'NONE' => 'none', + 'none' => 'none', #PH + '' => '', #PH + '4d2p' => 'Erdt Systems GmbH & Co KG', + 'AAMA' => 'Aamazing Technologies, Inc.', + 'ACER' => 'Acer Peripherals', + 'ACLT' => 'Acolyte Color Research', + 'ACTI' => 'Actix Systems, Inc.', + 'ADAR' => 'Adara Technology, Inc.', + 'ADBE' => 'Adobe Systems Inc.', + 'ADI ' => 'ADI Systems, Inc.', + 'AGFA' => 'Agfa Graphics N.V.', + 'ALMD' => 'Alps Electric USA, Inc.', + 'ALPS' => 'Alps Electric USA, Inc.', + 'ALWN' => 'Alwan Color Expertise', + 'AMTI' => 'Amiable Technologies, Inc.', + 'AOC ' => 'AOC International (U.S.A), Ltd.', + 'APAG' => 'Apago', + 'APPL' => 'Apple Computer Inc.', + 'appl' => 'Apple Computer Inc.', + 'AST ' => 'AST', + 'AT&T' => 'AT&T Computer Systems', + 'BAEL' => 'BARBIERI electronic', + 'berg' => 'bergdesign incorporated', + 'bICC' => 'basICColor GmbH', + 'BRCO' => 'Barco NV', + 'BRKP' => 'Breakpoint Pty Limited', + 'BROT' => 'Brother Industries, LTD', + 'BULL' => 'Bull', + 'BUS ' => 'Bus Computer Systems', + 'C-IT' => 'C-Itoh', + 'CAMR' => 'Intel Corporation', + 'CANO' => 'Canon, Inc. (Canon Development Americas, Inc.)', + 'CARR' => 'Carroll Touch', + 'CASI' => 'Casio Computer Co., Ltd.', + 'CBUS' => 'Colorbus PL', + 'CEL ' => 'Crossfield', + 'CELx' => 'Crossfield', + 'ceyd' => 'Integrated Color Solutions, Inc.', + 'CGS ' => 'CGS Publishing Technologies International GmbH', + 'CHM ' => 'Rochester Robotics', + 'CIGL' => 'Colour Imaging Group, London', + 'CITI' => 'Citizen', + 'CL00' => 'Candela, Ltd.', + 'CLIQ' => 'Color IQ', + 'clsp' => 'MacDermid ColorSpan, Inc.', + 'CMCO' => 'Chromaco, Inc.', + 'CMiX' => 'CHROMiX', + 'COLO' => 'Colorgraphic Communications Corporation', + 'COMP' => 'COMPAQ Computer Corporation', + 'COMp' => 'Compeq USA/Focus Technology', + 'CONR' => 'Conrac Display Products', + 'CORD' => 'Cordata Technologies, Inc.', + 'CPQ ' => 'Compaq Computer Corporation', + 'CPRO' => 'ColorPro', + 'CRN ' => 'Cornerstone', + 'CTX ' => 'CTX International, Inc.', + 'CVIS' => 'ColorVision', + 'CWC ' => 'Fujitsu Laboratories, Ltd.', + 'DARI' => 'Darius Technology, Ltd.', + 'DATA' => 'Dataproducts', + 'DCP ' => 'Dry Creek Photo', + 'DCRC' => 'Digital Contents Resource Center, Chung-Ang University', + 'DELL' => 'Dell Computer Corporation', + 'DIC ' => 'Dainippon Ink and Chemicals', + 'DICO' => 'Diconix', + 'DIGI' => 'Digital', + 'DL&C' => 'Digital Light & Color', + 'DPLG' => 'Doppelganger, LLC', + 'DS ' => 'Dainippon Screen', + 'ds ' => 'Dainippon Screen', + 'DSOL' => 'DOOSOL', + 'DUPN' => 'DuPont', + 'dupn' => 'DuPont', + 'Eizo' => 'EIZO NANAO CORPORATION', + 'EPSO' => 'Epson', + 'ESKO' => 'Esko-Graphics', + 'ETRI' => 'Electronics and Telecommunications Research Institute', + 'EVER' => 'Everex Systems, Inc.', + 'EXAC' => 'ExactCODE GmbH', + 'FALC' => 'Falco Data Products, Inc.', + 'FF ' => 'Fuji Photo Film Co.,LTD', + 'FFEI' => 'FujiFilm Electronic Imaging, Ltd.', + 'ffei' => 'FujiFilm Electronic Imaging, Ltd.', + 'flux' => 'FluxData Corporation', + 'FNRD' => 'fnord software', + 'FORA' => 'Fora, Inc.', + 'FORE' => 'Forefront Technology Corporation', + 'FP ' => 'Fujitsu', + 'FPA ' => 'WayTech Development, Inc.', + 'FUJI' => 'Fujitsu', + 'FX ' => 'Fuji Xerox Co., Ltd.', + 'GCC ' => 'GCC Technologies, Inc.', + 'GGSL' => 'Global Graphics Software Limited', + 'GMB ' => 'Gretagmacbeth', + 'GMG ' => 'GMG GmbH & Co. KG', + 'GOLD' => 'GoldStar Technology, Inc.', + 'GOOG' => 'Google', #PH + 'GPRT' => 'Giantprint Pty Ltd', + 'GTMB' => 'Gretagmacbeth', + 'GVC ' => 'WayTech Development, Inc.', + 'GW2K' => 'Sony Corporation', + 'HCI ' => 'HCI', + 'HDM ' => 'Heidelberger Druckmaschinen AG', + 'HERM' => 'Hermes', + 'HITA' => 'Hitachi America, Ltd.', + 'HiTi' => 'HiTi Digital, Inc.', + 'HP ' => 'Hewlett-Packard', + 'HTC ' => 'Hitachi, Ltd.', + 'IBM ' => 'IBM Corporation', + 'IDNT' => 'Scitex Corporation, Ltd.', + 'Idnt' => 'Scitex Corporation, Ltd.', + 'IEC ' => 'Hewlett-Packard', + 'IIYA' => 'Iiyama North America, Inc.', + 'IKEG' => 'Ikegami Electronics, Inc.', + 'IMAG' => 'Image Systems Corporation', + 'IMI ' => 'Ingram Micro, Inc.', + 'Inca' => 'Inca Digital Printers Ltd.', + 'INTC' => 'Intel Corporation', + 'INTL' => 'N/A (INTL)', + 'INTR' => 'Intra Electronics USA, Inc.', + 'IOCO' => 'Iocomm International Technology Corporation', + 'IPS ' => 'InfoPrint Solutions Company', + 'IRIS' => 'Scitex Corporation, Ltd.', + 'Iris' => 'Scitex Corporation, Ltd.', + 'iris' => 'Scitex Corporation, Ltd.', + 'ISL ' => 'Ichikawa Soft Laboratory', + 'ITNL' => 'N/A (ITNL)', + 'IVM ' => 'IVM', + 'IWAT' => 'Iwatsu Electric Co., Ltd.', + 'JPEG' => 'Joint Photographic Experts Group', #PH + 'JSFT' => 'Jetsoft Development', + 'JVC ' => 'JVC Information Products Co.', + 'KART' => 'Scitex Corporation, Ltd.', + 'Kart' => 'Scitex Corporation, Ltd.', + 'kart' => 'Scitex Corporation, Ltd.', + 'KFC ' => 'KFC Computek Components Corporation', + 'KLH ' => 'KLH Computers', + 'KMHD' => 'Konica Minolta Holdings, Inc.', + 'KNCA' => 'Konica Corporation', + 'KODA' => 'Kodak', + 'KYOC' => 'Kyocera', + 'LCAG' => 'Leica Camera AG', + 'LCCD' => 'Leeds Colour', + 'lcms' => 'Little CMS', #NealKrawetz + 'LDAK' => 'Left Dakota', + 'LEAD' => 'Leading Technology, Inc.', + 'Leaf' => 'Leaf', #PH + 'LEXM' => 'Lexmark International, Inc.', + 'LINK' => 'Link Computer, Inc.', + 'LINO' => 'Linotronic', + 'Lino' => 'Linotronic', #PH (NC) + 'lino' => 'Linotronic', #PH (NC) + 'LITE' => 'Lite-On, Inc.', + 'MAGC' => 'Mag Computronic (USA) Inc.', + 'MAGI' => 'MAG Innovision, Inc.', + 'MANN' => 'Mannesmann', + 'MICN' => 'Micron Technology, Inc.', + 'MICR' => 'Microtek', + 'MICV' => 'Microvitec, Inc.', + 'MINO' => 'Minolta', + 'MITS' => 'Mitsubishi Electronics America, Inc.', + 'MITs' => 'Mitsuba Corporation', + 'Mits' => 'Mitsubishi Electric Corporation Kyoto Works', + 'MNLT' => 'Minolta', + 'MODG' => 'Modgraph, Inc.', + 'MONI' => 'Monitronix, Inc.', + 'MONS' => 'Monaco Systems Inc.', + 'MORS' => 'Morse Technology, Inc.', + 'MOTI' => 'Motive Systems', + 'MSFT' => 'Microsoft Corporation', + 'MUTO' => 'MUTOH INDUSTRIES LTD.', + 'NANA' => 'NANAO USA Corporation', + 'NEC ' => 'NEC Corporation', + 'NEXP' => 'NexPress Solutions LLC', + 'NISS' => 'Nissei Sangyo America, Ltd.', + 'NKON' => 'Nikon Corporation', + 'ob4d' => 'Erdt Systems GmbH & Co KG', + 'obic' => 'Medigraph GmbH', + 'OCE ' => 'Oce Technologies B.V.', + 'OCEC' => 'OceColor', + 'OKI ' => 'Oki', + 'OKID' => 'Okidata', + 'OKIP' => 'Okidata', + 'OLIV' => 'Olivetti', + 'OLYM' => 'OLYMPUS OPTICAL CO., LTD', + 'ONYX' => 'Onyx Graphics', + 'OPTI' => 'Optiquest', + 'PACK' => 'Packard Bell', + 'PANA' => 'Matsushita Electric Industrial Co., Ltd.', + 'PANT' => 'Pantone, Inc.', + 'PBN ' => 'Packard Bell', + 'PFU ' => 'PFU Limited', + 'PHIL' => 'Philips Consumer Electronics Co.', + 'PNTX' => 'HOYA Corporation PENTAX Imaging Systems Division', + 'POne' => 'Phase One A/S', + 'PREM' => 'Premier Computer Innovations', + 'PRIN' => 'Princeton Graphic Systems', + 'PRIP' => 'Princeton Publishing Labs', + 'QLUX' => 'Hong Kong', + 'QMS ' => 'QMS, Inc.', + 'QPCD' => 'QPcard AB', + 'QUAD' => 'QuadLaser', + 'quby' => 'Qubyx Sarl', + 'QUME' => 'Qume Corporation', + 'RADI' => 'Radius, Inc.', + 'RDDx' => 'Integrated Color Solutions, Inc.', + 'RDG ' => 'Roland DG Corporation', + 'REDM' => 'REDMS Group, Inc.', + 'RELI' => 'Relisys', + 'RGMS' => 'Rolf Gierling Multitools', + 'RICO' => 'Ricoh Corporation', + 'RNLD' => 'Edmund Ronald', + 'ROYA' => 'Royal', + 'RPC ' => 'Ricoh Printing Systems,Ltd.', + 'RTL ' => 'Royal Information Electronics Co., Ltd.', + 'SAMP' => 'Sampo Corporation of America', + 'SAMS' => 'Samsung, Inc.', + 'SANT' => 'Jaime Santana Pomares', + 'SCIT' => 'Scitex Corporation, Ltd.', + 'Scit' => 'Scitex Corporation, Ltd.', + 'scit' => 'Scitex Corporation, Ltd.', + 'SCRN' => 'Dainippon Screen', + 'scrn' => 'Dainippon Screen', + 'SDP ' => 'Scitex Corporation, Ltd.', + 'Sdp ' => 'Scitex Corporation, Ltd.', + 'sdp ' => 'Scitex Corporation, Ltd.', + 'SEC ' => 'SAMSUNG ELECTRONICS CO.,LTD', + 'SEIK' => 'Seiko Instruments U.S.A., Inc.', + 'SEIk' => 'Seikosha', + 'SGUY' => 'ScanGuy.com', + 'SHAR' => 'Sharp Laboratories', + 'SICC' => 'International Color Consortium', + 'siwi' => 'SIWI GRAFIKA CORPORATION', + 'SONY' => 'SONY Corporation', + 'Sony' => 'Sony Corporation', + 'SPCL' => 'SpectraCal', + 'STAR' => 'Star', + 'STC ' => 'Sampo Technology Corporation', + 'TALO' => 'Talon Technology Corporation', + 'TAND' => 'Tandy', + 'TATU' => 'Tatung Co. of America, Inc.', + 'TAXA' => 'TAXAN America, Inc.', + 'TDS ' => 'Tokyo Denshi Sekei K.K.', + 'TECO' => 'TECO Information Systems, Inc.', + 'TEGR' => 'Tegra', + 'TEKT' => 'Tektronix, Inc.', + 'TI ' => 'Texas Instruments', + 'TMKR' => 'TypeMaker Ltd.', + 'TOSB' => 'TOSHIBA corp.', + 'TOSH' => 'Toshiba, Inc.', + 'TOTK' => 'TOTOKU ELECTRIC Co., LTD', + 'TRIU' => 'Triumph', + 'TSBT' => 'TOSHIBA TEC CORPORATION', + 'TTX ' => 'TTX Computer Products, Inc.', + 'TVM ' => 'TVM Professional Monitor Corporation', + 'TW ' => 'TW Casper Corporation', + 'ULSX' => 'Ulead Systems', + 'UNIS' => 'Unisys', + 'UTZF' => 'Utz Fehlau & Sohn', + 'VARI' => 'Varityper', + 'VIEW' => 'Viewsonic', + 'VISL' => 'Visual communication', + 'VIVO' => 'Vivo Mobile Communication Co., Ltd', + 'WANG' => 'Wang', + 'WLBR' => 'Wilbur Imaging', + 'WTG2' => 'Ware To Go', + 'WYSE' => 'WYSE Technology', + 'XERX' => 'Xerox Corporation', + 'XRIT' => 'X-Rite', + 'yxym' => 'YxyMaster GmbH', + 'Z123' => "Lavanya's test Company", + 'Zebr' => 'Zebra Technologies Inc', + 'ZRAN' => 'Zoran Corporation', + # also seen: " ",ACMS,KCMS,UCCM,etc2,SCTX +); + +# ICC_Profile tag table +%Image::ExifTool::ICC_Profile::Main = ( + GROUPS => { 2 => 'Image' }, + PROCESS_PROC => \&ProcessICC_Profile, + WRITE_PROC => \&WriteICC_Profile, + NOTES => q{ + ICC profile information is used in many different file types including JPEG, + TIFF, PDF, PostScript, Photoshop, PNG, MIFF, PICT, QuickTime, XCF and some + RAW formats. While the tags listed below are not individually writable, the + entire profile itself can be accessed via the extra 'ICC_Profile' tag, but + this tag is neither extracted nor written unless specified explicitly. See + L<http://www.color.org/icc_specs2.xalter> for the official ICC + specification. + }, + A2B0 => 'AToB0', + A2B1 => 'AToB1', + A2B2 => 'AToB2', + bXYZ => 'BlueMatrixColumn', # (called BlueColorant in ref 2) + bTRC => { + Name => 'BlueTRC', + Description => 'Blue Tone Reproduction Curve', + }, + B2A0 => 'BToA0', + B2A1 => 'BToA1', + B2A2 => 'BToA2', + calt => { + Name => 'CalibrationDateTime', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + targ => { + Name => 'CharTarget', + ValueConv => '$val=~s/\0.*//; length $val > 128 ? \$val : $val', + }, + chad => 'ChromaticAdaptation', + chrm => { + Name => 'Chromaticity', + Groups => { 1 => 'ICC_Profile#' }, #(just for the group list) + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Chromaticity', + Validate => '$type eq "chrm"', + }, + }, + clro => 'ColorantOrder', + clrt => { + Name => 'ColorantTable', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::ColorantTable', + Validate => '$type eq "clrt"', + }, + }, + clot => { # new in version 4.2 + Name => 'ColorantTableOut', + Binary => 1, + }, + cprt => { + Name => 'ProfileCopyright', + ValueConv => '$val=~s/\0.*//; $val', # may be null terminated + }, + crdi => 'CRDInfo', #2 + dmnd => { + Name => 'DeviceMfgDesc', + Groups => { 2 => 'Camera' }, + }, + dmdd => { + Name => 'DeviceModelDesc', + Groups => { 2 => 'Camera' }, + }, + devs => { + Name => 'DeviceSettings', #2 + Groups => { 2 => 'Camera' }, + }, + gamt => 'Gamut', + kTRC => { + Name => 'GrayTRC', + Description => 'Gray Tone Reproduction Curve', + }, + gXYZ => 'GreenMatrixColumn', # (called GreenColorant in ref 2) + gTRC => { + Name => 'GreenTRC', + Description => 'Green Tone Reproduction Curve', + }, + lumi => 'Luminance', + meas => { + Name => 'Measurement', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Measurement', + Validate => '$type eq "meas"', + }, + }, + bkpt => 'MediaBlackPoint', + wtpt => 'MediaWhitePoint', + ncol => 'NamedColor', #2 + ncl2 => 'NamedColor2', + resp => 'OutputResponse', + pre0 => 'Preview0', + pre1 => 'Preview1', + pre2 => 'Preview2', + desc => 'ProfileDescription', + pseq => 'ProfileSequenceDesc', + psd0 => 'PostScript2CRD0', #2 + psd1 => 'PostScript2CRD1', #2 + psd2 => 'PostScript2CRD2', #2 + ps2s => 'PostScript2CSA', #2 + ps2i => 'PS2RenderingIntent', #2 + rXYZ => 'RedMatrixColumn', # (called RedColorant in ref 2) + rTRC => { + Name => 'RedTRC', + Description => 'Red Tone Reproduction Curve', + }, + scrd => 'ScreeningDesc', + scrn => 'Screening', + 'bfd '=> { + Name => 'UCRBG', + Description => 'Under Color Removal and Black Gen.', + }, + tech => { + Name => 'Technology', + PrintConv => { + fscn => 'Film Scanner', + dcam => 'Digital Camera', + rscn => 'Reflective Scanner', + ijet => 'Ink Jet Printer', + twax => 'Thermal Wax Printer', + epho => 'Electrophotographic Printer', + esta => 'Electrostatic Printer', + dsub => 'Dye Sublimation Printer', + rpho => 'Photographic Paper Printer', + fprn => 'Film Writer', + vidm => 'Video Monitor', + vidc => 'Video Camera', + pjtv => 'Projection Television', + 'CRT '=> 'Cathode Ray Tube Display', + 'PMD '=> 'Passive Matrix Display', + 'AMD '=> 'Active Matrix Display', + KPCD => 'Photo CD', + imgs => 'Photo Image Setter', + grav => 'Gravure', + offs => 'Offset Lithography', + silk => 'Silkscreen', + flex => 'Flexography', + mpfs => 'Motion Picture Film Scanner', #5 + mpfr => 'Motion Picture Film Recorder', #5 + dmpc => 'Digital Motion Picture Camera', #5 + dcpj => 'Digital Cinema Projector', #5 + }, + }, + vued => 'ViewingCondDesc', + view => { + Name => 'ViewingConditions', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::ViewingConditions', + Validate => '$type eq "view"', + }, + }, + ciis => 'ColorimetricIntentImageState', #5 + scoe => 'SceneColorimetryEstimates', #5 + sape => 'SceneAppearanceEstimates', #5 + fpce => 'FocalPlaneColorimetryEstimates', #5 + rhoc => 'ReflectionHardcopyOrigColorimetry', #5 + rpoc => 'ReflectionPrintOutputColorimetry', #5 + psid => { #5 + Name => 'ProfileSequenceIdentifier', + Binary => 1, + }, + B2D0 => { Name => 'BToD0', Binary => 1 }, #5 + B2D1 => { Name => 'BToD1', Binary => 1 }, #5 + B2D2 => { Name => 'BToD2', Binary => 1 }, #5 + B2D3 => { Name => 'BToD3', Binary => 1 }, #5 + D2B0 => { Name => 'DToB0', Binary => 1 }, #5 + D2B1 => { Name => 'DToB1', Binary => 1 }, #5 + D2B2 => { Name => 'DToB2', Binary => 1 }, #5 + D2B3 => { Name => 'DToB3', Binary => 1 }, #5 + rig0 => { #5 + Name => 'PerceptualRenderingIntentGamut', + PrintConv => { + prmg => 'Perceptual Reference Medium Gamut', + }, + }, + rig2 => { #5 + Name => 'SaturationRenderingIntentGamut', + PrintConv => { + prmg => 'Perceptual Reference Medium Gamut', + }, + }, + meta => { #5 + Name => 'Metadata', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Metadata', + Validate => '$type eq "dict"', + }, + }, + + # ColorSync custom tags (ref 3) + psvm => 'PS2CRDVMSize', + vcgt => 'VideoCardGamma', + mmod => 'MakeAndModel', + dscm => 'ProfileDescriptionML', + ndin => 'NativeDisplayInfo', + + # Microsoft custom tags (ref http://msdn2.microsoft.com/en-us/library/ms536870.aspx) + MS00 => 'WCSProfiles', + + psd3 => { #6 + Name => 'PostScript2CRD3', + Binary => 1, # (NC) + }, + + # new tags in v5 (ref 7) + A2B3 => 'AToB3', + A2M0 => 'AToM0', + B2A3 => 'BToA3', + bcp0 => 'BRDFColorimetricParam0', + bcp1 => 'BRDFColorimetricParam1', + bcp2 => 'BRDFColorimetricParam2', + bcp3 => 'BRDFColorimetricParam3', + bsp0 => 'BRDFSpectralParam0', + bsp1 => 'BRDFSpectralParam1', + bsp2 => 'BRDFSpectralParam2', + bsp3 => 'BRDFSpectralParam3', + bAB0 => 'BRDFAToB0', + bAB1 => 'BRDFAToB1', + bAB2 => 'BRDFAToB2', + bAB3 => 'BRDFAToB3', + bBA0 => 'BRDFBToA0', + bBA1 => 'BRDFBToA1', + bBA2 => 'BRDFBToA2', + bBA3 => 'BRDFBToA3', + bBD0 => 'BRDFBToD0', + bBD1 => 'BRDFBToD1', + bBD2 => 'BRDFBToD2', + bBD3 => 'BRDFBToD3', + bDB0 => 'BRDFDToB0', + bDB1 => 'BRDFDToB1', + bDB2 => 'BRDFDToB2', + bDB3 => 'BRDFDToB3', + bMB0 => 'BRDFMToB0', + bMB1 => 'BRDFMToB1', + bMB2 => 'BRDFMToB2', + bMB3 => 'BRDFMToB3', + bMS0 => 'BRDFMToS0', + bMS1 => 'BRDFMToS1', + bMS2 => 'BRDFMToS2', + bMS3 => 'BRDFMToS3', + dAB0 => 'DirectionalAToB0', + dAB1 => 'DirectionalAToB1', + dAB2 => 'DirectionalAToB2', + dAB3 => 'DirectionalAToB3', + dBA0 => 'DirectionalBToA0', + dBA1 => 'DirectionalBToA1', + dBA2 => 'DirectionalBToA2', + dBA3 => 'DirectionalBToA3', + dBD0 => 'DirectionalBToD0', + dBD1 => 'DirectionalBToD1', + dBD2 => 'DirectionalBToD2', + dBD3 => 'DirectionalBToD3', + dDB0 => 'DirectionalDToB0', + dDB1 => 'DirectionalDToB1', + dDB2 => 'DirectionalDToB2', + dDB3 => 'DirectionalDToB3', + gdb0 => 'GamutBoundaryDescription0', + gdb1 => 'GamutBoundaryDescription1', + gdb2 => 'GamutBoundaryDescription2', + gdb3 => 'GamutBoundaryDescription3', + 'mdv '=> 'MultiplexDefaultValues', + mcta => 'MultiplexTypeArray', + minf => 'MeasurementInfo', + miin => 'MeasurementInputInfo', + M2A0 => 'MToA0', + M2B0 => 'MToB0', + M2B1 => 'MToB1', + M2B2 => 'MToB2', + M2B3 => 'MToB3', + M2S0 => 'MToS0', + M2S1 => 'MToS1', + M2S2 => 'MToS2', + M2S3 => 'MToS3', + cept => 'ColorEncodingParams', + csnm => 'ColorSpaceName', + cloo => 'ColorantOrderOut', + clio => 'ColorantInfoOut', + c2sp => 'CustomToStandardPcc', + 'CxF '=> 'CXF', + nmcl => 'NamedColor', + psin => 'ProfileSequenceInfo', + rfnm => 'ReferenceName', + svcn => 'SpectralViewingConditions', + swpt => 'SpectralWhitePoint', + s2cp => 'StandardToCustomPcc', + smap => 'SurfaceMap', + # smwp ? (seen in some v5 samples [was a mistake in sample production]) + + # the following entry represents the ICC profile header, and doesn't + # exist as a tag in the directory. It is only in this table to provide + # a link so ExifTool can locate the header tags + Header => { + Name => 'ProfileHeader', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Header', + }, + }, +); + +# ICC profile header definition +%Image::ExifTool::ICC_Profile::Header = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'ICC_Profile', 1 => 'ICC-header', 2 => 'Image' }, + 4 => { + Name => 'ProfileCMMType', + Format => 'string[4]', + SeparateTable => 'ManuSig', + PrintConv => \%manuSig, + }, + 8 => { + Name => 'ProfileVersion', + Format => 'int16s', + PrintConv => '($val >> 8).".".(($val & 0xf0)>>4).".".($val & 0x0f)', + }, + 12 => { + Name => 'ProfileClass', + Format => 'string[4]', + PrintConv => \%profileClass, + }, + 16 => { + Name => 'ColorSpaceData', + Format => 'string[4]', + }, + 20 => { + Name => 'ProfileConnectionSpace', + Format => 'string[4]', + }, + 24 => { + Name => 'ProfileDateTime', + Groups => { 2 => 'Time' }, + Format => 'int16u[6]', + ValueConv => 'sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d",split(" ",$val));', + PrintConv => '$self->ConvertDateTime($val)', + }, + 36 => { + Name => 'ProfileFileSignature', + Format => 'string[4]', + }, + 40 => { + Name => 'PrimaryPlatform', + Format => 'string[4]', + PrintConv => { + 'APPL' => 'Apple Computer Inc.', + 'MSFT' => 'Microsoft Corporation', + 'SGI ' => 'Silicon Graphics Inc.', + 'SUNW' => 'Sun Microsystems Inc.', + 'TGNT' => 'Taligent Inc.', + }, + }, + 44 => { + Name => 'CMMFlags', + Format => 'int32u', + PrintConv => q[ + ($val & 0x01 ? "Embedded, " : "Not Embedded, ") . + ($val & 0x02 ? "Not Independent" : "Independent") + ], + }, + 48 => { + Name => 'DeviceManufacturer', + Format => 'string[4]', + SeparateTable => 'ManuSig', + PrintConv => \%manuSig, + }, + 52 => { + Name => 'DeviceModel', + Format => 'string[4]', + # ROMM = Reference Output Medium Metric + }, + 56 => { + Name => 'DeviceAttributes', + Format => 'int32u[2]', + PrintConv => q[ + my @v = split ' ', $val; + ($v[1] & 0x01 ? "Transparency, " : "Reflective, ") . + ($v[1] & 0x02 ? "Matte, " : "Glossy, ") . + ($v[1] & 0x04 ? "Negative, " : "Positive, ") . + ($v[1] & 0x08 ? "B&W" : "Color"); + ], + }, + 64 => { + Name => 'RenderingIntent', + Format => 'int32u', + PrintConv => { + 0 => 'Perceptual', + 1 => 'Media-Relative Colorimetric', + 2 => 'Saturation', + 3 => 'ICC-Absolute Colorimetric', + }, + }, + 68 => { + Name => 'ConnectionSpaceIlluminant', + Format => 'fixed32s[3]', # xyz + }, + 80 => { + Name => 'ProfileCreator', + Format => 'string[4]', + SeparateTable => 'ManuSig', + PrintConv => \%manuSig, + }, + 84 => { + Name => 'ProfileID', + Format => 'int8u[16]', + PrintConv => 'Image::ExifTool::ICC_Profile::HexID($val)', + }, +); + +# viewingConditionsType (view) definition +%Image::ExifTool::ICC_Profile::ViewingConditions = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'ICC_Profile', 1 => 'ICC-view', 2 => 'Image' }, + 8 => { + Name => 'ViewingCondIlluminant', + Format => 'fixed32s[3]', # xyz + }, + 20 => { + Name => 'ViewingCondSurround', + Format => 'fixed32s[3]', # xyz + }, + 32 => { + Name => 'ViewingCondIlluminantType', + Format => 'int32u', + PrintConv => \%illuminantType, + }, +); + +# measurementType (meas) definition +%Image::ExifTool::ICC_Profile::Measurement = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'ICC_Profile', 1 => 'ICC-meas', 2 => 'Image' }, + 8 => { + Name => 'MeasurementObserver', + Format => 'int32u', + PrintConv => { + 1 => 'CIE 1931', + 2 => 'CIE 1964', + }, + }, + 12 => { + Name => 'MeasurementBacking', + Format => 'fixed32s[3]', # xyz + }, + 24 => { + Name => 'MeasurementGeometry', + Format => 'int32u', + PrintConv => { + 0 => 'Unknown', + 1 => '0/45 or 45/0', + 2 => '0/d or d/0', + }, + }, + 28 => { + Name => 'MeasurementFlare', + Format => 'fixed32u', + PrintConv => '$val*100 . "%"', # change into a percent + }, + 32 => { + Name => 'MeasurementIlluminant', + Format => 'int32u', + PrintConv => \%illuminantType, + }, +); + +# chromaticity (chrm) definition +%Image::ExifTool::ICC_Profile::Chromaticity = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'ICC_Profile', 1 => 'ICC-chrm', 2 => 'Image' }, + 8 => { + Name => 'ChromaticityChannels', + Format => 'int16u', + }, + 10 => { + Name => 'ChromaticityColorant', + Format => 'int16u', + PrintConv => { + 0 => 'Unknown', + 1 => 'ITU-R BT.709', + 2 => 'SMPTE RP145-1994', + 3 => 'EBU Tech.3213-E', + 4 => 'P22', + }, + }, + # include definitions for 4 channels -- if there are + # fewer then the ProcessBinaryData logic won't print them. + # If there are more, oh well. + 12 => { + Name => 'ChromaticityChannel1', + Format => 'fixed32u[2]', + }, + 20 => { + Name => 'ChromaticityChannel2', + Format => 'fixed32u[2]', + }, + 28 => { + Name => 'ChromaticityChannel3', + Format => 'fixed32u[2]', + }, + 36 => { + Name => 'ChromaticityChannel4', + Format => 'fixed32u[2]', + }, +); + +# colorantTable (clrt) definition +%Image::ExifTool::ICC_Profile::ColorantTable = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'ICC_Profile', 1 => 'ICC-clrt', 2 => 'Image' }, + 8 => { + Name => 'ColorantCount', + Format => 'int32u', + }, + # include definitions for 3 colorants -- if there are + # fewer then the ProcessBinaryData logic won't print them. + # If there are more, oh well. + 12 => { + Name => 'Colorant1Name', + Format => 'string[32]', + }, + 44 => { + Name => 'Colorant1Coordinates', + Format => 'int16u[3]', + }, + 50 => { + Name => 'Colorant2Name', + Format => 'string[32]', + }, + 82 => { + Name => 'Colorant2Coordinates', + Format => 'int16u[3]', + }, + 88 => { + Name => 'Colorant3Name', + Format => 'string[32]', + }, + 120 => { + Name => 'Colorant3Coordinates', + Format => 'int16u[3]', + }, +); + +# metadata (meta) tags +%Image::ExifTool::ICC_Profile::Metadata = ( + PROCESS_PROC => \&ProcessMetadata, + GROUPS => { 0 => 'ICC_Profile', 1 => 'ICC-meta', 2 => 'Image' }, + VARS => { NO_ID => 1 }, + NOTES => q{ + Only these few tags have been pre-defined, but ExifTool will extract any + Metadata tags that exist. + }, + ManufacturerName => { }, + MediaColor => { }, + MediaWeight => { }, + CreatorApp => { }, +); + +#------------------------------------------------------------------------------ +# Print ICC Profile ID in hex +# Inputs: 1) string of numbers +# Returns: string of hex digits +sub HexID($) +{ + my $val = shift; + my @vals = split(' ', $val); + # return a simple zero if no MD5 done + return 0 unless grep(!/^0/, @vals); + $val = ''; + foreach (@vals) { $val .= sprintf("%.2x",$_); } + return $val; +} + +#------------------------------------------------------------------------------ +# Get formatted value from ICC tag (which has the type embedded) +# Inputs: 0) data reference, 1) offset to tag data, 2) tag data size +# Returns: Formatted value or undefined if format not supported +# Notes: The following types are handled by BinaryTables: +# chromaticityType, colorantTableType, measurementType, viewingConditionsType +# The following types are currently not handled (most are large tables): +# curveType, lut16Type, lut8Type, lutAtoBType, lutBtoAType, namedColor2Type, +# parametricCurveType, profileSeqDescType, responseCurveSet16Type +# The multiLocalizedUnicodeType must be handled by the calling routine. +sub FormatICCTag($$$) +{ + my ($dataPt, $offset, $size) = @_; + + my $type; + if ($size >= 8) { + # get data type from start of tag data + $type = substr($$dataPt, $offset, 4); + } else { + $type = 'err'; + } + # colorantOrderType + if ($type eq 'clro' and $size >= 12) { + my $num = Get32u($dataPt, $offset+8); + if ($size >= $num + 12) { + my $pos = $offset + 12; + return join(' ',unpack("x$pos c$num", $$dataPt)); + } + } + # dataType + if ($type eq 'data' and $size >= 12) { + my $form = Get32u($dataPt, $offset+8); + # format 0 is UTF-8 data + $form == 0 and return substr($$dataPt, $offset+12, $size-12); + # binary data and other data types treat as binary (ie. don't format) + } + # dateTimeType + if ($type eq 'dtim' and $size >= 20) { + return sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d", + Get16u($dataPt, $offset+8), Get16u($dataPt, $offset+10), + Get16u($dataPt, $offset+12), Get16u($dataPt, $offset+14), + Get16u($dataPt, $offset+16), Get16u($dataPt, $offset+18)); + } + # s15Fixed16ArrayType + if ($type eq 'sf32') { + return ReadValue($dataPt,$offset+8,'fixed32s',($size-8)/4,$size-8); + } + # signatureType + if ($type eq 'sig ' and $size >= 12) { + return substr($$dataPt, $offset+8, 4); + } + # textType + $type eq 'text' and return substr($$dataPt, $offset+8, $size-8); + # textDescriptionType (ref 2, replaced by multiLocalizedUnicodeType) + if ($type eq 'desc' and $size >= 12) { + my $len = Get32u($dataPt, $offset+8); + if ($size >= $len + 12) { + my $str = substr($$dataPt, $offset+12, $len); + $str =~ s/\0.*//s; # truncate at null terminator + return $str; + } + } + # u16Fixed16ArrayType + if ($type eq 'uf32') { + return ReadValue($dataPt,$offset+8,'fixed32u',($size-8)/4,$size-8); + } + # uInt32ArrayType + if ($type eq 'ui32') { + return ReadValue($dataPt,$offset+8,'int32u',($size-8)/4,$size-8); + } + # uInt64ArrayType + if ($type eq 'ui64') { + return ReadValue($dataPt,$offset+8,'int64u',($size-8)/8,$size-8); + } + # uInt8ArrayType + if ($type eq 'ui08') { + return ReadValue($dataPt,$offset+8,'int8u',$size-8,$size-8); + } + # XYZType + if ($type eq 'XYZ ') { + my $str = ''; + my $pos; + for ($pos=8; $pos+12<=$size; $pos+=12) { + $str and $str .= ', '; + $str .= ReadValue($dataPt,$offset+$pos,'fixed32s',3,$size-$pos); + } + return $str; + } + return undef; # data type is not supported +} + +#------------------------------------------------------------------------------ +# Process ICC metadata record (ref 5) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessMetadata($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + my $dirEnd = $dirStart + $dirLen; + + if ($dirLen < 16 or substr($$dataPt, $dirStart, 4) ne 'dict') { + $et->Warn('Invalid ICC meta dictionary'); + return 0; + } + my $num = Get32u($dataPt, $dirStart + 8); + $et->VerboseDir('Metadata', $num); + my $size = Get32u($dataPt, $dirStart + 12); + $size < 16 and $et->Warn('Invalid ICC meta record size'), return 0; + # NOTE: In the example the minimum offset is 20, + # but this doesn't jive with the table (both in ref 5) + my $minPtr = 16 + $size * $num; + my $index; + for ($index=0; $index<$num; ++$index) { + my $entry = $dirStart + 16 + $size * $index; + if ($entry + $size > $dirEnd) { + $et->Warn('Truncated ICC meta dictionary'); + last; + } + my $namePtr = Get32u($dataPt, $entry); + my $nameLen = Get32u($dataPt, $entry + 4); + my $valuePtr = Get32u($dataPt, $entry + 8); + my $valueLen = Get32u($dataPt, $entry + 12); + next unless $namePtr and $valuePtr; # ignore if offsets are zero + if ($namePtr < $minPtr or $namePtr + $nameLen > $dirLen or + $valuePtr < $minPtr or $valuePtr + $valueLen > $dirLen) + { + $et->Warn('Corrupted ICC meta dictionary'); + last; + } + my $tag = substr($$dataPt, $dirStart + $namePtr, $nameLen); + my $val = substr($$dataPt, $dirStart + $valuePtr, $valueLen); + $tag = $et->Decode($tag, 'UTF16', 'MM', 'UTF8'); + $val = $et->Decode($val, 'UTF16', 'MM'); + # generate tagInfo if it doesn't exist + unless ($$tagTablePtr{$tag}) { + my $name = ucfirst $tag; + $name =~ s/\s+(.)/\u$1/g; + $name =~ tr/-_a-zA-Z0-9//dc; + next unless length $name; + $et->VPrint(0, $$et{INDENT}, "[adding $tag]\n"); + AddTagToTable($tagTablePtr, $tag, { Name => $name }); + } + $et->HandleTag($tagTablePtr, $tag, $val); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Write ICC profile file +# Inputs: 0) ExifTool object reference, 1) Reference to directory information +# Returns: 1 on success, 0 if this wasn't a valid ICC file, +# or -1 if a write error occurred +sub WriteICC($$) +{ + my ($et, $dirInfo) = @_; + # first make sure this is a valid ICC file (or no file at all) + my $raf = $$dirInfo{RAF}; + my $buff; + return 0 if $raf->Read($buff, 24) and ValidateICC(\$buff); + # now write the new ICC + $buff = WriteICC_Profile($et, $dirInfo); + if (defined $buff and length $buff) { + Write($$dirInfo{OutFile}, $buff) or return -1; + } else { + $et->Error('No ICC information to write'); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Write ICC data as a block +# Inputs: 0) ExifTool object reference, 1) source dirInfo reference, +# 2) tag table reference +# Returns: ICC data block (may be empty if no ICC data) +# Notes: Increments ExifTool CHANGED flag if changed +sub WriteICC_Profile($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + my $dirName = $$dirInfo{DirName} || 'ICC_Profile'; + # (don't write AsShotICCProfile or CurrentICCProfile here) + return undef unless $dirName eq 'ICC_Profile'; + my $nvHash = $et->GetNewValueHash($Image::ExifTool::Extra{$dirName}); + my $val = $et->GetNewValue($nvHash); + $val = '' unless defined $val; + return undef unless $et->IsOverwriting($nvHash, $val); + ++$$et{CHANGED}; + return $val; +} + +#------------------------------------------------------------------------------ +# Validate ICC data +# Inputs: 0) ICC data reference +# Returns: error string or undef on success +sub ValidateICC($) +{ + my $valPtr = shift; + my $err; + length($$valPtr) < 24 and return 'Invalid ICC profile'; + $profileClass{substr($$valPtr, 12, 4)} or $err = 'profile class'; + my $col = substr($$valPtr, 16, 4); # ColorSpaceData + my $con = substr($$valPtr, 20, 4); # ConnectionSpace + my $match = '(XYZ |Lab |Luv |YCbr|Yxy |RGB |GRAY|HSV |HLS |CMYK|CMY |[2-9A-F]CLR|nc..|\0{4})'; + $col =~ /$match/ or $err = 'color space'; + $con =~ /$match/ or $err = 'connection space'; + return $err ? "Invalid ICC profile (bad $err)" : undef; +} + +#------------------------------------------------------------------------------ +# Process ICC profile file +# Inputs: 0) ExifTool object reference, 1) Reference to directory information +# Returns: 1 if this was an ICC file +sub ProcessICC($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + $raf->Read($buff, 24) == 24 or return 0; + # check to see if this is a valid ICC profile file + return 0 if ValidateICC(\$buff); + $et->SetFileType(); + # read the profile + my $size = unpack('N', $buff); + if ($size < 128 or $size & 0x80000000) { + $et->Error("Bad ICC Profile length ($size)"); + return 1; + } + $raf->Seek(0, 0); + unless ($raf->Read($buff, $size) == $size) { + $et->Error('Truncated ICC profile'); + return 1; + } + my %dirInfo = ( + DataPt => \$buff, + DataLen => $size, + DirStart => 0, + DirLen => $size, + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::ICC_Profile::Main'); + return ProcessICC_Profile($et, \%dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Process ICC_Profile APP13 record +# Inputs: 0) ExifTool object reference, 1) Reference to directory information +# 2) Tag table reference (undefined to read ICC file) +# Returns: 1 on success +sub ProcessICC_Profile($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen}; + my $verbose = $et->Options('Verbose'); + + return 0 if $dirLen < 4; + + # extract binary ICC_Profile data block if binary mode or requested + if ((($$et{TAGS_FROM_FILE} and not $$et{EXCL_TAG_LOOKUP}{icc_profile}) or + $$et{REQ_TAG_LOOKUP}{icc_profile}) and + # (don't extract from AsShotICCProfile or CurrentICCProfile) + (not $$dirInfo{Name} or $$dirInfo{Name} eq 'ICC_Profile')) + { + $et->FoundTag('ICC_Profile', substr($$dataPt, $dirStart, $dirLen)); + } + + SetByteOrder('MM'); # ICC_Profile is always big-endian + + # check length of table + my $len = Get32u($dataPt, $dirStart); + if ($len != $dirLen or $len < 128) { + $et->Warn("Bad length ICC_Profile (length $len)"); + return 0 if $len < 128 or $dirLen < $len; + } + my $pos = $dirStart + 128; # position at start of table + my $numEntries = Get32u($dataPt, $pos); + if ($numEntries < 1 or $numEntries >= 0x100 + or $numEntries * 12 + 132 > $dirLen) + { + $et->Warn("Bad ICC_Profile table ($numEntries entries)"); + return 0; + } + + if ($verbose) { + $et->VerboseDir('ICC_Profile', $numEntries, $dirLen); + my $fakeInfo = { Name=>'ProfileHeader', SubDirectory => { } }; + $et->VerboseInfo(undef, $fakeInfo); + } + # increment ICC dir count + my $dirCount = $$et{DIR_COUNT}{ICC} = ($$et{DIR_COUNT}{ICC} || 0) + 1; + $$et{SET_GROUP1} = '+' . $dirCount if $dirCount > 1; + # process the header block + my %subdirInfo = ( + Name => 'ProfileHeader', + DataPt => $dataPt, + DataLen => $$dirInfo{DataLen}, + DirStart => $dirStart, + DirLen => 128, + Parent => $$dirInfo{DirName}, + DirName => 'Header', + ); + my $newTagTable = GetTagTable('Image::ExifTool::ICC_Profile::Header'); + $et->ProcessDirectory(\%subdirInfo, $newTagTable); + + $pos += 4; # skip item count + my $index; + for ($index=0; $index<$numEntries; ++$index) { + my $tagID = substr($$dataPt, $pos, 4); + my $offset = Get32u($dataPt, $pos + 4); + my $size = Get32u($dataPt, $pos + 8); + $pos += 12; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID); + # unknown tags aren't generated automatically by GetTagInfo() + # if the tagID's aren't numeric, so we must do this manually: + if (not $tagInfo and ($$et{OPTIONS}{Unknown} or $verbose)) { + $tagInfo = { Unknown => 1 }; + AddTagToTable($tagTablePtr, $tagID, $tagInfo); + } + next unless defined $tagInfo; + + if ($offset + $size > $dirLen) { + $et->Warn("Bad ICC_Profile table (truncated)"); + last; + } + my $valuePtr = $dirStart + $offset; + + my $subdir = $$tagInfo{SubDirectory}; + # format the value unless this is a subdirectory + my ($value, $fmt); + if ($size > 4) { + $fmt = substr($$dataPt, $valuePtr, 4); + # handle multiLocalizedUnicodeType + if ($fmt eq 'mluc' and not $subdir) { + next if $size < 28; + my $count = Get32u($dataPt, $valuePtr + 8); + my $recLen = Get32u($dataPt, $valuePtr + 12); + next if $recLen < 12; + my $i; + for ($i=0; $i<$count; ++$i) { + my $recPos = $valuePtr + 16 + $i * $recLen; + last if $recPos + $recLen > $valuePtr + $size; + my $lang = substr($$dataPt, $recPos, 4); + my $langInfo; + # validate language code and change to standard case (just in case) + if ($lang =~ s/^([a-z]{2})([A-Z]{2})$/\L$1-\U$2/i and $lang ne 'en-US') { + $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang); + } + my $strLen = Get32u($dataPt, $recPos + 4); + my $strPos = Get32u($dataPt, $recPos + 8); + last if $strPos + $strLen > $size; + my $str = substr($$dataPt, $valuePtr + $strPos, $strLen); + $str = $et->Decode($str, 'UTF16'); + $et->HandleTag($tagTablePtr, $tagID, $str, + TagInfo => $langInfo || $tagInfo, + Table => $tagTablePtr, + Index => $index, + Value => $str, + DataPt => $dataPt, + Size => $strLen, + Start => $valuePtr + $strPos, + Format => "type '${fmt}'", + ); + } + $et->Warn("Corrupted $$tagInfo{Name} data") if $i < $count; + next; + } + } else { + $fmt = 'err '; + } + $value = FormatICCTag($dataPt, $valuePtr, $size) unless $subdir; + $verbose and $et->VerboseInfo($tagID, $tagInfo, + Table => $tagTablePtr, + Index => $index, + Value => $value, + DataPt => $dataPt, + Size => $size, + Start => $valuePtr, + Format => "type '${fmt}'", + ); + if ($subdir) { + my $name = $$tagInfo{Name}; + undef $newTagTable; + if ($$subdir{TagTable}) { + $newTagTable = GetTagTable($$subdir{TagTable}); + unless ($newTagTable) { + warn "Unknown tag table $$subdir{TagTable}\n"; + next; + } + } else { + warn "Must specify TagTable for SubDirectory $name\n"; + next; + } + %subdirInfo = ( + Name => $name, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + DataLen => $$dirInfo{DataLen}, + DirStart => $valuePtr, + DirLen => $size, + DirName => $name, + Parent => $$dirInfo{DirName}, + ); + my $type = $fmt; + #### eval Validate ($type) + if (defined $$subdir{Validate} and not eval $$subdir{Validate}) { + $et->Warn("Invalid ICC $name data"); + } else { + $et->ProcessDirectory(\%subdirInfo, $newTagTable, $$subdir{ProcessProc}); + } + } elsif (defined $value) { + $et->FoundTag($tagInfo, $value); + } else { + $value = substr($$dataPt, $valuePtr, $size); + # treat unsupported formats as binary data + $$tagInfo{ValueConv} = '\$val' unless defined $$tagInfo{ValueConv}; + $et->FoundTag($tagInfo, $value); + } + } + delete $$et{SET_GROUP1}; + return 1; +} + + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::ICC_Profile - Read ICC Profile meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains the definitions to read information from ICC profiles. +ICC (International Color Consortium) profiles are used to translate color +data created on one device into another device's native color space. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.color.org/icc_specs2.html> + +=item L<http://developer.apple.com/documentation/GraphicsImaging/Reference/ColorSync_Manager/ColorSync_Manager.pdf> + +=item L<https://color.org/specification/ICC.2-2019.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/ICC_Profile Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/ICO.pm b/ExifTool/lib/Image/ExifTool/ICO.pm new file mode 100644 index 0000000..2d9060e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/ICO.pm @@ -0,0 +1,143 @@ +#------------------------------------------------------------------------------ +# File: ICO.pm +# +# Description: Read Windows ICO and CUR files +# +# Revisions: 2020-10-18 - P. Harvey Created +# +# References: 1) https://docs.fileformat.com/image/ico/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::ICO; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.00'; + +%Image::ExifTool::ICO::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + NOTES => 'Information extracted from Windows ICO (icon) and CUR (cursor) files.', + 2 => { + Name => 'ImageType', + Format => 'int16u', + PrintConv => { 1 => 'Icon', 2 => 'Cursor' }, + }, + 4 => { + Name => 'ImageCount', + Format => 'int16u', + RawConv => '$$self{ImageCount} = $val', + }, + 6 => { + Name => 'IconDir', + SubDirectory => { TagTable => 'Image::ExifTool::ICO::IconDir' }, + }, +); + +%Image::ExifTool::ICO::IconDir = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + 0 => { + Name => 'ImageWidth', + ValueConv => '$val or $val + 256', + }, + 1 => { + Name => 'ImageHeight', + ValueConv => '$val or $val + 256', + }, + 2 => 'NumColors', + 4 => [{ + Name => 'ColorPlanes', + Condition => '$$self{FileType} eq "ICO"', + Format => 'int16u', + Notes => 'ICO files', + },{ + Name => 'HotspotX', + Format => 'int16u', + Notes => 'CUR files', + }], + 6 => [{ + Name => 'BitsPerPixel', + Condition => '$$self{FileType} eq "ICO"', + Format => 'int16u', + Notes => 'ICO files', + },{ + Name => 'HotspotY', + Format => 'int16u', + Notes => 'CUR files', + }], + 8 => { + Name => 'ImageLength', + Format => 'int32u', + }, +); + +#------------------------------------------------------------------------------ +# Process ICO/CUR file +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid ICO/CUR file +sub ProcessICO($$$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($i, $buff); + # verify this is a valid ICO/CUR file + return 0 unless $raf->Read($buff, 6) == 6; + return 0 unless $buff =~ /^\0\0([\x01\x02])\0[^0]\0/s; + # (note: have seen cursor files in the wild with an 0x01 here, + # but SetFileType will use the .cur extension to identify these) + $et->SetFileType($1 eq "\x01" ? 'ICO' : 'CUR'); + SetByteOrder('II'); + my $tagTbl = GetTagTable('Image::ExifTool::ICO::Main'); + my $num = Get16u(\$buff, 4); + $et->HandleTag($tagTbl, 4, $num); + for ($i=0; $i<$num; ++$i) { + $raf->Read($buff, 16) == 16 or $et->Warn('Truncated file'), last; + $$et{DOC_NUM} = ++$$et{DOC_COUNT} if $i; + $et->HandleTag($tagTbl, 6, $buff); + } + delete $$et{DOC_NUM}; + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::ICO - Read ICO meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +information from Windows ICO (icon) and CUR (cursor) files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://docs.fileformat.com/image/ico/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/ICO Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/ID3.pm b/ExifTool/lib/Image/ExifTool/ID3.pm new file mode 100644 index 0000000..82efe71 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/ID3.pm @@ -0,0 +1,1680 @@ +#------------------------------------------------------------------------------ +# File: ID3.pm +# +# Description: Read ID3 and Lyrics3 meta information +# +# Revisions: 09/12/2005 - P. Harvey Created +# 09/08/2020 - PH Added Lyrics3 support +# +# References: 1) http://www.id3.org/ (now https://id3.org) +# 2) http://www.mp3-tech.org/ +# 3) http://www.fortunecity.com/underworld/sonic/3/id3tag.html +# 4) https://id3.org/Lyrics3 +#------------------------------------------------------------------------------ + +package Image::ExifTool::ID3; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.58'; + +sub ProcessID3v2($$$); +sub ProcessPrivate($$$); +sub ProcessSynText($$$); +sub ProcessID3Dir($$$); +sub ConvertID3v1Text($$); +sub ConvertTimeStamp($); + +# audio formats that we process after an ID3v2 header (in order) +my @audioFormats = qw(APE MPC FLAC OGG MP3); + +# audio formats where the processing proc is in a differently-named module +my %audioModule = ( + MP3 => 'ID3', + OGG => 'Ogg', +); + +# picture types for 'PIC' and 'APIC' tags +# (Note: Duplicated in ID3, ASF and FLAC modules!) +my %pictureType = ( + 0 => 'Other', + 1 => '32x32 PNG Icon', + 2 => 'Other Icon', + 3 => 'Front Cover', + 4 => 'Back Cover', + 5 => 'Leaflet', + 6 => 'Media', + 7 => 'Lead Artist', + 8 => 'Artist', + 9 => 'Conductor', + 10 => 'Band', + 11 => 'Composer', + 12 => 'Lyricist', + 13 => 'Recording Studio or Location', + 14 => 'Recording Session', + 15 => 'Performance', + 16 => 'Capture from Movie or Video', + 17 => 'Bright(ly) Colored Fish', + 18 => 'Illustration', + 19 => 'Band Logo', + 20 => 'Publisher Logo', +); + +my %dateTimeConv = ( + ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', +); + +# This table is just for documentation purposes +%Image::ExifTool::ID3::Main = ( + VARS => { NO_ID => 1 }, + PROCESS_PROC => \&ProcessID3Dir, # (used to process 'id3 ' chunk in WAV files) + NOTES => q{ + ExifTool extracts ID3 and Lyrics3 information from MP3, MPEG, WAV, AIFF, + OGG, FLAC, APE, MPC and RealAudio files. ID3v2 tags which support multiple + languages (eg. Comment and Lyrics) are extracted by specifying the tag name, + followed by a dash ('-'), then a 3-character ISO 639-2 language code (eg. + "Comment-spa"). See L<https://id3.org/> for the official ID3 specification + and L<http://www.loc.gov/standards/iso639-2/php/code_list.php> for a list of + ISO 639-2 language codes. + }, + ID3v1 => { + Name => 'ID3v1', + SubDirectory => { TagTable => 'Image::ExifTool::ID3::v1' }, + }, + ID3v1Enh => { + Name => 'ID3v1_Enh', + SubDirectory => { TagTable => 'Image::ExifTool::ID3::v1_Enh' }, + }, + ID3v22 => { + Name => 'ID3v2_2', + SubDirectory => { TagTable => 'Image::ExifTool::ID3::v2_2' }, + }, + ID3v23 => { + Name => 'ID3v2_3', + SubDirectory => { TagTable => 'Image::ExifTool::ID3::v2_3' }, + }, + ID3v24 => { + Name => 'ID3v2_4', + SubDirectory => { TagTable => 'Image::ExifTool::ID3::v2_4' }, + }, +); + +# Lyrics3 tags (ref 4) +%Image::ExifTool::ID3::Lyrics3 = ( + GROUPS => { 1 => 'Lyrics3', 2 => 'Audio' }, + NOTES => q{ + ExifTool extracts Lyrics3 version 1.00 and 2.00 tags from any file that + supports ID3. See L<https://id3.org/Lyrics3> for the specification. + }, + IND => 'Indications', + LYR => 'Lyrics', + INF => 'AdditionalInfo', + AUT => { Name => 'Author', Groups => { 2 => 'Author' } }, + EAL => 'ExtendedAlbumName', + EAR => 'ExtendedArtistName', + ETT => 'ExtendedTrackTitle', + IMG => 'AssociatedImageFile', + CRC => 'CRC', #PH +); + +# Mapping for ID3v1 Genre numbers +my %genre = ( + 0 => 'Blues', + 1 => 'Classic Rock', + 2 => 'Country', + 3 => 'Dance', + 4 => 'Disco', + 5 => 'Funk', + 6 => 'Grunge', + 7 => 'Hip-Hop', + 8 => 'Jazz', + 9 => 'Metal', + 10 => 'New Age', + 11 => 'Oldies', + 12 => 'Other', + 13 => 'Pop', + 14 => 'R&B', + 15 => 'Rap', + 16 => 'Reggae', + 17 => 'Rock', + 18 => 'Techno', + 19 => 'Industrial', + 20 => 'Alternative', + 21 => 'Ska', + 22 => 'Death Metal', + 23 => 'Pranks', + 24 => 'Soundtrack', + 25 => 'Euro-Techno', + 26 => 'Ambient', + 27 => 'Trip-Hop', + 28 => 'Vocal', + 29 => 'Jazz+Funk', + 30 => 'Fusion', + 31 => 'Trance', + 32 => 'Classical', + 33 => 'Instrumental', + 34 => 'Acid', + 35 => 'House', + 36 => 'Game', + 37 => 'Sound Clip', + 38 => 'Gospel', + 39 => 'Noise', + 40 => 'Alt. Rock', # (was AlternRock) + 41 => 'Bass', + 42 => 'Soul', + 43 => 'Punk', + 44 => 'Space', + 45 => 'Meditative', + 46 => 'Instrumental Pop', + 47 => 'Instrumental Rock', + 48 => 'Ethnic', + 49 => 'Gothic', + 50 => 'Darkwave', + 51 => 'Techno-Industrial', + 52 => 'Electronic', + 53 => 'Pop-Folk', + 54 => 'Eurodance', + 55 => 'Dream', + 56 => 'Southern Rock', + 57 => 'Comedy', + 58 => 'Cult', + 59 => 'Gangsta Rap', # (was Gansta) + 60 => 'Top 40', + 61 => 'Christian Rap', + 62 => 'Pop/Funk', + 63 => 'Jungle', + 64 => 'Native American', + 65 => 'Cabaret', + 66 => 'New Wave', + 67 => 'Psychedelic', # (was misspelt) + 68 => 'Rave', + 69 => 'Showtunes', + 70 => 'Trailer', + 71 => 'Lo-Fi', + 72 => 'Tribal', + 73 => 'Acid Punk', + 74 => 'Acid Jazz', + 75 => 'Polka', + 76 => 'Retro', + 77 => 'Musical', + 78 => 'Rock & Roll', + 79 => 'Hard Rock', + # The following genres are Winamp extensions + 80 => 'Folk', + 81 => 'Folk-Rock', + 82 => 'National Folk', + 83 => 'Swing', + 84 => 'Fast-Fusion', # (was Fast Fusion) + 85 => 'Bebop', # (was misspelt) + 86 => 'Latin', + 87 => 'Revival', + 88 => 'Celtic', + 89 => 'Bluegrass', + 90 => 'Avantgarde', + 91 => 'Gothic Rock', + 92 => 'Progressive Rock', + 93 => 'Psychedelic Rock', + 94 => 'Symphonic Rock', + 95 => 'Slow Rock', + 96 => 'Big Band', + 97 => 'Chorus', + 98 => 'Easy Listening', + 99 => 'Acoustic', + 100 => 'Humour', + 101 => 'Speech', + 102 => 'Chanson', + 103 => 'Opera', + 104 => 'Chamber Music', + 105 => 'Sonata', + 106 => 'Symphony', + 107 => 'Booty Bass', + 108 => 'Primus', + 109 => 'Porn Groove', + 110 => 'Satire', + 111 => 'Slow Jam', + 112 => 'Club', + 113 => 'Tango', + 114 => 'Samba', + 115 => 'Folklore', + 116 => 'Ballad', + 117 => 'Power Ballad', + 118 => 'Rhythmic Soul', + 119 => 'Freestyle', + 120 => 'Duet', + 121 => 'Punk Rock', + 122 => 'Drum Solo', + 123 => 'A Cappella', # (was Acapella) + 124 => 'Euro-House', + 125 => 'Dance Hall', + # ref http://yar.hole.ru/MP3Tech/lamedoc/id3.html + 126 => 'Goa', + 127 => 'Drum & Bass', + 128 => 'Club-House', + 129 => 'Hardcore', + 130 => 'Terror', + 131 => 'Indie', + 132 => 'BritPop', + 133 => 'Afro-Punk', # (was Negerpunk) + 134 => 'Polsk Punk', + 135 => 'Beat', + 136 => 'Christian Gangsta Rap', # (was Christian Gangsta) + 137 => 'Heavy Metal', + 138 => 'Black Metal', + 139 => 'Crossover', + 140 => 'Contemporary Christian', # (was Contemporary C) + 141 => 'Christian Rock', + 142 => 'Merengue', + 143 => 'Salsa', + 144 => 'Thrash Metal', + 145 => 'Anime', + 146 => 'JPop', + 147 => 'Synthpop', # (was SynthPop) + # ref http://alicja.homelinux.com/~mats/text/Music/MP3/ID3/Genres.txt + # (also used to update some Genres above) + 148 => 'Abstract', + 149 => 'Art Rock', + 150 => 'Baroque', + 151 => 'Bhangra', + 152 => 'Big Beat', + 153 => 'Breakbeat', + 154 => 'Chillout', + 155 => 'Downtempo', + 156 => 'Dub', + 157 => 'EBM', + 158 => 'Eclectic', + 159 => 'Electro', + 160 => 'Electroclash', + 161 => 'Emo', + 162 => 'Experimental', + 163 => 'Garage', + 164 => 'Global', + 165 => 'IDM', + 166 => 'Illbient', + 167 => 'Industro-Goth', + 168 => 'Jam Band', + 169 => 'Krautrock', + 170 => 'Leftfield', + 171 => 'Lounge', + 172 => 'Math Rock', + 173 => 'New Romantic', + 174 => 'Nu-Breakz', + 175 => 'Post-Punk', + 176 => 'Post-Rock', + 177 => 'Psytrance', + 178 => 'Shoegaze', + 179 => 'Space Rock', + 180 => 'Trop Rock', + 181 => 'World Music', + 182 => 'Neoclassical', + 183 => 'Audiobook', + 184 => 'Audio Theatre', + 185 => 'Neue Deutsche Welle', + 186 => 'Podcast', + 187 => 'Indie Rock', + 188 => 'G-Funk', + 189 => 'Dubstep', + 190 => 'Garage Rock', + 191 => 'Psybient', + 255 => 'None', + # ID3v2 adds some text short forms... + CR => 'Cover', + RX => 'Remix', +); + +# Tags for ID3v1 +%Image::ExifTool::ID3::v1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 1 => 'ID3v1', 2 => 'Audio' }, + PRIORITY => 0, # let ID3v2 tags replace these if they come later + 3 => { + Name => 'Title', + Format => 'string[30]', + ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)', + }, + 33 => { + Name => 'Artist', + Groups => { 2 => 'Author' }, + Format => 'string[30]', + ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)', + }, + 63 => { + Name => 'Album', + Format => 'string[30]', + ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)', + }, + 93 => { + Name => 'Year', + Groups => { 2 => 'Time' }, + Format => 'string[4]', + }, + 97 => { + Name => 'Comment', + Format => 'string[30]', + ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)', + }, + 125 => { # ID3v1.1 (ref http://en.wikipedia.org/wiki/ID3#Layout) + Name => 'Track', + Format => 'int8u[2]', + Notes => 'v1.1 addition -- last 2 bytes of v1.0 Comment field', + RawConv => '($val =~ s/^0 // and $val) ? $val : undef', + }, + 127 => { + Name => 'Genre', + Notes => 'CR and RX are ID3v2 only', + Format => 'int8u', + PrintConv => \%genre, + PrintConvColumns => 3, + }, +); + +# ID3v1 "Enhanced TAG" information (ref 3) +%Image::ExifTool::ID3::v1_Enh = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 1 => 'ID3v1_Enh', 2 => 'Audio' }, + NOTES => 'ID3 version 1 "Enhanced TAG" information (not part of the official spec).', + PRIORITY => 0, # let ID3v2 tags replace these if they come later + 4 => { + Name => 'Title2', + Format => 'string[60]', + ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)', + }, + 64 => { + Name => 'Artist2', + Groups => { 2 => 'Author' }, + Format => 'string[60]', + ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)', + }, + 124 => { + Name => 'Album2', + Format => 'string[60]', + ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)', + }, + 184 => { + Name => 'Speed', + Format => 'int8u', + PrintConv => { + 1 => 'Slow', + 2 => 'Medium', + 3 => 'Fast', + 4 => 'Hardcore', + }, + }, + 185 => { + Name => 'Genre', + Format => 'string[30]', + ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)', + }, + 215 => { + Name => 'StartTime', + Format => 'string[6]', + }, + 221 => { + Name => 'EndTime', + Format => 'string[6]', + }, +); + +# Tags for ID2v2.2 +%Image::ExifTool::ID3::v2_2 = ( + PROCESS_PROC => \&Image::ExifTool::ID3::ProcessID3v2, + GROUPS => { 1 => 'ID3v2_2', 2 => 'Audio' }, + NOTES => q{ + ExifTool extracts mainly text-based tags from ID3v2 information. The tags + in the tables below are those extracted by ExifTool, and don't represent a + complete list of available ID3v2 tags. + + ID3 version 2.2 tags. (These are the tags written by iTunes 5.0.) + }, + CNT => 'PlayCounter', + COM => 'Comment', + IPL => 'InvolvedPeople', + PIC => { + Name => 'Picture', + Groups => { 2 => 'Preview' }, + Binary => 1, + Notes => 'the 3 tags below are also extracted from this PIC frame', + }, + 'PIC-1' => { Name => 'PictureFormat', Groups => { 2 => 'Image' } }, + 'PIC-2' => { + Name => 'PictureType', + Groups => { 2 => 'Image' }, + PrintConv => \%pictureType, + SeparateTable => 1, + }, + 'PIC-3' => { Name => 'PictureDescription', Groups => { 2 => 'Image' } }, + POP => { + Name => 'Popularimeter', + PrintConv => '$val=~s/^(.*?) (\d+) (\d+)$/$1 Rating=$2 Count=$3/s; $val', + }, + SLT => { + Name => 'SynLyrics', + SubDirectory => { TagTable => 'Image::ExifTool::ID3::SynLyrics' }, + }, + TAL => 'Album', + TBP => 'BeatsPerMinute', + TCM => 'Composer', + TCO =>{ + Name => 'Genre', + Notes => 'uses same lookup table as ID3v1 Genre', + PrintConv => 'Image::ExifTool::ID3::PrintGenre($val)', + }, + TCP => { Name => 'Compilation', PrintConv => { 0 => 'No', 1 => 'Yes' } }, # iTunes + TCR => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + TDA => { Name => 'Date', Groups => { 2 => 'Time' } }, + TDY => 'PlaylistDelay', + TEN => 'EncodedBy', + TFT => 'FileType', + TIM => { Name => 'Time', Groups => { 2 => 'Time' } }, + TKE => 'InitialKey', + TLA => 'Language', + TLE => 'Length', + TMT => 'Media', + TOA => { Name => 'OriginalArtist', Groups => { 2 => 'Author' } }, + TOF => 'OriginalFileName', + TOL => 'OriginalLyricist', + TOR => 'OriginalReleaseYear', + TOT => 'OriginalAlbum', + TP1 => { Name => 'Artist', Groups => { 2 => 'Author' } }, + TP2 => 'Band', + TP3 => 'Conductor', + TP4 => 'InterpretedBy', + TPA => 'PartOfSet', + TPB => 'Publisher', + TRC => 'ISRC', # (international standard recording code) + TRD => 'RecordingDates', + TRK => 'Track', + TSI => 'Size', + TSS => 'EncoderSettings', + TT1 => 'Grouping', + TT2 => 'Title', + TT3 => 'Subtitle', + TXT => 'Lyricist', + TXX => 'UserDefinedText', + TYE => { Name => 'Year', Groups => { 2 => 'Time' } }, + ULT => 'Lyrics', + WAF => 'FileURL', + WAR => { Name => 'ArtistURL', Groups => { 2 => 'Author' } }, + WAS => 'SourceURL', + WCM => 'CommercialURL', + WCP => { Name => 'CopyrightURL', Groups => { 2 => 'Author' } }, + WPB => 'PublisherURL', + WXX => 'UserDefinedURL', + # the following written by iTunes 10.5 (ref PH) + RVA => 'RelativeVolumeAdjustment', + TST => 'TitleSortOrder', + TSA => 'AlbumSortOrder', + TSP => 'PerformerSortOrder', + TS2 => 'AlbumArtistSortOrder', + TSC => 'ComposerSortOrder', + ITU => { Name => 'iTunesU', Description => 'iTunes U', Binary => 1, Unknown => 1 }, + PCS => { Name => 'Podcast', Binary => 1, Unknown => 1 }, +); + +# tags common to ID3v2.3 and ID3v2.4 +my %id3v2_common = ( + # AENC => 'AudioEncryption', # Owner, preview start, preview length, encr data + APIC => { + Name => 'Picture', + Groups => { 2 => 'Preview' }, + Binary => 1, + Notes => 'the 3 tags below are also extracted from this APIC frame', + }, + 'APIC-1' => { Name => 'PictureMIMEType', Groups => { 2 => 'Image' } }, + 'APIC-2' => { + Name => 'PictureType', + Groups => { 2 => 'Image' }, + PrintConv => \%pictureType, + SeparateTable => 1, + }, + 'APIC-3' => { Name => 'PictureDescription', Groups => { 2 => 'Image' } }, + COMM => 'Comment', + # COMR => 'Commercial', + # ENCR => 'EncryptionMethod', + # ETCO => 'EventTimingCodes', + # GEOB => 'GeneralEncapsulatedObject', + # GRID => 'GroupIdentification', + # LINK => 'LinkedInformation', + MCDI => { Name => 'MusicCDIdentifier', Binary => 1 }, + # MLLT => 'MPEGLocationLookupTable', + OWNE => 'Ownership', + PCNT => 'PlayCounter', + POPM => { + Name => 'Popularimeter', + PrintConv => '$val=~s/^(.*?) (\d+) (\d+)$/$1 Rating=$2 Count=$3/s; $val', + }, + # POSS => 'PostSynchronization', + PRIV => { + Name => 'Private', + SubDirectory => { TagTable => 'Image::ExifTool::ID3::Private' }, + }, + # RBUF => 'RecommendedBufferSize', + # RVRB => 'Reverb', + SYLT => { + Name => 'SynLyrics', + SubDirectory => { TagTable => 'Image::ExifTool::ID3::SynLyrics' }, + }, + # SYTC => 'SynchronizedTempoCodes', + TALB => 'Album', + TBPM => 'BeatsPerMinute', + TCMP => { Name => 'Compilation', PrintConv => { 0 => 'No', 1 => 'Yes' } }, #PH (iTunes) + TCOM => 'Composer', + TCON =>{ + Name => 'Genre', + Notes => 'uses same lookup table as ID3v1 Genre', + PrintConv => 'Image::ExifTool::ID3::PrintGenre($val)', + }, + TCOP => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + TDLY => 'PlaylistDelay', + TENC => 'EncodedBy', + TEXT => 'Lyricist', + TFLT => 'FileType', + TIT1 => 'Grouping', + TIT2 => 'Title', + TIT3 => 'Subtitle', + TKEY => 'InitialKey', + TLAN => 'Language', + TLEN => { + Name => 'Length', + ValueConv => '$val / 1000', + PrintConv => '"$val s"', + }, + TMED => 'Media', + TOAL => 'OriginalAlbum', + TOFN => 'OriginalFileName', + TOLY => 'OriginalLyricist', + TOPE => { Name => 'OriginalArtist', Groups => { 2 => 'Author' } }, + TOWN => 'FileOwner', + TPE1 => { Name => 'Artist', Groups => { 2 => 'Author' } }, + TPE2 => 'Band', + TPE3 => 'Conductor', + TPE4 => 'InterpretedBy', + TPOS => 'PartOfSet', + TPUB => 'Publisher', + TRCK => 'Track', + TRSN => 'InternetRadioStationName', + TRSO => 'InternetRadioStationOwner', + TSRC => 'ISRC', # (international standard recording code) + TSSE => 'EncoderSettings', + TXXX => 'UserDefinedText', + # UFID => 'UniqueFileID', (not extracted because it is long and nasty and not very useful) + USER => 'TermsOfUse', + USLT => 'Lyrics', + WCOM => 'CommercialURL', + WCOP => 'CopyrightURL', + WOAF => 'FileURL', + WOAR => { Name => 'ArtistURL', Groups => { 2 => 'Author' } }, + WOAS => 'SourceURL', + WORS => 'InternetRadioStationURL', + WPAY => 'PaymentURL', + WPUB => 'PublisherURL', + WXXX => 'UserDefinedURL', +# +# non-standard frames +# + # the following are written by iTunes 10.5 (ref PH) + TSO2 => 'AlbumArtistSortOrder', + TSOC => 'ComposerSortOrder', + ITNU => { Name => 'iTunesU', Description => 'iTunes U', Binary => 1, Unknown => 1 }, + PCST => { Name => 'Podcast', Binary => 1, Unknown => 1 }, + # other proprietary Apple tags (ref http://help.mp3tag.de/main_tags.html) + TDES => 'PodcastDescription', + TGID => 'PodcastID', + WFED => 'PodcastURL', + TKWD => 'PodcastKeywords', + TCAT => 'PodcastCategory', + # more non-standard tags (ref http://eyed3.nicfit.net/compliance.html) + # NCON - unknown MusicMatch binary data + XDOR => { Name => 'OriginalReleaseTime',Groups => { 2 => 'Time' }, %dateTimeConv }, + XSOA => 'AlbumSortOrder', + XSOP => 'PerformerSortOrder', + XSOT => 'TitleSortOrder', + XOLY => { + Name => 'OlympusDSS', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::DSS' }, + }, + GRP1 => 'Grouping', + MVNM => 'MovementName', # (NC) + MVIN => 'MovementNumber', # (NC) +); + +# Tags for ID3v2.3 (http://www.id3.org/id3v2.3.0) +%Image::ExifTool::ID3::v2_3 = ( + PROCESS_PROC => \&Image::ExifTool::ID3::ProcessID3v2, + GROUPS => { 1 => 'ID3v2_3', 2 => 'Audio' }, + NOTES => q{ + ID3 version 2.3 tags. Includes some non-standard tags written by other + software. + }, + %id3v2_common, # include common tags + # EQUA => 'Equalization', + IPLS => 'InvolvedPeople', + # RVAD => 'RelativeVolumeAdjustment', + TDAT => { Name => 'Date', Groups => { 2 => 'Time' } }, + TIME => { Name => 'Time', Groups => { 2 => 'Time' } }, + TORY => 'OriginalReleaseYear', + TRDA => 'RecordingDates', + TSIZ => 'Size', + TYER => { Name => 'Year', Groups => { 2 => 'Time' } }, +); + +# Tags for ID3v2.4 (http://www.id3.org/id3v2.4.0-frames) +%Image::ExifTool::ID3::v2_4 = ( + PROCESS_PROC => \&Image::ExifTool::ID3::ProcessID3v2, + GROUPS => { 1 => 'ID3v2_4', 2 => 'Audio' }, + NOTES => q{ + ID3 version 2.4 tags. Includes some non-standard tags written by other + software. + }, + %id3v2_common, # include common tags + # EQU2 => 'Equalization', + RVA2 => 'RelativeVolumeAdjustment', + # SEEK => 'Seek', + # SIGN => 'Signature', + TDEN => { Name => 'EncodingTime', Groups => { 2 => 'Time' }, %dateTimeConv }, + TDOR => { Name => 'OriginalReleaseTime',Groups => { 2 => 'Time' }, %dateTimeConv }, + TDRC => { Name => 'RecordingTime', Groups => { 2 => 'Time' }, %dateTimeConv }, + TDRL => { Name => 'ReleaseTime', Groups => { 2 => 'Time' }, %dateTimeConv }, + TDTG => { Name => 'TaggingTime', Groups => { 2 => 'Time' }, %dateTimeConv }, + TIPL => 'InvolvedPeople', + TMCL => 'MusicianCredits', + TMOO => 'Mood', + TPRO => 'ProducedNotice', + TSOA => 'AlbumSortOrder', + TSOP => 'PerformerSortOrder', + TSOT => 'TitleSortOrder', + TSST => 'SetSubtitle', +); + +# Synchronized lyrics/text +%Image::ExifTool::ID3::SynLyrics = ( + GROUPS => { 1 => 'ID3', 2 => 'Audio' }, + VARS => { NO_ID => 1 }, + PROCESS_PROC => \&ProcessSynText, + NOTES => 'The following tags are extracted from synchronized lyrics/text frames.', + desc => { Name => 'SynchronizedLyricsDescription' }, + type => { + Name => 'SynchronizedLyricsType', + PrintConv => { + 0 => 'Other', + 1 => 'Lyrics', + 2 => 'Text Transcription', + 3 => 'Movement/part Name', + 4 => 'Events', + 5 => 'Chord', + 6 => 'Trivia/"pop-up" Information', + 7 => 'Web Page URL', + 8 => 'Image URL', + }, + }, + text => { + Name => 'SynchronizedLyricsText', + List => 1, + Notes => q{ + each list item has a leading time stamp in square brackets. Time stamps may + be in seconds with format [MM:SS.ss], or MPEG frames with format [FFFF], + depending on how this information was stored + }, + PrintConv => \&ConvertTimeStamp, + }, +); + +# ID3 PRIV tags (ref PH) +%Image::ExifTool::ID3::Private = ( + PROCESS_PROC => \&Image::ExifTool::ID3::ProcessPrivate, + GROUPS => { 1 => 'ID3', 2 => 'Audio' }, + VARS => { NO_ID => 1 }, + NOTES => q{ + ID3 private (PRIV) tags. ExifTool will decode any private tags found, even + if they do not appear in this table. + }, + XMP => { + SubDirectory => { + DirName => 'XMP', + TagTable => 'Image::ExifTool::XMP::Main', + }, + }, + PeakValue => { + ValueConv => 'length($val)==4 ? unpack("V",$val) : \$val', + }, + AverageLevel => { + ValueConv => 'length($val)==4 ? unpack("V",$val) : \$val', + }, + # Windows Media attributes ("/" in tag ID is converted to "_" by ProcessPrivate) + WM_WMContentID => { + Name => 'WM_ContentID', + ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)', + }, + WM_WMCollectionID => { + Name => 'WM_CollectionID', + ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)', + }, + WM_WMCollectionGroupID => { + Name => 'WM_CollectionGroupID', + ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)', + }, + WM_MediaClassPrimaryID => { + ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)', + }, + WM_MediaClassSecondaryID => { + ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)', + }, + WM_Provider => { + ValueConv => '$self->Decode($val,"UCS2","II")', #PH (NC) + }, + # there are lots more WM tags that could be decoded if I had samples or documentation - PH + # WM/AlbumArtist + # WM/AlbumTitle + # WM/Category + # WM/Composer + # WM/Conductor + # WM/ContentDistributor + # WM/ContentGroupDescription + # WM/EncodingTime + # WM/Genre + # WM/GenreID + # WM/InitialKey + # WM/Language + # WM/Lyrics + # WM/MCDI + # WM/MediaClassPrimaryID + # WM/MediaClassSecondaryID + # WM/Mood + # WM/ParentalRating + # WM/Period + # WM/ProtectionType + # WM/Provider + # WM/ProviderRating + # WM/ProviderStyle + # WM/Publisher + # WM/SubscriptionContentID + # WM/SubTitle + # WM/TrackNumber + # WM/UniqueFileIdentifier + # WM/WMCollectionGroupID + # WM/WMCollectionID + # WM/WMContentID + # WM/Writer + # WM/Year +); + +# lookup to check for existence of tags in other ID3 versions +my %otherTable = ( + \%Image::ExifTool::ID3::v2_4 => \%Image::ExifTool::ID3::v2_3, + \%Image::ExifTool::ID3::v2_3 => \%Image::ExifTool::ID3::v2_4, +); + +# ID3 Composite tags +%Image::ExifTool::ID3::Composite = ( + GROUPS => { 2 => 'Image' }, + DateTimeOriginal => { + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Priority => 0, + Desire => { + 0 => 'ID3:RecordingTime', + 1 => 'ID3:Year', + 2 => 'ID3:Date', + 3 => 'ID3:Time', + }, + ValueConv => q{ + return $val[0] if $val[0]; + return undef unless $val[1]; + return $val[1] unless $val[2] and $val[2] =~ /^(\d{2})(\d{2})$/; + $val[1] .= ":$1:$2"; + return $val[1] unless $val[3] and $val[3] =~ /^(\d{2})(\d{2})$/; + return "$val[1] $1:$2"; + }, + PrintConv => '$self->ConvertDateTime($val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::ID3'); + +# can't share tagInfo hashes between two tables, so we must make +# copies of the necessary hashes +{ + my $tag; + foreach $tag (keys %id3v2_common) { + next unless ref $id3v2_common{$tag} eq 'HASH'; + my %tagInfo = %{$id3v2_common{$tag}}; + # must also copy Groups hash if it exists + my $groups = $tagInfo{Groups}; + $tagInfo{Groups} = { %$groups } if $groups; + $Image::ExifTool::ID3::v2_4{$tag} = \%tagInfo; + } +} + +#------------------------------------------------------------------------------ +# Convert ID3v1 text to exiftool character set +# Inputs: 0) ExifTool object ref, 1) text string +# Returns: converted text +sub ConvertID3v1Text($$) +{ + my ($et, $val) = @_; + return $et->Decode($val, $et->Options('CharsetID3')); +} + +#------------------------------------------------------------------------------ +# Re-format time stamp in synchronized lyrics +# Inputs: 0) synchronized lyrics entry (eg. "[84.030]Da do do do") +# Returns: entry with formatted timestamp (eg. "[01:24.03]Da do do do") +sub ConvertTimeStamp($) +{ + my $val = shift; + # do nothing if this isn't a time stamp (frame count doesn't contain a decimal) + return $val unless $val =~ /^\[(\d+\.\d+)\]/g; + my $time = $1; + # print hours only if more than 60 minutes + my $h = int($time / 3600); + if ($h) { + $time -= $h * 3600; + $h = "$h:"; + } else { + $h = ''; + } + my $m = int($time / 60); + my $s = $time - $m * 60; + my $ss = sprintf('%05.2f', $s); + if ($ss >= 60) { + $ss = '00.00'; + ++$m >= 60 and $m -= 60, ++$h; + } + return sprintf('[%s%.2d:%s]', $h, $m, $ss) . substr($val, pos($val)); +} + +#------------------------------------------------------------------------------ +# Process ID3 synchronized lyrics/text +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +sub ProcessSynText($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + + $et->VerboseDir('SynLyrics', 0, length $$dataPt); + return unless length $$dataPt > 6; + + my ($enc,$lang,$timeCode,$type) = unpack('Ca3CC', $$dataPt); + $lang = lc $lang; + undef $lang if $lang !~ /^[a-z]{3}$/ or $lang eq 'eng'; + pos($$dataPt) = 6; + my ($termLen, $pat); + if ($enc == 1 or $enc == 2) { + $$dataPt =~ /\G(..)*?\0\0/sg or return; + $termLen = 2; + $pat = '\G(?:..)*?\0\0(....)'; + } else { + $$dataPt =~ /\0/g or return; + $termLen = 1; + $pat = '\0(....)'; + } + my $desc = substr($$dataPt, 6, pos($$dataPt) - 6 - $termLen); + $desc = DecodeString($et, $desc, $enc); + + my $tagInfo = $et->GetTagInfo($tagTablePtr, 'desc'); + $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang) if $lang; + $et->HandleTag($tagTablePtr, 'type', $type); + $et->HandleTag($tagTablePtr, 'desc', $desc, TagInfo => $tagInfo); + $tagInfo = $et->GetTagInfo($tagTablePtr, 'text'); + $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang) if $lang; + + for (;;) { + my $pos = pos $$dataPt; + last unless $$dataPt =~ /$pat/sg; + my $time = unpack('N', $1); + my $text = substr($$dataPt, $pos, pos($$dataPt) - $pos - 4 - $termLen); + $text = DecodeString($et, $text, $enc); + my $timeStr; + if ($timeCode == 2) { # time in ms + $timeStr = sprintf('%.3f', $time / 1000); + } else { # time in MPEG frames + $timeStr = sprintf('%.4d', $time); + $timeStr .= '?' if $timeCode != 1; + } + $et->HandleTag($tagTablePtr, 'text', "[$timeStr]$text", TagInfo => $tagInfo); + } +} + +#------------------------------------------------------------------------------ +# Process ID3 PRIV data +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +sub ProcessPrivate($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my ($tag, $start); + $et->VerboseDir('PRIV', 0, length $$dataPt); + if ($$dataPt =~ /^(.*?)\0/s) { + $tag = $1; + $start = length($tag) + 1; + } else { + $tag = ''; + $start = 0; + } + unless ($$tagTablePtr{$tag}) { + $tag =~ tr{/ }{_}d; # translate '/' to '_' and remove spaces + $tag = 'private' unless $tag =~ /^[-\w]{1,24}$/; + unless ($$tagTablePtr{$tag}) { + AddTagToTable($tagTablePtr, $tag, + { Name => ucfirst($tag), Binary => 1 }); + } + } + my $key = $et->HandleTag($tagTablePtr, $tag, undef, + Size => length($$dataPt) - $start, + Start => $start, + DataPt => $dataPt, + ); + # set group1 name + $et->SetGroup($key, $$et{ID3_Ver}) if $key; +} + +#------------------------------------------------------------------------------ +# Print ID3v2 Genre +# Inputs: TCON or TCO frame data +# Returns: Content type with decoded genre numbers +sub PrintGenre($) +{ + my $val = shift; + # make sure that %genre has an entry for all numbers we are interested in + # (genre numbers are in brackets for ID3v2.2 and v2.3) + while ($val =~ /\((\d+)\)/g) { + $genre{$1} or $genre{$1} = "Unknown ($1)"; + } + # (genre numbers are separated by nulls in ID3v2.4, + # but nulls are converted to '/' by DecodeString()) + while ($val =~ /(?:^|\/)(\d+)(\/|$)/g) { + $genre{$1} or $genre{$1} = "Unknown ($1)"; + } + $val =~ s/\((\d+)\)/\($genre{$1}\)/g; + $val =~ s/(^|\/)(\d+)(?=\/|$)/$1$genre{$2}/g; + $val =~ s/^\(([^)]+)\)\1?$/$1/; # clean up by removing brackets and duplicates + return $val; +} + +#------------------------------------------------------------------------------ +# Get Genre ID +# Inputs: 0) Genre name +# Returns: genre ID number, or undef +sub GetGenreID($) +{ + return Image::ExifTool::ReverseLookup(shift, \%genre); +} + +#------------------------------------------------------------------------------ +# Decode ID3 string +# Inputs: 0) ExifTool object reference +# 1) string beginning with encoding byte unless specified as argument +# 2) optional encoding (0=ISO-8859-1, 1=UTF-16 BOM, 2=UTF-16BE, 3=UTF-8) +# Returns: Decoded string in scalar context, or list of strings in list context +sub DecodeString($$;$) +{ + my ($et, $val, $enc) = @_; + return '' unless length $val; + unless (defined $enc) { + $enc = unpack('C', $val); + $val = substr($val, 1); # remove encoding byte + } + my @vals; + if ($enc == 0 or $enc == 3) { # ISO 8859-1 or UTF-8 + $val =~ s/\0+$//; # remove any null padding + # (must split before converting because conversion routines truncate at null) + @vals = split "\0", $val; + foreach $val (@vals) { + $val = $et->Decode($val, $enc ? 'UTF8' : 'Latin'); + } + } elsif ($enc == 1 or $enc == 2) { # UTF-16 with BOM, or UTF-16BE + my $bom = "\xfe\xff"; + my %order = ( "\xfe\xff" => 'MM', "\xff\xfe", => 'II' ); + for (;;) { + my $v; + # split string at null terminators on word boundaries + if ($val =~ s/((..)*?)\0\0//s) { + $v = $1; + } else { + last unless length $val > 1; + $v = $val; + $val = ''; + } + $bom = $1 if $v =~ s/^(\xfe\xff|\xff\xfe)//; + push @vals, $et->Decode($v, 'UCS2', $order{$bom}); + } + } else { + $val =~ s/\0+$//; + return "<Unknown encoding $enc> $val"; + } + return @vals if wantarray; + return join('/',@vals); +} + +#------------------------------------------------------------------------------ +# Convert sync-safe integer to a number we can use +# Inputs: 0) int32u sync-safe value +# Returns: actual number or undef on invalid value +sub UnSyncSafe($) +{ + my $val = shift; + return undef if $val & 0x80808080; + return ($val & 0x0000007f) | + (($val & 0x00007f00) >> 1) | + (($val & 0x007f0000) >> 2) | + (($val & 0x7f000000) >> 3); +} + +#------------------------------------------------------------------------------ +# Process ID3v2 information +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +sub ProcessID3v2($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $offset = $$dirInfo{DirStart}; + my $size = $$dirInfo{DirLen}; + my $vers = $$dirInfo{Version}; + my $verbose = $et->Options('Verbose'); + my $len; # frame data length + + $et->VerboseDir($tagTablePtr->{GROUPS}->{1}, 0, $size); + $et->VerboseDump($dataPt, Len => $size, Start => $offset); + + for (;;$offset+=$len) { + my ($id, $flags, $hi); + if ($vers < 0x0300) { + # version 2.2 frame header is 6 bytes + last if $offset + 6 > $size; + ($id, $hi, $len) = unpack("x${offset}a3Cn",$$dataPt); + last if $id eq "\0\0\0"; + $len += $hi << 16; + $offset += 6; + } else { + # version 2.3/2.4 frame header is 10 bytes + last if $offset + 10 > $size; + ($id, $len, $flags) = unpack("x${offset}a4Nn",$$dataPt); + last if $id eq "\0\0\0\0"; + $offset += 10; + # length is a "sync-safe" integer by the ID3v2.4 specification, but + # reportedly some versions of iTunes write this as a normal integer + # (ref http://www.id3.org/iTunes) + while ($vers >= 0x0400 and $len > 0x7f and not $len & 0x80808080) { + my $oldLen = $len; + $len = UnSyncSafe($len); + if (not defined $len or $offset + $len + 10 > $size) { + $et->Warn('Invalid ID3 frame size'); + last; + } + # check next ID to see if it makes sense + my $nextID = substr($$dataPt, $offset + $len, 4); + last if $$tagTablePtr{$nextID}; + # try again with the incorrect length word (patch for iTunes bug) + last if $offset + $oldLen + 10 > $size; + $nextID = substr($$dataPt, $offset + $len, 4); + $len = $oldLen if $$tagTablePtr{$nextID}; + last; # yes, "while" was really a "goto" in disguise + } + } + last if $offset + $len > $size; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $id); + unless ($tagInfo) { + my $otherTable = $otherTable{$tagTablePtr}; + $tagInfo = $et->GetTagInfo($otherTable, $id) if $otherTable; + if ($tagInfo) { + $et->WarnOnce("Frame '${id}' is not valid for this ID3 version", 1); + } else { + next unless $verbose or $et->Options('Unknown'); + $id =~ tr/-A-Za-z0-9_//dc; + $id = 'unknown' unless length $id; + unless ($$tagTablePtr{$id}) { + $tagInfo = { Name => "ID3_$id", Binary => 1 }; + AddTagToTable($tagTablePtr, $id, $tagInfo); + } + } + } + # decode v2.3 and v2.4 flags + my (%flags, %extra); + if ($flags) { + if ($vers < 0x0400) { + # version 2.3 flags + $flags & 0x80 and $flags{Compress} = 1; + $flags & 0x40 and $flags{Encrypt} = 1; + $flags & 0x20 and $flags{GroupID} = 1; + } else { + # version 2.4 flags + $flags & 0x40 and $flags{GroupID} = 1; + $flags & 0x08 and $flags{Compress} = 1; + $flags & 0x04 and $flags{Encrypt} = 1; + $flags & 0x02 and $flags{Unsync} = 1; + $flags & 0x01 and $flags{DataLen} = 1; + } + } + if ($flags{Encrypt}) { + $et->WarnOnce('Encrypted frames currently not supported'); + next; + } + # extract the value + my $val = substr($$dataPt, $offset, $len); + + # reverse the unsynchronization + $val =~ s/\xff\x00/\xff/g if $flags{Unsync}; + + # read grouping identity + if ($flags{GroupID}) { + length($val) >= 1 or $et->Warn("Short $id frame"), next; + $val = substr($val, 1); # (ignore it) + } + # read data length + my $dataLen; + if ($flags{DataLen} or $flags{Compress}) { + length($val) >= 4 or $et->Warn("Short $id frame"), next; + $dataLen = unpack('N', $val); # save the data length word + $val = substr($val, 4); + } + # uncompress data + if ($flags{Compress}) { + if (eval { require Compress::Zlib }) { + my $inflate = Compress::Zlib::inflateInit(); + my ($buff, $stat); + $inflate and ($buff, $stat) = $inflate->inflate($val); + if ($inflate and $stat == Compress::Zlib::Z_STREAM_END()) { + $val = $buff; + } else { + $et->Warn("Error inflating $id frame"); + next; + } + } else { + $et->WarnOnce('Install Compress::Zlib to decode compressed frames'); + next; + } + } + # validate data length + if (defined $dataLen) { + $dataLen = UnSyncSafe($dataLen); + defined $dataLen or $et->Warn("Invalid length for $id frame"), next; + $dataLen == length($val) or $et->Warn("Wrong length for $id frame"), next; + } + unless ($tagInfo) { + next unless $verbose; + %flags and $extra{Extra} = ', Flags=' . join(',', sort keys %flags); + $et->VerboseInfo($id, $tagInfo, + Table => $tagTablePtr, + Value => $val, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Size => $len, + Start => $offset, + %extra + ); + next; + } +# +# decode data in this frame (it is bad form to hard-code these, but the ID3 frame formats +# are so variable that it would be more work to define format types for each of them) +# + my $lang; + my $valLen = length($val); # actual value length (after decompression, etc) + if ($id =~ /^(TXX|TXXX)$/) { + # two encoded strings separated by a null + my @vals = DecodeString($et, $val); + foreach (0..1) { $vals[$_] = '' unless defined $vals[$_]; } + ($val = "($vals[0]) $vals[1]") =~ s/^\(\) //; + } elsif ($id =~ /^T/ or $id =~ /^(IPL|IPLS)$/) { + $val = DecodeString($et, $val); + } elsif ($id =~ /^(WXX|WXXX)$/) { + # one encoded string and one Latin string separated by a null + my $enc = unpack('C', $val); + my $url; + if ($enc == 1 or $enc == 2) { + ($val, $url) = ($val =~ /^(.(?:..)*?)\0\0(.*)/s); + } else { + ($val, $url) = ($val =~ /^(..*?)\0(.*)/s); + } + unless (defined $val and defined $url) { + $et->Warn("Invalid $id frame value"); + next; + } + $val = DecodeString($et, $val); + $url =~ s/\0.*//s; + $val = length($val) ? "($val) $url" : $url; + } elsif ($id =~ /^W/) { + $val =~ s/\0.*//s; # truncate at null + } elsif ($id =~ /^(COM|COMM|ULT|USLT)$/) { + $valLen > 4 or $et->Warn("Short $id frame"), next; + $lang = substr($val,1,3); + my @vals = DecodeString($et, substr($val,4), Get8u(\$val,0)); + foreach (0..1) { $vals[$_] = '' unless defined $vals[$_]; } + $val = length($vals[0]) ? "($vals[0]) $vals[1]" : $vals[1]; + } elsif ($id eq 'USER') { + $valLen > 4 or $et->Warn("Short $id frame"), next; + $lang = substr($val,1,3); + $val = DecodeString($et, substr($val,4), Get8u(\$val,0)); + } elsif ($id =~ /^(CNT|PCNT)$/) { + $valLen >= 4 or $et->Warn("Short $id frame"), next; + my ($cnt, @xtra) = unpack('NC*', $val); + $cnt = ($cnt << 8) + $_ foreach @xtra; + $val = $cnt; + } elsif ($id =~ /^(PIC|APIC)$/) { + $valLen >= 4 or $et->Warn("Short $id frame"), next; + my ($hdr, $attr); + my $enc = unpack('C', $val); + if ($enc == 1 or $enc == 2) { + $hdr = ($id eq 'PIC') ? ".(...)(.)((?:..)*?)\0\0" : ".(.*?)\0(.)((?:..)*?)\0\0"; + } else { + $hdr = ($id eq 'PIC') ? ".(...)(.)(.*?)\0" : ".(.*?)\0(.)(.*?)\0"; + } + # remove header (encoding, image format or MIME type, picture type, description) + $val =~ s/^$hdr//s or $et->Warn("Invalid $id frame"), next; + my @attrs = ($1, ord($2), DecodeString($et, $3, $enc)); + my $i = 1; + foreach $attr (@attrs) { + # must store descriptions even if they are empty to maintain + # sync between copy numbers when multiple images + $et->HandleTag($tagTablePtr, "$id-$i", $attr); + ++$i; + } + } elsif ($id eq 'POP' or $id eq 'POPM') { + # _email, 00, rating(1), counter(4-N) + my ($email, $dat) = ($val =~ /^([^\0]*)\0(.*)$/s); + unless (defined $dat and length($dat)) { + $et->Warn("Invalid $id frame"); + next; + } + my ($rating, @xtra) = unpack('C*', $dat); + my $cnt = 0; + $cnt = ($cnt << 8) + $_ foreach @xtra; + $val = "$email $rating $cnt"; + } elsif ($id eq 'OWNE') { + # enc(1), _price, 00, _date(8), Seller + my @strs = DecodeString($et, $val); + $strs[1] =~ s/^(\d{4})(\d{2})(\d{2})/$1:$2:$3 /s if $strs[1]; # format date + $val = "@strs"; + } elsif ($id eq 'RVA' or $id eq 'RVAD') { + my @dat = unpack('C*', $val); + my $flag = shift @dat; + my $bits = shift @dat or $et->Warn("Short $id frame"), next; + my $bytes = int(($bits + 7) / 8); + my @parse = (['Right',0,2,0x01],['Left',1,3,0x02],['Back-right',4,6,0x04], + ['Back-left',5,7,0x08],['Center',8,9,0x10],['Bass',10,11,0x20]); + $val = ''; + while (@parse) { + my $elem = shift @parse; + my $j = $$elem[2] * $bytes; + last if scalar(@dat) < $j + $bytes; + my $i = $$elem[1] * $bytes; + $val .= ', ' if $val; + my ($rel, $pk, $b); + for ($rel=0, $pk=0, $b=0; $b<$bytes; ++$b) { + $rel = $rel * 256 + $dat[$i + $b]; + $pk = $pk * 256 + $dat[$j + $b]; # (peak - not used in printout) + } + $rel =-$rel unless $flag & $$elem[3]; + $val .= sprintf("%+.1f%% %s", 100 * $rel / ((1<<$bits)-1), $$elem[0]); + } + } elsif ($id eq 'RVA2') { + my ($pos, $id) = $val=~/^([^\0]*)\0/s ? (length($1)+1, $1) : (1, ''); + my @vals; + while ($pos + 4 <= $valLen) { + my $type = Get8u(\$val, $pos); + my $str = ({ + 0 => 'Other', + 1 => 'Master', + 2 => 'Front-right', + 3 => 'Front-left', + 4 => 'Back-right', + 5 => 'Back-left', + 6 => 'Front-centre', + 7 => 'Back-centre', + 8 => 'Subwoofer', + }->{$type} || "Unknown($type)"); + my $db = Get16s(\$val,$pos+1) / 512; + # convert dB to percent as displayed by iTunes 10.5 + # (not sure why I need to divide by 20 instead of 10 as expected - PH) + push @vals, sprintf('%+.1f%% %s', 10**($db/20+2)-100, $str); + # step to next channel (ignoring peak volume) + $pos += 4 + int((Get8u(\$val,$pos+3) + 7) / 8); + } + $val = join ', ', @vals; + $val .= " ($id)" if $id; + } elsif ($id eq 'PRIV') { + # save version number to set group1 name for tag later + $$et{ID3_Ver} = $$tagTablePtr{GROUPS}{1}; + $et->HandleTag($tagTablePtr, $id, $val); + next; + } elsif ($$tagInfo{Format} or $$tagInfo{SubDirectory}) { + $et->HandleTag($tagTablePtr, $id, undef, DataPt => \$val); + next; + } elsif ($id eq 'GRP1' or $id eq 'MVNM' or $id eq 'MVIN') { + $val =~ s/(^\0+|\0+$)//g; # (PH guess) + } elsif (not $$tagInfo{Binary}) { + $et->Warn("Don't know how to handle $id frame"); + next; + } + if ($lang and $lang =~ /^[a-z]{3}$/i and $lang ne 'eng') { + $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, lc $lang); + } + %flags and $extra{Extra} = ', Flags=' . join(',', sort keys %flags); + $et->HandleTag($tagTablePtr, $id, $val, + TagInfo => $tagInfo, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Size => $len, + Start => $offset, + %extra + ); + } +} + +#------------------------------------------------------------------------------ +# Extract ID3 information from an audio file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this file didn't contain ID3 information +# - also processes audio data if any ID3 information was found +# - sets ExifTool DoneID3 to 1 when called, or to trailer size if an ID3v1 trailer exists +sub ProcessID3($$) +{ + my ($et, $dirInfo) = @_; + + return 0 if $$et{DoneID3}; # avoid infinite recursion + $$et{DoneID3} = 1; + + # allow this to be called with either RAF or DataPt + my $raf = $$dirInfo{RAF} || new File::RandomAccess($$dirInfo{DataPt}); + my ($buff, %id3Header, %id3Trailer, $hBuff, $tBuff, $eBuff, $tagTablePtr); + my $rtnVal = 0; + my $hdrEnd = 0; + my $id3Len = 0; + + # read first 3 bytes of file + $raf->Seek(0, 0); + return 0 unless $raf->Read($buff, 3) == 3; +# +# identify ID3v2 header +# + while ($buff =~ /^ID3/) { + $rtnVal = 1; + $raf->Read($hBuff, 7) == 7 or $et->Warn('Short ID3 header'), last; + my ($vers, $flags, $size) = unpack('nCN', $hBuff); + $size = UnSyncSafe($size); + defined $size or $et->Warn('Invalid ID3 header'), last; + my $verStr = sprintf("2.%d.%d", $vers >> 8, $vers & 0xff); + if ($vers >= 0x0500) { + $et->Warn("Unsupported ID3 version: $verStr"); + last; + } + unless ($raf->Read($hBuff, $size) == $size) { + $et->Warn('Truncated ID3 data'); + last; + } + # this flag only indicates use of unsynchronized frames in ID3v2.4 + if ($flags & 0x80 and $vers < 0x0400) { + # reverse the unsynchronization + $hBuff =~ s/\xff\x00/\xff/g; + } + my $pos = 10; + if ($flags & 0x40) { + # skip the extended header + $size >= 4 or $et->Warn('Bad ID3 extended header'), last; + my $len = UnSyncSafe(unpack('N', $hBuff)); + if ($len > length($hBuff)) { + $et->Warn('Truncated ID3 extended header'); + last; + } + $hBuff = substr($hBuff, $len); + $pos += $len; + } + if ($flags & 0x10) { + # ignore v2.4 footer (10 bytes long) + $raf->Seek(10, 1); + } + %id3Header = ( + DataPt => \$hBuff, + DataPos => $pos, + DirStart => 0, + DirLen => length($hBuff), + Version => $vers, + DirName => "ID3v$verStr", + ); + $id3Len += length($hBuff) + 10; + if ($vers >= 0x0400) { + $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v2_4'); + } elsif ($vers >= 0x0300) { + $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v2_3'); + } else { + $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v2_2'); + } + $hdrEnd = $raf->Tell(); + last; + } +# +# read ID3v1 trailer if it exists +# + my $trailSize = 0; + if ($raf->Seek(-128, 2) and $raf->Read($tBuff, 128) == 128 and $tBuff =~ /^TAG/) { + $trailSize = 128; + %id3Trailer = ( + DataPt => \$tBuff, + DataPos => $raf->Tell() - 128, + DirStart => 0, + DirLen => length($tBuff), + ); + $id3Len += length($tBuff); + $rtnVal = 1; + # load 'Enhanced TAG' information if available + my $eSize = 227; # size of ID3 Enhanced TAG info + if ($raf->Seek(-$trailSize - $eSize, 2) and $raf->Read($eBuff, $eSize) == $eSize and $eBuff =~ /^TAG+/) { + $id3Trailer{EnhancedTAG} = \$eBuff; + $trailSize += $eSize; + } + $$et{DoneID3} = $trailSize; # save trailer size + } +# +# read Lyrics3 trailer if it exists +# + if ($raf->Seek(-$trailSize-15, 2) and $raf->Read($buff, 15) == 15 and $buff =~ /^(.{6})LYRICS(END|200)$/) { + my $ver = $2; # Lyrics3 version ('END' for version 1) + my $len = ($ver eq 'END') ? 5100 : $1 + 15; # max Lyrics3 length + my $tbl = GetTagTable('Image::ExifTool::ID3::Lyrics3'); + $len = $raf->Tell() if $len > $raf->Tell(); + if ($raf->Seek(-$len, 1) and $raf->Read($buff, $len) == $len and $buff =~ /LYRICSBEGIN/g) { + my $pos = pos($buff); + $$et{DoneID3} = $trailSize + $len - $pos + 11; # update trailer length + my $oldIndent = $$et{INDENT}; + $$et{INDENT} .= '| '; + if ($et->Options('Verbose')) { + $et->VPrint(0, "Lyrics3:\n"); + $et->VerboseDir('Lyrics3', undef, $len); + if ($pos > 11) { + $buff = substr($buff, $pos - 11); + $pos = 11; + } + $et->VerboseDump(\$buff); + } + if ($ver eq 'END') { + # Lyrics3 v1.00 + my $val = substr($buff, $pos, $len - $pos - 9); + $et->HandleTag($tbl, 'LYR', $et->Decode($val, 'Latin')); + } else { + # Lyrics3 v2.00 + for (;;) { + # (note: the size field is 5 digits,, not 6 as per the documentation) + last unless $buff =~ /\G(.{3})(\d{5})/g; + my ($tag, $size) = ($1, $2); + $pos += 8; + last if $pos + $size > length($buff); + unless ($$tbl{$tag}) { + AddTagToTable($tbl, $tag, { Name => Image::ExifTool::MakeTagName("Lyrics3_$tag") }); + } + $et->HandleTag($tbl, $tag, $et->Decode(substr($buff, $pos, $size), 'Latin')); + $pos += $size; + pos($buff) = $pos; + } + $pos == length($buff) - 15 or $et->Warn('Malformed Lyrics3 v2.00 block'); + } + $$et{INDENT} = $oldIndent; + } else { + $et->Warn('Error reading Lyrics3 trailer'); + } + } +# +# process the information +# + if ($rtnVal) { + # first process audio data if it exists + if ($$dirInfo{RAF}) { + my $oldType = $$et{FILE_TYPE}; # save file type + # check current file type first + my @types = grep /^$oldType$/, @audioFormats; + push @types, grep(!/^$oldType$/, @audioFormats); + my $type; + foreach $type (@types) { + # seek to end of ID3 header + $raf->Seek($hdrEnd, 0); + # set type for this file if we are successful + $$et{FILE_TYPE} = $type; + my $module = $audioModule{$type} || $type; + require "Image/ExifTool/$module.pm" or next; + my $func = "Image::ExifTool::${module}::Process$type"; + # process the file + no strict 'refs'; + &$func($et, $dirInfo) and last; + use strict 'refs'; + } + $$et{FILE_TYPE} = $oldType; # restore original file type + } + # set file type to MP3 if we didn't find audio data + $et->SetFileType('MP3'); + # record the size of the ID3 metadata + $et->FoundTag('ID3Size', $id3Len); + # process ID3v2 header if it exists + if (%id3Header) { + $et->VPrint(0, "$id3Header{DirName}:\n"); + $et->ProcessDirectory(\%id3Header, $tagTablePtr); + } + # process ID3v1 trailer if it exists + if (%id3Trailer) { + $et->VPrint(0, "ID3v1:\n"); + SetByteOrder('MM'); + $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v1'); + $et->ProcessDirectory(\%id3Trailer, $tagTablePtr); + # process "Enhanced TAG" information if available + if ($id3Trailer{EnhancedTAG}) { + $et->VPrint(0, "ID3v1 Enhanced TAG:\n"); + $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v1_Enh'); + $id3Trailer{DataPt} = $id3Trailer{EnhancedTAG}; + $id3Trailer{DataPos} -= 227; # (227 = length of Enhanced TAG block) + $id3Trailer{DirLen} = 227; + $et->ProcessDirectory(\%id3Trailer, $tagTablePtr); + } + } + } + # return file pointer to start of file to read audio data if necessary + $raf->Seek(0, 0); + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Process ID3 directory +# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) dummy tag table ref +sub ProcessID3Dir($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et->VerboseDir('ID3', undef, length ${$$dirInfo{DataPt}}); + return ProcessID3($et, $dirInfo); +} + +#------------------------------------------------------------------------------ +# Extract ID3 information from an MP3 audio file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid MP3 file +sub ProcessMP3($$) +{ + my ($et, $dirInfo) = @_; + my $rtnVal = 0; + + # must first check for leading/trailing ID3 information + # (and process the rest of the file if found) + unless ($$et{DoneID3}) { + $rtnVal = ProcessID3($et, $dirInfo); + } + + # check for MPEG A/V data if not already processed above + unless ($rtnVal) { + my $raf = $$dirInfo{RAF}; + my $buff; +# +# extract information from first audio/video frame headers +# (if found in the first $scanLen bytes) +# + # scan further into a file that should be an MP3 + my $scanLen = ($$et{FILE_EXT} and $$et{FILE_EXT} eq 'MP3') ? 8192 : 256; + if ($raf->Read($buff, $scanLen)) { + require Image::ExifTool::MPEG; + if ($buff =~ /\0\0\x01(\xb3|\xc0)/) { + # look for A/V headers in first 64kB + my $buf2; + $raf->Read($buf2, 0x10000 - $scanLen) and $buff .= $buf2; + $rtnVal = 1 if Image::ExifTool::MPEG::ParseMPEGAudioVideo($et, \$buff); + } else { + # look for audio frame sync in first $scanLen bytes + # (set MP3 flag to 1 so this will fail unless layer 3 audio) + my $ext = $$et{FILE_EXT} || ''; + my $mp3 = ($ext eq 'MUS') ? 0 : 1; # MUS files are MP2 + $rtnVal = 1 if Image::ExifTool::MPEG::ParseMPEGAudio($et, \$buff, $mp3); + } + } + } + + # check for an APE trailer if this was a valid A/V file and we haven't already done it + if ($rtnVal and not $$et{DoneAPE}) { + require Image::ExifTool::APE; + Image::ExifTool::APE::ProcessAPE($et, $dirInfo); + } + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::ID3 - Read ID3 meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract ID3 +information from audio files. ID3 information is found in MP3 and various +other types of audio files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://id3.org/> + +=item L<http://www.mp3-tech.org/> + +=item L<http://www.fortunecity.com/underworld/sonic/3/id3tag.html> + +=item L<https://id3.org/Lyrics3> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/ID3 Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/IPTC.pm b/ExifTool/lib/Image/ExifTool/IPTC.pm new file mode 100644 index 0000000..ef341dc --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/IPTC.pm @@ -0,0 +1,1305 @@ +#------------------------------------------------------------------------------ +# File: IPTC.pm +# +# Description: Read IPTC meta information +# +# Revisions: Jan. 08/2003 - P. Harvey Created +# Feb. 05/2004 - P. Harvey Added support for records other than 2 +# +# References: 1) http://www.iptc.org/IIM/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::IPTC; + +use strict; +use vars qw($VERSION $AUTOLOAD %iptcCharset); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.58'; + +%iptcCharset = ( + "\x1b%G" => 'UTF8', + # don't translate these (at least until we handle ISO 2022 shift codes) + # because the sets are only designated and not invoked + # "\x1b,A" => 'Latin', # G0 = ISO 8859-1 (similar to Latin1, but codes 0x80-0x9f are missing) + # "\x1b-A" => 'Latin', # G1 " + # "\x1b.A" => 'Latin', # G2 + # "\x1b/A" => 'Latin', # G3 +); + +sub ProcessIPTC($$$); +sub WriteIPTC($$$); +sub CheckIPTC($$$); +sub PrintCodedCharset($); +sub PrintInvCodedCharset($); + +# standard IPTC locations +# (MWG specifies locations only for JPEG, TIFF and PSD -- the rest are ExifTool-defined) +my %isStandardIPTC = ( + 'JPEG-APP13-Photoshop-IPTC' => 1, + 'TIFF-IFD0-IPTC' => 1, + 'PSD-IPTC' => 1, + 'MIE-IPTC' => 1, + 'EPS-Photoshop-IPTC' => 1, + 'PS-Photoshop-IPTC' => 1, + 'EXV-APP13-Photoshop-IPTC' => 1, + # set file types to 0 if they have a standard location + JPEG => 0, + TIFF => 0, + PSD => 0, + MIE => 0, + EPS => 0, + PS => 0, + EXV => 0, +); + +my %fileFormat = ( + 0 => 'No ObjectData', + 1 => 'IPTC-NAA Digital Newsphoto Parameter Record', + 2 => 'IPTC7901 Recommended Message Format', + 3 => 'Tagged Image File Format (Adobe/Aldus Image data)', + 4 => 'Illustrator (Adobe Graphics data)', + 5 => 'AppleSingle (Apple Computer Inc)', + 6 => 'NAA 89-3 (ANPA 1312)', + 7 => 'MacBinary II', + 8 => 'IPTC Unstructured Character Oriented File Format (UCOFF)', + 9 => 'United Press International ANPA 1312 variant', + 10 => 'United Press International Down-Load Message', + 11 => 'JPEG File Interchange (JFIF)', + 12 => 'Photo-CD Image-Pac (Eastman Kodak)', + 13 => 'Bit Mapped Graphics File [.BMP] (Microsoft)', + 14 => 'Digital Audio File [.WAV] (Microsoft & Creative Labs)', + 15 => 'Audio plus Moving Video [.AVI] (Microsoft)', + 16 => 'PC DOS/Windows Executable Files [.COM][.EXE]', + 17 => 'Compressed Binary File [.ZIP] (PKWare Inc)', + 18 => 'Audio Interchange File Format AIFF (Apple Computer Inc)', + 19 => 'RIFF Wave (Microsoft Corporation)', + 20 => 'Freehand (Macromedia/Aldus)', + 21 => 'Hypertext Markup Language [.HTML] (The Internet Society)', + 22 => 'MPEG 2 Audio Layer 2 (Musicom), ISO/IEC', + 23 => 'MPEG 2 Audio Layer 3, ISO/IEC', + 24 => 'Portable Document File [.PDF] Adobe', + 25 => 'News Industry Text Format (NITF)', + 26 => 'Tape Archive [.TAR]', + 27 => 'Tidningarnas Telegrambyra NITF version (TTNITF DTD)', + 28 => 'Ritzaus Bureau NITF version (RBNITF DTD)', + 29 => 'Corel Draw [.CDR]', +); + +# main IPTC tag table +# Note: ALL entries in main IPTC table (except PROCESS_PROC) must be SubDirectory +# entries, each specifying a TagTable. +%Image::ExifTool::IPTC::Main = ( + GROUPS => { 2 => 'Image' }, + PROCESS_PROC => \&ProcessIPTC, + WRITE_PROC => \&WriteIPTC, + 1 => { + Name => 'IPTCEnvelope', + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::EnvelopeRecord', + }, + }, + 2 => { + Name => 'IPTCApplication', + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::ApplicationRecord', + }, + }, + 3 => { + Name => 'IPTCNewsPhoto', + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::NewsPhoto', + }, + }, + 7 => { + Name => 'IPTCPreObjectData', + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::PreObjectData', + }, + }, + 8 => { + Name => 'IPTCObjectData', + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::ObjectData', + }, + }, + 9 => { + Name => 'IPTCPostObjectData', + Groups => { 1 => 'IPTC#' }, #(just so this shows up in group list) + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::PostObjectData', + }, + }, + 240 => { + Name => 'IPTCFotoStation', + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::FotoStation', + }, + }, +); + +# Record 1 -- EnvelopeRecord +%Image::ExifTool::IPTC::EnvelopeRecord = ( + GROUPS => { 2 => 'Other' }, + WRITE_PROC => \&WriteIPTC, + CHECK_PROC => \&CheckIPTC, + WRITABLE => 1, + 0 => { + Name => 'EnvelopeRecordVersion', + Format => 'int16u', + Mandatory => 1, + }, + 5 => { + Name => 'Destination', + Flags => 'List', + Groups => { 2 => 'Location' }, + Format => 'string[0,1024]', + }, + 20 => { + Name => 'FileFormat', + Groups => { 2 => 'Image' }, + Format => 'int16u', + PrintConv => \%fileFormat, + }, + 22 => { + Name => 'FileVersion', + Groups => { 2 => 'Image' }, + Format => 'int16u', + }, + 30 => { + Name => 'ServiceIdentifier', + Format => 'string[0,10]', + }, + 40 => { + Name => 'EnvelopeNumber', + Format => 'digits[8]', + }, + 50 => { + Name => 'ProductID', + Flags => 'List', + Format => 'string[0,32]', + }, + 60 => { + Name => 'EnvelopePriority', + Format => 'digits[1]', + PrintConv => { + 0 => '0 (reserved)', + 1 => '1 (most urgent)', + 2 => 2, + 3 => 3, + 4 => 4, + 5 => '5 (normal urgency)', + 6 => 6, + 7 => 7, + 8 => '8 (least urgent)', + 9 => '9 (user-defined priority)', + }, + }, + 70 => { + Name => 'DateSent', + Groups => { 2 => 'Time' }, + Format => 'digits[8]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcDate($val)', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 80 => { + Name => 'TimeSent', + Groups => { 2 => 'Time' }, + Format => 'string[11]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifTime($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcTime($val)', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 90 => { + Name => 'CodedCharacterSet', + Notes => q{ + values are entered in the form "ESC X Y[, ...]". The escape sequence for + UTF-8 character coding is "ESC % G", but this is displayed as "UTF8" for + convenience. Either string may be used when writing. The value of this tag + affects the decoding of string values in the Application and NewsPhoto + records. This tag is marked as "unsafe" to prevent it from being copied by + default in a group operation because existing tags in the destination image + may use a different encoding. When creating a new IPTC record from scratch, + it is suggested that this be set to "UTF8" if special characters are a + possibility + }, + Protected => 1, + Format => 'string[0,32]', + ValueConvInv => '$val =~ /^UTF-?8$/i ? "\x1b%G" : $val', + # convert ISO 2022 escape sequences to a more readable format + PrintConv => \&PrintCodedCharset, + PrintConvInv => \&PrintInvCodedCharset, + }, + 100 => { + Name => 'UniqueObjectName', + Format => 'string[14,80]', + }, + 120 => { + Name => 'ARMIdentifier', + Format => 'int16u', + }, + 122 => { + Name => 'ARMVersion', + Format => 'int16u', + }, +); + +# Record 2 -- ApplicationRecord +%Image::ExifTool::IPTC::ApplicationRecord = ( + GROUPS => { 2 => 'Other' }, + WRITE_PROC => \&WriteIPTC, + CHECK_PROC => \&CheckIPTC, + WRITABLE => 1, + 0 => { + Name => 'ApplicationRecordVersion', + Format => 'int16u', + Mandatory => 1, + }, + 3 => { + Name => 'ObjectTypeReference', + Format => 'string[3,67]', + }, + 4 => { + Name => 'ObjectAttributeReference', + Flags => 'List', + Format => 'string[4,68]', + }, + 5 => { + Name => 'ObjectName', + Format => 'string[0,64]', + }, + 7 => { + Name => 'EditStatus', + Format => 'string[0,64]', + }, + 8 => { + Name => 'EditorialUpdate', + Format => 'digits[2]', + PrintConv => { + '01' => 'Additional language', + }, + }, + 10 => { + Name => 'Urgency', + Format => 'digits[1]', + PrintConv => { + 0 => '0 (reserved)', + 1 => '1 (most urgent)', + 2 => 2, + 3 => 3, + 4 => 4, + 5 => '5 (normal urgency)', + 6 => 6, + 7 => 7, + 8 => '8 (least urgent)', + 9 => '9 (user-defined priority)', + }, + }, + 12 => { + Name => 'SubjectReference', + Flags => 'List', + Format => 'string[13,236]', + }, + 15 => { + Name => 'Category', + Format => 'string[0,3]', + }, + 20 => { + Name => 'SupplementalCategories', + Flags => 'List', + Format => 'string[0,32]', + }, + 22 => { + Name => 'FixtureIdentifier', + Format => 'string[0,32]', + }, + 25 => { + Name => 'Keywords', + Flags => 'List', + Format => 'string[0,64]', + }, + 26 => { + Name => 'ContentLocationCode', + Flags => 'List', + Groups => { 2 => 'Location' }, + Format => 'string[3]', + }, + 27 => { + Name => 'ContentLocationName', + Flags => 'List', + Groups => { 2 => 'Location' }, + Format => 'string[0,64]', + }, + 30 => { + Name => 'ReleaseDate', + Groups => { 2 => 'Time' }, + Format => 'digits[8]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcDate($val)', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 35 => { + Name => 'ReleaseTime', + Groups => { 2 => 'Time' }, + Format => 'string[11]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifTime($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcTime($val)', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 37 => { + Name => 'ExpirationDate', + Groups => { 2 => 'Time' }, + Format => 'digits[8]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcDate($val)', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 38 => { + Name => 'ExpirationTime', + Groups => { 2 => 'Time' }, + Format => 'string[11]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifTime($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcTime($val)', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 40 => { + Name => 'SpecialInstructions', + Format => 'string[0,256]', + }, + 42 => { + Name => 'ActionAdvised', + Format => 'digits[2]', + PrintConv => { + '' => '', + '01' => 'Object Kill', + '02' => 'Object Replace', + '03' => 'Object Append', + '04' => 'Object Reference', + }, + }, + 45 => { + Name => 'ReferenceService', + Flags => 'List', + Format => 'string[0,10]', + }, + 47 => { + Name => 'ReferenceDate', + Groups => { 2 => 'Time' }, + Flags => 'List', + Format => 'digits[8]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcDate($val)', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 50 => { + Name => 'ReferenceNumber', + Flags => 'List', + Format => 'digits[8]', + }, + 55 => { + Name => 'DateCreated', + Groups => { 2 => 'Time' }, + Format => 'digits[8]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcDate($val)', + PrintConv => '$self->Options("DateFormat") ? $self->ConvertDateTime("$val 00:00:00") : $val', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 60 => { + Name => 'TimeCreated', + Groups => { 2 => 'Time' }, + Format => 'string[11]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifTime($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcTime($val)', + PrintConv => '$self->Options("DateFormat") ? $self->ConvertDateTime("1970:01:01 $val") : $val', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 62 => { + Name => 'DigitalCreationDate', + Groups => { 2 => 'Time' }, + Format => 'digits[8]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcDate($val)', + PrintConv => '$self->Options("DateFormat") ? $self->ConvertDateTime("$val 00:00:00") : $val', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 63 => { + Name => 'DigitalCreationTime', + Groups => { 2 => 'Time' }, + Format => 'string[11]', + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifTime($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcTime($val)', + PrintConv => '$self->Options("DateFormat") ? $self->ConvertDateTime("1970:01:01 $val") : $val', + PrintConvInv => 'Image::ExifTool::IPTC::InverseDateOrTime($self,$val)', + }, + 65 => { + Name => 'OriginatingProgram', + Format => 'string[0,32]', + }, + 70 => { + Name => 'ProgramVersion', + Format => 'string[0,10]', + }, + 75 => { + Name => 'ObjectCycle', + Format => 'string[1]', + PrintConv => { + 'a' => 'Morning', + 'p' => 'Evening', + 'b' => 'Both Morning and Evening', + }, + }, + 80 => { + Name => 'By-line', + Flags => 'List', + Format => 'string[0,32]', + Groups => { 2 => 'Author' }, + }, + 85 => { + Name => 'By-lineTitle', + Flags => 'List', + Format => 'string[0,32]', + Groups => { 2 => 'Author' }, + }, + 90 => { + Name => 'City', + Format => 'string[0,32]', + Groups => { 2 => 'Location' }, + }, + 92 => { + Name => 'Sub-location', + Format => 'string[0,32]', + Groups => { 2 => 'Location' }, + }, + 95 => { + Name => 'Province-State', + Format => 'string[0,32]', + Groups => { 2 => 'Location' }, + }, + 100 => { + Name => 'Country-PrimaryLocationCode', + Format => 'string[3]', + Groups => { 2 => 'Location' }, + }, + 101 => { + Name => 'Country-PrimaryLocationName', + Format => 'string[0,64]', + Groups => { 2 => 'Location' }, + }, + 103 => { + Name => 'OriginalTransmissionReference', + Format => 'string[0,32]', + Notes => 'now used as a job identifier', + }, + 105 => { + Name => 'Headline', + Format => 'string[0,256]', + }, + 110 => { + Name => 'Credit', + Groups => { 2 => 'Author' }, + Format => 'string[0,32]', + }, + 115 => { + Name => 'Source', + Groups => { 2 => 'Author' }, + Format => 'string[0,32]', + }, + 116 => { + Name => 'CopyrightNotice', + Groups => { 2 => 'Author' }, + Format => 'string[0,128]', + }, + 118 => { + Name => 'Contact', + Flags => 'List', + Groups => { 2 => 'Author' }, + Format => 'string[0,128]', + }, + 120 => { + Name => 'Caption-Abstract', + Format => 'string[0,2000]', + }, + 121 => { + Name => 'LocalCaption', + Format => 'string[0,256]', # (guess) + Notes => q{ + I haven't found a reference for the format of tags 121, 184-188 and + 225-232, so I have just make them writable as strings with + reasonable length. Beware that if this is wrong, other utilities + may not be able to read these tags as written by ExifTool + }, + }, + 122 => { + Name => 'Writer-Editor', + Flags => 'List', + Groups => { 2 => 'Author' }, + Format => 'string[0,32]', + }, + 125 => { + Name => 'RasterizedCaption', + Format => 'undef[7360]', + Binary => 1, + }, + 130 => { + Name => 'ImageType', + Groups => { 2 => 'Image' }, + Format => 'string[2]', + }, + 131 => { + Name => 'ImageOrientation', + Groups => { 2 => 'Image' }, + Format => 'string[1]', + PrintConv => { + P => 'Portrait', + L => 'Landscape', + S => 'Square', + }, + }, + 135 => { + Name => 'LanguageIdentifier', + Format => 'string[2,3]', + }, + 150 => { + Name => 'AudioType', + Format => 'string[2]', + PrintConv => { + '1A' => 'Mono Actuality', + '2A' => 'Stereo Actuality', + '1C' => 'Mono Question and Answer Session', + '2C' => 'Stereo Question and Answer Session', + '1M' => 'Mono Music', + '2M' => 'Stereo Music', + '1Q' => 'Mono Response to a Question', + '2Q' => 'Stereo Response to a Question', + '1R' => 'Mono Raw Sound', + '2R' => 'Stereo Raw Sound', + '1S' => 'Mono Scener', + '2S' => 'Stereo Scener', + '0T' => 'Text Only', + '1V' => 'Mono Voicer', + '2V' => 'Stereo Voicer', + '1W' => 'Mono Wrap', + '2W' => 'Stereo Wrap', + }, + }, + 151 => { + Name => 'AudioSamplingRate', + Format => 'digits[6]', + }, + 152 => { + Name => 'AudioSamplingResolution', + Format => 'digits[2]', + }, + 153 => { + Name => 'AudioDuration', + Format => 'digits[6]', + }, + 154 => { + Name => 'AudioOutcue', + Format => 'string[0,64]', + }, + 184 => { + Name => 'JobID', + Format => 'string[0,64]', # (guess) + }, + 185 => { + Name => 'MasterDocumentID', + Format => 'string[0,256]', # (guess) + }, + 186 => { + Name => 'ShortDocumentID', + Format => 'string[0,64]', # (guess) + }, + 187 => { + Name => 'UniqueDocumentID', + Format => 'string[0,128]', # (guess) + }, + 188 => { + Name => 'OwnerID', + Format => 'string[0,128]', # (guess) + }, + 200 => { + Name => 'ObjectPreviewFileFormat', + Groups => { 2 => 'Image' }, + Format => 'int16u', + PrintConv => \%fileFormat, + }, + 201 => { + Name => 'ObjectPreviewFileVersion', + Groups => { 2 => 'Image' }, + Format => 'int16u', + }, + 202 => { + Name => 'ObjectPreviewData', + Groups => { 2 => 'Preview' }, + Format => 'undef[0,256000]', + Binary => 1, + }, + 221 => { + Name => 'Prefs', + Groups => { 2 => 'Image' }, + Format => 'string[0,64]', + Notes => 'PhotoMechanic preferences', + PrintConv => q{ + $val =~ s[\s*(\d+):\s*(\d+):\s*(\d+):\s*(\S*)] + [Tagged:$1, ColorClass:$2, Rating:$3, FrameNum:$4]; + return $val; + }, + PrintConvInv => q{ + $val =~ s[Tagged:\s*(\d+).*ColorClass:\s*(\d+).*Rating:\s*(\d+).*FrameNum:\s*(\S*)] + [$1:$2:$3:$4]is; + return $val; + }, + }, + 225 => { + Name => 'ClassifyState', + Format => 'string[0,64]', # (guess) + }, + 228 => { + Name => 'SimilarityIndex', + Format => 'string[0,32]', # (guess) + }, + 230 => { + Name => 'DocumentNotes', + Format => 'string[0,1024]', # (guess) + }, + 231 => { + Name => 'DocumentHistory', + Format => 'string[0,256]', # (guess) + ValueConv => '$val =~ s/\0+/\n/g; $val', # (have seen embedded nulls) + ValueConvInv => '$val', + }, + 232 => { + Name => 'ExifCameraInfo', + Format => 'string[0,4096]', # (guess) + }, + 255 => { #PH + Name => 'CatalogSets', + List => 1, + Format => 'string[0,256]', # (guess) + Notes => 'written by iView MediaPro', + }, +); + +# Record 3 -- News photo +%Image::ExifTool::IPTC::NewsPhoto = ( + GROUPS => { 2 => 'Image' }, + WRITE_PROC => \&WriteIPTC, + CHECK_PROC => \&CheckIPTC, + WRITABLE => 1, + 0 => { + Name => 'NewsPhotoVersion', + Format => 'int16u', + Mandatory => 1, + }, + 10 => { + Name => 'IPTCPictureNumber', + Format => 'string[16]', + Notes => '4 numbers: 1-Manufacturer ID, 2-Equipment ID, 3-Date, 4-Sequence', + PrintConv => 'Image::ExifTool::IPTC::ConvertPictureNumber($val)', + PrintConvInv => 'Image::ExifTool::IPTC::InvConvertPictureNumber($val)', + }, + 20 => { + Name => 'IPTCImageWidth', + Format => 'int16u', + }, + 30 => { + Name => 'IPTCImageHeight', + Format => 'int16u', + }, + 40 => { + Name => 'IPTCPixelWidth', + Format => 'int16u', + }, + 50 => { + Name => 'IPTCPixelHeight', + Format => 'int16u', + }, + 55 => { + Name => 'SupplementalType', + Format => 'int8u', + PrintConv => { + 0 => 'Main Image', + 1 => 'Reduced Resolution Image', + 2 => 'Logo', + 3 => 'Rasterized Caption', + }, + }, + 60 => { + Name => 'ColorRepresentation', + Format => 'int16u', + PrintHex => 1, + PrintConv => { + 0x000 => 'No Image, Single Frame', + 0x100 => 'Monochrome, Single Frame', + 0x300 => '3 Components, Single Frame', + 0x301 => '3 Components, Frame Sequential in Multiple Objects', + 0x302 => '3 Components, Frame Sequential in One Object', + 0x303 => '3 Components, Line Sequential', + 0x304 => '3 Components, Pixel Sequential', + 0x305 => '3 Components, Special Interleaving', + 0x400 => '4 Components, Single Frame', + 0x401 => '4 Components, Frame Sequential in Multiple Objects', + 0x402 => '4 Components, Frame Sequential in One Object', + 0x403 => '4 Components, Line Sequential', + 0x404 => '4 Components, Pixel Sequential', + 0x405 => '4 Components, Special Interleaving', + }, + }, + 64 => { + Name => 'InterchangeColorSpace', + Format => 'int8u', + PrintConv => { + 1 => 'X,Y,Z CIE', + 2 => 'RGB SMPTE', + 3 => 'Y,U,V (K) (D65)', + 4 => 'RGB Device Dependent', + 5 => 'CMY (K) Device Dependent', + 6 => 'Lab (K) CIE', + 7 => 'YCbCr', + 8 => 'sRGB', + }, + }, + 65 => { + Name => 'ColorSequence', + Format => 'int8u', + }, + 66 => { + Name => 'ICC_Profile', + # ...could add SubDirectory support to read into this (if anybody cares) + Writable => 0, + Binary => 1, + }, + 70 => { + Name => 'ColorCalibrationMatrix', + Writable => 0, + Binary => 1, + }, + 80 => { + Name => 'LookupTable', + Writable => 0, + Binary => 1, + }, + 84 => { + Name => 'NumIndexEntries', + Format => 'int16u', + }, + 85 => { + Name => 'ColorPalette', + Writable => 0, + Binary => 1, + }, + 86 => { + Name => 'IPTCBitsPerSample', + Format => 'int8u', + }, + 90 => { + Name => 'SampleStructure', + Format => 'int8u', + PrintConv => { + 0 => 'OrthogonalConstangSampling', + 1 => 'Orthogonal4-2-2Sampling', + 2 => 'CompressionDependent', + }, + }, + 100 => { + Name => 'ScanningDirection', + Format => 'int8u', + PrintConv => { + 0 => 'L-R, Top-Bottom', + 1 => 'R-L, Top-Bottom', + 2 => 'L-R, Bottom-Top', + 3 => 'R-L, Bottom-Top', + 4 => 'Top-Bottom, L-R', + 5 => 'Bottom-Top, L-R', + 6 => 'Top-Bottom, R-L', + 7 => 'Bottom-Top, R-L', + }, + }, + 102 => { + Name => 'IPTCImageRotation', + Format => 'int8u', + PrintConv => { + 0 => 0, + 1 => 90, + 2 => 180, + 3 => 270, + }, + }, + 110 => { + Name => 'DataCompressionMethod', + Format => 'int32u', + }, + 120 => { + Name => 'QuantizationMethod', + Format => 'int8u', + PrintConv => { + 0 => 'Linear Reflectance/Transmittance', + 1 => 'Linear Density', + 2 => 'IPTC Ref B', + 3 => 'Linear Dot Percent', + 4 => 'AP Domestic Analogue', + 5 => 'Compression Method Specific', + 6 => 'Color Space Specific', + 7 => 'Gamma Compensated', + }, + }, + 125 => { + Name => 'EndPoints', + Writable => 0, + Binary => 1, + }, + 130 => { + Name => 'ExcursionTolerance', + Format => 'int8u', + PrintConv => { + 0 => 'Not Allowed', + 1 => 'Allowed', + }, + }, + 135 => { + Name => 'BitsPerComponent', + Format => 'int8u', + }, + 140 => { + Name => 'MaximumDensityRange', + Format => 'int16u', + }, + 145 => { + Name => 'GammaCompensatedValue', + Format => 'int16u', + }, +); + +# Record 7 -- Pre-object Data +%Image::ExifTool::IPTC::PreObjectData = ( + # (not actually writable, but used in BuildTagLookup to recognize IPTC tables) + WRITE_PROC => \&WriteIPTC, + 10 => { + Name => 'SizeMode', + Format => 'int8u', + PrintConv => { + 0 => 'Size Not Known', + 1 => 'Size Known', + }, + }, + 20 => { + Name => 'MaxSubfileSize', + Format => 'int32u', + }, + 90 => { + Name => 'ObjectSizeAnnounced', + Format => 'int32u', + }, + 95 => { + Name => 'MaximumObjectSize', + Format => 'int32u', + }, +); + +# Record 8 -- ObjectData +%Image::ExifTool::IPTC::ObjectData = ( + WRITE_PROC => \&WriteIPTC, + 10 => { + Name => 'SubFile', + Flags => 'List', + Binary => 1, + }, +); + +# Record 9 -- PostObjectData +%Image::ExifTool::IPTC::PostObjectData = ( + WRITE_PROC => \&WriteIPTC, + 10 => { + Name => 'ConfirmedObjectSize', + Format => 'int32u', + }, +); + +# Record 240 -- FotoStation proprietary data (ref PH) +%Image::ExifTool::IPTC::FotoStation = ( + GROUPS => { 2 => 'Other' }, + WRITE_PROC => \&WriteIPTC, + CHECK_PROC => \&CheckIPTC, + WRITABLE => 1, +); + +# IPTC Composite tags +%Image::ExifTool::IPTC::Composite = ( + GROUPS => { 2 => 'Image' }, + DateTimeCreated => { + Description => 'Date/Time Created', + Groups => { 2 => 'Time' }, + Require => { + 0 => 'IPTC:DateCreated', + 1 => 'IPTC:TimeCreated', + }, + ValueConv => '"$val[0] $val[1]"', + PrintConv => '$self->ConvertDateTime($val)', + }, + DigitalCreationDateTime => { + Description => 'Digital Creation Date/Time', + Groups => { 2 => 'Time' }, + Require => { + 0 => 'IPTC:DigitalCreationDate', + 1 => 'IPTC:DigitalCreationTime', + }, + ValueConv => '"$val[0] $val[1]"', + PrintConv => '$self->ConvertDateTime($val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::IPTC'); + + +#------------------------------------------------------------------------------ +# AutoLoad our writer routines when necessary +# +sub AUTOLOAD +{ + return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); +} + +#------------------------------------------------------------------------------ +# Print conversion for CodedCharacterSet +# Inputs: 0) value +sub PrintCodedCharset($) +{ + my $val = shift; + return $iptcCharset{$val} if $iptcCharset{$val}; + $val =~ s/(.)/ $1/g; + $val =~ s/ \x1b/, ESC/g; + $val =~ s/^,? //; + return $val; +} + +#------------------------------------------------------------------------------ +# Handle CodedCharacterSet +# Inputs: 0) ExifTool ref, 1) CodedCharacterSet value +# Returns: IPTC character set if translation required (or 'bad' if unknown) +sub HandleCodedCharset($$) +{ + my ($et, $val) = @_; + my $xlat = $iptcCharset{$val}; + unless ($xlat) { + if ($val =~ /^\x1b\x25/) { + # some unknown character set invoked + $xlat = 'bad'; # flag unsupported coding + } else { + $xlat = $et->Options('CharsetIPTC'); + } + } + # no need to translate if Charset is the same + undef $xlat if $xlat eq $et->Options('Charset'); + return $xlat; +} + +#------------------------------------------------------------------------------ +# Encode or decode coded string +# Inputs: 0) ExifTool ref, 1) value ptr, 2) IPTC charset (or 'bad') ref +# 3) flag set to decode (read) value from IPTC +# Updates value on return +sub TranslateCodedString($$$$) +{ + my ($et, $valPtr, $xlatPtr, $read) = @_; + if ($$xlatPtr eq 'bad') { + $et->Warn('Some IPTC characters not converted (unsupported CodedCharacterSet)'); + undef $$xlatPtr; + } elsif (not $read) { + $$valPtr = $et->Decode($$valPtr, undef, undef, $$xlatPtr); + } elsif ($$valPtr !~ /[\x14\x15\x1b]/) { + $$valPtr = $et->Decode($$valPtr, $$xlatPtr); + } else { + # don't yet support reading ISO 2022 shifted character sets + $et->WarnOnce('Some IPTC characters not converted (ISO 2022 shifting not supported)'); + } +} + +#------------------------------------------------------------------------------ +# Is this IPTC in a standard location? +# Inputs: 0) Current metadata path string +# Returns: true if path is standard, 0 if file type doesn't have standard IPTC, +# or undef if IPTC is non-standard +sub IsStandardIPTC($) +{ + my $path = shift; + return 1 if $isStandardIPTC{$path}; + return 0 unless $path =~ /^(\w+)/ and defined $isStandardIPTC{$1}; + return undef; # non-standard +} + +#------------------------------------------------------------------------------ +# get IPTC info +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# 2) reference to tag table +# Returns: 1 on success, 0 otherwise +sub ProcessIPTC($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || 0; + my $dirEnd = $pos + $dirLen; + my $verbose = $et->Options('Verbose'); + my $validate = $et->Options('Validate'); + my $success = 0; + my ($lastRec, $recordPtr, $recordName); + + $verbose and $dirInfo and $et->VerboseDir('IPTC', 0, $$dirInfo{DirLen}); + + if ($tagTablePtr eq \%Image::ExifTool::IPTC::Main) { + my $path = $et->MetadataPath(); + my $isStd = IsStandardIPTC($path); + if (defined $isStd and not $$et{DIR_COUNT}{STD_IPTC}) { + # set flag to ensure we only have one family 1 "IPTC" group + $$et{DIR_COUNT}{STD_IPTC} = 1; + # calculate MD5 if Digest::MD5 is available (truly standard IPTC only) + if ($isStd) { + my $md5; + if (eval { require Digest::MD5 }) { + if ($pos or $dirLen != length($$dataPt)) { + $md5 = Digest::MD5::md5(substr $$dataPt, $pos, $dirLen); + } else { + $md5 = Digest::MD5::md5($$dataPt); + } + } else { + # a zero digest indicates IPTC exists but we don't have Digest::MD5 + $md5 = "\0" x 16; + } + $et->FoundTag('CurrentIPTCDigest', $md5); + } + } else { + if (($Image::ExifTool::MWG::strict or $et->Options('Validate')) and + $$et{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/) + { + if ($Image::ExifTool::MWG::strict) { + # ignore non-standard IPTC while in strict MWG compatibility mode + $et->Warn("Ignored non-standard IPTC at $path"); + return 1; + } else { + $et->Warn("Non-standard IPTC at $path", 1); + } + } + # extract non-standard IPTC + my $count = ($$et{DIR_COUNT}{IPTC} || 0) + 1; # count non-standard IPTC + $$et{DIR_COUNT}{IPTC} = $count; + $$et{LOW_PRIORITY_DIR}{IPTC} = 1; # lower priority of non-standard IPTC + $$et{SET_GROUP1} = '+' . ($count + 1); # add number to family 1 group name + } + } + # begin by assuming default IPTC encoding + my $xlat = $et->Options('CharsetIPTC'); + undef $xlat if $xlat eq $et->Options('Charset'); + + # quick check for improperly byte-swapped IPTC + if ($dirLen >= 4 and substr($$dataPt, $pos, 1) ne "\x1c" and + substr($$dataPt, $pos + 3, 1) eq "\x1c") + { + $et->Warn('IPTC data was improperly byte-swapped'); + my $newData = pack('N*', unpack('V*', substr($$dataPt, $pos, $dirLen) . "\0\0\0")); + $dataPt = \$newData; + $pos = 0; + $dirEnd = $pos + $dirLen; + # NOTE: MUST NOT access $dirInfo DataPt, DirStart or DataLen after this! + } + # extract IPTC as a block if specified + if ($$et{REQ_TAG_LOOKUP}{iptc} or ($$et{TAGS_FROM_FILE} and + not $$et{EXCL_TAG_LOOKUP}{iptc})) + { + if ($pos or $dirLen != length($$dataPt)) { + $et->FoundTag('IPTC', substr($$dataPt, $pos, $dirLen)); + } else { + $et->FoundTag('IPTC', $$dataPt); + } + } + while ($pos + 5 <= $dirEnd) { + my $buff = substr($$dataPt, $pos, 5); + my ($id, $rec, $tag, $len) = unpack("CCCn", $buff); + unless ($id == 0x1c) { + unless ($id) { + # scan the rest of the data an give warning unless all zeros + # (iMatch pads the IPTC block with nulls for some reason) + my $remaining = substr($$dataPt, $pos, $dirEnd - $pos); + last unless $remaining =~ /[^\0]/; + } + $et->Warn(sprintf('Bad IPTC data tag (marker 0x%x)',$id)); + last; + } + $pos += 5; # step to after field header + # handle extended IPTC entry if necessary + if ($len & 0x8000) { + my $n = $len & 0x7fff; # get num bytes in length field + if ($pos + $n > $dirEnd or $n > 8) { + $et->VPrint(0, "Invalid extended IPTC entry (dataset $rec:$tag, len $len)\n"); + $success = 0; + last; + } + # determine length (a big-endian, variable sized int) + for ($len = 0; $n; ++$pos, --$n) { + $len = $len * 256 + ord(substr($$dataPt, $pos, 1)); + } + } + if ($pos + $len > $dirEnd) { + $et->VPrint(0, "Invalid IPTC entry (dataset $rec:$tag, len $len)\n"); + $success = 0; + last; + } + if (not defined $lastRec or $lastRec != $rec) { + if ($validate and defined $lastRec and $rec < $lastRec) { + $et->Warn("IPTC doesn't conform to spec: Records out of sequence",1) + } + my $tableInfo = $tagTablePtr->{$rec}; + unless ($tableInfo) { + $et->WarnOnce("Unrecognized IPTC record $rec (ignored)"); + $pos += $len; + next; # ignore this entry + } + my $tableName = $tableInfo->{SubDirectory}->{TagTable}; + unless ($tableName) { + $et->Warn("No table for IPTC record $rec!"); + last; # this shouldn't happen + } + $recordName = $$tableInfo{Name}; + $recordPtr = Image::ExifTool::GetTagTable($tableName); + $et->VPrint(0,$$et{INDENT},"-- $recordName record --\n"); + $lastRec = $rec; + } + my $val = substr($$dataPt, $pos, $len); + + # add tagInfo for all unknown tags: + unless ($$recordPtr{$tag}) { + # - no Format so format is auto-detected + # - no Name so name is generated automatically with decimal tag number + AddTagToTable($recordPtr, $tag, { Unknown => 1 }); + } + + my $tagInfo = $et->GetTagInfo($recordPtr, $tag); + my $format; + # (could use $$recordPtr{FORMAT} if no Format below, but don't do this to + # be backward compatible with improperly written PhotoMechanic tags) + $format = $$tagInfo{Format} if $tagInfo; + if (not $format) { + # guess at "int" format if not specified + $format = 'int' if $len <= 4 and $len != 3 and $val =~ /[\0-\x08]/; + } elsif ($validate) { + my ($fmt,$min,$max); + if ($format =~ /(.*)\[(\d+)(,(\d+))?\]/) { + $fmt = $1; + $min = $2; + $max = $4 || $2; + } else { + $fmt = $format; + $min = $max = 1; + } + my $siz = Image::ExifTool::FormatSize($fmt) || 1; + $min *= $siz; $max *= $siz; + if ($len < $min or $len > $max) { + my $should = ($min == $max) ? $min : ($len < $min ? "$min min" : "$max max"); + my $what = ($len < $siz * $min) ? 'short' : 'long'; + $et->Warn("IPTC $$tagInfo{Name} too $what ($len bytes; should be $should)", 1); + } + } + if ($format) { + if ($format =~ /^int/) { + if ($len <= 8) { # limit integer conversion to 8 bytes long + $val = 0; + my $i; + for ($i=0; $i<$len; ++$i) { + $val = $val * 256 + ord(substr($$dataPt, $pos+$i, 1)); + } + } + } elsif ($format =~ /^string/) { + # some braindead softwares add null terminators + if ($val =~ s/\0+$// and $validate) { + $et->Warn("IPTC $$tagInfo{Name} improperly terminated", 1); + } + if ($rec == 1) { + # handle CodedCharacterSet tag + $xlat = HandleCodedCharset($et, $val) if $tag == 90; + # translate characters if necessary and special characters exist + } elsif ($xlat and $rec < 7 and $val =~ /[\x80-\xff]/) { + # translate to specified character set + TranslateCodedString($et, \$val, \$xlat, 1); + } + } elsif ($format =~ /^digits/) { + if ($val =~ s/\0+$// and $validate) { + $et->Warn("IPTC $$tagInfo{Name} improperly terminated", 1); + } + } elsif ($format !~ /^undef/) { + warn("Invalid IPTC format: $format"); # (this would be a programming error) + } + } + $verbose and $et->VerboseInfo($tag, $tagInfo, + Table => $tagTablePtr, + Value => $val, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Size => $len, + Start => $pos, + Extra => ", $recordName record", + Format => $format, + ); + $et->FoundTag($tagInfo, $val) if $tagInfo; + $success = 1; + + $pos += $len; # increment to next field + } + delete $$et{SET_GROUP1}; + delete $$et{LOW_PRIORITY_DIR}{IPTC}; + return $success; +} + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::IPTC - Read IPTC meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +IPTC (International Press Telecommunications Council) meta information in +image files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.iptc.org/IIM/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/IPTC Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/ISO.pm b/ExifTool/lib/Image/ExifTool/ISO.pm new file mode 100644 index 0000000..d5e9dfd --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/ISO.pm @@ -0,0 +1,206 @@ +#------------------------------------------------------------------------------ +# File: ISO.pm +# +# Description: Read information from ISO 9660 disk images +# +# Revisions: 2016-04-07 - P. Harvey created +# +# References: 1) http://wiki.osdev.org/ISO_9660 +#------------------------------------------------------------------------------ + +package Image::ExifTool::ISO; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.01'; + +# trim trailing spaces and ignore tag if empty +my %rawStr = ( + RawConv => sub { + my $val = shift; + $val =~ s/ +$//; + return length($val) ? $val : undef; + }, +); + +# tag info for date/time tags +my %dateInfo = ( + Format => 'undef[17]', + Groups => { 2 => 'Time' }, + ValueConv => q{ + return undef if $val !~ /[^0\0 ]/; # ignore if empty + if ($val =~ s/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(.)/$1:$2:$3 $4:$5:$6.$7/s) { + $val .= TimeZoneString(unpack('c', $8) * 15); + } + return $val; + }, + PrintConv => '$self->ConvertDateTime($val)', +); + +# lookup for volume descriptor types +my %volumeDescriptorType = ( + 0 => 'Boot Record', + 1 => 'Primary Volume', + 2 => 'Supplementary Volume', + 3 => 'Volume Partition', + 255 => 'Terminator', +); + +# ISO tags +%Image::ExifTool::ISO::Main = ( + GROUPS => { 2 => 'Other' }, + NOTES => 'Tags extracted from ISO 9660 disk images.', + 0 => { + Name => 'BootRecord', + SubDirectory => { TagTable => 'Image::ExifTool::ISO::BootRecord' }, + }, + 1 => { + Name => 'PrimaryVolume', + SubDirectory => { TagTable => 'Image::ExifTool::ISO::PrimaryVolume' }, + }, +); + +%Image::ExifTool::ISO::BootRecord = ( + GROUPS => { 2 => 'Other' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + # 0 => { Name => 'VolumeType', PrintConv => \%volumeDescriptorType }, # (0 for boot record) + # 1 => { Name => 'Identifier', Format => 'undef[5]' }, # (always "CD001") + # 6 => 'VolumeDesriptorVersion', # (always 1) + # always extract BootSystem, even if empty, as an indication that this is bootable + 7 => { Name => 'BootSystem', Format => 'string[32]', ValueConv => '$val=~s/ +$//; $val' }, + 39 => { Name => 'BootIdentifier', Format => 'string[32]', %rawStr }, +); + +%Image::ExifTool::ISO::PrimaryVolume = ( + GROUPS => { 2 => 'Other' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + # 0 => { Name => 'VolumeType', PrintConv => \%volumeDescriptorType }, # (1 for primary volume) + # 1 => { Name => 'Identifier', Format => 'undef[5]' }, # (always "CD001") + # 6 => 'VolumeDesriptorVersion', # (always 1) + 8 => { Name => 'System', Format => 'string[32]', %rawStr }, + 40 => { Name => 'VolumeName', Format => 'string[32]', %rawStr }, + 80 => { Name => 'VolumeBlockCount', Format => 'int32u' }, + 120 => { Name => 'VolumeSetDiskCount', Format => 'int16u', Unknown => 1 }, + 124 => { Name => 'VolumeSetDiskNumber', Format => 'int16u', Unknown => 1 }, + 128 => { Name => 'VolumeBlockSize', Format => 'int16u' }, + 132 => { Name => 'PathTableSize', Format => 'int32u', Unknown => 1 }, + 140 => { Name => 'PathTableLocation', Format => 'int32u', Unknown => 1 }, + 174 => { + Name => 'RootDirectoryCreateDate', + Format => 'undef[7]', + Groups => { 2 => 'Time' }, + ValueConv => q{ + my @a = unpack('C6c', $val); + $a[0] += 1900; + $a[6] = TimeZoneString($a[6] * 15); + return sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d%s', @a); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 190 => { Name => 'VolumeSetName', Format => 'string[128]', %rawStr }, + 318 => { Name => 'Publisher', Format => 'string[128]', %rawStr }, + 446 => { Name => 'DataPreparer', Format => 'string[128]', %rawStr }, + 574 => { Name => 'Software', Format => 'string[128]', %rawStr }, + 702 => { Name => 'CopyrightFileName', Format => 'string[38]', %rawStr }, + 740 => { Name => 'AbstractFileName', Format => 'string[36]', %rawStr }, + 776 => { Name => 'BibligraphicFileName',Format => 'string[37]', %rawStr }, + 813 => { Name => 'VolumeCreateDate', %dateInfo }, + 830 => { Name => 'VolumeModifyDate', %dateInfo }, + 847 => { Name => 'VolumeExpirationDate',%dateInfo }, + 864 => { Name => 'VolumeEffectiveDate', %dateInfo }, + #881 => 'FileStructureVersion', # (always 1) +); + +# ISO Composite tags +%Image::ExifTool::ISO::Composite = ( + GROUPS => { 2 => 'Other' }, + VolumeSize => { + Require => { + 0 => 'ISO:VolumeBlockCount', + 1 => 'ISO:VolumeBlockSize', + }, + ValueConv => '$val[0] * $val[1]', + PrintConv => \&Image::ExifTool::ConvertFileSize, + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::ISO'); + +#------------------------------------------------------------------------------ +# Extract information from an ISO 9660 disk image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid ISO 9660 image +sub ProcessISO($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $tagTablePtr); + + # verify this is a valid ISO file + return 0 unless $raf->Seek(32768, 0); + + while ($raf->Read($buff, 2048) == 2048) { + last unless $buff =~ /^[\0-\x03\xff]CD001/; + unless ($tagTablePtr) { + $et->SetFileType(); # set the FileType tag + SetByteOrder('II'); # read little-endian values only + $tagTablePtr = GetTagTable('Image::ExifTool::ISO::Main'); + } + my $type = unpack('C', $buff); + $et->VPrint(0, "Volume descriptor type $type ($volumeDescriptorType{$type})\n"); + last if $type == 255; # stop at terminator + next unless $$tagTablePtr{$type}; + my $subTablePtr = GetTagTable($$tagTablePtr{$type}{SubDirectory}{TagTable}); + my %dirInfo = ( + DataPt => \$buff, + DataPos => $raf->Tell() - 2048, + DirStart => 0, + DirLen => length($buff), + ); + $et->ProcessDirectory(\%dirInfo, $subTablePtr); + } + return $tagTablePtr ? 1 : 0; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::ISO - Read information from ISO 9660 disk images + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +information from ISO 9660 disk images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://wiki.osdev.org/ISO_9660> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/ISO Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/ITC.pm b/ExifTool/lib/Image/ExifTool/ITC.pm new file mode 100644 index 0000000..f9a990c --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/ITC.pm @@ -0,0 +1,215 @@ +#------------------------------------------------------------------------------ +# File: ITC.pm +# +# Description: Read iTunes Cover Flow meta information +# +# Revisions: 01/12/2008 - P. Harvey Created +# +# References: 1) http://www.waldoland.com/dev/Articles/ITCFileFormat.aspx +# 2) http://www.falsecognate.org/2007/01/deciphering_the_itunes_itc_fil/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::ITC; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.02'; + +sub ProcessITC($$); + +# tags used in ITC files +%Image::ExifTool::ITC::Main = ( + NOTES => 'This information is found in iTunes Cover Flow data files.', + itch => { SubDirectory => { TagTable => 'Image::ExifTool::ITC::Header' } }, + item => { SubDirectory => { TagTable => 'Image::ExifTool::ITC::Item' } }, + data => { + Name => 'ImageData', + Notes => 'embedded JPEG or PNG image, depending on ImageType', + }, +); + +# ITC header information +%Image::ExifTool::ITC::Header = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0x10 => { + Name => 'DataType', + Format => 'undef[4]', + PrintConv => { artw => 'Artwork' }, + }, +); + +# ITC item information +%Image::ExifTool::ITC::Item = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + 0 => { + Name => 'LibraryID', + Format => 'undef[8]', + ValueConv => 'uc unpack "H*", $val', + }, + 2 => { + Name => 'TrackID', + Format => 'undef[8]', + ValueConv => 'uc unpack "H*", $val', + }, + 4 => { + Name => 'DataLocation', + Format => 'undef[4]', + PrintConv => { + down => 'Downloaded Separately', + locl => 'Local Music File', + }, + }, + 5 => { + Name => 'ImageType', + Format => 'undef[4]', + ValueConv => { # (not PrintConv because the unconverted JPEG value is nasty) + 'PNGf' => 'PNG', + "\0\0\0\x0d" => 'JPEG', + }, + }, + 7 => 'ImageWidth', + 8 => 'ImageHeight', +); + +#------------------------------------------------------------------------------ +# Process an iTunes Cover Flow (ITC) file +# Inputs: 0) ExifTool object reference, 1) Directory information reference +# Returns: 1 on success, 0 if this wasn't a valid ITC file +sub ProcessITC($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $rtnVal = 0; + my ($buff, $err, $pos, $tagTablePtr, %dirInfo); + + # loop through all blocks in this image + for (;;) { + # read the block header + my $n = $raf->Read($buff, 8); + unless ($n == 8) { + # no error if we reached the EOF normally + undef $err unless $n; + last; + } + my ($size, $tag) = unpack('Na4', $buff); + if ($rtnVal) { + last unless $size >= 8 and $size < 0x80000000; + } else { + # check to be sure this is a valid ITC image + # (first block must be 'itch') + last unless $tag eq 'itch'; + last unless $size >= 0x1c and $size < 0x10000; + $et->SetFileType(); + SetByteOrder('MM'); + $rtnVal = 1; # this is an ITC file + $err = 1; # format error unless we read to EOF + } + if ($tag eq 'itch') { + $pos = $raf->Tell(); + $size -= 8; # size of remaining data in block + $raf->Read($buff,$size) == $size or last; + # extract header information + %dirInfo = ( + DirName => 'ITC Header', + DataPt => \$buff, + DataPos => $pos, + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::ITC::Header'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($tag eq 'item') { + # don't want to read the entire item data (includes image) + $size > 12 or last; + $raf->Read($buff, 4) == 4 or last; + my $len = unpack('N', $buff); + $len >= 0xd0 and $len <= $size or last; + $size -= $len; # size of data after item header + $len -= 12; # length of remaining item header + # read in 4-byte blocks until we find the null terminator + # (this is just a guess about how to parse this variable-length part) + while ($len >= 4) { + $raf->Read($buff, 4) == 4 or last; + $len -= 4; + last if $buff eq "\0\0\0\0"; + } + last if $len < 4; + $pos = $raf->Tell(); + $raf->Read($buff, $len) == $len or last; + unless ($len >= 0xb4 and substr($buff, 0xb0, 4) eq 'data') { + $et->Warn('Parsing error. Please submit this ITC file for testing'); + last; + } + %dirInfo = ( + DirName => 'ITC Item', + DataPt => \$buff, + DataPos => $pos, + ); + $tagTablePtr = GetTagTable('Image::ExifTool::ITC::Item'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + # extract embedded image + $pos += $len; + if ($size > 0) { + $tagTablePtr = GetTagTable('Image::ExifTool::ITC::Main'); + my $tagInfo = $et->GetTagInfo($tagTablePtr, 'data'); + my $image = $et->ExtractBinary($pos, $size, $$tagInfo{Name}); + $et->FoundTag($tagInfo, \$image); + # skip the rest of the block if necessary + $raf->Seek($pos+$size, 0) or last + } elsif ($size < 0) { + last; + } + } else { + $et->VPrint(0, "Unknown $tag block ($size bytes)\n"); + $raf->Seek($size-8, 1) or last; + } + } + $err and $et->Warn('ITC file format error'); + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::ITC - Read iTunes Cover Flow meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains the routines required by Image::ExifTool to read meta +information (including artwork images) from iTunes Cover Flow files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.waldoland.com/dev/Articles/ITCFileFormat.aspx> + +=item L<http://www.falsecognate.org/2007/01/deciphering_the_itunes_itc_fil/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/ITC Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Import.pm b/ExifTool/lib/Image/ExifTool/Import.pm new file mode 100644 index 0000000..80c0f13 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Import.pm @@ -0,0 +1,360 @@ +#------------------------------------------------------------------------------ +# File: Import.pm +# +# Description: Import CSV and JSON database files +# +# Revisions: 2011-03-05 - P. Harvey Created +#------------------------------------------------------------------------------ +package Image::ExifTool::Import; + +use strict; +require Exporter; + +use vars qw($VERSION @ISA @EXPORT_OK); + +$VERSION = '1.10'; +@ISA = qw(Exporter); +@EXPORT_OK = qw(ReadCSV ReadJSON); + +sub ReadJSONObject($;$); + +my %unescapeJSON = ( 't'=>"\t", 'n'=>"\n", 'r'=>"\r", 'b' => "\b", 'f' => "\f" ); +my $charset; + +#------------------------------------------------------------------------------ +# Read CSV file +# Inputs: 0) CSV file name, file ref or RAF ref, 1) database hash ref, +# 2) missing tag value, 3) delimiter if other than ',' +# Returns: undef on success, or error string +# Notes: There are various flavours of CSV, but here we assume that only +# double quotes are escaped, and they are escaped by doubling them +sub ReadCSV($$;$$) +{ + local ($_, $/); + my ($file, $database, $missingValue, $delim) = @_; + my ($buff, @tags, $found, $err, $raf, $openedFile); + + if (UNIVERSAL::isa($file, 'File::RandomAccess')) { + $raf = $file; + $file = 'CSV file'; + } elsif (ref $file eq 'GLOB') { + $raf = new File::RandomAccess($file); + $file = 'CSV file'; + } else { + open CSVFILE, $file or return "Error opening CSV file '${file}'"; + binmode CSVFILE; + $openedFile = 1; + $raf = new File::RandomAccess(\*CSVFILE); + } + $delim = ',' unless defined $delim; + # set input record separator by first newline found in the file + # (safe because first line should contain only tag names) + while ($raf->Read($buff, 65536)) { + $buff =~ /(\x0d\x0a|\x0d|\x0a)/ and $/ = $1, last; + } + $raf->Seek(0,0); + while ($raf->ReadLine($buff)) { + my (@vals, $v, $i, %fileInfo); + my @toks = split /\Q$delim/, $buff; + while (@toks) { + ($v = shift @toks) =~ s/^ +//; # remove leading spaces + if ($v =~ s/^"//) { + # quoted value must end in an odd number of quotes + while ($v !~ /("+)\s*$/ or not length($1) & 1) { + if (@toks) { + $v .= $delim . shift @toks; + } else { + # read another line from the file + $raf->ReadLine($buff) or last; + @toks = split /\Q$delim/, $buff; + last unless @toks; + $v .= shift @toks; + } + } + $v =~ s/"\s*$//; # remove trailing quote and whitespace + $v =~ s/""/"/g; # un-escape quotes + } else { + $v =~ s/[ \n\r]+$//;# remove trailing spaces/newlines + } + push @vals, $v; + } + if (@tags) { + # save values for each tag + for ($i=0; $i<@vals and $i<@tags; ++$i) { + # ignore empty entries unless missingValue is empty too + next unless length $vals[$i] or defined $missingValue and $missingValue eq ''; + # delete tag (set value to undef) if value is same as missing tag + $fileInfo{$tags[$i]} = + (defined $missingValue and $vals[$i] eq $missingValue) ? undef : $vals[$i]; + } + # figure out the file name to use + if ($fileInfo{SourceFile}) { + $$database{$fileInfo{SourceFile}} = \%fileInfo; + $found = 1; + } + } else { + # the first row should be the tag names + foreach (@vals) { + # terminate at first blank tag name (eg. extra comma at end of line) + last unless length $_; + @tags or s/^\xef\xbb\xbf//; # remove UTF-8 BOM if it exists + /^[-\w]+(:[-\w+]+)?#?$/ or $err = "Invalid tag name '${_}'", last; + push(@tags, $_); + } + last if $err; + @tags or $err = 'No tags found', last; + # fix "SourceFile" case if necessary + $tags[0] = 'SourceFile' if lc $tags[0] eq 'sourcefile'; + } + } + close CSVFILE if $openedFile; + undef $raf; + $err = 'No SourceFile column' unless $found or $err; + return $err ? "$err in $file" : undef; +} + +#------------------------------------------------------------------------------ +# Convert unicode code point to UTF-8 +# Inputs: 0) integer Unicode character +# Returns: UTF-8 bytes +sub ToUTF8($) +{ + require Image::ExifTool::Charset; + return Image::ExifTool::Charset::Recompose(undef, [$_[0]], $charset); +} + +#------------------------------------------------------------------------------ +# Read JSON object from file +# Inputs: 0) RAF reference or undef, 1) optional scalar reference for data +# to read before reading from file (ie. the file read buffer) +# Returns: JSON object (scalar, hash ref, or array ref), or undef on EOF or +# empty object or array (and sets $$buffPt to empty string on EOF) +# Notes: position in buffer is significant +sub ReadJSONObject($;$) +{ + my ($raf, $buffPt) = @_; + # initialize buffer if necessary + my ($pos, $readMore, $rtnVal, $tok, $key, $didBOM); + if ($buffPt) { + $pos = pos $$buffPt; + $pos = pos($$buffPt) = 0 unless defined $pos; + } else { + my $buff = ''; + $buffPt = \$buff; + $pos = 0; + } +Tok: for (;;) { + # (didn't spend the time to understand how $pos could be undef, but + # put a test here to be safe because one user reported this problem) + last unless defined $pos; + if ($pos >= length $$buffPt or $readMore) { + last unless defined $raf; + # read another 64kB and add to unparsed data + my $offset = length($$buffPt) - $pos; + if ($offset) { + my $buff; + $raf->Read($buff, 65536) or $$buffPt = '', last; + $$buffPt = substr($$buffPt, $pos) . $buff; + } else { + $raf->Read($$buffPt, 65536) or $$buffPt = '', last; + } + unless ($didBOM) { + $$buffPt =~ s/^\xef\xbb\xbf//; # remove UTF-8 BOM if it exists + $didBOM = 1; + } + $pos = pos($$buffPt) = 0; + $readMore = 0; + } + unless ($tok) { + # skip white space and find next character + $$buffPt =~ /(\S)/g or $pos = length($$buffPt), next; + $tok = $1; + $pos = pos $$buffPt; + } + # see what type of object this is + if ($tok eq '{') { # object (hash) + $rtnVal = { } unless defined $rtnVal; + for (;;) { + # read "KEY":"VALUE" pairs + unless (defined $key) { + $key = ReadJSONObject($raf, $buffPt); + $pos = pos $$buffPt; + } + # ($key may be undef for empty JSON object) + if (defined $key) { + # scan to delimiting ':' + $$buffPt =~ /(\S)/g or $readMore = 1, next Tok; + $1 eq ':' or return undef; # error if not a colon + my $val = ReadJSONObject($raf, $buffPt); + $pos = pos $$buffPt; + return undef unless defined $val; + $$rtnVal{$key} = $val; + undef $key; + } + # scan to delimiting ',' or bounding '}' + $$buffPt =~ /(\S)/g or $readMore = 1, next Tok; + last if $1 eq '}'; # check for end of object + $1 eq ',' or return undef; # error if not a comma + } + } elsif ($tok eq '[') { # array + $rtnVal = [ ] unless defined $rtnVal; + for (;;) { + my $item = ReadJSONObject($raf, $buffPt); + $pos = pos $$buffPt; + # ($item may be undef for empty array) + push @$rtnVal, $item if defined $item; + # scan to delimiting ',' or bounding ']' + $$buffPt =~ /(\S)/g or $readMore = 1, next Tok; + last if $1 eq ']'; # check for end of array + $1 eq ',' or return undef; # error if not a comma + } + } elsif ($tok eq '"') { # quoted string + for (;;) { + $$buffPt =~ /(\\*)"/g or $readMore = 1, next Tok; + last unless length($1) & 1; # check for escaped quote + } + $rtnVal = substr($$buffPt, $pos, pos($$buffPt)-$pos-1); + # unescape characters + $rtnVal =~ s/\\u([0-9a-f]{4})/ToUTF8(hex $1)/ige; + $rtnVal =~ s/\\(.)/$unescapeJSON{$1}||$1/sge; + # decode base64 (binary data) values + if ($rtnVal =~ /^base64:[A-Za-z0-9+\/]*={0,2}$/ and length($rtnVal) % 4 == 3) { + require Image::ExifTool::XMP; + $rtnVal = ${Image::ExifTool::XMP::DecodeBase64(substr($rtnVal,7))}; + } + } elsif ($tok eq ']' or $tok eq '}' or $tok eq ',') { + # return undef for empty object, array, or list item + # (empty list item actually not valid JSON) + pos($$buffPt) = pos($$buffPt) - 1; + } else { # number, 'true', 'false', 'null' + $$buffPt =~ /([\s:,\}\]])/g or $readMore = 1, next; + pos($$buffPt) = pos($$buffPt) - 1; + $rtnVal = $tok . substr($$buffPt, $pos, pos($$buffPt)-$pos); + } + last; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Read JSON file +# Inputs: 0) JSON file name, file ref or RAF ref, 1) database hash ref, +# 2) flag to delete "-" tags, 3) character set +# Returns: undef on success, or error string +sub ReadJSON($$;$$) +{ + local $_; + my ($file, $database, $missingValue, $chset) = @_; + my ($raf, $openedFile); + + # initialize character set for converting "\uHHHH" chars + $charset = $chset || 'UTF8'; + if (UNIVERSAL::isa($file, 'File::RandomAccess')) { + $raf = $file; + $file = 'JSON file'; + } elsif (ref $file eq 'GLOB') { + $raf = new File::RandomAccess($file); + $file = 'JSON file'; + } else { + open JSONFILE, $file or return "Error opening JSON file '${file}'"; + binmode JSONFILE; + $openedFile = 1; + $raf = new File::RandomAccess(\*JSONFILE); + } + my $obj = ReadJSONObject($raf); + close JSONFILE if $openedFile; + unless (ref $obj eq 'ARRAY') { + ref $obj eq 'HASH' or return "Format error in JSON file '${file}'"; + $obj = [ $obj ]; + } + my ($info, $found); + foreach $info (@$obj) { + next unless ref $info eq 'HASH'; + # fix "SourceFile" case, or assume '*' if SourceFile not specified + unless (defined $$info{SourceFile}) { + my ($key) = grep /^SourceFile$/i, keys %$info; + if ($key) { + $$info{SourceFile} = $$info{$key}; + delete $$info{$key}; + } else { + $$info{SourceFile} = '*'; + } + } + if (defined $missingValue) { + $$info{$_} eq $missingValue and $$info{$_} = undef foreach keys %$info; + } + $$database{$$info{SourceFile}} = $info; + $found = 1; + } + return $found ? undef : "No valid JSON objects in '${file}'"; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Import - Import CSV and JSON database files + +=head1 SYNOPSIS + + use Image::ExifTool::Import qw(ReadCSV ReadJSON); + + $err = ReadCSV($csvFile, \%database); + + $err = ReadJSON($jsonfile, \%database); + +=head1 DESCRIPTION + +This module contains routines for importing tag information from CSV (Comma +Separated Value) and JSON (JavaScript Object Notation) database files. + +=head1 EXPORTS + +Exports nothing by default, but ReadCSV and ReadJSON may be exported. + +=head1 METHODS + +=head2 ReadCSV / ReadJSON + +Read CSV or JSON file into a database hash. + +=over 4 + +=item Inputs: + +0) CSV file name or file reference. + +1) Hash reference for database object. + +2) Optional string used to represent an undefined (missing) tag value. +(Used for deleting tags.) + +3) For ReadCSV this gives the delimiter for CSV entries, with a default of +",". For ReadJSON this is the character set for converting Unicode escape +sequences in strings, with a default of "UTF8". See the ExifTool Charset +option for a list of valid character sets. + +=item Return Value: + +These functions return an error string, or undef on success and populate the +database hash with entries from the CSV or JSON file. Entries are keyed +based on the SourceFile column of the CSV or JSON information, and are +stored as hash lookups of tag name/value for each SourceFile. + +=back + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/InDesign.pm b/ExifTool/lib/Image/ExifTool/InDesign.pm new file mode 100644 index 0000000..da71328 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/InDesign.pm @@ -0,0 +1,279 @@ +#------------------------------------------------------------------------------ +# File: InDesign.pm +# +# Description: Read/write meta information in Adobe InDesign files +# +# Revisions: 2009-06-17 - P. Harvey Created +# +# References: 1) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::InDesign; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.06'; + +# map for writing metadata to InDesign files (currently only write XMP) +my %indMap = ( + XMP => 'IND', +); + +# GUID's used in InDesign files +my $masterPageGUID = "\x06\x06\xed\xf5\xd8\x1d\x46\xe5\xbd\x31\xef\xe7\xfe\x74\xb7\x1d"; +my $objectHeaderGUID = "\xde\x39\x39\x79\x51\x88\x4b\x6c\x8E\x63\xee\xf8\xae\xe0\xdd\x38"; +my $objectTrailerGUID = "\xfd\xce\xdb\x70\xf7\x86\x4b\x4f\xa4\xd3\xc7\x28\xb3\x41\x71\x06"; + +#------------------------------------------------------------------------------ +# Read or write meta information in an InDesign file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid InDesign file, or -1 on write error +sub ProcessIND($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my ($hdr, $buff, $buf2, $err, $writeLen, $foundXMP); + + # validate the InDesign file + return 0 unless $raf->Read($hdr, 16) == 16; + return 0 unless $hdr eq $masterPageGUID; + return 0 unless $raf->Read($buff, 8) == 8; + $et->SetFileType($buff eq 'DOCUMENT' ? 'INDD' : 'IND'); # set the FileType tag + + # read the master pages + $raf->Seek(0, 0) or $err = 'Seek error', goto DONE; + unless ($raf->Read($buff, 4096) == 4096 and + $raf->Read($buf2, 4096) == 4096) + { + $err = 'Unexpected end of file'; + goto DONE; # (goto's can be our friend) + } + SetByteOrder('II'); + unless ($buf2 =~ /^\Q$masterPageGUID/) { + $err = 'Second master page is invalid'; + goto DONE; + } + my $seq1 = Get64u(\$buff, 264); + my $seq2 = Get64u(\$buf2, 264); + # take the most current master page + my $curPage = $seq2 > $seq1 ? \$buf2 : \$buff; + # byte order of stream data may be different than headers + my $streamInt32u = Get8u($curPage, 24); + if ($streamInt32u == 1) { + $streamInt32u = 'V'; # little-endian int32u + } elsif ($streamInt32u == 2) { + $streamInt32u = 'N'; # big-endian int32u + } else { + $err = 'Invalid stream byte order'; + goto DONE; + } + my $pages = Get32u($curPage, 280); + $pages < 2 and $err = 'Invalid page count', goto DONE; + my $pos = $pages * 4096; + if ($pos > 0x7fffffff and not $et->Options('LargeFileSupport')) { + $err = 'InDesign files larger than 2 GB not supported (LargeFileSupport not set)'; + goto DONE; + } + if ($outfile) { + # make XMP the preferred group for writing + $et->InitWriteDirs(\%indMap, 'XMP'); + + Write($outfile, $buff, $buf2) or $err = 1, goto DONE; + my $result = Image::ExifTool::CopyBlock($raf, $outfile, $pos - 8192); + unless ($result) { + $err = defined $result ? 'Error reading InDesign database' : 1; + goto DONE; + } + $writeLen = 0; + } else { + $raf->Seek($pos, 0) or $err = 'Seek error', goto DONE; + } + # scan through the contiguous objects for XMP + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + for (;;) { + $raf->Read($hdr, 32) or last; + unless (length($hdr) == 32 and $hdr =~ /^\Q$objectHeaderGUID/) { + # this must be null padding or we have an error + $hdr =~ /^\0+$/ or $err = 'Corrupt file or unsupported InDesign version'; + last; + } + my $len = Get32u(\$hdr, 24); + if ($verbose) { + printf $out "Contiguous object at offset 0x%x (%d bytes):\n", $raf->Tell(), $len; + if ($verbose > 2) { + my $len2 = $len < 1024000 ? $len : 1024000; + $raf->Seek(-$raf->Read($buff, $len2), 1) or $err = 1; + $et->VerboseDump(\$buff, Addr => $raf->Tell()); + } + } + # check for XMP if stream data is long enough + # (56 bytes is just enough for XMP header) + if ($len > 56) { + $raf->Read($buff, 56) == 56 or $err = 'Unexpected end of file', last; + if ($buff =~ /^(....)<\?xpacket begin=(['"])\xef\xbb\xbf\2 id=(['"])W5M0MpCehiHzreSzNTczkc9d\3/s) { + my $lenWord = $1; # save length word for writing later + $len -= 4; # get length of XMP only + $foundXMP = 1; + # I have a sample where the XMP is 107 MB, and ActivePerl may run into + # memory troubles (with its apparent 1 GB limit) if the XMP is larger + # than about 400 MB, so guard against this + if ($len > 300 * 1024 * 1024) { + my $msg = sprintf('Insanely large XMP (%.0f MB)', $len / (1024 * 1024)); + if ($outfile) { + $et->Error($msg, 2) and $err = 1, last; + } elsif ($et->Options('IgnoreMinorErrors')) { + $et->Warn($msg); + } else { + $et->Warn("$msg. Ignored.", 1); + $err = 1; + last; + } + } + # load and parse the XMP data + unless ($raf->Seek(-52, 1) and $raf->Read($buff, $len) == $len) { + $err = 'Error reading XMP stream'; + last; + } + my %dirInfo = ( + DataPt => \$buff, + Parent => 'IND', + NoDelete => 1, # do not allow this to be deleted when writing + ); + # validate xmp data length (should be same as length in header - 4) + my $xmpLen = unpack($streamInt32u, $lenWord); + unless ($xmpLen == $len) { + if ($xmpLen < $len) { + $dirInfo{DirLen} = $xmpLen; + } else { + $err = 'Truncated XMP stream (missing ' . ($xmpLen - $len) . ' bytes)'; + } + } + my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); + if ($outfile) { + last if $err; + # make sure that XMP is writable + my $classID = Get32u(\$hdr, 20); + $classID & 0x40000000 or $err = 'XMP stream is not writable', last; + my $xmp = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + if ($xmp and length $xmp) { + # write new xmp with leading length word + $buff = pack($streamInt32u, length $xmp) . $xmp; + # update header with new length and invalid checksum + Set32u(length($buff), \$hdr, 24); + Set32u(0xffffffff, \$hdr, 28); + } else { + $$et{CHANGED} = 0; # didn't change anything + $et->Warn("Can't delete XMP as a block from InDesign file") if defined $xmp; + # put length word back at start of stream + $buff = $lenWord . $buff; + } + } else { + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + $len = 0; # we got the full stream (nothing left to read) + } else { + $len -= 56; # we got 56 bytes of the stream + } + } else { + $buff = ''; # must reset this for writing later + } + if ($outfile) { + # write object header and data + Write($outfile, $hdr, $buff) or $err = 1, last; + my $result = Image::ExifTool::CopyBlock($raf, $outfile, $len); + unless ($result) { + $err = defined $result ? 'Truncated stream data' : 1; + last; + } + $writeLen += 32 + length($buff) + $len; + } elsif ($len) { + # skip over remaining stream data + $raf->Seek($len, 1) or $err = 'Seek error', last; + } + $raf->Read($buff, 32) == 32 or $err = 'Unexpected end of file', last; + unless ($buff =~ /^\Q$objectTrailerGUID/) { + $err = 'Invalid object trailer'; + last; + } + if ($outfile) { + # make sure object UID and ClassID are the same in the trailer + substr($hdr,16,8) eq substr($buff,16,8) or $err = 'Non-matching object trailer', last; + # write object trailer + Write($outfile, $objectTrailerGUID, substr($hdr,16)) or $err = 1, last; + $writeLen += 32; + } + } + if ($outfile) { + # write null padding if necessary + # (InDesign files must be an even number of 4096-byte blocks) + my $part = $writeLen % 4096; + Write($outfile, "\0" x (4096 - $part)) or $err = 1 if $part; + } +DONE: + if (not $err) { + $et->Warn('No XMP stream to edit') if $outfile and not $foundXMP; + return 1; # success! + } elsif (not $outfile) { + # issue warning on read error + $et->Warn($err) unless $err eq '1'; + } elsif ($err ne '1') { + # set error and return success code + $et->Error($err); + } else { + return -1; # write error + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::InDesign - Read/write meta information in Adobe InDesign files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read XMP +meta information from Adobe InDesign (.IND, .INDD and .INDT) files. + +=head1 LIMITATIONS + +1) Only XMP meta information is processed. + +2) A new XMP stream may not be created, so XMP tags may only be written to +InDesign files which previously contained XMP. + +3) File sizes of greater than 2 GB are supported only if the system supports +them and the LargeFileSupport option is enabled. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/InfiRay.pm b/ExifTool/lib/Image/ExifTool/InfiRay.pm new file mode 100644 index 0000000..061a45f --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/InfiRay.pm @@ -0,0 +1,227 @@ +#------------------------------------------------------------------------------ +# File: InfiRay.pm +# +# Description: InfiRay IJPEG thermal image metadata +# +# Revisions: 2023-02-08 - M. Del Sol Created +# +# References: 1) https://github.com/exiftool/exiftool/pull/184 +# +# Notes: Information in this document has been mostly gathered by +# disassembling the P2 Pro Android app, version 1.0.8.230111. +#------------------------------------------------------------------------------ + +package Image::ExifTool::InfiRay; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.00'; + +my %convFloat2 = ( PrintConv => 'sprintf("%.2f", $val)' ); +my %convPercent = ( PrintConv => 'sprintf("%.1f %%", $val * 100)' ); +my %convMeters = ( PrintConv => 'sprintf("%.2f m", $val)' ); +my %convCelsius = ( PrintConv => 'sprintf("%.2f C", $val)' ); + +# InfiRay IJPEG version header, found in JPEGs APP2 +%Image::ExifTool::InfiRay::Version = ( + GROUPS => { 0 => 'APP2', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ + This table lists tags found in the InfiRay APP2 IJPEG version header, found + in JPEGs taken with the P2 Pro camera app. + }, + 0x00 => { Name => 'IJPEGVersion', Format => 'int8u[4]' }, + # 0x04 => { Name => 'IJPEGSignature', Format => 'string[6]' }, # "IJPEG\0" + 0x0c => { Name => 'IJPEGOrgType', Format => 'int8u' }, + 0x0d => { Name => 'IJPEGDispType', Format => 'int8u' }, + 0x0e => { Name => 'IJPEGRotate', Format => 'int8u' }, + 0x0f => { Name => 'IJPEGMirrorFlip', Format => 'int8u' }, + 0x10 => { Name => 'ImageColorSwitchable', Format => 'int8u' }, + 0x11 => { Name => 'ThermalColorPalette', Format => 'int16u' }, + 0x20 => { Name => 'IRDataSize', Format => 'int64u' }, + 0x28 => { Name => 'IRDataFormat', Format => 'int16u' }, + 0x2a => { Name => 'IRImageWidth', Format => 'int16u' }, + 0x2c => { Name => 'IRImageHeight', Format => 'int16u' }, + 0x2e => { Name => 'IRImageBpp', Format => 'int8u' }, + 0x30 => { Name => 'TempDataSize', Format => 'int64u' }, + 0x38 => { Name => 'TempDataFormat', Format => 'int16u' }, + 0x3a => { Name => 'TempImageWidth', Format => 'int16u' }, + 0x3c => { Name => 'TempImageHeight', Format => 'int16u' }, + 0x3e => { Name => 'TempImageBpp', Format => 'int8u' }, + 0x40 => { Name => 'VisibleDataSize', Format => 'int64u' }, + 0x48 => { Name => 'VisibleDataFormat', Format => 'int16u' }, + 0x4a => { Name => 'VisibleImageWidth', Format => 'int16u' }, + 0x4c => { Name => 'VisibleImageHeight', Format => 'int16u' }, + 0x4e => { Name => 'VisibleImageBpp', Format => 'int8u' }, +); + +# InfiRay IJPEG factory temperature, found in IJPEG's APP4 section +%Image::ExifTool::InfiRay::Factory = ( + GROUPS => { 0 => 'APP4', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ + This table lists tags found in the InfiRay APP4 IJPEG camera factory + defaults and calibration data. + }, + 0x00 => { Name => 'IJPEGTempVersion', Format => 'int8u[4]' }, + 0x04 => { Name => 'FactDefEmissivity', Format => 'int8s' }, + 0x05 => { Name => 'FactDefTau', Format => 'int8s' }, + 0x06 => { Name => 'FactDefTa', Format => 'int16s' }, + 0x08 => { Name => 'FactDefTu', Format => 'int16s' }, + 0x0a => { Name => 'FactDefDist', Format => 'int16s' }, + 0x0c => { Name => 'FactDefA0', Format => 'int32s' }, + 0x10 => { Name => 'FactDefB0', Format => 'int32s' }, + 0x14 => { Name => 'FactDefA1', Format => 'int32s' }, + 0x18 => { Name => 'FactDefB1', Format => 'int32s' }, + 0x1c => { Name => 'FactDefP0', Format => 'int32s' }, + 0x20 => { Name => 'FactDefP1', Format => 'int32s' }, + 0x24 => { Name => 'FactDefP2', Format => 'int32s' }, + 0x44 => { Name => 'FactRelSensorTemp', Format => 'int16s' }, + 0x46 => { Name => 'FactRelShutterTemp', Format => 'int16s' }, + 0x48 => { Name => 'FactRelLensTemp', Format => 'int16s' }, + 0x64 => { Name => 'FactStatusGain', Format => 'int8s' }, + 0x65 => { Name => 'FactStatusEnvOK', Format => 'int8s' }, + 0x66 => { Name => 'FactStatusDistOK', Format => 'int8s' }, + 0x67 => { Name => 'FactStatusTempMap', Format => 'int8s' }, + # Missing: ndist_table_len, ndist_table, nuc_t_table_len, nuc_t_table +); + +# InfiRay IJPEG picture temperature information, found in IJPEG's APP5 section +%Image::ExifTool::InfiRay::Picture = ( + GROUPS => { 0 => 'APP5', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ + This table lists tags found in the InfiRay APP5 IJPEG picture temperature + information. + }, + 0x00 => { Name => 'EnvironmentTemp', Format => 'float', %convCelsius }, + 0x04 => { Name => 'Distance', Format => 'float', %convMeters }, + 0x08 => { Name => 'Emissivity', Format => 'float', %convFloat2 }, + 0x0c => { Name => 'Humidity', Format => 'float', %convPercent }, + 0x10 => { Name => 'ReferenceTemp', Format => 'float', %convCelsius }, + 0x20 => { Name => 'TempUnit', Format => 'int8u' }, + 0x21 => { Name => 'ShowCenterTemp', Format => 'int8u' }, + 0x22 => { Name => 'ShowMaxTemp', Format => 'int8u' }, + 0x23 => { Name => 'ShowMinTemp', Format => 'int8u' }, + 0x24 => { Name => 'TempMeasureCount', Format => 'int16u' }, + # TODO: process extra measurements list +); + +# InfiRay IJPEG visual-infrared mixing mode, found in IJPEG's APP6 section +%Image::ExifTool::InfiRay::MixMode = ( + GROUPS => { 0 => 'APP6', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ + This table lists tags found in the InfiRay APP6 IJPEG visual-infrared mixing + mode section. + }, + 0x00 => { Name => 'MixMode', Format => 'int8u' }, + 0x01 => { Name => 'FusionIntensity', Format => 'float', %convPercent }, + 0x05 => { Name => 'OffsetAdjustment', Format => 'float' }, + 0x09 => { Name => 'CorrectionAsix', Format => 'float[30]' }, +); + +# InfiRay IJPEG camera operation mode, found in IJPEG's APP7 section +# +# I do not know in what units these times are, or what do they represent. +%Image::ExifTool::InfiRay::OpMode = ( + GROUPS => { 0 => 'APP7', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ + This table lists tags found in the InfiRay APP7 IJPEG camera operation mode + section. + }, + 0x00 => { Name => 'WorkingMode', Format => 'int8u' }, + 0x01 => { Name => 'IntegralTime', Format => 'int32u' }, + 0x05 => { Name => 'IntegratTimeHdr', Format => 'int32u' }, + 0x09 => { Name => 'GainStable', Format => 'int8u' }, + 0x0a => { Name => 'TempControlEnable', Format => 'int8u' }, + 0x0b => { Name => 'DeviceTemp', Format => 'float', %convCelsius }, +); + +# InfiRay IJPEG isothermal information, found in IJPEG's APP8 section +# +# I have genuinely no clue what is the meaning of any of this information, or +# what is it used for. +%Image::ExifTool::InfiRay::Isothermal = ( + GROUPS => { 0 => 'APP8', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ + This table lists tags found in the InfiRay APP8 IJPEG picture isothermal + information. + }, + 0x00 => { Name => 'IsothermalMax', Format => 'float' }, + 0x04 => { Name => 'IsothermalMin', Format => 'float' }, + 0x08 => { Name => 'ChromaBarMax', Format => 'float' }, + 0x0c => { Name => 'ChromaBarMin', Format => 'float' }, +); + +# InfiRay IJPEG sensor information, found in IJPEG's APP9 section +%Image::ExifTool::InfiRay::Sensor = ( + GROUPS => { 0 => 'APP9', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ + This table lists tags found in the InfiRay APP9 IJPEG sensor information + chunk. + }, + 0x000 => { Name => 'IRSensorManufacturer', Format => 'string[12]' }, + 0x040 => { Name => 'IRSensorName', Format => 'string[12]' }, + 0x080 => { Name => 'IRSensorPartNumber', Format => 'string[32]' }, + 0x0c0 => { Name => 'IRSensorSerialNumber', Format => 'string[32]' }, + 0x100 => { Name => 'IRSensorFirmware', Format => 'string[12]' }, + 0x140 => { Name => 'IRSensorAperture', Format => 'float', %convFloat2 }, + 0x144 => { Name => 'IRFocalLength', Format => 'float', %convFloat2 }, + 0x180 => { Name => 'VisibleSensorManufacturer', Format => 'string[12]' }, + 0x1c0 => { Name => 'VisibleSensorName', Format => 'string[12]' }, + 0x200 => { Name => 'VisibleSensorPartNumber', Format => 'string[32]' }, + 0x240 => { Name => 'VisibleSensorSerialNumber', Format => 'string[32]' }, + 0x280 => { Name => 'VisibleSensorFirmware', Format => 'string[12]' }, + 0x2c0 => { Name => 'VisibleSensorAperture', Format => 'float' }, + 0x2c4 => { Name => 'VisibleFocalLength', Format => 'float' }, +); + +__END__ + +=head1 NAME + +Image::ExifTool::InfiRay - InfiRay IJPEG thermal image metadata + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +metadata and thermal-related information of pictures saved by the InfiRay +IJPEG SDK, used in cameras such as the P2 Pro. + +=head1 AUTHOR + +Copyright 2003-2023, Marcos Del Sol Vives (marcos at orca.pet) + +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<https://github.com/exiftool/exiftool/pull/184> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/InfiRay Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/JPEG.pm b/ExifTool/lib/Image/ExifTool/JPEG.pm new file mode 100644 index 0000000..bcd8d09 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/JPEG.pm @@ -0,0 +1,779 @@ +#------------------------------------------------------------------------------ +# File: JPEG.pm +# +# Description: Definitions for uncommon JPEG segments +# +# Revisions: 10/06/2006 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::JPEG; +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.35'; + +sub ProcessOcad($$$); +sub ProcessJPEG_HDR($$$); + +# (most of the tags in this table are for documentation purposes only) +%Image::ExifTool::JPEG::Main = ( + NOTES => q{ + This table lists information extracted by ExifTool from JPEG images. See + L<https://www.w3.org/Graphics/JPEG/jfif3.pdf> for the JPEG specification. + }, + APP0 => [{ + Name => 'JFIF', + Condition => '$$valPt =~ /^JFIF\0/', + SubDirectory => { TagTable => 'Image::ExifTool::JFIF::Main' }, + }, { + Name => 'JFXX', + Condition => '$$valPt =~ /^JFXX\0\x10/', + SubDirectory => { TagTable => 'Image::ExifTool::JFIF::Extension' }, + }, { + Name => 'CIFF', + Condition => '$$valPt =~ /^(II|MM).{4}HEAPJPGM/s', + SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::Main' }, + }, { + Name => 'AVI1', + Condition => '$$valPt =~ /^AVI1/', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::AVI1' }, + }, { + Name => 'Ocad', + Condition => '$$valPt =~ /^Ocad/', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::Ocad' }, + }], + APP1 => [{ + Name => 'EXIF', + Condition => '$$valPt =~ /^Exif\0/', + SubDirectory => { TagTable => 'Image::ExifTool::Exif::Main' }, + }, { + Name => 'ExtendedXMP', + Condition => '$$valPt =~ m{^http://ns.adobe.com/xmp/extension/\0}', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, { + Name => 'XMP', + Condition => '$$valPt =~ /^http/ or $$valPt =~ /<exif:/', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, { + Name => 'QVCI', + Condition => '$$valPt =~ /^QVCI\0/', + SubDirectory => { TagTable => 'Image::ExifTool::Casio::QVCI' }, + }, { + Name => 'FLIR', + Condition => '$$valPt =~ /^FLIR\0/', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::FFF' }, + }, { + Name => 'RawThermalImage', # (from Parrot Bebop-Pro Thermal drone) + Condition => '$$valPt =~ /^PARROT\0(II\x2a\0|MM\0\x2a)/', + Groups => { 0 => 'APP1', 1 => 'Parrot', 2 => 'Preview' }, + Notes => 'thermal image from Parrot Bebop-Pro Thermal drone', + RawConv => 'substr($val, 7)', + Binary => 1, + }], + APP2 => [{ + Name => 'ICC_Profile', + Condition => '$$valPt =~ /^ICC_PROFILE\0/', + SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' }, + }, { + Name => 'FPXR', + Condition => '$$valPt =~ /^FPXR\0/', + SubDirectory => { TagTable => 'Image::ExifTool::FlashPix::Main' }, + }, { + Name => 'MPF', + Condition => '$$valPt =~ /^MPF\0/', + SubDirectory => { TagTable => 'Image::ExifTool::MPF::Main' }, + }, { + Name => 'InfiRayVersion', + Condition => '$$valPt =~ /^....IJPEG\0/s', + SubDirectory => { TagTable => 'Image::ExifTool::InfiRay::Version' }, + }, { + Name => 'PreviewImage', + Condition => '$$valPt =~ /^(|QVGA\0|BGTH)\xff\xd8\xff\xdb/', + Notes => 'Samsung APP2 preview image', # (Samsung/GoPro="", BenQ="QVGA\0", Digilife="BGTH") + }], + APP3 => [{ + Name => 'Meta', + Condition => '$$valPt =~ /^(Meta|META|Exif)\0\0/', + SubDirectory => { TagTable => 'Image::ExifTool::Kodak::Meta' }, + }, { + Name => 'Stim', + Condition => '$$valPt =~ /^Stim\0/', + SubDirectory => { TagTable => 'Image::ExifTool::Stim::Main' }, + }, { + Name => 'JPS', + Condition => '$$valPt =~ /^_JPSJPS_/', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::JPS' }, + }, { + Name => 'ThermalData', # (written by DJI FLIR models) + Condition => '$$self{Make} eq "DJI"', + Notes => 'DJI raw thermal data', + Groups => { 0 => 'APP3', 1 => 'DJI', 2 => 'Image' }, + Binary => 1, + }, { + Name => 'ImagingData', # (written by InfiRay models) + Condition => '$$self{HasIJPEG}', + Notes => 'InfiRay IR+thermal+visible data', + Groups => { 0 => 'APP3', 1 => 'InfiRay', 2 => 'Image' }, + Binary => 1, + }, { + Name => 'PreviewImage', # (written by HP R837 and Samsung S1060) + Condition => '$$valPt =~ /^\xff\xd8\xff\xdb/', + Notes => 'Samsung/HP preview image', # (Samsung, HP, BenQ) + }], + APP4 => [{ + Name => 'Scalado', + Condition => '$$valPt =~ /^SCALADO\0/', + SubDirectory => { TagTable => 'Image::ExifTool::Scalado::Main' }, + }, { + Name => 'FPXR', # (non-standard location written by some HP models) + Condition => '$$valPt =~ /^FPXR\0/', + SubDirectory => { TagTable => 'Image::ExifTool::FlashPix::Main' }, + }, { + Name => 'InfiRayFactory', + Condition => '$$self{HasIJPEG}"', + SubDirectory => { TagTable => 'Image::ExifTool::InfiRay::Factory' }, + }, { + Name => 'ThermalParams', # (written by some DJI FLIR models) + Condition => '$$self{Make} eq "DJI" and $$valPt =~ /^\xaa\x55\x12\x06/', + SubDirectory => { TagTable => 'Image::ExifTool::DJI::ThermalParams' }, + }, { + Name => 'ThermalParams2', # (written by M3T) + Condition => '$$self{Make} eq "DJI" and $$valPt =~ /^(.{32})?.{32}\x2c\x01\x20\0/s', + SubDirectory => { TagTable => 'Image::ExifTool::DJI::ThermalParams2' }, + }, { + Name => 'ThermalParams3', # (written by M30T) + Condition => '$$self{Make} eq "DJI" and $$valPt =~ /^.{32}\xaa\x55\x38\0/s', + SubDirectory => { TagTable => 'Image::ExifTool::DJI::ThermalParams3' }, + }, { + Name => 'PreviewImage', # (eg. Samsung S1060) + Notes => 'continued from APP3', + }], + APP5 => [{ + Name => 'RMETA', + Condition => '$$valPt =~ /^RMETA\0/', + SubDirectory => { TagTable => 'Image::ExifTool::Ricoh::RMETA' }, + }, { + Name => 'SamsungUniqueID', + Condition => '$$valPt =~ /ssuniqueid\0/', + SubDirectory => { TagTable => 'Image::ExifTool::Samsung::APP5' }, + }, { + Name => 'InfiRayPicture', + Condition => '$$self{HasIJPEG}', + SubDirectory => { TagTable => 'Image::ExifTool::InfiRay::Picture' }, + }, { + Name => 'ThermalCalibration', # (written by DJI FLIR models) + Condition => '$$self{Make} eq "DJI"', + Notes => 'DJI thermal calibration data', + Groups => { 0 => 'APP5', 1 => 'DJI', 2 => 'Image' }, + Binary => 1, + }, { + Name => 'PreviewImage', # (eg. BenQ DC E1050) + Notes => 'continued from APP4', + }], + APP6 => [{ + Name => 'EPPIM', + Condition => '$$valPt =~ /^EPPIM\0/', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::EPPIM' }, + }, { + Name => 'NITF', + Condition => '$$valPt =~ /^NTIF\0/', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::NITF' }, + }, { + Name => 'HP_TDHD', # (written by R837) + Condition => '$$valPt =~ /^TDHD\x01\0\0\0/', + SubDirectory => { TagTable => 'Image::ExifTool::HP::TDHD' }, + }, { + Name => 'GoPro', + Condition => '$$valPt =~ /^GoPro\0/', + SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' }, + }, { + Name => 'InfiRayMixMode', + Condition => '$$self{HasIJPEG}', + SubDirectory => { TagTable => 'Image::ExifTool::InfiRay::MixMode' }, + }, { + Name => 'DJI_DTAT', # (written by ZH20T) + Condition => '$$valPt =~ /^DTAT\0\0.\{/s', + Groups => { 0 => 'APP6', 1 => 'DJI' }, + Notes => 'DJI Thermal Analysis Tool record', + ValueConv => 'substr($val,7)', + # also seen Motorola APP6 "MMIMETA\0", with sub-types: AL3A,ALED,MMI0,MOTD,QC3A + }], + APP7 => [{ + Name => 'Pentax', + Condition => '$$valPt =~ /^PENTAX \0/', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::Main' }, + }, { + Name => 'Huawei', + Condition => '$$valPt =~ /^HUAWEI\0\0/', + SubDirectory => { TagTable => 'Image::ExifTool::Unknown::Main' }, + }, { + Name => 'Qualcomm', + Condition => '$$valPt =~ /^\x1aQualcomm Camera Attributes/', + SubDirectory => { TagTable => 'Image::ExifTool::Qualcomm::Main' }, + }, { + Name => 'InfiRayOpMode', + Condition => '$$self{HasIJPEG}', + SubDirectory => { TagTable => 'Image::ExifTool::InfiRay::OpMode' }, + }, { + Name => 'DJI-DBG', + Condition => '$$valPt =~ /^DJI-DBG\0/', + SubDirectory => { TagTable => 'Image::ExifTool::DJI::Info' }, + }], + APP8 => [{ + Name => 'SPIFF', + Condition => '$$valPt =~ /^SPIFF\0/', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::SPIFF' }, + }, { + Name => 'InfiRayIsothermal', + Condition => '$$self{HasIJPEG}', + SubDirectory => { TagTable => 'Image::ExifTool::InfiRay::Isothermal' }, + }], + APP9 => [{ + Name => 'MediaJukebox', + Condition => '$$valPt =~ /^Media Jukebox\0/', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::MediaJukebox' }, + }, { + Name => 'InfiRaySensor', + Condition => '$$self{HasIJPEG}', + SubDirectory => { TagTable => 'Image::ExifTool::InfiRay::Sensor' }, + }], + APP10 => { + Name => 'Comment', + Condition => '$$valPt =~ /^UNICODE\0/', + Notes => 'PhotoStudio Unicode comment', + }, + APP11 => [{ + Name => 'JPEG-HDR', + Condition => '$$valPt =~ /^HDR_RI /', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::HDR' }, + },{ + Name => 'JUMBF', + Condition => '$$valPt =~ /^JP/', + SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::Main' }, + # Note: The recommended options for reading C2PA JUMBF metadata are "-G3 -b -j -u" + }], + APP12 => [{ + Name => 'PictureInfo', + Condition => '$$valPt =~ /(\[picture info\]|Type=)/', + SubDirectory => { TagTable => 'Image::ExifTool::APP12::PictureInfo' }, + }, { + Name => 'Ducky', + Condition => '$$valPt =~ /^Ducky/', + SubDirectory => { TagTable => 'Image::ExifTool::APP12::Ducky' }, + }], + APP13 => [{ + Name => 'Photoshop', + Condition => '$$valPt =~ /^(Photoshop 3.0\0|Adobe_Photoshop2.5)/', + SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Main' }, + }, { + Name => 'Adobe_CM', + Condition => '$$valPt =~ /^Adobe_CM/', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::AdobeCM' }, + }], + APP14 => { + Name => 'Adobe', + Condition => '$$valPt =~ /^Adobe/', + Writable => 2, # (for docs only) + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::Adobe' }, + }, + APP15 => { + Name => 'GraphicConverter', + Condition => '$$valPt =~ /^Q\s*(\d+)/', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::GraphConv' }, + }, + # APP15 - Also unknown "TEXT\0" segment stored by Casio/FujiFilm + COM => { + Name => 'Comment', + # note: flag as writable for documentation, but it won't show up + # in the TagLookup as writable because there is no WRITE_PROC + Writable => 2, + }, + SOF => { + Name => 'StartOfFrame', + SubDirectory => { TagTable => 'Image::ExifTool::JPEG::SOF' }, + }, + DQT => { + Name => 'DefineQuantizationTable', + Notes => 'used to calculate the Extra JPEGDigest tag value', + }, + Trailer => [{ + Name => 'AFCP', + Condition => '$$valPt =~ /AXS(!|\*).{8}$/s', + SubDirectory => { TagTable => 'Image::ExifTool::AFCP::Main' }, + }, { + Name => 'CanonVRD', + Condition => '$$valPt =~ /CANON OPTIONAL DATA\0.{44}$/s', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::Main' }, + }, { + Name => 'FotoStation', + Condition => '$$valPt =~ /\xa1\xb2\xc3\xd4$/', + SubDirectory => { TagTable => 'Image::ExifTool::FotoStation::Main' }, + }, { + Name => 'PhotoMechanic', + Condition => '$$valPt =~ /cbipcbbl$/', + SubDirectory => { TagTable => 'Image::ExifTool::PhotoMechanic::Main' }, + }, { + Name => 'MIE', + Condition => q{ + $$valPt =~ /~\0\x04\0zmie~\0\0\x06.{4}[\x10\x18]\x04$/s or + $$valPt =~ /~\0\x04\0zmie~\0\0\x0a.{8}[\x10\x18]\x08$/s + }, + SubDirectory => { TagTable => 'Image::ExifTool::MIE::Main' }, + }, { + Name => 'Samsung', + Condition => '$$valPt =~ /QDIOBS$/', + SubDirectory => { TagTable => 'Image::ExifTool::Samsung::Trailer' }, + }, { + Name => 'EmbeddedVideo', + Notes => 'extracted only when ExtractEmbedded option is used', + Condition => '$$valPt =~ /^.{4}ftyp/s', + }, { + Name => 'Insta360', + Condition => '$$valPt =~ /8db42d694ccc418790edff439fe026bf$/', + }, { + Name => 'NikonApp', + Condition => '$$valPt =~ m(\0{6}/NIKON APP$)', + Notes => 'contains editing information in XMP format', + }, { + Name => 'PreviewImage', + Condition => '$$valPt =~ /^\xff\xd8\xff/', + Writable => 2, # (for docs only) + }], +); + +# JPS APP3 segment (ref http://paulbourke.net/stereographics/stereoimage/) +%Image::ExifTool::JPEG::JPS = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'APP3', 1 => 'JPS', 2 => 'Image' }, + NOTES => 'Tags found in JPEG Stereo (JPS) images.', + 0x0a => { + Name => 'JPSSeparation', + Format => 'int32u', # (just so we can look ahead to MediaType); + Notes => 'stereo only', + RawConv => q{ + $$self{MediaType} = $val & 0xff; + return undef unless $$self{MediaType} == 1; + return(($val >> 24) & 0xff); + }, + }, + 0x08 => { + Name => 'HdrLength', + Format => 'int16u', + Hidden => 1, + RawConv => '$$self{HdrLength} = $val; undef', + }, + 0x0b => { + Name => 'JPSFlags', + PrintConv => { BITMASK => { + 0 => 'Half height', + 1 => 'Half width', + 2 => 'Left field first', + }}, + }, + 0x0c => [{ + Name => 'JPSLayout', + Condition => '$$self{MediaType} == 0', + Notes => 'mono', + PrintConv => { + 0 => 'Both Eyes', + 1 => 'Left Eye', + 2 => 'Right Eye', + }, + },{ + Name => 'JPSLayout', + Condition => '$$self{MediaType} == 1', + Notes => 'stereo', + PrintConv => { + 1 => 'Interleaved', + 2 => 'Side By Side', + 3 => 'Over Under', + 4 => 'Anaglyph', + }, + }], + 0x0d => { + Name => 'JPSType', + Hook => '$varSize += $$self{HdrLength} - 4', # comment starts after header block + PrintConv => { 0 => 'Mono', 1 => 'Stereo' }, + }, + # 0x0e - in16u comment length (ignored -- assume the remainder is all comment) + # (this is offset if we had a 4-byte JPS header block) + 0x10 => { + Name => 'JPSComment', + Format => 'string', + }, +); + +# EPPIM APP6 (Toshiba PrintIM) segment (ref PH, from PDR-M700 samples) +%Image::ExifTool::JPEG::EPPIM = ( + GROUPS => { 0 => 'APP6', 1 => 'EPPIM', 2 => 'Image' }, + NOTES => q{ + APP6 is used in by the Toshiba PDR-M700 to store a TIFF structure containing + PrintIM information. + }, + 0xc4a5 => { + Name => 'PrintIM', + # must set Writable here so this tag will be saved with MakerNotes option + # (but it isn't actually writable because there is no WRITE_PROC) + Writable => 'undef', + Description => 'Print Image Matching', + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, +); + +# APP8 SPIFF segment. Refs: +# 1) http://www.fileformat.info/format/spiff/ +# 2) http://www.jpeg.org/public/spiff.pdf +%Image::ExifTool::JPEG::SPIFF = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'APP8', 1 => 'SPIFF', 2 => 'Image' }, + NOTES => q{ + This information is found in APP8 of SPIFF-style JPEG images (the "official" + yet rarely used JPEG file format standard: Still Picture Interchange File + Format). See L<http://www.jpeg.org/public/spiff.pdf> for the official + specification. + }, + 0 => { + Name => 'SPIFFVersion', + Format => 'int8u[2]', + PrintConv => '$val =~ tr/ /./; $val', + }, + 2 => { + Name => 'ProfileID', + PrintConv => { + 0 => 'Not Specified', + 1 => 'Continuous-tone Base', + 2 => 'Continuous-tone Progressive', + 3 => 'Bi-level Facsimile', + 4 => 'Continuous-tone Facsimile', + }, + }, + 3 => 'ColorComponents', + 6 => { + Name => 'ImageHeight', + Notes => q{ + at index 4 in specification, but there are 2 extra bytes here in my only + SPIFF sample, version 1.2 + }, + Format => 'int32u', + }, + 10 => { + Name => 'ImageWidth', + Format => 'int32u', + }, + 14 => { + Name => 'ColorSpace', + PrintConv => { + 0 => 'Bi-level', + 1 => 'YCbCr, ITU-R BT 709, video', + 2 => 'No color space specified', + 3 => 'YCbCr, ITU-R BT 601-1, RGB', + 4 => 'YCbCr, ITU-R BT 601-1, video', + 8 => 'Gray-scale', + 9 => 'PhotoYCC', + 10 => 'RGB', + 11 => 'CMY', + 12 => 'CMYK', + 13 => 'YCCK', + 14 => 'CIELab', + }, + }, + 15 => 'BitsPerSample', + 16 => { + Name => 'Compression', + PrintConv => { + 0 => 'Uncompressed, interleaved, 8 bits per sample', + 1 => 'Modified Huffman', + 2 => 'Modified READ', + 3 => 'Modified Modified READ', + 4 => 'JBIG', + 5 => 'JPEG', + }, + }, + 17 => { + Name => 'ResolutionUnit', + PrintConv => { + 0 => 'None', + 1 => 'inches', + 2 => 'cm', + }, + }, + 18 => { + Name => 'YResolution', + Format => 'int32u', + }, + 22 => { + Name => 'XResolution', + Format => 'int32u', + }, +); + +# APP9 Media Jukebox segment (ref PH) +%Image::ExifTool::JPEG::MediaJukebox = ( + GROUPS => { 0 => 'XML', 1 => 'MediaJukebox', 2 => 'Image' }, + VARS => { NO_ID => 1 }, + NOTES => 'Tags found in the XML metadata of the APP9 "Media Jukebox" segment.', + Date => { + Groups => { 2 => 'Time' }, + # convert from days since Dec 30, 1899 to seconds since Jan 1, 1970 + ValueConv => 'ConvertUnixTime(($val - (70 * 365 + 17 + 2)) * 24 * 3600)', + PrintConv => '$self->ConvertDateTime($val)', + }, + Album => { }, + Caption => { }, + Keywords => { }, + Name => { }, + People => { }, + Places => { }, + Tool_Name => { }, + Tool_Version => { }, +); + +# JPEG-HDR APP11 information (ref PH, guessed from http://anyhere.com/gward/papers/cic05.pdf) +%Image::ExifTool::JPEG::HDR = ( + GROUPS => { 0 => 'APP11', 1 => 'JPEG-HDR', 2 => 'Image' }, + PROCESS_PROC => \&ProcessJPEG_HDR, + TAG_PREFIX => '', # (no prefix for unknown tags) + NOTES => 'Information extracted from APP11 of a JPEG-HDR image.', + ver => 'JPEG-HDRVersion', + # (need names for the next 3 tags) + ln0 => { Description => 'Ln0' }, + ln1 => { Description => 'Ln1' }, + s2n => { Description => 'S2n' }, + alp => { Name => 'Alpha' }, # (Alpha/Beta are saturation parameters) + bet => { Name => 'Beta' }, + cor => { Name => 'CorrectionMethod' }, + RatioImage => { + Groups => { 2 => 'Preview' }, + Notes => 'the embedded JPEG-compressed ratio image', + Binary => 1, + }, +); + +# AdobeCM APP13 (no references) +%Image::ExifTool::JPEG::AdobeCM = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'APP13', 1 => 'AdobeCM', 2 => 'Image' }, + NOTES => q{ + The APP13 "Adobe_CM" segment presumably contains color management + information, but the meaning of the data is currently unknown. If anyone + has an idea about what this means, please let me know. + }, + FORMAT => 'int16u', + 0 => 'AdobeCMType', +); + +# Adobe APP14 refs: +# http://partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf +# http://java.sun.com/j2se/1.5.0/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#color +%Image::ExifTool::JPEG::Adobe = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'APP14', 1 => 'Adobe', 2 => 'Image' }, + NOTES => q{ + The APP14 "Adobe" segment stores image encoding information for DCT filters. + This segment may be copied or deleted as a block using the Extra "Adobe" + tag, but note that it is not deleted by default when deleting all metadata + because it may affect the appearance of the image. + }, + FORMAT => 'int16u', + 0 => 'DCTEncodeVersion', + 1 => { + Name => 'APP14Flags0', + PrintConv => { + 0 => '(none)', + BITMASK => { + 15 => 'Encoded with Blend=1 downsampling' + }, + }, + }, + 2 => { + Name => 'APP14Flags1', + PrintConv => { + 0 => '(none)', + BITMASK => { }, + }, + }, + 3 => { + Name => 'ColorTransform', + Format => 'int8u', + PrintConv => { + 0 => 'Unknown (RGB or CMYK)', + 1 => 'YCbCr', + 2 => 'YCCK', + }, + }, +); + +# GraphicConverter APP15 (ref PH) +%Image::ExifTool::JPEG::GraphConv = ( + GROUPS => { 0 => 'APP15', 1 => 'GraphConv', 2 => 'Image' }, + NOTES => 'APP15 is used by GraphicConverter to store JPEG quality.', + 'Q' => 'Quality', +); + +# APP0 AVI1 segment (ref http://www.schnarff.com/file-formats/bmp/BMPDIB.TXT) +%Image::ExifTool::JPEG::AVI1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'APP0', 1 => 'AVI1', 2 => 'Image' }, + NOTES => 'This information may be found in APP0 of JPEG image data from AVI videos.', + FIRST_ENTRY => 0, + 0 => { + Name => 'InterleavedField', + PrintConv => { + 0 => 'Not Interleaved', + 1 => 'Odd', + 2 => 'Even', + }, + }, +); + +# APP0 Ocad segment (ref PH) +%Image::ExifTool::JPEG::Ocad = ( + PROCESS_PROC => \&ProcessOcad, + GROUPS => { 0 => 'APP0', 1 => 'Ocad', 2 => 'Image' }, + TAG_PREFIX => 'Ocad', + FIRST_ENTRY => 0, + NOTES => q{ + Tags extracted from the JPEG APP0 "Ocad" segment (found in Photobucket + images). + }, + Rev => { + Name => 'OcadRevision', + Format => 'string[6]', + } +); + +# APP6 NITF segment (National Imagery Transmission Format) +# ref http://www.gwg.nga.mil/ntb/baseline/docs/n010697/bwcguide25aug98.pdf +%Image::ExifTool::JPEG::NITF = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'APP6', 1 => 'NITF', 2 => 'Image' }, + NOTES => q{ + Information in APP6 used by the National Imagery Transmission Format. See + L<http://www.gwg.nga.mil/ntb/baseline/docs/n010697/bwcguide25aug98.pdf> for + the official specification. + }, + 0 => { + Name => 'NITFVersion', + Format => 'int8u[2]', + ValueConv => 'sprintf("%d.%.2d", split(" ",$val))', + }, + 2 => { + Name => 'ImageFormat', + ValueConv => 'chr($val & 0xff)', + PrintConv => { B => 'IMode B' }, + }, + 3 => { + Name => 'BlocksPerRow', + Format => 'int16u', + }, + 5 => { + Name => 'BlocksPerColumn', + Format => 'int16u', + }, + 7 => { + Name => 'ImageColor', + PrintConv => { 0 => 'Monochrome' }, + }, + 8 => 'BitDepth', + 9 => { + Name => 'ImageClass', + PrintConv => { + 0 => 'General Purpose', + 4 => 'Tactical Imagery', + }, + }, + 10 => { + Name => 'JPEGProcess', + PrintConv => { + 1 => 'Baseline sequential DCT, Huffman coding, 8-bit samples', + 4 => 'Extended sequential DCT, Huffman coding, 12-bit samples', + }, + }, + 11 => 'Quality', + 12 => { + Name => 'StreamColor', + PrintConv => { 0 => 'Monochrome' }, + }, + 13 => 'StreamBitDepth', + 14 => { + Name => 'Flags', + Format => 'int32u', + PrintConv => 'sprintf("0x%x", $val)', + }, +); + +#------------------------------------------------------------------------------ +# Extract information from the JPEG APP0 Ocad segment +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessOcad($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + $et->VerboseDir('APP0 Ocad', undef, length $$dataPt); + for (;;) { + last unless $$dataPt =~ /\$(\w+):([^\0\$]+)/g; + my ($tag, $val) = ($1, $2); + $val =~ s/^\s+//; $val =~ s/\s+$//; # remove leading/trailing spaces + AddTagToTable($tagTablePtr, $tag) unless $$tagTablePtr{$tag}; + $et->HandleTag($tagTablePtr, $tag, $val); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from the JPEG APP0 Ocad segment +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessJPEG_HDR($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + $$dataPt =~ /~\0/g or $et->Warn('Unrecognized JPEG-HDR format'), return 0; + my $pos = pos $$dataPt; + my $meta = substr($$dataPt, 7, $pos-9); + $et->VerboseDir('APP11 JPEG-HDR', undef, length $$dataPt); + while ($meta =~ /(\w+)=([^,\s]*)/g) { + my ($tag, $val) = ($1, $2); + AddTagToTable($tagTablePtr, $tag) unless $$tagTablePtr{$tag}; + $et->HandleTag($tagTablePtr, $tag, $val); + } + $et->HandleTag($tagTablePtr, 'RatioImage', substr($$dataPt, $pos)); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::JPEG - Definitions for uncommon JPEG segments + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool for some +uncommon JPEG segments. For speed reasons, definitions for more common JPEG +segments are included in the Image::ExifTool module itself. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/JPEG Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/JPEGDigest.pm b/ExifTool/lib/Image/ExifTool/JPEGDigest.pm new file mode 100644 index 0000000..a998f07 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/JPEGDigest.pm @@ -0,0 +1,2617 @@ +#------------------------------------------------------------------------------ +# File: JPEGDigest.pm +# +# Description: Calculate JPEGDigest and JPEGQualityEstimate +# +# Revisions: 2008/09/15 - P. Harvey Created +# 2016/01/05 - PH Added calculation of JPEGQualityEstimate +# +# References: JD) Jens Duttke +# 2) Franz Buchinger private communication +# 3) https://github.com/ImageMagick/ImageMagick/blob/master/coders/jpeg.c +#------------------------------------------------------------------------------ + +package Image::ExifTool::JPEGDigest; +use strict; +use vars qw($VERSION); + +$VERSION = '1.06'; + +# the print conversion for the JPEGDigest tag +my %PrintConv = ( #JD + # No DQT defined + 'd41d8cd98f00b204e9800998ecf8427e' => 'No DQT defined', + + # Tested with: + # - Independent JPEG Group library (used by many applications) X3 (Win) + # - Different subsamplings possible + # - Dynamic Photo HDR 3.0 (Win) + # - Fixed to quality 92? + # - Fixed subsampling of 111111? + # - FixFoto (Win) + # - These DQTs are only used for the Quality settings 24 to 100 + # - Different subsamplings possible + # - Subsampling of 221111 is default + # - GraphicConverter 4.4.4 (Mac) + # - Using the JPEG 6.0 library + # - Fixed subsampling to 221111 + # - IrfanView 4.10 (Win) + # - Subsampling 111111 with option "Disable subsampling", otherwise 221111 + # - Quality mode 0 doesn't exist here + # - Jasc Paint Shop Pro Version 5.01 (Win) + # - Fixed subsampling of 221111 + # - Use reversed Quality values (0 or 1 = 99; 2 = 98; 3 = 97 etc.) + # - Microsoft GDI+ Version 5.1.3102.2180 (Win) + # - Fixed subsampling of 221111 (or is that just the default subsampling?) + # - Photomatix pro 3.0 (Win) + # - Fixed subsampling to 221111 + + # IJG library + 'f83c5bc303fa1f74265863c2c6844edf' => 'Independent JPEG Group library (used by many applications), Quality 0 or 1', + '1e81ee25c96cdf46f44a9b930780f8c0' => 'Independent JPEG Group library (used by many applications), Quality 2', + '205d4a597e68f2da78137e52f39d2728' => 'Independent JPEG Group library (used by many applications), Quality 3', + '81a936d8371a7d59da428fcfc349850f' => 'Independent JPEG Group library (used by many applications), Quality 4', + '610772f3cf75c2fd89214fafbd7617a6' => 'Independent JPEG Group library (used by many applications), Quality 5', + '577ec8895d884612d064771e84cf231f' => 'Independent JPEG Group library (used by many applications), Quality 6', + '7dc25cc528116e25dd1aeb590dd7cb66' => 'Independent JPEG Group library (used by many applications), Quality 7', + 'e3cc8a85db1a32e81650b9668b98644a' => 'Independent JPEG Group library (used by many applications), Quality 8', + 'e2408846813c1f5c7f5ce3cf69e741c4' => 'Independent JPEG Group library (used by many applications), Quality 9', + 'e800426d2ef8d3cda13a0b41f1b2cc5a' => 'Independent JPEG Group library (used by many applications), Quality 10', + '8ef467e72e5006d1b48209e7b5d94541' => 'Independent JPEG Group library (used by many applications), Quality 11', + 'af6a08d0742aa8ed6bae2f1c374e7931' => 'Independent JPEG Group library (used by many applications), Quality 12', + 'e002d9728c73f60e4d0509e1cea9af43' => 'Independent JPEG Group library (used by many applications), Quality 13', + '1eb8b98bef8b062e4bc10cffea2d8372' => 'Independent JPEG Group library (used by many applications), Quality 14', + '99d933a8a9ece6f2ee65757aa81ef5bd' => 'Independent JPEG Group library (used by many applications), Quality 15', + 'c7d0f7dee5631d01bc55b7c598805d98' => 'Independent JPEG Group library (used by many applications), Quality 16', + '71555bee8d64c9dfca3cefa9dd332472' => 'Independent JPEG Group library (used by many applications), Quality 17', + '5009778c2a1df1deeb040c85fb9d0db2' => 'Independent JPEG Group library (used by many applications), Quality 18', + '81339d89e8294729c69fc096f1a448f3' => 'Independent JPEG Group library (used by many applications), Quality 19', + '82028a770d9e45d64d6aa26faee97e72' => 'Independent JPEG Group library (used by many applications), Quality 20', + '3ebb7aa9113b3e1628f55732de7dae7f' => 'Independent JPEG Group library (used by many applications), Quality 21', + '704c1868fe865f1038aa2bd0697f71a0' => 'Independent JPEG Group library (used by many applications), Quality 22', + '555fd108ab97e641fcfefee4bda6a306' => 'Independent JPEG Group library (used by many applications), Quality 23', + 'f1d9a0410c5ea11613569783625f5cf3' => 'Independent JPEG Group library (used by many applications), Quality 24', + 'ce378a14d52ee5752ded23b5a444f75e' => 'Independent JPEG Group library (used by many applications), Quality 25', + '3f243aacd371617b69f1e1eacadbbe2e' => 'Independent JPEG Group library (used by many applications), Quality 26', + 'c70a1df73760a88deb890003cdab3bfe' => 'Independent JPEG Group library (used by many applications), Quality 27', + 'aa5e05f96c53f6bc498f5016b1113651' => 'Independent JPEG Group library (used by many applications), Quality 28', + '740d4a06f4d0f774c6aac95719673793' => 'Independent JPEG Group library (used by many applications), Quality 29', + '1426dd9c1c8098936f395e201f1eb56d' => 'Independent JPEG Group library (used by many applications), Quality 30', + '891a53bb6a5261a2c076cf8931c3660e' => 'Independent JPEG Group library (used by many applications), Quality 31', + '1c87cb13f4b7a5ed45b09fc4f0e52d68' => 'Independent JPEG Group library (used by many applications), Quality 32', + '2457f78378c5efdac8e1ef619a4285cd' => 'Independent JPEG Group library (used by many applications), Quality 33', + '20f79a557d4edb1917d243d00a7a9ba8' => 'Independent JPEG Group library (used by many applications), Quality 34', + 'e06d44ffef23792c88f215a5b2ed9478' => 'Independent JPEG Group library (used by many applications), Quality 35', + 'df1b50991b82b66c82dc856cf82383da' => 'Independent JPEG Group library (used by many applications), Quality 36', + 'ffd6e30af6d99997d38ec3e303687e25' => 'Independent JPEG Group library (used by many applications), Quality 37', + 'ed923fdb1e5009215a49c0d92061c3b0' => 'Independent JPEG Group library (used by many applications), Quality 38', + 'aaf1ebc6949569327f95cf7da78ee7bc' => 'Independent JPEG Group library (used by many applications), Quality 39', + '0fe5225afaf055efd8453426c00e81e1' => 'Independent JPEG Group library (used by many applications), Quality 40', + 'a79e66299883be78b02ceaaf9159c320' => 'Independent JPEG Group library (used by many applications), Quality 41', + 'c9f10fb6d352cc8a8967e7e96a64862c' => 'Independent JPEG Group library (used by many applications), Quality 42', + '6dc3abbdd52b2f26b790ddb33b82099a' => 'Independent JPEG Group library (used by many applications), Quality 43', + '29572be1991c210fabacaeb658a74844' => 'Independent JPEG Group library (used by many applications), Quality 44', + '0c9f89a3728234e0e85645c968d1d84a' => 'Independent JPEG Group library (used by many applications), Quality 45', + 'a5e5355ae60c569dec306eb971a49276' => 'Independent JPEG Group library (used by many applications), Quality 46', + '4a2605b7c5b1bc99b6715342de7b6562' => 'Independent JPEG Group library (used by many applications), Quality 47', + '6af4053465275c1b1b2d5c97f4d841aa' => 'Independent JPEG Group library (used by many applications), Quality 48', + '905a0b1688644902f6a65d872d68a9db' => 'Independent JPEG Group library (used by many applications), Quality 49', + 'ffcd5eab8daeced571d59d4cdcc002c4' => 'Independent JPEG Group library (used by many applications), Quality 50', + '76806382fa57818da2e406a0dc23ce20' => 'Independent JPEG Group library (used by many applications), Quality 51', + '7c71a5eb9408b93be3ea6cf9be1d31ea' => 'Independent JPEG Group library (used by many applications), Quality 52', + 'ccf3f63196092667f97c2ff723481a21' => 'Independent JPEG Group library (used by many applications), Quality 53', + 'acc5e596cc4eb156b83eb89190b289a7' => 'Independent JPEG Group library (used by many applications), Quality 54', + 'a16e53aa41aa66124557c0b976d73734' => 'Independent JPEG Group library (used by many applications), Quality 55', + '2101fdf5c6f6e92943bc16ccf8aa46a8' => 'Independent JPEG Group library (used by many applications), Quality 56', + 'aaa2026d750590e466d9198f20b888e4' => 'Independent JPEG Group library (used by many applications), Quality 57', + 'fcd8c97cf6004230444ce21dab8a167f' => 'Independent JPEG Group library (used by many applications), Quality 58', + '318081abf5329c90d984c2214d69f097' => 'Independent JPEG Group library (used by many applications), Quality 59', + '95348adff2bf5a88f3967e9237fc571e' => 'Independent JPEG Group library (used by many applications), Quality 60; Pentax Better', + 'e22b26930415798b7ecf2b060c1cdc2a' => 'Independent JPEG Group library (used by many applications), Quality 61', + '3e646bfadb62d25ae8404b179e93e74e' => 'Independent JPEG Group library (used by many applications), Quality 62', + '34bf5c46342f2a514f8ae562e520ece0' => 'Independent JPEG Group library (used by many applications), Quality 63', + 'fc9f2bd278075ea89d482e1f9e66738f' => 'Independent JPEG Group library (used by many applications), Quality 64', + 'b55f27c368f119f25957c8e0036b27f8' => 'Independent JPEG Group library (used by many applications), Quality 65', + '5f7b9af6d66eaf12874aa680e7b0d31b' => 'Independent JPEG Group library (used by many applications), Quality 66', + 'a78fd0f1183b268b2fdfa23308c3ad44' => 'Independent JPEG Group library (used by many applications), Quality 67', + '61814a2eca233e10b0ba26881551fb50' => 'Independent JPEG Group library (used by many applications), Quality 68', + 'ce0f36a3d870b24f9816634000ea2d2e' => 'Independent JPEG Group library (used by many applications), Quality 69', + 'f7cba1affebd362a322abd45ce580e56' => 'Independent JPEG Group library (used by many applications), Quality 70', + '7b85fffdf97680de53e49788712c50de' => 'Independent JPEG Group library (used by many applications), Quality 71', + '4abd7dbca7735c987eb899b0f8646ce4' => 'Independent JPEG Group library (used by many applications), Quality 72', + '167959a11aff7940df84ed9f3379ed0a' => 'Independent JPEG Group library (used by many applications), Quality 73', + 'd61807da54a72c4651466049c9f67541' => 'Independent JPEG Group library (used by many applications), Quality 74', + '2851eea5e15f1b977c1496a77c884b4f' => 'Independent JPEG Group library (used by many applications), Quality 75', + 'b095631a0665a515d9aa290639f6672b' => 'Independent JPEG Group library (used by many applications), Quality 76', + '0ee4ea4687d3c57d060e3afd2559b7bb' => 'Independent JPEG Group library (used by many applications), Quality 77', + '37e999828e5bc43ee4a470bf29ea97b7' => 'Independent JPEG Group library (used by many applications), Quality 78', + 'dd9b9d09a624deab730d9bd5b8825baa' => 'Independent JPEG Group library (used by many applications), Quality 79', + '5319a82dc93c1ee5c8d2265e4f1fb60e' => 'Independent JPEG Group library (used by many applications), Quality 80', + '6fa04ba1184e986c4da3df8141e05a42' => 'Independent JPEG Group library (used by many applications), Quality 81', + '1d099901cfe37674e4aeb2cdbddf0703' => 'Independent JPEG Group library (used by many applications), Quality 82', + '8cc04e05813c028683e3cb8e6eab79eb' => 'Independent JPEG Group library (used by many applications), Quality 83', + '3eafbf05c0dd233b385856065e456c11' => 'Independent JPEG Group library (used by many applications), Quality 84', + '98dec36fe95ed7c1772d9ed67a67e260' => 'Independent JPEG Group library (used by many applications), Quality 85', + 'a1ee06d19dcb62c7467768d1ba73cf12' => 'Independent JPEG Group library (used by many applications), Quality 86', + '643ad95753c261eacc5ea3f4c9e4d469' => 'Independent JPEG Group library (used by many applications), Quality 87', + '2557828257dd798ad636df350c685a26' => 'Independent JPEG Group library (used by many applications), Quality 88', + 'a5b4bbf018828a3d00e54ab72a175fdc' => 'Independent JPEG Group library (used by many applications), Quality 89', + '50325a7b6e0a9b7f9aea8f5a6f7f31aa' => 'Independent JPEG Group library (used by many applications), Quality 90', + 'c0cf6b0c508a35a13acd8c912548a269' => 'Independent JPEG Group library (used by many applications), Quality 91', + 'ff0aa07a220cd8973a4b86f3ecd4325b' => 'Independent JPEG Group library (used by many applications), Quality 92', + 'f251d4554524d22f94a34668ab17957c' => 'Independent JPEG Group library (used by many applications), Quality 93', + 'cd993be55bad60bb539df0cc2d7f9f6f' => 'Independent JPEG Group library (used by many applications), Quality 94', + 'adf507352f9ce218a4605700459d597f' => 'Independent JPEG Group library (used by many applications), Quality 95', + '9e9e3e22af4e41ea4ec1b8f656f28f42' => 'Independent JPEG Group library (used by many applications), Quality 96', + 'b9f5c003ef62cbd8fc93be6679c1c3bc' => 'Independent JPEG Group library (used by many applications), Quality 97', + '95b6a316836182441b12039279872ec3' => 'Independent JPEG Group library (used by many applications), Quality 98', + 'b9b1ce23552e2f82b95b48de20065ed3' => 'Independent JPEG Group library (used by many applications), Quality 99', + 'a97591462e9b2339efd4f88ca97bb471' => 'Independent JPEG Group library (used by many applications), Quality 100', + + # Independent JPEG Group library (Grayscale) + # + # Tested with: + # - Corel PhotoImpact X3 (Win) + # - IrfanView (Win) + # - Quality mode 0 doesn't exist here + '185893c53196f6156d458a84e1135c43:11' => 'Independent JPEG Group library (used by many applications), Quality 0 or 1 (Grayscale)', + 'b41ccbe66e41a05de5e68832c07969a7:11' => 'Independent JPEG Group library (used by many applications), Quality 2 (Grayscale)', + 'efa024d741ecc5204e7edd4f590a7a25:11' => 'Independent JPEG Group library (used by many applications), Quality 3 (Grayscale)', + '3396344724a1868ada2330ebaeb9448e:11' => 'Independent JPEG Group library (used by many applications), Quality 4 (Grayscale)', + '14276fffb98deb42b7dbce30abb8425f:11' => 'Independent JPEG Group library (used by many applications), Quality 5 (Grayscale)', + 'a99e2826c10d0922ce8942c5437f53a6:11' => 'Independent JPEG Group library (used by many applications), Quality 6 (Grayscale)', + '0d3de456aa5cbb8a2578208250aa9b88:11' => 'Independent JPEG Group library (used by many applications), Quality 7 (Grayscale)', + 'fa987940fdedbe883cc0e9fcc907f89e:11' => 'Independent JPEG Group library (used by many applications), Quality 8 (Grayscale)', + '1c9bb67190ee64e82d3c67f7943bf4a4:11' => 'Independent JPEG Group library (used by many applications), Quality 9 (Grayscale)', + '57d20578d190b04c7667b10d3df241bb:11' => 'Independent JPEG Group library (used by many applications), Quality 10 (Grayscale)', + '619fd49197f0403ce13d86cffec46419:11' => 'Independent JPEG Group library (used by many applications), Quality 11 (Grayscale)', + '327f47dd8f999b2bbb3bb25c43cf5be5:11' => 'Independent JPEG Group library (used by many applications), Quality 12 (Grayscale)', + 'e4e5bc705c40cfaffff6565f16fe98a9:11' => 'Independent JPEG Group library (used by many applications), Quality 13 (Grayscale)', + '6c64fa9ad302624a826f04ecc80459be:11' => 'Independent JPEG Group library (used by many applications), Quality 14 (Grayscale)', + '039a3f0e101f1bcdb6bb81478cf7ae6b:11' => 'Independent JPEG Group library (used by many applications), Quality 15 (Grayscale)', + 'c23b08c94d7537c9447691d54ae1080c:11' => 'Independent JPEG Group library (used by many applications), Quality 16 (Grayscale)', + '200107bc0174104bbf1d4653c4b05058:11' => 'Independent JPEG Group library (used by many applications), Quality 17 (Grayscale)', + '72abfdc6e65b32ded2cd7ac77a04f447:11' => 'Independent JPEG Group library (used by many applications), Quality 18 (Grayscale)', + '1799a236c36da0b30729d9005ca7c7f9:11' => 'Independent JPEG Group library (used by many applications), Quality 19 (Grayscale)', + 'c33a667bff7f590655d196010c5e39f3:11' => 'Independent JPEG Group library (used by many applications), Quality 20 (Grayscale)', + 'b1dc98f6a2f8828f8432872da43e7d94:11' => 'Independent JPEG Group library (used by many applications), Quality 21 (Grayscale)', + '07318a0acfebe9086f0e04a4c4f5398a:11' => 'Independent JPEG Group library (used by many applications), Quality 22 (Grayscale)', + 'a295b7163305f327a5a45ae177a0a19c:11' => 'Independent JPEG Group library (used by many applications), Quality 23 (Grayscale)', + 'c741c1b134cf81ab69acc81f15a67137:11' => 'Independent JPEG Group library (used by many applications), Quality 24 (Grayscale)', + 'a68893776502a591548c7b5bece13e1b:11' => 'Independent JPEG Group library (used by many applications), Quality 25 (Grayscale)', + '111848d9e41f6f408ef70841f90c0519:11' => 'Independent JPEG Group library (used by many applications), Quality 26 (Grayscale)', + '886374ceebcfd4dfed200b0b34b4baca:11' => 'Independent JPEG Group library (used by many applications), Quality 27 (Grayscale)', + '666dd95fd0e20f5c20bc44d78d528869:11' => 'Independent JPEG Group library (used by many applications), Quality 28 (Grayscale)', + '1aa58cb85dda84de2ddf436667124dcd:11' => 'Independent JPEG Group library (used by many applications), Quality 29 (Grayscale)', + '9d321ab2bdda6f3cb76d2d88838aa8c3:11' => 'Independent JPEG Group library (used by many applications), Quality 30 (Grayscale)', + '6ad87d648101c268f83fa379d4c773f2:11' => 'Independent JPEG Group library (used by many applications), Quality 31 (Grayscale)', + 'cdf8e921300f27a4af7661a2de16e91a:11' => 'Independent JPEG Group library (used by many applications), Quality 32 (Grayscale)', + '3f48672e37b6dd2e571b222e4b7ff97d:11' => 'Independent JPEG Group library (used by many applications), Quality 33 (Grayscale)', + 'a53a7d4cc86d01f4c1b867270c9c078f:11' => 'Independent JPEG Group library (used by many applications), Quality 34 (Grayscale)', + '09ec03f5096df106c692123f3fd34296:11' => 'Independent JPEG Group library (used by many applications), Quality 35 (Grayscale)', + 'a946498fd1902c9de87a1f5182966742:11' => 'Independent JPEG Group library (used by many applications), Quality 36 (Grayscale)', + '5d650a1d38108fd79d4f336ba8e254c2:11' => 'Independent JPEG Group library (used by many applications), Quality 37 (Grayscale)', + '81d620f1b470fd535b26544b4ea20643:11' => 'Independent JPEG Group library (used by many applications), Quality 38 (Grayscale)', + '892788bdf8cbef5c6fbd7019a079bf8e:11' => 'Independent JPEG Group library (used by many applications), Quality 39 (Grayscale)', + 'cf3929fd4c1e5c28b7f137f982178ad1:11' => 'Independent JPEG Group library (used by many applications), Quality 40 (Grayscale)', + '31f288945896ed839f1d936bff06fb03:11' => 'Independent JPEG Group library (used by many applications), Quality 41 (Grayscale)', + 'e0c38f0c5e6562445d4e92bae51713be:11' => 'Independent JPEG Group library (used by many applications), Quality 42 (Grayscale)', + '18fa29d1164984883a6af76377b60d5a:11' => 'Independent JPEG Group library (used by many applications), Quality 43 (Grayscale)', + 'eff737b226fbce48c42625c5bf9dabb6:11' => 'Independent JPEG Group library (used by many applications), Quality 44 (Grayscale)', + 'b900f91ee8697255d5daebce858caaeb:11' => 'Independent JPEG Group library (used by many applications), Quality 45 (Grayscale)', + 'ab2f8513823067af242f7e3c04a88a9c:11' => 'Independent JPEG Group library (used by many applications), Quality 46 (Grayscale)', + '60b682c4d412f5255efbaa32787c46ca:11' => 'Independent JPEG Group library (used by many applications), Quality 47 (Grayscale)', + 'ea50813e06203c2ad1165252bcb99a1d:11' => 'Independent JPEG Group library (used by many applications), Quality 48 (Grayscale)', + 'f6308a717437d3653b0751ebf511db0f:11' => 'Independent JPEG Group library (used by many applications), Quality 49 (Grayscale)', + '7c8242581553e818ef243fc680879a19:11' => 'Independent JPEG Group library (used by many applications), Quality 50 (Grayscale)', + 'fc41ab8251718977bc6676f502f457e0:11' => 'Independent JPEG Group library (used by many applications), Quality 51 (Grayscale)', + '606c4c78c0226646bf4d3c5a5898fb17:11' => 'Independent JPEG Group library (used by many applications), Quality 52 (Grayscale)', + '0e6c6a5440d33d25f1c25836a45cfa69:11' => 'Independent JPEG Group library (used by many applications), Quality 53 (Grayscale)', + '7464b2361e5b5f5a9ba74a87475dda91:11' => 'Independent JPEG Group library (used by many applications), Quality 54 (Grayscale)', + 'aeaa2ca48eabb3088ebb713b3c4e1a67:11' => 'Independent JPEG Group library (used by many applications), Quality 55 (Grayscale)', + '3f36450b0ba074578391e77f7340cef0:11' => 'Independent JPEG Group library (used by many applications), Quality 56 (Grayscale)', + 'be232444027e83db6f8d8b79d078442e:11' => 'Independent JPEG Group library (used by many applications), Quality 57 (Grayscale)', + '712c145d6472a2b315b2ecfb916d1590:11' => 'Independent JPEG Group library (used by many applications), Quality 58 (Grayscale)', + 'ae3dd4568cc71c47d068cf831c66b59d:11' => 'Independent JPEG Group library (used by many applications), Quality 59 (Grayscale)', + 'b290e52c21a435fede4586636ef5e287:11' => 'Independent JPEG Group library (used by many applications), Quality 60 (Grayscale)', + 'a09ca4c4391e0221396a08f229a65f9d:11' => 'Independent JPEG Group library (used by many applications), Quality 61 (Grayscale)', + '0818578fc5fc571b4f8d5ffefc9dc0d8:11' => 'Independent JPEG Group library (used by many applications), Quality 62 (Grayscale)', + '7c685e2916555eda34cb37a1e71adc6a:11' => 'Independent JPEG Group library (used by many applications), Quality 63 (Grayscale)', + '69c6b9440342adfc0db89a6c91aba332:11' => 'Independent JPEG Group library (used by many applications), Quality 64 (Grayscale)', + 'd5d484b68e25b44288e67e699829695c:11' => 'Independent JPEG Group library (used by many applications), Quality 65 (Grayscale)', + 'de8310d09116a7a62965f3e0e43ef525:11' => 'Independent JPEG Group library (used by many applications), Quality 66 (Grayscale)', + 'e4735f63e88baf04599afc034e690845:11' => 'Independent JPEG Group library (used by many applications), Quality 67 (Grayscale)', + 'b4ef810b14dee9c6d6d8cace98f799a6:11' => 'Independent JPEG Group library (used by many applications), Quality 68 (Grayscale)', + '52886ef80147c9a136e20b2bc3b76f52:11' => 'Independent JPEG Group library (used by many applications), Quality 69 (Grayscale)', + '9c62dbc848be82ef91219ba9843998be:11' => 'Independent JPEG Group library (used by many applications), Quality 70 (Grayscale)', + 'bfe8c1c73eea84b85673487a82f67627:11' => 'Independent JPEG Group library (used by many applications), Quality 71 (Grayscale)', + 'ea445840d29c51009a2a8cd49b96ccee:11' => 'Independent JPEG Group library (used by many applications), Quality 72 (Grayscale)', + '71c1a56890fff9b0a095fa5a1c96132b:11' => 'Independent JPEG Group library (used by many applications), Quality 73 (Grayscale)', + 'f080b02331ac8adf03de2281042d2b49:11' => 'Independent JPEG Group library (used by many applications), Quality 74 (Grayscale)', + 'd0eaa368737f17f6037757d393a22599:11' => 'Independent JPEG Group library (used by many applications), Quality 75 (Grayscale)', + '303663905d055b77bb547fe0b0beb9c5:11' => 'Independent JPEG Group library (used by many applications), Quality 76 (Grayscale)', + '5cdf1d5bbe19375ad5c7237273dddede:11' => 'Independent JPEG Group library (used by many applications), Quality 77 (Grayscale)', + 'd64e7ff8292fd77131932864d3c9ce7c:11' => 'Independent JPEG Group library (used by many applications), Quality 78 (Grayscale)', + '12b4cc13891c5aef3dadb3405b6fa65d:11' => 'Independent JPEG Group library (used by many applications), Quality 79 (Grayscale)', + 'b008cd63591f8fd366f77d2b224b9c9c:11' => 'Independent JPEG Group library (used by many applications), Quality 80 (Grayscale)', + '49b6e472c7d5ecead593c6009768e765:11' => 'Independent JPEG Group library (used by many applications), Quality 81 (Grayscale)', + 'bce6fa61623ad4f65ff3fec1528cb026:11' => 'Independent JPEG Group library (used by many applications), Quality 82 (Grayscale)', + 'c2b037bf9f5e5baba804d7bbbb2dc73b:11' => 'Independent JPEG Group library (used by many applications), Quality 83 (Grayscale)', + '7fe7b339c6ffc62b984eeab4b0df9168:11' => 'Independent JPEG Group library (used by many applications), Quality 84 (Grayscale)', + '274bbeb0ac3939f90c578ebb1f5a9eef:11' => 'Independent JPEG Group library (used by many applications), Quality 85 (Grayscale)', + '0a0268c655d616b0e4af2851533aa3af:11' => 'Independent JPEG Group library (used by many applications), Quality 86 (Grayscale)', + '52318e260c0d6b3dbee85c87f9b94e63:11' => 'Independent JPEG Group library (used by many applications), Quality 87 (Grayscale)', + 'b64cc19a0f81a506ed5bcfb9c131c8fe:11' => 'Independent JPEG Group library (used by many applications), Quality 88 (Grayscale)', + 'd8c54333eb475b8db9f32f11fe96337e:11' => 'Independent JPEG Group library (used by many applications), Quality 89 (Grayscale)', + '12fe6b9bfd20f4d7f0ac2a221c566c45:11' => 'Independent JPEG Group library (used by many applications), Quality 90 (Grayscale)', + '12aefbf7689633c83da714c9f0e90e05:11' => 'Independent JPEG Group library (used by many applications), Quality 91 (Grayscale)', + 'a3a96add050fc51a2b3ce59a9a491034:11' => 'Independent JPEG Group library (used by many applications), Quality 92 (Grayscale)', + '7b0242bd9aaeab4962f5d5b39b9a4027:11' => 'Independent JPEG Group library (used by many applications), Quality 93 (Grayscale)', + '12fc29c1d8940c93a47ee9d927a17561:11' => 'Independent JPEG Group library (used by many applications), Quality 94 (Grayscale)', + 'e1fedef5184beeb7b0f5c055c7ae1d31:11' => 'Independent JPEG Group library (used by many applications), Quality 95 (Grayscale)', + 'ae9202355f603776794d3e62c43578d6:11' => 'Independent JPEG Group library (used by many applications), Quality 96 (Grayscale)', + '36da00bae6cd81d1f97e32748c07e33f:11' => 'Independent JPEG Group library (used by many applications), Quality 97 (Grayscale)', + '54dc50b16e7cc9bc383eb9e73e85e199:11' => 'Independent JPEG Group library (used by many applications), Quality 98 (Grayscale)', + '23a59c4f9ec045faf9f8379b3ca302bb:11' => 'Independent JPEG Group library (used by many applications), Quality 99 (Grayscale)', + 'bbd2dbcfe20b59e981e9a42cd1eb6ece:11' => 'Independent JPEG Group library (used by many applications), Quality 100 (Grayscale)', + + # Tested with Adobe Photoshop Lightroom 1.4.1 (Win) - "Export" + '683506a889c78d9bc230a0c7ee5f62f3:221111' => 'Adobe Lightroom, Quality 0% - 7%', + 'bc490651af6592cd1dbbbc4fa2cfa1fb:221111' => 'Adobe Lightroom, Quality 8% - 15%', + 'ce4286d9f07999524c3c7472b065c5ab:221111' => 'Adobe Lightroom, Quality 16% - 23%', + 'cbfbfef12aead8841585ef605c789b9f:221111' => 'Adobe Lightroom, Quality 24% - 30%', + 'a0772e73dec2bdc4057c27da47bff376:221111' => 'Adobe Lightroom, Quality 31% - 38%', + '7ef06dbde538346b8b01c6b538ca70c6:221111' => 'Adobe Lightroom, Quality 39% - 46%', + '0ff225f58a214f79d1d85d78f6f5dab8:221111' => 'Adobe Lightroom, Quality 47% - 53%', + '8a8603650fa5ae5fdcf4b2eaf0b23638:111111' => 'Adobe Lightroom, Quality 54% - 61%', + '44f583ed6b65cb8ba915ec5df051616c:111111' => 'Adobe Lightroom, Quality 62% - 69%', + 'de94c5591bafc7456ccaef430271b907:111111' => 'Adobe Lightroom, Quality 70% - 76%', + 'a6841b35e9ffefa5d83a0445dddd2621:111111' => 'Adobe Lightroom, Quality 77% - 84%', + '6e3f6a3a5a1eae6155331d42d6f968dd:111111' => 'Adobe Lightroom, Quality 85% - 92%', + '5379e0133d4439b6f7c7039fc7f7734f:111111' => 'Adobe Lightroom, Quality 93% - 100%', + + # Adobe Photoshop Lightroom 2.1 (Win) - "Export" + # (0-23% equal to Lightroom 1.4.1) + '8453391d3adf377c46a1a0cee08c35c3:221111' => 'Adobe Lightroom, Quality 24% - 30%', + 'f8ede291b1272576d1580e333d30103e:221111' => 'Adobe Lightroom, Quality 31% - 38%', + 'de0fb6d13e12e8df26140dd74691bf0f:221111' => 'Adobe Lightroom, Quality 39% - 46%', + '0d5b0af72561f68c671731f22d9e41e2:221111' => 'Adobe Lightroom, Quality 47% - 53%', + 'b9fd15fd52408af5ea2a5045227233d8:111111' => 'Adobe Lightroom, Quality 54% - 61%', + '27472e3714251402d5509438505611c3:111111' => 'Adobe Lightroom, Quality 62% - 69%', + '34a599dff2b6aaed12143938b7374f2f:111111' => 'Adobe Lightroom, Quality 70% - 76%', + '5c508e529d045b6f0c800e29ba2d6ab5:111111' => 'Adobe Lightroom, Quality 77% - 84%', + '42bfe52476bf07f1ed0e6451903cc9ee:111111' => 'Adobe Lightroom, Quality 85% - 92%', + '4c3c425b4024b68c0de03904a825bc35:111111' => 'Adobe Lightroom, Quality 93% - 100%', + + # Tested with Adobe Photoshop CS2 Version 9.0.2 (Win) - "Save as..." (RGB/CMYK) + '683506a889c78d9bc230a0c7ee5f62f3:221111' => 'Adobe Photoshop, Quality 0', + 'bc490651af6592cd1dbbbc4fa2cfa1fb:221111' => 'Adobe Photoshop, Quality 1', + 'ce4286d9f07999524c3c7472b065c5ab:221111' => 'Adobe Photoshop, Quality 2', + 'cbfbfef12aead8841585ef605c789b9f:221111' => 'Adobe Photoshop, Quality 3', + 'a0772e73dec2bdc4057c27da47bff376:221111' => 'Adobe Photoshop, Quality 4', + '7ef06dbde538346b8b01c6b538ca70c6:221111' => 'Adobe Photoshop, Quality 5', + '0ff225f58a214f79d1d85d78f6f5dab8:221111' => 'Adobe Photoshop, Quality 6', + '8a8603650fa5ae5fdcf4b2eaf0b23638:111111' => 'Adobe Photoshop, Quality 7', + '44f583ed6b65cb8ba915ec5df051616c:111111' => 'Adobe Photoshop, Quality 8', + 'de94c5591bafc7456ccaef430271b907:111111' => 'Adobe Photoshop, Quality 9', + 'a6841b35e9ffefa5d83a0445dddd2621:111111' => 'Adobe Photoshop, Quality 10', + '6e3f6a3a5a1eae6155331d42d6f968dd:111111' => 'Adobe Photoshop, Quality 11', + '5379e0133d4439b6f7c7039fc7f7734f:111111' => 'Adobe Photoshop, Quality 12', + + # Tested with Adobe Photoshop CS2 Version 9.0.2 (Win) - "Save as..." (Grayscale) + '3b0b5975a0e1c9d732c93e1b37a6978b:11' => 'Adobe Photoshop, Quality 0', + 'f4d19ed563e2d0519d6a547088771ddb:11' => 'Adobe Photoshop, Quality 1', + 'e9ef286567fd84a1f479b35ca00db43c:11' => 'Adobe Photoshop, Quality 2', + 'b39cafdb459a42749be3f6459a596677:11' => 'Adobe Photoshop, Quality 3', + 'b3f215deea48e982e205619af279205f:11' => 'Adobe Photoshop, Quality 4', + 'fccd63ce166e198065eaae05c8d78407:11' => 'Adobe Photoshop, Quality 5', + '0a50266ad8d1dff11c90cd1480c0a2be:11' => 'Adobe Photoshop, Quality 6', + '6579941db0216f41f0a20de9b626538a:11' => 'Adobe Photoshop, Quality 7', + '57aa47876e10c6b4f35ecb8889e55ad9:11' => 'Adobe Photoshop, Quality 8', + '076598d43c5186f6d7a1020b64b93625:11' => 'Adobe Photoshop, Quality 9', + '37132e8ea81137fdf26ce30926ab8100:11' => 'Adobe Photoshop, Quality 10', + '46f55ee294723cee9faa816549b3cfa7:11' => 'Adobe Photoshop, Quality 11', + '7b83284f61decf47ab3f8f7361c18943:11' => 'Adobe Photoshop, Quality 12', + + # Tested with Adobe Photoshop CS2 Version 9.0.2 (Win) - "Save for web..." + '9ac881c536e509675e5cf3795a85d9de:221111' => 'Adobe Photoshop, Save for web, Quality 0', + '3521d793fd9d2d9aac85dc4f0be40290:221111' => 'Adobe Photoshop, Save for web, Quality 1', + '041c9e3cf0d34a8b89539e3115bca31b:221111' => 'Adobe Photoshop, Save for web, Quality 2', + '029b3a6f0b92af6786d753788eafabfe:221111' => 'Adobe Photoshop, Save for web, Quality 3', + '6cdd3762e346b16a59af4bddb213b07a:221111' => 'Adobe Photoshop, Save for web, Quality 4', + '84a69c0b43505dd0cbc25d640873b5b9:221111' => 'Adobe Photoshop, Save for web, Quality 5', + '7254c012821f2bc866d7d6dd7906c92d:221111' => 'Adobe Photoshop, Save for web, Quality 6', + '428ba2c747ea4e495ff3c7ff44a988d2:221111' => 'Adobe Photoshop, Save for web, Quality 7', + '42cb001aea7e24d239f6c2fcbd861862:221111' => 'Adobe Photoshop, Save for web, Quality 8', + 'a3698813ce90772a30b6eb9a7deb3f4a:221111' => 'Adobe Photoshop, Save for web, Quality 9', + '301158b292e3232856a765486da26fa6:221111' => 'Adobe Photoshop, Save for web, Quality 10', + '8d9edea9287aa919e433b620f61468dc:221111' => 'Adobe Photoshop, Save for web, Quality 11', + 'c1e0554d8a6ed003eb98e068429b56b9:221111' => 'Adobe Photoshop, Save for web, Quality 12', + '0e0a151e0a52097cbd7683c9385e3a7c:221111' => 'Adobe Photoshop, Save for web, Quality 13', + '911e66f21fe242cc74e0a5738b0330bd:221111' => 'Adobe Photoshop, Save for web, Quality 14', + '028fafd94aa66ee269f58d800c89d838:221111' => 'Adobe Photoshop, Save for web, Quality 15', + '866b8adb1ce7c9dc0e58b7c1e013280f:221111' => 'Adobe Photoshop, Save for web, Quality 16', + '7f712aecf513621f635a007aadda61af:221111' => 'Adobe Photoshop, Save for web, Quality 17', + '38f26622a54ba22accac05f7c0a3b307:221111' => 'Adobe Photoshop, Save for web, Quality 18', + 'd241d5165e64e98024b47dfbf76be88c:221111' => 'Adobe Photoshop, Save for web, Quality 19', + 'afb31cfed194d4e125bde8fd4755bb8a:221111' => 'Adobe Photoshop, Save for web, Quality 20', + '0d501a036c984d2caf49fd298b2d0d16:221111' => 'Adobe Photoshop, Save for web, Quality 21', + '9e992f35767c4aa023b8afd243b247bf:221111' => 'Adobe Photoshop, Save for web, Quality 22', + '0a80e5bf01a9c5650384dfe1a428f61d:221111' => 'Adobe Photoshop, Save for web, Quality 23', + '2501aad23cdf94b25c6df0ab6984b6e0:221111' => 'Adobe Photoshop, Save for web, Quality 24', + '09c168d2e075070d3a2535e7f2e455df:221111' => 'Adobe Photoshop, Save for web, Quality 25', + '63190207beeb805306f7d0bcc3898cb3:221111' => 'Adobe Photoshop, Save for web, Quality 26', + 'e47902bc7ba3037921010c568648c8c3:221111' => 'Adobe Photoshop, Save for web, Quality 27', + '410ed63b6e5225d8b99da6272fd6069b:221111' => 'Adobe Photoshop, Save for web, Quality 28', + 'b40f3f3c46d70a560e2033fadd8c7bb5:221111' => 'Adobe Photoshop, Save for web, Quality 29', + '45148ae63b12ccaa6fb5a487ca7620e9:221111' => 'Adobe Photoshop, Save for web, Quality 30', + '5180e51bd58432c7b51a305ed0c24d1b:221111' => 'Adobe Photoshop, Save for web, Quality 31', + 'c5c472d899462bbe31da9aa8c072c0bc:221111' => 'Adobe Photoshop, Save for web, Quality 32', + '28cdbc95898e02dd0ffc45ba48596ca7:221111' => 'Adobe Photoshop, Save for web, Quality 33', + '42cd88e0eb3c14a705b952550ec2eacd:221111' => 'Adobe Photoshop, Save for web, Quality 34', + '78a2a442aac5cca7fa2ef5a8bd96219e:221111' => 'Adobe Photoshop, Save for web, Quality 35', + '96bce854134a2fccfcb68dca6687cd51:221111' => 'Adobe Photoshop, Save for web, Quality 36', + 'fefd00ec4610895e4294de690f5977e9:221111' => 'Adobe Photoshop, Save for web, Quality 37', + 'd71c8ddb9117920304d83a6f8b7832a4:221111' => 'Adobe Photoshop, Save for web, Quality 38', + '1727e720300403e5f315b5e17ef84d3f:221111' => 'Adobe Photoshop, Save for web, Quality 39', + '8fb05e3c3b0a7404ff6ca54f952d2a5e:221111' => 'Adobe Photoshop, Save for web, Quality 40', + '328ab751ea48f5a8bc7c4b8628138ce0:221111' => 'Adobe Photoshop, Save for web, Quality 41', + 'd9653333a3af8842dd4b72856ac4ef4e:221111' => 'Adobe Photoshop, Save for web, Quality 42', + '276da99e50e1b39134e13826789d655e:221111' => 'Adobe Photoshop, Save for web, Quality 43', + '23f2bd2d96ec531815609503dae4a2b0:221111' => 'Adobe Photoshop, Save for web, Quality 44', + 'bafe2a89809f23bc7367e9a819570728:221111' => 'Adobe Photoshop, Save for web, Quality 45', + '6bfdcd36327406f801be86d0e8ca6b60:221111' => 'Adobe Photoshop, Save for web, Quality 46', + 'eb8e5c42d31b916737ac21dffd6f012b:221111' => 'Adobe Photoshop, Save for web, Quality 47', + 'e57a9878be74473990343573c6585f79:221111' => 'Adobe Photoshop, Save for web, Quality 48', + '91dfacd928ce717cb135c6da03afd907:221111' => 'Adobe Photoshop, Save for web, Quality 49', + '16c443478b9417d44893f8748d49b790:221111' => 'Adobe Photoshop, Save for web, Quality 50', + '84de345dcf710f937a39a0b631b87fc4:111111' => 'Adobe Photoshop, Save for web, Quality 51', + 'bdd6043e7f5a5f1512b99b2394a075e2:111111' => 'Adobe Photoshop, Save for web, Quality 52', + 'c7614d3d384a02630721be335062ef75:111111' => 'Adobe Photoshop, Save for web, Quality 53', + '42d6f71aace3de2ccfdd8348b0198704:111111' => 'Adobe Photoshop, Save for web, Quality 54', + '84d5f059ce3e1b78d91355e1e86e2d1a:111111' => 'Adobe Photoshop, Save for web, Quality 55', + '5881004f575752d77ee00e767d848e51:111111' => 'Adobe Photoshop, Save for web, Quality 56', + '0cb697537acde3d2e85078377461a8e0:111111' => 'Adobe Photoshop, Save for web, Quality 57', + 'b2762ffa5c0a1799fb2e9ad6dfd2171a:111111' => 'Adobe Photoshop, Save for web, Quality 58', + '2b7a6a83259aa9967e098d3e70f1ee09:111111' => 'Adobe Photoshop, Save for web, Quality 59', + '6123a3685e1012af5a0d024de1ce0304:111111' => 'Adobe Photoshop, Save for web, Quality 60', + 'd08c8435de33f2c186aa2dd9cba3e874:111111' => 'Adobe Photoshop, Save for web, Quality 61', + 'e69be2174dbbfb952e54576fbdfe6c14:111111' => 'Adobe Photoshop, Save for web, Quality 62', + '2ec2d5c10641952fce5c435b331b8872:111111' => 'Adobe Photoshop, Save for web, Quality 63', + '98201e1185b7069f1247ac3cdc56c824:111111' => 'Adobe Photoshop, Save for web, Quality 64', + '8fc0325d05c9199bc1e2dec417c3a55e:111111' => 'Adobe Photoshop, Save for web, Quality 65', + '016600f44a61cc5a5673c9bad85e23a3:111111' => 'Adobe Photoshop, Save for web, Quality 66', + '91d7b4300c98c726aff7b19cbe098a3e:111111' => 'Adobe Photoshop, Save for web, Quality 67', + 'f9b83ba21b86a3d4ddb507e3edce490c:111111' => 'Adobe Photoshop, Save for web, Quality 68', + 'd312a23c8ecb3bf59bc11bbe17d79e55:111111' => 'Adobe Photoshop, Save for web, Quality 69', + '240fffe5f8e2d8f3345b8175f9cb0a40:111111' => 'Adobe Photoshop, Save for web, Quality 70', + 'ba60a642bfb1a184c11e5561581d7115:111111' => 'Adobe Photoshop, Save for web, Quality 71', + 'c901580e589f58d309f8b50590cfe214:111111' => 'Adobe Photoshop, Save for web, Quality 72', + 'c244f94d84a016840c6ef06250c58ade:111111' => 'Adobe Photoshop, Save for web, Quality 73', + '3589227bdd85f880f3337b492e895c5d:111111' => 'Adobe Photoshop, Save for web, Quality 74', + 'cae6fd91a423ff181d50bb9c26a0d392:111111' => 'Adobe Photoshop, Save for web, Quality 75', + '7d8ee11ca66d2c22ff9ed1f778b5dbac:111111' => 'Adobe Photoshop, Save for web, Quality 76', + 'a16371762ce48953d42dfb5b77d1bfc6:111111' => 'Adobe Photoshop, Save for web, Quality 77', + '204b111d4aaa85b430e86273a63fd004:111111' => 'Adobe Photoshop, Save for web, Quality 78', + '6a243ac0b8575c2ed962070cd7d39e04:111111' => 'Adobe Photoshop, Save for web, Quality 79', + '51879d6e5178d2282d5e8276ed4e2439:111111' => 'Adobe Photoshop, Save for web, Quality 80', + 'ca683ab6caaa3132bf661a0ebf32ef4e:111111' => 'Adobe Photoshop, Save for web, Quality 81', + '5399adc3f21ecb30c96d6a94b38ab74c:111111' => 'Adobe Photoshop, Save for web, Quality 82', + '43eb3b161279ccc1fb4f9cbe7b92398f:111111' => 'Adobe Photoshop, Save for web, Quality 83', + '2d387641f4e94b6986908b3770fb762e:111111' => 'Adobe Photoshop, Save for web, Quality 84', + '75ee5a0fd61559c6bf8e6ebc920c93b0:111111' => 'Adobe Photoshop, Save for web, Quality 85', + '60d17e041a23d47b96c5aac86180a022:111111' => 'Adobe Photoshop, Save for web, Quality 86', + '8e5290b1d12832ad259de92a53e1ef4e:111111' => 'Adobe Photoshop, Save for web, Quality 87', + 'dc19a48af9051bbdc54cf7e88c03f13e:111111' => 'Adobe Photoshop, Save for web, Quality 88', + 'c3fbc85c803ddc81c8882c03330b5b15:111111' => 'Adobe Photoshop, Save for web, Quality 89', + '5e016a2d28f8ad3e7e27e4e2981031d2:111111' => 'Adobe Photoshop, Save for web, Quality 90', + 'ef4fa43f4d548e0687c4d4151a0bf1bd:111111' => 'Adobe Photoshop, Save for web, Quality 91', + '00f03e367cd316b71de360c6e7af0e6b:111111' => 'Adobe Photoshop, Save for web, Quality 92', + '982fc46fd167df238fbf23494a1ce761:111111' => 'Adobe Photoshop, Save for web, Quality 93', + '6fd7b56ac6b58dc861e6021815fb5704:111111' => 'Adobe Photoshop, Save for web, Quality 94', + 'c6d9120293c8435cf6b40574b45756bb:111111' => 'Adobe Photoshop, Save for web, Quality 95', + '1e133f4bf9f7c7c1e0accf44c0b1107d:111111' => 'Adobe Photoshop, Save for web, Quality 96', + 'fb91d6a8a1b72388d68130f551698865:111111' => 'Adobe Photoshop, Save for web, Quality 97', + '4ea4e07900e04a3bd7572d4b59aa7a74:111111' => 'Adobe Photoshop, Save for web, Quality 98', + '15e1d2321b96b355d4ad109a8d2fe882:111111' => 'Adobe Photoshop, Save for web, Quality 99', + '234d8f310d75effc9f77beb1d3847f49:111111' => 'Adobe Photoshop, Save for web, Quality 100', + + # Adobe Photoshop Express (Release build number: 481589) + 'aeb34eb083acc888770d65e691497bcf:111111' => 'Adobe Photoshop Express, Original Size', + + # Apple QuickTime (Color) + # + # Tested with: + # - QuickTime 7.5.0 (Win) + # - QuickTime 7.5.0 (Mac) + # - QuickTime 7.4.1 (Mac) + # - GraphicConverter 4.4.4 (Mac) + # - Using the QuickTime library + # - Fixed subsampling to 221111 + # - Use a subset of 101 of the 1024 values + 'dbdc6a6f3c9ffff19e67cfad2cc51ae4:221111' => 'Apple QuickTime, Quality 0-63 (0-6%)', + '23bd5edb6224e03e2f7c282e04986553:221111' => 'Apple QuickTime, Quality 64-80 (6-8%)', + '69747272d4079b78c2ee2ef0f5e63f30:221111' => 'Apple QuickTime, Quality 81-92 (8-9%)', + '4f50903ec9314f739e460c79552a20c5:221111' => 'Apple QuickTime, Quality 93-101 (9-10%)', + '2535e621267a9ff2e2d09148643e3389:221111' => 'Apple QuickTime, Quality 102-109 (10-11%)', + '46a125048b572576eed271816b2cbad2:221111' => 'Apple QuickTime, Quality 110-116 (11%)', + 'ce05e4b45c53a6e321d9cf1061c62128:221111' => 'Apple QuickTime, Quality 117-122 (11-12%)', + 'c3b80241282d06ac5114f2750089589a:221111' => 'Apple QuickTime, Quality 123-127 (12%)', + '9d91481900305fb9ad09339f0924f690:221111' => 'Apple QuickTime, Quality 128-133 (13%)', + '348f4874d57ae6aae04ef96132374913:221111' => 'Apple QuickTime, Quality 134-137 (13%)', + '4e3daadebe0517955b1c86fb1fbc1dc1:221111' => 'Apple QuickTime, Quality 138-142 (13-14%)', + '599b6ad93f32b9d5ce67e1622338c379:221111' => 'Apple QuickTime, Quality 143-146 (14%)', + '66aeb0f03343673eeed6462e0ce9e1aa:221111' => 'Apple QuickTime, Quality 147-150 (14-15%)', + '13570e05da917fe51235ef66a295bc78:221111' => 'Apple QuickTime, Quality 151-154 (15%)', + '25e399a9cf70fe7a13867b40ac2c3416:221111' => 'Apple QuickTime, Quality 155-157 (15%)', + '25eb3d27e65659435a89e401edfab65f:221111' => 'Apple QuickTime, Quality 158-161 (15-16%)', + 'c3bb3c557e70b56a890b07236348518b:221111' => 'Apple QuickTime, Quality 162-164 (16%)', + '792e93c41ac63451068b887b11ad0c2e:221111' => 'Apple QuickTime, Quality 165-167 (16%)', + '35af99d11406974cf2ffa6676801b10c:221111' => 'Apple QuickTime, Quality 168-170 (16-17%)', + '606e5652fc33c6a02328f0bd23ee9751:221111' => 'Apple QuickTime, Quality 171-173 (17%)', + '9b62a9e4544cbc1033c67732ea0bbb08:221111' => 'Apple QuickTime, Quality 174-176 (17%)', + '93ef48999d5659763a33c45a2a0fa784:221111' => 'Apple QuickTime, Quality 177-179 (17%)', + '01b48291bfeccf2fadab996816225b9b:221111' => 'Apple QuickTime, Quality 180-182 (18%)', + '613ef896fc4af5baad36e2680968a7ba:221111' => 'Apple QuickTime, Quality 183-184 (18%)', + '758d37a9d3b91c0ba383d23f5a080d8f:221111' => 'Apple QuickTime, Quality 185-187 (18%)', + '34457d32b9531f04696a52969e02dc1a:221111' => 'Apple QuickTime, Quality 188-189 (18%)', + '6634cdad61e7a8e6fb3a4ba1a0416256:221111' => 'Apple QuickTime, Quality 190-191 (19%)', + 'e5b0739f8e02c6d481e0cdafe7326ae2:221111' => 'Apple QuickTime, Quality 192-194 (19%)', + 'fca91c73d4275748587f97b472b59280:221111' => 'Apple QuickTime, Quality 195-196 (19%)', + '7dc9316230c4f197fb5d1b36f09cd883:221111' => 'Apple QuickTime, Quality 197-198 (19%)', + '13d6536913342860ab993be8b141f644:221111' => 'Apple QuickTime, Quality 199-201 (19-20%)', + 'd835580b2be669d4aa6c68ead27c0c2f:221111' => 'Apple QuickTime, Quality 202-203 (20%)', + '1e91365abfe1d9f7a008c363c834a66e:221111' => 'Apple QuickTime, Quality 204-205 (20%)', + '6f7825365b673f9eb2ac050d27a21d1b:221111' => 'Apple QuickTime, Quality 206-207 (20%)', + '2a98c0b884281080eefcdf98dd33fd6b:221111' => 'Apple QuickTime, Quality 208-209 (20%)', + '0ffe7e9fc17393a338b95c345052b7c5:221111' => 'Apple QuickTime, Quality 210-211 (21%)', + '9dd5c9717d0fd45486af4d26e59ebb35:221111' => 'Apple QuickTime, Quality 212-213 (21%)', + '0ef85155f08194f8fed3f4e7197126e6:221111' => 'Apple QuickTime, Quality 214-215 (21%)', + '35886289b5c8921f7932f895d7f1855d:221111' => 'Apple QuickTime, Quality 216-217 (21%)', + '1d7ac617d70b1880be9c7ba16f96a3ec:221111' => 'Apple QuickTime, Quality 218 (21%)', + 'b26a53dce1477ac3970335df110bb240:221111' => 'Apple QuickTime, Quality 219-220 (21%)', + 'a333be9f2b13b53bfdf64bf5665f8e55:221111' => 'Apple QuickTime, Quality 221-222 (22%)', + 'f1e8d9b3d66fa34ec9a51a987b48a159:221111' => 'Apple QuickTime, Quality 223-224 (22%)', + '20f37f34a9fd18089aa58fe77493a7b7:221111' => 'Apple QuickTime, Quality 225 (22%)', + 'b80e56d3ed0c4a8e1c6bb0c5a1d45ca9:221111' => 'Apple QuickTime, Quality 226-227 (22%)', + 'd14411ab659ac68209ee8c75b941cb48:221111' => 'Apple QuickTime, Quality 228-229 (22%)', + 'a5da49ac5bfe27aafda44bae107ae1c5:221111' => 'Apple QuickTime, Quality 230 (22%)', + '9aa9359126240c0712610121371f870c:221111' => 'Apple QuickTime, Quality 231-232 (23%)', + '5975500a23ab9a547ba149bf1aaa1893:221111' => 'Apple QuickTime, Quality 233-234 (23%)', + 'd03a4790dd96a862113b1a2408103ad6:221111' => 'Apple QuickTime, Quality 235 (23%)', + '44d2a7baaf1e3f8c3d45e4e6272a39b1:221111' => 'Apple QuickTime, Quality 236-237 (23%)', + '7755ba223679105c184be0ada8c99f92:221111' => 'Apple QuickTime, Quality 238 (23%)', + '1ea3f373d0adf989e8416ecb11c38608:221111' => 'Apple QuickTime, Quality 239-240 (23%)', + '12196a46c697fbb88d8bef279b52b106:221111' => 'Apple QuickTime, Quality 241 (24%)', + '2183a6f77fe72f5c70726244dcabc963:221111' => 'Apple QuickTime, Quality 242-243 (24%)', + 'd1ca3a3723c1385d2989b199a7a30557:221111' => 'Apple QuickTime, Quality 244 (24%)', + 'd83207842d60965f9d194d89f3281ccd:221111' => 'Apple QuickTime, Quality 245-246 (24%)', + '6ae041573525edd42e800e1b61d4313c:221111' => 'Apple QuickTime, Quality 247 (24%)', + '6c42d12564d1c5706653a8ddb5375192:221111' => 'Apple QuickTime, Quality 248-249 (24%)', + '671a071a1b17f49a774da3893f7199c7:221111' => 'Apple QuickTime, Quality 250 (24%)', + 'fb3c0cc15ad21b6c19576dd8d7d29a0e:221111' => 'Apple QuickTime, Quality 251 (25%)', + 'c9ce3dc3d0567f631e463cc3ff1b2e30:221111' => 'Apple QuickTime, Quality 252-253 (25%)', + 'b309a0dc90b16ac01f0798a04c3127e8:221111' => 'Apple QuickTime, Quality 254 (25%)', + 'cd15038a76bd8752c3afd14669816c2e:221111' => 'Apple QuickTime, Quality 255 (25%)', + 'd275e9aebd39cf411496caf6e54d0c5f:221111' => 'Apple QuickTime, Quality 256-257 (25%)', + '5e75328df5dadca132bb83e0883ce522:221111' => 'Apple QuickTime, Quality 258 (25%)', + 'b04cbc1812939770d59461982cd9d32d:221111' => 'Apple QuickTime, Quality 259 (25%)', + 'ddf1f3b922ea51f6f4ca3cb6863eeae0:221111' => 'Apple QuickTime, Quality 260-261 (25%)', + '5532e398abb0a455b528659e59c7cfd7:221111' => 'Apple QuickTime, Quality 262 (26%)', + 'f9bff3eeb4e94fb9ab4820184b0b6058:221111' => 'Apple QuickTime, Quality 263 (26%)', + '081da80ed314194b571ff9880a7c11d3:221111' => 'Apple QuickTime, Quality 264-265 (26%)', + '178aa0138d7a08be081aeff794956a71:221111' => 'Apple QuickTime, Quality 266 (26%)', + '78bb04e3ced3eee51c78e94b421ecc26:221111' => 'Apple QuickTime, Quality 267 (26%)', + '17782e930dc2cba42da909d95278fe9b:221111' => 'Apple QuickTime, Quality 268 (26%)', + '093b011ce21ae794d3eca7c64eecf5b6:221111' => 'Apple QuickTime, Quality 269 (26%)', + '4bf515768d1a06e4c529ebae3e03b4b5:221111' => 'Apple QuickTime, Quality 270-271 (26%)', + '3dd79429ada0455422ff6605c1727456:221111' => 'Apple QuickTime, Quality 272 (27%)', + '4be1504b9732d1d9f6265d0616bad21b:221111' => 'Apple QuickTime, Quality 273 (27%)', + 'b2118dc8e8b1762cc634e135a2a1893c:221111' => 'Apple QuickTime, Quality 274 (27%)', + '70d843457698f46db30181ac616deb75:221111' => 'Apple QuickTime, Quality 275 (27%)', + 'f3c42f077883313db21c72bd240de05f:221111' => 'Apple QuickTime, Quality 276 (27%)', + '56e9a02eb25508a9f71ad1a7cb9f9f4d:221111' => 'Apple QuickTime, Quality 277-278 (27%)', + '1d956197da5eb19ffe8855a0e2a52c98:221111' => 'Apple QuickTime, Quality 279 (27%)', + '3e37de5c00962684feba769939fce685:221111' => 'Apple QuickTime, Quality 280 (27%)', + '5628aeb29bb04d9c5073bc1caf371f01:221111' => 'Apple QuickTime, Quality 281 (27%)', + 'a2e7b219d18177294378485759215f72:221111' => 'Apple QuickTime, Quality 282 (28%)', + '537f40d0aae588fbce4cde9ba148604d:221111' => 'Apple QuickTime, Quality 283 (28%)', + '14b7d58b539ad8d6f1c4f8fd82c91358:221111' => 'Apple QuickTime, Quality 284 (28%)', + '58b302794024b9842657bbe7cb667577:221111' => 'Apple QuickTime, Quality 285 (28%)', + '4be64c2782cbb36b757cdcadd756498a:221111' => 'Apple QuickTime, Quality 286 (28%)', + '6c947f09bc02f87b257a26f9f5c77a77:221111' => 'Apple QuickTime, Quality 287 (28%)', + '663b8a9dbd00efa78281f5028b35c503:221111' => 'Apple QuickTime, Quality 288 (28%)', + 'e5deb190a5e17492a01e8136afdfd6c1:221111' => 'Apple QuickTime, Quality 289 (28%)', + 'ba36e1298ce7fed908ee3e02b83ae7c3:221111' => 'Apple QuickTime, Quality 290 (28%)', + 'bc4abc4600f2efc0bdead1e4be78801b:221111' => 'Apple QuickTime, Quality 291-292 (28-29%)', + '43887ad276efb9ca8e8110498b38d814:221111' => 'Apple QuickTime, Quality 293 (29%)', + '3050624718ce9acc06f85c2fa0208cc7:221111' => 'Apple QuickTime, Quality 294 (29%)', + 'f6d9c8699e54823040b66c4b8e1361aa:221111' => 'Apple QuickTime, Quality 295 (29%)', + 'a9b2875fc3c21e7b998969c57f74fa7a:221111' => 'Apple QuickTime, Quality 296 (29%)', + 'b9be740b8a374a52808ad5ef6db2bfe7:221111' => 'Apple QuickTime, Quality 297 (29%)', + '63f9786c6a9b8ef87c791818ddaba058:221111' => 'Apple QuickTime, Quality 298 (29%)', + 'a65113fd3b66ef137f9b1144367f731b:221111' => 'Apple QuickTime, Quality 299 (29%)', + 'ee58773aa7b774040d650365937cf173:221111' => 'Apple QuickTime, Quality 300 (29%)', + '29a9ee0cae41784d90fa74d7cd240a3e:221111' => 'Apple QuickTime, Quality 301 (29%)', + '369e1cfc338b45a239cb7db09778037e:221111' => 'Apple QuickTime, Quality 302 (29%)', + '131ddd6eec5f51e825cf7afd9c7ab3b2:221111' => 'Apple QuickTime, Quality 303 (30%)', + '88a2772be7b74a5a9b7ebbea28ddde47:221111' => 'Apple QuickTime, Quality 304 (30%)', + '83e206dafb515f20a4b9a0c16f770940:221111' => 'Apple QuickTime, Quality 305 (30%)', + 'dddc3adae44a64457b05416affc2502e:221111' => 'Apple QuickTime, Quality 306 (30%)', + 'ff0758d87a0cbdb323fb93bf9ed1fdff:221111' => 'Apple QuickTime, Quality 307 (30%)', + 'b1b1a08ebaf13142b731c95771d97226:221111' => 'Apple QuickTime, Quality 308 (30%)', + 'd6e206f8224d6a3582fb1066b511437b:221111' => 'Apple QuickTime, Quality 309 (30%)', + '2926d6bf5a27174bd9057bd6198413cd:221111' => 'Apple QuickTime, Quality 310 (30%)', + '5d1b5e80f9777a636d1d5cb402fcfc32:221111' => 'Apple QuickTime, Quality 311 (30%)', + 'ae39c8775a10e34accdf2bba3bffc483:221111' => 'Apple QuickTime, Quality 312 (30%)', + 'bc6d3a9f349a97c5cde3f8fa4e1b5beb:221111' => 'Apple QuickTime, Quality 313 (31%)', + '91bd468ca96fe548a7df9646b51880d1:221111' => 'Apple QuickTime, Quality 314 (31%)', + '104c3b63e4ca667a4ee2e4250340052c:221111' => 'Apple QuickTime, Quality 315 (31%)', + '64b80be38604eaecc99236b1f74a99f8:221111' => 'Apple QuickTime, Quality 316 (31%)', + '284efada45882694778e65969f761478:221111' => 'Apple QuickTime, Quality 317 (31%)', + 'd8bd88390c27b2b05a0784eafd4b31ef:221111' => 'Apple QuickTime, Quality 318 (31%)', + '99bf8158a4060d354b521f3d6f5648ac:221111' => 'Apple QuickTime, Quality 319 (31%)', + '96c9e3cd827097ec03edc458fc1053e4:221111' => 'Apple QuickTime, Quality 320 (31%)', + '272b5b12f7701be4cceba51e9d5dbf13:221111' => 'Apple QuickTime, Quality 321 (31%)', + 'dcc3ffcda228ab283d53e1dc2cb739ef:221111' => 'Apple QuickTime, Quality 322-324 (31-32%)', + 'c44701e8185306f5e6d09be16a2b0fbd:221111' => 'Apple QuickTime, Quality 325 (32%)', + '476a1ebd043ed59e56d18dd6d08777d7:221111' => 'Apple QuickTime, Quality 326 (32%)', + '26831dfc8d0dc1d202d50d6cf7b4f4a4:221111' => 'Apple QuickTime, Quality 327 (32%)', + '00f929d549fdd9f89fbb10303445cc2c:221111' => 'Apple QuickTime, Quality 328 (32%)', + '030736cda242f0583a7064cb60cc026e:221111' => 'Apple QuickTime, Quality 329 (32%)', + '825fb58744c6c2432d232f5fb83a9597:221111' => 'Apple QuickTime, Quality 330 (32%)', + '0bac94d5b6ef090da7875e294a7f8040:221111' => 'Apple QuickTime, Quality 331 (32%)', + '16de07616490b8439576d837c74aefbe:221111' => 'Apple QuickTime, Quality 332 (32%)', + '8190e844832ee8ea97492b509c728de4:221111' => 'Apple QuickTime, Quality 333 (33%)', + '0bb6bf7365676f75d285bb38a40b8e3f:221111' => 'Apple QuickTime, Quality 334 (33%)', + '04710d4ba5233b4f82bd260263f9e992:221111' => 'Apple QuickTime, Quality 335 (33%)', + 'fcb49e821b83f8436d450b03f1b1f182:221111' => 'Apple QuickTime, Quality 336 (33%)', + '1bca645051a125cd2c3af262074f70e7:221111' => 'Apple QuickTime, Quality 337 (33%)', + '7a5c04b63f9fe6af176efef387ba1f03:221111' => 'Apple QuickTime, Quality 338 (33%)', + '24fff8dcfdc8640225fff020ad869c18:221111' => 'Apple QuickTime, Quality 339 (33%)', + '08549fa433585b86d6eab75b6dcb1fe3:221111' => 'Apple QuickTime, Quality 340 (33%)', + 'caabe462a50217592c74902def037c07:221111' => 'Apple QuickTime, Quality 341 (33%)', + '757e97f3490ebc5b74fd63792fb23992:221111' => 'Apple QuickTime, Quality 342 (33%)', + 'aa6072a632f7bae361c8d371aa022c57:221111' => 'Apple QuickTime, Quality 343 (33%)', + '03809d08372d3a9fd86ff254854f45b7:221111' => 'Apple QuickTime, Quality 344 (34%)', + 'bdebcafc7b5f6b7fea114943e042df5e:221111' => 'Apple QuickTime, Quality 345 (34%)', + '555dc90fb10df448f37c67ee7ec31bc2:221111' => 'Apple QuickTime, Quality 346 (34%)', + 'a2f2a404cd1c2278ef65f2a27c0365e0:221111' => 'Apple QuickTime, Quality 347 (34%)', + 'aee867276d6dc4ed4b682a454815acd1:221111' => 'Apple QuickTime, Quality 348 (34%)', + '8f2f9e8433104cedb50c3e54577fcd00:221111' => 'Apple QuickTime, Quality 349 (34%)', + '84c2067991afbb6851204f21f5d132ea:221111' => 'Apple QuickTime, Quality 350 (34%)', + '2d2a77bf6078ab4f07261c76b637b597:221111' => 'Apple QuickTime, Quality 351 (34%)', + 'fc9d0f82571701e8b4cf764125ac0d2e:221111' => 'Apple QuickTime, Quality 352 (34%)', + '75d4ffdc6c10675cb1b5bd002d4e0e41:221111' => 'Apple QuickTime, Quality 353 (34%)', + '4b96c3457701c201f90d56af1a82d43b:221111' => 'Apple QuickTime, Quality 354 (35%)', + 'a56edeb9e571dc790a429c26ebc59976:221111' => 'Apple QuickTime, Quality 355 (35%)', + '3673ce9ec4f6f916009d39282ff3a8d7:221111' => 'Apple QuickTime, Quality 356 (35%)', + 'b7279ade733ff1c88073971cebe6edd8:221111' => 'Apple QuickTime, Quality 357 (35%)', + 'b35f3358027aa4d2cca0c64425aa8f1b:221111' => 'Apple QuickTime, Quality 358 (35%)', + '854d2e536bc92a9e2e3db3ff2c18e138:221111' => 'Apple QuickTime, Quality 359 (35%)', + '48b6aa4f0258162cceb9d43e19c96043:221111' => 'Apple QuickTime, Quality 360 (35%)', + '1dfb48b5955cf2a50011f52b9a05f1a4:221111' => 'Apple QuickTime, Quality 361 (35%)', + 'd67da3fcbac8975acffe7f1ab088f646:221111' => 'Apple QuickTime, Quality 362 (35%)', + '7be7bded72d0ade6f907e3adcf62b391:221111' => 'Apple QuickTime, Quality 363 (35%)', + '02c0554e4a004ceaddd0d7772e68a38b:221111' => 'Apple QuickTime, Quality 364 (36%)', + 'e7b9303f785f78a2cb27f83616c18726:221111' => 'Apple QuickTime, Quality 365 (36%)', + '4a4a154781db3f5f500e8cf177a4b446:221111' => 'Apple QuickTime, Quality 366 (36%)', + '7e4b44f2900a405e7b85090af7d40298:221111' => 'Apple QuickTime, Quality 367 (36%)', + '76aa290370382de8a3516f73389f9350:221111' => 'Apple QuickTime, Quality 368 (36%)', + '912779b5b7c935f2b533af0f400402f3:221111' => 'Apple QuickTime, Quality 369 (36%)', + '91c7f694fbf07321037a838c3a4d6e7d:221111' => 'Apple QuickTime, Quality 370 (36%)', + 'f98a4286abf1cbe8bf46fba1e78cec61:221111' => 'Apple QuickTime, Quality 371 (36%)', + '2fbbcce5a035d6215e4851a0ae63481f:221111' => 'Apple QuickTime, Quality 372 (36%)', + '8d8fab3b6b7386a4e81f10c15a7abaa5:221111' => 'Apple QuickTime, Quality 373 (36%)', + '82e672854d7e00d47a988855b95d2f7f:221111' => 'Apple QuickTime, Quality 374 (37%)', + '9fffe6b2fbbce23598c19e6cd177adb0:221111' => 'Apple QuickTime, Quality 375 (37%)', + '3f69293a4abeb2201004e7241fe22c75:221111' => 'Apple QuickTime, Quality 376 (37%)', + 'a01d3a7766c7c593a79ff6c63433860a:221111' => 'Apple QuickTime, Quality 377 (37%)', + '787ed74c3d5570c03f98804bc9d0c448:221111' => 'Apple QuickTime, Quality 378 (37%)', + '12239aa16bb4091d8f873f9536e40371:221111' => 'Apple QuickTime, Quality 379 (37%)', + 'f45d495d3b470eadba70bcca888042b3:221111' => 'Apple QuickTime, Quality 380 (37%)', + '97346edee67c3afea7823c72e57cb6c5:221111' => 'Apple QuickTime, Quality 381 (37%)', + '5b35e4bc9cbbc353b8e4b73132324088:221111' => 'Apple QuickTime, Quality 382 (37%)', + 'a43b370edaaee853bb16e46ee4a002e8:221111' => 'Apple QuickTime, Quality 383 (37%)', + '7f3d110973a4d7d5824724c4e577b407:221111' => 'Apple QuickTime, Quality 384 (38%)', + '0e36104efe90a5a77e9b686d0a6528ab:221111' => 'Apple QuickTime, Quality 385 (38%)', + '0bc0941c2a59d9a12b66d1d34117cfd7:221111' => 'Apple QuickTime, Quality 386 (38%)', + '342e3bddb81140ea9df00400a46461d7:221111' => 'Apple QuickTime, Quality 387 (38%)', + 'ad8bbd6b23b87950d1b76278fbb7de87:221111' => 'Apple QuickTime, Quality 388 (38%)', + 'd58f5d339b69e1296911a3387cc664a4:221111' => 'Apple QuickTime, Quality 389 (38%)', + 'c740804ef8493bb467744e1cdb8882c1:221111' => 'Apple QuickTime, Quality 390 (38%)', + 'ab975404bdb713bb6a58ac560330aaf1:221111' => 'Apple QuickTime, Quality 391 (38%)', + 'eb68a0ff9c83267e5fb5e998365b4480:221111' => 'Apple QuickTime, Quality 392 (38%)', + '9c1865e7fdc0289dc5fe8f4c1f65577e:221111' => 'Apple QuickTime, Quality 393 (38%)', + '21db4122ad8183006542018e53e0c653:221111' => 'Apple QuickTime, Quality 394 (38%)', + '734255167cbd052200fb4c474f05bcd9:221111' => 'Apple QuickTime, Quality 395 (39%)', + 'e5dcd017a9734f9f0e18b515c7fa1787:221111' => 'Apple QuickTime, Quality 396 (39%)', + 'c58a47c2e3dc737b9591420812b9cc27:221111' => 'Apple QuickTime, Quality 397 (39%)', + 'ea25d0beaa91434a14348fb60f5cff31:221111' => 'Apple QuickTime, Quality 398 (39%)', + '21f8a4a67742edcde0ac854522028c9f:221111' => 'Apple QuickTime, Quality 399 (39%)', + '8b8dc34912d8b18742a7670be4b1c867:221111' => 'Apple QuickTime, Quality 400 (39%)', + 'f7adac1fb54bb1fc566b66822122a9c6:221111' => 'Apple QuickTime, Quality 401 (39%)', + '0fe0c7e65c0696d9e76ad819d61e44ae:221111' => 'Apple QuickTime, Quality 402 (39%)', + '6dabf05ddc213b650ff08aa9a8cb9f50:221111' => 'Apple QuickTime, Quality 403 (39%)', + '226788e417078cdcc5aa989379b9e824:221111' => 'Apple QuickTime, Quality 404 (39%)', + '9366dde6c37f4cac36a8e8cea4d5f51c:221111' => 'Apple QuickTime, Quality 405 (40%)', + '3ce5057aaf0ff155ee69d66591c8290d:221111' => 'Apple QuickTime, Quality 406 (40%)', + '58fe81014a9ee26a7bd393c8e31f4011:221111' => 'Apple QuickTime, Quality 407 (40%)', + '41e44bae14ab49d1b0f06438d34cb316:221111' => 'Apple QuickTime, Quality 408 (40%)', + '80ccbe5645cc62ebd4ae7b2128b42d91:221111' => 'Apple QuickTime, Quality 409 (40%)', + 'b08af3cffd1904e8a8cfbbba71077069:221111' => 'Apple QuickTime, Quality 410 (40%)', + 'abb56efe234d4b8fdf50016a19c63684:221111' => 'Apple QuickTime, Quality 411 (40%)', + 'e41806d0928fbb5552225e10db7b55d0:221111' => 'Apple QuickTime, Quality 412 (40%)', + 'ae9de0a8343d730e2e6a358849c29a4e:221111' => 'Apple QuickTime, Quality 413 (40%)', + 'b83146f54d17b2c8e242f7f36dc36f19:221111' => 'Apple QuickTime, Quality 414 (40%)', + 'a748fdfe8d6dc9493253908410e517eb:221111' => 'Apple QuickTime, Quality 415 (41%)', + 'd4bb4c59b5284630a4c716a0290d9091:221111' => 'Apple QuickTime, Quality 416 (41%)', + 'de93dd8ab7918b25f191923f4a43a5c2:221111' => 'Apple QuickTime, Quality 417 (41%)', + '0807f8b3b41b01054509858fa74dcf4d:221111' => 'Apple QuickTime, Quality 418 (41%)', + 'efd780e10dcd0ab8ca0a0f4f3cb215d3:221111' => 'Apple QuickTime, Quality 419 (41%)', + '2ec3d0ec37690e40f009b7a9f9b17c49:221111' => 'Apple QuickTime, Quality 420 (41%)', + 'a4680e71907e5c6f7b18e20e46286412:221111' => 'Apple QuickTime, Quality 421 (41%)', + '8e54abf2320cca661b6dd67b7658c9f3:221111' => 'Apple QuickTime, Quality 422 (41%)', + '00f76480eafd05aa5267053aec3aa122:221111' => 'Apple QuickTime, Quality 423 (41%)', + '849bce1254b14d44e24a6b419c385597:221111' => 'Apple QuickTime, Quality 424 (41%)', + '8a91452f2df82874183be50601242106:221111' => 'Apple QuickTime, Quality 425 (42%)', + 'a670182cd48f37dd16652db878791a7a:221111' => 'Apple QuickTime, Quality 426 (42%)', + 'c2afe9aca67de0276a6fb507861c3e80:221111' => 'Apple QuickTime, Quality 427 (42%)', + '18836b72e5399e2a19cd6420562ab1ff:221111' => 'Apple QuickTime, Quality 428 (42%)', + 'cb207af75faf8ee1ef0ca3caa593bb69:221111' => 'Apple QuickTime, Quality 429 (42%)', + '7a318965f27e3c09d11f53cbb10a872b:221111' => 'Apple QuickTime, Quality 430 (42%)', + 'b0a501129cb83e54f97006610ec9ed64:221111' => 'Apple QuickTime, Quality 431 (42%)', + 'f1a8af8c0abe4b3423d5ac8c6273a7ca:221111' => 'Apple QuickTime, Quality 432 (42%)', + '56224ea0ac2fccb92cbe9702896f9796:221111' => 'Apple QuickTime, Quality 433 (42%)', + '2f2101a8450c617a09ccad472c275b88:221111' => 'Apple QuickTime, Quality 434 (42%)', + '13e218420429e2c94d4b9474ab03f8e4:221111' => 'Apple QuickTime, Quality 435 (42%)', + '41061e1cdb97926ed5bded3da11af209:221111' => 'Apple QuickTime, Quality 436 (43%)', + 'cc3e4dc4e190d00a12bd03199efdcc6d:221111' => 'Apple QuickTime, Quality 437 (43%)', + '36b2371ec6df13143af12d600232c2ab:221111' => 'Apple QuickTime, Quality 438 (43%)', + '71d0e3444a4c82cf39048ba8cf7b1d5f:221111' => 'Apple QuickTime, Quality 439 (43%)', + '9f48e71f610caa47b94d3e474608cb3d:221111' => 'Apple QuickTime, Quality 440 (43%)', + 'dcecd4f366d521e118e94d87ef915caa:221111' => 'Apple QuickTime, Quality 441 (43%)', + 'cf0a070dc9b4a8983b50f8e3f105b857:221111' => 'Apple QuickTime, Quality 442 (43%)', + '5504a428191bc87e5c1ba4b5e9984a37:221111' => 'Apple QuickTime, Quality 443 (43%)', + 'de6a322383022ee8d966e848a2df4f28:221111' => 'Apple QuickTime, Quality 444 (43%)', + '163be99e863436e9b3d32615785ec8e1:221111' => 'Apple QuickTime, Quality 445 (43%)', + '958185c48000065b5b8d03b0f975d95b:221111' => 'Apple QuickTime, Quality 446 (44%)', + '2d719cb263e284fc8621bbec1fe52cd5:221111' => 'Apple QuickTime, Quality 447 (44%)', + '8f0f54955cf19689f38df36715908b76:221111' => 'Apple QuickTime, Quality 448 (44%)', + '737c61e006222488645fa2e007f83f3c:221111' => 'Apple QuickTime, Quality 449 (44%)', + 'bb342113b57cf66ce0cf3a09fae5fd16:221111' => 'Apple QuickTime, Quality 450 (44%)', + 'cd3ed5b396580d8e9f0cb7b78baed8b8:221111' => 'Apple QuickTime, Quality 451 (44%)', + 'e7914fbf6c9b2a127af3676726e6bd8b:221111' => 'Apple QuickTime, Quality 452 (44%)', + '377a7b50c2d7484255bbbf537bf9fa86:221111' => 'Apple QuickTime, Quality 453 (44%)', + '09f9009406d2fe8dfa1b35236f8b1bdb:221111' => 'Apple QuickTime, Quality 454 (44%)', + '78b0e590ea36cb11c495097049022d2e:221111' => 'Apple QuickTime, Quality 455 (44%)', + '984c0f34636e6197b508265f17cbd6c9:221111' => 'Apple QuickTime, Quality 456 (45%)', + '7fca22065811c0efe6599a15ca38f05e:221111' => 'Apple QuickTime, Quality 457 (45%)', + 'd55d9744065708d7b6fa7fb6e8eb2453:221111' => 'Apple QuickTime, Quality 458 (45%)', + '63ef900bf59d41003a0e0602baa60681:221111' => 'Apple QuickTime, Quality 459 (45%)', + 'e8b31dbd18c91229a3c40356efeb2622:221111' => 'Apple QuickTime, Quality 460 (45%)', + '8cb1bb16c6fa524199dad5513386d225:221111' => 'Apple QuickTime, Quality 461 (45%)', + 'c53438ece0552cedb1ec0d50ad2d5dbe:221111' => 'Apple QuickTime, Quality 462 (45%)', + '5db6302d7e68c1a274139033681b8fcc:221111' => 'Apple QuickTime, Quality 463 (45%)', + 'acd1d5ec1787c9d346d87c281a7b6da0:221111' => 'Apple QuickTime, Quality 464-465 (45%)', + 'e66c03f97b19213f385136f014c78ac1:221111' => 'Apple QuickTime, Quality 466-467 (46%)', + '23816ed847127a41e3c7f52e04072e41:221111' => 'Apple QuickTime, Quality 468 (46%)', + 'ebce337e9ef5a07775cebe40d7623862:221111' => 'Apple QuickTime, Quality 469 (46%)', + '09563e47ab174b05fb19f722e9aa43c3:221111' => 'Apple QuickTime, Quality 470 (46%)', + '545e14e832fb81f032526a9efcbf2450:221111' => 'Apple QuickTime, Quality 471 (46%)', + 'edda5d6ae456d4cdccec80e390ac9279:221111' => 'Apple QuickTime, Quality 472 (46%)', + '9a43fc0aa6223673c32a49fb76d6525c:221111' => 'Apple QuickTime, Quality 473 (46%)', + 'bdaf3e68c2925cbaad3864359fdbbb77:221111' => 'Apple QuickTime, Quality 474 (46%)', + '4f83a1a8338a8e6e70eaa58cd236f62a:221111' => 'Apple QuickTime, Quality 475 (46%)', + '879b320cfd6e27b2e283b573483bda81:221111' => 'Apple QuickTime, Quality 476 (46%)', + '2748fa249a86361b1b5f0662a88abdb3:221111' => 'Apple QuickTime, Quality 477 (47%)', + '32eb803f68d72719267a1313548e7180:221111' => 'Apple QuickTime, Quality 478 (47%)', + '771eeb43856b1821a271b0aa8398a243:221111' => 'Apple QuickTime, Quality 479 (47%)', + '653c5006512bb3aaa1e6a4e77078b630:221111' => 'Apple QuickTime, Quality 480 (47%)', + 'c5c102ba5f004d49656f424d89e9773c:221111' => 'Apple QuickTime, Quality 481 (47%)', + 'd5220fcfa99764e440684fbac6273cff:221111' => 'Apple QuickTime, Quality 482 (47%)', + 'd300f18258f46060d89c994dbc370131:221111' => 'Apple QuickTime, Quality 483 (47%)', + '60258dcc1e3a81858d176080ef774730:221111' => 'Apple QuickTime, Quality 484 (47%)', + '8bda9fb1ed75249ac5b2feaad7b51d2f:221111' => 'Apple QuickTime, Quality 485 (47%)', + '9e334af92d75ab7d4ea1a9816840ea73:221111' => 'Apple QuickTime, Quality 486 (47%)', + 'c40a38c96832a6042c6ddfc9754c1d6d:221111' => 'Apple QuickTime, Quality 487 (48%)', + '30be130aa27d0b91d6f55ed9b1cd6c84:221111' => 'Apple QuickTime, Quality 488 (48%)', + '3601e95d6cd507065d46b3f058229d91:221111' => 'Apple QuickTime, Quality 489 (48%)', + '9127f8ddd20e583523bc848e99061126:221111' => 'Apple QuickTime, Quality 490 (48%)', + '0d605d279c48a74ef71a24e89ca426a8:221111' => 'Apple QuickTime, Quality 491 (48%)', + 'a5cd2d8592e1c45b67cfb3009d07fb49:221111' => 'Apple QuickTime, Quality 492 (48%)', + 'e346ce6e3bee6abff16420f5ba95ceb9:221111' => 'Apple QuickTime, Quality 493 (48%)', + '03295b26893cab9c7dea4ec15ed56d08:221111' => 'Apple QuickTime, Quality 494 (48%)', + '76af24fe94edf8f3992e38c1dd6eebce:221111' => 'Apple QuickTime, Quality 495 (48%)', + '3c58e82299d87346d37023ea015f3e80:221111' => 'Apple QuickTime, Quality 496 (48%)', + 'aa8940194463b7adc14f20dbee9c6a75:221111' => 'Apple QuickTime, Quality 497 (49%)', + '68a808b23bfa8096e04006171926b72c:221111' => 'Apple QuickTime, Quality 498 (49%)', + '34c0043b98d09193beda0cf5d1ada274:221111' => 'Apple QuickTime, Quality 499 (49%)', + '5881bb3c6e7e2ac43983b4b1e947a6c3:221111' => 'Apple QuickTime, Quality 500 (49%)', + 'a0a7061bc100f051a3c5470559661138:221111' => 'Apple QuickTime, Quality 501 (49%)', + '62e6812d1f7935adddd1a69227cdf626:221111' => 'Apple QuickTime, Quality 502 (49%)', + '94f6dbd754fb4ba3c92698d5f08084f9:221111' => 'Apple QuickTime, Quality 503 (49%)', + 'b7257ba67e4b38b7ccdca2a65d60c970:221111' => 'Apple QuickTime, Quality 504 (49%)', + '5a19d6130b03080dfedef45b6415f4f8:221111' => 'Apple QuickTime, Quality 505 (49%)', + '2b3262e10b1563600a5f0738fec342ed:221111' => 'Apple QuickTime, Quality 506 (49%)', + '295cb1e2772312ba5cd546966d1aa70d:221111' => 'Apple QuickTime, Quality 507 (50%)', + 'c910bcb7b9e8967b87cfa08229d9ca34:221111' => 'Apple QuickTime, Quality 508 (50%)', + '61c8506b490d5e596151b951ffa7a14f:221111' => 'Apple QuickTime, Quality 509 (50%)', + 'ae15629cecc940fef9f24ad9f207fa10:221111' => 'Apple QuickTime, Quality 510 (50%)', + 'c8ef3c50ca99c44ea13f1692ac1190dc:221111' => 'Apple QuickTime, Quality 511 (50%)', + '50500b1272433ef5c9c96f16069fbdf1:221111' => 'Apple QuickTime, Quality 512 (50%)', + 'e41e5416e21dbfb5a41f006b3485f5bb:221111' => 'Apple QuickTime, Quality 513 (50%)', + 'd606add3e7590885ac8978af6d09a2aa:221111' => 'Apple QuickTime, Quality 514 (50%)', + 'cc3936c39c298ef67d9196d0254b0c19:221111' => 'Apple QuickTime, Quality 515 (50%)', + 'e83f8505dc3f5f46b37e22b590f71b98:221111' => 'Apple QuickTime, Quality 516 (50%)', + '2a8e27e03b6e1555335c91231c452bba:221111' => 'Apple QuickTime, Quality 517 (50%)', + '52ab880d25db7b36137e2a3c04987c9a:221111' => 'Apple QuickTime, Quality 518 (51%)', + 'ce2335cc1f8289deda620877f50fd90d:221111' => 'Apple QuickTime, Quality 519 (51%)', + 'bbad0e19b252268530df19c563aa9176:221111' => 'Apple QuickTime, Quality 520 (51%)', + '3184b71ca26bfe0c80811cf10423fa92:221111' => 'Apple QuickTime, Quality 521 (51%)', + '37802f44dab089a35e03b94a298b19da:221111' => 'Apple QuickTime, Quality 522 (51%)', + '5e528bd6778792490c6cf292cf9ba8df:221111' => 'Apple QuickTime, Quality 523 (51%)', + 'fbe0f5b89f266ff382f2b14c70a83097:221111' => 'Apple QuickTime, Quality 524 (51%)', + 'a4bfd80e0c8b9ae7a1114d79a7b63ad6:221111' => 'Apple QuickTime, Quality 525 (51%)', + 'abdf532dc2005805db7d8d0214227146:221111' => 'Apple QuickTime, Quality 526 (51%)', + '2a7ec778642b15b8bce238f7b63ef537:221111' => 'Apple QuickTime, Quality 527 (51%)', + '70073f02f04ee893510bceb09e411d53:221111' => 'Apple QuickTime, Quality 528 (52%)', + '345d210b180a45bd23b0c7931c59c263:221111' => 'Apple QuickTime, Quality 529 (52%)', + 'ddbc4e6566bbcc74b6205526393ef468:221111' => 'Apple QuickTime, Quality 530 (52%)', + '420af34c4f718cc0a10de5285140b6e0:221111' => 'Apple QuickTime, Quality 531 (52%)', + '16b6c2d8688113b1a28afbbc57f46f80:221111' => 'Apple QuickTime, Quality 532-533 (52%)', + '4a2a0e381fed49e5d5ba074998652561:221111' => 'Apple QuickTime, Quality 534 (52%)', + '50f1255f2424b2de5b930751ddf24842:221111' => 'Apple QuickTime, Quality 535 (52%)', + '80f2c05e2ad3524f18dd55bac10ee2e3:221111' => 'Apple QuickTime, Quality 536 (52%)', + '6c0916ab5aa02602cc682bcdbc22369e:221111' => 'Apple QuickTime, Quality 537 (52%)', + 'f7e5656e1f2cf036e9a57a6c02373398:221111' => 'Apple QuickTime, Quality 538 (53%)', + '92044affd220e31ee953aff021144b29:221111' => 'Apple QuickTime, Quality 539 (53%)', + '2bafe4b75b8a105d72e981b21fe3b6cf:221111' => 'Apple QuickTime, Quality 540 (53%)', + 'ca0e84028714f19cf20cb868d1cd346c:221111' => 'Apple QuickTime, Quality 541 (53%)', + 'db1ae392a31d30cd5564dc7bbea24019:221111' => 'Apple QuickTime, Quality 542 (53%)', + '4417e739b9244781987769c2177abc6f:221111' => 'Apple QuickTime, Quality 543 (53%)', + 'f1de58c1c6a48dc36ce7e8c69636539c:221111' => 'Apple QuickTime, Quality 544 (53%)', + '10c931d7bff7bfcc20e37f0868887228:221111' => 'Apple QuickTime, Quality 545 (53%)', + 'e082971717023e667f3d922bbccf089b:221111' => 'Apple QuickTime, Quality 546 (53%)', + '5a285190351b16fee0eb14778280d74f:221111' => 'Apple QuickTime, Quality 547 (53%)', + 'aad0e2cd42c5adaec41080a05be4ffdc:221111' => 'Apple QuickTime, Quality 548 (54%)', + 'de802b8c64d7f854081c7df6ed345b43:221111' => 'Apple QuickTime, Quality 549 (54%)', + '82f45d11d651d93a67995965b94aa649:221111' => 'Apple QuickTime, Quality 550 (54%)', + '617c4c853344ef079f4a1f1062672e8c:221111' => 'Apple QuickTime, Quality 551-552 (54%)', + '90d96923be1883e6ee15a9d0d32a114c:221111' => 'Apple QuickTime, Quality 553 (54%)', + '307c47179fdad179b5f962228c115db8:221111' => 'Apple QuickTime, Quality 554 (54%)', + 'a4683813bdf6e2bd429c4c5676128384:221111' => 'Apple QuickTime, Quality 555 (54%)', + '8e3cfc2fc9cfbba0f6aed9850504ebb6:221111' => 'Apple QuickTime, Quality 556 (54%)', + 'a8f8928c72b69049e1da7639e977c9c7:221111' => 'Apple QuickTime, Quality 557 (54%)', + '1fb4c8af2d70cdeecab3fd9fc882e0ce:221111' => 'Apple QuickTime, Quality 558 (54%)', + '98ddda3b0ada32ce919b9af9df4054dd:221111' => 'Apple QuickTime, Quality 559 (55%)', + 'b43ab5c404469c416e853e52497b3f0d:221111' => 'Apple QuickTime, Quality 560 (55%)', + 'd9696efa02b9de813caf8d684b06346f:221111' => 'Apple QuickTime, Quality 561 (55%)', + 'be63c4e967eff819bd8a052a561a4576:221111' => 'Apple QuickTime, Quality 562 (55%)', + 'f984581f90913e44f3898fffd8fce8b0:221111' => 'Apple QuickTime, Quality 563 (55%)', + '082779cf55f6b922036f11b74df54110:221111' => 'Apple QuickTime, Quality 564 (55%)', + '066fd6cb3a5dd994fc6159987afde581:221111' => 'Apple QuickTime, Quality 565 (55%)', + '133351a0f39427f1199312585cd6c997:221111' => 'Apple QuickTime, Quality 566 (55%)', + '6c23da63c864f1433ec198ae202e56f0:221111' => 'Apple QuickTime, Quality 567 (55%)', + '7d1819ccce2756fcf6dfbb67565c2552:221111' => 'Apple QuickTime, Quality 568 (55%)', + 'd9794fa54e2ef47be48b972cdca910c2:221111' => 'Apple QuickTime, Quality 569 (56%)', + '085db73bd47194c8fdf567fc619c3b62:221111' => 'Apple QuickTime, Quality 570 (56%)', + '9866add6e1d251e1d4c40793f4300dce:221111' => 'Apple QuickTime, Quality 571 (56%)', + '1b9e7e39831b05b058025ae0a7482d44:221111' => 'Apple QuickTime, Quality 572 (56%)', + 'a8b52e666bd3d81404c0f8915ac18b43:221111' => 'Apple QuickTime, Quality 573 (56%)', + 'e34d11f979458a87492b57eabfd4f4ea:221111' => 'Apple QuickTime, Quality 574 (56%)', + '4295c1330dec60585760cbb05b79662d:221111' => 'Apple QuickTime, Quality 575 (56%)', + '9205cc28769d94d6d00c25804ac70a88:221111' => 'Apple QuickTime, Quality 576 (56%)', + 'b5648f13228d20fd7ae81965394f7515:221111' => 'Apple QuickTime, Quality 577 (56%)', + 'c8f02bf550c40daa39b28911a4ef5a69:221111' => 'Apple QuickTime, Quality 578 (56%)', + 'c2b23a91d377ce2d99ac4109f2740069:221111' => 'Apple QuickTime, Quality 579 (57%)', + 'c0cecb47363aff00a2764a915f95cd35:221111' => 'Apple QuickTime, Quality 580 (57%)', + 'fc0d8f17be060220464fe7bc0a2d754e:221111' => 'Apple QuickTime, Quality 581 (57%)', + '63b59904874e5e427ddecb37e12f90c7:221111' => 'Apple QuickTime, Quality 582 (57%)', + 'df6535865562ce7cbf08e9368e991a95:221111' => 'Apple QuickTime, Quality 583 (57%)', + '74523ad3424dcff6aa697c3ce433ad4e:221111' => 'Apple QuickTime, Quality 584 (57%)', + 'cbdec670ec6d9105277434b304226920:221111' => 'Apple QuickTime, Quality 585 (57%)', + '68783ed0a7956cf0b7a1b2787e756213:221111' => 'Apple QuickTime, Quality 586 (57%)', + 'a431976a61e281e7b9d808f094b74d2e:221111' => 'Apple QuickTime, Quality 587 (57%)', + '8e2a66454fb149552d4538d53ec033aa:221111' => 'Apple QuickTime, Quality 588 (57%)', + '49222c4a3be01e93baad695bba63b254:221111' => 'Apple QuickTime, Quality 589 (58%)', + 'ab8fe796c87f9f61cedbfa64af9f5dec:221111' => 'Apple QuickTime, Quality 590 (58%)', + '9bfe788e7ae4bc9cbe76d36f9a2b1b5e:221111' => 'Apple QuickTime, Quality 591 (58%)', + '22b5f11b635ea5484469708cd7e6e3d9:221111' => 'Apple QuickTime, Quality 592 (58%)', + '44e36eb25c6f9e313ef2a8f4c520c335:221111' => 'Apple QuickTime, Quality 593 (58%)', + 'c71aa81fb12b378dd31a1ca128942f76:221111' => 'Apple QuickTime, Quality 594 (58%)', + 'e56ca8f4da20395ec1f87d380198fa0a:221111' => 'Apple QuickTime, Quality 595 (58%)', + '38f4d508dcf9c82d9488b42a2487b191:221111' => 'Apple QuickTime, Quality 596 (58%)', + '3da1e7270e0900a17a0a4ff8d3c9a488:221111' => 'Apple QuickTime, Quality 597 (58%)', + '900fee18a5f6d1dc3fd856d3d92f5414:221111' => 'Apple QuickTime, Quality 598 (58%)', + '3c724d4b5d8cbe203ebbf92ea8e22808:221111' => 'Apple QuickTime, Quality 599 (58%)', + 'e7e7befa282a985a0532634f360df7db:221111' => 'Apple QuickTime, Quality 600 (59%)', + '1f21bf5b7e0e79c229ef4d06fc9d3cc8:221111' => 'Apple QuickTime, Quality 601 (59%)', + '3bb09b202acd618286d26a33f688f7c7:221111' => 'Apple QuickTime, Quality 602 (59%)', + '9717c5a17cbffdfaa2e5d3769b87fbc5:221111' => 'Apple QuickTime, Quality 603 (59%)', + 'ffa7874d293c62ecc55c098b8f305ae1:221111' => 'Apple QuickTime, Quality 604 (59%)', + 'e68841bf28d33d749d0031bfe3a5219c:221111' => 'Apple QuickTime, Quality 605 (59%)', + '5862c8c2b241a9760f6804d970eefd66:221111' => 'Apple QuickTime, Quality 606 (59%)', + '1d069604250e871bd92a4a24c7be2bd5:221111' => 'Apple QuickTime, Quality 607 (59%)', + '3806bcbefd350e8791be95dfc62bab27:221111' => 'Apple QuickTime, Quality 608 (59%)', + '490b035a665ef80c7b48804461d55b7f:221111' => 'Apple QuickTime, Quality 609 (59%)', + 'bd6943a8c92a14e74d2b24052a19400a:221111' => 'Apple QuickTime, Quality 610 (60%)', + '93e725418f46b2a70723523bef0979fe:221111' => 'Apple QuickTime, Quality 611 (60%)', + '5b66fa5c0c1ba746289747229193cfb0:221111' => 'Apple QuickTime, Quality 612 (60%)', + '1bdac971e8cddd198ad3123849370037:221111' => 'Apple QuickTime, Quality 613 (60%)', + '20f7b70185f4b324a8451ac4657c1d66:221111' => 'Apple QuickTime, Quality 614 (60%)', + '86ab18d6c1359a424f303fcfd0930df2:221111' => 'Apple QuickTime, Quality 615 (60%)', + '0d70031c9962dba7c39da59ada2f1660:221111' => 'Apple QuickTime, Quality 616-617 (60%)', + '9cb9a256b6deb481cf13e5230fe87dbb:221111' => 'Apple QuickTime, Quality 618-619 (60%)', + 'cb34e1a0e18a4dd7ffe823f9c92b3622:221111' => 'Apple QuickTime, Quality 620 (61%)', + 'ac015afc1d80314edd832aebfb495d25:221111' => 'Apple QuickTime, Quality 621 (61%)', + '4974cc7044768888244b324449a238ab:221111' => 'Apple QuickTime, Quality 622 (61%)', + 'bc4541f5bc4d58b99b53d24f3f520b32:221111' => 'Apple QuickTime, Quality 623 (61%)', + 'ec2fd56a50df0e42498018d441a3aa75:221111' => 'Apple QuickTime, Quality 624 (61%)', + '153a6f0994d16003aa4f1112e6757467:221111' => 'Apple QuickTime, Quality 625 (61%)', + 'ed6b90ca62ed648d1102e1c506a0af26:221111' => 'Apple QuickTime, Quality 626 (61%)', + '18593e50c21c8ad521b30933ef7479b1:221111' => 'Apple QuickTime, Quality 627 (61%)', + '6f96ed52a987d67e8d950b2627d3fbc2:221111' => 'Apple QuickTime, Quality 628 (61%)', + 'e76d86e8de4f0bf9e58cd389e0a8c117:221111' => 'Apple QuickTime, Quality 629 (61%)', + '4fa27c83741226576ac6359cd4f6248e:221111' => 'Apple QuickTime, Quality 630 (62%)', + 'be010732a7783ee345548a1eb95d024a:221111' => 'Apple QuickTime, Quality 631 (62%)', + '6700663d4ebaeb394bfd3c85597347b5:221111' => 'Apple QuickTime, Quality 632 (62%)', + '34dba33043aa5ee317b7649242e702b1:221111' => 'Apple QuickTime, Quality 633 (62%)', + '821d7e59bcf756171b7644ec5736266e:221111' => 'Apple QuickTime, Quality 634 (62%)', + 'd81683c0458d9ad72751530d6fbc1389:221111' => 'Apple QuickTime, Quality 635 (62%)', + '4c04d6fe904a4b6ff8b25c9f0e9f0a16:221111' => 'Apple QuickTime, Quality 636 (62%)', + '8efda55d6186d9867189c5cb572c5413:221111' => 'Apple QuickTime, Quality 637 (62%)', + '25497c83113bd738e89d91bd48d7086c:221111' => 'Apple QuickTime, Quality 638 (62%)', + '84c8e142e6d27734b126f76653b9199d:221111' => 'Apple QuickTime, Quality 639 (62%)', + 'bdaf13038b56b5701f60300528f8a89c:221111' => 'Apple QuickTime, Quality 640-641 (63%)', + 'b015ada43293b8d5bd2a8f288f8fb928:221111' => 'Apple QuickTime, Quality 642 (63%)', + '8134ff0c4713cc1ef4a25ff60b49ac54:221111' => 'Apple QuickTime, Quality 643 (63%)', + '967fc5c3ece2b69662257c76397416c9:221111' => 'Apple QuickTime, Quality 644 (63%)', + 'd01a38e0f568d2a7b6b71f8fa63b8bcc:221111' => 'Apple QuickTime, Quality 645 (63%)', + '5229288e448311401bb284133ac7d48c:221111' => 'Apple QuickTime, Quality 646 (63%)', + '76d22de881d1b95b491689b589743b7a:221111' => 'Apple QuickTime, Quality 647 (63%)', + 'c81b03b0291d2277461a551ed6861252:221111' => 'Apple QuickTime, Quality 648-649 (63%)', + 'ebb774b4e106d1a9df5824958d4e5a95:221111' => 'Apple QuickTime, Quality 650 (63%)', + '9ed53fb5bc8e397daf9409251c0a0a6c:221111' => 'Apple QuickTime, Quality 651 (64%)', + '87d40f2e4dad34fa435c62af6817dc18:221111' => 'Apple QuickTime, Quality 652 (64%)', + '0b933cf90e62682da926267d6356ac2b:221111' => 'Apple QuickTime, Quality 653 (64%)', + '2c4a4cb841ee92aa3a2b4c93467ba7a8:221111' => 'Apple QuickTime, Quality 654 (64%)', + '8a6ba56597670b7adb70901eca278049:221111' => 'Apple QuickTime, Quality 655 (64%)', + 'd1ef25928fd4eefe131ffcfc249b9f8a:221111' => 'Apple QuickTime, Quality 656 (64%)', + '8c85462b5a01db09bcbf304d7be1d543:221111' => 'Apple QuickTime, Quality 657 (64%)', + '19c03533b9b2e3304a0b02d9b1054497:221111' => 'Apple QuickTime, Quality 658 (64%)', + 'd2baa8fbc56f0970f820c376c6065d41:221111' => 'Apple QuickTime, Quality 659 (64%)', + 'e6a0a679a13a99de16e13c6ea2829deb:221111' => 'Apple QuickTime, Quality 660-661 (64-65%)', + '8e4f695afcf2a06254561e5e22b7a80b:221111' => 'Apple QuickTime, Quality 662 (65%)', + 'd053fd2c67ce96b0ecf9ffc4b7f7775d:221111' => 'Apple QuickTime, Quality 663 (65%)', + '326c33f64f96592487d2bfdd198738bf:221111' => 'Apple QuickTime, Quality 664 (65%)', + '1ff8f5ff33353a3ee0b6dc8fbb6321a0:221111' => 'Apple QuickTime, Quality 665 (65%)', + '14a5534e4216458662a43101d56d84c8:221111' => 'Apple QuickTime, Quality 666 (65%)', + '0643b87475939754c8d56825cd96242f:221111' => 'Apple QuickTime, Quality 667 (65%)', + '45c46a02a434d8ea759742907bfa0ee5:221111' => 'Apple QuickTime, Quality 668 (65%)', + '2916d9453b885ee4123e6e3ee94ccbc7:221111' => 'Apple QuickTime, Quality 669-671 (65-66%)', + 'b8fca611f92cbc459fe21e11f0214328:221111' => 'Apple QuickTime, Quality 672-673 (66%)', + 'f40fb322c4bde68a2902c86c613af841:221111' => 'Apple QuickTime, Quality 674 (66%)', + '1cbd419717a2916b53f9f504ec1167ca:221111' => 'Apple QuickTime, Quality 675 (66%)', + '79f546689b548868a904f50214928aa1:221111' => 'Apple QuickTime, Quality 676 (66%)', + '8ff6f2d4369155b0474417b00c3c4ac9:221111' => 'Apple QuickTime, Quality 677 (66%)', + 'c60dbbefd4f215b9359dd004f4fb0fd3:221111' => 'Apple QuickTime, Quality 678 (66%)', + 'c192d5847d1146a31db621263a9ce2f5:221111' => 'Apple QuickTime, Quality 679 (66%)', + '4a2361c48a583f6df779d1e6088ed83c:221111' => 'Apple QuickTime, Quality 680 (66%)', + '6c6260b84a3a588614d65133430289ea:221111' => 'Apple QuickTime, Quality 681 (67%)', + '6773f3db56ae831012dbe43c1650571a:221111' => 'Apple QuickTime, Quality 682 (67%)', + '813b89236cfe429fe534361f28ace015:221111' => 'Apple QuickTime, Quality 683 (67%)', + '363b54d38094e5f2e2d63c50870ae76c:221111' => 'Apple QuickTime, Quality 684 (67%)', + '7b17607b9954c37e525b1fbc35271553:221111' => 'Apple QuickTime, Quality 685 (67%)', + '8b1138e2d88033d42698a386a2e8605b:221111' => 'Apple QuickTime, Quality 686 (67%)', + 'c029b8a48e3c93f7c0367f2a149491c7:221111' => 'Apple QuickTime, Quality 687 (67%)', + 'a36199f5a090de94b10a32fbe05f2916:221111' => 'Apple QuickTime, Quality 688-689 (67%)', + 'a873e49b871c32bcaf8e3c6622744e70:221111' => 'Apple QuickTime, Quality 690 (67%)', + 'b9d16f36087d4cca70eef1512c4be569:221111' => 'Apple QuickTime, Quality 691 (67%)', + 'd5994dbe056ea3544b3256a7a6b53749:221111' => 'Apple QuickTime, Quality 692 (68%)', + '0106cf02dcf4109cc6f02fa4ec0e2700:221111' => 'Apple QuickTime, Quality 693 (68%)', + '7d71776416a8771d10e3c2e6dc6a5f21:221111' => 'Apple QuickTime, Quality 694-695 (68%)', + 'c1557f789acc622c8858be4dfbc53c31:221111' => 'Apple QuickTime, Quality 696 (68%)', + '5a54f085c1780cadb13a7dea8347c7c6:221111' => 'Apple QuickTime, Quality 697 (68%)', + '04a5bb959bc203221e72e6575ff39602:221111' => 'Apple QuickTime, Quality 698 (68%)', + '116e3d5fee4e3a695c0f79c09c89ff84:221111' => 'Apple QuickTime, Quality 699 (68%)', + '145bfd5481e99e18c4c3707228557fa5:221111' => 'Apple QuickTime, Quality 700 (68%)', + '19ceef79e864691318beea6502ddc3e1:221111' => 'Apple QuickTime, Quality 701 (68%)', + 'dd0a023941d7bfd118d272f4f925e6e2:221111' => 'Apple QuickTime, Quality 702 (69%)', + '81f039d6a0ded8227dc51273d153b295:221111' => 'Apple QuickTime, Quality 703-704 (69%)', + '9a7ebf265afce16abaa6ca2fbb550b63:221111' => 'Apple QuickTime, Quality 705 (69%)', + '8d0fed09156984328f90f9f19fb5a079:221111' => 'Apple QuickTime, Quality 706 (69%)', + 'be7c72e09c46622b0d2b93e170a03e17:221111' => 'Apple QuickTime, Quality 707 (69%)', + '50a510968effffab80bed1d08c6c5ccc:221111' => 'Apple QuickTime, Quality 708 (69%)', + 'da2501a6f59b2256adb0833b58b504f2:221111' => 'Apple QuickTime, Quality 709 (69%)', + '2a1b83345108443a090cdab4c83143fb:221111' => 'Apple QuickTime, Quality 710 (69%)', + 'e7f293f640878b53fe95a7cb0b1dcc83:221111' => 'Apple QuickTime, Quality 711-712 (69-70%)', + 'd9e0a4c08ef5d7f72eecce74c94c054d:221111' => 'Apple QuickTime, Quality 713 (70%)', + 'c5774ffb4573926fd03d4175818c0e5d:221111' => 'Apple QuickTime, Quality 714 (70%)', + 'e2b368a164b67e15598683f9f184bd77:221111' => 'Apple QuickTime, Quality 715 (70%)', + 'f30792e8fad278c3e1677b5f5b74c682:221111' => 'Apple QuickTime, Quality 716-717 (70%)', + '6f6bfc10750e6717cc3791a9ea1d7569:221111' => 'Apple QuickTime, Quality 718-719 (70%)', + '5e3981a937c61480451d5bdc253e5472:221111' => 'Apple QuickTime, Quality 720 (70%)', + 'c4fb82f47a7b002d5cab421592ae4972:221111' => 'Apple QuickTime, Quality 721 (70%)', + 'a6c4a173169d168e003839e51f035661:221111' => 'Apple QuickTime, Quality 722 (71%)', + '72df283a5c07671eba341500a3fc18f1:221111' => 'Apple QuickTime, Quality 723 (71%)', + 'dfb203555c34fe146c526350e11309eb:221111' => 'Apple QuickTime, Quality 724 (71%)', + '033472a8a855fab8cd8f6a5788dd07c8:221111' => 'Apple QuickTime, Quality 725 (71%)', + '5b8a79eec9b7eb7755deb7f2c189e94a:221111' => 'Apple QuickTime, Quality 726 (71%)', + 'ad3aad027e3829959ebeb6288bfab268:221111' => 'Apple QuickTime, Quality 727 (71%)', + '2234156f0550a047700c2a08459c8242:221111' => 'Apple QuickTime, Quality 728 (71%)', + '0c0351c3a444b851cd105dd5cc4db59c:221111' => 'Apple QuickTime, Quality 729 (71%)', + 'c10fca5e6f66238ab09f7e8105f54e39:221111' => 'Apple QuickTime, Quality 730 (71%)', + '74cc07bbb7049d59aff0c4965d4d5084:221111' => 'Apple QuickTime, Quality 731 (71%)', + 'ce9ad8466ffd84b91039326e8688c44a:221111' => 'Apple QuickTime, Quality 732-733 (71-72%)', + 'e4b0c56d41f4af9e10971876ad7ad56d:221111' => 'Apple QuickTime, Quality 734 (72%)', + '96076425ecc546ec028d0eab48332756:221111' => 'Apple QuickTime, Quality 735 (72%)', + 'ba49b0656894f3c76d852223721b3b1f:221111' => 'Apple QuickTime, Quality 736 (72%)', + 'ae5c6eab0d57249acbcb8b1990b2602f:221111' => 'Apple QuickTime, Quality 737-738 (72%)', + '72f08842473a6c504469d341259e5cd7:221111' => 'Apple QuickTime, Quality 739-740 (72%)', + 'c9f953acdfc1f5afdbb9e9f74692d23e:221111' => 'Apple QuickTime, Quality 741 (72%)', + '5a1b57a2583acf5c2428cd62fe24b773:221111' => 'Apple QuickTime, Quality 742 (72%)', + '6a9ead8b2339567482a172a581e86c15:221111' => 'Apple QuickTime, Quality 743-744 (73%)', + '513d9e9dabbb480eb60f7ef76b1d755e:221111' => 'Apple QuickTime, Quality 745 (73%)', + 'b99bdcd0145833d52b916e71f2c20a04:221111' => 'Apple QuickTime, Quality 746 (73%)', + 'ff084566430a3ed4733cd59aec26a55d:221111' => 'Apple QuickTime, Quality 747 (73%)', + '76bcc27918d8f12b343e6e5a41108781:221111' => 'Apple QuickTime, Quality 748 (73%)', + '7bf7022a7c12b3b7ea085b46158253e6:221111' => 'Apple QuickTime, Quality 749 (73%)', + '4baf3b1df2426fbdac3d0aaa0503ee94:221111' => 'Apple QuickTime, Quality 750 (73%)', + 'bb0180b9eda074c3f913c8ada3d4c1ad:221111' => 'Apple QuickTime, Quality 751 (73%)', + 'c5fcb1748f616ac97794d34b1b93616e:221111' => 'Apple QuickTime, Quality 752-753 (73-74%)', + '0da77ccec22a9cff9a049a47e86d3502:221111' => 'Apple QuickTime, Quality 754 (74%)', + 'c31f71de437dc301d34f847d95267d9e:221111' => 'Apple QuickTime, Quality 755 (74%)', + '01137dc7ef90f0aee15362c221f7b1d3:221111' => 'Apple QuickTime, Quality 756-758 (74%)', + '026780f2172c289bc1ff73a34c6aee57:221111' => 'Apple QuickTime, Quality 759-760 (74%)', + '3fab8f2b141f95a989fc4b046ad825cb:221111' => 'Apple QuickTime, Quality 761 (74%)', + 'cd091eeb9d27d9dc7cdb5bff73572679:221111' => 'Apple QuickTime, Quality 762 (74%)', + 'ad2221ee8bb94a3558ed16766efaec4f:221111' => 'Apple QuickTime, Quality 763 (75%)', + '8a4ff70dce3efc9312ff7239e79b6bc9:221111' => 'Apple QuickTime, Quality 764 (75%)', + 'b8d1fcda3a19d00788c2be73fd4c2c8e:221111' => 'Apple QuickTime, Quality 765-766 (75%)', + '3af16b87c33bb2e48152e249beb9147b:221111' => 'Apple QuickTime, Quality 767 (75%)', + '3af16b87c33bb2e48152e249beb9147b:211111' => 'Apple QuickTime, Quality 768 (75%)', + '683270dbffdc5cd2d4e6cb841f17b206:211111' => 'Apple QuickTime, Quality 769 (75%)', + '285bdd58fac87b174a22d2a93d69cd7c:211111' => 'Apple QuickTime, Quality 770 (75%)', + '312e047b5d9076cd1e126f3dbce928e5:211111' => 'Apple QuickTime, Quality 771 (75%)', + '99458d7a01a39fe126592d9afb1402ce:211111' => 'Apple QuickTime, Quality 772 (75%)', + 'b4633256b0e0d5e2a5021f01ebabc105:211111' => 'Apple QuickTime, Quality 773 (75%)', + '90d39fd222f9114f613a315a894283ca:211111' => 'Apple QuickTime, Quality 774 (76%)', + '61b1d4a02498b7467f2c8e8cfebdfae9:211111' => 'Apple QuickTime, Quality 775-776 (76%)', + '987ebcbd20b633b40241fcd30266e986:211111' => 'Apple QuickTime, Quality 777 (76%)', + '31e214243395b008048469d4bc4dc780:211111' => 'Apple QuickTime, Quality 778 (76%)', + 'db5b3a078a942131b5d86bc189baac24:211111' => 'Apple QuickTime, Quality 779 (76%)', + 'ec440a2ffcbce8895beb663b36975073:211111' => 'Apple QuickTime, Quality 780-781 (76%)', + '93d7ac97a931be74c7fe849edc482ea1:211111' => 'Apple QuickTime, Quality 782 (76%)', + '3974d72e6831171ec970bbb09b9cc506:211111' => 'Apple QuickTime, Quality 783 (76%)', + '07bd22218437079a86ce0b93ffa9cc90:211111' => 'Apple QuickTime, Quality 784 (77%)', + '32757023bb5e7f703acf737a5a29c9d6:211111' => 'Apple QuickTime, Quality 785-786 (77%)', + '6096eb584b99a587f5527e20473aa9d1:211111' => 'Apple QuickTime, Quality 787 (77%)', + 'c6d134475eb85bd454f2ee5153366c51:211111' => 'Apple QuickTime, Quality 788 (77%)', + 'a3ba20f325ff36f874d633919185f92d:211111' => 'Apple QuickTime, Quality 789 (77%)', + 'af10133169e143a2b3634c48dede9440:211111' => 'Apple QuickTime, Quality 790 (77%)', + '9dbb8223620e7f25ca3292849f7aa025:211111' => 'Apple QuickTime, Quality 791-792 (77%)', + '5071640a38c5898dd5d2043346fd23e1:211111' => 'Apple QuickTime, Quality 793-795 (77-78%)', + '60a0ca27f3e7289d97c033ca217899cc:211111' => 'Apple QuickTime, Quality 796-798 (78%)', + 'f7a5ea485a254cba0d39cdeaf89ad344:211111' => 'Apple QuickTime, Quality 799 (78%)', + 'd8cd0ca367d9afaf9a1aca0415da5361:211111' => 'Apple QuickTime, Quality 800 (78%)', + '87fb3c7402ba4edcda34b71696d2b0e3:211111' => 'Apple QuickTime, Quality 801 (78%)', + 'f8df76525f7f97d2e89173989e6786af:211111' => 'Apple QuickTime, Quality 802-804 (78-79%)', + '64677161baed1c47d2fdd6eefd779583:211111' => 'Apple QuickTime, Quality 805 (79%)', + 'ca39dde8e9b4ccd6261b28e089181639:211111' => 'Apple QuickTime, Quality 806-807 (79%)', + 'e6c99d520b86fd6f5eb513d1a084324e:211111' => 'Apple QuickTime, Quality 808 (79%)', + '9e048c787b12b9ab47d6166e81bc8bda:211111' => 'Apple QuickTime, Quality 809 (79%)', + '03c035b39889356e0b10805d8549a1f7:211111' => 'Apple QuickTime, Quality 810 (79%)', + '15de51ede231cfbe123daa42a1a46070:211111' => 'Apple QuickTime, Quality 811-813 (79%)', + '725bcc59a6f5a1436dfa0dfd96cdcf44:211111' => 'Apple QuickTime, Quality 814 (79%)', + 'd00103d50108e8be370a78d47f51aba0:211111' => 'Apple QuickTime, Quality 815 (80%)', + '1c4c74ccc581b11050cfe18792246e5e:211111' => 'Apple QuickTime, Quality 816 (80%)', + 'b63e97c56859f2476ed3f15f40775fb5:211111' => 'Apple QuickTime, Quality 817 (80%)', + 'aac2510e3cd617eb2cd60e7dc6f5d252:211111' => 'Apple QuickTime, Quality 818 (80%)', + '028caa124d0837dd9b1a64028e4f2965:211111' => 'Apple QuickTime, Quality 819 (80%)', + '0440231d1a4a1187bffaa5b5576827f9:211111' => 'Apple QuickTime, Quality 820 (80%)', + '6f879b2b5642ee3d01faf3410a721e2d:211111' => 'Apple QuickTime, Quality 821 (80%)', + '4642245b427d5dd5c1c3766c323204ac:211111' => 'Apple QuickTime, Quality 822-824 (80%)', + '8bd486eb557ae8f39948775aba222731:211111' => 'Apple QuickTime, Quality 825 (81%)', + '8bc4e4bec8e9b193c11ad90c7f8bfaf3:211111' => 'Apple QuickTime, Quality 826 (81%)', + 'f54c2ea8437408238f6c181a355af6cb:211111' => 'Apple QuickTime, Quality 827-829 (81%)', + '0f58458f2b9959dbc57b4868200c0432:211111' => 'Apple QuickTime, Quality 830-832 (81%)', + '24f95056dce30d11bad39b33ab271262:211111' => 'Apple QuickTime, Quality 833-834 (81%)', + 'bad6fdd8761fb9d0921384013acf783f:211111' => 'Apple QuickTime, Quality 835 (82%)', + '8c482fe6aef2a59a94cb779e6795e512:211111' => 'Apple QuickTime, Quality 836 (82%)', + '50b309f18bcf477742aa491ea55af777:211111' => 'Apple QuickTime, Quality 837 (82%)', + '92a9e0d027a1b2e5f7e49f7ffd96277e:211111' => 'Apple QuickTime, Quality 838-839 (82%)', + 'a6df2748a4972d4323f0386820ce35a4:211111' => 'Apple QuickTime, Quality 840 (82%)', + '9ffb80389e2eed2301e6b07860c2fbd7:211111' => 'Apple QuickTime, Quality 841 (82%)', + '6042038094d7f4ad72c61c2a2e7a467f:211111' => 'Apple QuickTime, Quality 842 (82%)', + '78d004490e822405acded09846135e50:211111' => 'Apple QuickTime, Quality 843 (82%)', + 'a589d880de576ed888c57814ccea47a0:211111' => 'Apple QuickTime, Quality 844 (82%)', + 'beee113eea5950b8211cdc49e5a04099:211111' => 'Apple QuickTime, Quality 845 (83%)', + 'f04fed79cdc47709d649187cfcc7e342:211111' => 'Apple QuickTime, Quality 846-849 (83%)', + 'edb0be7fcce943c28d02ff78ae600afb:211111' => 'Apple QuickTime, Quality 850 (83%)', + '0e9648c1f28b99a377dcf7deec6450e6:211111' => 'Apple QuickTime, Quality 851-852 (83%)', + '2dffe433bbb9c81b05e569afd3d9b585:211111' => 'Apple QuickTime, Quality 853 (83%)', + '0ef4f8fa922f87f1be646fccaa0ef42e:211111' => 'Apple QuickTime, Quality 854 (83%)', + '4b799df6fc9476102f890343080e66f5:211111' => 'Apple QuickTime, Quality 855-856 (83-84%)', + '53a66cb32deb83c855f36b26527f4c10:211111' => 'Apple QuickTime, Quality 857 (84%)', + '151d7cd5a95929d45c6790beb87705fe:211111' => 'Apple QuickTime, Quality 858 (84%)', + 'bc2afe0a9c7c68b8d84bd231209be3e2:211111' => 'Apple QuickTime, Quality 859-860 (84%)', + 'b9d66564ab9c4bb0910eb228aa9a48e1:211111' => 'Apple QuickTime, Quality 861-863 (84%)', + '6808ca55a29fcb9c15db1925a84370c3:211111' => 'Apple QuickTime, Quality 864 (84%)', + 'ee5b4ed7f04821d1e3a509d7565cb10d:211111' => 'Apple QuickTime, Quality 865 (84%)', + 'd38be79f7c8c6c27a3268275b144add6:211111' => 'Apple QuickTime, Quality 866 (85%)', + '59eedef87f255db058b5ba0b1d3a4ce8:211111' => 'Apple QuickTime, Quality 867 (85%)', + '5e5530c45def7006a7f672ce5778513d:211111' => 'Apple QuickTime, Quality 868 (85%)', + 'b09abfa40fc6607dc26d8b5df48c72fc:211111' => 'Apple QuickTime, Quality 869 (85%)', + 'cfc78404529f2b81b16d3f25fc96e8f4:211111' => 'Apple QuickTime, Quality 870 (85%)', + '14c62682032efe8dc2de80c9330c6206:211111' => 'Apple QuickTime, Quality 871-872 (85%)', + 'ffadac945c3420537e21e67ab3a843d6:211111' => 'Apple QuickTime, Quality 873-875 (85%)', + 'e67a8a7e92a9f03413e9a67b99624b8b:211111' => 'Apple QuickTime, Quality 876-877 (86%)', + 'ba4af3bb30dda0a7be4c04ff1ebbd9ef:211111' => 'Apple QuickTime, Quality 878-880 (86%)', + '3eedb8a357141ff5ae765fd3be2b232f:211111' => 'Apple QuickTime, Quality 881-886 (86-87%)', + '127b0599fc6804909a33832be7a9dd36:211111' => 'Apple QuickTime, Quality 887 (87%)', + 'b697448eec21ef07f3111b62d592c423:211111' => 'Apple QuickTime, Quality 888-890 (87%)', + 'a08a6b6535f292518b5ff6d0d05ae187:211111' => 'Apple QuickTime, Quality 891-893 (87%)', + 'a439b365c2d0cf1fbaad2e42d331d759:211111' => 'Apple QuickTime, Quality 894 (87%)', + 'bf0c20b20af6473b7c4a338ba57d1a96:211111' => 'Apple QuickTime, Quality 895 (87%)', + '09cf94311753aa9796ffd720749c51f7:211111' => 'Apple QuickTime, Quality 896 (88%)', + 'a60bbd6538af00192c411020d7494a1d:211111' => 'Apple QuickTime, Quality 897-898 (88%)', + 'df8ea903695e76e4b1466bdd3a3480c7:211111' => 'Apple QuickTime, Quality 899 (88%)', + 'eb4eb617beaa4f23acf41167742806fc:211111' => 'Apple QuickTime, Quality 900-901 (88%)', + '591c923a44c635c33769704c9cfa6ab7:211111' => 'Apple QuickTime, Quality 902 (88%)', + '960caf85ef273541ac2e76c9554dc860:211111' => 'Apple QuickTime, Quality 903 (88%)', + '22c77ec6f4e8f75d48f98473abe62e59:211111' => 'Apple QuickTime, Quality 904-905 (88%)', + 'fc8d384969030e7bc0255d34a7a5c0b0:211111' => 'Apple QuickTime, Quality 906 (88%)', + '42e7323506b113685e82e6d42664626f:211111' => 'Apple QuickTime, Quality 907-908 (89%)', + '7163b345b90553e246296a48b46cc0b3:211111' => 'Apple QuickTime, Quality 909-910 (89%)', + '52f25cf8c4d610dffcc45681def8fb49:211111' => 'Apple QuickTime, Quality 911-914 (89%)', + '5554cfd817a2713a690b957145b088ed:211111' => 'Apple QuickTime, Quality 915-917 (89-90%)', + '6ef0b71a5676c4645a3166b9c34744fa:211111' => 'Apple QuickTime, Quality 918-920 (90%)', + '1228da2b97793a88a41542ddcfca7ad2:211111' => 'Apple QuickTime, Quality 921-922 (90%)', + '9060906039e9ff37171ba48d908f6ad5:211111' => 'Apple QuickTime, Quality 923-924 (90%)', + '6eb301fb89e7d625129b77a53fe30dcc:211111' => 'Apple QuickTime, Quality 925-926 (90%)', + 'ad5399708089baad5891319303ba92df:211111' => 'Apple QuickTime, Quality 927 (91%)', + 'afd16e145464c7c5a3cd703017b4ef7a:211111' => 'Apple QuickTime, Quality 928 (91%)', + '4271405c840705072a102d7e18b374d9:211111' => 'Apple QuickTime, Quality 929 (91%)', + '72a91837a63fa7444416bc00a05d988b:211111' => 'Apple QuickTime, Quality 930 (91%)', + '8fe3845bafb06ee4de1a6f75c2a42e9b:211111' => 'Apple QuickTime, Quality 931 (91%)', + '8d3b678651ec71f27e3727718123f354:211111' => 'Apple QuickTime, Quality 932-933 (91%)', + '36d42b031eea0c9f626f15533e72162a:211111' => 'Apple QuickTime, Quality 934-936 (91%)', + '789076781ff1e18154091f2460c1bab5:211111' => 'Apple QuickTime, Quality 937-938 (92%)', + '07464723ecfd8e5ed8fd6904e9d15a23:211111' => 'Apple QuickTime, Quality 939 (92%)', + '0efd0d9423b440cfc8efacf2e4dfcb7f:211111' => 'Apple QuickTime, Quality 940 (92%)', + '80409b38f84336548b62e337a850e9cb:211111' => 'Apple QuickTime, Quality 941-943 (92%)', + '5fa6bb26309d43ca6c89d6cc776a68a4:211111' => 'Apple QuickTime, Quality 944-946 (92%)', + '705064f644ac4b24884500a40ad0f7cf:211111' => 'Apple QuickTime, Quality 947-948 (92-93%)', + 'c181c79bc41cf5fe11e6f253242ce2c4:211111' => 'Apple QuickTime, Quality 949-951 (93%)', + '1a7da03994ee019a30dbd37117761467:211111' => 'Apple QuickTime, Quality 952-954 (93%)', + '070620a25578b4a38ed0c09d6d512de8:211111' => 'Apple QuickTime, Quality 955 (93%)', + '6a092d8fd56ca0e852d74bd86cfc4f47:211111' => 'Apple QuickTime, Quality 956-957 (93%)', + '66e85870faf72f4f3fe25486409b286a:211111' => 'Apple QuickTime, Quality 958 (94%)', + '31365833a4d7d0ef2c1db9b90e515f7f:211111' => 'Apple QuickTime, Quality 959-961 (94%)', + '2edccd94198ab5a459a8396d9a0be4aa:211111' => 'Apple QuickTime, Quality 962 (94%)', + 'ca0bf66c467278f9d5ca5301840e7a7f:211111' => 'Apple QuickTime, Quality 963-967 (94%)', + '261bdba7fe6d8bca5302e4e93b52c1fb:211111' => 'Apple QuickTime, Quality 968-970 (95%)', + '762f9501e83d58307d1e102ddb343207:211111' => 'Apple QuickTime, Quality 971 (95%)', + 'ba18a8f4175bdedfea7af9bf5fe8dd9c:211111' => 'Apple QuickTime, Quality 972-973 (95%)', + 'd1a8052e7152e0c35d167e9e56418eb7:211111' => 'Apple QuickTime, Quality 974 (95%)', + '32682ece28c3bee7754fde6fec109b47:211111' => 'Apple QuickTime, Quality 975-977 (95%)', + 'a8780d0f85eef638c6a448e57b157378:211111' => 'Apple QuickTime, Quality 978-979 (96%)', + 'b79ff1a16807a48a31d457ad7e0b94f2:211111' => 'Apple QuickTime, Quality 980-984 (96%)', + '2bf80ea6a878f7ecb88ea827b58c98f8:211111' => 'Apple QuickTime, Quality 985-987 (96%)', + 'add779ad00786bd2ccb9dcc226386b1a:211111' => 'Apple QuickTime, Quality 988-991 (96-97%)', + '56c4efb597cc30275229486199e60f70:211111' => 'Apple QuickTime, Quality 992-993 (97%)', + 'c2df556e8ede9fb199b9a16e01279c6b:211111' => 'Apple QuickTime, Quality 994-996 (97%)', + '6af868a0eececd267495f749a38b4f95:211111' => 'Apple QuickTime, Quality 997-998 (97%)', + 'c92c755320e7ce8f46f644b90b7907e8:211111' => 'Apple QuickTime, Quality 999-1000 (98%)', + '6fcbaaa11108d1712bad5410b3db5b91:211111' => 'Apple QuickTime, Quality 1001-1002 (98%)', + 'f7d803e16f0c66df7d46747715b1ae24:211111' => 'Apple QuickTime, Quality 1003 (98%)', + '7f51ebf21174bcd3b027ae3cc77c4459:211111' => 'Apple QuickTime, Quality 1004 (98%)', + 'f2423a8ae68a49cc6191a2ec80367893:211111' => 'Apple QuickTime, Quality 1005-1006 (98%)', + 'f97cd4c7b1125556dc3eb57fc494e6b5:211111' => 'Apple QuickTime, Quality 1007-1009 (98-99%)', + '389e1ca056b1bd05dd29ecaecae5b4ae:211111' => 'Apple QuickTime, Quality 1010-1013 (99%)', + '43f9929d00af93968662983b891364d8:211111' => 'Apple QuickTime, Quality 1014-1016 (99%)', + '7e1453eec55a8c40166b2d8985ad6bdc:211111' => 'Apple QuickTime, Quality 1017 (99%)', + '31697e4b294a13e35ab8d55d3a9612ca:211111' => 'Apple QuickTime, Quality 1018-1020 (99-100%)', + 'ec76274ff22c07e53299ad34633ba88f:211111' => 'Apple QuickTime, Quality 1021-1023 (100%)', + '7f8b33a26e7f35a6eaf2e95df81e1cca:111111' => 'Apple QuickTime, Quality 1024 (Lossless)', + + # Apple QuickTime (Grayscale) + # + # Tested with: + # - QuickTime 7.5.0 (Win) + '7e6246d9be5273b979beb680b284e7b8:11' => 'Apple QuickTime, Quality 0-63 (0-6%)', + 'caf33ddc94762bf60a8c5e5024550b21:11' => 'Apple QuickTime, Quality 64-80 (6-8%)', + '042ae0dbef2b1e91c4eb36e66a39b5b9:11' => 'Apple QuickTime, Quality 81-92 (8-9%)', + 'bdcc7abca09941326c079bb3bc30de4d:11' => 'Apple QuickTime, Quality 93-101 (9-10%)', + '8edf0677ca6be750511593fad835bbb5:11' => 'Apple QuickTime, Quality 102-109 (10-11%)', + 'dd54b4e3d8801f3a7969be542d165c6b:11' => 'Apple QuickTime, Quality 110-116 (11%)', + 'c00374dece11c3cab5f2c3bf9621d365:11' => 'Apple QuickTime, Quality 117-122 (11-12%)', + 'a2e3baa02454492ef811619ac18c65da:11' => 'Apple QuickTime, Quality 123-127 (12%)', + '8f699e4439175f5f0cf0f903040fb3c5:11' => 'Apple QuickTime, Quality 128-133 (13%)', + '50f9224c87a32486851bdbd3e686fd5b:11' => 'Apple QuickTime, Quality 134-137 (13%)', + 'cccd5f36920fbe8ad77da2214f8ab6ed:11' => 'Apple QuickTime, Quality 138-142 (13-14%)', + '8bc7e3b8f24507e284075ebeb272c3f4:11' => 'Apple QuickTime, Quality 143-146 (14%)', + 'bc156b933365b88e5ba9f1bd4b2fee4e:11' => 'Apple QuickTime, Quality 147-150 (14-15%)', + 'a50ff29c6c2a7e73f742ca94678956ba:11' => 'Apple QuickTime, Quality 151-154 (15%)', + 'd0a67359275cf9e2e8f35de79d2e28ae:11' => 'Apple QuickTime, Quality 155-157 (15%)', + '37914b5d31e7f0f13066e5292c07c305:11' => 'Apple QuickTime, Quality 158-161 (15-16%)', + '340aeb15b2b6c05968bb2c6e3d85cbed:11' => 'Apple QuickTime, Quality 162-164 (16%)', + 'bc93228921ec863e90850325cfd90dd2:11' => 'Apple QuickTime, Quality 165-167 (16%)', + 'd5c95455812515ad4855ed725d5bf2d9:11' => 'Apple QuickTime, Quality 168-170 (16-17%)', + 'd018c811df2390446b43cc702888864c:11' => 'Apple QuickTime, Quality 171-173 (17%)', + '824a9788f50aad6ca26ada301cae5c72:11' => 'Apple QuickTime, Quality 174-176 (17%)', + 'd35254d58224b1b6babda94d7f1a5ffe:11' => 'Apple QuickTime, Quality 177-179 (17%)', + '4177be1c82543b32bf6578dc3a78d49d:11' => 'Apple QuickTime, Quality 180-182 (18%)', + '88b94edfd7a6c7aadac520905e6cfa0a:11' => 'Apple QuickTime, Quality 183-184 (18%)', + '589b1ef8cc8bece150218e4646d9dfd6:11' => 'Apple QuickTime, Quality 185-187 (18%)', + 'a6c15a75ab70e28c78e6084f909523bf:11' => 'Apple QuickTime, Quality 188-189 (18%)', + 'd052e48078f986c715e68f502d371ccc:11' => 'Apple QuickTime, Quality 190-191 (19%)', + '7d4205e3d4e0b6c7071a418c9b5840cb:11' => 'Apple QuickTime, Quality 192-194 (19%)', + '2200d1873e51bf812bdcb57c10c6c14b:11' => 'Apple QuickTime, Quality 195-196 (19%)', + '395ef59782311cd2081887c78c40c4bc:11' => 'Apple QuickTime, Quality 197-198 (19%)', + 'd33d12dc779097bee959fefac6de9a3e:11' => 'Apple QuickTime, Quality 199-201 (19-20%)', + '64ff54dc33f610e3705cae31428ce43d:11' => 'Apple QuickTime, Quality 202-203 (20%)', + 'dbee605b07dfe30c992622877dffb049:11' => 'Apple QuickTime, Quality 204-205 (20%)', + 'aa5a427657696f05da789e1516b8c2ff:11' => 'Apple QuickTime, Quality 206-207 (20%)', + 'cf8fce0d4bde00a2feb680bb52667c8f:11' => 'Apple QuickTime, Quality 208-209 (20%)', + '81ac42cc63416f7c66cd2a51a8801cbd:11' => 'Apple QuickTime, Quality 210-211 (21%)', + '5e55cc3328e61e88b9f2a49af4ec2268:11' => 'Apple QuickTime, Quality 212-213 (21%)', + 'ef938a0533502fe19f311d46c43fa86c:11' => 'Apple QuickTime, Quality 214-215 (21%)', + 'f841cbd6a77d64924ab19845219f3399:11' => 'Apple QuickTime, Quality 216-217 (21%)', + '98b684f30055c84ba5734e29f7b98b5f:11' => 'Apple QuickTime, Quality 218 (21%)', + '8d4f697b3a2baaecc8765f31f54a76ae:11' => 'Apple QuickTime, Quality 219-220 (21%)', + '6fc283989bb3a8c91f6c4384df2fa25d:11' => 'Apple QuickTime, Quality 221-222 (22%)', + 'dccca51d261b315120f069697872377d:11' => 'Apple QuickTime, Quality 223-224 (22%)', + '5869e4a9592a7900e740b09fe19261a1:11' => 'Apple QuickTime, Quality 225 (22%)', + 'ebd575cf069eb906d2f2b2e202f67247:11' => 'Apple QuickTime, Quality 226-227 (22%)', + '7ed52852c280b97fd44def8434d84051:11' => 'Apple QuickTime, Quality 228-229 (22%)', + '984d291debac8a0caeaccccea5fbfbdf:11' => 'Apple QuickTime, Quality 230 (22%)', + '9b2247e0f55b4485e7c55a04ee6a801c:11' => 'Apple QuickTime, Quality 231-232 (23%)', + 'd2a1887cf45aecd63d838e585dbb5794:11' => 'Apple QuickTime, Quality 233-234 (23%)', + '6bb5ab15f80beebcb73fae0ef089fa61:11' => 'Apple QuickTime, Quality 235 (23%)', + '281f65a19e5de33d9ff5f3afeda06973:11' => 'Apple QuickTime, Quality 236-237 (23%)', + '563f732877b2c654d571c269bbb36a40:11' => 'Apple QuickTime, Quality 238 (23%)', + 'fa11118bb9f90b1464e34c785d0da357:11' => 'Apple QuickTime, Quality 239-240 (23%)', + '8dc4cff27c3c5196b4bc8905ef32f119:11' => 'Apple QuickTime, Quality 241 (24%)', + '2ac28889e4ad4724d49f8b4c36b0cece:11' => 'Apple QuickTime, Quality 242-243 (24%)', + '5cbeb8f83a47b6a5e8711fe0ea7c42d7:11' => 'Apple QuickTime, Quality 244 (24%)', + 'd5d329f5687d154e4ceeb48697b848ba:11' => 'Apple QuickTime, Quality 245-246 (24%)', + 'd3eaad34ae4fc8a3ac6330c1c9dceb28:11' => 'Apple QuickTime, Quality 247 (24%)', + '29b74834ce7570b9c175d0200e75316e:11' => 'Apple QuickTime, Quality 248-249 (24%)', + '850f2b2aaa99ad390bc9443be1b587dc:11' => 'Apple QuickTime, Quality 250 (24%)', + '036d2395718f99bf916486e1af42cb92:11' => 'Apple QuickTime, Quality 251 (25%)', + 'e9275719ef4cb335f9dfed63c3737f0e:11' => 'Apple QuickTime, Quality 252-253 (25%)', + 'dfcbd3df5c6b96106e6348b77f89c56a:11' => 'Apple QuickTime, Quality 254 (25%)', + 'f9ef906cd67c9f9b62514a6ac1f8bd3f:11' => 'Apple QuickTime, Quality 255 (25%)', + '469d14cef27dbb7c1f6f49324c077852:11' => 'Apple QuickTime, Quality 256-257 (25%)', + '65d7471a913f6cc87e9dc65ea594606b:11' => 'Apple QuickTime, Quality 258 (25%)', + '6b590185b5d6ecbc1d79c2624a0d5319:11' => 'Apple QuickTime, Quality 259 (25%)', + 'fbf3d8d87f68077aa95e5e40047c1607:11' => 'Apple QuickTime, Quality 260-261 (25%)', + '7e3999424de8a8f6bb84e3cfc07628e8:11' => 'Apple QuickTime, Quality 262 (26%)', + '6c0476ba4b3fcc4675cfab20d3c96368:11' => 'Apple QuickTime, Quality 263 (26%)', + '0bb76dd0e08175a90343a9c7dab48bfa:11' => 'Apple QuickTime, Quality 264-265 (26%)', + '83c8ceab43dedde06d8068e5b8ccdc2b:11' => 'Apple QuickTime, Quality 266 (26%)', + '5aee693372b77c9721dba9d3596e371c:11' => 'Apple QuickTime, Quality 267 (26%)', + '1312bc5c7456856400f43749d407fb9f:11' => 'Apple QuickTime, Quality 268 (26%)', + '3f01645e33791ef09fbeb6c0e63db6a9:11' => 'Apple QuickTime, Quality 269 (26%)', + '785c36a6aa2bedd207cb1fa450a5e6d4:11' => 'Apple QuickTime, Quality 270-271 (26%)', + '84788c494352a07ab54f360f4a2a3d34:11' => 'Apple QuickTime, Quality 272 (27%)', + 'bf29abfdf0086437452e2ca220e69cae:11' => 'Apple QuickTime, Quality 273 (27%)', + 'c7da2e951711b8b1314a7c531e09cbdc:11' => 'Apple QuickTime, Quality 274 (27%)', + 'fb549f21b7ad3b556bc91165b3067a77:11' => 'Apple QuickTime, Quality 275 (27%)', + '0a7497e67acef345c655f79fd00b26de:11' => 'Apple QuickTime, Quality 276 (27%)', + '1e2be0dde2c5d2216bca879a3f89c565:11' => 'Apple QuickTime, Quality 277-278 (27%)', + 'd09f78b68290ff6b470720ead4d79b15:11' => 'Apple QuickTime, Quality 279 (27%)', + '5b54396a8a725e49e9bd4c9883b151df:11' => 'Apple QuickTime, Quality 280 (27%)', + 'f60d4afe566a641f0187a42ca6462560:11' => 'Apple QuickTime, Quality 281 (27%)', + 'b69090d1ab951e6355ab193b1f20bf48:11' => 'Apple QuickTime, Quality 282 (28%)', + '7ce7d00283cada911c3ebc347680bc7d:11' => 'Apple QuickTime, Quality 283 (28%)', + '2502314c1b957a0e4f911d17db770a01:11' => 'Apple QuickTime, Quality 284 (28%)', + '100f3392aa8292fb78548513a619671a:11' => 'Apple QuickTime, Quality 285 (28%)', + 'af683de3118ab595c41b5796b57a9540:11' => 'Apple QuickTime, Quality 286 (28%)', + '1228f1572d76b53658f4042bda8e99a2:11' => 'Apple QuickTime, Quality 287 (28%)', + 'e9c647b8bf2d7535d259eed6fbabe206:11' => 'Apple QuickTime, Quality 288 (28%)', + '9ab0afefad0e6bb7c3c1a8bca0c3f987:11' => 'Apple QuickTime, Quality 289 (28%)', + 'b6900aafebce0e59136abb701eacb1e5:11' => 'Apple QuickTime, Quality 290 (28%)', + 'a3c40b635e584c8f49d6b6b110846fee:11' => 'Apple QuickTime, Quality 291-292 (28-29%)', + '80175a9dbb871d045c738fdeb6fcbdc7:11' => 'Apple QuickTime, Quality 293 (29%)', + 'c79231716eff96853fe03a26c1c38120:11' => 'Apple QuickTime, Quality 294 (29%)', + '8f96a0f2af7f1f1b0b2d4895bced1326:11' => 'Apple QuickTime, Quality 295 (29%)', + 'ba3c103d00719e795b093ac7a75e6fac:11' => 'Apple QuickTime, Quality 296 (29%)', + '42fd2864197991a38b3f80374a69d4e9:11' => 'Apple QuickTime, Quality 297 (29%)', + '76bc1d777c94b680683610218732eb11:11' => 'Apple QuickTime, Quality 298 (29%)', + '8b9e19fe69d7c7e1989018aca76c0aea:11' => 'Apple QuickTime, Quality 299 (29%)', + 'd3c0e7437c630f3bed0867737c5f1921:11' => 'Apple QuickTime, Quality 300 (29%)', + 'a8ecf55a88fd0e1b29646207aff8c36f:11' => 'Apple QuickTime, Quality 301 (29%)', + 'eaffe0714878be5fb67a914f5bb79fef:11' => 'Apple QuickTime, Quality 302 (29%)', + 'a6a49ea0300157ecb401ce45d7f1f850:11' => 'Apple QuickTime, Quality 303 (30%)', + 'fc28ca358af7cd55dc78853e4288f26d:11' => 'Apple QuickTime, Quality 304 (30%)', + '61cb5e93e3e69f6929d97653824733b0:11' => 'Apple QuickTime, Quality 305 (30%)', + '2cba6ba1aede8c791ada1acaba8c162e:11' => 'Apple QuickTime, Quality 306 (30%)', + 'c1bcc3db9f417dc52595f2bb224e30d7:11' => 'Apple QuickTime, Quality 307 (30%)', + '2273274a8d695da4bebff145cbcbafcc:11' => 'Apple QuickTime, Quality 308 (30%)', + 'e8bdbff8c7908e36c51e1344c0e99746:11' => 'Apple QuickTime, Quality 309 (30%)', + 'ffb8ea8efdb22c5c8256cc4e4008f11c:11' => 'Apple QuickTime, Quality 310 (30%)', + 'a1a8f92dc00c42877eb9a1d7462f8408:11' => 'Apple QuickTime, Quality 311 (30%)', + '723c2a2de195391f2db06456e9345c5b:11' => 'Apple QuickTime, Quality 312 (30%)', + '916225049ab8d411a5e0138ea9087e37:11' => 'Apple QuickTime, Quality 313 (31%)', + '3b0315316de45b649bd8ba5b5471ab81:11' => 'Apple QuickTime, Quality 314 (31%)', + '6cf948e65c9d32279c757394a4f5b77e:11' => 'Apple QuickTime, Quality 315 (31%)', + '4c4b7fc28e54a2bbdccd90d3618f01e8:11' => 'Apple QuickTime, Quality 316 (31%)', + '767c20d7d54970b0974f205c790d7d04:11' => 'Apple QuickTime, Quality 317 (31%)', + '81c1ce1c7d15394d95eaf2d6bd1495e3:11' => 'Apple QuickTime, Quality 318 (31%)', + '6547daee398d39f773742be92ef2d0d0:11' => 'Apple QuickTime, Quality 319 (31%)', + '65edf81f975f01a7b3ad1c16a1af64cb:11' => 'Apple QuickTime, Quality 320 (31%)', + 'f8948967aeda9fb6ca1637a082ed04db:11' => 'Apple QuickTime, Quality 321 (31%)', + '2444e1c407a9965fb5ea2dafd269911f:11' => 'Apple QuickTime, Quality 322-324 (31-32%)', + '7c8242581553e818ef243fc680879a19:11' => 'Apple QuickTime, Quality 325 (32%)', + 'e2fe91d57078586f15b09e3b9c8cd3fa:11' => 'Apple QuickTime, Quality 326 (32%)', + '0740db8af7951c1363f2c8d75462d378:11' => 'Apple QuickTime, Quality 327 (32%)', + '3c1ff7ebab192163b4578e7dfcf63ce6:11' => 'Apple QuickTime, Quality 328 (32%)', + '705ae76b905302bd9f3b78cc8d1cb28f:11' => 'Apple QuickTime, Quality 329 (32%)', + '9438633929a283aac168f415d8ca44d6:11' => 'Apple QuickTime, Quality 330 (32%)', + '68799ccfa08e2f55b5be79264d3ca58a:11' => 'Apple QuickTime, Quality 331 (32%)', + '9bda57f21c56ea0dc971164b8dc56394:11' => 'Apple QuickTime, Quality 332 (32%)', + 'f9988c61ae580fcfc8bf929134b07c2e:11' => 'Apple QuickTime, Quality 333 (33%)', + '59faa8c6fb70d4cf42765a92c1c7afc1:11' => 'Apple QuickTime, Quality 334 (33%)', + '277982593a55786fe424c80a17224cd7:11' => 'Apple QuickTime, Quality 335 (33%)', + 'c2a8a67d050b22a0673ee9ad6685a540:11' => 'Apple QuickTime, Quality 336 (33%)', + '040e09f495355470a44c580bca654693:11' => 'Apple QuickTime, Quality 337 (33%)', + '93173762094b6b506aa495e022ced65f:11' => 'Apple QuickTime, Quality 338 (33%)', + '281c39340554f672ff62c65e0bf1036b:11' => 'Apple QuickTime, Quality 339 (33%)', + '82d40afcb23ac10dba01bbab101da176:11' => 'Apple QuickTime, Quality 340 (33%)', + 'f56a4679494e5af4692381caa63b9062:11' => 'Apple QuickTime, Quality 341 (33%)', + '78787c9f0aae4ab8d15ab47eaea5035c:11' => 'Apple QuickTime, Quality 342 (33%)', + 'a1664b510ce4c6aa3588cdbc327a6f57:11' => 'Apple QuickTime, Quality 343 (33%)', + 'f6150beda200179d9744527637e52baa:11' => 'Apple QuickTime, Quality 344 (34%)', + '620244f053fef313466fbcb232077aca:11' => 'Apple QuickTime, Quality 345 (34%)', + '91c1b36d4411306ba3afaea0658f1ad8:11' => 'Apple QuickTime, Quality 346 (34%)', + '0381b4e34e700adecd618afdcfb5513e:11' => 'Apple QuickTime, Quality 347 (34%)', + '4d8f909ee8cb53e0386eb09c1591099b:11' => 'Apple QuickTime, Quality 348 (34%)', + 'f7425d5d0a0207e6dfaa0ee7c35d4ec6:11' => 'Apple QuickTime, Quality 349 (34%)', + '8d0663f8149a308365e18bdeb8c867e8:11' => 'Apple QuickTime, Quality 350 (34%)', + '4fa58542b5953534072b6dc1085deadf:11' => 'Apple QuickTime, Quality 351 (34%)', + '2358594d2a85b48dc0bd03e024dec9bd:11' => 'Apple QuickTime, Quality 352 (34%)', + 'b9594c8100236f288cdc01e6488cbc41:11' => 'Apple QuickTime, Quality 353 (34%)', + '3542444d51fa859ed5af78a1f5fc4f36:11' => 'Apple QuickTime, Quality 354 (35%)', + '7c95c94440f652232530fe4c411be1a2:11' => 'Apple QuickTime, Quality 355 (35%)', + '8361a9dbb5d93ad098a0ce2091b0bdf5:11' => 'Apple QuickTime, Quality 356 (35%)', + '44c8e4d0d7678034cb206609652ffeef:11' => 'Apple QuickTime, Quality 357 (35%)', + '4694896b11fb898106e30fd4ed50cded:11' => 'Apple QuickTime, Quality 358 (35%)', + '9ddc6134fe65ea64048fdfd27c82bed7:11' => 'Apple QuickTime, Quality 359 (35%)', + 'c60f75f7e09f0454db9cc48392a7eeed:11' => 'Apple QuickTime, Quality 360 (35%)', + '151731e5cd38be847f4dad794c023a69:11' => 'Apple QuickTime, Quality 361 (35%)', + '0468ecbf6fc1303467adfdcab8edfe6d:11' => 'Apple QuickTime, Quality 362 (35%)', + 'debd5adf671e3b907c10155cc910dcc1:11' => 'Apple QuickTime, Quality 363 (35%)', + '6385ee79b090ea430190dbe1ee93ddca:11' => 'Apple QuickTime, Quality 364 (36%)', + '67ed20f2fe283549dae4ba40860c3777:11' => 'Apple QuickTime, Quality 365 (36%)', + 'e168523157ee45551ba30378d597dfd6:11' => 'Apple QuickTime, Quality 366 (36%)', + 'd0cbe6c7372724a802d0183c6de66f8b:11' => 'Apple QuickTime, Quality 367 (36%)', + '38a1f9d86241eb3b96d5d42bc6587598:11' => 'Apple QuickTime, Quality 368 (36%)', + '4dc4b433113acbde9d77a4cbad69bb14:11' => 'Apple QuickTime, Quality 369 (36%)', + '186948d91ea43a64f874ebb9dee44564:11' => 'Apple QuickTime, Quality 370 (36%)', + '786aa4e46172ac65e10b230f3dcaadb2:11' => 'Apple QuickTime, Quality 371 (36%)', + 'ac76c6ebb64c843736fc765a03674d94:11' => 'Apple QuickTime, Quality 372 (36%)', + '38a60cdb8033a9f90027895eab0c40ba:11' => 'Apple QuickTime, Quality 373 (36%)', + 'ce48f7fb2ba9edee46c3f4839b40ef60:11' => 'Apple QuickTime, Quality 374 (37%)', + '7b3058792db9876a86c65ec44c0261b3:11' => 'Apple QuickTime, Quality 375 (37%)', + '5e983407295808e244f6bdece469c8be:11' => 'Apple QuickTime, Quality 376 (37%)', + '68ff8bfc0e15c93586ef6b4cf347469c:11' => 'Apple QuickTime, Quality 377 (37%)', + '9d4a8c44917390e56bca2352a8a4b1be:11' => 'Apple QuickTime, Quality 378 (37%)', + '36e7560256c5ffd285a1ca0f6d4bf97d:11' => 'Apple QuickTime, Quality 379 (37%)', + 'a88bad671d80cf6a70bd6e37be9c95c9:11' => 'Apple QuickTime, Quality 380 (37%)', + '23ab27876006666358e95d9c1104bcd0:11' => 'Apple QuickTime, Quality 381 (37%)', + 'b87750acf49940bf1f01f6a134a600b1:11' => 'Apple QuickTime, Quality 382 (37%)', + '731fa7404c090db157030e40804604b6:11' => 'Apple QuickTime, Quality 383 (37%)', + '442c2664c07af1ec15d86581f43aab0b:11' => 'Apple QuickTime, Quality 384 (38%)', + '145c52a48a9b2e954e785c3f8df5c27e:11' => 'Apple QuickTime, Quality 385 (38%)', + '55d37ee1e3c8d12a70e67206fa1c9b0c:11' => 'Apple QuickTime, Quality 386 (38%)', + '60880ff1f7bfe6a85cd80c2d4582395b:11' => 'Apple QuickTime, Quality 387 (38%)', + '85fc5daf51e6cbb04352016c817e5714:11' => 'Apple QuickTime, Quality 388 (38%)', + 'd7c835210eec5a8bedb3a18d32cbe066:11' => 'Apple QuickTime, Quality 389 (38%)', + '315e7fee22864b37b1b7670957f259fe:11' => 'Apple QuickTime, Quality 390 (38%)', + '46dd3917c1473ed0f8fc3f1e6f08416d:11' => 'Apple QuickTime, Quality 391 (38%)', + '1e93645e6163af46937c35a18b55c601:11' => 'Apple QuickTime, Quality 392 (38%)', + 'ba1a32697c0ae4e76a78f4b5624a8ce0:11' => 'Apple QuickTime, Quality 393 (38%)', + '68a0d6250be9df2c05556ff59988c499:11' => 'Apple QuickTime, Quality 394 (38%)', + '13b1310840627eddaf435e9feffebebe:11' => 'Apple QuickTime, Quality 395 (39%)', + '2e420a34dcf01dab91fd8509d4dbaab5:11' => 'Apple QuickTime, Quality 396 (39%)', + 'eef3afec34329517513541a8509b7aab:11' => 'Apple QuickTime, Quality 397 (39%)', + '80bdd75a2fc87b5288bc77763481df83:11' => 'Apple QuickTime, Quality 398 (39%)', + 'e230e3ac7c740f3e8fe6bc74fff72c10:11' => 'Apple QuickTime, Quality 399 (39%)', + '68b07a219cda4b9fc9a8507b788d8230:11' => 'Apple QuickTime, Quality 400 (39%)', + 'f1c23475d19d9e950dbc4086902365a3:11' => 'Apple QuickTime, Quality 401 (39%)', + 'dc2af4340202aa481491b86539888720:11' => 'Apple QuickTime, Quality 402 (39%)', + '495aeee0f43a596938c98c5364feb2ee:11' => 'Apple QuickTime, Quality 403 (39%)', + '8319dfe3caedea6988e5024b0196d317:11' => 'Apple QuickTime, Quality 404 (39%)', + 'd62cd17e8e04ebd568a8f5abc38cad4a:11' => 'Apple QuickTime, Quality 405 (40%)', + '047e1711c44262f352034452d0b0d07b:11' => 'Apple QuickTime, Quality 406 (40%)', + '552bf986ae119444955ded5f485d5dc4:11' => 'Apple QuickTime, Quality 407 (40%)', + 'd39329b38fdcabe9e1ae5f1b205c825a:11' => 'Apple QuickTime, Quality 408 (40%)', + 'bf2904a3e3870a2b4d060e0863530d92:11' => 'Apple QuickTime, Quality 409 (40%)', + '1acceb7ae4f9edbb835006d97ca30094:11' => 'Apple QuickTime, Quality 410 (40%)', + '5de50c687a6e885634bf16adfd75e6bc:11' => 'Apple QuickTime, Quality 411 (40%)', + 'd92c5bba7cfd1bfbb8c662c1a27ca413:11' => 'Apple QuickTime, Quality 412 (40%)', + 'cea44b5645cd1dbf469c8ae5600e4ff5:11' => 'Apple QuickTime, Quality 413 (40%)', + '5caf3989a757842c716220e4e426bde2:11' => 'Apple QuickTime, Quality 414 (40%)', + 'fd2f7a6518c12848a9ecdb1c3beb1fa8:11' => 'Apple QuickTime, Quality 415 (41%)', + 'de0547c872fed9c9c75c8fec2fe010e6:11' => 'Apple QuickTime, Quality 416 (41%)', + '6cf4dfbe3df89d9728e0f34b7b145223:11' => 'Apple QuickTime, Quality 417 (41%)', + 'd94d79b70686d3e2568d61d07e5819eb:11' => 'Apple QuickTime, Quality 418 (41%)', + '9ec82f50503769a9bb17e876594833b6:11' => 'Apple QuickTime, Quality 419 (41%)', + '4a39b0ae55f0eaa5672f00015cae2d40:11' => 'Apple QuickTime, Quality 420 (41%)', + '42bae7ef4a41562b2e98d74248f4f22e:11' => 'Apple QuickTime, Quality 421 (41%)', + '8c0ea132cfacf212c518ad297229be34:11' => 'Apple QuickTime, Quality 422 (41%)', + 'c1af7f1a3716bef087124306b068605c:11' => 'Apple QuickTime, Quality 423 (41%)', + 'd3ea3c519f92dd870fed03f63cabf05e:11' => 'Apple QuickTime, Quality 424 (41%)', + '0442196d850319833f27df632e92f064:11' => 'Apple QuickTime, Quality 425 (42%)', + '8c372e99fa96d2598e431f8137e47da6:11' => 'Apple QuickTime, Quality 426 (42%)', + '9bade640c3fcb807ed1322479f9e7f1c:11' => 'Apple QuickTime, Quality 427 (42%)', + '8015ad9fa22d6565ca61ce9979f3663f:11' => 'Apple QuickTime, Quality 428 (42%)', + '09a13f94022839a24065b82d5f4ffdbd:11' => 'Apple QuickTime, Quality 429 (42%)', + '0e570bc627acaee0962472a1a646816b:11' => 'Apple QuickTime, Quality 430 (42%)', + 'be7e7114f08e1775ca9676d2feeeccca:11' => 'Apple QuickTime, Quality 431 (42%)', + '44be972c54cd64be7524a133a7395401:11' => 'Apple QuickTime, Quality 432 (42%)', + 'b2f7b5e3007387aa22df74e82e916195:11' => 'Apple QuickTime, Quality 433 (42%)', + '5c13c8db3a0b590f4fa3ec462b8890c3:11' => 'Apple QuickTime, Quality 434 (42%)', + '2733a3cb2e0a2313b74d686437fa3ae2:11' => 'Apple QuickTime, Quality 435 (42%)', + '66309175abaa59d6246237a77ce9eb76:11' => 'Apple QuickTime, Quality 436 (43%)', + '6a2eb9f07f3c96365a06d91da171e673:11' => 'Apple QuickTime, Quality 437 (43%)', + 'e395118c42b6492dd4d9d30754f0a697:11' => 'Apple QuickTime, Quality 438 (43%)', + '7a75abc5c5ec8cc0fa43f239ab048c08:11' => 'Apple QuickTime, Quality 439 (43%)', + 'c558f3407dc549c902efad68c54920de:11' => 'Apple QuickTime, Quality 440 (43%)', + '49cd1849b501868260d8a3b1e96d8625:11' => 'Apple QuickTime, Quality 441 (43%)', + 'f7d70cfab7ff888c97078d277fa01307:11' => 'Apple QuickTime, Quality 442 (43%)', + 'f06ddea698cebe653bdd0c208c3d8c95:11' => 'Apple QuickTime, Quality 443 (43%)', + '73478bb7714d1d2342bbf22c5fdc04d6:11' => 'Apple QuickTime, Quality 444 (43%)', + '24406aee81b89ea50881ae71f878d0ec:11' => 'Apple QuickTime, Quality 445 (43%)', + 'e3e88302627b6743725cace74ddb17f9:11' => 'Apple QuickTime, Quality 446 (44%)', + '3fb9c046dff30dcb4128df984532d6ba:11' => 'Apple QuickTime, Quality 447 (44%)', + 'c9a4b04bc8e580608014b6f3111322d7:11' => 'Apple QuickTime, Quality 448 (44%)', + '7749ec06b1f1b1be30aa58dbef838d49:11' => 'Apple QuickTime, Quality 449 (44%)', + 'c0a54e87a2ef1c163311bcc1abf85214:11' => 'Apple QuickTime, Quality 450 (44%)', + 'f20a253d2513f4d8f2cfeea980852820:11' => 'Apple QuickTime, Quality 451 (44%)', + '6538fc6f5f1744b40c0b8b5bc7179983:11' => 'Apple QuickTime, Quality 452 (44%)', + '68a4a67af696f82bbbb7db15a16c0c46:11' => 'Apple QuickTime, Quality 453 (44%)', + 'e477932560b308940ac7439eed9f63da:11' => 'Apple QuickTime, Quality 454 (44%)', + 'fd732b0493e7ff16da4bde7faa88e22d:11' => 'Apple QuickTime, Quality 455 (44%)', + '61884dc8b93e63c07bb487a6e29d6fb7:11' => 'Apple QuickTime, Quality 456 (45%)', + '67515a725833d40535a54b4ef9551e05:11' => 'Apple QuickTime, Quality 457 (45%)', + 'cc6bb734b742b0631ab6562a329e1603:11' => 'Apple QuickTime, Quality 458 (45%)', + '1801686a97836f690ce3d5523ffcfa9a:11' => 'Apple QuickTime, Quality 459 (45%)', + 'f73f690cacd5d4e247f59964ad0f43b9:11' => 'Apple QuickTime, Quality 460 (45%)', + '853946ede6a624136546ec5b68ecdc49:11' => 'Apple QuickTime, Quality 461 (45%)', + 'f05c48d79edbefdb4d260dc23cf258e6:11' => 'Apple QuickTime, Quality 462 (45%)', + '8dc8361e94137f5466c8dd1f9aa06781:11' => 'Apple QuickTime, Quality 463 (45%)', + '6c121faf4784a5a93fbf7fff4470dea4:11' => 'Apple QuickTime, Quality 464-465 (45%)', + 'd0eaa368737f17f6037757d393a22599:11' => 'Apple QuickTime, Quality 466-467 (46%)', + '9c6f5faa1009cafe8bc3060fe18d4b60:11' => 'Apple QuickTime, Quality 468 (46%)', + 'ac47d493602dddace7844a9bc962e5ed:11' => 'Apple QuickTime, Quality 469 (46%)', + '24784b5651e1790242c01de522a6e05b:11' => 'Apple QuickTime, Quality 470 (46%)', + 'adbb56f1f0e0392392f9c7a38351a9ec:11' => 'Apple QuickTime, Quality 471 (46%)', + '6e0952a44c37bc2d98dbede4ec429c99:11' => 'Apple QuickTime, Quality 472 (46%)', + '20c7942ddec30475a182cb281f12bc03:11' => 'Apple QuickTime, Quality 473 (46%)', + '4080277d75b20871d00ebc01ffbdb848:11' => 'Apple QuickTime, Quality 474 (46%)', + '4f15f7e4c56e7a75c0fe5454ab7e8f72:11' => 'Apple QuickTime, Quality 475 (46%)', + 'ff82adb92189413246aee9a992eb2013:11' => 'Apple QuickTime, Quality 476 (46%)', + '16df79eb7c5f062aeebde385fbce1553:11' => 'Apple QuickTime, Quality 477 (47%)', + 'df02b0ea9dab7d291950b6cfc65c4bb1:11' => 'Apple QuickTime, Quality 478 (47%)', + '4ca8ec2a0c651e0508aab3b153cfee23:11' => 'Apple QuickTime, Quality 479 (47%)', + 'bbbae155e558e9d37686ec34bd065a53:11' => 'Apple QuickTime, Quality 480 (47%)', + '9bf86a5ec6e5382f214e07364a62b1b3:11' => 'Apple QuickTime, Quality 481 (47%)', + '41d873034f29b298d899b48cd321c93f:11' => 'Apple QuickTime, Quality 482 (47%)', + '52092035b4e3fd45de3298c4d641385a:11' => 'Apple QuickTime, Quality 483 (47%)', + '70e5babe9507bae6725e401a36903070:11' => 'Apple QuickTime, Quality 484 (47%)', + '3cfa966dde2536c83c921aa250b978b3:11' => 'Apple QuickTime, Quality 485 (47%)', + 'b0144b1d2671d145d29812ebcebd863d:11' => 'Apple QuickTime, Quality 486 (47%)', + '2a6a136faaf1f13c2b80dcb4786d90b2:11' => 'Apple QuickTime, Quality 487 (48%)', + '3a6eac793d818f378e7b24826c9115cc:11' => 'Apple QuickTime, Quality 488 (48%)', + '4a78c6570fc84378e3334bfcd8a5680f:11' => 'Apple QuickTime, Quality 489 (48%)', + '0709c0afc0eae932a50903e56ec95ad2:11' => 'Apple QuickTime, Quality 490 (48%)', + 'b013b5c9b7bafc9dcad9a1e87fc629ff:11' => 'Apple QuickTime, Quality 491 (48%)', + '7cb380e582317b8387037450cc68db5e:11' => 'Apple QuickTime, Quality 492 (48%)', + 'f94618c1a011209cb3b060887c7e244e:11' => 'Apple QuickTime, Quality 493 (48%)', + '649a90949cab8f45d3ecef78068165d1:11' => 'Apple QuickTime, Quality 494 (48%)', + '70e105a22b036f7c1ce0b5d02fa1c34e:11' => 'Apple QuickTime, Quality 495 (48%)', + '18c10ea6fe5918e09daf1a3a7a74e678:11' => 'Apple QuickTime, Quality 496 (48%)', + '23822cafcc61ce2a52691f1fc963ff18:11' => 'Apple QuickTime, Quality 497 (49%)', + '5bb2cf3e6721c2dd8eb3341f9bff4159:11' => 'Apple QuickTime, Quality 498 (49%)', + 'e55b5345d9668d1b11b657537f707072:11' => 'Apple QuickTime, Quality 499 (49%)', + '4cbebcb06d1003e29429e9d5c9445919:11' => 'Apple QuickTime, Quality 500 (49%)', + '916b16f020b2b21e4c8114da8c05d584:11' => 'Apple QuickTime, Quality 501 (49%)', + '98abef3366c7f451e44f5c2799e2be6d:11' => 'Apple QuickTime, Quality 502 (49%)', + 'f8e99ed03828752f16c51bb8c9887e9e:11' => 'Apple QuickTime, Quality 503 (49%)', + 'eef05c558c1aba5cf2891fb13ee07167:11' => 'Apple QuickTime, Quality 504 (49%)', + 'cb5fc7927d88ac99f556b2dd7985eaf9:11' => 'Apple QuickTime, Quality 505 (49%)', + 'f0e3f635bbcf96654812e8c78b227701:11' => 'Apple QuickTime, Quality 506 (49%)', + '234c9cf6d7fe671b52c3ec5a20046ec8:11' => 'Apple QuickTime, Quality 507 (50%)', + '9dfcc9ae3baee4bb4ad63abf2f740275:11' => 'Apple QuickTime, Quality 508 (50%)', + 'b1f1b6519991ac7696b233dd9b9de6b5:11' => 'Apple QuickTime, Quality 509 (50%)', + 'dba2f5203ffecada66a8bf9b1272f1eb:11' => 'Apple QuickTime, Quality 510 (50%)', + '367b3d63cddc0cd27e58030c2b8f1aaa:11' => 'Apple QuickTime, Quality 511 (50%)', + 'c28ab3fd6480c92028327957228c0a11:11' => 'Apple QuickTime, Quality 512 (50%)', + '6bc9ebaf9f3ed62ec8818076f6f81c7f:11' => 'Apple QuickTime, Quality 513 (50%)', + '3bbfcd817441d2267a49bf76b48c5f47:11' => 'Apple QuickTime, Quality 514 (50%)', + '27e6ed2cecfebe31eb3d66128c926562:11' => 'Apple QuickTime, Quality 515 (50%)', + '3cf112c5843f98410599ea2a197e5cf6:11' => 'Apple QuickTime, Quality 516 (50%)', + '2821aae8108df4bd98e5eaa451a351d2:11' => 'Apple QuickTime, Quality 517 (50%)', + '798f48b6dbe3f1cd7b40b03fae8d2611:11' => 'Apple QuickTime, Quality 518 (51%)', + '67a7c8896d03a030b56130e1f9c5caad:11' => 'Apple QuickTime, Quality 519 (51%)', + '4bf1b53c292dec3f7cf3c020a3a9d911:11' => 'Apple QuickTime, Quality 520 (51%)', + '2bfe0ace876b80be6f601a1703187d94:11' => 'Apple QuickTime, Quality 521 (51%)', + '7590bc1a40090163a101bfd28daa3fc2:11' => 'Apple QuickTime, Quality 522 (51%)', + '118c7b118b1df404c90cfb1d10cf2a77:11' => 'Apple QuickTime, Quality 523 (51%)', + '86cfac24ca9f4ab254f882ad399ea758:11' => 'Apple QuickTime, Quality 524 (51%)', + '0268c3e9e3e1c3e6eb25fe0d31940c7f:11' => 'Apple QuickTime, Quality 525 (51%)', + '9beb1b7c55129a34c850c359d7263457:11' => 'Apple QuickTime, Quality 526 (51%)', + '0cec2c8f96c092bd6e7cf0f7ea294c99:11' => 'Apple QuickTime, Quality 527 (51%)', + 'd8c5179c2419775f43e9a7899bacddd7:11' => 'Apple QuickTime, Quality 528 (52%)', + '9ce50f6e0b00d2e601f2fcc151abc4d8:11' => 'Apple QuickTime, Quality 529 (52%)', + '80db52b1671d32d8bd3126bf1d7db8ec:11' => 'Apple QuickTime, Quality 530 (52%)', + 'f3ef06f90579eaf1008e07b94e818a40:11' => 'Apple QuickTime, Quality 531 (52%)', + '3c85026793f58eb45141847a27854fe2:11' => 'Apple QuickTime, Quality 532-533 (52%)', + 'e3042cbd43d2067ae92e1a8ce3f2c5a1:11' => 'Apple QuickTime, Quality 534 (52%)', + 'dd71fdab3d46341a9b6ca0b6c6929d23:11' => 'Apple QuickTime, Quality 535 (52%)', + 'a451c79ccddcd543a80e1ce0449dcb0d:11' => 'Apple QuickTime, Quality 536 (52%)', + '2da67fe5f0bb3c8b10403295895fb154:11' => 'Apple QuickTime, Quality 537 (52%)', + '4f72f3cdc82d433e7f749be8036d4ce0:11' => 'Apple QuickTime, Quality 538 (53%)', + 'da29cc9a4d5fd7e0dc36a2dd0c70e84f:11' => 'Apple QuickTime, Quality 539 (53%)', + '63f61a0d3c4f1ace8ebe5b6ae23e3f25:11' => 'Apple QuickTime, Quality 540 (53%)', + 'fde14219617069bbf6b26dcb42036de7:11' => 'Apple QuickTime, Quality 541 (53%)', + 'c84313bc621c6d05999510fa57c56d05:11' => 'Apple QuickTime, Quality 542 (53%)', + '92658d4c879d6e48bfda1a6e9f49ef8d:11' => 'Apple QuickTime, Quality 543 (53%)', + '3deffd01a1c03929873dddd86a5339f1:11' => 'Apple QuickTime, Quality 544 (53%)', + 'e76c1b26bbd196efe2e793e27727704d:11' => 'Apple QuickTime, Quality 545 (53%)', + '9f3289994c790a10ecb2d93021677840:11' => 'Apple QuickTime, Quality 546 (53%)', + 'f7493b01895b7880c651841c73678d33:11' => 'Apple QuickTime, Quality 547 (53%)', + '3c9d094741c995c2c0ac9daf14c4e683:11' => 'Apple QuickTime, Quality 548 (54%)', + '8c0e1d4cd6138817963af6ca149cb5d5:11' => 'Apple QuickTime, Quality 549 (54%)', + 'd781cc6f686fc7c7b9b6eef90fab4d87:11' => 'Apple QuickTime, Quality 550 (54%)', + '7dd6377a907070b1ca7e05f770ca2aab:11' => 'Apple QuickTime, Quality 551-552 (54%)', + 'c2d5e2e93ec191015d8181c9e25387d8:11' => 'Apple QuickTime, Quality 553 (54%)', + '65d20361f4ba0725cf150c7ae2033776:11' => 'Apple QuickTime, Quality 554 (54%)', + '2d7eb8eb9df9f4831a843626f4fc7e19:11' => 'Apple QuickTime, Quality 555 (54%)', + 'ce0e14187fec73f57242becd633a89a3:11' => 'Apple QuickTime, Quality 556 (54%)', + '140cc5a99ef865e318a217ea069aa84d:11' => 'Apple QuickTime, Quality 557 (54%)', + '0db59bd18beb49f9beb901f3435e22a5:11' => 'Apple QuickTime, Quality 558 (54%)', + '78d19da8de8095644aa31fb409033fe7:11' => 'Apple QuickTime, Quality 559 (55%)', + '00687c4e4852ed1cd446c09a3764e505:11' => 'Apple QuickTime, Quality 560 (55%)', + 'f3795398903c82e1beababf95d3a8413:11' => 'Apple QuickTime, Quality 561 (55%)', + 'b749b90354443bf17da7a67a5ad53397:11' => 'Apple QuickTime, Quality 562 (55%)', + 'c722656df4bb0651821cd90880953a20:11' => 'Apple QuickTime, Quality 563 (55%)', + '4d17c873e65b9d398f27735b0020c777:11' => 'Apple QuickTime, Quality 564 (55%)', + '067a76c2e5386ae85f9187e3e2134621:11' => 'Apple QuickTime, Quality 565 (55%)', + '09b689f7d0c1d4bb0d96d06c02b8dcf8:11' => 'Apple QuickTime, Quality 566 (55%)', + 'ffc0192eb5a182370a641cffe9b1d71f:11' => 'Apple QuickTime, Quality 567 (55%)', + '3af2163438180050bbcf123d4f4587d3:11' => 'Apple QuickTime, Quality 568 (55%)', + '3bdb097a9791f3ce6d7bbc4d6a194aa4:11' => 'Apple QuickTime, Quality 569 (56%)', + '8c1fead15819016583650eff5a4f5bda:11' => 'Apple QuickTime, Quality 570 (56%)', + '967aeb5bc4d75a0d5c0998bbfb282982:11' => 'Apple QuickTime, Quality 571 (56%)', + 'cdd7bd689f14d5f7c3ea790f6f09ae64:11' => 'Apple QuickTime, Quality 572 (56%)', + '6a20041beb5b67d38525bb7507ffeb49:11' => 'Apple QuickTime, Quality 573 (56%)', + '2bf57fbe54370c4d917f259631af033e:11' => 'Apple QuickTime, Quality 574 (56%)', + 'd2e983c44eae2983f48f526992fbbfb4:11' => 'Apple QuickTime, Quality 575 (56%)', + 'c5bb48d86e26ac496bb4b4bc888cc06a:11' => 'Apple QuickTime, Quality 576 (56%)', + 'a595c40b5dbd45557c3c8d23ebee5e24:11' => 'Apple QuickTime, Quality 577 (56%)', + 'f2d84e1114ef85682818b96720d439b5:11' => 'Apple QuickTime, Quality 578 (56%)', + '332d9c49c5b32ae2addbb06a1e32fd49:11' => 'Apple QuickTime, Quality 579 (57%)', + '19c5c7c0270bd36c49f695475a62c293:11' => 'Apple QuickTime, Quality 580 (57%)', + '6516e60b3995e21b6750ebca1ddcfee5:11' => 'Apple QuickTime, Quality 581 (57%)', + '8daf4a87b28106876529a549cf1040b8:11' => 'Apple QuickTime, Quality 582 (57%)', + '6b37d6acc52259bf972a41e84dea7754:11' => 'Apple QuickTime, Quality 583 (57%)', + '588666e111892f10ca3f17bc362d9276:11' => 'Apple QuickTime, Quality 584 (57%)', + '9ec2859c370f557783903608748e7fb1:11' => 'Apple QuickTime, Quality 585 (57%)', + '18e3ac85da74fe92ab5da3d5f7614e09:11' => 'Apple QuickTime, Quality 586 (57%)', + 'da4c88f145393972fbe9d3f40838cab9:11' => 'Apple QuickTime, Quality 587 (57%)', + '8ae7d2b569c437904a20a10bbd21fe89:11' => 'Apple QuickTime, Quality 588 (57%)', + 'fc6dfb9669566b249cb03228aeb020c3:11' => 'Apple QuickTime, Quality 589 (58%)', + '198f3a32e4036d7c37fbc0c343d883af:11' => 'Apple QuickTime, Quality 590 (58%)', + '876ec039f82e49b925b232843b4703d4:11' => 'Apple QuickTime, Quality 591 (58%)', + '673b05962b8255cbc9bdbbc48965b4b7:11' => 'Apple QuickTime, Quality 592 (58%)', + '02e9a58edf45d75be000dee144316c66:11' => 'Apple QuickTime, Quality 593 (58%)', + '2abebf7a61009c5c1aa9516539b9084e:11' => 'Apple QuickTime, Quality 594 (58%)', + 'fe44d8625d6242f4b5deb82be8ccaacf:11' => 'Apple QuickTime, Quality 595 (58%)', + '66529cc8ef9694e6a37e8787d0f160fd:11' => 'Apple QuickTime, Quality 596 (58%)', + '2129ee2bff47bfa8a8bb79ea9fb67b92:11' => 'Apple QuickTime, Quality 597 (58%)', + 'ccb55ec1549a51212859495e104c626b:11' => 'Apple QuickTime, Quality 598 (58%)', + '05e53fb216ba4a1734eefaccd249d8e2:11' => 'Apple QuickTime, Quality 599 (58%)', + 'f4923d7b7dedd365646169e720eee427:11' => 'Apple QuickTime, Quality 600 (59%)', + 'c0f2265630bdf5f29e8c95df25c89edb:11' => 'Apple QuickTime, Quality 601 (59%)', + '0fdd23e8274090da3c925a3db7303adf:11' => 'Apple QuickTime, Quality 602 (59%)', + 'a5bcbd80472fdf697db770ac78d6a4e3:11' => 'Apple QuickTime, Quality 603 (59%)', + 'b157c4aabba2816f391c8f76ca3d4072:11' => 'Apple QuickTime, Quality 604 (59%)', + 'b8ef26dabc2d81a8ba13b1f49ea711d3:11' => 'Apple QuickTime, Quality 605 (59%)', + '8a983844f9b0aec26fc8ac75a258e3ac:11' => 'Apple QuickTime, Quality 606 (59%)', + '44ced96f11e3a410201beed353a864cf:11' => 'Apple QuickTime, Quality 607 (59%)', + '4a14a3e37c89e5a7570f672b1970ca55:11' => 'Apple QuickTime, Quality 608 (59%)', + 'e5ec78e112e3ba6463de24b3518347eb:11' => 'Apple QuickTime, Quality 609 (59%)', + '29b7cdc7a570b950457d20541c22c4ce:11' => 'Apple QuickTime, Quality 610 (60%)', + '470c0c761e2bb5e314a7112f3d64b277:11' => 'Apple QuickTime, Quality 611 (60%)', + 'ced483058f2abf19df0f7935dafd217a:11' => 'Apple QuickTime, Quality 612 (60%)', + '8a202c89c57e77f50e1df27a3be7d5b7:11' => 'Apple QuickTime, Quality 613 (60%)', + '7eb9fe0338a7b802860a60e0088418fd:11' => 'Apple QuickTime, Quality 614 (60%)', + 'ac25112c596d62f95518af109457975c:11' => 'Apple QuickTime, Quality 615 (60%)', + 'f57e9a5f1d8dea7fd83a1b5840243686:11' => 'Apple QuickTime, Quality 616-617 (60%)', + '0eef5c6ff8ba65ff799081a9c96a2297:11' => 'Apple QuickTime, Quality 618-619 (60%)', + '05f98e12bfa14ba6347fb43f2241ba43:11' => 'Apple QuickTime, Quality 620 (61%)', + '91885755c780ebe16b1278a0359eda83:11' => 'Apple QuickTime, Quality 621 (61%)', + 'b44cb1ca15fc9e3a27420df2ddae5879:11' => 'Apple QuickTime, Quality 622 (61%)', + 'ae6c2112dd560530b7bacc8bfa9fb7f6:11' => 'Apple QuickTime, Quality 623 (61%)', + 'd3b05f3cd78e0ac3ab37a02152e22831:11' => 'Apple QuickTime, Quality 624 (61%)', + 'b0c2c3f76d848ee2e8f47a9a90131a21:11' => 'Apple QuickTime, Quality 625 (61%)', + '752fbc15f77a8c2149f5ae6bf49204b8:11' => 'Apple QuickTime, Quality 626 (61%)', + '9c34d3dedfe47d95edabdcbc5568a2a8:11' => 'Apple QuickTime, Quality 627 (61%)', + '252ba8a31aeb601e23b3a70f9af7abc1:11' => 'Apple QuickTime, Quality 628 (61%)', + '359038cd7c45242e96e176e91210922d:11' => 'Apple QuickTime, Quality 629 (61%)', + '8ab83dc2e7ca8b9db1f3b0ab500f3aca:11' => 'Apple QuickTime, Quality 630 (62%)', + '21c9f9a0ff71d4528ef7a19d2cfd0b6c:11' => 'Apple QuickTime, Quality 631 (62%)', + 'a62fb887c209e0fab99fcb7ac81137a2:11' => 'Apple QuickTime, Quality 632 (62%)', + 'e454ca92aca849d59b873d9be817baea:11' => 'Apple QuickTime, Quality 633 (62%)', + '9ad6008e7b4f8478043bfa54e1d9e48e:11' => 'Apple QuickTime, Quality 634 (62%)', + '45b20f0b0d7d88d8330354212af2e087:11' => 'Apple QuickTime, Quality 635 (62%)', + 'b0a00ffee2e55457cd999bba2d07f63e:11' => 'Apple QuickTime, Quality 636 (62%)', + '5dfcb96d9a1186f662c6adb38bb9520a:11' => 'Apple QuickTime, Quality 637 (62%)', + 'd973f38c501796adff40c4f70cbd8885:11' => 'Apple QuickTime, Quality 638 (62%)', + 'dcfe5898ec101a8b2bf98330445dd1bf:11' => 'Apple QuickTime, Quality 639 (62%)', + '2b81eecf0fecd33679adac27e79ef3f4:11' => 'Apple QuickTime, Quality 640-641 (63%)', + '5aad44c55bf4f6dc538eaca006cafac2:11' => 'Apple QuickTime, Quality 642 (63%)', + '7079c2d71ff33e7c4e8efdece23c307b:11' => 'Apple QuickTime, Quality 643 (63%)', + 'a5f724e9d7148f1a84ee597f45c33141:11' => 'Apple QuickTime, Quality 644 (63%)', + '3ae705fae9e895eda345d482525215e3:11' => 'Apple QuickTime, Quality 645 (63%)', + '7cbd635e5fee8bbd290b0d383b03da5a:11' => 'Apple QuickTime, Quality 646 (63%)', + '0f125e2c5ee6b123cf67b586ea23d422:11' => 'Apple QuickTime, Quality 647 (63%)', + '87eac5c1375cca9aa16eba0704616a7b:11' => 'Apple QuickTime, Quality 648-649 (63%)', + 'aa83fd556c569ddcd81e0cc1ba866373:11' => 'Apple QuickTime, Quality 650 (63%)', + 'dab4fa97da49aa37889185c5b43917c1:11' => 'Apple QuickTime, Quality 651 (64%)', + '51ad55cb254f36748123ca83f43556f4:11' => 'Apple QuickTime, Quality 652 (64%)', + '86e707c017682fe08213216d064b1b51:11' => 'Apple QuickTime, Quality 653 (64%)', + '3730182602996b4a1d540eb3fd970072:11' => 'Apple QuickTime, Quality 654 (64%)', + '1bf7a5d7477ad75b9c7b281de622d53b:11' => 'Apple QuickTime, Quality 655 (64%)', + '82b4bc7c4a832b620e810311a33c9771:11' => 'Apple QuickTime, Quality 656 (64%)', + '6502d634e5bf3f849e9d382886fc32fe:11' => 'Apple QuickTime, Quality 657 (64%)', + 'a10e87fa030f8177a4f59f8d16a20afd:11' => 'Apple QuickTime, Quality 658 (64%)', + '338a78a7658ff3c1de33d88aa0ab7c74:11' => 'Apple QuickTime, Quality 659 (64%)', + '4c30c4399ef4bb2920601d940ed404eb:11' => 'Apple QuickTime, Quality 660-661 (64-65%)', + 'ab7cfb73667875854893982a8cfcfab9:11' => 'Apple QuickTime, Quality 662 (65%)', + '7bff346b97abf46ca005af4e74b560fa:11' => 'Apple QuickTime, Quality 663 (65%)', + '2eae93ed601a50284ee31c20651584cb:11' => 'Apple QuickTime, Quality 664 (65%)', + 'e566eaef7eacd6c7161feebf4cec79e8:11' => 'Apple QuickTime, Quality 665 (65%)', + 'a9f461d3bca42dfab57c834fa5f34419:11' => 'Apple QuickTime, Quality 666 (65%)', + '2b394105d4dd418e79b32e66496679d4:11' => 'Apple QuickTime, Quality 667 (65%)', + 'c65677d79e37baf57767e10d7b7f1ce8:11' => 'Apple QuickTime, Quality 668 (65%)', + '688c7dca6b12c22a21e0caf1a0336c80:11' => 'Apple QuickTime, Quality 669-671 (65-66%)', + '20d4b3c9e3c292c68181974704fe5048:11' => 'Apple QuickTime, Quality 672-673 (66%)', + '3bc3948025869f25b143aa517b2154ac:11' => 'Apple QuickTime, Quality 674 (66%)', + '8a2ae82991917070de49cc48d104446f:11' => 'Apple QuickTime, Quality 675 (66%)', + 'b5424d9dce37dd5c9e0e38bcc775f48e:11' => 'Apple QuickTime, Quality 676 (66%)', + '28f718af4edb0069a1fdab00f6ea978d:11' => 'Apple QuickTime, Quality 677 (66%)', + '910416483a49503202cbe3ecee33afc9:11' => 'Apple QuickTime, Quality 678 (66%)', + '153196d4f99569f9bbd3fe0e96d2909c:11' => 'Apple QuickTime, Quality 679 (66%)', + '919a38ebb9fc0bcba388643a9b3ef27c:11' => 'Apple QuickTime, Quality 680 (66%)', + 'ef511ac2c9d7153c16e3e1846325b727:11' => 'Apple QuickTime, Quality 681 (67%)', + 'e8b4ef8f94d89c59c855758a73ec451f:11' => 'Apple QuickTime, Quality 682 (67%)', + 'a09fe2e60a7ff12e1e5ca00afa9719ef:11' => 'Apple QuickTime, Quality 683 (67%)', + 'e56cf84423c16869a9a4fd6605b15ba4:11' => 'Apple QuickTime, Quality 684 (67%)', + 'f64d88456f97a65fe562eb69e619782a:11' => 'Apple QuickTime, Quality 685 (67%)', + '8cbf6cda8ae0249fb91c1ff5ab788a04:11' => 'Apple QuickTime, Quality 686 (67%)', + 'f0ebdd8d44ac1a80727041a087553847:11' => 'Apple QuickTime, Quality 687 (67%)', + 'c8f917220d6285cda6428a2cf6a9a1b3:11' => 'Apple QuickTime, Quality 688-689 (67%)', + '83ff61ebceff5f888b9615b250aa7b76:11' => 'Apple QuickTime, Quality 690 (67%)', + 'a2d1de53a0882047287a954f86bc783d:11' => 'Apple QuickTime, Quality 691 (67%)', + 'c78717ac2705274888912f349eeb2c8e:11' => 'Apple QuickTime, Quality 692 (68%)', + 'e032225ecdcf1d91d85626df59a2d0c6:11' => 'Apple QuickTime, Quality 693 (68%)', + 'ebf82c50697d66a6913727095299f192:11' => 'Apple QuickTime, Quality 694-695 (68%)', + 'bcc29022955cc7532b08640ab118259c:11' => 'Apple QuickTime, Quality 696 (68%)', + '6a87dd29703c2b3ef80f1b5d2cc8d26a:11' => 'Apple QuickTime, Quality 697 (68%)', + '91c9d96e11d96e10b328a5f18960247b:11' => 'Apple QuickTime, Quality 698 (68%)', + '6eeaaacec8edc933b68602b01d16204e:11' => 'Apple QuickTime, Quality 699 (68%)', + 'c638d9151dc650993b56f4effc0fe19c:11' => 'Apple QuickTime, Quality 700 (68%)', + 'c67d246229fcc0dd1b974f7df556d247:11' => 'Apple QuickTime, Quality 701 (68%)', + '7a52e3960831057d58e9b1ba03b87cf3:11' => 'Apple QuickTime, Quality 702 (69%)', + '907e599e3c462b498e936dfc35b20bb9:11' => 'Apple QuickTime, Quality 703-704 (69%)', + '65bf1ddc176fe4002a7a2ecaac60e58c:11' => 'Apple QuickTime, Quality 705 (69%)', + '40e59fdb430180502ceacf7b4034eff8:11' => 'Apple QuickTime, Quality 706 (69%)', + '4205aec34791d70be03b90ab1e54ef8c:11' => 'Apple QuickTime, Quality 707 (69%)', + '5c0a83e613d3bdd9ec7e86983f75b5be:11' => 'Apple QuickTime, Quality 708 (69%)', + 'b3ebdf0376c9c48cca51ea8b550f6c51:11' => 'Apple QuickTime, Quality 709 (69%)', + '72161116404ed3cb449674760d0e4776:11' => 'Apple QuickTime, Quality 710 (69%)', + '35b3697c265e35185e9463aac6ce9b2d:11' => 'Apple QuickTime, Quality 711-712 (69-70%)', + '85adcc8c52c25334a9e7ea9d79433f8d:11' => 'Apple QuickTime, Quality 713 (70%)', + '8f72f67948f264bdbd33107c33b1e0a0:11' => 'Apple QuickTime, Quality 714 (70%)', + 'c25ed5069735a3f9677ee494108a52bc:11' => 'Apple QuickTime, Quality 715 (70%)', + 'fd3167b1cdcfa1bdd37a4841d37b1624:11' => 'Apple QuickTime, Quality 716-717 (70%)', + 'a02b6b8286cf6d036961911e98bd8f89:11' => 'Apple QuickTime, Quality 718-719 (70%)', + 'd876e8934da14a985abda0ebe722bbee:11' => 'Apple QuickTime, Quality 720 (70%)', + '31852b7659883eade6e273ac61ef0262:11' => 'Apple QuickTime, Quality 721 (70%)', + 'de3aa6ed96eaf3ed3cd3ea70a2f75002:11' => 'Apple QuickTime, Quality 722 (71%)', + 'f178977e9e0133711f395943816d26aa:11' => 'Apple QuickTime, Quality 723 (71%)', + '18fce3103e312ce26252ec4af6cd1350:11' => 'Apple QuickTime, Quality 724 (71%)', + 'fdf6b04a2d8ac06d3fe64d1dceba4cd9:11' => 'Apple QuickTime, Quality 725 (71%)', + '6a37c2572bb47dff514aa4b343c104b5:11' => 'Apple QuickTime, Quality 726 (71%)', + 'fdbe851f6e559bc17ce3610b91e7fead:11' => 'Apple QuickTime, Quality 727 (71%)', + '9fee7fc42e670d6e30a5e9fcf696241d:11' => 'Apple QuickTime, Quality 728 (71%)', + '5aedf3816a813ed63b0521b0c384a677:11' => 'Apple QuickTime, Quality 729 (71%)', + 'c1278992838bdd62e71bf41c20126a5f:11' => 'Apple QuickTime, Quality 730 (71%)', + '791e3897f6ac92fb5e708b28dbd361b1:11' => 'Apple QuickTime, Quality 731 (71%)', + 'd5b2902ae3fcd87e1521da86585e7b3a:11' => 'Apple QuickTime, Quality 732-733 (71-72%)', + 'a99810db58e835fe4b213d707dc0b754:11' => 'Apple QuickTime, Quality 734 (72%)', + 'e8032085aa55f664f7f74201ac10bb99:11' => 'Apple QuickTime, Quality 735 (72%)', + '488e5d04f779b15c53f76e67cccdb2ed:11' => 'Apple QuickTime, Quality 736 (72%)', + '548a841c0a8c3b2beeb134c6c3b922fc:11' => 'Apple QuickTime, Quality 737-738 (72%)', + 'cfe4549eb2dd81684920aa32b598260c:11' => 'Apple QuickTime, Quality 739-740 (72%)', + 'b6ff9215f87e4b053aaee36381f59005:11' => 'Apple QuickTime, Quality 741 (72%)', + 'd1c8c1e1fc2bfb776d2ee1aace3fc6f9:11' => 'Apple QuickTime, Quality 742 (72%)', + '365693ebd558aebc31a79a1abff9709d:11' => 'Apple QuickTime, Quality 743-744 (73%)', + '0233ba670d2891e75da3ce5e7664cb67:11' => 'Apple QuickTime, Quality 745 (73%)', + 'a91f5f8e8d743e52169d965992c5021e:11' => 'Apple QuickTime, Quality 746 (73%)', + '1a0487e7e1a8f4ade97b2e8a4cab46ec:11' => 'Apple QuickTime, Quality 747 (73%)', + '5a2311c438c7183f6cd1f45c10e5783a:11' => 'Apple QuickTime, Quality 748 (73%)', + '2750f3df7a97d6007d6a17f5dd27790a:11' => 'Apple QuickTime, Quality 749 (73%)', + '74fddf6faaf251b28a00b4d0cd9e5621:11' => 'Apple QuickTime, Quality 750 (73%)', + 'ea65bdd87f78f7507fe6098473cbe0c9:11' => 'Apple QuickTime, Quality 751 (73%)', + '26d087368a13e3aca9ca13a54bcc648f:11' => 'Apple QuickTime, Quality 752-753 (73-74%)', + '5664c6ca557bc75526f59bea6aebde51:11' => 'Apple QuickTime, Quality 754 (74%)', + 'ff7a6007b6aab26f3c72c715ce487d72:11' => 'Apple QuickTime, Quality 755 (74%)', + '912804912a3914f0b470b29495810d8c:11' => 'Apple QuickTime, Quality 756-758 (74%)', + '6537be61d21f1b6ded3253fdd84f599b:11' => 'Apple QuickTime, Quality 759-760 (74%)', + '72d947637340246a35ff3ee969fd613f:11' => 'Apple QuickTime, Quality 761 (74%)', + '8c5c788bd53945222fc98f1a6155004c:11' => 'Apple QuickTime, Quality 762 (74%)', + '9fa9c3d1041911322544aef0298695ba:11' => 'Apple QuickTime, Quality 763 (75%)', + '33113dc71a90e8db06b43dadfe36b020:11' => 'Apple QuickTime, Quality 764 (75%)', + '55e0cf02a898abf8e224e2e9cf6e6ca5:11' => 'Apple QuickTime, Quality 765-766 (75%)', + '4f00127e7931d668a7b472c8a669925a:11' => 'Apple QuickTime, Quality 767-768 (75%)', + '32aa33dc6de46b7c5c5948b0ae06cb0e:11' => 'Apple QuickTime, Quality 769 (75%)', + 'a2b6067d9e5731be8029e17c00d7e043:11' => 'Apple QuickTime, Quality 770 (75%)', + 'cb6bc96131c4a5f762b5e5f79e7c4b66:11' => 'Apple QuickTime, Quality 771 (75%)', + 'e582a5f93f66cf34facfba5918a1d9e2:11' => 'Apple QuickTime, Quality 772 (75%)', + '54ab2e8fbd7c4ecac9eba5fb02a5ccd9:11' => 'Apple QuickTime, Quality 773 (75%)', + 'c71131cb485b59faf920d11982f7d454:11' => 'Apple QuickTime, Quality 774 (76%)', + 'c18239304e22bd19e3c8a21f9875ba39:11' => 'Apple QuickTime, Quality 775-776 (76%)', + '8278e4f14c7bf6efd2a847ef40f392e3:11' => 'Apple QuickTime, Quality 777 (76%)', + '6c0e396c705a59ec610a22f11466621b:11' => 'Apple QuickTime, Quality 778 (76%)', + 'a0c4d0114c04c89c879d9dc03463b347:11' => 'Apple QuickTime, Quality 779 (76%)', + 'c804929d3963c7427fa143e0d1e8c94e:11' => 'Apple QuickTime, Quality 780-781 (76%)', + 'e331a0dd2c53616c2881bb381fc4c1e2:11' => 'Apple QuickTime, Quality 782 (76%)', + '24ec8f0996e5e8d4dd7019d2b6063290:11' => 'Apple QuickTime, Quality 783 (76%)', + '36e9cb02d3ef245a3e15272c5071b0ee:11' => 'Apple QuickTime, Quality 784 (77%)', + 'b691578c1d6b16687fe2df12843d0642:11' => 'Apple QuickTime, Quality 785-786 (77%)', + 'ecbf939a145939d5aa48fc3c9e19cfe8:11' => 'Apple QuickTime, Quality 787 (77%)', + '9440b11ee5eb970a3ea6de9353099761:11' => 'Apple QuickTime, Quality 788 (77%)', + '282914c43bab6e5c62f3caaf549f1510:11' => 'Apple QuickTime, Quality 789-790 (77%)', + '040c8ed8b19485d8677b964b03bc9929:11' => 'Apple QuickTime, Quality 791-792 (77%)', + 'd9e503dc2dd4f6493be988ecb0f44f2c:11' => 'Apple QuickTime, Quality 793-795 (77-78%)', + '64d056c9e5e558d6c04d07d9d21aa7a3:11' => 'Apple QuickTime, Quality 796-798 (78%)', + '498f446de17202060a4752434df1ed7b:11' => 'Apple QuickTime, Quality 799 (78%)', + '5519e1c07692f51d0ee421ede78fb907:11' => 'Apple QuickTime, Quality 800 (78%)', + '511c5bddc18566a2544732291920caf3:11' => 'Apple QuickTime, Quality 801 (78%)', + '659f7466f80a8034f74a00ba07b8c3fb:11' => 'Apple QuickTime, Quality 802-804 (78-79%)', + 'fb64b35fe4021c34f16cf5bb1e59c0e8:11' => 'Apple QuickTime, Quality 805 (79%)', + '54fcb6649f5ba51c32c68970797e41ea:11' => 'Apple QuickTime, Quality 806-807 (79%)', + '80ef22ca4475af1bbb8963ac511144d7:11' => 'Apple QuickTime, Quality 808 (79%)', + 'ccdf1a403ec068ad21ee78686a86dd10:11' => 'Apple QuickTime, Quality 809 (79%)', + '1487f0cfc64949393aee2eab71b6b72c:11' => 'Apple QuickTime, Quality 810 (79%)', + 'a8be3b3791e958d092b3e37286802e0c:11' => 'Apple QuickTime, Quality 811-813 (79%)', + 'f9176b3ef0b4038c6c52b30ba033eb7f:11' => 'Apple QuickTime, Quality 814 (79%)', + '60264e35250325032bf7866ca8beaf58:11' => 'Apple QuickTime, Quality 815 (80%)', + '5efe038d405e029badcea4c8ee2bfc88:11' => 'Apple QuickTime, Quality 816 (80%)', + '717cbe19ae1dc9f72c86ef518aefea16:11' => 'Apple QuickTime, Quality 817 (80%)', + '9f490145dcc00db3e57014d41d2f99f2:11' => 'Apple QuickTime, Quality 818 (80%)', + '120b93a6ab4a1e8c78578e86e3ef837f:11' => 'Apple QuickTime, Quality 819 (80%)', + '9d765f3947408b2c6f26163d7b072895:11' => 'Apple QuickTime, Quality 820 (80%)', + 'a9c018e06868989776a81650300bcfce:11' => 'Apple QuickTime, Quality 821 (80%)', + '2c6581e20fa5393b3dd4d58f0df01957:11' => 'Apple QuickTime, Quality 822-824 (80%)', + '925b6581f0ae2f288530b00168152b80:11' => 'Apple QuickTime, Quality 825 (81%)', + 'c94e09eec2df4a41b2806c23d9939cb6:11' => 'Apple QuickTime, Quality 826 (81%)', + '68d5ce8ce1a9e337ee9630dadad0650e:11' => 'Apple QuickTime, Quality 827-829 (81%)', + 'f73704219e174961963f0fcd832b09a8:11' => 'Apple QuickTime, Quality 830-832 (81%)', + 'b783df92ec8a787b1eae4e05888b184b:11' => 'Apple QuickTime, Quality 833-834 (81%)', + '55cd2cad99821382b1bd78355980b1d1:11' => 'Apple QuickTime, Quality 835 (82%)', + '9ebd96ea70c2bcf4f377a175c71baf2c:11' => 'Apple QuickTime, Quality 836 (82%)', + '12d303b2e6a467b4f20a34e695f9da7e:11' => 'Apple QuickTime, Quality 837 (82%)', + '8711d6e1c56049c0e643bc6cf19a735c:11' => 'Apple QuickTime, Quality 838-839 (82%)', + '88558d6e9a513ff713945bd60ed19cc7:11' => 'Apple QuickTime, Quality 840 (82%)', + '25b45d6c1668613a61b81c6b60fa158a:11' => 'Apple QuickTime, Quality 841 (82%)', + 'b2f95d6a0eec4de39e0964a3b7303e9f:11' => 'Apple QuickTime, Quality 842 (82%)', + '631548841b70e871ee16009737dd4b9c:11' => 'Apple QuickTime, Quality 843-844 (82%)', + '7fac641c5b795e68ca8cfae4466a19c7:11' => 'Apple QuickTime, Quality 845 (83%)', + 'a15a74418a924874c5f3d2ca20e7af90:11' => 'Apple QuickTime, Quality 846-849 (83%)', + 'e4d76c3cd4d36b72537a2234a3597933:11' => 'Apple QuickTime, Quality 850 (83%)', + '36f1ec430bae8e5c72af6388e2a4d807:11' => 'Apple QuickTime, Quality 851-852 (83%)', + 'b558f097ed59f547d2b370a73145caf9:11' => 'Apple QuickTime, Quality 853 (83%)', + 'ceecacb651d0e01e9b1c78cde2d7835a:11' => 'Apple QuickTime, Quality 854 (83%)', + '82a9cce34fb9c7c3c0ab908533a9a9bf:11' => 'Apple QuickTime, Quality 855-856 (83-84%)', + 'e93244fdb14bb27a2c30ee133b3e9f5e:11' => 'Apple QuickTime, Quality 857 (84%)', + '7a12ccd01bfdd2cf3b8ee2df498b8ae0:11' => 'Apple QuickTime, Quality 858 (84%)', + 'd0d6f781130c0fecd985df78c15c5c16:11' => 'Apple QuickTime, Quality 859-860 (84%)', + '1c19e4fde384a33f208074c73775d990:11' => 'Apple QuickTime, Quality 861-863 (84%)', + '8fb281fcf4d481e59e1c15ed51ef8f19:11' => 'Apple QuickTime, Quality 864 (84%)', + 'c3f615274e58eb887a2aa75acad436ff:11' => 'Apple QuickTime, Quality 865 (84%)', + '9146c5df73615b0f2a470521bab7e4c4:11' => 'Apple QuickTime, Quality 866 (85%)', + 'ac2d99dd3ff609760c207419312e7b30:11' => 'Apple QuickTime, Quality 867 (85%)', + '7c7d225760bce3b4e479aca8bcd2b850:11' => 'Apple QuickTime, Quality 868 (85%)', + '9da4a7e310cb44578b009e78458d3b19:11' => 'Apple QuickTime, Quality 869 (85%)', + '1ae782c12797f5e5c9b083099148e43a:11' => 'Apple QuickTime, Quality 870 (85%)', + '193ace8d9e274bb9188b1a9a7bdee777:11' => 'Apple QuickTime, Quality 871-872 (85%)', + '9160a8eeb898a05dcb02206603a45a65:11' => 'Apple QuickTime, Quality 873-877 (85-86%)', + '0443da2c934e95fca0df8a0a1433eea4:11' => 'Apple QuickTime, Quality 878-880 (86%)', + 'fa99ff1ecc29c5caa95fa62189fca670:11' => 'Apple QuickTime, Quality 881-886 (86-87%)', + 'd9a914baea5468bb52c09d1d0b5bd131:11' => 'Apple QuickTime, Quality 887 (87%)', + '6c0ac535ec30285b609e0b8ca18e4dc0:11' => 'Apple QuickTime, Quality 888-890 (87%)', + 'dffb0a244fa54783569693edf84d1cda:11' => 'Apple QuickTime, Quality 891-893 (87%)', + '507b49a59dce17c02dc16fcb329352eb:11' => 'Apple QuickTime, Quality 894 (87%)', + 'dd757185a44c3d6222e9d16a0c2ee890:11' => 'Apple QuickTime, Quality 895 (87%)', + 'f3526abe33de71ad0eb728c9d446b545:11' => 'Apple QuickTime, Quality 896 (88%)', + '34466385e7bf9b2708adf19be1eb3c2d:11' => 'Apple QuickTime, Quality 897-898 (88%)', + 'e43438348a912a2210261472d1a747ab:11' => 'Apple QuickTime, Quality 899 (88%)', + '184d01e77f6239f63fd6ab6d36761e64:11' => 'Apple QuickTime, Quality 900-901 (88%)', + '26005cdf9397dcce883660aeecb0426b:11' => 'Apple QuickTime, Quality 902 (88%)', + '09321d810a54503da7ad7b8e883227ea:11' => 'Apple QuickTime, Quality 903 (88%)', + 'd6438ea5a93b6d4d0ba26de7c56f2634:11' => 'Apple QuickTime, Quality 904-905 (88%)', + 'ddc90333cb2279aa533a339710bd81ef:11' => 'Apple QuickTime, Quality 906 (88%)', + '14a352b80a350e2df6b2bc444ccc74f6:11' => 'Apple QuickTime, Quality 907-908 (89%)', + '91a7f0747ed633f481918a83b1a7c77c:11' => 'Apple QuickTime, Quality 909-914 (89%)', + 'a3bb9728b3dfa7002364659db0c420fc:11' => 'Apple QuickTime, Quality 915-917 (89-90%)', + '9df94f927ffc1f3345923232691fab3b:11' => 'Apple QuickTime, Quality 918-920 (90%)', + '259764d950c1f1e399cdb27e159c8985:11' => 'Apple QuickTime, Quality 921-922 (90%)', + '73313d816524749545292ed2284c804c:11' => 'Apple QuickTime, Quality 923-924 (90%)', + '449ed4370a849e0f736b57ee7ccab942:11' => 'Apple QuickTime, Quality 925-926 (90%)', + 'bd1f81bd50cf9859eb0ae6d2dbf75d09:11' => 'Apple QuickTime, Quality 927 (91%)', + '820db98d1ee91bb648e7a05498a340b0:11' => 'Apple QuickTime, Quality 928 (91%)', + '93c1ba0af1f50d889cb5e2364be3a056:11' => 'Apple QuickTime, Quality 929 (91%)', + 'cc96e1c8906353c4023bc7e6b72bb684:11' => 'Apple QuickTime, Quality 930 (91%)', + 'eb728dc3105ddb5a0597384b54ed948c:11' => 'Apple QuickTime, Quality 931 (91%)', + 'dc0278d78fa5c8daf84f8c2672f582c6:11' => 'Apple QuickTime, Quality 932-933 (91%)', + '1a603cba63dbba0d0815fc7319271b93:11' => 'Apple QuickTime, Quality 934-936 (91%)', + '3d8f4957eab9756993a78efe08ba3798:11' => 'Apple QuickTime, Quality 937-938 (92%)', + 'cef5a49e1834c316a4a9e7dca8d69157:11' => 'Apple QuickTime, Quality 939 (92%)', + 'ca21386b17f3866a235fca4b6e72b124:11' => 'Apple QuickTime, Quality 940 (92%)', + 'ec662935c494c5abd6f6c907f77be65c:11' => 'Apple QuickTime, Quality 941-943 (92%)', + '190a54eb1127ee87231795bc3d661b5a:11' => 'Apple QuickTime, Quality 944-946 (92%)', + 'e78229129afca214a07ad978262c545e:11' => 'Apple QuickTime, Quality 947-951 (92-93%)', + 'c31cb63954b137b62c5fe35379235e2e:11' => 'Apple QuickTime, Quality 952-954 (93%)', + '25ca43baa972ad9c82128606cd383805:11' => 'Apple QuickTime, Quality 955 (93%)', + '334c67d739adf957d1620201cb7a521c:11' => 'Apple QuickTime, Quality 956-957 (93-93%)', + '29c42d951dc84d62bd134bec71bf731b:11' => 'Apple QuickTime, Quality 958 (94%)', + 'fa6f80883480ab3ddea8ee2f6796a14b:11' => 'Apple QuickTime, Quality 959-961 (94%)', + 'e40f6a9a3daf4bfc42aedcb9107076ea:11' => 'Apple QuickTime, Quality 962 (94%)', + 'd26a06f6114d83714e3b64b6dbe97e6f:11' => 'Apple QuickTime, Quality 963-967 (94%)', + 'c40848be9d1d1018747630cdac2d7294:11' => 'Apple QuickTime, Quality 968-970 (95%)', + 'ab2b6d0624f294bf4e53e34208caeaa6:11' => 'Apple QuickTime, Quality 971 (95%)', + 'b6ea2f838fa1942a21e41f6bba417782:11' => 'Apple QuickTime, Quality 972-973 (95%)', + 'ff765e75c06c9db34f66ae7ee0202d05:11' => 'Apple QuickTime, Quality 974 (95%)', + 'c33573208ef877f1bc9a64f595e00c4d:11' => 'Apple QuickTime, Quality 975-977 (95%)', + '6528da6df208ce35fd84dccabc81d4da:11' => 'Apple QuickTime, Quality 978-979 (96%)', + '420d094d00a4a8aec94c5667254d2053:11' => 'Apple QuickTime, Quality 980-984 (96%)', + '427271dd1e2a3d7a7ce54af73d9e6c77:11' => 'Apple QuickTime, Quality 985-987 (96%)', + '99589fb7d66a29f15d9ff0f37235e7a2:11' => 'Apple QuickTime, Quality 988-991 (96-97%)', + '98fddd9e5862e06385b46a016597c02f:11' => 'Apple QuickTime, Quality 992-993 (97%)', + '381d1ca1d61986f28cfd6da0fca348da:11' => 'Apple QuickTime, Quality 994-996 (97%)', + '11a232fa9de634fadde1869007baab9c:11' => 'Apple QuickTime, Quality 997-998 (97%)', + '02374d6405239e8fe5ab939b92f4fd03:11' => 'Apple QuickTime, Quality 999-1000 (98%)', + 'f91cfb708c99c2fef0f7148976514f44:11' => 'Apple QuickTime, Quality 1001-1002 (98%)', + '83f3452abc7906930b228c29a4320089:11' => 'Apple QuickTime, Quality 1003 (98%)', + '4379016ba81dc331ffd5f9a8ab5b6399:11' => 'Apple QuickTime, Quality 1004 (98%)', + 'e2cccecebc01c7d8a4fca2dab682ba8f:11' => 'Apple QuickTime, Quality 1005-1009 (98-99%)', + '8d4b2a14a176d63e509684aa4246dabb:11' => 'Apple QuickTime, Quality 1010-1013 (99%)', + '40a7c1d8f58612f4470c2531768d93b5:11' => 'Apple QuickTime, Quality 1014-1016 (99%)', + '8392742f8f5971ed08c7520d0e9c81f3:11' => 'Apple QuickTime, Quality 1017 (99%)', + '14040c7711b6fa62383da3edc9ade1b7:11' => 'Apple QuickTime, Quality 1018-1020 (99-100%)', + '22ccf9c976b36da34f48385a09b1951b:11' => 'Apple QuickTime, Quality 1021-1023 (100%)', + '4aa632db3be6b6e85565c1fe66cb22d1:11' => 'Apple QuickTime, Quality 1024 (lossless)', + + # Apple Aperture 2.1.3 (ref 2) + '60cb2afa0cfa7395635a9360fc690b46:221111' => 'Apple Aperture Quality 0', + '6b9be09d6ec6491a20c2827dbeb678c0:221111' => 'Apple Aperture Quality 1', + 'dbb17a02e661f2475411fc1dc37902ef:221111' => 'Apple Aperture Quality 2', + '8a5df2b5337bf8251c3f66f6adbb5262:221111' => 'Apple Aperture Quality 3', + '3841f0f3be30520a1a57f41c449588ee:221111' => 'Apple Aperture Quality 4', + '2b1dba266c728a9f46d06e6e5c247953:221111' => 'Apple Aperture Quality 5', + # 'Independent JPEG Group library (used by many applications), Quality 91' => 'Apple Aperture Quality 6', + '93818f3a0e6d491500cb62e1f683da22:221111' => 'Apple Aperture Quality 7', + '8c0c36696a99fd889e0f0c7d64824f3c:221111' => 'Apple Aperture Quality 8', + '043645382c79035b6f2afc62d373a37f:221111' => 'Apple Aperture Quality 9', + '558d017ce6d5b5282ce76727fe99b91e:221111' => 'Apple Aperture Quality 10', + '0b52b82694040193aee10e8074cd7ad5:221111' => 'Apple Aperture Quality 11', + # 'Independent JPEG Group library (used by many applications), Quality 100' => 'Apple Aperture Quality 12', + + # Tested with Corel Paint Shop Pro PHOTO X2 (Win) - Different subsamplings possible + '1c78c0daaa0bbfd4a1678b5569b0fa13' => 'Corel Paint Shop Pro PHOTO, Quality 1', + '5ffdd2e918ec293efc79083703737290' => 'Corel Paint Shop Pro PHOTO, Quality 2', + '4ed4751d772933938600c4e7560bf19c' => 'Corel Paint Shop Pro PHOTO, Quality 3', + 'f647f0fb4320c61f52e2a79d12bbc8cc' => 'Corel Paint Shop Pro PHOTO, Quality 4', + '6194167174dfcb4a769cf26f5c7a018d' => 'Corel Paint Shop Pro PHOTO, Quality 5', + '6120ded86d4cc42cd7ca2131b1f51fad' => 'Corel Paint Shop Pro PHOTO, Quality 6', + 'c07a6430e56ef16a0526673398e87ac6' => 'Corel Paint Shop Pro PHOTO, Quality 7', + '507cc511e561916efa3b49228ffc8c9a' => 'Corel Paint Shop Pro PHOTO, Quality 8', + '612941a50f2c0992938bc13106caf228' => 'Corel Paint Shop Pro PHOTO, Quality 9', + '7624f08396d811fdb6f1ead575e67e58' => 'Corel Paint Shop Pro PHOTO, Quality 10', + 'e215df38e258b3d8bceb57aa64388d26' => 'Corel Paint Shop Pro PHOTO, Quality 11', + '78f66ee0bc442950808e25daa02a2b02' => 'Corel Paint Shop Pro PHOTO, Quality 12', + '14efb0bb5124910a37bcbd5f06de9aa9' => 'Corel Paint Shop Pro PHOTO, Quality 13', + 'd61168238621bd221ef1eb3dcbe270a3' => 'Corel Paint Shop Pro PHOTO, Quality 14', + 'e2d2755891b4e4bc5f7c8d76dcbb0d53' => 'Corel Paint Shop Pro PHOTO, Quality 15', + 'f6c4502144a2e5c82c07994d3cd01665' => 'Corel Paint Shop Pro PHOTO, Quality 16', + '78801638505e95827c2f7cc0c7ef78f4' => 'Corel Paint Shop Pro PHOTO, Quality 17', + 'e8ff3d165b4c028c18ec8a8f940a12a1' => 'Corel Paint Shop Pro PHOTO, Quality 18', + '984c359b9fbcc4d6f805946aa23ae708' => 'Corel Paint Shop Pro PHOTO, Quality 19', + 'd1dc48d911055bc533779d6e086f7242' => 'Corel Paint Shop Pro PHOTO, Quality 20', + 'd7437a18e86ac2832d73204acd82aa89' => 'Corel Paint Shop Pro PHOTO, Quality 21', + 'bceaee6c1a150006b3643de6942ccfa3' => 'Corel Paint Shop Pro PHOTO, Quality 22', + 'c448e6817efa9acdad225e60ed0013f9' => 'Corel Paint Shop Pro PHOTO, Quality 23', + '904f231c98f390400ba7ae17c252813f' => 'Corel Paint Shop Pro PHOTO, Quality 24', + 'ccd6708ca1dbd66a23d40cee635a0f76' => 'Corel Paint Shop Pro PHOTO, Quality 25', + '10d87624d888b75b29e156be8dad35f4' => 'Corel Paint Shop Pro PHOTO, Quality 26', + '8558c6d41f03db192198dceefbd1e89b' => 'Corel Paint Shop Pro PHOTO, Quality 27', + '058fc759cff9d615f91d9ffb4b46436a' => 'Corel Paint Shop Pro PHOTO, Quality 28', + '5c606e0f7168a78fd8d0c91646c801a3' => 'Corel Paint Shop Pro PHOTO, Quality 29', + 'e9555e593a6fd9aeee399de16080cd61' => 'Corel Paint Shop Pro PHOTO, Quality 30', + '2c2726484978a15d3d756d43b0baa290' => 'Corel Paint Shop Pro PHOTO, Quality 31', + '8b1d11d31bc9445278cf9af55b0c156b' => 'Corel Paint Shop Pro PHOTO, Quality 32', + 'aa4a5528ae18ecd36ec052014b91f651' => 'Corel Paint Shop Pro PHOTO, Quality 33', + '9a26194b114b7db253601ff80b03da9a' => 'Corel Paint Shop Pro PHOTO, Quality 34', + '3fa780a3dff1d787f7d883585a46dcfb' => 'Corel Paint Shop Pro PHOTO, Quality 35', + '0a899361ed0d51e224dc535ceb02f9a1' => 'Corel Paint Shop Pro PHOTO, Quality 36', + '3a2ab96a6ad9612e1377ddc822f02ddd' => 'Corel Paint Shop Pro PHOTO, Quality 37', + '315f4faadd967e72d730155091c4912f' => 'Corel Paint Shop Pro PHOTO, Quality 38', + '5f6e3a66672d6e4c41b1689996ca57d3' => 'Corel Paint Shop Pro PHOTO, Quality 39', + '9503a86793e86d1fca3d8797548fa243' => 'Corel Paint Shop Pro PHOTO, Quality 40', + '3b95f11bd77cb8af977c09d5851131f8' => 'Corel Paint Shop Pro PHOTO, Quality 41', + 'ececf8dfa473110534b506db58d98f15' => 'Corel Paint Shop Pro PHOTO, Quality 42', + 'cfe3144d4f8048a0507269a9d8a85993' => 'Corel Paint Shop Pro PHOTO, Quality 43', + 'eb9d48d135b2c61c51fc3f23b0001b4d' => 'Corel Paint Shop Pro PHOTO, Quality 44', + 'b08313a6919d308e50b806f138a8a2a1' => 'Corel Paint Shop Pro PHOTO, Quality 45', + '7c34e6e7fe2cc760fa5c3ed812a8b74c' => 'Corel Paint Shop Pro PHOTO, Quality 46', + '90ece7123e8d614d9aab55eaba6dd7da' => 'Corel Paint Shop Pro PHOTO, Quality 47', + '6d79fe623c4c5320bdbe4d3026f4e71a' => 'Corel Paint Shop Pro PHOTO, Quality 48', + 'a7e85552c3e5e40288891d225f308590' => 'Corel Paint Shop Pro PHOTO, Quality 49', + '67b9a678d9f669167c5b4bf12422ad50' => 'Corel Paint Shop Pro PHOTO, Quality 50', + '1fab112b17e94f53e94a9208e9091b7b' => 'Corel Paint Shop Pro PHOTO, Quality 51', + '4971237e046795a030a99a0e8d2c5acb' => 'Corel Paint Shop Pro PHOTO, Quality 52', + 'f3e1672b93ff159231c51b1b157e45fd' => 'Corel Paint Shop Pro PHOTO, Quality 53', + '6e9cfb8131373c3d1873e3f497e46b64' => 'Corel Paint Shop Pro PHOTO, Quality 54', + '9155c8acf8322e8af898272c694fa1d6' => 'Corel Paint Shop Pro PHOTO, Quality 55', + '52b20edc779f206f2aed50610971f181' => 'Corel Paint Shop Pro PHOTO, Quality 56', + 'ad801813f822ef9774801ab4d9145a61' => 'Corel Paint Shop Pro PHOTO, Quality 57', + '07259679e2a842478df97c7f0ddd4df3' => 'Corel Paint Shop Pro PHOTO, Quality 58', + '67db25c57803c34b065736f46f6afadb' => 'Corel Paint Shop Pro PHOTO, Quality 59', + 'c7498fc4b3802b290a452631dd1e1b63' => 'Corel Paint Shop Pro PHOTO, Quality 60', + '3f7b04c7952f96d2624813ed9896f128' => 'Corel Paint Shop Pro PHOTO, Quality 61', + 'd5ec901d20f3887007d0f4cfb7d1460d' => 'Corel Paint Shop Pro PHOTO, Quality 62', + '61bb38e23040b6a8b0e8721e6d6eff66' => 'Corel Paint Shop Pro PHOTO, Quality 63', + '48fac53d9d168eab3ce9b6edc4b9fcb1' => 'Corel Paint Shop Pro PHOTO, Quality 64', + '8cb101a5ae986e45cc31a9e19a35535d' => 'Corel Paint Shop Pro PHOTO, Quality 65', + '0e08dc629e883530cb2ae78c90f125b3' => 'Corel Paint Shop Pro PHOTO, Quality 66', + '5134762d2d4baac8711a52e76730591c' => 'Corel Paint Shop Pro PHOTO, Quality 67', + '14b57dc6d5381fd0a743c7bd8b28bed1' => 'Corel Paint Shop Pro PHOTO, Quality 68', + '9d398f1b1f40b7aaec1bd9cdb6922530' => 'Corel Paint Shop Pro PHOTO, Quality 69', + 'c7e68d88bee5c2ee4b61a11bc2e68c80' => 'Corel Paint Shop Pro PHOTO, Quality 70', + '917fe67f6ded5decac1820642239622c' => 'Corel Paint Shop Pro PHOTO, Quality 71', + '362c3e0c08f6951018cde7b412cd513f' => 'Corel Paint Shop Pro PHOTO, Quality 72', + 'd91cd4a2dcd1a29e6ef652ebcfdd58d7' => 'Corel Paint Shop Pro PHOTO, Quality 73', + '11f5fbd5e74e5c5e305b95dbbc4356a8' => 'Corel Paint Shop Pro PHOTO, Quality 74', + 'bf010771f909049fc5fceedcaa0f917c' => 'Corel Paint Shop Pro PHOTO, Quality 75', + 'a455a3149812ba6951a016ee6114f9da' => 'Corel Paint Shop Pro PHOTO, Quality 76', + '42e0c4082ec4d026c77d19a053a983f4' => 'Corel Paint Shop Pro PHOTO, Quality 77', + '326bd5938e2db7de9250a9fb0efc6692' => 'Corel Paint Shop Pro PHOTO, Quality 78', + 'a3e2cc4ea95cda49501bc73c494e9420' => 'Corel Paint Shop Pro PHOTO, Quality 79', + '8c89043f00678bb5c68ee90390c1b43b' => 'Corel Paint Shop Pro PHOTO, Quality 80', + 'fc5812ad9a4cd0122eb1c63f0ac3b5a3' => 'Corel Paint Shop Pro PHOTO, Quality 81', + '84dbe33962674aab86e03681ac3bd35f' => 'Corel Paint Shop Pro PHOTO, Quality 82', + 'b6b80a78472dca05c9135702e96fdad9' => 'Corel Paint Shop Pro PHOTO, Quality 83', + '01f997907a4c1dfd1e6b00aca9ff5d80' => 'Corel Paint Shop Pro PHOTO, Quality 84', + '8431e86434062b325c519fd836353cd0' => 'Corel Paint Shop Pro PHOTO, Quality 85', + '15f375a620952738ff21ff4aa496b8f7' => 'Corel Paint Shop Pro PHOTO, Quality 86', + '7b0f02aa96271376d3f81658d98fb1df' => 'Corel Paint Shop Pro PHOTO, Quality 87', + '86e7666b05bd1fc130fbf4b48f854288' => 'Corel Paint Shop Pro PHOTO, Quality 88', + '6af05d547e8911fe2d1f2b4d968a477e' => 'Corel Paint Shop Pro PHOTO, Quality 89', + '8baa876790518bf509dd09093759331d' => 'Corel Paint Shop Pro PHOTO, Quality 90', + 'eb7d90d291044d1bd8f40ca1b3ce0ddf' => 'Corel Paint Shop Pro PHOTO, Quality 91', + '6f338385a8f2cd2dd3420a4f6138a206' => 'Corel Paint Shop Pro PHOTO, Quality 92', + 'b0a0fd1ec2dd366ad00d3e83d6dedec2' => 'Corel Paint Shop Pro PHOTO, Quality 93', + 'e09026128c9880b44ac71224f477cd3b' => 'Corel Paint Shop Pro PHOTO, Quality 94', + 'd0a8f50ff547da69a57eeb892e194cff' => 'Corel Paint Shop Pro PHOTO, Quality 95', + '7849ba902d96273b5ac7b6eb98f4d009' => 'Corel Paint Shop Pro PHOTO, Quality 96', + '379f9f196d4190298a732ab9a7031001' => 'Corel Paint Shop Pro PHOTO, Quality 97', + 'c3d1601f84ec3adfbc8ca17883ef6378' => 'Corel Paint Shop Pro PHOTO, Quality 98', + '1f5e87bec674bdd7dff166c2ea9ca004' => 'Corel Paint Shop Pro PHOTO, Quality 99', + + # Tested with FixFoto Version 2.90 Build 136 (Win) - different subsamplings possible + '866dd04cb0fe2e00cda7395162480117' => 'FixFoto, Quality 0 or 1', + '1e400ba25fa835e2771772bbfb15b94b' => 'FixFoto, Quality 2', + '302ff1ad1a50d0f01a82cc88f286c649' => 'FixFoto, Quality 3', + '1343a117f5fab26d556a3e7558366591' => 'FixFoto, Quality 4', + '8fbb8cc5368224625689df80bf4d2a04' => 'FixFoto, Quality 5', + 'a371d1ffc8d85d502854a356f3b0ea74' => 'FixFoto, Quality 6', + 'a9a0a5000cd6fb322960a4c45cf1d032' => 'FixFoto, Quality 7', + 'aaac84043224d33e1d3a1723b653b0cd' => 'FixFoto, Quality 8', + '701e4820f6d0b68e67b6a2b90a7baa0c' => 'FixFoto, Quality 9', + '877d03a5abf5b6c4ad03c39afd97f4a2' => 'FixFoto, Quality 10', + 'b3d9bdc2090200537fb42f4d69631150' => 'FixFoto, Quality 11', + '3cf156d54120b53057f56e9f38ee2896' => 'FixFoto, Quality 12', + '69fe5c29b9d5e4c823f8a082ab7b3285' => 'FixFoto, Quality 13', + 'cf8573af40ced1793dcbc2346f969240' => 'FixFoto, Quality 14', + '22944c3bc03d6adea8d6819f914452c3' => 'FixFoto, Quality 15', + 'd768df38fb51c4b9977e5d7185f97a6c' => 'FixFoto, Quality 16', + '7ef2cd2b66d51fe80d94d5db427ee9ef' => 'FixFoto, Quality 17', + 'ed3d3b9ff9faf0009e44b9803f6295d7' => 'FixFoto, Quality 18', + '70a0b15e2e5f97e0a9333a2011afe5cd' => 'FixFoto, Quality 19', + 'd798b707a6b83eb54664abe0833b46aa' => 'FixFoto, Quality 20', + 'bf68d1866b75cea8f99cf2fc46f9d686' => 'FixFoto, Quality 21', + 'b98b8adb8f1f78b65800efe6c329ceab' => 'FixFoto, Quality 22', + 'c063344185079018af9fcf161a3fdf98' => 'FixFoto, Quality 23', + + # Tested with Nikon Capture NX Version 2.0.0 (Win) + '0ef9d9f62ab68807eedf6cb8c2ec120b:221111' => 'Nikon Capture NX, Quality 0', + 'efbc50df45bc1d1fbbbd29c3e5de04b2:221111' => 'Nikon Capture NX, Quality 1', + 'cbde745c78fd546d6e83dd7512ebe863:221111' => 'Nikon Capture NX, Quality 2', + '33731f743fc28e9d81e542f0ed7cdfba:221111' => 'Nikon Capture NX, Quality 3', + '866fcb1296d7da02b4ad31afb242f25f:221111' => 'Nikon Capture NX, Quality 4', + 'cfbe44397240092d3a67241a23342528:221111' => 'Nikon Capture NX, Quality 5', + 'a4b8b3408ae302ae81f125e972901131:221111' => 'Nikon Capture NX, Quality 6', + '3a6cefd4f43c513fdf0858f26afeab5a:221111' => 'Nikon Capture NX, Quality 7', + '1e861ce223babf95bc795e18cbdb49d1:221111' => 'Nikon Capture NX, Quality 8', + '4d5b512d8bc173f14e6a3cf8574f670a:221111' => 'Nikon Capture NX, Quality 9', + '9b1e6d379d3030dfa313bcaedc1ef3c7:221111' => 'Nikon Capture NX, Quality 10', + 'e39b60fcecf3221d14c62dc13ddf4726:221111' => 'Nikon Capture NX, Quality 11', + '3654bbf4a45e0c0758a82a075b3f77cc:221111' => 'Nikon Capture NX, Quality 12', + '4f5889173779409ec604622a1894ab4a:221111' => 'Nikon Capture NX, Quality 13', + '738685b86b80ff0e8b562102d1b58f71:221111' => 'Nikon Capture NX, Quality 14', + '48a53035374c08e6490893d8113ed6b3:221111' => 'Nikon Capture NX, Quality 15', + '03651ac1d15043f77949a63ac3762584:221111' => 'Nikon Capture NX, Quality 16', + '27811b28d02bd417857904f0a9e1ed58:221111' => 'Nikon Capture NX, Quality 17', + '03201bd5642a451d99b99bfd10fc42df:221111' => 'Nikon Capture NX, Quality 18', + '67d5eb5f55c9a5baa0a67d42a841d77b:221111' => 'Nikon Capture NX, Quality 19', + '18392b08bf8cf788a579f376297c3334:221111' => 'Nikon Capture NX, Quality 20', + 'de0c784b75953851dc370f4daecfa1a9:221111' => 'Nikon Capture NX, Quality 21', + '75f260644b87a9779188126da8709e7f:221111' => 'Nikon Capture NX, Quality 22', + 'c44701e8185306f5e6d09be16a2b0fbd:221111' => 'Nikon Capture NX, Quality 23', + '086e5ce1149e14efd9e424956734fe05:221111' => 'Nikon Capture NX, Quality 24', + 'aad1109d9c49b8170feac125148b2a50:221111' => 'Nikon Capture NX, Quality 25', + 'c97965ce5392623f668a386b30e41cee:221111' => 'Nikon Capture NX, Quality 26', + 'd9dadfb6f0a25765abe00e69857c5520:221111' => 'Nikon Capture NX, Quality 27', + '0ee9ca02a1fe8a17b6e50a2e86d19a7c:221111' => 'Nikon Capture NX, Quality 28', + '88b1726a20759f29eecfa2b129773127:221111' => 'Nikon Capture NX, Quality 29', + '70a311935ed066da954897fad5079377:221111' => 'Nikon Capture NX, Quality 30', + 'aa2d374bbab2a30e00c1863264588a42:221111' => 'Nikon Capture NX, Quality 31', + '097b684846696b3a8bbdf2bd2f9ded9c:221111' => 'Nikon Capture NX, Quality 32', + 'bb313d5398065376c7765092fc8ea0f0:221111' => 'Nikon Capture NX, Quality 33', + 'aa049fdc1387851a664143df0408f55c:221111' => 'Nikon Capture NX, Quality 34', + '087c1c1a368adc82900d83235f432d3f:221111' => 'Nikon Capture NX, Quality 35', + '7dec6568dbad7a70622c994a326957e2:221111' => 'Nikon Capture NX, Quality 36', + 'd2e14d8ba7d38f7544b569eea7221255:221111' => 'Nikon Capture NX, Quality 37', + 'ce6bcb98c5f9358594f5934e64b4ecc3:221111' => 'Nikon Capture NX, Quality 38', + '4785aafc8471873402819e423b8969a9:221111' => 'Nikon Capture NX, Quality 39', + '66ae78a749b520b35d4daf4531df8ae5:221111' => 'Nikon Capture NX, Quality 40', + '946d9f9346a0c65eec478945ad3d6143:221111' => 'Nikon Capture NX, Quality 41', + 'f46e96afa026233c1662c9114feb61e9:221111' => 'Nikon Capture NX, Quality 42', + '96a267e050b6d8a13439f8a9bb89722c:221111' => 'Nikon Capture NX, Quality 43', + '27c301566e155f700b01906a43473ffe:221111' => 'Nikon Capture NX, Quality 44', + 'ceff136f6dd88242500bfd639cb0c003:221111' => 'Nikon Capture NX, Quality 45', + '939b804eefc95158a934bb48e3f3b545:221111' => 'Nikon Capture NX, Quality 46', + '06186292fe0ccaaeb5999319a366c4b4:221111' => 'Nikon Capture NX, Quality 47', + 'e456c998dc126c1efad013eb7b0186c1:221111' => 'Nikon Capture NX, Quality 48', + 'ef0cd1902fb1afe284468a67eaffd078:221111' => 'Nikon Capture NX, Quality 49', + 'f4693035f8db19e0788f41255c3c052e:221111' => 'Nikon Capture NX, Quality 50', + '40c6f2886cdca8f19a654ce321ea993e:221111' => 'Nikon Capture NX, Quality 51', + 'e9387b4065bba8570375d6535ab2124b:221111' => 'Nikon Capture NX, Quality 52', + 'f3a55e422a4ab829b2c1f5a1784ce9f6:221111' => 'Nikon Capture NX, Quality 53', + '2fff3c6e48247992d1543d9e5c679759:221111' => 'Nikon Capture NX, Quality 54', + '5a1849b49122ff09949f1d355b4f9eaa:221111' => 'Nikon Capture NX, Quality 55', + 'a582968bb1890620ffbae916ebafcb64:221111' => 'Nikon Capture NX, Quality 56', + '81597eb992e32e186d2b5565bbe4ae3a:221111' => 'Nikon Capture NX, Quality 57', + '7364416ce4f2a9282efdbe052574527b:221111' => 'Nikon Capture NX, Quality 58', + '5301c2bcae09fd4305e47ffc56b2c8a7:221111' => 'Nikon Capture NX, Quality 59', + '5a1849b49122ff09949f1d355b4f9eaa:211111' => 'Nikon Capture NX, Quality 60', + '9be2446f168941ff42d9fc7441f2429b:211111' => 'Nikon Capture NX, Quality 61', + 'bbba80e58afae43278e287021d4f1499:211111' => 'Nikon Capture NX, Quality 62', + '2a9ae394dc32a418960522cbe9c6df24:211111' => 'Nikon Capture NX, Quality 63', + '67fbe0dce139b6db1813e30bbbceccf3:211111' => 'Nikon Capture NX, Quality 64', + '17bce376f588ebf2b3e9002a337c239d:211111' => 'Nikon Capture NX, Quality 65', + 'cd2c6c01d8eb8d985086b54e2269278a:211111' => 'Nikon Capture NX, Quality 66', + '34b25782fc089616807bbbe7f7cd8413:211111' => 'Nikon Capture NX, Quality 67', + '37b8bbab382a228eabb0dc64c0edcb0f:211111' => 'Nikon Capture NX, Quality 68', + 'b163f35baed567d70aa2536695558724:211111' => 'Nikon Capture NX, Quality 69', + '251eb2d7903f63b168348ec483ba499a:211111' => 'Nikon Capture NX, Quality 70', + '42e7cdf33b9067a7124dd27020704f9a:211111' => 'Nikon Capture NX, Quality 71', + '032678d9de74e5530896c28079f666af:211111' => 'Nikon Capture NX, Quality 72', + '30d7b6db02954dfc4ce47a089d0f40d9:211111' => 'Nikon Capture NX, Quality 73', + '5c1a40094128ac76eab0405dcb4ae3c7:211111' => 'Nikon Capture NX, Quality 74', + '2706b8b0cf6686148e285b6d3e44dd72:211111' => 'Nikon Capture NX, Quality 75', + '6ca4a27cb36f35ab84b0e2df06bb32f4:211111' => 'Nikon Capture NX, Quality 76', + '6f9cae52d3f47f514f7c927314455a5a:211111' => 'Nikon Capture NX, Quality 77', + 'c0204862b8aafa2c286c7b58d755c31f:211111' => 'Nikon Capture NX, Quality 78', + 'd8ef40736b072f09bead5f73f5ec1372:211111' => 'Nikon Capture NX, Quality 79', + '8c389c29eca238b3b331f65f7e124a27:111111' => 'Nikon Capture NX, Quality 80', + '6f9cae52d3f47f514f7c927314455a5a:111111' => 'Nikon Capture NX, Quality 81', + '8e1ceace8fafe31282393d8677e76994:111111' => 'Nikon Capture NX, Quality 82', + '60f75a915647ed50d1724179d50a35d2:111111' => 'Nikon Capture NX, Quality 83', + 'df54eb20ec90f41f1e6c37e241ee381c:111111' => 'Nikon Capture NX, Quality 84', + '5522213c915e2af3ad01ee2ec27ee3ed:111111' => 'Nikon Capture NX, Quality 85', + '08c063f0997262d9977df4b44e682d82:111111' => 'Nikon Capture NX, Quality 86', + 'd2e34c70872ac119dda6bdeeb36bf229:111111' => 'Nikon Capture NX, Quality 87', + 'e5abf48ce0cc2b4a3db7eca3a1112b7a:111111' => 'Nikon Capture NX, Quality 88', + 'b69dcb672088f296323d891219464ad8:111111' => 'Nikon Capture NX, Quality 89', + 'b6d1c6efb27ea721577888b5f981ad7b:111111' => 'Nikon Capture NX, Quality 90', + 'b023f424f81c8cbbab20119c06163dce:111111' => 'Nikon Capture NX, Quality 91', + '77f680490d08697cb0f11ff3fe76b7e8:111111' => 'Nikon Capture NX, Quality 92', + '1860106097672532e7ebc2026d7f9681:111111' => 'Nikon Capture NX, Quality 93', + '0c7d4861b3bee5d766a93f2d34027bfa:111111' => 'Nikon Capture NX, Quality 94', + '3adf9a0b85a4000243bbf833cd8e6966:111111' => 'Nikon Capture NX, Quality 95', + '9530dfffc5574606841a597212ec25b4:111111' => 'Nikon Capture NX, Quality 96', + 'c7294290fe26155147072f9041705cfb:111111' => 'Nikon Capture NX, Quality 97', + 'c24c44a4dadd77c15e0b4c741a2d4bd5:111111' => 'Nikon Capture NX, Quality 98', + '36016cd5527c505ef3bbba8b3e22f9db:111111' => 'Nikon Capture NX, Quality 99', + 'c9309ab058680151be5f97e6c54dc687:111111' => 'Nikon Capture NX, Quality 100', + + # Tested with ACDsee PhotoEditor 4.0.208c-de (Win) + '2ab2f6a116ca6fc0bbf188b19b9de967' => 'ACD Systems Digital Imaging, Quality 0 or 1', + 'f4f9d5c07c78e8700a6f3def0782a18e' => 'ACD Systems Digital Imaging, Quality 2', + '66fc410ab8f71a7fdef86fd70b742dc1' => 'ACD Systems Digital Imaging, Quality 3', + '8e763b5b9255df1f4cb7b9732e99c210' => 'ACD Systems Digital Imaging, Quality 4', + 'fd3eed19f6667ab0bedfa3263390ce25' => 'ACD Systems Digital Imaging, Quality 5 or 6', + 'dc0dc92085037072e27247f64af0f22d' => 'ACD Systems Digital Imaging, Quality 7', + '233ed690eb7e9008c20ed16e79aa3eb5' => 'ACD Systems Digital Imaging, Quality 8', + '684649f6c1590f5a912a827a6d8bfc6b' => 'ACD Systems Digital Imaging, Quality 9', + 'ed6aec096e8776b483b2c2b3d7e15d76' => 'ACD Systems Digital Imaging, Quality 10 or 11', + '9cd85933ddb1101d9b859a19e9a30334' => 'ACD Systems Digital Imaging, Quality 12', + '222a8769205a592ec834b6f5fc654a21' => 'ACD Systems Digital Imaging, Quality 13', + '29f957e2a0af0f44d271c3c4e27eec4b' => 'ACD Systems Digital Imaging, Quality 14', + 'c46c764191f9c3db2bfe8d134512bcd8' => 'ACD Systems Digital Imaging, Quality 15 or 16', + '56caa684ce7eb0b1cf662e1c88ed1614' => 'ACD Systems Digital Imaging, Quality 17', + 'cedc5208c6e1cbffd8be0e47bfd76698' => 'ACD Systems Digital Imaging, Quality 18', + 'dec0717305bae8309a934e1d6a251d88' => 'ACD Systems Digital Imaging, Quality 19', + '8c85e0e8f41257e2cd739a5b158ec218' => 'ACD Systems Digital Imaging, Quality 20 or 21', + '6ae7ab4e6d5e0e67006cca59c70f843c' => 'ACD Systems Digital Imaging, Quality 22', + '840be626ed18db6cdef3c5c357e24d34' => 'ACD Systems Digital Imaging, Quality 23', + 'd48c2b9e514e25fcc4b3f2408d168d72' => 'ACD Systems Digital Imaging, Quality 24', + 'b9eb63b89c80c71f4eac8c6e27d272f1' => 'ACD Systems Digital Imaging, Quality 25 or 26', + 'bcd4d36a9db91a51d1a571f71f8230d4' => 'ACD Systems Digital Imaging, Quality 27', + 'ac2f66ab2559019fcf021b9a32b049ab' => 'ACD Systems Digital Imaging, Quality 28', + '4208fca702ec702bd5d41c8231883057' => 'ACD Systems Digital Imaging, Quality 29', + 'fa620c67ab09a4c0d1c5b8e65ade361e' => 'ACD Systems Digital Imaging, Quality 30 or 31', + '679dea81c8d4563e07efac4fab6b89ca' => 'ACD Systems Digital Imaging, Quality 32', + '43ceb0c1a5d94d55ee20dc3a168498b2' => 'ACD Systems Digital Imaging, Quality 33', + 'a9cc8a19ae25bc024c3d92d84c13c7a5' => 'ACD Systems Digital Imaging, Quality 34', + 'e3e7280c8a9e82d31e22d24d5b733580' => 'ACD Systems Digital Imaging, Quality 35 or 36', + 'a06d250213e349005897bd6fa5bebca8' => 'ACD Systems Digital Imaging, Quality 37', + '40d08b823fa60b838dd9998d1e2b550a' => 'ACD Systems Digital Imaging, Quality 38', + '4998abefc838e35cf0180395309e2e33' => 'ACD Systems Digital Imaging, Quality 39', + '280205c47c8d3706c2f36b1986e9b149' => 'ACD Systems Digital Imaging, Quality 40 or 41', + '8534b67f8115ddc0296623a1ed3fc8ec' => 'ACD Systems Digital Imaging, Quality 42', + '292b83b37765408b65f496cddd3f96ea' => 'ACD Systems Digital Imaging, Quality 43', + 'cae0c8eb9a11a1f6eb7eca9651d8dbc0' => 'ACD Systems Digital Imaging, Quality 44', + '078db0d0bffafa44def2e8b85eec26f6' => 'ACD Systems Digital Imaging, Quality 45 or 46', + '6a26a11cc28df00e01d5979e2e0fb4f7' => 'ACD Systems Digital Imaging, Quality 47', + 'b41b3d226ba21244b8070ba719ec721a' => 'ACD Systems Digital Imaging, Quality 48', + '9a8a54328e297faa0a546c46145c9aa8' => 'ACD Systems Digital Imaging, Quality 49', + '256e617be51dade18503fcbbe87cd4a6' => 'ACD Systems Digital Imaging, Quality 50 or 51', + '064f160a8504465551738c9071f3850f' => 'ACD Systems Digital Imaging, Quality 52', + '5aef4c0bc6a5c8f1baded29946a56310' => 'ACD Systems Digital Imaging, Quality 53', + 'c20f4841a1ff7e393af8f6ea4124403c' => 'ACD Systems Digital Imaging, Quality 54', + '14afe9b58e0eacef42db61e1d7fdd09c' => 'ACD Systems Digital Imaging, Quality 55 or 56', + '147598404233439485574200e253f88e' => 'ACD Systems Digital Imaging, Quality 57', + '17479c1e73d2c062872c871db80d949b' => 'ACD Systems Digital Imaging, Quality 58', + 'd237b1202f88ba8183bc1cb69dd4be66' => 'ACD Systems Digital Imaging, Quality 59', + 'fc923f2d38e0e549134e1ec86f58149a' => 'ACD Systems Digital Imaging, Quality 60 or 61', + '93b4929d4a3b955f4996ab7e3b6fbe53' => 'ACD Systems Digital Imaging, Quality 62', + '054f418c24a6a733186a27aa739dc93a' => 'ACD Systems Digital Imaging, Quality 63', + '0df2be705ae86e5de1e508db95efb182' => 'ACD Systems Digital Imaging, Quality 64', + 'c1978a445de1173b5039b0cf8d8a91fe' => 'ACD Systems Digital Imaging, Quality 65 or 66', + '3d8e25b74d0d9be662f26ec5fed6fe94' => 'ACD Systems Digital Imaging, Quality 67', + '8887b718c97e0d80ed8d9a198387e2eb' => 'ACD Systems Digital Imaging, Quality 68', + '8cdb9100cfbb246d440d469e72ce37a6' => 'ACD Systems Digital Imaging, Quality 69', + '379efafa6e71a90ccfcb57073d0bc5c8' => 'ACD Systems Digital Imaging, Quality 70 or 71', + 'e1e122ebb2733a5ccdb5ff1cdce86d4d' => 'ACD Systems Digital Imaging, Quality 72', + '41dd47887a2b87e22ad3bbacc022374e' => 'ACD Systems Digital Imaging, Quality 73', + 'a0a30c816d5d47a91c66e5645eb5fdb8' => 'ACD Systems Digital Imaging, Quality 74', + '731f7ffedba80407d039c1db5a785f95' => 'ACD Systems Digital Imaging, Quality 75 or 76', + '7cfd092a41a0e1c029e82467cb4c034f' => 'ACD Systems Digital Imaging, Quality 77', + 'f1b005980104aac41b49973beed9c8c2' => 'ACD Systems Digital Imaging, Quality 78', + 'ba12dbfbd652c9cde69822996bdb2139' => 'ACD Systems Digital Imaging, Quality 79', + 'd3784280d08a8df51e607bde8c8b5ead' => 'ACD Systems Digital Imaging, Quality 80 or 81', + '7ed560efea0b44168d910a73fab9204c' => 'ACD Systems Digital Imaging, Quality 82', + 'deaa8bbd7c5414b93d8029aa14a76d3a' => 'ACD Systems Digital Imaging, Quality 83', + '9ae3a57ce98290176c4700baaff5661f' => 'ACD Systems Digital Imaging, Quality 84', + 'cb99b9bd30ae36929755fee9208ab36b' => 'ACD Systems Digital Imaging, Quality 85 or 86', + '75ff62bbf17aa1762dd15677e961ce67' => 'ACD Systems Digital Imaging, Quality 87', + 'd4f1922c71a6c96a530a9a8268fbc63b' => 'ACD Systems Digital Imaging, Quality 88', + 'ec994ef421efd6bc78671858b9f942ad' => 'ACD Systems Digital Imaging, Quality 89', + '5ca52e1ffe2c84660d7377c33c88ad53' => 'ACD Systems Digital Imaging, Quality 90 or 91', + '5522213c915e2af3ad01ee2ec27ee3ed' => 'ACD Systems Digital Imaging, Quality 92', + '21aa1a0036251eecfffd24e37d7ce3dd' => 'ACD Systems Digital Imaging, Quality 93', + '3233b63fc39fbbaa9af364e8a33862ff' => 'ACD Systems Digital Imaging, Quality 94', + '1860106097672532e7ebc2026d7f9681' => 'ACD Systems Digital Imaging, Quality 95 or 96', + '0c7d4861b3bee5d766a93f2d34027bfa' => 'ACD Systems Digital Imaging, Quality 97', + 'c9309ab058680151be5f97e6c54dc687' => 'ACD Systems Digital Imaging, Quality 98', + 'ffe6bb565b2c9008ab917c57ba94cd67' => 'ACD Systems Digital Imaging, Quality 99', + 'd6390cc36d2f03c1d2dd13d6910ca46b' => 'ACD Systems Digital Imaging, Quality 100; Pentax K20D/OptioE60 Premium', + + # StereoPhoto Maker Version 3.25 - Option "No compression ghosting" activated + '185893c53196f6156d458a84e1135c43:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 1', + 'b41ccbe66e41a05de5e68832c07969a7:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 2', + 'efa024d741ecc5204e7edd4f590a7a25:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 3', + '3396344724a1868ada2330ebaeb9448e:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 4', + '14276fffb98deb42b7dbce30abb8425f:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 5', + 'a99e2826c10d0922ce8942c5437f53a6:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 6', + '0d3de456aa5cbb8a2578208250aa9b88:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 7', + 'fa987940fdedbe883cc0e9fcc907f89e:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 8', + '1c9bb67190ee64e82d3c67f7943bf4a4:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 9', + '57d20578d190b04c7667b10d3df241bb:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 10', + '619fd49197f0403ce13d86cffec46419:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 11', + '327f47dd8f999b2bbb3bb25c43cf5be5:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 12', + 'e4e5bc705c40cfaffff6565f16fe98a9:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 13', + '6c64fa9ad302624a826f04ecc80459be:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 14', + '039a3f0e101f1bcdb6bb81478cf7ae6b:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 15', + 'c23b08c94d7537c9447691d54ae1080c:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 16', + '200107bc0174104bbf1d4653c4b05058:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 17', + '72abfdc6e65b32ded2cd7ac77a04f447:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 18', + '1799a236c36da0b30729d9005ca7c7f9:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 19', + 'c33a667bff7f590655d196010c5e39f3:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 20', + 'b1dc98f6a2f8828f8432872da43e7d94:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 21', + '07318a0acfebe9086f0e04a4c4f5398a:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 22', + 'a295b7163305f327a5a45ae177a0a19c:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 23', + 'c741c1b134cf81ab69acc81f15a67137:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 24', + 'a68893776502a591548c7b5bece13e1b:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 25', + '111848d9e41f6f408ef70841f90c0519:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 26', + '886374ceebcfd4dfed200b0b34b4baca:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 27', + '666dd95fd0e20f5c20bc44d78d528869:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 28', + '1aa58cb85dda84de2ddf436667124dcd:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 29', + '9d321ab2bdda6f3cb76d2d88838aa8c3:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 30', + '6ad87d648101c268f83fa379d4c773f2:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 31', + 'cdf8e921300f27a4af7661a2de16e91a:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 32', + '3f48672e37b6dd2e571b222e4b7ff97d:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 33', + 'a53a7d4cc86d01f4c1b867270c9c078f:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 34', + '09ec03f5096df106c692123f3fd34296:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 35', + 'a946498fd1902c9de87a1f5182966742:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 36', + '5d650a1d38108fd79d4f336ba8e254c2:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 37', + '81d620f1b470fd535b26544b4ea20643:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 38', + '892788bdf8cbef5c6fbd7019a079bf8e:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 39', + 'cf3929fd4c1e5c28b7f137f982178ad1:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 40', + '31f288945896ed839f1d936bff06fb03:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 41', + 'e0c38f0c5e6562445d4e92bae51713be:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 42', + '18fa29d1164984883a6af76377b60d5a:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 43', + 'eff737b226fbce48c42625c5bf9dabb6:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 44', + 'b900f91ee8697255d5daebce858caaeb:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 45', + 'ab2f8513823067af242f7e3c04a88a9c:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 46', + '60b682c4d412f5255efbaa32787c46ca:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 47', + 'ea50813e06203c2ad1165252bcb99a1d:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 48', + 'f6308a717437d3653b0751ebf511db0f:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 49', + '7c8242581553e818ef243fc680879a19:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 50', + 'fc41ab8251718977bc6676f502f457e0:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 51', + '606c4c78c0226646bf4d3c5a5898fb17:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 52', + '0e6c6a5440d33d25f1c25836a45cfa69:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 53', + '7464b2361e5b5f5a9ba74a87475dda91:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 54', + 'aeaa2ca48eabb3088ebb713b3c4e1a67:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 55', + '3f36450b0ba074578391e77f7340cef0:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 56', + 'be232444027e83db6f8d8b79d078442e:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 57', + '712c145d6472a2b315b2ecfb916d1590:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 58', + 'ae3dd4568cc71c47d068cf831c66b59d:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 59', + 'b290e52c21a435fede4586636ef5e287:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 60', + 'a09ca4c4391e0221396a08f229a65f9d:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 61', + '0818578fc5fc571b4f8d5ffefc9dc0d8:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 62', + '7c685e2916555eda34cb37a1e71adc6a:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 63', + '69c6b9440342adfc0db89a6c91aba332:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 64', + 'd5d484b68e25b44288e67e699829695c:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 65', + 'de8310d09116a7a62965f3e0e43ef525:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 66', + 'e4735f63e88baf04599afc034e690845:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 67', + 'b4ef810b14dee9c6d6d8cace98f799a6:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 68', + '52886ef80147c9a136e20b2bc3b76f52:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 69', + '9c62dbc848be82ef91219ba9843998be:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 70', + 'bfe8c1c73eea84b85673487a82f67627:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 71', + 'ea445840d29c51009a2a8cd49b96ccee:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 72', + '71c1a56890fff9b0a095fa5a1c96132b:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 73', + 'f080b02331ac8adf03de2281042d2b49:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 74', + 'd0eaa368737f17f6037757d393a22599:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 75', + '303663905d055b77bb547fe0b0beb9c5:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 76', + '5cdf1d5bbe19375ad5c7237273dddede:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 77', + 'd64e7ff8292fd77131932864d3c9ce7c:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 78', + '12b4cc13891c5aef3dadb3405b6fa65d:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 79', + 'b008cd63591f8fd366f77d2b224b9c9c:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 80', + '49b6e472c7d5ecead593c6009768e765:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 81', + 'bce6fa61623ad4f65ff3fec1528cb026:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 82', + 'c2b037bf9f5e5baba804d7bbbb2dc73b:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 83', + '7fe7b339c6ffc62b984eeab4b0df9168:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 84', + '274bbeb0ac3939f90c578ebb1f5a9eef:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 85', + '0a0268c655d616b0e4af2851533aa3af:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 86', + '52318e260c0d6b3dbee85c87f9b94e63:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 87', + 'b64cc19a0f81a506ed5bcfb9c131c8fe:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 88', + 'd8c54333eb475b8db9f32f11fe96337e:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 89', + '12fe6b9bfd20f4d7f0ac2a221c566c45:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 90', + '12aefbf7689633c83da714c9f0e90e05:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 91', + 'a3a96add050fc51a2b3ce59a9a491034:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 92', + '7b0242bd9aaeab4962f5d5b39b9a4027:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 93', + '12fc29c1d8940c93a47ee9d927a17561:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 94', + 'e1fedef5184beeb7b0f5c055c7ae1d31:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 95', + 'ae9202355f603776794d3e62c43578d6:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 96', + '36da00bae6cd81d1f97e32748c07e33f:111111' => 'StereoPhoto Maker, No compression ghosting, Quality 97', + + # Tested with FujiFilm FinePixViewer Ver 5.4.11G (Win) + '3a8a34631e388e39d13616d003f05957:211111' => 'FinePixViewer, Basic', + 'b6a2598792fd87b7eb0c094cbd52862f:211111' => 'FinePixViewer, Fine', + '4ee61c39b97558a273f310e085d0bdd2:211111' => 'FinePixViewer, Normal', + + # Tested with Canon Digital Photo Professional Version 3.4.1.1 (Win) + '252482232ff1c8cf77db4f0c6402f858:211111' => 'Canon Digital Photo Professional, Quality 1', + 'ec6c55677b94970bc09f70265f1d5b55:211111' => 'Canon Digital Photo Professional, Quality 2', + 'a1085c167f1cd610258fe38c8a84a8b9:211111' => 'Canon Digital Photo Professional, Quality 3', + '8ab1119f4ed4941736cb8ec1796f5674:211111' => 'Canon Digital Photo Professional, Quality 4', + 'e66c03f97b19213f385136f014c78ac1:211111' => 'Canon Digital Photo Professional, Quality 5', + 'a2f4b6ac52f87791380bdfe38ae333e1:211111' => 'Canon Digital Photo Professional, Quality 6', + 'fe85b802c5779dcf45ea4bb7749ee886:211111' => 'Canon Digital Photo Professional, Quality 7', + '35686967efa5fb333fb8f4844efc33a3:211111' => 'Canon Digital Photo Professional, Quality 8', + 'a5894172d7ec5f0c1550934c9e9385c9:211111' => 'Canon Digital Photo Professional, Quality 9', + 'd6390cc36d2f03c1d2dd13d6910ca46b:211111' => 'Canon Digital Photo Professional, Quality 10', + + # Tested with Canon ZoomBrowser EX 6.1.1.21 (Win) + 'e66c03f97b19213f385136f014c78ac1:211111' => 'Canon ZoomBrowser, Low', + 'bf72e4d4aacbdaeb86fd3f67c8df2667:211111' => 'Canon ZoomBrowser, Medium', + 'd6390cc36d2f03c1d2dd13d6910ca46b:211111' => 'Canon ZoomBrowser, Highest', + 'aeb34eb083acc888770d65e691497bcf:211111' => 'Canon ZoomBrowser, High', + + # Tested with PENTAX PHOTO Laboratory 3.51 (Win) + '76d958276bf2cac3c36b7d9a677094a7:211111' => 'PENTAX PHOTO Laboratory, Highest compression', + 'bf72e4d4aacbdaeb86fd3f67c8df2667:211111' => 'PENTAX PHOTO Laboratory, High compression', + 'fa8720d025f2a164542b6a8e31112991:211111' => 'PENTAX PHOTO Laboratory, Medium quality', + 'f3235a7d187d083b7b7ead949653f730:211111' => 'PENTAX PHOTO Laboratory, High quality', + 'd6390cc36d2f03c1d2dd13d6910ca46b:211111' => 'PENTAX PHOTO Laboratory, Highest quality', + + # Tested with Sony Image Data Converter SR Version 2.0.00.08150 (Win) + 'd6390cc36d2f03c1d2dd13d6910ca46b:211111' => 'Sony Image Data Suite, Quality 1 (high quality)', + 'aeb34eb083acc888770d65e691497bcf:211111' => 'Sony Image Data Suite, Quality 2', + '524742ca0cff64ecc0c7d7413e7d4b8d:211111' => 'Sony Image Data Suite, Quality 3', + 'c44701e8185306f5e6d09be16a2b0fbd:211111' => 'Sony Image Data Suite, Quality 4 (high compression)', + + # Devices + + # Canon + '0147c5088beb16642f9754f8671f13b3:211111' => 'Canon PowerShot, Fine', + '586b40c7d4b95e11309636703e81fbe9:211111' => 'Canon EOS 20D, Fine', + '6640ae3bb6f646013769b182c74931b5:211111' => 'Canon PowerShot, Normal', + '83d6d7dd7ace56feeeb65b88accae1bc:211111' => 'Canon PowerShot, Normal Small', + 'b8548a302585d78a0c269b54bff86541:211111' => 'Canon PowerShot, Fine Small', + '9d125046484461bbc155d8eff6d4e8f0:211111' => 'Canon PowerShot, Superfine (A430/A460)', + '0e618a0e79b4d540da1f6e07fcdce354:211111' => 'Canon PowerShot, Superfine Small', + 'd255f70a910a2d0039f4e792d2c01210:211111' => 'Canon PowerShot, Superfine Medium2', + '8bc267b04a54c02fdee1f4fdf0bcce83:211111' => 'Canon EOS 1DmkIII/5DmkII/40D/1000D, Fine', + '17cb779485969589a5c7eb07a5d53247:211111' => 'Canon EOS 1DmkIII, Fine (pre-production)', + 'ee1c033afaf4cd5263ff2b1c1ff8966c:211111' => 'Canon PowerShot, Superfine', + 'a92912eb3c81e5c873d49433264af842:211111' => 'Canon EOS 30D/40D/50D/300D, Normal', + '0cec88a0cd8fe35720e78cdcdbdadef6:121111' => 'Canon EOS 1DmkII, Fine (A)', + '72cdcc91e3ddc2c3d17c20173b75c5ef:211111' => 'Canon EOS 1DmkII, Fine (B)', + '483b5288e4256aa8ff96d6ccb96eba43:211111' => 'Canon EOS 1DmkII, Fine (C)', + 'ea2f997a0261bab501bf122b04cbc859:211111' => 'Canon EOS 1DSmkII, Fine', + '98af13526b7e4bbf73a9fb11a8fa789d:121111' => 'Canon EOS 1DSmkII, Fine (vertical)', + '9e6abfb26d3b95b8cd2f710e78def947:121111' => 'Canon EOS 300D, Fine (vertical)', + '4d6b36e81fe30c67dd53edb4d7c05422:121111' => 'Canon EOS 40D, Fine (vertical)', + '92c1557deaa14f1cdaf92cf0531487f1:121111' => 'Canon EOS 1D/1DS, Fine', + 'db8d4df12405d0d69eb25f06a963ac5b:211111' => 'Canon DV', + 'eaead98bbdfde35210f48286662e8ad2:211111' => 'Canon DV Hi-Res', + '74f0ef9476707be45f06951ca9a809ba:211111' => 'Canon DV/Optura/Elura, Superfine', + + # HTC + 'bf72e4d4aacbdaeb86fd3f67c8df2667:221111' => 'HTC Touch Diamond P3700, Quality Unknown', + + # Konica/Minolta + 'b5c213a3785c4c62b25d8f8c30758593:211111' => 'Konica/Minolta DYNAX 7D, Fine', + + # Nikon + '118a60a90c56bcb363fdd93b911a3371:211111' => 'Nikon D50 / D80, Fine', + '457b05fd0787a8e29bd43cd65911d6ca:211111' => 'Nikon D80, Basic', + '5701582a0da2e9e8dcd923a5cf877494:211111' => 'Nikon D50, Fine', + '662bd7fb9dff6426e310f9261a3703d0:211111' => 'Nikon D50, Fine', + 'e06eb7848ec8766239ff014aa8b62e49:211111' => 'Nikon D80, Normal', + '9e201a496a3700a77d9102c0dd0f8dbf:211111' => 'Nikon D300, Basic', + + # Panasonic + '07d3cd227395b060a132411cbfc22593:211111' => 'Panasonic DMC-FZ50, High (A)', + '118a60a90c56bcb363fdd93b911a3371:211111' => 'Panasonic DMC-FZ50/TZ3, High (A)', + '1b8d04b1d56a4c0c811a0d3a68e86d06:211111' => 'Panasonic DMC-FZ50, High (B)', + '1e619cbdee1f8ff196d34dad9140876f:211111' => 'Panasonic DMC-FZ50, High (C)', + '493abc7f4b392a0341bfcac091edb8f8:211111' => 'Panasonic DMC-FZ30, High (B)', + '4aa883c43840de7f0d090284120c69bc:211111' => 'Panasonic DMC-FZ50, High (D)', + '7eafb9874384d391836e64911e912295:211111' => 'Panasonic DMC-FZ50, High (E)', + '82b56237e4eccde035edff4a5abdba44:211111' => 'Panasonic DMC-FZ50, High (F)', + '8335023e5a1ee8df80d52327b0556c44:211111' => 'Panasonic DMC-FZ30, High (C)', + '8c105b3669931607853fa5ba4fffb839:211111' => 'Panasonic DMC-FZ30, High (D)', + '8ecfb959bc76e5d6703f3f3bba2c5529:211111' => 'Panasonic DMC-FZ30, High (E)', + '96eda111b2153648b3f27d6c1a9ec48f:211111' => 'Panasonic DMC-FZ50/TZ3, High (B)', + '99f76923cfbd774febea883b603b8103:211111' => 'Panasonic DMC-FZ30, High (F)', + '9b3475b865b9d31e433538460b75a588:211111' => 'Panasonic DMC-FZ10, High', + '9eb7cdfd07099c1bb8e2c6c04b20b8ba:211111' => 'Panasonic DMC-FZ30, High (G)', + '9fc030294fa5c4044dbb0cb461b0cf93:211111' => 'Panasonic DMC-TZ5, High (A)', + 'a8779af4cb8afa2def1d346a9b16e81a:211111' => 'Panasonic DMC-TZ5, High (B)', + 'bebd334aca511e2a2b6c60f43f9e6cf1:211111' => 'Panasonic DMC-FZ30, High (H)', + 'c871ce0851d4647f226b2dcfd49fe9a9:211111' => 'Panasonic DMC-L1, Very High', + 'eb625c64e32314f51dc4286564a71f7b:211111' => 'Panasonic DMC-FZ10, High', + + # Pentax + # (K10D uses same DQT tables for different qualities) + '1027a4af6a2a07e58bbd6df5b197d44e:211111' => 'Pentax K10D (A)', + '17a77c2574ff5b72b3284f57977187f3:211111' => 'Pentax K10D (B)', + '1aee684c7eb75320d988f6296c4c16ea:211111' => 'Pentax K10D (C)', + '32386501afff88b45432b23fe41593e8:211111' => 'Pentax K10D (D)', + '35ad02c3d8237a074b67423c39d3d61c:211111' => 'Pentax K10D (E)', + '39d929c095f37a90e7d083db40e8642d:211111' => 'Pentax K10D (F)', + '4127433151f74654762b1ef3293781f4:211111' => 'Pentax K10D (G)', + '599a7794c32b9d60e80426909ed40a09:211111' => 'Pentax K10D (H)', + '641812174c82d5b62ec86c33bd852204:211111' => 'Pentax K10D (I)', + '76d958276bf2cac3c36b7d9a677094a7:211111' => 'Pentax K10D (J)', + '79b07131be4827795315bf42c65212f2:211111' => 'Pentax K10D (K)', + '836448ef538366adb50202927b53808a:211111' => 'Pentax K10D (L)', + '8f70e4a31ad4584043ddc655eca17e89:211111' => 'Pentax K10D (M)', + '90d3c964eaf6e4bd12cf5ca791a7d753:211111' => 'Pentax K10D (N)', + '994a9f2060976d95719ca7064be3a99c:211111' => 'Pentax K10D (0)', + '994a9f2060976d95719ca7064be3a99c:211111' => 'Pentax K10D/K20D (P)', + '9971f02a466c47d640e8f20a2e4b55b9:211111' => 'Pentax K10D (Q)', + 'a16626c285e5a2290d331f99f4eec774:211111' => 'Pentax K10D (R)', + 'a64569d6387a118992e44e41aaeac27e:211111' => 'Pentax K10D (S)', + 'a8055a53fda7f9a0e387026c81960aa4:211111' => 'Pentax K10D (T)', + 'ab50a9f53a44ffecc54efe1cb7c6620a:211111' => 'Pentax K10D (U)', + 'aeb34eb083acc888770d65e691497bcf:211111' => 'Pentax K10D (V)', + 'af2a112c30fa29213a402dbd3c2b2d3a:211111' => 'Pentax K10D (W)', + 'bb4475a9e14464eb4682fd81cceb1f91:211111' => 'Pentax K10D (X)', + 'bf72e4d4aacbdaeb86fd3f67c8df2667:211111' => 'Pentax K10D (Y)', + '0a953ba56b59fa0bbbdac0162ea1c96b:211111' => 'Pentax K10D (Z)', + '387354b46b9726f33da5c0c1a0c383a0:211111' => 'Pentax K10D/K20D (AA)', + '4e7f4e5cd15f4fc089ab25890619dc60:211111' => 'Pentax K10D (AB)', + '6518270228fd20730740a08cc8a171f6:211111' => 'Pentax K10D (AC)', + '72bce7df55635509eb6468fc6406941d:211111' => 'Pentax K10D (AD)', + '7cafc25f204fc4ddf39d86e2f0f07b62:211111' => 'Pentax K10D (AE)', + '811e5b0229f0e8baf4b40cd2d8777550:211111' => 'Pentax K10D (AF)', + '9282a1cec6bbd1232b3673091164d43d:211111' => 'Pentax K10D (AG)', + 'c59a4cf0beedbfd1b102dc3d3c8e73ac:211111' => 'Pentax K10D (AH)', + 'd97b27b45fdbe82a79364e0939adbf90:121111' => 'Pentax K10D (AI)', + 'db87a4c5c1d4e03dc6645bcf0535a930:211111' => 'Pentax K10D (AJ)', + 'f9a93cb70da7bbe87e35cd9980a5fd47:211111' => 'Pentax K10D (AK)', + 'ff6a158f803e42bfbf9f702c016b84b3:211111' => 'Pentax K10D (AL)', + 'ff6d4a4a60a1c5e032e7fb7d9c91f817:211111' => 'Pentax K10D (AM)', + 'dca5476d81d0ceca97f480fecd09b23c:211111' => 'Pentax K10D (AN)', + 'efbe7634221900639b3c072395c61bef:211111' => 'Pentax K10D (AO)', + 'f4dba22dd251350a21f8122f2777e7b0:211111' => 'Pentax K10D (AP)', + 'f90135fcff0e1720dda86e9ad718c0c0:211111' => 'Pentax K10D (AQ)', + 'fa3d7753be7b329ab9961657cbc65386:211111' => 'Pentax K10D (AR)', + 'fa8720d025f2a164542b6a8e31112991:211111' => 'Pentax K10D (AS)', + '2941d12ef34511d96b659ba30d682cd1:211111' => 'Pentax K10D (AT)', + '2aa82b6717f1cdfe6b6d60a4486b5671:211111' => 'Pentax K10D (AU)', + '2aa82b6717f1cdfe6b6d60a4486b5671:211111' => 'Pentax K10D (AV)', + '3527616df6f26a3ab36b80a8d885fc07:211111' => 'Pentax K10D (AW)', + '3527616df6f26a3ab36b80a8d885fc07:211111' => 'Pentax K10D (AX)', + '3527616df6f26a3ab36b80a8d885fc07:211111' => 'Pentax K10D (AY)', + '5ea9e766888399a41f3f1a3c5c15cd90:211111' => 'Pentax K10D (AZ)', + 'f83d978290d0699054eabb0a7811c7a4:211111' => 'Pentax K10D (BA)', + 'f83d978290d0699054eabb0a7811c7a4:211111' => 'Pentax K10D (BB)', + 'fa8720d025f2a164542b6a8e31112991:211111' => 'Pentax K10D/K100D', + '586b40c7d4b95e11309636703e81fbe9:211111' => 'Pentax K20D/K200D/Optio 230, Best; Canon EOS 10D/300D/350D, Fine', + 'b73481179da895f3b9ecea1737054a9c:211111' => 'Pentax K20D, Best (B)', + 'b8fce00f93108e7db57a012c51fad341:211111' => 'Pentax K20D, Best (C)', + '5ee766b90badc8fed5a5386e78a80783:211111' => 'Pentax *istDS, Good (edit in camera)', + 'd528fac9b63536ff52041745945dcb09:211111' => 'Pentax *istDS, Better (edit in camera)', + 'd6390cc36d2f03c1d2dd13d6910ca46b:211111' => 'Pentax *istDS, Best (edit in camera)', + 'dc149d41f08d16cb9d52a5bdd487a67e:121111' => 'Pentax *istD/K100Dsuper/Optio300GS, Best', + 'e10030f09a14acdd647eff13c0bf333a:211111' => 'Pentax *istD/DS/DS2/K100D/Optio330GS/33L, Best', + 'ef0cd1902fb1afe284468a67eaffd078:211111' => 'Pentax *istDS/K100D/K100Dsuper, Good', + 'f1262dfcada6e6c2cd4b9fa7e881233b:211111' => 'Pentax *istDL/DS, Better', + 'f3235a7d187d083b7b7ead949653f730:211111' => 'Pentax K20D/K200D, Best (D)', + '6686cddc46088f0987e7476861fbfb47:211111' => 'Pentax K2000, Best (A)', + '5910b8431fdd8ab93ce258f366c4b867:211111' => 'Pentax K2000, Best (B)', + 'c8bfcc60aeec937300405f59373be4ef:211111' => 'Pentax K2000, Best (C)', + '689a0e3511f2aea75637f46e6af9fd9f:211111' => 'Pentax Optio A40, Best (edit in camera)', + '8d14598ae9cc1b7f5357424a19d05a71:211111' => 'Pentax Optio A30/A40, Good', + 'a4cb8a3a000484b37c4373cde1170091:211111' => 'Pentax Optio A30/A40/S10/S12, Best', + '0ac5cb651c496369d0e924ae070b7c53:211111' => 'Pentax Optio A40, Better (edit in camera)', + '1068be028c278941bd8abf3b0021655e:211111' => 'Pentax Optio A40, Good (edit in camera)', + '336eeeb78e386bf66fe6325b4a0fcfa6:211111' => 'Pentax Optio A40, Better', + 'ae2efaf1a96a4fdcfa9003b9aa963ae4:221111' => 'Pentax Optio 330, Best (vertical)', + '3803d7f6b7aed64c658c21dbb2bc0797:221111' => 'Pentax Optio 330, Best', + '353bf09900feb764885329e7bebfd95e:211111' => 'Pentax Optio 330GS, Good', + '6c2bc41a4b6ad1e20655ffcc0dfd2c41:221111' => 'Pentax Optio 330RS, Fine', + 'e9206045838e9f5f9bd207744254e96d:221111' => 'Pentax Optio 430, Best', + '759fb7011e13fa5f975bb668f5b94d8b:211111' => 'Pentax Optio 550/750Z/M60/X, Best', + '637103ef9d8e84f8345f8218f158fc3c:211111' => 'Pentax Optio 550/M10/T30/W30, Best', + '23f2a5970523c5f7fd2ab7fa3b09dff9:211111' => 'Pentax Optio 550/555/M20/M30/W10/W20, Best', + '8d2f02a07bad6b5cec48466036fef319:121111' => 'Pentax Optio 550, Better', + '27297008a89ee49804f0859ea6435878:211111' => 'Pentax Optio MX, Best', + '6cfe3833aadd87487afc11129d8cb2aa:221111' => 'Pentax Optio S, Better', + 'fcef35c97674aeb26c67e539b726057f:221111' => 'Pentax Optio S, Best (A)', + '13b2644cdad6f75767667e8ea5c218a3:221111' => 'Pentax Optio S, Best (B)', + '310b70bc4fac884f64a07040a4b87468:221111' => 'Pentax Optio S, Best (C)', + 'aa05fbe795d86a1063c55865e8613536:221111' => 'Pentax Optio S, Best (D)', + 'd57ac6956e4fe86c386f0eef00a5e021:221111' => 'Pentax Optio S, Best (E)', + '28782f5ee24fe983fe90b9438b39ae2e:221111' => 'Pentax Optio S4, Best', + '804bd63907214e005f01fb65a2bb00e6:221111' => 'Pentax Optio S4i, Best', + '84285f5b3248884488e5142b8c7210e2:211111' => 'Pentax Optio S6, Good', + 'e97694f0093de13987a335e131b30eb0:221111' => 'Pentax Optio SVi, Best', + '037d043c8a8d5332c28d59f71a0dcfd2:211111' => 'Pentax Optio E35', + 'dd8ad8ce688c4248f924022c38d3228c:211111' => 'Pentax Optio 43WR, Good', + 'e55e0c1adbbca8b9d100881248050eb5:211111' => 'Pentax Optio 43WR, Better', + '7770d784d852b3333f9213713e481125:211111' => 'Pentax Optio 450, Best', + '61d311bde22762ae0e88b768e835eced:211111' => 'Pentax Optio 33WR/M50, Best', + 'bc066ff3fbea8a290c6f9882687945e0:221111' => 'Pentax Optio 430RS, Fine', + 'b6bd9f956309a20e3a56294077536391:211111' => 'Pentax Optio A10/S7, Best', + 'a4ecd6b77f06671530942783c3595aca:211111' => 'Pentax Optio A20, Best', + '40f66b0a209f24716320636b776dda94:211111' => 'Pentax Optio E30/E40, Best', + '59a868b3d11d9cdc87859c02757e13bb:211111' => 'Pentax Optio E50, Best', + '9570584f017ed2c4f0fb91782b51faa9:211111' => 'Pentax Optio M40/Z10, Best', + '5a74f09fb2586fa000c42e98e3b9f2d8:211111' => 'Pentax Optio T10', + '0867bdf854d1fbb141411de518a66ba6:211111' => 'Pentax Optio T20 (A)', + 'f74b3853185743c111ccb13e6febdc21:211111' => 'Pentax Optio T20 (B)', + 'b6640d3879f9922708d23e6adb3d61c9:211111' => 'Pentax Optio V10, Best', + '253467dc35dfbb32cb3d619fc635d689:211111' => 'Pentax Optio V20/W60, Best', + + # Sony + '6bd350bf5df27ed1b5bf1d83fa9d021f:211111' => 'Sony DSLR-A700, Fine', +); + +#------------------------------------------------------------------------------ +# Estimate JPEG quality from quantization tables (ref 3) +# Inputs: 0) 1) DQT segments array ref +# Returns: JPEG quality, or undefined if it can't be calculated +sub EstimateQuality($) +{ + local $_; + my $dqtList = shift; + my ($i, $dqt, @qtbl, $quality, @hash, @sums); + + # unpack DQT segments and sum quantization tables + my $sum=0; +DQT: foreach $dqt (@$dqtList) { + next unless defined $dqt; + for ($i=1; $i+64<=length($dqt); $i+=65) { + my @qt = unpack("x$i C64", $dqt); + $sum += $_ foreach @qt; + push @qtbl, \@qt; + last DQT if @qtbl >= 4; + } + } + return undef unless @qtbl; + + my $qval = $qtbl[0][2] + $qtbl[0][53]; + if (@qtbl > 1) { + # color JPEG + $qval += $qtbl[1][0] + $qtbl[1][63]; + @hash =( + 1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645, + 632, 623, 613, 607, 600, 594, 589, 585, 581, 571, + 555, 542, 529, 514, 494, 474, 457, 439, 424, 410, + 397, 386, 373, 364, 351, 341, 334, 324, 317, 309, + 299, 294, 287, 279, 274, 267, 262, 257, 251, 247, + 243, 237, 232, 227, 222, 217, 213, 207, 202, 198, + 192, 188, 183, 177, 173, 168, 163, 157, 153, 148, + 143, 139, 132, 128, 125, 119, 115, 108, 104, 99, + 94, 90, 84, 79, 74, 70, 64, 59, 55, 49, + 45, 40, 34, 30, 25, 20, 15, 11, 6, 4 + ); + @sums = ( + 32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104, 27670, + 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946, 23572, 22846, + 21801, 20842, 19949, 19121, 18386, 17651, 16998, 16349, 15800, 15247, + 14783, 14321, 13859, 13535, 13081, 12702, 12423, 12056, 11779, 11513, + 11135, 10955, 10676, 10392, 10208, 9928, 9747, 9564, 9369, 9193, + 9017, 8822, 8639, 8458, 8270, 8084, 7896, 7710, 7527, 7347, + 7156, 6977, 6788, 6607, 6422, 6236, 6054, 5867, 5684, 5495, + 5305, 5128, 4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, + 3509, 3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846, + 1666, 1483, 1297, 1109, 927, 735, 554, 375, 201, 128 + ); + } else { + # greyscale JPEG + @hash = ( + 510, 505, 422, 380, 355, 338, 326, 318, 311, 305, + 300, 297, 293, 291, 288, 286, 284, 283, 281, 280, + 279, 278, 277, 273, 262, 251, 243, 233, 225, 218, + 211, 205, 198, 193, 186, 181, 177, 172, 168, 164, + 158, 156, 152, 148, 145, 142, 139, 136, 133, 131, + 129, 126, 123, 120, 118, 115, 113, 110, 107, 105, + 102, 100, 97, 94, 92, 89, 87, 83, 81, 79, + 76, 74, 70, 68, 66, 63, 61, 57, 55, 52, + 50, 48, 44, 42, 39, 37, 34, 31, 29, 26, + 24, 21, 18, 16, 13, 11, 8, 6, 3, 2 + ); + @sums = ( + 16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859, 12560, + 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679, 9368, 9056, + 8680, 8331, 7995, 7668, 7376, 7084, 6823, 6562, 6345, 6125, + 5939, 5756, 5571, 5421, 5240, 5086, 4976, 4829, 4719, 4616, + 4463, 4393, 4280, 4166, 4092, 3980, 3909, 3835, 3755, 3688, + 3621, 3541, 3467, 3396, 3323, 3247, 3170, 3096, 3021, 2952, + 2874, 2804, 2727, 2657, 2583, 2509, 2437, 2362, 2290, 2211, + 2136, 2068, 1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, + 1398, 1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736, + 667, 592, 518, 441, 369, 292, 221, 151, 86, 64 + ); + } + for ($i=0; $i<100; ++$i) { + next if $qval < $hash[$i] and $sum < $sums[$i]; + $quality = $i + 1 if ($qval <= $hash[$i] and $sum <= $sums[$i]) or $i >= 50; + last; + } + return $quality; +} + +#------------------------------------------------------------------------------ +# Calculate JPEGDigest and/or JPEGQualityEstimate +# Inputs: 0) ExifTool object ref, 1) DQT segments array ref, 2) subsampling string +sub Calculate($$$) +{ + my ($et, $dqtList, $subSampling) = @_; + + # estimate JPEG quality if requested + my $all = ($$et{OPTIONS}{RequestAll} and $$et{OPTIONS}{RequestAll} > 2); + if ($all or $$et{REQ_TAG_LOOKUP}{jpegqualityestimate}) { + my $quality = EstimateQuality($dqtList); + $quality = '<unknown>' unless defined $quality; + $et->FoundTag('JPEGQualityEstimate', $quality); + } + return unless ($all or $$et{REQ_TAG_LOOKUP}{jpegdigest}) and $subSampling; + + unless (eval { require Digest::MD5 }) { + $et->Warn('Digest::MD5 must be installed to calculate JPEGDigest'); + return; + } + # create a string of DQT tables (in indexed order), separated by zero bytes + my $dqt = ''; + my $dat; + foreach $dat (@$dqtList) { + next unless $dat; + $dqt .= "\0" if $dqt; + $dqt .= $dat; + } + # generate ASCII-hex string of DQT MD5 digest + my $md5 = unpack 'H*', Digest::MD5::md5($dqt); + + # add sub-sampling string unless we get a match without it + $md5 .= ':' . $subSampling unless $PrintConv{$md5}; + + # add print conversion for JPEGDigest dynamically so it doesn't + # bulk up the documentation and slow down loading unnecessarily + $Image::ExifTool::Extra{JPEGDigest}{PrintConv} = \%PrintConv; + $et->FoundTag('JPEGDigest', $md5); +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::JPEGDigest - Calculate JPEGDigest and JPEGQualityEstimate + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains a lookup for values of the JPEG DQT digest, allowing +some image identification from JPEG data alone. It also calculates an +estimated JPEG quality if requested. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://github.com/ImageMagick/ImageMagick/blob/master/coders/jpeg.c> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke for most of the work that went into this module, and +to Franz Buchinger for the values he added. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/JPEG Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/JSON.pm b/ExifTool/lib/Image/ExifTool/JSON.pm new file mode 100644 index 0000000..ebc9f56 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/JSON.pm @@ -0,0 +1,190 @@ +#------------------------------------------------------------------------------ +# File: JSON.pm +# +# Description: Read JSON files +# +# Notes: Set ExifTool MissingTagValue to "null" to ignore JSON nulls +# +# Revisions: 2017/03/13 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::JSON; +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Import; + +$VERSION = '1.05'; + +sub ProcessJSON($$); +sub ProcessTag($$$$%); + +%Image::ExifTool::JSON::Main = ( + GROUPS => { 0 => 'JSON', 1 => 'JSON', 2 => 'Other' }, + VARS => { NO_ID => 1 }, + PROCESS_PROC => \&ProcessJSON, + NOTES => q{ + Other than a few tags in the table below, JSON tags have not been + pre-defined. However, ExifTool will read any existing tags from basic + JSON-formatted files. + }, + # ON1 settings tags + ON1_SettingsData => { + RawConv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::DecodeBase64($val); + }, + SubDirectory => { TagTable => 'Image::ExifTool::PLIST::Main' }, + }, + ON1_SettingsMetadataCreated => { Groups => { 2 => 'Time' } }, + ON1_SettingsMetadataModified => { Groups => { 2 => 'Time' } }, + ON1_SettingsMetadataName => { }, + ON1_SettingsMetadataPluginID => { }, + ON1_SettingsMetadataTimestamp => { Groups => { 2 => 'Time' } }, + ON1_SettingsMetadataUsage => { }, + ON1_SettingsMetadataVisibleToUser=>{ }, +); + +#------------------------------------------------------------------------------ +# Store a tag value +# Inputs: 0) ExifTool ref, 1) tag table, 2) tag ID, 3) value, 4) tagInfo flags +sub FoundTag($$$$%) +{ + my ($et, $tagTablePtr, $tag, $val, %flags) = @_; + + # special case to reformat ON1 tag names + if ($tag =~ s/^settings\w{8}-\w{4}-\w{4}-\w{4}-\w{12}(Data|Metadata.+)$/ON1_Settings$1/) { + $et->OverrideFileType('ONP','application/on1') if $$et{FILE_TYPE} eq 'JSON'; + } + + # avoid conflict with special table entries + $tag .= '!' if $Image::ExifTool::specialTags{$tag}; + + AddTagToTable($tagTablePtr, $tag, { + Name => Image::ExifTool::MakeTagName($tag), + %flags, + Temporary => 1, + }) unless $$tagTablePtr{$tag}; + + $et->HandleTag($tagTablePtr, $tag, $val); +} + +#------------------------------------------------------------------------------ +# Process a JSON tag +# Inputs: 0) ExifTool ref, 1) tag table, 2) tag ID, 3) value, 4) tagInfo flags +# - expands structures into flattened tags as required +sub ProcessTag($$$$%) +{ + local $_; + my ($et, $tagTablePtr, $tag, $val, %flags) = @_; + + if (ref $val eq 'HASH') { + if ($et->Options('Struct')) { + FoundTag($et, $tagTablePtr, $tag, $val, %flags, Struct => 1); + return unless $et->Options('Struct') > 1; + } + # support hashes with ordered keys + my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val; + foreach (@keys) { + my $tg = $tag . ((/^\d/ and $tag =~ /\d$/) ? '_' : '') . ucfirst; + $tg =~ s/([^a-zA-Z])([a-z])/$1\U$2/g; + ProcessTag($et, $tagTablePtr, $tg, $$val{$_}, %flags, Flat => 1); + } + } elsif (ref $val eq 'ARRAY') { + foreach (@$val) { + ProcessTag($et, $tagTablePtr, $tag, $_, %flags, List => 1); + } + } elsif (defined $val) { + FoundTag($et, $tagTablePtr, $tag, $val, %flags); + } +} + +#------------------------------------------------------------------------------ +# Extract meta information from a JSON file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a recognized JSON file +sub ProcessJSON($$) +{ + local $_; + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $structOpt = $et->Options('Struct'); + my (%database, $key, $tag, $dataPt); + + unless ($raf) { + $dataPt = $$dirInfo{DataPt}; + if ($$dirInfo{DirStart} or ($$dirInfo{DirLen} and $$dirInfo{DirLen} ne length($$dataPt))) { + my $buff = substr(${$$dirInfo{DataPt}}, $$dirInfo{DirStart}, $$dirInfo{DirLen}); + $dataPt = \$buff; + } + $raf = new File::RandomAccess($dataPt); + # extract as a block if requested + my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : ''; + my $blockExtract = $et->Options('BlockExtract'); + if ($blockName and ($blockExtract or $$et{REQ_TAG_LOOKUP}{lc $blockName} or + ($$et{TAGS_FROM_FILE} and not $$et{EXCL_TAG_LOOKUP}{lc $blockName}))) + { + $et->FoundTag($$dirInfo{BlockInfo}, $$dataPt); + return 1 if $blockExtract and $blockExtract > 1; + } + $et->VerboseDir('JSON'); + } + + # read information from JSON file into database structure + my $err = Image::ExifTool::Import::ReadJSON($raf, \%database, + $et->Options('MissingTagValue'), $et->Options('Charset')); + + return 0 if $err or not %database; + + $et->SetFileType() unless $dataPt; + + my $tagTablePtr = GetTagTable('Image::ExifTool::JSON::Main'); + + # remove any old tag definitions in case they change flags + foreach $key (TagTableKeys($tagTablePtr)) { + delete $$tagTablePtr{$key} if $$tagTablePtr{$key}{Temporary}; + } + + # extract tags from JSON database + foreach $key (sort keys %database) { + foreach $tag (sort keys %{$database{$key}}) { + my $val = $database{$key}{$tag}; + # (ignore SourceFile if generated automatically by ReadJSON) + next if $tag eq 'SourceFile' and defined $val and $val eq '*'; + ProcessTag($et, $tagTablePtr, $tag, $val); + } + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::JSON - Read JSON files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool read +information from JSON files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/JSON Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/JVC.pm b/ExifTool/lib/Image/ExifTool/JVC.pm new file mode 100644 index 0000000..372edac --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/JVC.pm @@ -0,0 +1,130 @@ +#------------------------------------------------------------------------------ +# File: JVC.pm +# +# Description: JVC EXIF maker notes tags +# +# Revisions: 12/21/2005 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::JVC; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.03'; + +sub ProcessJVCText($$$); + +# JVC EXIF-based maker notes +%Image::ExifTool::JVC::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'JVC EXIF maker note tags.', + #0x0001 - almost always '2', but '3' for GR-DV700 samples + 0x0002 => { #PH + Name => 'CPUVersions', + # remove trailing nulls/spaces and split at remaining nulls/spaces + ValueConv => '$_=$val; s/(\s*\0)+$//; s/(\s*\0)+/, /g; $_', + }, + 0x0003 => { #PH + Name => 'Quality', + PrintConv => { + 0 => 'Low', + 1 => 'Normal', + 2 => 'Fine', + }, + }, +); + +# JVC text-based maker notes +%Image::ExifTool::JVC::Text = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessJVCText, + NOTES => 'JVC/Victor text-based maker note tags.', + VER => 'MakerNoteVersion', #PH + QTY => { #PH + Name => 'Quality', + PrintConv => { + STND => 'Normal', + STD => 'Normal', + FINE => 'Fine', + }, + }, +); + +#------------------------------------------------------------------------------ +# Process JVC text-based maker notes +# Inputs: 0) ExifTool object reference +# 1) Reference to directory information hash +# 2) Pointer to tag table for this directory +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessJVCText($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dataLen = $$dirInfo{DataLen}; + my $dirLen = $$dirInfo{DirLen} || $dataLen - $dirStart; + my $verbose = $et->Options('Verbose'); + + my $data = substr($$dataPt, $dirStart, $dirLen); + # validate text maker notes + unless ($data =~ /^VER:/) { + $et->Warn('Bad JVC text maker notes'); + return 0; + } + while ($data =~ m/([A-Z]+):(.{3,4})/sg) { + my ($tag, $val) = ($1, $2); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + $et->VerboseInfo($tag, $tagInfo, + Table => $tagTablePtr, + Value => $val, + ) if $verbose; + unless ($tagInfo) { + next unless $$et{OPTIONS}{Unknown}; + $tagInfo = { + Name => "JVC_Text_$tag", + Unknown => 1, + PrintConv => 'length($val) > 60 ? substr($val,0,55) . "[...]" : $val', + }; + # add tag information to table + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + $et->FoundTag($tagInfo, $val); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::JVC - JVC EXIF maker notes tags + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains routines used by Image::ExifTool to interpret JVC maker +notes. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/JVC Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Jpeg2000.pm b/ExifTool/lib/Image/ExifTool/Jpeg2000.pm new file mode 100644 index 0000000..af0a1a7 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Jpeg2000.pm @@ -0,0 +1,1629 @@ +#------------------------------------------------------------------------------ +# File: Jpeg2000.pm +# +# Description: Read JPEG 2000 meta information +# +# Revisions: 02/11/2005 - P. Harvey Created +# 06/22/2007 - PH Added write support (EXIF, IPTC and XMP only) +# +# References: 1) http://www.jpeg.org/public/fcd15444-2.pdf +# 2) ftp://ftp.remotesensing.org/jpeg2000/fcd15444-1.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::Jpeg2000; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.36'; + +sub ProcessJpeg2000Box($$$); +sub ProcessJUMD($$$); + +my %resolutionUnit = ( + -3 => 'km', + -2 => '100 m', + -1 => '10 m', + 0 => 'm', + 1 => '10 cm', + 2 => 'cm', + 3 => 'mm', + 4 => '0.1 mm', + 5 => '0.01 mm', + 6 => 'um', +); + +# top-level boxes containing image data +my %isImageData = ( jp2c=>1, jbrd=>1, jxlp=>1, jxlc=>1 ); + +# map of where information is written in JPEG2000 image +my %jp2Map = ( + IPTC => 'UUID-IPTC', + IFD0 => 'UUID-EXIF', + XMP => 'UUID-XMP', + 'UUID-IPTC' => 'JP2', + 'UUID-EXIF' => 'JP2', + 'UUID-XMP' => 'JP2', + jp2h => 'JP2', + colr => 'jp2h', + ICC_Profile => 'colr', + IFD1 => 'IFD0', + EXIF => 'IFD0', # to write EXIF as a block + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', +); + +# map of where information is written in a JXL image +my %jxlMap = ( + IFD0 => 'Exif', + XMP => 'xml ', + 'Exif' => 'JP2', + IFD1 => 'IFD0', + EXIF => 'IFD0', # to write EXIF as a block + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', +); + +# UUID's for writable UUID directories (by tag name) +my %uuid = ( + 'UUID-EXIF' => 'JpgTiffExif->JP2', + 'UUID-EXIF2' => '', # (flags a warning when writing) + 'UUID-EXIF_bad' => '0', # (flags a warning when reading and writing) + 'UUID-IPTC' => "\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\xba\xf1\xa3\xe0\x97\xad\x38", + 'UUID-XMP' => "\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac", + # (can't yet write GeoJP2 information) + # 'UUID-GeoJP2' => "\xb1\x4b\xf8\xbd\x08\x3d\x4b\x43\xa5\xae\x8c\xd7\xd5\xa6\xce\x03", +); + +# JPEG2000 codestream markers (ref ISO/IEC FCD15444-1/2) +my %j2cMarker = ( + 0x4f => 'SOC', # start of codestream + 0x51 => 'SIZ', # image and tile size + 0x52 => 'COD', # coding style default + 0x53 => 'COC', # coding style component + 0x55 => 'TLM', # tile-part lengths + 0x57 => 'PLM', # packet length, main header + 0x58 => 'PLT', # packet length, tile-part header + 0x5c => 'QCD', # quantization default + 0x5d => 'QCC', # quantization component + 0x5e => 'RGN', # region of interest + 0x5f => 'POD', # progression order default + 0x60 => 'PPM', # packed packet headers, main + 0x61 => 'PPT', # packed packet headers, tile-part + 0x63 => 'CRG', # component registration + 0x64 => 'CME', # comment and extension + 0x90 => 'SOT', # start of tile-part + 0x91 => 'SOP', # start of packet + 0x92 => 'EPH', # end of packet header + 0x93 => 'SOD', # start of data + # extensions (ref ISO/IEC FCD15444-2) + 0x70 => 'DCO', # variable DC offset + 0x71 => 'VMS', # visual masking + 0x72 => 'DFS', # downsampling factor style + 0x73 => 'ADS', # arbitrary decomposition style + # 0x72 => 'ATK', # arbitrary transformation kernels ? + 0x78 => 'CBD', # component bit depth + 0x74 => 'MCT', # multiple component transformation definition + 0x75 => 'MCC', # multiple component collection + 0x77 => 'MIC', # multiple component intermediate collection + 0x76 => 'NLT', # non-linearity point transformation +); + +# JPEG 2000 "box" (ie. atom) names +# Note: only tags with a defined "Format" are extracted +%Image::ExifTool::Jpeg2000::Main = ( + GROUPS => { 2 => 'Image' }, + PROCESS_PROC => \&ProcessJpeg2000Box, + WRITE_PROC => \&ProcessJpeg2000Box, + PREFERRED => 1, # always add these tags when writing + NOTES => q{ + The tags below are found in JPEG 2000 images and the JUMBF metadata in JPEG + images, but not all of these are extracted. Note that ExifTool currently + writes only EXIF, IPTC and XMP tags in Jpeg2000 images, and EXIF and XMP in + JXL images. ExifTool will read/write Brotli-compressed EXIF and XMP in JXL + images, but the API L<Compress|../ExifTool.html#Compress> option must be set to create new EXIF and XMP + in compressed format. + }, +# +# NOTE: ONLY TAGS WITH "Format" DEFINED ARE EXTRACTED! +# + 'jP ' => 'JP2Signature', # (ref 1) + "jP\x1a\x1a" => 'JP2Signature', # (ref 2) + prfl => 'Profile', + ftyp => { + Name => 'FileType', + SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::FileType' }, + }, + rreq => 'ReaderRequirements', + jp2h => { + Name => 'JP2Header', + SubDirectory => { }, + }, + # JP2Header sub boxes... + ihdr => { + Name => 'ImageHeader', + SubDirectory => { + TagTable => 'Image::ExifTool::Jpeg2000::ImageHeader', + }, + }, + bpcc => 'BitsPerComponent', + colr => { + Name => 'ColorSpecification', + SubDirectory => { + TagTable => 'Image::ExifTool::Jpeg2000::ColorSpec', + }, + }, + pclr => 'Palette', + cdef => 'ComponentDefinition', + 'res '=> { + Name => 'Resolution', + SubDirectory => { }, + }, + # Resolution sub boxes... + resc => { + Name => 'CaptureResolution', + SubDirectory => { + TagTable => 'Image::ExifTool::Jpeg2000::CaptureResolution', + }, + }, + resd => { + Name => 'DisplayResolution', + SubDirectory => { + TagTable => 'Image::ExifTool::Jpeg2000::DisplayResolution', + }, + }, + jpch => { + Name => 'CodestreamHeader', + SubDirectory => { }, + }, + # CodestreamHeader sub boxes... + 'lbl '=> { + Name => 'Label', + Format => 'string', + }, + cmap => 'ComponentMapping', + roid => 'ROIDescription', + jplh => { + Name => 'CompositingLayerHeader', + SubDirectory => { }, + }, + # CompositingLayerHeader sub boxes... + cgrp => 'ColorGroup', + opct => 'Opacity', + creg => 'CodestreamRegistration', + dtbl => 'DataReference', + ftbl => { + Name => 'FragmentTable', + Subdirectory => { }, + }, + # FragmentTable sub boxes... + flst => 'FragmentList', + cref => 'Cross-Reference', + mdat => 'MediaData', + comp => 'Composition', + copt => 'CompositionOptions', + inst => 'InstructionSet', + asoc => { + Name => 'Association', + SubDirectory => { }, + }, + # (Association box may contain any other sub-box) + nlst => 'NumberList', + bfil => 'BinaryFilter', + drep => 'DesiredReproductions', + # DesiredReproductions sub boxes... + gtso => 'GraphicsTechnologyStandardOutput', + chck => 'DigitalSignature', + mp7b => 'MPEG7Binary', + free => 'Free', + jp2c => [{ + Name => 'ContiguousCodestream', + Condition => 'not $$self{jumd_level}', + },{ + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Format => 'undef', + Binary => 1, + }], + jp2i => { + Name => 'IntellectualProperty', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, + 'xml '=> [{ + Name => 'XML', + Condition => 'not $$self{IsJXL}', + Writable => 'undef', + Flags => [ 'Binary', 'Protected', 'BlockExtract' ], + List => 1, + Notes => q{ + by default, the XML data in this tag is parsed using the ExifTool XMP module + to to allow individual tags to be accessed when reading, but it may also be + extracted as a block via the "XML" tag, which is also how this tag is + written and copied. It may also be extracted as a block by setting the API + BlockExtract option. This is a List-type tag because multiple XML blocks + may exist + }, + # (note: extracting as a block was broken in 11.04, and finally fixed in 12.14) + SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' }, + },{ + Name => 'XMP', + Notes => 'used for XMP in JPEG XL files', + # NOTE: the hacked code relies on this being at index 1 of the tagInfo list! + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }], + uuid => [ + { + Name => 'UUID-EXIF', + # (this is the EXIF that we create in JP2) + Condition => '$$valPt=~/^JpgTiffExif->JP2(?!Exif\0\0)/', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + DirName => 'EXIF', + Start => '$valuePtr + 16', + }, + }, + { + Name => 'UUID-EXIF2', + # written by Photoshop 7.01+Adobe JPEG2000-plugin v1.5 + Condition => '$$valPt=~/^\x05\x37\xcd\xab\x9d\x0c\x44\x31\xa7\x2a\xfa\x56\x1f\x2a\x11\x3e/', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + DirName => 'EXIF', + Start => '$valuePtr + 16', + }, + }, + { + Name => 'UUID-EXIF_bad', + # written by Digikam + Condition => '$$valPt=~/^JpgTiffExif->JP2/', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + DirName => 'EXIF', + Start => '$valuePtr + 22', + }, + }, + { + Name => 'UUID-IPTC', + # (this is the IPTC that we create in JP2) + Condition => '$$valPt=~/^\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\xba\xf1\xa3\xe0\x97\xad\x38/', + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::Main', + Start => '$valuePtr + 16', + }, + }, + { + Name => 'UUID-IPTC2', + # written by Photoshop 7.01+Adobe JPEG2000-plugin v1.5 + Condition => '$$valPt=~/^\x09\xa1\x4e\x97\xc0\xb4\x42\xe0\xbe\xbf\x36\xdf\x6f\x0c\xe3\x6f/', + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::Main', + Start => '$valuePtr + 16', + }, + }, + { + Name => 'UUID-XMP', + # ref http://www.adobe.com/products/xmp/pdfs/xmpspec.pdf + Condition => '$$valPt=~/^\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac/', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + Start => '$valuePtr + 16', + }, + }, + { + Name => 'UUID-GeoJP2', + # ref http://www.remotesensing.org/jpeg2000/ + Condition => '$$valPt=~/^\xb1\x4b\xf8\xbd\x08\x3d\x4b\x43\xa5\xae\x8c\xd7\xd5\xa6\xce\x03/', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + Start => '$valuePtr + 16', + }, + }, + { + Name => 'UUID-Photoshop', + # written by Photoshop 7.01+Adobe JPEG2000-plugin v1.5 + Condition => '$$valPt=~/^\x2c\x4c\x01\x00\x85\x04\x40\xb9\xa0\x3e\x56\x21\x48\xd6\xdf\xeb/', + SubDirectory => { + TagTable => 'Image::ExifTool::Photoshop::Main', + Start => '$valuePtr + 16', + }, + }, + { + Name => 'UUID-Signature', # (seen in JUMB data of JPEG images) + # (may be able to remove this when JUMBF specification is finalized) + Condition => '$$valPt=~/^casg\x00\x11\x00\x10\x80\x00\x00\xaa\x00\x38\x9b\x71/', + Format => 'undef', + ValueConv => 'substr($val,16)', + }, + { + Name => 'UUID-C2PAClaimSignature', # (seen in incorrectly-formatted JUMB data of JPEG images) + # (may be able to remove this when JUMBF specification is finalized) + Condition => '$$valPt=~/^c2cs\x00\x11\x00\x10\x80\x00\x00\xaa\x00\x38\x9b\x71/', + SubDirectory => { + TagTable => 'Image::ExifTool::CBOR::Main', + Start => '$valuePtr + 16', + }, + }, + { + Name => 'UUID-Unknown', + }, + # also written by Adobe JPEG2000 plugin v1.5: + # 3a 0d 02 18 0a e9 41 15 b3 76 4b ca 41 ce 0e 71 - 1 byte (01) + # 47 c9 2c cc d1 a1 45 81 b9 04 38 bb 54 67 71 3b - 1 byte (01) + # bc 45 a7 74 dd 50 4e c6 a9 f6 f3 a1 37 f4 7e 90 - 4 bytes (00 00 00 32) + # d7 c8 c5 ef 95 1f 43 b2 87 57 04 25 00 f5 38 e8 - 4 bytes (00 00 00 32) + ], + uinf => { + Name => 'UUIDInfo', + SubDirectory => { }, + }, + # UUIDInfo sub boxes... + ulst => 'UUIDList', + 'url '=> { + Name => 'URL', + Format => 'string', + }, + # JUMBF boxes (ref https://github.com/thorfdbg/codestream-parser) + jumd => { + Name => 'JUMBFDescr', + SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::JUMD' }, + }, + jumb => { + Name => 'JUMBFBox', + SubDirectory => { + TagTable => 'Image::ExifTool::Jpeg2000::Main', + ProcessProc => \&ProcessJUMB, + }, + }, + json => { + Name => 'JSONData', + Flags => [ 'Binary', 'Protected', 'BlockExtract' ], + Notes => q{ + by default, data in this tag is parsed using the ExifTool JSON module to to + allow individual tags to be accessed when reading, but it may also be + extracted as a block via the "JSONData" tag or by setting the API + BlockExtract option + }, + SubDirectory => { TagTable => 'Image::ExifTool::JSON::Main' }, + }, + cbor => { + Name => 'CBORData', + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::CBOR::Main' }, + }, + bfdb => { # used in JUMBF (see # (used when tag is renamed according to JUMDLabel) + Name => 'BinaryDataType', + Notes => 'JUMBF, MIME type and optional file name', + Format => 'undef', + # (ignore "toggles" byte and just extract MIME type and file name) + ValueConv => '$_=substr($val,1); s/\0+$//; s/\0/, /; $_', + JUMBF_Suffix => 'Type', # (used when tag is renamed according to JUMDLabel) + }, + bidb => { # used in JUMBF + Name => 'BinaryData', + Notes => 'JUMBF', + Groups => { 2 => 'Preview' }, + Format => 'undef', + Binary => 1, + JUMBF_Suffix => 'Data', # (used when tag is renamed according to JUMDLabel) + }, + c2sh => { # used in JUMBF + Name => 'C2PASaltHash', + Format => 'undef', + ValueConv => 'unpack("H*",$val)', + JUMBF_Suffix => 'Salt', # (used when tag is renamed according to JUMDLabel) + }, +# +# stuff seen in JPEG XL images: +# + # jbrd - JPEG Bitstream Reconstruction Data (allows lossless conversion back to original JPG) + jxlc => { + Name => 'JXLCodestream', + Format => 'undef', + Notes => q{ + Codestream in JPEG XL image. Currently processed only to determine + ImageSize + }, + RawConv => 'Image::ExifTool::Jpeg2000::ProcessJXLCodestream($self,\$val); undef', + }, + jxlp => { + Name => 'PartialJXLCodestream', + Format => 'undef', + Notes => q{ + Partial codestreams in JPEG XL image. Currently processed only to determine + ImageSize + }, + RawConv => 'Image::ExifTool::Jpeg2000::ProcessJXLCodestream($self,\$val); undef', + }, + Exif => { + Name => 'EXIF', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + DirName => 'EXIF', + Start => '$valuePtr + 4 + (length($$dataPt)-$valuePtr > 4 ? unpack("N", $$dataPt) : 0)', + }, + }, + hrgm => { + Name => 'GainMapImage', + Groups => { 2 => 'Preview' }, + Format => 'undef', + Binary => 1, + }, + brob => [{ # Brotli-encoded metadata (see https://libjxl.readthedocs.io/en/latest/api_decoder.html) + Name => 'BrotliXMP', + Condition => '$$valPt =~ /^xml /i', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + ProcessProc => \&ProcessBrotli, + WriteProc => \&ProcessBrotli, + # (don't set DirName to 'XMP' because this would enable a block write of raw XMP) + }, + },{ + Name => 'BrotliEXIF', + Condition => '$$valPt =~ /^exif/i', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&ProcessBrotli, + WriteProc => \&ProcessBrotli, + # (don't set DirName to 'EXIF' because this would enable a block write of raw EXIF) + }, + },{ + Name => 'BrotliJUMB', + Condition => '$$valPt =~ /^jumb/i', + SubDirectory => { + TagTable => 'Image::ExifTool::Jpeg2000::Main', + ProcessProc => \&ProcessBrotli, + }, + }], +); + +%Image::ExifTool::Jpeg2000::ImageHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'ImageHeight', + Format => 'int32u', + }, + 4 => { + Name => 'ImageWidth', + Format => 'int32u', + }, + 8 => { + Name => 'NumberOfComponents', + Format => 'int16u', + }, + 10 => { + Name => 'BitsPerComponent', + PrintConv => q{ + $val == 0xff and return 'Variable'; + my $sign = ($val & 0x80) ? 'Signed' : 'Unsigned'; + return (($val & 0x7f) + 1) . " Bits, $sign"; + }, + }, + 11 => { + Name => 'Compression', + PrintConv => { + 0 => 'Uncompressed', + 1 => 'Modified Huffman', + 2 => 'Modified READ', + 3 => 'Modified Modified READ', + 4 => 'JBIG', + 5 => 'JPEG', + 6 => 'JPEG-LS', + 7 => 'JPEG 2000', + 8 => 'JBIG2', + }, + }, +); + +# (ref fcd15444-1/2/6.pdf) +# (also see http://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html) +%Image::ExifTool::Jpeg2000::FileType = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + 0 => { + Name => 'MajorBrand', + Format => 'undef[4]', + PrintConv => { + 'jp2 ' => 'JPEG 2000 Image (.JP2)', # image/jp2 + 'jpm ' => 'JPEG 2000 Compound Image (.JPM)', # image/jpm + 'jpx ' => 'JPEG 2000 with extensions (.JPX)', # image/jpx + 'jxl ' => 'JPEG XL Image (.JXL)', # image/jxl + }, + }, + 1 => { + Name => 'MinorVersion', + Format => 'undef[4]', + ValueConv => 'sprintf("%x.%x.%x", unpack("nCC", $val))', + }, + 2 => { + Name => 'CompatibleBrands', + Format => 'undef[$size-8]', + # ignore any entry with a null, and return others as a list + ValueConv => 'my @a=($val=~/.{4}/sg); @a=grep(!/\0/,@a); \@a', + }, +); + +%Image::ExifTool::Jpeg2000::CaptureResolution = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int8s', + 0 => { + Name => 'CaptureYResolution', + Format => 'rational32u', + }, + 4 => { + Name => 'CaptureXResolution', + Format => 'rational32u', + }, + 8 => { + Name => 'CaptureYResolutionUnit', + SeparateTable => 'ResolutionUnit', + PrintConv => \%resolutionUnit, + }, + 9 => { + Name => 'CaptureXResolutionUnit', + SeparateTable => 'ResolutionUnit', + PrintConv => \%resolutionUnit, + }, +); + +%Image::ExifTool::Jpeg2000::DisplayResolution = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int8s', + 0 => { + Name => 'DisplayYResolution', + Format => 'rational32u', + }, + 4 => { + Name => 'DisplayXResolution', + Format => 'rational32u', + }, + 8 => { + Name => 'DisplayYResolutionUnit', + SeparateTable => 'ResolutionUnit', + PrintConv => \%resolutionUnit, + }, + 9 => { + Name => 'DisplayXResolutionUnit', + SeparateTable => 'ResolutionUnit', + PrintConv => \%resolutionUnit, + }, +); + +%Image::ExifTool::Jpeg2000::ColorSpec = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, # (we don't actually call this) + GROUPS => { 2 => 'Image' }, + FORMAT => 'int8s', + WRITABLE => 1, + # (Note: 'colr' is not a real group, but is used as a hack to write the + # necessary colr box. This hack necessitated another hack in TagInfoXML.pm + # to avoid reporting this fake group in the XML output) + WRITE_GROUP => 'colr', + DATAMEMBER => [ 0 ], + IS_SUBDIR => [ 3 ], + NOTES => q{ + The table below contains tags in the color specification (colr) box. This + box may be rewritten by writing either ICC_Profile, ColorSpace or + ColorSpecData. When writing, any existing colr boxes are replaced with the + newly created colr box. + + B<NOTE>: Care must be taken when writing this color specification because + writing a specification that is incompatible with the image data may make + the image undisplayable. + }, + 0 => { + Name => 'ColorSpecMethod', + RawConv => '$$self{ColorSpecMethod} = $val', + Protected => 1, + Notes => q{ + default for writing is 2 when writing ICC_Profile, 1 when writing + ColorSpace, or 4 when writing ColorSpecData + }, + PrintConv => { + 1 => 'Enumerated', + 2 => 'Restricted ICC', + 3 => 'Any ICC', + 4 => 'Vendor Color', + }, + }, + 1 => { + Name => 'ColorSpecPrecedence', + Notes => 'default for writing is 0', + Protected => 1, + }, + 2 => { + Name => 'ColorSpecApproximation', + Notes => 'default for writing is 0', + Protected => 1, + PrintConv => { + 0 => 'Not Specified', + 1 => 'Accurate', + 2 => 'Exceptional Quality', + 3 => 'Reasonable Quality', + 4 => 'Poor Quality', + }, + }, + 3 => [ + { + Name => 'ICC_Profile', + Condition => q{ + $$self{ColorSpecMethod} == 2 or + $$self{ColorSpecMethod} == 3 + }, + Format => 'undef[$size-3]', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + }, + { + Name => 'ColorSpace', + Condition => '$$self{ColorSpecMethod} == 1', + Format => 'int32u', + Protected => 1, + PrintConv => { # ref 15444-2 2002-05-15 + 0 => 'Bi-level', + 1 => 'YCbCr(1)', + 3 => 'YCbCr(2)', + 4 => 'YCbCr(3)', + 9 => 'PhotoYCC', + 11 => 'CMY', + 12 => 'CMYK', + 13 => 'YCCK', + 14 => 'CIELab', + 15 => 'Bi-level(2)', # (incorrectly listed as 18 in 15444-2 2000-12-07) + 16 => 'sRGB', + 17 => 'Grayscale', + 18 => 'sYCC', + 19 => 'CIEJab', + 20 => 'e-sRGB', + 21 => 'ROMM-RGB', + # incorrect in 15444-2 2000-12-07 + #22 => 'sRGB based YCbCr', + #23 => 'YPbPr(1125/60)', + #24 => 'YPbPr(1250/50)', + 22 => 'YPbPr(1125/60)', + 23 => 'YPbPr(1250/50)', + 24 => 'e-sYCC', + }, + }, + { + Name => 'ColorSpecData', + Format => 'undef[$size-3]', + Writable => 'undef', + Protected => 1, + Binary => 1, + }, + ], +); + +# JUMBF description box +%Image::ExifTool::Jpeg2000::JUMD = ( + PROCESS_PROC => \&ProcessJUMD, + GROUPS => { 0 => 'JUMBF', 1 => 'JUMBF', 2 => 'Image' }, + NOTES => 'Information extracted from the JUMBF description box.', + 'type' => { + Name => 'JUMDType', + ValueConv => 'unpack "H*", $val', + PrintConv => q{ + my @a = $val =~ /^(\w{8})(\w{4})(\w{4})(\w{16})$/; + return $val unless @a; + my $ascii = pack 'H*', $a[0]; + $a[0] = "($ascii)" if $ascii =~ /^[a-zA-Z0-9]{4}$/; + return join '-', @a; + }, + # seen: + # cacb/cast/caas/cacl/casg/json-00110010800000aa00389b71 + # 6579d6fbdba2446bb2ac1b82feeb89d1 - JPEG image + }, + 'label' => { Name => 'JUMDLabel' }, + 'toggles' => { + Name => 'JUMDToggles', + Unknown => 1, + PrintConv => { BITMASK => { + 0 => 'Requestable', + 1 => 'Label', + 2 => 'ID', + 3 => 'Signature', + }}, + }, + 'id' => { Name => 'JUMDID', Description => 'JUMD ID' }, + 'sig' => { Name => 'JUMDSignature', PrintConv => 'unpack "H*", $val' }, +); + +#------------------------------------------------------------------------------ +# Read JUMBF box to keep track of sub-document numbers +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessJUMB($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + if ($$et{jumd_level}) { + ++$$et{jumd_level}[-1]; # increment current sub-document number + } else { + $$et{jumd_level} = [ ++$$et{DOC_COUNT} ]; # new top-level sub-document + $$et{SET_GROUP0} = 'JUMBF'; + } + $$et{DOC_NUM} = join '-', @{$$et{jumd_level}}; + push @{$$et{jumd_level}}, 0; + ProcessJpeg2000Box($et, $dirInfo, $tagTablePtr); + delete $$et{DOC_NUM}; + delete $$et{JUMBFLabel}; + pop @{$$et{jumd_level}}; + if (@{$$et{jumd_level}} < 2) { + delete $$et{jumd_level}; + delete $$et{SET_GROUP0}; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Read JUMBF description box (ref https://github.com/thorfdbg/codestream-parser) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessJUMD($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $end = $pos + $$dirInfo{DirLen}; + $et->VerboseDir('JUMD', 0, $end-$pos); + delete $$et{JUMBFLabel}; + $$dirInfo{DirLen} < 17 and $et->Warn('Truncated JUMD directory'), return 0; + my $type = substr($$dataPt, $pos, 4); + $et->HandleTag($tagTablePtr, 'type', substr($$dataPt, $pos, 16)); + $pos += 16; + my $flags = Get8u($dataPt, $pos++); + $et->HandleTag($tagTablePtr, 'toggles', $flags); + if ($flags & 0x02) { # label exists? + pos($$dataPt) = $pos; + $$dataPt =~ /\0/g or $et->Warn('Missing JUMD label terminator'), return 0; + my $len = pos($$dataPt) - $pos; + my $name = substr($$dataPt, $pos, $len); + $et->HandleTag($tagTablePtr, 'label', $name); + $pos += $len; + if ($len) { + $name =~ s/[^-_a-zA-Z0-9]([a-z])/\U$1/g; # capitalize characters after illegal characters + $name =~ tr/-_a-zA-Z0-9//dc; # remove other illegal characters + $name =~ s/__/_/; # collapse double underlines + $name = ucfirst $name; # capitalize first letter + $name = "Tag$name" if length($name) < 2; # must at least 2 characters long + $$et{JUMBFLabel} = $name; + } + } + if ($flags & 0x04) { # ID exists? + $pos + 4 > $end and $et->Warn('Missing JUMD ID'), return 0; + $et->HandleTag($tagTablePtr, 'id', Get32u($dataPt, $pos)); + $pos += 4; + } + if ($flags & 0x08) { # signature exists? + $pos + 32 > $end and $et->Warn('Missing JUMD signature'), return 0; + $et->HandleTag($tagTablePtr, 'sig', substr($$dataPt, $pos, 32)); + $pos += 32; + } + my $more = $end - $pos; + if ($more) { + # (may find c2sh box hiding after JUMD record) + if ($more >= 8) { + my %dirInfo = ( + DataPt => $dataPt, + DataLen => $$dirInfo{DataLen}, + DirStart => $pos, + DirLen => $more, + DirName => 'JUMDPrivate', + ); + $et->ProcessDirectory(\%dirInfo, GetTagTable('Image::ExifTool::Jpeg2000::Main')); + } else { + $et->Warn("Extra data in JUMD box $more bytes)", 1); + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Create new JPEG 2000 boxes when writing +# (Currently only supports adding top-level Writable JPEG2000 tags and certain UUID boxes) +# Inputs: 0) ExifTool object ref, 1) Output file or scalar ref +# Returns: 1 on success +sub CreateNewBoxes($$) +{ + my ($et, $outfile) = @_; + my $addTags = $$et{AddJp2Tags}; + my $addDirs = $$et{AddJp2Dirs}; + delete $$et{AddJp2Tags}; + delete $$et{AddJp2Dirs}; + my ($tag, $dirName); + # add JPEG2000 tags + foreach $tag (sort keys %$addTags) { + my $tagInfo = $$addTags{$tag}; + my $nvHash = $et->GetNewValueHash($tagInfo); + # (native JPEG2000 information is always preferred, so don't check IsCreating) + next unless $$tagInfo{List} or $et->IsOverwriting($nvHash) > 0; + next if $$nvHash{EditOnly}; + my @vals = $et->GetNewValue($nvHash); + my $val; + foreach $val (@vals) { + my $boxhdr = pack('N', length($val) + 8) . $$tagInfo{TagID}; + Write($outfile, $boxhdr, $val) or return 0; + ++$$et{CHANGED}; + $et->VerboseValue("+ Jpeg2000:$$tagInfo{Name}", $val); + } + } + # add UUID boxes (and/or JXL Exif/XML boxes) + foreach $dirName (sort keys %$addDirs) { + # handle JPEG XL XMP and EXIF + if ($dirName eq 'xml ' or $dirName eq 'Exif') { + my ($tag, $dir) = $dirName eq 'xml ' ? ('xml ', 'XMP') : ('Exif', 'EXIF'); + my $tagInfo = $Image::ExifTool::Jpeg2000::Main{$tag}; + $tagInfo = $$tagInfo[1] if ref $tagInfo eq 'ARRAY'; # (hack for stupid JXL XMP) + my $subdir = $$tagInfo{SubDirectory}; + my $tagTable = GetTagTable($$subdir{TagTable}); + $tagTable = GetTagTable('Image::ExifTool::XMP::Main') if $dir eq 'XMP'; + my %dirInfo = ( + DirName => $dir, + Parent => $tag, + ); + my $compress = $et->Options('Compress'); + $dirInfo{Compact} = 1 if $$et{IsJXL} and $compress; + my $newdir = $et->WriteDirectory(\%dirInfo, $tagTable, $$subdir{WriteProc}); + if (defined $newdir and length $newdir) { + # not sure why, but EXIF box is padded with leading 0's in my sample + my $pad = $dirName eq 'Exif' ? "\0\0\0\0" : ''; + if ($$et{IsJXL} and $compress) { + # create as Brotli-compressed metadata + if (eval { require IO::Compress::Brotli }) { + my $compressed; + eval { $compressed = IO::Compress::Brotli::bro($pad . $newdir) }; + if ($@ or not $compressed) { + $et->Warn("Error encoding $dirName brob box"); + } else { + $et->VPrint(0, " Writing Brotli-compressed $dir\n"); + $newdir = $compressed; + $pad = $tag; + $tag = 'brob'; + } + } else { + $et->WarnOnce('Install IO::Compress::Brotli to create Brotli-compressed metadata'); + } + } + my $boxhdr = pack('N', length($newdir) + length($pad) + 8) . $tag; + Write($outfile, $boxhdr, $pad, $newdir) or return 0; + next; + } + } + next unless $uuid{$dirName}; + my $tagInfo; + foreach $tagInfo (@{$Image::ExifTool::Jpeg2000::Main{uuid}}) { + next unless $$tagInfo{Name} eq $dirName; + my $subdir = $$tagInfo{SubDirectory}; + my $tagTable = GetTagTable($$subdir{TagTable}); + my %dirInfo = ( + DirName => $$subdir{DirName} || $dirName, + Parent => 'JP2', + ); + # remove "UUID-" from start of directory name to allow appropriate + # directories to be written as a block + $dirInfo{DirName} =~ s/^UUID-//; + my $newdir = $et->WriteDirectory(\%dirInfo, $tagTable, $$subdir{WriteProc}); + if (defined $newdir and length $newdir) { + my $boxhdr = pack('N', length($newdir) + 24) . 'uuid' . $uuid{$dirName}; + Write($outfile, $boxhdr, $newdir) or return 0; + last; + } + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Create Color Specification Box +# Inputs: 0) ExifTool object ref, 1) Output file or scalar ref +# Returns: 1 on success +sub CreateColorSpec($$) +{ + my ($et, $outfile) = @_; + my $meth = $et->GetNewValue('Jpeg2000:ColorSpecMethod'); + my $prec = $et->GetNewValue('Jpeg2000:ColorSpecPrecedence') || 0; + my $approx = $et->GetNewValue('Jpeg2000:ColorSpecApproximation') || 0; + my $icc = $et->GetNewValue('ICC_Profile'); + my $space = $et->GetNewValue('Jpeg2000:ColorSpace'); + my $cdata = $et->GetNewValue('Jpeg2000:ColorSpecData'); + unless ($meth) { + if ($icc) { + $meth = 2; + } elsif (defined $space) { + $meth = 1; + } elsif (defined $cdata) { + $meth = 4; + } else { + $et->Warn('Color space not defined'), return 0; + } + } + if ($meth eq '1') { + defined $space or $et->Warn('Must specify ColorSpace'), return 0; + $cdata = pack('N', $space); + } elsif ($meth eq '2' or $meth eq '3') { + defined $icc or $et->Warn('Must specify ICC_Profile'), return 0; + $cdata = $icc; + } elsif ($meth eq '4') { + defined $cdata or $et->Warn('Must specify ColorSpecData'), return 0; + } else { + $et->Warn('Unknown ColorSpecMethod'), return 0; + } + my $boxhdr = pack('N', length($cdata) + 11) . 'colr'; + Write($outfile, $boxhdr, pack('CCC',$meth,$prec,$approx), $cdata) or return 0; + ++$$et{CHANGED}; + $et->VPrint(1, " + Jpeg2000:ColorSpec\n"); + return 1; +} + +#------------------------------------------------------------------------------ +# Process JPEG 2000 box +# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) Pointer to tag table +# Returns: 1 on success when reading, or -1 on write error +# (or JP2 box or undef when writing from buffer) +sub ProcessJpeg2000Box($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = $$dirInfo{DataLen}; + my $dataPos = $$dirInfo{DataPos} || 0; + my $dirLen = $$dirInfo{DirLen} || 0; + my $dirStart = $$dirInfo{DirStart} || 0; + my $base = $$dirInfo{Base} || 0; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my $dirEnd = $dirStart + $dirLen; + my ($err, $outBuff, $verbose, $doColour, $hash); + + if ($outfile) { + unless ($raf) { + # buffer output to be used for return value + $outBuff = ''; + $outfile = \$outBuff; + } + # determine if we will be writing colr box + if ($$dirInfo{DirName} and $$dirInfo{DirName} eq 'JP2Header') { + $doColour = 2 if defined $et->GetNewValue('ColorSpecMethod') or $et->GetNewValue('ICC_Profile') or + defined $et->GetNewValue('ColorSpecPrecedence') or defined $et->GetNewValue('ColorSpace') or + defined $et->GetNewValue('ColorSpecApproximation') or defined $et->GetNewValue('ColorSpecData'); + } + } else { + # (must not set verbose flag when writing!) + $verbose = $$et{OPTIONS}{Verbose}; + $et->VerboseDir($$dirInfo{DirName}) if $verbose; + # do hash if requested, but only for top-level image data + $hash = $$et{ImageDataHash} if $raf; + } + # loop through all contained boxes + my ($pos, $boxLen, $lastBox); + for ($pos=$dirStart; ; $pos+=$boxLen) { + my ($boxID, $buff, $valuePtr); + my $hdrLen = 8; # the box header length + if ($raf) { + $dataPos = $raf->Tell() - $base; + my $n = $raf->Read($buff,$hdrLen); + unless ($n == $hdrLen) { + $n and $err = '', last; + CreateNewBoxes($et, $outfile) or $err = 1 if $outfile; + last; + } + $dataPt = \$buff; + $dirLen = $dirEnd = $hdrLen; + $pos = 0; + } elsif ($pos >= $dirEnd - $hdrLen) { + $err = '' unless $pos == $dirEnd; + last; + } + $boxLen = unpack("x$pos N",$$dataPt); # (length includes header and data) + $boxID = substr($$dataPt, $pos+4, 4); + # (ftbl box contains flst boxes with absolute file offsets, not currently handled) + if ($outfile and $boxID eq 'ftbl') { + $et->Error("Can't yet handle fragmented JPX files"); + return -1; + } + # remove old colr boxes if necessary + if ($doColour and $boxID eq 'colr') { + if ($doColour == 1) { # did we successfully write the new colr box? + $et->VPrint(1," - Jpeg2000:ColorSpec\n"); + ++$$et{CHANGED}; + next; + } + $et->Warn('Out-of-order colr box encountered'); + undef $doColour; + } + $lastBox = $boxID; + $pos += $hdrLen; # move to end of box header + if ($boxLen == 1) { + # box header contains an additional 8-byte integer for length + $hdrLen += 8; + if ($raf) { + my $buf2; + if ($raf->Read($buf2,8) == 8) { + $buff .= $buf2; + $dirLen = $dirEnd = $hdrLen; + } + } + $pos > $dirEnd - 8 and $err = '', last; + my ($hi, $lo) = unpack("x$pos N2",$$dataPt); + $hi and $err = "Can't currently handle JPEG 2000 boxes > 4 GB", last; + $pos += 8; # move to end of extended-length box header + $boxLen = $lo - $hdrLen; # length of remaining box data + } elsif ($boxLen == 0) { + if ($raf) { + if ($outfile) { + CreateNewBoxes($et, $outfile) or $err = 1; + # copy over the rest of the file + Write($outfile, $$dataPt) or $err = 1; + while ($raf->Read($buff, 65536)) { + Write($outfile, $buff) or $err = 1; + } + } else { + if ($verbose) { + my $msg = sprintf("offset 0x%.4x to end of file", $dataPos + $base + $pos); + $et->VPrint(0, "$$et{INDENT}- Tag '${boxID}' ($msg)\n"); + } + if ($hash and $isImageData{$boxID}) { + $et->ImageDataHash($raf, undef, $boxID); + } + } + last; # (ignore the rest of the file when reading) + } + $boxLen = $dirEnd - $pos; # data runs to end of file + } else { + $boxLen -= $hdrLen; # length of remaining box data + } + $boxLen < 0 and $err = 'Invalid JPEG 2000 box length', last; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $boxID); + unless (defined $tagInfo or $verbose) { + # no need to process this box + if ($raf) { + if ($outfile) { + Write($outfile, $$dataPt) or $err = 1; + $raf->Read($buff,$boxLen) == $boxLen or $err = '', last; + Write($outfile, $buff) or $err = 1; + } elsif ($hash and $isImageData{$boxID}) { + $et->ImageDataHash($raf, $boxLen, $boxID); + } else { + $raf->Seek($boxLen, 1) or $err = 'Seek error', last; + } + } elsif ($outfile) { + Write($outfile, substr($$dataPt, $pos-$hdrLen, $boxLen+$hdrLen)) or $err = '', last; + } + next; + } + if ($raf) { + # read the box data + $dataPos = $raf->Tell() - $base; + $raf->Read($buff,$boxLen) == $boxLen or $err = '', last; + if ($hash and $isImageData{$boxID}) { + $hash->add($buff); + $et->VPrint(0, "$$et{INDENT}(ImageDataHash: $boxLen bytes of $boxID data)\n"); + } + $valuePtr = 0; + $dataLen = $boxLen; + } elsif ($pos + $boxLen > $dirEnd) { + $err = ''; + last; + } else { + $valuePtr = $pos; + } + if (defined $tagInfo and not $tagInfo) { + # GetTagInfo() required the value for a Condition + my $tmpVal = substr($$dataPt, $valuePtr, $boxLen < 128 ? $boxLen : 128); + $tagInfo = $et->GetTagInfo($tagTablePtr, $boxID, \$tmpVal); + } + # delete all UUID boxes and any writable box if deleting all information + if ($outfile and $tagInfo) { + if ($boxID eq 'uuid' and $$et{DEL_GROUP}{'*'}) { + $et->VPrint(0, " Deleting $$tagInfo{Name}\n"); + ++$$et{CHANGED}; + next; + } elsif ($$tagInfo{Writable}) { + my $isOverwriting; + if ($$et{DEL_GROUP}{Jpeg2000}) { + $isOverwriting = 1; + } else { + my $nvHash = $et->GetNewValueHash($tagInfo); + $isOverwriting = $et->IsOverwriting($nvHash); + } + if ($isOverwriting) { + my $val = substr($$dataPt, $valuePtr, $boxLen); + $et->VerboseValue("- Jpeg2000:$$tagInfo{Name}", $val); + ++$$et{CHANGED}; + next; + } elsif (not $$tagInfo{List}) { + delete $$et{AddJp2Tags}{$boxID}; + } + } + } + # create new tag for JUMBF data values with name corresponding to JUMBFLabel + if ($tagInfo and $$et{JUMBFLabel} and (not $$tagInfo{SubDirectory} or $$tagInfo{BlockExtract})) { + $tagInfo = { %$tagInfo, Name => $$et{JUMBFLabel} . ($$tagInfo{JUMBF_Suffix} || '') }; + delete $$tagInfo{Description}; + AddTagToTable($tagTablePtr, '_JUMBF_' . $$et{JUMBFLabel}, $tagInfo); + delete $$tagInfo{Protected}; # (must do this so -j -b returns JUMBF binary data) + $$tagInfo{TagID} = $boxID; + } + if ($verbose) { + $et->VerboseInfo($boxID, $tagInfo, + Table => $tagTablePtr, + DataPt => $dataPt, + Size => $boxLen, + Start => $valuePtr, + Addr => $valuePtr + $dataPos + $base, + ); + next unless $tagInfo; + } + if ($$tagInfo{SubDirectory}) { + my $subdir = $$tagInfo{SubDirectory}; + my $subdirStart = $valuePtr; + my $subdirLen = $boxLen; + if (defined $$subdir{Start}) { + #### eval Start ($valuePtr, $dataPt) + $subdirStart = eval($$subdir{Start}); + $subdirLen -= $subdirStart - $valuePtr; + if ($subdirLen < 0) { + $subdirStart = $valuePtr; + $subdirLen = 0; + } + } + my %subdirInfo = ( + Parent => 'JP2', + DataPt => $dataPt, + DataPos => -$subdirStart, # (relative to Base) + DataLen => $dataLen, + DirStart => $subdirStart, + DirLen => $subdirLen, + DirName => $$subdir{DirName} || $$tagInfo{Name}, + OutFile => $outfile, + Base => $base + $dataPos + $subdirStart, + ); + my $uuid = $uuid{$$tagInfo{Name}}; + # remove "UUID-" prefix to allow appropriate directories to be written as a block + $subdirInfo{DirName} =~ s/^UUID-//; + my $subTable = GetTagTable($$subdir{TagTable}) || $tagTablePtr; + if ($outfile) { + # (special case for brob box, which may be EXIF or XMP) + my $fakeID = $boxID; + if ($boxID eq 'brob') { + # I have seen 'brob' ID's with funny cases, so standardize these + $fakeID = 'xml ' if $$dataPt =~ /^xml /i; + $fakeID = 'Exif' if $$dataPt =~ /^Exif/i; + } + my $newdir; + # only edit writable UUID, Exif and jp2h boxes + if ($uuid or $fakeID eq 'Exif' or ($fakeID eq 'xml ' and $$et{IsJXL}) or + ($boxID eq 'jp2h' and $$et{EDIT_DIRS}{jp2h})) + { + my $compress = $et->Options('Compress'); + $subdirInfo{Parent} = $fakeID; + $subdirInfo{Compact} = 1 if $compress and $$et{IsJXL}; + $newdir = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc}); + next if defined $newdir and not length $newdir; # next if deleting the box + # compress JXL EXIF or XMP metadata if requested + if (defined $newdir and $$et{IsJXL} and defined $compress and + ($fakeID eq 'Exif' or $fakeID eq 'xml ')) + { + if ($compress and $boxID ne 'brob') { + # rewrite as a Brotli-compressed 'brob' box + if (eval { require IO::Compress::Brotli }) { + my $pad = $boxID eq 'Exif' ? "\0\0\0\0" : ''; + my $compressed; + eval { $compressed = IO::Compress::Brotli::bro($pad . $newdir) }; + if ($@ or not $compressed) { + $et->Warn("Error encoding $boxID brob box"); + } else { + $et->VPrint(0, " Writing Brotli-compressed $boxID\n"); + $newdir = $boxID . $compressed; + $boxID = 'brob'; + $subdirStart = $valuePtr = 0; + ++$$et{CHANGED}; + } + } else { + $et->WarnOnce('Install IO::Compress::Brotli to write Brotli-compressed metadata'); + } + } elsif (not $compress and $boxID eq 'brob') { + # (in this case, ProcessBrotli has returned uncompressed data, + # so change to the uncompressed 'xml ' or 'Exif' box type) + $et->VPrint(0, " Writing uncompressed $fakeID\n"); + $boxID = $fakeID; + $subdirStart = $valuePtr = 0; + ++$$et{CHANGED}; + } + } + } elsif (defined $uuid) { + $et->Warn("Not editing $$tagInfo{Name} box", 1); + } + # remove this directory from our create list + delete $$et{AddJp2Dirs}{$fakeID}; # (eg. 'Exif' or 'xml ') + if ($boxID eq 'brob') { + # (can't make tag Name 'XMP' or 'Exif' for Brotli-compressed tags because it + # would break the logic in WriteDirectory(), so we do a lookup here instead) + delete $$et{AddJp2Dirs}{{'xml '=>'XMP','Exif'=>'EXIF'}->{$fakeID}}; + } else { + delete $$et{AddJp2Dirs}{$$tagInfo{Name}}; # (eg. 'EXIF' or 'XMP') + } + # use old box data if not changed + defined $newdir or $newdir = substr($$dataPt, $subdirStart, $subdirLen); + my $prefixLen = $subdirStart - $valuePtr; + my $boxhdr = pack('N', length($newdir) + 8 + $prefixLen) . $boxID; + $boxhdr .= substr($$dataPt, $valuePtr, $prefixLen) if $prefixLen; + Write($outfile, $boxhdr, $newdir) or $err = 1; + # write new colr box immediately after ihdr + if ($doColour and $boxID eq 'ihdr') { + # (shouldn't be multiple ihdr boxes, but just in case, write only 1) + $doColour = $doColour==2 ? CreateColorSpec($et, $outfile) : 0; + } + } else { + # extract as a block if specified + $subdirInfo{BlockInfo} = $tagInfo if $$tagInfo{BlockExtract}; + $et->Warn("Reading non-standard $$tagInfo{Name} box") if defined $uuid and $uuid eq '0'; + unless ($et->ProcessDirectory(\%subdirInfo, $subTable, $$subdir{ProcessProc})) { + if ($subTable eq $tagTablePtr) { + $err = 'JPEG 2000 format error'; + last; + } + $et->Warn("Unrecognized $$tagInfo{Name} box"); + } + } + } elsif ($$tagInfo{Format} and not $outfile) { + # only save tag values if Format was specified + my $rational; + my $val = ReadValue($dataPt, $valuePtr, $$tagInfo{Format}, undef, $boxLen, \$rational); + if (defined $val) { + my $key = $et->FoundTag($tagInfo, $val); + # save Rational value + $$et{RATIONAL}{$key} = $rational if defined $rational and defined $key; + } + } elsif ($outfile) { + my $boxhdr = pack('N', $boxLen + 8) . $boxID; + Write($outfile, $boxhdr, substr($$dataPt, $valuePtr, $boxLen)) or $err = 1; + } + } + if (defined $err) { + $err or $err = 'Truncated JPEG 2000 box'; + if ($outfile) { + $et->Error($err) unless $err eq '1'; + return $raf ? -1 : undef; + } + $et->Warn($err); + } + return $outBuff if $outfile and not $raf; + return 1; +} + +#------------------------------------------------------------------------------ +# Return bits from a bitstream object +# Inputs: 0) array ref, 1) number of bits +# Returns: specified number of bits as an integer, and shifts input bitstream +sub GetBits($$) +{ + my ($a, $n) = @_; + my $v = 0; + my $bit = 1; + my $i; + while ($n--) { + for ($i=0; $i<@$a; ++$i) { + # consume bits LSB first + my $set = $$a[$i] & 1; + $$a[$i] >>= 1; + if ($i) { + $$a[$i-1] |= 0x80 if $set; + } else { + $v |= $bit if $set; + $bit <<= 1; + } + } + } + return $v; +} + +#------------------------------------------------------------------------------ +# Read/write Brotli-encoded metadata +# Inputs: 0) ExifTool ref, 1) dirInfoRef, 2) tag table ref +# Returns: 1 on success when reading, or new data when writing (undef if unchanged) +# (ref https://libjxl.readthedocs.io/en/latest/api_decoder.html) +sub ProcessBrotli($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + + return 0 unless length($$dataPt) > 4; + + my $isWriting = $$dirInfo{IsWriting}; + my $type = substr($$dataPt, 0, 4); + $et->VerboseDir("Decrypted Brotli '${type}'") unless $isWriting; + my %knownType = ( exif => 'Exif', 'xml ' => 'xml ', jumb => 'jumb' ); + my $stdType = $knownType{lc $type}; + unless ($stdType) { + $et->Warn('Unknown Brotli box type', 1); + return 1; + } + if ($type ne $stdType) { + $et->Warn("Incorrect case for Brotli '${type}' data (should be '${stdType}')"); + $type = $stdType; + } + if (eval { require IO::Uncompress::Brotli }) { + if ($isWriting and not eval { require IO::Compress::Brotli }) { + $et->WarnOnce('Install IO::Compress::Brotli to write Brotli-compressed metadata'); + return undef; + } + my $compress = $et->Options('Compress'); + my $verbose = $isWriting ? 0 : $et->Options('Verbose'); + my $dat = substr($$dataPt, 4); + eval { $dat = IO::Uncompress::Brotli::unbro($dat, 100000000) }; + $@ and $et->Warn("Error decoding $type brob box"), return 1; + $verbose > 2 and $et->VerboseDump(\$dat, Prefix => $$et{INDENT} . ' '); + my %dirInfo = ( DataPt => \$dat ); + if ($type eq 'xml ') { + $dirInfo{DirName} = 'XMP'; # (necessary for block read/write) + require Image::ExifTool::XMP; + if ($isWriting) { + $dirInfo{Compact} = 1 if $compress; # (no need to add padding if writing compressed) + $dat = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + } else { + Image::ExifTool::XMP::ProcessXMP($et, \%dirInfo, $tagTablePtr); + } + } elsif ($type eq 'Exif') { + $dirInfo{DirName} = 'EXIF'; # (necessary for block read/write) + $dirInfo{DirStart} = 4 + (length($dat) > 4 ? unpack("N", $dat) : 0); + if ($dirInfo{DirStart} > length $dat) { + $et->Warn("Corrupted Brotli '${type}' data"); + } elsif ($isWriting) { + $dat = $et->WriteDirectory(\%dirInfo, $tagTablePtr, \&Image::ExifTool::WriteTIFF); + # add back header word + $dat = "\0\0\0\0" . $dat if defined $dat and length $dat; + } else { + $et->ProcessTIFF(\%dirInfo, $tagTablePtr); + } + } elsif ($type eq 'jumb') { + return undef if $isWriting; # (can't yet write JUMBF) + Image::ExifTool::ProcessJUMB($et, \%dirInfo, $tagTablePtr); # (untested) + } + if ($isWriting) { + return undef unless defined $dat; + # rewrite as uncompressed if Compress option is set to 0 (or '') + return $dat if defined $compress and not $compress; + eval { $dat = IO::Compress::Brotli::bro($dat) }; + $@ and $et->Warn("Error encoding $type brob box"), return undef; + $et->VPrint(0, " Writing Brotli-compressed $type\n"); + return $type . $dat; + } + } else { + $et->WarnOnce('Install IO::Uncompress::Brotli to decode Brotli-compressed metadata'); + return undef if $isWriting; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract parameters from JPEG XL codestream [unverified!] +# Inputs: 0) ExifTool ref, 1) codestream ref +# Returns: 1 on success +sub ProcessJXLCodestream($$) +{ + my ($et, $dataPt) = @_; + + return 0 unless $$dataPt =~ /^(\0\0\0\0)?\xff\x0a/; # validate codestream + # ignore if already extracted (ie. subsequent jxlp boxes) + return 0 if $$et{ProcessedJXLCodestream}; + $$et{ProcessedJXLCodestream} = 1; + # work with first 64 bytes of codestream data + # (and add padding if necessary to avoid unpacking past end of data) + my $dat; + if (length $$dataPt > 64) { + $dat = substr($$dataPt, 0, 64); + } elsif (length $$dataPt < 18) { + $dat = $$dataPt . ("\0" x 18); # (so we'll have a minimum 14 bytes to work with) + } else { + $dat = $$dataPt; + } + $dat =~ s/^\0\0\0\0//; # remove jxlp header word + my @a = unpack 'x2C12', $dat; + my ($x, $y); + my $small = GetBits(\@a, 1); + if ($small) { + $y = (GetBits(\@a, 5) + 1) * 8; + } else { + $y = GetBits(\@a, [9, 13, 18, 30]->[GetBits(\@a, 2)]) + 1; + } + my $ratio = GetBits(\@a, 3); + if ($ratio == 0) { + if ($small) { + $x = (GetBits(\@a, 5) + 1) * 8;; + } else { + $x = GetBits(\@a, [9, 13, 18, 30]->[GetBits(\@a, 2)]) + 1; + } + } else { + my $r = [[1,1],[12,10],[4,3],[3,2],[16,9],[5,4],[2,1]]->[$ratio-1]; + $x = int($y * $$r[0] / $$r[1]); + } + $et->FoundTag(ImageWidth => $x); + $et->FoundTag(ImageHeight => $y); + return 1; +} + +#------------------------------------------------------------------------------ +# Read/write meta information from a JPEG 2000 image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid JPEG 2000 file, or -1 on write error +sub ProcessJP2($$) +{ + local $_; + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my $hdr; + + # check to be sure this is a valid JPG2000 file + return 0 unless $raf->Read($hdr,12) == 12; + unless ($hdr eq "\0\0\0\x0cjP \x0d\x0a\x87\x0a" or # (ref 1) + $hdr eq "\0\0\0\x0cjP\x1a\x1a\x0d\x0a\x87\x0a" or # (ref 2) + $$et{IsJXL}) + { + return 0 unless $hdr =~ /^\xff\x4f\xff\x51\0/; # check for JP2 codestream format + if ($outfile) { + $et->Error('Writing of J2C files is not yet supported'); + return 0 + } + # add J2C markers if not done already + unless ($Image::ExifTool::jpegMarker{0x4f}) { + $Image::ExifTool::jpegMarker{$_} = $j2cMarker{$_} foreach keys %j2cMarker; + } + $et->SetFileType('J2C'); + $raf->Seek(0,0); + return $et->ProcessJPEG($dirInfo); # decode with JPEG processor + } + if ($outfile) { + Write($outfile, $hdr) or return -1; + if ($$et{IsJXL}) { + $et->InitWriteDirs(\%jxlMap); + $$et{AddJp2Tags} = { }; # (don't add JP2 tags in JXL files) + } else { + $et->InitWriteDirs(\%jp2Map); + $$et{AddJp2Tags} = $et->GetNewTagInfoHash(\%Image::ExifTool::Jpeg2000::Main); + } + # save list of directories to create + my %addDirs = %{$$et{ADD_DIRS}}; # (make a copy) + $$et{AddJp2Dirs} = \%addDirs; + } else { + my ($buff, $fileType); + # recognize JPX and JPM as unique types of JP2 + if ($raf->Read($buff, 12) == 12 and $buff =~ /^.{4}ftyp(.{4})/s) { + $fileType = 'JPX' if $1 eq 'jpx '; + $fileType = 'JPM' if $1 eq 'jpm '; + $fileType = 'JXL' if $1 eq 'jxl '; + } + $raf->Seek(-length($buff), 1) if defined $buff; + $et->SetFileType($fileType); + } + SetByteOrder('MM'); # JPEG 2000 files are big-endian + my %dirInfo = ( + RAF => $raf, + DirName => 'JP2', + OutFile => $$dirInfo{OutFile}, + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::Jpeg2000::Main'); + return $et->ProcessDirectory(\%dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Read/write meta information in a JPEG XL image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid JPEG XL file, -1 on write error +sub ProcessJXL($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my ($hdr, $buff); + + return 0 unless $raf->Read($hdr,12) == 12; + if ($hdr eq "\0\0\0\x0cJXL \x0d\x0a\x87\x0a") { + # JPEG XL in ISO BMFF container + $$et{IsJXL} = 1; + } elsif ($hdr =~ /^\xff\x0a/) { + # JPEG XL codestream + if ($outfile) { + if ($$et{OPTIONS}{IgnoreMinorErrors}) { + $et->Warn('Wrapped JXL codestream in ISO BMFF container'); + } else { + $et->Error('Will wrap JXL codestream in ISO BMFF container for writing',1); + return 0; + } + $$et{IsJXL} = 2; + my $buff = "\0\0\0\x0cJXL \x0d\x0a\x87\x0a\0\0\0\x14ftypjxl \0\0\0\0jxl "; + # add metadata to empty ISO BMFF container + $$dirInfo{RAF} = new File::RandomAccess(\$buff); + } else { + $et->SetFileType('JXL Codestream','image/jxl', 'jxl'); + if ($$et{ImageDataHash} and $raf->Seek(0,0)) { + $et->ImageDataHash($raf, undef, 'JXL'); + } + return ProcessJXLCodestream($et, \$hdr); + } + } else { + return 0; + } + $raf->Seek(0,0) or $et->Error('Seek error'), return 0; + + my $success = ProcessJP2($et, $dirInfo); + + if ($outfile and $success > 0 and $$et{IsJXL} == 2) { + # attach the JXL codestream box to the ISO BMFF file + $raf->Seek(0,2) or return -1; + my $size = $raf->Tell(); + $raf->Seek(0,0) or return -1; + SetByteOrder('MM'); + Write($outfile, Set32u($size + 8), 'jxlc') or return -1; + while ($raf->Read($buff, 65536)) { + Write($outfile, $buff) or return -1; + } + } + return $success; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Jpeg2000 - Read JPEG 2000 meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read JPEG 2000 +files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.jpeg.org/public/fcd15444-2.pdf> + +=item L<ftp://ftp.remotesensing.org/jpeg2000/fcd15444-1.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Jpeg2000 Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Kodak.pm b/ExifTool/lib/Image/ExifTool/Kodak.pm new file mode 100644 index 0000000..83045cb --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Kodak.pm @@ -0,0 +1,3279 @@ +#------------------------------------------------------------------------------ +# File: Kodak.pm +# +# Description: Kodak EXIF maker notes and APP3 "Meta" tags +# +# Revisions: 03/28/2005 - P. Harvey Created +# +# References: 1) http://search.cpan.org/dist/Image-MetaData-JPEG/ +# 2) http://www.ozhiker.com/electronics/pjmt/jpeg_info/meta.html +# 3) http://www.cybercom.net/~dcoffin/dcraw/ +# 4) Jim McGarvey private communication +# IB) Iliah Borg private communication (LibRaw) +# +# Notes: There really isn't much public information about Kodak formats. +# The only source I could find was Image::MetaData::JPEG, which +# didn't provide information about decoding the tag values. So +# this module represents a lot of work downloading sample images +# (about 100MB worth!), and testing with my daughter's CX4200. +#------------------------------------------------------------------------------ + +package Image::ExifTool::Kodak; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.47'; + +sub ProcessKodakIFD($$$); +sub ProcessKodakText($$$); +sub ProcessPose($$$); +sub WriteKodakIFD($$$); + +# Kodak type 1 maker notes (ref 1) +%Image::ExifTool::Kodak::Main = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + NOTES => q{ + The table below contains the most common set of Kodak tags. The following + Kodak camera models have been tested and found to use these tags: C360, + C663, C875, CX6330, CX6445, CX7330, CX7430, CX7525, CX7530, DC4800, DC4900, + DX3500, DX3600, DX3900, DX4330, DX4530, DX4900, DX6340, DX6440, DX6490, + DX7440, DX7590, DX7630, EasyShare-One, LS420, LS443, LS633, LS743, LS753, + V530, V550, V570, V603, V610, V705, Z650, Z700, Z710, Z730, Z740, Z760 and + Z7590. + }, + WRITABLE => 1, + FIRST_ENTRY => 8, + 0x00 => { + Name => 'KodakModel', + Format => 'string[8]', + }, + 0x09 => { + Name => 'Quality', + PrintConv => { #PH + 1 => 'Fine', + 2 => 'Normal', + }, + }, + 0x0a => { + Name => 'BurstMode', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x0c => { + Name => 'KodakImageWidth', + Format => 'int16u', + }, + 0x0e => { + Name => 'KodakImageHeight', + Format => 'int16u', + }, + 0x10 => { + Name => 'YearCreated', + Groups => { 2 => 'Time' }, + Format => 'int16u', + }, + 0x12 => { + Name => 'MonthDayCreated', + Groups => { 2 => 'Time' }, + Format => 'int8u[2]', + ValueConv => 'sprintf("%.2d:%.2d",split(" ", $val))', + ValueConvInv => '$val=~tr/:./ /;$val', + }, + 0x14 => { + Name => 'TimeCreated', + Groups => { 2 => 'Time' }, + Format => 'int8u[4]', + Shift => 'Time', + ValueConv => 'sprintf("%.2d:%.2d:%.2d.%.2d",split(" ", $val))', + ValueConvInv => '$val=~tr/:./ /;$val', + }, + 0x18 => { + Name => 'BurstMode2', + Format => 'int16u', + Unknown => 1, # not sure about this tag (or other 'Unknown' tags) + }, + 0x1b => { + Name => 'ShutterMode', + PrintConv => { #PH + 0 => 'Auto', + 8 => 'Aperture Priority', + 32 => 'Manual?', + }, + }, + 0x1c => { + Name => 'MeteringMode', + PrintConv => { #PH + 0 => 'Multi-segment', + 1 => 'Center-weighted average', + 2 => 'Spot', + }, + }, + 0x1d => 'SequenceNumber', + 0x1e => { + Name => 'FNumber', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => 'int($val * 100 + 0.5)', + }, + 0x20 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val / 1e5', + ValueConvInv => '$val * 1e5', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x24 => { + Name => 'ExposureCompensation', + Format => 'int16s', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x26 => { + Name => 'VariousModes', + Format => 'int16u', + Unknown => 1, + }, + 0x28 => { + Name => 'Distance1', + Format => 'int32u', + Unknown => 1, + }, + 0x2c => { + Name => 'Distance2', + Format => 'int32u', + Unknown => 1, + }, + 0x30 => { + Name => 'Distance3', + Format => 'int32u', + Unknown => 1, + }, + 0x34 => { + Name => 'Distance4', + Format => 'int32u', + Unknown => 1, + }, + 0x38 => { + Name => 'FocusMode', + PrintConv => { + 0 => 'Normal', + 2 => 'Macro', + }, + }, + 0x3a => { + Name => 'VariousModes2', + Format => 'int16u', + Unknown => 1, + }, + 0x3c => { + Name => 'PanoramaMode', + Format => 'int16u', + Unknown => 1, + }, + 0x3e => { + Name => 'SubjectDistance', + Format => 'int16u', + Unknown => 1, + }, + 0x40 => { + Name => 'WhiteBalance', + Priority => 0, + PrintConv => { #PH + 0 => 'Auto', + 1 => 'Flash?', + 2 => 'Tungsten', + 3 => 'Daylight', + # 5 - seen this for "Auto" with a ProBack 645M + }, + }, + 0x5c => { + Name => 'FlashMode', + Flags => 'PrintHex', + # various models express this number differently + PrintConv => { #PH + 0x00 => 'Auto', + 0x01 => 'Fill Flash', + 0x02 => 'Off', + 0x03 => 'Red-Eye', + 0x10 => 'Fill Flash', + 0x20 => 'Off', + 0x40 => 'Red-Eye?', + }, + }, + 0x5d => { + Name => 'FlashFired', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x5e => { + Name => 'ISOSetting', + Format => 'int16u', + PrintConv => '$val ? $val : "Auto"', + PrintConvInv => '$val=~/^\d+$/ ? $val : 0', + }, + 0x60 => { + Name => 'ISO', + Format => 'int16u', + }, + 0x62 => { + Name => 'TotalZoom', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0x64 => { + Name => 'DateTimeStamp', + Format => 'int16u', + PrintConv => '$val ? "Mode $val" : "Off"', + PrintConvInv => '$val=~tr/0-9//dc; $val ? $val : 0', + }, + 0x66 => { + Name => 'ColorMode', + Format => 'int16u', + Flags => 'PrintHex', + # various models express this number differently + PrintConv => { #PH + 0x01 => 'B&W', + 0x02 => 'Sepia', + 0x03 => 'B&W Yellow Filter', + 0x04 => 'B&W Red Filter', + 0x20 => 'Saturated Color', + 0x40 => 'Neutral Color', + 0x100 => 'Saturated Color', + 0x200 => 'Neutral Color', + 0x2000 => 'B&W', + 0x4000 => 'Sepia', + }, + }, + 0x68 => { + Name => 'DigitalZoom', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0x6b => { + Name => 'Sharpness', + Format => 'int8s', + %Image::ExifTool::Exif::printParameter, + }, +); + +# Kodak type 2 maker notes (ref PH) +%Image::ExifTool::Kodak::Type2 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + NOTES => q{ + These tags are used by the Kodak DC220, DC260, DC265 and DC290, + Hewlett-Packard PhotoSmart 618, C500 and C912, Pentax EI-200 and EI-2000, + and Minolta EX1500Z. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + 0x08 => { + Name => 'KodakMaker', + Format => 'string[32]', + }, + 0x28 => { + Name => 'KodakModel', + Format => 'string[32]', + }, + 0x6c => { + Name => 'KodakImageWidth', + Format => 'int32u', + }, + 0x70 => { + Name => 'KodakImageHeight', + Format => 'int32u', + }, +); + +# Kodak type 3 maker notes (ref PH) +%Image::ExifTool::Kodak::Type3 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + NOTES => 'These tags are used by the DC240, DC280, DC3400 and DC5000.', + WRITABLE => 1, + FIRST_ENTRY => 0, + 0x0c => { + Name => 'YearCreated', + Groups => { 2 => 'Time' }, + Format => 'int16u', + }, + 0x0e => { + Name => 'MonthDayCreated', + Groups => { 2 => 'Time' }, + Format => 'int8u[2]', + ValueConv => 'sprintf("%.2d:%.2d",split(" ", $val))', + ValueConvInv => '$val=~tr/:./ /;$val', + }, + 0x10 => { + Name => 'TimeCreated', + Groups => { 2 => 'Time' }, + Format => 'int8u[4]', + Shift => 'Time', + ValueConv => 'sprintf("%2d:%.2d:%.2d.%.2d",split(" ", $val))', + ValueConvInv => '$val=~tr/:./ /;$val', + }, + 0x1e => { + Name => 'OpticalZoom', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0x37 => { + Name => 'Sharpness', + Format => 'int8s', + %Image::ExifTool::Exif::printParameter, + }, + 0x38 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val / 1e5', + ValueConvInv => '$val * 1e5', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x3c => { + Name => 'FNumber', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => 'int($val * 100 + 0.5)', + }, + 0x4e => { + Name => 'ISO', + Format => 'int16u', + }, +); + +# Kodak type 4 maker notes (ref PH) +%Image::ExifTool::Kodak::Type4 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + NOTES => 'These tags are used by the DC200 and DC215.', + WRITABLE => 1, + FIRST_ENTRY => 0, + 0x20 => { + Name => 'OriginalFileName', + Format => 'string[12]', + }, +); + +# Kodak type 5 maker notes (ref PH) +%Image::ExifTool::Kodak::Type5 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + NOTES => q{ + These tags are used by the CX4200, CX4210, CX4230, CX4300, CX4310, CX6200 + and CX6230. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + 0x14 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val / 1e5', + ValueConvInv => '$val * 1e5', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x1a => { + Name => 'WhiteBalance', + PrintConv => { + 1 => 'Daylight', + 2 => 'Flash', + 3 => 'Tungsten', + }, + }, + 0x1c => { + Name => 'FNumber', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => 'int($val * 100 + 0.5)', + }, + 0x1e => { + Name => 'ISO', + Format => 'int16u', + }, + 0x20 => { + Name => 'OpticalZoom', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0x22 => { + Name => 'DigitalZoom', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0x27 => { + Name => 'FlashMode', + PrintConv => { + 0 => 'Auto', + 1 => 'On', + 2 => 'Off', + 3 => 'Red-Eye', + }, + }, + 0x2a => { + Name => 'ImageRotated', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x2b => { + Name => 'Macro', + PrintConv => { 0 => 'On', 1 => 'Off' }, + }, +); + +# Kodak type 6 maker notes (ref PH) +%Image::ExifTool::Kodak::Type6 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + NOTES => 'These tags are used by the DX3215 and DX3700.', + WRITABLE => 1, + FIRST_ENTRY => 0, + 0x10 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val / 1e5', + ValueConvInv => '$val * 1e5', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x14 => { + Name => 'ISOSetting', + Format => 'int32u', + Unknown => 1, + }, + 0x18 => { + Name => 'FNumber', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => 'int($val * 100 + 0.5)', + }, + 0x1a => { + Name => 'ISO', + Format => 'int16u', + }, + 0x1c => { + Name => 'OpticalZoom', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0x1e => { + Name => 'DigitalZoom', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0x22 => { + Name => 'Flash', + Format => 'int16u', + PrintConv => { + 0 => 'No Flash', + 1 => 'Fired', + }, + }, +); + +# Kodak type 7 maker notes (ref PH) +%Image::ExifTool::Kodak::Type7 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + NOTES => q{ + The maker notes of models such as the C340, C433, CC533, LS755, V803 and + V1003 seem to start with the camera serial number. The C310, C315, C330, + C643, C743, CD33, CD43, CX7220 and CX7300 maker notes are also decoded using + this table, although the strings for these cameras don't conform to the + usual Kodak serial number format, and instead have the model name followed + by 8 digits. + }, + 0 => { # (not confirmed) + Name => 'SerialNumber', + Format => 'string[16]', + ValueConv => '$val=~s/\s+$//; $val', # remove trailing whitespace + ValueConvInv => '$val', + }, +); + +# Kodak IFD-format maker notes (ref PH) +%Image::ExifTool::Kodak::Type8 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + Kodak models such as the ZD710, P712, P850, P880, V1233, V1253, V1275, + V1285, Z612, Z712, Z812, Z885 use standard TIFF IFD format for the maker + notes. In keeping with Kodak's strategy of inconsistent makernotes, models + such as the M380, M1033, M1093, V1073, V1273, Z1012, Z1085 and Z8612 + also use these tags, but these makernotes begin with a TIFF header instead + of an IFD entry count and use relative instead of absolute offsets. There + is a large amount of information stored in these maker notes (apparently + with much duplication), but relatively few tags have so far been decoded. + }, + 0xfc00 => [{ + Name => 'SubIFD0', + Condition => '$format eq "undef"', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + NestedHtmlDump => 2, # (so HtmlDump doesn't show these as double-referenced) + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD0', + Base => '$start', + ProcessProc => \&ProcessKodakIFD, + WriteProc => \&WriteKodakIFD, + }, + },{ + Name => 'SubIFD0', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD0', + Start => '$val', + # (odd but true: the Base for this SubIFD is different than 0xfc01-0xfc06) + }, + }], + # SubIFD1 and higher data is preceded by a TIFF byte order mark to indicate + # the byte ordering used. Beginning with the M580, these subdirectories are + # stored as 'undef' data rather than as a standard EXIF SubIFD. + 0xfc01 => [{ + Name => 'SubIFD1', + Condition => '$format eq "undef"', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD1', + Base => '$start', + }, + },{ + Name => 'SubIFD1', + Condition => '$$valPt ne "\0\0\0\0"', # may be zero if dir doesn't exist + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD1', + Start => '$val', + Base => '$start', + }, + }], + 0xfc02 => [{ + Name => 'SubIFD2', + Condition => '$format eq "undef"', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD2', + Base => '$start', + }, + },{ + Name => 'SubIFD2', + Condition => '$$valPt ne "\0\0\0\0"', # may be zero if dir doesn't exist + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD2', + Start => '$val', + Base => '$start', + }, + }], + 0xfc03 => [{ + Name => 'SubIFD3', + Condition => '$format eq "undef"', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD3', + Base => '$start', + }, + },{ + Name => 'SubIFD3', + Condition => '$$valPt ne "\0\0\0\0"', # may be zero if dir doesn't exist + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD3', + Start => '$val', + Base => '$start', + }, + }], + # (SubIFD4 has the pointer zeroed in my samples, but support it + # in case it is used by future models -- ignored if pointer is zero) + 0xfc04 => [{ + Name => 'SubIFD4', + Condition => '$format eq "undef"', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD4', + Base => '$start', + }, + },{ + Name => 'SubIFD4', + Condition => '$$valPt ne "\0\0\0\0"', # may be zero if dir doesn't exist + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD4', + Start => '$val', + Base => '$start', + }, + }], + 0xfc05 => [{ + Name => 'SubIFD5', + Condition => '$format eq "undef"', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD5', + Base => '$start', + }, + },{ + Name => 'SubIFD5', + Condition => '$$valPt ne "\0\0\0\0"', # may be zero if dir doesn't exist + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD5', + Start => '$val', + Base => '$start', + }, + }], + 0xfc06 => [{ # new for the M580 + Name => 'SubIFD6', + Condition => '$format eq "undef"', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD6', + Base => '$start', + }, + },{ + Name => 'SubIFD6', + Condition => '$$valPt ne "\0\0\0\0"', # may be zero if dir doesn't exist + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD6', + Start => '$val', + Base => '$start', + }, + }], + 0xfcff => { + Name => 'SubIFD255', + Condition => '$format eq "undef"', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SubIFD0', + # (uses the same Base as the main MakerNote IFD) + }, + }, + 0xff00 => { + Name => 'CameraInfo', + Condition => '$$valPt ne "\0\0\0\0"', # may be zero if dir doesn't exist + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::CameraInfo', + Start => '$val', + # (uses the same Base as the main MakerNote IFD) + }, + }, +); + +# Kodak type 9 maker notes (ref PH) +%Image::ExifTool::Kodak::Type9 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + NOTES => q{ + These tags are used by the Kodak C140, C180, C913, C1013, M320, M340 and + M550, as well as various cameras marketed by other manufacturers. + }, + 0x0c => [ + { + Name => 'FNumber', + Condition => '$$self{Make} =~ /Kodak/i', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => 'int($val * 100 + 0.5)', + },{ + Name => 'FNumber', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => 'int($val * 10 + 0.5)', + }, + ], + 0x10 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val / 1e6', + ValueConvInv => '$val * 1e6', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x14 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Format => 'string[20]', + Shift => 'Time', + ValueConv => '$val=~s{/}{:}g; $val', + ValueConvInv => '$val=~s{^(\d{4}):(\d{2}):}{$1/$2/}; $val', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,0)', + }, + 0x34 => { + Name => 'ISO', + Format => 'int16u', + }, + 0x57 => { + Name => 'FirmwareVersion', + Condition => '$$self{Make} =~ /Kodak/i', + Format => 'string[16]', + Notes => 'Kodak only', + }, + 0xa8 => { + Name => 'UnknownNumber', # (was SerialNumber, but not unique for all cameras. eg. C1013) + Condition => '$$self{Make} =~ /Kodak/i and $$valPt =~ /^([A-Z0-9]{1,11}\0|[A-Z0-9]{12})/i', + Format => 'string[12]', + Notes => 'Kodak only', + Writable => 0, + }, + 0xc4 => { + Name => 'UnknownNumber', # (confirmed NOT to be serial number for Easyshare Mini - PH) + Condition => '$$self{Make} =~ /Kodak/i and $$valPt =~ /^([A-Z0-9]{1,11}\0|[A-Z0-9]{12})/i', + Format => 'string[12]', + Notes => 'Kodak only', + Writable => 0, + }, +); + +# more Kodak IFD-format maker notes (ref PH) +%Image::ExifTool::Kodak::Type10 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PRIORITY => 0, + NOTES => q{ + Another variation of the IFD-format type, this time with just a byte order + indicator instead of a full TIFF header. These tags are used by the Z980. + }, + # 0x01 int16u - always 0 + 0x02 => { + Name => 'PreviewImageSize', + Writable => 'int16u', + Count => 2, + PrintConv => '$val =~ tr/ /x/; $val', + PrintConvInv => '$val =~ tr/x/ /; $val', + }, + # 0x03 int32u - ranges from about 33940 to 40680 + # 0x04 int32u - always 18493 + # 0x06 undef[4] - 07 d9 04 11 + # 0x07 undef[3] - varies + # 0x08 int16u - 1 (mostly), 2 + # 0x09 int16u - 255 + # 0x0b int16u[2] - '0 0' (mostly), '20 0', '21 0', '1 0' + # 0x0c int16u - 1 (mostly), 3, 259, 260 + # 0x0d int16u - 0 + # 0x0e int16u - 0, 1, 2 (MeteringMode? 0=Partial, 1,2=Multi) + # 0x0f int16u - 0, 5 (MeteringMode? 0=Multi, 5=Partial) + # 0x10 int16u - ranges from about 902 to 2308 + 0x12 => { + Name => 'ExposureTime', + Writable => 'int32u', + ValueConv => '$val / 1e5', + ValueConvInv => '$val * 1e5', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x13 => { + Name => 'FNumber', + Writable => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x14 => { + Name => 'ISO', + Writable => 'int16u', + ValueConv => 'exp($val/3*log(2))*25', + ValueConvInv => '3*log($val/25)/log(2)', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + # 0x15 int16u - 18-25 (SceneMode? 21=auto, 24=Aperture Priority, 19=high speed) + # 0x16 int16u - 50 + # 0x17 int16u - 0, 65535 (MeteringMode? 0=Multi, 65535=Partial) + # 0x19 int16u - 0, 4 (WhiteBalance? 0=Auto, 4=Manual) + # 0x1a int16u - 0, 65535 + # 0x1b int16u - 416-696 + # 0x1c int16u - 251-439 (low when 0x1b is high) + 0x1d => { + Name => 'FocalLength', + Writable => 'int32u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', + }, + # 0x1e int16u - 100 + # 0x1f int16u - 0, 1 + # 0x20,0x21 int16u - 1 + # 0x27 undef[4] - fe ff ff ff + # 0x32 undef[4] - 00 00 00 00 + # 0x61 int32u[2] - '0 0' or '34050 0' + # 0x62 int8u - 0, 1 + # 0x63 int8u - 1 + # 0x64,0x65 int8u - 0, 1, 2 + # 0x66 int32u - 0 + # 0x67 int32u - 3 + # 0x68 int32u - 0 + # 0x3fe undef[2540] +); + +# Kodak PixPro S-1 maker notes (ref PH) +# (similar to Ricoh::Type2 and GE::Main) +%Image::ExifTool::Kodak::Type11 = ( + # (can't currently write these) + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES =>q{ + These tags are found in models such as the PixPro S-1. They are not + writable because the inconsistency of Kodak maker notes is beginning to get + on my nerves. + }, + # (these are related to the Kodak QuickTime UserData tags) + 0x0104 => 'FirmwareVersion', + 0x0203 => { + Name => 'PictureEffect', + PrintConv => { + 0 => 'None', + 3 => 'Monochrome', + 9 => 'Kodachrome', + }, + }, + # 0x0204 - ExposureComp or FlashExposureComp maybe? + 0x0207 => 'KodakModel', + 0x0300 => 'KodakMake', + 0x0308 => 'LensSerialNumber', + 0x0309 => 'LensModel', + 0x030d => { Name => 'LevelMeter', Unknown => 1 }, # (guess) + 0x0311 => 'Pitch', # Units?? + 0x0312 => 'Yaw', # Units?? + 0x0313 => 'Roll', # Units?? + 0x0314 => { Name => 'CX', Unknown => 1 }, + 0x0315 => { Name => 'CY', Unknown => 1 }, + 0x0316 => { Name => 'Rads', Unknown => 1 }, +); + +# Kodak SubIFD0 tags (ref PH) +%Image::ExifTool::Kodak::SubIFD0 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'SubIFD0 through SubIFD5 tags are written a number of newer Kodak models.', + 0xfa02 => { + Name => 'SceneMode', + Writable => 'int16u', + Notes => 'may not be valid for some models', # eg. M580? + PrintConvColumns => 2, + PrintConv => { + 1 => 'Sport', + 3 => 'Portrait', + 4 => 'Landscape', + 6 => 'Beach', + 7 => 'Night Portrait', + 8 => 'Night Landscape', + 9 => 'Snow', + 10 => 'Text', + 11 => 'Fireworks', + 12 => 'Macro', + 13 => 'Museum', + 16 => 'Children', + 17 => 'Program', + 18 => 'Aperture Priority', + 19 => 'Shutter Priority', + 20 => 'Manual', + 25 => 'Back Light', + 28 => 'Candlelight', + 29 => 'Sunset', + 31 => 'Panorama Left-right', + 32 => 'Panorama Right-left', + 33 => 'Smart Scene', + 34 => 'High ISO', + }, + }, + # 0xfa04 - values: 0 (normally), 2 (panorama shots) + # 0xfa0f - values: 0 (normally), 1 (macro?) + # 0xfa11 - some sort of FNumber (x 100) + 0xfa19 => { + Name => 'SerialNumber', # (verified with Z712 - PH) + Writable => 'string', + }, + 0xfa1d => { + Name => 'KodakImageWidth', + Writable => 'int16u', + }, + 0xfa1e => { + Name => 'KodakImageHeight', + Writable => 'int16u', + }, + 0xfa20 => { + Name => 'SensorWidth', + Writable => 'int16u', + }, + 0xfa21 => { + Name => 'SensorHeight', + Writable => 'int16u', + }, + 0xfa23 => { + Name => 'FNumber', + Writable => 'int16u', + Priority => 0, + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0xfa24 => { + Name => 'ExposureTime', + Writable => 'int32u', + Priority => 0, + ValueConv => '$val / 1e5', + ValueConvInv => '$val * 1e5', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0xfa2e => { + Name => 'ISO', + Writable => 'int16u', + Priority => 0, + }, + 0xfa3d => { + Name => 'OpticalZoom', + Writable => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => 'sprintf("%.2f",$val)', + PrintConvInv => '$val=~s/ ?x//; $val', + }, + 0xfa46 => { + Name => 'ISO', + Writable => 'int16u', + Priority => 0, + }, + # 0xfa4c - related to focal length (1=wide, 32=full zoom) + 0xfa51 => { + Name => 'KodakImageWidth', + Writable => 'int16u', + }, + 0xfa52 => { + Name => 'KodakImageHeight', + Writable => 'int16u', + }, + 0xfa54 => { + Name => 'ThumbnailWidth', + Writable => 'int16u', + }, + 0xfa55 => { + Name => 'ThumbnailHeight', + Writable => 'int16u', + }, + 0xfa57 => { + Name => 'PreviewImageWidth', + Writable => 'int16u', + }, + 0xfa58 => { + Name => 'PreviewImageHeight', + Writable => 'int16u', + }, +); + +# Kodak SubIFD1 tags (ref PH) +%Image::ExifTool::Kodak::SubIFD1 = ( + PROCESS_PROC => \&ProcessKodakIFD, + WRITE_PROC => \&WriteKodakIFD, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0027 => { + Name => 'ISO', + Writable => 'int16u', + Priority => 0, + }, + 0x0028 => { + Name => 'ISO', + Writable => 'int16u', + Priority => 0, + }, +); + +my %sceneModeUsed = ( + 0 => 'Program', + 2 => 'Aperture Priority', + 3 => 'Shutter Priority', + 4 => 'Manual', + 5 => 'Portrait', + 6 => 'Sport', + 7 => 'Children', + 8 => 'Museum', + 10 => 'High ISO', + 11 => 'Text', + 12 => 'Macro', + 13 => 'Back Light', + 16 => 'Landscape', + 17 => 'Night Landscape', + 18 => 'Night Portrait', + 19 => 'Snow', + 20 => 'Beach', + 21 => 'Fireworks', + 22 => 'Sunset', + 23 => 'Candlelight', + 28 => 'Panorama', +); + +# Kodak SubIFD2 tags (ref PH) +%Image::ExifTool::Kodak::SubIFD2 = ( + PROCESS_PROC => \&ProcessKodakIFD, + WRITE_PROC => \&WriteKodakIFD, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x6002 => { + Name => 'SceneModeUsed', + Writable => 'int32u', + PrintConvColumns => 2, + PrintConv => \%sceneModeUsed, + }, + 0x6006 => { + Name => 'OpticalZoom', + Writable => 'int32u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => 'sprintf("%.2f",$val)', + PrintConvInv => '$val=~s/ ?x//; $val', + }, + # 0x6009 - some sort of FNumber (x 100) + 0x6103 => { + Name => 'MaxAperture', + Writable => 'int32u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0xf002 => { + Name => 'SceneModeUsed', + Writable => 'int32u', + PrintConvColumns => 2, + PrintConv => \%sceneModeUsed, + }, + 0xf006 => { + Name => 'OpticalZoom', + Writable => 'int32u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => 'sprintf("%.2f",$val)', + PrintConvInv => '$val=~s/ ?x//; $val', + }, + # 0xf009 - some sort of FNumber (x 100) + 0xf103 => { + Name => 'FNumber', + Writable => 'int32u', + Priority => 0, + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0xf104 => { + Name => 'ExposureTime', + Writable => 'int32u', + Priority => 0, + ValueConv => '$val / 1e6', + ValueConvInv => '$val * 1e6', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0xf105 => { + Name => 'ISO', + Writable => 'int32u', + Priority => 0, + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, +); + +# Kodak SubIFD3 tags (ref PH) +%Image::ExifTool::Kodak::SubIFD3 = ( + PROCESS_PROC => \&ProcessKodakIFD, + WRITE_PROC => \&WriteKodakIFD, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x1000 => { + Name => 'OpticalZoom', + Writable => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => 'sprintf("%.2f",$val)', + PrintConvInv => '$val=~s/ ?x//; $val', + }, + # 0x1002 - related to focal length (1=wide, 32=full zoom) + # 0x1006 - pictures remaining? (gradually decreases as pictures are taken) +# +# the following unknown Kodak tags in subIFD3 may store an IFD count of 0 or 1 instead +# of the correct value (which changes from model to model). This bad count is fixed +# with the "FixCount" patch. Models known to have this problem include: +# M380, M1033, M1093IS, V1073, V1233, V1253, V1273, V1275, V1285, Z612, Z712, +# Z812, Z885, Z915, Z950, Z1012IS, Z1085IS, ZD710 +# + 0x2007 => { + Name => 'Kodak_SubIFD3_0x2007', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x2008 => { + Name => 'Kodak_SubIFD3_0x2008', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x2009 => { + Name => 'Kodak_SubIFD3_0x2009', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x200a => { + Name => 'Kodak_SubIFD3_0x200a', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x200b => { + Name => 'Kodak_SubIFD3_0x200b', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x3020 => { + Name => 'Kodak_SubIFD3_0x3020', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x3030 => { + Name => 'Kodak_SubIFD3_0x3030', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x3040 => { + Name => 'Kodak_SubIFD3_0x3040', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x3050 => { + Name => 'Kodak_SubIFD3_0x3050', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x3060 => { + Name => 'Kodak_SubIFD3_0x3060', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x8001 => { + Name => 'Kodak_SubIFD3_0x8001', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x8002 => { + Name => 'Kodak_SubIFD3_0x8002', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x8003 => { + Name => 'Kodak_SubIFD3_0x8003', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x8004 => { + Name => 'Kodak_SubIFD3_0x8004', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x8005 => { + Name => 'Kodak_SubIFD3_0x8005', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x8006 => { + Name => 'Kodak_SubIFD3_0x8006', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x8007 => { + Name => 'Kodak_SubIFD3_0x8007', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x8008 => { + Name => 'Kodak_SubIFD3_0x8008', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x8009 => { + Name => 'Kodak_SubIFD3_0x8009', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x800a => { + Name => 'Kodak_SubIFD3_0x800a', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x800b => { + Name => 'Kodak_SubIFD3_0x800b', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, + 0x800c => { + Name => 'Kodak_SubIFD3_0x800c', + Flags => [ 'FixCount', 'Unknown', 'Hidden' ], + }, +); + +# Kodak SubIFD4 tags (ref PH) +%Image::ExifTool::Kodak::SubIFD4 = ( + PROCESS_PROC => \&ProcessKodakIFD, + WRITE_PROC => \&WriteKodakIFD, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, +); + +# Kodak SubIFD5 tags (ref PH) +%Image::ExifTool::Kodak::SubIFD5 = ( + PROCESS_PROC => \&ProcessKodakIFD, + WRITE_PROC => \&WriteKodakIFD, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x000f => { + Name => 'OpticalZoom', + Writable => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => 'sprintf("%.2f",$val)', + PrintConvInv => '$val=~s/ ?x//; $val', + }, +); + +# Kodak SubIFD6 tags (ref PH) +%Image::ExifTool::Kodak::SubIFD6 = ( + PROCESS_PROC => \&ProcessKodakIFD, + WRITE_PROC => \&WriteKodakIFD, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'SubIFD6 is written by the M580.', +); + +# Decoded from P712, P850 and P880 samples (ref PH) +%Image::ExifTool::Kodak::CameraInfo = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are used by the P712, P850 and P880.', + 0xf900 => { + Name => 'SensorWidth', + Writable => 'int16u', + Notes => 'effective sensor size', + }, + 0xf901 => { + Name => 'SensorHeight', + Writable => 'int16u', + }, + 0xf902 => { + Name => 'BayerPattern', + Writable => 'string', + }, + 0xf903 => { + Name => 'SensorFullWidth', + Writable => 'int16u', + Notes => 'includes black border?', + }, + 0xf904 => { + Name => 'SensorFullHeight', + Writable => 'int16u', + }, + 0xf907 => { + Name => 'KodakImageWidth', + Writable => 'int16u', + }, + 0xf908 => { + Name => 'KodakImageHeight', + Writable => 'int16u', + }, + 0xfa00 => { + Name => 'KodakInfoType', + Writable => 'string', + }, + 0xfa04 => { + Name => 'SerialNumber', # (unverified) + Writable => 'string', + }, + 0xfd04 => { + Name => 'FNumber', + Writable => 'int16u', + Priority => 0, + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0xfd05 => { + Name => 'ExposureTime', + Writable => 'int32u', + Priority => 0, + ValueConv => '$val / 1e6', + ValueConvInv => '$val * 1e6', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0xfd06 => { + Name => 'ISO', + Writable => 'int16u', + Priority => 0, + }, +); + +# treat unknown maker notes as binary data (allows viewing with -U) +%Image::ExifTool::Kodak::Unknown = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, +); + +# tags found in the KodakIFD (in IFD0 of KDC, DCR, TIFF and JPEG images) (ref PH) +%Image::ExifTool::Kodak::IFD = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'KodakIFD', 2 => 'Image'}, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITE_GROUP => 'KodakIFD', + SET_GROUP1 => 1, + NOTES => q{ + These tags are found in a separate IFD of JPEG, TIFF, DCR and KDC images + from some older Kodak models such as the DC50, DC120, DCS760C, DCS Pro 14N, + 14nx, SLR/n, Pro Back and Canon EOS D2000. + }, + # 0x0000: int8u[4] - values: "1 0 0 0" (DC50), "1 1 0 0" (DC120) + 0x0000 => { #4 + Name => 'KodakVersion', + Writable => 'int8u', + Count => 4, + PrintConv => '$val =~ tr/ /./; $val', + PrintConvInv => '$val =~ tr/./ /; $val', + }, + 0x0001 => { + # (related to EV but exact meaning unknown) + Name => 'UnknownEV', # ("DeletedTag", ref 4) + Writable => 'rational64u', + Unknown => 1, + }, + # 0x0002: int8u - values: 0 + 0x0003 => { Name => 'ExposureValue', Writable => 'rational64u' }, + # 0x0004: rational64u - values: 2.875,3.375,3.625,4,4.125,7.25 + # 0x0005: int8u - values: 0 + # 0x0006: int32u[12] - ? + # 0x0007: int32u[3] - values: "65536 67932 69256" + 0x03e9 => { Name => 'OriginalFileName', Writable => 'string' }, + 0x03ea => { Name => 'KodakTag', Writable => 'int32u' }, #4 + 0x03eb => { Name => 'SensorLeftBorder', Writable => 'int16u' }, # ("FinishedImageX", ref 4) + 0x03ec => { Name => 'SensorTopBorder', Writable => 'int16u' }, # ("FinishedImageY", ref 4) + 0x03ed => { Name => 'SensorImageWidth', Writable => 'int16u' }, # ("FinishedImageWidth", ref 4) + 0x03ee => { Name => 'SensorImageHeight',Writable => 'int16u' }, # ("FinishedImageHeight", ref 4) + 0x03ef => { Name => 'BlackLevelTop', Writable => 'int16u' }, #4 + 0x03f0 => { Name => 'BlackLevelBottom', Writable => 'int16u' }, #4 + 0x03f1 => { + Name => 'TextualInfo', # ("CameraSettingString", ref 4) + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::TextualInfo', + }, + }, + 0x03f2 => { #IB/4 + Name => 'FlashMode', + Writable => 'int16u', + Unknown => 1, + Priority => 0, + }, + 0x03f3 => { #IB/4 + Name => 'FlashCompensation', + Writable => 'rational64s', + }, + 0x03f4 => { #4 + Name => 'WindMode', + Writable => 'int16u', + Unknown => 1, + }, + 0x03f5 => { #4 + Name => 'FocusMode', + Writable => 'int16u', + Unknown => 1, + Priority => 0, + }, + 0x03f8 => { #IB/4 + Name => 'MinAperture', + Writable => 'rational64u', + }, + 0x03f9 => { #IB/4 + Name => 'MaxAperture', + Writable => 'rational64u', + }, + 0x03fa => { #4 + Name => 'WhiteBalanceMode', + Writable => 'int16u', + Unknown => 1, + }, + 0x03fb => { #4 + Name => 'WhiteBalanceDetected', + Writable => 'int16u', + Unknown => 1, + }, + 0x03fc => { #3 + Name => 'WhiteBalance', + Writable => 'int16u', + Priority => 0, + PrintConv => { }, # no values yet known + }, + 0x03fd => [{ #3 + Name => 'Processing', + Condition => '$count == 72', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Processing', + }, + },{ + Name => 'ProcessingParameters', #4 + Binary => 1, + # int8u[256] + }], + 0x03fe => { Name => 'ImageAbsoluteX', Writable => 'int16s', }, #4 + 0x03ff => { Name => 'ImageAbsoluteY', Writable => 'int16s' }, #4 + 0x0400 => { Name => 'ApplicationKeyString', Writable => 'string' }, #4 + 0x0401 => { + Name => 'Time', # ("CaptureTime", ref 4) + Groups => { 2 => 'Time' }, + Writable => 'string', + }, + 0x0402 => { #4 + Name => 'GPSString', + Groups => { 2 => 'Location' }, + Writable => 'string', + }, + 0x0403 => { #4 + Name => 'EventLogCapture', + Binary => 1, + Unknown => 1, + # int32u[3072] + }, + 0x0404 => { #4 + Name => 'ComponentTable', + Binary => 1, + Unknown => 1, + }, + 0x0405 => { #4 + Name => 'CustomIlluminant', + Writable => 'int16u', + Unknown => 1, + }, + 0x0406 => [{ #IB/4 + Name => 'CameraTemperature', + Condition => '$count == 1', + Groups => { 2 => 'Camera' }, + Writable => 'rational64s', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + },{ + Name => 'CameraTemperature', + # (when count is 2, values seem related to temperature, but are not Celius) + }], + 0x0407 => { #IB/4 + Name => 'AdapterVoltage', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + }, + 0x0408 => { #IB/4 + Name => 'BatteryVoltage', + Groups => { 2 => 'Camera' }, + Writable => 'rational64u', + }, + 0x0409 => { #4 + Name => 'DacVoltages', + # rational64u[8] + }, + 0x040a => { #4 + Name => 'IlluminantDetectorData', + Binary => 1, + Unknown => 1, + }, + 0x040b => { #4 + Name => 'PixelClockFrequency', + Writable => 'int32u', + }, + 0x040c => { #4 + Name => 'CenterPixel', + Writable => 'int16u', + Count => 3, + }, + 0x040d => { #4 + Name => 'BurstCount', + Writable => 'int16u', + }, + 0x040e => { #4 + Name => 'BlackLevelRough', + Writable => 'int16u', + }, + 0x040f => { #4 + Name => 'OffsetMapHorizontal', + Binary => 1, + Unknown => 1, + # int16s[1736] + }, + 0x0410 => { #4 + Name => 'OffsetMapVertical', + Binary => 1, + Unknown => 1, + # int16s[1160] + }, + 0x0411 => { #4 + Name => 'Histogram', + Binary => 1, + Unknown => 1, + # int16u[256] + }, + 0x0412 => { #4 + Name => 'VerticalClockOverlaps', + Writable => 'int16u', + Count => 2, + }, + 0x0413 => 'SensorTemperature', #4 + 0x0414 => { Name => 'XilinxVersion', Writable => 'string' }, #4 + 0x0415 => { Name => 'FirmwareVersion', Writable => 'int32u' }, #4 + 0x0416 => { Name => 'BlackLevelRoughAfter', Writable => 'int16u' }, #4 + 0x0417 => 'BrightRowsTop', #4 + 0x0418 => 'EventLogProcess', #4 + 0x0419 => 'DacVoltagesFlush', #4 + 0x041a => 'FlashUsed', #4 + 0x041b => 'FlashType', #4 + 0x041c => 'SelfTimer', #4 + 0x041d => 'AFMode', #4 + 0x041e => 'LensType', #4 + 0x041f => { Name => 'ImageCropX', Writable => 'int16s' }, #4 + 0x0420 => { Name => 'ImageCropY', Writable => 'int16s' }, #4 + 0x0421 => 'AdjustedTbnImageWidth', #4 + 0x0422 => 'AdjustedTbnImageHeight', #4 + 0x0423 => { Name => 'IntegrationTime', Writable => 'int32u' }, #4 + 0x0424 => 'BracketingMode', #4 + 0x0425 => 'BracketingStep', #4 + 0x0426 => 'BracketingCounter', #4 + 0x042e => 'HuffmanTableLength', #4 (int8u[16]) + 0x042f => 'HuffmanTableValue', #4 (int8u[13]) + 0x0438 => { Name => 'MainBoardVersion', Writable => 'int32u' }, #4 + 0x0439 => { Name => 'ImagerBoardVersion', Writable => 'int32u' }, #4 + 0x044c => 'FocusEdgeMap', #4 + 0x05e6 => 'IdleTiming', #4 + 0x05e7 => 'FlushTiming', #4 + 0x05e8 => 'IntegrateTiming', #4 + 0x05e9 => 'RegisterReadTiming', #4 + 0x05ea => 'FirstLineTransferTiming', #4 + 0x05eb => 'ShiftTiming', #4 + 0x05ec => 'NormalLineTransferTiming', #4 + 0x05ed => 'TestTransferTiming', #4 + # 0x05f0-0x05f9 "TestTiming", ref 4 + 0x05fa => 'MinimumFlushRows', #4 + 0x05fd => { Name => 'ImagerPowerOnDelayMsec', Writable => 'int32u' }, #4 + 0x05fe => 'ImagerInitialTimingCode', #4 + 0x05ff => 'ImagerLogicProgram', #4 + 0x0600 => { Name => 'ImagerBiasSettlingDelayMsec', Writable => 'int32u' }, #4 + 0x0604 => 'IdleSequence', #4 + 0x0605 => 'FirstFlushSequence', #4 + 0x0606 => 'FinalFlushSequence', #4 + 0x0607 => 'SampleBlackSequence', #4 + 0x0608 => 'TransferSequence', #4 + 0x060e => 'DacCountsPerVolt', #4 + 0x060f => 'BlackDacChannel', #4 + 0x0610 => 'BlackAdCountsPerDacVolt', #4 + 0x0611 => 'BlackTarget', #4 + 0x0612 => 'BlackDacSettlingMsec', #4 + # 0x0618-0x062b - reserved for .IF file use, ref 4 + 0x07d0 => { #4 + Name => 'StandardMatrixDaylight', + Writable => 'rational64s', + Count => 9, + }, + 0x07d1 => { #4 + Name => 'StandardMatrixTungsten', + Writable => 'rational64s', + Count => 9, + }, + 0x07d2 => { #4 + Name => 'StandardMatrixFluorescent', + Writable => 'rational64s', + Count => 9, + }, + 0x07d3 => { #4 + Name => 'StandardMatrixFlash', + Writable => 'rational64s', + Count => 9, + }, + 0x07d4 => { #4 (never used) + Name => 'StandardMatrixCustom', + Writable => 'rational64s', + Count => 9, + }, + 0x07da => { #4 + Name => 'DeviantMatrixDaylight', + Writable => 'rational64s', + Count => 9, + }, + 0x07db => { #4 + Name => 'DeviantMatrixTungsten', + Writable => 'rational64s', + Count => 9, + }, + 0x07dc => { #4 + Name => 'DeviantMatrixFluorescent', + Writable => 'rational64s', + Count => 9, + }, + 0x07dd => { #4 + Name => 'DeviantMatrixFlash', + Writable => 'rational64s', + Count => 9, + }, + 0x07de => { #4 (never used) + Name => 'DeviantMatrixCustom', + Writable => 'rational64s', + Count => 9, + }, + 0x07e4 => { #4 + Name => 'UniqueMatrixDaylight', + Writable => 'rational64s', + Count => 9, + }, + 0x07e5 => { #4 + Name => 'UniqueMatrixTungsten', + Writable => 'rational64s', + Count => 9, + }, + 0x07e6 => { #4 + Name => 'UniqueMatrixFluorescent', + Writable => 'rational64s', + Count => 9, + }, + 0x07e7 => { #4 + Name => 'UniqueMatrixFlash', + Writable => 'rational64s', + Count => 9, + }, + 0x07e8 => { #4 + Name => 'UniqueMatrixCustom', + Writable => 'rational64s', + Count => 9, + }, + 0x07e9 => { #4 + Name => 'UniqueMatrixAuto', + Writable => 'rational64s', + Count => 9, + }, + 0x0834 => { #4 + Name => 'StandardWhiteDaylight', + Writable => 'rational64s', + Count => 3, + }, + 0x0835 => { #4 + Name => 'StandardWhiteTungsten', + Writable => 'rational64s', + Count => 3, + }, + 0x0836 => { #4 + Name => 'StandardWhiteFluorescent', + Writable => 'rational64s', + Count => 3, + }, + 0x0837 => { #4 + Name => 'StandardWhiteFlash', + Writable => 'rational64s', + Count => 3, + }, + 0x0838 => { #4 (never used) + Name => 'StandardWhiteCustom', + Writable => 'rational64s', + Count => 3, + }, + 0x083e => { #4 + Name => 'DeviantWhiteDaylight', + Writable => 'rational64s', + Count => 3, + }, + 0x083f => { #4 + Name => 'DeviantWhiteTungsten', + Writable => 'rational64s', + Count => 3, + }, + 0x0840 => { #4 + Name => 'DeviantWhiteFluorescent', + Writable => 'rational64s', + Count => 3, + }, + 0x0841 => { #4 + Name => 'DeviantWhiteFlash', + Writable => 'rational64s', + Count => 3, + }, + 0x0842 => { #4 (never used) + Name => 'DeviantWhiteCustom', + Writable => 'rational64s', + Count => 3, + }, + # 0x0843 - rational64u[3] + # 0x0844 - rational64u[3] + 0x0846 => { #3 ("WhiteBalanceKelvin", ref 4) + Name => 'ColorTemperature', + Writable => 'int16u', + }, + 0x0847 => 'WB_RGBLevelsAsShot', #4 + 0x0848 => 'WB_RGBLevelsDaylight', #IB (rational64s/u[3]) ("UniqueWhiteDaylight", ref 4) + 0x0849 => 'WB_RGBLevelsTungsten', #IB (rational64s/u[3]) ("UniqueWhiteTungsten", ref 4) + 0x084a => 'WB_RGBLevelsFluorescent', #IB (rational64s/u[3]) ("UniqueWhiteFluorescent", ref 4) + 0x084b => 'WB_RGBLevelsFlash', #IB (rational64s/u[3]) ("UniqueWhiteFlash", ref 4) + 0x084c => 'WB_RGBLevelsCustom', #IB (rational64u[3]) ("UniqueWhiteCustom", ref 4) + 0x084d => 'WB_RGBLevelsAuto', #IB (rational64u[3]) ("UniqueWhiteAuto", ref 4) + 0x0852 => { #3/4 + Name => 'WB_RGBMulDaylight', # ("AdjustWhiteFactorsDaylight", ref 4) + Writable => 'rational64u', + Count => 3, + }, + 0x0853 => { #3/4 + Name => 'WB_RGBMulTungsten', # ("AdjustWhiteFactorsTungsten", ref 4) + Writable => 'rational64u', + Count => 3, + }, + 0x0854 => { #3/4 + Name => 'WB_RGBMulFluorescent', # ("AdjustWhiteFactorsFluorescent", ref 4) + Writable => 'rational64u', + Count => 3, + }, + 0x0855 => { #3/4 + Name => 'WB_RGBMulFlash', # ("AdjustWhiteFactorsFlash", ref 4) + Writable => 'rational64u', + Count => 3, + }, + 0x085c => { Name => 'WB_RGBCoeffsDaylight', Binary => 1 }, #3 ("WhiteBalanceParametersDaylight", ref 4) + 0x085d => { Name => 'WB_RGBCoeffsTungsten', Binary => 1 }, #3 ("WhiteBalanceParametersTungsten", ref 4) + 0x085e => { Name => 'WB_RGBCoeffsFluorescent',Binary=>1 }, #3 ("WhiteBalanceParametersFluorescent", ref 4) + 0x085f => { Name => 'WB_RGBCoeffsFlash', Binary => 1 }, #3 ("WhiteBalanceParametersFlash", ref 4) + 0x0898 => { Name => 'ExposureGainDaylight', Writable => 'rational64s' }, #4 + 0x0899 => { Name => 'ExposureGainTungsten', Writable => 'rational64s' }, #4 + 0x089a => { Name => 'ExposureGainFluorescent',Writable=>'rational64s' }, #4 + 0x089b => { Name => 'ExposureGainFlash', Writable => 'rational64s' }, #4 + 0x089c => { Name => 'ExposureGainCustom', Writable => 'rational64s' }, #4 (never used) + 0x089d => { Name => 'AnalogISOTable', Writable => 'rational64u', Count => 3 }, #4 + 0x089e => { Name => 'AnalogCaptureISO', Writable => 'int32u' }, #4 + 0x089f => { Name => 'ISOCalibrationGain', Writable => 'rational64u' }, #4 + 0x08a0 => 'ISOCalibrationGainTable', #4 + 0x08a1 => 'ExposureHeadroomFactor', #4 + 0x08ab => 'LinearitySplineTags', #4 (int16u[24]) + # 0x08ac-0x08fb - LinearitySpline(n) (rational64s[75]) + 0x08fc => { #4 + Name => 'MonitorMatrix', + Writable => 'rational64s', + Count => 9, + }, + 0x08fd => 'TonScaleTable', #4 + 0x08fe => { Name => 'Gamma', Writable => 'rational64u' }, #4 + 0x08ff => 'LogLinTable', #4 + 0x0900 => 'LinLogTable', #4 + 0x0901 => { Name => 'GammaTable', Binary => 1 }, #4 (int16u[4096]) + 0x0902 => { Name => 'LogScale', Writable => 'rational64u' }, #4 + 0x0903 => { Name => 'BaseISO', Writable => 'rational64u' }, #IB (ISO before digital gain) + 0x0904 => { Name => 'LinLogCoring', Writable => 'int16u' }, #4 + 0x0905 => { Name => 'PatternGainConversionTable', Binary => 1 }, #4 (int8u[256]) + 0x0906 => 'DefectCount', #4 + 0x0907 => { Name => 'DefectList', Binary => 1 }, #4 (undef[48]) + 0x0908 => { Name => 'DefectListPacked', Binary => 1 }, #4 (int16u[296]) + 0x0909 => { Name => 'ImageSpace', Writable => 'int16u' }, #4 + 0x090a => { Name => 'ThumbnailCompressionTable',Binary => 1 }, #4 (int8u[4096]) + 0x090b => { Name => 'ThumbnailExpansionTable', Binary => 1 }, #4 (int16u[256]) + 0x090c => { Name => 'ImageCompressionTable', Binary => 1 }, #4 (int16u[4096]) + 0x090d => { Name => 'ImageExpansionTable', Binary => 1 }, #4 (int16u[1024/4096]) + 0x090e => 'EighteenPercentPoint', #4 + 0x090f => { Name => 'DefectIsoCode', Writable => 'int16u' }, #4 + 0x0910 => { Name => 'BaseISODaylight', Writable => 'rational64u' }, #4 + 0x0911 => { Name => 'BaseISOTungsten', Writable => 'rational64u' }, #4 + 0x0912 => { Name => 'BaseISOFluorescent', Writable => 'rational64u' }, #4 + 0x0913 => { Name => 'BaseISOFlash', Writable => 'rational64u' }, #4 + 0x091a => { Name => 'MatrixSelectThreshold',Writable => 'int16s' }, #4 + 0x091b => { Name => 'MatrixSelectK', Writable => 'rational64u' }, #4 + 0x091c => { Name => 'IlluminantDetectTable',Binary => 1 }, #4 (int16u[200]) + 0x091d => 'RGTable', #4 + 0x091e => { Name => 'MatrixSelectThreshold1', Writable => 'int16s' }, #4 + 0x091f => { Name => 'MatrixSelectThreshold2', Writable => 'int16s' }, #4 + 0x0924 => 'PortraitMatrix', #4 + 0x0925 => 'PortraitToneScaleTable', #4 + 0x092e => { Name => 'EnableSharpening', Writable => 'int16u' }, #4 + 0x092f => { #4 + Name => 'SharpeningKernel', + Writable => 'int16s', + Count => 25, + }, + 0x0930 => { Name => 'EdgeMapSlope', Writable => 'int16u' }, #4 + 0x0931 => { Name => 'EdgeMapX1', Writable => 'int16u' }, #4 + 0x0932 => { Name => 'EdgeMapX2', Writable => 'int16u' }, #4 + 0x0933 => { #4 + Name => 'KernelDenominators', + Writable => 'int16u', + Count => 3, + }, + 0x0934 => { Name => 'EdgeMapX3', Writable => 'int16u' }, #4 + 0x0935 => { Name => 'EdgeMapX4', Writable => 'int16u' }, #4 + 0x0936 => 'SharpenForThumbnail', #4 + 0x0937 => 'EdgeSpline', #4 + 0x0938 => 'DownSampleBy2Hor', #4 (int16s[4]) + 0x0939 => 'DownSampleBy2Ver', #4 (int16s[4]) + 0x093a => 'DownSampleBy4Hor', #4 (int16s[6]) + 0x093b => 'DownSampleBy4Ver', #4 (int16s[6]) + 0x093c => 'DownSampleBy3Hor', #4 (int16s[6]) + 0x093d => 'DownSampleBy3Ver', #4 (int16s[6]) + 0x093e => 'DownSampleBy6Hor', #4 (int16s[8]) + 0x093f => 'DownSampleBy6Ver', #4 (int16s[8]) + 0x0940 => 'DownSampleBy2Hor3MPdcr', #4 + 0x0941 => 'DownSampleBy2Ver3MPdcr', #4 + 0x0942 => 'ThumbnailResizeRatio', #4 + 0x0943 => { Name => 'AtCaptureUserCrop', Writable => 'int32u', Count => 4 }, #4 (Top, Left, Bottom, Right) + 0x0944 => { Name => 'ImageResolution', Writable => 'int32u' }, #4 (Contains enum for imageDcrRes or imageJpegRes) + 0x0945 => { Name => 'ImageResolutionJpg', Writable => 'int32u' }, #4 (Contains enum for imageJpegRes) + 0x094c => 'USMParametersLow', #4 (int16s[8]) + 0x094d => 'USMParametersMed', #4 (int16s[8]) + 0x094e => 'USMParametersHigh', #4 (int16s[8]) + 0x094f => 'USMParametersHost', #4 (int16s[10]) + 0x0950 => { Name => 'EdgeSplineLow', Binary => 1 }, #4 (rational64s[57]) + 0x0951 => { Name => 'EdgeSplineMed', Binary => 1 }, #4 (rational64s[69]) + 0x0952 => { Name => 'EdgeSplineHigh',Binary => 1 }, #4 (rational64s[69]) + 0x0953 => 'USMParametersHost6MP', #4 (int16s[10]) + 0x0954 => 'USMParametersHost3MP', #4 (int16s[10]) + 0x0960 => { Name => 'PatternImagerWidth', Writable => 'int16u' }, #4 + 0x0961 => { Name => 'PatternImagerHeight', Writable => 'int16u' }, #4 + 0x0962 => { Name => 'PatternAreaWidth', Writable => 'int16u' }, #4 + 0x0963 => { Name => 'PatternAreaHeight', Writable => 'int16u' }, #4 + 0x0964 => { Name => 'PatternCorrectionGains', Binary => 1 }, #4 (undef[48174]) + 0x0965 => 'PatternCorrectionOffsets', #4 + 0x0966 => { Name => 'PatternX', Writable => 'int16u' }, #4 + 0x0967 => { Name => 'PatternY', Writable => 'int16u' }, #4 + 0x0968 => { Name => 'PatternCorrectionFactors', Binary => 1 }, #4 (undef[48174]) + 0x0969 => { Name => 'PatternCorrectionFactorScale', Writable => 'int16u' }, #4 + 0x096a => { Name => 'PatternCropRows1', Writable => 'int16u' }, #4 + 0x096b => { Name => 'PatternCropRows2', Writable => 'int16u' }, #4 + 0x096c => { Name => 'PatternCropCols1', Writable => 'int16u' }, #4 + 0x096d => { Name => 'PatternCropCols2', Writable => 'int16u' }, #4 + 0x096e => 'PixelCorrectionGains', #4 + 0x096f => 'StitchRows', #4 (int16u[6]) + 0x0970 => 'StitchColumns', #4 (int16u) + 0x0971 => { Name => 'PixelCorrectionScale', Writable => 'int16u' }, #4 + 0x0972 => { Name => 'PixelCorrectionOffset',Writable => 'int16u' }, #4 + 0x0988 => 'LensTableIndex', #4 + 0x0992 => 'DiffTileGains602832', #4 + 0x0993 => 'DiffTileGains24t852822', #4 (reserved tags 2450-2459) + 0x099c => 'TileGainDeterminationTable', #4 (int16s[0]) + 0x099d => 'NemoBlurKernel', #4 (int16s[14]) + 0x099e => 'NemoTileSize', #4 (int16u) + 0x099f => 'NemoGainFactors', #4 (rational64s[4]) + 0x09a0 => 'NemoDarkLimit', #4 (int16u) + 0x09a1 => 'NemoHighlight12Limit', #4 + 0x09c4 => { Name => 'ImagerFileProductionLevel',Writable => 'int16u' }, #4 + 0x09c5 => { Name => 'ImagerFileDateCreated', Writable => 'int32u' }, #4 (unknown encoding - PH) + 0x09c6 => { Name => 'CalibrationVersion', Writable => 'string' }, #4 + 0x09c7 => { Name => 'ImagerFileTagsVersionStandard', Writable => 'int16u' }, #4 + 0x09c8 => { Name => 'IFCameraModel', Writable => 'string' }, #4 + 0x09c9 => { Name => 'CalibrationHistory', Writable => 'string' }, #4 + 0x09ca => { Name => 'CalibrationLog', Binary => 1 }, #4 (undef[1140]) + 0x09ce => { Name => 'SensorSerialNumber', Writable => 'string', Groups => { 2 => 'Camera' } }, #IB/4 + 0x09f6 => 'DefectConcealArtCorrectThres', #4 (no longer used) + 0x09f7 => 'SglColDCACThres1', #4 + 0x09f8 => 'SglColDCACThres2', #4 + 0x09f9 => 'SglColDCACTHres3', #4 + 0x0a01 => 'DblColDCACThres1', #4 + 0x0a02 => 'DblColDCACThres2', #4 + 0x0a0a => { Name => 'DefectConcealThresTable', Binary => 1 }, #4 (int16u[121/79]) + 0x0a28 => 'MonoUniqueMatrix', #4 + 0x0a29 => 'MonoMonitorMatrix', #4 + 0x0a2a => 'MonoToneScaleTable', #4 + # 0x0a32-0x0a3b "OmenInitialSurfaceRed(n)", ref 4 + # 0x0a3c-0x0a45 "OmenInitialSurfaceGoR(n)", ref 4 + # 0x0a46-0x0a4f "OmenInitialSurfaceBlue(n)", ref 4 + # 0x0a50-0x0a59 "OmenInitialSurfaceGoB(n)", ref 4 + 0x0a5a => 'OmenInitialScaling', #4 + 0x0a5b => 'OmenInitialRows', #4 + 0x0a5c => 'OmenInitialColumns', #4 + 0x0a5d => { Name => 'OmenInitialIPFStrength',Writable => 'int32s', Count => 4 }, #4 + 0x0a5e => { Name => 'OmenEarlyStrength', Writable => 'int32s', Count => 4 }, #4 + 0x0a5f => { Name => 'OmenAutoStrength', Writable => 'int32s', Count => 4 }, #4 + 0x0a60 => { Name => 'OmenAtCaptureStrength', Writable => 'int32s', Count => 4 }, #4 + 0x0a61 => 'OmenAtCaptureMode', #4 + 0x0a62 => { Name => 'OmenFocalLengthLimit', Writable => 'int16s' }, #4 + 0x0a64 => { Name => 'OmenSurfaceIndex', Writable => 'int16s' }, #4 (which InitialSurface to use) + 0x0a65 => 'OmenPercentToRationalLimitsRed', #4 (signed rationals for 0 and 100) + 0x0a66 => 'OmenPercentToRationalLimitsGoR', #4 (signed rationals for 0 and 100) + 0x0a67 => 'OmenPercentToRationalLimitsBlue', #4 (signed rationals for 0 and 100) + 0x0a68 => 'OmenPercentToRationalLimitsGoB', #4 (signed rationals for 0 and 100) + 0x0a6e => 'OmenEarlyGoBSurface', #4 + 0x0a6f => 'OmenEarlyGoBRows', #4 + 0x0a70 => 'OmenEarlyGoBColumns', #4 + 0x0a73 => 'OmenSmoothingKernel', #4 + 0x0a74 => 'OmenGradientOffset', #4 + 0x0a75 => 'OmenGradientKernel', #4 + 0x0a76 => 'OmenGradientKernelTaps', #4 + 0x0a77 => 'OmenRatioClipFactors', #4 + 0x0a78 => 'OmenRatioExclusionFactors', #4 + 0x0a79 => 'OmenGradientExclusionLimits', #4 + 0x0a7a => 'OmenROICoordinates', #4 + 0x0a7b => 'OmenROICoefficients', #4 + 0x0a7c => 'OmenRangeWeighting', #4 + 0x0a7d => 'OmenMeanToStrength', #4 + 0x0bb8 => 'FactoryWhiteGainsDaylight', #4 + 0x0bb9 => 'FactoryWhiteOffsetsDaylight', #4 + 0x0bba => 'DacGainsCoarse', #4 + 0x0bbb => 'DacGainsFine', #4 + 0x0bbc => 'DigitalExposureGains', #4 + 0x0bbd => 'DigitalExposureBiases', #4 + 0x0bbe => 'BlackClamp', #4 + 0x0bbf => 'ChannelCoarseGainAdjust', #4 + 0x0bc0 => 'BlackClampOffset', #4 + 0x0bf4 => 'DMPixelThresholdFactor', #4 (TIFF_RATIONAL) + 0x0bf5 => 'DMWindowThresholdFactor', #4 (TIFF_RATIONAL) + 0x0bf6 => 'DMTrimFraction', #4 (TIFF_RATIONAL) + 0x0bf7 => 'DMSmoothRejThresh', #4 (TIFF_RATIONAL) + 0x0bf8 => 'DMFillRejThresh', #4 (TIFF_RATIONAL) + 0x0bf9 => 'VMWsize', #4 (TIFF_SHORT) + 0x0bfa => 'DMErodeRadius', #4 (TIFF_SHORT) + 0x0bfb => 'DMNumPatches', #4 (TIFF_SHORT) + 0x0bfc => 'DMNoiseScale', #4 (TIFF_RATIONAL) + 0x0bfe => 'BrightDefectThreshold', #4 + 0x0bff => 'BrightDefectIntegrationMS', #4 + 0x0c00 => 'BrightDefectIsoCode', #4 + 0x0c03 => 'TopDarkRow1', #4 (support 330 dark map generation algorithm) + 0x0c04 => 'TopDarkRow2', #4 (these tags were 3175-3192 prior to 18Jan2001) + 0x0c05 => 'BottomDarkRow1', #4 + 0x0c06 => 'BottomDarkRow2', #4 + 0x0c07 => 'LeftDarkCol1', #4 + 0x0c08 => 'LeftDarkCol2', #4 + 0x0c09 => 'RightDarkCol1', #4 + 0x0c0a => 'RightDarkCol2', #4 + 0x0c0b => 'HMPixThresh', #4 + 0x0c0c => 'HMColThresh', #4 + 0x0c0d => 'HMWsize', #4 + 0x0c0e => 'HMColRejThresh', #4 + 0x0c0f => 'VMPixThresh', #4 + 0x0c10 => 'VMColThresh', #4 + 0x0c11 => 'VMNbands', #4 + 0x0c12 => 'VMColDropThresh', #4 + 0x0c13 => 'VMPatchResLimit', #4 + 0x0c14 => 'MapScale', #4 + 0x0c1c => 'Klut', #4 + 0x0c1d => 'RimNonlinearity', #4 (Obsolete) + 0x0c1e => 'InverseRimNonlinearity', #4 (Obsolete) + 0x0c1f => 'RembrandtToneScale', #4 (Obsolete) + 0x0c20 => 'RimToNifColorTransform', #4 (Obsolete) + 0x0c21 => 'RimToNifScaleFactor', #4 (Obsolete) + 0x0c22 => 'NifNonlinearity', #4 (Obsolete) + 0x0c23 => 'SBALogTransform', #4 (Obsolete) + 0x0c24 => 'InverseSBALogTransform', #4 (Obsolete) + 0x0c25 => { Name => 'SBABlack', Writable => 'int16u' }, #4 + 0x0c26 => { Name => 'SBAGray', Writable => 'int16u' }, #4 + 0x0c27 => { Name => 'SBAWhite', Writable => 'int16u' }, #4 + 0x0c28 => { Name => 'GaussianWeights', Binary => 1 }, #4 (int16u[864]) + 0x0c29 => { Name => 'SfsBoundary', Binary => 1 }, #4 (int16u[6561]) + 0x0c2a => 'CoringTableBest', #4 (Obsolete) + 0x0c2b => 'CoringTableBetter', #4 (Obsolete) + 0x0c2c => 'CoringTableGood', #4 (Obsolete) + 0x0c2d => 'ExposureReferenceGain', #4 (Obsolete) + 0x0c2e => 'ExposureReferenceOffset', #4 (Obsolete) + 0x0c2f => 'SBARedBalanceLut', #4 (Obsolete) + 0x0c30 => 'SBAGreenBalanceLut', #4 (Obsolete) + 0x0c31 => 'SBABlueBalanceLut', #4 (Obsolete) + 0x0c32 => { Name => 'SBANeutralBAL', Writable => 'int32s' }, #4 + 0x0c33 => { Name => 'SBAGreenMagentaBAL', Writable => 'int32s' }, #4 + 0x0c34 => { Name => 'SBAIlluminantBAL', Writable => 'int32s' }, #4 + 0x0c35 => { Name => 'SBAAnalysisComplete', Writable => 'int8u' }, #4 + 0x0c36 => 'JPEGQTableBest', #4 + 0x0c37 => 'JPEGQTableBetter', #4 + 0x0c38 => 'JPEGQTableGood', #4 + 0x0c39 => 'RembrandtPortraitToneScale', #4 (Obsolete) + 0x0c3a => 'RembrandtConsumerToneScale', #4 (Obsolete) + 0x0c3b => 'CFAGreenThreshold1', #4 (Now CFAGreenThreshold1H) + 0x0c3c => 'CFAGreenThreshold2', #4 (Now CFAGreenThreshold2V) + 0x0c3d => { Name => 'QTableLarge50Pct', Binary => 1 }, #4 (undef[130]) + 0x0c3e => { Name => 'QTableLarge67Pct', Binary => 1 }, #4 (undef[130]) + 0x0c3f => { Name => 'QTableLarge100Pct', Binary => 1 }, #4 (undef[130]) + 0x0c40 => { Name => 'QTableMedium50Pct', Binary => 1 }, #4 (undef[130]) + 0x0c41 => { Name => 'QTableMedium67Pct', Binary => 1 }, #4 (undef[130]) + 0x0c42 => { Name => 'QTableMedium100Pct', Binary => 1 }, #4 (undef[130]) + 0x0c43 => { Name => 'QTableSmall50Pct', Binary => 1 }, #4 (undef[130]) + 0x0c44 => { Name => 'QTableSmall67Pct', Binary => 1 }, #4 (undef[130]) + 0x0c45 => { Name => 'QTableSmall100Pct', Binary => 1 }, #4 (undef[130]) + 0x0c46 => { Name => 'SBAHighGray', Writable => 'int16u' }, #4 + 0x0c47 => { Name => 'SBALowGray', Writable => 'int16u' }, #4 + 0x0c48 => { Name => 'CaptureLook', Writable => 'int16u' }, #4 (was "ToneScaleFlag") + 0x0c49 => { Name => 'SBAIllOffset', Writable => 'int16s' }, #4 + 0x0c4a => { Name => 'SBAGmOffset', Writable => 'int16s' }, #4 + 0x0c4b => 'NifNonlinearity12Bit', #4 (Obsolete) + 0x0c4c => 'SharpeningOn', #4 (Obsolete) + 0x0c4d => 'NifNonlinearity16Bit', #4 (Obsolete) + 0x0c4e => 'RawHistogram', #4 + 0x0c4f => 'RawCFAComponentAverages', #4 + 0x0c50 => 'DisableFlagsPresent', #4 + 0x0c51 => 'DelayCols', #4 + 0x0c52 => 'DummyColsLeft', #4 + 0x0c53 => 'TrashColsRight', #4 + 0x0c54 => 'BlackColsRight', #4 + 0x0c55 => 'DummyColsRight', #4 + 0x0c56 => 'OverClockColsRight', #4 + 0x0c57 => 'UnusedBlackRowsTopOut', #4 + 0x0c58 => 'TrashRowsBottom', #4 + 0x0c59 => 'BlackRowsBottom', #4 + 0x0c5a => 'OverClockRowsBottom', #4 + 0x0c5b => 'BlackColsLeft', #4 + 0x0c5c => 'BlackRowsTop', #4 + 0x0c5d => 'PartialActiveColsLeft', #4 + 0x0c5e => 'PartialActiveColsRight', #4 + 0x0c5f => 'PartialActiveRowsTop', #4 + 0x0c60 => 'PartialActiveRowsBottom', #4 + 0x0c61 => { Name => 'ProcessBorderColsLeft', Writable => 'int16u' }, #4 + 0x0c62 => { Name => 'ProcessBorderColsRight', Writable => 'int16u' }, #4 + 0x0c63 => { Name => 'ProcessBorderRowsTop', Writable => 'int16u' }, #4 + 0x0c64 => { Name => 'ProcessBorderRowsBottom', Writable => 'int16u' }, #4 + 0x0c65 => 'ActiveCols', #4 + 0x0c66 => 'ActiveRows', #4 + 0x0c67 => 'FirstLines', #4 + 0x0c68 => 'UnusedBlackRowsTopIn', #4 + 0x0c69 => 'UnusedBlackRowsBottomIn', #4 + 0x0c6a => 'UnusedBlackRowsBottomOut', #4 + 0x0c6b => 'UnusedBlackColsLeftOut', #4 + 0x0c6c => 'UnusedBlackColsLeftIn', #4 + 0x0c6d => 'UnusedBlackColsRightIn', #4 + 0x0c6e => 'UnusedBlackColsRightOut', #4 + 0x0c6f => { Name => 'CFAOffsetRows', Writable => 'int32u' }, #4 + 0x0c70 => { Name => 'ShiftCols', Writable => 'int16s' }, #4 + 0x0c71 => { Name => 'CFAOffsetCols', Writable => 'int32u' }, #4 + 0x0c76 => 'DarkMapScale', #4 + 0x0c77 => 'HMapHandling', #4 + 0x0c78 => 'VMapHandling', #4 + 0x0c79 => 'DarkThreshold', #4 + 0x0c7a => { Name => 'DMDitherMatrix', Writable => 'int16u' }, #4 + 0x0c7b => { Name => 'DMDitherMatrixWidth', Writable => 'int16u' }, #4 + 0x0c7c => { Name => 'DMDitherMatrixHeight', Writable => 'int16u' }, #4 + 0x0c7d => { Name => 'MaxPixelValueThreshold', Writable => 'int16u' }, #4 + 0x0c7e => { Name => 'HoleFillDeltaThreshold', Writable => 'int16u' }, #4 + 0x0c7f => { Name => 'DarkPedestal', Writable => 'int16u' }, #4 + 0x0c80 => { Name => 'ImageProcessingFileTagsVersionNumber', Writable => 'int16u' }, #4 + 0x0c81 => { Name => 'ImageProcessingFileDateCreated', Writable => 'string', Groups => { 2 => 'Time' } }, #4 + 0x0c82 => { Name => 'DoublingMicrovolts', Writable => 'int32s' }, #4 + 0x0c83 => { #4 + Name => 'DarkFrameShortExposure', + Writable => 'int32u', + ValueConv => '$val / 1e6', # (microseconds) + ValueConvInv => '$val * 1e6', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => '$val', + }, + 0x0c84 => { #4 + Name => 'DarkFrameLongExposure', + Writable => 'int32u', + ValueConv => '$val / 1e6', # (microseconds) + ValueConvInv => '$val * 1e6', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => '$val', + }, + 0x0c85 => { Name => 'DarkFrameCountFactor', Writable => 'rational64u' }, #4 + 0x0c88 => { Name => 'HoleFillDarkDeltaThreshold', Writable => 'int16u' }, #4 + 0x0c89 => 'FarkleWhiteThreshold', #4 + 0x0c8a => { Name => 'ColumnResetOffsets', Binary => 1 }, #4 (int16u[3012]) + 0x0c8b => { Name => 'ColumnGainFactors', Binary => 1 }, #4 (int16u[3012]) + # 0x0c94-0x0c9d ColumnOffsets (int16u[3012]), ref 4 + 0x0c8c => 'Channel0LagKernel', #4 + 0x0c8d => 'Channel1LagKernel', #4 + 0x0c8e => 'Channel2LagKernel', #4 + 0x0c8f => 'Channel3LagKernel', #4 + 0x0c90 => 'BluegrassTable', #4 + 0x0c91 => 'BluegrassScale1', #4 + 0x0c92 => 'BluegrassScale2', #4 + 0x0ce4 => 'FinishedFileProcessingRequest', #4 + 0x0ce5 => { Name => 'FirmwareVersion', Writable => 'string', Groups => { 2 => 'Camera' } }, # ("ProcessingSoftware", ref 4) + 0x0ce6 => 'HostSoftwareExportVersion', #4 (ULONG only exists if made by host SW) + 0x0ce7 => { #4 + Name => 'HostSoftwareRendering', + Writable => 'int32u', + PrintConv => { + 0 => 'Normal (sRGB)', + 1 => 'Linear (camera RGB)', + 2 => 'Pro Photo RGB', + 3 => 'Unknown', + 4 => 'Other Profile', + }, + }, + 0x0dac => 'DCS3XXProcessingInfoIFD', #4 (Obsolete) + 0x0dad => 'DCS3XXProcessingInfo', #4 (Obsolete) + 0x0dae => { Name => 'IPAVersion', Writable => 'int32u' }, #4 + 0x0db6 => 'FinishIPAVersion', #4 + 0x0db7 => 'FinishIPFVersion', #4 + 0x0db8 => { #4 + Name => 'FinishFileType', + Writable => 'int32u', + PrintConv => { + 0 => 'JPEG Best', + 1 => 'JPEG Better', + 2 => 'JPEG Good', + 3 => 'TIFF RGB', + }, + }, + 0x0db9 => { #4 + Name => 'FinishResolution', + Writable => 'int32u', + PrintConv => { + 0 => '100%', + 1 => '67%', + 2 => '50%', + 3 => '25%', + }, + }, + 0x0dba => { #4 + Name => 'FinishNoise', + Writable => 'int32u', + PrintConv => { + 0 => 'Normal', + 1 => 'Strong', + 2 => 'Low', + }, + }, + 0x0dbb => { #4 + Name => 'FinishSharpening', + Writable => 'int32u', + PrintConv => { + 0 => 'None', + 1 => 'High', + 2 => 'Medium', + 3 => 'Low', + }, + }, + 0x0dbc => { #4 + Name => 'FinishLook', + Writable => 'int32u', + PrintConv => { + 0 => 'Product', + 1 => 'Portrait', + 2 => 'Product Reduced', + 3 => 'Portrait Reduced', + 4 => 'Monochrome Product', + 5 => 'Monochrome Portrait', + 6 => 'Wedding', + 7 => 'Event', + 8 => 'Product Hi Color', + 9 => 'Portrait Hi Color', + # (past this is not yet implemented) + 10 => 'Product Hi Color Hold', + 11 => 'Portrait Hi Color Hold', + 13 => 'DCS BW Normal', + 14 => 'DCS BW Wratten 8', + 15 => 'DCS BW Wratten 25', + 16 => 'DCS Sepia 1', + 17 => 'DCS Sepia 2', + }, + }, + 0x0dbd => { #4 + Name => 'FinishExposure', + Writable => 'int32u', + PrintConv => { 0 => 'Yes', 1 => 'No' }, + }, + 0x0e0b => { Name => 'SigmaScalingFactorLowRes', Writable => 'rational64u' }, #4 (for scaling sigma tables in-camera) + 0x0e0c => { Name => 'SigmaScalingFactorCamera', Writable => 'rational64u' }, #4 (for scaling sigma tables in-camera) + 0x0e0d => { Name => 'SigmaImpulseParameters', Writable => 'int16u', Count => -1 }, #4 (for impulse reduction control) + 0x0e0e => { Name => 'SigmaNoiseThreshTableV2', Binary => 1 }, #4 (replaces V2 caltable) + 0x0e0f => { Name => 'SigmaSizeTable', Writable => 'int16u', Count => -1 }, #4 + 0x0e10 => 'DacGainsCoarseAdjPreIF41', #4 (Tag not used due to abandoned implementation) + 0x0e11 => { Name => 'SigmaNoiseFilterCalTableV1', Binary => 1 }, #4 (undef[418]) (Defines Version 1 of Sigma Noise Filter table within .IF/.IPF) + 0x0e12 => { Name => 'SigmaNoiseFilterTableV1', Binary => 1}, #4 (Defines Version 1 of Sigma Noise Filter table within image) + 0x0e13 => 'Lin12ToKlut8', #4 + 0x0e14 => 'SigmaNoiseFilterTableV1Version', #4 (int16u/int32u) (This tag describes the version level of the SigmaNoiseFilterTableV1 class of tags) + 0x0e15 => 'Lin12ToKlut12', #4 + 0x0e16 => 'Klut12ToLin12', #4 + 0x0e17 => { Name => 'NifNonlinearity12To16', Binary => 1 }, #4 (int16u[4096]) + 0x0e18 => { Name => 'SBALog12Transform', Binary => 1 }, #4 (int16u[4096]) + 0x0e19 => { Name => 'InverseSBALog12Transform', Binary=> 1 }, #4 (int16u[3613]) + 0x0e1a => { Name => 'ToneScale0', Binary => 1 }, #4 (int16u[4096]) (Was Portrait12ToneScale on DCS3XX 1.3.1) + 0x0e1b => { Name => 'ToneScale1', Binary => 1 }, #4 (int16u[4096]) (Was Consumer12ToneScale on DCS3XX 1.3.1) + 0x0e1c => { Name => 'ToneScale2', Binary => 1 }, #4 (int16u[4096]) + 0x0e1d => { Name => 'ToneScale3', Binary => 1 }, #4 (int16u[4096]) + 0x0e1e => { Name => 'ToneScale4', Binary => 1 }, #4 (int16u[4096]) + 0x0e1f => { Name => 'ToneScale5', Binary => 1 }, #4 (int16u[4096]) + 0x0e20 => { Name => 'ToneScale6', Binary => 1 }, #4 (int16u[4096]) + 0x0e21 => { Name => 'ToneScale7', Binary => 1 }, #4 (int16u[4096]) + 0x0e22 => { Name => 'ToneScale8', Binary => 1 }, #4 (int16u[4096]) + 0x0e23 => { Name => 'ToneScale9', Binary => 1 }, #4 (int16u[4096]) + 0x0e24 => 'DayMat0', #4 (Obsolete) + 0x0e25 => 'DayMat1', #4 (Obsolete) + 0x0e26 => 'DayMat2', #4 (Obsolete) + 0x0e27 => 'DayMat3', #4 (Obsolete) + 0x0e28 => 'DayMat4', #4 (Obsolete) + 0x0e29 => 'DayMat5', #4 (Obsolete) + 0x0e2a => 'DayMat6', #4 (Obsolete) + 0x0e2b => 'DayMat7', #4 (Obsolete) + 0x0e2c => 'DayMat8', #4 (Obsolete) + 0x0e2d => 'DayMat9', #4 (Obsolete) + 0x0e2e => 'TungMat0', #4 (Obsolete) + 0x0e2f => 'TungMat1', #4 (Obsolete) + 0x0e30 => 'TungMat2', #4 (Obsolete) + 0x0e31 => 'TungMat3', #4 (Obsolete) + 0x0e32 => 'TungMat4', #4 (Obsolete) + 0x0e33 => 'TungMat5', #4 (Obsolete) + 0x0e34 => 'TungMat6', #4 (Obsolete) + 0x0e35 => 'TungMat7', #4 (Obsolete) + 0x0e36 => 'TungMat8', #4 (Obsolete) + 0x0e37 => 'TungMat9', #4 (Obsolete) + 0x0e38 => 'FluorMat0', #4 (Obsolete) + 0x0e39 => 'FluorMat1', #4 (Obsolete) + 0x0e3a => 'FluorMat2', #4 (Obsolete) + 0x0e3b => 'FluorMat3', #4 (Obsolete) + 0x0e3c => 'FluorMat4', #4 (Obsolete) + 0x0e3d => 'FluorMat5', #4 (Obsolete) + 0x0e3e => 'FluorMat6', #4 (Obsolete) + 0x0e3f => 'FluorMat7', #4 (Obsolete) + 0x0e40 => 'FluorMat8', #4 (Obsolete) + 0x0e41 => 'FluorMat9', #4 (Obsolete) + 0x0e42 => 'FlashMat0', #4 (Obsolete) + 0x0e43 => 'FlashMat1', #4 (Obsolete) + 0x0e44 => 'FlashMat2', #4 (Obsolete) + 0x0e45 => 'FlashMat3', #4 (Obsolete) + 0x0e46 => 'FlashMat4', #4 (Obsolete) + 0x0e47 => 'FlashMat5', #4 (Obsolete) + 0x0e48 => 'FlashMat6', #4 (Obsolete) + 0x0e49 => 'FlashMat7', #4 (Obsolete) + 0x0e4a => 'FlashMat8', #4 (Obsolete) + 0x0e4b => 'FlashMat9', #4 (Obsolete) + 0x0e4c => { #IB + Name => 'KodakLook', # ("LookNameTable", ref 4) + Format => 'undef', + Writable => 'string', + ValueConv => '$val=~tr/\0/\n/; $val', + ValueConvInv => '$val=~tr/\n/\0/; $val', + }, + 0x0e4d => { Name => 'IPFCameraModel', Writable => 'string' }, #4 + 0x0e4e => { Name => 'AH2GreenInterpolationThreshold', Writable => 'int16u' }, #4 + 0x0e4f => { Name => 'ResamplingKernelDenominators067', Writable => 'int16u', Count => 3 }, #4 (table of sharpening denoms; 0=Low, 1=Medium, 2=High) + 0x0e50 => { Name => 'ResamplingKernelDenominators050', Writable => 'int16u', Count => 3 }, #4 (table of sharpening denoms; 0=Low, 1=Medium, 2=High) + 0x0e51 => { Name => 'ResamplingKernelDenominators100', Writable => 'int16u', Count => 3 }, #4 (table of sharpening denoms; 0=Low, 1=Medium, 2=High) + 0x0e56 => 'LookMat0', #4 (rational64s[9]) + 0x0e57 => 'LookMat1', #4 (rational64s[9]) + 0x0e58 => 'LookMat2', #4 (rational64s[9]) + 0x0e59 => 'LookMat3', #4 (rational64s[9]) + 0x0e5a => 'LookMat4', #4 (rational64s[9]) + 0x0e5b => 'LookMat5', #4 (rational64s[9]) + 0x0e5c => 'LookMat6', #4 (rational64s[9]) + 0x0e5d => 'LookMat7', #4 (rational64s[9]) + 0x0e5e => 'LookMat8', #4 (rational64s[9]) + 0x0e5f => 'LookMat9', #4 (rational64s[9]) + 0x0e60 => { #4 + Name => 'CFAInterpolationAlgorithm', + Writable => 'int16u', + PrintConv => { 0 => 'AH2', 1 => 'Karnak' }, + }, + 0x0e61 => { #4 + Name => 'CFAInterpolationMetric', + Writable => 'int16u', + PrintConv => { 0 => 'Linear12', 1 => 'KLUT12' }, + }, + 0x0e62 => { Name => 'CFAZipperFixThreshold', Writable => 'int16u' }, #4 + 0x0e63 => { Name => 'NoiseReductionParametersKhufuRGB', Writable => 'int16u', Count => 9 }, #4 + 0x0e64 => { Name => 'NoiseReductionParametersKhufu6MP', Writable => 'int16u', Count => 9 }, #4 + 0x0e65 => { Name => 'NoiseReductionParametersKhufu3MP', Writable => 'int16u', Count => 9 }, #4 + 0x0e6a => { Name => 'ChromaNoiseHighFThresh', Writable => 'int32u', Count => 2 }, #4 + 0x0e6b => { Name => 'ChromaNoiseLowFThresh', Writable => 'int32u', Count => 2 }, #4 + 0x0e6c => { Name => 'ChromaNoiseEdgeMapThresh', Writable => 'int32u' }, #4 + 0x0e6d => { Name => 'ChromaNoiseColorSpace', Writable => 'int32u' }, #4 + 0x0e6e => { Name => 'EnableChromaNoiseReduction', Writable => 'int16u' }, #4 + # 9 values for noise reduction parameters: + # 0 - NRLowType + # 1 - NRLowRadius + # 2 - NRLowStrength + # 3 - NRMediumType + # 4 - NRMediumRadius + # 5 - NRMediumStrength + # 6 - NRHighType + # 7 - NRHighRadius + # 8 - NRHighStrength + # NRType values: + # 0 = None + # 1 = SigmaChroma + # 2 = SigmaOnly + # 3 = SigmaMoire + # 4 = SigmaChromaWithRadius + # 5 = SigmaMoireWithRadius + # 6 = SigmaExpert (aka Khufu) + # 7 = SigmaWithRadius + 0x0e6f => { Name => 'NoiseReductionParametersHostRGB', Writable => 'int16u', Count => 9 }, #4 + 0x0e70 => { Name => 'NoiseReductionParametersHost6MP', Writable => 'int16u', Count => 9 }, #4 + 0x0e71 => { Name => 'NoiseReductionParametersHost3MP', Writable => 'int16u', Count => 9 }, #4 + 0x0e72 => { Name => 'NoiseReductionParametersCamera', Writable => 'int16u', Count => 6 }, #4 + 0x0e73 => { #4 + Name => 'NoiseReductionParametersAtCapture', + Writable => 'int16u', + Count => 6, + # 6 values: + # 0 - Algorithm type + # 1 - Radius + # 2 - Strength + # 3 - Khufu Luma + # 4 - Reserved 1 + # 5 - Reserved 2 + }, + 0x0e74 => { Name => 'LCDMatrix', Writable => 'rational64s', Count => 9 }, #4 + 0x0e75 => { Name => 'LCDMatrixChickFix', Writable => 'rational64s', Count => 9 }, #4 + 0x0e76 => { Name => 'LCDMatrixMarvin', Writable => 'rational64s', Count => 9 }, #4 + 0x0e7c => { Name => 'LCDGammaTableChickFix',Binary => 1 }, #4 (int16u[4096]) + 0x0e7d => { Name => 'LCDGammaTableMarvin', Binary => 1 }, #4 (int16u[4096]) + 0x0e7e => { Name => 'LCDGammaTable', Binary => 1 }, #4 (int16u[4096]) + 0x0e7f => 'LCDSharpeningF1', #4 (int16s[4]) + 0x0e80 => 'LCDSharpeningF2', #4 + 0x0e81 => 'LCDSharpeningF3', #4 + 0x0e82 => 'LCDSharpeningF4', #4 + 0x0e83 => 'LCDEdgeMapX1', #4 + 0x0e84 => 'LCDEdgeMapX2', #4 + 0x0e85 => 'LCDEdgeMapX3', #4 + 0x0e86 => 'LCDEdgeMapX4', #4 + 0x0e87 => 'LCDEdgeMapSlope', #4 + 0x0e88 => 'YCrCbMatrix', #4 + 0x0e89 => 'LCDEdgeSpline', #4 (rational64s[9]) + 0x0e92 => { Name => 'Fac18Per', Writable => 'int16u' }, #4 + 0x0e93 => { Name => 'Fac170Per', Writable => 'int16u' }, #4 + 0x0e94 => { Name => 'Fac100Per', Writable => 'int16u' }, #4 + 0x0e9b => 'ExtraTickLocations', #4 (int16u[7]) + 0x0e9c => { Name => 'RGBtoeV0', Binary => 1 }, #4 (int16s[256]) + 0x0e9d => { Name => 'RGBtoeV1', Binary => 1 }, #4 (int16s[256]) + 0x0e9e => { Name => 'RGBtoeV2', Binary => 1 }, #4 (int16s[256]) + 0x0e9f => { Name => 'RGBtoeV3', Binary => 1 }, #4 (int16s[256]) + 0x0ea0 => { Name => 'RGBtoeV4', Binary => 1 }, #4 (int16s[256]) + 0x0ea1 => { Name => 'RGBtoeV5', Binary => 1 }, #4 (int16s[256]) + 0x0ea2 => { Name => 'RGBtoeV6', Binary => 1 }, #4 (int16s[256]) + 0x0ea3 => { Name => 'RGBtoeV7', Binary => 1 }, #4 (int16s[256]) + 0x0ea4 => { Name => 'RGBtoeV8', Binary => 1 }, #4 (int16s[256]) + 0x0ea5 => { Name => 'RGBtoeV9', Binary => 1 }, #4 (int16s[256]) + 0x0ea6 => { Name => 'LCDHistLUT0', Binary => 1 }, #4 (rational64s[48]) + 0x0ea7 => { Name => 'LCDHistLUT1', Binary => 1 }, #4 (rational64s[57]) + 0x0ea8 => { Name => 'LCDHistLUT2', Binary => 1 }, #4 (rational64s[48]) + 0x0ea9 => { Name => 'LCDHistLUT3', Binary => 1 }, #4 (rational64s[57]) + 0x0eaa => { Name => 'LCDHistLUT4', Binary => 1 }, #4 (rational64s[48]) + 0x0eab => { Name => 'LCDHistLUT5', Binary => 1 }, #4 (rational64s[57]) + 0x0eac => { Name => 'LCDHistLUT6', Binary => 1 }, #4 (rational64s[48]) + 0x0ead => { Name => 'LCDHistLUT7', Binary => 1 }, #4 (rational64s[48]) + 0x0eae => { Name => 'LCDHistLUT8', Binary => 1 }, #4 + 0x0eaf => { Name => 'LCDHistLUT9', Binary => 1 }, #4 + 0x0eb0 => 'LCDLinearClipValue', #4 + 0x0ece => 'LCDStepYvalues', #4 (int8u[10]) + 0x0ecf => 'LCDStepYvaluesChickFix', #4 (int8u[10]) + 0x0ed0 => 'LCDStepYvaluesMarvin', #4 (int8u[10]) + 0x0ed8 => { Name => 'InterpolationCoefficients', Binary => 1 }, #4 (int16s[69]) + 0x0ed9 => 'InterpolationCoefficients6MP', #4 + 0x0eda => 'InterpolationCoefficients3MP', #4 + 0x0f00 => { Name => 'NoiseReductionParametersHostNormal', Binary => 1 }, #4 (int16u[140]) + 0x0f01 => { Name => 'NoiseReductionParametersHostStrong', Binary => 1 }, #4 (int16u[140]) + 0x0f02 => { Name => 'NoiseReductionParametersHostLow', Binary => 1 }, #4 + 0x0f0a => { Name => 'MariahTextureThreshold', Writable => 'int16u' }, #4 + 0x0f0b => { Name => 'MariahMapLoThreshold', Writable => 'int16u' }, #4 + 0x0f0c => { Name => 'MariahMapHiThreshold', Writable => 'int16u' }, #4 + 0x0f0d => { Name => 'MariahChromaBlurSize', Writable => 'int16u' }, #4 + 0x0f0e => { Name => 'MariahSigmaThreshold', Writable => 'int16u' }, #4 + 0x0f0f => { Name => 'MariahThresholds', Binary => 1 }, #4 + 0x0f10 => { Name => 'MariahThresholdsNormal', Binary => 1 }, #4 (int16u[140]) + 0x0f11 => { Name => 'MariahThresholdsStrong', Binary => 1 }, #4 (int16u[140]) + 0x0f12 => { Name => 'MariahThresholdsLow', Binary => 1 }, #4 + 0x0f14 => 'KhufuLinearRedMixingCoefficient', #4 + 0x0f15 => 'KhufuLinearGreenMixingCoefficient', #4 + 0x0f16 => 'KhufuLinearBlueMixingCoefficient', #4 + 0x0f17 => 'KhufuUSpaceC2MixingCoefficient', #4 + 0x0f18 => 'KhufuSigmaGaussianWeights', #4 + 0x0f19 => 'KhufuSigmaScalingFactors6MP', #4 (rational64u[6]) + 0x0f1a => 'KhufuSigmaScalingFactors3MP', #4 (rational64u[6]) + 0x0f1b => 'KhufuSigmaScalingFactors14MP', #4 + # 0x0f1e-0x0f27 - KhufuLinearRGBtoLogRGB(n), (for Khufu) ref 4 + # 0x0f28-0x0f31 - KhufuLogRGBtoLinearRGB(n), (for Khufu) ref 4 + 0x0f32 => { Name => 'KhufuI0Thresholds', Binary => 1 }, #4 (int32s[348]) + 0x0f33 => { Name => 'KhufuI1Thresholds', Binary => 1 }, #4 (int32s[348]) + 0x0f34 => { Name => 'KhufuI2Thresholds', Binary => 1 }, #4 (int32s[348]) + 0x0f35 => { Name => 'KhufuI3Thresholds', Binary => 1 }, #4 (int32s[348]) + 0x0f36 => { Name => 'KhufuI4Thresholds', Binary => 1 }, #4 (int32s[348]) + 0x0f37 => { Name => 'KhufuI5Thresholds', Binary => 1 }, #4 (int32s[348]) + 0x0f3c => { Name => 'CondadoDayBVThresh', Writable => 'int16u' }, #4 + 0x0f3d => { Name => 'CondadoNeuRange', Writable => 'int16u' }, #4 + 0x0f3e => { Name => 'CondadoBVFactor', Writable => 'int16s' }, #4 + 0x0f3f => { Name => 'CondadoIllFactor', Writable => 'int16s' }, #4 + 0x0f40 => { Name => 'CondadoTunThresh', Writable => 'int16s' }, #4 + 0x0f41 => { Name => 'CondadoFluThresh', Writable => 'int16s' }, #4 + 0x0f42 => { Name => 'CondadoDayOffsets', Writable => 'int16s', Count => 2 }, #4 + 0x0f43 => { Name => 'CondadoTunOffsets', Writable => 'int16s', Count => 2 }, #4 + 0x0f44 => { Name => 'CondadoFluOffsets', Writable => 'int16s', Count => 2 }, #4 + 0x0f5a => 'ERIMMToCRGB0Spline', #4 (rational64s[33]) + 0x0f5b => 'ERIMMToCRGB1Spline', #4 (rational64s[36]) + 0x0f5c => 'ERIMMToCRGB2Spline', #4 (rational64s[33]) + 0x0f5d => 'ERIMMToCRGB3Spline', #4 (rational64s[33]) + 0x0f5e => 'ERIMMToCRGB4Spline', #4 (rational64s[33]) + 0x0f5f => 'ERIMMToCRGB5Spline', #4 (rational64s[33]) + 0x0f60 => 'ERIMMToCRGB6Spline', #4 (rational64s[33]) + 0x0f61 => 'ERIMMToCRGB7Spline', #4 (rational64s[33]) + 0x0f62 => 'ERIMMToCRGB8Spline', #4 + 0x0f63 => 'ERIMMToCRGB9Spline', #4 + 0x0f64 => 'CRGBToERIMM0Spline', #4 (rational64s[27]) + 0x0f65 => 'CRGBToERIMM1Spline', #4 (rational64s[54]) + 0x0f66 => 'CRGBToERIMM2Spline', #4 (rational64s[27]) + 0x0f67 => 'CRGBToERIMM3Spline', #4 (rational64s[54]) + 0x0f68 => 'CRGBToERIMM4Spline', #4 (rational64s[27]) + 0x0f69 => 'CRGBToERIMM5Spline', #4 (rational64s[54]) + 0x0f6a => 'CRGBToERIMM6Spline', #4 (rational64s[27]) + 0x0f6b => 'CRGBToERIMM7Spline', #4 (rational64s[27]) + 0x0f6c => 'CRGBToERIMM8Spline', #4 + 0x0f6d => 'CRGBToERIMM9Spline', #4 + 0x0f6e => 'ERIMMNonLinearitySpline', #4 (rational64s[42]) + 0x0f6f => 'Delta12To8Spline', #4 (rational64s[12]) + 0x0f70 => 'Delta8To12Spline', #4 (rational64s[12]) + 0x0f71 => 'InverseMonitorMatrix', #4 (rational64s[9]) + 0x0f72 => { Name => 'NifNonlinearityExt', Binary => 1 }, #4 (int16s[8000]) + 0x0f73 => { Name => 'InvNifNonLinearity', Binary => 1 }, #4 (int16u[256]) + 0x0f74 => 'RIMM13ToERIMM12Spline', #4 (rational64s[51]) + 0x0f78 => 'ToneScale0Spline', #4 (rational64s[57]) + 0x0f79 => 'ToneScale1Spline', #4 (rational64s[51]) + 0x0f7a => 'ToneScale2Spline', #4 (rational64s[57]) + 0x0f7b => 'ToneScale3Spline', #4 (rational64s[51]) + 0x0f7c => 'ToneScale4Spline', #4 (rational64s[57]) + 0x0f7d => 'ToneScale5Spline', #4 (rational64s[51]) + 0x0f7e => 'ToneScale6Spline', #4 (rational64s[57]) + 0x0f7f => 'ToneScale7Spline', #4 (rational64s[57]) + 0x0f80 => 'ToneScale8Spline', #4 + 0x0f81 => 'ToneScale9Spline', #4 + 0x0f82 => 'ERIMMToneScale0Spline', #4 (rational64s[60]) + 0x0f83 => 'ERIMMToneScale1Spline', #4 (rational64s[54]) + 0x0f84 => 'ERIMMToneScale2Spline', #4 (rational64s[60]) + 0x0f85 => 'ERIMMToneScale3Spline', #4 (rational64s[54]) + 0x0f86 => 'ERIMMToneScale4Spline', #4 (rational64s[60]) + 0x0f87 => 'ERIMMToneScale5Spline', #4 (rational64s[54]) + 0x0f88 => 'ERIMMToneScale6Spline', #4 (rational64s[60]) + 0x0f89 => 'ERIMMToneScale7Spline', #4 (rational64s[60]) + 0x0f8a => 'ERIMMToneScale8Spline', #4 + 0x0f8b => 'ERIMMToneScale9Spline', #4 + 0x0f8c => 'RIMMToCRGB0Spline', #4 (rational64s[66]) + 0x0f8d => 'RIMMToCRGB1Spline', #4 (rational64s[84]) + 0x0f8e => 'RIMMToCRGB2Spline', #4 (rational64s[66]) + 0x0f8f => 'RIMMToCRGB3Spline', #4 (rational64s[84]) + 0x0f90 => 'RIMMToCRGB4Spline', #4 (rational64s[66]) + 0x0f91 => 'RIMMToCRGB5Spline', #4 (rational64s[84]) + 0x0f92 => 'RIMMToCRGB6Spline', #4 (rational64s[66]) + 0x0f93 => 'RIMMToCRGB7Spline', #4 (rational64s[66]) + 0x0f94 => 'RIMMToCRGB8Spline', #4 + 0x0f95 => 'RIMMToCRGB9Spline', #4 + 0x0fa0 => 'QTableLarge25Pct', #4 + 0x0fa1 => 'QTableMedium25Pct', #4 + 0x0fa2 => 'QTableSmall25Pct', #4 + 0x1130 => 'NoiseReductionKernel', #4 (Noise filter kernel. No longer needed) + 0x1388 => 'UserMetaData', #4 (undef[0]) + 0x1389 => { Name => 'InputProfile', Writable => 'undef', Binary => 1 }, #IB ("SourceProfile", ref 4) + 0x138a => { Name => 'KodakLookProfile', Writable => 'undef', Binary => 1 }, #IB ("LookProfile", ref 4) + 0x138b => { Name => 'OutputProfile', Writable => 'undef', Binary => 1 }, #IB ("DestinationProfile", ref 4) + 0x1390 => { Name => 'SourceProfilePrefix', Writable => 'string' }, #4 (eg. 'DCSProSLRn') + 0x1391 => { Name => 'ToneCurveProfileName', Writable => 'string' }, #4 + 0x1392 => { Name => 'InputProfile', SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' } }, #4 + 0x1393 => { Name => 'ProcessParametersV2', Binary => 1 }, #4 (Used by the SDK, Firmware should not use!) + 0x1394 => 'ReservedBlob2', #4 + 0x1395 => 'ReservedBlob3', #4 + 0x1396 => 'ReservedBlob4', #4 + 0x1397 => 'ReservedBlob5', #4 + 0x1398 => 'ReservedBlob6', #4 + 0x1399 => 'ReservedBlob7', #4 + 0x139a => 'ReservedBlob8', #4 + 0x139b => 'ReservedBlob9', #4 + 0x1770 => { Name => 'ScriptVersion', Writable => 'int32u' }, #4 + 0x177a => 'ImagerTimingData', #4 + 0x1784 => { Name => 'ISO', Writable => 'int32u' }, #3 ("NsecPerIcCode", ref 4) + 0x17a2 => 'Scav11Cols', #4 + 0x17a3 => 'Scav12Cols', #4 + 0x17a4 => 'Scav21Cols', #4 + 0x17a5 => 'Scav22Cols', #4 + 0x17a6 => 'ActiveCTEMonitor1Cols', #4 + 0x17a7 => 'ActiveCTEMonitor2Cols', #4 + 0x17a8 => 'ActiveCTEMonitorRows', #4 + 0x17a9 => 'ActiveBuf1Cols', #4 + 0x17aa => 'ActiveBuf2Cols', #4 + 0x17ab => 'ActiveBuf1Rows', #4 + 0x17ac => 'ActiveBuf2Rows', #4 + 0x17c0 => 'HRNoiseLines', #4 + 0x17c1 => 'RNoiseLines', #4 + 0x17c2 => 'ANoiseLines', #4 + 0x17d4 => { Name => 'ImagerCols', Writable => 'int16u' }, #4 + 0x17de => { Name => 'ImagerRows', Writable => 'int16u' }, #4 + 0x17e8 => { Name => 'PartialActiveCols1', Writable => 'int32u' }, #4 + 0x17f2 => { Name => 'PartialActiveCols2', Writable => 'int32u' }, #4 + 0x17fc => { Name => 'PartialActiveRows1', Writable => 'int32u' }, #4 + 0x1806 => { Name => 'PartialActiveRows2', Writable => 'int32u' }, #4 + 0x1810 => { Name => 'ElectricalBlackColumns',Writable=> 'int32u' }, #4 + 0x181a => { Name => 'ResetBlackSegRows', Writable => 'int32u' }, #4 + 0x1838 => { Name => 'CaptureWidthNormal', Writable => 'int32u' }, #4 + 0x1839 => { Name => 'CaptureHeightNormal', Writable => 'int32u' }, #4 + 0x183a => 'CaptureWidthResetBlackSegNormal', #4 + 0x183b => 'CaptureHeightResetBlackSegNormal', #4 + 0x183c => 'DarkRefOffsetNormal', #4 + 0x1842 => { Name => 'CaptureWidthTest', Writable => 'int32u' }, #4 + 0x1843 => 'CaptureHeightTest', #4 + 0x1844 => 'CaptureWidthResetBlackSegTest', #4 + 0x1845 => 'CaptureHeightResetBlackSegTest', #4 + 0x1846 => 'DarkRefOffsetTest', #4 + 0x184c => { Name => 'ImageSegmentStartLine',Writable => 'int32u' }, #4 + 0x184d => { Name => 'ImageSegmentLines', Writable => 'int32u' }, #4 + 0x184e => { Name => 'SkipLineTime', Writable => 'int32u' }, #4 + 0x1860 => { Name => 'FastResetLineTime', Writable => 'int32u' }, #4 + 0x186a => { Name => 'NormalLineTime', Writable => 'int32u' }, #4 + 0x1874 => { Name => 'MinIntegrationRows', Writable => 'int32u' }, #4 + 0x187e => { Name => 'PreReadFastResetCount',Writable => 'int32u' }, #4 + 0x1888 => { Name => 'TransferTimeNormal', Writable => 'int32u' }, #4 + 0x1889 => { Name => 'TransferTimeTest', Writable => 'int32u' }, #4 + 0x188a => { Name => 'QuietTime', Writable => 'int32u' }, #4 + 0x189c => { Name => 'OverClockCols', Writable => 'int16u' }, #4 + 0x18a6 => { Name => 'H2ResetBlackPixels', Writable => 'int32u' }, #4 + 0x18b0 => { Name => 'H3ResetBlackPixels', Writable => 'int32u' }, #4 + 0x18ba => { Name => 'BlackAcquireRows', Writable => 'int32u' }, #4 + 0x18c4 => { Name => 'OverClockRows', Writable => 'int16u' }, #4 + 0x18ce => { Name => 'H3ResetBlackColumns', Writable => 'int32u' }, #4 + 0x18d8 => { Name => 'DarkBlackSegRows', Writable => 'int32u' }, #4 + 0x1900 => 'CrossbarEnable', #4 + 0x1901 => { Name => 'FifoenOnePixelDelay', Writable => 'int32u' }, #4 + 0x1902 => { Name => 'ReadoutTypeRequested', Writable => 'int32u' }, #4 + 0x1903 => { Name => 'ReadoutTypeActual', Writable => 'int32u' }, #4 + 0x190a => { Name => 'OffsetDacValue', Writable => 'int32u' }, #4 + 0x1914 => { Name => 'TempAmpGainX100', Writable => 'int32u' }, #4 + 0x191e => { Name => 'VarrayDacNominalValues',Writable=> 'int32u', Count => 3 }, #4 + 0x1928 => 'VddimDacNominalValues', #4 + 0x1964 => { Name => 'C14Configuration', Writable => 'int32u' }, #4 + 0x196e => { Name => 'TDA1Offset', Writable => 'int32u', Count => 3 }, #4 + 0x196f => { Name => 'TDA1Bandwidth', Writable => 'int32u' }, #4 + 0x1970 => { Name => 'TDA1Gain', Writable => 'int32u', Count => 3 }, #4 + 0x1971 => { Name => 'TDA1EdgePolarity', Writable => 'int32u' }, #4 + 0x1978 => { Name => 'TDA2Offset', Writable => 'int32u', Count => 3 }, #4 + 0x1979 => { Name => 'TDA2Bandwidth', Writable => 'int32u' }, #4 + 0x197a => { Name => 'TDA2Gain', Writable => 'int32u', Count => 3 }, #4 + 0x197b => { Name => 'TDA2EdgePolarity', Writable => 'int32u' }, #4 + 0x1982 => { Name => 'TDA3Offset', Writable => 'int32u', Count => 3 }, #4 + 0x1983 => { Name => 'TDA3Bandwidth', Writable => 'int32u' }, #4 + 0x1984 => { Name => 'TDA3Gain', Writable => 'int32u', Count => 3 }, #4 + 0x1985 => { Name => 'TDA3EdgePolarity', Writable => 'int32u' }, #4 + 0x198c => { Name => 'TDA4Offset', Writable => 'int32u', Count => 3 }, #4 + 0x198d => { Name => 'TDA4Bandwidth', Writable => 'int32u' }, #4 + 0x198e => { Name => 'TDA4Gain', Writable => 'int32u', Count => 3 }, #4 + 0x198f => { Name => 'TDA4EdgePolarity', Writable => 'int32u' }, #4 + 0xfde8 => { Name => 'ComLenBlkSize', Writable => 'int16u' }, #4 +); + +# contains WB adjust set in software (ref 3) +%Image::ExifTool::Kodak::Processing = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + 20 => { + Name => 'WB_RGBLevels', + Format => 'int16u[3]', + ValueConv => q{ + my @a = split ' ',$val; + foreach (@a) { + $_ = 2048 / $_ if $_; + } + return join ' ', @a; + } + }, +); + +# tags found in the Kodak KDC_IFD (in IFD0 of KDC images) (ref 3) +%Image::ExifTool::Kodak::KDC_IFD = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'KDC_IFD', 2 => 'Image'}, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITE_GROUP => 'KDC_IFD', + SET_GROUP1 => 1, + NOTES => q{ + These tags are found in a separate IFD of KDC images from some newer Kodak + models such as the P880 and Z1015IS. + }, + 0xfa00 => { + Name => 'SerialNumber', #PH (unverified) + Writable => 'string', + }, + 0xfa0d => { + Name => 'WhiteBalance', + Writable => 'int8u', + PrintConv => { #PH + 0 => 'Auto', + 1 => 'Fluorescent', # (NC) + 2 => 'Tungsten', # (NC) + 3 => 'Daylight', # (NC) + 6 => 'Shade', # (NC, called "Open Shade" by Kodak) + }, + }, + # the following tags are numbered for use in the Composite tag lookup + 0xfa25 => 'WB_RGBLevelsAuto', + 0xfa27 => 'WB_RGBLevelsTungsten', # (NC) + 0xfa28 => 'WB_RGBLevelsFluorescent', # (NC) + 0xfa29 => 'WB_RGBLevelsDaylight', # (NC) + 0xfa2a => 'WB_RGBLevelsShade', # (NC) +); + +# textual-based Kodak TextualInfo tags (not found in KDC images) (ref PH) +%Image::ExifTool::Kodak::TextualInfo = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Kodak', 2 => 'Image'}, + PROCESS_PROC => \&ProcessKodakText, + NOTES => q{ + Below is a list of tags which have been observed in the Kodak TextualInfo + data, however ExifTool will extract information from any tags found here. + }, + 'Actual Compensation' => 'ActualCompensation', + 'AF Function' => 'AFMode', # values: "S" (=Single?, then maybe C for Continuous, M for Manual?) - PH + 'Aperture' => { + Name => 'Aperture', + ValueConv => '$val=~s/^f//i; $val', + }, + 'Auto Bracket' => 'AutoBracket', + 'Brightness Value' => 'BrightnessValue', + 'Camera' => 'CameraModel', + 'Camera body' => 'CameraBody', + 'Compensation' => 'ExposureCompensation', + 'Date' => { + Name => 'Date', + Groups => { 2 => 'Time' }, + }, + 'Exposure Bias' => 'ExposureBias', + 'Exposure Mode' => { + Name => 'ExposureMode', + PrintConv => { + OTHER => sub { shift }, # pass other values straight through + 'M' => 'Manual', + 'A' => 'Aperture Priority', #(NC -- I suppose this could be "Auto" too) + 'S' => 'Shutter Priority', #(NC) + 'P' => 'Program', #(NC) + 'B' => 'Bulb', #(NC) + # have seen "Manual (M)" written by DCS760C - PH + # and "Aperture priority AE (Av)" written by a ProBack 645M + }, + }, + 'Firmware Version' => 'FirmwareVersion', + 'Flash Compensation' => 'FlashExposureComp', + 'Flash Fired' => 'FlashFired', + 'Flash Sync Mode' => 'FlashSyncMode', + 'Focal Length' => { + Name => 'FocalLength', + PrintConv => '"$val mm"', + }, + 'Height' => 'KodakImageHeight', + 'Image Number' => 'ImageNumber', + 'ISO' => 'ISO', + 'ISO Speed' => 'ISO', + 'Lens' => { Name => 'Lens', Priority => 0 }, + 'Max Aperture' => { + Name => 'MaxAperture', + ValueConv => '$val=~s/^f//i; $val', + }, + 'Meter Mode' => 'MeterMode', + 'Min Aperture' => { + Name => 'MinAperture', + ValueConv => '$val=~s/^f//i; $val', + }, + 'Popup Flash' => 'PopupFlash', + 'Serial Number' => 'SerialNumber', + 'Shooting Mode' => 'ShootingMode', + 'Shutter' => 'ShutterSpeed', + 'Temperature' => 'Temperature', # with a value of 15653, what could this be? - PH + 'Time' => { + Name => 'Time', + Groups => { 2 => 'Time' }, + }, + 'White balance' => 'WhiteBalance', + 'Width' => 'KodakImageWidth', + '_other_info' => { + Name => 'OtherInfo', + Notes => 'any other information without a tag name', + }, +); + +# Kodak APP3 "Meta" tags (ref 2) +%Image::ExifTool::Kodak::Meta = ( + GROUPS => { 0 => 'Meta', 1 => 'MetaIFD', 2 => 'Image'}, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITE_GROUP => 'MetaIFD', # default write group + NOTES => q{ + These tags are found in the APP3 "Meta" segment of JPEG images from Kodak + cameras such as the DC280, DC3400, DC5000, MC3, M580, Z950 and Z981. The + structure of this segment is similar to the APP1 "Exif" segment, but a + different set of tags is used. + }, + 0xc350 => 'FilmProductCode', + 0xc351 => 'ImageSourceEK', + 0xc352 => 'CaptureConditionsPAR', + 0xc353 => { + Name => 'CameraOwner', + Writable => 'undef', + RawConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,$tag)', + RawConvInv => 'Image::ExifTool::Exif::EncodeExifText($self,$val)', + }, + 0xc354 => { + Name => 'SerialNumber', + Writable => 'undef', + Groups => { 2 => 'Camera' }, + RawConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,$tag)', #PH + RawConvInv => 'Image::ExifTool::Exif::EncodeExifText($self,$val)', + }, + 0xc355 => 'UserSelectGroupTitle', + 0xc356 => 'DealerIDNumber', + 0xc357 => 'CaptureDeviceFID', + 0xc358 => 'EnvelopeNumber', + 0xc359 => 'FrameNumber', + 0xc35a => 'FilmCategory', + 0xc35b => 'FilmGencode', + 0xc35c => 'ModelAndVersion', + 0xc35d => 'FilmSize', + 0xc35e => 'SBA_RGBShifts', + 0xc35f => 'SBAInputImageColorspace', + 0xc360 => 'SBAInputImageBitDepth', + 0xc361 => { + Name => 'SBAExposureRecord', + Binary => 1, + }, + 0xc362 => { + Name => 'UserAdjSBA_RGBShifts', + Binary => 1, + }, + 0xc363 => 'ImageRotationStatus', + 0xc364 => 'RollGuidElements', + 0xc365 => 'MetadataNumber', + 0xc366 => 'EditTagArray', + 0xc367 => 'Magnification', + # 0xc36b - string[8]: "1.0" + 0xc36c => 'NativeXResolution', + 0xc36d => 'NativeYResolution', + 0xc36e => { + Name => 'KodakEffectsIFD', + Flags => 'SubIFD', + Groups => { 1 => 'KodakEffectsIFD' }, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::SpecialEffects', + Start => '$val', + }, + }, + 0xc36f => { + Name => 'KodakBordersIFD', + Flags => 'SubIFD', + Groups => { 1 => 'KodakBordersIFD' }, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Borders', + Start => '$val', + }, + }, + 0xc37a => 'NativeResolutionUnit', + 0xc418 => 'SourceImageDirectory', + 0xc419 => 'SourceImageFileName', + 0xc41a => 'SourceImageVolumeName', + 0xc46c => 'PrintQuality', + 0xc46e => 'ImagePrintStatus', + # 0cx46f - int16u: 1 +); + +# Kodak APP3 "Meta" Special Effects sub-IFD (ref 2) +%Image::ExifTool::Kodak::SpecialEffects = ( + GROUPS => { 0 => 'Meta', 1 => 'KodakEffectsIFD', 2 => 'Image'}, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + 0 => 'DigitalEffectsVersion', + 1 => { + Name => 'DigitalEffectsName', + PrintConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,"DigitalEffectsName")', + }, + 2 => 'DigitalEffectsType', +); + +# Kodak APP3 "Meta" Borders sub-IFD (ref 2) +%Image::ExifTool::Kodak::Borders = ( + GROUPS => { 0 => 'Meta', 1 => 'KodakBordersIFD', 2 => 'Image'}, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + 0 => 'BordersVersion', + 1 => { + Name => 'BorderName', + PrintConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,"BorderName")', + }, + 2 => 'BorderID', + 3 => 'BorderLocation', + 4 => 'BorderType', + 8 => 'WatermarkType', +); + +# tags in Kodak MOV videos (ref PH) +# (similar information in Kodak,Minolta,Nikon,Olympus,Pentax and Sanyo videos) +%Image::ExifTool::Kodak::MOV = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => q{ + This information is found in the TAGS atom of MOV videos from Kodak models + such as the P880. + }, + 0 => { + Name => 'Make', + Format => 'string[21]', + }, + 0x16 => { + Name => 'Model', + Format => 'string[42]', + }, + 0x40 => { + Name => 'ModelType', + Format => 'string[8]', + }, + # (01 00 at offset 0x48) + 0x4e => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val ? 10 / $val : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x52 => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x5a => { + Name => 'ExposureCompensation', + Format => 'rational64s', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + # 0x6c => 'WhiteBalance', ? + 0x70 => { + Name => 'FocalLength', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + }, +); + +# Kodak DcMD atoms (ref PH) +%Image::ExifTool::Kodak::DcMD = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Video' }, + NOTES => 'Metadata directory found in MOV and MP4 videos from some Kodak cameras.', + Cmbo => { + Name => 'CameraByteOrder', + PrintConv => { + II => 'Little-endian (Intel, II)', + MM => 'Big-endian (Motorola, MM)', + }, + }, + CMbo => { # (as written by Kodak Playsport video camera) + Name => 'CameraByteOrder', + PrintConv => { + II => 'Little-endian (Intel, II)', + MM => 'Big-endian (Motorola, MM)', + }, + }, + DcME => { + Name => 'DcME', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::DcME', + }, + }, + DcEM => { + Name => 'DcEM', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::DcEM', + }, + }, +); + +# Kodak DcME atoms (ref PH) +%Image::ExifTool::Kodak::DcME = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Video' }, + # Mtmd - 24 bytes: ("00 00 00 00 00 00 00 01" x 3) + # Keyw - keywords? (six bytes all zero) + # Rate - 2 bytes: 00 00 +); + +# Kodak DcEM atoms (ref PH) +%Image::ExifTool::Kodak::DcEM = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Video' }, + # Mtmd - 24 bytes: ("00 00 00 00 00 00 00 01" x 3) + # Csat - 16 bytes: 00 06 00 00 62 00 61 00 73 00 69 00 63 00 00 00 [....b.a.s.i.c...] + # Ksre - 8 bytes: 00 01 00 00 00 00 +); + +# tags in "free" atom of Kodak M5370 MP4 videos (ref PH) +%Image::ExifTool::Kodak::Free = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Video' }, + NOTES => q{ + Information stored in the "free" atom of Kodak MP4 videos. (VERY bad form + for Kodak to store useful information in an atom intended for unused space!) + }, + # (2012/01/19: Kodak files for bankruptcy -- this is poetic metadata justice) + Seri => { + Name => 'SerialNumber', + # byte 0 is string length; byte 1 is zero; string starts at byte 2 + ValueConv => 'substr($val, 2, unpack("C",$val))', + }, + SVer => { + Name => 'FirmwareVersion', + ValueConv => 'substr($val, 2, unpack("C",$val))', + }, + # Clor - 2 bytes: 0 1 (?) + # CapM - 2 bytes: 0 1 (capture mode? = exposure mode?) + # WBMD - 2 bytes: 0 0 (white balance?) + Expc => { # (NC) + Name => 'ExposureCompensation', + Format => 'int16s', + ValueConv => '$val / 3', # (guess) + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + # Zone - 2 bytes: 0 2 (time zone? focus zone?) + # FoMD - 2 bytes: 0 0 (focus mode?) + # Shap - 2 bytes: 0 2 (sharpness?) + Expo => { + Name => 'ExposureTime', + Format => 'rational32u', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + FNum => { + Name => 'FNumber', + Format => 'int16u', + ValueConv => '$val / 100', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + ISOS => { Name => 'ISO', Format => 'int16u' }, + StSV => { + Name => 'ShutterSpeedValue', + Format => 'int16s', + ValueConv => 'abs($val)<100 ? 2**(-$val/3) : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + AprV => { + Name => 'ApertureValue', + Format => 'int16s', + ValueConv => '2 ** ($val / 2000)', + PrintConv => 'sprintf("%.1f",$val)', + }, + BrtV => { # (NC) + Name => 'BrightnessValue', + Format => 'int32s', + ValueConv => '$val / 1000', # (guess) + }, + FoLn => { + Name => 'FocalLength', + Groups => { 2 => 'Camera' }, + Format => 'int16u', + PrintConv => 'sprintf("%.1f mm",$val)', + }, + FL35 => { + Name => 'FocalLengthIn35mmFormat', + Groups => { 2 => 'Camera' }, + Format => 'int16u', + PrintConv => '"$val mm"', + }, + Scrn => { + Name => 'PreviewInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Kodak::Scrn' }, + }, +); + +# tags in "frea" atom of Kodak PixPro SP360 MP4 videos (ref PH) +%Image::ExifTool::Kodak::frea = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => 'Information stored in the "frea" atom of Kodak PixPro SP360 MP4 videos.', + tima => { + Name => 'Duration', + Format => 'int32u', + Priority => 0, # (only integer seconds) + PrintConv => 'ConvertDuration($val)', + }, + 'ver '=> { Name => 'KodakVersion' }, + thma => { Name => 'ThumbnailImage', Groups => { 2 => 'Preview' }, Binary => 1 }, + scra => { Name => 'PreviewImage', Groups => { 2 => 'Preview' }, Binary => 1 }, +); + +# preview information in free/Scrn atom of MP4 videos (ref PH) +%Image::ExifTool::Kodak::Scrn = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16u', + 0 => 'PreviewImageWidth', + 1 => 'PreviewImageHeight', + 2 => { Name => 'PreviewImageLength', Format => 'int32u' }, + 4 => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Format => 'undef[$val{2}]', + RawConv => '$self->ValidateImage(\$val, $tag)', + }, +); + +# acceleration information extracted from 'pose' atom of MP4 videos (ref PH, PixPro 4KVR360) +%Image::ExifTool::Kodak::pose = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Video' }, + PROCESS_PROC => \&ProcessPose, + NOTES => q{ + Streamed orientation information from the PixPro 4KVR360, extracted as + sub-documents when the L<Duplicates|../ExifTool.html#Duplicates> option is used. + }, + Accelerometer => { }, # up, back, left? units of g + AngularVelocity => { } # left, up, ccw? units? +); + +# Kodak composite tags +%Image::ExifTool::Kodak::Composite = ( + GROUPS => { 2 => 'Camera' }, + DateCreated => { + Groups => { 2 => 'Time' }, + Require => { + 0 => 'Kodak:YearCreated', + 1 => 'Kodak:MonthDayCreated', + }, + ValueConv => '"$val[0]:$val[1]"', + }, + WB_RGBLevels => { + Require => { + 0 => 'KDC_IFD:WhiteBalance', + }, + # indices of the following entries are KDC_IFD:WhiteBalance + 1 + Desire => { + 1 => 'WB_RGBLevelsAuto', + 2 => 'WB_RGBLevelsFluorescent', + 3 => 'WB_RGBLevelsTungsten', + 4 => 'WB_RGBLevelsDaylight', + 5 => 'WB_RGBLevels4', + 6 => 'WB_RGBLevels5', + 7 => 'WB_RGBLevelsShade', + }, + ValueConv => '$val[$val[0] + 1]', + }, + WB_RGBLevels2 => { + Name => 'WB_RGBLevels', + Require => { + 0 => 'KodakIFD:WhiteBalance', + 1 => 'WB_RGBMul0', + 2 => 'WB_RGBMul1', + 3 => 'WB_RGBMul2', + 4 => 'WB_RGBMul3', + 5 => 'WB_RGBCoeffs0', + 6 => 'WB_RGBCoeffs1', + 7 => 'WB_RGBCoeffs2', + 8 => 'WB_RGBCoeffs3', + }, + # indices of the following entries are KDC_IFD:WhiteBalance + 1 + Desire => { + 9 => 'KodakIFD:ColorTemperature', + 10 => 'Kodak:WB_RGBLevels', + }, + ValueConv => 'Image::ExifTool::Kodak::CalculateRGBLevels(@val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Kodak'); + +#------------------------------------------------------------------------------ +# Process Kodak accelerometer data (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessPose($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = length $$dataPt; + my $ee = $et->Options('ExtractEmbedded'); + my ($i, $pos); + + return 0 if $dirLen < 0x14; + my $num = Get32u($dataPt, 0x10); + return 0 if $dirLen < 0x14 + $num * 24; + + $et->VerboseDir('Kodak pose', undef, $dirLen); + + $$et{DOC_NUM} = 0; + for ($i=0, $pos=0x14; $i<$num; ++$i, $pos+=24) { + $et->HandleTag($tagTablePtr, AngularVelocity => + Image::ExifTool::GetRational64s($dataPt, $pos) . ' ' . + Image::ExifTool::GetRational64s($dataPt, $pos + 8) . ' ' . + Image::ExifTool::GetRational64s($dataPt, $pos + 16)); + $ee or $pos += $num * 24, last; + ++$$et{DOC_NUM}; + } + $$et{DOC_NUM} = 0; + + return 1 if $dirLen < $pos + 0x10; + $num = Get32u($dataPt, $pos + 0x0c); + return 1 if $dirLen < $pos + 0x10 + $num * 24; + + for ($i=0, $pos+=0x10; $i<$num; ++$i, $pos+=24) { + $et->HandleTag($tagTablePtr, Accelerometer => + Image::ExifTool::GetRational64s($dataPt, $pos) . ' ' . + Image::ExifTool::GetRational64s($dataPt, $pos + 8) . ' ' . + Image::ExifTool::GetRational64s($dataPt, $pos + 16)); + $ee or $pos += $num * 24, last; + ++$$et{DOC_NUM}; + } + $$et{DOC_NUM} = 0; + $ee or $et->Warn('Use the ExtractEmbedded option to extract all accelerometer data',3); + return 1; +} + +#------------------------------------------------------------------------------ +# Calculate RGB levels from associated tags (ref 3) +# Inputs: 0) KodakIFD:WhiteBalance, 1-4) WB_RGBMul0-3, 5-8) WB_RGBCoeffs0-3 +# 9) (optional) KodakIFD:ColorTemperature, 10) (optional) Kodak:WB_RGBLevels +# Returns: WB_RGBLevels or undef +sub CalculateRGBLevels(@) +{ + return undef if $_[10]; # use existing software levels if they exist + my $wbi = $_[0]; + return undef if $wbi < 0 or $wbi > 3; + my @mul = split ' ', $_[$wbi + 1], 13; # (only use the first 12 coeffs) + my @coefs = split ' ', ${$_[$wbi + 5]}; # (extra de-reference for Binary data) + my $wbtemp100 = ($_[9] || 6500) / 100; + return undef unless @mul >= 3 and @coefs >= 12; + my ($i, $c, $n, $num, @cam_mul); + for ($c=$n=0; $c<3; ++$c) { + for ($num=$i=0; $i<4; ++$i) { + $num += $coefs[$n++] * ($wbtemp100 ** $i); + } + $cam_mul[$c] = 2048 / ($num * $mul[$c]); + } + return join(' ', @cam_mul); +} + +#------------------------------------------------------------------------------ +# Process Kodak textual TextualInfo +# Inputs: 0) ExifTool object ref, 1) dirInfo hash ref, 2) tag table ref +# Returns: 1 on success +sub ProcessKodakText($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || length($$dataPt) - $dirStart; + my $data = substr($$dataPt, $dirStart, $dirLen); + $data =~ s/\0.*//s; # truncate at null if it exists + my @lines = split /[\n\r]+/, $data; + my ($line, $success, @other, $tagInfo); + $et->VerboseDir('Kodak Text'); + foreach $line (@lines) { + if ($line =~ /(.*?):\s*(.*)/) { + my ($tag, $val) = ($1, $2); + if ($$tagTablePtr{$tag}) { + $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + } else { + my $tagName = $tag; + $tagName =~ s/([A-Z])\s+([A-Za-z])/${1}_\U$2/g; + $tagName =~ s/([a-z])\s+([A-Za-z0-9])/${1}\U$2/g; + $tagName =~ s/\s+//g; + $tagName =~ s/[^-\w]+//g; # delete remaining invalid characters + $tagName = 'NoName' unless $tagName; + $tagInfo = { Name => $tagName }; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + $et->HandleTag($tagTablePtr, $tag, $val, TagInfo => $tagInfo); + $success = 1; + } else { + # strip off leading/trailing white space and ignore blank lines + push @other, $1 if $line =~ /^\s*(\S.*?)\s*$/; + } + } + if ($success) { + if (@other) { + $tagInfo = $et->GetTagInfo($tagTablePtr, '_other_info'); + $et->FoundTag($tagInfo, \@other); + } + } else { + $et->Warn("Can't parse Kodak TextualInfo data", 1); + } + return $success; +} + +#------------------------------------------------------------------------------ +# Process Kodak IFD (with leading byte order mark) +# Inputs: 0) ExifTool object ref, 1) dirInfo hash ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessKodakIFD($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dirStart = $$dirInfo{DirStart} || 0; + return 1 if $dirStart <= 0 or $dirStart + 2 > $$dirInfo{DataLen}; + my $byteOrder = substr(${$$dirInfo{DataPt}}, $dirStart, 2); + unless (Image::ExifTool::SetByteOrder($byteOrder)) { + $et->Warn("Invalid Kodak $$dirInfo{Name} directory"); + return 1; + } + $$dirInfo{DirStart} += 2; # skip byte order mark + $$dirInfo{DirLen} -= 2; + if ($$et{HTML_DUMP}) { + my $base = $$dirInfo{Base} + $$dirInfo{DataPos}; + $et->HDump($dirStart+$base, 2, "Byte Order Mark"); + } + return Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Write Kodak IFD (with leading byte order mark) +# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref +# Returns: Exif data block (may be empty if no Exif data) or undef on error +sub WriteKodakIFD($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dirStart = $$dirInfo{DirStart} || 0; + return '' if $dirStart <= 0 or $dirStart + 2 > $$dirInfo{DataLen}; + my $byteOrder = substr(${$$dirInfo{DataPt}}, $dirStart, 2); + return '' unless Image::ExifTool::SetByteOrder($byteOrder); + $$dirInfo{DirStart} += 2; # skip byte order mark + $$dirInfo{DirLen} -= 2; + my $buff = Image::ExifTool::Exif::WriteExif($et, $dirInfo, $tagTablePtr); + return $buff unless defined $buff and length $buff; + # apply one-time fixup for length of byte order mark + if ($$dirInfo{Fixup}) { + $dirInfo->{Fixup}->{Shift} += 2; + $$dirInfo{Fixup}->ApplyFixup(\$buff); + delete $$dirInfo{Fixup}; + } + return Image::ExifTool::GetByteOrder() . $buff; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Kodak - Kodak EXIF maker notes and APP3 "Meta" 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 Kodak maker notes EXIF meta information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<Image::MetaData::JPEG|Image::MetaData::JPEG> + +=item L<http://www.ozhiker.com/electronics/pjmt/jpeg_info/meta.html> + +=item L<http://www.cybercom.net/~dcoffin/dcraw/> + +=item (...plus lots of testing with my daughter's CX4200!) + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Kodak Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/KyoceraRaw.pm b/ExifTool/lib/Image/ExifTool/KyoceraRaw.pm new file mode 100644 index 0000000..2082e63 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/KyoceraRaw.pm @@ -0,0 +1,173 @@ +#------------------------------------------------------------------------------ +# File: KyoceraRaw.pm +# +# Description: Read Kyocera RAW meta information +# +# Revisions: 02/17/2006 - P. Harvey Created +# +# References: 1) http://www.cybercom.net/~dcoffin/dcraw/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::KyoceraRaw; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.03'; + +sub ProcessRAW($$); + +# utility to reverse order of characters in a string +sub ReverseString($) { pack('C*',reverse unpack('C*',shift)) } + +# Contax N Digital tags (ref PH) +%Image::ExifTool::KyoceraRaw::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Tags for Kyocera Contax N Digital RAW images.', + 0x01 => { + Name => 'FirmwareVersion', + Format => 'string[10]', + ValueConv => \&ReverseString, + }, + 0x0c => { + Name => 'Model', + Format => 'string[12]', + ValueConv => \&ReverseString, + }, + 0x19 => { #1 + Name => 'Make', + Format => 'string[7]', + ValueConv => \&ReverseString, + }, + 0x21 => { #1 + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Format => 'string[20]', + ValueConv => \&ReverseString, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x34 => { + Name => 'ISO', + Groups => { 2 => 'Image' }, + Format => 'int32u', + PrintConv => { + 7 => 25, + 8 => 32, + 9 => 40, + 10 => 50, + 11 => 64, + 12 => 80, + 13 => 100, + 14 => 125, + 15 => 160, + 16 => 200, + 17 => 250, + 18 => 320, + 19 => 400, + }, + }, + 0x38 => { + Name => 'ExposureTime', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '2**($val / 8) / 16000', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x3c => { #1 + Name => 'WB_RGGBLevels', + Groups => { 2 => 'Image' }, + Format => 'int32u[4]', + }, + 0x58 => { + Name => 'FNumber', + Groups => { 2 => 'Image' }, + Format => 'int32u', + ValueConv => '2**($val/16)', + PrintConv => 'sprintf("%.2g",$val)', + }, + 0x68 => { + Name => 'MaxAperture', + Format => 'int32u', + ValueConv => '2**($val/16)', + PrintConv => 'sprintf("%.2g",$val)', + }, + 0x70 => { + Name => 'FocalLength', + Format => 'int32u', + PrintConv => '"$val mm"', + }, + 0x7c => { + Name => 'Lens', + Format => 'string[32]', + }, +); + +#------------------------------------------------------------------------------ +# Extract information from Kyocera RAW image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 if this was a valid Kyocera RAW image +sub ProcessRAW($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $size = 156; # size of header + my $buff; + + $raf->Read($buff, $size) == $size or return 0; + # validate Make string ('KYOCERA' reversed) + substr($buff, 0x19, 7) eq 'ARECOYK' or return 0; + $et->SetFileType(); + SetByteOrder('MM'); + my %dirInfo = ( + DataPt => \$buff, + DataPos => 0, + DataLen => $size, + DirStart => 0, + DirLen => $size, + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::KyoceraRaw::Main'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::KyoceraRaw - Read Kyocera RAW meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +meta information from Kyocera Contax N Digital RAW images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 SEE ALSO + +L<Image::ExifTool::TagNames/KyoceraRaw Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/LIF.pm b/ExifTool/lib/Image/ExifTool/LIF.pm new file mode 100644 index 0000000..576ec0e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/LIF.pm @@ -0,0 +1,161 @@ +#------------------------------------------------------------------------------ +# File: LIF.pm +# +# Description: Read LIF (Leica Image File) files +# +# Revisions: 2021-06-21 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::LIF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::XMP; + +$VERSION = '1.01'; + +%Image::ExifTool::LIF::Main = ( + GROUPS => { 0 => 'XML', 1 => 'XML', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP, + VARS => { NO_ID => 1 }, + NOTES => q{ + Tags extracted from Leica Image Format (LIF) imaging files. As well as the + tags listed below, all available information is extracted from the + XML-format metadata in the LIF header. + }, + TimeStampList => { + Groups => { 2 => 'Time' }, + ValueConv => q{ + my $unixTimeZero = 134774 * 24 * 3600; + my @vals = split ' ', $val; + foreach (@vals) { + if (/[^0-9a-f]/i) { + $_ = '0000:00:00 00:00:00'; + } elsif (length $_ > 8) { + my $lo = hex substr($_, -8); + my $hi = hex substr($_, 0, -8); + $_ = 1e-7 * ($hi * 4294967296 + $lo); + } else { + $_ = 1e-7 * hex($_); + } + # shift from Jan 1, 1601 to Jan 1, 1970 + $_ = Image::ExifTool::ConvertUnixTime($_ - $unixTimeZero); + } + return \@vals; + }, + }, +); + +#------------------------------------------------------------------------------ +# Shorten obscenely long LIF tag names +# Inputs: Tag name +# Returns: Shortened tag name +sub ShortenTagNames($) +{ + local $_; + $_ = shift; + s/DescriptionDimensionsDimensionDescription/Dimensions/; + s/DescriptionChannelsChannelDescription/Channel/; + s/ShutterListShutter/Shutter/; + s/SettingDefinition/Setting/; + s/AdditionalZPositionListAdditionalZPosition/AdditionalZPosition/; + s/LMSDataContainerHeader//g; + s/FilterWheelWheel/FilterWheel/; + s/FilterWheelFilter/FilterWheel/; + s/DetectorListDetector/Detector/; + s/OnlineDyeSeparationOnlineDyeSeparation/OnlineDyeSeparation/; + s/AotfListAotf/Aotf/; + s/SettingAotfLaserLineSetting/SettingAotfLaser/; + s/DataROISetROISet/DataROISet/; + s/AdditionalZPosition/AddZPos/; + s/FRAPplusBlock_FRAPBlock_FRAP_PrePost_Info/FRAP_/; + s/FRAPplusBlock_FRAPBlock_FRAP_(Master)?/FRAP_/; + s/LDM_Block_SequentialLDM_Block_Sequential_/LDM_/; + s/ATLConfocalSetting/ATLConfocal/; + s/LaserArrayLaser/Laser/; + s/LDM_Master/LDM_/; + s/(List)?ATLConfocal/ATL_/; + s/Separation/Sep/; + s/BleachPointsElement/BleachPoint/; + s/BeamPositionBeamPosition/BeamPosition/; + s/DataROISetPossible(ROI)?/DataROISet/; + s/RoiElementChildrenElementDataROISingle(Roi)?/Roi/; + s/InfoLaserLineSettingArrayLaserLineSetting/LastLineSetting/; + s/FilterWheelWheelNameFilterName/FilterWheelFilterName/; + s/LUT_ListLut/Lut/; + s/ROI_ListRoiRoidata/ROI_/; + s/LaserLineSettingArrayLaserLineSetting/LaserLineSetting/; + return $_; +} + +#------------------------------------------------------------------------------ +# Extract metadata from a LIF image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid LIF file +sub ProcessLIF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + + # verify this is a valid LIF file + return 0 unless $raf->Read($buff, 15) == 15 and $buff =~ /^\x70\0{3}.{4}\x2a.{4}<\0/s; + + $et->SetFileType(); + SetByteOrder('II'); + + my $size = Get32u(\$buff, 4); # XML chunk size + my $len = Get32u(\$buff, 9) * 2; # XML data length + + $size < $len and $et->Error('Corrupted LIF XML block'), return 1; + $size > 100000000 and $et->Error('LIF XML block too large'), return 1; + + $raf->Seek(-2, 1) and $raf->Read($buff, $len) == $len or $et->Error('Truncated LIF XML block'), return 1; + + my $tagTablePtr = GetTagTable('Image::ExifTool::LIF::Main'); + + # convert from UCS2 to UTF8 + my $xml = Image::ExifTool::Decode($et, $buff, 'UCS2', 'II', 'UTF8'); + + my %dirInfo = ( DataPt => \$xml ); + + $$et{XmpIgnoreProps} = [ 'LMSDataContainerHeader', 'Element', 'Children', 'Data', 'Image', 'Attachment' ]; + $$et{ShortenXmpTags} = \&ShortenTagNames; + + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::LIF - Read LIF meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +metadata from Leica Image File (LIF) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/LIF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/LNK.pm b/ExifTool/lib/Image/ExifTool/LNK.pm new file mode 100644 index 0000000..ebbd388 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/LNK.pm @@ -0,0 +1,727 @@ +#------------------------------------------------------------------------------ +# File: LNK.pm +# +# Description: Read meta information from MS Shell Link files +# +# Revisions: 2009/09/19 - P. Harvey Created +# +# References: 1) http://msdn.microsoft.com/en-us/library/dd871305(PROT.10).aspx +# 2) http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::LNK; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.09'; + +sub ProcessItemID($$$); +sub ProcessLinkInfo($$$); + +# Information extracted from LNK (Windows Shortcut) files +%Image::ExifTool::LNK::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + VARS => { HEX_ID => 1 }, # print hex ID's in documentation + NOTES => 'Information extracted from MS Shell Link (Windows shortcut) files.', + # maybe the Flags aren't very useful to the user (since they are + # mainly structural), but extract them anyway for completeness + 0x14 => { + Name => 'Flags', + Format => 'int32u', + PrintConv => { BITMASK => { + 0 => 'IDList', + 1 => 'LinkInfo', + 2 => 'Description', + 3 => 'RelativePath', + 4 => 'WorkingDir', + 5 => 'CommandArgs', + 6 => 'IconFile', + 7 => 'Unicode', + 8 => 'NoLinkInfo', + 9 => 'ExpString', + 10 => 'SeparateProc', + 12 => 'DarwinID', + 13 => 'RunAsUser', + 14 => 'ExpIcon', + 15 => 'NoPidAlias', + 17 => 'RunWithShim', + 18 => 'NoLinkTrack', + 19 => 'TargetMetadata', + 20 => 'NoLinkPathTracking', + 21 => 'NoKnownFolderTracking', + 22 => 'NoKnownFolderAlias', + 23 => 'LinkToLink', + 24 => 'UnaliasOnSave', + 25 => 'PreferEnvPath', + 26 => 'KeepLocalIDList', + }}, + }, + 0x18 => { + Name => 'FileAttributes', + Format => 'int32u', + PrintConv => { BITMASK => { + 0 => 'Read-only', + 1 => 'Hidden', + 2 => 'System', + 3 => 'Volume', #(not used) + 4 => 'Directory', + 5 => 'Archive', + 6 => 'Encrypted?', #(ref 2, not used in XP) + 7 => 'Normal', + 8 => 'Temporary', + 9 => 'Sparse', + 10 => 'Reparse point', + 11 => 'Compressed', + 12 => 'Offline', + 13 => 'Not indexed', + 14 => 'Encrypted', + }}, + }, + 0x1c => { + Name => 'CreateDate', + Format => 'int64u', + Groups => { 2 => 'Time' }, + # convert time from 100-ns intervals since Jan 1, 1601 + RawConv => '$val ? $val : undef', + ValueConv => '$val=$val/1e7-11644473600; ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x24 => { + Name => 'AccessDate', + Format => 'int64u', + Groups => { 2 => 'Time' }, + RawConv => '$val ? $val : undef', + ValueConv => '$val=$val/1e7-11644473600; ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x2c => { + Name => 'ModifyDate', + Format => 'int64u', + Groups => { 2 => 'Time' }, + RawConv => '$val ? $val : undef', + ValueConv => '$val=$val/1e7-11644473600; ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x34 => { + Name => 'TargetFileSize', + Format => 'int32u', + }, + 0x38 => { + Name => 'IconIndex', + Format => 'int32u', + PrintConv => '$val ? $val : "(none)"', + }, + 0x3c => { + Name => 'RunWindow', + Format => 'int32u', + PrintConv => { + 0 => 'Hide', + 1 => 'Normal', + 2 => 'Show Minimized', + 3 => 'Show Maximized', + 4 => 'Show No Activate', + 5 => 'Show', + 6 => 'Minimized', + 7 => 'Show Minimized No Activate', + 8 => 'Show NA', + 9 => 'Restore', + 10 => 'Show Default', + }, + }, + 0x40 => { + Name => 'HotKey', + Format => 'int32u', + PrintHex => 1, + PrintConv => { + OTHER => sub { + my $val = shift; + my $ch = $val & 0xff; + if (chr $ch =~ /^[A-Z0-9]$/) { + $ch = chr $ch; + } elsif ($ch >= 0x70 and $ch <= 0x87) { + $ch = 'F' . ($ch - 0x6f); + } elsif ($ch == 0x90) { + $ch = 'Num Lock'; + } elsif ($ch == 0x91) { + $ch = 'Scroll Lock'; + } else { + $ch = sprintf('Unknown (0x%x)', $ch); + } + $ch = "Alt-$ch" if $val & 0x400; + $ch = "Control-$ch" if $val & 0x200; + $ch = "Shift-$ch" if $val & 0x100; + return $ch; + }, + 0x00 => '(none)', + # these entries really only for documentation + 0x90 => 'Num Lock', + 0x91 => 'Scroll Lock', + "0x30'-'0x39" => "0-9", + "0x41'-'0x5a" => "A-Z", + "0x70'-'0x87" => "F1-F24", + 0x100 => 'Shift', + 0x200 => 'Control', + 0x400 => 'Alt', + }, + }, + # note: tags 0x10xx are synthesized tag ID's + 0x10000 => { + Name => 'ItemID', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::ItemID' }, + }, + 0x20000 => { + Name => 'LinkInfo', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::LinkInfo' }, + }, + 0x30004 => 'Description', + 0x30008 => 'RelativePath', + 0x30010 => 'WorkingDirectory', + 0x30020 => 'CommandLineArguments', + 0x30040 => 'IconFileName', + # note: tags 0xa000000x are actually ID's (not indices) + 0xa0000000 => { + Name => 'UnknownData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' }, + }, + 0xa0000001 => { + Name => 'EnvVarData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' }, + }, + 0xa0000002 => { + Name => 'ConsoleData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::ConsoleData' }, + }, + 0xa0000003 => { + Name => 'TrackerData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::TrackerData' }, + }, + 0xa0000004 => { + Name => 'ConsoleFEData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::ConsoleFEData' }, + }, + 0xa0000005 => { + Name => 'SpecialFolderData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' }, + }, + 0xa0000006 => { + Name => 'DarwinData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' }, + }, + 0xa0000007 => { + Name => 'IconEnvData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' }, + }, + 0xa0000008 => { + Name => 'ShimData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' }, + }, + 0xa0000009 => { + Name => 'PropertyStoreData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' }, + }, + 0xa000000b => { + Name => 'KnownFolderData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' }, + }, + 0xa000000c => { + Name => 'VistaIDListData', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' }, + }, +); + +%Image::ExifTool::LNK::ItemID = ( + GROUPS => { 2 => 'Other' }, + PROCESS_PROC => \&ProcessItemID, + # (can't find any documentation on these items) + 0x0032 => { + Name => 'Item0032', + SubDirectory => { TagTable => 'Image::ExifTool::LNK::Item0032' }, + }, +); + +%Image::ExifTool::LNK::Item0032 = ( + GROUPS => { 2 => 'Other' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + 0x0e => { + Name => 'TargetFileDOSName', + Format => 'var_string', + }, + #not at a fixed offset -- offset is given by last 2 bytes of the item + 0x14 + #0x22 => { + # Name => 'TargetFileName', + # Format => 'var_ustring', + #}, +); + +%Image::ExifTool::LNK::LinkInfo = ( + GROUPS => { 2 => 'Other' }, + PROCESS_PROC => \&ProcessLinkInfo, + FORMAT => 'int32u', + VARS => { NO_ID => 1 }, + VolumeID => { }, + DriveType => { + PrintConv => { + 0 => 'Unknown', + 1 => 'Invalid Root Path', + 2 => 'Removable Media', + 3 => 'Fixed Disk', + 4 => 'Remote Drive', + 5 => 'CD-ROM', + 6 => 'Ram Disk', + }, + }, + DriveSerialNumber => { + PrintConv => 'join("-", unpack("A4 A4", sprintf("%08X", $val)))', + }, + VolumeLabel => { }, + LocalBasePath => { }, + CommonNetworkRelLink => { }, + CommonPathSuffix => { }, + NetName => { }, + DeviceName => { }, + NetProviderType => { + PrintHex => 1, + PrintConv => { + 0x1a0000 => 'AVID', + 0x1b0000 => 'DOCUSPACE', + 0x1c0000 => 'MANGOSOFT', + 0x1d0000 => 'SERNET', + 0x1e0000 => 'RIVERFRONT1', + 0x1f0000 => 'RIVERFRONT2', + 0x200000 => 'DECORB', + 0x210000 => 'PROTSTOR', + 0x220000 => 'FJ_REDIR', + 0x230000 => 'DISTINCT', + 0x240000 => 'TWINS', + 0x250000 => 'RDR2SAMPLE', + 0x260000 => 'CSC', + 0x270000 => '3IN1', + 0x290000 => 'EXTENDNET', + 0x2a0000 => 'STAC', + 0x2b0000 => 'FOXBAT', + 0x2c0000 => 'YAHOO', + 0x2d0000 => 'EXIFS', + 0x2e0000 => 'DAV', + 0x2f0000 => 'KNOWARE', + 0x300000 => 'OBJECT_DIRE', + 0x310000 => 'MASFAX', + 0x320000 => 'HOB_NFS', + 0x330000 => 'SHIVA', + 0x340000 => 'IBMAL', + 0x350000 => 'LOCK', + 0x360000 => 'TERMSRV', + 0x370000 => 'SRT', + 0x380000 => 'QUINCY', + 0x390000 => 'OPENAFS', + 0x3a0000 => 'AVID1', + 0x3b0000 => 'DFS', + }, + }, +); + +%Image::ExifTool::LNK::UnknownData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, +); + +%Image::ExifTool::LNK::ConsoleData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + 0x08 => { + Name => 'FillAttributes', + Format => 'int16u', + PrintConv => 'sprintf("0x%.2x", $val)', + }, + 0x0a => { + Name => 'PopupFillAttributes', + Format => 'int16u', + PrintConv => 'sprintf("0x%.2x", $val)', + }, + 0x0c => { + Name => 'ScreenBufferSize', + Format => 'int16u[2]', + PrintConv => '$val=~s/ / x /; $val', + }, + 0x10 => { + Name => 'WindowSize', + Format => 'int16u[2]', + PrintConv => '$val=~s/ / x /; $val', + }, + 0x14 => { + Name => 'WindowOrigin', + Format => 'int16u[2]', + PrintConv => '$val=~s/ / x /; $val', + }, + 0x20 => { + Name => 'FontSize', + Format => 'int16u[2]', + PrintConv => '$val=~s/ / x /; $val', + }, + 0x24 => { + Name => 'FontFamily', + Format => 'int32u', + PrintHex => 1, + PrintConv => { + 0 => "Don't Care", + 0x10 => 'Roman', + 0x20 => 'Swiss', + 0x30 => 'Modern', + 0x40 => 'Script', + 0x50 => 'Decorative', + }, + }, + 0x28 => { + Name => 'FontWeight', + Format => 'int32u', + }, + 0x2c => { + Name => 'FontName', + Format => 'undef[64]', + RawConv => q{ + $val = $self->Decode($val, 'UCS2'); + $val =~ s/\0.*//s; + return length($val) ? $val : undef; + }, + }, + 0x6c => { + Name => 'CursorSize', + Format => 'int32u', + }, + 0x70 => { + Name => 'FullScreen', + Format => 'int32u', + PrintConv => '$val ? "Yes" : "No"', + }, + 0x74 => { #PH (MISSING FROM MS DOCUMENTATION! -- screws up subsequent offsets) + Name => 'QuickEdit', + Format => 'int32u', + PrintConv => '$val ? "Yes" : "No"', + }, + 0x78 => { + Name => 'InsertMode', + Format => 'int32u', + PrintConv => '$val ? "Yes" : "No"', + }, + 0x7c => { + Name => 'WindowOriginAuto', + Format => 'int32u', + PrintConv => '$val ? "Yes" : "No"', + }, + 0x80 => { + Name => 'HistoryBufferSize', + Format => 'int32u', + }, + 0x84 => { + Name => 'NumHistoryBuffers', + Format => 'int32u', + }, + 0x88 => { + Name => 'RemoveHistoryDuplicates', + Format => 'int32u', + PrintConv => '$val ? "Yes" : "No"', + }, +); + +%Image::ExifTool::LNK::TrackerData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + 0x10 => { + Name => 'MachineID', + Format => 'var_string', + }, +); + +%Image::ExifTool::LNK::ConsoleFEData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + 0x08 => { + Name => 'CodePage', + Format => 'int32u', + }, +); + +#------------------------------------------------------------------------------ +# Extract null-terminated ASCII or Unicode string from buffer +# Inputs: 0) buffer ref, 1) start position, 2) flag for unicode string +# Return: string or undef if start position is outside bounds +sub GetString($$;$) +{ + my ($dataPt, $pos, $unicode) = @_; + return undef if $pos >= length($$dataPt); + pos($$dataPt) = $pos; + return $1 if ($unicode ? $$dataPt=~/\G((?:..)*?)\0\0/sg : $$dataPt=~/\G(.*?)\0/sg); + return substr($$dataPt, $pos); +} + +#------------------------------------------------------------------------------ +# Process item ID data +# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref +# Returns: 1 on success +sub ProcessItemID($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = length $$dataPt; + my $pos = 0; + my %opts = ( + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + ); + $et->VerboseDir('ItemID', undef, $dataLen); + for (;;) { + last if $pos + 4 >= $dataLen; + my $size = Get16u($dataPt, $pos); + last if $size < 2 or $pos + $size > $dataLen; + my $tag = Get16u($dataPt, $pos+2); # (just a guess -- may not be a tag at all) + AddTagToTable($tagTablePtr, $tag, { + Name => sprintf('Item%.4x', $tag), + SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' }, + }) unless $$tagTablePtr{$tag}; + $et->HandleTag($tagTablePtr, $tag, undef, %opts, Start => $pos, Size => $size); + $pos += $size; + } +} + +#------------------------------------------------------------------------------ +# Process link information data +# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref +# Returns: 1 on success +sub ProcessLinkInfo($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = length $$dataPt; + return 0 if $dataLen < 0x20; + my $hdrLen = Get32u($dataPt, 4); + my $lif = Get32u($dataPt, 8); # link info flags + my %opts = ( + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Size => 4, # (typical value size) + ); + my ($off, $unicode, $pos, $val, $size); + $et->VerboseDir('LinkInfo', undef, $dataLen); + if ($lif & 0x01) { + # read Volume ID + $off = Get32u($dataPt, 0x0c); + if ($off and $off + 0x20 <= $dataLen) { + # my $len = Get32u($dataPt, $off); + $et->HandleTag($tagTablePtr, 'DriveType', undef, %opts, Start=>$off+4); + $et->HandleTag($tagTablePtr, 'DriveSerialNumber', undef, %opts, Start=>$off+8); + $pos = Get32u($dataPt, $off + 0x0c); + if ($pos == 0x14) { + # use VolumeLabelOffsetUnicode instead + $pos = Get32u($dataPt, $off + 0x10); + $unicode = 1; + } + $pos += $off; + $val = GetString($dataPt, $pos, $unicode); + if (defined $val) { + $size = length $val; + $val = $et->Decode($val, 'UCS2') if $unicode; + $et->HandleTag($tagTablePtr, 'VolumeLabel', $val, %opts, Start=>$pos, Size=>$size); + } + } + # read local base path + if ($hdrLen >= 0x24) { + $pos = Get32u($dataPt, 0x1c); + $unicode = 1; + } else { + $pos = Get32u($dataPt, 0x10); + undef $unicode; + } + $val = GetString($dataPt, $pos, $unicode); + if (defined $val) { + $size = length $val; + $val = $et->Decode($val, 'UCS2') if $unicode; + $et->HandleTag($tagTablePtr, 'LocalBasePath', $val, %opts, Start=>$pos, Size=>$size); + } + } + if ($lif & 0x02) { + # read common network relative link + $off = Get32u($dataPt, 0x14); + if ($off and $off + 0x14 <= $dataLen) { + my $siz = Get32u($dataPt, $off); + return 0 if $off + $siz > $dataLen; + $pos = Get32u($dataPt, $off + 0x08); + if ($pos > 0x14 and $siz >= 0x18) { + $pos = Get32u($dataPt, $off + 0x14); + $unicode = 1; + } else { + undef $unicode; + } + $val = GetString($dataPt, $off + $pos, $unicode); + if (defined $val) { + $size = length $val; + $val = $et->Decode($val, 'UCS2') if $unicode; + $et->HandleTag($tagTablePtr, 'NetName', $val, %opts, Start=>$pos, Size=>$size); + } + my $flg = Get32u($dataPt, $off + 0x04); + if ($flg & 0x01) { + $pos = Get32u($dataPt, $off + 0x0c); + if ($pos > 0x14 and $siz >= 0x1c) { + $pos = Get32u($dataPt, $off + 0x18); + $unicode = 1; + } else { + undef $unicode; + } + $val = GetString($dataPt, $off + $pos, $unicode); + if (defined $val) { + $size = length $val; + $val = $et->Decode($val, 'UCS2') if $unicode; + $et->HandleTag($tagTablePtr, 'DeviceName', $val, %opts, Start=>$pos, Size=>$size); + } + } + if ($flg & 0x02) { + $val = Get32u($dataPt, $off + 0x10); + $et->HandleTag($tagTablePtr, 'NetProviderType', $val, %opts, Start=>$off + 0x10); + } + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from a MS Shell Link (Windows shortcut) file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid LNK file +sub ProcessLNK($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $buf2, $len, $i); + + # read LNK file header + $raf->Read($buff, 0x4c) == 0x4c or return 0; + $buff =~ /^.{4}\x01\x14\x02\0{5}\xc0\0{6}\x46/s or return 0; + $len = unpack('V', $buff); + $len >= 0x4c or return 0; + if ($len > 0x4c) { + $raf->Read($buf2, $len - 0x4c) == $len - 0x4c or return 0; + $buff .= $buf2; + } + $et->SetFileType(); + SetByteOrder('II'); + + my $tagTablePtr = GetTagTable('Image::ExifTool::LNK::Main'); + my %dirInfo = ( + DataPt => \$buff, + DataPos => 0, + DataLen => length $buff, + DirLen => length $buff, + ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + + my $flags = Get32u(\$buff, 0x14); + + # read link target ID list + if ($flags & 0x01) { + $raf->Read($buff, 2) or return 1; + $len = unpack('v', $buff); + $raf->Read($buff, $len) == $len or return 1; + $et->HandleTag($tagTablePtr, 0x10000, undef, + DataPt => \$buff, + DataPos => $raf->Tell() - $len, + Size => $len, + ); + } + + # read link information + if ($flags & 0x02) { + $raf->Read($buff, 4) or return 1; + $len = unpack('V', $buff); + return 1 if $len < 4; + $raf->Read($buf2, $len - 4) == $len - 4 or return 1; + $buff .= $buf2; + $et->HandleTag($tagTablePtr, 0x20000, undef, + DataPt => \$buff, + DataPos => $raf->Tell() - $len, + Size => $len, + ); + } + + # read string data + my @strings = qw(Description RelativePath WorkingDirectory + CommandLineArguments IconFileName); + for ($i=0; $i<@strings; ++$i) { + my $mask = 0x04 << $i; + next unless $flags & $mask; + $raf->Read($buff, 2) or return 1; + $len = unpack('v', $buff); + $len *= 2 if $flags & 0x80; # characters are 2 bytes if Unicode flag is set + $raf->Read($buff, $len) or return 1; + my $val; + $val = $et->Decode($buff, 'UCS2') if $flags & 0x80; + $et->HandleTag($tagTablePtr, 0x30000 | $mask, $val, + DataPt => \$buff, + DataPos => $raf->Tell() - $len, + Size => $len, + ); + } + + # read extra data + while ($raf->Read($buff, 4) == 4) { + $len = unpack('V', $buff); + last if $len < 4; + $len -= 4; + $raf->Read($buf2, $len) == $len or last; + next unless $len > 4; + $buff .= $buf2; + my $tag = Get32u(\$buff, 4); + my $tagInfo = $$tagTablePtr{$tag}; + unless (ref $tagInfo eq 'HASH' and $$tagInfo{SubDirectory}) { + $tagInfo = $$tagTablePtr{0xa0000000}; + } + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => \$buff, + DataPos => $raf->Tell() - $len - 4, + TagInfo => $tagInfo, + ); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::LNK - Read MS Shell Link (.LNK) meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information MS Shell Link (Windows shortcut) files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://msdn.microsoft.com/en-us/library/dd871305(PROT.10).aspx> + +=item L<http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/LNK Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Lang/cs.pm b/ExifTool/lib/Image/ExifTool/Lang/cs.pm new file mode 100644 index 0000000..ab91e4a --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/cs.pm @@ -0,0 +1,1588 @@ +#------------------------------------------------------------------------------ +# File: cs.pm +# +# Description: ExifTool Czech language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::cs; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.07'; + +%Image::ExifTool::Lang::cs::Translate = ( + 'AEMeteringMode' => { + PrintConv => { + 'Multi-segment' => 'Multi segment', + }, + }, + 'AEProgramMode' => { + PrintConv => { + 'Landscape' => 'Krajina', + 'Macro' => 'Makro', + 'Portrait' => 'Portrét', + }, + }, + 'AFPoint' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'AFPointBrightness' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'AFPointSelectionMethod' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'AFPointsInFocus' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'APEVersion' => 'APE verze', + 'ActiveD-Lighting' => { + PrintConv => { + 'Low' => 'MénÄ›', + 'Normal' => 'Normální', + }, + }, + 'ActiveD-LightingMode' => { + PrintConv => { + 'Low' => 'MénÄ›', + 'Normal' => 'Normální', + }, + }, + 'AdultContentWarning' => { + PrintConv => { + 'Unknown' => 'Neznámý', + }, + }, + 'Annotations' => 'Poznámky Photoshop', + 'Aperture' => 'Clona', + 'ApertureValue' => 'Clona', + 'Artist' => 'Autor', + 'AssistButtonFunction' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'Author' => 'Autor', + 'AuthorsPosition' => 'Pozice autora', + 'AutoLightingOptimizer' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'AutoRotate' => { + PrintConv => { + 'None' => 'Žádná', + 'Rotate 180' => '180° (dolů/vpravo)', + 'Rotate 270 CW' => '90° po smÄ›ru HR (vlevo/dolů)', + 'Rotate 90 CW' => '90° ptoti smÄ›ru HR (vpravo/nahoru)', + 'n/a' => 'Neznámý', + }, + }, + 'BadFaxLines' => 'Å patné faxové řádky', + 'BannerImageType' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'BatteryLevel' => 'Stav baterie', + 'BitsPerSample' => 'PoÄet bitů na složku', + 'BlurWarning' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'Brightness' => 'Jas', + 'BrightnessValue' => 'Jas', + 'By-line' => 'Autor', + 'CFAPattern' => 'CFA matrice', + 'CFAPattern2' => 'CFA matice 2', + 'CFARepeatPatternDim' => 'Velikost berevné matice CFA', + 'CPUType' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'CalibrationIlluminant1' => { + PrintConv => { + 'Cloudy' => 'Zataženo', + 'Cool White Fluorescent' => 'Studená zářivka', + 'Day White Fluorescent' => 'Denní zářivka', + 'Daylight' => 'Denní svÄ›tlo', + 'Daylight Fluorescent' => 'Denní svÄ›tlo', + 'Fine Weather' => 'SluneÄno', + 'Flash' => 'Blesk', + 'Fluorescent' => 'Žárovka', + 'ISO Studio Tungsten' => 'Studiová svÄ›tla', + 'Other' => 'Jiné osvÄ›tlení', + 'Shade' => 'Stíny', + 'Standard Light A' => 'Standardní svÄ›tlo A', + 'Standard Light B' => 'Standardní svÄ›tlo B', + 'Standard Light C' => 'Standardní svÄ›tlo C', + 'Tungsten (Incandescent)' => 'Zářivka', + 'Unknown' => 'Neznámý', + 'White Fluorescent' => 'Bílá zářivka', + }, + }, + 'CalibrationIlluminant2' => { + PrintConv => { + 'Cloudy' => 'Zataženo', + 'Cool White Fluorescent' => 'Studená zářivka', + 'Day White Fluorescent' => 'Denní zářivka', + 'Daylight' => 'Denní svÄ›tlo', + 'Daylight Fluorescent' => 'Denní svÄ›tlo', + 'Fine Weather' => 'SluneÄno', + 'Flash' => 'Blesk', + 'Fluorescent' => 'Žárovka', + 'ISO Studio Tungsten' => 'Studiová svÄ›tla', + 'Other' => 'Jiné osvÄ›tlení', + 'Shade' => 'Stíny', + 'Standard Light A' => 'Standardní svÄ›tlo A', + 'Standard Light B' => 'Standardní svÄ›tlo B', + 'Standard Light C' => 'Standardní svÄ›tlo C', + 'Tungsten (Incandescent)' => 'Zářivka', + 'Unknown' => 'Neznámý', + 'White Fluorescent' => 'Bílá zářivka', + }, + }, + 'CameraOrientation' => { + Description => 'Orientace', + PrintConv => { + 'Horizontal (normal)' => '0° (nahoru/vlevo)', + 'Rotate 270 CW' => '90° po smÄ›ru HR (vlevo/dolů)', + 'Rotate 90 CW' => '90° ptoti smÄ›ru HR (vpravo/nahoru)', + }, + }, + 'CanonExposureMode' => { + PrintConv => { + 'Aperture-priority AE' => 'Priorita clony', + 'Manual' => 'Manuální', + 'Shutter speed priority AE' => 'Priorita Äasu', + }, + }, + 'Caption-Abstract' => 'Popisek', + 'CaptionWriter' => 'Autor popisku', + 'CaptureXResolutionUnit' => { + PrintConv => { + 'um' => 'µm (mikrometr)', + }, + }, + 'CaptureYResolutionUnit' => { + PrintConv => { + 'um' => 'µm (mikrometr)', + }, + }, + 'Categories' => 'Kategorie', + 'Category' => 'Kategorie', + 'CellLength' => 'Délka buňky', + 'CellWidth' => 'Šířka buňky', + 'CenterWeightedAreaSize' => { + PrintConv => { + 'Average' => 'PrůmÄ›r', + }, + }, + 'ChrominanceNR_TIFF_JPEG' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'ChrominanceNoiseReduction' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'City' => 'MÄ›sto', + 'CleanFaxData' => 'ÄŒistá fax data', + 'ColorEffect' => { + PrintConv => { + 'Sepia' => 'Sépie', + }, + }, + 'ColorFilter' => 'Barevný filtr', + 'ColorMap' => 'Barevná mapa', + 'ColorMode' => { + PrintConv => { + 'Landscape' => 'Krajina', + 'Normal' => 'Normální', + 'Portrait' => 'Portrét', + }, + }, + 'ColorMoireReductionMode' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'ColorResponseUnit' => 'Odpovídající barevná jednotka', + 'ColorSpace' => { + Description => 'Barevný prostor', + PrintConv => { + 'ICC Profile' => 'ICC Profil', + 'Uncalibrated' => 'Nekalibrován', + }, + }, + 'ColorTemperature' => 'Teplota barev', + 'ColorTone' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'CommanderGroupAMode' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'CommanderGroupBMode' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'CommanderInternalFlash' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'Comment' => 'Komentář', + 'ComponentsConfiguration' => 'UrÄení složek', + 'CompressedBitsPerPixel' => 'KomprimaÄní mod', + 'Compression' => { + Description => 'Kompresní algoritmus', + PrintConv => { + 'JPEG' => 'JPEG komprese', + 'JPEG (old-style)' => 'JPEG (pův. verze)', + 'Kodak DCR Compressed' => 'Kodak DCR komprese', + 'Kodak KDC Compressed' => 'Kodak KDC komprese', + 'Next' => 'Kódování NeXT 2-bit', + 'Nikon NEF Compressed' => 'Nikon NEF komprese', + 'None' => 'Žádná', + 'Pentax PEF Compressed' => 'Pentax PEF komprese', + 'SGILog' => 'Kódování SGI 32-bit Log Luminance', + 'SGILog24' => 'Kódování SGI 24-bit Log Luminance', + 'Sony ARW Compressed' => 'Sony ARW komprese', + 'Thunderscan' => 'Kódování ThunderScan 4-bit', + 'Uncompressed' => 'Bez komprese', + }, + }, + 'CompressionType' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'ConsecutiveBadFaxLines' => 'Sekvence vadných faxových řádků', + 'Contrast' => { + Description => 'Kontrast', + PrintConv => { + 'High' => 'Více', + 'Low' => 'MénÄ›', + 'Normal' => 'Normální', + }, + }, + 'ConversionLens' => { + PrintConv => { + 'Macro' => 'Makro', + }, + }, + 'Copyright' => 'Držitel práv', + 'CopyrightNotice' => 'Oznámení o autorských právech', + 'CopyrightStatus' => { + PrintConv => { + 'Unknown' => 'Neznámý', + }, + }, + 'Country' => 'ZemÄ›', + 'Country-PrimaryLocationName' => 'ZemÄ›', + 'CreateDate' => 'Datum a Äas generování digitálních dat', + 'CreationDate' => 'Datum vytvoÅ™ení', + 'Credit' => 'Kredit', + 'CropUnit' => { + PrintConv => { + 'inches' => 'Palce', + }, + }, + 'CropUnits' => { + PrintConv => { + 'inches' => 'Palce', + }, + }, + 'CustomRendered' => { + Description => 'Zpracování obrazu', + PrintConv => { + 'Custom' => 'Uživatelské zpracování', + 'Normal' => 'Normální proces', + }, + }, + 'DataImprint' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'DateCreated' => 'Datum vytvoÅ™ení', + 'DateTime' => 'Datum a Äas zmÄ›ny souboru', + 'DateTimeOriginal' => 'Datum a Äas vzniku originálních dat', + 'Description' => 'Popis', + 'DeviceSettingDescription' => 'Popis nastavení zařízení', + 'DialDirectionTvAv' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'DigitalZoom' => { + Description => 'Digitální pÅ™iblížení', + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'DigitalZoomRatio' => 'Digitální zoom', + 'Directory' => 'UmístÄ›ní souboru', + 'DisplaySize' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'DisplayUnits' => { + PrintConv => { + 'inches' => 'Palce', + }, + }, + 'DisplayXResolutionUnit' => { + PrintConv => { + 'um' => 'µm (mikrometr)', + }, + }, + 'DisplayYResolutionUnit' => { + PrintConv => { + 'um' => 'µm (mikrometr)', + }, + }, + 'DisplayedUnitsX' => { + PrintConv => { + 'inches' => 'Palce', + }, + }, + 'DisplayedUnitsY' => { + PrintConv => { + 'inches' => 'Palce', + }, + }, + 'DjVuVersion' => 'DjVu verze', + 'DocumentName' => 'Jméno dokumentu', + 'DotRange' => 'Bodová rozteÄ', + 'DriveMode' => 'Režim spouÅ¡tÄ›', + 'ETTLII' => { + PrintConv => { + 'Average' => 'PrůmÄ›r', + }, + }, + 'EasyMode' => { + PrintConv => { + 'Landscape' => 'Krajina', + 'Macro' => 'Makro', + 'Manual' => 'Manuální', + 'Night' => 'NoÄní foto', + 'Portrait' => 'Portrét', + }, + }, + 'Emphasis' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'ExifImageHeight' => 'Výška', + 'ExifImageWidth' => 'Šířka', + 'ExifOffset' => 'Ukazatel Exif IFD', + 'ExifToolVersion' => 'ExifTool verze', + 'ExifVersion' => 'Exif verze', + 'ExpandFilm' => 'Ext. film', + 'ExpandFilterLens' => 'Ext. filtr objektivu', + 'ExpandFlashLamp' => 'Ext. svÄ›tlo blesku', + 'ExpandLens' => 'Ext. objektiv', + 'ExpandScanner' => 'Ext. skener', + 'ExpandSoftware' => 'Ext. Software', + 'ExposureCompensation' => 'Korekce expozice', + 'ExposureIndex' => 'Index expozice', + 'ExposureMode' => { + Description => 'Mód expozice', + PrintConv => { + 'Aperture Priority' => 'Priorita clony', + 'Aperture-priority AE' => 'Priorita clony', + 'Auto' => 'Automatická expozice', + 'Auto bracket' => 'Auto braketing', + 'Landscape' => 'Krajina', + 'Manual' => 'Manuální expozice', + 'Portrait' => 'Portrét', + 'Shutter Priority' => 'Priorita Äasu', + 'Shutter speed priority AE' => 'Priorita Äasu', + }, + }, + 'ExposureModeInManual' => { + PrintConv => { + 'Center-weighted average' => 'ZvýraznÄ›ný stÅ™ed', + 'Partial metering' => 'Blokové', + 'Spot metering' => 'StÅ™edový bod', + }, + }, + 'ExposureProgram' => { + Description => 'ExpoziÄní mod', + PrintConv => { + 'Action (High speed)' => 'AkÄní program (ovlivnÄ›ný Äas závÄ›rky)', + 'Aperture Priority' => 'Priorita clony', + 'Aperture-priority AE' => 'Priorita clony', + 'Bulb' => 'Žárovka', + 'Creative (Slow speed)' => 'Kreativní program (ovlivnÄ›ná hloubka ostrosti)', + 'Landscape' => 'Krajina', + 'Manual' => 'Manuální', + 'Not Defined' => 'Nedefinovaný', + 'Portrait' => 'Portrét', + 'Program AE' => 'Normální program', + 'Shutter Priority' => 'Priorita Äasu', + 'Shutter speed priority AE' => 'Priorita Äasu', + }, + }, + 'ExposureTime' => 'ExpoziÄní Äas', + 'ExposureTime2' => 'ExpoziÄní Äas 2', + 'FNumber' => 'F hodnota', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => '0° (nahoru/vlevo)', + 'Rotate 180' => '180° (dolů/vpravo)', + 'Rotate 270 CW' => '90° po smÄ›ru HR (vlevo/dolů)', + 'Rotate 90 CW' => '90° ptoti smÄ›ru HR (vpravo/nahoru)', + }, + }, + 'FaxProfile' => { + PrintConv => { + 'Unknown' => 'Neznámý', + }, + }, + 'FaxRecvParams' => 'Parametry příjemce faxu', + 'FaxRecvTime' => 'ÄŒas příjmu faxu', + 'FaxSubAddress' => 'Sub adresa faxu', + 'FileFormat' => 'Formát', + 'FileModifyDate' => 'Datum úpravy', + 'FileName' => 'Jméno', + 'FileSize' => 'Velikost', + 'FileSource' => { + Description => 'Zdroj dat', + PrintConv => { + 'Digital Camera' => 'Digitální fotoaparát', + 'Film Scanner' => 'Filmový skener', + 'Reflection Print Scanner' => 'Skener', + }, + }, + 'FileType' => 'Typ', + 'Filename' => 'Jméno', + 'FillOrder' => { + Description => 'PoÅ™adí výplnÄ›', + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'FilterEffect' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'FilterEffectMonochrome' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'Flash' => { + Description => 'Blesk', + PrintConv => { + 'Auto, Did not fire' => 'Blesk nepoužit, auto mod', + 'Auto, Did not fire, Red-eye reduction' => 'Auto mod, nepoužit, redukce Äervených oÄí', + 'Auto, Fired' => 'Blesk použit, auto mod', + 'Auto, Fired, Red-eye reduction' => 'Blesk použit, auto mod, redukce Äervených oÄí', + 'Auto, Fired, Red-eye reduction, Return detected' => 'Blesk použit, auto mod, redukce Äervených oÄí, odraz detekován', + 'Auto, Fired, Red-eye reduction, Return not detected' => 'Blesk použit, auto mod, redukce Äervených oÄí, odraz nezjiÅ¡tÄ›n', + 'Auto, Fired, Return detected' => 'Blesk použit, auto mod, odraz detekován', + 'Auto, Fired, Return not detected' => 'Blesk použit, auto mod, odraz nedetekován', + 'Did not fire' => 'Blesk ne', + 'Fired' => 'Blesk ano', + 'Fired, Red-eye reduction' => 'Blesk použit, redukce Äervených oÄí', + 'Fired, Red-eye reduction, Return detected' => 'Blesk použit, redukce Äervených oÄí, odraz detekován', + 'Fired, Red-eye reduction, Return not detected' => 'Blesk použit, redukce Äervených oÄí, odraz nezjiÅ¡tÄ›n', + 'Fired, Return detected' => 'Odraz strobozáblesků detekován', + 'Fired, Return not detected' => 'Odraz strobozáblesků nezjiÅ¡tÄ›n', + 'No Flash' => 'Blesk ne', + 'No flash function' => 'Blesk nezjiÅ¡tÄ›n', + 'Off' => 'Blesk nepoužit, vynucený mod', + 'Off, Did not fire' => 'Blesk nepoužit, vynucený mod', + 'Off, Did not fire, Return not detected' => 'Blesk vypnut, bez záblesku, odraz nezachycen', + 'Off, No flash function' => 'Neaktivní, bez funkce blesku', + 'Off, Red-eye reduction' => 'Neaktivní, redukce Äervených oÄí', + 'On' => 'Blesk použit, vynucený mod', + 'On, Did not fire' => 'Blesk zapnut, nepoužit', + 'On, Fired' => 'Blesk použit, vynucený mod', + 'On, Red-eye reduction' => 'Blesk použit, vynucený mod, redukce Äervených oÄí', + 'On, Red-eye reduction, Return detected' => 'Blesk použit, vynucený mod, redukce Äervených oÄí, odraz detekován', + 'On, Red-eye reduction, Return not detected' => 'Blesk použit, vynucený mod, redukce Äervených oÄí, odraz nezjiÅ¡tÄ›n', + 'On, Return detected' => 'Blesk použit, vynucený mod, odraz detekován', + 'On, Return not detected' => 'Blesk použit, vynucený mod, odraz nezjiÅ¡tÄ›n', + }, + }, + 'FlashControlMode' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'FlashDevice' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'FlashEnergy' => 'Síla záblesku', + 'FlashExposureComp' => 'Kompenzace blesku', + 'FlashGroupAControlMode' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'FlashGroupBControlMode' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'FlashGroupCControlMode' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'FlashIntensity' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'FlashMode' => { + PrintConv => { + 'Normal' => 'Normální', + 'Unknown' => 'Neznámý', + }, + }, + 'FlashModel' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'FlashOptions' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'FlashOptions2' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'FlashType' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'FlashpixVersion' => 'Podporovaná verze Flashpix', + 'FocalLength' => 'Ohnisková vzdálenost', + 'FocalLength35efl' => 'Ohnisková vzdálenost', + 'FocalLengthIn35mmFormat' => 'PÅ™epoÄtená ohnisková vzdálenost (35mm)', + 'FocalPlaneResolutionUnit' => { + Description => 'Jednotka rozliÅ¡ení senzoru', + PrintConv => { + 'None' => 'Žádná', + 'inches' => 'Palce', + 'um' => 'µm (mikrometr)', + }, + }, + 'FocalPlaneXResolution' => 'Horizontální rozliÅ¡ení senzoru', + 'FocalPlaneYResolution' => 'Vertikální rozliÅ¡ení senzoru', + 'Focus' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'FocusContinuous' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'FocusMode' => { + Description => 'OstÅ™ení', + PrintConv => { + 'Macro' => 'Makro', + 'Manual' => 'Manuální', + 'Normal' => 'Normální', + }, + }, + 'FocusMode2' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'FocusModeSetting' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'FocusRange' => { + PrintConv => { + 'Macro' => 'Makro', + 'Manual' => 'Manuální', + 'Normal' => 'Normální', + }, + }, + 'FocusTrackingLockOn' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'FrameRate' => 'Snímkovací frekvence', + 'FrameSize' => 'Velikost snímku', + 'FreeByteCounts' => 'PoÄet volných bytů', + 'FreeOffsets' => 'Volná datová pozice', + 'GIFVersion' => 'GIF verze', + 'GPSAltitude' => 'NadmoÅ™ská výška', + 'GPSAltitudeRef' => { + Description => 'NadmoÅ™ská výška', + PrintConv => { + 'Above Sea Level' => 'NadmoÅ™ská výška', + 'Below Sea Level' => 'NadmoÅ™ská výška (záporná hodnota)', + }, + }, + 'GPSAreaInformation' => 'Název GPS oblasti', + 'GPSDOP' => 'PÅ™esnost měření', + 'GPSDateStamp' => 'GPS Datum', + 'GPSDateTime' => 'GPS Äas (atomový Äas)', + 'GPSDestBearing' => 'Azimut cíle', + 'GPSDestBearingRef' => { + Description => 'Reference azimutu cíle.', + PrintConv => { + 'Magnetic North' => 'Magnetický smÄ›r', + 'True North' => 'Geografický smÄ›r', + }, + }, + 'GPSDestDistance' => 'Vzdálenost k cíli', + 'GPSDestDistanceRef' => { + Description => 'Reference vzdálenosti cíle', + PrintConv => { + 'Kilometers' => 'Kilometry', + 'Miles' => 'Míle', + 'Nautical Miles' => 'Uzle', + }, + }, + 'GPSDestLatitude' => 'ZemÄ›pisná šířka cíle', + 'GPSDestLatitudeRef' => { + Description => 'Reference pro zemÄ›pisnou šířku cíle', + PrintConv => { + 'North' => 'Severní šířka', + 'South' => 'Jižní šířka', + }, + }, + 'GPSDestLongitude' => 'ZemÄ›pisná délka cíle', + 'GPSDestLongitudeRef' => { + Description => 'Reference pro zemÄ›pisnou délku cíle', + PrintConv => { + 'East' => 'Východní délka', + 'West' => 'Západní délka', + }, + }, + 'GPSDifferential' => { + Description => 'GPS rozdílová korekce', + PrintConv => { + 'Differential Corrected' => 'ZapoÄítaná rozdílová korekce', + 'No Correction' => 'Měření bez korekce', + }, + }, + 'GPSImgDirection' => 'Orientace obrázku', + 'GPSImgDirectionRef' => { + Description => 'Reference k orientaci obrázku', + PrintConv => { + 'Magnetic North' => 'Magnetický smÄ›r', + 'True North' => 'Geografický smÄ›r', + }, + }, + 'GPSInfo' => 'IFD ukazatel v GPS informacích', + 'GPSLatitude' => 'ZemÄ›pisná šířka', + 'GPSLatitudeRef' => { + Description => 'Severní nebo Jižní šířka', + PrintConv => { + 'North' => 'Severní šířka', + 'South' => 'Jižní šířka', + }, + }, + 'GPSLongitude' => 'ZemÄ›pisná délka', + 'GPSLongitudeRef' => { + Description => 'Východní nebo západní délka', + PrintConv => { + 'East' => 'Východní délka', + 'West' => 'Západní délka', + }, + }, + 'GPSMapDatum' => 'Geodetická data', + 'GPSMeasureMode' => { + Description => 'Mod GPS', + PrintConv => { + '2-D' => '2-dimenzionální měření', + '2-Dimensional' => '2-dimenzionální měření', + '2-Dimensional Measurement' => '2-dimenzionální měření', + '3-D' => '3-dimenzionální měření', + '3-Dimensional' => '3-dimenzionální měření', + '3-Dimensional Measurement' => '3-dimenzionální měření', + }, + }, + 'GPSProcessingMethod' => 'Název procesní metody GPS', + 'GPSSatellites' => 'GPS satelity využité pÅ™i měření', + 'GPSSpeed' => 'Rychlost GPS pÅ™ijímaÄe', + 'GPSSpeedRef' => { + Description => 'Jednotka rychlosti', + PrintConv => { + 'km/h' => 'Kilometry za hodinu', + 'knots' => 'Uzle', + 'mph' => 'Míle za hodinu', + }, + }, + 'GPSStatus' => { + Description => 'Stav GPS pÅ™ijímaÄe', + PrintConv => { + 'Measurement Active' => 'Probíhá měření', + 'Measurement Void' => 'Vzájemné měření', + }, + }, + 'GPSTimeStamp' => 'GPS Äas (atomový Äas)', + 'GPSTrack' => 'SmÄ›r pohybu', + 'GPSTrackRef' => { + Description => 'Reference pro smÄ›r pohybu', + PrintConv => { + 'Magnetic North' => 'Magnetický smÄ›r', + 'True North' => 'Geografický smÄ›r', + }, + }, + 'GPSVersionID' => 'Verze GPS TAGu', + 'GainControl' => { + Description => 'Míra jasu', + PrintConv => { + 'High gain down' => 'Silné zeslabení', + 'High gain up' => 'Silné zesílení', + 'Low gain down' => 'Slabé zeslabení', + 'Low gain up' => 'Slabé zesílení', + 'None' => 'Žádná', + }, + }, + 'Gradation' => 'Pusobivy', + 'GrayResponseCurve' => 'Å edá referenÄní kÅ™ivka', + 'GrayResponseUnit' => { + Description => 'Jednotka odezvy Å¡edé', + PrintConv => { + '0.0001' => 'Číslo udávající tisíce jednotek', + '0.001' => 'Číslo udávající stovky jednotek', + '0.1' => 'Číslo udávající desítky jednotek', + '1e-05' => 'Číslo udávající desetitisíce jednotek', + '1e-06' => 'Číslo udávající statisíce jednotek', + }, + }, + 'HalftoneHints' => 'Půltóny', + 'Headline' => 'Titulek', + 'HighISONoiseReduction' => { + PrintConv => { + 'Low' => 'MénÄ›', + 'Normal' => 'Normální', + }, + }, + 'HostComputer' => 'Host', + 'Hue' => 'Odstín', + 'ICCProfile' => 'ICC-Profil', + 'IPTC-NAA' => 'IPTC-NAA metadata', + 'ISO' => 'Citlivost ISO', + 'ISOSetting' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'ImageDescription' => 'Popis obrázku', + 'ImageHeight' => 'Výška', + 'ImageHistory' => 'Historie obrázku', + 'ImageNumber' => 'Číslo obrázku', + 'ImageOrientation' => { + PrintConv => { + 'Portrait' => 'Portrét', + }, + }, + 'ImageQuality' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'ImageSize' => 'Velikost snímku', + 'ImageSourceData' => 'Zdrojová data obrázku', + 'ImageTone' => { + PrintConv => { + 'Landscape' => 'Krajina', + 'Portrait' => 'Portrét', + }, + }, + 'ImageUniqueID' => 'JedineÄné ID obrázku', + 'ImageWidth' => 'Šířka', + 'Index' => 'NápovÄ›da', + 'InkNames' => 'Název náplnÄ›', + 'InkSet' => 'Inkoustová sada', + 'Instructions' => 'Pokyny', + 'InternalFlash' => { + PrintConv => { + 'Fired' => 'Blesk ano', + 'Manual' => 'Manuální', + 'No' => 'Blesk ne', + }, + }, + 'InteropIndex' => { + Description => 'Identifikace', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03: DCF option file (Adobe RGB)', + 'R98 - DCF basic file (sRGB)' => 'R98: DCF basic file (sRGB)', + 'THM - DCF thumbnail file' => 'THM: DCF thumbnail file', + }, + }, + 'InteropOffset' => 'ZnaÄka souÄinnosti', + 'InteropVersion' => 'Verze kompatibility', + 'JFIFVersion' => 'JFIF verze', + 'JPEGQuality' => { + PrintConv => { + 'Standard' => 'Normální', + }, + }, + 'Keyword' => 'KlíÄová slova', + 'Keywords' => 'KlíÄová slova', + 'LeafData' => 'Leaf data', + 'Lens' => 'Objektiv', + 'LensInfo' => 'Informace o optice', + 'LicenseType' => { + PrintConv => { + 'Unknown' => 'Neznámý', + }, + }, + 'LightSource' => { + Description => 'Zdroj svÄ›tla', + PrintConv => { + 'Cloudy' => 'Zataženo', + 'Cool White Fluorescent' => 'Studená zářivka', + 'Day White Fluorescent' => 'Denní zářivka', + 'Daylight' => 'Denní svÄ›tlo', + 'Daylight Fluorescent' => 'Denní svÄ›tlo', + 'Fine Weather' => 'SluneÄno', + 'Flash' => 'Blesk', + 'Fluorescent' => 'Žárovka', + 'ISO Studio Tungsten' => 'Studiová svÄ›tla', + 'Other' => 'Jiné osvÄ›tlení', + 'Shade' => 'Stíny', + 'Standard Light A' => 'Standardní svÄ›tlo A', + 'Standard Light B' => 'Standardní svÄ›tlo B', + 'Standard Light C' => 'Standardní svÄ›tlo C', + 'Tungsten (Incandescent)' => 'Zářivka', + 'Unknown' => 'Neznámý', + 'White Fluorescent' => 'Bílá zářivka', + }, + }, + 'Lightness' => 'Jas', + 'Location' => 'Lokalita', + 'LoopStyle' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'LuminanceNoiseReduction' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'MIEVersion' => 'MIE verze', + 'Macro' => { + PrintConv => { + 'Macro' => 'Makro', + 'Manual' => 'Manuální', + 'Normal' => 'Normální', + }, + }, + 'MacroMode' => { + PrintConv => { + 'Macro' => 'Makro', + 'Normal' => 'Normální', + }, + }, + 'Make' => 'Výrobce', + 'MakerNote' => 'Privátní data výrobce', + 'MakerNotes' => 'Poznámka výrobce', + 'ManualFlashOutput' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'MaxAperture' => 'Maximální clona+C1233 objektivu', + 'MaxApertureValue' => 'Max clona objektivu', + 'MaxSampleValue' => 'Max. hodnota vzorku', + 'MediaType' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'Metering' => { + PrintConv => { + 'Spot' => 'StÅ™edový bod', + }, + }, + 'MeteringMode' => { + Description => 'Režim měření expozice', + PrintConv => { + 'Average' => 'PrůmÄ›r', + 'Center-weighted average' => 'ZvýraznÄ›ný stÅ™ed', + 'Multi-segment' => 'Multi segment', + 'Multi-spot' => 'Vícebodové', + 'Other' => 'Jiné', + 'Partial' => 'Blokové', + 'Spot' => 'StÅ™edový bod', + 'Unknown' => 'Neznámý', + }, + }, + 'MeteringMode2' => { + PrintConv => { + 'Multi-segment' => 'Multi segment', + }, + }, + 'MeteringMode3' => { + PrintConv => { + 'Multi-segment' => 'Multi segment', + }, + }, + 'MinSampleValue' => 'Min. hodnota vzorku', + 'MinoltaQuality' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'Model' => 'Typ fotoaparátu', + 'Model2' => 'Typ zařízení (2)', + 'ModifiedPictureStyle' => { + PrintConv => { + 'Landscape' => 'Krajina', + 'None' => 'Žádná', + 'Portrait' => 'Portrét', + }, + }, + 'ModifiedSharpnessFreq' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'ModifiedToneCurve' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'ModifiedWhiteBalance' => { + PrintConv => { + 'Cloudy' => 'Zataženo', + 'Daylight' => 'Denní svÄ›tlo', + 'Daylight Fluorescent' => 'Denní svÄ›tlo', + 'Flash' => 'Blesk', + 'Fluorescent' => 'Žárovka', + 'Shade' => 'Stíny', + 'Tungsten' => 'Zářivka', + }, + }, + 'ModifyDate' => 'Datum a Äas zmÄ›ny souboru', + 'MonochromeFilterEffect' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'MonochromeToningEffect' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'NEFCompression' => { + PrintConv => { + 'Uncompressed' => 'Bez komprese', + }, + }, + 'Noise' => 'Å um', + 'NoiseFilter' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'NoiseReduction' => { + Description => 'PotlaÄení Å¡umu', + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'NumberofInks' => 'Číslo náplnÄ›', + 'ObjectFileType' => { + PrintConv => { + 'None' => 'Žádná', + 'Unknown' => 'Neznámý', + }, + }, + 'OldSubfileType' => 'Typ podsekce', + 'Opto-ElectricConvFactor' => 'Optoel. konverzní faktor (OECF)', + 'Orientation' => { + Description => 'Orientace', + PrintConv => { + 'Horizontal (normal)' => '0° (nahoru/vlevo)', + 'Mirror horizontal' => '0° (nahoru/vpravo)', + 'Mirror horizontal and rotate 270 CW' => '90° po smÄ›ru HR (vlevo/nahoru)', + 'Mirror horizontal and rotate 90 CW' => '90° ptoti smÄ›ru HR (vpravo/dolů)', + 'Mirror vertical' => '180° (dolů/vlevo)', + 'Rotate 180' => '180° (dolů/vpravo)', + 'Rotate 270 CW' => '90° po smÄ›ru HR (vlevo/dolů)', + 'Rotate 90 CW' => '90° ptoti smÄ›ru HR (vpravo/nahoru)', + }, + }, + 'PEFVersion' => 'PEF verze', + 'Padding' => 'Náhradní znaky', + 'PageName' => 'Jméno stránky', + 'PageNumber' => 'Číslo stránky', + 'PhotoEffectsType' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'PhotometricInterpretation' => { + Description => 'Pixelové schéma', + PrintConv => { + 'BlackIsZero' => 'ÄŒerná je nula', + 'Color Filter Array' => 'CFA (Color Filter Matrix)', + 'Pixar LogL' => 'CIE Log2(L) (Log luminance)', + 'Pixar LogLuv' => 'CIE Log2(L)(u\',v\') (Log luminance and chrominance)', + 'RGB Palette' => 'Barevné schema', + 'Transparency Mask' => 'Průhlednost', + 'WhiteIsZero' => 'Bílá je nula', + }, + }, + 'PhotoshopAnnotations' => 'Poznámky Photoshop', + 'PictureFinish' => { + PrintConv => { + 'Portrait' => 'Portrét', + }, + }, + 'PictureMode' => { + PrintConv => { + 'Aperture-priority AE' => 'Priorita clony', + 'Landscape' => 'Krajina', + 'Macro' => 'Makro', + 'Manual' => 'Manuální', + 'Portrait' => 'Portrét', + 'Shutter speed priority AE' => 'Priorita Äasu', + }, + }, + 'PictureMode2' => { + PrintConv => { + 'Aperture Priority' => 'Priorita clony', + 'Manual' => 'Manuální', + 'Shutter Speed Priority' => 'Priorita Äasu', + }, + }, + 'PictureStyle' => { + PrintConv => { + 'Landscape' => 'Krajina', + 'None' => 'Žádná', + 'Portrait' => 'Portrét', + }, + }, + 'PixelUnits' => { + PrintConv => { + 'Unknown' => 'Neznámý', + }, + }, + 'PlanarConfiguration' => { + Description => 'Uspořádání obrazových dat', + PrintConv => { + 'Chunky' => 'Chunky Format (prokládaný)', + 'Planar' => 'Planární (dvojrozmÄ›rný)', + }, + }, + 'Predictor' => { + Description => 'Predikce', + PrintConv => { + 'Horizontal differencing' => 'Horizontální diferenciace', + 'None' => 'Bez predikce', + }, + }, + 'PreviewColorSpace' => { + PrintConv => { + 'Unknown' => 'Neznámý', + }, + }, + 'PreviewImage' => 'Náhled', + 'PreviewQuality' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'PrimaryChromaticities' => 'ChromatiÄnost primárních barev', + 'ProgramLine' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'ProgramMode' => { + PrintConv => { + 'None' => 'Žádná', + 'Portrait' => 'Portrét', + }, + }, + 'Province-State' => 'Stát/provincie', + 'Quality' => { + PrintConv => { + 'Low' => 'MénÄ›', + 'Normal' => 'Normální', + }, + }, + 'QualityMode' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'RAFVersion' => 'RAF verze', + 'Rating' => 'Hodnocení', + 'RatingPercent' => 'Hodnocení v procentech', + 'RawJpgQuality' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'RecordMode' => { + Description => 'Režim záznamu', + PrintConv => { + 'Aperture Priority' => 'Priorita clony', + 'Manual' => 'Manuální', + 'Shutter Priority' => 'Priorita Äasu', + }, + }, + 'RecordingMode' => { + PrintConv => { + 'Landscape' => 'Krajina', + 'Manual' => 'Manuální', + 'Portrait' => 'Portrét', + }, + }, + 'ReferenceBlackWhite' => 'ÄŒerný a bílý referenÄní bod', + 'RelatedImageFileFormat' => 'Obrazový formát', + 'RelatedImageHeight' => 'Výška obrázku', + 'RelatedImageWidth' => 'Šířka obrázku', + 'RelatedSoundFile' => 'Audio soubor', + 'ResampleParamsQuality' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'ResolutionUnit' => { + Description => 'Jednotka X a Y rozliÅ¡ení', + PrintConv => { + 'None' => 'Žádná', + 'cm' => 'Pixely/cm', + 'inches' => 'Palce', + }, + }, + 'RetouchHistory' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'Rotation' => { + PrintConv => { + 'Horizontal' => '0° (nahoru/vlevo)', + 'Horizontal (Normal)' => '0° (nahoru/vlevo)', + 'Horizontal (normal)' => '0° (nahoru/vlevo)', + 'Rotate 180' => '180° (dolů/vpravo)', + 'Rotate 270 CW' => '90° po smÄ›ru HR (vlevo/dolů)', + 'Rotate 90 CW' => '90° ptoti smÄ›ru HR (vpravo/nahoru)', + 'Rotated 180' => '180° (dolů/vpravo)', + 'Rotated 270 CW' => '90° po smÄ›ru HR (vlevo/dolů)', + 'Rotated 90 CW' => '90° ptoti smÄ›ru HR (vpravo/nahoru)', + }, + }, + 'RowsPerStrip' => 'PoÄet řádek v Äásti', + 'SPIFFVersion' => 'SPIFF verze', + 'SVGVersion' => 'SVG verze', + 'SampleFormat' => { + Description => 'Formát vzorku', + PrintConv => { + 'Complex int' => 'Komplexní Äíslo', + 'Float' => 'Desetinná Äárka', + 'Signed' => 'Záporné Äíslo', + 'Undefined' => 'Nedefinované', + 'Unsigned' => 'Kladné Äíslo', + }, + }, + 'SamplesPerPixel' => 'PoÄet složek', + 'Saturation' => { + Description => 'Saturace', + PrintConv => { + 'High' => 'Vysoká', + 'Low' => 'Nízká', + 'Normal' => 'Normální', + }, + }, + 'SceneCaptureType' => { + Description => 'Typ scény', + PrintConv => { + 'Landscape' => 'Krajina', + 'Night' => 'NoÄní foto', + 'Portrait' => 'Portrét', + }, + }, + 'SceneMode' => { + PrintConv => { + 'Aperture Priority' => 'Priorita clony', + 'Landscape' => 'Krajina', + 'Macro' => 'Makro', + 'Manual' => 'Manuální', + 'Normal' => 'Normální', + 'Portrait' => 'Portrét', + 'Shutter Priority' => 'Priorita Äasu', + 'Spot' => 'StÅ™edový bod', + 'Sunset' => 'Západ', + }, + }, + 'SceneModeUsed' => { + PrintConv => { + 'Aperture Priority' => 'Priorita clony', + 'Landscape' => 'Krajina', + 'Macro' => 'Makro', + 'Manual' => 'Manuální', + 'Portrait' => 'Portrét', + 'Shutter Priority' => 'Priorita Äasu', + }, + }, + 'SceneSelect' => { + PrintConv => { + 'Night' => 'NoÄní foto', + }, + }, + 'SceneType' => { + Description => 'Typ scény', + PrintConv => { + 'Directly photographed' => 'Přímo pořízený snímek', + }, + }, + 'SecurityClassification' => { + Description => 'BezpeÄnostní klasifikace', + PrintConv => { + 'Confidential' => 'DůvÄ›rný', + 'Restricted' => 'Vyhrazený', + 'Secret' => 'Tajný', + 'Top Secret' => 'Velmi tajný', + 'Unclassified' => 'NeurÄeno', + }, + }, + 'SelfTimerMode' => 'Samospoušť', + 'SensingMethod' => { + Description => 'Metoda měření', + PrintConv => { + 'Color sequential area' => 'Barevný sekvenÄní ploÅ¡ný sensor', + 'Color sequential linear' => 'Barevný sekvenÄné-lineární senzor', + 'Monochrome area' => 'Monochromatický senzor', + 'Monochrome linear' => 'Monochromatický lineární senzor', + 'Not defined' => 'Nedefinovaný', + 'One-chip color area' => 'JednoÄipový barevný senzor', + 'Three-chip color area' => 'TříÄipový barevný senzor', + 'Trilinear' => 'Třílineární senzor', + 'Two-chip color area' => 'DvouÄipový barevný senzor', + }, + }, + 'SequentialShot' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'SerialNumber' => 'ID fotoaparátu', + 'SetButtonCrossKeysFunc' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'ShadingCompensation' => 'Kompenzace stínování', + 'Sharpness' => { + Description => 'DoostÅ™ení', + PrintConv => { + 'Hard' => 'Silné', + 'Normal' => 'Normální', + 'Soft' => 'Lehké', + }, + }, + 'SharpnessFrequency' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'ShootingMode' => { + Description => 'Režim focení', + PrintConv => { + 'Aperture Priority' => 'Priorita clony', + 'Macro' => 'Makro', + 'Manual' => 'Manuální', + 'Normal' => 'Normální', + 'Portrait' => 'Portrét', + 'Shutter Priority' => 'Priorita Äasu', + 'Spot' => 'StÅ™edový bod', + }, + }, + 'ShutterMode' => { + PrintConv => { + 'Aperture Priority' => 'Priorita clony', + }, + }, + 'ShutterSpeed' => 'ExpoziÄní Äas', + 'ShutterSpeedValue' => 'ÄŒas závÄ›rky', + 'SlowShutter' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'Software' => 'Použitý software', + 'Source' => 'Zdroj', + 'SpatialFrequencyResponse' => 'Spatial frequency response', + 'SpecialEffectsOpticalFilter' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'SpectralSensitivity' => 'Spektrální citlivost', + 'State' => 'Stát', + 'StripByteCounts' => 'Bytů na komprimovanou Äást', + 'StripOffsets' => 'Pozice obrazových dat', + 'SubSecCreateDate' => 'Datum a Äas generování digitálních dat', + 'SubSecDateTimeOriginal' => 'Datum a Äas vzniku originálních dat', + 'SubSecModifyDate' => 'Datum a Äas zmÄ›ny souboru', + 'SubSecTime' => 'DateTime 1/100 sekundy', + 'SubSecTimeDigitized' => 'DateTimeDigitized 1/100 sekund', + 'SubSecTimeOriginal' => 'DateTimeOriginal 1/100 sekund', + 'SubfileType' => 'Nový typ podsekce', + 'Subject' => 'Popis', + 'SubjectArea' => 'Pozice hlavního objektu', + 'SubjectDistance' => 'Vzdálenost objektu', + 'SubjectDistanceRange' => { + Description => 'Rozsah vzdálenosti objektu', + PrintConv => { + 'Close' => 'Blízký', + 'Distant' => 'Vzdálený', + 'Macro' => 'Makro', + 'Unknown' => 'Neznámý', + }, + }, + 'SubjectLocation' => 'Pozice hlavního objektu', + 'SubjectProgram' => { + PrintConv => { + 'None' => 'Žádná', + 'Portrait' => 'Portrét', + }, + }, + 'Subsystem' => { + PrintConv => { + 'Unknown' => 'Neznámý', + }, + }, + 'SupplementalCategories' => 'Doplňkové kategorie', + 'T4Options' => 'Plné bity', + 'T6Options' => 'Volby T6', + 'TargetPrinter' => 'Cílová tiskárna', + 'Teleconverter' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'Thresholding' => 'Práh', + 'ThumbnailImage' => 'Náhled', + 'ThumbnailImageSize' => 'Velkost náhledu', + 'TileByteCounts' => 'PoÄet bytů prvku', + 'TileLength' => 'Délka prvku', + 'TileOffsets' => 'Offset prvku', + 'TileWidth' => 'Šířka prvku', + 'TimeScaleParamsQuality' => { + PrintConv => { + 'Low' => 'MénÄ›', + }, + }, + 'Title' => 'Název', + 'ToneCurve' => { + PrintConv => { + 'Manual' => 'Manuální', + }, + }, + 'ToningEffect' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'ToningEffectMonochrome' => { + PrintConv => { + 'None' => 'Žádná', + }, + }, + 'TransferFunction' => 'Transfer funkce', + 'Transformation' => { + PrintConv => { + 'Horizontal (normal)' => '0° (nahoru/vlevo)', + 'Mirror horizontal' => '0° (nahoru/vpravo)', + 'Mirror horizontal and rotate 270 CW' => '90° po smÄ›ru HR (vlevo/nahoru)', + 'Mirror horizontal and rotate 90 CW' => '90° ptoti smÄ›ru HR (vpravo/dolů)', + 'Mirror vertical' => '180° (dolů/vlevo)', + 'Rotate 180' => '180° (dolů/vpravo)', + 'Rotate 270 CW' => '90° po smÄ›ru HR (vlevo/dolů)', + 'Rotate 90 CW' => '90° ptoti smÄ›ru HR (vpravo/nahoru)', + }, + }, + 'TransmissionReference' => 'Reference pÅ™enosu', + 'Trapped' => { + PrintConv => { + 'Unknown' => 'Neznámý', + }, + }, + 'Urgency' => 'Naléhavost', + 'UserComment' => 'Komentář', + 'UserDef1PictureStyle' => { + PrintConv => { + 'Landscape' => 'Krajina', + 'Portrait' => 'Portrét', + }, + }, + 'UserDef2PictureStyle' => { + PrintConv => { + 'Landscape' => 'Krajina', + 'Portrait' => 'Portrét', + }, + }, + 'UserDef3PictureStyle' => { + PrintConv => { + 'Landscape' => 'Krajina', + 'Portrait' => 'Portrét', + }, + }, + 'VRDVersion' => 'VRD verze', + 'Version' => 'Verze', + 'VignetteControl' => { + PrintConv => { + 'Normal' => 'Normální', + }, + }, + 'WBAdjLighting' => { + PrintConv => { + 'Daylight (direct sunlight)' => 'Denní svÄ›tlo (0)', + 'Daylight (shade)' => 'Denní svÄ›tlo (1)', + 'Daylight (cloudy)' => 'Denní svÄ›tlo (2)', + 'Flash' => 'Blesk', + 'None' => 'Žádná', + }, + }, + 'WhiteBalance' => { + Description => 'Vyvážení bílé', + PrintConv => { + 'Auto' => 'Automatické vyvážení bílé', + 'Black & White' => 'ÄŒernobílé foto', + 'Cloudy' => 'Zataženo', + 'Cool White Fluorescent' => 'Chladná bílá fluorescentní', + 'Custom 1' => 'VLASTNÃ1', + 'Custom 2' => 'VLASTNÃ2', + 'Custom 3' => 'VLASTNÃ3', + 'Custom 4' => 'VLASTNÃ4', + 'Day White Fluorescent' => 'Denní zářivka', + 'Daylight' => 'Denní svÄ›tlo', + 'Daylight Fluorescent' => 'Denní svÄ›tlo', + 'Flash' => 'Blesk', + 'Fluorescent' => 'Žárovka', + 'Manual' => 'Manuální', + 'Shade' => 'Stíny', + 'Tungsten' => 'Zářivka', + 'Unknown' => 'Neznámý', + 'White Fluorescent' => 'Bílá zářivka', + }, + }, + 'WhiteBalanceAdj' => { + PrintConv => { + 'Cloudy' => 'Zataženo', + 'Daylight' => 'Denní svÄ›tlo', + 'Flash' => 'Blesk', + 'Fluorescent' => 'Žárovka', + 'Shade' => 'Stíny', + 'Tungsten' => 'Zářivka', + }, + }, + 'WhiteBalanceMode' => { + PrintConv => { + 'Unknown' => 'Neznámý', + }, + }, + 'WhiteBalanceSet' => { + PrintConv => { + 'Cloudy' => 'Zataženo', + 'Daylight' => 'Denní svÄ›tlo', + 'Daylight Fluorescent' => 'Denní svÄ›tlo', + 'Flash' => 'Blesk', + 'Manual' => 'Manuální', + 'Shade' => 'Stíny', + 'Tungsten' => 'Zářivka', + 'White Fluorescent' => 'Bílá zářivka', + }, + }, + 'WhitePoint' => 'ChromatiÄnost bílého bodu', + 'Writer-Editor' => 'Autor popisku', + 'XMP' => 'XMP metadata', + 'XPAuthor' => 'Autor', + 'XPComment' => 'Komentář', + 'XPKeywords' => 'KlíÄová slova', + 'XPSubject' => 'Popis', + 'XPTitle' => 'Název', + 'XPosition' => 'X-pozice', + 'XResolution' => 'RozliÅ¡ení obrázku na šířku', + 'YCbCrCoefficients' => 'Koeficienty transformaÄní YCbCr matrice', + 'YCbCrPositioning' => { + Description => 'Y a C pozice', + PrintConv => { + 'Centered' => 'Centrované', + 'Co-sited' => 'Po stranách', + }, + }, + 'YCbCrSubSampling' => 'Vzorkovací pomÄ›r Y k C', + 'YPosition' => 'Y-pozice', + 'YResolution' => 'RozliÅ¡ení obrázku na výšku', +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::cs.pm - ExifTool Czech language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke and Petr MichE<aacute>lek for providing this +translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/de.pm b/ExifTool/lib/Image/ExifTool/Lang/de.pm new file mode 100644 index 0000000..e3a1680 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/de.pm @@ -0,0 +1,8720 @@ +#------------------------------------------------------------------------------ +# File: de.pm +# +# Description: ExifTool German language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::de; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.36'; + +%Image::ExifTool::Lang::de::Translate = ( + 'AEAperture' => 'AE-Blende', + 'AEBAutoCancel' => { + Description => 'Automatisches Bracketingende', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AEBBracketValue' => 'AEB-Korrekturwert', + 'AEBSequence' => 'Bracketing-Sequenz', + 'AEBSequenceAutoCancel' => { + Description => 'WB-Sequenz/autom. Abschaltung', + PrintConv => { + '-,0,+/Disabled' => '-,0,+/Aus', + '-,0,+/Enabled' => '-,0,+/Ein', + '0,-,+/Disabled' => '0,-,+/Aus', + '0,-,+/Enabled' => '0,-,+/Ein', + }, + }, + 'AEBShotCount' => 'Anzahl Belichtungsreihenaufnahmen', + 'AEBXv' => 'AEB-Belichtungskorrektur', + 'AEExposureTime' => 'AE-Belichtungszeit', + 'AEExtra' => 'AE-Extra?', + 'AEFlags' => { + PrintConv => { + 'AE lock' => 'AE Speicherung', + 'Aperture wide open' => 'Offene Blende', + 'Flash recommended?' => 'Blitz erforderlich', + }, + }, + 'AEInfo' => 'Automatikbelichtungs-Informationen', + 'AELock' => { + Description => 'Belichtungsspeicher', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AELockButton' => { + Description => 'AE-L/AF-L-Taste', + PrintConv => { + 'AE Lock (hold)' => 'Nur Belichtung (halten)', + 'AE Lock Only' => 'Nur Belichtung', + 'AE-L/AF Area' => 'Belichtung & Messfeld', + 'AE-L/AF-L/AF Area' => 'Bel. & Fokus & Messfeld', + 'AE/AF Lock' => 'Belichtung & Fokus', + 'AF Lock Only' => 'Nur Fokus', + 'AF-L/AF Area' => 'Fokus & Messfeld', + 'AF-ON' => 'AF-Aktivierung', + 'AF-ON/AF Area' => 'AF-Aktiv. & Messfeld', + 'FV Lock' => 'FV-Messwertspeicher', + 'Focus Area Selection' => 'AF-Messfeldauswahl', + }, + }, + 'AEMaxAperture' => 'Größte AE-Blende', + 'AEMaxAperture2' => 'Größte AE-Blende (2)', + 'AEMeteringMode' => { + Description => 'AE Belichtungs-Messmethode', + PrintConv => { + 'Center-weighted average' => 'Mittenbetont', + 'Multi-segment' => 'Multi-Segment', + }, + }, + 'AEMeteringSegments' => 'AE-Messfelder', + 'AEMicroadjustment' => { + Description => 'AE Feinabstimmung', + PrintConv => { + 'Disable' => 'Deaktiviert', + 'Enable' => 'Aktiviert', + }, + }, + 'AEMinAperture' => 'Kleinste AE-Blende', + 'AEMinExposureTime' => 'Kürzeste AE Belichtungszeit', + 'AEProgramMode' => { + Description => 'AE-Programm-Modus', + PrintConv => { + 'Av, B or X' => 'Av, B oder X', + 'Candlelight' => 'Kerzenlicht', + 'DOF Program' => 'Schärfentiefe-Priorität', + 'DOF Program (P-Shift)' => 'Schärfentiefe-Priorität (P Shift)', + 'Hi-speed Program' => 'HS-Priorität', + 'Hi-speed Program (P-Shift)' => 'HS-Priorität (P Shift)', + 'Kids' => 'Kinder', + 'Landscape' => 'Landschaft', + 'M, P or TAv' => 'M, P oder TAv', + 'MTF Program' => 'MTF-Priorität', + 'MTF Program (P-Shift)' => 'MTF-Priorität (P Shift)', + 'Macro' => 'Makro', + 'Night Scene' => 'Nachtszene', + 'Night Scene Portrait' => 'Nacht-Porträt', + 'No Flash' => 'Kein Blitz', + 'Pet' => 'Haustiere', + 'Portrait' => 'Porträt', + 'Sunset' => 'Sonnenuntergang', + 'Surf & Snow' => 'Surf & Schnee', + 'Sv or Green Mode' => 'Sv oder "Grünes" AE-Programm', + }, + }, + 'AESetting' => { + Description => 'AE-Einstellung', + PrintConv => { + 'AE Lock' => 'AE-Speicherung', + 'AE Lock + Exposure Comp.' => 'AE-Speicherung + Belichtungskorrektur', + 'Exposure Compensation' => 'Belichtungskorrektur', + 'No AE' => 'Kein AE', + }, + }, + 'AEXv' => 'AE-Belichtungskorrektur', + 'AE_ISO' => 'AE-ISO-Empfindlichkeit', + 'AF-CPrioritySelection' => { + Description => 'Priorität bei AF-C', + PrintConv => { + 'Focus' => 'Schärfepriorität', + 'Release' => 'Auslösepriorität', + 'Release + Focus' => 'Auslösepriorität & AF', + }, + }, + 'AF-OnForMB-D10' => { + Description => 'AF-ON-Taste (MB-D10)', + PrintConv => { + 'AE Lock (hold)' => 'Belichtung speichern ein/aus', + 'AE Lock (reset on release)' => 'Bel. speichern ein/aus (Reset)', + 'AE Lock Only' => 'Belichtung speichern', + 'AE/AF Lock' => 'Belichtung & Fokus speichern', + 'AF Lock Only' => 'Fokus speichern', + 'AF-On' => 'Autofokus aktiviert', + 'Same as FUNC Button' => 'Wie Funktionstaste', + }, + }, + 'AF-SPrioritySelection' => { + Description => 'Priorität bei AF-S (Einzel-AF)', + PrintConv => { + 'Focus' => 'Schärfepriorität', + 'Release' => 'Auslösepriorität', + }, + }, + 'AFActivation' => { + Description => 'AF-Aktivierung', + PrintConv => { + 'AF-On Only' => 'Nur AF-ON-Taste', + 'Shutter/AF-On' => 'AF-On-Taste & Auslöser', + }, + }, + 'AFAdjustment' => 'AF-Korrektur', + 'AFAndMeteringButtons' => { + Description => 'AF And Mess-Tasten', + PrintConv => { + 'AE lock' => 'AE Speicherung', + 'AF stop' => 'AE Stopp', + 'Metering + AF start' => 'Messung + AF Start', + 'Metering start' => 'Messung Start', + 'No function' => 'Keine Funktion', + }, + }, + 'AFAperture' => 'AF-Blende', + 'AFArea' => 'AF Bereich', + 'AFAreaHeight' => 'AF-Bereichshöhe', + 'AFAreaHeights' => 'AF-Bereichshöhe', + 'AFAreaIllumination' => { + Description => 'Messfeld-LED', + PrintConv => { + 'Auto' => 'Automatisch', + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AFAreaMode' => { + Description => 'Messfeldsteuerung', + PrintConv => { + '1-area' => '1 Bereich', + '1-area (high speed)' => '1 Bereich (kurze Verschlußzeit)', + '23-area' => '23 Bereiche', + '3-area (center)?' => '3 Bereiche (mitte)', + '3-area (high speed)' => '3 Bereiche (kurze Verschlußzeit)', + '3-area (left)?' => '3 Bereiche (links)', + '3-area (right)?' => '3 Bereiche (rechts)', + '5-area' => '5 Bereiche', + '9-area' => '9 Bereiche', + 'AF Point Expansion' => 'AF Punkt-Erweiterung', + 'Auto (41 points)' => 'Automatisch (41 Punkte)', + 'Auto-area' => 'Autom. Messfeldgr.', + 'Center' => 'Mitte', + 'Contrast-detect' => 'Kontrasterkennung', + 'Contrast-detect (face priority)' => 'Kontrasterkennung (Gesichtserkennung)', + 'Contrast-detect (normal area)' => 'Kontrasterkennung (Standardbereich)', + 'Contrast-detect (subject tracking)' => 'Kontrasterkennung (Objektverfolgung)', + 'Contrast-detect (wide area)' => 'Kontrasterkennung (großer Bereich)', + 'Default' => 'Standard', + 'Dynamic Area' => 'Dynamisch', + 'Dynamic Area (21 points)' => 'Dynamischer Bereich (21 Punkte)', + 'Dynamic Area (3D-tracking)' => 'Dynamischer Bereich (3D-Nachführung)', + 'Dynamic Area (51 points)' => 'Dynamischer Bereich (51 Punkte)', + 'Dynamic Area (51 points, 3D-tracking)' => 'Dynamischer Bereich (51 Punkte, 3D-Nachführung)', + 'Dynamic Area (9 points)' => 'Dynamischer Bereich (9 Punkte)', + 'Dynamic Area (closest subject)' => 'Dynamic Messfeldgruppensteuerung (priorität der kürzesten Aufnahmedistanz)', + 'Dynamic Area (wide)' => 'Dynamische Messfeldsteuerung (groß)', + 'Dynamic Area (wide, 3D-tracking)' => 'Dynamischer Bereich (groß, 3D-Nachführung)', + 'Face + Tracking' => 'Gesichtserkennung + Nachführung', + 'Face Detect' => 'Gesichtserkennung', + 'Face Detect AF' => 'Gesichtserkennung AF', + 'Face Priority (41 points)' => 'Gesichtserkennung (41 Punkte)', + 'Face Tracking' => 'Gesichtserkennung', + 'Flexible' => 'Angepasst', + 'Flexible Spot' => 'Gesetzter Punkt', + 'Flexizone Multi' => 'Mehrpunkt-Bereich', + 'Flexizone Single' => 'Einpunkt-Bereich', + 'Group Dynamic' => 'Dynamische Messfeldgruppensteuerung', + 'Local' => 'Lokal', + 'Manual' => 'Manuell', + 'Multi' => 'Mehrpunkt', + 'Multi-point AF or AI AF' => 'Mehrpunkt AF oder AI AF', + 'Normal?' => 'Normal', + 'Off (Manual Focus)' => 'Aus (Manueller Fokus)', + 'Selective (for Miniature effect)' => 'Selektiv (für Vorschau-Effekt)', + 'Single (135 points)' => 'Einpunkt (135 Punkte)', + 'Single Area' => 'Einzelfeld', + 'Single Area (wide)' => 'Einzelfeldmessung (groß)', + 'Single-point AF' => 'Einpunkt AF', + 'Spot Focusing' => 'Spotfokussierung', + 'Spot Focusing 2' => 'Spotfokussierung 2', + 'Spot Mode Off' => 'Spot-Modus Aus', + 'Spot Mode On' => 'Spot-Modus Ein', + 'Subject Tracking (41 points)' => 'Objektverfolgung (41 Punkte)', + 'Touch' => 'Berührungspunkt', + 'Tracking' => 'Nachführung', + 'Wide' => 'Weit', + 'Zone' => 'Bereich', + 'Zone AF' => 'Zonen AF', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'AFAreaModeSetting' => { + Description => 'Messfeldsteuerung', + PrintConv => { + '3D-tracking (11 points)' => '3D Nachführung (11 Punkte)', + 'Auto-area' => 'Automatischer Bereich', + 'Center' => 'Mitte', + 'Closest Subject' => 'Nächstes Objekt', + 'Dynamic Area' => 'Dynamisch', + 'Flexible Spot' => 'Gesetzter Punkt', + 'Local' => 'Lokal', + 'Multi' => 'Mehrpunkt', + 'Single Area' => 'Einzelfeld', + 'Wide' => 'Weit', + 'Zone' => 'Bereich', + }, + }, + 'AFAreaWidth' => 'AF-Bereichsbreite', + 'AFAreaWidths' => 'AF-Bereichsbreite', + 'AFAreaXPosition' => 'AF-Bereich X Position', + 'AFAreaXPosition1' => 'AF-Bereich X Position 1', + 'AFAreaXPositions' => 'AF Bereich X Positionen', + 'AFAreaYPosition' => 'AF-Bereich Y Position', + 'AFAreaYPosition1' => 'AF-Bereich Y Position 1', + 'AFAreaYPositions' => 'AF Bereich Y Positionen', + 'AFAreas' => 'AF-Bereiche', + 'AFAssist' => { + Description => 'AF-Hilfslicht', + PrintConv => { + 'Does not emit/Fires' => 'Kein Messlicht/Zündung', + 'Emits/Does not fire' => 'Messlicht/keine Zündung', + 'Emits/Fires' => 'Messlicht/Zündung', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Only ext. flash emits/Fires' => 'Nur ext. Messl./Zündung', + }, + }, + 'AFAssistBeam' => { + Description => 'AF-Hilfslicht Aussendung', + PrintConv => { + 'Does not emit' => 'Deaktiv', + 'Emits' => 'Aktiv', + 'Only ext. flash emits' => 'Nur bei ext. Blitz aktiv', + }, + }, + 'AFAssistLamp' => { + Description => 'AF Hilfslicht', + PrintConv => { + 'Disabled and Not Required' => 'Nicht eingestellt, war nicht nötig', + 'Disabled but Required' => 'Nicht eingestellt, war aber nötig', + 'Enabled but Not Used' => 'Eingestellt, aber nicht verwendet', + 'Fired' => 'Blitz wurde ausgelöst', + }, + }, + 'AFDefocus' => 'AF-Defocus', + 'AFDuringLiveView' => { + Description => 'AF bei Live View-Aufnahmen', + PrintConv => { + 'Disable' => 'Inaktiv', + 'Enable' => 'Aktiv', + 'Live mode' => 'LiveModus', + 'Quick mode' => 'QuickModus', + }, + }, + 'AFFineTune' => { + Description => 'AF-Feinabstimmung', + PrintConv => { + 'Off' => 'Aus', + 'On (1)' => 'Ein (1)', + 'On (2)' => 'Ein (2)', + }, + }, + 'AFFineTuneAdj' => 'AF-Feinabstimmung', + 'AFIlluminator' => { + PrintConv => { + 'Off' => 'Aus', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'AFImageHeight' => 'AF-Bildhöhe', + 'AFImageWidth' => 'AF-Bildbreite', + 'AFInFocus' => 'AF in Fokus', + 'AFInfo' => 'AF-Modus', + 'AFInfo2' => 'AF-Informationen', + 'AFInfo2Version' => 'AF-Info-Version', + 'AFIntegrationTime' => 'AF-Messzeit', + 'AFMicroAdj' => 'AF Feinabstimmung', + 'AFMicroAdjMode' => { + Description => 'AF Feinabstimmung Modus', + PrintConv => { + 'Adjust all by the same amount' => 'Korrektur immer um gleichen Wert', + 'Adjust by lens' => 'Korrektur objektivabhängig', + 'Disable' => 'Deaktiviert', + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AFMicroAdjRegisteredLenses' => 'AF Feinabstimmung bekannte Objektive', + 'AFMicroAdjValue' => 'AF Feinabstimmung Wert', + 'AFMicroadjustment' => { + Description => 'AF Feinabstimmung', + PrintConv => { + 'Adjust all by same amount' => 'Alle auf gleichen Wert', + 'Adjust by lens' => 'Abstimmung pro Objektiv', + 'Disable' => 'Deaktivieren', + }, + }, + 'AFMode' => { + Description => 'AF-Modus', + PrintConv => { + 'Face Detection' => 'Gesichtserkennung', + 'Off' => 'Aus', + 'Tracking' => 'Nachführung', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'AFOnAELockButtonSwitch' => { + Description => 'AF-ON/AELocktaste- Schalter', + PrintConv => { + 'Disable' => 'Deaktiviert', + 'Enable' => 'Aktiviert', + }, + }, + 'AFPoint' => { + Description => 'Gewählter AF-Punkt', + PrintConv => { + '(none)' => '(keiner)', + 'Auto AF point selection' => 'Automatische Wahl des AF-Punktes', + 'Bottom' => 'Unten', + 'Bottom (horizontal)' => 'Unten (horizontal)', + 'Bottom (vertical)' => 'Unten (vertikal)', + 'Bottom Center' => 'Unten Mitte', + 'Bottom Left' => 'Unten Links', + 'Bottom Right' => 'Unten Rechts', + 'Bottom-center (horizontal)' => 'Unten Mitte (horizontal)', + 'Bottom-center (vertical)' => 'Unten Mitte (vertikal)', + 'Bottom-left' => 'Unten Links', + 'Bottom-left (horizontal)' => 'Unten Links (horizontal)', + 'Bottom-left (vertical)' => 'Unten Links (vertikal)', + 'Bottom-right' => 'Unten Rechts', + 'Bottom-right (horizontal)' => 'Unten Rechts (horizontal)', + 'Bottom-right (vertical)' => 'Unten Rechts (vertikal)', + 'Center' => 'Mitte', + 'Center (horizontal)' => 'Mitte (horizontal)', + 'Center (vertical)' => 'Mitte (vertikal)', + 'Center Left' => 'Mitte Links', + 'Center Right' => 'Mitte Rechts', + 'Face Detect' => 'Gesichtserkennung', + 'Far Left' => 'Weit links', + 'Far Left (horizontal)' => 'Weit links (horizontal)', + 'Far Left (vertical)' => 'Weit links (vertikal)', + 'Far Left/Right of Center' => 'Weit Links/Rechts der Mitte', + 'Far Left/Right of Center/Bottom' => 'Weit Links/Rechts der Mitte/Unten', + 'Far Right' => 'Weit rechts', + 'Far Right (horizontal)' => 'Weit rechts (horizontal)', + 'Far Right (vertical)' => 'Weit rechts (vertikal)', + 'Left' => 'Links', + 'Left (horizontal)' => 'Links (horizontal)', + 'Left (or n/a)' => 'Links (oder Nicht gesetzt)', + 'Left (vertical)' => 'Links (vertikal)', + 'Lower Far Left' => 'Unten ganz links', + 'Lower Far Right' => 'Unten ganz rechts', + 'Lower-left' => 'Links unten', + 'Lower-left (horizontal)' => 'Links unten (horizontal)', + 'Lower-left (vertical)' => 'Links unten (vertikal)', + 'Lower-middle' => 'Untere Mitte', + 'Lower-right' => 'Rechts unten', + 'Lower-right (horizontal)' => 'Rechts unten (horizontal)', + 'Lower-right (vertical)' => 'Unten rechts (vertikal)', + 'Manual AF point selection' => 'Manuell gewählter AF-Punkt', + 'Mid-left' => 'Links mitte', + 'Mid-left (horizontal)' => 'Links mitte (horizontal)', + 'Mid-left (vertical)' => 'Links mitte (vertikal)', + 'Mid-right' => 'Rechts mitte', + 'Mid-right (horizontal)' => 'Rechts mitte (horizontal)', + 'Mid-right (vertical)' => 'Rechts mitte (vertikal)', + 'Near Left' => 'Nahe Links', + 'Near Left/Right of Center' => 'Nahe Links/Rechts der Mitte', + 'Near Right' => 'Nahe Rechts', + 'Near Upper/Left' => 'Nahe Links oben', + 'None' => 'Keiner', + 'None (MF)' => 'Keiner (MF)', + 'Right' => 'Rechts', + 'Right (horizontal)' => 'Rechts (horizontal)', + 'Right (vertical)' => 'Rechts (vertikal)', + 'Top' => 'Oben', + 'Top (horizontal)' => 'Oben (horizontal)', + 'Top (vertical)' => 'Oben (vertikal)', + 'Top Center' => 'Oben Mitte', + 'Top Left' => 'Oben Links', + 'Top Near-left' => 'Oben nahe-Links', + 'Top Near-right' => 'Nahe Rechts-oben', + 'Top Right' => 'Oben Rechts', + 'Top-center (horizontal)' => 'Oben Mitte (horizontal)', + 'Top-center (vertical)' => 'Oben Mitte (vertikal)', + 'Top-left' => 'Oben Links', + 'Top-left (horizontal)' => 'Oben Links (horizontal)', + 'Top-left (vertical)' => 'Oben Links (vertikal)', + 'Top-right' => 'Oben Rechts', + 'Top-right (horizontal)' => 'Oben Rechts (horizontal)', + 'Top-right (vertical)' => 'Oben Rechts (vertikal)', + 'Upper Far Left' => 'Oben ganz links', + 'Upper Far Right' => 'Oben ganz rechts', + 'Upper Left' => 'Links oben', + 'Upper Right' => 'Rechts oben', + 'Upper-left' => 'Links oben', + 'Upper-left (horizontal)' => 'Links oben (horizontal)', + 'Upper-left (vertical)' => 'Links oben (vertikal)', + 'Upper-middle' => 'Obere Mitte', + 'Upper-right' => 'Rechts oben', + 'Upper-right (horizontal)' => 'Rechts oben (horizontal)', + 'Upper-right (vertical)' => 'Oben rechts (vertikal)', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'AFPointActivationArea' => { + Description => 'AF-Messfeld-Aktivierungsbereich', + PrintConv => { + 'Expanded' => 'Erweitert', + }, + }, + 'AFPointAreaExpansion' => { + Description => 'AF-Messbereich Ausweitung', + PrintConv => { + 'All 45 points area' => 'Alle 45 Punkte', + 'Disable' => 'Aus', + 'Enable' => 'Ein', + 'Left/right AF points' => 'Möglich (linkes/rechtes zusätzliches AF-Messfeld)', + 'Surrounding AF points' => 'Möglich (entsprechendes zusätzliches AF-Messfeld)', + }, + }, + 'AFPointAtShutterRelease' => { + Description => 'AF-Punkt beim Auslösen', + PrintConv => { + '(out of focus)' => '(ausserhalb Fokus)', + 'Bottom (horizontal)' => 'Unten (horizontal)', + 'Bottom (vertical)' => 'Unten (vertikal)', + 'Center (horizontal)' => 'Mitte (horizontal)', + 'Center (vertical)' => 'Mitte (vertikal)', + 'Far Left' => 'Weit links', + 'Far Left (horizontal)' => 'Weit links (horizontal)', + 'Far Left (vertical)' => 'Weit links (vertikal)', + 'Far Right' => 'Weit rechts', + 'Far Right (horizontal)' => 'Weit rechts (horizontal)', + 'Far Right (vertical)' => 'Weit rechts (vertikal)', + 'Left' => 'Links', + 'Left (horizontal)' => 'Links (horizontal)', + 'Left (vertical)' => 'Links (vertikal)', + 'Lower Far Left' => 'Unten ganz links', + 'Lower Far Right' => 'Unten ganz rechts', + 'Lower-left' => 'Links unten', + 'Lower-left (horizontal)' => 'Links unten (horizontal)', + 'Lower-left (vertical)' => 'Links unten (vertikal)', + 'Lower-middle' => 'Unten mitte', + 'Lower-right' => 'Rechts unten', + 'Lower-right (horizontal)' => 'Rechts unten (horizontal)', + 'Lower-right (vertical)' => 'Rechts unten (vertikal)', + 'Near Left' => 'Nahe links', + 'Near Right' => 'Nahe rechts', + 'Right' => 'Rechts', + 'Right (horizontal)' => 'Rechts (horizontal)', + 'Right (vertical)' => 'Rechts (vertikal)', + 'Top (horizontal)' => 'Oben (horizontal)', + 'Top (vertical)' => 'Oben (vertikal)', + 'Upper Far Left' => 'Oben ganz links', + 'Upper Far Right' => 'Oben ganz rechts', + 'Upper-left' => 'Links oben', + 'Upper-left (horizontal)' => 'Links oben (horizontal)', + 'Upper-left (vertical)' => 'Links oben (vertikal)', + 'Upper-middle' => 'Oben mitte', + 'Upper-right' => 'Rechts oben', + 'Upper-right (horizontal)' => 'Rechts oben (horizontal)', + 'Upper-right (vertical)' => 'Rechts oben (vertikal)', + }, + }, + 'AFPointAutoSelection' => { + Description => 'Automatische AF-Feldwahl', + PrintConv => { + 'Control-direct:disable/Main:disable' => 'Schnelleinstellrad-Direkt:nicht möglich/Haupt-Wahlrad:nein', + 'Control-direct:disable/Main:enable' => 'Schnelleinstellrad-Direkt:nicht möglich/Haupt-Wahlrad:möglich', + 'Control-direct:enable/Main:enable' => 'Schnelleinstellrad-Direkt:möglich/Haupt-Wahlrad:möglich', + }, + }, + 'AFPointBrightness' => { + Description => 'AF-Feld Helligkeit', + PrintConv => { + 'Brighter' => 'Heller', + 'Extra High' => 'Extra Hoch', + 'High' => 'Hoch', + 'Low' => 'Niedrig', + }, + }, + 'AFPointDisplayDuringFocus' => { + Description => 'AF-Feld Anzeige während Fokus', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + 'On (when focus achieved)' => 'Ein (nach Scharfeinstellung)', + }, + }, + 'AFPointIllumination' => { + Description => 'Messfeld-LED', + PrintConv => { + 'Auto' => 'Automatisch', + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AFPointInFocus' => { + Description => 'AF-Punkt im Fokus', + PrintConv => { + '(none)' => '(keiner)', + 'Bottom (horizontal)' => 'Unten (horizontal)', + 'Bottom (vertical)' => 'Unten (vertikal)', + 'Center (horizontal)' => 'Mitte (horizontal)', + 'Center (vertical)' => 'Mitte (vertikal)', + 'Far Left' => 'Weit links', + 'Far Left (horizontal)' => 'Weit links(horizontal)', + 'Far Left (vertical)' => 'Weit links (vertikal)', + 'Far Right' => 'Weit rechts', + 'Far Right (horizontal)' => 'Weit rechts (horizontal)', + 'Far Right (vertical)' => 'Weit rechts (vertikal)', + 'Left' => 'Links', + 'Left (horizontal)' => 'Links (horizontal)', + 'Left (vertical)' => 'Links (vertikal)', + 'Lower Far Left' => 'Unten ganz Links', + 'Lower Far Right' => 'Unten ganz rechts', + 'Lower-left' => 'Links unten', + 'Lower-left (horizontal)' => 'Links unten (horizontal)', + 'Lower-left (vertical)' => 'Links unten (vertikal)', + 'Lower-middle' => 'Unten mitte', + 'Lower-right' => 'Rechts unten', + 'Lower-right (horizontal)' => 'Rechts unten (horizontal)', + 'Lower-right (vertical)' => 'Rechts unten (vertikal)', + 'Near Left' => 'Nahe links', + 'Near Right' => 'Nahe rechts', + 'Right' => 'Rechts', + 'Right (horizontal)' => 'Rechts (horizontal)', + 'Right (vertical)' => 'Rechts (vertikal)', + 'Top (horizontal)' => 'Oben (horizontal)', + 'Top (vertical)' => 'Oben (vertikal)', + 'Upper Far Left' => 'Oben ganz links', + 'Upper Far Right' => 'Oben ganz rechts', + 'Upper-left' => 'Links oben', + 'Upper-left (horizontal)' => 'Links oben (horizontal)', + 'Upper-left (vertical)' => 'Links oben (vertikal)', + 'Upper-middle' => 'Oben mitte', + 'Upper-right' => 'Rechts oben', + 'Upper-right (horizontal)' => 'Rechts oben (horizontal)', + 'Upper-right (vertical)' => 'Rechts oben (vertikal)', + }, + }, + 'AFPointMode' => { + Description => 'AF-Punkt-Modus', + PrintConv => { + 'Auto' => 'Automatisch', + }, + }, + 'AFPointPosition' => 'AF-Punkt Position', + 'AFPointRegistration' => { + Description => 'AF-Feld Speicherung', + PrintConv => { + 'Automatic' => 'Automatisch', + 'Bottom' => 'Unten', + 'Center' => 'Mitte', + 'Extreme Left' => 'Ganz links', + 'Extreme Right' => 'Ganz rechts', + 'Left' => 'Links', + 'Right' => 'Rechts', + 'Top' => 'Oben', + }, + }, + 'AFPointSelected' => { + Description => 'AF gewählter Punkt', + PrintConv => { + 'Auto' => 'Automatisch', + 'Automatic Tracking AF' => 'Nachführ AF', + 'Bottom' => 'Unten', + 'Bottom-left' => 'Unten-links', + 'Bottom-right' => 'Unten-rechts', + 'Center' => 'Mitte', + 'Face Detect AF' => 'Gesichtserkennungs-AF', + 'Far Left' => 'Weit links', + 'Far Right' => 'Weit rechts', + 'Fixed Center' => 'Auf Mitte fixiert', + 'Left' => 'Links', + 'Lower Far Left' => 'Unten weit Links', + 'Lower Far Right' => 'Unten weit Rechts', + 'Lower-left' => 'Links unten', + 'Lower-middle' => 'Untere Mitte', + 'Lower-right' => 'Rechts unten', + 'Mid-left' => 'Links mitte', + 'Mid-right' => 'Rechts mitte', + 'Near Left' => 'Nahe Links', + 'Near Right' => 'Nahe Rechts', + 'Right' => 'Rechts', + 'Top' => 'Oben', + 'Top-left' => 'Oben-links', + 'Top-right' => 'Oben-rechts', + 'Upper Far Left' => 'Oben weit Links', + 'Upper Far Right' => 'Oben weit Rechts', + 'Upper-left' => 'Links oben', + 'Upper-middle' => 'Obere Mitte', + 'Upper-right' => 'Rechts oben', + }, + }, + 'AFPointSelected2' => { + Description => 'AF gewählter Punkt 2', + PrintConv => { + 'Auto' => 'Automatisch', + 'Bottom' => 'Unten', + 'Center' => 'Mitte', + 'Left' => 'Links', + 'Lower-left' => 'Links unten', + 'Lower-right' => 'Rechts unten', + 'Mid-left' => 'Links mitte', + 'Mid-right' => 'Rechts mitte', + 'Right' => 'Rechts', + 'Top' => 'Oben', + 'Upper-left' => 'Links oben', + 'Upper-right' => 'Rechts oben', + }, + }, + 'AFPointSelection' => { + Description => 'AF-Messfeldauswahl', + PrintConv => { + '11 Points' => '11 Messfelder', + '51 Points' => '51 Messfelder (3D-Tracking)', + }, + }, + 'AFPointSelectionMethod' => { + Description => 'Wahlmethode für AF-Messfeld', + PrintConv => { + 'Multi-controller direct' => 'Multicontroller', + 'Quick Control Dial direct' => 'Schnelleinstellrad', + }, + }, + 'AFPointSet' => { + Description => 'AF-Punkt Einstellung', + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'AFPointSpotMetering' => 'Anzahl AF-Messff./Spotmsg.', + 'AFPoints' => 'AF-Punkte', + 'AFPointsInFocus' => { + Description => 'AF-Punkte im Fokus', + PrintConv => { + '(none)' => '(Keine)', + 'All' => 'Alle', + 'All 11 Points' => 'Alle 11 Punkte', + 'Bottom' => 'Unten', + 'Bottom, Center' => 'Unten, Mitte', + 'Bottom-center' => 'Unten-mitte', + 'Bottom-left' => 'Unten-links', + 'Bottom-right' => 'Unten-rechts', + 'Center' => 'Mitte', + 'Center (horizontal)' => 'Mitte (horizontal)', + 'Center (vertical)' => 'Mitte (vertikal)', + 'Center+Right' => 'Mitte Rechts', + 'Far Left' => 'Weit links', + 'Far Right' => 'Weit rechts', + 'Fixed Center or Multiple' => 'Auf Mitte fixiert oder mehrere', + 'Left' => 'Links', + 'Left+Center' => 'Links Mitte', + 'Left+Right' => 'Links Rechts', + 'Lower-left' => 'Links unten', + 'Lower-left, Bottom' => 'Links unten, Unten', + 'Lower-left, Mid-left' => 'Links unten, Links mitte', + 'Lower-right' => 'Rechts unten', + 'Lower-right, Bottom' => 'Rechts unten, Unten', + 'Lower-right, Mid-right' => 'Rechts unten, Rechts mitte', + 'Mid-left' => 'Links mitte', + 'Mid-left, Center' => 'Links mitte, Mitte', + 'Mid-right' => 'Rechts mitte', + 'Mid-right, Center' => 'Rechts mitte, Mitte', + 'None' => 'Keiner', + 'None (MF)' => 'Keiner (MF)', + 'Right' => 'Rechts', + 'Top' => 'Oben', + 'Top, Center' => 'Oben, Mitte', + 'Top-center' => 'Oben-Mitte', + 'Top-left' => 'Oben-links', + 'Top-right' => 'Oben-rechts', + 'Upper-left' => 'Links oben', + 'Upper-left, Mid-left' => 'Links oben, Links mitte', + 'Upper-left, Top' => 'Links oben, Oben', + 'Upper-right' => 'Rechts oben', + 'Upper-right, Mid-right' => 'Rechts oben, Rechts mitte', + 'Upper-right, Top' => 'Rechts oben, Oben', + }, + }, + 'AFPointsInFocus1D' => 'AF-Punkte im Fokus 1D', + 'AFPointsInFocus5D' => { + Description => 'AF-Punkte im Fokus', + PrintConv => { + 'Bottom' => 'Unten', + 'Center' => 'Mitte', + 'Left' => 'Links', + 'Lower-left' => 'Unten links', + 'Lower-right' => 'Rechts unten', + 'Right' => 'Rechts', + 'Top' => 'Oben', + 'Upper-left' => 'Links oben', + 'Upper-right' => 'Rechts oben', + }, + }, + 'AFPointsSelected' => 'AF gewählte Punkte', + 'AFPointsUnknown1' => { + Description => 'AF-Punkte Unbekannt 1', + PrintConv => { + 'All' => 'Alle', + 'Bottom' => 'Unten', + 'Center' => 'Mitte', + 'Central 9 points' => 'Alle mittleren 9 Punkte', + 'Left' => 'Links', + 'Lower-left' => 'Links unten', + 'Lower-right' => 'Rechts unten', + 'Mid-left' => 'Links mitte', + 'Mid-right' => 'Rechts mitte', + 'Right' => 'Rechts', + 'Top' => 'Oben', + 'Upper-left' => 'Links oben', + 'Upper-right' => 'Rechts oben', + }, + }, + 'AFPointsUnknown2' => { + Description => 'AF-Punkte Unbekannt 2?', + PrintConv => { + 'Auto' => 'Automatisch', + 'Bottom' => 'Unten', + 'Center' => 'Mitte', + 'Left' => 'Links', + 'Lower-left' => 'Links unten', + 'Lower-right' => 'Rechts unten', + 'Mid-left' => 'Links mitte', + 'Mid-right' => 'Rechts mitte', + 'Right' => 'Rechts', + 'Top' => 'Oben', + 'Upper-left' => 'Links oben', + 'Upper-right' => 'Rechts oben', + }, + }, + 'AFPointsUsed' => { + Description => 'Verwendete AF-Punkte', + PrintConv => { + 'All 11 Points' => 'Alle 11 Punkte', + 'Bottom' => 'Unten', + 'Center' => 'Mitte', + 'Far Left' => 'Weit links', + 'Far Right' => 'Weit rechts', + 'Lower-left' => 'Links unten', + 'Lower-right' => 'Rechts unten', + 'Mid-left' => 'Links mitte', + 'Mid-right' => 'Rechts mitte', + 'Top' => 'Oben', + 'Upper-left' => 'Links oben', + 'Upper-right' => 'Rechts oben', + }, + }, + 'AFPredictor' => 'AF-Prädiktor', + 'AFResponse' => 'Verwendeter AF', + 'AFSearch' => { + Description => 'AF Fokussierung', + PrintConv => { + 'Not Ready' => 'Nicht bereit', + 'Ready' => 'Bereit', + }, + }, + 'AFSensorActive' => { + Description => 'Aktiver AF Sensor', + PrintConv => { + 'Bottom' => 'Unten', + 'Bottom-left' => 'Unten Links', + 'Bottom-right' => 'Unten Rechts', + 'Center Vertical' => 'Mitte vertikal', + 'Middle Horizontal' => 'Mitte horizontal', + 'Top' => 'Oben', + 'Top-left' => 'Oben Links', + 'Top-right' => 'Rechts Oben', + }, + }, + 'AFStatusActiveSensor' => { + Description => 'Status aktiver AF Sensor', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusBottom' => { + Description => 'AF Status Unten', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusBottom-left' => { + Description => 'AF Status Unten Links', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusBottom-right' => { + Description => 'AF Status Unten Rechts', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusCenterHorizontal' => { + Description => 'AF Status Mitte horizontal', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusCenterVertical' => { + Description => 'AF Status Mitte vertikal', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusLeft' => { + Description => 'AF Status Links', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusMiddleHorizontal' => { + Description => 'AF Status Mitte horizontal', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusRight' => { + Description => 'AF Status Rechts', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusTop' => { + Description => 'AF Status Oben', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusTop-left' => { + Description => 'AF Status Oben Links', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFStatusTop-right' => { + Description => 'AF Status Oben Rechts', + PrintConv => { + 'In Focus' => 'Im Fokus', + 'Out of Focus' => 'Nicht im Fokus', + }, + }, + 'AFType' => { + Description => 'AF-Typ', + PrintConv => { + '15-point' => '15 Punkte', + '19-point' => '19 Punkte', + }, + }, + 'AIServoContinuousShooting' => 'Auslösepriorität', + 'AIServoImagePriority' => { + Description => 'AI Servo Priorität 1./2. Bild', + PrintConv => { + '1: AF, 2: Drive speed' => 'AF-Priorität/Transportgeschwindigkeit', + '1: AF, 2: Tracking' => 'AF-Priorität/Nachführpriorität', + '1: Release, 2: Drive speed' => 'Auslösung/Transportgeschwindigkeit', + '1: Release, 2: Tracking' => '1: Auslösung, 2: Nachführung', + }, + }, + 'AIServoTrackingMethod' => { + Description => 'AI Servo AF Nachführung', + PrintConv => { + 'Continuous AF track priority' => 'AF Nachführ-Priorität', + 'Main focus point priority' => 'Hauptfokussierungsfeld', + }, + }, + 'AIServoTrackingSensitivity' => { + Description => 'AI Servo Empfindlichkeit', + PrintConv => { + 'Fast' => 'Schnell', + 'Medium Fast' => 'Mittel-Schnell', + 'Medium Slow' => 'Mittel', + 'Slow' => 'Langsam', + }, + }, + 'APEVersion' => 'APE-Version', + 'ARMVersion' => 'ARM-Version', + 'AccessDate' => 'Zugriffsdatum', + 'AccessoryType' => 'Zubehör-Typ', + 'ActionAdvised' => 'Aktion empfohlen', + 'ActiveArea' => 'Aktiver Bereich', + 'ActiveD-Lighting' => { + Description => 'Aktives D-Lighting', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Leicht', + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ActiveD-LightingMode' => { + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Leicht', + 'Off' => 'Aus', + }, + }, + 'AddAspectRatioInfo' => { + Description => 'Seitenverhältnisinfo zufügen', + PrintConv => { + 'Off' => 'Aus', + }, + }, + 'AddOriginalDecisionData' => { + Description => 'Originaldaten zufügen', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Address' => 'Adresse', + 'AdjustmentMode' => 'Korrekturmodus', + 'AdultContentWarning' => { + PrintConv => { + 'Unknown' => 'Unbekannt', + }, + }, + 'AdvancedRaw' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AdvancedSceneMode' => { + Description => 'Erweiteter Szenenmodus', + PrintConv => { + 'Off' => 'Aus', + 'Creative Macro' => 'Makro kreativ', + 'Flower' => 'Blumen', + 'HDR B&W' => 'HDR Schwarz-Weiß', + 'High Key' => 'High-Key', + 'Indoor Sports' => 'Hallensport', + 'Low Key' => 'Low-Key', + 'Outdoor Sports' => 'Freiluftsport', + }, + }, + 'AdvancedSceneType' => 'Erweiterter Szenentyp', + 'AlphaByteCount' => 'Anzahl Bytes der Alpha-Kanal-Daten', + 'AlphaChannelsNames' => 'Alpha-Kanal-Namen', + 'AlphaDataDiscard' => { + Description => 'Verworfene Alpha-Kanal-Daten', + PrintConv => { + 'Flexbits Discarded' => 'FlexBits verworfen', + 'Full Resolution' => 'Volle Auflösung', + 'HighPass Frequency Data Discarded' => 'Hochpass-Frequenz-Daten verworfen', + 'Highpass and LowPass Frequency Data Discarded' => 'Hochpass- und Tiefpass-Frequenz-Daten verworfen', + }, + }, + 'AlphaOffset' => 'Alpha-Kanal-Datenposition', + 'AlreadyApplied' => 'Bereits zugewiesen', + 'AnalogBalance' => 'Analog-Balance', + 'Annotation' => 'Anmerkung', + 'Annotations' => 'Anmerkungen', + 'Anti-Blur' => { + Description => 'Verwacklungsschutz', + PrintConv => { + 'Off' => 'Aus', + 'On (Continuous)' => 'Ein (Kontinuierlich)', + 'On (Shooting)' => 'Ein (Aufnahme)', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'AntiAliasStrength' => 'Anti-Aliasing Stärke', + 'Aperture' => 'Blende', + 'ApertureDisplayed' => 'Angezeigte Blende', + 'ApertureRange' => { + Description => 'Einstellung Verschlusszeitenbereich', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'ApertureRingUse' => { + Description => 'Blendenring-Verwendung', + PrintConv => { + 'Permitted' => 'Erlaubt', + 'Prohibited' => 'Nicht erlaubt', + }, + }, + 'ApertureSetting' => 'Blendeneinstellung', + 'ApertureValue' => 'Blende', + 'ApplicationRecordVersion' => 'IPTC-Modell-2-Version', + 'ApplyShootingMeteringMode' => { + Description => 'Angewandter Belichtungs-/Messmodus', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'ApproximateFNumber' => 'F-Wert angenähert', + 'ApproximateFocusDistance' => 'Fokus-Distanz angenähert', + 'ArtFilter' => { + Description => 'Kunst Filter', + PrintConv => { + 'Drawing' => 'Zeichnung', + 'Fish Eye' => 'Fischauge', + 'Off' => 'Aus', + 'Reflection' => 'Reflektierung', + 'Soft Focus' => 'Weichzeichner', + 'Soft Focus 2' => 'Weichzeichner 2', + 'Sparkle' => 'Perleffekt', + 'Watercolor' => 'Wasserfarbe', + 'Watercolor II' => 'Wasserfarbe II', + }, + }, + 'ArtFilterEffect' => { + Description => 'Filtereffekt Kunst', + PrintConv => { + 'B&W' => 'Schwarz/Weiß', + 'Drawing' => 'Zeichnung', + 'Fish Eye' => 'Fischauge', + 'No Effect' => 'Ohne Effekt', + 'Off' => 'Aus', + 'Reflection' => 'Reflektierung', + 'Soft Focus' => 'Weichzeichner', + 'Soft Focus 2' => 'Weichzeichner 2', + 'Sparkle' => 'Perleffekt', + 'Watercolor' => 'Wasserfarbe', + 'Watercolor II' => 'Wasserfarbe II', + }, + }, + 'ArtMode' => { + PrintConv => { + 'Silent Movie' => 'Stillleben', + }, + }, + 'Artist' => 'Künstler', + 'ArtworkCreator' => 'Artwork Ersteller', + 'AsShotICCProfile' => 'Aufnahme Farbprofil', + 'AsShotNeutral' => 'Aufnahme Neutral', + 'AsShotPreProfileMatrix' => 'Aufnahme Pre Profil Matrix', + 'AsShotProfileName' => 'Aufnahme Pre Profilname', + 'AsShotWhiteXY' => 'Aufnahme Weiß XY', + 'AspectRatio' => 'Bildformat', + 'AssignBktButton' => { + Description => 'Zugeordnete Belichtungsreihen-Taste', + PrintConv => { + 'Auto Bracketing' => 'Automatische Belichtungsreihe', + 'Multiple Exposure' => 'Mehrfachbelichtung', + }, + }, + 'AssignFuncButton' => { + Description => 'FUNC.-Taste zuordnen', + PrintConv => { + 'Exposure comp./AEB setting' => 'Belichtungskorrektur/AEB-Einstellung', + 'Image jump with main dial' => 'Bildsprung mit Haupt-Wahlrad', + 'Image quality' => 'Qualität ändern', + 'LCD brightness' => 'LCD-Helligkeit', + 'Live view function settings' => 'Livebild Funktionseinstellung', + }, + }, + 'AssistButtonFunction' => { + Description => 'Funktion Assist-Taste', + PrintConv => { + 'Av+/- (AF point by QCD)' => 'Av+/- (AF-Feld mit Daumenrad)', + 'FE lock' => 'FE Blitzmesswertspeicherung', + 'Select HP (while pressing)' => 'Ausw.G.pos.(Ass-Taste gedr.)', + 'Select Home Position' => 'Auswahl Grundposition', + }, + }, + 'Audio' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'AudioCodecID' => { + PrintConv => { + 'Unknown -' => 'Unbekannt -', + }, + }, + 'AudioDuration' => 'Audiodauer', + 'AudioOutcue' => 'Audio-Outcue', + 'AudioSamplingRate' => 'Audio-Samplingrate', + 'AudioSamplingResolution' => 'Audio-Samplingauflösung', + 'AudioType' => 'Audiotyp', + 'Author' => 'Autor', + 'AuthorURL' => 'Autor URL', + 'AuthorsPosition' => 'Autorenposition', + 'AutoAperture' => { + Description => 'Blendenring auf A', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AutoBracket' => 'Automatische Belichtungsreihe', + 'AutoBracketModeM' => { + Description => 'Belichtungsreihen bei M', + PrintConv => { + 'Flash Only' => 'Nur Blitz', + 'Flash/Aperture' => 'Blitz & Blende', + 'Flash/Speed' => 'Blitz & Zeit', + 'Flash/Speed/Aperture' => 'Blitz, Zeit & Blende', + }, + }, + 'AutoBracketOrder' => 'BKT-Reihenfolge', + 'AutoBracketSet' => { + Description => 'Belichtungsreihen', + PrintConv => { + 'AE & Flash' => 'Belichtung & Blitz', + 'AE Only' => 'Nur Belichtung', + 'Exposure' => 'Belichtung', + 'Flash Only' => 'Nur Blitz', + 'WB Bracketing' => 'Weißabgleichs-Belichtungsreihe', + }, + }, + 'AutoBracketing' => { + Description => 'Automatische Belichtungsreihe', + PrintConv => { + 'AE' => 'Belichtung', + 'Contrast' => 'Kontrast', + 'Effect' => 'Effekt', + 'No flash & flash' => 'Kein Blitz & Blitz', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Pre-shot' => 'Vorauslösung', + 'WB' => 'Weißabgleich', + 'WB2' => 'Weißabgleich 2', + }, + }, + 'AutoBracketingSet' => { + Description => 'Automatische Belichtungsreihen-Einstellung', + PrintConv => { + 'AE & Flash' => 'Belichtung & Blitz', + 'AE Only' => 'Nur Belichtung', + 'Flash Only' => 'Nur Blitz', + 'WB Bracketing' => 'Weißabgleich-Belichtungsreihe', + }, + }, + 'AutoBrightness' => 'Helligkeit Auto', + 'AutoContrast' => 'Kontrast Auto', + 'AutoDistortionControl' => { + Description => 'Automatische Verzeichnungskontrolle', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AutoExposure' => 'Belichtung Auto', + 'AutoExposureBracketing' => { + Description => 'Auto-Belichtungsreihe', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AutoFP' => { + Description => 'FP-Kurzzeitsynchr.', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AutoFocus' => { + Description => 'Auto-Fokus', + PrintConv => { + 'Off' => 'Deaktiviert', + 'On' => 'Aktiviert', + }, + }, + 'AutoISO' => { + Description => 'ISO-Automatik', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + 'On (high sensitivity)' => 'Ein (Hohe Empfindlichkeit)', + }, + }, + 'AutoISOMax' => 'ISO-Automatik Max', + 'AutoISOMinShutterSpeed' => 'ISO-Automatik Längste Belichtungszeit', + 'AutoLightingOptimizer' => { + Description => 'Autom. Belichtungsoptimierung', + PrintConv => { + 'Disable' => 'Inaktiv', + 'Enable' => 'Aktiv', + 'Low' => 'Gering', + 'Off' => 'Aus', + 'Strong' => 'Stark', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'AutoLightingOptimizerOn' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'AutoPortraitFramed' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'AutoRedEye' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'AutoRotate' => { + Description => 'Automatische Bilddrehung', + PrintConv => { + 'None' => 'Keine', + 'Rotate 180' => '180° gedreht', + 'Rotate 270 CW' => '90° gegen den Uhrzeigersinn', + 'Rotate 90 CW' => '90° im Uhrzeigersinn', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'AutoShadows' => 'Schatten Auto', + 'AuxiliaryLens' => 'Vorsatzlinse', + 'AvApertureSetting' => 'Av Blenden-Einstellung', + 'AvSettingWithoutLens' => { + Description => 'Blendeneinstellung ohne Objektiv', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'BWFilter' => 'S/W-Filter', + 'BWMode' => { + Description => 'Schwarz-Weiß Modus', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'BabyAge' => 'Kindesalter', + 'BabyName' => 'Kindesname', + 'BackLight' => 'Hintergrundbeleuchtung', + 'BackgroundColor' => 'Hintergrundfarbe', + 'BackgroundColorIndicator' => 'Indikator Hintergrundfarbe', + 'BackgroundColorValue' => 'Hintergrundfarbwert', + 'BackgroundTiling' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'BannerImageType' => { + PrintConv => { + 'None' => 'Keines', + }, + }, + 'BaseExposureCompensation' => 'Basis-Belichtungskorrektur', + 'BaseISO' => 'Basis-ISO', + 'BaseName' => 'Basisname', + 'BaseURL' => 'Basis URL', + 'BaselineExposure' => 'Referenzbelichtung', + 'BaselineExposureOffset' => 'Referenzbelichtung Abweichung', + 'BaselineNoise' => 'Grundrauschen', + 'BaselineSharpness' => 'Referenzschärfe', + 'BatteryInfo' => 'Stromquelle', + 'BatteryLevel' => 'Batteriestatus', + 'BatteryOrder' => { + Description => 'Akkureihenfolge', + PrintConv => { + 'Camera Battery First' => 'Zuerst Akku in der Kamera', + 'MB-D10 First' => 'Zuerst Akkus im MB-D10', + }, + }, + 'BatteryState' => { + Description => 'Batteriestatus', + PrintConv => { + 'Almost full' => 'Fast Voll', + 'Empty' => 'Leer', + 'Full' => 'Voll', + 'Half full' => 'Halb Voll', + 'Low' => 'Niedrig', + }, + }, + 'BayerGreenSplit' => 'Pixelaufteilung nach Bayer', + 'BayerPattern' => 'Bayer Matrix', + 'Beep' => { + Description => 'Tonsignal', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Tief', + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'BestShotMode' => { + Description => 'Bester Aufnahmemodus', + PrintConv => { + 'Autumn Leaves' => 'Herbstlaub', + 'Baby CS' => 'Kleinkind', + 'Backlight' => 'Hintergrundbeleuchtung', + 'Beach' => 'Strand', + 'Candlelight Portrait' => 'Kerzenlicht Porträt', + 'Child CS' => 'Kinder', + 'Children' => 'Kinder', + 'Collection' => 'Sammlung', + 'Fireworks' => 'Feuerwerk', + 'Flower' => 'Blumen', + 'Food' => 'Lebensmittel', + 'For YouTube' => 'Für YouTube', + 'For eBay' => 'Für eBay', + 'High Sensitivity' => 'Hohe Empfindlichkeit', + 'Natural Green' => 'Naturgrün', + 'Night Scene' => 'Nachtszene', + 'Night Scene Portrait' => 'Nacht-Porträt', + 'People' => 'Menschen', + 'Pet' => 'Haustiere', + 'Pet CS' => 'Haustiere', + 'Portrait' => 'Porträt', + 'Silent' => 'Stillleben', + 'Snow' => 'Schnee', + 'Sports' => 'Sport', + 'Sports CS' => 'Sport CS', + 'Sundown' => 'Sonnenuntergang', + 'Twilight' => 'Dämmerung', + 'Underwater' => 'Unterwasser', + }, + }, + 'BigImage' => 'Big Image Vorschaubild', + 'BitDepth' => 'Bit-Tiefe', + 'BitsPerSample' => 'Anzahl der Bits pro Komponente', + 'BlackLevel' => 'Schwarzwert', + 'BlackLevel2' => 'Schwarzwert 2', + 'BlackLevelData' => 'Schwarzwert Daten', + 'BlackLevelDeltaH' => 'Schwarzwert Delta H', + 'BlackLevelDeltaV' => 'Schwarzwert Delta V', + 'BlackPoint' => 'Schwarzpunkt', + 'BlueAdjust' => 'Blau-Korrektur', + 'BlueBalance' => 'Farbabgleich Blau', + 'BlueHue' => 'Farbton Blau', + 'BlueMatrixColumn' => 'Blau-Matrixspalte', + 'BlueSaturation' => 'Sättigung Blau', + 'BlueTRC' => 'Blau-Tonwertwiedergabe-Kurve', + 'BlueX' => 'Blaupunkt X', + 'BlueY' => 'Blaupunkt Y', + 'BlurControl' => { + Description => 'Bildstabilisierung', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Niedrig', + 'Medium' => 'Mittel', + 'Off' => 'Aus', + }, + }, + 'BlurWarning' => { + Description => 'Verwackelungswarnung', + PrintConv => { + 'Blur Warning' => 'Verwackelungswarnung', + 'None' => 'Keine', + }, + }, + 'BodyBatteryADLoad' => 'Kamerabatterie A/D unter Last', + 'BodyBatteryADNoLoad' => 'Kamerabatterie A/D im Leerlauf', + 'BodyBatteryState' => { + Description => 'Kamerabatterie-Status', + PrintConv => { + 'Almost Empty' => 'Fast leer', + 'Empty or Missing' => 'Leer oder nicht vorhanden', + 'Full' => 'Voll geladen', + 'Running Low' => 'Schwach', + }, + }, + 'BodyFirmwareVersion' => 'Kamera-Firmware-Version', + 'BracketMode' => { + Description => 'Belichtungsreihen-Modus', + PrintConv => { + 'Off' => 'Aus', + }, + }, + 'BracketSequence' => 'Belichtungsreihen-Abfolge', + 'BracketShot' => 'Belichtungsreihen-Aufnahme', + 'BracketShotNumber' => { + Description => 'Belichtungsreihen-Bildnummer', + PrintConv => { + '1 of 2' => '1 von 2', + '1 of 3' => '1 von 3', + '1 of 5' => '1 von 5', + '2 of 2' => '2 von 2', + '2 of 3' => '2 von 3', + '2 of 5' => '2 von 5', + '3 of 3' => '3 von 3', + '3 of 5' => '3 von 5', + '4 of 5' => '4 von 5', + '5 of 5' => '5 von 5', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'BracketStep' => { + Description => 'Belichtungsreihenschritte', + PrintConv => { + '1 EV' => '1 LW', + '1/3 EV' => '1/3 LW', + '2/3 EV' => '2/3 LW', + }, + }, + 'BracketValue' => 'Belichtungsreihen-Wert', + 'Brightness' => 'Helligkeit', + 'BrightnessAdj' => 'Helligkeitskorrektur', + 'BrightnessValue' => 'Helligkeit', + 'BulbDuration' => 'Bulb-Dauer', + 'BurstMode' => { + Description => 'Burst Modus', + PrintConv => { + 'Auto Exposure Bracketing (AEB)' => 'Automatische Belichtungsreihe', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Unlimited' => 'Unbegrenzt', + }, + }, + 'BurstMode2' => 'Burst Modus 2', + 'BurstShot' => 'Burst Aufnahme', + 'ButtonFunctionControlOff' => { + Description => 'Tastenfunktion wenn Schnelleinstellrad OFF', + PrintConv => { + 'Disable main, Control, Multi-control' => 'Deaktiv Haupt-Wahlrad, Schnelleinstellrad, Multicontroller', + 'Normal (enable)' => 'Normal (eingeschaltet)', + }, + }, + 'By-line' => 'Ersteller', + 'By-lineTitle' => 'Beruf des Erstellers', + 'ByteOrder' => 'Bytereihenfolge', + 'CCDSensitivity' => 'CCD Empfindlichkeit', + 'CFALayout' => { + Description => 'Farbfilter Anordnung', + PrintConv => { + 'Rectangular' => 'Rechteckig', + }, + }, + 'CFAPattern' => 'Farbfiltermatrix', + 'CFAPattern2' => 'Farbfiltermatrix 2', + 'CFAPatternColumns' => 'Farbfiltermatrix Spalten', + 'CFAPatternRows' => 'Farbfiltermatrix Zeilen', + 'CFAPatternValues' => 'Farbfiltermatrix Werte', + 'CFARepeatPatternDim' => 'Farbfiltermatrix-Größe', + 'CLModeShootingSpeed' => 'Lowspeed-Bildrate', + 'CMContrast' => 'CM Kontrast', + 'CMExposureCompensation' => 'CM Belichtungskorrektur', + 'CMHue' => 'CM Farbton', + 'CMMFlags' => 'CMM-Flags', + 'CMSaturation' => 'CM Farbsättigung', + 'CMSharpness' => 'CM Schärfe', + 'CMWhiteBalance' => 'CM Weißabgleich', + 'CMWhiteBalanceComp' => 'CM Weißabgleichsausgleich', + 'CMWhiteBalanceGrayPoint' => 'CM Weißabgleich Graupunkt', + 'CPUArchitecture' => 'CPU Architektur', + 'CPUByteOrder' => { + Description => 'CPU Bytereihenfolge', + PrintConv => { + 'Big endian' => 'Big-endian', + 'Little endian' => 'Little-endian', + }, + }, + 'CPUCount' => 'Anzahl CPU', + 'CPUFirmwareVersion' => 'CPU-Firmware-Version', + 'CPUSubtype' => 'CPU Subtyp', + 'CPUType' => { + Description => 'CPU Typ', + PrintConv => { + 'Any' => 'Unbestimmt', + 'None' => 'Unbestimmt', + 'i860 big endian' => 'i860 big-endian', + 'i860 little endian' => 'i860 little-endian', + }, + }, + 'CalibrationIlluminant1' => { + Description => 'Lichtquellenkalibrierung 1', + PrintConv => { + 'Cloudy' => 'Bewölkt', + 'Cool White Fluorescent' => 'Neonlicht kaltweiß', + 'Day White Fluorescent' => 'Neonlicht neutralweiß', + 'Daylight' => 'Tageslicht', + 'Daylight Fluorescent' => 'Neonlicht tageslichtweiß', + 'Fine Weather' => 'Wolkenlos', + 'Flash' => 'Blitz', + 'Fluorescent' => 'Neonlicht', + 'ISO Studio Tungsten' => 'ISO Studio-Kunstlicht (Glühbirne)', + 'Other' => 'Andere Lichtquelle', + 'Shade' => 'Schatten', + 'Standard Light A' => 'Standard-Licht A', + 'Standard Light B' => 'Standard-Licht B', + 'Standard Light C' => 'Standard-Licht C', + 'Tungsten (Incandescent)' => 'Kunstlicht (Glühbirne)', + 'Unknown' => 'Unbekannt', + 'Warm White Fluorescent' => 'Neonlicht warmweiß', + 'White Fluorescent' => 'Neonlicht universalweiß', + }, + }, + 'CalibrationIlluminant2' => { + Description => 'Lichtquellenkalibrierung 2', + PrintConv => { + 'Cloudy' => 'Bewölkt', + 'Cool White Fluorescent' => 'Neonlicht kaltweiß', + 'Day White Fluorescent' => 'Neonlicht neutralweiß', + 'Daylight' => 'Tageslicht', + 'Daylight Fluorescent' => 'Neonlicht tageslichtweiß', + 'Fine Weather' => 'Wolkenlos', + 'Flash' => 'Blitz', + 'Fluorescent' => 'Neonlicht', + 'ISO Studio Tungsten' => 'ISO Studio-Kunstlicht (Glühbirne)', + 'Other' => 'Andere Lichtquelle', + 'Shade' => 'Schatten', + 'Standard Light A' => 'Standard-Licht A', + 'Standard Light B' => 'Standard-Licht B', + 'Standard Light C' => 'Standard-Licht C', + 'Tungsten (Incandescent)' => 'Kunstlicht (Glühbirne)', + 'Unknown' => 'Unbekannt', + 'Warm White Fluorescent' => 'Neonlicht warmweiß', + 'White Fluorescent' => 'Neonlicht universalweiß', + }, + }, + 'CameraBody' => 'Kamera Gehäuse', + 'CameraByteOrder' => 'Kamera Bytereihenfolge', + 'CameraCalibration1' => 'Kamerakalibrierung 1', + 'CameraCalibration2' => 'Kamerakalibrierung 2', + 'CameraColorCalibration01' => 'Kamera Farbkalibrierung 01', + 'CameraColorCalibration02' => 'Kamera Farbkalibrierung 02', + 'CameraColorCalibration03' => 'Kamera Farbkalibrierung 03', + 'CameraColorCalibration04' => 'Kamera Farbkalibrierung 04', + 'CameraColorCalibration05' => 'Kamera Farbkalibrierung 05', + 'CameraColorCalibration06' => 'Kamera Farbkalibrierung 06', + 'CameraColorCalibration07' => 'Kamera Farbkalibrierung 07', + 'CameraColorCalibration08' => 'Kamera Farbkalibrierung 08', + 'CameraColorCalibration09' => 'Kamera Farbkalibrierung 09', + 'CameraColorCalibration10' => 'Kamera Farbkalibrierung 10', + 'CameraColorCalibration11' => 'Kamera Farbkalibrierung 11', + 'CameraColorCalibration12' => 'Kamera Farbkalibrierung 12', + 'CameraColorCalibration13' => 'Kamera Farbkalibrierung 13', + 'CameraColorCalibration14' => 'Kamera Farbkalibrierung 14', + 'CameraColorCalibration15' => 'Kamera Farbkalibrierung 15', + 'CameraID' => 'Kamera ID', + 'CameraISO' => 'Kamera-ISO', + 'CameraInfo' => 'Pentax-Modell', + 'CameraInfoByteOrder' => 'Kamerainformation Bytereihenfolge', + 'CameraModel' => 'Kamera Modell', + 'CameraOrientation' => { + Description => 'Ausrichtung der Kamera', + PrintConv => { + 'Downwards' => 'Abwärts', + 'Rotate 180' => '180° gedreht', + 'Rotate 270 CW' => '90° gegen den Uhrzeigersinn', + 'Rotate 90 CW' => '90° im Uhrzeigersinn', + 'Upwards' => 'Aufwärts', + }, + }, + 'CameraProfile' => 'Kameraprofil', + 'CameraProfileDigest' => 'Kennwert des Kameraprofils', + 'CameraSerialNumber' => 'Kamera-Seriennummer', + 'CameraSettings' => 'Kameraeinstellungen', + 'CameraSettingsVersion' => 'Kameraeinstellungen-Version', + 'CameraTemperature' => 'Kamera-Temperatur', + 'CameraType' => { + Description => 'Kameratyp', + PrintConv => { + 'Compact' => 'Kompakt', + 'DV Camera' => 'DV Kamera', + 'EOS High-end' => 'EOS Highend', + 'EOS Mid-range' => 'EOS Mittelklasse', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'CameraType2' => 'Kameratyp 2', + 'CanonAFInfo' => 'AF-Info', + 'CanonAFInfo2' => 'AF-Info (2)', + 'CanonExposureMode' => { + Description => 'Belichtungsmodus', + PrintConv => { + 'Aperture-priority AE' => 'Blendenpriorität', + 'Bulb' => 'Bulb-Modus', + 'Manual' => 'Manuell', + 'Program AE' => 'Programmautomatik', + 'Shutter speed priority AE' => 'Verschlusspriorität', + }, + }, + 'CanonFileDescription' => 'Canon Dateibeschreibung', + 'CanonFileInfo' => 'Dateiinformationen', + 'CanonFileLength' => 'Dateilänge', + 'CanonFirmwareVersion' => 'Firmware-Version', + 'CanonFlashMode' => { + Description => 'Blitz-Modus', + PrintConv => { + 'Auto' => 'Automatisch', + 'External flash' => 'Externer Blitz', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Red-eye reduction' => 'Rote-Augen-Reduzierung', + 'Red-eye reduction (Auto)' => 'Rote-Augen-Reduzierung (Automatisch)', + 'Red-eye reduction (On)' => 'Rote-Augen-Reduzierung (Ein)', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'CanonFocalLength' => 'Objektivart', + 'CanonImageHeight' => 'Canon-Bildhöhe', + 'CanonImageSize' => { + Description => 'Canon-Bildgröße', + PrintConv => { + 'Large' => 'Groß', + 'Medium' => 'Mittelgroß', + 'Medium 1' => 'Mittelgroß 1', + 'Medium 2' => 'Mittelgroß 2', + 'Medium 3' => 'Mittelgroß 3', + 'Medium Movie' => 'Mittelgroßer Film', + 'Postcard' => 'Postkarte', + 'Small' => 'Klein', + 'Small 1' => 'Klein 1', + 'Small 2' => 'Klein 2', + 'Small 3' => 'Klein 3', + 'Small Movie' => 'Kleiner Film', + 'Widescreen' => 'Breitbild', + }, + }, + 'CanonImageType' => 'Canon-Bildtyp', + 'CanonImageWidth' => 'Canon-Bildbreite', + 'CanonModelID' => 'Canon-Modell ID', + 'Caption' => 'Bildtext', + 'Caption-Abstract' => 'Beschreibung/Zusammenfassung', + 'CaptionWriter' => 'Bildtextautor', + 'CaptureXResolutionUnit' => { + PrintConv => { + 'um' => 'µm (Mikrometer)', + }, + }, + 'CaptureYResolutionUnit' => { + PrintConv => { + 'um' => 'µm (Mikrometer)', + }, + }, + 'CasioImageSize' => 'Casio Bildgröße', + 'CatalogSets' => 'Katalogzusammenstellungen', + 'Categories' => { + Description => 'Kategorien', + PrintConv => { + 'People' => 'Menschen', + 'Scenery' => 'Szene', + }, + }, + 'Category' => 'Kategorie', + 'CenterAFArea' => { + Description => 'AF-Messfeld Mitte', + PrintConv => { + 'Normal Zone' => 'Normal', + 'Wide Zone' => 'Groß', + }, + }, + 'CenterFocusPoint' => { + Description => 'Fokuspunkt Mitte', + PrintConv => { + 'Normal Zone' => 'Normaler Bereich', + 'Wide Zone' => 'Großer Bereich', + }, + }, + 'CenterWeightedAreaSize' => { + Description => 'Messfeldgröße Mitte', + PrintConv => { + 'Average' => 'Durchschnitt', + }, + }, + 'Certificate' => 'Zertifikat', + 'Channels' => 'Kanäle', + 'Chapter' => 'Kapitel', + 'CharacterSet' => 'Zeichensatz', + 'Children' => 'Kinder', + 'ChromaticAberration' => 'Farbabweichung', + 'ChromaticAberrationB' => 'Farbabweichung B', + 'ChromaticAberrationBlue' => 'Farbabweichung Blau', + 'ChromaticAberrationCorr' => { + Description => 'Farbabweichung Korrektur', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ChromaticAberrationCorrection' => { + Description => 'Farbabweichung Korrektur', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ChromaticAberrationOn' => 'Farbabweichung Ein', + 'ChromaticAberrationR' => 'Farbabweichung R', + 'ChromaticAberrationRed' => 'Farbabweichung Rot', + 'ChromaticAberrationSetting' => { + Description => 'Farbabweichung Einstellung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Chromaticity' => 'Chromatizität', + 'ChrominanceNR_TIFF_JPEG' => { + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Leicht', + 'Off' => 'Aus', + }, + }, + 'ChrominanceNoiseReduction' => { + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Leicht', + 'Off' => 'Aus', + }, + }, + 'CircleOfConfusion' => 'Unschärfekreis', + 'City' => 'Stadt/Ort', + 'ClassifyState' => 'Klassifizierungs-Status', + 'CodePage' => { + PrintConv => { + 'Unicode UTF-16, big endian' => 'Unicode UTF-16, Big-endian', + 'Unicode UTF-16, little endian' => 'Unicode UTF-16, Little-endian', + 'Unicode UTF-32, big endian' => 'Unicode UTF-32, Big-endian', + 'Unicode UTF-32, little endian' => 'Unicode UTF-32, Little-endian', + }, + }, + 'CodedCharacterSet' => 'IPTC Characterset', + 'CodedContentScanningKind' => { + PrintConv => { + 'Unknown' => 'Unbekannt', + }, + }, + 'ColorAberrationControl' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ColorAdjustment' => 'Farbeinstellung', + 'ColorAdjustmentMode' => { + Description => 'Farbeinstellung Modus', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ColorBW' => 'Schwarz/Weiß', + 'ColorBalance' => 'Farbabgleich', + 'ColorBalance1' => 'Farbabgleich 1', + 'ColorBalanceA' => 'Farbabgleich A', + 'ColorBalanceAdj' => { + Description => 'Farbabgleich Korrektur', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ColorBalanceBlue' => 'Farbabgleich Blau', + 'ColorBalanceGreen' => 'Farbabgleich Grün', + 'ColorBalanceRed' => 'Farbangleich Rot', + 'ColorBalanceVersion' => 'Farbabgleich Version', + 'ColorBitDepth' => 'Farbtiefe', + 'ColorBoostType' => { + PrintConv => { + 'Nature' => 'Natur', + 'People' => 'Menschen', + }, + }, + 'ColorBooster' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ColorCompensationFilter' => 'Farbkorrektur-Filter', + 'ColorComponents' => 'Anzahl der Bildkomponenten', + 'ColorCorrection' => 'Farbkorrektur', + 'ColorDataVersion' => 'Farbdaten Version', + 'ColorEffect' => { + Description => 'Farbeffekt', + PrintConv => { + 'Black & White' => 'Schwarz/Weiß', + 'Cool' => 'Kühl', + 'Off' => 'Aus', + }, + }, + 'ColorFilter' => { + Description => 'Farbfilter', + PrintConv => { + 'Black & White' => 'Schwarz/Weiß', + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'Off' => 'Aus', + 'Purple' => 'Lila', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + }, + }, + 'ColorHue' => 'Farbwiedergabe', + 'ColorInfo' => 'Farb-Informationen', + 'ColorMap' => 'Farbtafel', + 'ColorMatrix' => 'Farb-Matrix', + 'ColorMatrix1' => 'Farbmatrix 1', + 'ColorMatrix2' => 'Farbmatrix 2', + 'ColorMatrixNumber' => 'Farbmatrix Nummer', + 'ColorMode' => { + Description => 'Farbmodus', + PrintConv => { + 'Autumn' => 'Herbst', + 'Autumn Leaves' => 'Herbstlaub', + 'B & W' => 'S/W', + 'B&W' => 'Schwarz/Weiß', + 'B&W Red Filter' => 'Schwarz-Weiß Rotfilter', + 'B&W Yellow Filter' => 'Schwarz-Weiß Gelbfilter', + 'Black & White' => 'Schwarz/Weiß', + 'Chrome' => 'Farbe', + 'Clear' => 'Klar', + 'Deep' => 'Tief', + 'Duotone' => 'Zweiton', + 'Evening' => 'Abends', + 'Grayscale' => 'Graustufen', + 'Indexed' => 'Indiziert', + 'Indexed Color' => 'Indizierte Farben', + 'Landscape' => 'Landschaft', + 'Light' => 'Hell', + 'Multichannel' => 'Mehrkanal', + 'Natural' => 'Natürlich', + 'Natural color' => 'Natürliche Farben', + 'Natural sRGB' => 'Neutral sRGB', + 'Natural+ sRGB' => 'Neutral+ sRGB', + 'Night Portrait' => 'Nachtporträt', + 'Night Scene' => 'Nachtszene', + 'Night View' => 'Abendszene', + 'Night View/Portrait' => 'Abendszene/Porträt', + 'Off' => 'Aus', + 'Portrait' => 'Porträt', + 'RGB Color' => 'RGB Farbe', + 'Solarization' => 'Solarisation', + 'Sunset' => 'Sonnenuntergang', + 'Vivid' => 'Lebhafte Farbe', + 'Vivid color' => 'Lebhafte Farbe', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorMoireReduction' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ColorMoireReductionMode' => { + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Leicht', + 'Off' => 'Aus', + }, + }, + 'ColorNoiseReduction' => 'Farbrauschunterdrückung', + 'ColorNoiseReductionDetail' => 'Farbrauschunterdrückung Detail', + 'ColorPalette' => 'Farbpalette', + 'ColorPlanes' => 'Farbebenen', + 'ColorProfile' => { + Description => 'Farbprofil', + PrintConv => { + 'Embedded' => 'Eingebunden', + 'Not Embedded' => 'Nicht eingebunden', + }, + }, + 'ColorRepresentation' => 'Farbdarstellung', + 'ColorReproduction' => 'Farbreproduktion', + 'ColorSequence' => 'Farbreihenfolge', + 'ColorSpace' => { + Description => 'Farbraum', + PrintConv => { + 'B&W' => 'Schwarz/Weiß', + 'ICC Profile' => 'ICC-Profil', + 'Monochrome' => 'Monochrom', + 'Natural sRGB' => 'Neutral sRGB', + 'Natural+ sRGB' => 'Neutral+ sRGB', + 'Uncalibrated' => 'Nicht festgelegt', + 'Undefined' => 'Nicht definiert', + }, + }, + 'ColorSpaceData' => 'Farbraum Daten', + 'ColorTempAsShot' => 'Farbtemperatur Aufnahme', + 'ColorTempAuto' => 'Farbtemperatur Auto', + 'ColorTempCloudy' => 'Farbtemperatur Bewölkt', + 'ColorTempCustom' => 'Farbtemperatur Benutzerdefiniert', + 'ColorTempCustom1' => 'Farbtemperatur Benutzerdefiniert 1', + 'ColorTempCustom2' => 'Farbtemperatur Benutzerdefiniert 2', + 'ColorTempDaylight' => 'Farbtemperatur Tageslicht', + 'ColorTempFlash' => 'Farbtemperatur Blitz', + 'ColorTempFlashData' => 'Farbtemperatur Blitzdaten', + 'ColorTempFluorescent' => 'Farbtemperatur Neonlicht', + 'ColorTempKelvin' => 'Farbtemperatur Kelvin', + 'ColorTempMeasured' => 'Farbtemperatur Messung', + 'ColorTempPC1' => 'Farbtemperatur PC1', + 'ColorTempPC2' => 'Farbtemperatur PC2', + 'ColorTempPC3' => 'Farbtemperatur PC3', + 'ColorTempShade' => 'Farbtemperatur Schatten', + 'ColorTempTungsten' => 'Farbtemperatur Glühbirne', + 'ColorTempUnknown' => 'Farbtemperatur Unbekannt', + 'ColorTempUnknown10' => 'Farbtemperatur Unbekannt 10', + 'ColorTempUnknown11' => 'Farbtemperatur Unbekannt 11', + 'ColorTempUnknown12' => 'Farbtemperatur Unbekannt 12', + 'ColorTempUnknown13' => 'Farbtemperatur Unbekannt 13', + 'ColorTempUnknown14' => 'Farbtemperatur Unbekannt 14', + 'ColorTempUnknown15' => 'Farbtemperatur Unbekannt 15', + 'ColorTempUnknown16' => 'Farbtemperatur Unbekannt 16', + 'ColorTempUnknown17' => 'Farbtemperatur Unbekannt 17', + 'ColorTempUnknown18' => 'Farbtemperatur Unbekannt 18', + 'ColorTempUnknown19' => 'Farbtemperatur Unbekannt 19', + 'ColorTempUnknown2' => 'Farbtemperatur Unbekannt 2', + 'ColorTempUnknown20' => 'Farbtemperatur Unbekannt 20', + 'ColorTempUnknown3' => 'Farbtemperatur Unbekannt 3', + 'ColorTempUnknown4' => 'Farbtemperatur Unbekannt 4', + 'ColorTempUnknown5' => 'Farbtemperatur Unbekannt 5', + 'ColorTempUnknown6' => 'Farbtemperatur Unbekannt 6', + 'ColorTempUnknown7' => 'Farbtemperatur Unbekannt 7', + 'ColorTempUnknown8' => 'Farbtemperatur Unbekannt 8', + 'ColorTempUnknown9' => 'Farbtemperatur Unbekannt 9', + 'ColorTemperature' => 'Farbtemperatur', + 'ColorTemperatureAdj' => 'Farbtemperaturkorrektur', + 'ColorTemperatureSetting' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + }, + }, + 'ColorTone' => 'Farbton', + 'ColorToneAdj' => 'Farbtonkorrektur', + 'ColorToneFaithful' => { + Description => 'Farbton Natürlich', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorToneLandscape' => { + Description => 'Farbton Landschaft', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorToneMonochrome' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorToneNeutral' => { + Description => 'Farbton Neutral', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorTonePortrait' => { + Description => 'Farbton Porträt', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorToneStandard' => { + Description => 'Farbton Standard', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorToneUnknown' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorToneUserDef1' => { + Description => 'Farbton Benutzerdefiniert 1', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorToneUserDef2' => { + Description => 'Farbton Benutzerdefiniert 2', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorToneUserDef3' => { + Description => 'Farbton Benutzerdefiniert 3', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ColorTransform' => { + Description => 'Farbtransformation', + PrintConv => { + 'Unknown (RGB or CMYK)' => 'Unbekannt (RGB oder CMYK)', + }, + }, + 'ColorimetricReference' => 'Farbmetrische Referenz', + 'CommandDials' => { + Description => 'Einstellräder', + PrintConv => { + 'Reversed (Main Aperture, Sub Shutter)' => 'Vertauscht', + 'Standard (Main Shutter, Sub Aperture)' => 'Standard', + }, + }, + 'CommandDialsApertureSetting' => { + Description => 'Einstellräder Blendeneinstellung', + PrintConv => { + 'Aperture Ring' => 'Mit Blendenring', + 'Sub-command Dial' => 'Mit Einstellrad', + }, + }, + 'CommandDialsChangeMainSub' => { + Description => 'Einstellräder Funktionsbelegung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'CommandDialsMenuAndPlayback' => { + Description => 'Einstellräder Menüs und Wiedergabe', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'CommandDialsReverseRotation' => { + Description => 'Einstellräder Auswahlrichtung', + PrintConv => { + 'No' => 'Standard', + 'Yes' => 'Umgekehrt', + }, + }, + 'CommanderChannel' => 'Master-Steuerung Kanal', + 'CommanderGroupAManualOutput' => 'Master-Steuerung Gruppe A M Korr', + 'CommanderGroupAMode' => { + Description => 'Master-Steuerung Gruppe A Modus', + PrintConv => { + 'Auto Aperture' => 'Blendenautomatik (AA)', + 'Manual' => 'Manuell', + 'Off' => 'Aus', + }, + }, + 'CommanderGroupA_TTL-AAComp' => 'Master-Steuerung Gruppe A TTL/AA Korr', + 'CommanderGroupA_TTLComp' => 'Master-Steuerung Gruppe-A TTL-Korrektur', + 'CommanderGroupBManualOutput' => 'Master-Steuerung Gruppe B M Korr', + 'CommanderGroupBMode' => { + Description => 'Master-Steuerung Gruppe B Modus', + PrintConv => { + 'Auto Aperture' => 'Blendenautomatik (AA)', + 'Manual' => 'Manuell', + 'Off' => 'Aus', + }, + }, + 'CommanderGroupB_TTL-AAComp' => 'Master-Steuerung Gruppe B TTL/AA Korr', + 'CommanderGroupB_TTLComp' => 'Master-Steuerung Gruppe-B TTL-Korrektur', + 'CommanderInternalFlash' => { + Description => 'Master-Steuerung Intgr. Blitz Modus', + PrintConv => { + 'Manual' => 'Manuell', + 'Off' => 'Aus', + }, + }, + 'CommanderInternalManualOutput' => 'Master-Steuerung Intgr. Blitz M Korr', + 'CommanderInternalTTLComp' => 'Master-Steuerung Intgr. Blitz TTL Korr', + 'Comment' => 'Kommentar', + 'Comments' => 'Kommentare', + 'Compilation' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'ComponentBitDepth' => 'Komponente Farbtiefe', + 'ComponentsConfiguration' => 'Bedeutung jeder Komponente', + 'CompressedBitsPerPixel' => 'Bildkomprimierungsmodus', + 'CompressedImageSize' => 'Komprimierte Bildgröße', + 'Compression' => { + Description => 'Komprimierungsschema', + PrintConv => { + 'Bitfields' => 'Bitfelder', + 'JBIG B&W' => 'JBIG Schwarz-Weiß', + 'JBIG Color' => 'JBIG Farbe', + 'JPEG' => 'JPEG-Komprimierung', + 'JPEG (old-style)' => 'JPEG (alte Version)', + 'Kodak DCR Compressed' => 'Kodak DCR-Komprimierung', + 'Kodak KDC Compressed' => 'Kodak KDC-Komprimierung', + 'Next' => 'NeXT 2-Bit Kodierung', + 'Nikon NEF Compressed' => 'Nikon NEF-Komprimierung', + 'None' => 'Keines', + 'PNG' => 'PNG-Komprimierung', + 'Pentax PEF Compressed' => 'Pentax PEF-Komprimierung', + 'SGILog' => 'SGI 32-Bit Log Luminance Kodierung', + 'SGILog24' => 'SGI 24-Bit Log Luminance Kodierung', + 'Sony ARW Compressed' => 'Sony ARW-Komprimierung', + 'Thunderscan' => 'ThunderScan 4-Bit Kodierung', + 'Uncompressed' => 'Nicht komprimiert', + }, + }, + 'CompressionFactor' => 'Komprimierungsfaktor', + 'CompressionLevel' => 'Komprimierungsgrad', + 'CompressionRatio' => 'Komprimierungsrate', + 'CompressionType' => { + Description => 'Komprimierungsschema', + PrintConv => { + 'Little-endian, no compression' => 'Little-endian, keine Komprimierung', + 'None' => 'Keines', + }, + }, + 'ConditionalFEC' => 'Blitzbelichtungskorrektur', + 'ConnectionSpaceIlluminant' => 'Weißpunkt des Verbindungsfarbraums', + 'ConstrainedCropHeight' => 'Ausschnitt erzeugte Höhe', + 'ConstrainedCropWidth' => 'Ausschnitt erzeugte Breite', + 'Contact' => 'Kontakt', + 'ContentLocationCode' => 'Inhaltspositionscode', + 'ContentLocationName' => 'Inhaltspositionsname', + 'ContinuousBracketing' => { + Description => 'Serienbild-Belichtungsreihe', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Niedrig', + }, + }, + 'ContinuousDrive' => { + Description => 'Aufnahme-Modus', + PrintConv => { + 'Continuous' => 'Serienaufnahme', + 'Movie' => 'Filmen', + 'Silent Single' => 'Stillleben', + 'Single' => 'Einzelbild', + }, + }, + 'ContinuousShootingSpeed' => { + Description => 'Geschwindigkeit Reihenaufnahmen', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'ContinuousShotLimit' => { + Description => 'Limit Anzahl Reihenaufnahmen', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'Contrast' => { + Description => 'Kontrast', + PrintConv => { + '+1 (medium high)' => '+1 (Leicht erhöht)', + '+2 (high)' => '+2 (Stark)', + '+3 (very high)' => '+3 (Sehr hoch)', + '-1 (medium low)' => '-1 (Leicht verringert)', + '-2 (low)' => '-2 (Leicht)', + '-3 (very low)' => '-3 (Sehr gering)', + 'Film Simulation' => 'Film-Simulation', + 'High' => 'Stark', + 'Low' => 'Leicht', + 'Med High' => 'Leicht erhöht', + 'Med Low' => 'Leicht verringert', + 'Medium High' => 'Mittel-Hoch', + 'Medium Low' => 'Mittel-Gering', + 'Very High' => 'Sehr hoch', + 'Very Low' => 'Sehr gering', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'Contrast2012' => 'Kontrast 2012', + 'ContrastAdj' => 'Kontrastkorrektur', + 'ContrastAdjustment' => 'Kontrastkorrektur', + 'ContrastCurve' => 'Kontrast-Kurve', + 'ContrastDetectAFArea' => 'AF-Bereich Kontrast gesteuert', + 'ContrastFaithful' => { + Description => 'Kontrast Natürlich', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ContrastHighlightShadowAdj' => { + Description => 'Kontrast helle Stellen', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ContrastLandscape' => { + Description => 'Kontrast Landschaft', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ContrastMode' => { + Description => 'Kontrast Modus', + PrintConv => { + 'High' => 'Hoch', + 'High Dynamic' => 'Hoch dynamisch', + 'Low' => 'Niedrig', + 'Low Key' => 'Low-Key', + 'Medium High' => 'Mitte hoch', + 'Medium Low' => 'Mitte niedrig', + }, + }, + 'ContrastMonochrome' => { + Description => 'Kontrast Monochrom', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ContrastNeutral' => { + Description => 'Kontrast Neutral', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ContrastPortrait' => { + Description => 'Kontrast Porträt', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ContrastSetting' => 'Kontrasteinstellung', + 'ContrastShadow' => 'Kontrast dunkle Stellen', + 'ContrastStandard' => { + Description => 'Kontrast Standard', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ContrastUnknown' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ContrastUserDef1' => { + Description => 'Kontrast Benutzerdefiniert 1', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ContrastUserDef2' => { + Description => 'Kontrast Benutzerdefiniert 2', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ContrastUserDef3' => { + Description => 'Kontrast Benutzerdefiniert 3', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'Contributor' => 'Mitwirkender', + 'Contributors' => 'Mitwirkende', + 'ControlDialSet' => { + PrintConv => { + 'Aperture' => 'Blende', + 'Shutter Speed' => 'Belichtungszeit', + }, + }, + 'ControlMode' => { + Description => 'Steuerungsmethode', + PrintConv => { + 'Camera Local Control' => 'Lokale Kamerasteuerung', + 'Computer Remote Control' => 'Kamerasteuerung per Computer', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ConversionLens' => { + Description => 'Vorsatzlinse', + PrintConv => { + 'Macro' => 'Makro', + 'Off' => 'Aus', + 'Telephoto' => 'Tele', + }, + }, + 'Converter' => 'Konverter', + 'Copyright' => 'Urheberrechtsvermerk', + 'CopyrightNotice' => 'Urheberrechtsvermerk', + 'CopyrightStatus' => { + PrintConv => { + 'Unknown' => 'Unbekannt', + }, + }, + 'CoringValues' => 'Coring Werte', + 'Country' => 'Land', + 'Country-PrimaryLocationCode' => 'ISO-Ländercode', + 'Country-PrimaryLocationName' => 'Land', + 'CountryCode' => 'ISO-Ländercode', + 'Coverage' => 'Anwendungsbereich', + 'CreateDate' => 'Digitalisierungsdatum/-uhrzeit', + 'CreationDate' => 'Aufnahmedatum', + 'CreativeStyle' => { + Description => 'Kreativmodus', + PrintConv => { + 'Autumn' => 'Herbst', + 'Autumn Leaves' => 'Herbstlaub', + 'B&W' => 'Schwarz/Weiß', + 'Clear' => 'Klar', + 'Deep' => 'Tief', + 'Landscape' => 'Landschaft', + 'Light' => 'Hell', + 'Night View/Portrait' => 'Abendszene/Porträt', + 'Portrait' => 'Porträt', + 'Sunset' => 'Sonnenuntergang', + 'Vivid' => 'Lebhafte Farbe', + }, + }, + 'CreativeStyleSetting' => { + PrintConv => { + 'B&W' => 'Schwarz/Weiß', + 'Landscape' => 'Landschaft', + 'Portrait' => 'Porträt', + 'Sunset' => 'Sonnenuntergang', + }, + }, + 'Creator' => 'Ersteller', + 'CreatorAddress' => 'Ersteller - Adresse', + 'CreatorAppID' => { + Description => 'Ersteller App ID', + PrintConv => { + 'Unknown' => 'Unbekannt', + }, + }, + 'CreatorAppVersion' => 'Ersteller App Version', + 'CreatorApplication' => 'Ersteller der Applikation', + 'CreatorCity' => 'Ersteller - Ort', + 'CreatorContactInfo' => 'Ersteller Kontaktinfo', + 'CreatorCountry' => 'Ersteller - Land', + 'CreatorPostalCode' => 'Ersteller - PLZ', + 'CreatorRegion' => 'Ersteller - Bundesland/Kanton', + 'CreatorTool' => 'Erstellertool', + 'CreatorWorkEmail' => 'Ersteller - E-Mail', + 'CreatorWorkTelephone' => 'Ersteller - Telefon', + 'CreatorWorkURL' => 'Ersteller - Webseite(n)', + 'Credit' => 'Anbieter', + 'CropActive' => { + Description => 'Ausschnitt aktiviert', + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'CropAngle' => 'Ausschnitt Winkel', + 'CropAspectRatio' => { + Description => 'Ausschnitt Bildformat', + PrintConv => { + 'A-size Landscape' => 'DIN A Querformat', + 'A-size Portrait' => 'DIN A Hochformat', + 'Circle' => 'Kreis', + 'Custom' => 'Benutzerdefiniert', + 'Free' => 'Frei', + 'Letter-size Landscape' => 'Letter Querformat', + 'Letter-size Portrait' => 'Letter Querformat', + }, + }, + 'CropBottom' => 'Ausschnitt Unten', + 'CropBottomMargin' => 'Ausschnitt Rand Unten', + 'CropCircleActive' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'CropHeight' => 'Ausschnitt Höhe', + 'CropHiSpeed' => 'Ausschnitt Highspeed', + 'CropLeft' => 'Ausschnitt Links', + 'CropLeftMargin' => 'Ausschnitt Rand Links', + 'CropRight' => 'Ausschnitt Rechts', + 'CropRightMargin' => 'Ausschnitt Rand Rechts', + 'CropRotation' => 'Ausschnitt Drehung', + 'CropTop' => 'Ausschnitt Oben', + 'CropTopMargin' => 'Ausschnitt Rand Oben', + 'CropUnit' => { + Description => 'Ausschnitt Einheit', + PrintConv => { + 'inches' => 'Zoll', + 'pixels' => 'Pixel', + }, + }, + 'CropUnits' => { + Description => 'Ausschnitt Einheiten', + PrintConv => { + 'inches' => 'Zoll', + 'pixels' => 'Pixel', + }, + }, + 'CropWidth' => 'Ausschnitt Breite', + 'CroppedImageHeight' => 'Ausschnitt Bildhöhe', + 'CroppedImageLeft' => 'Ausschnitt Bildanfang Links', + 'CroppedImageTop' => 'Ausschnitt Bildanfang Oben', + 'CroppedImageWidth' => 'Ausschnitt Bildbreite', + 'CurrentICCProfile' => 'Aktuelles ICC-Profile', + 'CurrentIPTCDigest' => 'Aktueller IPTC Kennwert', + 'Curves' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Custom1' => 'Benutzerdefiniert 1', + 'Custom2' => 'Benutzerdefiniert 2', + 'Custom3' => 'Benutzerdefiniert 3', + 'Custom4' => 'Benutzerdefiniert 4', + 'CustomLinear' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'CustomPictureStyleFileName' => 'Benutzer-Bildstil Dateiname', + 'CustomRendered' => { + Description => 'Benutzerdefinierte Bildverarbeitung', + PrintConv => { + 'Custom' => 'Benutzerdefinierter Prozess', + 'Normal' => 'Standard-Prozess', + }, + }, + 'D-LightingHQ' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'D-LightingHQSelected' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'D-LightingHS' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'D-RangeOptimizerMode' => { + PrintConv => { + 'Manual' => 'Manuell', + 'Off' => 'Aus', + }, + }, + 'DECPosition' => { + Description => 'DEC-Position', + PrintConv => { + 'Contrast' => 'Kontrast', + 'Exposure' => 'Belichtung', + 'Saturation' => 'Sättigung', + }, + }, + 'DLOOn' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'DNGBackwardVersion' => 'DNG Versionskompatibilität', + 'DNGLensInfo' => 'DNG Objektiv-Informationen', + 'DNGVersion' => 'DNG-Version', + 'DOF' => 'Schärfentiefe', + 'DSPFirmwareVersion' => 'DSP-Firmware-Version', + 'DataCompressionMethod' => 'Daten Komprimierungsmethode', + 'DataDump' => 'Daten-Dump', + 'DataDump2' => 'Daten-Dump 2', + 'DataImprint' => { + Description => 'Daten-Einblendung', + PrintConv => { + 'MM/DD/HH:MM' => 'MM/TT/SS:MM', + 'None' => 'Keine', + 'YYYY/MM/DD' => 'JJJJ/MM/TT', + }, + }, + 'DataLength' => 'Datenlänge', + 'Date' => 'Datum', + 'DateCreated' => 'Erstellungsdatum', + 'DateDisplayFormat' => { + Description => 'Datumsformat', + PrintConv => { + 'D/M/Y' => 'Tag/Monat/Jahr', + 'M/D/Y' => 'Monat/Tag/Jahr', + 'Y/M/D' => 'Jahr/Monat/Tag', + }, + }, + 'DateSent' => 'Absendedatum', + 'DateStampMode' => { + Description => 'Zeitstempel-Modus', + PrintConv => { + 'Date' => 'Datum', + 'Off' => 'Aus', + }, + }, + 'DateTime' => 'Änderungsdatum', + 'DateTimeCreated' => 'Erstellungsdatum/-uhrzeit', + 'DateTimeDigitized' => 'Datum/Uhrzeit der Digitalisierung', + 'DateTimeOriginal' => 'Erstellungsdatum/-uhrzeit', + 'DateTimeStamp' => 'Datum Uhrzeitangabe', + 'DaylightSavings' => { + Description => 'Sommerzeit', + PrintConv => { + 'No' => 'Aus', + 'Yes' => 'Ein', + }, + }, + 'Declination' => 'Deklination', + 'DefaultCropOrigin' => 'System-Ausschnitt Beginn', + 'DefaultCropSize' => 'System-Ausschnitt Größe', + 'DefaultDisplayHeight' => 'Default Anzeigehöhe', + 'DefaultDisplayWidth' => 'Default Anzeigebreite', + 'DefaultEraseOption' => { + Description => 'System-Löscheinstellung', + PrintConv => { + 'Cancel selected' => 'Abbruch', + 'Erase selected' => 'Löschen', + }, + }, + 'DefaultImageColor' => 'System-Bildfarbe', + 'DefaultScale' => 'Systemmaß', + 'DeletedImageCount' => 'Anzahl gelöschter Bilder', + 'Description' => 'Beschreibung', + 'Destination' => 'Ziel', + 'DestinationCity' => 'Zielort', + 'DestinationCityCode' => 'Zielort-Code', + 'DestinationDST' => { + Description => 'Zielort Sommerzeit (DST)', + PrintConv => { + 'No' => 'Deaktiviert', + 'Yes' => 'Aktiviert', + }, + }, + 'DevelopmentDynamicRange' => 'Dynamikbereich Entwicklung', + 'DeviceAttributes' => 'Geräte-Eigenschaften', + 'DeviceManufacturer' => 'Gerätehersteller', + 'DeviceMfgDesc' => 'Gerätehersteller-Bezeichnung', + 'DeviceModel' => 'Geräte-Modell', + 'DeviceModelDesc' => 'Geräte-Modell-Bezeichnung', + 'DeviceSettingDescription' => 'Geräteeinstellung', + 'DialDirectionTvAv' => { + Description => 'Drehung Wählrad bei Tv/Av', + PrintConv => { + 'Reversed' => 'Umgekehrt', + }, + }, + 'DigitalCreationDate' => 'Digitalisierungsdatum', + 'DigitalCreationDateTime' => 'Digitalisierungsdatum/-uhrzeit', + 'DigitalCreationTime' => 'Digitalisierungszeit', + 'DigitalFilter01' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter02' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter03' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter04' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter05' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter06' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter07' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter08' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter09' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter10' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter11' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter12' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter13' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter14' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter15' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter16' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter17' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter18' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter19' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalFilter20' => { + PrintConv => { + 'Color Filter' => 'Farbfilter', + 'Fisheye' => 'Fischauge', + 'High Contrast' => 'Hoher Kontrast', + }, + }, + 'DigitalGain' => 'Digitale Verstärkung', + 'DigitalZoom' => { + Description => 'Digital-Zoom', + PrintConv => { + 'Electronic magnification' => 'Elektronische Vergrößerung', + 'None' => 'Kein', + 'Off' => 'Aus', + 'Other' => 'Unbekannt', + }, + }, + 'DigitalZoomOn' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'DigitalZoomRatio' => 'Digitaler Zoom-Faktor', + 'Directory' => 'Verzeichnis', + 'DirectoryIndex' => 'Verzeichnis-Index', + 'DirectoryNumber' => 'Ordner-Nummer', + 'Disclaimer' => 'Haftungsbeschränkung', + 'DisplayAllAFPoints' => { + Description => 'Anzeige aller AF-Punkte', + PrintConv => { + 'Disable' => 'Deaktiviert', + 'Enable' => 'Aktiviert', + }, + }, + 'DisplayAperture' => 'Angezeigte Blende', + 'DisplayHeight' => 'Anzeigehöhe', + 'DisplayUnit' => { + PrintConv => { + 'Pixels' => 'Pixel', + 'inches' => 'Zoll', + }, + }, + 'DisplayUnits' => { + Description => 'Anzeigeeinheit', + PrintConv => { + 'inches' => 'Zoll', + 'meters' => 'Meter', + }, + }, + 'DisplayXResolutionUnit' => { + PrintConv => { + 'um' => 'µm (Mikrometer)', + }, + }, + 'DisplayYResolutionUnit' => { + PrintConv => { + 'um' => 'µm (Mikrometer)', + }, + }, + 'DisplayedUnitsX' => { + Description => 'Einheit der horiz. Auflösung', + PrintConv => { + 'inches' => 'Zoll', + }, + }, + 'DisplayedUnitsY' => { + Description => 'Einheit der vert. Auflösung', + PrintConv => { + 'inches' => 'Zoll', + }, + }, + 'DistortionControl' => { + Description => 'Verzeichnungskontrolle', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'DistortionCorrection' => { + Description => 'Verzeichnungskorrektur', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'DistortionCorrection2' => { + Description => 'Verzeichnungskorrektur 2', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'DistortionCorrectionOn' => 'Verzeichnungskorrektur Ein', + 'DistortionN' => 'Verzeichnung N', + 'DistortionParam02' => 'Verzeichnungsparameter 02', + 'DistortionParam04' => 'Verzeichnungsparameter 04', + 'DistortionParam08' => 'Verzeichnungsparameter 08', + 'DistortionParam09' => 'Verzeichnungsparameter 09', + 'DistortionParam11' => 'Verzeichnungsparameter 11', + 'DistortionScale' => 'Verzeichnungsausmaß', + 'DistortionVersion' => 'Verzeichnung Version', + 'DjVuVersion' => 'DjVu-Version', + 'DocSecurity' => { + Description => 'Dokumentensicherheit', + PrintConv => { + 'Locked for annotations' => 'Gesperrt für Anmerkungen', + 'None' => 'Keine', + 'Password protected' => 'Passwort geschützt', + 'Read-only enforced' => 'Nur Lesen - erzwungen', + 'Read-only recommended' => 'Nur Lesen - vorgeschlagen', + }, + }, + 'Document' => 'Dokument', + 'DocumentHistory' => 'Historie des Dokuments', + 'DocumentName' => 'Dokumentenname', + 'DocumentNotes' => 'Notizen zum Dokument', + 'DriveMode' => { + Description => 'Aufnahmeart', + PrintConv => { + '10 s Timer' => 'Selbstauslöser 10 s', + '2 s Timer' => 'Selbstauslöser 2 s', + 'Auto Bracket' => 'Belichtungsreihe', + 'Bracketing' => 'Belichtungsreihe', + 'Continuous' => 'Serienaufnahme', + 'Continuous (Lo)' => 'Serienaufnahme (Niedrig)', + 'Continuous Bracketing' => 'Serienbild-Belichtungsreihe', + 'Continuous Exposure Bracketing' => 'Serienaufnahme Belichtungsreihe', + 'Continuous High' => 'Serienaufnahme (Hi)', + 'Continuous Low' => 'Serienaufnahme Niedrig', + 'Continuous Shooting' => 'Serienaufnahme', + 'HS continuous' => 'High-Speed Serienbild', + 'Mirror Lock-up' => 'Spiegel hochgeklappt', + 'Multi Shot' => 'Serienaufnahme', + 'Multiple Exposure' => 'Mehrfachbelichtung', + 'No Timer' => 'Ohne Selbstauslöser', + 'Off' => 'Aus', + 'Remote Control' => 'Fernauslöser', + 'Remote Control (3 s delay)' => 'Fernauslöser (3 Sek. Verzögerung)', + 'Self-Timer 2 sec' => 'Selbstauslöser 2 s', + 'Self-timer' => 'Selbstauslöser', + 'Self-timer (12 s)' => 'Selbstauslöser (12 Sek.)', + 'Self-timer (2 s)' => 'Selbstauslöser (2 Sek.)', + 'Self-timer 10 sec' => 'Selbstauslöser 10 s', + 'Self-timer 2 sec, Mirror Lock-up' => 'Selbstauslöser 2 s, Spiegel hochgeklappt', + 'Self-timer Operation' => 'Selbstauslöser', + 'Shutter Button' => 'Kamera-Auslöser', + 'Single' => 'Einzelbild', + 'Single Exposure' => 'Einzelbelichtung', + 'Single Frame' => 'Einzelbild', + 'Single Shot' => 'Einzelbild', + 'Single-Frame Bracketing' => 'Einzelbild-Belichtungsreihe', + 'Single-frame' => 'Einzelbild', + 'Single-frame Bracketing' => 'Einzelbild-Belichtungsreihe', + 'Single-frame Exposure Bracketing' => 'Einzelbild Belichtungsreihe', + 'Single-frame Shooting' => 'Einzelbild', + 'UHS continuous' => 'Ultra High-Speed Serienbild', + 'White Balance Bracketing' => 'Weißabgleichs-Belichtungsreihe', + 'White Balance Bracketing High' => 'Weißabgleich-Belichtungsreihe Hoch', + 'White Balance Bracketing Low' => 'Weißabgleich-Belichtungsreihe Niedrig', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'DriveMode2' => { + Description => 'Mehrfachbelichtung', + PrintConv => { + 'Continous Bracketing Low' => 'Serienaufnahme Belichtungsreihe Niedrig', + 'Continuous' => 'Serienaufnahme', + 'Continuous (Lo)' => 'Serienaufnahme (Niedrig)', + 'Continuous Bracketing' => 'Serienaufnahme Belichtungsreihe', + 'Continuous Bracketing 0.3 EV' => 'Serienaufnahme Belichtungsreihe 0.3 EV', + 'Continuous Bracketing 0.7 EV' => 'Serienaufnahme Belichtungsreihe 0.7 EV', + 'Continuous Bracketing High' => 'Serienaufnahme Belichtungsreihe Hoch', + 'Continuous High' => 'Serienaufnahme Hoch', + 'Continuous Low' => 'Serienaufnahme Niedrig', + 'Exposure Bracket' => 'Belichtungsreihe', + 'Mirror Lock-up' => 'Spiegel hochgeklappt', + 'Multiple Exposure' => 'Mehrfachbelichtung', + 'Remote Control' => 'Fernauslöser', + 'Remote Control (3 s delay)' => 'Fernauslöser (3 s verzögert)', + 'Self-timer (12 s)' => 'Selbstauslöser (12 s)', + 'Self-timer (2 s)' => 'Selbstauslöser (2 s)', + 'Self-timer 10 sec' => 'Selbstauslöser 10 s', + 'Self-timer 2 sec' => 'Selbstauslöser 2 s', + 'Self-timer 2 sec, Mirror Lock-up' => 'Selbstauslöser 2 s, Spiegel hochgeklappt', + 'Single Frame' => 'Einzelbild', + 'Single-frame' => 'Einzelbildaufnahme', + 'Single-frame Bracketing' => 'Einzelbild Belichtungsreihe', + 'Single-frame Bracketing High' => 'Einzelbild Belichtungsreihe Hoch', + 'Single-frame Bracketing Low' => 'Einzelbild Belichtungsreihe Niedrig', + 'White Balance Bracketing High' => 'Weißabgleich Belichtungsreihe Hoch', + 'White Balance Bracketing Low' => 'Weißabgleich Belichtungsreihe Niedrig', + }, + }, + 'DriveModeSetting' => { + Description => 'Aufnahmeart Einstellung', + PrintConv => { + 'Continuous High' => 'Serienaufnahme Hoch', + 'Continuous Low' => 'Serienaufnahme Niedrig', + 'Self-timer 10 sec' => 'Selbstauslöser 10 s', + 'Self-timer 2 sec, Mirror Lock-up' => 'Selbstauslöser 2 s, Spiegel hochgeklappt', + 'Single Frame' => 'Einzelbild', + }, + }, + 'DriveType' => { + PrintConv => { + 'Unknown' => 'Unbekannt', + }, + }, + 'Duration' => 'Dauer', + 'DustRemovalData' => 'Dunstentfernungsdaten', + 'DynamicAFArea' => { + Description => 'Dynamisches AF-Messfeld', + PrintConv => { + '21 Points' => '21 Messfelder', + '51 Points' => '51 Messfelder', + '51 Points (3D-tracking)' => '51 Messfelder (3D-Tracking)', + '9 Points' => '9 Messfelder', + }, + }, + 'DynamicRange' => { + Description => 'Dynamikbereich', + PrintConv => { + 'Wide' => 'Weit', + }, + }, + 'DynamicRangeExpansion' => { + Description => 'Dynamikbereich-Erweiterung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'DynamicRangeOptimizer' => { + Description => 'Dynamikbereich-Optimierung', + PrintConv => { + 'Advanced Auto' => 'Erw. Automatik', + 'Advanced Lv1' => 'Erw. Stufe 1', + 'Advanced Lv2' => 'Erw. Stufe 2', + 'Advanced Lv3' => 'Erw. Stufe 3', + 'Advanced Lv4' => 'Erw. Stufe 4', + 'Advanced Lv5' => 'Erw. Stufe 5', + 'Auto' => 'Automatisch', + 'Off' => 'Aus', + }, + }, + 'DynamicRangeOptimizerBracket' => { + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Niedrig', + 'Off' => 'Aus', + }, + }, + 'DynamicRangeOptimizerMode' => { + PrintConv => { + 'Off' => 'Aus', + }, + }, + 'DynamicRangeSetting' => { + Description => 'Dynamikbereich-Einstellungen', + PrintConv => { + 'Film Simulation' => 'Film-Simulation', + 'Wide1 (230%)' => 'Weit1 (230%)', + 'Wide2 (400%)' => 'Weit2 (400%)', + }, + }, + 'E-DialInProgram' => { + PrintConv => { + 'Tv or Av' => 'Tv oder Av', + }, + }, + 'ETTLII' => { + PrintConv => { + 'Average' => 'Integralmessung', + 'Evaluative' => 'Mehrfeldmessung', + }, + }, + 'EVStepInfo' => 'EV-Schritte Information', + 'EVStepSize' => { + Description => 'Belichtungswerte', + PrintConv => { + '1/2 EV' => '1/2 LW', + '1/3 EV' => '1/3 LW', + }, + }, + 'EVSteps' => { + Description => 'LW-Schritte', + PrintConv => { + '1/2 EV Steps' => '1/2 LW-Schritte', + '1/3 EV Steps' => '1/3 LW-Schritte', + }, + }, + 'EasyExposureComp' => 'Easy Belichtungskorrektur', + 'EasyExposureCompensation' => { + Description => 'Easy Belichtungskorrektur', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + 'On (auto reset)' => 'Einstellrad (Reset)', + }, + }, + 'EasyMode' => { + Description => 'Easy-Modus', + PrintConv => { + 'Beach' => 'Strand', + 'Black & White' => 'Schwarz/Weiß', + 'Blur Reduction' => 'Unschärfereduktion', + 'Color Accent' => 'Farbton', + 'Color Swap' => 'Farbwechsel', + 'Digital Macro' => 'Digitales Makro', + 'Fireworks' => 'Feuerwerk', + 'Foliage' => 'Laub', + 'Full auto' => 'Vollautomatisch', + 'Indoor' => 'Innenaufnahme', + 'Kids & Pets' => 'Kinder & Tiere', + 'Landscape' => 'Landschaft', + 'Live View Control' => 'Live View Kontrolle', + 'Low Light' => 'Wenig Licht', + 'Low Light 2' => 'Wenig Licht 2', + 'Macro' => 'Makro', + 'Manual' => 'Manuell', + 'Night' => 'Nachtszene', + 'Night Scene' => 'Nachtszene', + 'Night Snapshot' => 'Nacht Schnappschuss', + 'Pan focus' => 'Pan Fokus', + 'Portrait' => 'Porträt', + 'Snow' => 'Schnee', + 'Sports' => 'Sport', + 'Sunset' => 'Sonnenuntergang', + 'Super Macro' => 'Super-Makro', + 'Super Macro 2' => 'Super Makro 2', + 'Underwater' => 'Unterwasser', + 'Zoom Blur' => 'Zoom Unschärfe', + }, + }, + 'EdgeNoiseReduction' => { + Description => 'Rauschunterdrückung Ecken', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'EditStatus' => 'Bearbeitungsstatus', + 'Edition' => 'Ausgabe', + 'EditorialUpdate' => 'Redaktionelle Überarbeitung', + 'EffectiveLV' => 'Effektiver LW', + 'EffectiveMaxAperture' => 'Effektiv größte Blende', + 'Email' => 'E-Mail', + 'EmbeddedImage' => 'Eingebettetes Bild', + 'EmbeddedImageByteOrder' => 'Eingebettetes Bild Bytereihenfolge', + 'EmbeddedImageHeight' => 'Eingebettetes Bild Bildhöhe', + 'EmbeddedImageType' => 'Eingebettetes Bild Bildtyp', + 'EmbeddedImageWidth' => 'Eingebettetes Bild Bildbreite', + 'Emphasis' => { + PrintConv => { + 'None' => 'Keine', + 'reserved' => 'reserviert', + }, + }, + 'Encoding' => { + PrintConv => { + 'Unknown -' => 'Unbekannt -', + }, + }, + 'EncodingProcess' => 'JPEG-Kodierung Prozess', + 'Encryption' => 'Verschlüsselung', + 'EnhanceDarkTones' => { + Description => 'Dunkle Stellen aufhellen', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Enhancement' => { + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'Off' => 'Aus', + 'Red' => 'Rot', + 'Underwater' => 'Unterwasser', + }, + }, + 'EnvelopePriority' => { + Description => 'Priorität', + PrintConv => { + '0 (reserved)' => '0 (reserviert)', + '1 (most urgent)' => '1 (sehr dringend)', + '5 (normal urgency)' => '5 (normale Dringlichkeit)', + '8 (least urgent)' => '8 (geringe Wichtigkeit)', + '9 (user-defined priority)' => '9 (benutzerdefinierte Priorität)', + }, + }, + 'EnvelopeRecordVersion' => 'IPTC-Modell-1-Version', + 'EpsonImageHeight' => 'Epson-Bildhöhe', + 'EpsonImageWidth' => 'Epson-Bildbreite', + 'Equipment' => 'Equipment-IFD-Zeiger', + 'EquipmentVersion' => 'Equipment-Version', + 'Error' => 'Fehler', + 'ExifByteOrder' => 'Exif Byte-Reihenfolge', + 'ExifCameraInfo' => 'Exif Kamerainformationen', + 'ExifImageHeight' => 'Exif-Bildhöhe', + 'ExifImageWidth' => 'Exif-Bildbreite', + 'ExifOffset' => 'Exif IFD-Zeiger', + 'ExifToolVersion' => 'ExifTool-Version', + 'ExifUnicodeByteOrder' => 'Exif Unicode Byte-Reihenfolge', + 'ExifVersion' => 'Exif-Version', + 'ExitPupilPosition' => 'Austrittspupillenposition', + 'ExpandFilm' => 'Erweitert Film', + 'ExpandFilterLens' => 'Erweitert Filterlinse', + 'ExpandFlashLamp' => 'Erweitert Blitzlicht', + 'ExpandLens' => 'Erweitert Objektiv', + 'ExpandScanner' => 'Erweitert Scanner', + 'ExpandSoftware' => 'Erweitert Software', + 'ExpirationDate' => 'Ablaufdatum', + 'ExpirationTime' => 'Ablaufzeit', + 'Exposure' => 'Belichtung', + 'Exposure2012' => 'Belichtung 2012', + 'ExposureAdj' => 'Belichtungskorrektur', + 'ExposureAdj2' => 'Belichtungskorrektur 2', + 'ExposureAdjust' => 'Belichtungskorrektur', + 'ExposureBias' => 'Belichtungskorrekturwert', + 'ExposureBracketShotNumber' => 'Belichtungsreihen-Bildnummer', + 'ExposureBracketStepSize' => 'Belichtungsreihen-Stufenabstand', + 'ExposureBracketValue' => 'Belichtungsreihenwert', + 'ExposureCompStepSize' => { + Description => 'Belichtungskorrekturstufe', + PrintConv => { + '1 EV' => '1 LW', + '1/2 EV' => '1/2 LW', + '1/3 EV' => '1/3 LW', + }, + }, + 'ExposureCompensation' => 'Belichtungskorrektur', + 'ExposureCompensation2' => 'Belichtungskorrektur 2', + 'ExposureCompensationMode' => 'Belichtungskorrekturmodus', + 'ExposureControlStepSize' => { + Description => 'Belichtungswert', + PrintConv => { + '1 EV' => '1 LW', + '1/2 EV' => '1/2 LW', + '1/3 EV' => '1/3 LW', + }, + }, + 'ExposureDelayMode' => { + Description => 'Spiegelvorauslösung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ExposureDifference' => 'Belichtungsabweichung', + 'ExposureIndex' => 'Belichtungsindex', + 'ExposureLevelIncrements' => { + Description => 'Belichtungswert', + PrintConv => { + '1-stop set, 1/3-stop comp.' => '1-Blende, 1/3-Blendenkompensation', + '1/2 Stop' => '1/2 LW', + '1/2-stop set, 1/2-stop comp.' => '1/2-Blende, 1/2-Blendenkompensation', + '1/3 Stop' => '1/3 LW', + '1/3-stop set, 1/3-stop comp.' => '1/3-Blende, 1/3-Blendenkompensation', + }, + }, + 'ExposureMode' => { + Description => 'Belichtungsmodus', + PrintConv => { + 'Anti Motion Blur' => 'Verwackelungsschutz', + 'Aperture Priority' => 'Blendenpriorität', + 'Aperture-priority AE' => 'Blendenpriorität', + 'Auto' => 'Automatische Belichtung', + 'Auto bracket' => 'Belichtungsreihe', + 'Auto?' => 'Automatisch?', + 'Backlight Correction HDR' => 'Hintergrundbeleuchtung HDR Korrektur', + 'Beach' => 'Strand', + 'Bulb' => 'Bulb-Modus', + 'Fireworks' => 'Feuerwerk', + 'Food' => 'Lebensmittel', + 'High Sensitivity' => 'Hohe Empfindlichkeit', + 'Landscape' => 'Landschaft', + 'Macro' => 'Makro', + 'Manual' => 'Manuelle Belichtung', + 'Night Scene / Twilight' => 'Nachtszene / Dämmerung', + 'Night View/Portrait' => 'Abendszene/Porträt', + 'Pet' => 'Haustiere', + 'Portrait' => 'Porträt', + 'Program' => 'Programmautomatik', + 'Program AE' => 'Programmautomatik', + 'Program-shift' => 'Programm-Shift', + 'Program-shift A' => 'Programmverschiebung A', + 'Program-shift S' => 'Programmverschiebung S', + 'Shutter Priority' => 'Verschlusspriorität', + 'Shutter speed priority AE' => 'Verschlusspriorität', + 'Snow' => 'Schnee', + 'Sports' => 'Sport', + 'Sunset' => 'Sonnenuntergang', + 'Twilight Portrait' => 'Dämmerung Portät', + 'Underwater' => 'Unterwasser', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ExposureModeInManual' => { + Description => 'Belichtungsmodus bei manueller Belichtung', + PrintConv => { + 'Center-weighted average' => 'Mittenbetont', + 'Evaluative metering' => 'Mehrfeldmessung', + 'Partial metering' => 'Teilbild', + 'Specified metering mode' => 'Spezifizierte Messmethode', + 'Spot metering' => 'Spotmessung', + }, + }, + 'ExposureProgram' => { + Description => 'Belichtungsprogramm', + PrintConv => { + 'Action (High speed)' => 'Kreativ-Programm (ausgerichtet auf schnelle Verschlussgeschwindigkeit)', + 'Anti Motion Blur' => 'Verwackelungsschutz', + 'Aperture Priority' => 'Blendenpriorität', + 'Aperture-priority AE' => 'Blendenpriorität', + 'Creative (Slow speed)' => 'Kreativ-Programm (ausgerichtet auf Schärfentiefe)', + 'Landscape' => 'Landschaft', + 'Macro' => 'Makro', + 'Manual' => 'Manuell', + 'Night Portrait' => 'Nachtporträt', + 'Night view' => 'Abendszene', + 'Night view/portrait' => 'Abendszene/Porträt', + 'Not Defined' => 'Nicht definiert', + 'Portrait' => 'Porträt', + 'Program' => 'Programmautomatik', + 'Program AE' => 'Programmautomatik', + 'Shutter Priority' => 'Verschlusspriorität', + 'Shutter speed priority AE' => 'Verschlusspriorität', + 'Sports' => 'Sport', + 'Sunset' => 'Sonnenuntergang', + }, + }, + 'ExposureTime' => 'Belichtungsdauer', + 'ExposureTime2' => 'Belichtungsdauer 2', + 'ExposureValue' => 'Belichtungsdauer', + 'ExposureWarning' => { + Description => 'Belichtungswarnung', + PrintConv => { + 'Bad exposure' => 'Schlechte Belichtung', + 'Good' => 'OK', + }, + }, + 'ExtendedWBDetect' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Extender' => { + Description => 'Konverter', + PrintConv => { + 'None' => 'Keiner', + }, + }, + 'ExtenderFirmwareVersion' => 'Konverter-Firmware-Version', + 'ExtenderMake' => 'Konverterhersteller', + 'ExtenderModel' => 'Konverter-Modell', + 'ExtenderSerialNumber' => 'Konverter-Seriennummer', + 'ExtenderStatus' => { + Description => 'Status Telekonverter', + PrintConv => { + 'Attached' => 'Angesetzt', + 'Not attached' => 'Nicht angesetzt', + 'Removed' => 'Entfernt', + }, + }, + 'ExternalFlash' => { + Description => 'Externer Blitz', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ExternalFlashAE1' => 'Externer Blitz AE1', + 'ExternalFlashAE1_0' => 'Externer Blitz AE1 0', + 'ExternalFlashAE2' => 'Externer Blitz AE2', + 'ExternalFlashAE2_0' => 'Externer Blitz AE2 0', + 'ExternalFlashBounce' => { + Description => 'Externer Blitz - Bounce', + PrintConv => { + 'Bounce' => 'Mit Bounce', + 'Direct' => 'Direkt', + 'No' => 'Nein', + 'Yes' => 'Ja', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ExternalFlashCompensation' => 'Externe Blitzbelichtungskorrektur', + 'ExternalFlashExposureComp' => { + Description => 'Belichtungskorrektur des externen Blitzgeräts', + PrintConv => { + '-0.5' => '-0.5 LW', + '-1.0' => '-1.0 LW', + '-1.5' => '-1.5 LW', + '-2.0' => '-2.0 LW', + '-2.5' => '-2.5 LW', + '-3.0' => '-3.0 LW', + '0.0' => '0.0 LW', + '0.5' => '0.5 LW', + '1.0' => '1.0 LW', + 'n/a' => 'Nicht gesetzt (Aus oder Auto-Modi)', + 'n/a (Manual Mode)' => 'Nicht gesetzt (Manueller Modus)', + }, + }, + 'ExternalFlashFirmware' => { + Description => 'Externer Blitz Firmware', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ExternalFlashFlags' => { + Description => 'Externer Blitz Flags', + PrintConv => { + 'Fired' => 'Ausgelöst', + }, + }, + 'ExternalFlashGValue' => 'Externer Blitz Leitzahl', + 'ExternalFlashGuideNumber' => 'Leitzahl des externen Blitzgeräts', + 'ExternalFlashMode' => { + Description => 'Slave-Blitz-Messfeld 3', + PrintConv => { + 'Off' => 'Aus', + 'On, Auto' => 'Ein, Auto', + 'On, Contrast-control Sync' => 'Ein, Kontrast-Steuerungs-Synchronisation', + 'On, Flash Problem' => 'Ein, Blitzproblem?', + 'On, High-speed Sync' => 'Ein, High-Speed-Synchronisation', + 'On, Manual' => 'Ein, Manuell', + 'On, P-TTL Auto' => 'Ein, P-TTL-Blitzautomatik', + 'On, Wireless' => 'Ein, Drahtlos', + 'On, Wireless, High-speed Sync' => 'Ein, Drahtlos, High-Speed-Synchronisation', + 'n/a - Off-Auto-Aperture' => 'K/A - Blendenring nicht auf A', + }, + }, + 'ExternalFlashZoom' => 'Externer Blitz-Zoom', + 'ExternalSensorBrightnessValue' => 'Externer Sensor Helligkeitswert', + 'ExtraSamples' => 'Zusätzliche Komponenten', + 'FEMicroadjustment' => { + Description => 'FE Feinabstimmung', + PrintConv => { + 'Disable' => 'Deaktiviert', + 'Enable' => 'Aktiviert', + }, + }, + 'FNumber' => 'F-Wert', + 'FOV' => 'Sichtfeld', + 'Face0Position' => 'Position, 0. Gesicht', + 'Face10Position' => 'Position, 10. Gesicht', + 'Face10Size' => 'Größe, 10. Gesicht', + 'Face11Position' => 'Position, 11. Gesicht', + 'Face11Size' => 'Größe, 11. Gesicht', + 'Face12Position' => 'Position, 12. Gesicht', + 'Face12Size' => 'Größe, 12. Gesicht', + 'Face13Position' => 'Position, 13. Gesicht', + 'Face13Size' => 'Größe, 13. Gesicht', + 'Face14Position' => 'Position, 14. Gesicht', + 'Face14Size' => 'Größe, 14. Gesicht', + 'Face15Position' => 'Position, 15. Gesicht', + 'Face15Size' => 'Größe, 15. Gesicht', + 'Face16Position' => 'Position, 16. Gesicht', + 'Face16Size' => 'Größe, 16. Gesicht', + 'Face17Position' => 'Position, 17. Gesicht', + 'Face17Size' => 'Größe, 17. Gesicht', + 'Face18Position' => 'Position, 18. Gesicht', + 'Face18Size' => 'Größe, 18. Gesicht', + 'Face19Position' => 'Position, 19. Gesicht', + 'Face19Size' => 'Größe, 19. Gesicht', + 'Face1Position' => 'Position, 1. Gesicht', + 'Face1Size' => 'Größe, 1. Gesicht', + 'Face20Position' => 'Position, 20. Gesicht', + 'Face20Size' => 'Größe, 20. Gesicht', + 'Face21Position' => 'Position, 21. Gesicht', + 'Face21Size' => 'Größe, 21. Gesicht', + 'Face22Position' => 'Position, 22. Gesicht', + 'Face22Size' => 'Größe, 22. Gesicht', + 'Face23Position' => 'Position, 23. Gesicht', + 'Face23Size' => 'Größe, 23. Gesicht', + 'Face24Position' => 'Position, 24. Gesicht', + 'Face24Size' => 'Größe, 24. Gesicht', + 'Face25Position' => 'Position, 25. Gesicht', + 'Face25Size' => 'Größe, 25. Gesicht', + 'Face26Position' => 'Position, 26. Gesicht', + 'Face26Size' => 'Größe, 26. Gesicht', + 'Face27Position' => 'Position, 27. Gesicht', + 'Face27Size' => 'Größe, 27. Gesicht', + 'Face28Position' => 'Position, 28. Gesicht', + 'Face28Size' => 'Größe, 28. Gesicht', + 'Face29Position' => 'Position, 29. Gesicht', + 'Face29Size' => 'Größe, 29. Gesicht', + 'Face2Position' => 'Position, 2. Gesicht', + 'Face2Size' => 'Größe, 2. Gesicht', + 'Face30Position' => 'Position, 30. Gesicht', + 'Face30Size' => 'Größe, 30. Gesicht', + 'Face31Position' => 'Position, 31. Gesicht', + 'Face31Size' => 'Größe, 31. Gesicht', + 'Face32Position' => 'Position, 32. Gesicht', + 'Face32Size' => 'Größe, 32. Gesicht', + 'Face3Position' => 'Position, 3. Gesicht', + 'Face3Size' => 'Größe, 3. Gesicht', + 'Face4Position' => 'Position, 4. Gesicht', + 'Face4Size' => 'Größe, 4. Gesicht', + 'Face5Position' => 'Position, 5. Gesicht', + 'Face5Size' => 'Größe, 5. Gesicht', + 'Face6Position' => 'Position, 6. Gesicht', + 'Face6Size' => 'Größe, 6. Gesicht', + 'Face7Position' => 'Position, 7. Gesicht', + 'Face7Size' => 'Größe, 7. Gesicht', + 'Face8Position' => 'Position, 8. Gesicht', + 'Face8Size' => 'Größe, 8. Gesicht', + 'Face9Position' => 'Position, 9. Gesicht', + 'Face9Size' => 'Größe, 9. Gesicht', + 'FaceDetect' => { + Description => 'Gesichtserkennung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'FaceDetectArea' => 'Gesichtserkennung Bereich', + 'FaceDetectFrameSize' => 'Gesichtserkennung Bereichsgröße', + 'FaceInfoUnknown' => 'Gesichterinformation Unbekannt', + 'FaceOrientation' => { + Description => 'Ausrichtung Gesichtserkennung', + PrintConv => { + 'Rotate 180' => '180° gedreht', + 'Rotate 270 CW' => '90° gegen den Uhrzeigersinn', + 'Rotate 90 CW' => '90° im Uhrzeigersinn', + }, + }, + 'FacePositions' => 'Positionen der Gesichter', + 'FacesDetected' => { + Description => 'Gesichter erkannt', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FacesRecognized' => 'Gesichter erkannt', + 'FastSeek' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'FaxProfile' => { + Description => 'Faxprofil', + PrintConv => { + 'Unknown' => 'Unbekannt', + }, + }, + 'FaxRecvParams' => 'Fax-Empfangsparameter', + 'FaxRecvTime' => 'Fax-Empfangszeit', + 'FaxSubAddress' => 'Fax-Sub-Adresse', + 'FileAccessDate' => 'Datum/Uhrzeit des letzten Dateizugriffs', + 'FileCreateDate' => 'Datum/Uhrzeit der Dateierstellung', + 'FileDescription' => 'Dateibeschreibung', + 'FileExtension' => 'Dateiendung', + 'FileFormat' => 'Dateiformat', + 'FileIndex' => 'Datei-Index', + 'FileInfo' => 'Datei-Informationen', + 'FileInfoVersion' => 'Datei-Informationen-Version', + 'FileInodeChangeDate' => 'Datum/Uhrzeit der letzten Inode-Änderung', + 'FileModifyDate' => 'Datum/Uhrzeit der Dateiänderung', + 'FileName' => 'Dateiname', + 'FileNumber' => 'Dateinummer', + 'FileNumberMemory' => { + Description => 'Dateinummernspeicher', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'FileNumberSequence' => { + Description => 'Nummernspeicher', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'FilePermissions' => 'Dateiberechtigungen', + 'FileSequence' => 'Dateireihenfolge', + 'FileSize' => 'Dateigröße', + 'FileSource' => { + Description => 'Dateiquelle', + PrintConv => { + 'Digital Camera' => 'Digital-Kamera', + 'Film Scanner' => 'Film-Scanner', + 'Reflection Print Scanner' => 'Scanner', + 'Sigma Digital Camera' => 'Sigma Digital-Kamera', + }, + }, + 'FileType' => 'Dateityp', + 'FileTypeDescription' => 'Dateityp Beschreibung', + 'FileVersion' => 'Dateiformatversion', + 'Filename' => 'Dateiname', + 'FillFlashAutoReduction' => { + Description => 'E-TTL II-Automatikblitz-System', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'FilmMode' => { + Description => 'Film-Modus', + PrintConv => { + 'F1/Studio Portrait' => 'F1/Studio-Porträt', + 'F1a/Studio Portrait Enhanced Saturation' => 'F1a/Studio-Porträt Erweiterte Sättigung', + 'F1b/Studio Portrait Smooth Skin Tone (ASTIA)' => 'F1b/Studio-Porträt Weiche Hauttöne', + 'F1b/Studio Portrait Smooth Skin Tone (Astia)' => 'F1b/Studio-Porträt Weiche Hauttöne', + 'F1c/Studio Portrait Increased Sharpness' => 'F1c/Studio-Porträt Erhöhte Schärfe', + 'F3/Studio Portrait Ex' => 'F3/Studio Porträt Ex', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilmType' => 'Filmtyp', + 'FilterEffect' => { + Description => 'Filtereffekt', + PrintConv => { + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Off' => 'Aus', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilterEffectFaithful' => { + Description => 'Filtereffekt Natürlich', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilterEffectLandscape' => { + Description => 'Filtereffekt Landschaft', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilterEffectMonochrome' => { + Description => 'Filtereffekt Monochrom', + PrintConv => { + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilterEffectNeutral' => { + Description => 'Filtereffekt Neutral', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilterEffectPortrait' => { + Description => 'Filtereffekt Porträt', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilterEffectStandard' => { + Description => 'Filtereffekt Standard', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilterEffectUnknown' => { + Description => 'Filtereffekt Unbekannt', + PrintConv => { + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilterEffectUserDef1' => { + Description => 'Filtereffekt Benutzerdefiniert 1', + PrintConv => { + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilterEffectUserDef2' => { + Description => 'Filtereffekt Benutzerdefineirt 2', + PrintConv => { + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FilterEffectUserDef3' => { + Description => 'Filtereffekt Benutzerdefiniert 3', + PrintConv => { + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FinderDisplayDuringExposure' => { + Description => 'Sucheranzeige bei Belichtung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'FineSharpness' => { + Description => 'Detail-Schärfe', + PrintConv => { + 'Extra fine' => 'Extra', + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'FineTuneOptCenterWeighted' => 'Feinabstimmung Mittenbetonte Messung', + 'FineTuneOptMatrixMetering' => 'Feinabstimmung Matrixmessung', + 'FineTuneOptSpotMetering' => 'Feinabstimmung Spotmessung', + 'FirmwareRevision' => 'Firmware-Revision', + 'FirmwareRevision2' => 'Firmware-Revision 2', + 'FirmwareVersion' => 'Firmware-Version', + 'FixtureIdentifier' => 'Kennzeichnung', + 'Flash' => { + Description => 'Blitzmodus', + PrintConv => { + 'Auto, Did not fire' => 'Blitz wurde nicht ausgelöst, Automodus', + 'Auto, Did not fire, Red-eye reduction' => 'Blitz wurde nicht ausgelöst, Rote-Augen-Reduzierung', + 'Auto, Fired' => 'Blitz wurde ausgelöst, Automodus', + 'Auto, Fired, Red-eye reduction' => 'Blitz wurde ausgelöst, Automodus, Rote-Augen-Reduzierung', + 'Auto, Fired, Red-eye reduction, Return detected' => 'Blitz wurde ausgelöst, Automodus, Messblitz-Licht zurückgeworfen, Rote-Augen-Reduzierung', + 'Auto, Fired, Red-eye reduction, Return not detected' => 'Blitz wurde ausgelöst, Automodus, kein Messblitz-Licht zurückgeworfen, Rote-Augen-Reduzierung', + 'Auto, Fired, Return detected' => 'Blitz wurde ausgelöst, Automodus, Messblitz-Licht zurückgeworfen', + 'Auto, Fired, Return not detected' => 'Blitz wurde ausgelöst, Automodus, kein Messblitz-Licht zurückgeworfen', + 'Did not fire' => 'Blitz wurde nicht ausgelöst', + 'Fired' => 'Blitz wurde ausgelöst', + 'Fired, Red-eye reduction' => 'Blitz wurde ausgelöst, Rote-Augen-Reduzierung', + 'Fired, Red-eye reduction, Return detected' => 'Blitz wurde ausgelöst, Rote-Augen-Reduzierung, Messblitz-Licht zurückgeworfen', + 'Fired, Red-eye reduction, Return not detected' => 'Blitz wurde ausgelöst, Rote-Augen-Reduzierung, kein Messblitz-Licht zurückgeworfen', + 'Fired, Return detected' => 'Messblitz-Licht zurückgeworfen', + 'Fired, Return not detected' => 'Kein Messblitz-Licht zurückgeworfen', + 'No Flash' => 'Blitz wurde nicht ausgelöst', + 'No flash function' => 'Keine Blitzfunktion', + 'Off' => 'Aus', + 'Off, Did not fire' => 'Blitz wurde nicht ausgelöst, Blitz unterdrücken-Modus', + 'Off, Did not fire, Return not detected' => 'Deaktiviert, Blitz wurde nicht ausgelöst, kein Messblitz-Licht zurückgeworfen', + 'Off, No flash function' => 'Deaktiviert, Keine Blitzfunktion', + 'Off, Red-eye reduction' => 'Deaktiviert, Rote-Augen-Reduzierung', + 'On' => 'Ein', + 'On, Did not fire' => 'Ein, Blitz wurde nicht ausgelöst', + 'On, Fired' => 'Blitz wurde ausgelöst, Blitz erzwingen-Modus', + 'On, Red-eye reduction' => 'Blitz wurde ausgelöst, Blitz erzwingen-Modus, Rote-Augen-Reduzierung', + 'On, Red-eye reduction, Return detected' => 'Blitz wurde ausgelöst, Blitz erzwingen-Modus, Rote-Augen-Reduzierung, Messblitz-Licht zurückgeworfen', + 'On, Red-eye reduction, Return not detected' => 'Blitz wurde ausgelöst, Blitz erzwingen-Modus, Rote-Augen-Reduzierung, kein Messblitz-Licht zurückgeworfen', + 'On, Return detected' => 'Blitz wurde ausgelöst, Blitz erzwingen-Modus, Messblitz-Licht zurückgeworfen', + 'On, Return not detected' => 'Blitz wurde ausgelöst, Blitz erzwingen-Modus, kein Messblitz-Licht zurückgeworfen', + }, + }, + 'FlashAction2' => { + PrintConv => { + 'Did not fire' => 'Blitz wurde nicht ausgelöst', + 'Fired' => 'Blitz wurde ausgelöst', + }, + }, + 'FlashActivity' => 'Blitz-Leistung', + 'FlashBatteryLevel' => 'Blitz Batteriestatus', + 'FlashBias' => 'Blitzkorrektur', + 'FlashBits' => { + Description => 'Blitz-Details', + PrintConv => { + '2nd-curtain sync used' => 'Synchronisatiopn auf 2. Verschlußvorhang', + 'Built-in' => 'Integrierter Blitz', + 'External' => 'Extern', + 'FP sync enabled' => 'FP Synchronisation eingestellt', + 'FP sync used' => 'FP Synchronistaion', + 'Manual' => 'Manuell', + }, + }, + 'FlashColorFilter' => 'Blitz Farbfilter', + 'FlashCommanderMode' => { + Description => 'Master-Steuerung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'FlashCompensation' => 'Blitzbelichtungs-Korrektur', + 'FlashControlBuilt-in' => { + PrintConv => { + 'Commander Mode' => 'Master-Steuerung', + }, + }, + 'FlashControlMode' => { + Description => 'Blitzlichtsteuerungsmodus', + PrintConv => { + 'Auto Aperture' => 'Blendenautomatik (AA)', + 'Manual' => 'Manuell', + 'Off' => 'Aus', + 'Repeating Flash' => 'Stroboskopblitz', + }, + }, + 'FlashCurtain' => { + Description => 'Blitzsynchronisation auf Verschluß', + PrintConv => { + '1st' => '1. Vorhang', + '2nd' => '2. Vorhang', + }, + }, + 'FlashDefault' => { + Description => 'Systemblitz', + PrintConv => { + 'Fill Flash' => 'Aufhellblitz', + }, + }, + 'FlashDevice' => { + Description => 'Blitzgerät', + PrintConv => { + 'External' => 'Extern', + 'Internal' => 'Intern', + 'Internal + External' => 'Intern + Extern', + 'None' => 'Keines', + }, + }, + 'FlashDistance' => 'Blitzabstand', + 'FlashEnergy' => 'Blitzstärke', + 'FlashExposureBracketValue' => 'Blitzbelichtungsreihenwert', + 'FlashExposureComp' => 'Blitzbelichtungskorrektur', + 'FlashExposureComp2' => 'Blitzbelichtungskorrektur 2', + 'FlashExposureComp3' => 'Blitzbelichtungskorrektur 3', + 'FlashExposureComp4' => 'Blitzbelichtungskorrektur 4', + 'FlashExposureCompSet' => 'Eingestellte Blitz-Belichtungskorrektur', + 'FlashExposureCompSet2' => 'Blitzbelichtungskorrektur 2', + 'FlashExposureIndicator' => { + Description => 'Blitz-Belichtungsindikator', + PrintConv => { + 'Bottom of Scale' => 'Unterer Wert', + 'Not Indicated' => 'Nicht angezeigt', + 'Over Scale' => 'Wert zu hoch', + 'Top of Scale' => 'Oberer Wert', + 'Under Scale' => 'Wert zu niedrig', + }, + }, + 'FlashExposureIndicatorLast' => { + Description => 'Blitz-Ende-Belichtungsindikator', + PrintConv => { + 'Bottom of Scale' => 'Unterer Wert', + 'Not Indicated' => 'Nicht angezeigt', + 'Over Scale' => 'Wert zu hoch', + 'Top of Scale' => 'Oberer Wert', + 'Under Scale' => 'Wert zu niedrig', + }, + }, + 'FlashExposureIndicatorNext' => { + Description => 'Blitz-Folge-Belichtungsindikator', + PrintConv => { + 'Bottom of Scale' => 'Unterer Wert', + 'Not Indicated' => 'Nicht angezeigt', + 'Over Scale' => 'Wert zu hoch', + 'Top of Scale' => 'Oberer Wert', + 'Under Scale' => 'Wert zu niedrig', + }, + }, + 'FlashExposureLock' => { + Description => 'Blitzbelichtung-Speicherung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'FlashFired' => { + Description => 'Blitz wurde ausgelöst', + PrintConv => { + 'False' => 'Nein', + 'No' => 'Nein', + 'True' => 'Ja', + 'Yes' => 'Ja', + }, + }, + 'FlashFiring' => { + Description => 'Blitzzündung', + PrintConv => { + 'Does not fire' => 'Unterdrückt', + 'Fires' => 'Aktiv', + }, + }, + 'FlashFirmwareVersion' => 'Blitz-Firmware-Version', + 'FlashFocalLength' => 'Blitz-Brennweite', + 'FlashFunction' => { + Description => 'Blitzfunktion', + PrintConv => { + 'Built-in flash' => 'Integrierter Blitz', + 'False' => 'Nein', + 'Manual' => 'Manuell', + 'No flash' => 'Kein Blitz', + 'Strobe' => 'Stroboskop', + 'True' => 'Ja', + }, + }, + 'FlashGNDistance' => { + Description => 'Blitzleitzahl Entfernung', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FlashGroupACompensation' => 'Gruppe A, Blitzbelichtungs-Korrektur', + 'FlashGroupAControlMode' => { + Description => 'Gruppe A, Blitzlichtsteuerungsmodus', + PrintConv => { + 'Auto Aperture' => 'Blendenautomatik (AA)', + 'Manual' => 'Manuell', + 'Off' => 'Aus', + 'Repeating Flash' => 'Stroboskopblitz', + }, + }, + 'FlashGroupAOutput' => 'Gruppe A, Blitz-Leistung', + 'FlashGroupBCompensation' => 'Gruppe B, Blitzbelichtungs-Korrektur', + 'FlashGroupBControlMode' => { + Description => 'Gruppe B, Blitzlichtsteuerungsmodus', + PrintConv => { + 'Auto Aperture' => 'Blendenautomatik (AA)', + 'Automatic' => 'Automatik', + 'Manual' => 'Manuell', + 'Off' => 'Aus', + 'Repeating Flash' => 'Stroboskopblitz', + }, + }, + 'FlashGroupBOutput' => 'Gruppe B, Blitz-Leistung', + 'FlashGroupCCompensation' => 'Gruppe C, Blitzbelichtungs-Korrektur', + 'FlashGroupCControlMode' => { + Description => 'Gruppe C, Blitzlichtsteuerungsmodus', + PrintConv => { + 'Auto Aperture' => 'Blendenautomatik (AA)', + 'Manual' => 'Manuell', + 'Off' => 'Aus', + 'Repeating Flash' => 'Stroboskopblitz', + }, + }, + 'FlashGroupCOutput' => 'Gruppe C, Blitz-Leistung', + 'FlashGuideNumber' => 'Blitzleitzahl', + 'FlashInfo' => 'Blitz-Informationen', + 'FlashInfoVersion' => 'Blitz-Informationen-Version', + 'FlashIntensity' => { + Description => 'Blitz Stärke', + PrintConv => { + 'High' => 'Hoch', + 'Strong' => 'Stark', + }, + }, + 'FlashLevel' => { + Description => 'Blitzbelichtungskorr.', + PrintConv => { + 'High' => 'Hoch', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FlashMake' => 'Blitzgerätehersteller', + 'FlashMetering' => { + Description => 'Blitz-Messung', + PrintConv => { + 'Manual flash control' => 'Manuelle Blitz-Kontrolle', + 'Pre-flash TTL' => 'Vorblitz TTL', + }, + }, + 'FlashMeteringMode' => { + Description => 'Blitz-Belichtungsmessmethode', + PrintConv => { + 'External Auto' => 'Extern Automatisch', + 'External Manual' => 'Extern Manuell', + 'Off' => 'Aus', + }, + }, + 'FlashMeteringSegments' => 'Blitz-Messfelder', + 'FlashMode' => { + Description => 'Blitz-Modus', + PrintConv => { + '2nd Curtain' => 'Auf 2. Verschlußvorhang', + 'Auto' => 'Automatisch', + 'Auto, Did not fire' => 'Auto, nicht ausgelöst', + 'Auto, Did not fire, Red-eye reduction' => 'Auto, nicht ausgelöst, Rote-Augen-Reduzierung', + 'Auto, Fired' => 'Auto, ausgelöst', + 'Auto, Fired, Red-eye reduction' => 'Auto, ausgelöst, Rote-Augen-Reduzierung', + 'Did Not Fire' => 'Nicht ausgelöst', + 'Disabled' => 'Deaktiviert', + 'External, Auto' => 'Extern, Auto', + 'External, Contrast-control Sync' => 'Extern, Kontrast-Steuerungs-Synchronisation', + 'External, Flash Problem' => 'Extern, Blitzproblem?', + 'External, High-speed Sync' => 'Extern, High-Speed-Synchronisation', + 'External, Manual' => 'Extern, Manuell', + 'External, P-TTL Auto' => 'Extern, P-TTL-Blitzautomatik', + 'External, Wireless' => 'Extern, Drahtlos', + 'External, Wireless, High-speed Sync' => 'Extern, Drahtlos, High-Speed-Synchronisation', + 'Fill flash' => 'Aufhellblitz', + 'Fill-in' => 'Aufhellen', + 'Fired, Commander Mode' => 'Ausgelöst, Befehlsmodus', + 'Fired, External' => 'Ausgelöst, Extern', + 'Fired, Manual' => 'Ausgelöst, Manuell', + 'Fired, TTL Mode' => 'Ausgelöst, TTL-Modus', + 'Internal' => 'Intern', + 'Not Ready' => 'Nicht bereit', + 'Off' => 'Aus', + 'Off, Did not fire' => 'Aus', + 'Off?' => 'Aus?', + 'On' => 'Ein', + 'On, Did not fire' => 'Ein, nicht ausgelöst', + 'On, Did not fire, Wireless (Master)' => 'Ein, nicht ausgelöst, Drahtlos (Hauptblitz)', + 'On, Fired' => 'Ein', + 'On, Red-eye reduction' => 'Ein, Rote-Augen-Reduzierung', + 'On, Slow-sync' => 'Ein, Langzeit-Synchronisation', + 'On, Slow-sync, Red-eye reduction' => 'Ein, Langzeit-Synchronisation, Rote-Augen-Reduzierung', + 'On, Soft' => 'Ein, Softblitz', + 'On, Trailing-curtain Sync' => 'Ein, 2. Verschlussvorhang', + 'On, Wireless (Control)' => 'Ein, Drahtlos (Steuerblitz)', + 'On, Wireless (Master)' => 'Ein, Drahtlos (Hauptblitz)', + 'Rear flash sync' => 'Synchronisation auf den zweiten Verschlussvorhang', + 'Red eye' => 'Rote-Augen-Reduzierung', + 'Red-eye' => 'Rote Augen', + 'Red-eye Reduction' => 'Rote-Augen-Reduzierung', + 'Red-eye reduction' => 'Rote-Augen-Reduzierung', + 'Unknown' => 'Unbekannt', + 'Wireless' => 'Drahtlos', + 'n/a - Off-Auto-Aperture' => 'K/A - Blendenring nicht auf A', + }, + }, + 'FlashModel' => { + Description => 'Blitz-Modell', + PrintConv => { + 'None' => 'Keines', + }, + }, + 'FlashOptions' => { + Description => 'Blitz-Optionen', + PrintConv => { + 'Auto' => 'Automatisch', + 'Auto, Red-eye reduction' => 'Auto, Rote-Augen-Reduzierung', + 'Red-eye reduction' => 'Rote-Augen-Reduzierung', + 'Slow-sync' => 'Langzeit-Synchronisation', + 'Slow-sync, Red-eye reduction' => 'Langzeit-Synchronisation, Rote-Augen-Reduzierung', + 'Trailing-curtain Sync' => '2. Verschlussvorhang', + 'Wireless (Control)' => 'Drahtlos (Steuerblitz)', + 'Wireless (Master)' => 'Drahtlos (Hauptblitz)', + }, + }, + 'FlashOptions2' => { + Description => 'Blitz-Optionen (2)', + PrintConv => { + 'Auto' => 'Automatisch', + 'Auto, Red-eye reduction' => 'Auto, Rote-Augen-Reduzierung', + 'Red-eye reduction' => 'Rote-Augen-Reduzierung', + 'Slow-sync' => 'Langzeit-Synchronisation', + 'Slow-sync, Red-eye reduction' => 'Langzeit-Synchronisation, Rote-Augen-Reduzierung', + 'Trailing-curtain Sync' => '2. Verschlussvorhang', + 'Wireless (Control)' => 'Drahtlos (Steuerblitz)', + 'Wireless (Master)' => 'Drahtlos (Hauptblitz)', + }, + }, + 'FlashOutput' => 'Blitzstärke', + 'FlashRedEyeMode' => { + Description => 'Blitz Rote-Augen-Modus', + PrintConv => { + 'False' => 'Nein', + 'True' => 'Ja', + }, + }, + 'FlashRemoteControl' => { + Description => 'Blitz Fernauslöser', + PrintConv => { + 'Off' => 'Aus', + }, + }, + 'FlashReturn' => { + Description => 'Blitz Reflexion', + PrintConv => { + 'No return detection' => 'Keine Erkennung', + 'Return detected' => 'Reflexion erkannt', + 'Return not detected' => 'Reflexion nicht erkannt', + 'Subject Inside Flash Range' => 'Objekt innerhalb der Blitzreichweite', + 'Subject Outside Flash Range' => 'Objekt ausserhalb der Blitzreichweite', + }, + }, + 'FlashSerialNumber' => 'Blitz-Seriennummer', + 'FlashSetting' => 'Blitzeinstellung', + 'FlashShutterSpeed' => 'Längste Verschlussz. (Blitz)', + 'FlashStatus' => { + Description => 'Slave-Blitz-Messfeld 1', + PrintConv => { + 'Built-in Flash present' => 'Integrierter Blitz vorhanden', + 'Built-in Flash present and fired' => 'Integrierter Blitz vorhanden und ausgelöst', + 'External Flash present' => 'Externer Blitz vorhanden', + 'External Flash present and fired' => 'Externer Blitz vorhanden und ausgelöst', + 'External, Did not fire' => 'Extern, nicht ausgelöst', + 'External, Fired' => 'Extern, ausgelöst', + 'Internal, Did not fire' => 'Intern, nicht ausgelöst', + 'Internal, Did not fire (0x08)' => 'Intern, nicht ausgelöst', + 'Internal, Fired' => 'Intern, ausgelöst', + 'No Flash present' => 'Kein Blitz vorhanden', + 'Off' => 'Aus', + 'Off (1)' => 'Aus (1)', + }, + }, + 'FlashSyncMode' => 'Blitz-Synchronisationsmodus', + 'FlashSyncSpeed' => 'Blitzsynchronzeit', + 'FlashSyncSpeedAv' => { + Description => 'Blitzsynchronzeit bei Av', + PrintConv => { + '1/200 Fixed' => '1/200 Fest', + '1/200-1/60 Auto' => '1/200-1/60 automatisch', + '1/250 Fixed' => '1/250 Fest', + '1/250-1/60 Auto' => '1/200-1/60 automatisch', + '1/300 Fixed' => '1/300 Fest', + 'Auto' => 'Automatisch', + }, + }, + 'FlashTTLMode' => 'Blitz TTL-Modus', + 'FlashType' => { + Description => 'Blitztyp', + PrintConv => { + 'Built-In Flash' => 'Intern', + 'External' => 'Extern', + 'None' => 'Keiner', + }, + }, + 'FlashWarning' => { + Description => 'Blitzsymbol', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'FlashpixVersion' => 'Unterstützte Flashpix-Version', + 'FlickerReduce' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'FlipHorizontal' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'FocalLength' => 'Brennweite', + 'FocalLength35efl' => 'Brennweite', + 'FocalLengthIn35mmFormat' => 'Brennweite in 35 mm-Kleinbildformat', + 'FocalPlaneDiagonal' => 'Diagonale des Sensors', + 'FocalPlaneResolutionUnit' => { + Description => 'Einheit der Sensorauflösung', + PrintConv => { + 'None' => 'Keine', + 'inches' => 'Zoll', + 'um' => 'µm (Mikrometer)', + }, + }, + 'FocalPlaneXResolution' => 'Sensorauflösung horizontal', + 'FocalPlaneXSize' => 'Sensorgröße horizontal', + 'FocalPlaneXUnknown' => 'Sensorgröße horizontal unbekannt', + 'FocalPlaneYResolution' => 'Sensorauflösung vertikal', + 'FocalPlaneYSize' => 'Sensorgröße vertikal', + 'FocalPlaneYUnknown' => 'Sensorgröße vertikal unbekannt', + 'FocalType' => { + Description => 'Objektivart', + PrintConv => { + 'Fixed' => 'Festbrennweite', + 'Zoom' => 'Zoom-Objektiv', + }, + }, + 'FocalUnits' => 'Fokussiereinheit', + 'Focus' => { + Description => 'Schärfepriorität', + PrintConv => { + 'Auto-focus Didn\'t Lock' => 'Autofokus nicht gesperrt', + 'Auto-focus Locked' => 'Autofokus gesperrt', + 'Manual' => 'Manuell', + }, + }, + 'FocusArea' => { + Description => 'Fokus-Bereich', + PrintConv => { + 'Spot Focus' => 'Spot-AF-Messfeld', + 'Wide Focus (normal)' => 'Großes AF-Messfeld (normal)', + }, + }, + 'FocusAreaSelection' => { + Description => 'Scrollen bei Messfeldauswahl', + PrintConv => { + 'No Wrap' => 'Am Rand stoppen', + 'Wrap' => 'Umlaufend', + }, + }, + 'FocusContinuous' => { + Description => 'Fortlaufende Fokussierung', + PrintConv => { + 'Continuous' => 'Serienaufnahme', + 'Manual' => 'Manuell', + }, + }, + 'FocusDisplayAIServoAndMF' => { + PrintConv => { + 'Disable' => 'Deaktiviert', + 'Enable' => 'Aktiviert', + }, + }, + 'FocusDistance' => 'Fokus-Distanz', + 'FocusDistanceLower' => 'Nahe Fokus-Distanz', + 'FocusDistanceUpper' => 'Entfernte Fokus-Distanz', + 'FocusInfoVersion' => 'FokusInfo Version', + 'FocusMode' => { + Description => 'Fokus-Modus', + PrintConv => { + 'AF-C' => 'AF-C (Kontinuierlicher Autofokus)', + 'AF-S' => 'AF-S (Einzelautofokus)', + 'AI Focus AF' => 'AI Fokus AF', + 'Auto' => 'Automatisch', + 'Continuous' => 'Serienaufnahme', + 'Custom' => 'Benutzerdefiniert', + 'Face Detect' => 'Gesichtserkennung AF', + 'Infinity' => 'Unendlich', + 'Macro' => 'Makro', + 'Macro (1)' => 'Makro (1)', + 'Macro (2)' => 'Makro (2)', + 'Manual' => 'Manuell', + 'Manual Focus (3)' => 'Manueller Fokus (3)', + 'Manual Focus (6)' => 'Manueller Fokus (6)', + 'Multi AF' => 'Mehrpunkt AF', + 'One-shot AF' => 'One-Shot AF', + 'Pan Focus' => 'Pan-Fokus', + 'Single' => 'Einzelbild', + 'Single AF' => 'Einpunkt AF', + 'Super Macro' => 'Super-Makro', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'FocusMode2' => { + Description => 'Fokus-Modus 2', + PrintConv => { + 'AF-C' => 'AF-C (Kontinuierlicher Autofokus)', + 'AF-S' => 'AF-S (Einzelautofokus)', + 'Manual' => 'Manuell', + }, + }, + 'FocusModeSetting' => { + Description => 'Autofokus', + PrintConv => { + 'AF-A' => 'AF-Automatik', + 'AF-C' => 'AF-C (Kontinuierlicher Autofokus)', + 'AF-S' => 'AF-S (Einzelautofokus)', + 'Manual' => 'Manuell', + }, + }, + 'FocusModeSwitch' => { + Description => 'Fokus-Modus Schalter', + PrintConv => { + 'Manual' => 'Manuell', + }, + }, + 'FocusPixel' => 'Fokus-Pixel', + 'FocusPointWrap' => { + Description => 'Scrollen bei Messfeldauswahl', + PrintConv => { + 'No Wrap' => 'Am Rand stoppen', + 'Wrap' => 'Umlaufend', + }, + }, + 'FocusPos' => 'Fokus-Position', + 'FocusPosition' => 'Fokus-Distanz', + 'FocusProcess' => { + Description => 'Fokussierung', + PrintConv => { + 'AF Not Used' => 'Ohne AF', + 'AF Used' => 'Mit AF', + }, + }, + 'FocusRange' => { + Description => 'Fokus-Bereich', + PrintConv => { + 'Auto' => 'Automatisch', + 'Close' => 'Nah', + 'Far Range' => 'Entfernt', + 'Infinity' => 'Unendlich', + 'Macro' => 'Makro', + 'Manual' => 'Manuell', + 'Middle Range' => 'Mittlerer Bereich', + 'Not Known' => 'Nicht bekannt', + 'Pan Focus' => 'Pan-Fokus', + 'Super Macro' => 'Super-Makro', + 'Very Close' => 'Sehr nah', + }, + }, + 'FocusStepCount' => 'Fokus-Stufenzähler', + 'FocusStepInfinity' => 'Fokus-Stufe Unendlich', + 'FocusStepNear' => 'Fokus-Stufe Nah', + 'FocusTrackingLockOn' => { + Description => 'Schärfenarchiv. mit Lock-On', + PrintConv => { + '1 (Short)' => '5 (Kurz)', + '1 Short' => '1 Kurz', + '5 (Long)' => '5 (Lang)', + '5 Long' => '5 Lang', + 'Long' => 'Lang', + 'Off' => 'Aus', + 'Short' => 'Kurz', + }, + }, + 'FocusWarning' => { + Description => 'Fokus-Warnung', + PrintConv => { + 'Good' => 'OK', + 'Out of focus' => 'Ausserhalb des Fokus', + }, + }, + 'FocusingScreen' => 'Mattscheibe', + 'FolderName' => 'Ordner-Name', + 'FrameHeight' => 'Bereichshöhe', + 'FrameNumber' => 'Bildnummer', + 'FrameRate' => 'Bildwechselfrequenz', + 'FrameSize' => 'Einzelbildgröße', + 'FrameWidth' => 'Bereichsbreite', + 'FreeByteCounts' => 'Anzahl Bytes des leeren Datenbereiches', + 'FreeMemoryCardImages' => 'Platz auf Speicherkarten für', + 'FreeOffsets' => 'Leerdatenposition', + 'FujiFlashMode' => { + Description => 'Blitz-Modus', + PrintConv => { + 'Auto' => 'Automatisch', + 'External' => 'Externer Blitz', + 'Off' => 'Unterdrückter Blitz', + 'On' => 'Erzwungener Blitz', + 'Red-eye reduction' => 'Rote-Augen-Reduzierung', + }, + }, + 'FullImageSize' => 'Volle Bildgröße', + 'FunctionButton' => { + Description => 'Funktionstaste', + PrintConv => { + 'AF-area Mode' => 'Messfeldsteuerung', + 'Auto Bracketing' => 'Belichtungsreihe', + 'Center AF Area' => 'AF-Messfeldgröße', + 'Center-weighted' => 'Mittenbetont', + 'FV Lock' => 'FV-Messwertspeicher', + 'Flash Off' => 'Blitz aus', + 'Framing Grid' => 'Gitterlinien', + 'ISO Display' => 'ISO-Anzeige', + 'Image Quality' => 'Bildqualität', + 'Matrix Metering' => 'Matrixmessung', + 'Spot Metering' => 'Spotmessung', + 'White Balance' => 'Weißabgleich', + }, + }, + 'GEImageSize' => 'GE Bildgröße', + 'GIFVersion' => 'GIF-Version', + 'GPSAltitude' => 'GPS Höhe', + 'GPSAltitudeRef' => { + Description => 'GPS-Höhe Bezug', + PrintConv => { + 'Above Sea Level' => 'Höhe über Normal-Null (Meeresspiegel)', + 'Below Sea Level' => 'Höhe unter Normal-Null (Meeresspiegel)', + }, + }, + 'GPSAreaInformation' => 'Name des GPS-Gebietes', + 'GPSDOP' => 'Messgenauigkeit', + 'GPSDateStamp' => 'GPS Datum', + 'GPSDateTime' => 'GPS Zeitstempel', + 'GPSDestBearing' => 'Motivrichtung', + 'GPSDestBearingRef' => { + Description => 'Referenz für Motivrichtung', + PrintConv => { + 'Magnetic North' => 'Magnetische Ausrichtung', + 'True North' => 'Geographische Ausrichtung', + }, + }, + 'GPSDestDistance' => 'GPS Zielentfernung', + 'GPSDestDistanceRef' => { + Description => 'GPS-Zielentfernung Maßeinheit', + PrintConv => { + 'Kilometers' => 'Kilometer', + 'Miles' => 'Meilen', + 'Nautical Miles' => 'Knoten', + }, + }, + 'GPSDestLatitude' => 'Breite des Zieles', + 'GPSDestLatitudeRef' => { + Description => 'Referenz für die Breite des Zieles', + PrintConv => { + 'North' => 'Nördliche Breite', + 'South' => 'Südliche Breite', + }, + }, + 'GPSDestLongitude' => 'Längengrad des Ziels', + 'GPSDestLongitudeRef' => { + Description => 'Referenz für die Länge des Zieles', + PrintConv => { + 'East' => 'Östliche Länge', + 'West' => 'Westliche Länge', + }, + }, + 'GPSDifferential' => { + Description => 'GPS Differentialkorrektur', + PrintConv => { + 'Differential Corrected' => 'Differentialkorrektur angewandt', + 'No Correction' => 'Messung ohne Differentialkorrektur', + }, + }, + 'GPSImgDirection' => 'Bildrichtung', + 'GPSImgDirectionRef' => { + Description => 'Referenz für die Ausrichtung des Bildes', + PrintConv => { + 'Magnetic North' => 'Magnetische Ausrichtung', + 'True North' => 'Geographische Ausrichtung', + }, + }, + 'GPSInfo' => 'GPS Info IFD-Zeiger', + 'GPSLatitude' => 'Geografische Breite', + 'GPSLatitudeRef' => { + Description => 'Nördl. oder südl. Breite', + PrintConv => { + 'North' => 'Nördliche Breite', + 'South' => 'Südliche Breite', + }, + }, + 'GPSLongitude' => 'Geografische Länge', + 'GPSLongitudeRef' => { + Description => 'östl. oder westl. Länge', + PrintConv => { + 'East' => 'Östliche Länge', + 'West' => 'Westliche Länge', + }, + }, + 'GPSMapDatum' => 'Geodätisches Datum', + 'GPSMeasureMode' => { + Description => 'GPS Messverfahren', + PrintConv => { + '2-D' => '2-Dimensionale Messung', + '2-Dimensional' => '2-Dimensionale Messung', + '2-Dimensional Measurement' => '2-Dimensionale Messung', + '3-D' => '3-Dimensionale Messung', + '3-Dimensional' => '3-Dimensionale Messung', + '3-Dimensional Measurement' => '3-Dimensionale Messung', + }, + }, + 'GPSProcessingMethod' => 'Name der GPS-Verarbeitungsmethode', + 'GPSSatellites' => 'Für die Messung verwendete Satelliten', + 'GPSSpeed' => 'Geschwindigkeit des GPS-Empfängers', + 'GPSSpeedRef' => { + Description => 'Geschwindigkeitseinheit', + PrintConv => { + 'km/h' => 'Kilometer pro Stunde', + 'knots' => 'Knoten', + 'mph' => 'Meilen pro Stunde', + }, + }, + 'GPSStatus' => { + Description => 'GPS-Empfänger Status', + PrintConv => { + 'Measurement Active' => 'Messung aktiv', + 'Measurement Void' => 'Messung ungültig', + }, + }, + 'GPSTimeStamp' => 'GPS-Zeit UTC', + 'GPSTrack' => 'Bewegungsrichtung', + 'GPSTrackRef' => { + Description => 'Referenz für Bewegungsrichtung', + PrintConv => { + 'Magnetic North' => 'Magnetische Ausrichtung', + 'True North' => 'Geographische Ausrichtung', + }, + }, + 'GPSVersionID' => 'GPS-Tag-Version', + 'GainControl' => { + Description => 'Belichtungsverstärkung', + PrintConv => { + 'High gain down' => 'Hohe Helligkeitsminderung', + 'High gain up' => 'Hohe Helligkeitsverstärkung', + 'Low gain down' => 'Geringe Helligkeitsminderung', + 'Low gain up' => 'Geringe Helligkeitsverstärkung', + 'None' => 'Keine', + }, + }, + 'Gapless' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'Gradation' => { + PrintConv => { + 'Auto-Override' => 'Automatisch', + 'High Key' => 'High-Key', + 'Low Key' => 'Low-Key', + 'User-Selected' => 'Benutzerauswahl', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'GrayResponseUnit' => { + PrintConv => { + '0.0001' => 'Nummer stellt ein 1000tel einer Einheit dar', + '0.001' => 'Nummer stellt ein 100tel einer Einheit dar', + '0.1' => 'Nummer stellt ein 10tel einer Einheit dar', + '1e-05' => 'Nummer stellt ein 10000tel einer Einheit dar', + '1e-06' => 'Nummer stellt ein 100000tel einer Einheit dar', + }, + }, + 'GrayTRC' => 'Grau-Tonwertwiedergabe-Kurve', + 'GreenAdjust' => 'Grün-Korrektur', + 'GreenMatrixColumn' => 'Grün-Matrixspalte', + 'GreenTRC' => 'Grün-Tonwertwiedergabe-Kurve', + 'GreenX' => 'Grünpunkt X', + 'GreenY' => 'Grünpunkt Y', + 'GridDisplay' => { + Description => 'Gitterlinien', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'GripBatteryADLoad' => 'Griffbatterie A/D unter Last', + 'GripBatteryADNoLoad' => 'Griffbatterie A/D im Leerlauf', + 'GripBatteryState' => { + Description => 'Griffbatterie-Status', + PrintConv => { + 'Almost Empty' => 'Fast leer', + 'Empty or Missing' => 'Leer oder nicht vorhanden', + 'Full' => 'Voll geladen', + 'Running Low' => 'Schwach', + }, + }, + 'Grouping' => 'Gruppierung', + 'HDR' => { + Description => 'Auto HDR', + PrintConv => { + 'Off' => 'Aus', + }, + }, + 'HDRImageType' => { + Description => 'HDR Bildtyp', + PrintConv => { + 'HDR Image' => 'HDR Bild', + 'Original Image' => 'Originalbild', + }, + }, + 'Headline' => 'Überschrift', + 'HeightResolution' => 'Vertikale Bildauflösung', + 'HierarchicalSubject' => 'Hierarchische Schlüsselwörter', + 'HighISONoiseReduction' => { + Description => 'Rauschunterdrückung bei hoher Empfindlichkeit', + PrintConv => { + '+1 (medium strong)' => '+1', + '+2 (strong)' => '+2 (Stark)', + '+3 (very strong)' => '+3', + '+4 (strongest)' => '+4', + '-1 (medium weak)' => '-1', + '-2 (weak)' => '-2 (Gering)', + '-3 (very weak)' => '-3', + '-4 (weakest)' => '-4 (Sehr gering)', + '0 (normal)' => '0 (Normal)', + 'Auto' => 'Automatisch', + 'High' => 'Stärker', + 'Low' => 'Schwächer', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Strong' => 'Stark', + 'Weak' => 'Gering', + 'Weakest' => 'Sehr gering', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'HighISONoiseReduction2' => { + Description => 'Rauschunterdrückung bei hoher Empfindlichkeit', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Niedrig', + 'Off' => 'Aus', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'HighLowKeyAdj' => 'High-/Low-Key Abstimmung', + 'Highlight' => 'Helle Stellen', + 'HighlightTonePriority' => { + Description => 'Tonwert Priorität', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'HometownCity' => 'Heimatort', + 'HometownCityCode' => 'Heimatort-Code', + 'HometownDST' => { + Description => 'Heimatort Sommerzeit (DST)', + PrintConv => { + 'No' => 'Deaktiviert', + 'Yes' => 'Aktiviert', + }, + }, + 'Hue' => 'Farbton', + 'HueAdj' => 'Farbtonkorrektur', + 'HueAdjust' => { + Description => 'Farbtonkorrektur', + PrintConv => { + 'Cool' => 'Kühl', + 'Off' => 'Aus', + 'Yellow' => 'Gelb', + }, + }, + 'HueAdjustment' => 'Farbtonkorrektur', + 'HueSetting' => 'Farbtoneinstellung', + 'HuffmanTable' => 'Huffman Tabelle', + 'HyperfocalDistance' => 'Hyperfokale Entfernung', + 'ICCProfile' => 'ICC-Profil', + 'ICCProfileName' => 'ICC-Profil Name', + 'ICC_Profile' => 'ICC-Profil', + 'ID3Size' => 'ID3 Datenlänge', + 'IDCCreativeStyle' => { + PrintConv => { + 'Autumn Leaves' => 'Herbstlaub', + 'B&W' => 'Schwarz/Weiß', + 'Landscape' => 'Landschaft', + 'Night View' => 'Abendszene', + 'Portrait' => 'Porträt', + 'Sunset' => 'Sonnenuntergang', + }, + }, + 'IDCPreviewImage' => 'IDC Vorschaubild', + 'IDCPreviewLength' => 'IDC Vorschaubild-Datenlänge', + 'IDCPreviewStart' => 'IDC Vorschaubild-Datenposition', + 'IPTC-NAA' => 'IPTC-NAA Metadaten', + 'IPTCDigest' => 'IPTC Kennwert', + 'IPTCImageHeight' => 'IPTC-Bildhöhe', + 'IPTCImageRotation' => { + Description => 'IPTC Bildausrichtung', + PrintConv => { + '0' => 'Normal', + '180' => '180° gedreht', + '270' => '90° gegen den Uhrzeigersinn', + '90' => '90° im Uhrzeigersinn', + }, + }, + 'IPTCImageWidth' => 'IPTC-Bildbreite', + 'IPTCPictureNumber' => 'IPTC Bildnummer', + 'IPTCPixelHeight' => 'IPTC-Pixelhöhe', + 'IPTCPixelWidth' => 'IPTC-Pixelbreite', + 'ISO' => 'ISO-Empfindlichkeit', + 'ISO2' => 'ISO-Empfindlichkeit (2)', + 'ISOAuto' => 'ISO-Automatik', + 'ISODisplay' => 'ISO-Anzeige', + 'ISOExpansion' => { + Description => 'ISO-Erweiterung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ISOExpansion2' => { + Description => 'ISO-Erweiterung (2)', + PrintConv => { + 'Off' => 'Aus', + }, + }, + 'ISOFloor' => 'ISO-Untergrenze', + 'ISOInfo' => 'ISO-Informationen', + 'ISOSelection' => 'ISO-Auswahl', + 'ISOSetting' => { + Description => 'ISO-Einstellung', + PrintConv => { + '200 (Zone Matching High)' => '200 (Zonenabgleich High)', + '80 (Zone Matching Low)' => '80 (Zonenabgleich Low)', + 'Auto' => 'Automatisch', + 'Manual' => 'Manuell', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ISOSpeedExpansion' => { + Description => 'ISO-Erweiterung', + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'ISOSpeedIncrements' => { + Description => 'ISO-Schrittweite', + PrintConv => { + '1 Stop' => '1 LW', + '1/3 Stop' => '1/3 LW', + }, + }, + 'ISOSpeedRange' => { + Description => 'Einstellung ISO-Bereich', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'ISOStepSize' => { + Description => 'ISO-Schrittweite', + PrintConv => { + '1 EV' => '1 LW', + '1/2 EV' => '1/2 LW', + '1/3 EV' => '1/3 LW', + }, + }, + 'ISRCNumber' => 'ISRC Nummer', + 'Illumination' => { + Description => 'Displaybeleuchtung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Image::ExifTool::APP12::PictureInfo' => 'APP12 Bildinformation', + 'Image::ExifTool::Canon::AFMicroAdj' => 'Canon AF Feinabstimmung', + 'Image::ExifTool::Canon::CameraInfo1000D' => 'Canon KameraInfo 1000D', + 'Image::ExifTool::Canon::CameraInfo1D' => 'Canon KameraInfo 1D', + 'Image::ExifTool::Canon::CameraInfo1DX' => 'Canon KameraInfo 1DX', + 'Image::ExifTool::Canon::CameraInfo1DmkII' => 'Canon KameraInfo 1DmkII', + 'Image::ExifTool::Canon::CameraInfo1DmkIII' => 'Canon KameraInfo 1DmkIII', + 'Image::ExifTool::Canon::CameraInfo1DmkIIN' => 'Canon KameraInfo 1DmkIIN', + 'Image::ExifTool::Canon::CameraInfo1DmkIV' => 'Canon KameraInfo 1DmkIV', + 'Image::ExifTool::Canon::CameraInfo40D' => 'Canon KameraInfo 40D', + 'Image::ExifTool::Canon::CameraInfo450D' => 'Canon KameraInfo 450D', + 'Image::ExifTool::Canon::CameraInfo500D' => 'Canon KameraInfo 500D', + 'Image::ExifTool::Canon::CameraInfo50D' => 'Canon KameraInfo 50D', + 'Image::ExifTool::Canon::CameraInfo550D' => 'Canon KameraInfo 550D', + 'Image::ExifTool::Canon::CameraInfo5D' => 'Canon KameraInfo 5D', + 'Image::ExifTool::Canon::CameraInfo5DmkII' => 'Canon KameraInfo 5DmkII', + 'Image::ExifTool::Canon::CameraInfo5DmkIII' => 'Canon KameraInfo 5DmkIII', + 'Image::ExifTool::Canon::CameraInfo600D' => 'Canon KameraInfo 600D', + 'Image::ExifTool::Canon::CameraInfo60D' => 'Canon KameraInfo 60D', + 'Image::ExifTool::Canon::CameraInfo650D' => 'Canon KameraInfo 650D', + 'Image::ExifTool::Canon::CameraInfo7D' => 'Canon KameraInfo 7D', + 'Image::ExifTool::Canon::CameraInfoPowerShot' => 'Canon KameraInfo PowerShot', + 'Image::ExifTool::Canon::CameraInfoPowerShot2' => 'Canon KameraInfo PowerShot2', + 'Image::ExifTool::Canon::CameraInfoUnknown32' => 'Canon KameraInfo Unbekannt32', + 'Image::ExifTool::Canon::CropInfo' => 'Canon Ausschnitt Info', + 'Image::ExifTool::CanonRaw::ImageFormat' => 'CanonRaw Bildformat', + 'Image::ExifTool::DNG::OriginalRaw' => 'DNG Original RAW', + 'Image::ExifTool::ICC_Profile::Measurement' => 'ICC_Profil Messung', + 'Image::ExifTool::IPTC::ApplicationRecord' => 'IPTC Modell', + 'Image::ExifTool::Jpeg2000::FileType' => 'Jpeg2000 Dateityp', + 'Image::ExifTool::Jpeg2000::ImageHeader' => 'Jpeg2000 Bild-Header', + 'Image::ExifTool::Kodak::CameraInfo' => 'Kodak KameraInfo', + 'Image::ExifTool::MIE::Meta' => 'MIE Metadaten', + 'Image::ExifTool::Minolta::CameraInfoA100' => 'Minolta KameraInfo A100', + 'Image::ExifTool::Olympus::CameraSettings' => 'Olympus Kameraeinstellungen', + 'Image::ExifTool::Olympus::FocusInfo' => 'Olympus FokusInfo', + 'Image::ExifTool::Olympus::ImageProcessing' => 'Olympus Bildverarbeitung', + 'Image::ExifTool::PNG::ImageHeader' => 'PNG Bild-Header', + 'Image::ExifTool::PNG::PhysicalPixel' => 'PNG physikalische Pixel', + 'Image::ExifTool::PNG::PrimaryChromaticities' => 'PNG primäre Chromatizität', + 'Image::ExifTool::PNG::StereoImage' => 'PNG Stereobild', + 'Image::ExifTool::PNG::TextualData' => 'PNG Textdaten', + 'Image::ExifTool::PNG::VirtualPage' => 'PNG virtuelle Seite', + 'Image::ExifTool::PSP::Creator' => 'PSP Ersteller', + 'Image::ExifTool::Pentax::CameraInfo' => 'Pentax KameraInfo', + 'Image::ExifTool::Sony::CameraInfo' => 'Sony KameraInfo', + 'Image::ExifTool::Sony::CameraInfo2' => 'Sony KameraInfo2', + 'ImageAdjustment' => 'Bildanpassung', + 'ImageAreaOffset' => 'Bildbereichsoffset', + 'ImageAuthentication' => { + Description => 'Bild-Authentifikation', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ImageBoundary' => 'Bildbegrenzung', + 'ImageByteCount' => 'Anzahl Bytes der Bilddaten', + 'ImageCount' => 'Bildzähler', + 'ImageDataDiscard' => { + Description => 'Verworfene Bilddaten', + PrintConv => { + 'Flexbits Discarded' => 'FlexBits verworfen', + 'Full Resolution' => 'Volle Auflösung', + 'HighPass Frequency Data Discarded' => 'Hochpass-Frequenz-Daten verworfen', + 'Highpass and LowPass Frequency Data Discarded' => 'Hochpass- und Tiefpass-Frequenz-Daten verworfen', + }, + }, + 'ImageDataSize' => 'Bilddatengröße', + 'ImageDescription' => 'Bildbeschreibung', + 'ImageDustOff' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ImageEditCount' => 'Bildverarbeitungszähler', + 'ImageEditing' => { + Description => 'Bildverarbeitung', + PrintConv => { + 'Cropped' => 'Beschnitten', + 'Digital Filter' => 'Digitalfilter', + 'Frame Synthesis?' => 'Rahmen?', + 'None' => 'Unbearbeitet', + }, + }, + 'ImageEffects' => { + PrintConv => { + 'High Key' => 'High-Key', + }, + }, + 'ImageGeneration' => { + Description => 'Bilderstellung', + PrintConv => { + 'Original Image' => 'Originalbild', + 'Re-developed from RAW' => 'RAW generiert', + }, + }, + 'ImageHeight' => 'Bildhöhe', + 'ImageHistory' => 'Bild-Historie', + 'ImageLength' => 'Bild-Datenlänge', + 'ImageNumber' => 'Bildnummer', + 'ImageNumber2' => 'Bildnummer (2)', + 'ImageOffset' => 'Bilddatenposition', + 'ImageOptimization' => 'Bildoptimierung', + 'ImageOrientation' => { + Description => 'Bildausrichtung', + PrintConv => { + 'Landscape' => 'Querformat', + 'Portrait' => 'Porträt', + 'Square' => 'Quadratisch', + }, + }, + 'ImageProcessing' => 'Bildverarbeitung', + 'ImageProcessingVersion' => 'Bildverarbeitung Version', + 'ImageQuality' => { + Description => 'Bildqualität', + PrintConv => { + 'High' => 'Hoch', + }, + }, + 'ImageQuality2' => 'Bildqualität 2', + 'ImageReview' => { + Description => 'Bildkontrolle', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ImageReviewTime' => 'Ausschaltzeit Bildkontrolle', + 'ImageRotated' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'ImageRotation' => { + Description => 'Bildausrichtung', + PrintConv => { + 'None' => 'Keine', + }, + }, + 'ImageSize' => 'Bildgröße', + 'ImageStabilization' => { + Description => 'Bildstabilisierung', + PrintConv => { + 'Anti-Shake' => 'Verwackeln', + 'Best Shot' => 'Beste Aufnahme', + 'CCD Shift + High Sensitivity' => 'CCD Shift + Hohe Empfindlichkeit', + 'Dynamic' => 'Dynamisch', + 'Dynamic (2)' => 'Dynamisch (2)', + 'High Sensitivity' => 'Hohe Empfindlichkeit', + 'None' => 'Keine', + 'Off' => 'Aus', + 'Off (0xbf)' => 'Aus (0xbf)', + 'Off (1)' => 'Aus (1)', + 'Off (2)' => 'Aus (2)', + 'On' => 'Ein', + 'On (0x3f)' => 'Ein (0x3f)', + 'On (2)' => 'Ein (2)', + 'On (mode 1, continuous)' => 'Ein (Modus 1, nachführend)', + 'On (mode 2, shooting only)' => 'Ein (Modus 2, Aufnahme)', + 'On, Mode 1' => 'Ein, Modus 1', + 'On, Mode 2' => 'Ein, Modus 2', + 'On, Mode 3' => 'Ein, Modus 3', + 'On, Mode 4' => 'Ein, Modus 4', + 'Optical' => 'Optisch', + 'Panning' => 'Schwenken', + 'Panning (2)' => 'Schwenken (2)', + 'Sensor-shift' => 'Sensor-Shift', + 'Shoot Only' => 'Aufnahme', + 'Shoot Only (2)' => 'Aufnahme (2)', + 'Slow Shutter' => 'Kurzer Verschluß', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ImageStabilizationSetting' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ImageStyle' => { + PrintConv => { + 'B&W' => 'Schwarz/Weiß', + 'Landscape' => 'Landschaft', + 'Night View/Portrait' => 'Abendszene/Porträt', + 'Portrait' => 'Porträt', + 'Sunset' => 'Sonnenuntergang', + }, + }, + 'ImageTone' => { + Description => 'Farbdynamik', + PrintConv => { + 'Bright' => 'Leuchtend', + 'Landscape' => 'Landschaft', + 'Monochrome' => 'Monochrom', + 'Natural' => 'Natürlich', + 'Portrait' => 'Porträt', + }, + }, + 'ImageType' => { + Description => 'Bildtyp', + PrintConv => { + 'Page' => 'Seite', + 'Preview' => 'Vorschau', + }, + }, + 'ImageUIDList' => 'Bilder UID Liste', + 'ImageUniqueID' => 'Eindeutige Bild-ID', + 'ImageWidth' => 'Bildbreite', + 'InfoButtonWhenShooting' => { + Description => 'INFO-Taste bei Aufnahme', + PrintConv => { + 'Displays camera settings' => 'Anzeige Kameradaten', + 'Displays shooting functions' => 'Anzeige Aufnahmedaten', + }, + }, + 'InitialZoomSetting' => { + Description => 'Erste Vergrößerungsstufe', + PrintConv => { + 'High Magnification' => 'Starke Vergrößerung', + 'Low Magnification' => 'Geringe Vergrößerung', + 'Medium Magnification' => 'Mittlere Vergrößerung', + }, + }, + 'Instructions' => 'Anweisungen', + 'IntellectualGenre' => 'Intellektuelles Genre', + 'IntelligentAuto' => { + PrintConv => { + 'Advanced' => 'Erweitert', + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'IntelligentContrast' => { + Description => 'Intelligenter Kontrast', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'IntelligentD-Range' => { + Description => 'Intelligenter D-Bereich', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Niedrig', + 'Off' => 'Aus', + }, + }, + 'IntelligentExposure' => { + Description => 'Intelligente Belichtung', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Niedrig', + 'Off' => 'Aus', + }, + }, + 'IntelligentResolution' => { + Description => 'Intelligente Bildauflösung', + PrintConv => { + 'Extended' => 'Erweitert', + 'High' => 'Hoch', + 'Low' => 'Niedrig', + 'Off' => 'Aus', + }, + }, + 'IntensityStereo' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'InternalFlash' => { + Description => 'Integriertes Blitzgerät', + PrintConv => { + 'Commander Mode' => 'Master-Steuerung', + 'Fired' => 'Blitz wurde ausgelöst', + 'Manual' => 'Manuell', + 'No' => 'Blitz wurde nicht ausgelöst', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Repeating Flash' => 'Stroboskopblitz', + }, + }, + 'InternalFlashAE1' => 'Integriertes Blitzgerät AE1', + 'InternalFlashAE1_0' => 'Integriertes Blitzgerät AE1 0', + 'InternalFlashAE2' => 'Integriertes Blitzgerät AE2', + 'InternalFlashAE2_0' => 'Integriertes Blitzgerät AE2 0', + 'InternalFlashMode' => { + Description => 'Slave-Blitz-Messfeld 2', + PrintConv => { + 'Did not fire, (Unknown 0xf4)' => 'Aus (Unbekannt 0xF4?)', + 'Did not fire, Auto' => 'Aus, Auto', + 'Did not fire, Auto, Red-eye reduction' => 'Aus, Auto, Rote-Augen-Reduzierung', + 'Did not fire, Normal' => 'Aus, Normal', + 'Did not fire, Red-eye reduction' => 'Aus, Rote-Augen-Reduzierung', + 'Did not fire, Slow-sync' => 'Aus, Langzeit-Synchronisation', + 'Did not fire, Slow-sync, Red-eye reduction' => 'Aus, Langzeit-Synchronisation, Rote-Augen-Reduzierung', + 'Did not fire, Trailing-curtain Sync' => 'Aus, 2. Verschlussvorhang', + 'Did not fire, Wireless (Control)' => 'Aus, Drahtlos (Steuerblitz)', + 'Did not fire, Wireless (Master)' => 'Aus, Drahtlos (Hauptblitz)', + 'Fired' => 'Ein', + 'Fired, Auto' => 'Ein, Auto', + 'Fired, Auto, Red-eye reduction' => 'Ein, Auto, Rote-Augen-Reduzierung', + 'Fired, Red-eye reduction' => 'Ein, Rote-Augen-Reduzierung', + 'Fired, Slow-sync' => 'Ein, Langzeit-Synchronisation', + 'Fired, Slow-sync, Red-eye reduction' => 'Ein, Langzeit-Synchronisation, Rote-Augen-Reduzierung', + 'Fired, Trailing-curtain Sync' => 'Ein, 2. Verschlussvorhang', + 'Fired, Wireless (Control)' => 'Ein, Drahtlos (Steuerblitz)', + 'Fired, Wireless (Master)' => 'Ein, Drahtlos (Hauptblitz)', + 'n/a - Off-Auto-Aperture' => 'K/A - Blendenring nicht auf A', + }, + }, + 'InternalFlashStrength' => 'Slave-Blitz-Messfeld 4', + 'InternalName' => 'Interner Name', + 'InternalSerialNumber' => 'Interne Seriennummer', + 'InteropIndex' => { + Description => 'Interoperabilität Identifikation', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03: DCF Option-Format (Adobe RGB)', + 'R98 - DCF basic file (sRGB)' => 'R98: DCF Basic-Format (sRGB)', + 'THM - DCF thumbnail file' => 'THM: DCF Miniaturbild-Format', + }, + }, + 'InteropOffset' => 'Interoperabilitäts-Tag', + 'InteropVersion' => 'Interoperabilitäts-Version', + 'IntervalLength' => 'Intervallänge', + 'IntervalMode' => { + Description => 'Interval-Modus', + PrintConv => { + 'Still Image' => 'Standbild', + 'Time-lapse Movie' => 'Zeitraffer-Film', + }, + }, + 'IntervalNumber' => 'Intervalnummer', + 'IsCustomPictureStyle' => { + Description => 'Bildstil benutzerdefinert', + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'JFIFVersion' => 'JFIF-Version', + 'JPEGDigest' => 'JPEG Kennwert', + 'JPEGProc' => { + Description => 'JPEG Verfahren', + PrintConv => { + 'Lossless' => 'Verlustfrei', + }, + }, + 'JPEGQuality' => { + Description => 'Bildqualität', + PrintConv => { + 'Extra Fine' => 'Extra-Fein', + 'Fine' => 'Fein', + 'Standard' => 'Standardqualität', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'JPEGSize' => 'JPEG Größe', + 'JPEGTables' => 'JPEG Tabellen', + 'JobID' => 'Job-Kennung', + 'JpgFromRaw' => 'Jpg From Raw Bild', + 'JpgFromRawLength' => 'Jpg From Raw Datenlänge', + 'JpgFromRawStart' => 'Jpg From Raw Datenposition', + 'JpgRecordedPixels' => 'JPEG-Auflösung', + 'Key' => 'Schlüssel', + 'Keyword' => 'Schlüsselwort', + 'Keywords' => 'Schlüsselwörter', + 'KodakImageHeight' => 'Kodak-Bildhöhe', + 'KodakImageWidth' => 'Kodak-Bildbreite', + 'LC1' => 'Objektiv-Wert', + 'LC10' => 'Mv\' nv\'-Daten', + 'LC11' => 'AVC 1/EXP-Wert', + 'LC12' => 'Mv1 Avminsif-Wert', + 'LC14' => 'UNT_12 UNT_6-Wert', + 'LC15' => 'Incorporated Flash Suited END-Wert', + 'LC2' => 'Entfernungscode', + 'LC3' => 'K-Wert (LC3)', + 'LC4' => 'Wert für Aberrationskorrektur im Nahbereich', + 'LC5' => 'Wert für Aberrationskorrektur heller Farben', + 'LC6' => 'Wert für Aberrationskorrektur bei offener Blende', + 'LC7' => 'AF Minimum Actuation Condition-Wert', + 'LCDDisplayAtPowerOn' => { + Description => 'LCD-Display bei Kamera Ein', + PrintConv => { + 'Retain power off status' => 'Aus-Status beibehalten', + }, + }, + 'LCDDisplayReturnToShoot' => { + Description => 'LC-Display->Zurück zur Aufn.', + PrintConv => { + 'Also with * etc.' => 'Auch mit * etc.', + 'With Shutter Button only' => 'Nur mit Auslöser', + }, + }, + 'LCDIllumination' => { + Description => 'Displaybeleuchtung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'LCDIlluminationDuringBulb' => { + Description => 'LCD-Beleuchtung bei Langzeitaufnahme', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'LCDPanels' => 'LCD oben/LCD Rückwand', + 'LCHEditor' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Label' => 'Beschriftung', + 'Landmark' => 'Sehenswürdigkeit', + 'Language' => 'Sprache', + 'LanguageCode' => { + Description => 'Sprache', + PrintConv => { + 'Process default' => 'Systemeinstellung', + }, + }, + 'LanguageIdentifier' => 'Sprachkennung', + 'LastFileNumber' => 'Letzte Dateinummer', + 'LastKeywordIPTC' => 'Letztes IPTC Schlüsselwort', + 'LastKeywordXMP' => 'Letztes XMP Schlüsselwort', + 'LateralChromaticAberration' => { + Description => 'Laterale Farbabweichung', + PrintConv => { + 'Off' => 'Aus', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'LeafData' => 'Leaf Daten', + 'LegacyIPTCDigest' => 'Bisheriger IPTC Kennwert', + 'Lens' => 'Objektiv', + 'Lens35efl' => 'Objektiv', + 'LensAFStopButton' => { + Description => 'Funktion Objektiv-AF-Stopptaste', + PrintConv => { + 'AE lock' => 'AE-Speicherung', + 'AE lock while metering' => 'AE-Sperre b. aktiv. Messung', + 'AF Stop' => 'AF-Stopp', + 'AF point: M->Auto/Auto->ctr' => 'AF-Messf: M->Aut./Aut.->Ctr', + 'AF start' => 'AF-Start', + 'AF stop' => 'AF-Stopp', + 'IS start' => 'Start Bildstabilisierung', + 'Switch to registered AF point' => 'Auf gesp. AF-Messf. schalten', + }, + }, + 'LensApertureRange' => 'Objektiv Blendenbereich', + 'LensData' => 'K-Wert', + 'LensDataVersion' => 'Objektivdaten-Version', + 'LensDistortionParams' => 'Objektiv Verzeichnungsparameter', + 'LensDriveNoAF' => { + Description => 'Schärfensuche wenn AF unmöglich', + PrintConv => { + 'Focus search off' => 'Schärfensuche aus', + 'Focus search on' => 'Schärfensuche ein', + }, + }, + 'LensFStops' => 'Objektiv-Blendenstufen', + 'LensFirmwareVersion' => 'Objektiv-Firmware-Version', + 'LensID' => 'Objektiv-ID', + 'LensIDNumber' => 'Objektivkennnummer', + 'LensInfo' => 'Objektiv-Informationen', + 'LensKind' => 'Objektivtyp / Version (LC0)', + 'LensMake' => 'Objektivhersteller', + 'LensManufacturer' => 'Objektivhersteller', + 'LensMaxApertureRange' => 'Objektiv Blendenbereich', + 'LensModel' => 'Objektiv-Typ', + 'LensProfileDigest' => 'Kennwert des Objektivprofils', + 'LensProperties' => 'Objektivfunktionen?', + 'LensSerialNumber' => 'Objektiv-Seriennummer', + 'LensSpec' => 'Objektiv', + 'LensType' => { + Description => 'Objektivtyp', + PrintConv => { + 'Uncoded lens' => 'Nicht kodiertes Objektiv', + }, + }, + 'LevelOrientation' => { + Description => 'Level Ausrichtung', + PrintConv => { + 'Downwards' => 'Abwärts', + 'Horizontal; Off Level' => 'Horizontal; ohne Level', + 'Rotate 180' => '180° gedreht', + 'Rotate 180; Off Level' => '180° gedreht; ohne Level', + 'Rotate 270 CW' => '90° gegen den Uhrzeigersinn', + 'Rotate 270 CW; Off Level' => '90° gegen den Uhrzeigersinn; ohne Level', + 'Rotate 90 CW' => '90° im Uhrzeigersinn', + 'Rotate 90 CW; Off Level' => '90° im Uhrzeigersinn; ohne Level', + 'Upwards' => 'Aufwärts', + }, + }, + 'License' => 'Lizenz', + 'LicenseType' => { + Description => 'Lizenztyp', + PrintConv => { + 'Commercial' => 'Kommerziell', + 'Unknown' => 'Unbekannt', + }, + }, + 'LightReading' => 'Helligkeitsauswertung', + 'LightSource' => { + Description => 'Lichtquelle', + PrintConv => { + 'Cloudy' => 'Bewölkt', + 'Cool White Fluorescent' => 'Neonlicht kaltweiß', + 'Day White Fluorescent' => 'Neonlicht neutralweiß', + 'Daylight' => 'Tageslicht', + 'Daylight Fluorescent' => 'Neonlicht tageslichtweiß', + 'Evening Sunlight' => 'Abendstimmung', + 'Fine Weather' => 'Wolkenlos', + 'Flash' => 'Blitz', + 'Fluorescent' => 'Neonlicht', + 'ISO Studio Tungsten' => 'ISO Studio-Kunstlicht (Glühbirne)', + 'One Touch White Balance' => 'Sofort-Weißabgleich', + 'Other' => 'Andere Lichtquelle', + 'Shade' => 'Schatten', + 'Standard Light A' => 'Standard-Licht A', + 'Standard Light B' => 'Standard-Licht B', + 'Standard Light C' => 'Standard-Licht C', + 'Tungsten' => 'Glühbirne', + 'Tungsten (Incandescent)' => 'Kunstlicht (Glühbirne)', + 'Unknown' => 'Unbekannt', + 'Warm White Fluorescent' => 'Neonlicht warmweiß', + 'White Fluorescent' => 'Neonlicht universalweiß', + }, + }, + 'LightSourceSpecial' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'LightValue' => 'Lichtwert(ISO)', + 'Lightness' => 'Graustufung', + 'LinearizationTable' => 'Linearisierungstabelle', + 'Lit' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'LiveViewAFAreaMode' => { + Description => 'Live-View AF-Modus', + PrintConv => { + 'Face-Priority' => 'Gesichtserkennung', + 'NormalArea' => 'Normaler Bereich', + 'SubjectTracking' => 'Objektnachführung', + 'WideArea' => 'Großer Bereich', + }, + }, + 'LiveViewAFMethod' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'LiveViewAFMode' => 'Live-View AF-Modus', + 'LiveViewAFSetting' => { + Description => 'Live View AF Einstellung', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'LiveViewExposureSimulation' => { + Description => 'Livebild-Belichtungssimulator', + PrintConv => { + 'Disable (LCD auto adjust)' => 'Inaktiv (automatische LCD-Anzeige)', + 'Enable (simulates exposure)' => 'Aktiv (simuliert Belichtung)', + }, + }, + 'LiveViewFocusMode' => { + Description => 'Live-View Fokus-Modus', + PrintConv => { + 'Manual' => 'Manuell', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'LiveViewMetering' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'LiveViewShooting' => { + Description => 'Live View Aufnahme', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'LocalizedCameraModel' => 'Lokalisiertes Kameramodell', + 'Location' => 'Aufnahmeort', + 'LockMicrophoneButton' => { + Description => 'Mikrofone-Tastenfunktion', + PrintConv => { + 'Protect (hold:record memo)' => 'Geschützt (drücken:Tonaufnahme)', + 'Record memo (protect:disable)' => 'Tonaufnahme (ungeschützt)', + }, + }, + 'LongExposureNoiseReduction' => { + Description => 'Langzeit-Rauschminderung', + PrintConv => { + 'Auto' => 'Automatisch', + 'Off' => 'Aus', + 'On' => 'Ein', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'LongExposureNoiseReduction2' => { + Description => 'Langzeit Rauschunterdrückung 2', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + 'On (1D)' => 'Ein (1D)', + }, + }, + 'Luminance' => 'Luminanz', + 'LuminanceNoiseReduction' => { + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Leicht', + 'Off' => 'Aus', + }, + }, + 'MB-D10Batteries' => { + Description => 'Akku-/Batterietyp', + PrintConv => { + 'FR6 (AA lithium)' => 'FR6 (Mignon, Lithium)', + 'HR6 (AA Ni-MH)' => 'HR6 (Mignon, NiMH)', + 'LR6 (AA alkaline)' => 'LR6 (Mignon, Alkaline)', + 'ZR6 (AA Ni-Mn)' => 'ZR6 (Mignon, NiMn)', + }, + }, + 'MB-D10BatteryType' => 'Akku-/Batterietyp', + 'MB-D80Batteries' => { + Description => 'Akku-/Batterietyp', + PrintConv => { + 'FR6 (AA Lithium)' => 'FR6 (Mignon-Lithium)', + 'HR6 (AA Ni-MH)' => 'HR6 (Mignon-Ni-MH)', + 'LR6 (AA Alkaline)' => 'LR6 (Mignon-Alkaline)', + 'ZR6 (AA Ni-Mg)' => 'ZR6 (Mignon-Ni-Mg)', + }, + }, + 'MB-D80BatteryType' => 'MB-D80 Batterietyp', + 'MCUVersion' => 'MCU-Version', + 'MD5Digest' => 'MD5 Kennwert', + 'MIEVersion' => 'MIE-Version', + 'MIMEType' => 'MIME-Typ', + 'MPImage' => 'MP Vorschaubild', + 'MPImageFlags' => 'MP Vorschaubild Flags', + 'MPImageFormat' => 'MP Vorschaubild Format', + 'MPImageLength' => 'MP Vorschaubild-Datenlänge', + 'MPImageStart' => 'MP Vorschaubild Datenposition', + 'MPImageType' => { + Description => 'MP Vorschaubild Typ', + PrintConv => { + 'Large Thumbnail (VGA equivalent)' => 'Vorschaubild groß (VGA)', + 'Large Thumbnail (full HD equivalent)' => 'Vorschaubild groß (HD)', + 'Multi-frame Panorama' => 'Mehrbild Panorama', + 'Undefined' => 'Nicht definiert', + }, + }, + 'MSStereo' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Macro' => { + Description => 'Makro', + PrintConv => { + 'Macro' => 'Makro', + 'Manual' => 'Manuell', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Super Macro' => 'Super-Makro', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'MacroLED' => { + Description => 'Makro LED', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'MacroMagnification' => 'Makro Vergrößerung', + 'MacroMode' => { + Description => 'Makro-Modus', + PrintConv => { + 'Macro' => 'Makro', + 'Macro Zoom' => 'Makro-Zoom', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Super Macro' => 'Super-Makro', + 'Tele-Macro' => 'Tele-Makro', + }, + }, + 'MagicFilter' => { + PrintConv => { + 'Drawing' => 'Zeichnung', + 'Fish Eye' => 'Fischauge', + 'Off' => 'Aus', + 'Reflection' => 'Reflektierung', + 'Soft Focus' => 'Weichzeichner', + 'Soft Focus 2' => 'Weichzeichner 2', + 'Sparkle' => 'Perleffekt', + 'Watercolor' => 'Wasserfarben', + }, + }, + 'MagnifiedView' => { + Description => 'Lupenfunktion', + PrintConv => { + 'Image playback only' => 'Nur bei Bildwiedergabe', + 'Image review and playback' => 'Sofortbild u. Wiedergabe', + }, + }, + 'MainDialExposureComp' => { + Description => 'Main Dial Belichtungskorrektur', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Make' => 'Gerätehersteller', + 'MakeAndModel' => 'Hersteller und Modell', + 'MakerNote' => 'Hersteller-eigene Daten', + 'MakerNoteApple' => 'Herstellerdaten Apple', + 'MakerNoteCanon' => 'Herstellerdaten Canon', + 'MakerNoteCasio' => 'Herstellerdaten Casio', + 'MakerNoteCasio2' => 'Herstellerdaten Casio 2', + 'MakerNoteFLIR' => 'Herstellerdaten FLIR', + 'MakerNoteFujiFilm' => 'Herstellerdaten Fuji Film', + 'MakerNoteGE' => 'Herstellerdaten GE', + 'MakerNoteGE2' => 'Herstellerdaten GE2', + 'MakerNoteHP' => 'Herstellerdaten HP', + 'MakerNoteHP2' => 'Herstellerdaten HP2', + 'MakerNoteHP4' => 'Herstellerdaten HP4', + 'MakerNoteHP6' => 'Herstellerdaten HP6', + 'MakerNoteHasselblad' => 'Herstellerdaten Hasselblad', + 'MakerNoteISL' => 'Herstellerdaten ISL', + 'MakerNoteJVC' => 'Herstellerdaten JVC', + 'MakerNoteJVCText' => 'Herstellerdaten JVC Text', + 'MakerNoteKodak10' => 'Herstellerdaten Kodak 10', + 'MakerNoteKodak1a' => 'Herstellerdaten Kodak 1a', + 'MakerNoteKodak1b' => 'Herstellerdaten Kodak 1b', + 'MakerNoteKodak2' => 'Herstellerdaten Kodak 2', + 'MakerNoteKodak3' => 'Herstellerdaten Kodak 3', + 'MakerNoteKodak4' => 'Herstellerdaten Kodak 4', + 'MakerNoteKodak5' => 'Herstellerdaten Kodak 5', + 'MakerNoteKodak6a' => 'Herstellerdaten Kodak 6a', + 'MakerNoteKodak6b' => 'Herstellerdaten Kodak 6b', + 'MakerNoteKodak7' => 'Herstellerdaten Kodak 7', + 'MakerNoteKodak8a' => 'Herstellerdaten Kodak 8a', + 'MakerNoteKodak8b' => 'Herstellerdaten Kodak 8b', + 'MakerNoteKodak9' => 'Herstellerdaten Kodak 9', + 'MakerNoteKodakUnknown' => 'Herstellerdaten Kodak Unbekannt', + 'MakerNoteKyocera' => 'Herstellerdaten Kyocera', + 'MakerNoteLeica' => 'Herstellerdaten Leica', + 'MakerNoteLeica2' => 'Herstellerdaten Leica 2', + 'MakerNoteLeica3' => 'Herstellerdaten Leica 3', + 'MakerNoteLeica4' => 'Herstellerdaten Leica 4', + 'MakerNoteLeica5' => 'Herstellerdaten Leica 5', + 'MakerNoteLeica6' => 'Herstellerdaten Leica 6', + 'MakerNoteMinolta' => 'Herstellerdaten Minolta', + 'MakerNoteMinolta2' => 'Herstellerdaten Minolta 2', + 'MakerNoteMinolta3' => 'Herstellerdaten Minolta 3', + 'MakerNoteNikon' => 'Herstellerdaten Nikon', + 'MakerNoteNikon2' => 'Herstellerdaten Nikon 2', + 'MakerNoteNikon3' => 'Herstellerdaten Nikon 3', + 'MakerNoteOlympus' => 'Herstellerdaten Olympus', + 'MakerNoteOlympus2' => 'Herstellerdaten Olympus 2', + 'MakerNotePanasonic' => 'Herstellerdaten Panasonic', + 'MakerNotePanasonic2' => 'Herstellerdaten Panasonic 2', + 'MakerNotePentax' => 'Herstellerdaten Pentax', + 'MakerNotePentax2' => 'Herstellerdaten Pentax 2', + 'MakerNotePentax3' => 'Herstellerdaten Pentax 3', + 'MakerNotePentax4' => 'Herstellerdaten Pentax 4', + 'MakerNotePentax5' => 'Herstellerdaten Pentax 5', + 'MakerNotePentax6' => 'Herstellerdaten Pentax 6', + 'MakerNotePhaseOne' => 'Herstellerdaten Phase One', + 'MakerNoteReconyx' => 'Herstellerdaten Reconyx', + 'MakerNoteRicoh' => 'Herstellerdaten Ricoh', + 'MakerNoteRicohText' => 'Herstellerdaten Ricoh Text', + 'MakerNoteSafety' => { + Description => 'Sicherheit der Hersteller-Informationsdaten', + PrintConv => { + 'Safe' => 'Sicher', + 'Unsafe' => 'Unsicher', + }, + }, + 'MakerNoteSamsung1a' => 'Herstellerdaten Samsung 1a', + 'MakerNoteSamsung1b' => 'Herstellerdaten Samsung 1b', + 'MakerNoteSamsung2' => 'Herstellerdaten Samsung 2', + 'MakerNoteSanyo' => 'Herstellerdaten Sanyo', + 'MakerNoteSanyoC4' => 'Herstellerdaten Sanyo C4', + 'MakerNoteSanyoPatch' => 'Herstellerdaten Sanyo Patch', + 'MakerNoteSigma' => 'Herstellerdaten Sigma', + 'MakerNoteSony' => 'Herstellerdaten Sony', + 'MakerNoteSony2' => 'Herstellerdaten Sony 2', + 'MakerNoteSony3' => 'Herstellerdaten Sony 3', + 'MakerNoteSony4' => 'Herstellerdaten Sony 4', + 'MakerNoteSony5' => 'Herstellerdaten Sony 5', + 'MakerNoteSonyEricsson' => 'Herstellerdaten Sony Ericsson', + 'MakerNoteSonySRF' => 'Herstellerdaten Sony SRF', + 'MakerNoteType' => 'Benutzerdaten Typ', + 'MakerNoteUnknown' => 'Herstellerdaten Unbekannt', + 'MakerNoteUnknownBinary' => 'Herstellerdaten Unbekannt-Binär', + 'MakerNoteUnknownText' => 'Herstellerdaten Unbekannt-Text', + 'MakerNoteVersion' => 'MakerNote-Version', + 'MakerNotes' => 'Hinweise des Herstellers', + 'ManometerPressure' => 'Gemessener Luft- bzw. Wasserdruck', + 'ManometerReading' => 'Berechnete Höhe oder Tauchtiefe', + 'ManualAFPointSelectPattern' => 'Manuelle Wahl der AF-Punkte', + 'ManualFlash' => 'Manueller Blitz', + 'ManualFlashOutput' => { + Description => 'Manuelle Blitzstärke', + PrintConv => { + 'Full' => 'Voll', + 'Low' => 'Gering', + 'Medium' => 'Mittel', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ManualFlashStrength' => 'Manuelle Blitzstärke', + 'ManualFocusDistance' => 'Manuelle Fokusdistanz', + 'ManualTv' => { + Description => 'Manuelle Tv/Av-Einstellung für Manuelle Belichtung', + PrintConv => { + 'Tv=Control/Av=Main' => 'Tv=Schnelleinstellrad/Av=Haupt-Wahlrad', + 'Tv=Main/Av=Control' => 'Tv=Haupt-Wahlrad/Av=Schnelleinstellrad', + }, + }, + 'ManufactureDate' => 'Herstellungsdatum', + 'Marked' => 'Markiert', + 'MasterDocumentID' => 'ID des Originaldokuments', + 'MatrixMetering' => 'Mehrfeldmessung', + 'MaxAperture' => 'Größte Blende', + 'MaxApertureAtCurrentFocal' => 'Größte Blende bei aktueller Brennweite', + 'MaxApertureAtMaxFocal' => 'Größte Blende bei größter Brennweite', + 'MaxApertureAtMinFocal' => 'Größte Blende bei geringster Brennweite', + 'MaxApertureValue' => 'Größtmögliche Blende', + 'MaxContinuousRelease' => 'Max. Bildanzahl pro Serie', + 'MaxFocalLength' => 'Größte Brennweite', + 'MaxJPEGTableIndex' => 'Größter Index JPEG Tabellen', + 'MaxSampleValue' => 'Größter Sample Wert', + 'MeasuredEV' => 'Gemessener LW', + 'MeasuredLV' => 'Lichtwert gemessen', + 'MeasuredRGGB' => 'Messung RGGB', + 'MeasuredRGGBData' => 'Messung RGGB', + 'MeasurementBacking' => 'Basis der Messung', + 'MeasurementFlare' => 'Messung Lichtschein', + 'MeasurementGeometry' => { + Description => 'Geometrie der Messung', + PrintConv => { + '0/45 or 45/0' => '0/45 oder 45/0', + '0/d or d/0' => '0/d oder d/0', + }, + }, + 'MeasurementIlluminant' => 'Messung Beleuchtung', + 'MeasurementObserver' => 'Messung nach', + 'MediaBlackPoint' => 'Medium-Schwarzpunkt', + 'MediaWhitePoint' => 'Medium-Weißpunkt', + 'Medium' => 'Mittelgroß', + 'MenuButtonDisplayPosition' => { + Description => 'Positionsanzeige Menuetaste', + PrintConv => { + 'Previous' => 'Vorherige Anzeige', + 'Previous (top if power off)' => 'Vorherige (Anfang nach AUS)', + 'Top' => 'Oben', + }, + }, + 'MenuButtonReturn' => { + PrintConv => { + 'Previous' => 'Vorherige Anzeige', + 'Top' => 'Oben', + }, + }, + 'MetadataCreator' => 'Metadaten Ersteller', + 'MetadataDate' => 'Datum der Metadaten', + 'MetadataID' => 'Metadaten ID', + 'Metering' => { + Description => 'Belichtungsmessung', + PrintConv => { + 'Center-weighted' => 'Mittenbetont', + 'Matrix' => 'Mehrfeldmessung', + 'Spot' => 'Spotmessung', + }, + }, + 'MeteringMode' => { + Description => 'Belichtungsmessmethode', + PrintConv => { + 'Average' => 'Integralmessung', + 'Center-weighted Average' => 'Mittenbetont', + 'Center-weighted average' => 'Mittenbetont', + 'Default' => 'System', + 'Evaluative' => 'Mehrfeldmessung', + 'Multi-segment' => 'Multi-Segment', + 'Multi-spot' => 'MultiSpot', + 'Other' => 'Andere', + 'Partial' => 'Teilbild', + 'Spot' => 'Spotmessung', + 'Spot+Highlight control' => 'Spot+Helligkeitsbetont', + 'Spot+Shadow control' => 'Spot+Schattenbetont', + 'Unknown' => 'Unbekannt', + }, + }, + 'MeteringMode2' => { + Description => 'Belichtungs-Messmethode 2', + PrintConv => { + 'Center-weighted average' => 'Mittenbetont', + 'Multi-segment' => 'Multi-Segment', + }, + }, + 'MeteringMode3' => { + Description => 'Belichtungs-Messmethode (3)', + PrintConv => { + 'Multi-segment' => 'Multi-Segment', + }, + }, + 'MeteringTime' => { + Description => 'Ausschaltzeit Belichtungsmesser', + PrintConv => { + 'No Limit' => 'Unbegrenzt', + }, + }, + 'MinAperture' => 'Kleinste Blende', + 'MinApertureValue' => 'Kleinste Blende', + 'MinFocalLength' => 'Kleinste Brennweite', + 'MinSampleValue' => 'Kleinster Sample Wert', + 'MiniatureFilterOrientation' => { + PrintConv => { + 'Vertical' => 'Vertikal', + }, + }, + 'MinoltaCameraSettings2' => 'Kameraeinstellungen 2', + 'MinoltaCameraSettings5D' => 'Kameraeinstellungen (5D)', + 'MinoltaCameraSettings7D' => 'Kameraeinstellungen (7D)', + 'MinoltaDate' => 'Minolta-Datum', + 'MinoltaImageSize' => { + Description => 'Minolta-Bildgröße', + PrintConv => { + 'Full' => 'Volle Größe', + 'Large' => 'Groß', + 'Medium' => 'Mittelgroß', + 'Small' => 'Klein', + }, + }, + 'MinoltaMakerNote' => 'Minolta-Herstellerinformationen', + 'MinoltaModelID' => 'Minolta-Modell ID', + 'MinoltaQuality' => { + Description => 'Minolta-Bildqualität', + PrintConv => { + 'Extra Fine' => 'Extra-Fein', + 'Fine' => 'Fein', + 'RAW+JPEG' => 'RAW + JPEG', + 'Super Fine' => 'Super-Fein', + }, + }, + 'MinoltaTime' => 'Minolta-Zeit', + 'MirrorLockup' => { + Description => 'Spiegelverriegelung', + PrintConv => { + 'Disable' => 'Ausgeschaltet', + 'Enable' => 'Eingeschaltet', + 'Enable: Down with Set' => 'Eingeschaltet: Abwärts mit SET (Taste)', + }, + }, + 'ModeDialPosition' => { + PrintConv => { + 'Aperture-priority AE' => 'Blendenpriorität', + 'Manual' => 'Manuell', + 'No Flash' => 'Kein Blitz', + 'Program AE' => 'Programmautomatik', + 'Shutter speed priority AE' => 'Verschlußpriorität', + }, + }, + 'Model' => 'Kameramodell', + 'Model2' => 'Kameramodell (2)', + 'ModelID' => 'Modell ID', + 'ModelingFlash' => { + Description => 'Einstelllicht', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ModifiedColorTemp' => 'Geänderte Farbtemperatur', + 'ModifiedDigitalGain' => 'Digitale Verstärkung geändert', + 'ModifiedPictureStyle' => { + Description => 'Geänderter Bildstil', + PrintConv => { + 'Faithful' => 'Natürlich', + 'Landscape' => 'Landschaft', + 'Monochrome' => 'Monochrom', + 'None' => 'Keiner', + 'Portrait' => 'Porträt', + 'User Def. 1' => 'Benutzerdefiniert 1', + 'User Def. 2' => 'Benutzerdefiniert 2', + 'User Def. 3' => 'Benutzerdefiniert 3', + }, + }, + 'ModifiedSaturation' => { + PrintConv => { + 'Off' => 'Aus', + }, + }, + 'ModifiedSharpness' => 'Schärfe verändert', + 'ModifiedSharpnessFreq' => { + PrintConv => { + 'High' => 'Hoch', + 'Highest' => 'Höchste', + 'Low' => 'Leicht', + 'Lowest' => 'Niedrigste', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ModifiedToneCurve' => { + Description => 'Tonwertkurve verändert', + PrintConv => { + 'Custom' => 'Benutzerdefiniert', + 'Manual' => 'Manuell', + }, + }, + 'ModifiedWhiteBalance' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Black & White' => 'Schwarz/Weiß', + 'Cloudy' => 'Bewölkt', + 'Custom' => 'Benutzerdefiniert', + 'Custom 1' => 'Benutzerdefiniert 1', + 'Custom 2' => 'Benutzerdefiniert 2', + 'Custom 3' => 'Benutzerdefiniert 3', + 'Custom 4' => 'Benutzerdefiniert 4', + 'Daylight' => 'Tageslicht', + 'Daylight Fluorescent' => 'Neonlicht tageslichtweiß', + 'Flash' => 'Blitz', + 'Fluorescent' => 'Neonlicht', + 'Manual Temperature (Kelvin)' => 'Manuelle Temperatur (Kelvin)', + 'Shade' => 'Schatten', + 'Tungsten' => 'Glühbirne', + 'Underwater' => 'Unterwasser', + }, + }, + 'ModifyDate' => 'Änderungsdatum', + 'MoireFilter' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'MonitorOffTime' => 'Ausschaltzeit des Monitors', + 'MonochromeFilterEffect' => { + Description => 'Filtereffekt Monochrom', + PrintConv => { + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + }, + }, + 'MonochromeLinear' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'MonochromeToningEffect' => { + Description => 'Tönungseffekt Monochrom', + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Purple' => 'Lila', + }, + }, + 'Month' => 'Monat', + 'MultiBurstImageHeight' => 'Multi-Burst Bildhöhe', + 'MultiBurstImageWidth' => 'Multi-Burst Bildbreite', + 'MultiBurstMode' => { + Description => 'Mehrfach-Burst Modus', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'MultiControllerWhileMetering' => { + Description => 'Multicontroller bei Messung', + PrintConv => { + 'AF point selection' => 'AF-Punkt-Auswahl', + 'Off' => 'Aus', + }, + }, + 'MultiExposure' => 'Mehrfachbelichtungsdaten', + 'MultiExposureAutoGain' => { + Description => 'Mehrfachbelichtung Automatik', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'MultiExposureMode' => { + Description => 'Mehrfachbelichtungsmodus', + PrintConv => { + 'Image Overlay' => 'Bildüberlagerung', + 'Multiple Exposure' => 'Mehrfachbelichtung', + 'Off' => 'Aus', + }, + }, + 'MultiExposureShots' => 'Mehrfachbelichtung Anzahl Aufnahmen', + 'MultiExposureVersion' => 'Mehrfachbelichtungsdaten-Version', + 'MultiFrameNoiseReduction' => { + Description => 'Ruisond. Multi Frame', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'MultiFunctionLock' => { + PrintConv => { + 'Main dial' => 'Haupt-Wahlrad', + 'Multi-controller' => 'Multicontroller', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Quick control dial' => 'Schnelleinstellrad', + }, + }, + 'MultiSelector' => { + Description => 'Multifunktionswähler', + PrintConv => { + 'Do Nothing' => 'Ohne Funktion', + 'Reset Meter-off Delay' => 'Ruhezustand verzögern', + }, + }, + 'MultiSelectorPlaybackMode' => { + Description => 'Mitteltaste Bei Wiedergabe', + PrintConv => { + 'Choose Folder' => 'Ordner auswählen', + 'Thumbnail On/Off' => 'Bildindex ein/aus', + 'View Histograms' => 'Histogramme anzeigen', + 'Zoom On/Off' => 'Ausschnitt ein/aus', + }, + }, + 'MultiSelectorShootMode' => { + Description => 'Mitteltaste Bei Aufnahme', + PrintConv => { + 'Highlight Active Focus Point' => 'AF-Messfeld hervorheben', + 'Not Used' => 'Ohne Funktion', + 'Select Center Focus Point' => 'Mittleres AF-Messfeld', + }, + }, + 'MultipleExposureMode' => { + PrintConv => { + 'Off' => 'Aus', + 'On (2 frames)' => 'Ein (2 Bilder', + 'On (3 frames)' => 'Ein (3 Bilder', + }, + }, + 'MultipleExposureSet' => { + Description => 'Mehrfachbelichtung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Mute' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'MyColorMode' => { + Description => 'My Color-Modus', + PrintConv => { + 'B&W' => 'Schwarz/Weiß', + 'Custom' => 'Benutzerdefiniert', + 'Off' => 'Aus', + }, + }, + 'MyColors' => 'My Color-Modus', + 'NDFilter' => { + Description => 'ND-Filter', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'NEFBitDepth' => { + PrintConv => { + 'n/a (JPEG)' => 'Nicht gesetzt (JPEG)', + }, + }, + 'NEFCompression' => { + Description => 'RAW-Komprimierung', + PrintConv => { + 'Lossless' => 'Verlustfrei', + 'Lossy (type 1)' => 'Verlustbehaftet (Type 1)', + 'Lossy (type 2)' => 'Verlustbehaftet (Type 2)', + 'Uncompressed' => 'Nicht komprimiert', + }, + }, + 'NEFLinearizationTable' => 'Linearisierungstabelle', + 'NativeDigest' => 'EXIF Kennwert', + 'NetExposureCompensation' => 'Net Belichtungskorrektur', + 'NewMieTag1' => 'Weiterer MIE Tag 1', + 'NewPngTag1' => 'Weiterer PNG Tag 1', + 'NewPngTag2' => 'Weiterer PNG Tag 2', + 'NewPngTag3' => 'Weiterer PNG Tag 3', + 'NewRawImageDigest' => 'Neuer RAW Image Kennwert', + 'NewsPhotoVersion' => 'IPTC-Modell-3-Version', + 'NikonCaptureData' => 'Nikon Capture-Daten', + 'NikonCaptureOutput' => 'Nikon Capture-Ausgabe', + 'NikonCaptureVersion' => 'Nikon Capture-Version', + 'NikonICCProfile' => 'Nikon ICC-Profil', + 'NikonImageSize' => { + Description => 'Nikon Bildgröße', + PrintConv => { + 'Large' => 'Groß', + 'Large (10.0 M)' => 'Groß (10.0M)', + 'Medium' => 'Mittel', + 'Medium (5.6 M)' => 'Mittel (5.6M)', + 'Small' => 'Klein', + 'Small (2.5 M)' => 'Klein (2.5M)', + }, + }, + 'NoMemoryCard' => { + Description => 'Auslösesperre', + PrintConv => { + 'Enable Release' => 'Aus', + 'Release Locked' => 'Ein', + }, + }, + 'Noise' => 'Bildrauschen', + 'NoiseFilter' => { + Description => 'Rauschfilter', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Leicht', + 'Off' => 'Aus', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'NoiseReduction' => { + Description => 'Rauschreduktion', + PrintConv => { + 'Auto' => 'Automatisch', + 'High (+1)' => 'Hoch (+1)', + 'Highest (+2)' => 'Am höchsten (+2)', + 'Low' => 'Gering', + 'Low (-1)' => 'Niedrig (-1)', + 'Lowest (-2)' => 'Am niedrigsten (+2)', + 'Max' => 'Maximal', + 'Noise Filter' => 'Rauschfilter', + 'Noise Filter (ISO Boost)' => 'Rauschfilter (ISO Boost)', + 'Noise Reduction' => 'Rauschreduktion', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Strong' => 'Stark', + 'Weak' => 'Schwach', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'NoiseReduction2' => { + Description => 'Rauschunterdrückung 2', + PrintConv => { + 'Noise Filter' => 'Rauschfilter', + 'Noise Filter (ISO Boost)' => 'Rauschfilter (ISO Boost)', + 'Noise Reduction' => 'Rauschreduktion', + }, + }, + 'NoiseReductionIntensity' => 'Stärke Rauschunterdrückung', + 'NoiseReductionMethod' => { + Description => 'Rauschunterdrückung Methode', + PrintConv => { + 'Better Quality' => 'Bessere Qualität"', + 'Faster' => 'Schneller', + }, + }, + 'NoiseReductionMode' => { + Description => 'Rauschunterdrückungsmodus', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'NoiseReductionSharpness' => 'Rauschunterdrückung Schärfe', + 'NoiseReductionValue' => 'Rauschunterdrückungswert', + 'NominalMaxAperture' => 'Nominaler AVmin', + 'NominalMinAperture' => 'Nominaler AVmax', + 'Notes' => 'Hinweise', + 'NumAFPoints' => 'Anzahl der AF-Punkte', + 'NumChannels' => 'Anzahl Kanäle', + 'NumColors' => 'Anzahl Farben', + 'NumFacePositions' => 'Gesichtspositionen', + 'NumImportantColors' => 'Anzahl Hauptfarben', + 'NumberOfFocusPoints' => { + Description => 'Anzahl AF-Punkte', + PrintConv => { + '11 Points' => '11 Punkte', + '39 Points' => '39 Punkte', + }, + }, + 'NumberOfImages' => 'Anzahl Bilder', + 'NumberOfPlanes' => 'Anzahl Ebenen', + 'OPIProxy' => { + PrintConv => { + 'Higher resolution image does not exist' => 'Höher aufgelöstes Bild nicht vorhanden', + 'Higher resolution image exists' => 'Höher aufgelöstes Bild vorhanden', + }, + }, + 'ObjectAttributeReference' => 'Gattung', + 'ObjectCycle' => { + Description => 'Objektzyklus', + PrintConv => { + 'Both Morning and Evening' => 'Beides', + 'Evening' => 'Abends', + 'Morning' => 'Morgens', + }, + }, + 'ObjectDistance' => 'Objektabstand', + 'ObjectFileType' => { + Description => 'Objekt Dateityp', + PrintConv => { + 'Executable file' => 'Ausführbare Datei', + 'None' => 'Keiner', + 'Unknown' => 'Unbekannt', + }, + }, + 'ObjectName' => 'Titel', + 'ObjectPreviewData' => 'Objektdatenvorschau', + 'ObjectPreviewFileFormat' => 'Dateiformat der Objektdatenvorschau', + 'ObjectPreviewFileVersion' => 'Dateiformatversion der Objektdatenvorschau', + 'ObjectTypeReference' => 'Objekttypreferenz', + 'OffsetSchema' => 'Offset-Schema', + 'OldSubfileType' => { + Description => 'Unterdatei-Typ', + PrintConv => { + 'Full-resolution image' => 'Bild in voller Auflösung', + 'Reduced-resolution image' => 'Bild in reduzierter Auflösung', + 'Single page of multi-page image' => 'Einzelbild eines mehrseitigen Bildes', + }, + }, + 'OlympusImageHeight' => 'Olympus-Bildhöhe', + 'OlympusImageWidth' => 'Olympus-Bildbreite', + 'OneTouchWB' => { + Description => 'Sofort-Weißabgleich', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + 'On (Preset)' => 'Ein (Preset)', + }, + }, + 'OperatingSystem' => { + PrintConv => { + 'unknown' => 'Unbekannt', + }, + }, + 'OpticalZoom' => 'Optischer Zoom', + 'OpticalZoomCode' => 'Optischer Zoom-Code', + 'OpticalZoomMode' => { + Description => 'Optischer Zoom-Modus', + PrintConv => { + 'Extended' => 'Erweitert', + }, + }, + 'OpticalZoomOn' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Opto-ElectricConvFactor' => 'Optoelektronischer Umrechnungsfaktor', + 'OrderNumber' => 'Auftragsnummer', + 'Organization' => 'Organisation', + 'Orientation' => { + Description => 'Ausrichtung', + PrintConv => { + 'Mirror horizontal' => 'Horizontal gespiegelt', + 'Mirror horizontal and rotate 270 CW' => 'Horizontal gespiegelt und 90° gegen den Uhrzeigersinn', + 'Mirror horizontal and rotate 90 CW' => 'Horizontal gespiegelt und 90° im Uhrzeigersinn', + 'Mirror vertical' => 'Vertikal gespiegelt', + 'Rotate 180' => '180° gedreht', + 'Rotate 270 CW' => '90° gegen den Uhrzeigersinn', + 'Rotate 90 CW' => '90° im Uhrzeigersinn', + }, + }, + 'Orientation2' => { + Description => 'Ausrichtung 2', + PrintConv => { + 'Rotate 180' => '180° gedreht', + 'Rotate 270 CW' => '90° gegen den Uhrzeigersinn', + 'Rotate 90 CW' => '90° im Uhrzeigersinn', + }, + }, + 'OriginalFileName' => 'Original Dateiname', + 'OriginalFileType' => 'Original Dateityp', + 'OriginalImageHeight' => 'Bildbreite Original', + 'OriginalImageWidth' => 'Bildbreite Original', + 'OriginalRawCreator' => 'Original RAW Ersteller', + 'OriginalRawFileData' => 'Original Raw Daten', + 'OriginalRawFileDigest' => 'Original RAW Image Kennwert', + 'OriginalRawFileName' => 'Original Raw Dateiname', + 'OriginalRawFileType' => 'Original RAW Dateityp', + 'OriginalRawImage' => 'Original RAW Bild', + 'OriginalRawResource' => 'Original RAW Basisdaten', + 'OriginalTHMCreator' => 'Original THM Ersteller', + 'OriginalTHMFileType' => 'Original THM Dateityp', + 'OriginalTHMImage' => 'Original THM Bild', + 'OriginalTHMResource' => 'Original THM Basisdaten', + 'OriginalTransmissionReference' => 'Anbietervermerk Verweis', + 'OriginatingProgram' => 'Erstellungsprogramm', + 'OtherImage' => 'Other Image Vorschaubild', + 'OtherImageLength' => 'OtherImage Datenlänge', + 'OtherImageStart' => 'Other Image Datenposition', + 'OutputImageHeight' => 'Ausgabe-Bildhöhe', + 'OutputImageWidth' => 'Ausgabe-Bildbreite', + 'OutputResolution' => 'Ausgabe Auflösung', + 'OverlayPlanes' => 'Überlagerungsebenen', + 'Owner' => 'Besitzer', + 'OwnerID' => 'Besitzer-ID', + 'OwnerName' => 'Name des Besitzers', + 'PEFVersion' => 'PEF-Version', + 'PNGWarning' => 'PNG Warnung', + 'Padding' => 'Platzhalter', + 'PageName' => 'Seitenname', + 'PageNumber' => 'Seitenummer', + 'Pages' => 'Seiten', + 'PanOrientation' => { + Description => 'Pan Ausrichtung', + PrintConv => { + 'Bottom to top' => 'Von Unten nach Oben', + 'Clockwise' => 'Im Uhrzeigersinn', + 'Counter clockwise' => 'Gegen den Uhrzeigersinn', + 'Left to right' => 'Von Links nach Rechts', + 'Right to left' => 'Von Rechts nach Links', + 'Start at bottom left' => 'Links unten beginnend', + 'Start at bottom right' => 'Rechts unten beginnend', + 'Start at top left' => 'Links oben beginnend', + 'Start at top right' => 'Rechts oben beginnend', + 'Top to bottom' => 'Von Oben nach Unten', + '[unused]' => '[nicht verwendet]', + }, + }, + 'PanasonicImageHeight' => 'Panasonic Bildhöhe', + 'PanasonicImageWidth' => 'Panasonic Bildbreite', + 'PanasonicTitle' => 'Titel', + 'PanoramaCropBottom' => 'Panorama Ausschnitt Unten', + 'PanoramaCropLeft' => 'Panorama Ausschnitt Links', + 'PanoramaCropRight' => 'Panorame Ausschnitt Rechts', + 'PanoramaCropTop' => 'Panorama Ausschnitt Oben', + 'PanoramaDirection' => { + Description => 'Panorama-Richtung', + PrintConv => { + '2x2 Matrix (Clockwise)' => '2x2 Matrix (im Uhrzeigersinn)', + 'Bottom to Top' => 'Unten nach Oben', + 'Left to Right' => 'Links nach Rechts', + 'Right to Left' => 'Rechts nach Links', + 'Top to Bottom' => 'Oben nach Unten', + }, + }, + 'PanoramaFrameNumber' => 'Panorama-Bild', + 'PanoramaMode' => 'Panorama Modus', + 'PanoramaSize3D' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'PentaxImageSize' => { + Description => 'Pentax-Bildgröße', + PrintConv => { + '2304x1728 or 2592x1944' => '2304 x 1728 oder 2592 x 1944', + '2560x1920 or 2304x1728' => '2560 x 1920 oder 2304 x 1728', + '2816x2212 or 2816x2112' => '2816 x 2212 oder 2816 x 2112', + '3008x2008 or 3040x2024' => '3008 x 2008 oder 3040 x 2024', + 'Full' => 'Voll', + }, + }, + 'PentaxModelID' => 'Pentax-Modell ID', + 'PentaxVersion' => 'Pentax-Version', + 'People' => 'Menschen', + 'Permits' => { + PrintConv => { + 'Distribution' => 'Verteilung', + 'Reproduction' => 'Reproduktion', + }, + }, + 'PhaseDetectAF' => { + Description => 'Auto-Fokus', + PrintConv => { + 'Off' => 'Aus', + 'On (11-point)' => 'Ein (11-Punkt)', + 'On (39-point)' => 'Ein (39 Punkte)', + 'On (51-point)' => 'Ein (51-Punkt)', + 'On (hybrid)' => 'Ein (Hybrid)', + }, + }, + 'PhoneNumber' => 'Telefonnummer', + 'PhotoEffect' => { + Description => 'Foto-Effekt', + PrintConv => { + 'B&W' => 'Schwarz/Weiß', + 'Custom' => 'Benutzerdefiniert', + 'Off' => 'Aus', + }, + }, + 'PhotoEffects' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'PhotoEffectsData' => 'Bildeffekt-Daten', + 'PhotoEffectsType' => { + Description => 'Bildeffekt-Methode', + PrintConv => { + 'B&W' => 'Schwarz/Weiß', + 'None' => 'Keine', + 'Tinted' => 'Getont', + }, + }, + 'PhotoInfoPlayback' => { + Description => 'Bildinfos & Wiedergabe', + PrintConv => { + 'Info Left-right, Playback Up-down' => 'Info <> / Wiedergabe', + 'Info Up-down, Playback Left-right' => 'Info / Wiedergabe <>', + }, + }, + 'PhotoStyle' => { + Description => 'Fotostil', + PrintConv => { + 'Monochrome' => 'Schwarz/Weiß', + 'Natural' => 'Natürlich', + 'Portrait' => 'Porträt', + 'Scenery' => 'Szene', + 'Standard or Custom' => 'Standard oder benutzerdefiniert', + 'Vivid' => 'Lebendig', + }, + }, + 'PhotometricInterpretation' => { + Description => 'Pixel-Schema', + PrintConv => { + 'BlackIsZero' => 'Schwarz ist Null', + 'Color Filter Array' => 'CFA (Farbfiltermatrix)', + 'Pixar LogL' => 'CIE Log2(L) (Log Luminanz)', + 'Pixar LogLuv' => 'CIE Log2(L)(u\',v\') (Log Luminanz und Chrominanz)', + 'Transparency Mask' => 'Transparenzmaske', + 'WhiteIsZero' => 'Weiß ist Null', + }, + }, + 'PhotoshopBGRThumbnail' => 'Photoshop BGR-Vorschaubild', + 'PhotoshopFormat' => 'Photoshop-Format', + 'PhotoshopQuality' => 'Photoshop-Qualität', + 'PhysicalImageSize' => 'Physikalische Bildgröße', + 'PictureControl' => { + Description => 'Bildoptimierung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'PictureControlActive' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'PictureControlAdjust' => { + Description => 'Bildoptimierung-Anpassung', + PrintConv => { + 'Default Settings' => 'Standardeinstellungen', + 'Full Control' => 'Manuelle Einstellung', + 'Quick Adjust' => 'Schnelleinstellung', + }, + }, + 'PictureControlBase' => 'Bildoptimierung-Basis', + 'PictureControlName' => 'Bildoptimierung-Name', + 'PictureControlQuickAdjust' => 'Bildoptimierung-Schnelleinstellung', + 'PictureControlVersion' => 'Bildoptimierung-Version', + 'PictureFinish' => { + PrintConv => { + 'Evening Scene' => 'Abendszene', + 'Monochrome' => 'Monochrom', + 'Natural' => 'Natürlich', + 'Natural+' => 'Natürlich+', + 'Night Portrait' => 'Nachtporträt', + 'Night Scene' => 'Nachtszene', + 'Portrait' => 'Porträt', + 'Wind Scene' => 'Windszene', + }, + }, + 'PictureMode' => { + Description => 'Motivprogramm', + PrintConv => { + '1/2 EV steps' => '1/2 LW Schritte', + '1/3 EV steps' => '1/3 LW Schitte', + 'Anti-blur' => 'Motivschärfe-Modus', + 'Aperture Priority' => 'Zeitautomatik', + 'Aperture Priority, Off-Auto-Aperture' => 'Zeitautomatik (Blendenring nicht auf A)', + 'Aperture-priority AE' => 'Blendenpriorität', + 'Auto' => 'Automatisch', + 'Auto PICT (Landscape)' => 'Auto PICT (Landschaft)', + 'Auto PICT (Macro)' => 'Auto PICT (Makro)', + 'Auto PICT (Portrait)' => 'Auto PICT (Porträt)', + 'Auto PICT (Sport)' => 'Auto PICT (Motiv in Bewegung)', + 'Auto PICT (Standard)' => 'Auto PICT (Normal)', + 'Autumn' => 'Herbst', + 'Backlight Silhouette' => 'Hintergrundbeleuchtung Silhouette', + 'Beach' => 'Strand', + 'Beach & Snow' => 'Strand & Schnee', + 'Black & White' => 'Schwarz/Weiß', + 'Blue' => 'Blau', + 'Blur Control' => 'Bildstabilisierung', + 'Blur Reduction' => 'Unschärfereduktion', + 'Bulb' => 'Bulb-Modus', + 'Bulb, Off-Auto-Aperture' => 'Bulb (Blendenring nicht auf A)', + 'Candlelight' => 'Kerzenlicht', + 'DOF Program' => 'Schärfentiefe-Priorität', + 'DOF Program (HyP)' => 'Schärfentiefe-Priorität (Hyper-Programm)', + 'Dark Pet' => 'Haustier (Dunkel)', + 'Digital Filter' => 'Digitalfilter', + 'Fireworks' => 'Feuerwerk', + 'Fisheye' => 'Fischauge', + 'Flash X-Sync Speed AE' => 'Blitz X-synch. Zeit', + 'Flower' => 'Blumen', + 'Food' => 'Lebensmittel', + 'Forest' => 'Wald', + 'Frame Composite' => 'Rahmen', + 'Green' => 'Grün', + 'Green Mode' => 'Grüner Modus', + 'Half-length Portrait' => 'Brustbild', + 'Hi-speed Program' => 'HS-Priorität', + 'Hi-speed Program (HyP)' => 'HS-Priorität (Hyper-Programm)', + 'Illustrations' => 'Dokument', + 'Kids' => 'Kinder', + 'Landscape' => 'Landschaft', + 'Light Pet' => 'Haustier (Hell)', + 'MTF Program' => 'MTF-Priorität', + 'MTF Program (HyP)' => 'MTF-Priorität (Hyper-Programm)', + 'Macro' => 'Makro', + 'Manual' => 'Manuell', + 'Manual, Off-Auto-Aperture' => 'Manuell (Blendenring nicht auf A)', + 'Medium Pet' => 'Haustier (Neutrale Helligkeit)', + 'Natural' => 'Natur', + 'Natural Light' => 'Umgebungslicht', + 'Natural Light & Flash' => 'Umgebungslicht & Blitz', + 'Natural Skin Tone' => 'Nat. Hautton', + 'Night Scene' => 'Nachtszene', + 'Night Scene HDR' => 'Nachtszene', + 'Night Scene Portrait' => 'Nacht-Porträt', + 'No Flash' => 'Kein Blitz', + 'Pet' => 'Haustiere', + 'Pink' => 'Rosa', + 'Portrait' => 'Porträt', + 'Portrait 2' => 'Porträt 2', + 'Program' => 'Programmautomatik', + 'Program (HyP)' => 'Programmautomatik (Hyper-Programm)', + 'Program AE' => 'Programmautomatik', + 'Program Av Shift' => 'Av Shift-Belichtungsprogramm', + 'Program Tv Shift' => 'Tv Shift-Belichtungsprogramm', + 'Purple' => 'Violett', + 'Red' => 'Rot', + 'Self Portrait' => 'Selbstporträt', + 'Sensitivity Priority AE' => 'Blenden- & Zeitautomatik (Sv, ISO-Vorgabe)', + 'Shutter & Aperture Priority AE' => 'Empfindlichkeitsautomatik (TAv, Zeit-/Blendenvorgabe)', + 'Shutter Speed Priority' => 'Verschlusspriorität', + 'Shutter speed priority AE' => 'Verschlusspriorität', + 'Snow' => 'Schnee', + 'Soft' => 'Soft (Weichzeichnung)', + 'Sports' => 'Sport', + 'Sunset' => 'Sonnenuntergang', + 'Surf & Snow' => 'Surf & Schnee', + 'Synchro Sound Record' => 'Synchr. Sprachnotiz', + 'Underwater' => 'Unterwasser', + 'Yellow' => 'Gelb', + }, + }, + 'PictureMode2' => { + Description => 'Motivprogramm 2', + PrintConv => { + 'Aperture Priority' => 'Blendenpriorität', + 'Aperture Priority, Off-Auto-Aperture' => 'Zeitautomatik (Blendenring nicht auf A)', + 'Bulb' => 'Bulb-Modus', + 'Bulb, Off-Auto-Aperture' => 'Bulb (Blendenring nicht auf A)', + 'Flash X-Sync Speed AE' => 'Blitz X-synch. Zeit', + 'Green Mode' => '"Grünes" AE-Programm', + 'Manual' => 'Manuell', + 'Manual, Off-Auto-Aperture' => 'Manuell (Blendenring nicht auf A)', + 'Program AE' => 'Programmautomatik', + 'Program Av Shift' => 'Av Shift-Belichtungsprogramm', + 'Program Tv Shift' => 'Tv Shift-Belichtungsprogramm', + 'Scene Mode' => 'Motivprogramm', + 'Sensitivity Priority AE' => 'Blenden- & Zeitautomatik (Sv, ISO-Vorgabe)', + 'Shutter & Aperture Priority AE' => 'Empfindlichkeitsautomatik (TAv, Zeit-/Blendenvorgabe)', + 'Shutter Speed Priority' => 'Verschlusspriorität', + }, + }, + 'PictureModeBWFilter' => { + Description => 'Motivprogramm S/W Filter', + PrintConv => { + 'Green' => 'Grün', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'PictureModeContrast' => 'Motivprogramm Kontrast', + 'PictureModeEffect' => { + Description => 'Motivprogramm Effekt', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Niedrig', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'PictureModeHue' => 'Motivprogramm Farbton', + 'PictureModeSaturation' => 'Motivprogramm Farbsättigung', + 'PictureModeSharpness' => 'Motivprogramm Schärfe', + 'PictureModeTone' => { + Description => 'Motivprogramm Tonwert', + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'Purple' => 'Lila', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'PictureStyle' => { + Description => 'Bildstil', + PrintConv => { + 'Custom' => 'Benutzerdefiniert', + 'Faithful' => 'Natürlich', + 'High Saturation' => 'Hohe Farbsättigung', + 'Landscape' => 'Landschaft', + 'Low Saturation' => 'Geringe Farbsättigung', + 'Monochrome' => 'Monochrom', + 'None' => 'Keiner', + 'Portrait' => 'Porträt', + 'Unknown?' => 'Unbekannt?', + 'User Def. 1' => 'Benutzerdefiniert 1', + 'User Def. 2' => 'Benutzerdefiniert 2', + 'User Def. 3' => 'Benutzerdefiniert 3', + }, + }, + 'PictureWizardMode' => { + PrintConv => { + 'Forest' => 'Wald', + 'Landscape' => 'Landschaft', + 'Portrait' => 'Porträt', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'PixelFormat' => { + Description => 'Pixel-Format', + PrintConv => { + 'Black & White' => 'Schwarz/Weiß', + }, + }, + 'PixelUnits' => { + Description => 'Pixel Einheit', + PrintConv => { + 'Unknown' => 'Unbekannt', + 'meters' => 'Meter', + }, + }, + 'PixelsPerMeterX' => 'Pixel per Meter X', + 'PixelsPerMeterY' => 'Pixel per Meter Y', + 'PixelsPerUnitX' => 'Pixel pro X-Einheit', + 'PixelsPerUnitY' => 'Pixel pro Y-Einheit', + 'PlanarConfiguration' => { + Description => 'Bilddatenausrichtung', + PrintConv => { + 'Chunky' => 'Kompaktformat', + 'Planar' => 'Ebenes Format', + }, + }, + 'Planes' => 'Ebenen', + 'PostalCode' => 'Postleitzahl', + 'PowerSource' => { + Description => 'Stromquelle', + PrintConv => { + 'Body Battery' => 'Batterie im Gehäuse', + 'External Power Supply' => 'Externe Stromversorgung', + 'Grip Battery' => 'Batterie im Griff', + }, + }, + 'Predictor' => { + Description => 'Prädiktor', + PrintConv => { + 'Horizontal differencing' => 'Horizontale Differenzierung', + 'None' => 'Kein Prädiktor-Schema in Benutzung', + }, + }, + 'PresetWhiteBalance' => { + Description => 'Weißabgleich Voreinstellung', + PrintConv => { + 'Cloudy' => 'Bewölkt', + 'Daylight' => 'Tageslicht', + 'Flash' => 'Blitz', + 'Fluorescent' => 'Neonlicht', + 'Shade' => 'Schatten', + 'Tungsten' => 'Glühbirne', + }, + }, + 'PresetWhiteBalanceAdj' => 'Weißabgleichkorrektur-Einstellung', + 'Preview' => 'Preview-IFD-Zeiger', + 'Preview0' => 'Vorschau 0', + 'Preview1' => 'Vorschau 1', + 'Preview2' => 'Vorschau 2', + 'PreviewColorSpace' => { + PrintConv => { + 'Unknown' => 'Unbekannt', + }, + }, + 'PreviewCropBottom' => 'Vorschau Ausschnitt Unten', + 'PreviewCropLeft' => 'Vorschau Ausschnitt Links', + 'PreviewCropRight' => 'Vorschau Ausschnitt Rechts', + 'PreviewCropTop' => 'Vorschau Ausschnitt Oben', + 'PreviewIFD' => 'Preview-IFD-Zeiger', + 'PreviewImage' => 'Vorschaubild', + 'PreviewImageBorders' => 'Vorschaubild-Ränder', + 'PreviewImageData' => 'Vorschaubild-Daten', + 'PreviewImageHeight' => 'Vorschaubild-Höhe', + 'PreviewImageLength' => 'Vorschaubild-Datenlänge', + 'PreviewImageName' => 'Vorschaubild-Name', + 'PreviewImageSize' => 'Vorschaubild-Größe', + 'PreviewImageStart' => 'Vorschaubild-Datenposition', + 'PreviewImageType' => 'Vorschaubild-Typ', + 'PreviewImageValid' => { + Description => 'Vorschaubild gültig', + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'PreviewImageWidth' => 'Vorschaubild-Breite', + 'PreviewPNG' => 'PNG Vorschaubild', + 'PreviewQuality' => { + Description => 'Vorschaubild-Qualität', + PrintConv => { + 'Fine' => 'Fein', + 'Superfine' => 'Superfein', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'PreviewSettingsDigest' => 'Kennwert der Voreinstellungen', + 'PreviewWMF' => 'WMF Vorschaubild', + 'PrimaryAFPoint' => { + Description => 'Primärer AF-Punkt', + PrintConv => { + '(none)' => '(keiner)', + 'Bottom' => 'Unten', + 'C6 (Center)' => 'C6 (Mitte)', + 'Center' => 'Mitte', + 'Far Left' => 'Weit links', + 'Far Right' => 'Weit rechts', + 'Lower-left' => 'Links unten', + 'Lower-right' => 'Rechts unten', + 'Mid-left' => 'Links mitte', + 'Mid-right' => 'Rechts mitte', + 'Top' => 'Oben', + 'Upper-left' => 'Links oben', + 'Upper-right' => 'Rechts oben', + }, + }, + 'PrimaryChromaticities' => 'Chromatizität der Primärfarben', + 'PrimaryPlatform' => 'Hauptplattform', + 'ProcessingInfo' => 'Verarbeitungsinformationen', + 'ProcessingSoftware' => 'Verarbeitungssoftware', + 'Producer' => 'Produzent', + 'ProducerKeywords' => 'Hersteller Schlüsselwörter', + 'Producers' => 'Produzent', + 'ProductID' => 'Produkt-ID', + 'ProductionCode' => 'Herstellungskennzeichen', + 'ProfileCMMType' => 'Profil CMM-Typ', + 'ProfileClass' => { + Description => 'Profil-Klasse', + PrintConv => { + 'Abstract Profile' => 'Abstract-Profil', + 'ColorSpace Conversion Profile' => 'Farbraum-Konvertierungsprofile', + 'DeviceLink Profile' => 'DeviceLink-Profil', + 'Display Device Profile' => 'Bildschirm-Geräteprofil', + 'Input Device Profile' => 'Eingabe-Geräteprofil', + 'NamedColor Profile' => 'Named Color-Profil', + 'Nikon Input Device Profile (NON-STANDARD!)' => 'Nikon-Profil ("nkpf")', + 'Output Device Profile' => 'Ausgabe-Geräteprofil', + }, + }, + 'ProfileConnectionSpace' => 'Profil-Verbindungsfarbraum', + 'ProfileCopyright' => 'Urheberrechtsvermerk', + 'ProfileCreator' => 'Profilersteller', + 'ProfileDateTime' => 'Profil-Erstellungszeit', + 'ProfileDescription' => 'Farbprofil Name', + 'ProfileDescriptionML' => 'Farbprofil Name mehrsprachig', + 'ProfileFileSignature' => 'Profil-Datei-Signatur', + 'ProfileID' => 'Profile-ID', + 'ProfileSequenceDesc' => 'Profilsequenz-Beschreibung', + 'ProfileToneCurve' => 'Tonwertkurve Profil', + 'ProfileType' => { + Description => 'Profiltyp', + PrintConv => { + 'Group 3 FAX' => 'Gruppe 3 Fax', + 'Unspecified' => 'Nicht bekannt', + }, + }, + 'ProfileVersion' => 'Profil-Version', + 'ProgramISO' => { + Description => 'ISO Programm', + PrintConv => { + 'Intelligent ISO' => 'ISO intelligent', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ProgramLine' => { + Description => 'Belichtungsprogrammtyp', + PrintConv => { + 'Depth' => 'Schärfentiefe-Priorität', + 'Hi Speed' => 'HS-Priorität', + 'MTF' => 'MTF-Priorität', + }, + }, + 'ProgramMode' => { + Description => 'Programmmodus', + PrintConv => { + 'Night Portrait' => 'Nachtporträt', + 'None' => 'Keiner', + 'Portrait' => 'Porträt', + 'Sports' => 'Sport', + 'Sunset' => 'Sonnenuntergang', + }, + }, + 'ProgramShift' => 'Programmverschiebung', + 'ProgramVersion' => 'Programmversion', + 'Prohibits' => { + Description => 'Verbote', + PrintConv => { + 'Commercial Use' => 'Kommerzielle Verwendung', + }, + }, + 'Protect' => 'Schutz', + 'Province-State' => 'Bundesland/Kanton', + 'Publisher' => 'Herausgeber', + 'Quality' => { + Description => 'Qualität', + PrintConv => { + 'Best' => 'Optimal', + 'Better' => 'Besser', + 'Compressed RAW' => 'Komprimiertes RAW', + 'Compressed RAW + JPEG' => 'Komprimiertes RAW + JPEG', + 'Extra Fine' => 'Extra-Fein', + 'Fine' => 'Fein', + 'Good' => 'Gut', + 'High' => 'Hoch', + 'Low' => 'Leicht', + 'Normal' => 'Standardqualität', + 'RAW + JPEG' => 'RAW+JPEG', + 'Super Fine' => 'Super-Fein', + 'Superfine' => 'Superfein', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'Quality2' => 'Qualität 2', + 'QualityMode' => { + PrintConv => { + 'Fine' => 'Fein', + }, + }, + 'QuickAdjust' => 'Schnelleinstellung', + 'QuickControlDialInMeter' => { + Description => 'Schnelleinstellrad bei Messung', + PrintConv => { + 'AF point selection' => 'Auswahl des AF-Messfelds', + 'Exposure comp/Aperture' => 'Belichtungskorrektur/Blende', + 'ISO speed' => 'ISO-Empfindlichkeit', + }, + }, + 'QuickShot' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'RAFVersion' => 'RAF-Version', + 'RasterizedCaption' => 'Rasterbeschreibung', + 'Rating' => 'Bewertung', + 'RatingPercent' => 'Bewertung in Prozent', + 'RawAndJpgRecording' => { + Description => 'Dateiformat und JPEG-Qualität', + PrintConv => { + 'JPEG (Best)' => 'JPEG (Optimal)', + 'JPEG (Better)' => 'JPEG (Besser)', + 'JPEG (Good)' => 'JPEG (Gut)', + 'RAW (DNG, Best)' => 'RAW (DNG, Optimal)', + 'RAW (DNG, Better)' => 'RAW (DNG, Besser)', + 'RAW (DNG, Good)' => 'RAW (DNG, Gut)', + 'RAW (PEF, Best)' => 'RAW (PEF, Optimal)', + 'RAW (PEF, Better)' => 'RAW (PEF, Besser)', + 'RAW (PEF, Good)' => 'RAW (PEF, Gut)', + 'RAW+JPEG (DNG, Best)' => 'RAW+JPEG (DNG, Optimal)', + 'RAW+JPEG (DNG, Better)' => 'RAW+JPEG (DNG, Besser)', + 'RAW+JPEG (DNG, Good)' => 'RAW+JPEG (DNG, Gut)', + 'RAW+JPEG (PEF, Best)' => 'RAW+JPEG (PEF, Optimal)', + 'RAW+JPEG (PEF, Better)' => 'RAW+JPEG (PEF, Besser)', + 'RAW+JPEG (PEF, Good)' => 'RAW+JPEG (PEF, Gut)', + 'RAW+Large/Fine' => 'RAW+Groß/Fein', + 'RAW+Large/Normal' => 'RAW+Groß/Normal', + 'RAW+Medium/Fine' => 'RAW+Mittel/Fein', + 'RAW+Medium/Normal' => 'RAW+Mittel/Normal', + 'RAW+Small/Fine' => 'RAW+Klein/Fein', + 'RAW+Small/Normal' => 'RAW+Klein/Normal', + }, + }, + 'RawBrightnessAdj' => 'Raw Helligkeitskorrektur', + 'RawColorAdj' => { + Description => 'Raw Farbkorrektur', + PrintConv => { + 'Custom' => 'Benutzerdefiniert', + 'Faithful' => 'Natürlich', + 'Shot Settings' => 'Aufnahmeeinstellung', + }, + }, + 'RawCropBottom' => 'Raw Ausschnitt Unten', + 'RawCropLeft' => 'Raw Ausschnitt Links', + 'RawCropRight' => 'Raw Ausschnitt Rechts', + 'RawCropTop' => 'Raw Ausschnitt Oben', + 'RawData' => 'Raw-Daten', + 'RawDataByteOrder' => 'RAW Daten Bytereihenfolge', + 'RawDataLength' => 'RAW-Daten Länge', + 'RawDataOffset' => 'RAW-Daten Offset', + 'RawDataUniqueID' => 'Raw-Daten eindeutige ID', + 'RawDevArtFilter' => { + PrintConv => { + 'Drawing' => 'Zeichnung', + 'Fish Eye' => 'Fischauge', + 'Off' => 'Aus', + 'Reflection' => 'Reflektierung', + 'Soft Focus' => 'Weichzeichner', + 'Soft Focus 2' => 'Weichzeichner 2', + 'Sparkle' => 'Perleffekt', + 'Watercolor' => 'Wasserfarbe', + 'Watercolor II' => 'Wasserfarbe II', + }, + }, + 'RawDevAutoGradation' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'RawDevNoiseReduction' => { + Description => 'RAW Rauschunterdrückung', + PrintConv => { + 'Noise Filter' => 'Rauschfilter', + 'Noise Filter (ISO Boost)' => 'Rauschfilter (ISO Boost)', + 'Noise Reduction' => 'Rauschreduktion', + }, + }, + 'RawDevPMPictureTone' => { + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'Purple' => 'Lila', + }, + }, + 'RawDevPM_BWFilter' => { + PrintConv => { + 'Green' => 'Grün', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + }, + }, + 'RawDevPictureMode' => { + PrintConv => { + 'Natural' => 'Natürlich', + }, + }, + 'RawDevWhiteBalance' => { + PrintConv => { + 'Color Temperature' => 'Farbtemperatur', + }, + }, + 'RawFile' => 'RAW Datei', + 'RawFileName' => 'RAW Dateiname', + 'RawImageCenter' => 'RAW-Bildmitte', + 'RawImageDigest' => 'RAW Image Kennwert', + 'RawImageHeight' => 'Raw Bildhöhe', + 'RawImageSegmentation' => 'Raw Bild Aufteilung', + 'RawImageSize' => 'RAW-Bildgröße', + 'RawImageWidth' => 'Raw Bildbreite', + 'RawInfoVersion' => 'RawInfo Version', + 'RawJpgQuality' => { + Description => 'RAW JPEG-Qualität', + PrintConv => { + 'Fine' => 'Fein', + 'Superfine' => 'Superfein', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'RawJpgSize' => { + Description => 'RAW JPEG-Größe', + PrintConv => { + 'Large' => 'Groß', + 'Medium' => 'Mittelgroß', + 'Medium 1' => 'Mittelgroß 1', + 'Medium 2' => 'Mittelgroß 2', + 'Medium 3' => 'Mittelgroß 3', + 'Medium Movie' => 'Mittelgroßer Film', + 'Postcard' => 'Postkarte', + 'Small' => 'Klein', + 'Small Movie' => 'Kleiner Film', + 'Widescreen' => 'Breitbild', + }, + }, + 'RawMeasuredRGGB' => 'Raw Messung RGGB', + 'RecognizedFace1Age' => 'Alter erkanntes Gesicht 1', + 'RecognizedFace1Name' => 'Name erkanntes Gesicht 1', + 'RecognizedFace1Position' => 'Position erkanntes Gesicht 1', + 'RecognizedFace2Age' => 'Alter erkanntes Gesicht 2', + 'RecognizedFace2Name' => 'Name erkanntes Gesicht 2', + 'RecognizedFace2Position' => 'Position erkanntes Gesicht 2', + 'RecognizedFace3Age' => 'Alter erkanntes Gesicht 3', + 'RecognizedFace3Name' => 'Name erkanntes Gesicht 3', + 'RecognizedFace3Position' => 'Position erkanntes Gesicht 3', + 'RecordMode' => { + Description => 'Aufzeichnungsmodus', + PrintConv => { + 'Aperture Priority' => 'Blendenpriorität', + 'Manual' => 'Manuell', + 'Shutter Priority' => 'Verschlusspriorität', + }, + }, + 'RecordingMode' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Landscape' => 'Landschaft', + 'Manual' => 'Manuell', + 'Night Scene' => 'Nachtszene', + 'Portrait' => 'Porträt', + }, + }, + 'RedAdjust' => 'Rot-Korrektur', + 'RedBalance' => 'Farbabgleich Rot', + 'RedEyeCorrection' => { + Description => 'Rote-Augen-Reduzierung', + PrintConv => { + 'Automatic' => 'Automatisch', + 'Click on Eyes' => 'Klick auf Augen', + 'Off' => 'Aus', + }, + }, + 'RedEyeReduction' => { + Description => 'Rote Augen Reduzierung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'RedMatrixColumn' => 'Rot-Matrixspalte', + 'RedSaturation' => 'Farbsättingung Rot', + 'RedTRC' => 'Rot-Tonwertwiedergabe-Kurve', + 'RedX' => 'Rotpunkt X', + 'RedY' => 'Rotpunkt Y', + 'ReductionMatrix1' => 'Reduktionsmatrix 1', + 'ReductionMatrix2' => 'Reduktionsmatrix 2', + 'ReferenceBlackWhite' => 'Schwarz-Weiß-Referenzpunkte', + 'ReferenceDate' => 'Referenzdatum', + 'ReferenceNumber' => 'Referenznummer', + 'ReferenceService' => 'Referenzdienst', + 'References' => 'Verweise', + 'RelatedImageFileFormat' => 'Dateiformat der Bilddaten', + 'RelatedImageHeight' => 'Bildhöhe', + 'RelatedImageWidth' => 'Bildbreite', + 'RelatedSoundFile' => 'Zugehörige Audio-Datei', + 'ReleaseButtonToUseDial' => { + Description => 'Tastenverhalten', + PrintConv => { + 'No' => 'Gedrückt halten', + 'Yes' => 'Ein & aus', + }, + }, + 'ReleaseDate' => 'Veröffentlichungsdatum', + 'ReleaseMode' => { + Description => 'Auslösemodus', + PrintConv => { + 'AE Bracketing' => 'Belichtungsreihe', + 'Contrast Bracketing' => 'Kontrast Belichtungsreihe', + 'High Speed Burst' => 'Aufnahme Burst', + 'WB Bracketing' => 'Weißabgleich-Belichtungsreihe', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ReleaseTime' => 'Veröffentlichungszeit', + 'RemoteOnDuration' => 'Fernauslöser', + 'RenderingIntent' => { + Description => 'Umrechnungsmethode', + PrintConv => { + 'ICC-Absolute Colorimetric' => 'Absolut farbmetrisch', + 'Media-Relative Colorimetric' => 'Relativ farbmetrisch', + 'Perceptual' => 'Wahrnehmungsorientiert (perzeptiv, fotografisch)', + 'Saturation' => 'Sättigungserhaltend', + }, + }, + 'RepeatingFlashCount' => 'Stroboskopblitz Anzahl', + 'RepeatingFlashOutput' => 'Stroboskopblitz Leistung', + 'RepeatingFlashRate' => 'Stroboskopblitz Freq.', + 'ResampleParamsQuality' => { + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Leicht', + }, + }, + 'Resaved' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'Resolution' => 'Bildauflösung', + 'ResolutionUnit' => { + Description => 'Einheit der X- und Y-Auflösung', + PrintConv => { + 'None' => 'Keine', + 'inches' => 'Zoll', + }, + }, + 'RetouchHistory' => { + Description => 'Bildbearbeitungsschritte', + PrintConv => { + 'B & W' => 'Schwarz/Weiß', + 'Color Custom' => 'Farbabgleich', + 'Cyanotype' => 'Blauton', + 'Distortion Control' => 'Verzeichnungskontrolle', + 'Fisheye' => 'Fischauge', + 'Image Overlay' => 'Bildmontage', + 'None' => 'Keine', + 'Perspective Control' => 'Perspektivenkontrolle', + 'Red Eye' => 'Rote-Augen-Korrektur', + 'Sky Light' => 'Skylight', + 'Small Picture' => 'Kompaktbild', + 'Soft Filter' => 'Weichzeichner', + 'Trim' => 'Beschneiden', + 'Warm Tone' => 'Warmer Farbton', + }, + }, + 'ReverseIndicators' => 'Skalen spiegeln', + 'ReverseShutterSpeedAperture' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'RevisionDate' => 'Revisionsdatum', + 'RevisionNumber' => 'Revisionsnummer', + 'RicohDate' => 'Ricoh Datum', + 'RicohImageHeight' => 'Ricoh-Bildhöhe', + 'RicohImageWidth' => 'Ricoh-Bildbreite', + 'Rights' => 'Rechte', + 'Rotation' => { + Description => 'Ausrichtung', + PrintConv => { + 'Horizontal (Normal)' => 'Horizontal (normal)', + 'Rotate 180' => '180° gedreht', + 'Rotate 270 CW' => '90° gegen den Uhrzeigersinn', + 'Rotate 90 CW' => '90° im Uhrzeigersinn', + 'Rotated 180' => '180° gedreht', + 'Rotated 270 CW' => '90° gegen den Uhrzeigersinn', + 'Rotated 90 CW' => '90° im Uhrzeigersinn', + }, + }, + 'RowsPerStrip' => 'Anzahl der Bild-Zeilen', + 'SPIFFVersion' => 'SPIFF-Version', + 'SRAWQuality' => { + Description => 'SRAW Qualität', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SRActive' => { + Description => 'Bildstabilisator', + PrintConv => { + 'No' => 'Deaktiviert', + 'Yes' => 'Aktiviert', + }, + }, + 'SRFocalLength' => 'SR Brennweite', + 'SRHalfPressTime' => 'Auslöseverzögerung', + 'SRResult' => { + Description => 'Bildstabilisator', + PrintConv => { + 'Not stabilized' => 'Nicht stabilisiert', + }, + }, + 'SVGVersion' => 'SVG-Version', + 'SafetyShift' => { + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable (ISO speed)' => 'Möglich (ISO Empfindlichkeit)', + 'Enable (Tv/Av)' => 'Möglich (Tv/Av)', + }, + }, + 'SafetyShiftInAvOrTv' => { + Description => 'Safety Shift in AV oder TV', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'SampleFormat' => { + PrintConv => { + 'Complex int' => 'Komplexer Integer', + 'Float' => 'Fließkommawert', + 'Signed' => 'Vorzeichenbehafteter Integer', + 'Undefined' => 'Nicht definiert', + 'Unsigned' => 'Vorzeichenloser Integer', + }, + }, + 'SamplesPerPixel' => 'Anzahl der Komponenten', + 'Saturation' => { + Description => 'Farbsättigung', + PrintConv => { + '+1 (medium high)' => '+1 (Leicht erhöht)', + '+2 (high)' => '+2 (Hohe Farbsättigung)', + '+3 (very high)' => '+3 (Sehr hoch)', + '+4 (highest)' => '+4', + '+4 (maximum)' => '+4', + '-1 (medium low)' => '-1 (Leicht verringert)', + '-2 (low)' => '-2 (Geringe Farbsättigung)', + '-3 (very low)' => '-3 (Sehr gering)', + '-4 (lowest)' => '-4', + '-4 (minimum)' => '-4', + '0 (normal)' => '0 (Normal)', + 'B&W' => 'Schwarz/Weiß', + 'B&W Green Filter' => 'Schwarz-Weiß Grünfilter', + 'B&W Red Filter' => 'Schwarz-Weiß Rotfilter', + 'B&W Sepia' => 'Schwarz-Weiß Sepia', + 'B&W Yellow Filter' => 'Schwarz-Weiß Gelbfilter', + 'Black & White' => 'Schwarz/Weiß', + 'Film Simulation' => 'Film-Simulation', + 'High' => 'Hohe Farbsättigung', + 'Low' => 'Geringe Farbsättigung', + 'Medium High' => 'Mittel-Hoch', + 'Medium Low' => 'Mittel-Gering', + 'Natural' => 'Natürlich', + 'None' => 'Nicht gesetzt', + 'None (B&W)' => 'Keine (S&W)', + 'Toning Effect' => 'Tönungseffekt', + 'Vintage B&W' => 'Vintage Schwarz-Weiß', + 'Vivid' => 'Lebhaft', + }, + }, + 'SaturationAdj' => 'Sättigungskorrektur', + 'SaturationAdjustmentAqua' => 'Farbsättigung Korrektur Cyan', + 'SaturationAdjustmentBlue' => 'Farbsättigung Korrektur Blau', + 'SaturationAdjustmentGreen' => 'Farbsättigung Korrektur Grün', + 'SaturationAdjustmentMagenta' => 'Farbsättigung Korrektur Magenta', + 'SaturationAdjustmentOrange' => 'Farbsättigung Korrektur Orange', + 'SaturationAdjustmentPurple' => 'Farbsättigung Korrektur Lila', + 'SaturationAdjustmentRed' => 'Farbsättigung Korrektur Rot', + 'SaturationAdjustmentYellow' => 'Farbsättigung Korrektur Gelb', + 'SaturationFaithful' => { + Description => 'Farbsättigung Natürlich', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SaturationInfo' => 'Farbsättigung', + 'SaturationLandscape' => { + Description => 'Farbsättigung Landschaft', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SaturationMonochrome' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SaturationNeutral' => { + Description => 'Farbsättigung Neutral', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SaturationPlanes' => 'Sättigungsebenen', + 'SaturationPortrait' => { + Description => 'Farbsättigung Porträt', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SaturationSetting' => 'Sättigungseinstellung', + 'SaturationStandard' => { + Description => 'Farbsättigung Standard', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SaturationUnknown' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SaturationUserDef1' => { + Description => 'Farbsättigung Benutzerdefiniert 1', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SaturationUserDef2' => { + Description => 'Farbsättigung Benutzerdefiniert 2', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SaturationUserDef3' => { + Description => 'Farbsättigung Benutzerdefiniert 3', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ScaleFactor35efl' => 'Formatfaktor zu 35 mm', + 'ScanImageEnhancer' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Scene' => 'Szene', + 'SceneAssist' => 'Szenen-Assistent', + 'SceneCaptureType' => { + Description => 'Szenenaufnahmetyp', + PrintConv => { + 'Landscape' => 'Landschaft', + 'Night' => 'Nachtszene', + 'Portrait' => 'Porträt', + }, + }, + 'SceneMode' => { + Description => 'Szenen-Modus', + PrintConv => { + '3D Sweep Panorama' => '3D Sweep-Panorama', + 'Anti Motion Blur' => 'Verwackelungsschutz', + 'Aperture Priority' => 'Blendenpriorität', + 'Auto' => 'Automatisch', + 'Available Light' => 'Verfügbares Licht', + 'Baby' => 'Kleinkind', + 'Beach' => 'Strand', + 'Beach & Snow' => 'Strand & Schnee', + 'Behind Glass' => 'Hinterglas', + 'Candle' => 'Kerzenlicht', + 'Candlelight' => 'Kerzenlicht', + 'Children' => 'Kinder', + 'Color Effects' => 'Farbeffekte', + 'Cont. Priority AE' => 'Andauernde AE Priorität', + 'Creative Control' => 'Kreativprogramm', + 'Digital Filter' => 'Digitaler Filter', + 'Documents' => 'Dokumente', + 'Fireworks' => 'Feuerwerk', + 'Food' => 'Lebensmittel', + 'Handheld Night Shot' => 'Nachtaufnahme händisch', + 'High Key' => 'High-Key', + 'High Sensitivity' => 'Hohe Empfindlichkeit', + 'Indoor' => 'Innenaufnahme', + 'Intelligent ISO' => 'Iso Intelligent', + 'Landscape' => 'Landschaft', + 'Landscape+Portrait' => 'Landschaft+Porträt', + 'Low Key' => 'Low-Key', + 'Macro' => 'Makro', + 'Manual' => 'Manuell', + 'My Mode' => 'Benutzerdefiniert', + 'Night Landscape' => 'Landschaft bei Nacht', + 'Night Portrait' => 'Nachtporträt', + 'Night Scene' => 'Nachtszene', + 'Night Scenery' => 'Nachtszene', + 'Night View/Portrait' => 'Abendszene/Porträt', + 'Night+Portrait' => 'Nacht+Porträt', + 'Off' => 'Aus', + 'Pet' => 'Haustiere', + 'Portrait' => 'Porträt', + 'Program' => 'Programmautomatik', + 'Self Portrait' => 'Selbstporträt', + 'Self Portrait+Self Timer' => 'Selbstporträt+Selbstauslöser', + 'Shutter Priority' => 'Verschlusspriorität', + 'Snow' => 'Schnee', + 'Sports' => 'Sport', + 'Spot' => 'Spotmessung', + 'Starry Night' => 'Sternennacht', + 'Sunset' => 'Sonnenuntergang', + 'Super Macro' => 'Super-Makro', + 'Sweep Panorama' => 'Sweep-Panorama', + 'Underwater' => 'Unterwasser', + 'Underwater Macro' => 'Unterwasser Makro', + 'Underwater Wide1' => 'Unterwasserlandschaft', + 'Underwater Wide2' => 'Unterwasserlandschaft 2', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SceneModeUsed' => { + Description => 'Szenen-Modus', + PrintConv => { + 'Aperture Priority' => 'Blendenpriorität', + 'Beach' => 'Strand', + 'Candlelight' => 'Kerzenlicht', + 'Children' => 'Kinder', + 'Fireworks' => 'Feuerwerk', + 'Landscape' => 'Landschaft', + 'Macro' => 'Makro', + 'Manual' => 'Manuell', + 'Night Landscape' => 'Landschaft bei Nacht', + 'Night Portrait' => 'Nachtporträt', + 'Portrait' => 'Porträt', + 'Program' => 'Programmautomatik', + 'Shutter Priority' => 'Verschlusspriorität', + 'Snow' => 'Schnee', + 'Sunset' => 'Sonnenuntergang', + }, + }, + 'SceneSelect' => { + PrintConv => { + 'Night' => 'Nachtszene', + 'Off' => 'Aus', + }, + }, + 'SceneType' => { + Description => 'Szenentyp', + PrintConv => { + 'Directly photographed' => 'Direkt aufgenommenes Bild', + }, + }, + 'Security' => { + Description => 'Sicherheit', + PrintConv => { + 'Locked for annotations' => 'Gesperrt für Anmerkungen', + 'None' => 'Keine', + 'Password protected' => 'Passwort geschützt', + 'Read-only enforced' => 'Nur Lesen - erzwungen', + 'Read-only recommended' => 'Nur Lesen - vorgeschlagen', + }, + }, + 'SecurityClassification' => { + Description => 'Sicherheitsklassifizierung', + PrintConv => { + 'Confidential' => 'Vertraulich', + 'Restricted' => 'Eingeschränkt', + 'Secret' => 'Geheim', + 'Top Secret' => 'Streng geheim', + 'Unclassified' => 'Nicht klassifiziert', + }, + }, + 'SelectAFAreaSelectMode' => { + Description => 'AF-Bereich Auswahlmodus', + PrintConv => { + 'Disable' => 'Deaktiviert', + 'Enable' => 'Aktiviert', + 'Select AF-modes' => 'Wahl AF-Modus', + }, + }, + 'SelectableAFPoint' => { + Description => 'Wählbares AF-Feld', + PrintConv => { + '11 points' => '11 Felder', + '19 Points, Multi-controller selectable' => '19 Punkte, wählbar mit Multicontroller', + '19 points' => '19 Felder', + '45 points' => '45 Felder', + 'Inner 9 Points, Multi-controller selectable' => 'Innere 9 Punkte, wählbar mit Multicontroller', + 'Inner 9 points' => 'Innere 9 Felder', + 'Outer 9 Points, Multi-controller selectable' => 'Äußere 9 Punkte, wählbar mit Multicontroller', + 'Outer 9 points' => 'Äußere 9 Felder', + }, + }, + 'SelfTimer' => { + Description => 'Selbstauslöser', + PrintConv => { + '10 s / 3 pictures' => '10 s / 3 Bilder', + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'SelfTimer2' => 'Selbstauslöser (2)', + 'SelfTimerMode' => 'Selbstauslösermodus', + 'SelfTimerTime' => 'Selbstauslöser-Vorlaufzeit', + 'SensingMethod' => { + Description => 'Messmethode', + PrintConv => { + 'Color sequential area' => 'Color-Sequential-Area-Sensor', + 'Color sequential linear' => 'Color-Sequential-Linear-Sensor', + 'Monochrome area' => 'Monochrom-Sensor', + 'Monochrome linear' => 'Monochrom-linearer Sensor', + 'Not defined' => 'Nicht definiert', + 'One-chip color area' => 'Ein-Chip-Farbsensor', + 'Three-chip color area' => 'Drei-Chip-Farbsensor', + 'Trilinear' => 'Trilinearer Sensor', + 'Two-chip color area' => 'Zwei-Chip-Farbsensor', + }, + }, + 'SensitivityAdjust' => 'ISO-Empfindlichkeitsanpassung', + 'SensitivitySteps' => { + Description => 'Empfindlichkeits-Schritte', + PrintConv => { + '1 EV Steps' => '1 LW-Schritte', + 'As EV Steps' => 'Wie LW-Schritte', + }, + }, + 'SensitivityType' => { + Description => 'Art der Empfindlichkeit', + PrintConv => { + 'ISO Speed' => 'ISO Empfindlichkeit', + 'Recommended Exposure Index' => 'Empfohlener Belichtungsindex', + 'Recommended Exposure Index and ISO Speed' => 'Empfohlener Belichtungsindex und ISO Empfindlichkeit', + 'Standard Output Sensitivity' => 'Standard Ausgabeempfindlichkeit', + 'Standard Output Sensitivity and ISO Speed' => 'Standard Ausgabeempfindlichkeit und ISO Empfindlichkeit', + 'Standard Output Sensitivity and Recommended Exposure Index' => 'Standard Ausgabeempfindlichkeit und empfohlener Belichtungsindex', + 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed' => 'Standard Ausgabeempfindlichkeit,empfohlener Belichtungsindex und ISO Empfindlichkeit', + 'Unknown' => 'Unbekannt', + }, + }, + 'SensorBlueLevel' => 'Sensor Blau-Level', + 'SensorBottomBorder' => 'Sensor unterer Rand', + 'SensorCleaning' => { + Description => 'Sensorreinigung', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'SensorHeight' => 'Sensor Höhe', + 'SensorImageHeight' => 'Sensor-Bildhöhe', + 'SensorImageWidth' => 'Sensor-Bildbreite', + 'SensorLeftBorder' => 'Sensor linker Rand', + 'SensorPixelSize' => 'Sensor-Pixelgröße', + 'SensorRedLevel' => 'Sensor Rot-Level', + 'SensorRightBorder' => 'Sensor rechter Rand', + 'SensorSize' => 'Sensorgröße', + 'SensorTemperature' => 'Sensor Temperatur', + 'SensorTopBorder' => 'Sensor oberer Rand', + 'SensorWidth' => 'Sensor Breite', + 'SequenceNumber' => { + Description => 'Bildsequenznummer', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SequentialShot' => { + Description => 'Reihenaufnahme', + PrintConv => { + 'Adjust Exposure' => 'Belichtungskorrektur', + 'Best' => 'Beste', + 'None' => 'Keine', + }, + }, + 'SerialNumber' => 'Seriennummer', + 'SerialNumberFormat' => 'Seriennummer-Format', + 'ServiceIdentifier' => 'Service-ID', + 'SetButtonCrossKeysFunc' => { + Description => 'SET Taste/Kreuztaste Funkt.', + PrintConv => { + 'Cross keys: AF point select' => 'Kreuztaste:AF Feld Auswahl', + 'Set: Flash Exposure Comp' => 'SET:Blitzbelichtungskorrektur', + 'Set: Parameter' => 'SET:Parameter ändern', + 'Set: Picture Style' => 'SET:Bildstil', + 'Set: Playback' => 'SET:Wiedergabe', + 'Set: Quality' => 'SET:Qualität', + }, + }, + 'SetButtonFunction' => 'Funktion SET-Taste b. Aufnahme', + 'SetButtonWhenShooting' => { + Description => 'SET-Taste bei Aufnahme', + PrintConv => { + 'Change ISO speed' => 'ISO-Wert ändern', + 'Change parameters' => 'Parameter ändern', + 'Default (no function)' => 'Normal (gesperrt)', + 'Disabled' => 'Gesperrt', + 'Flash exposure compensation' => 'Blitzbelichtungskorrektur', + 'ISO speed' => 'ISO-Empfindlichkeit', + 'Image playback' => 'Bildwiedergabe', + 'Image quality' => 'Qualität ändern', + 'Image size' => 'Bildgröße', + 'LCD monitor On/Off' => 'LCD-Monitor Ein/Aus', + 'Menu display' => 'Menüanzeige', + 'Normal (disabled)' => 'Normal (gesperrt)', + 'Picture style' => 'Bildstil', + 'Quick control screen' => 'Schnelleinstellung Bildschirm', + 'Record func. + media/folder' => 'Aufnahme-Funktion + Medium/Ordner', + 'Record movie (Live View)' => 'Movie-Aufnahme (Livebild)', + 'White balance' => 'Weißabgleich', + }, + }, + 'SetFunctionWhenShooting' => { + Description => 'SET-Taste bei Aufnahme', + PrintConv => { + 'Change Parameters' => 'Parameter ändern', + 'Change Picture Style' => 'Bildstil', + 'Change quality' => 'Qualität ändern', + 'Default (no function)' => 'Normal (gesperrt)', + 'Image replay' => 'Bildwiedergabe', + 'Menu display' => 'Menüanzeige', + }, + }, + 'ShadingCompensation' => { + Description => 'Schattenaufhellung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ShadingCompensation2' => { + Description => 'Schattenaufhellung 2', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Shadow' => 'Schatten', + 'Shadows' => 'Schatten', + 'ShakeReduction' => { + Description => 'Bildstabilisator (Einstellung)', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ShakeReductionInfo' => 'Bildstabilisator', + 'SharpenDetail' => 'Schärfungsdetail', + 'SharpenRadius' => 'Schärfungsradius', + 'Sharpening' => { + Description => 'Schärfung', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Niedrig', + 'Medium High' => 'Mittel', + 'Off' => 'Aus', + }, + }, + 'SharpeningAdj' => 'Schärfekorrektur', + 'Sharpness' => { + Description => 'Schärfe', + PrintConv => { + '+1 (medium hard)' => '+1 (Leicht erhöht)', + '+2 (hard)' => '+2 (Stark)', + '+3 (very hard)' => '+3 (Sehr hoch)', + '+4 (hardest)' => '+4', + '+4 (maximum)' => '+4', + '-1 (medium soft)' => '-1 (Leicht verringert)', + '-2 (soft)' => '-2 (Leicht)', + '-3 (very soft)' => '-3 (Sehr weich)', + '-4 (minimum)' => '-4', + '-4 (softest)' => '-4', + '0 (normal)' => '0 (Normal)', + 'Film Simulation' => 'Film-Simulation', + 'Hard' => 'Stark', + 'Sharp' => 'Hart', + 'Soft' => 'Leicht', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessAdj' => 'Schärfekorrektur', + 'SharpnessFactor' => 'Schärfungsfaktor', + 'SharpnessFaithful' => { + Description => 'Schärfe Natürlich', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessFrequency' => { + PrintConv => { + 'High' => 'Hoch', + 'Highest' => 'Höchste', + 'Low' => 'Leicht', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessLandscape' => { + Description => 'Schärfe Landschaft', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessMonochrome' => { + Description => 'Schärfe Monochrom', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessNeutral' => { + Description => 'Schärfe Neutral', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessOvershoot' => 'Schärfe Grenzwertüberschreitung', + 'SharpnessPortrait' => { + Description => 'Schärfe Porträt', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessSetting' => 'Schärfeeinstellung', + 'SharpnessStandard' => { + Description => 'Schärfe Standard', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessTable' => 'Schärfe Tabelle', + 'SharpnessThreshold' => 'Schärfe Grenzwert', + 'SharpnessUndershoot' => 'Schärfe Grenzwertunterschreitung', + 'SharpnessUnknown' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessUserDef1' => { + Description => 'Schärfe Benutzerdefiniert 1', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessUserDef2' => { + Description => 'Schärfe Benutzerdefiniert 2', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SharpnessUserDef3' => { + Description => 'Schärfe Benutzerdefiniert 3', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ShootingInfoDisplay' => { + Description => 'Aufnahmeinfo-Ansicht', + PrintConv => { + 'Auto' => 'Automatisch', + 'Manual (dark on light)' => 'Manuell - Dunkel auf Hell', + 'Manual (light on dark)' => 'Manuell - Hell auf dunkel', + }, + }, + 'ShootingMode' => { + Description => 'Aufnahmemodus', + PrintConv => { + 'Aperture Priority' => 'Blendenpriorität', + 'Baby' => 'Kleinkind', + 'Beach' => 'Strand', + 'Candlelight' => 'Kerzenlicht', + 'Color Effects' => 'Farbeffekte', + 'Fireworks' => 'Feuerwerk', + 'Food' => 'Lebensmittel', + 'High Sensitivity' => 'Hohe Empfindlichkeit', + 'Macro' => 'Makro', + 'Manual' => 'Manuell', + 'Night Portrait' => 'Nachtporträt', + 'Night Scenery' => 'Nachtszene', + 'Pet' => 'Haustiere', + 'Portrait' => 'Porträt', + 'Program' => 'Programmautomatik', + 'Self Portrait' => 'Selbstportait', + 'Shutter Priority' => 'Verschlusspriorität', + 'Snow' => 'Schnee', + 'Sports' => 'Sport', + 'Spot' => 'Spotmessung', + 'Starry Night' => 'Sternennacht', + 'Sunset' => 'Sonnenuntergang', + 'Underwater' => 'Unterwasser', + }, + }, + 'ShootingModeSetting' => { + Description => 'Messfeldsteuerung', + PrintConv => { + 'Continuous' => 'Serienaufnahme', + 'Delayed Remote' => 'Fernauslöser m. Vorlauf', + 'Quick-response Remote' => 'Fernauslöser', + 'Self-timer' => 'Selbstauslöser', + 'Single Frame' => 'Einzelbild', + }, + }, + 'ShortDescription' => 'Kurzbeschreibung', + 'ShortDocumentID' => 'Kurze Bild-ID', + 'ShortReleaseTimeLag' => { + Description => 'Verkürzte Auslöseverzögerung', + PrintConv => { + 'Disable' => 'Ausgeschaltet', + 'Enable' => 'Eingeschaltet', + }, + }, + 'ShotInfoVersion' => 'Aufnahmeinfo-Version', + 'Shutter-AELock' => { + Description => 'Auslöser/AE-Speicherung', + PrintConv => { + 'AE lock/AF' => 'AE-Speicherung/AF', + 'AE/AF, No AE lock' => 'AE/AF, keine AE-Speicherung', + 'AF/AE lock' => 'AF/AE-Speicherung', + 'AF/AF lock' => 'AF/AF-Speicherung', + 'AF/AF lock, No AE lock' => 'AF/AF-Speicherung, keine AE-Speicherung', + }, + }, + 'ShutterAELButton' => 'Auslöser/AE-Speichertaste', + 'ShutterButtonAFOnButton' => { + Description => 'Auslöser/AF-Starttaste', + PrintConv => { + 'AE lock/Metering + AF start' => 'AESpeicherung/Messung+AFStart', + 'Metering + AF start' => 'Messung+AFStart', + 'Metering + AF start/AF stop' => 'Messung+AFStart / AFStopp', + 'Metering + AF start/disable' => 'Messung+AFStart/Deaktiviert', + 'Metering start/Meter + AF start' => 'Messung Start/Mess.+AFStart', + }, + }, + 'ShutterCount' => 'Anzahl der Auslösungen', + 'ShutterCurtainSync' => { + Description => 'Verschluss-Synchronisation', + PrintConv => { + '1st-curtain sync' => '1. Verschlussvorhang', + '2nd-curtain sync' => '2. Verschlussvorhang', + }, + }, + 'ShutterMode' => { + PrintConv => { + 'Aperture Priority' => 'Blendenpriorität', + 'Auto' => 'Automatisch', + }, + }, + 'ShutterReleaseButtonAE-L' => { + Description => 'Belichtungsspeicher', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ShutterReleaseMethod' => { + PrintConv => { + 'Continuous Shooting' => 'Serienaufnahme', + 'Single Shot' => 'Einzelbild', + }, + }, + 'ShutterReleaseNoCFCard' => { + Description => 'Verschlussausl. ohne Karte', + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'ShutterReleaseTiming' => { + PrintConv => { + 'Priority on focus' => 'Schärfepriorität', + 'Priority on shutter' => 'Verschlußpriorität', + }, + }, + 'ShutterSpeed' => 'Belichtungsdauer', + 'ShutterSpeedDisplayed' => 'Angezeigte Belichtungszeit', + 'ShutterSpeedRange' => { + Description => 'Einstellung Blendenbereich', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'ShutterSpeedSetting' => 'Belichtungszeit Einstellung', + 'ShutterSpeedValue' => 'Belichtungszeit', + 'SimilarityIndex' => 'Bildgleichheits-Index', + 'SingleFrameBracketing' => { + Description => 'Einzelbild-Belichtungsreihe', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Niedrig', + }, + }, + 'SlaveFlashMeteringSegments' => 'Slave-Blitz-Messfeld', + 'SlideShow' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'SlowShutter' => { + Description => 'Langzeitbelichtungseinstellung', + PrintConv => { + 'Night Scene' => 'Nachtszene', + 'None' => 'Keine', + 'Off' => 'Aus', + 'On' => 'Ein', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SlowSync' => { + Description => 'Slow-Synchro', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'SoftSkinEffect' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'SonyImageSize' => { + Description => 'Sony Bildgröße', + PrintConv => { + 'Large' => 'Groß', + 'Large (16:9)' => 'Groß (16:9)', + 'Large (3:2)' => 'Groß (3:2)', + 'Medium' => 'Mittel', + 'Medium (16:9)' => 'Mittel (16:9)', + 'Medium (3:2)' => 'Mittel (3:2)', + 'Small' => 'Klein', + 'Small (16:9)' => 'Klein (16:9)', + 'Small (3:2)' => 'Klein (3:2)', + }, + }, + 'Source' => 'Quelle', + 'SpatialFrequencyResponse' => 'Raumfrequenz-Antwort', + 'SpecialEffectMode' => { + Description => 'Spezialeffekte Modus', + PrintConv => { + 'Mist Removal' => 'Dunstentfernung', + 'Off' => 'Aus', + 'Vivid Landscape' => 'Lebende Landschaft', + }, + }, + 'SpecialEffectsOpticalFilter' => { + Description => 'Spezialeffekt Filter', + PrintConv => { + 'Colored' => 'Farbfilter', + 'Diffusion' => 'Diffusionsfilter', + 'Multi-image' => 'Mehrfachbildfilter', + 'None' => 'Keiner', + 'Polarizing' => 'Polarisationsfilter', + 'Split-field' => 'Splitfilter', + 'Star' => 'Sternenfilter', + }, + }, + 'SpecialInstructions' => 'Anweisungen', + 'SpecialMode' => 'Spezialmodus', + 'SpectralSensitivity' => 'Spektralempfindlichkeit', + 'SpotFocusPointX' => 'Spot-Fokuspunkt X', + 'SpotFocusPointY' => 'Spot-Fokuspunkt Y', + 'SpotMeterLinkToAFPoint' => { + Description => 'Spotmessung AF-Feld verknüpft', + PrintConv => { + 'Disable (use center AF point)' => 'Deaktiviert (zentrales AF-Feld)', + 'Enable (use active AF point)' => 'Aktiviert (aktives AF-Feld)', + }, + }, + 'SpotMeteringMode' => { + Description => 'Spot-Messmethode', + PrintConv => { + 'AF Point' => 'AF-Punkt', + 'Center' => 'Mitte', + }, + }, + 'State' => 'Bundesland/Kanton', + 'StereoMode' => 'Stereomodus', + 'StripByteCounts' => 'Anzahl Bytes pro komprimiertem Bildabschnitt', + 'StripOffsets' => 'Bilddatenposition', + 'Sub-location' => 'Ort des Motivs', + 'SubSecCreateDate' => 'Digitalisierungsdatum/-uhrzeit', + 'SubSecDateTimeOriginal' => 'Erstellungsdatum/-uhrzeit', + 'SubSecModifyDate' => 'Änderungsdatum', + 'SubSecTime' => 'Datum/Uhrzeit 1/100 Sekunden', + 'SubSecTimeDigitized' => 'Digitalisierungsdatum/-uhrzeit 1/100 Sekunden', + 'SubSecTimeOriginal' => 'Erstellungsdatum/-uhrzeit 1/100 Sekunden', + 'SubfileType' => { + Description => 'Unterdatei-Typ', + PrintConv => { + 'Alternate reduced-resolution image' => 'Alternatives Bild in reduzierter Auflösung', + 'Full-resolution image' => 'Bild in voller Auflösung', + 'Reduced-resolution image' => 'Bild in reduzierter Auflösung', + 'Single page of multi-page image' => 'Einzelbild eines mehrseitigen Bildes', + 'Single page of multi-page reduced-resolution image' => 'Einzelbild eines mehrseitigen Bildes in reduzierter Auflösung', + 'TIFF-FX mixed raster content' => 'TIFF-FX gersteter Inhalt', + 'TIFF/IT final page' => 'TIFF/IT endgültige Seite', + 'Thumbnail image' => 'Miniaturbild', + 'Transparency mask' => 'Transparenzmaske', + 'Transparency mask of multi-page image' => 'Transparenzmaske eines mehrseitigen Bildes', + 'Transparency mask of reduced-resolution image' => 'Transparenzmaske eines Bildes in reduzierter Auflösung', + 'Transparency mask of reduced-resolution multi-page image' => 'Transparenzmaske eines mehrseitigen Bildes in reduzierter Auflösung', + }, + }, + 'SubimageColor' => { + PrintConv => { + 'Monochrome' => 'Monochrom', + }, + }, + 'Subject' => 'Themen/Schlüsselwörter', + 'SubjectArea' => 'Hauptobjektposition', + 'SubjectCode' => 'IPTC Themencode', + 'SubjectDistance' => 'Objektentfernung', + 'SubjectDistanceRange' => { + Description => 'Objektdistanzbereich', + PrintConv => { + 'Close' => 'Nahaufnahme', + 'Distant' => 'Fernaufnahme', + 'Macro' => 'Makro', + 'Unknown' => 'Unbekannt', + }, + }, + 'SubjectLocation' => 'Hauptobjektposition', + 'SubjectProgram' => { + Description => 'Szenenauswahl', + PrintConv => { + 'Night portrait' => 'Nachtporträt', + 'None' => 'Keine', + 'Portrait' => 'Porträt', + 'Sports action' => 'Sportereignis', + 'Sunset' => 'Sonnenuntergang', + }, + }, + 'SubjectReference' => 'Themencode', + 'SubjectUnits' => { + PrintConv => { + 'meters' => 'Meter', + 'radians' => 'Winkelgrade', + }, + }, + 'Subsystem' => { + PrintConv => { + 'Unknown' => 'Unbekannt', + }, + }, + 'SuperMacro' => { + Description => 'Super Makro', + PrintConv => { + 'Off' => 'Aus', + }, + }, + 'SuperimposedDisplay' => { + Description => 'Eingeblendete Anzeige', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'SupplementalCategories' => 'Zusätzliche Kategorien', + 'SvISOSetting' => 'Sv ISO-Einstellung', + 'SwitchToRegisteredAFPoint' => { + Description => 'Auf gesp. AF-Messf. schalten', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + 'Only while AEL is pressed' => 'Nur während AEL gedrückt', + 'Switch with multi-controller' => 'Wechseln mit Multicontroller', + }, + }, + 'T4Options' => 'Füllbits hinzugefügt', + 'T6Options' => 'T6 Optionen', + 'T82Options' => 'T82 Option', + 'TIFFPreview' => 'TIFF Vorschaubild', + 'TIFF_FXExtensions' => { + PrintConv => { + 'B&W JBIG2' => 'Schwarz-Weiß JBIG2', + }, + }, + 'TTL_DA_ADown' => 'Slave-Blitz-Messfeld 6', + 'TTL_DA_AUp' => 'Slave-Blitz-Messfeld 5', + 'TTL_DA_BDown' => 'Slave-Blitz-Messfeld 8', + 'TTL_DA_BUp' => 'Slave-Blitz-Messfeld 7', + 'Tagged' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'TargetAperture' => 'Zielblendenwert', + 'TargetCompressionRatio' => 'Ziel-Komprimierungsrate', + 'TargetExposureTime' => 'Zielbelichtungszeit', + 'Technology' => { + Description => 'Technologie', + PrintConv => { + 'Active Matrix Display' => 'Aktives Matrix-Display', + 'Cathode Ray Tube Display' => 'Kathodenstrahlröhrenbildschirm', + 'Digital Camera' => 'Digitalkamera', + 'Dye Sublimation Printer' => 'Thermosublimationsdrucker', + 'Electrophotographic Printer' => 'Laserdrucker', + 'Electrostatic Printer' => 'Elektrostatischer Drucker', + 'Film Scanner' => 'Film-Scanner', + 'Film Writer' => 'Film-Writer', + 'Flexography' => 'Flexographie', + 'Gravure' => 'Gravur', + 'Ink Jet Printer' => 'Tintenstrahldrucker', + 'Offset Lithography' => 'Offset Lithographie', + 'Passive Matrix Display' => 'Passives Matrix-Display', + 'Photo CD' => 'Photo-CD', + 'Photo Image Setter' => 'Foto-Filmbelichter', + 'Photographic Paper Printer' => 'Fotopapierdrucker', + 'Projection Television' => 'Projektionsfernsehgerät', + 'Reflective Scanner' => 'Reflexionsscanner', + 'Thermal Wax Printer' => 'Thermowachsdrucker', + 'Video Camera' => 'Videokamera', + 'Video Monitor' => 'Video-Monitor', + }, + }, + 'Teleconverter' => { + Description => 'Telekonverter', + PrintConv => { + 'None' => 'Keiner', + }, + }, + 'TextEncoding' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'TextStamp' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ThumbnailFileName' => 'Miniaturbild-Dateiname', + 'ThumbnailFormat' => 'Miniaturbild-Format', + 'ThumbnailHeight' => 'Miniaturbild-Höhe', + 'ThumbnailImage' => 'Miniaturbild', + 'ThumbnailImageName' => 'Miniaturbild-Name', + 'ThumbnailImageSize' => 'Miniaturbild-Größe', + 'ThumbnailImageType' => 'Miniaturbild-Typ', + 'ThumbnailImageValidArea' => 'Gültiger Bereich des Miniaturbildes', + 'ThumbnailLength' => 'Miniaturbild-Datenlänge', + 'ThumbnailOffset' => 'Miniaturbild-Datenposition', + 'ThumbnailWidth' => 'Miniaturbild-Breite', + 'Time' => 'Zeit', + 'TimeCreated' => 'Erstellungszeit', + 'TimeScaleParamsQuality' => { + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Leicht', + }, + }, + 'TimeSent' => 'Absendezeit', + 'TimeSincePowerOn' => 'Einschaltdauer', + 'TimeStamp' => 'Zeitstempel', + 'TimeStamp1' => 'Zeitstempel (1)', + 'TimeZone' => 'Zeitzone', + 'TimeZoneCity' => { + Description => 'Zeitzone Stadt', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'TimeZoneCode' => 'Zeitzonen-Code', + 'TimeZoneInfo' => 'Zeitzonen-Info', + 'TimeZoneOffset' => 'Zeitzonen-Offset', + 'TimerFunctionButton' => { + Description => 'Funktionstaste', + PrintConv => { + 'Auto Bracketing' => 'Belichtungsreihe', + 'ISO' => 'ISO-Empfindlichkeit', + 'Image Quality/Size' => 'Bildqualität/-größe', + 'Self-timer' => 'Selbstauslöser', + 'Shooting Mode' => 'Aufnahmebetriebsart', + 'White Balance' => 'Weißabgleich', + }, + }, + 'TimerLength' => { + Description => 'Intervalldauer für Timer', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'Title' => 'Titel', + 'ToneComp' => 'Tonwertkorrektur', + 'ToneCurve' => { + Description => 'Ton-Kurve', + PrintConv => { + 'Custom' => 'Benutzerdefiniert', + 'Manual' => 'Manuell', + }, + }, + 'ToneCurve1' => 'Tonwertkurve 1', + 'ToneCurve2' => 'Tonwertkurve 2', + 'ToneCurve3' => 'Tonwertkurve 3', + 'ToneCurve4' => 'Tonwertkurve 4', + 'ToneCurveActive' => { + Description => 'Tonwertkurve aktiv', + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'ToneCurveBlue' => 'Tonwertkurve Blau', + 'ToneCurveBlueX' => 'Tonwertkurve Blau X', + 'ToneCurveBlueY' => 'Tonwertkurve Blau Y', + 'ToneCurveBrightnessX' => 'Tonwertkurve Helligkeit X', + 'ToneCurveBrightnessY' => 'Tonwertkurve Helligkeit Y', + 'ToneCurveFileName' => 'Tonwertkurve Dateiname', + 'ToneCurveGreen' => 'Tonwertkurve Grün', + 'ToneCurveGreenX' => 'Tonwertkurve Grün X', + 'ToneCurveGreenY' => 'Tonwertkurve Grün Y', + 'ToneCurveInterpolation' => { + Description => 'Tonwertkurve Interpolation', + PrintConv => { + 'Curve' => 'Kurve', + 'Straight' => 'Gerade', + }, + }, + 'ToneCurveMatching' => 'Tonwertkurve Übereinstimmung', + 'ToneCurveMode' => { + Description => 'Tonwertkurve Modus', + PrintConv => { + 'Luminance' => 'Luminanz', + }, + }, + 'ToneCurveName' => { + Description => 'Tonwertkurve Name', + PrintConv => { + 'Custom' => 'Benutzerdefiniert', + 'Medium Contrast' => 'Kontrast mittel', + 'Strong Contrast' => 'Kontrast stark', + }, + }, + 'ToneCurveName2012' => 'Tonwertkurve Name 2012', + 'ToneCurvePV2012' => 'Tonwertkurve PV2012', + 'ToneCurvePV2012Blue' => 'Tonwertkurve PV2012 Blau', + 'ToneCurvePV2012Green' => 'Tonwertkurve PV2012 Grün', + 'ToneCurvePV2012Red' => 'Tonwertkurve PV2012 Rot', + 'ToneCurveProperty' => { + Description => 'Tonwertkurve Eigenschaft', + PrintConv => { + 'Custom 1' => 'Benutzerdefiniert 1', + 'Custom 2' => 'Benutzerdefiniert 2', + 'Custom 3' => 'Benutzerdefiniert 3', + 'Custom 4' => 'Benutzerdefiniert 4', + 'Custom 5' => 'Benutzerdefiniert 5', + 'Shot Settings' => 'Aufnahmeeinstellung', + }, + }, + 'ToneCurveRed' => 'Tonwertkurve Rot', + 'ToneCurveRedX' => 'Tonwertkurve Rot X', + 'ToneCurveRedY' => 'Tonwertkurve Rot Y', + 'ToneCurveTable' => 'Tonwertkurve Tabelle', + 'ToneCurves' => 'Ton-Kurven', + 'ToningEffect' => { + Description => 'Tönungseffekt', + PrintConv => { + 'B&W' => 'Schwarz/Weiß', + 'Blue' => 'Blau', + 'Blue-green' => 'Blau-Grün', + 'Color' => 'Farbe', + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Purple' => 'Lila', + 'Purple-blue' => 'Violett-Blau', + 'Red' => 'Rot', + 'Red-purple' => 'Rot-Violett', + 'Yellow' => 'Gelb', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningEffectFaithful' => { + Description => 'Tönungseffekt Natürlich', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningEffectLandscape' => { + Description => 'Tönungseffekt Landschaft', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningEffectMonochrome' => { + Description => 'Tönungseffekt Monochrom', + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Purple' => 'Lila', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningEffectNeutral' => { + Description => 'Tönungseffekt Neutral', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningEffectPortrait' => { + Description => 'Tönungseffekt Porträt', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningEffectStandard' => { + Description => 'Tönungseffekt Standard', + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningEffectUnknown' => { + Description => 'Tönungseffekt Unbekannt', + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Purple' => 'Lila', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningEffectUserDef1' => { + Description => 'Tönungseffekt Benutzerdefiniert 1', + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Purple' => 'Lila', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningEffectUserDef2' => { + Description => 'Tönungseffekt Benutzerdefiniert 2', + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Purple' => 'Lila', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningEffectUserDef3' => { + Description => 'Tönungseffekt Benutzerdefiniert 3', + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'None' => 'Keiner', + 'Purple' => 'Lila', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ToningSaturation' => 'Tönungssättigung', + 'TotalZoom' => 'Gesamtzoom', + 'TrailerSignature' => 'Signatur des Nachspanns', + 'TransferFunction' => 'Transformationsfunktion', + 'Transform' => { + Description => 'Transformation', + PrintConv => { + 'Off' => 'Aus', + 'Slim High' => 'Stark abnehmend', + 'Slim Low' => 'Wenig abnehmend', + 'Stretch High' => 'Stark streckend', + 'Stretch Low' => 'Wenig streckend', + }, + }, + 'Transformation' => { + PrintConv => { + 'Mirror horizontal' => 'Horizontal gespiegelt', + 'Mirror horizontal and rotate 270 CW' => 'Horizontal gespiegelt und 90° gegen den Uhrzeigersinn', + 'Mirror horizontal and rotate 90 CW' => 'Horizontal gespiegelt und 90° im Uhrzeigersinn', + 'Mirror vertical' => 'Vertikal gespiegelt', + 'Rotate 180' => '180° gedreht', + 'Rotate 270 CW' => '90° gegen den Uhrzeigersinn', + 'Rotate 90 CW' => '90° im Uhrzeigersinn', + }, + }, + 'TransmissionReference' => 'Anbietervermerk', + 'Trapped' => { + PrintConv => { + 'Unknown' => 'Unbekannt', + }, + }, + 'TravelDay' => 'Reisetag', + 'TvExposureTimeSetting' => 'Tv Belichtungszeit-Einstellung', + 'Type' => 'Typ', + 'TypeOfOriginal' => { + PrintConv => { + 'B&W Document' => 'Schwarz-Weiß Dokument', + 'B&W Print' => 'Schwarz-Weiß Druck', + 'Color Document' => 'Farb Dokument', + 'Color Print' => 'Farbdruck', + }, + }, + 'USMLensElectronicMF' => { + Description => 'USM-Objektiv, elektr. MF', + PrintConv => { + 'Disable after one-shot AF' => 'Nicht mögl. nach One-Shot AF', + 'Disable in AF mode' => 'Nicht möglich im AF-Modus', + 'Enable after one-shot AF' => 'Möglich nach One-Shot AF', + }, + }, + 'Uncompressed' => { + Description => 'Unkomprimiert', + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'UniqueCameraModel' => 'Eindeutige Kamerabezeichnung', + 'UniqueDocumentID' => 'Eindeutige Bild-ID', + 'Unknown' => 'Unbekannt', + 'UnknownInfoVersion' => 'Unbekannte Info Version', + 'UnknownLinear' => { + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'Unknown_CNDB' => 'CNDB unbekannt', + 'Unsharp1Color' => { + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + }, + }, + 'Unsharp2Color' => { + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + }, + }, + 'Unsharp3Color' => { + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + }, + }, + 'Unsharp4Color' => { + PrintConv => { + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'Red' => 'Rot', + 'Yellow' => 'Gelb', + }, + }, + 'UnsharpMask' => { + Description => 'Unschärfemaske', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Urgency' => { + Description => 'Dringlichkeit', + PrintConv => { + '0 (reserved)' => '0 (reserviert)', + '1 (most urgent)' => '1 (sehr dringend)', + '5 (normal urgency)' => '5 (normale Dringlichkeit)', + '8 (least urgent)' => '8 (geringe Dringlichkeit)', + '9 (user-defined priority)' => '9 (benutzerdefinierte Priorität)', + }, + }, + 'UsableMeteringModes' => { + Description => 'Wahl nutzbarer Messmethoden', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'UsableShootingModes' => { + Description => 'Wahl nutzbarer Aufnahmemodi', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'UsageTerms' => 'Nutzungsbedingungen', + 'UserComment' => 'Benutzerkommentar', + 'UserDef1PictureStyle' => { + Description => 'Bildstil Benutzerdefiniert 1', + PrintConv => { + 'Faithful' => 'Natürlich', + 'Landscape' => 'Landschaft', + 'Monochrome' => 'Monochrom', + 'Portrait' => 'Porträt', + }, + }, + 'UserDef2PictureStyle' => { + Description => 'Bildstil Benutzerdefiniert 2', + PrintConv => { + 'Faithful' => 'Natürlich', + 'Landscape' => 'Landschaft', + 'Monochrome' => 'Monochrom', + 'Portrait' => 'Porträt', + }, + }, + 'UserDef3PictureStyle' => { + Description => 'Bildstil Benutzerdefiniert 3', + PrintConv => { + 'Faithful' => 'Natürlich', + 'Landscape' => 'Landschaft', + 'Monochrome' => 'Monochrom', + 'Portrait' => 'Porträt', + }, + }, + 'UserFields' => 'Benutzerfelder', + 'UserProfile' => { + Description => 'Benutzerprofil', + PrintConv => { + 'User Profile 0 (Dynamic)' => 'Benutzerprofil 0 (dynamisch)', + 'User Profile 1' => 'Benutzerprofil 1', + 'User Profile 2' => 'Benutzerprofil 2', + 'User Profile 3' => 'Benutzerprofil 3', + }, + }, + 'VFDisplayIllumination' => { + PrintConv => { + 'Disable' => 'Deaktiviert', + 'Enable' => 'Aktiviert', + }, + }, + 'VRDVersion' => 'VRD-Version', + 'VRInfo' => 'Bildstabilisator-Informationen', + 'VRInfoVersion' => 'VR-Info-Version', + 'VR_0x66' => { + PrintConv => { + 'Off' => 'Aus', + 'On (active)' => 'Ein (Aktiv)', + 'On (normal)' => 'Ein (Normal)', + }, + }, + 'ValidAFPoints' => 'Gültige AF-Punkte', + 'ValidBits' => 'Verwendete Bits', + 'ValidPixelDepth' => 'Farbtiefe', + 'VariProgram' => 'Aufnahmeprogramm', + 'VibrationReduction' => { + Description => 'Bildstabilisation', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + 'On (1)' => 'Ein (1)', + 'On (2)' => 'Ein (2)', + 'On (3)' => 'Ein (3)', + 'n/a' => '(nicht gesetzt)', + }, + }, + 'ViewInfoDuringExposure' => { + Description => 'Sucherinfo bei Belichtung', + PrintConv => { + 'Disable' => 'Nicht möglich', + 'Enable' => 'Möglich', + }, + }, + 'ViewfinderWarning' => { + Description => 'Warnsymbol im Sucher', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'ViewfinderWarnings' => { + PrintConv => { + 'ISO expansion' => 'ISO-Erweiterung', + }, + }, + 'ViewingMode2' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'VignetteControl' => { + Description => 'Vignettierungskorrektur', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Schwach', + 'Normal' => 'Mittel', + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'VignetteControlIntensity' => 'Vignettierungskorrektur Stärke', + 'VignettingCorrection' => { + PrintConv => { + 'n/a' => '(nicht gesetzt)', + }, + }, + 'VirtualImageHeight' => 'Virtuelle Bildhöhe', + 'VirtualImageWidth' => 'Virtuelle Bildbreite', + 'VirtualPageUnits' => 'Virtuelle Seitenzahl', + 'VoiceMemo' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Volume' => 'Band', + 'WBAdjColorTemp' => 'Weißabgleich Farbtemperaturkorrektur', + 'WBAdjLighting' => { + PrintConv => { + 'Daylight (cloudy)' => 'Tageslicht (2)', + 'Daylight (direct sunlight)' => 'Tageslicht (0)', + 'Daylight (shade)' => 'Tageslicht (1)', + 'Flash' => 'Blitz', + 'Incandescent' => 'Glühbirne', + 'None' => 'Keines', + }, + }, + 'WBBlueLevel' => 'Farbabgleich Blau', + 'WBBracketMode' => { + Description => 'Weißabgleich Belichtungsreihen-Modus', + PrintConv => { + 'Off' => 'Aus', + 'On (shift AB)' => 'Ein (AB-Verschiebung)', + 'On (shift GM)' => 'Ein (GM-Verschiebung)', + }, + }, + 'WBBracketShotNumber' => 'Weißabgleich-Belichtungsreihen-Bildnummer', + 'WBBracketValueAB' => 'Weißabgleich AB-Belichtungsreihen-Wert', + 'WBBracketValueGM' => 'Weißabgleich GM-Belichtungsreihen-Wert', + 'WBFineTuneActive' => { + Description => 'Weißabgleich Feinabstimmung aktiv', + PrintConv => { + 'No' => 'Nein', + 'Yes' => 'Ja', + }, + }, + 'WBFineTuneSaturation' => 'Weißabgleich Sättigung Feinabstimmung', + 'WBFineTuneTone' => 'Weißabgleich Farbton Feinabstimmung', + 'WBGreenLevel' => 'Farbabgleich Grün', + 'WBMediaImageSizeSetting' => { + Description => 'WB+Media/Bildgrößeneinstellung', + PrintConv => { + 'LCD monitor' => 'LCD-Monitor', + 'Rear LCD panel' => 'Hinteres LCD-Panel', + }, + }, + 'WBMode' => { + PrintConv => { + 'Auto' => 'Automatisch', + }, + }, + 'WBRedLevel' => 'Farbabgleich Rot', + 'WBShiftAB' => 'Weißabgleich AB-Korrektur', + 'WBShiftGM' => 'Weißabgleich GM-Korrektur', + 'WB_GBRGLevels' => 'Weißabgleich GBRG-Farbverteilung', + 'WB_GLevel' => 'Weißabgleich G-Farbverteilung', + 'WB_GLevel3000K' => 'Weißabgleich G-Farbverteilung 3000K', + 'WB_GLevel3300K' => 'Weißabgleich G-Farbverteilung 3300K', + 'WB_GLevel3600K' => 'Weißabgleich G-Farbverteilung 3600K', + 'WB_GLevel3900K' => 'Weißabgleich G-Farbverteilung 3900K', + 'WB_GLevel4000K' => 'Weißabgleich G-Farbverteilung 4000K', + 'WB_GLevel4300K' => 'Weißabgleich G-Farbverteilung 4300K', + 'WB_GLevel4500K' => 'Weißabgleich G-Farbverteilung 4500K', + 'WB_GLevel4800K' => 'Weißabgleich G-Farbverteilung 4800K', + 'WB_GLevel5300K' => 'Weißabgleich G-Farbverteilung 5300K', + 'WB_GLevel6000K' => 'Weißabgleich G-Farbverteilung 6000K', + 'WB_GLevel6600K' => 'Weißabgleich G-Farbverteilung 6600K', + 'WB_GLevel7500K' => 'Weißabgleich G-Farbverteilung 7500K', + 'WB_GRBGLevels' => 'Weißabgleich GRBG-Farbverteilung', + 'WB_GRGBLevels' => 'Weißabgleich GRGB-Farbverteilung', + 'WB_RBGGLevels' => 'Weißabgleich RBGG-Farbverteilung', + 'WB_RBLevels' => 'Weißabgleich RB-Farbverteilung', + 'WB_RBLevels3000K' => 'Weißabgleich RB-Farbverteilung 3000K', + 'WB_RBLevels3300K' => 'Weißabgleich RB-Farbverteilung 3300K', + 'WB_RBLevels3600K' => 'Weißabgleich RB-Farbverteilung 3600K', + 'WB_RBLevels3900K' => 'Weißabgleich RB-Farbverteilung 3800K', + 'WB_RBLevels4000K' => 'Weißabgleich RB-Farbverteilung 4000K', + 'WB_RBLevels4300K' => 'Weißabgleich RB-Farbverteilung 4300K', + 'WB_RBLevels4500K' => 'Weißabgleich RB-Farbverteilung 4500K', + 'WB_RBLevels4800K' => 'Weißabgleich RB-Farbverteilung 4800K', + 'WB_RBLevels5300K' => 'Weißabgleich RB-Farbverteilung 5300K', + 'WB_RBLevels6000K' => 'Weißabgleich RB-Farbverteilung 6000K', + 'WB_RBLevels6600K' => 'Weißabgleich RB-Farbverteilung 6600K', + 'WB_RBLevels7500K' => 'Weißabgleich RB-Farbverteilung 7500K', + 'WB_RBLevelsAuto' => 'Weißabgleich RB-Farbverteilung Automatik', + 'WB_RBLevelsCWB1' => 'Weißabgleich RB-Farbverteilung CWB1', + 'WB_RBLevelsCWB2' => 'Weißabgleich RB-Farbverteilung CWB2', + 'WB_RBLevelsCWB3' => 'Weißabgleich RB-Farbverteilung CWB3', + 'WB_RBLevelsCWB4' => 'Weißabgleich RB-Farbverteilung CWB4', + 'WB_RBLevelsCloudy' => 'Weißabgleich RB-Farbverteilung Bewölkt', + 'WB_RBLevelsCoolWhiteFluor' => 'Weißabgleich RB-Farbverteilung Neonlicht kaltweiß', + 'WB_RBLevelsDayWhiteFluor' => 'Weißabgleich RB-Farbverteilung Neonlicht neutralweiß', + 'WB_RBLevelsDaylightFluor' => 'Weißabgleich RB-Farbverteilung Neonlicht tageslichtweiß', + 'WB_RBLevelsEveningSunlight' => 'Weißabgleich RB-Farbverteilung Sonnenuntergang', + 'WB_RBLevelsFineWeather' => 'Weißabgleich RB-Farbverteilung Wolkenlos', + 'WB_RBLevelsShade' => 'Weißabgleich RB-Farbverteilung Schatten', + 'WB_RBLevelsTungsten' => 'Weißabgleich RB-Farbverteilung Glühbirne', + 'WB_RBLevelsUsed' => 'Weißabgleich RB-Farbverteilung verwendet', + 'WB_RBLevelsWhiteFluorescent' => 'Weißabgleich RB-Farbverteilung Neonlicht universalweiß', + 'WB_RGBGLevels' => 'Weißabgleich RGBG-Farbverteilung', + 'WB_RGBLevels' => 'Weißabgleich RGB-Farbverteilung', + 'WB_RGBLevelsCloudy' => 'Weißabgleich RGB-Farbverteilung Bewölkt', + 'WB_RGBLevelsDaylight' => 'Weißabgleich RGB-Farbverteilung Tageslicht', + 'WB_RGBLevelsFlash' => 'Weißabgleich RGB-Farbverteilung Blitz', + 'WB_RGBLevelsFluorescent' => 'Weißabgleich RGB-Farbverteilung Neonlicht', + 'WB_RGBLevelsShade' => 'Weißabgleich RGB-Farbverteilung Schatten', + 'WB_RGBLevelsTungsten' => 'Weißabgleich RGB-Farbverteilung Glühbirne', + 'WB_RGGBLevels' => 'Weißabgleich RGGB-Farbverteilung', + 'WB_RGGBLevelsAsShot' => 'Weißabgleich RGGB-Farbverteilung Aufnahme', + 'WB_RGGBLevelsAuto' => 'Weißabgleich RGGB-Farbverteilung Auto', + 'WB_RGGBLevelsCloudy' => 'Weißabgleich RGGB-Farbverteilung Bewölkt', + 'WB_RGGBLevelsCustom' => 'Weißabgleich RGGB-Farbverteilung Benutzerdefiniert', + 'WB_RGGBLevelsCustom1' => 'Weißabgleich RGGB-Farbverteilung Benutzerdefiniert 1', + 'WB_RGGBLevelsCustom2' => 'Weißabgleich RGGB-Farbverteilung Benutzerdefiniert 2', + 'WB_RGGBLevelsDaylight' => 'Weißabgleich RGGB-Farbverteilung Tageslicht', + 'WB_RGGBLevelsFlash' => 'Weißabgleich RGGB-Farbverteilung Blitz', + 'WB_RGGBLevelsFluorescent' => 'Weißabgleich RGGB-Farbverteilung Neonlicht', + 'WB_RGGBLevelsFluorescentD' => 'Weißabgleich RGGB-Farbverteilung Neonlicht D', + 'WB_RGGBLevelsFluorescentN' => 'Weißabgleich RGGB-Farbverteilung Neonlicht N', + 'WB_RGGBLevelsFluorescentW' => 'Weißabgleich RGGB-Farbverteilung Neonlicht W', + 'WB_RGGBLevelsKelvin' => 'Weißabgleich RGGB-Farbverteilung Kelvin', + 'WB_RGGBLevelsMeasured' => 'Weißabgleich RGGB-Farbverteilung Messung', + 'WB_RGGBLevelsPC1' => 'Weißabgleich RGGB-Farbverteilung PC1', + 'WB_RGGBLevelsPC2' => 'Weißabgleich RGGB-Farbverteilung PC2', + 'WB_RGGBLevelsPC3' => 'Weißabgleich RGGB-Farbverteilung PC3', + 'WB_RGGBLevelsShade' => 'Weißabgleich RGGB-Farbverteilung Schatten', + 'WB_RGGBLevelsTungsten' => 'Weißabgleich RGGB-Farbverteilung Glühbirne', + 'WB_RGGBLevelsUnknown' => 'Weißabgleich RGGB-Farbverteilung Unbekannt', + 'WB_RGGBLevelsUnknown10' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 10', + 'WB_RGGBLevelsUnknown11' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 11', + 'WB_RGGBLevelsUnknown12' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 12', + 'WB_RGGBLevelsUnknown13' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 13', + 'WB_RGGBLevelsUnknown14' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 14', + 'WB_RGGBLevelsUnknown15' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 15', + 'WB_RGGBLevelsUnknown16' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 16', + 'WB_RGGBLevelsUnknown17' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 17', + 'WB_RGGBLevelsUnknown18' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 18', + 'WB_RGGBLevelsUnknown19' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 19', + 'WB_RGGBLevelsUnknown2' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 2', + 'WB_RGGBLevelsUnknown20' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 20', + 'WB_RGGBLevelsUnknown3' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 3', + 'WB_RGGBLevelsUnknown4' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 4', + 'WB_RGGBLevelsUnknown5' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 5', + 'WB_RGGBLevelsUnknown6' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 6', + 'WB_RGGBLevelsUnknown7' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 7', + 'WB_RGGBLevelsUnknown8' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 8', + 'WB_RGGBLevelsUnknown9' => 'Weißabgleich RGGB-Farbverteilung Unbekannt 9', + 'WCSProfiles' => 'Windows Color System-Profil', + 'WangAnnotation' => 'Wang Anmerkung', + 'Warning' => 'Warnung', + 'Watermark' => 'Wasserzeichen', + 'WhiteBalance' => { + Description => 'Weißabgleich', + PrintConv => { + 'As Shot' => 'Aufnahme', + 'Auto' => 'Automatisch', + 'Black & White' => 'Schwarz/Weiß', + 'Cloudy' => 'Bewölkt', + 'Color Temperature/Color Filter' => 'Farbtemperatur/Farbfilter', + 'Cool White Fluorescent' => 'Neonlicht kaltweiß', + 'Custom' => 'Benutzerdefiniert', + 'Custom 1' => 'Benutzerdefiniert 1', + 'Custom 2' => 'Benutzerdefiniert 2', + 'Custom 3' => 'Benutzerdefiniert 3', + 'Custom 4' => 'Benutzerdefiniert 4', + 'Custom2' => 'Benutzerdefiniert 2', + 'Custom3' => 'Benutzerdefiniert 3', + 'Custom4' => 'Benutzerdefiniert 4', + 'Custom5' => 'Benutzerdefiniert 5', + 'Day White Fluorescent' => 'Neonlicht neutralweiß', + 'Daylight' => 'Tageslicht', + 'Daylight Fluorescent' => 'Neonlicht tageslichtweiß', + 'Flash' => 'Blitz', + 'Flash?' => 'Blitz', + 'Fluorescent' => 'Neonlicht', + 'Incandescent' => 'Glühbirne', + 'Living Room Warm White Fluorescent' => 'Neonlicht Wohnzimmer-warmweiß)', + 'Manual' => 'Manuell', + 'Manual Temperature (Kelvin)' => 'Manuelle Temperatur (Kelvin)', + 'Shade' => 'Schatten', + 'Tungsten' => 'Glühbirne', + 'Underwater' => 'Unterwasser', + 'Underwater 1 (Blue Water)' => 'Unterwasser 1 (blaues Wasser)', + 'Underwater 2 (Green Water)' => 'Unterwasser 2 (grünes Wasser)', + 'Unknown' => 'Unbekannt', + 'User-Selected' => 'Benutzerdefiniert', + 'Warm White Fluorescent' => 'Neonlicht warmweiß', + 'White Fluorescent' => 'Neonlicht universalweiß', + }, + }, + 'WhiteBalance2' => { + Description => 'Weißabgleich 2', + PrintConv => { + '3000K (Tungsten light)' => '3000K (Glühbirne)', + '3600K (Tungsten light-like)' => '3600K (ähnlich Glühbirne)', + '4000K (Cool white fluorescent)' => '4000K (Neonlicht kaltweiß)', + '4500K (Neutral white fluorescent)' => '4500K (Neonlicht neutralweiß)', + '5300K (Fine Weather)' => '5300K (Wolkenlos)', + '6000K (Cloudy)' => '6000K (Bewölkt)', + '6600K (Daylight fluorescent)' => '6600K (Neonlicht tageslichtweiß)', + '7500K (Fine Weather with Shade)' => '7500K (Sonne und Schatten)', + 'Auto' => 'Automatisch', + }, + }, + 'WhiteBalanceAdj' => { + Description => 'Weißabgleich Korrektur', + PrintConv => { + 'Auto' => 'Automatisch', + 'Cloudy' => 'Bewölkt', + 'Daylight' => 'Tageslicht', + 'Flash' => 'Blitz', + 'Fluorescent' => 'Neonlicht', + 'Off' => 'Aus', + 'On' => 'Ein', + 'Shade' => 'Schatten', + 'Tungsten' => 'Glühbirne', + }, + }, + 'WhiteBalanceAutoAdjustment' => { + Description => 'Weißabgleich automatische Abstimmung', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'WhiteBalanceBias' => 'Weißabgleich Bias', + 'WhiteBalanceBlue' => 'Farbabgleich Blau', + 'WhiteBalanceBracket' => 'Weißabgleichs-Belichtungsreihe', + 'WhiteBalanceBracketing' => { + Description => 'Weißabgleichs-Belichtungsreihe', + PrintConv => { + 'High' => 'Hoch', + 'Low' => 'Niedrig', + 'Off' => 'Aus', + }, + }, + 'WhiteBalanceComp' => 'Weißabgleichsausgleich', + 'WhiteBalanceFineTune' => 'Weißabgleichsfeineinstellung', + 'WhiteBalanceMatching' => 'Weißabgleich Übereinstimmung', + 'WhiteBalanceMode' => { + Description => 'Weißabgleich-Modus', + PrintConv => { + 'Auto (Cloudy)' => 'Automatisch (Bewölkt)', + 'Auto (Day White Fluorescent)' => 'Automatisch (Neonlicht neutralweiß)', + 'Auto (Daylight Fluorescent)' => 'Automatisch (Neonlicht tageslichtweiß)', + 'Auto (Daylight)' => 'Automatisch (Tageslicht)', + 'Auto (Flash)' => 'Automatisch (Blitz)', + 'Auto (Shade)' => 'Automatisch (Schatten)', + 'Auto (Tungsten)' => 'Automatisch (Glühbirne)', + 'Auto (White Fluorescent)' => 'Automatisch (Neonlicht universalweiß)', + 'Unknown' => 'Unbekannt', + 'User-Selected' => 'Benutzerdefiniert', + }, + }, + 'WhiteBalanceRed' => 'Farbabgleich Rot', + 'WhiteBalanceSet' => { + Description => 'Eingestellter Weißabgleich', + PrintConv => { + 'Auto' => 'Automatisch', + 'Cloudy' => 'Bewölkt', + 'Day White Fluorescent' => 'Neonlicht neutralweiß', + 'Daylight' => 'Tageslicht', + 'Daylight Fluorescent' => 'Neonlicht tageslichtweiß', + 'Flash' => 'Blitz', + 'Manual' => 'Manuell', + 'Set Color Temperature 1' => 'Farbtemperatur-Einstellung 1', + 'Set Color Temperature 2' => 'Farbtemperatur-Einstellung 2', + 'Set Color Temperature 3' => 'Farbtemperatur-Einstellung 3', + 'Shade' => 'Schatten', + 'Tungsten' => 'Glühbirne', + 'White Fluorescent' => 'Neonlicht universalweiß', + }, + }, + 'WhiteBalanceSetting' => { + Description => 'Weißabgleichs-Einstellung', + PrintConv => { + 'Color Temperature/Color Filter' => 'Farbtemperatur/Farbfilter', + 'Custom' => 'Benutzerdefiniert', + 'Preset' => 'Voreinstellung', + }, + }, + 'WhiteBalanceTable' => 'Weißabgleich Tabelle', + 'WhiteBalanceTemperature' => 'Weißabgleich Farbtemperatur', + 'WhiteBoard' => 'Whiteboard Funktion', + 'WhiteLevel' => 'Weißwert', + 'WhitePoint' => 'Weißpunkt-Chromatizität', + 'WhitePointX' => 'Weißpunkt X', + 'WhitePointY' => 'Weißpunkt Y', + 'Wide' => 'Breit', + 'WideFocusZone' => { + Description => 'Zone des großen AF-Messfeldes', + PrintConv => { + 'Center zone (horizontal orientation)' => 'Mittlere Zone (horizontale Ausrichtung)', + 'Center zone (vertical orientation)' => 'Mittlere Zone (vertikale Ausrichtung)', + 'Left zone' => 'Linke Zone', + 'No zone' => 'Keine Zone', + 'Right zone' => 'Rechte Zone', + }, + }, + 'WideRange' => { + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'WidthResolution' => 'Horizontale Bildauflösung', + 'WorldTime' => 'Zeitzone', + 'WorldTimeLocation' => { + Description => 'Weltzeit-Position', + PrintConv => { + 'Destination' => 'Zielort', + 'Home' => 'Heimatort', + 'Hometown' => 'Heimatort', + }, + }, + 'Writer-Editor' => 'Verfasser der Beschreibung', + 'Writers' => 'Schreiber', + 'XMP' => 'XMP Metadaten', + 'XPAuthor' => 'XP Autor', + 'XPComment' => 'XP Kommentar', + 'XPKeywords' => 'XP Schlüsselwörter', + 'XPSubject' => 'XP Thema', + 'XPTitle' => 'XP Titel', + 'XResolution' => 'Horizontale Bildauflösung', + 'XYResolution' => 'XY Auflösung', + 'YCbCrCoefficients' => 'YCbCr-Koeffizienten', + 'YCbCrPositioning' => { + Description => 'Y und C Ausrichtung', + PrintConv => { + 'Centered' => 'Zentriert', + 'Co-sited' => 'Benachbart', + }, + }, + 'YCbCrSubSampling' => 'Subsampling Rate von Y bis C', + 'YResolution' => 'Vertikale Bildauflösung', + 'Year' => 'Jahr', + 'ZipCompression' => 'Zip Komprimierung', + 'ZoneMatching' => { + Description => 'Zonenabgleich', + PrintConv => { + 'High Key' => 'Hi', + 'ISO Setting Used' => 'Aus (ISO-Einstellung verwendet)', + 'Low Key' => 'Lo', + }, + }, + 'ZoneMatchingOn' => { + Description => 'Zonenabgleich', + PrintConv => { + 'Off' => 'Aus', + 'On' => 'Ein', + }, + }, + 'Zoom' => 'Zoom-Objektiv', + 'ZoomPos' => 'Zoom Position', + 'ZoomSourceWidth' => 'Vergrößerungs-Ursprungsgröße', + 'ZoomStepCount' => 'Zoom-Stufenzähler', + 'ZoomTargetWidth' => 'Vergrößerungs-Endgröße', + 'ZoomedPreviewImage' => 'Vergrößertes Vorschaubild', + 'ZoomedPreviewLength' => 'Vergößertes Vorschaubild-Datenlänge', + 'ZoomedPreviewSize' => 'Vergößertes Vorschaubild-Größe', + 'ZoomedPreviewStart' => 'Vergößertes Vorschaubild-Datenposition', +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::de.pm - ExifTool German language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke, Herbert Kauer and Jobi for providing this +translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/en_ca.pm b/ExifTool/lib/Image/ExifTool/Lang/en_ca.pm new file mode 100644 index 0000000..66c0078 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/en_ca.pm @@ -0,0 +1,1002 @@ +#------------------------------------------------------------------------------ +# File: en_ca.pm +# +# Description: ExifTool Canadian English language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::en_ca; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.12'; + +%Image::ExifTool::Lang::en_ca::Translate = ( + 'AboveColor' => 'Above Colour', + 'AdvancedFilter' => { + PrintConv => { + 'Partial Color Blue' => 'Partial Colour Blue', + 'Partial Color Green' => 'Partial Colour Green', + 'Partial Color Orange' => 'Partial Colour Orange', + 'Partial Color Purple' => 'Partial Colour Purple', + 'Partial Color Red' => 'Partial Colour Red', + 'Partial Color Yellow' => 'Partial Colour Yellow', + 'Pop Color' => 'Pop Colour', + }, + }, + 'AdvancedSceneMode' => { + PrintConv => { + 'Color Select' => 'Colour Select', + }, + }, + 'AllColorFlatField1' => 'All Colour Flat Field 1', + 'AllColorFlatField2' => 'All Colour Flat Field 2', + 'AllColorFlatField3' => 'All Colour Flat Field 3', + 'Alpha' => { + PrintConv => { + 'Alpha Exists (W color component)' => 'Alpha Exists (W colour component)', + 'Alpha Exists (color not premultiplied)' => 'Alpha Exists (colour not premultiplied)', + 'Alpha Exists (color premultiplied)' => 'Alpha Exists (colour premultiplied)', + }, + }, + 'AlternateDuotoneColors' => 'Alternate Duotone Colours', + 'AlternateSpotColors' => 'Alternate Spot Colours', + 'ArtFilter' => { + PrintConv => { + 'Pale & Light Color' => 'Pale & Light Colour', + 'Pale & Light Color II' => 'Pale & Light Colour II', + 'Partial Color' => 'Partial Colour', + 'Partial Color II' => 'Partial Colour II', + 'Partial Color III' => 'Partial Colour III', + 'Watercolor' => 'Watercolour', + 'Watercolor I' => 'Watercolour I', + 'Watercolor II' => 'Watercolour II', + }, + }, + 'ArtFilterEffect' => { + PrintConv => { + 'Green Color Filter' => 'Green Colour Filter', + 'No Color Filter' => 'No Colour Filter', + 'Orange Color Filter' => 'Orange Colour Filter', + 'Pale & Light Color' => 'Pale & Light Colour', + 'Pale & Light Color II' => 'Pale & Light Colour II', + 'Partial Color' => 'Partial Colour', + 'Partial Color II' => 'Partial Colour II', + 'Partial Color III' => 'Partial Colour III', + 'Red Color Filter' => 'Red Colour Filter', + 'Watercolor' => 'Watercolour', + 'Watercolor I' => 'Watercolour I', + 'Watercolor II' => 'Watercolour II', + 'Yellow Color Filter' => 'Yellow Colour Filter', + }, + }, + 'AutoAFPointColorTracking' => 'Auto AF Point Colour Tracking', + 'BackgroundColor' => 'Background Colour', + 'BackgroundColorIndicator' => { + Description => 'Background Colour Indicator', + PrintConv => { + 'Specified Background Color' => 'Specified Background Colour', + 'Unspecified Background Color' => 'Unspecified Background Colour', + }, + }, + 'BackgroundColorValue' => 'Background Colour Value', + 'BasicColorImageSequence' => 'Basic Colour Image Sequence', + 'BelowColor' => 'Below Colour', + 'BestShotMode' => { + PrintConv => { + 'Water Color' => 'Water Colour', + }, + }, + 'BkColor' => 'Background Colour', + 'BluePaletteColorTableData' => 'Blue Palette Colour Table Data', + 'BluePaletteColorTableDescriptor' => 'Blue Palette Colour Table Descriptor', + 'BorderColor' => 'Border Colour', + 'CFAPlaneColor' => 'CFA Plane Colour', + 'CameraColorCalibration01' => 'Camera Colour Calibration 01', + 'CameraColorCalibration02' => 'Camera Colour Calibration 02', + 'CameraColorCalibration03' => 'Camera Colour Calibration 03', + 'CameraColorCalibration04' => 'Camera Colour Calibration 04', + 'CameraColorCalibration05' => 'Camera Colour Calibration 05', + 'CameraColorCalibration06' => 'Camera Colour Calibration 06', + 'CameraColorCalibration07' => 'Camera Colour Calibration 07', + 'CameraColorCalibration08' => 'Camera Colour Calibration 08', + 'CameraColorCalibration09' => 'Camera Colour Calibration 09', + 'CameraColorCalibration10' => 'Camera Colour Calibration 10', + 'CameraColorCalibration11' => 'Camera Colour Calibration 11', + 'CameraColorCalibration12' => 'Camera Colour Calibration 12', + 'CameraColorCalibration13' => 'Camera Colour Calibration 13', + 'CameraColorCalibration14' => 'Camera Colour Calibration 14', + 'CameraColorCalibration15' => 'Camera Colour Calibration 15', + 'CameraRawColorTone' => 'Camera Raw Colour Tone', + 'CanonColorInfo1' => 'Canon Colour Info 1', + 'CanonColorInfo2' => 'Canon Colour Info 2', + 'ChromaticityColorant' => 'Chromaticity Colourant', + 'CodingMethods' => { + PrintConv => { + 'JBIG color' => 'JBIG colour', + }, + }, + 'Color' => { + Description => 'Colour', + PrintConv => { + 'Color' => 'Colour', + }, + }, + 'ColorAberrationControl' => 'Colour Aberration Control', + 'ColorAdjustment' => 'Colour Adjustment', + 'ColorAdjustmentMode' => 'Colour Adjustment Mode', + 'ColorAverages' => 'Colour Averages', + 'ColorBW' => 'Colour BW', + 'ColorBalance' => 'Colour Balance', + 'ColorBalanceAdj' => 'Colour Balance Adj', + 'ColorBalanceBlue' => 'Colour Balance Blue', + 'ColorBalanceGreen' => 'Colour Balance Green', + 'ColorBalanceRed' => 'Colour Balance Red', + 'ColorBalanceUnknown' => 'Colour Balance Unknown', + 'ColorBalanceVersion' => 'Colour Balance Version', + 'ColorBitDepth' => 'Colour Bit Depth', + 'ColorBlur' => 'Colour Blur', + 'ColorBlurOn' => 'Colour Blur On', + 'ColorBoostLevel' => 'Colour Boost Level', + 'ColorBoostType' => 'Colour Boost Type', + 'ColorBooster' => 'Colour Booster', + 'ColorCalibrationMatrix' => 'Colour Calibration Matrix', + 'ColorCasts' => 'Colour Casts', + 'ColorCharacterization' => 'Colour Characterization', + 'ColorClass' => 'Colour Class', + 'ColorCompensationFilter' => 'Colour Compensation Filter', + 'ColorCompensationFilterCustom' => 'Colour Compensation Filter Custom', + 'ColorCompensationFilterSet' => 'Colour Compensation Filter Set', + 'ColorComponents' => 'Colour Components', + 'ColorControl' => 'Colour Control', + 'ColorCorrection' => 'Colour Correction', + 'ColorCreatorEffect' => 'Colour Creator Effect', + 'ColorDataVersion' => 'Colour Data Version', + 'ColorDescriptor' => 'Colour Descriptor', + 'ColorEffect' => 'Colour Effect', + 'ColorFieldCode' => 'Colour Field Code', + 'ColorFilter' => 'Colour Filter', + 'ColorGain' => 'Colour Gain', + 'ColorGroup' => 'Colour Group', + 'ColorHalftoningInfo' => 'Colour Halftoning Info', + 'ColorHue' => 'Colour Hue', + 'ColorImagePrintingFlag' => 'Colour Image Printing Flag', + 'ColorLabel' => 'Colour Label', + 'ColorMap' => 'Colour Map', + 'ColorMatrix' => 'Colour Matrix', + 'ColorMatrix1' => 'Colour Matrix 1', + 'ColorMatrix2' => 'Colour Matrix 2', + 'ColorMatrixA' => 'Colour Matrix A', + 'ColorMatrixAdobeRGB' => 'Colour Matrix Adobe RGB', + 'ColorMatrixB' => 'Colour Matrix B', + 'ColorMatrixNumber' => 'Colour Matrix Number', + 'ColorMatrixSRGB' => 'Colour Matrix sRGB', + 'ColorMode' => { + Description => 'Colour Mode', + PrintConv => { + 'Indexed Color' => 'Indexed Colour', + 'Natural color' => 'Natural colour', + 'Neutral Color' => 'Neutral Colour', + 'RGB Color' => 'RGB Colour', + 'Saturated Color' => 'Saturated Colour', + 'Vivid color' => 'Vivid colour', + }, + }, + 'ColorMoireReduction' => 'Colour Moire Reduction', + 'ColorMoireReductionMode' => 'Colour Moire Reduction Mode', + 'ColorNoiseReduction' => 'Colour Noise Reduction', + 'ColorNoiseReductionDetail' => 'Colour Noise Reduction Detail', + 'ColorNoiseReductionIntensity' => 'Colour Noise Reduction Intensity', + 'ColorNoiseReductionSharpness' => 'Colour Noise Reduction Sharpness', + 'ColorNoiseReductionSmoothness' => 'Colour Noise Reduction Smoothness', + 'ColorObjBackType' => 'Colour Obj Back Type', + 'ColorObjName' => 'Colour Obj Name', + 'ColorObjType' => 'Colour Obj Type', + 'ColorObjVersion' => 'Colour Obj Version', + 'ColorPalette' => 'Colour Palette', + 'ColorPlanes' => 'Colour Planes', + 'ColorPrimaries' => 'Colour Primaries', + 'ColorProfile' => 'Colour Profile', + 'ColorProfileSettings' => 'Colour Profile Settings', + 'ColorRangeLevels' => 'Colour Range Levels', + 'ColorRepresentation' => 'Colour Representation', + 'ColorReproduction' => 'Colour Reproduction', + 'ColorResolutionDepth' => 'Colour Resolution Depth', + 'ColorResponseUnit' => 'Colour Response Unit', + 'ColorSamplersResource' => 'Colour Samplers Resource', + 'ColorSamplersResource2' => 'Colour Samplers Resource 2', + 'ColorSaturationAdj' => 'Colour Saturation Adj', + 'ColorSequence' => 'Colour Sequence', + 'ColorSiting' => 'Colour Siting', + 'ColorSpace' => { + Description => 'Colour Space', + PrintConv => { + 'Embedded Color Profile' => 'Embedded Colour Profile', + 'Linked Color Profile' => 'Linked Colour Profile', + 'No color space specified' => 'No colour space specified', + 'Windows Color Space' => 'Windows Colour Space', + }, + }, + 'ColorSpaceData' => 'Colour Space Data', + 'ColorSpecApproximation' => 'Colour Spec Approximation', + 'ColorSpecData' => 'Colour Spec Data', + 'ColorSpecMethod' => { + Description => 'Colour Spec Method', + PrintConv => { + 'Vendor Color' => 'Vendor Colour', + }, + }, + 'ColorSpecPrecedence' => 'Colour Spec Precedence', + 'ColorSpecification' => 'Colour Specification', + 'ColorTable' => 'Colour Table', + 'ColorTempAsShot' => 'Colour Temp As Shot', + 'ColorTempAuto' => 'Colour Temp Auto', + 'ColorTempCloudy' => 'Colour Temp Cloudy', + 'ColorTempCustom' => 'Colour Temp Custom', + 'ColorTempCustom1' => 'Colour Temp Custom 1', + 'ColorTempCustom2' => 'Colour Temp Custom 2', + 'ColorTempDaylight' => 'Colour Temp Daylight', + 'ColorTempFlash' => 'Colour Temp Flash', + 'ColorTempFlashData' => 'Colour Temp Flash Data', + 'ColorTempFluorescent' => 'Colour Temp Fluorescent', + 'ColorTempFluorescentD' => 'Colour Temp Fluorescent D', + 'ColorTempFluorescentN' => 'Colour Temp Fluorescent N', + 'ColorTempFluorescentW' => 'Colour Temp Fluorescent W', + 'ColorTempKelvin' => 'Colour Temp Kelvin', + 'ColorTempMeasured' => 'Colour Temp Measured', + 'ColorTempPC1' => 'Colour Temp PC1', + 'ColorTempPC2' => 'Colour Temp PC2', + 'ColorTempPC3' => 'Colour Temp PC3', + 'ColorTempShade' => 'Colour Temp Shade', + 'ColorTempTungsten' => 'Colour Temp Tungsten', + 'ColorTempUnknown' => 'Colour Temp Unknown', + 'ColorTempUnknown10' => 'Colour Temp Unknown 10', + 'ColorTempUnknown11' => 'Colour Temp Unknown 11', + 'ColorTempUnknown12' => 'Colour Temp Unknown 12', + 'ColorTempUnknown13' => 'Colour Temp Unknown 13', + 'ColorTempUnknown14' => 'Colour Temp Unknown 14', + 'ColorTempUnknown15' => 'Colour Temp Unknown 15', + 'ColorTempUnknown16' => 'Colour Temp Unknown 16', + 'ColorTempUnknown17' => 'Colour Temp Unknown 17', + 'ColorTempUnknown18' => 'Colour Temp Unknown 18', + 'ColorTempUnknown19' => 'Colour Temp Unknown 19', + 'ColorTempUnknown2' => 'Colour Temp Unknown 2', + 'ColorTempUnknown20' => 'Colour Temp Unknown 20', + 'ColorTempUnknown21' => 'Colour Temp Unknown 21', + 'ColorTempUnknown22' => 'Colour Temp Unknown 22', + 'ColorTempUnknown23' => 'Colour Temp Unknown 23', + 'ColorTempUnknown24' => 'Colour Temp Unknown 24', + 'ColorTempUnknown25' => 'Colour Temp Unknown 25', + 'ColorTempUnknown26' => 'Colour Temp Unknown 26', + 'ColorTempUnknown27' => 'Colour Temp Unknown 27', + 'ColorTempUnknown28' => 'Colour Temp Unknown 28', + 'ColorTempUnknown29' => 'Colour Temp Unknown 29', + 'ColorTempUnknown3' => 'Colour Temp Unknown 3', + 'ColorTempUnknown30' => 'Colour Temp Unknown 30', + 'ColorTempUnknown4' => 'Colour Temp Unknown 4', + 'ColorTempUnknown5' => 'Colour Temp Unknown 5', + 'ColorTempUnknown6' => 'Colour Temp Unknown 6', + 'ColorTempUnknown7' => 'Colour Temp Unknown 7', + 'ColorTempUnknown8' => 'Colour Temp Unknown 8', + 'ColorTempUnknown9' => 'Colour Temp Unknown 9', + 'ColorTemperature' => 'Colour Temperature', + 'ColorTemperatureAdj' => 'Colour Temperature Adj', + 'ColorTemperatureAuto' => 'Colour Temperature Auto', + 'ColorTemperatureBG' => 'Colour Temperature BG', + 'ColorTemperatureCustom' => 'Colour Temperature Custom', + 'ColorTemperatureRG' => 'Colour Temperature RG', + 'ColorTemperatureSet' => 'Colour Temperature Set', + 'ColorTemperatureSetting' => { + Description => 'Colour Temperature Setting', + PrintConv => { + 'Color Filter' => 'Colour Filter', + }, + }, + 'ColorTone' => 'Colour Tone', + 'ColorToneAdj' => 'Colour Tone Adj', + 'ColorToneAuto' => 'Colour Tone Auto', + 'ColorToneFaithful' => 'Colour Tone Faithful', + 'ColorToneLandscape' => 'Colour Tone Landscape', + 'ColorToneMonochrome' => 'Colour Tone Monochrome', + 'ColorToneNeutral' => 'Colour Tone Neutral', + 'ColorTonePortrait' => 'Colour Tone Portrait', + 'ColorToneStandard' => 'Colour Tone Standard', + 'ColorToneUserDef1' => 'Colour Tone User Def 1', + 'ColorToneUserDef2' => 'Colour Tone User Def 2', + 'ColorToneUserDef3' => 'Colour Tone User Def 3', + 'ColorTransferFuncs' => 'Colour Transfer Funcs', + 'ColorTransform' => 'Colour Transform', + 'ColorTwistMatrix' => 'Colour Twist Matrix', + 'ColorType' => { + Description => 'Colour Type', + PrintConv => { + 'Color' => 'Colour', + 'Color Alpha' => 'Colour Alpha', + }, + }, + 'Colorant1Coordinates' => 'Colourant 1 Coordinates', + 'Colorant1Name' => 'Colourant 1 Name', + 'Colorant2Coordinates' => 'Colourant 2 Coordinates', + 'Colorant2Name' => 'Colourant 2 Name', + 'Colorant3Coordinates' => 'Colourant 3 Coordinates', + 'Colorant3Name' => 'Colourant 3 Name', + 'ColorantA' => 'Colourant A', + 'ColorantB' => 'Colourant B', + 'ColorantBlack' => 'Colourant Black', + 'ColorantBlue' => 'Colourant Blue', + 'ColorantCount' => 'Colourant Count', + 'ColorantCyan' => 'Colourant Cyan', + 'ColorantGray' => 'Colourant Gray', + 'ColorantGreen' => 'Colourant Green', + 'ColorantL' => 'Colourant L', + 'ColorantMagenta' => 'Colourant Magenta', + 'ColorantMode' => 'Colourant Mode', + 'ColorantOrder' => 'Colourant Order', + 'ColorantRed' => 'Colourant Red', + 'ColorantSwatchName' => 'Colourant Swatch Name', + 'ColorantTableOut' => 'Colourant Table Out', + 'ColorantTint' => 'Colourant Tint', + 'ColorantType' => 'Colourant Type', + 'ColorantYellow' => 'Colourant Yellow', + 'Colorants' => 'Colourants', + 'ColorimetricIntentImageState' => 'Colourimetric Intent Image State', + 'ColorimetricReference' => 'Colourimetric Reference', + 'Colorimetry' => 'Colourimetry', + 'ColorimetryCode' => 'Colourimetry Code', + 'Colors' => 'Colours', + 'Compression' => { + PrintConv => { + 'JBIG Color' => 'JBIG Colour', + }, + }, + 'ContrastMode' => { + PrintConv => { + 'Dynamic (Color Film)' => 'Dynamic (Colour Film)', + 'Dynamic Art (My Color)' => 'Dynamic Art (My Colour)', + 'Elegant (My Color)' => 'Elegant (My Colour)', + 'Nature (Color Film)' => 'Nature (Colour Film)', + 'Nostalgic (Color Film)' => 'Nostalgic (Colour Film)', + 'Retro (My Color)' => 'Retro (My Colour)', + 'Smooth (Color Film) or Pure (My Color)' => 'Smooth (Colour Film) or Pure (My Colour)', + 'Vibrant (Color Film) or Expressive (My Color)' => 'Vibrant (Colour Film) or Expressive (My Colour)', + }, + }, + 'CustomColorTone' => 'Custom Colour Tone', + 'D-LightingHQColorBoost' => 'D-Lighting HQ Colour Boost', + 'D-LightingHSColorBoost' => 'D-Lighting HS Colour Boost', + 'DefHilite' => 'Use Default Highlight Colour', + 'DefaultImageColor' => 'Default Image Colour', + 'DeltaType' => { + PrintConv => { + 'Color Addition' => 'Colour Addition', + 'Color Replacement' => 'Colour Replacement', + }, + }, + 'DigitalFilter01' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Replace Color' => 'Replace Colour', + 'Unicolor Bold' => 'Unicolour Bold', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter02' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter03' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter04' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter05' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter06' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter07' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter08' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter09' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter10' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter11' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter12' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter13' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter14' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter15' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter16' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter17' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter18' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter19' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter20' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DisplayUnits' => { + PrintConv => { + 'meters' => 'Metres', + }, + }, + 'EasyMode' => { + PrintConv => { + 'Color Accent' => 'Colour Accent', + 'Color Swap' => 'Colour Swap', + 'My Colors' => 'My Colours', + }, + }, + 'EmbeddedImageColorSpace' => 'Embedded Image Colour Space', + 'ExposureProgram' => { + PrintConv => { + 'Partial Color Blue' => 'Partial Colour Blue', + 'Partial Color Green' => 'Partial Colour Green', + 'Partial Color Red' => 'Partial Colour Red', + 'Partial Color Yellow' => 'Partial Colour Yellow', + 'Pop Color' => 'Pop Colour', + }, + }, + 'FaithfulRawColorTone' => 'Faithful Raw Colour Tone', + 'FaxProfile' => { + PrintConv => { + 'Lossless color and grayscale, L' => 'Lossless colour and grayscale, L', + 'Lossy color and grayscale, C' => 'Lossy colour and grayscale, C', + }, + }, + 'FgColor' => 'Foreground Colour', + 'FilmColorProcess' => 'Film Colour Process', + 'FilmMode' => { + PrintConv => { + 'Dynamic (color)' => 'Dynamic (colour)', + 'Nature (color)' => 'Nature (colour)', + 'Smooth (color)' => 'Smooth (colour)', + 'Standard (color)' => 'Standard (colour)', + }, + }, + 'FlagColor' => 'Flag Colour', + 'FlashColorFilter' => 'Flash Colour Filter', + 'FocalPlaneColorimetryEstimates' => 'Focal Plane Colourimetry Estimates', + 'GammaColorTone' => 'Gamma Colour Tone', + 'GenOpColor' => 'Gen Op Colour', + 'GenreID' => { + PrintConv => { + 'Books|Kids|Basic Concepts|Colors' => 'Books|Kids|Basic Concepts|Colours', + }, + }, + 'GreenPaletteColorTableData' => 'Green Palette Colour Table Data', + 'GreenPaletteColorTableDescriptor' => 'Green Palette Colour Table Descriptor', + 'HasColorMap' => 'Has Colour Map', + 'HighlightColorDistortReduct' => 'Highlight Colour Distort Reduct', + 'HiliteColor' => 'Highlight Colour', + 'ImageAlterationConstraints' => { + PrintConv => { + 'No Colorization' => 'No Colourization', + 'No De-Colorization' => 'No De-Colourization', + }, + }, + 'ImageColor' => 'Image Colour', + 'ImageColorIndicator' => { + Description => 'Image Colour Indicator', + PrintConv => { + 'Specified Image Color' => 'Specified Image Colour', + 'Unspecified Image Color' => 'Unspecified Image Colour', + }, + }, + 'ImageColorValue' => 'Image Colour Value', + 'ImageMedium' => { + PrintConv => { + 'Color hard copy' => 'Colour hard copy', + 'Color negative' => 'Colour negative', + 'Color reversal' => 'Colour reversal', + }, + }, + 'IndexedColorTableCount' => 'Indexed Colour Table Count', + 'InterchangeColorSpace' => 'Interchange Colour Space', + 'Isotherm1Color' => 'Isotherm 1 Colour', + 'Isotherm2Color' => 'Isotherm 2 Colour', + 'LandscapeRawColorTone' => 'Landscape Raw Colour Tone', + 'LargeBluePaletteColorTableData' => 'Large Blue Palette Colour Table Data', + 'LargeBluePaletteColorTableDescr' => 'Large Blue Palette Colour Table Descr', + 'LargeGreenPaletteColorTableData' => 'Large Green Palette Colour Table Data', + 'LargeGreenPaletteColorTableDescr' => 'Large Green Palette Colour Table Descr', + 'LargePaletteColorLookupTableUID' => 'Large Palette Colour Lookup Table UID', + 'LargeRedPaletteColorTableData' => 'Large Red Palette Colour Table Data', + 'LargeRedPaletteColorTableDescr' => 'Large Red Palette Colour Table Descr', + 'LayerBlendModes' => { + PrintConv => { + 'Color' => 'Colour', + 'Color Burn' => 'Colour Burn', + 'Color Dodge' => 'Colour Dodge', + 'Darker Color' => 'Darker Colour', + 'Lighter Color' => 'Lighter Colour', + }, + }, + 'MDColorTable' => 'MD Colour Table', + 'MDItemColorSpace' => 'MD Item Colour Space', + 'MagicFilter' => { + PrintConv => { + 'Pale & Light Color' => 'Pale & Light Colour', + 'Pale & Light Color II' => 'Pale & Light Colour II', + 'Partial Color' => 'Partial Colour', + 'Partial Color II' => 'Partial Colour II', + 'Partial Color III' => 'Partial Colour III', + 'Watercolor' => 'Watercolour', + 'Watercolor I' => 'Watercolour I', + 'Watercolor II' => 'Watercolour II', + }, + }, + 'MandatoryBackground' => { + PrintConv => { + 'Color Advisory, Image Mandatory' => 'Colour Advisory, Image Mandatory', + 'Color Mandatory, Image Advisory' => 'Colour Mandatory, Image Advisory', + 'Color and Image Advisory' => 'Colour and Image Advisory', + 'Color and Image Mandatory' => 'Colour and Image Mandatory', + }, + }, + 'MattColor' => 'Matt Colour', + 'MediaColor' => 'Media Colour', + 'ModifiedColorTemp' => 'Modified Colour Temp', + 'MonochromeColor' => 'Monochrome Colour', + 'MyColorMode' => { + Description => 'My Colour Mode', + PrintConv => { + 'Color Accent' => 'Colour Accent', + 'Color Swap' => 'Colour Swap', + }, + }, + 'NamedColor' => 'Named Colour', + 'NamedColor2' => 'Named Colour 2', + 'NeutralRawColorTone' => 'Neutral Raw Colour Tone', + 'NewColorType' => 'New Colour Type', + 'NumColors' => 'Num Colours', + 'NumImportantColors' => 'Num Important Colours', + 'OpColor' => 'Op Colour', + 'OverflowColor' => 'Overflow Colour', + 'PF25ColorMatrix' => 'PF25 Colour Matrix', + 'PaletteColorTableUID' => 'Palette Colour Table UID', + 'PaletteColors' => 'Palette Colours', + 'PhotoEffect' => { + PrintConv => { + 'My Color Data' => 'My Colour Data', + }, + }, + 'PhotometricInterpretation' => { + PrintConv => { + 'Color Filter Array' => 'Colour Filter Array', + }, + }, + 'Photoshop2ColorTable' => 'Photoshop 2 Colour Table', + 'PictureEffect' => { + PrintConv => { + 'Partial Color (blue)' => 'Partial Colour (blue)', + 'Partial Color (green)' => 'Partial Colour (green)', + 'Partial Color (red)' => 'Partial Colour (red)', + 'Partial Color (yellow)' => 'Partial Colour (yellow)', + 'Pop Color' => 'Pop Colour', + 'Water Color' => 'Water Colour', + 'Water Color 2' => 'Water Colour 2', + }, + }, + 'PictureEffect2' => { + PrintConv => { + 'Partial Color' => 'Partial Colour', + 'Pop Color' => 'Pop Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'PictureIndexedColors' => 'Picture Indexed Colours', + 'PictureMode' => { + PrintConv => { + 'Color Creator' => 'Colour Creator', + 'Color Profile 1' => 'Colour Profile 1', + 'Color Profile 2' => 'Colour Profile 2', + 'Color Profile 3' => 'Colour Profile 3', + }, + }, + 'PictureType' => { + PrintConv => { + 'Bright(ly) Colored Fish' => 'Bright(ly) Coloured Fish', + }, + }, + 'PictureWizardColor' => 'Picture Wizard Colour', + 'PortraitRawColorTone' => 'Portrait Raw Colour Tone', + 'PresetWhiteBalance' => { + PrintConv => { + 'Color Temperature' => 'Colour Temperature', + }, + }, + 'PreviewColorSpace' => 'Preview Colour Space', + 'ProfileClass' => { + PrintConv => { + 'ColorSpace Conversion Profile' => 'ColourSpace Conversion Profile', + 'NamedColor Profile' => 'Named Colour Profile', + }, + }, + 'PseudoColorType' => 'Pseudo Colour Type', + 'QuantizationMethod' => { + PrintConv => { + 'Color Space Specific' => 'Colour Space Specific', + }, + }, + 'RGBBkCol' => 'Background Colour', + 'RGBFgCol' => 'Foreground Colour', + 'ROIDisplayColor' => 'ROI Display Colour', + 'RawColorAdj' => 'Raw Colour Adj', + 'RawDevArtFilter' => { + PrintConv => { + 'Pale & Light Color' => 'Pale & Light Colour', + 'Pale & Light Color II' => 'Pale & Light Colour II', + 'Partial Color' => 'Partial Colour', + 'Partial Color II' => 'Partial Colour II', + 'Partial Color III' => 'Partial Colour III', + 'Watercolor' => 'Watercolour', + 'Watercolor I' => 'Watercolour I', + 'Watercolor II' => 'Watercolour II', + }, + }, + 'RawDevColorSpace' => 'Raw Dev Colour Space', + 'RawDevMemoryColorEmphasis' => 'Raw Dev Memory Colour Emphasis', + 'RawDevSettings' => { + PrintConv => { + 'Color Space' => 'Colour Space', + 'WB Color Temp' => 'WB Colour Temp', + }, + }, + 'RawDevWhiteBalance' => { + PrintConv => { + 'Color Temperature' => 'Colour Temperature', + }, + }, + 'RedPaletteColorTableData' => 'Red Palette Colour Table Data', + 'RedPaletteColorTableDescriptor' => 'Red Palette Colour Table Descriptor', + 'ReflectionHardcopyOrigColorimetry' => 'Reflection Hardcopy Orig Colourimetry', + 'ReflectionPrintOutputColorimetry' => 'Reflection Print Output Colourimetry', + 'RenderingIntent' => { + PrintConv => { + 'Absolute Colorimetric (LCS_GM_ABS_COLORIMETRIC)' => 'Absolute Colourimetric (LCS_GM_ABS_COLORIMETRIC)', + 'ICC-Absolute Colorimetric' => 'ICC-Absolute Colourimetric', + 'Media-Relative Colorimetric' => 'Media-Relative Colourimetric', + }, + }, + 'RetouchHistory' => { + PrintConv => { + 'Color Custom' => 'Colour Custom', + 'Color Outline' => 'Colour Outline', + 'Color Sketch' => 'Colour Sketch', + 'Selective Color' => 'Selective Colour', + }, + }, + 'SBAInputImageColorspace' => 'SBA Input Image Colourspace', + 'SRGBRendering' => { + PrintConv => { + 'Absolute Colorimetric' => 'Absolute Colourimetric', + 'Relative Colorimetric' => 'Relative Colourimetric', + }, + }, + 'SceneBalanceAlgorithmCommand' => { + PrintConv => { + 'Neutral SBA Off, Color SBA Off' => 'Neutral SBA Off, Colour SBA Off', + 'Neutral SBA Off, Color SBA On' => 'Neutral SBA Off, Colour SBA On', + 'Neutral SBA On, Color SBA Off' => 'Neutral SBA On, Colour SBA Off', + 'Neutral SBA On, Color SBA On' => 'Neutral SBA On, Colour SBA On', + }, + }, + 'SceneColorimetryEstimates' => 'Scene Colourimetry Estimates', + 'SceneMode' => { + PrintConv => { + 'Color Effects' => 'Colour Effects', + 'My Color' => 'My Colour', + }, + }, + 'ScreenMinimumColorBitDepth' => 'Screen Minimum Colour Bit Depth', + 'SegmentedBlueColorTableData' => 'Segmented Blue Colour Table Data', + 'SegmentedGreenColorTableData' => 'Segmented Green Colour Table Data', + 'SegmentedRedColorTableData' => 'Segmented Red Colour Table Data', + 'SensingMethod' => { + PrintConv => { + 'Color sequential area' => 'Colour sequential area', + 'Color sequential linear' => 'Colour sequential linear', + 'One-chip color area' => 'One-chip colour area', + 'Three-chip color area' => 'Three-chip colour area', + 'Two-chip color area' => 'Two-chip colour area', + }, + }, + 'ShootingMode' => { + PrintConv => { + 'Color Effects' => 'Colour Effects', + 'My Color' => 'My Colour', + }, + }, + 'ShutterPresentationColorCIELabVal' => 'Shutter Presentation Colour CIE Lab Val', + 'SmartAlbumColor' => 'Smart Album Colour', + 'SpecialEffectsOpticalFilter' => { + PrintConv => { + 'Colored' => 'Coloured', + }, + }, + 'StandardRawColorTone' => 'Standard Raw Colour Tone', + 'StdOutputColorMode' => 'Std Output Colour Mode', + 'StreamColor' => 'Stream Colour', + 'SubfileType' => { + PrintConv => { + 'Color IW44' => 'Colour IW44', + }, + }, + 'SubimageColor' => 'Subimage Colour', + 'SwatchColorantA' => 'Swatch Colourant A', + 'SwatchColorantB' => 'Swatch Colourant B', + 'SwatchColorantBlack' => 'Swatch Colourant Black', + 'SwatchColorantBlue' => 'Swatch Colourant Blue', + 'SwatchColorantCyan' => 'Swatch Colourant Cyan', + 'SwatchColorantGray' => 'Swatch Colourant Gray', + 'SwatchColorantGreen' => 'Swatch Colourant Green', + 'SwatchColorantL' => 'Swatch Colourant L', + 'SwatchColorantMagenta' => 'Swatch Colourant Magenta', + 'SwatchColorantMode' => 'Swatch Colourant Mode', + 'SwatchColorantRed' => 'Swatch Colourant Red', + 'SwatchColorantSwatchName' => 'Swatch Colourant Swatch Name', + 'SwatchColorantTint' => 'Swatch Colourant Tint', + 'SwatchColorantType' => 'Swatch Colourant Type', + 'SwatchColorantYellow' => 'Swatch Colourant Yellow', + 'SwatchGroupsColorants' => 'Swatch Groups Colourants', + 'TestTarget' => { + PrintConv => { + 'Color Chart' => 'Colour Chart', + }, + }, + 'TextColor' => 'Text Colour', + 'ToneCurveColorSpace' => 'Tone Curve Colour Space', + 'ToningEffect' => { + PrintConv => { + 'Color' => 'Colour', + }, + }, + 'TypeOfOriginal' => { + PrintConv => { + 'Color Document' => 'Colour Document', + 'Color Print' => 'Colour Print', + }, + }, + 'UCRBG' => 'Under Colour Removal & Black Gen.', + 'USPTOOriginalContentType' => { + PrintConv => { + 'Color' => 'Colour', + }, + }, + 'UltrasoundColorDataPresent' => 'Ultrasound Colour Data Present', + 'UnderflowColor' => 'Underflow Colour', + 'Unsharp1Color' => 'Unsharp 1 Colour', + 'Unsharp2Color' => 'Unsharp 2 Colour', + 'Unsharp3Color' => 'Unsharp 3 Colour', + 'Unsharp4Color' => 'Unsharp 4 Colour', + 'VideoAlphaPremultipleColor' => 'Video Alpha Premultiple Colour', + 'VideoAlphaPremultipleColorA' => 'Video Alpha Premultiple Colour A', + 'VideoAlphaPremultipleColorB' => 'Video Alpha Premultiple Colour B', + 'VideoAlphaPremultipleColorBlack' => 'Video Alpha Premultiple Colour Black', + 'VideoAlphaPremultipleColorBlue' => 'Video Alpha Premultiple Colour Blue', + 'VideoAlphaPremultipleColorCyan' => 'Video Alpha Premultiple Colour Cyan', + 'VideoAlphaPremultipleColorGray' => 'Video Alpha Premultiple Colour Gray', + 'VideoAlphaPremultipleColorGreen' => 'Video Alpha Premultiple Colour Green', + 'VideoAlphaPremultipleColorL' => 'Video Alpha Premultiple Colour L', + 'VideoAlphaPremultipleColorMagenta' => 'Video Alpha Premultiple Colour Magenta', + 'VideoAlphaPremultipleColorMode' => 'Video Alpha Premultiple Colour Mode', + 'VideoAlphaPremultipleColorRed' => 'Video Alpha Premultiple Colour Red', + 'VideoAlphaPremultipleColorSwatchName' => 'Video Alpha Premultiple Colour Swatch Name', + 'VideoAlphaPremultipleColorTint' => 'Video Alpha Premultiple Colour Tint', + 'VideoAlphaPremultipleColorType' => 'Video Alpha Premultiple Colour Type', + 'VideoAlphaPremultipleColorYellow' => 'Video Alpha Premultiple Colour Yellow', + 'VideoColorKind' => 'Video Colour Kind', + 'VideoColorSpace' => 'Video Colour Space', + 'VisualColor' => { + Description => 'Visual Colour', + PrintConv => { + 'Color' => 'Colour', + }, + }, + 'WBAdjColorTemp' => 'WB Adj Colour Temp', + 'WBAdjLighting' => { + PrintConv => { + 'High Color Rendering Fluorescent (3700K)' => 'High Colour Rendering Fluorescent (1)', + 'High Color Rendering Fluorescent (5000K)' => 'High Colour Rendering Fluorescent (3)', + 'High Color Rendering Fluorescent (cool white)' => 'High Colour Rendering Fluorescent (2)', + 'High Color Rendering Fluorescent (daylight)' => 'High Colour Rendering Fluorescent (4)', + 'High Color Rendering Fluorescent (warm white)' => 'High Colour Rendering Fluorescent (0)', + 'Sodium Vapor Lamps' => 'Sodium Vapour Lamps', + 'Standard Fluorescent (high temperature mercury vapor)' => 'Standard Fluorescent (high temperature mercury vapour)', + }, + }, + 'WatercolorFilter' => 'Watercolour Filter', + 'WhiteBalance' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Color Temperature' => 'Colour Temperature', + 'Color Temperature Enhancement' => 'Colour Temperature Enhancement', + 'Color Temperature/Color Filter' => 'Colour Temperature/Colour Filter', + }, + }, + 'WhiteBalance2' => { + PrintConv => { + 'Auto (Keep Warm Color Off)' => 'Auto (Keep Warm Colour Off)', + }, + }, + 'WhiteBalanceSet' => { + PrintConv => { + 'Set Color Temperature 1' => 'Set Colour Temperature 1', + 'Set Color Temperature 2' => 'Set Colour Temperature 2', + 'Set Color Temperature 3' => 'Set Colour Temperature 3', + }, + }, + 'WhiteBalanceSetting' => { + PrintConv => { + 'Color Temperature/Color Filter' => 'Colour Temperature/Colour Filter', + }, + }, + 'WorkColorSpace' => { + Description => 'Work Colour Space', + PrintConv => { + 'ColorMatch RGB' => 'ColourMatch RGB', + }, + }, + 'XMethod' => { + PrintConv => { + 'Color Closest Pixel and Alpha Linear Interpolation' => 'Colour Closest Pixel and Alpha Linear Interpolation', + 'Color Linear Interpolation and Alpha Closest Pixel' => 'Colour Linear Interpolation and Alpha Closest Pixel', + }, + }, + 'YMethod' => { + PrintConv => { + 'Color Closest Pixel and Alpha Linear Interpolation' => 'Colour Closest Pixel and Alpha Linear Interpolation', + 'Color Linear Interpolation and Alpha Closest Pixel' => 'Colour Linear Interpolation and Alpha Closest Pixel', + }, + }, +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::en_ca.pm - ExifTool Canadian English language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/en_gb.pm b/ExifTool/lib/Image/ExifTool/Lang/en_gb.pm new file mode 100644 index 0000000..00154a8 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/en_gb.pm @@ -0,0 +1,1045 @@ +#------------------------------------------------------------------------------ +# File: en_gb.pm +# +# Description: ExifTool British English language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::en_gb; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.13'; + +%Image::ExifTool::Lang::en_gb::Translate = ( + 'AboveColor' => 'Above Colour', + 'AdvancedFilter' => { + PrintConv => { + 'Partial Color Blue' => 'Partial Colour Blue', + 'Partial Color Green' => 'Partial Colour Green', + 'Partial Color Orange' => 'Partial Colour Orange', + 'Partial Color Purple' => 'Partial Colour Purple', + 'Partial Color Red' => 'Partial Colour Red', + 'Partial Color Yellow' => 'Partial Colour Yellow', + 'Pop Color' => 'Pop Colour', + }, + }, + 'AdvancedSceneMode' => { + PrintConv => { + 'Color Select' => 'Colour Select', + }, + }, + 'AllColorFlatField1' => 'All Colour Flat Field 1', + 'AllColorFlatField2' => 'All Colour Flat Field 2', + 'AllColorFlatField3' => 'All Colour Flat Field 3', + 'Alpha' => { + PrintConv => { + 'Alpha Exists (W color component)' => 'Alpha Exists (W colour component)', + 'Alpha Exists (color not premultiplied)' => 'Alpha Exists (colour not premultiplied)', + 'Alpha Exists (color premultiplied)' => 'Alpha Exists (colour premultiplied)', + }, + }, + 'AlternateDuotoneColors' => 'Alternate Duotone Colours', + 'AlternateSpotColors' => 'Alternate Spot Colours', + 'ArtFilter' => { + PrintConv => { + 'Pale & Light Color' => 'Pale & Light Colour', + 'Pale & Light Color II' => 'Pale & Light Colour II', + 'Partial Color' => 'Partial Colour', + 'Partial Color II' => 'Partial Colour II', + 'Partial Color III' => 'Partial Colour III', + 'Watercolor' => 'Watercolour', + 'Watercolor I' => 'Watercolour I', + 'Watercolor II' => 'Watercolour II', + }, + }, + 'ArtFilterEffect' => { + PrintConv => { + 'Green Color Filter' => 'Green Colour Filter', + 'No Color Filter' => 'No Colour Filter', + 'Orange Color Filter' => 'Orange Colour Filter', + 'Pale & Light Color' => 'Pale & Light Colour', + 'Pale & Light Color II' => 'Pale & Light Colour II', + 'Partial Color' => 'Partial Colour', + 'Partial Color II' => 'Partial Colour II', + 'Partial Color III' => 'Partial Colour III', + 'Red Color Filter' => 'Red Colour Filter', + 'Watercolor' => 'Watercolour', + 'Watercolor I' => 'Watercolour I', + 'Watercolor II' => 'Watercolour II', + 'Yellow Color Filter' => 'Yellow Colour Filter', + }, + }, + 'AudioIsInitialized' => 'Audio Is Initialised', + 'AutoAFPointColorTracking' => 'Auto AF Point Colour Tracking', + 'AutoLightingOptimizer' => 'Auto Lighting Optimiser', + 'AutoLightingOptimizerOn' => 'Auto Lighting Optimiser On', + 'BackgroundColor' => 'Background Colour', + 'BackgroundColorIndicator' => { + Description => 'Background Colour Indicator', + PrintConv => { + 'Specified Background Color' => 'Specified Background Colour', + 'Unspecified Background Color' => 'Unspecified Background Colour', + }, + }, + 'BackgroundColorValue' => 'Background Colour Value', + 'BasicColorImageSequence' => 'Basic Colour Image Sequence', + 'BelowColor' => 'Below Colour', + 'BestShotMode' => { + PrintConv => { + 'Water Color' => 'Water Colour', + }, + }, + 'BkColor' => 'Background Colour', + 'BluePaletteColorTableData' => 'Blue Palette Colour Table Data', + 'BluePaletteColorTableDescriptor' => 'Blue Palette Colour Table Descriptor', + 'BorderColor' => 'Border Colour', + 'CFAPlaneColor' => 'CFA Plane Colour', + 'CameraColorCalibration01' => 'Camera Colour Calibration 01', + 'CameraColorCalibration02' => 'Camera Colour Calibration 02', + 'CameraColorCalibration03' => 'Camera Colour Calibration 03', + 'CameraColorCalibration04' => 'Camera Colour Calibration 04', + 'CameraColorCalibration05' => 'Camera Colour Calibration 05', + 'CameraColorCalibration06' => 'Camera Colour Calibration 06', + 'CameraColorCalibration07' => 'Camera Colour Calibration 07', + 'CameraColorCalibration08' => 'Camera Colour Calibration 08', + 'CameraColorCalibration09' => 'Camera Colour Calibration 09', + 'CameraColorCalibration10' => 'Camera Colour Calibration 10', + 'CameraColorCalibration11' => 'Camera Colour Calibration 11', + 'CameraColorCalibration12' => 'Camera Colour Calibration 12', + 'CameraColorCalibration13' => 'Camera Colour Calibration 13', + 'CameraColorCalibration14' => 'Camera Colour Calibration 14', + 'CameraColorCalibration15' => 'Camera Colour Calibration 15', + 'CameraRawColorTone' => 'Camera Raw Colour Tone', + 'CanonColorInfo1' => 'Canon Colour Info 1', + 'CanonColorInfo2' => 'Canon Colour Info 2', + 'ChromaticityColorant' => 'Chromaticity Colourant', + 'CodingMethods' => { + PrintConv => { + 'JBIG color' => 'JBIG colour', + }, + }, + 'Color' => { + Description => 'Colour', + PrintConv => { + 'Color' => 'Colour', + }, + }, + 'ColorAberrationControl' => 'Colour Aberration Control', + 'ColorAdjustment' => 'Colour Adjustment', + 'ColorAdjustmentMode' => 'Colour Adjustment Mode', + 'ColorAverages' => 'Colour Averages', + 'ColorBW' => 'Colour BW', + 'ColorBalance' => 'Colour Balance', + 'ColorBalanceAdj' => 'Colour Balance Adj', + 'ColorBalanceBlue' => 'Colour Balance Blue', + 'ColorBalanceGreen' => 'Colour Balance Green', + 'ColorBalanceRed' => 'Colour Balance Red', + 'ColorBalanceUnknown' => 'Colour Balance Unknown', + 'ColorBalanceVersion' => 'Colour Balance Version', + 'ColorBitDepth' => 'Colour Bit Depth', + 'ColorBlur' => 'Colour Blur', + 'ColorBlurOn' => 'Colour Blur On', + 'ColorBoostLevel' => 'Colour Boost Level', + 'ColorBoostType' => 'Colour Boost Type', + 'ColorBooster' => 'Colour Booster', + 'ColorCalibrationMatrix' => 'Colour Calibration Matrix', + 'ColorCasts' => 'Colour Casts', + 'ColorCharacterization' => 'Colour Characterization', + 'ColorClass' => 'Colour Class', + 'ColorCompensationFilter' => 'Colour Compensation Filter', + 'ColorCompensationFilterCustom' => 'Colour Compensation Filter Custom', + 'ColorCompensationFilterSet' => 'Colour Compensation Filter Set', + 'ColorComponents' => 'Colour Components', + 'ColorControl' => 'Colour Control', + 'ColorCorrection' => 'Colour Correction', + 'ColorCreatorEffect' => 'Colour Creator Effect', + 'ColorDataVersion' => 'Colour Data Version', + 'ColorDescriptor' => 'Colour Descriptor', + 'ColorEffect' => 'Colour Effect', + 'ColorFieldCode' => 'Colour Field Code', + 'ColorFilter' => 'Colour Filter', + 'ColorGain' => 'Colour Gain', + 'ColorGroup' => 'Colour Group', + 'ColorHalftoningInfo' => 'Colour Halftoning Info', + 'ColorHue' => 'Colour Hue', + 'ColorImagePrintingFlag' => 'Colour Image Printing Flag', + 'ColorLabel' => 'Colour Label', + 'ColorMap' => 'Colour Map', + 'ColorMatrix' => 'Colour Matrix', + 'ColorMatrix1' => 'Colour Matrix 1', + 'ColorMatrix2' => 'Colour Matrix 2', + 'ColorMatrixA' => 'Colour Matrix A', + 'ColorMatrixAdobeRGB' => 'Colour Matrix Adobe RGB', + 'ColorMatrixB' => 'Colour Matrix B', + 'ColorMatrixNumber' => 'Colour Matrix Number', + 'ColorMatrixSRGB' => 'Colour Matrix sRGB', + 'ColorMode' => { + Description => 'Colour Mode', + PrintConv => { + 'Indexed Color' => 'Indexed Colour', + 'Natural color' => 'Natural colour', + 'Neutral Color' => 'Neutral Colour', + 'RGB Color' => 'RGB Colour', + 'Saturated Color' => 'Saturated Colour', + 'Vivid color' => 'Vivid colour', + }, + }, + 'ColorMoireReduction' => 'Colour Moire Reduction', + 'ColorMoireReductionMode' => 'Colour Moire Reduction Mode', + 'ColorNoiseReduction' => 'Colour Noise Reduction', + 'ColorNoiseReductionDetail' => 'Colour Noise Reduction Detail', + 'ColorNoiseReductionIntensity' => 'Colour Noise Reduction Intensity', + 'ColorNoiseReductionSharpness' => 'Colour Noise Reduction Sharpness', + 'ColorNoiseReductionSmoothness' => 'Colour Noise Reduction Smoothness', + 'ColorObjBackType' => 'Colour Obj Back Type', + 'ColorObjName' => 'Colour Obj Name', + 'ColorObjType' => 'Colour Obj Type', + 'ColorObjVersion' => 'Colour Obj Version', + 'ColorPalette' => 'Colour Palette', + 'ColorPlanes' => 'Colour Planes', + 'ColorPrimaries' => 'Colour Primaries', + 'ColorProfile' => 'Colour Profile', + 'ColorProfileSettings' => 'Colour Profile Settings', + 'ColorRangeLevels' => 'Colour Range Levels', + 'ColorRepresentation' => 'Colour Representation', + 'ColorReproduction' => 'Colour Reproduction', + 'ColorResolutionDepth' => 'Colour Resolution Depth', + 'ColorResponseUnit' => 'Colour Response Unit', + 'ColorSamplersResource' => 'Colour Samplers Resource', + 'ColorSamplersResource2' => 'Colour Samplers Resource 2', + 'ColorSaturationAdj' => 'Colour Saturation Adj', + 'ColorSequence' => 'Colour Sequence', + 'ColorSiting' => 'Colour Siting', + 'ColorSpace' => { + Description => 'Colour Space', + PrintConv => { + 'Embedded Color Profile' => 'Embedded Colour Profile', + 'Linked Color Profile' => 'Linked Colour Profile', + 'No color space specified' => 'No colour space specified', + 'Windows Color Space' => 'Windows Colour Space', + }, + }, + 'ColorSpaceData' => 'Colour Space Data', + 'ColorSpecApproximation' => 'Colour Spec Approximation', + 'ColorSpecData' => 'Colour Spec Data', + 'ColorSpecMethod' => { + Description => 'Colour Spec Method', + PrintConv => { + 'Vendor Color' => 'Vendor Colour', + }, + }, + 'ColorSpecPrecedence' => 'Colour Spec Precedence', + 'ColorSpecification' => 'Colour Specification', + 'ColorTable' => 'Colour Table', + 'ColorTempAsShot' => 'Colour Temp As Shot', + 'ColorTempAuto' => 'Colour Temp Auto', + 'ColorTempCloudy' => 'Colour Temp Cloudy', + 'ColorTempCustom' => 'Colour Temp Custom', + 'ColorTempCustom1' => 'Colour Temp Custom 1', + 'ColorTempCustom2' => 'Colour Temp Custom 2', + 'ColorTempDaylight' => 'Colour Temp Daylight', + 'ColorTempFlash' => 'Colour Temp Flash', + 'ColorTempFlashData' => 'Colour Temp Flash Data', + 'ColorTempFluorescent' => 'Colour Temp Fluorescent', + 'ColorTempFluorescentD' => 'Colour Temp Fluorescent D', + 'ColorTempFluorescentN' => 'Colour Temp Fluorescent N', + 'ColorTempFluorescentW' => 'Colour Temp Fluorescent W', + 'ColorTempKelvin' => 'Colour Temp Kelvin', + 'ColorTempMeasured' => 'Colour Temp Measured', + 'ColorTempPC1' => 'Colour Temp PC1', + 'ColorTempPC2' => 'Colour Temp PC2', + 'ColorTempPC3' => 'Colour Temp PC3', + 'ColorTempShade' => 'Colour Temp Shade', + 'ColorTempTungsten' => 'Colour Temp Tungsten', + 'ColorTempUnknown' => 'Colour Temp Unknown', + 'ColorTempUnknown10' => 'Colour Temp Unknown 10', + 'ColorTempUnknown11' => 'Colour Temp Unknown 11', + 'ColorTempUnknown12' => 'Colour Temp Unknown 12', + 'ColorTempUnknown13' => 'Colour Temp Unknown 13', + 'ColorTempUnknown14' => 'Colour Temp Unknown 14', + 'ColorTempUnknown15' => 'Colour Temp Unknown 15', + 'ColorTempUnknown16' => 'Colour Temp Unknown 16', + 'ColorTempUnknown17' => 'Colour Temp Unknown 17', + 'ColorTempUnknown18' => 'Colour Temp Unknown 18', + 'ColorTempUnknown19' => 'Colour Temp Unknown 19', + 'ColorTempUnknown2' => 'Colour Temp Unknown 2', + 'ColorTempUnknown20' => 'Colour Temp Unknown 20', + 'ColorTempUnknown21' => 'Colour Temp Unknown 21', + 'ColorTempUnknown22' => 'Colour Temp Unknown 22', + 'ColorTempUnknown23' => 'Colour Temp Unknown 23', + 'ColorTempUnknown24' => 'Colour Temp Unknown 24', + 'ColorTempUnknown25' => 'Colour Temp Unknown 25', + 'ColorTempUnknown26' => 'Colour Temp Unknown 26', + 'ColorTempUnknown27' => 'Colour Temp Unknown 27', + 'ColorTempUnknown28' => 'Colour Temp Unknown 28', + 'ColorTempUnknown29' => 'Colour Temp Unknown 29', + 'ColorTempUnknown3' => 'Colour Temp Unknown 3', + 'ColorTempUnknown30' => 'Colour Temp Unknown 30', + 'ColorTempUnknown4' => 'Colour Temp Unknown 4', + 'ColorTempUnknown5' => 'Colour Temp Unknown 5', + 'ColorTempUnknown6' => 'Colour Temp Unknown 6', + 'ColorTempUnknown7' => 'Colour Temp Unknown 7', + 'ColorTempUnknown8' => 'Colour Temp Unknown 8', + 'ColorTempUnknown9' => 'Colour Temp Unknown 9', + 'ColorTemperature' => 'Colour Temperature', + 'ColorTemperatureAdj' => 'Colour Temperature Adj', + 'ColorTemperatureAuto' => 'Colour Temperature Auto', + 'ColorTemperatureBG' => 'Colour Temperature BG', + 'ColorTemperatureCustom' => 'Colour Temperature Custom', + 'ColorTemperatureRG' => 'Colour Temperature RG', + 'ColorTemperatureSet' => 'Colour Temperature Set', + 'ColorTemperatureSetting' => { + Description => 'Colour Temperature Setting', + PrintConv => { + 'Color Filter' => 'Colour Filter', + }, + }, + 'ColorTone' => 'Colour Tone', + 'ColorToneAdj' => 'Colour Tone Adj', + 'ColorToneAuto' => 'Colour Tone Auto', + 'ColorToneFaithful' => 'Colour Tone Faithful', + 'ColorToneLandscape' => 'Colour Tone Landscape', + 'ColorToneMonochrome' => 'Colour Tone Monochrome', + 'ColorToneNeutral' => 'Colour Tone Neutral', + 'ColorTonePortrait' => 'Colour Tone Portrait', + 'ColorToneStandard' => 'Colour Tone Standard', + 'ColorToneUserDef1' => 'Colour Tone User Def 1', + 'ColorToneUserDef2' => 'Colour Tone User Def 2', + 'ColorToneUserDef3' => 'Colour Tone User Def 3', + 'ColorTransferFuncs' => 'Colour Transfer Funcs', + 'ColorTransform' => 'Colour Transform', + 'ColorTwistMatrix' => 'Colour Twist Matrix', + 'ColorType' => { + Description => 'Colour Type', + PrintConv => { + 'Color' => 'Colour', + 'Color Alpha' => 'Colour Alpha', + }, + }, + 'Colorant1Coordinates' => 'Colourant 1 Coordinates', + 'Colorant1Name' => 'Colourant 1 Name', + 'Colorant2Coordinates' => 'Colourant 2 Coordinates', + 'Colorant2Name' => 'Colourant 2 Name', + 'Colorant3Coordinates' => 'Colourant 3 Coordinates', + 'Colorant3Name' => 'Colourant 3 Name', + 'ColorantA' => 'Colourant A', + 'ColorantB' => 'Colourant B', + 'ColorantBlack' => 'Colourant Black', + 'ColorantBlue' => 'Colourant Blue', + 'ColorantCount' => 'Colourant Count', + 'ColorantCyan' => 'Colourant Cyan', + 'ColorantGray' => 'Colourant Gray', + 'ColorantGreen' => 'Colourant Green', + 'ColorantL' => 'Colourant L', + 'ColorantMagenta' => 'Colourant Magenta', + 'ColorantMode' => 'Colourant Mode', + 'ColorantOrder' => 'Colourant Order', + 'ColorantRed' => 'Colourant Red', + 'ColorantSwatchName' => 'Colourant Swatch Name', + 'ColorantTableOut' => 'Colourant Table Out', + 'ColorantTint' => 'Colourant Tint', + 'ColorantType' => 'Colourant Type', + 'ColorantYellow' => 'Colourant Yellow', + 'Colorants' => 'Colourants', + 'ColorimetricIntentImageState' => 'Colourimetric Intent Image State', + 'ColorimetricReference' => 'Colourimetric Reference', + 'Colorimetry' => 'Colourimetry', + 'ColorimetryCode' => 'Colourimetry Code', + 'Colors' => 'Colours', + 'Compression' => { + PrintConv => { + 'JBIG Color' => 'JBIG Colour', + }, + }, + 'ContrastMode' => { + PrintConv => { + 'Dynamic (Color Film)' => 'Dynamic (Colour Film)', + 'Dynamic Art (My Color)' => 'Dynamic Art (My Colour)', + 'Elegant (My Color)' => 'Elegant (My Colour)', + 'Nature (Color Film)' => 'Nature (Colour Film)', + 'Nostalgic (Color Film)' => 'Nostalgic (Colour Film)', + 'Retro (My Color)' => 'Retro (My Colour)', + 'Smooth (Color Film) or Pure (My Color)' => 'Smooth (Colour Film) or Pure (My Colour)', + 'Vibrant (Color Film) or Expressive (My Color)' => 'Vibrant (Colour Film) or Expressive (My Colour)', + }, + }, + 'CustomColorTone' => 'Custom Colour Tone', + 'D-LightingHQColorBoost' => 'D-Lighting HQ Colour Boost', + 'D-LightingHSColorBoost' => 'D-Lighting HS Colour Boost', + 'D-RangeOptimizerHighlight' => 'D-Range Optimiser Highlight', + 'D-RangeOptimizerMode' => 'D-Range Optimiser Mode', + 'D-RangeOptimizerShadow' => 'D-Range Optimiser Shadow', + 'D-RangeOptimizerValue' => 'D-Range Optimiser Value', + 'DateTimeDigitized' => 'Date/Time Digitised', + 'DefHilite' => 'Use Default Highlight Colour', + 'DefaultImageColor' => 'Default Image Colour', + 'DefineQuantizationTable' => 'Define Quantisation Table', + 'DeltaType' => { + PrintConv => { + 'Color Addition' => 'Colour Addition', + 'Color Replacement' => 'Colour Replacement', + }, + }, + 'DigitalFilter01' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Replace Color' => 'Replace Colour', + 'Unicolor Bold' => 'Unicolour Bold', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter02' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter03' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter04' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter05' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter06' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter07' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter08' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter09' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter10' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter11' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter12' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter13' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter14' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter15' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter16' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter17' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter18' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter19' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DigitalFilter20' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Extract Color' => 'Extract Colour', + 'Invert Color' => 'Invert Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'DisplayUnits' => { + PrintConv => { + 'meters' => 'Metres', + }, + }, + 'DriveMode' => { + PrintConv => { + 'D-Range Optimizer Bracketing High' => 'D-Range Optimiser Bracketing High', + 'D-Range Optimizer Bracketing Low' => 'D-Range Optimiser Bracketing Low', + }, + }, + 'DriveMode2' => { + PrintConv => { + 'D-Range Optimizer Bracketing High' => 'D-Range Optimiser Bracketing High', + 'D-Range Optimizer Bracketing Low' => 'D-Range Optimiser Bracketing Low', + }, + }, + 'DynamicRangeOptimizer' => 'Dynamic Range Optimiser', + 'DynamicRangeOptimizerBracket' => 'Dynamic Range Optimiser Bracket', + 'DynamicRangeOptimizerLevel' => 'Dynamic Range Optimiser Level', + 'DynamicRangeOptimizerMode' => 'Dynamic Range Optimiser Mode', + 'DynamicRangeOptimizerSetting' => 'Dynamic Range Optimiser Setting', + 'EasyMode' => { + PrintConv => { + 'Color Accent' => 'Colour Accent', + 'Color Swap' => 'Colour Swap', + 'My Colors' => 'My Colours', + }, + }, + 'EmbeddedImageColorSpace' => 'Embedded Image Colour Space', + 'ExposureProgram' => { + PrintConv => { + 'Partial Color Blue' => 'Partial Colour Blue', + 'Partial Color Green' => 'Partial Colour Green', + 'Partial Color Red' => 'Partial Colour Red', + 'Partial Color Yellow' => 'Partial Colour Yellow', + 'Pop Color' => 'Pop Colour', + }, + }, + 'FaithfulRawColorTone' => 'Faithful Raw Colour Tone', + 'FaxProfile' => { + PrintConv => { + 'Lossless color and grayscale, L' => 'Lossless colour and grayscale, L', + 'Lossy color and grayscale, C' => 'Lossy colour and grayscale, C', + }, + }, + 'FgColor' => 'Foreground Colour', + 'FileFormat' => { + PrintConv => { + 'JPEG (lossy/non-quantization toggled)' => 'JPEG (lossy/non-quantisation toggled)', + 'JPEG (non-quantization)' => 'JPEG (non-quantisation)', + }, + }, + 'FilmColorProcess' => 'Film Colour Process', + 'FilmMode' => { + PrintConv => { + 'Dynamic (color)' => 'Dynamic (colour)', + 'Nature (color)' => 'Nature (colour)', + 'Smooth (color)' => 'Smooth (colour)', + 'Standard (color)' => 'Standard (colour)', + }, + }, + 'FlagColor' => 'Flag Colour', + 'FlashColorFilter' => 'Flash Colour Filter', + 'FocalPlaneColorimetryEstimates' => 'Focal Plane Colourimetry Estimates', + 'GammaColorTone' => 'Gamma Colour Tone', + 'GenOpColor' => 'Gen Op Colour', + 'GenreID' => { + PrintConv => { + 'Books|Kids|Basic Concepts|Colors' => 'Books|Kids|Basic Concepts|Colours', + }, + }, + 'GreenPaletteColorTableData' => 'Green Palette Colour Table Data', + 'GreenPaletteColorTableDescriptor' => 'Green Palette Colour Table Descriptor', + 'HasColorMap' => 'Has Colour Map', + 'HighlightColorDistortReduct' => 'Highlight Colour Distort Reduct', + 'HiliteColor' => 'Highlight Colour', + 'ImageAlterationConstraints' => { + PrintConv => { + 'No Colorization' => 'No Colourization', + 'No De-Colorization' => 'No De-Colourization', + }, + }, + 'ImageColor' => 'Image Colour', + 'ImageColorIndicator' => { + Description => 'Image Colour Indicator', + PrintConv => { + 'Specified Image Color' => 'Specified Image Colour', + 'Unspecified Image Color' => 'Unspecified Image Colour', + }, + }, + 'ImageColorValue' => 'Image Colour Value', + 'ImageMedium' => { + PrintConv => { + 'Color hard copy' => 'Colour hard copy', + 'Color negative' => 'Colour negative', + 'Color reversal' => 'Colour reversal', + }, + }, + 'ImageOptimization' => 'Image Optimisation', + 'IndexedColorTableCount' => 'Indexed Colour Table Count', + 'InitializedDataSize' => 'Initialised Data Size', + 'InterchangeColorSpace' => 'Interchange Colour Space', + 'Isotherm1Color' => 'Isotherm 1 Colour', + 'Isotherm2Color' => 'Isotherm 2 Colour', + 'LandscapeRawColorTone' => 'Landscape Raw Colour Tone', + 'LargeBluePaletteColorTableData' => 'Large Blue Palette Colour Table Data', + 'LargeBluePaletteColorTableDescr' => 'Large Blue Palette Colour Table Descr', + 'LargeGreenPaletteColorTableData' => 'Large Green Palette Colour Table Data', + 'LargeGreenPaletteColorTableDescr' => 'Large Green Palette Colour Table Descr', + 'LargePaletteColorLookupTableUID' => 'Large Palette Colour Lookup Table UID', + 'LargeRedPaletteColorTableData' => 'Large Red Palette Colour Table Data', + 'LargeRedPaletteColorTableDescr' => 'Large Red Palette Colour Table Descr', + 'LayerBlendModes' => { + PrintConv => { + 'Color' => 'Colour', + 'Color Burn' => 'Colour Burn', + 'Color Dodge' => 'Colour Dodge', + 'Darker Color' => 'Darker Colour', + 'Lighter Color' => 'Lighter Colour', + }, + }, + 'MDColorTable' => 'MD Colour Table', + 'MDItemColorSpace' => 'MD Item Colour Space', + 'MagicFilter' => { + PrintConv => { + 'Pale & Light Color' => 'Pale & Light Colour', + 'Pale & Light Color II' => 'Pale & Light Colour II', + 'Partial Color' => 'Partial Colour', + 'Partial Color II' => 'Partial Colour II', + 'Partial Color III' => 'Partial Colour III', + 'Watercolor' => 'Watercolour', + 'Watercolor I' => 'Watercolour I', + 'Watercolor II' => 'Watercolour II', + }, + }, + 'MandatoryBackground' => { + PrintConv => { + 'Color Advisory, Image Mandatory' => 'Colour Advisory, Image Mandatory', + 'Color Mandatory, Image Advisory' => 'Colour Mandatory, Image Advisory', + 'Color and Image Advisory' => 'Colour and Image Advisory', + 'Color and Image Mandatory' => 'Colour and Image Mandatory', + }, + }, + 'MattColor' => 'Matt Colour', + 'MediaColor' => 'Media Colour', + 'ModifiedColorTemp' => 'Modified Colour Temp', + 'MonochromeColor' => 'Monochrome Colour', + 'MyColorMode' => { + Description => 'My Colour Mode', + PrintConv => { + 'Color Accent' => 'Colour Accent', + 'Color Swap' => 'Colour Swap', + }, + }, + 'NamedColor' => 'Named Colour', + 'NamedColor2' => 'Named Colour 2', + 'NeutralRawColorTone' => 'Neutral Raw Colour Tone', + 'NewColorType' => 'New Colour Type', + 'NumColors' => 'Num Colours', + 'NumImportantColors' => 'Num Important Colours', + 'OpColor' => 'Op Colour', + 'OverflowColor' => 'Overflow Colour', + 'PF25ColorMatrix' => 'PF25 Colour Matrix', + 'PaletteColorTableUID' => 'Palette Colour Table UID', + 'PaletteColors' => 'Palette Colours', + 'PhotoEffect' => { + PrintConv => { + 'My Color Data' => 'My Colour Data', + }, + }, + 'PhotometricInterpretation' => { + PrintConv => { + 'Color Filter Array' => 'Colour Filter Array', + }, + }, + 'Photoshop2ColorTable' => 'Photoshop 2 Colour Table', + 'PhotoshopFormat' => { + PrintConv => { + 'Optimized' => 'Optimised', + }, + }, + 'PictureEffect' => { + PrintConv => { + 'Partial Color (blue)' => 'Partial Colour (blue)', + 'Partial Color (green)' => 'Partial Colour (green)', + 'Partial Color (red)' => 'Partial Colour (red)', + 'Partial Color (yellow)' => 'Partial Colour (yellow)', + 'Pop Color' => 'Pop Colour', + 'Water Color' => 'Water Colour', + 'Water Color 2' => 'Water Colour 2', + }, + }, + 'PictureEffect2' => { + PrintConv => { + 'Partial Color' => 'Partial Colour', + 'Pop Color' => 'Pop Colour', + 'Water Color' => 'Water Colour', + }, + }, + 'PictureIndexedColors' => 'Picture Indexed Colours', + 'PictureMode' => { + PrintConv => { + 'Color Creator' => 'Colour Creator', + 'Color Profile 1' => 'Colour Profile 1', + 'Color Profile 2' => 'Colour Profile 2', + 'Color Profile 3' => 'Colour Profile 3', + }, + }, + 'PictureType' => { + PrintConv => { + 'Bright(ly) Colored Fish' => 'Bright(ly) Coloured Fish', + }, + }, + 'PictureWizardColor' => 'Picture Wizard Colour', + 'PortraitRawColorTone' => 'Portrait Raw Colour Tone', + 'PresetWhiteBalance' => { + PrintConv => { + 'Color Temperature' => 'Colour Temperature', + }, + }, + 'PreviewColorSpace' => 'Preview Colour Space', + 'ProfileClass' => { + PrintConv => { + 'ColorSpace Conversion Profile' => 'ColourSpace Conversion Profile', + 'NamedColor Profile' => 'Named Colour Profile', + }, + }, + 'PseudoColorType' => 'Pseudo Colour Type', + 'QuantizationDefault' => 'Quantisation Default', + 'QuantizationMethod' => { + Description => 'Quantisation Method', + PrintConv => { + 'Color Space Specific' => 'Colour Space Specific', + }, + }, + 'RGBBkCol' => 'Background Colour', + 'RGBFgCol' => 'Foreground Colour', + 'ROIDisplayColor' => 'ROI Display Colour', + 'RawColorAdj' => 'Raw Colour Adj', + 'RawDevArtFilter' => { + PrintConv => { + 'Pale & Light Color' => 'Pale & Light Colour', + 'Pale & Light Color II' => 'Pale & Light Colour II', + 'Partial Color' => 'Partial Colour', + 'Partial Color II' => 'Partial Colour II', + 'Partial Color III' => 'Partial Colour III', + 'Watercolor' => 'Watercolour', + 'Watercolor I' => 'Watercolour I', + 'Watercolor II' => 'Watercolour II', + }, + }, + 'RawDevColorSpace' => 'Raw Dev Colour Space', + 'RawDevMemoryColorEmphasis' => 'Raw Dev Memory Colour Emphasis', + 'RawDevSettings' => { + PrintConv => { + 'Color Space' => 'Colour Space', + 'WB Color Temp' => 'WB Colour Temp', + }, + }, + 'RawDevWhiteBalance' => { + PrintConv => { + 'Color Temperature' => 'Colour Temperature', + }, + }, + 'RedPaletteColorTableData' => 'Red Palette Colour Table Data', + 'RedPaletteColorTableDescriptor' => 'Red Palette Colour Table Descriptor', + 'ReflectionHardcopyOrigColorimetry' => 'Reflection Hardcopy Orig Colourimetry', + 'ReflectionPrintOutputColorimetry' => 'Reflection Print Output Colourimetry', + 'RenderingIntent' => { + PrintConv => { + 'Absolute Colorimetric (LCS_GM_ABS_COLORIMETRIC)' => 'Absolute Colourimetric (LCS_GM_ABS_COLORIMETRIC)', + 'ICC-Absolute Colorimetric' => 'ICC-Absolute Colourimetric', + 'Media-Relative Colorimetric' => 'Media-Relative Colourimetric', + }, + }, + 'RetouchHistory' => { + PrintConv => { + 'Color Custom' => 'Colour Custom', + 'Color Outline' => 'Colour Outline', + 'Color Sketch' => 'Colour Sketch', + 'Selective Color' => 'Selective Colour', + }, + }, + 'SBAInputImageColorspace' => 'SBA Input Image Colourspace', + 'SRGBRendering' => { + PrintConv => { + 'Absolute Colorimetric' => 'Absolute Colourimetric', + 'Relative Colorimetric' => 'Relative Colourimetric', + }, + }, + 'SceneBalanceAlgorithmCommand' => { + PrintConv => { + 'Neutral SBA Off, Color SBA Off' => 'Neutral SBA Off, Colour SBA Off', + 'Neutral SBA Off, Color SBA On' => 'Neutral SBA Off, Colour SBA On', + 'Neutral SBA On, Color SBA Off' => 'Neutral SBA On, Colour SBA Off', + 'Neutral SBA On, Color SBA On' => 'Neutral SBA On, Colour SBA On', + }, + }, + 'SceneColorimetryEstimates' => 'Scene Colourimetry Estimates', + 'SceneMode' => { + PrintConv => { + 'Color Effects' => 'Colour Effects', + 'My Color' => 'My Colour', + }, + }, + 'ScreenMinimumColorBitDepth' => 'Screen Minimum Colour Bit Depth', + 'SegmentedBlueColorTableData' => 'Segmented Blue Colour Table Data', + 'SegmentedGreenColorTableData' => 'Segmented Green Colour Table Data', + 'SegmentedRedColorTableData' => 'Segmented Red Colour Table Data', + 'SensingMethod' => { + PrintConv => { + 'Color sequential area' => 'Colour sequential area', + 'Color sequential linear' => 'Colour sequential linear', + 'One-chip color area' => 'One-chip colour area', + 'Three-chip color area' => 'Three-chip colour area', + 'Two-chip color area' => 'Two-chip colour area', + }, + }, + 'ShootingMode' => { + PrintConv => { + 'Color Effects' => 'Colour Effects', + 'My Color' => 'My Colour', + }, + }, + 'ShutterPresentationColorCIELabVal' => 'Shutter Presentation Colour CIE Lab Val', + 'SmartAlbumColor' => 'Smart Album Colour', + 'SpecialEffectsOpticalFilter' => { + PrintConv => { + 'Colored' => 'Coloured', + }, + }, + 'StandardRawColorTone' => 'Standard Raw Colour Tone', + 'StdOutputColorMode' => 'Std Output Colour Mode', + 'StreamColor' => 'Stream Colour', + 'SubSecTimeDigitized' => 'Sub Sec Time Digitised', + 'SubfileType' => { + PrintConv => { + 'Color IW44' => 'Colour IW44', + }, + }, + 'SubimageColor' => 'Subimage Colour', + 'SwatchColorantA' => 'Swatch Colourant A', + 'SwatchColorantB' => 'Swatch Colourant B', + 'SwatchColorantBlack' => 'Swatch Colourant Black', + 'SwatchColorantBlue' => 'Swatch Colourant Blue', + 'SwatchColorantCyan' => 'Swatch Colourant Cyan', + 'SwatchColorantGray' => 'Swatch Colourant Gray', + 'SwatchColorantGreen' => 'Swatch Colourant Green', + 'SwatchColorantL' => 'Swatch Colourant L', + 'SwatchColorantMagenta' => 'Swatch Colourant Magenta', + 'SwatchColorantMode' => 'Swatch Colourant Mode', + 'SwatchColorantRed' => 'Swatch Colourant Red', + 'SwatchColorantSwatchName' => 'Swatch Colourant Swatch Name', + 'SwatchColorantTint' => 'Swatch Colourant Tint', + 'SwatchColorantType' => 'Swatch Colourant Type', + 'SwatchColorantYellow' => 'Swatch Colourant Yellow', + 'SwatchGroupsColorants' => 'Swatch Groups Colourants', + 'TestTarget' => { + PrintConv => { + 'Color Chart' => 'Colour Chart', + }, + }, + 'TextColor' => 'Text Colour', + 'ToneCurveColorSpace' => 'Tone Curve Colour Space', + 'ToningEffect' => { + PrintConv => { + 'Color' => 'Colour', + }, + }, + 'TypeOfOriginal' => { + PrintConv => { + 'Color Document' => 'Colour Document', + 'Color Print' => 'Colour Print', + }, + }, + 'UCRBG' => 'Under Colour Removal & Black Gen.', + 'USPTOOriginalContentType' => { + PrintConv => { + 'Color' => 'Colour', + }, + }, + 'UltrasoundColorDataPresent' => 'Ultrasound Colour Data Present', + 'UnderflowColor' => 'Underflow Colour', + 'UninitializedDataSize' => 'Uninitialised Data Size', + 'Unsharp1Color' => 'Unsharp 1 Colour', + 'Unsharp2Color' => 'Unsharp 2 Colour', + 'Unsharp3Color' => 'Unsharp 3 Colour', + 'Unsharp4Color' => 'Unsharp 4 Colour', + 'VideoAlphaPremultipleColor' => 'Video Alpha Premultiple Colour', + 'VideoAlphaPremultipleColorA' => 'Video Alpha Premultiple Colour A', + 'VideoAlphaPremultipleColorB' => 'Video Alpha Premultiple Colour B', + 'VideoAlphaPremultipleColorBlack' => 'Video Alpha Premultiple Colour Black', + 'VideoAlphaPremultipleColorBlue' => 'Video Alpha Premultiple Colour Blue', + 'VideoAlphaPremultipleColorCyan' => 'Video Alpha Premultiple Colour Cyan', + 'VideoAlphaPremultipleColorGray' => 'Video Alpha Premultiple Colour Gray', + 'VideoAlphaPremultipleColorGreen' => 'Video Alpha Premultiple Colour Green', + 'VideoAlphaPremultipleColorL' => 'Video Alpha Premultiple Colour L', + 'VideoAlphaPremultipleColorMagenta' => 'Video Alpha Premultiple Colour Magenta', + 'VideoAlphaPremultipleColorMode' => 'Video Alpha Premultiple Colour Mode', + 'VideoAlphaPremultipleColorRed' => 'Video Alpha Premultiple Colour Red', + 'VideoAlphaPremultipleColorSwatchName' => 'Video Alpha Premultiple Colour Swatch Name', + 'VideoAlphaPremultipleColorTint' => 'Video Alpha Premultiple Colour Tint', + 'VideoAlphaPremultipleColorType' => 'Video Alpha Premultiple Colour Type', + 'VideoAlphaPremultipleColorYellow' => 'Video Alpha Premultiple Colour Yellow', + 'VideoColorKind' => 'Video Colour Kind', + 'VideoColorSpace' => 'Video Colour Space', + 'VisualColor' => { + Description => 'Visual Colour', + PrintConv => { + 'Color' => 'Colour', + }, + }, + 'WBAdjColorTemp' => 'WB Adj Colour Temp', + 'WBAdjLighting' => { + PrintConv => { + 'High Color Rendering Fluorescent (3700K)' => 'High Colour Rendering Fluorescent (1)', + 'High Color Rendering Fluorescent (5000K)' => 'High Colour Rendering Fluorescent (3)', + 'High Color Rendering Fluorescent (cool white)' => 'High Colour Rendering Fluorescent (2)', + 'High Color Rendering Fluorescent (daylight)' => 'High Colour Rendering Fluorescent (4)', + 'High Color Rendering Fluorescent (warm white)' => 'High Colour Rendering Fluorescent (0)', + 'Sodium Vapor Lamps' => 'Sodium Vapour Lamps', + 'Standard Fluorescent (high temperature mercury vapor)' => 'Standard Fluorescent (high temperature mercury vapour)', + }, + }, + 'WatercolorFilter' => 'Watercolour Filter', + 'WhiteBalance' => { + PrintConv => { + 'Color Filter' => 'Colour Filter', + 'Color Temperature' => 'Colour Temperature', + 'Color Temperature Enhancement' => 'Colour Temperature Enhancement', + 'Color Temperature/Color Filter' => 'Colour Temperature/Colour Filter', + }, + }, + 'WhiteBalance2' => { + PrintConv => { + 'Auto (Keep Warm Color Off)' => 'Auto (Keep Warm Colour Off)', + }, + }, + 'WhiteBalanceSet' => { + PrintConv => { + 'Set Color Temperature 1' => 'Set Colour Temperature 1', + 'Set Color Temperature 2' => 'Set Colour Temperature 2', + 'Set Color Temperature 3' => 'Set Colour Temperature 3', + }, + }, + 'WhiteBalanceSetting' => { + PrintConv => { + 'Color Temperature/Color Filter' => 'Colour Temperature/Colour Filter', + }, + }, + 'WorkColorSpace' => { + Description => 'Work Colour Space', + PrintConv => { + 'ColorMatch RGB' => 'ColourMatch RGB', + }, + }, + 'XMethod' => { + PrintConv => { + 'Color Closest Pixel and Alpha Linear Interpolation' => 'Colour Closest Pixel and Alpha Linear Interpolation', + 'Color Linear Interpolation and Alpha Closest Pixel' => 'Colour Linear Interpolation and Alpha Closest Pixel', + }, + }, + 'YMethod' => { + PrintConv => { + 'Color Closest Pixel and Alpha Linear Interpolation' => 'Colour Closest Pixel and Alpha Linear Interpolation', + 'Color Linear Interpolation and Alpha Closest Pixel' => 'Colour Linear Interpolation and Alpha Closest Pixel', + }, + }, +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::en_gb.pm - ExifTool British English language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/es.pm b/ExifTool/lib/Image/ExifTool/Lang/es.pm new file mode 100644 index 0000000..1f708b1 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/es.pm @@ -0,0 +1,4023 @@ +#------------------------------------------------------------------------------ +# File: es.pm +# +# Description: ExifTool Spanish language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::es; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.16'; + +%Image::ExifTool::Lang::es::Translate = ( + 'AEAperture' => 'Aperture AE', + 'AELock' => 'Bloqueo AE', + 'AELockButton' => { + Description => 'Botón Bloqueo AE', + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'AELockButtonPlusDials' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'AEMaxAperture2' => 'Apertura máxima AE 2', + 'AEMinAperture' => 'Apertura mínima AE', + 'AEProgramMode' => { + PrintConv => { + 'Landscape' => 'Paisaje', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + }, + }, + 'AESetting' => { + PrintConv => { + 'AE Lock' => 'Bloqueo AE', + 'Exposure Compensation' => 'Compensación Exposición', + }, + }, + 'AFAperture' => 'Apertura AF', + 'AFAreaHeight' => 'AF Alto Ãrea', + 'AFAreaHeights' => 'AF Alto Ãrea', + 'AFAreaIllumination' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'AFAreaMode' => { + Description => 'AF Modo Ãrea', + PrintConv => { + 'Face Detect AF' => 'Detección Caras AF', + 'Multi-point AF or AI AF' => 'Multipunto AF o AI AF', + 'Off (Manual Focus)' => 'Desactivado (Enfoque Manual)', + 'Single-point AF' => 'Punto único AF)', + 'Zone AF' => 'Zona AF', + }, + }, + 'AFAreaWidth' => 'AF Ancho Ãrea', + 'AFAreaWidths' => 'AF Ancho Ãrea', + 'AFAssist' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'AFImageHeight' => 'AF Alto Imágen', + 'AFImageWidth' => 'AF Ancho Imágen', + 'AFMode' => 'Modo AF', + 'AFPoint' => { + Description => 'Punto AF', + PrintConv => { + 'Center' => 'Centro', + 'Face Detect' => 'Detección Caras', + 'Left' => 'Izquierda', + 'None' => 'Ninguno', + 'None (MF)' => 'Ninguno (MF)', + 'Right' => 'Derecha', + }, + }, + 'AFPointActivationArea' => { + Description => 'Area de Activación Punto AF', + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'AFPointAreaExpansion' => { + Description => 'Area Expansion Punto AF', + PrintConv => { + 'Disable' => 'Desactivado', + }, + }, + 'AFPointAutoSelection' => 'Autoselección Punto AF', + 'AFPointBrightness' => { + Description => 'Brillo Punto AF', + PrintConv => { + 'Brighter' => 'Brillante', + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'AFPointDisplayDuringFocus' => { + Description => 'Mostrar Punto AF durante el enfoque', + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'AFPointIllumination' => { + Description => 'Iluminación de Punto AF', + PrintConv => { + 'Brighter' => 'Brillante', + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'AFPointMode' => 'Modo Punto AF', + 'AFPointPosition' => 'Posición Punto AF', + 'AFPointRegistration' => 'Registro de Puntos AF', + 'AFPointSelected' => { + Description => 'Punto AF Seleccionado', + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'AFPointSelected2' => 'Punto AF Seleccionado 2 ', + 'AFPointSelection' => 'Selección de Punto AF', + 'AFPointSelectionMethod' => 'Método Selección Punto AF', + 'AFPoints' => 'Punto AF', + 'AFPointsInFocus' => { + Description => 'Puntos AF en foco', + PrintConv => { + 'All' => 'Todo', + 'None' => 'Ninguno', + }, + }, + 'AFPointsInFocus1D' => 'Puntos AF en foco', + 'AFPointsInFocus5D' => { + Description => 'Puntos AF en foco 5D', + PrintConv => { + 'Bottom' => 'Abajo', + 'Center' => 'Centro', + 'Left' => 'Izquierda', + 'Lower-left' => 'Inferior izquierda', + 'Lower-right' => 'Inferior derecha', + 'Right' => 'Derecha', + 'Top' => 'Arriba', + 'Upper-left' => 'Superior izquierda', + 'Upper-right' => 'Superior derecha', + }, + }, + 'AFPointsSelected' => 'Puntos AF seleccionados', + 'AFPointsUnknown1' => { + PrintConv => { + 'All' => 'Todo', + }, + }, + 'AFPointsUsed' => 'Puntos AF utilizados', + 'AIServoTrackingSensitivity' => { + PrintConv => { + 'Fast' => 'Rápido', + 'Standard' => 'Estándar', + }, + }, + 'APEVersion' => 'Versión APE', + 'ARMIdentifier' => 'Identificador ARM', + 'ARMVersion' => 'Versión ARM', + 'AToB0' => 'A a B0', + 'AToB1' => 'A a B1', + 'AToB2' => 'A a B2', + 'ActionAdvised' => { + Description => 'Acción Aconsejada', + PrintConv => { + 'Object Append' => 'Añadir Objeto', + 'Object Kill' => 'Destruir Objecto', + 'Object Reference' => 'Referencia Objecto', + 'Object Replace' => 'Reemplazar Objecto', + 'Ojbect Append' => 'Añadir Objeto', + }, + }, + 'ActiveArea' => 'Ãrea Activa', + 'ActiveD-Lighting' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ActiveD-LightingMode' => { + PrintConv => { + 'High' => 'Alto', + 'Off' => 'Desactivado', + }, + }, + 'AddAspectRatioInfo' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'AddOriginalDecisionData' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'AdjustmentMode' => 'Modo Ajuste', + 'AdvancedRaw' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'AdvancedSceneMode' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + 'Soft' => 'Suave', + }, + }, + 'Album' => 'Ãlbum', + 'AlphaByteCount' => 'Número Byte Alfa', + 'AlphaDataDiscard' => { + Description => 'Datos Alfa Descartados', + PrintConv => { + 'Flexbits Discarded' => 'FlexBits Descartado', + 'Full Resolution' => 'Resolución Total', + 'HighPass Frequency Data Discarded' => 'Datos Frecuencia High-Pass Descartados', + 'Highpass and LowPass Frequency Data Discarded' => 'Dato Frecuencia High-Pass y Low-Pass Descartados', + }, + }, + 'AlphaOffset' => 'Offset Alfa', + 'AmbienceSelection' => { + PrintConv => { + 'Brighter' => 'Brillante', + 'Cool' => 'Frío', + 'Darker' => 'Oscuro', + 'Intense' => 'Intenso', + 'Monochrome' => 'Monocromo', + 'Soft' => 'Suave', + 'Standard' => 'Estándar', + 'Vivid' => 'Vívido', + 'Warm' => 'Cálido', + }, + }, + 'AnalogBalance' => 'Balance Analógico', + 'Annotation' => 'Anotación', + 'Annotations' => 'Anotaciones', + 'Anti-Blur' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'AntiAliasStrength' => 'Potencia Relativa del Filtro Antialiasing', + 'Aperture' => 'Apertura', + 'ApertureRange' => 'Rango Apertura', + 'ApertureSetting' => 'Ajustes Apertura', + 'ApertureValue' => 'Apertura', + 'ApplicationRecordVersion' => 'Versión Registro Aplicación', + 'ArtMode' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + 'Panorama' => 'Panoramica', + }, + }, + 'Artist' => 'Autor', + 'AsShotICCProfile' => 'Perfil ICC Captura', + 'AsShotNeutral' => 'Captura Neutral', + 'AsShotPreProfileMatrix' => 'Matriz Pre Perfil Captura', + 'AsShotProfileName' => 'Nombre Perfil Captura', + 'AsShotWhiteXY' => 'Captura Blanco X-Y', + 'Audio' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'AudioChannelType' => { + PrintConv => { + 'Other' => 'Otro', + }, + }, + 'AudioChannels' => 'Canales Audio', + 'AudioCodecID' => { + PrintConv => { + 'Unknown -' => 'Desconocido -', + }, + }, + 'AudioDuration' => 'Duración Audio', + 'AudioOutcue' => 'Cola Audio', + 'AudioSampleType' => { + PrintConv => { + 'Other' => 'Otro', + }, + }, + 'AudioSamplingRate' => 'Ratio Muestreo Audio', + 'AudioSamplingResolution' => 'Resolución Muestreo Audio', + 'AudioType' => { + Description => 'Tipo Audio', + PrintConv => { + 'Mono Actuality' => 'Actualidad (audio mono (1 canal))', + 'Mono Music' => 'Música transmitida por si misma (audio mono (1 canal))', + 'Mono Question and Answer Session' => 'Sesión pregunta y respuesta (audio mono (1 canal))', + 'Mono Raw Sound' => 'Sonido bruto (audio mono (1 canal))', + 'Mono Response to a Question' => 'Respuesta a una pregunta (audio mono (1 canal))', + 'Mono Scener' => 'Escena (audio mono (1 canal))', + 'Mono Voicer' => 'Voz (audio mono (1 canal))', + 'Mono Wrap' => 'Envolvente (audio mono (1 canal))', + 'Stereo Actuality' => 'Actualidad (audio estéreo (2 canales))', + 'Stereo Music' => 'Música transmitida por si misma (audio estéreo (2 canales))', + 'Stereo Question and Answer Session' => 'Sesión pregunta y respuesta (audio estéreo (2 canales))', + 'Stereo Raw Sound' => 'Sonido bruto (audio estéreo (2 canales))', + 'Stereo Response to a Question' => 'Respuesta a una pregunta (audio estéreo (2 canales))', + 'Stereo Scener' => 'Escena (audio estéreo (2 canales))', + 'Stereo Voicer' => 'Voz (audio estéreo (2 canales))', + 'Stereo Wrap' => 'Envolvente (audio estéreo (2 canales))', + 'Text Only' => 'Solo texto (sin dato de objeto)', + }, + }, + 'Author' => 'Autor', + 'AuthorsPosition' => 'Posición del Autor', + 'AutoAperture' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'AutoBracket' => 'Auto-horquillado', + 'AutoExposureBracketing' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'AutoFP' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'AutoLightingOptimizer' => { + PrintConv => { + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + 'Standard' => 'Estándar', + 'Strong' => 'Fuerte', + }, + }, + 'AutoLightingOptimizerOn' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'AutoRedEye' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'AutoRotate' => { + PrintConv => { + 'None' => 'Ninguno', + 'Rotate 180' => 'Girado 180°', + 'Rotate 270 CW' => 'Girado 270° sentido reloj', + 'Rotate 90 CW' => 'Girado 90° sentido reloj', + }, + }, + 'AverageLevel' => 'Nivel Medio', + 'BToA0' => 'B a A0', + 'BToA1' => 'B a A1', + 'BToA2' => 'B a A2', + 'BackgroundColorIndicator' => 'Indicador Color Fondo', + 'BackgroundColorValue' => 'Valor Color Fondo', + 'BackgroundTiling' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'BadFaxLines' => 'Líneas Fax Malas', + 'BannerImageType' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'BaselineExposure' => 'Exposición Base', + 'BaselineNoise' => 'Ruido Base', + 'BaselineSharpness' => 'Nitidez Base', + 'BatteryLevel' => 'Nivel Batería', + 'BatteryState' => { + PrintConv => { + 'Low' => 'Bajo', + }, + }, + 'BayerGreenSplit' => 'Mosaico Bayer Verde', + 'Beep' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'BeepPitch' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'BestQualityScale' => 'Escala Mayor Calidad', + 'BestShotMode' => { + PrintConv => { + 'Beach' => 'Playa', + 'Fireworks' => 'Fuegos Artificiales', + 'Food' => 'Comida', + 'Monochrome' => 'Monocromo', + 'Portrait' => 'Retrato', + 'Snow' => 'Nieve', + 'Underwater' => 'Subacuatica', + }, + }, + 'BitsPerComponent' => 'Bits Por Componente', + 'BitsPerExtendedRunLength' => 'Bits Por "Run Length" Extendido', + 'BitsPerRunLength' => 'Bits Por "Run Length"', + 'BitsPerSample' => 'Número de Bits Por Muestra', + 'BlackLevel' => 'Nivel Negro', + 'BlackLevelDeltaH' => 'Nivel Negro Delta H', + 'BlackLevelDeltaV' => 'Nivel Negro Delta V', + 'BlackLevelRepeatDim' => 'Dimensión Repetición Nivel Negro', + 'BleachBypassToning' => { + PrintConv => { + 'Green' => 'Verde', + 'Orange' => 'Naranja', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'BlocksPerFrame' => 'Bloques por Imagen', + 'BlueBalance' => 'Balance de azules', + 'BlueMatrixColumn' => 'Columna Matriz Azul', + 'BlueTRC' => 'Curva Reproducción Tono Azul', + 'BlurControl' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'BlurWarning' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'BodyFirmwareVersion' => 'Versión Firmware del cuerpo de la cámara', + 'BracketMode' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'Brightness' => 'Brillo', + 'BrightnessValue' => 'Luminosidad', + 'By-line' => 'Creador', + 'By-lineTitle' => 'Puesto del Creador', + 'CFALayout' => { + Description => 'Distribución CFA', + PrintConv => { + 'Even columns offset down 1/2 row' => 'Distribución escalonada A: columnas pares son movidas hacia abajo 1/2 fila', + 'Even columns offset up 1/2 row' => 'Distribución escalonada B: columnas pares son movidas hacia arriba 1/2 fila', + 'Even rows offset left 1/2 column' => 'Distribución escalonada D: filas pares son movidas a la izquierda 1/2 columna', + 'Even rows offset right 1/2 column' => 'Distribución escalonada C: filas pares son movidas a la derecha 1/2 columna', + 'Rectangular' => 'Distribución Rectangular (o cuadrada)', + }, + }, + 'CFAPattern' => 'Patrón CFA', + 'CFAPattern2' => 'Patrón CFA 2', + 'CFAPlaneColor' => 'Color Plano CFA', + 'CFARepeatPatternDim' => 'Dimensión Patrón Repetición CFA', + 'CMMFlags' => 'Banderas CMM', + 'CMYKEquivalent' => 'CMYK Equivalente', + 'CPUType' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'CalibrationDateTime' => 'Fecha y Hora Calibración', + 'CalibrationIlluminant1' => { + Description => 'Calibración Iluminación 1', + PrintConv => { + 'Cloudy' => 'Tiempo Nublado', + 'Cool White Fluorescent' => 'Fluorescente blanco cálido (W 3800 - 4500K)', + 'Day White Fluorescent' => 'Fluorescente blanco día (N 4600 - 5500K)', + 'Daylight' => 'Luz del día', + 'Daylight Fluorescent' => 'Fluorescente luz de día (D 5700 - 7100K)', + 'Fine Weather' => 'Buen tiempo', + 'Fluorescent' => 'Fluorescente', + 'ISO Studio Tungsten' => 'Tungsteno estudio ISO', + 'Other' => 'Otras Fuentes Luz', + 'Shade' => 'Sombrío', + 'Standard Light A' => 'Luz Estándar A', + 'Standard Light B' => 'Luz Estándar B', + 'Standard Light C' => 'Luz Estándar C', + 'Tungsten (Incandescent)' => 'Tungsteno (luz incandescente)', + 'Unknown' => 'Desconocido', + 'Warm White Fluorescent' => 'Fluorescente blanco cálido (L 2600 - 3250K)', + 'White Fluorescent' => 'Fluorescente blanco (WW 3250 - 3800K)', + }, + }, + 'CalibrationIlluminant2' => { + Description => 'Calibración Iluminación 2', + PrintConv => { + 'Cloudy' => 'Tiempo Nublado', + 'Cool White Fluorescent' => 'Fluorescente blanco cálido (W 3800 - 4500K)', + 'Day White Fluorescent' => 'Fluorescente blanco día (N 4600 - 5500K)', + 'Daylight' => 'Luz del día', + 'Daylight Fluorescent' => 'Fluorescente luz de día (D 5700 - 7100K)', + 'Fine Weather' => 'Buen tiempo', + 'Fluorescent' => 'Fluorescente', + 'ISO Studio Tungsten' => 'Tungsteno estudio ISO', + 'Other' => 'Otras Fuentes Luz', + 'Shade' => 'Sombrío', + 'Standard Light A' => 'Luz Estándar A', + 'Standard Light B' => 'Luz Estándar B', + 'Standard Light C' => 'Luz Estándar C', + 'Tungsten (Incandescent)' => 'Tungsteno (luz incandescente)', + 'Unknown' => 'Desconocido', + 'Warm White Fluorescent' => 'Fluorescente blanco cálido (L 2600 - 3250K)', + 'White Fluorescent' => 'Fluorescente blanco (WW 3250 - 3800K)', + }, + }, + 'CameraCalibration1' => 'Calibración Cámara 1', + 'CameraCalibration2' => 'Calibración Cámara 2', + 'CameraCalibrationSig' => 'Firma Calibración Cámara', + 'CameraISO' => 'Camara-ISO', + 'CameraOrientation' => { + Description => 'Orientación Cámara', + PrintConv => { + 'Rotate 270 CW' => 'Girado 270° sentido reloj', + 'Rotate 90 CW' => 'Girado 90° sentido reloj', + }, + }, + 'CameraSerialNumber' => 'Número Serie Cámara', + 'CameraTemperature' => 'Temperatura Cámara', + 'CameraType' => 'Tipo Cámara', + 'CameraType2' => 'Tipo Cámara', + 'CanonFileLength' => 'Tamaño Archivo', + 'CanonFlashMode' => { + PrintConv => { + 'Auto' => 'Automático', + 'External flash' => 'Flash Externo', + 'Off' => 'Desactivado', + 'On' => 'Activado', + 'Red-eye reduction' => 'Réducción ojos rojos', + 'Red-eye reduction (Auto)' => 'Réducción ojos rojos (Automático)', + 'Red-eye reduction (On)' => 'Réducción ojos rojos (Activado)', + }, + }, + 'CanonImageSize' => { + PrintConv => { + '1280x720 Movie' => 'Película 1280x720', + '1920x1080 Movie' => 'Película 1920x1080', + '640x480 Movie' => 'Película 640x480', + 'Large' => 'Ancho', + 'Medium' => 'Medio', + 'Medium 1' => 'Medio 1', + 'Medium 2' => 'Medio 2', + 'Medium 3' => 'Medio 3', + 'Small' => 'Pequeño', + 'Small 1' => 'Pequeño 1', + 'Small 2' => 'Pequeño 2', + 'Small 3' => 'Pequeño 3', + 'Small Movie' => 'Película Pequeña', + }, + }, + 'Caption-Abstract' => 'Título/Descripción', + 'CaptionWriter' => 'Autor del Pie de Foto', + 'Categories' => 'Categorías', + 'Category' => 'Categoría', + 'CellLength' => 'Alto Celda', + 'CellWidth' => 'Ancho Celda', + 'Certificate' => 'Certificado', + 'Channels' => 'Canales', + 'CharTarget' => 'Objetivo Caracter', + 'CharacterSet' => 'Conjunto de Caracteres', + 'ChromaBlurRadius' => 'Radio Mezcla Croma', + 'ChromaticAdaptation' => 'Adaptación Cromática', + 'Chromaticity' => 'Cromaticidad', + 'ChrominanceNR_TIFF_JPEG' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + }, + }, + 'ChrominanceNoiseReduction' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + }, + }, + 'City' => 'Ciudad', + 'ClassifyState' => 'Clasificar Estado', + 'CleanFaxData' => 'Datos Fax Claro', + 'ClipPath' => 'Camino Fragmento', + 'CodedCharacterSet' => 'Juego Caracteres Codificado', + 'ColorAberrationControl' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ColorAdjustment' => 'Ajuste Color', + 'ColorAdjustmentMode' => { + Description => 'Modo Ajuste Color', + PrintConv => { + 'Off' => 'Apagado', + 'On' => 'Encendido', + }, + }, + 'ColorBalanceAdj' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ColorBooster' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ColorCalibrationMatrix' => 'Tabla Matriz Calibración Color', + 'ColorCharacterization' => 'Caracterización Color', + 'ColorComponents' => 'Componentes de Color', + 'ColorEffect' => { + PrintConv => { + 'Cool' => 'Frío', + 'Warm' => 'Cálido', + }, + }, + 'ColorFilter' => { + Description => 'Filtro de Color', + PrintConv => { + 'Green' => 'Verde', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'ColorMap' => 'Mapa Color', + 'ColorMatrix' => 'Matriz de Color', + 'ColorMatrix1' => 'Matriz Color 1', + 'ColorMatrix2' => 'Matriz Color 2', + 'ColorMatrixA' => 'Matriz de Color A', + 'ColorMatrixAdobeRGB' => 'Matriz de Color Adobe RGB', + 'ColorMatrixB' => 'Matriz de Color B', + 'ColorMatrixNumber' => 'Número de Matriz de Color', + 'ColorMatrixSRGB' => 'Matriz de Color SRGB', + 'ColorMode' => { + Description => 'Modo de Color', + PrintConv => { + 'Autumn Leaves' => 'Hojas de otoño', + 'B&W' => 'ByN', + 'Clear' => 'Claro', + 'Deep' => 'Profundo', + 'Evening' => 'Tarde', + 'Landscape' => 'Paisaje', + 'Light' => 'Luz', + 'Neutral' => 'Neutro', + 'Night View' => 'Vista nocturna', + 'Night View/Portrait' => 'Retrato noct.', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + 'Sunset' => 'Puesta sol', + 'Vivid' => 'Vívido', + }, + }, + 'ColorMoireReduction' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ColorMoireReductionMode' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + }, + }, + 'ColorPalette' => 'Paleta Color', + 'ColorRepresentation' => { + Description => 'Representación Color', + PrintConv => { + '3 Components, Frame Sequential in Multiple Objects' => 'Tres componentes, Marco secuencial en múltiples objectos', + '3 Components, Frame Sequential in One Object' => 'Tres componentes, Marco secuencial en un único objeto', + '3 Components, Line Sequential' => 'Tres componentes, Línea secuencial', + '3 Components, Pixel Sequential' => 'Tres componentes, Pixel secuencial', + '3 Components, Single Frame' => 'Tres componentes, Marco simple', + '3 Components, Special Interleaving' => 'Tres componentes, Entrelazado especial', + '4 Components, Frame Sequential in Multiple Objects' => 'Cuatro componentes, Marco secuencial en múltiples objectos', + '4 Components, Frame Sequential in One Object' => 'Cuatro componentes, Marco secuencial en un único objeto', + '4 Components, Line Sequential' => 'Cuatro componentes, Línea secuencial', + '4 Components, Pixel Sequential' => 'Cuatro componentes, Pixel secuencial', + '4 Components, Single Frame' => 'Cuatro componentes, Marco simple', + '4 Components, Special Interleaving' => 'Cuatro componentes, Entrelazado especial', + 'Monochrome, Single Frame' => 'Monocromo, Marco simple', + 'No Image, Single Frame' => 'Sin imagen, Marco simple', + }, + }, + 'ColorResponseUnit' => 'Unidad Respuesta Color', + 'ColorSequence' => 'Representación de Color', + 'ColorSpace' => { + Description => 'Espacio Color', + PrintConv => { + 'ICC Profile' => 'Perfil ICC', + 'Monochrome' => 'Monocromo', + 'Uncalibrated' => 'Sin calibrar', + 'Wide Gamut RGB' => 'Gamut RVB Grande', + }, + }, + 'ColorSpaceData' => 'Espacio Color Datos', + 'ColorTable' => 'Tabla Color', + 'ColorTempAuto' => 'Temperatura Color Automática', + 'ColorTempCloudy' => 'Temperatura Color Nublado', + 'ColorTempCustom' => 'Temperatura Color Personalizada', + 'ColorTempCustom1' => 'Temperatura Color Personalizada 1', + 'ColorTempCustom2' => 'Temperatura Color Personalizada 2', + 'ColorTempDaylight' => 'Temperatura Color Luz de Día', + 'ColorTempFlash' => 'Temperatura Color Flash', + 'ColorTempFluorescent' => 'Temperatura Color Fluorescente', + 'ColorTempKelvin' => 'Temperatura Color Kelvin', + 'ColorTempMeasured' => 'Temperatura Color Medida', + 'ColorTempShade' => 'Temperatura Color Sombrío', + 'ColorTempTungsten' => 'Temperatura Color Tungsteno', + 'ColorTempUnknown' => 'Temperatura de Color Desconocida', + 'ColorTempUnknown10' => 'Temperatura de Color Desconocida 10', + 'ColorTempUnknown11' => 'Temperatura de Color Desconocida 11', + 'ColorTempUnknown12' => 'Temperatura de Color Desconocida 12', + 'ColorTempUnknown13' => 'Temperatura de Color Desconocida 13', + 'ColorTempUnknown14' => 'Temperatura de Color Desconocida 14', + 'ColorTempUnknown15' => 'Temperatura de Color Desconocida 15', + 'ColorTempUnknown16' => 'Temperatura de Color Desconocida 16', + 'ColorTempUnknown17' => 'Temperatura de Color Desconocida 17', + 'ColorTempUnknown18' => 'Temperatura de Color Desconocida 18', + 'ColorTempUnknown19' => 'Temperatura de Color Desconocida 19', + 'ColorTempUnknown2' => 'Temperatura de Color Desconocida 2', + 'ColorTempUnknown20' => 'Temperatura de Color Desconocida 20', + 'ColorTempUnknown3' => 'Temperatura de Color Desconocida 3', + 'ColorTempUnknown4' => 'Temperatura de Color Desconocida 4', + 'ColorTempUnknown5' => 'Temperatura de Color Desconocida 5', + 'ColorTempUnknown6' => 'Temperatura de Color Desconocida 6', + 'ColorTempUnknown7' => 'Temperatura de Color Desconocida 7', + 'ColorTempUnknown8' => 'Temperatura de Color Desconocida 8', + 'ColorTempUnknown9' => 'Temperatura de Color Desconocida 9', + 'ColorTemperature' => 'Temperatura de Color', + 'ColorTone' => { + Description => 'Tono de Color', + PrintConv => { + 'Normal' => 'Estándar', + }, + }, + 'ColorantOrder' => 'Orden Colorante', + 'ColorantTable' => 'Tabla Colorante', + 'ColorimetricReference' => 'Referencia Colorimétrica', + 'CommandDialsChangeMainSub' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'CommandDialsMenuAndPlayback' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'CommandDialsReverseRotation' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'CommanderGroupAMode' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'CommanderGroupBMode' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'CommanderInternalFlash' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'Comment' => 'Comentario', + 'Compatibility' => 'Compatibilidad', + 'Compilation' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'ComponentsConfiguration' => 'Configuración de Componentes', + 'Composer' => 'Compositor', + 'CompressedBitsPerPixel' => 'Modo Compresión Imagen', + 'CompressedSize' => 'Tamaño Comprimido', + 'Compression' => { + Description => 'Compresión', + PrintConv => { + 'JPEG' => 'Compresión JPEG', + 'JPEG (old-style)' => 'JPEG (estilo antiguo)', + 'Kodak DCR Compressed' => 'Compresión Kodak DCR', + 'Kodak KDC Compressed' => 'Compresión Kodak KDC', + 'Next' => 'Codificación NeXT 2-bit', + 'Nikon NEF Compressed' => 'Compresión Nikon NEF', + 'None' => 'Ninguno', + 'Pentax PEF Compressed' => 'Compresión Pentax PEF', + 'SGILog' => 'Codificación Log Luminancia SGI 32-bit', + 'SGILog24' => 'Codificación Log Luminancia SGI 24-bit', + 'Sony ARW Compressed' => 'Compresión Sony ARW', + 'Thunderscan' => 'Codificación ThunderScan 4-bit', + 'Uncompressed' => 'Sin comprimir', + }, + }, + 'CompressionFactor' => 'Factor de compresión', + 'CompressionLevel' => 'Nivel Compresión', + 'CompressionType' => { + Description => 'Tipo Compresión', + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'CompressorName' => 'Nombre Compresor', + 'Conductor' => 'Director', + 'Conductors' => 'Directores', + 'ConnectionSpaceIlluminant' => 'Iluminación Espacio Conexión', + 'ConsecutiveBadFaxLines' => 'Líneas Fax Malas Consecutivas', + 'Contact' => 'Contacto', + 'ContentLocationCode' => 'Código Localización Contenido', + 'ContentLocationName' => 'Nombre Localización Contenido', + 'ContinuousBracketing' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'ContinuousDrive' => { + PrintConv => { + 'Continuous' => 'Continuo', + 'Continuous, High' => 'Continuo, Alto', + 'Continuous, Low' => 'Continuo, Bajo', + 'Continuous, Speed Priority' => 'Continuo, Prioridad Velocidad', + 'Movie' => 'Película', + 'Single' => 'Simple', + }, + }, + 'Contrast' => { + Description => 'Contraste', + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Normal' => 'Estándar', + }, + }, + 'ContrastMode' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'ContrastSetting' => 'Ajustes de Contraste', + 'Copyright' => 'Copyright Perfil', + 'CopyrightNotice' => 'Aviso Copyright', + 'Country' => 'País', + 'Country-PrimaryLocationCode' => 'Código País ISO', + 'Country-PrimaryLocationName' => 'País', + 'CountryCode' => 'Código País', + 'CreateDate' => 'Fecha y Hora de Datos Digital', + 'CreationDate' => 'Fecha Creación', + 'CreativeStyle' => { + PrintConv => { + 'Autumn Leaves' => 'Hojas de otoño', + 'B&W' => 'ByN', + 'Clear' => 'Claro', + 'Deep' => 'Profundo', + 'Landscape' => 'Paisaje', + 'Light' => 'Luz', + 'Neutral' => 'Neutro', + 'Night View/Portrait' => 'Retrato noct.', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + 'Sunset' => 'Puesta sol', + 'Vivid' => 'Vívido', + }, + }, + 'CreativeStyleSetting' => { + PrintConv => { + 'Landscape' => 'Paisaje', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + 'Vivid' => 'Vívido', + }, + }, + 'Creator' => 'Creador', + 'CreatorAddress' => 'Creador - Dirección', + 'CreatorCity' => 'Creador - Ciudad', + 'CreatorContactInfo' => 'Contacto Creador', + 'CreatorCountry' => 'Creador - País', + 'CreatorPostalCode' => 'Creador - Código Postal', + 'CreatorRegion' => 'Creador - Estado/Provincia', + 'CreatorWorkEmail' => 'Creador - Email(s)', + 'CreatorWorkTelephone' => 'Creador - Teléfono(s)', + 'CreatorWorkURL' => 'Creador - Website(s)', + 'Credit' => 'Proveedor', + 'CropActive' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'CropHeight' => 'Recorte Altura', + 'CropLeft' => 'Recorte Izquierda', + 'CropTop' => 'Recorte Arriba', + 'CropWidth' => 'Recorte Anchura', + 'CroppedImageHeight' => 'Alto Imágen Recortada', + 'CroppedImageLeft' => 'Izquierda Imágen Recortada', + 'CroppedImageTop' => 'Superior Imágen Recortada', + 'CroppedImageWidth' => 'Ancho Imágen Recortada', + 'CurrentICCProfile' => 'Perfil ICC Actual', + 'CurrentPreProfileMatrix' => 'Matriz Pre Perfil Actual', + 'Curves' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'CustomRendered' => { + Description => 'Proceso Imagen Personalizado', + PrintConv => { + 'Custom' => 'Proceso personalizado', + 'Normal' => 'Proceso normal', + }, + }, + 'CustomSaturation' => 'Saturación personalizada', + 'D-LightingHQ' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'D-LightingHQSelected' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'D-LightingHS' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'DNGBackwardVersion' => 'Versión Antigua DNG', + 'DNGLensInfo' => 'Distancia Focal Mínima', + 'DNGVersion' => 'Versión DNG', + 'DOF' => 'Profundidad de campo', + 'Data' => 'Datos', + 'DataCompressionMethod' => 'Proveedor/Propietario Algoritmo Compresión Datos', + 'DataImprint' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'DataPackets' => ' Paquetes de Datos', + 'DataType' => 'Tipo Datos', + 'Date' => 'Fecha', + 'DateCreated' => 'Fecha Creación', + 'DateSent' => 'Fecha Envío', + 'DateStampMode' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'DateTimeDigitized' => 'Fecha y Hora Digital', + 'DateTimeOriginal' => 'Fecha y Hora de Datos Original', + 'DaylightSavings' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'DefaultBlackRender' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'DefaultCropOrigin' => 'Origen Corte Defecto', + 'DefaultCropSize' => 'Tamaño Corte Defecto', + 'DefaultScale' => 'Escala por Defecto', + 'DerivedFromMaskMarkers' => { + PrintConv => { + 'All' => 'Todo', + 'None' => 'Ninguno', + }, + }, + 'Description' => 'Descripción', + 'Destination' => 'Destino', + 'DestinationDST' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'DeviceAttributes' => 'Atributos Dispositivo', + 'DeviceManufacturer' => 'Fabricante Dispositivo', + 'DeviceMfgDesc' => 'Descripción Fabricante Dispositivo', + 'DeviceModel' => 'Modelo Dispositivo', + 'DeviceModelDesc' => 'Descripción Modelo Dispositivo', + 'DeviceSettingDescription' => 'Descripción Ajustes Dispositivo', + 'DigitalCreationDate' => 'Fecha Creación Digital', + 'DigitalCreationTime' => 'Hora Creación Digital', + 'DigitalFilter01' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter02' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter03' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter04' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter05' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter06' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter07' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter08' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter09' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter10' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter11' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter12' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter13' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter14' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter15' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter16' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter17' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter18' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter19' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalFilter20' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'DigitalSignature' => 'Firma Digital', + 'DigitalZoom' => { + Description => 'Zoom Digital', + PrintConv => { + 'None' => 'Ninguno', + 'Off' => 'Desactivado', + }, + }, + 'DigitalZoomOn' => { + Description => 'Zoom Digital Encendido', + PrintConv => { + 'Off' => 'Apagado', + 'On' => 'Encendido', + }, + }, + 'DigitalZoomRatio' => 'Ratio Zoom Digital', + 'Directory' => 'Ubicación del Fichero', + 'DistortionControl' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'DistortionCorrection' => { + Description => 'Corrección Distorsión', + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'DistortionCorrection2' => { + Description => 'Corrección Distorsión 2', + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'DistortionCorrectionOn' => 'Corrección Distorsión Activada', + 'DocSecurity' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'DocumentHistory' => 'Historial del Documento', + 'DocumentName' => 'Nombre Documento', + 'DocumentNotes' => 'Notas del Documento', + 'DotRange' => 'Intervalo Puntos', + 'DriveMode' => { + Description => 'Modo Entrada', + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'Duration' => 'Duración', + 'DynamicRange' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'DynamicRangeOptimizer' => { + Description => 'Optim.gama diná', + PrintConv => { + 'Advanced Auto' => 'Avanzado Autom', + 'Advanced Lv1' => 'Avanzado Nvl.1', + 'Advanced Lv2' => 'Avanzado Nvl.2', + 'Advanced Lv3' => 'Avanzado Nvl.3', + 'Advanced Lv4' => 'Avanzado Nvl.4', + 'Advanced Lv5' => 'Avanzado Nvl.5', + 'Off' => 'Desactivado', + 'Standard' => 'Estándar', + }, + }, + 'DynamicRangeOptimizerBracket' => { + PrintConv => { + 'Low' => 'Bajo', + }, + }, + 'DynamicRangeOptimizerMode' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'DynamicRangeOptimizerSetting' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'EasyExposureCompensation' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'EasyMode' => { + PrintConv => { + 'Beach' => 'Playa', + 'Black & White' => 'Blanco y Negro', + 'Digital Macro' => 'Macro digital', + 'Easy' => 'Fácil', + 'Fireworks' => 'Fuegos Artificiales', + 'Fisheye Effect' => 'Efecto Ojo de Pez', + 'Flash Off' => 'Flash Desactivado', + 'Foliage' => 'Follaje', + 'Gray Scale' => 'Escala de Grises', + 'Indoor' => 'Interior', + 'Kids & Pets' => 'Niños y Mascotas', + 'Landscape' => 'Paisaje', + 'Monochrome' => 'Monocromo', + 'Neutral' => 'Neutro', + 'Night' => 'Nocturno', + 'Night Scene' => 'Escena Nocturna', + 'Night Snapshot' => 'Fotografía Nocturna', + 'Nostalgic' => 'Nostalgico', + 'Portrait' => 'Retrato', + 'Smile' => 'Sonrisa', + 'Snow' => 'Nieve', + 'Sports' => 'Deportes', + 'Sunset' => 'Puesta de sol', + 'Surface' => 'Superficie', + 'Underwater' => 'Subacuatica', + 'Vivid' => 'Vívido', + }, + }, + 'EdgeNoiseReduction' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'EditStatus' => 'Estado Edición', + 'EditorialUpdate' => { + Description => 'Actualización Editorial', + PrintConv => { + 'Additional language' => 'Idioma Adicional', + }, + }, + 'EffectiveMaxAperture' => 'Aperture Máxima Efectiva', + 'Emphasis' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'EncodedBy' => 'Codificado por', + 'EncodingProcess' => 'Proceso de codificación', + 'EncodingSettings' => 'Ajustes de Codificación', + 'EncodingTime' => 'Hora de codificación', + 'EndPoints' => 'Puntos Finales', + 'EnhanceDarkTones' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'Enhancement' => { + PrintConv => { + 'Green' => 'Verde', + 'Red' => 'Rojo', + 'Underwater' => 'Subacuatica', + }, + }, + 'EnvelopeNumber' => 'Número Sobre', + 'EnvelopePriority' => { + Description => 'Prioridad Sobre', + PrintConv => { + '0 (reserved)' => '0 (reservada para uso futuro)', + '1 (most urgent)' => '1 (más urgente)', + '5 (normal urgency)' => '5 (urgencia normal)', + '8 (least urgent)' => '8 (menos urgente)', + '9 (user-defined priority)' => '9 (prioridad definida por el usuario)', + }, + }, + 'EnvelopeRecordVersion' => 'Versión Registro Sobre', + 'EquipmentVersion' => 'Versión Equipo', + 'ErrorCorrection' => 'Correción Error', + 'ErrorCorrectionType' => 'Tipo Corrección Error', + 'ExcursionTolerance' => { + Description => 'Tolerancia Excursión', + PrintConv => { + 'Allowed' => 'Puede ocurrir', + 'Not Allowed' => 'No Permitido (defecto)', + }, + }, + 'ExifCameraInfo' => 'Información Cámara Exif', + 'ExifImageHeight' => 'Alto Imagen', + 'ExifImageWidth' => 'Ancho Imagen', + 'ExifOffset' => 'Puntero Exif IFD', + 'ExifToolVersion' => 'Versión ExifTool', + 'ExifVersion' => 'Versión Exif', + 'ExpandFilm' => 'Película Expandida', + 'ExpandFilterLens' => 'Filtro Objetivo Expandida', + 'ExpandFlashLamp' => 'Lampara Flash Expandida', + 'ExpandLens' => 'Objetivo Expandido', + 'ExpandScanner' => 'Escaner Expandido', + 'ExpandSoftware' => 'Software Expandido', + 'ExpirationDate' => 'Fecha Expiración', + 'ExpirationTime' => 'Hora Expiración', + 'ExposureCompensation' => 'Compensación Exposición', + 'ExposureDelayMode' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ExposureIndex' => 'Ãndice Exposición', + 'ExposureMode' => { + Description => 'Modo Exposición', + PrintConv => { + 'Aperture-priority AE' => 'Prioridad Aberture AE', + 'Auto' => 'Exposición automática', + 'Auto bracket' => 'Auto-horquillado', + 'Beach' => 'Playa', + 'Fireworks' => 'Fuegos Artificiales', + 'Food' => 'Comida', + 'Landscape' => 'Paisaje', + 'Manual' => 'Exposición manual', + 'Panorama' => 'Panoramica', + 'Portrait' => 'Retrato', + 'Program AE' => 'Programa AE', + 'Shutter speed priority AE' => 'Prioridad velocidad obturador AE', + 'Snow' => 'Nieve', + 'Underwater' => 'Subacuatica', + }, + }, + 'ExposureProgram' => { + Description => 'Programa Exposición', + PrintConv => { + 'Action (High speed)' => 'Programa acción (orientado a velocidad de obturación rápida)', + 'Aperture-priority AE' => 'Prioridad Apertura', + 'Creative (Slow speed)' => 'Programa creativo (orientado a profundidad de campo)', + 'Landscape' => 'Modo paisaje (para fotos de paisaje con el fondo en enfoque)', + 'Manual' => 'Exposición manual', + 'Not Defined' => 'No definido', + 'Portrait' => 'Modo retrato (para fotos de cerca con el fondo fuera de enfoque)', + 'Program AE' => 'Programa normal', + 'Shutter speed priority AE' => 'Prioridad obturador', + }, + }, + 'ExposureTime' => 'Tiempo de Exposición', + 'ExposureTime2' => 'Tiempo de Exposición 2', + 'Extender' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'ExternalFlash' => { + Description => 'Flash Externo', + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ExternalFlashBounce' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'ExternalFlashMode' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'ExternalFlashZoom' => 'Zoom Flash Externo', + 'ExtraSamples' => 'Muestra Extra', + 'FNumber' => 'Número F', + 'FOV' => 'Angulo de Visión', + 'FaceDetectArea' => 'Area detección caras', + 'FaceDetectFrameSize' => 'Tamaño Area detección caras', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => '0° (arriba/izquierda)', + 'Rotate 180' => 'Girado 180°', + 'Rotate 270 CW' => 'Girado 270° sentido reloj', + 'Rotate 90 CW' => 'Girado 90° sentido reloj', + }, + }, + 'FacesDetected' => 'Caras Detectadas', + 'FastSeek' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'FaxProfile' => { + PrintConv => { + 'Unknown' => 'Desconocido', + }, + }, + 'FaxRecvParams' => 'Parámetros Recepción Fax', + 'FaxRecvTime' => 'Hora Recepción Fax', + 'FaxSubAddress' => 'Subdirección Fax', + 'FileAccessDate' => 'Fecha y Hora de Acceso', + 'FileCreateDate' => 'Fecha y Hora de Creación', + 'FileFormat' => 'Formato Archivo', + 'FileLength' => 'Tamaño Archivo', + 'FileModifyDate' => 'Fecha Actualización', + 'FileName' => 'Nombre Archivo', + 'FileNumberMemory' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'FileNumberSequence' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'FileOwner' => 'Propietario del Archivo', + 'FilePermissions' => 'Permisos', + 'FileSize' => 'Tamaño Archivo', + 'FileSource' => { + Description => 'Fuente Archivo', + PrintConv => { + 'Digital Camera' => 'Cámara Digital', + 'Film Scanner' => 'Escaner Película', + 'Reflection Print Scanner' => 'Escaner de Reflexión', + }, + }, + 'FileType' => 'Tipo Archivo', + 'FileVersion' => 'Versión Formato Archivo', + 'Filename' => 'Nombre archivo', + 'FillOrder' => 'Orden Rellenado', + 'FilterEffect' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + 'Off' => 'Desactivado', + 'Orange' => 'Naranja', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'FilterEffectMonochrome' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + 'Orange' => 'Naranja', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'FilterEffectUnknown' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + 'Orange' => 'Naranja', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'FilterEffectUserDef1' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + 'Orange' => 'Naranja', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'FilterEffectUserDef2' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + 'Orange' => 'Naranja', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'FilterEffectUserDef3' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + 'Orange' => 'Naranja', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'FinderDisplayDuringExposure' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'FirmwareVersion' => 'Versión Firmware', + 'FixtureIdentifier' => 'Identificador Marca', + 'Flags' => { + PrintConv => { + 'Comment' => 'Comentario', + 'FileName' => 'Nombre de Archivo', + 'Text' => 'Texto', + }, + }, + 'Flash' => { + PrintConv => { + 'Auto, Did not fire' => 'Flash no disparado, modo automático', + 'Auto, Did not fire, Red-eye reduction' => 'Auto, Flash no disparado, modo reducción ojos rojos', + 'Auto, Fired' => 'Flash disparado, modo automático', + 'Auto, Fired, Red-eye reduction' => 'Flash disparado, modo automático, modo reducción ojos rojos', + 'Auto, Fired, Red-eye reduction, Return detected' => 'Flash disparado, modo automático, retorno luz detectado, modo reducción ojos rojos', + 'Auto, Fired, Red-eye reduction, Return not detected' => 'Flash disparado, modo automático, retorno luz no detectado, modo reducción ojos rojos', + 'Auto, Fired, Return detected' => 'Flash disparado, modo automático, retorno luz detectado', + 'Auto, Fired, Return not detected' => 'Flash disparado, modo automático, retorno luz no detectado', + 'Did not fire' => 'No se ha disparado el flash', + 'Fired' => 'Flash disparado', + 'Fired, Red-eye reduction' => 'Flash disparado, modo reducción ojos rojos', + 'Fired, Red-eye reduction, Return detected' => 'Flash disparado, modo reducción ojos rojos, retorno luz detectado', + 'Fired, Red-eye reduction, Return not detected' => 'Flash disparado, modo reducción ojos rojos, retorno luz no detectado', + 'Fired, Return detected' => 'Luz devuelta en captador detectada', + 'Fired, Return not detected' => 'Luz devuelta en captador no detectada', + 'No Flash' => 'Flash no disparado', + 'No flash function' => 'Sin función flash', + 'Off' => 'Desactivado', + 'Off, Did not fire' => 'Flash no disparado, modo flash forzado', + 'Off, Did not fire, Return not detected' => 'Apagado, flash no disparado, retorno luz no detectado', + 'Off, No flash function' => 'Apagado, sin función flash', + 'Off, Red-eye reduction' => 'Apagado, modo reducción ojos rojos', + 'On' => 'Activado', + 'On, Did not fire' => 'Encendido, flash no disparado', + 'On, Fired' => 'Flash disparado, modo flash forzardo', + 'On, Red-eye reduction' => 'Flash disparado, modo flash forzado, modo reducción ojos rojos', + 'On, Red-eye reduction, Return detected' => 'Flash disparado, modo flash forzado, modo reducción ojos rojos, retorno luz detectado', + 'On, Red-eye reduction, Return not detected' => 'Flash disparado, modo flash forzado, modo reducción ojos rojos, retorno luz no detectado', + 'On, Return detected' => 'Flash disparado, modo flash forzado, retorno luz detectado', + 'On, Return not detected' => 'Flash disparado, modo flash forzado, retorno luz no detectado', + }, + }, + 'FlashColorFilter' => { + PrintConv => { + 'None' => 'Ninguno', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'FlashCommanderMode' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'FlashControlMode' => { + Description => 'Modo Control Flash', + PrintConv => { + 'Off' => 'Desactivado', + 'Repeating Flash' => 'Flash Estroboscopico', + }, + }, + 'FlashDevice' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'FlashEnergy' => 'Energía Flash', + 'FlashExposureComp' => 'Compensación Exposición Flash', + 'FlashExposureLock' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'FlashFired' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'FlashFocalLength' => 'Longitud Flash Flash', + 'FlashGroupAControlMode' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'FlashGroupBControlMode' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'FlashGroupCControlMode' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'FlashIntensity' => { + Description => 'Intensidad Flash', + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'FlashLevel' => { + PrintConv => { + 'Low' => 'Bajo', + }, + }, + 'FlashMeteringMode' => { + PrintConv => { + 'External Auto' => 'Externo Automatico', + 'External Manual' => 'Externo Manual', + 'Off' => 'Desactivado', + }, + }, + 'FlashMode' => { + Description => 'Modo Flash', + PrintConv => { + 'Auto' => 'Automático', + 'Disabled' => 'Desactivado', + 'Force' => 'Forzado', + 'Off' => 'Apagado', + 'On' => 'Encendido', + 'Red eye' => 'Ojos rojos', + }, + }, + 'FlashModel' => { + Description => 'Modelo Flash', + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'FlashOutput' => 'Flash Salida', + 'FlashRemoteControl' => 'Control Remote Flash', + 'FlashSerialNumber' => 'Número Serie Flash', + 'FlashSource' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'FlashStatus' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'FlashType' => { + Description => 'Tipo Flash', + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'FlashWarning' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'FlashpixVersion' => 'Versión Flashpix Soportado', + 'FlickerReduce' => { + Description => 'Reducir Parpadeo', + PrintConv => { + 'Off' => 'Apagado', + 'On' => 'Encendido', + }, + }, + 'FlipHorizontal' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'FocalLength' => 'Distancia Focal Objetivo', + 'FocalLength35efl' => 'Longitud Focal (Conversión a 35 mm)', + 'FocalLengthIn35mmFormat' => 'Distancia Focal en Película de 35 mm', + 'FocalPlaneResolutionUnit' => { + Description => 'Unidad Resolución Plano Focal', + PrintConv => { + 'None' => 'Ninguno', + 'inches' => 'Pulgada', + 'um' => 'µm (Micrometro)', + }, + }, + 'FocalPlaneXResolution' => 'Resolución X Plano Focal', + 'FocalPlaneYResolution' => 'Resolución Y Plano Focal', + 'FocusContinuous' => { + PrintConv => { + 'Continuous' => 'Continuo', + 'Single' => 'Sencillo', + }, + }, + 'FocusMode' => { + Description => 'Modo Enfoque', + PrintConv => { + 'Continuous' => 'Continuo', + 'Manual Focus (3)' => 'Enfoque Manual (6)', + 'Manual Focus (6)' => 'Enfoque Manual (6)', + 'Single' => 'Simple', + }, + }, + 'FocusRange' => { + Description => 'Rango de Enfoque', + PrintConv => { + 'Close' => 'Próximo', + 'Infinity' => 'Infinito', + 'Not Known' => 'Desconocido', + 'Very Close' => 'Muy Próximo', + }, + }, + 'FocusSetting' => 'Ajuste Enfoque', + 'FocusTrackingLockOn' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'ForwardMatrix1' => 'Matriz Avance 1', + 'ForwardMatrix2' => 'Matriz Avance 2', + 'FrameRate' => 'Velocidad del Fotograma', + 'FrameSize' => 'Tamaño del Fotograma', + 'FreeByteCounts' => 'Número Bytes Libres', + 'FreeOffsets' => 'Offsets Libres', + 'FuncButton' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'FuncButtonPlusDials' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'GEModel' => 'Modelo', + 'GPSAltitude' => 'Altitud', + 'GPSAltitudeRef' => { + Description => 'Referencia Altitud', + PrintConv => { + 'Above Sea Level' => 'Nivel del Mar', + 'Below Sea Level' => 'Referencia Nivel del Mar (valor negativo)', + }, + }, + 'GPSAreaInformation' => 'Nombre de Zona GPS', + 'GPSDOP' => 'Precisión Medición', + 'GPSDateStamp' => 'Fecha GPS', + 'GPSDateTime' => 'Fecha y Hora GPS', + 'GPSDestBearing' => 'Orientación de Destino', + 'GPSDestBearingRef' => { + Description => 'Referencia para Orientación de Destino', + PrintConv => { + 'Magnetic North' => 'Dirección magnética', + 'True North' => 'Dirección real', + }, + }, + 'GPSDestDistance' => 'Distancia a Destino', + 'GPSDestDistanceRef' => { + Description => 'Referencia para Distancia a Destino', + PrintConv => { + 'Kilometers' => 'Kilómetros', + 'Miles' => 'Millas', + 'Nautical Miles' => 'Nudos', + }, + }, + 'GPSDestLatitude' => 'Latitud de Destino', + 'GPSDestLatitudeRef' => { + Description => 'Referencia para Latitud de Destino', + PrintConv => { + 'North' => 'Latitud norte', + 'South' => 'Latitud sur', + }, + }, + 'GPSDestLongitude' => 'Longitud de Destino', + 'GPSDestLongitudeRef' => { + Description => 'Referencia para Longitud de Destino', + PrintConv => { + 'East' => 'Longitud este', + 'West' => 'Longitud oeste', + }, + }, + 'GPSDifferential' => { + Description => 'Corrección Diferencial GPS', + PrintConv => { + 'Differential Corrected' => 'Corrección diferencial aplicada', + 'No Correction' => 'Medición sin corrección diferencial', + }, + }, + 'GPSImgDirection' => 'Dirección de Imagen', + 'GPSImgDirectionRef' => { + Description => 'Referencia para Dirección de Imagen', + PrintConv => { + 'Magnetic North' => 'Dirección magnética', + 'True North' => 'Dirección real', + }, + }, + 'GPSInfo' => 'Puntero IFD de Información GPS', + 'GPSLatitude' => 'Latitud', + 'GPSLatitudeRef' => { + Description => 'Latitud Norte o Sur', + PrintConv => { + 'North' => 'Latitud norte', + 'South' => 'Latitud sur', + }, + }, + 'GPSLongitude' => 'Longitud', + 'GPSLongitudeRef' => { + Description => 'Longitud Este u Oeste', + PrintConv => { + 'East' => 'Longitud Este', + 'West' => 'Longitud Oeste', + }, + }, + 'GPSMapDatum' => 'Dato Medición Geodésica Usado', + 'GPSMeasureMode' => { + Description => 'Modo Medición GPS', + PrintConv => { + '2-Dimensional Measurement' => 'Medición bidimensional', + '3-Dimensional Measurement' => 'Medición tridimensional', + }, + }, + 'GPSProcessingMethod' => 'Nombre del Método de Procesado GPS', + 'GPSSatellites' => 'Satélites GPS Usados para Medida', + 'GPSSpeed' => 'Velocidad del Receptor GPS', + 'GPSSpeedRef' => { + Description => 'Unidad Velocidad', + PrintConv => { + 'km/h' => 'Kilómetros por hora', + 'knots' => 'Nudos', + 'mph' => 'Millas por hora', + }, + }, + 'GPSStatus' => { + Description => 'Estado Receptor GPS', + PrintConv => { + 'Measurement Active' => 'Medición Activa', + 'Measurement Void' => 'Medición Vacía', + }, + }, + 'GPSTimeStamp' => 'Hora GPS (reloj atómico)', + 'GPSTrack' => 'Dirección de Movimiento', + 'GPSTrackRef' => { + Description => 'Referencia de Dirección de Movimiento', + PrintConv => { + 'Magnetic North' => 'Dirección magnética', + 'True North' => 'Dirección real', + }, + }, + 'GPSVersionID' => 'Versión Etiqueta GPS', + 'GainControl' => { + Description => 'Control Ganancia', + PrintConv => { + 'High gain down' => 'Atenuación alta', + 'High gain up' => 'Ganancia alta', + 'Low gain down' => 'Atenuación baja', + 'Low gain up' => 'Ganancia debil', + 'None' => 'Ninguno', + }, + }, + 'GammaCompensatedValue' => 'Valor Compensado Gamma', + 'Gapless' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'Genre' => { + Description => 'Género', + PrintConv => { + 'None' => 'Ninguno', + 'Other' => 'Otro', + }, + }, + 'GenreID' => 'ID Género', + 'GeoTiffAsciiParams' => 'Etiqueta Parámetros Ascii Geo', + 'GeoTiffDirectory' => 'Etiqueta Directorio Clave Geo', + 'GeoTiffDoubleParams' => 'Etiqueta Parámetros Doble Geo', + 'Gradation' => 'Luminosidad', + 'GrayResponseCurve' => 'Curva Respuesta Gris', + 'GrayResponseUnit' => { + Description => 'Unidad Respuesta Gris', + PrintConv => { + '0.0001' => 'Número representa la milésima de una unidad', + '0.001' => 'Número representa la centésima de una unidad', + '0.1' => 'Número representa la décima de una unidad', + '1e-05' => 'Número representa la diezmilésima de una unidad', + '1e-06' => 'Número representa la cienmilésima de una unidad', + }, + }, + 'GrayTRC' => 'Columna Matriz Gris', + 'GreenMatrixColumn' => 'Columna Matriz Verde', + 'GreenTRC' => 'Curva Reproducción Tono Verde', + 'GridDisplay' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'HCUsage' => 'Uso HC', + 'HDR' => { + Description => 'Auto HDR', + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'HDRSmoothing' => { + PrintConv => { + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + }, + }, + 'HalftoneHints' => 'Indicación Medio Tono', + 'HasAttachedImages' => 'Tiene Imagenes Adjuntas', + 'HasAudio' => 'Tiene Audio', + 'HasImage' => 'Tiene Imagen', + 'HasScript' => 'Tiene Script', + 'HasVideo' => 'Tiene Video', + 'Headline' => 'Titular', + 'HeightResolution' => 'Resolución Imagen Vertical', + 'HighISONoiseReduction' => { + Description => 'Reducción Ruido ISO Alta', + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + 'On' => 'Activado', + 'Standard' => 'Estándar', + 'Strong' => 'Fuerte', + }, + }, + 'HighISONoiseReduction2' => { + PrintConv => { + 'Low' => 'Bajo', + }, + }, + 'Highlight' => 'Realce', + 'HighlightColorDistortReduct' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'HighlightTonePriority' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'HometownDST' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'HostComputer' => 'Ordenador Principal', + 'Hue' => { + Description => 'Tono', + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'HyperfocalDistance' => 'Distancia Hiperfocal', + 'ICCProfile' => 'Perfil ICC', + 'ICC_Profile' => 'Perfil Color Entrada ICC', + 'IDCCreativeStyle' => { + PrintConv => { + 'Landscape' => 'Retrato', + 'Neutral' => 'Neutro', + 'Standard' => 'Estándar', + 'Vivid' => 'Vívido', + }, + }, + 'IPTC-NAA' => 'Metadato IPTC-NAA', + 'IPTCBitsPerSample' => 'Número de Bits por Muestra', + 'IPTCImageHeight' => 'Número de Líneas', + 'IPTCImageRotation' => { + Description => 'Rotación Imagen', + PrintConv => { + '0' => 'Sin rotación', + '180' => 'Rotación 180 grados', + '270' => 'Rotación 270 grados', + '90' => 'Rotación 90 grados', + }, + }, + 'IPTCImageWidth' => 'Pixels por Línea', + 'IPTCPictureNumber' => 'Número Imagen', + 'IPTCPixelHeight' => 'Tamaño Pixel Perpendicular a Dirección Escaneo', + 'IPTCPixelWidth' => 'Tamaño Pixel en Dirección Escaneo', + 'ISO' => 'Ratio Velocidad ISO', + 'ISOAutoParameters' => { + PrintConv => { + 'Fast' => 'Rápido', + 'Standard' => 'Estándar', + }, + }, + 'ISOExpansion' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'ISOExpansion2' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'ISOSetting' => 'Ajuste ISO', + 'ISOSpeedExpansion' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'IT8Header' => 'Cabecera IT8', + 'Illumination' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'Image::ExifTool::AIFF::Comment' => 'Comentario AIFF', + 'ImageAuthentication' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'ImageByteCount' => 'Número Byte Imagen', + 'ImageColor' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'ImageColorIndicator' => 'Indicador Color Imagen', + 'ImageColorValue' => 'Valor Color Imagen', + 'ImageDataDiscard' => { + Description => 'Datos Imagen Descartado', + PrintConv => { + 'Flexbits Discarded' => 'FlexBits Descartados', + 'Full Resolution' => 'Resolución Total', + 'HighPass Frequency Data Discarded' => 'Datos Frecuencia High-Pass Descartados', + 'Highpass and LowPass Frequency Data Discarded' => 'Dato Frecuencia High-Pass y Low-Pass Descartados', + }, + }, + 'ImageDepth' => 'Ancho Imagen', + 'ImageDescription' => 'Título Imagen', + 'ImageDustOff' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ImageEditing' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'ImageFileFormatAsDelivered' => { + PrintConv => { + 'Other' => 'Otro', + }, + }, + 'ImageHeight' => 'Alto Imagen', + 'ImageHistory' => 'Historia Imagen', + 'ImageID' => 'ID Imagen', + 'ImageInfo' => 'Info Imagen', + 'ImageLayer' => 'Capa Imagen', + 'ImageLength' => 'Longitud Imagen', + 'ImageNumber' => 'Número Imagen', + 'ImageOffset' => 'Offset Imagen', + 'ImageOrientation' => { + Description => 'Orientación Imagen', + PrintConv => { + 'Landscape' => 'Paisaje', + 'Portrait' => 'Retrato', + 'Square' => 'Cuadro', + }, + }, + 'ImageQuality' => { + PrintConv => { + 'High' => 'Alto', + 'Standard' => 'Estándar', + }, + }, + 'ImageResourceBlocks' => 'Bloques Recursos Imagen', + 'ImageReview' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ImageRotated' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'ImageRotation' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'ImageSize' => 'Tamaño de la Imagen', + 'ImageSourceData' => 'Datos Fuente Imagen', + 'ImageStabilization' => { + Description => 'Estabilización Imagen', + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ImageStabilizationSetting' => 'Ajustes Estabilización Imagen', + 'ImageStyle' => { + PrintConv => { + 'Landscape' => 'Paisaje', + 'Neutral' => 'Neutro', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + 'Vivid' => 'Vívido', + }, + }, + 'ImageTone' => { + PrintConv => { + 'Landscape' => 'Paisaje', + 'Monochrome' => 'Monocromo', + 'Portrait' => 'Retrato', + }, + }, + 'ImageType' => { + Description => 'Tipo Imagen', + PrintConv => { + 'Other' => 'Otro', + }, + }, + 'ImageUniqueID' => 'ID Único Imagen', + 'ImageWidth' => 'Ancho Imagen', + 'Index' => 'Ãndice', + 'Indexed' => 'Indizado', + 'IngredientsMaskMarkers' => { + PrintConv => { + 'All' => 'Todo', + 'None' => 'Ninguno', + }, + }, + 'InitialKey' => 'Clave inicial', + 'InkNames' => 'Nombres Tinta', + 'InkSet' => 'Conjunto Tinta', + 'Instructions' => 'Instrucciones', + 'IntellectualGenre' => 'Género Intelectual', + 'IntelligentContrast' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + 'n/a' => 'No Aplica', + }, + }, + 'IntelligentD-Range' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Standard' => 'Estándar', + }, + }, + 'IntelligentExposure' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Standard' => 'Estándar', + }, + }, + 'IntelligentResolution' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Standard' => 'Estándar', + }, + }, + 'IntensityStereo' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'InterchangeColorSpace' => { + PrintConv => { + 'CMY (K) Device Dependent' => 'Dispositivo dependiente CMY(K)', + 'RGB Device Dependent' => 'Dispositivo dependiente RGB', + }, + }, + 'IntergraphMatrix' => 'Etiqueta Matriz Intergráfica', + 'Interlace' => 'Entrelazado', + 'InternalFlash' => 'Flash Interno', + 'InternalFlashAE1' => 'Flash Interno AE1', + 'InternalFlashAE1_0' => 'Flash Interno', + 'InternalFlashAE2' => 'Flash Interno AE2', + 'InternalFlashAE2_0' => 'Flash Interno AE2', + 'InternalFlashMode' => { + Description => 'Modo Flash Interno', + PrintConv => { + 'Fired' => 'Activado', + }, + }, + 'InternalFlashTable' => 'Tabla Flash Interno', + 'InternalSerialNumber' => 'Número Serie Interno', + 'InteropIndex' => { + Description => 'Identificación Interoperabilidad', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03: Archivo opción DCF (Adobe RGB)', + 'R98 - DCF basic file (sRGB)' => 'R98: Archivo básico DCF (sRGB)', + 'THM - DCF thumbnail file' => 'THM: Archivo miniatura DCF', + }, + }, + 'InteropOffset' => 'Etiqueta de Interoperabilidad', + 'InteropVersion' => 'Versión Interoperabilidad', + 'Is_Protected' => 'Está protegido', + 'Is_Trusted' => 'Es de confianza', + 'JFIFVersion' => 'Versión JFIF', + 'JPEGACTables' => 'Tablas AC JPEG', + 'JPEGDCTables' => 'Tablas DC JPEG', + 'JPEGLosslessPredictors' => 'Predictores Sin Perdidas JPEG', + 'JPEGPointTransforms' => 'Tranformadores Puntos JPEG', + 'JPEGProc' => 'Proc JPEG', + 'JPEGQTables' => 'Tablas Q JPEG', + 'JPEGQuality' => { + Description => 'Calidad', + PrintConv => { + 'Extra Fine' => 'Extrafina', + 'Fine' => 'Fina', + 'Standard' => 'Calidad estándar', + }, + }, + 'JPEGRestartInterval' => 'Intervalo Reinicio JPEG', + 'JPEGTables' => 'Tablas JPEG', + 'JobID' => 'ID del Trabajo', + 'JobTitle' => 'Cargo', + 'Keyword' => 'Palabras Clave', + 'Keywords' => 'Clave', + 'LCDIllumination' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'LCDIlluminationDuringBulb' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'LCHEditor' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'Language' => 'Idioma', + 'LanguageCode' => { + PrintConv => { + 'Neutral' => 'Neutro', + }, + }, + 'LanguageIdentifier' => 'Identificador Idioma', + 'LanguageList' => 'Lista de Idiomas', + 'LeafData' => 'Datos Hoja', + 'Lens' => 'Objetivo', + 'LensApertureRange' => 'Intervalo Apertura Objetivo', + 'LensFirmwareVersion' => 'Versión Firmware Objetivo', + 'LensID' => 'ID Objetivo', + 'LensIDNumber' => 'Número ID Objetivo', + 'LensInfo' => 'Información del Objetivo', + 'LensModel' => 'Modelo Objetivo', + 'LensProperties' => 'Propiedades Objetivo', + 'LensSerialNumber' => 'Número Serie Objetivo', + 'LensType' => { + Description => 'Tipo Objetivo', + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'LightSource' => { + Description => 'Fuente Luz', + PrintConv => { + 'Cloudy' => 'Tiempo Nublado', + 'Cool White Fluorescent' => 'Fluorescente blanco cálido (W 3800 - 4500K)', + 'Day White Fluorescent' => 'Fluorescente blanco día (N 4600 - 5500K)', + 'Daylight' => 'Luz del día', + 'Daylight Fluorescent' => 'Fluorescente luz de día (D 5700 - 7100K)', + 'Fine Weather' => 'Buen tiempo', + 'Fluorescent' => 'Fluorescente', + 'ISO Studio Tungsten' => 'Tungsteno estudio ISO', + 'Other' => 'Otras Fuentes Luz', + 'Shade' => 'Sombrío', + 'Standard Light A' => 'Luz Estándar A', + 'Standard Light B' => 'Luz Estándar B', + 'Standard Light C' => 'Luz Estándar C', + 'Tungsten (Incandescent)' => 'Tungsteno (luz incandescente)', + 'Unknown' => 'Desconocido', + 'Warm White Fluorescent' => 'Fluorescente blanco cálido (L 2600 - 3250K)', + 'White Fluorescent' => 'Fluorescente blanco (WW 3250 - 3800K)', + }, + }, + 'LightSourceSpecial' => { + Description => 'Fuente Luz Especial', + PrintConv => { + 'Off' => 'Apagado', + 'On' => 'Encendido', + }, + }, + 'Lightness' => 'Luminosidad', + 'LinearResponseLimit' => 'Límite Respuesta Lineal', + 'LinearizationTable' => 'Tabla Linearización', + 'Lit' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'LiveViewShooting' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'LocalizedCameraModel' => 'Modelo Cámara Traducido', + 'Location' => 'Localización', + 'LongExposureNoiseReduction' => { + Description => 'RR Exp.Larga', + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'LookupTable' => 'Tabla de Consulta', + 'Luminance' => 'Luminancia', + 'LuminanceNoiseReduction' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + }, + }, + 'Lyrics' => 'Letras', + 'Lyrics_Synchronised' => 'Letras Sincronizadas', + 'MSStereo' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'Macro' => { + PrintConv => { + 'Off' => 'Apagado', + 'On' => 'Encendido', + 'View' => 'Vista', + }, + }, + 'MacroMode' => 'Modo Macro', + 'MainDialExposureComp' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'Make' => 'Marca', + 'MakeAndModel' => 'Marca y Modelo', + 'MakerNote' => 'Dato Privado DNG', + 'MakerNoteOffset' => 'Offset Maker Note', + 'MakerNoteSafety' => { + Description => 'Seguridad Maker Note', + PrintConv => { + 'Safe' => 'Seguro', + 'Unsafe' => 'No Seguro', + }, + }, + 'MakerNoteType' => 'Tipo Maker Note', + 'MakerNoteVersion' => 'Versión nota Fabricante', + 'MakerNotes' => 'Notas del Fabricante', + 'ManagedFromMaskMarkers' => { + PrintConv => { + 'All' => 'Todo', + 'None' => 'Ninguno', + }, + }, + 'ManifestReferenceMaskMarkers' => { + PrintConv => { + 'All' => 'Todo', + 'None' => 'Ninguno', + }, + }, + 'ManometerPressure' => 'Presión Manometrica', + 'ManometerReading' => 'Lectura Manometrica', + 'ManualFlashOutput' => { + PrintConv => { + 'Full' => 'Completo', + 'Low' => 'Bajo', + 'Medium' => 'Medio', + 'n/a' => 'No Aplica', + }, + }, + 'ManualFocusDistance' => 'Distancia Enfoque Manual', + 'Marker' => 'Marcador', + 'MaskedAreas' => 'Ãrea Oculta', + 'MasterDocumentID' => 'ID de Documento Maestro', + 'Matteing' => 'Mate', + 'MaxAperture' => 'Máxima Apertura del Objetivo', + 'MaxApertureAtMaxFocal' => 'Apertura máxima a focal máxima', + 'MaxApertureAtMinFocal' => 'Apertura máxima a focal mínima', + 'MaxApertureValue' => 'Apertura Lente Máxima', + 'MaxFaces' => 'Máximo caras', + 'MaxFocalLength' => 'Longitud focal máxima', + 'MaxPacketSize' => 'Tamaño Máximo Paquete', + 'MaxSampleValue' => 'Valor Muestra Max', + 'MaximumDensityRange' => 'Rango Densidad Maxima', + 'Measurement' => 'Observador de Medida', + 'MeasurementBacking' => 'Apoyo de Medida', + 'MeasurementFlare' => 'Llama de Medida', + 'MeasurementGeometry' => { + Description => 'Geometría de Medida', + PrintConv => { + '0/45 or 45/0' => '0/45 o 45/0', + '0/d or d/0' => '0/d o d/0', + }, + }, + 'MeasurementIlluminant' => 'Iluminación de Medida', + 'MeasurementObserver' => 'Observador de Medida', + 'MediaBlackPoint' => 'Punto Negro Medio', + 'MediaWhitePoint' => 'Punto Blanco Medio', + 'MeteringMode' => { + Description => 'Modo Medición', + PrintConv => { + 'Average' => 'Promedio', + 'Center-weighted average' => 'Media ponderada al centro', + 'Multi-segment' => 'Multi-segmento', + 'Multi-spot' => 'Multi-puntual', + 'Other' => 'Otro', + 'Partial' => 'Parcial', + 'Spot' => 'Puntual', + 'Unknown' => 'Desconocido', + }, + }, + 'MinAperture' => 'Apertura mínima', + 'MinFocalLength' => 'Longitud focal mínima', + 'MinPacketSize' => 'Tamaño Mínimo Paquete', + 'MinSampleValue' => 'Valor Muestra Min', + 'MinoltaQuality' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'ModeDialPosition' => { + PrintConv => { + 'Panorama' => 'Panoramica', + }, + }, + 'Model' => 'Modelo', + 'Model2' => 'Modelo Equipamiento Entrada Imagen (2)', + 'ModelReleaseStatus' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'ModelTiePoint' => 'Etiqueta Modelo Punto Lazo', + 'ModelTransform' => 'Etiqueta Modelo Transformación', + 'ModelingFlash' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ModifiedBy' => 'Modificado por', + 'ModifiedPictureStyle' => { + PrintConv => { + 'High Saturation' => 'Saturación Alta', + 'Landscape' => 'Paisaje', + 'Low Saturation' => 'Saturación Baja', + 'Monochrome' => 'Monocromo', + 'Neutral' => 'Neutro', + 'None' => 'Ninguno', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + }, + }, + 'ModifiedSharpnessFreq' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Standard' => 'Estándar', + }, + }, + 'ModifiedToneCurve' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'ModifiedWhiteBalance' => { + PrintConv => { + 'Underwater' => 'Subacuatica', + }, + }, + 'ModifyDate' => 'Fecha y Hora de Cambio del Archivo', + 'MoireFilter' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'MonochromeFilterEffect' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + 'Orange' => 'Naranja', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'MonochromeLinear' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'MonochromeToning' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'MonochromeToningEffect' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + }, + }, + 'MultiExposureAutoGain' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'MultiExposureMode' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'MultiFrameNoiseReduction' => { + Description => 'Reduc. ruido varios fotogr.', + PrintConv => { + 'None' => 'Ninguno', + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'MultipleExposureMode' => 'Modo Exposición Múltiple', + 'MultipleExposureSet' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'Mute' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'MyColorMode' => { + PrintConv => { + 'Neutral' => 'Neutro', + 'Off' => 'Desactivado', + 'Vivid' => 'Vívido', + }, + }, + 'NDFilter' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'NSC_Description' => 'Descripción NSC', + 'Name' => 'Nombre', + 'NamedColor2' => 'Color Llamado 2', + 'NativeDisplayInfo' => 'Información Pantalla Nativa', + 'NewsPhotoVersion' => 'Versión Registro Foto Noticias', + 'Noise' => 'Ruido', + 'NoiseFilter' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + 'Standard' => 'Estándar', + }, + }, + 'NoiseReduction' => { + Description => 'Reducción Ruido', + PrintConv => { + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + 'Standard' => 'Estándar', + }, + }, + 'NoiseReductionApplied' => 'Reducción Ruido Aplicada', + 'NominalMaxAperture' => 'Apertura máxima nominal', + 'NominalMinAperture' => 'Apertura mínima nominal', + 'NumAFPoints' => 'Número de Puntos AF', + 'NumChannels' => 'Número Canales', + 'NumColors' => 'Número de colores', + 'NumImportantColors' => 'Número Colores Importantes', + 'NumIndexEntries' => 'Número de Entradas de Ãndice', + 'NumSampleFrames' => 'Número de fotogramas', + 'NumberOfFrames' => 'Número de imágenes', + 'NumberofInks' => 'Número de Tintas', + 'OPIProxy' => 'Proxy OPI', + 'ObjectAttributeReference' => 'Género Intelectual', + 'ObjectCycle' => { + Description => 'Ciclo Objecto', + PrintConv => { + 'Both Morning and Evening' => 'Ambos', + 'Evening' => 'Tarde', + 'Morning' => 'Mañana', + }, + }, + 'ObjectFileType' => { + PrintConv => { + 'None' => 'Ninguno', + 'Unknown' => 'Desconocido', + }, + }, + 'ObjectName' => 'Título', + 'ObjectPreviewData' => 'Datos Previos del Objecto', + 'ObjectPreviewFileFormat' => 'Formato Archivo Previo de Objecto', + 'ObjectPreviewFileVersion' => 'Versión Formato Archivo Previo del Objecto', + 'ObjectTypeReference' => 'Referencia Tipo Objeto', + 'OffsetSchema' => 'Offset Esquema', + 'OperatingSystem' => { + Description => 'Sistema Operativo', + PrintConv => { + 'unknown' => 'desconocido', + }, + }, + 'OpticalZoomMode' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'OpticalZoomOn' => { + Description => 'Zoom Óptico Encendido', + PrintConv => { + 'Off' => 'Apagado', + 'On' => 'Encendido', + }, + }, + 'Opto-ElectricConvFactor' => 'Factor Conversión Optoeléctrico', + 'Orientation' => { + Description => 'Orientación de Imagen', + PrintConv => { + 'Horizontal (normal)' => '0° (arriba/izquierda)', + 'Mirror horizontal' => 'Invertir horizontal', + 'Mirror horizontal and rotate 270 CW' => 'Invertir horizontal y rotar 270° sentido reloj', + 'Mirror horizontal and rotate 90 CW' => 'Invertir horizontal y rotar 90° sentido reloj', + 'Mirror vertical' => 'Invertir vertical', + 'Rotate 180' => 'Girado 180°', + 'Rotate 270 CW' => 'Girado 270° sentido reloj', + 'Rotate 90 CW' => 'Girado 90° sentido reloj', + }, + }, + 'OriginPlatform' => { + PrintConv => { + 'Other' => 'Otro', + }, + }, + 'OriginalAlbumTitle' => 'Título Original Album', + 'OriginalArtist' => 'Artista original', + 'OriginalFileName' => 'Nombre archivo original', + 'OriginalLyricist' => 'Letrista Original', + 'OriginalRawFileData' => 'Dato Archivo Raw Original', + 'OriginalRawFileDigest' => 'Cifrado Archivo Raw Original', + 'OriginalRawFileName' => 'Nombre Archivo Raw Original', + 'OriginalReleaseYear' => 'Año Versión Original', + 'OriginalTransmissionReference' => 'Identificador de Trabajo', + 'OriginatingProgram' => 'Programa Originario', + 'OutputResponse' => 'Respuesta Salida', + 'Owner' => 'Propietario', + 'OwnerID' => 'ID del Propietario', + 'OwnerName' => 'Nombre del Propietario', + 'PF25ColorMatrix' => 'Matriz de Color PF25', + 'PackingMethod' => { + PrintConv => { + 'Best Compression' => 'Mejor Compresión', + 'Fast' => 'Rápido', + 'Fastest' => 'Mas Rápido', + 'Good Compression' => 'Buena Compresión', + 'Stored' => 'Almacenado', + }, + }, + 'Padding' => 'Margen Inferior', + 'PageName' => 'Nombre Página', + 'PageNumber' => 'Número Página', + 'PanoramaSize3D' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'Period' => 'Período', + 'PhotoEffect' => { + PrintConv => { + 'B&W' => 'Blanco y Negro', + 'Custom' => 'Personalizado', + 'Neutral' => 'Neutro', + 'Off' => 'Desactivado', + 'Vivid' => 'Vívido', + }, + }, + 'PhotoEffects' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'PhotoEffectsType' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'PhotometricInterpretation' => { + Description => 'Interpretación Fotométrica', + PrintConv => { + 'BlackIsZero' => 'Negro es cero', + 'Color Filter Array' => 'CFA (Matriz Filtro Color)', + 'Pixar LogL' => 'CIE Log2(L) (Log luminancia)', + 'Pixar LogLuv' => 'CIE Log2(L)(u\',v\') (Log luminancia y crominancia)', + 'RGB Palette' => 'Paleta Color', + 'Transparency Mask' => 'Máscara de transparencia', + 'WhiteIsZero' => 'Blanco es cero', + }, + }, + 'PhotoshopAnnotations' => 'Anotaciones Photoshop', + 'PhotoshopFormat' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'PictInfo' => 'Info Imagen', + 'Picture' => 'Imágen', + 'PictureControl' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'PictureControlActive' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'PictureDescription' => 'Descripción Imágen', + 'PictureFinish' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + 'Portrait' => 'Retrato', + }, + }, + 'PictureMode' => { + PrintConv => { + 'Beach' => 'Playa', + 'Fireworks' => 'Fuegos Artificiales', + 'Food' => 'Comida', + 'Green' => 'Verde', + 'Landscape' => 'Paisaje', + 'Panorama' => 'Panoramica', + 'Portrait' => 'Retrato', + 'Red' => 'Rojo', + 'Snow' => 'Nieve', + 'Soft' => 'Suave', + 'Standard' => 'Estándar', + 'Underwater' => 'Subacuatica', + 'Vivid' => 'Vívido', + 'Yellow' => 'Amarillo', + }, + }, + 'PictureModeBWFilter' => { + PrintConv => { + 'Green' => 'Verde', + 'Neutral' => 'Neutro', + 'Orange' => 'Naranja', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'PictureModeEffect' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Standard' => 'Estándar', + }, + }, + 'PictureModeTone' => { + PrintConv => { + 'Green' => 'Verde', + 'Neutral' => 'Neutro', + }, + }, + 'PictureStyle' => { + PrintConv => { + 'Faithful' => 'Fiel', + 'High Saturation' => 'Saturación Alta', + 'Landscape' => 'Paisaje', + 'Low Saturation' => 'Saturación Baja', + 'Monochrome' => 'Monocromo', + 'Neutral' => 'Neutro', + 'None' => 'Ninguno', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + }, + }, + 'PictureType' => { + Description => 'Tipo Imágen', + PrintConv => { + '32x32 PNG Icon' => 'Icono PNG 32x32', + 'Artist' => 'Artista', + 'Back Cover' => 'Cubierta Posterior', + 'Composer' => 'Compositor', + 'Conductor' => 'Director', + 'Front Cover' => 'Cubierta Frontal', + 'Illustration' => 'Ilustración', + 'Lyricist' => 'Letrista', + 'Media' => 'Soporte', + 'Other' => 'Otro', + 'Other Icon' => 'Otro Icono', + 'Performance' => 'Interpretación', + 'Recording Session' => 'Sesión Grabación', + }, + }, + 'PictureWizardMode' => { + PrintConv => { + 'Cool' => 'Frío', + 'Landscape' => 'Paisaje', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + 'Vivid' => 'Vívido', + }, + }, + 'PixelFormat' => 'Formato Pixel', + 'PixelIntensityRange' => 'Intervalo Intensidad Pixel', + 'PixelScale' => 'Etiqueta Escala Pixel Modelo', + 'PlanarConfiguration' => { + Description => 'Ajuste Datos Imagen', + PrintConv => { + 'Chunky' => 'Formato \'Chunky\' (Entrelazado)', + 'Planar' => 'Formato \'Planar\'', + }, + }, + 'Predictor' => { + PrintConv => { + 'Horizontal differencing' => 'Diferenciación Horizontal', + 'None' => 'No se usa esquema de predicción antes de codificar', + }, + }, + 'Preview0' => 'Previa 0', + 'Preview1' => 'Previa 1', + 'Preview2' => 'Previa 2', + 'PreviewApplicationName' => 'Nombre Aplicación Previa', + 'PreviewApplicationVersion' => 'Versión Aplicación Previa', + 'PreviewButton' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'PreviewButtonPlusDials' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'PreviewColorSpace' => { + Description => 'Espacio Color Previa', + PrintConv => { + 'Unknown' => 'Desconocido', + }, + }, + 'PreviewDateTime' => 'Fecha y Hora Previa', + 'PreviewImage' => 'Vista Previa', + 'PreviewImageLength' => 'Longitud Imagen Previa', + 'PreviewImageSize' => 'Tamaño Imagen Previa', + 'PreviewImageStart' => 'Inicio Imagen Previa', + 'PreviewImageValid' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'PreviewSettingsDigest' => 'Cifrado Configuración Previa', + 'PreviewSettingsName' => 'Nombre Configuración Previa', + 'PrimaryAFPoint' => 'Punto AF primario', + 'PrimaryChromaticities' => 'Cromaticidades de Colores Primarios', + 'PrimaryPlatform' => 'Plataforma Primaria', + 'ProcessingSoftware' => 'Tratamiendo de Software', + 'Producer' => 'Productor', + 'ProductID' => 'ID Producto', + 'ProfileCMMType' => 'Tipo Perfil CMM', + 'ProfileCalibrationSig' => 'Firma Perfil Calibración', + 'ProfileClass' => { + Description => 'Clase Perfil', + PrintConv => { + 'Abstract Profile' => 'Perfil Abstracto', + 'ColorSpace Conversion Profile' => 'Perfil Conversión Espacio Color', + 'DeviceLink Profile' => 'Perfil Dispositivo Conexión', + 'Display Device Profile' => 'Perfil Dispositivo Pantalla', + 'Input Device Profile' => 'Perfil Dispositivo Entrada', + 'NamedColor Profile' => 'Perfil Color Nombrado', + 'Nikon Input Device Profile (NON-STANDARD!)' => 'Perfil Nikon ("nkpf")', + 'Output Device Profile' => 'Perfil Dispositivo Salida', + }, + }, + 'ProfileConnectionSpace' => 'Espacio Conexión Perfil', + 'ProfileCopyright' => 'Copyright', + 'ProfileCreator' => 'Creador Perfil', + 'ProfileDateTime' => 'Fecha y Hora Perfil', + 'ProfileDescription' => 'Descripción Perfil', + 'ProfileDescriptionML' => 'Descripción Perfil ML', + 'ProfileEmbedPolicy' => { + Description => 'Perfil Política Incrustada', + PrintConv => { + 'Allow Copying' => 'Permitir copia', + 'Embed if Used' => 'Incrustar si se usa', + 'Never Embed' => 'Incrustado nunca', + 'No Restrictions' => 'Sin restricciones', + }, + }, + 'ProfileFileSignature' => 'Firma Archivo Perfil', + 'ProfileHueSatMapData1' => 'Perfil Matiz Sat. Mapa Dato 1', + 'ProfileHueSatMapData2' => 'Perfil Matiz Sat. Mapa Dato 2', + 'ProfileHueSatMapDims' => 'Divisiones Matiz', + 'ProfileID' => 'ID Perfil', + 'ProfileLookTableData' => 'Perfil Datos Tabla Consulta', + 'ProfileLookTableDims' => 'Divisiones Matiz', + 'ProfileName' => 'Nombre Perfil', + 'ProfileSequenceDesc' => 'Descripción Secuencia Perfil', + 'ProfileToneCurve' => 'Perfil Curva Tono', + 'ProfileVersion' => 'Versión Perfil', + 'ProgramMode' => { + PrintConv => { + 'None' => 'Ninguno', + 'Portrait' => 'Retrato', + }, + }, + 'ProgramVersion' => 'Versión Programa', + 'Projects' => 'Proyectos', + 'PromotionURL' => 'URL Promocional', + 'PropertyReleaseStatus' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'Protect' => 'Protección', + 'ProtectionType' => 'Tipo Protección', + 'Provider' => 'Proveedor', + 'ProviderCopyright' => 'Copyright Proveedor', + 'Province-State' => 'Provincia/Estado', + 'Publisher' => 'Editor', + 'Quality' => { + Description => 'Calidad', + PrintConv => { + 'Compressed RAW' => 'cRAW', + 'Compressed RAW + JPEG' => 'cRAW+JPEG', + 'Extra Fine' => 'Extrafina', + 'Fine' => 'Fina', + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Normal' => 'Calidad estándar', + 'RAW + JPEG' => 'RAW+JPEG', + 'Standard' => 'Estándar', + 'n/a' => 'no aplica', + }, + }, + 'QuantizationMethod' => { + Description => 'Método Cuantización', + PrintConv => { + 'AP Domestic Analogue' => 'AP Doméstico Análogo', + 'Color Space Specific' => 'Espacio Color Específico', + 'Compression Method Specific' => 'Método Compresión Específico', + 'Gamma Compensated' => 'Gamma Compensada', + 'IPTC Ref B' => 'IPTC ref "B"', + 'Linear Density' => 'Densidad lineal', + 'Linear Dot Percent' => 'Porcentaje Punto lineal', + 'Linear Reflectance/Transmittance' => 'Reflectancia/transmitancia lineal', + }, + }, + 'QuickShot' => { + Description => 'Disparo Rápido', + PrintConv => { + 'Off' => 'Apagado', + 'On' => 'Encendido', + }, + }, + 'RadioStationName' => 'Nombre Emisora Radio', + 'RadioStationOwner' => 'Propietario Emisora Radio', + 'RasterPadding' => 'Relleno Trama', + 'RasterizedCaption' => 'Título Rasterizado', + 'Rating' => { + Description => 'Clasificación', + PrintConv => { + 'none' => 'Ninguno', + }, + }, + 'RatingPercent' => 'Valoración en Porcentaje', + 'RawCustomSaturation' => 'Raw Saturación personalizada', + 'RawDataUniqueID' => 'ID Único Dato Raw', + 'RawDevAutoGradation' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'RawDevNoiseReduction' => { + PrintConv => { + 'Noise Filter' => 'Filtro ruido', + 'Noise Filter (ISO Boost)' => 'Filtro ruido (ISO Boost)', + 'Noise Reduction' => 'Reducción ruido', + }, + }, + 'RawDevPMPictureTone' => { + PrintConv => { + 'Green' => 'Verde', + 'Neutral' => 'Neutro', + }, + }, + 'RawDevPM_BWFilter' => { + PrintConv => { + 'Green' => 'Verde', + 'Neutral' => 'Neutro', + 'Orange' => 'Naranja', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'RawDevPictureMode' => { + PrintConv => { + 'Vivid' => 'Vívido', + }, + }, + 'RawDevSettings' => { + PrintConv => { + 'Noise Reduction' => 'Reducción ruido', + }, + }, + 'RawImageDigest' => 'Cifrado Imagen RAW', + 'RawJpgSize' => { + PrintConv => { + 'Medium' => 'Medio', + 'Medium 1' => 'Medio 1 ', + 'Medium 2' => 'Medio 2', + 'Medium 3' => 'Medio 3', + 'Postcard' => 'Tarjeta Postal', + 'Small 1' => 'Pequeño 1', + 'Small 2' => 'Pequeño 21', + 'Small 3' => 'Pequeño 3', + }, + }, + 'RecordMode' => 'Modo de Grabación', + 'RecordShutterRelease' => { + Description => 'Soltar Obturador Grabación', + PrintConv => { + 'Press start, press stop' => 'Pulsa para iniciar, pulsa para parar', + 'Record while down' => 'Grabar mientras se pulsa', + }, + }, + 'RecordedTrackNumber' => 'Número Pista grabada', + 'RecordingMode' => { + PrintConv => { + 'Panorama' => 'Panoramica', + 'Portrait' => 'Retrato', + }, + }, + 'RedEyeCorrection' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'RedEyeReduction' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'RedMatrixColumn' => 'Columna Matriz Rojo', + 'RedTRC' => 'Curva Reproducción Tono Rojo', + 'ReductionMatrix1' => 'Matriz Reducción 1', + 'ReductionMatrix2' => 'Matriz Reducción 2', + 'ReferenceBlackWhite' => 'Par de Valores de Referencia Blanco y Negro', + 'ReferenceDate' => 'Fecha Referencia', + 'ReferenceNumber' => 'Número Referencia', + 'ReferenceService' => 'Servicio Referencia', + 'RelatedImageFileFormat' => 'Formato Archivo Imagen Relacionado', + 'RelatedImageHeight' => 'Alto Imagen Relacionada', + 'RelatedImageWidth' => 'Ancho Imagen Relacionada', + 'RelatedSoundFile' => 'Archivo Audio Relacionado', + 'ReleaseButtonToUseDial' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'ReleaseDate' => 'Fecha Lanzamiento', + 'ReleaseTime' => 'Hora Lanzamiento', + 'RenderingIntent' => { + Description => 'Intento Interpretación', + PrintConv => { + 'ICC-Absolute Colorimetric' => 'Colorimétrica Absoluta', + 'Media-Relative Colorimetric' => 'Colorimétrica Relativa', + 'Saturation' => 'Saturación', + }, + }, + 'RenditionOfMaskMarkers' => { + PrintConv => { + 'All' => 'Todo', + 'None' => 'Ninguno', + }, + }, + 'ResampleParamsQuality' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'Resaved' => { + Description => 'Regrabado', + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'Reserved1' => 'Reservado 1', + 'Resolution' => 'Resolución', + 'ResolutionMode' => 'Modo Resolución', + 'ResolutionUnit' => { + Description => 'Unidad de Resolución de X e Y', + PrintConv => { + 'None' => 'Ninguno', + 'cm' => 'Píxeles/cm', + 'inches' => 'Pulgada', + }, + }, + 'RetouchHistory' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'RevisionNumber' => 'Número Revisión', + 'Rotation' => { + Description => 'Rotación', + PrintConv => { + 'Rotate 180' => 'Girado 180°', + 'Rotate 270 CW' => 'Girado 270° sentido reloj', + 'Rotate 90 CW' => 'Girado 90° sentido reloj', + 'Rotated 180' => 'Girado 180°', + 'Rotated 270 CW' => 'Girado 270° sentido reloj', + 'Rotated 90 CW' => 'Girado 90° sentido reloj', + }, + }, + 'RowInterleaveFactor' => 'Factor Interpolar Fila', + 'RowsPerStrip' => 'Número de Filas por Tira', + 'SMaxSampleValue' => 'Valor Muestra Max S', + 'SMinSampleValue' => 'Valor Muestra Min S', + 'SRAWQuality' => { + PrintConv => { + 'n/a' => 'No Aplica', + }, + }, + 'SRActive' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'SampleFormat' => 'Formato Muestra', + 'SampleRate' => 'Frecuencia muestreo', + 'SampleSize' => 'Tamaño muestra', + 'SampleStructure' => { + Description => 'Estructura Muestreo', + PrintConv => { + 'CompressionDependent' => 'Definido dentro del proceso de compresión', + 'Orthogonal4-2-2Sampling' => 'Ortogonal con las frecuencias de muestreo en el ratio de 4:2:2:(4)', + 'OrthogonalConstangSampling' => 'Ortogonal con la mismas frecuencias de muestreo relativo en cada componente', + }, + }, + 'SamplesPerPixel' => 'Número de Componentes', + 'SanyoQuality' => { + Description => 'Calidad Sanyo', + PrintConv => { + 'Fine/High' => 'Fino/Alto', + 'Fine/Low' => 'Fino/Bajo', + 'Fine/Medium' => 'Fino/Medio', + 'Fine/Medium High' => 'Fino/Medio Alto', + 'Fine/Medium Low' => 'Fino/Medio bajo', + 'Fine/Super High' => 'Fino/Super Alto', + 'Fine/Very High' => 'Fino/Muy Alto', + 'Fine/Very Low' => 'Fino/Muy bajo', + 'Normal/High' => 'Normal/Alto', + 'Normal/Low' => 'Normal/Bajo', + 'Normal/Medium' => 'Normal/Medio', + 'Normal/Medium High' => 'Normal/Medio Alto', + 'Normal/Medium Low' => 'Normal/Medio bajo', + 'Normal/Super High' => 'Normal/Super Alto', + 'Normal/Very High' => 'Normal/Muy Alto', + 'Normal/Very Low' => 'Normal/Muy bajo', + 'Super Fine/High' => 'Super Fino/Alto', + 'Super Fine/Low' => 'Super Fino/Bajo', + 'Super Fine/Medium' => 'Super Fino/Medio', + 'Super Fine/Medium High' => 'Super Fino/Medio Alto', + 'Super Fine/Medium Low' => 'Super Fino/Medio Bajo', + 'Super Fine/Super High' => 'Super Fino/Super Alto', + 'Super Fine/Very High' => 'Super Fino/Muy Alto', + 'Super Fine/Very Low' => 'Super Fino/Muy Bajo', + }, + }, + 'SanyoThumbnail' => 'Miniatura Sanyo', + 'Saturation' => { + Description => 'Saturación', + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'None' => 'Ninguno', + 'None (B&W)' => 'Ninguna (N&B)', + 'Normal' => 'Estándar', + 'Vivid' => 'Vívido', + }, + }, + 'ScanImageEnhancer' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ScanningDirection' => { + Description => 'Dirección Escaneo', + PrintConv => { + 'Bottom-Top, L-R' => 'Abajo a arriba, izquierda a derecha', + 'Bottom-Top, R-L' => 'Abajo a arriba, derecha a izquierda', + 'L-R, Bottom-Top' => 'Izquierda a derecha, abajo a arriba', + 'L-R, Top-Bottom' => 'Izquierda a derecha, arriba a abajo', + 'R-L, Bottom-Top' => 'Derecha a izquierda, abajo a arriba', + 'R-L, Top-Bottom' => 'Derecha a izquierda, arriba a abajo', + 'Top-Bottom, L-R' => 'Arriba a abajo, izquierda a derecha', + 'Top-Bottom, R-L' => 'Arriba a abajo, derecha a izquierda', + }, + }, + 'Scene' => 'Escena', + 'SceneCaptureType' => { + Description => 'Tipo Captura Escena', + PrintConv => { + 'Landscape' => 'Paisaje', + 'Night' => 'Escena nocturna', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + }, + }, + 'SceneMode' => { + Description => 'Selección de escena', + PrintConv => { + '3D Sweep Panorama' => '3D', + 'Anti Motion Blur' => 'Anti movimiento', + 'Beach' => 'Playa', + 'Cont. Priority AE' => 'AE prioridad cont.', + 'Fireworks' => 'Fuegos Artificiales', + 'Food' => 'Comida', + 'Handheld Night Shot' => 'Toma noct. manual', + 'Indoor' => 'Interior', + 'Landscape' => 'Paisaje', + 'Night Portrait' => 'Retrato noct.', + 'Night Scene' => 'Vista nocturna', + 'Night View/Portrait' => 'Vista/retrato nocturno', + 'Off' => 'Desactivado', + 'Panorama' => 'Panoramica', + 'Portrait' => 'Retrato', + 'Snow' => 'Nieve', + 'Sports' => 'Acción deportiva', + 'Standard' => 'Estándar', + 'Sunset' => 'Puesta sol', + 'Sweep Panorama' => 'Barrido panorámico', + 'Underwater' => 'Subacuatica', + 'Vivid' => 'Vívido', + }, + }, + 'SceneModeUsed' => { + PrintConv => { + 'Beach' => 'Playa', + 'Fireworks' => 'Fuegos Artificiales', + 'Landscape' => 'Paisaje', + 'Panorama' => 'Panoramica', + 'Portrait' => 'Retrato', + 'Snow' => 'Nieve', + }, + }, + 'SceneSelect' => { + Description => 'Selección Escena', + PrintConv => { + 'Lamp' => 'Lámpara', + 'Night' => 'Noche', + 'Off' => 'Apagado', + 'Sport' => 'Deporte', + 'User 1' => 'Usuario 1', + 'User 2' => 'Usuario 2', + }, + }, + 'SceneType' => { + Description => 'Tipo Escena', + PrintConv => { + 'Directly photographed' => 'Imagen fotografiada directamente', + }, + }, + 'Security' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'SecurityClassification' => { + Description => 'Clasificación Seguridad', + PrintConv => { + 'Confidential' => 'Confidencial', + 'Restricted' => 'Restringida', + 'Secret' => 'Secreta', + 'Top Secret' => 'Alto secreto', + 'Unclassified' => 'Sin clasificar', + }, + }, + 'SelectableAFPoint' => 'Punto AF seleccionable', + 'SelfTimer' => { + Description => 'Temporizador Automático', + PrintConv => { + 'Off' => 'Apagado', + 'On' => 'Encendido', + }, + }, + 'SelfTimerMode' => 'Modo Automático', + 'SensingMethod' => { + Description => 'Método Sensor', + PrintConv => { + 'Color sequential area' => 'Sensor de color secuencial', + 'Color sequential linear' => 'Sensor lineal de color secuencial', + 'Monochrome area' => 'Sensor monocromo', + 'Monochrome linear' => 'Sensor lineal monocromo', + 'Not defined' => 'No definido', + 'One-chip color area' => 'Sensor monochip de color', + 'Three-chip color area' => 'Sensor tres chips de color', + 'Trilinear' => 'Sensor trilineal', + 'Two-chip color area' => 'Sensor bichip de color', + }, + }, + 'SensitivityType' => { + PrintConv => { + 'Unknown' => 'Desconocido', + }, + }, + 'SequenceShotInterval' => { + Description => 'Intervalo Disparo Secuencial', + PrintConv => { + '10 frames/s' => '10 cuadros/s', + '15 frames/s' => '15 cuadros/s', + '20 frames/s' => '20 cuadros/s', + '5 frames/s' => '5 cuadros/s', + }, + }, + 'SequentialShot' => { + Description => 'Disparo Secuencial', + PrintConv => { + 'Adjust Exposure' => 'Ajustar Exposición', + 'Best' => 'Mejor', + 'None' => 'Ninguno', + 'Standard' => 'Estándar', + }, + }, + 'SerialNumber' => 'Número Serie', + 'ServiceIdentifier' => 'Identificador Servicio', + 'ShadingCompensation' => 'Compensación de Sombreado', + 'ShadingCompensation2' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'Shadow' => 'Sombrío', + 'ShadowScale' => 'Escala Sombrío', + 'ShakeReduction' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'Sharpening' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'Sharpness' => { + Description => 'Nitidez', + PrintConv => { + 'Hard' => 'Fuerte', + 'Normal' => 'Estándar', + 'Sharp' => 'Nitido', + 'Soft' => 'Suave', + }, + }, + 'SharpnessFrequency' => { + PrintConv => { + 'High' => 'Alto', + 'Highest' => 'Muy Alto', + 'Low' => 'Bajo', + 'Lowest' => 'Mas bajo', + 'Standard' => 'Estándar', + 'n/a' => 'No Aplica', + }, + }, + 'ShootingMode' => { + Description => 'Modo de Disparo', + PrintConv => { + 'Beach' => 'Playa', + 'Fireworks' => 'Fuegos Artificiales', + 'Food' => 'Comida', + 'Portrait' => 'Retrato', + 'Snow' => 'Nieve', + 'Underwater' => 'Subacuatica', + }, + }, + 'ShortDocumentID' => 'ID Corta del Documento', + 'Shutter-AELock' => 'Disparador Bloqueo AE', + 'ShutterCount' => 'Contador de disparos', + 'ShutterReleaseButtonAE-L' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ShutterReleaseNoCFCard' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'ShutterSpeed' => 'Tiempo de Exposición', + 'ShutterSpeedValue' => 'Velocidad Obturación', + 'Signature_Name' => 'Firma', + 'SimilarityIndex' => 'Ãndice de Similitudes', + 'SingleFrameBracketing' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'Site' => 'Sitio', + 'SlideShow' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'SlowShutter' => { + PrintConv => { + 'None' => 'Ninguno', + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'SoftSkinEffect' => { + PrintConv => { + 'Low' => 'Bajo', + }, + }, + 'Software' => 'Programa Utilizado', + 'SoftwareVersion' => 'Versión Software', + 'SonyImageSize' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'SonyQuality' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'Source' => 'Fuente', + 'SpatialFrequencyResponse' => 'Respuesta Frecuencia Espacial', + 'SpecialEffectsOpticalFilter' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'SpecialInstructions' => 'Instrucciones', + 'SpecialMode' => 'Modo Especial', + 'SpectralSensitivity' => 'Sensibilidad Espectral', + 'Speed' => { + PrintConv => { + 'Fast' => 'Rápido', + }, + }, + 'SpotMeteringMode' => { + PrintConv => { + 'AF Point' => 'Punto AF', + 'Center' => 'Centro', + }, + }, + 'State' => 'Estado', + 'StreamColor' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'StreamType' => { + PrintConv => { + 'Binary' => 'Binario', + 'File Transfer' => 'Transferencia Archivo', + }, + }, + 'StripByteCounts' => 'Bytes por Tira Comprimida', + 'StripOffsets' => 'Localización Datos Imagen', + 'Sub-location' => 'Localización', + 'SubSecTime' => 'Subsegundos DateTime', + 'SubSecTimeDigitized' => 'Subsegundos DateTimeDigitized', + 'SubSecTimeOriginal' => 'Subsegundos DateTimeOriginal', + 'SubTileBlockSize' => 'Tamaño Bloque Submosaico', + 'SubfileType' => 'Nuevo Tipo Subarchivo', + 'SubimageColor' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'Subject' => 'Sujeto', + 'SubjectArea' => 'Zona Sujeto', + 'SubjectDistance' => 'Distancia Sujeto', + 'SubjectDistanceRange' => { + Description => 'Intervalo Distancia Sujeto', + PrintConv => { + 'Close' => 'Vista cercana', + 'Distant' => 'Vista alejada', + 'Unknown' => 'Desconocido', + }, + }, + 'SubjectLocation' => 'Localización Sujeto', + 'SubjectProgram' => { + PrintConv => { + 'None' => 'Ninguno', + 'Portrait' => 'Retrato', + }, + }, + 'SubjectReference' => 'Código Sujeto', + 'Subsystem' => { + PrintConv => { + 'Unknown' => 'Desconocido', + }, + }, + 'Subtitle' => 'Subtitulo', + 'SubtitleDescription' => 'Descripción Subtitulo', + 'SuperimposedDisplay' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'SupplementalCategories' => 'Categoría Suplementaria', + 'SupplementalType' => { + Description => 'Tipo Suplemento', + PrintConv => { + 'Main Image' => 'No Definido', + 'Rasterized Caption' => 'Título Rasterizado', + 'Reduced Resolution Image' => 'Imagen resolución reducida', + }, + }, + 'SweepPanoramaSize' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'SwitchToRegisteredAFPoint' => 'Conmutación de Punto AF registrado', + 'SynchronizedLyricsType' => { + PrintConv => { + 'Other' => 'Otro', + }, + }, + 'T4Options' => 'Opciones T4', + 'T6Options' => 'Opciones T6', + 'TIFF-EPStandardID' => 'ID Estándar TIFF/EP', + 'Tagged' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'TargetPrinter' => 'Impresora Objetivo', + 'Technology' => { + Description => 'Tecnología', + PrintConv => { + 'Active Matrix Display' => 'Pantalla Matriz Activa', + 'Cathode Ray Tube Display' => 'Pantalla Tubo Rayos Catódicos', + 'Digital Camera' => 'Cámara Digital', + 'Dye Sublimation Printer' => 'Impresora Sublimación', + 'Electrophotographic Printer' => 'Impresora Electrofotográfica (Laser)', + 'Electrostatic Printer' => 'Impresora Electrostática', + 'Film Scanner' => 'Escaner Película', + 'Film Writer' => 'Impresora Película', + 'Flexography' => 'Flexografía', + 'Gravure' => 'Grabado', + 'Ink Jet Printer' => 'Impresora Inyección Tinta', + 'Offset Lithography' => 'Litografía Offset', + 'Passive Matrix Display' => 'Pantalla Matriz Pasiva', + 'Photo Image Setter' => 'Marco Foto', + 'Photographic Paper Printer' => 'Impresora Papel Fotográfico', + 'Projection Television' => 'Televisión Proyección', + 'Reflective Scanner' => 'Escaner Reflectivo', + 'Silkscreen' => 'Pantalla Sedosa', + 'Thermal Wax Printer' => 'Impresora Cera Termal', + 'Video Camera' => 'Videocámara', + 'Video Monitor' => 'Monitor Video', + }, + }, + 'Teleconverter' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'Text' => 'Texto', + 'TextStamp' => { + PrintConv => { + 'On' => 'Activado', + }, + }, + 'Thresholding' => 'Umbral', + 'ThumbnailImage' => 'Miniatura', + 'ThumbnailImageSize' => 'Tamaño de la Vista en Miniatura', + 'TileByteCounts' => 'Número Byte Elemento', + 'TileDepth' => 'Ancho Elemento', + 'TileLength' => 'Largo Elemento', + 'TileOffsets' => 'Offsets Elemento', + 'TileWidth' => 'Ancho Elemento', + 'TimeCreated' => 'Hora Creación', + 'TimeScaleParamsQuality' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'TimeSent' => 'Hora Envío', + 'TimeSignature' => { + PrintConv => { + 'other' => 'Otro', + }, + }, + 'TimeStamp' => 'Marca de Tiempo', + 'TimeStamp1' => 'Marca de Tiempo 1', + 'TimeZoneOffset' => 'Offset Zona Horaria', + 'Title' => 'Título', + 'ToneCurve' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'ToneCurveActive' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'ToningEffect' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'ToningEffectMonochrome' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + }, + }, + 'ToningEffectUnknown' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + }, + }, + 'ToningEffectUserDef1' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + }, + }, + 'ToningEffectUserDef2' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + }, + }, + 'ToningEffectUserDef3' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Ninguno', + }, + }, + 'ToolName' => 'Nombre Herramienta', + 'ToolVersion' => 'Versión Herramienta', + 'TotalFrames' => 'Total Imagenes', + 'Track' => 'Pista', + 'TrackCreateDate' => 'Fecha creación Track', + 'TrackDefault' => 'Pista Defecto', + 'TrackForced' => 'Pista Forzada', + 'TrackHeaderVersion' => 'Versión cabecera Pista', + 'TrackID' => 'ID Pista', + 'TrackName' => 'Nombre Pista', + 'TrackNumber' => 'Número Pista', + 'TrackType' => 'Tipo Pista', + 'TrackUsed' => 'Pista utilizada', + 'Tracks' => 'Pistas', + 'TransferFunction' => 'Función Transferencia', + 'TransferRange' => 'Intervalo Transferencia', + 'Transformation' => { + Description => 'Transformación', + PrintConv => { + 'Mirror horizontal' => 'Invertir horizontal', + 'Mirror horizontal and rotate 270 CW' => 'Invertir horizontal y rotar 270° sentido reloj', + 'Mirror horizontal and rotate 90 CW' => 'Invertir horizontal y rotar 90° sentido reloj', + 'Mirror vertical' => 'Invertir vertical', + 'Rotate 180' => 'Girado 180°', + 'Rotate 270 CW' => 'Girado 270° sentido reloj', + 'Rotate 90 CW' => 'Girado 90° sentido reloj', + }, + }, + 'TransmissionReference' => 'Referencia de Transmisión', + 'TransparencyIndicator' => 'Indicador Transparencia', + 'TrapIndicator' => 'Indicador Tampa', + 'Type' => 'Tipo', + 'Uncompressed' => { + Description => 'Sin Comprimir', + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'UncompressedSize' => 'Tamaño Descomprimido', + 'UniqueCameraModel' => 'Modelo Cámara Unico', + 'UniqueDocumentID' => 'ID de Documento Única', + 'UniqueFileIdentifier' => 'Identificador Unico Archivo', + 'UniqueObjectName' => 'Nombre Único de Objeto', + 'Unknown' => 'Desconocido', + 'Unsharp1Color' => { + PrintConv => { + 'Green' => 'Verde', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'Unsharp2Color' => { + PrintConv => { + 'Green' => 'Verde', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'Unsharp3Color' => { + PrintConv => { + 'Green' => 'Verde', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'Unsharp4Color' => { + PrintConv => { + 'Green' => 'Verde', + 'Red' => 'Rojo', + 'Yellow' => 'Amarillo', + }, + }, + 'UnsharpMask' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'Urgency' => { + Description => 'Urgencia', + PrintConv => { + '0 (reserved)' => '0 (reservado para futuro uso)', + '1 (most urgent)' => '1 (más urgente)', + '5 (normal urgency)' => '5 (urgencia normal)', + '8 (least urgent)' => '8 (menos urgente)', + '9 (user-defined priority)' => '9 (reservado para futuro uso)', + }, + }, + 'UserComment' => 'Comentarios Usuario', + 'UserDef1PictureStyle' => { + PrintConv => { + 'Landscape' => 'Paisaje', + 'Monochrome' => 'Monocromo', + 'Neutral' => 'Neutro', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + }, + }, + 'UserDef2PictureStyle' => { + PrintConv => { + 'Landscape' => 'Paisaje', + 'Monochrome' => 'Monocromo', + 'Neutral' => 'Neutro', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + }, + }, + 'UserDef3PictureStyle' => { + PrintConv => { + 'Landscape' => 'Paisaje', + 'Monochrome' => 'Monocromo', + 'Neutral' => 'Neutro', + 'Portrait' => 'Retrato', + 'Standard' => 'Estándar', + }, + }, + 'VR_0x66' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'ValidAFPoints' => 'Puntos AF validos', + 'Version' => 'Versión PrintIM', + 'VersionYear' => 'Año versión', + 'VibrationReduction' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'VideoAlphaMode' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'VideoCardGamma' => 'Tarjeta Video Gamma', + 'VideoCompressor' => 'Video Compresor', + 'VideoFieldOrder' => { + PrintConv => { + 'Lower' => 'Inferior', + 'Progressive' => 'Progresivo', + 'Upper' => 'Superior', + }, + }, + 'VideoHeight' => 'Altura Video', + 'VideoPixelDepth' => { + PrintConv => { + '16-bit integer' => 'Entero 16-bits', + '24-bit integer' => 'Entero 24-bits', + '32-bit float' => 'Flotante 32-bits', + '32-bit integer' => 'Entero 32-bits', + '8-bit integer' => 'Entero 8-bits', + 'Other' => 'Otro', + }, + }, + 'VideoQuality' => { + PrintConv => { + 'Low' => 'Bajo', + 'Standard' => 'Estándar', + }, + }, + 'VideoWidth' => 'Ancho Video', + 'ViewCompressionFactor' => 'Ver Factor de compresión', + 'ViewfinderWarning' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ViewfinderWarnings' => { + PrintConv => { + 'Monochrome' => 'Monocromo', + }, + }, + 'ViewingCondDesc' => 'Descripción en Condiciones de Visión', + 'ViewingCondIlluminant' => 'Iluminación en Condiciones de Visión', + 'ViewingCondIlluminantType' => 'Tipo Iluminación en Condiciones de Visión', + 'ViewingCondSurround' => 'Entorno en Condiciones de Visión', + 'ViewingConditions' => 'Iluminación en Condiciones de Visión', + 'VignetteControl' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'VoiceMemo' => { + Description => 'Notas Voz', + PrintConv => { + 'Off' => 'Apagado', + 'On' => 'Encendido', + }, + }, + 'WBAdjLighting' => { + PrintConv => { + 'None' => 'Ninguno', + }, + }, + 'WBBracketMode' => { + PrintConv => { + 'Off' => 'Desactivado', + }, + }, + 'WBFineTuneActive' => { + PrintConv => { + 'Yes' => 'Si', + }, + }, + 'WCSProfiles' => 'Perfil Sistema Color Windows', + 'WhiteBalance' => { + Description => 'Balance de Blancos', + PrintConv => { + 'Auto' => 'Automático', + 'Black & White' => 'Monocromo', + 'Cloudy' => 'Nublado', + 'Color Temperature/Color Filter' => 'Temperatura de color / Filtro de color', + 'Cool White Fluorescent' => 'Fluorescente blanco frío', + 'Custom' => 'Personalizado', + 'Custom 1' => 'Personalizado 1', + 'Custom 2' => 'Personalizado 2', + 'Custom 3' => 'Personalizado 3', + 'Custom 4' => 'Personalizado 4', + 'Day White Fluorescent' => 'Fluorescente blanco de día', + 'Daylight' => 'Luz de día', + 'Daylight Fluorescent' => 'Fluorescente luz de día', + 'Fluorescent' => 'Flourescente', + 'Manual' => 'Equilibrio del blanco manual', + 'Manual Temperature (Kelvin)' => 'Temperatura Manual (Kelvin)', + 'Shade' => 'Sombrío', + 'Tungsten' => 'Tungsteno', + 'Underwater' => 'Subacuatica', + 'Unknown' => 'Desconocido', + 'Warm White Fluorescent' => 'Fluorescente blanco cálido', + 'White Fluorescent' => 'Fluorescente blanco', + }, + }, + 'WhiteBalanceAdj' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'WhiteBalanceBracketing' => { + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Bajo', + }, + }, + 'WhiteLevel' => 'Nivel Blanco', + 'WhitePoint' => 'Cromaticidad Punto Blanco', + 'WideRange' => { + Description => 'Intervalo Extendido', + PrintConv => { + 'Off' => 'Apagado', + 'On' => 'Encendido', + }, + }, + 'WidthResolution' => 'Resolución Imagen Horizontal', + 'Writer' => 'Escritor', + 'Writer-Editor' => 'Título/Descripción Escritor', + 'X3FillLight' => 'Luz Relleno X3', + 'XClipPathUnits' => 'Unidades Camino Fragmento X', + 'XMP' => 'Metadatos XMP', + 'XPosition' => 'Posición X', + 'XResolution' => 'Resolución Imagen Horizontal', + 'YCbCrCoefficients' => 'Coeficientes de Matriz de Tranformación de Espacio de Color', + 'YCbCrPositioning' => { + Description => 'Posicionamiento Y y C', + PrintConv => { + 'Centered' => 'Centrado', + 'Co-sited' => 'Vecino', + }, + }, + 'YCbCrSubSampling' => 'Ratio Submuestreo de Y a C', + 'YClipPathUnits' => 'Unidades Camino Fragmento Y', + 'YPosition' => 'Posición Y', + 'YResolution' => 'Resolución Imagen Vertical', + 'Year' => 'Año', + 'YearCreated' => 'Año Creación', + 'ZipCompressedSize' => 'Zip Tamaño Comprimido', + 'ZipCompression' => { + Description => 'Compresión Zip', + PrintConv => { + 'None' => 'Ninguno', + 'Reduced with compression factor 1' => 'Reducido con factor de compresión 1', + 'Reduced with compression factor 2' => 'Reducido con factor de compresión 2', + 'Reduced with compression factor 3' => 'Reducido con factor de compresión 3', + 'Reduced with compression factor 4' => 'Reducido con factor de compresión 4', + }, + }, + 'ZipUncompressedSize' => 'Zip Tamaño Descomprimido', + 'ZoneMatching' => { + Description => 'Zone matching', + PrintConv => { + 'High Key' => 'Alto', + 'ISO Setting Used' => 'Desactivado', + 'Low Key' => 'Bajo', + }, + }, + 'ZoneMatchingMode' => { + PrintConv => { + 'Standard' => 'Estándar', + }, + }, + 'ZoneMatchingOn' => { + PrintConv => { + 'Off' => 'Desactivado', + 'On' => 'Activado', + }, + }, + 'ZoomPos' => 'Posición Zoom', +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::es.pm - ExifTool Spanish language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke, Santiago del BrE<iacute>o GonzE<aacute>lez and Emilio +Sancha for providing this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/fi.pm b/ExifTool/lib/Image/ExifTool/Lang/fi.pm new file mode 100644 index 0000000..7d5b2ee --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/fi.pm @@ -0,0 +1,2896 @@ +#------------------------------------------------------------------------------ +# File: fi.pm +# +# Description: ExifTool Finnish language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::fi; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.04'; + +%Image::ExifTool::Lang::fi::Translate = ( + 'AEAperture' => 'AE-aukko', + 'AEBBracketValue' => 'AEB-haarukointiarvo', + 'AEBXv' => 'AEB-valotuksen korjaus', + 'AEExposureTime' => 'AE-valotusaika', + 'AEInfo' => 'Automaattivalotustiedot', + 'AELock' => { + Description => 'AE-lukitus', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'AELockButton' => 'AE-lukituspainike', + 'AEMeteringMode' => 'AE-valotuksen mittaustapa', + 'AEMeteringSegments' => 'AE-mittaussegmentit', + 'AEProgramMode' => 'AE-ohjelmatapa', + 'AESetting' => 'AE-asetus', + 'AEXv' => 'AE-valotuksen korjaus', + 'AF-CPrioritySelection' => 'AF-C -esivalinnat', + 'AF-OnForMB-D10' => 'AF-ON -painike (MB-D10)', + 'AF-SPrioritySelection' => 'AF-S -esivalinnat', + 'AFActivation' => 'AF-aktivointi', + 'AFAdjustment' => 'AF-säätö', + 'AFAperture' => 'AF-aukko', + 'AFArea' => 'AF-alue', + 'AFAreaHeight' => 'AF-alueen korkeus', + 'AFAreaIllumination' => 'AF-alueen valaisu', + 'AFAreaMode' => 'AF-aluetarkennus', + 'AFAreaModeSetting' => 'AF-aluemittaustavan asetus', + 'AFAreaWidth' => 'AF-alueen leveys', + 'AFAreas' => 'AF-alueet', + 'AFAssist' => 'AF-apu', + 'AFAssistLamp' => { + PrintConv => { + 'Disabled and Not Required' => 'Deaktivoitu eikä tarpeen', + 'Disabled but Required' => 'Deaktivoitu mutta tarpeen', + 'Enabled but Not Used' => 'Aktivoitu muttei käytetty', + 'Fired' => 'Lauennut', + }, + }, + 'AFDefocus' => 'AF-defokus (poikkeama polttopisteestä)', + 'AFFineTune' => 'AF-hienosäätö', + 'AFImageHeight' => 'AF-kuvan korkeus', + 'AFImageWidth' => 'AF-kuvan leveys', + 'AFInfo' => 'Automaattitarkennustiedot', + 'AFInfo2' => 'AF-tiedot', + 'AFInfo2Version' => 'AF-tietojen versio', + 'AFIntegrationTime' => 'AF-integrointiaika', + 'AFMode' => 'AF-muoto', + 'AFPoint' => { + Description => 'AF-piste', + PrintConv => { + 'Bottom Center' => 'Alhaalla keskellä', + 'Bottom Left' => 'Alavasemmalla', + 'Bottom Right' => 'Alaoikealla', + 'Center' => 'Keskellä', + 'Center Left' => 'Keskellä vasemmalla', + 'Center Right' => 'Keskellä oikealla', + 'Far Left/Right of Center' => 'Kaukana vasemmalla/keskioikealla', + 'Far Left/Right of Center/Bottom' => 'Kaukana vasemmalla/keskioikealla/alhaalla', + 'Near Left/Right of Center' => 'Hieman vasemmalla/keskioikealla', + 'Near Upper/Left' => 'Hieman ylhäällä/vasemmalla', + 'Top Center' => 'Ylhäällä keskellä', + 'Top Left' => 'Ylävasemmalla', + 'Top Near-left' => 'Hieman ylävasemmalla', + 'Top Near-right' => 'Hieman yläoikealla', + 'Top Right' => 'Yläoikealla', + 'Upper Left' => 'Ylävasemmalla', + 'Upper Right' => 'Yläoikealla', + }, + }, + 'AFPointIllumination' => 'AF -pistevalaisu', + 'AFPointMode' => 'AF-pistetapa', + 'AFPointSelected' => { + Description => 'Valittu AF-piste', + PrintConv => { + 'Auto' => 'Automaattinen', + 'Automatic Tracking AF' => 'Automaattiseuranta-AF', + 'Bottom' => 'Alas', + 'Center' => 'Keskelle', + 'Face Detect AF' => 'Kasvojentunnistus-AF', + 'Fixed Center' => 'Kiinteästi keskustaan', + 'Left' => 'Vasemmalle', + 'Lower-left' => 'Alavasemmalle', + 'Lower-right' => 'Alaoikealle', + 'Mid-left' => 'Keskivasemmalle', + 'Mid-right' => 'Keskioikealle', + 'Right' => 'Oikealle', + 'Top' => 'Ylös', + 'Upper-left' => 'Ylävasemmalle', + 'Upper-right' => 'Yläoikealle', + }, + }, + 'AFPointSelected2' => 'Valittu AF-piste 2', + 'AFPointSelection' => 'AF-pistevalinnat', + 'AFPoints' => 'AF-pisteet', + 'AFPointsInFocus' => { + Description => 'Tarkennuksen AF-pisteet', + PrintConv => { + 'Bottom-center' => 'Alakeski', + 'Bottom-left' => 'Alavasen', + 'Bottom-right' => 'Alaoikea', + 'Center' => 'Keski', + 'Fixed Center or Multiple' => 'Kiinteästi keskellä tai useita', + 'Left' => 'Vasen', + 'None' => 'Ei yhtään', + 'Right' => 'Oikea', + 'Top-center' => 'Yläkeski', + 'Top-left' => 'Yläoikea', + 'Top-right' => 'Yläoikea', + }, + }, + 'AFPointsSelected' => 'Valitut AF-pisteet', + 'AFPointsUsed' => 'Käytetyt AF-pisteet', + 'AFPredictor' => 'Ennakoiva AF', + 'AFResponse' => 'Käytetty AF', + 'AFResult' => 'AF-tulos', + 'AFSearch' => { + Description => 'AF-haku', + PrintConv => { + 'Not Ready' => 'Ei valmis', + 'Ready' => 'Valmis', + }, + }, + 'ARMIdentifier' => 'ARM-tunnistin', + 'ARMVersion' => 'ARM-versio', + 'AToB0' => 'A - B0', + 'AToB1' => 'A - B1', + 'AToB2' => 'A - B2', + 'AccessoryType' => 'Lisälaitetyyppi', + 'ActionAdvised' => { + Description => 'Suositettava toiminto', + PrintConv => { + 'Object Append' => 'Objektiliitäntä', + 'Object Kill' => 'Objektin tuhonta', + 'Object Reference' => 'Objektiviite', + 'Object Replace' => 'Objektin korvaus', + 'Ojbect Append' => 'Objektiliitäntä', + }, + }, + 'ActiveArea' => 'Aktiivialue', + 'ActiveD-Lighting' => 'Aktiivinen D-Lighting', + 'ActiveD-LightingMode' => 'Aktivoitu D-Lighting-tapa', + 'AdjustmentMode' => 'Säätötapa', + 'AdvancedRaw' => 'Edistynyt RAW', + 'AdvancedSceneMode' => { + Description => 'Edistynyt näkymätapa', + PrintConv => { + 'Auto' => 'Automaattinen', + 'Creative' => 'Luova', + 'Indoor/Architecture/Objects/HDR B&W' => 'Sisällä/arkkitehtuuri/kohteet', + 'Normal' => 'Normaali', + 'Outdoor/Illuminations/Flower/HDR Art' => 'Ulkona/valaistukset/kukka', + }, + }, + 'AlphaByteCount' => 'Alfa-tavujen määrä', + 'AlphaChannelsNames' => 'Alfa-kanavien nimet', + 'AlphaDataDiscard' => { + Description => 'Alfa-data hylätty', + PrintConv => { + 'Flexbits Discarded' => 'FlexBits hylätty', + 'Full Resolution' => 'Täysi resoluutio', + 'HighPass Frequency Data Discarded' => 'Ylipäästötaajuusdata hylätty', + 'Highpass and LowPass Frequency Data Discarded' => 'Yli- ja alipäästötaajuusdata hylätty', + }, + }, + 'AlphaOffset' => 'Alfa-siirtymä', + 'AnalogBalance' => 'Analoginen tasapaino', + 'AntiAliasStrength' => 'Kameran anti-alias -suotimen suhteellinen vahvuus', + 'Aperture' => 'Aukko', + 'ApertureRingUse' => 'Himmenninrenkaan käyttö', + 'ApertureValue' => 'Aukkoarvo', + 'ApplicationRecordVersion' => 'Sovellustietueen versio', + 'ArtFilter' => 'Taidesuodin', + 'Artist' => 'Kuvan luonut henkilö', + 'AsShotICCProfile' => 'Kuin kuvatessa -ICC-profiili', + 'AsShotNeutral' => 'Kuin kuvatessa -neutraali', + 'AsShotPreProfileMatrix' => 'Kuin kuvatessa -pre-profiilimatriisi', + 'AsShotProfileName' => 'Kuin kuvatessa -profiilin nimi', + 'AsShotWhiteXY' => 'Kuin kuvatessa -valkoinen X-Y', + 'Audio' => { + Description => 'Ääni', + PrintConv => { + 'No' => 'Ei', + 'Yes' => 'Kyllä', + }, + }, + 'AudioDuration' => 'Audion kesto', + 'AudioOutcue' => 'Audion lopetus', + 'AudioSamplingRate' => 'Audion näytetaajuus', + 'AudioSamplingResolution' => 'Audion näyteresoluutio', + 'AudioType' => { + Description => 'Audiotyyppi', + PrintConv => { + 'Mono Actuality' => 'Aktuaalisuus (monoaudio (1 kanava))', + 'Mono Music' => 'Musiikki, itsensä välittämä (monoaudio (1 kanava))', + 'Mono Question and Answer Session' => 'Kysymys- ja vastausistunto (monoaudio (1 kanava))', + 'Mono Raw Sound' => 'Raakaääni (monoaudio (1 kanava))', + 'Mono Response to a Question' => 'Vastaus kysymykseen (monoaudio (1 kanava))', + 'Mono Scener' => 'Kohtaus (monoaudio (1 kanava))', + 'Mono Voicer' => 'Ihmisääni (monoaudio (1 kanava))', + 'Mono Wrap' => 'Pakkaus (monoaudio (1 kanava))', + 'Stereo Actuality' => 'Aktuaalisuus (stereoaudio (2 kanavaa))', + 'Stereo Music' => 'Musiikki, itsensä välittämä (stereomono (2 kanavaa))', + 'Stereo Question and Answer Session' => 'Kysymys- ja vastausistunto (stereoaudio (2 kanavaa))', + 'Stereo Raw Sound' => 'Raakaääni (stereoaudio (2 kanavaa))', + 'Stereo Response to a Question' => 'Vastaus kysymykseen (stereoaudio (2 kanavaa))', + 'Stereo Scener' => 'Kohtaus (stereoaudio (2 kanavaa))', + 'Stereo Voicer' => 'Ihmisääni (stereoaudio (2 kanavaa))', + 'Stereo Wrap' => 'Pakkaus (stereoaudio (2 kanavaa))', + 'Text Only' => 'Vain teksti (ei objektidataa)', + }, + }, + 'Author' => 'Tekijä', + 'AutoAperture' => 'Himmenninrengas A:ssa', + 'AutoBracket' => 'Automaattihaarukointi', + 'AutoBracketSet' => 'Haarukointisarja', + 'AutoBracketing' => { + Description => 'Automaattinen haarukointi', + PrintConv => { + 'No flash & flash' => 'Ei salamaa & Salama', + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'AutoBracketingSet' => 'Haarukointisarja', + 'AutoDistortionControl' => 'Automaattinen vääristymäkorjaus', + 'AutoExposureBracketing' => 'Automaattinen valotuksen haarukointi', + 'AutoFP' => 'Automaattinen FP', + 'AutoFocus' => { + Description => 'Automaattitarkennus', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'AutoISO' => { + Description => 'Automaatti-ISO', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'AutoRedEye' => 'Automaattinen punasilmien esto', + 'AutoRotate' => 'Automaattinen kuvankierto', + 'AuxiliaryLens' => 'Lisälinssi', + 'AvApertureSetting' => 'Av-aukkoasetus', + 'BToA0' => 'B - A0', + 'BToA1' => 'B - A1', + 'BToA2' => 'B - A2', + 'BWMode' => { + Description => 'M/V-muoto', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'BackgroundColor' => 'Taustaväri', + 'BackgroundColorIndicator' => 'Taustavärin ilmaisin', + 'BackgroundColorValue' => 'Taustavärin arvo', + 'BadFaxLines' => 'Huonot faksirivit', + 'BaseExposureCompensation' => 'Valotuksen perussäätö', + 'BaseISO' => 'Perus-ISO', + 'BaselineExposure' => 'Valotuksen perusta', + 'BaselineNoise' => 'Kohinan perusta', + 'BaselineSharpness' => 'Terävyyden perusta', + 'BatteryLevel' => 'Pariston varaus', + 'BatteryOrder' => 'Paristojärjestys', + 'BayerGreenSplit' => 'Bayer Green Split -suodin', + 'Beep' => 'Äänimerkki', + 'BestQualityScale' => 'Parhaan laadun asteikko', + 'BestShotMode' => { + Description => 'Parhaan kuvan tapa', + PrintConv => { + 'Off' => 'Pois', + }, + }, + 'BitDepth' => 'Bittisyvyys', + 'BitsPerComponent' => 'Bittejä per komponentti', + 'BitsPerExtendedRunLength' => 'Bittejä per jatkettu jakson pituus', + 'BitsPerPixel' => 'Bittejä per pikseli', + 'BitsPerRunLength' => 'Bittejä per jakson pituus', + 'BitsPerSample' => 'Bittejä per näyte', + 'BlackLevel' => 'Mustan taso', + 'BlackLevel2' => 'Mustan taso 2', + 'BlackLevelDeltaH' => 'Mustan tason Delta H', + 'BlackLevelDeltaV' => 'Mustan tason Delta V', + 'BlackLevelRepeatDim' => 'Mustan tason toiston dimensio', + 'BlackMaskBottomBorder' => 'Mustan maskin alareuna', + 'BlackMaskLeftBorder' => 'Mustan maskin vasen reuna', + 'BlackMaskRightBorder' => 'Mustan maskin oikea reuna', + 'BlackMaskTopBorder' => 'Mustan maskin yläreuna', + 'BlackPoint' => 'Musta piste', + 'BlueBalance' => 'Sinitasapaino', + 'BlueMatrixColumn' => 'Sinisen matriisin sarake', + 'BlueTRC' => 'Sinisen toonin toistokäyrä', + 'BlueX' => 'Sininen X', + 'BlueY' => 'Sininen Y', + 'BlurWarning' => { + Description => 'Tärinävaroitus', + PrintConv => { + 'Blur Warning' => 'Tärinävaroitus', + 'None' => 'Ei mitään', + }, + }, + 'BodyFirmwareVersion' => 'Rungon laiteohjelmistoversio', + 'BorderID' => 'Kehyksen ID', + 'BorderLocation' => 'Kehyksen sijainti', + 'BorderName' => 'Kehyksen nimi', + 'BorderType' => 'Kehyksen tyyppi', + 'BordersVersion' => 'Kehysten versio', + 'BracketMode' => 'Haarukointitapa', + 'BracketSequence' => 'Haarukointijärjestys', + 'BracketShotNumber' => 'Haarukoinnin otosnumero', + 'BracketStep' => 'Haarukointiväli', + 'BracketValue' => 'Haarukointiarvo', + 'Brightness' => 'Kirkkaus', + 'BrightnessData' => 'Kirkkausdata', + 'BrightnessValue' => 'Kirkkausarvo', + 'BulbDuration' => 'Aikavalotuksen kesto', + 'BurstMode' => { + Description => 'Sarjatapa', + PrintConv => { + 'Auto Exposure Bracketing (AEB)' => 'Ääretön', + 'Infinite' => 'Ääretön', + 'Off' => 'Pois', + 'On' => 'Matala/korkea laatu', + 'Unlimited' => 'Rajaton', + }, + }, + 'By-line' => 'Tekijä', + 'By-lineTitle' => 'Tekijän ammattinimike', + 'CCDScanMode' => { + Description => 'CCD-skannaustapa', + PrintConv => { + 'Interlaced' => 'Lomitettu', + 'Progressive' => 'Progressiivinen', + }, + }, + 'CFALayout' => { + Description => 'CFA-sommitelma', + PrintConv => { + 'Even columns offset down 1/2 row' => 'Horjahtanut sommitelma A: parilliset sarakkeet siirtyneet alas 1/2 riviä', + 'Even columns offset up 1/2 row' => 'Horjahtanut sommitelma B: parilliset sarakkeet osiirtyneet ylös 1/2 riviä', + 'Even rows offset left 1/2 column' => 'Horjahtanut sommitelma D: parilliset rivit siirtyneet vasemmalle 1/2 saraketta', + 'Even rows offset right 1/2 column' => 'Horjahtanut sommitelma C: parilliset rivit siirtyneet oikealle 1/2 saraketta', + 'Rectangular' => 'Suorakulmainen (tai neliömäinen) sommitelma', + }, + }, + 'CFAPattern' => 'CFA-kuvio', + 'CFAPattern2' => 'CFA-kuvio 2', + 'CFAPlaneColor' => 'CFA-tasoväri', + 'CFARepeatPatternDim' => 'Toistuvan CFA-kuvion laajuus', + 'CLModeShootingSpeed' => 'Hidas jatkuva kuvaus (CL)', + 'CMContrast' => 'CM:n kontrasti', + 'CMExposureCompensation' => 'CM:n valotuksen säätö', + 'CMHue' => 'CM:n sävy', + 'CMMFlags' => 'CMM-liput', + 'CMSaturation' => 'CM:n värikylläisyys', + 'CMSharpness' => 'CM:n terävyys', + 'CMWhiteBalance' => 'CM:n valkotasapaino', + 'CMWhiteBalanceComp' => 'CM:n valkotasapainon säätö', + 'CMWhiteBalanceGrayPoint' => 'CM:n valkotasapainon harmaapiste', + 'CMYKEquivalent' => 'CMYK-vastaava', + 'CPUFirmwareVersion' => 'CPU-laiteohjelmistoversio', + 'CalibrationDateTime' => 'Kalibroinnin päiväysaika', + 'CalibrationIlluminant1' => { + Description => 'Valaistuksen kalibrointi 1', + PrintConv => { + 'Unknown' => 'Tuntematon', + }, + }, + 'CalibrationIlluminant2' => { + Description => 'Valaistuksen kalibrointi 2', + PrintConv => { + 'Unknown' => 'Tuntematon', + }, + }, + 'CameraCalibration1' => 'Kameran kalibrointi 1', + 'CameraCalibration2' => 'Kameran kalibrointi 2', + 'CameraCalibrationSig' => 'Kameran kalibrointitunniste', + 'CameraID' => 'Kameran ID', + 'CameraISO' => 'Kamera-ISO', + 'CameraOwner' => 'Kameran omistaja', + 'CameraParameters' => 'Kameran parametrit', + 'CameraSerialNumber' => 'Kameran rungon nro', + 'CameraSettings' => 'Kameran asetukset', + 'CameraSettingsVersion' => 'Kamera-asetusten versio', + 'CameraTemperature' => 'Kameran lämpötila', + 'CameraType' => 'Kameratyyppi', + 'CameraType2' => 'Kameratyyppi 2', + 'CanonAFInfo' => 'AF-info', + 'CanonAFInfo2' => 'AF-info (2)', + 'CanonFileInfo' => 'Tiedostoinfo', + 'CanonFileLength' => 'Tiedoston pituus', + 'CanonFirmwareVersion' => 'Laiteohjelmiston versio', + 'CanonFlags' => 'Canon-liput', + 'CanonImageType' => 'Kuvatyyppi', + 'CanonModelID' => 'Mallin ID', + 'Caption-Abstract' => 'Seloste/Kuvaus', + 'CaptureConditionsPAR' => 'PAR-kuvausolosuhteet', + 'CaptureDeviceFID' => 'Kuvauslaitteen FID', + 'CasioImageSize' => 'Casion kuvakoko', + 'Category' => 'Kategoria', + 'CellLength' => 'Kennon pituus', + 'CellWidth' => 'Kennon leveys', + 'CenterAFArea' => 'Keskialueen AF', + 'CharTarget' => 'Kirjainmerkin kohde', + 'CharacterSet' => 'Merkistö', + 'ChromaBlurRadius' => 'Kromaattisen sumeuden säde', + 'ChromaticAdaptation' => 'Kromaattinen adaptaatio', + 'Chromaticity' => 'Kromaattisuus', + 'City' => 'Kaupunki', + 'ClassifyState' => 'Luokitustila', + 'CleanFaxData' => 'Poista faksidata', + 'ClipPath' => 'Leikepolku', + 'CodedCharacterSet' => 'Koodattu merkistö', + 'ColorAberrationControl' => 'Väripoikkeaman hallinta', + 'ColorAdjustment' => 'Värien säätö', + 'ColorAdjustmentMode' => { + Description => 'Värin säätötapa', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'ColorBalance' => 'Väritasapaino', + 'ColorBalanceAdj' => 'Väritasapainon säätö', + 'ColorBalanceBlue' => 'Väritasapaino sininen', + 'ColorBalanceGreen' => 'Väritasapaino vihreä', + 'ColorBalanceRed' => 'Väritasapaino punainen', + 'ColorBalanceVersion' => 'Väritasapainon versio', + 'ColorBoostData' => 'Väritehostusdata', + 'ColorBoostLevel' => 'Väritehostustaso', + 'ColorBoostType' => 'Väritehostustyyppi', + 'ColorBooster' => 'Väritehostus', + 'ColorCalibrationMatrix' => 'Värien kalibrointimatriisitaulukko', + 'ColorCharacterization' => 'Värin luonnehdinta', + 'ColorCompensationFilter' => 'Värikorjailusuodin', + 'ColorControl' => 'Värisäätö', + 'ColorEffect' => { + Description => 'Väriefekti', + PrintConv => { + 'Black & White' => 'Musta & Valkoinen', + 'Cool' => 'Viileä', + 'Off' => 'Pois', + 'Sepia' => 'Seepia', + 'Warm' => 'Lämmin', + }, + }, + 'ColorFilter' => { + Description => 'Värisuodin', + PrintConv => { + 'Off' => 'Pois', + }, + }, + 'ColorGain' => 'Värien vahvistus', + 'ColorHue' => 'Värisävy', + 'ColorInfo' => 'Väritiedot', + 'ColorMap' => 'Värikartta', + 'ColorMatrix' => 'Värimatriisi', + 'ColorMatrix1' => 'Värimatriisi 1', + 'ColorMatrix2' => 'Värimatriisi 2', + 'ColorMatrixNumber' => 'Värimatriisin numero', + 'ColorMode' => { + Description => 'Värimuoto', + PrintConv => { + 'Autumn Leaves' => 'Syksyn lehdet', + 'B & W' => 'M & V', + 'B&W' => 'M&V', + 'Black & White' => 'Heleät värit', + 'Chrome' => 'Kellastunut', + 'Clear' => 'Selkeä', + 'Deep' => 'Syvä', + 'Evening' => 'Ilta', + 'Landscape' => 'Maisema', + 'Light' => 'Valoisa', + 'Natural' => 'Luonnollinen', + 'Natural color' => 'Luonnonvalo', + 'Natural sRGB' => 'Luonnollinen sRGB', + 'Natural+ sRGB' => 'Luonnollinen+ sRGB', + 'Neutral' => 'Neutraali', + 'Night Portrait' => 'Yön muotokuva', + 'Night Scene' => 'Yönäkymä', + 'Night View' => 'Iltanäkymä', + 'Night View/Portrait' => 'Yönäkymä', + 'Normal' => 'Normaali', + 'Off' => 'Pois', + 'Portrait' => 'Muotokuva', + 'Sepia' => 'Seepia', + 'Solarization' => 'Solarisaatio', + 'Standard' => 'Vakio', + 'Sunset' => 'Auringonlasku', + 'Vivid' => 'Heleät värit', + 'Vivid color' => 'Heleä väri', + }, + }, + 'ColorMoireReduction' => 'Värimoareen vaimennus', + 'ColorMoireReductionMode' => 'Värimoareen vaimennustapa', + 'ColorPalette' => 'Väripaletti', + 'ColorProfile' => 'Väriprofiili', + 'ColorRepresentation' => 'Väriesitys', + 'ColorReproduction' => 'Värientoisto', + 'ColorResponseUnit' => 'Värin vasteyksikkö', + 'ColorSamplersResource' => 'Värinäytteenotinresurssit', + 'ColorSequence' => 'Värien esitys', + 'ColorSpace' => { + Description => 'Väriavaruus', + PrintConv => { + 'ICC Profile' => 'ICC-profiili', + 'Uncalibrated' => 'Kalibroimaton', + 'Wide Gamut RGB' => 'Laaja Gamut RVB', + }, + }, + 'ColorTable' => 'Väritaulukko', + 'ColorTemperature' => 'Värilämpötila', + 'ColorTone' => 'Värisävy', + 'ColorToneFaithful' => 'Kohdeuskollinen värisävy', + 'ColorToneLandscape' => 'Maisemakuvan värisävy', + 'ColorToneNeutral' => 'Neutraali värisävy', + 'ColorTonePortrait' => 'Muotokuvan värisävy', + 'ColorToneStandard' => 'Vakiovärisävy', + 'ColorType' => 'Värityyppi', + 'ColorantOrder' => 'Väriainejärjestys', + 'ColorantTable' => 'Väriainetaulukko', + 'ColorimetricReference' => 'Kolorimetrinen referenssi', + 'Comment' => 'Kommentti', + 'ComponentsConfiguration' => 'Kunkin komponentin tarkoitus', + 'CompressedBitsPerPixel' => 'Kuvan pakkausmuoto', + 'CompressedImageSize' => 'Pakatun kuvan koko', + 'Compression' => 'Pakkausskeema', + 'CompressionRatio' => 'Pakkaussuhde', + 'ConnectionSpaceIlluminant' => 'Yhteystilan valaistus', + 'ConsecutiveBadFaxLines' => 'Peräkkäiset huonot faksirivit', + 'Contact' => 'Kontakti', + 'ContentLocationCode' => 'Sisällön sijaintikoodi', + 'ContentLocationName' => 'Sisällön sijaintinimi', + 'ContinuousBracketing' => 'Jatkuva haarukointi', + 'Contrast' => { + Description => 'Kontrasti', + PrintConv => { + 'Film Simulation' => 'Filminsimulaatio', + 'High' => 'Korkea', + 'Low' => 'Matala', + 'Medium High' => 'Korkeahko', + 'Medium Low' => 'Matalahko', + 'Normal' => 'Vakio', + }, + }, + 'ContrastAdjustment' => 'Kontrastin säätö', + 'ContrastCurve' => 'Kontrastikäyrä', + 'ContrastFaithful' => 'Kohdeuskollinen kontrasti', + 'ContrastLandscape' => 'Maisemakuvan kontrasti', + 'ContrastMonochrome' => 'Yksivärikuvan kontrasti', + 'ContrastNeutral' => 'Neutraali kontrasti', + 'ContrastPortrait' => 'Muotokuvan kontrasti', + 'ContrastSetting' => 'Kontrastiasetus', + 'ContrastStandard' => 'Vakiokontrasti', + 'ControlMode' => 'Ohjaustapa', + 'ConversionLens' => { + Description => 'Objektiivilisäke', + PrintConv => { + 'Macro' => 'Makro', + 'Off' => 'Pois', + 'Telephoto' => 'Tele', + 'Wide' => 'Laajakulma', + }, + }, + 'Converter' => 'Konvertteri', + 'Copyright' => 'Profiilin copyright', + 'CopyrightFlag' => 'Copyright-lippu', + 'CopyrightNotice' => 'Tekijänoikeusilmoitus', + 'CoringFilter' => 'Ydinsuodin', + 'CoringValues' => 'Ydinsuodatusarvot', + 'Country-PrimaryLocationCode' => 'ISO-maakoodi', + 'Country-PrimaryLocationName' => 'Maa', + 'CreateDate' => 'Digitaalisen datan luonnin päiväys ja aika', + 'CreationDate' => 'Luontipäivä', + 'CreativeStyle' => { + Description => 'Luova tyyli', + PrintConv => { + 'Autumn Leaves' => 'Syksyn lehdet', + 'B&W' => 'M&V', + 'Clear' => 'Selkeä', + 'Deep' => 'Syvä', + 'Landscape' => 'Maisema', + 'Light' => 'Valoisa', + 'Neutral' => 'Neutraali', + 'Night View/Portrait' => 'Yönäkymä', + 'Portrait' => 'Muotokuva', + 'Sepia' => 'Seepia', + 'Standard' => 'Vakio', + 'Sunset' => 'Auringonlasku', + 'Vivid' => 'Heleät värit', + }, + }, + 'CreatorAddress' => 'Tekijä - Osoite', + 'CreatorCity' => 'Tekijä - Kaupunki', + 'CreatorCountry' => 'Tekijä - Maa', + 'CreatorPostalCode' => 'Tekijä - Postinumero', + 'CreatorRegion' => 'Tekijä - Valtio/Provinssi', + 'CreatorWorkEmail' => 'Tekijä - email(it)', + 'CreatorWorkTelephone' => 'Tekijä - Puhelinnumero(t)', + 'CreatorWorkURL' => 'Tekijä - Web-sivusto(t)', + 'Credit' => 'Tarjoaja', + 'CropBottom' => 'Rajaus alhaalta', + 'CropData' => 'Rajausdata', + 'CropLeft' => 'Rajaus vasemmalta', + 'CropOutputHeight' => 'Rajaustuloksen korkeus', + 'CropOutputPixels' => 'Rajaustulokset pikselit', + 'CropOutputResolution' => 'Rajaustuloksen resoluutio', + 'CropOutputScale' => 'Rajaustuloksen mittasuhteet', + 'CropOutputWidth' => 'Rajaustuloksen leveys', + 'CropRight' => 'Rajaus oikealta', + 'CropScaledResolution' => 'Rajauksen skaalausresoluutio', + 'CropSourceResolution' => 'Rajauslähteen resoluutio', + 'CropTop' => 'Rajaus ylhäältä', + 'CurrentICCProfile' => 'Nykyinen ICC-profiili', + 'CurrentPreProfileMatrix' => 'Nykyinen pre-profiilimatriisi', + 'Curves' => 'Käyrät', + 'Custom1' => 'Mukautus 1', + 'Custom2' => 'Mukautus 2', + 'Custom3' => 'Mukautus 3', + 'Custom4' => 'Mukautus 4', + 'CustomRendered' => { + Description => 'Mukautettu kuvankäsittely', + PrintConv => { + 'Custom' => 'Mukautettu käsittely', + 'Normal' => 'Normaali käsittely', + }, + }, + 'CustomSaturation' => 'Mukautettu värikylläisyys', + 'D-LightingHQColorBoost' => 'D-Lighting HQ -väritehostus', + 'D-LightingHQHighlight' => 'D-Lighting HQ -valoalue', + 'D-LightingHQSelected' => 'Valittu D-Lighting HQ', + 'D-LightingHQShadow' => 'D-Lighting HQ -varjoalue', + 'D-LightingHSAdjustment' => 'D-Lighting HS -säätö', + 'D-LightingHSColorBoost' => 'D-Lighting HS -väritehostus', + 'DECPosition' => 'DEC-sijainti', + 'DNGBackwardVersion' => 'Edellinen DNG-versio', + 'DNGVersion' => 'DNG-versio', + 'DSPFirmwareVersion' => 'DSP-laiteohjelmistoversio', + 'DataDump' => 'Datadumppi', + 'DataImprint' => 'Datan sisällytys', + 'DataType' => 'Mattaus', + 'Date' => 'Päiväys', + 'DateCreated' => 'Luontipäiväys', + 'DateImprint' => 'Päiväysleima', + 'DateSent' => 'Lähetyspäivä', + 'DateStampMode' => 'Päiväysleiman muoto', + 'DateTime' => 'Päiväysaika', + 'DateTimeOriginal' => 'Alkuperäisen datan luonnin päiväys ja aika', + 'DealerIDNumber' => 'Myyjän ID-numero', + 'DefaultCropOrigin' => 'Oletusrajauksen alkuperä', + 'DefaultCropSize' => 'Oletusrajauskoko', + 'DefaultScale' => 'Oletusasteikko', + 'DelayTime' => 'Viiveaika', + 'DeletedImageCount' => 'Poistettujen kuvien määrä', + 'Description' => 'Deskriptio', + 'Destination' => 'Kohde', + 'DestinationCity' => 'Kohdepaikkakunta', + 'DestinationCityCode' => 'Kohdepaikkakoodi', + 'DestinationDST' => { + Description => 'Kohteen kesäaika (DST)', + PrintConv => { + 'No' => 'Ei', + 'Yes' => 'Kyllä', + }, + }, + 'DevelopmentDynamicRange' => 'Dynamiikka-alueen kehitys', + 'DeviceAttributes' => 'Laiteattribuutit', + 'DeviceManufacturer' => 'Laitteen valmistaja', + 'DeviceMfgDesc' => 'Laitteen valmistajan kuvaus', + 'DeviceModel' => 'Laitteen malli', + 'DeviceModelDesc' => 'Laitemallin kuvaus', + 'DeviceSettingDescription' => 'Laiteasetusten kuvaus', + 'DigitalCreationDate' => 'Digitalisointipäiväys', + 'DigitalCreationTime' => 'Digitalisointiaika', + 'DigitalDEEHighlightAdj' => 'Digitaalinen DEE -vaalean pään säätö', + 'DigitalDEEShadowAdj' => 'Digitaalinen DEE-varjonsäätö', + 'DigitalDEEThreshold' => 'Digitaalinen DEE-kynnys', + 'DigitalEffectsName' => 'Digitaalisten efektien nimi', + 'DigitalEffectsType' => 'Digitaalisten efektien tyyppi', + 'DigitalEffectsVersion' => 'Digitaalisten efektien versio', + 'DigitalGEM' => 'Digitaalinen GEM', + 'DigitalGain' => 'Digitaalinen vahvistus', + 'DigitalICE' => 'Digitaalinen ICE', + 'DigitalROC' => 'Digitaalinen ROC', + 'DigitalZoom' => 'Digitaalinen zoom', + 'DigitalZoomOn' => { + Description => 'Digitaalinen zoom', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'DigitalZoomRatio' => 'Digitaalinen zoomaussuhde', + 'DirectoryIndex' => 'Kansioindeksi', + 'DirectoryNumber' => 'Kansion numero', + 'DisplayAperture' => 'Aukon näyttö', + 'DisplayedUnitsX' => 'Vaakaresoluution yksikkö', + 'DisplayedUnitsY' => 'Pystyresoluution yksikkö', + 'DistortionCorrection' => 'Vääristymäkorjaus', + 'DocumentHistory' => 'Dokumentin historia', + 'DocumentName' => 'Dokumentin nimi', + 'DocumentNotes' => 'Dokumentin huomautukset', + 'DotRange' => 'Pisteiden määrä', + 'DriveMode' => 'Kuvaustapa', + 'DriveMode2' => 'Kuvaustapa 2', + 'DustRemovalData' => 'Pölynpoistodata', + 'DynamicAFArea' => 'Dynaamisen alueen AF', + 'DynamicRange' => { + Description => 'Dynamiikka-alue', + PrintConv => { + 'Standard' => 'Vakio', + 'Wide' => 'Laaja', + }, + }, + 'DynamicRangeExpansion' => { + Description => 'Dynamiikka-alueen laajennus', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'DynamicRangeOptimizer' => { + Description => 'Dynamiikka-alueen optimointi', + PrintConv => { + 'Advanced Auto' => 'Edistynyt automatiikka', + 'Advanced Lv1' => 'Edistynyt taso 1', + 'Advanced Lv2' => 'Edistynyt taso 2', + 'Advanced Lv3' => 'Edistynyt taso 3', + 'Advanced Lv4' => 'Edistynyt taso 4', + 'Advanced Lv5' => 'Edistynyt taso 5', + 'Off' => 'Pois', + 'Standard' => 'Vakio', + }, + }, + 'DynamicRangeSetting' => { + Description => 'Dynamiikka-alueen säätö', + PrintConv => { + 'Film Simulation' => 'Filminsimulaatio', + 'Standard (100%)' => 'Vakio (100%)', + 'Wide1 (230%)' => 'Laaja 1 (230%)', + 'Wide2 (400%)' => 'Laaja 2 (400%)', + }, + }, + 'E-DialInProgram' => 'Säätökiekko ohjelmassa', + 'EVStepInfo' => 'EV-askeltiedot', + 'EVStepSize' => 'EV-askeleen koko', + 'EVSteps' => 'EV-askeleet', + 'EasyExposureCompensation' => 'Valotuksen pikakorjaus', + 'EasyMode' => 'Helppo tapa', + 'EdgeNoiseReduction' => 'Reunakohinan vaimennus', + 'EditStatus' => 'Editoinnin tila', + 'EditTagArray' => 'Muokkaa tagiryhmää', + 'EditorialUpdate' => { + Description => 'Sisällön päivitys', + PrintConv => { + 'Additional language' => 'Lisäkielet', + }, + }, + 'EffectiveLV' => 'Tehollinen valoarvo (LV)', + 'EffectiveMaxAperture' => 'Suurin tehollinen aukko', + 'Encoder' => 'Kooderi', + 'EncodingProcess' => 'Koodausmetodi', + 'EndPoints' => 'Loppupisteet', + 'EnhanceDarkTones' => 'Tummien sävyjen parantelu', + 'Enhancement' => { + Description => 'Parantelu', + PrintConv => { + 'Blue' => 'Sininen', + 'Flesh Tones' => 'Lihanvärinen', + 'Green' => 'Vihreä', + 'Off' => 'Pois', + 'Red' => 'Punainen', + }, + }, + 'EnhancerValues' => 'Korjainarvot', + 'EnvelopeNumber' => 'Pakkauksen numero', + 'EnvelopePriority' => { + Description => 'Pakkauksen prioriteetti', + PrintConv => { + '0 (reserved)' => '0 (varattu tulevaan käyttöön)', + '1 (most urgent)' => '1 (hyvin kiireellinen)', + '5 (normal urgency)' => '5 (normaali kiireellisyys)', + '8 (least urgent)' => '8 (lievän kiireellinen)', + '9 (user-defined priority)' => '9 (käyttäjän määrittämä prioriteetti)', + }, + }, + 'EnvelopeRecordVersion' => 'Pakkaustietueen versio', + 'EpsonImageHeight' => 'Epson-kuvan korkeus', + 'EpsonImageWidth' => 'Epson-kuvan leveys', + 'EpsonSoftware' => 'Epson-ohjelmisto', + 'Equipment' => 'Välineistön IFD-osoitin', + 'EquipmentVersion' => 'Välineistöversio', + 'ExcursionTolerance' => { + Description => 'Toleranssin ekskursio', + PrintConv => { + 'Allowed' => 'Voi toteutua', + 'Not Allowed' => 'Ei sallittu (oletus)', + }, + }, + 'ExifCameraInfo' => 'Exif-kameratiedot', + 'ExifImageHeight' => 'Kuvan korkeus', + 'ExifImageWidth' => 'Kuvan leveys', + 'ExifVersion' => 'Exif-versio', + 'ExitPupilPosition' => 'Lähtöpupillin sijainti', + 'ExpandFilm' => 'Filmilaajennus', + 'ExpandFilterLens' => 'Suodinlinssilaajennus', + 'ExpandFlashLamp' => 'Salamavalolaajennus', + 'ExpandLens' => 'Objektiivilaajennus', + 'ExpandScanner' => 'Skannerilaajennus', + 'ExpandSoftware' => 'Ohjelmistolaajennus', + 'ExpirationDate' => 'Päättymispäiväys', + 'ExpirationTime' => 'Päättymisaika', + 'Exposure' => 'Valotus', + 'ExposureBracketStepSize' => 'Valotuksen haarukointiväli', + 'ExposureBracketValue' => 'Valotushaarukoinnin arvo', + 'ExposureCompensation' => 'Valotuksen korjaus', + 'ExposureDelayMode' => 'Valotuksen viivetila', + 'ExposureDifference' => 'Valotusero', + 'ExposureIndex' => 'Valotusindeksi', + 'ExposureMode' => { + Description => 'Valotustapa', + PrintConv => { + 'Aperture Priority' => 'Aukon esivalinta', + 'Aperture-priority AE' => 'Aukon esivalinta AE', + 'Auto' => 'Automaattinen', + 'Auto bracket' => 'Automaattihaarukointi', + 'Landscape' => 'Maisema', + 'Manual' => 'Manuaalinen', + 'Night Scene / Twilight' => 'Yönäkymä', + 'Program' => 'Ohjelma', + 'Program AE' => 'Ohjelma-AE', + 'Program-shift' => 'Ohjelma-siirto', + 'Shutter Priority' => 'Ajan esivalinta', + 'Shutter speed priority AE' => 'Ajan esivalinta AE', + 'n/a' => 'Ei asetettu', + }, + }, + 'ExposureProgram' => { + Description => 'Valotusohjelma', + PrintConv => { + 'Action (High speed)' => 'Toimintaohjelma (painotettu suljinnopeutta)', + 'Aperture-priority AE' => 'Aukon esivalinta', + 'Creative (Slow speed)' => 'Luova ohjelma (painotettu syväterävyyttä)', + 'Landscape' => 'Maisemamoodi (maisemakuviin terävällä taustalla)', + 'Manual' => 'Manuaalinen', + 'Not Defined' => 'Ei määritetty', + 'Portrait' => 'Muotokuvamoodi (lähikuviin epäterävällä taustalla)', + 'Program AE' => 'Normaali ohjelma', + 'Shutter speed priority AE' => 'Ajan esivalinta', + }, + }, + 'ExposureTime' => 'Valotusaika', + 'ExposureTime2' => 'Valotusaika 2', + 'ExposureWarning' => { + Description => 'Valotusvaroitus', + PrintConv => { + 'Bad exposure' => 'Valotusvirhe', + 'Good' => 'OK', + }, + }, + 'ExtendedWBDetect' => { + Description => 'Laajennettu valkotas. tunnistus', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'Extender' => 'Konvertteri', + 'ExtenderFirmwareVersion' => 'Konvertterin laiteohjelmistoversio', + 'ExtenderModel' => 'Konvertterimalli', + 'ExtenderSerialNumber' => 'Konvertterin sarjanumero', + 'ExternalFlash' => 'Ulkoinen salama', + 'ExternalFlashAE1' => 'Ulkoinen salama AE 1?', + 'ExternalFlashAE1_0' => 'Ulkoinen salama AE 1 (0)?', + 'ExternalFlashAE2' => 'Ulkoinen salama AE 2?', + 'ExternalFlashAE2_0' => 'Ulkoinen salama AE 2 (0)?', + 'ExternalFlashBounce' => { + Description => 'Ulkoisen salaman heijastus', + PrintConv => { + 'Bounce or Off' => 'Heijastus tai pois', + 'Direct' => 'Suora', + 'No' => 'Ei', + 'Yes' => 'Kyllä', + }, + }, + 'ExternalFlashExposureComp' => 'Ulkoisen salaman valotuksen säätö', + 'ExternalFlashFlags' => 'Ulkoisen salaman liput', + 'ExternalFlashGuideNumber' => 'Ulkoisen salaman ohjeluku?', + 'ExternalFlashMode' => 'Ulkoisen salaman tapa', + 'ExternalFlashZoom' => 'Ulkoisen salaman zoom', + 'ExtraSamples' => 'Lisänäytteet', + 'FNumber' => 'Aukkoarvo', + 'Face1Position' => 'Kasvojen 1 sijainti', + 'Face2Position' => 'Kasvojen 2 sijainti', + 'Face3Position' => 'Kasvojen 3 sijainti', + 'Face4Position' => 'Kasvojen 4 sijainti', + 'Face5Position' => 'Kasvojen 5 sijainti', + 'Face6Position' => 'Kasvojen 6 sijainti', + 'Face7Position' => 'Kasvojen 7 sijainti', + 'Face8Position' => 'Kasvojen 8 sijainti', + 'Face9Position' => 'Kasvojen 9 sijainti', + 'FaceDetect' => 'Kasvojen tunnistus', + 'FacePositions' => 'Kasvojen sijainnit', + 'FacesDetected' => 'Tunnistetut kasvot', + 'FaxRecvParams' => 'Faxin vastaanottoparametrit', + 'FaxRecvTime' => 'Faxin vastaanottoaika', + 'FaxSubAddress' => 'Faxin alaosoite', + 'FileFormat' => 'Tiedostomuoto', + 'FileIndex' => 'Tiedostoindeksi', + 'FileInfo' => 'Tiedostoinfot', + 'FileInfoVersion' => 'Tiedostoinfojen versio', + 'FileName' => 'Tiedostonimi', + 'FileNumber' => 'Tiedoston numero', + 'FileNumberMemory' => 'Tiedostonumeromuisti', + 'FileNumberSequence' => 'Tiedostojen numerojärjestys', + 'FileSize' => 'Tiedoston koko', + 'FileSource' => { + Description => 'Tiedoston lähde', + PrintConv => { + 'Digital Camera' => 'Digitaalikamera', + 'Film Scanner' => 'Filmiskanneri', + 'Reflection Print Scanner' => 'Heijastava skanneri', + }, + }, + 'FileType' => 'Tiedostotyyppi', + 'Filename' => 'Tiedostonimi', + 'FillOrder' => 'Täyttöjärjestys', + 'FilmCategory' => 'Filmiluokka', + 'FilmGencode' => 'Filmin gencode', + 'FilmMode' => { + Description => 'Filmitila', + PrintConv => { + 'Dynamic (B&W)' => 'Dynaaminen (M&V)', + 'Dynamic (color)' => 'Dynaaminen (väri)', + 'F0/Standard (PROVIA)' => 'F0/vakio', + 'F0/Standard (Provia)' => 'F0/vakio', + 'F1/Studio Portrait' => 'F1/studiomuotokuva', + 'F1a/Studio Portrait Enhanced Saturation' => 'F1a/studiomuotokuva laajennettu värikylläisyys', + 'F1b/Studio Portrait Smooth Skin Tone (ASTIA)' => 'F1b/studiomuotokuva pehmeä ihonväri', + 'F1b/Studio Portrait Smooth Skin Tone (Astia)' => 'F1b/studiomuotokuva pehmeä ihonväri', + 'F1c/Studio Portrait Increased Sharpness' => 'F1c/studiomuotokuva lisätty terävyys', + 'F3/Studio Portrait Ex' => 'F3/studiomuotokuva Ex', + 'Nature (color)' => 'Luonnollinen (väri)', + 'Nostalgic' => 'Nostalginen väri', + 'Smooth (B&W)' => 'Pehmeä (M&V)', + 'Smooth (color)' => 'Pehmeä (väri)', + 'Standard (B&W)' => 'Vakio (M&V)', + 'Standard (color)' => 'Vakio (väri)', + 'Vibrant' => 'Eloisa väri', + }, + }, + 'FilmProductCode' => 'Filmin tuotekoodi', + 'FilmType' => 'Filmityyppi', + 'Filter' => 'Suodin', + 'FilterEffect' => 'Suodinefekti', + 'FilterEffectMonochrome' => 'Yksivärisuodintehoste', + 'Firmware' => 'Laiteohjelmisto', + 'FirmwareDate' => 'Laiteohjelmiston päiväys', + 'FirmwareID' => 'Laiteohjelmiston ID', + 'FirmwareRevision' => 'Laiteohjelmiston revisio', + 'FirmwareVersion' => 'Laiteohjelmiston versio', + 'FixtureIdentifier' => 'Ominaisuuden tunnistin', + 'Flash' => { + Description => 'Salama', + PrintConv => { + 'Auto, Did not fire' => 'Salama ei lauennut, automaattimoodi', + 'Auto, Did not fire, Red-eye reduction' => 'Automaattinen, salama ei lauennut, punasilmien esto', + 'Auto, Fired' => 'Salama lauennut, automaattimoodi', + 'Auto, Fired, Red-eye reduction' => 'Salama lauennut, automaattimoodi, punasilmien estomoodi', + 'Auto, Fired, Red-eye reduction, Return detected' => 'Salama lauennut, automaattimoodi, heijastusvalo havaittu, punasilmien estomoodi', + 'Auto, Fired, Red-eye reduction, Return not detected' => 'Salama lauennut, automaattimoodi, heijastusvaloa ei havaittu, punasilmien estomoodi', + 'Auto, Fired, Return detected' => 'Salama lauennut, automaattimoodi. heijastusvalo havaittu', + 'Auto, Fired, Return not detected' => 'Salama lauennut, automaattimoodi, heijastusvaloa ei havaittu', + 'Fired' => 'Salama lauennut', + 'Fired, Red-eye reduction' => 'Salama lauennut, punasilmien estomoodi', + 'Fired, Red-eye reduction, Return detected' => 'Salama lauennut, punasilmien estomoodi, heijastusvalo havaittu', + 'Fired, Red-eye reduction, Return not detected' => 'Salama lauennut, punasilmien estomoodi, heijastusvaloa ei havaittu', + 'Fired, Return detected' => 'Strobo-salaman heijastus havaittu', + 'Fired, Return not detected' => 'Strobo-salaman heijastusta ei havaittu', + 'No Flash' => 'Salama ei lauennut', + 'No flash function' => 'Ei salamatoimintoa', + 'Off, Did not fire' => 'Salama ei lauennut, pakkosalamatila', + 'Off, Did not fire, Return not detected' => 'Pois, salama ei lauennut, heijastusta ei havaittu', + 'Off, No flash function' => 'Pois, ei salamatoimintoa', + 'Off, Red-eye reduction' => 'Pois, punasilmien esto', + 'On, Did not fire' => 'Päällä, salama ei lauennut', + 'On, Fired' => 'Salama lauennut, pakkosalamatila', + 'On, Red-eye reduction' => 'Salama lauennut, pakkosalamatila, punasilmien estomoodi', + 'On, Red-eye reduction, Return detected' => 'Salama lauennut, pakkosalamatila, punasilmien estomoodi, heijastusvalo havaittu', + 'On, Red-eye reduction, Return not detected' => 'Salama lauennut, pakkosalamatila, punasilmien estomoodi, heijastusvaloa ei havaittu', + 'On, Return detected' => 'Salama lauennut, pakkosalamatila, heijastusvalo havaittu', + 'On, Return not detected' => 'Salama lauennut, pakkosalamatila, heijastusvaloa ei havaittu', + }, + }, + 'FlashActivity' => 'Salaman toiminta', + 'FlashBias' => 'Salaman muutos', + 'FlashChargeLevel' => 'Salaman varaustila', + 'FlashCompensation' => 'Salaman säätö', + 'FlashControlMode' => 'Salaman ohjaustapa', + 'FlashDevice' => 'Salamalaite', + 'FlashDistance' => 'Salamaetäisyys', + 'FlashEnergy' => 'Salaman teho', + 'FlashExposureBracketValue' => 'Salamavalotuksen haarukoinnin arvo', + 'FlashExposureComp' => 'Salaman kirkkauden säätö', + 'FlashExposureComp2' => 'Salaman kirkkauden säätö 2', + 'FlashFired' => { + Description => 'Salama lauennut', + PrintConv => { + 'No' => 'Ei', + 'Yes' => 'Kyllä', + }, + }, + 'FlashFirmwareVersion' => 'Salaman laiteohjelmistoversio', + 'FlashFocalLength' => 'Salaman polttoväli', + 'FlashGuideNumber' => 'Salaman ohjeluku', + 'FlashInfoVersion' => 'Salaman tietojen versio', + 'FlashIntensity' => { + Description => 'Salaman teho', + PrintConv => { + 'High' => 'Korkea', + 'Low' => 'Matala', + 'Normal' => 'Normaali', + 'Strong' => 'Voimakas', + 'Weak' => 'Heikko', + }, + }, + 'FlashMetering' => 'Salamamittaus', + 'FlashMeteringSegments' => 'Salaman mittaussegmentit', + 'FlashMode' => { + Description => 'Salamatapa', + PrintConv => { + 'Auto' => 'Automaattinen', + 'Did Not Fire' => 'Ei lauennut', + 'Disabled' => 'Deaktivoitu', + 'Fired, Commander Mode' => 'Lauennut, pakkotapa', + 'Fired, External' => 'Lauennut, ulkoinen', + 'Fired, Manual' => 'Lauennut, manuaalinen', + 'Fired, TTL Mode' => 'Lauennut, TTL-tapa', + 'Force' => 'Pakkotapa', + 'Not Ready' => 'Ei valmis', + 'Off' => 'Pois (3)', + 'On' => 'Päällä (2)', + 'Red eye' => 'Punasilmien esto', + }, + }, + 'FlashModel' => { + Description => 'Salaman malli', + PrintConv => { + 'None' => 'Ei mitään', + }, + }, + 'FlashOptions' => 'Salamavalinnat', + 'FlashOutput' => 'Salaman teho', + 'FlashRemoteControl' => 'Salaman kaukosäätö', + 'FlashSerialNumber' => 'Salaman sarjanumero', + 'FlashSetting' => 'Salaman asetus', + 'FlashShutterSpeed' => 'Suljinaika salamakuvauksessa', + 'FlashStatus' => 'Salaman tila', + 'FlashSyncSpeed' => 'Salaman täsmäysaika', + 'FlashType' => { + Description => 'Salamatyyppi', + PrintConv => { + 'E-System' => 'E-järjestelmä', + 'None' => 'Ei mitään', + 'Simple E-System' => 'Yksinkertainen E-järjestelmä', + }, + }, + 'FlashWarning' => 'Salamavaroitus', + 'FlashpixVersion' => 'Tuettu Flashpix-versio', + 'FlickerReduce' => { + Description => 'Värinän vaimennus', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'FlipHorizontal' => 'Kiepautus vaakatasossa', + 'FocalLength' => 'Polttoväli', + 'FocalLengthIn35mmFormat' => 'Polttoväli 35 mm filmikoolla', + 'FocalPlaneDiagonal' => 'Polttopistetason lävistäjä', + 'FocalPlaneResolutionUnit' => { + Description => 'Polttopistetason resoluutioyksikkö', + PrintConv => { + 'None' => 'Ei mitään', + 'inches' => 'Tuuma', + 'um' => 'µm (mikrometri)', + }, + }, + 'FocalPlaneXResolution' => 'Polttopistetason vaakaresoluutio', + 'FocalPlaneXSize' => 'Polttopistetason leveys', + 'FocalPlaneYResolution' => 'Polttopistetason pystyresoluutio', + 'FocalPlaneYSize' => 'Polttopistetason korkeus', + 'FocalType' => 'Objektiivityyppi', + 'Focus' => 'Tarkennusesivalinta', + 'FocusArea' => 'Tarkennusalue', + 'FocusAreaSelection' => 'Tarkennusalueen valinta', + 'FocusContinuous' => 'Jatkuva tarkennus', + 'FocusDistance' => 'Tarkennusetäisyys', + 'FocusDistanceLower' => 'Lähitarkennus', + 'FocusDistanceUpper' => 'Kaukotarkennus', + 'FocusMode' => { + Description => 'Tarkennustapa', + PrintConv => { + 'Auto' => 'Automaattinen', + 'Manual' => 'Manuaalinen', + }, + }, + 'FocusMode2' => 'Tarkennustapa', + 'FocusPixel' => 'Tarkennuspikseli', + 'FocusPosition' => 'Polttopisteen paikka', + 'FocusProcess' => 'Tarkennusprosessi', + 'FocusRange' => { + Description => 'Tarkennusalue', + PrintConv => { + 'Macro' => 'Makro', + 'Normal' => 'Normaali', + }, + }, + 'FocusSetting' => 'Tarkennusasetus', + 'FocusStepCount' => 'Tarkennusaskelmäärä', + 'FocusWarning' => { + Description => 'Tarkennusvaroitus', + PrintConv => { + 'Good' => 'OK', + 'Out of focus' => 'Epätarkka', + }, + }, + 'FolderName' => 'Kansion nimi', + 'ForwardMatrix1' => 'Eteenpäin-matriisi 1', + 'ForwardMatrix2' => 'Eteenpäin-matriisi 2', + 'FrameHeight' => 'Ruudun korkeus', + 'FrameNumber' => 'Ruudun numero', + 'FrameWidth' => 'Ruudun leveys', + 'FreeByteCounts' => 'Vapaiden tavujen määrät', + 'FreeMemoryCardImages' => 'Muistikortilla tilaa', + 'FreeOffsets' => 'Vapaat siirtymät', + 'FujiFlashMode' => { + Description => 'Salamatila', + PrintConv => { + 'External' => 'Ulkoinen salama', + 'Off' => 'Pois', + 'On' => 'Päällä', + 'Red-eye reduction' => 'Punasilmäisyyden vähennys', + }, + }, + 'GEMInfo' => 'GEM-tiedot', + 'GEModel' => 'Malli', + 'GIFVersion' => 'GIF-versio', + 'GPSAltitude' => 'Korkeus', + 'GPSAltitudeRef' => { + Description => 'Viitekorkeus', + PrintConv => { + 'Above Sea Level' => 'Merenpinnan korkeus', + 'Below Sea Level' => 'Merenpinnan viitekorkeus (negatiivinen arvo)', + }, + }, + 'GPSAreaInformation' => 'GPS-alueen nimi', + 'GPSDOP' => 'Mittaustarkkuus', + 'GPSDateStamp' => 'GPS-päiväys', + 'GPSDestBearing' => 'Kohteen suuntima', + 'GPSDestBearingRef' => { + Description => 'Kohteen suuntiman viite', + PrintConv => { + 'Magnetic North' => 'Magneettinen suunta', + 'True North' => 'Todellinen suunta', + }, + }, + 'GPSDestDistance' => 'Etäisyys kohteeseen', + 'GPSDestDistanceRef' => { + Description => 'Viite etäisyydelle kohteeseen', + PrintConv => { + 'Kilometers' => 'Kilometriä', + 'Miles' => 'Mailia', + 'Nautical Miles' => 'Solmua', + }, + }, + 'GPSDestLatitude' => 'Kohteen leveysaste', + 'GPSDestLatitudeRef' => { + Description => 'Kohteen leveysasteen viite', + PrintConv => { + 'North' => 'Pohjoista leveyttä', + 'South' => 'Eteläistä leveyttä', + }, + }, + 'GPSDestLongitude' => 'Kohteen pituusaste', + 'GPSDestLongitudeRef' => { + Description => 'Kohteen pituusasteen viite', + PrintConv => { + 'East' => 'Itäistä pituutta', + 'West' => 'Läntistä pituutta', + }, + }, + 'GPSDifferential' => { + Description => 'GPS-differentiaalikorjaus', + PrintConv => { + 'Differential Corrected' => 'Differentiaalikorjausta käytetty', + 'No Correction' => 'Mittaus ilman differentiaalikorjausta', + }, + }, + 'GPSImgDirection' => 'Kuvan´ suunta', + 'GPSImgDirectionRef' => { + Description => 'Kuvan suunnan viite', + PrintConv => { + 'Magnetic North' => 'Magneettinen suunta', + 'True North' => 'Todellinen suunta', + }, + }, + 'GPSLatitude' => 'Leveysaste', + 'GPSLatitudeRef' => { + Description => 'Pohjoista tai eteläistä leveyttä', + PrintConv => { + 'North' => 'Pohjoista leveyttä', + 'South' => 'Eteläistä leveyttä', + }, + }, + 'GPSLongitude' => 'Pituusaste', + 'GPSLongitudeRef' => { + Description => 'Itäistä tai läntistä pituutta', + PrintConv => { + 'East' => 'Itäistä pituutta', + 'West' => 'Läntistä pituutta', + }, + }, + 'GPSMapDatum' => 'Käytetty geodeettinen karttadata', + 'GPSMeasureMode' => { + Description => 'GPS-mittaustapa', + PrintConv => { + '2-Dimensional Measurement' => '2-ulotteinen mittaus', + '3-Dimensional Measurement' => '3-ulotteinen mittaus', + }, + }, + 'GPSProcessingMethod' => 'GPS-prosessointimetodin nimi', + 'GPSSatellites' => 'Mittaukseen käytetyt GPS-satelliitit', + 'GPSSpeed' => 'GPS-vastaanottimen nopeus', + 'GPSSpeedRef' => { + Description => 'Nopeusyksikkö', + PrintConv => { + 'km/h' => 'Kilometriä per tunti', + 'knots' => 'Solmua', + 'mph' => 'Mailia per tunti', + }, + }, + 'GPSStatus' => { + Description => 'GPS-vastaanottimen tila', + PrintConv => { + 'Measurement Active' => 'Mittaus aktiivinen', + 'Measurement Void' => 'Mittaus virheellinen', + }, + }, + 'GPSTimeStamp' => 'GPS-aika (atomikello)', + 'GPSTrack' => 'Liikkeen suunta', + 'GPSTrackRef' => { + Description => 'Liikkeen suunnan viite', + PrintConv => { + 'Magnetic North' => 'Magneettinen suunta', + 'True North' => 'Todellinen suunta', + }, + }, + 'GPSVersionID' => 'GPS-tagin versio', + 'GainBase' => 'Perusvahvistus', + 'GainControl' => { + Description => 'Herkkyyden säätö', + PrintConv => { + 'High gain down' => 'Suuri valoisuuden vähennys', + 'High gain up' => 'Suuri valoisuuden lisäys', + 'Low gain down' => 'Pieni valoisuuden vähennys', + 'Low gain up' => 'Pieni valoisuuden lisäys', + 'None' => 'Ei mitään', + }, + }, + 'GammaCompensatedValue' => 'Kompensoidun gamman arvo', + 'GeoTiffAsciiParams' => 'Geo-ASCII -parametrien tagi', + 'GeoTiffDirectory' => 'Geo-avain -hakemiston tagi', + 'GeoTiffDoubleParams' => 'Geo-kaksoisparametrien tagi', + 'Gradation' => 'Porrastus', + 'GrayPoint' => 'Harmaapiste', + 'GrayResponseCurve' => 'Harmaan vastekäyrä', + 'GrayResponseUnit' => { + Description => 'Harmaan vasteyksikkö', + PrintConv => { + '0.0001' => 'Numero edustaa yksikön tuhannesosia', + '0.001' => 'Numero edustaa yksikön sadasosia', + '0.1' => 'Numero edustaa yksikön kymmenyksiä', + '1e-05' => 'Numero edustaa yksikön kymmenestuhannesosia', + '1e-06' => 'Numero edustaa yksikön sadastuhannesosia', + }, + }, + 'GrayScale' => 'Harmaa-asteikko', + 'GrayTRC' => 'Harmaan toonin toistokäyrä', + 'GreenMatrixColumn' => 'Vihreän matriisin sarake', + 'GreenTRC' => 'Vihreän toonin toistokäyrä', + 'GreenX' => 'Vihreä X', + 'GreenY' => 'Vihreä Y', + 'GridDisplay' => 'Ristikko', + 'HCUsage' => 'HC-käyttö', + 'HalftoneHints' => 'Puolisävyviitteet', + 'Headline' => 'Otsikko', + 'HighISONoiseReduction' => { + Description => 'Korkean ISO-tason kohinan vaimennus', + PrintConv => { + 'High' => 'Korkea', + 'Low' => 'Matala', + 'Minimal' => 'Minimaalinen', + 'Normal' => 'Normaali', + 'Off' => 'Pois', + }, + }, + 'Highlight' => 'Valoalue', + 'HighlightData' => 'Valoalueen data', + 'HighlightProtection' => 'Valoalueen suojaus', + 'HometownCity' => 'Kotiseutu', + 'HometownCityCode' => 'Kotiseutukoodi', + 'HometownDST' => { + Description => 'Kotiseudun kesäaika (DST)', + PrintConv => { + 'No' => 'Ei', + 'Yes' => 'Kyllä', + }, + }, + 'HostComputer' => 'Isäntätietokone', + 'Hue' => 'Sävy', + 'HueAdjustment' => 'Värisävyn säätö', + 'HueSetting' => 'Sävyn asetus', + 'HuffmanTable' => 'Huffman-taulukko', + 'ICCProfile' => 'ICC-profiili', + 'ICC_Profile' => 'ICC-syötteen väriprofiili', + 'IPTC-NAA' => 'IPTC-NAA -metadata', + 'IPTCBitsPerSample' => 'Bittien määrä per näyte', + 'IPTCData' => 'IPTC-data', + 'IPTCDigest' => 'IPTC-yhteenveto', + 'IPTCImageHeight' => 'Viivojen määrä', + 'IPTCImageRotation' => { + Description => 'Kuvan kierto', + PrintConv => { + '0' => 'Ei kiertoa', + '180' => '180 asteen kierto', + '270' => '270 asteen kierto', + '90' => '90 asteen kierto', + }, + }, + 'IPTCImageWidth' => 'Pikseleitä per viiva', + 'IPTCPictureNumber' => 'Kuvan numero', + 'IPTCPixelHeight' => 'Pikselikoko suorassa kulmassa skannaussuuntaan', + 'IPTCPixelWidth' => 'Pikselikoko skannaussuunnassa', + 'ISO' => 'ISO-herkkyys', + 'ISOAuto' => 'Automaattinen ISO', + 'ISODisplay' => 'ISO-näyttö', + 'ISOExpansion' => 'ISO-laajennus', + 'ISOFloor' => 'ISO-alaraja', + 'ISOInfo' => 'ISO-tiedot', + 'ISOSelection' => 'ISO-valinta', + 'ISOSetting' => 'ISO-asetus', + 'ISOValue' => 'ISO-arvo', + 'IT8Header' => 'IT8-ylätunniste', + 'Illumination' => 'Näytön valaisu', + 'ImageAdjustment' => 'Kuvan säätö', + 'ImageAreaOffset' => 'Kuva-alueen siirtymä', + 'ImageAuthentication' => { + Description => 'Kuvan aitoustodennus', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'ImageBoundary' => 'Kuvan rajat', + 'ImageByteCount' => 'Kuvan tavumäärä', + 'ImageColorIndicator' => 'Kuvan värin ilmaisin', + 'ImageColorValue' => 'Kuvan värin arvo', + 'ImageCount' => 'Kuvamäärä', + 'ImageData' => 'Kuvadata', + 'ImageDataDiscard' => { + Description => 'Kuvadatan hylkäys', + PrintConv => { + 'Flexbits Discarded' => 'FlexBits hylätty', + 'Full Resolution' => 'Täysi resoluutio', + 'HighPass Frequency Data Discarded' => 'Ylipäästötaajuusdata hylätty', + 'Highpass and LowPass Frequency Data Discarded' => 'Yli- ja alipäästötaajuusdata hylätty', + }, + }, + 'ImageDataSize' => 'Kuvadatan koko', + 'ImageDepth' => 'Kuvan syvyys', + 'ImageDescription' => 'Kuvan otsake', + 'ImageDustOff' => 'Kuvan pölynpoisto', + 'ImageEditCount' => 'Kuvan prosessointimäärä', + 'ImageHeight' => 'Kuvan korkeus', + 'ImageHistory' => 'Kuvahistoria', + 'ImageID' => 'Kuvan ID', + 'ImageInfo' => 'Kuvatiedot', + 'ImageLayer' => 'Kuvataso', + 'ImageNumber' => 'Tiedostonumero', + 'ImageOffset' => 'Kuvan siirtrymä', + 'ImageOptimization' => 'Kuvan optimointi', + 'ImageOrientation' => { + Description => 'Kuvan suunta', + PrintConv => { + 'Landscape' => 'Vaakakuva', + 'Portrait' => 'Pystykuva', + 'Square' => 'Neliömäinen', + }, + }, + 'ImagePrintStatus' => 'Kuvan tulostuksen tila', + 'ImageProcessing' => 'Kuvan prosessointi', + 'ImageProcessingVersion' => 'Kuvaprosessoinnin versio', + 'ImageQuality' => 'Kuvan laatu', + 'ImageQuality2' => 'Kuvan laatu 2', + 'ImageReview' => 'Kuvan tarkastelu', + 'ImageReviewTime' => 'Kuvan katseluaika', + 'ImageRotationStatus' => 'Kuvan kierron tila', + 'ImageSize' => 'Kuvakoko', + 'ImageSourceData' => 'Kuvan lähdedata', + 'ImageSourceEK' => 'EK-kuvalähde', + 'ImageStabilization' => { + Description => 'Kuvanvakautus', + PrintConv => { + 'Best Shot' => 'Paras kuva', + 'Off' => 'Pois', + 'On' => 'Päällä', + 'On, Mode 1' => 'Päällä, tapa 1', + 'On, Mode 2' => 'Päällä, tapa 2', + }, + }, + 'ImageTone' => { + Description => 'Kuvamuoto', + PrintConv => { + 'Bleach Bypass' => 'Bleach bypass -esivalotus', + 'Bright' => 'Kirkas', + 'Landscape' => 'Maisema', + 'Monochrome' => 'Yksivärinen', + 'Muted' => 'Vaimennettu', + 'Natural' => 'Luonnollinen', + 'Portrait' => 'Muotokuva', + 'Reversal Film' => 'Kääntöfilmi', + 'Vibrant' => 'Eloisa', + }, + }, + 'ImageType' => 'Sivu', + 'ImageUniqueID' => 'Kuvan uniikki ID', + 'ImageWidth' => 'Kuvan leveys', + 'Indexed' => 'Indeksoitu', + 'InkNames' => 'Musteiden nimet', + 'InkSet' => 'Mustesarja', + 'IntellectualGenre' => 'Intellektuaalinen genre', + 'IntelligentAuto' => 'Intelligentti automatiikka', + 'IntergraphMatrix' => 'Intergraph-matriisi -tagi', + 'Interlace' => 'Lomitus', + 'InternalFlash' => { + Description => 'Sisäinen salama', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'InternalFlashAE1' => 'Sisäinen salama AE 1?', + 'InternalFlashAE1_0' => 'Sisäinen salama AE 1 (0)?', + 'InternalFlashAE2' => 'Sisäinen salama AE 2?', + 'InternalFlashAE2_0' => 'Sisäinen salama AE 2 (0)?', + 'InternalFlashMode' => 'Sisäisen salaman tapa', + 'InternalFlashStrength' => 'Sisäisen salaman teho', + 'InternalFlashTable' => 'Sisäisen salaman taulukko', + 'InternalSerialNumber' => 'Sisäinen sarjanumero', + 'InteropIndex' => { + Description => 'Interoperabiliteetin identifiointi', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03: DCF-valintatiedosto (Adobe RGB)', + 'R98 - DCF basic file (sRGB)' => 'R98: DCF-perustiedosto (sRGB)', + 'THM - DCF thumbnail file' => 'THM: DCF-näytekuvatiedosto', + }, + }, + 'InteropVersion' => 'Interoperabiliteetin versio', + 'IntervalLength' => 'Ajastusvälin pituus', + 'IntervalMode' => 'Ajastettu kuvaustapa', + 'IntervalNumber' => 'Ajastuksen numero', + 'JFIFVersion' => 'JFIF-versio', + 'JPEGACTables' => 'JPEG:n AC-taulukot', + 'JPEGDCTables' => 'JPEG:n DC-taulukot', + 'JPEGLosslessPredictors' => 'JPEG:n häviöttömät prediktorit', + 'JPEGPointTransforms' => 'JPEG:n pistemuunnot', + 'JPEGProc' => 'JPEG-proc', + 'JPEGQTables' => 'JPEG:n Q-taulukot', + 'JPEGQuality' => { + Description => 'JPEG-laatu', + PrintConv => { + 'Extra Fine' => 'Erityishieno', + 'Fine' => 'Hieno', + 'Standard' => 'Normaali', + 'n/a' => 'Ei asetettu', + }, + }, + 'JPEGRestartInterval' => 'JPEG:n uudelleenaloitusväli', + 'JPEGTables' => 'JPEG-taulukot', + 'JobID' => 'Työn ID', + 'Keyword' => 'Avainsana', + 'Keywords' => 'Avainsana', + 'LCDIllumination' => 'LCD:n valaisu', + 'Language' => 'Kieli', + 'LanguageIdentifier' => 'Kielen tunnistin', + 'LastFileNumber' => 'Viimeinen tiedostonumero', + 'LeafData' => 'Leaf-data', + 'Lens' => 'Objektiivi', + 'LensApertureRange' => 'Objektiivin aukkoalue', + 'LensDataVersion' => 'Objektiividatan versio', + 'LensDistortionParams' => 'Linssivääristymän parametrit', + 'LensFStops' => 'Objektiivin aukkoarvot', + 'LensFirmwareVersion' => 'Objektiivin laiteohjelmistoversio', + 'LensID' => 'Objektiivin ID', + 'LensIDNumber' => 'Objektiivin ID-numero', + 'LensInfo' => 'Objektiivin tiedot', + 'LensSerialNumber' => 'Objektiivin sarjanumero', + 'LensTemperature' => 'Objektiivin lämpötila', + 'LensType' => 'Objektiivityyppi', + 'LightCondition' => 'Valaistusolot', + 'LightReading' => 'Valolukema', + 'LightSource' => { + Description => 'Valonlähde', + PrintConv => { + 'Cloudy' => 'Pilvinen', + 'Cool White Fluorescent' => 'Viileä valkoinen loistelamppu (W 3900 - 4500K)', + 'Day White Fluorescent' => 'Valkoinen päivänvaloloistelamppu (N 4600 - 5400K)', + 'Daylight' => 'Päivänvalo', + 'Daylight Fluorescent' => 'Päivänvaloloistelamppu (D 5700 - 7100K)', + 'Fine Weather' => 'Pilvetön', + 'Flash' => 'Salama', + 'Fluorescent' => 'Loistevalo', + 'ISO Studio Tungsten' => 'ISO studiokeinovalo (hehkulamppu)', + 'Other' => 'Muu valonlähde', + 'Shade' => 'Varjo', + 'Standard Light A' => 'Standardivalo A', + 'Standard Light B' => 'Standardivalo B', + 'Standard Light C' => 'Standardivalo C', + 'Tungsten (Incandescent)' => 'Keinovalo (hehkulamppu)', + 'Unknown' => 'Tuntematon', + 'White Fluorescent' => 'Lämmin valkoinen loistelamppu (WW 3200 - 3700K)', + }, + }, + 'LightSourceSpecial' => { + Description => 'Erikoisvalonlähde', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'LinearResponseLimit' => 'Lineaarisen vasteen raja', + 'LinearizationTable' => 'Linearisaatiotaulukko', + 'LiveViewAF' => 'Suoranäyttö-AF', + 'LocalizedCameraModel' => 'Lokalisoitu kameramalli', + 'Location' => 'Sijainti', + 'LongExposureNoiseReduction' => { + Description => 'Pitkien valotusten kohinan vaimennus', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + 'n/a' => 'Ei asetettu', + }, + }, + 'LookupTable' => 'Hakutaulukko', + 'Luminance' => 'Luminanssi', + 'MB-D10BatteryType' => 'MB-D10 -paristotyyppi', + 'MB-D80Batteries' => 'MB-D80 -paristot', + 'MCUVersion' => 'MCU-versio', + 'Macro' => { + Description => 'Makro', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + 'n/a' => 'Ei asetettu', + }, + }, + 'MacroMode' => { + Description => 'Makrotapa', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + 'Super Macro' => 'Supermakro', + 'Tele-Macro' => 'Telemakro', + }, + }, + 'Magnification' => 'Suurennus', + 'Make' => 'Valmistaja', + 'MakeAndModel' => 'Valmistaja ja malli', + 'MakerNote' => 'DNG-erityistiedot', + 'MakerNoteOffset' => 'Valmistajatietojen siirtymä', + 'MakerNoteSafety' => { + Description => 'Valmistatietojen turvallisuus', + PrintConv => { + 'Safe' => 'Turvallinen', + 'Unsafe' => 'Turvaton', + }, + }, + 'MakerNoteType' => 'Valmistajatietojen tyyppi', + 'MakerNoteVersion' => 'Valmistajatietojen versio', + 'ManometerPressure' => 'Manometrin ilmaisema paine', + 'ManualFlash' => 'Manuaalinen salama', + 'ManualFlashOutput' => 'Salaman käsisäätö', + 'ManualFocusDistance' => 'Manuaalinen etäisyydensäätö', + 'ManufactureDate' => 'Valmistuspäiväys', + 'MaskedAreas' => 'Maskilliset alueet', + 'MasterDocumentID' => 'Alkuperäisdokumentin ID', + 'MasterGain' => 'Yleisvahvistus', + 'MatrixMetering' => 'Matriisimittaus', + 'Matteing' => 'Mattaus', + 'MaxAperture' => 'Suurin aukko', + 'MaxApertureAtCurrentFocal' => 'Suurin aukko nykyisellä polttovälillä', + 'MaxApertureAtMaxFocal' => 'Suurin aukko pisimmällä polttovälillä', + 'MaxApertureAtMinFocal' => 'Suurin aukko lyhimmällä polttovälillä', + 'MaxApertureValue' => 'Objektiivin maksimiaukko', + 'MaxFocalLength' => 'Pisin polttoväli', + 'MaxSampleValue' => 'Näytteen maksimiarvo', + 'MaximumDensityRange' => 'Tiheyden maksimilaajuus', + 'MeasuredEV' => 'Mitattu EV', + 'MeasurementBacking' => 'Mittaustuenta', + 'MeasurementFlare' => 'Hajavalomittaus', + 'MeasurementGeometry' => 'Mittausgeometria', + 'MeasurementIlluminant' => 'Valaistusmittaus', + 'MeasurementObserver' => 'Mittauksen tarkkailija', + 'MediaBlackPoint' => 'Median musta piste', + 'MediaWhitePoint' => 'Median valkoinen piste', + 'Medium' => 'Keskikokoa', + 'MetadataNumber' => 'Metadatan määrä', + 'Metering' => 'Valonmittaus', + 'MeteringMode' => { + Description => 'Valonmittaustapa', + PrintConv => { + 'Average' => 'Keskiarvo', + 'Center-weighted average' => 'Keskustapainotteinen keskiarvo', + 'Multi-segment' => 'Monisegmenttimittaus', + 'Multi-spot' => 'Monipiste', + 'Other' => 'Muu', + 'Partial' => 'Osa-alue', + 'Pattern+AF' => 'Kuvio + AF', + 'Spot' => 'Pistemittaus', + 'Spot+Highlight control' => 'Pistemittaus + valoalueen säätö', + 'Spot+Shadow control' => 'Pistemittaus + varjoalueen säätö', + 'Unknown' => 'Tuntematon', + }, + }, + 'MeteringTime' => 'Mittausaika', + 'MinAperture' => 'Pienin aukko', + 'MinFocalLength' => 'Lyhin polttoväli', + 'MinSampleValue' => 'Näytteen minimiarvo', + 'MinoltaCameraSettings2' => 'Kameran asetukset 2', + 'MinoltaCameraSettings5D' => 'Kameran asetukset (5D)', + 'MinoltaCameraSettings7D' => 'Kamera-asetukset (7D)', + 'MinoltaMakerNote' => 'Minolta-valmistajatiedot', + 'MinoltaQuality' => { + Description => 'Kuvakoko', + PrintConv => { + 'Economy' => 'Taloudellinen', + 'Extra fine' => 'Erityishieno', + 'Fine' => 'Hieno', + 'Standard' => 'Normaali', + 'Super Fine' => 'Superhieno', + }, + }, + 'Model' => 'Malli', + 'Model2' => 'Kuvasyötelaitteen malli (2)', + 'ModelAndVersion' => 'Malli ja versio', + 'ModelID' => 'Mallin ID', + 'ModelTiePoint' => 'Sidospistemalli-tagi', + 'ModelTransform' => 'Muuntomalli-tagi', + 'ModelingFlash' => 'Mallinnussalama', + 'ModifiedDigitalGain' => 'Modifioitu digitaalinen vahvistus', + 'ModifiedInfo' => 'Modifioidut tiedot', + 'ModifiedParamFlag' => 'Modifioitu parametrilippu', + 'ModifiedPictureStyle' => 'Modifioitu kuvan tyyli', + 'ModifiedSaturation' => { + Description => 'Modifoitu värikylläisyys', + PrintConv => { + 'CM1 (Red Enhance)' => 'CM1 (punaisen parantelu)', + 'CM2 (Green Enhance)' => 'CM2 (vihreän parantelu)', + 'CM3 (Blue Enhance)' => 'CM3 (sinisen parantelu)', + 'CM4 (Skin Tones)' => 'CM4 (ihonvärin parantelu)', + 'Off' => 'Pois', + }, + }, + 'ModifiedSensorBlueLevel' => 'Modifioitu anturin sinitaso', + 'ModifiedSensorRedLevel' => 'Modifioitu anturin punataso', + 'ModifiedSharpness' => 'Modifioitu terävyys', + 'ModifiedToneCurve' => 'Modifioitu sävykäyrä', + 'ModifiedWhiteBalance' => 'Modifioitu valkotasapaino', + 'ModifiedWhiteBalanceBlue' => 'Modifioitu valkotasapainon sininen', + 'ModifiedWhiteBalanceRed' => 'Modifioitu valkotasapainon punainen', + 'ModifyDate' => 'Tiedostomuutoksen päiväys ja aika', + 'MultiExposure' => 'Monivalotusdata', + 'MultiExposureAutoGain' => 'Monivalotuksen automaattivalotus', + 'MultiExposureMode' => 'Monivalotustapa', + 'MultiExposureShots' => 'Monivalotusotoksia', + 'MultiExposureVersion' => 'Monivalotusdatan versio', + 'MultiSample' => 'Moninäyte', + 'MultiSelector' => 'Monivalitsin', + 'MultipleExposureSet' => 'Monivalotus', + 'MyColorMode' => 'Oma värimuoto', + 'NDFilter' => 'ND-suodin', + 'NEFLinearizationTable' => 'Linearisaatiotaulukko', + 'NamedColor2' => 'Nimetty väri 2', + 'NativeDisplayInfo' => 'Paikallisen näytön tiedot', + 'NativeResolutionUnit' => 'Paikallisen resoluution yksikkö', + 'NativeXResolution' => 'Paikallinen vaakaresoluutio', + 'NativeYResolution' => 'Paikallinen pystyresoluutio', + 'NikonCaptureData' => 'Nikon Capture -data', + 'NikonCaptureOffsets' => 'Nikon Capture -siirtymät', + 'NikonCaptureOutput' => 'Nikon Capture -tuotos', + 'NikonCaptureVersion' => 'Nikon Capture -versio', + 'NikonICCProfile' => 'Nikon ICC-profiilin osoitin', + 'NoMemoryCard' => 'Ei muistikorttia', + 'Noise' => 'Kohina', + 'NoiseFilter' => 'Kohinasuodin', + 'NoiseReduction' => { + Description => 'Kohinan vähennys', + PrintConv => { + 'High (+1)' => '+1 (korkea)', + 'Highest (+2)' => '+2 (korkein)', + 'Low' => 'Matala', + 'Low (-1)' => '-1 (matala)', + 'Lowest (-2)' => '-2 (matalin)', + 'Normal' => 'Vakio', + 'Off' => 'Pois', + 'On' => 'Päällä', + 'Standard' => '±0 (vakio)', + }, + }, + 'NoiseReductionApplied' => 'Suoritettu kohinan vähennys', + 'NoiseReductionData' => 'Kohinanvaimennusdata', + 'NoiseReductionIntensity' => 'Kohinanvaimennuksen voimakkuus', + 'NoiseReductionMethod' => 'Kohinanvaimennusmetodi', + 'NoiseReductionSharpness' => 'Kohinanvaimennuksen terävyys', + 'NumIndexEntries' => 'Indeksimerkintöjen määrä', + 'NumberOfChannels' => 'Kanavien määrä', + 'NumberofInks' => 'Musteiden numerot', + 'OPIProxy' => 'OPI-välipalvelin', + 'ObjectAttributeReference' => 'Intellektuaalinen genre', + 'ObjectCycle' => { + Description => 'Objektin sykli', + PrintConv => { + 'Both Morning and Evening' => 'Molempina', + 'Evening' => 'Iltaisin', + 'Morning' => 'Aamuisin', + }, + }, + 'ObjectDistance' => 'Kohteen etäisyys', + 'ObjectName' => 'Nimike', + 'ObjectPreviewData' => 'Objektidatan esikatseludata', + 'ObjectPreviewFileFormat' => 'Objektidatan esikatselun tiedostomuoto', + 'ObjectPreviewFileVersion' => 'Objektidatan esikatselun tiedostomuodon versio', + 'ObjectTypeReference' => 'Objektyypin viiteryhmä', + 'OffsetSchema' => 'Siirtymäskeema', + 'OlympusImageHeight' => 'Kuvan korkeus', + 'OlympusImageWidth' => 'Kuvan leveys', + 'OneTouchWB' => { + Description => 'Pikavalkotasapaino', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + 'On (Preset)' => 'Päällä (esiasetus)', + }, + }, + 'OpticalZoomCode' => 'Optisen zoomin koodi', + 'OpticalZoomMode' => { + Description => 'Optinen zoomaustapa', + PrintConv => { + 'Extended' => 'Laajennettu', + 'Standard' => 'Vakio', + }, + }, + 'OpticalZoomOn' => { + Description => 'Optinen zoom', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'Opto-ElectricConvFactor' => 'Optoelektrinen muuntokerroin', + 'OrderNumber' => 'Valokuva-CD:n ostotilausnumero', + 'Orientation' => { + Description => 'Kuvan suunta', + PrintConv => { + 'Horizontal (normal)' => '0° (ylä/vasen)', + 'Mirror horizontal' => '0° (ylä/oikea)', + 'Mirror horizontal and rotate 270 CW' => '90° myötäpäivään (vasen/ylä)', + 'Mirror horizontal and rotate 90 CW' => '90° vastapäivään (oikea/ala)', + 'Mirror vertical' => '180° (ala/vasen)', + 'Rotate 180' => '180° (ala/oikea)', + 'Rotate 270 CW' => '90° myötäpäivään (vasen/ala)', + 'Rotate 90 CW' => '90° vastapäivään (oikea/ylä)', + }, + }, + 'OriginalDecisionDataOffset' => 'Alkuperäisen ratkaisudatan siirtymä', + 'OriginalRawFileData' => 'Originaali raw-tiedoston data', + 'OriginalRawFileDigest' => 'Alkuperäisen raw-tiedoston tiivistelmä', + 'OriginalRawFileName' => 'Originaali raw-tiedoston nimi', + 'OriginalTransmissionReference' => 'Tehtävän tunnistin', + 'OriginatingProgram' => 'Luontiohjelma', + 'OutputImageHeight' => 'Tuotoskuvan korkeus', + 'OutputImageWidth' => 'Tuotoskuvan leveys', + 'OutputResolution' => 'Tuotoksen resoluutio', + 'OutputResponse' => 'Tuotosvaste', + 'OwnerID' => 'Omistajan ID', + 'OwnerName' => 'Omistajan nimi', + 'Padding' => 'Täyte', + 'PageName' => 'Sivun nimi', + 'PageNumber' => 'Sivunumero', + 'PanasonicExifVersion' => 'Panasonic-exif -versio', + 'PanasonicRawVersion' => 'Panasonic-RAW:n versio', + 'PanasonicTitle' => 'Otsikko', + 'PanoramaDirection' => 'Panoraaman suunta', + 'PanoramaMode' => 'Panoraamatapa', + 'PentaxImageSize' => { + Description => 'Pentax-kuvakoko', + PrintConv => { + '2304x1728 or 2592x1944' => '2304 x 1728 tai 2592 x 1944', + '2560x1920 or 2304x1728' => '2560 x 1920 tai 2304 x 1728', + '2816x2212 or 2816x2112' => '2816 x 2212 tai 2816 x 2112', + '3008x2008 or 3040x2024' => '3008 x 2008 tai 3040 x 2024', + 'Full' => 'Täysi', + }, + }, + 'PentaxVersion' => 'Pentax-versio', + 'People' => 'Ihmiset', + 'PeripheralLighting' => 'Oheislaitevalaistus', + 'PeripheralLightingSetting' => 'Oheislaitevalaistuksen asetus', + 'PeripheralLightingValue' => 'Oheislaitevalaistuksen arvo', + 'PhotoEffect' => 'Valokuvaefekti', + 'PhotoEffects' => 'Valokuvaefektit', + 'PhotoEffectsBlue' => 'Valokuvaefekti sininen', + 'PhotoEffectsData' => 'Valokuvaefektidata', + 'PhotoEffectsGreen' => 'Valokuvaefekti vihreä', + 'PhotoEffectsRed' => 'Valokuvaefekti punainen', + 'PhotoEffectsType' => 'Valokuvaefektityyppi', + 'PhotometricInterpretation' => { + Description => 'Pikseliskeema', + PrintConv => { + 'BlackIsZero' => 'Musta on nolla', + 'Color Filter Array' => 'CFA (värisuodinmatriisi)', + 'Pixar LogL' => 'CIE Log2(L) (Log-luminanssi)', + 'Pixar LogLuv' => 'CIE Log2(L)(u\',v\') (Log-luminanssi ja -krominanssi)', + 'RGB Palette' => 'Väripaletti', + 'Transparency Mask' => 'Läpinäkyvyysmaski', + 'WhiteIsZero' => 'Valkoinen on nolla', + }, + }, + 'PhotoshopBGRThumbnail' => 'Photoshopin BRG-näytekuva', + 'PhotoshopFormat' => 'Photoshop-muoto', + 'PhotoshopQuality' => 'Photoshop-laatu', + 'PictInfo' => 'Kuvatiedot', + 'PictureControl' => 'Kuvan optimointi', + 'PictureControlActive' => 'Kuvan käsittely aktivoitu', + 'PictureControlAdjust' => 'Kuvan optimoinnin säätö', + 'PictureControlBase' => 'Kuvan perusoptimointi', + 'PictureControlData' => 'Kuvan käsittely', + 'PictureControlMode' => 'Kuvan käsittelytapa', + 'PictureControlName' => 'Kuvan optimoinnin nimi', + 'PictureControlQuickAdjust' => 'Kuvan optimoinnin pikasäätö', + 'PictureControlVersion' => 'Kuvan optimoinnin versio', + 'PictureFinish' => 'Kuvan viimeistely', + 'PictureMode' => { + Description => 'Kuvausohjelma', + PrintConv => { + 'Anti-blur' => 'Epätarkkuden vähennys', + 'Aperture-priority AE' => 'Aukon esivalinta AE', + 'Auto' => 'Automaattinen', + 'Beach' => 'Hiekkaranta', + 'Beach & Snow' => 'Hiekkaranta & Lumi', + 'Fireworks' => 'Ilotulitus', + 'Flower' => 'Kukka', + 'Landscape' => 'Maisema', + 'Macro' => 'Lähikuva (Kukka)', + 'Manual' => 'Manuaalinen', + 'Museum' => 'Museo', + 'Natural Light' => 'Luonnonvalo', + 'Natural Light & Flash' => 'Luonnonvalo & Salama', + 'Night Scene' => 'Yö', + 'Party' => 'Juhlat', + 'Portrait' => 'Muotokuva', + 'Program AE' => 'Automaattinen valotusohjelma', + 'Shutter speed priority AE' => 'Ajan esivalinta AE', + 'Snow' => 'Lumi', + 'Sports' => 'Urheilu', + 'Sunset' => 'Auringonlasku', + 'Text' => 'Teksti', + 'Underwater' => 'Veden alla', + }, + }, + 'PictureMode2' => 'Kuvamuoto 2', + 'PictureModeBWFilter' => { + Description => 'BW-suodin -kuvamuoto', + PrintConv => { + 'Green' => 'Vihreä', + 'Neutral' => 'Neutraali', + 'Orange' => 'Oranssi', + 'Red' => 'Punainen', + 'Yellow' => 'Keltainen', + 'n/a' => 'E.s.', + }, + }, + 'PictureModeContrast' => 'Kontrastinen kuvamuoto', + 'PictureModeHue' => 'Sävykäs kuvamuoto?', + 'PictureModeSaturation' => 'Värikylläinen kuvamuoto', + 'PictureModeSharpness' => 'Terävä kuvamuoto', + 'PictureModeTone' => { + Description => 'Vivahteikas kuvamuoto', + PrintConv => { + 'Blue' => 'Sininen', + 'Green' => 'Vihreä', + 'Neutral' => 'Neutraali', + 'Purple' => 'Violetti', + 'Sepia' => 'Seepia', + 'n/a' => 'E.s.', + }, + }, + 'PictureStyle' => 'Kuvan tyyli', + 'PixelAspectRatio' => 'Pikselien kokosuhde', + 'PixelFormat' => 'Pikseliformaatti', + 'PixelIntensityRange' => 'Pikseli-intensiteetin alue', + 'PixelScale' => 'Pikseliasteikkomalli-tagi', + 'PlanarConfiguration' => { + Description => 'Kuvadatan järjestys', + PrintConv => { + 'Chunky' => 'Lohkoformaatti (lomitettu)', + 'Planar' => 'Tasoformaatti', + }, + }, + 'PlaybackMenusTime' => 'Toistovalikkojen aika', + 'PowerSource' => 'Virtalähde', + 'PreCaptureFrames' => 'Esiasetetut kehykset', + 'Predictor' => { + Description => 'Prediktori', + PrintConv => { + 'Horizontal differencing' => 'Horisontaalinen differointi', + 'None' => 'Prediktioskeemaa ei käytetty ennen koodausta', + }, + }, + 'Preview' => 'Esikatselu', + 'Preview0' => 'Esikatselu 0', + 'Preview1' => 'Esikatselu 1', + 'Preview2' => 'Esikatselu 2', + 'PreviewApplicationName' => 'Sovelluksen nimen esikatselu', + 'PreviewApplicationVersion' => 'Sovellusversion esikatselu', + 'PreviewColorSpace' => { + Description => 'Väriavaruuden esikatselu', + PrintConv => { + 'Unknown' => 'Tuntematon', + }, + }, + 'PreviewDateTime' => 'Päiväyksen ja ajan esikatselu', + 'PreviewImage' => 'Kuvan esikatselu', + 'PreviewImageBorders' => 'Kuvan reunojen esikatselu', + 'PreviewImageData' => 'Kuvadatan tarkastelu', + 'PreviewImageLength' => 'Kuvan pituuden esikatselu', + 'PreviewImageSize' => 'Kuvakoon esikatselu', + 'PreviewImageStart' => 'Esikatselukuvan alku', + 'PreviewImageValid' => { + Description => 'Kelvollinen esikatselukuva', + PrintConv => { + 'No' => 'Ei', + 'Yes' => 'Kyllä', + }, + }, + 'PreviewSettingsDigest' => 'Asetustiivistelmän esikatselu', + 'PreviewSettingsName' => 'Asetusten nimen esikatselu', + 'PrimaryAFPoint' => 'Ensisijainen AF-piste', + 'PrimaryChromaticities' => 'Päävärien kromaattisuudet', + 'PrimaryPlatform' => 'Primaari alusta', + 'PrintQuality' => 'Tulostuslaatu', + 'ProcessingInfo' => 'Prosessointitiedot', + 'ProcessingSoftware' => 'Prosessointiohjelmisto', + 'ProductID' => 'Tuotteen ID', + 'ProfileCMMType' => 'CMM-tyypin profiili', + 'ProfileCalibrationSig' => 'Profiilin kalibrointitunniste', + 'ProfileClass' => 'Profiilin luokka', + 'ProfileConnectionSpace' => 'Profiilin yhteystila', + 'ProfileCopyright' => 'Profiilin copyright', + 'ProfileCreator' => 'Profiilin luoja', + 'ProfileDateTime' => 'Profiilin päiväysaika', + 'ProfileDescription' => 'Profiilin kuvaus', + 'ProfileDescriptionML' => 'Monikieliprofiilin kuvaus', + 'ProfileEmbedPolicy' => { + Description => 'Profiilin upotusmenetelmä', + PrintConv => { + 'Allow Copying' => 'Salli kopiointi', + 'Embed if Used' => 'Upota jos käytössä', + 'Never Embed' => 'Älä koskaan upota', + 'No Restrictions' => 'Ei rajoituksia', + }, + }, + 'ProfileFileSignature' => 'Profiilidatan tunniste', + 'ProfileHueSatMapData1' => 'Profiilin Sävy Värik. -kartan data 1', + 'ProfileHueSatMapData2' => 'Profiilin Sävy Värik. -kartan data 2', + 'ProfileID' => 'Profiilin ID', + 'ProfileLookTableData' => 'Profiilin hakutaulukon data', + 'ProfileName' => 'Profiilin nimi', + 'ProfileSequenceDesc' => 'Profiilisarjan kuvaus', + 'ProfileToneCurve' => 'Profiilin toonin käyrä', + 'ProfileVersion' => 'Profiilin versio', + 'ProgramISO' => 'Ohjelma-ISO', + 'ProgramLine' => 'Valotusohjelma', + 'ProgramMode' => 'Ohjelman muoto', + 'ProgramShift' => 'Ohjelmasiirto', + 'ProgramVersion' => 'Ohjelmaversio', + 'ProgressiveScans' => 'Progressiiviset skannaukset', + 'Province-State' => 'Provinssi/Valtio', + 'Quality' => { + Description => 'Laatu', + PrintConv => { + 'Best' => 'Paras', + 'Better' => 'Parempi', + 'Compressed RAW' => 'Pakattu RAW', + 'Compressed RAW + JPEG' => 'Pakattu RAW + JPEG', + 'Economy' => 'Taloudellinen', + 'Extra Fine' => 'Erityishieno', + 'Fine' => 'Hieno', + 'Good' => 'Hyvä', + 'Normal' => 'Normaali', + 'Standard' => 'Vakio', + 'Super Fine' => 'Superhieno', + 'n/a' => 'Ei asetettu', + }, + }, + 'Quality2' => 'Laatu 2', + 'QualityMode' => { + Description => 'Kuvalaatu', + PrintConv => { + 'Economy' => 'Taloudellinen', + 'Fine' => 'Hieno', + 'Normal' => 'Normaali', + }, + }, + 'QuantizationMethod' => { + Description => 'Kvantisointimetodi', + PrintConv => { + 'AP Domestic Analogue' => 'AP domestinen analogia', + 'Color Space Specific' => 'Erityisväriavaruus', + 'Compression Method Specific' => 'Erityispakkausmetodi', + 'Gamma Compensated' => 'Kompensoitu gamma', + 'IPTC Ref B' => 'IPTC ref "B"', + 'Linear Density' => 'Lineaarinen tiheys', + 'Linear Dot Percent' => 'Lineaarinen pisteprosentti', + 'Linear Reflectance/Transmittance' => 'Lineaarinen heijastavuus/läpäisevyys', + }, + }, + 'QuickAdjust' => 'Pikasäätö', + 'QuickShot' => { + Description => 'Pikakuvaus', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'ROCInfo' => 'ROC-tiedot', + 'RangeFinder' => 'Alue-etsin', + 'RasterPadding' => 'Rasteritäyte', + 'RasterizedCaption' => 'Rasteroitu seloste', + 'Rating' => 'Arvostelu', + 'RawDataOffset' => 'RAW-datan siirtymä', + 'RawDataUniqueID' => 'Raw-datan uniikki ID', + 'RawDevColorSpace' => 'Väriavaruus', + 'RawDevContrastValue' => 'Kontrastiarvo', + 'RawDevEditStatus' => { + Description => 'Editointitila', + PrintConv => { + 'Edited (Landscape)' => 'Studio (maisema)', + 'Edited (Portrait)' => 'Studio (muotokuva)', + 'Original' => 'Alkuperäinen', + }, + }, + 'RawDevEngine' => { + Description => 'Moottori', + PrintConv => { + 'Advanced High Speed' => 'Edistynyt suurnopeus', + 'High Function' => 'Hyvä suorituskyky', + 'High Speed' => 'Suurnopeus', + }, + }, + 'RawDevExposureBiasValue' => 'Valotuksen korjausarvo', + 'RawDevGrayPoint' => 'Harmaapiste', + 'RawDevMemoryColorEmphasis' => 'Muistivärin korostus', + 'RawDevNoiseReduction' => 'Kohinasuodin (ISO-tehostus)', + 'RawDevSaturationEmphasis' => 'Värikylläisyyden korostus', + 'RawDevSettings' => 'Kohinanvaimennus', + 'RawDevSharpnessValue' => 'Kontrastiarvo', + 'RawDevVersion' => 'RAW-kehityksen versio', + 'RawDevWBFineAdjustment' => 'Valkotasapainon hienosäätö', + 'RawDevWhiteBalanceValue' => 'Valkotasapainon arvo', + 'RawImageCenter' => 'RAW-kuvan keskusta', + 'RawImageDigest' => 'RAW-kuvan tiivistelmä', + 'RawImageHeight' => 'Kuvan korkeus', + 'RawImageSize' => 'RAW-kuvakoko', + 'RawImageWidth' => 'Kuvan leveys', + 'RawInfoVersion' => 'RAW Info -versio', + 'RecognizedFace1Age' => 'Tunnistettujen kasvojen 1 ikä', + 'RecognizedFace1Name' => 'Tunnistettujen kasvojen 1 nimi', + 'RecognizedFace1Position' => 'Tunnistettujen kasvojen 1 sijainti', + 'RecognizedFace2Age' => 'Tunnistettujen kasvojen 2 ikä', + 'RecognizedFace2Name' => 'Tunnistettujen kasvojen 2 nimi', + 'RecognizedFace2Position' => 'Tunnistettujen kasvojen 2 sijainti', + 'RecognizedFace3Age' => 'Tunnistettujen kasvojen 2 ikä', + 'RecognizedFace3Name' => 'Tunnistettujen kasvojen 3 nimi', + 'RecognizedFace3Position' => 'Tunnistettujen kasvojen 3 sijainti', + 'RecognizedFaceFlags' => 'Tunnistettujen kasvojen liput', + 'RecordMode' => { + Description => 'Tallennustapa', + PrintConv => { + 'Aperture Priority' => 'Aukon esivalinta', + 'Best Shot' => 'Paras kuva', + 'Manual' => 'Manuaalinen', + 'Movie' => 'Elokuva', + 'Movie (19)' => 'Elokuva (19)', + 'Program AE' => 'AE-ohjelma', + 'Shutter Priority' => 'Ajan esivalinta', + 'YouTube Movie' => 'YouTube-elokuva', + }, + }, + 'RecordShutterRelease' => { + Description => 'Laukaisujen tallennus', + PrintConv => { + 'Press start, press stop' => 'Aloitus painaen, lopetus painaen', + 'Record while down' => 'Tallennus laukaisin alhaalla', + }, + }, + 'RecordingMode' => 'Tallennustapa', + 'RedBalance' => 'Punatasapaino', + 'RedEyeCorrection' => 'Punasilmäkorjaus', + 'RedEyeData' => 'Punasilmädata', + 'RedMatrixColumn' => 'Punaisen matriisin sarake', + 'RedTRC' => 'Punaisen toonin toistokäyrä', + 'RedX' => 'Punainen X', + 'RedY' => 'Punainen Y', + 'ReductionMatrix1' => 'Reduktiomatriisi 1', + 'ReductionMatrix2' => 'Reduktiomatriisi 2', + 'ReferenceBlackWhite' => 'Musta ja valkoinen viitearvopari', + 'ReferenceDate' => 'Viitepäiväys', + 'ReferenceNumber' => 'Viitenumero', + 'ReferenceService' => 'Viitepalvelu', + 'RelatedImageFileFormat' => 'Kuvatiedoston muoto', + 'RelatedImageHeight' => 'Kuvan korkeus', + 'RelatedImageWidth' => 'Kuvan leveys', + 'RelatedSoundFile' => 'Asiaan liittyvä audiotiedosto', + 'ReleaseButtonToUseDial' => 'Käytä valitsinta vapauttaen painike', + 'ReleaseDate' => 'Julkaisupäiväys', + 'ReleaseMode' => 'Laukaisutapa', + 'ReleaseTime' => 'Julkaisuaika', + 'RemoteOnDuration' => 'Kaukolaukaisin', + 'RenderingIntent' => 'Muokkausmenetelmä', + 'Resaved' => { + Description => 'Tallennus uudelleen', + PrintConv => { + 'No' => 'Ei', + 'Yes' => 'Kyllä', + }, + }, + 'ResolutionMode' => 'Resoluutiomuoto', + 'ResolutionUnit' => { + Description => 'X- ja Y-resoluution yksikkö', + PrintConv => { + 'None' => 'Ei mitään', + 'inches' => 'Tuuma', + }, + }, + 'RetouchHistory' => 'Korjailuhistoria', + 'ReverseIndicators' => 'Käänteiset ilmaisimet', + 'RevisionNumber' => 'Revision numero', + 'RollGuidElements' => 'Guid-elementtien vieritys', + 'Rotation' => { + Description => 'Kierto', + PrintConv => { + 'Horizontal (normal)' => 'Vaakatasossa (normaali)', + 'Rotate 270 CW' => 'Kierto 90° VP', + 'Rotate 90 CW' => 'Kierto 90° MP', + }, + }, + 'RowInterleaveFactor' => 'Rivin lomitusfaktori', + 'RowsPerStrip' => 'Rivien määrä per strippi', + 'SBAExposureRecord' => 'SBA-valotuksen tallennus', + 'SBAInputImageBitDepth' => 'SBA-syötekuvan bittisyvyys', + 'SBAInputImageColorspace' => 'SBA-syötekuvan väriavaruus', + 'SMaxSampleValue' => 'S-maksimiminäytteen arvo', + 'SMinSampleValue' => 'S-miniminäytteen arvo', + 'SRFocalLength' => 'Vakain (SR) eri polttoväleillä', + 'SRResult' => 'Kuvanvakain', + 'SampleFormat' => 'Näyteformaatti', + 'SampleStructure' => { + Description => 'Näytteiden rakenne', + PrintConv => { + 'CompressionDependent' => 'Määritetty pakkaustoiminnon yhteydessä', + 'Orthogonal4-2-2Sampling' => 'Toisistaan riippumattomat näytetaajuuksin suhteessa 4:2:2:(4)', + 'OrthogonalConstangSampling' => 'Toisistaan riippumattomat samoin suhteellisin näytetaajuuksin kussakin komponentissa', + }, + }, + 'SamplesPerPixel' => 'Komponenttien määrä', + 'SanyoQuality' => { + Description => 'Sanyo-laatu', + PrintConv => { + 'Fine/High' => 'Hieno/korkea', + 'Fine/Low' => 'Hieno/matala', + 'Fine/Medium' => 'Hieno/keskilaatu', + 'Fine/Medium High' => 'Hieno/korkeahko', + 'Fine/Medium Low' => 'Hieno/matalahko', + 'Fine/Super High' => 'Hieno/superkorkea', + 'Fine/Very High' => 'Hieno/hyvin korkea', + 'Fine/Very Low' => 'Hieno/hyvin matala', + 'Normal/High' => 'Normaali/korkea', + 'Normal/Low' => 'Normaali/matala', + 'Normal/Medium' => 'Normaali/keskilaatu', + 'Normal/Medium High' => 'Normaali/korkeahko', + 'Normal/Medium Low' => 'Normaali/matalahko', + 'Normal/Super High' => 'Normaali/superkorkea', + 'Normal/Very High' => 'Normaali/hyvin korkea', + 'Normal/Very Low' => 'Normaali/hyvin matala', + 'Super Fine/High' => 'Superhieno/korkea', + 'Super Fine/Low' => 'Superhieno/matala', + 'Super Fine/Medium' => 'Superhieno/keskilaatu', + 'Super Fine/Medium High' => 'Superhieno/korkeahko', + 'Super Fine/Medium Low' => 'Superhieno/matalahko', + 'Super Fine/Super High' => 'Superhieno/superkorkea', + 'Super Fine/Very High' => 'Superhieno/hyvin korkea', + 'Super Fine/Very Low' => 'Superhieno/hyvin matala', + }, + }, + 'SanyoThumbnail' => 'Sanyo-näytekuva', + 'Saturation' => { + Description => 'Värikylläisyys', + PrintConv => { + '+1 (medium high)' => 'Korkeahko', + '+2 (high)' => 'Korkea', + '-1 (medium low)' => 'Matalahko', + '0 (normal)' => 'Vakio', + 'Film Simulation' => 'Filminsimulaatio', + 'High' => 'Korkea', + 'Low' => 'Matala', + 'Medium High' => 'Korkeahko', + 'Medium Low' => 'Matalahko', + 'None (B&W)' => 'Ei mitään (M&V)', + 'Normal' => 'Vakio', + }, + }, + 'SaturationFaithful' => 'Kohdeuskollinen värikylläisyys', + 'SaturationLandscape' => 'Maisemakuvan värikylläisyys', + 'SaturationNeutral' => 'Neutraali värikylläisyys', + 'SaturationPortrait' => 'Muotokuvan värikylläisyys', + 'SaturationSetting' => 'Värikylläisyyden sasetus', + 'SaturationStandard' => 'Vakiovärikylläisyys', + 'ScanImageEnhancer' => { + Description => 'Skannauskuvan parantelu', + PrintConv => { + 'Off' => 'Pois', + }, + }, + 'ScanningDirection' => { + Description => 'Skannaussuunta', + PrintConv => { + 'Bottom-Top, L-R' => 'Alhaalta ylös, vasemmalta oikealle', + 'Bottom-Top, R-L' => 'Alhaalta ylös, oikealta vasemmalle', + 'L-R, Bottom-Top' => 'Vasemmalta oikealle, alhaalta ylös', + 'L-R, Top-Bottom' => 'Vasemmalta oikealle, ylhäältä alas', + 'R-L, Bottom-Top' => 'Oikealta vasemmalle, alhaalta ylös', + 'R-L, Top-Bottom' => 'Oikealta vasemmalle, ylhäältä alas', + 'Top-Bottom, L-R' => 'Ylhäältä alas, vasemmalta oikealle', + 'Top-Bottom, R-L' => 'Ylhäältä alas, oikealta vasemmalle', + }, + }, + 'Scene' => 'Näyttämö', + 'SceneArea' => 'Näkymäalue?', + 'SceneAssist' => 'Näkymä-avustaja', + 'SceneCaptureType' => { + Description => 'Kuvatun näkymän tyyppi', + PrintConv => { + 'Landscape' => 'Maisema', + 'Night' => 'Yönäkymä', + 'Portrait' => 'Muotokuva', + 'Standard' => 'Standardi', + }, + }, + 'SceneDetect' => 'Kohtauksen tunnistus', + 'SceneMode' => { + Description => 'Näkymän muoto', + PrintConv => { + '2 in 1' => '2 1:ssä', + 'Auction' => 'Huutokauppa', + 'Auto' => 'Automaattinen', + 'Available Light' => 'Vallitseva valo', + 'Beach' => 'Hiekkaranta', + 'Beach & Snow' => 'Hiekkaranta&Lumi', + 'Behind Glass' => 'Lasin takaa', + 'Candle' => 'Kynttilä', + 'Children' => 'Lapset', + 'Cuisine' => 'Ateria', + 'Digital Image Stabilization' => 'Digitaalinen kuvanvakautus', + 'Documents' => 'Dokumentit', + 'Face Portrait' => 'Kasvokuva', + 'Fireworks' => 'Ilotulitus', + 'Food' => 'Ateria', + 'High Key' => 'High key -yläsävykuva', + 'Indoor' => 'Sisäkuva', + 'Landscape' => 'Maisema', + 'Landscape+Portrait' => 'Maisema+muotokuva', + 'Low Key' => 'Low key -alasävykuva', + 'Macro' => 'Makro', + 'Movie' => 'Elokuva', + 'Museum' => 'Museo', + 'My Mode' => 'Oma tapa', + 'Nature Macro' => 'Luontomakro', + 'Night Portrait' => 'Yön muotokuva', + 'Night Scene' => 'Iltanäkymä', + 'Night View/Portrait' => 'Iltanäkymä/Yön muotokuva', + 'Night+Portrait' => 'Yö+muotokuva', + 'Normal' => 'Normaali', + 'Panorama' => 'Panoraama', + 'Pet' => 'Lemmikki', + 'Portrait' => 'Muotokuva', + 'Self Portrait' => 'Omakuva', + 'Self Portrait+Self Timer' => 'Omakuva+itselaukaisin', + 'Self Protrait+Timer' => 'Omakuva+itselaukaisin', + 'Shoot & Select' => 'Kuvaa & Valitse', + 'Shoot & Select1' => 'Kuvaus&valinta 1', + 'Shoot & Select2' => 'Kuvaus&valinta 2', + 'Shooting Guide' => 'Kuvausopas', + 'Smile Shot' => 'Hymykuva', + 'Snow' => 'Lumi', + 'Sport' => 'Urheilu', + 'Sports' => 'Urheilutapahtuma', + 'Standard' => 'Vakio', + 'Sunset' => 'Auringonlasku', + 'Super Macro' => 'Supermakro', + 'Text' => 'Teksti', + 'Underwater Macro' => 'Veden alla makro', + 'Underwater Snapshot' => 'Veden alla tilannekuva', + 'Underwater Wide1' => 'Veden alla laaja 1', + 'Underwater Wide2' => 'Veden alla laaja 2', + 'Vivid' => 'Heleä', + }, + }, + 'SceneSelect' => { + Description => 'Näkymän valinta', + PrintConv => { + 'Lamp' => 'Lamppu', + 'Night' => 'Yö', + 'Off' => 'Pois', + 'Sport' => 'Urheilu', + 'User 1' => 'Käyttäjä 1', + 'User 2' => 'Käyttäjä 2', + }, + }, + 'SceneType' => { + Description => 'Näkymätyyppi', + PrintConv => { + 'Directly photographed' => 'Suoraan otettu valokuva', + }, + }, + 'SecurityClassification' => { + Description => 'Turvallisuusluokitus', + PrintConv => { + 'Confidential' => 'Luottamuksellinen', + 'Restricted' => 'Rajoitettu', + 'Secret' => 'Salainen', + 'Top Secret' => 'Huippusalainen', + 'Unclassified' => 'Ei luokiteltu', + }, + }, + 'SelfTimer' => { + Description => 'Itselaukaisin', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'SelfTimerMode' => 'Itselaukaisumoodi', + 'SelfTimerShotCount' => 'Itselaukaisimen otosmäärä', + 'SelfTimerTime' => 'Itselaukaisimen viive', + 'SensingMethod' => { + Description => 'Mittausmetodi', + PrintConv => { + 'Color sequential area' => 'Peräkkäisvärialueen anturi', + 'Color sequential linear' => 'Peräkkäisvärien lineaarianturi', + 'Monochrome area' => 'Monokromialueen anturi', + 'Monochrome linear' => 'Monokromilineaarinen anturi', + 'Not defined' => 'Ei määritetty', + 'One-chip color area' => 'Yhden chipin värialueen anturi', + 'Three-chip color area' => 'Kolmen chipin värialueen anturi', + 'Trilinear' => 'Trilineaarinen anturi', + 'Two-chip color area' => 'Kahden chipin värialueen anturi', + }, + }, + 'SensitivityAdjust' => 'Herkkyyden asetus', + 'SensitivitySteps' => 'Herkkyysaskeleet', + 'SensorBlueLevel' => 'Anturin sinitaso', + 'SensorBottomBorder' => 'Anturin alareuna', + 'SensorHeight' => 'Anturin korkeus', + 'SensorLeftBorder' => 'Anturin vasen reuna', + 'SensorPixelSize' => 'Anturin pikselikoko', + 'SensorRedLevel' => 'Anturin punataso', + 'SensorRightBorder' => 'Anturin oikea reuna', + 'SensorTemperature' => 'Anturin lämpötila', + 'SensorTopBorder' => 'Anturin yläreuna', + 'SensorWidth' => 'Anturin leveys', + 'Sequence' => 'Järjestys', + 'SequenceNumber' => 'Järjestysnumero', + 'SequenceShotInterval' => { + Description => 'Sarjakuvauksen taajuus', + PrintConv => { + '10 frames/s' => '10 ruutua/s', + '15 frames/s' => '15 ruutua/s', + '20 frames/s' => '20 ruutua/s', + '5 frames/s' => '5 ruutua/s', + }, + }, + 'SequentialShot' => { + Description => 'Sarjakuvaus', + PrintConv => { + 'Adjust Exposure' => 'Valotuksen säätö', + 'Best' => 'Paras', + 'None' => 'Ei mitään', + 'Standard' => 'Vakio', + }, + }, + 'SerialNumber' => 'Sarjanumero', + 'SerialNumberFormat' => 'Sarjanumeron muoto', + 'ServiceIdentifier' => 'Palvelun tunnistin', + 'ShadingCompensation' => { + Description => 'Varjostumakorjaus', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'Shadow' => 'Varjo', + 'ShadowProtection' => 'Varjoalueen suojaus ', + 'ShadowScale' => 'Varjoasteikko', + 'Sharpness' => { + Description => 'Terävyys', + PrintConv => { + '+1 (medium hard)' => 'Kovahko', + '+2 (hard)' => 'Kova', + '+3 (very hard)' => 'Kova 2', + '-1 (medium soft)' => 'Pehmeähkö', + '-2 (soft)' => 'Pehmeä 2', + '-3 (very soft)' => 'Pehmeä', + '0 (normal)' => 'Vakio', + 'Film Simulation' => 'Filminsimulaatio', + 'Hard' => 'Kova', + 'Hard2' => 'Kova 2', + 'Medium Hard' => 'Kovahko', + 'Medium Soft' => 'Pehmeähkö', + 'Normal' => 'Vakio', + 'Soft' => 'Pehmeä', + 'Soft2' => 'Pehmeä 2', + 'n/a' => 'E.s.', + }, + }, + 'SharpnessFactor' => 'Terävöintikerroin', + 'SharpnessFaithful' => 'Kohdeuskollinen terävyys', + 'SharpnessFreqTable' => 'Terävyyden taajuustaulukko', + 'SharpnessFrequency' => 'Terävyystaajuus', + 'SharpnessLandscape' => 'Maisemakuvan terävyys', + 'SharpnessMonochrome' => 'Yksivärikuvan terävyys', + 'SharpnessNeutral' => 'Neutraali terävyys', + 'SharpnessPortrait' => 'Muotokuvan terävyys', + 'SharpnessSetting' => 'Terävyysasetus', + 'SharpnessStandard' => 'Vakioterävyys', + 'SharpnessTable' => 'Terävyystaulukko', + 'ShootingInfoDisplay' => 'Kuvaustietojen näyttö', + 'ShootingMode' => 'IR-kauko-ohjaus', + 'ShortDocumentID' => 'Lyhyt dokumentin ID', + 'ShortOwnerName' => 'Omistajan lyhyt nimi', + 'ShotInfoVersion' => 'Otostietojen versio', + 'ShutterCount' => 'Laukaisumäärä', + 'ShutterReleaseButtonAE-L' => 'Laukaisin AE-L', + 'ShutterSpeedValue' => 'Suljinnopeuden arvo', + 'SimilarityIndex' => 'Yhtäläisyysindeksi', + 'Site' => 'Sivusto', + 'SlaveFlashMeteringSegments' => 'Orjasalaman mittaussegmentit', + 'SlowShutter' => 'Hidas suljin', + 'SlowSync' => { + Description => 'Hidas suljin', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'Software' => 'Ohjelmisto', + 'SoftwareVersion' => 'Ohjelmistoversio', + 'Source' => 'Lähde', + 'SourceImageDirectory' => 'Lähdekuvan kansio', + 'SourceImageFileName' => 'Lähdekuvan tiedostonimi', + 'SourceImageVolumeName' => 'Lähdekuvan taltion nimi', + 'SpatialFrequencyResponse' => 'Spatiaalisen taajuuden vaste', + 'SpecialInstructions' => 'Ohjeet', + 'SpecialMode' => 'Erikoistapa', + 'SpectralSensitivity' => 'Spektraalinen herkkyys', + 'Sport' => 'Urheilu', + 'SpotFocusPointX' => 'Pistetarkennuksen piste X', + 'SpotFocusPointY' => 'Pistetarkennuksen piste Y', + 'SpotMeteringMode' => 'Pistemittaustapa', + 'StorageMethod' => 'Tallennusmetodi', + 'StraightenAngle' => 'Kuvan suoristus', + 'StripByteCounts' => 'Tavuja per pakattu strippi', + 'StripOffsets' => 'Kuvadatan sijainti', + 'Sub-location' => 'Sijainti', + 'SubSecTime' => 'DateTime sekunnin murto-osina', + 'SubSecTimeDigitized' => 'DateTimeDigitized sekunnin murto-osina', + 'SubSecTimeOriginal' => 'DateTimeOriginal sekunnin murto-osina', + 'SubTileBlockSize' => 'Alalaattalohkon koko', + 'Subject' => 'Aihe', + 'SubjectArea' => 'Kohdealue', + 'SubjectDistance' => 'Kohteen etäisyys', + 'SubjectDistanceRange' => { + Description => 'Objektiivin tarkennusetäisyys', + PrintConv => { + 'Close' => 'Lähikuva', + 'Distant' => 'Etäkuva', + 'Macro' => 'Makro', + 'Unknown' => 'Tuntematon', + }, + }, + 'SubjectLocation' => 'Kohteen sijainti', + 'SubjectProgram' => 'Kuvausohjelma', + 'SubjectReference' => 'Aiheen koodi', + 'SuperMacro' => { + Description => 'Supermakro', + PrintConv => { + 'Off' => 'Pois', + 'On (1)' => 'Päällä (1)', + 'On (2)' => 'Päällä (2)', + }, + }, + 'SupplementalCategories' => 'Täydentävä kategoria', + 'SupplementalType' => { + Description => 'Lisäystyyppi', + PrintConv => { + 'Main Image' => 'Ei asetettu', + 'Rasterized Caption' => 'Rasteroitu seloste', + 'Reduced Resolution Image' => 'Alennetun resoluution kuva', + }, + }, + 'SvISOSetting' => 'Sv-ISO-asetus', + 'T4Options' => 'Lisätyt täytebitit', + 'T6Options' => 'T6-valinnat', + 'TIFF-EPStandardID' => 'TIFF/EP-standardin ID', + 'TargetAperture' => 'Tavoiteaukko', + 'TargetExposureTime' => 'Tavoitevalotusaika', + 'TargetPrinter' => 'Kohdetulostin', + 'Technology' => { + Description => 'Teknologia', + PrintConv => { + 'Digital Camera' => 'Digitaalikamera', + 'Dye Sublimation Printer' => 'Lämpösublimaatiotulostin', + 'Electrophotographic Printer' => 'Lasertulostin', + 'Electrostatic Printer' => 'Sähköstaattinen tulostin', + 'Film Scanner' => 'Filmiskanneri', + 'Film Writer' => 'Filmitulostin', + 'Flexography' => 'Fleksografia', + 'Gravure' => 'Syväpaino', + 'Ink Jet Printer' => 'Mustesuihkutulostin', + 'Offset Lithography' => 'Offsetlitografia', + 'Photo CD' => 'Valokuva-CD', + 'Photo Image Setter' => 'Valokuvaimagesetteri', + 'Photographic Paper Printer' => 'Valokuvapaperitulostin', + 'Projection Television' => 'Projektiotelevisio', + 'Reflective Scanner' => 'Heijastusskanneri', + 'Silkscreen' => 'Silkkipaino', + 'Thermal Wax Printer' => 'Lämpövahatulostin', + 'Video Camera' => 'Videokamera', + 'Video Monitor' => 'Videomonitori', + }, + }, + 'Text' => 'Teksti', + 'TextInfo' => 'Tekstitiedot', + 'TextStamp' => { + Description => 'Tekstileimasin', + PrintConv => { + 'Off' => 'Päällä', + }, + }, + 'Thresholding' => 'Kynnystys', + 'ThumbnailImage' => 'Näytekuva', + 'ThumbnailImageValidArea' => 'Näytekuvan kelpaava alue', + 'TileByteCounts' => 'Ruutujen tavumäärät', + 'TileDepth' => 'Ruudun syvyys', + 'TileLength' => 'Ruudun pituus', + 'TileOffsets' => 'Ruutusiirtymät', + 'TileWidth' => 'Otsikon leveys', + 'Time' => 'Aika', + 'TimeCreated' => 'Luontiaika', + 'TimeSent' => 'Lähetysaika', + 'TimeSincePowerOn' => 'Virran päälläoloaika', + 'TimeStamp' => 'Aikaleima', + 'TimeZone' => 'Aikavyöhyke', + 'TimeZoneOffset' => 'Aikavyöhykkeen siirtymä', + 'TimerFunctionButton' => 'Ajastustoimintopainike', + 'Title' => 'Otsikko', + 'ToneComp' => 'Sävykorjailu', + 'ToneCurve' => 'Sävykäyrä', + 'ToneCurveMatching' => 'Sävykäyrän täsmäys', + 'ToneCurveTable' => 'Sävykäyrätaulukko', + 'ToneCurves' => 'Sävykäyrät', + 'ToningEffect' => 'Sävytysefekti', + 'ToningEffectMonochrome' => 'Yksivärisävytysefekti', + 'ToningSaturation' => 'Sävykylläisyys', + 'TransferFunction' => 'Muuntotoiminto', + 'TransferRange' => 'Siirtonopeus', + 'Transform' => 'Muunna', + 'Transformation' => { + Description => 'Muunto', + PrintConv => { + 'Mirror horizontal' => 'Peilaa vaakasuunnassa', + 'Mirror horizontal and rotate 270 CW' => 'Peilaa vaakasuunnassa ja kierrä 270° myötäpäivään', + 'Mirror horizontal and rotate 90 CW' => 'Peilaa vaakasuunnassa ja kierrä 90° myötäpäivään', + 'Mirror vertical' => 'Peilaa pystysuunnassa', + 'Rotate 180' => 'Kierrä 180°', + 'Rotate 270 CW' => 'Kierrä 90° myötäpäivään', + 'Rotate 90 CW' => 'Kierrä 90° myötäpäivään', + }, + }, + 'TransparencyIndicator' => 'Läpinäkyvyyden ilmaisin', + 'TravelDay' => 'Matkapäivä', + 'TvExposureTimeSetting' => 'Tv-valotusaikaasetus', + 'Type' => 'Tyyppi', + 'Uncompressed' => { + Description => 'Pakkaamaton', + PrintConv => { + 'No' => 'Ei', + 'Yes' => 'Kyllä', + }, + }, + 'UniqueCameraModel' => 'Uniikki kameramalli', + 'UniqueDocumentID' => 'Uniikin dokumentin ID', + 'Unknown' => 'Tuntematon', + 'Unsharp1Color' => 'Terävöinti 1 -väri', + 'Unsharp1HaloWidth' => 'Terävöinti 1 -halolaajuus', + 'Unsharp1Intensity' => 'Terävöinti 1 -voimakkuus', + 'Unsharp1Threshold' => 'Terävöinti 1 -kynnys', + 'Unsharp2Color' => 'Terävöinti 2 -väri', + 'Unsharp2HaloWidth' => 'Terävöinti 2 -halolaajuus', + 'Unsharp2Intensity' => 'Terävöinti 2 -voimakkuus', + 'Unsharp2Threshold' => 'Terävöinti 2 -kynnys', + 'Unsharp3Color' => 'Terävöinti 3 -väri', + 'Unsharp3HaloWidth' => 'Terävöinti 3 -halolaajuus', + 'Unsharp3Intensity' => 'Terävöinti 3 -voimakkuus', + 'Unsharp3Threshold' => 'Terävöinti 3 -kynnys', + 'Unsharp4Color' => 'Terävöinti 4 -väri', + 'Unsharp4HaloWidth' => 'Terävöinti 4 -halolaajuus', + 'Unsharp4Intensity' => 'Terävöinti 4 -voimakkuus', + 'Unsharp4Threshold' => 'Terävöinti 4 -kynnys', + 'UnsharpCount' => 'Terävöintimäärä', + 'UnsharpData' => 'Terävöintidata', + 'UnsharpMask' => 'Epäterävä maski', + 'Urgency' => { + Description => 'Kiireellisyys', + PrintConv => { + '0 (reserved)' => '0 (varattu tulevaan käyttöön)', + '1 (most urgent)' => '1 (hyvin kiireellinen)', + '5 (normal urgency)' => '5 (normaali kiirellisyys)', + '8 (least urgent)' => '8 (lievän kiireellinen)', + '9 (user-defined priority)' => '9 (varattu tulevaan käyttöön)', + }, + }, + 'UserComment' => 'Käyttäjän kommentti', + 'UserSelectGroupTitle' => 'Käyttäjän valitseman ryhmän otsikko', + 'VRDOffset' => 'VRD-siirtymä', + 'VRInfo' => 'Kuvanvakaimen tiedot', + 'VRInfoVersion' => 'VR- (kuvanvakain) tietojen versio', + 'ValidAFPoints' => 'Kelpaavat AF-pisteet', + 'VariProgram' => 'Kuvausohjelma', + 'Version' => 'Versio', + 'VibrationReduction' => 'Kuvanvakautus', + 'VideoCardGamma' => 'Grafiikkakortin gamma', + 'ViewfinderWarning' => 'Etsinvaroitus', + 'ViewingCondDesc' => 'Katseluolojen kuvaus', + 'VignetteControl' => { + Description => 'Vinjetoinnin korjaus', + PrintConv => { + 'High' => 'Korkea', + 'Low' => 'Matala', + 'Normal' => 'Normaali', + 'Off' => 'Pois', + }, + }, + 'VignetteControlIntensity' => 'Vinjetoinnin korjauksen voimakkuus', + 'VoiceMemo' => { + Description => 'Äänen tallennus', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'WBAdjData' => 'Valkotaspainon säätödata', + 'WBBlueLevel' => 'Valkotas. sinisen taso', + 'WBBracketMode' => 'Valkotasapaino-haarukointitapa', + 'WBBracketValueAB' => 'Valkotasapainon AB-haarukointiarvo', + 'WBBracketValueGM' => 'Valkotasapainon GM-haarukointiarvo', + 'WBGreenLevel' => 'Vihreän taso', + 'WBMode' => { + Description => 'Valkotasapainotila', + PrintConv => { + 'Auto' => 'Automaattinen', + }, + }, + 'WBRedLevel' => 'Valkotas. punaisen taso', + 'WBShiftAB' => 'Valkotas. säätö keltainen/sininen', + 'WBShiftGM' => 'Valkotas. säätö vihreä/magenta', + 'WB_RBLevels' => 'Valkotasapaino punainen/sininen', + 'WB_RBLevelsAuto' => 'Valkotas. RB-tasot, automatiikka', + 'WB_RBLevelsCoolWhiteFluor' => 'Valkotas. RB-tasot, viileän valk. loistel.', + 'WB_RBLevelsDayWhiteFluor' => 'Valkotas. RB-tasot, valk. päivänvaloloistel.', + 'WB_RBLevelsDaylightFluor' => 'Valkotas. RB-tasot, päivänvaloloistel.', + 'WB_RBLevelsEveningSunlight' => 'Valkotas. RB-tasot, ilta-aurinko', + 'WB_RBLevelsFineWeather' => 'Valkotas. RB-tasot, kaunis sää', + 'WB_RBLevelsShade' => 'Valkotas. RB-tasot, varjo', + 'WB_RBLevelsTungsten' => 'Valkotas. RB-tasot, hehkulamppu', + 'WB_RBLevelsUsed' => 'Käytetyt valkotas. RB-tasot', + 'WB_RBLevelsWhiteFluorescent' => 'Valkotas. RB-tasot, valk. loistelamppu', + 'WB_RGGBLevelsCloudy' => 'Valkotasapainon RGGB-tasojen pilvinen', + 'WB_RGGBLevelsDaylight' => 'Valkotasapainon RGGB-tasojen päivänvalo', + 'WB_RGGBLevelsFlash' => 'Valkotasapainon RGGB-tasojen salama', + 'WB_RGGBLevelsFluorescentD' => 'Valkotasapainon RGGB-tasojen loistelamppu D', + 'WB_RGGBLevelsFluorescentN' => 'Valkotasapainon RGGB-tasojen loistelamppu N', + 'WB_RGGBLevelsFluorescentW' => 'Valkotasapainon RGGB-tasojen loistelamppu W', + 'WB_RGGBLevelsShade' => 'Valkotasapainon RGGB-tasojen varjo', + 'WB_RGGBLevelsTungsten' => 'Valkotasapainon RGGB-tasojen hehkulamppu', + 'Watermark' => 'Vesileima', + 'WatermarkType' => 'Vesileiman tyyppi', + 'WhiteBalance' => { + Description => 'Valkotasapaino', + PrintConv => { + 'Auto' => 'Automaattinen', + 'Cloudy' => 'Pilvinen', + 'Custom' => 'Mukautus', + 'Custom2' => 'Mukautus 2', + 'Custom3' => 'Mukautus 3', + 'Custom4' => 'Mukautus 4', + 'Day White Fluorescent' => 'Valkoinen päivänvaloloistelamppu', + 'Daylight' => 'Auringonvalo', + 'Daylight Fluorescent' => 'Päivänvaloloistelamppu', + 'Flash' => 'Salama', + 'Incandescent' => 'Hehkulamppu', + 'Living Room Warm White Fluorescent' => 'Asuinhuoneen lämmin valkoinen loistelamppu', + 'Manual' => 'Manuaalinen', + 'Warm White Fluorescent' => 'Lämmin valkoinen loistelamppu', + 'White Fluorescent' => 'Valkoinen loistelamppu', + }, + }, + 'WhiteBalance2' => { + Description => 'Valkotasapaino', + PrintConv => { + '3000K (Tungsten light)' => '3000 K (hehkulamppu)', + '3600K (Tungsten light-like)' => '3600 K (hehkulamppumainen valo)', + '4000K (Cool white fluorescent)' => '4000 K (viileän valkoinen loistelamppu)', + '4500K (Neutral white fluorescent)' => '4500 K (neutraali valkoinen loistelamppu)', + '5300K (Fine Weather)' => '5300 K (kaunis sää)', + '6000K (Cloudy)' => '6000 K (pilvinen)', + '6600K (Daylight fluorescent)' => '6600 K (päivänvaloloistelamppu)', + '7500K (Fine Weather with Shade)' => '7500 K (kaunis sää ja varjoja)', + 'Auto' => 'Automaattinen', + 'Custom WB 1' => 'Mukautettu valkotasapaino 1', + 'Custom WB 2' => 'Mukautettu valkotasapaino 2', + 'Custom WB 2900K' => 'Mukautettu valkotasapaino 2900 K', + 'Custom WB 3' => 'Mukautettu valkotasapaino 3', + 'Custom WB 4' => 'Mukautettu valkotasapaino 4', + 'Custom WB 5400K' => 'Mukautettu valkotasapaino 5400 K', + 'Custom WB 8000K' => 'Mukautettu valkotasapaino 8000 K', + 'One Touch WB 1' => 'Mukautettu valkotasapaino 1', + 'One Touch WB 2' => 'Mukautettu valkotasapaino 2', + 'One Touch WB 3' => 'Mukautettu valkotasapaino 3', + 'One Touch WB 4' => 'Mukautettu valkotasapaino 4', + }, + }, + 'WhiteBalanceAdj' => 'Väritasapainon säätö', + 'WhiteBalanceBias' => 'Valkotasapainon korjaus', + 'WhiteBalanceBlue' => 'Valkotasapaino sininen', + 'WhiteBalanceBracket' => 'Valkotasapainon haarukointi', + 'WhiteBalanceBracketing' => 'Valkotasapainohaarukointi', + 'WhiteBalanceComp' => 'Valkotasapainon säätö', + 'WhiteBalanceFineTune' => 'Valkotasapainon hienosäätö', + 'WhiteBalanceMatching' => 'Valkotasapainon täsmäys', + 'WhiteBalanceMode' => { + Description => 'Valkotasapainon muoto', + PrintConv => { + 'Auto (Cloudy)' => 'Automaattinen (pilvinen)', + 'Auto (Day White Fluorescent)' => 'Automaattinen (valkoinen päivänvaloloistelamppu)', + 'Auto (Daylight Fluorescent)' => 'Automaattinen (päivänvaloloistelamppu)', + 'Auto (Daylight)' => 'Automaattinen (auringonvalo)', + 'Auto (Flash)' => 'Automaattinen (salama)', + 'Auto (Shade)' => 'Automaattinen (varjo)', + 'Auto (Tungsten)' => 'Automaattinen (hehkulamppu)', + 'Auto (White Fluorescent)' => 'Automaattinen (valkoinen loistelamppu)', + 'Unknown' => 'Automaattinen (ei tunnistettu)', + 'User-Selected' => 'Itse valittu', + }, + }, + 'WhiteBalanceRed' => 'Valkotasapaino punainen', + 'WhiteBalanceSet' => 'Asetettu valkotasapaino', + 'WhiteBalanceTable' => 'Valkotasapainotaulukko', + 'WhiteBalanceTemperature' => 'Valkotaspainon lämpötila', + 'WhiteBoard' => 'Valkoinen pohja', + 'WhiteLevel' => 'Valkoinen taso', + 'WhitePoint' => 'Valkoinen piste', + 'WhitePointX' => 'Valkoinen piste X', + 'WhitePointY' => 'Valkoinen piste Y', + 'Wide' => 'Laaja', + 'WideFocusZone' => 'Laaja-alatarkennuksen alue', + 'WideRange' => { + Description => 'Laaja alue', + PrintConv => { + 'Off' => 'Pois', + 'On' => 'Päällä', + }, + }, + 'WorldTimeLocation' => { + Description => 'Maailmanaikasijainti', + PrintConv => { + 'Destination' => 'Kohde', + 'Home' => 'Koti', + 'Hometown' => 'Kotiseutu', + }, + }, + 'Writer-Editor' => 'Selosteen/Kuvatekstin kirjoittaja', + 'X3FillLight' => 'X3-täytevalo', + 'XClipPathUnits' => 'X-leikepolkuyksiköt', + 'XMP' => 'XMP-metadata', + 'XPosition' => 'X-sijainti', + 'XResolution' => 'Kuvan vaakaresoluutio', + 'YCbCrCoefficients' => 'Väriavaruuden muunnon matriisikertoimet', + 'YCbCrPositioning' => { + Description => 'Y:n ja C:n sijoitus', + PrintConv => { + 'Centered' => 'Keskitetty', + 'Co-sited' => 'Vierekkäin', + }, + }, + 'YCbCrSubSampling' => 'Suhteen Y - C subsamplaus', + 'YClipPathUnits' => 'Y-leikepolkuyksiköt', + 'YPosition' => 'Y-sijainti', + 'YResolution' => 'Kuvan pystyresoluutio', + 'ZoneMatching' => { + Description => 'Yli/alivalotuksen esto', + PrintConv => { + 'High Key' => 'Yläsävykuva', + 'ISO Setting Used' => 'Pois (käytetty ISO-asetusta)', + 'Low Key' => 'Alasävykuva', + }, + }, + 'Zoom' => 'Zoom-objektiivi', + 'ZoomSourceWidth' => 'Zoomauksen lähtökoko', + 'ZoomStepCount' => 'Zoom-askelmäärä', + 'ZoomTargetWidth' => 'Zoomauksen loppukoko', +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::fi.pm - ExifTool Finnish language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke and Jarkko ME<auml>kineva for providing this +translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/fr.pm b/ExifTool/lib/Image/ExifTool/Lang/fr.pm new file mode 100644 index 0000000..6c00dce --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/fr.pm @@ -0,0 +1,11290 @@ +#------------------------------------------------------------------------------ +# File: fr.pm +# +# Description: ExifTool French language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::fr; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.36'; + +%Image::ExifTool::Lang::fr::Translate = ( + 'AEAperture' => 'Ouverture AE', + 'AEApertureSteps' => 'Pas d\'ouverture de l\\’exposition automatique', + 'AEBAutoCancel' => { + Description => 'Annulation bracketing auto', + PrintConv => { + 'Off' => 'Arrêt', + 'On' => 'Marche', + }, + }, + 'AEBBracketValue' => 'Valeur du braquet AEB', + 'AEBSequence' => { + Description => 'Séquence de bracketing AEB', + PrintConv => { + '+,0,-' => '+0', + '-,0,+' => '-0', + }, + }, + 'AEBSequenceAutoCancel' => { + Description => 'Séquence auto AEB/annuler', + PrintConv => { + '-,0,+/Disabled' => '-,0,+/Désactivé', + '-,0,+/Enabled' => '-,0,+/Activé', + '0,-,+/Disabled' => '0,-,+/Désactivé', + '0,-,+/Enabled' => '0,-,+/Activé', + }, + }, + 'AEBShotCount' => 'Nombre de vues bracketées', + 'AEBXv' => 'Compensation d\'expo. auto en bracketing', + 'AECSnapshotDigitalGain' => 'Gain numérique de l\'instantané AEC', + 'AEExposureTime' => 'Temps d\'exposition AE', + 'AEExtra' => 'Suppléments AE', + 'AEInfo' => 'Info sur l\'exposition auto', + 'AELock' => { + Description => 'Verrouillage AE', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'AELockMeterModeAfterFocus' => { + PrintConv => { + 'Center-weighted' => 'Pondération centrale', + }, + }, + 'AEMaxAperture' => 'Ouverture maxi AE', + 'AEMaxAperture2' => 'Ouverture maxi AE (2)', + 'AEMeteringMode' => { + Description => 'Mode de mesure AE', + PrintConv => { + 'Center-weighted average' => 'Moyenne pondérée centrale', + 'Multi-segment' => 'Multizone', + }, + }, + 'AEMeteringSegments' => 'Segments de mesure AE', + 'AEMinAperture' => 'Ouverture mini AE', + 'AEMinExposureTime' => 'Temps d\'exposition mini AE', + 'AEProgramMode' => { + Description => 'Mode programme AE', + PrintConv => { + 'Av, B or X' => 'Av, B ou X', + 'Candlelight' => 'Bougie', + 'DOF Program' => 'Programme PdC', + 'DOF Program (P-Shift)' => 'Programme PdC (décalage P)', + 'Hi-speed Program' => 'Programme grande vitesse', + 'Hi-speed Program (P-Shift)' => 'Programme grande vitesse (décalage P)', + 'Kids' => 'Enfants', + 'Landscape' => 'Paysage', + 'M, P or TAv' => 'M, P ou TAv', + 'MTF Program' => 'Programme FTM', + 'MTF Program (P-Shift)' => 'Programme FTM (décalage P)', + 'Museum' => 'Musée', + 'Night Scene' => 'Nocturne', + 'Night Scene Portrait' => 'Portrait nocturne', + 'No Flash' => 'Sans flash', + 'Pet' => 'Animaux de compagnie', + 'Sunset' => 'Coucher de soleil', + 'Surf & Snow' => 'Surf et neige', + 'Sv or Green Mode' => 'Sv ou mode vert', + 'Text' => 'Texte', + }, + }, + 'AESetting' => { + Description => 'Réglage d\'exposition automatique', + PrintConv => { + 'AE Lock' => 'Verrouillage de l\'exposition automatique', + 'AE Lock + Exposure Comp.' => 'Verrouillage de l\'exposition automatique+Compensations d\'exposition', + 'Exposure Compensation' => 'Compensations d\'exposition', + 'No AE' => 'Pas d\'exposition automatique', + 'Normal AE' => 'Exposition automatique normale', + }, + }, + 'AEXv' => 'Compensation d\'exposition auto', + 'AE_ISO' => 'Sensibilité ISO AE', + 'AFAdjustment' => 'Ajustement AF', + 'AFAperture' => 'Ouverture de l\'autofocus', + 'AFArea' => 'Zone d\'autofocus', + 'AFAreaHeight' => 'Hauteur de la zone de l\'autofocus', + 'AFAreaHeights' => 'Hauteurs des zones de l\'autofocus', + 'AFAreaIllumination' => { + Description => 'Illumination de la zone d\'autofocus', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'AFAreaMode' => { + Description => 'Mode de la zone d\'autofocus', + PrintConv => { + '1-area' => 'Mise au point 1 zone', + '1-area (high speed)' => 'Mise au point 1 zone (haute vitesse)', + '225-area' => 'Zone 225', + '23-area' => 'Zone 23', + '3-area (center)?' => 'Mise au point 3 zones (au centre) ?', + '3-area (high speed)' => 'Mise au point 3 zones (haute vitesse)', + '3-area (left)?' => 'Mise au point 3 zones (à gauche) ?', + '3-area (right)?' => 'Mise au point 3 zones (à droite) ?', + '3D-tracking' => 'Suivi en 3D', + '49-area' => 'Zone 49', + '5-area' => 'Mise au point 5 zones', + '9-area' => 'Mise au point 9 zones', + 'AF Point Expansion (4 point)' => 'Extension du point AF (4 points)', + 'AF Point Expansion (8 point)' => 'Extension du point AF (8 points)', + 'AF Point Expansion (surround)' => 'Extension du point AF (surround)', + 'Animal Eye Tracking' => 'Suivi de l\'Å“il des animaux', + 'Auto (Animals)' => 'Auto (Animaux)', + 'Auto (People)' => 'Auto (Personnes)', + 'Auto-area' => 'Zone auto', + 'Center' => 'Centré', + 'Contrast-detect' => 'Détection de contraste', + 'Contrast-detect (face priority)' => 'Détection de contraste (priorité au visage)', + 'Contrast-detect (normal area)' => 'Détection de contraste (zone normale)', + 'Contrast-detect (subject tracking)' => 'Détection de contraste (suivi du sujet)', + 'Contrast-detect (wide area)' => 'Détection de contraste (large zone)', + 'Default' => 'Par défaut', + 'Dynamic' => 'Dynamique', + 'Dynamic Area' => 'Zone dynamique', + 'Dynamic Area (21 points)' => 'Zone dynamique (21 points)', + 'Dynamic Area (25 points)' => 'Zone dynamique (25 points)', + 'Dynamic Area (3D-tracking)' => 'Zone dynamique (suivi 3D)', + 'Dynamic Area (49 points)' => 'Zone dynamique (49 points)', + 'Dynamic Area (51 points)' => 'Zone dynamique (51 points)', + 'Dynamic Area (51 points, 3D-tracking)' => 'Zone dynamique (51 points, suivi 3D)', + 'Dynamic Area (72 points)' => 'Zone dynamique (72 points)', + 'Dynamic Area (9 points)' => 'Zone dynamique (9 points)', + 'Dynamic Area (L)' => 'Zone dynamique (L)', + 'Dynamic Area (M)' => 'Zone dynamique (M)', + 'Dynamic Area (S)' => 'Zone dynamique (S)', + 'Dynamic Area (closest subject)' => 'Zone dynamique (sujet le plus proche)', + 'Dynamic Area (wide)' => 'Zone dynamique (large)', + 'Dynamic Area (wide, 3D-tracking)' => 'Zone dynamique (large, suivi 3D)', + 'Dynamic-area' => 'Zone dynamique', + 'Expanded Flexible Spot' => 'Spot flexible étendu', + 'Face + Tracking' => 'Visage + suivi', + 'Face Detect' => 'Détection de visage', + 'Face Detect (animal detect off)' => 'Détection de visage (détection d\'animaux désactivée)', + 'Face Detect (animal detect on)' => 'Détection de visage (détection d\'animaux activée)', + 'Face Detect AF' => 'Détection de visage', + 'Face Priority (41 points)' => 'Priorité au visage (41 points)', + 'Face Tracking' => 'Suivi du visage', + 'Face-priority AF' => 'Priorité aux visages AF', + 'Flexible Spot' => 'Spot flexible', + 'Flexizone Multi (49 point)' => 'Flexizone Multi (49 points)', + 'Flexizone Multi (9 point)' => 'Flexizone Multi (9 points)', + 'Flexizone Single' => 'Flexizone unique)', + 'Group Area' => 'Zone du groupe', + 'Group Area (HL)' => 'Zone du groupe (HL)', + 'Group Area (VL)' => 'Zone du groupe (HL)', + 'Group Dynamic' => 'Dynamique de groupe', + 'Large Zone AF' => 'Grande zone AF', + 'Local' => 'Locale', + 'Manual' => 'Manuelle', + 'Normal-area AF' => 'Zone normale AF', + 'Normal?' => 'Normale ?', + 'Off (Manual Focus)' => 'Désactivée (mise au point manuelle)', + 'Pinpoint' => 'Point de mire', + 'Pinpoint focus' => 'Point de mire de mise au point', + 'Selective (for Miniature effect)' => 'Sélective', + 'Single' => 'Simple', + 'Single Area' => 'Zone unique', + 'Single Area (wide)' => 'Zone unique (large)', + 'Single Point' => 'Point unique', + 'Single-point' => 'Point unique', + 'Single-point AF' => 'Mise au point automatique à point unique', + 'Spot Focusing' => 'Mise au point Spot', + 'Spot Mode Off' => 'Mode Spot désactivé', + 'Spot Mode On' => 'Mode Spot enclenché', + 'Subject Tracking (41 points)' => 'Suivi du sujet (41 points)', + 'Subject-tracking AF' => 'Suivi du sujet AF', + 'Touch' => 'Tactile', + 'Tracking' => 'Suivi', + 'Wide' => 'Large', + 'Wide (C1)' => 'Large (C1)', + 'Wide (C2)' => 'Large (C2)', + 'Wide (L)' => 'Large (L)', + 'Wide (L-animals)' => 'Large (L-animaux)', + 'Wide (L-people)' => 'Large (L-personnes)', + 'Wide (S)' => 'Large (S)', + 'Wide-area AF' => 'Zone large AF', + 'Wide/Tracking' => 'Large/Suivi', + 'n/a' => 'Non applicable', + }, + }, + 'AFAreaModeSetting' => { + Description => 'Réglage du mode de la zone d\'autofocus', + PrintConv => { + '3D-tracking (11 points)' => 'Suivi 3D (11 points)', + 'Auto-area' => 'Zone automatique', + 'Center' => 'Centre', + 'Center (LA-EA4)' => 'Centre (LA-EA4)', + 'Closest Subject' => 'Sujet le plus proche', + 'Dynamic Area' => 'Zone dynamique', + 'Expanded Flexible Spot' => 'Spot flexible étendu', + 'Flexible Spot' => 'Spot flexible', + 'Flexible Spot (LA-EA4)' => 'Spot flexible (LA-EA4)', + 'Single Area' => 'Zone unique', + 'Wide' => 'Large', + }, + }, + 'AFAreaPointSize' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'AFAreaWidth' => 'Largeur de la zone de l\'autofocus', + 'AFAreaWidths' => 'Largeurs des zones de l\'autofocus', + 'AFAreaXPosition' => 'Position horizontale Zone de l\'autofocus', + 'AFAreaXPosition1' => 'Position horizontale 1 Zone de l\'autofocus', + 'AFAreaXPositions' => 'Positions horizontales Zone de l\'autofocus', + 'AFAreaYPosition' => 'Position verticale Zone de l\'autofocus', + 'AFAreaYPosition1' => 'Position verticale 1 Zone de l\'autofocus', + 'AFAreaYPositions' => 'Positions verticales Zone de l\'autofocus', + 'AFAreaZoneSize' => { + Description => 'Taille de la zone de l\'autofocus', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'AFAreas' => 'Zones d\'autofocus', + 'AFAssist' => { + Description => 'Faisceau d\'assistance de l\'autofocus', + PrintConv => { + 'Does not emit/Fires' => 'N\'émet pas/Se déclenche', + 'Emits/Does not fire' => 'Émet/Ne se déclenche pas', + 'Emits/Fires' => 'émet/Se déclenche', + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'Only ext. flash emits/Fires' => 'Seul le flash ext. émet/se déclenche', + }, + }, + 'AFAssistBeam' => { + Description => 'Faisceau d\'assistance de l\'autofocus', + PrintConv => { + 'Does not emit' => 'Désactivé', + 'Emits' => 'Activé', + 'Only ext. flash emits' => 'Uniquement par flash ext.', + }, + }, + 'AFAssistLamp' => { + Description => 'Lampe d\'assistance de l\'autofocus', + PrintConv => { + 'Disabled and Not Required' => 'Désactivée et non requise', + 'Disabled but Required' => 'Désactivée mais requise', + 'Enabled but Not Used' => 'Activée mais non utilisée', + 'Fired' => 'Déclenchée', + }, + }, + 'AFButtonPressed' => { + Description => 'Bouton Auto Focus pressé', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'AFDefocus' => 'Défocalisation AF', + 'AFDuringLiveView' => { + Description => 'AF pendant la visée directe', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + 'Live mode' => 'Mode visée directe', + 'Quick mode' => 'Mode rapide', + }, + }, + 'AFIlluminator' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'AFImageHeight' => 'Hauteur de l\'image d\'Autofocus', + 'AFImageWidth' => 'Largeur de l\'image d\'Autofocus', + 'AFInfo' => 'Info autofocus', + 'AFInfo2' => 'Infos AF', + 'AFInfo2Version' => 'Version des infos de l\'Autofocus', + 'AFIntegrationTime' => 'Temps d\'intégration de l\'Autofocus', + 'AFMicroadjustment' => { + Description => 'Micro-ajustement de l\'AF', + PrintConv => { + 'Adjust all by same amount' => 'Ajuster idem tous obj', + 'Adjust by lens' => 'Ajuster par objectif', + 'Disable' => 'Désactivé', + }, + }, + 'AFMode' => 'Mode de l\'Autofocus', + 'AFOnAELockButtonSwitch' => { + Description => 'Permutation touche AF/Mémo', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + }, + }, + 'AFOnButton' => { + PrintConv => { + 'AE Lock (hold)' => 'Verrouillage de l\'exposition automatique (maintien)', + 'AWB Lock (hold)' => 'Verrouillage de la balance des blancs automatique (maintien)', + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'AFPoint' => { + Description => 'Point de l\'Autofocus', + PrintConv => { + '(none)' => '(aucun)', + 'All Target' => 'Toutes les cibles', + 'Auto AF point selection' => 'Sélection automatique du point d\'autofocus', + 'Bottom' => 'En bas', + 'Bottom (horizontal)' => 'En bas (horizontal)', + 'Bottom (vertical)' => 'En bas (vertical)', + 'Bottom Assist-left' => 'En bas aide à gauche', + 'Bottom Assist-right' => 'En bas aide à droite', + 'Bottom Center' => 'En bas centré', + 'Bottom Left' => 'En bas à gauche', + 'Bottom Right' => 'En bas à droite', + 'Bottom-center (horizontal)' => 'En bas centré (horizontal)', + 'Bottom-center (vertical)' => 'En bas centré (vertical)', + 'Bottom-left' => 'En bas à gauche', + 'Bottom-left (horizontal)' => 'En bas à gauche (horizontal)', + 'Bottom-left (vertical)' => 'En bas à gauche (vertical)', + 'Bottom-right' => 'En bas à droite', + 'Bottom-right (horizontal)' => 'En bas à droite (horizontal)', + 'Bottom-right (vertical)' => 'En bas à droite (vertical)', + 'Center' => 'Au Centre', + 'Center (10)' => 'Au Centre (10)', + 'Center (11)' => 'Au Centre (11)', + 'Center (12)' => 'Au Centre (12)', + 'Center (14)' => 'Au Centre (14)', + 'Center (7)' => 'Au Centre (7)', + 'Center (9)' => 'Au Centre (9)', + 'Center (horizontal)' => 'Au Centre (horizontal)', + 'Center (vertical)' => 'Au Centre (vertical)', + 'Center F2.8' => 'Au Centre F2.8', + 'Center Left' => 'Au Centre à gauche', + 'Center Right' => 'Au Centre à droite', + 'Center Vertical' => 'Au Centre vertical', + 'Dynamic Single Target' => 'Cible unique dynamique', + 'E6 Center' => 'E6 Centré', + 'E6 Center F2.8' => 'E6 Centré F2.8', + 'E6 Center Vertical' => 'E6 Centré vertical', + 'Face Detect' => 'Détection de visage', + 'Far Left' => 'À l\'extrême-gauche', + 'Far Left (horizontal)' => 'À l\'extrême gauche (horizontal)', + 'Far Left (vertical)' => 'À l\'extrême gauche (vertical)', + 'Far Left/Right of Center' => 'À l\'extrême gauche/droite du centre', + 'Far Left/Right of Center/Bottom' => 'À l\'extrême gauche/droite du centre/en bas', + 'Far Right' => 'À l\'extrême-droit', + 'Far Right (horizontal)' => 'À l\'extrême droite (horizontal)', + 'Far Right (vertical)' => 'À l\'extrême droite (vertical)', + 'Left' => 'À gauche', + 'Left (horizontal)' => 'À gauche (horizontal)', + 'Left (or n/a)' => 'À gauche (ou non applicable)', + 'Left (vertical)' => 'À gauche (vertical)', + 'Lower Far Left' => 'En bas à l\'extrême gauche', + 'Lower Far Right' => 'En bas à l\'extrême droite', + 'Lower-left' => 'En bas à gauche', + 'Lower-left (horizontal)' => 'En bas à gauche (horizontal)', + 'Lower-left (vertical)' => 'En bas à gauche (vertical)', + 'Lower-middle' => 'En bas au centre', + 'Lower-right' => 'En bas à droite', + 'Lower-right (horizontal)' => 'En bas à droite (horizontal)', + 'Lower-right (vertical)' => 'En bas à droite (vertical)', + 'Manual AF point selection' => 'Sélection manuelle du point de mise au point', + 'Mid-left' => 'Au Milieu à gauche', + 'Mid-left (horizontal)' => 'Au Milieu (horizontal)', + 'Mid-left (vertical)' => 'Au Milieu (vertical)', + 'Mid-right' => 'Au milieu à droite', + 'Mid-right (horizontal)' => 'Au milieu à droite (horizontal)', + 'Mid-right (vertical)' => 'Au milieu à droite (vertical)', + 'Middle Horizontal' => 'Au milieu horizontal', + 'Near Left' => 'Près de la gauche', + 'Near Left/Right of Center' => 'Près de la gauche/droite du centre', + 'Near Right' => 'Près de la gauche', + 'Near Upper/Left' => 'Près du haut à gauche', + 'None' => 'Aucun', + 'None (MF)' => 'Aucun (MF)', + 'Right' => 'À droite', + 'Right (horizontal)' => 'À droite (horizontal)', + 'Right (vertical)' => 'À droite (vertical)', + 'Single Target' => 'Cible unique', + 'Top' => 'En haut', + 'Top (horizontal)' => 'En haut (horizontal)', + 'Top (vertical)' => 'En haut (vertical)', + 'Top Assist-left' => 'En haut aide à gauche', + 'Top Assist-right' => 'En haut aide à droite', + 'Top Center' => 'En haut au centre', + 'Top Left' => 'En haut à gauche', + 'Top Near-left' => 'En haut près de la gauche', + 'Top Near-right' => 'En haut près de la droite', + 'Top Right' => 'En haut à droite', + 'Top-center (horizontal)' => 'En haut au centre (horizontal)', + 'Top-center (vertical)' => 'En haut au centre (vertical)', + 'Top-left' => 'En haut à gauche', + 'Top-left (horizontal)' => 'En haut à gauche (horizontal)', + 'Top-left (vertical)' => 'En haut à gauche (vertical)', + 'Top-right' => 'En haut à droite', + 'Top-right (horizontal)' => 'En haut à droite (horizontal)', + 'Top-right (vertical)' => 'En haut à droite (vertical)', + 'Upper Far Left' => 'En haut à l\'extrême gauche', + 'Upper Far Right' => 'En haut à l\'extrême droite', + 'Upper Left' => 'En haut à gauche', + 'Upper Right' => 'En haut à droite', + 'Upper-left' => 'En haut à gauche', + 'Upper-left (horizontal)' => 'En haut à gauche (horizontal)', + 'Upper-left (vertical)' => 'En haut à gauche (vertical)', + 'Upper-middle' => 'En haut au centre', + 'Upper-right' => 'En haut à droite', + 'Upper-right (horizontal)' => 'En haut à droite (horizontal)', + 'Upper-right (vertical)' => 'En haut à droite (vertical)', + 'n/a' => 'Non applicable', + }, + }, + 'AFPointActivationArea' => { + Description => 'Zone d\'activation des points de l\'autofocus', + PrintConv => { + 'Automatic expanded (max. 13)' => 'Expansion auto (13 max.)', + 'Expanded (TTL. of 7 AF points)' => 'Expansion (TTL 7 collimat.)', + 'Single AF point' => 'Un seul collimateur AF', + }, + }, + 'AFPointAreaExpansion' => { + Description => 'Extension de la zone d\'autofocus', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + 'Left/right AF points' => 'Activé (gauche/droite collimateurs autofocus d\'assistance)', + 'Surrounding AF points' => 'Activée (Collimateurs autofocus d\'assistance environnants)', + }, + }, + 'AFPointAutoSelection' => { + Description => 'Sélection des collimateurs automatique', + PrintConv => { + 'Control-direct:disable/Main:disable' => 'Contrôle rapide-Directe:désactivé/Principale:désactivé', + 'Control-direct:disable/Main:enable' => 'Contrôle rapide-Directe:désactivé/Principale:activé', + 'Control-direct:enable/Main:enable' => 'Contrôle rapide-Directe:activé/Principale:activé', + }, + }, + 'AFPointBrightness' => { + Description => 'Intensité d\'illumination de l\'autofocus', + PrintConv => { + 'Brighter' => 'Forte', + 'Normal' => 'Normale', + }, + }, + 'AFPointDisplayDuringFocus' => { + Description => 'Affichage des points d\'autofocus pendant la mise au point', + PrintConv => { + 'All (constant)' => 'Tous (constant)', + 'Disable display' => 'Désactiver l\'affichage', + 'Disabled' => 'Désactivé', + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'On (when focus achieved)' => 'Activé (lorsque la mise au point est effectuée)', + 'Selected (constant)' => 'Sélectionné (constant)', + 'Selected (focused)' => 'Sélectionné (focalisé)', + 'Selected (pre-AF, focused)' => 'Sélectionné (pré-auto focus, focalisé)', + }, + }, + 'AFPointIllumination' => { + Description => 'Illumination des points d\'autofocus', + PrintConv => { + 'Brighter' => 'Plus brillant', + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'On During Manual Focusing' => 'Activé pendant la mise au point manuelle', + 'On in Continuous Shooting Modes' => 'Activé en modes de prise de vue continue', + 'On in Continuous Shooting and Manual Focusing' => 'Activé en prise de vue continue et en mise au point manuelle', + 'On without dimming' => 'Activé sans atténuation', + }, + }, + 'AFPointMode' => { + Description => 'Mode de mise au point automatique', + PrintConv => { + 'Fixed Center' => 'Centre fixe', + 'Select' => 'Sélectionner', + }, + }, + 'AFPointPosition' => 'Position du point AF', + 'AFPointRegistration' => { + Description => 'Enregistrement du point AF', + PrintConv => { + 'Automatic' => 'Auto', + 'Bottom' => 'En bas', + 'Center' => 'Au centre', + 'Extreme Left' => 'À l\'extrême gauche', + 'Extreme Right' => 'À l\'extrême droite', + 'Left' => 'À gauche', + 'Right' => 'À droite', + 'Top' => 'En haut', + }, + }, + 'AFPointSelected' => { + Description => 'Point AF sélectionné', + PrintConv => { + 'Automatic Tracking AF' => 'AF en suivi auto', + 'Bottom' => 'Bas', + 'Center' => 'Centre', + 'Face Detect AF' => 'AF en reconnaissance de visage', + 'Fixed Center' => 'Fixe au centre', + 'Left' => 'Gauche', + 'Lower-left' => 'Bas gauche', + 'Lower-right' => 'Bas droit', + 'Mid-left' => 'Milieu gauche', + 'Mid-right' => 'Milieu droit', + 'Right' => 'Droit', + 'Top' => 'Haut', + 'Upper-left' => 'Haut gauche', + 'Upper-right' => 'Haut droite', + 'n/a' => 'Non applicable', + }, + }, + 'AFPointSelected2' => 'Point AF sélectionné 2', + 'AFPointSelection' => 'Méthode sélect. collimateurs AF', + 'AFPointSelectionMethod' => { + Description => 'Méthode sélection collim. AF', + PrintConv => { + 'Multi-controller direct' => 'Multicontrôleur direct', + 'Normal' => 'Normale', + 'Quick Control Dial direct' => 'Molette AR directe', + }, + }, + 'AFPointSpotMetering' => { + Description => 'Nombre collimateurs/mesure spot', + PrintConv => { + '11/Active AF point' => '11/collimateur AF actif', + '11/Center AF point' => '11/collimateur AF central', + '45/Center AF point' => '45/collimateur AF central', + '9/Active AF point' => '9/collimateur AF actif', + }, + }, + 'AFPointsInFocus' => { + Description => 'Points d\'autofocus dans le focus', + PrintConv => { + '(none)' => '(aucun)', + 'All' => 'Tous', + 'All 11 Points' => 'Tous les 11 points', + 'Bottom' => 'En bas', + 'Bottom Near-left' => 'En bas près de la gauche', + 'Bottom Near-right' => 'En bas près de la droite', + 'Bottom, Center' => 'En Bas, centré', + 'Bottom-center' => 'En Bas, centré', + 'Bottom-left' => 'En bas à gauche', + 'Bottom-right' => 'En bas à droite', + 'Center' => 'Au centre', + 'Center (horizontal)' => 'Au centre (horizontal)', + 'Center (vertical)' => 'Au centre (vertical)', + 'Center+Right' => 'Au centre et à droite)', + 'Far Left' => 'À l\'extrême gauche', + 'Far Right' => 'À l\'extrême droite', + 'Fixed Center or Multiple' => 'Centre fixe ou multiple', + 'Left' => 'À gauche', + 'Left+Center' => 'À Gauche et au centre', + 'Left+Right' => 'À Gauche et à droite', + 'Lower Near-left' => 'En bas près de la gauche', + 'Lower Near-right' => 'En bas près de la droite', + 'Lower-left' => 'En bas à gauche', + 'Lower-left, Bottom' => 'En Bas à gauche et en bas', + 'Lower-left, Mid-left' => 'En Bas à gauche et au milieu à gauche', + 'Lower-middle' => 'En bas au milieu', + 'Lower-right' => 'En bas à droite', + 'Lower-right, Bottom' => 'En bas à droite et en bas', + 'Lower-right, Mid-right' => 'En bas à droite et au milieu à droite', + 'Mid-left' => 'Au milieu à gauche', + 'Mid-left, Center' => 'Au milieu et au centre', + 'Mid-right' => 'Au milieu à droite', + 'Mid-right, Center' => 'Au milieu à droite et au centre', + 'Near-left' => 'Près de la gauche', + 'Near-right' => 'Près de la droite', + 'None' => 'Aucun', + 'None (MF)' => 'Aucun (MF)', + 'Right' => 'À droite', + 'Top' => 'En haut', + 'Top Near-left' => 'En haut près de la gauche', + 'Top Near-right' => 'En haut près de la droite', + 'Top, Center' => 'En haut, au centre', + 'Top-center' => 'En haut, au centre', + 'Top-left' => 'En haut à gauche', + 'Top-right' => 'En haut à droite', + 'Upper Near-left' => 'En haut près de la gauche', + 'Upper Near-right' => 'En haut près de la doite', + 'Upper-left' => 'En haut à gauche', + 'Upper-left, Mid-left' => 'En haut à gauche, au milieu à gauche', + 'Upper-left, Top' => 'En haut à gauche, en haut', + 'Upper-middle' => 'En haut au milieu', + 'Upper-right' => 'En haut à droite', + 'Upper-right, Mid-right' => 'En haut à droite, au milieu à droit', + 'Upper-right, Top' => 'En haut à droite, en haut', + }, + }, + 'AFPointsInFocus1D' => 'Points d\'autofocus dans le focus 1D', + 'AFPointsInFocus5D' => { + Description => 'Points d\'autofocus dans le focus 5D', + PrintConv => { + '(none)' => '(aucun)', + 'AI Servo1' => 'IA Servo 1', + 'AI Servo2' => 'IA Servo 2', + 'AI Servo3' => 'IA Servo 3', + 'AI Servo4' => 'IA Servo 4', + 'AI Servo5' => 'IA Servo 5', + 'AI Servo6' => 'IA Servo 6', + 'Bottom' => 'En bas', + 'Center' => 'Au centre', + 'Left' => 'À gauche', + 'Lower-left' => 'En bas à gauche', + 'Lower-right' => 'En bas à droite', + 'Right' => 'À droite', + 'Top' => 'En haut', + 'Upper-left' => 'En haut à gauche', + 'Upper-right' => 'En haut à droite', + }, + }, + 'AFPointsSelected' => { + Description => 'Points d\'Autofocus sélectionnés', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'AFPointsUnknown1' => { + PrintConv => { + 'All' => 'Tous', + 'Central 9 points' => '9 points centraux', + }, + }, + 'AFPointsUnknown2' => 'Points AF inconnus 2', + 'AFPointsUsed' => { + Description => 'Points de mise au point automatique utilisés', + PrintConv => { + '(none)' => '(aucun)', + 'All 11 Points' => 'Tous les 11 points', + 'Bottom' => 'En bas', + 'Center' => 'Au centre', + 'Far Left' => 'À l\'extrême gauche', + 'Far Right' => 'À l\'extrême droite', + 'Left' => 'À gauche', + 'Lower Far Left' => 'À l\'extrême bas gauche', + 'Lower Far Right' => 'À l\'extrême bas droite', + 'Lower-left' => 'En bas à gauche', + 'Lower-middle' => 'Au milieu inférieur', + 'Lower-right' => 'En bas à droite', + 'Mid-left' => 'Au milieu gauche', + 'Mid-right' => 'Au milieu droit', + 'Near Left' => 'Près de la gauche', + 'Near Right' => 'Près de la droite', + 'Right' => 'À droite', + 'Top' => 'En haut', + 'Upper Far Left' => 'À l\'extrême haut gauche', + 'Upper Far Right' => 'À l\'extrême haut droit', + 'Upper-left' => 'En haut à gauche', + 'Upper-middle' => 'Au milieu supérieur', + 'Upper-right' => 'En haut à droite', + }, + }, + 'AFPredictor' => 'Prédicteur AF', + 'AFResponse' => 'Réponse AF', + 'AFSearch' => { + Description => 'Recherche de l\'autofocus', + PrintConv => { + 'Not Ready' => 'Non prête', + 'Ready' => 'Prête', + }, + }, + 'AFTracking' => { + Description => 'Suivi de la mise au point automatique', + PrintConv => { + 'Face tracking' => 'Suivi du visage', + 'Lock On AF' => 'Verrouillage de la mise au point automatique', + 'Off' => 'Désactivé', + }, + }, + 'AFTrackingSensitivity' => 'Sensibilité du suivi de la mise au point automatique', + 'AFType' => { + Description => 'Type de mise au point automatique', + PrintConv => { + '15-point' => '15 points', + '19-point' => '19 points', + '79-point' => '79 points', + }, + }, + 'AIColorUsage' => 'Usage des couleurs AIM', + 'AICreatorVersion' => 'Version créateur AIM', + 'AIFCSummary' => 'Résumé AIM', + 'AIFileFormat' => 'Format fichier AIM', + 'AINumLayers' => 'Calques AIM', + 'AIRulerUnits' => { + Description => 'Unités règles AIM', + PrintConv => { + 'Centimeters' => 'Centimètres', + 'Inches' => 'Pouces', + 'Millimeters' => 'Milimètres', + }, + }, + 'AIServoContinuousShooting' => 'Priorité vit. méca. AI Servo', + 'AIServoImagePriority' => { + Description => '1er Servo Ai/2e priorité déclenchement', + PrintConv => { + '1: AF, 2: Drive speed' => 'Priorité AF/Priorité cadence vues', + '1: AF, 2: Tracking' => 'Priorité AF/Priorité suivi AF', + '1: Release, 2: Drive speed' => 'Déclenchement/Priorité cadence vues', + }, + }, + 'AIServoTrackingMethod' => { + Description => 'Méthode de suivi autofocus AI Servo', + PrintConv => { + 'Continuous AF track priority' => 'Priorité suivi AF en continu', + 'Main focus point priority' => 'Priorité point AF principal', + }, + }, + 'AIServoTrackingSensitivity' => { + Description => 'Sensibili. de suivi AI Servo', + PrintConv => { + 'Fast' => 'Rapide', + 'Medium Fast' => 'Moyenne rapide', + 'Medium Slow' => 'Moyenne lent', + 'Moderately fast' => 'Moyennement rapide', + 'Moderately slow' => 'Moyennement lent', + 'Slow' => 'Lent', + }, + }, + 'APEVersion' => 'Version APE', + 'APS-CSizeCapture' => { + Description => 'Capture de la taille APS-C', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ARMIdentifier' => 'Identificateur ARM', + 'ARMVersion' => 'Version ARM', + 'AToB0' => 'A à B0', + 'AToB1' => 'A à B1', + 'AToB2' => 'A à B2', + 'Acceleration' => 'Accélération', + 'AccelerationTracking' => 'Suivi de l\'accélération', + 'AccelerationVector' => 'Vecteurs d\'accélération', + 'AccelerometerX' => 'Accéléromètre X', + 'AccelerometerY' => 'Accéléromètre Y', + 'AccelerometerZ' => 'Accéléromètre Z', + 'AccessoryType' => 'Type d\'accessoire', + 'ActionAdvised' => { + Description => 'Action conseillée', + PrintConv => { + 'Object Append' => 'Ajouter un objet', + 'Object Kill' => 'Détruire un objet', + 'Object Reference' => 'Référencer un objet', + 'Object Replace' => 'Remplacer un objet', + 'Ojbect Append' => 'Ajout d\'objet', + }, + }, + 'ActiveArea' => 'Zone active', + 'ActiveD-Lighting' => { + Description => 'D-Lighting actif', + PrintConv => { + 'Extra High' => 'Extra haut', + 'Extra High 1' => 'Extra haut 1', + 'Extra High 2' => 'Extra haut 2', + 'Extra High 3' => 'Extra haut 3', + 'Extra High 4' => 'Extra haut 4', + 'High' => 'Haut', + 'Low' => 'Bas', + 'Normal' => 'Normale', + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ActiveD-LightingMode' => { + PrintConv => { + 'Low' => 'Bas', + 'Normal' => 'Normale', + 'Off' => 'Désactivé', + }, + }, + 'AddAspectRatioInfo' => { + Description => 'Info ratio d\'aspect ajouté', + PrintConv => { + 'Off' => 'Désactivé', + }, + }, + 'AddOriginalDecisionData' => { + Description => 'Aj. données décis. origine', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'AdditionalModelInformation' => 'Modèle d\'Information additionnel', + 'Address' => 'Adresse', + 'AdjustmentMode' => 'Mode de réglage', + 'AdultContentWarning' => { + PrintConv => { + 'Unknown' => 'Inconnu', + }, + }, + 'AdvancedFilter' => { + PrintConv => { + 'Hi Key' => 'Tons clairs', + 'Low Key' => 'Tons sombres', + 'Pop Color' => 'Couleur pop', + 'Soft Focus' => 'Flou artistique', + 'Toy Camera' => 'Caméra jouet', + }, + }, + 'AdvancedRaw' => { + Description => 'RAW avancé', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'AdvancedSceneMode' => { + Description => 'Mode Scène avancé', + PrintConv => { + 'Aerial Photo / Underwater / Multi-aspect' => 'Photo aérienne / Sous-marine / Multi-aspect', + 'Beach' => 'Plage', + 'Bleach Bypass' => 'Traitement sans blanchiment', + 'Cinema' => 'Cinéma', + 'Color Select' => 'Désaturation partielle', + 'Creative Macro' => 'Macro créative', + 'Creative Night Scenery' => 'Scène nocturne créative', + 'Creative Portrait' => 'Portrait créatif', + 'Creative Scenery' => 'Scènes créatives', + 'Creative Sports' => 'Sports créatifs', + 'Cross Process' => 'Traitement croisé', + 'Dynamic Art' => 'Art dynamique', + 'Dynamic Monochrome' => 'Monochrome dynamique', + 'Elegant' => 'Élegant', + 'Expressive' => 'Expressif', + 'Fantasy' => 'Fantaisie', + 'Fireworks' => 'Feux d\'artifices', + 'Flower' => 'Fleur', + 'HDR Art' => 'HDR artistique', + 'HDR B&W' => 'HDR N&B', + 'Handheld Night Shot' => 'Photo nocturne à main levée', + 'High Dynamic' => 'Haute dynamique', + 'High Key' => 'Tons clairs', + 'High Sensitivity' => 'Haute Sensibilité', + 'High-speed Burst (shot 1)' => 'Rafale haute vitesse (prise de vue 1)', + 'High-speed Burst (shot 2)' => 'Rafale haute vitesse (prise de vue 2)', + 'High-speed Burst (shot 3)' => 'Rafale haute vitesse (prise de vue 3)', + 'Impressive Art' => 'Impressionisme', + 'Indoor Portrait' => 'Portrait en intérieur', + 'Indoor Sports' => 'Sports en intérieur', + 'Low Key' => 'Tons sombres', + 'Miniature' => 'Effet miniature', + 'Off' => 'Désactivé', + 'Old Days' => 'Vieux jours', + 'Outdoor Portrait' => 'Portrait en extérieur', + 'Outdoor Sports' => 'Sports en extérieur', + 'Retro' => 'Rétro', + 'Rough Monochrome' => 'Monochrome brut', + 'Sepia' => 'Sépia', + 'Silky Monochrome' => 'Monochrome soyeux', + 'Snow' => 'Neige', + 'Soft' => 'Mise au point douce', + 'Star' => 'Filtre étoile', + 'Starry Sky' => 'Ciel étoilé', + 'Sunshine' => 'Ensoleillée', + 'Toy Effect' => 'Effet jouet', + 'Toy Pop' => 'Jouet Pop', + }, + }, + 'AdvancedSceneType' => 'Type de scène avancé', + 'Advisory' => 'Adversité', + 'AlarmUID' => 'UID Alarme', + 'AmbienceSelection' => { + Description => 'Sélecteur d\'ambiance', + PrintConv => { + 'Brighter' => 'Plus lumineux', + 'Cool' => 'Frais', + 'Darker' => 'Plus sombre', + 'Soft' => 'Douce', + 'Vivid' => 'Éclatant', + 'Warm' => 'Chaud', + }, + }, + 'AmbientTemperature' => 'Température ambiante', + 'AmbientTemperatureFahrenheit' => 'Température ambiante en Fahrenheit', + 'AnalogBalance' => 'Balance analogique', + 'AngleAxis' => 'Angle de l\'axe', + 'AngleInfoRoll' => 'Angle de roulis', + 'AngleInfoYaw' => 'Angle de lacet', + 'AngleNumber' => 'Numéro de l\'angle', + 'AngleOfAttack' => 'Angle d\\attaque', + 'AngleToNorth' => 'Angle par rapport au Nord', + 'AngularPosition' => 'Position angulaire', + 'AngularUnitKind' => 'Type d\'unité angulaire', + 'AngularVelocity' => 'Vélocité angulaire', + 'Annotations' => 'Annotations Photoshop', + 'Anti-Blur' => { + Description => 'Anti-flou', + PrintConv => { + 'Off' => 'Désactivé', + 'On (Continuous)' => 'Activé (continu)', + 'On (Shooting)' => 'Activé (Prise de vue)', + 'n/a' => 'Non applicable', + }, + }, + 'AntiAliasStrength' => 'Puissance relative du filtre anticrénelage de l\'appareil', + 'Aperture' => 'Ouverture', + 'ApertureDisplayed' => 'Ouverture affichée', + 'ApertureRange' => { + Description => 'Régler gamme d\'ouvertures', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activée', + }, + }, + 'ApertureRingUse' => { + Description => 'Utilisation de la bague de diaphragme', + PrintConv => { + 'Permitted' => 'Autorisée', + 'Prohibited' => 'Interdite', + }, + }, + 'ApertureValue' => 'Ouverture', + 'ApplicationRecordVersion' => 'Version d\'enregistrement de l\'application', + 'ApplyShootingMeteringMode' => { + Description => 'Appliquer mode de prise de vue/de mesure', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activée', + }, + }, + 'ApproximateFNumber' => 'Diaphragme Æ’ aproximatif', + 'ArtFilter' => { + PrintConv => { + 'Cross Process' => 'Traitement croisé', + 'Cross Process II' => 'Traitement croisé II', + 'Partial Color' => 'Couleur partielle', + 'Partial Color II' => 'Couleur partielle II', + 'Partial Color III' => 'Couleur partielle III', + 'Soft Focus' => 'Flou artistique', + 'Soft Focus 2' => 'Flou artistique 2', + }, + }, + 'ArtFilterEffect' => { + PrintConv => { + 'Cross Process' => 'Traitement croisé', + 'Cross Process II' => 'Traitement croisé II', + 'Partial Color' => 'Couleur partielle', + 'Partial Color II' => 'Couleur partielle II', + 'Partial Color III' => 'Couleur partielle III', + 'Soft Focus' => 'Flou artistique', + 'Soft Focus 2' => 'Flou artistique 2', + }, + }, + 'ArtMode' => { + PrintConv => { + 'Art HDR' => 'HDR artistique', + 'Light Tone' => 'Tonalité claire', + 'Painting' => 'Peinture', + 'Toy Camera' => 'Caméra jouet', + }, + }, + 'Artist' => 'Artiste', + 'Artist2' => 'Artiste 2', + 'ArtworkCopyrightNotice' => 'Notice copyright de l\'Illustration', + 'ArtworkCreator' => 'Créateur de l\'Illustration', + 'ArtworkDateCreated' => 'Date de création de l\'Illustration', + 'ArtworkSource' => 'Source de l\'Illustration', + 'ArtworkSourceInventoryNo' => 'No d\'Inventaire du source de l\'Illustration', + 'ArtworkTitle' => 'Titre de l\'Illustration', + 'AsShotICCProfile' => 'Profil ICC à la prise de vue', + 'AsShotNeutral' => 'Balance neutre à la prise de vue', + 'AsShotPreProfileMatrix' => 'Matrice de pré-profil à la prise de vue', + 'AsShotProfileName' => 'Nom du profil du cliché', + 'AsShotWhiteXY' => 'Balance blanc X-Y à la prise de vue', + 'AspectRatio' => { + Description => 'Ratio d\'aspect', + PrintConv => { + '16:9, 525 line, NTSC' => '16:9, 525 lignes, NTSC', + '16:9, 625 line, PAL' => '16:9, 625 lignes, PAL', + '3:2 (APS-C crop)' => '3:2 (recadrage APS-C)', + '3:2 (APS-H crop)' => '3:2 (recadrage APS-H))', + '4:3 crop' => '4:3 recadré', + '4:3, 525 line, NTSC, CCIR601' => '4:3, 525 lignes, NTSC, CCIR601', + '4:3, 625 line, PAL, CCIR601' => '4:3, 625 lignes, PAL, CCIR601', + }, + }, + 'AspectRatioType' => { + Description => 'Type de ratio d\'aspect', + PrintConv => { + 'Fixed' => 'Fixe', + 'Free Resizing' => 'Redimensionnement libre', + 'Keep Aspect Ratio' => 'Conserver le ratio d\'aspect', + }, + }, + 'AssignFuncButton' => { + Description => 'Changer fonct. touche FUNC.', + PrintConv => { + 'Exposure comp./AEB setting' => 'Correct. expo/réglage AEB', + 'Image jump with main dial' => 'Saut image par molette principale', + 'Image quality' => 'Changer de qualité', + 'LCD brightness' => 'Luminosité LCD', + 'Live view function settings' => 'Réglages Visée par l’écran', + }, + }, + 'AssignMB-D17FuncButtonPlusDials' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'AssignMB-D18FuncButtonPlusDials' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'AssignMovieRecordButton' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'AssistButtonFunction' => { + Description => 'Touche de fonction rapide', + PrintConv => { + 'Av+/- (AF point by QCD)' => 'Av+/- (AF par mol. AR)', + 'FE lock' => 'Mémo expo. au flash', + 'Normal' => 'Normale', + 'Select HP (while pressing)' => 'Sélect. HP (en appuyant)', + 'Select Home Position' => 'Sélect. position origine', + }, + }, + 'Audio' => { + PrintConv => { + 'No' => 'Non', + 'Stereo' => 'Stéréo', + 'Yes' => 'Oui', + }, + }, + 'AudioBitRateControlMode' => 'Mode de contrôle du Bitrate audio', + 'AudioBitrate' => 'Bitrate audio', + 'AudioBitrateMode' => { + Description => 'Mode Bitrate audio', + PrintConv => { + 'Fixed' => 'Fixe', + }, + }, + 'AudioButton' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HD', + }, + }, + 'AudioButtonPlaybackMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HD', + }, + }, + 'AudioDuration' => 'Durée audio', + 'AudioOutcue' => 'Queue audio', + 'AudioSamplingRate' => 'Taux d\'échantillonnage audio', + 'AudioSamplingResolution' => 'Résolution d\'échantillonnage audio', + 'AudioType' => { + Description => 'Type audio', + PrintConv => { + 'Mono Actuality' => 'Actualité (audio mono (1 canal)', + 'Mono Music' => 'Musique, transmise par elle-même (audio mono (1 canal)', + 'Mono Question and Answer Session' => 'Question et réponse (audio mono (1 canal)', + 'Mono Raw Sound' => 'Son brut (audio mono (1 canal)', + 'Mono Response to a Question' => 'Réponse à une question (audio mono (1 canal)', + 'Mono Scener' => 'Scener (audio mono (1 canal)', + 'Mono Voicer' => 'Voix (audio mono (1 canal)', + 'Mono Wrap' => 'Wrap (audio mono (1 canal)', + 'Stereo Actuality' => 'Actualité (audio stéréo (2 canaux)', + 'Stereo Music' => 'Musique, transmise par elle-même (audio stéréo (2 canaux)', + 'Stereo Question and Answer Session' => 'Question et réponse (audio stéréo (2 canaux)', + 'Stereo Raw Sound' => 'Son brut (audio stéréo (2 canaux)', + 'Stereo Response to a Question' => 'Réponse à une question (audio stéréo (2 canaux)', + 'Stereo Scener' => 'Scener (audio stéréo (2 canaux)', + 'Stereo Voicer' => 'Voix (audio stéréo (2 canaux)', + 'Stereo Wrap' => 'Wrap (audio stéréo (2 canaux)', + 'Text Only' => 'Texte seul (pas de données d\'objet)', + }, + }, + 'Author' => 'Auteur', + 'AuthorsPosition' => 'Titre du créateur', + 'AutoAperture' => { + Description => 'Auto-diaph', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'AutoBracket' => 'Auto bracketing', + 'AutoBracketSet' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'AutoBracketing' => { + Description => 'Bracketing auto', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'AutoBracketingSet' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'AutoDistortionControl' => { + Description => 'Contrôle de distorsion automatique', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'On (underwater)' => 'Activé (sous l\'eau)', + }, + }, + 'AutoExposureBracketing' => { + Description => 'Bracketing d\'exposition automatique', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'On (shot 1)' => 'Activé (prise de vue 1)', + 'On (shot 2)' => 'Activé (prise de vue 2)', + 'On (shot 3)' => 'Activé (prise de vue 3)', + }, + }, + 'AutoFP' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'AutoFocus' => { + Description => 'Auto-Focus', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'AutoISO' => { + Description => 'ISO auto', + PrintConv => { + 'High Speed' => 'Haute vitesse', + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'On (anti-shake)' => 'Activé (anti-vibration)', + 'On (high sensitivity)' => 'Activé (haute sensibilité)', + }, + }, + 'AutoLightingOptimizer' => { + Description => 'Correction auto de luminosité', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Actif', + 'Low' => 'Faible', + 'Off' => 'Désactivé', + 'Strong' => 'Importante', + 'n/a' => 'Non applicable', + }, + }, + 'AutoLightingOptimizerOn' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'AutoPortraitFramed' => { + Description => 'Autoportrait encadré', + PrintConv => { + 'No' => 'Non', + }, + }, + 'AutoRedEye' => { + Description => 'Correction yeux-rouges automatique', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'AutoRotate' => { + Description => 'Rotation automatique', + PrintConv => { + 'None' => 'Aucune', + 'Rotate 180' => 'Rotation de 180°', + 'Rotate 270 CW' => 'Rotation antihoraire de 270°', + 'Rotate 90 CW' => 'Rotation antihoraire de 90°', + 'n/a' => 'Non applicable', + }, + }, + 'AuxiliaryImageType' => 'Type d\'image auxiliaire', + 'AuxiliaryLens' => 'Objectif Auxiliaire', + 'AvApertureSetting' => 'Réglage d\'ouverture Av', + 'AvSettingWithoutLens' => { + Description => 'Réglage Av sans objectif', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + }, + }, + 'AverageFrameRate' => 'Fréquence d\'images moyenne', + 'BToA0' => 'B à A0', + 'BToA1' => 'B à A1', + 'BToA2' => 'B à A2', + 'BWMode' => { + Description => 'Mode N&B', + PrintConv => { + '(none)' => '(aucun))', + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'BabyAge' => 'Âge du bébé', + 'BabyName' => 'Nom du bébé', + 'BackLight' => { + Description => 'Contre-jour', + PrintConv => { + 'Back Lit 1' => 'Rétroéclairage 1', + 'Back Lit 2' => 'Rétroéclairage 1', + 'Front Lit' => 'Lumière frontale', + }, + }, + 'BackgroundColorIndicator' => 'Indicateur de couleur d\'arrière-plan', + 'BackgroundColorValue' => 'Valeur de couleur d\'arrière-plan', + 'BackgroundTiling' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'BadFaxLines' => 'Mauvaises lignes de Fax', + 'BannerImageType' => { + PrintConv => { + 'None' => 'Aucune', + }, + }, + 'BannerImageURL' => 'URL de l\'image-bannière', + 'Barcode' => 'Code-barres', + 'BarcodeSymbology' => 'Symbologie du code-barres', + 'BarcodeValue' => 'Valeur du code-barres', + 'Barcodes' => 'Codes-barres', + 'BaseExposureCompensation' => 'Compensation d\'exposition de référence', + 'BaseISO' => 'ISO de référence', + 'BaseISODaylight' => 'ISO de référence Lumière du jour', + 'BaseISOFlash' => 'ISO de référence Flash', + 'BaseISOFluorescent' => 'ISO de référence Fluorescent', + 'BaseISOTungsten' => 'ISO de référence Tungstène', + 'BaseName' => 'Nom de référence', + 'BaseURL' => 'URL de référence', + 'BaselineExposure' => 'Exposition de référence', + 'BaselineNoise' => 'Bruit de référence', + 'BaselineSharpness' => 'Netteté de référence', + 'BatteryInfo' => 'Source d\'alimentation', + 'BatteryLevel' => { + Description => 'Batterie', + PrintConv => { + 'Full' => 'Totalement chargée', + 'Low' => 'Niveau bas', + 'Medium' => 'Niveau moyen', + 'Medium Low' => 'Niveau moyen bas', + 'Near Empty' => 'Presque vide', + 'Near Full' => 'Presque totalement chargée', + 'n/a' => 'Non applicable', + }, + }, + 'BatteryTemperature' => 'Température de la batterie', + 'BayerGreenSplit' => 'Séparation de vert Bayer', + 'Beep' => { + PrintConv => { + 'High' => 'Bruyant', + 'Low' => 'Calme', + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'BeepVolume' => { + Description => 'Volume du bip', + PrintConv => { + 'Off' => 'Désactivé', + }, + }, + 'BestQualityScale' => 'Meilleure balance de qualité', + 'BestShotMode' => { + Description => 'Meilleur mode de prise de vue', + PrintConv => { + 'Anti Shake' => 'Anti-vibration', + 'Auction' => 'Enchères', + 'Auto Best Shot' => 'Meilleure prise de vue automatique', + 'Auto Framing' => 'Cadrage automatique', + 'Autumn Leaves' => 'Feuilles d\'automne', + 'Baby CS' => 'Bébé CS', + 'Backlight' => 'Contre-jour', + 'Beach' => 'Plage', + 'Blurred Background' => 'Arrière-plan flou', + 'Business Cards' => 'Cartes de visite', + 'Candlelight Portrait' => 'Portrait à la chandelle', + 'Child CS' => 'Enfant CS', + 'Child High Speed Movie' => 'Vidéo d\'enfant à grande vitesse', + 'Children' => 'Enfants', + 'Cross Filter' => 'Filtre transversal', + 'Dynamic Photo' => 'Photo dynamique', + 'Fashion Accessories' => 'Accessoires de mode', + 'Fireworks' => 'Feux d\'artifice', + 'Flower' => 'Fleur', + 'Food' => 'Alimentation', + 'For YouTube' => 'Pour YouTube', + 'For eBay' => 'Pour eBay', + 'HDR Art' => 'HDR artistique', + 'High Sensitivity' => 'Haute Sensibilité', + 'High Speed Anti Shake' => 'Anti-vibration à grande vitesse', + 'High Speed Best Selection' => 'Meilleure sélection à haute vitesse', + 'High Speed CS' => 'Haute vitesse CS', + 'High Speed Lighting' => 'Éclairage à haute vitesse', + 'High Speed Night Scene' => 'Scène nocturne à haute vitesse', + 'High Speed Night Scene and Portrait' => 'Scène nocturne à haute vitesse et Portrait', + 'High Speed Night Shot' => 'Photo nocturne à haute vitesse', + 'Interval Movie' => 'Film à intervalles', + 'Interval Snapshot' => 'Instantané à intervalle', + 'Lag Correction' => 'Correction du décalage', + 'Layout (2 images)' => 'Mise en page (2 images)', + 'Layout (3 images)' => 'Mise en page (3 images)', + 'Light Tone' => 'Tonalités claires', + 'Move In CS' => 'Entrer CS', + 'Move Out CS' => 'Sortir CS', + 'Movie' => 'Vidéo', + 'Multi SR Zoom' => 'Zoom SR multiple', + 'Multi-motion Image' => 'Image multi-mouvement', + 'Natural Green' => 'Vert naturel', + 'Night Scene' => 'Scène nocturne', + 'Night Scene Portrait' => 'Portrait de scène nocturne', + 'Off' => 'Désactivé', + 'Oil Painting' => 'Peinture à l\'huile', + 'Old Photo' => 'Vieille photo', + 'Party' => 'Fête', + 'Past Movie' => 'Vidéo passée', + 'People' => 'Personnes', + 'Pet' => 'Animal de compagnie', + 'Pet CS' => 'Animal de compagnie CS', + 'Pet High Speed Movie' => 'Film animalier à grande vitesse', + 'Portrait with Scenery' => 'Portrait avec paysage', + 'Pre-record Movie' => 'Pré-enregistrement de la vidéo', + 'Premium Auto' => 'Auto Premium', + 'Prerecord (Movie)' => 'Pré-enregistrement (Vidéo)', + 'Retro' => 'Rétro', + 'Scenery' => 'Paysage', + 'Self Slow Motion (behind)' => 'Ralenti automatique (arrière)', + 'Self Slow Motion (front)' => 'Ralenti automatique (avant)', + 'Self-portrait (1 person)' => 'Autoportrait (1 personne)', + 'Self-portrait (2 people)' => 'Autoportrait (2 personnes)', + 'Sepia' => 'Sépia', + 'Short Movie' => 'Court-métrage', + 'Silent' => 'Silencieux', + 'Slide Panorama' => 'Panorama de diapositives', + 'Slow Motion Swing (behind)' => 'Swing au ralenti (derrière)', + 'Slow Motion Swing (front)' => 'Swing au ralenti (avant)', + 'Snow' => 'Neige', + 'Soft Flowing Water' => 'Eau douce qui coule', + 'Soft Focus' => 'Flou artistique', + 'Splashing Water' => 'Éclaboussures d\'eau', + 'Sports High Speed Movie' => 'Film de sport à haute vitesse', + 'Sundown' => 'Crépuscule', + 'Swing Burst' => 'Swing éclaté', + 'Text' => 'Texte', + 'Toy Camera' => 'Caméra jouet', + 'Twilight' => 'Crépuscule', + 'Underwater' => 'Subaquatique', + 'Voice Recording' => 'Enregistrement de la voix', + 'Water Color' => 'Couleur de l\'eau', + 'White Board' => 'Tableau blanc', + 'Wide Shot' => 'Plan large', + }, + }, + 'BitDepthChroma' => 'Bits de profondeur de chrominance', + 'BitDepthLuma' => 'Bits de profondeur de luminance', + 'BitsPerComponent' => 'Bits par composante', + 'BitsPerExtendedRunLength' => 'Bits par « Run Length » étendue', + 'BitsPerRunLength' => 'Bits par « Run Length »', + 'BitsPerSample' => 'Nombre de bits par échantillon', + 'BlackLevel' => 'Niveau noir', + 'BlackLevel2' => 'Niveau de noir 2', + 'BlackLevelDeltaH' => 'Delta H du niveau noir', + 'BlackLevelDeltaV' => 'Delta V du niveau noir', + 'BlackLevelRepeatDim' => 'Dimension de répétition du niveau noir', + 'BlackMaskBottomBorder' => 'Bordure inférieure du masque noir', + 'BlackMaskLeftBorder' => 'Bordure gauche du masque noir', + 'BlackMaskRightBorder' => 'Bordure droite du masque noir', + 'BlackMaskTopBorder' => 'Bordure supérieure du masque noir', + 'BlackPoint' => 'Point noir', + 'BleachBypassToning' => { + Description => 'Tonalité traitement sans blanchiment', + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'Off' => 'Désactivé', + 'Purple' => 'Violet', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + 'n/a' => 'Non applicable', + }, + }, + 'BlueAdjust' => 'Ajustement du bleu', + 'BlueBalance' => 'Balance bleue', + 'BlueMatrixColumn' => 'Colonne de la matrice bleue', + 'BlueSaturation' => 'Saturation du bleu', + 'BlueTRC' => 'Courbe de reproduction des tonalités bleues', + 'BlurWarning' => { + Description => 'Alerte de flou', + PrintConv => { + 'Blur Warning' => 'Alerte de flou', + 'None' => 'Aucune', + }, + }, + 'BoardTemperature' => 'Température de la carte', + 'BodyBatteryADLoad' => 'Tension accu boîtier en charge', + 'BodyBatteryADNoLoad' => 'Tension accu boîtier à vide', + 'BodyBatteryState' => { + Description => 'État de accu boîtier', + PrintConv => { + 'Almost Empty' => 'Presque vide', + 'Empty or Missing' => 'Vide ou absent', + 'Full' => 'Plein', + 'Running Low' => 'En baisse', + }, + }, + 'BodyFirmwareVersion' => 'Version du micrologiciel du boitier', + 'BracketMode' => { + Description => 'Mode Bracket', + PrintConv => { + 'Off' => 'Désactivé', + }, + }, + 'BracketProgram' => { + PrintConv => { + 'N/A' => 'N/a', + }, + }, + 'BracketShot' => 'Cliché en bracketing', + 'BracketShotNumber' => { + Description => 'Numéro de cliché en bracketing', + PrintConv => { + '1 of 3' => '1 sur 3', + '1 of 5' => '1 sur 5', + '2 of 3' => '2 sur 3', + '2 of 5' => '2 sur 5', + '3 of 3' => '3 sur 3', + '3 of 5' => '3 sur 5', + '4 of 5' => '4 sur 5', + '5 of 5' => '5 sur 5', + 'n/a' => 'Non applicable', + }, + }, + 'BracketValue' => 'Valeur Bracket', + 'Brightness' => 'Luminosité', + 'BrightnessAdj' => 'Réglage de la luminosité', + 'BrightnessValue' => 'Luminosité', + 'BulbDuration' => 'Durée du pose longue', + 'BurstMode' => { + Description => 'Mode Rafale', + PrintConv => { + 'Infinite' => 'Infini', + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'BurstMode2' => 'Mode rafale', + 'BurstPrimary' => 'Rafale primaire', + 'BurstShot' => 'Prise de vue en rafale', + 'BurstSpeed' => 'Vitesse de rafale', + 'BurstUUID' => 'UUID de raffale', + 'ButtonFunctionControlOff' => { + Description => 'Fonction de touche si Contrôle Rapide OFF', + PrintConv => { + 'Disable main, Control, Multi-control' => 'Désactivée principale, Contrôle rapide, Multicontrôleur', + 'Normal (enable)' => 'Normale (activée)', + }, + }, + 'By-line' => 'Créateur', + 'By-lineTitle' => 'Fonction du créateur', + 'CFALayout' => { + Description => 'Organisation CFA', + PrintConv => { + 'Even columns offset down 1/2 row' => 'Organisation décalée A : les colonnes paires sont décalées vers le bas d\'une demi-rangée.', + 'Even columns offset up 1/2 row' => 'Organisation décalée B : les colonnes paires sont décalées vers le haut d\'une demi-rangée.', + 'Even rows offset left 1/2 column' => 'Organisation décalée D : les rangées paires sont décalées vers la gauche d\'une demi-colonne.', + 'Even rows offset right 1/2 column' => 'Organisation décalée C : les rangées paires sont décalées vers la droite d\'une demi-colonne.', + 'Rectangular' => 'Plan rectangulaire (ou carré)', + }, + }, + 'CFAPattern' => { + Description => 'Matrice de filtrage couleur', + PrintConv => { + '[Blue,Green][Green,Red]' => '[Bleu,Vert][Vert,Rouge]', + '[Green,Blue][Red,Green]' => '[Vert,Bleu][Rouge,Vert]', + '[Green,Red][Blue,Green]' => '[Vert,Rouge][Bleu,Vert]', + '[Red,Green][Green,Blue]' => '[Rouge,Vert][Vert,Bleu]', + 'n/a' => 'Non applicable', + }, + }, + 'CFAPattern2' => 'Modèle CFA 2', + 'CFAPlaneColor' => 'Couleur de plan CFA', + 'CFARepeatPatternDim' => 'Dimension du modèle de répétition CFA', + 'CHMVersion' => 'Version Mode CH', + 'CHModeShootingSpeed' => 'Mode CH Vitesse de prise de vue', + 'CMMFlags' => 'Indicateurs CMM', + 'CMSaturation' => 'Saturation CM', + 'CMYKEquivalent' => 'Equivalent CMJK', + 'CPUArchitecture' => { + Description => 'Architecture processeur', + PrintConv => { + '32 bit' => '32 bits', + '64 bit' => '64 bits', + }, + }, + 'CPUByteOrder' => 'Ordre de traitement des octets processeur', + 'CPUFirmwareVersion' => 'Version de firmware de CPU', + 'CPUType' => { + PrintConv => { + 'None' => 'Aucune', + }, + }, + 'CR2CFAPattern' => { + Description => 'Motif CR2 CFA', + PrintConv => { + '[Blue,Green][Green,Red]' => '[Bleu,Vert][Vert,Rouge]', + '[Green,Blue][Red,Green]' => '[Vert,Bleu][Rouge,Vert]', + '[Green,Red][Blue,Green]' => '[Vert,Rouge][Bleu,Vert]', + '[Red,Green][Green,Blue]' => '[Rouge,Vert][Vert,Bleu]', + }, + }, + 'CalibrationDateTime' => 'Date et heure de calibration', + 'CalibrationIlluminant1' => { + Description => 'Illuminant de calibration 1', + PrintConv => { + 'Cloudy' => 'Temps nuageux', + 'Cool White Fluorescent' => 'Fluorescente type soft', + 'Day White Fluorescent' => 'Fluorescente type blanc', + 'Daylight' => 'Lumière du jour', + 'Daylight Fluorescent' => 'Fluorescente type jour', + 'Fine Weather' => 'Beau temps', + 'Fluorescent' => 'Fluorescente', + 'ISO Studio Tungsten' => 'Tungstène studio ISO', + 'Other' => 'Autre source de lumière', + 'Shade' => 'Ombre', + 'Standard Light A' => 'Lumière standard A', + 'Standard Light B' => 'Lumière standard B', + 'Standard Light C' => 'Lumière standard C', + 'Tungsten (Incandescent)' => 'Tungstène (lumière incandescente)', + 'Unknown' => 'Inconnue', + 'Warm White Fluorescent' => 'Fluorescent blanc chaud', + 'White Fluorescent' => 'Fluorescent blanc', + }, + }, + 'CalibrationIlluminant2' => { + Description => 'Illuminant de calibration 2', + PrintConv => { + 'Cloudy' => 'Temps nuageux', + 'Cool White Fluorescent' => 'Fluorescente type soft', + 'Day White Fluorescent' => 'Fluorescente type blanc', + 'Daylight' => 'Lumière du jour', + 'Daylight Fluorescent' => 'Fluorescente type jour', + 'Fine Weather' => 'Beau temps', + 'Fluorescent' => 'Fluorescente', + 'ISO Studio Tungsten' => 'Tungstène studio ISO', + 'Other' => 'Autre source de lumière', + 'Shade' => 'Ombre', + 'Standard Light A' => 'Lumière standard A', + 'Standard Light B' => 'Lumière standard B', + 'Standard Light C' => 'Lumière standard C', + 'Tungsten (Incandescent)' => 'Tungstène (lumière incandescente)', + 'Unknown' => 'Inconnue', + 'Warm White Fluorescent' => 'Fluorescent blanc chaud', + 'White Fluorescent' => 'Fluorescent blanc', + }, + }, + 'CameraCalibration' => 'Calibration de l\'appareil photo', + 'CameraCalibration1' => 'Calibration de l\'appareil photo 1', + 'CameraCalibration2' => 'Calibration de l\'appareil photo 2', + 'CameraCalibration3' => 'Calibration de l\'appareil photo 3', + 'CameraCalibrationSig' => 'Signature de calibration de l\'appareil photo', + 'CameraDateTime' => 'Date et heure de l\'appareil photo', + 'CameraDirection' => 'Direction de l\'appareil photo', + 'CameraE-mountVersion' => 'Version de l\'appareil photo à monture E', + 'CameraElevationAngle' => 'Angle d\'élémvation de l\'appareil photo', + 'CameraFilename' => 'Nom de fichier de l\'appareil photo', + 'CameraID' => 'Identifiant de l\'appareil photo', + 'CameraISO' => 'ISO de l\'appareil photo', + 'CameraIdentifier' => 'Identificateur de l\'appareil photo', + 'CameraImage' => 'Image de l\'appareil photo', + 'CameraInfoByteOrder' => 'Ordre des octets de l\'appareil photo', + 'CameraMakeModel' => 'Appareil photo Marque et Modèle', + 'CameraMaker' => 'Fabricant de l\'appareil photo', + 'CameraManufacturer' => 'Fabricant de l\'appareil photo', + 'CameraModel' => 'Modèle de l\'appareil photo', + 'CameraMotion' => 'Mouvement de l\'appareil photo', + 'CameraMove' => 'Mouvement de l\'appareil photo', + 'CameraName' => 'Nom de l\'appareil photo', + 'CameraOrientation' => { + Description => 'Orientation de l\'appareil photo', + PrintConv => { + 'Downwards' => 'Dirigé vers le bas', + 'Horizontal (normal)' => 'À l\'horizontal (normal)', + 'Normal' => 'Orientation Normale', + 'Rotate 180' => 'Pivoté de de 180°', + 'Rotate 270 CW' => 'Pivoté de 270° dans le sens antihoraire', + 'Rotate 90 CW' => 'Pivoté de 90° dans le sens antihoraire', + 'Rotate CCW' => 'Pivoté à droite', + 'Rotate CW' => 'Pivoté à gauche', + 'Tilt Downwards' => 'Incliné vers le bas', + 'Tilt Upwards' => 'Incliné vers le haut', + 'Upwards' => 'Dirigé vers le haut', + }, + }, + 'CameraPictureStyle' => { + Description => 'Style d\'image de l\'appareil photo', + PrintConv => { + 'Landscape' => 'Paysage', + 'Neutral' => 'Neutre', + 'User Defined 1' => 'Défini par l\'utilisateur 1', + 'User Defined 2' => 'Défini par l\'utilisateur 2', + 'User Defined 3' => 'Défini par l\'utilisateur 3', + }, + }, + 'CameraPitch' => 'Inclinaison de la caméra', + 'CameraSerialNumber' => 'Numéro de série de l\'appareil photo', + 'CameraSettings' => 'Réglages de l\'appareil', + 'CameraSettingsVersion' => 'Version des réglages de l\'appareil photo', + 'CameraTemperature' => 'Température de l\'appareil photo', + 'CameraType' => { + Description => 'Type d\'appareil photo', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'CameraType2' => 'Type d\'appareil photo 2', + 'CameraYaw' => 'Lacet de la caméra', + 'Cameras' => 'Caméras', + 'CanonColorInfo1' => 'Info couleurs 1 Canon', + 'CanonColorInfo2' => 'Info couleurs 2 Canon', + 'CanonExposureMode' => { + Description => 'Mode d\'exposition Canon', + PrintConv => { + 'Aperture-priority AE' => 'Priorité à l\'ouverture AE', + 'Bulb' => 'Ampoule', + 'Depth-of-field AE' => 'Profondeur de champ AE', + 'Easy' => 'Simple', + 'Flexible-priority AE' => 'Priorité flexible AE', + 'Manual' => 'Manuel', + 'Program AE' => 'Programme d\'exposition automatique', + 'Shutter speed priority AE' => 'Priorité à la vitesse d\'obturation AE', + }, + }, + 'CanonFileDescription' => 'Description du fichier Canon', + 'CanonFileLength' => 'Longueur du fichier Canon', + 'CanonFirmwareVersion' => 'Version du firmware Canon', + 'CanonFlashInfo' => 'Info Flash Canon', + 'CanonFlashMode' => { + Description => 'Mode Flash Canon', + PrintConv => { + 'External flash' => 'Flash externe', + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'Red-eye reduction' => 'Réduction yeux rouges', + 'Red-eye reduction (Auto)' => 'Réduction yeux rouges (Auto)', + 'Red-eye reduction (On)' => 'Réduction yeux rouges (Activée)', + 'Slow-sync' => 'Synchro lente', + 'n/a' => 'Non applicable', + }, + }, + 'CanonImageHeight' => 'Hauteur de l\'image Canon', + 'CanonImageSize' => { + Description => 'Taille de l\'image Canon', + PrintConv => { + '1280x720 Movie' => 'Vidéo 1280x720', + '1920x1080 Movie' => 'Vidéo 1920x1080', + '4096x2160 Movie' => 'Vidéo 4096x2160', + '640x480 Movie' => 'Vidéo 640x480', + 'Large' => 'Grande', + 'Medium' => 'Moyenne', + 'Medium 1' => 'Moyenne 1', + 'Medium 2' => 'Moyenne 2', + 'Medium 3' => 'Moyenne 3', + 'Medium Movie' => 'Vidéo moyenne', + 'Medium Widescreen' => 'Écran large moyen', + 'Postcard' => 'Carte postale', + 'Small' => 'Petite', + 'Small 1' => 'Petite 1', + 'Small 2' => 'Petite 2', + 'Small 3' => 'Petite 3', + 'Small Movie' => 'Petite vidéo', + 'Widescreen' => 'Écran large', + 'n/a' => 'Non applicable', + }, + }, + 'CanonImageType' => 'Type d\'image Canon', + 'CanonImageWidth' => 'Largeur de l\'image Canon', + 'CanonLogVersion' => { + Description => 'Version du journal Canon', + PrintConv => { + 'OFF' => 'Désactivé', + }, + }, + 'CanonModelID' => 'Identifiant du modèle Canon', + 'Caption-Abstract' => 'Légende / Description', + 'CaptionWriter' => 'Rédacteur', + 'CaptureXResolutionUnit' => { + PrintConv => { + 'um' => 'µm (micromètre)', + }, + }, + 'CaptureYResolutionUnit' => { + PrintConv => { + 'um' => 'µm (micromètre)', + }, + }, + 'CardiacBeatRejectionTechnique' => 'Technique de rejet du rythme cardiaque', + 'CardiacCyclePosition' => 'Position du cycle cardiaque', + 'CardiacFramingType' => 'Type de cadrage cardiaque', + 'CardiacNumberOfImages' => 'Nombre d\'images cardiaques', + 'CardiacPhases' => 'Phases cardiaques', + 'CardiacRRIntervalSpecified' => 'Intervalle cardiaque RR spécifié', + 'CardiacRepetitionTime' => 'Temps de répétition cardiaque', + 'CardiacSignalSource' => 'Source des signaux cardiaques', + 'CardiacSynchronizationTechnique' => 'Technique de synchronisation cardiaque', + 'CardiacTriggerSequence' => 'Séquence de déclenchement cardiaque', + 'CasioImageSize' => 'Taille de l\'image Casio', + 'CasioQuality' => { + Description => 'Qualité Casio', + PrintConv => { + 'Economy' => 'Économie', + 'Normal' => 'Normale', + }, + }, + 'Categories' => { + Description => 'Catégories', + PrintConv => { + '(none)' => '(aucune)', + 'Events' => 'Événements', + 'People' => 'Personnes', + 'Scenery' => 'Paysage', + 'To Do' => 'À faire', + 'User 1' => 'Utilisateur 1', + 'User 2' => 'Utilisateur 2', + 'User 3' => 'Utilisateur 3', + }, + }, + 'Category' => 'Catégorie', + 'CellLength' => 'Longueur de la cellule', + 'CellWidth' => 'Largeur de la cellule', + 'CenterWeightedAreaSize' => { + PrintConv => { + 'Average' => 'Moyenne', + }, + }, + 'Certificate' => 'Certificat', + 'CharTarget' => 'Cible caractère', + 'CharacterSet' => { + Description => 'Jeu de caractères', + PrintConv => { + '191 characters ISO 8850-1' => '191 caractères ISO 8850-1', + '38 characters ISO 646' => '38 caractères ISO 646', + '65 characters ISO 646' => '65 caractères ISO 646', + '95 characters ISO 646' => '95 caractères ISO 646', + 'Includes characters not ISO 2375 registered' => 'Comprend des caractères non enregistrés dans l\'ISO 2375', + 'Windows, Arabic' => 'Windows, Arabe', + 'Windows, Chinese (Simplified)' => 'Windows, Chinois (Simplifié)', + 'Windows, Cyrillic' => 'Windows, Cyrillique', + 'Windows, Greek' => 'Windows, Grec', + 'Windows, Hebrew' => 'Windows, Hébreux', + 'Windows, Japan (Shift - JIS X-0208)' => 'Windows, Japonais (Majuscule - JIS X-0208)', + 'Windows, Korea (Shift - KSC 5601)' => 'Windows, Coréen (Majuscule - KSC 5601)', + 'Windows, Latin2 (Eastern European)' => 'Windows, Latin2 (Europe de l\'Est)', + 'Windows, Turkish' => 'Windows, Turc', + }, + }, + 'CheckMark' => { + Description => 'Coche', + PrintConv => { + 'Clear' => 'Effacer', + }, + }, + 'ChromaBlurRadius' => 'Rayon de flou de chromatisme', + 'ChromaFormat' => 'Structures d\'échantillonnage de chrominance', + 'ChromaticAberrationCorrParams' => 'Paramètres de correction de l\'aberration chromatique', + 'ChromaticAberrationCorrection' => { + Description => 'Correction de l\'aberration chromatique', + PrintConv => { + 'No correction params available' => 'Aucun paramètre de correction disponible', + 'Off' => 'Désactivée', + 'On' => 'Activée', + }, + }, + 'ChromaticAberrationParams' => 'Paramètres d\'aberration chromatique', + 'ChromaticAdaptation' => 'Adaptation chromatique', + 'Chromaticity' => 'Chromaticité', + 'ChrominanceNR_TIFF_JPEG' => { + PrintConv => { + 'Low' => 'Bas', + 'Off' => 'Désactivé', + }, + }, + 'ChrominanceNoiseReduction' => { + PrintConv => { + 'Low' => 'Bas', + 'Off' => 'Désactivé', + }, + }, + 'CircleOfConfusion' => 'Cercle de confusion', + 'City' => 'Ville', + 'City2' => 'Ville 2', + 'CityName' => 'Nom de la ville', + 'Clarity' => 'Clarté', + 'Clarity2012' => 'Clarté 2012', + 'ClarityControl' => { + Description => 'Contrôle de clarté', + PrintConv => { + 'Off' => 'Désactivée', + }, + }, + 'ClassifyState' => 'État de classification', + 'CleanFaxData' => 'Données de Fax propres', + 'ClearRetouch' => { + Description => 'Retouche claire', + PrintConv => { + 'Off' => 'Désactivée', + 'On' => 'Activée', + }, + }, + 'ClearRetouchValue' => 'Valeur de retouche claire', + 'ClipPath' => 'Chemin d\'accès à l\'extrait', + 'ClipboardOrientation' => 'Orientation du presse-papier', + 'CodecDecodeAll' => { + Description => 'Codec de décodage total', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'CodedCharacterSet' => 'Codage du jeu de caractères', + 'CodedContentScanningKind' => { + Description => 'Type d\'analyse du contenu codé', + PrintConv => { + 'Interlaced' => 'Entrelacé', + 'Mixed' => 'Mixte', + 'Progressive' => 'Progressif', + 'Unknown' => 'Inconnu', + }, + }, + 'CollectionName' => 'Nom de collection', + 'Color' => { + PrintConv => { + 'Sepia' => 'Sépia', + }, + }, + 'ColorAberrationControl' => { + Description => 'Contrôle de l\'aberration de la couleur', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ColorAdjustment' => 'Réglage des couleurs', + 'ColorAdjustmentMode' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ColorBW' => 'Couleur N&B', + 'ColorBalance' => 'Balance des couleurs', + 'ColorBalanceAdj' => { + Description => 'Réglage de la balance des couleurs', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ColorBalanceVersion' => 'Version de la Balance des couleurs', + 'ColorBitDepth' => 'Profondeur de bit des couleurs', + 'ColorBoostLevel' => 'Niveau de boost de couleur', + 'ColorBoostType' => { + Description => 'Type de boost de couleur', + PrintConv => { + 'People' => 'Personnes', + }, + }, + 'ColorBooster' => { + Description => 'Booster de couleur', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ColorCalibrationMatrix' => 'Matrice de calibration de la couleur', + 'ColorCharacterization' => 'Caractérisation de la couleur', + 'ColorCompensationFilter' => 'Filtre de compensation des couleurs', + 'ColorCompensationFilterCustom' => 'Filtre de compensation des couleurs personnalisé', + 'ColorCompensationFilterSet' => 'Réglage du filtre de compensation des couleurs', + 'ColorComponents' => 'Composants colorimétriques', + 'ColorControl' => 'Contrôle des couleurs', + 'ColorDataVersion' => 'Version des données de couleur', + 'ColorEffect' => { + Description => 'Effet de couleurs', + PrintConv => { + 'Black & White' => 'Noir et blanc', + 'Cool' => 'Frais', + 'Happy' => 'Joyeux', + 'Off' => 'Désactivé', + 'Sepia' => 'Sépia', + 'Vivid' => 'Éclatant', + 'Warm' => 'Chaud', + }, + }, + 'ColorFilter' => { + Description => 'Filtre de couleur', + PrintConv => { + 'Blue' => 'Bleu', + 'Full' => 'Plein', + 'Green' => 'Vert', + 'Off' => 'Désactivé', + 'Pink' => 'Rose', + 'Purple' => 'Violet', + 'Red' => 'Rouge', + 'Sepia' => 'Sépia', + 'Yellow' => 'Jaune', + }, + }, + 'ColorHue' => 'Teinte de couleur', + 'ColorInfo' => 'Info couleur', + 'ColorMap' => 'Charte de couleur', + 'ColorMatrix' => { + Description => 'Matrice des couleurs', + PrintConv => { + 'EOS Original' => 'Originale EOS', + 'Neutral' => 'Neutre', + }, + }, + 'ColorMatrix1' => 'Matrice de couleurs 1', + 'ColorMatrix2' => 'Matrice de couleurs 2', + 'ColorMatrix3' => 'Matrice de couleurs 3', + 'ColorMatrixA' => 'Matrice de couleurs A', + 'ColorMatrixA2' => 'Matrice de couleurs A2', + 'ColorMatrixAdobeRGB' => 'Matrice de couleurs AdobeRVB', + 'ColorMatrixB' => 'Matrice de couleurs B', + 'ColorMatrixB2' => 'Matrice de couleurs B2', + 'ColorMatrixNumber' => 'Numéro de la matrice de couleurs', + 'ColorMatrixSRGB' => 'Matrice de couleurs SRVB', + 'ColorMode' => { + Description => 'Mode colorimétrique', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'Autumn Leaves' => 'Feuilles automne', + 'B & W' => 'N & B', + 'B&W' => 'N&B', + 'B&W Red Filter' => 'Filtre rouge N&B', + 'B&W Yellow Filter' => 'Filtre jaune N&B', + 'Bitmap' => 'Matriciel', + 'Black & White' => 'Noir & Blanc', + 'CMYK' => 'CMJN', + 'Clear' => 'Clair', + 'Color Palette' => 'Palette de couleurs', + 'Deep' => 'Profond', + 'Embed Adobe RGB' => 'Adobe RVB intégré', + 'Evening' => 'Soir', + 'FOV Classic Blue' => 'FOV Bleu classique', + 'Grayscale' => 'Niveaux de gris', + 'Indexed' => 'Indexé', + 'Indexed Color' => 'Couleurs indexées', + 'Landscape' => 'Paysage', + 'Light' => 'Lumière', + 'Multichannel' => 'Multicanal', + 'Natural' => 'Naturel', + 'Natural color' => 'Couleur naturelle', + 'Natural sRGB' => 'SRVB naturel', + 'Natural+ sRGB' => 'SRVB naturel+', + 'Neutral' => 'Neutre', + 'Neutral Color' => 'Couleur neutre', + 'Night Portrait' => 'Portrait nocturne', + 'Night Scene' => 'Scène nocturne', + 'Night View' => 'Vue nocturne', + 'Night View/Portrait' => 'Vue/Portrait nocturne', + 'Normal' => 'Normale', + 'Off' => 'Désactivé', + 'RGB' => 'RVB', + 'RGB Color' => 'Couleur RVB', + 'Saturated Color' => 'Couleurs saturées', + 'Solarization' => 'Solarisation', + 'Sunset' => 'Coucher de soleil', + 'Vivid' => 'Éclatant', + 'Vivid 2' => 'Éclatant 2', + 'Vivid color' => 'Couleurs éclatantes', + 'n/a' => 'Non applicable', + }, + }, + 'ColorMoireReduction' => { + Description => 'Réduction du moiré des couleurs', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ColorMoireReductionMode' => { + Description => 'Mode de réduction du moiré de couleur', + PrintConv => { + 'High' => 'Haut', + 'Low' => 'Bas', + 'Medium' => 'Moyen', + 'Off' => 'Désactivé', + }, + }, + 'ColorNoiseReduction' => 'Réduction du bruit de la couleur', + 'ColorNoiseReductionDetail' => 'Détail de la réduction du bruit de la couleur', + 'ColorNoiseReductionIntensity' => 'Réduction du bruit d\'intensité de la couleur', + 'ColorNoiseReductionSharpness' => 'Réduction du bruit de netteté de la couleur', + 'ColorNoiseReductionSmoothness' => 'Réduction du bruit lissé de la couleur', + 'ColorPalette' => 'Palette de couleur', + 'ColorRepresentation' => { + Description => 'Représentation de couleur', + PrintConv => { + '3 Components, Frame Sequential in Multiple Objects' => 'Trois composantes, Vue séquentielle dans différents objets', + '3 Components, Frame Sequential in One Object' => 'Trois composantes, Vue séquentielle dans un objet', + '3 Components, Line Sequential' => 'Trois composantes, Ligne séquentielle', + '3 Components, Pixel Sequential' => 'Trois composantes, Pixel séquentiel', + '3 Components, Single Frame' => 'Trois composantes, Vue unique', + '3 Components, Special Interleaving' => 'Trois composantes, Entrelacement spécial', + '4 Components, Frame Sequential in Multiple Objects' => 'Quatre composantes, Vue séquentielle dans différents objets', + '4 Components, Frame Sequential in One Object' => 'Quatre composantes, Vue séquentielle dans un objet', + '4 Components, Line Sequential' => 'Quatre composantes, Ligne séquentielle', + '4 Components, Pixel Sequential' => 'Quatre composantes, Pixel séquentiel', + '4 Components, Single Frame' => 'Quatre composantes, Vue unique', + '4 Components, Special Interleaving' => 'Quatre composantes, Entrelacement spécial', + 'Monochrome, Single Frame' => 'Monochrome, Vue unique', + 'No Image, Single Frame' => 'Pas d\'image, Vue unique', + }, + }, + 'ColorResponseUnit' => 'Unité de réponse de la couleur', + 'ColorSequence' => 'Séquence de couleur', + 'ColorSpace' => { + Description => 'Espace colorimétrique', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'Adobe RGB (A700)' => 'Adobe RVB (A700)', + 'Adobe RGB (ICC)' => 'Adobe RVB (ICC)', + 'B&W' => 'N&B', + 'ICC Profile' => 'Profil ICC', + 'RGB' => 'RVB', + 'Uncalibrated' => 'Non calibré', + 'Wide Gamut RGB' => 'Wide Gamut RVB', + 'n/a' => 'Non applicable', + 'sRGB' => 'sRVB', + }, + }, + 'ColorSpaceData' => 'Espace de couleur des données', + 'ColorSpaceName' => 'Nom de l\'espace couleur', + 'ColorSpecApproximation' => { + Description => 'Approximation de la spécification des couleurs', + PrintConv => { + 'Accurate' => 'Précise', + 'Exceptional Quality' => 'Qualité exceptionnelle', + 'Not Specified' => 'Non spécifié', + 'Poor Quality' => 'Qualité médiocre', + 'Reasonable Quality' => 'Qualité raisonnable', + }, + }, + 'ColorTable' => 'Tableau de couleurs', + 'ColorTempAsShot' => 'Température couleur telle que capturée', + 'ColorTempAuto' => 'Température couleur Automatique', + 'ColorTempCloudy' => 'Température couleur Nuageux', + 'ColorTempCustom' => 'Température couleur personnalisée', + 'ColorTempCustom1' => 'Température couleur personnalisée 1', + 'ColorTempCustom2' => 'Température couleur personnalisée 2', + 'ColorTempDaylight' => 'Température couleur Lumière du jour', + 'ColorTempFlash' => 'Température couleur Flash', + 'ColorTempFluorescent' => 'Température couleurs Fluorescent', + 'ColorTempFluorescentD' => 'Température couleurs Fluorescent D', + 'ColorTempFluorescentN' => 'Température couleurs Fluorescent N', + 'ColorTempFluorescentW' => 'Température couleurs Fluorescent W', + 'ColorTempKelvin' => 'Température couleur Kelvin', + 'ColorTempMeasured' => 'Température couleur mesurée', + 'ColorTempPC1' => 'Température couleur PC 1', + 'ColorTempPC2' => 'Température couleur PC 2', + 'ColorTempPC3' => 'Température couleur PC 3', + 'ColorTempShade' => 'Température couleur Ombre', + 'ColorTempTungsten' => 'Température couleur Tungstène', + 'ColorTemperature' => 'Température couleur', + 'ColorTone' => { + Description => 'Teinte couleur', + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'ColorToneAuto' => { + Description => 'Tonalité couleur Auto', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ColorToneFaithful' => { + Description => 'Tonalité couleur Fidèle', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ColorToneLandscape' => { + Description => 'Tonalité couleur Paysage', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ColorToneMonochrome' => { + Description => 'Tonalité couleur Monochrome', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ColorToneNeutral' => { + Description => 'Tonalité couleur Neutre', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ColorTonePortrait' => { + Description => 'Tonalité couleur Portrait', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ColorToneStandard' => { + Description => 'Tonalité couleur Standard', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ColorToneUserDef1' => { + Description => 'Tonalité couleur défini par l’utilisateur 1', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ColorToneUserDef2' => { + Description => 'Tonalité couleur défini par l’utilisateur 2', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ColorToneUserDef3' => { + Description => 'Tonalité couleur défini par l’utilisateur 3', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ColorType' => { + PrintConv => { + 'RGB' => 'RVB', + }, + }, + 'ColorantOrder' => 'Ordre de colorant', + 'ColorantTable' => 'Table de colorant', + 'ColorimetricReference' => 'Référence colorimétrique', + 'CommandDialsChangeMainSub' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'CommandDialsMenuAndPlayback' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'CommandDialsReverseRotation' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'CommanderGroupAMode' => { + PrintConv => { + 'Manual' => 'Manuelle', + 'Off' => 'Désactivé', + }, + }, + 'CommanderGroupBMode' => { + PrintConv => { + 'Manual' => 'Manuelle', + 'Off' => 'Désactivé', + }, + }, + 'CommanderInternalFlash' => { + PrintConv => { + 'Manual' => 'Manuelle', + 'Off' => 'Désactivé', + }, + }, + 'Comment' => 'Commentaire', + 'Comments' => 'Commentaires', + 'Compatibility' => 'Compatibilité', + 'CompatibleBrands' => 'Labels compatibles', + 'CompatibleFontName' => 'Nom de police compatible', + 'CompatibleVersion' => 'Version compatible', + 'Compilation' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'ComponentBitDepth' => 'Profondeur de bits des composants', + 'ComponentsConfiguration' => { + Description => 'Configuration des composants', + PrintConv => { + 'Alpha (matte)' => 'Alpha (mat)', + 'Alpha, B, G, R' => 'Alpha, B, V, R', + 'Blue (B)' => 'Bleu (B)', + 'Chrominance (Cb, Cr, subsampled by two)' => 'Chrominance (Cb, Cr, sous-échantillonné par deux)', + 'Composite video' => 'Vidéo composite', + 'Depth (Z)' => 'Profondeur (Z)', + 'G' => 'V', + 'Green (G)' => 'Vert (V)', + 'R, G, B' => 'R, V, B', + 'R, G, B, Alpha' => 'R, V, B, Alpha', + 'Red (R)' => 'Rouge (R)', + 'User-defined 2 component element' => 'Élément à 2 composants défini par l\'utilisateur', + 'User-defined 3 component element' => 'Élément à 3 composants défini par l\'utilisateur', + 'User-defined 4 component element' => 'Élément à 4 composants défini par l\'utilisateur', + 'User-defined 5 component element' => 'Élément à 5 composants défini par l\'utilisateur', + 'User-defined 6 component element' => 'Élément à 6 composants défini par l\'utilisateur', + 'User-defined 7 component element' => 'Élément à 7 composants défini par l\'utilisateur', + 'User-defined 8 component element' => 'Élément à 8 composants défini par l\'utilisateur', + 'User-defined single component' => 'Composant unique défini par l\\utilisateur', + }, + }, + 'CompositeImage' => { + Description => 'Image composite', + PrintConv => { + 'Composite Image Captured While Shooting' => 'Image composite capturée pendant la prise de vue', + 'General Composite Image' => 'Image composite générale', + 'Not a Composite Image' => 'N\'est pas une image composite', + 'Unknown' => 'Inconnue', + }, + }, + 'CompressedBitsPerPixel' => 'Mode de compression de l\'image', + 'CompressedDataLength' => 'Longuuer des données compressées', + 'CompressedDataOffset' => 'Décalage des données compressées', + 'Compression' => { + Description => 'Schéma de compression', + PrintConv => { + '4-Bit RLE' => '4 Bits RLE', + '8-Bit RLE' => '8 Bits RLE', + 'Aperio JPEG 2000 RGB' => 'Aperio JPEG 2000 RVB', + 'Bitfields' => 'Champs de bits', + 'Deflated' => 'Deflaté', + 'Huffman-coded baseline JPEG' => 'JPEG de base à codage Huffman', + 'JBIG B&W' => 'JBIG N&B', + 'JBIG Color' => 'JBIG Couleur', + 'JPEG' => 'Compression JPEG', + 'JPEG (old-style)' => 'JPEG (ancien schéma)', + 'JPEG-LS' => 'JPEG LS', + 'Kodak DCR Compressed' => 'Compressé avec Kodak DCR', + 'Kodak KDC Compressed' => 'Compressé avec Kodak KDC', + 'Lossy JPEG' => 'JPEG avec pertes', + 'Microsoft Document Imaging (MDI) Binary Level Codec' => 'Microsoft Document Imaging (MDI) Codec de niveau binaire', + 'Microsoft Document Imaging (MDI) Progressive Transform Codec' => 'Microsoft Document Imaging (MDI) Codec de transformation progressive', + 'Microsoft Document Imaging (MDI) Vector' => 'Microsoft Document Imaging (MDI) Vectoriel', + 'Modified Huffman' => 'Huffman modifié', + 'Modified Modified READ' => 'LECTURE Modifiée Modifiée', + 'Modified READ' => 'LECTURE Modifiée', + 'Next' => 'Encodage NeXT 2 bits', + 'Nikon NEF Compressed' => 'Nikon NEF compressé', + 'None' => 'Aucun', + 'Packed RAW' => 'RAW encapsulé', + 'Panasonic RAW 1' => 'RAW 1 Panasonic', + 'Panasonic RAW 2' => 'RAW 2 Panasonic', + 'Panasonic RAW 3' => 'RAW 3 Panasonic', + 'Panasonic RAW 4' => 'RAW 4 Panasonic', + 'Pentax PEF Compressed' => 'Compression Pentax PEF', + 'PixarFilm' => 'Film Pixar', + 'PixarLog' => 'Journal Pixar', + 'RLE Encoding' => 'Encodage RLE', + 'SGILog' => 'Encodage Log luminance SGI 32 bits', + 'SGILog24' => 'Encodage Log luminance SGI 24 bits', + 'Samsung SRW Compressed' => 'Samsung SRW Compressé', + 'Samsung SRW Compressed 2' => 'Samsung SRW Compressé 2', + 'Sony ARW Compressed' => 'Sony ARW Compressé', + 'T4/Group 3 Fax' => 'T4/Groupe 3 Fax', + 'T6/Group 4 Fax' => 'T6/Groupe 4 Fax', + 'Thunderscan' => 'Encodage ThunderScan 4 bits', + 'Uncompressed' => 'Non compressé', + 'Uncompressed, interleaved, 8 bits per sample' => 'Non compressé, entrelacé, 8 bits par échantillon', + 'ZIP with prediction' => 'ZIP avec prédiction', + 'ZIP without prediction' => 'ZIP sans prédiction', + }, + }, + 'CompressionType' => { + PrintConv => { + 'None' => 'Aucune', + }, + }, + 'ConditionalFEC' => 'Compensation exposition flash', + 'Confidence' => 'Confiance', + 'ConfidenceLevel' => 'Niveau de confiance', + 'ConnectionSpaceIlluminant' => 'Illuminant d\'espace de connexion', + 'ConsecutiveBadFaxLines' => 'Mauvaises lignes de Fax consécutives', + 'ConstantFrameRate' => { + Description => 'Fréquence d\'images constante', + PrintConv => { + 'Constant Frame Rate' => 'Fréquence d\'images constante', + 'Each Temporal Layer is Constant Frame Rate' => 'Chaque couche temporelle a une fréquence d\'images constante', + 'Unknown' => 'Inconnue', + }, + }, + 'ConstraintIndicatorFlags' => 'Indicateurs de contrainte', + 'ContentLocationCode' => 'Code du lieu du contenu', + 'ContentLocationName' => 'Nom du lieu du contenu', + 'ContinuousDrive' => { + Description => 'Entraînement continu', + PrintConv => { + 'Continuous' => 'Continu', + 'Continuous, High' => 'Continu, Haut', + 'Continuous, High+' => 'Continu, Haut+', + 'Continuous, Low' => 'Continu, bas', + 'Continuous, Silent' => 'Continu, silencieux', + 'Continuous, Speed Priority' => 'Continu, Priorité à la vitesse', + 'Movie' => 'Vidéo', + 'Silent Single' => 'Simple silencieux', + 'Single' => 'Simple', + 'Single, Silent' => 'Simple, Silencieux', + }, + }, + 'ContinuousShootingSpeed' => { + Description => 'Vitesse de prise de vues en continu', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activée', + }, + }, + 'ContinuousShotLimit' => { + Description => 'Limiter nombre de vues en continu', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activée', + }, + }, + 'Contrast' => { + Description => 'Contraste', + PrintConv => { + '+1 (medium high)' => '+1 (moyen fort)', + '+2 (high)' => '+2 (fort)', + '+3 (very high)' => '+3 (très fort)', + '+4 (maximum)' => '+4 (maximal)', + '-1 (medium low)' => '-1 (moyen faible)', + '-2 (low)' => '-2 (faible)', + '-3 (very low)' => '-3 (très faible)', + '-4 (minimum)' => '4 (minimal)', + '0 (normal)' => '0 (Normal)', + 'Film Simulation' => 'Simulation de film', + 'High' => 'Fort', + 'Low' => 'Faible', + 'Medium High' => 'Moyen fort', + 'Medium Low' => 'Moyen faible', + 'Normal' => 'Normale', + 'n/a' => 'Non applicable', + }, + }, + 'ContrastAuto' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ContrastCurve' => 'Courbe de contraste', + 'ContrastFaithful' => { + Description => 'Fidélité du contraste', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ContrastHighlight' => 'Mise en lumière des contrastes', + 'ContrastLandscape' => { + Description => 'Contraste Paysage', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ContrastMode' => { + Description => 'Mode Contraste', + PrintConv => { + 'Cinema' => 'Cinéma', + 'Cross Process' => 'Traitement croisé', + 'Cross Process 2' => 'Traitement croisé 2', + 'Dynamic (B&W Film)' => 'Dynamique (Film N&B)', + 'Dynamic (Color Film)' => 'Dynamique (Film couleur)', + 'Dynamic Art (My Color)' => 'Art dynamique (My Color)', + 'Dynamic Mono' => 'Dynamique Mono', + 'Dynamic Monochrome' => 'Dynamique Monochrome', + 'Dynamic Monochrome 2' => 'Dynamique Monochrome 2', + 'Dynamic Range (film-like)' => 'Plage dynamique (similaire à celle d\'un film)', + 'Elegant (My Color)' => 'Élégant (My Color)', + 'Expressive' => 'Expressif', + 'Expressive 2' => 'Expressif 2', + 'Fantasy' => 'Fantaisie', + 'High' => 'Haut', + 'High Dynamic' => 'Haute dynamique', + 'High Dynamic 2' => 'Haute dynamique 2', + 'High Key 2' => 'Tons clairs 2', + 'Impressive Art' => 'Art impressionnant', + 'Impressive Art 2' => 'Art impressionnant 2', + 'Low' => 'Bas', + 'Low Key' => 'Tons sombres', + 'Low Key 2' => 'Tons sombres 2', + 'Match Filter Effects Toy' => 'Jouet à effets de filtres assortis', + 'Match Photo Style L. Monochrome' => 'Correspondre au style de photo L. Monochrome', + 'Medium High' => 'Moyennement élevé', + 'Medium Low' => 'Moyennement faible', + 'Nature (Color Film)' => 'Nature (film couleur)', + 'Nostalgic (Color Film)' => 'Nostalgique (Film couleur)', + 'Old Days' => 'Vieux jours', + 'Retro' => 'Rétro', + 'Retro (My Color)' => 'Rétro (My Color)', + 'Retro 2' => 'Rétro 2', + 'Sepia' => 'Sépia', + 'Smooth (B&W Film)' => 'Doux (Film Noir & Blanc)', + 'Smooth (Color Film) or Pure (My Color)' => 'Doux (Film couleur) ou Pur (My Color)', + 'Toy Effect' => 'Effet jouet', + 'Toy Effect 2' => 'Effet jouet 2', + 'Toy Pop' => 'Jouet Pop', + 'Vibrant (Color Film) or Expressive (My Color)' => 'Vibrant (Film couleur) ou expressif (My Color)', + }, + }, + 'ContrastMonochrome' => { + Description => 'Contraste Monochrome', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ContrastNeutral' => { + Description => 'Contraste Neutre', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ContrastPortrait' => { + Description => 'Contraste Portrait', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ContrastSetting' => 'Réglage du contraste', + 'ContrastShadow' => 'Contraste de l\'ombre', + 'ContrastStandard' => { + Description => 'Contraste Standard', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ContrastUserDef1' => { + Description => 'Contraste défini par l\'utilisateur 1', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ContrastUserDef2' => { + Description => 'Contraste défini par l\'utilisateur 2', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ContrastUserDef3' => { + Description => 'Contraste défini par l\'utilisateur 3', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'Contributor' => 'Contributeur', + 'ControlMode' => { + Description => 'Mode de contrôle', + PrintConv => { + 'Camera Local Control' => 'Commande locale de la caméra', + 'Computer Remote Control' => 'Télécommande de l\'ordinateur', + 'n/a' => 'Non applicable', + }, + }, + 'ConversionLens' => { + Description => 'Lentille de conversion', + PrintConv => { + 'Off' => 'Désactivée', + 'Telephoto' => 'Téléobjectif', + 'Wide' => 'Grand angle', + }, + }, + 'Copyright' => 'Propriétaire du copyright', + 'CopyrightNotice' => 'Mention de copyright', + 'CopyrightStatus' => { + PrintConv => { + 'Unknown' => 'Inconnu', + }, + }, + 'CoringFilter' => 'Filtre de carrotage', + 'CoringValues' => 'Valeurs du filtre de carrotage', + 'Country' => 'Pays', + 'Country-PrimaryLocationCode' => 'Code de pays ISO', + 'Country-PrimaryLocationName' => 'Pays', + 'CountryCode' => 'Code pays', + 'Coverage' => 'Couverture', + 'CreateDate' => 'Date de création des données numériques', + 'CreationDate' => 'Date de création', + 'CreativeStyle' => { + Description => 'Style créatif', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'Autumn Leaves' => 'Feuilles d\'automne', + 'B&W' => 'N&B', + 'Clear' => 'Clair', + 'Deep' => 'Profond', + 'Landscape' => 'Paysage', + 'Light' => 'Lumière', + 'Neutral' => 'Neutre', + 'Night View/Portrait' => 'Vue/Portrait nocturne', + 'None' => 'Aucun', + 'Real' => 'Réel', + 'Sepia' => 'Sépia', + 'Sunset' => 'Coucher de soleil', + 'Vivid' => 'Éclatant', + 'Vivid 2' => 'Éclatant 2', + }, + }, + 'CreativeStyleSetting' => { + Description => 'Réglage du style créatif', + PrintConv => { + 'B&W' => 'N&B', + 'Landscape' => 'Paysage', + 'Sunset' => 'Coucher de soleil', + 'Vivid' => 'Éclatant', + }, + }, + 'CreativeStyleWasChanged' => { + Description => 'Le style créatif a été modifié', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'Creator' => 'Créateur', + 'CreatorAddress' => 'Adresse du créateur', + 'CreatorAppVersion' => 'Version de l\'application du créateur', + 'CreatorApplication' => 'Application du créateur', + 'CreatorBuildNumber' => 'Numéro de build du créateur', + 'CreatorBuildNumber2' => 'Numéro de build 2 du créateur', + 'CreatorCity' => 'Ville du créateur', + 'CreatorContactInfo' => 'Informations de contact du créateur', + 'CreatorCountry' => 'Pays du créateur', + 'CreatorIdentifier' => 'Identifiant du créateur', + 'CreatorMajorVersion' => 'Version majeure du créateur', + 'CreatorMinorVersion' => 'Version mineure du créateur', + 'CreatorName' => 'Nom du créateur', + 'CreatorPostalCode' => 'Code postal du créateur', + 'CreatorRegion' => 'Région du créateur', + 'CreatorRole' => 'Rôle du créateur', + 'CreatorSoftware' => 'Logiciel du créateur', + 'CreatorTool' => 'Outil de création', + 'CreatorWorkEmail' => 'Courriel professionnel du créateur', + 'CreatorWorkTelephone' => 'Téléphone professionnel du créateur', + 'CreatorWorkURL' => 'URL professionnelle du créateur', + 'Credit' => 'Fournisseur', + 'CropActive' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'CropBottom' => 'Recadrage inférieur', + 'CropH' => 'Hauteur de recadrage', + 'CropHeight' => 'Hauteur de recadrage', + 'CropHiSpeed' => { + Description => 'Recadrage haute vitesse', + PrintConv => { + '1.3x Crop' => 'Recadrage 1.3x', + '1.3x Movie Crop' => 'Recadrage vidéo 1.3x', + '1.4x Movie Crop' => 'Recadrage vidéo 1.4x', + '1.5x Movie Crop' => 'Recadrage vidéo 1.5x', + '16:9 Crop' => 'Recadrage 16:9', + '1:1 Crop' => 'Recadrage 1:1', + '2.7x Crop' => 'Recadrage 2.7x', + '2.8x Movie Crop' => 'Recadrage vidéo 2.8x', + '3:2 Crop' => 'Recadrage 3:2', + '5:4 Crop' => 'Recadrage 5:4', + 'DX Crop' => 'Recadrage DX', + 'DX Movie Crop' => 'Recadrage vidéo DX', + 'DX Uncropped' => 'DX non recadré', + 'FX Uncropped' => 'FX non recadré', + 'Off' => 'Désactivé', + }, + }, + 'CropLeft' => 'Recadrage gauche', + 'CropMode' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'CropOriginalHeight' => 'Hauteur original du recadrage ', + 'CropOriginalWidth' => 'Largeur originale du recadrage', + 'CropOutputHeight' => 'Hauteur du recadrage en sortie', + 'CropOutputHeightInches' => 'Hauteur du recadrage en sortie en pouces', + 'CropOutputPixels' => 'Pixels de recadrage en sortie', + 'CropOutputResolution' => 'Résolution du recadrage de la sortie', + 'CropOutputScale' => 'Échelle du recadrage en sortie', + 'CropOutputWidth' => 'Largeur du recadrage en sortie', + 'CropOutputWidthInches' => 'Largeur du recadrage en sortie en pouces', + 'CropRight' => 'Recadrage droite', + 'CropRightMargin' => 'Recadrage de la marge droite', + 'CropRotatedOriginalHeight' => 'Hauteur originale du recadrage de la rotation', + 'CropRotatedOriginalWidth' => 'Largeur originale du recadrage de la rotation', + 'CropRotation' => 'Recadrage de la rotation', + 'CropScaledResolution' => 'Résolution du recadrage mis à l\'échelle', + 'CropSourceResolution' => 'Résolution du recadrage de la source', + 'CropTop' => 'Recadrage supérieur', + 'CropTopMargin' => 'Recadrage de la marge supérieure', + 'CropUnit' => { + Description => 'Unité de recadrage', + PrintConv => { + 'inches' => 'Pouce', + }, + }, + 'CropUnits' => { + PrintConv => { + 'inches' => 'Pouce', + }, + }, + 'CropW' => 'Largeur de recadrage', + 'CropWidth' => 'Largeur de recadrage', + 'CroppedImageHeight' => 'Hauteur de l\'image recadrée', + 'CroppedImageLeft' => 'Image recadrée gauche', + 'CroppedImageTop' => 'Image recadrée haut', + 'CroppedImageWidth' => 'Largeur de l\'image recadrée', + 'Cropping' => { + Description => 'Recadrage', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'CurrentICCProfile' => 'Profil ICC actuel', + 'CurrentIPTCDigest' => 'Sommaire IPTC courant', + 'CurrentNumberInSequence' => 'Numéro actuel en séquence', + 'CurrentPatientLocation' => 'Emplacement actuel du patient', + 'CurrentPreProfileMatrix' => 'Matrice de pré-profil actuelle', + 'CurrentRepeatNumber' => 'Numéro de répétition actuel', + 'CurrentTime' => 'Heure courante', + 'CurrentUser' => 'Utilisateur courant', + 'CurrentVersion' => 'Version courante', + 'CursorSize' => 'Taille du curseur', + 'Curves' => { + Description => 'Courbes', + PrintConv => { + 'Off' => 'Désactivée', + 'On' => 'Activeé', + }, + }, + 'CustomRendered' => { + Description => 'Traitement de l\'image', + PrintConv => { + 'Custom' => 'Traitement personnalisé', + 'HDR (no original saved)' => 'HDR (pas d\'original enregistré)', + 'HDR (original saved)' => 'HDR (original enregistré)', + 'Normal' => 'Traitement normal', + 'Original (for HDR)' => 'Original (pour HDR)', + }, + }, + 'CustomSaturation' => 'Saturation personnalisée', + 'CustomSettingsAllDefault' => { + Description => 'Réglages de saturation personnalisée par défaut', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'D-LightingHQ' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'D-LightingHQColorBoost' => 'D-Lighting HQ Boost couleur', + 'D-LightingHQHighlight' => 'D-Lighting HQ Boost éclairage', + 'D-LightingHQSelected' => { + Description => 'D-Lighting haute Qualité sélectionné', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'D-LightingHQShadow' => 'D-Lighting HQ Ombre', + 'D-LightingHS' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'D-LightingHSAdjustment' => 'Réglage lumière du jour HS', + 'D-LightingHSColorBoost' => 'Lumière du jour HS boost couleur', + 'DECPosition' => { + PrintConv => { + 'Contrast' => 'Contraste', + 'Exposure' => 'Exposition', + 'Filter' => 'Fitre', + }, + }, + 'DNGBackwardVersion' => 'Version DNG antérieure', + 'DNGLensInfo' => 'Distance focale minimale', + 'DNGVersion' => 'Version DNG', + 'DOF' => 'Profondeur de champ', + 'DSPFirmwareVersion' => 'Version de firmware de DSP', + 'DarkFocusEnvironment' => { + Description => 'Environnement de la focalisation sur le noir', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'DataCompressionMethod' => 'Fournisseur/propriétaire de l\'algorithme de compression de données', + 'DataDump' => 'Dump de données', + 'DataDump2' => 'Dump de données 2', + 'DataImprint' => { + PrintConv => { + 'None' => 'Aucune', + 'Text' => 'Texte', + }, + }, + 'DataScaling' => 'Échelle des données', + 'DataType' => 'Type de données', + 'DateCreated' => 'Date de création', + 'DateDisplayFormat' => { + Description => 'Format date', + PrintConv => { + 'D/M/Y' => 'Jour/Mois/Année', + 'M/D/Y' => 'Mois/Jour/Année', + 'Y/M/D' => 'Année/Mois/Jour', + }, + }, + 'DateSent' => 'Date d\'envoi', + 'DateStampMode' => { + Description => 'Mode Horodatage', + PrintConv => { + 'Date & Time' => 'Date et heure', + 'Date Counter' => 'Compteur de date', + 'Off' => 'Désactivé', + }, + }, + 'DateTime' => 'Date de modification du fichier', + 'DateTimeCreated' => 'Date et heure de création', + 'DateTimeDigitized' => 'Date et heure de numérisation', + 'DateTimeOriginal' => 'Date de création des données originales', + 'DaylightSavings' => { + Description => 'Heure d\'été', + PrintConv => { + 'No' => 'Non', + 'Off' => 'Désactivée', + 'On' => 'Activée', + 'Yes' => 'Oui', + }, + }, + 'DecoderTableNumber' => 'Numéro de la table de décodage', + 'DefaultCropOrigin' => 'Origine du recadrage par défaut', + 'DefaultCropSize' => 'Taille du recadrage par défaut', + 'DefaultScale' => 'Echelle par défaut', + 'DeletedImageCount' => 'Compteur d\'images supprimées', + 'DependentImage1EntryNumber' => 'Numéro d\'entrée de l\'image dépendante 1', + 'DependentImage2EntryNumber' => 'Numéro d\'entrée de l\'image dépendante 2', + 'DestinationCity' => { + Description => 'Ville de destination', + PrintConv => { + 'Adelaide' => 'Adelaïde', + 'Algiers' => 'Alger', + 'Athens' => 'Athènes', + 'Beijing' => 'Pékin', + 'Cairo' => 'Le Caire', + 'Caracus' => 'Caracas', + 'Copenhagen' => 'Copenhague', + 'Jerusalem' => 'Jérusalem', + 'Kabul' => 'Kaboul', + 'Lisbon' => 'Lisbonne', + 'London' => 'Londre', + 'Manila' => 'Manille', + 'Moscow' => 'Moscou', + 'Noumea' => 'Nouméa', + 'Seoul' => 'Séoul', + 'Singapore' => 'Singapour', + 'Taipei' => 'Taïpei', + 'Tehran' => 'Téhéran', + 'Warsaw' => 'Varsovie', + 'Yangon' => 'Rangoun', + }, + }, + 'DestinationCityCode' => 'Code ville de destination', + 'DestinationDST' => { + Description => 'Heure d\'été à destination', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'DestinationID' => 'ID Destination', + 'DetailsOfCoefficients' => 'Détails des coefficients', + 'DetectedFaceBounds' => 'Limites de visage détectées', + 'DetectedFaceID' => 'ID Visage détecté', + 'DetectedFaceRollAngle' => 'Angle de roulis du visage détecté', + 'DetectedFaceYawAngle' => 'Angle de lacet du visage détecté', + 'Detector' => 'Détecteur', + 'DevelopmentDynamicRange' => 'Développement de la plage dynamique', + 'DeviceAttributes' => 'Attributs d\'appareil', + 'DeviceManufacturer' => 'Fabricant de l\'appareil', + 'DeviceManufacturerName' => 'Nom du fabricant de l\'appareil', + 'DeviceMfgDesc' => 'Description du fabricant de l\'appareil', + 'DeviceModel' => 'Modèle de l\'appareil', + 'DeviceModelDesc' => 'Description du modèle de l\'appareil', + 'DeviceName' => 'Nom de l\'appareil', + 'DeviceOrientation' => 'Orientation de l\'appareil', + 'DeviceParametersSets' => 'Jeux de paramètres de l\'appareil', + 'DeviceRelativeHeading' => 'Cap relatif de l\'appareil', + 'DeviceRelativePositionX' => 'Position relative en X de l\'appareil', + 'DeviceRelativePositionY' => 'Position relative en Y de l\'appareil', + 'DeviceRelativePositionZ' => 'Position relative en Z de l\'appareil', + 'DeviceRelativePositionalAccuracy' => 'Précision de la position relative de l\'appareil', + 'DeviceRelativeSpeed' => 'Vitesse relative de l\'appareil', + 'DeviceSequence' => 'Séquence de l\'appareil', + 'DeviceSerialNumber' => 'Numéro de série de l\'appareil', + 'DeviceSettingDescription' => 'Description des réglages de l\'appareil', + 'DeviceSettingDescriptionColumns' => 'Colonnes de description des paramètres de l\'appareil', + 'DeviceSettingDescriptionRows' => 'Lignes de description des paramètres de l\'appareil', + 'DeviceSettingDescriptionSettings' => 'Réglage de la description des paramètres de l\'appareil', + 'DeviceSettings' => 'Réglages de l\'appareil', + 'DeviceType' => { + Description => 'Type d\'appareil', + PrintConv => { + 'Cell Phone' => 'Téléphone portable', + 'Compact Digital Camera' => 'Appareil photo numérique compact', + 'HXM Video Camera' => 'Caméra vidéo HXM', + 'High-end NX Camera' => 'Appareil photo NX haut de gamme', + 'SMX Video Camera' => 'Caméra vidéo SMX', + }, + }, + 'DeviceUID' => 'UID de l\'appareil', + 'DeviceUsageDescription' => 'Description de l\'utilisation de l\'appareil', + 'DeviceVolume' => 'Volume de l\'appareil', + 'DialDirectionTvAv' => { + Description => 'Sens rotation molette Tv/Av', + PrintConv => { + 'Normal' => 'Normale', + 'Reversed' => 'Inversé', + }, + }, + 'DigitalCreationDate' => 'Date de création numérique', + 'DigitalCreationDateTime' => 'Date et heure de création numérique', + 'DigitalCreationTime' => 'Heure de création numérique', + 'DigitalFilter' => 'Filtre numérique', + 'DigitalFilter01' => { + Description => 'Filtre numérique 01', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter02' => { + Description => 'Filtre numérique 02', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter03' => { + Description => 'Filtre numérique 03', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter04' => { + Description => 'Filtre numérique 04', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter05' => { + Description => 'Filtre numérique 05', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter06' => { + Description => 'Filtre numérique 06', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter07' => { + Description => 'Filtre numérique 07', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter08' => { + Description => 'Filtre numérique 08', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter09' => { + Description => 'Filtre numérique 09', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter10' => { + Description => 'Filtre numérique 10', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter11' => { + Description => 'Filtre numérique 11', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter12' => { + Description => 'Filtre numérique 12', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter13' => { + Description => 'Filtre numérique 13', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter14' => { + Description => 'Filtre numérique 14', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter15' => { + Description => 'Filtre numérique 15', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter16' => { + Description => 'Filtre numérique 16', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter17' => { + Description => 'Filtre numérique 17', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter18' => { + Description => 'Filtre numérique 18', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter19' => { + Description => 'Filtre numérique 19', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalFilter20' => { + Description => 'Filtre numérique 20', + PrintConv => { + 'Base Parameter Adjust' => 'Réglage des paramètres de base', + 'Bold Monochrome' => 'Monochrome gras', + 'Color Filter' => 'Filtre couleur', + 'Custom Filter' => 'Filtre personnalisé', + 'Extract Color' => 'Couleur de l\'extrait', + 'High Contrast' => 'Contraste élevé', + 'Invert Color' => 'Inversion de couleur', + 'Off' => 'Désactivé', + 'Posterization' => 'Postérisation', + 'Replace Color' => 'Remplacement de couleur', + 'Retro' => 'Rétro', + 'Shading' => 'Ombrage', + 'Sketch Filter' => 'Filtre d\'esquisse', + 'Slim' => 'Fin', + 'Soft Focus' => 'Flou artistique', + 'Starburst' => 'Éclats d\'étoiles', + 'Tone Expansion' => 'Expansion de la tonalité', + 'Toy Camera' => 'Caméra jouet', + 'Unicolor Bold' => 'Unicolore gras', + 'Water Color' => 'Aquarelle', + }, + }, + 'DigitalGain' => 'Gain numérique', + 'DigitalImageGUID' => 'GUID de l\'image numérique', + 'DigitalSourceFileType' => 'Type du fichier de la source numérique', + 'DigitalZoom' => { + Description => 'Zoom numérique', + PrintConv => { + 'None' => 'Aucune', + 'Off' => 'Désactivé', + }, + }, + 'DigitalZoomOn' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'DigitalZoomRatio' => 'Rapport de zoom numérique', + 'Directory' => 'Répertoire', + 'DirectoryIndex' => 'Index du répertoire', + 'DirectoryIndex2' => 'Index du répertoire 2', + 'DirectoryNumber' => 'Numéro du répertoire', + 'DisplaySize' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'DisplayUnits' => { + PrintConv => { + 'inches' => 'Pouce', + }, + }, + 'DisplayXResolutionUnit' => { + PrintConv => { + 'um' => 'µm (micromètre)', + }, + }, + 'DisplayYResolutionUnit' => { + PrintConv => { + 'um' => 'µm (micromètre)', + }, + }, + 'DisplayedUnitsX' => { + PrintConv => { + 'inches' => 'Pouce', + }, + }, + 'DisplayedUnitsY' => { + PrintConv => { + 'inches' => 'Pouce', + }, + }, + 'DistortionCompensation' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'DistortionControl' => { + Description => 'Contrôle de la distorsion', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'DistortionCorrParams' => 'Paramètres de correction de la distorsion', + 'DistortionCorrParamsNumber' => { + Description => 'Numéro du paramètre de correction de la distorsion', + PrintConv => { + '16 (Full-frame)' => '16 (plein cadre)', + }, + }, + 'DistortionCorrParamsPresent' => { + Description => 'Paramètre de correction de la distorsion présent', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'DistortionCorrection' => { + Description => 'Correction de la distorsion', + PrintConv => { + 'Applied' => 'Appliquée', + 'Auto fixed by lens' => 'Correction automatique réalisée par l\'objectif', + 'No correction params available' => 'Aucun paramètre de correction disponible', + 'None' => 'Aucune', + 'Off' => 'Désactivée', + 'On' => 'Activée', + }, + }, + 'DistortionCorrection2' => { + Description => 'Correction de la distorsion 2', + PrintConv => { + 'Off' => 'Désactivée', + 'On' => 'Activée', + }, + }, + 'DistortionCorrectionSetting' => { + Description => 'Réglage de la correction de la distorsion', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'n/a' => 'Non applicable', + }, + }, + 'DistortionEffect' => { + PrintConv => { + 'Shot Settings' => 'Paramétrage prise de vue', + }, + }, + 'DjVuVersion' => 'Version DjVu', + 'DocumentHistory' => 'Historique du document', + 'DocumentName' => 'Nom du document', + 'DocumentNotes' => 'Remarques sur le document', + 'DotRange' => 'Étendue de points', + 'DriveMode' => { + Description => 'Mode de prise de vue', + PrintConv => { + 'Burst' => 'Rafale', + 'Continuous' => 'Continu', + 'Continuous - HDR' => 'Continu - HDR', + 'Continuous High' => 'Continu (ultrarapide)', + 'Continuous Shooting' => 'Prise de vues en continu', + 'HDR Manual' => 'HDR Manuel', + 'HDR Strong 1' => 'HDR fort 1', + 'HDR Strong 2' => 'HDR fort 2', + 'HDR Strong 3' => 'HDR fort 3', + 'Multiple Exposure' => 'Exposition multiple', + 'No Timer' => 'Pas de retardateur', + 'Off' => 'Désactivé', + 'Remote Commander' => 'Télécommande', + 'Remote Continuous Shooting' => 'Prise de vue continue télécommandée', + 'Remote Control' => 'Télécommande', + 'Remote Control (3 s delay)' => 'Télécommande (retard 3 s)', + 'Self-Timer 2 sec' => 'Retardateur (2 sec)', + 'Self-timer' => 'Retardateur', + 'Self-timer (12 s)' => 'Retardateur (12 s)', + 'Self-timer (2 s)' => 'Retardateur (2 s)', + 'Self-timer 10 sec' => 'Retardateur 10 sec', + 'Self-timer 2 sec, Mirror Lock-up' => 'Retardateur 2 sec, Blocage du miroir', + 'Self-timer Operation' => 'Opération avec retardateur', + 'Shutter Button' => 'Déclencheur', + 'Single' => 'Simple', + 'Single Exposure' => 'Exposition unique', + 'Single Frame' => 'Image unique', + 'Single Shot' => 'Prise de vue unique', + 'Single-Frame Bracketing' => 'Bracketing sur image unique', + 'Single-frame' => 'Image unqiue', + 'Single-frame Bracketing' => 'Bracketing sur image unique', + 'Single-frame Exposure Bracketing' => 'Bracketing d\'exposition sur image unique', + 'Single-frame Shooting' => 'Prise de vue unique', + 'Speed Priority Continuous' => 'Priorité à la vitesse continue', + 'UHS continuous' => 'UHS continu', + 'Video' => 'Vidéo', + 'White Balance Bracketing' => 'Bracketing de la balance des blancs', + 'White Balance Bracketing High' => 'Bracketing de la balance des blancs Haut', + 'White Balance Bracketing Low' => 'Bracketing de la balance des blancs Bas', + 'n/a' => 'Non applicable', + }, + }, + 'DriveMode2' => { + Description => 'Exposition multiple', + PrintConv => { + 'Single Frame' => 'Vue par vue', + 'Single-frame' => 'Vue par vue', + }, + }, + 'DriveModeSetting' => { + PrintConv => { + 'Single Frame' => 'Vue par vue', + }, + }, + 'DriveSpeed' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'DronePitch' => 'Inclinaison du drone', + 'DroneQuaternion' => 'Quaternion du drone', + 'DroneRoll' => 'Roulis du drone', + 'DroneYaw' => 'Lacet du drone', + 'Duration' => 'Durée', + 'DynamicRange' => { + Description => 'Plage dynamique', + PrintConv => { + 'Wide' => 'Large', + }, + }, + 'DynamicRangeExpansion' => { + Description => 'Expansion de la dynamique', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'DynamicRangeOptimizer' => { + Description => 'Optimiseur Dyna', + PrintConv => { + 'Advanced Auto' => 'Avancé Auto', + 'Advanced Lv1' => 'Avancé Niv1', + 'Advanced Lv2' => 'Avancé Niv2', + 'Advanced Lv3' => 'Avancé Niv3', + 'Advanced Lv4' => 'Avancé Niv4', + 'Advanced Lv5' => 'Avancé Niv5', + 'Auto' => 'Auto.', + 'Off' => 'Désactivé', + 'n/a' => 'Non applicable', + }, + }, + 'DynamicRangeSetting' => { + Description => 'Réglage de la plage dynamique', + PrintConv => { + 'Film Simulation' => 'Simulation de film', + 'Manual' => 'Manuel', + 'Wide1 (230%)' => 'Large1 (230%)', + 'Wide2 (400%)' => 'Large2 (400%)', + }, + }, + 'E-DialInProgram' => { + Description => 'Programme E-Dial In', + PrintConv => { + 'P Shift' => 'Décalage P', + 'Tv or Av' => 'Tv ou Av', + }, + }, + 'ETTLII' => { + Description => 'E TTL II', + PrintConv => { + 'Average' => 'Moyenne', + 'Evaluative' => 'Évaluative', + }, + }, + 'EVStepInfo' => 'Info de pas IL', + 'EVSteps' => { + Description => 'Pas IL', + PrintConv => { + '1/2 EV Steps' => 'Pas de 1/2 IL', + '1/3 EV Steps' => 'Pas de 1/3 IL', + }, + }, + 'EasyExposureCompensation' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'EasyMode' => { + Description => 'Mode simple', + PrintConv => { + 'Beach' => 'Plage', + 'Best Image Selection' => 'Meilleure sélection d\'image', + 'Black & White' => 'Noir et blanc', + 'Blur Reduction' => 'Réduction de l\'effet de flou', + 'Color Accent' => 'Accentuation des couleurs', + 'Color Swap' => 'Permutation des couleurs', + 'Creative Auto' => 'Auto créatif', + 'Creative Light Effect' => 'Effet de lumière créatif', + 'Digital Macro' => 'Macro numérique', + 'Discreet' => 'Discret', + 'Easy' => 'Simple', + 'Face Self-timer' => 'Retardateur de visage', + 'Fast shutter' => 'Obturateur rapide', + 'Fireworks' => 'Feu d\'artifice', + 'Fisheye Effect' => 'Effet Fisheye', + 'Flash Off' => 'Flash désactivé', + 'Foliage' => 'Feuillage', + 'Food' => 'Alimentation', + 'Full auto' => 'Totalement automatique', + 'Gray Scale' => 'Niveaux de gris', + 'HDR Art Bold' => 'HDR artistique Gras', + 'HDR Art Standard' => 'HDR artistique standard', + 'HDR Art Vivid' => 'HDR artistique éclatante', + 'Handheld Night Scene' => 'Scène nocturne à main levée', + 'High Dynamic Range' => 'Plage dynamique élevée', + 'High-speed Burst' => 'Raffale haute vitesse', + 'High-speed Burst HQ' => 'Raffale HQ haute vitesse', + 'Indoor' => 'Intérieur', + 'Kids & Pets' => 'Enfants & animaux', + 'Landscape' => 'Paysage', + 'Live View Control' => 'Contrôle du Live View', + 'Long Shutter' => 'Obturateur long', + 'Low Light' => 'Faible luminosité', + 'Low Light 2' => 'Faible luminosité 2', + 'Manual' => 'Manuel', + 'Miniature Effect' => 'Effet miniature', + 'Movie Digest' => 'Résumé du film', + 'Movie Snap' => 'Instantané de film', + 'Neutral' => 'Neutre', + 'Night' => 'Nuit', + 'Night 2' => 'Nuit 2', + 'Night Scene' => 'Scène nocturne', + 'Night Snapshot' => 'Prise de vue nocturne', + 'Night+' => 'Nuit+', + 'Nostalgic' => 'Nostalgique', + 'Pan focus' => 'Mise au point panoramique', + 'Poster Effect' => 'Effet poster', + 'Quick Shot' => 'Prise de vue rapide', + 'Scene Intelligent Auto' => 'Scène Intelligente Auto', + 'Sepia' => 'Sépia', + 'Slow shutter' => 'Obturateur à vitesse lente', + 'Smile' => 'Sourire', + 'Smooth Skin' => 'Peau lisse', + 'Snow' => 'Neige', + 'Soft Focus' => 'Flou artistique', + 'Sports' => 'Sport', + 'Spotlight' => 'Projecteur', + 'Sunset' => 'Coucher de soleil', + 'Super Macro' => 'Super macro', + 'Super Night' => 'Super Nuit', + 'Super Vivid' => 'Super éclatant', + 'Toy Camera Effect' => 'Effet caméra jouet', + 'Underwater' => 'Subaquatique', + 'Vivid' => 'Éclatant', + 'Wink Self-timer' => 'Minuteur clignotant', + 'Zoom Blur' => 'Flou de zoom', + }, + }, + 'EdgeNoiseReduction' => { + Description => 'Réduction du bruit de fond', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'EditStatus' => 'Statut d\'édition', + 'EditorialUpdate' => { + Description => 'Mise à jour éditoriale', + PrintConv => { + 'Additional language' => 'Langues supplémentaires', + }, + }, + 'EffectiveLV' => 'Indice de lumination effectif', + 'EffectiveMaxAperture' => 'Ouverture effective maxi de l\'Objectif', + 'ElectronicFrontCurtainShutter' => { + Description => 'Volet électronique du rideau avant', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'Emphasis' => { + PrintConv => { + 'None' => 'Aucune', + }, + }, + 'EncodingProcess' => { + Description => 'Procédé de codage', + PrintConv => { + 'Baseline DCT, Huffman coding' => 'Baseline DCT, codage Huffman', + 'Extended sequential DCT, Huffman coding' => 'Extended sequential DCT, codage Huffman', + 'Extended sequential DCT, arithmetic coding' => 'Extended sequential DCT, codage arithmétique', + 'Lossless, Differential Huffman coding' => 'Lossless, codage Huffman différentiel', + 'Lossless, Huffman coding' => 'Lossless, codage Huffman', + 'Lossless, arithmetic coding' => 'Lossless, codage arithmétique', + 'Lossless, differential arithmetic coding' => 'Lossless, codage arithmétique différentiel', + 'Progressive DCT, Huffman coding' => 'Progressive DCT, codage Huffman', + 'Progressive DCT, arithmetic coding' => 'Progressive DCT, codage arithmétique', + 'Progressive DCT, differential Huffman coding' => 'Progressive DCT, codage Huffman différentiel', + 'Progressive DCT, differential arithmetic coding' => 'Progressive DCT, codage arithmétique différentiel', + 'Sequential DCT, differential Huffman coding' => 'Sequential DCT, codage Huffman différentiel', + 'Sequential DCT, differential arithmetic coding' => 'Sequential DCT, codage arithmétique différentiel', + }, + }, + 'Encryption' => 'Chiffrage', + 'EndPoints' => 'Points de terminaison', + 'EnhanceDarkTones' => { + Description => 'Améliorer les tons foncés', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'Enhancement' => { + Description => 'Amélioration', + PrintConv => { + 'Blue' => 'Bleu', + 'Flesh Tones' => 'Tons chair', + 'Green' => 'Vert', + 'Off' => 'Désactivé', + 'Red' => 'Rouge', + 'Scenery' => 'Paysage', + 'Underwater' => 'Subaquatique', + }, + }, + 'Enhancer' => 'Amplificateur', + 'EnhancerValues' => 'Valeurs de l\'amplificateur', + 'EnvelopeNumber' => 'Numéro d\'enveloppe', + 'EnvelopePriority' => { + Description => 'Priorité d\'enveloppe', + PrintConv => { + '0 (reserved)' => '0 (réservé pour utilisation future)', + '1 (most urgent)' => '1 (très urgent)', + '5 (normal urgency)' => '5 (normalement urgent)', + '8 (least urgent)' => '8 (moins urgent)', + '9 (user-defined priority)' => '9 (priorité définie par l\'utilisateur)', + }, + }, + 'EnvelopeRecordVersion' => 'Version d\'enregistrement de l\'enveloppe', + 'EquipmentVersion' => 'Version de l\'équipement', + 'Error' => 'Erreur', + 'Event' => 'Evenement', + 'ExcursionTolerance' => { + Description => 'Tolérance d\'excursion ', + PrintConv => { + 'Allowed' => 'Possible', + 'Not Allowed' => 'Non permis (défaut)', + }, + }, + 'ExifByteOrder' => { + Description => 'Ordre des octets Exif', + PrintConv => { + 'Big-endian (Motorola, MM)' => 'Big-endian (Motorola, MM', + }, + }, + 'ExifCameraInfo' => 'Info d\'appareil photo Exif', + 'ExifImageHeight' => 'Hauteur de l\'image', + 'ExifImageWidth' => 'Largeur de l\'image', + 'ExifOffset' => 'Pointeur Exif IFD', + 'ExifToolVersion' => 'Version ExifTool', + 'ExifUnicodeByteOrder' => 'Ordre des octets Unicode Exif', + 'ExifVersion' => 'Version Exif', + 'ExitPupilPosition' => 'Position de la pupille de sortie', + 'ExpandFilm' => 'Extension film', + 'ExpandFilterLens' => 'Objectif à filtre extensible', + 'ExpandFlashLamp' => 'Extension de lampe flash', + 'ExpandLens' => 'Extension d\'objectif', + 'ExpandScanner' => 'Extension Scanner', + 'ExpandSoftware' => 'Extension logiciel', + 'ExpirationDate' => 'Date d\'expiration', + 'ExpirationTime' => 'Heure d\'expiration', + 'Exposure' => 'Exposition', + 'Exposure2012' => 'Exposition 2012', + 'ExposureAdj' => 'Réglage de l\'exposition', + 'ExposureAdj2' => 'Réglage de l\'exposition 2', + 'ExposureAdjust' => 'Réglage de l\'exposition', + 'ExposureBias' => 'Réglage du biais', + 'ExposureBracketStepSize' => 'Intervalle de bracketing d\'exposition', + 'ExposureBracketValue' => 'Valeur Bracketing Expo', + 'ExposureCompensation' => 'Décalage d\'exposition', + 'ExposureCount' => 'Nombre d\'expositions', + 'ExposureDelayMode' => { + Description => 'Mode de retardement d\'exposition', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ExposureDifference' => 'Correction d\'exposition', + 'ExposureIndex' => 'Indice d\'exposition', + 'ExposureLevelIncrements' => { + Description => 'Paliers de réglage d\'expo', + PrintConv => { + '1-stop set, 1/3-stop comp.' => 'Réglage 1 valeur, correction 1/3 val.', + '1/2 Stop' => 'Palier 1/2', + '1/2-stop set, 1/2-stop comp.' => 'Réglage 1/2 valeur, correction 1/2 val.', + '1/3 Stop' => 'Palier 1/3', + '1/3-stop set, 1/3-stop comp.' => 'Réglage 1/3 valeur, correction 1/3 val.', + }, + }, + 'ExposureMode' => { + Description => 'Mode d\'exposition', + PrintConv => { + 'Aperture Priority' => 'Priorité à l\'ouverture', + 'Aperture-priority AE' => 'Priorité ouverture', + 'Auto' => 'Exposition automatique', + 'Auto bracket' => 'Bracketting auto', + 'Bulb' => 'Pose B', + 'Landscape' => 'Paysage', + 'Manual' => 'Exposition manuelle', + 'Night Scene / Twilight' => 'Nocturne', + 'Night View/Portrait' => 'Vue/Portrait nocturne', + 'Shutter Priority' => 'Priorité à l\'obturateur', + 'Shutter speed priority AE' => 'Priorité vitesse', + 'Sunset' => 'Coucher de soleil', + 'n/a' => 'Non applicable', + }, + }, + 'ExposureModeInManual' => { + Description => 'Mode d\'exposition manuelle', + PrintConv => { + 'Center-weighted average' => 'Moyenne pondérée centrale', + 'Evaluative metering' => 'Mesure évaluative', + 'Partial metering' => 'Mesure partielle', + 'Specified metering mode' => 'Mode de mesure spécifié', + 'Spot metering' => 'Mesure spot', + }, + }, + 'ExposureProgram' => { + Description => 'Programme d\'exposition', + PrintConv => { + 'Action (High speed)' => 'Programme action (orienté grandes vitesses d\'obturation)', + 'Aperture Priority' => 'Priorité à l\'ouverture', + 'Aperture-priority AE' => 'Priorité à l\'ouverture AE', + 'Creative (Slow speed)' => 'Programme créatif (orienté profondeur de champ)', + 'High Contrast Monochrome' => 'Monochrome à fort contraste', + 'Landscape' => 'Paysage', + 'Manual' => 'Manuel', + 'Night view' => 'Vue nocturne', + 'Night view/portrait' => 'Vue/Portrait nocturne', + 'Not Defined' => 'Non défini', + 'Pop Color' => 'Couleur pop', + 'Portrait' => 'Mode portrait', + 'Posterization' => 'Postérisation', + 'Program AE' => 'Programme normal', + 'Retro Photo' => 'Photo rétro', + 'Shutter Priority' => 'Priorité à l\'obturateur', + 'Shutter speed priority AE' => 'Priorité vitesse', + 'Shutter/aperture priority AE' => 'Priorité à l\'obturateur/l\'ouverture AE', + 'Sunset' => 'Coucher de soleil', + 'Toy Camera' => 'Caméra jouet', + }, + }, + 'ExposureStandardAdjustment' => 'Réglage de la norme d\'exposition ', + 'ExposureTime' => 'Temps de pose', + 'ExposureTime2' => 'Temps de pose 2', + 'ExposureTuning' => 'Réglage de l\'exposition', + 'ExposureWarning' => { + Description => 'Alerte d\'exposition', + PrintConv => { + 'Bad exposure' => 'Mauvaise exposition', + 'Good' => 'Bonne exposition', + }, + }, + 'ExtendedWBDetect' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'Extender' => { + Description => 'Extension', + PrintConv => { + 'None' => 'Aucune', + 'Olympus EX-25 Extension Tube' => 'Tube d\'extension Olympus EX-25', + 'Olympus Zuiko Digital EC-14 1.4x Teleconverter' => 'Téléconvertisseur Olympus Zuiko Digital EC-14 1.4x', + 'Olympus Zuiko Digital EC-20 2.0x Teleconverter' => 'Téléconvertisseur Olympus Zuiko Digital EC-20 2.0x', + }, + }, + 'ExtenderFirmwareVersion' => 'Numéro de série du micrologiciel de l\'extension', + 'ExtenderSerialNumber' => 'Numéro de série de l\'extension', + 'ExtenderStatus' => { + Description => 'État de l\'extension', + PrintConv => { + 'Attached' => 'Attaché', + 'Not attached' => 'Non attaché', + 'Removed' => 'Retiré', + }, + }, + 'ExternalFlash' => { + Description => 'Flash externe', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ExternalFlashBounce' => { + Description => 'Réflexion flash externe', + PrintConv => { + 'Bounce' => 'Avec réflecteur', + 'Bounce or Off' => 'Avec réflecteur ou Désactivé', + 'No' => 'Non', + 'Yes' => 'Oui', + 'n/a' => 'Non applicable', + }, + }, + 'ExternalFlashExposureComp' => { + Description => 'Compensation d\'exposition flash externe', + PrintConv => { + '-0.5' => '-0.5 IL', + '-1.0' => '-1.0 IL', + '-1.5' => '-1.5 IL', + '-2.0' => '-2.0 IL', + '-2.5' => '-2.5 IL', + '-3.0' => '-3.0 IL', + '0.0' => '0.0 IL', + '0.5' => '0.5 IL', + '1.0' => '1.0 IL', + 'n/a' => 'Non applicable', + 'n/a (Manual Mode)' => 'Non applicable (Mode manuel)', + }, + }, + 'ExternalFlashFirmware' => { + Description => 'Micrologiciel Flash externe', + PrintConv => { + '1.01 (SB-800 or Metz 58 AF-1)' => '1.01 (SB-800 ou Metz 58 AF-1)', + '3.01 (SU-800 Remote Commander)' => '3.01 (SU-800 Commande à distance)', + 'n/a' => 'Non applicable', + }, + }, + 'ExternalFlashFlags' => { + Description => 'Indicateurs de Flash externe', + PrintConv => { + '(none)' => '(aucun)', + 'Bounce Flash' => 'Flash à rebond', + 'Dome Diffuser' => 'Diffuseur à dôme', + 'Fired' => 'Déclenché', + 'Wide Flash Adapter' => 'Adaptateur pour flash large', + }, + }, + 'ExternalFlashGuideNumber' => 'Numéro du guide du flash externe', + 'ExternalFlashMode' => { + Description => 'Mode du flash externe', + PrintConv => { + 'Not Connected' => 'Non connecté', + 'Off' => 'Désactivé', + 'On, Auto' => 'Activé, auto', + 'On, Contrast-control Sync' => 'Activé, Synchro du contrôle du contraste', + 'On, Flash Problem' => 'Activé, problème de flash', + 'On, High-speed Sync' => 'Activé, synchro haute vitesse', + 'On, Manual' => 'Activé, manuel', + 'On, P-TTL Auto' => 'Activé, auto P-TTL', + 'On, Wireless' => 'Activé, sans cordon', + 'On, Wireless, High-speed Sync' => 'Activé, sans cordon, synchro haute vitesse', + 'n/a - Off-Auto-Aperture' => 'Non applicable - Ouverture automatique désactivé', + }, + }, + 'ExternalFlashReadyState' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ExternalFlashZoom' => 'Zoom du flash externe', + 'ExtraSamples' => 'Echantillons supplémentaires', + 'FLIR_Unknown' => 'FLIR Inconnu', + 'FNumber' => 'Diaphragme Æ’', + 'FOV' => 'Champ de vision', + 'FPFVersion' => 'Version FPF', + 'FSType' => 'Type FS', + 'Fac100Per' => 'Visages 100 personnes', + 'Fac170Per' => 'Visages 170 personnes', + 'Fac18Per' => 'Visages 18 personnes', + 'Face10Position' => 'Position visage 10', + 'Face10Size' => 'Taille visage 10', + 'Face11Position' => 'Position visage 11', + 'Face11Size' => 'Taille visage 11', + 'Face12Position' => 'Position visage 12', + 'Face12Size' => 'Taille visage 12', + 'Face13Position' => 'Position visage 13', + 'Face13Size' => 'Taille visage 13', + 'Face14Position' => 'Position visage 14', + 'Face14Size' => 'Taille visage 14', + 'Face15Position' => 'Position visage 15', + 'Face15Size' => 'Taille visage 15', + 'Face16Position' => 'Position visage 16', + 'Face16Size' => 'Taille visage 16', + 'Face17Position' => 'Position visage 17', + 'Face17Size' => 'Taille visage 17', + 'Face18Position' => 'Position visage 18', + 'Face18Size' => 'Taille visage 18', + 'Face19Position' => 'Position visage 19', + 'Face19Size' => 'Taille visage 19', + 'Face1Birthday' => 'Anniversaire Visage 1', + 'Face1Category' => { + Description => 'Catégorie visage 1', + PrintConv => { + 'Family' => 'Famille', + 'Friend' => 'Ami', + 'Partner' => 'Conjoint', + }, + }, + 'Face1Name' => 'Nom visage 1', + 'Face1Position' => 'Position visage 1', + 'Face1Size' => 'Taille visage 1', + 'Face20Position' => 'Position visage 20', + 'Face20Size' => 'Taille visage 20', + 'Face21Position' => 'Position visage 21', + 'Face21Size' => 'Taille visage 21', + 'Face22Position' => 'Position visage 22', + 'Face22Size' => 'Taille visage 22', + 'Face23Position' => 'Position visage 23', + 'Face23Size' => 'Taille visage 23', + 'Face24Position' => 'Position visage 24', + 'Face24Size' => 'Taille visage 24', + 'Face25Position' => 'Position visage 25', + 'Face25Size' => 'Taille visage 25', + 'Face26Position' => 'Position visage 26', + 'Face26Size' => 'Taille visage 26', + 'Face27Position' => 'Position visage 27', + 'Face27Size' => 'Taille visage 27', + 'Face28Position' => 'Position visage 28', + 'Face28Size' => 'Taille visage 28', + 'Face29Position' => 'Position visage 29', + 'Face29Size' => 'Taille visage 29', + 'Face2Birthday' => 'Anniversaire Visage 2', + 'Face2Category' => { + Description => 'Catégorie visage 2', + PrintConv => { + 'Family' => 'Famille', + 'Friend' => 'Ami', + 'Partner' => 'Conjoint', + }, + }, + 'Face2Name' => 'Nom visage 2', + 'Face2Position' => 'Position visage 2', + 'Face2Size' => 'Taille visage 2', + 'Face30Position' => 'Position visage 30', + 'Face30Size' => 'Taille visage 30', + 'Face31Position' => 'Position visage 31', + 'Face31Size' => 'Taille visage 31', + 'Face32Position' => 'Position visage 32', + 'Face32Size' => 'Taille visage 32', + 'Face3Birthday' => 'Anniversaire Visage 3', + 'Face3Category' => { + Description => 'Catégorie visage 3', + PrintConv => { + 'Family' => 'Famille', + 'Friend' => 'Ami', + 'Partner' => 'Conjoint', + }, + }, + 'Face3Name' => 'Nom visage 3', + 'Face3Position' => 'Position visage 3', + 'Face3Size' => 'Taille visage 3', + 'Face4Birthday' => 'Anniversaire Visage 4', + 'Face4Category' => { + Description => 'Catégorie visage 4', + PrintConv => { + 'Family' => 'Famille', + 'Friend' => 'Ami', + 'Partner' => 'Conjoint', + }, + }, + 'Face4Name' => 'Nom visage 4', + 'Face4Position' => 'Position visage 4', + 'Face4Size' => 'Taille visage 4', + 'Face5Birthday' => 'Anniversaire Visage 5', + 'Face5Category' => { + Description => 'Catégorie visage 5', + PrintConv => { + 'Family' => 'Famille', + 'Friend' => 'Ami', + 'Partner' => 'Conjoint', + }, + }, + 'Face5Name' => 'Nom visage 5', + 'Face5Position' => 'Position visage 5', + 'Face5Size' => 'Taille visage 5', + 'Face6Birthday' => 'Anniversaire Visage 6', + 'Face6Category' => { + Description => 'Catégorie visage 6', + PrintConv => { + 'Family' => 'Famille', + 'Friend' => 'Ami', + 'Partner' => 'Conjoint', + }, + }, + 'Face6Name' => 'Nom visage 6', + 'Face6Position' => 'Position visage 6', + 'Face6Size' => 'Taille visage 6', + 'Face7Birthday' => 'Anniversaire Visage 7', + 'Face7Category' => { + Description => 'Catégorie visage 7', + PrintConv => { + 'Family' => 'Famille', + 'Friend' => 'Ami', + 'Partner' => 'Conjoint', + }, + }, + 'Face7Name' => 'Nom visage 7', + 'Face7Position' => 'Position visage 7', + 'Face7Size' => 'Taille visage 7', + 'Face8Birthday' => 'Anniversaire Visage 8', + 'Face8Category' => { + Description => 'Catégorie visage 8', + PrintConv => { + 'Family' => 'Famille', + 'Friend' => 'Ami', + 'Partner' => 'Conjoint', + }, + }, + 'Face8Name' => 'Nom visage 8', + 'Face8Position' => 'Position visage 8', + 'Face8Size' => 'Taille visage 8', + 'Face9Position' => 'Position visage 9', + 'Face9Size' => 'Taille visage 9', + 'FaceDetect' => { + Description => 'Détection du visage', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'FaceDetectArea' => 'Zone de détection du visage', + 'FaceDetectFrameCrop' => 'Recadrage du cadre de détection du visage', + 'FaceDetectFrameSize' => 'Taille du cadre de détection du visage', + 'FaceDetected' => 'Visage détecté', + 'FaceDetection' => { + Description => 'Détection du visage', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'FaceElementTypes' => { + Description => 'Types d\'éléments détectés', + PrintConv => { + 'Aircraft Body' => 'Carlingue d\'avion', + 'Aircraft Cockpit' => 'Cockpit d\'avion', + 'Animal Body' => 'Corps d\'animal', + 'Animal Face' => 'Visage d\'animal', + 'Animal Head' => 'Tête d\'animal', + 'Animal Left Eye' => 'Å’il gauche d\'animal', + 'Animal Right Eye' => 'Å’il droit d\'animal', + 'Bike' => 'Vélo', + 'Bird Body' => 'Corps d\'oiseau', + 'Bird Head' => 'Tête d\'oiseau', + 'Bird Left Eye' => 'Å’il gauche d\'oiseau', + 'Bird Right Eye' => 'Å’il droit d\'oiseau', + 'Body' => 'Corps', + 'Body of Car' => 'Carrosserie de voiture', + 'Face' => 'Visage', + 'Front of Car' => 'Calandre de voiture', + 'Head' => 'Tête', + 'Left Eye' => 'Å’il gauche', + 'Right Eye' => 'Å’il droit', + 'Train Cockpit' => 'Poste de conduite de train', + 'Train Front' => 'Avant de train', + }, + }, + 'FaceID' => 'ID Visage', + 'FaceInfoLength' => 'Longueur des informations sur le visage', + 'FaceInfoOffset' => 'Décalage de l\'information sur le visage', + 'FaceInfoUnknown' => 'Infos visage inconnu', + 'FaceName' => 'Nom du visage', + 'FaceNumbers' => 'Numéros du visage', + 'FaceOrientation' => { + Description => 'Orientation du visage', + PrintConv => { + 'Horizontal (normal)' => '0° (haut/gauche)', + 'Rotate 180' => 'Tourné à 180°', + 'Rotate 270 CW' => 'Tourné à 270° dans le sens antihoraire', + 'Rotate 90 CW' => 'Tourné à 90° dans le sens antihoraire', + }, + }, + 'FacePosition' => 'Position du visage', + 'FacePositions' => 'Positions du visage', + 'FaceRecognition' => { + Description => 'Reconnaissance faciale', + PrintConv => { + 'Off' => 'Désactivée', + 'On' => 'Activée', + }, + }, + 'FaceTest2' => 'Test facial 2', + 'FaceTest8' => 'Test facial 8', + 'FaceWidth' => 'Largeur de visage', + 'FacesDetected' => { + Description => 'Visages détectés', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + 'n/a' => 'Non applicable', + }, + }, + 'FacesRecognized' => 'Visages reconnus', + 'FacetSequence' => 'Séquence de facette', + 'Fade' => 'Fondu', + 'Fade-InDuration' => 'Durée du fondu enchaîné', + 'Fade-InType' => 'Type du fondu enchaîné', + 'Fade-OutDuration' => 'Durée du fondu à la fermeture', + 'Fade-OutType' => 'Type du fondu à la fermeture', + 'FastSeek' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'FaxProfile' => { + PrintConv => { + 'Unknown' => 'Inconnu', + }, + }, + 'FaxRecvParams' => 'Paramètres de réception Fax', + 'FaxRecvTime' => 'Temps de réception Fax', + 'FaxSubAddress' => 'Sous-adresse Fax', + 'FieldOfView' => { + PrintConv => { + 'Wide' => 'Large', + }, + }, + 'FileAccessDate' => 'Date d\'accès au fichier', + 'FileAttributes' => { + Description => 'Attribut du fichier', + PrintConv => { + 'Block' => 'Bloc', + 'Character' => 'Caractère', + 'Compressed' => 'Compressé', + 'Device' => 'Appareil', + 'Directory' => 'Répertoire', + 'Encrypted' => 'Crypté', + 'Encrypted?' => 'Crypté?', + 'Hidden' => 'Masqué', + 'Mux Block' => 'Blocs multipexés', + 'Mux Character' => 'Caractères multipexés', + 'Not Content Indexed' => 'Aucun contenu indexé', + 'Not indexed' => 'Non indexé', + 'Offline' => 'Hors ligne', + 'Read Only' => 'Lecture seule', + 'Read-only' => 'Lecture seule', + 'Regular' => 'Régulier', + 'Reparse Point' => 'Point de reparsage', + 'Reparse point' => 'Point de reparsage', + 'Set Group ID' => 'ID du jeu d\'ensemble', + 'Solaris Door' => 'Porte Solaris', + 'Solaris Shadow Inode' => 'Inode fantôme Solaris', + 'Sparse File' => 'Fichier Sparse', + 'Symbolic Link' => 'Lien symbolique', + 'System' => 'Système', + 'Temporary' => 'Temporaire', + 'Unknown' => 'Inconnu', + 'Volume Label' => 'Label du volume', + 'VxFS Compressed' => 'Compressé VxFS', + 'XENIX Named' => 'Nommage XENIX', + }, + }, + 'FileFormat' => { + Description => 'Format du fichier', + PrintConv => { + 'Compressed Binary File [.ZIP] (PKWare Inc)' => 'Fichier binaire compressé [.ZIP] (PKWare Inc)', + 'Digital Audio File [.WAV] (Microsoft & Creative Labs)' => 'Fichier audio numérique [.WAV] (Microsoft & Creative Labs)', + 'Hypertext Markup Language [.HTML] (The Internet Society)' => 'HyperText Markup Language [.HTML] (The Internet Society)', + 'Illustrator (Adobe Graphics data)' => 'Illustrator (Adobe Graphics data', + 'JPEG (lossy)' => 'JPEG (avec perte)', + 'JPEG (lossy/non-quantization toggled)' => 'JPEG (basculement entre perte et non-quantification)', + 'JPEG (non-quantization)' => 'JPEG (non-quantification)', + 'Tagged Image File Format (Adobe/Aldus Image data)' => 'Tagged Image File Format (Adobe/Aldus Image data', + 'Tape Archive [.TAR]' => 'Ape Archive [.TAR]', + }, + }, + 'FileGroupID' => 'ID du groupe de fichiers', + 'FileID' => 'ID du fichier', + 'FileIndex' => 'Index du fichier', + 'FileIndex2' => 'Index 2 du fichier', + 'FileInfo' => 'Infos Fichier', + 'FileInfoVersion' => 'Version des Infos Fichier', + 'FileInodeChangeDate' => 'Date de changement d\'inode du fichier', + 'FileInodeNumber' => 'Numéro d\'inode du fichier', + 'FileLength' => 'Longueur du fichier', + 'FileModifyDate' => 'Date de modification du fichier', + 'FileName' => 'Nom du fichier', + 'FileNameAsDelivered' => 'Nom du fichier tel que livré', + 'FileNameLength' => 'Longueur du nom du fichier', + 'FileNumber' => 'Numéro du fichier', + 'FileNumberMemory' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'FileNumberSequence' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'FileOS' => { + Description => 'Système d\'exploitation des fichiers', + PrintConv => { + 'OS/2 16-bit' => 'OS/2 16-bits', + 'OS/2 16-bit PM-16' => 'OS/2 16-bits PM-16', + 'OS/2 32-bit' => 'OS/2 32-bits', + 'OS/2 32-bit PM-32' => 'OS/2 32-bits PM-32', + 'Windows 16-bit' => 'Windows 16-bits', + 'Windows 32-bit' => 'Windows 32-bits', + 'Windows NT 32-bit' => 'Windows NT 32-bits', + }, + }, + 'FileOwner' => 'Propriétaire du fichier', + 'FilePath' => 'Chemin d\'accès au fichier', + 'FilePermissions' => 'Permissions du fichier', + 'FileProfileVersion' => 'Version du profil du fichier', + 'FileSecurityReport' => 'Rapport de sécurité du fichier', + 'FileSequence' => 'Séquence du fichier', + 'FileSetConsistencyFlag' => 'Drapeau de cohérence du jeu de fichiers', + 'FileSetID' => 'ID du jeu de fichiers', + 'FileSize' => 'Taille du fichier', + 'FileSizeBytes' => 'Taille du fichier en octets', + 'FileSource' => { + Description => 'Source du fichier', + PrintConv => { + 'Computer Graphics' => 'Infographie', + 'Digital Camera' => 'Appareil photo numérique', + 'Film Scanner' => 'Scanner de film', + 'Reflection Print Scanner' => 'Scanner par réflexion', + 'Sigma Digital Camera' => 'Appareil photo numérique Sigma', + 'Video Capture' => 'Capture vidéo', + }, + }, + 'FileSubtype' => 'Sous-type de fichier', + 'FileType' => 'Type de fichier', + 'FileTypeDescription' => 'Description du type de fichier', + 'FileTypeExtension' => 'Extension du type de fichiers', + 'FileURL' => 'URL du fichier', + 'FileUserID' => 'ID utilisateur de fichier', + 'FileVersion' => 'Version de format de fichier', + 'FileVersionNumber' => 'Numéro de version du fichier', + 'Filename' => 'Nom du fichier ', + 'Files' => 'Fichiers', + 'FillFlashAutoReduction' => { + Description => 'Mesure E-TTL', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + }, + }, + 'FillMethod' => { + Description => 'Méthode de remplissage', + PrintConv => { + 'Bit Replication' => 'Réplication de bits', + 'Zero Fill' => 'Remplissage à zéro', + }, + }, + 'FillOrder' => { + Description => 'Ordre de remplissage', + PrintConv => { + 'Normal' => 'Normale', + 'Reversed' => 'Inversé', + }, + }, + 'FilmMode' => { + Description => 'Mode vidéo', + PrintConv => { + 'Dynamic (B&W)' => 'Vives (N & Bà)', + 'Dynamic (color)' => 'Couleurs vives', + 'Nature (color)' => 'Couleurs naturelles', + 'Smooth (B&W)' => 'Pastel (N & B)', + 'Smooth (color)' => 'Couleurs pastel', + 'Standard (B&W)' => 'Normales (N & B)', + 'Standard (color)' => 'Couleurs normales', + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffect' => { + Description => 'Effet de filtre', + PrintConv => { + 'Cross Process' => 'Traitement croisé', + 'Dynamic Monochrome' => 'Monochrome dynamique', + 'Expressive' => 'Expressif', + 'Fantasy' => 'Fantaisie', + 'Green' => 'Vert', + 'High Dynamic' => 'Haute dynamique', + 'High Key' => 'Tons clairs', + 'Impressive Art' => 'Art impressionnant', + 'Low Key' => 'Tons sombres', + 'Miniature Effect' => 'Effet miniature', + 'None' => 'Aucun', + 'Off' => 'Désactivé', + 'Old Days' => 'Vieux jours', + 'One Point Color' => 'Couleur d\'un point', + 'Red' => 'Rouge', + 'Retro' => 'Rétro', + 'Rough Monochrome' => 'Monochrome brut', + 'Sepia' => 'Sépia', + 'Silky Monochrome' => 'Monochrome soyeux', + 'Soft Focus' => 'Flou artistique', + 'Star Filter' => 'Filtre d\'étoiles', + 'Sunshine' => 'Soleil', + 'Toy Effect' => 'Effet jouet', + 'Toy Pop' => 'Jouet Pop', + 'Yellow' => 'Jaune', + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffectAuto' => { + Description => 'Effet filtre Auto', + PrintConv => { + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffectFaithful' => { + Description => 'Effet filtre Fidèle', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffectLandscape' => { + Description => 'Effet filtre Paysage', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffectMonochrome' => { + Description => 'Effet filtre Monochrome', + PrintConv => { + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffectNeutral' => { + Description => 'Effet filtre Neutre', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffectPortrait' => { + Description => 'Effet filtre Portrait', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffectStandard' => { + Description => 'Effet filtre Standard', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffectUserDef1' => { + Description => 'Effet filtre définit par l\'utilisateur 1', + PrintConv => { + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffectUserDef2' => { + Description => 'Effet filtre définit par l\'utilisateur 2', + PrintConv => { + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + 'n/a' => 'Non applicable', + }, + }, + 'FilterEffectUserDef3' => { + Description => 'Effet filtre définit par l\'utilisateur 3', + PrintConv => { + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + 'n/a' => 'Non applicable', + }, + }, + 'FinderDisplayDuringExposure' => { + Description => 'Affich. viseur pendant expo.', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'Firmware' => 'Micrologiciel', + 'Firmware2' => 'Micrologiciel 2', + 'FirmwareDate' => 'Date du micrologiciel', + 'FirmwareID' => 'Identifiant du micrologiciel', + 'FirmwareName' => 'Nom du micrologiciel', + 'FirmwareRevision' => 'Révision du micrologiciel', + 'FirmwareRevision2' => 'Version du micrologiciel 2', + 'FirmwareVersion' => 'Version du micrologiciel', + 'FirmwareVersion2' => 'Version du firmware 2', + 'FirmwareVersion3' => 'Version de micrologiciel 3', + 'FirmwareVersions' => 'Versions du micrologiciel', + 'FirstName' => 'Prénom', + 'FisheyeFilter' => { + Description => 'Filtre Fisheye', + PrintConv => { + 'Off' => 'Désactivé', + }, + }, + 'FixtureIdentifier' => 'Identificateur d\'installation', + 'Flash' => { + Description => 'Flash ', + PrintConv => { + 'Auto, Did not fire' => 'Flash non déclenché, mode auto', + 'Auto, Did not fire, Red-eye reduction' => 'Auto, flash non déclenché, mode réduction yeux rouges', + 'Auto, Fired' => 'Flash déclenché, mode auto', + 'Auto, Fired, Red-eye reduction' => 'Flash déclenché, mode auto, mode réduction yeux rouges, lumière renvoyée détectée', + 'Auto, Fired, Red-eye reduction, Return detected' => 'Flash déclenché, mode auto, lumière renvoyée détectée, mode réduction yeux rouges', + 'Auto, Fired, Red-eye reduction, Return not detected' => 'Flash déclenché, mode auto, lumière renvoyée non détectée, mode réduction yeux rouges', + 'Auto, Fired, Return detected' => 'Flash déclenché, mode auto, lumière renvoyée détectée', + 'Auto, Fired, Return not detected' => 'Flash déclenché, mode auto, lumière renvoyée non détectée', + 'Did not fire' => 'Flash non déclenché', + 'Fired' => 'Flash déclenché', + 'Fired, Red-eye reduction' => 'Flash déclenché, mode réduction yeux rouges', + 'Fired, Red-eye reduction, Return detected' => 'Flash déclenché, mode réduction yeux rouges, lumière renvoyée détectée', + 'Fired, Red-eye reduction, Return not detected' => 'Flash déclenché, mode réduction yeux rouges, lumière renvoyée non détectée', + 'Fired, Return detected' => 'Lumière renvoyée sur le capteur détectée', + 'Fired, Return not detected' => 'Lumière renvoyée sur le capteur non détectée', + 'No Flash' => 'Flash non déclenché', + 'No flash function' => 'Pas de fonction flash', + 'Off' => 'Désactivé', + 'Off, Did not fire' => 'Flash non déclenché, mode flash forcé', + 'Off, Did not fire, Return not detected' => 'Éteint, flash non déclenché, lumière renvoyée non détectée', + 'Off, No flash function' => 'Éteint, pas de fonction flash', + 'Off, Red-eye reduction' => 'Éteint, mode réduction yeux rouges', + 'On' => 'Activé', + 'On, Did not fire' => 'Hors service, flash non déclenché', + 'On, Fired' => 'Flash déclenché, mode flash forcé', + 'On, Red-eye reduction' => 'Flash déclenché, mode forcé, mode réduction yeux rouges', + 'On, Red-eye reduction, Return detected' => 'Flash déclenché, mode forcé, mode réduction yeux rouges, lumière renvoyée détectée', + 'On, Red-eye reduction, Return not detected' => 'Flash déclenché, mode forcé, mode réduction yeux rouges, lumière renvoyée non détectée', + 'On, Return detected' => 'Flash déclenché, mode flash forcé, lumière renvoyée détectée', + 'On, Return not detected' => 'Flash déclenché, mode flash forcé, lumière renvoyée non détectée', + }, + }, + 'FlashAction' => { + Description => 'Action du flash', + PrintConv => { + 'Did not fire' => 'N\'a pas déclenché', + 'External Flash Fired' => 'Flash externe déclenché', + 'External Flash, Did not fire' => 'Flash externe, n\'a pas déclenché', + 'External Flash, Fired' => 'Flash externe, déclenché', + 'Fired' => 'Déclenché', + 'Flash Fired' => 'Flash déclenché', + 'Wireless Controlled Flash Fired' => 'Flash commandé sans cordon déclenché', + }, + }, + 'FlashActionExternal' => { + Description => 'Action Flash Externe', + PrintConv => { + 'Did not fire' => 'N\'a pas déclenché', + 'Fired' => 'A déclenché', + 'Fired, HSS' => 'Déclenché HSS', + }, + }, + 'FlashActivity' => 'Activité du flash', + 'FlashAttributes' => { + Description => 'Attribut du flash', + PrintConv => { + 'ActionScript3' => 'Action Script 3', + 'HasMetadata' => 'A des métadonnées', + 'UseNetwork' => 'Utilise le réseau', + }, + }, + 'FlashBatteryLevel' => 'Niveau de batterie du flash', + 'FlashBias' => 'Décalage Flash', + 'FlashBits' => { + Description => 'Bits de flash', + PrintConv => { + '(none)' => '(aucun)', + '2nd-curtain sync used' => 'Synchronisation du 2ème rideau utilisée', + 'Built-in' => 'Intégré', + 'External' => 'Externe', + 'FP sync enabled' => 'Synchro FP activée', + 'FP sync used' => 'Synchro FP utilisée', + 'Manual' => 'Manuel', + }, + }, + 'FlashBurstPriority' => { + Description => 'Priorité à la rafale de flash', + PrintConv => { + 'Exposure' => 'Exposition', + 'Frame Rate' => 'Fréquence d\'images', + }, + }, + 'FlashButtonFunction' => { + Description => 'Fonction du bouton de flash', + PrintConv => { + 'ISO speed' => 'Vitesse ISO', + 'Raise built-in flash' => 'Relever le flash intégré', + }, + }, + 'FlashChargeLevel' => 'Niveau de charge du flash', + 'FlashColorFilter' => { + Description => 'Filtre couleur du flash', + PrintConv => { + 'Amber' => 'Ambré', + 'Blue' => 'Bleu', + 'FL-GL1 or SZ-2FL Fluorescent' => 'FL-GL1 ou SZ-2FL Fluorescent', + 'None' => 'Aucun', + 'Red' => 'Rouge', + 'TN-A1 or SZ-2TN Incandescent' => 'TN-A1 ou SZ-2TN Incandescent', + 'Yellow' => 'Jaune', + }, + }, + 'FlashCommanderMode' => { + Description => 'Mode commandant du flash', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'FlashCompensation' => 'Compensation du flash', + 'FlashControl' => { + Description => 'Contrôle du flash', + PrintConv => { + 'ADI Flash' => 'Flash ADI', + 'Manual' => 'Manuel', + 'Pre-flash TTL' => 'Pré-flash TTL', + }, + }, + 'FlashControlBuilt-in' => { + Description => 'Contrôle du flash intégré', + PrintConv => { + 'Commander Mode' => 'Mode Maître', + 'Manual' => 'Manuel', + 'Repeating Flash' => 'Flash répétitif', + }, + }, + 'FlashControlMode' => { + Description => 'Mode de Contrôle du Flash', + PrintConv => { + 'Auto Aperture' => 'Ouverture automatique', + 'Auto External Flash' => 'Flash externe automatique', + 'Automatic' => 'Automatique', + 'GN (distance priority)' => 'GN (priorité à la distance)', + 'Manual' => 'Manuel', + 'Off' => 'Désactivé', + 'Repeating Flash' => 'Flash répétitif', + }, + }, + 'FlashCurtain' => { + Description => 'Rideau du flash', + PrintConv => { + '1st' => '1er', + '2nd' => '2ième', + 'n/a' => 'Non applicable', + }, + }, + 'FlashDefault' => { + Description => 'Falsh par défaut', + PrintConv => { + 'Fill Flash' => 'Flash d\'appoint', + }, + }, + 'FlashDevice' => { + PrintConv => { + 'None' => 'Aucune', + }, + }, + 'FlashEnergy' => 'Énergie du flash', + 'FlashExposureBracketValue' => 'Valeur du bracketing d\'exposition au flash', + 'FlashExposureComp' => 'Compensation d\'exposition au flash', + 'FlashExposureCompSet' => 'Réglage de compensation d\'exposition au flash', + 'FlashExposureLock' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'FlashFired' => { + Description => 'Flash utilisé', + PrintConv => { + 'External' => 'Externe', + 'False' => 'Faux', + 'Internal' => 'Interne', + 'No' => 'Non', + 'True' => 'Vrai', + 'Yes' => 'Oui', + }, + }, + 'FlashFiring' => { + Description => 'Émission de l\'éclair', + PrintConv => { + 'Does not fire' => 'Désactivé', + 'Fires' => 'Activé', + }, + }, + 'FlashFirmwareVersion' => 'Version du micrologiciel du flash', + 'FlashFocalLength' => 'Focale Flash', + 'FlashFunction' => { + Description => 'Fonction flash', + PrintConv => { + 'Bounce flash' => 'Flash à rebond', + 'Built-in flash' => 'Flash intégré', + 'False' => 'Faux', + 'Fill flash, ADI' => 'Flash d\'appoint, ADI', + 'Fill flash, Pre-flash TTL' => 'Flash d\'appoint, pré-flash TTL', + 'HSS' => 'Synchro Haute Vitesse (HSS)', + 'Manual' => 'Manuel', + 'No flash' => 'Pas de flash', + 'Rear sync, ADI' => 'Synchro arrière, ADI', + 'True' => 'Vrai', + 'Wireless' => 'Sans cordon', + }, + }, + 'FlashGNDistance' => { + Description => 'Distance du flash GN', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'FlashGroupACompensation' => 'Compensation du groupe de flash A', + 'FlashGroupAControlMode' => { + Description => 'Mode de contrôle du groupe de flash A', + PrintConv => { + 'Auto Aperture' => 'Ouverture automatique', + 'Automatic' => 'Automatique', + 'GN (distance priority)' => 'GN (priorité à la distance)', + 'Manual' => 'Manuel', + 'Off' => 'Désactivé', + 'Repeating Flash' => 'Répétition du flash', + }, + }, + 'FlashGroupAOutput' => 'Sortie Flash Groupe A', + 'FlashGroupBCompensation' => 'Compensation du groupe de flash B', + 'FlashGroupBControlMode' => { + Description => 'Mode de contrôle du groupe de flash B', + PrintConv => { + 'Auto Aperture' => 'Ouverture automatique', + 'Automatic' => 'Automatique', + 'GN (distance priority)' => 'GN (priorité à la distance)', + 'Manual' => 'Manuel', + 'Off' => 'Désactivé', + 'Repeating Flash' => 'Répétition du flash', + }, + }, + 'FlashGroupBOutput' => 'Sortie Flash Groupe B', + 'FlashGroupCCompensation' => 'Compensation du groupe de flash C', + 'FlashGroupCControlMode' => { + Description => 'Mode de contrôle du groupe de flash C', + PrintConv => { + 'Auto Aperture' => 'Ouverture automatique', + 'Automatic' => 'Automatique', + 'GN (distance priority)' => 'GN (priorité à la distance)', + 'Manual' => 'Manuel', + 'Off' => 'Désactivé', + 'Repeating Flash' => 'Répétition du flash', + }, + }, + 'FlashGroupCOutput' => 'Sortie Flash Groupe C', + 'FlashGuideNumber' => 'Numéro du guide du flash', + 'FlashInfo' => 'Information flash', + 'FlashInfoVersion' => 'Version de l\'info Flash', + 'FlashIntensity' => { + Description => 'Intensité du flash', + PrintConv => { + 'High' => 'Haute', + 'Low' => 'Basse', + 'Normal' => 'Normale', + 'Strong' => 'Forte', + 'Weak' => 'Faible', + 'n/a' => 'Non applicable', + 'n/a (x4)' => 'Non applicable (x4)', + }, + }, + 'FlashLevel' => { + Description => 'Niveau de flash', + PrintConv => { + 'High' => 'Haut', + 'Low' => 'Bas', + 'n/a' => 'Non applicable', + }, + }, + 'FlashMeteringSegments' => 'Segments de mesure flash', + 'FlashMode' => { + Description => 'Mode du flash', + PrintConv => { + '2nd Curtain' => 'Flash 2ème rideau', + 'Auto' => 'Flash Auto', + 'Auto, Did not fire' => 'Flash Auto, non déclenché', + 'Auto, Did not fire, Red-eye reduction' => 'Flash Auto, non déclenché, réduction yeux rouges', + 'Auto, Fired' => 'Flash Auto, déclenché', + 'Auto, Fired, Red-eye reduction' => 'Auto, flash déclenché, réduction yeux rouges', + 'Autoflash' => 'Flash auto (Autoflash)', + 'Did Not Fire' => 'Flash non-déclenché', + 'Disabled' => 'Flash désactivé', + 'External, Auto' => 'Flash externe, auto', + 'External, Contrast-control Sync' => 'Flash externe, synchro contrôle des contrastes', + 'External, Flash Problem' => 'Flash externe, problème de flash', + 'External, High-speed Sync' => 'Flash externe, synchro haute vitesse', + 'External, Manual' => 'Flash externe, manuel', + 'External, P-TTL Auto' => 'Flash externe, P-TTL', + 'External, Wireless' => 'Flash externe, sans cordon', + 'External, Wireless, High-speed Sync' => 'Flash externe, sans cordon, synchro haute vitesse', + 'Fill Flash' => 'Flash d\'appoint', + 'Fill flash' => 'Flash d\'appoint', + 'Fill-flash' => 'Flash d\'appoint', + 'Fill-in' => 'Flash d\'Appoint (Fill-in)', + 'Fired, Commander Mode' => 'Flash déclenché, Mode Maître', + 'Fired, External' => 'Flash déclenché, Exterieur', + 'Fired, Manual' => 'Flash déclenché, Manuel', + 'Fired, TTL Mode' => 'Flash déclenché, Mode TTL', + 'Flash Off' => 'Flash désactivé', + 'Force' => 'Flash forcé', + 'Forced On' => 'Flash activé forcé', + 'Internal' => 'Flash interne', + 'LED Light' => 'Flash LED', + 'Manual' => 'Flash manuel', + 'Normal' => 'Flash normale', + 'Not Ready' => 'Flash non prêt', + 'Off' => 'Flash désactivé', + 'Off, Did not fire' => 'Flash désactivé, non déclenché', + 'Off?' => 'Flash désactivé ?', + 'On' => 'Flash activé', + 'On, Did not fire' => 'Flash activé, non déclenché', + 'On, Did not fire, Wireless (Master)' => 'Flash activé, non déclenché, sans cordon (Maître)', + 'On, Fired' => 'Flash activé, déclenché', + 'On, Red-eye reduction' => 'Flash activé, réduction yeux rouges', + 'On, Slow-sync' => 'Flash activé, synchro lente', + 'On, Slow-sync, Red-eye reduction' => 'Flash activé, synchro lente, réduction yeux rouges', + 'On, Soft' => 'Flash activé, doux', + 'On, Trailing-curtain Sync' => 'Flash activé, synchro 2e rideau', + 'On, Wireless (Control)' => 'Flash activé, sans cordon (Contrôle)', + 'On, Wireless (Master)' => 'Flash activé, sans cordon (Maître)', + 'Rear Sync' => 'Flash synchro arrière', + 'Rear flash sync' => 'Flash synchro arrière', + 'Red eye' => 'Flash yeux rouge', + 'Red-Eye' => 'Flash yeux rouge', + 'Red-Eye?' => 'Flash yeux rouge ?', + 'Red-eye' => 'Flash yeux rouge', + 'Red-eye Reduction' => 'Flash yeux rouge, réduction yeux rouges', + 'Red-eye reduction' => 'Flash yeux rouge réduction yeux rouges', + 'Slow Sync' => 'Flash synchro lente', + 'Slow-sync' => 'Flash synchro lente', + 'Synchro, Red-eye reduction' => 'Flash synchro réduction yeux rouges', + 'Unknown' => 'Inconnu', + 'Wireless' => 'Flash sans cordon', + 'n/a - Off-Auto-Aperture' => 'Non applicable - Auto-diaph hors service', + }, + }, + 'FlashModel' => { + Description => 'Modèle du Flash', + PrintConv => { + 'None' => 'Aucun', + }, + }, + 'FlashOptions' => { + Description => 'Options du flash', + PrintConv => { + 'Auto, Red-eye reduction' => 'Auto, réduction yeux rouges', + 'Normal' => 'Normale', + 'Red-eye reduction' => 'Réduction yeux rouges', + 'Slow-sync' => 'Synchro lente', + 'Slow-sync, Red-eye reduction' => 'Synchro lente, réduction yeux rouges', + 'Trailing-curtain Sync' => 'Synchro 2e rideau', + 'Wireless (Control)' => 'Sans cordon (contrôleur)', + 'Wireless (Master)' => 'Sans cordon (maître)', + }, + }, + 'FlashOptions2' => { + Description => 'Options de flash (2)', + PrintConv => { + 'Auto, Red-eye reduction' => 'Auto, réduction yeux rouges', + 'Normal' => 'Normale', + 'Red-eye reduction' => 'Réduction yeux rouges', + 'Slow-sync' => 'Synchro lente', + 'Slow-sync, Red-eye reduction' => 'Synchro lente, réduction yeux rouges', + 'Trailing-curtain Sync' => 'Synchro 2e rideau', + 'Wireless (Control)' => 'Sans cordon (contrôleur)', + 'Wireless (Master)' => 'Sans cordon (maître)', + }, + }, + 'FlashOutput' => 'Puissance du flash', + 'FlashPower' => 'Puissance du flash', + 'FlashRedEyeMode' => { + Description => 'Flash anti-yeux rouges', + PrintConv => { + 'False' => 'Faux', + 'True' => 'Vrai', + }, + }, + 'FlashReturn' => { + Description => 'Retour de flash', + PrintConv => { + 'No return detection' => 'Pas de détection de retour', + 'Return detected' => 'Retour détecté', + 'Return not detected' => 'Retour non détecté', + 'Subject Inside Flash Range' => 'Sujet en porté du flash', + 'Subject Outside Flash Range' => 'Sujet hors de porté du flash', + }, + }, + 'FlashSerialNumber' => 'Numéro de série du flash', + 'FlashSetting' => 'Réglages Flash', + 'FlashSource' => { + Description => 'Source du flash', + PrintConv => { + 'External' => 'Externe', + 'Internal' => 'Interne', + 'None' => 'Aucun', + }, + }, + 'FlashStatus' => { + Description => 'État du flash', + PrintConv => { + 'Built-in' => 'Flash intégré', + 'Built-in Flash Fired' => 'Flash intégré déclenché', + 'Built-in Flash Inhibited' => 'Flash intégré inhibé', + 'Built-in Flash present' => 'Flash intégré présent', + 'External' => 'Flash externe', + 'External Flash Fired' => 'Flash externe déclenché', + 'External Flash present' => 'Flash externe présent', + 'External, Did not fire' => 'Flash externe, non déclenché', + 'External, Fired' => 'Flash externe, déclenché', + 'Flash Inhibited' => 'Flash inhibé', + 'Internal, Did not fire' => 'Flash interne, non déclenché', + 'Internal, Did not fire (0x08)' => 'Flash interne, non déclenché (0x08)', + 'Internal, Fired' => 'Flash interne, déclenché', + 'No Flash present' => 'Aucun flash présent', + 'None' => 'Aucun', + 'Off' => 'Désactivé', + 'Off (1)' => 'Désactivé (1)', + }, + }, + 'FlashSyncSpeedAv' => { + Description => 'Vitesse synchro en mode Av', + PrintConv => { + '1/200 Fixed' => '1/200 fixe', + '1/250 Fixed' => '1/250 fixe', + '1/300 Fixed' => '1/300 fixe', + }, + }, + 'FlashType' => { + Description => 'Type de flash', + PrintConv => { + 'Built-In Flash' => 'Intégré', + 'External' => 'Externe', + 'None' => 'Aucune', + }, + }, + 'FlashWarning' => { + Description => 'Avertissement de flash', + PrintConv => { + 'No' => 'Non', + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'Yes (flash required but disabled)' => 'Oui (flash requis mais désactivé)', + }, + }, + 'FlashpixVersion' => 'Version Flashpix', + 'FlexibleSpotPosition' => 'Position flexible du spot', + 'FlickerReduce' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'FlightDegree' => 'Degré en vol', + 'FlightPitchDegree' => 'Degré d\'inclinaison en vol', + 'FlightRollDegree' => 'Degré de roulis en vol', + 'FlightSpeed' => 'Vitesse en vol', + 'FlightXSpeed' => 'Vitesse horizontale (X) en vol', + 'FlightYSpeed' => 'Vitesse verticale (Y) en vol', + 'FlightYawDegree' => 'Degré de lacet en vol', + 'FlightZSpeed' => 'Vitesse en profondeur (Z) du vol', + 'FlipAngle' => 'Angle de retournement', + 'FlipHorizontal' => { + Description => 'Retournement horizontal', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'FlipStatus' => 'État du retournement', + 'FocalLength' => 'Focale de l\'objectif', + 'FocalLength35efl' => 'Focale de l\'objectif', + 'FocalLength35mm' => 'Distance focale 35 mm', + 'FocalLengthIn35mmFormat' => 'Longueur de focale équivalente à un film 35 mm', + 'FocalPlaneAFPointArea' => 'Plan focal de la zone de mise au point automatique', + 'FocalPlaneAFPointLocation1' => 'Localisation de la zone de mise au point automatique du plan focal 1', + 'FocalPlaneAFPointLocation10' => 'Localisation de la zone de mise au point automatique du plan focal 10', + 'FocalPlaneAFPointLocation11' => 'Localisation de la zone de mise au point automatique du plan focal 11', + 'FocalPlaneAFPointLocation12' => 'Localisation de la zone de mise au point automatique du plan focal 12', + 'FocalPlaneAFPointLocation13' => 'Localisation de la zone de mise au point automatique du plan focal 13', + 'FocalPlaneAFPointLocation14' => 'Localisation de la zone de mise au point automatique du plan focal 14', + 'FocalPlaneAFPointLocation15' => 'Localisation de la zone de mise au point automatique du plan focal 15', + 'FocalPlaneAFPointLocation2' => 'Localisation de la zone de mise au point automatique du plan focal 2', + 'FocalPlaneAFPointLocation3' => 'Localisation de la zone de mise au point automatique du plan focal 3', + 'FocalPlaneAFPointLocation4' => 'Localisation de la zone de mise au point automatique du plan focal 4', + 'FocalPlaneAFPointLocation5' => 'Localisation de la zone de mise au point automatique du plan focal 5', + 'FocalPlaneAFPointLocation6' => 'Localisation de la zone de mise au point automatique du plan focal 6', + 'FocalPlaneAFPointLocation7' => 'Localisation de la zone de mise au point automatique du plan focal 7', + 'FocalPlaneAFPointLocation8' => 'Localisation de la zone de mise au point automatique du plan focal 8', + 'FocalPlaneAFPointLocation9' => 'Localisation de la zone de mise au point automatique du plan focal 9', + 'FocalPlaneAFPointsUsed' => { + Description => 'Points de mise au point automatique du plan focal utilisés', + PrintConv => { + '(none)' => 'Aucun', + }, + }, + 'FocalPlaneDiagonal' => 'Diagonale du plan focal', + 'FocalPlaneResolutionUnit' => { + Description => 'Unité de résolution de plan focal', + PrintConv => { + 'None' => 'Aucune', + 'inches' => 'Pouces', + 'um' => 'µm (micromètre)', + }, + }, + 'FocalPlaneXResolution' => 'Résolution du plan focal horizontal', + 'FocalPlaneXSize' => 'Taille du plan focal horizontal', + 'FocalPlaneXUnknown' => 'Plan focal inconnu', + 'FocalPlaneYResolution' => 'Résolution du plan focal vertical', + 'FocalPlaneYSize' => 'Taille du plan focal vertical', + 'FocalType' => { + Description => 'Type de focal', + PrintConv => { + 'Fixed' => 'Fixe', + }, + }, + 'FocalUnits' => 'Unités focales', + 'Focus' => { + Description => 'Mise au point', + PrintConv => { + 'Auto-focus Didn\'t Lock' => 'Mise au point automatique non verrouillée', + 'Auto-focus Locked' => 'Mise au point automatique verrouillée', + 'Manual' => 'Manuelle', + }, + }, + 'FocusContinuous' => { + Description => 'Mise au point continue', + PrintConv => { + 'Continuous' => 'Continue', + 'Manual' => 'Manuelle', + 'Single' => 'Simple', + }, + }, + 'FocusDistance' => 'Distance focale', + 'FocusDistance2' => 'Distance focale 2', + 'FocusDistanceLower' => 'Distance focale inférieure', + 'FocusDistanceRange' => 'Plage de distance focale', + 'FocusDistanceRangeWidth' => 'Largeur de la plage de distance focale', + 'FocusDistanceUpper' => 'Distance focale supérieure', + 'FocusFrameSize' => 'Taille focale de mise au point', + 'FocusInfoVersion' => 'Version infos de focus', + 'FocusLocation' => 'Localisation du focus', + 'FocusLocked' => { + Description => 'Focus verrouillé', + PrintConv => { + 'Continuous Focus' => 'Focus continu', + 'Manual Focus' => 'Focus manuel', + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'FocusMode' => { + Description => 'Mode de mise au point', + PrintConv => { + '(none)' => '(aucun)', + 'AF sensor' => 'Capteur AF', + 'AF-A (Focus-priority)' => 'AF-A (Priorité à la mise au point)', + 'AF-A (Release-priority)' => 'AF-A (Priorité à la libération)', + 'AF-C' => 'AF-C (prise de vue en rafale)', + 'AF-C (Focus-priority)' => 'AF-C (Priorité à la mise au point)', + 'AF-C (Release-priority)' => 'AF-C (Priorité à la libération)', + 'AF-S' => 'AF-S (prise de vue unique)', + 'AF-S (Focus-priority)' => 'AF-S (Priorité à la mise au point)', + 'AF-S (Release-priority)' => 'AF-S (Priorité à la libération)', + 'Auto, Continuous' => 'Auto, continue', + 'Auto, Focus button' => 'Bouton autofocus', + 'Continuous' => 'Auto, continue', + 'Continuous AF' => 'Auto, continue AF', + 'Contrast-detect (Focus-priority)' => 'Détection du contraste (Priorité à la mise au point)', + 'Contrast-detect (Release-priority)' => 'Détection du contraste (Priorité au déclenchement)', + 'Custom' => 'Personalisé', + 'Face Detect' => 'Détection de visage', + 'Face detect' => 'Détection de visage', + 'Focus Lock' => 'Verrouillage de la mise au point', + 'Infinity' => 'Infini', + 'Live View Magnification Frame' => 'Cadre d\'agrandissement Live View', + 'Manual' => 'Manuelle', + 'Manual Focus (3)' => 'Mise au point manuelle (3)', + 'Manual Focus (6)' => 'Mise au point manuelle (6)', + 'Movie' => 'Vidéo', + 'Movie Servo AF' => 'Vidéo Servo AF', + 'Movie Snap Focus' => 'Vidéo Snap Focus', + 'Multi-Area Auto Focus' => 'Mise au point automatique multizone', + 'Normal' => 'Normale', + 'One-shot AF' => 'Mise au point automatique en une seule prise', + 'Pan Focus' => 'Hyperfocale', + 'Permanent-AF' => 'Permanent AF', + 'Pinpoint AF' => 'Point de mire AF', + 'Semi-manual' => 'Semi-manuelle', + 'Sequential shooting AF' => 'Prise de vue séquentielle en mise au point automatique', + 'Single' => 'Simple', + 'Single AF' => 'Mise au point automatique unique', + 'Single-Area Auto Focus' => 'Mise au point automatique à zone unique', + 'Snap' => 'Instantané', + 'Starry Sky AF' => 'Ciel étoilé AF', + 'Subject Tracking' => 'Suivi du sujet', + 'Tracking Contrast-detect (Focus-priority)' => 'Suivi de la détection du contraste (priorité à la mise au point)', + 'n/a' => 'Non applicable', + }, + }, + 'FocusMode2' => { + Description => 'Mode de mise au point 2', + PrintConv => { + 'AF-C' => 'AF-C (prise de vue en rafale)', + 'AF-S' => 'AF-S (prise de vue unique)', + 'Manual' => 'Manuelle', + }, + }, + 'FocusModeSetting' => { + PrintConv => { + 'AF-C' => 'AF-C (prise de vue en rafale)', + 'AF-S' => 'AF-S (prise de vue unique)', + 'Manual' => 'Manuelle', + }, + }, + 'FocusPixel' => 'Pixels de mise au point', + 'FocusPosition' => 'Distance de mise au point', + 'FocusPosition2' => 'Distance 2 de mise au point', + 'FocusProcess' => { + Description => 'Processus de mise au point', + PrintConv => { + 'AF Not Used' => 'Autofocus non utilisé', + 'AF Used' => 'Autofocus utilisé', + }, + }, + 'FocusRange' => { + Description => 'Plage de mise au point', + PrintConv => { + 'Close' => 'Près', + 'Far Range' => 'Distance lointaine', + 'Infinity' => 'Infini', + 'Manual' => 'Manuelle', + 'Middle Range' => 'Distance moyenne', + 'Normal' => 'Normale', + 'Not Known' => 'Non connue', + 'Pan Focus' => 'Hyperfocale', + 'Super Macro' => 'Super macro', + 'Very Close' => 'Très près', + }, + }, + 'FocusRangeIndex' => { + Description => 'Index de la plage de mise au point', + PrintConv => { + '0 (very close)' => '0 (très proche)', + '1 (close)' => '1 (proche)', + '6 (far)' => '6 (loin)', + '7 (very far)' => '7 (très loin)', + }, + }, + 'FocusSetting' => 'Réglage de la mise au point', + 'FocusStepCount' => 'Nombre de pas du focus', + 'FocusTrackingLockOn' => { + PrintConv => { + 'Normal' => 'Normale', + 'Off' => 'Désactivé', + }, + }, + 'FocusWarning' => { + Description => 'Alerte de mise au point', + PrintConv => { + 'Good' => 'Bonne focalisation', + 'Out of focus' => 'Pas de focalisation', + }, + }, + 'FocusingScreen' => 'Ecran de mise au point', + 'ForwardMatrix1' => 'Matrice de déplacement 1', + 'ForwardMatrix2' => 'Matrice de déplacement 2', + 'ForwardMatrix3' => 'Matrice de déplacement 3', + 'ForwardTo' => 'Déplacer vers', + 'FrameNumber' => 'Numéro de vue', + 'FrameRate' => 'Vitesse', + 'FrameSize' => 'Taille du cadre', + 'FreeByteCounts' => 'Nombre d\'octets libres', + 'FreeBytes' => 'Octets libres', + 'FreeOffsets' => 'Offsets libres', + 'FujiFlashMode' => { + Description => 'Mode Flash Fuji', + PrintConv => { + 'Commander' => 'Commandeur', + 'External' => 'Externe', + 'Flash Commander' => 'Commandeur de Flash', + 'High Speed Sync (HSS)' => 'Synchronisation haute vitesse (HSS)', + 'Manual' => 'Manuel', + 'Not Attached' => 'Non attaché', + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'Red-eye reduction' => 'Réduction yeux rouges', + 'TTL - Red-eye Flash - 1st Curtain (front)' => 'TTL - Flash yeux rouges - 1er rideau (avant)', + 'TTL - Red-eye Flash - 2nd Curtain (rear)' => 'TTL - Flash yeux rouges - 2eme rideau (arrière)', + 'TTL Auto - 1st Curtain (front)' => 'TTL Auto - 1er rideau (avant)', + 'TTL Auto - 2nd Curtain (rear)' => 'TTL Auto - 2eme rideau (arrière)', + 'TTL Auto - Did not fire' => 'TTL Auto - Non déclenché', + 'TTL Auto - Red-eye Flash - 1st Curtain (front)' => 'TTL Auto - 1er rideau (avant)', + 'TTL Auto - Red-eye Flash - 2nd Curtain (rear)' => 'TTL Auto - 2eme rideau (arrière)', + 'TTL Slow - 1st Curtain (front)' => 'TTL Lent - 1er rideau (avant', + 'TTL Slow - 2nd Curtain (rear)' => 'TTL Lent - 2eme rideau (arrière)', + 'TTL Slow - Red-eye Flash - 1st Curtain (front)' => 'TTL Lent - Flash yeux rouges - 1er rideau (avant', + 'TTL Slow - Red-eye Flash - 2nd Curtain (rear)' => 'TTL Lent - Flash yeux rouges - 2eme rideau (arrière)', + }, + }, + 'FujiLayout' => 'Mise en page Fuji', + 'FujiModel' => 'Modèle Fuji', + 'FujiModel2' => 'Modèle Fuji2', + 'FullImageSize' => 'Taille complète de l\'image', + 'FullName' => 'Nom complet', + 'Func1Button' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'Func1ButtonPlaybackMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'Func1ButtonPlusDials' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'Func2Button' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'Func2ButtonPlaybackMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'Func2ButtonPlusDials' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'Func3Button' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'Func3ButtonPlaybackMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'Func4Button' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'Func4ButtonPlaybackMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'FuncButton' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'FuncButtonPlusDials' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'FunctionButton' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + 'Center-weighted' => 'Pondération centrale', + }, + }, + 'GIFVersion' => 'Version GIF', + 'GPSAltitude' => 'Altitude GPS', + 'GPSAltitudeRef' => { + Description => 'Référence de l\'altitude GPS', + PrintConv => { + 'Above Sea Level' => 'Au-dessus du niveau de la mer', + 'Below Sea Level' => 'En-dessous du niveau de la mer', + }, + }, + 'GPSAreaInformation' => 'Nom de la zone GPS', + 'GPSCoordinates' => 'Coordonnées GPS', + 'GPSDOP' => 'Précision de la mesure', + 'GPSDataList' => 'Liste des données GPS', + 'GPSDataList2' => 'Liste des données GPS 2', + 'GPSDateStamp' => 'Horodatage GPS', + 'GPSDateTime' => 'Date/Heure GPS (horloge atomique)', + 'GPSDateTimeRaw' => 'Date/Heure GPS brute', + 'GPSDestAltitude' => 'Altitude GPS de la destination', + 'GPSDestBearing' => 'Azimut GPS de la destination', + 'GPSDestBearingRef' => { + Description => 'Référence de l\'azimut GPS de la destination', + PrintConv => { + 'Magnetic North' => 'Nord magnétique', + 'True North' => 'Nord vrai', + }, + }, + 'GPSDestDistance' => 'Distance GPS de la destination', + 'GPSDestDistanceRef' => { + Description => 'Référence de la distance GPS de la destination', + PrintConv => { + 'Kilometers' => 'Kilomètres', + 'Nautical Miles' => 'Milles nautiques', + }, + }, + 'GPSDestLatitude' => 'Latitude GPS de la destination', + 'GPSDestLatitudeRef' => { + Description => 'Référence de la valeur GPS en latitude de la destination', + PrintConv => { + 'North' => 'Nord', + 'South' => 'Sud', + }, + }, + 'GPSDestLongitude' => 'Longitude GPS de la destination', + 'GPSDestLongitudeRef' => { + Description => 'Référence de la valeur GPS en longitude de la destination', + PrintConv => { + 'East' => 'Est', + 'West' => 'Ouest', + }, + }, + 'GPSDifferential' => { + Description => 'Différentiel GPS', + PrintConv => { + 'Differential Corrected' => 'Différentiel corrigé', + 'No Correction' => 'Sans correction', + }, + }, + 'GPSFramingAltitude' => 'Altitude de cadrage GPS', + 'GPSFramingLatitude' => 'Latitude de cadrage GPS', + 'GPSFramingLongitude' => 'Longitude de cadrage GPS', + 'GPSHPositioningError' => 'Erreur horizontale de la position GPS', + 'GPSHorizontalAccuracy' => 'Précision GPS horizontale', + 'GPSImgDirection' => 'Direction GPS de la prise de vue', + 'GPSImgDirectionRef' => { + Description => 'Référence de la direction GPS de la prise de vue', + PrintConv => { + 'Magnetic North' => 'Nord magnétique', + 'True North' => 'Nord vrai', + }, + }, + 'GPSInfo' => 'Pointeur IFD d\'informations GPS', + 'GPSLatitude' => 'Latitude GPS', + 'GPSLatitudeRaw' => 'Latitude GPS brute', + 'GPSLatitudeRef' => { + Description => 'Référence de la valeur GPS en latitude', + PrintConv => { + 'North' => 'Nord', + 'South' => 'Sud', + }, + }, + 'GPSLog' => 'Journal GPS', + 'GPSLongitude' => 'Longitude GPS', + 'GPSLongitudeRaw' => 'Longitude GPS brute', + 'GPSLongitudeRef' => { + Description => 'Référence de la valeur GPS en Longitude', + PrintConv => { + 'East' => 'Est', + 'West' => 'Ouest', + }, + }, + 'GPSLongtitude' => 'Longitude GPS', + 'GPSMapDatum' => 'Données de surveillance géodésique utilisées', + 'GPSMeasureMode' => { + Description => 'Mode de mesure GPS', + PrintConv => { + '2-D' => '2D', + '2-Dimensional' => 'Mesure à deux dimensions', + '2-Dimensional Measurement' => 'Mesure bidimensionnelle', + '3-D' => '3D', + '3-Dimensional' => 'Mesure à trois dimensions', + '3-Dimensional Measurement' => 'Mesure tridimensionnelle', + 'No Measurement' => 'Aucune mesure', + }, + }, + 'GPSMode' => 'Mode GPS', + 'GPSPitch' => 'Tangage GPS', + 'GPSPosition' => 'Position GPS', + 'GPSProcessingMethod' => 'Nom de la méthode de traitement GPS', + 'GPSRoll' => 'Roulis GPS', + 'GPSSatellites' => 'Satellites GPS utilisés pour la mesure', + 'GPSSpeed' => 'Vitesse du récepteur GPS', + 'GPSSpeed3D' => 'Vitesse GPS 3D', + 'GPSSpeedAccuracy' => 'Précision de la vitesse du GPS', + 'GPSSpeedRaw' => 'Vitesse GPS brute', + 'GPSSpeedRef' => { + Description => 'Référence de vitesse du GPS', + PrintConv => { + 'km/h' => 'Kilomètres par heure', + 'knots' => 'nÅ“uds', + 'mph' => 'Miles par heure', + }, + }, + 'GPSStatus' => { + Description => 'État du récepteur GPS', + PrintConv => { + 'Measurement Active' => 'Mesure active', + 'Measurement Void' => 'Mesure vide', + }, + }, + 'GPSTimeStamp' => 'Heure GPS (horloge atomique)', + 'GPSTrack' => 'Tracé GPS', + 'GPSTrackRaw' => 'Tracé GPS brut', + 'GPSTrackRef' => { + Description => 'Référence pour du tracé GPS', + PrintConv => { + 'Magnetic North' => 'Nord magnétique', + 'True North' => 'Nord vrai', + }, + }, + 'GPSVersionID' => 'Identifiant de la version GPS', + 'GTModelType' => { + Description => 'Type de modèle GT', + PrintConv => { + 'Geocentric' => 'Géocentrique', + 'Geographic' => 'Géographique', + 'Projected' => 'Projeté', + 'User Defined' => 'Défini par l\'utilisateur', + }, + }, + 'GainBase' => 'Gain de référence', + 'GainControl' => { + Description => 'Contrôle du gain', + PrintConv => { + 'High gain down' => 'Réduction du gain élevé', + 'High gain up' => 'Augmentation du gain élevé', + 'Low gain down' => 'Réduction du gain faible', + 'Low gain up' => 'Augmentation du gain faible', + 'None' => 'Aucun', + }, + }, + 'GammaBlackPoint' => 'Gamma point noir', + 'GammaBlue' => 'Gamma bleu', + 'GammaColorTone' => 'Gamma Tonalité de couleur', + 'GammaCompensatedValue' => 'Valeur de compensation gamma', + 'GammaContrast' => 'Gamma Contraste', + 'GammaCurveOutputRange' => 'Plage de sortie de la courbe gamma', + 'GammaEnable' => 'Activation du gamma', + 'GammaGreen' => 'Gamma vert', + 'GammaLinear' => { + Description => 'Gamma linéaire', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'Gapless' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'GenProfileCompatibilityFlags' => { + Description => 'Indicateurs de compatibilité du profil Générique', + PrintConv => { + '3D Main' => '3D principal', + 'Format Range Extensions' => 'Extensions de la plage de formats', + 'High Throughput' => 'Haut débit', + 'High Throughput Screen Content Coding Extensions' => 'Extensions du codage du contenu de l\'écrans à haut débit', + 'Main' => 'Principal', + 'Main 10' => 'Principal 10', + 'Main Still Picture' => 'Photo principale', + 'Multiview Main' => 'Multi-vues principale', + 'No Profile' => 'Aucun profil', + 'Scalable Format Range Extensions' => 'Extensions évolutives de la plage de formats', + 'Scalable Main' => 'Évolutif principal', + 'Screen Content Coding Extensions' => 'Extensions du codage du contenu de l\'écran', + }, + }, + 'GeneralLevelIDC' => 'Niveau général IDC', + 'GeneralProfileIDC' => { + Description => 'Profil général IDC', + PrintConv => { + '3D Main' => '3D principal', + 'Format Range Extensions' => 'Extensions de la plage de formats', + 'High Throughput' => 'Haut débit', + 'High Throughput Screen Content Coding Extensions' => 'Extensions du codage du contenu de l\'écrans à haut débit', + 'Main' => 'Principal', + 'Main 10' => 'Principal 10', + 'Main Still Picture' => 'Photo principale', + 'Multiview Main' => 'Multi-vues principale', + 'No Profile' => 'Aucun profil', + 'Scalable Format Range Extensions' => 'Extensions évolutives de la plage de formats', + 'Scalable Main' => 'Évolutif principal', + 'Screen Content Coding Extensions' => 'Extensions du codage du contenu de l\'écran', + }, + }, + 'GeneralProfileSpace' => { + Description => 'Espace du profil général', + PrintConv => { + 'Conforming' => 'Conforme', + }, + }, + 'GeneralTierFlag' => { + Description => 'Drapeau de niveau général', + PrintConv => { + 'High Tier' => 'Niveau supérieur', + 'Main Tier' => 'Niveau principal', + }, + }, + 'GeoTiffAsciiParams' => 'Tag de paramètres Ascii GeoTiff', + 'GeoTiffDirectory' => 'Tag de répertoire de clé GeoTiff', + 'GeoTiffDoubleParams' => 'Tag de paramètres doubles GeoTiff', + 'GeographicalCoordinates' => 'Coordonnées géographiques', + 'Geography' => 'Géographie', + 'Geolocation' => 'Géolocalisation', + 'GeologicalContext' => 'Contexte géologique', + 'GeologicalContextBed' => 'Lit du contexte géologique', + 'GeologicalContextFormation' => 'Formation du contexte géologique', + 'GeologicalContextGroup' => 'Groupe du contexte géologique', + 'GeologicalContextID' => 'Identifiant du contexte géologique', + 'GeologicalContextMember' => 'Membre du contexte géologique', + 'GeometricDistortionParams' => 'Paramètres de la distorsion géométrique', + 'GeometricMaximumDistortion' => 'Distorsion géométrique maximale', + 'GeometricalProperties' => 'Propriétés géométriques', + 'GimbalDegree' => 'Degré du gimbal', + 'GimbalPitch' => 'Inclinaison du gimbal', + 'GimbalPitchDegree' => 'Degré d\'inclinaison du gimbal', + 'GimbalReverse' => 'Inversion du gimbal', + 'GimbalRoll' => 'Roulis du gimbal', + 'GimbalRollDegree' => 'Degré de roulis du gimbal', + 'GimbalYaw' => 'Lacet du gimbal', + 'GimbalYawDegree' => 'Degré de lacet du gimbal', + 'Good' => 'Bon', + 'GoogleBot' => 'Bot Google', + 'GoogleHostHeader' => 'En-tête de l\'hôte Google', + 'GooglePingMessage' => 'Message Ping Google', + 'GooglePingURL' => 'URL de ping Google', + 'GooglePlusUploadCode' => 'Code de téléchargement de Google Plus', + 'GoogleSourceData' => 'Données sources de Google', + 'GoogleStartTime' => 'Heure de début Google', + 'GoogleTrackDuration' => 'Durée du suivi Google', + 'Gradation' => { + PrintConv => { + 'Auto-Override' => 'Priorité automatique', + 'High Key' => 'Tons clairs', + 'Low Key' => 'Tons sombres', + 'Normal' => 'Normale', + 'User-Selected' => 'Sélection utilisateur', + 'n/a' => 'Non applicable', + }, + }, + 'GrayResponseCurve' => 'Courbe de réponse grise', + 'GrayResponseUnit' => { + Description => 'Unités des valeurs de réponses grises', + PrintConv => { + '0.0001' => '0.0001 - La valeur représente des millièmes', + '0.001' => '0.001. La valeur représente des centièmes', + '0.1' => '0.1. La valeur représente des dixièmes', + '1e-05' => '1e-05 - LLa valeur représente des dix-millièmes', + '1e-06' => '1e-06 - La valeur représente des cent-millièmes', + }, + }, + 'GrayTRC' => 'Courbe de reproduction des tonalités grises', + 'GreenAdjust' => 'Ajustement du vert', + 'GreenMatrixColumn' => 'Colonne de la matrice verte', + 'GreenTRC' => 'Courbe de reproduction des tonalités vertes', + 'GridDisplay' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'GridFocalDistance' => 'Distance focale de la grille', + 'GridFrameOffsetVector' => 'Vecteur de décalage du cadre de la grille', + 'GridGuidesInfo' => 'Info sur les guides de la grille', + 'GridID' => 'Identifiant de la grille', + 'GridPeriod' => 'Période de quadrillage', + 'GridPitch' => 'Pas de la grille', + 'GridResolution' => 'Résolution de la grille', + 'GridSize' => 'Taille de la grille', + 'GridSpacingMaterial' => 'Matériau d\'espacement de la grille', + 'GridThickness' => 'Épaisseur de la grille', + 'GripBatteryADLoad' => 'Tension accu poignée en charge', + 'GripBatteryADNoLoad' => 'Tension accu poignée à vide', + 'GripBatteryState' => { + Description => 'État de la batterie de la poignée', + PrintConv => { + 'Almost Empty' => 'Presque vide', + 'Empty or Missing' => 'Vide ou absent', + 'Full' => 'Pleinz', + 'Running Low' => 'Niveau faible', + }, + }, + 'HCUsage' => 'Usage HC', + 'HDR' => { + Description => 'HDR auto', + PrintConv => { + 'Auto-align Off' => 'Alignement automatique désactivé', + 'Auto-align On' => 'Alignement automatique activé', + 'HDR Advanced' => 'HDR avancé', + 'HDR Auto' => 'HDR auto', + 'HDR image (fail 1)' => 'Image HDR (ratée 1)', + 'HDR image (fail 2)' => 'Image HDR (ratée 2)', + 'HDR image (good)' => 'Image HDR (bonne)', + 'Off' => 'Désactivée', + 'On' => 'Activée', + 'On (normal)' => 'Activée (normale)', + 'Uncorrected image' => 'Image incorrecte', + 'n/a' => 'Non applicable', + }, + }, + 'HDREffect' => { + Description => 'Effet HDR', + PrintConv => { + 'Art (bold)' => 'Art (gras)', + 'Art (embossed)' => 'Art (embossé)', + 'Art (vivid)' => 'Art (éclatant)', + 'Natural' => 'Naturel', + }, + }, + 'HDRImageType' => { + Description => 'Type de l\'image HDR', + PrintConv => { + 'HDR Image' => 'Image HDR', + 'Original Image' => 'Image orginale', + }, + }, + 'HDRInfoVersion' => 'Info version HDR', + 'HDRLevel' => { + Description => 'Niveau HDR', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'HDRLevel2' => { + Description => 'Niveau HDR 2', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'HDRSetting' => { + Description => 'Réglage HDR', + PrintConv => { + 'Off' => 'Désactivé', + 'On (Auto)' => 'Activé (Auto)', + 'On (Manual)' => 'Activé (Manuel)', + }, + }, + 'HDRShot' => { + Description => 'Prise de vue HDR', + PrintConv => { + 'Off' => 'Désactivée', + 'On' => 'Activée', + }, + }, + 'HDRSmoothing' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'HEVCConfigurationVersion' => 'Version de la configuration HEVC', + 'HalftoneHints' => 'Indications sur les demi-teintes', + 'HandlerType' => { + Description => 'Type de gestionnaire', + PrintConv => { + 'Alias Data' => 'Données alias', + 'Audio Track' => 'Piste audio', + 'Camera Metadata' => 'Métadonnées d\'appareil photo', + 'Clock Reference' => 'Référence d\'horloge', + 'Data' => 'Données', + 'Hint Track' => 'Piste d\'indice', + 'MPEG-7 Stream' => 'Flux MPEG-7', + 'Metadata' => 'Métadonnées', + 'Metadata Tags' => 'Tags de métadonnées', + 'Non-Real Time Metadata' => 'Métadonnées non Temps réel', + 'Object Content' => 'Contenu d\'objet', + 'Object Descriptor' => 'Descripteur d\'objet', + 'Panasonic Static Metadata' => 'Métadonnées statiques Panasonic', + 'Picture' => 'Image', + 'Private' => 'Privé', + 'Scene Description' => 'Description de scène', + 'Subpicture' => 'Sous-image', + 'Subtitle' => 'Sous-titre', + 'Text' => 'Texte', + 'Time Code' => 'Code temporel', + 'Video Track' => 'Piste vidéo', + }, + }, + 'Headline' => 'Titre principal', + 'HierarchicalSubject' => 'Sujet hiérarchique', + 'HighISONoiseReduction' => { + Description => 'Réduction du bruit en sensibilité ISO élevée', + PrintConv => { + 'Active (Medium)' => 'Active (Moyenne)', + 'Active (Strong)' => 'Active (Forte)', + 'Active (Weak)' => 'Active (Faible)', + 'Auto' => 'Auto.', + 'High' => 'Forte', + 'Low' => 'Faible', + 'Medium' => 'Moyenne', + 'Medium High' => 'Moyennement élevée', + 'Medium Low' => 'Moyennement faible', + 'Minimal' => 'Minimale', + 'Normal' => 'Normale', + 'Off' => 'Désactivée', + 'On' => 'Activée', + 'Strong' => 'Importante', + 'Weak' => 'Faible', + 'Weakest' => 'La plus faible', + }, + }, + 'HighISONoiseReduction2' => { + Description => 'Réduction du bruit en sensibilité ISO élevée 2', + PrintConv => { + 'High' => 'Forte', + 'Low' => 'Faible', + 'Normal' => 'Normale', + 'Off' => 'Désactivée', + 'n/a' => 'Non applicable', + }, + }, + 'Highlight' => 'Mise en lumière', + 'HighlightTone' => { + Description => 'Tonalité lumineuse', + PrintConv => { + '+1 (medium hard)' => '+1 (moyennement dure)', + '+2 (hard)' => '+2 (dure)', + '+3 (very hard)' => '+3 (très dure)', + '+4 (hardest)' => '+4 (la plus dure)', + '-1 (medium soft)' => '-1 (moyennement douce)', + '-2 (soft)' => '-2 (douce)', + '0 (normal)' => '0 (normale)', + }, + }, + 'HighlightTonePriority' => { + Description => 'Priorité Tonalités lumineuses', + PrintConv => { + 'Disable' => 'Désactivée', + 'Enable' => 'Activée', + 'Off' => 'Désactivée', + 'On' => 'Activée', + }, + }, + 'HighlightWarning' => { + Description => 'Avertissement de mise en lumière', + PrintConv => { + 'Disabled' => 'Désactivé', + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'Highlights' => 'Lumières', + 'History' => 'Récapitulatif', + 'HometownCity' => { + Description => 'Ville de résidence', + PrintConv => { + 'Adelaide' => 'Adélaïde', + 'Algiers' => 'Alger', + 'Athens' => 'Athène', + 'Beijing' => 'Pékin', + 'Cairo' => 'Le Caire', + 'Caracus' => 'Caracas', + 'Jerusalem' => 'Jérusalem', + 'Kabul' => 'Kaboul', + 'Kathmandu' => 'Kathmandou', + 'Lisbon' => 'Lisbonne', + 'London' => 'Londre', + 'Male' => 'Malé', + 'Manila' => 'Manille', + 'Moscow' => 'Moscou', + 'Noumea' => 'Nouméa', + 'Seoul' => 'Séoul', + 'Singapore' => 'Singapour', + 'Tehran' => 'Téhéran', + 'Warsaw' => 'Varsovie', + 'Yangon' => 'Rangoun', + }, + }, + 'HometownCityCode' => 'Code ville de résidence', + 'HometownDST' => { + Description => 'Heure d\'été de résidence', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'HostComputer' => 'Ordinateur hôte', + 'Hue' => 'Nuance', + 'HueAdjustment' => 'Teinte', + 'HyperfocalDistance' => 'Distance hyperfocale', + 'ICCProfile' => 'Profil ICC', + 'ICCProfileName' => 'Nom du profil ICC', + 'ICC_Profile' => 'Profil de couleur ICC d\'entrée', + 'ID3Size' => 'Taille ID3', + 'IDCCreativeStyle' => { + Description => 'Style créatif IDC', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'Autumn Leaves' => 'Feuilles d\'automne', + 'B&W' => 'N&B', + 'Camera Setting' => 'Réglage de l\'appareil photo', + 'Clear' => 'Clair', + 'Deep' => 'Profond', + 'Landscape' => 'Paysage', + 'Light' => 'Lumière', + 'Neutral' => 'Neutre', + 'Night View' => 'Vue nocturne', + 'Real' => 'Réel', + 'Sepia' => 'Sépia', + 'Sunset' => 'Coucher de soleil', + 'Vivid' => 'Éclatant', + }, + }, + 'IPTC-NAA' => 'Métadonnées IPTC-NAA', + 'IPTCBitsPerSample' => 'Nombre de bits par échantillon IPTC', + 'IPTCDigest' => 'Résumé IPTC', + 'IPTCImageHeight' => 'Hauteur de l\'image IPTC', + 'IPTCImageRotation' => { + Description => 'Rotation de l\'image IPTC', + PrintConv => { + '0' => 'Pas de rotation', + '180' => 'Rotation de 180 degrés', + '270' => 'Rotation de 270 degrés', + '90' => 'Rotation de 90 degrés', + }, + }, + 'IPTCImageWidth' => 'Largeur de l\'image IPTC', + 'IPTCLastEdited' => 'Dernière modification IPTC', + 'IPTCPictureNumber' => 'Numéro d\'image IPTC', + 'IPTCPixelHeight' => 'Hauteur du pixel IPTC', + 'IPTCPixelWidth' => 'Largeur du pixel IPTC', + 'ISO' => 'Sensibilité ISO', + 'ISO2' => 'ISO 2', + 'ISO3166CountryCode' => 'Code pays ISO 3166', + 'ISO639-1LanguageCode' => 'Code langue ISO 639-1', + 'ISO639CaptionsLanguageCode' => 'Code langue des sous-titres ISO 639', + 'ISO639TextLanguageCode' => 'Code langue du texte ISO 639', + 'ISOAuto' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ISOAutoMax' => 'ISO Auto maximum', + 'ISOAutoMin' => 'ISO Auto minimum', + 'ISOAutoOffset' => 'Décalage automatique de l\'ISO', + 'ISOAutoParameters' => { + Description => 'Paramètres de l\'ISO Auto', + PrintConv => { + 'Fast' => 'Rapide', + 'Slow' => 'Lent', + }, + }, + 'ISOExpansion' => { + Description => 'Extension sensibilité ISO', + PrintConv => { + 'Off' => 'Arrêt', + 'On' => 'Marche', + }, + }, + 'ISOExpansion2' => { + PrintConv => { + 'Off' => 'Désactivé', + }, + }, + 'ISOFloor' => 'Seuil ISO', + 'ISOInfo' => 'Info ISO', + 'ISOSelection' => 'Choix ISO', + 'ISOSetting' => { + Description => 'Réglage ISO', + PrintConv => { + 'Manual' => 'Manuelle', + 'n/a' => 'Non applicable', + }, + }, + 'ISOSpeedExpansion' => { + Description => 'Extension de sensibilité ISO', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'ISOSpeedIncrements' => { + Description => 'Incréments de sensibilité ISO', + PrintConv => { + '1/3 Stop' => 'Palier 1/3', + }, + }, + 'ISOSpeedRange' => { + Description => 'Régler l\'extension de sensibilité ISO', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activée', + }, + }, + 'IT8Header' => 'En-tête IT8', + 'IcingDetected' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + 'n/a' => 'Non applicable', + }, + }, + 'Identifier' => 'Identifiant', + 'Illumination' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'Image::ExifTool::Apple::Main' => 'Image::ExifTool::Apple::Principal', + 'Image::ExifTool::Apple::RunTime' => 'Image::ExifTool::Apple::Temps de fonctionnement', + 'Image::ExifTool::Canon::uuid' => 'Image::ExifTool::Canon::uuid', + 'Image::ExifTool::FLIR::GPS_UUID' => 'Image::ExifTool::FLIR::UUID du GPS', + 'ImageAdjustment' => 'Ajustement Image', + 'ImageAreaOffset' => 'Décalage de zone d\'image', + 'ImageAuthentication' => { + Description => 'Authentication de l\'image', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ImageBoardID' => 'Identifiant de la carte d\'images', + 'ImageBoundary' => 'Cadre Image', + 'ImageColor' => 'Couleur de l\'image', + 'ImageColorIndicator' => 'Indicateur de couleur de l\'image', + 'ImageColorValue' => 'Valeur de couleur de l\'image', + 'ImageCount' => 'Compteur d\'images', + 'ImageDataSize' => 'Taille de l\'image', + 'ImageDepth' => 'Profondeur de l\'image', + 'ImageDescription' => 'Description de l\'image', + 'ImageDustOff' => { + Description => 'Dépoussiérage de l\'image', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ImageEditCount' => 'Compteur de traitement de l\'image', + 'ImageEditing' => { + Description => 'Traitement de l\'image', + PrintConv => { + 'Cropped' => 'Recadré', + 'Digital Filter' => 'Filtre numérique', + 'Frame Synthesis?' => 'Synthèse de vue ?', + 'None' => 'Aucun', + }, + }, + 'ImageEffects' => { + PrintConv => { + 'Cross Process' => 'Traitement croisé', + 'High Key' => 'Tons clairs', + 'Vivid' => 'Éclatant', + }, + }, + 'ImageFileSizeAsDelivered' => { + Description => 'Taille du fichier image généré', + PrintConv => { + 'Greater than 50 MB' => 'Supérieure à 50 Mo', + 'Up to 1 MB' => 'Jusqu\'à 1 Mo', + 'Up to 10 MB' => 'Jusqu\'à 10 Mo', + 'Up to 30 MB' => 'Jusqu\'à 30 Mo', + 'Up to 50 MB' => 'Jusqu\'à 50 Mo', + }, + }, + 'ImageGeneration' => { + Description => 'Génération de l\'image', + PrintConv => { + 'Original Image' => 'Image orginale', + 'Re-developed from RAW' => 'Redéveloppée à partir du RAW', + }, + }, + 'ImageHeight' => 'Hauteur de l\'image', + 'ImageHistory' => 'Historique de l\'image', + 'ImageID' => 'ID de l\'image', + 'ImageLayer' => 'Couche image', + 'ImageNumber' => 'Numéro de l\'image', + 'ImageNumber2' => 'Numéro de l\'image 2', + 'ImageOptimization' => 'Optimisation de l\'image', + 'ImageOrientation' => { + Description => 'Orientation de l\'image', + PrintConv => { + 'Landscape' => 'Paysage', + 'Square' => 'Carré', + }, + }, + 'ImagePixelDepth' => 'Profondeur en pixels de l\'image', + 'ImagePixelFormat' => { + Description => 'Format des pixels de l\'image', + PrintConv => { + '2-byte short integer' => 'Nombre entier court de 2 octets', + '4-byte float' => 'Nombre flottant de 4 octets', + '4-byte long integer' => 'Nombre entier long de 4 octets', + '8-byte double' => 'Nombre entier double de 8 octets', + }, + }, + 'ImageProcessing' => 'Retouche de l\'image', + 'ImageProcessingVersion' => 'Version du traitement d\'image', + 'ImageQuality' => { + Description => 'Qualité de l\'image', + PrintConv => { + '4k Movie' => 'Film 4k', + 'Full HD Movie' => 'Vidéo Full HD', + 'High' => 'Haute', + 'JPEG Basic' => 'JPEG Basique', + 'JPEG Fine' => 'JPEG Fin', + 'Motion Picture' => 'Film d\'animation', + 'NEF (RAW) + JPEG Basic' => 'NEF (RAW) + JPEG basique', + 'NEF (RAW) + JPEG Fine' => 'NEF (RAW) + JPEG Fin', + 'NEF (RAW) + JPEG Norm' => 'NEF (RAW) + JPEG Normal', + 'Normal' => 'Normale', + 'Snap Shot' => 'Instantané', + 'TIF (RGB)' => 'TIF (RVB)', + 'Very High' => 'Très haute', + }, + }, + 'ImageQuality2' => 'Qualité de l\'image 2', + 'ImageReview' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ImageRotated' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'ImageSize' => 'Taille de l\'image', + 'ImageSourceData' => 'Données source de l\'image', + 'ImageSpatialExtent' => 'Étendue spatiale de l\'image', + 'ImageStabilization' => { + Description => 'Stabilisation de l\'image', + PrintConv => { + 'Off' => 'Désactivée', + 'On' => 'Activée', + 'On (0x3f)' => 'Activée (0x3f)', + 'On (2)' => 'Activée (2)', + 'On (mode 1, continuous)' => 'Activée (mode 1, continu)', + 'On (mode 2, shooting only)' => 'Activée (mode 2, prise de vue uniquement)', + 'On, Body-only' => 'Activée, Corps seul', + 'On, Body-only Panning' => 'Activée, Panoramique du boitier seulement', + 'On, Mode 1' => 'Activée, Mode 1', + 'On, Mode 2' => 'Activée, Mode 2', + 'On, Mode 3' => 'Activée, Mode 3', + 'On, Mode 4' => 'Activée, Mode 4', + 'On, Optical' => 'Activée, Optique', + 'On, Optical Panning' => 'Activée, Panoramique optique', + 'Optical' => 'Optique', + 'Panning' => 'Panoramique', + 'Panning (2)' => 'Panoramique (2)', + 'Sensor-shift' => 'Déplacement du capteur', + 'Shoot Only' => 'Prise de vue uniquement', + 'Shoot Only (2)' => 'Prise de vue uniquement (2)', + 'Slow Shutter' => 'Obturateur à vitesse lente', + 'n/a' => 'Non applicable', + }, + }, + 'ImageStyle' => { + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'B&W' => 'N&B', + 'Landscape' => 'Paysage', + 'Neutral' => 'Neutre', + 'Night View/Portrait' => 'Vue/Portrait nocturne', + 'Sunset' => 'Coucher de soleil', + 'Vivid' => 'Éclatant', + }, + }, + 'ImageTone' => { + Description => 'Tonalité de l\'image', + PrintConv => { + 'Bright' => 'Brillant', + 'Cross Processing' => 'Traitement croisé', + 'Landscape' => 'Paysage', + 'Natural' => 'Naturel', + }, + }, + 'ImageType' => 'Type de l\'image', + 'ImageUniqueID' => 'Identificateur unique de l\'image', + 'ImageWidth' => 'Largeur de l\'image', + 'Indexed' => 'Indexé', + 'InfoButtonWhenShooting' => { + Description => 'Touche INFO au déclenchement', + PrintConv => { + 'Displays camera settings' => 'Affiche les réglages en cours', + 'Displays shooting functions' => 'Affiche les fonctions', + }, + }, + 'InkNames' => 'Nom des encres', + 'InkSet' => 'Encrage', + 'IntegrationTime' => 'Temps d\'intégration', + 'IntellectualGenre' => 'Genre intellectuel', + 'IntelligentAuto' => { + Description => 'Mode Auto intelligent', + PrintConv => { + 'Advanced' => 'Avancé', + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'IntelligentContrast' => { + Description => 'Contraste intelligent', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'n/a' => 'Non applicable', + }, + }, + 'IntelligentExposure' => { + Description => 'Exposition intelligente', + PrintConv => { + 'High' => 'Haute', + 'Low' => 'Basse', + 'Off' => 'Désactivée', + }, + }, + 'IntelligentResolution' => { + Description => 'Résolution intelligente', + PrintConv => { + 'Extended' => 'Étendue', + 'High' => 'Haute', + 'Low' => 'Basse', + 'Off' => 'Désactivée', + }, + }, + 'IntensityStereo' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'InterchangeColorSpace' => { + PrintConv => { + 'CMY (K) Device Dependent' => 'CMY(K) dépendant de l\'appareil', + 'RGB Device Dependent' => 'RVB dépendant de l\'appareil', + }, + }, + 'IntergraphMatrix' => 'Matrice intergraphe', + 'Interlace' => 'Entrelacement', + 'InternalFlash' => { + Description => 'Zoom du flash interne', + PrintConv => { + 'Commander Mode' => 'Mode Maître', + 'Fired' => 'Flash déclenché', + 'Manual' => 'Manuelle', + 'No' => 'Flash non déclenché', + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'InternalFlashMode' => { + Description => 'Segment de mesure flash esclave 2', + PrintConv => { + 'Did not fire, (Unknown 0xf4)' => 'Hors service (inconnue 0xF4)', + 'Did not fire, Auto' => 'Hors service, auto', + 'Did not fire, Auto, Red-eye reduction' => 'Hors service, auto, réduction yeux rouges', + 'Did not fire, Normal' => 'Hors service, normal', + 'Did not fire, Red-eye reduction' => 'Hors service, réduction yeux rouges', + 'Did not fire, Slow-sync' => 'Hors service, synchro lente', + 'Did not fire, Slow-sync, Red-eye reduction' => 'Hors service, synchro lente, réduction yeux rouges', + 'Did not fire, Trailing-curtain Sync' => 'Hors service, synchro 2e rideau', + 'Did not fire, Wireless (Control)' => 'Hors service, sans cordon (contrôleur)', + 'Did not fire, Wireless (Master)' => 'Hors service, sans cordon (maître)', + 'Fired' => 'Activé', + 'Fired, Auto' => 'En service, auto', + 'Fired, Auto, Red-eye reduction' => 'En service, auto, réduction yeux rouges', + 'Fired, Red-eye reduction' => 'En service, réduction yeux rouges', + 'Fired, Slow-sync' => 'En service, synchro lente', + 'Fired, Slow-sync, Red-eye reduction' => 'En service, synchro lente, réduction yeux rouges', + 'Fired, Trailing-curtain Sync' => 'En service, synchro 2e rideau', + 'Fired, Wireless (Control)' => 'En service, sans cordon (contrôleur)', + 'Fired, Wireless (Master)' => 'En service, sans cordon (maître)', + 'n/a - Off-Auto-Aperture' => 'Non applicable - Auto-diaph hors service', + }, + }, + 'InternalFlashStrength' => 'Segment de mesure flash esclave 4', + 'InternalIDNumber' => 'Numéro d\'identification interne', + 'InternalLensSerialNumber' => 'Numéro de série de l\'objectif interne', + 'InternalNDFilter' => 'Filtre ND interne', + 'InternalName' => 'Nom interne', + 'InternalSerialNumber' => 'Numéro de série interne', + 'InternalVersionNumber' => 'Numéro de version interne', + 'InteropIndex' => { + Description => 'Identification d\'interopérabilité', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03: fichier d\'option DCF (Adobe RVB)', + 'R98 - DCF basic file (sRGB)' => 'R98: fichier de base DCF (sRVB)', + 'THM - DCF thumbnail file' => 'THM: fichier de vignette DCF', + }, + }, + 'InteropOffset' => 'Indicateur d\'interfonctionnement', + 'InteropVersion' => 'Version d\'interopérabilité', + 'IptcLastEdited' => 'Dernière édition IPTC', + 'JFIFVersion' => 'Version JFIF', + 'JPEG-HEIFSwitch' => { + Description => 'Sélecteur JPEG-HEIF', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'JPEGACTables' => 'Tableaux AC JPEG', + 'JPEGDCTables' => 'Tableaux DC JPEG', + 'JPEGLosslessPredictors' => 'Prédicteurs JPEG sans perte', + 'JPEGPointTransforms' => 'Transformations de point JPEG', + 'JPEGProc' => 'Proc JPEG', + 'JPEGQTables' => 'Tableaux Q JPEG', + 'JPEGQuality' => { + Description => 'Qualité', + PrintConv => { + 'Basic' => 'Basique', + 'Extra Fine' => 'Extra fine', + 'High' => 'Haute', + 'Standard' => 'Normale', + 'Very High' => 'Trés haute', + 'n/a' => 'Non applicable', + 'n/a (Movie)' => 'Non applicable (vidéo)', + 'n/a (RAW only)' => 'Non applicable (RAW seulement)', + }, + }, + 'JPEGRestartInterval' => 'Intervalle de redémarrage JPEG', + 'JPEGTables' => 'Tableaux JPEG', + 'JobID' => 'ID de la tâche', + 'JpgFromRaw' => 'Jpeg à partir des données RAW', + 'JpgFromRawLength' => 'Longueur du Jpeg à partir des données RAW', + 'JpgFromRawStart' => 'Début du Jpeg à partir des données RAW', + 'JpgRecordedPixels' => { + Description => 'Pixels enregistrés JPEG', + PrintConv => { + '10 MP' => '10 Mpx', + '2 MP' => '2 Mpx', + '6 MP' => '6 Mpx', + }, + }, + 'Keyword' => 'Mot-clé', + 'Keywords' => 'Mots-clés', + 'LC1' => 'Données d\'objectif', + 'LC10' => 'Données mv\' nv\'', + 'LC11' => 'Données AVC 1/EXP', + 'LC12' => 'Données mv1 Avminsif', + 'LC14' => 'Données UNT_12 UNT_6', + 'LC15' => 'Données d\'adaptation de flash incorporé', + 'LC2' => 'Code de distance', + 'LC3' => 'Valeur K', + 'LC4' => 'Données de correction d\'aberration à courte distance', + 'LC5' => 'Données de correction d\'aberration chromatique', + 'LC6' => 'Données d\'aberration d\'ouverture', + 'LC7' => 'Données de condition minimale de déclenchement AF', + 'LCDDisplayAtPowerOn' => { + Description => 'État LCD lors de l\'allumage', + PrintConv => { + 'Display' => 'Allumé', + 'Retain power off status' => 'État précédent', + }, + }, + 'LCDDisplayReturnToShoot' => { + Description => 'Affich. LCD -> Prise de vues', + PrintConv => { + 'Also with * etc.' => 'Aussi par * etc.', + 'With Shutter Button only' => 'Par déclencheur uniq.', + }, + }, + 'LCDIllumination' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'LCDIlluminationDuringBulb' => { + Description => 'Éclairage LCD pendant pose longue', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'LCDPanels' => { + Description => 'Ecran LCD supérieur/arrière', + PrintConv => { + 'ISO/File no.' => 'ISO/No. fichier', + 'ISO/Remain. shots' => 'ISO/Vues restantes', + 'Remain. shots/File no.' => 'Vues restantes/No. fichier', + 'Shots in folder/Remain. shots' => 'Vues dans dossier/Vues restantes', + }, + }, + 'LCHEditor' => { + Description => 'Éditeur LCH', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'LameQuality' => 'Qualité Lame', + 'LameStereoMode' => { + Description => 'Mode stéréo Lame', + PrintConv => { + 'Dual Channels' => 'Doubles canaux', + 'Forced Joint Stereo' => 'Stéréo jointe forcée', + 'Intensity Stereo' => 'Stéréo Intensité', + 'Joint Stereo' => 'Stéréo jointe', + 'Stereo' => 'Stéréo', + }, + }, + 'Landmark' => 'Point de repère', + 'LandmarkCounter' => 'Compteur de points de repères', + 'Language' => 'Langage', + 'LanguageCode' => { + Description => 'Code langue', + PrintConv => { + 'Albanian' => 'Albanais', + 'Arabic' => 'Arabe', + 'Azeri' => 'Azéri', + 'Belarusian' => 'Belarusse', + 'Bulgarian' => 'Bulgare', + 'Byelorussian' => 'Biélorusse', + 'Chinese (Simplified)' => 'Chinois (Simplifié)', + 'Chinese (Traditional)' => 'Chinois (Traditionnel)', + 'Cornish' => 'Cornique', + 'Croato-Serbian (Latin)' => 'Serbo-Croate', + 'Czech' => 'Tchèque', + 'Danish' => 'Danois', + 'Dutch' => 'Flamand', + 'Dutch (Belgian)' => 'Flamand (Belge)', + 'English (Australian)' => 'Anglais (Australien)', + 'English (British)' => 'Anglais (Britanique)', + 'English (Canadian)' => 'Anglais (Canadien)', + 'English (U.S.)' => 'Anglais (U.S.)', + 'English (US)' => 'Anglais (US)', + 'Estonian' => 'Estonien', + 'Finnish' => 'Finnois', + 'French' => 'Français', + 'French (Belgian)' => 'Français (Belge)', + 'French (Canadian)' => 'Français (Canadien)', + 'French (Swiss)' => 'Français (Suisse)', + 'Gaelic' => 'Gaëlic', + 'Georgian' => 'Georgien', + 'German' => 'Allemand', + 'German (Austrian)' => 'Allemand (Autralien)', + 'German (Swiss)' => 'Allemand (Suisse)', + 'Greek' => 'Grecque', + 'Hebrew' => 'Hébreux', + 'Hungarian' => 'Hongrois', + 'Icelandic' => 'Islandais', + 'Indonesian' => 'Indonésien', + 'Italian' => 'Italien', + 'Italian (Swiss)' => 'Italian (Suisse)', + 'Japanese' => 'Japonnais', + 'Korean' => 'Coréen', + 'Latvian' => 'Letton', + 'Lithuanian' => 'Lithuanien', + 'Macedonian' => 'Macedonien', + 'Malay' => 'Malais', + 'Malaysian' => 'Malaysien', + 'Maltese' => 'Maltais', + 'Mongolian' => 'Mongolien', + 'Nepali' => 'Népalais', + 'Neutral' => 'Neutre', + 'Neutral 2' => 'Neutre 2', + 'None' => 'Aucune', + 'Norwegian (Bokmal)' => 'Norvégien (Bokmal)', + 'Norwegian (Bokml)' => 'Norvégien (Bokml)', + 'Norwegian (Nynorsk)' => 'Norvégien (Nynorsk)', + 'Oriya' => 'Odia', + 'Polish' => 'Polonais', + 'Portuguese' => 'Portugais', + 'Portuguese (Brazilian)' => 'Portugais (Brésilen)', + 'Process default' => 'Traitement par défaut', + 'Punjabi' => 'Penjabi', + 'Rhaeto-Romanic' => 'Rhaeto-Romain', + 'Romanian' => 'Roumain', + 'Russian' => 'Russe', + 'Serbo-Croatian (Cyrillic)' => 'Serbo-Croate (Cyrillique)', + 'Simplified Chinese' => 'Chinois simplifié', + 'Slovak' => 'Slovaque', + 'Slovenian' => 'Slovène', + 'Spanish (Castilian)' => 'Espagnol (Castillan)', + 'Spanish (Mexican)' => 'Espagnol (Mexicain)', + 'Spanish (Modern)' => 'Espagnol (Moderne)', + 'Swedish' => 'Suédois', + 'Tamil' => 'Tamoul', + 'Traditional Chinese' => 'Chinois traditionnel', + 'Turkish' => 'Turque', + 'Ukrainian' => 'Ukrainien', + 'Urdu' => 'Ourdou', + 'Uzbek' => 'Ouzbek', + 'Vietnamese' => 'Vietnamien', + 'Walon' => 'Wallon', + 'Welsh' => 'Gallois', + 'Zulu' => 'Zoulou', + }, + }, + 'LanguageIdentifier' => 'Identificateur de langue', + 'LastKeywordIPTC' => 'Dernier mot-clé IPTC', + 'LastKeywordXMP' => 'Dernier mot-clé XMP', + 'LateralChromaticAberration' => { + Description => 'Aberration chromatique latérale', + PrintConv => { + 'Off' => 'Désactivée', + 'n/a' => 'Non applicable', + }, + }, + 'LeafData' => 'Données Leaf', + 'Lens' => 'Objectif ', + 'Lens35efl' => 'Objectif 35 efl', + 'LensAFStopButton' => { + Description => 'Bouton d\'arrêt de l\'autofocus de l\'objectif', + PrintConv => { + 'AE lock' => 'Verrouillage de l\'exposition automatique', + 'AE lock while metering' => 'Verrouillage de l\'exposition automatique pendant le mesurage', + 'AF Stop' => 'Arrêt de l\'autofocus', + 'AF mode: ONE SHOT <-> AI SERVO' => 'Mode autofocus: UNE PRISE DE VUE <-> IA SERVO', + 'AF point: M -> Auto / Auto -> Ctr.' => 'Points autofocus: M -> Auto / Auto -> Ctr.', + 'AF point: M->Auto/Auto->ctr' => 'Points autofocus: M->Auto/Auto->ctr', + 'AF start' => 'Activation autofocus', + 'AF stop' => 'Arrêt de l\'autofocus', + 'IS start' => 'Activation stabilisation image', + 'Lock AE and start timer' => 'Verrouillage de l\'autofocus et démarrage du minuteur', + 'ONE SHOT <-> AI SERVO' => 'UNE PRISE DE VUE <-> IA SERVO', + 'One Shot <-> AI servo' => 'Une prise de vue <-> IA Servo', + 'Operate AF' => 'Exploitation de l\'autofocus', + 'Spot AF' => 'Spot de l\'autofocus', + 'Switch to registered AF point' => 'Passage au point d\'autofocus enregistré', + }, + }, + 'LensApertureRange' => 'Plage d\'ouverture de l\'objectif', + 'LensAttached' => { + Description => 'Objectif fixé', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'LensData' => 'Valeur K (LC3)', + 'LensDataVersion' => 'Version des Données Objectif', + 'LensDistortionParams' => 'Paramètres de distorsion de l\'objectif', + 'LensDriveNoAF' => { + Description => 'Pilot. obj. si AF impossible', + PrintConv => { + 'Focus search off' => 'Pas de recherche du point', + 'Focus search on' => 'Recherche du point', + }, + }, + 'LensE-mountVersion' => 'Version de l\'objectif à monture E', + 'LensFStops' => 'Nombre de diaphs de l\'objectif', + 'LensFirmwareVersion' => 'Version du micrologiciel de l\'objectif', + 'LensFocalLength' => 'Longueur de la focale de l\'objectif', + 'LensFocalRange' => 'Plage de focale de l\'objectif', + 'LensFocusFunctionButtons' => { + Description => 'Boutons de mise au point de l\'objectif', + PrintConv => { + 'AE Lock Only' => 'Verrouillage Exposition Auto uniquement', + 'AE/AF Lock' => 'Verrouillage AF/Exposition Auto', + 'AF Lock Only' => 'Verrouillage AF uniquement', + 'AF-Area Mode (Auto Area AF)' => 'AF-Mode zone (AF Zone auto)', + 'AF-Area Mode (Dynamic Area 152 Points)' => 'AF-Mode zone Zone-dynamique (152 points)', + 'AF-Area Mode (Dynamic Area 153 Points)' => 'AF-Mode zone Zone-dynamique (153 points)', + 'AF-Area Mode (Dynamic Area 25 Points)' => 'AF-Mode zone Zone-dynamique (25 points)', + 'AF-Area Mode (Dynamic Area 72 Points)' => 'AF-Mode zone Zone-dynamique (72 points)', + 'AF-Area Mode (Dynamic Area 9 Points)' => 'AF-Mode zone Zone-dynamique (9 points)', + 'AF-Area Mode (Group Area AF)' => 'AF-Mode zone (AF Zone-groupe)', + 'AF-Area Mode (Single)' => 'AF-Mode zone (Point unique)', + 'AF-Area Mode + AF-On (Auto Area AF)' => 'AF-Mode zone: AF Activé (AF Zone auto)', + 'AF-Area Mode + AF-On (Dynamic Area 152 Points)' => 'AF-Mode zone: AF Activé (AF Zone-dynamique (152 points)', + 'AF-Area Mode + AF-On (Dynamic Area 153 Points)' => 'AF-Mode zone: AF Activé (AF Zone-dynamique (153 points)', + 'AF-Area Mode + AF-On (Dynamic Area 25 Points)' => 'AF-Mode zone: AF Activé (AF Zone-dynamique (25 points)', + 'AF-Area Mode + AF-On (Dynamic Area 72 Points)' => 'AF-Mode zone: AF Activé (AF Zone-dynamique (72 points)', + 'AF-Area Mode + AF-On (Dynamic Area 9 Points)' => 'AF-Mode zone: AF Activé (AF Zone-dynamique (9 points)', + 'AF-Area Mode + AF-On (Group Area AF)' => 'AF-Mode zone: AF Activé (AF Zone-groupe)', + 'AF-Area Mode + AF-On (Single)' => 'AF-Mode zone: AF Activé (Unique)', + 'AF-Area Mode: Single-point AF' => 'AF-Mode zone: AF Point unique', + 'AF-Area Mode: Auto area AF' => 'AF-Mode zone: AF Zone auto', + 'AF-Area Mode: Dynamic-area AF (21 points)' => 'AF-Mode zone: AF Zone-dynamique (21 points)', + 'AF-Area Mode: Dynamic-area AF (51 points)' => 'AF-Mode zone: AF Zone-dynamique (51 points)', + 'AF-Area Mode: Dynamic-area AF (9 points)' => 'AF-Mode zone: AF Zone-dynamique (9 points)', + 'AF-Area Mode: Group-area AF' => 'AF-Mode zone: AF Zone-groupe', + 'AF-On' => 'AF-Activé', + 'Disable Synchronized Release' => 'Désactiver la synchro de déclenchement', + 'Flash Disable/Enable' => 'Désactivation/activation du flash', + 'Preset Focus Point' => 'Point de mise au point prédéfini', + 'Preset focus Point' => 'Point de mise au point prédéfini', + 'Remote Release Only' => 'Déclenchement en télécommande seulement', + 'Sync Release (Master Only)' => 'Synchro déclenchement (Maître seulement)', + 'Sync Release (Remote Only)' => 'Synchro déclenchement (Télécommande seulement)', + }, + }, + 'LensFormat' => { + Description => 'Format de l\'objectif', + PrintConv => { + 'Full-frame' => 'Plein cadre', + 'Unknown' => 'Inconnu', + }, + }, + 'LensFunc1Button' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'LensFunc2Button' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'LensID' => 'Identifiant de l\'Objectif', + 'LensIDNumber' => 'Numéro de l\'objectif', + 'LensInfo' => 'Informations sur l\'objectif', + 'LensKind' => 'Type d\'objectif / version (LC0)', + 'LensMake' => 'Marque de l\'objectif', + 'LensMaker' => 'Fabricant de l\'objectif', + 'LensManualDistortionAmount' => 'Taux de distorsion manuel de l\'objectif', + 'LensManufacturer' => 'Fabricant de l\'objectif', + 'LensMaxApertureRange' => 'Plage d\'ouverture maximale de l\'objectif', + 'LensModel' => 'Modèle de l\'objectif', + 'LensModulationOptimizer' => { + Description => 'Optimiseur de modulation de l\'objectif', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'LensMount' => { + Description => 'Monture de l\'objectif', + PrintConv => { + 'A-mount' => 'Monture A', + 'A-mount (3)' => 'Monture A (3)', + 'E-mount' => 'Monture E', + 'Unknown' => 'Inconnue', + }, + }, + 'LensMount2' => { + Description => 'Monture de l\'objectif 2', + PrintConv => { + 'A-mount (1)' => 'Monture A (1)', + 'A-mount (5)' => 'Monture A (5)', + 'E-mount' => 'Monture E', + 'Unknown' => 'Inconnue', + }, + }, + 'LensMountType' => { + Description => 'Type de monture de l\'objectif', + PrintConv => { + 'F-mount Lens' => 'Objectif à monture F', + 'Z-mount Lens' => 'Objectif à monture Z', + }, + }, + 'LensNumber' => 'Numéro de l\'objectif', + 'LensPartNumber' => 'Numéro de pièce de l\'objectif', + 'LensPositionAbsolute' => 'Position absolue de l\'objectif', + 'LensProfileChromaticAberrationScale' => 'Échelle d\'aberration chromatique du profil de l\'objectif', + 'LensProfileDigest' => 'Résumé du profil de l\'objectif', + 'LensProfileDistortionScale' => 'Échelle de distorsion du profil de l\'objectif', + 'LensProfileEnable' => 'Activation du profil de l\'objectif', + 'LensProfileFilename' => 'Nom du fichier du profil de l\'objectif', + 'LensProfileIsEmbedded' => 'Le profil de l\'objectif est intégré', + 'LensProfileName' => 'Nom du profil de l\'objectif', + 'LensProfileSetup' => 'Réglage du profil de l\'objectif', + 'LensProfileVignettingScale' => 'Échelle de vignettage du profil de l\'objectif', + 'LensProperties' => 'Propriétés de l\'objectif', + 'LensSegmentType' => 'Type de segment de l\'objectif', + 'LensSerialNumber' => 'Numéro de série de l\'objectif', + 'LensShading' => 'Teinte de l\'objectif', + 'LensShutterLock' => { + Description => 'Verrouillage de l\'obturateur de l\'objectif', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'LensSpec' => 'Spécification de l\'objectif', + 'LensSpecFeatures' => 'Caractéristiques de l\'objectif', + 'LensType' => { + Description => 'Type d\'objectif', + PrintConv => { + 'n/a' => 'Non applicable', + 'smc PENTAX-F 100-300mm F4.5-5.6 or Sigma Lens' => 'smc PENTAX-F 100-300mm F4.5-5.6 ou objectif Sigma', + 'smc PENTAX-F 28-80mm F3.5-4.5 or Tokina Lens' => 'smc PENTAX-F 28-80mm F3.5-4.5 ou objectif Tokina', + 'smc PENTAX-F 35-105mm F4-5.6 or Sigma or Tokina Lens' => 'smc PENTAX-F 35-105mm F4-5.6 ou objectif Sigma ou Tokina', + 'smc PENTAX-F 70-210mm F4-5.6 or Tokina or Takumar Lens' => 'smc PENTAX-F 70-210mm F4-5.6 or Tokina ou objectif Takumar', + 'smc PENTAX-F Macro 50mm F2.8 or Sigma Lens' => 'smc PENTAX-F Macro 50mm F2.8 ou objectif Sigma', + 'smc PENTAX-FA 28-200mm F3.8-5.6 AL[IF] or Tamron Lens' => 'smc PENTAX-FA 28-200mm F3.8-5.6 AL[IF] ou objectif Tamron', + 'smc PENTAX-FA 31mm F1.8 AL Limited' => 'smc PENTAX-FA 31mm F1.8 Limité AL', + }, + }, + 'LensType2' => 'Type d\'objectif 2', + 'LensType3' => 'Type d\'objectif 3', + 'LensTypeMake' => 'Marque du type d\'objectif', + 'LensTypeModel' => 'Modèle du type d\'objectif', + 'LensZoomPosition' => 'Position du zoom de l\'objectif', + 'LevelOrientation' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'LicenseType' => { + PrintConv => { + 'Unknown' => 'Inconnu', + }, + }, + 'LightReading' => 'Lecture de la lumière', + 'LightSource' => { + Description => 'Source de lumière', + PrintConv => { + 'Cloudy' => 'Nuageux', + 'Cool White Fluorescent' => 'Fluorescente type soft', + 'Custom 1-4' => 'Personnalisé 1-4', + 'Day White Fluorescent' => 'Jour blanc Fluorescent', + 'Daylight' => 'Lumière du jour', + 'Daylight Fluorescent' => 'Lumière du jour Fluorescente', + 'Evening Sunlight' => 'Soleil du soir', + 'Fine Weather' => 'Beau temps', + 'Fluorescent' => 'Fluorescente', + 'ISO Studio Tungsten' => 'Tungstène studio ISO', + 'One Touch White Balance' => 'Balance des blancs par simple pression', + 'Other' => 'Autre', + 'Shade' => 'Ombre', + 'Standard Light A' => 'Lumière standard A', + 'Standard Light B' => 'Lumière standard B', + 'Standard Light C' => 'Lumière standard C', + 'Tungsten (Incandescent)' => 'Tungstène (lumière incandescente)', + 'Unknown' => 'Inconnue', + 'Warm White Fluorescent' => 'Fluorescent blanc chaud', + 'White Fluorescent' => 'Fluorescent blanc', + }, + }, + 'LightSourceSpecial' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'LightValue' => 'Luminosité', + 'Lightness' => 'Luminosité', + 'LinearResponseLimit' => 'Limite de réponse linéaire', + 'LinearizationTable' => 'Table de linéarisation', + 'Lit' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'LithostratigraphicTerms' => 'Termes lithostratigraphiques', + 'LivePhotoVideoIndex' => 'Index vidéo Live Photo', + 'LivePhotoVitalityScore' => 'Note de vitalité Live Photo', + 'LivePhotoVitalityScoringVersion' => 'Version du score de vitalité Live Photo', + 'LiveViewAFMethod' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'LiveViewAFSetting' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'LiveViewExposureSimulation' => { + Description => 'Simulation d\'exposition directe', + PrintConv => { + 'Disable (LCD auto adjust)' => 'Désactivée (réglage écran auto)', + 'Enable (simulates exposure)' => 'Activée (simulation exposition)', + }, + }, + 'LiveViewFocusMode' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'LiveViewMetering' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'LiveViewShooting' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'LocalizedCameraModel' => 'Nom localisé du modèle de l\'appareil', + 'Location' => 'Lieu', + 'LocationCreatedCity' => 'Localisation de la ville de création', + 'LocationCreatedCountryName' => 'Localisation du pays de création', + 'LocationCreatedProvinceState' => 'Localisation de la région/état de création', + 'LocationSets' => 'Jeux de localisation', + 'LocationShown' => 'Localisation indiquée', + 'LocationShownCity' => 'Localisation de la Ville indiquée', + 'LocationShownCountryName' => 'Localisation du nom du pays indiqué', + 'LocationShownProvinceState' => 'Localisation de la région/état indiqué', + 'LockMicrophoneButton' => { + Description => 'Fonction de touche microphone', + PrintConv => { + 'Protect (hold:record memo)' => 'Protéger (maintien: enregistrement sonore)', + 'Record memo (protect:disable)' => 'Enregistrement sonore (protéger: désactivée)', + }, + }, + 'LongExposureNoiseReduction' => { + Description => 'Réduction du bruit en exposition longue', + PrintConv => { + 'Off' => 'Désactivée', + 'Off (65535)' => 'Désactivée (65535)', + 'On' => 'Activée', + 'On (65535)' => 'Activée (65535)', + 'On (dark subtracted)' => 'Activée (obscurité soustraite)', + 'On (unused)' => 'Activée (non utilisée)', + 'n/a' => 'Non applicable', + }, + }, + 'LongExposureNoiseReduction2' => { + Description => 'Réduction du bruit en exposition longue 2', + PrintConv => { + 'Off' => 'Désactivée', + 'On' => 'Activée', + 'On (1D)' => 'Activée (1D)', + }, + }, + 'LookupTable' => 'Table de correspondance', + 'LoopStyle' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'LuminanceNoiseReduction' => { + PrintConv => { + 'Low' => 'Bas', + 'Off' => 'Désactivé', + }, + }, + 'MCUVersion' => 'Version MCU', + 'MIEVersion' => 'Version MIE', + 'MIMEType' => 'Type MIME', + 'MPEGVideoRecodingDataset' => 'Jeu de données de recodage vidéo MPEG', + 'MPFVersion' => 'Version MPF', + 'MPImage' => 'Image MP', + 'MPImageFlags' => { + Description => 'Indicateurs image MP', + PrintConv => { + 'Dependent child image' => 'Image fille dépendante', + 'Dependent parent image' => 'Image parent dépendante', + 'Representative image' => 'Image représentative', + }, + }, + 'MPImageFormat' => 'Format de l\'image MP', + 'MPImageLength' => 'Longueur de l\'image MP', + 'MPImageStart' => 'Début de l\'image MP', + 'MPImageType' => { + Description => 'Type de l\'image MP', + PrintConv => { + 'Baseline MP Primary Image' => 'Image primaire MP de référence', + 'Large Thumbnail (VGA equivalent)' => 'Grande vignette (équivalent VGA)', + 'Large Thumbnail (full HD equivalent)' => 'Grande vignette (équivalent à full HD)', + 'Multi-frame Disparity' => 'Disparité multi-image', + 'Multi-frame Panorama' => 'Panorama multi-image', + 'Undefined' => 'Indéfini', + }, + }, + 'MSStereo' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'Macro' => { + PrintConv => { + 'Close Focus' => 'Gros plan', + 'Manual' => 'Manuel', + 'Normal' => 'Normale', + 'Off' => 'Désactivée', + 'On' => 'Activée', + 'Super Macro' => 'Super macro', + 'View' => 'Vue', + 'n/a' => 'Non applicable', + }, + }, + 'MacroMode' => { + Description => 'Mode Macro', + PrintConv => { + 'Macro Zoom' => 'Zoom Macro', + 'Normal' => 'Normale', + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'Super Macro' => 'Super macro', + 'Tele-Macro' => 'Télé-Macro', + }, + }, + 'MagicFilter' => { + PrintConv => { + 'Cross Process' => 'Traitement croisé', + 'Cross Process II' => 'Traitement croisé II', + 'Partial Color' => 'Couleur partielle', + 'Partial Color II' => 'Couleur partielle II', + 'Partial Color III' => 'Couleur partielle III', + 'Soft Focus' => 'Flou artistique', + 'Soft Focus 2' => 'Flou artistique 2', + }, + }, + 'MagnifiedView' => { + Description => 'Agrandissement en lecture', + PrintConv => { + 'Image playback only' => 'Lecture image uniquement', + 'Image review and playback' => 'Aff. inst. et lecture', + }, + }, + 'MainDialExposureComp' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'MajorBrand' => { + Description => 'Label majeur', + PrintConv => { + '3GPP (.3GP) Release 6 MBMS Extended Presentations' => '3GPP (.3GP) Version 6 Présentations étendues de MBMS', + '3GPP (.3GP) Release 7 MBMS Extended Presentations' => '3GPP (.3GP) Version 7 Présentations étendues de MBMS', + '3GPP Media (.3GP) Release 1 (probably non-existent)' => '3GPP Media (.3GP) Version 1 (probablement inexistante)', + '3GPP Media (.3GP) Release 2 (probably non-existent)' => '3GPP Media (.3GP) Version 2 (probablement inexistante)', + '3GPP Media (.3GP) Release 3 (probably non-existent)' => '3GPP Media (.3GP) Version 3 (probablement inexistante)', + '3GPP Media (.3GP) Release 4' => '3GPP Media (.3GP) Version 4', + '3GPP Media (.3GP) Release 5' => '3GPP Media (.3GP) Version 5', + '3GPP Media (.3GP) Release 6 Streaming Servers' => '3GPP Media (.3GP) Version 6 Serveurs de streaming', + '3GPP Media (.3GP) Release 7 Streaming Servers' => '3GPP Media (.3GP) Version 7 Serveurs de streaming', + '3GPP Release 6 General Profile' => '3GPP Version 6 Profil général', + '3GPP2 EZmovie for KDDI 3G cellphones' => '3GPP2 EZmovie pour les téléphones portables 3G de KDDI', + '3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-0 V1.0' => '3GPP2 Media (.3G2)conforme à 3GPP2 C.S0050-0 V1.0', + '3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-A V1.0.0' => '3GPP2 Media (.3G2) conforme à 3GPP2 C.S0050-A V1.0.0', + '3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-B v1.0' => '3GPP2 Media (.3G2) conforme à 3GPP2 C.S0050-B v1.0', + 'AV1 Image File Format (.AVIF)' => 'Format de fichier d\'image AV1 (.AVIF)', + 'Apple iTunes AAC-LC (.M4B) Audio Book' => 'Apple iTunes AAC-LC (.M4B) Livre Audio', + 'Apple iTunes AAC-LC (.M4P) AES Protected Audio' => 'Apple iTunes AAC-LC (.M4P) Audio protégée par AES', + 'Apple iTunes Video (.M4V) Video' => 'Apple iTunes Video (.M4V) Vidéo', + 'Audible Enhanced Audiobook (.AAX)' => 'Livre audio enrichi Audible (.AAX)', + 'Audio Book for Adobe Flash Player 9+ (.F4B)' => 'Livre audio pour Adobe Flash Player 9+ (.F4B)', + 'Audio for Adobe Flash Player 9+ (.F4A)' => 'Audio pour Adobe Flash Player 9+ (.F4A)', + 'Canon Digital Camera' => 'Appareil photo numérique Canon', + 'Casio Digital Camera' => 'Appareil photo numérique Casio', + 'DMB MAF aud w/ HE-AAC v2 aud, MOT slides, DLS, JPG/PNG/MNG images' => 'DMB MAF aud avec HE-AAC v2 aud, diapositives MOT, DLS, images JPG/PNG/MNG', + 'DMB MAF aud with HE-AAC aud, JPG/PNG/MNG images' => 'DMB MAF aud avec HE-AAC aud, images JPG/PNG/MNG', + 'DMB MAF audio with ER-BSAC audio, JPG/PNG/MNG images' => 'DMB MAF audio avec ER-BSAC audio, images JPG/PNG/MNG', + 'DMB MAF supporting all the components defined in the specification' => 'DMB MAF prenant en charge tous les composants définis dans la spécification', + 'DMB MAF vid w/ AVC vid, ER-BSAC aud, BIFS, JPG/PNG/MNG images, TS' => 'DMB MAF vid avec vid AVC, ER-BSAC aud, BIFS, images JPG/PNG/MNG, TS', + 'DMB MAF vid w/ AVC vid, HE-AAC aud, BIFS, JPG/PNG/MNG images, TS' => 'DMB MAF vid avec vid AVC, HE-AAC aud, BIFS, images JPG/PNG/MNG, TS', + 'DMB MAF vid w/ AVC vid, HE-AAC v2 aud, BIFS, JPG/PNG/MNG images, TS' => 'DMB MAF vid avec vid AVC, HE-AAC v2 aud, BIFS, images JPG/PNG/MNG, TS', + 'DMB MAF w/ MPEG Layer II aud, MOT slides, DLS, JPG/PNG/MNG images' => 'DMB MAF avec couche MPEG II aud, diapositives MOT, DLS, images JPG/PNG/MNG', + 'DMB MAF, extending DA0A, with 3GPP timed text, DID, TVA, REL, IPMP' => 'DMB MAF, extension de dA0A, avec texte minuté 3GPP, DID, TVA, REL, IPMP', + 'DMB MAF, extending da1a, with 3GPP timed text, DID, TVA, REL, IPMP' => 'DMB MAF, extension de da1a, avec texte minuté 3GPP, DID, TVA, REL, IPMP', + 'DMB MAF, extending da2a, with 3GPP timed text, DID, TVA, REL, IPMP' => 'DMB MAF, extension de da2a, avec texte minuté 3GPP, DID, TVA, REL, IPMP', + 'DMB MAF, extending da3a w/ BIFS, 3GPP timed text, DID, TVA, REL, IPMP' => 'DMB MAF, extension de da3a, avec BIFS, texte minuté 3GPP, DID, TVA, REL, IPMP', + 'DMB MAF, extending dv1a, with 3GPP timed text, DID, TVA, REL, IPMP' => 'DMB MAF, extension de dv1a, avec texte minuté 3GPP, DID, TVA, REL, IPMP', + 'DMB MAF, extending dv2a, with 3GPP timed text, DID, TVA, REL, IPMP' => 'DMB MAF, extension de dv2a, avec texte minuté 3GPP, DID, TVA, REL, IPMP', + 'DMB MAF, extending dv3a, with 3GPP timed text, DID, TVA, REL, IPMP' => 'DMB MAF, extension de dv3a, avec texte minuté 3GPP, DID, TVA, REL, IPMP', + 'DVB (.DVB) over MPEG-2 Transport Stream' => 'DVB (.DVB) sur flux de transport MPEG-2', + 'DVB (.DVB) over RTP' => 'DVB (.DVB) sur RTP', + 'Digital Media Project' => 'Projet média numérique', + 'Dirac (wavelet compression), encapsulated in ISO base media (MP4)' => 'Dirac (compression en ondelettes), encapsulé en base ISO (MP4)', + 'H.264/MPEG-4 AVC (.MP4) Nero Cinema Profile' => 'H.264/MPEG-4 AVC (.MP4) Profil cinéma Nero', + 'H.264/MPEG-4 AVC (.MP4) Nero HDTV Profile' => 'H.264/MPEG-4 AVC (.MP4) Profil HDTV Nero', + 'H.264/MPEG-4 AVC (.MP4) Nero Mobile Profile' => 'H.264/MPEG-4 AVC (.MP4) Profil mobile Nero', + 'H.264/MPEG-4 AVC (.MP4) Nero Portable Profile' => 'H.264/MPEG-4 AVC (.MP4) Profil portable Nero', + 'H.264/MPEG-4 AVC (.MP4) Nero Standard Profile' => 'H.264/MPEG-4 AVC (.MP4) Profil standard Nero', + 'High Efficiency Image Format HEVC sequence (.HEICS)' => 'Sequence HEVC au format High Efficiency Image Format (.HEICS)', + 'High Efficiency Image Format HEVC still image (.HEIC)' => 'Image fixe HEVC au format High Efficiency Image Format (.HEIC)', + 'High Efficiency Image Format sequence (.HEIFS)' => 'Séquence au format High Efficiency Image Format (.HEIFS)', + 'High Efficiency Image Format still image (.HEIF)' => 'Image fixe au format High Efficiency Image Format (.HEIF)', + 'ISMACryp 2.0 Encrypted File' => 'Fichier crypté ISMACryp 2.0', + 'JPEG 2000 Compound Image (.JPM)' => 'Image composée JPEG 2000 (.JPM', + 'JPEG 2000 Compound Image (.JPM) [ISO 15444-6]' => 'Image composée JPEG 2000 (.JPM) [ISO 15444-6]', + 'JPEG 2000 Image (.JP2)' => 'Image JPEG 2000 Image (.JP2)', + 'JPEG 2000 Image (.JP2) [ISO 15444-1 ?]' => 'Image JPEG 2000 (.JP2) [ISO 15444-1 ?]', + 'JPEG 2000 with extensions (.JPX)' => 'Image JPEG 2000 avec extensions (.JPX)', + 'JPEG 2000 with extensions (.JPX) [ISO 15444-2]' => 'Image JPEG 2000 avec extensions (.JPX) [ISO 15444-2]', + 'JPEG XL Image (.JXL)' => 'Image JPEG XL (.JXL)', + 'MP4 Base w/ AVC ext [ISO 14496-12:2005]' => 'Base avec AVC ext [ISO 14496-12:2005]', + 'MP4 v2 [ISO 14496-14] Nero Digital AAC Audio' => 'MP4 v2 [ISO 14496-14] Audio AAC numérique Nero', + 'MPEG-4 (.MP4) Nero Cinema Profile' => 'MPEG-4 (.MP4) Profil cinéma Nero', + 'MPEG-4 (.MP4) Nero HDTV Profile' => 'MPEG-4 (.MP4) Profil HDTV Nero', + 'MPEG-4 (.MP4) Nero Mobile Profile' => 'MPEG-4 (.MP4) Profil mobile Nero', + 'MPEG-4 (.MP4) Nero Portable Profile' => 'MPEG-4 (.MP4) Profil portable Nero', + 'MPEG-4 (.MP4) Nero Standard Profile' => 'MPEG-4 (.MP4) Profil standard Nero', + 'MPEG-4 (.MP4) for SonyPSP' => 'MPEG-4 (.MP4) pour SonyPSP', + 'MPEG-4/3GPP Mobile Profile (.MP4/3GP) (for NTT)' => 'MPEG-4/3GPP Profil mobile (.MP4/3GP) (pour NTT)', + 'Motion JPEG 2000 [ISO 15444-3] General Profile' => 'Motion JPEG 2000 [ISO 15444-3] Profil général', + 'Motion JPEG 2000 [ISO 15444-3] Simple Profile' => 'Motion JPEG 2000 [ISO 15444-3] Profil simple', + 'Panasonic Digital Camera' => 'Appareil photo numérique Panasonic', + 'Photo Player, MAF [ISO/IEC 23000-3]' => 'Lecteur de photos, MAF [ISO/IEC 23000-3]', + 'Protected Video for Adobe Flash Player 9+ (.F4P)' => 'Vidéo protégée pour Adobe Flash Player 9+ (.F4P)', + 'Ross Video' => 'Vidéo Ross', + 'SD Memory Card Video' => 'Carte mémoire SD Vidéo', + 'Samsung stereoscopic, dual stream' => 'Samsung stéréoscopique, double flux', + 'Samsung stereoscopic, single stream' => 'Samsung stéréoscopique, flux unique', + 'Sony / Mobile QuickTime (.MQV) US Patent 7,477,830 (Sony Corp)' => 'Sony / Mobile QuickTime (.MQV) Brevet US 7.477.830 (Sony Corp)', + 'Unknown, from GPAC samples (prob non-existent)' => 'Inconnu, à partir d\'échantillons GPAC (probablement inexistant)', + 'Video for Adobe Flash Player 9+ (.F4V)' => 'Vidéo pour Adobe Flash Player 9+ (.F4V)', + }, + }, + 'MajorVersion' => 'Version majeure', + 'Make' => 'Fabricant', + 'MakeAndModel' => 'Fabricant et modèle', + 'MakerNote' => 'Note du fabricant', + 'MakerNoteApple' => 'Note du fabricant Apple', + 'MakerNoteCanon' => 'Note du fabricant Canon', + 'MakerNoteCasio' => 'Note du fabricant Casio', + 'MakerNoteCasio2' => 'Note du fabricant Casio 2', + 'MakerNoteDJI' => 'Note du fabricant DJI', + 'MakerNoteDJIInfo' => 'Note du fabricant Infos DJI', + 'MakerNoteFLIR' => 'Note du fabricant FLIR', + 'MakerNoteFujiFilm' => 'Note du fabricant FujiFilm', + 'MakerNoteGE' => 'Note du fabricant GE', + 'MakerNoteGE2' => 'Note du fabricant GE 2', + 'MakerNoteHP' => 'Note du fabricant HP', + 'MakerNoteHP2' => 'Note du fabricant HP 2', + 'MakerNoteHP4' => 'Note du fabricant HP 4', + 'MakerNoteHP6' => 'Note du fabricant HP 6', + 'MakerNoteHasselblad' => 'Note du fabricant Hasselblad', + 'MakerNoteISL' => 'Note du fabricant ISL', + 'MakerNoteJVC' => 'Note du fabricant JVC', + 'MakerNoteJVCText' => 'Note du fabricant JVC Texte', + 'MakerNoteKodak10' => 'Note du fabricant Kodak 10', + 'MakerNoteKodak11' => 'Note du fabricant Kodak 11', + 'MakerNoteKodak12' => 'Note du fabricant Kodak 12', + 'MakerNoteKodak1a' => 'Note du fabricant Kodak 1a', + 'MakerNoteKodak1b' => 'Note du fabricant Kodak 1b', + 'MakerNoteKodak2' => 'Note du fabricant Kodak 2', + 'MakerNoteKodak3' => 'Note du fabricant Kodak 3', + 'MakerNoteKodak4' => 'Note du fabricant Kodak 4', + 'MakerNoteKodak5' => 'Note du fabricant Kodak 5', + 'MakerNoteKodak6a' => 'Note du fabricant Kodak 6a', + 'MakerNoteKodak6b' => 'Note du fabricant Kodak 6b', + 'MakerNoteKodak7' => 'Note du fabricant Kodak 7', + 'MakerNoteKodak8a' => 'Note du fabricant Kodak 8a', + 'MakerNoteKodak8b' => 'Note du fabricant Kodak 8b', + 'MakerNoteKodak8c' => 'Note du fabricant Kodak 8c', + 'MakerNoteKodak9' => 'Note du fabricant Kodak 9', + 'MakerNoteKodakUnknown' => 'Note du fabricant Kodak Inconnue', + 'MakerNoteKyocera' => 'Note du fabricant Kyocera', + 'MakerNoteLeica' => 'Note du fabricant Leica', + 'MakerNoteLeica10' => 'Note du fabricant Leica 10', + 'MakerNoteLeica2' => 'Note du fabricant Leica 2', + 'MakerNoteLeica3' => 'Note du fabricant Leica 3', + 'MakerNoteLeica4' => 'Note du fabricant Leica 4', + 'MakerNoteLeica5' => 'Note du fabricant Leica 5', + 'MakerNoteLeica6' => 'Note du fabricant Leica 6', + 'MakerNoteLeica7' => 'Note du fabricant Leica 7', + 'MakerNoteLeica8' => 'Note du fabricant Leica 8', + 'MakerNoteLeica9' => 'Note du fabricant Leica 9', + 'MakerNoteMinolta' => 'Note du fabricant Minolta', + 'MakerNoteMinolta2' => 'Note du fabricant Minolta 2', + 'MakerNoteMinolta3' => 'Note du fabricant Minolta 3', + 'MakerNoteMotorola' => 'Note du fabricant Motorola', + 'MakerNoteNikon' => 'Note du fabricant Nikon', + 'MakerNoteNikon2' => 'Note du fabricant Nikon 2', + 'MakerNoteNikon3' => 'Note du fabricant Nikon 3', + 'MakerNoteNintendo' => 'Note du fabricant Nintendo', + 'MakerNoteOffset' => 'Note du fabricant Offset', + 'MakerNoteOlympus' => 'Note du fabricant Olympus', + 'MakerNoteOlympus2' => 'Note du fabricant Olympus 2', + 'MakerNoteOlympus3' => 'Note du fabricant Olympus 3', + 'MakerNotePanasonic' => 'Note du fabricant Panasonic', + 'MakerNotePanasonic2' => 'Note du fabricant Panasonic 2', + 'MakerNotePanasonic3' => 'Note du fabricant Panasonic 3', + 'MakerNotePentax' => 'Note du fabricant Pentax', + 'MakerNotePentax2' => 'Note du fabricant Pentax 2', + 'MakerNotePentax3' => 'Note du fabricant Pentax 3', + 'MakerNotePentax4' => 'Note du fabricant Pentax 4', + 'MakerNotePentax5' => 'Note du fabricant Pentax 5', + 'MakerNotePentax6' => 'Note du fabricant Pentax 6', + 'MakerNotePentaxUnknown' => 'Note du fabricant Pentax Inconnue', + 'MakerNotePhaseOne' => 'Note du fabricant PhaseOne', + 'MakerNoteReconyx' => 'Note du fabricant Reconyx', + 'MakerNoteReconyx2' => 'Note du fabricant Reconyx 2', + 'MakerNoteReconyx3' => 'Note du fabricant Reconyx 3', + 'MakerNoteRicoh' => 'Note du fabricant Ricoh', + 'MakerNoteRicoh2' => 'Note du fabricant Ricoh 2', + 'MakerNoteRicohPentax' => 'Note du fabricant Ricoh Pentax', + 'MakerNoteRicohText' => 'Note du fabricant Ricoh Texte', + 'MakerNoteSafety' => { + Description => 'Sécurité de la note du fabricant', + PrintConv => { + 'Safe' => 'Sûre', + 'Unsafe' => 'Pas sûre', + }, + }, + 'MakerNoteVersion' => 'Version des informations spécifiques fabricant', + 'MakerNotes' => 'Notes fabricant', + 'ManualFlashOutput' => { + Description => 'Sortie de flash manuelle', + PrintConv => { + 'Full' => 'Totale', + 'Low' => 'Basse', + 'Medium' => 'Moyenne', + 'n/a' => 'Non applicable', + }, + }, + 'ManualFlashStrength' => { + Description => 'Force du flash manuel', + PrintConv => { + 'n/a' => 'Non applicable', + 'n/a (x4)' => 'Non applicable (x4)', + }, + }, + 'ManualFocusDistance' => 'Distance de mise au point manuelle', + 'ManualFocusPointIllumination' => { + Description => 'Illumination manuelle des points de mise au point', + PrintConv => { + 'On' => 'Activé', + 'On During Focus Point Selection Only' => 'Activé uniquement pendant la sélection de la zone de mise au point', + }, + }, + 'ManualTv' => { + Description => 'Régl. Tv/Av manuel pour exp. M', + PrintConv => { + 'Tv=Control/Av=Main' => 'Tv=Contrôle rapide/Av=Principale', + 'Tv=Control/Av=Main w/o lens' => 'Tv=Contrôle rapide/Av=Principale sans objectif', + 'Tv=Main/Av=Control' => 'Tv=Principale/Av=Contrôle rapide', + 'Tv=Main/Av=Main w/o lens' => 'Tv=Principale/Av=Contrôle rapide sans objectif', + }, + }, + 'ManufactureDate' => 'Date de fabrication', + 'MarkBits' => 'Bits de marquage', + 'Marked' => 'Marqué', + 'MaskedAreas' => 'Zones masquées', + 'MasterDocumentID' => 'ID du document maître', + 'Matteing' => 'Matité', + 'MaxAperture' => 'Ouverture maximale', + 'MaxApertureAtMaxFocal' => 'Ouverture à la focale maxi', + 'MaxApertureAtMinFocal' => 'Ouverture à la focale mini', + 'MaxApertureValue' => 'Ouverture maximale de l\'objectif', + 'MaxAvailHeight' => 'Hauteur max Disponible', + 'MaxAvailWidth' => 'Largeur max Disponible', + 'MaxFocalLength' => 'Focale maxi', + 'MaxSampleValue' => 'Valeur maxi d\'échantillon', + 'MaxVal' => 'Valeur max', + 'MaximumDensityRange' => 'Etendue maximale de densité', + 'MeasuredEV' => 'EV mesuré', + 'MeasuredEV2' => 'EV 2 mesuré', + 'MeasuredEV3' => 'EV 3 mesuré', + 'MeasuredLV' => 'LV mesuré', + 'MeasuredRGGB' => 'RVVB mesuré', + 'MeasuredRGGBData' => 'Donnée RVVB mesurée', + 'Measurement' => 'Observateur de mesure', + 'MeasurementAccuracy' => 'Précision de la mesure', + 'MeasurementBacking' => 'Support de mesure', + 'MeasurementDeterminedBy' => 'Mesure déterminée par', + 'MeasurementDeterminedDate' => 'Date de détermination de la mesure', + 'MeasurementFlare' => 'Flare de mesure', + 'MeasurementGeometry' => { + Description => 'Géométrie de mesure', + PrintConv => { + '0/45 or 45/0' => '0/45 ou 45/0', + '0/d or d/0' => '0/d ou d/0', + }, + }, + 'MeasurementIlluminant' => 'Illuminant de mesure', + 'MeasurementObserver' => 'Observateur de mesure', + 'MediaBlackPoint' => 'Point noir moyen', + 'MediaDataOffset' => 'Décalage des données du média', + 'MediaDataSize' => 'Taille des données du média', + 'MediaGroupUUID' => 'UUID du groupe de médias', + 'MediaLanguageCode' => 'Code de la langue du média', + 'MediaLocation' => 'Emplacement du média', + 'MediaModifyDate' => 'Date de modification du média', + 'MediaType' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'MediaWhitePoint' => 'Point blanc moyen', + 'Megapixels' => 'Mégapixels', + 'MenuButtonDisplayPosition' => { + Description => 'Position début touche menu', + PrintConv => { + 'Previous' => 'Précédente', + 'Previous (top if power off)' => 'Précédente (Haut si dés.)', + 'Top' => 'Haut', + }, + }, + 'MenuButtonReturn' => { + PrintConv => { + 'Previous' => 'Précédente', + 'Top' => 'Haut', + }, + }, + 'MetaFormat' => 'Format méta', + 'MetaImageSize' => 'Taille méta de l\'image', + 'MetaType' => 'Type méta', + 'MetaVersion' => 'Version méta', + 'MetadataDate' => 'Date des metadonnées', + 'Metering' => { + Description => 'Mesurage', + PrintConv => { + 'Center-weighted' => 'Pondération centrale', + 'Matrix' => 'Matrice', + }, + }, + 'MeteringMode' => { + Description => 'Mode de mesure', + PrintConv => { + '8-segment' => '8 segments', + 'Average' => 'Moyenne', + 'Center-weighted average' => 'Moyenne pondérée centrale', + 'Default' => 'Par défaut', + 'Evaluative' => 'Évaluative', + 'Highlight' => 'Mise en lumière', + 'Multi-segment' => 'Multi-segments', + 'Multi-spot' => 'Multi-spots', + 'Other' => 'Autre', + 'Partial' => 'Partielle', + 'Pattern+AF' => 'Motif+AF', + 'Spot+Highlight control' => 'Spot+Mise en lumière contrôlée', + 'Spot+Shadow control' => 'Spot+Ombre contrôlée', + 'Unknown' => 'Inconnue', + }, + }, + 'MeteringMode2' => { + Description => 'Mode de mesure 2', + PrintConv => { + 'Average' => 'Moyenne', + 'Center-weighted average' => 'Moyenne pondérée centrale', + 'Highlight' => 'Mise en lumière', + 'Multi-segment' => 'Multi-segments', + 'Spot (Large)' => 'Spot (Grand)', + 'Spot (Standard)' => 'Spot (standard)', + }, + }, + 'MeteringMode3' => { + Description => 'Mode de mesure (3)', + PrintConv => { + 'Center-weighted average' => 'Moyenne pondérée centrale', + 'Multi-segment' => 'Multi-segments', + }, + }, + 'MeteringOffScaleIndicator' => { + Description => 'Indicateur de mesure hors échelle', + PrintConv => { + 'Out of Range' => 'Hors plage de valeurs', + 'Under/Over Range' => 'En dessous/au-dessus de la plage de valeurs', + 'Within Range' => 'Dans la plage de valeurs', + }, + }, + 'MeteringTime' => { + Description => 'Temps de mesure', + PrintConv => { + 'No Limit' => 'Non limité', + }, + }, + 'MicrophoneAttenuator' => { + Description => 'Atténuateur de microphone', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'MicrophoneFrequencyResponse' => { + Description => 'Réponse en fréquence du microphone', + PrintConv => { + 'Vocal Range' => 'Bande vocale', + 'Wide Range' => 'Bande large', + }, + }, + 'MinAperture' => 'Ouverture minimale', + 'MinApertureValue' => 'Valeur d\'ouverture minimale', + 'MinFocalLength' => 'Focale mini', + 'MinFocusDistance' => 'Distance minimale de mise au point', + 'MinSampleValue' => 'Valeur mini d\'échantillon', + 'MinSpatialSegmentationIDC' => 'Segmentation spatiale minimale IDC', + 'MinoltaQuality' => { + Description => 'Qualité Minolta', + PrintConv => { + 'Economy' => 'Économie', + 'Extra fine' => 'Extra Fine', + 'Normal' => 'Normale', + 'Raw' => 'RAW', + }, + }, + 'MinorVersion' => 'Version mineure', + 'MirrorLockup' => { + Description => 'Verrouillage du miroir', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + 'Enable: Down with Set' => 'Activé: Retour par touche SET', + }, + }, + 'ModDate' => 'Date de modification', + 'Model' => { + Description => 'Modèle de l\'appareil photo', + PrintConv => { + 'E-PL1s' => 'E-PL1', + }, + }, + 'Model2' => 'Modèle de l\'appareil photo (2)', + 'ModelAge' => 'Age du modèle de l\'appareil photo', + 'ModelAndVersion' => 'Modèle et version', + 'ModelID' => 'ID modèle', + 'ModelReleaseID' => 'ID de mise à disposition du modèle ', + 'ModelReleaseStatus' => { + Description => 'État de mise à disposition ', + PrintConv => { + 'Limited or Incomplete Model Releases' => 'Mise à disposition limitée ou incomplète du modèle', + 'None' => 'Aucun', + 'Not Applicable' => 'Non applicable', + 'Unlimited Model Releases' => 'Mise à disposition non limitée du modèle', + }, + }, + 'ModelReleaseYear' => 'Année de lancement du modèle', + 'ModelTiePoint' => 'Point d\'attache du modèle', + 'ModelTransform' => 'Transformation du modèle', + 'ModelType' => 'Type de modèle', + 'ModelYear' => 'Année du modèle', + 'ModelingFlash' => { + Description => 'Modélisation du flash', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ModifiedDigitalGain' => 'Gain numérique modifié', + 'ModifiedPictureStyle' => { + Description => 'Style d\'image modifié', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'CM Set 1' => 'CM jeu 1', + 'CM Set 2' => 'CM jeu 2', + 'Faithful' => 'Fidèle', + 'Fine Detail' => 'Détails fins', + 'High Saturation' => 'Saturation élevée', + 'Landscape' => 'Paysage', + 'Low Saturation' => 'Saturation faible', + 'Neutral' => 'Neutre', + 'None' => 'Aucun', + 'User Def. 1' => 'Défini par l\'utilisateur 1', + 'User Def. 2' => 'Défini par l\'utilisateur 2', + 'User Def. 3' => 'Défini par l\'utilisateur 3', + 'n/a' => 'Non applicable', + }, + }, + 'ModifiedSaturation' => { + Description => 'Saturation modifiée', + PrintConv => { + 'CM1 (Red Enhance)' => 'CM1 (Amélioration rouge)', + 'CM2 (Green Enhance)' => 'CM2 (Amélioration verte)', + 'CM3 (Blue Enhance)' => 'CM3 (Amélioration bleue)', + 'CM4 (Skin Tones)' => 'CM4 (Tons de la chair)', + 'Off' => 'Désactivé', + }, + }, + 'ModifiedSensorRedLevel' => 'Niveau rouge modifié du capteur', + 'ModifiedSharpnessFreq' => { + PrintConv => { + 'High' => 'Haut', + 'Highest' => 'Plus haut', + 'Low' => 'Doux', + 'n/a' => 'Non applicable', + }, + }, + 'ModifiedToneCurve' => { + PrintConv => { + 'Manual' => 'Manuelle', + }, + }, + 'ModifiedWhiteBalance' => { + PrintConv => { + 'Cloudy' => 'Temps nuageux', + 'Daylight' => 'Lumière du jour', + 'Daylight Fluorescent' => 'Fluorescente type jour', + 'Fluorescent' => 'Fluorescente', + 'Shade' => 'Ombre', + 'Tungsten' => 'Tungstène (lumière incandescente)', + }, + }, + 'ModifiedWhiteBalanceBlue' => 'Balance des blancs modifiée Bleue', + 'ModifiedWhiteBalanceRed' => 'Balance des blancs modifiée Rouge', + 'ModifyDate' => 'Date de modification du fichier', + 'MoireFilter' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'MonochromeColor' => { + Description => 'Couleur monochrome', + PrintConv => { + '(none)' => '(aucune)', + 'Blue' => 'Bleue', + 'Green' => 'Verte', + 'Normal' => 'Normale', + 'Purple' => 'Violette', + 'Sepia' => 'Sépia', + }, + }, + 'MonochromeFilterEffect' => { + Description => 'Effet de filtre monochrome', + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'Infrared' => 'Infrarouge', + 'None' => 'Aucun', + 'Off' => 'Désactivé', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + }, + }, + 'MonochromeGrainEffect' => { + Description => 'Effet de grain monochrome', + PrintConv => { + 'High' => 'Haut', + 'Low' => 'Bas', + 'Off' => 'Désactivé', + }, + }, + 'MonochromeLinear' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'MonochromeToningEffect' => { + Description => 'Effet de tonalité monochrome', + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Purple' => 'Violet', + 'Sepia' => 'Sépia', + }, + }, + 'MovieAF-OnButton' => { + PrintConv => { + 'Flash Disable/Enable' => 'Flash désactivé/activé', + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'MovieAFAreaMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + 'Single' => 'Simple', + }, + }, + 'MovieAFTrackingSensitivity' => { + Description => 'Sensibilité du suivi de la mise au point automatique pour les films', + PrintConv => { + '1 (High)' => '1 (Haute)', + '7 (Low)' => '7 (Basse)', + }, + }, + 'MovieAutoDistortionControl' => { + Description => 'Contrôle automatique de la distorsion pour les vidéo', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'MovieFunc1Button' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'MovieFunc3Button' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'MovieImageArea' => 'Zone de l\'image vidéo', + 'MovieMeteringMode' => { + Description => 'Mode de mesure vidéo', + PrintConv => { + 'Center' => 'Centre', + 'Highlight' => 'Mise en lumière', + 'Matrix' => 'Matrice', + }, + }, + 'MovieMultiSelector' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'MovieRecordButtonPlaybackMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'MovieSubjectDetection' => { + PrintConv => { + 'People' => 'Personnes', + }, + }, + 'MultiExposure' => { + Description => 'Infos Surimpression', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'MultiExposureAutoGain' => { + Description => 'Auto-expo des surimpressions', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'MultiExposureMode' => { + Description => 'Mode de surimpression', + PrintConv => { + 'Off' => 'Désactivé', + }, + }, + 'MultiExposureShots' => 'Nombre de prises de vue', + 'MultiExposureVersion' => 'Version Surimpression', + 'MultiFrameNREffect' => { + Description => 'Effet RB multi-photos', + PrintConv => { + 'High' => 'Haut', + }, + }, + 'MultiFrameNoiseReduction' => { + Description => 'Réduction du bruit multi-photos', + PrintConv => { + 'None' => 'Aucune', + 'Off' => 'Désactivée', + 'On' => 'Activé(e)', + 'n/a' => 'Non applicable', + }, + }, + 'MultiSelectorPlaybackMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'MultiSelectorShootMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'MultipleExposureSet' => { + Description => 'Exposition multiple', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'Mute' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'MyColorMode' => { + Description => 'Mode MyColor', + PrintConv => { + 'B&W' => 'N&B', + 'Color Accent' => 'Accentuation de la couleur', + 'Color Swap' => 'Permutation des couleurs', + 'Custom' => 'Personnalisé', + 'Dark Skin Tone' => 'Teint de peau foncé', + 'Light Skin Tone' => 'Teint de peau clair', + 'Neutral' => 'Neutre', + 'Off' => 'Désactivé', + 'Positive Film' => 'Film positif', + 'Sepia' => 'Sépia', + 'Vivid' => 'Éclatant', + 'Vivid Blue' => 'Bleu éclatant', + 'Vivid Green' => 'Vert éclatant', + 'Vivid Red' => 'Rouge éclatant', + }, + }, + 'NDFilter' => { + Description => 'Filtre ND', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'n/a' => 'Non applicable', + }, + }, + 'NEFBitDepth' => { + Description => 'Profondeur de bits NEF', + PrintConv => { + 'n/a (JPEG)' => 'Non applicable (JPEG)', + }, + }, + 'NEFCompression' => { + Description => 'Compression NEF', + PrintConv => { + 'High Efficiency' => 'Haute efficacité', + 'High Efficiency*' => 'Haute efficacité*', + 'Lossless' => 'Sans perte', + 'Lossy (type 1)' => 'Avec perte (type 1)', + 'Lossy (type 2)' => 'Avec perte (type 2)', + 'Small' => 'Petite', + 'Uncompressed' => 'Non compressé', + 'Uncompressed (reduced to 12 bit)' => 'Non compressé (réduit à 12 bits)', + }, + }, + 'NEFLinearizationTable' => 'Table de Linearization', + 'Name' => 'Nom', + 'NamedColor2' => 'Couleur nommée 2', + 'NativeDigest' => 'Sommaire natif', + 'NativeDisplayInfo' => 'Information sur l\'affichage natif', + 'NetExposureCompensation' => 'Compensation de l\'exposition', + 'NewsPhotoVersion' => 'Version d\'enregistrement news photo', + 'Nickname' => 'Surnom', + 'NikonCaptureData' => 'Données Nikon Capture', + 'NikonCaptureVersion' => 'Version Nikon Capture', + 'NikonMeteringMode' => { + Description => 'Mode de mesure Nikon', + PrintConv => { + 'Center' => 'Centre', + 'Highlight' => 'Mise en lumière', + 'Matrix' => 'Matrice', + }, + }, + 'Noise' => 'Bruit', + 'NoiseFilter' => { + PrintConv => { + 'Low' => 'Bas', + 'Off' => 'Désactivé', + 'n/a' => 'Non applicable', + }, + }, + 'NoiseReduction' => { + Description => 'Réduction du bruit', + PrintConv => { + '(none)' => '(aucune)', + '+1 (medium strong)' => '+1 (moyennement forte)', + '+2 (strong)' => '+2 (forte)', + '+3 (very strong)' => '+3 (très forte)', + '+4 (strongest)' => '+4 (la plus forte)', + '-1 (medium weak)' => '-1 (moyennement faible)', + '-2 (weak)' => '-2 (faible)', + '-3 (very weak)' => '-3 (très faible)', + '-4 (weakest)' => '-4 (la plus faible)', + '0 (normal)' => '0 (normale)', + 'High (+1)' => 'Forte (+1)', + 'Highest (+2)' => 'La plus forte (+2)', + 'Low' => 'Faible', + 'Low (-1)' => 'Faible (-1)', + 'Lowest (-2)' => 'La plus faible (-2)', + 'Medium' => 'Moyenne', + 'Noise Filter' => 'Avec filtre anti-bruit', + 'Noise Filter (ISO Boost)' => 'Avec filtre anti-bruit (ISO Boost)', + 'Noise Reduction' => 'Avec réduction du bruit', + 'Normal' => 'Normale', + 'Off' => 'Désactivée', + 'On' => 'Activée', + 'Standard' => '±0 (normal)', + 'Strong' => 'Forte', + 'Weak' => 'Faible', + 'n/a' => 'Non applicable', + }, + }, + 'NoiseReduction2' => { + Description => 'Réduction du bruit 2', + PrintConv => { + '(none)' => '(aucun)', + 'Noise Filter' => 'Filtre anti-bruit', + 'Noise Filter (ISO Boost)' => 'Filtre anti-bruit (Boost ISO)', + 'Noise Reduction' => 'Réduction du bruit', + }, + }, + 'NoiseReductionApplied' => 'Réduction de bruit appliquée', + 'NoiseReductionIntensity' => 'Intensité de réduction du bruit', + 'NoiseReductionMethod' => { + Description => 'Méthode de réduction du bruit', + PrintConv => { + 'Better Quality' => 'Meilleure qualité', + 'Better Quality 2013' => 'Meilleure qualité 2013', + 'Faster' => 'Plus rapide', + }, + }, + 'NoiseReductionSharpness' => 'Réduction de bruit netteté', + 'NominalMaxAperture' => 'Ouverture maxi nominal', + 'NominalMinAperture' => 'Ouverture mini nominal', + 'NumAFPoints' => 'Nombre de points de mise au point automatique', + 'NumChannels' => 'Nombre de canaux', + 'NumColors' => 'Nombre de couleurs', + 'NumFaceElements' => 'Nombre d\'éléments de visages', + 'NumFacePositions' => 'Nombre de positions de visage', + 'NumFonts' => 'Nombre de polices', + 'NumIndexEntries' => 'Nombre d\'entrées d\'index', + 'NumTemporalLayers' => 'Nombre de couches temporelles', + 'NumberOfImages' => 'Nombre d\'images', + 'NumberOfImagesArchived' => 'Nombre d\'images archivées', + 'NumberofInks' => 'Nombre d\'encres', + 'OECFColumns' => 'Colonnes OECF', + 'OECFNames' => 'Noms OECF', + 'OECFRows' => 'Lignes OECF', + 'OECFValues' => 'Valeurs OECF', + 'OPIProxy' => 'Proxy OPI', + 'ObjectAttributeReference' => 'Genre intellectuel', + 'ObjectCycle' => { + Description => 'Cycle d\'objet', + PrintConv => { + 'Both Morning and Evening' => 'Les deux', + 'Evening' => 'Soir', + 'Morning' => 'Matin', + }, + }, + 'ObjectFileType' => { + PrintConv => { + 'None' => 'Aucune', + 'Unknown' => 'Inconnu', + }, + }, + 'ObjectName' => 'Titre', + 'ObjectPreviewData' => 'Données de la miniature de l\\aperçu', + 'ObjectPreviewFileFormat' => 'Format du fichier de la miniature de l\'aperçu', + 'ObjectPreviewFileVersion' => 'Version du format du fichier de la miniature de l\'aperçu', + 'ObjectTypeReference' => 'Référence du type d\'objet', + 'OffsetSchema' => 'Schéma de décalage', + 'OffsetTime' => 'Valeur du décalage UTC', + 'OffsetTimeDigitized' => 'Valeur du décalage UTC des données numériques', + 'OffsetTimeOriginal' => 'Valeur du décalage UTC des données originales', + 'OldSubfileType' => 'Type du sous-fichier', + 'OlympusImageHeight' => 'Hauteur d\'image Olympus', + 'OlympusImageWidth' => 'Largeur d\'image Olympus', + 'OneTouchWB' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'OpticalZoom' => 'Zoom optique', + 'OpticalZoomCode' => 'Code du zoom optique', + 'OpticalZoomMode' => { + Description => 'Mode du zoom optique', + PrintConv => { + 'Extended' => 'Étendu', + 'Standard' => 'Normal', + }, + }, + 'OpticalZoomOn' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'Opto-ElectricConvFactor' => 'Facteur de conversion optoélectrique', + 'Orientation' => { + Description => 'Orientation de l\'image', + PrintConv => { + 'Flipped left-right' => 'Retourné de gauche à droite', + 'Flipped left-right, then up-down' => 'Retourné de gauche à droite, puis de haut en bas', + 'Flipped up-down' => 'Retourné de haut en bas', + 'Horizontal' => 'Horizontale', + 'Horizontal (normal)' => 'Horizontale (normale)', + 'Mirror horizontal' => 'Mise en miroir horizontal', + 'Mirror horizontal and rotate 270 CW' => 'Mise en miroir horizontal et rotation antihoraire de 270°', + 'Mirror horizontal and rotate 90 CW' => 'Mise en miroir horizontal et rotation antihoraire de 90°', + 'Mirror vertical' => 'Mise en miroir vertical', + 'Rotate 180' => 'Rotation de 180°', + 'Rotate 270 CW' => 'Rotation antihoraire de 270°', + 'Rotate 90 CW' => 'Rotation antihoraire de 90°', + 'Same as source' => 'Identique à la source', + 'Tiled' => 'Carrelée', + 'Vertical' => 'Verticale', + }, + }, + 'Orientation2' => { + PrintConv => { + 'Horizontal (normal)' => 'Horizontale (normale)', + 'Rotate 180' => 'Rotation de 180°', + 'Rotate 270 CW' => 'Rotation antihoraire de 270°', + 'Rotate 90 CW' => 'Rotation antihoraire de 90°', + }, + }, + 'OriginalDecisionDataOffset' => 'Décalage des données de décision originales', + 'OriginalFileName' => 'Nom du fichier original', + 'OriginalRawFileData' => 'Données du fichier RAW d\'origine', + 'OriginalRawFileDigest' => 'Digest du fichier RAW original', + 'OriginalRawFileName' => 'Nom du fichier RAW d\'origine', + 'OriginalRawFileType' => 'Type de fichier RAW d\'origine', + 'OriginalRawImage' => 'Image RAW originale', + 'OriginalTransmissionReference' => 'Identificateur de tâche', + 'OriginatingProgram' => 'Programme d\'origine', + 'OtherImage' => 'Autre image', + 'OutputLUT' => 'Sortie de la LUT (Table de correspondance)', + 'OutputResponse' => 'Réponse de sortie', + 'Owner' => 'Propriétaire', + 'OwnerID' => 'ID du propriétaire', + 'OwnerName' => 'Nom du propriétaire', + 'PDFVersion' => 'Version PDF', + 'PEFVersion' => 'Version PEF', + 'PF25MeteringMode' => 'Mode de mesure PF25', + 'Padding' => 'Remplissage', + 'PageName' => 'Nom de page', + 'PageNumber' => 'Page numéro', + 'PanasonicExifVersion' => 'Version Exif Panasonic', + 'PanasonicImageHeight' => 'Hauteur de l\'image Panasonic', + 'PanasonicImageWidth' => 'Largeur de l\'image Panasonic', + 'PanasonicRawVersion' => 'Version RAW Panasonic', + 'PanasonicTitle' => 'Titre', + 'PanoramaSize3D' => { + PrintConv => { + 'Wide' => 'Large', + 'n/a' => 'Non applicable', + }, + }, + 'ParallelismType' => 'Type de parallélisme', + 'PentaxImageSize' => { + Description => 'Taille de l\'image Pentax', + PrintConv => { + '2304x1728 or 2592x1944' => '2304 x 1728 ou 2592 x 1944', + '2560x1920 or 2304x1728' => '2560 x 1920 ou 2304 x 1728', + '2816x2212 or 2816x2112' => '2816 x 2212 ou 2816 x 2112', + '3008x2008 or 3040x2024' => '3008 x 2008 ou 3040 x 2024', + 'Full' => 'Pleine', + }, + }, + 'PentaxModelID' => 'Modèle Pentax', + 'PentaxModelType' => 'Type de modèle Pentax', + 'PentaxVersion' => 'Version Pentax', + 'People' => 'Personnes', + 'PerChannelBlackLevel' => 'Niveau de noir par canal', + 'PeripheralLighting' => { + Description => 'Correction éclairage périphérique', + PrintConv => { + 'Off' => 'Désactiver', + 'On' => 'Activer', + }, + }, + 'Person' => 'Personne', + 'PersonInImage' => 'Personnes sur l\'Image', + 'PersonInImageCharacteristic' => 'Caractéristiques des personnes sur l\'image', + 'PhaseDetectAF' => 'Auto-Focus', + 'PhotoEffect' => { + Description => 'Effet Photo', + PrintConv => { + 'B&W' => 'N&B', + 'Custom' => 'Personnalisé', + 'My Color Data' => 'Données My Color', + 'Neutral' => 'Neutre', + 'Off' => 'Désactivé', + 'Sepia' => 'Sépia', + 'Smooth' => 'Doux', + 'Vivid' => 'Éclatant', + }, + }, + 'PhotoEffects' => { + Description => 'Effets photo', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'PhotoEffectsBlue' => 'Effets photo bleu', + 'PhotoEffectsGreen' => 'Effets photo vert', + 'PhotoEffectsRed' => 'Effets photo rouge', + 'PhotoEffectsType' => { + Description => 'Type d\'effet photo', + PrintConv => { + 'B&W' => 'N&B', + 'None' => 'Aucune', + 'Sepia' => 'Sépia', + 'Tinted' => 'Teinté', + }, + }, + 'PhotoStyle' => { + Description => 'Style de la photo', + PrintConv => { + 'Natural' => 'Naturel', + 'Scenery' => 'Paysage', + 'Standard or Custom' => 'Standard ou personnalisé', + 'Vivid' => 'Éclatant', + }, + }, + 'PhotographicSensitivity' => 'Sensibilité photographique', + 'PhotometricInterpretation' => { + Description => 'Interprétation photométrique', + PrintConv => { + 'BlackIsZero' => 'Zéro pour noir', + 'Color Filter Array' => 'CFA (Matrice de filtres de couleurs)', + 'Pixar LogL' => 'CIE Log2(L) (Log luminance)', + 'Pixar LogLuv' => 'CIE Log2(L)(u\',v\') (Log luminance et chrominance)', + 'RGB' => 'RVB', + 'RGB Palette' => 'Palette RVB', + 'Transparency Mask' => 'Masque de transparence', + 'WhiteIsZero' => 'Zéro pour blanc', + }, + }, + 'PhotoshopAnnotations' => 'Annotations Photoshop', + 'PictureControl' => { + Description => 'Optimisation de l\'image', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'PictureControlActive' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'PictureControlAdjust' => { + Description => 'Ajustement de l\'optimisation d\'image', + PrintConv => { + 'Default Settings' => 'Paramètres par défault', + 'Full Control' => 'Réglages manuels', + 'Quick Adjust' => 'Réglages rapides', + }, + }, + 'PictureControlBase' => 'Optimisation de l\'image de base', + 'PictureControlName' => 'Optimisation de l\'image - Nom', + 'PictureControlQuickAdjust' => 'Optimisation de l\'image - Réglages rapides', + 'PictureControlVersion' => 'Optimisation de l\'image - Version', + 'PictureDescription' => 'Description de l\'image', + 'PictureDisplayRate' => 'Évaluation de l\'image', + 'PictureEffect' => { + Description => 'Effet d\'image', + PrintConv => { + 'HDR Painting' => 'Peinture HDR', + 'HDR Painting (high)' => 'Peinture HDR (haute)', + 'HDR Painting (low)' => 'Peinture HDR (basse)', + 'High Contrast Monochrome' => 'Monochrome à fort contraste', + 'Illustration (high)' => 'Illustration (haute)', + 'Illustration (low)' => 'Illustration (basse)', + 'Miniature (bottom)' => 'Miniature (en bas)', + 'Miniature (left)' => 'Miniature (à gauche)', + 'Miniature (middle horizontal)' => 'Miniature (milieu horizontal)', + 'Miniature (middle vertical)' => 'Miniature (milieu vertical)', + 'Miniature (right)' => 'Miniature (à droite)', + 'Miniature (top)' => 'Miniature (en haut)', + 'None' => 'Aucun', + 'Off' => 'Désactivé', + 'Partial Color (blue)' => 'Couleur partielle (bleue)', + 'Partial Color (green)' => 'Couleur partielle (verte)', + 'Partial Color (red)' => 'Couleur partielle (rouge)', + 'Partial Color (yellow)' => 'Couleur partielle (jaune)', + 'Pop Color' => 'Couleur pop', + 'Posterization' => 'Postérisation', + 'Posterization B/W' => 'Postérisation N&B', + 'Retro Photo' => 'Photo rétro', + 'Rich-tone Monochrome' => 'Monochrome riche en tonalités', + 'Soft Focus' => 'Flou artistique', + 'Soft Focus (high)' => 'Flou artistique (élevé)', + 'Soft Focus (low)' => 'Flou artistique (faible)', + 'Soft High Key' => 'Tons clairs logiciel', + 'Toy Camera' => 'Caméra jouet', + 'Toy Camera (cool)' => 'Caméra jouet (frais)', + 'Toy Camera (green)' => 'Caméra jouet (vert)', + 'Toy Camera (magenta)' => 'Caméra jouet (magenta)', + 'Toy Camera (normal)' => 'Caméra jouet (normal)', + 'Toy Camera (warm)' => 'Caméra jouet (chaud)', + 'Water Color' => 'Couleur de l\'eau', + 'Water Color 2' => 'Couleur de l\'eau 2', + }, + }, + 'PictureEffect2' => { + Description => 'Effet d\'image 2', + PrintConv => { + 'HDR Painting' => 'Peinture HDR', + 'High Contrast Monochrome' => 'Monochrome à fort contraste', + 'Off' => 'Désactivé', + 'Partial Color' => 'Couleur partielle', + 'Pop Color' => 'Couleur pop', + 'Posterization' => 'Postérisation', + 'Retro Photo' => 'Photo rétro', + 'Rich-tone Monochrome' => 'Monochrome riche en tonalités', + 'Soft Focus' => 'Flou artistique', + 'Soft High Key' => 'Tons clairs logiciel', + 'Toy Camera' => 'Caméra jouet', + 'Water Color' => 'Couleur de l\'eau', + }, + }, + 'PictureFinish' => { + Description => 'Finition de l\'image', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'Adobe RGB (ICC)' => 'Adobe RVB (ICC)', + 'Evening Scene' => 'Scène du soir', + 'Natural' => 'Naturel', + 'Natural+' => 'Naturel+', + 'Night Scene' => 'Nocturne', + 'Wind Scene' => 'Scène venteuse', + }, + }, + 'PictureMode' => { + Description => 'Mode de l\'image', + PrintConv => { + '1/2 EV steps' => 'Pas de 1/2 IL', + '1/3 EV steps' => 'Pas de 1/3 IL', + 'Anti-blur' => 'Anti-flou', + 'Aperture Priority' => 'Priorité à l\'ouverture', + 'Aperture Priority, Off-Auto-Aperture' => 'Priorité à l\'ouverture (auto-diaph hors service)', + 'Aperture-priority AE' => 'Priorité à l\'ouverture AE', + 'Auto PICT (Landscape)' => 'Image Auto (Paysage)', + 'Auto PICT (Macro)' => 'Image Auto (Macro)', + 'Auto PICT (Portrait)' => 'Image Auto (Portrait)', + 'Auto PICT (Sport)' => 'Auto PICT (sport)', + 'Auto PICT (Standard)' => 'Image Auto (Standard)', + 'Autumn' => 'Automne', + 'Blur Reduction' => 'Réduction du flou', + 'Bulb' => 'Pose B', + 'Bulb, Off-Auto-Aperture' => 'Pose B (auto-diaph hors service)', + 'Candlelight' => 'Bougie', + 'DOF Program' => 'Programme PdC', + 'DOF Program (HyP)' => 'Programme PdC (Hyper-programme)', + 'Dark Pet' => 'Animal foncé', + 'Digital Filter' => 'Filtre numérique', + 'Fireworks' => 'Feux d\'artifice', + 'Flash X-Sync Speed AE' => 'Synchro X flash vitesse AE', + 'Food' => 'Nourriture', + 'Frame Composite' => 'Vue composite', + 'Green Mode' => 'Mode vert', + 'Half-length Portrait' => 'Portrait (buste)', + 'Hi-speed Program' => 'Programme grande vitesse', + 'Hi-speed Program (HyP)' => 'Programme grande vitesse (Hyper-programme)', + 'Kids' => 'Enfants', + 'Landscape' => 'Paysage', + 'Light Pet' => 'Animal clair', + 'MTF Program' => 'Programme FTM', + 'MTF Program (HyP)' => 'Programme FTM (Hyper-programme)', + 'Manual' => 'Manuel', + 'Manual, Off-Auto-Aperture' => 'Manuel (auto-diaph hors service)', + 'Medium Pet' => 'Animal demi-teintes', + 'Museum' => 'Musée', + 'Natural' => 'Naturel', + 'Natural Light' => 'Lumière naturelle', + 'Natural Light & Flash' => 'Lumière naturelle & Flash', + 'Natural Skin Tone' => 'Ton chair naturel', + 'Night Scene' => 'Scène nocturne', + 'Night Scene HDR' => 'Scène nocturne HDR', + 'Night Scene Portrait' => 'Scène portrait nocturne', + 'No Flash' => 'Sans flash', + 'Pet' => 'Animaux de compagnie', + 'Program' => 'Programme', + 'Program (HyP)' => 'Programme AE (Hyper-programme)', + 'Program AE' => 'Priorité vitesse', + 'Program Av Shift' => 'Décalage programme Av', + 'Program Tv Shift' => 'Décalage programme Tv', + 'Purple' => 'Violet', + 'Self Portrait' => 'Autoportrait', + 'Sensitivity Priority AE' => 'Priorité sensibilité AE', + 'Sepia' => 'Sépia', + 'Shutter & Aperture Priority AE' => 'Priorité à l\'obturateur et à l\'ouverture AE', + 'Shutter Speed Priority' => 'Priorité vitesse', + 'Shutter speed priority AE' => 'Priorité vitesse', + 'Snow' => 'Neige', + 'Soft' => 'Doux', + 'Sunset' => 'Coucher de soleil', + 'Surf & Snow' => 'Surf et neige', + 'Synchro Sound Record' => 'Enregistrement de son synchro', + 'Text' => 'Texte', + 'Underwater' => 'Sous-marine', + 'Vivid' => 'Éclatant', + }, + }, + 'PictureMode2' => { + Description => 'Mode de l\'image 2', + PrintConv => { + 'Aperture Priority' => 'Priorité à l\'ouverture', + 'Aperture Priority, Off-Auto-Aperture' => 'Priorité ouverture (auto-diaph hors service)', + 'Auto PICT' => 'Image auto', + 'Bulb' => 'Pose B', + 'Bulb, Off-Auto-Aperture' => 'Pose B (auto-diaph hors service)', + 'Flash X-Sync Speed AE' => 'Expo auto, vitesse de synchro flash X', + 'Green Mode' => 'Mode vert', + 'Manual' => 'Manuelle', + 'Manual, Off-Auto-Aperture' => 'Manuel (auto-diaph hors service)', + 'Program AE' => 'Programme AE', + 'Program Av Shift' => 'Décalage programme Av', + 'Program Tv Shift' => 'Décalage programme Tv', + 'Scene Mode' => 'Mode scène', + 'Sensitivity Priority AE' => 'Expo auto, priorité sensibilité', + 'Shutter & Aperture Priority AE' => 'Expo auto, priorité vitesse et ouverture', + 'Shutter Speed Priority' => 'Priorité vitesse', + }, + }, + 'PictureModeBWFilter' => { + Description => 'Mode d\'image Filtre BW', + PrintConv => { + 'Green' => 'Vert', + 'Neutral' => 'Neutre', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + 'n/a' => 'Non applicable', + }, + }, + 'PictureModeEffect' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'PictureModeTone' => { + Description => 'Mode de tonalité de l\'image', + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'Neutral' => 'Neutre', + 'Purple' => 'Violet', + 'Sepia' => 'Sépia', + 'n/a' => 'Non applicable', + }, + }, + 'PictureProfile' => { + Description => 'Profil de l\'image ', + PrintConv => { + 'Gamma Cine1 (PP5)' => 'Gamma Ciné1 (PP5)', + 'Gamma Cine2 (PP6)' => 'Gamma Ciné1 (PP6)', + 'Gamma Cine3' => 'Gamma Ciné3', + 'Gamma Cine4' => 'Gamma Ciné4', + 'Gamma ITU709 (PP3 or PP4)' => 'Gamma ITU709 (PP3 ou PP4)', + 'Gamma Movie (PP1)' => 'Gamma Vidéo (PP1)', + 'Gamma S-Log3 (PP8 or PP9)' => 'Gamma S-Log3 (PP8 ou PP9)', + 'Gamma Still - B&W/Sepia' => 'Gamma fixe - N&B/Sépia', + 'Gamma Still - Clear' => 'Gamma fixe - Clair', + 'Gamma Still - Deep' => 'Gamma fixe - Profond', + 'Gamma Still - Light' => 'Gamma fixe - Lumière', + 'Gamma Still - Night View/Portrait' => 'Gamma fixe - Vue/Portrait nocturne', + 'Gamma Still - Portrait' => 'Gamma fixe - Portrait', + 'Gamma Still - Real' => 'Gamma fixe - Réel', + 'Gamma Still - Standard/Neutral (PP2)' => 'Gamma fixe - Standard/Neutre (PP2)', + 'Gamma Still - Vivid' => 'Gamma fixe - Éclatant', + }, + }, + 'PictureStyle' => { + Description => 'Style de l\'image', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'CM Set 1' => 'CM jeu 1', + 'CM Set 2' => 'CM jeu 2', + 'Custom' => 'Personnalisé', + 'Faithful' => 'Fidèle', + 'Fine Detail' => 'Détails fins', + 'High Saturation' => 'Saturation élevée', + 'Landscape' => 'Paysage', + 'Low Saturation' => 'Saturation faible', + 'Neutral' => 'Neutre', + 'None' => 'Aucun', + 'Shot Settings' => 'Paramétrage prise de vue', + 'Unknown?' => 'Inconnu ?', + 'User Def. 1' => 'Défini par l\'utilisateur 1', + 'User Def. 2' => 'Défini par l\'utilisateur 2', + 'User Def. 3' => 'Défini par l\'utilisateur 3', + 'n/a' => 'Non applicable', + }, + }, + 'PictureStylePC' => { + Description => 'Style de l\'image PC', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'CM Set 1' => 'CM jeu 1', + 'CM Set 2' => 'CM jeu 2', + 'Faithful' => 'Fidèle', + 'Fine Detail' => 'Détails fins', + 'High Saturation' => 'Saturation élevée', + 'Landscape' => 'Paysage', + 'Low Saturation' => 'Saturation faible', + 'Neutral' => 'Neutre', + 'None' => 'Aucun', + 'User Def. 1' => 'Défini par l\'utilisateur 1', + 'User Def. 2' => 'Défini par l\'utilisateur 2', + 'User Def. 3' => 'Défini par l\'utilisateur 3', + 'n/a' => 'Non applicable', + }, + }, + 'PictureStyleUserDef' => { + Description => 'Style de l\'image défini par l\'utilisateur', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'CM Set 1' => 'CM jeu 1', + 'CM Set 2' => 'CM jeu 2', + 'Faithful' => 'Fidèle', + 'Fine Detail' => 'Détails fins', + 'High Saturation' => 'Saturation élevée', + 'Landscape' => 'Paysage', + 'Low Saturation' => 'Saturation faible', + 'Neutral' => 'Neutre', + 'None' => 'Aucun', + 'User Def. 1' => 'Défini par l\'utilisateur 1', + 'User Def. 2' => 'Défini par l\'utilisateur 2', + 'User Def. 3' => 'Défini par l\'utilisateur 3', + 'n/a' => 'Non applicable', + }, + }, + 'PictureWizardMode' => { + PrintConv => { + 'Landscape' => 'Paysage', + 'Vivid' => 'Éclatant', + 'n/a' => 'Non applicable', + }, + }, + 'Pitch' => { + Description => 'Tangage', + PrintConv => { + 'High' => 'Élevé', + 'Low' => 'Faible', + }, + }, + 'PitchAngle' => 'Angle de tangage', + 'PixelAspectRatio' => 'Ratio d\'aspect des pixels', + 'PixelIntensityRange' => 'Plage d\'intensité des pixels', + 'PixelScale' => 'Échelle en pixel', + 'PixelShiftInfo' => { + Description => 'Informations sur le décalage des pixels', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'PixelUnits' => { + PrintConv => { + 'Unknown' => 'Inconnu', + }, + }, + 'PlanarConfiguration' => { + Description => 'Configuration planaire', + PrintConv => { + 'Chunky' => 'Format Chunky (composants entrelacés)', + 'Planar' => 'Format Planaire (composants séparés)', + }, + }, + 'PostalCode' => 'Code Postal', + 'PowerSource' => { + Description => 'Source d\'alimentation', + PrintConv => { + 'Body Battery' => 'Accu boîtier', + 'External Power Supply' => 'Alimentation externe', + 'Grip Battery' => 'Accu poignée', + }, + }, + 'Predictor' => { + Description => 'Prédicteur', + PrintConv => { + 'Horizontal differencing' => 'Différentiation horizontale', + 'None' => 'Aucun schéma de prédicteur utilisé avant l\'encodage', + }, + }, + 'Preview0' => 'Aperçu 0', + 'Preview1' => 'Aperçu 1', + 'Preview2' => 'Aperçu 2', + 'PreviewApplicationName' => 'Nom de l\'application d\'aperçu', + 'PreviewApplicationVersion' => 'Version de l\'application d\'aperçu', + 'PreviewButton' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'PreviewButtonPlusDials' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'PreviewColorSpace' => { + Description => 'Espace de couleur de l\'aperçu', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'Gray Gamma 2.2' => 'Gamma gris 2.2', + 'ProPhoto RGB' => 'Photo Pro Adobe RVB', + 'Unknown' => 'Inconnu', + 'sRGB' => 'sRVB', + }, + }, + 'PreviewDate' => 'Date de l\'aperçu', + 'PreviewDateTime' => 'Horodatage de l\'aperçu', + 'PreviewDuration' => 'Durée de l\'aperçu', + 'PreviewImage' => 'Aperçu de l\'image', + 'PreviewImage1' => 'Aperçu de l\'image 1', + 'PreviewImage2' => 'Aperçu de l\'image 2', + 'PreviewImageBorders' => 'Limites de l\'aperçu', + 'PreviewImageData' => 'Données de l\'aperçu', + 'PreviewImageHeight' => 'Hauteur de l\'aperçu', + 'PreviewImageLength' => 'Longueur de l\'aperçu', + 'PreviewImageName' => 'Nom de l\'aperçu', + 'PreviewImageSize' => 'Taille de l\'aperçu', + 'PreviewImageStart' => 'Début de l\'aperçu', + 'PreviewImageType' => 'Type de l\\image d\'aperçu', + 'PreviewImageValid' => { + Description => 'Aperçu valide', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'PreviewQuality' => { + PrintConv => { + 'CRAW' => 'cRAW', + 'Economy' => 'Économie', + 'Light (RAW)' => 'Légère (RAW)', + 'Normal' => 'Normale', + 'Superfine' => 'Super fine', + 'n/a' => 'Non applicable', + }, + }, + 'PreviewSettingsDigest' => 'Digest des réglages d\'aperçu', + 'PreviewSettingsName' => 'Nom des réglages d\'aperçu', + 'PrimaryAFPoint' => { + Description => 'Points de mise au point automatique utilisés', + PrintConv => { + '(none)' => '(aucun)', + 'Bottom' => 'En bas', + 'C6 (Center)' => 'C6 (Centre)', + 'Center' => 'Au centre', + 'D8 (Center)' => 'D8 (Centre)', + 'E8 (Center)' => 'E8 (Centre)', + 'E9 (Center)' => 'E9 (Centre)', + 'F11 (Center)' => 'F11 (Centre)', + 'F8 (Center)' => 'F8 (Centre)', + 'Far Left' => 'À l\'extrême gauche', + 'Far Right' => 'À l\'extrême droite', + 'Lower-left' => 'En bas à gauche', + 'Lower-right' => 'En bas à droite', + 'Mid-left' => 'Au milieu à gauche', + 'Mid-right' => 'Au milieu à droite', + 'Top' => 'En haut', + 'Upper-left' => 'En haut à gauche', + 'Upper-right' => 'En haut à droite', + }, + }, + 'PrimaryChromaticities' => 'Chromaticité des couleurs primaires', + 'PrimaryItemReference' => 'Référence de l\'élément principal', + 'PrimaryPlatform' => 'Plateforme primaire', + 'PrintIM' => 'Print IM', + 'PrintIMVersion' => 'Version Print IM', + 'PrintInfo' => 'Imprimer Info', + 'PrintInfo2' => 'Imprimer Info 2', + 'PrintStyle' => { + Description => 'Style d\'impression', + PrintConv => { + 'Centered' => 'Centré', + 'Size to Fit' => 'Adapter à la taille', + 'User Defined' => 'Défini par l\'utilisateur', + }, + }, + 'PrioritySetInAWB' => { + Description => 'Priorité accordée à la balance automatique des blancs', + PrintConv => { + 'Ambience' => 'Ambiance', + 'White' => 'Blanc', + }, + }, + 'PrioritySetupShutterRelease' => { + PrintConv => { + 'AF' => 'Mise au point automatique', + }, + }, + 'ProcessingSoftware' => 'Logiciel de traitement', + 'Producer' => 'Producteur', + 'ProductID' => 'ID de produit', + 'ProductionCode' => 'L\'appareil est passé en SAV', + 'ProfileCMMType' => { + Description => 'Type du profil CMM', + PrintConv => { + 'Jetsoft Development' => 'Etsoft Development', + 'Modgraph, Inc.' => 'Odgraph, Inc.', + 'Philips Consumer Electronics Co.' => 'Hilips Consumer Electronics Co.', + 'none' => 'Aucun', + }, + }, + 'ProfileCalibrationSig' => 'Signature de calibration du profil', + 'ProfileClass' => { + Description => 'Classe du profil', + PrintConv => { + 'Abstract Profile' => 'Profil de résumé', + 'ColorSpace Conversion Profile' => 'Profil de conversion d\'espace de couleur', + 'DeviceLink Profile' => 'Profil de liaison', + 'Display Device Profile' => 'Profil d\'appareil d\'affichage', + 'Input Device Profile' => 'Profil d\'appareil d\'entrée', + 'NamedColor Profile' => 'Profil de couleur nommée', + 'Nikon Input Device Profile (NON-STANDARD!)' => 'Profil Nikon ("nkpf")', + 'Output Device Profile' => 'Profil d\'appareil de sortie', + }, + }, + 'ProfileConnectionSpace' => 'Espace de connexion du profil', + 'ProfileCopyright' => 'Copyright du profil', + 'ProfileCreator' => 'Créateur du profil', + 'ProfileDateTime' => 'Horodatage du profil', + 'ProfileDescription' => 'Description du profil', + 'ProfileDescriptionML' => 'Description ML du profil', + 'ProfileEmbedPolicy' => { + Description => 'Règles d\'usage du profil incluses', + PrintConv => { + 'Allow Copying' => 'Permet la copie', + 'Embed if Used' => 'Inclus si utilisé', + 'Never Embed' => 'Jamais inclus', + 'No Restrictions' => 'Pas de restriction', + }, + }, + 'ProfileFileSignature' => 'Signature du fichier du profil', + 'ProfileHueSatMapData1' => 'Données du profil teinte sat. 1', + 'ProfileHueSatMapData2' => 'Données du profil teinte sat. 2', + 'ProfileHueSatMapDims' => 'Divisions de teinte', + 'ProfileID' => 'ID du profil', + 'ProfileLookTableData' => 'Table de correspondance (LUT) du profil - Données', + 'ProfileLookTableDims' => 'Table de correspondance (LUT) du profil - Teintes', + 'ProfileLookTableEncoding' => { + Description => 'Codage de la table de correspondance', + PrintConv => { + 'Linear' => 'Linéaire', + }, + }, + 'ProfileName' => 'Nom du profil', + 'ProfileSequenceDesc' => 'Description de séquence du profil', + 'ProfileToneCurve' => 'Courbe de tonalité du profil', + 'ProfileType' => { + Description => 'Type de profil', + PrintConv => { + 'Group 3 FAX' => 'Fax Group 3', + 'Unspecified' => 'Non spécifié', + }, + }, + 'ProfileVersion' => 'Version du profil', + 'ProgramISO' => { + Description => 'Programme ISO', + PrintConv => { + 'Intelligent ISO' => 'ISO Intelligent', + 'n/a' => 'Non applicable', + }, + }, + 'ProgramLine' => { + Description => 'Ligne de programme', + PrintConv => { + 'Depth' => 'Priorité profondeur de champ', + 'Hi Speed' => 'Priorité grande vitesse', + 'MTF' => 'Priorité FTM', + 'Normal' => 'Normale', + }, + }, + 'ProgramMode' => { + PrintConv => { + 'None' => 'Aucune', + 'Sunset' => 'Coucher de soleil', + 'Text' => 'Texte', + }, + }, + 'ProgramShift' => 'Décalage Programme', + 'ProgramVersion' => 'Version du programme', + 'Protect' => 'Protéger', + 'Province-State' => 'État / Région', + 'Publisher' => 'Editeur', + 'Quality' => { + Description => 'Qualité', + PrintConv => { + '2 (Telephone)' => '2 (Téléphone)', + '3 (Thumb)' => '3 (Pouce)', + '6 (Xtreme)' => '6 (Extrême)', + '7 (Insane)' => '7 (Folle)', + '8 (BrainDead)' => '8 (Mort cérébrale)', + 'Basic' => 'Basique', + 'Best' => 'Meilleure', + 'Better' => 'Mieux', + 'CRAW' => 'cRAW', + 'Compressed RAW' => 'RAW compressé', + 'Compressed RAW + JPEG' => 'RAW compressé + JPEG', + 'Dynamic Pixel Shift' => 'Décalage dynamique des pixels', + 'Economy' => 'Économie', + 'Extra Fine' => 'Extra fine', + 'Good' => 'Bonne', + 'High' => 'Haute', + 'Light' => 'Légère', + 'Light (RAW)' => 'Légère (RAW)', + 'Low' => 'Basse', + 'Medium' => 'Moyenne', + 'Normal' => 'Normale', + 'RAW (pixel shift enabled)' => 'RAW (décalage des pixels activé)', + 'RAW + JPEG' => 'RAW+JPEG', + 'RAW + Light' => 'RAW + Légère', + 'Superfine' => 'Super fine', + 'Unstable/Experimental' => 'Instable/Expérimental', + 'n/a' => 'Non applicable', + }, + }, + 'Quality2' => 'Qualité 2', + 'QualityButton' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'QualityButtonPlaybackMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'QualityMode' => { + Description => 'Qualité', + PrintConv => { + 'Economy' => 'Économie', + 'Fine' => 'Haute', + 'Normal' => 'Normale', + }, + }, + 'QuantizationMethod' => { + Description => 'Méthode de quantification', + PrintConv => { + 'Color Space Specific' => 'Spécifique à l\'espace de couleur', + 'Compression Method Specific' => 'Spécifique à la méthode de compression', + 'Gamma Compensated' => 'Compensée gamma', + 'IPTC Ref B' => 'IPTC réf "B"', + 'Linear Density' => 'Densité linéaire', + 'Linear Dot Percent' => 'Pourcentage de point linéaire', + 'Linear Reflectance/Transmittance' => 'Réflectance/transmittance linéaire', + }, + }, + 'QuickAdjust' => 'Réglages rapides', + 'QuickControlDialInMeter' => { + Description => 'Molette de contrôle rapide en mesure', + PrintConv => { + 'AF point selection' => 'Sélection collimateur AF', + 'Exposure comp/Aperture' => 'Correction exposition/ouverture', + 'ISO speed' => 'Sensibilité ISO', + }, + }, + 'QuickShot' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'QuietShutterShootingSpeed' => { + PrintConv => { + 'Single' => 'Simple', + }, + }, + 'RAFVersion' => 'Version RAF', + 'RAWFileType' => { + Description => 'Type de fichier RAW', + PrintConv => { + 'Compressed RAW' => 'RAW compressé', + 'Lossless Compressed RAW' => 'RAW compressé sans perte', + 'Uncompressed RAW' => 'RAW non compressé', + 'n/a' => 'Non applicable', + }, + }, + 'RFLensType' => { + Description => 'Type d\'objectif RF', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ROMOperationMode' => 'Mode de fonctionnement de la ROM', + 'RasterPadding' => 'Remplissage raster', + 'RasterizedCaption' => 'Légende rastérisée', + 'Rating' => { + Description => 'Classement', + PrintConv => { + 'Clean' => 'Propre', + 'Explicit' => 'Explicite', + 'Explicit (old)' => 'Explicite (ancien)', + 'none' => 'aucun', + }, + }, + 'RatingPercent' => 'Rapport en pourcentage', + 'RawAndJpgRecording' => { + Description => 'Enregistrement RAW et JPEG', + PrintConv => { + 'JPEG (Best)' => 'JPEG (le meilleur)', + 'JPEG (Better)' => 'JPEG (meilleur)', + 'JPEG (Good)' => 'JPEG (bon)', + 'RAW (DNG, Best)' => 'RAW (DNG, le meilleur)', + 'RAW (DNG, Better)' => 'RAW (DNG, meilleur)', + 'RAW (DNG, Good)' => 'RAW (DNG, bon)', + 'RAW (PEF, Best)' => 'RAW (PEF, le meilleur)', + 'RAW (PEF, Better)' => 'RAW (PEF, meilleur)', + 'RAW (PEF, Good)' => 'RAW (PEF, bon)', + 'RAW+JPEG (DNG, Best)' => 'RAW+JPEG (DNG, le meilleur)', + 'RAW+JPEG (DNG, Better)' => 'RAW+JPEG (DNG, meilleur)', + 'RAW+JPEG (DNG, Good)' => 'RAW+JPEG (DNG, bon)', + 'RAW+JPEG (PEF, Best)' => 'RAW+JPEG (PEF, le meilleur)', + 'RAW+JPEG (PEF, Better)' => 'RAW+JPEG (PEF, meilleur)', + 'RAW+JPEG (PEF, Good)' => 'RAW+JPEG (PEF, bon)', + 'RAW+Large/Fine' => 'RAW+grande/fine', + 'RAW+Large/Normal' => 'RAW+grande/normale', + 'RAW+Medium/Fine' => 'RAW+moyenne/fine', + 'RAW+Medium/Normal' => 'RAW+moyenne/normale', + 'RAW+Small/Fine' => 'RAW+petite/fine', + 'RAW+Small/Normal' => 'RAW+petite/normale', + }, + }, + 'RawColorAdj' => { + PrintConv => { + 'Shot Settings' => 'Paramétrage prise de vue', + }, + }, + 'RawData' => 'Données RAW', + 'RawDataOffset' => 'Décalage données RAW', + 'RawDataUniqueID' => 'ID unique de données brutes', + 'RawDevArtFilter' => { + PrintConv => { + 'Cross Process' => 'Traitement croisé', + 'Cross Process II' => 'Traitement croisé II', + 'Partial Color' => 'Couleur partielle', + 'Partial Color II' => 'Couleur partielle II', + 'Partial Color III' => 'Couleur partielle III', + 'Soft Focus' => 'Flou artistique', + 'Soft Focus 2' => 'Flou artistique 2', + }, + }, + 'RawDevAutoGradation' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'RawDevColorSpace' => { + Description => 'Espace couleur du développeur RAW', + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'Pro Photo RGB' => 'Photo Pro Adobe RVB', + 'sRGB' => 'sRVB', + }, + }, + 'RawDevContrastValue' => 'Valeur de contraste du développeur RAW', + 'RawDevEditStatus' => { + Description => 'État d\'édition du développeur RAW', + PrintConv => { + 'Edited (Landscape)' => 'Édité (Paysage)', + 'Edited (Portrait)' => 'Édité (Portrait)', + }, + }, + 'RawDevEngine' => { + Description => 'Moteur du développeur RAW', + PrintConv => { + 'Advanced High Function' => 'Haute fonctionnalité avancée', + 'Advanced High Speed' => 'Grande vitesse avancée', + 'High Function' => 'Haute fonctionnalité', + 'High Speed' => 'Grande vitesse', + }, + }, + 'RawDevExposureBiasValue' => 'Valeur du biais d\'exposition du développeur RAW', + 'RawDevGrayPoint' => 'Point gris du développeur RAW', + 'RawDevMemoryColorEmphasis' => 'Accentuation des couleurs en mémoire du développeur RAW', + 'RawDevNoiseReduction' => { + Description => 'Réduction du bruit du développeur RAW', + PrintConv => { + '(none)' => '(aucun)', + 'Noise Filter' => 'Filtre anti-bruit', + 'Noise Filter (ISO Boost)' => 'Filtre anti-bruit (Boost ISO)', + 'Noise Reduction' => 'Réduction du bruit', + }, + }, + 'RawDevPMPictureTone' => { + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'Neutral' => 'Neutre', + 'Purple' => 'Violet', + 'Sepia' => 'Sépia', + }, + }, + 'RawDevPM_BWFilter' => { + PrintConv => { + 'Green' => 'Vert', + 'Neutral' => 'Neutre', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + }, + }, + 'RawDevPictureMode' => { + PrintConv => { + 'Natural' => 'Naturel', + 'Sepia' => 'Sépia', + 'Vivid' => 'Éclatant', + }, + }, + 'RawDevSaturationEmphasis' => 'Accentuation de la saturation du développeur RAW', + 'RawDevSettings' => { + Description => 'Réglages du développeur RAW', + PrintConv => { + '(none)' => '(aucun)', + 'Color Space' => 'Espace couleur', + 'Contrast' => 'Contraste', + 'High Function' => 'Haute fonction', + 'Noise Reduction' => 'Réduction du bruit', + 'Sharpness' => 'Netteté', + 'WB Color Temp' => 'Balance des blancs Température de couleur', + 'WB Gray Point' => 'Balance des blancs Point gris', + }, + }, + 'RawDevSharpnessValue' => 'Valeur de netteté du développeur RAW', + 'RawDevVersion' => 'Version du développeur RAW', + 'RawDevWBFineAdjustment' => 'Réglage fin de la Balance des blancs du développeur RAW', + 'RawDevWhiteBalance' => { + PrintConv => { + 'Color Temperature' => 'Température de couleur', + 'Gray Point' => 'Point gris', + }, + }, + 'RawDevWhiteBalanceValue' => 'Valeur de la balance des blancs du développeur RAW', + 'RawDevelopmentProcess' => 'Processus de développement RAW', + 'RawExposureBias' => 'Biais d\'exposition', + 'RawFile' => 'Fichier RAW', + 'RawFileName' => 'Nom du fichier RAW', + 'RawFormat' => 'Format RAW', + 'RawImageCenter' => 'Centre de l\'image RAW', + 'RawImageCropTopLeft' => 'Image RAW recadrée en haut à gauche', + 'RawImageCroppedSize' => 'Image RAW taille recadrée', + 'RawImageDigest' => 'Résumé de l\'image RAW', + 'RawImageFullHeight' => 'Hauteur totale de l\'image RAW', + 'RawImageFullSize' => 'Taille totale de l\'image RAW', + 'RawImageFullWidth' => 'Largeur total de l\'image RAW', + 'RawImageHeight' => 'Hauteur de l\'image RAW', + 'RawImageMode' => 'Mode de l\'image RAW', + 'RawImageSegmentation' => 'Segmentation de l\'image RAW', + 'RawImageSize' => 'Taille de l\'image RAW', + 'RawImageWidth' => 'Largeur de l\'image brute', + 'RawInfoVersion' => 'Information version RAW', + 'RawJpgQuality' => { + Description => 'Qualité Jpeg RAW', + PrintConv => { + 'CRAW' => 'cRAW', + 'Economy' => 'Économie', + 'Light (RAW)' => 'Légère (RAW)', + 'Normal' => 'Normale', + 'Superfine' => 'Super fine', + 'n/a' => 'Non applicable', + }, + }, + 'RawJpgSize' => { + Description => 'Taille Jpeg RAW', + PrintConv => { + '1280x720 Movie' => 'Vidéo 1280x720', + '1920x1080 Movie' => 'Vidéo 1920 x1080', + '4096x2160 Movie' => 'Vidéo 4096x2160', + '640x480 Movie' => 'Vidéo 640x480', + 'Large' => 'Grande', + 'Medium' => 'Moyenne', + 'Medium 1' => 'Moyenne 1', + 'Medium 2' => 'Moyenne 2', + 'Medium 3' => 'Moyenne 3', + 'Medium Movie' => 'Vidéo moyenne', + 'Medium Widescreen' => 'Écran large moyen', + 'Postcard' => 'Carte postale', + 'Small' => 'Petite', + 'Small 1' => 'Petite 1', + 'Small 2' => 'Petite 2', + 'Small 3' => 'Petite 3', + 'Small Movie' => 'Petite vidéo', + 'Widescreen' => 'Écran large', + 'n/a' => 'Non applicable', + }, + }, + 'RecommendedExposureIndex' => 'Index d\'exposition recommandé', + 'RecordID' => 'Identifiant de l\'enregistrement', + 'RecordMode' => { + Description => 'Mode d\'enregistrement', + PrintConv => { + 'Aperture Priority' => 'Priorité à l\'ouverture', + 'Best Shot' => 'Meilleure prise de vue', + 'Manual' => 'Manuel', + 'Movie' => 'Vidéo', + 'Movie (19)' => 'Vidéo (19)', + 'Program AE' => 'Programme d\'exposition automatique', + 'Shutter Priority' => 'Priorité à l\'obturateur', + 'YouTube Movie' => 'Vidéo YouTube', + }, + }, + 'RecordingMode' => { + PrintConv => { + 'Landscape' => 'Paysage', + 'Manual' => 'Manuelle', + 'Night Scene' => 'Nocturne', + }, + }, + 'RedAdjust' => 'Ajustement du rouge', + 'RedBalance' => 'Balance rouge', + 'RedEyeCorrection' => { + Description => 'Correction des yeux rouges', + PrintConv => { + 'Automatic' => 'Automatique', + 'Click on Eyes' => 'Clic sur les yeux', + 'Off' => 'Désactivé', + }, + }, + 'RedEyeReduction' => { + Description => 'Réduction yeux rouges', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'RedMatrixColumn' => 'Colonne de la matrice rouge', + 'RedTRC' => 'Courbe de reproduction des tonalités rouges', + 'ReductionMatrix1' => 'Matrice de réduction 1', + 'ReductionMatrix2' => 'Matrice de réduction 2', + 'ReductionMatrix3' => 'Matrice de réduction 3', + 'ReferenceBlackWhite' => 'Paire de valeurs de référence noir et blanc', + 'ReferenceDate' => 'Date de référence', + 'ReferenceNumber' => 'Numéro de référence', + 'ReferenceService' => 'Service de référence', + 'RegionAppliedToDimensions' => 'Dimensions appliquées à la zone', + 'RegionAppliedToDimensionsH' => 'Dimensions appliquées en hauteur à la zone', + 'RegionAppliedToDimensionsUnit' => 'Unité des valeurs appliquées aux dimensions', + 'RegionAppliedToDimensionsW' => 'Dimensions appliquées en largeur à la zone', + 'RegionArea' => 'Zone', + 'RegionAreaD' => 'Profondeur de la zone', + 'RegionAreaH' => 'Hauteur de la zone', + 'RegionAreaUnit' => 'Unité des valeurs de la zone', + 'RegionAreaW' => 'Largeur de la zone', + 'RegionAreaX' => 'Position horizontale de la zone', + 'RegionAreaY' => 'Position verticale de la zone', + 'RegionBarCodeValue' => 'Valeur du code-barres de la zone', + 'RegionCode' => 'Code de la zone', + 'RegionConstraints' => 'Contraintes de la zone', + 'RegionDataType' => 'Type de données de la zone', + 'RegionDescription' => 'Description de la zone', + 'RegionExtensions' => 'Extensions de la zone', + 'RegionFlags' => 'Indicateurs de la zone', + 'RegionFocusUsage' => { + Description => 'Utilisation de la zone', + PrintConv => { + 'Evaluated, Not Used' => 'Évaluée, non utilisée', + 'Evaluated, Used' => 'Évaluée, utilisée', + 'Not Evaluated, Not Used' => 'Non évaluée, non utilisée', + }, + }, + 'RegionName' => 'Nom de la zone', + 'RegionPersonDisplayName' => 'Nom de la personne dans la zone', + 'RegionRectangle' => 'Région - Rectangle', + 'RegionType' => { + Description => 'Type de région', + PrintConv => { + 'BarCode' => 'Code-barres', + 'Face' => 'Visage', + 'Pet' => 'Animal de compagnie', + }, + }, + 'RelatedImageFileFormat' => 'Format du fichier image associé', + 'RelatedImageHeight' => 'Hauteur de l\'image asspciée', + 'RelatedImageWidth' => 'Largeur de l\'image associée', + 'RelatedSoundFile' => 'Fichier audio associé', + 'RelatedTo' => 'Associé à', + 'RelativeTime' => 'Temps relatif', + 'RelativeTimeUnits' => 'Unités du temps relatif', + 'RelativeTimestamp' => 'Horodatage relatif', + 'RelativeTimestampScale' => 'Echelle de l\'horodatage relatif', + 'RelativeTimestampValue' => 'Valeur de l\'horodatage relatif', + 'ReleaseButtonToUseDial' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'ReleaseDate' => 'Date de version', + 'ReleaseMode' => { + Description => 'Mode de déclenchement', + PrintConv => { + 'AE Bracketing' => 'Bracketing AE', + 'Continuous' => 'Continu', + 'Continuous High Speed' => 'Haute vitesse continue', + 'Continuous Low Speed' => 'Vitesse lente continue', + 'Contrast Bracketing' => 'Bracketing de contraste', + 'DRO Bracketing' => 'Bracketing DRO', + 'Exposure Bracketing' => 'Bracketing d\'exposition', + 'High Speed Burst' => 'Rafale haute vitesse', + 'Mirror-Up' => 'Miroir en haut', + 'Quiet' => 'Silencieux', + 'Single Frame' => 'Vue par vue', + 'Timer' => 'Minuterie', + 'WB Bracketing' => 'Bracketing de balance des blancs', + 'White Balance Bracketing' => 'Bracketing de balance des blancs', + 'n/a' => 'Non applicable', + }, + }, + 'ReleaseMode2' => { + Description => 'Mode de déclenchement 2', + PrintConv => { + 'Continuous' => 'Continu', + 'Continuous - 3D Image' => 'Continu - Image 3D', + 'Continuous - 3D Sweep Panorama' => 'Continu - Panorama 3D par balayage', + 'Continuous - Anti-Motion Blur, Hand-held Twilight' => 'Continu - Flou anti-mouvement, crépuscule à main levée', + 'Continuous - Background defocus' => 'Continu - Défocalisation de l\'arrière-plan', + 'Continuous - Burst' => 'Continu - Rafale', + 'Continuous - Burst 2' => 'Continu - Rafale 2', + 'Continuous - Exposure Bracketing' => 'Continu - Bracketing d\'exposition', + 'Continuous - HDR' => 'Continu - HDR', + 'Continuous - High Resolution Sweep Panorama' => 'Continu - Panorama par balayage haute résolution', + 'Continuous - High Sensitivity' => 'Continu - Haute Sensibilité', + 'Continuous - Multi Frame NR' => 'Continu - RB multi-photos', + 'Continuous - Speed/Advance Priority' => 'Continu - Priorité vitesse/avance', + 'Continuous - Sweep Panorama' => 'Continu - Panorama par balayage', + 'Continuous - Tele-zoom Advance Priority' => 'Continu - Télé-zoom Priorité à l\'avance', + 'Continuous Low' => 'Continu - Bas', + 'DRO or White Balance Bracketing' => 'Bracketing DRO ou balance des blancs', + 'Single Frame - Capture During Movie' => 'Image unique - Capture pendant la vidéo', + 'Single Frame - Movie Capture' => 'Image unique - Capture vidéo', + 'Single-frame - Exposure Bracketing' => 'Image unique - Bracketing d\'exposition', + 'Smile Shutter' => 'Obturateur sur sourire', + }, + }, + 'ReleaseMode3' => { + Description => 'Mode de déclenchement 3', + PrintConv => { + 'Continuous' => 'Continu', + 'Continuous - Burst' => 'Continu - Rafale', + 'Continuous - Speed/Advance Priority' => 'Continu - Priorité vitesse/avance', + 'Normal - Self-timer' => 'Normal - Retardateur', + 'Single Burst Shooting' => 'Prise de vue en rafale', + }, + }, + 'ReleaseTime' => 'Heure de version', + 'RenderingIntent' => { + Description => 'Intention de rendu', + PrintConv => { + 'ICC-Absolute Colorimetric' => 'Colorimétrique absolu', + 'Media-Relative Colorimetric' => 'Colorimétrique relatif', + 'Perceptual' => 'Perceptif', + }, + }, + 'ResampleParamsQuality' => { + PrintConv => { + 'Low' => 'Bas', + }, + }, + 'Resaved' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'Resolution' => 'Résolution de l\'image', + 'ResolutionMode' => 'Mode de résolution', + 'ResolutionUnit' => { + Description => 'Unité de résolutions', + PrintConv => { + 'None' => 'Aucune', + 'cm' => 'Pixels/cm', + 'inches' => 'pouce', + }, + }, + 'ResourceForkSize' => 'Taille du champ de ressources', + 'RetouchHistory' => { + Description => 'Historique retouche', + PrintConv => { + 'Cross Process (blue)' => 'Traitement croisé (bleu)', + 'Cross Process (green)' => 'Traitement croisé (vert)', + 'Cross Process (red)' => 'Traitement croisé (rouge)', + 'Cross Process (yellow)' => 'Traitement croisé (jaune)', + 'Cross Screen' => 'Écran croisé', + 'High Key' => 'Tons clairs', + 'Low Key' => 'Tons sombres', + 'None' => 'Aucune', + 'Sepia' => 'Sépia', + }, + }, + 'RevisionNumber' => 'Numéro de révision', + 'Rights' => 'Droits', + 'Roll' => 'Roulis', + 'RollAngle' => 'Angle de roulis', + 'Rotation' => { + PrintConv => { + '0' => '0°', + '180' => '180°', + '270' => '270°', + '90' => '90°', + 'Horizontal' => 'Horizontale', + 'Horizontal (Normal)' => 'Horizontale (normale)', + 'Horizontal (normal)' => 'Horizontale (normale)', + 'Rotate 180' => 'Rotation de 180°', + 'Rotate 270 CW' => 'Rotation antihoraire de 270°', + 'Rotate 90 CW' => 'Rotation antihoraire de 90°', + }, + }, + 'RowInterleaveFactor' => 'Facteur d\'entrelacement des lignes', + 'RowsPerStrip' => 'Nombre de rangées par bande', + 'RunTimeEpoch' => 'Temps de fonctionnement Epoch', + 'RunTimeFlags' => { + Description => 'Indicateurs de temps de fonctionnement', + PrintConv => { + 'Has been rounded' => 'A été arrondi', + 'Indefinite' => 'Indéfini', + 'Negative infinity' => 'Infinité négative', + 'Positive infinity' => 'Infinité positive', + 'Valid' => 'Valide', + }, + }, + 'RunTimeScale' => 'Échelle du temps de fonctionnement', + 'RunTimeSincePowerUp' => 'Temps de fonctionnement depuis la mise sous tension', + 'RunTimeValue' => 'Valeur du temps de fonctionnement', + 'SMaxSampleValue' => 'Valeur maxi d\'échantillon S', + 'SMinSampleValue' => 'Valeur mini d\'échantillon S', + 'SPIFFVersion' => 'Version SPIFF', + 'SR2SubIFDKey' => 'Clé du SubIFD SR2', + 'SR2SubIFDLength' => 'Longueur du SubIFD SR2', + 'SR2SubIFDOffset' => 'Décalage du SubIFD SR2', + 'SRAWQuality' => { + Description => 'Qualité sRAW', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SRActive' => { + Description => 'Réduction de bougé active', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'SRFocalLength' => 'Focale de réduction de bougé', + 'SRHalfPressTime' => 'Temps entre mesure et déclenchement', + 'SRResult' => { + Description => 'Stabilisation', + PrintConv => { + 'Not stabilized' => 'Non stabilisé', + }, + }, + 'SRawType' => 'Type sRAW', + 'SVGVersion' => 'Version SVG', + 'SafetyShift' => { + Description => 'Décalage de sécurité', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable (ISO speed)' => 'Activé (sensibilité ISO)', + 'Enable (Tv/Av)' => 'Activé (Tv/Av)', + }, + }, + 'SafetyShiftInAvOrTv' => { + Description => 'Décalage de sécurité Av ou Tv', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + }, + }, + 'SampleFormat' => { + Description => 'Format d\'échantillon', + PrintConv => { + 'Complex int' => 'Entier complexe', + 'Float' => 'Réel à virgule flottante', + 'Signed' => 'Entier signé', + 'Undefined' => 'Non défini', + 'Unsigned' => 'Entier non signé', + }, + }, + 'SampleStructure' => { + Description => 'Structure d\'échantillonnage', + PrintConv => { + 'CompressionDependent' => 'Définie dans le processus de compression', + 'Orthogonal4-2-2Sampling' => 'Orthogonale, avec les fréquences d\'échantillonnage dans le rapport 4:2:2:(4)', + 'OrthogonalConstangSampling' => 'Orthogonale, avec les mêmes fréquences d\'échantillonnage relatives sur chaque composante', + }, + }, + 'SamplesPerPixel' => 'Nombre de composantes', + 'Saturation' => { + PrintConv => { + '+1 (medium high)' => '+1 (moyennement élevée)', + '+2 (high)' => '+2 (élevée)', + '+3 (very high)' => '+3 (très élevée)', + '+4 (highest)' => '+4 (la plus élevée)', + '+4 (maximum)' => '+4 (maximale)', + '-1 (medium low)' => '-1 (moyennement faible)', + '-2 (low)' => '-2 (faible)', + '-3 (very low)' => '-3 (très faible)', + '-4 (lowest)' => '-4 (la plus faible)', + '-4 (minimum)' => '-4 (minimale)', + '0 (normal)' => '0 (normale)', + 'Acros Green Filter' => 'Filtre vert Acros', + 'Acros Red Filter' => 'Filtre rouge Acros', + 'Acros Yellow Filter' => 'Filtre jaune Acros', + 'B&W' => 'N&B', + 'B&W Green Filter' => 'Filtre vert N&B', + 'B&W Red Filter' => 'Filtre rouge N&B', + 'B&W Sepia' => 'Filtre sépia N&B', + 'B&W Yellow Filter' => 'Filtre jaune N&B', + 'Black & White' => 'Noir et blanc', + 'Film Simulation' => 'Simulation de film', + 'High' => 'Forte', + 'Low' => 'Faible', + 'Medium High' => 'Moyennement élevée', + 'Medium Low' => 'Moyennement élevée', + 'Natural' => 'Naturel', + 'None' => 'Aucune', + 'None (B&W)' => 'Aucune (N&B)', + 'Normal' => 'Normale', + 'Toning Effect' => 'Effet tonique', + 'Vintage B&W' => 'Vintage N&B', + 'Vivid' => 'Éclatant', + }, + }, + 'SaturationAdj' => 'Réglage de la saturation', + 'SaturationAdjustmentAqua' => 'Réglage de la saturation Aqua', + 'SaturationAdjustmentBlue' => 'Réglage de la saturation Bleue', + 'SaturationAdjustmentGreen' => 'Réglage de la saturation Verte', + 'SaturationAdjustmentMagenta' => 'Réglage de la saturation Magenta', + 'SaturationAdjustmentOrange' => 'Réglage de la saturation Orange', + 'SaturationAdjustmentPurple' => 'Réglage de la saturation Violette', + 'SaturationAdjustmentRed' => 'Réglage de la saturation Rouge', + 'SaturationAdjustmentYellow' => 'Réglage de la saturation Jaune', + 'SaturationAuto' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SaturationFaithful' => { + Description => 'Saturation Fidèle', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SaturationLandscape' => { + Description => 'Saturation Paysage', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SaturationMonochrome' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SaturationNeutral' => { + Description => 'Saturation Neutre', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SaturationPortrait' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SaturationStandard' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SaturationUserDef1' => { + Description => 'Saturation définie par l\\utilisateur 1', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SaturationUserDef2' => { + Description => 'Saturation définie par l\\utilisateur 2', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SaturationUserDef3' => { + Description => 'Saturation définie par l\\utilisateur 3', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ScaleFactor' => 'Facteur d\'échelle', + 'ScaleFactor35efl' => 'Facteur d\'échelle équivalent à un objectif 35 mm', + 'ScanImageEnhancer' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ScanLength' => 'Longueur de numérisation', + 'ScanMode' => 'Mode de numérisation', + 'ScanModeEnumeration' => { + Description => 'Mode de numérisation', + PrintConv => { + 'Other' => 'Autre', + }, + }, + 'ScanOptions' => 'Options de numérisation', + 'ScanSoftware' => 'Logiciel de numérisation', + 'ScanSoftwareRevisionDate' => 'Date de révision du logiciel de numérisation', + 'ScanVelocity' => 'Vitesse de numérisation', + 'ScannerFirmwareDate' => 'Date du micrologiciel du scanner', + 'ScannerFirmwareVersion' => 'Version du micrologiciel du scanner', + 'ScannerMake' => 'Fabricant du scanner', + 'ScannerModel' => 'Modèle du scanner', + 'ScannerProductID' => 'ID produit du scanner', + 'ScannerSerialNumber' => 'Numéro de série du scanner', + 'ScanningDirection' => { + Description => 'Direction de la numérisation', + PrintConv => { + 'Bottom-Top, L-R' => 'De bas en haut, de gauche à droite', + 'Bottom-Top, R-L' => 'De bas en haut, de droite à gauche', + 'L-R, Bottom-Top' => 'De gauche à droite, de bas en haut', + 'L-R, Top-Bottom' => 'De gauche à droite, de haut en bas', + 'R-L, Bottom-Top' => 'De droite à gauche, de bas en haut', + 'R-L, Top-Bottom' => 'De droite à gauche, de haut en bas', + 'Top-Bottom, L-R' => 'De haut en bas, de gauche à droite', + 'Top-Bottom, R-L' => 'De haut en bas, de droite à gauche', + }, + }, + 'Scene' => 'Scène', + 'SceneAssist' => 'Assistant Scene', + 'SceneCaptureType' => { + Description => 'Type de capture de scène', + PrintConv => { + 'Landscape' => 'Paysage', + 'Night' => 'Nocturne', + 'Other' => 'Autre', + }, + }, + 'SceneDetect' => 'Scène détectée', + 'SceneDetectData' => 'Données de scène détectée', + 'SceneMode' => { + Description => 'Mode Scène', + PrintConv => { + '2 in 1' => '2 en 1', + '3D Sweep Panorama' => 'Panorama par balayage 3D', + 'Aerial Photo' => 'Photo aérienne', + 'Anti Motion Blur' => 'Anti-flou de mouvement', + 'Aperture Priority' => 'Priorité à l\'ouverture', + 'Appetizing Food' => 'Alimentation appétissante', + 'Artistic Nightscape' => 'Paysage nocturne artistique', + 'Auction' => 'Vente aux enchères', + 'Auto' => 'Auto.', + 'Auto PICT' => 'Image auto', + 'Available Light' => 'Lumière disponible', + 'Baby' => 'Bébé', + 'Back Light' => 'Contre-jour', + 'Backlight HDR' => 'Contre-jour HDR', + 'Backlight Silhouette' => 'Silhouette à contre-jour', + 'Backlit Softness' => 'Douceur du contre-jour', + 'Beach' => 'Plage', + 'Beach & Snow' => 'Mer & Montagne ', + 'Beauty Skin' => 'Beauté de la peau', + 'Behind Glass' => 'Derrière la vitre', + 'Bird Watching' => 'Observation des oiseaux', + 'Blue Sky' => 'Ciel bleu', + 'Bright Blue Sky' => 'Ciel bleu lumineux', + 'Bulb' => 'Ampoule', + 'Candle' => 'Bougie', + 'Candlelight' => 'Lumière de bougie', + 'Children' => 'Enfants', + 'Clear Night Portrait' => 'Portrait par nuit claire', + 'Clear Nightscape' => 'Paysage par nuit claire', + 'Clear Portrait' => 'Portrait clair', + 'Clear Sports Shot' => 'Prise de vue sportive claire', + 'Clear in Backlight' => 'Clair en contre-jour', + 'Clipboard' => 'Presse-papiers', + 'Color Effects' => 'Effets de couleurs', + 'Cont. Priority AE' => 'AE priorité continue', + 'Cool Night Sky' => 'Ciel nocturne calme', + 'Creative Control' => 'Contrôle créatif', + 'Cute Dessert' => 'Dessert sympa', + 'Digital Filter' => 'Filtre numérique', + 'Digital Image Stabilization' => 'Stabilisation numérique de l\'image', + 'Distinct Scenery' => 'Paysages distincts', + 'Economy' => 'Économie', + 'Face Portrait' => 'Portrait de visage', + 'Film Grain' => 'Grain de pellicule', + 'Fireworks' => 'Feux d\'artifice', + 'Flash Burst' => 'Éclat de flash', + 'Food' => 'Alimentation', + 'Forest' => 'Forêt', + 'Freeze Animal Motion' => 'Mouvements d\'animaux figés', + 'Glass Through' => 'Verre translucide', + 'Glistening Water' => 'Eau scintillante', + 'Glittering Illuminations' => 'Illuminations scintillantes', + 'Hand-held Starlight' => 'Lumière stellaire à main levée', + 'Handheld Night Shot' => 'Photo nocturne à main levée', + 'High ISO' => 'Haute sensibilité ISO', + 'High Key' => 'Tons clairs', + 'High Sensitivity' => 'Haute Sensibilité', + 'High Speed Continuous Shooting' => 'Prise de vue en continu à haute vitesse', + 'Indoor' => 'Intérieur', + 'Intelligent Auto' => 'Auto Intelligent', + 'Intelligent Auto Plus' => 'Auto Intelligent Plus', + 'Intelligent ISO' => 'ISO intelligente', + 'Kids' => 'Enfants', + 'Landscape' => 'Paysage', + 'Landscape+Portrait' => 'Paysage+Portrait', + 'Light Trails' => 'Sentiers lumineux', + 'Low Key' => 'Tons sombres', + 'Magic Filter' => 'Filtre magique', + 'Manual' => 'Manuelle', + 'Movie' => 'Vidéo', + 'Movie Preview' => 'Prévisualisation vidéo', + 'Multi Focus Shot' => 'Prise de vue multi-focus', + 'Multiple Exposure' => 'Exposition multiple', + 'Museum' => 'Musée', + 'My Color' => 'Ma couleur', + 'My Mode' => 'Mon mode', + 'Nature Macro' => 'Macro Nature', + 'Night Landscape' => 'Paysage nocturne', + 'Night Portrait' => 'Portrait nocturne', + 'Night Scene' => 'Scène nocturne', + 'Night Scene HDR' => 'Scène HDR nocturne', + 'Night Scene Portrait' => 'Portrait de scène nocturne', + 'Night Scenery' => 'Scène nocturne', + 'Night Snap' => 'Instantané nocturne', + 'Night View/Portrait' => 'Vue/Portrait nocturne', + 'Night+Portrait' => 'Nocturne+Portrait', + 'No Flash' => 'Pas de flash', + 'Normal' => 'Normale', + 'Off' => 'Désactivée', + 'Panning' => 'Panoramique', + 'Panorama Assist' => 'Assistance panoramique', + 'Panorama Left-right' => 'Panorama gauche-droite', + 'Panorama Right-left' => 'Panorama droite-gauche', + 'Party' => 'Fête', + 'Peripheral Defocus' => 'Défocalisation périphérique', + 'Pet' => 'Animal de compagnie', + 'Photo Frame' => 'Cadre photo', + 'Pin Hole' => 'Trou d\'épingle', + 'Program' => 'Programme', + 'Quick Shutter' => 'Obturateur rapide', + 'Relaxing Tone' => 'Tonalité relaxante', + 'Romantic Sunset Glow' => 'Lueur romantique au coucher du soleil', + 'Scenery' => 'Paysage', + 'Self Portrait' => 'Autoportrait', + 'Self Portrait+Self Timer' => 'Autoportrait+Auto-Temporisation', + 'Self Protrait+Timer' => 'Autoportrait+Temporisateur', + 'Shoot & Select' => 'Photographier et sélectionner', + 'Shoot & Select1' => 'Photographier et sélectionner 1', + 'Shoot & Select2' => 'Photographier et sélectionner 2', + 'Shooting Guide' => 'Guide de prise de vue', + 'Shutter Priority' => 'Priorité à l\'obturateur', + 'Silent' => 'Silencieux', + 'Silky Skin' => 'Peau soyeuse', + 'Slow Shutter' => 'Obturateur à vitesse lente', + 'Smart Scene' => 'Scène intelligente', + 'Smile Shot' => 'Prise de vue sur sourire', + 'Snow' => 'Neige', + 'Soft Background Shot' => 'Prise de vue en arrière-plan doux', + 'Soft Image of a Flower' => 'Douce image d\'une fleur', + 'Soft Skin' => 'Peau douce', + 'Stage Lighting' => 'Éclairage de scène', + 'Starry Night' => 'Nuit étoilée', + 'Sunset' => 'Coucher de soleil', + 'Super Macro' => 'Super macro', + 'Superior Auto' => 'Auto Supérieur', + 'Surf & Snow' => 'Surf et neige', + 'Sweep Panorama' => 'Panorama par balayage', + 'Sweet Child\'s Face' => 'Doux visage d\'enfant', + 'Text' => 'Texte', + 'Transform' => 'Transformer', + 'Underwater' => 'Subaquatique', + 'Underwater Macro' => 'Macro subaquatique', + 'Underwater Snapshot' => 'Prise de vue Subaquatique', + 'Underwater Wide1' => 'Plan large sous-marin 1', + 'Underwater Wide2' => 'Plan large sous-marin 2', + 'Vivid' => 'Éclatant', + 'Vivid Sunset Glow' => 'Coucher de soleil éclatant', + 'Warm Glowing Nightscape' => 'Paysage nocturne chaud et lumineux', + 'n/a' => 'Non applicable', + }, + }, + 'SceneModeUsed' => { + Description => 'Mode scène utilisé', + PrintConv => { + 'Aperture Priority' => 'Priorité à l\'ouverture', + 'Back Light' => 'Rétro-éclairage', + 'Beach' => 'Plage', + 'Candlelight' => 'Chandelle', + 'Children' => 'Enfants', + 'Fireworks' => 'Feux d\'artifice', + 'High ISO' => 'Haute sensibilité ISO', + 'Landscape' => 'Paysage', + 'Manual' => 'Manuel', + 'Museum' => 'Musé', + 'Night Landscape' => 'Paysage nocturne', + 'Night Portrait' => 'Portrait nocturne', + 'Program' => 'Programme', + 'Shutter Priority' => 'Priorité à l\'obturateur', + 'Snow' => 'Neige', + 'Sunset' => 'Coucher de soleil', + 'Text' => 'Texte', + }, + }, + 'SceneNumber' => 'Numéro de scène', + 'SceneRecognition' => { + Description => 'Reconnaissance de scène', + PrintConv => { + 'Backlit Portrait' => 'Portrait rétro-éclairé', + 'Landscape Image' => 'Image de paysage', + 'Night Portrait' => 'Portrait nocturne', + 'Night Scene' => 'Scène nocturne', + 'Portrait Image' => 'Image de portrait', + 'Unrecognized' => 'Non reconnue', + }, + }, + 'SceneSelect' => { + Description => 'Sélection de la scène', + PrintConv => { + 'Lamp' => 'Lampe', + 'Night' => 'Nocturne', + 'Off' => 'Désactivée', + 'User 1' => 'Utilisateur 1', + 'User 2' => 'Utilisateur 2', + }, + }, + 'SceneType' => { + Description => 'Type de scène', + PrintConv => { + 'Digital Scene Generation' => 'Génération d\'une scène numérique', + 'Directly photographed' => 'Photographié directement', + 'Original Scene' => 'Scène originale', + 'Second Generation Scene' => 'Scène de deuxième génération', + }, + }, + 'SecurityClassification' => { + Description => 'Classification de sécurité', + PrintConv => { + 'Confidential' => 'Confidentiel', + 'Restricted' => 'Restreint', + 'Top Secret' => 'Trés secret', + 'Unclassified' => 'Non classifié', + }, + }, + 'SelectableAFPoint' => { + Description => 'Collimateurs AF sélectionnables', + PrintConv => { + '11 points' => '11 collimateurs', + '19 points' => '19 collimateurs', + '45 points' => '45 collimateurs', + 'Inner 9 points' => '9 collimateurs centraux', + 'Outer 9 points' => '9 collimateurs périphériques', + }, + }, + 'SelfTimer' => { + Description => 'Minuteur', + PrintConv => { + '10 s / 3 pictures' => '10 s / 3 images', + '10 s after shutter pressed' => '10 s après avoir pressé l\'obturateur', + '2 s after shutter pressed' => '2 s après avoir pressé l\'obturateur', + '3 photos after 10 s' => '3 photos après 10 s', + 'Off' => 'Désactivé', + 'Off (0)' => 'Désactivé (0)', + 'On' => 'Activé', + 'Self-timer 10 s' => 'Minuteur 10 s', + 'Self-timer 2 s' => 'Minuteur 2 s', + 'Self-timer 5 or 10 s' => 'Minuteur 5 ou 10 s', + }, + }, + 'SelfTimer2' => 'Minuteur automatique (2)', + 'SelfTimerMode' => 'Mode du minuteur', + 'SelfTimerShotCount' => 'Compteur de prise de vue du minuteur automatique', + 'SelfTimerShotInterval' => 'Intervalle du minuteur automatique', + 'SelfTimerTime' => 'Durée du minuteur automatique', + 'SensingMethod' => { + Description => 'Méthode de capture', + PrintConv => { + 'Color sequential area' => 'Capteur couleur séquentiel', + 'Color sequential linear' => 'Capteur couleur séquentiel linéaire', + 'Monochrome area' => 'Capteur monochrome', + 'Monochrome linear' => 'Capteur linéaire monochrome', + 'Not defined' => 'Non définie', + 'One-chip color area' => 'Capteur mono-puce couleur', + 'Three-chip color area' => 'Capteur tri-puces couleur', + 'Trilinear' => 'Capteur tri-linéaire', + 'Two-chip color area' => 'Capteur bi-puces couleur', + }, + }, + 'Sensitivity' => 'Sensibilité', + 'SensitivityAdjust' => 'Réglage de sensibilité', + 'SensitivityCalibrated' => 'Sensibilité calibrée', + 'SensitivitySteps' => { + Description => 'Pas de sensibilité', + PrintConv => { + '1 EV Steps' => 'Pas de 1 IL', + 'As EV Steps' => 'Comme pas IL', + }, + }, + 'SensitivityType' => { + Description => 'Type de sensibilité', + PrintConv => { + 'ISO Speed' => 'Vitesse ISO', + 'Recommended Exposure Index' => 'Index d\'exposition recommandé', + 'Recommended Exposure Index and ISO Speed' => 'Index d\'exposition et vitesse ISO recommandés', + 'Standard Output Sensitivity' => 'Sensibilité de sortie standard', + 'Standard Output Sensitivity and ISO Speed' => 'Sensibilité de sortie standard et vitesse ISO', + 'Standard Output Sensitivity and Recommended Exposure Index' => 'Sensibilité de sortie standard et index d\'exposition recommandé', + 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed' => 'Sensibilité de sortie standard, index d\'exposition et vitesse ISO recommandés', + 'Unknown' => 'Inconnu', + }, + }, + 'Sensor' => 'Capteur', + 'SensorArea' => 'Zone du capteur', + 'SensorAreas' => 'Zones du capteur', + 'SensorBitDepth' => 'Profondeur de bit du capteur', + 'SensorBlueLevel' => 'Niveau bleu du capteur', + 'SensorBottomBorder' => 'Bordure inférieure du capteur', + 'SensorCalibration' => 'Calibrage du capteur', + 'SensorCalibration_0x0404' => 'Calibrage du capteur_0x0404', + 'SensorCalibration_0x0405' => 'Calibrage du capteur_0x0405', + 'SensorCalibration_0x0406' => 'Calibrage du capteur_0x0406', + 'SensorCalibration_0x0408' => 'Calibrage du capteur_0x0408', + 'SensorCalibration_0x040f' => 'Calibrage du capteur_0x040f', + 'SensorCalibration_0x0413' => 'Calibrage du capteur_0x0413', + 'SensorCalibration_0x0414' => 'Calibrage du capteur_0x0414', + 'SensorCalibration_0x0418' => 'Calibrage du capteur_0x0418', + 'SensorCalibration_0x041c' => 'Calibrage du capteur_0x041c', + 'SensorCalibration_0x041e' => 'Calibrage du capteur_0x041e', + 'SensorCleaning' => { + Description => 'Nettoyage du capteur', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + }, + }, + 'SensorData' => 'Données du capteur', + 'SensorFieldOfViewName' => { + PrintConv => { + 'Wide' => 'Large', + }, + }, + 'SensorFullHeight' => 'Hauteur totale du capteur', + 'SensorFullWidth' => 'Largeur totale du capteur', + 'SensorHeight' => 'Hauteur du capteur', + 'SensorID' => 'Identifiant du capteur', + 'SensorImageHeight' => 'Hauteur de l\'image du capteur', + 'SensorImageWidth' => 'Largeur de l\'image du capteur', + 'SensorLeftBorder' => 'Bordure gauche du capteur', + 'SensorLeftMargin' => 'Marge gauche du capteur', + 'SensorMode' => 'Mode du capteur', + 'SensorName' => 'Nom du capteur', + 'SensorPixelSize' => 'Taille des pixels du capteur', + 'SensorRedLevel' => 'Niveau rouge du capteur', + 'SensorRightBorder' => 'Bordure droit du capteur', + 'SensorRollAngle' => 'Angle de roulis du capteur', + 'SensorSerialNumber' => 'Numéro de sérue du capteur', + 'SensorShield' => { + Description => 'Protection du capteur', + PrintConv => { + 'Closes' => 'Se ferme', + 'Stays Open' => 'Reste ouvert', + }, + }, + 'SensorSize' => 'Taille du capteur', + 'SensorTemperature' => 'Capteur de température', + 'SensorTemperature2' => 'Capteur de température 2', + 'SensorTopBorder' => 'Bordure supérieure du capteur', + 'SensorTopMargin' => 'Marge supérieure du capteur', + 'SensorType' => 'Type de capteur', + 'SensorTypeCode' => 'Code du type de senseur', + 'SensorWidth' => 'Largeur du capteur', + 'SequenceFileNumber' => 'Numéro du fichier de la séquence', + 'SequenceImageNumber' => 'Numéro de l\'image de la séquence', + 'SequenceLength' => { + Description => 'Longueur de la séquence', + PrintConv => { + '1 file' => '1 fichier', + '1 shot' => '1 prise de vue', + '10 files' => '10 fichiers', + '10 shots' => '10 prises de vue', + '12 shots' => '12 prises de vue', + '16 shots' => '16 prises de vue', + '2 files' => '2 fichiers', + '2 shots' => '2 prises de vue', + '3 files' => '3 fichiers', + '3 shots' => '3 prises de vue', + '4 shots' => '4 prises de vue', + '5 files' => '5 fichiers', + '5 shots' => '5 prises de vue', + '7 files' => '7 fichiers', + '7 shots' => '7 prises de vue', + '9 files' => '9 fichiers', + '9 shots' => '9 prises de vue', + 'Continuous' => 'Continue', + 'Continuous - Sweep Panorama' => 'Continue - Balayage du panorama', + 'Continuous - iSweep Panorama' => 'Continue - iBalayage du panorama', + }, + }, + 'SequenceNumber' => { + Description => 'Numéro de Séquence', + PrintConv => { + 'Single' => 'Unique', + 'n/a' => 'Non applicable', + }, + }, + 'SequentialShot' => { + PrintConv => { + 'None' => 'Aucune', + }, + }, + 'SerialNumber' => 'Numéro de série', + 'SerialNumberFormat' => 'Format du numéro de série ', + 'ServiceIdentifier' => 'Identificateur de service', + 'SetButtonCrossKeysFunc' => { + Description => 'Réglage touche SET/joypad', + PrintConv => { + 'Cross keys: AF point select' => 'Joypad:Sélec. collim. AF', + 'Normal' => 'Normale', + 'Set: Flash Exposure Comp' => 'SET:Cor expo flash', + 'Set: Parameter' => 'SET:Changer de paramètres', + 'Set: Picture Style' => 'SET:Style d’image', + 'Set: Playback' => 'SET:Lecture', + 'Set: Quality' => 'SET:Qualité', + }, + }, + 'SetButtonWhenShooting' => { + Description => 'Touche SET au déclenchement', + PrintConv => { + 'Change parameters' => 'Changer de paramètres', + 'Default (no function)' => 'Normal (désactivée)', + 'Disabled' => 'Désactivée', + 'Flash exposure compensation' => 'Correction expo flash', + 'ISO speed' => 'Sensibilité ISO', + 'Image playback' => 'Lecture de l\'image', + 'Image quality' => 'Changer de qualité', + 'Image size' => 'Taille de l\'image', + 'LCD monitor On/Off' => 'Écran LCD On/Off', + 'Menu display' => 'Affichage du menu', + 'Normal (disabled)' => 'Normal (désactivée)', + 'Picture style' => 'Style de l\'image', + 'Quick control screen' => 'Écran de contrôle rapide', + 'Record func. + media/folder' => 'Fonction enregistrement + média/dossier', + 'Record movie (Live View)' => 'Enr. vidéo (visée écran)', + 'White balance' => 'Balance des blancs', + }, + }, + 'SetFunctionWhenShooting' => { + Description => 'Régler la fonction lors de la prise de vue', + PrintConv => { + 'Change Parameters' => 'Changer de paramètres', + 'Change Picture Style' => 'Changer de style de l\'image', + 'Change quality' => 'Changer de qualité', + 'Default (no function)' => 'Par défaut (aucune fonction)', + 'Image replay' => 'Reprise de l\'image', + 'Menu display' => 'Affichage du menu', + }, + }, + 'ShadingCompensation' => { + Description => 'Compensation de l\'ombrage', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ShadingCompensation2' => { + Description => 'Compensation de l\'ombrage 2', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'Shadow' => 'Ombre', + 'ShadowScale' => 'Echelle d\'ombre', + 'ShadowTone' => { + Description => 'Tonalité d\'ombrage', + PrintConv => { + '+1 (medium hard)' => '+1 (moyennement dure)', + '+2 (hard)' => '+2 (dure)', + '+3 (very hard)' => '+3 (très dure)', + '+4 (hardest)' => '+4 (la plus dure)', + '-1 (medium soft)' => '-1 (moyennement douce)', + '-2 (soft)' => '-2 (douce)', + '0 (normal)' => '0 (normale)', + }, + }, + 'Shadows' => 'Ombres', + 'ShakeReduction' => { + Description => 'Réduction des tremblements', + PrintConv => { + 'Off' => 'Désactivée', + 'Off (4)' => 'Désactivée (4)', + 'Off (AA simulation off)' => 'Désactivée (simulation AA désactivée)', + 'Off (AA simulation type 1)' => 'Désactivée (simulation AA type 1)', + 'Off (AA simulation type 1) (8)' => 'Désactivée (simulation AA type 1) (8)', + 'Off (AA simulation type 2)' => 'Désactivée (simulation AA type 2)', + 'Off (AA simulation type 2) (16)' => 'Désactivée (simulation AA type 2) (16)', + 'On' => 'Activée', + 'On (135)' => 'Activée (135)', + 'On (15)' => 'Activée (15)', + 'On (7)' => 'Activée (7)', + 'On (AA simulation off)' => 'Activée (simulation AA désactivée)', + 'On (AA simulation type 1)' => 'Activée (simulation AA type 1)', + 'On (AA simulation type 2)' => 'Activée (simulation AA type 2)', + 'On (Video)' => 'Activée (Vidéo)', + 'On (mode 1)' => 'Activée (mode 1)', + 'On (mode 2)' => 'Activée (mode 2)', + 'On but Disabled' => 'Activée mais Désactivée', + }, + }, + 'ShakeReductionInfo' => 'Stabilisation', + 'Sharpness' => { + Description => 'Netteté', + PrintConv => { + '+1 (medium hard)' => '+1 (moyennement dure)', + '+2 (hard)' => '+2 (dure)', + '+3 (very hard)' => '+3 (très dure)', + '+4 (hardest)' => '+4 (la plus dure)', + '+4 (maximum)' => '+4 (maximale)', + '-1 (medium soft)' => '-1 (moyennement dure)', + '-2 (soft)' => '-2 (douce)', + '-3 (very soft)' => '-3 (très douce)', + '-4 (minimum)' => '-4 (minimale)', + '-4 (softest)' => '-4 (la plus douce)', + '0 (normal)' => '0 (normale)', + 'Film Simulation' => 'Simulation de film', + 'Hard' => 'Dure', + 'Normal' => 'Normale', + 'Sharp' => 'Dure', + 'Soft' => 'Douce', + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessAuto' => { + Description => 'Netteté Auto', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessFactor' => 'Facteur de netteté', + 'SharpnessFaithful' => { + Description => 'Netteté Fidèle', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessFrequency' => { + Description => 'Fréquence de netteté', + PrintConv => { + 'High' => 'Haute', + 'Highest' => 'La plus haute', + 'Low' => 'Basse', + 'Lowest' => 'La plus basse', + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessLandscape' => { + Description => 'Netteté Payasage', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessMonochrome' => { + Description => 'Netteté Monochrome', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessNeutral' => { + Description => 'Netteté Neutre', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessPortrait' => { + Description => 'Netteté Portrait', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessRange' => 'Plage de netteté', + 'SharpnessSetting' => 'Réglage de netteté', + 'SharpnessStandard' => { + Description => 'Netteté Standard', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessUserDef1' => { + Description => 'Netteté définie par l\'utlisateur 1', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessUserDef2' => { + Description => 'Netteté définie par l\'utlisateur 2', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'SharpnessUserDef3' => { + Description => 'Netteté définie par l\'utlisateur 3', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ShootingMode' => { + Description => 'Mode de prise de vue', + PrintConv => { + 'Aerial Photo' => 'Photo aérienne', + 'Aperture Priority' => 'Priorité à l\'ouverture', + 'Appetizing Food' => 'Alimentation appétissante', + 'Artistic Nightscape' => 'Scène artistique nocturne', + 'Baby' => 'Bébé', + 'Backlit Softness' => 'Douceur du contre-jour', + 'Beach' => 'Plage', + 'Bright Blue Sky' => 'Ciel bleu lumineux', + 'Candlelight' => 'Éclairage de Bougie', + 'Clear Night Portrait' => 'Portrait par nuit claire', + 'Clear Nightscape' => 'Scène par nuit claire', + 'Clear Portrait' => 'Portrait clair', + 'Clear Sports Shot' => 'Prise de vue sportive claire', + 'Clear in Backlight' => 'Clair en contre-jour', + 'Clipboard' => 'Presse-papiers', + 'Color Effects' => 'Effets de couleurs', + 'Cool Night Sky' => 'Ciel nocturne calme', + 'Creative Control' => 'Contrôle créatif', + 'Cute Dessert' => 'Dessert sympa', + 'Digital Filter' => 'Filtre numérique', + 'Distinct Scenery' => 'Paysage distinct', + 'Economy' => 'Économie', + 'Film Grain' => 'Grain de pellicule', + 'Fireworks' => 'Feu d\'artifice', + 'Flash Burst' => 'Éclat de flash', + 'Food' => 'Nourriture', + 'Freeze Animal Motion' => 'Mouvements d\'animaux figés', + 'Glass Through' => 'Verre translucide', + 'Glistening Water' => 'Eau scintillante', + 'Glittering Illuminations' => 'Illuminations scintillantes', + 'Handheld Night Shot' => 'Photo nocturne à main levée', + 'High Sensitivity' => 'Haute sensibilité', + 'High Speed Continuous Shooting' => 'Déclenchement continu à grande vitesse', + 'Intelligent Auto' => 'Mode Auto intelligent', + 'Intelligent Auto Plus' => 'Mode Auto Plus intelligent', + 'Intelligent ISO' => 'ISO Intelligent', + 'Manual' => 'Manuel', + 'Movie' => 'Vidéo', + 'Movie Preview' => 'Prévisualisation vidéo', + 'Multi-aspect' => 'MISMulti-aspectSING', + 'My Color' => 'Ma couleur', + 'Night Portrait' => 'Portrait nocturne', + 'Night Scenery' => 'Scène nocturne', + 'Normal' => 'Normale', + 'Panning' => 'Panoramique', + 'Panorama Assist' => 'Assistant Panorama', + 'Party' => 'Fête', + 'Peripheral Defocus' => 'Défocalisation périphérique', + 'Pet' => 'Animal domestique', + 'Photo Frame' => 'Cadre photo', + 'Pin Hole' => 'Trou d\'épingle', + 'Program' => 'Programme', + 'Relaxing Tone' => 'Tonalité relaxante', + 'Romantic Sunset Glow' => 'Lueur romantique au coucher du soleil', + 'Scenery' => 'Paysage', + 'Self Portrait' => 'Autoportrait', + 'Shutter Priority' => 'Priorité à l\'obturateur', + 'Silky Skin' => 'Peau soyeuse', + 'Snow' => 'Neige', + 'Soft Image of a Flower' => 'Douce image d\'une fleur', + 'Soft Skin' => 'Peau douce', + 'Starry Night' => 'Nuit étoilée', + 'Sunset' => 'Coucher de soleil', + 'Sweet Child\'s Face' => 'Doux visage d\'enfant', + 'Transform' => 'Transformer', + 'Underwater' => 'Subaquatique', + 'Vivid Sunset Glow' => 'Coucher de soleil éclatant', + 'Warm Glowing Nightscape' => 'Paysage nocturne chaud et lumineux', + }, + }, + 'ShootingModeSetting' => { + Description => 'Paramétrage du mode de prise de vue', + PrintConv => { + 'Continuous' => 'Continu', + 'Delayed Remote' => 'Télécommande retardée', + 'Quick-response Remote' => 'Télécommande à réponse rapide', + 'Self-timer' => 'Retardateur', + 'Single Frame' => 'Vue par vue', + }, + }, + 'ShortDocumentID' => 'ID court de document', + 'ShortOwnerName' => 'Nom abrégé du propriétaire', + 'ShortReleaseTimeLag' => { + Description => 'Inertie au déclenchement réduite', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + }, + }, + 'ShotInfoVersion' => 'Version des Infos prise de vue', + 'ShotName' => 'Nom de la prise de vue', + 'ShotNumber' => 'Numéro de la prise de vue', + 'ShotNumberSincePowerUp' => 'Numéro de la prise de vue depuis la mise sous tension', + 'ShotNumberSincePowerUp2' => 'Numéro de la prise de vue depuis la mise sous tension 2', + 'Shutter' => { + Description => 'Obturateur', + PrintConv => { + 'Silent / Electronic (0 0 0)' => 'Silencieux / électronique (0 0 0)', + }, + }, + 'Shutter-AELock' => { + Description => 'Déclencheur/Touche verr. AE', + PrintConv => { + 'AE lock/AF' => 'Verrouillage AE/autofocus', + 'AE/AF, No AE lock' => 'AE/AF, pas de verrou. AE', + 'AF/AE lock' => 'Autofocus/verrouillage AE', + 'AF/AF lock' => 'Autofocus/verrouillage AF', + 'AF/AF lock, No AE lock' => 'AF/verr.AF, pas de verr.AE', + }, + }, + 'ShutterAELButton' => { + Description => 'Déclencheur/Touche verr. AE', + PrintConv => { + 'AE lock/AF' => 'Verrouillage AE/Autofocus', + 'AE/AF, No AE lock' => 'AE/AF, pas de verrou. AE', + 'AF/AE lock stop' => 'Autofocus/Verrouillage AE', + 'AF/AF lock, No AE lock' => 'AF/verr.AF, pas de verr.AE', + }, + }, + 'ShutterButtonAFOnButton' => { + Description => 'Déclencheur/Touche AF', + PrintConv => { + 'AE lock/Metering + AF start' => 'Mémo expo/lct. mesure+AF', + 'Metering + AF start' => 'Mesure + lancement AF', + 'Metering + AF start/AF stop' => 'Mesure + lancement/arrêt AF', + 'Metering + AF start/disable' => 'Lct. mesure+AF/désactivée', + 'Metering start/Meter + AF start' => 'Lct. mesure/lct. mesure+AF', + }, + }, + 'ShutterCount' => 'Décompte des déclenchements', + 'ShutterCount2' => 'Décompte des déclenchements 2', + 'ShutterCount3' => 'Décompte des déclenchements 3', + 'ShutterCurtainHack' => { + Description => 'Hack de rideaux à volets', + PrintConv => { + '1st-curtain sync' => 'Synchro du 1er rideau', + '2nd-curtain sync' => 'Synchro du 2e rideau', + }, + }, + 'ShutterCurtainSync' => { + Description => 'Synchronisation du rideau', + PrintConv => { + '1st-curtain sync' => 'Synchronisation premier rideau', + '2nd-curtain sync' => 'Synchronisation deuxième rideau', + }, + }, + 'ShutterMode' => { + PrintConv => { + 'Aperture Priority' => 'Priorité à l\'ouverture', + }, + }, + 'ShutterReleaseButtonAE-L' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ShutterReleaseNoCFCard' => { + Description => 'Déclench. obtur. sans carte', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'ShutterSpeed' => 'Temps de pose', + 'ShutterSpeedDisplayed' => 'Vitesse d\'obturation affichée', + 'ShutterSpeedRange' => { + Description => 'Régler gamme de vitesses', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activée', + }, + }, + 'ShutterSpeedValue' => 'Vitesse d\'obturation', + 'SidecarForExtension' => 'Extension', + 'SimilarityIndex' => 'Indice de similarité', + 'SlaveFlashMeteringSegments' => 'Segments de mesure flash esclave', + 'SlideShow' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'SlowShutter' => { + Description => 'Vitesse d\'obturation lente', + PrintConv => { + 'Night Scene' => 'Nocturne', + 'None' => 'Aucune', + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'n/a' => 'Non applicable', + }, + }, + 'SlowSync' => { + Description => 'Synchronisation lente', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'SmartAlbumColor' => { + Description => 'Couleur de l\'album intelligent', + PrintConv => { + 'Black' => 'Noir', + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'Red' => 'Rouge', + 'Various' => 'Divers', + 'White' => 'Blanc', + 'Yellow' => 'Jaune', + 'n/a' => 'Non applicable', + }, + }, + 'SoftSkinEffect' => { + Description => 'Effet peau douce', + PrintConv => { + 'High' => 'Élevé', + 'Low' => 'Faible', + 'Mid' => 'Moyen', + 'Off' => 'Désactivé', + 'n/a' => 'Non applicable', + }, + }, + 'Software' => 'Logiciel', + 'SonyCropSize' => 'Taille du recadrage Sony', + 'SonyCropTopLeft' => 'Recadrage en haut à gauche Sony', + 'SonyDateTime' => 'Date et heure Sony', + 'SonyDateTime2' => 'Date et heure 2 Sony', + 'SonyExposureTime' => 'Temps d\'exposition Sony', + 'SonyExposureTime2' => 'Temps d\'exposition 2 Sony', + 'SonyFNumber' => 'Diaphragme Æ’ Sony', + 'SonyFNumber2' => 'Diaphragme Æ’ 2 Sony', + 'SonyISO' => 'ISO Sony', + 'SonyImageHeight' => 'Hauteur de l\'image Sony', + 'SonyImageHeightMax' => 'Hauteur maximale de l\'image Sony', + 'SonyImageWidth' => 'Largeur d\'image Sony', + 'SonyImageWidthMax' => 'Largeur d\'image maximale Sony', + 'SonyMaxAperture' => 'Ouverture maximale Sony', + 'SonyMaxApertureValue' => 'Valeur d\'ouverture maximale Sony', + 'SonyMinAperture' => 'Valeur d\'ouverture minimale Sony', + 'SonyModelID' => { + Description => 'Identifiant du modèle Sony', + PrintConv => { + 'DSLR-A850 (APS-C mode)' => 'DSLR-A850 (mode APS-C)', + 'DSLR-A900 (APS-C mode)' => 'DSLR-A900 (mode APS-C)', + }, + }, + 'SonyRawFileType' => { + Description => 'Type de fichier RAW Sony', + PrintConv => { + 'Sony Compressed RAW' => 'RAW Sony compressé', + 'Sony Lossless Compressed RAW' => 'RAW Sony compressé sans perte', + 'Sony Lossless Compressed RAW 2' => 'RAW 2 Sony compressé sans perte', + 'Sony Uncompressed 12-bit RAW' => 'RAW Sony 12 bits non compressé', + 'Sony Uncompressed 14-bit RAW' => 'RAW Sony 14 bits non compressé', + }, + }, + 'SonyToneCurve' => 'Courbe de tonalité Sony', + 'SourceDirectoryIndex' => 'Index du répertoire source', + 'SpatialFrequencyResponse' => 'Réponse spatiale en fréquence', + 'SpecialEffectsOpticalFilter' => { + PrintConv => { + 'None' => 'Aucune', + }, + }, + 'SpecialMode' => 'Mode spécial', + 'SpectralSensitivity' => 'Sensibilité spectrale', + 'Speed' => { + Description => 'Vitesse', + PrintConv => { + 'Fast' => 'Rapide', + 'Hardcore' => 'Matérielle', + 'Medium' => 'Moyenne', + 'Slow' => 'Lent', + }, + }, + 'SpotMeterLinkToAFPoint' => { + Description => 'Mesure spot liée au viseur AF', + PrintConv => { + 'Disable (use center AF point)' => 'Désactivée (utilise le centre du viseur AF)', + 'Enable (use active AF point)' => 'Activée (utilise le viseur AF actif)', + }, + }, + 'SpotMeteringMode' => { + Description => 'Mode de mesurage Spot', + PrintConv => { + 'AF Point' => 'Point de mise au point', + 'Center' => 'Centre', + }, + }, + 'StackedImage' => { + PrintConv => { + 'HDR1' => 'HDR 1', + 'HDR2' => 'HDR 2', + }, + }, + 'State' => 'État / Région', + 'StopsAboveBaseISO' => 'Arrêt au-dessus de l\'ISO de référence', + 'StraightenAngle' => 'Angle de redressement', + 'StreamType' => { + PrintConv => { + 'Text' => 'Texte', + }, + }, + 'StripByteCounts' => 'Nombre d\'octets dans la bande', + 'StripOffsets' => 'Décalage des bandes', + 'Sub-location' => 'Lieu', + 'SubSecCreateDate' => 'Date de création des données numériques', + 'SubSecDateTimeOriginal' => 'Date de création des données originales', + 'SubSecModifyDate' => 'Date de modification du fichier', + 'SubSecTime' => 'Fractions de seconde de l\'heure de création', + 'SubSecTimeDigitized' => 'Fractions de seconde de l\'heure de création des données numériques', + 'SubSecTimeOriginal' => 'Fractions de seconde de l\'heure de création des données originales', + 'SubSelector' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'SubTileBlockSize' => 'Taille de bloc de sous-tuile', + 'SubfileType' => { + Description => 'Type du nouveau sous-fichier', + PrintConv => { + 'Alternate reduced-resolution image' => 'Image alternative à résolution réduite', + 'Color IW44' => 'Couleurs IW44', + 'Depth map' => 'Carte de profondeur', + 'Depth map of reduced-resolution image' => 'Carte de profondeur de l\'image en résolution réduite', + 'Enhanced image data' => 'Données d\'image améliorées', + 'Full-resolution image' => 'Image en pleine résolution', + 'Grayscale IW44' => 'Niveaux de gris IW44', + 'Multi-page document' => 'Document multipage', + 'Reduced-resolution image' => 'Image en résolution réduite', + 'Semantic Mask' => 'Masque sémantique', + 'Shared component' => 'Composant partagé', + 'Single page of multi-page image' => 'Une page d\'une image multipage', + 'Single page of multi-page reduced-resolution image' => 'Une page d\'une image multipage en résolution réduite', + 'Single-page image' => 'Image sur une seule page', + 'Thumbnail image' => 'Image miniature', + 'Transparency mask' => 'Masque de transparence', + 'Transparency mask of multi-page image' => 'Masque de transparence de l\'image multipage', + 'Transparency mask of reduced-resolution image' => 'Masque de transparence de l\'image en résolution réduite', + 'Transparency mask of reduced-resolution multi-page image' => 'Masque de transparence de l\'image multipage à résolution réduite', + 'invalid' => 'invalide', + }, + }, + 'SubimageColor' => { + PrintConv => { + 'RGB' => 'RVB', + }, + }, + 'Subject' => 'Sujet', + 'SubjectArea' => 'Zone du sujet', + 'SubjectCode' => 'Code sujet', + 'SubjectDetection' => { + PrintConv => { + 'People' => 'Personnes', + }, + }, + 'SubjectDistance' => 'Distance du sujet', + 'SubjectDistanceRange' => { + Description => 'Plage de distance du sujet', + PrintConv => { + 'Close' => 'Proche', + 'Distant' => 'Lointaine', + 'Unknown' => 'Inconnue', + }, + }, + 'SubjectLocation' => 'Localisation du sujet', + 'SubjectMotion' => { + Description => 'Mouvements du sujet', + PrintConv => { + 'Erratic' => 'Erratiques', + 'Middle' => 'Moyens', + 'Steady' => 'Stables', + }, + }, + 'SubjectProgram' => { + Description => 'Programme du sujet', + PrintConv => { + 'Night portrait' => 'Portrait nocturne', + 'None' => 'Aucun', + 'Sports action' => 'Action sportive', + 'Sunset' => 'Coucher de soleil', + 'Text' => 'Texte', + }, + }, + 'SubjectReference' => 'Code de sujet', + 'Subsystem' => { + Description => 'Sous-système', + PrintConv => { + 'EFI ROM' => 'ROM EFI', + 'EFI application' => 'Application EFI', + 'EFI boot service' => 'Service de démarrage EFI', + 'EFI runtime driver' => 'Pilote d\'exécution EFI', + 'Native' => 'Natif', + 'OS/2 command line' => 'Ligne de commande OS/2', + 'POSIX command line' => 'Ligne de commande POSIX', + 'Unknown' => 'Inconnu', + 'Windows CE GUI' => 'Interface graphique Windows CE', + 'Windows GUI' => 'Interface graphique Windows', + 'Windows command line' => 'Ligne de commande Windows', + }, + }, + 'SuperMacro' => { + PrintConv => { + 'Off' => 'Désactivé', + }, + }, + 'SuperimposedDisplay' => { + Description => 'Affichage superposé', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'SupplementalCategories' => 'Catégorie d\'appoint', + 'SupplementalType' => { + Description => 'Type de supplément', + PrintConv => { + 'Main Image' => 'Non établi', + 'Rasterized Caption' => 'Titre rastérisé', + 'Reduced Resolution Image' => 'Image de résolution réduite', + }, + }, + 'SvISOSetting' => 'Réglage ISO Sv', + 'SweepPanoramaDirection' => { + Description => 'Direction du balayage panoramique', + PrintConv => { + 'Bottom to Top' => 'De bas en haut', + 'Down' => 'Vers le bas', + 'Left' => 'Vers la gauche', + 'Left to Right' => 'De gauche à droite', + 'Off' => 'Désactivé', + 'Right' => 'Vers la droite', + 'Right to Left' => 'De droite à gauche', + 'Top to Bottom' => 'De haut en bas', + 'Up' => 'Vers le haut', + }, + }, + 'SweepPanoramaFieldOfView' => 'Champ de vision panoramique par balayage', + 'SweepPanoramaSize' => { + Description => 'Taille du panorama balayé', + PrintConv => { + 'Wide' => 'Large', + }, + }, + 'SwitchToRegisteredAFPoint' => { + Description => 'Activer collimateur enregistré', + PrintConv => { + 'Assist' => 'Touche d\'assistance', + 'Assist + AF' => 'Touche d\'assistance + touche AF', + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + 'Only while pressing assist' => 'Seulement en appuyant touche d\'assistance', + }, + }, + 'System' => 'Système', + 'T4Options' => 'Bits de remplissage ajoutés', + 'T6Options' => 'Options T6', + 'TIFF-EPStandardID' => 'Identification standard TIFF-EP', + 'TTL_DA_ADown' => 'Segment de mesure flash esclave 6', + 'TTL_DA_AUp' => 'Segment de mesure flash esclave 5', + 'TTL_DA_BDown' => 'Segment de mesure flash esclave 8', + 'TTL_DA_BUp' => 'Segment de mesure flash esclave 7', + 'Tagged' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'TargetAperture' => 'Ouverture de la cible', + 'TargetCompressionRatio' => 'Taux de compression cible', + 'TargetExposureTime' => 'Temps d\'exposition de la cible', + 'TargetImageType' => { + Description => 'Type d\'image cible', + PrintConv => { + 'Real-world Subject' => 'Sujet du monde réel', + 'Written Document' => 'Document écrit', + }, + }, + 'TargetPrinter' => 'Imprimante cible', + 'Technology' => { + Description => 'Technologie', + PrintConv => { + 'Active Matrix Display' => 'Afficheur à matrice active', + 'Cathode Ray Tube Display' => 'Afficheur à tube cathodique', + 'Digital Camera' => 'Appareil photo numérique', + 'Digital Cinema Projector' => 'Projecteur cinéma numérique', + 'Digital Motion Picture Camera' => 'Caméra numérique de cinéma', + 'Dye Sublimation Printer' => 'Imprimante à sublimation thermique', + 'Electrophotographic Printer' => 'Imprimante électrophotographique', + 'Electrostatic Printer' => 'Imprimante électrostatique', + 'Film Scanner' => 'Scanner de film', + 'Flexography' => 'Flexographie', + 'Ink Jet Printer' => 'Imprimante à jet d\'encre', + 'Motion Picture Film Recorder' => 'Enregistreur de film cinématographique', + 'Motion Picture Film Scanner' => 'Scanneur de films cinématographiques', + 'Offset Lithography' => 'Lithographie offset', + 'Passive Matrix Display' => 'Afficheur à matrice passive', + 'Photo CD' => 'CD photo', + 'Photo Image Setter' => 'Cadre photo', + 'Photographic Paper Printer' => 'Imprimante à papier photo', + 'Projection Television' => 'Télévision par projection', + 'Reflective Scanner' => 'Scanner réflectif', + 'Silkscreen' => 'Sérigraphie', + 'Thermal Wax Printer' => 'Imprimante thermique à cire', + 'Video Camera' => 'Caméra vidéo', + 'Video Monitor' => 'Moniteur vidéo', + }, + }, + 'Teleconverter' => { + PrintConv => { + 'None' => 'Aucune', + }, + }, + 'TemporalIDNested' => { + Description => 'ID temporel imbriqué', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'Text' => 'Texte', + 'TextColor' => 'Couleur du texte', + 'TextComments' => 'Commentaires du texte ', + 'TextEncoding' => { + Description => 'Encodage du texte', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'TextFace' => { + Description => 'Style du texte', + PrintConv => { + 'Bold' => 'Gras', + 'Condense' => 'Condensé', + 'Extend' => 'Étendu', + 'Italic' => 'Italique', + 'Outline' => 'Contour', + 'Plain' => 'Normal', + 'Shadow' => 'Ombré', + 'Underline' => 'Souligné', + }, + }, + 'TextStamp' => { + Description => 'Tampon du texte', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'TextString' => 'Chaîne de caractères du texte', + 'TextToSpeech' => { + PrintConv => { + 'Disabled' => 'Désactivé', + 'Enabled' => 'Activé', + }, + }, + 'Three-DTrackingWatchArea' => { + PrintConv => { + 'Wide' => 'Large', + }, + }, + 'Thresholding' => 'Seuil', + 'ThumbnailFileName' => 'Nom du fichier de la vignette', + 'ThumbnailHeight' => 'Hauteur de la vignette', + 'ThumbnailImage' => 'Vignette', + 'ThumbnailImageSize' => 'Taille des miniatures', + 'ThumbnailImageValidArea' => 'Zone de validité de l\'image miniature', + 'ThumbnailLength' => 'Longueur de la vignette', + 'ThumbnailOffset' => 'Décalage de la vignette', + 'ThumbnailTIFF' => 'Vignette TIFF', + 'ThumbnailURL' => 'URL de la vignette', + 'ThumbnailWidth' => 'Hauteur de la vignette', + 'TiffMeteringImage' => 'Image de mesure TIFF', + 'TiffMeteringImageHeight' => 'Hauteur de l\'image de mesure TIFF', + 'TiffMeteringImageWidth' => 'Largeur de l\'image de mesure TIFF', + 'TileByteCounts' => 'Nombre d\'octets d\'élément', + 'TileDepth' => 'Profondeur d\'élément', + 'TileLength' => 'Longueur de l\'élément', + 'TileOffsets' => 'Décalages de l\'élément', + 'TileWidth' => 'Largeur de l\'élément', + 'Time' => 'Heure', + 'TimeCreated' => 'Heure de création', + 'TimeLapseShotNumber' => 'Nombre de prises de vue en temps réel', + 'TimeScaleParamsQuality' => { + PrintConv => { + 'Low' => 'Bas', + }, + }, + 'TimeSent' => 'Heure d\'envoi', + 'TimeSincePowerOn' => 'Temps écoulé depuis la mise sous tension', + 'TimeStamp' => 'Horodateur', + 'TimeStamp1' => 'Horodateur 1', + 'TimeZone' => { + Description => 'Fuseau horaire', + PrintConv => { + '+00:00 (London)' => '+00:00 (Londre)', + '+02:00 (Athens, Helsinki)' => '+02:00 (Athènes, Helsinki)', + '+03:00 (Moscow, Nairobi)' => '+03:00 (Moscou, Nairobi)', + '+03:30 (Tehran)' => '+03:30 (Téhéran)', + '+04:30 (Kabul)' => '+04:30 (Kaboul)', + '+05:45 (Kathmandu)' => '+05:45 (Katmandou)', + '+08:00 (Beijing, Honk Kong, Sinapore)' => '+08:00 (Pékin, Honk Kong, Singapour)', + '-01:00 (Azores)' => '-01:00 (Les Açores)', + }, + }, + 'TimeZone2' => 'Fuseau horaire 2', + 'TimeZoneCity' => { + Description => 'Zone horaire de la ville', + PrintConv => { + '(not set)' => '(non défini)', + 'Adelaide' => 'Adélaïde', + 'Azores' => 'Les Açores', + 'Cairo' => 'Le Caire', + 'Chatham Islands' => 'Iles Chatham', + 'Dubai' => 'Dubaï', + 'Kabul' => 'Kaboul', + 'Kathmandu' => 'Katmandou', + 'London' => 'Londre', + 'Moscow' => 'Moscou', + 'Newfoundland' => 'Terre-Neuve', + 'Solomon Islands' => 'Iles Salomon', + 'Tehran' => 'Téhéran', + 'Yangon' => 'Rangoun', + 'n/a' => 'Non applicable', + }, + }, + 'TimeZoneCode' => 'Code du fuseau horaire', + 'TimeZoneInfo' => 'Info du fuseau horaire', + 'TimeZoneOffset' => 'Décalage du fuseau horaire', + 'TimeZoneURL' => 'URL du fuseau horaire', + 'TimerFunctionButton' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'TimerLength' => { + Description => 'Durée du retardateur', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activée', + }, + }, + 'TimerRecording' => { + Description => 'Enregistrement par minuterie', + PrintConv => { + 'Focus Bracketing' => 'Bracketing de mise au point', + 'Off' => 'Désactivé', + 'Stop-motion Animation' => 'Animation en stop motion', + 'Time Lapse' => 'Lapse de temps', + }, + }, + 'Title' => 'Titre', + 'ToneComp' => 'Correction de tonalité', + 'ToneCurve' => { + Description => 'Courbe de ton', + PrintConv => { + 'Manual' => 'Manuelle', + }, + }, + 'ToneCurveActive' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'ToneCurveProperty' => { + PrintConv => { + 'Shot Settings' => 'Paramétrage prise de vue', + }, + }, + 'ToneCurveX' => 'Courbe de tonalité X', + 'ToneCurveY' => 'Courbe de tonalité Y', + 'ToneCurves' => 'Courbes de tonalité', + 'ToneLevel' => { + Description => 'Niveau de tonalité', + PrintConv => { + 'Highlights' => 'Lumineux', + 'Midtones' => 'Moyen', + 'Shadows' => 'Ombres', + }, + }, + 'ToningEffect' => { + Description => 'Effet tonifiant', + PrintConv => { + 'B&W' => 'N&B', + 'Blue' => 'Bleu', + 'Blue-green' => 'Bleu-vert', + 'Color' => 'Coleur', + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Off' => 'Désactivé', + 'Purple' => 'Violet', + 'Purple-blue' => 'Violet-bleu', + 'Red' => 'Rouge', + 'Red-purple' => 'Rouge-violet', + 'Sepia' => 'Sépia', + 'Yellow' => 'Jaune', + 'n/a' => 'Non applicable', + }, + }, + 'ToningEffectAuto' => { + Description => 'Effet de tonalité Auto', + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Purple' => 'Violet', + 'Sepia' => 'Sépia', + 'n/a' => 'Non applicable', + }, + }, + 'ToningEffectFaithful' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ToningEffectLandscape' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ToningEffectMonochrome' => { + Description => 'Effet de tonalité monochrome ', + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Purple' => 'Violet', + 'Sepia' => 'Sépia', + 'n/a' => 'Non applicable', + }, + }, + 'ToningEffectNeutral' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ToningEffectPortrait' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ToningEffectStandard' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'ToningEffectUserDef1' => { + Description => 'Effet de tonalité personnalisé 1', + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Purple' => 'Violet', + 'Sepia' => 'Sépia', + 'n/a' => 'Non applicable', + }, + }, + 'ToningEffectUserDef2' => { + Description => 'Effet de tonalité personnalisé 2', + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Purple' => 'Violet', + 'Sepia' => 'Sépia', + 'n/a' => 'Non applicable', + }, + }, + 'ToningEffectUserDef3' => { + Description => 'Effet de tonalité personnalisé 3', + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'None' => 'Aucun', + 'Purple' => 'Violet', + 'Sepia' => 'Sépia', + 'n/a' => 'Non applicable', + }, + }, + 'ToningSaturation' => 'Tonalité de la saturation', + 'TouchAE' => { + Description => 'AE Tactile', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'TransferFunction' => 'Fonction de transfert', + 'TransferRange' => 'Intervalle de transfert', + 'Transformation' => { + PrintConv => { + 'Horizontal (normal)' => 'Horizontale (normale)', + 'Mirror horizontal' => 'Mise en miroir horizontal', + 'Mirror horizontal and rotate 270 CW' => 'Mise en miroir horizontal et rotation antihoraire de 270°', + 'Mirror horizontal and rotate 90 CW' => 'Mise en miroir horizontal et rotation antihoraire de 90°', + 'Mirror vertical' => 'Mise en miroir vertical)', + 'Rotate 180' => 'Rotation de 180°', + 'Rotate 270 CW' => 'Rotation antihoraire de 270°', + 'Rotate 90 CW' => 'Rotation antihoraire de 90°', + }, + }, + 'TransmissionReference' => 'Référence transmission', + 'TransparencyIndicator' => 'Indicateur de transparence', + 'TrapIndicator' => 'Indicateur de piège', + 'Trapped' => { + Description => 'Piégé', + PrintConv => { + 'False' => 'Faux', + 'True' => 'Vrai', + 'Unknown' => 'Inconnu', + }, + }, + 'TravelDay' => 'Date du Voyage', + 'TvExposureTimeSetting' => 'Réglage de temps de pose Tv', + 'URL' => 'URL ', + 'USMLensElectronicMF' => { + Description => 'MF électronique à objectif USM', + PrintConv => { + 'Always turned off' => 'Toujours débrayé', + 'Disable after one-shot AF' => 'Désactivée après One-Shot AF', + 'Disable in AF mode' => 'Désactivée en mode AF', + 'Enable after one-shot AF' => 'Activée après AF One-Shot', + 'Turns off after one-shot AF' => 'Débrayé après One-Shot AF', + 'Turns on after one-shot AF' => 'Activé après One-Shot AF', + }, + }, + 'Uncompressed' => { + Description => 'Non.comprimé', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'UncompressedQuickTime' => 'QuickTime non compressé', + 'UncompressedSize' => 'Taille non compressée', + 'UncompressedTextLength' => 'Longueur du texte non compressé', + 'UniqueCameraModel' => 'Nom unique de modèle d\'appareil', + 'UniqueDocumentID' => 'ID unique de document', + 'UniqueFileIdentifier' => 'ID unique du fichier', + 'UniqueID' => 'ID unique', + 'UniqueObjectName' => 'Nom Unique d\'Objet', + 'Unknown' => 'Inconnu', + 'Unsharp1Color' => { + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'RGB' => 'RVB', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + }, + }, + 'Unsharp2Color' => { + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'RGB' => 'RVB', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + }, + }, + 'Unsharp3Color' => { + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'RGB' => 'RVB', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + }, + }, + 'Unsharp4Color' => { + PrintConv => { + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'RGB' => 'RVB', + 'Red' => 'Rouge', + 'Yellow' => 'Jaune', + }, + }, + 'UnsharpCount' => 'Compteur d\'unsharp', + 'UnsharpMask' => { + Description => 'Masque d\'unsharp', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'Urgency' => { + Description => 'Urgence', + PrintConv => { + '0 (reserved)' => '0 (réservé pour utilisation future)', + '1 (most urgent)' => '1 (très urgent)', + '5 (normal urgency)' => '5 (normalement urgent)', + '8 (least urgent)' => '8 (moins urgent)', + '9 (user-defined priority)' => '9 (réservé pour utilisation future)', + }, + }, + 'UsableMeteringModes' => { + Description => 'Modes de mesure utilisables', + PrintConv => { + 'Disable' => 'Désactivés', + 'Enable' => 'Activées', + }, + }, + 'UsableShootingModes' => { + Description => 'Modes de prises de vue utilisables', + PrintConv => { + 'Disable' => 'Désactivés', + 'Enable' => 'Activés', + }, + }, + 'Usage' => 'Utilisation', + 'UsageRightsMessage' => 'Message relatif aux droits d\'utilisation', + 'UsageTerms' => 'Conditions d\'Utilisation', + 'UseDialWithoutHold' => { + Description => 'Utilisation de la fonction d\'appel sans attente', + PrintConv => { + 'Off' => 'Désactivée', + 'On' => 'Activée', + }, + }, + 'UserCollection' => 'Collection utilisateur', + 'UserComment' => 'Commentaire de l\'utilisateur', + 'UserCustom1' => 'Personnalisé par l\'utilisateur 1', + 'UserCustom2' => 'Personnalisé par l\'utilisateur 2', + 'UserData' => 'Données utilisateur', + 'UserData01' => 'Données utilisateur 01', + 'UserData02' => 'Données utilisateur 02', + 'UserData03' => 'Données utilisateur 03', + 'UserData04' => 'Données utilisateur 04', + 'UserData05' => 'Données utilisateur 05', + 'UserData06' => 'Données utilisateur 06', + 'UserData07' => 'Données utilisateur 07', + 'UserData08' => 'Données utilisateur 08', + 'UserData09' => 'Données utilisateur 09', + 'UserData10' => 'Données utilisateur 10', + 'UserData11' => 'Données utilisateur 11', + 'UserData12' => 'Données utilisateur 12', + 'UserData13' => 'Données utilisateur 13', + 'UserData14' => 'Données utilisateur 14', + 'UserData15' => 'Données utilisateur 15', + 'UserData16' => 'Données utilisateur 16', + 'UserData17' => 'Données utilisateur 17', + 'UserData18' => 'Données utilisateur 18', + 'UserData19' => 'Données utilisateur 19', + 'UserData20' => 'Données utilisateur 20', + 'UserData21' => 'Données utilisateur 21', + 'UserData22' => 'Données utilisateur 22', + 'UserData23' => 'Données utilisateur 23', + 'UserDataMode' => 'Mode des données utilisateur', + 'UserDef1PictureStyle' => { + Description => 'Style d’image définit par l’utilisateur 1', + PrintConv => { + 'Faithful' => 'Fidèle', + 'Landscape' => 'Paysage', + 'Neutral' => 'Neutre', + }, + }, + 'UserDef2PictureStyle' => { + Description => 'Style d’image définit par l’utilisateur 2', + PrintConv => { + 'Faithful' => 'Fidèle', + 'Landscape' => 'Paysage', + 'Neutral' => 'Neutre', + }, + }, + 'UserDef3PictureStyle' => { + Description => 'Style d’image définit par l’utilisateur 3', + PrintConv => { + 'Faithful' => 'Fidèle', + 'Landscape' => 'Paysage', + 'Neutral' => 'Neutre', + }, + }, + 'VRDOffset' => 'Décalage VRD', + 'VRDVersion' => 'Version VRD', + 'VRInfo' => 'Information stabilisateur', + 'VRInfoVersion' => 'Info Version VR', + 'VRMode' => { + Description => 'Mode VR', + PrintConv => { + 'Active' => 'Actif', + 'Off' => 'Désactivé', + 'On (1)' => 'Activé (1)', + }, + }, + 'VR_0x66' => { + PrintConv => { + 'Off' => 'Désactivé', + }, + }, + 'ValidAFPoints' => 'Points de mise au point automatique valides', + 'ValidBits' => 'Bits valides', + 'VariProgram' => 'Variprogramme', + 'VariableLowPassFilter' => { + Description => 'Filtre passe-bas variable', + PrintConv => { + 'High' => 'Haut', + 'Off' => 'Désactivé', + 'n/a' => 'Non applicable', + }, + }, + 'VerticalAFOnButton' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'VerticalFuncButton' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'VerticalFuncButtonPlaybackMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'VerticalFuncButtonPlusDials' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'VerticalFuncPlusDials' => { + PrintConv => { + 'Active D-Lighting' => 'D-Lighting actif', + }, + }, + 'VerticalMovieAFOnButton' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'VerticalMovieFuncButton' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'VerticalMultiSelector' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'VibrationReduction' => { + Description => 'Reduction des vibrations', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'n/a' => 'Non applicable', + }, + }, + 'VideoCardGamma' => 'Gamma de la carte vidéo', + 'VideoFrameRate' => { + Description => 'Fréquence d\'images vidéo', + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'VideoOrientation' => { + Description => 'Orientation vidéo', + PrintConv => { + 'Horizontal (normal)' => 'Horizontale (normale)', + 'Mirror horizontal' => 'Mise en miroir horizontal', + 'Mirror horizontal and rotate 270 CW' => 'Mise en miroir horizontal et retournée de 270° dans le sens antihoraire', + 'Mirror horizontal and rotate 90 CW' => 'Mise en miroir horizontal et retournée de 90° dans le sens antihoraire', + 'Mirror vertical' => 'Mise en miroir vertical', + 'Rotate 180' => 'Retournée de 180°', + 'Rotate 270 CW' => 'Retournée de 270° dans le sens antihoraire', + 'Rotate 90 CW' => 'Retournée de 90° dans le sens antihoraire', + }, + }, + 'ViewInfoDuringExposure' => { + Description => 'Infos viseur pendant exposition', + PrintConv => { + 'Disable' => 'Désactivé', + 'Enable' => 'Activé', + }, + }, + 'ViewfinderWarning' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ViewingCondDesc' => 'Description des conditions de visionnage', + 'ViewingCondIlluminant' => 'Illuminant des conditions de visionnage', + 'ViewingCondIlluminantType' => 'Type d\'illuminant des conditions de visionnage', + 'ViewingCondSurround' => 'Environnement des conditions de visionnage', + 'ViewingMode2' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'VignetteControl' => { + Description => 'Controle du vignettage', + PrintConv => { + 'High' => 'Haut', + 'Low' => 'Bas', + 'Normal' => 'Normale', + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'VignetteControlIntensity' => 'Intensité du contrôle des vignettes', + 'VignetteCorrectionAlreadyApplied' => 'Correction de vignette déjà appliquée', + 'VignetteMidpoint' => 'Point central de la vignette', + 'Vignetting' => { + Description => 'Vignettage', + PrintConv => { + 'High' => 'Haut', + 'Low' => 'Bas', + 'Medium' => 'Moyen', + 'Off' => 'Désactivé', + }, + }, + 'VignettingCorrParams' => 'Paramètres de correction du vignettage', + 'VignettingCorrVersion' => 'Version de la correction du vignettage', + 'VignettingCorrection' => { + Description => 'Correction du vignettage', + PrintConv => { + 'No correction params available' => 'Aucun paramètre de correction disponible', + 'Off' => 'Désactivée', + 'n/a' => 'Non applicable', + }, + }, + 'VignettingParams' => 'Paramètres de vignettage', + 'VignettingSetting' => 'Réglages du vignettage', + 'VintageStrength' => 'Force du vintage', + 'VisualColor' => { + Description => 'Couleur visuelle', + PrintConv => { + 'Color' => 'Couleur', + }, + }, + 'VoiceMemo' => { + Description => 'Mémo vocal', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'WBAdjBlueBalance' => 'Balance des blancs Réglage de la balance bleue', + 'WBAdjColorTemp' => 'Balance des blancs Réglage de la température de la couleur', + 'WBAdjLighting' => { + Description => 'Balance des blancs Réglage de la lumière', + PrintConv => { + 'Daylight (cloudy)' => 'Lumière du jour (2)', + 'Daylight (direct sunlight)' => 'Lumière du jour (0)', + 'Daylight (shade)' => 'Lumière du jour (1)', + 'Flash (FL-G1 filter)' => 'Flash (Filtre FL-G1)', + 'Flash (FL-G2 filter)' => 'Flash (Filtre FL-G2)', + 'Flash (TN-A1 filter)' => 'Flash (Filtre FL-TN-A1)', + 'Flash (TN-A2 filter)' => 'Flash (Filtre FL-TN-A2)', + 'High Color Rendering Fluorescent (3700K)' => 'Fluorescent à haut rendu des couleurs (3700K)', + 'High Color Rendering Fluorescent (5000K)' => 'Fluorescent à haut rendu des couleurs (5000K)', + 'High Color Rendering Fluorescent (cool white)' => 'Fluorescent à haut rendu des couleurs (blanc froid)', + 'High Color Rendering Fluorescent (daylight)' => 'Fluorescent à haut rendu des couleurs (lumière du jour)', + 'High Color Rendering Fluorescent (warm white)' => 'Fluorescent à haut rendu des couleurs (blanc chaud)', + 'None' => 'Aucune', + 'Sodium Vapor Lamps' => 'Lampes à vapeur de sodium', + 'Standard Fluorescent (3700K)' => 'Fluorescent standard (3700K)', + 'Standard Fluorescent (5000K)' => 'Fluorescent standard (5000K)', + 'Standard Fluorescent (cool white)' => 'Fluorescent standard (blanc froid)', + 'Standard Fluorescent (daylight)' => 'Fluorescent standard (lumière du jour)', + 'Standard Fluorescent (high temperature mercury vapor)' => 'Fluorescent standard (vapeur de mercure à haute température)', + 'Standard Fluorescent (warm white)' => 'Fluorescent standard (blanc chaud)', + }, + }, + 'WBAdjMode' => { + Description => 'Mode de réglage de la balance des blancs ', + PrintConv => { + 'Calculate Automatically' => 'Calculer automatiquement', + 'Recorded Value' => 'Valeur enregistrée', + 'Underwater' => 'Sous l\'eau', + 'Use Gray Point' => 'Utiliser le point gris', + 'Use Temperature' => 'Utiliser la température', + }, + }, + 'WBAdjRGGBLevels' => 'Balance des blancs Réglage des niveaux RVVB', + 'WBAdjRedBalance' => 'Balance des blancs Réglage de la balance rouge', + 'WBAdjTemperature' => 'Balance des blancs Réglage de la température', + 'WBBlueLevel' => 'Niveau Bleu Balance des Blancs', + 'WBBracketMode' => { + Description => 'Mode Bracketing de la balance des blancs', + PrintConv => { + 'Off' => 'Désactivé', + 'On (shift AB)' => 'Activé (changement AB)', + 'On (shift GM)' => 'Activé (changement GM)', + }, + }, + 'WBBracketValueAB' => 'Balance des blancs Valeur de bracketing AB', + 'WBBracketValueGM' => 'Balance des blancs Valeur de bracketing GM', + 'WBFineTuneActive' => { + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'WBGreenLevel' => 'Niveau Vert Balance des Blancs', + 'WBMediaImageSizeSetting' => { + Description => 'Réglage de balance des blancs + taille d\'image', + PrintConv => { + 'LCD monitor' => 'Écran LCD', + 'Rear LCD panel' => 'Panneau LCD arrière', + }, + }, + 'WBRedLevel' => 'Niveau Rouge Balance des Blancs', + 'WBShiftAB' => 'Décalage Balance des Blancs AB', + 'WBShiftAB_GM' => 'Décalage Balance des Blancs AB_GM', + 'WBShiftAB_GM_Precise' => 'Décalage Balance des Blancs AB_Précis', + 'WBShiftCreativeControl' => 'Changement du Contrôle créatif de la balance des blancs', + 'WBShiftGM' => 'Décalage Balance Blancs vert-magenta', + 'WBShiftIntelligentAuto' => 'Décalage Balance des Blancs Auto intelligent', + 'WBType1' => { + Description => 'Type de balance des blancs 1', + PrintConv => { + 'Cloudy' => 'Nuageux', + 'Cool White Fluorescent' => 'Blanc froid fluorescent', + 'Day White Fluorescent' => 'Blanc fluorescent de jour', + 'Daylight' => 'Lumière du jour', + 'Daylight Fluorescent' => 'Lumière du jour fluorescent', + 'Fine Weather' => 'Beau temps', + 'ISO Studio Tungsten' => 'ISO Studio Tungstène', + 'Other' => 'Autre', + 'Shade' => 'Ombre', + 'Standard Light A' => 'Lumière standard A', + 'Standard Light B' => 'Lumière standard B', + 'Standard Light C' => 'Lumière standard C', + 'Tungsten (Incandescent)' => 'Tungstène (Incandescent)', + 'Unknown' => 'Inconnu', + 'Warm White Fluorescent' => 'Blanc chaud fluorescent', + 'White Fluorescent' => 'Blanc fluorescent', + }, + }, + 'WB_GBRGLevels' => 'Balance des Blancs des niveaux VBRV', + 'WB_GLevel' => 'Niveau balance des blancs G', + 'WB_GLevel3000K' => 'Niveau balance des blancs G 3000K', + 'WB_GLevel3300K' => 'Niveau balance des blancs G 3300K', + 'WB_GLevel3600K' => 'Niveau balance des blancs G 3600K', + 'WB_GLevel3900K' => 'Niveau balance des blancs G 3900K', + 'WB_GLevel4000K' => 'Niveau balance des blancs G 4000K', + 'WB_GLevel4300K' => 'Niveau balance des blancs G 4300K', + 'WB_GLevel4500K' => 'Niveau balance des blancs G 4500K', + 'WB_GLevel4800K' => 'Niveau balance des blancs G 4800K', + 'WB_GLevel5300K' => 'Niveau balance des blancs G 5300K', + 'WB_GLevel6000K' => 'Niveau balance des blancs G 6000K', + 'WB_GLevel6600K' => 'Niveau balance des blancs G 6600K', + 'WB_GLevel7500K' => 'Niveau balance des blancs G 7500K', + 'WB_GRBGLevels' => 'Niveau balance des Blancs des niveaux VRBV', + 'WB_GRBLevels' => 'Balance des Blancs des niveaux VRB', + 'WB_GRBLevelsAuto' => 'Balance des Blancs Auto des niveaux VRB', + 'WB_GRBLevelsStandard' => 'Balance des Blancs Standard des niveaux VRB', + 'WB_GRGBLevels' => 'Balance des Blancs des niveaux VRVB', + 'WB_GRGBLevelsAuto' => 'Balance des Blancs Auto des niveaux VRVB', + 'WB_RBGGLevels' => 'Niveau balance des blancs RBVV', + 'WB_RBLevels' => 'Niveaux balance des blancs RB', + 'WB_RBLevels1' => 'Niveaux balance des blancs RB 1', + 'WB_RBLevels2' => 'Niveaux balance des blancs RB 2', + 'WB_RBLevels3' => 'Niveaux balance des blancs RB 3', + 'WB_RBLevels3000K' => 'Niveaux balance des blancs RB 3000K', + 'WB_RBLevels3300K' => 'Niveaux balance des blancs RB 3300K', + 'WB_RBLevels3500K' => 'Niveaux balance des blancs RB 3500K', + 'WB_RBLevels3600K' => 'Niveaux balance des blancs RB 3600K', + 'WB_RBLevels3900K' => 'Niveaux balance des blancs RB 3800K', + 'WB_RBLevels4' => 'Niveaux balance des blancs RB 4', + 'WB_RBLevels4000K' => 'Niveaux balance des blancs RB 4000K', + 'WB_RBLevels4300K' => 'Niveaux balance des blancs RB 4300K', + 'WB_RBLevels4500K' => 'Niveaux balance des blancs RB 4500K', + 'WB_RBLevels4800K' => 'Niveaux balance des blancs RB 4800K', + 'WB_RBLevels5' => 'Niveaux balance des blancs RB 5', + 'WB_RBLevels5300K' => 'Niveaux balance des blancs RB 5300K', + 'WB_RBLevels6' => 'Niveaux balance des blancs RB 6', + 'WB_RBLevels6000K' => 'Niveaux balance des blancs RB 6000K', + 'WB_RBLevels6500K' => 'Niveaux balance des blancs RB 6500K', + 'WB_RBLevels6600K' => 'Niveaux balance des blancs RB 6600K', + 'WB_RBLevels7' => 'Niveaux balance des blancs RB 7', + 'WB_RBLevels7500K' => 'Niveaux balance des blancs RB 7500K', + 'WB_RBLevelsAuto' => 'Niveaux balance des blancs RB Automatique', + 'WB_RBLevelsCWB1' => 'Niveaux balance des blancs RB CWB 1', + 'WB_RBLevelsCWB2' => 'Niveaux balance des blancs RB CWB 2', + 'WB_RBLevelsCWB3' => 'Niveaux balance des blancs RB CWB 3', + 'WB_RBLevelsCWB4' => 'Niveaux balance des blancs RB CWB 4', + 'WB_RBLevelsCloudy' => 'Niveaux balance des blancs RB nuageux', + 'WB_RBLevelsShade' => 'Balance des Blancs des niveaux RB ombre', + 'WB_RBLevelsTungsten' => 'Balance des Blancs des niveaux RB tungstène', + 'WB_RGBGLevels' => 'Balance des Blancs des niveaux RVBV', + 'WB_RGBLevels' => 'Balance des Blancs des niveaux RVB', + 'WB_RGBLevels1' => 'Balance des Blancs des niveaux RVB 1', + 'WB_RGBLevels2' => 'Balance des Blancs des niveaux RVB 2', + 'WB_RGBLevels2500K' => 'Balance des Blancs des niveaux RVB 2500K', + 'WB_RGBLevels3' => 'Balance des Blancs des niveaux RVB 3', + 'WB_RGBLevels3200K' => 'Balance des Blancs des niveaux RVB 3200K', + 'WB_RGBLevels4' => 'Balance des Blancs des niveaux RVB 4', + 'WB_RGBLevels4500K' => 'Balance des Blancs des niveaux RVB 4500K', + 'WB_RGBLevels5' => 'Balance des Blancs des niveaux RVB 5', + 'WB_RGBLevels6' => 'Balance des Blancs des niveaux RVB 6', + 'WB_RGBLevels6000K' => 'Balance des Blancs des niveaux RVB 6000K', + 'WB_RGBLevels7' => 'Balance des Blancs des niveaux RVB 7', + 'WB_RGBLevels8500K' => 'Balance des Blancs des niveaux RVB 8500K', + 'WB_RGBLevelsAsShot' => 'Balance des blancs des niveaux RVB tels que capturés', + 'WB_RGBLevelsCloudy' => 'Balance des Blancs des niveaux RVB nuageux', + 'WB_RGBLevelsDaylight' => 'Balance des Blancs des niveaux RVB Lumière du jour', + 'WB_RGBLevelsFlash' => 'Balance des Blancs des niveaux RVB flash', + 'WB_RGBLevelsFluorescent' => 'Balance des Blancs des niveaux RVB fluorescent', + 'WB_RGBLevelsFluorescentM1' => 'Balance des Blancs des niveaux RVB Fluorescent M1', + 'WB_RGBLevelsFluorescentP1' => 'Balance des Blancs des niveaux RVB Fluorescent P1', + 'WB_RGBLevelsFluorescentP2' => 'Balance des Blancs des niveaux RVB Fluorescent P2', + 'WB_RGBLevelsShade' => 'Balance des Blancs des niveaux RVB ombre', + 'WB_RGBLevelsTungsten' => 'Balance des Blancs des niveaux RVB tungstène', + 'WB_RGGBLevels' => 'Balance des blancs des niveaux RVVB', + 'WB_RGGBLevelsAsShot' => 'Balance des blancs des niveaux RVVB tel que capturé', + 'WB_RGGBLevelsAuto' => 'Balance des blancs des niveaux RVVB automatiques', + 'WB_RGGBLevelsBlack' => 'Balance des Blancs des niveaux RVVB noirs', + 'WB_RGGBLevelsCloudy' => 'Balance des Blancs des niveaux RVVB nuageux', + 'WB_RGGBLevelsCustom' => 'Balance des Blancs des niveaux RVVB personnalisés', + 'WB_RGGBLevelsCustom1' => 'Balance des Blancs des niveaux RVVB personnalisés 1', + 'WB_RGGBLevelsCustom2' => 'Balance des Blancs des niveaux RVVB personnalisés 2', + 'WB_RGGBLevelsDaylight' => 'Balance des Blancs des niveaux RVVB lumière du jour', + 'WB_RGGBLevelsFlash' => 'Balance des Blancs des niveaux RVVB flash', + 'WB_RGGBLevelsFluorescent' => 'Balance des Blancs des niveaux RVVB fluorescent', + 'WB_RGGBLevelsFluorescentD' => 'Balance des Blancs des niveaux RVVB fluorescent', + 'WB_RGGBLevelsFluorescentN' => 'Balance des Blancs des niveaux RVVB fluo N', + 'WB_RGGBLevelsFluorescentW' => 'Balance des Blancs des niveaux RVVB fluo W', + 'WB_RGGBLevelsKelvin' => 'Balance des Blancs des niveaux RVVB Kelvin', + 'WB_RGGBLevelsMeasured' => 'Balance des Blancs des niveaux RVVB mesurés', + 'WB_RGGBLevelsPC1' => 'Balance des Blancs des niveaux RVVB PC 1', + 'WB_RGGBLevelsPC2' => 'Balance des Blancs des niveaux RVVB PC 2', + 'WB_RGGBLevelsPC3' => 'Balance des Blancs des niveaux RVVB PC 3', + 'WB_RGGBLevelsShade' => 'Balance des Blancs des niveaux RVVB ombre', + 'WB_RGGBLevelsTungsten' => 'Balance des Blancs des niveaux RVVB tungstène', + 'WCSProfiles' => 'Profil Windows Color System', + 'Warning' => 'Attention', + 'Watched' => { + Description => 'Regardé', + PrintConv => { + 'No' => 'Non', + 'Yes' => 'Oui', + }, + }, + 'WaterDepth' => 'Profondeur de l\'eau', + 'WatercolorFilter' => { + Description => 'Filtre aquatique', + PrintConv => { + 'Off' => 'Désactivé', + }, + }, + 'Watermark' => 'Filigrane', + 'WatermarkType' => 'Type de filigrane', + 'WatermarkURL' => 'URL du filigrane', + 'WebP_Flags' => { + Description => 'Indicateurs WebP', + PrintConv => { + 'EXIF' => 'Exif', + 'ICC Profile' => 'Profil ICC', + }, + }, + 'WebStatement' => 'Déclaration sur internet', + 'Webpage' => 'Page internet', + 'WhiteBalance' => { + Description => 'Balance des blancs', + PrintConv => { + 'Auto' => 'Equilibrage automatique des blancs', + 'Black & White' => 'Monochrome', + 'Cloudy' => 'Temps nuageux', + 'Color Temperature/Color Filter' => 'Temp. Couleur / Filtre couleur', + 'Cool White Fluorescent' => 'Fluorescente type soft', + 'Custom' => 'Personnalisée', + 'Custom 1' => 'Personnalisée 1', + 'Custom 2' => 'Personnalisée 2', + 'Custom 3' => 'Personnalisée 3', + 'Custom 4' => 'Personnalisée 4', + 'Day White Fluorescent' => 'Fluorescente type blanc', + 'Daylight' => 'Lumière du jour', + 'Daylight Fluorescent' => 'Fluorescente type jour', + 'Fluorescent' => 'Fluorescente', + 'Manual' => 'Manuelle', + 'Manual Temperature (Kelvin)' => 'Température de couleur (Kelvin)', + 'Shade' => 'Ombre', + 'Tungsten' => 'Tungstène (lumière incandescente)', + 'Unknown' => 'Inconnu', + 'User-Selected' => 'Sélectionnée par l\'utilisateur', + 'Warm White Fluorescent' => 'Fluorescent blanc chaud', + 'White Fluorescent' => 'Fluorescent blanc', + }, + }, + 'WhiteBalance0' => 'Balance des blancs 0', + 'WhiteBalance1' => 'Balance des blancs 1', + 'WhiteBalance2' => { + Description => 'Balance des blancs 2', + PrintConv => { + '3000K (Tungsten light)' => '3000K (Lumière au tungstène)', + '3600K (Tungsten light-like)' => '3600K (Similaire à la lumière du tungstène)', + '4000K (Cool white fluorescent)' => '4000K (Fluorescent blanc froid)', + '4500K (Neutral white fluorescent)' => '4500K (Fluorescent blanc neutre)', + '5300K (Fine Weather)' => '5300K (Beau temps)', + '6000K (Cloudy)' => '6000K (Nuageux)', + '6600K (Daylight fluorescent)' => '6600K (Lumière du jour fluorescente)', + '7500K (Fine Weather with Shade)' => '7500K (Beau temps avec de l\'ombre)', + 'Auto (Keep Warm Color Off)' => 'Auto (Maintien de la couleur chaude désactivé)', + 'Auto Setup' => 'Configuration automatique', + 'Custom WB 1' => 'Balance des blancs personnalisée 1', + 'Custom WB 2' => 'Balance des blancs personnalisée 2', + 'Custom WB 3' => 'Balance des blancs personnalisée 3', + 'Custom WB 4' => 'Balance des blancs personnalisée 4', + 'One Touch WB 1' => 'Balance des blancs par simple pression 1', + 'One Touch WB 2' => 'Balance des blancs par simple pression 2', + 'One Touch WB 3' => 'Balance des blancs par simple pression 3', + 'One Touch WB 4' => 'Balance des blancs par simple pression 3', + 'Underwater' => 'Sous-marine', + 'White Fluorescent' => 'Fluorescent blanc', + }, + }, + 'WhiteBalanceAdj' => { + Description => 'Ajustement de la balance des blancs', + PrintConv => { + 'Cloudy' => 'Temps nuageux', + 'Daylight' => 'Lumière du jour', + 'Fluorescent' => 'Fluorescente', + 'Manual (Click)' => 'Manuel (Clic)', + 'Off' => 'Désactivé', + 'On' => 'Activé', + 'Shade' => 'Ombre', + 'Shot Settings' => 'Paramétrage prise de vue', + 'Tungsten' => 'Tungstène (lumière incandescente)', + }, + }, + 'WhiteBalanceAutoAdjustment' => { + Description => 'Ajustement automatique de la balance des blancs', + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'WhiteBalanceBias' => 'Décalage de Balance des blancs', + 'WhiteBalanceBlue' => 'Balance des blancs Bleue', + 'WhiteBalanceBracket' => 'Bracket de la balance des blancs', + 'WhiteBalanceBracketing' => { + Description => 'Bracketing de la balance des blancs', + PrintConv => { + 'High' => 'Haut', + 'Low' => 'Bas', + 'Off' => 'Désactivé', + }, + }, + 'WhiteBalanceButtonPlaybackMode' => { + PrintConv => { + 'HDR Overlay' => 'Recouvrement HDR', + }, + }, + 'WhiteBalanceDetected' => { + PrintConv => { + 'n/a' => 'Non applicable', + }, + }, + 'WhiteBalanceFineTune' => 'Balance des blancs - Réglage fin', + 'WhiteBalanceMode' => { + Description => 'Mode de balance des blancs', + PrintConv => { + 'Auto (Cloudy)' => 'Auto (nuageux)', + 'Auto (Day White Fluorescent)' => 'Auto (fluo jour)', + 'Auto (Daylight Fluorescent)' => 'Auto (fluo lum. jour)', + 'Auto (Daylight)' => 'Auto (lumière du jour)', + 'Auto (Flash)' => 'Auto (flash)', + 'Auto (Shade)' => 'Auto (ombre)', + 'Auto (Tungsten)' => 'Auto (tungstène)', + 'Auto (White Fluorescent)' => 'Auto (fluo blanc)', + 'Unknown' => 'Inconnu', + 'User-Selected' => 'Sélectionnée par l\'utilisateur', + }, + }, + 'WhiteBalanceRGB' => 'Balance des blancs RVB', + 'WhiteBalanceRed' => 'Balance des blancs Rouge', + 'WhiteBalanceSet' => { + Description => 'Réglage de balance des blancs', + PrintConv => { + 'Cloudy' => 'Temps nuageux', + 'Day White Fluorescent' => 'Fluorescent blanc jour', + 'Daylight' => 'Lumière du jour', + 'Daylight Fluorescent' => 'Fluorescente type jour', + 'Manual' => 'Manuelle', + 'Set Color Temperature 1' => 'Température de couleur définie 1', + 'Set Color Temperature 2' => 'Température de couleur définie 2', + 'Set Color Temperature 3' => 'Température de couleur définie 3', + 'Shade' => 'Ombre', + 'Tungsten' => 'Tungstène (lumière incandescente)', + 'White Fluorescent' => 'Fluorescent blanc', + 'n/a' => 'Non applicable', + }, + }, + 'WhiteBalanceTemperature' => 'Température de la balance des blancs', + 'WhiteLevel' => 'Niveau blanc', + 'WhitePoint' => 'Chromaticité du point blanc', + 'WhiteSampleBits' => 'Bits d\'échantillons blancs', + 'WhiteSampleHeight' => 'Hauteur de l\'échantillon blanc', + 'WhiteSampleLeftBorder' => 'Bordure gauche de l\'échantillon blanc', + 'WhiteSampleTopBorder' => 'Bordure supérieure de l\'échantillon blanc', + 'WhiteSampleWidth' => 'Largeur de l\'échantillon blanc', + 'Wide' => 'Large', + 'WideRange' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'WorkColorSpace' => { + PrintConv => { + 'Adobe RGB' => 'Adobe RVB', + 'Apple RGB' => 'Apple RVB', + }, + }, + 'WorldTime' => 'Fuseau horaire', + 'WorldTimeLocation' => { + Description => 'Référentiel de l\'heure mondiale', + PrintConv => { + 'Home' => 'Domicile', + 'Hometown' => 'Ville d\'origine', + }, + }, + 'Writer-Editor' => 'Auteur de la légende / description', + 'X3FillLight' => 'Lumière de remplissage X3F', + 'XClipPathUnits' => 'Unités de la valeur horizontale du chemin d\'accès de l\'extrait', + 'XMP' => 'Métadonnées XMP', + 'XMPToolkit' => 'Boîte à outils XMP', + 'XPAuthor' => 'Auteur', + 'XPComment' => 'Commentaire', + 'XPKeywords' => 'Mots-clés', + 'XPSubject' => 'Sujet', + 'XPTitle' => 'Titre', + 'XPosition' => 'Position horizontale', + 'XResolution' => 'Résolution horizontale de l\'image', + 'XTransLayout' => 'Mise en page X-Trans', + 'YCbCrCoefficients' => 'Coefficients de la matrice de transformation de l\'espace colorimétrique', + 'YCbCrPositioning' => { + Description => 'Positionnement YCbCr', + PrintConv => { + 'Centered' => 'Centré', + 'Co-sited' => 'Côte à côte', + }, + }, + 'YCbCrSubSampling' => 'Sous-échantillonnage YCbCr', + 'YClipPathUnits' => 'Unités de la valeur verticale du chemin d\'accès de l\'extrait', + 'YPosition' => 'Position verticale', + 'YResolution' => 'Résolution verticale de l\'image', + 'Year' => 'Année', + 'ZebraPatternToneRange' => { + Description => 'Gamme de tons du motif hachuré', + PrintConv => { + 'Highlights' => 'Lumineuse', + 'Midtones' => 'Moyenne', + 'Off' => 'Désactivée', + }, + }, + 'ZoneMatching' => { + Description => 'Ajustage de la zone', + PrintConv => { + 'High Key' => 'Tons clairs', + 'ISO Setting Used' => 'Désactivée', + 'Low Key' => 'Tons sombres', + }, + }, + 'ZoneMatchingOn' => { + PrintConv => { + 'Off' => 'Désactivé', + 'On' => 'Activé', + }, + }, + 'ZoomCenter' => 'Centre du zoom', + 'ZoomFactor' => 'Facetur de zoom ', + 'ZoomPosition' => 'Position zoom ', + 'ZoomSourceWidth' => 'Largeur de la source de zoom', + 'ZoomStepCount' => 'Nombre de pas du zoom', + 'ZoomTargetWidth' => 'Largeur de la cible du zoom', +); + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::fr.pm - ExifTool French language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke, Bernard Guillotin, Jean Glasser, Jean Piquemal, Harry +Nizard, Alphonse Philippe and Philippe Bonnaure (GraphicConverter) for +providing this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/it.pm b/ExifTool/lib/Image/ExifTool/Lang/it.pm new file mode 100644 index 0000000..888fcc1 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/it.pm @@ -0,0 +1,7921 @@ +#------------------------------------------------------------------------------ +# File: it.pm +# +# Description: ExifTool Italian language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::it; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.14'; + +%Image::ExifTool::Lang::it::Translate = ( + 'A100DataOffset' => 'Offset dati A100', + 'AAFManufacturerID' => 'ID AAF produttore', + 'ACoordOfBottomRightCorner' => 'Una coord in basso a destra', + 'ACoordOfTopRightCorner' => 'Una coord in alto a destra', + 'AEAperture' => 'Apertura esposizione automatica', + 'AEBAutoCancel' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AEBSequenceAutoCancel' => { + PrintConv => { + '-,0,+/Disabled' => '-,0,+/Disabilitato', + '-,0,+/Enabled' => '-,0,+/Abilitato', + '0,-,+/Disabled' => '0,-,+/Disabilitato', + '0,-,+/Enabled' => '0,-,+/Abilitato', + }, + }, + 'AEExposureTime' => 'Durata esposizione automatica', + 'AEFlags' => { + Description => 'Flag esposizione automatica', + PrintConv => { + 'AE lock' => 'Blocco esposizione automatica', + 'Aperture wide open' => 'Diaframma molto aperto', + 'Flash recommended?' => 'Flash consigliato?', + }, + }, + 'AELExposureIndicator' => { + PrintConv => { + 'Not Indicated' => 'Non indicato', + }, + }, + 'AELock' => { + Description => 'Blocco esposizione automatica', + PrintConv => { + 'Off' => 'Spento', + 'On' => 'Acceso', + }, + }, + 'AELockButton' => { + Description => 'Pulsante blocco esposizione automatica', + PrintConv => { + 'Flash Off' => 'Flash spento', + 'None' => 'Nessuno', + 'Preview' => 'Anteprima', + 'Virtual Horizon' => 'Orizzonte virtuale', + }, + }, + 'AELockButtonPlusDials' => { + PrintConv => { + 'Choose Image Area' => 'Seleziona area immagine', + 'None' => 'Nessuno', + }, + }, + 'AEMeteringMode' => { + PrintConv => { + 'Center-weighted average' => 'Media centrale ponderata', + 'Multi-segment' => 'Multi zona', + }, + }, + 'AEMicroadjustment' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'AEProgramMode' => { + PrintConv => { + 'Kids' => 'Bambini', + 'Landscape' => 'Orizzontale', + 'No Flash' => 'No flash', + 'Portrait' => 'Verticale', + 'Program' => 'Programma', + 'Sunset' => 'Tramonto', + 'Text' => 'Testo', + }, + }, + 'AESetting' => { + PrintConv => { + 'Exposure Compensation' => 'Compensazione esposizione', + }, + }, + 'AE_ISO' => 'ISO esposizione automatica', + 'AFAndMeteringButtons' => { + PrintConv => { + 'No function' => 'Nessuna funzione', + }, + }, + 'AFAperture' => 'Diaframma AF', + 'AFAreaIllumination' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AFAreaMode' => { + Description => 'Modo AF', + PrintConv => { + 'Dynamic Area' => 'Area Dinamica', + 'Dynamic Area (closest subject)' => 'Area Dinamica più Vicina al Soggetto', + 'Group Dynamic' => 'Gruppo Dinamico', + 'Local' => 'Locale', + 'Off (Manual Focus)' => 'Spento (focus manuale)', + 'Single Area' => 'Area Singola', + }, + }, + 'AFAssist' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AFDuringLiveView' => { + PrintConv => { + 'Enable' => 'Abilita', + 'Quick mode' => 'Modo veloce', + }, + }, + 'AFFineTune' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AFIlluminator' => { + PrintConv => { + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'AFInfo' => 'Modo AF', + 'AFMode' => { + Description => 'Modo AF', + PrintConv => { + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'AFOnAELockButtonSwitch' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'AFPoint' => { + Description => 'Punto AF', + PrintConv => { + '(none)' => '(nessuno)', + 'Bottom' => 'Basso', + 'Center' => 'Centro', + 'Far Left' => 'Tutto a sinistra', + 'Far Right' => 'Tutto a destra', + 'Left' => 'Sinistra', + 'Lower-left' => 'Inferiore sinistro', + 'Lower-right' => 'Inferiore destro', + 'Mid-left' => 'Centro/Sinistra', + 'Mid-right' => 'Centro/Destra', + 'None' => 'Nessuno', + 'Right' => 'Destra', + 'Right (horizontal)' => 'Destra (orizzontale)', + 'Right (vertical)' => 'Destra (verticale)', + 'Top' => 'Alto', + 'Upper-left' => 'Superiore sinistro', + 'Upper-right' => 'Superiore destro', + }, + }, + 'AFPointActivationArea' => { + PrintConv => { + 'Expanded' => 'Espanso', + }, + }, + 'AFPointAreaExpansion' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'AFPointBrightness' => { + PrintConv => { + 'Low' => 'Basso', + 'Normal' => 'Normale', + }, + }, + 'AFPointDisplayDuringFocus' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AFPointIllumination' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AFPointMode' => { + PrintConv => { + 'Fixed Center' => 'Centro fisso', + 'Select' => 'Seleziona', + }, + }, + 'AFPointRegistration' => { + PrintConv => { + 'Bottom' => 'Basso', + 'Center' => 'Centro', + 'Extreme Left' => 'Tutto a Sinistra', + 'Extreme Right' => 'Tutto a Destra', + 'Left' => 'Sinistra', + 'Right' => 'Destra', + 'Top' => 'Alto', + }, + }, + 'AFPointSelected' => { + PrintConv => { + 'Bottom' => 'Basso', + 'Center' => 'Centro', + 'Far Left' => 'Tutto a sinistra', + 'Far Right' => 'Tutto a destra', + 'Fixed Center' => 'Centro fisso', + 'Left' => 'Sinistra', + 'None' => 'Nessuno', + 'Right' => 'Destra', + 'Top' => 'Alto', + }, + }, + 'AFPointSelected2' => { + PrintConv => { + 'Center' => 'Centro', + 'Left' => 'Sinistra', + 'Right' => 'Destra', + }, + }, + 'AFPointSelection' => { + PrintConv => { + '11 Points' => '11 punti', + }, + }, + 'AFPointSelectionMethod' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'AFPoints' => { + PrintConv => { + 'Center' => 'Centro', + 'Left' => 'Sinistra', + 'Right' => 'Destra', + }, + }, + 'AFPointsInFocus' => { + Description => 'Punti a fuoco', + PrintConv => { + 'Bottom' => 'Basso', + 'Center' => 'Centro', + 'Far Left' => 'Tutto a sinistra', + 'Far Right' => 'Tutto a destra', + 'Left' => 'Sinistra', + 'Lower-left' => 'Inferiore sinistro', + 'Lower-right' => 'Inferiore destro', + 'None' => 'Nessuno', + 'Right' => 'Destra', + 'Top' => 'Alto', + 'Upper-left' => 'Superiore sinistro', + 'Upper-right' => 'Superiore destro', + }, + }, + 'AFPointsInFocus1D' => 'Punti a fuoco 1D', + 'AFPointsInFocus5D' => { + Description => 'Punti a fuoco 5D', + PrintConv => { + 'Center' => 'Centro', + 'Left' => 'Sinistra', + 'Right' => 'Destra', + }, + }, + 'AFPointsUnknown1' => { + PrintConv => { + 'Center' => 'Centro', + 'Left' => 'Sinistra', + 'Right' => 'Destra', + }, + }, + 'AFPointsUnknown2' => { + PrintConv => { + 'Center' => 'Centro', + 'Left' => 'Sinistra', + 'Right' => 'Destra', + }, + }, + 'AFPointsUsed' => { + PrintConv => { + 'Bottom' => 'Basso', + 'Center' => 'Centro', + 'Far Left' => 'Tutto a sinistra', + 'Far Right' => 'Tutto a destra', + 'Lower-left' => 'Inferiore sinistro', + 'Lower-right' => 'Inferiore destro', + 'Top' => 'Alto', + 'Upper-left' => 'Superiore sinistro', + 'Upper-right' => 'Superiore destro', + }, + }, + 'AFSearch' => { + PrintConv => { + 'Not Ready' => 'Non pronto', + 'Ready' => 'Pronto', + }, + }, + 'AFWithShutter' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AIServoImagePriority' => { + PrintConv => { + '1: AF, 2: Drive speed' => '1: AF, 2: drive speed', + '1: AF, 2: Tracking' => '1: AF, 2: puntamento', + '1: Release, 2: Drive speed' => '1: rilascio, 2: drive speed', + '1: Release, 2: Tracking' => '1: rilascio, 2: puntamento', + }, + }, + 'AIServoTrackingSensitivity' => { + PrintConv => { + 'Fast' => 'Veloce', + }, + }, + 'APEVersion' => 'Versione APE', + 'ARMIdentifier' => 'ID ARM', + 'ARMVersion' => 'Versione ARM', + 'AToB0' => 'Da A a B0', + 'AToB1' => 'Da A a B1', + 'AToB2' => 'Da A a B2', + 'AberrationCorrectionDistance' => 'Distanza della correzione di aberrazione', + 'About' => 'Informazioni su', + 'AbsPeakAudioFilePath' => 'Percorso file audio del picco assoluto', + 'AbsoluteChannelDisplayScale' => 'Scala assoluta canale', + 'Abstract' => 'Sommario', + 'AbstractPriorCodeSequence' => 'Sequenza codice precedente astratto', + 'AbstractPriorValue' => 'Valore codice precedente astratto', + 'AccessDate' => 'Data di accesso', + 'AccessionNumber' => 'Numero di adesione', + 'AccessoryCode' => 'Codice accessorio', + 'AccessoryType' => 'Tipo accessorio', + 'AccountName' => 'Nome account', + 'AccountingReferenceNumber' => 'Riferimento contabile', + 'AcqreconRecordChecksum' => 'Checksum del record AcqRecon', + 'AcquiredImageAreaDoseProduct' => 'Dose prodotto dell\'area acquisita', + 'AcquisitionComments' => 'Commenti acquisizione', + 'AcquisitionContextDescription' => 'Descrizione contesto acquisizione', + 'AcquisitionContextSequence' => 'Sequenza contesto acquisizione', + 'AcquisitionContrast' => 'Contrasto acquisizione', + 'AcquisitionDate' => 'Data acquisizione', + 'AcquisitionDateTime' => 'Data/ora acquisizione', + 'AcquisitionDeviceProcessingCode' => 'Codice processo di acquisizione del dispositivo', + 'AcquisitionDeviceProcessingDescr' => 'Descrizione processo di acquisizione del dispositivo', + 'AcquisitionDeviceTypeCodeSequence' => 'Sequenza codici tipo di acquisizione del dispositivo', + 'AcquisitionDuration' => 'Durata acquisizione', + 'AcquisitionEndConditionData' => 'Dati condizione fine acquisizione', + 'AcquisitionGroupLength' => 'Lunghezza gruppo di acquisizione', + 'AcquisitionIndex' => 'Indice acquisizione', + 'AcquisitionMatrix' => 'Matrice acquisizione', + 'AcquisitionNumber' => 'Numero acquisizione', + 'AcquisitionProtocolDescription' => 'Descrizione protocollo di acquisizione', + 'AcquisitionProtocolName' => 'Nome protocollo di acquisizione', + 'AcquisitionStartCondition' => 'Condizione iniziale acquisizione', + 'AcquisitionStartConditionData' => 'Dati condizione iniziale acquisizione', + 'AcquisitionTerminationCondition' => 'Condizione finale acquisizione', + 'AcquisitionTime' => 'Ora acquisizione', + 'AcquisitionTimeDay' => 'Ora acquisizione - Giorno', + 'AcquisitionTimeMonth' => 'Ora acquisizione - Mese', + 'AcquisitionTimeSynchronized' => 'Ora acquisizione sincronizzata', + 'AcquisitionTimeYear' => 'Ora acquisizione - Anno', + 'AcquisitionTimeYearMonth' => 'Ora acquisizione - Anno mese', + 'AcquisitionTimeYearMonthDay' => 'Ora acquisizione - Anno mese giorno', + 'AcquisitionType' => 'Tipo acquisizione', + 'AcquisitionsInSeries' => 'Acquisizioni in serie', + 'AcquisitionsInStudy' => 'Acquisizioni in esame', + 'AcrossScanSpatialResolution' => 'Attraverso risoluzione di scansione spaziale', + 'ActionAdvised' => { + Description => 'Azione consigliata', + PrintConv => { + 'Object Append' => 'Allega oggetto', + 'Object Kill' => 'Distruzione oggetto', + 'Object Reference' => 'Riferimento oggetto', + 'Object Replace' => 'Sostituzione oggetto', + }, + }, + 'ActiveArea' => 'Area attiva', + 'ActiveD-Lighting' => { + Description => 'D-Lighting attivo', + PrintConv => { + 'Extra High' => 'Molto alto', + 'High' => 'Alto', + 'Low' => 'Basso', + 'Normal' => 'Normale', + 'Off' => 'Spento', + 'On' => 'Acceso', + }, + }, + 'ActiveD-LightingMode' => { + Description => 'Modalità D-Lighting attiva', + PrintConv => { + 'Extra High' => 'Molto alto', + 'High' => 'Alto', + 'Low' => 'Basso', + 'Normal' => 'Normale', + 'Off' => 'Spento', + 'Unchanged' => 'Immutato', + }, + }, + 'ActiveFormatDescriptor' => 'Descrittore formato attivo', + 'ActiveLinesperFrame' => 'Linee per quadro attuali', + 'ActiveSamplesperLine' => 'Linee per campione attuali', + 'ActiveSourceDiameter' => 'Diametro sorgente attuale', + 'ActiveSourceLength' => 'Lunghezza sorgente attuale', + 'ActiveState' => 'Stato attivo', + 'Actor' => 'Attore', + 'ActualCardiacTriggerDelayTime' => 'Tempo di ritardo corrente trigger cardiaco', + 'ActualCompensation' => 'Compensazione corrente', + 'ActualFrameDuration' => 'Durata frame attuale', + 'ActualHumanPerformersSequence' => 'Sequenza attuale esecutori umani', + 'ActualReceiveGainAnalog' => 'Guadagno analogico in ricezione attuale', + 'ActualReceiveGainDigital' => 'Guadagno digitale in ricezione attuale', + 'ActualRespiratoryTriggerDelayTime' => 'Tempo di ritardo corrente trigger respiratorio', + 'ActualSeriesDataTimeStamp' => 'Marca temporale serie di dati attuali', + 'Ad-ID' => 'ID Ad', + 'AdaptiveMapFormat' => 'Formato mappa adattativa', + 'AddAspectRatioInfo' => { + Description => 'Aggiunta info rapporto di aspetto', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AddIntermediateSequence' => 'Aggiunta sequenza intermedia', + 'AddNearSequence' => 'Aggiunta sequenza vicina', + 'AddOriginalDecisionData' => { + Description => 'Aggiunta dati decisione originale', + PrintConv => { + 'Off' => 'Spento', + 'On' => 'Acceso', + }, + }, + 'AddOtherSequence' => 'Aggiunta altra sequenza', + 'AddPower' => 'Aggiunta potenza', + 'AdditionalDrugSequence' => 'Sequenza farmaci addizionali', + 'AdditionalModelInformation' => 'Ulteriori informazioni modello', + 'AdditionalPatientHistory' => 'Ulteriore storia del paziente', + 'Address' => 'Indirizzo', + 'AddressLine' => 'Linea indirizzo', + 'AddressNameValueSets' => 'Gruppi di valori nome indirizzo', + 'AddressSets' => 'Gruppi di indirizzi', + 'AddressTrial' => 'Studio indirizzo', + 'AdjustmentMode' => 'Modo adattamento', + 'AdministrationRouteCodeSequence' => 'Sequenza codici percorso di amministrazione', + 'AdmissionID' => 'ID ammissione', + 'AdmittingDate' => 'Data ammissione', + 'AdmittingDiagnosesCodeSequence' => 'Sequenza codici diagnosi ammissione', + 'AdmittingDiagnosesDescription' => 'Descrizioni diagnosi ammissione', + 'AdmittingTime' => 'Ora ammissione', + 'AdobeCMType' => 'Tipo Adobe CM', + 'AdoptedNeutral' => 'Adottato neutro', + 'AdultContentWarning' => { + Description => 'Avviso contenuto per adulti', + PrintConv => { + 'Adult Content Warning Required' => 'Avviso contenuto per adulti richiesto', + 'Not Required' => 'Non richiesto', + 'Unknown' => 'Sconosciuto', + }, + }, + 'AdvancedContentEncryption' => 'Crittografia avanzata del contenuto', + 'AdvancedMutualExcl' => 'Mutua esclusione avanzata', + 'AdvancedRaw' => { + Description => 'Raw avanzato', + PrintConv => { + 'Off' => 'Spento', + 'On' => 'Acceso', + }, + }, + 'AdvancedSceneMode' => { + Description => 'Modo scena avanzato', + PrintConv => { + 'Architecture' => 'Architettura', + 'Color Select' => 'Selezione colore', + 'Creative Macro' => 'Macro creativo', + 'Creative Night Scenery' => 'Scenario notturno creativo', + 'Creative Portrait' => 'Ritratto creativo', + 'Creative Scenery' => 'Scenario creativo', + 'Creative Sports' => 'Sport creativi', + 'Cross Process' => 'Cross process', + 'Dynamic Art' => 'Arte dinamica', + 'Dynamic Monochrome' => 'Monocromatico dinamico', + 'Elegant' => 'Elegante', + 'Expressive' => 'Espressivo', + 'Flower' => 'Fiore', + 'HDR Art' => 'HDR artistico', + 'HDR B&W' => 'HDR in bianco e nero', + 'High Dynamic' => 'High dynamic', + 'High Key' => 'High key', + 'Illuminations' => 'Illuminazioni', + 'Indoor Portrait' => 'Ritratto al chiuso', + 'Indoor Sports' => 'Sport al chiuso', + 'Low Key' => 'Low key', + 'Minature' => 'Minuatura', + 'Monochrome' => 'Monocromatico', + 'Nature' => 'Natura', + 'Objects' => 'Oggetti', + 'Off' => 'Spento', + 'Outdoor Portrait' => 'Ritratto all\'aperto', + 'Outdoor Sports' => 'Sport all\'aperto', + 'Pure' => 'Puro', + 'Retro' => 'Retrò', + 'Sepia' => 'Seppia', + 'Soft' => 'Morbido', + 'Star' => 'Stella', + 'Toy Effect' => 'Effetto giocattolo', + }, + }, + 'AdvancedSceneType' => 'Tipo scena avanzato', + 'AdvantageCompOverflow' => 'Overflow componente avanzato', + 'AdvantageCompUnderflow' => 'Underflow componente avanzato', + 'AdventRevision' => 'Revisione arrivo', + 'AdventScale' => 'Scala arrivo', + 'AdvertisingMaterialReference' => 'Riferimento materiale pubblicitario', + 'Advisory' => 'Consultivo', + 'AlbumArtistSortOrder' => 'Ordinamento album-artista', + 'AlbumSortOrder' => 'Ordinamento album', + 'AliasLayerMetadata' => 'Livello metadati alias', + 'AlphaByteCount' => 'Numero byte trasparenza', + 'AlphaDataDiscard' => { + Description => 'Scarto dati trasparenza', + PrintConv => { + 'Flexbits Discarded' => 'Flexbit scartati', + 'Full Resolution' => 'Risoluzione piena', + 'HighPass Frequency Data Discarded' => 'Dati in frequenza passa-alto scartati', + 'Highpass and LowPass Frequency Data Discarded' => 'Dati in frequenza passa-alto e passa-basso scartati', + }, + }, + 'AlphaInterlace' => { + PrintConv => { + 'Noninterlaced' => 'Non interlacciato', + }, + }, + 'AlphaOffset' => 'Scostamento trasparenza', + 'AlphaTransparency' => { + PrintConv => { + 'Not Inverted' => 'Non invertito', + }, + }, + 'AmbienceSelection' => { + PrintConv => { + 'Vivid' => 'Vivace', + }, + }, + 'AnalogBalance' => 'Bilanciamento analogico', + 'Annotation' => 'Annotazione', + 'Annotations' => 'Annotazioni', + 'Anti-Blur' => { + PrintConv => { + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'AntiAliasStrength' => 'Forza antialiasing', + 'Aperture' => 'Diaframma', + 'ApertureDisplayed' => 'Diaframma mostrato', + 'ApertureRange' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'ApertureRingUse' => { + PrintConv => { + 'Prohibited' => 'Proibito', + }, + }, + 'ApertureValue' => 'Apertura diaframma', + 'AppleStoreCountry' => { + PrintConv => { + 'Italy' => 'Italia', + 'Japan' => 'Giappone', + 'Norway' => 'Norvegia', + 'Portugal' => 'Portogallo', + 'Sweden' => 'Svezia', + 'United Kingdom' => 'Regno Unito', + 'United States' => 'Stati Uniti', + }, + }, + 'ApplicationNotes' => 'Note applicazione', + 'ApplicationRecordVersion' => 'Versione Registrazione Applicazione', + 'ApplyShootingMeteringMode' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'ArtFilter' => { + PrintConv => { + 'Fish Eye' => 'Fish-eye', + 'Fragmented' => 'Frammentato', + 'Gentle Sepia' => 'Seppia leggero', + 'Off' => 'Spento', + 'Reflection' => 'Riflessione', + }, + }, + 'ArtFilterEffect' => { + PrintConv => { + 'Fish Eye' => 'Fish-eye', + 'Fragmented' => 'Frammentato', + 'Gentle Sepia' => 'Seppia leggero', + 'Off' => 'Spento', + 'Reflection' => 'Riflessione', + 'Star Light' => 'Luce stelle', + }, + }, + 'ArtMode' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'Artist' => 'Persona che ha creato l\'immagine', + 'Artist2' => 'Artista 2', + 'ArtistURL' => 'URL artista', + 'AsShotICCProfile' => 'Profilo ICC allo scatto', + 'AsShotNeutral' => 'Neutrale allo scatto', + 'AsShotPreProfileMatrix' => 'Matrice pre-profilo allo scatto', + 'AsShotProfileName' => 'Nome profilo allo scatto', + 'AsShotWhiteXY' => 'Bilanciamento del bianco X-T allo scatto', + 'AspectRatio' => 'Rapporto immagine', + 'AspectRatioType' => { + Description => 'Tipo rapporto immagine', + PrintConv => { + 'Fixed' => 'Fisso', + }, + }, + 'AspectRatioX' => 'Rapporto immagine X', + 'AspectRatioY' => 'Rapporto immagine Y', + 'AssistButtonFunction' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'Attachments' => 'Allegati', + 'Audio' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'AudioAttributes' => { + Description => 'Attributi audio', + PrintConv => { + 'Encrypted' => 'Crittografato', + }, + }, + 'AudioBytes' => 'Byte audio', + 'AudioChannelType' => 'Tipo canale audio', + 'AudioChannels' => 'Canali audio', + 'AudioCodec' => 'Codec audio', + 'AudioCodecDescription' => 'Descrizione codec audio', + 'AudioCodecID' => { + Description => 'ID codec audio', + PrintConv => { + 'QDesign Music' => 'Musica QDesign', + 'Unknown -' => 'Sconosciuto -', + }, + }, + 'AudioCodecInfo' => 'Info codec audio', + 'AudioCodecName' => 'Nome codec audio', + 'AudioCompression' => 'Compressione audio', + 'AudioCompressionAlgorithm' => 'Algoritmo di compressione audio', + 'AudioFormat' => 'Formato audio', + 'AudioLayer' => 'Livello audio', + 'AudioStreamType' => { + PrintConv => { + 'Reserved' => 'Riservato', + }, + }, + 'AudioType' => { + Description => 'Tipo Audio', + PrintConv => { + 'Text Only' => 'Solo testo', + }, + }, + 'Author' => 'Autore', + 'AuthorsPosition' => 'Posizione dell\'autore', + 'AutoAperture' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AutoBracketModeM' => { + PrintConv => { + 'Flash Only' => 'Solo flash', + }, + }, + 'AutoBracketSet' => { + PrintConv => { + 'Exposure' => 'Esposizione', + 'Flash Only' => 'Solo flash', + }, + }, + 'AutoBracketing' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AutoDistortionControl' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AutoExposureBracketing' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AutoFP' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AutoFocus' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AutoISO' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AutoLightingOptimizer' => { + PrintConv => { + 'Enable' => 'Abilita', + 'Low' => 'Basso', + 'Off' => 'Spento', + 'Strong' => 'Forte', + 'n/a' => 'n/d', + }, + }, + 'AutoLightingOptimizerOn' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'AutoRedEye' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'AutoRotate' => { + PrintConv => { + 'None' => 'Nessuno', + 'Rotate 180' => 'Ruota di 180°', + 'Rotate 270 CW' => 'Ruota di 270° in senso orario', + 'Rotate 90 CW' => 'Ruota di 90° senso orario', + 'n/a' => 'n/d', + }, + }, + 'AuxiliaryLens' => 'Obiettivo Ausiliario', + 'AvSettingWithoutLens' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'AverageLevel' => 'Livello medio', + 'Azimuth' => { + PrintConv => { + 'NNW' => 'NNO', + }, + }, + 'BWMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'BackgroundColorIndicator' => { + Description => 'Indicatore colore di sfondo', + PrintConv => { + 'Specified Background Color' => 'Colore di sfondo specificato', + 'Unspecified Background Color' => 'Colore di sfondo non specificato', + }, + }, + 'BackgroundColorValue' => 'Valore colore di sfondo', + 'BackgroundTiling' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'BadFaxLines' => 'Linee fax non valide', + 'BannerImageType' => { + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'BaselineExposure' => 'Esposizione di riferimento', + 'BaselineNoise' => 'Rumore di riferimento', + 'BaselineSharpness' => 'Nitidezza di riferimento', + 'BatteryLevel' => 'Livello batteria', + 'BatteryState' => { + PrintConv => { + 'Low' => 'Basso', + }, + }, + 'BeatsPerMinute' => 'Battiti al minuto', + 'Beep' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'BeepPitch' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'BeepVolume' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'BestQualityScale' => 'Scala qualità migliore', + 'BestShotMode' => { + PrintConv => { + 'Children' => 'Bambini', + 'Fireworks' => 'Fuochi artificiali', + 'Flower' => 'Fiore', + 'For YouTube' => 'Per YouTube', + 'For eBay' => 'Per eBay', + 'Off' => 'Spento', + 'Retro' => 'Retrò', + 'Scenery' => 'Paesaggio', + 'Short Movie' => 'Filmato breve', + 'Text' => 'Testo', + }, + }, + 'BitsPerComponent' => 'Bits per componente', + 'BitsPerExtendedRunLength' => 'Bit per rrun-length esteso', + 'BitsPerRunLength' => 'Bit per rrun-length', + 'BitsPerSample' => 'Numero di bit per componente', + 'BlackLevel' => 'Livello del nero', + 'BlackLevelDeltaH' => 'Livello del nero - Delta H', + 'BlackLevelDeltaV' => 'Livello del nero - Delta V', + 'BlackLevelRepeatDim' => 'Dim ripeti livello del nero', + 'BleachBypassToning' => { + PrintConv => { + 'Green' => 'Verde', + 'Off' => 'Spento', + 'Purple' => 'Porpora', + 'Red' => 'Rosso', + }, + }, + 'BlocksPerFrame' => 'Blocchi per frame', + 'BlueBalance' => 'Bilanciamento del blu', + 'BlueMatrixColumn' => 'Colonna della Matrice Blu', + 'BlueTRC' => 'Curva riproduzione tono blu', + 'BlurControl' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'BlurWarning' => { + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'BracketMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'BracketShotNumber' => { + PrintConv => { + '1 of 2' => '1 di 5', + '1 of 3' => '1 di 3', + '1 of 5' => '1 di 2', + 'n/a' => 'n/d', + }, + }, + 'Brightness' => 'Luminosità', + 'BrightnessValue' => 'Valore di luminosità', + 'BuildDate' => 'Data compilazione', + 'BuildVersion' => 'Versione compilazione', + 'BurstMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ButtonFunctionControlOff' => { + PrintConv => { + 'Normal (enable)' => 'Normale (abilitato)', + }, + }, + 'By-line' => 'Creatore', + 'By-lineTitle' => 'Titolo Creatore', + 'CCDScanMode' => { + PrintConv => { + 'Progressive' => 'Progressivo', + }, + }, + 'CFALayout' => { + Description => 'Layout CFA', + PrintConv => { + 'Even columns offset down 1/2 row' => 'Colonne pari giù di 1/2 riga', + 'Even columns offset up 1/2 row' => 'Colonne pari su di 1/2 riga', + 'Even rows offset down by 1/2 row, even columns offset left by 1/2 column' => 'Righe pari giù di 1/2 riga, colonne pari a sinistra di 1/2 colonna', + 'Even rows offset down by 1/2 row, even columns offset right by 1/2 column' => 'Righe pari giù di 1/2 riga, colonne pari a destra di 1/2 colonna', + 'Even rows offset left 1/2 column' => 'Righe pari a sinistra di 1/2 colonna', + 'Even rows offset right 1/2 column' => 'Righe pari a destra di 1/2 colonna', + 'Even rows offset up by 1/2 row, even columns offset left by 1/2 column' => 'Righe pari su 1/2 riga, colonne pari a sinistra di 1/2 colonna', + 'Even rows offset up by 1/2 row, even columns offset right by 1/2 column' => 'Righe pari su 1/2 riga, colonne pari a destra di 1/2 colonna', + 'Rectangular' => 'Rettangolare', + }, + }, + 'CFAPattern' => 'Pattern CFA', + 'CFAPattern2' => 'Pattern CFA 2', + 'CFAPlaneColor' => 'Piano colori CFA', + 'CFARepeatPatternDim' => 'Dim pattern ripetuto CFA', + 'CHMVersion' => 'Versione CHM', + 'CIP3DataFile' => 'File di dati CIP3', + 'CIP3Sheet' => 'Foglio CIP3', + 'CIP3Side' => 'Lato CIP3', + 'CMYKEquivalent' => 'CMYK equivalente', + 'CPUArchitecture' => 'Architettura CPU', + 'CPUByteOrder' => { + Description => 'Ordine byte CPU', + PrintConv => { + 'Big endian' => 'Big-endian', + 'Little endian' => 'Little-endian', + }, + }, + 'CPUCount' => 'Numero processori', + 'CPUSubtype' => { + Description => 'Sottotipo CPU', + PrintConv => { + 'ARM (all)' => 'ARM (tutti)', + 'HPPA (all)' => 'HPPA (tutti)', + 'MC680x0 (all)' => 'MC680x0 (tutti)', + 'MC88000 (all)' => 'MC88000 (tutti)', + 'MC98000 (all)' => 'MC98000 (tutti)', + 'MIPS (all)' => 'MIPS (tutti)', + 'NS32032 (all)' => 'NS32032 (tutti)', + 'NS32032 DPC (032 CPU)' => 'NS32032 DPC (CPU 032)', + 'NS32332 (all)' => 'NS32332 (tutti)', + 'NS32332 DPC (032 CPU)' => 'NS32332 DPC (CPU 032)', + 'PowerPC (all)' => 'PowerPC (tutti)', + 'RS6000 (all)' => 'RS6000 (tutti)', + 'RT (all)' => 'RT (tutti)', + 'SPARC (all)' => 'SPARC (tutti)', + 'VAX (all)' => 'VAX (tutti)', + 'i386 (all)' => 'i386 (tutti)', + 'i860 (all)' => 'i860 (tutti)', + 'i860 little (all)' => 'i860 little (tutti)', + }, + }, + 'CPUType' => { + Description => 'Tipo CPU', + PrintConv => { + 'Any' => 'Qualsiasi', + 'Axis Communications 32-bit embedded processor' => 'Processore integrato a 32 bit Axis Communications', + 'None' => 'Nessuno', + 'S/390 (old)' => 'S/390 (precedente)', + 'i860 big endian' => 'i860 big-endian', + 'i860 little endian' => 'i860 little-endian', + 'm32r (old)' => 'm32r (precedente)', + 'v850 (old)' => 'v850 (precedente)', + }, + }, + 'CalibrationDateTime' => 'Data/ora di calibrazione', + 'CalibrationIlluminant1' => { + Description => 'Calibration illuminazione 1', + PrintConv => { + 'Cloudy' => 'Nuvoloso', + 'Cool White Fluorescent' => 'Fluorescente a luce calda', + 'Day White Fluorescent' => 'Fluorescente a luce del giorno bianca', + 'Daylight' => 'Luce del giorno', + 'Daylight Fluorescent' => 'Fluorescente a luce del giorno', + 'Fine Weather' => 'Bel tempo', + 'Fluorescent' => 'Fluorescente', + 'ISO Studio Tungsten' => 'Tungsteno studio ISO', + 'Other' => 'Altra Sorgente di Luce', + 'Shade' => 'Ombrato', + 'Standard Light A' => 'Luce standard A', + 'Standard Light B' => 'Luce standard B', + 'Standard Light C' => 'Luce standard C', + 'Tungsten (Incandescent)' => 'Tungsteno (luce incandescente)', + 'Unknown' => 'Sconosciuto', + 'Warm White Fluorescent' => 'Luce fluorescente bianca calda', + 'White Fluorescent' => 'Fluorescente bianca', + }, + }, + 'CalibrationIlluminant2' => { + Description => 'Calibration illuminazione 2', + PrintConv => { + 'Cloudy' => 'Nuvoloso', + 'Cool White Fluorescent' => 'Fluorescente a luce calda', + 'Day White Fluorescent' => 'Fluorescente a luce del giorno bianca', + 'Daylight' => 'Luce del giorno', + 'Daylight Fluorescent' => 'Fluorescente a luce del giorno', + 'Fine Weather' => 'Bel tempo', + 'Fluorescent' => 'Fluorescente', + 'ISO Studio Tungsten' => 'Tungsteno studio ISO', + 'Other' => 'Altra Sorgente di Luce', + 'Shade' => 'Ombrato', + 'Standard Light A' => 'Luce standard A', + 'Standard Light B' => 'Luce standard B', + 'Standard Light C' => 'Luce standard C', + 'Tungsten (Incandescent)' => 'Tungsteno (luce incandescente)', + 'Unknown' => 'Sconosciuto', + 'Warm White Fluorescent' => 'Luce fluorescente bianca calda', + 'White Fluorescent' => 'Fluorescente bianca', + }, + }, + 'CameraAngle' => 'Angolo fotocamera', + 'CameraBody' => 'Corpo fotocamera', + 'CameraByteOrder' => 'Ordine byte fotocamera', + 'CameraCalibration1' => 'Calibrazione fotocamera 1', + 'CameraCalibration2' => 'Calibrazione fotocamera 2', + 'CameraCalibrationSig' => 'Segnale calibrazione fotocamera', + 'CameraColorCalibration01' => 'Calibrazione colore fotocamera 01', + 'CameraColorCalibration02' => 'Calibrazione colore fotocamera 02', + 'CameraColorCalibration03' => 'Calibrazione colore fotocamera 03', + 'CameraColorCalibration04' => 'Calibrazione colore fotocamera 04', + 'CameraColorCalibration05' => 'Calibrazione colore fotocamera 05', + 'CameraColorCalibration06' => 'Calibrazione colore fotocamera 06', + 'CameraColorCalibration07' => 'Calibrazione colore fotocamera 07', + 'CameraColorCalibration08' => 'Calibrazione colore fotocamera 08', + 'CameraColorCalibration09' => 'Calibrazione colore fotocamera 09', + 'CameraColorCalibration10' => 'Calibrazione colore fotocamera 10', + 'CameraColorCalibration11' => 'Calibrazione colore fotocamera 11', + 'CameraColorCalibration12' => 'Calibrazione colore fotocamera 12', + 'CameraColorCalibration13' => 'Calibrazione colore fotocamera 13', + 'CameraColorCalibration14' => 'Calibrazione colore fotocamera 14', + 'CameraColorCalibration15' => 'Calibrazione colore fotocamera 15', + 'CameraDateTime' => 'Data/ora fotocamera', + 'CameraDirection' => 'Direzione fotocamera', + 'CameraID' => 'ID fotocamera', + 'CameraISO' => 'ISO fotocamera', + 'CameraIdentifier' => 'Identificativo fotocamera', + 'CameraLabel' => 'Etichetta fotocamera', + 'CameraMaker' => 'Marca fotocamera', + 'CameraManufacturer' => 'Produttore fotocamera', + 'CameraModel' => 'Modello fotocamera', + 'CameraMotion' => 'Camera motion', + 'CameraMove' => 'Sposta fotocamera', + 'CameraName' => 'Nome fotocamera', + 'CameraObjBackType' => 'Nome oggetto nero fotocamera', + 'CameraObjName' => 'Nome oggetto fotocamera', + 'CameraObjType' => 'Tipo oggetto fotocamera', + 'CameraObjVersion' => 'Versione oggetto fotocamera', + 'CameraOrientation' => { + Description => 'Orientazione fotocamera', + PrintConv => { + 'Horizontal (normal)' => 'Orizzontale (normale)', + 'Rotate 180' => 'Ruota di 180°', + 'Rotate 270 CW' => 'Ruota di 270° in senso orario', + 'Rotate 90 CW' => 'Ruota di 90° senso orario', + }, + }, + 'CameraOwner' => 'Proprietario fotocamera', + 'CameraParameters' => 'Parametri fotocamera', + 'CameraProfile' => 'Profilo fotocamera', + 'CameraProfileDigest' => 'Sommario profilo fotocamera', + 'CameraProfileVersion' => 'Versione profilo fotocamera', + 'CameraSerialNumber' => 'Numero di serie fotocamera', + 'CameraSettingsVersion' => 'Versione impostazioni fotocamera', + 'CameraTemperature' => 'Temperatura fotocamera', + 'CameraTemperature2' => 'Temperatura fotocamera 2', + 'CameraTemperature3' => 'Temperatura fotocamera 3', + 'CameraTemperature4' => 'Temperatura fotocamera 4', + 'CameraTemperature5' => 'Temperatura fotocamera 5', + 'CameraType' => { + Description => 'Tipo fotocamera', + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'CameraType2' => 'Tipo fotocamera 2', + 'CanonExposureMode' => { + PrintConv => { + 'Aperture-priority AE' => 'Priorità diaframma', + 'Manual' => 'Manuale', + 'Program AE' => 'Programma AE', + 'Shutter speed priority AE' => 'Priorità otturatore AE', + }, + }, + 'CanonFlashMode' => { + PrintConv => { + 'External flash' => 'Flash esterno', + 'Off' => 'Spento', + 'Red-eye reduction' => 'Riduzione occhi rossi', + 'Red-eye reduction (Auto)' => 'Riduzione occhi rossi (auto)', + 'Red-eye reduction (On)' => 'Riduzione occhi rossi (attivo)', + }, + }, + 'CanonImageSize' => { + PrintConv => { + 'Postcard' => 'Cartolina', + }, + }, + 'Caption-Abstract' => 'Didascalia/descrizione', + 'CaptionWriter' => 'Autore della didascalia', + 'CardShutterLock' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'CasioQuality' => { + Description => 'Qualità Casio', + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'Categories' => { + Description => 'Categorie', + PrintConv => { + 'Events' => 'Eventi', + 'Scenery' => 'Paesaggio', + 'To Do' => 'Da fare', + }, + }, + 'Category' => 'Categoria', + 'CellLength' => 'Lunghezza cella', + 'CellWidth' => 'Larghezza cella', + 'CenterWeightedAreaSize' => { + PrintConv => { + 'Average' => 'Media', + }, + }, + 'ChExtra' => 'Larghezza aggiunta per caratteri non spaziatori', + 'Channel' => 'Canale', + 'ChannelID' => 'ID canale', + 'ChannelIdentificationCode' => 'Codice identificativo canale', + 'ChannelLabel' => 'Etichetta canale', + 'ChannelLength' => 'Lunghezza canale', + 'ChannelMode' => 'Modo canale', + 'ChannelNumber' => 'Numero canale', + 'ChannelPositions' => 'Posizioni canale', + 'ChannelStatus' => 'Stato canale', + 'ChannelStatusMode' => 'Modo stato canale', + 'ChannelTotalTime' => 'Tempo totale canale', + 'ChannelWidth' => 'Larghezza canale', + 'Channels' => 'Canali', + 'Chapter' => 'Capitolo', + 'ChapterPhysicalEquivalent' => { + PrintConv => { + 'Session' => 'Sessione', + 'Side' => 'Lato', + }, + }, + 'ChapterProcessTime' => { + PrintConv => { + 'For Duration of Chapter' => 'Per la durata del capitolo', + }, + }, + 'CharacterSet' => { + Description => 'Set di caratteri', + PrintConv => { + 'Windows, Arabic' => 'Windows, Arabo', + 'Windows, Chinese (Simplified)' => 'Windows, Cinese semplificato', + 'Windows, Cyrillic' => 'Windows, Cirillico', + 'Windows, Greek' => 'Windows, Greco', + 'Windows, Hebrew' => 'Windows, Ebraico', + 'Windows, Japan (Shift - JIS X-0208)' => 'Windows, Giappone (Shift - JIS X-0208)', + 'Windows, Korea (Shift - KSC 5601)' => 'Windows, Corea (Shift - KSC 5601)', + 'Windows, Latin2 (Eastern European)' => 'Windows, Latin2 (Europa orientale)', + 'Windows, Turkish' => 'Windows, Turco', + }, + }, + 'Characters' => 'Caratteri', + 'CharactersWithSpaces' => 'Caratteri con spazi', + 'Children' => 'Bambini', + 'ChromaBlurRadius' => 'Raggio sfocatura cromatica', + 'ChromaticAberrationCorrection' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ChromaticAdaptation' => 'Adattamento cromatico', + 'Chromaticity' => 'Cromatismo', + 'ChrominanceNR_TIFF_JPEG' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'ChrominanceNoiseReduction' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'City' => 'Città', + 'ClassifyState' => 'Stato di Classificazione', + 'CleanFaxData' => { + Description => 'Dati fax precisi', + PrintConv => { + 'Clean' => 'Pulito', + 'Regenerated' => 'Rigenerato', + 'Unclean' => 'Sporco', + }, + }, + 'ClipPath' => 'Percoso clip', + 'CodePage' => { + PrintConv => { + 'Japanese (JIS 0208-1990 and 0121-1990)' => 'Giapponese (JIS 0208-1990 e 0121-1990)', + 'Russian/Cyrillic (KOI8-R)' => 'Russo/Cirillico (KOI8-R)', + }, + }, + 'CodeSize' => 'Dimensione codice', + 'CodedCharacterSet' => 'Impostazione Caratteri Codificati', + 'CodedContentScanningKind' => { + PrintConv => { + 'Mixed' => 'Misto', + 'Progressive' => 'Progressivo', + 'Unknown' => 'Sconosciuto', + }, + }, + 'CodingMethods' => { + Description => 'Metodi di codifica', + PrintConv => { + 'Baseline JPEG' => 'Linea di base JPEG', + 'JBIG color' => 'JBIG a colori', + 'Modified Huffman' => 'Huffman modificato', + 'Unspecified compression' => 'Compressione non specificata', + }, + }, + 'ColorAberrationControl' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ColorAdjustmentMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ColorBalance' => 'Bilanciamento Colore', + 'ColorBalanceAdj' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ColorBalanceVersion' => 'Versione Bilanciamento Colore', + 'ColorBooster' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ColorCharacterization' => 'Caratterizzazione colore', + 'ColorClass' => { + PrintConv => { + '0 (None)' => '0 (Nessuno)', + '1 (Winner)' => '1 (Vincitore)', + }, + }, + 'ColorEffect' => { + PrintConv => { + 'Off' => 'Spento', + 'Sepia' => 'Seppia', + }, + }, + 'ColorFilter' => { + Description => 'Filtro colori', + PrintConv => { + 'Green' => 'Verde', + 'Off' => 'Spento', + 'Purple' => 'Porpora', + 'Red' => 'Rosso', + 'Sepia' => 'Seppia', + 'Yellow' => 'Giallo', + }, + }, + 'ColorHue' => 'Colore Hue', + 'ColorMap' => 'Mappa colore', + 'ColorMatrix1' => 'Matrice colore 1', + 'ColorMatrix2' => 'Matrice colore 2', + 'ColorMode' => { + Description => 'Modo colore', + PrintConv => { + 'Autumn Leaves' => 'Foglie d\'autunno', + 'B&W' => 'B/N', + 'Clear' => 'Trasparente', + 'Deep' => 'Cupa', + 'Evening' => 'Sera', + 'Grayscale' => 'Scala di grigi', + 'Landscape' => 'Orizzontale', + 'Light' => 'Chiara', + 'Neutral' => 'Neutra', + 'Night View' => 'Visione notturna', + 'Night View/Portrait' => 'Rit. notturno', + 'Normal' => 'Normale', + 'Off' => 'Spento', + 'Portrait' => 'Verticale', + 'RGB Color' => 'Colore RGB', + 'Saturated Color' => 'Colore saturato', + 'Sepia' => 'Seppia', + 'Sunset' => 'Tramonto', + 'Vivid' => 'Vivace', + 'Vivid color' => 'Colore vivace', + 'n/a' => 'n/d', + }, + }, + 'ColorMoireReduction' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ColorMoireReductionMode' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'ColorPalette' => 'Palette di Colore', + 'ColorProfile' => { + PrintConv => { + 'Not Embedded' => 'Non incorporato', + }, + }, + 'ColorRepresentation' => { + Description => 'Rappresentazione del Colore', + PrintConv => { + 'No Image, Single Frame' => 'Nessuna immagine, quadro singolo', + }, + }, + 'ColorResponseUnit' => 'Unità risposta colore', + 'ColorSequence' => 'Sequenza colori', + 'ColorSpace' => { + Description => 'Spazio colore', + PrintConv => { + 'Gray-scale' => 'Scala di grigi', + 'Grayscale' => 'Scala di grigi', + 'ICC Profile' => 'Profilo ICC', + 'Uncalibrated' => 'Non calibrato', + }, + }, + 'ColorSpaceData' => 'Dati Spazio Colore', + 'ColorSpecApproximation' => { + PrintConv => { + 'Not Specified' => 'Non specificato', + }, + }, + 'ColorSpecMethod' => { + PrintConv => { + 'Enumerated' => 'Enumerato', + }, + }, + 'ColorTable' => 'Tabella colore', + 'ColorTemperature' => 'Temperatura colore', + 'ColorTemperatureSetting' => { + PrintConv => { + 'Temperature' => 'Temperatura', + }, + }, + 'ColorTone' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'ColorToneFaithful' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ColorToneLandscape' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ColorToneMonochrome' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ColorToneNeutral' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ColorTonePortrait' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ColorToneStandard' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ColorToneUnknown' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ColorToneUserDef1' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ColorToneUserDef2' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ColorToneUserDef3' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ColorType' => { + PrintConv => { + 'Grayscale' => 'Scala di grigi', + 'RGB with Alpha' => 'RGB con trasparenza', + }, + }, + 'ColorimetricReference' => 'Riferimento colorimetrico', + 'CommandDialsChangeMainSub' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'CommandDialsMenuAndPlayback' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'CommandDialsReverseRotation' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'CommanderGroupAMode' => { + PrintConv => { + 'Auto Aperture' => 'Diaframma automatico', + 'Manual' => 'Manuale', + 'Off' => 'Spento', + }, + }, + 'CommanderGroupBMode' => { + PrintConv => { + 'Auto Aperture' => 'Diaframma automatico', + 'Manual' => 'Manuale', + 'Off' => 'Spento', + }, + }, + 'CommanderInternalFlash' => { + PrintConv => { + 'Manual' => 'Manuale', + 'Off' => 'Spento', + }, + }, + 'Comment' => 'Commento', + 'CommentTime' => 'Ora commento', + 'Comments' => 'Commenti', + 'CommercialURL' => 'URL pubblicitario', + 'CompanyName' => 'Nome azienda', + 'Compilation' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'ComponentsConfiguration' => 'Configurazione componenti', + 'Composer' => 'Compositore', + 'ComposerSortOrder' => 'Ordinamento compositore', + 'CompositionAdjust' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'CompositionMode' => { + PrintConv => { + 'Replace' => 'Sostituisci', + }, + }, + 'Compressed' => { + PrintConv => { + 'False' => 'Falso', + }, + }, + 'CompressedBitsPerPixel' => 'Bit per pixel compressi', + 'Compression' => { + Description => 'Compressione', + PrintConv => { + 'Adobe Deflate' => 'Deflate Adobe', + 'JBIG B&W' => 'JBIG B&N', + 'JBIG Color' => 'JBIG a colori', + 'JPEG' => 'Compressione JPEG', + 'JPEG (old-style)' => 'JPEG (vecchio stile)', + 'Kodak DCR Compressed' => 'Kodak DCR compresso', + 'Kodak KDC Compressed' => 'Kodak KDC compresso', + 'Microsoft Document Imaging (MDI) Binary Level Codec' => 'Microsoft Document Imaging (MDI) - Codec a livello binario', + 'Microsoft Document Imaging (MDI) Progressive Transform Codec' => 'Microsoft Document Imaging (MDI) - Codec a trasformazione progressiva', + 'Microsoft Document Imaging (MDI) Vector' => 'Microsoft Document Imaging (MDI) - Vettore', + 'Next' => 'Codifica NeXT', + 'Nikon NEF Compressed' => 'Nikon NEF compresso', + 'None' => 'Nessuno', + 'Packed RAW' => 'RAW pacchettizzato', + 'Pentax PEF Compressed' => 'Pentax PEF compresso', + 'RLE Encoding' => 'Codifica RLE', + 'Samsung SRW Compressed' => 'Samsung SRW compresso', + 'Sony ARW Compressed' => 'Sony ARW compresso', + 'T4/Group 3 Fax' => 'Fax T4/Group 3', + 'T6/Group 4 Fax' => 'Fax T6/Group 4', + 'Uncompressed' => 'Non compresso', + }, + }, + 'CompressionLevel' => 'Livello di compressione', + 'CompressionType' => { + Description => 'Tipo compressione', + PrintConv => { + 'Little-endian, no compression' => 'Little-endian, senza compressione', + 'None' => 'Nessuno', + }, + }, + 'CompressorName' => 'Nome compressione', + 'ConditionalFEC' => 'Compensazione Esposizione Flash', + 'Conductor' => 'Direttore d\'orchestra', + 'ConsecutiveBadFaxLines' => 'Linee fax non valide consecutive', + 'Contact' => 'Contatto', + 'ContentEncodingType' => { + PrintConv => { + 'Encryption' => 'Crittografia', + }, + }, + 'ContentEncryptionAlgorithm' => { + PrintConv => { + 'Not Encrypted' => 'Non crittografato', + }, + }, + 'ContentLocationCode' => 'Codice ubicazione contenuto', + 'ContentLocationName' => 'Nome Ubicazione Contenuto', + 'ContentSignatureAlgorithm' => { + PrintConv => { + 'Not Signed' => 'Senza segno', + }, + }, + 'ContentSignatureHashAlgorithm' => { + PrintConv => { + 'Not Signed' => 'Senza segno', + }, + }, + 'ContinuousBracketing' => { + PrintConv => { + 'Low' => 'Basso', + }, + }, + 'ContinuousShootingSpeed' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'ContinuousShotLimit' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'Contrast' => { + Description => 'Contrasto', + PrintConv => { + 'High' => 'Alto', + 'Low' => 'Basso', + 'Normal' => 'Normale', + }, + }, + 'ContrastCurve' => 'Curva di contrasto', + 'ContrastDetectAF' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ContrastFaithful' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ContrastHighlightShadowAdj' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ContrastLandscape' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ContrastMode' => { + PrintConv => { + 'Elegant (My Color)' => 'Elegante (mio colore)', + 'Expressive' => 'Espressivo', + 'Low' => 'Basso', + 'Normal' => 'Normale', + 'Nostalgic (Color Film)' => 'Nostalgico (pellicola a colori)', + 'Retro' => 'Retrò', + 'Retro (My Color)' => 'Retro (colore personalizzato)', + }, + }, + 'ContrastMonochrome' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ContrastNeutral' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ContrastPortrait' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ContrastStandard' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ContrastUnknown' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ContrastUserDef1' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ContrastUserDef2' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ContrastUserDef3' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ControlDialSet' => { + PrintConv => { + 'Shutter Speed' => 'Tempo esposizione', + }, + }, + 'ControlMode' => { + PrintConv => { + 'Camera Local Control' => 'Controllo locale fotocamera', + 'n/a' => 'n/d', + }, + }, + 'ConversionLens' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Converter' => 'Convertitore', + 'Copyright' => 'Titolare del copyright', + 'CopyrightFlag' => { + PrintConv => { + 'False' => 'Falso', + }, + }, + 'CopyrightNotice' => 'Info Copyright', + 'CopyrightStatus' => { + PrintConv => { + 'Not specified' => 'Non specificato', + 'Protected' => 'Protetto', + 'Public Domain' => 'Pubblico dominio', + 'Unknown' => 'Sconosciuto', + }, + }, + 'CopyrightURL' => 'URL copyright', + 'Country' => 'Paese', + 'Country-PrimaryLocationCode' => 'Codice ISO Nazione', + 'Country-PrimaryLocationName' => 'Nazione', + 'CreateDate' => 'Data di creazione', + 'CreationDate' => 'Data di creazione', + 'CreativeStyle' => { + PrintConv => { + 'Autumn Leaves' => 'Foglie d\'autunno', + 'B&W' => 'B/N', + 'Clear' => 'Trasparente', + 'Deep' => 'Cupa', + 'Landscape' => 'Orizzontale', + 'Light' => 'Chiara', + 'Neutral' => 'Neutra', + 'Night View/Portrait' => 'Rit. notturno', + 'Portrait' => 'Verticale', + 'Sepia' => 'Seppia', + 'Sunset' => 'Tramonto', + 'Vivid' => 'Vivace', + }, + }, + 'CreativeStyleSetting' => { + PrintConv => { + 'Landscape' => 'Orizzontale', + 'Sunset' => 'Tramonto', + 'Vivid' => 'Vivace', + }, + }, + 'Creator' => 'Creatore', + 'CreatorAddress' => 'Autore - Indirizzo', + 'CreatorAppID' => { + PrintConv => { + 'Unknown' => 'Sconosciuto', + }, + }, + 'CreatorCity' => 'Autore - Città', + 'CreatorCountry' => 'Autore - Nazione', + 'CreatorPostalCode' => 'Autore - Codice Postale', + 'CreatorRegion' => 'Autore - Stato/Provincia', + 'CreatorWorkEmail' => 'Autore - E.Mail', + 'CreatorWorkTelephone' => 'Autore - Numero di Telefono', + 'CreatorWorkURL' => 'Autore - Sito web', + 'Credit' => 'Fornitore', + 'CreditLineRequired' => { + PrintConv => { + 'Not Required' => 'Non richiesto', + }, + }, + 'CropActive' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'CropUnit' => { + PrintConv => { + 'inches' => 'Pollici', + }, + }, + 'CropUnits' => { + PrintConv => { + 'inches' => 'Pollici', + }, + }, + 'CrossProcess' => { + PrintConv => { + 'Favorite 1' => 'Preferito 1', + 'Favorite 2' => 'Preferito 2', + 'Favorite 3' => 'Preferito 3', + 'Off' => 'Spento', + 'Random' => 'Casuale', + }, + }, + 'CurrentICCProfile' => 'Profilo ICC attuale', + 'CurrentPreProfileMatrix' => 'Matrice di pre-profilo attuale', + 'Curves' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'CustomRendered' => { + Description => 'Resa personalizzata', + PrintConv => { + 'Custom' => 'Personalizzata', + 'Normal' => 'Normale', + }, + }, + 'CustomWBError' => { + PrintConv => { + 'Error' => 'Errore', + }, + }, + 'D-LightingHQ' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'D-LightingHQSelected' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'D-LightingHS' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'D-RangeOptimizerMode' => { + PrintConv => { + 'Manual' => 'Manuale', + 'Off' => 'Spento', + }, + }, + 'DECPosition' => { + PrintConv => { + 'Exposure' => 'Esposizione', + 'Saturation' => 'Saturazione', + }, + }, + 'DNGBackwardVersion' => 'Versione precedente DNG', + 'DNGLensInfo' => 'Informazioni lenti DNG', + 'DNGPrivateData' => 'Dati privati DNG', + 'DNGVersion' => 'Versione DNG', + 'DataImprint' => { + PrintConv => { + 'None' => 'Nessuno', + 'Text' => 'Testo', + }, + }, + 'DataType' => 'Tipo di dati', + 'Date' => 'Data', + 'DateCreated' => 'Data di creazione', + 'DateDisplayFormat' => { + Description => 'Formato Data', + PrintConv => { + 'D/M/Y' => 'Giorno/mese/anno', + 'M/D/Y' => 'Mese/giorno/anno', + 'Y/M/D' => 'Anno/mese/giorno', + }, + }, + 'DateImprint' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'DateSent' => 'Data d\'invio', + 'DateStampMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'DateTime' => 'Data/ora', + 'DateTimeOriginal' => 'Data/ora originale di creazione', + 'DaylightSavings' => { + PrintConv => { + 'Off' => 'Spento', + 'Yes' => 'Sì', + }, + }, + 'Decode' => 'Decodifica', + 'DefaultCropOrigin' => 'Ritaglio origine predefinito', + 'DefaultCropSize' => 'Ritaglio dimensione predefinito', + 'DefaultImageColor' => 'Colore immagine predefinito', + 'DefaultScale' => 'Scala predefinita', + 'DeletedImageCount' => 'Conteggio Immagini Cancellate', + 'DeltaType' => { + PrintConv => { + 'Absolute' => 'Assoluto', + }, + }, + 'Description' => 'Descrizione', + 'Destination' => 'Destinazione', + 'DestinationCity' => { + PrintConv => { + 'Jerusalem' => 'Gerusalemme', + 'Lisbon' => 'Lisbona', + 'London' => 'Londra', + 'Milan' => 'Milano', + 'Prague' => 'Praga', + 'Rome' => 'Roma', + 'Stockholm' => 'Stoccolma', + 'Tehran' => 'Teheran', + }, + }, + 'DestinationDST' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'DeviceAttributes' => 'Attributi Dispositivo', + 'DeviceManufacturer' => 'Costruttore dispositivo', + 'DeviceMfgDesc' => 'Descrizione del Costruttore Dispositivo', + 'DeviceModel' => 'Modello Dispositivo', + 'DeviceModelDesc' => 'Descrizione Modello Dispositivo', + 'DeviceSettingDescription' => 'Descrizione impostazioni dispositivo', + 'DialDirectionTvAv' => { + PrintConv => { + 'Normal' => 'Normale', + 'Reversed' => 'Invertito', + }, + }, + 'DigitalCreationDate' => 'Data di creazione digitale', + 'DigitalCreationTime' => 'Ora di Creazione Digitale', + 'DigitalFilter01' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter02' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter03' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter04' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter05' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter06' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter07' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter08' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter09' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter10' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter11' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter12' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter13' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter14' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter15' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter16' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter17' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter18' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter19' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalFilter20' => { + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Retro' => 'Retrò', + 'Shading' => 'Ombreggiatura', + 'Starburst' => 'Esplosione stellare', + }, + }, + 'DigitalZoom' => { + Description => 'Zoom Digitale', + PrintConv => { + 'None' => 'Nessuno', + 'Off' => 'Spento', + }, + }, + 'DigitalZoomOn' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'DigitalZoomRatio' => 'Rapporto zoom digitale', + 'Directory' => 'Posizione file', + 'DisplayAllAFPoints' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'DisplaySize' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'DisplayUnits' => { + PrintConv => { + 'inches' => 'Pollici', + }, + }, + 'DisplayedUnitsX' => { + PrintConv => { + 'inches' => 'Pollici', + }, + }, + 'DisplayedUnitsY' => { + PrintConv => { + 'inches' => 'Pollici', + }, + }, + 'DistortionControl' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'DistortionCorrection' => { + PrintConv => { + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'DistortionCorrection2' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'DjVuVersion' => 'Versione DjVu', + 'DocSecurity' => { + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'DocumentHistory' => 'Cronologia documento', + 'DocumentName' => 'Nome documento', + 'DocumentNotes' => 'Note del Documento', + 'DotRange' => 'Intervallo puntuale', + 'DriveMode' => { + Description => 'Modalità esecuzione', + PrintConv => { + '10 s Timer' => 'Timer 10 s', + 'Off' => 'Spento', + 'Self-Timer 2 sec' => 'Autoscatto 2 sec', + 'Self-timer' => 'Autoscatto', + 'Self-timer (12 s)' => 'Autoscatto (12 s)', + 'Self-timer (2 s)' => 'Autoscatto (2 s)', + 'Self-timer 10 sec' => 'Autoscatto 10 sec', + 'Self-timer Operation' => 'Operazione autoscatto', + 'n/a' => 'n/d', + }, + }, + 'DriveMode2' => { + PrintConv => { + 'Self-timer (12 s)' => 'Autoscatto (12 s)', + 'Self-timer (2 s)' => 'Autoscatto (2 s)', + 'Self-timer 10 sec' => 'Autoscatto 10 sec', + 'Self-timer 2 sec' => 'Autoscatto 2 sec', + }, + }, + 'DriveModeSetting' => { + PrintConv => { + 'Self-timer 10 sec' => 'Autoscatto 10 sec', + }, + }, + 'DriveType' => { + PrintConv => { + 'Fixed Disk' => 'Disco fisso', + 'Unknown' => 'Sconosciuto', + }, + }, + 'DynamicRangeExpansion' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'DynamicRangeOptimizer' => { + Description => 'Ott.gamma din.', + PrintConv => { + 'Advanced Auto' => 'Avanz.autom.', + 'Advanced Lv1' => 'Liv.Avanzato 1', + 'Advanced Lv2' => 'Liv.Avanzato 2', + 'Advanced Lv3' => 'Liv.Avanzato 3', + 'Advanced Lv4' => 'Liv.Avanzato 4', + 'Advanced Lv5' => 'Liv.Avanzato 5', + 'Auto' => 'Automatico', + 'Off' => 'Spento', + }, + }, + 'DynamicRangeOptimizerBracket' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'DynamicRangeOptimizerMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'DynamicRangeOptimizerSetting' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'DynamicRangeSetting' => { + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'ETTLII' => { + PrintConv => { + 'Average' => 'Media', + }, + }, + 'EVSteps' => { + PrintConv => { + '1/2 EV Steps' => 'Step 1/2 EV', + '1/3 EV Steps' => 'Step 1/3 EV', + }, + }, + 'EXIFVersion' => 'Versione EXIF', + 'EXRAuto' => { + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'EasyExposureComp' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'EasyExposureCompensation' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'EasyMode' => { + PrintConv => { + 'Fireworks' => 'Fuochi artificiali', + 'Fisheye Effect' => 'Effetto fish-eye', + 'Flash Off' => 'Flash spento', + 'Kids & Pets' => 'Bambini e animali dimestici', + 'Landscape' => 'Orizzontale', + 'Manual' => 'Manuale', + 'Night' => 'Scena notturna', + 'Nostalgic' => 'Nostalgico', + 'Portrait' => 'Verticale', + 'Quick Shot' => 'Scatto veloce', + 'Sepia' => 'Seppia', + 'Sunset' => 'Tramonto', + 'Vivid' => 'Vivace', + 'Zoom Blur' => 'Sfocatura zoom', + }, + }, + 'EdgeNoiseReduction' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'EditStatus' => 'Modifica Stato', + 'EditorialUpdate' => { + Description => 'Aggiornamento Editoriale', + PrintConv => { + 'Additional language' => 'Linguaggio addizionale', + }, + }, + 'EffectiveMaxAperture' => 'Diaframma massimo effettivo', + 'Elevation' => 'Elevazione', + 'Emphasis' => { + PrintConv => { + 'None' => 'Nessuno', + 'reserved' => 'riservato', + }, + }, + 'EncodedBy' => 'Codificato da', + 'EncodedPixelsDimensions' => 'Dimensioni pixel codificati', + 'EncodedUsing' => 'Codificato usando', + 'EncodedWith' => 'Codificato con', + 'Encoder' => 'Codificatore', + 'EncoderSettings' => 'Impostazioni codificatore', + 'EncoderVersion' => 'Versione codificatore', + 'Encoding' => { + Description => 'Codifica', + PrintConv => { + 'QDesign Music' => 'Musica QDesign', + 'Unknown -' => 'Sconosciuto -', + }, + }, + 'EncodingProcess' => 'Processo di codifica', + 'EncodingScheme' => 'Schema di codifica', + 'EncodingSettings' => 'Impostazioni di codifica', + 'EncodingTime' => 'Durata codifica', + 'Encryption' => 'Crittografia', + 'EndOfItems' => 'Fine elementi', + 'EndPoints' => 'Punti finali', + 'EndTime' => 'Ora fine', + 'EndUser' => 'Utente finale', + 'EndUserID' => 'ID utente finale', + 'EndUserName' => 'Nome utente finale', + 'EndingPage' => 'Pagina finale', + 'EnhanceDarkTones' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Enhancement' => { + Description => 'Miglioramento', + PrintConv => { + 'Green' => 'Verde', + 'Off' => 'Spento', + 'Red' => 'Rosso', + 'Scenery' => 'Paesaggio', + }, + }, + 'EnhancementOrModificationDescription' => 'Descrizione miglioramento/modifica', + 'EntryPoint' => 'Punto d\'ingresso', + 'EnvelopePriority' => { + PrintConv => { + '0 (reserved)' => '0 (riservato)', + '1 (most urgent)' => '1 (molto urgente)', + '5 (normal urgency)' => '5 (urgenza normale)', + '8 (least urgent)' => '8 (meno urgente)', + '9 (user-defined priority)' => '9 (priorità definita dall\'utente)', + }, + }, + 'Error' => 'Errore', + 'ErrorCorrection' => 'Correzione errore', + 'ErrorCorrectionType' => 'Tipo correzione errore', + 'EthnicGroup' => 'Gruppo etnico', + 'Event' => 'Evento', + 'EventAbsoluteDuration' => 'Durata assoluta evento', + 'Events' => 'Eventi', + 'ExcursionTolerance' => { + PrintConv => { + 'Allowed' => 'Possibile', + 'Not Allowed' => 'Non permesso', + }, + }, + 'ExecutionStatus' => 'Stato esecuzione', + 'ExecutionStatusInfo' => 'Info stato esecuzione', + 'ExifByteOrder' => 'Ordine dei byte Exif', + 'ExifCameraInfo' => 'Info Exif fotocamera', + 'ExifImageHeight' => 'Altezza immagine Exif', + 'ExifImageWidth' => 'Larghezza immagine Exif', + 'ExifInfo2' => 'Info 2 Exif', + 'ExifOffset' => 'Puntatore Exif IFD', + 'ExifToolVersion' => 'Numero versione ExifTool', + 'ExifUnicodeByteOrder' => 'Ordine unicode dei byte Exif', + 'ExifVersion' => 'Versione Exif', + 'ExitPupilPosition' => 'Posizione pupilla d\'uscita', + 'ExpirationDate' => 'Data scadenza', + 'ExpirationTime' => 'Ora scadenza', + 'Expires' => 'Scade', + 'ExposedArea' => 'Area esposta', + 'Exposure' => 'Esposizione', + 'Exposure2012' => 'Esposizione 2012', + 'ExposureBracketValue' => 'Valore Esposizione a Forcella', + 'ExposureBracketingIndicatorLast' => { + PrintConv => { + 'Not Indicated' => 'Non indicato', + }, + }, + 'ExposureCompensation' => 'Compensazione esposizione', + 'ExposureCompensationMode' => 'Modo compensazione esposizione', + 'ExposureControlMode' => 'Modo controllo esposizione', + 'ExposureControlModeDescription' => 'Descrizione modo controllo esposizione', + 'ExposureDelayMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ExposureDifference' => 'Differenza esposizione', + 'ExposureIndex' => 'Indice di esposizione', + 'ExposureIndicator' => { + PrintConv => { + 'Not Indicated' => 'Non indicato', + }, + }, + 'ExposureLevelIncrements' => { + PrintConv => { + '1/2 Stop' => '1/2 stop', + '1/3 Stop' => '1/3 stop', + }, + }, + 'ExposureMode' => { + Description => 'Modo esposizione', + PrintConv => { + 'Aperture Priority' => 'Priorità diaframma', + 'Aperture-priority AE' => 'Priorità diaframma', + 'Auto' => 'Esposizione automatica', + 'Auto bracket' => 'A forcella automatica', + 'Fireworks' => 'Fuochi artificiali', + 'Landscape' => 'Orizzontale', + 'Manual' => 'Manuale', + 'Portrait' => 'Verticale', + 'Program' => 'Programma', + 'Program AE' => 'Programma AE', + 'Shutter Priority' => 'Priorità otturatore', + 'Shutter speed priority AE' => 'Priorità otturatore AE', + 'Sunset' => 'Tramonto', + 'n/a' => 'n/d', + }, + }, + 'ExposureModeInManual' => { + Description => 'Modo esposizione in manuale', + PrintConv => { + 'Center-weighted average' => 'Media centrale ponderata', + 'Partial metering' => 'Parziale', + }, + }, + 'ExposureModulationType' => 'Tipo modulazione esposizione', + 'ExposureProgram' => { + Description => 'Programma esposizione', + PrintConv => { + 'Action (High speed)' => 'Azione (alta velocità)', + 'Aperture Priority' => 'Priorità diaframma', + 'Aperture-priority AE' => 'Priorità diaframma', + 'Bulb' => 'Bulbo', + 'Creative (Slow speed)' => 'Creativo (bassa velocità)', + 'Landscape' => 'Orizzontale', + 'Manual' => 'Manuale', + 'Not Defined' => 'Non definito', + 'Portrait' => 'Verticale', + 'Posterization' => 'Posterizzazione', + 'Posterization B/W' => 'Posterizzazione B&N', + 'Program' => 'Programma', + 'Program AE' => 'Programma AE', + 'Retro Photo' => 'Foto retrò', + 'Shutter Priority' => 'Priorità otturatore', + 'Shutter speed priority AE' => 'Priorità otturatore AE', + 'Sunset' => 'Tramonto', + 'Sweep Panorama' => 'Panoramica ad arco', + }, + }, + 'ExposureSequence' => 'Sequenza esposizione', + 'ExposureStatus' => 'Stato esposizione', + 'ExposureTime' => 'Tempo esposizione', + 'ExposureTime2' => 'Tempo esposizione 2', + 'ExposureTimeInMicroSec' => 'Tempo esposizione in microsecondi', + 'ExposureTimeInMilliSec' => 'Tempo esposizione in millisecondi', + 'ExposureTuning' => 'Sintonizzazione esposizione', + 'ExposureUnknown' => 'Esposizione sconosciuta', + 'ExposureValue' => 'Valore esposizione', + 'ExtendedWBDetect' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Extender' => { + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'ExtenderStatus' => { + PrintConv => { + 'Not attached' => 'Non applicabile', + 'Removed' => 'Rimosso', + }, + }, + 'ExtensionPersistence' => { + PrintConv => { + 'Potentially Invalidated By Modification' => 'Potenzialmente invalidato per modifica', + }, + }, + 'ExternalFlash' => { + Description => 'Flash esterno', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ExternalFlashBounce' => { + PrintConv => { + 'Yes' => 'Sì', + 'n/a' => 'n/d', + }, + }, + 'ExternalFlashExposureComp' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ExternalFlashFirmware' => { + PrintConv => { + '1.01 (SB-800 or Metz 58 AF-1)' => '1.01 (SB-800 o Metz 58 AF-1)', + 'n/a' => 'n/d', + }, + }, + 'ExternalFlashFlags' => 'Flags Flash Esterno', + 'ExternalFlashMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ExtraFlags' => { + PrintConv => { + '(none)' => '(nessuno)', + 'Fastest Algorithm' => 'Algoritmo più veloce', + }, + }, + 'ExtraSamples' => { + Description => 'Campioni extra', + PrintConv => { + 'Associated Alpha' => 'Associati alla trasparenza', + 'Unassociated Alpha' => 'Non associati alla trasparenza', + 'Unspecified' => 'Non specificati', + }, + }, + 'EyeStartAF' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FEMicroadjustment' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'FNumber' => 'Numero F', + 'Face1Category' => { + PrintConv => { + 'Family' => 'Famiglia', + }, + }, + 'Face2Category' => { + PrintConv => { + 'Family' => 'Famiglia', + }, + }, + 'Face3Category' => { + PrintConv => { + 'Family' => 'Famiglia', + }, + }, + 'Face4Category' => { + PrintConv => { + 'Family' => 'Famiglia', + }, + }, + 'Face5Category' => { + PrintConv => { + 'Family' => 'Famiglia', + }, + }, + 'Face6Category' => { + PrintConv => { + 'Family' => 'Famiglia', + }, + }, + 'Face7Category' => { + PrintConv => { + 'Family' => 'Famiglia', + }, + }, + 'Face8Category' => { + PrintConv => { + 'Family' => 'Famiglia', + }, + }, + 'FaceDetection' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => 'Orizzontale (normale)', + 'Rotate 180' => 'Ruota di 180°', + 'Rotate 270 CW' => 'Ruota di 270° in senso orario', + 'Rotate 90 CW' => 'Ruota di 90° senso orario', + }, + }, + 'FacesDetected' => { + Description => 'Facce rimosse', + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'FacesRecognized' => 'Facce riconosciute', + 'FamilyName' => 'Nome famiglia', + 'FastSeek' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'FaxNumber' => 'Numero di fax', + 'FaxProfile' => { + Description => 'Profilo fax', + PrintConv => { + 'Extended B&W lossless, F' => 'Bianco e nero esteso senza perdita, F', + 'Lossless JBIG B&W, J' => 'Senza perdita JBIG bianco e nero, J', + 'Lossless color and grayscale, L' => 'Senza perdita a colori e scala di grigi, L', + 'Lossy color and grayscale, C' => 'Con perdita a colori e scala di grigi, C', + 'Minimal B&W lossless, S' => 'Bianco e nero minimale con perdita, S', + 'Mixed raster content, M' => 'Contenuto raster misto, M', + 'Multi Profiles' => 'Profili multipli', + 'Profile T' => 'Profilo T', + 'Unknown' => 'Sconosciuto', + }, + }, + 'FaxRecvParams' => 'Parametri ricezione fax', + 'FaxRecvTime' => 'Ora ricezione fax', + 'FaxSubAddress' => 'Sotto-indirizzo fax', + 'FileAttributes' => { + PrintConv => { + 'Encrypted' => 'Crittografato', + 'Encrypted?' => 'Crittografato?', + 'Normal' => 'Normale', + 'Not indexed' => 'Non indicizzato', + 'Read-only' => 'Sola lettura', + 'System' => 'Sistema', + }, + }, + 'FileDescription' => 'Descrizione file', + 'FileDescriptors' => 'Descrittori file', + 'FileFlags' => { + Description => 'Attributi file', + PrintConv => { + 'Info inferred' => 'Info desunte', + 'Private build' => 'Compilazione privata', + 'Special build' => 'Compilazione speciale', + }, + }, + 'FileFlagsMask' => 'Maschera attributi file', + 'FileFormat' => { + Description => 'Formato file', + PrintConv => { + 'Ritzaus Bureau NITF version (RBNITF DTD)' => 'Versione Ritzaus Bureau NITF (RBNITF DTD)', + }, + }, + 'FileFunctionFlags' => { + PrintConv => { + 'Fragmented' => 'Frammentato', + }, + }, + 'FileID' => 'ID file', + 'FileIndex' => 'Indice file', + 'FileIndex2' => 'Indice file 2', + 'FileLength' => 'Dimensioni file', + 'FileModifyDate' => 'Data aggiornamento', + 'FileName' => 'Nome file', + 'FileNameLength' => 'Lunghezza nome file', + 'FileNumber' => 'Numero file', + 'FileNumberMemory' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FileNumberSequence' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FileOS' => 'OS del file', + 'FileOwner' => 'Proprietario del file', + 'FilePermissions' => 'Permessi file', + 'FileProfileVersion' => 'Versione profilo file', + 'FileSecurityReport' => 'Rapporto sicurezza file', + 'FileSequence' => 'Sequenza file', + 'FileSetID' => 'ID gruppo file', + 'FileSize' => 'Dimensione file', + 'FileSizeBytes' => 'Dimensione file in byte', + 'FileSource' => { + Description => 'Origine file', + PrintConv => { + 'Digital Camera' => 'Fotocamera digitale', + 'Film Scanner' => 'Scanner per pellicola', + 'Reflection Print Scanner' => 'Scanner a riflessione', + 'Sigma Digital Camera' => 'Fotocamera digitale Sigma', + }, + }, + 'FileSubtype' => 'Sottotipo file', + 'FileType' => 'Tipo di file', + 'FileURL' => 'URL file', + 'FileVersion' => 'Versione file', + 'FileVersionNumber' => 'Numero versione file', + 'Filename' => 'Nome file', + 'Files' => 'File', + 'FillFlashAutoReduction' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'FillOrder' => { + Description => 'Ordine riempimento', + PrintConv => { + 'Normal' => 'Normale', + 'Reversed' => 'Invertito', + }, + }, + 'FilmMode' => { + PrintConv => { + 'Nostalgic' => 'Nostalgico', + 'Standard (B&W)' => 'Standard (B&N)', + 'Standard (color)' => 'Standard (colori)', + 'n/a' => 'n/d', + }, + }, + 'FilterEffect' => { + Description => 'Effetto Filtro', + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Off' => 'Spento', + 'Orange' => 'Arancio', + 'Red' => 'Rosso', + 'Yellow' => 'Giallo', + 'n/a' => 'n/d', + }, + }, + 'FilterEffectFaithful' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'FilterEffectLandscape' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'FilterEffectMonochrome' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Orange' => 'Arancio', + 'Red' => 'Rosso', + 'Yellow' => 'Giallo', + 'n/a' => 'n/d', + }, + }, + 'FilterEffectNeutral' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'FilterEffectPortrait' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'FilterEffectStandard' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'FilterEffectUnknown' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Red' => 'Rosso', + 'n/a' => 'n/d', + }, + }, + 'FilterEffectUserDef1' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Red' => 'Rosso', + 'n/a' => 'n/d', + }, + }, + 'FilterEffectUserDef2' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Red' => 'Rosso', + 'n/a' => 'n/d', + }, + }, + 'FilterEffectUserDef3' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Red' => 'Rosso', + 'n/a' => 'n/d', + }, + }, + 'FinalFrameBlocks' => 'Blocchi frame finali', + 'FinderDisplayDuringExposure' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FineSharpness' => { + PrintConv => { + 'Normal' => 'Normale', + 'Off' => 'Spento', + }, + }, + 'FirmwareDate' => 'Data firmware', + 'FirmwareID' => 'ID firmware', + 'FirmwareName' => 'Nome firmware', + 'FirmwareRevision' => 'Revisione firmware', + 'FirmwareRevision2' => 'Revisione 2 firmware', + 'FirmwareVersion' => 'Versione firmware', + 'FirstChar' => 'Primo carattere', + 'FirstObject' => 'Primo oggetto', + 'FirstObjectID' => 'ID primo oggetto', + 'FixtureIdentifier' => 'Identificatore d\'installazione', + 'Flags' => { + PrintConv => { + 'FileName' => 'Nome file', + 'Text' => 'Testo', + }, + }, + 'Flash' => { + PrintConv => { + 'Auto, Did not fire' => 'Auto, flash non emesso', + 'Auto, Did not fire, Red-eye reduction' => 'Auto, flash non emesso, riduzione occhi rossi', + 'Auto, Fired' => 'Auto, flash emesso', + 'Auto, Fired, Red-eye reduction' => 'Auto, flash emesso, riduzione occhi rossi', + 'Auto, Fired, Red-eye reduction, Return detected' => 'Auto, flash emesso, riduzione occhi rossi, luce di ritorno rilevata', + 'Auto, Fired, Red-eye reduction, Return not detected' => 'Auto, flash emesso, riduzione occhi rossi, luce di ritorno non rilevata', + 'Auto, Fired, Return detected' => 'Auto, flash emesso, luce di ritorno rilevata', + 'Auto, Fired, Return not detected' => 'Auto, flash emesso, luce di ritorno non rilevata', + 'Did not fire' => 'Flash non emesso', + 'Fired' => 'Emesso', + 'Fired, Red-eye reduction' => 'Emesso, riduzione occhi rossi', + 'Fired, Red-eye reduction, Return detected' => 'Emesso, riduzione occhi rossi, ritorno rilevato', + 'Fired, Red-eye reduction, Return not detected' => 'Emesso, riduzione occhi rossi, ritorno non rilevato', + 'Fired, Return detected' => 'Emesso, ritorno rilevato', + 'Fired, Return not detected' => 'Emesso, ritorno non rilevato', + 'Flash Fired' => 'Flash emesso', + 'No Flash' => 'Flash non emesso', + 'No flash function' => 'Nessuna funzione flash', + 'Off' => 'Flash non emesso, modalità flash forzata', + 'Off, Did not fire' => 'Flash non emesso, modalità flash forzata', + 'Off, Did not fire, Return not detected' => 'Off, Non emesso. Luce di ritorno non rilevata', + 'Off, No flash function' => 'Off, Nessuna funzione flash', + 'Off, Red-eye reduction' => 'Disattivato, riduzione occhi rossi', + 'On' => 'Flash emesso, modalità flash forzata', + 'On, Did not fire' => 'Attivato, non emesso', + 'On, Fired' => 'Attivato, emesso', + 'On, Red-eye reduction' => 'Attivato, riiduzione occhi rossi', + 'On, Red-eye reduction, Return detected' => 'Attivato, riiduzione occhi rossi, ritorno rilevato', + 'On, Red-eye reduction, Return not detected' => 'Attivato, riiduzione occhi rossi, ritorno non rilevato', + 'On, Return detected' => 'Attivato, ritorno rilevato', + 'On, Return not detected' => 'Attivato, ritorno non rilevato', + }, + }, + 'FlashBits' => { + PrintConv => { + 'External' => 'Esterno', + 'Manual' => 'Manuale', + }, + }, + 'FlashButtonFunction' => { + PrintConv => { + 'ISO speed' => 'Velocità ISO', + }, + }, + 'FlashColorFilter' => { + PrintConv => { + 'None' => 'Nessuno', + 'Red' => 'Rosso', + }, + }, + 'FlashCommanderMode' => { + Description => 'Modo Commander', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FlashCompensation' => 'Compensazione flash', + 'FlashControl' => 'Controllo flash', + 'FlashControlBuilt-in' => { + PrintConv => { + 'Manual' => 'Manuale', + 'Repeating Flash' => 'Ripetizione flash', + }, + }, + 'FlashControlMode' => { + Description => 'Modo Controllo Flash', + PrintConv => { + 'Auto Aperture' => 'Diaframma automatico', + 'Manual' => 'Manuale', + 'Off' => 'Spento', + 'Repeating Flash' => 'Ripetizione flash', + }, + }, + 'FlashDevice' => { + PrintConv => { + 'External' => 'Esterno', + 'None' => 'Nessuno', + }, + }, + 'FlashEnergy' => 'Energia del flash', + 'FlashExposureBracketValue' => 'Valore Esposizione a Forcella Flash', + 'FlashExposureComp' => 'Compensazione Esposizione Flash', + 'FlashExposureIndicator' => { + PrintConv => { + 'Not Indicated' => 'Non indicato', + }, + }, + 'FlashExposureIndicatorLast' => { + PrintConv => { + 'Not Indicated' => 'Non indicato', + }, + }, + 'FlashExposureIndicatorNext' => { + PrintConv => { + 'Not Indicated' => 'Non indicato', + }, + }, + 'FlashExposureLock' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FlashFired' => { + Description => 'Flash emesso', + PrintConv => { + 'External' => 'Esterno', + 'Yes' => 'Sì', + }, + }, + 'FlashFiring' => 'Emissione flash', + 'FlashFirmwareVersion' => 'Versione firmware flash', + 'FlashFocalLength' => 'Lunghezza focale flash', + 'FlashFunction' => { + Description => 'Funzione flash', + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'FlashGNDistance' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'FlashGroupAControlMode' => { + Description => 'Gruppo A Modo Controllo Flash', + PrintConv => { + 'Auto Aperture' => 'Diaframma automatico', + 'Manual' => 'Manuale', + 'Off' => 'Spento', + 'Repeating Flash' => 'Ripetizione flash', + }, + }, + 'FlashGroupBControlMode' => { + Description => 'Gruppo B Modo Controllo Flash', + PrintConv => { + 'Auto Aperture' => 'Diaframma automatico', + 'Manual' => 'Manuale', + 'Off' => 'Spento', + 'Repeating Flash' => 'Ripetizione flash', + }, + }, + 'FlashGroupCControlMode' => { + Description => 'Gruppo C Modo Controllo Flash', + PrintConv => { + 'Manual' => 'Manuale', + 'Off' => 'Spento', + 'Repeating Flash' => 'Ripetizione flash', + }, + }, + 'FlashInfoVersion' => 'Info Versione Flash', + 'FlashIntensity' => { + PrintConv => { + 'Low' => 'Basso', + 'Normal' => 'Normale', + 'Strong' => 'Forte', + }, + }, + 'FlashLevel' => { + Description => 'Livello flash', + PrintConv => { + 'Low' => 'Basso', + 'Normal' => 'Normale', + }, + }, + 'FlashMeteringMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FlashMode' => { + Description => 'Modo flash', + PrintConv => { + 'Did Not Fire' => 'Non emesso', + 'Fired, Commander Mode' => 'Emesso, Modalità Commander', + 'Fired, External' => 'Emesso Esterno', + 'Fired, Manual' => 'Emesso, Manuale', + 'Fired, TTL Mode' => 'Emesso, Modalità TTL', + 'Flash Off' => 'Flash spento', + 'Normal' => 'Normale', + 'Not Ready' => 'Non pronto', + 'Off' => 'Spento', + 'Off, Did not fire' => 'Spendo, non emesso', + 'Off?' => 'Spento?', + 'Red eye' => 'Occhi rossi', + 'Red-Eye' => 'Occhi rossi', + 'Red-Eye?' => 'Occhi rossi?', + 'Red-eye' => 'Occhi rossi', + 'Red-eye Reduction' => 'Riduzione occhi rossi', + 'Red-eye reduction' => 'Riduzione occhi rossi', + 'Unknown' => 'Sconosciuta', + }, + }, + 'FlashModel' => { + Description => 'Modello flash', + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'FlashOptions' => { + Description => 'Opzioni flash', + PrintConv => { + 'Normal' => 'Normale', + 'Red-eye reduction' => 'Riduzione occhi rossi', + }, + }, + 'FlashOptions2' => { + Description => 'Opzioni 2 flash', + PrintConv => { + 'Normal' => 'Normale', + 'Red-eye reduction' => 'Riduzione occhi rossi', + }, + }, + 'FlashRemoteControl' => { + Description => 'Telecomando flash', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FlashReturn' => 'Ritorno flash', + 'FlashSerialNumber' => 'Numero di serie del flash', + 'FlashSetting' => 'Impostazione flash', + 'FlashSource' => { + Description => 'Fonte flash', + PrintConv => { + 'External' => 'Esterno', + 'None' => 'Nessuno', + }, + }, + 'FlashStatus' => { + Description => 'Stato flash', + PrintConv => { + 'Off' => 'Spento', + 'Off (1)' => 'Spento (1)', + }, + }, + 'FlashSyncSpeed' => { + PrintConv => { + '1/250 s (auto FP)' => '1/250 s (piano focale auto)', + '1/320 s (auto FP)' => '1/320 s (piano focale auto)', + }, + }, + 'FlashSyncSpeedAv' => { + PrintConv => { + '1/200 Fixed' => '1/200 fisso', + '1/200-1/60 Auto' => '1/200-1/60 auto', + '1/250 Fixed' => '1/250 fisso', + '1/250-1/60 Auto' => '1/250-1/60 auto', + '1/300 Fixed' => '1/300 fisso', + '1/300-1/60 Auto' => '1/300-1/60 auto', + }, + }, + 'FlashType' => { + Description => 'Tipo di flash', + PrintConv => { + 'External' => 'Esterno', + 'None' => 'Nessuno', + 'Off' => 'Spento', + }, + }, + 'FlashVersion' => 'Versione del flash', + 'FlashWarning' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FlashpixVersion' => 'Versione Flashpix', + 'FlickerReduce' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'FlipHorizontal' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'FocalLength' => 'Lunghezza focale', + 'FocalLength35efl' => 'Lunghezza focale', + 'FocalLength35mm' => 'Lunghezza focale 35mm', + 'FocalLengthIn35mmFormat' => 'Lunghezza focale in formato 35mm', + 'FocalPlaneDiagonal' => 'Diagonale piano focale', + 'FocalPlaneResolutionUnit' => { + Description => 'Unità risoluzione piano focale', + PrintConv => { + 'None' => 'Nessuno', + 'inches' => 'pollici', + }, + }, + 'FocalPlaneXResolution' => 'Risoluzione X del piano focale', + 'FocalPlaneXSize' => 'Dimensione X del piano focale', + 'FocalPlaneXUnknown' => 'X del piano focale sconisciuta', + 'FocalPlaneYResolution' => 'Risoluzione Y del piano focale', + 'FocalPlaneYSize' => 'Dimensione Y del piano focale', + 'FocalPlaneYUnknown' => 'Y del piano focale sconisciuta', + 'FocalType' => { + PrintConv => { + 'Fixed' => 'Fisso', + }, + }, + 'Focus' => { + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'FocusContinuous' => { + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'FocusDisplayAIServoAndMF' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'FocusDistance' => 'Distanza fuoco', + 'FocusMode' => { + Description => 'Modalità messa a fuoco', + PrintConv => { + 'Manual' => 'Manuale', + 'Normal' => 'Normale', + 'n/a' => 'n/d', + }, + }, + 'FocusMode2' => { + Description => 'Modalità messa a fuoco 2', + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'FocusModeSetting' => { + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'FocusModeSwitch' => { + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'FocusPos' => 'Posizione fuoco', + 'FocusPosition' => 'Posizione fuoco', + 'FocusRange' => { + PrintConv => { + 'Manual' => 'Manuale', + 'Normal' => 'Normale', + 'Not Known' => 'Sconosciuto', + 'Very Close' => 'Molto vicino', + }, + }, + 'FocusStatus' => { + PrintConv => { + 'Failed' => 'Fallito', + 'Not confirmed' => 'Non confermato', + }, + }, + 'FocusTrackingLockOn' => { + PrintConv => { + '1 (Short)' => '1 (Breve)', + '1 Short' => '1 breve', + 'Normal' => 'Normale', + 'Off' => 'Spento', + 'Short' => 'Breve', + }, + }, + 'FontFamily' => { + Description => 'Tipo di carattere', + PrintConv => { + 'Swiss' => 'Svizzera', + }, + }, + 'FontFileName' => 'Nome file carattere', + 'FontName' => 'Nome carattere', + 'FontSize' => 'Dimensioni carattere', + 'FontSubfamily' => 'Sottotipo di carattere', + 'FontSubfamilyID' => 'ID sottotipo di carattere', + 'FontType' => 'Tipo di carattere', + 'FontVersion' => 'Versione carattere', + 'FontWeight' => 'Peso carattere', + 'Fonts' => 'Caratteri', + 'For' => 'Per', + 'Format' => 'Formato', + 'ForwardMatrix1' => 'Matrice predittiva 1', + 'ForwardMatrix2' => 'Matrice predittiva 2', + 'FrameRate' => 'Frequenza fotogramma', + 'FrameSize' => 'Dimensioni fotogrammi', + 'FreeByteCounts' => 'Byte disponibili', + 'FreeOffsets' => 'Offset disponibili', + 'FujiFlashMode' => { + PrintConv => { + 'External' => 'Esterno', + 'Off' => 'Spento', + 'Red-eye reduction' => 'Riduzione occhi rossi', + }, + }, + 'FuncButton' => { + PrintConv => { + 'Flash Off' => 'Flash spento', + 'None' => 'Nessuno', + 'Preview' => 'Anteprima', + 'Virtual Horizon' => 'Orizzonte virtuale', + }, + }, + 'FuncButtonPlusDials' => { + PrintConv => { + 'Choose Image Area' => 'Seleziona area immagine', + 'None' => 'Nessuno', + }, + }, + 'FunctionButton' => { + PrintConv => { + 'Flash Off' => 'Flash spento', + }, + }, + 'GDALMetadata' => 'Metadati GDAL', + 'GDALNoData' => 'Nessun dato GDAL', + 'GIFVersion' => 'Versione GIF', + 'GPSAltitude' => 'Altitudine GPS', + 'GPSAltitudeRef' => { + Description => 'Riferimento altitudine GPS', + PrintConv => { + 'Above Sea Level' => 'Sul livello del mare', + 'Below Sea Level' => 'Sotto il livello del mare', + }, + }, + 'GPSAreaInformation' => 'Informazioni area GPS', + 'GPSDOP' => 'Precisione misurazione GPS', + 'GPSDateStamp' => 'Data GPS', + 'GPSDateTime' => 'Ora GPS (orologio atomico)', + 'GPSDestBearing' => 'Direzione destinazione GPS', + 'GPSDestBearingRef' => { + Description => 'Riferimento direzione destinazione GPS', + PrintConv => { + 'Magnetic North' => 'Nord magnetico', + 'True North' => 'Nord geografico', + }, + }, + 'GPSDestDistance' => 'Distanza destinazione GPS', + 'GPSDestDistanceRef' => { + Description => 'Riferimento distanza destinazione GPS', + PrintConv => { + 'Kilometers' => 'Chilometri', + 'Miles' => 'Miglia', + 'Nautical Miles' => 'Miglia nautiche', + }, + }, + 'GPSDestLatitude' => 'Latitudine destinazione GPS', + 'GPSDestLatitudeRef' => { + Description => 'Riferimento latitudine destinazione GPS', + PrintConv => { + 'North' => 'Nord', + 'South' => 'Sud', + }, + }, + 'GPSDestLongitude' => 'Longitudine destinazione GPS', + 'GPSDestLongitudeRef' => { + Description => 'Riferimento per longitudine di destinazione', + PrintConv => { + 'East' => 'Est', + 'West' => 'Ovest', + }, + }, + 'GPSDifferential' => { + Description => 'Correzione differenziale GPS', + PrintConv => { + 'Differential Corrected' => 'Correzione differenziale applicata', + 'No Correction' => 'Misurazione senza correzione differenziale', + }, + }, + 'GPSHPositioningError' => 'Errore posizionamento orizzontale GPS', + 'GPSImgDirection' => 'Direzione di immagine', + 'GPSImgDirectionRef' => { + Description => 'Riferimento per direzione di immagine', + PrintConv => { + 'Magnetic North' => 'Nord magnetico', + 'True North' => 'Nord geografico', + }, + }, + 'GPSInfo' => 'Puntatore GPS Info IFD', + 'GPSLatitude' => 'Latitudine', + 'GPSLatitudeRef' => { + Description => 'Latitudine Nord o Sud', + PrintConv => { + 'North' => 'Latitudine Nord', + 'South' => 'Latitudine Sud', + }, + }, + 'GPSLongitude' => 'Longitudine', + 'GPSLongitudeRef' => { + Description => 'Longitudine Est o Ovest', + PrintConv => { + 'East' => 'Longitudine Est', + 'West' => 'Longitudine Ovest', + }, + }, + 'GPSMapDatum' => 'Dati di rilevamento geodetico utilizzati', + 'GPSMeasureMode' => { + Description => 'Modalità misurazione GPS', + PrintConv => { + '2-Dimensional Measurement' => 'Misurazione 2D', + '3-Dimensional Measurement' => 'Misurazione tridimensionale', + }, + }, + 'GPSProcessingMethod' => 'Nome metodo di elaborazione GPS', + 'GPSSatellites' => 'Satelliti GPS usati per la misurazione', + 'GPSSpeed' => 'Velocità ricevitore GPS', + 'GPSSpeedRef' => { + Description => 'Unità velocità', + PrintConv => { + 'km/h' => 'Chilometri all\'ora', + 'knots' => 'Nodi', + 'mph' => 'Miglie all\'ora', + }, + }, + 'GPSStatus' => { + Description => 'Stato ricevitore GPS', + PrintConv => { + 'Measurement Active' => 'Misurazione in corso', + 'Measurement Void' => 'Interoperatività misurazione', + }, + }, + 'GPSTimeStamp' => 'Ora GPS (orologio atomico)', + 'GPSTrack' => 'Direzione di movimento', + 'GPSTrackRef' => { + Description => 'Riferimento per direzione di movimento', + PrintConv => { + 'Magnetic North' => 'Direzione magnetica', + 'True North' => 'Direzione effettiva', + }, + }, + 'GPSVersionID' => 'Versione tag GPS', + 'GTModelType' => { + PrintConv => { + 'Projected' => 'Proiettato', + }, + }, + 'GainControl' => { + Description => 'Controllo di Guadagno', + PrintConv => { + 'High gain down' => 'Diminuire il guadagno alto', + 'High gain up' => 'Aumentare il guadagno alto', + 'Low gain down' => 'Diminuire il guadagno basso', + 'Low gain up' => 'Aumentare il guadagno basso', + 'None' => 'Nessuno', + }, + }, + 'GammaCompensatedValue' => 'Valore di Compensazione Gamma', + 'Gapless' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'Genre' => { + Description => 'Genere', + PrintConv => { + 'Acapella' => 'A cappella', + 'Acid Jazz' => 'Acid jazz', + 'Acid Punk' => 'Acid punk', + 'Acoustic' => 'Acustica', + 'Ambient' => 'Ambientale', + 'Avantgarde' => 'Avanguardia', + 'Ballad' => 'Ballata', + 'Bass' => 'Basso', + 'Big Band' => 'Big band', + 'Black Metal' => 'Black metal', + 'Booty Bass' => 'Basso booty', + 'Celtic' => 'Celtica', + 'Chamber Music' => 'Musica da camera', + 'Chorus' => 'Coro', + 'Christian Gangsta' => 'Gangsta cristiano', + 'Christian Rap' => 'Rap cristiano', + 'Christian Rock' => 'Rock cristiano', + 'Classic Rock' => 'Rock classico', + 'Classical' => 'Classica', + 'Club-House' => 'Club-house', + 'Comedy' => 'Commedia', + 'Contemporary C' => 'C contemporaneo', + 'Country' => 'Nazione', + 'Cover' => 'Copertina', + 'Dance Hall' => 'Dance hall', + 'Death Metal' => 'Death metal', + 'Drum & Bass' => 'Batteria e basso', + 'Drum Solo' => 'Batteria solo', + 'Duet' => 'Duetto', + 'Easy Listening' => 'Easy listening', + 'Electronic' => 'Elettronica', + 'Ethnic' => 'Etnica', + 'Euro-House' => 'Euro-house', + 'Euro-Techno' => 'Euro-techno', + 'Fast Fusion' => 'Fast fusion', + 'Folk-Rock' => 'Folk-rock', + 'Game' => 'Gioco', + 'Gothic' => 'Gotica', + 'Gothic Rock' => 'Rock gotico', + 'Hard Rock' => 'Hard rock', + 'Heavy Metal' => 'Heavy metal', + 'Hip-Hop' => 'Hip-hop', + 'Humour' => 'Umoristica', + 'Instrumental' => 'Strumentale', + 'Instrumental Pop' => 'Pop strumentale', + 'Instrumental Rock' => 'Rock strumentale', + 'Jazz+Funk' => 'Jazz+funk', + 'Latin' => 'Latina', + 'Lo-Fi' => 'Lo-fi', + 'Meditative' => 'Medidativa', + 'National Folk' => 'Folklore nazionale', + 'Native American' => 'Nativi americani', + 'New Age' => 'New age', + 'New Wave' => 'New wave', + 'Noise' => 'Rumore', + 'None' => 'Nessuno', + 'Oldies' => 'Vecchi successi', + 'Other' => 'Altro', + 'Polsk Punk' => 'Punk polacco', + 'Pop-Folk' => 'Pop-folk', + 'Pop/Funk' => 'Pop/funk', + 'Porn Groove' => 'Porn groove', + 'Power Ballad' => 'Power ballad', + 'Progressive Rock' => 'Progressive rock', + 'Psychadelic' => 'Psichedelica', + 'Psychedelic Rock' => 'Rock psichedelica', + 'Punk Rock' => 'Punk rock', + 'Retro' => 'Retrò', + 'Rhythmic Soul' => 'Rhythmic soul', + 'Rock & Roll' => 'Rock & roll', + 'Satire' => 'Satira', + 'Slow Jam' => 'Slow jam', + 'Slow Rock' => 'Slow rock', + 'Sound Clip' => 'Clip sonora', + 'Soundtrack' => 'Colonna sonora', + 'Southern Rock' => 'Southern rock', + 'Speech' => 'Parlato', + 'Symphonic Rock' => 'Rock sinfonico', + 'Symphony' => 'Sinfonia', + 'Techno-Industrial' => 'Techno-industrial', + 'Terror' => 'Terrore', + 'Thrash Metal' => 'Thrash metal', + 'Tribal' => 'Tribale', + 'Trip-Hop' => 'Trip-hop', + }, + }, + 'GenreID' => 'ID genere', + 'GeoTiffAsciiParams' => 'Parametri Geo Tiff Ascii', + 'GeoTiffDirectory' => 'Cartella Geo Tiff', + 'GeoTiffDoubleParams' => 'Parametri Geo Tiff Double', + 'GeogGeodeticDatum' => { + PrintConv => { + 'Lisbon' => 'Lisbona', + 'Stockholm 1938' => 'Stoccolma 1938', + }, + }, + 'GeogPrimeMeridian' => { + PrintConv => { + 'Lisbon' => 'Lisbona', + 'Rome' => 'Roma', + 'Stockholm' => 'Stoccolma', + }, + }, + 'GeographicType' => { + PrintConv => { + 'Greek' => 'Greco', + 'Lisbon' => 'Lisbona', + 'Stockholm 1938' => 'Stoccolma 1938', + }, + }, + 'Gradation' => { + Description => 'Ottimizzaz.', + PrintConv => { + 'Normal' => 'Normale', + 'n/a' => 'n/d', + }, + }, + 'GrayResponseCurve' => 'Curva di risposta Grigio', + 'GrayResponseUnit' => { + Description => 'Unità di risposta Grigio', + PrintConv => { + '1e-05' => '1e-005', + '1e-06' => '1e-006', + }, + }, + 'GrayTRC' => 'Curva Riproduzione Tono Grigio', + 'GreenMatrixColumn' => 'Colonna della Matrice Verde', + 'GreenTRC' => 'Curva Riproduziozne Tono Verde', + 'GridDisplay' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Grouping' => 'Raggruppamento', + 'HCUsage' => { + Description => 'Uso HC', + PrintConv => { + 'Line Art' => 'Line art', + }, + }, + 'HDR' => { + Description => 'HDR auto', + PrintConv => { + 'Off' => 'Disattivata', + }, + }, + 'HDRSetting' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'HalftoneHints' => 'Suggerimenti mezzi toni', + 'HandlerType' => { + PrintConv => { + 'Text' => 'Testo', + }, + }, + 'Headline' => 'Intestazione', + 'HeightResolution' => 'Risoluzione altezza', + 'HighISONoiseReduction' => { + Description => 'Riduzione Rumore High ISO', + PrintConv => { + 'Auto' => 'Automatico', + 'High' => 'Hi', + 'Low' => 'Leggero', + 'Normal' => 'Normale', + 'Off' => 'Disattivata', + 'Strong' => 'Forte', + 'n/a' => 'n/d', + }, + }, + 'HighSpeedSync' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'HighlightTonePriority' => { + PrintConv => { + 'Enable' => 'Abilita', + 'Off' => 'Spento', + }, + }, + 'HometownCity' => { + PrintConv => { + 'Jerusalem' => 'Gerusalemme', + 'Lisbon' => 'Lisbona', + 'London' => 'Londra', + 'Milan' => 'Milano', + 'Prague' => 'Praga', + 'Rome' => 'Roma', + 'Stockholm' => 'Stoccolma', + 'Tehran' => 'Teheran', + }, + }, + 'HometownDST' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'HostComputer' => 'Computer ospite', + 'HotKey' => { + PrintConv => { + '(none)' => '(nessuno)', + }, + }, + 'Hue' => { + Description => 'Tonalità', + PrintConv => { + 'None' => 'Nessuno', + 'Normal' => 'Normale', + }, + }, + 'HueAdjustment' => 'Regolazione Hue', + 'ICCProfile' => 'Profili ICC', + 'IDCCreativeStyle' => { + PrintConv => { + 'Camera Setting' => 'Impostazioni fotocamera', + 'Landscape' => 'Orizzontale', + 'Light' => 'Chiara', + 'Real' => 'Reale', + 'Sepia' => 'Seppia', + 'Sunset' => 'Tramonto', + 'Vivid' => 'Vivace', + }, + }, + 'INGRReserved' => 'Riseervato a INGR', + 'IPTC-NAA' => 'Metadati IPTC-NAA', + 'IPTCBitsPerSample' => 'Numero di Bits per Campione', + 'IPTCImageHeight' => 'Numero di linee', + 'IPTCImageRotation' => { + Description => 'Rotazione Immagine', + PrintConv => { + '0' => 'Nessuna rotazione', + '180' => 'Rotazione di 180 gradi', + '270' => 'Rotazione di 270 gradi', + '90' => 'Rotazione di 90 gradi', + }, + }, + 'IPTCImageWidth' => 'Pixels per linea', + 'IPTCPictureNumber' => 'Numero Immagine', + 'ISO' => 'Sensibilità ISO', + 'ISOAuto' => { + Description => 'ISO auto', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ISOAutoParameters' => { + Description => 'Parametri ISO auto', + PrintConv => { + 'Fast' => 'Veloce', + }, + }, + 'ISODisplay' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ISOExpansion' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ISOExpansion2' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ISOInfo' => 'Info ISO', + 'ISOSelection' => 'Selezione ISO', + 'ISOSensitivityStep' => 'Passo sensibilità ISO', + 'ISOSetting' => { + Description => 'Impostazione ISO', + PrintConv => { + '100 (Low)' => '100 (basso)', + 'Manual' => 'Manuale', + 'n/a' => 'n/d', + }, + }, + 'ISOSpeed' => 'Velocità ISO', + 'ISOSpeedExpansion' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'ISOSpeedIncrements' => { + PrintConv => { + '1 Stop' => '1 stop', + '1/3 Stop' => '1/3 stop', + }, + }, + 'ISOSpeedLatitudeyyy' => 'Velocità ISO - Latitudine yyy', + 'ISOSpeedLatitudezzz' => 'Velocità ISO - Latitudine zzz', + 'ISOSpeedRange' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'ISOValue' => 'Valore ISO', + 'IT8Header' => 'Intestazione IT8', + 'Illumination' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Image::ExifTool::AIFF::Comment' => 'Commento AIFF', + 'Image::ExifTool::EXE::CHM' => 'EXE CHM', + 'Image::ExifTool::EXE::ELF' => 'EXE ELF', + 'Image::ExifTool::EXE::MachO' => 'EXE MachO', + 'Image::ExifTool::EXE::Main' => 'EXE', + 'Image::ExifTool::EXE::PEF' => 'EXE PEF', + 'Image::ExifTool::EXE::PEString' => 'EXE PEString', + 'Image::ExifTool::EXE::PEVersion' => 'EXE PEVersion', + 'Image::ExifTool::Exif::Main' => 'Exif', + 'Image::ExifTool::FLAC::Main' => 'FLAC', + 'Image::ExifTool::Flash::Main' => 'Flash', + 'Image::ExifTool::Flash::Video' => 'Video flash', + 'Image::ExifTool::FlashPix::Main' => 'FlashPix', + 'Image::ExifTool::Font::Main' => 'Carattere', + 'Image::ExifTool::Font::Name' => 'Nome carattere', + 'Image::ExifTool::Font::PFM' => 'Carattere PFM', + 'Image::ExifTool::GPS::Main' => 'GPS', + 'Image::ExifTool::ID3::Private' => 'ID3 privato', + 'Image::ExifTool::ID3::v1' => 'ID3 v1', + 'Image::ExifTool::ID3::v1_Enh' => 'ID3 v1_Enh', + 'Image::ExifTool::ID3::v2_2' => 'ID3 v2_2', + 'Image::ExifTool::ID3::v2_3' => 'ID3 v2_3', + 'Image::ExifTool::ID3::v2_4' => 'ID3 v2_4', + 'Image::ExifTool::ITC::Main' => 'ITC', + 'Image::ExifTool::JFIF::Main' => 'JFIF', + 'Image::ExifTool::Kodak::DcMD' => 'Kodak DcMD', + 'Image::ExifTool::Kodak::Free' => 'Kodak Free', + 'Image::ExifTool::Kodak::KDC_IFD' => 'KDC_IFD Kodak', + 'Image::ExifTool::Kodak::Main' => 'Kodak', + 'Image::ExifTool::Microsoft::Stitch' => 'Microsoft Stitch', + 'Image::ExifTool::Olympus::AVI' => 'AVI Olympus', + 'Image::ExifTool::Olympus::Main' => 'Olympus', + 'Image::ExifTool::PSP::Creator' => 'Creatore PSP', + 'Image::ExifTool::PSP::Image' => 'Immagine PSP', + 'Image::ExifTool::PSP::Main' => 'PSP', + 'Image::ExifTool::PanasonicRaw::Main' => 'PanasonicRaw', + 'Image::ExifTool::PostScript::Main' => 'PostScript', + 'Image::ExifTool::PrintIM::Main' => 'PrintIM', + 'Image::ExifTool::Qualcomm::Main' => 'Qualcomm', + 'Image::ExifTool::QuickTime::Main' => 'QuickTime', + 'Image::ExifTool::RIFF::Main' => 'RIFF', + 'Image::ExifTool::Radiance::Main' => 'Radianza', + 'Image::ExifTool::Rawzor::Main' => 'Rawzor', + 'Image::ExifTool::Real::AudioV3' => 'Real AudioV3', + 'Image::ExifTool::Real::AudioV4' => 'Real AudioV4', + 'Image::ExifTool::Real::AudioV5' => 'Real AudioV5', + 'Image::ExifTool::Ricoh::AVI' => 'AVI Ricoh', + 'Image::ExifTool::Ricoh::FaceInfo' => 'FaceInfo Ricoh', + 'Image::ExifTool::Ricoh::FirmwareInfo' => 'Info firmware Ricoh', + 'Image::ExifTool::Ricoh::ImageInfo' => 'Info immagine Ricoh', + 'Image::ExifTool::Ricoh::Main' => 'Ricoh', + 'Image::ExifTool::Ricoh::RMETA' => 'RMETA Ricoh', + 'Image::ExifTool::Ricoh::SerialInfo' => 'Info seriale Ricoh', + 'Image::ExifTool::Ricoh::Subdir' => 'Sotto-cartella Ricoh', + 'Image::ExifTool::Ricoh::Text' => 'Testo Ricoh', + 'Image::ExifTool::Samsung::INFO' => 'Samsung INFO', + 'Image::ExifTool::Samsung::MP4' => 'Samsung MP4', + 'Image::ExifTool::Samsung::PictureWizard' => 'Samsung PictureWizard', + 'Image::ExifTool::Sanyo::Main' => 'Sanyo', + 'Image::ExifTool::Sigma::Main' => 'Sigma', + 'ImageAdjustment' => 'Regolazione Immagine', + 'ImageAlterationConstraints' => { + PrintConv => { + 'No Merging' => 'Nessuna fusione', + }, + }, + 'ImageAuthentication' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ImageByteCount' => 'Numero di byte dell\'immagine', + 'ImageColorIndicator' => { + Description => 'Indicazione colore dell\'immagine', + PrintConv => { + 'Specified Image Color' => 'Colore immagine specificato', + 'Unspecified Image Color' => 'Colore immagine non specificato', + }, + }, + 'ImageColorValue' => 'Valore colore dell\'immagine', + 'ImageCount' => 'Conteggio Immagini', + 'ImageDataDiscard' => { + Description => 'Dati scartati dell\'immagine', + PrintConv => { + 'Flexbits Discarded' => 'Flexbit scartati', + 'Full Resolution' => 'Risoluzione completa', + 'HighPass Frequency Data Discarded' => 'Dati in frequenza passa-alto scartati', + 'Highpass and LowPass Frequency Data Discarded' => 'Dati in frequenza passa-alto e passa-basso scartati', + }, + }, + 'ImageDataSize' => 'Dimensione Dati Immagine', + 'ImageDepth' => 'Profondità Immagine', + 'ImageDescription' => 'Descrizione Immagine', + 'ImageDustOff' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ImageEditing' => { + PrintConv => { + 'None' => 'Nessuno', + 'Resized' => 'Ridimensionato', + }, + }, + 'ImageHeight' => 'Altezza immagine', + 'ImageHistory' => 'Cronologia immagine', + 'ImageID' => 'ID Immagine', + 'ImageLayer' => 'Livello immagine', + 'ImageNumber' => 'Numero immagine', + 'ImageOffset' => 'Offset immagine', + 'ImageOptimization' => 'Ottimizzazione Immagine', + 'ImageOrientation' => { + Description => 'Orientamento Immagine', + PrintConv => { + 'Landscape' => 'Orizzontale', + 'Portrait' => 'Verticale', + 'Square' => 'Quadrata', + }, + }, + 'ImageQuality' => { + Description => 'Qualità Immagine', + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'ImageReview' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ImageRotated' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'ImageRotation' => { + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'ImageSize' => 'Dimensioni immagini', + 'ImageSourceData' => 'Dati origine immagine', + 'ImageStabilization' => { + Description => 'Stabilizzazione Immagine', + PrintConv => { + 'Off' => 'Spento', + 'Off (1)' => 'Spento (1)', + 'Off (2)' => 'Spento (2)', + 'n/a' => 'n/d', + }, + }, + 'ImageStabilizationSetting' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ImageStyle' => { + PrintConv => { + 'Landscape' => 'Orizzontale', + 'Sunset' => 'Tramonto', + 'Vivid' => 'Vivace', + }, + }, + 'ImageTone' => { + PrintConv => { + 'Landscape' => 'Orizzontale', + 'Portrait' => 'Verticale', + 'Radiant' => 'Radiante', + }, + }, + 'ImageType' => { + Description => 'Tipo immagine', + PrintConv => { + 'Page' => 'Pagina', + 'Preview' => 'Anteprima', + }, + }, + 'ImageUniqueID' => 'ID unico immagine', + 'ImageVersion' => 'Versione immagine', + 'ImageWidth' => 'Larghezza immagine', + 'Index' => 'Indice', + 'Indexable' => { + PrintConv => { + 'False' => 'Falso', + }, + }, + 'Indexed' => { + Description => 'Indicizzato', + PrintConv => { + 'Indexed' => 'Indicizzato', + 'Not indexed' => 'Non indicizzato', + }, + }, + 'InfraredIlluminator' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'InitialDisplayEffect' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'InitialKey' => 'Chiave iniziale', + 'InitializedDataSize' => 'Dimensione dati inizializzati', + 'InkNames' => 'Nomi inchiostri', + 'InkSet' => { + Description => 'Gruppo inchiostri', + PrintConv => { + 'Not CMYK' => 'Non CMYK', + }, + }, + 'Instructions' => 'Istruzioni', + 'IntellectualGenre' => 'Genere Intellettuale', + 'IntelligentAuto' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'IntelligentContrast' => { + PrintConv => { + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'IntelligentD-Range' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'IntelligentExposure' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'IntelligentResolution' => { + PrintConv => { + 'Extended' => 'Esteso', + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'IntensityStereo' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Interlace' => { + Description => 'Interlacciamento', + PrintConv => { + 'Noninterlaced' => 'Non interlacciato', + 'Progressive' => 'Progressivo', + }, + }, + 'InterleavedField' => { + PrintConv => { + 'Even' => 'Pari', + }, + }, + 'InternalFlash' => { + PrintConv => { + 'Fired' => 'Flash emesso', + 'Manual' => 'Manuale', + 'No' => 'Flash non emesso', + 'Off' => 'Spento', + 'Repeating Flash' => 'Ripetizione flash', + }, + }, + 'InternalName' => 'Nome interno', + 'InternetRadioStationName' => 'Nome stazione radio internet', + 'InternetRadioStationOwner' => 'Proprietario stazione radio internet', + 'InternetRadioStationURL' => 'URL stazione radio internet', + 'InteropIndex' => { + Description => 'Identificazione interoperatività', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03 - file opzioni DCF (Adobe RGB)', + 'R98 - DCF basic file (sRGB)' => 'R98 - file base DCF (sRGB)', + 'THM - DCF thumbnail file' => 'File miniatura THM - DCF', + }, + }, + 'InteropOffset' => 'Etichetta interoperatività', + 'InteropVersion' => 'Versione interoperatività', + 'InterpretedBy' => 'Interpetrato da', + 'InvolvedPeople' => 'Persone coinvolte', + 'Italic' => 'Corsivo', + 'JFIFVersion' => 'Versione JFIF', + 'JPEGACTables' => 'Tabelle JPEGAC', + 'JPEGDCTables' => 'Tabelle JPEGDC', + 'JPEGLosslessPredictors' => 'Predittori JPEG senza perdita', + 'JPEGPointTransforms' => 'Trasformazioni puntiali JPEG', + 'JPEGProc' => { + Description => 'Proc JPEG', + PrintConv => { + 'Baseline' => 'Linea di base', + 'Lossless' => 'Senza perdita', + }, + }, + 'JPEGQTables' => 'Tabelle JPEGQ', + 'JPEGQuality' => { + Description => 'Qualità', + PrintConv => { + 'Extra Fine' => 'Extra fine', + 'Standard' => 'Normale', + 'n/a' => 'n/d', + }, + }, + 'JPEGRestartInterval' => 'Intervallo riavvio JPEG', + 'JPEGTables' => 'Tabelle JPEG', + 'JobID' => 'ID processo', + 'JobName' => 'Nome processo', + 'JobRef' => 'Rif processo', + 'JobStatus' => 'Stato processo', + 'JpgFromRaw' => 'JPG da raw', + 'JpgFromRawLength' => 'Lunghezza JPG da raw', + 'JpgFromRawStart' => 'Inizio JPG da raw', + 'Keyword' => 'Parola chiave', + 'KeywordInfo' => 'Info parola chiave', + 'Keywords' => 'Parole chiave', + 'Kinds' => 'Tipi', + 'LCDIllumination' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'LCDIlluminationDuringBulb' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'LCHEditor' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Language' => 'Lingua', + 'LanguageCode' => { + Description => 'Codice lingua', + PrintConv => { + 'Albanian' => 'Albanese', + 'Arabic' => 'Arabo', + 'Armenian' => 'Armeno', + 'Azeri' => 'Azero', + 'Basque' => 'Basco', + 'Belarusian' => 'Bielorusso', + 'Breton' => 'Bretone', + 'Bulgarian' => 'Bulgaro', + 'Catalan' => 'Catalano', + 'Chinese (Simplified)' => 'Cinese (semplificato)', + 'Chinese (Traditional)' => 'Cinese (tradizionale)', + 'Croato-Serbian (Latin)' => 'Serbo-croato (alfabeto latino)', + 'Czech' => 'Ceco', + 'Danish' => 'Danese', + 'Dutch' => 'Olandese', + 'Dutch (Belgian)' => 'Olandese (Belgio)', + 'English (Australian)' => 'Inglese (Australia)', + 'English (British)' => 'Inglese (Gran Bretagna)', + 'English (Canadian)' => 'Inglese (Canada)', + 'English (U.S.)' => 'Inglese (U.S.A.)', + 'Estonian' => 'Estone', + 'Farsi' => 'Persiano', + 'Finnish' => 'Finlandese', + 'French' => 'Francese', + 'French (Belgian)' => 'Francese (Belgio)', + 'French (Canadian)' => 'Francese (Canada)', + 'French (Swiss)' => 'Francese (Svizzera)', + 'Gaelic' => 'Gaelico', + 'Galician' => 'Galiziano', + 'Georgian' => 'Georgiano', + 'German' => 'Tedesco', + 'German (Austrian)' => 'Tedesco (Austria)', + 'German (Swiss)' => 'Tedesco (Svizzera)', + 'Greek' => 'Greco', + 'Hebrew' => 'Ebraico', + 'Hungarian' => 'Ungherese', + 'Icelandic' => 'Islandese', + 'Indonesian' => 'Indonesiano', + 'Invariant' => 'Invariante', + 'Italian' => 'Italiano', + 'Italian (Swiss)' => 'Italiano (Svizzera)', + 'Japanese' => 'Giapponese', + 'Kazak' => 'Kazako', + 'Korean' => 'Coreano', + 'Kyrgyz' => 'Kirghizo', + 'Latvian' => 'Lettone', + 'Lithuanian' => 'Lituano', + 'Macedonian' => 'Macedone', + 'Mongolian' => 'Mongolo', + 'Nepali' => 'Nepalese', + 'Neutral' => 'Neutrale', + 'Neutral 2' => 'Neutrale 2', + 'Norwegian (Bokml)' => 'Norvegese (BokmÃ¥l)', + 'Norwegian (Nynorsk)' => 'Norvegese (Nynorsk)', + 'Polish' => 'Polacco', + 'Portuguese' => 'Portogese', + 'Portuguese (Brazilian)' => 'Portoghese (Brasile)', + 'Process default' => 'Predefinita del processo', + 'Rhaeto-Romanic' => 'Reto-romanico', + 'Romanian' => 'Rumeno', + 'Russian' => 'Russo', + 'Sanskrit' => 'Sanscrito', + 'Serbo-Croatian (Cyrillic)' => 'Serbo-croato (cirillico)', + 'Slovak' => 'Slovacco', + 'Slovenian' => 'Sloveno', + 'Spanish (Castilian)' => 'Spagnolo (Castiglia)', + 'Spanish (Mexican)' => 'Spagnolo (Messico)', + 'Spanish (Modern)' => 'Spagnolo moderno', + 'Swedish' => 'Svedese', + 'Syriac' => 'Siriaco', + 'Turkish' => 'Turco', + 'Ukrainian' => 'Ucraino', + 'Uzbek' => 'Usbeco', + 'Vietnamese' => 'Vietnamita', + 'Welsh' => 'Gallese', + }, + }, + 'LanguageIdentifier' => 'Identificativo lingua', + 'LanguageList' => 'Lista lingue', + 'LanguageName' => 'Nome lingua', + 'LateralChromaticAberration' => { + PrintConv => { + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'Layout' => { + PrintConv => { + 'Tiles' => 'Tasselli', + }, + }, + 'LegalCopyright' => 'Copyright legali', + 'LegalTrademarks' => 'Marchi legali', + 'Length' => 'Durata', + 'Lens' => 'Obiettivo', + 'Lens35efl' => 'Obiettivo', + 'LensDataVersion' => 'Versione Dati Obiettivo', + 'LensFStops' => 'F-Stops Obiettivo', + 'LensID' => { + Description => 'Obiettivo Utilizzato', + PrintConv => { + 'Ricoh Lens A16 24-85mm F3.5-5.5' => 'Obiettivo Ricoh A16 24-85mm F3.5-5.5', + 'Ricoh Lens P10 28-300mm F3.5-5.6 VC' => 'Obiettivo Ricoh P10 28-300mm F3.5-5.6 VC', + 'Ricoh Lens S10 24-70mm F2.5-4.4 VC' => 'Obiettivo Ricoh S10 24-70mm F2.5-4.4 VC', + }, + }, + 'LensIDNumber' => 'Numero ID Obiettivo', + 'LensInfo' => 'Informazioni obiettivo', + 'LensMake' => 'Marca obiettivo', + 'LensModel' => 'Modello obiettivo', + 'LensSerialNumber' => 'Numero di serie obiettivo', + 'LensShutterLock' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'LensSpec' => 'Obiettivo', + 'LensType' => { + Description => 'Tipo obiettivo', + PrintConv => { + '02 Standard Zoom 5-15mm F2.8-4.5' => '02 Zoom standard 5-15mm F2.8-4.5', + '04 Toy Lens Wide 6.3mm F7.1' => '04 Grandangolare toy 6.3mm F7.1', + '05 Toy Lens Telephoto 18mm F8' => '05 Teleobiettivo toy 18mm F8', + '1.4x Teleconverter' => 'Moltiplicatore di focale 1.4x', + 'None' => 'Nessuno', + 'Sigma 100-300mm F4 EX (APO (D) or D IF)' => 'Sigma 100-300mm F4 EX (APO (D) o D IF)', + }, + }, + 'LevelGaugePitch' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'LevelGaugeRoll' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'LevelOrientation' => { + PrintConv => { + 'Horizontal (normal)' => 'Orizzontale', + 'Rotate 180' => 'Ruota di 180°', + 'Rotate 270 CW' => 'Ruota di 270° in senso orario', + 'Rotate 90 CW' => 'Ruota di 90° senso orario', + }, + }, + 'License' => 'Licenza', + 'LicenseID' => 'ID licenza', + 'LicenseType' => { + PrintConv => { + 'Public Domain' => 'Pubblico dominio', + 'Unknown' => 'Sconosciuto', + }, + }, + 'LightCondition' => 'Condizione luce', + 'LightSource' => { + Description => 'Sorgente di luce', + PrintConv => { + 'Cloudy' => 'Nuvoloso', + 'Cool White Fluorescent' => 'Fluorescente a luce calda', + 'Day White Fluorescent' => 'Fluorescente a luce del giorno bianca', + 'Daylight' => 'Luce del giorno', + 'Daylight Fluorescent' => 'Fluorescente a luce del giorno', + 'Evening Sunlight' => 'Luce solare serale', + 'Fine Weather' => 'Bel tempo', + 'Fluorescent' => 'Fluorescente', + 'ISO Studio Tungsten' => 'Tungsteno studio ISO', + 'Other' => 'Altra Sorgente di Luce', + 'Shade' => 'Ombrato', + 'Standard Light A' => 'Luce standard A', + 'Standard Light B' => 'Luce standard B', + 'Standard Light C' => 'Luce standard C', + 'Tungsten (Incandescent)' => 'Tungsteno (luce incandescente)', + 'Unknown' => 'Sconosciuto', + 'Warm White Fluorescent' => 'Luce fluorescente bianca calda', + 'White Fluorescent' => 'Fluorescente bianca', + }, + }, + 'LightSourceSpecial' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'LightingMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Lightness' => 'Luce', + 'Line' => 'Linea', + 'LineOrder' => { + PrintConv => { + 'Random Y' => 'Casuale Y', + }, + }, + 'LinearResponseLimit' => 'Limite risposta lineare', + 'LinearizationTable' => 'Tabella di linearizzazione', + 'Lines' => 'Linee', + 'LinkAEToAFPoint' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'LinkerVersion' => 'Versione linker', + 'Lit' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'LiveViewAFMethod' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'LiveViewAFSetting' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'LiveViewFocusMode' => { + PrintConv => { + 'Manual' => 'Manuale', + 'n/a' => 'n/d', + }, + }, + 'LiveViewMetering' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'LiveViewShooting' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'LocalDeltaType' => { + PrintConv => { + 'Absolute' => 'Assoluto', + }, + }, + 'LocalizedCameraModel' => 'Modello fotocamera localizzato', + 'Location' => 'Località', + 'LocationKind' => 'Tipo località', + 'LocationName' => 'Nome località', + 'LocationNote' => 'Note località', + 'LongDescription' => 'Descrizione estesa', + 'LongExposureNoiseReduction' => { + Description => 'Espososizione lunga riduzione rumore', + PrintConv => { + 'Off' => 'Spento', + 'Off (65535)' => 'Spento (65535)', + 'On' => 'Attivata', + 'n/a' => 'n/d', + }, + }, + 'LongExposureNoiseReduction2' => { + Description => 'Espososizione lunga riduzione rumore 2', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'LongText' => 'Testo lungo', + 'LoopStyle' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'LuminanceNoiseReduction' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'Lyricist' => 'Paroliere', + 'Lyrics' => 'Testo', + 'MCUVersion' => 'Versione MCU', + 'MDColorTable' => 'Tabella colori MD', + 'MDFileTag' => 'Tag file MD', + 'MDFileUnits' => 'Unità file MD', + 'MDLabName' => 'Nome lab MD', + 'MDPrepDate' => 'Data prep MD', + 'MDPrepTime' => 'Ora prep MD', + 'MDSampleInfo' => 'Info campione MD', + 'MDScalePixel' => 'Scala pixel MD', + 'MIEVersion' => 'Versione MIE', + 'MIMEType' => 'Tipo mime', + 'MIMETypeOfEncapsulatedDocument' => 'Tipo mime del documento incapsulato', + 'MSDocumentText' => 'Testo documento MS', + 'MSDocumentTextPosition' => 'Posizione testo documento MS', + 'MSPropertySetStorage' => 'Gruppo di memoria proprietà MS', + 'MSStereo' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MachineType' => { + Description => 'Tipo macchina', + PrintConv => { + 'ARM little endian' => 'ARM little-endian', + 'Alpha AXP (old)' => 'Alpha AXP (precedente)', + 'EFI Byte Code' => 'Byte code EFI', + 'Intel 386 or later, and compatibles' => 'Intel 386 o successivo e compatibili', + 'MIPS little endian (R4000)' => 'MIPS little-endian (R4000)', + 'MIPS little endian WCI v2' => 'MIPS little-endian WCI v2', + 'MIPS with FPU' => 'MIPS con FPU', + 'MIPS16 with FPU' => 'MIPS16 con FPU', + 'Mitsubishi M32R little endian' => 'Mitsubishi M32R little-endian', + 'Motorola 68000 series' => 'Serie Motorola 68000', + 'PowerPC little endian' => 'PowerPC little-endian', + 'PowerPC with floating point support' => 'PowerPC con supporto numeri in in virgola mobile', + 'clr pure MSIL' => 'clr con MSIL puro', + }, + }, + 'Macro' => { + PrintConv => { + 'Manual' => 'Manuale', + 'Normal' => 'Normale', + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'MacroLED' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MacroMode' => { + PrintConv => { + 'Normal' => 'Normale', + 'Off' => 'Spento', + }, + }, + 'MagicFilter' => { + PrintConv => { + 'Fish Eye' => 'Fish-eye', + 'Fragmented' => 'Frammentato', + 'Gentle Sepia' => 'Seppia leggero', + 'Off' => 'Spento', + 'Reflection' => 'Riflessione', + }, + }, + 'MainDialExposureComp' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Make' => 'Costruttore', + 'MakeAndModel' => 'Marca e Modello', + 'MakerNote' => 'Dati DNG Privati', + 'MakerNoteCanon' => 'Note marca Canon', + 'MakerNoteCasio' => 'Note marca Casio', + 'MakerNoteCasio2' => 'Note marca Casio 2', + 'MakerNoteFujiFilm' => 'Note marca Fuji Film', + 'MakerNoteGE' => 'Note marca GE', + 'MakerNoteGE2' => 'Note marca GE2', + 'MakerNoteHP' => 'Note marca HP', + 'MakerNoteHP2' => 'Note marca HP2', + 'MakerNoteHP4' => 'Note marca HP4', + 'MakerNoteHP6' => 'Note marca HP6', + 'MakerNoteHasselblad' => 'Note marca Hasselblad', + 'MakerNoteISL' => 'Note marca ISL', + 'MakerNoteJVC' => 'Note marca JVC', + 'MakerNoteJVCText' => 'Note marca JVC - Testo', + 'MakerNoteKodak10' => 'Note marca Kodak 10', + 'MakerNoteKodak1a' => 'Note marca Kodak 1a', + 'MakerNoteKodak1b' => 'Note marca Kodak 1b', + 'MakerNoteKodak2' => 'Note marca Kodak 2', + 'MakerNoteKodak3' => 'Note marca Kodak 3', + 'MakerNoteKodak4' => 'Note marca Kodak 4', + 'MakerNoteKodak5' => 'Note marca Kodak 5', + 'MakerNoteKodak6a' => 'Note marca Kodak 6a', + 'MakerNoteKodak6b' => 'Note marca Kodak 6b', + 'MakerNoteKodak7' => 'Note marca Kodak 7', + 'MakerNoteKodak8a' => 'Note marca Kodak 8a', + 'MakerNoteKodak8b' => 'Note marca Kodak 8b', + 'MakerNoteKodak9' => 'Note marca Kodak 9', + 'MakerNoteKodakUnknown' => 'Note marca Kodak sconosciuta', + 'MakerNoteKyocera' => 'Note marca Kyocera', + 'MakerNoteLeica' => 'Note marca Leica', + 'MakerNoteLeica2' => 'Note marca Leica 2', + 'MakerNoteLeica3' => 'Note marca Leica 3', + 'MakerNoteLeica4' => 'Note marca Leica 4', + 'MakerNoteLeica5' => 'Note marca Leica 5', + 'MakerNoteLeica6' => 'Note marca Leica 6', + 'MakerNoteMinolta' => 'Note marca Minolta', + 'MakerNoteMinolta2' => 'Note marca Minolta 2', + 'MakerNoteMinolta3' => 'Note marca Minolta 3', + 'MakerNoteNikon' => 'Note marca Nikon', + 'MakerNoteNikon2' => 'Note marca Nikon 2', + 'MakerNoteNikon3' => 'Note marca Nikon 3', + 'MakerNoteOlympus' => 'Note marca Olympus', + 'MakerNoteOlympus2' => 'Note marca Olympus 2', + 'MakerNotePanasonic' => 'Note marca Panasonic', + 'MakerNotePanasonic2' => 'Note marca Panasonic 2', + 'MakerNotePentax' => 'Note marca Pentax', + 'MakerNotePentax2' => 'Note marca Pentax 2', + 'MakerNotePentax3' => 'Note marca Pentax 3', + 'MakerNotePentax4' => 'Note marca Pentax 4', + 'MakerNotePentax5' => 'Note marca Pentax 5', + 'MakerNotePentax6' => 'Note marca Pentax 6', + 'MakerNotePhaseOne' => 'Note marca Phase One', + 'MakerNoteReconyx' => 'Note marca Reconyx', + 'MakerNoteRicoh' => 'Note marca Ricoh', + 'MakerNoteRicohText' => 'Note marca Ricoh - Testo', + 'MakerNoteSafety' => { + Description => 'Note marca Safety', + PrintConv => { + 'Safe' => 'Sicuro', + 'Unsafe' => 'Non sicuro', + }, + }, + 'MakerNoteSamsung1a' => 'Note marca Samsung 1a', + 'MakerNoteSamsung1b' => 'Note marca Samsung 1b', + 'MakerNoteSamsung2' => 'Note marca Samsung 2', + 'MakerNoteSanyo' => 'Note marca Sanyo', + 'MakerNoteSanyoC4' => 'Note marca Sanyo C4', + 'MakerNoteSanyoPatch' => 'Note marca Sanyo Patch', + 'MakerNoteSigma' => 'Note marca Sigma', + 'MakerNoteSony' => 'Note marca Sony', + 'MakerNoteSony2' => 'Note marca Sony 2', + 'MakerNoteSony3' => 'Note marca Sony 3', + 'MakerNoteSony4' => 'Note marca Sony 4', + 'MakerNoteSonyEricsson' => 'Note marca Sony Ericsson', + 'MakerNoteSonySRF' => 'Note marca Sony SRF', + 'MakerNoteUnknown' => 'Note marca sconosciuta', + 'MakerNoteUnknownText' => 'Note marca sconosciuta - Testo', + 'MakerNoteVersion' => 'Note Versione Costruttore', + 'MakerNotes' => 'Note produttore', + 'ManualFlashOutput' => { + PrintConv => { + 'Low' => 'Basso', + 'n/a' => 'n/d', + }, + }, + 'ManualFocusDistance' => 'Messa a Fuoco Manuale', + 'MarkerID' => 'ID marker', + 'MaskedAreas' => 'Aree mascherate', + 'MasterDocumentID' => 'ID Documento Principale', + 'Matteing' => 'Opacizzazione', + 'MaxAperture' => 'Massima apertura delle lenti', + 'MaxApertureAtMaxFocal' => 'Diaframma massimo alla focale massima', + 'MaxApertureAtMinFocal' => 'Diaframma massimo alla focale minima', + 'MaxApertureValue' => 'Diaframma massimo obiettivo', + 'MaxFocalLength' => 'Lunghezza focale massima', + 'MaxSampleValue' => 'Massimo valore campioni', + 'MeasurementGeometry' => { + PrintConv => { + '0/45 or 45/0' => '0/45 o 45/0', + '0/d or d/0' => '0/d o d/0', + }, + }, + 'MediaBlackPoint' => 'Media Punto Nero', + 'MediaType' => { + PrintConv => { + 'Normal (Music)' => 'Normale (musica)', + }, + }, + 'MediaWhitePoint' => 'Media Punto Bianco', + 'MenuButtonDisplayPosition' => { + PrintConv => { + 'Top' => 'Alto', + }, + }, + 'MenuButtonReturn' => { + PrintConv => { + 'Top' => 'Alto', + }, + }, + 'MeteringMode' => { + Description => 'Modalità misurazione', + PrintConv => { + 'Average' => 'Media', + 'Center-weighted Average' => 'Media centrale ponderata', + 'Center-weighted average' => 'Media centrale ponderata', + 'Multi-segment' => 'Multi-zona', + 'Multi-spot' => 'Multi-punto', + 'Other' => 'Altro', + 'Partial' => 'Parziale', + 'Spot' => 'Punto', + 'Unknown' => 'Sconosciuto', + }, + }, + 'MeteringMode2' => { + PrintConv => { + 'Center-weighted average' => 'Media centrale ponderata', + 'Multi-segment' => 'Multi zona', + }, + }, + 'MeteringMode3' => { + PrintConv => { + 'Center-weighted average' => 'Media centrale ponderata', + 'Multi-segment' => 'Multi zona', + }, + }, + 'MeteringTime' => { + PrintConv => { + 'No Limit' => 'Nessun limite', + }, + }, + 'MinFocalLength' => 'Lunghezza focale minima', + 'MinSampleValue' => 'Minimo valore campioni', + 'MiniatureFilterOrientation' => { + PrintConv => { + 'Horizontal' => 'Orizzontale', + 'Vertical' => 'Verticale', + }, + }, + 'MinoltaQuality' => { + Description => 'Qualità', + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'MirrorLockup' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'ModeNumber' => 'Numero modo', + 'Model' => 'Nome modello fotocamera', + 'Model2' => 'Modello 2', + 'ModelReleaseStatus' => { + PrintConv => { + 'None' => 'Nessuno', + 'Not Applicable' => 'Non applicabile', + }, + }, + 'ModelingFlash' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ModifiedPictureStyle' => { + PrintConv => { + 'Landscape' => 'Orizzontale', + 'None' => 'Nessuno', + 'Portrait' => 'Verticale', + }, + }, + 'ModifiedSaturation' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ModifiedSharpnessFreq' => { + PrintConv => { + 'Low' => 'Basso', + 'n/a' => 'n/d', + }, + }, + 'ModifiedToneCurve' => { + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'ModifiedWhiteBalance' => { + PrintConv => { + 'Cloudy' => 'Nuvoloso', + 'Daylight' => 'Luce del giorno', + 'Daylight Fluorescent' => 'Fluorescente a luce del giorno', + 'Fluorescent' => 'Fluorescente', + 'Shade' => 'Ombrato', + 'Tungsten' => 'Tungsteno (luce incandescente)', + }, + }, + 'ModifyDate' => 'Data modifica', + 'MoireFilter' => { + Description => 'Filtro moire', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MonitorDisplayOff' => { + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'MonochromeFilterEffect' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Orange' => 'Arancio', + 'Red' => 'Rosso', + 'Yellow' => 'Giallo', + }, + }, + 'MonochromeLinear' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'MonochromeToning' => { + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'MonochromeToningEffect' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Purple' => 'Porpora', + 'Sepia' => 'Seppia', + }, + }, + 'MultiBurstMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MultiControllerWhileMetering' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MultiExposure' => 'Dati Esposizione Multipla', + 'MultiExposureAutoGain' => { + Description => 'Guadagno Automatico Esposizione Multipla', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MultiExposureMode' => { + Description => 'Modo Esposizione Multipla', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MultiExposureShots' => 'Scatti Esposizione Multipla', + 'MultiExposureVersion' => 'Versione Dati Esposizione Multipla', + 'MultiFrameNoiseReduction' => { + Description => 'Riduz. distur. su più fotogr.', + PrintConv => { + 'None' => 'Nessuno', + 'Off' => 'Spento', + 'On' => 'Attivata', + 'n/a' => 'n/d', + }, + }, + 'MultiFunctionLock' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MultiProfiles' => { + Description => 'Profili multipli', + PrintConv => { + 'JBIG2 Profile M' => 'JBIG2 TIFF FX', + 'N Layer Profile M' => 'Livello N profilo M', + 'Profile C' => 'Profilo C', + 'Profile F' => 'Profilo F', + 'Profile J' => 'Profilo J', + 'Profile L' => 'Profilo L', + 'Profile M' => 'Profilo M', + 'Profile S' => 'Profilo S', + 'Profile T' => 'Profilo T', + 'Resolution/Image Width' => 'Risoluzione/larghezza immagine', + 'Shared Data' => 'Dati condivisi', + }, + }, + 'MultiSelectorLiveView' => { + PrintConv => { + 'Not Used' => 'Non usato', + 'Reset' => 'Reimposta', + 'Zoom On/Off' => 'Zoon sì/no', + }, + }, + 'MultiSelectorPlaybackMode' => { + PrintConv => { + 'Choose Folder' => 'Seleziona cartella', + 'Thumbnail On/Off' => 'Thumbnail sì/no', + 'Zoom On/Off' => 'Zoon sì/no', + }, + }, + 'MultiSelectorShootMode' => { + PrintConv => { + 'Not Used' => 'Non usato', + }, + }, + 'MultipleExposureMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MultipleExposureSet' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MusicCDIdentifier' => 'Identificativo CD musicale', + 'MusicianCredits' => 'Info sul musicista', + 'Mute' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'MyColorMode' => { + PrintConv => { + 'Off' => 'Spento', + 'Sepia' => 'Seppia', + 'Vivid' => 'Vivace', + 'Vivid Blue' => 'Blu vivace', + 'Vivid Green' => 'Verde vivace', + }, + }, + 'NDFilter' => { + PrintConv => { + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'NEFCompression' => { + Description => 'Compressione RAW', + PrintConv => { + 'Lossless' => 'Senza perdita', + 'Uncompressed' => 'Non compresso', + }, + }, + 'Name' => 'Nome', + 'NamedColor2' => 'Colore Chiamato 2', + 'NativeDisplayInfo' => 'Info Display Nativo', + 'NeutralDensityFilter' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'NikonCaptureData' => 'Dati Nikon Capture', + 'NikonCaptureVersion' => 'Versione Nikon Capture', + 'NoMemoryCard' => 'Scheda di memoria assente', + 'Noise' => 'Rumore', + 'NoiseFilter' => { + Description => 'Filtro rumore', + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'NoiseProfile' => 'Profilo rumore', + 'NoiseReduction' => { + Description => 'Riduzione rumore', + PrintConv => { + 'Low' => 'Basso', + 'Low (-1)' => 'Basso (-1)', + 'Noise Filter' => 'Filtro rumore', + 'Noise Reduction' => 'Riduzione rumore', + 'Normal' => 'Normale', + 'Off' => 'Spento', + 'Strong' => 'Forte', + 'n/a' => 'n/d', + }, + }, + 'NoiseReduction2' => { + Description => 'Riduzione rumore 2', + PrintConv => { + 'Noise Filter' => 'Filtro rumore', + 'Noise Reduction' => 'Riduzione rumore', + }, + }, + 'NoiseReductionApplied' => 'Riduzione rumore applicata', + 'NoiseReductionIntensity' => 'Intensità riduzione rumore', + 'NoiseReductionMethod' => { + Description => 'Metodo riduzione rumore', + PrintConv => { + 'Faster' => 'Più veloce', + }, + }, + 'NoiseReductionMode' => { + Description => 'Modo riduzione rumore', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'NoiseReductionValue' => 'Valore riduzione rumore', + 'Notes' => 'Note', + 'Now' => 'Adesso', + 'NumChannels' => 'Numero canali', + 'NumSampleFrames' => 'Numero fotogrammi campionamento', + 'NumberOfFocusPoints' => { + PrintConv => { + '11 Points' => '11 punti', + }, + }, + 'NumberofInks' => 'Numero di inchiostri', + 'OKButton' => { + PrintConv => { + 'Not Used' => 'Non usato', + 'Off' => 'Spento', + }, + }, + 'OPIProxy' => { + Description => 'Proxy OPI', + PrintConv => { + 'Higher resolution image does not exist' => 'Immagine a risoluzione maggiore non esistente', + 'Higher resolution image exists' => 'Immagine a risoluzione maggiore esistente', + }, + }, + 'OSVersion' => 'Versione OS', + 'Object' => 'Oggetto', + 'ObjectAttributeReference' => 'Genere intellettuale', + 'ObjectCycle' => { + Description => 'Ciclo oggetto', + PrintConv => { + 'Both Morning and Evening' => 'Entrambi', + 'Evening' => 'Sera', + 'Morning' => 'Mattino', + }, + }, + 'ObjectFileType' => { + Description => 'Tipo file oggetto', + PrintConv => { + 'Core file' => 'File core', + 'Demand paged executable' => 'Eseguibile paginato a richiesta', + 'Dynamic link editor' => 'Editor con collegamenti dinamici', + 'Dynamic link library' => 'Libreria a collegamento dinamico', + 'Dynamically bound bundle' => 'Pacchetto incorporato dinamicamente', + 'Dynamically bound shared library' => 'Libreria condivisa collegata dinamicamente', + 'Executable application' => 'Applicazione eseguibile', + 'Executable file' => 'File eseguibile', + 'Fixed VM shared library' => 'Libreria fissa VM condivisa', + 'Font' => 'Carattere', + 'None' => 'Nessuno', + 'Preloaded executable' => 'Eseguibile precaricato', + 'Relocatable file' => 'File rilocabile', + 'Relocatable object' => 'Oggetto rilocabile', + 'Shared library stub for static linking' => 'Stub libreria condivisa per il collegamenti statici', + 'Shared object file' => 'File con oggetti condivisi', + 'Static library' => 'Libreria statica', + 'Unknown' => 'Sconosciuto', + }, + }, + 'ObjectName' => 'Titolo', + 'ObjectPreviewFileFormat' => { + PrintConv => { + 'Ritzaus Bureau NITF version (RBNITF DTD)' => 'Versione Ritzaus Bureau NITF (RBNITF DTD)', + }, + }, + 'ObjectTypeReference' => 'Riferimento Tipo Oggetto', + 'OffsetSchema' => 'Schema Offset', + 'OldSubfileType' => { + Description => 'Vecchio tipo sotto-file', + PrintConv => { + 'Full-resolution image' => 'Immagine con risoluzione originale', + 'Reduced-resolution image' => 'Immagine a risoluzione ridotta', + 'Single page of multi-page image' => 'Singola pagina di un\'immagine multi-pagina', + }, + }, + 'OneTouchWB' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'OpEndPic' => 'Fine immagine', + 'OpcodeList1' => 'Lista opcode 1', + 'OpcodeList2' => 'Lista opcode 2', + 'OpcodeList3' => 'Lista opcode 3', + 'OpticalZoomMode' => { + PrintConv => { + 'Extended' => 'Esteso', + }, + }, + 'OpticalZoomOn' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Opto-ElectricConvFactor' => 'Fattore di conversione optoelettrica', + 'Orientation' => { + Description => 'Orientamento', + PrintConv => { + 'Horizontal (normal)' => 'Orizzontale (normale)', + 'Mirror horizontal' => 'Rifletti orizzontalmente', + 'Mirror horizontal and rotate 270 CW' => 'Rifletti orizzontalmente e ruota di 270° in senso orario', + 'Mirror horizontal and rotate 90 CW' => 'Rifletti orizzontalmente e ruota di 90° in senso orario', + 'Mirror vertical' => 'Rifletti verticalmente', + 'Rotate 180' => 'Ruota di 180°', + 'Rotate 270 CW' => 'Ruota di 270° in senso orario', + 'Rotate 90 CW' => 'Ruota di 90° senso orario', + 'Tiled' => 'Tassellato', + }, + }, + 'Origin' => 'Origine', + 'OriginCode' => 'Codice origine', + 'OriginPlatform' => { + Description => 'Piattaforma origine', + PrintConv => { + 'Print' => 'Stampa', + }, + }, + 'OriginalAlbum' => 'Album originale', + 'OriginalAlbumTitle' => 'Titolo album originale', + 'OriginalArtist' => 'Artista originale', + 'OriginalFileName' => 'Nome file originale', + 'OriginalLyricist' => 'Paroliere originale', + 'OriginalMedia' => { + PrintConv => { + 'False' => 'Falso', + }, + }, + 'OriginalRawFileData' => 'Dati file raw originale', + 'OriginalRawFileDigest' => 'Sommario file raw originale', + 'OriginalRawFileName' => 'Nome file raw originale', + 'OriginalReleaseTime' => 'Ora di rilascio originale', + 'OriginalReleaseYear' => 'Anno di rilascio originale', + 'OriginalTransmissionReference' => 'ID Lavoro', + 'OriginatingProgram' => 'Programma d\'origine', + 'OtherCodecDescription' => 'Descrizione altro codec', + 'OtherCodecName' => 'Nome altro codec', + 'OtherImageLength' => 'Altra lunghezza immagine', + 'OtherImageStart' => 'Altro inizio immagine', + 'OwnerID' => 'ID proprietario', + 'OwnerName' => 'Nome proprietario', + 'PEFVersion' => 'Versione PEF', + 'PEType' => 'Tipo PE', + 'PackingMethod' => { + PrintConv => { + 'Fast' => 'Veloce', + 'Fastest' => 'Massimamente veloce', + 'Normal' => 'Normale', + }, + }, + 'PageName' => 'Nome pagina', + 'PageNumber' => 'Numero pagina', + 'PanOrientation' => { + PrintConv => { + '[unused]' => '[non usato]', + }, + }, + 'PanasonicRawVersion' => 'Versione raw Panasonic', + 'PanasonicTitle' => 'Titolo Panasonic', + 'PanasonicTitle2' => 'Titolo Panasonic 2', + 'PanoramaSize3D' => { + Description => 'Dimensione panomara 3D', + PrintConv => { + 'Wide' => 'Ampio', + 'n/a' => 'n/d', + }, + }, + 'PartOfSet' => 'Parte del gruppo', + 'PaymentURL' => 'URL pagamento', + 'PeakValue' => 'Valore di picco', + 'PerformerSortOrder' => 'Ordinamento interprete', + 'PeripheralLighting' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'PeripheralLightingSetting' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'PhaseDetectAF' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'PhotoEffect' => { + PrintConv => { + 'Off' => 'Spento', + 'Sepia' => 'Seppia', + 'Vivid' => 'Vivace', + }, + }, + 'PhotoEffects' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'PhotoEffectsType' => { + PrintConv => { + 'None' => 'Nessuno', + 'Sepia' => 'Seppia', + 'Tinted' => 'Tinteggiato', + }, + }, + 'PhotoInfoPlayback' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'PhotometricInterpretation' => { + Description => 'Interpretazione fotometrica', + PrintConv => { + 'BlackIsZero' => 'Nero è zero', + 'Color Filter Array' => 'Array filtro colore', + 'Linear Raw' => 'Raw lineare', + 'Pixar LogL' => 'Pixar LogLuv', + 'Pixar LogLuv' => 'Pixar LogL', + 'RGB Palette' => 'Tavolozza RGB', + 'Transparency Mask' => 'Maschera trasparenza', + 'WhiteIsZero' => 'Bianco è zero', + }, + }, + 'PhotoshopFormat' => { + PrintConv => { + 'Progressive' => 'Progressivo', + }, + }, + 'Picture' => 'Immagine', + 'PictureControl' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'PictureControlActive' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'PictureControlAdjust' => { + PrintConv => { + 'Default Settings' => 'Impostazioni Predefinite', + 'Full Control' => 'Controllo completo', + 'Quick Adjust' => 'Regolazione rapida', + }, + }, + 'PictureDescription' => 'Descrizione immagine', + 'PictureEffect' => { + PrintConv => { + 'Off' => 'Spento', + 'Posterization' => 'Posterizzazione', + 'Posterization B/W' => 'Posterizzazione B&N', + 'Retro Photo' => 'Foto retrò', + }, + }, + 'PictureFinish' => { + PrintConv => { + 'Evening Scene' => 'Scena serale', + 'Portrait' => 'Verticale', + }, + }, + 'PictureFormat' => 'Formato immagine', + 'PictureMIMEType' => 'MIME type immagine', + 'PictureMode' => { + PrintConv => { + '1/2 EV steps' => 'Step 1/2 EV', + '1/3 EV steps' => 'Step 1/3 EV', + 'Aperture-priority AE' => 'Priorità diaframma', + 'Fireworks' => 'Fuochi artificiali', + 'Fisheye' => 'Fish-eye', + 'Flower' => 'Fiore', + 'Forest' => 'Foresta', + 'Green' => 'Verde', + 'Kids' => 'Bambini', + 'Landscape' => 'Orizzontale', + 'Manual' => 'Manuale', + 'No Flash' => 'No flash', + 'Portrait' => 'Verticale', + 'Program' => 'Programma', + 'Program (HyP)' => 'Programma (HyP)', + 'Program AE' => 'Programma AE', + 'Purple' => 'Porpora', + 'Quick Macro' => 'Macro veloce', + 'Red' => 'Rosso', + 'Sepia' => 'Seppia', + 'Shutter speed priority AE' => 'Priorità otturatore AE', + 'Sunset' => 'Tramonto', + 'Text' => 'Testo', + 'Vivid' => 'Vivace', + }, + }, + 'PictureMode2' => { + PrintConv => { + 'Aperture Priority' => 'Priorità diaframma', + 'Manual' => 'Manuale', + 'Program AE' => 'Programma AE', + 'Scene Mode' => 'Modo scena', + 'Shutter Speed Priority' => 'Priorità otturatore', + }, + }, + 'PictureModeBWFilter' => { + PrintConv => { + 'Green' => 'Verde', + 'Orange' => 'Arancio', + 'Red' => 'Rosso', + 'Yellow' => 'Giallo', + 'n/a' => 'n/d', + }, + }, + 'PictureModeEffect' => { + PrintConv => { + 'Low' => 'Basso', + 'n/a' => 'n/d', + }, + }, + 'PictureModeTone' => { + PrintConv => { + 'Green' => 'Verde', + 'Purple' => 'Porpora', + 'Sepia' => 'Seppia', + 'n/a' => 'n/d', + }, + }, + 'PictureStyle' => { + PrintConv => { + 'Landscape' => 'Orizzontale', + 'None' => 'Nessuno', + 'Portrait' => 'Verticale', + }, + }, + 'PictureType' => { + Description => 'Tipo immagine', + PrintConv => { + '32x32 PNG Icon' => 'Icona PNG 32x32', + 'Artist' => 'Artista', + 'Back Cover' => 'Retrocopertina', + 'Band Logo' => 'Logo band', + 'Bright(ly) Colored Fish' => 'Pesci dai vivaci colori', + 'Capture from Movie or Video' => 'Acquisizione da film o video', + 'Composer' => 'Compositore', + 'Conductor' => 'Direttore d\'orchestra', + 'Front Cover' => 'Copertina', + 'Illustration' => 'Illustrazione', + 'Lead Artist' => 'Artista principale', + 'Leaflet' => 'Volantino', + 'Lyricist' => 'Paroliere', + 'Other' => 'Altro', + 'Other Icon' => 'Altra icona', + 'Performance' => 'Interpretazione', + 'Publisher Logo' => 'Logo editore', + 'Recording Session' => 'Sessione registrazione', + 'Recording Studio or Location' => 'Studio/luogo registrazione', + }, + }, + 'PictureWizardMode' => { + PrintConv => { + 'Forest' => 'Foresta', + 'Landscape' => 'Orizzontale', + 'Retro' => 'Retrò', + 'Vivid' => 'Vivace', + 'n/a' => 'n/d', + }, + }, + 'PixelFormat' => { + Description => 'Formato pixel', + PrintConv => { + '112-bit 6 Channels Alpha' => '112-bit 6 canali trasparenza', + '112-bit 7 Channels' => '112-bit 7 canali', + '128-bit 7 Channels Alpha' => '128-bit 7 canali trasparenza', + '128-bit 8 Channels' => '128-bit 8 canali', + '128-bit PRGBA Float' => '128-bit PRGBA virgola mobile', + '128-bit RGB Float' => '128-bit RGB virgola mobile', + '128-bit RGBA Fixed Point' => '128-bit RGBA virgola fissa', + '128-bit RGBA Float' => '128-bit RGBA virgola mobile', + '144-bit 8 Channels Alpha' => '144-bit 8 canali trasparenza', + '16-bit Gray' => '16-bit grigio', + '16-bit Gray Half' => '16-bit grigio metà', + '24-bit 3 Channels' => '24-bit 3 canali', + '32-bit 3 Channels Alpha' => '32-bit 3 canali trasparenza', + '32-bit 4 Channels' => '32-bit 4 canali', + '32-bit Gray Fixed Point' => '32-bit punto grigio virgola fissa', + '32-bit Gray Float' => '32-bit punto grigio virgola mobile', + '40-bit 4 Channels Alpha' => '40-bit 4 canali trasparenza', + '40-bit 5 Channels' => '40-bit 5 canali', + '40-bit CMYK Alpha' => '40-bit CMYK trasparenza', + '48-bit 3 Channels' => '48-bit 3 canali', + '48-bit 5 Channels Alpha' => '48-bit 5 canali trasparenza', + '48-bit 6 Channels' => '48-bit 6 canali', + '48-bit RGB Fixed Point' => '48-bit RGB virgola fissa', + '48-bit RGB Half' => '48-bit RGB metà', + '56-bit 6 Channels Alpha' => '56-bit 6 canali trasparenza', + '56-bit 7 Channels' => '56-bit 7 canali', + '64-bit 3 Channels Alpha' => '64-bit 3 canali trasparenza', + '64-bit 4 Channels' => '64-bit 4 canali', + '64-bit 7 Channels Alpha' => '64-bit 7 canali trasparenza', + '64-bit 8 Channels' => '64-bit 8 canali', + '64-bit RGBA Fixed Point' => '64-bit RGBA virgola fissa', + '64-bit RGBA Half' => '64-bit RGBA mezzi toni', + '72-bit 8 Channels Alpha' => '72-bit 8 canali trasparenza', + '8-bit Gray' => '8-bit grigio', + '80-bit 4 Channels Alpha' => '80-bit 4 canali trasparenza', + '80-bit 5 Channels' => '80-bit 5 canali', + '80-bit CMYK Alpha' => '80-bit CMYK trasparenza', + '96-bit 5 Channels Alpha' => '96-bit 5 canali trasparenza', + '96-bit 6 Channels' => '96-bit 6 canali', + '96-bit RGB Fixed Point' => '96-bit RGB virgola fissa', + 'Black & White' => 'Bianco e nero', + }, + }, + 'PixelIntensityRange' => 'Intervallo intensità pixel', + 'PixelMagicJBIGOptions' => 'Opzioni Pixel Magic JBIG', + 'PixelScale' => 'Scala pixel', + 'PixelUnits' => { + PrintConv => { + 'Unknown' => 'Sconosciuto', + }, + }, + 'PlanarConfiguration' => { + Description => 'Configurazione planare', + PrintConv => { + 'Chunky' => 'Spezzettato', + 'Planar' => 'Planare', + }, + }, + 'PlayCounter' => 'Conteggio esecuzioni', + 'PlayGap' => { + PrintConv => { + 'No Gap' => 'Nessun salto', + }, + }, + 'PlaylistDelay' => 'Attesa playlist', + 'PortraitRefiner' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Position' => 'Posizione', + 'PostScriptFontName' => 'Nome carattere PostScript', + 'PostprocessingFunction' => 'Funzione post-processamento', + 'PowerSource' => 'Fonte alimentazione', + 'PowerUpTime' => 'Ora accensione', + 'Predictor' => { + Description => 'Predittore', + PrintConv => { + 'Horizontal differencing' => 'Differenziazione orizzontale', + 'None' => 'Nessuno', + }, + }, + 'PredictorColumns' => 'Colonne predittore', + 'PredictorConstants' => 'Costanti predittore', + 'PredictorRows' => 'Righe predittore', + 'PreferredFamily' => 'Famiglia preferita', + 'PreferredSubfamily' => 'Sotto-famiglia preferita', + 'PreferredVolume' => 'Volume preferito', + 'Prefs' => 'Preferenze', + 'PresetWhiteBalance' => { + PrintConv => { + 'Camera Setting' => 'Impostazioni fotocamera', + 'Fluorescent' => 'Fluorescente', + 'Shade' => 'Ombrato', + }, + }, + 'PrevFileName' => 'Nome file precedente', + 'PrevSize' => 'Dimensione precedente', + 'PrevUID' => 'UID precedente', + 'Preview' => 'Anteprima', + 'Preview0' => 'Anteprima 0 ', + 'Preview1' => 'Anteprima 1 ', + 'Preview2' => 'Anteprima 2', + 'PreviewApplicationName' => 'Nome applicazione anteprima ', + 'PreviewApplicationVersion' => 'Versione applicazione anteprima ', + 'PreviewButton' => { + Description => 'Pulsante anteprima', + PrintConv => { + 'Flash Off' => 'Flash spento', + 'None' => 'Nessuno', + 'Preview' => 'Anteprima', + 'Virtual Horizon' => 'Orizzonte virtuale', + }, + }, + 'PreviewButtonPlusDials' => { + PrintConv => { + 'Choose Image Area' => 'Seleziona area immagine', + 'None' => 'Nessuno', + }, + }, + 'PreviewColorSpace' => { + Description => 'Spazio colore anteprima', + PrintConv => { + 'Gray Gamma 2.2' => 'Gamma grigio 2.2', + 'Unknown' => 'Sconosciuto', + }, + }, + 'PreviewDate' => 'Data anteprima', + 'PreviewDateTime' => 'Data ora anteprima', + 'PreviewDuration' => 'Durata anteprima', + 'PreviewIFD' => 'Anteprima Puntatore IFD', + 'PreviewImage' => 'Immagine anteprima', + 'PreviewImageBorders' => 'Bordi immagine anteprima', + 'PreviewImageData' => 'Altezza immagine anteprima', + 'PreviewImageHeight' => 'Altezza immagine anteprima', + 'PreviewImageLength' => 'Lunghezza immagine anteprima', + 'PreviewImageName' => 'Nome immagine anteprima', + 'PreviewImageSize' => 'Dimensioni immagine anteprima', + 'PreviewImageStart' => 'Inizio immagine anteprima', + 'PreviewImageType' => 'Tipo immagine anteprima', + 'PreviewImageValid' => { + Description => 'Immagine anteprima valida', + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'PreviewImageWidth' => 'Larghezza immagine anteprima', + 'PreviewInfo' => 'Info anteprima', + 'PreviewQuality' => { + Description => 'Qualtià anteprima', + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'PreviewSettingsDigest' => 'Sommario impostazioni anteprima', + 'PreviewSettingsName' => 'Nome impostazioni anteprima', + 'PrimaryAFPoint' => { + PrintConv => { + '(none)' => '(nessuno)', + 'Bottom' => 'Basso', + 'C6 (Center)' => 'C6 (Centro)', + 'Center' => 'Centro', + 'Far Left' => 'Tutto a sinistra', + 'Far Right' => 'Tutto a destra', + 'Lower-left' => 'Inferiore sinistro', + 'Lower-right' => 'Inferiore destro', + 'Mid-left' => 'Centro/sinistra', + 'Mid-right' => 'Centro/destra', + 'Top' => 'Alto', + 'Upper-left' => 'Superiore sinistro', + 'Upper-right' => 'Superiore destro', + }, + }, + 'PrimaryChromaticities' => 'Cromatismo dei colori primari', + 'PrimaryPlatform' => 'Piattaforma primaria', + 'PrintIM' => 'Stampa Image Matching', + 'PrintIMVersion' => 'Versione PrintIM', + 'PrintPriority' => 'Priorità stampa', + 'PrintQuality' => 'Qualità stampa', + 'PrintScale' => 'Scala stampa', + 'PrinterName' => 'Nome stampante', + 'Priority' => 'Priorità', + 'PrivateBuild' => 'Compilazione privata', + 'ProcessingSoftware' => 'Software di elaborazione', + 'ProducedNotice' => 'Note prodotte', + 'Product' => 'Prodotto', + 'ProductDescription' => 'Descrizione prodotto', + 'ProductID' => 'ID prodotto', + 'ProductName' => 'Nome prodotto', + 'ProductVersion' => 'Versione prodotto', + 'ProductVersionNumber' => 'Numero di versione prodotto', + 'Profession' => 'Professione', + 'Profile' => 'Profilo', + 'ProfileAndLevel' => 'Profilo e livello', + 'ProfileCMMType' => 'Tipo profilo CMM', + 'ProfileCalibrationSig' => 'Segn calibrazione profilo', + 'ProfileClass' => { + Description => 'Classe profilo', + PrintConv => { + 'Abstract Profile' => 'Profilo Astratto', + 'ColorSpace Conversion Profile' => 'Profilo Conversione Spazio Colore', + 'DeviceLink Profile' => 'Profilo Dispositivo di Collegamento', + 'Display Device Profile' => 'Profilo Dispositivo Visualizzazione', + 'Input Device Profile' => 'Profilo dispositivo di Input', + 'NamedColor Profile' => 'Profilo Colore Chiamato', + 'Nikon Input Device Profile (NON-STANDARD!)' => 'Profilo Nikon ("nkpf")', + 'Output Device Profile' => 'Profilo Dispositivo Output', + }, + }, + 'ProfileConnectionSpace' => 'Spazio connessione profilo', + 'ProfileCopyright' => 'Copyright profilo', + 'ProfileCreator' => 'Autore profilo', + 'ProfileDateTime' => 'Data/ora profilo', + 'ProfileDescription' => 'Descrizione del Profilo', + 'ProfileDescriptionML' => 'Descrizione profilo multilinguaggio.', + 'ProfileEmbedPolicy' => { + Description => 'Politica incorporamento profilo', + PrintConv => { + 'Allow Copying' => 'Permetti la copia', + 'Embed if Used' => 'Incorpora se usato', + 'Never Embed' => 'Non incorporare mai', + 'No Restrictions' => 'Nessuna restrizione', + }, + }, + 'ProfileFileSignature' => 'Firma file profilo', + 'ProfileID' => { + Description => 'ID profilo', + PrintConv => { + 'Not Specified' => 'Non specificato', + }, + }, + 'ProfileName' => 'Nome profilo', + 'ProfileSequenceDesc' => 'Descrizione della Sequenza del Profilo', + 'ProfileType' => { + Description => 'Tipo profilo', + PrintConv => { + 'Unspecified' => 'Non specificato', + }, + }, + 'ProfileVersion' => 'Versione profilo', + 'ProgID' => 'ID programma', + 'ProgramISO' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ProgramKind' => 'Tipo di programma', + 'ProgramLine' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'ProgramMode' => { + Description => 'Modo programma', + PrintConv => { + 'None' => 'Nessuno', + 'Portrait' => 'Verticale', + 'Sunset' => 'Tramonto', + 'Text' => 'Testo', + }, + }, + 'ProgramName' => 'Nome programma', + 'ProgramNumber' => 'Numero programma', + 'ProgramVersion' => 'Versione programma', + 'ProgrammingGroupKind' => 'Tipo gruppo programmazione', + 'ProgrammingGroupTitle' => 'Titolo gruppo programmazione', + 'ProgressiveScans' => 'Scansioni progressive', + 'Project' => 'Progetto', + 'ProjectName' => 'Nome progetto', + 'ProjectNumber' => 'Numero progetto', + 'ProjectRef' => 'Rif progetto', + 'ProjectSet' => 'Set progetto', + 'ProjectedCSType' => { + Description => 'Tipo CS proiettato', + PrintConv => { + 'ETRS89 Poland CS2000 zone 5' => 'ETRS89 Poland CS2000 zona 5', + 'ETRS89 Poland CS2000 zone 7' => 'ETRS89 Poland CS2000 zona 7', + 'ETRS89 Poland CS2000 zone 8' => 'ETRS89 Poland CS2000 zona 8', + 'PSAD56 UTM zone 17S' => 'PSAD56 UTM zona 17S', + 'PSAD56 UTM zone 18N' => 'PSAD56 UTM zona 18N', + 'PSAD56 UTM zone 18S' => 'PSAD56 UTM zona 18S', + 'PSAD56 UTM zone 19N' => 'PSAD56 UTM zona 19N', + 'PSAD56 UTM zone 19S' => 'PSAD56 UTM zona 19S', + 'PSAD56 UTM zone 20N' => 'PSAD56 UTM zona 20N', + 'PSAD56 UTM zone 20S' => 'PSAD56 UTM zona 20S', + 'PSAD56 UTM zone 21N' => 'PSAD56 UTM zona 21N', + 'Pulkovo Gauss zone 10' => 'Pulkovo Gauss zona 10', + 'Pulkovo Gauss zone 11' => 'Pulkovo Gauss zona 11', + 'Pulkovo Gauss zone 12' => 'Pulkovo Gauss zona 12', + 'Pulkovo Gauss zone 13' => 'Pulkovo Gauss zona 13', + 'Pulkovo Gauss zone 14' => 'Pulkovo Gauss zona 14', + 'Pulkovo Gauss zone 15' => 'Pulkovo Gauss zona 15', + 'Pulkovo Gauss zone 16' => 'Pulkovo Gauss zona 16', + 'Pulkovo Gauss zone 17' => 'Pulkovo Gauss zona 17', + 'Pulkovo Gauss zone 18' => 'Pulkovo Gauss zona 18', + 'Pulkovo Gauss zone 19' => 'Pulkovo Gauss zona 19', + 'Pulkovo Gauss zone 20' => 'Pulkovo Gauss zona 20', + 'Pulkovo Gauss zone 21' => 'Pulkovo Gauss zona 21', + 'Pulkovo Gauss zone 22' => 'Pulkovo Gauss zona 22', + 'Pulkovo Gauss zone 23' => 'Pulkovo Gauss zona 23', + 'Pulkovo Gauss zone 24' => 'Pulkovo Gauss zona 24', + 'Pulkovo Gauss zone 25' => 'Pulkovo Gauss zona 25', + 'Pulkovo Gauss zone 26' => 'Pulkovo Gauss zona 26', + 'Pulkovo Gauss zone 27' => 'Pulkovo Gauss zona 27', + 'Pulkovo Gauss zone 28' => 'Pulkovo Gauss zona 28', + 'Pulkovo Gauss zone 29' => 'Pulkovo Gauss zona 29', + 'Pulkovo Gauss zone 30' => 'Pulkovo Gauss zona 30', + 'Pulkovo Gauss zone 31' => 'Pulkovo Gauss zona 31', + 'Pulkovo Gauss zone 32' => 'Pulkovo Gauss zona 32', + 'Pulkovo Gauss zone 4' => 'Pulkovo Gauss zona 4', + 'Pulkovo Gauss zone 5' => 'Pulkovo Gauss zona 5', + 'Pulkovo Gauss zone 6' => 'Pulkovo Gauss zona 6', + 'Pulkovo Gauss zone 7' => 'Pulkovo Gauss zona 7', + 'Pulkovo Gauss zone 8' => 'Pulkovo Gauss zona 8', + 'Pulkovo Gauss zone 9' => 'Pulkovo Gauss zona 9', + 'Sudan UTM zone 35N' => 'Sudan UTM zona 35N', + 'Sudan UTM zone 36N' => 'Sudan UTM zona 36N', + }, + }, + 'Projection' => 'Proiezione', + 'ProjectionAlgorithm' => 'Algoritmo proiezione', + 'ProjectionAngle' => 'Angolo proiezione', + 'Projects' => 'Progetti', + 'Properties' => 'Proprietà', + 'PropertyReleaseStatus' => { + PrintConv => { + 'None' => 'Nessuno', + 'Not Applicable' => 'Non applicabile', + }, + }, + 'Protect' => 'Proteggi', + 'Protected' => 'Protetto', + 'ProtocolName' => 'Nome protocollo', + 'Province-State' => 'Provincia/Stato', + 'PublicationDate' => 'Data pubblicazione', + 'PublicationName' => 'Nome pubblicazione', + 'PublicationSets' => 'Set pupplicazione', + 'Publisher' => 'Editore', + 'PublisherURL' => 'URL editore', + 'PurchaseDate' => 'Data acquisto', + 'PurchaserAccountName' => 'Nome account acquirente', + 'PurchaserAccountNumber' => 'Numero account acquirente', + 'PurchaserIdentificationKind' => 'Tipo identificazione acquirente', + 'PurchaserIdentificationValue' => 'Valore identificazione acquirente', + 'PurchasingDepartment' => 'Reparto acquirente', + 'PurchasingOrganizationName' => 'Nome organizzazione acquirente', + 'Purpose' => 'Scopo', + 'PurposeOfReferenceCodeSequence' => 'Scopo sequenza codici riferimento', + 'PyramidLevels' => 'Livelli piramite', + 'Quality' => { + Description => 'Qualità', + PrintConv => { + 'Compressed RAW' => 'RAW compresso', + 'Compressed RAW + JPEG' => 'RAW+JPEG compresso', + 'Extra Fine' => 'Extra fine', + 'Low' => 'Basso', + 'Normal' => 'Normale', + 'RAW + JPEG' => 'RAW+JPEG', + 'n/a' => 'n/d', + }, + }, + 'QualityControlImage' => 'Immagine controllo qualità', + 'QualityFlag' => 'Indicatore qualità', + 'QualityMode' => { + Description => 'Modo qualità', + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'Quantity' => 'Quantità', + 'QuantitySequence' => 'Sequenza quantità', + 'QuantizationDefault' => 'Default quantizzazione', + 'QuantizationMethod' => 'Metodo quantizzazione', + 'QueueStatus' => 'Stato coda', + 'QuickAdjust' => 'Regolazione rapida', + 'QuickControlDialInMeter' => { + PrintConv => { + 'ISO speed' => 'Velocità ISO', + }, + }, + 'QuickEdit' => 'Modifica velote', + 'QuickMaskInfo' => 'Info maschera veloce', + 'QuickShot' => { + Description => 'Scatto veloce', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'R2ABlueCtbl00' => 'R2 A blu Stbl 30', + 'R2ABlueCtbl01' => 'R2 A blu Stbl 29', + 'R2ABlueCtbl02' => 'R2 A blu Stbl 28', + 'R2ABlueCtbl03' => 'R2 A blu Stbl 27', + 'R2ABlueCtbl04' => 'R2 A blu Stbl 26', + 'R2ABlueCtbl05' => 'R2 A blu Stbl 25', + 'R2ABlueCtbl06' => 'R2 A blu Stbl 24', + 'R2ABlueCtbl07' => 'R2 A blu Stbl 23', + 'R2ABlueCtbl08' => 'R2 A blu Stbl 22', + 'R2ABlueCtbl09' => 'R2 A blu Stbl 21', + 'R2ABlueCtbl10' => 'R2 A blu Stbl 20', + 'R2ABlueCtbl11' => 'R2 A blu Stbl 19', + 'R2ABlueCtbl12' => 'R2 A blu Stbl 18', + 'R2ABlueCtbl13' => 'R2 A blu Stbl 17', + 'R2ABlueCtbl14' => 'R2 A blu Stbl 16', + 'R2ABlueCtbl15' => 'R2 A blu Stbl 15', + 'R2ABlueCtbl16' => 'R2 A blu Stbl 14', + 'R2ABlueCtbl17' => 'R2 A blu Stbl 13', + 'R2ABlueCtbl18' => 'R2 A blu Stbl 12', + 'R2ABlueCtbl19' => 'R2 A blu Stbl 11', + 'R2ABlueCtbl20' => 'R2 A blu Stbl 10', + 'R2ABlueCtbl21' => 'R2 A blu Stbl 09', + 'R2ABlueCtbl22' => 'R2 A blu Stbl 08', + 'R2ABlueCtbl23' => 'R2 A blu Stbl 07', + 'R2ABlueCtbl24' => 'R2 A blu Stbl 06', + 'R2ABlueCtbl25' => 'R2 A blu Stbl 05', + 'R2ABlueCtbl26' => 'R2 A blu Stbl 04', + 'R2ABlueCtbl27' => 'R2 A blu Stbl 03', + 'R2ABlueCtbl28' => 'R2 A blu Stbl 02', + 'R2ABlueCtbl29' => 'R2 A blu Stbl 01', + 'R2ABlueCtbl30' => 'R2 A blu Stbl 00', + 'R2ABlueCtbl31' => 'R2 A blu Ctbl 31', + 'R2ABlueStbl00' => 'R2 A blu Ctbl 30', + 'R2ABlueStbl01' => 'R2 A blu Ctbl 29', + 'R2ABlueStbl02' => 'R2 A blu Ctbl 28', + 'R2ABlueStbl03' => 'R2 A blu Ctbl 27', + 'R2ABlueStbl04' => 'R2 A blu Ctbl 26', + 'R2ABlueStbl05' => 'R2 A blu Ctbl 25', + 'R2ABlueStbl06' => 'R2 A blu Ctbl 24', + 'R2ABlueStbl07' => 'R2 A blu Ctbl 23', + 'R2ABlueStbl08' => 'R2 A blu Ctbl 22', + 'R2ABlueStbl09' => 'R2 A blu Ctbl 21', + 'R2ABlueStbl10' => 'R2 A blu Ctbl 20', + 'R2ABlueStbl11' => 'R2 A blu Ctbl 19', + 'R2ABlueStbl12' => 'R2 A blu Ctbl 18', + 'R2ABlueStbl13' => 'R2 A blu Ctbl 17', + 'R2ABlueStbl14' => 'R2 A blu Ctbl 16', + 'R2ABlueStbl15' => 'R2 A blu Ctbl 15', + 'R2ABlueStbl16' => 'R2 A blu Ctbl 14', + 'R2ABlueStbl17' => 'R2 A blu Ctbl 13', + 'R2ABlueStbl18' => 'R2 A blu Ctbl 12', + 'R2ABlueStbl19' => 'R2 A blu Ctbl 11', + 'R2ABlueStbl20' => 'R2 A blu Ctbl 10', + 'R2ABlueStbl21' => 'R2 A blu Ctbl 09', + 'R2ABlueStbl22' => 'R2 A blu Ctbl 08', + 'R2ABlueStbl23' => 'R2 A blu Ctbl 07', + 'R2ABlueStbl24' => 'R2 A blu Ctbl 06', + 'R2ABlueStbl25' => 'R2 A blu Ctbl 05', + 'R2ABlueStbl26' => 'R2 A blu Ctbl 04', + 'R2ABlueStbl27' => 'R2 A blu Ctbl 03', + 'R2ABlueStbl28' => 'R2 A blu Ctbl 02', + 'R2ABlueStbl29' => 'R2 A blu Ctbl 01', + 'R2ABlueStbl30' => 'R2 A blu Ctbl 00', + 'R2ABlueStbl31' => 'R2 A blu Stbl 31', + 'R2AGreenCtbl00' => 'R2 A verde Ctbl 00', + 'R2AGreenCtbl01' => 'R2 A verde Ctbl 01', + 'R2AGreenCtbl02' => 'R2 A verde Ctbl 02', + 'R2AGreenCtbl03' => 'R2 A verde Ctbl 03', + 'R2AGreenCtbl04' => 'R2 A verde Ctbl 04', + 'R2AGreenCtbl05' => 'R2 A verde Ctbl 05', + 'R2AGreenCtbl06' => 'R2 A verde Ctbl 06', + 'R2AGreenCtbl07' => 'R2 A verde Ctbl 07', + 'R2AGreenCtbl08' => 'R2 A verde Ctbl 08', + 'R2AGreenCtbl09' => 'R2 A verde Ctbl 09', + 'R2AGreenCtbl10' => 'R2 A verde Ctbl 10', + 'R2AGreenCtbl11' => 'R2 A verde Ctbl 11', + 'R2AGreenCtbl12' => 'R2 A verde Ctbl 12', + 'R2AGreenCtbl13' => 'R2 A verde Ctbl 13', + 'R2AGreenCtbl14' => 'R2 A verde Ctbl 14', + 'R2AGreenCtbl15' => 'R2 A verde Ctbl 15', + 'R2AGreenCtbl16' => 'R2 A verde Ctbl 16', + 'R2AGreenCtbl17' => 'R2 A verde Ctbl 17', + 'R2AGreenCtbl18' => 'R2 A verde Ctbl 18', + 'R2AGreenCtbl19' => 'R2 A verde Ctbl 19', + 'R2AGreenCtbl20' => 'R2 A verde Ctbl 20', + 'R2AGreenCtbl21' => 'R2 A verde Ctbl 21', + 'R2AGreenCtbl22' => 'R2 A verde Ctbl 22', + 'R2AGreenCtbl23' => 'R2 A verde Ctbl 23', + 'R2AGreenCtbl24' => 'R2 A verde Ctbl 24', + 'R2AGreenCtbl25' => 'R2 A verde Ctbl 25', + 'R2AGreenCtbl26' => 'R2 A verde Ctbl 26', + 'R2AGreenCtbl27' => 'R2 A verde Ctbl 27', + 'R2AGreenCtbl28' => 'R2 A verde Ctbl 28', + 'R2AGreenCtbl29' => 'R2 A verde Ctbl 29', + 'R2AGreenCtbl30' => 'R2 A verde Ctbl 30', + 'R2AGreenCtbl31' => 'R2 A verde Ctbl 31', + 'R2AGreenStbl00' => 'R2 A verde Stbl 00', + 'R2AGreenStbl01' => 'R2 A verde Stbl 01', + 'R2AGreenStbl02' => 'R2 A verde Stbl 02', + 'R2AGreenStbl03' => 'R2 A verde Stbl 03', + 'R2AGreenStbl04' => 'R2 A verde Stbl 04', + 'R2AGreenStbl05' => 'R2 A verde Stbl 05', + 'R2AGreenStbl06' => 'R2 A verde Stbl 06', + 'R2AGreenStbl07' => 'R2 A verde Stbl 07', + 'R2AGreenStbl08' => 'R2 A verde Stbl 08', + 'R2AGreenStbl09' => 'R2 A verde Stbl 09', + 'R2AGreenStbl10' => 'R2 A verde Stbl 10', + 'R2AGreenStbl11' => 'R2 A verde Stbl 11', + 'R2AGreenStbl12' => 'R2 A verde Stbl 12', + 'R2AGreenStbl13' => 'R2 A verde Stbl 13', + 'R2AGreenStbl14' => 'R2 A verde Stbl 14', + 'R2AGreenStbl15' => 'R2 A verde Stbl 15', + 'R2AGreenStbl16' => 'R2 A verde Stbl 16', + 'R2AGreenStbl17' => 'R2 A verde Stbl 17', + 'R2AGreenStbl18' => 'R2 A verde Stbl 18', + 'R2AGreenStbl19' => 'R2 A verde Stbl 19', + 'R2AGreenStbl20' => 'R2 A verde Stbl 20', + 'R2AGreenStbl21' => 'R2 A verde Stbl 21', + 'R2AGreenStbl22' => 'R2 A verde Stbl 22', + 'R2AGreenStbl23' => 'R2 A verde Stbl 23', + 'R2AGreenStbl24' => 'R2 A verde Stbl 24', + 'R2AGreenStbl25' => 'R2 A verde Stbl 25', + 'R2AGreenStbl26' => 'R2 A verde Stbl 26', + 'R2AGreenStbl27' => 'R2 A verde Stbl 27', + 'R2AGreenStbl28' => 'R2 A verde Stbl 28', + 'R2AGreenStbl29' => 'R2 A verde Stbl 29', + 'R2AGreenStbl30' => 'R2 A verde Stbl 30', + 'R2AGreenStbl31' => 'R2 A verde Stbl 31', + 'R2AHeight' => 'R2 A altezza', + 'R2AIntervals' => 'R2 A intervalli', + 'R2ARedCtbl00' => 'R2 A rosso Ctbl 00', + 'R2ARedCtbl01' => 'R2 A rosso Ctbl 01', + 'R2ARedCtbl02' => 'R2 A rosso Ctbl 02', + 'R2ARedCtbl03' => 'R2 A rosso Ctbl 03', + 'R2ARedCtbl04' => 'R2 A rosso Ctbl 04', + 'R2ARedCtbl05' => 'R2 A rosso Ctbl 05', + 'R2ARedCtbl06' => 'R2 A rosso Ctbl 06', + 'R2ARedCtbl07' => 'R2 A rosso Ctbl 07', + 'R2ARedCtbl08' => 'R2 A rosso Ctbl 08', + 'R2ARedCtbl09' => 'R2 A rosso Ctbl 09', + 'R2ARedCtbl10' => 'R2 A rosso Ctbl 10', + 'R2ARedCtbl11' => 'R2 A rosso Ctbl 11', + 'R2ARedCtbl12' => 'R2 A rosso Ctbl 12', + 'R2ARedCtbl13' => 'R2 A rosso Ctbl 13', + 'R2ARedCtbl14' => 'R2 A rosso Ctbl 14', + 'R2ARedCtbl15' => 'R2 A rosso Ctbl 15', + 'R2ARedCtbl16' => 'R2 A rosso Ctbl 16', + 'R2ARedCtbl17' => 'R2 A rosso Ctbl 17', + 'R2ARedCtbl18' => 'R2 A rosso Ctbl 18', + 'R2ARedCtbl19' => 'R2 A rosso Ctbl 19', + 'R2ARedCtbl20' => 'R2 A rosso Ctbl 20', + 'R2ARedCtbl21' => 'R2 A rosso Ctbl 21', + 'R2ARedCtbl22' => 'R2 A rosso Ctbl 22', + 'R2ARedCtbl23' => 'R2 A rosso Ctbl 23', + 'R2ARedCtbl24' => 'R2 A rosso Ctbl 24', + 'R2ARedCtbl25' => 'R2 A rosso Ctbl 25', + 'R2ARedCtbl26' => 'R2 A rosso Ctbl 26', + 'R2ARedCtbl27' => 'R2 A rosso Ctbl 27', + 'R2ARedCtbl28' => 'R2 A rosso Ctbl 28', + 'R2ARedCtbl29' => 'R2 A rosso Ctbl 29', + 'R2ARedCtbl30' => 'R2 A rosso Ctbl 30', + 'R2ARedCtbl31' => 'R2 A rosso Ctbl 31', + 'R2ARedStbl00' => 'R2 A rosso Stbl 00', + 'R2ARedStbl01' => 'R2 A rosso Stbl 01', + 'R2ARedStbl02' => 'R2 A rosso Stbl 02', + 'R2ARedStbl03' => 'R2 A rosso Stbl 03', + 'R2ARedStbl04' => 'R2 A rosso Stbl 04', + 'R2ARedStbl05' => 'R2 A rosso Stbl 05', + 'R2ARedStbl06' => 'R2 A rosso Stbl 06', + 'R2ARedStbl07' => 'R2 A rosso Stbl 07', + 'R2ARedStbl08' => 'R2 A rosso Stbl 08', + 'R2ARedStbl09' => 'R2 A rosso Stbl 09', + 'R2ARedStbl10' => 'R2 A rosso Stbl 10', + 'R2ARedStbl11' => 'R2 A rosso Stbl 11', + 'R2ARedStbl12' => 'R2 A rosso Stbl 12', + 'R2ARedStbl13' => 'R2 A rosso Stbl 13', + 'R2ARedStbl14' => 'R2 A rosso Stbl 14', + 'R2ARedStbl15' => 'R2 A rosso Stbl 15', + 'R2ARedStbl16' => 'R2 A rosso Stbl 16', + 'R2ARedStbl17' => 'R2 A rosso Stbl 17', + 'R2ARedStbl18' => 'R2 A rosso Stbl 18', + 'R2ARedStbl19' => 'R2 A rosso Stbl 19', + 'R2ARedStbl20' => 'R2 A rosso Stbl 20', + 'R2ARedStbl21' => 'R2 A rosso Stbl 21', + 'R2ARedStbl22' => 'R2 A rosso Stbl 22', + 'R2ARedStbl23' => 'R2 A rosso Stbl 23', + 'R2ARedStbl24' => 'R2 A rosso Stbl 24', + 'R2ARedStbl25' => 'R2 A rosso Stbl 25', + 'R2ARedStbl26' => 'R2 A rosso Stbl 26', + 'R2ARedStbl27' => 'R2 A rosso Stbl 27', + 'R2ARedStbl28' => 'R2 A rosso Stbl 28', + 'R2ARedStbl29' => 'R2 A rosso Stbl 29', + 'R2ARedStbl30' => 'R2 A rosso Stbl 30', + 'R2ARedStbl31' => 'R2 A rosso Stbl 31', + 'R2AWidth' => 'R2 A larghezza', + 'R2D65BlueCtbl00' => 'R2 D65 blu Ctbl 00', + 'R2D65BlueCtbl01' => 'R2 D65 blu Ctbl 01', + 'R2D65BlueCtbl02' => 'R2 D65 blu Ctbl 02', + 'R2D65BlueCtbl03' => 'R2 D65 blu Ctbl 03', + 'R2D65BlueCtbl04' => 'R2 D65 blu Ctbl 04', + 'R2D65BlueCtbl05' => 'R2 D65 blu Ctbl 05', + 'R2D65BlueCtbl06' => 'R2 D65 blu Ctbl 06', + 'R2D65BlueCtbl07' => 'R2 D65 blu Ctbl 07', + 'R2D65BlueCtbl08' => 'R2 D65 blu Ctbl 08', + 'R2D65BlueCtbl09' => 'R2 D65 blu Ctbl 09', + 'R2D65BlueCtbl10' => 'R2 D65 blu Ctbl 10', + 'R2D65BlueCtbl11' => 'R2 D65 blu Ctbl 11', + 'R2D65BlueCtbl12' => 'R2 D65 blu Ctbl 12', + 'R2D65BlueCtbl13' => 'R2 D65 blu Ctbl 13', + 'R2D65BlueCtbl14' => 'R2 D65 blu Ctbl 14', + 'R2D65BlueCtbl15' => 'R2 D65 blu Ctbl 15', + 'R2D65BlueCtbl16' => 'R2 D65 blu Ctbl 16', + 'R2D65BlueCtbl17' => 'R2 D65 blu Ctbl 17', + 'R2D65BlueCtbl18' => 'R2 D65 blu Ctbl 18', + 'R2D65BlueCtbl19' => 'R2 D65 blu Ctbl 19', + 'R2D65BlueCtbl20' => 'R2 D65 blu Ctbl 20', + 'R2D65BlueCtbl21' => 'R2 D65 blu Ctbl 21', + 'R2D65BlueCtbl22' => 'R2 D65 blu Ctbl 22', + 'R2D65BlueCtbl23' => 'R2 D65 blu Ctbl 23', + 'R2D65BlueCtbl24' => 'R2 D65 blu Ctbl 24', + 'R2D65BlueCtbl25' => 'R2 D65 blu Ctbl 25', + 'R2D65BlueCtbl26' => 'R2 D65 blu Ctbl 26', + 'R2D65BlueCtbl27' => 'R2 D65 blu Ctbl 27', + 'R2D65BlueCtbl28' => 'R2 D65 blu Ctbl 28', + 'R2D65BlueCtbl29' => 'R2 D65 blu Ctbl 29', + 'R2D65BlueCtbl30' => 'R2 D65 blu Ctbl 30', + 'R2D65BlueCtbl31' => 'R2 D65 blu Ctbl 31', + 'R2D65BlueStbl00' => 'R2 D65 blu Stbl 00', + 'R2D65BlueStbl01' => 'R2 D65 blu Stbl 01', + 'R2D65BlueStbl02' => 'R2 D65 blu Stbl 02', + 'R2D65BlueStbl03' => 'R2 D65 blu Stbl 03', + 'R2D65BlueStbl04' => 'R2 D65 blu Stbl 04', + 'R2D65BlueStbl05' => 'R2 D65 blu Stbl 05', + 'R2D65BlueStbl06' => 'R2 D65 blu Stbl 06', + 'R2D65BlueStbl07' => 'R2 D65 blu Stbl 07', + 'R2D65BlueStbl08' => 'R2 D65 blu Stbl 08', + 'R2D65BlueStbl09' => 'R2 D65 blu Stbl 09', + 'R2D65BlueStbl10' => 'R2 D65 blu Stbl 10', + 'R2D65BlueStbl11' => 'R2 D65 blu Stbl 11', + 'R2D65BlueStbl12' => 'R2 D65 blu Stbl 12', + 'R2D65BlueStbl13' => 'R2 D65 blu Stbl 13', + 'R2D65BlueStbl14' => 'R2 D65 blu Stbl 14', + 'R2D65BlueStbl15' => 'R2 D65 blu Stbl 15', + 'R2D65BlueStbl16' => 'R2 D65 blu Stbl 16', + 'R2D65BlueStbl17' => 'R2 D65 blu Stbl 17', + 'R2D65BlueStbl18' => 'R2 D65 blu Stbl 18', + 'R2D65BlueStbl19' => 'R2 D65 blu Stbl 19', + 'R2D65BlueStbl20' => 'R2 D65 blu Stbl 20', + 'R2D65BlueStbl21' => 'R2 D65 blu Stbl 21', + 'R2D65BlueStbl22' => 'R2 D65 blu Stbl 22', + 'R2D65BlueStbl23' => 'R2 D65 blu Stbl 23', + 'R2D65BlueStbl24' => 'R2 D65 blu Stbl 24', + 'R2D65BlueStbl25' => 'R2 D65 blu Stbl 25', + 'R2D65BlueStbl26' => 'R2 D65 blu Stbl 26', + 'R2D65BlueStbl27' => 'R2 D65 blu Stbl 27', + 'R2D65BlueStbl28' => 'R2 D65 blu Stbl 28', + 'R2D65BlueStbl29' => 'R2 D65 blu Stbl 29', + 'R2D65BlueStbl30' => 'R2 D65 blu Stbl 30', + 'R2D65BlueStbl31' => 'R2 D65 blu Stbl 31', + 'R2D65GreenCtbl00' => 'R2 D65 verde Ctbl 00', + 'R2D65GreenCtbl01' => 'R2 D65 verde Ctbl 01', + 'R2D65GreenCtbl02' => 'R2 D65 verde Ctbl 02', + 'R2D65GreenCtbl03' => 'R2 D65 verde Ctbl 03', + 'R2D65GreenCtbl04' => 'R2 D65 verde Ctbl 04', + 'R2D65GreenCtbl05' => 'R2 D65 verde Ctbl 05', + 'R2D65GreenCtbl06' => 'R2 D65 verde Ctbl 06', + 'R2D65GreenCtbl07' => 'R2 D65 verde Ctbl 07', + 'R2D65GreenCtbl08' => 'R2 D65 verde Ctbl 08', + 'R2D65GreenCtbl09' => 'R2 D65 verde Ctbl 09', + 'R2D65GreenCtbl10' => 'R2 D65 verde Ctbl 10', + 'R2D65GreenCtbl11' => 'R2 D65 verde Ctbl 11', + 'R2D65GreenCtbl12' => 'R2 D65 verde Ctbl 12', + 'R2D65GreenCtbl13' => 'R2 D65 verde Ctbl 13', + 'R2D65GreenCtbl14' => 'R2 D65 verde Ctbl 14', + 'R2D65GreenCtbl15' => 'R2 D65 verde Ctbl 15', + 'R2D65GreenCtbl16' => 'R2 D65 verde Ctbl 16', + 'R2D65GreenCtbl17' => 'R2 D65 verde Ctbl 17', + 'R2D65GreenCtbl18' => 'R2 D65 verde Ctbl 18', + 'R2D65GreenCtbl19' => 'R2 D65 verde Ctbl 19', + 'R2D65GreenCtbl20' => 'R2 D65 verde Ctbl 20', + 'R2D65GreenCtbl21' => 'R2 D65 verde Ctbl 21', + 'R2D65GreenCtbl22' => 'R2 D65 verde Ctbl 22', + 'R2D65GreenCtbl23' => 'R2 D65 verde Ctbl 23', + 'R2D65GreenCtbl24' => 'R2 D65 verde Ctbl 24', + 'R2D65GreenCtbl25' => 'R2 D65 verde Ctbl 25', + 'R2D65GreenCtbl26' => 'R2 D65 verde Ctbl 26', + 'R2D65GreenCtbl27' => 'R2 D65 verde Ctbl 27', + 'R2D65GreenCtbl28' => 'R2 D65 verde Ctbl 28', + 'R2D65GreenCtbl29' => 'R2 D65 verde Ctbl 29', + 'R2D65GreenCtbl30' => 'R2 D65 verde Ctbl 30', + 'R2D65GreenCtbl31' => 'R2 D65 verde Ctbl 31', + 'R2D65GreenStbl00' => 'R2 D65 verde Stbl 00', + 'R2D65GreenStbl01' => 'R2 D65 verde Stbl 01', + 'R2D65GreenStbl02' => 'R2 D65 verde Stbl 02', + 'R2D65GreenStbl03' => 'R2 D65 verde Stbl 03', + 'R2D65GreenStbl04' => 'R2 D65 verde Stbl 04', + 'R2D65GreenStbl05' => 'R2 D65 verde Stbl 05', + 'R2D65GreenStbl06' => 'R2 D65 verde Stbl 06', + 'R2D65GreenStbl07' => 'R2 D65 verde Stbl 07', + 'R2D65GreenStbl08' => 'R2 D65 verde Stbl 08', + 'R2D65GreenStbl09' => 'R2 D65 verde Stbl 09', + 'R2D65GreenStbl10' => 'R2 D65 verde Stbl 10', + 'R2D65GreenStbl11' => 'R2 D65 verde Stbl 11', + 'R2D65GreenStbl12' => 'R2 D65 verde Stbl 12', + 'R2D65GreenStbl13' => 'R2 D65 verde Stbl 13', + 'R2D65GreenStbl14' => 'R2 D65 verde Stbl 14', + 'R2D65GreenStbl15' => 'R2 D65 verde Stbl 15', + 'R2D65GreenStbl16' => 'R2 D65 verde Stbl 16', + 'R2D65GreenStbl17' => 'R2 D65 verde Stbl 17', + 'R2D65GreenStbl18' => 'R2 D65 verde Stbl 18', + 'R2D65GreenStbl19' => 'R2 D65 verde Stbl 19', + 'R2D65GreenStbl20' => 'R2 D65 verde Stbl 20', + 'R2D65GreenStbl21' => 'R2 D65 verde Stbl 21', + 'R2D65GreenStbl22' => 'R2 D65 verde Stbl 22', + 'R2D65GreenStbl23' => 'R2 D65 verde Stbl 23', + 'R2D65GreenStbl24' => 'R2 D65 verde Stbl 24', + 'R2D65GreenStbl25' => 'R2 D65 verde Stbl 25', + 'R2D65GreenStbl26' => 'R2 D65 verde Stbl 26', + 'R2D65GreenStbl27' => 'R2 D65 verde Stbl 27', + 'R2D65GreenStbl28' => 'R2 D65 verde Stbl 28', + 'R2D65GreenStbl29' => 'R2 D65 verde Stbl 29', + 'R2D65GreenStbl30' => 'R2 D65 verde Stbl 30', + 'R2D65GreenStbl31' => 'R2 D65 verde Stbl 31', + 'R2D65Height' => 'R2 D65 altezza', + 'R2D65Intervals' => 'R2 D65 intervalli', + 'R2D65RedCtbl00' => 'R2 D65 rosso Ctbl 00', + 'R2D65RedCtbl01' => 'R2 D65 rosso Ctbl 01', + 'R2D65RedCtbl02' => 'R2 D65 rosso Ctbl 02', + 'R2D65RedCtbl03' => 'R2 D65 rosso Ctbl 03', + 'R2D65RedCtbl04' => 'R2 D65 rosso Ctbl 04', + 'R2D65RedCtbl05' => 'R2 D65 rosso Ctbl 05', + 'R2D65RedCtbl06' => 'R2 D65 rosso Ctbl 06', + 'R2D65RedCtbl07' => 'R2 D65 rosso Ctbl 07', + 'R2D65RedCtbl08' => 'R2 D65 rosso Ctbl 08', + 'R2D65RedCtbl09' => 'R2 D65 rosso Ctbl 09', + 'R2D65RedCtbl10' => 'R2 D65 rosso Ctbl 10', + 'R2D65RedCtbl11' => 'R2 D65 rosso Ctbl 11', + 'R2D65RedCtbl12' => 'R2 D65 rosso Ctbl 12', + 'R2D65RedCtbl13' => 'R2 D65 rosso Ctbl 13', + 'R2D65RedCtbl14' => 'R2 D65 rosso Ctbl 14', + 'R2D65RedCtbl15' => 'R2 D65 rosso Ctbl 15', + 'R2D65RedCtbl16' => 'R2 D65 rosso Ctbl 16', + 'R2D65RedCtbl17' => 'R2 D65 rosso Ctbl 17', + 'R2D65RedCtbl18' => 'R2 D65 rosso Ctbl 18', + 'R2D65RedCtbl19' => 'R2 D65 rosso Ctbl 19', + 'R2D65RedCtbl20' => 'R2 D65 rosso Ctbl 20', + 'R2D65RedCtbl21' => 'R2 D65 rosso Ctbl 21', + 'R2D65RedCtbl22' => 'R2 D65 rosso Ctbl 22', + 'R2D65RedCtbl23' => 'R2 D65 rosso Ctbl 23', + 'R2D65RedCtbl24' => 'R2 D65 rosso Ctbl 24', + 'R2D65RedCtbl25' => 'R2 D65 rosso Ctbl 25', + 'R2D65RedCtbl26' => 'R2 D65 rosso Ctbl 26', + 'R2D65RedCtbl27' => 'R2 D65 rosso Ctbl 27', + 'R2D65RedCtbl28' => 'R2 D65 rosso Ctbl 28', + 'R2D65RedCtbl29' => 'R2 D65 rosso Ctbl 29', + 'R2D65RedCtbl30' => 'R2 D65 rosso Ctbl 30', + 'R2D65RedCtbl31' => 'R2 D65 rosso Ctbl 31', + 'R2D65RedStbl00' => 'R2 D65 rosso Stbl 00', + 'R2D65RedStbl01' => 'R2 D65 rosso Stbl 01', + 'R2D65RedStbl02' => 'R2 D65 rosso Stbl 02', + 'R2D65RedStbl03' => 'R2 D65 rosso Stbl 03', + 'R2D65RedStbl04' => 'R2 D65 rosso Stbl 04', + 'R2D65RedStbl05' => 'R2 D65 rosso Stbl 05', + 'R2D65RedStbl06' => 'R2 D65 rosso Stbl 06', + 'R2D65RedStbl07' => 'R2 D65 rosso Stbl 07', + 'R2D65RedStbl08' => 'R2 D65 rosso Stbl 08', + 'R2D65RedStbl09' => 'R2 D65 rosso Stbl 09', + 'R2D65RedStbl10' => 'R2 D65 rosso Stbl 10', + 'R2D65RedStbl11' => 'R2 D65 rosso Stbl 11', + 'R2D65RedStbl12' => 'R2 D65 rosso Stbl 12', + 'R2D65RedStbl13' => 'R2 D65 rosso Stbl 13', + 'R2D65RedStbl14' => 'R2 D65 rosso Stbl 14', + 'R2D65RedStbl15' => 'R2 D65 rosso Stbl 15', + 'R2D65RedStbl16' => 'R2 D65 rosso Stbl 16', + 'R2D65RedStbl17' => 'R2 D65 rosso Stbl 17', + 'R2D65RedStbl18' => 'R2 D65 rosso Stbl 18', + 'R2D65RedStbl19' => 'R2 D65 rosso Stbl 19', + 'R2D65RedStbl20' => 'R2 D65 rosso Stbl 20', + 'R2D65RedStbl21' => 'R2 D65 rosso Stbl 21', + 'R2D65RedStbl22' => 'R2 D65 rosso Stbl 22', + 'R2D65RedStbl23' => 'R2 D65 rosso Stbl 23', + 'R2D65RedStbl24' => 'R2 D65 rosso Stbl 24', + 'R2D65RedStbl25' => 'R2 D65 rosso Stbl 25', + 'R2D65RedStbl26' => 'R2 D65 rosso Stbl 26', + 'R2D65RedStbl27' => 'R2 D65 rosso Stbl 27', + 'R2D65RedStbl28' => 'R2 D65 rosso Stbl 28', + 'R2D65RedStbl29' => 'R2 D65 rosso Stbl 29', + 'R2D65RedStbl30' => 'R2 D65 rosso Stbl 30', + 'R2D65RedStbl31' => 'R2 D65 rosso Stbl 31', + 'R2D65Width' => 'R2 D65 larghezza', + 'R2TL84BlueCtbl00' => 'R2 TL84 blu Ctbl 00', + 'R2TL84BlueCtbl01' => 'R2 TL84 blu Ctbl 01', + 'R2TL84BlueCtbl02' => 'R2 TL84 blu Ctbl 02', + 'R2TL84BlueCtbl03' => 'R2 TL84 blu Ctbl 03', + 'R2TL84BlueCtbl04' => 'R2 TL84 blu Ctbl 04', + 'R2TL84BlueCtbl05' => 'R2 TL84 blu Ctbl 05', + 'R2TL84BlueCtbl06' => 'R2 TL84 blu Ctbl 06', + 'R2TL84BlueCtbl07' => 'R2 TL84 blu Ctbl 07', + 'R2TL84BlueCtbl08' => 'R2 TL84 blu Ctbl 08', + 'R2TL84BlueCtbl09' => 'R2 TL84 blu Ctbl 09', + 'R2TL84BlueCtbl10' => 'R2 TL84 blu Ctbl 10', + 'R2TL84BlueCtbl11' => 'R2 TL84 blu Ctbl 11', + 'R2TL84BlueCtbl12' => 'R2 TL84 blu Ctbl 12', + 'R2TL84BlueCtbl13' => 'R2 TL84 blu Ctbl 13', + 'R2TL84BlueCtbl14' => 'R2 TL84 blu Ctbl 14', + 'R2TL84BlueCtbl15' => 'R2 TL84 blu Ctbl 15', + 'R2TL84BlueCtbl16' => 'R2 TL84 blu Ctbl 16', + 'R2TL84BlueCtbl17' => 'R2 TL84 blu Ctbl 17', + 'R2TL84BlueCtbl18' => 'R2 TL84 blu Ctbl 18', + 'R2TL84BlueCtbl19' => 'R2 TL84 blu Ctbl 19', + 'R2TL84BlueCtbl20' => 'R2 TL84 blu Ctbl 20', + 'R2TL84BlueCtbl21' => 'R2 TL84 blu Ctbl 21', + 'R2TL84BlueCtbl22' => 'R2 TL84 blu Ctbl 22', + 'R2TL84BlueCtbl23' => 'R2 TL84 blu Ctbl 23', + 'R2TL84BlueCtbl24' => 'R2 TL84 blu Ctbl 24', + 'R2TL84BlueCtbl25' => 'R2 TL84 blu Ctbl 25', + 'R2TL84BlueCtbl26' => 'R2 TL84 blu Ctbl 26', + 'R2TL84BlueCtbl27' => 'R2 TL84 blu Ctbl 27', + 'R2TL84BlueCtbl28' => 'R2 TL84 blu Ctbl 28', + 'R2TL84BlueCtbl29' => 'R2 TL84 blu Ctbl 29', + 'R2TL84BlueCtbl30' => 'R2 TL84 blu Ctbl 30', + 'R2TL84BlueCtbl31' => 'R2 TL84 blu Ctbl 31', + 'R2TL84BlueStbl00' => 'R2 TL84 blu Stbl 00', + 'R2TL84BlueStbl01' => 'R2 TL84 blu Stbl 01', + 'R2TL84BlueStbl02' => 'R2 TL84 blu Stbl 02', + 'R2TL84BlueStbl03' => 'R2 TL84 blu Stbl 03', + 'R2TL84BlueStbl04' => 'R2 TL84 blu Stbl 04', + 'R2TL84BlueStbl05' => 'R2 TL84 blu Stbl 05', + 'R2TL84BlueStbl06' => 'R2 TL84 blu Stbl 06', + 'R2TL84BlueStbl07' => 'R2 TL84 blu Stbl 07', + 'R2TL84BlueStbl08' => 'R2 TL84 blu Stbl 08', + 'R2TL84BlueStbl09' => 'R2 TL84 blu Stbl 09', + 'R2TL84BlueStbl10' => 'R2 TL84 blu Stbl 10', + 'R2TL84BlueStbl11' => 'R2 TL84 blu Stbl 11', + 'R2TL84BlueStbl12' => 'R2 TL84 blu Stbl 12', + 'R2TL84BlueStbl13' => 'R2 TL84 blu Stbl 13', + 'R2TL84BlueStbl14' => 'R2 TL84 blu Stbl 14', + 'R2TL84BlueStbl15' => 'R2 TL84 blu Stbl 15', + 'R2TL84BlueStbl16' => 'R2 TL84 blu Stbl 16', + 'R2TL84BlueStbl17' => 'R2 TL84 blu Stbl 17', + 'R2TL84BlueStbl18' => 'R2 TL84 blu Stbl 18', + 'R2TL84BlueStbl19' => 'R2 TL84 blu Stbl 19', + 'R2TL84BlueStbl20' => 'R2 TL84 blu Stbl 20', + 'R2TL84BlueStbl21' => 'R2 TL84 blu Stbl 21', + 'R2TL84BlueStbl22' => 'R2 TL84 blu Stbl 22', + 'R2TL84BlueStbl23' => 'R2 TL84 blu Stbl 23', + 'R2TL84BlueStbl24' => 'R2 TL84 blu Stbl 24', + 'R2TL84BlueStbl25' => 'R2 TL84 blu Stbl 25', + 'R2TL84BlueStbl26' => 'R2 TL84 blu Stbl 26', + 'R2TL84BlueStbl27' => 'R2 TL84 blu Stbl 27', + 'R2TL84BlueStbl28' => 'R2 TL84 blu Stbl 28', + 'R2TL84BlueStbl29' => 'R2 TL84 blu Stbl 29', + 'R2TL84BlueStbl30' => 'R2 TL84 blu Stbl 30', + 'R2TL84BlueStbl31' => 'R2 TL84 blu Stbl 31', + 'R2TL84GreenCtbl00' => 'R2 TL84 verde Ctbl 00', + 'R2TL84GreenCtbl01' => 'R2 TL84 verde Ctbl 01', + 'R2TL84GreenCtbl02' => 'R2 TL84 verde Ctbl 02', + 'R2TL84GreenCtbl03' => 'R2 TL84 verde Ctbl 03', + 'R2TL84GreenCtbl04' => 'R2 TL84 verde Ctbl 04', + 'R2TL84GreenCtbl05' => 'R2 TL84 verde Ctbl 05', + 'R2TL84GreenCtbl06' => 'R2 TL84 verde Ctbl 06', + 'R2TL84GreenCtbl07' => 'R2 TL84 verde Ctbl 07', + 'R2TL84GreenCtbl08' => 'R2 TL84 verde Ctbl 08', + 'R2TL84GreenCtbl09' => 'R2 TL84 verde Ctbl 09', + 'R2TL84GreenCtbl10' => 'R2 TL84 verde Ctbl 10', + 'R2TL84GreenCtbl11' => 'R2 TL84 verde Ctbl 11', + 'R2TL84GreenCtbl12' => 'R2 TL84 verde Ctbl 12', + 'R2TL84GreenCtbl13' => 'R2 TL84 verde Ctbl 13', + 'R2TL84GreenCtbl14' => 'R2 TL84 verde Ctbl 14', + 'R2TL84GreenCtbl15' => 'R2 TL84 verde Ctbl 15', + 'R2TL84GreenCtbl16' => 'R2 TL84 verde Ctbl 16', + 'R2TL84GreenCtbl17' => 'R2 TL84 verde Ctbl 17', + 'R2TL84GreenCtbl18' => 'R2 TL84 verde Ctbl 18', + 'R2TL84GreenCtbl19' => 'R2 TL84 verde Ctbl 19', + 'R2TL84GreenCtbl20' => 'R2 TL84 verde Ctbl 20', + 'R2TL84GreenCtbl21' => 'R2 TL84 verde Ctbl 21', + 'R2TL84GreenCtbl22' => 'R2 TL84 verde Ctbl 22', + 'R2TL84GreenCtbl23' => 'R2 TL84 verde Ctbl 23', + 'R2TL84GreenCtbl24' => 'R2 TL84 verde Ctbl 24', + 'R2TL84GreenCtbl25' => 'R2 TL84 verde Ctbl 25', + 'R2TL84GreenCtbl26' => 'R2 TL84 verde Ctbl 26', + 'R2TL84GreenCtbl27' => 'R2 TL84 verde Ctbl 27', + 'R2TL84GreenCtbl28' => 'R2 TL84 verde Ctbl 28', + 'R2TL84GreenCtbl29' => 'R2 TL84 verde Ctbl 29', + 'R2TL84GreenCtbl30' => 'R2 TL84 verde Ctbl 30', + 'R2TL84GreenCtbl31' => 'R2 TL84 verde Ctbl 31', + 'R2TL84GreenStbl00' => 'R2 TL84 verde Stbl 00', + 'R2TL84GreenStbl01' => 'R2 TL84 verde Stbl 01', + 'R2TL84GreenStbl02' => 'R2 TL84 verde Stbl 02', + 'R2TL84GreenStbl03' => 'R2 TL84 verde Stbl 03', + 'R2TL84GreenStbl04' => 'R2 TL84 verde Stbl 04', + 'R2TL84GreenStbl05' => 'R2 TL84 verde Stbl 05', + 'R2TL84GreenStbl06' => 'R2 TL84 verde Stbl 06', + 'R2TL84GreenStbl07' => 'R2 TL84 verde Stbl 07', + 'R2TL84GreenStbl08' => 'R2 TL84 verde Stbl 08', + 'R2TL84GreenStbl09' => 'R2 TL84 verde Stbl 09', + 'R2TL84GreenStbl10' => 'R2 TL84 verde Stbl 10', + 'R2TL84GreenStbl11' => 'R2 TL84 verde Stbl 11', + 'R2TL84GreenStbl12' => 'R2 TL84 verde Stbl 12', + 'R2TL84GreenStbl13' => 'R2 TL84 verde Stbl 13', + 'R2TL84GreenStbl14' => 'R2 TL84 verde Stbl 14', + 'R2TL84GreenStbl15' => 'R2 TL84 verde Stbl 15', + 'R2TL84GreenStbl16' => 'R2 TL84 verde Stbl 16', + 'R2TL84GreenStbl17' => 'R2 TL84 verde Stbl 17', + 'R2TL84GreenStbl18' => 'R2 TL84 verde Stbl 18', + 'R2TL84GreenStbl19' => 'R2 TL84 verde Stbl 19', + 'R2TL84GreenStbl20' => 'R2 TL84 verde Stbl 20', + 'R2TL84GreenStbl21' => 'R2 TL84 verde Stbl 21', + 'R2TL84GreenStbl22' => 'R2 TL84 verde Stbl 22', + 'R2TL84GreenStbl23' => 'R2 TL84 verde Stbl 23', + 'R2TL84GreenStbl24' => 'R2 TL84 verde Stbl 24', + 'R2TL84GreenStbl25' => 'R2 TL84 verde Stbl 25', + 'R2TL84GreenStbl26' => 'R2 TL84 verde Stbl 26', + 'R2TL84GreenStbl27' => 'R2 TL84 verde Stbl 27', + 'R2TL84GreenStbl28' => 'R2 TL84 verde Stbl 28', + 'R2TL84GreenStbl29' => 'R2 TL84 verde Stbl 29', + 'R2TL84GreenStbl30' => 'R2 TL84 verde Stbl 30', + 'R2TL84GreenStbl31' => 'R2 TL84 verde Stbl 31', + 'R2TL84Height' => 'R2 TL84 altezza', + 'R2TL84Intervals' => 'R2 TL84 intervalli', + 'R2TL84RedCtbl00' => 'R2 TL84 rosso Ctbl 00', + 'R2TL84RedCtbl01' => 'R2 TL84 rosso Ctbl 01', + 'R2TL84RedCtbl02' => 'R2 TL84 rosso Ctbl 02', + 'R2TL84RedCtbl03' => 'R2 TL84 rosso Ctbl 03', + 'R2TL84RedCtbl04' => 'R2 TL84 rosso Ctbl 04', + 'R2TL84RedCtbl05' => 'R2 TL84 rosso Ctbl 05', + 'R2TL84RedCtbl06' => 'R2 TL84 rosso Ctbl 06', + 'R2TL84RedCtbl07' => 'R2 TL84 rosso Ctbl 07', + 'R2TL84RedCtbl08' => 'R2 TL84 rosso Ctbl 08', + 'R2TL84RedCtbl09' => 'R2 TL84 rosso Ctbl 09', + 'R2TL84RedCtbl10' => 'R2 TL84 rosso Ctbl 10', + 'R2TL84RedCtbl11' => 'R2 TL84 rosso Ctbl 11', + 'R2TL84RedCtbl12' => 'R2 TL84 rosso Ctbl 12', + 'R2TL84RedCtbl13' => 'R2 TL84 rosso Ctbl 13', + 'R2TL84RedCtbl14' => 'R2 TL84 rosso Ctbl 14', + 'R2TL84RedCtbl15' => 'R2 TL84 rosso Ctbl 15', + 'R2TL84RedCtbl16' => 'R2 TL84 rosso Ctbl 16', + 'R2TL84RedCtbl17' => 'R2 TL84 rosso Ctbl 17', + 'R2TL84RedCtbl18' => 'R2 TL84 rosso Ctbl 18', + 'R2TL84RedCtbl19' => 'R2 TL84 rosso Ctbl 19', + 'R2TL84RedCtbl20' => 'R2 TL84 rosso Ctbl 20', + 'R2TL84RedCtbl21' => 'R2 TL84 rosso Ctbl 21', + 'R2TL84RedCtbl22' => 'R2 TL84 rosso Ctbl 22', + 'R2TL84RedCtbl23' => 'R2 TL84 rosso Ctbl 23', + 'R2TL84RedCtbl24' => 'R2 TL84 rosso Ctbl 24', + 'R2TL84RedCtbl25' => 'R2 TL84 rosso Ctbl 25', + 'R2TL84RedCtbl26' => 'R2 TL84 rosso Ctbl 26', + 'R2TL84RedCtbl27' => 'R2 TL84 rosso Ctbl 27', + 'R2TL84RedCtbl28' => 'R2 TL84 rosso Ctbl 28', + 'R2TL84RedCtbl29' => 'R2 TL84 rosso Ctbl 29', + 'R2TL84RedCtbl30' => 'R2 TL84 rosso Ctbl 30', + 'R2TL84RedCtbl31' => 'R2 TL84 rosso Ctbl 31', + 'R2TL84RedStbl00' => 'R2 TL84 rosso Stbl 00', + 'R2TL84RedStbl01' => 'R2 TL84 rosso Stbl 01', + 'R2TL84RedStbl02' => 'R2 TL84 rosso Stbl 02', + 'R2TL84RedStbl03' => 'R2 TL84 rosso Stbl 03', + 'R2TL84RedStbl04' => 'R2 TL84 rosso Stbl 04', + 'R2TL84RedStbl05' => 'R2 TL84 rosso Stbl 05', + 'R2TL84RedStbl06' => 'R2 TL84 rosso Stbl 06', + 'R2TL84RedStbl07' => 'R2 TL84 rosso Stbl 07', + 'R2TL84RedStbl08' => 'R2 TL84 rosso Stbl 08', + 'R2TL84RedStbl09' => 'R2 TL84 rosso Stbl 09', + 'R2TL84RedStbl10' => 'R2 TL84 rosso Stbl 10', + 'R2TL84RedStbl11' => 'R2 TL84 rosso Stbl 11', + 'R2TL84RedStbl12' => 'R2 TL84 rosso Stbl 12', + 'R2TL84RedStbl13' => 'R2 TL84 rosso Stbl 13', + 'R2TL84RedStbl14' => 'R2 TL84 rosso Stbl 14', + 'R2TL84RedStbl15' => 'R2 TL84 rosso Stbl 15', + 'R2TL84RedStbl16' => 'R2 TL84 rosso Stbl 16', + 'R2TL84RedStbl17' => 'R2 TL84 rosso Stbl 17', + 'R2TL84RedStbl18' => 'R2 TL84 rosso Stbl 18', + 'R2TL84RedStbl19' => 'R2 TL84 rosso Stbl 19', + 'R2TL84RedStbl20' => 'R2 TL84 rosso Stbl 20', + 'R2TL84RedStbl21' => 'R2 TL84 rosso Stbl 21', + 'R2TL84RedStbl22' => 'R2 TL84 rosso Stbl 22', + 'R2TL84RedStbl23' => 'R2 TL84 rosso Stbl 23', + 'R2TL84RedStbl24' => 'R2 TL84 rosso Stbl 24', + 'R2TL84RedStbl25' => 'R2 TL84 rosso Stbl 25', + 'R2TL84RedStbl26' => 'R2 TL84 rosso Stbl 26', + 'R2TL84RedStbl27' => 'R2 TL84 rosso Stbl 27', + 'R2TL84RedStbl28' => 'R2 TL84 rosso Stbl 28', + 'R2TL84RedStbl29' => 'R2 TL84 rosso Stbl 29', + 'R2TL84RedStbl30' => 'R2 TL84 rosso Stbl 30', + 'R2TL84RedStbl31' => 'R2 TL84 rosso Stbl 31', + 'R2TL84Width' => 'R2 TL84 larghezza', + 'RAFVersion' => 'Versione RAF', + 'RGBCurveLimits' => 'Limiti curva RGB', + 'RGBCurvePoints' => 'Punti curva RGB', + 'RadialPosition' => 'Posizione radiale', + 'RandomIndexMetadata' => 'Indice casuale metadati', + 'RandomIndexMetadataV10' => 'Indice casuale metadati V10', + 'RangeFinder' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'RasterPadding' => { + PrintConv => { + 'Long Sector' => 'Settore lungo', + 'Long Word' => 'Parola lunga', + 'Sector' => 'Settore', + 'Word' => 'Parola', + }, + }, + 'RasterizedCaption' => 'Didascalia rasterizzata', + 'Rating' => { + Description => 'Valutazione', + PrintConv => { + 'Explicit' => 'Esplicito', + }, + }, + 'RatingPercent' => 'Valutazione percentuale', + 'RawAndJpgRecording' => { + Description => 'Registrazione raw e jpg', + PrintConv => { + 'Off' => 'Spento', + 'RAW (DNG, Best)' => 'RAW (DNG, migliore)', + 'RAW (DNG, Better)' => 'RAW (DNG, più buona)', + 'RAW (DNG, Good)' => 'RAW (DNG, buona)', + 'RAW (PEF, Best)' => 'RAW (PEF, migliore)', + 'RAW (PEF, Better)' => 'RAW (PEF, più buona)', + 'RAW (PEF, Good)' => 'RAW (PEF, buona)', + 'RAW+JPEG (DNG, Best)' => 'RAW+JPEG (DNG, migliore)', + 'RAW+JPEG (DNG, Better)' => 'RAW+JPEG (DNG, più buona)', + 'RAW+JPEG (DNG, Good)' => 'RAW+JPEG (DNG, buona)', + 'RAW+JPEG (PEF, Best)' => 'RAW+JPEG (PEF, migliore)', + 'RAW+JPEG (PEF, Better)' => 'RAW+JPEG (PEF, più buona)', + 'RAW+JPEG (PEF, Good)' => 'RAW+JPEG (PEF, buona)', + }, + }, + 'RawColorAdj' => { + PrintConv => { + 'Shot Settings' => 'Impostazioni scatto', + }, + }, + 'RawDataOffset' => 'Offset dati raw', + 'RawDataUniqueID' => 'ID unico dati raw', + 'RawDepth' => 'Profondità raw', + 'RawDevAutoGradation' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'RawDevEditStatus' => { + PrintConv => { + 'Original' => 'Originale', + }, + }, + 'RawDevNoiseReduction' => { + PrintConv => { + 'Noise Filter' => 'Filtro rumore', + 'Noise Reduction' => 'Riduzione rumore', + }, + }, + 'RawDevPMPictureTone' => { + PrintConv => { + 'Green' => 'Verde', + 'Purple' => 'Porpora', + 'Sepia' => 'Seppia', + }, + }, + 'RawDevPM_BWFilter' => { + PrintConv => { + 'Green' => 'Verde', + 'Orange' => 'Arancio', + 'Red' => 'Rosso', + 'Yellow' => 'Giallo', + }, + }, + 'RawDevPictureMode' => { + PrintConv => { + 'Sepia' => 'Seppia', + 'Vivid' => 'Vivace', + }, + }, + 'RawDevSettings' => { + PrintConv => { + 'Noise Reduction' => 'Riduzione rumore', + 'Saturation' => 'Saturazione', + 'Sharpness' => 'Nitidezza', + }, + }, + 'RawFile' => 'File raw', + 'RawFileName' => 'Nome file raw', + 'RawImageCenter' => 'Centro immagine raw', + 'RawImageDigest' => 'Sommario file raw', + 'RawImageFullSize' => 'Dimensione finale immagine raw', + 'RawImageHeight' => 'Altezza immagine raw', + 'RawImageMode' => 'Modo immagine raw', + 'RawImageSegmentation' => 'Segmentazione file raw', + 'RawImageSize' => 'Dimensione immagine raw', + 'RawImageWidth' => 'Larghezza immagine raw', + 'RawInfoVersion' => 'Info versione raw', + 'RawJpgHeight' => 'Altezza jpg raw', + 'RawJpgQuality' => { + Description => 'Qualità jpg raw', + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'RawJpgSize' => { + Description => 'Dimensione jpg raw', + PrintConv => { + 'Postcard' => 'Cartolina', + }, + }, + 'RawJpgWidth' => 'Larghezza jpg raw', + 'ReadStatus' => 'Stato lettura', + 'RecommendedExposureIndex' => 'Indice esposizione raccomandato', + 'RecordDisplay' => { + PrintConv => { + 'Horizontal' => 'Orizzontale', + }, + }, + 'RecordMode' => { + Description => 'Modo registrazione', + PrintConv => { + 'Aperture Priority' => 'Priorità diaframma', + 'Manual' => 'Manuale', + 'Program AE' => 'Programma AE', + 'Shutter Priority' => 'Priorità otturatore', + }, + }, + 'RecordShutterRelease' => { + PrintConv => { + 'Press start, press stop' => 'Premi start, premi stop', + }, + }, + 'RecordedFormat' => 'Formato registrato', + 'RecordingDates' => 'Date registrazioni', + 'RecordingLabelName' => 'Nome etichetta registrazione', + 'RecordingMode' => { + Description => 'Modo registrazione', + PrintConv => { + 'Landscape' => 'Orizzontale', + 'Manual' => 'Manuale', + 'Portrait' => 'Verticale', + }, + }, + 'RecordingTime' => 'Tempo registrazione', + 'RedAdjust' => 'Correzione rosso', + 'RedBalance' => 'Bilanciamento del rosso', + 'RedCurveLimits' => 'Limiti curva rosso', + 'RedCurvePoints' => 'Punti curva rosso', + 'RedEyeCorrection' => { + Description => 'Correzione occhi rossi', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'RedEyeInfo' => 'Info occhi rossi', + 'RedEyeReduction' => { + Description => 'Riduzione occhi rossi', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'RedGain' => 'Guadagno rosso', + 'RedHue' => 'Tinta rosso', + 'RedMatrixColumn' => 'Colonna matrice rosso', + 'RedPaletteColorTableData' => 'Dati tabella tavolozza colori rosso', + 'RedPaletteColorTableDescriptor' => 'Descrittore tabella tavolozza colori rosso', + 'RedPrimary' => 'Rosso primario', + 'RedSample' => 'Campione rosso', + 'RedSaturation' => 'Saturazione rosso', + 'RedTRC' => 'Curva riproduzione tono rosso', + 'ReductionMatrix1' => 'Matrice di riduzione 1', + 'ReductionMatrix2' => 'Matrice di riduzione 2', + 'Reference' => 'Riferimento', + 'Reference1' => 'Riferimento 1', + 'Reference2' => 'Riferimento 2', + 'Reference3' => 'Riferimento 3', + 'Reference4' => 'Riferimento 4', + 'Reference5' => 'Riferimento 5', + 'Reference6' => 'Riferimento 6', + 'ReferenceBlackWhite' => 'Coppia valori riferimento di bianco e nero', + 'ReferenceBlock' => 'Blocco di riferimento', + 'ReferenceChannels' => 'Canali di riferimento', + 'ReferenceCoordinates' => 'Coordinate di riferimento', + 'ReferenceDate' => 'Data di riferimento', + 'ReferenceNumber' => 'Numero di riferimento', + 'ReferencePixelPhysicalValueX' => 'Valore fisico valore X pixel di riferimento', + 'ReferencePixelPhysicalValueY' => 'Valore fisico valore Y pixel di riferimento', + 'ReferencePixelX0' => 'Pixel X0 di riferimento', + 'ReferencePixelY0' => 'Pixel Y0 di riferimento', + 'ReferencePriority' => 'Priorità riferimento', + 'ReferenceService' => 'Riferimento di servizio', + 'ReferenceToRecordedSound' => 'Riferimento a suono registrato', + 'ReferenceVirtual' => 'Riferimento virtuale', + 'Refresh' => 'Aggiorna', + 'RegionCode' => 'Codice regione', + 'RegionInfo' => 'Info regione', + 'RegionList' => 'Elenco regioni', + 'RegionName' => 'Nome regione', + 'RegionOfResidence' => 'Regione di residenza', + 'RelatedImageFileFormat' => 'Formato file immagine correlato', + 'RelatedImageHeight' => 'Numero delle righe dei dati immagine', + 'RelatedImageWidth' => 'Larghezza immagine correlata', + 'RelatedSoundFile' => 'File audio relativo', + 'Relation' => 'Relazione', + 'RelativeVolumeAdjustment' => 'Correzione relativa volume', + 'ReleaseButtonToUseDial' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'ReleaseDate' => 'Data di rilascio', + 'ReleaseMode' => { + PrintConv => { + 'Normal' => 'Normale', + 'n/a' => 'n/d', + }, + }, + 'ReleaseTime' => 'Ora di rilascio', + 'RenderingIntent' => { + PrintConv => { + 'ICC-Absolute Colorimetric' => 'Colorimetrico Assoluto', + 'Media-Relative Colorimetric' => 'Colorimetrico Relativo', + 'Perceptual' => 'Percentuale', + 'Saturation' => 'Saturazione', + }, + }, + 'RenderingType3D' => 'Tipo resa 3D', + 'RepeatingFlashCount' => 'Conteggio ripetizione flash', + 'RepeatingFlashOutput' => 'Uscita ripetizione flash', + 'RepeatingFlashRate' => 'Frequenza ripetizione flash', + 'ReplyTo' => 'Rispondi a', + 'Resaved' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'Reserved' => 'Riservato', + 'Reserved1' => 'Riservato 1', + 'Resolution' => 'Risoluzione', + 'ResolutionMode' => 'Modo risoluzione', + 'ResolutionUnit' => { + Description => 'Unità risoluzione', + PrintConv => { + 'None' => 'Nessuno', + 'inches' => 'Pollici', + }, + }, + 'ResonantNucleus' => 'Nucleo risonante', + 'ResourceID' => 'ID risorsa', + 'ResourceType' => 'Tipo risorsa', + 'Resources' => 'Risorse', + 'ResourcesNeeded' => 'Risorse richieste', + 'Restrictions' => 'Restrizioni', + 'ResultsID' => 'ID risultati', + 'RetouchHistory' => { + Description => 'Cronologia ritocco', + PrintConv => { + 'Fisheye' => 'Fish-eye', + 'None' => 'Nessuno', + 'Quick Retouch' => 'Ritocco veloce', + 'Red Eye' => 'Occhi rossi', + 'Red Intensifier' => 'Intensificatore rosso', + 'Resize' => 'Ridimensione', + 'Sepia' => 'Seppia', + 'Straighten' => 'Raddrizza', + }, + }, + 'RetouchInfo' => 'Info ritocco', + 'Reuse' => { + Description => 'Riutilizza', + PrintConv => { + 'Not Applicable' => 'Non applicabile', + }, + }, + 'ReverseIndicators' => 'Inverti indicatori', + 'ReversedByteOrder' => 'Ordine byte invertito', + 'Revision' => 'Revisione', + 'RevisionDate' => 'Data revisione', + 'RevisionNumber' => 'Numero revisione', + 'RicohDate' => 'Data Ricoh', + 'RicohImageHeight' => 'Altezza immagine Ricoh', + 'RicohImageWidth' => 'Larghezza immagine Ricoh', + 'RicohRDC2' => 'RDC2 Ricoh', + 'RightAscension' => 'Ascensione retta', + 'Robots' => 'Robot', + 'RoleName' => 'Nome ruolo', + 'RoomNumber' => 'Numero stanza', + 'RoomOrSuiteName' => 'Numero stanza/suite', + 'RootFormatVersion' => 'Versione formato radice', + 'Rotation' => { + Description => 'Rotazione', + PrintConv => { + 'Horizontal' => 'Orizzontale', + 'Horizontal (Normal)' => 'Orizzontale (normale)', + 'Horizontal (normal)' => 'Orizzontale (normale)', + 'Rotate 180' => 'Ruota di 180°', + 'Rotate 270 CW' => 'Ruota di 270° in senso orario', + 'Rotate 90 CW' => 'Ruota di 90° senso orario', + 'Rotated 180' => 'Ruotato di 180°', + 'Rotated 270 CW' => 'Ruotato di 270° in senso orario', + 'Rotated 90 CW' => 'Ruotato di 90° in senso orario', + }, + }, + 'RotationAngle' => 'Angolo rotazione', + 'RotationDirection' => 'Direzione rotazione', + 'RowsPerStrip' => 'Righe per striscia', + 'RunWindow' => { + PrintConv => { + 'Normal' => 'Normale', + 'Restore' => 'Ripristina', + }, + }, + 'SEMInfo' => 'Info SEM', + 'SPIFFVersion' => 'Versione SPIFF', + 'SRAWQuality' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SRActive' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'SRGBRendering' => { + PrintConv => { + 'Saturation' => 'Saturazione', + }, + }, + 'SRResult' => { + PrintConv => { + 'Not ready' => 'Non pronto', + }, + }, + 'SVGVersion' => 'Versione SVG', + 'SafetyShiftInAvOrTv' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'SampleBits' => 'Bit campione', + 'SampleFormat' => { + Description => 'Formato campioni', + PrintConv => { + 'Complex float' => 'Virgola mobile immaginario', + 'Complex int' => 'Intero immaginario', + 'Float' => 'Virgola mobile', + 'Signed' => 'Con segno', + 'Undefined' => 'Non definito', + 'Unsigned' => 'Senza segno', + }, + }, + 'SampleIndex' => 'Indice campione', + 'SampleRate' => 'Frequenza campionamento', + 'SampleRate2' => 'Frequenza campionamento 2', + 'SampleSize' => 'Dimensione campione', + 'SampleSizes' => 'Dimensioni campione', + 'SampleStructure' => 'Struttura d\'esempio', + 'SampleText' => 'Testo d\'esempio', + 'SamplesPerPixel' => 'Campioni per pixel', + 'SamplesPerPixelUsed' => 'Campioni per pixel', + 'SamplingFrequency' => 'Frequenza campionamento', + 'Saturation' => { + Description => 'Saturazione', + PrintConv => { + 'High' => 'Alta', + 'Low' => 'Basso', + 'None' => 'Nessuno', + 'None (B&W)' => 'Nessuno (B&N)', + 'Normal' => 'Normale', + 'Vivid' => 'Vivace', + }, + }, + 'SaturationAdj' => 'Correzione saturazione', + 'SaturationFaithful' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SaturationLandscape' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SaturationMonochrome' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SaturationNeutral' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SaturationPortrait' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SaturationStandard' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SaturationUnknown' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SaturationUserDef1' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SaturationUserDef2' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SaturationUserDef3' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ScanImageEnhancer' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ScanningDirection' => { + PrintConv => { + 'R-L, Bottom-Top' => 'D-S, basso-alto', + 'R-L, Top-Bottom' => 'D-S, alto-basso', + }, + }, + 'Scene' => 'Scena', + 'SceneArea' => 'Area scena', + 'SceneCaptureType' => { + Description => 'Tipo cattura scena', + PrintConv => { + 'Landscape' => 'Orizzontale', + 'Night' => 'Notte', + 'Portrait' => 'Verticale', + }, + }, + 'SceneMode' => { + Description => 'Modo scena', + PrintConv => { + '3D Sweep Panorama' => '3D', + 'Anti Motion Blur' => 'Riduz. sfocat. movim.', + 'Aperture Priority' => 'Priorità diaframma', + 'Auto' => 'Automatico', + 'Children' => 'Bambini', + 'Cont. Priority AE' => 'AE prior. avan.cont.', + 'Fireworks' => 'Fuochi artificiali', + 'Handheld Night Shot' => 'Foto nott. senza trepp.', + 'Landscape' => 'Orizzontale', + 'Manual' => 'Manuale', + 'Night Portrait' => 'Rit. notturno', + 'Night Scene' => 'Visione notturna', + 'Night View/Portrait' => 'Visione/Ritratto notturni', + 'Normal' => 'Normale', + 'Off' => 'Spento', + 'Portrait' => 'Verticale', + 'Program' => 'Programma', + 'Scenery' => 'Paesaggio', + 'Shutter Priority' => 'Priorità otturatore', + 'Sports' => 'Evento sportivo', + 'Sunset' => 'Tramonto', + 'Sweep Panorama' => 'Panoramica ad arco', + 'Text' => 'Testo', + 'Vivid' => 'Vivace', + 'n/a' => 'n/d', + }, + }, + 'SceneModeUsed' => { + Description => 'Modo scena usato', + PrintConv => { + 'Aperture Priority' => 'Priorità diaframma', + 'Children' => 'Bambini', + 'Fireworks' => 'Fuochi artificiali', + 'Landscape' => 'Orizzontale', + 'Manual' => 'Manuale', + 'Portrait' => 'Verticale', + 'Program' => 'Programma', + 'Shutter Priority' => 'Priorità otturatore', + 'Sunset' => 'Tramonto', + 'Text' => 'Testo', + }, + }, + 'SceneNumber' => 'Numero scena', + 'SceneSelect' => { + PrintConv => { + 'Night' => 'Scena notturna', + 'Off' => 'Spento', + }, + }, + 'SceneType' => { + Description => 'Tipo scena', + PrintConv => { + 'Directly photographed' => 'Immagine fotografata direttamente', + }, + }, + 'School' => 'Scuola', + 'ScreenTips' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Section' => 'Sezione', + 'SectorSize' => 'Dimensione settore', + 'Security' => { + Description => 'Sicurezza', + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'SecurityClassification' => { + Description => 'Classificazione sicurezza', + PrintConv => { + 'Confidential' => 'Confidenziale', + 'Restricted' => 'Riservato', + 'Secret' => 'Segreto', + 'Top Secret' => 'Massima segretezza', + 'Unclassified' => 'Non classificato', + }, + }, + 'SelectAFAreaSelectMode' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'SelectableAFPoint' => { + PrintConv => { + '11 points' => '11 punti', + }, + }, + 'Selected' => 'Selezionato', + 'SelfTimer' => { + Description => 'Autoscatto', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'SelfTimer2' => 'Autoscatto 2', + 'SelfTimerInterval' => 'Intervallo autoscatto', + 'SelfTimerMode' => 'Modo autoscatto', + 'SelfTimerTime' => 'Tempo autoscatto', + 'SenderAddress' => 'Indirizzo mittente', + 'SenderName' => 'Nome mittente', + 'SensingMethod' => { + Description => 'Metodo misurazione esposimetrica', + PrintConv => { + 'Color sequential area' => 'Area sequenziale a colori', + 'Color sequential linear' => 'Lineare sequenziale a colori', + 'Not defined' => 'Non definito', + 'One-chip color area' => 'Sensore area a colori a un chip', + 'Three-chip color area' => 'Sensore area a colori a tre chip', + 'Trilinear' => 'Trilineare', + 'Two-chip color area' => 'Sensore area a colori a due chip', + }, + }, + 'Sensitivity' => 'Sensibilità', + 'SensitivityCalibrated' => 'Sensibilità calibrata', + 'SensitivitySteps' => { + Description => 'Passi sensibilità', + PrintConv => { + '1 EV Steps' => 'Step 1 EV', + }, + }, + 'SensitivityType' => { + Description => 'Tipo sensibilità', + PrintConv => { + 'ISO Speed' => 'Velocità ISO', + 'Recommended Exposure Index' => 'Indice esposizione raccomandato', + 'Recommended Exposure Index and ISO Speed' => 'Indice esposizione raccomandato e velocità ISO', + 'Standard Output Sensitivity' => 'Sensibilità predefinita uscita', + 'Standard Output Sensitivity and ISO Speed' => 'Sensibilità predefinita uscita e velocità ISO', + 'Standard Output Sensitivity and Recommended Exposure Index' => 'Sensibilità predefinita uscita e indice esposizione consigliato', + 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed' => 'Sensibilità predefinita uscita, indice esposizione consigliato e velocità ISO', + 'Unknown' => 'Sconosciuto', + }, + }, + 'SensorAreas' => 'Aree sensore', + 'SensorCleaning' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'SensorHeight' => 'Altezza sensore', + 'SensorID' => 'ID sensore', + 'SensorLeftBorder' => 'Bordo sinistro sensore', + 'SensorMode' => 'Modo sensore', + 'SensorPixelSize' => 'Dimensione pixel sensore', + 'SensorRedLevel' => 'Livello rosso sensore', + 'SensorSize' => 'Dimensione sensore', + 'SensorTemperature' => 'Temperatura sensore', + 'SensorTopBorder' => 'Bordo superiore sensore', + 'SensorType' => 'Tipo sensore', + 'SensorTypeCode' => 'Codice tipo sensore', + 'SensorWidth' => 'Larghezza sensore', + 'Sequence' => 'Sequenza', + 'SequenceName' => 'Nome sequenza', + 'SequenceNumber' => { + Description => 'Numero sequenza', + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SequenceShotInterval' => { + PrintConv => { + '10 frames/s' => '10 frame/s', + }, + }, + 'SequentialShot' => { + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'SerialNumber' => 'Numero di serie', + 'SerialNumberFormat' => { + Description => 'Formato numero di serie', + PrintConv => { + 'Format 1' => 'Formato 1', + 'Format 2' => 'Formato 2', + }, + }, + 'ServiceID' => 'ID servizio', + 'ServiceIdentifier' => 'Identificativo servizio', + 'ServiceOrganizationName' => 'Nome organizzazione servizio', + 'SetButtonCrossKeysFunc' => { + PrintConv => { + 'Normal' => 'Normale', + }, + }, + 'SetButtonWhenShooting' => { + PrintConv => { + 'Change ISO speed' => 'Cambia velocità ISO', + 'Change parameters' => 'Cambia parametri', + 'ISO speed' => 'Velocità ISO', + 'Normal (disabled)' => 'Normale (disabilitato)', + 'White balance' => 'Bilanciamento del bianco', + }, + }, + 'SetCookie' => 'Imposta cookie', + 'SetFunctionWhenShooting' => { + PrintConv => { + 'Change Parameters' => 'Cambia parametri', + }, + }, + 'SetInfo' => 'Imposta info', + 'SetSubtitle' => 'Imposta sottotitoli', + 'ShadingCompensation' => { + Description => 'Compensazione ombreggiatura', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ShadingCompensation2' => { + Description => 'Compensazione ombreggiatura 2', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Shadow' => 'Ombra', + 'ShadowCorrection' => { + PrintConv => { + 'Normal' => 'Normale', + 'Off' => 'Spento', + 'Strong' => 'Forte', + }, + }, + 'ShadowScale' => 'Scala ombre', + 'Shadows' => 'Ombre', + 'ShakeReduction' => { + PrintConv => { + 'Off' => 'Spento', + 'Off (4)' => 'Spento (4)', + }, + }, + 'SharedData' => 'Dati condivisi', + 'Sharpening' => { + PrintConv => { + 'Low' => 'Basso', + 'Normal' => 'Normale', + 'Off' => 'Spento', + }, + }, + 'Sharpness' => { + Description => 'Nitidezza', + PrintConv => { + 'Hard' => 'Forte', + 'Normal' => 'Normale', + 'Soft' => 'Leggera', + 'n/a' => 'n/d', + }, + }, + 'SharpnessFaithful' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SharpnessFrequency' => { + PrintConv => { + 'Low' => 'Basso', + 'n/a' => 'n/d', + }, + }, + 'SharpnessLandscape' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SharpnessMonochrome' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SharpnessNeutral' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SharpnessPortrait' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SharpnessStandard' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SharpnessUnknown' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SharpnessUserDef1' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SharpnessUserDef2' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'SharpnessUserDef3' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ShootingMode' => { + Description => 'Modo scatto', + PrintConv => { + 'Aperture Priority' => 'Priorità diaframma', + 'Fireworks' => 'Fuochi artificiali', + 'Manual' => 'Manuale', + 'Normal' => 'Normale', + 'Portrait' => 'Verticale', + 'Program' => 'Programma', + 'Scenery' => 'Paesaggio', + 'Shutter Priority' => 'Priorità otturatore', + 'Sunset' => 'Tramonto', + }, + }, + 'ShootingModeSetting' => { + Description => 'Impostazione modo scatto', + PrintConv => { + 'Self-timer' => 'Autoscatto', + }, + }, + 'ShortComment' => 'Commento breve', + 'ShortDescription' => 'Descrizione breve', + 'ShortDocumentID' => 'ID documento breve', + 'ShortReleaseTimeLag' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'ShotComment' => 'Commento scatto', + 'ShotCommentKind' => 'Tipo commento scatto', + 'ShotDate' => 'Data scatto', + 'ShotDay' => 'Giorno scatto', + 'ShotDescription' => 'Descrizione scatto', + 'ShotDuration' => 'Durata scatto', + 'ShotInfoVersion' => 'Info versione scatto', + 'ShotList' => 'Elenco scatti', + 'ShotLocation' => 'Posizione scatto', + 'ShotLocationSets' => 'Insiemi posizione scatto', + 'ShotName' => 'Nome scatto', + 'ShotNumber' => 'Numero scatto', + 'ShotSize' => 'Dimensione scatto', + 'ShutterCount' => 'Conteggio scatti', + 'ShutterMode' => { + PrintConv => { + 'Aperture Priority' => 'Priorità diaframma', + }, + }, + 'ShutterReleaseButtonAE-L' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ShutterReleaseNoCFCard' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'ShutterSpeed' => 'Tempo esposizione', + 'ShutterSpeedRange' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'ShutterSpeedValue' => 'Velocità otturatore', + 'SideNumber' => 'Numero lato', + 'Sidebars' => 'Barre laterali', + 'SimilarityIndex' => 'Indice di Somiglianza', + 'SingleFrameBracketing' => { + PrintConv => { + 'Low' => 'Basso', + }, + }, + 'Site' => 'Sito', + 'Size' => 'Dimensioni', + 'SlideShow' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'SlowShutter' => { + PrintConv => { + 'None' => 'Nessuno', + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'SlowSync' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'SmartRange' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'SmileShutter' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Smoothness' => 'Arrotondamento', + 'Software' => 'Software utilizzato', + 'Source' => 'Origine', + 'SourceTitle' => 'Titolo fonte', + 'SourceTrackID' => 'ID traccia fonte', + 'SourceTrackIDs' => 'ID traccia fonte', + 'SourceType' => 'Tipo fonte', + 'SourceURL' => 'URL fonte', + 'SourceValue' => 'Valore fonte', + 'SpatialFrequencyResponse' => 'Risposta in frequenza spaziale', + 'SpecialBuild' => 'Compilazione speciale', + 'SpecialEffectMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'SpecialEffectSetting' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'SpecialEffectsOpticalFilter' => { + PrintConv => { + 'None' => 'Nessuno', + 'Star' => 'Stella', + }, + }, + 'SpecialInstructions' => 'Istruzioni', + 'SpectralSensitivity' => 'Sensibilità spettro', + 'Speed' => { + Description => 'Velocità', + PrintConv => { + 'Fast' => 'Veloce', + 'Medium' => 'Medio', + 'Slow' => 'Lento', + }, + }, + 'SpotMeteringMode' => { + PrintConv => { + 'Center' => 'Centro', + }, + }, + 'StandardOutputSensitivity' => 'Sensibilità predefinita uscita', + 'StartTime' => 'Ora inizio', + 'State' => 'Stato', + 'StorageMethod' => { + PrintConv => { + 'Linear' => 'Lineare', + }, + }, + 'StreamType' => { + PrintConv => { + 'File Transfer' => 'Trasferimento file', + 'Text' => 'Testo', + }, + }, + 'StretchMode' => { + PrintConv => { + 'Fixed length' => 'Lunghezza fissa', + }, + }, + 'StripByteCounts' => 'Byte per striscia', + 'StripOffsets' => 'Offset striscia', + 'StripRowCounts' => 'Numero righe striscia', + 'Sub-location' => 'Località', + 'SubSecCreateDate' => 'Data di creazione', + 'SubSecDateTimeOriginal' => 'Data/ora originali', + 'SubSecModifyDate' => 'Data modifica', + 'SubSecTime' => 'Sottosecondi ora', + 'SubSecTimeDigitized' => 'Sottosecondi ora digitalizzazione', + 'SubSecTimeOriginal' => 'Sottosecondi ora originale', + 'SubTileBlockSize' => 'Dimensione blocco sotto-tasselli', + 'SubfileType' => { + Description => 'Tipo sotto-file', + PrintConv => { + 'Full-resolution image' => 'Immagine con risoluzione originale', + 'Reduced-resolution image' => 'Immagine a risoluzione ridotta', + 'Single page of multi-page image' => 'Singola pagina di un\'immagine multi-pagina', + 'Single page of multi-page reduced-resolution image' => 'Singola pagina di un\'immagine multi-pagina a risoluzione ridotta', + 'TIFF-FX mixed raster content' => 'Contenuto raster misto TIFF-FX', + 'TIFF/IT final page' => 'Pagina finale TIFF/IT', + 'Thumbnail image' => 'Miniatura', + 'Transparency mask' => 'Maschera trasparenza', + 'Transparency mask of multi-page image' => 'Maschera trasparenza di immagine multi-pagina', + 'Transparency mask of reduced-resolution image' => 'Maschera trasparenza di immagine a risoluzione ridotta', + 'Transparency mask of reduced-resolution multi-page image' => 'Maschera trasparenza di immagine multi-pagina a risoluzione ridotta', + 'invalid' => 'non valido', + }, + }, + 'SubimageColor' => { + PrintConv => { + 'RGB (uncalibrated)' => 'RGB (non calibrato)', + 'RGB with Opacity' => 'RGB con opacità', + 'RGB with Opacity (uncalibrated)' => 'RGB con opacità (non calibrato)', + }, + }, + 'Subject' => 'Soggetto', + 'SubjectArea' => 'Area soggetto', + 'SubjectCode' => 'Codice sottetto', + 'SubjectDistance' => 'Distanza soggetto', + 'SubjectDistanceRange' => { + Description => 'Intervallo distanza soggetto', + PrintConv => { + 'Close' => 'Vicino', + 'Distant' => 'Lontano', + 'Unknown' => 'Sconosciuto', + }, + }, + 'SubjectLocation' => 'Posizione soggetto', + 'SubjectName' => 'Nome soggetto', + 'SubjectProgram' => { + PrintConv => { + 'None' => 'Nessuno', + 'Portrait' => 'Verticale', + 'Sunset' => 'Tramonto', + 'Text' => 'Testo', + }, + }, + 'SubjectReference' => 'Codice Soggetto', + 'SubjectUnits' => { + PrintConv => { + 'radians' => 'Radianti', + }, + }, + 'Subsystem' => { + Description => 'Sottosistema', + PrintConv => { + 'EFI ROM' => 'ROM EFI', + 'EFI application' => 'Applicazione EFI', + 'EFI boot service' => 'Servizio di avvio EFI', + 'EFI runtime driver' => 'Driver a runtine EFI', + 'Native' => 'Nativo', + 'OS/2 command line' => 'Linea di comando OS/2', + 'POSIX command line' => 'Linea di comando POSIX', + 'Unknown' => 'Sconosciuto', + 'Windows command line' => 'Linea di comando Windows', + }, + }, + 'SubsystemVersion' => 'Versione sottosistema', + 'Subtitle' => 'Sottotitolo', + 'Suffix' => 'Suffisso', + 'SuggestedPalette' => 'Tavolozza suggerita', + 'Summary' => 'Sommario', + 'SuperMacro' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'SuperimposedDisplay' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'SupplementalCategories' => 'Categoria Supplementare', + 'SupplementalType' => { + PrintConv => { + 'Main Image' => 'Non impostato', + 'Rasterized Caption' => 'Didascalia rasterizzata', + 'Reduced Resolution Image' => 'Risoluzione immagine ridotta', + }, + }, + 'SurroundMode' => { + PrintConv => { + 'Not Dolby surround' => 'Non Dolby surround', + 'Not indicated' => 'Non indicato', + }, + }, + 'SweepPanoramaDirection' => { + Description => 'Direzione panoramica ad arco', + PrintConv => { + 'Left' => 'Sinistra', + 'Right' => 'Destra', + }, + }, + 'SweepPanoramaSize' => 'Dimensione panoramica ad arco', + 'SwitchToRegisteredAFPoint' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'SynchronizedLyricText' => 'Testo sincronizzato', + 'SystemNameOrNumber' => 'Nome/numero sistema', + 'T4Options' => { + Description => 'Opzioni T4', + PrintConv => { + '2-Dimensional encoding' => 'Codifica 2D', + 'Fill bits added' => 'Bit di riempimento aggiunti', + 'Uncompressed' => 'Non compresso', + }, + }, + 'T6Options' => { + Description => 'Opzioni T6', + PrintConv => { + 'Uncompressed' => 'Non compresso', + }, + }, + 'T82Options' => 'Opzioni T82', + 'T88Options' => 'Opzioni T88', + 'TIFFPreview' => 'Anteprima TIFF', + 'TIFFSummary' => 'Sommario TIFF', + 'TIFF_FXExtensions' => { + Description => 'Estensioni TIFF FX', + PrintConv => { + 'B&W JBIG2' => 'JBIG2 bianco e nero', + 'JBIG2 Profile M' => 'JBIG2 TIFF FX', + 'N Layer Profile M' => 'Livello N profilo M', + 'Resolution/Image Width' => 'Risoluzione/larghezza immagine', + 'Shared Data' => 'Dati condivisi', + }, + }, + 'Tagged' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'TaggingTime' => 'Ora tag', + 'TargetDeltaType' => { + PrintConv => { + 'Absolute' => 'Assoluto', + }, + }, + 'TargetPrinter' => 'Stampante di destinazione', + 'Technology' => { + Description => 'Tecnologia', + PrintConv => { + 'Active Matrix Display' => 'Display a matrice attiva', + 'Cathode Ray Tube Display' => 'Monitor con Tubo a Raggi Catodici', + 'Digital Camera' => 'Fotocamera Digitale', + 'Dye Sublimation Printer' => 'Stampante a Sublimazione Termica', + 'Electrophotographic Printer' => 'Stampante Laser', + 'Electrostatic Printer' => 'Stampante Elettrostatica', + 'Film Scanner' => 'Scanner per Pellicola', + 'Ink Jet Printer' => 'Stampante Ink Jet', + 'Offset Lithography' => 'Litografía Offset', + 'Passive Matrix Display' => 'Display a Matrice passiva', + 'Photographic Paper Printer' => 'Stampante per Carta Fotografica', + 'Projection Television' => 'Proiettore televisivo', + 'Reflective Scanner' => 'Scanner a riflessione', + 'Thermal Wax Printer' => 'Stampante Thermal Wax', + }, + }, + 'Teleconverter' => { + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'Temperature' => 'Temperatura', + 'Template' => 'Modello', + 'TermsOfUse' => 'Termini di utilizzo', + 'TestTarget' => { + PrintConv => { + 'Grayscale' => 'Scala di grigi', + }, + }, + 'Text' => 'Testo', + 'TextColor' => 'Colore testo', + 'TextComments' => 'Testo commenti', + 'TextEncoding' => { + Description => 'Codifica testo', + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'TextFace' => { + Description => 'Aspetto testo', + PrintConv => { + 'Extend' => 'Estendi', + 'Italic' => 'Corsivo', + 'Shadow' => 'Ombra', + }, + }, + 'TextFont' => { + Description => 'Carattere testo', + PrintConv => { + 'System' => 'Sistema', + }, + }, + 'TextLayers' => 'Livelli testo', + 'TextSize' => 'Dimenione testo', + 'TextStamp' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'TextString' => 'Stringa testo', + 'TextValue' => 'Valore testo', + 'Thresholding' => { + Description => 'Soglia', + PrintConv => { + 'No dithering or halftoning' => 'Nessun dithering o resa a mezzi toni', + 'Randomized dither' => 'Dither casuale', + }, + }, + 'ThumbnailFileName' => 'Nome file miniatura', + 'ThumbnailFormat' => 'Formato miniatura', + 'ThumbnailHeight' => 'Miniatura', + 'ThumbnailImage' => 'Miniatura', + 'ThumbnailImageName' => 'Nome miniatura', + 'ThumbnailImageSize' => 'Dimensione miniatura', + 'ThumbnailImageType' => 'Tipo miniatura', + 'ThumbnailImageValidArea' => 'Area valida miniatura', + 'ThumbnailLength' => 'Dimensioni miniatura', + 'ThumbnailOffset' => 'Offset miniatura', + 'ThumbnailWidth' => 'Larghezza miniatura', + 'Thumbnails' => 'Miniature', + 'TileByteCounts' => 'Byte tassello', + 'TileDepth' => 'Profondità tassello', + 'TileLength' => 'Lunghezza tassello', + 'TileOffsets' => 'Offset tasselli', + 'TileWidth' => 'Larghezza tassello', + 'Tiles' => 'Tasselli', + 'Time' => 'Ora', + 'TimeCreated' => 'Ora di Creazione', + 'TimeSent' => 'Ora d\'invio', + 'TimeStamp' => 'Marcatura oraria', + 'TimeStamp1' => 'Marcatura oraria 1', + 'TimeZone' => 'Fuso orario', + 'TimeZoneCity' => { + Description => 'Città fuso orario', + PrintConv => { + 'London' => 'Londra', + 'Tehran' => 'Teheran', + 'n/a' => 'n/d', + }, + }, + 'TimeZoneCode' => 'Codice fuso orario', + 'TimeZoneInfo' => 'Info fuso orario', + 'TimeZoneOffset' => 'Scostamento fuso orario', + 'TimerFunctionButton' => { + PrintConv => { + 'Self-timer' => 'Autoscatto', + 'Shooting Mode' => 'Modo scatto', + 'White Balance' => 'Bilanciamento del bianco', + }, + }, + 'TimerLength' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'Tint' => 'Tinta', + 'Title' => 'Titolo', + 'Title2' => 'Titolo 2', + 'TitleKind' => 'Tipo titolo', + 'TitleLen' => 'Lungh titolo', + 'TitleNum' => 'Num titolo', + 'TitleOfParts' => 'Titolo parti', + 'TitleSortOrder' => 'Ordinamento titolo', + 'TitlesOfParts' => 'Titoli parti', + 'TitlesSets' => 'Insiemi di titoli', + 'ToDoTitle' => 'Titolo da fare', + 'ToneComp' => 'Compensazione Tono', + 'ToneCurve' => { + PrintConv => { + 'Manual' => 'Manuale', + }, + }, + 'ToneCurveActive' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'ToneCurveName' => { + PrintConv => { + 'Linear' => 'Lineare', + 'Strong Contrast' => 'Contrasto elevato', + }, + }, + 'ToneCurveProperty' => { + PrintConv => { + 'Linear' => 'Lineare', + 'Shot Settings' => 'Impostazioni scatto', + }, + }, + 'ToningEffect' => { + PrintConv => { + 'Blue-green' => 'Blu-Verde', + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Purple' => 'Porpora', + 'Purple-blue' => 'Porpora-blu', + 'Red' => 'Rosso', + 'Red-purple' => 'Rosso-porpora', + 'Sepia' => 'Seppia', + 'Yellow' => 'Giallo', + 'n/a' => 'n/d', + }, + }, + 'ToningEffectFaithful' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ToningEffectLandscape' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ToningEffectMonochrome' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Purple' => 'Porpora', + 'Sepia' => 'Seppia', + 'n/a' => 'n/d', + }, + }, + 'ToningEffectNeutral' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ToningEffectPortrait' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ToningEffectStandard' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'ToningEffectUnknown' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Purple' => 'Porpora', + 'Sepia' => 'Seppia', + 'n/a' => 'n/d', + }, + }, + 'ToningEffectUserDef1' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Purple' => 'Porpora', + 'Sepia' => 'Seppia', + 'n/a' => 'n/d', + }, + }, + 'ToningEffectUserDef2' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Purple' => 'Porpora', + 'Sepia' => 'Seppia', + 'n/a' => 'n/d', + }, + }, + 'ToningEffectUserDef3' => { + PrintConv => { + 'Green' => 'Verde', + 'None' => 'Nessuno', + 'Purple' => 'Porpora', + 'Sepia' => 'Seppia', + 'n/a' => 'n/d', + }, + }, + 'ToolName' => 'Nome strumento', + 'ToolVersion' => 'Versione strumento', + 'TotalFrames' => 'Frame totali', + 'Track' => 'Traccia', + 'TrackProperty' => { + PrintConv => { + 'Read only' => 'Sola lettura', + }, + }, + 'TransferFunction' => 'Funzione di trasferimento', + 'TransferRange' => 'Intervallo di trasferimento', + 'Transform' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Transformation' => { + Description => 'Trasformazione', + PrintConv => { + 'Horizontal (normal)' => 'Orizzontale (normale)', + 'Mirror horizontal' => 'Rifletti orizzontalmente', + 'Mirror horizontal and rotate 270 CW' => 'Rifletti orizzontalmente e ruota di 270° in senso orario', + 'Mirror horizontal and rotate 90 CW' => 'Rifletti orizzontalmente e ruota di 90° in senso orario', + 'Mirror vertical' => 'Rifletti verticalmente', + 'Rotate 180' => 'Ruota di 180°', + 'Rotate 270 CW' => 'Ruota di 270° in senso orario', + 'Rotate 90 CW' => 'Ruota di 90° senso orario', + }, + }, + 'TransmissionReference' => 'Riferimento trasmissione', + 'TransparencyIndicator' => 'Indicatore trasparenza', + 'TrapIndicator' => 'Indicatore trap', + 'Trapped' => { + PrintConv => { + 'False' => 'Falso', + 'Unknown' => 'Sconosciuto', + }, + }, + 'TungstenAWB' => { + PrintConv => { + 'Strong Correction' => 'Correzione elevata', + }, + }, + 'TxFace' => 'Stile carattere testo', + 'TxFont' => 'Numero carattere', + 'TxSize' => 'Dimenione testo', + 'UIC1Tag' => 'Tag UIC1', + 'UIC2Tag' => 'Tag UIC2', + 'UIC3Tag' => 'Tag UIC3', + 'UIC4Tag' => 'Tag UIC4', + 'USPTOOriginalContentType' => { + PrintConv => { + 'Color' => 'Colore', + 'Grayscale' => 'Scala di grigi', + 'Text or Drawing' => 'Testo o disegno', + }, + }, + 'UV-IRFilterCorrection' => { + PrintConv => { + 'Not Active' => 'Non attivo', + }, + }, + 'Uncompressed' => { + Description => 'Non compresso', + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'UninitializedDataSize' => 'Dimensione dati non inizializzati', + 'UniqueCameraModel' => 'Modello unico fotocamera', + 'UniqueDocumentID' => 'ID Unico del Documento', + 'Units' => 'Unità', + 'Unknown' => 'Sconosciuto', + 'Unsharp1Color' => { + PrintConv => { + 'Green' => 'Verde', + 'Red' => 'Rosso', + 'Yellow' => 'Giallo', + }, + }, + 'Unsharp2Color' => { + PrintConv => { + 'Green' => 'Verde', + 'Red' => 'Rosso', + 'Yellow' => 'Giallo', + }, + }, + 'Unsharp3Color' => { + PrintConv => { + 'Green' => 'Verde', + 'Red' => 'Rosso', + 'Yellow' => 'Giallo', + }, + }, + 'Unsharp4Color' => { + PrintConv => { + 'Green' => 'Verde', + 'Red' => 'Rosso', + 'Yellow' => 'Giallo', + }, + }, + 'UnsharpMask' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Urgency' => { + Description => 'Urgenza', + PrintConv => { + '0 (reserved)' => '0 (riservato)', + '1 (most urgent)' => '1 (molto urgente)', + '5 (normal urgency)' => '5 (urgenza normale)', + '8 (least urgent)' => '8 (meno urgent)e', + '9 (user-defined priority)' => '9 (riservato per usi futuri)', + }, + }, + 'UsableMeteringModes' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'UsableShootingModes' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'UserAccess' => { + PrintConv => { + 'Print' => 'Stampa', + }, + }, + 'UserComment' => 'Commento utente', + 'UserDef1PictureStyle' => { + PrintConv => { + 'Landscape' => 'Orizzontale', + 'Portrait' => 'Verticale', + }, + }, + 'UserDef2PictureStyle' => { + PrintConv => { + 'Landscape' => 'Orizzontale', + 'Portrait' => 'Verticale', + }, + }, + 'UserDef3PictureStyle' => { + PrintConv => { + 'Landscape' => 'Orizzontale', + 'Portrait' => 'Verticale', + }, + }, + 'UserDefinedText' => 'Testo personalizzato', + 'UserDefinedURL' => 'URL personalizzato', + 'VFDisplayIllumination' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'VRDVersion' => 'Versione VRD', + 'VRInfo' => 'Info Riduzione Vibrazione', + 'VRInfoVersion' => 'Info Versione VR', + 'VR_0x66' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'Value' => 'Valore', + 'VariousModes' => 'Modi vari', + 'VariousModes2' => 'Modi vari 2', + 'Version' => 'Numero versione immagine', + 'Version2' => 'Versione 2', + 'VersionBF' => 'Versione BF', + 'VersionCreateDate' => 'Data creazione versione', + 'VersionID' => 'ID versione', + 'VersionIdentifier' => 'Identificativo versione', + 'VersionInfo' => 'Info versione', + 'VersionModifyDate' => 'Data modifica versione', + 'VersionNumber' => 'Numero versione', + 'VersionNumberString' => 'Stringa numero versione', + 'VersionTitle' => 'Titolo versione', + 'VersionYear' => 'Anno versione', + 'Versions' => 'Versioni', + 'VerticalCSType' => { + PrintConv => { + 'Caspian Sea' => 'Mar Caspio', + 'WGS 84 ellipsoid' => 'Ellissoide WGS 84', + }, + }, + 'VerticalUnits' => 'Unità verticali', + 'VibrationReduction' => { + Description => 'Riduzione vibrazione', + PrintConv => { + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'VideoAlphaMode' => { + PrintConv => { + 'None' => 'Nessuno', + }, + }, + 'VideoAttributes' => { + PrintConv => { + 'Encrypted' => 'Crittografato', + }, + }, + 'VideoBitrate' => 'Bitrate video', + 'VideoCardGamma' => 'Gamma della Scheda Video', + 'VideoCodec' => 'Codec video', + 'VideoCodecDescription' => 'Descrizione codec video', + 'VideoCodecID' => 'ID codec video', + 'VideoCodecInfo' => 'Info codec video', + 'VideoCodecName' => 'Nome codec video', + 'VideoFieldOrder' => { + PrintConv => { + 'Progressive' => 'Progressivo', + }, + }, + 'VideoQuality' => { + PrintConv => { + 'Low' => 'Basso', + }, + }, + 'VideoScanType' => { + PrintConv => { + 'Progressive' => 'Progressivo', + }, + }, + 'VideoStreamType' => { + PrintConv => { + 'Reserved' => 'Riservato', + }, + }, + 'ViewInfoDuringExposure' => { + PrintConv => { + 'Enable' => 'Abilita', + }, + }, + 'ViewfinderWarning' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ViewingCondDesc' => 'Descrizione Condizioni di Visualizzazione', + 'ViewingMode2' => { + PrintConv => { + 'n/a' => 'n/d', + }, + }, + 'VignetteControl' => { + PrintConv => { + 'Low' => 'Basso', + 'Normal' => 'Normale', + 'Off' => 'Spento', + }, + }, + 'VignettingCorrection' => { + PrintConv => { + 'Off' => 'Spento', + 'n/a' => 'n/d', + }, + }, + 'VirtualImageHeight' => 'Altezza immagine virtuale', + 'VirtualImageWidth' => 'Larghezza immagine virtuale', + 'VirtualPageUnits' => 'Unità pagina virtuale', + 'VoiceMemo' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'WBAdjLighting' => { + PrintConv => { + 'Daylight (cloudy)' => 'Luce del giorno (2)', + 'Daylight (direct sunlight)' => 'Luce del giorno (0)', + 'Daylight (shade)' => 'Luce del giorno (1)', + 'None' => 'Nessuno', + }, + }, + 'WBBracketMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'WBFineTuneActive' => { + PrintConv => { + 'Yes' => 'Sì', + }, + }, + 'WCSProfiles' => 'Profilo Windows Color System', + 'WhiteBalance' => { + Description => 'Bilanciamento del bianco', + PrintConv => { + 'Auto' => 'Equilibrio del bianco automatico', + 'Black & White' => 'Monocromatico', + 'Cloudy' => 'Nuvoloso', + 'Color Temperature/Color Filter' => 'Temperatura colore / Filtro colore', + 'Cool White Fluorescent' => 'Fluorescente bianca fredda', + 'Custom' => 'Personalizzato', + 'Custom 1' => 'PERSONAL.1', + 'Custom 2' => 'PERSONAL.2', + 'Custom 3' => 'PERSONAL.3', + 'Custom 4' => 'PERSONAL.4', + 'Day White Fluorescent' => 'Fluorescente a luce del giorno bianca', + 'Daylight' => 'Luce del giorno', + 'Daylight Fluorescent' => 'Fluorescente a luce del giorno', + 'Fluorescent' => 'Fluorescente', + 'Manual' => 'Manuale', + 'Shade' => 'Ombrato', + 'Tungsten' => 'Tungsteno (luce incandescente)', + 'Unknown' => 'Sconosciuto', + 'Warm White Fluorescent' => 'Luce fluorescente bianca calda', + 'White Fluorescent' => 'Fluorescente bianca', + }, + }, + 'WhiteBalance2' => 'Bilanciamento del bianco 2', + 'WhiteBalanceAdj' => { + Description => 'Adattamento bilanciamento bianco', + PrintConv => { + 'Cloudy' => 'Nuvoloso', + 'Daylight' => 'Luce del giorno', + 'Fluorescent' => 'Fluorescente', + 'Off' => 'Spento', + 'Shade' => 'Ombrato', + 'Shot Settings' => 'Impostazioni scatto', + 'Tungsten' => 'Tungsteno (luce incandescente)', + }, + }, + 'WhiteBalanceAutoAdjustment' => { + Description => 'Adattamento automatico bilanciamento bianco', + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'WhiteBalanceBracketing' => { + PrintConv => { + 'Low' => 'Basso', + 'Off' => 'Spento', + }, + }, + 'WhiteBalanceFineTune' => 'Regolazione Fine Bilanciamento Bianco', + 'WhiteBalanceMode' => { + PrintConv => { + 'Unknown' => 'Sconosciuto', + }, + }, + 'WhiteBalanceSet' => { + PrintConv => { + 'Cloudy' => 'Nuvoloso', + 'Daylight' => 'Luce del giorno', + 'Daylight Fluorescent' => 'Fluorescente a luce del giorno', + 'Manual' => 'Manuale', + 'Shade' => 'Ombrato', + 'Tungsten' => 'Tungsteno (luce incandescente)', + 'White Fluorescent' => 'Fluorescente bianca', + }, + }, + 'WhiteBalanceSetting' => { + PrintConv => { + 'Fluorescent (+1)' => 'Fluorescente (+1)', + 'Fluorescent (+2)' => 'Fluorescente (+2)', + 'Fluorescent (+3)' => 'Fluorescente (+3)', + 'Fluorescent (-1)' => 'Fluorescente (-1)', + 'Fluorescent (-2)' => 'Fluorescente (-2)', + 'Fluorescent (-3)' => 'Fluorescente (-3)', + 'Fluorescent (0)' => 'Fluorescente (0)', + 'Shade (+1)' => 'Ombrato (+1)', + 'Shade (+2)' => 'Ombrato (+2)', + 'Shade (+3)' => 'Ombrato (+3)', + 'Shade (-1)' => 'Ombrato (-1)', + 'Shade (-2)' => 'Ombrato (-2)', + 'Shade (-3)' => 'Ombrato (-3)', + 'Shade (0)' => 'Ombrato (0)', + }, + }, + 'WhiteLevel' => 'Livello bianco', + 'WhitePoint' => 'Punto bianco', + 'WideRange' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'WidthResolution' => 'Risoluzione larghezza', + 'Writer-Editor' => 'Autore Didascalia/Descrizione', + 'XMethod' => { + PrintConv => { + 'No Magnification' => 'Nessun ingrandimento', + }, + }, + 'XPAuthor' => 'Autore', + 'XPComment' => 'Commento XP', + 'XPKeywords' => 'Parole chiave XP', + 'XPSubject' => 'Soggetto XP', + 'XPTitle' => 'Titolo XP', + 'XPosition' => 'Posizione X', + 'XResolution' => 'Risoluzione orizzontale immagine', + 'YCbCrCoefficients' => 'Coefficienti matrice trasformazione spazio colori', + 'YCbCrPositioning' => { + Description => 'Posizionamento Y e C', + PrintConv => { + 'Centered' => 'Centrato', + 'Co-sited' => 'Affiancato', + }, + }, + 'YCbCrSubSampling' => 'Indice sottocampionamento da Y a C', + 'YMethod' => { + PrintConv => { + 'No Magnification' => 'Nessun ingrandimento', + }, + }, + 'YPosition' => 'Posizione Y', + 'YResolution' => 'Risoluzione verticale immagine', + 'Year' => 'Anno', + 'ZipCompression' => { + PrintConv => { + 'None' => 'Nessuno', + 'Reduced with compression factor 1' => 'Ridotto con fattore compressione 1', + 'Reduced with compression factor 2' => 'Ridotto con fattore compressione 2', + 'Reduced with compression factor 3' => 'Ridotto con fattore compressione 3', + 'Reduced with compression factor 4' => 'Ridotto con fattore compressione 4', + }, + }, + 'ZoneMatching' => { + Description => 'Adeguamento zona', + PrintConv => { + 'High Key' => 'Hi', + 'ISO Setting Used' => 'Impostazione ISO usata', + 'Low Key' => 'Lo', + }, + }, + 'ZoneMatchingMode' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ZoneMatchingOn' => { + PrintConv => { + 'Off' => 'Spento', + }, + }, + 'ZoomCenter' => 'Centro zoom', + 'ZoomFactor' => 'Fattore di zoom', + 'ZoomPos' => 'Posizione zoom', + 'iTunesU' => 'ITunes U', +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::it.pm - ExifTool Italian language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke, Ferdinando Agovino, Emilio Dati and Michele Locati +for providing this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/ja.pm b/ExifTool/lib/Image/ExifTool/Lang/ja.pm new file mode 100644 index 0000000..10ab6c4 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/ja.pm @@ -0,0 +1,5858 @@ +#------------------------------------------------------------------------------ +# File: ja.pm +# +# Description: ExifTool Japanese language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::ja; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.25'; + +%Image::ExifTool::Lang::ja::Translate = ( + 'AEAperture' => 'AE絞り', + 'AEBAutoCancel' => { + Description => 'ブラケティング自動解除', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AEBBracketValue' => 'AEBブラケット値', + 'AEBSequence' => 'ブラケティング順åº', + 'AEBSequenceAutoCancel' => { + Description => 'ブラケティング順åº/自動解除', + PrintConv => { + '-,0,+/Disabled' => 'ï¼â†’0 →+/ã—ãªã„', + '-,0,+/Enabled' => 'ï¼â†’0 →+/ã™ã‚‹', + '0,-,+/Disabled' => '0 →ï¼â†’+/ã—ãªã„', + '0,-,+/Enabled' => '0 →ï¼â†’+/ã™ã‚‹', + }, + }, + 'AEBShotCount' => 'ãƒ–ãƒ©ã‚±ãƒ†ã‚£ãƒ³ã‚°æ™‚ã®æ’®å½±æžšæ•°', + 'AEBXv' => 'AEブラケット 露出補正', + 'AEExposureTime' => 'AE露出時間', + 'AEExtra' => 'AE特別?', + 'AEInfo' => '自動露出情報', + 'AELock' => { + Description => 'AEロック', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AELockButton' => { + Description => 'AE-L/AF-L', + PrintConv => { + 'AE-L/AF Area' => 'AE-L/AFエリア', + 'AE-L/AF-L/AF Area' => 'AE-L/AF-L/AFエリア', + 'AF-L/AF Area' => 'AF-L/AFエリア', + 'AF-ON/AF Area' => 'AFオン/AFエリア', + 'FV Lock' => 'FVロック', + 'Focus Area Selection' => 'ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã‚¨ãƒªã‚¢é¸æŠž', + }, + }, + 'AEMaxAperture' => 'AE最大絞り', + 'AEMaxAperture2' => 'AE最大絞り(2)', + 'AEMeteringMode' => { + Description => 'AE測光モード', + PrintConv => { + 'Multi-segment' => 'パターン', + }, + }, + 'AEMeteringSegments' => '自動露出測光値', + 'AEMinAperture' => 'AE最å°çµžã‚Š', + 'AEMinExposureTime' => 'AE最å°éœ²å‡ºæ™‚é–“', + 'AEProgramMode' => { + Description => 'AEプログラムモード', + PrintConv => { + 'Av, B or X' => 'Av, Bã‹X', + 'Candlelight' => 'キャンドルライト', + 'DOF Program' => '深度優先プログラム', + 'DOF Program (P-Shift)' => '深度優先プログラム(Pシフト)', + 'Hi-speed Program' => '高速優先プログラム', + 'Hi-speed Program (P-Shift)' => '高速優先プログラム(Pシフト)', + 'Kids' => 'キッズ', + 'Landscape' => '風景', + 'M, P or TAv' => 'M, Pã‹TAv', + 'MTF Program' => 'MTF優先プログラム', + 'MTF Program (P-Shift)' => 'MTF優先プログラム(Pシフト)', + 'Macro' => 'マクロ', + 'Museum' => 'ミュージアム', + 'Night Scene' => '夜景', + 'Night Scene Portrait' => '人物ã€å¤œæ™¯', + 'No Flash' => 'フラッシュ無ã—', + 'Pet' => 'ペット', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Sport' => 'スãƒãƒ¼ãƒ„', + 'Standard' => 'スタンダード', + 'Sunset' => '夕日', + 'Surf & Snow' => 'サーフ&スノー', + 'Sv or Green Mode' => 'Svã‹ã‚°ãƒªãƒ¼ãƒ³ãƒ¢ãƒ¼ãƒ‰', + 'Text' => 'テキスト', + }, + }, + 'AESetting' => { + Description => '自動露出設定', + PrintConv => { + 'Exposure Compensation' => '露出補正', + }, + }, + 'AEXv' => 'AE 露出補正', + 'AE_ISO' => 'AE ISO感度', + 'AF-CPrioritySelection' => { + Description => 'AF-Cãƒ—ãƒ©ã‚¤ã‚ªãƒªãƒ†ã‚£ãƒ¼é¸æŠž', + PrintConv => { + 'Focus' => 'フォーカス', + 'Release' => 'レリーズ', + 'Release + Focus' => 'レリーズ+フォーカス', + }, + }, + 'AF-OnForMB-D10' => { + Description => 'MB-D10用AFオン', + PrintConv => { + 'AE Lock (hold)' => 'AEロック(ホールド)', + 'AE Lock (reset on release)' => 'AEロック(レリーズ時リセット)', + 'AE Lock Only' => 'AEロックã®ã¿', + 'AE/AF Lock' => 'AE/AFロック', + 'AF Lock Only' => 'AFロックã®ã¿', + 'AF-On' => 'AFオン', + 'Same as FUNC Button' => 'ファンクションボタンã¨åŒä¸€', + }, + }, + 'AF-SPrioritySelection' => { + Description => 'AF-Sãƒ—ãƒ©ã‚¤ã‚ªãƒªãƒ†ã‚£ãƒ¼é¸æŠž', + PrintConv => { + 'Focus' => 'フォーカス', + 'Release' => 'レリーズ', + }, + }, + 'AFActivation' => { + Description => 'AFアクティベーション', + PrintConv => { + 'AF-On Only' => 'AFオンã®ã¿', + 'Shutter/AF-On' => 'シャッター/AFオン', + }, + }, + 'AFAdjustment' => 'AF微調整', + 'AFArea' => 'AFエリア', + 'AFAreaHeight' => 'AFエリア高ã•', + 'AFAreaHeights' => 'AFエリア高ã•', + 'AFAreaIllumination' => { + Description => 'AFエリアイルミãƒãƒ¼ã‚·ãƒ§ãƒ³', + PrintConv => { + 'Auto' => 'オート', + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AFAreaMode' => { + Description => 'AFエリアモード', + PrintConv => { + '1-area' => '1点', + '1-area (high speed)' => '高速1点', + '3-area (center)?' => '3点中央?', + '3-area (left)?' => '3点左?', + '3-area (right)?' => '3点å³ï¼Ÿ', + '5-area' => '5点', + 'Auto-area' => '自動エリアAF', + 'Dynamic Area' => 'ダイナミックエリア', + 'Dynamic Area (closest subject)' => 'ダイナミック(é‡ç‚¹ä¸»é¡Œï¼‰', + 'Dynamic Area (wide)' => 'ダイナミック(ワイド)', + 'Face Detect AF' => '顔優先', + 'Group Dynamic' => 'グループダイナミック', + 'Normal?' => 'ノーマル?', + 'Single Area' => 'シングルãƒã‚¤ãƒ³ãƒˆ', + 'Single Area (wide)' => 'シングルãƒã‚¤ãƒ³ãƒˆï¼ˆãƒ¯ã‚¤ãƒ‰ï¼‰', + 'Spot Focusing' => 'スãƒãƒƒãƒˆãƒ•ォーカス', + 'Spot Mode On' => 'スãƒãƒƒãƒˆãƒ¢ãƒ¼ãƒ‰ã‚ªãƒ³', + }, + }, + 'AFAreaModeSetting' => { + Description => 'AFエリアモード', + PrintConv => { + 'Closest Subject' => 'オートエリア', + 'Dynamic Area' => 'ダイナミックエリア', + 'Single Area' => 'シングルãƒã‚¤ãƒ³ãƒˆ', + }, + }, + 'AFAreaWidth' => 'AFエリア幅', + 'AFAreaWidths' => 'AFエリア幅', + 'AFAreas' => 'AFエリア', + 'AFAssist' => { + Description => 'AFアシスト', + PrintConv => { + 'Does not emit/Fires' => 'ã—ãªã„/ã™ã‚‹', + 'Emits/Does not fire' => 'ã™ã‚‹/ã—ãªã„', + 'Emits/Fires' => 'ã™ã‚‹/ã™ã‚‹', + 'Off' => 'オフ', + 'On' => 'オン', + 'Only ext. flash emits/Fires' => '外部ストロボã®ã¿ã™ã‚‹/ã™ã‚‹', + }, + }, + 'AFAssistBeam' => { + Description => 'AFè£œåŠ©å…‰ã®æŠ•å…‰', + PrintConv => { + 'Does not emit' => 'ã—ãªã„', + 'Emits' => 'ã™ã‚‹', + 'Only ext. flash emits' => '外部ストロボã®ã¿ã™ã‚‹', + }, + }, + 'AFDefocus' => 'AFã¼ã‘é‡', + 'AFDuringLiveView' => { + Description => 'ライブビュー撮影中ã®AF', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + 'Live mode' => 'ライブモード', + 'Quick mode' => 'クイックモード', + }, + }, + 'AFFineTune' => 'AFファインãƒãƒ¥ãƒ¼ãƒ³', + 'AFFineTuneAdj' => 'AFファインãƒãƒ¥ãƒ¼ãƒ³', + 'AFImageHeight' => 'AFç”»åƒé«˜ã•', + 'AFImageWidth' => 'AFç”»åƒå¹…', + 'AFInfo' => 'AFモード', + 'AFInfo2' => 'AF情報', + 'AFInfo2Version' => 'AF情報ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'AFIntegrationTime' => 'AFé›†ç©æ™‚é–“', + 'AFMicroadjustment' => { + Description => 'AFマイクロアジャストメント', + PrintConv => { + 'Adjust all by same amount' => '全レンズ一律調整', + 'Adjust by lens' => 'レンズã”ã¨ã«èª¿æ•´', + 'Disable' => 'ã—ãªã„', + }, + }, + 'AFMode' => 'AFモード', + 'AFOnAELockButtonSwitch' => { + Description => 'AF-ON/AEロックボタン入替', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'AFPoint' => { + Description => 'AFé¸æŠžãƒã‚¤ãƒ³ãƒˆ', + PrintConv => { + 'Auto AF point selection' => 'オートAFãƒã‚¤ãƒ³ãƒˆé¸æŠž', + 'Bottom' => '下', + 'Center' => '中央', + 'Face Detect' => 'é¡”èªè­˜', + 'Left' => 'å·¦', + 'Manual AF point selection' => 'マニュアルAFãƒã‚¤ãƒ³ãƒˆé¸æŠž', + 'Mid-left' => '中央左', + 'Mid-right' => '中央å³', + 'None' => 'ç„¡ã—', + 'None (MF)' => 'ç„¡ã—(MF)', + 'Right' => 'å³', + 'Top' => '上', + }, + }, + 'AFPointActivationArea' => { + Description => 'AFフレームã®é ˜åŸŸæ‹¡å¤§', + PrintConv => { + 'Standard' => 'スタンダード', + }, + }, + 'AFPointAreaExpansion' => { + Description => 'ä»»æ„é¸æŠžæ™‚ã®AFフレーム領域拡大', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + 'Left/right AF points' => 'ã™ã‚‹(å·¦å³1領域アシスト有効)', + 'Surrounding AF points' => 'ã™ã‚‹(周囲1領域アシスト有効)', + }, + }, + 'AFPointAutoSelection' => { + Description => 'AFãƒ•ãƒ¬ãƒ¼ãƒ è‡ªå‹•é¸æŠžã®é¸æŠžå¯å¦', + PrintConv => { + 'Control-direct:disable/Main:disable' => 'サブ電å­ãƒ€ã‚¤ãƒ¤ãƒ«ç›´æŽ¥:ä¸å¯/メイン電å­ãƒ€ã‚¤ãƒ¤ãƒ«â†’ä¸å¯', + 'Control-direct:disable/Main:enable' => 'サブ電å­ãƒ€ã‚¤ãƒ¤ãƒ«ç›´æŽ¥:ä¸å¯/メイン電å­ãƒ€ã‚¤ãƒ¤ãƒ«â†’å¯', + 'Control-direct:enable/Main:enable' => 'サブ電å­ãƒ€ã‚¤ãƒ¤ãƒ«ç›´æŽ¥:å¯/メイン電å­ãƒ€ã‚¤ãƒ¤ãƒ«â†’å¯', + }, + }, + 'AFPointBrightness' => { + Description => 'AFフレーム点ç¯è¼åº¦', + PrintConv => { + 'Brighter' => '明るã„', + 'Normal' => '標準', + }, + }, + 'AFPointDisplayDuringFocus' => { + Description => 'æ¸¬è·æ™‚ã®AFフレーム表示', + PrintConv => { + 'Off' => 'ã—ãªã„', + 'On' => 'ã™ã‚‹', + 'On (when focus achieved)' => 'ã™ã‚‹(åˆç„¦æ™‚)', + }, + }, + 'AFPointIllumination' => { + Description => 'AFãƒã‚¤ãƒ³ãƒˆã‚¤ãƒ«ãƒŸãƒãƒ¼ã‚·ãƒ§ãƒ³', + PrintConv => { + 'Auto' => 'オート', + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AFPointMode' => { + Description => 'AF測è·ç‚¹ãƒ¢ãƒ¼ãƒ‰', + PrintConv => { + 'Auto' => 'オート', + }, + }, + 'AFPointRegistration' => { + Description => 'AFフレームã®ç™»éŒ²', + PrintConv => { + 'Automatic' => 'オート', + 'Bottom' => '下', + 'Center' => '中央', + 'Extreme Left' => '左端', + 'Extreme Right' => 'å³ç«¯', + 'Left' => 'å·¦', + 'Right' => 'å³', + 'Top' => '上', + }, + }, + 'AFPointSelected' => { + Description => 'AF測è·ç‚¹', + PrintConv => { + 'Auto' => 'オート', + 'Automatic Tracking AF' => '自動追尾', + 'Bottom' => '下', + 'Center' => '中央', + 'Face Detect AF' => 'é¡”èªè­˜', + 'Fixed Center' => '中央固定', + 'Left' => 'å·¦', + 'Lower-left' => '左下', + 'Lower-right' => 'å³ä¸‹', + 'Mid-left' => '中央左', + 'Mid-right' => '中央å³', + 'Right' => 'å³', + 'Top' => '上', + 'Upper-left' => '左上', + 'Upper-right' => 'å³ä¸Š', + }, + }, + 'AFPointSelected2' => { + Description => 'AF測è·ç‚¹é¸æŠž2', + PrintConv => { + 'Auto' => 'オート', + }, + }, + 'AFPointSelection' => { + Description => 'AFãƒã‚¤ãƒ³ãƒˆé¸æŠž', + PrintConv => { + '11 Points' => '11点', + '51 Points' => '51点(3Dトラッキング)', + }, + }, + 'AFPointSelectionMethod' => { + Description => 'AFãƒ•ãƒ¬ãƒ¼ãƒ é¸æŠžæ–¹æ³•', + PrintConv => { + 'Multi-controller direct' => 'マルãƒã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ©ãƒ¼ãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆ', + 'Normal' => '標準', + 'Quick Control Dial direct' => 'サブ電å­ãƒ€ã‚¤ãƒ¤ãƒ«ãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆ', + }, + }, + 'AFPointSpotMetering' => 'AFフレーム数ï¼ã‚¹ãƒãƒƒãƒˆæ¸¬å…‰', + 'AFPoints' => 'AFãƒã‚¤ãƒ³ãƒˆ', + 'AFPointsInFocus' => { + Description => 'AF測è·ç‚¹', + PrintConv => { + 'All' => 'å…¨ã¦', + 'Bottom' => '下', + 'Bottom, Center' => '下+中央', + 'Bottom-center' => '中央下', + 'Bottom-left' => '左下', + 'Bottom-right' => 'å³ä¸‹', + 'Center' => '中央', + 'Center (horizontal)' => '中央(水平)', + 'Center (vertical)' => '中央(水平)', + 'Center+Right' => '中央+å³', + 'Fixed Center or Multiple' => '中央固定ã¾ãŸã¯è¤‡æ•°', + 'Left' => 'å·¦', + 'Left+Center' => 'å·¦+中央', + 'Left+Right' => 'å·¦+å³', + 'Lower-left, Bottom' => '左下+下', + 'Lower-left, Mid-left' => '左下+中央左', + 'Lower-right, Bottom' => 'å³ä¸‹+下', + 'Lower-right, Mid-right' => 'å³ä¸‹+中央å³', + 'Mid-left' => '中央左', + 'Mid-left, Center' => '中央左+中央', + 'Mid-right' => '中央å³', + 'Mid-right, Center' => '中央å³+中央', + 'None' => 'ç„¡ã—', + 'None (MF)' => 'ç„¡ã—(MF)', + 'Right' => 'å³', + 'Top' => '上', + 'Top, Center' => '上+中央', + 'Top-center' => '中央上', + 'Top-left' => '左上', + 'Top-right' => 'å³ä¸Š', + 'Upper-left, Mid-left' => '左上+中央左', + 'Upper-left, Top' => '左上+上', + 'Upper-right, Mid-right' => 'å³ä¸Š+中央å³', + 'Upper-right, Top' => 'å³ä¸Š+上', + }, + }, + 'AFPointsInFocus1D' => 'AF測è·ç‚¹(1D)', + 'AFPointsInFocus5D' => 'AF測è·ç‚¹', + 'AFPointsSelected' => 'AFãƒã‚¤ãƒ³ãƒˆé¸æŠž', + 'AFPointsUnknown2' => { + Description => 'AF測è·ç‚¹ 未確èª2?', + PrintConv => { + 'Auto' => 'オート', + }, + }, + 'AFPointsUsed' => { + Description => 'AF測è·ç‚¹', + PrintConv => { + 'Bottom' => '下', + 'Center' => '中央', + 'Mid-left' => '中央左', + 'Mid-right' => '中央å³', + 'Top' => '上', + }, + }, + 'AFPredictor' => 'AF予測', + 'AFResponse' => 'AFレスãƒãƒ³ã‚¹', + 'AFResult' => 'AFçµæžœ', + 'AFSearch' => { + Description => 'AFサーãƒ', + PrintConv => { + 'Not Ready' => '準備ãŒã§ãã¦ã„ãªã„', + 'Ready' => '準備完了', + }, + }, + 'AIServoContinuousShooting' => 'AI SERVO 連続撮影・撮影速度優先', + 'AIServoImagePriority' => { + Description => 'AIサーボ1コマ目/2コマ目以é™å‹•作', + PrintConv => { + '1: AF, 2: Drive speed' => 'ピント優先/撮影速度優先', + '1: AF, 2: Tracking' => 'ピント優先/被写体追従優先', + '1: Release, 2: Drive speed' => 'レリーズ優先/撮影速度最優先', + }, + }, + 'AIServoTrackingMethod' => { + Description => 'AIã‚µãƒ¼ãƒœæ™‚ã®æ¸¬è·ç‚¹é¸æŠžç‰¹æ€§', + PrintConv => { + 'Continuous AF track priority' => '測è·é€£ç¶šæ€§å„ªå…ˆ', + 'Main focus point priority' => '測è·ä¸­å¿ƒå„ªå…ˆ', + }, + }, + 'AIServoTrackingSensitivity' => { + Description => 'AIサーボ時ã®è¢«å†™ä½“è¿½å¾“æ•æ„Ÿåº¦', + PrintConv => { + 'Fast' => '速ã„', + 'Slow' => 'é…ã„', + 'Standard' => 'スタンダード', + }, + }, + 'APEVersion' => 'APEãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'ARMIdentifier' => 'ARM識別å­', + 'ARMVersion' => 'ARMãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'AToB0' => 'Aã‹ã‚‰B0', + 'AToB1' => 'Aã‹ã‚‰B1', + 'AToB2' => 'Aã‹ã‚‰B2', + 'AccessoryType' => 'アクセサリータイプ', + 'ActionAdvised' => '動作推奨', + 'ActiveArea' => 'アクティブ領域', + 'ActiveD-Lighting' => { + Description => 'アクティブDライティング', + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + 'Normal' => '標準', + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ActiveD-LightingMode' => { + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + 'Normal' => '標準', + 'Off' => 'オフ', + }, + }, + 'AddAspectRatioInfo' => { + Description => 'アスペクト比情報ã®ä»˜åŠ ', + PrintConv => { + 'Off' => 'オフ', + }, + }, + 'AddOriginalDecisionData' => { + Description => 'オリジナル画åƒåˆ¤å®šç”¨ãƒ‡ãƒ¼ã‚¿ã®ä»˜åŠ ', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AdjustmentMode' => '調整モード', + 'AdultContentWarning' => { + PrintConv => { + 'Unknown' => '䏿˜Ž', + }, + }, + 'AdvancedRaw' => { + Description => 'アドãƒãƒ³ã‚¹RAW', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AdvancedSceneMode' => { + PrintConv => { + 'Auto' => 'インテリジェントオート', + }, + }, + 'Album' => 'アルãƒãƒ ', + 'AlphaByteCount' => 'アルファãƒã‚¤ãƒˆæ•°', + 'AlphaDataDiscard' => { + Description => 'アルファデータ破棄', + PrintConv => { + 'Flexbits Discarded' => 'フレックスビット破棄', + 'Full Resolution' => '完全ãªè§£åƒåº¦', + 'HighPass Frequency Data Discarded' => 'ãƒã‚¤ãƒ‘ス周波数データ破棄', + 'Highpass and LowPass Frequency Data Discarded' => 'ãƒã‚¤ãƒ‘スã¨ãƒ­ãƒ¼ãƒ‘ス周波数データ破棄', + }, + }, + 'AlphaOffset' => 'アルファオフセット', + 'AnalogBalance' => 'アナログãƒãƒ©ãƒ³ã‚¹', + 'Annotations' => 'フォトショップ注釈', + 'Anti-Blur' => { + PrintConv => { + 'Off' => 'オフ', + 'On (Continuous)' => 'オン(連写)', + 'On (Shooting)' => 'オン(撮影)', + 'n/a' => '該当無ã—', + }, + }, + 'AntiAliasStrength' => 'カメラã®ã‚¢ãƒ³ãƒã‚¨ã‚¤ãƒªã‚¢ã‚¹ãƒ•ィルタã®ç›¸å¯¾çš„ãªå¼·åº¦', + 'Aperture' => '絞り', + 'ApertureRange' => { + Description => '絞り数値ã®åˆ¶å¾¡ç¯„囲ã®è¨­å®š', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'ApertureRingUse' => { + Description => '絞りリングã®ä½¿ç”¨', + PrintConv => { + 'Permitted' => '許å¯', + 'Prohibited' => 'ç¦æ­¢', + }, + }, + 'ApertureValue' => '絞り', + 'ApplicationRecordVersion' => 'アプリケーションレコードãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'ApplyShootingMeteringMode' => { + Description => '撮影・測光モードã®å‘¼å‡º', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'Artist' => 'ç”»åƒä½œæˆè€…', + 'AsShotICCProfile' => '撮影時ICCプロファイル', + 'AsShotNeutral' => 'ニュートラルショット', + 'AsShotPreProfileMatrix' => '撮影時プロファイルマトリックス', + 'AsShotProfileName' => '撮影時プロフィールå', + 'AsShotWhiteXY' => 'ホワイトX-Yショット', + 'AssignFuncButton' => { + Description => 'FUNC.ãƒœã‚¿ãƒ³ã®æ©Ÿèƒ½', + PrintConv => { + 'Exposure comp./AEB setting' => '露出補正ï¼AEB設定', + 'Image jump with main dial' => 'メイン電å­ãƒ€ã‚¤ãƒ¤ãƒ« ã§ã®ç”»åƒé€ã‚Š', + 'Image quality' => 'è¨˜éŒ²ç”»è³ªé¸æŠž', + 'LCD brightness' => 'æ¶²æ™¶ã®æ˜Žã‚‹ã•', + 'Live view function settings' => 'ライブビュー機能設定', + }, + }, + 'AssistButtonFunction' => { + Description => 'ã‚¢ã‚·ã‚¹ãƒˆãƒœã‚¿ãƒ³ã®æ©Ÿèƒ½', + PrintConv => { + 'Av+/- (AF point by QCD)' => 'Av±(サブ電å­ï¼šAFãƒ•ãƒ¬ãƒ¼ãƒ é¸æŠžï¼‰', + 'FE lock' => 'FEロック', + 'Normal' => '標準', + 'Select HP (while pressing)' => 'HPã«ç§»å‹•(押ã—ã¦ã„る間)', + 'Select Home Position' => 'HPã«ç§»å‹•', + }, + }, + 'Audio' => { + Description => '音声', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'AudioDuration' => 'オーディオ時間', + 'AudioOutcue' => 'オーディオ終了åˆå›³', + 'AudioSamplingRate' => 'オーディオサンプリングレート', + 'AudioSamplingResolution' => 'オーディオサンプリング解åƒåº¦', + 'AudioType' => 'オーディオタイプ', + 'Author' => '作者', + 'AuthorsPosition' => 'å½¹è·å', + 'AutoAperture' => { + Description => '自動絞り', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AutoBracket' => 'オートブラケット', + 'AutoBracketModeM' => { + Description => 'オートブラケット(モードM)', + PrintConv => { + 'Flash Only' => 'フラッシュã®ã¿', + 'Flash/Aperture' => 'フラッシュ/絞り', + 'Flash/Speed' => 'フラッシュ/シャッター速度', + 'Flash/Speed/Aperture' => 'フラッシュ/シャッター速度/絞り', + }, + }, + 'AutoBracketOrder' => 'ブラケット順', + 'AutoBracketSet' => { + Description => 'オートブラケット設定', + PrintConv => { + 'AE & Flash' => '自動露出&フラッシュ', + 'AE Only' => '自動露出ã®ã¿', + 'Flash Only' => 'フラッシュã®ã¿', + 'WB Bracketing' => 'ホワイトãƒãƒ©ãƒ³ã‚¹ãƒ–ラケット', + }, + }, + 'AutoBracketing' => { + Description => 'オートブラケット', + PrintConv => { + 'No flash & flash' => 'フラッシュ無ã—&フラッシュ有り', + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AutoBracketingSet' => 'オートブラケット設定', + 'AutoExposureBracketing' => { + Description => 'フラッシュãƒã‚¤ã‚¢ã‚¹', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + 'On (shot 1)' => 'オン(ショット1)', + 'On (shot 2)' => 'オン(ショット2)', + 'On (shot 3)' => 'オン(ショット3)', + }, + }, + 'AutoFP' => { + Description => 'オートFP', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AutoFocus' => { + Description => 'オートフォーカス', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AutoISO' => { + Description => 'ISOオート', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AutoISOMax' => 'ISOオート 最大感度', + 'AutoISOMinShutterSpeed' => 'ISOオート 最å°ã‚·ãƒ£ãƒƒã‚¿ãƒ¼é€Ÿåº¦', + 'AutoLightingOptimizer' => { + Description => 'オートライティングオプティマイザ', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + 'Low' => 'ソフト', + 'Off' => 'オフ', + 'Standard' => 'スタンダード', + 'Strong' => 'å¼·', + 'n/a' => '該当無ã—', + }, + }, + 'AutoLightingOptimizerOn' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'AutoRedEye' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'AutoRotate' => { + Description => '自動回転', + PrintConv => { + 'None' => 'ç„¡ã—', + 'Rotate 180' => '180度回転', + 'Rotate 270 CW' => '270度回転 CW', + 'Rotate 90 CW' => '90度回転 CW', + 'n/a' => 'ソフトウェアã§å›žè»¢', + }, + }, + 'AuxiliaryLens' => '補助レンズ', + 'AvApertureSetting' => 'Av絞り設定', + 'AvSettingWithoutLens' => { + Description => 'ãƒ¬ãƒ³ã‚ºæœªè£…ç€æ™‚ã®çµžã‚Šæ•°å€¤è¨­å®š', + PrintConv => { + 'Disable' => 'ä¸å¯', + 'Enable' => 'å¯', + }, + }, + 'BToA0' => 'Bã‹ã‚‰A0', + 'BToA1' => 'Bã‹ã‚‰A1', + 'BToA2' => 'Bã‹ã‚‰A2', + 'BWFilter' => '白黒フィルター', + 'BWMode' => { + Description => '白黒モード', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'BabyAge' => '赤ã¡ã‚ƒã‚“/ペットã®å¹´é½¢', + 'BackgroundColorIndicator' => '背景色指標', + 'BackgroundColorValue' => '背景色値', + 'BackgroundTiling' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'BadFaxLines' => '粗悪ãªFAXç·š', + 'BannerImageType' => { + PrintConv => { + 'None' => 'ç„¡ã—', + }, + }, + 'BaseExposureCompensation' => '基本露出補正', + 'BaseISO' => 'ベース感度', + 'BaselineExposure' => 'ベースライン露出', + 'BaselineNoise' => 'ベースラインノイズ', + 'BaselineSharpness' => 'ベースラインシャープãƒã‚¹', + 'BatteryInfo' => 'é›»æº', + 'BatteryLevel' => 'ãƒãƒƒãƒ†ãƒªãƒ¼ãƒ¬ãƒ™ãƒ«', + 'BatteryOrder' => { + Description => 'ãƒãƒƒãƒ†ãƒªãƒ¼é †', + PrintConv => { + 'Camera Battery First' => 'カメラã®ãƒãƒƒãƒ†ãƒªãƒ¼ã‚’最åˆã«ä½¿ã†', + 'MB-D10 First' => 'MB-D10ã®ãƒãƒƒãƒ†ãƒªãƒ¼ã‚’最åˆã«ä½¿ã†', + }, + }, + 'BayerGreenSplit' => 'ベイヤーグリーン分割', + 'Beep' => { + Description => 'ビープ', + PrintConv => { + 'High' => '高ã„', + 'Low' => '低ã„', + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'BestQualityScale' => '高å“質スケール', + 'BestShotMode' => { + Description => 'ベストショットモード', + PrintConv => { + 'Off' => 'オフ', + }, + }, + 'BitDepth' => 'ビット深度', + 'BitsPerComponent' => 'コンãƒãƒ¼ãƒãƒ³ãƒˆã‚ãŸã‚Šãƒ“ット', + 'BitsPerExtendedRunLength' => '拡張ランレングスã‚ãŸã‚Šã®ãƒ“ット', + 'BitsPerRunLength' => 'ランレングスã‚ãŸã‚Šã®ãƒ“ット', + 'BitsPerSample' => 'コンãƒãƒ¼ãƒãƒ³ãƒˆã®ãƒ“ット数', + 'BlackLevel' => '黒レベル', + 'BlackLevel2' => '黒レベル2', + 'BlackLevelDeltaH' => '黒レベルデルタH', + 'BlackLevelDeltaV' => '黒レベルデルタV', + 'BlackLevelRepeatDim' => '黒レベルå復値', + 'BlackPoint' => '黒点', + 'BlueBalance' => 'ブルーãƒãƒ©ãƒ³ã‚¹', + 'BlueMatrixColumn' => 'é’色マトリックス列', + 'BlueTRC' => 'é’色調増殖曲線', + 'BlurWarning' => { + Description => '手振れ警告', + PrintConv => { + 'Blur Warning' => '手振れ警告', + 'None' => 'ç„¡ã—', + }, + }, + 'BodyBatteryADLoad' => 'é›»æºA/D本体起動時', + 'BodyBatteryADNoLoad' => 'é›»æºA/D本体オフ時', + 'BodyBatteryState' => 'é›»æºçŠ¶æ…‹æœ¬ä½“', + 'BodyFirmwareVersion' => '本体ファームウェアãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'BracketMode' => { + Description => 'ブラケットモード', + PrintConv => { + 'Off' => 'オフ', + }, + }, + 'BracketSequence' => 'ブラケットシーケンス', + 'BracketShotNumber' => { + Description => 'ブラケットショット数', + PrintConv => { + 'n/a' => '該当無ã—', + }, + }, + 'BracketStep' => { + Description => 'ブラケットステップ', + PrintConv => { + '1 EV' => '1ステップ', + '1/3 EV' => '1/3ステップ', + '2/3 EV' => '2/3ステップ', + }, + }, + 'BracketValue' => 'ブラケット値', + 'Brightness' => 'ブライトãƒã‚¹', + 'BrightnessData' => 'ブライトãƒã‚¹ãƒ‡ãƒ¼ã‚¿', + 'BrightnessValue' => 'ブライトãƒã‚¹', + 'BulbDuration' => 'ãƒãƒ«ãƒ–時間', + 'BurstMode' => { + Description => 'ブラストモード', + PrintConv => { + 'Auto Exposure Bracketing (AEB)' => 'ç„¡é™', + 'Infinite' => 'ç„¡é™', + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ButtonFunctionControlOff' => { + Description => 'サブ電å­ãƒ€ã‚¤ãƒ¤ãƒ«ã€ˆOFF〉時ã®ãƒœã‚¿ãƒ³æ“作', + PrintConv => { + 'Disable main, Control, Multi-control' => 'メイン電å­ãƒ€ã‚¤ãƒ¤ãƒ«ã€ã‚µãƒ–é›»å­ãƒ€ã‚¤ãƒ¤ãƒ«ã€ãƒžãƒ«ãƒã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ©ãƒ¼ã¯ç„¡åй', + 'Normal (enable)' => '通常(有効)', + }, + }, + 'By-line' => '製作者', + 'By-lineTitle' => '製作者ã®è·ç¨®', + 'CCDScanMode' => { + Description => 'CCDスキャンモード', + PrintConv => { + 'Interlaced' => 'インタレース', + 'Progressive' => 'プログレッシブ', + }, + }, + 'CFALayout' => 'CFAレイアウト', + 'CFAPattern' => 'CFAパターン', + 'CFAPattern2' => 'CFAパターン2', + 'CFAPlaneColor' => 'CFAプレーン色', + 'CFARepeatPatternDim' => 'CFAå復パターン特性', + 'CLModeShootingSpeed' => 'CLモード撮影速度', + 'CMContrast' => 'CMコントラスト', + 'CMExposureCompensation' => 'CM露出補正', + 'CMHue' => 'CM色相', + 'CMMFlags' => 'CMMフラグ', + 'CMSaturation' => 'CM彩度', + 'CMSharpness' => 'CMシャープãƒã‚¹', + 'CMWhiteBalance' => 'CMホワイトãƒãƒ©ãƒ³ã‚¹', + 'CMWhiteBalanceComp' => 'CMホワイトãƒãƒ©ãƒ³ã‚¹è£œæ­£', + 'CMWhiteBalanceGrayPoint' => 'CMホワイトãƒãƒ©ãƒ³ã‚¹ã‚°ãƒ¬ãƒ¼ãƒã‚¤ãƒ³ãƒˆ', + 'CMYKEquivalent' => 'CMYK等価物', + 'CPUFirmwareVersion' => 'CPUファームウエアãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'CPUType' => { + PrintConv => { + 'None' => 'ç„¡ã—', + }, + }, + 'CalibrationDateTime' => 'キャリブレーション日時', + 'CalibrationIlluminant1' => { + Description => 'å…‰æºã‚­ãƒ£ãƒªãƒ–レーション1', + PrintConv => { + 'Cloudy' => '曇り', + 'Cool White Fluorescent' => '白色è›å…‰ç¯', + 'Day White Fluorescent' => '昼白色è›å…‰ç¯', + 'Daylight' => '昼光', + 'Daylight Fluorescent' => '昼光色è›å…‰ç¯', + 'Fine Weather' => '良ã„天気', + 'Flash' => 'ストロボ', + 'Fluorescent' => 'è›å…‰ç¯', + 'ISO Studio Tungsten' => 'ISOスタジオタングステン', + 'Other' => 'ãã®ä»–ã®å…‰æº', + 'Shade' => '日陰', + 'Standard Light A' => '標準ライトA', + 'Standard Light B' => '標準ライトB', + 'Standard Light C' => '標準ライトC', + 'Tungsten (Incandescent)' => 'タングステン(白熱ç¯)', + 'Unknown' => '䏿˜Ž', + 'Warm White Fluorescent' => '暖白光色è›å…‰ç¯', + 'White Fluorescent' => '温白色è›å…‰ç¯', + }, + }, + 'CalibrationIlluminant2' => { + Description => 'å…‰æºã‚­ãƒ£ãƒªãƒ–レーション2', + PrintConv => { + 'Cloudy' => '曇り', + 'Cool White Fluorescent' => '白色è›å…‰ç¯', + 'Day White Fluorescent' => '昼白色è›å…‰ç¯', + 'Daylight' => '昼光', + 'Daylight Fluorescent' => '昼光色è›å…‰ç¯', + 'Fine Weather' => '良ã„天気', + 'Flash' => 'ストロボ', + 'Fluorescent' => 'è›å…‰ç¯', + 'ISO Studio Tungsten' => 'ISOスタジオタングステン', + 'Other' => 'ãã®ä»–ã®å…‰æº', + 'Shade' => '日陰', + 'Standard Light A' => '標準ライトA', + 'Standard Light B' => '標準ライトB', + 'Standard Light C' => '標準ライトC', + 'Tungsten (Incandescent)' => 'タングステン(白熱ç¯)', + 'Unknown' => '䏿˜Ž', + 'Warm White Fluorescent' => '暖白光色è›å…‰ç¯', + 'White Fluorescent' => '温白色è›å…‰ç¯', + }, + }, + 'CameraCalibration1' => 'カメラキャリブレーション1', + 'CameraCalibration2' => 'カメラキャリブレーション2', + 'CameraCalibrationSig' => 'カメラキャリブレーションサイン', + 'CameraID' => 'カメラID', + 'CameraISO' => 'カメラISO', + 'CameraInfo' => 'ペンタックスモデル', + 'CameraOrientation' => { + Description => 'ç”»åƒã®å‘ã', + PrintConv => { + 'Horizontal (normal)' => '水平(標準)', + 'Rotate 270 CW' => '270度回転 CW', + 'Rotate 90 CW' => '90度回転 CW', + }, + }, + 'CameraParameters' => 'カメラパラメーター', + 'CameraSerialNumber' => 'カメラシリアル番å·', + 'CameraSettings' => 'カメラ設定', + 'CameraSettingsVersion' => 'カメラ設定ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'CameraTemperature' => 'カメラ温度', + 'CameraType' => { + Description => 'カメラタイプ', + PrintConv => { + 'Compact' => 'コンパクト', + 'DV Camera' => 'DVカメラ', + 'EOS High-end' => 'EOSãƒã‚¤ã‚¨ãƒ³ãƒ‰', + 'EOS Mid-range' => 'EOSミドルレンジ', + }, + }, + 'CameraType2' => 'カメラタイプ2', + 'CanonAFInfo' => 'AF情報', + 'CanonAFInfo2' => 'AF情報2', + 'CanonExposureMode' => { + Description => '露出モード', + PrintConv => { + 'Aperture-priority AE' => '絞り優先', + 'Bulb' => 'ãƒãƒ«ãƒ–', + 'Depth-of-field AE' => '被写界深度AE', + 'Easy' => 'ç°¡å˜', + 'Manual' => 'マニュアル', + 'Program AE' => 'プログラムAE', + 'Shutter speed priority AE' => 'シャッター優先', + }, + }, + 'CanonFileInfo' => 'ファイル情報', + 'CanonFileLength' => 'ファイル長', + 'CanonFirmwareVersion' => 'ファームウェアãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'CanonFlashMode' => { + Description => 'フラッシュモード', + PrintConv => { + 'Auto' => 'オート', + 'External flash' => '外付フラッシュ', + 'Off' => 'オフ', + 'On' => 'オン', + 'Red-eye reduction' => '赤目軽減', + 'Red-eye reduction (Auto)' => '赤目軽減(オート)', + 'Red-eye reduction (On)' => '赤目軽減(オン)', + 'Slow-sync' => 'スローシンクロ', + }, + }, + 'CanonFocalLength' => 'フォーカスタイプ', + 'CanonImageHeight' => 'ç”»åƒé«˜ã•', + 'CanonImageSize' => { + Description => 'イメージサイズ', + PrintConv => { + 'Large' => 'ラージ', + 'Medium' => 'ミドル', + 'Medium 1' => 'ミドル1', + 'Medium 2' => 'ミドル2', + 'Medium 3' => 'ミドル3', + 'Medium Movie' => 'ミディアム動画', + 'Postcard' => 'ãƒã‚¬ã‚­', + 'Small' => 'スモール', + 'Small 1' => 'スモール1', + 'Small 2' => 'スモール2', + 'Small 3' => 'スモール3', + 'Small Movie' => 'スモール動画', + 'Widescreen' => 'ワイド画é¢', + }, + }, + 'CanonImageType' => 'イメージタイプ', + 'CanonImageWidth' => 'ç”»åƒå¹…', + 'CanonModelID' => 'モデルID', + 'Caption-Abstract' => '表題/説明', + 'CaptionWriter' => 'キャプション作æˆè€…', + 'CasioImageSize' => 'カシオイメージサイズ', + 'Categories' => 'カテゴリー', + 'Category' => 'カテゴリー', + 'CellLength' => 'セル長', + 'CellWidth' => 'セル幅', + 'CenterAFArea' => { + Description => '中央AFエリア', + PrintConv => { + 'Normal Zone' => 'ノーマルゾーン', + 'Wide Zone' => 'ワイドゾーン', + }, + }, + 'CenterWeightedAreaSize' => { + Description => '中央é‡ç‚¹ã‚¨ãƒªã‚¢', + PrintConv => { + 'Average' => 'å¹³å‡', + }, + }, + 'CharTarget' => '目的文字', + 'CharacterSet' => 'キャラクターセット', + 'ChromaBlurRadius' => '彩度ã¼ã‘åŠå¾„', + 'ChromaticAdaptation' => '色彩順応化', + 'Chromaticity' => '色度', + 'ChrominanceNR_TIFF_JPEG' => { + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + 'Off' => 'オフ', + }, + }, + 'ChrominanceNoiseReduction' => { + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + 'Off' => 'オフ', + }, + }, + 'City' => '都市', + 'ClassifyState' => '分類状態', + 'CleanFaxData' => '純粋ãªFAXデータ', + 'ClipPath' => 'クリップパス', + 'CodedCharacterSet' => 'キャラクタセットコード', + 'ColorAberrationControl' => { + Description => '色åŽå·®ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ColorAdjustment' => '色調整', + 'ColorAdjustmentMode' => { + Description => '色調整モード', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ColorBalance' => 'カラーãƒãƒ©ãƒ³ã‚¹', + 'ColorBalanceAdj' => { + Description => 'カラーãƒãƒ©ãƒ³ã‚¹èª¿æ•´', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ColorBalanceBlue' => 'カラーãƒãƒ©ãƒ³ã‚¹é’', + 'ColorBalanceGreen' => 'カラーãƒãƒ©ãƒ³ã‚¹ç·‘', + 'ColorBalanceRed' => 'カラーãƒãƒ©ãƒ³ã‚¹èµ¤', + 'ColorBoostData' => 'カラーブーストデータ', + 'ColorBoostLevel' => 'カラーブーストレベル1', + 'ColorBoostType' => { + Description => 'カラーブーストタイプ', + PrintConv => { + 'Nature' => '自然', + 'People' => '人々', + }, + }, + 'ColorBooster' => { + Description => 'カラーブースター', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ColorCalibrationMatrix' => 'カラーキャリブレーションマトリックステーブル', + 'ColorCharacterization' => 'カラー特徴æå†™', + 'ColorControl' => 'カラーコントロール', + 'ColorEffect' => { + Description => 'カラーエフェクト', + PrintConv => { + 'Black & White' => '白黒', + 'Cool' => '冷色', + 'Off' => 'オフ', + 'Sepia' => 'セピア', + 'Warm' => '暖色', + }, + }, + 'ColorFilter' => { + Description => 'カラーフィルター', + PrintConv => { + 'Black & White' => '白黒', + 'Blue' => 'é’', + 'Green' => 'ç·‘', + 'Off' => 'オフ', + 'Pink' => 'ピンク', + 'Purple' => 'ç´«', + 'Red' => '赤', + 'Sepia' => 'セピア', + 'Yellow' => '黄色', + }, + }, + 'ColorGain' => 'カラーゲイン', + 'ColorHue' => '色相', + 'ColorInfo' => '色情報', + 'ColorMap' => 'カラーマップ', + 'ColorMatrix' => 'カラーマトリックス', + 'ColorMatrix1' => 'カラーマトリックス1', + 'ColorMatrix2' => 'カラーマトリックス2', + 'ColorMatrixNumber' => 'カラーマトリックス番å·', + 'ColorMode' => { + Description => 'カラーモード', + PrintConv => { + 'Autumn Leaves' => '紅葉', + 'B & W' => '白黒', + 'B&W' => '白黒', + 'Black & White' => '白黒', + 'Chrome' => 'クローム', + 'Clear' => 'クリアー', + 'Deep' => 'ディープ', + 'Evening' => '夕焼ã‘', + 'Landscape' => '風景', + 'Light' => 'ライト', + 'Natural' => 'ナãƒãƒ¥ãƒ©ãƒ«', + 'Natural color' => 'ナãƒãƒ¥ãƒ©ãƒ«ã‚«ãƒ©ãƒ¼', + 'Natural sRGB' => 'ナãƒãƒ¥ãƒ©ãƒ« sRGB', + 'Natural+ sRGB' => 'ナãƒãƒ¥ãƒ©ãƒ«+ sRGB', + 'Neutral' => 'ニュートラル', + 'Night Portrait' => '人物夜景', + 'Night Scene' => '夜景', + 'Night View' => 'ナイトビュー', + 'Night View/Portrait' => '夜景/夜景ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Normal' => 'ノーマル', + 'Off' => 'オフ', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Sepia' => 'セピア', + 'Solarization' => 'ソラリゼーション', + 'Standard' => 'スタンダード', + 'Sunset' => '夕日', + 'Vivid' => 'ビビッド', + 'Vivid color' => 'ビビッドカラー', + }, + }, + 'ColorMoireReduction' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ColorMoireReductionMode' => { + Description => '色モアレリダクション', + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + 'Off' => 'オフ', + }, + }, + 'ColorPalette' => 'カラーパレット', + 'ColorProfile' => { + Description => 'カラープロフィール', + PrintConv => { + 'Embedded' => '埋ã‚è¾¼ã¿', + 'Not Embedded' => '埋ã‚è¾¼ã¿ç„¡ã—', + }, + }, + 'ColorRepresentation' => '色表ç¾', + 'ColorReproduction' => '色å†ç¾', + 'ColorResponseUnit' => '色応答å˜ä½', + 'ColorSequence' => 'カラーシーケンス', + 'ColorSpace' => { + Description => '色空間', + PrintConv => { + 'ICC Profile' => 'ICCプロフィール', + 'Monochrome' => 'モノトーン', + 'Uncalibrated' => '未調整', + }, + }, + 'ColorSpaceData' => 'カラースペースデータ', + 'ColorTable' => 'カラーテーブル', + 'ColorTemperature' => '色温度', + 'ColorTone' => { + Description => 'カラートーン', + PrintConv => { + 'Normal' => '標準', + }, + }, + 'ColorToneFaithful' => 'カラートーン忠実設定', + 'ColorToneLandscape' => 'カラートーン風景', + 'ColorToneNeutral' => 'カラートーンニュートラル', + 'ColorTonePortrait' => 'カラートーンãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'ColorToneStandard' => 'カラートーンスタンダード', + 'ColorToneUserDef1' => 'カラートーンユーザ設定1', + 'ColorToneUserDef2' => 'カラートーンユーザ設定2', + 'ColorToneUserDef3' => 'カラートーンユーザ設定3', + 'ColorantOrder' => 'ç€è‰²é †', + 'ColorantTable' => 'ç€è‰²ãƒ†ãƒ¼ãƒ–ル', + 'ColorimetricReference' => '比色分æžå‚ç…§', + 'CommandDials' => { + Description => 'コマンダーダイヤル', + PrintConv => { + 'Reversed (Main Aperture, Sub Shutter)' => 'リãƒãƒ¼ã‚¹', + 'Standard (Main Shutter, Sub Aperture)' => 'デフォルト', + }, + }, + 'CommandDialsApertureSetting' => { + Description => 'コマンドダイヤルカスタマイズ 絞り設定', + PrintConv => { + 'Aperture Ring' => '絞りリング', + 'Sub-command Dial' => 'サブコントロールダイヤル', + }, + }, + 'CommandDialsChangeMainSub' => { + Description => 'コマンドダイヤルカスタマイズ メイン/サブ変更', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'CommandDialsMenuAndPlayback' => { + Description => 'コマンドダイヤルカスタマイズ メニューã¨å†ç”Ÿ', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'CommandDialsReverseRotation' => { + Description => 'コマンドダイヤルカスタマイズ å›žè»¢ä¿æŒ', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'CommanderChannel' => 'コマンダーモード ãƒãƒ£ãƒ³ãƒãƒ«', + 'CommanderGroupAManualOutput' => 'コマンダーモード グループA M 補正', + 'CommanderGroupAMode' => { + Description => 'コマンダーモード グループA モード', + PrintConv => { + 'Auto Aperture' => '自動絞り(AA)', + 'Manual' => 'マニュアル', + 'Off' => 'オフ', + }, + }, + 'CommanderGroupA_TTL-AAComp' => 'コマンダーモード 内蔵フラッシュ TTL/AA 補正', + 'CommanderGroupBManualOutput' => 'コマンダーモード グループB M 補正', + 'CommanderGroupBMode' => { + Description => 'コマンダーモード グループB モード', + PrintConv => { + 'Auto Aperture' => '自動絞り(AA)', + 'Manual' => 'マニュアル', + 'Off' => 'オフ', + }, + }, + 'CommanderGroupB_TTL-AAComp' => 'コマンダーモード グループB TTL/AA 補正', + 'CommanderInternalFlash' => { + Description => 'コマンダーモード 内蔵フラッシュ モード', + PrintConv => { + 'Manual' => 'マニュアル', + 'Off' => 'オフ', + }, + }, + 'CommanderInternalManualOutput' => 'コマンダーモード 内蔵フラッシュ M 補正', + 'CommanderInternalTTLComp' => 'コマンダーモード 内蔵フラッシュ TTL 補正', + 'Comment' => 'コメント', + 'Compilation' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'ComponentsConfiguration' => '儿§‹æˆè¦ç´ ã®æ„味', + 'CompressedBitsPerPixel' => 'ç”»åƒåœ§ç¸®ãƒ¢ãƒ¼ãƒ‰', + 'CompressedImageSize' => '圧縮画åƒã‚µã‚¤ã‚º', + 'Compression' => { + Description => '圧縮計画', + PrintConv => { + 'JPEG' => 'JPEG圧縮率', + 'JPEG (old-style)' => 'JPEG (å¤ã„å½¢å¼)', + 'None' => 'ç„¡ã—', + 'Uncompressed' => 'éžåœ§ç¸®', + }, + }, + 'CompressionRatio' => '圧縮率', + 'CompressionType' => { + PrintConv => { + 'None' => 'ç„¡ã—', + }, + }, + 'ConditionalFEC' => 'フラッシュ露出補正', + 'ConnectionSpaceIlluminant' => '接続スペース光æº', + 'ConsecutiveBadFaxLines' => '連続的ã«ç²—悪ãªFAXç·š', + 'Contact' => '連絡', + 'ContentLocationCode' => '内容ä½ç½®ã‚³ãƒ¼ãƒ‰', + 'ContentLocationName' => '内容ä½ç½®å', + 'ContinuousDrive' => { + Description => 'ドライブモード', + PrintConv => { + 'Continuous' => '連続撮影', + 'Continuous, High' => '連写(High)', + 'Continuous, Low' => '連写(Low)', + 'Continuous, Speed Priority' => '高速連写', + 'Movie' => 'å‹•ç”»', + 'Single' => '1コマ撮影', + }, + }, + 'ContinuousShootingSpeed' => { + Description => '連続撮影速度', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'ContinuousShotLimit' => { + Description => 'é€£ç¶šæ’®å½±æ™‚ã®æ’®å½±æžšæ•°åˆ¶é™', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'Contrast' => { + Description => 'コントラスト', + PrintConv => { + '+1 (medium high)' => '+1 (å°‘ã—高ã„)', + '+2 (high)' => '+2 (ãƒãƒ¼ãƒ‰)', + '+3 (very high)' => '+3 (ã‹ãªã‚Šé«˜ã„)', + '-1 (medium low)' => '-1 (å°‘ã—低ã„)', + '-2 (low)' => '-2 (ソフト)', + '-3 (very low)' => '-3 (ã‹ãªã‚Šä½Žã„)', + '0 (normal)' => '0 (スタンダード)', + 'Film Simulation' => 'フィルムシミュレーション', + 'High' => 'ãƒãƒ¼ãƒ‰', + 'Low' => 'ソフト', + 'Medium High' => 'å°‘ã—高ã„', + 'Medium Low' => 'å°‘ã—低ã„', + 'Normal' => 'スタンダード', + }, + }, + 'ContrastCurve' => 'コントラストカーブ', + 'ContrastFaithful' => 'コントラスト忠実設定', + 'ContrastLandscape' => 'コントラスト風景', + 'ContrastMonochrome' => 'コントラストモノクロ', + 'ContrastNeutral' => 'コントラストニュートラル', + 'ContrastPortrait' => 'コントラストãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'ContrastSetting' => 'コントラスト設定', + 'ContrastStandard' => 'コントラストスタンダード', + 'ContrastUserDef1' => 'コントラストユーザ設定1', + 'ContrastUserDef2' => 'コントラストユーザ設定2', + 'ContrastUserDef3' => 'コントラストユーザ設定3', + 'ControlMode' => { + Description => 'コントロールモード', + PrintConv => { + 'Camera Local Control' => 'カメラローカルコントロール', + 'Computer Remote Control' => 'コンピュータリモートコントロール', + 'n/a' => '該当無ã—', + }, + }, + 'ConversionLens' => { + Description => 'コンãƒãƒ¼ã‚¸ãƒ§ãƒ³ãƒ¬ãƒ³ã‚º', + PrintConv => { + 'Macro' => 'マクロ', + 'Off' => 'オフ', + 'Telephoto' => 'テレフォト', + 'Wide' => 'ワイド', + }, + }, + 'Converter' => 'コンãƒãƒ¼ã‚¿ãƒ¼', + 'Copyright' => '版権所有者', + 'CopyrightNotice' => '著作権表示', + 'CopyrightStatus' => { + PrintConv => { + 'Unknown' => '䏿˜Ž', + }, + }, + 'CoringFilter' => 'コアリングフィルタ', + 'CoringValues' => 'コアリング値', + 'Country' => '国å', + 'Country-PrimaryLocationCode' => 'ISO国コード', + 'Country-PrimaryLocationName' => '国', + 'CountryCode' => '撮影国コード', + 'CreateDate' => 'ãƒ‡ã‚¸ã‚¿ãƒ«ãƒ‡ãƒ¼ã‚¿ä½œæˆæ—¥æ™‚', + 'CreationDate' => 'ä½œæˆæ—¥æ™‚', + 'CreativeStyle' => { + Description => 'クリエイティブスタイル', + PrintConv => { + 'Autumn' => 'ç§‹', + 'Autumn Leaves' => '紅葉', + 'B&W' => '白黒', + 'Clear' => 'クリアー', + 'Deep' => 'ディープ', + 'Landscape' => '風景', + 'Light' => 'ライト', + 'Neutral' => 'ニュートラル', + 'Night View/Portrait' => '夜景/夜景ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Sepia' => 'セピア', + 'Standard' => 'スタンダード', + 'Sunset' => '夕日', + 'Vivid' => 'ビビッド', + }, + }, + 'Creator' => '製作者', + 'CreatorAddress' => 'クリエーター -ã€€ä½æ‰€', + 'CreatorCity' => 'クリエーター - 街', + 'CreatorContactInfo' => '作æˆè€…ã®ã‚³ãƒ³ タクト先', + 'CreatorCountry' => 'クリエーター - 国', + 'CreatorPostalCode' => 'クリエーター - 郵便番å·', + 'CreatorRegion' => 'クリエーター - 国/å·ž', + 'CreatorWorkEmail' => 'クリエーター - 電å­ãƒ¡ãƒ¼ãƒ«', + 'CreatorWorkTelephone' => 'クリエーター - 電話番å·', + 'CreatorWorkURL' => 'クリエーター - WEBサイト', + 'Credit' => 'プロãƒã‚¤ãƒ€ãƒ¼', + 'CropActive' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'CropData' => 'クロップデータ', + 'CropHeight' => '最終高ã•', + 'CropHiSpeed' => 'ãƒã‚¤ã‚¹ãƒ”ードクロップ', + 'CropLeft' => '開始オフセットX', + 'CropTop' => '開始オフセットY', + 'CropWidth' => '最終幅', + 'CurrentICCProfile' => 'カレントICCプロファイル', + 'CurrentPreProfileMatrix' => 'カレントプロファイルマトリックス', + 'Curves' => { + Description => 'カーブ', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Custom1' => 'カスタム1', + 'Custom2' => 'カスタム2', + 'Custom3' => 'カスタム3', + 'Custom4' => 'カスタム4', + 'CustomRendered' => { + Description => 'カスタム画åƒå‡¦ç†', + PrintConv => { + 'Custom' => 'カスタム処ç†', + 'Normal' => '標準処ç†', + }, + }, + 'CustomSaturation' => 'カスタム彩度', + 'D-LightingHQ' => { + Description => 'DライティングHQ', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'D-LightingHQColorBoost' => 'DライティングHQカラーブースト', + 'D-LightingHQHighlight' => 'DライティングHQãƒã‚¤ãƒ©ã‚¤ãƒˆ', + 'D-LightingHQSelected' => { + Description => 'DライティングHQé¸æŠž', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'D-LightingHQShadow' => 'DライティングHQシャドウ', + 'D-LightingHS' => { + Description => 'DライティングHS', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'D-LightingHSAdjustment' => 'DライティングHS調整', + 'D-LightingHSColorBoost' => 'DライティングHSカラーブースト', + 'DECPosition' => { + Description => 'DECä½ç½®', + PrintConv => { + 'Contrast' => 'コントラスト', + 'Exposure' => '露出', + 'Filter' => 'フィルター', + 'Saturation' => '彩度', + }, + }, + 'DNGBackwardVersion' => 'DNGãƒãƒƒã‚¯ãƒ¯ãƒ¼ãƒ‰ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'DNGLensInfo' => 'レンズ情報', + 'DNGVersion' => 'DNGãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'DSPFirmwareVersion' => 'DSPファームウエアãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'DataCompressionMethod' => 'データ圧縮アルゴリズム プロãƒã‚¤ãƒ€ãƒ¼/オーナー', + 'DataDump' => 'データダンプ', + 'DataImprint' => { + Description => 'データインプリント', + PrintConv => { + 'None' => 'ç„¡ã—', + 'Text' => 'テキスト', + }, + }, + 'DataType' => '日付型', + 'Date' => '日付', + 'DateCreated' => 'ä½œæˆæ—¥ä»˜', + 'DateDisplayFormat' => { + Description => '日付形å¼', + PrintConv => { + 'D/M/Y' => 'æ—¥/月/å¹´', + 'M/D/Y' => '月/æ—¥/å¹´', + 'Y/M/D' => 'å¹´/月/æ—¥', + }, + }, + 'DateSent' => 'ç™ºé€æ—¥ä»˜', + 'DateStampMode' => { + Description => '日付スタンプモード', + PrintConv => { + 'Date' => '日付', + 'Off' => 'オフ', + }, + }, + 'DateTime' => 'ãƒ•ã‚¡ã‚¤ãƒ«ä½œæˆæ—¥æ™‚', + 'DateTimeOriginal' => 'ã‚ªãƒªã‚¸ãƒŠãƒ«ãƒ‡ãƒ¼ã‚¿ä½œæˆæ—¥æ™‚', + 'DaylightSavings' => { + Description => '夿™‚é–“', + PrintConv => { + 'No' => 'オフ', + 'Yes' => 'オン', + }, + }, + 'DefaultCropOrigin' => 'デフォルト切å–り基点', + 'DefaultCropSize' => 'デフォルト切å–りサイズ', + 'DefaultScale' => 'デフォルトスケール', + 'DeletedImageCount' => '削除イメージカウント', + 'Description' => '説明', + 'Destination' => '宛先', + 'DestinationCity' => '目的地', + 'DestinationCityCode' => '目的地コード', + 'DestinationDST' => { + Description => '目的地DST', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'DevelopmentDynamicRange' => '進化ダイナミックレンジ', + 'DeviceAttributes' => '機器属性', + 'DeviceManufacturer' => '機器メーカー', + 'DeviceMfgDesc' => '機器メーカー説明', + 'DeviceModel' => '機器モデル', + 'DeviceModelDesc' => '機器モデル説明', + 'DeviceSettingDescription' => 'デãƒã‚¤ã‚¹è¨­å®šã®èª¬æ˜Ž', + 'DialDirectionTvAv' => { + Description => 'Tv/Av値設定時ã®ãƒ€ã‚¤ãƒ¤ãƒ«å›žè»¢', + PrintConv => { + 'Normal' => '通常', + 'Reversed' => '設定方å‘ã‚’å転', + }, + }, + 'DigitalCreationDate' => 'ãƒ‡ã‚¸ã‚¿ãƒ«ä½œæˆæ—¥ä»˜', + 'DigitalCreationTime' => 'ãƒ‡ã‚¸ã‚¿ãƒ«ä½œæˆæ™‚é–“', + 'DigitalGEM' => 'デジタルGEM', + 'DigitalICE' => 'デジタルICE', + 'DigitalROC' => 'デジタルROC', + 'DigitalZoom' => { + Description => 'デジタルズーム', + PrintConv => { + 'None' => 'ç„¡ã—', + 'Off' => 'オフ', + 'Other' => '未確èª', + }, + }, + 'DigitalZoomOn' => { + Description => 'デジタルズームオン', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'DigitalZoomRatio' => 'デジタルズーム比率', + 'Directory' => 'ファイルã®å ´æ‰€', + 'DirectoryIndex' => 'ディレクトリ索引', + 'DirectoryNumber' => 'ディレクトリ番å·', + 'DisplayAperture' => '絞り表示', + 'DisplaySize' => { + PrintConv => { + 'Normal' => '標準', + }, + }, + 'DistortionCorrection' => { + Description => '歪曲修正', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'DistortionCorrection2' => { + Description => '歪曲補正編集', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'DjVuVersion' => 'DjVuãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'DocumentHistory' => '文書履歴', + 'DocumentName' => 'ドキュメントå', + 'DocumentNotes' => '文書ノート', + 'DotRange' => 'ドット範囲', + 'DriveMode' => { + Description => 'ドライブモード', + PrintConv => { + 'Bracketing' => 'ブラケット', + 'Burst' => '高速連射', + 'Continuous' => '連続撮影', + 'Continuous High' => '連射 (Hi)', + 'Continuous Shooting' => '連続撮影', + 'HS continuous' => 'HS連写', + 'Interval' => 'インターãƒãƒ«', + 'Multiple Exposure' => '複数ã®éœ²å‡º', + 'No Timer' => 'タイマー無ã—', + 'Off' => 'オフ', + 'Remote Control' => 'リモコン', + 'Remote Control (3 s delay)' => 'リモコン (3秒後レリーズ)', + 'Self-timer' => 'セルフタイマー', + 'Self-timer (12 s)' => 'セルフタイマー (12ç§’)', + 'Self-timer (2 s)' => 'セルフタイマー (2ç§’)', + 'Self-timer Operation' => 'セルフタイマー', + 'Shutter Button' => 'シャッターボタン', + 'Single' => '1コマ撮影', + 'Single Exposure' => 'シングル露出', + 'Single Frame' => '1コマ撮影', + 'Single Shot' => '1コマ撮影', + 'Single-frame' => '1コマ撮影', + 'Single-frame Shooting' => '1コマ撮影', + 'UHS continuous' => 'UHS連写', + }, + }, + 'DriveMode2' => { + Description => '多é‡éœ²å…‰', + PrintConv => { + 'Single-frame' => '1コマ撮影', + }, + }, + 'DynamicAFArea' => { + Description => 'ダイナミックAFエリア', + PrintConv => { + '21 Points' => '21点', + '51 Points' => '51点', + '51 Points (3D-tracking)' => '51点(3Dトラッキング)', + '9 Points' => '9点', + }, + }, + 'DynamicRange' => { + Description => 'ダイナミックレンジ', + PrintConv => { + 'Standard' => 'スタンダード', + 'Wide' => 'ワイド', + }, + }, + 'DynamicRangeExpansion' => { + Description => 'ダイナミックレンジ拡大', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'DynamicRangeOptimizer' => { + Description => 'Dレンジオプティマイザー', + PrintConv => { + 'Advanced Auto' => 'アドãƒãƒ³ã‚¹ã‚ªãƒ¼ãƒˆ', + 'Advanced Lv1' => 'アドãƒãƒ³ã‚¹Lv1', + 'Advanced Lv2' => 'アドãƒãƒ³ã‚¹Lv2', + 'Advanced Lv3' => 'アドãƒãƒ³ã‚¹Lv3', + 'Advanced Lv4' => 'アドãƒãƒ³ã‚¹Lv4', + 'Advanced Lv5' => 'アドãƒãƒ³ã‚¹Lv5', + 'Auto' => 'オート', + 'Off' => 'オフ', + 'Standard' => 'スタンダード', + }, + }, + 'DynamicRangeSetting' => 'ダイナミックレンジ設定', + 'E-DialInProgram' => { + Description => 'é›»å­ãƒ€ã‚¤ãƒ¤ãƒ«ãƒ—ログラム', + PrintConv => { + 'P Shift' => 'Pシフト', + 'Tv or Av' => 'Tvã‹Av', + }, + }, + 'ETTLII' => { + PrintConv => { + 'Average' => 'å¹³å‡', + 'Evaluative' => '評価', + }, + }, + 'EVStepInfo' => 'EVステップ情報', + 'EVStepSize' => { + Description => 'EVステップ', + PrintConv => { + '1/2 EV' => '1/2ステップ', + '1/3 EV' => '1/3ステップ', + }, + }, + 'EVSteps' => { + Description => '露出ステップ', + PrintConv => { + '1/2 EV Steps' => '1/2 EVステップ', + '1/3 EV Steps' => '1/3 EVステップ', + }, + }, + 'EasyExposureCompensation' => { + Description => 'ç°¡å˜ãªéœ²å‡ºè£œæ­£', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + 'On (auto reset)' => 'オン(自動リセット)', + }, + }, + 'EasyMode' => { + Description => 'ç°¡å˜ãƒ¢ãƒ¼ãƒ‰', + PrintConv => { + 'Aquarium' => 'æ°´æ—館', + 'Beach' => 'ビーãƒ', + 'Black & White' => '白黒', + 'Color Accent' => 'カラーアクセント', + 'Color Swap' => 'スイッãƒã‚«ãƒ©ãƒ¼', + 'Digital Macro' => 'デジタルマクロ', + 'Fast shutter' => '高速シャッター', + 'Fireworks' => '花ç«', + 'Flash Off' => 'フラッシュオフ', + 'Foliage' => '葉', + 'Full auto' => 'フルオート', + 'Gray Scale' => 'グレースケール', + 'ISO 3200' => 'ISO3200', + 'Indoor' => '室内', + 'Kids & Pets' => 'キッズ&ペット', + 'Landscape' => '風景', + 'Long Shutter' => '長秒シャッター', + 'Macro' => 'マクロ', + 'Manual' => 'マニュアル', + 'My Colors' => 'ワンãƒã‚¤ãƒ³ãƒˆã‚«ãƒ©ãƒ¼', + 'Neutral' => 'ニュートラル', + 'Night' => '夜景', + 'Night Scene' => '夜景', + 'Night Snapshot' => 'ナイトスナップ', + 'Pan focus' => 'パンフォーカス', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Sepia' => 'セピア', + 'Slow shutter' => 'スローシャッター', + 'Snow' => 'スノー', + 'Sports' => 'スãƒãƒ¼ãƒ„', + 'Still Image' => '陿­¢ç”»åƒ', + 'Sunset' => '夕焼ã‘', + 'Super Macro' => 'スーパーマクロ', + 'Underwater' => '水中', + 'Vivid' => 'ビビッド', + }, + }, + 'EdgeNoiseReduction' => { + Description => 'エッジノイズリダクション', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'EditStatus' => '編集状態', + 'EditorialUpdate' => { + Description => '更新編集', + PrintConv => { + 'Additional language' => '追加言語', + }, + }, + 'EffectiveLV' => '効果レベル', + 'Emphasis' => { + PrintConv => { + 'None' => 'ç„¡ã—', + }, + }, + 'EncodingProcess' => 'JPEGã®ç¬¦å·åŒ–処ç†', + 'EndPoints' => '末端', + 'EnhanceDarkTones' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Enhancement' => { + Description => '強度', + PrintConv => { + 'Blue' => 'é’', + 'Flesh Tones' => '肌調', + 'Green' => 'ç·‘', + 'Off' => 'オフ', + 'Red' => '赤', + }, + }, + 'Enhancer' => '増大値2', + 'EnhancerValues' => '増大値', + 'EnvelopeNumber' => 'エンベロープ数', + 'EnvelopePriority' => { + Description => 'エンベロープ優先度', + PrintConv => { + '0 (reserved)' => '0 (å°†æ¥æ‹¡å¼µç”¨)', + '1 (most urgent)' => '1 (高ã„緊急性)', + '5 (normal urgency)' => '5 (普通ã®ç·Šæ€¥æ€§)', + '8 (least urgent)' => '8 (低ã„緊急性)', + '9 (user-defined priority)' => '9 (ユーザ定義優先度)', + }, + }, + 'EnvelopeRecordVersion' => 'エンベロープレコードãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'EpsonImageHeight' => 'エプソン画åƒé«˜', + 'EpsonImageWidth' => 'エプソン画åƒå¹…', + 'EpsonSoftware' => 'エプソンソフトウェア', + 'Equipment' => 'イクイップメントIFDãƒã‚¤ãƒ³ã‚¿ãƒ¼', + 'EquipmentVersion' => 'イクイップメントãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'ExcursionTolerance' => '振幅許容範囲', + 'ExifCameraInfo' => 'Exifカメラ情報', + 'ExifImageHeight' => 'ç”»åƒé«˜ã•', + 'ExifImageWidth' => 'ç”»åƒå¹…', + 'ExifOffset' => 'Exif IFDã¸ã®ãƒã‚¤ãƒ³ã‚¿', + 'ExifToolVersion' => 'ExifToolãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'ExifVersion' => 'Exifãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'ExpandFilm' => '拡張フイルム', + 'ExpandFilterLens' => '拡張レンズフィルター', + 'ExpandFlashLamp' => '拡張フラッシュランプ', + 'ExpandLens' => '拡張レンズ', + 'ExpandScanner' => '拡張スキャナー', + 'ExpandSoftware' => '拡張ソフト', + 'ExpirationDate' => '有効日付', + 'ExpirationTime' => '有効時間', + 'Exposure' => '露出', + 'ExposureBracketStepSize' => '露出ブラケットステップサイズ', + 'ExposureBracketValue' => '露出ブラケット値', + 'ExposureCompStepSize' => { + Description => '露出補正/ファインãƒãƒ¥ãƒ¼ãƒ³', + PrintConv => { + '1 EV' => '1ステップ', + '1/2 EV' => '1/2ステップ', + '1/3 EV' => '1/3ステップ', + }, + }, + 'ExposureCompensation' => '露出補正値', + 'ExposureControlStepSize' => { + Description => '露出制御ã®EVステップ', + PrintConv => { + '1 EV' => '1ステップ', + '1/2 EV' => '1/2ステップ', + '1/3 EV' => '1/3ステップ', + }, + }, + 'ExposureDelayMode' => { + Description => '露出é…延モード', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ExposureDifference' => '露出差', + 'ExposureIndex' => '露出指標', + 'ExposureLevelIncrements' => { + Description => '露出制御ã®EVステップ', + PrintConv => { + '1-stop set, 1/3-stop comp.' => '設定1 露出補正1/3', + '1/2 Stop' => '1/2ステップ', + '1/2-stop set, 1/2-stop comp.' => '設定1/2 露出補正1/2', + '1/3 Stop' => '1/3ステップ', + '1/3-stop set, 1/3-stop comp.' => '設定1/3 露出補正1/3', + }, + }, + 'ExposureMode' => { + Description => '露光モード', + PrintConv => { + 'Aperture Priority' => '絞り優先', + 'Aperture-priority AE' => '絞り優先', + 'Auto' => '自動露出', + 'Auto bracket' => 'オートブラケット', + 'Bulb' => 'ãƒãƒ«ãƒ–', + 'Landscape' => '風景', + 'Manual' => 'マニュアル露出', + 'Night Scene / Twilight' => '夜景', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Program' => 'プログラム', + 'Program AE' => 'プログラムAE', + 'Program-shift' => 'プログラムシフト', + 'Shutter Priority' => 'シャッター優先', + 'Shutter speed priority AE' => 'シャッター優先', + 'n/a' => '未設定', + }, + }, + 'ExposureModeInManual' => { + Description => 'ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«éœ²å‡ºæ™‚ã®æ¸¬å…‰ãƒ¢ãƒ¼ãƒ‰', + PrintConv => { + 'Center-weighted average' => '中央é‡ç‚¹', + 'Evaluative metering' => '評価測光', + 'Partial metering' => '部分', + 'Specified metering mode' => '設定測光モード', + 'Spot metering' => 'スãƒãƒƒãƒˆ', + }, + }, + 'ExposureProgram' => { + Description => '露出プログラム', + PrintConv => { + 'Action (High speed)' => 'スãƒãƒ¼ãƒ„モード(高速シャッター優先)', + 'Aperture Priority' => '絞り優先', + 'Aperture-priority AE' => '絞り優先', + 'Bulb' => 'ãƒãƒ«ãƒ–', + 'Creative (Slow speed)' => 'クリエイティブプログラム(被写界深度優先)', + 'Landscape' => '風景モード', + 'Manual' => 'マニュアル', + 'Not Defined' => '未定義', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆãƒ¢ãƒ¼ãƒ‰', + 'Program' => 'プログラム', + 'Program AE' => 'ノーマルプログラム', + 'Shutter Priority' => 'シャッター優先', + 'Shutter speed priority AE' => 'シャッター優先', + }, + }, + 'ExposureTime' => '露出時間', + 'ExposureTime2' => '露出時間 2', + 'ExposureWarning' => { + Description => '露出警告', + PrintConv => { + 'Bad exposure' => '露出失敗', + 'Good' => '驿­£', + }, + }, + 'ExtendedWBDetect' => { + Description => 'å»¶é•·WB検出', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Extender' => 'エクステンダー', + 'ExtenderFirmwareVersion' => 'エクステンダーファームウェアãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'ExtenderModel' => 'エクステンダーモデル', + 'ExtenderSerialNumber' => 'エクステンダーシリアル番å·', + 'ExternalFlash' => { + Description => '外付フラッシュ', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ExternalFlashAE1' => '外付フラッシュAE1?', + 'ExternalFlashAE1_0' => '外付フラッシュAE1(0)?', + 'ExternalFlashAE2' => '外付フラッシュAE2?', + 'ExternalFlashAE2_0' => '外付フラッシュAE2(0)?', + 'ExternalFlashBounce' => { + Description => '外付フラッシュãƒã‚¦ãƒ³ã‚¹', + PrintConv => { + 'Bounce' => 'ãƒã‚¦ãƒ³ã‚¹', + 'Bounce or Off' => 'ãƒã‚¦ãƒ³ã‚¹ã‹ã‚ªãƒ•', + 'Direct' => 'ダイレクト', + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + 'n/a' => '該当無ã—', + }, + }, + 'ExternalFlashExposureComp' => { + Description => '外付ストロボ露出補正', + PrintConv => { + 'n/a' => '未設定(オフã‹ã‚ªãƒ¼ãƒˆãƒ¢ãƒ¼ãƒ‰ï¼‰', + 'n/a (Manual Mode)' => '未設定(マニュアルモード)', + }, + }, + 'ExternalFlashFlags' => '外付フラッシュフラグ', + 'ExternalFlashGuideNumber' => '外付フラッシュガイドナンãƒãƒ¼ï¼Ÿ', + 'ExternalFlashMode' => { + Description => '外付フラッシュモード', + PrintConv => { + 'Off' => 'オフ', + 'On, Auto' => 'オンã€ã‚ªãƒ¼ãƒˆ', + 'On, Contrast-control Sync' => 'オンã€å…‰é‡æ¯”制御シンクロ', + 'On, Flash Problem' => 'オンã€ãƒ•ラッシュã®å•題?', + 'On, High-speed Sync' => 'オンã€ãƒã‚¤ã‚¹ãƒ”ードシンクロ', + 'On, Manual' => 'オンã€ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«', + 'On, P-TTL Auto' => 'オンã€P-TTLオート', + 'On, Wireless' => 'オンã€ãƒ¯ã‚¤ãƒ¤ãƒ¬ã‚¹', + 'On, Wireless, High-speed Sync' => 'オンã€ãƒ¯ã‚¤ãƒ¤ãƒ¬ã‚¹ã€ãƒã‚¤ã‚¹ãƒ”ードシンクロ', + 'n/a - Off-Auto-Aperture' => '該当ãªã—-自動絞りオフ', + }, + }, + 'ExternalFlashZoom' => '外付フラッシュズーム', + 'ExtraSamples' => '特別サンプル', + 'FNumber' => 'F値', + 'Face0Position' => 'é¡”0ä½ç½®', + 'Face1Position' => 'é¡”1ä½ç½®', + 'Face2Position' => 'é¡”2ä½ç½®', + 'Face3Position' => 'é¡”3ä½ç½®', + 'Face4Position' => 'é¡”4ä½ç½®', + 'Face5Position' => 'é¡”5ä½ç½®', + 'Face6Position' => 'é¡”6ä½ç½®', + 'Face7Position' => 'é¡”7ä½ç½®', + 'Face8Position' => 'é¡”8ä½ç½®', + 'Face9Position' => 'é¡”9ä½ç½®', + 'FaceDetect' => { + Description => 'é¡”èªè­˜', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'FaceDetectArea' => '顔エリア', + 'FaceDetectFrameSize' => 'フレームサイズ', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => '水平(標準)', + 'Rotate 180' => '180度回転', + 'Rotate 270 CW' => '270度回転 CW', + 'Rotate 90 CW' => '90度回転 CW', + }, + }, + 'FacesDetected' => 'é¡”èªè­˜', + 'FastSeek' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'FaxProfile' => { + PrintConv => { + 'Unknown' => '䏿˜Ž', + }, + }, + 'FaxRecvParams' => 'FAXå—信パラメータ', + 'FaxRecvTime' => 'FAXå—信時間', + 'FaxSubAddress' => 'FAXサブアドレス', + 'FileFormat' => 'ファイル形å¼', + 'FileIndex' => 'ファイル索引', + 'FileInfo' => 'ファイル情報', + 'FileInfoVersion' => 'ファイル情報ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'FileModifyDate' => '更新日時', + 'FileName' => 'ファイルå', + 'FileNumber' => 'ファイル番å·', + 'FileNumberMemory' => { + Description => 'ファイル番å·ãƒ¡ãƒ¢ãƒª', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'FileNumberSequence' => { + Description => 'ファイル番å·é€£ç•ª', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'FileSize' => 'ファイルã®ã‚µã‚¤ã‚º', + 'FileSource' => { + Description => 'ファイルソース', + PrintConv => { + 'Digital Camera' => 'デジタルカメラ', + 'Film Scanner' => 'フィルムスキャナー', + 'Reflection Print Scanner' => 'åå°„å°åˆ·ã‚¹ã‚­ãƒ£ãƒŠãƒ¼', + }, + }, + 'FileType' => 'ファイルタイプ', + 'FileVersion' => 'ファイル形å¼ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'Filename' => 'ファイルå', + 'FillFlashAutoReduction' => { + Description => '日中シンクロ・ストロボ露出自動低減制御', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'FillOrder' => { + Description => 'フルオーダー', + PrintConv => { + 'Normal' => '標準', + }, + }, + 'FilmMode' => { + Description => 'フィルムモード', + PrintConv => { + 'Dynamic (B&W)' => 'ダイナミック(白黒)', + 'Dynamic (color)' => 'ダイナミック(カラー)', + 'Nature (color)' => 'ナãƒãƒ¥ãƒ©ãƒ«ï¼ˆã‚«ãƒ©ãƒ¼ï¼‰', + 'Smooth (B&W)' => '滑らã‹ï¼ˆç™½é»’)', + 'Smooth (color)' => '滑らã‹ï¼ˆã‚«ãƒ©ãƒ¼ï¼‰', + 'Standard (B&W)' => 'スタンダード(白黒)', + 'Standard (color)' => 'スタンダード(カラー)', + }, + }, + 'FilmType' => 'フィルムタイプ', + 'Filter' => { + Description => 'フィルター', + PrintConv => { + 'Off' => 'オフ', + }, + }, + 'FilterEffect' => { + Description => 'フィルター効果', + PrintConv => { + 'Green' => 'ç·‘', + 'None' => 'ç„¡ã—', + 'Off' => 'オフ', + 'Orange' => 'オレンジ', + 'Red' => '赤', + 'Yellow' => '黄色', + 'n/a' => '該当無ã—', + }, + }, + 'FilterEffectMonochrome' => { + Description => 'モノクロフィルター効果', + PrintConv => { + 'Green' => 'ç·‘', + 'None' => 'ç„¡ã—', + 'Orange' => 'オレンジ', + 'Red' => '赤', + 'Yellow' => '黄色', + }, + }, + 'FinderDisplayDuringExposure' => { + Description => '露光中ã®ãƒ•ァインダー内表示', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'FineTuneOptCenterWeighted' => '最é©éœ²å‡ºå¾®èª¿æ•´ 中央é‡ç‚¹æ¸¬å…‰', + 'FineTuneOptMatrixMetering' => '最é©éœ²å‡ºå¾®èª¿æ•´ 分割測光', + 'FineTuneOptSpotMetering' => '最é©éœ²å‡ºå¾®èª¿æ•´ スãƒãƒƒãƒˆæ¸¬å…‰', + 'Firmware' => 'ファームウェア', + 'FirmwareDate' => 'ファームウェア日付', + 'FirmwareRevision' => 'ファームウェアリビジョン', + 'FirmwareVersion' => 'ファームウェアãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'FixtureIdentifier' => 'フィクãƒãƒ£ãƒ¼è­˜åˆ¥å­', + 'Flash' => { + Description => 'ストロボ', + PrintConv => { + 'Auto, Did not fire' => 'フラッシュ未発光ã€ã‚ªãƒ¼ãƒˆãƒ¢ãƒ¼ãƒ‰', + 'Auto, Did not fire, Red-eye reduction' => 'オートã€ãƒ•ラッシュ未発光ã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰', + 'Auto, Fired' => 'フラッシュ発光ã€ã‚ªãƒ¼ãƒˆãƒ¢ãƒ¼ãƒ‰', + 'Auto, Fired, Red-eye reduction' => 'フラッシュ発光ã€ã‚ªãƒ¼ãƒˆãƒ¢ãƒ¼ãƒ‰ã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰', + 'Auto, Fired, Red-eye reduction, Return detected' => 'フラッシュ発光ã€ã‚ªãƒ¼ãƒˆãƒ¢ãƒ¼ãƒ‰ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æ¤œçŸ¥ã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰', + 'Auto, Fired, Red-eye reduction, Return not detected' => 'フラッシュ発光ã€ã‚ªãƒ¼ãƒˆãƒ¢ãƒ¼ãƒ‰ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æœªæ¤œçŸ¥ã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰', + 'Auto, Fired, Return detected' => 'フラッシュ発光ã€ã‚ªãƒ¼ãƒˆãƒ¢ãƒ¼ãƒ‰ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æ¤œçŸ¥', + 'Auto, Fired, Return not detected' => 'フラッシュ発光ã€ã‚ªãƒ¼ãƒˆãƒ¢ãƒ¼ãƒ‰ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æœªæ¤œçŸ¥', + 'Did not fire' => 'フラッシュ未発光', + 'Fired' => 'フラッシュ発光', + 'Fired, Red-eye reduction' => 'フラッシュ発光ã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰', + 'Fired, Red-eye reduction, Return detected' => 'フラッシュ発光ã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æ¤œçŸ¥', + 'Fired, Red-eye reduction, Return not detected' => 'フラッシュ発光ã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æœªæ¤œçŸ¥', + 'Fired, Return detected' => 'ストロボ光検知', + 'Fired, Return not detected' => 'ストロボ光未検知', + 'No Flash' => 'フラッシュ未発光', + 'No flash function' => 'フラッシュ機能無ã—', + 'Off' => 'オフ', + 'Off, Did not fire' => 'フラッシュ未発光ã€å¼·åˆ¶ç™ºå…‰ãƒ¢ãƒ¼ãƒ‰', + 'Off, Did not fire, Return not detected' => 'オフã€ãƒ•ラッシュ未発光ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æœªæ¤œçŸ¥', + 'Off, No flash function' => 'オフã€ãƒ•ラッシュ機能無ã—', + 'Off, Red-eye reduction' => 'オフã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰', + 'On' => 'オン', + 'On, Did not fire' => 'オンã€ãƒ•ラッシュ未発光', + 'On, Fired' => 'フラッシュ発光ã€å¼·åˆ¶ç™ºå…‰ãƒ¢ãƒ¼ãƒ‰', + 'On, Red-eye reduction' => 'フラッシュ発光ã€å¼·åˆ¶ç™ºå…‰ãƒ¢ãƒ¼ãƒ‰ã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰', + 'On, Red-eye reduction, Return detected' => 'フラッシュ発光ã€å¼·åˆ¶ç™ºå…‰ãƒ¢ãƒ¼ãƒ‰ã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æ¤œçŸ¥', + 'On, Red-eye reduction, Return not detected' => 'フラッシュ発光ã€å¼·åˆ¶ç™ºå…‰ãƒ¢ãƒ¼ãƒ‰ã€èµ¤ç›®è»½æ¸›ãƒ¢ãƒ¼ãƒ‰ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æœªæ¤œçŸ¥', + 'On, Return detected' => 'フラッシュ発光ã€å¼·åˆ¶ç™ºå…‰ãƒ¢ãƒ¼ãƒ‰ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æ¤œçŸ¥', + 'On, Return not detected' => 'フラッシュ発光ã€å¼·åˆ¶ç™ºå…‰ãƒ¢ãƒ¼ãƒ‰ã€ã‚¹ãƒˆãƒ­ãƒœå…‰æœªæ¤œçŸ¥', + }, + }, + 'FlashActivity' => 'フラッシュ稼åƒ', + 'FlashBias' => 'フラッシュãƒã‚¤ã‚¢ã‚¹', + 'FlashBits' => 'フラッシュ詳細', + 'FlashChargeLevel' => 'フラッシュãƒãƒ£ãƒ¼ã‚¸ãƒ¬ãƒ™ãƒ«', + 'FlashCommanderMode' => { + Description => 'コマンダーモード', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'FlashCompensation' => 'フラッシュ補正', + 'FlashControlMode' => { + Description => 'フラッシュコントロールモード', + PrintConv => { + 'Auto Aperture' => '自動絞り(AA)', + 'Manual' => 'マニュアル', + 'Off' => 'オフ', + 'Repeating Flash' => 'リピーティングフラッシュ', + }, + }, + 'FlashDevice' => { + Description => 'フラッシュデãƒã‚¤ã‚¹', + PrintConv => { + 'External' => '外付ã‘', + 'Internal' => '内蔵', + 'Internal + External' => '内蔵+外付ã‘', + 'None' => 'ç„¡ã—', + }, + }, + 'FlashDistance' => 'フラッシュ強度', + 'FlashEnergy' => 'フラッシュ強度', + 'FlashExposureBracketValue' => 'フラッシュ露出ブラケット値', + 'FlashExposureComp' => 'フラッシュ露出補正', + 'FlashExposureComp2' => 'フラッシュ露出補正', + 'FlashExposureCompSet' => 'ストロボ露出補正設定', + 'FlashExposureLock' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'FlashFired' => { + Description => 'フラッシュ発光', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'FlashFiring' => { + Description => 'ストロボã®ç™ºå…‰', + PrintConv => { + 'Does not fire' => 'ã—ãªã„', + 'Fires' => 'ã™ã‚‹', + }, + }, + 'FlashFirmwareVersion' => 'フラッシュファームウェアãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'FlashFocalLength' => 'フラッシュ焦点è·é›¢', + 'FlashGroupACompensation' => 'グループAフラッシュ補正', + 'FlashGroupAControlMode' => { + Description => 'グループAフラッシュコントロールモード', + PrintConv => { + 'Auto Aperture' => '自動絞り(AA)', + 'Manual' => 'マニュアル', + 'Off' => 'オフ', + 'Repeating Flash' => 'リピーティングフラッシュ', + }, + }, + 'FlashGroupAOutput' => 'グループAフラッシュ出力', + 'FlashGroupBCompensation' => 'グループBフラッシュ補正', + 'FlashGroupBControlMode' => { + Description => 'グループBフラッシュコントロールモード', + PrintConv => { + 'Auto Aperture' => '自動絞り(AA)', + 'Manual' => 'マニュアル', + 'Off' => 'オフ', + 'Repeating Flash' => 'リピーティングフラッシュ', + }, + }, + 'FlashGroupBOutput' => 'グループBフラッシュ出力', + 'FlashGroupCCompensation' => 'グループCフラッシュ補正', + 'FlashGroupCControlMode' => { + Description => 'グループCフラッシュコントロールモード', + PrintConv => { + 'Auto Aperture' => '自動絞り(AA)', + 'Manual' => 'マニュアル', + 'Off' => 'オフ', + 'Repeating Flash' => 'リピーティングフラッシュ', + }, + }, + 'FlashGroupCOutput' => 'グループCフラッシュ出力', + 'FlashGuideNumber' => 'フラッシュガイドナンãƒãƒ¼', + 'FlashInfo' => 'ストロボ情報', + 'FlashInfoVersion' => 'フラッシュ情報ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'FlashIntensity' => { + Description => 'フラッシュ強度', + PrintConv => { + 'High' => '高ã„', + 'Low' => '低ã„', + 'Normal' => '標準', + 'Strong' => 'å¼·ã„', + 'Weak' => 'å¼±ã„', + }, + }, + 'FlashLevel' => 'フラッシュ補正', + 'FlashMetering' => 'フラッシュ計測', + 'FlashMeteringSegments' => 'フラッシュ測光値', + 'FlashMode' => { + Description => 'フラッシュモード', + PrintConv => { + 'Auto' => 'オート', + 'Auto, Did not fire' => 'オートã€ç™ºå…‰ç„¡ã—', + 'Auto, Did not fire, Red-eye reduction' => 'オートã€ç™ºå…‰ç„¡ã—ã€èµ¤ç›®è»½æ¸›', + 'Auto, Fired' => 'オートã€ç™ºå…‰', + 'Auto, Fired, Red-eye reduction' => 'オートã€ç™ºå…‰ã€èµ¤ç›®è»½æ¸›', + 'Did Not Fire' => 'ç™ºå…‰ç¦æ­¢', + 'External, Auto' => '外付ã€ã‚ªãƒ¼ãƒˆ', + 'External, Contrast-control Sync' => '外付ã€å…‰é‡æ¯”制御シンクロ', + 'External, Flash Problem' => '外付ã€ãƒ•ラッシュã®å•題?', + 'External, High-speed Sync' => '外付ã€ãƒã‚¤ã‚¹ãƒ”ードシンクロ', + 'External, Manual' => '外付ã€ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«', + 'External, P-TTL Auto' => '外付ã€P-TTL自動調光', + 'External, Wireless' => '外付ã€ãƒ¯ã‚¤ãƒ¤ãƒ¬ã‚¹', + 'External, Wireless, High-speed Sync' => '外付ã€ãƒ¯ã‚¤ãƒ¤ãƒ¬ã‚¹ã€ãƒã‚¤ã‚¹ãƒ”ードシンクロ', + 'Fill flash' => '強制発光', + 'Fired, Commander Mode' => '発光ã€ã‚³ãƒžãƒ³ãƒ€ãƒ¼ãƒ¢ãƒ¼ãƒ‰', + 'Fired, External' => '発光ã€å¤–付', + 'Fired, Manual' => '発光ã€ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«', + 'Fired, TTL Mode' => '発光ã€TTLモード', + 'Internal' => '内蔵', + 'Normal' => '標準', + 'Off' => 'オフ', + 'Off, Did not fire' => 'オフ', + 'Off?' => 'オフ?', + 'On' => 'オン', + 'On, Did not fire' => 'オンã€ç™ºå…‰ç„¡ã—', + 'On, Fired' => 'オン', + 'On, Red-eye reduction' => 'オンã€èµ¤ç›®è»½æ¸›', + 'On, Slow-sync' => 'オンã€ã‚¹ãƒ­ãƒ¼ã‚·ãƒ³ã‚¯ãƒ­', + 'On, Slow-sync, Red-eye reduction' => 'オンã€ã‚¹ãƒ­ãƒ¼ã‚·ãƒ³ã‚¯ãƒ­ã€èµ¤ç›®è»½æ¸›', + 'On, Soft' => 'オンã€ã‚½ãƒ•ト', + 'On, Trailing-curtain Sync' => 'オンã€å¾Œå¹•シンクロ', + 'On, Wireless (Control)' => 'オンã€ãƒ¯ã‚¤ãƒ¤ãƒ¬ã‚¹ (コントロール)', + 'On, Wireless (Master)' => 'オンã€ãƒ¯ã‚¤ãƒ¤ãƒ¬ã‚¹ (マスター)', + 'Rear flash sync' => 'リアフラッシュシンクロ', + 'Red-eye Reduction' => '赤目軽減', + 'Red-eye reduction' => '赤目軽減', + 'Unknown' => '䏿˜Ž', + 'Wireless' => 'ワイヤレス', + 'n/a - Off-Auto-Aperture' => '該当ãªã—-自動絞りオフ', + }, + }, + 'FlashModel' => { + Description => 'フラッシュモデル', + PrintConv => { + 'None' => 'ç„¡ã—', + }, + }, + 'FlashOptions' => { + Description => 'フラッシュオプション', + PrintConv => { + 'Auto' => 'オート', + 'Auto, Red-eye reduction' => 'オートã€èµ¤ç›®è»½æ¸›', + 'Normal' => '標準', + 'Red-eye reduction' => '赤目軽減', + 'Slow-sync' => 'スローシンクロ', + 'Slow-sync, Red-eye reduction' => 'スローシンクロã€èµ¤ç›®è»½æ¸›', + 'Trailing-curtain Sync' => '後幕シンクロ', + 'Wireless (Control)' => 'ワイヤレス(コントロール発光)', + 'Wireless (Master)' => 'ワイヤレス(マスター発光)', + }, + }, + 'FlashOptions2' => { + Description => 'ストロボオプション(2)', + PrintConv => { + 'Auto' => 'オート', + 'Auto, Red-eye reduction' => 'オートã€èµ¤ç›®è»½æ¸›', + 'Normal' => '標準', + 'Red-eye reduction' => '赤目軽減', + 'Slow-sync' => 'スローシンクロ', + 'Slow-sync, Red-eye reduction' => 'スローシンクロã€èµ¤ç›®è»½æ¸›', + 'Trailing-curtain Sync' => '後幕シンクロ', + 'Wireless (Control)' => 'ワイヤレス(コントロール発光)', + 'Wireless (Master)' => 'ワイヤレス(マスター発光)', + }, + }, + 'FlashOutput' => 'フラッシュ出力', + 'FlashRemoteControl' => 'フラッシュリモートコントロール', + 'FlashSerialNumber' => 'フラッシュシリアル番å·', + 'FlashSetting' => 'フラッシュ設定', + 'FlashShutterSpeed' => 'フラッシュシャッター速度', + 'FlashStatus' => { + Description => 'ストロボ状態', + PrintConv => { + 'External, Did not fire' => 'å¤–ä»˜ã€æœªç™ºå…‰', + 'External, Fired' => '外付ã€ç™ºå…‰', + 'Internal, Did not fire' => 'å†…è”µã€æœªç™ºå…‰', + 'Internal, Fired' => '内蔵ã€ç™ºå…‰', + 'Off' => 'オフ', + }, + }, + 'FlashSyncSpeed' => 'フラッシュåŒèª¿é€Ÿåº¦', + 'FlashSyncSpeedAv' => { + Description => 'Avモード時ã®ã‚¹ãƒˆãƒ­ãƒœåŒèª¿é€Ÿåº¦', + PrintConv => { + '1/200 Fixed' => '1/200秒固定', + '1/250 Fixed' => '1/250秒固定', + '1/300 Fixed' => '1/300秒固定', + 'Auto' => 'オート', + }, + }, + 'FlashType' => { + Description => 'フラッシュタイプ', + PrintConv => { + 'E-System' => 'E-システム', + 'None' => 'ç„¡ã—', + 'Simple E-System' => 'シンプルE-システム', + }, + }, + 'FlashWarning' => { + Description => 'フラッシュ警告', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'FlashpixVersion' => 'サãƒãƒ¼ãƒˆãƒ•ラッシュピックスãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'FlickerReduce' => { + Description => 'フリッカー軽減', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'FlipHorizontal' => { + Description => '横フリップ', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'FocalLength' => 'レンズ焦点è·é›¢', + 'FocalLength35efl' => 'レンズ焦点è·é›¢', + 'FocalLengthIn35mmFormat' => '35mmフイルムæ›ç®—焦点è·é›¢', + 'FocalPlaneDiagonal' => '焦点é¢å¯¾è§’ç·š', + 'FocalPlaneResolutionUnit' => { + Description => '焦点é¢è§£åƒåº¦å˜ä½', + PrintConv => { + 'None' => 'ç„¡ã—', + 'inches' => 'インãƒ', + }, + }, + 'FocalPlaneXResolution' => '焦点é¢Xè§£åƒåº¦', + 'FocalPlaneXSize' => '焦点é¢Xサイズ', + 'FocalPlaneXUnknown' => '焦点é¢Xサイズ', + 'FocalPlaneYResolution' => '焦点é¢Yè§£åƒåº¦', + 'FocalPlaneYSize' => '焦点é¢Yサイズ', + 'FocalPlaneYUnknown' => '焦点é¢Yサイズ', + 'FocalType' => { + Description => 'フォーカスタイプ', + PrintConv => { + 'Fixed' => '固定', + 'Zoom' => 'ズーム', + }, + }, + 'FocalUnits' => '焦点å˜ä½/mm', + 'Focus' => { + Description => 'フォーカス', + PrintConv => { + 'Manual' => 'マニュアル', + }, + }, + 'FocusArea' => 'フォーカスエリア', + 'FocusAreaSelection' => { + Description => 'フォーカスãƒã‚¤ãƒ³ãƒˆãƒ©ãƒƒãƒ—アラウンド', + PrintConv => { + 'No Wrap' => 'ノーラップ', + 'Wrap' => 'ラップ', + }, + }, + 'FocusContinuous' => { + Description => '連続フォーカス', + PrintConv => { + 'Continuous' => '連続', + 'Manual' => 'マニュアル', + 'Single' => 'シングル', + }, + }, + 'FocusDistance' => '被写体è·é›¢', + 'FocusDistanceLower' => '焦点è·é›¢ä½Žéƒ¨åˆ†', + 'FocusDistanceUpper' => '焦点è·é›¢é«˜éƒ¨åˆ†', + 'FocusMode' => { + Description => 'フォーカスモード', + PrintConv => { + 'AI Focus AF' => 'AIフォーカスAF', + 'AI Servo AF' => 'AIサーボAF', + 'Auto' => 'オート', + 'Auto, Continuous' => 'オートã€ã‚³ãƒ³ãƒ†ã‚£ãƒ‹ãƒ¥ã‚¢ã‚¹', + 'Auto, Focus button' => 'オートã€ãƒ•ォーカスボタン', + 'Continuous' => '連写', + 'Continuous AF' => 'コンティニュアスAF', + 'Custom' => 'カスタム', + 'Infinity' => 'ç„¡é™é ', + 'Macro' => 'マクロ', + 'Macro (1)' => 'マクロ(1)', + 'Macro (2)' => 'マクロ(2)', + 'Manual' => 'マニュアル', + 'Manual Focus (3)' => 'マニュアルフォーカス(3)', + 'Manual Focus (6)' => 'マニュアルフォーカス(6)', + 'Multi AF' => 'マルãƒAF', + 'Normal' => '標準', + 'One-shot AF' => 'ワンショットAF', + 'Pan Focus' => 'パンフォーカス', + 'Sequential shooting AF' => 'シーケンシャルシューティングAF', + 'Single' => 'シングル', + 'Single AF' => 'シングルAF', + 'Super Macro' => 'スーパーマクロ', + }, + }, + 'FocusMode2' => 'フォーカスモード2', + 'FocusModeSetting' => { + Description => 'フォーカスモード', + PrintConv => { + 'Manual' => 'マニュアル', + }, + }, + 'FocusPixel' => '焦点解åƒåº¦', + 'FocusPointWrap' => { + Description => 'フォーカスãƒã‚¤ãƒ³ãƒˆãƒ©ãƒƒãƒ—アラウンド', + PrintConv => { + 'No Wrap' => 'ノーラップ', + 'Wrap' => 'ラップ', + }, + }, + 'FocusPosition' => 'フォーカスè·é›¢', + 'FocusProcess' => { + Description => 'フォーカスプロセス', + PrintConv => { + 'AF Not Used' => 'AF未使用', + 'AF Used' => 'AF使用', + }, + }, + 'FocusRange' => { + Description => 'フォーカスレンジ', + PrintConv => { + 'Auto' => 'オート', + 'Close' => '近景', + 'Far Range' => 'é æ™¯', + 'Infinity' => 'ç„¡é™é ', + 'Macro' => 'マクロ', + 'Manual' => 'マニュアル', + 'Middle Range' => '中間範囲', + 'Normal' => 'ノーマル', + 'Not Known' => '䏿˜Ž', + 'Pan Focus' => 'パンフォーカス', + 'Super Macro' => 'スーパーマクロ', + 'Very Close' => '近接', + }, + }, + 'FocusSetting' => 'フォーカス設定', + 'FocusStepCount' => 'フォーカスステップ数', + 'FocusStepInfinity' => 'ç„¡é™ãƒ¬ãƒ³ã‚ºã‚¹ãƒ†ãƒƒãƒ—', + 'FocusStepNear' => 'ニアステップ数', + 'FocusTrackingLockOn' => { + Description => 'フォーカストラッキングã¨ãƒ­ãƒƒã‚¯ã‚ªãƒ³', + PrintConv => { + 'Long' => 'ロング', + 'Normal' => '標準', + 'Off' => 'オフ', + 'Short' => 'ショート', + }, + }, + 'FocusWarning' => { + Description => 'フォーカス警告', + PrintConv => { + 'Good' => 'ジャスピン', + 'Out of focus' => 'ピンボケ', + }, + }, + 'FocusingScreen' => 'フォーカシングスクリーン', + 'FolderName' => 'フォルダå', + 'ForwardMatrix1' => 'å‰è¡Œåˆ—1', + 'ForwardMatrix2' => 'å‰è¡Œåˆ—2', + 'FrameHeight' => 'フレーム高', + 'FrameNumber' => 'フレーム番å·', + 'FrameRate' => 'フレームレート', + 'FrameSize' => 'フレームサイズ', + 'FrameWidth' => 'フレーム幅', + 'FreeByteCounts' => 'フリーãƒã‚¤ãƒˆæ•°', + 'FreeMemoryCardImages' => 'フリーメモリーカードイメージ', + 'FreeOffsets' => 'フリーオフセット', + 'FujiFlashMode' => { + Description => 'フラッシュモード', + PrintConv => { + 'Auto' => 'オート', + 'External' => '外付フラッシュ', + 'Off' => 'オフ', + 'On' => 'オン', + 'Red-eye reduction' => '赤目軽減', + }, + }, + 'FunctionButton' => { + Description => 'FUNCボタン', + PrintConv => { + 'AF-area Mode' => 'AFエリアモード', + 'Center AF Area' => '中央AFエリア', + 'Center-weighted' => '中央é‡ç‚¹æ¸¬å…‰', + 'FV Lock' => 'FVロック', + 'Flash Off' => 'フラッシュオフ', + 'Framing Grid' => 'フレーミンググリッド', + 'ISO Display' => 'ISO表示', + 'Matrix Metering' => '分割測光', + 'Spot Metering' => 'スãƒãƒƒãƒˆæ¸¬å…‰', + }, + }, + 'GEMInfo' => 'GEM情報', + 'GEModel' => 'モデル', + 'GIFVersion' => 'GIFãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'GPSAltitude' => '高度', + 'GPSAltitudeRef' => { + Description => 'å‚照高度', + PrintConv => { + 'Above Sea Level' => 'æµ·æ°´é¢', + 'Below Sea Level' => 'å‚ç…§æµ·æ°´é¢(è² ã®å€¤)', + }, + }, + 'GPSAreaInformation' => 'GPSエリアã®åç§°', + 'GPSDOP' => '測定精度', + 'GPSDateStamp' => 'GPSデータ', + 'GPSDateTime' => 'GPS時間(åŽŸå­æ™‚計)', + 'GPSDestBearing' => 'ç›®çš„åœ°ã®æ–¹å‘', + 'GPSDestBearingRef' => { + Description => 'ç›®çš„åœ°ã®æ–¹å‘ã®å‚ç…§', + PrintConv => { + 'Magnetic North' => 'ç£æ°—ã®æ–¹å‘', + 'True North' => 'æ­£å¸¸ãªæ–¹å‘', + }, + }, + 'GPSDestDistance' => '目的地ã®è·é›¢', + 'GPSDestDistanceRef' => { + Description => '目的地ã®è·é›¢ã®å‚ç…§', + PrintConv => { + 'Kilometers' => 'キロメートル', + 'Miles' => 'マイル', + 'Nautical Miles' => 'ノット', + }, + }, + 'GPSDestLatitude' => '目的地ã®ç·¯åº¦', + 'GPSDestLatitudeRef' => { + Description => '目的地ã®ç·¯åº¦ã®ãŸã‚ã®å‚ç…§', + PrintConv => { + 'North' => '北緯', + 'South' => 'å—ç·¯', + }, + }, + 'GPSDestLongitude' => '目的地ã®çµŒåº¦', + 'GPSDestLongitudeRef' => { + Description => '目的地ã®çµŒåº¦ã®ãŸã‚ã®å‚ç…§', + PrintConv => { + 'East' => 'æ±çµŒ', + 'West' => '西経', + }, + }, + 'GPSDifferential' => { + Description => 'GPS誤差修正', + PrintConv => { + 'Differential Corrected' => '誤差修正ã‚り', + 'No Correction' => '誤差修正無ã—', + }, + }, + 'GPSImgDirection' => 'ã‚¤ãƒ¡ãƒ¼ã‚¸ã®æ–¹å‘', + 'GPSImgDirectionRef' => { + Description => 'ç”»åƒæ–¹å‘å‚ç…§', + PrintConv => { + 'Magnetic North' => 'ç£æ°—ã®æ–¹å‘', + 'True North' => 'æœ¬å½“ã®æ–¹å‘', + }, + }, + 'GPSInfo' => 'GPS IFDã¸ã®ãƒã‚¤ãƒ³ã‚¿', + 'GPSLatitude' => '緯度', + 'GPSLatitudeRef' => { + Description => '北緯ã¾ãŸã¯å—ç·¯', + PrintConv => { + 'North' => '北緯', + 'South' => 'å—ç·¯', + }, + }, + 'GPSLongitude' => '経度', + 'GPSLongitudeRef' => { + Description => 'æ±çµŒã¾ãŸã¯è¥¿çµŒ', + PrintConv => { + 'East' => 'æ±çµŒ', + 'West' => '西経', + }, + }, + 'GPSMapDatum' => 'データãŒä½¿ã£ãŸæ¸¬åœ°æ¸¬é‡', + 'GPSMeasureMode' => { + Description => 'GPS測定モード', + PrintConv => { + '2-D' => '2次元測定', + '2-Dimensional' => '2次元測定', + '2-Dimensional Measurement' => '2次元測定', + '3-D' => '3次元測定', + '3-Dimensional' => '3次元測定', + '3-Dimensional Measurement' => '3次元測定', + }, + }, + 'GPSProcessingMethod' => 'GPSå‡¦ç†æ–¹æ³•ã®åç§°', + 'GPSSatellites' => '測定ã®ãŸã‚ã«ä½¿ã‚れãŸGPS衛星', + 'GPSSpeed' => 'GPSå—信機ã®ã‚¹ãƒ”ード', + 'GPSSpeedRef' => { + Description => 'スピードå˜ä½', + PrintConv => { + 'km/h' => '時速(km)', + 'knots' => 'ノット', + 'mph' => '時速(マイル)', + }, + }, + 'GPSStatus' => { + Description => 'GPSå—信機ステータス', + PrintConv => { + 'Measurement Active' => '測定アクティブ', + 'Measurement Void' => '測定無効', + }, + }, + 'GPSTimeStamp' => 'GPS時間(åŽŸå­æ™‚計)', + 'GPSTrack' => '動作方å‘', + 'GPSTrackRef' => { + Description => '動作方å‘ã®å‚ç…§', + PrintConv => { + 'Magnetic North' => 'ç£æ°—ã®æ–¹å‘', + 'True North' => 'æœ¬å½“ã®æ–¹å‘', + }, + }, + 'GPSVersionID' => 'GPSã‚¿ã‚°ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'GainBase' => '基本ゲイン', + 'GainControl' => { + Description => 'ゲインコントロール', + PrintConv => { + 'High gain down' => '高ã„ゲインダウン', + 'High gain up' => '高ã„ゲインアップ', + 'Low gain down' => '低ã„ゲインダウン', + 'Low gain up' => '低ã„ゲインアップ', + 'None' => 'ç„¡ã—', + }, + }, + 'Gamma' => 'ガンマ', + 'GammaCompensatedValue' => 'ガンマ補償値', + 'Gamut' => '全域', + 'Gapless' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'GeoTiffAsciiParams' => 'ジオアスキー設定タグ', + 'GeoTiffDirectory' => 'ジオキーディレクトリタグ', + 'GeoTiffDoubleParams' => 'ジオダブル設定タグ', + 'Gradation' => 'グラデーション', + 'GrayPoint' => 'グレーãƒã‚¤ãƒ³ãƒˆ', + 'GrayResponseCurve' => 'グレーå応曲線', + 'GrayResponseUnit' => { + Description => 'グレーå応å˜ä½', + PrintConv => { + '0.0001' => 'å˜ä½ã®æ•°ã¯1000を表ã™', + '0.001' => 'å˜ä½ã®æ•°ã¯100を表ã™', + '0.1' => 'å˜ä½ã®æ•°ã¯10を表ã™', + '1e-05' => 'å˜ä½ã®æ•°ã¯10-1000を表ã™', + '1e-06' => 'å˜ä½ã®æ•°ã¯100-1000を表ã™', + }, + }, + 'GrayScale' => 'グレースケール', + 'GrayTRC' => 'ç°è‰²èª¿å¢—殖曲線', + 'GreenMatrixColumn' => '緑色マトリックス列', + 'GreenTRC' => '緑色調増殖曲線', + 'GridDisplay' => { + Description => 'ビューファインダーグリッド表示', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'GripBatteryADLoad' => 'é›»æºA/Dグリップ起動時', + 'GripBatteryADNoLoad' => 'é›»æºA/Dグリップオフ時', + 'GripBatteryState' => 'é›»æºçŠ¶æ…‹ã‚°ãƒªãƒƒãƒ—', + 'HCUsage' => 'HC使用', + 'HDR' => { + Description => 'オートHDR', + PrintConv => { + 'Off' => '切', + }, + }, + 'HalftoneHints' => 'ãƒãƒ¼ãƒ•トーンヒント', + 'Headline' => 'ヘッドライン', + 'HeightResolution' => 'é«˜ã•æ–¹å‘ã®ç”»åƒè§£åƒåº¦', + 'HighISONoiseReduction' => { + Description => '高感度ノイズリダクション', + PrintConv => { + '+2 (strong)' => '+2 (å¼·)', + '-2 (weak)' => '-2 (å¼±)', + '-4 (weakest)' => '-4 (微弱)', + 'Auto' => 'オート', + 'High' => '高ã„', + 'Low' => 'ソフト', + 'Minimal' => '最å°', + 'Normal' => '標準', + 'Off' => 'オフ', + 'On' => 'オン', + 'Standard' => 'スタンダード', + 'Strong' => 'å¼·', + 'Weak' => 'å¼±', + 'Weakest' => '微弱', + }, + }, + 'Highlight' => 'ãƒã‚¤ãƒ©ã‚¤ãƒˆ', + 'HighlightTonePriority' => { + Description => '高è¼åº¦å´ãƒ»éšŽèª¿å„ªå…ˆ', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'HometownCity' => 'ç¾åœ¨åœ°', + 'HometownCityCode' => 'ç¾åœ¨åœ°ã‚³ãƒ¼ãƒ‰', + 'HometownDST' => { + Description => 'ç¾åœ¨åœ°DST', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'HostComputer' => 'ホストコンピューター', + 'Hue' => '色相', + 'HueAdjustment' => '色相調整', + 'HueSetting' => '色相設定', + 'ICCProfile' => 'ICCプロフィール', + 'ICC_Profile' => 'ICC入力色プロフィール', + 'IPTC-NAA' => 'IPTC-NAAメタデータ', + 'IPTCBitsPerSample' => 'サンプルã‚ãŸã‚Šãƒ“ット数', + 'IPTCData' => 'IPTCデータ', + 'IPTCImageHeight' => 'ライン数', + 'IPTCImageRotation' => 'イメージ回転', + 'IPTCImageWidth' => 'ラインã‚ãŸã‚Šã®ãƒ”クセル数', + 'IPTCPictureNumber' => '写真番å·', + 'IPTCPixelHeight' => 'スキャン方å‘垂直ピクセルサイズ', + 'IPTCPixelWidth' => 'スキャン方å‘ピクセルサイズ', + 'ISO' => 'ISOスピードレート', + 'ISO2' => 'ISO(2)', + 'ISOAuto' => 'ISOオート', + 'ISODisplay' => 'ISO表示', + 'ISOExpansion' => { + Description => 'ISO感度拡張', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ISOExpansion2' => { + Description => 'ISO拡大(2)', + PrintConv => { + 'Off' => 'オフ', + }, + }, + 'ISOFloor' => '最低感度', + 'ISOInfo' => 'ISO情報', + 'ISOSelection' => 'ISOé¸æŠž', + 'ISOSetting' => { + Description => 'ISO設定', + PrintConv => { + 'Auto' => 'オート', + 'Manual' => 'マニュアル', + }, + }, + 'ISOSpeedExpansion' => { + Description => 'ISO感度拡張', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'ISOSpeedIncrements' => { + Description => 'ISO感度ステップ値', + PrintConv => { + '1 Stop' => '1ステップ', + '1/3 Stop' => '1/3ステップ', + }, + }, + 'ISOSpeedRange' => { + Description => 'ISO感度ã®åˆ¶å¾¡ç¯„囲ã®è¨­å®š', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'ISOStepSize' => { + Description => 'ISO感度ステップ値', + PrintConv => { + '1 EV' => '1ステップ', + '1/2 EV' => '1/2ステップ', + '1/3 EV' => '1/3ステップ', + }, + }, + 'ISOValue' => 'ISO感度', + 'IT8Header' => 'IT8ヘッダー', + 'Illumination' => { + Description => 'イルミãƒãƒ¼ã‚·ãƒ§ãƒ³', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Image::ExifTool::NikonCapture::RedEyeData' => 'Nikon Capture 赤目データ', + 'ImageAdjustment' => 'イメージ調整', + 'ImageAreaOffset' => 'イメージ領域オフセット', + 'ImageAuthentication' => { + Description => 'イメージèªè¨¼', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ImageBoundary' => 'イメージ境界線', + 'ImageByteCount' => 'ç”»åƒãƒã‚¤ãƒˆæ•°', + 'ImageColorIndicator' => 'ç”»åƒè‰²æŒ‡æ¨™', + 'ImageColorValue' => 'ç”»åƒè‰²å€¤', + 'ImageCount' => 'イメージカウント', + 'ImageDataDiscard' => { + Description => 'イメージデータ廃棄', + PrintConv => { + 'Flexbits Discarded' => 'フレックスビット破棄', + 'Full Resolution' => '完全ãªè§£åƒåº¦', + 'HighPass Frequency Data Discarded' => 'ãƒã‚¤ãƒ‘ス周波数データ破棄', + 'Highpass and LowPass Frequency Data Discarded' => 'ãƒã‚¤ãƒ‘スã¨ãƒ­ãƒ¼ãƒ‘ス周波数データ破棄', + }, + }, + 'ImageDataSize' => 'イメージデータサイズ', + 'ImageDepth' => 'ã‚¤ãƒ¡ãƒ¼ã‚¸ã®æ·±ã•', + 'ImageDescription' => 'イメージ説明', + 'ImageDustOff' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ImageEditCount' => 'ç”»åƒå‡¦ç†ã‚«ã‚¦ãƒ³ãƒˆ', + 'ImageEditing' => { + Description => 'イメージ処ç†', + PrintConv => { + 'Cropped' => 'クロップ', + 'Digital Filter' => 'デジタルフィルター', + 'Frame Synthesis?' => 'ãƒ•ãƒ¬ãƒ¼ãƒ åˆæˆï¼Ÿ', + 'None' => '未処ç†', + }, + }, + 'ImageHeight' => 'ç”»åƒé«˜ã•', + 'ImageHistory' => 'ç”»åƒå±¥æ­´', + 'ImageID' => 'イメージID', + 'ImageInfo' => 'ç”»åƒæƒ…å ±', + 'ImageLayer' => 'イメージレイヤー', + 'ImageNumber' => 'イメージ番å·', + 'ImageNumber2' => 'イメージ番å·(2)', + 'ImageOffset' => 'イメージオフセット', + 'ImageOptimization' => 'イメージ最é©åŒ–', + 'ImageOrientation' => { + Description => 'イメージ方å‘', + PrintConv => { + 'Landscape' => 'ランドスケープ', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Square' => '正方形', + }, + }, + 'ImageProcessing' => 'イメージ処ç†', + 'ImageProcessingVersion' => 'ç”»åƒå‡¦ç†ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'ImageQuality' => { + Description => 'ç”»åƒå“質', + PrintConv => { + 'High' => '高ã„', + 'Motion Picture' => 'å‹•ç”»', + 'Normal' => '標準', + }, + }, + 'ImageQuality2' => 'イメージå“質2', + 'ImageResourceBlocks' => 'イメージリソースブロック', + 'ImageReview' => { + Description => 'ç”»åƒè©•価', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ImageReviewTime' => '自動オフタイマー ç”»åƒè©•価時間', + 'ImageRotated' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'ImageSize' => 'イメージサイズ', + 'ImageSourceData' => 'イメージソースデータ', + 'ImageStabilization' => { + Description => 'イメージスタビライザー', + PrintConv => { + 'Best Shot' => 'ベストショット', + 'Off' => 'オフ', + 'On' => 'オン', + 'On, Mode 1' => 'オンã€ãƒ¢ãƒ¼ãƒ‰ï¼‘', + 'On, Mode 2' => 'オンã€ãƒ¢ãƒ¼ãƒ‰ï¼’', + }, + }, + 'ImageTone' => { + Description => 'ç”»åƒä»•上', + PrintConv => { + 'Bright' => '鮮やã‹', + 'Landscape' => '風景', + 'Monochrome' => 'モノトーン', + 'Natural' => 'ナãƒãƒ¥ãƒ©ãƒ«', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Vibrant' => 'é›…(MIYABI)', + }, + }, + 'ImageType' => 'ページ', + 'ImageUniqueID' => 'ユニークãªã‚¤ãƒ¡ãƒ¼ã‚¸ID', + 'ImageWidth' => 'ç”»åƒå¹…', + 'Index' => '目次', + 'Indexed' => 'インデックス', + 'InfoButtonWhenShooting' => { + Description => '撮影時ã®INFOボタン', + PrintConv => { + 'Displays camera settings' => 'カメラ設定内容を表示', + 'Displays shooting functions' => '撮影機能ã®è¨­å®šçŠ¶æ…‹ã‚’è¡¨ç¤º', + }, + }, + 'InitialZoomSetting' => { + Description => 'åˆæœŸã‚ºãƒ¼ãƒ è¨­å®š', + PrintConv => { + 'High Magnification' => 'é«˜ã„æ‹¡å¤§', + 'Low Magnification' => 'ä½Žã„æ‹¡å¤§', + 'Medium Magnification' => '中間拡大', + }, + }, + 'InkNames' => 'インクå', + 'InkSet' => 'インクセット', + 'Instructions' => '詳細', + 'IntellectualGenre' => 'インテリジャンル', + 'IntelligentAuto' => 'インテリジェントオート', + 'IntensityStereo' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'IntergraphMatrix' => '相互グラフマトリックスタグ', + 'Interlace' => 'インタレース', + 'InternalFlash' => { + Description => '内蔵フラッシュコントロール', + PrintConv => { + 'Commander Mode' => 'コマンダーモード', + 'Fired' => 'フラッシュ発光', + 'Manual' => 'マニュアル', + 'No' => 'フラッシュ未発光', + 'Off' => 'オフ', + 'On' => 'オン', + 'Repeating Flash' => 'リピーティングフラッシュ', + }, + }, + 'InternalFlashAE1' => '内蔵フラッシュAE1?', + 'InternalFlashAE1_0' => '内蔵フラッシュAE1(0)?', + 'InternalFlashAE2' => '内蔵フラッシュAE2?', + 'InternalFlashAE2_0' => '内蔵フラッシュAE2(0)?', + 'InternalFlashMode' => { + Description => '内蔵ストロボモード', + PrintConv => { + 'Did not fire, (Unknown 0xf4)' => 'ã‚ªãƒ•ï¼ˆæœªç¢ºèª 0xF4?)', + 'Did not fire, Auto' => 'オフã€ã‚ªãƒ¼ãƒˆ', + 'Did not fire, Auto, Red-eye reduction' => 'オフã€ã‚ªãƒ¼ãƒˆã€èµ¤ç›®è»½æ¸›', + 'Did not fire, Normal' => 'ã‚ªãƒ•ã€æ¨™æº–', + 'Did not fire, Red-eye reduction' => 'オフã€èµ¤ç›®è»½æ¸›', + 'Did not fire, Slow-sync' => 'オフã€ã‚¹ãƒ­ãƒ¼ã‚·ãƒ³ã‚¯ãƒ­', + 'Did not fire, Slow-sync, Red-eye reduction' => 'オフã€ã‚¹ãƒ­ãƒ¼ã‚·ãƒ³ã‚¯ãƒ­ã€èµ¤ç›®è»½æ¸›', + 'Did not fire, Trailing-curtain Sync' => 'オフã€å¾Œå¹•シンクロ', + 'Did not fire, Wireless (Control)' => 'オフã€ãƒ¯ã‚¤ãƒ¤ãƒ¬ã‚¹ï¼ˆã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«ç™ºå…‰ï¼‰', + 'Did not fire, Wireless (Master)' => 'オフã€ãƒ¯ã‚¤ãƒ¤ãƒ¬ã‚¹ï¼ˆãƒžã‚¹ã‚¿ãƒ¼ç™ºå…‰ï¼‰', + 'Fired' => 'オン', + 'Fired, Auto' => 'オンã€ã‚ªãƒ¼ãƒˆ', + 'Fired, Auto, Red-eye reduction' => 'オンã€ã‚ªãƒ¼ãƒˆã€èµ¤ç›®è»½æ¸›', + 'Fired, Red-eye reduction' => 'オンã€èµ¤ç›®è»½æ¸›', + 'Fired, Slow-sync' => 'オンã€ã‚¹ãƒ­ãƒ¼ã‚·ãƒ³ã‚¯ãƒ­', + 'Fired, Slow-sync, Red-eye reduction' => 'オンã€ã‚¹ãƒ­ãƒ¼ã‚·ãƒ³ã‚¯ãƒ­ã€èµ¤ç›®è»½æ¸›', + 'Fired, Trailing-curtain Sync' => 'オンã€å¾Œå¹•シンクロ', + 'Fired, Wireless (Control)' => 'オンã€ãƒ¯ã‚¤ãƒ¤ãƒ¬ã‚¹ï¼ˆã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«ç™ºå…‰ï¼‰', + 'Fired, Wireless (Master)' => 'オンã€ãƒ¯ã‚¤ãƒ¤ãƒ¬ã‚¹ï¼ˆãƒžã‚¹ã‚¿ãƒ¼ç™ºå…‰ï¼‰', + 'n/a - Off-Auto-Aperture' => '該当ãªã—-自動絞りオフ', + }, + }, + 'InternalFlashStrength' => '内蔵フラッシュ強度', + 'InternalFlashTable' => '内蔵フラッシュテーブル', + 'InternalSerialNumber' => '内部シリアル番å·', + 'InteropIndex' => { + Description => 'インターオペラビリティID', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03: DCFオプションファイル(Adobe RGB)', + 'R98 - DCF basic file (sRGB)' => 'R98: DCF基本ファイル(sRGB)', + 'THM - DCF thumbnail file' => 'THM: DCFサムãƒã‚¤ãƒ«ãƒ•ァイル', + }, + }, + 'InteropOffset' => 'äº’æ›æ€§IFDã¸ã®ãƒã‚¤ãƒ³ã‚¿', + 'InteropVersion' => 'インターオペラビリティãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'IntervalLength' => 'インターãƒãƒ«é•·', + 'IntervalMode' => 'インターãƒãƒ«ãƒ¢ãƒ¼ãƒ‰', + 'IntervalNumber' => 'インターãƒãƒ«æ•°', + 'JFIFVersion' => 'JFIFãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'JPEGACTables' => 'JPEG AC テーブル', + 'JPEGDCTables' => 'JPEG DC テーブル', + 'JPEGLosslessPredictors' => 'JPEGロスレス予測', + 'JPEGPointTransforms' => 'JPEGä½ç½®å¤‰æ›', + 'JPEGProc' => 'JPEG処ç†', + 'JPEGQTables' => 'JPEG Q テーブル', + 'JPEGQuality' => { + Description => 'JPEG å“質', + PrintConv => { + 'Extra Fine' => 'エクストラファイン', + 'Fine' => 'ファイン', + 'Standard' => 'ノーマル', + 'n/a' => '未設定', + }, + }, + 'JPEGRestartInterval' => 'JPEGå†é–‹é–“éš”', + 'JPEGTables' => 'JPEGテーブル', + 'JobID' => 'ジョブID', + 'JpgRecordedPixels' => 'JPEG記録サイズ', + 'Keyword' => 'キーワード', + 'Keywords' => 'キーワード', + 'LC1' => 'レンズデータ', + 'LC10' => 'Mv\' nv\' データ', + 'LC11' => 'AVC 1/EXP データ', + 'LC12' => 'Mv1 Avminsifデータ', + 'LC14' => 'UNT_12 UNT_6 データ', + 'LC15' => 'çµ±åˆãƒ•ラッシュ最é©ã‚¨ãƒ³ãƒ‰ãƒ‡ãƒ¼ã‚¿', + 'LC2' => 'è·é›¢ã‚³ãƒ¼ãƒ‰ãƒ‡ãƒ¼ã‚¿', + 'LC3' => 'K値(', + 'LC4' => 'è¿‘è·é›¢åŽå·®è¨‚正データ', + 'LC5' => '明色åŽå·®è¨‚正データ', + 'LC6' => 'オープンåŽå·®ãƒ‡ãƒ¼ã‚¿', + 'LC7' => 'AF最低作動状態データ(LC7)', + 'LCDDisplayAtPowerOn' => { + Description => 'é›»æºã‚¹ã‚¤ãƒƒãƒã€ˆONã€‰æ™‚ã®æ¶²æ™¶ç‚¹ç¯', + PrintConv => { + 'Display' => '点ç¯', + 'Retain power off status' => 'é›»æºã€ˆOFF〉時ã®çŠ¶æ…‹ã‚’ä¿æŒ', + }, + }, + 'LCDDisplayReturnToShoot' => { + Description => 'æ¶²æ™¶ãƒ¢ãƒ‹ã‚¿ãƒ¼è¡¨ç¤ºä¸­ã®æ’®å½±çŠ¶æ…‹å¾©å¸°', + PrintConv => { + 'Also with * etc.' => '*ボタンãªã©ã§ã‚‚復帰', + 'With Shutter Button only' => 'シャッターボタンã§ã®ã¿å¾©å¸°', + }, + }, + 'LCDIllumination' => { + Description => 'LCDイルミãƒãƒ¼ã‚·ãƒ§ãƒ³', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'LCDIlluminationDuringBulb' => { + Description => 'ãƒãƒ«ãƒ–撮影中ã®è¡¨ç¤ºãƒ‘ãƒãƒ«ç…§æ˜Ž', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'LCDPanels' => '上é¢è¡¨ç¤ºãƒ‘ãƒãƒ«ï¼èƒŒé¢è¡¨ç¤ºãƒ‘ãƒãƒ«', + 'LCHEditor' => { + Description => 'LCHエディター', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'LanguageIdentifier' => '言語識別å­', + 'LastFileNumber' => '最終ファイル番å·', + 'LeafData' => 'リーフデータ', + 'Lens' => 'レンズ', + 'Lens35efl' => 'レンズ', + 'LensAFStopButton' => { + Description => 'レンズ・AFã‚¹ãƒˆãƒƒãƒ—ãƒœã‚¿ãƒ³ã®æ©Ÿèƒ½', + PrintConv => { + 'AE lock' => 'AEロック', + 'AE lock while metering' => 'AEロック(タイマー中)', + 'AF Stop' => 'AFストップ', + 'AF point: M->Auto/Auto->ctr' => '測è·ç‚¹ ä»»æ„→自動/自動→中央', + 'AF start' => 'AFスタート', + 'AF stop' => 'AFストップ', + 'IS start' => '手ブレ補正機能作動', + 'One Shot <-> AI servo' => 'ワンショット/AIサーボ', + 'Switch to registered AF point' => '登録AFフレームã¸ã®åˆ‡ã‚Šæ›ãˆ', + }, + }, + 'LensApertureRange' => 'レンズ絞り範囲', + 'LensData' => 'K値(LC3)', + 'LensDistortionParams' => 'レンズ歪曲パラメータ', + 'LensDriveNoAF' => { + Description => 'AF測è·ä¸èƒ½æ™‚ã®ãƒ¬ãƒ³ã‚ºå‹•作', + PrintConv => { + 'Focus search off' => '駆動ã—ãªã„', + 'Focus search on' => 'サーãƒé§†å‹•ã™ã‚‹', + }, + }, + 'LensFStops' => 'レンズF値', + 'LensFirmwareVersion' => 'レンズファームウェアãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'LensID' => 'レンズID', + 'LensInfo' => 'レンズ情報', + 'LensKind' => 'レンズ種類/ãƒãƒ¼ã‚¸ãƒ§ãƒ³(LC0)', + 'LensProperties' => 'レンズ機能?', + 'LensSerialNumber' => 'レンズシリアル番å·', + 'LensSpec' => 'レンズ', + 'LensTemperature' => 'レンズ温度', + 'LensType' => 'レンズタイプ', + 'LicenseType' => { + PrintConv => { + 'Unknown' => '䏿˜Ž', + }, + }, + 'LightCondition' => 'ライトコンディション', + 'LightReading' => 'ライトリーディング', + 'LightSource' => { + Description => 'å…‰æº', + PrintConv => { + 'Cloudy' => '曇り', + 'Cool White Fluorescent' => '白色è›å…‰ç¯', + 'Custom 1-4' => 'カスタム1-4', + 'Day White Fluorescent' => '昼白色è›å…‰ç¯', + 'Daylight' => '昼光', + 'Daylight Fluorescent' => '昼光色è›å…‰ç¯', + 'Fine Weather' => '良ã„天気', + 'Flash' => 'ストロボ', + 'Fluorescent' => 'è›å…‰ç¯', + 'ISO Studio Tungsten' => 'ISOスタジオタングステン', + 'Other' => 'ãã®ä»–ã®å…‰æº', + 'Shade' => '日陰', + 'Standard Light A' => '標準ライトA', + 'Standard Light B' => '標準ライトB', + 'Standard Light C' => '標準ライトC', + 'Tungsten (Incandescent)' => 'タングステン(白熱ç¯)', + 'Unknown' => '䏿˜Ž', + 'Warm White Fluorescent' => '暖白光色è›å…‰ç¯', + 'White Fluorescent' => '温白色è›å…‰ç¯', + }, + }, + 'LightSourceSpecial' => { + Description => 'å…‰æºã‚¹ãƒšã‚·ãƒ£ãƒ«', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Lightness' => '明度', + 'LinearResponseLimit' => 'ç·šåž‹å応é™ç•Œ', + 'LinearizationTable' => '線形化テーブル', + 'Lit' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'LiveViewExposureSimulation' => { + Description => 'ライブビュー露出シミュレーション', + PrintConv => { + 'Disable (LCD auto adjust)' => 'ã—ãªã„(驿­£è¡¨ç¤º)', + 'Enable (simulates exposure)' => 'ã™ã‚‹(撮影露出イメージ表示)', + }, + }, + 'LiveViewShooting' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'LocalizedCameraModel' => 'é™å®šã‚«ãƒ¡ãƒ©ãƒ¢ãƒ‡ãƒ«', + 'Location' => '撮影場所', + 'LockMicrophoneButton' => { + Description => 'プロテクト/録音ボタン ã‚¿ãƒ³ã®æ©Ÿèƒ½', + PrintConv => { + 'Protect (hold:record memo)' => 'プロテクト(長押ã—ã§éŒ²éŸ³)', + 'Record memo (protect:disable)' => '録音(プロテクトä¸å¯)', + }, + }, + 'LongExposureNoiseReduction' => { + Description => '長秒露光ノイズリダクション', + PrintConv => { + 'Auto' => 'オート', + 'Off' => 'オフ', + 'On' => 'オン', + 'n/a' => '未設定', + }, + }, + 'LookupTable' => 'ルックアップテーブル', + 'LoopStyle' => { + PrintConv => { + 'Normal' => '標準', + }, + }, + 'Luminance' => 'è¼åº¦', + 'LuminanceNoiseReduction' => { + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + 'Off' => 'オフ', + }, + }, + 'MB-D10Batteries' => 'MB-D10é›»æºã‚¿ã‚¤ãƒ—', + 'MB-D10BatteryType' => 'MB-D10é›»æºã‚¿ã‚¤ãƒ—', + 'MB-D80Batteries' => 'MB-D80ãƒãƒƒãƒ†ãƒªãƒ¼', + 'MIEVersion' => 'MIEãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'MIMEType' => 'MIMEタイプ', + 'MSStereo' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Macro' => { + Description => 'マクロ', + PrintConv => { + 'Macro' => 'マクロ', + 'Manual' => 'マニュアル', + 'Normal' => '標準', + 'Off' => 'オフ', + 'On' => 'オン', + 'Super Macro' => 'スーパーマクロ', + 'n/a' => '未設定', + }, + }, + 'MacroMode' => { + Description => 'マクロモード', + PrintConv => { + 'Macro' => 'マクロ', + 'Normal' => 'ノーマル', + 'Off' => 'オフ', + 'On' => 'オン', + 'Super Macro' => 'スーパーマクロ', + 'Tele-Macro' => 'テレマクロ', + }, + }, + 'MagnifiedView' => { + Description => '拡大ズーム表示', + PrintConv => { + 'Image playback only' => 'å†ç”Ÿæ™‚ã®ã¿', + 'Image review and playback' => '撮影直後ã¨å†ç”Ÿæ™‚', + }, + }, + 'MainDialExposureComp' => { + Description => 'Main Dial 露出補正', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Make' => 'メーカー', + 'MakeAndModel' => '作æˆã¨ãƒ¢ãƒ‡ãƒ«', + 'MakerNote' => 'DNGプライベートデータ', + 'MakerNoteOffset' => 'メーカーノートオフセット', + 'MakerNoteSafety' => { + Description => 'メーカーノートセーフティ', + PrintConv => { + 'Safe' => '安全', + 'Unsafe' => 'å±é™º', + }, + }, + 'MakerNoteType' => 'メーカーノートタイプ', + 'MakerNoteVersion' => 'メーカーノートãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'MakerNotes' => 'メーカーノート', + 'ManometerPressure' => '気圧計圧力', + 'ManometerReading' => '気圧計高度', + 'ManualFlash' => 'マニュアルフラッシュ', + 'ManualFlashOutput' => { + Description => 'マニュアルフラッシュ出力', + PrintConv => { + 'Low' => 'ソフト', + 'n/a' => '該当無ã—', + }, + }, + 'ManualFocusDistance' => 'マニュアルフォーカスè·é›¢', + 'ManualTv' => { + Description => 'マニュアル露出時Tvã€Av値設定', + PrintConv => { + 'Tv=Control/Av=Main' => 'Tv値=サブ電å­ãƒ€ã‚¤ãƒ¤ãƒ«/Av値=メイン電å­ãƒ€ã‚¤ãƒ¤ãƒ«', + 'Tv=Main/Av=Control' => 'Tv値=メイン電å­ãƒ€ã‚¤ãƒ¤ãƒ«/Av値=サブ電å­ãƒ€ã‚¤ãƒ¤ãƒ«', + }, + }, + 'ManufactureDate' => '製造日付?', + 'MaskedAreas' => 'マスク領域', + 'MasterDocumentID' => 'マスタ文書ID', + 'MasterGain' => 'マスターゲイン', + 'MatrixMetering' => '分割測光', + 'Matteing' => 'マッãƒãƒ³ã‚°', + 'MaxAperture' => '最大絞り', + 'MaxApertureAtCurrentFocal' => 'ç¾åœ¨ç„¦ç‚¹è·é›¢ã®æœ€å¤§çµžã‚Š', + 'MaxApertureAtMaxFocal' => '最大焦点時最大絞り', + 'MaxApertureAtMinFocal' => '最å°ç„¦ç‚¹æ™‚最大絞り', + 'MaxApertureValue' => '最大レンズå£å¾„', + 'MaxContinuousRelease' => '最大連写レリーズ', + 'MaxFocalLength' => '最大焦点è·é›¢', + 'MaxSampleValue' => '最大サンプル値', + 'MaximumDensityRange' => '最大密度範囲', + 'MeasuredEV' => '計測EV', + 'Measurement' => '測定オブザーãƒãƒ¼', + 'MeasurementBacking' => 'ãƒãƒƒã‚¯æ¸¬å®š', + 'MeasurementFlare' => 'フレア測定', + 'MeasurementGeometry' => '幾何学測定', + 'MeasurementIlluminant' => 'å…‰æºæ¸¬å®š', + 'MeasurementObserver' => '測定オブザーãƒãƒ¼', + 'MediaBlackPoint' => 'メディア黒点', + 'MediaType' => { + PrintConv => { + 'Movie' => 'å‹•ç”»', + 'Normal' => '標準', + }, + }, + 'MediaWhitePoint' => 'メディア白点', + 'Medium' => 'ミドル', + 'MenuButtonDisplayPosition' => { + Description => 'メニューã®è¡¨ç¤ºä½ç½®', + PrintConv => { + 'Previous' => 'ç›´å‰ã®ãƒ¡ãƒ‹ãƒ¥ãƒ¼', + 'Previous (top if power off)' => 'ç›´å‰ã®ãƒ¡ãƒ‹ãƒ¥ãƒ¼ï¼ˆé›»æºåˆ‡ã§å…ˆé ­ï¼‰', + 'Top' => 'メニューã®å…ˆé ­', + }, + }, + 'MenuButtonReturn' => { + PrintConv => { + 'Previous' => 'ç›´å‰ã®ãƒ¡ãƒ‹ãƒ¥ãƒ¼', + 'Top' => '上', + }, + }, + 'Metering' => { + Description => '測光', + PrintConv => { + 'Center-weighted' => '中央é‡ç‚¹', + 'Matrix' => '分割', + 'Spot' => 'スãƒãƒƒãƒˆ', + }, + }, + 'MeteringMode' => { + Description => '測光モード', + PrintConv => { + 'Average' => 'å¹³å‡', + 'Center-weighted average' => '中央é‡ç‚¹', + 'Default' => 'デフォルト', + 'Evaluative' => '評価', + 'Multi-segment' => 'パターン', + 'Multi-spot' => 'マルãƒã‚¹ãƒãƒƒãƒˆ', + 'Other' => 'ãã®ä»–', + 'Partial' => '部分', + 'Pattern+AF' => 'パターン+AF', + 'Spot' => 'スãƒãƒƒãƒˆ', + 'Spot+Highlight control' => 'スãƒãƒƒãƒˆ+ãƒã‚¤ãƒ©ã‚¤ãƒˆã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«', + 'Spot+Shadow control' => 'スãƒãƒƒãƒˆ+シャドウコントロール', + 'Unknown' => '䏿˜Ž', + }, + }, + 'MeteringMode2' => { + Description => '測光モード2', + PrintConv => { + 'Multi-segment' => 'パターン', + }, + }, + 'MeteringMode3' => { + Description => '測光モード3', + PrintConv => { + 'Multi-segment' => 'パターン', + }, + }, + 'MeteringTime' => '自動オフタイマー メータオフ時間', + 'MinAperture' => '最å°çµžã‚Š', + 'MinFocalLength' => '最å°ç„¦ç‚¹è·é›¢', + 'MinSampleValue' => '最å°ã‚µãƒ³ãƒ—ル値', + 'MinoltaCameraSettings2' => 'カメラ設定2', + 'MinoltaCameraSettings5D' => 'カメラ設定(5D)', + 'MinoltaCameraSettings7D' => 'カメラ設定(7D)', + 'MinoltaDate' => '日付', + 'MinoltaImageSize' => { + Description => 'イメージサイズ', + PrintConv => { + 'Full' => 'フル', + 'Large' => 'ラージ', + 'Medium' => 'ミドル', + 'Small' => 'スモール', + }, + }, + 'MinoltaMakerNote' => 'ミノルタメーカーノート', + 'MinoltaModelID' => 'モデルID', + 'MinoltaQuality' => { + Description => 'イメージå“質', + PrintConv => { + 'Economy' => 'エコノミー', + 'Extra Fine' => 'エクストラファイン', + 'Extra fine' => 'エクストラファイン', + 'Fine' => 'ファイン', + 'Normal' => '標準', + 'Standard' => 'スタンダード', + 'Super Fine' => 'スーパーファイン', + }, + }, + 'MinoltaTime' => '時間', + 'MirrorLockup' => { + Description => 'ミラーアップ撮影', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + 'Enable: Down with Set' => 'ã™ã‚‹(SETボタンã§ãƒ€ã‚¦ãƒ³)', + }, + }, + 'Model' => 'ç”»åƒå…¥åŠ›æ©Ÿå™¨ãƒ¢ãƒ‡ãƒ«', + 'Model2' => 'ç”»åƒå…¥åŠ›æ©Ÿå™¨ãƒ¢ãƒ‡ãƒ«(2)', + 'ModelID' => 'モデルID', + 'ModelTiePoint' => 'モデル拘æŸãƒã‚¤ãƒ³ãƒˆã‚¿ã‚°', + 'ModelTransform' => 'モデル変化タグ', + 'ModelingFlash' => { + Description => 'モデリングフラッシュ', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ModifiedPictureStyle' => { + PrintConv => { + 'CM Set 1' => 'CMセット1', + 'CM Set 2' => 'CMセット2', + 'Faithful' => '忠実設定', + 'High Saturation' => '高彩度', + 'Landscape' => '風景', + 'Low Saturation' => '低彩度', + 'Monochrome' => 'モノトーン', + 'Neutral' => 'ニュートラル', + 'None' => 'ç„¡ã—', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Standard' => 'スタンダード', + 'User Def. 1' => 'ユーザ設定1', + 'User Def. 2' => 'ユーザ設定2', + 'User Def. 3' => 'ユーザ設定3', + }, + }, + 'ModifiedSaturation' => { + Description => '彩度修正', + PrintConv => { + 'CM1 (Red Enhance)' => 'CM1 (赤増)', + 'CM2 (Green Enhance)' => 'CM2 (緑増)', + 'CM3 (Blue Enhance)' => 'CM3 (é’増)', + 'CM4 (Skin Tones)' => 'CM4 (肌色増)', + 'Off' => 'オフ', + }, + }, + 'ModifiedSharpnessFreq' => { + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + 'Standard' => 'スタンダード', + 'n/a' => '該当無ã—', + }, + }, + 'ModifiedToneCurve' => { + PrintConv => { + 'Custom' => 'カスタム', + 'Manual' => 'マニュアル', + 'Standard' => 'スタンダード', + }, + }, + 'ModifiedWhiteBalance' => { + PrintConv => { + 'Auto' => 'オート', + 'Black & White' => '白黒', + 'Cloudy' => '曇り', + 'Custom' => 'カスタム', + 'Custom 1' => 'カスタム1', + 'Custom 2' => 'カスタム2', + 'Custom 3' => 'カスタム3', + 'Custom 4' => 'カスタム4', + 'Daylight' => '昼光', + 'Daylight Fluorescent' => '昼光色è›å…‰ç¯', + 'Flash' => 'ストロボ', + 'Fluorescent' => 'è›å…‰ç¯', + 'Manual Temperature (Kelvin)' => 'マニュアル白熱ç¯ï¼ˆã‚±ãƒ«ãƒ“ン)', + 'PC Set1' => 'PC設定1', + 'PC Set2' => 'PC設定2', + 'PC Set3' => 'PC設定3', + 'PC Set4' => 'PC設定4', + 'PC Set5' => 'PC設定5', + 'Shade' => '日陰', + 'Tungsten' => 'タングステン(白熱ç¯)', + 'Underwater' => '水中', + }, + }, + 'ModifyDate' => 'ãƒ•ã‚¡ã‚¤ãƒ«ä½œæˆæ—¥æ™‚', + 'MoireFilter' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'MonitorOffTime' => 'モニターオフé…延時間', + 'MonochromeFilterEffect' => { + PrintConv => { + 'Green' => 'ç·‘', + 'None' => 'ç„¡ã—', + 'Orange' => 'オレンジ', + 'Red' => '赤', + 'Yellow' => '黄色', + }, + }, + 'MonochromeLinear' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'MonochromeToningEffect' => { + PrintConv => { + 'Blue' => 'é’', + 'Green' => 'ç·‘', + 'None' => 'ç„¡ã—', + 'Purple' => 'ç´«', + 'Sepia' => 'セピア', + }, + }, + 'MultiExposure' => '多é‡éœ²å‡ºãƒ‡ãƒ¼ã‚¿', + 'MultiExposureAutoGain' => { + Description => '多é‡éœ²å‡ºè‡ªå‹•ゲイン', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'MultiExposureMode' => { + Description => '多é‡éœ²å‡ºãƒ¢ãƒ¼ãƒ‰', + PrintConv => { + 'Image Overlay' => 'イメージオーãƒãƒ¼ãƒ¬ã‚¤', + 'Multiple Exposure' => '多é‡éœ²å‡º', + 'Off' => 'オフ', + }, + }, + 'MultiExposureShots' => '多é‡éœ²å‡ºã‚·ãƒ§ãƒƒãƒˆ', + 'MultiExposureVersion' => '多é‡éœ²å‡ºãƒ‡ãƒ¼ã‚¿ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'MultiFrameNoiseReduction' => { + Description => 'マルãƒã‚·ãƒ§ãƒƒãƒˆãƒŽã‚¤ã‚ºãƒªãƒ€ã‚¯ã‚·ãƒ§ãƒ³', + PrintConv => { + 'Off' => '切', + 'On' => 'å…¥', + }, + }, + 'MultiSample' => 'マルãƒã‚µãƒ³ãƒ—ル', + 'MultiSelector' => { + Description => 'マルãƒé¸æŠž', + PrintConv => { + 'Do Nothing' => '何もã—ãªã„', + 'Reset Meter-off Delay' => 'メーターオフé…延時間リセット', + }, + }, + 'MultiSelectorPlaybackMode' => { + Description => 'マルãƒé¸æŠž å†ç”Ÿãƒ¢ãƒ¼ãƒ‰', + PrintConv => { + 'Choose Folder' => 'ãƒ•ã‚©ãƒ«ãƒ€ãƒ¼é¸æŠž', + 'Thumbnail On/Off' => 'サムãƒã‚¤ãƒ«ã€€ã‚ªãƒ³/オフ', + 'View Histograms' => 'ヒストグラム表示', + 'Zoom On/Off' => 'ズーム オン/オフ', + }, + }, + 'MultiSelectorShootMode' => { + Description => 'マルãƒé¸æŠž 撮影モード', + PrintConv => { + 'Highlight Active Focus Point' => 'ãƒã‚¤ãƒ©ã‚¤ãƒˆã‚¢ã‚¯ãƒ†ã‚£ãƒ–フォーカスãƒã‚¤ãƒ³ãƒˆ', + 'Not Used' => '未使用', + 'Select Center Focus Point' => '中央フォーカスãƒã‚¤ãƒ³ãƒˆé¸æŠž', + }, + }, + 'MultipleExposureSet' => { + Description => '多é‡éœ²å‡ºè¨­å®š', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Mute' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'MyColorMode' => { + Description => 'マイカラーモード', + PrintConv => { + 'B&W' => '白黒', + 'Color Accent' => 'カラーアクセント', + 'Color Swap' => 'スイッãƒã‚«ãƒ©ãƒ¼', + 'Custom' => 'カスタム', + 'Dark Skin Tone' => 'ダークスキン調', + 'Light Skin Tone' => 'ライトスカイ調', + 'Neutral' => 'ニュートラル', + 'Off' => 'オフ', + 'Positive Film' => 'ãƒã‚¸ãƒ•ィルム', + 'Sepia' => 'セピア', + 'Vivid' => 'ビビッド', + 'Vivid Blue' => 'ビビッドé’', + 'Vivid Green' => 'ビビッド緑', + 'Vivid Red' => 'ビビッド赤', + }, + }, + 'MyColors' => 'マイカラーモード', + 'NDFilter' => { + Description => 'NDフィルター', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'NEFCompression' => { + Description => 'RAW圧縮', + PrintConv => { + 'Lossless' => 'ロスレス', + 'Lossy (type 1)' => '圧縮(タイプ1)', + 'Lossy (type 2)' => '圧縮(タイプ2)', + 'Uncompressed' => 'éžåœ§ç¸®', + }, + }, + 'NEFLinearizationTable' => '線形化表', + 'NamedColor2' => '色åç§°2', + 'NativeDisplayInfo' => 'ãƒã‚¤ãƒ†ã‚£ãƒ–ディスプレイ情報', + 'NewsPhotoVersion' => 'å ±é“写真レコードãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'NikonCaptureData' => 'ニコンキャプãƒãƒ£ãƒ¼ãƒ‡ãƒ¼ã‚¿', + 'NikonCaptureOffsets' => 'ニコンキャプãƒãƒ£ãƒ¼ã‚ªãƒ•セット', + 'NikonCaptureVersion' => 'ニコンキャプãƒãƒ£ãƒ¼ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'NoMemoryCard' => { + Description => 'メモリーカード無ã—', + PrintConv => { + 'Enable Release' => 'レリーズå¯èƒ½', + 'Release Locked' => 'レリーズロック', + }, + }, + 'Noise' => 'ノイズ', + 'NoiseFilter' => { + Description => 'ピクãƒãƒ£ãƒ¼ãƒ¢ãƒ¼ãƒ‰ãƒŽã‚¤ã‚ºãƒ•ィルター', + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + 'Off' => 'オフ', + 'Standard' => 'スタンダード', + }, + }, + 'NoiseReduction' => { + Description => 'ノイズリダクション', + PrintConv => { + 'Auto' => 'オート', + 'Low' => '低ã„', + 'Normal' => '標準', + 'Off' => 'オフ', + 'On' => 'オン', + 'Standard' => 'スタンダード', + }, + }, + 'NoiseReduction2' => 'ノイズリダクション2', + 'NoiseReductionApplied' => 'é©ç”¨ãƒŽã‚¤ã‚ºãƒªãƒ€ã‚¯ã‚·ãƒ§ãƒ³', + 'NoiseReductionData' => 'ノイズリダクションデータ', + 'NoiseReductionIntensity' => 'ノイズリダクション強度', + 'NoiseReductionMethod' => 'ノイズリダクション方法', + 'NoiseReductionSharpness' => 'ノイズリダクションシャープãƒã‚¹', + 'NominalMaxAperture' => '最大絞り', + 'NominalMinAperture' => '最å°çµžã‚Š', + 'NumAFPoints' => 'AFãƒã‚¤ãƒ³ãƒˆç•ªå·', + 'NumIndexEntries' => 'インデックスエントリ数', + 'NumberofInks' => 'インク番å·', + 'OPIProxy' => 'OPIプロキシー', + 'ObjectAttributeReference' => 'インテリジャンル', + 'ObjectCycle' => 'オブジェクトサイクル', + 'ObjectDistance' => '被写体ã¨ã®è·é›¢', + 'ObjectFileType' => { + PrintConv => { + 'None' => 'ç„¡ã—', + 'Unknown' => '䏿˜Ž', + }, + }, + 'ObjectName' => 'タイトル', + 'ObjectPreviewData' => 'オブジェクトデータプレビューデータ', + 'ObjectPreviewFileFormat' => 'オブジェクトデータプレビューファイル形å¼', + 'ObjectPreviewFileVersion' => 'オブジェクトデータプレビューファイル形å¼ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'ObjectTypeReference' => 'オブジェクトタイプå‚ç…§', + 'OffsetSchema' => 'ã‚ªãƒ•ã‚»ãƒƒãƒˆã®æ¦‚è¦', + 'OlympusImageHeight' => 'イメージ高', + 'OlympusImageWidth' => 'イメージ幅', + 'OneTouchWB' => { + Description => 'ワンタッãƒãƒ›ãƒ¯ã‚¤ãƒˆãƒãƒ©ãƒ³ã‚¹', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + 'On (Preset)' => 'オン(プリセット)', + }, + }, + 'OpticalZoomCode' => '光学ズームコード', + 'OpticalZoomMode' => { + Description => '光学ズームモード', + PrintConv => { + 'Extended' => 'EX光学', + 'Standard' => 'スタンダード', + }, + }, + 'OpticalZoomOn' => { + Description => '光学ズームオン', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Opto-ElectricConvFactor' => '光電交æ›é–¢æ•°', + 'OrderNumber' => 'オーダー番å·', + 'Orientation' => { + Description => 'ç”»åƒã®å‘ã', + PrintConv => { + 'Horizontal (normal)' => '水平(標準)', + 'Rotate 180' => '180度回転', + 'Rotate 270 CW' => '270度回転 CW', + 'Rotate 90 CW' => '90度回転 CW', + }, + }, + 'OriginalDecisionDataOffset' => 'オリジナル決定データオフセット', + 'OriginalRawFileData' => 'オリジナルRAWファイルデータ', + 'OriginalRawFileDigest' => 'オリジナルRAWファイルè¦ç´„', + 'OriginalRawFileName' => 'オリジナルRAWファイルå', + 'OriginalTransmissionReference' => '作業識別å­', + 'OriginatingProgram' => '開始プログラム', + 'OutputResponse' => '出力å応', + 'OwnerID' => 'オーナーID', + 'OwnerName' => 'オーナーå', + 'PEFVersion' => 'PEFãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'Padding' => '引ã伸ã°ã—', + 'PageName' => 'ページå', + 'PageNumber' => 'ページ番å·', + 'PanasonicExifVersion' => 'パナソニックExifãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'PanasonicRawVersion' => 'パナソニックRAWãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'PanasonicTitle' => 'タイトル', + 'PanoramaDirection' => 'F値', + 'PanoramaMode' => 'パノラマモード', + 'PentaxImageSize' => 'ペンタックスイメージサイズ', + 'PentaxModelID' => 'ペンタックスモデル', + 'PentaxVersion' => 'ペンタックスãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'People' => '人々', + 'PhaseDetectAF' => { + Description => 'オートフォーカス', + PrintConv => { + 'Off' => 'オフ', + 'On (51-point)' => 'オン', + }, + }, + 'PhotoEffect' => { + Description => '写真効果', + PrintConv => { + 'B&W' => '白黒', + 'Custom' => 'カスタム', + 'Neutral' => 'ニュートラル', + 'Off' => 'オフ', + 'Sepia' => 'セピア', + 'Vivid' => 'ビビッド', + }, + }, + 'PhotoEffects' => { + Description => 'フォトエフェクト', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'PhotoEffectsBlue' => 'フォトエフェクトé’', + 'PhotoEffectsData' => 'フォトエフェクトデータ', + 'PhotoEffectsGreen' => 'フォトエフェクト緑', + 'PhotoEffectsRed' => 'フォトエフェクト赤', + 'PhotoEffectsType' => { + Description => 'フォトエフェクトタイプ', + PrintConv => { + 'B&W' => '白黒', + 'None' => 'ç„¡ã—', + 'Sepia' => 'セピア', + 'Tinted' => '淡調', + }, + }, + 'PhotoInfoPlayback' => { + Description => '写真情報/å†ç”Ÿ', + PrintConv => { + 'Info Left-right, Playback Up-down' => '情報<>/å†ç”Ÿ', + 'Info Up-down, Playback Left-right' => '情報/å†ç”Ÿ<>', + }, + }, + 'PhotometricInterpretation' => { + Description => 'ピクセル形å¼', + PrintConv => { + 'BlackIsZero' => 'é»’ã¯ã‚¼ãƒ­', + 'Color Filter Array' => 'CFA (カラーフィルターマトリックス)', + 'Pixar LogL' => 'CIE Log2(L) (ログè¼åº¦)', + 'Pixar LogLuv' => 'CIE Log2(L)(u\',v\') (ログè¼åº¦ã¨åŸºæº–色)', + 'RGB Palette' => 'パレット色', + 'Transparency Mask' => '逿˜Žåº¦ãƒžã‚¹ã‚¯', + 'WhiteIsZero' => '白ã¯ã‚¼ãƒ­', + }, + }, + 'PhotoshopAnnotations' => 'フォトショップ注釈', + 'PhotoshopFormat' => { + PrintConv => { + 'Standard' => 'スタンダード', + }, + }, + 'PictInfo' => '写真情報', + 'PictureControl' => { + Description => 'ピクãƒãƒ£ãƒ¼ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'PictureControlActive' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'PictureControlAdjust' => { + Description => 'ピクãƒãƒ£ãƒ¼ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«èª¿æ•´', + PrintConv => { + 'Default Settings' => 'デフォルト設定', + 'Full Control' => 'フルコントロール', + 'Quick Adjust' => 'クイック調整', + }, + }, + 'PictureControlBase' => 'ピクãƒãƒ£ãƒ¼ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«ãƒ™ãƒ¼ã‚¹', + 'PictureControlName' => 'ピクãƒãƒ£ãƒ¼ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«å', + 'PictureControlQuickAdjust' => 'ピクãƒãƒ£ãƒ¼ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«ã‚¯ã‚¤ãƒƒã‚¯èª¿æ•´', + 'PictureControlVersion' => 'ピクãƒãƒ£ãƒ¼ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'PictureFinish' => { + Description => 'ピクãƒãƒ£ãƒ¼ãƒ•ィニッシュ', + PrintConv => { + 'Monochrome' => 'モノトーン', + 'Natural' => 'ナãƒãƒ¥ãƒ©ãƒ«', + 'Night Portrait' => '人物夜景', + 'Night Scene' => '夜景', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + }, + }, + 'PictureMode' => { + Description => 'ピクãƒãƒ£ãƒ¼ãƒ¢ãƒ¼ãƒ‰', + PrintConv => { + '1/2 EV steps' => '1/2 EVステップ', + '1/3 EV steps' => '1/3 EVステップ', + 'Anti-blur' => '手振れ補正', + 'Aperture Priority' => '絞り優先', + 'Aperture Priority, Off-Auto-Aperture' => '絞り優先(自動絞りOFF)', + 'Aperture-priority AE' => '絞り優先', + 'Auto' => 'オート', + 'Auto PICT (Landscape)' => 'オートピクãƒãƒ£ãƒ¼ï¼ˆé¢¨æ™¯ï¼‰', + 'Auto PICT (Macro)' => 'オートピクãƒãƒ£ãƒ¼ï¼ˆãƒžã‚¯ãƒ­ï¼‰', + 'Auto PICT (Portrait)' => 'オートピクãƒãƒ£ãƒ¼ï¼ˆãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆï¼‰', + 'Auto PICT (Sport)' => 'オートピクãƒãƒ£ãƒ¼ï¼ˆã‚¹ãƒãƒ¼ãƒ„)', + 'Auto PICT (Standard)' => 'オートピクãƒãƒ£ãƒ¼ï¼ˆæ¨™æº–)', + 'Autumn' => 'ç§‹', + 'Beach' => 'ビーãƒ', + 'Beach & Snow' => 'ビーãƒï¼†ã‚¹ãƒŽãƒ¼', + 'Blur Reduction' => 'Digital SR', + 'Bulb' => 'ãƒãƒ«ãƒ–', + 'Bulb, Off-Auto-Aperture' => 'ãƒãƒ«ãƒ–(自動絞りOFF)', + 'Candlelight' => 'キャンドルライト', + 'DOF Program' => '深度優先プログラム', + 'DOF Program (HyP)' => '深度優先プログラム(ãƒã‚¤ãƒ‘ープログラム)', + 'Dark Pet' => 'ペット黒色', + 'Digital Filter' => 'デジタルフィルター', + 'Fireworks' => '花ç«', + 'Flash X-Sync Speed AE' => 'ストロボåŒèª¿é€Ÿåº¦AE', + 'Flower' => '花', + 'Food' => 'æ–™ç†', + 'Frame Composite' => 'ãƒ•ãƒ¬ãƒ¼ãƒ åˆæˆ', + 'Green Mode' => 'グリーンモード', + 'Hi-speed Program' => '高速優先プログラム', + 'Hi-speed Program (HyP)' => '高速優先プログラム(ãƒã‚¤ãƒ‘ープログラム)', + 'Illustrations' => 'イラスト', + 'Kids' => 'キッズ', + 'Landscape' => '風景', + 'Light Pet' => 'ペット白色', + 'MTF Program' => 'MTF優先プログラム', + 'MTF Program (HyP)' => 'MTF優先プログラム(ãƒã‚¤ãƒ‘ープログラム)', + 'Macro' => 'マクロ', + 'Manual' => 'マニュアル', + 'Manual, Off-Auto-Aperture' => 'マニュアル(自動絞りOFF)', + 'Medium Pet' => 'ペットç°è‰²', + 'Monotone' => 'モノトーン', + 'Museum' => '美術館', + 'Muted' => 'å¼±ã‚ã‚‹', + 'Natural' => 'ナãƒãƒ¥ãƒ©ãƒ«', + 'Natural Light' => 'ナãƒãƒ¥ãƒ©ãƒ«ãƒ•ォト', + 'Natural Light & Flash' => 'ナãƒãƒ¥ãƒ©ãƒ«ãƒ•ォト&フラッシュ', + 'Natural Skin Tone' => '美肌', + 'Night Scene' => '夜景', + 'Night Scene Portrait' => '人物ã€å¤œæ™¯', + 'No Flash' => 'フラッシュ無ã—', + 'Panorama' => 'パノラマ', + 'Party' => 'パーティ', + 'Pet' => 'ペット', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Program' => 'プログラム', + 'Program (HyP)' => 'プログラムAE(ãƒã‚¤ãƒ‘ープログラム)', + 'Program AE' => 'プログラムAE', + 'Program Av Shift' => 'プログラムAvシフト', + 'Program Tv Shift' => 'プログラムTvシフト', + 'Self Portrait' => '自分撮り', + 'Sensitivity Priority AE' => '感度優先AE', + 'Sepia' => 'セピア', + 'Shutter & Aperture Priority AE' => 'シャッター&絞り優先AE', + 'Shutter Speed Priority' => 'シャッター優先', + 'Shutter speed priority AE' => 'シャッター優先', + 'Snow' => 'スノー', + 'Soft' => 'ソフト', + 'Sport' => 'スãƒãƒ¼ãƒ„', + 'Sports' => 'スãƒãƒ¼ãƒ„', + 'Standard' => 'スタンダード', + 'Sunset' => '夕日', + 'Surf & Snow' => 'サーフ&スノー', + 'Synchro Sound Record' => 'ボイスレコーディング', + 'Text' => 'テキスト', + 'Underwater' => '水中', + 'Vivid' => 'ビビッド', + }, + }, + 'PictureMode2' => { + Description => 'ピクãƒãƒ£ãƒ¼ãƒ¢ãƒ¼ãƒ‰ 2', + PrintConv => { + 'Aperture Priority' => '絞り優先', + 'Aperture Priority, Off-Auto-Aperture' => '絞り優先(自動絞りオフ)', + 'Auto PICT' => 'オートピクãƒãƒ£', + 'Bulb' => 'ãƒãƒ«ãƒ–', + 'Bulb, Off-Auto-Aperture' => 'ãƒãƒ«ãƒ–(自動絞りオフ)', + 'Flash X-Sync Speed AE' => 'ストロボåŒèª¿é€Ÿåº¦AE', + 'Green Mode' => 'グリーンモード', + 'Manual' => 'マニュアル', + 'Manual, Off-Auto-Aperture' => 'マニュアル(自動絞りオフ)', + 'Program AE' => 'プログラムAE', + 'Program Av Shift' => 'プログラムAvシフト', + 'Program Tv Shift' => 'プログラムTvシフト', + 'Scene Mode' => 'シーンモード', + 'Sensitivity Priority AE' => '感度優先AE', + 'Shutter & Aperture Priority AE' => 'シャッター&絞り優先AE', + 'Shutter Speed Priority' => 'シャッター優先', + }, + }, + 'PictureModeBWFilter' => { + Description => 'ピクãƒãƒ£ãƒ¼ãƒ¢ãƒ¼ãƒ‰BWフィルター', + PrintConv => { + 'Green' => 'ç·‘', + 'Neutral' => 'ニュートラル', + 'Orange' => 'オレンジ', + 'Red' => '赤', + 'Yellow' => '黄色', + 'n/a' => '該当無ã—', + }, + }, + 'PictureModeContrast' => 'ピクãƒãƒ£ãƒ¼ãƒ¢ãƒ¼ãƒ‰ã‚³ãƒ³ãƒˆãƒ©ã‚¹ãƒˆ', + 'PictureModeHue' => 'ピクãƒãƒ£ãƒ¼ãƒ¢ãƒ¼ãƒ‰è‰²ç›¸ï¼Ÿ', + 'PictureModeSaturation' => 'ピクãƒãƒ£ãƒ¼ãƒ¢ãƒ¼ãƒ‰å½©åº¦', + 'PictureModeSharpness' => 'ピクãƒãƒ£ãƒ¼ãƒ¢ãƒ¼ãƒ‰ã‚·ãƒ£ãƒ¼ãƒ—ãƒã‚¹', + 'PictureModeTone' => { + Description => 'ピクãƒãƒ£ãƒ¼ãƒ¢ãƒ¼ãƒ‰ãƒˆãƒ¼ãƒ³', + PrintConv => { + 'Blue' => 'é’', + 'Green' => 'ç·‘', + 'Neutral' => 'ニュートラル', + 'Purple' => 'ç´«', + 'Sepia' => 'セピア', + 'n/a' => '該当無ã—', + }, + }, + 'PictureStyle' => { + Description => 'ピクãƒãƒ£ãƒ¼ã‚¹ã‚¿ã‚¤ãƒ«', + PrintConv => { + 'CM Set 1' => 'CMセット1', + 'CM Set 2' => 'CMセット2', + 'Faithful' => '忠実設定', + 'High Saturation' => '高彩度', + 'Landscape' => '風景', + 'Low Saturation' => '低彩度', + 'Monochrome' => 'モノトーン', + 'Neutral' => 'ニュートラル', + 'None' => 'ç„¡ã—', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Standard' => 'スタンダード', + 'User Def. 1' => 'ユーザ設定1', + 'User Def. 2' => 'ユーザ設定2', + 'User Def. 3' => 'ユーザ設定3', + }, + }, + 'PixelFormat' => { + Description => 'ピクセルフォーマット', + PrintConv => { + 'Black & White' => '白黒', + }, + }, + 'PixelIntensityRange' => 'ピクセル強度範囲', + 'PixelScale' => 'モデル画素スケールタグ', + 'PixelUnits' => { + PrintConv => { + 'Unknown' => '䏿˜Ž', + }, + }, + 'PlanarConfiguration' => { + Description => 'ç”»åƒãƒ‡ãƒ¼ã‚¿ã®ä¸¦ã³', + PrintConv => { + 'Chunky' => 'ç‚¹é †æ¬¡å½¢å¼ (é‡ã­åˆã‚ã›)', + 'Planar' => 'å¹³é¢å½¢å¼', + }, + }, + 'PowerSource' => { + Description => 'é›»æº', + PrintConv => { + 'Body Battery' => '本体電æº', + 'External Power Supply' => '外部電æº', + 'Grip Battery' => 'ãƒãƒƒãƒ†ãƒªãƒ¼ã‚°ãƒªãƒƒãƒ—', + }, + }, + 'PreCaptureFrames' => 'プレキャプãƒãƒ£ãƒ¼ãƒ•レーム', + 'Predictor' => '指標', + 'Preview' => 'プレビューIFDãƒã‚¤ãƒ³ã‚¿ãƒ¼', + 'Preview0' => 'プレビュー0', + 'Preview1' => 'プレビュー1', + 'Preview2' => 'プレビュー2', + 'PreviewApplicationName' => 'プレビューアプリケーションå', + 'PreviewApplicationVersion' => 'プレビューアプリケーションãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'PreviewColorSpace' => { + Description => 'プレビュー色空間', + PrintConv => { + 'Unknown' => '䏿˜Ž', + }, + }, + 'PreviewDateTime' => 'プレビュー日時', + 'PreviewIFD' => 'プレビューIFDãƒã‚¤ãƒ³ã‚¿ãƒ¼', + 'PreviewImage' => 'プレビューイメージ', + 'PreviewImageBorders' => 'プレビュー画åƒå¢ƒç•Œ', + 'PreviewImageData' => 'プレビュー画åƒãƒ‡ãƒ¼ã‚¿', + 'PreviewImageLength' => 'プレビューイメージ容é‡', + 'PreviewImageSize' => 'プレビューイメージサイズ', + 'PreviewImageStart' => 'プレビューイメージ開始', + 'PreviewImageValid' => { + Description => '有効プレビュー画åƒ', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'PreviewQuality' => { + PrintConv => { + 'Economy' => 'エコノミー', + 'Fine' => 'ファイン', + 'Normal' => '標準', + 'Normal Movie' => '標準動画', + 'Superfine' => 'S.ファイン', + }, + }, + 'PreviewSettingsDigest' => 'プレビュー設定è¦ç´„', + 'PreviewSettingsName' => 'プレビュー設定å', + 'PrimaryAFPoint' => { + Description => 'プライマリAFãƒã‚¤ãƒ³ãƒˆ', + PrintConv => { + 'Bottom' => '下', + 'C6 (Center)' => 'C6 (中央)', + 'Center' => '中央', + 'Mid-left' => '中央左', + 'Mid-right' => '中央å³', + 'Top' => '上', + }, + }, + 'PrimaryChromaticities' => '原色色度', + 'PrimaryPlatform' => '主è¦ãƒ—ラットフォーム', + 'PrintIM' => 'プリントイメージマッãƒãƒ³ã‚°', + 'ProcessingSoftware' => '処ç†ã‚½ãƒ•トウェア', + 'ProductID' => '製å“ID', + 'ProductionCode' => 'カメラãŒä¿®ç†ã•れãŸã‹ï¼Ÿ', + 'ProfileCMMType' => 'CMMタイププロフィール', + 'ProfileCalibrationSig' => 'プロフィールキャリブレーションサイン', + 'ProfileClass' => { + Description => 'プロフィールクラス', + PrintConv => { + 'Abstract Profile' => '抜粋プロフィール', + 'ColorSpace Conversion Profile' => '色空間変æ›ãƒ—ロフィール', + 'DeviceLink Profile' => 'デãƒã‚¤ã‚¹ãƒªãƒ³ã‚¯ãƒ—ロフィール', + 'Display Device Profile' => '表示装置プロフィール', + 'Input Device Profile' => '入力装置プロフィール', + 'NamedColor Profile' => '色å称プロフィール', + 'Nikon Input Device Profile (NON-STANDARD!)' => 'ニコンプロフィール("nkpf")', + 'Output Device Profile' => '出力装置プロフィール', + }, + }, + 'ProfileConnectionSpace' => '接続スペースプロフィール', + 'ProfileCopyright' => 'プロフィール著作権', + 'ProfileCreator' => 'プロフィール製作者', + 'ProfileDateTime' => 'プロフィール日時', + 'ProfileDescription' => 'プロフィール説明', + 'ProfileDescriptionML' => 'プロフィール説明ML', + 'ProfileEmbedPolicy' => { + Description => 'プロフィール埋ã‚è¾¼ã¿æ–¹é‡', + PrintConv => { + 'Allow Copying' => 'コピー許å¯', + 'Embed if Used' => '埋ã‚è¾¼ã¿ä½¿ç”¨', + 'Never Embed' => '埋ã‚è¾¼ã¿ç¦æ­¢', + 'No Restrictions' => '無制é™', + }, + }, + 'ProfileFileSignature' => 'プロフィールファイルシグãƒãƒ¼ãƒãƒ£', + 'ProfileHueSatMapData1' => '色相Sat.マップデータプロフィール1', + 'ProfileHueSatMapData2' => '色相Sat.マップデータプロフィール2', + 'ProfileHueSatMapDims' => '色境界', + 'ProfileID' => 'プロフィールID', + 'ProfileLookTableData' => '表示テーブルデータプロフィール', + 'ProfileLookTableDims' => '色境界', + 'ProfileName' => 'プロフィールå', + 'ProfileSequenceDesc' => 'プロフィールシーケンス説明', + 'ProfileToneCurve' => 'トーンカーブプロフィール', + 'ProfileVersion' => 'プロフィールãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'ProgramISO' => 'プログラムISO', + 'ProgramLine' => { + Description => 'プログラムライン', + PrintConv => { + 'Depth' => '深度優先', + 'Hi Speed' => '高速優先', + 'MTF' => 'MTF優先', + 'Normal' => 'ノーマル', + }, + }, + 'ProgramMode' => { + PrintConv => { + 'Night Portrait' => '人物夜景', + 'None' => 'ç„¡ã—', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Sports' => 'スãƒãƒ¼ãƒ„', + 'Sunset' => '夕日', + 'Text' => 'テキスト', + }, + }, + 'ProgramShift' => 'プログラムシフト', + 'ProgramVersion' => 'プログラムãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'Province-State' => '行政区/å·ž', + 'Quality' => { + Description => 'å“質', + PrintConv => { + 'Best' => 'S.ファイン', + 'Better' => 'ファイン', + 'Compressed RAW' => 'cRAW', + 'Compressed RAW + JPEG' => 'cRAW+JPEG', + 'Economy' => 'エコノミー', + 'Extra Fine' => 'エクストラファイン', + 'Fine' => 'ファイン', + 'Good' => 'エコノミー', + 'Low' => '低画質', + 'Normal' => 'ノーマル', + 'Premium' => 'プレミアム', + 'RAW + JPEG' => 'RAW+JPEG', + 'Standard' => 'スタンダード', + 'n/a' => '未設定', + }, + }, + 'Quality2' => 'å“質', + 'QualityMode' => { + Description => 'å“質モード', + PrintConv => { + 'Economy' => 'エコノミー', + 'Fine' => 'ファイン', + 'Normal' => 'ノーマル', + }, + }, + 'QuantizationMethod' => 'é‡å­åŒ–方法', + 'QuickAdjust' => 'クイック調整', + 'QuickControlDialInMeter' => { + Description => '測光タイマー中ã®ã‚µãƒ–é›»å­ãƒ€ã‚¤ãƒ¤ãƒ«', + PrintConv => { + 'AF point selection' => 'AFãƒ•ãƒ¬ãƒ¼ãƒ é¸æŠž', + 'Exposure comp/Aperture' => '露出補正/絞り数値', + 'ISO speed' => 'ISO感度', + }, + }, + 'QuickShot' => { + Description => 'クイックショット', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'RAFVersion' => 'RAFãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'ROCInfo' => 'ROC情報', + 'RasterPadding' => 'ラスタパディング', + 'RasterizedCaption' => 'ラスタ化表題', + 'Rating' => '格付ã‘', + 'RatingPercent' => '格付ã‘(%)', + 'RawAndJpgRecording' => { + Description => 'RAWã¨JPEG記録', + PrintConv => { + 'RAW+Large/Fine' => 'RAW+ラージï¼ãƒ•ァイン', + 'RAW+Large/Normal' => 'RAW+ラージï¼ãƒŽãƒ¼ãƒžãƒ«', + 'RAW+Medium/Fine' => 'RAW+ミドルï¼ãƒ•ァイン', + 'RAW+Medium/Normal' => 'RAW+ミドルï¼ãƒŽãƒ¼ãƒžãƒ«', + 'RAW+Small/Fine' => 'RAW+スモールï¼ãƒ•ァイン', + 'RAW+Small/Normal' => 'RAW+スモールï¼ãƒŽãƒ¼ãƒžãƒ«', + }, + }, + 'RawColorAdj' => { + PrintConv => { + 'Custom' => 'カスタム', + 'Faithful' => '忠実設定', + }, + }, + 'RawDataOffset' => 'RAWデータオフセット', + 'RawDataUniqueID' => 'RAWデータユニークID', + 'RawDevAutoGradation' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'RawDevColorSpace' => '色空間', + 'RawDevContrastValue' => 'コントラスト値', + 'RawDevEditStatus' => { + Description => '編集状態', + PrintConv => { + 'Edited (Landscape)' => 'スタジオ(風景)', + 'Edited (Portrait)' => 'スタジオ(ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆï¼‰', + 'Original' => 'オリジナル', + }, + }, + 'RawDevEngine' => { + Description => 'エンジン', + PrintConv => { + 'Advanced High Speed' => 'アドãƒãƒ³ã‚¹é«˜é€Ÿ', + 'High Function' => '高機能', + 'High Speed' => '高速', + }, + }, + 'RawDevExposureBiasValue' => '露出ãƒã‚¤ã‚¢ã‚¹å€¤', + 'RawDevGrayPoint' => 'グレーãƒã‚¤ãƒ³ãƒˆ', + 'RawDevMemoryColorEmphasis' => '記憶色強調', + 'RawDevNoiseReduction' => 'ノイズフィルター(増感)', + 'RawDevPMPictureTone' => { + PrintConv => { + 'Blue' => 'é’', + 'Green' => 'ç·‘', + 'Neutral' => 'ニュートラル', + 'Purple' => 'ç´«', + 'Sepia' => 'セピア', + }, + }, + 'RawDevPM_BWFilter' => { + PrintConv => { + 'Green' => 'ç·‘', + 'Neutral' => 'ニュートラル', + 'Orange' => 'オレンジ', + 'Red' => '赤', + 'Yellow' => '黄色', + }, + }, + 'RawDevPictureMode' => { + PrintConv => { + 'Natural' => 'ナãƒãƒ¥ãƒ©ãƒ«', + 'Sepia' => 'セピア', + 'Vivid' => 'ビビッド', + }, + }, + 'RawDevSaturationEmphasis' => '彩度強調', + 'RawDevSettings' => 'ノイズリダクション', + 'RawDevSharpnessValue' => 'コントラスト値', + 'RawDevVersion' => 'RAW展開ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'RawDevWBFineAdjustment' => 'ホワイトãƒãƒ©ãƒ³ã‚¹å¾®èª¿æ•´', + 'RawDevWhiteBalance' => { + PrintConv => { + 'Color Temperature' => '色温度', + }, + }, + 'RawDevWhiteBalanceValue' => 'ホワイトãƒãƒ©ãƒ³ã‚¹å€¤', + 'RawImageCenter' => 'RAWイメージセンター', + 'RawImageDigest' => 'RAWイメージè¦ç´„', + 'RawImageHeight' => 'イメージ高ã•', + 'RawImageSegmentation' => 'RAWイメージ部分番å·', + 'RawImageSize' => 'RAWイメージサイズ', + 'RawImageWidth' => 'イメージ幅', + 'RawInfoVersion' => 'RAW情報ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'RawJpgQuality' => { + Description => 'RAW JPEG å“質', + PrintConv => { + 'Economy' => 'エコノミー', + 'Fine' => 'ファイン', + 'Normal' => '標準', + 'Normal Movie' => '標準動画', + 'Superfine' => 'S.ファイン', + }, + }, + 'RawJpgSize' => { + Description => 'RAW JPEG サイズ', + PrintConv => { + 'Large' => 'ラージ', + 'Medium' => 'ミドル', + 'Medium 1' => 'ミドル1', + 'Medium 2' => 'ミドル2', + 'Medium 3' => 'ミドル3', + 'Medium Movie' => 'ミディアム動画', + 'Postcard' => 'ãƒã‚¬ã‚­', + 'Small' => 'スモール', + 'Small Movie' => 'スモール動画', + 'Widescreen' => 'ワイド画é¢', + }, + }, + 'RecordMode' => { + Description => '記録モード', + PrintConv => { + 'Aperture Priority' => '絞り優先', + 'Best Shot' => 'ベストショット', + 'Manual' => 'マニュアル', + 'Movie' => 'å‹•ç”»', + 'Movie (19)' => '動画(19)', + 'Program AE' => 'プログラムAE', + 'Shutter Priority' => 'シャッター優先', + 'YouTube Movie' => 'YouTubeå‹•ç”»', + }, + }, + 'RecordShutterRelease' => 'レコードシャッターレリーズ', + 'RecordingMode' => { + Description => '記録モード', + PrintConv => { + 'Auto' => 'オート', + 'Landscape' => '風景', + 'Manual' => 'マニュアル', + 'Night Scene' => '夜景', + 'Panorama' => 'パノラマ', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Single Shutter' => 'シングルシャッター', + }, + }, + 'RedBalance' => 'レッドãƒãƒ©ãƒ³ã‚¹', + 'RedEyeCorrection' => { + PrintConv => { + 'Automatic' => 'オート', + 'Off' => 'オフ', + }, + }, + 'RedEyeData' => '赤目データ', + 'RedEyeReduction' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'RedMatrixColumn' => '赤色マトリックス列', + 'RedTRC' => '赤色調増殖曲線', + 'ReductionMatrix1' => '縮å°ãƒžãƒˆãƒªãƒƒã‚¯ã‚¹1', + 'ReductionMatrix2' => '縮å°ãƒžãƒˆãƒªãƒƒã‚¯ã‚¹2', + 'ReferenceBlackWhite' => '白黒ã®åŸºæº–値ã®ä¸€çµ„', + 'ReferenceDate' => 'å‚照日付', + 'ReferenceNumber' => 'å‚ç…§æ•°', + 'ReferenceService' => 'å‚照サービス', + 'RelatedImageFileFormat' => '関連イメージファイル形å¼', + 'RelatedImageHeight' => '関連イメージ高', + 'RelatedImageWidth' => '関連イメージ幅', + 'RelatedSoundFile' => '関連オーディオファイル', + 'ReleaseButtonToUseDial' => { + Description => 'レリーズボタンã‹ã‚‰ä½¿ç”¨ãƒ€ã‚¤ãƒ¤ãƒ«', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'ReleaseDate' => 'リリース日付', + 'ReleaseTime' => 'リリース時間', + 'RemoteOnDuration' => 'リモートæŒç¶šæ™‚é–“', + 'RenderingIntent' => { + Description => 'æ„æ€è¡¨ç¾', + PrintConv => { + 'ICC-Absolute Colorimetric' => '絶対比色分æž', + 'Media-Relative Colorimetric' => '相対比色分æž', + 'Perceptual' => '知覚的', + 'Saturation' => '飽和', + }, + }, + 'RepeatingFlashCount' => 'リピーティングフラッシュ 時間', + 'RepeatingFlashOutput' => 'リピーティングフラッシュ 出力', + 'RepeatingFlashRate' => 'リピーティングフラッシュ 周波数', + 'ResampleParamsQuality' => { + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + }, + }, + 'Resaved' => { + Description => '予約', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'ResolutionMode' => 'è§£åƒåº¦ãƒ¢ãƒ¼ãƒ‰', + 'ResolutionUnit' => { + Description => 'Xã¨Yè§£åƒåº¦å˜ä½', + PrintConv => { + 'None' => 'ç„¡ã—', + 'cm' => 'ピクセル/cm', + 'inches' => 'インãƒ', + }, + }, + 'RetouchHistory' => { + Description => 'レタッãƒå±¥æ­´', + PrintConv => { + 'None' => 'ç„¡ã—', + 'Sepia' => 'セピア', + }, + }, + 'ReverseIndicators' => '指標逆転', + 'Rotation' => { + Description => '回転', + PrintConv => { + 'Horizontal' => '水平(標準)', + 'Horizontal (Normal)' => '水平(標準)', + 'Horizontal (normal)' => '水平(標準)', + 'Rotate 180' => '180度回転', + 'Rotate 270 CW' => '270度回転 CW', + 'Rotate 90 CW' => '90度回転 CW', + 'Rotated 180' => '180度回転', + 'Rotated 270 CW' => '270度回転 CW', + 'Rotated 90 CW' => '90度回転 CW', + }, + }, + 'RowInterleaveFactor' => '列を挟むè¦å› ', + 'RowsPerStrip' => '1片ã®åˆ—æ•°', + 'SMaxSampleValue' => 'S 最大サンプル値', + 'SMinSampleValue' => 'S 最å°ã‚µãƒ³ãƒ—ル値', + 'SPIFFVersion' => 'SPIFFãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'SRAWQuality' => { + PrintConv => { + 'n/a' => '該当無ã—', + }, + }, + 'SRActive' => { + Description => '手ã¶ã‚Œè£œæ­£çŠ¶æ…‹', + PrintConv => { + 'No' => 'オフ', + 'Yes' => 'オン', + }, + }, + 'SRFocalLength' => 'SR焦点è·é›¢', + 'SRHalfPressTime' => 'ã‚·ãƒ£ãƒƒã‚¿ãƒ¼åŠæŠ¼ã—æ™‚é–“', + 'SRResult' => { + Description => 'SR効果', + PrintConv => { + 'Not stabilized' => 'ãªã—', + }, + }, + 'SVGVersion' => 'SVGãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'SafetyShift' => { + Description => 'セイフティシフト', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable (ISO speed)' => 'ã™ã‚‹(ISO感度)', + 'Enable (Tv/Av)' => 'ã™ã‚‹(Tv/Av値)', + }, + }, + 'SafetyShiftInAvOrTv' => { + Description => 'セイフティシフトã®è¨­å®š', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'SampleFormat' => 'サンプル形å¼', + 'SampleStructure' => 'サンプリング構造', + 'SamplesPerPixel' => 'コンãƒãƒ¼ãƒãƒ³ãƒˆæ•°', + 'SanyoQuality' => 'サンヨーå“質', + 'SanyoThumbnail' => 'サンヨーサムãƒã‚¤ãƒ«', + 'Saturation' => { + Description => '彩度', + PrintConv => { + '+1 (medium high)' => '+1 (å°‘ã—高ã„)', + '+2 (high)' => '+2 (ãƒãƒ¼ãƒ‰)', + '+3 (very high)' => '+3 (ã‹ãªã‚Šé«˜ã„)', + '+4 (highest)' => '+4', + '+4 (maximum)' => '+4', + '-1 (medium low)' => '-1 (å°‘ã—低ã„)', + '-2 (low)' => '-2 (ソフト)', + '-3 (very low)' => '-3 (ã‹ãªã‚Šä½Žã„)', + '-4 (lowest)' => '-4', + '-4 (minimum)' => '-4', + '0 (normal)' => '0 (スタンダード)', + 'B&W' => '黒&白', + 'Film Simulation' => 'フィルムシミュレーション', + 'High' => '高ã„彩度', + 'Low' => '低ã„彩度', + 'Medium High' => 'å°‘ã—高ã„', + 'Medium Low' => 'å°‘ã—低ã„', + 'None' => '未設定', + 'None (B&W)' => 'ç„¡ã—(黒&白)', + 'Normal' => '標準', + }, + }, + 'SaturationFaithful' => '彩度忠実設定', + 'SaturationLandscape' => '彩度風景', + 'SaturationNeutral' => '彩度ニュートラル', + 'SaturationPortrait' => '彩度ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'SaturationSetting' => '彩度設定', + 'SaturationStandard' => '彩度スタンダード', + 'SaturationUserDef1' => '彩度ユーザ設定1', + 'SaturationUserDef2' => '彩度ユーザ設定2', + 'SaturationUserDef3' => '彩度ユーザ設定3', + 'ScanImageEnhancer' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ScanningDirection' => '走査方å‘', + 'Scene' => 'å ´é¢', + 'SceneArea' => 'シーンエリア?', + 'SceneAssist' => 'シーン調整', + 'SceneCaptureType' => { + Description => 'シーンキャプãƒãƒ£ã‚¿ã‚¤ãƒ—', + PrintConv => { + 'Landscape' => '風景', + 'Night' => '夜景', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Standard' => 'スタンダード', + }, + }, + 'SceneDetect' => 'シーン検出', + 'SceneDetectData' => 'シーン検出データ?', + 'SceneMode' => { + Description => 'シーンモード', + PrintConv => { + '2 in 1' => '2イン1', + '3D Sweep Panorama' => '3D', + 'Aerial Photo' => '空撮', + 'Anti Motion Blur' => '人物ブレ軽減', + 'Aperture Priority' => '絞り優先', + 'Auction' => 'アクション', + 'Auto' => 'オート', + 'Auto+' => 'Auto アドãƒãƒ³ã‚¹', + 'Available Light' => '自然光', + 'Baby' => '赤ã¡ã‚ƒã‚“', + 'Beach' => 'ビーãƒ', + 'Beach & Snow' => 'ビーãƒï¼†ã‚¹ãƒŽãƒ¼', + 'Behind Glass' => 'ガラス越ã—', + 'Candle' => 'キャンドル', + 'Candlelight' => 'キャンドルライト', + 'Children' => 'å­ä¾›', + 'Color Effects' => 'カラーエフェクト', + 'Cont. Priority AE' => '連続撮影優先AE', + 'Cuisine' => 'æ–™ç†', + 'Digital Image Stabilization' => 'デジタル手振れ補正', + 'Documents' => '文書', + 'Face Portrait' => 'å¯é¡”', + 'Fireworks' => '花ç«', + 'Food' => 'æ–™ç†', + 'Handheld Night Shot' => '手æŒã¡å¤œæ™¯', + 'High Key' => 'ãƒã‚¤ã‚­ãƒ¼', + 'High Sensitivity' => '高感度', + 'High Speed Continuous Shooting' => '高速連写', + 'Indoor' => '屋内撮影', + 'Intelligent Auto' => 'インテリジェントオート', + 'Intelligent ISO' => 'インテリジェントISO', + 'Landscape' => '風景', + 'Landscape+Portrait' => '風景+人物', + 'Low Key' => 'ローキー', + 'Macro' => 'マクロ', + 'Manual' => 'マニュアル', + 'Movie' => 'å‹•ç”»', + 'Movie Preview' => '動画プレビュー', + 'Museum' => '美術館', + 'My Mode' => 'マイモード', + 'Nature Macro' => '自然マクロ', + 'Night Portrait' => '人物夜景', + 'Night Scene' => '夜景', + 'Night Scenery' => '夜景', + 'Night View/Portrait' => 'ナイトビュー/ãƒãƒ¼ãƒˆãƒ¬ã‚¤ãƒˆ', + 'Night+Portrait' => '夜景+人物', + 'Normal' => 'ノーマル', + 'Off' => 'オフ', + 'Panning' => 'パンニング', + 'Panorama' => 'パノラマ', + 'Panorama Assist' => 'パノラマアシスト', + 'Party' => 'パーティ', + 'Pet' => 'ペット', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Program' => 'プログラム', + 'Scenery' => '風景', + 'Self Portrait' => '自分撮り', + 'Self Portrait+Self Timer' => '自分撮り+セルフタイマー', + 'Self Protrait+Timer' => '自分撮り+セルフタイマー', + 'Shoot & Select' => 'ショット&セレクト', + 'Shoot & Select1' => 'ショット&セレクト1', + 'Shoot & Select2' => 'ショット&セレクト2', + 'Shooting Guide' => '撮影ガイド', + 'Shutter Priority' => 'シャッター優先', + 'Simple' => 'シンプル', + 'Smile Shot' => 'スマイルショット', + 'Snow' => 'スノー', + 'Soft Skin' => 'ソフトスキン', + 'Sport' => 'スãƒãƒ¼ãƒ„', + 'Sports' => 'スãƒãƒ¼ãƒ„', + 'Spot' => 'スãƒãƒƒãƒˆ', + 'Standard' => 'スタンダード', + 'Starry Night' => '星空', + 'Sunset' => '夕日', + 'Super Macro' => 'スーパーマクロ', + 'Sweep Panorama' => 'スイングパノラマ', + 'Text' => 'テキスト', + 'Underwater' => '水中', + 'Underwater Macro' => '水中マクロ', + 'Underwater Snapshot' => '水中スナップ', + 'Underwater Wide1' => '水中ワイド1', + 'Underwater Wide2' => '水中ワイド2', + 'Vivid' => 'ビビッド', + }, + }, + 'SceneModeUsed' => { + Description => 'シーンモード', + PrintConv => { + 'Aperture Priority' => '絞り優先', + 'Beach' => 'ビーãƒ', + 'Candlelight' => 'キャンドルライト', + 'Fireworks' => '花ç«', + 'Landscape' => '風景', + 'Macro' => 'マクロ', + 'Manual' => 'マニュアル', + 'Night Portrait' => '人物夜景', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Program' => 'プログラム', + 'Shutter Priority' => 'シャッター優先', + 'Snow' => 'スノー', + 'Sunset' => '夕日', + 'Text' => 'テキスト', + }, + }, + 'SceneSelect' => { + Description => 'ã‚·ãƒ¼ãƒ³é¸æŠž', + PrintConv => { + 'Lamp' => 'ランプ', + 'Night' => '夜景', + 'Off' => 'オフ', + 'Sport' => 'スãƒãƒ¼ãƒ„', + 'TV' => 'テレビ', + 'User 1' => 'ユーザー1', + 'User 2' => 'ユーザー2', + }, + }, + 'SceneType' => { + Description => 'シーンタイプ', + PrintConv => { + 'Directly photographed' => '直接撮影画åƒ', + }, + }, + 'SecurityClassification' => { + Description => 'セキュリティ区分', + PrintConv => { + 'Confidential' => '機密文書', + 'Restricted' => 'é™å®š', + 'Secret' => '秘密', + 'Top Secret' => '最高機密', + 'Unclassified' => '未分類', + }, + }, + 'SelectableAFPoint' => { + Description => 'ä»»æ„é¸æŠžå¯èƒ½ãªAFフレーム', + PrintConv => { + '11 points' => '11点', + '19 points' => '19点', + '45 points' => '45点', + 'Inner 9 points' => '9点(内å´)', + 'Outer 9 points' => '9点(外å´)', + }, + }, + 'SelfTimer' => { + Description => 'セルフタイマー長', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'SelfTimer2' => 'セルフタイマー(2)', + 'SelfTimerMode' => 'セルフタイマーモード', + 'SelfTimerTime' => 'セルフタイマーé…延時間', + 'SensingMethod' => { + Description => 'センサー方å¼', + PrintConv => { + 'Color sequential area' => 'シーケンシャルカラーセンサー', + 'Color sequential linear' => 'シーケンシャルカラーラインセンサー', + 'Monochrome area' => 'モノクロエリアセンサー', + 'Monochrome linear' => 'モノクロラインセンサー', + 'Not defined' => '未定義', + 'One-chip color area' => 'å˜æ¿å¼ã‚«ãƒ©ãƒ¼ã‚»ãƒ³ã‚µãƒ¼', + 'Three-chip color area' => '3æ¿å¼ã‚«ãƒ©ãƒ¼ã‚»ãƒ³ã‚µãƒ¼', + 'Trilinear' => '3ラインセンサー', + 'Two-chip color area' => 'ï¼’æ¿å¼ã‚«ãƒ©ãƒ¼ã‚»ãƒ³ã‚µãƒ¼', + }, + }, + 'SensitivityAdjust' => '感度調節', + 'SensitivitySteps' => { + Description => '感度ステップ', + PrintConv => { + '1 EV Steps' => '1EVステップ', + 'As EV Steps' => '露出ステップã«å¾“ã†', + }, + }, + 'SensorCleaning' => { + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'SensorHeight' => 'センサー高ã•', + 'SensorImageHeight' => 'センサー高ã•', + 'SensorImageWidth' => 'センサー幅', + 'SensorLeftBorder' => 'イメージ左部', + 'SensorPixelSize' => 'センサーピクセルサイズ', + 'SensorTemperature' => 'センサー温度', + 'SensorTopBorder' => 'イメージ上部', + 'SensorWidth' => 'センサー幅', + 'Sequence' => 'シーケンス', + 'SequenceNumber' => 'シーケンス番å·', + 'SequenceShotInterval' => 'シーケンスショットインターãƒãƒ«', + 'SequentialShot' => { + Description => 'シーケンシャルショット', + PrintConv => { + 'None' => 'ç„¡ã—', + 'Standard' => 'スタンダード', + }, + }, + 'SerialNumber' => 'シリアル番å·', + 'SerialNumberFormat' => 'シリアル番å·å½¢å¼', + 'ServiceIdentifier' => 'サービス識別å­', + 'SetButtonCrossKeysFunc' => { + Description => 'SETボタン/å字キー機能', + PrintConv => { + 'Cross keys: AF point select' => 'å字キー:AFãƒ•ãƒ¬ãƒ¼ãƒ é¸æŠž', + 'Normal' => '標準', + 'Set: Flash Exposure Comp' => 'SET:調光補正', + 'Set: Parameter' => 'SET:ç¾åƒãƒ‘ãƒ©ãƒ¡ãƒ¼ã‚¿ãƒ¼é¸æŠž', + 'Set: Picture Style' => 'SET:ピクãƒãƒ£ãƒ¼ã‚¹ã‚¿ã‚¤ãƒ«', + 'Set: Playback' => 'SET:ç”»åƒã®å†ç”Ÿ', + 'Set: Quality' => 'SET:記録画質', + }, + }, + 'SetButtonWhenShooting' => { + Description => '撮影時ã®SETボタン', + PrintConv => { + 'Change parameters' => 'ç¾åƒãƒ‘ãƒ©ãƒ¡ãƒ¼ã‚¿ãƒ¼é¸æŠž', + 'Default (no function)' => '通常(ãªã—)', + 'Disabled' => '無効', + 'Flash exposure compensation' => '調光補正', + 'ISO speed' => 'ISO感度', + 'Image playback' => 'ç”»åƒå†ç”Ÿ', + 'Image quality' => 'è¨˜éŒ²ç”»è³ªé¸æŠž', + 'Image size' => 'ç”»åƒã‚µã‚¤ã‚º', + 'LCD monitor On/Off' => '液晶モニターã®å…¥/切', + 'Menu display' => 'メニュー表示', + 'Normal (disabled)' => '通常(無効)', + 'Picture style' => 'ピクãƒãƒ£ãƒ¼ã‚¹ã‚¿ã‚¤ãƒ«', + 'Quick control screen' => 'クイック設定画é¢', + 'Record func. + media/folder' => '記録機能ã¨ãƒ¡ãƒ‡ã‚£ã‚¢ãƒ»ãƒ•ォルダ', + 'Record movie (Live View)' => '動画撮影(ライブビュー)', + 'White balance' => 'ホワイトãƒãƒ©ãƒ³ã‚¹', + }, + }, + 'SetFunctionWhenShooting' => { + Description => '撮影時ã®ã‚»ãƒƒãƒˆãƒœã‚¿ãƒ³æ©Ÿèƒ½', + PrintConv => { + 'Change Parameters' => 'ç¾åƒãƒ‘ãƒ©ãƒ¡ãƒ¼ã‚¿ãƒ¼é¸æŠž', + 'Change quality' => 'è¨˜éŒ²ç”»è³ªé¸æŠž', + 'Default (no function)' => '通常(ãªã—)', + 'Image replay' => 'ç”»åƒã®å†ç”Ÿ', + 'Menu display' => 'メニュー表示', + }, + }, + 'ShadingCompensation' => { + Description => '陰影修正', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ShadingCompensation2' => { + Description => '陰影補正編集', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Shadow' => 'シャドウ', + 'ShadowScale' => 'シャドウスケール', + 'Shadows' => 'シャドウ', + 'ShakeReduction' => { + Description => '手ã¶ã‚Œè£œæ­£(設定)', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ShakeReductionInfo' => 'SR効果', + 'Sharpness' => { + Description => 'シャープãƒã‚¹', + PrintConv => { + '+1 (medium hard)' => '+1 (å°‘ã—ãƒãƒ¼ãƒ‰)', + '+2 (hard)' => '+2 (ãƒãƒ¼ãƒ‰)', + '+3 (very hard)' => '+3 (ã‹ãªã‚Šãƒãƒ¼ãƒ‰)', + '+4 (hardest)' => '+4', + '+4 (maximum)' => '+4', + '-1 (medium soft)' => '-1 (ミドルソフト)', + '-2 (soft)' => '-2 (ソフト)', + '-3 (very soft)' => '-3 (ã‹ãªã‚Šã‚½ãƒ•ト)', + '-4 (minimum)' => '-4', + '-4 (softest)' => '-4', + '0 (normal)' => '0 (ノーマル)', + 'Film Simulation' => 'フィルムシミュレーション', + 'Hard' => 'ãƒãƒ¼ãƒ‰', + 'Hard2' => 'ãƒãƒ¼ãƒ‰2', + 'Medium Hard' => 'ミドルãƒãƒ¼ãƒ‰', + 'Medium Soft' => 'ミドルソフト', + 'Normal' => 'ノーマル', + 'Sharp' => 'シャープ', + 'Soft' => 'ソフト', + 'Soft2' => 'ソフト2', + 'n/a' => '該当無ã—', + }, + }, + 'SharpnessFactor' => 'シャープãƒã‚¹è¦å› ', + 'SharpnessFaithful' => 'シャープãƒã‚¹å¿ å®Ÿè¨­å®š', + 'SharpnessFrequency' => { + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + 'Standard' => 'スタンダード', + 'n/a' => '該当無ã—', + }, + }, + 'SharpnessLandscape' => 'シャープãƒã‚¹é¢¨æ™¯', + 'SharpnessMonochrome' => 'シャープãƒã‚¹ãƒ¢ãƒŽã‚¯ãƒ­', + 'SharpnessNeutral' => 'シャープãƒã‚¹ãƒ‹ãƒ¥ãƒ¼ãƒˆãƒ©ãƒ«', + 'SharpnessPortrait' => 'シャープãƒã‚¹ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'SharpnessSetting' => 'シャープãƒã‚¹è¨­å®š', + 'SharpnessStandard' => 'シャープãƒã‚¹ã‚¹ã‚¿ãƒ³ãƒ€ãƒ¼ãƒ‰', + 'SharpnessUserDef1' => 'シャープãƒã‚¹ãƒ¦ãƒ¼ã‚¶è¨­å®š1', + 'SharpnessUserDef2' => 'シャープãƒã‚¹ãƒ¦ãƒ¼ã‚¶è¨­å®š2', + 'SharpnessUserDef3' => 'シャープãƒã‚¹ãƒ¦ãƒ¼ã‚¶è¨­å®š3', + 'ShootingInfoDisplay' => { + Description => '撮影情報表示', + PrintConv => { + 'Auto' => 'オート', + 'Manual (dark on light)' => '手動-黒地ã«ç™½', + 'Manual (light on dark)' => '手動-白地ã«é»’', + }, + }, + 'ShootingMode' => { + Description => '撮影モード', + PrintConv => { + 'Aerial Photo' => '空撮', + 'Aperture Priority' => '絞り優先', + 'Baby' => '赤ã¡ã‚ƒã‚“', + 'Beach' => 'ビーãƒ', + 'Candlelight' => 'キャンドルライト', + 'Clipboard' => 'メモ', + 'Color Effects' => 'カラーエフェクト', + 'Economy' => 'エコモード', + 'Fireworks' => '花ç«', + 'Food' => 'æ–™ç†', + 'High Sensitivity' => '高感度', + 'High Speed Continuous Shooting' => '高速連写', + 'Intelligent Auto' => 'インテリジェントオート', + 'Intelligent ISO' => 'インテリジェントISO', + 'Macro' => 'マクロ', + 'Manual' => 'マニュアル', + 'Movie Preview' => '動画プレビュー', + 'Night Portrait' => '人物夜景', + 'Night Scenery' => '夜景', + 'Normal' => '標準', + 'Panning' => 'パンニング', + 'Panorama Assist' => 'パノラマアシスト', + 'Party' => 'パーティ', + 'Pet' => 'ペット', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Program' => 'プログラム', + 'Scenery' => '風景', + 'Self Portrait' => '自分撮り', + 'Shutter Priority' => 'シャッター優先', + 'Simple' => 'シンプル', + 'Snow' => 'スノー', + 'Soft Skin' => 'ソフトスキン', + 'Sports' => 'スãƒãƒ¼ãƒ„', + 'Spot' => 'スãƒãƒƒãƒˆ', + 'Starry Night' => '星空', + 'Sunset' => '夕日', + 'Underwater' => '水中', + }, + }, + 'ShootingModeSetting' => { + Description => '撮影モード', + PrintConv => { + 'Continuous' => '連続撮影', + 'Delayed Remote' => 'é…延リモート', + 'Quick-response Remote' => '峿™‚リモート', + 'Self-timer' => 'セルフタイマー', + 'Single Frame' => '1コマ撮影', + }, + }, + 'ShortDocumentID' => '短文書ID', + 'ShortOwnerName' => '短ã„オーナーå', + 'ShortReleaseTimeLag' => { + Description => 'レリーズタイムラグ最速化', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'ShotInfoVersion' => 'ショット情報ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'Shutter-AELock' => { + Description => 'シャッターボタン/AEロックボタン', + PrintConv => { + 'AE lock/AF' => 'AEロック/AF', + 'AE/AF, No AE lock' => 'AE/AF(AEロックãªã—)', + 'AF/AE lock' => 'AF/AEロック', + 'AF/AF lock' => 'AF/AFロック', + 'AF/AF lock, No AE lock' => 'AF/AFロック(AEロックãªã—)', + }, + }, + 'ShutterAELButton' => 'シャッターボタンï¼AEロックボタン', + 'ShutterButtonAFOnButton' => { + Description => 'シャッター/AF-ONボタン', + PrintConv => { + 'AE lock/Metering + AF start' => 'AEロック/測光・AFé–‹å§‹', + 'Metering + AF start' => '測光・AFé–‹å§‹', + 'Metering + AF start/AF stop' => '測光・AFé–‹å§‹/AFストップ', + 'Metering + AF start/disable' => '測光・AFé–‹å§‹/無効', + 'Metering start/Meter + AF start' => '測光開始/測光・AFé–‹å§‹', + }, + }, + 'ShutterCount' => 'シャッター回数', + 'ShutterCurtainSync' => { + Description => 'ストロボã®ã‚·ãƒ³ã‚¯ãƒ­ã‚¿ã‚¤ãƒŸãƒ³ã‚°', + PrintConv => { + '1st-curtain sync' => '先幕シンクロ', + '2nd-curtain sync' => '後幕シンクロ', + }, + }, + 'ShutterMode' => { + PrintConv => { + 'Aperture Priority' => '絞り優先', + 'Auto' => 'オート', + }, + }, + 'ShutterReleaseButtonAE-L' => { + Description => 'シャッターレリーズボタン AE-L', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ShutterReleaseNoCFCard' => { + Description => 'CFカード未装填時ã®ãƒ¬ãƒªãƒ¼ã‚º', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'ShutterSpeed' => '露出時間', + 'ShutterSpeedRange' => { + Description => 'シャッター速度ã®åˆ¶å¾¡ç¯„囲ã®è¨­å®š', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'ShutterSpeedValue' => 'シャッタースピード', + 'SimilarityIndex' => '類似インデックス', + 'Site' => 'サイト', + 'SlaveFlashMeteringSegments' => 'スレーブフラッシュ測光値', + 'SlideShow' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'SlowShutter' => { + Description => 'スローシャッター', + PrintConv => { + 'Night Scene' => '夜景', + 'None' => 'ç„¡ã—', + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'SlowSync' => { + Description => 'スローシンクロ', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Software' => 'ソフトウェア', + 'SoftwareVersion' => 'ソフトウェアãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'Source' => 'ソース', + 'SpatialFrequencyResponse' => '空間周波数特性', + 'SpecialEffectsOpticalFilter' => { + PrintConv => { + 'None' => 'ç„¡ã—', + }, + }, + 'SpecialInstructions' => '手順', + 'SpecialMode' => 'スペシャルモード', + 'SpectralSensitivity' => 'スペクトル感度', + 'Sport' => 'スãƒãƒ¼ãƒ„', + 'SpotFocusPointX' => 'スãƒãƒƒãƒˆãƒ•ォーカスãƒã‚¤ãƒ³ãƒˆX', + 'SpotFocusPointY' => 'スãƒãƒƒãƒˆãƒ•ォーカスãƒã‚¤ãƒ³ãƒˆY', + 'SpotMeterLinkToAFPoint' => { + Description => '測è·ç‚¹é€£å‹•スãƒãƒƒãƒˆæ¸¬å…‰', + PrintConv => { + 'Disable (use center AF point)' => 'ã—ãªã„(中央固定', + 'Enable (use active AF point)' => 'ã™ã‚‹(測è·ç‚¹é€£å‹•)', + }, + }, + 'SpotMeteringMode' => { + Description => 'スãƒãƒƒãƒˆãƒ¡ãƒ¼ã‚¿ãƒ¼ãƒ¢ãƒ¼ãƒ‰', + PrintConv => { + 'Center' => '中央', + }, + }, + 'State' => '都é“府県å', + 'StoNits' => 'Stoニット', + 'StraightenAngle' => 'ストレートアングル', + 'StreamType' => { + PrintConv => { + 'Text' => 'テキスト', + }, + }, + 'StripByteCounts' => '圧縮片ã®ãƒã‚¤ãƒˆæ•°', + 'StripOffsets' => 'ç”»åƒãƒ‡ãƒ¼ã‚¿ä½ç½®', + 'Sub-location' => '場所', + 'SubSecCreateDate' => 'ãƒ‡ã‚¸ã‚¿ãƒ«ãƒ‡ãƒ¼ã‚¿ä½œæˆæ—¥æ™‚', + 'SubSecDateTimeOriginal' => 'ã‚ªãƒªã‚¸ãƒŠãƒ«ãƒ‡ãƒ¼ã‚¿ä½œæˆæ—¥æ™‚', + 'SubSecModifyDate' => 'ãƒ•ã‚¡ã‚¤ãƒ«ä½œæˆæ—¥æ™‚', + 'SubSecTime' => 'DateTimeサブ秒', + 'SubSecTimeDigitized' => 'DateTimeDigitizedサブ秒', + 'SubSecTimeOriginal' => 'DateTimeOriginalサブ秒', + 'SubTileBlockSize' => 'サブタイトルブロックサイズ', + 'SubfileType' => 'æ–°è¦ã‚µãƒ–ファイルタイプ', + 'SubimageColor' => { + PrintConv => { + 'Monochrome' => 'モノトーン', + }, + }, + 'Subject' => 'サブジェクト', + 'SubjectArea' => '対象領域', + 'SubjectCode' => 'サブジェクトコード', + 'SubjectDistance' => '対象è·é›¢', + 'SubjectDistanceRange' => { + Description => '被写体è·é›¢ç¯„囲', + PrintConv => { + 'Close' => '近景', + 'Distant' => 'é æ™¯', + 'Macro' => 'マクロ', + 'Unknown' => '䏿˜Ž', + }, + }, + 'SubjectLocation' => '対象領域', + 'SubjectProgram' => { + Description => '被写体プログラム', + PrintConv => { + 'Night portrait' => '人物夜景', + 'None' => 'ç„¡ã—', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Sports action' => 'スãƒãƒ¼ãƒ„アクション', + 'Sunset' => '夕日', + 'Text' => 'テキスト', + }, + }, + 'SubjectReference' => 'サブジェクトコード', + 'Subsystem' => { + PrintConv => { + 'Unknown' => '䏿˜Ž', + }, + }, + 'SuperMacro' => { + Description => 'スーパーマクロ', + PrintConv => { + 'Off' => 'オフ', + 'On (1)' => 'オン(1)', + 'On (2)' => 'オン(2)', + }, + }, + 'SuperimposedDisplay' => { + Description => 'スーパーインãƒãƒ¼ã‚ºã®è¡¨ç¤º', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'SupplementalCategories' => '補足カテゴリー', + 'SupplementalType' => '補足タイプ', + 'SvISOSetting' => 'SVISO感度設定', + 'SwitchToRegisteredAFPoint' => { + Description => '登録AFフレームã¸ã®åˆ‡ã‚Šæ›ãˆ', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'T4Options' => 'T4オプション', + 'T6Options' => 'T6オプション', + 'TIFF-EPStandardID' => 'TIFF/EP標準ID', + 'TTL_DA_ADown' => 'TTL D/A Aãƒãƒ£ãƒ³ãƒãƒ«ã€€ãƒ€ã‚¦ãƒ³', + 'TTL_DA_AUp' => 'TTL D/A Aãƒãƒ£ãƒ³ãƒãƒ« アップ', + 'TTL_DA_BDown' => 'TTL D/A Bãƒãƒ£ãƒ³ãƒãƒ«ã€€ãƒ€ã‚¦ãƒ³', + 'TTL_DA_BUp' => 'TTL D/A Bãƒãƒ£ãƒ³ãƒãƒ«ã€€ã‚¢ãƒƒãƒ—', + 'Tagged' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'TargetAperture' => 'ターゲット絞り', + 'TargetExposureTime' => 'ターゲット露出時間', + 'TargetPrinter' => '標的プリンタ', + 'Technology' => { + Description => 'テクノロジー', + PrintConv => { + 'Active Matrix Display' => 'アクティブマトリクス型ディスプレイ', + 'Cathode Ray Tube Display' => 'CRTディスプレイ', + 'Digital Camera' => 'デジタルカメラ', + 'Dye Sublimation Printer' => '昇è¯åž‹ãƒ—リンター', + 'Electrophotographic Printer' => 'é™é›»è¨˜éŒ²å¼ãƒ—リンター', + 'Electrostatic Printer' => 'é™é›»æ°—プリンター', + 'Film Scanner' => 'フイルムスキャナー', + 'Film Writer' => 'フィルムライター', + 'Flexography' => 'アニリンå°åˆ·', + 'Gravure' => 'グラビアå°åˆ·', + 'Ink Jet Printer' => 'インクジェットプリンター', + 'Offset Lithography' => 'オフセットå°åˆ·', + 'Passive Matrix Display' => 'å˜ç´”マトリクス型ディスプレイ', + 'Photo CD' => 'フォトCD', + 'Photo Image Setter' => 'フォトイメージセッター', + 'Photographic Paper Printer' => 'å°ç”»ç´™ãƒ—リンター', + 'Projection Television' => 'プロジェクションテレビ', + 'Reflective Scanner' => 'å射スキャナ', + 'Silkscreen' => 'シルクスクリーンå°åˆ·', + 'Thermal Wax Printer' => '熱転写プリンター', + 'Video Camera' => 'ビデオカメラ', + 'Video Monitor' => 'ビデオモニター', + }, + }, + 'Teleconverter' => { + PrintConv => { + 'None' => 'ç„¡ã—', + }, + }, + 'Text' => 'テキスト', + 'TextInfo' => 'テキスト情報', + 'TextStamp' => { + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Thresholding' => '閾値化', + 'ThumbnailImage' => 'サムãƒã‚¤ãƒ«ç”»åƒ', + 'ThumbnailImageSize' => 'サムãƒã‚¤ãƒ«ã‚µã‚¤ã‚º', + 'ThumbnailImageValidArea' => 'サムãƒã‚¤ãƒ«ç”»åƒæœ‰åŠ¹é ˜åŸŸ', + 'TileByteCounts' => 'タイルã®ãƒã‚¤ãƒˆæ•°', + 'TileDepth' => 'ã‚¿ã‚¤ãƒ«ã®æ·±ã•', + 'TileLength' => 'タイルã®é•·ã•', + 'TileOffsets' => 'タイルã®ã‚ªãƒ•セット', + 'TileWidth' => 'タイルã®å¹…', + 'Time' => '時間', + 'TimeCreated' => 'ä½œæˆæ™‚é–“', + 'TimeScaleParamsQuality' => { + PrintConv => { + 'High' => '高ã„', + 'Low' => 'ソフト', + }, + }, + 'TimeSent' => 'ç™ºé€æ™‚é–“', + 'TimeSincePowerOn' => 'é›»æºã‚ªãƒ³çµŒéŽæ™‚é–“', + 'TimeStamp' => 'タイムスタンプ', + 'TimeStamp1' => 'タイムスタンプ1', + 'TimeZone' => 'タイムゾーン', + 'TimeZoneOffset' => 'タイムゾーンオフセット', + 'TimerFunctionButton' => { + Description => 'ファンクションボタン', + PrintConv => { + 'ISO' => 'ISO感度', + 'Image Quality/Size' => 'ç”»åƒå“質/サイズ', + 'Self-timer' => 'セルフタイマー', + 'Shooting Mode' => '撮影モード', + 'White Balance' => 'ホワイトãƒãƒ©ãƒ³ã‚¹', + }, + }, + 'TimerLength' => { + Description => 'å„ç¨®ã‚¿ã‚¤ãƒžãƒ¼ä¿æŒæ™‚é–“', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'Title' => 'タイトル', + 'ToneComp' => 'トーン補正', + 'ToneCurve' => { + Description => 'トーンカーブ', + PrintConv => { + 'Custom' => 'カスタム', + 'Manual' => 'マニュアル', + 'Standard' => 'スタンダード', + }, + }, + 'ToneCurveActive' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'ToneCurveName' => { + PrintConv => { + 'Custom' => 'カスタム', + }, + }, + 'ToneCurves' => 'トーンカーブ(s)', + 'ToningEffect' => { + Description => 'トーン効果', + PrintConv => { + 'B&W' => '白黒', + 'Blue' => 'é’', + 'Blue-green' => 'é’ç·‘', + 'Cyanotype' => 'é’写真', + 'Green' => 'ç·‘', + 'None' => 'ç„¡ã—', + 'Purple' => 'ç´«', + 'Purple-blue' => 'é’ç´«', + 'Red' => '赤', + 'Red-purple' => '赤紫', + 'Sepia' => 'セピア', + 'Yellow' => '黄色', + 'n/a' => '該当無ã—', + }, + }, + 'ToningEffectMonochrome' => { + Description => 'モノクロトーン効果', + PrintConv => { + 'Blue' => 'é’', + 'Green' => 'ç·‘', + 'None' => 'ç„¡ã—', + 'Purple' => 'ç´«', + 'Sepia' => 'セピア', + }, + }, + 'ToningSaturation' => '彩度トーン', + 'TransferFunction' => 'è»¢é€æ©Ÿèƒ½', + 'TransferRange' => '転é€ç¯„囲', + 'Transformation' => { + Description => '変形', + PrintConv => { + 'Horizontal (normal)' => '水平(標準)', + 'Rotate 180' => '180度回転', + 'Rotate 270 CW' => '270度回転 CW', + 'Rotate 90 CW' => '90度回転 CW', + }, + }, + 'TransmissionReference' => 'é€ä¿¡å…ƒè¨˜éŒ²', + 'TransparencyIndicator' => '逿˜Žåº¦æŒ‡æ¨™', + 'TrapIndicator' => 'トラップインジケーター', + 'Trapped' => { + PrintConv => { + 'Unknown' => '䏿˜Ž', + }, + }, + 'TravelDay' => 'トラベル日付', + 'TvExposureTimeSetting' => 'Tv露出時間設定', + 'Type' => 'タイプ', + 'USMLensElectronicMF' => { + Description => 'USMレンズã®é›»å­å¼æ‰‹å‹•フォーカス', + PrintConv => { + 'Disable after one-shot AF' => 'ワンショットAF作動後・ä¸å¯', + 'Disable in AF mode' => 'AF時ã™ã¹ã¦ä¸å¯', + 'Enable after one-shot AF' => 'ワンショットAF作動後・å¯', + }, + }, + 'Uncompressed' => { + Description => 'éžåœ§ç¸®', + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'UniqueCameraModel' => 'ユニークカメラモデル', + 'UniqueDocumentID' => 'ユニーク文書ID', + 'UniqueObjectName' => 'ユニーク・ãƒãƒ¼ãƒ ãƒ»ã‚ªãƒ–・オブジェクト', + 'Unknown' => '䏿˜Ž', + 'Unsharp1Color' => { + Description => 'アンシャープ1カラー', + PrintConv => { + 'Blue' => 'é’', + 'Cyan' => 'シアン', + 'Green' => 'ç·‘', + 'Magenta' => 'マゼンダ', + 'Red' => '赤', + 'Yellow' => '黄色', + }, + }, + 'Unsharp1HaloWidth' => 'アンシャープ1円光幅', + 'Unsharp1Intensity' => 'アンシャープ1強度', + 'Unsharp1Threshold' => 'アンシャープ1起点', + 'Unsharp2Color' => { + Description => 'アンシャープ2カラー', + PrintConv => { + 'Blue' => 'é’', + 'Cyan' => 'シアン', + 'Green' => 'ç·‘', + 'Magenta' => 'マゼンダ', + 'Red' => '赤', + 'Yellow' => '黄色', + }, + }, + 'Unsharp2HaloWidth' => 'アンシャープ2円光幅', + 'Unsharp2Intensity' => 'アンシャープ2強度', + 'Unsharp2Threshold' => 'アンシャープ2起点', + 'Unsharp3Color' => { + Description => 'アンシャープ3カラー', + PrintConv => { + 'Blue' => 'é’', + 'Cyan' => 'シアン', + 'Green' => 'ç·‘', + 'Magenta' => 'マゼンダ', + 'Red' => '赤', + 'Yellow' => '黄色', + }, + }, + 'Unsharp3HaloWidth' => 'アンシャープ3円光幅', + 'Unsharp3Intensity' => 'アンシャープ3強度', + 'Unsharp3Threshold' => 'アンシャープ3起点', + 'Unsharp4Color' => { + Description => 'アンシャープ4カラー', + PrintConv => { + 'Blue' => 'é’', + 'Cyan' => 'シアン', + 'Green' => 'ç·‘', + 'Magenta' => 'マゼンダ', + 'Red' => '赤', + 'Yellow' => '黄色', + }, + }, + 'Unsharp4HaloWidth' => 'アンシャープ4円光幅', + 'Unsharp4Intensity' => 'アンシャープ4強度', + 'Unsharp4Threshold' => 'アンシャープ4起点', + 'UnsharpCount' => 'アンシャープカウント', + 'UnsharpData' => 'アンシャープデータ', + 'UnsharpMask' => { + Description => 'アンシャープマスク', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Urgency' => '緊急性', + 'UsableMeteringModes' => { + Description => '測光モードã®é™å®š', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'UsableShootingModes' => { + Description => '撮影モードã®é™å®š', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'UserComment' => 'ユーザーコメント', + 'UserDef1PictureStyle' => { + Description => 'ユーザ設定1 ピクãƒãƒ£ãƒ¼ã‚¹ã‚¿ã‚¤ãƒ«', + PrintConv => { + 'Faithful' => '忠実設定', + 'Landscape' => '風景', + 'Monochrome' => 'モノトーン', + 'Neutral' => 'ニュートラル', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Standard' => 'スタンダード', + }, + }, + 'UserDef2PictureStyle' => { + Description => 'ユーザ設定2 ピクãƒãƒ£ãƒ¼ã‚¹ã‚¿ã‚¤ãƒ«', + PrintConv => { + 'Faithful' => '忠実設定', + 'Landscape' => '風景', + 'Monochrome' => 'モノトーン', + 'Neutral' => 'ニュートラル', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Standard' => 'スタンダード', + }, + }, + 'UserDef3PictureStyle' => { + Description => 'ユーザ設定3 ピクãƒãƒ£ãƒ¼ã‚¹ã‚¿ã‚¤ãƒ«', + PrintConv => { + 'Faithful' => '忠実設定', + 'Landscape' => '風景', + 'Monochrome' => 'モノトーン', + 'Neutral' => 'ニュートラル', + 'Portrait' => 'ãƒãƒ¼ãƒˆãƒ¬ãƒ¼ãƒˆ', + 'Standard' => 'スタンダード', + }, + }, + 'VRDVersion' => 'VRDãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'VRInfo' => 'VR(手振れ補正)情報', + 'VRInfoVersion' => 'VR情報ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'VR_0x66' => { + PrintConv => { + 'Off' => 'オフ', + 'On (active)' => 'オン(アクティブ)', + 'On (normal)' => 'オン(ノーマル)', + }, + }, + 'ValidAFPoints' => '有効ãªAFãƒã‚¤ãƒ³ãƒˆ', + 'ValidBits' => 'ピクセルã«ã¤ã有効ãªãƒ“ット', + 'VariProgram' => 'ãƒãƒªãƒ—ログラム', + 'Version' => 'ãƒãƒ¼ã‚¸ãƒ§ãƒ³', + 'VibrationReduction' => { + Description => '手振れ補正(VR)', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + 'On (1)' => 'オン(1)', + 'On (2)' => 'オン(2)', + 'On (3)' => 'オン(3)', + 'n/a' => '該当無ã—', + }, + }, + 'VideoCardGamma' => 'ビデオカードガンマ', + 'ViewInfoDuringExposure' => { + Description => '露光中ã®ãƒ•ァインダー内表示', + PrintConv => { + 'Disable' => 'ã—ãªã„', + 'Enable' => 'ã™ã‚‹', + }, + }, + 'ViewfinderWarning' => { + Description => 'ビューファインダー警告表示', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'ViewingCondDesc' => '視è´çŠ¶æ…‹èª¬æ˜Ž', + 'ViewingCondIlluminant' => '視è´çŠ¶æ…‹å…‰æº', + 'ViewingCondIlluminantType' => '視è´çŠ¶æ…‹å…‰æºã‚¿ã‚¤ãƒ—', + 'ViewingCondSurround' => '視è´çŠ¶æ…‹å‘¨è¾º', + 'ViewingConditions' => '視è´çŠ¶æ…‹å…‰æº', + 'VignetteControl' => { + Description => 'ビãƒãƒƒãƒˆã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«', + PrintConv => { + 'High' => '高ã„', + 'Low' => '低ã„', + 'Normal' => '標準', + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'VignetteControlIntensity' => 'ビãƒãƒƒãƒˆã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«å¼·åº¦', + 'VoiceMemo' => { + Description => 'ボイスメモ', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'WBAdjData' => 'ホワイトãƒãƒ©ãƒ³ã‚¹èª¿æ•´ãƒ‡ãƒ¼ã‚¿', + 'WBAdjLighting' => { + Description => 'ホワイトãƒãƒ©ãƒ³ã‚¹èª¿æ•´ã€ãƒ©ã‚¤ãƒ†ã‚£ãƒ³ã‚°', + PrintConv => { + 'Daylight' => '昼光', + 'Flash' => 'ストロボ', + 'High Color Rendering Fluorescent (3700K)' => 'ãƒã‚¤ã‚«ãƒ©ãƒ¼ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°è›å…‰ç¯ (1)', + 'High Color Rendering Fluorescent (5000K)' => 'ãƒã‚¤ã‚«ãƒ©ãƒ¼ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°è›å…‰ç¯ (3)', + 'High Color Rendering Fluorescent (cool white)' => 'ãƒã‚¤ã‚«ãƒ©ãƒ¼ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°è›å…‰ç¯ (2)', + 'High Color Rendering Fluorescent (daylight)' => 'ãƒã‚¤ã‚«ãƒ©ãƒ¼ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°è›å…‰ç¯ (4)', + 'High Color Rendering Fluorescent (warm white)' => 'ãƒã‚¤ã‚«ãƒ©ãƒ¼ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°è›å…‰ç¯ (0)', + 'Incandescent' => 'é›»çƒ', + 'None' => 'ç„¡ã—', + 'Standard Fluorescent (3700K)' => '標準è›å…‰ç¯ (1)', + 'Standard Fluorescent (5000K)' => '標準è›å…‰ç¯ (3)', + 'Standard Fluorescent (6500K)' => '標準è›å…‰ç¯ (4)', + 'Standard Fluorescent (cool white)' => '標準è›å…‰ç¯ (2)', + 'Standard Fluorescent (high temperature mercury vapor)' => '標準è›å…‰ç¯ (5)', + 'Standard Fluorescent (warm white)' => '標準è›å…‰ç¯ (0)', + }, + }, + 'WBAdjMode' => { + Description => 'ホワイトãƒãƒ©ãƒ³ã‚¹èª¿æ•´ãƒ¢ãƒ¼ãƒ‰', + PrintConv => { + 'Calculate Automatically' => '自動計算', + 'Recorded Value' => '記録値', + 'Use Gray Point' => 'グレーãƒã‚¤ãƒ³ãƒˆä½¿ç”¨', + 'Use Temperature' => '温度使用', + }, + }, + 'WBAdjTemperature' => 'ホワイトãƒãƒ©ãƒ³ã‚¹èª¿æ•´ã€è‰²æ¸©åº¦', + 'WBBlueLevel' => 'ホワイトãƒãƒ©ãƒ³ã‚¹é’レベル', + 'WBBracketMode' => { + Description => 'ホワイトãƒãƒ©ãƒ³ã‚¹ãƒ–ラケットモード', + PrintConv => { + 'Off' => 'オフ', + 'On (shift AB)' => 'オン(シフトAB)', + 'On (shift GM)' => 'オン(シフトGM)', + }, + }, + 'WBBracketValueAB' => 'ホワイトãƒãƒ©ãƒ³ã‚¹ãƒ–ラケット値AB', + 'WBBracketValueGM' => 'ホワイトãƒãƒ©ãƒ³ã‚¹ãƒ–ラケット値GM', + 'WBFineTuneActive' => { + PrintConv => { + 'No' => 'ã„ã„ãˆ', + 'Yes' => 'ã¯ã„', + }, + }, + 'WBGreenLevel' => 'ホワイトãƒãƒ©ãƒ³ã‚¹ç·‘レベル', + 'WBMediaImageSizeSetting' => { + Description => 'WB/メディア・画åƒã‚µã‚¤ã‚ºã®è¨­å®š', + PrintConv => { + 'LCD monitor' => '液晶モニター', + 'Rear LCD panel' => '背é¢è¡¨ç¤ºãƒ‘ãƒãƒ«', + }, + }, + 'WBMode' => { + Description => 'ホワイトãƒãƒ©ãƒ³ã‚¹ãƒ¢ãƒ¼ãƒ‰', + PrintConv => { + 'Auto' => 'オート', + }, + }, + 'WBRedLevel' => 'ホワイトãƒãƒ©ãƒ³ã‚¹èµ¤ãƒ¬ãƒ™ãƒ«', + 'WBShiftAB' => 'WB AB補正', + 'WBShiftGM' => 'WB GM補正', + 'WB_GBRGLevels' => 'WB GBRG レベル', + 'WB_GRBGLevels' => 'WB GRBG レベル', + 'WB_GRGBLevels' => 'WB GRGB レベル', + 'WB_RBGGLevels' => 'WB RBGG レベル', + 'WB_RBLevels' => 'WB RBレベル', + 'WB_RBLevels3000K' => 'WB RGGB3000Kレベル', + 'WB_RBLevels3300K' => 'WB RGGB3300Kレベル', + 'WB_RBLevels3600K' => 'WB RGGB3600Kレベル', + 'WB_RBLevels3900K' => 'WB RGGB3800Kレベル', + 'WB_RBLevels4000K' => 'WB RGGB4000Kレベル', + 'WB_RBLevels4300K' => 'WB RGGB4300Kレベル', + 'WB_RBLevels4500K' => 'WB RGGB4500Kレベル', + 'WB_RBLevels4800K' => 'WB RGGB4800Kレベル', + 'WB_RBLevels5300K' => 'WB RGGB5300Kレベル', + 'WB_RBLevels6000K' => 'WB RGGB6000Kレベル', + 'WB_RBLevels6600K' => 'WB RGGB6600Kレベル', + 'WB_RBLevels7500K' => 'WB RGGB7500Kレベル', + 'WB_RBLevelsAuto' => 'WB RB レベル オート', + 'WB_RBLevelsCloudy' => 'WB RB レベル 曇天', + 'WB_RBLevelsCoolWhiteFluor' => 'WB RB レベル 冷白è›å…‰ç¯', + 'WB_RBLevelsDayWhiteFluor' => 'WB RB レベル 昼白è›å…‰ç¯', + 'WB_RBLevelsDaylightFluor' => 'WB RB レベル 昼光è›å…‰ç¯', + 'WB_RBLevelsEveningSunlight' => 'WB RB レベル 夕日', + 'WB_RBLevelsFineWeather' => 'WB RB レベル 晴天', + 'WB_RBLevelsShade' => 'WB RB レベル 日陰', + 'WB_RBLevelsTungsten' => 'WB RB レベル 白熱ç¯', + 'WB_RBLevelsUsed' => 'WB RB レベル使用', + 'WB_RBLevelsWhiteFluorescent' => 'WB RB レベル 白è›å…‰ç¯', + 'WB_RGBGLevels' => 'WB RGBG レベル', + 'WB_RGBLevels' => 'WB RGB レベル', + 'WB_RGBLevelsCloudy' => 'WB RGB レベル 曇天', + 'WB_RGBLevelsDaylight' => 'WB RGB レベル 昼光', + 'WB_RGBLevelsFlash' => 'WB RGB レベル ストロボ', + 'WB_RGBLevelsFluorescent' => 'WB RGGB レベル è›å…‰ç¯', + 'WB_RGBLevelsShade' => 'WB RGB レベル 日陰', + 'WB_RGBLevelsTungsten' => 'WB RGB レベル 白熱ç¯', + 'WB_RGGBLevels' => 'WB RGGB レベル', + 'WB_RGGBLevelsCloudy' => 'WB RGGB レベル 曇天', + 'WB_RGGBLevelsDaylight' => 'WB RGGB レベル 昼光', + 'WB_RGGBLevelsFlash' => 'WB RGGB レベル ストロボ', + 'WB_RGGBLevelsFluorescent' => 'WB RGGB レベル è›å…‰ç¯', + 'WB_RGGBLevelsFluorescentD' => 'WB RGGB レベル è›å…‰ç¯(D)', + 'WB_RGGBLevelsFluorescentN' => 'WB RGGB レベル è›å…‰ç¯(N)', + 'WB_RGGBLevelsFluorescentW' => 'WB RGGB レベル è›å…‰ç¯(W)', + 'WB_RGGBLevelsShade' => 'WB RGGB レベル 日陰', + 'WB_RGGBLevelsTungsten' => 'WB RGGB レベル 白熱ç¯', + 'WCSProfiles' => 'Windowsカラーシステムプロフィール', + 'WhiteBalance' => { + Description => 'ホワイトãƒãƒ©ãƒ³ã‚¹', + PrintConv => { + 'Auto' => 'オート', + 'Black & White' => '白黒', + 'Cloudy' => '曇り', + 'Color Temperature/Color Filter' => '色温度マニュアル指定', + 'Cool White Fluorescent' => '白色è›å…‰ç¯', + 'Custom' => 'カスタム', + 'Custom 1' => 'カスタム1', + 'Custom 2' => 'カスタム2', + 'Custom 3' => 'カスタム3', + 'Custom 4' => 'カスタム4', + 'Custom2' => 'カスタム2', + 'Custom3' => 'カスタム3', + 'Custom4' => 'カスタム4', + 'Custom5' => 'カスタム5', + 'Day White Fluorescent' => '昼白色è›å…‰ç¯', + 'Daylight' => '昼光', + 'Daylight Fluorescent' => '昼光色è›å…‰ç¯', + 'Flash' => 'ストロボ', + 'Fluorescent' => 'è›å…‰ç¯', + 'Incandescent' => 'é›»çƒ', + 'Kelvin' => 'ケルビン', + 'Living Room Warm White Fluorescent' => 'リビング暖白光色è›å…‰ç¯', + 'Manual' => 'マニュアル', + 'Manual Temperature (Kelvin)' => 'マニュアル白熱ç¯ï¼ˆã‚±ãƒ«ãƒ“ン)', + 'PC Set1' => 'PC設定1', + 'PC Set2' => 'PC設定2', + 'PC Set3' => 'PC設定3', + 'PC Set4' => 'PC設定4', + 'PC Set5' => 'PC設定5', + 'Shade' => '日陰', + 'Tungsten' => 'タングステン(白熱ç¯)', + 'Underwater' => '水中', + 'Unknown' => '䏿˜Ž', + 'User-Selected' => 'ãƒ¦ãƒ¼ã‚¶é¸æŠž', + 'Warm White Fluorescent' => '暖白光色è›å…‰ç¯', + 'White Fluorescent' => '温白色è›å…‰ç¯', + }, + }, + 'WhiteBalance2' => { + Description => 'ホワイトãƒãƒ©ãƒ³ã‚¹2', + PrintConv => { + '3000K (Tungsten light)' => '3000K (白熱電çƒ)', + '3600K (Tungsten light-like)' => '3600K (白熱電çƒ-like)', + '4000K (Cool white fluorescent)' => '4000K (è›å…‰ç¯ï¼‘)', + '4500K (Neutral white fluorescent)' => '4500K (è›å…‰ç¯ï¼’)', + '5300K (Fine Weather)' => '5300K (晴天)', + '6000K (Cloudy)' => '6000K (曇り)', + '6600K (Daylight fluorescent)' => '6600K (è›å…‰ç¯ï¼“)', + '7500K (Fine Weather with Shade)' => '7500K (晴天ã¨å½±)', + 'Auto' => 'オート', + 'Custom WB 1' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹1', + 'Custom WB 2' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹2', + 'Custom WB 2900K' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹2900K', + 'Custom WB 3' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹3', + 'Custom WB 4' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹4', + 'Custom WB 5400K' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹5400K', + 'Custom WB 8000K' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹8000K', + 'One Touch WB 1' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹1', + 'One Touch WB 2' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹2', + 'One Touch WB 3' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹3', + 'One Touch WB 4' => 'カスタムホワイトãƒãƒ©ãƒ³ã‚¹4', + }, + }, + 'WhiteBalanceAdj' => { + Description => 'ホワイトãƒãƒ©ãƒ³ã‚¹èª¿æ•´', + PrintConv => { + 'Auto' => 'オート', + 'Cloudy' => '曇り', + 'Daylight' => '昼光', + 'Flash' => 'ストロボ', + 'Fluorescent' => 'è›å…‰ç¯', + 'Off' => 'オフ', + 'On' => 'オン', + 'Shade' => '日陰', + 'Tungsten' => 'タングステン(白熱ç¯)', + }, + }, + 'WhiteBalanceBias' => 'ホワイトãƒãƒ©ãƒ³ã‚¹ãƒã‚¤ã‚¢ã‚¹', + 'WhiteBalanceBracket' => 'ホワイトãƒãƒ©ãƒ³ã‚¹ãƒ–ラケット', + 'WhiteBalanceComp' => 'ホワイトãƒãƒ©ãƒ³ã‚¹è£œæ­£', + 'WhiteBalanceFineTune' => 'ホワイトãƒãƒ©ãƒ³ã‚¹ãƒ•ァインãƒãƒ¥ãƒ¼ãƒ³', + 'WhiteBalanceMode' => { + Description => 'ホワイトãƒãƒ©ãƒ³ã‚¹ãƒ¢ãƒ¼ãƒ‰', + PrintConv => { + 'Auto (Cloudy)' => 'オート(曇天)', + 'Auto (Day White Fluorescent)' => 'オート(昼白色è›å…‰ç¯ï¼‰', + 'Auto (Daylight Fluorescent)' => 'オート(昼光色è›å…‰ç¯ï¼‰', + 'Auto (Daylight)' => 'オート(昼光)', + 'Auto (Flash)' => 'オート(ストロボ)', + 'Auto (Shade)' => 'オート(日陰)', + 'Auto (Tungsten)' => 'オート(白熱ç¯ï¼‰', + 'Auto (White Fluorescent)' => 'オート(白色è›å…‰ç¯ï¼‰', + 'Unknown' => '䏿˜Ž', + 'User-Selected' => 'ãƒ¦ãƒ¼ã‚¶ãƒ¼é¸æŠž', + }, + }, + 'WhiteBalanceSet' => { + Description => 'ホワイトãƒãƒ©ãƒ³ã‚¹è¨­å®š', + PrintConv => { + 'Auto' => 'オート', + 'Cloudy' => '曇り', + 'Day White Fluorescent' => '昼白色è›å…‰ç¯', + 'Daylight' => '昼光', + 'Daylight Fluorescent' => '昼光色è›å…‰ç¯', + 'Flash' => 'ストロボ', + 'Manual' => 'マニュアル', + 'Set Color Temperature 1' => '色温度設定1', + 'Set Color Temperature 2' => '色温度設定2', + 'Set Color Temperature 3' => '色温度設定3', + 'Shade' => '日陰', + 'Tungsten' => 'タングステン(白熱ç¯)', + 'White Fluorescent' => '温白色è›å…‰ç¯', + }, + }, + 'WhiteBalanceTemperature' => 'ホワイトãƒãƒ©ãƒ³ã‚¹æ¸©åº¦', + 'WhiteBoard' => 'ホワイトボード', + 'WhiteLevel' => '白レベル', + 'WhitePoint' => '白点色度', + 'Wide' => 'ワイド', + 'WideFocusZone' => 'ワイドフォーカスゾーン', + 'WideRange' => { + Description => 'ワイドレンジ', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'WidthResolution' => 'å¹…æ–¹å‘ã®ç”»åƒè§£åƒåº¦', + 'WorldTime' => 'タイムゾーン', + 'WorldTimeLocation' => { + Description => 'ワールドタイムä½ç½®', + PrintConv => { + 'Destination' => '目的地', + 'Home' => '自宅', + 'Hometown' => 'ç¾åœ¨åœ°', + }, + }, + 'Writer-Editor' => '表題/説明ã®ä½œå®¶', + 'X3FillLight' => 'X3光を満ãŸã™', + 'XClipPathUnits' => 'Xクリップパスå˜ä½', + 'XMP' => 'XMPメタデータ', + 'XPAuthor' => '作者', + 'XPComment' => 'コメント', + 'XPKeywords' => 'キーワード', + 'XPSubject' => 'サブジェクト', + 'XPTitle' => 'タイトル', + 'XPosition' => 'Xä½ç½®', + 'XResolution' => 'ç”»åƒå¹…ã®è§£åƒåº¦', + 'YCbCrCoefficients' => 'カラースペース変化マトリックス係数', + 'YCbCrPositioning' => { + Description => 'Yã¨Cã®ä½ç½®', + PrintConv => { + 'Centered' => '中央', + 'Co-sited' => '相互é…ç½®', + }, + }, + 'YCbCrSubSampling' => 'Yã¨Cã®ã‚µãƒ–サンプリング比率', + 'YClipPathUnits' => 'Yクリップパスå˜ä½', + 'YPosition' => 'Yä½ç½®', + 'YResolution' => 'ç”»åƒé«˜ã•ã®è§£åƒåº¦', + 'ZoneMatching' => { + Description => 'ゾーンマッãƒãƒ³ã‚°', + PrintConv => { + 'High Key' => 'ãƒã‚¤ã‚­ãƒ¼', + 'ISO Setting Used' => 'オフ(ISO設定使用)', + 'Low Key' => 'ローキー', + }, + }, + 'ZoneMatchingOn' => { + Description => 'ゾーンマッãƒãƒ³ã‚°', + PrintConv => { + 'Off' => 'オフ', + 'On' => 'オン', + }, + }, + 'Zoom' => 'ズーム', + 'ZoomSourceWidth' => 'ズームソース幅', + 'ZoomStepCount' => 'ズームステップ数', + 'ZoomTargetWidth' => 'ズームターゲット幅', +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::ja.pm - ExifTool Japanese language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke and Kazunari Nishina for providing this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/ko.pm b/ExifTool/lib/Image/ExifTool/Lang/ko.pm new file mode 100644 index 0000000..f2b72d5 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/ko.pm @@ -0,0 +1,2333 @@ +#------------------------------------------------------------------------------ +# File: ko.pm +# +# Description: ExifTool Korean language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::ko; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.07'; + +%Image::ExifTool::Lang::ko::Translate = ( + 'AELock' => { + Description => 'AE ê³ ì •', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'AELockButton' => { + Description => 'AE-L/AF-L', + PrintConv => { + 'AE Lock (hold)' => 'AE ê³ ì •(유지)', + 'AE Lock Only' => 'AE ê³ ì •', + 'AE/AF Lock' => 'AE/AF ê³ ì •', + 'AF Lock Only' => 'AF ê³ ì •', + }, + }, + 'AF-CPrioritySelection' => { + Description => 'AF-C ì„ íƒ ìš°ì„ ', + PrintConv => { + 'Focus' => 'í¬ì»¤ìФ', + 'Release' => '릴리즈', + 'Release + Focus' => '릴리즈 + í¬ì»¤ìФ', + }, + }, + 'AF-OnForMB-D10' => { + Description => 'MB-D10ì—서 AF-On', + PrintConv => { + 'AE Lock (hold)' => 'AE ê³ ì • (유지)', + 'AE Lock (reset on release)' => 'AE ê³ ì • (릴리즈때 리셋)', + 'AE Lock Only' => 'AE ê³ ì •', + 'AE/AF Lock' => 'AE/AF ê³ ì •', + 'AF Lock Only' => 'AF ê³ ì •', + 'AF-On' => 'AF-ON', + 'Same as FUNC Button' => 'FUNC 버튼과 ê°™ìŒ', + }, + }, + 'AF-SPrioritySelection' => { + Description => 'AF-S ì„ íƒ ìš°ì„ ', + PrintConv => { + 'Focus' => 'í¬ì»¤ìФ', + 'Release' => '릴리즈', + }, + }, + 'AFActivation' => { + Description => 'AF ìž‘ë™', + PrintConv => { + 'AF-On Only' => 'AF-ON ê³ ì •', + 'Shutter/AF-On' => '셔터/AF-ON', + }, + }, + 'AFAperture' => 'AF 조리개', + 'AFAreaIllumination' => { + Description => 'AF í¬ì¸íЏ 조명', + PrintConv => { + 'Auto' => 'ìžë™', + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'AFAreaMode' => { + Description => 'AF ì˜ì—­ 모드', + PrintConv => { + 'Auto-area' => 'ìžë™ ì˜ì—­ AF', + 'Dynamic Area' => '다ì´ë‚´ë¯¹ ì˜ì—­', + 'Dynamic Area (closest subject)' => '다ì´ë‚´ë¯¹ ì˜ì—­ (지근거리 ìš°ì„ )', + 'Dynamic Area (wide)' => '다ì´ë‚´ë¯¹ ì˜ì—­ (와ì´ë“œ)', + 'Group Dynamic' => '그룹 다ì´ë‚´ë¯¹', + 'Single Area' => '싱글 ì˜ì—­', + 'Single Area (wide)' => '싱글 ì˜ì—­ (와ì´ë“œ)', + }, + }, + 'AFAreaModeSetting' => { + Description => 'AF ì˜ì—­ 모드', + PrintConv => { + 'Closest Subject' => '접사', + 'Dynamic Area' => '다ì´ë‚´ë¯¹ ì˜ì—­', + 'Single Area' => '싱글 ì˜ì—­', + }, + }, + 'AFAssist' => { + Description => '내장 AF ë³´ì¡°ê´‘', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'AFFineTune' => 'AF 미세 ì¡°ì •', + 'AFFineTuneAdj' => 'AF 미세 ì¡°ì •', + 'AFInfo' => 'AF 모드', + 'AFInfo2' => 'AF ì •ë³´', + 'AFInfo2Version' => 'AF ì •ë³´ 버전', + 'AFMode' => 'AF 모드', + 'AFPoint' => { + Description => 'AF í¬ì¸íЏ', + PrintConv => { + 'Bottom' => '하단', + 'Center' => '중앙', + 'Far Left' => '맨 좌측', + 'Far Right' => '맨 우측', + 'Left' => '왼쪽', + 'Lower-left' => '좌하단', + 'Lower-right' => '우하단', + 'Mid-left' => '왼쪽', + 'Mid-right' => '오른쪽', + 'Right' => '오른쪽', + 'Top' => 'ìƒë‹¨', + 'Upper-left' => '좌ìƒë‹¨', + 'Upper-right' => 'ìš°ìƒë‹¨', + }, + }, + 'AFPointIllumination' => { + Description => 'AF í¬ì¸íЏ 조명', + PrintConv => { + 'Auto' => 'ìžë™', + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'AFPointMode' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'AFPointSelected' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'AFPointSelected2' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'AFPointSelection' => { + Description => 'AF í¬ì¸íЏ ì„ íƒ', + PrintConv => { + '11 Points' => '11 í¬ì¸íЏ', + '51 Points' => '51 í¬ì¸íЏ', + }, + }, + 'AFPointsInFocus' => { + Description => 'ì´ˆì ì—ì„œì˜ AF í¬ì¸íЏ', + PrintConv => { + 'Far Left' => '맨 좌측', + 'Far Right' => '맨 우측', + 'Lower-left' => '좌하단', + 'Lower-right' => '우하단', + 'Upper-left' => '좌ìƒë‹¨', + 'Upper-right' => 'ìš°ìƒë‹¨', + }, + }, + 'AFPointsUnknown2' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'AFPointsUsed' => { + Description => 'ì‚¬ìš©ëœ AF í¬ì¸íЏ', + PrintConv => { + 'Bottom' => '하단', + 'Center' => '중앙', + 'Far Left' => '맨 좌측', + 'Far Right' => '맨 우측', + 'Lower-left' => '좌하단', + 'Lower-right' => '우하단', + 'Top' => 'ìƒë‹¨', + 'Upper-left' => '좌ìƒë‹¨', + 'Upper-right' => 'ìš°ìƒë‹¨', + }, + }, + 'AFResponse' => 'AF ë°˜ì‘', + 'ActiveD-Lighting' => { + Description => '액티브 D-Lighting', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ActiveD-LightingMode' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'AddAspectRatioInfo' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'AddOriginalDecisionData' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'AdvancedRaw' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Album' => '앨범', + 'Anti-Blur' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'Aperture' => '조리개', + 'ApertureValue' => '조리개', + 'ApplicationRecordVersion' => '어플리케ì´ì…˜ ê¸°ë¡ ë²„ì „', + 'Artist' => 'ì´ë¯¸ì§€ë¥¼ 만든 사람', + 'Author' => '작성ìž', + 'AuthorsPosition' => 'ì§ì±…', + 'AutoAperture' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'AutoBracketModeM' => { + Description => 'ìžë™ 브ë¼ì¼€íŒ… (모드 M)', + PrintConv => { + 'Flash Only' => '플래시', + 'Flash/Aperture' => '플래시/조리개', + 'Flash/Speed' => '플래시/스피드', + 'Flash/Speed/Aperture' => '플래시/스피드/조리개', + }, + }, + 'AutoBracketOrder' => '브ë¼ì¼€íŒ… 순서', + 'AutoBracketSet' => { + Description => 'ìžë™ 브ë¼ì¼€íŒ… 설정', + PrintConv => { + 'AE & Flash' => 'AE & 플래시', + 'AE Only' => 'AE 브ë¼ì¼€íŒ…', + 'Flash Only' => '플래시 브ë¼ì¼€íŒ…', + 'WB Bracketing' => 'WB 브ë¼ì¼€íŒ…', + }, + }, + 'AutoBracketing' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'AutoBracketingSet' => 'ìžë™ 브ë¼ì¼€íŒ… 설정', + 'AutoExposureBracketing' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'AutoFP' => { + Description => 'ìžë™ FP', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'AutoFocus' => { + Description => '오토í¬ì»¤ìФ', + PrintConv => { + 'Off' => '비활성', + 'On' => '활성', + }, + }, + 'AutoISO' => { + Description => 'ìžë™ ISO', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'AutoISOMax' => 'ìžë™ ISO 최대 ê°ë„', + 'AutoISOMinShutterSpeed' => 'ìžë™ ISO 최소 셔터 ì†ë„', + 'AutoLightingOptimizer' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'AutoRedEye' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'AuxiliaryLens' => 'ë³´ì¡° 렌즈', + 'BWMode' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'BatteryLevel' => '베터리 레벨', + 'BatteryOrder' => { + Description => '배터리 순서', + PrintConv => { + 'Camera Battery First' => 'ì¹´ë©”ë¼ ë°°í„°ë¦¬ ìš°ì„ ', + 'MB-D10 First' => 'MB-D10 배터리 ìš°ì„ ', + }, + }, + 'Beep' => { + Description => 'ì „ìžìŒ', + PrintConv => { + 'High' => 'í¬ê²Œ', + 'Low' => '작게', + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'BitsPerSample' => 'ì„±ë¶„ì— ë”°ë¥¸ 비트 수', + 'BracketMode' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'Brightness' => 'ë°ê¸°', + 'BrightnessValue' => 'ë°ê¸°', + 'BurstMode' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'By-line' => '제작ìž', + 'CFAPattern' => 'CFA 패턴', + 'CLModeShootingSpeed' => 'CL 모드 ì´¬ì˜ ì†ë„', + 'CalibrationIlluminant1' => { + PrintConv => { + 'Cloudy' => 'í린 날씨', + 'Cool White Fluorescent' => '냉백색 형광등 (W 3800 - 4500K)', + 'Day White Fluorescent' => '주백색 형광등 (N 4600 - 5500K)', + 'Daylight' => '주광', + 'Daylight Fluorescent' => '주광색 형광등 (D 5700 - 7100K)', + 'Fine Weather' => 'ë§‘ì€ ë‚ ì”¨', + 'Flash' => '플래시', + 'Fluorescent' => '형광등', + 'ISO Studio Tungsten' => 'ISO 스튜디오 텅스í…', + 'Other' => '기타 ê´‘ì›', + 'Shade' => '그늘', + 'Standard Light A' => '표준 ê´‘ì› A', + 'Standard Light B' => '표준 ê´‘ì› B', + 'Standard Light C' => '표준 ê´‘ì› C', + 'Tungsten (Incandescent)' => 'í……ìŠ¤í… (백열등)', + 'Unknown' => '알 수 ì—†ìŒ', + 'Warm White Fluorescent' => '따뜻한 í°ìƒ‰ 형광 (L 2600 - 3250K)', + 'White Fluorescent' => '백색 형광등 (WW 3250 - 3800K)', + }, + }, + 'CalibrationIlluminant2' => { + PrintConv => { + 'Cloudy' => 'í린 날씨', + 'Cool White Fluorescent' => '냉백색 형광등 (W 3800 - 4500K)', + 'Day White Fluorescent' => '주백색 형광등 (N 4600 - 5500K)', + 'Daylight' => '주광', + 'Daylight Fluorescent' => '주광색 형광등 (D 5700 - 7100K)', + 'Fine Weather' => 'ë§‘ì€ ë‚ ì”¨', + 'Flash' => '플래시', + 'Fluorescent' => '형광등', + 'ISO Studio Tungsten' => 'ISO 스튜디오 텅스í…', + 'Other' => '기타 ê´‘ì›', + 'Shade' => '그늘', + 'Standard Light A' => '표준 ê´‘ì› A', + 'Standard Light B' => '표준 ê´‘ì› B', + 'Standard Light C' => '표준 ê´‘ì› C', + 'Tungsten (Incandescent)' => 'í……ìŠ¤í… (백열등)', + 'Unknown' => '알 수 ì—†ìŒ', + 'Warm White Fluorescent' => '따뜻한 í°ìƒ‰ 형광 (L 2600 - 3250K)', + 'White Fluorescent' => '백색 형광등 (WW 3250 - 3800K)', + }, + }, + 'CanonExposureMode' => { + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'CanonFlashMode' => { + PrintConv => { + 'Auto' => 'ìžë™', + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Caption-Abstract' => '제목/설명', + 'CaptionWriter' => '캡션 작성ìž', + 'Categories' => '범주', + 'Category' => '범주', + 'CenterAFArea' => { + Description => '중앙 ì´ˆì  ì˜ì—­', + PrintConv => { + 'Normal Zone' => 'ì¼ë°˜ ì˜ì—­', + 'Wide Zone' => '와ì´ë“œ ì˜ì—­', + }, + }, + 'CenterWeightedAreaSize' => '중앙 ì¤‘ì  ì˜ì—­', + 'ChrominanceNR_TIFF_JPEG' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'ChrominanceNoiseReduction' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'City' => 'ë„시', + 'CodedCharacterSet' => 'ì½”ë“œëœ ìºë¦­í„° 세트', + 'ColorAberrationControl' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ColorBalance' => '컬러 밸런스', + 'ColorBalanceAdj' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ColorBalanceVersion' => '컬러 밸런스 버전', + 'ColorBooster' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ColorEffect' => { + PrintConv => { + 'Off' => '꺼ì§', + 'Sepia' => '세피아', + }, + }, + 'ColorFilter' => { + Description => '색 í•„í„°', + PrintConv => { + 'Green' => '녹색', + 'Off' => '꺼ì§', + 'Red' => '빨강', + 'Yellow' => '노랑', + }, + }, + 'ColorHue' => 'ìƒ‰ìƒ í˜•ì‹', + 'ColorMode' => { + Description => '컬러 모드', + PrintConv => { + 'Autumn Leaves' => '단í’', + 'B&W' => 'í‘ë°±', + 'Clear' => '반투명', + 'Deep' => '진한', + 'Landscape' => 'í’ê²½', + 'Light' => 'ë¼ì´íЏ', + 'Neutral' => '뉴트럴', + 'Night View' => '야경', + 'Night View/Portrait' => '야경 ì¸ë¬¼', + 'Off' => '꺼ì§', + 'Portrait' => 'ì¸ë¬¼', + 'Standard' => '표준', + 'Sunset' => 'ì¼ëª°', + 'Vivid' => 'ìƒìƒí•œ', + }, + }, + 'ColorMoireReduction' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ColorMoireReductionMode' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'ColorSpace' => { + Description => '색공간', + PrintConv => { + 'ICC Profile' => 'ICC 프로필', + 'Uncalibrated' => 'ì¡°ì •ë˜ì§€ 않ìŒ', + }, + }, + 'ColorTemperature' => '색 온ë„', + 'CommandDials' => { + Description => '커맨드 다ì´ì–¼', + PrintConv => { + 'Reversed (Main Aperture, Sub Shutter)' => 'ì—­ë°©í–¥', + 'Standard (Main Shutter, Sub Aperture)' => '기본', + }, + }, + 'CommandDialsApertureSetting' => { + Description => '커맨드 다ì´ì–¼ 수정 조리개 설정', + PrintConv => { + 'Aperture Ring' => '조리개 ë§', + 'Sub-command Dial' => '서브 커맨드 다ì´ì–¼', + }, + }, + 'CommandDialsChangeMainSub' => { + Description => '커맨드 다ì´ì–¼ 수정 매ì¸/서브 êµì²´', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'CommandDialsMenuAndPlayback' => { + Description => '커맨드 다ì´ì–¼ 수정 매뉴와 재ìƒ', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'CommandDialsReverseRotation' => { + Description => '커맨드 다ì´ì–¼ 수정 ë°©í–¥ 전환', + PrintConv => { + 'No' => '아니오', + 'Yes' => '예', + }, + }, + 'CommanderChannel' => '커맨드모드 채ë„', + 'CommanderGroupAManualOutput' => '커맨드모드 그룹 A M Comp', + 'CommanderGroupAMode' => { + Description => '커맨드모드 그룹 A 모드', + PrintConv => { + 'Auto Aperture' => 'ìžë™ 조리개 (AA)', + 'Manual' => '수ë™', + 'Off' => '꺼ì§', + }, + }, + 'CommanderGroupA_TTL-AAComp' => '커맨드모드 그룹 A TTL/AA Comp', + 'CommanderGroupBManualOutput' => '커맨드모드 그룹 B M Comp', + 'CommanderGroupBMode' => { + Description => '커맨드모드 그룹 B 모드', + PrintConv => { + 'Auto Aperture' => 'ìžë™ 조리개 (AA)', + 'Manual' => '수ë™', + 'Off' => '꺼ì§', + }, + }, + 'CommanderGroupB_TTL-AAComp' => '커맨드모드 그룹 B TTL/AA Comp', + 'CommanderInternalFlash' => { + Description => '커맨드모드 내장 플래시 모드', + PrintConv => { + 'Manual' => '수ë™', + 'Off' => '꺼ì§', + }, + }, + 'CommanderInternalManualOutput' => '커맨드모드 내장플래시 M Comp.', + 'CommanderInternalTTLComp' => '커맨드모드 내장플래시 TTL Comp', + 'Comment' => '코멘트', + 'ComponentsConfiguration' => 'ê° êµ¬ì„± ìš”ì†Œì˜ ì˜ë¯¸', + 'CompressedBitsPerPixel' => 'ì´ë¯¸ì§€ ì••ì¶• 모드', + 'Compression' => { + Description => 'ì••ì¶• 설계', + PrintConv => { + 'JPEG' => 'JPEG ì••ì¶•', + 'JPEG (old-style)' => 'JPEG (예전 스타ì¼)', + 'Kodak DCR Compressed' => 'Kodak DCR ì••ì¶•', + 'Kodak KDC Compressed' => 'Kodak KDC ì••ì¶•', + 'Next' => 'NeXT 2-bit ì¸ì½”딩', + 'Nikon NEF Compressed' => 'Nikon NEF ì••ì¶•', + 'Pentax PEF Compressed' => 'Pentax PEF ì••ì¶•', + 'SGILog' => 'SGI 32-bit Log íœ˜ë„ ì¸ì½”딩', + 'SGILog24' => 'SGI 24-bit Log íœ˜ë„ ì¸ì½”딩', + 'Sony ARW Compressed' => 'Sony ARW ì••ì¶•', + 'Thunderscan' => 'ThunderScan 4-bit ì¸ì½”딩', + 'Uncompressed' => '무압축', + }, + }, + 'Contact' => 'ì—°ë½ì²˜', + 'Contrast' => { + Description => '대비', + PrintConv => { + 'High' => '강하게', + 'Low' => '약하게', + 'Normal' => '표준', + }, + }, + 'ContrastCurve' => '대비 커브', + 'ConversionLens' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'Copyright' => '저작권 소유ìž', + 'CopyrightNotice' => '저작권 공고', + 'Country' => '국명', + 'Country-PrimaryLocationName' => 'êµ­ê°€', + 'CreateDate' => '디지털 ë°ì´í„° ìƒì„± ì¼ì‹œ', + 'CreationDate' => 'ì´¬ì˜ ë‚ ì§œ', + 'Credit' => 'ì •ë³´', + 'Curves' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'CustomRendered' => { + Description => 'ì‚¬ìš©ìž ì´ë¯¸ì§€ 처리', + PrintConv => { + 'Custom' => 'ì‚¬ìš©ìž ì²˜ë¦¬', + 'Normal' => '표준 처리', + }, + }, + 'D-LightingHQ' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'D-LightingHS' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'DataDump' => 'ë°ì´í„° ë¤í”„', + 'DateCreated' => '만들어진 ë‚ ì§œ', + 'DateDisplayFormat' => { + Description => 'ë‚ ì§œ 형ì‹', + PrintConv => { + 'D/M/Y' => 'ì¼/ì›”/ë…„', + 'M/D/Y' => 'ì›”/ì¼/ë…„', + 'Y/M/D' => 'ë…„/ì›”/ì¼', + }, + }, + 'DateStampMode' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'DateTimeOriginal' => 'ì›ë³¸ ë°ì´í„° ìƒì„± ì¼ì‹œ', + 'DaylightSavings' => { + Description => 'ì¼ê´‘ 시간 절약', + PrintConv => { + 'No' => '꺼ì§', + 'Yes' => '켜ì§', + }, + }, + 'DeletedImageCount' => 'ì‚­ì œëœ ì´ë¯¸ì§€ 카운트', + 'DeviceSettingDescription' => '장비 설정 설명', + 'DigitalZoom' => { + Description => '디지털 줌', + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'DigitalZoomRatio' => '디지털 줌 비율', + 'Directory' => '파ì¼ìœ„치', + 'DirectoryNumber' => '디렉토리 숫ìž', + 'DistortionCorrection' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'DistortionCorrection2' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'DriveMode' => { + Description => '드ë¼ì´ë¸Œ 모드', + PrintConv => { + 'Multiple Exposure' => '다중 노출', + 'Off' => '꺼ì§', + }, + }, + 'DynamicAFArea' => { + Description => '다ì´ë‚´ë¯¹ AF ì˜ì—­', + PrintConv => { + '21 Points' => '21 í¬ì¸íЏ', + '51 Points' => '51 í¬ì¸íЏ', + '51 Points (3D-tracking)' => '51í¬ì¸íЏ (3D-트래킹)', + '9 Points' => '9 í¬ì¸íЏ', + }, + }, + 'DynamicRangeExpansion' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'DynamicRangeOptimizer' => { + Description => 'D-ë ˆì¸ì§€ 최ì í™”', + PrintConv => { + 'Advanced Auto' => '고급 ìžë™', + 'Advanced Lv1' => '고급 레벨1', + 'Advanced Lv2' => '고급 레벨2', + 'Advanced Lv3' => '고급 레벨3', + 'Advanced Lv4' => '고급 레벨4', + 'Advanced Lv5' => '고급 레벨5', + 'Auto' => 'ìžë™', + 'Off' => '꺼ì§', + 'Standard' => '표준', + }, + }, + 'EVStepSize' => { + Description => '노출 설정 간격', + PrintConv => { + '1/2 EV' => '1/2 스í…', + '1/3 EV' => '1/3 스í…', + }, + }, + 'EasyExposureCompensation' => { + Description => '쉬운 노출 ë³´ì •', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + 'On (auto reset)' => 'ì¼œì§ (ìžë™ 리셋)', + }, + }, + 'EasyMode' => { + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'EdgeNoiseReduction' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'EffectiveMaxAperture' => '유효 최대 조리개', + 'EnhanceDarkTones' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Enhancement' => { + PrintConv => { + 'Green' => '녹색', + 'Off' => '꺼ì§', + 'Red' => '빨강', + }, + }, + 'EnvelopeRecordVersion' => 'ì••ì¶• ê¸°ë¡ ë²„ì „', + 'ExifImageHeight' => 'ì´ë¯¸ì§€ 높ì´', + 'ExifImageWidth' => 'ì´ë¯¸ì§€ ë„“ì´', + 'ExifOffset' => 'Exif IFD Pointer', + 'ExifVersion' => 'Exif 버전', + 'ExitPupilPosition' => '출구공 위치', + 'ExposureBracketValue' => '노출 브ë¼ì¼€íŒ… ê°’', + 'ExposureCompStepSize' => '노출보정/미세조정', + 'ExposureCompensation' => '노출 ë³´ì •', + 'ExposureControlStepSize' => '노출 ì¡°ì • EV 스í…', + 'ExposureDelayMode' => { + Description => '노출 대기 모드', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ExposureDifference' => '노출 ë³´ì •', + 'ExposureIndex' => '노출 ì¸ë±ìФ', + 'ExposureMode' => { + Description => '노출 모드', + PrintConv => { + 'Auto' => 'ìžë™ 노출', + 'Auto bracket' => '오토 브ë¼ì¼€ìŠ¤íŒ…', + 'Manual' => 'ìˆ˜ë™ ë…¸ì¶œ', + }, + }, + 'ExposureProgram' => { + Description => '노출 프로그램', + PrintConv => { + 'Action (High speed)' => '스í¬ì¸  모드 (빠른 셔터 ìŠ¤í”¼ë“œì— íŽ¸í–¥ë¨)', + 'Aperture-priority AE' => '조리개 ìš°ì„ ', + 'Bulb' => '벌브', + 'Creative (Slow speed)' => 'ë…ì°½ì  í”„ë¡œê·¸ëž¨ (피사계심ë„ì— íŽ¸í–¥ë¨)', + 'Landscape' => 'í’ê²½ 모드 (ë°°ê²½ì˜ ì¸í¬ì»¤ìŠ¤ê°€ 있는 í’ê²½ 사진)', + 'Manual' => '수ë™', + 'Not Defined' => 'ì •ì˜ë˜ì§€ 않ìŒ', + 'Portrait' => 'ì¸ë¬¼ 모드 (ë°°ê²½ì˜ ì•„ì›ƒí¬ì»¤ìŠ¤ê°€ 있는 근접 사진)', + 'Program AE' => '보통 프로그램', + 'Shutter speed priority AE' => '셔터 ìš°ì„ ', + }, + }, + 'ExposureTime' => '노출 시간', + 'ExtendedWBDetect' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ExternalFlash' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ExternalFlashFlags' => '외장 플래시 플래그', + 'ExternalFlashMode' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'FNumber' => 'F 숫ìž', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => '0° (위쪽/좌측)', + 'Rotate 180' => '180° (아래/우측)', + 'Rotate 270 CW' => '90° 시계방향 (좌측/아래)', + 'Rotate 90 CW' => '90° 반시계방향 (우측/위쪽)', + }, + }, + 'FileFormat' => '형ì‹', + 'FileInfo' => 'íŒŒì¼ ì •ë³´', + 'FileInfoVersion' => 'íŒŒì¼ ì •ë³´ 버전', + 'FileModifyDate' => '갱신 ì¼ìž', + 'FileName' => '파ì¼ëª…', + 'FileNumber' => 'íŒŒì¼ ìˆ«ìž', + 'FileNumberMemory' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'FileNumberSequence' => { + Description => '파ì¼ëª… ì—°ì† ë²ˆí˜¸', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'FileSize' => '파ì¼í¬ê¸°', + 'FileSource' => { + Description => 'íŒŒì¼ ì¶œì²˜', + PrintConv => { + 'Digital Camera' => '디지털 ì¹´ë©”ë¼', + 'Film Scanner' => '필름 스ìºë„ˆ', + 'Reflection Print Scanner' => '반사 프린트 스ìºë„ˆ', + }, + }, + 'FileType' => '파ì¼í˜•ì‹', + 'Filename' => '파ì¼ëª…', + 'FilmType' => '필름 형ì‹', + 'Filter' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'FilterEffect' => { + Description => 'í•„í„° 효과', + PrintConv => { + 'Green' => '녹색', + 'Off' => '꺼ì§', + 'Orange' => '주황', + 'Red' => '빨강', + 'Yellow' => '노랑', + 'n/a' => '설정 안ë¨', + }, + }, + 'FilterEffectMonochrome' => { + PrintConv => { + 'Green' => '녹색', + 'Orange' => '주황', + 'Red' => '빨강', + 'Yellow' => '노랑', + }, + }, + 'FinderDisplayDuringExposure' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'FineTuneOptCenterWeighted' => 'ìµœì  ë…¸ì¶œ ì¡°ì • 중앙 중ì ', + 'FineTuneOptMatrixMetering' => 'ìµœì  ë…¸ì¶œ ì¡°ì • í‰ê°€ 측광', + 'FineTuneOptSpotMetering' => 'ìµœì  ë…¸ì¶œ ì¡°ì • 스팟', + 'Flash' => { + Description => '플래시', + PrintConv => { + 'Auto, Did not fire' => '플래시 발광 안ë¨, ìžë™ëª¨ë“œ', + 'Auto, Did not fire, Red-eye reduction' => 'ìžë™, 발광안ë¨, ì ëª©ê°ì†Œ', + 'Auto, Fired' => '플래시 발광, ìžë™ëª¨ë“œ', + 'Auto, Fired, Red-eye reduction' => '플래시 발광, ìžë™ëª¨ë“œ, ì ëª©ê°ì†Œëª¨ë“œ', + 'Auto, Fired, Red-eye reduction, Return detected' => '플래시 발광, ìžë™ëª¨ë“œ, 복귀광 ê°ì§€ë¨, ì ëª©ê°ì†Œëª¨ë“œ', + 'Auto, Fired, Red-eye reduction, Return not detected' => '플래시 발광, ìžë™ëª¨ë“œ, 복귀광 ê°ì§€ì•ˆë¨, ì ëª©ê°ì†Œëª¨ë“œ', + 'Auto, Fired, Return detected' => '플래시 발광, ìžë™ëª¨ë“œ, 복귀광 ê°ì§€ë¨', + 'Auto, Fired, Return not detected' => '플래시 발광, ìžë™ëª¨ë“œ, 복귀광 ê°ì§€ 안ë¨', + 'Did not fire' => '플래시가 ì ë“±í•˜ì§€ 않았습니다', + 'Fired' => '플래시 발광', + 'Fired, Red-eye reduction' => '플래시 발광, ì ëª©ê°ì†Œëª¨ë“œ', + 'Fired, Red-eye reduction, Return detected' => '플래시 발광, ì ëª©ê°ì†Œëª¨ë“œ, 복귀광 ê°ì§€ë¨', + 'Fired, Red-eye reduction, Return not detected' => '플래시 발광, ì ëª©ê°ì†Œëª¨ë“œ, 복귀광 ê°ì§€ 안ë¨', + 'Fired, Return detected' => '스트로브 복귀광 ê°ì§€ë¨', + 'Fired, Return not detected' => '스트로브 복귀광 ê°ì§€ 안ë¨', + 'No Flash' => '플래시 발광 안ë¨', + 'No flash function' => '플래시 ìž‘ë™ ì—†ìŒ', + 'Off, Did not fire' => '플래시 발광 안ë¨, 강제발광모드', + 'Off, Did not fire, Return not detected' => '꺼ì§, 발광 안ë¨, 복귀광 ê°ì§€ 안ë¨', + 'Off, No flash function' => '꺼ì§, 플래시 ìž‘ë™ ì—†ìŒ', + 'Off, Red-eye reduction' => '꺼ì§, ì ëª©ê°ì†Œ', + 'On, Did not fire' => '켜ì§, 플래시 발광 안ë¨', + 'On, Fired' => '플래시 발광, 강제발광모드', + 'On, Red-eye reduction' => '플래시 발광, 강제발광모드, ì ëª©ê°ì†Œëª¨ë“œ', + 'On, Red-eye reduction, Return detected' => '플래시 발광, 강제발광모드, ì ëª©ê°ì†Œëª¨ë“œ, 복귀광 ê°ì§€ë¨', + 'On, Red-eye reduction, Return not detected' => '플래시 발광, 강제발광모드, ì ëª©ê°ì†Œëª¨ë“œ, 복귀광 ê°ì§€ 안ë¨', + 'On, Return detected' => '플래시 발광, 강제발광모드, 복귀광 ê°ì§€ë¨', + 'On, Return not detected' => '플래시 발광, 강제발광모드, 복귀광 ê°ì§€ 안ë¨', + }, + }, + 'FlashCommanderMode' => { + Description => 'ì»¤ë§¨ë” ëª¨ë“œ', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'FlashCompensation' => '플래시 ë³´ì •', + 'FlashControlMode' => { + Description => '플래시 컨트롤 모드', + PrintConv => { + 'Auto Aperture' => 'ìžë™ 조리개 (AA)', + 'Manual' => '수ë™', + 'Off' => '꺼ì§', + 'Repeating Flash' => '리피팅 플래시', + }, + }, + 'FlashEnergy' => '플래시 ì—너지', + 'FlashExposureBracketValue' => '플래시 노출 브ë¼ì¼€íŒ… ê°’', + 'FlashExposureComp' => '플래시 노출 ë³´ì •', + 'FlashExposureLock' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'FlashFired' => '플래시 발광ë¨', + 'FlashFocalLength' => '플래시 ì´ˆì  ê¸¸ì´', + 'FlashGroupACompensation' => '그룹 A 플래시 ë³´ì •', + 'FlashGroupAControlMode' => { + Description => '그룹 A 플래시 모드', + PrintConv => { + 'Auto Aperture' => 'ìžë™ 조리개 (AA)', + 'Manual' => '수ë™', + 'Off' => '꺼ì§', + 'Repeating Flash' => '리피팅 플래시', + }, + }, + 'FlashGroupAOutput' => '그룹 A 플래시 출력', + 'FlashGroupBCompensation' => '그룹 B 플래시 ë³´ì •', + 'FlashGroupBControlMode' => { + Description => '그룹 B 플래시 모드', + PrintConv => { + 'Auto Aperture' => 'ìžë™ 조리개 (AA)', + 'Manual' => '수ë™', + 'Off' => '꺼ì§', + 'Repeating Flash' => '리피팅 플래시', + }, + }, + 'FlashGroupBOutput' => '그룹 B 플래시 출력', + 'FlashGroupCCompensation' => '그룹 C 플래시 ë³´ì •', + 'FlashGroupCControlMode' => { + Description => '그룹 C 플래시 컨트롤 모드', + PrintConv => { + 'Auto Aperture' => 'ìžë™ 조리개 (AA)', + 'Manual' => '수ë™', + 'Off' => '꺼ì§', + 'Repeating Flash' => '리피팅 플래시', + }, + }, + 'FlashGroupCOutput' => '그룹 C 플래시 출력', + 'FlashInfoVersion' => '플래시 ì •ë³´ 버전', + 'FlashLevel' => '플래시 ë³´ì •', + 'FlashMode' => { + Description => '플래시 모드', + PrintConv => { + 'Did Not Fire' => '발광 안ë¨', + 'Fired, Commander Mode' => '발광ë¨, ì»¤ë§¨ë” ëª¨ë“œ', + 'Fired, External' => '발광ë¨, 외장', + 'Fired, Manual' => '발광ë¨, 수ë™', + 'Fired, TTL Mode' => '발광ë¨, TTL 모드', + }, + }, + 'FlashModel' => '플래시 모ë¸', + 'FlashOptions' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'FlashOptions2' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'FlashOutput' => '플래시 출력', + 'FlashSetting' => '플래시 설정', + 'FlashShutterSpeed' => '플래시 셔터 스피드', + 'FlashStatus' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'FlashSyncSpeed' => '플래시 ë™ì¡° ì†ë„', + 'FlashSyncSpeedAv' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'FlashType' => '플래시 형ì‹', + 'FlashWarning' => { + Description => '플래시 경고', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'FlashpixVersion' => 'ì§€ì›ë˜ëŠ” Flashpix 버전', + 'FocalLength' => 'ì´ˆì  ê¸¸ì´', + 'FocalLength35efl' => 'ì´ˆì ê±°ë¦¬ (35mm 변환)', + 'FocalLengthIn35mmFormat' => '35 mm 필름 환산 ì´ˆì ê¸¸ì´', + 'FocalPlaneResolutionUnit' => { + Description => 'ì´ˆì ë©´ í•´ìƒë„ 단위', + PrintConv => { + 'None' => 'ì—†ìŒ', + 'inches' => 'ì¸ì¹˜', + 'um' => 'µm (마ì´í¬ë¡œë¯¸í„°)', + }, + }, + 'FocalPlaneXResolution' => 'ìˆ˜í‰ í•´ìƒë„ ì´ˆì ë©´', + 'FocalPlaneYResolution' => 'ìˆ˜ì§ í•´ìƒë„ ì´ˆì ë©´', + 'Focus' => { + Description => 'í¬ì»¤ìФ', + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'FocusArea' => 'AF í¬ì¸íЏ 순환', + 'FocusAreaSelection' => { + Description => 'AF í¬ì¸íЏ 순환', + PrintConv => { + 'No Wrap' => '꺼ì§', + 'Wrap' => '켜ì§', + }, + }, + 'FocusContinuous' => { + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'FocusDistance' => 'ì´ˆì  ê±°ë¦¬', + 'FocusMode' => { + Description => 'ì´ˆì ëª¨ë“œ', + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'FocusMode2' => { + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'FocusModeSetting' => { + Description => 'ì´ˆì  ëª¨ë“œ', + PrintConv => { + 'AF-A' => 'ìžë™ AF 서보', + 'AF-C' => '컨티뉴어스 AF 서보', + 'AF-S' => '싱글 AF 서보', + 'Manual' => '수ë™', + }, + }, + 'FocusPointWrap' => { + Description => 'ì´ˆì  í¬ì¸íЏ 순환', + PrintConv => { + 'No Wrap' => '순환 안함', + 'Wrap' => '순환', + }, + }, + 'FocusPosition' => 'ì´ˆì  ìœ„ì¹˜', + 'FocusRange' => { + PrintConv => { + 'Auto' => 'ìžë™', + 'Manual' => '수ë™', + }, + }, + 'FocusTrackingLockOn' => { + Description => 'ì´ˆì  íŠ¸ëž˜í‚¹ Lock-On', + PrintConv => { + 'Long' => '길게', + 'Normal' => '표준', + 'Off' => '꺼ì§', + 'Short' => '짧게', + }, + }, + 'FrameRate' => '프레임 비율', + 'FrameSize' => '프레임 í¬ê¸°', + 'FujiFlashMode' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'FunctionButton' => { + Description => '펑션 버튼', + PrintConv => { + 'AF-area Mode' => 'AF ì˜ì—­ 모드 설정', + 'Center AF Area' => '중앙 ì´ˆì  ì˜ì—­', + 'Center-weighted' => '중앙부 ì¤‘ì  ì¸¡ê´‘', + 'FV Lock' => 'FV ê³ ì •', + 'Flash Off' => '플래시 OFF', + 'Framing Grid' => '격ìžì„  표시', + 'ISO Display' => 'ISO 표시', + 'Matrix Metering' => '멀티 패턴 측광', + 'Spot Metering' => '스팟 측광', + }, + }, + 'GPSAltitude' => 'ê³ ë„', + 'GPSAltitudeRef' => { + Description => 'ê³ ë„ ì°¸ì¡°', + PrintConv => { + 'Above Sea Level' => 'í•´ë©´', + 'Below Sea Level' => 'í•´ë©´ 참조(ìŒìˆ˜ ê°’)', + }, + }, + 'GPSAreaInformation' => 'GPS ì˜ì—­ ì´ë¦„', + 'GPSDOP' => '측정 ì •ë°€ë„', + 'GPSDateStamp' => 'GPS ë‚ ì§œ', + 'GPSDestBearing' => '목ì ì§€ì˜ 방위', + 'GPSDestBearingRef' => '목ì ì§€ ë°©ìœ„ì˜ ê¸°ì¤€', + 'GPSDestDistance' => '목ì ì§€ê¹Œì§€ì˜ 거리', + 'GPSDestDistanceRef' => '목ì ì§€ê¹Œì§€ì˜ 거리 단위', + 'GPSDestLatitude' => '목ì ì§€ì˜ 위ë„', + 'GPSDestLatitudeRef' => '목ì ì§€ 위ë„ì˜ ê¸°ì¤€', + 'GPSDestLongitude' => '목ì ì§€ì˜ ê²½ë„', + 'GPSDestLongitudeRef' => '목ì ì§€ ê²½ë„ì˜ ê¸°ì¤€', + 'GPSDifferential' => { + Description => 'GPS 편차 ë³´ì •', + PrintConv => { + 'Differential Corrected' => '편차 ë³´ì • ì ìš©', + 'No Correction' => '편차 ë³´ì • ì—†ì´ ì¸¡ì •', + }, + }, + 'GPSImgDirection' => 'ì´ë¯¸ì§€ ë°©í–¥', + 'GPSImgDirectionRef' => 'ì´ë¯¸ì§€ ë°©í–¥ì˜ ê¸°ì¤€', + 'GPSInfo' => 'GPS Info IFD Pointer', + 'GPSLatitude' => '위ë„', + 'GPSLatitudeRef' => { + Description => 'ë¶ìœ„ ë˜ëŠ” 남위', + PrintConv => { + 'North' => 'ë¶ìœ„', + 'South' => '남위', + }, + }, + 'GPSLongitude' => 'ê²½ë„', + 'GPSLongitudeRef' => { + Description => 'ë™ê²½ ë˜ëŠ” 서경', + PrintConv => { + 'East' => 'ë™ê²½', + 'West' => '서경', + }, + }, + 'GPSMapDatum' => 'ì‚¬ìš©ëœ ì¸¡ì§€ ë°ì´í„°', + 'GPSMeasureMode' => { + Description => 'GPS 측정 모드', + PrintConv => { + '2-Dimensional Measurement' => '2-ì°¨ì› ì¸¡ëŸ‰(í‰ë©´ 측량)', + '3-Dimensional Measurement' => '3ì°¨ì› ì¸¡ì •', + }, + }, + 'GPSProcessingMethod' => 'GPS 처리 ë°©ì‹ ì´ë¦„', + 'GPSSatellites' => 'ì¸¡ì •ì— ì‚¬ìš©ëœ GPS 위성', + 'GPSSpeed' => 'GPS 수신기 ì†ë„', + 'GPSSpeedRef' => { + Description => 'ì†ë„ 단위', + PrintConv => { + 'km/h' => '시간당 킬로미터', + 'knots' => '노트', + 'mph' => '시간당 마ì¼', + }, + }, + 'GPSStatus' => { + Description => 'GPS 수신기 ìƒíƒœ', + PrintConv => { + 'Measurement Active' => 'ì§„í–‰ ì¤‘ì¸ ì¸¡ì •', + 'Measurement Void' => '측정 ìƒí˜¸ 운용성', + }, + }, + 'GPSTimeStamp' => 'GPS 시간(ì›ìž 시계)', + 'GPSTrack' => 'ì´ë™ ë°©í–¥', + 'GPSTrackRef' => { + Description => 'ì´ë™ ë°©í–¥ì˜ ê¸°ì¤€', + PrintConv => { + 'Magnetic North' => 'ìžê¸° ë°©í–¥', + 'True North' => '실제 ë°©í–¥', + }, + }, + 'GPSVersionID' => 'GPS 태그 버전', + 'GainControl' => { + Description => 'ì´ë“ 제어', + PrintConv => { + 'High gain down' => 'ë†’ì€ ì´ë“ ê°ì†Œ', + 'High gain up' => 'ë†’ì€ ì´ë“ ì¦ê°€', + 'Low gain down' => 'ì ì€ ì´ë“ ê°ì†Œ', + 'Low gain up' => 'ì ì€ ì´ë“ ì¦ê°€', + 'None' => 'ì—†ìŒ', + }, + }, + 'Gamma' => 'ê°ë§ˆ', + 'Gradation' => '계조', + 'GridDisplay' => { + Description => 'ê²©ìž í‘œì‹œ', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'HDR' => { + Description => 'ìžë™ HDR', + PrintConv => { + 'Off' => 'ì—†ìŒ', + }, + }, + 'Headline' => '헤드ë¼ì¸', + 'HighISONoiseReduction' => { + Description => 'ê³ ISOì—서 ë…¸ì´ì¦ˆì œê±°', + PrintConv => { + 'Auto' => 'ìžë™', + 'High' => '강함', + 'Low' => '약함', + 'Minimal' => '최소', + 'Normal' => '표준', + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'HighlightTonePriority' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Hue' => '색ìƒ', + 'HueAdjustment' => '색조', + 'ISO' => 'ISO ì†ë„', + 'ISOAuto' => 'ìžë™ ISO', + 'ISODisplay' => 'ISO 표시', + 'ISOExpansion' => { + Description => 'ISO 확장', + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'ISOExpansion2' => { + Description => 'ISO 확장 (2)', + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'ISOInfo' => 'ISO ì •ë³´', + 'ISOSelection' => 'ISO ì„ íƒ', + 'ISOSetting' => { + Description => 'ISO 설정', + PrintConv => { + 'Auto' => 'ìžë™', + 'Manual' => '수ë™', + }, + }, + 'ISOStepSize' => 'ISO ê°ë„ ìŠ¤í… ìˆ˜ì¹˜', + 'Illumination' => { + Description => '조명', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ImageAdjustment' => 'ì´ë¯¸ì§€ ì¡°ì •', + 'ImageAuthentication' => { + Description => 'ì´ë¯¸ì§€ ì¸ì¦', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ImageBoundary' => 'ì´ë¯¸ì§€ 경계', + 'ImageCount' => 'ì´ë¯¸ì§€ 카운트', + 'ImageDataSize' => 'ì´ë¯¸ì§€ ë°ì´í„° í¬ê¸°', + 'ImageDustOff' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ImageHeight' => 'ì´ë¯¸ì§€ 높ì´', + 'ImageHistory' => 'ì´ë¯¸ì§€ ì´ë ¥', + 'ImageNumber' => 'ì´ë¯¸ì§€ 숫ìž', + 'ImageOptimization' => 'ì´ë¯¸ì§€ 최ì í™”', + 'ImageProcessing' => 'ì´ë¯¸ì§€ 처리', + 'ImageQuality' => 'ì´ë¯¸ì§€ 화질', + 'ImageReview' => { + Description => 'ì´ë¯¸ì§€ 미리보기', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ImageReviewTime' => 'ì´ë¯¸ì§€ ìž¬ìƒ ì‹œê°„', + 'ImageSize' => 'ì´ë¯¸ì§€ í¬ê¸°', + 'ImageStabilization' => { + Description => 'ì†ë–¨ë¦¼ ë³´ì •', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ImageUniqueID' => '고유 ì´ë¯¸ì§€ ID', + 'ImageWidth' => 'ì´ë¯¸ì§€ ë„“ì´', + 'Index' => '색ì¸', + 'InitialZoomSetting' => { + Description => 'ì´ë‹ˆì…œ 줌 설정', + PrintConv => { + 'High Magnification' => 'í° í™•ëŒ€', + 'Low Magnification' => 'ìž‘ì€ í™•ëŒ€', + 'Medium Magnification' => '중간 확대', + }, + }, + 'Instructions' => '안내', + 'IntensityStereo' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'InternalFlash' => { + Description => '내장플래시 컨트롤', + PrintConv => { + 'Commander Mode' => 'ì»¤ë§¨ë” ëª¨ë“œ', + 'Manual' => '수ë™', + 'Off' => '꺼ì§', + 'On' => '켜ì§', + 'Repeating Flash' => '리피팅 플래시', + }, + }, + 'InternalFlashMode' => { + PrintConv => { + 'On' => '켜ì§', + }, + }, + 'InteropIndex' => 'ìƒí˜¸ìš´ìš©ì„± ì¦ëª…', + 'InteropOffset' => 'ìƒí˜¸ 운용성 태그', + 'InteropVersion' => 'ìƒí˜¸ 운용성 버전', + 'JPEGQuality' => { + Description => '화질', + PrintConv => { + 'Extra Fine' => 'ì—‘ìŠ¤íŠ¸ë¼ íŒŒì¸', + 'Fine' => '파ì¸', + 'Standard' => '표준화질', + }, + }, + 'Keyword' => '키워드', + 'Keywords' => '키워드', + 'LCDIllumination' => { + Description => 'LCD 조명', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'LCDIlluminationDuringBulb' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'LCHEditor' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Lens' => '렌즈', + 'LensDataVersion' => '렌즈 ë°ì´í„° 버전', + 'LensFStops' => '렌즈 F-숫ìž', + 'LensID' => 'ì‚¬ìš©ëœ ë Œì¦ˆ', + 'LensIDNumber' => '렌즈 ID 숫ìž', + 'LensInfo' => '렌즈 ì •ë³´', + 'LensType' => '렌즈 형ì‹', + 'LightSource' => { + Description => 'ê´‘ì› ì¢…ë¥˜', + PrintConv => { + 'Cloudy' => 'í린 날씨', + 'Cool White Fluorescent' => '냉백색 형광등 (W 3800 - 4500K)', + 'Day White Fluorescent' => '주백색 형광등 (N 4600 - 5500K)', + 'Daylight' => '주광', + 'Daylight Fluorescent' => '주광색 형광등 (D 5700 - 7100K)', + 'Fine Weather' => 'ë§‘ì€ ë‚ ì”¨', + 'Flash' => '플래시', + 'Fluorescent' => '형광등', + 'ISO Studio Tungsten' => 'ISO 스튜디오 텅스í…', + 'Other' => '기타 ê´‘ì›', + 'Shade' => '그늘', + 'Standard Light A' => '표준 ê´‘ì› A', + 'Standard Light B' => '표준 ê´‘ì› B', + 'Standard Light C' => '표준 ê´‘ì› C', + 'Tungsten (Incandescent)' => 'í……ìŠ¤í… (백열등)', + 'Unknown' => '알 수 ì—†ìŒ', + 'Warm White Fluorescent' => '따뜻한 í°ìƒ‰ 형광 (L 2600 - 3250K)', + 'White Fluorescent' => '백색 형광등 (WW 3250 - 3800K)', + }, + }, + 'Lightness' => '명ë„', + 'LinearizationTable' => '선형ë„표', + 'LiveViewShooting' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Location' => '위치', + 'LongExposureNoiseReduction' => { + Description => '장시간 노출 NR', + PrintConv => { + 'Auto' => 'ìžë™', + 'Off' => 'ì—†ìŒ', + 'On' => '있ìŒ', + }, + }, + 'LuminanceNoiseReduction' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'MB-D10Batteries' => 'MB-D10 배터리 형ì‹', + 'MB-D10BatteryType' => 'MB-D10 배터리 형ì‹', + 'MB-D80Batteries' => { + Description => 'MB-D80 배터리', + PrintConv => { + 'FR6 (AA Lithium)' => 'FR6 (AA 리튬)', + 'LR6 (AA Alkaline)' => 'LR6 (AA 알카ë¼ì¸)', + }, + }, + 'MCUVersion' => 'MCU 버전', + 'MSStereo' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Macro' => { + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'MacroMode' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'MainDialExposureComp' => { + Description => '노출 ë³´ì •', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Make' => 'ë©”ì´ì»¤', + 'MakerNote' => '제조사 노트', + 'MakerNoteVersion' => '제조사노트 버전', + 'MakerNotes' => '제조업체 ì •ë³´', + 'ManualFlashOutput' => '내장 플래시 ìˆ˜ë™ ì¶œë ¥', + 'ManualFocusDistance' => 'ìˆ˜ë™ ì´ˆì  ê±°ë¦¬', + 'MatrixMetering' => '멀티 패턴 측광', + 'MaxAperture' => '최대 렌즈 조리개', + 'MaxApertureAtMaxFocal' => '최대 ì´ˆì ê¸¸ì´ì—서 최대 조리개', + 'MaxApertureAtMinFocal' => '최소 ì´ˆì ê¸¸ì´ì—서 최대 조리개', + 'MaxApertureValue' => '최대 렌즈 조리개', + 'MaxContinuousRelease' => '최대 연사 릴리즈', + 'MaxFocalLength' => '최대 ì´ˆì ê¸¸ì´', + 'Metering' => { + Description => '측광', + PrintConv => { + 'Center-weighted' => '중앙부 중ì ', + 'Matrix' => '멀티패턴', + 'Spot' => '스팟', + }, + }, + 'MeteringMode' => { + Description => '측광 모드', + PrintConv => { + 'Average' => 'í‰ê· ', + 'Center-weighted average' => 'ì¤‘ì•™ì¤‘ì  í‰ê· ', + 'Multi-segment' => '멀티 패턴', + 'Multi-spot' => '멀티 스팟', + 'Other' => '기타', + 'Partial' => '부분', + 'Spot' => '스팟', + 'Unknown' => '알 수 ì—†ìŒ', + }, + }, + 'MeteringTime' => { + Description => 'ìžë™ 측광 êº¼ì§ ì‹œê°„', + PrintConv => { + 'No Limit' => '무제한', + }, + }, + 'MinFocalLength' => '최소 ì´ˆì ê¸¸ì´', + 'Model' => 'ì¹´ë©”ë¼ ëª¨ë¸', + 'ModelingFlash' => { + Description => '최대', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ModifiedSaturation' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'ModifiedToneCurve' => { + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'ModifiedWhiteBalance' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'ModifyDate' => 'íŒŒì¼ ë³€ê²½ ë‚ ì§œ ë° ì‹œê°„', + 'MoireFilter' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'MonitorOffTime' => '모니터 êº¼ì§ ì‹œê°„', + 'MonochromeFilterEffect' => { + PrintConv => { + 'Green' => '녹색', + 'Orange' => '주황', + 'Red' => '빨강', + 'Yellow' => '노랑', + }, + }, + 'MonochromeToningEffect' => { + PrintConv => { + 'Green' => '녹색', + }, + }, + 'MultiExposure' => '다중 노출 ë°ì´í„°', + 'MultiExposureAutoGain' => { + Description => '다중 노출 ìžë™ 게ì¸', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'MultiExposureMode' => { + Description => '다중노출 모드', + PrintConv => { + 'Image Overlay' => 'ì´ë¯¸ì§€ 오버레ì´', + 'Multiple Exposure' => '다중 노출', + 'Off' => '꺼ì§', + }, + }, + 'MultiExposureShots' => '다중 노출 ì´¬ì˜', + 'MultiExposureVersion' => '다중 노출 ë°ì´í„° 버전', + 'MultiFrameNoiseReduction' => { + Description => '다중 프레임 ë…¸ì´ì¦ˆ ê°ì‡„', + PrintConv => { + 'Off' => 'ì—†ìŒ', + 'On' => '있ìŒ', + }, + }, + 'MultiSelector' => { + Description => '멀티셀렉터', + PrintConv => { + 'Do Nothing' => 'ì•„ë¬´ê²ƒë„ ì•ˆí•¨', + 'Reset Meter-off Delay' => '노출계 êº¼ì§ ì‹œê°„ 초기화', + }, + }, + 'MultiSelectorPlaybackMode' => { + Description => '멀티셀렉터 ìž¬ìƒ ëª¨ë“œ', + PrintConv => { + 'Choose Folder' => 'í´ë” ì„ íƒ', + 'Thumbnail On/Off' => 'ì„¬ë„¤ì¼ on/off', + 'View Histograms' => '히스토그램 표시', + 'Zoom On/Off' => '줌 on/off', + }, + }, + 'MultiSelectorShootMode' => { + Description => '멀티셀렉터 ì´¬ì˜ ëª¨ë“œ', + PrintConv => { + 'Highlight Active Focus Point' => '활성 ì´ˆì  í¬ì¸íЏ 하ì´ë¼ì´íЏ', + 'Not Used' => '사용 안ë¨', + 'Select Center Focus Point' => '중앙 ì´ˆì  í¬ì¸íЏ ì„ íƒ', + }, + }, + 'MultipleExposureSet' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Mute' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'MyColorMode' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'NDFilter' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'NEFCompression' => { + Description => 'RAW ì••ì¶•', + PrintConv => { + 'Lossless' => 'ì†ì‹¤ ì—†ìŒ', + 'Lossy (type 1)' => 'ì†ì‹¤ë¨ (타입 1)', + 'Lossy (type 2)' => 'ì†ì‹¤ë¨ (타입 2)', + 'Uncompressed' => 'ì••ì¶•ë˜ì§€ 않ìŒ', + }, + }, + 'NEFLinearizationTable' => '선형ë„표', + 'NikonCaptureData' => 'Nikon Capture ë°ì´í„°', + 'NikonCaptureVersion' => 'Nikon Capture 버전', + 'NikonImageSize' => { + Description => 'ì´ë¯¸ì§€ í¬ê¸°', + PrintConv => { + 'Large (10.0 M)' => 'í° (10.0M)', + 'Medium (5.6 M)' => '중간 (5.6M)', + 'Small (2.5 M)' => 'ìž‘ì€ (2.5M)', + }, + }, + 'NoMemoryCard' => { + Description => '메모리카드 ì—†ì„ ê²½ìš°', + PrintConv => { + 'Enable Release' => '릴리즈 허용', + 'Release Locked' => '릴리즈 잠금', + }, + }, + 'Noise' => 'ë…¸ì´ì¦ˆ', + 'NoiseFilter' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'NoiseReduction' => { + Description => 'ë…¸ì´ì¦ˆ 제거', + PrintConv => { + 'Auto' => 'ìžë™', + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'OneTouchWB' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'Opto-ElectricConvFactor' => 'ê´‘ì „ìž ë³€í™˜ 계수', + 'Orientation' => { + Description => 'ì´ë¯¸ì§€ 위치', + PrintConv => { + 'Horizontal (normal)' => '0° (위쪽/좌측)', + 'Mirror horizontal' => '0° (위쪽/우측)', + 'Mirror horizontal and rotate 270 CW' => '90° 시계방향 (좌측/위쪽)', + 'Mirror horizontal and rotate 90 CW' => '90° 반시계방향 (우측/아래)', + 'Mirror vertical' => '180° (아래/좌측)', + 'Rotate 180' => '180° (아래/우측)', + 'Rotate 270 CW' => '90° 시계방향 (좌측/아래)', + 'Rotate 90 CW' => '90° 반시계방향 (우측/위쪽)', + }, + }, + 'PhaseDetectAF' => { + Description => '오토í¬ì»¤ìФ', + PrintConv => { + 'Off' => '비활성', + 'On (51-point)' => '활성', + }, + }, + 'PhotoEffect' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'PhotoEffects' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'PhotoInfoPlayback' => { + Description => '사진 ì •ë³´/재ìƒ', + PrintConv => { + 'Info Left-right, Playback Up-down' => 'ì •ë³´ <> / 재ìƒ', + 'Info Up-down, Playback Left-right' => 'ì •ë³´ / ìž¬ìƒ <>', + }, + }, + 'PhotometricInterpretation' => { + Description => '픽셀 형ì‹', + PrintConv => { + 'BlackIsZero' => 'ë¸”ëž™ì€ ì œë¡œìž„', + 'RGB Palette' => '팔렛트 컬러', + 'Transparency Mask' => '투명 마스í¬', + 'WhiteIsZero' => 'í™”ì´íŠ¸ëŠ” 제로임', + }, + }, + 'PictureControl' => { + Description => '픽ì³ì»¨íŠ¸ë¡¤', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'PictureControlActive' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'PictureControlAdjust' => { + Description => '픽ì³ì»¨íŠ¸ë¡¤ ì¡°ì •', + PrintConv => { + 'Default Settings' => '기본 설정', + 'Full Control' => 'ì „ì²´ 제어', + 'Quick Adjust' => '빠른 ì¡°ì •', + }, + }, + 'PictureControlBase' => '픽ì³ì»¨íŠ¸ë¡¤ 기초', + 'PictureControlName' => '픽ì³ì»¨íŠ¸ë¡¤ ì´ë¦„', + 'PictureControlQuickAdjust' => '픽ì³ì»¨íŠ¸ë¡¤ 빠른 ì¡°ì •', + 'PictureControlVersion' => '픽ì³ì»¨íŠ¸ë¡¤ 버전', + 'PictureMode' => { + PrintConv => { + 'Auto' => 'ìžë™', + 'Manual' => '수ë™', + }, + }, + 'PictureMode2' => { + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'PictureModeBWFilter' => { + PrintConv => { + 'Green' => '녹색', + 'Orange' => '주황', + 'Red' => '빨강', + 'Yellow' => '노랑', + }, + }, + 'PictureModeTone' => { + PrintConv => { + 'Green' => '녹색', + }, + }, + 'PlanarConfiguration' => { + Description => 'ì´ë¯¸ì§€ ë°ì´í„° ì •ë ¬', + PrintConv => { + 'Chunky' => '청키 형ì‹', + 'Planar' => 'í‰ë©´ 형ì‹', + }, + }, + 'Preview' => 'IFD í¬ì¸í„° 미리보기', + 'PreviewIFD' => 'IFD í¬ì¸í„° 미리보기', + 'PrimaryAFPoint' => { + PrintConv => { + 'Bottom' => '하단', + 'C6 (Center)' => 'C6 (중앙)', + 'Center' => '중앙', + 'Far Left' => '맨 좌측', + 'Far Right' => '맨 우측', + 'Lower-left' => '좌하단', + 'Lower-right' => '우하단', + 'Mid-left' => '왼쪽', + 'Mid-right' => '오른쪽', + 'Top' => 'ìƒë‹¨', + 'Upper-left' => '좌ìƒë‹¨', + 'Upper-right' => 'ìš°ìƒë‹¨', + }, + }, + 'PrimaryChromaticities' => '기본 색ë„', + 'ProgramShift' => '프로그램 쉬프트', + 'Province-State' => 'ë„', + 'Quality' => { + Description => '화질', + PrintConv => { + 'Compressed RAW' => 'cRAW', + 'Compressed RAW + JPEG' => 'cRAW+JPEG', + 'Extra Fine' => 'ì—‘ìŠ¤íŠ¸ë¼ íŒŒì¸', + 'Fine' => '파ì¸', + 'Low' => '저화질', + 'Normal' => '표준화질', + 'RAW + JPEG' => 'RAW+JPEG', + 'Standard' => '표준', + }, + }, + 'QuickAdjust' => '빠른 ì¡°ì •', + 'RawDevAutoGradation' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'RawDevPMPictureTone' => { + PrintConv => { + 'Green' => '녹색', + }, + }, + 'RawDevPM_BWFilter' => { + PrintConv => { + 'Green' => '녹색', + 'Orange' => '주황', + 'Red' => '빨강', + 'Yellow' => '노랑', + }, + }, + 'RawImageCenter' => 'RAW ì´ë¯¸ì§€ 중앙', + 'RecordMode' => { + Description => 'ì´ë¯¸ì§€ 화질 모드', + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'RecordingMode' => { + PrintConv => { + 'Auto' => 'ìžë™', + 'Manual' => '수ë™', + }, + }, + 'RedEyeCorrection' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'RedEyeReduction' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ReferenceBlackWhite' => 'í‘ë°± 참조 ê°’ì˜ ìŒ', + 'RelatedImageFileFormat' => '관련 ì´ë¯¸ì§€ íŒŒì¼ í˜•ì‹', + 'RelatedImageHeight' => '관련 ì´ë¯¸ì§€ 길ì´', + 'RelatedImageWidth' => '관련 ì´ë¯¸ì§€ 너비', + 'RelatedSoundFile' => 'ì—°ê´€ëœ ì˜¤ë””ì˜¤ 파ì¼', + 'ReleaseButtonToUseDial' => { + Description => '다ì´ì–¼ë¡œ 릴리즈 버튼 사용', + PrintConv => { + 'No' => '아니오', + 'Yes' => '예', + }, + }, + 'RemoteOnDuration' => '리모컨 ì§€ì† ì‹œê°„', + 'RepeatingFlashCount' => '리피팅 플래시 시간', + 'RepeatingFlashOutput' => '리피팅 플래시 출력', + 'RepeatingFlashRate' => '리피팅 플래시 간격', + 'ResolutionUnit' => { + Description => 'X 와 Y í•´ìƒë„ 단위', + PrintConv => { + 'None' => 'ì—†ìŒ', + 'cm' => '센티미터', + 'inches' => 'ì¸ì¹˜', + }, + }, + 'RetouchHistory' => { + Description => '리터치 ì´ë ¥', + PrintConv => { + 'B & W' => 'í‘ë°±', + 'Color Custom' => '커스텀 컬러', + 'Cyanotype' => '청사진', + 'Image Overlay' => 'ì´ë¯¸ì§€ 오버레ì´', + 'None' => 'ì—†ìŒ', + 'Sepia' => '세피아', + 'Sky Light' => 'ìŠ¤ì¹´ì´ ë¼ì´íЏ', + 'Small Picture' => '스몰', + 'Trim' => '트리ë°', + 'Warm Tone' => '따뜻한 톤', + }, + }, + 'ReverseIndicators' => 'ì¸ë””ì¼€ì´í„° 전환', + 'Rotation' => { + Description => 'ì¹´ë©”ë¼ íšŒì „ ë°©í–¥', + PrintConv => { + 'Horizontal' => '0° (수í‰)', + 'Rotated 180' => '180° (ìƒí•˜ë°˜ì „)', + 'Rotated 270 CW' => '90° 좌회전', + 'Rotated 90 CW' => '90° 우회전', + }, + }, + 'RowsPerStrip' => '스트립 당 í–‰ì˜ ìˆ˜', + 'SamplesPerPixel' => '구성 요소 수', + 'Saturation' => { + Description => '채ë„', + PrintConv => { + 'High' => '고채ë„', + 'Low' => '저채ë„', + 'Normal' => '표준', + }, + }, + 'ScanImageEnhancer' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'SceneAssist' => 'ë³´ì¡° 센서', + 'SceneCaptureType' => { + Description => '장면 ê¸°ë¡ í˜•ì‹', + PrintConv => { + 'Landscape' => 'í’ê²½', + 'Night' => '야경', + 'Portrait' => 'ì¸ë¬¼', + 'Standard' => '표준', + }, + }, + 'SceneMode' => { + Description => '장면 모드', + PrintConv => { + '3D Sweep Panorama' => '3D', + 'Anti Motion Blur' => 'ì¸ë¬¼ í”들림 ë°©ì§€', + 'Auto' => 'ìžë™', + 'Cont. Priority AE' => 'ì—°ì† ì´¬ì˜ ìš°ì„  AE', + 'Handheld Night Shot' => 'ì†ìœ¼ë¡œ 야간 ì´¬ì˜', + 'Landscape' => 'í’ê²½', + 'Macro' => '매í¬ë¡œ', + 'Manual' => '수ë™', + 'Night Portrait' => '야경 ì¸ë¬¼', + 'Night Scene' => '야경', + 'Night View/Portrait' => '야경/ì¸ë¬¼', + 'Off' => '꺼ì§', + 'Portrait' => 'ì¸ë¬¼', + 'Sports' => '스í¬ì¸  ì•¡ì…˜', + 'Sunset' => 'ì„ì–‘ì´¬ì˜', + 'Sweep Panorama' => '스위프 파노ë¼ë§ˆ', + }, + }, + 'SceneModeUsed' => { + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'SceneType' => { + Description => '장면 형ì‹', + PrintConv => { + 'Directly photographed' => 'ì§ì ‘ ì´¬ì˜ëœ ì´ë¯¸ì§€', + }, + }, + 'SecurityClassification' => { + Description => '보안 분류', + PrintConv => { + 'Confidential' => '기밀', + 'Restricted' => '제한', + 'Secret' => '비밀', + 'Top Secret' => '1급비밀', + 'Unclassified' => '분류안ë¨', + }, + }, + 'SelfTimerMode' => '셀프 타ì´ë¨¸ 모드', + 'SelfTimerTime' => '셀프타ì´ë¨¸', + 'SensingMethod' => { + Description => '검출 ë°©ì‹', + PrintConv => { + 'Color sequential area' => 'ìƒ‰ìƒ ìˆœì°¨ ì˜ì—­ 센서', + 'Color sequential linear' => 'ìƒ‰ìƒ ìˆœì°¨ 선형 센서', + 'Monochrome area' => '모노í¬ë¡¬ ì˜ì—­ 센서', + 'Monochrome linear' => '모노í¬ë¡¬ 선형 센서', + 'Not defined' => 'ì •ì˜ë˜ì§€ 않ìŒ', + 'One-chip color area' => 'One-chip ìƒ‰ìƒ ì˜ì—­ 센서', + 'Three-chip color area' => 'Three-chip ìƒ‰ìƒ ì˜ì—­ 센서', + 'Trilinear' => 'Trilinear 센서', + 'Two-chip color area' => 'Two-chip ìƒ‰ìƒ ì˜ì—­ 센서', + }, + }, + 'SensorPixelSize' => '센서 픽셀 í¬ê¸°', + 'SerialNumber' => '시리얼번호', + 'ShadingCompensation' => { + Description => '주변광량 ë³´ì •', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ShadingCompensation2' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ShakeReduction' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Sharpness' => { + Description => '선명ë„', + PrintConv => { + 'Hard' => '강하게', + 'Normal' => '표준', + 'Soft' => '약하게', + }, + }, + 'ShootingInfoDisplay' => { + Description => 'ì´¬ì˜ ì •ë³´ 표시', + PrintConv => { + 'Auto' => 'ìžë™', + 'Manual (dark on light)' => 'ìˆ˜ë™ - ë°ì€ë°°ê²½ì— ì–´ë‘움', + 'Manual (light on dark)' => 'ìˆ˜ë™ - ì–´ë‘ìš´ ë°°ê²½ì— ë°ìŒ', + }, + }, + 'ShootingMode' => { + Description => 'ì›ê²© 리모컨', + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'ShootingModeSetting' => { + Description => 'ì´¬ì˜ ëª¨ë“œ', + PrintConv => { + 'Continuous' => '연사', + 'Delayed Remote' => 'ì´¬ì˜ëŒ€ê¸° 리모컨', + 'Quick-response Remote' => 'ì¦‰ì‹œì´¬ì˜ ë¦¬ëª¨ì»¨', + 'Self-timer' => '셀프타ì´ë¨¸', + 'Single Frame' => '싱글 프레임', + }, + }, + 'ShotInfoVersion' => 'ì´¬ì˜ ì •ë³´ 버전', + 'ShutterCount' => '셔터 카운트', + 'ShutterMode' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'ShutterReleaseButtonAE-L' => { + Description => '셔터 릴리즈 버튼 AE-L', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ShutterSpeed' => '노출 시간', + 'ShutterSpeedValue' => '셔터 ì†ë„', + 'SlowShutter' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'SlowSync' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'Software' => '소프트웨어', + 'Source' => '소스', + 'SpatialFrequencyResponse' => '공간 주파수 ì‘답', + 'SpectralSensitivity' => 'ë¶„ê´‘ ê°ë„', + 'State' => 'ë„', + 'StripByteCounts' => 'ì••ì¶•ëœ ìŠ¤íŠ¸ë¦½ 당 ë°”ì´íЏ', + 'StripOffsets' => 'ì´ë¯¸ì§€ ë°ì´í„° 위치', + 'SubSecTime' => 'ì¼ì‹œ 1/100 ì´ˆ', + 'SubSecTimeDigitized' => '디지털화 ì¼ì‹œ 1/100 ì´ˆ', + 'SubSecTimeOriginal' => 'ì›ë³¸ì¼ì‹œ 1/100 ì´ˆ', + 'SubfileType' => '새로운 ì„œë¸ŒíŒŒì¼ í˜•ì‹', + 'SubjectArea' => '피사체 ì˜ì—­', + 'SubjectDistance' => 'í”¼ì‚¬ì²´ì˜ ê±°ë¦¬', + 'SubjectDistanceRange' => { + Description => '피사체 거리 한계', + PrintConv => { + 'Close' => '근경', + 'Distant' => 'ì›ê²½', + 'Macro' => '매í¬ë¡œ', + 'Unknown' => '알 수 ì—†ìŒ', + }, + }, + 'SubjectLocation' => '피사체 위치', + 'SuperMacro' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'SuperimposedDisplay' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'SupplementalCategories' => 'ë³´ì¶© 범주', + 'TextStamp' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ThumbnailImage' => '축소 그림', + 'ThumbnailImageSize' => '갤러리 í¬ê¸°', + 'TimeZone' => '표준 시간대', + 'TimerFunctionButton' => { + Description => 'Fn 버튼', + PrintConv => { + 'ISO' => 'ISO ê°ë„', + 'Image Quality/Size' => 'ì´ë¯¸ì§€ 화질/í¬ê¸°', + 'Self-timer' => '셀프타ì´ë¨¸', + 'Shooting Mode' => 'ì´¬ì˜ ëª¨ë“œ', + 'White Balance' => 'í™”ì´íЏ 밸런스', + }, + }, + 'Title' => '제목', + 'ToneComp' => '계조 ë³´ì •', + 'ToneCurve' => { + PrintConv => { + 'Manual' => '수ë™', + }, + }, + 'ToningEffect' => { + Description => '조색 효과', + PrintConv => { + 'B&W' => 'í‘ë°±', + 'Blue' => '블루', + 'Blue-green' => '블루-그린', + 'Green' => '녹색', + 'Purple-blue' => 'í¼í”Œ-블루', + 'Red' => '빨강', + 'Red-purple' => '레드-í¼í”Œ', + 'Yellow' => '노랑', + 'n/a' => '설정 안ë¨', + }, + }, + 'ToningEffectMonochrome' => { + PrintConv => { + 'Green' => '녹색', + }, + }, + 'ToningSaturation' => 'ì±„ë„ ì¡°ì •', + 'TransferFunction' => '전송 기능', + 'TransmissionReference' => '전송 참조', + 'Uncompressed' => 'ì••ì¶•ë˜ì§€ 않ìŒ', + 'Unsharp1Color' => { + PrintConv => { + 'Green' => '녹색', + 'Red' => '빨강', + 'Yellow' => '노랑', + }, + }, + 'Unsharp2Color' => { + PrintConv => { + 'Green' => '녹색', + 'Red' => '빨강', + 'Yellow' => '노랑', + }, + }, + 'Unsharp3Color' => { + PrintConv => { + 'Green' => '녹색', + 'Red' => '빨강', + 'Yellow' => '노랑', + }, + }, + 'Unsharp4Color' => { + PrintConv => { + 'Green' => '녹색', + 'Red' => '빨강', + 'Yellow' => '노랑', + }, + }, + 'UnsharpMask' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'Urgency' => '중요ë„', + 'UserComment' => 'ì‚¬ìš©ìž ì½”ë©˜íŠ¸', + 'VRInfo' => 'ì†ë–¨ë¦¼ ë³´ì • ì •ë³´', + 'VRInfoVersion' => 'VR ì •ë³´ 버전', + 'VR_0x66' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'VariProgram' => '다중 프로그램', + 'VibrationReduction' => { + Description => 'ì†ë–¨ë¦¼ ë³´ì •', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'ViewfinderWarning' => { + Description => '뷰파ì¼ë” 경고', + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'VignetteControl' => { + Description => '비네팅 컨트롤', + PrintConv => { + 'High' => '높ìŒ', + 'Low' => 'ë‚®ìŒ', + 'Normal' => '표준', + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'WBBracketMode' => { + PrintConv => { + 'Off' => '꺼ì§', + }, + }, + 'WBMode' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'WhiteBalance' => { + Description => 'í™”ì´íŠ¸ë°¸ëŸ°ìŠ¤', + PrintConv => { + 'Auto' => 'ìžë™', + 'Black & White' => 'í‘ë°±', + 'Cloudy' => 'í린날', + 'Color Temperature/Color Filter' => '색 ì˜¨ë„ / 컬러 í•„í„°', + 'Cool White Fluorescent' => '차가운 백색 형광', + 'Custom' => 'ì‚¬ìš©ìž ì •ì˜', + 'Custom 1' => 'ê°œì¸ì„¤ì •1', + 'Custom 2' => 'ê°œì¸ì„¤ì •2', + 'Custom 3' => 'ê°œì¸ì„¤ì •3', + 'Custom 4' => 'ê°œì¸ì„¤ì •4', + 'Day White Fluorescent' => '중성 백색 형광', + 'Daylight' => 'ë§‘ì€ë‚ ', + 'Daylight Fluorescent' => 'ì¼ê´‘ 형광', + 'Flash' => '플래시', + 'Fluorescent' => '형광등', + 'Manual' => '수ë™', + 'Shade' => '그늘', + 'Tungsten' => '백열등', + 'Unknown' => '알 수 ì—†ìŒ', + 'Warm White Fluorescent' => '따뜻한 í°ìƒ‰ 형광', + 'White Fluorescent' => '백색 형광등', + }, + }, + 'WhiteBalance2' => { + PrintConv => { + 'Auto' => 'ìžë™', + }, + }, + 'WhiteBalanceAdj' => { + PrintConv => { + 'Auto' => 'ìžë™', + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, + 'WhiteBalanceFineTune' => 'í™”ì´íŠ¸ë°¸ëŸ°ìŠ¤ ì¡°ì •', + 'WhiteBalanceSet' => { + PrintConv => { + 'Auto' => 'ìžë™', + 'Manual' => '수ë™', + }, + }, + 'WhitePoint' => 'í°ìƒ‰ ì  ìƒ‰ë„', + 'WorldTime' => '표준 시간대', + 'Writer-Editor' => '캡션 작성ìž', + 'XResolution' => 'ìˆ˜í‰ í•´ìƒë„', + 'YCbCrCoefficients' => 'ìƒ‰ìƒ ê³µê°„ 변환 매트릭스 계수', + 'YCbCrPositioning' => { + Description => 'Y and C 위치', + PrintConv => { + 'Centered' => '중앙', + 'Co-sited' => '주변', + }, + }, + 'YCbCrSubSampling' => 'Y->C ì„œë¸Œìƒ˜í”Œë§ ë¹„ìœ¨', + 'YResolution' => 'ìˆ˜ì§ í•´ìƒë„', + 'ZoneMatching' => { + Description => 'ì˜ì—­ 전환', + PrintConv => { + 'High Key' => 'Hi', + 'ISO Setting Used' => 'ì—†ìŒ', + 'Low Key' => 'Lo', + }, + }, + 'ZoneMatchingOn' => { + PrintConv => { + 'Off' => '꺼ì§', + 'On' => '켜ì§', + }, + }, +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::ko.pm - ExifTool Korean language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke and Jeong Beom Kim for providing this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/nl.pm b/ExifTool/lib/Image/ExifTool/Lang/nl.pm new file mode 100644 index 0000000..ea4096e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/nl.pm @@ -0,0 +1,3226 @@ +#------------------------------------------------------------------------------ +# File: nl.pm +# +# Description: ExifTool Dutch language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::nl; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.13'; + +%Image::ExifTool::Lang::nl::Translate = ( + 'AEBAutoCancel' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AELock' => { + Description => 'AE-vergrendeling', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AELockButton' => { + Description => 'AE-L/AF-L', + PrintConv => { + 'AE Lock (hold)' => 'AE-vergrendeling vast', + 'AE Lock Only' => 'AE-vergrendeling', + 'AE-L/AF Area' => 'AE-L/AF veld', + 'AE-L/AF-L/AF Area' => 'AE-L/AF-L/AF veld', + 'AE/AF Lock' => 'AE/AF-vergrendeling', + 'AF Lock Only' => 'AF-vergrendeling', + 'AF-L/AF Area' => 'AF-L/AF veld', + 'AF-ON/AF Area' => 'AF-ON/AF veld', + 'FV Lock' => 'FV-vergrendeling', + 'Focus Area Selection' => 'AF-veld', + }, + }, + 'AEMeteringMode' => { + PrintConv => { + 'Multi-segment' => 'Multi segment', + }, + }, + 'AEProgramMode' => { + PrintConv => { + 'Landscape' => 'Landschap', + 'Portrait' => 'Portret', + 'Standard' => 'Standaard', + }, + }, + 'AF-CPrioritySelection' => { + Description => 'Selectie AF-C-prioriteit', + PrintConv => { + 'Focus' => 'Scherpstelling', + 'Release' => 'Ontspannen', + 'Release + Focus' => 'Ontspannen + scherpstelling', + }, + }, + 'AF-OnForMB-D10' => { + Description => 'Functie AF-ON-knop MBD10', + PrintConv => { + 'AE Lock (hold)' => 'AE-vergrendeling (vast)', + 'AE Lock (reset on release)' => 'AE-vergr. (herstel na ontspan.)', + 'AE Lock Only' => 'AE-vergrendeling', + 'AE/AF Lock' => 'AE/AF-vergrendeling', + 'AF Lock Only' => 'AF-vergrendeling', + 'AF-On' => 'AF-ON', + 'Same as FUNC Button' => 'Zelfde als FUNC.-knop', + }, + }, + 'AF-SPrioritySelection' => { + Description => 'Selectie AF-S-prioriteit', + PrintConv => { + 'Focus' => 'Scherpstelling', + 'Release' => 'Ontspannen', + }, + }, + 'AFActivation' => { + Description => 'AF activering', + PrintConv => { + 'AF-On Only' => 'Alleen AF-ON', + 'Shutter/AF-On' => 'Ontspanknop/AF-ON', + }, + }, + 'AFAreaIllumination' => { + Description => 'AF-veld verlichting', + PrintConv => { + 'Auto' => 'Automatisch', + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AFAreaMode' => { + Description => 'AF-veldstand', + PrintConv => { + 'Auto-area' => 'Automatischveld AF', + 'Dynamic Area' => 'Dynamisch veld', + 'Single Area' => 'Enkelveld', + }, + }, + 'AFAreaModeSetting' => { + Description => 'AF-veldstand', + PrintConv => { + 'Closest Subject' => 'Dichtstbz. onderw.', + 'Dynamic Area' => 'Dynamisch veld', + 'Single Area' => 'Enkelveld', + }, + }, + 'AFAssist' => { + Description => 'AF-hulpverlichting', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AFPoint' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'AFPointActivationArea' => { + PrintConv => { + 'Standard' => 'Standaard', + }, + }, + 'AFPointBrightness' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'AFPointDisplayDuringFocus' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AFPointIllumination' => { + Description => 'Verlichting scherpstelpunt', + PrintConv => { + 'Auto' => 'Automatisch', + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AFPointMode' => { + PrintConv => { + 'Auto' => 'Automatisch', + }, + }, + 'AFPointRegistration' => { + PrintConv => { + 'Automatic' => 'Automatisch', + }, + }, + 'AFPointSelected' => { + PrintConv => { + 'Auto' => 'Automatisch', + }, + }, + 'AFPointSelected2' => { + PrintConv => { + 'Auto' => 'Automatisch', + }, + }, + 'AFPointSelection' => { + Description => 'Selectie scherpstelpunt', + PrintConv => { + '11 Points' => '11 punten', + '51 Points' => '51 punten (3D-tracking)', + }, + }, + 'AFPointSelectionMethod' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'AFPointsInFocus' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'AFPointsUnknown2' => { + PrintConv => { + 'Auto' => 'Automatisch', + }, + }, + 'AIServoTrackingSensitivity' => { + PrintConv => { + 'Standard' => 'Standaard', + }, + }, + 'APEVersion' => 'APE versie', + 'ARMIdentifier' => 'ARM herkenningscode', + 'ARMVersion' => 'ARM versie', + 'ActionAdvised' => { + Description => 'Actie advies', + PrintConv => { + 'Object Append' => 'Object toevoegen', + 'Object Kill' => 'Object verwijderen', + 'Object Reference' => 'Object referentie', + 'Object Replace' => 'Object vervangen', + 'Ojbect Append' => 'Object toevoegen', + }, + }, + 'ActiveD-Lighting' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Normal' => 'Normaal', + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ActiveD-LightingMode' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Normal' => 'Normaal', + 'Off' => 'Uit', + }, + }, + 'AddAspectRatioInfo' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'AddOriginalDecisionData' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AdultContentWarning' => { + PrintConv => { + 'Unknown' => 'Onbekend', + }, + }, + 'AdvancedRaw' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AlphaByteCount' => 'Aantal alphagegevens in bytes', + 'AlphaDataDiscard' => { + Description => 'Afgedankte alphagegevens', + PrintConv => { + 'Flexbits Discarded' => 'Afgedankte flexbits', + 'Full Resolution' => 'Volledige resolutie', + 'HighPass Frequency Data Discarded' => 'Afgedankte hoogdoorlaat frequentiegegevens', + 'Highpass and LowPass Frequency Data Discarded' => 'Afgedankte hoogdoorlaat en laagdoorlaat frequentiegegevens', + }, + }, + 'AlphaOffset' => 'Alphaverplaatsing', + 'Anti-Blur' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'Aperture' => 'Diafragma', + 'ApertureValue' => 'Diafragma', + 'ApplicationRecordVersion' => 'Gegevensversie', + 'Artist' => 'Maker van de afbeelding', + 'AssistButtonFunction' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'Audio' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'AudioDuration' => 'Audio duur', + 'AudioOutcue' => 'Audio eindaftiteling', + 'AudioSamplingRate' => 'Audio bemonsteringssnelheid', + 'AudioSamplingResolution' => 'Audio bemonsteringsresolutie', + 'AudioType' => { + Description => 'Type audio', + PrintConv => { + 'Mono Actuality' => 'Actualiteit (mono (1 kanaal) audio)', + 'Mono Music' => 'Muziek, zelf verstuurd (mono (1 kanaal) audio)', + 'Mono Question and Answer Session' => 'Vraag en antwoord sessie (mono (1 kanaal) audio)', + 'Mono Raw Sound' => 'Ruwe geluid (mono (1 kanaal) audio)', + 'Mono Response to a Question' => 'Beantwoord een vraag (mono (1 kanaal) audio)', + 'Mono Scener' => 'Toneel (mono (1 kanaal) audio)', + 'Mono Voicer' => 'Stem (mono (1 kanaals) audio)', + 'Mono Wrap' => 'Wrap (mono (1 kanaals) audio)', + 'Stereo Actuality' => 'Actualiteit (stereo (2 kanalen) audio)', + 'Stereo Music' => 'Muziek, zelf verstuurd (stereo (2 kanalen) audio)', + 'Stereo Question and Answer Session' => 'Vraag en antwoord sessie (stereo (2 kanalen) audio)', + 'Stereo Raw Sound' => 'Ruwe geluid (stereo (2 kanalen) audio)', + 'Stereo Response to a Question' => 'Beantwoord een vraag (stereo (2 kanalen) audio)', + 'Stereo Scener' => 'Toneel (stereo (2 kanaals) audio)', + 'Stereo Voicer' => 'Stem (stereo (2 kanaals) audio)', + 'Stereo Wrap' => 'Wrap (stereo (2 kanaals) audio)', + 'Text Only' => 'Alleen tekst (geen objectgegevens)', + }, + }, + 'Author' => 'Auteur', + 'AuthorsPosition' => 'Positie van de auteur', + 'AutoAperture' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AutoBracketModeM' => { + Description => 'Auto bracketing (M-stand)', + PrintConv => { + 'Flash Only' => 'Alleen flits', + 'Flash/Aperture' => 'Flits/diafragma', + 'Flash/Speed' => 'Flits/sluitertijd', + 'Flash/Speed/Aperture' => 'Flits/sluitertijd/diafragma', + }, + }, + 'AutoBracketOrder' => 'Bracketingvolgorde', + 'AutoBracketSet' => { + Description => 'Inst. voor auto bracketing', + PrintConv => { + 'AE & Flash' => 'AE & flits', + 'AE Only' => 'Alleen AE', + 'Flash Only' => 'Alleen flits', + 'WB Bracketing' => 'Witbalans bracketing', + }, + }, + 'AutoBracketing' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'AutoBracketingSet' => 'Inst. voor auto bracketing', + 'AutoExposureBracketing' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AutoFP' => { + Description => 'Auto FP', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AutoFocus' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AutoISO' => { + Description => 'ISO auto', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AutoISOMax' => 'ISO auto Maximale', + 'AutoISOMinShutterSpeed' => 'ISO auto Langste sluitertijd', + 'AutoLightingOptimizer' => { + PrintConv => { + 'Low' => 'Laag', + 'Off' => 'Uit', + 'Standard' => 'Standaard', + }, + }, + 'AutoLightingOptimizerOn' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'AutoRedEye' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'AutoRotate' => { + PrintConv => { + 'None' => 'Geen', + 'Rotate 180' => '180° (onder/rechts)', + 'Rotate 270 CW' => 'Draai 270° met de klok mee', + 'Rotate 90 CW' => '90° tegen de klok in (rechts/boven)', + 'n/a' => 'Onbekend', + }, + }, + 'BWMode' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'BackgroundColorIndicator' => 'Achtergrond kleur indicator', + 'BackgroundColorValue' => 'Achtergrond kleur waarde', + 'BackgroundTiling' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'BannerImageType' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'BatteryLevel' => 'Batterij status', + 'BatteryOrder' => { + Description => 'Batterijvolgorde', + PrintConv => { + 'Camera Battery First' => 'Camerabatterij eerst', + 'MB-D10 First' => 'MB-D10 batterijen eerst', + }, + }, + 'Beep' => { + Description => 'Signaal', + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'BitsPerSample' => 'Aantal Bits per component', + 'BlueMatrixColumn' => 'Blauwe matrixkolom', + 'BlueTRC' => 'Blauwe toon reproductie curve', + 'BlurWarning' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'BracketMode' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'BracketStep' => { + PrintConv => { + '1 EV' => '1 stop', + '1/3 EV' => '1/3 stop', + '2/3 EV' => '2/3 stop', + }, + }, + 'Brightness' => 'Helderheid', + 'BrightnessValue' => 'Helderheid', + 'BurstMode' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'By-line' => 'Maker', + 'By-lineTitle' => 'Beroep van de maker', + 'CFAPattern' => 'Kleur filter matrix', + 'CFAPattern2' => 'Kleurfiltermatrix 2', + 'CFARepeatPatternDim' => 'Kleurfiltermatrix grootte', + 'CLModeShootingSpeed' => 'Opnamesnelheid', + 'CMMFlags' => 'CMM vlaggen', + 'CPUType' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'CalibrationIlluminant1' => { + PrintConv => { + 'Cloudy' => 'Bewolkt', + 'Cool White Fluorescent' => 'Koel wit TL-licht', + 'Day White Fluorescent' => 'Daglicht wit TL-licht', + 'Daylight' => 'Daglicht', + 'Daylight Fluorescent' => 'Daglicht TL-licht', + 'Fine Weather' => 'Onbewolkt', + 'Flash' => 'Flits', + 'Fluorescent' => 'Fluoresceren', + 'ISO Studio Tungsten' => 'ISO studio kunstlicht (gloeilamp)', + 'Other' => 'Andere lichtbron', + 'Shade' => 'Schaduw', + 'Standard Light A' => 'Standaard licht A', + 'Standard Light B' => 'Standaard licht B', + 'Standard Light C' => 'Standaard licht C', + 'Tungsten (Incandescent)' => 'Kunstlicht (gloeilamp)', + 'Unknown' => 'Onbekend', + 'Warm White Fluorescent' => 'Warm wit TL-licht', + 'White Fluorescent' => 'Wit TL-licht', + }, + }, + 'CalibrationIlluminant2' => { + PrintConv => { + 'Cloudy' => 'Bewolkt', + 'Cool White Fluorescent' => 'Koel wit TL-licht', + 'Day White Fluorescent' => 'Daglicht wit TL-licht', + 'Daylight' => 'Daglicht', + 'Daylight Fluorescent' => 'Daglicht TL-licht', + 'Fine Weather' => 'Onbewolkt', + 'Flash' => 'Flits', + 'Fluorescent' => 'Fluoresceren', + 'ISO Studio Tungsten' => 'ISO studio kunstlicht (gloeilamp)', + 'Other' => 'Andere lichtbron', + 'Shade' => 'Schaduw', + 'Standard Light A' => 'Standaard licht A', + 'Standard Light B' => 'Standaard licht B', + 'Standard Light C' => 'Standaard licht C', + 'Tungsten (Incandescent)' => 'Kunstlicht (gloeilamp)', + 'Unknown' => 'Onbekend', + 'Warm White Fluorescent' => 'Warm wit TL-licht', + 'White Fluorescent' => 'Wit TL-licht', + }, + }, + 'CameraOrientation' => { + Description => 'Oriëntatie van de afbeelding', + PrintConv => { + 'Horizontal (normal)' => '0° (boven/links)', + 'Rotate 270 CW' => 'Draai 270° met de klok mee', + 'Rotate 90 CW' => '90° tegen de klok in (rechts/boven)', + }, + }, + 'CanonExposureMode' => { + PrintConv => { + 'Aperture-priority AE' => 'Diafragmaprioriteit', + 'Manual' => 'Handmatig', + 'Shutter speed priority AE' => 'Sluiterprioriteit', + }, + }, + 'CanonFlashMode' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'Caption-Abstract' => 'Titel/Beschrijving', + 'CaptionWriter' => 'Schrijver van het onderschrift', + 'CaptureXResolutionUnit' => { + PrintConv => { + 'um' => 'µm (micrometer)', + }, + }, + 'CaptureYResolutionUnit' => { + PrintConv => { + 'um' => 'µm (micrometer)', + }, + }, + 'Categories' => 'Categorieën', + 'Category' => 'Categorie', + 'CellLength' => 'Cel lengte', + 'CellWidth' => 'Cel breedte', + 'CenterAFArea' => { + Description => 'Centrale AF-veld', + PrintConv => { + 'Normal Zone' => 'Normaal', + 'Wide Zone' => 'Breed', + }, + }, + 'CenterWeightedAreaSize' => { + Description => 'Grootte meetgebied', + PrintConv => { + 'Average' => 'Gemiddeld', + }, + }, + 'CharacterSet' => 'Tekenset', + 'ChrominanceNR_TIFF_JPEG' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Off' => 'Uit', + }, + }, + 'ChrominanceNoiseReduction' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Off' => 'Uit', + }, + }, + 'City' => 'Plaats', + 'ClassifyState' => 'Rangschik status', + 'CodedCharacterSet' => 'Gecodeerde character set', + 'ColorAberrationControl' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ColorAdjustmentMode' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ColorBalanceAdj' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ColorBooster' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ColorEffect' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'ColorFilter' => { + Description => 'Kleurfilter', + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'ColorMode' => { + Description => 'Instellingen', + PrintConv => { + 'Autumn Leaves' => 'Herfstbladeren', + 'B&W' => 'Zwart-wit', + 'Clear' => 'Doorzichtig', + 'Deep' => 'Diep', + 'Evening' => 'Avond', + 'Landscape' => 'Landschap', + 'Light' => 'Licht', + 'Neutral' => 'Neutraal', + 'Night View' => 'Nacht', + 'Night View/Portrait' => 'Nachtportret', + 'Normal' => 'Normaal', + 'Off' => 'Uit', + 'Portrait' => 'Portret', + 'Standard' => 'Standaard', + 'Sunset' => 'Zonsondergang', + 'Vivid' => 'Levendige kleuren', + }, + }, + 'ColorMoireReduction' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ColorMoireReductionMode' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Off' => 'Uit', + }, + }, + 'ColorSpace' => { + Description => 'Kleur ruimte', + PrintConv => { + 'ICC Profile' => 'ICC-profiel', + 'Uncalibrated' => 'Niet vastgelegd', + }, + }, + 'ColorSpaceData' => 'Gegevenskleurenruimte', + 'ColorTable' => 'Kleur tabel', + 'ColorTemperature' => 'Kleurtemperatuur', + 'ColorTone' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'CommandDials' => { + Description => 'Instelschijven', + PrintConv => { + 'Reversed (Main Aperture, Sub Shutter)' => 'Verwissel hoofd/sec.', + 'Standard (Main Shutter, Sub Aperture)' => 'Standaard', + }, + }, + 'CommandDialsApertureSetting' => { + Description => 'Functie instelschijven inst. Instellen diafragma', + PrintConv => { + 'Aperture Ring' => 'Diafragmaring', + 'Sub-command Dial' => 'Secundaire instelschijf', + }, + }, + 'CommandDialsChangeMainSub' => { + Description => 'Functie instelschijven inst. Verwissel hoofd/secundair', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'CommandDialsMenuAndPlayback' => { + Description => 'Functie instelschijven inst. Menu’s en weergave', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'CommandDialsReverseRotation' => { + Description => 'Functie instelschijven inst. Rotatie omkeren', + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'CommanderChannel' => 'Commanderstand Kanaal', + 'CommanderGroupAManualOutput' => 'Commanderstand Groep A M Corrct', + 'CommanderGroupAMode' => { + Description => 'Commanderstand Groep A Stand', + PrintConv => { + 'Manual' => 'Handmatig', + 'Off' => 'Uit', + 'TTL' => 'DDL', + }, + }, + 'CommanderGroupA_TTL-AAComp' => 'Commanderstand Groep A DDL/AA Corrct', + 'CommanderGroupBManualOutput' => 'Commanderstand Groep B M Corrct', + 'CommanderGroupBMode' => { + Description => 'Commanderstand Groep B Stand', + PrintConv => { + 'Manual' => 'Handmatig', + 'Off' => 'Uit', + 'TTL' => 'DDL', + }, + }, + 'CommanderGroupB_TTL-AAComp' => 'Commanderstand Groep B DDL/AA Corrct', + 'CommanderInternalFlash' => { + Description => 'Commanderstand Ingb. flitsr Stand', + PrintConv => { + 'Manual' => 'Handmatig', + 'Off' => 'Uit', + 'TTL' => 'DDL', + }, + }, + 'CommanderInternalManualOutput' => 'Commanderstand Ingb. flitsr M Corrct', + 'CommanderInternalTTLComp' => 'Commanderstand Ingb. flitsr DDL Corrct', + 'Comment' => 'Kommentaar', + 'Compilation' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'ComponentsConfiguration' => 'Betekenis van elke component', + 'CompressedBitsPerPixel' => 'Afbeelding compressie modus', + 'Compression' => { + Description => 'Compressie schema', + PrintConv => { + 'JPEG' => 'JPEG-compressie', + 'JPEG (old-style)' => 'JPEG (oude versie)', + 'Kodak DCR Compressed' => 'Kodak DCR gcomprimeerd', + 'Kodak KDC Compressed' => 'Kodak KDC gecomprimeerd', + 'Next' => 'NeXT 2-Bit codering', + 'Nikon NEF Compressed' => 'Nikon NEF gecomprimeerd', + 'None' => 'Geen', + 'Pentax PEF Compressed' => 'Pentax PEF gecomprimeerd', + 'SGILog' => 'SGI 32-Bit Log Luminance gecodeerd', + 'SGILog24' => 'SGI 24-Bit Log Luminance gecodeerd', + 'Sony ARW Compressed' => 'Sony ARW gecomprimeerd', + 'Thunderscan' => 'ThunderScan 4-Bit codering', + 'Uncompressed' => 'Niet gecomprimeerd', + }, + }, + 'CompressionType' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'ConnectionSpaceIlluminant' => 'Witpunt van connectiekleurruimte', + 'ContentLocationCode' => 'Locatiecode van inhoud', + 'ContentLocationName' => 'Locatienaam van inhoud', + 'ContinuousDrive' => { + PrintConv => { + 'Continuous' => 'Continu', + }, + }, + 'Contrast' => { + Description => 'Kontrast', + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Normal' => 'Normaal', + }, + }, + 'ConversionLens' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'Copyright' => 'Copyright houder', + 'CopyrightNotice' => 'Copyright vermelding', + 'CopyrightStatus' => { + PrintConv => { + 'Unknown' => 'Onbekend', + }, + }, + 'Country' => 'Land', + 'Country-PrimaryLocationCode' => 'ISO landcode', + 'Country-PrimaryLocationName' => 'Land', + 'CreateDate' => 'Datum van de originele data generatie', + 'CreationDate' => 'Opname datum', + 'Creator' => 'Maker', + 'CreatorAddress' => 'Maker - Adres', + 'CreatorCity' => 'Maker - Plaats', + 'CreatorCountry' => 'Maker - Land', + 'CreatorPostalCode' => 'Maker - Postcode', + 'CreatorRegion' => 'Maker - Provincie', + 'CreatorWorkEmail' => 'Maker - E-mail', + 'CreatorWorkTelephone' => 'Maker - Telefoonnummer', + 'CreatorWorkURL' => 'Maker - Website(s)', + 'Credit' => 'Leverancier', + 'CropActive' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'Curves' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'CustomRendered' => { + Description => 'Gebruiker gedefineerde beeldverwerking', + PrintConv => { + 'Custom' => 'Gebruiker gedefineerd proces', + 'Normal' => 'Standaard proces', + }, + }, + 'D-LightingHQ' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'D-LightingHQSelected' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'D-LightingHS' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'DataImprint' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'DateCreated' => 'Opnamedatum', + 'DateSent' => 'Datum van zenden', + 'DateStampMode' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'DateTime' => 'Datum bestand wijziging', + 'DateTimeOriginal' => 'Datum van de originele data generatie', + 'DaylightSavings' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'Description' => 'Beschrijving', + 'Destination' => 'Bestemming', + 'DestinationDST' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'DeviceAttributes' => 'Apparaateigenschappen', + 'DeviceManufacturer' => 'Apparaatproducent', + 'DeviceMfgDesc' => 'Apparaatproducent kenmerk', + 'DeviceModel' => 'Apparaatmodel', + 'DeviceModelDesc' => 'Apparaatmodel kenmerk', + 'DeviceSettingDescription' => 'Toestelinstellingen', + 'DialDirectionTvAv' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'DigitalCreationDate' => 'Digitale opnamedatum', + 'DigitalCreationTime' => 'Digitaal opnametijdstip', + 'DigitalZoom' => { + Description => 'Digitaal zoomen', + PrintConv => { + 'None' => 'Geen', + 'Off' => 'Uit', + }, + }, + 'DigitalZoomOn' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'DigitalZoomRatio' => 'Digitale zoom factor', + 'Directory' => 'Plaats van het bestand', + 'DisplaySize' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'DisplayXResolutionUnit' => { + PrintConv => { + 'um' => 'µm (micrometer)', + }, + }, + 'DisplayYResolutionUnit' => { + PrintConv => { + 'um' => 'µm (micrometer)', + }, + }, + 'DistortionCorrection' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'DistortionCorrection2' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'DjVuVersion' => 'DjVu versie', + 'DocumentHistory' => 'Documentgeschiedenis', + 'DocumentName' => 'Document naam', + 'DocumentNotes' => 'Documentopmerkingen', + 'DriveMode' => { + Description => 'Ontspannermodus', + PrintConv => { + 'Continuous' => 'Continu', + 'Continuous Shooting' => 'Continu', + 'Off' => 'Uit', + 'Self-timer' => 'Zelfontspanner', + 'Self-timer Operation' => 'Zelfontspanner', + 'Single' => 'Enkel beeld', + 'Single Frame' => 'Enkel beeld', + 'Single Shot' => 'Enkel beeld', + 'Single-frame Shooting' => 'Enkel beeld', + }, + }, + 'DynamicAFArea' => { + Description => 'Dynamisch AF-veld', + PrintConv => { + '21 Points' => '21 punten', + '51 Points' => '51 punten', + '51 Points (3D-tracking)' => '51 punten (3D-tracking)', + '9 Points' => '9 punten', + }, + }, + 'DynamicRange' => { + PrintConv => { + 'Standard' => 'Standaard', + }, + }, + 'DynamicRangeExpansion' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'DynamicRangeOptimizer' => { + Description => 'Dyn.-bereikoptim', + PrintConv => { + 'Advanced Auto' => 'Geavancrd Auto', + 'Advanced Lv1' => 'Geavanceerd-1', + 'Advanced Lv2' => 'Geavanceerd-2', + 'Advanced Lv3' => 'Geavanceerd-3', + 'Advanced Lv4' => 'Geavanceerd-4', + 'Advanced Lv5' => 'Geavanceerd-5', + 'Auto' => 'Automatisch', + 'Off' => 'Uit', + 'Standard' => 'Standaard', + }, + }, + 'ETTLII' => { + PrintConv => { + 'Average' => 'Gemiddeld', + }, + }, + 'EVStepSize' => { + Description => 'LW stapgrootte', + PrintConv => { + '1/2 EV' => '1/2 stop', + '1/3 EV' => '1/3 stop', + }, + }, + 'EasyExposureCompensation' => { + Description => 'Eenv. belichtingscorrectie', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + 'On (auto reset)' => 'Aan (autoherstel)', + }, + }, + 'EasyMode' => { + PrintConv => { + 'Landscape' => 'Landschap', + 'Manual' => 'Handmatig', + 'Night' => 'Nachtscene', + 'Portrait' => 'Portret', + }, + }, + 'EdgeNoiseReduction' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'EditStatus' => 'Bewerkingsstatus', + 'EditorialUpdate' => { + Description => 'Redactionele bewerking', + PrintConv => { + 'Additional language' => 'Extra taal', + }, + }, + 'Emphasis' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'EnhanceDarkTones' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'Enhancement' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'EnvelopeNumber' => 'Basisdatanummer', + 'EnvelopePriority' => { + Description => 'Prioriteit', + PrintConv => { + '0 (reserved)' => '0 (Gereserveerd voor toekomstig gebruik)', + '1 (most urgent)' => '1 (Meest belangrijk)', + '5 (normal urgency)' => '5 (Normaal)', + '8 (least urgent)' => '8 (Minst belangrijk)', + '9 (user-defined priority)' => '9 (Door gebruiker aangegeven prioriteit)', + }, + }, + 'EnvelopeRecordVersion' => 'Recordversie', + 'ExifCameraInfo' => 'Exif Camera-informatie', + 'ExifImageHeight' => 'Afbeelding hoogte', + 'ExifImageWidth' => 'Afbeelding breedte', + 'ExifOffset' => 'Exif IFD-wijzer', + 'ExifToolVersion' => 'ExifTool versie', + 'ExifVersion' => 'Exif versie', + 'ExpandFilm' => 'Breid film uit', + 'ExpandFilterLens' => 'Breid filterlens uit', + 'ExpandFlashLamp' => 'Breid flitser uit', + 'ExpandLens' => 'Breid objectief', + 'ExpandScanner' => 'Breid scanner uit', + 'ExpandSoftware' => 'Breid software uit', + 'ExpirationDate' => 'Verloopdatum', + 'ExpirationTime' => 'Verlooptijdstip', + 'ExposureCompStepSize' => { + Description => 'Stapgrootte belichtingscorr.', + PrintConv => { + '1 EV' => '1 stop', + '1/2 EV' => '1/2 stop', + '1/3 EV' => '1/3 stop', + }, + }, + 'ExposureCompensation' => 'Belichtingscorrectie', + 'ExposureControlStepSize' => { + Description => 'Stapgrootte inst. belichting', + PrintConv => { + '1 EV' => '1 stop', + '1/2 EV' => '1/2 stop', + '1/3 EV' => '1/3 stop', + }, + }, + 'ExposureDelayMode' => { + Description => 'Spiegelvoorontspanning', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ExposureIndex' => 'Belichtingsindex', + 'ExposureLevelIncrements' => { + Description => 'Stapgrootte inst. belichting', + PrintConv => { + '1/2 Stop' => '1/2 stop', + '1/3 Stop' => '1/3 stop', + }, + }, + 'ExposureMode' => { + Description => 'Belichting modus', + PrintConv => { + 'Aperture Priority' => 'Diafragmaprioriteit', + 'Aperture-priority AE' => 'Diafragmaprioriteit', + 'Auto' => 'Automatische belichting', + 'Auto bracket' => 'Belichting serie', + 'Landscape' => 'Landschap', + 'Manual' => 'Handmatige belichting', + 'Portrait' => 'Portret', + 'Shutter Priority' => 'Sluiterprioriteit', + 'Shutter speed priority AE' => 'Sluiterprioriteit', + }, + }, + 'ExposureModeInManual' => { + PrintConv => { + 'Center-weighted average' => 'Centrum gemiddelde', + 'Partial metering' => 'Gedeelte', + }, + }, + 'ExposureProgram' => { + Description => 'Belichtingsprogramma', + PrintConv => { + 'Action (High speed)' => 'Actie programma (georiënteerd op snelle sluitertijden)', + 'Aperture Priority' => 'Diafragmaprioriteit', + 'Aperture-priority AE' => 'Diafragmaprioriteit', + 'Creative (Slow speed)' => 'Creatief programma (georiënteerd op scherptediepte)', + 'Landscape' => 'Landschap modus', + 'Manual' => 'Handmatig', + 'Not Defined' => 'Niet gedefinieerd', + 'Portrait' => 'Portret modus', + 'Program AE' => 'Normaal programma', + 'Shutter Priority' => 'Sluiterprioriteit', + 'Shutter speed priority AE' => 'Sluiterprioriteit', + }, + }, + 'ExposureTime' => 'Belichtingstijd', + 'ExposureTime2' => 'Belichtingstijd 2', + 'ExtendedWBDetect' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ExternalFlash' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ExternalFlashBounce' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'ExternalFlashMode' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'ExtraSamples' => 'Extra componenten', + 'FNumber' => 'F waarde', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => '0° (boven/links)', + 'Rotate 180' => '180° (onder/rechts)', + 'Rotate 270 CW' => 'Draai 270° met de klok mee', + 'Rotate 90 CW' => '90° tegen de klok in (rechts/boven)', + }, + }, + 'FastSeek' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'FaxProfile' => { + PrintConv => { + 'Unknown' => 'Onbekend', + }, + }, + 'FaxRecvParams' => 'Fax ontvangst parameters', + 'FaxRecvTime' => 'Fax ontvangst tijd', + 'FaxSubAddress' => 'Fax sub adres', + 'FileFormat' => 'Fileformaat', + 'FileModifyDate' => 'Datum actualisering', + 'FileName' => 'Bestandnaam', + 'FileNumberMemory' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'FileNumberSequence' => { + Description => 'Opeenvolgende nummering', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'FileSize' => 'Bestandgrootte', + 'FileSource' => { + Description => 'Bestand bron', + PrintConv => { + 'Digital Camera' => 'Digitale camera', + 'Film Scanner' => 'Film scanner', + 'Reflection Print Scanner' => 'Scanner', + }, + }, + 'FileType' => 'Bestandtype', + 'FileVersion' => 'Fileformaat versie', + 'Filename' => 'Bestandnaam', + 'FillOrder' => { + Description => 'Vul volgorde', + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'Filter' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'FilterEffect' => { + PrintConv => { + 'None' => 'Geen', + 'Off' => 'Uit', + }, + }, + 'FilterEffectMonochrome' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'FinderDisplayDuringExposure' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'FineTuneOptCenterWeighted' => 'Fijnafst. voor opt. belichting Centrumgericht', + 'FineTuneOptMatrixMetering' => 'Fijnafst. voor opt. belichting Matrixmeting', + 'FineTuneOptSpotMetering' => 'Fijnafst. voor opt. belichting Spotmeting', + 'FixtureIdentifier' => 'Kenmerk', + 'Flash' => { + Description => 'Flits', + PrintConv => { + 'Auto, Did not fire' => 'Flits werd niet ontstoken, automodus', + 'Auto, Did not fire, Red-eye reduction' => 'Flits werd niet ontstoken, rode ogen reductie', + 'Auto, Fired' => 'Flits werd ontstoken, automodus', + 'Auto, Fired, Red-eye reduction' => 'Flits werd ontstoken, automodus, rode ogen reductie', + 'Auto, Fired, Red-eye reduction, Return detected' => 'Flits werd ontstoken, automodus, gereflecteerd flitslicht, rode ogen reductie', + 'Auto, Fired, Red-eye reduction, Return not detected' => 'Flits werd ontstoken, automodus, geen gereflecteerd flitslicht, rode ogen reductie', + 'Auto, Fired, Return detected' => 'Flits werd ontstoken, automodus, gereflecteerd flitslicht', + 'Auto, Fired, Return not detected' => 'Flits werd ontstoken, automodus, geen gereflecteerd flitslicht', + 'Did not fire' => 'Geen flits', + 'Fired' => 'Flits afgevuurd', + 'Fired, Red-eye reduction' => 'Flits werd ontstoken, Rode ogen reductie', + 'Fired, Red-eye reduction, Return detected' => 'Flits werd ontstoken, rode ogen reductie, gereflecteerd flitslicht', + 'Fired, Red-eye reduction, Return not detected' => 'Flits werd ontstoken, rode oen reductie, geen gereflecteerd flitslicht', + 'Fired, Return detected' => 'Gereflecteerd flitslicht gedetecteerd', + 'Fired, Return not detected' => 'Geen gereflecteerd flitslicht gedetecteerd', + 'No Flash' => 'Geen flits', + 'No flash function' => 'Geen flits functie', + 'Off' => 'Uit', + 'Off, Did not fire' => 'Flits werd niet ontstoken, flits onderdruk modus', + 'Off, Did not fire, Return not detected' => 'Gedeactiveerd, flits werd niet ontstoken, geen gereflecteerd flitslicht', + 'Off, No flash function' => 'Gedeactiveerd, geen flits functie', + 'Off, Red-eye reduction' => 'Gedeactiveerd, rode ogen reductie', + 'On' => 'Aan', + 'On, Did not fire' => 'Aan, flits werd niet ontstoken', + 'On, Fired' => 'Flits werd ontstoken, flits afdwing modus', + 'On, Red-eye reduction' => 'Flits werd ontstoken, flits afdwing modus, rode ogen reductie', + 'On, Red-eye reduction, Return detected' => 'Flits werd ontstoken, flits afdwing modus, rode ogen reductie, gereflecteerd flitslicht', + 'On, Red-eye reduction, Return not detected' => 'Flits werd ontstoken, flits afdwing modus, rode ogen reductie, geen gereflecteerd flitslicht', + 'On, Return detected' => 'Flits werd ontstoken, flits afdwing modus, gereflecteerd flitslicht gedetecteerd', + 'On, Return not detected' => 'Flits werd ontstoken, flits afdwing modus, geen gereflecteerd flitslicht gedetecteerd', + }, + }, + 'FlashCommanderMode' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'FlashCompensation' => 'Flitscorrectie', + 'FlashControlMode' => { + PrintConv => { + 'Manual' => 'Handmatig', + 'Off' => 'Uit', + 'Repeating Flash' => 'Stroboscopisch flitsen', + }, + }, + 'FlashDevice' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'FlashEnergy' => 'Flits energie', + 'FlashExposureComp' => 'Flitscompensatie', + 'FlashExposureLock' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'FlashFired' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'FlashGroupAControlMode' => { + PrintConv => { + 'Manual' => 'Handmatig', + 'Off' => 'Uit', + 'Repeating Flash' => 'Stroboscopisch flitsen', + }, + }, + 'FlashGroupBControlMode' => { + PrintConv => { + 'Manual' => 'Handmatig', + 'Off' => 'Uit', + 'Repeating Flash' => 'Stroboscopisch flitsen', + }, + }, + 'FlashGroupCControlMode' => { + PrintConv => { + 'Manual' => 'Handmatig', + 'Off' => 'Uit', + 'Repeating Flash' => 'Stroboscopisch flitsen', + }, + }, + 'FlashIntensity' => { + PrintConv => { + 'High' => 'Hoog', + 'Normal' => 'Normaal', + }, + }, + 'FlashLevel' => 'Flitscorrectie', + 'FlashMode' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Normal' => 'Normaal', + 'Off' => 'Uit', + 'On' => 'Aan', + 'Unknown' => 'Onbekend', + }, + }, + 'FlashModel' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'FlashOptions' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Normal' => 'Normaal', + }, + }, + 'FlashOptions2' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Normal' => 'Normaal', + }, + }, + 'FlashShutterSpeed' => 'Langste sluitertijd bij flits', + 'FlashStatus' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'FlashSyncSpeed' => 'Flitssynchronisatie snelheid', + 'FlashSyncSpeedAv' => { + PrintConv => { + 'Auto' => 'Automatisch', + }, + }, + 'FlashType' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'FlashWarning' => { + Description => 'Flitswaarschuwing', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'FlashpixVersion' => 'Ondersteunde Flashpix versie', + 'FlickerReduce' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'FlipHorizontal' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'FocalLength' => 'Brandpuntafstand', + 'FocalLength35efl' => 'Brandpuntafstand', + 'FocalLengthIn35mmFormat' => 'Brandpuntafstand in 35 mm kleinbeeld formaat', + 'FocalPlaneResolutionUnit' => { + Description => 'Sensor resolutie eenheid', + PrintConv => { + 'None' => 'Geen', + 'inches' => 'inch', + 'um' => 'µm (micrometer)', + }, + }, + 'FocalPlaneXResolution' => 'Horizontale sensor resolutie', + 'FocalPlaneYResolution' => 'Verticale sensor resolutie', + 'Focus' => { + Description => 'Scherpstelling', + PrintConv => { + 'Manual' => 'Handmatig', + }, + }, + 'FocusArea' => 'Scherpstelveld', + 'FocusAreaSelection' => { + Description => 'Doorloop scherpstelpunt', + PrintConv => { + 'No Wrap' => 'Geen doorloop', + 'Wrap' => 'Doorloop', + }, + }, + 'FocusContinuous' => { + PrintConv => { + 'Continuous' => 'Continu', + 'Manual' => 'Handmatig', + }, + }, + 'FocusMode' => { + Description => 'Focus modus', + PrintConv => { + 'Auto' => 'Automatisch', + 'Continuous' => 'Continu', + 'Manual' => 'Handmatig', + 'Normal' => 'Normaal', + }, + }, + 'FocusMode2' => { + PrintConv => { + 'Manual' => 'Handmatig', + }, + }, + 'FocusModeSetting' => { + Description => 'Scherpstelstand', + PrintConv => { + 'AF-A' => 'Automatische AF', + 'AF-C' => 'Continue AF', + 'AF-S' => 'Enkelvoudige AF', + 'Manual' => 'Handmatig', + }, + }, + 'FocusPointWrap' => { + Description => 'Doorloop scherpstelpunt', + PrintConv => { + 'No Wrap' => 'Geen doorloop', + 'Wrap' => 'Doorloop', + }, + }, + 'FocusRange' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Manual' => 'Handmatig', + 'Normal' => 'Normaal', + }, + }, + 'FocusTrackingLockOn' => { + Description => 'Focus Tracking met Lock-On', + PrintConv => { + 'Long' => 'Lang', + 'Normal' => 'Normaal', + 'Off' => 'Uit', + 'Short' => 'Kort', + }, + }, + 'FrameRate' => 'Beeldwisselsnelheid', + 'FrameSize' => 'Beeldformaat', + 'FreeByteCounts' => 'Aantal bytes van het lege databereik', + 'FreeOffsets' => 'Vrije data posities', + 'FujiFlashMode' => { + PrintConv => { + 'Auto' => 'Automatisch', + }, + }, + 'FunctionButton' => { + Description => 'FUNC. knop', + PrintConv => { + 'AF-area Mode' => 'AF-veldstand', + 'Center AF Area' => 'Centrale AF-veld', + 'Center-weighted' => 'Centrumgericht', + 'FV Lock' => 'FV-vergrendeling', + 'Flash Off' => 'Flitser uit', + 'Framing Grid' => 'Rasterweergave', + 'ISO Display' => 'ISO-weergave', + 'Matrix Metering' => 'Matrixmeting', + 'Spot Metering' => 'Spotmeting', + }, + }, + 'GIFVersion' => 'GIF versie', + 'GPSAltitude' => 'Hoogte', + 'GPSAltitudeRef' => { + Description => 'GPS hoogte - referentie', + PrintConv => { + 'Above Sea Level' => 'Boven zeeniveau', + 'Below Sea Level' => 'Onder zeeniveau', + }, + }, + 'GPSAreaInformation' => 'GPS naam van het gebied', + 'GPSDOP' => 'GPS meetnauwkeurigheid', + 'GPSDateStamp' => 'GPS UTC datum', + 'GPSDateTime' => 'GPS UTC datum en tijd', + 'GPSDestBearing' => 'GPS peiling van bestemming', + 'GPSDestBearingRef' => { + Description => 'GPS peiling van bestemming - referentie', + PrintConv => { + 'Magnetic North' => 'Magnetische noorden', + 'True North' => 'Geografische noorden', + }, + }, + 'GPSDestDistance' => 'GPS afstand tot bestemming', + 'GPSDestDistanceRef' => { + Description => 'GPS afstand tot bestemming - referentie', + PrintConv => { + 'Miles' => 'Engelse mijlen', + 'Nautical Miles' => 'Zeemijlen', + }, + }, + 'GPSDestLatitude' => 'GPS breedtegraad van bestemming', + 'GPSDestLatitudeRef' => { + Description => 'GPS breedtegraad van bestemming - referentie', + PrintConv => { + 'North' => 'Noorderbreedte', + 'South' => 'Zuiderbreedte', + }, + }, + 'GPSDestLongitude' => 'GPS lengtegraad van bestemming', + 'GPSDestLongitudeRef' => { + Description => 'GPS lengtegraad van bestemming - referentie', + PrintConv => { + 'East' => 'Oosterlengte', + 'West' => 'Westerlengte', + }, + }, + 'GPSDifferential' => { + Description => 'GPS differentiaal correctie', + PrintConv => { + 'Differential Corrected' => 'Met differentiaal correctie', + 'No Correction' => 'Zonder differentiaal correctie', + }, + }, + 'GPSImgDirection' => 'GPS peiling van de afbeelding', + 'GPSImgDirectionRef' => { + Description => 'GPS peiling van de afbeelding - referentie', + PrintConv => { + 'Magnetic North' => 'Magnetische noorden', + 'True North' => 'Geografische noorden', + }, + }, + 'GPSInfo' => 'GPS Info', + 'GPSLatitude' => 'GPS breedtegraad', + 'GPSLatitudeRef' => { + Description => 'GPS breedtegraad - referentie', + PrintConv => { + 'North' => 'Noorderbreedte', + 'South' => 'Zuiderbreedte', + }, + }, + 'GPSLongitude' => 'GPS lengtegraad', + 'GPSLongitudeRef' => { + Description => 'GPS lengtegraad - referentie', + PrintConv => { + 'East' => 'Oosterlengte', + 'West' => 'Westerlengte', + }, + }, + 'GPSMapDatum' => 'GPS geodetisch datum', + 'GPSMeasureMode' => { + Description => 'GPS meetmethode', + PrintConv => { + '2-D' => 'Tweedimensionale meting', + '2-Dimensional' => 'Tweedimensionale meting', + '2-Dimensional Measurement' => 'Tweedimensionale meting', + '3-D' => 'Driedimensionale meting', + '3-Dimensional' => 'Driedimensionale meting', + '3-Dimensional Measurement' => 'Driedimensionale meting', + }, + }, + 'GPSPosition' => 'GPS positie', + 'GPSProcessingMethod' => 'GPS verwerkingsmethode', + 'GPSSatellites' => 'GPS satellieten gebruikt voor de meting', + 'GPSSpeed' => 'GPS ontvanger bewegingssnelheid', + 'GPSSpeedRef' => { + Description => 'GPS ontvanger bewegingssnelheid - referentie', + PrintConv => { + 'km/h' => 'Kilometer per uur', + 'knots' => 'Knopen', + 'mph' => 'Mijl per uur', + }, + }, + 'GPSStatus' => { + Description => 'GPS ontvanger status', + PrintConv => { + 'Measurement Active' => 'Actuele meting beschikbaar', + 'Measurement Void' => 'Actuele meting niet beschikbaar', + }, + }, + 'GPSTimeStamp' => 'GPS UTC tijd', + 'GPSTrack' => 'GPS ontvanger bewegingsrichting', + 'GPSTrackRef' => { + Description => 'GPS ontvanger bewegingsrichting - referentie', + PrintConv => { + 'Magnetic North' => 'Magnetische noorden', + 'True North' => 'Geografische noorden', + }, + }, + 'GPSVersionID' => 'GPS versie ID', + 'GainControl' => { + Description => 'Belichtingsversterking', + PrintConv => { + 'High gain down' => 'Hoge helderheidsverminderring', + 'High gain up' => 'Hoge helderheidsvesterking', + 'Low gain down' => 'Kleine helderheidsverminderring', + 'Low gain up' => 'Kleine helderheidsvesterking', + 'None' => 'Geen', + }, + }, + 'Gapless' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'Gradation' => 'Levendig', + 'GrayResponseUnit' => { + PrintConv => { + '0.0001' => 'Nummer stelt een 1000ste van een eenheid voor', + '0.001' => 'Nummer stelt een 100ste van een eenheid voor', + '0.1' => 'Nummer stelt een 10de van een eenheid voor', + '1e-05' => 'Nummer stelt een 10000ste van een eenheid voor', + '1e-06' => 'Nummer stelt een 100000ste van een eenheid voor', + }, + }, + 'GrayTRC' => 'Grijze toon reproductie curve', + 'GreenMatrixColumn' => 'Groene matrixkolom', + 'GreenTRC' => 'Groene toon reproductie curve', + 'GridDisplay' => { + Description => 'Rasterweergave in zoeker', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'HDR' => { + Description => 'Auto HDR', + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'Headline' => 'Opschrift', + 'HeightResolution' => 'Beeldresolutie verticaal', + 'HighISONoiseReduction' => { + Description => 'NR bij hoge-ISO', + PrintConv => { + 'Auto' => 'Automatisch', + 'High' => 'Hi', + 'Low' => 'Laag', + 'Normal' => 'Normaal', + 'Off' => 'Uit', + 'On' => 'Aan', + 'Standard' => 'Standaard', + }, + }, + 'HighlightTonePriority' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'HometownDST' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'Hue' => 'Kleurtoon', + 'ICCProfile' => 'ICC Profiel', + 'IPTC-NAA' => 'IPTC-NAA metadata', + 'ISO' => 'ISO gevoeligheid', + 'ISOAuto' => 'ISO auto', + 'ISODisplay' => 'ISO-weergave', + 'ISOExpansion' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ISOExpansion2' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'ISOSetting' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Manual' => 'Handmatig', + }, + }, + 'ISOSpeedExpansion' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'ISOSpeedIncrements' => { + Description => 'ISO-stapgrootte', + PrintConv => { + '1 Stop' => '1 stop', + '1/3 Stop' => '1/3 stop', + }, + }, + 'ISOStepSize' => { + Description => 'ISO-stapgrootte', + PrintConv => { + '1 EV' => '1 stop', + '1/2 EV' => '1/2 stop', + '1/3 EV' => '1/3 stop', + }, + }, + 'Illumination' => { + Description => 'Verlichting', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ImageAuthentication' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'ImageByteCount' => 'Aantal beeldgegevens in bytes', + 'ImageColorIndicator' => 'Afbeelding kleur indicator', + 'ImageColorValue' => 'Afbeelding kleur waarde', + 'ImageDataDiscard' => { + Description => 'Afgedankte beeldgegevens', + PrintConv => { + 'Flexbits Discarded' => 'Afgedankte flexbits', + 'Full Resolution' => 'Volledige resolutie', + 'HighPass Frequency Data Discarded' => 'Afgedankte hoogdoorlaat frequentiegegevens', + 'Highpass and LowPass Frequency Data Discarded' => 'Afgedankte hoogdoorlaat en laagdoorlaat frequentiegegevens', + }, + }, + 'ImageDescription' => 'Afbeelding beschrijving', + 'ImageDustOff' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ImageHeight' => 'Afbeeldingshoogte', + 'ImageHistory' => 'Afbeelding geschiedenis', + 'ImageNumber' => 'Afbeelding nummer', + 'ImageOffset' => 'Beeldverplaatsing', + 'ImageOrientation' => { + Description => 'Foto oriëntatie', + PrintConv => { + 'Landscape' => 'Landschap', + 'Portrait' => 'Portret', + 'Square' => 'Vierkant', + }, + }, + 'ImageQuality' => { + Description => 'Bldkwaliteit', + PrintConv => { + 'High' => 'Hoog', + 'Normal' => 'Normaal', + }, + }, + 'ImageQuality2' => 'Bldkwaliteit 2', + 'ImageReview' => { + Description => 'Beeld terugspelen', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ImageReviewTime' => 'Timers uit Beeld terugspelen', + 'ImageRotated' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'ImageSize' => 'Beeldformaat', + 'ImageStabilization' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ImageTone' => { + PrintConv => { + 'Landscape' => 'Landschap', + 'Portrait' => 'Portret', + }, + }, + 'ImageType' => 'Beeldtype', + 'ImageUniqueID' => 'Uniek afbeeldings ID', + 'ImageWidth' => 'Afbeeldingsbreedte', + 'InitialZoomSetting' => { + Description => 'Aanvankelijke zoominstelling', + PrintConv => { + 'High Magnification' => 'Hoge zoom', + 'Low Magnification' => 'Lage zoom', + 'Medium Magnification' => 'Gemiddelde zoom', + }, + }, + 'Instructions' => 'Instructies', + 'IntellectualGenre' => 'Genre', + 'IntensityStereo' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'InternalFlash' => { + Description => 'Flitssturing ingeb. flitser', + PrintConv => { + 'Commander Mode' => 'Commanderstand', + 'Fired' => 'Flits afgevuurd', + 'Manual' => 'Handmatig', + 'No' => 'Geen flits', + 'Off' => 'Uit', + 'On' => 'Aan', + 'Repeating Flash' => 'Stroboscopisch flitsen', + 'TTL' => 'DDL', + }, + }, + 'InternalFlashMode' => { + PrintConv => { + 'On' => 'Aan', + }, + }, + 'InteropIndex' => { + Description => 'Interoperabiliteits Identificatie', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03: DCF Optie formaat (Adobe RGB)', + 'R98 - DCF basic file (sRGB)' => 'R98: DCF Basis formaat (sRGB)', + 'THM - DCF thumbnail file' => 'THM: DCF Miniatuur formaat', + }, + }, + 'InteropOffset' => 'Interoperabiliteit-tag', + 'InteropVersion' => 'Interoperabiliteits versie', + 'JFIFVersion' => 'JFIF versie', + 'JPEGQuality' => { + Description => 'Beeldkwaliteit', + PrintConv => { + 'Extra Fine' => 'Extra fijn', + 'Fine' => 'Fijn', + 'Standard' => 'Normaal', + }, + }, + 'JobID' => 'ID van baan', + 'Keyword' => 'Trefwoorden', + 'Keywords' => 'Trefwoord', + 'LCDIllumination' => { + Description => 'LCD-verlichting', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'LCDIlluminationDuringBulb' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'LCHEditor' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'LanguageIdentifier' => 'Taalherkenning', + 'Lens' => 'Objectief', + 'LensInfo' => 'Lensgegevens', + 'LicenseType' => { + PrintConv => { + 'Unknown' => 'Onbekend', + }, + }, + 'LightSource' => { + Description => 'Lichtbron', + PrintConv => { + 'Cloudy' => 'Bewolkt', + 'Cool White Fluorescent' => 'Koel wit TL-licht', + 'Day White Fluorescent' => 'Daglicht wit TL-licht', + 'Daylight' => 'Daglicht', + 'Daylight Fluorescent' => 'Daglicht TL-licht', + 'Fine Weather' => 'Onbewolkt', + 'Flash' => 'Flits', + 'Fluorescent' => 'Fluoresceren', + 'ISO Studio Tungsten' => 'ISO studio kunstlicht (gloeilamp)', + 'Other' => 'Andere lichtbron', + 'Shade' => 'Schaduw', + 'Standard Light A' => 'Standaard licht A', + 'Standard Light B' => 'Standaard licht B', + 'Standard Light C' => 'Standaard licht C', + 'Tungsten (Incandescent)' => 'Kunstlicht (gloeilamp)', + 'Unknown' => 'Onbekend', + 'Warm White Fluorescent' => 'Warm wit TL-licht', + 'White Fluorescent' => 'Wit TL-licht', + }, + }, + 'LightSourceSpecial' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'Lightness' => 'Helderheid', + 'Lit' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'LiveViewShooting' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'Location' => 'Lokatie', + 'LongExposureNoiseReduction' => { + Description => 'NR lang-belicht', + PrintConv => { + 'Auto' => 'Automatisch', + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'LoopStyle' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'LuminanceNoiseReduction' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Off' => 'Uit', + }, + }, + 'MB-D10Batteries' => 'MB-D10 batterijen', + 'MB-D10BatteryType' => 'MB-D10 batterijen', + 'MB-D80Batteries' => 'MB-D80 batterijen', + 'MIEVersion' => 'MIE versie', + 'MSStereo' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'Macro' => { + PrintConv => { + 'Manual' => 'Handmatig', + 'Normal' => 'Normaal', + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'MacroMode' => { + PrintConv => { + 'Normal' => 'Normaal', + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'MainDialExposureComp' => { + Description => 'Main Dial Belichtingscorrectie', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'Make' => 'Fabrikant', + 'MakeAndModel' => 'Producent en model', + 'MakerNote' => 'Fabrikant informatie', + 'MakerNotes' => 'Wenken van de fabrikant', + 'ManualFlashOutput' => { + Description => 'Ingebouwde flitser Handmatig Sterkte', + PrintConv => { + 'Low' => 'Laag', + }, + }, + 'MasterDocumentID' => 'ID van hoofddocument', + 'MatrixMetering' => 'Matrixmeting', + 'MaxAperture' => 'Maximale lensopening', + 'MaxApertureValue' => 'Grootste diafragma', + 'MaxContinuousRelease' => 'Max. aant. continuopnamen', + 'MaxSampleValue' => 'Max sample waarde', + 'MediaBlackPoint' => 'Media zwartpunt', + 'MediaType' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'MediaWhitePoint' => 'Media Witpunt', + 'Metering' => { + Description => 'Lichtmeting', + PrintConv => { + 'Center-weighted' => 'Centrumgericht', + 'Matrix' => 'Matrixmeting', + }, + }, + 'MeteringMode' => { + Description => 'Belichting meet methode', + PrintConv => { + 'Average' => 'Gemiddeld', + 'Center-weighted average' => 'Centrum gemiddelde', + 'Multi-segment' => 'Multi segment', + 'Multi-spot' => 'MultiSpot', + 'Other' => 'Andere', + 'Partial' => 'Gedeelte', + 'Unknown' => 'Onbekend', + }, + }, + 'MeteringMode2' => { + PrintConv => { + 'Multi-segment' => 'Multi segment', + }, + }, + 'MeteringMode3' => { + PrintConv => { + 'Multi-segment' => 'Multi segment', + }, + }, + 'MeteringTime' => { + Description => 'Timers uit Belichtingsmeters', + PrintConv => { + 'No Limit' => 'Altijd aan', + }, + }, + 'MinSampleValue' => 'Min sample waarde', + 'MinoltaQuality' => { + PrintConv => { + 'Normal' => 'Normaal', + 'Standard' => 'Standaard', + }, + }, + 'Model' => 'Camera model', + 'Model2' => 'Camera model (2)', + 'ModelingFlash' => { + Description => 'Instellicht', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ModifiedPictureStyle' => { + PrintConv => { + 'Landscape' => 'Landschap', + 'None' => 'Geen', + 'Portrait' => 'Portret', + 'Standard' => 'Standaard', + }, + }, + 'ModifiedSaturation' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'ModifiedSharpnessFreq' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Standard' => 'Standaard', + }, + }, + 'ModifiedToneCurve' => { + PrintConv => { + 'Manual' => 'Handmatig', + 'Standard' => 'Standaard', + }, + }, + 'ModifiedWhiteBalance' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Cloudy' => 'Bewolkt', + 'Daylight' => 'Daglicht', + 'Daylight Fluorescent' => 'Daglicht TL-licht', + 'Flash' => 'Flits', + 'Fluorescent' => 'Fluoresceren', + 'Shade' => 'Schaduw', + 'Tungsten' => 'Kunstlicht (gloeilamp)', + }, + }, + 'ModifyDate' => 'Datum bestand wijziging', + 'MoireFilter' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'MonitorOffTime' => 'Monitor uit', + 'MonochromeFilterEffect' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'MonochromeLinear' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'MonochromeToningEffect' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'MultiExposureAutoGain' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'MultiExposureMode' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'MultiFrameNoiseReduction' => { + Description => 'Ruisond. Multi Frame', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'MultiSelector' => { + Description => 'Multi-selector', + PrintConv => { + 'Do Nothing' => 'Doe niets', + 'Reset Meter-off Delay' => 'Activeer lichtmeter', + }, + }, + 'MultiSelectorPlaybackMode' => { + Description => 'Centrale knop multiselector Weergavestand', + PrintConv => { + 'Choose Folder' => 'Map selecteren', + 'Thumbnail On/Off' => 'Miniatuur aan/uit', + 'View Histograms' => 'Histogrammen weergeven', + 'Zoom On/Off' => 'Zoom aan/uit', + }, + }, + 'MultiSelectorShootMode' => { + Description => 'Centrale knop multiselector Opnamestand', + PrintConv => { + 'Highlight Active Focus Point' => 'Actieve AF-punt markeren', + 'Not Used' => 'Geen functie', + 'Select Center Focus Point' => 'Middelste AF-punt selecteren', + }, + }, + 'MultipleExposureSet' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'Mute' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'MyColorMode' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'NDFilter' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'NEFCompression' => { + PrintConv => { + 'Uncompressed' => 'Niet gecomprimeerd', + }, + }, + 'NikonImageSize' => 'Beeldformaat', + 'NoMemoryCard' => { + Description => 'Geen geheugenkaart', + PrintConv => { + 'Enable Release' => 'Ontgrendel ontspanknop', + 'Release Locked' => 'Vergrendel ontspanknop', + }, + }, + 'Noise' => 'Ruis', + 'NoiseFilter' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Off' => 'Uit', + 'Standard' => 'Standaard', + }, + }, + 'NoiseReduction' => { + Description => 'Ruisreductie', + PrintConv => { + 'Auto' => 'Automatisch', + 'Normal' => 'Normaal', + 'Off' => 'Uit', + 'On' => 'Aan', + 'Standard' => 'Standaard', + }, + }, + 'ObjectAttributeReference' => 'Intellectuele genre', + 'ObjectCycle' => { + Description => 'Objectcyclus', + PrintConv => { + 'Both Morning and Evening' => 'Beide', + 'Evening' => 'Avond', + 'Morning' => 'Ochtend', + }, + }, + 'ObjectFileType' => { + PrintConv => { + 'None' => 'Geen', + 'Unknown' => 'Onbekend', + }, + }, + 'ObjectName' => 'Titel', + 'ObjectPreviewData' => 'Voorvertoning objectgegevens', + 'ObjectPreviewFileFormat' => 'Voorvertoning objectgegevens bestandsformaat', + 'ObjectPreviewFileVersion' => 'Voorvertoning objectgegevens bestandsformaatversie', + 'ObjectTypeReference' => 'Referentie van Object type', + 'OldSubfileType' => 'Subbestand type', + 'OneTouchWB' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'OpticalZoomMode' => { + PrintConv => { + 'Standard' => 'Standaard', + }, + }, + 'OpticalZoomOn' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'Opto-ElectricConvFactor' => 'Opto elektronische omreken factor', + 'Orientation' => { + Description => 'Oriëntatie van de afbeelding', + PrintConv => { + 'Horizontal (normal)' => '0° (boven/links)', + 'Mirror horizontal' => 'Horizontaal gespiegeld', + 'Mirror horizontal and rotate 270 CW' => 'Spiegel horizontaal en draai 270° met de klok mee', + 'Mirror horizontal and rotate 90 CW' => 'Spiegel horizontaal en draai 90° met de klok mee', + 'Mirror vertical' => 'Vertikaal gespiegeld', + 'Rotate 180' => '180° (onder/rechts)', + 'Rotate 270 CW' => 'Draai 270° met de klok mee', + 'Rotate 90 CW' => '90° tegen de klok in (rechts/boven)', + }, + }, + 'OriginalTransmissionReference' => 'Werknummer', + 'OriginatingProgram' => 'Oorspronkelijk programma', + 'OwnerID' => 'ID van eigenaar', + 'PEFVersion' => 'PEF versie', + 'Padding' => 'Plaatshouder', + 'PageName' => 'Pagina naam', + 'PageNumber' => 'Pagina nummer', + 'PhotoEffect' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'PhotoEffects' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'PhotoEffectsType' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'PhotoInfoPlayback' => { + Description => 'Functie van multi-selector', + PrintConv => { + 'Info Left-right, Playback Up-down' => 'Info <> / Foto’s', + 'Info Up-down, Playback Left-right' => 'Info / Foto’s <>', + }, + }, + 'PhotometricInterpretation' => { + Description => 'Pixel schema', + PrintConv => { + 'BlackIsZero' => 'Zwart is nul', + 'Color Filter Array' => 'CFA (Kleur Filter Matrix)', + 'Pixar LogL' => 'CIE Log2(L) (Log Luminantie)', + 'Pixar LogLuv' => 'CIE Log2(L)(u\',v\') (Log Luminantie en Chrominantie)', + 'RGB Palette' => 'RGB palet', + 'Transparency Mask' => 'Transparent masker', + 'WhiteIsZero' => 'Wit is nul', + }, + }, + 'PhotoshopFormat' => { + PrintConv => { + 'Standard' => 'Standaard', + }, + }, + 'PictureControl' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'PictureControlActive' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'PictureFinish' => { + PrintConv => { + 'Portrait' => 'Portret', + }, + }, + 'PictureMode' => { + PrintConv => { + 'Aperture-priority AE' => 'Diafragmaprioriteit', + 'Auto' => 'Automatisch', + 'Landscape' => 'Landschap', + 'Manual' => 'Handmatig', + 'Portrait' => 'Portret', + 'Shutter speed priority AE' => 'Sluiterprioriteit', + 'Standard' => 'Standaard', + }, + }, + 'PictureMode2' => { + PrintConv => { + 'Aperture Priority' => 'Diafragmaprioriteit', + 'Manual' => 'Handmatig', + 'Shutter Speed Priority' => 'Sluiterprioriteit', + }, + }, + 'PictureStyle' => { + PrintConv => { + 'Landscape' => 'Landschap', + 'None' => 'Geen', + 'Portrait' => 'Portret', + 'Standard' => 'Standaard', + }, + }, + 'PixelFormat' => 'Pixelformaat', + 'PixelUnits' => { + PrintConv => { + 'Unknown' => 'Onbekend', + }, + }, + 'PlanarConfiguration' => { + Description => 'Afbeelding data arrangement', + PrintConv => { + 'Chunky' => 'Chunky Formaat (Interleaved)', + 'Planar' => 'Planar Formaat', + }, + }, + 'Predictor' => { + PrintConv => { + 'Horizontal differencing' => 'Horizontale differencering', + 'None' => 'Geen prodictor schema in gebruik', + }, + }, + 'PreviewColorSpace' => { + PrintConv => { + 'Unknown' => 'Onbekend', + }, + }, + 'PreviewImage' => 'Voorbeeld', + 'PreviewImageValid' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'PreviewQuality' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'PrimaryChromaticities' => 'Chromaticiteit van primaire kleuren', + 'PrimaryPlatform' => 'Primaire platform', + 'ProcessingSoftware' => 'Verwerkingssoftware', + 'ProductID' => 'Produkt ID', + 'ProfileCMMType' => 'Profiel CMM-type', + 'ProfileClass' => { + Description => 'Profielklasse', + PrintConv => { + 'Abstract Profile' => 'Abstractprofiel', + 'ColorSpace Conversion Profile' => 'Kleurenruimteprofiel', + 'DeviceLink Profile' => 'Apparaatverbindingsprofiel', + 'Display Device Profile' => 'Beeldschermapparaatprofiel', + 'Input Device Profile' => 'Invoerapparaatprofiel', + 'NamedColor Profile' => 'Genoemd kleurenprofiel', + 'Nikon Input Device Profile (NON-STANDARD!)' => 'Nikonprofiel ("nkpf")', + 'Output Device Profile' => 'Uitvoerapparaatprofiel', + }, + }, + 'ProfileConnectionSpace' => 'Profielconnectieruimte', + 'ProfileCopyright' => 'Profielcopyright', + 'ProfileCreator' => 'Profielproducent', + 'ProfileDateTime' => 'Datum 1e aanmaak profiel', + 'ProfileDescription' => 'Profielbeschrijving', + 'ProfileDescriptionML' => 'Profielbeschrijving ML', + 'ProfileFileSignature' => 'Profiel filekenmerk', + 'ProfileID' => 'Profiel-ID', + 'ProfileSequenceDesc' => 'Profielvolgorde beschrijving', + 'ProfileVersion' => 'Profielversie', + 'ProgramLine' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'ProgramMode' => { + PrintConv => { + 'None' => 'Geen', + 'Portrait' => 'Portret', + }, + }, + 'ProgramVersion' => 'Programmaversie', + 'Province-State' => 'Provincie', + 'Quality' => { + Description => 'Beeldkwaliteit', + PrintConv => { + 'Compressed RAW' => 'cRAW', + 'Compressed RAW + JPEG' => 'cRAW+JPEG', + 'Extra Fine' => 'Extra fijn', + 'Fine' => 'Fijn', + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Normal' => 'Normaal', + 'RAW + JPEG' => 'RAW+JPEG', + 'Standard' => 'Standaard', + }, + }, + 'QualityMode' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'QuickShot' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'RAFVersion' => 'RAF versie', + 'RasterizedCaption' => 'Gerasterde titel', + 'Rating' => 'Waardering', + 'RatingPercent' => 'Waardering in procent', + 'RawDevAutoGradation' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'RawJpgQuality' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'RecordMode' => { + Description => 'Opnamemodus', + PrintConv => { + 'Aperture Priority' => 'Diafragmaprioriteit', + 'Manual' => 'Handmatig', + 'Shutter Priority' => 'Sluiterprioriteit', + }, + }, + 'RecordingMode' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Landscape' => 'Landschap', + 'Manual' => 'Handmatig', + 'Portrait' => 'Portret', + }, + }, + 'RedEyeCorrection' => { + PrintConv => { + 'Automatic' => 'Automatisch', + 'Off' => 'Uit', + }, + }, + 'RedEyeReduction' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'RedMatrixColumn' => 'Rode matrixkolom', + 'RedTRC' => 'Rode toon reproductie curve', + 'ReferenceBlackWhite' => 'Zwart/wit referentie punten', + 'ReferenceDate' => 'Referentiedatum', + 'ReferenceNumber' => 'Referentienummer', + 'ReferenceService' => 'Referentieservice', + 'RelatedImageFileFormat' => 'Bestandsformaat van de afbeelding', + 'RelatedImageHeight' => 'Afbeeldingshoogte', + 'RelatedImageWidth' => 'Afbeeldingsbreedte', + 'RelatedSoundFile' => 'Bijbehorend audio bestand', + 'ReleaseButtonToUseDial' => { + Description => 'Knop loslaten voor instelsch.', + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'ReleaseDate' => 'Datum van vrijgave', + 'ReleaseTime' => 'Tijdstip van vrijgave', + 'RemoteOnDuration' => 'Afstandsbediening', + 'RenderingIntent' => { + Description => 'Rendermethode', + PrintConv => { + 'ICC-Absolute Colorimetric' => 'Absoluut colorimetrisch', + 'Media-Relative Colorimetric' => 'Relatieve colorimetrisch', + 'Perceptual' => 'Waarnemend', + 'Saturation' => 'Verzadiging', + }, + }, + 'RepeatingFlashCount' => 'Stroboscopisch flitsen Aantal', + 'RepeatingFlashOutput' => 'Stroboscopisch flitsen Sterkte', + 'RepeatingFlashRate' => 'Stroboscopisch flitsen Freq.', + 'ResampleParamsQuality' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + }, + }, + 'Resaved' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'ResolutionUnit' => { + Description => 'Eenheid van de X und Y resolutie', + PrintConv => { + 'None' => 'Geen', + 'cm' => 'centimeter', + }, + }, + 'RetouchHistory' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'ReverseIndicators' => 'Aanduidingen omkeren', + 'Rotation' => { + PrintConv => { + 'Horizontal' => '0° (boven/links)', + 'Horizontal (Normal)' => '0° (boven/links)', + 'Horizontal (normal)' => '0° (boven/links)', + 'Rotate 180' => '180° (onder/rechts)', + 'Rotate 270 CW' => 'Draai 270° met de klok mee', + 'Rotate 90 CW' => '90° tegen de klok in (rechts/boven)', + 'Rotated 180' => '180° (onder/rechts)', + 'Rotated 270 CW' => 'Draai 270° met de klok mee', + 'Rotated 90 CW' => '90° tegen de klok in (rechts/boven)', + }, + }, + 'RowsPerStrip' => 'Aantal lijnen in de afbeelding', + 'SPIFFVersion' => 'SPIFF versie', + 'SRActive' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'SVGVersion' => 'SVG versie', + 'SampleFormat' => { + Description => 'Sample Formaat', + PrintConv => { + 'Complex int' => 'Complexe integer', + 'Float' => 'Drijvende komma waarde', + 'Signed' => 'Integer met voorteken', + 'Undefined' => 'Niet gedefinierd', + 'Unsigned' => 'Integer zonder voorteken', + }, + }, + 'SamplesPerPixel' => 'Aantal van de componenten', + 'Saturation' => { + Description => 'Kleurverzadiging', + PrintConv => { + 'High' => 'Hoge kleurvezadiging', + 'Low' => 'Lage kleurverzadiging', + 'Normal' => 'Normaal', + }, + }, + 'ScanImageEnhancer' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'SceneCaptureType' => { + Description => 'Scene opname type', + PrintConv => { + 'Landscape' => 'Landschap', + 'Night' => 'Nachtscene', + 'Portrait' => 'Portret', + 'Standard' => 'Standaard', + }, + }, + 'SceneMode' => { + Description => 'Scènekeuze', + PrintConv => { + '3D Sweep Panorama' => '3D', + 'Anti Motion Blur' => 'Anti-bewegingswaas', + 'Aperture Priority' => 'Diafragmaprioriteit', + 'Auto' => 'Automatisch', + 'Cont. Priority AE' => 'Continuvoork. AE', + 'Handheld Night Shot' => 'Nachtopname uit hand', + 'Landscape' => 'Landschap', + 'Manual' => 'Handmatig', + 'Night Portrait' => 'Nachtportret', + 'Night Scene' => 'Nacht', + 'Night View/Portrait' => 'Nacht/portret', + 'Normal' => 'Normaal', + 'Off' => 'Uit', + 'Portrait' => 'Portret', + 'Shutter Priority' => 'Sluiterprioriteit', + 'Sports' => 'Sportactie', + 'Standard' => 'Standaard', + 'Sunset' => 'Zonsondergang', + 'Sweep Panorama' => 'Panorama d. beweg.', + }, + }, + 'SceneModeUsed' => { + PrintConv => { + 'Aperture Priority' => 'Diafragmaprioriteit', + 'Landscape' => 'Landschap', + 'Manual' => 'Handmatig', + 'Portrait' => 'Portret', + 'Shutter Priority' => 'Sluiterprioriteit', + }, + }, + 'SceneSelect' => { + PrintConv => { + 'Night' => 'Nachtscene', + 'Off' => 'Uit', + }, + }, + 'SceneType' => { + Description => 'Scene type', + PrintConv => { + 'Directly photographed' => 'Direkt opgenomen afbeelding', + }, + }, + 'SecurityClassification' => { + Description => 'Veiligheid classificering', + PrintConv => { + 'Confidential' => 'Vertrouwelijk', + 'Restricted' => 'Begrensd', + 'Secret' => 'Geheim', + 'Top Secret' => 'Streng geheim', + 'Unclassified' => 'Niet geclassificeerd', + }, + }, + 'SelfTimer' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'SelfTimerMode' => 'Zelfontspanner mode', + 'SelfTimerTime' => 'Vertraging zelfontspanner', + 'SensingMethod' => { + Description => 'Meet methode', + PrintConv => { + 'Color sequential area' => 'Color sequential area sensor', + 'Color sequential linear' => 'Kleur sequentiële lineaire sensor', + 'Monochrome area' => 'Monochrome sensor', + 'Monochrome linear' => 'Monochrome lineaire sensor', + 'Not defined' => 'Niet gedefineerd', + 'One-chip color area' => 'Één chip kleur sensor', + 'Three-chip color area' => 'Drie chip kleur sensor', + 'Trilinear' => 'Trilineaire sensor', + 'Two-chip color area' => 'Twee chip kleur sensor', + }, + }, + 'SequentialShot' => { + PrintConv => { + 'None' => 'Geen', + 'Standard' => 'Standaard', + }, + }, + 'SerialNumber' => 'Camera-ID', + 'ServiceIdentifier' => 'Service ID', + 'SetButtonCrossKeysFunc' => { + PrintConv => { + 'Normal' => 'Normaal', + }, + }, + 'ShadingCompensation' => { + Description => 'Schaduwcompensatie', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ShadingCompensation2' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ShakeReduction' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'Sharpness' => { + Description => 'Scherpte', + PrintConv => { + 'Hard' => '+', + 'Normal' => 'Normaal', + 'Soft' => 'Zacht', + }, + }, + 'SharpnessFrequency' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + 'Standard' => 'Standaard', + }, + }, + 'ShootingInfoDisplay' => { + Description => 'Weergave opname-info', + PrintConv => { + 'Auto' => 'Automatisch', + 'Manual (dark on light)' => 'Handmatig - Donker op licht', + 'Manual (light on dark)' => 'Handmatig - Licht op donker', + }, + }, + 'ShootingMode' => { + Description => 'Opnamestand', + PrintConv => { + 'Aperture Priority' => 'Diafragmaprioriteit', + 'Manual' => 'Handmatig', + 'Normal' => 'Normaal', + 'Portrait' => 'Portret', + 'Shutter Priority' => 'Sluiterprioriteit', + }, + }, + 'ShootingModeSetting' => { + Description => 'Opnamestand', + PrintConv => { + 'Continuous' => 'Continu', + 'Delayed Remote' => 'Vertraagd op afstand', + 'Quick-response Remote' => 'Direct op afstand', + 'Self-timer' => 'Zelfontspanner', + 'Single Frame' => 'Enkel beeld', + }, + }, + 'ShortDocumentID' => 'Korte foto ID', + 'ShutterMode' => { + PrintConv => { + 'Aperture Priority' => 'Diafragmaprioriteit', + 'Auto' => 'Automatisch', + }, + }, + 'ShutterReleaseButtonAE-L' => { + Description => 'AE-vergr. ontspanknop', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ShutterReleaseNoCFCard' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'ShutterSpeed' => 'Belichtingstijd', + 'ShutterSpeedValue' => 'Belichtingstijd', + 'SimilarityIndex' => 'Fotogelijkheidsindex', + 'SlideShow' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'SlowShutter' => { + PrintConv => { + 'None' => 'Geen', + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'SlowSync' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'Software' => 'Gebruikte software', + 'Source' => 'Bron', + 'SpatialFrequencyResponse' => 'Spatial frequency response', + 'SpecialEffectsOpticalFilter' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'SpecialInstructions' => 'Instructies', + 'SpectralSensitivity' => 'Spectrale gevoeligheid', + 'State' => 'Staat', + 'StripByteCounts' => 'Aantal bytes per gecomprimeerd afbeelding deel', + 'StripOffsets' => 'Positie van afbeelding data', + 'Sub-location' => 'Locatie', + 'SubSecCreateDate' => 'Datum van de originele data generatie', + 'SubSecDateTimeOriginal' => 'Datum van de originele data generatie', + 'SubSecModifyDate' => 'Datum bestand wijziging', + 'SubSecTime' => 'DateTime 1/100 seconden', + 'SubSecTimeDigitized' => 'DateTimeDigitized 1/100 seconden', + 'SubSecTimeOriginal' => 'DateTimeOriginal 1/100 seconden', + 'SubfileType' => 'Nieuw subbestand type', + 'Subject' => 'Onderwerp', + 'SubjectArea' => 'Positie hoofdobject', + 'SubjectDistance' => 'Object afstand', + 'SubjectDistanceRange' => { + Description => 'Objectief afstandsbereik', + PrintConv => { + 'Close' => 'Dichtbij', + 'Distant' => 'Verweg', + 'Unknown' => 'Onbekend', + }, + }, + 'SubjectLocation' => 'Positie hoofdobject', + 'SubjectProgram' => { + PrintConv => { + 'None' => 'Geen', + 'Portrait' => 'Portret', + }, + }, + 'SubjectReference' => 'Onderwerp code', + 'Subsystem' => { + PrintConv => { + 'Unknown' => 'Onbekend', + }, + }, + 'SuperMacro' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'SuperimposedDisplay' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'SupplementalCategories' => 'Aanvullende categorie', + 'T4Options' => 'Opvul bit toegevoegd', + 'T6Options' => 'T6 Opties', + 'Tagged' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'TargetPrinter' => 'Doel Printer', + 'Technology' => { + Description => 'Technologie', + PrintConv => { + 'Active Matrix Display' => 'Actieve matrixdisplay', + 'Cathode Ray Tube Display' => 'CRT-beeldscherm', + 'Digital Camera' => 'Digitale camera', + 'Dye Sublimation Printer' => 'Dye sublimation printer', + 'Electrophotographic Printer' => 'Laserprinter', + 'Electrostatic Printer' => 'Electrostatische printer', + 'Film Scanner' => 'Filmscanner', + 'Film Writer' => 'Filmprinter', + 'Flexography' => 'Flexografie', + 'Gravure' => 'Fotogravure - koperdiepdruk', + 'Ink Jet Printer' => 'Inkjet printer', + 'Offset Lithography' => 'Offset Lithografie', + 'Passive Matrix Display' => 'Passieve matrixdisplay', + 'Photo CD' => 'Photo-CD', + 'Photo Image Setter' => 'Fotofilmbelichter', + 'Photographic Paper Printer' => 'Fotopapier printer', + 'Projection Television' => 'Projectietelevisie', + 'Reflective Scanner' => 'Reflectieve Scanner', + 'Thermal Wax Printer' => 'Thermische was printer', + 'Video Camera' => 'Videocamera', + 'Video Monitor' => 'Videomonitor', + }, + }, + 'Teleconverter' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'TextStamp' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ThumbnailImage' => 'Miniatuur', + 'ThumbnailImageSize' => 'Thumbnail formaat', + 'TimeCreated' => 'Opnametijdstip', + 'TimeScaleParamsQuality' => { + PrintConv => { + 'High' => 'Hoog', + 'Low' => 'Laag', + }, + }, + 'TimeSent' => 'Tijdstip van zenden', + 'TimerFunctionButton' => { + Description => 'Fn-knop', + PrintConv => { + 'ISO' => 'ISO-gevoeligheid', + 'Image Quality/Size' => 'Bldkwaliteit/-formaat', + 'Self-timer' => 'Zelfontspanner', + 'Shooting Mode' => 'Opnamestand', + 'White Balance' => 'Witbalans', + }, + }, + 'Title' => 'Titel', + 'ToneCurve' => { + PrintConv => { + 'Manual' => 'Handmatig', + 'Standard' => 'Standaard', + }, + }, + 'ToneCurveActive' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'ToningEffect' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'ToningEffectMonochrome' => { + PrintConv => { + 'None' => 'Geen', + }, + }, + 'TransferFunction' => 'Transformatie functie', + 'Transformation' => { + PrintConv => { + 'Horizontal (normal)' => '0° (boven/links)', + 'Mirror horizontal' => 'Horizontaal gespiegeld', + 'Mirror horizontal and rotate 270 CW' => 'Spiegel horizontaal en draai 270° met de klok mee', + 'Mirror horizontal and rotate 90 CW' => 'Spiegel horizontaal en draai 90° met de klok mee', + 'Mirror vertical' => 'Vertikaal gespiegeld', + 'Rotate 180' => '180° (onder/rechts)', + 'Rotate 270 CW' => 'Draai 270° met de klok mee', + 'Rotate 90 CW' => '90° tegen de klok in (rechts/boven)', + }, + }, + 'TransmissionReference' => 'Transmissiereferentie', + 'Trapped' => { + PrintConv => { + 'Unknown' => 'Onbekend', + }, + }, + 'Uncompressed' => { + Description => 'Niet gecomprimeerd', + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'UniqueDocumentID' => 'Uniek foto ID', + 'UniqueObjectName' => 'Unieke Naam van het Object', + 'Unknown' => 'Onbekend', + 'UnsharpMask' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'Urgency' => { + Description => 'Urgentie', + PrintConv => { + '0 (reserved)' => '0 (Gereserveerd voor toekomstig gebruik)', + '1 (most urgent)' => '1 (zeer dringend)', + '5 (normal urgency)' => '5 (dringend)', + '8 (least urgent)' => '8 (minst dringend)', + '9 (user-defined priority)' => '9 (Gereserveerd voor toekomstig gebruik)', + }, + }, + 'UserComment' => 'Gebruiker kommentaar', + 'UserDef1PictureStyle' => { + PrintConv => { + 'Landscape' => 'Landschap', + 'Portrait' => 'Portret', + 'Standard' => 'Standaard', + }, + }, + 'UserDef2PictureStyle' => { + PrintConv => { + 'Landscape' => 'Landschap', + 'Portrait' => 'Portret', + 'Standard' => 'Standaard', + }, + }, + 'UserDef3PictureStyle' => { + PrintConv => { + 'Landscape' => 'Landschap', + 'Portrait' => 'Portret', + 'Standard' => 'Standaard', + }, + }, + 'VRDVersion' => 'VRD versie', + 'VR_0x66' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'Version' => 'Versie', + 'VibrationReduction' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'ViewfinderWarning' => { + Description => 'Zoekerwaarschuwing', + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'VignetteControl' => { + PrintConv => { + 'High' => 'Hoog', + 'Normal' => 'Normaal', + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'VoiceMemo' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'WBAdjLighting' => { + PrintConv => { + 'Daylight (cloudy)' => 'Daglicht (2)', + 'Daylight (direct sunlight)' => 'Daglicht (0)', + 'Daylight (shade)' => 'Daglicht (1)', + 'Flash' => 'Flits', + 'None' => 'Geen', + }, + }, + 'WBBracketMode' => { + PrintConv => { + 'Off' => 'Uit', + }, + }, + 'WBFineTuneActive' => { + PrintConv => { + 'No' => 'Nee', + 'Yes' => 'Ja', + }, + }, + 'WBMode' => { + PrintConv => { + 'Auto' => 'Automatisch', + }, + }, + 'WCSProfiles' => 'Windows kleursysteemprofiel', + 'WhiteBalance' => { + Description => 'Witbalans', + PrintConv => { + 'Auto' => 'Automatisch', + 'Black & White' => 'Monochroom', + 'Cloudy' => 'Bewolkt', + 'Color Temperature/Color Filter' => 'Kleurtemperatuur / Kleurfilter', + 'Cool White Fluorescent' => 'Koelwit TL-licht', + 'Custom' => 'Eigen instel.', + 'Custom 1' => 'VOORKEUR 1', + 'Custom 2' => 'VOORKEUR 2', + 'Custom 3' => 'VOORKEUR 3', + 'Custom 4' => 'VOORKEUR 4', + 'Day White Fluorescent' => 'Daglicht wit TL-licht', + 'Daylight' => 'Daglicht', + 'Daylight Fluorescent' => 'Daglicht TL-licht', + 'Flash' => 'Flits', + 'Fluorescent' => 'Fluoresceren', + 'Manual' => 'Handmatig', + 'Shade' => 'Schaduw', + 'Tungsten' => 'Kunstlicht (gloeilamp)', + 'Unknown' => 'Onbekend', + 'Warm White Fluorescent' => 'Warm wit TL-licht', + 'White Fluorescent' => 'Wit TL-licht', + }, + }, + 'WhiteBalance2' => { + PrintConv => { + 'Auto' => 'Automatisch', + }, + }, + 'WhiteBalanceAdj' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Cloudy' => 'Bewolkt', + 'Daylight' => 'Daglicht', + 'Flash' => 'Flits', + 'Fluorescent' => 'Fluoresceren', + 'Off' => 'Uit', + 'On' => 'Aan', + 'Shade' => 'Schaduw', + 'Tungsten' => 'Kunstlicht (gloeilamp)', + }, + }, + 'WhiteBalanceMode' => { + PrintConv => { + 'Unknown' => 'Onbekend', + }, + }, + 'WhiteBalanceSet' => { + PrintConv => { + 'Auto' => 'Automatisch', + 'Cloudy' => 'Bewolkt', + 'Daylight' => 'Daglicht', + 'Daylight Fluorescent' => 'Daglicht TL-licht', + 'Flash' => 'Flits', + 'Manual' => 'Handmatig', + 'Shade' => 'Schaduw', + 'Tungsten' => 'Kunstlicht (gloeilamp)', + 'White Fluorescent' => 'Warm wit TL-licht', + }, + }, + 'WhitePoint' => 'Wit punt chromaticiteit', + 'WideRange' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, + 'WidthResolution' => 'Beeldresolutie horizontaal', + 'Writer-Editor' => 'Titel/Beschrijving auteur', + 'XMP' => 'XMP metadata', + 'XPAuthor' => 'Auteur', + 'XPComment' => 'Kommentaar', + 'XPKeywords' => 'Trefwoorden', + 'XPSubject' => 'Onderwerp', + 'XPTitle' => 'Titel', + 'XPosition' => 'X positie', + 'XResolution' => 'Horizontale afbeelding resolutie', + 'YCbCrCoefficients' => 'YCbCr coëfficiënt', + 'YCbCrPositioning' => { + Description => 'Y en C positie', + PrintConv => { + 'Centered' => 'Gecentreerd', + 'Co-sited' => 'Naast liggend', + }, + }, + 'YCbCrSubSampling' => 'Subsampling ratio van Y tot C', + 'YPosition' => 'Y positie', + 'YResolution' => 'Vertikale afbeelding resolutie', + 'ZoneMatching' => { + Description => 'Zoneaanpassing', + PrintConv => { + 'High Key' => 'Hi', + 'ISO Setting Used' => 'Uit', + 'Low Key' => 'Lo', + }, + }, + 'ZoneMatchingOn' => { + PrintConv => { + 'Off' => 'Uit', + 'On' => 'Aan', + }, + }, +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::nl.pm - ExifTool Dutch language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke, Peter Moonen, Herman Beld and Peter van der Laan for +providing this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/pl.pm b/ExifTool/lib/Image/ExifTool/Lang/pl.pm new file mode 100644 index 0000000..2ea71d4 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/pl.pm @@ -0,0 +1,1276 @@ +#------------------------------------------------------------------------------ +# File: pl.pm +# +# Description: ExifTool Polish language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::pl; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.12'; + +%Image::ExifTool::Lang::pl::Translate = ( + 'A100DataOffset' => 'PrzesuniÄ™cie danych A100', + 'AEAperture' => 'Priorytet AE', + 'AEExposureTime' => 'Czas ekspozycji AE', + 'AEInfo' => 'Informacja o automatycznej ekspozycji', + 'AELock' => { + Description => 'Blokada AE', + PrintConv => { + 'Off' => 'Wyłączona', + 'On' => 'Włączona', + }, + }, + 'AEMeteringMode' => 'Tryb pomiaru AE', + 'AEMeteringSegments' => 'Segmenty pomiaru AE', + 'AEProgramMode' => 'Tryb programu AE', + 'AFAdjustment' => 'Korekta AF', + 'AFInfo' => 'Informacje autofocusa', + 'AFIntegrationTime' => 'Czas integracji AF', + 'AFPoint' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'AFPointMode' => 'Tryb Autofokusa', + 'AFPointSelected' => { + Description => 'Wybrany punkt AF', + PrintConv => { + 'Auto' => 'Automatyczny', + 'Automatic Tracking AF' => 'ÅšledzÄ…cy AF', + 'Bottom' => 'Dolny', + 'Center' => 'Centralny', + 'Face Detect AF' => 'Wykrywanie twarzy', + 'Fixed Center' => 'Centralny', + 'Left' => 'Lewy', + 'Lower-left' => 'Dolny-lewy', + 'Lower-right' => 'Dolny-prawy', + 'Mid-left' => 'Åšrodek-lewy', + 'Mid-right' => 'Åšrodek-prawy', + 'Right' => 'Prawy', + 'Top' => 'Górny', + 'Upper-left' => 'Górny-lewy', + 'Upper-right' => 'Górny-prawy', + }, + }, + 'AFPointSelected2' => 'Wybrany punkt autofokusa 2', + 'AFPointsInFocus' => { + Description => 'Punkty AF w ostroÅ›ci', + PrintConv => { + 'Bottom-center' => 'Dolny-centralny', + 'Bottom-left' => 'Dolny-lewy', + 'Bottom-right' => 'Dolny-prawy', + 'Center' => 'Centralny', + 'Fixed Center or Multiple' => 'Centralny lub wiele', + 'Left' => 'Lewy', + 'None' => 'Brak', + 'Right' => 'Prawy', + 'Top-center' => 'Górny-centralny', + 'Top-left' => 'Górny-lewy', + 'Top-right' => 'Górny-prawy', + }, + }, + 'AFPointsSelected' => 'Wybrane punkty AF', + 'AFPredictor' => 'PrzewidujÄ…cy AF', + 'Aperture' => 'PrzysÅ‚ona', + 'ApertureRingUse' => 'Użycie pierÅ›cienia przysÅ‚ony', + 'ApertureValue' => 'PrzysÅ‚ona', + 'Artist' => 'Artysta', + 'Author' => 'Autor', + 'AuthorsPosition' => 'Pozycja autora', + 'AutoAperture' => 'Automatyczna przysÅ‚ona', + 'AutoBracketing' => 'Bracketing automatyczny', + 'AutoRotate' => { + PrintConv => { + 'None' => 'Brak', + 'Rotate 180' => '180° (dół/prawo)', + 'Rotate 270 CW' => '90° CW (lewo/dół)', + 'Rotate 90 CW' => '90° CCW (prawo/góra)', + }, + }, + 'AvApertureSetting' => 'Ustawienia priorytetu przysÅ‚ony Av', + 'BadFaxLines' => 'Uszkodzone wiersze transmisji Fax', + 'BannerImageType' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'BaseExposureCompensation' => 'Podstawowa kompensacja ekspozycji', + 'BitsPerSample' => 'Liczba bitów na skÅ‚adnik', + 'BlackPoint' => 'Punkt czerni', + 'BlueBalance' => 'Balans niebieskiego', + 'BlurWarning' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'BracketShotNumber' => 'Ilość zdjęć w bracketingu', + 'Brightness' => 'Jasność', + 'By-line' => 'Autor', + 'CFAPattern' => 'Wzorzec CFA', + 'CMMFlags' => 'Flagi CMM', + 'CPUFirmwareVersion' => 'Wersja firmware CPU', + 'CPUType' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'CalibrationIlluminant1' => { + PrintConv => { + 'Cloudy' => 'Zachmurzone niebo', + 'Cool White Fluorescent' => 'Zimna biaÅ‚a jarzeniówka (W 3800 - 4500K)', + 'Day White Fluorescent' => 'Jarzeniówka z naturalnym biaÅ‚ym Å›wiatÅ‚em (N 4600 - 5500K)', + 'Daylight' => 'ÅšwiatÅ‚o dzienne', + 'Daylight Fluorescent' => 'Jarzeniówka dajÄ…ca Å›wiatÅ‚o dzienne (D 5700 - 7100K)', + 'Fine Weather' => 'Dobra pogoda', + 'Flash' => 'Lampa bÅ‚yskowa', + 'Fluorescent' => 'Jarzeniowy', + 'ISO Studio Tungsten' => 'ISO dla studyjnych lamp żarowych', + 'Other' => 'Inne źródÅ‚o Å›wiatÅ‚a', + 'Shade' => 'CieÅ„', + 'Standard Light A' => 'Standardowe Å›wiatÅ‚o A', + 'Standard Light B' => 'Standardowe Å›wiatÅ‚o B', + 'Standard Light C' => 'Standardowe Å›wiatÅ‚o C', + 'Tungsten (Incandescent)' => 'ÅšwiatÅ‚o żarowe', + 'Unknown' => 'Nieznane', + 'White Fluorescent' => 'BiaÅ‚a jarzeniówka (WW3250 - 3800K)', + }, + }, + 'CalibrationIlluminant2' => { + PrintConv => { + 'Cloudy' => 'Zachmurzone niebo', + 'Cool White Fluorescent' => 'Zimna biaÅ‚a jarzeniówka (W 3800 - 4500K)', + 'Day White Fluorescent' => 'Jarzeniówka z naturalnym biaÅ‚ym Å›wiatÅ‚em (N 4600 - 5500K)', + 'Daylight' => 'ÅšwiatÅ‚o dzienne', + 'Daylight Fluorescent' => 'Jarzeniówka dajÄ…ca Å›wiatÅ‚o dzienne (D 5700 - 7100K)', + 'Fine Weather' => 'Dobra pogoda', + 'Flash' => 'Lampa bÅ‚yskowa', + 'Fluorescent' => 'Jarzeniowy', + 'ISO Studio Tungsten' => 'ISO dla studyjnych lamp żarowych', + 'Other' => 'Inne źródÅ‚o Å›wiatÅ‚a', + 'Shade' => 'CieÅ„', + 'Standard Light A' => 'Standardowe Å›wiatÅ‚o A', + 'Standard Light B' => 'Standardowe Å›wiatÅ‚o B', + 'Standard Light C' => 'Standardowe Å›wiatÅ‚o C', + 'Tungsten (Incandescent)' => 'ÅšwiatÅ‚o żarowe', + 'Unknown' => 'Nieznane', + 'White Fluorescent' => 'BiaÅ‚a jarzeniówka (WW3250 - 3800K)', + }, + }, + 'CameraOrientation' => { + Description => 'Orientacja obrazu', + PrintConv => { + 'Horizontal (normal)' => '0° (góra/lewo)', + 'Rotate 270 CW' => '90° CW (lewo/dół)', + 'Rotate 90 CW' => '90° CCW (prawo/góra)', + }, + }, + 'CameraSettings' => 'Ustawienia aparatu', + 'CameraTemperature' => 'Temperatura aparatu', + 'Caption-Abstract' => 'Podpis', + 'CaptionWriter' => 'Autor podpisu', + 'Categories' => 'Kategorie', + 'Category' => 'Kategorie', + 'CellLength' => 'DÅ‚ugość komórki', + 'CellWidth' => 'Szerokość komórki', + 'City' => 'Miasto', + 'CleanFaxData' => { + Description => 'Poprawne wiersze transmisji Fax', + PrintConv => { + 'Clean' => 'Dobry', + 'Regenerated' => 'Ponowiony', + 'Unclean' => 'Niedobry', + }, + }, + 'ClipPath' => 'Åšcieżka obciÄ™cia', + 'CodingMethods' => { + Description => 'Metody kompresji', + PrintConv => { + 'Baseline JPEG' => 'JPEG podstawowa', + 'JBIG color' => 'JBIG kolor', + 'Modified Huffman' => 'Huffmana zmodyfikowana', + 'Modified MR' => 'Zmodyfikowany MR', + 'Modified Read' => 'Zmodyfikowany odczyt', + 'Unspecified compression' => 'Nie podane', + }, + }, + 'ColorFilter' => 'Filtr kolorowy', + 'ColorInfo' => 'Informacje o kolorze', + 'ColorMap' => 'Mapa kolorów', + 'ColorMatrix1' => 'Macierz kolorów 1', + 'ColorMatrix2' => 'Macierz kolorów 2', + 'ColorSpace' => { + Description => 'Informacja o przestrzeni barwowej', + PrintConv => { + 'Uncalibrated' => 'Nie skalibrowany', + }, + }, + 'ColorSpaceData' => 'Dane przestrzeni barw', + 'ColorTemperature' => 'Temperatura barwowa', + 'Comment' => 'Komentarz', + 'ComponentsConfiguration' => 'Znaczenie każdego komponentu', + 'CompressedBitsPerPixel' => 'Tryb kompresji obrazu', + 'Compression' => { + Description => 'Algorytm kompresji', + PrintConv => { + 'Epson ERF Compressed' => 'Skompresowany Epson ERF', + 'JBIG B&W' => 'JBIG Czarno BiaÅ‚y', + 'JBIG Color' => 'JBIG Kolorowy', + 'JPEG' => 'Kompresja JPEG', + 'JPEG (old-style)' => 'JPEG (w starym stylu)', + 'Kodak DCR Compressed' => 'Skompresowany Kodak DCR', + 'Kodak KDC Compressed' => 'Skompresowany Kodak KDC', + 'Next' => 'Kodowanie 2-bitowe NeXT', + 'Nikon NEF Compressed' => 'Skompresowany Nikon NEF', + 'None' => 'Brak', + 'Packed RAW' => 'Spakowany RAW', + 'Pentax PEF Compressed' => 'Skompresowany Pentax PEF', + 'SGILog' => 'Kodowanie 32-bitowe SGI Log Luminance', + 'SGILog24' => 'Kodowanie 24-bitowe SGI Log Luminance', + 'Samsung SRW Compressed' => 'Skompresowany Samsung SRW', + 'Sony ARW Compressed' => 'Skompresowany Sony ARW', + 'Thunderscan' => 'Kodowanie 4-bitowe ThunderScan', + 'Uncompressed' => 'Bez kompresji', + }, + }, + 'CompressionType' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'ConsecutiveBadFaxLines' => 'Sekwencja uszkodzonych wierszy transmisji Fax', + 'Contrast' => { + Description => 'Kontrast', + PrintConv => { + 'High' => 'Ostre', + 'Low' => 'MiÄ™kkie', + 'Normal' => 'Standard', + }, + }, + 'Copyright' => 'Posiadacz praw autorskich', + 'CopyrightNotice' => 'Informacja o prawach autorskich', + 'Country' => 'Kraj', + 'Country-PrimaryLocationName' => 'Kraj', + 'CreateDate' => 'Data utworzenia', + 'CreationDate' => 'Data utworzenia', + 'Credit' => 'PodziÄ™kowania', + 'CropUnit' => { + PrintConv => { + 'inches' => 'Cal', + }, + }, + 'CropUnits' => { + PrintConv => { + 'inches' => 'Cal', + }, + }, + 'CustomRendered' => { + Description => 'Przetwarzanie zdjęć wedÅ‚ug ustawieÅ„ wÅ‚asnych', + PrintConv => { + 'Custom' => 'Proces zdefiniowany przez użytkownika', + 'Normal' => 'Normalny proces', + }, + }, + 'DNGBackwardVersion' => 'Poprzednia wersja DNG', + 'DNGVersion' => 'Wersja DNG', + 'DSPFirmwareVersion' => 'Wersja firmware DSP', + 'DataDump' => 'Zrzut danych?', + 'DataImprint' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'DataType' => 'Typ daty', + 'Date' => 'Data', + 'DateCreated' => 'Data utworzenia', + 'DateSent' => 'WysÅ‚ano datÄ™', + 'DateTimeOriginal' => 'Pierwotna data i godzina', + 'Decode' => 'Zdekodowany', + 'DefaultImageColor' => 'DomyÅ›lny kolor w obrazie', + 'Description' => 'Opis', + 'DestinationCity' => 'Miasto przeznaczenia', + 'DestinationCityCode' => 'Kod miasta przeznaczenia', + 'DestinationDST' => { + Description => 'Czas letni miasta przeznaczenia', + PrintConv => { + 'No' => 'Nie', + 'Yes' => 'Tak', + }, + }, + 'DeviceAttributes' => 'Atrybuty urzÄ…dzenia', + 'DeviceManufacturer' => 'Producent urzÄ…dzenia', + 'DeviceModel' => 'Model urzÄ…dzenia', + 'DeviceSettingDescription' => 'Opis ustawieÅ„ urzÄ…dzenia', + 'DigitalZoom' => 'Cyfrowy zoom', + 'DigitalZoomRatio' => 'Współczynnik cyfrowego zoomu', + 'Directory' => 'Lokalizacja pliku', + 'DisplayUnits' => { + PrintConv => { + 'inches' => 'Cal', + }, + }, + 'DisplayedUnitsX' => { + PrintConv => { + 'inches' => 'Cal', + }, + }, + 'DisplayedUnitsY' => { + PrintConv => { + 'inches' => 'Cal', + }, + }, + 'DocumentName' => 'Nazwa dokumentu', + 'DriveMode' => 'Tryb przesuwu', + 'DriveMode2' => 'Tryb zdjęć 2', + 'Duration' => 'Czas', + 'DynamicRangeExpansion' => { + Description => 'Rozszerzenie zakresu dynamiki', + PrintConv => { + 'Off' => 'Wyłączone', + 'On' => 'Włączone', + }, + }, + 'E-DialInProgram' => 'Program E-Dial', + 'EVStepInfo' => 'Informacja o krokach EV', + 'EVSteps' => 'Krok EV', + 'EffectiveLV' => 'Efektywne LV', + 'Emphasis' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'EnvelopePriority' => { + PrintConv => { + '0 (reserved)' => '0 (zarezerwowane na przyszÅ‚ość)', + '1 (most urgent)' => '1 (najpilniejszy)', + '5 (normal urgency)' => '5 (zwykÅ‚y)', + '8 (least urgent)' => '8 (niezbyt pilny)', + '9 (user-defined priority)' => '9 (priorytet okreÅ›lony przez użytkownika)', + }, + }, + 'ExifImageHeight' => 'Wysokość obrazu', + 'ExifImageWidth' => 'Szerokość obrazu', + 'ExifOffset' => 'Wskaźnik Exif IFD', + 'ExifVersion' => 'Wersja Exif', + 'ExposureBracketStepSize' => 'Krok bracketingu ekspozycji', + 'ExposureCompensation' => 'Różnica ekspozycji', + 'ExposureIndex' => 'Wskaźnik ekspozycji', + 'ExposureMode' => { + Description => 'Tryb ekspozycji', + PrintConv => { + 'Auto' => 'Automatyczna ekspozycja', + 'Auto bracket' => 'Funkcja Autobracketing', + 'Manual' => 'Manualna ekspozycja', + }, + }, + 'ExposureProgram' => { + Description => 'Program ekspozycji', + PrintConv => { + 'Action (High speed)' => 'Program akcji', + 'Aperture-priority AE' => 'Priorytet przysÅ‚ony', + 'Creative (Slow speed)' => 'Program kreatywny', + 'Landscape' => 'Krajobraz', + 'Manual' => 'Manualna ekspozycja', + 'Portrait' => 'Portret', + 'Program AE' => 'Normalny program', + 'Shutter speed priority AE' => 'Priorytet migawki', + }, + }, + 'ExposureTime' => 'Czas ekspozycji', + 'ExternalFlashExposureComp' => 'Kompensacja ekspozycji zewnetrznej lampy', + 'ExternalFlashGuideNumber' => 'Liczba przewodnia lampy zewnÄ™trznej', + 'ExternalFlashMode' => 'Tryb lampy zewnÄ™trznej', + 'FNumber' => 'PrzysÅ‚ona', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => '0° (góra/lewo)', + 'Rotate 180' => '180° (dół/prawo)', + 'Rotate 270 CW' => '90° CW (lewo/dół)', + 'Rotate 90 CW' => '90° CCW (prawo/góra)', + }, + }, + 'FaxProfile' => { + Description => 'Profil faxu (rodzaj zawartoÅ›ci)', + PrintConv => { + 'Extended B&W lossless, F' => 'Rozszerzony cz.b. bezstratny, F', + 'Lossless JBIG B&W, J' => 'Bezstratny JBIG cz.b., J', + 'Lossless color and grayscale, L' => 'Bezstratne kolor i skala szaroÅ›ci, L', + 'Lossy color and grayscale, C' => 'Stratne kolor i skala szaroÅ›ci, C', + 'Minimal B&W lossless, S' => 'Minimalny cz.b. bezstratny, S', + 'Mixed raster content, M' => 'Raster — zawartość mieszana, M', + 'Multi Profiles' => 'Wiele profili', + 'Unknown' => 'Nieznany', + }, + }, + 'FileFormat' => 'Format', + 'FileModifyDate' => 'Data aktualizacji', + 'FileName' => 'Nazwa pliku', + 'FileSize' => 'Wielkość pliku', + 'FileSource' => { + Description => 'ŹródÅ‚o pliku', + PrintConv => { + 'Digital Camera' => 'DSC', + 'Film Scanner' => 'Skaner do materiałów transparentnych', + 'Reflection Print Scanner' => 'Skaner do zdjęć', + }, + }, + 'FileType' => 'Typ pliku', + 'Filename' => 'Nazwa pliku', + 'FillOrder' => { + Description => 'Kolejność wypeÅ‚niania', + PrintConv => { + 'Normal' => 'Normalna', + 'Reversed' => 'ZajÄ™te', + }, + }, + 'FilterEffectMonochrome' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'Flash' => { + Description => 'Lampa', + PrintConv => { + 'Auto, Fired' => 'Włączony (Automatyczny bÅ‚ysk lampy)', + 'Auto, Fired, Red-eye reduction' => '"Włączony (BÅ‚ysk automatyczny, Redukcja efektu czerwonych oczu)"', + 'Auto, Fired, Red-eye reduction, Return detected' => '"Włączony (BÅ‚ysk automatyczny, Redukcja efektu czerwonych oczu, rejestracja Å›wiatÅ‚a odbitego)"', + 'Auto, Fired, Return detected' => '"Włączony (Automatyczny bÅ‚ysk lampy, rejestracja Å›wiatÅ‚a odbitego)"', + 'Did not fire' => 'Nie nastÄ…piÅ‚ bÅ‚ysk lampy', + 'Fired' => 'NastÄ…piÅ‚ bÅ‚ysk lampy', + 'Fired, Red-eye reduction' => 'Włączony (Redukcja efektu czerwonych oczu)', + 'Fired, Red-eye reduction, Return detected' => '"Włączony (Redukcja efektu czerwonych oczu, rejestracja Å›wiatÅ‚a odbitego)"', + 'Fired, Return detected' => 'Włączony (Rejestracja Å›wiatÅ‚a odbitego)', + 'No Flash' => 'Brak funkcji lampy bÅ‚yskowej', + 'On, Fired' => 'Włączony (BÅ‚ysk dopeÅ‚niajÄ…cy)', + 'On, Red-eye reduction' => '"Włączony (BÅ‚ysk dopeÅ‚niajÄ…cy, Redukcja efektu czerwonych oczu)"', + 'On, Red-eye reduction, Return detected' => '"Włączony (BÅ‚ysk dopeÅ‚niajÄ…cy, Redukcja efektu czerwonych oczu, rejestracja Å›wiatÅ‚a odbitego)"', + 'On, Return detected' => '"Włączony (BÅ‚ysk dopeÅ‚niajÄ…cy, rejestracja Å›wiatÅ‚a odbitego)"', + }, + }, + 'FlashDevice' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'FlashEnergy' => 'SiÅ‚a lampy bÅ‚yskowej', + 'FlashExposureComp' => 'Kompensacja lampy', + 'FlashMeteringSegments' => 'Segmenty pomiaru bÅ‚ysku', + 'FlashMode' => 'Tryb lampy', + 'FlashModel' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'FlashOptions' => 'Opcje lampy', + 'FlashStatus' => 'Stan lampy', + 'FlashType' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'FlashpixVersion' => 'ObsÅ‚ugiwana wersja Flashpix', + 'FocalLength' => 'Ogniskowa', + 'FocalLength35efl' => 'DÅ‚ugość ogniskowej (w wartoÅ›ciach dla formatu maÅ‚oobrazkowego)', + 'FocalLengthIn35mmFormat' => 'DÅ‚ugość ogniskowej dla aparatów maÅ‚oobrazkowych', + 'FocalPlaneResolutionUnit' => { + Description => 'Jednostka rozdzielczoÅ›ci w pÅ‚aszczyźnie ogniskowej', + PrintConv => { + 'None' => 'Brak', + 'inches' => 'Cal', + 'um' => 'µm (mikrometr)', + }, + }, + 'FocalPlaneXResolution' => 'Rozdzielczość w pÅ‚aszczyźnie ogniskowej - oÅ› x', + 'FocalPlaneYResolution' => 'Rozdzielczość w pÅ‚aszczyźnie ogniskowej - oÅ› y', + 'FocusMode' => 'Tryb ostrzenia', + 'FocusMode2' => 'Tryb Autofokusa 2', + 'FrameNumber' => 'Numer zdjÄ™cia', + 'FrameRate' => 'CzÄ™stotliwość zmiany kadrów', + 'FrameSize' => 'Wielkość kadru', + 'FreeByteCounts' => 'Liczba wolnych bajtów', + 'FreeOffsets' => 'Wolne offsety', + 'GDALMetadata' => 'Metadane GDAL', + 'GDALNoData' => 'Brak danych GDAL', + 'GPSAltitude' => 'Wysokość', + 'GPSAltitudeRef' => { + Description => 'Wysokość odniesienia', + PrintConv => { + 'Above Sea Level' => 'Poziom morza', + 'Below Sea Level' => 'WzglÄ™dem poziomu morza (wartość ujemna)', + }, + }, + 'GPSAreaInformation' => 'Nazwa obszaru GPS', + 'GPSDOP' => 'Precyzja pomiaru', + 'GPSDateStamp' => 'Dane GPS', + 'GPSDestBearing' => 'Azymut punktu docelowego', + 'GPSDestBearingRef' => 'Wartość odniesienia dla azymutu punktu docelowego.', + 'GPSDestDistance' => 'OdlegÅ‚ość do punktu docelowego.', + 'GPSDestDistanceRef' => 'Wartość odniesienia dla odlegÅ‚oÅ›ci do punktu docelowego.', + 'GPSDestLatitude' => 'Szerokość geograficzna punktu docelowego', + 'GPSDestLatitudeRef' => 'Wartość odniesienia dla szerokoÅ›ci geograficznej punktu docelowego.', + 'GPSDestLongitude' => 'DÅ‚ugość geograficzna punktu docelowego', + 'GPSDestLongitudeRef' => 'Wartość odniesienia dla dÅ‚ugoÅ›ci geograficznej punktu docelowego.', + 'GPSDifferential' => { + Description => 'Różnicowa korekcja GPS', + PrintConv => { + 'Differential Corrected' => 'Zastosowanie korekcji różnicowej', + 'No Correction' => 'Pomiar bez korekcji różnicowej', + }, + }, + 'GPSHPositioningError' => 'Błąd poziomy wyznaczenia pozycji', + 'GPSImgDirection' => 'Kierunek obrazu', + 'GPSImgDirectionRef' => 'Wartość odniesienia dla kierunku obrazu', + 'GPSInfo' => 'Wskaźnik GPS Info IFD', + 'GPSLatitude' => 'Szerokość geograficzna', + 'GPSLatitudeRef' => { + Description => 'Szerokość geograficzna północna lub poÅ‚udniowa', + PrintConv => { + 'North' => 'SzerokoÅ›ci północnej', + 'South' => 'SzerokoÅ›ci poÅ‚udniowej', + }, + }, + 'GPSLongitude' => 'DÅ‚ugość geograficzna', + 'GPSLongitudeRef' => { + Description => 'DÅ‚ugość geograficzna wschodnia lub zachodnia', + PrintConv => { + 'East' => 'DÅ‚ugoÅ›ci wschodniej', + 'West' => 'DÅ‚ugoÅ›ci zachodniej', + }, + }, + 'GPSMapDatum' => 'Wykorzystane dane z badaÅ„ geodezyjnych', + 'GPSMeasureMode' => { + Description => 'Tryb pomiaru GPS', + PrintConv => { + '3-Dimensional Measurement' => 'Pomiar 3-wymiarowy', + }, + }, + 'GPSProcessingMethod' => 'Nazwa metody przetwarzania GPS', + 'GPSSatellites' => 'Satelity GPS używane do pomiaru', + 'GPSSpeed' => 'PrÄ™dkość odbiornika GPS', + 'GPSSpeedRef' => { + Description => 'Jednostka prÄ™dkoÅ›ci', + PrintConv => { + 'km/h' => 'Kilometrów na godzinÄ™', + 'knots' => 'WÄ™zÅ‚y', + 'mph' => 'Mil na godzinÄ™', + }, + }, + 'GPSStatus' => { + Description => 'Stan odbiornika GPS', + PrintConv => { + 'Measurement Active' => 'Trwa pomiar', + 'Measurement Void' => 'Wzajemna niesprzeczność pomiarów', + }, + }, + 'GPSTimeStamp' => 'Czas GPS (zegar atomowy)', + 'GPSTrack' => 'Kierunek przesuniÄ™cia', + 'GPSTrackRef' => { + Description => 'Wartość odniesienia dla kierunku ruchu', + PrintConv => { + 'Magnetic North' => 'Kierunek strzaÅ‚ki magnetycznej', + 'True North' => 'Rzeczywisty kierunek', + }, + }, + 'GPSVersionID' => 'Wersja znacznika GPS', + 'GainControl' => { + Description => 'Sterowanie krokiem', + PrintConv => { + 'High gain down' => 'Z dużym krokiem do doÅ‚u', + 'High gain up' => 'Z dużym krokiem w górÄ™', + 'Low gain down' => 'Z maÅ‚ym krokiem do doÅ‚u', + 'Low gain up' => 'Z maÅ‚ym krokiem w górÄ™', + 'None' => 'Brak', + }, + }, + 'Genre' => 'Gatunek', + 'Gradation' => 'Gradacja', + 'GrayResponseCurve' => 'Krzywa odpowiedzi SzaroÅ›ci', + 'GrayResponseUnit' => 'Wielkość jednostki dla krzywej odpowiedzi szaroÅ›ci', + 'HalftoneHints' => 'Półtony', + 'Headline' => 'Nagłówek', + 'HighISONoiseReduction' => 'Redukcja szumu przy wysokim ISO', + 'HometownCity' => 'Miasto domowe', + 'HometownCityCode' => 'Kod miasta domowego', + 'HometownDST' => { + Description => 'Czas letni miasta domowego', + PrintConv => { + 'No' => 'Nie', + 'Yes' => 'Tak', + }, + }, + 'HostComputer' => 'Komputer użyty do wygenerowania obrazu', + 'Hue' => 'Barwa', + 'ICCProfile' => 'Profil ICC', + 'ISO' => 'CzuÅ‚ość ISO', + 'ISOFloor' => 'Minimalne ISO', + 'ISOSetting' => 'Ustawienia ISO', + 'ImageAreaOffset' => 'PrzesuniÄ™cie obszaru obrazu', + 'ImageDescription' => 'Opis obrazu', + 'ImageEditCount' => 'Licznik przetworzonych zdjęć', + 'ImageHeight' => 'Wysokość obrazu', + 'ImageID' => 'Identyfikator obrazu', + 'ImageProcessing' => 'Przetwarzanie obrazu', + 'ImageSize' => 'Rozmiar zdjÄ™cia', + 'ImageTone' => { + Description => 'OdcieÅ„ zdjÄ™cia', + PrintConv => { + 'Bright' => 'Jasny', + 'Landscape' => 'Krajobraz', + 'Monochrome' => 'Monochromatyczny', + 'Natural' => 'Naturalny', + 'Portrait' => 'Portret', + }, + }, + 'ImageType' => { + Description => 'Rodzaj obrazu', + PrintConv => { + 'Page' => 'Strona', + }, + }, + 'ImageUniqueID' => 'Unikalny kod ID zdjÄ™cia', + 'ImageWidth' => 'Szerokość obrazu', + 'Index' => 'Indeks', + 'Indexed' => { + Description => 'Indeksowane', + PrintConv => { + 'Indexed' => 'Indeksowane', + 'Not indexed' => 'Nie indeksowane', + }, + }, + 'InkNames' => 'Nazwy tuszy', + 'InkSet' => { + Description => 'Zestaw tuszy', + PrintConv => { + 'Not CMYK' => 'Nie CMYK', + }, + }, + 'Instructions' => 'Instrukcje', + 'InternalFlashMode' => 'Tryb wewnÄ™trznej lampy', + 'InternalFlashStrength' => 'Moc wewnÄ™trznej lampy', + 'InteropIndex' => { + Description => 'Identyfikacja wzajemnej zgodnoÅ›ci', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03: Plik pomocniczy DCF (Adobe RGB)', + 'R98 - DCF basic file (sRGB)' => 'R98: Plik zasadniczy DCF (sRGB)', + 'THM - DCF thumbnail file' => 'THM: Plik miniatury DCF', + }, + }, + 'InteropOffset' => 'Znacznik wzajemnej zgodnoÅ›ci', + 'InteropVersion' => 'Wersja wzajemnej zgodnoÅ›ci', + 'JPEGProc' => { + PrintConv => { + 'Baseline' => 'JPEG Podstawowy', + 'Lossless' => 'Bezstratny', + }, + }, + 'JPEGQuality' => { + Description => 'Jakość', + PrintConv => { + 'Standard' => 'Standardowa jakość', + }, + }, + 'JPEGRestartInterval' => 'JPEG odstÄ™p restartów', + 'Keywords' => 'SÅ‚owa kluczowe', + 'Lens' => 'Obiektyw', + 'LensID' => 'ID obiektywu', + 'LensInfo' => 'Informacja o obiektywie', + 'LensMake' => 'Producent obiektywu', + 'LensModel' => 'Model obiektywu', + 'LensSerialNumber' => 'Numer seryjny obiektywu', + 'LightReading' => 'Pomiar Å›wiatÅ‚a', + 'LightSource' => { + Description => 'ŹródÅ‚o Å›wiatÅ‚a', + PrintConv => { + 'Cloudy' => 'Zachmurzone niebo', + 'Cool White Fluorescent' => 'Zimna biaÅ‚a jarzeniówka (W 3800 - 4500K)', + 'Day White Fluorescent' => 'Jarzeniówka z naturalnym biaÅ‚ym Å›wiatÅ‚em (N 4600 - 5500K)', + 'Daylight' => 'ÅšwiatÅ‚o dzienne', + 'Daylight Fluorescent' => 'Jarzeniówka dajÄ…ca Å›wiatÅ‚o dzienne (D 5700 - 7100K)', + 'Fine Weather' => 'Dobra pogoda', + 'Flash' => 'Lampa bÅ‚yskowa', + 'Fluorescent' => 'Jarzeniowy', + 'ISO Studio Tungsten' => 'ISO dla studyjnych lamp żarowych', + 'Other' => 'Inne źródÅ‚o Å›wiatÅ‚a', + 'Shade' => 'CieÅ„', + 'Standard Light A' => 'Standardowe Å›wiatÅ‚o A', + 'Standard Light B' => 'Standardowe Å›wiatÅ‚o B', + 'Standard Light C' => 'Standardowe Å›wiatÅ‚o C', + 'Tungsten (Incandescent)' => 'ÅšwiatÅ‚o żarowe', + 'Unknown' => 'Nieznane', + 'White Fluorescent' => 'BiaÅ‚a jarzeniówka (WW3250 - 3800K)', + }, + }, + 'Lightness' => 'Jasność', + 'LocalizedCameraModel' => 'Lokalizowany model aparatu', + 'Location' => 'Miejsce', + 'Luminance' => 'Luminancja', + 'Macro' => 'Makro', + 'Make' => 'Producent', + 'MakeAndModel' => 'Producent i model', + 'MakerNote' => 'Prywatne dane DNG', + 'MakerNotes' => 'Dane producenta', + 'MaxAperture' => 'Maks. przysÅ‚ona obiektywu', + 'MaxSampleValue' => 'Maksymalny rozmiar próbki', + 'MeasurementGeometry' => { + Description => 'Geometria pomiaru', + PrintConv => { + '0/45 or 45/0' => '0/45 lub 45/0', + '0/d or d/0' => '0/d lub d/0', + }, + }, + 'MediaBlackPoint' => 'Punkt czerni materiaÅ‚u', + 'MediaWhitePoint' => 'Punkt bieli materiaÅ‚u', + 'MeteringMode' => { + Description => 'Tryb pomiaru', + PrintConv => { + 'Average' => 'Åšredni', + 'Center-weighted average' => 'Centralnie ważony uÅ›redniony', + 'Multi-segment' => 'Wzór', + 'Multi-spot' => 'Wielopunktowy', + 'Other' => 'Inne', + 'Partial' => 'Częściowy', + 'Spot' => 'Punktowy', + 'Unknown' => 'Nieznane', + }, + }, + 'MinSampleValue' => 'Minimalny rozmiar próbki', + 'ModeNumber' => 'Numer trybu', + 'Model' => 'Aparat', + 'ModifiedPictureStyle' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'ModifyDate' => 'Data i godzina zmiany pliku', + 'MonochromeFilterEffect' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'MonochromeToningEffect' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'MultipleExposureSet' => 'Wielokrotna ekspozycja', + 'NEFCompression' => { + PrintConv => { + 'Uncompressed' => 'Bez kompresji', + }, + }, + 'NativeDisplayInfo' => 'Informacja o natywnym(?) wyÅ›wietlaczu', + 'Noise' => 'Szumy', + 'NoiseReduction' => { + Description => 'Redukcja szumów', + PrintConv => { + 'Off' => 'Wyłączona', + 'On' => 'Włączona', + }, + }, + 'NumberofInks' => 'Liczba tuszy', + 'ObjectFileType' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'OldSubfileType' => { + Description => 'Stary typ podsekcji', + PrintConv => { + 'Full-resolution image' => 'Obraz w peÅ‚nej rozdzielczoÅ›ci', + 'Reduced-resolution image' => 'Obraz o zredukowanej rozdzielczoÅ›ci', + 'Single page of multi-page image' => 'Jedna strona obrazu wielostronicowego', + }, + }, + 'Opto-ElectricConvFactor' => 'Współczynnik przeksztaÅ‚cenia optyczno-elektrycznego', + 'Orientation' => { + Description => 'Orientacja obrazu', + PrintConv => { + 'Horizontal (normal)' => '0° (góra/lewo)', + 'Mirror horizontal' => '0° (góra/prawo)', + 'Mirror horizontal and rotate 270 CW' => '90° CW (lewo/góra)', + 'Mirror horizontal and rotate 90 CW' => '90° CCW (prawo/dół)', + 'Mirror vertical' => '180° (dół/lewo)', + 'Rotate 180' => '180° (dół/prawo)', + 'Rotate 270 CW' => '90° CW (lewo/dół)', + 'Rotate 90 CW' => '90° CCW (prawo/góra)', + }, + }, + 'OwnerName' => 'Nazwa wÅ‚aÅ›ciciela', + 'PageName' => 'Nazwa strony', + 'PageNumber' => 'Numer strony', + 'PentaxImageSize' => { + Description => 'Rozmiar obrazu Pentax\'a', + PrintConv => { + '2304x1728 or 2592x1944' => '2304 x 1728 lub 2592 x 1944', + '2560x1920 or 2304x1728' => '2560 x 1920 lub 2304 x 1728', + '2816x2212 or 2816x2112' => '2816 x 2212 lub 2816 x 2112', + '3008x2008 or 3040x2024' => '3008 x 2008 lub 3040 x 2024', + 'Full' => 'PeÅ‚ny', + }, + }, + 'PentaxVersion' => 'Wersja Pentax\'a', + 'PhotoEffectsType' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'PhotometricInterpretation' => { + Description => 'Schemat pikseli', + PrintConv => { + 'BlackIsZero' => 'Czarny jest zerem', + 'Color Filter Array' => 'CFA (Filtr kolorów matrycy)', + 'Pixar LogL' => 'CIE Log2(L) (Log luminancji)', + 'Pixar LogLuv' => 'CIE Log2(l)(u\',v\') (Log luminancji i chrominancji)', + 'RGB Palette' => 'Paleta kolorów', + 'Transparency Mask' => 'Maska przeźroczystoÅ›ci', + 'WhiteIsZero' => 'BiaÅ‚y jest zerem', + }, + }, + 'PictureMode' => 'Tryb obrazu', + 'PictureMode2' => 'Tryb obrazu 2', + 'PictureStyle' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'PixelFormat' => { + Description => 'Format zapisu koloru', + PrintConv => { + '128-bit PRGBA Float' => '128-bitów PRGBA (zapis zmienno przecinkowy)', + '128-bit RGB Float' => '128-bitów RGB (zapis zmienno przecinkowy)', + '128-bit RGBA Float' => '128-bitów RGBA (zapis zmienno przecinkowy)', + '16-bit Gray' => '16-bitów Szarość', + '32-bit Gray Float' => '32-bity Szarość (zapis zmienno przecinkowy)', + '48-bit RGB Fixed Point' => '48-bitów RGB (zapis staÅ‚opozycyjny)', + '8-bit Gray' => '8-bitów Szarość', + '96-bit RGB Fixed Point' => '96-bitów RGB (zapis staÅ‚opozycyjny)', + 'Black & White' => 'Czarno BiaÅ‚y', + }, + }, + 'PlanarConfiguration' => { + Description => 'UkÅ‚ad danych obrazu', + PrintConv => { + 'Chunky' => 'Format "chunky" (z przeplotem)', + 'Planar' => 'Format "planar"', + }, + }, + 'PowerSource' => 'Zasilanie', + 'Predictor' => { + Description => 'Przelicznik', + PrintConv => { + 'Horizontal differencing' => 'W oparciu o różnicÄ™ w poziomie', + 'None' => 'Bez przelicznika', + }, + }, + 'Preview0' => 'PodglÄ…d 0', + 'Preview1' => 'PodglÄ…d 1', + 'Preview2' => 'PodglÄ…d 2', + 'PreviewColorSpace' => { + PrintConv => { + 'Unknown' => 'Nieznany', + }, + }, + 'PreviewImage' => 'PodglÄ…d', + 'PreviewImageBorders' => 'Ramka podglÄ…du', + 'PreviewImageData' => 'Dane podglÄ…du obrazu', + 'PreviewImageLength' => 'DÅ‚ugość miniatury z podglÄ…dem', + 'PreviewImageSize' => 'Rozmiar podglÄ…du', + 'PreviewImageStart' => 'PoczÄ…tek miniatury z podglÄ…dem', + 'PrimaryChromaticities' => 'Tonalność kolorów podstawowych', + 'ProcessingSoftware' => 'Oprogramowanie wykorzystane do przetwarzania', + 'ProductID' => 'ID produktu', + 'ProfileCMMType' => 'Typ profilu CMM', + 'ProfileClass' => { + Description => 'Klasa profilu', + PrintConv => { + 'Abstract Profile' => 'Profil abstrakcyjny', + 'ColorSpace Conversion Profile' => 'Profil konwersji przestrzeni barw', + 'DeviceLink Profile' => 'Profil DeviceLink', + 'Display Device Profile' => 'Profil urzÄ…dzenia wyÅ›wietlajÄ…cego', + 'Input Device Profile' => 'Profil urzÄ…dzenia wejÅ›ciowego', + 'NamedColor Profile' => 'Nazwany profil kolorów', + 'Nikon Input Device Profile (NON-STANDARD!)' => 'Profil Nikon ("nkpf")', + 'Output Device Profile' => 'Profil urzÄ…dzenia wyjÅ›ciowego', + }, + }, + 'ProfileConnectionSpace' => 'Profil Connection Space', + 'ProfileDateTime' => 'Data i czas profilu', + 'ProfileDescription' => 'Opis profilu', + 'ProfileDescriptionML' => 'WielojÄ™zyczny opis profilu.', + 'ProfileSequenceDesc' => 'Opis sekwencji profilu', + 'ProfileType' => { + Description => 'Typ profilu', + PrintConv => { + 'Unspecified' => 'Nie podany', + }, + }, + 'ProfileVersion' => 'Wersja profilu', + 'ProgramLine' => 'Linia programu', + 'ProgramMode' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'Province-State' => 'Region', + 'Quality' => { + Description => 'Jakość', + PrintConv => { + 'Best' => 'Najlepsza', + 'Better' => 'Lepsza', + 'Good' => 'Dobra', + 'Low' => 'Niska jakość', + 'Normal' => 'Standardowa jakość', + }, + }, + 'Rating' => 'Ocena', + 'RatingPercent' => 'Ocena procentowo', + 'RawImageSize' => 'Rozmiar obrazu RAW', + 'RecordMode' => 'Tryb zapisu', + 'RedBalance' => 'Balans czerwonego', + 'RedMatrixColumn' => 'Kolumna matrycy czerwieni', + 'RedTRC' => 'Krzywa reprodukcji czerwieni', + 'ReferenceBlackWhite' => 'Para wartoÅ›ci odniesienia dla czarno-biaÅ‚ego obrazu', + 'RelatedImageFileFormat' => 'Format pliku powiÄ…zanego zdjÄ™cia', + 'RelatedImageHeight' => 'DÅ‚ugość powiÄ…zanego zdjÄ™cia', + 'RelatedImageWidth' => 'Szerokość powiÄ…zanego zdjÄ™cia', + 'RelatedSoundFile' => 'PowiÄ…zany plik audio', + 'ResolutionUnit' => { + Description => 'Jednostka rozdzielczoÅ›ci X i Y', + PrintConv => { + 'None' => 'Brak', + 'cm' => 'centymetry', + 'inches' => 'Cal', + }, + }, + 'RetouchHistory' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'Rotation' => { + Description => 'Obrót', + PrintConv => { + 'Horizontal' => '0° (góra/lewo)', + 'Horizontal (Normal)' => '0° (góra/lewo)', + 'Horizontal (normal)' => '0° (góra/lewo)', + 'Rotate 180' => '180° (dół/prawo)', + 'Rotate 270 CW' => '90° CW (lewo/dół)', + 'Rotate 90 CW' => '90° CCW (prawo/góra)', + 'Rotated 180' => '180° (dół/prawo)', + 'Rotated 270 CW' => '90° CW (lewo/dół)', + 'Rotated 90 CW' => '90° CCW (prawo/góra)', + }, + }, + 'RowsPerStrip' => 'Liczba rzÄ™dów w pasku', + 'SRFocalLength' => 'DÅ‚ugość fokalna SR', + 'SRResult' => 'Stabilizacja obrazu', + 'SampleFormat' => { + Description => 'Format próbki', + PrintConv => { + 'Float' => 'Zmienno przecinkowa', + 'Signed' => 'CaÅ‚kowita ze znakiem', + 'Undefined' => 'Nie podano', + 'Unsigned' => 'CaÅ‚kowita bez znaku', + }, + }, + 'SamplesPerPixel' => 'Liczba skÅ‚adników', + 'Saturation' => { + Description => 'Nasycenie', + PrintConv => { + 'High' => 'Wysokie nasycenie', + 'Low' => 'Niskie nasycenie', + 'Normal' => 'Standardowy', + }, + }, + 'SceneCaptureType' => { + Description => 'UjÄ™cie z programem tematycznym', + PrintConv => { + 'Landscape' => 'Krajobraz', + 'Night' => 'Sceny nocne', + 'Portrait' => 'Portret', + 'Standard' => 'Standardowy', + }, + }, + 'SceneMode' => { + Description => 'Tryby tematyczne', + PrintConv => { + 'Sunset' => 'Zachód sÅ‚oÅ„ca', + }, + }, + 'SceneType' => { + Description => 'Rodzaj sceny', + PrintConv => { + 'Directly photographed' => 'ZdjÄ™cie uzyskane bezpoÅ›rednio', + }, + }, + 'SensingMethod' => { + Description => 'Metoda pomiaru', + PrintConv => { + 'Color sequential area' => 'Sekwencyjny sensor obszaru koloru', + 'Color sequential linear' => 'Sekwencyjny liniowy sensor koloru', + 'One-chip color area' => 'Jednoprocesorowy sensor obszaru koloru', + 'Three-chip color area' => 'Trójprocesorowy sensor obszaru koloru', + 'Trilinear' => 'Trój liniowy sensor', + 'Two-chip color area' => 'Dwuprocesorowy sensor obszaru koloru', + }, + }, + 'SensitivityAdjust' => 'Regulacja czuÅ‚oÅ›ci', + 'SensitivitySteps' => 'Krok ustawienia czuÅ‚oÅ›ci', + 'SequentialShot' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'SerialNumber' => 'ID aparatu fotograficznego', + 'ServiceIdentifier' => 'Identyfikator usÅ‚ugi', + 'ShadingCompensation' => 'Kompensacja zacienienia', + 'Sharpness' => { + Description => 'Ostrość', + PrintConv => { + 'Hard' => 'Ostry', + 'Normal' => 'Standardowy', + 'Soft' => 'MiÄ™kki', + }, + }, + 'ShootingMode' => 'Tryb fotografowania', + 'ShutterCount' => 'Licznik migawki', + 'ShutterSpeed' => 'Czas ekspozycji', + 'ShutterSpeedValue' => 'PrÄ™dkość migawki', + 'SlaveFlashMeteringSegments' => 'Segmenty pomiarowe lampy podrzÄ™dnej', + 'SlowShutter' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'Software' => 'Oprogramowanie', + 'Source' => 'ŹródÅ‚o', + 'SpatialFrequencyResponse' => 'Przestrzenno - czÄ™stotliwoÅ›ciowa charakterystyka', + 'SpecialEffectsOpticalFilter' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'SpectralSensitivity' => 'CzuÅ‚ość spektralna', + 'State' => 'Region', + 'StripByteCounts' => 'Bajtów na skompresowany pasek', + 'StripOffsets' => 'Lokalizacja danych zdjÄ™cia', + 'SubSecTime' => '"Data i godzina, subsekundy"', + 'SubSecTimeDigitized' => '"Cyfrowa data i godzina, subsekundy"', + 'SubSecTimeOriginal' => '"Pierwotna data i godzina, sub-sekundy"', + 'SubfileType' => { + Description => 'Typ podsekcji', + PrintConv => { + 'Full-resolution image' => 'Obraz w peÅ‚nej rozdzielczoÅ›ci', + 'Reduced-resolution image' => 'Obraz o zredukowanej rozdzielczoÅ›ci', + 'Single page of multi-page image' => 'Jedna strona obrazu wielostronicowego', + 'Single page of multi-page reduced-resolution image' => 'Jedna strona obrazu wielostronicowego o zredukowanej rozdzielczoÅ›ci', + 'TIFF/IT final page' => 'Ostatnia strona TIFF/IT', + 'Transparency mask' => 'Maska przezroczystoÅ›ci', + 'Transparency mask of multi-page image' => 'Maska przezroczystoÅ›ci obrazu wielostronicowego', + 'Transparency mask of reduced-resolution image' => 'Maska przezroczystoÅ›ci obrazu o zredukowanej rozdzielczoÅ›ci', + 'Transparency mask of reduced-resolution multi-page image' => 'Maska przezroczystoÅ›ci obrazu wielostronicowego o zredukowanej rozdzielczoÅ›ci', + 'invalid' => 'Błędny', + }, + }, + 'SubjectArea' => 'Obszar obiektu', + 'SubjectDistance' => 'OdlegÅ‚ość od obiektu', + 'SubjectDistanceRange' => { + Description => 'Zakres odlegÅ‚oÅ›ci do obiektu', + PrintConv => { + 'Close' => 'Zbliżenie', + 'Distant' => 'OdlegÅ‚y plan', + 'Macro' => 'Makro', + }, + }, + 'SubjectLocation' => 'Lokalizacja obiektu', + 'SubjectProgram' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'SupplementalCategories' => 'Kategorie dodatkowe', + 'SvISOSetting' => 'Ustawienia ISO Sv', + 'T6Options' => { + PrintConv => { + 'Uncompressed' => 'Nieskompresowany', + }, + }, + 'TargetPrinter' => 'Docelowa drukarka', + 'Technology' => { + Description => 'Technologia', + PrintConv => { + 'Active Matrix Display' => 'WyÅ›wietlacz z matrycÄ… aktywnÄ…', + 'Cathode Ray Tube Display' => 'WyÅ›wietlacz kineskopowy', + 'Digital Camera' => 'Aparat cyfrowy', + 'Dye Sublimation Printer' => 'Drukarka termosublimacyjna', + 'Electrophotographic Printer' => 'Drukarka laserowa', + 'Film Scanner' => 'Skaner do filmów', + 'Film Writer' => 'Zapis na filmie', + 'Ink Jet Printer' => 'Drukarka atramentowa', + 'Passive Matrix Display' => 'WyÅ›wietlacz z matrycÄ… pasywnÄ…', + 'Photo CD' => 'Foto-CD', + 'Photographic Paper Printer' => 'Drukarka z papierem fotograficznym', + 'Projection Television' => 'Projektor telewizyjny', + 'Reflective Scanner' => 'Skaner', + 'Video Camera' => 'Kamera wideo', + 'Video Monitor' => 'Monitor wideo', + }, + }, + 'Teleconverter' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'Text' => 'Tekst', + 'Thresholding' => 'Progowanie', + 'ThumbnailImage' => 'Miniatura', + 'ThumbnailImageSize' => 'Rozmiar miniaturki', + 'TileByteCounts' => 'Liczba bajtów na kafelek', + 'TileLength' => 'Wysokość kafelka', + 'TileOffsets' => 'PrzesuniÄ™cie kafelków', + 'TileWidth' => 'Szerokość kafelka', + 'Time' => 'Czas', + 'Title' => 'TytuÅ‚', + 'ToneCurve' => 'Krzywa barwy', + 'ToneCurves' => 'Krzywe barwy', + 'ToningEffectMonochrome' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'Track' => 'Åšcieżka', + 'TransferFunction' => 'Funkcja transferu', + 'Transformation' => { + Description => 'PrzeksztaÅ‚cenie', + PrintConv => { + 'Horizontal (normal)' => '0° (góra/lewo)', + 'Mirror horizontal' => '0° (góra/prawo)', + 'Mirror horizontal and rotate 270 CW' => '90° CW (lewo/góra)', + 'Mirror horizontal and rotate 90 CW' => '90° CCW (prawo/dół)', + 'Mirror vertical' => '180° (dół/lewo)', + 'Rotate 180' => '180° (dół/prawo)', + 'Rotate 270 CW' => '90° CW (lewo/dół)', + 'Rotate 90 CW' => '90° CCW (prawo/góra)', + }, + }, + 'TransmissionReference' => 'OdnoÅ›nik transmisji', + 'TvExposureTimeSetting' => 'Ustawienia czasu ekspozycji TV', + 'Uncompressed' => 'Nieskompresowany', + 'UniqueCameraModel' => 'Unikatowy model aparatu', + 'Unknown' => 'Nieznany', + 'Urgency' => 'Priorytet', + 'UserComment' => 'Komentarz użytkownika', + 'VersionYear' => 'Rok wersji', + 'VideoCardGamma' => 'Gamma karty graficznej', + 'WBAdjLighting' => { + PrintConv => { + 'None' => 'Brak', + }, + }, + 'WB_RGGBLevelsCloudy' => 'Poziomy WB RGGB - zachmurzenie ', + 'WB_RGGBLevelsDaylight' => 'Poziomy WB RGGB - Å›wiatÅ‚o dzienne ', + 'WB_RGGBLevelsFlash' => 'Poziomy WB RGGB - lampa bÅ‚yskowa ', + 'WB_RGGBLevelsFluorescentD' => 'Poziomy WB RGGB - Å›wiatÅ‚o fluorescencyjne ', + 'WB_RGGBLevelsFluorescentN' => 'Poziomy WB RGGB - Å›wiatÅ‚o fluorescencyjne N ', + 'WB_RGGBLevelsFluorescentW' => 'Poziomy WB RGGB - Å›wiatÅ‚o fluorescencyjne W ', + 'WB_RGGBLevelsShade' => 'Poziomy WB RGGB - cieÅ„ ', + 'WB_RGGBLevelsTungsten' => 'Poziomy WB RGGB - Å›wiatÅ‚o żarowe ', + 'WhiteBalance' => { + Description => 'Balans bieli', + PrintConv => { + 'Auto' => 'Automatyczny balans bieli', + 'Black & White' => 'Monochromatyczny', + 'Cloudy' => 'Pochmurna pogoda', + 'Cool White Fluorescent' => 'Jarzeniówka z zimnym biaÅ‚ym Å›wiatÅ‚em', + 'Custom 1' => 'WÅASNE 1', + 'Custom 2' => 'WÅASNE 2', + 'Custom 3' => 'WÅASNE 3', + 'Custom 4' => 'WÅASNE 4', + 'Day White Fluorescent' => 'Jarzeniówka z naturalnym biaÅ‚ym Å›wiatÅ‚em', + 'Daylight' => 'ÅšwiatÅ‚o dzienne', + 'Daylight Fluorescent' => 'Jarzeniówka ze Å›wiatÅ‚em dziennym', + 'Fluorescent' => 'Jarzeniowy', + 'Manual' => 'Manualny balans bieli', + 'Shade' => 'CieÅ„', + 'Tungsten' => 'ÅšwiatÅ‚o żarówek', + }, + }, + 'WhiteBalanceMode' => { + Description => 'Tryb balansu bieli', + PrintConv => { + 'Auto (Cloudy)' => 'Automatyczny (zachmurzenie)', + 'Auto (Day White Fluorescent)' => 'Automatyczny (biaÅ‚e fluorescencyjne Å›wiatÅ‚o dzienne)', + 'Auto (Daylight Fluorescent)' => 'Automatyczny (fluorescencyjne Å›wiatÅ‚o dzienne)', + 'Auto (Daylight)' => 'Automatyczny (Å›wiatÅ‚o dzienne)', + 'Auto (Flash)' => 'Automatyczny (lampa bÅ‚yskowa)', + 'Auto (Shade)' => 'Automatyczny (cieÅ„)', + 'Auto (Tungsten)' => 'Automatyczny (Å›wiatÅ‚o żarowe)', + 'Auto (White Fluorescent)' => 'Automatyczny (biaÅ‚e Å›wiatÅ‚o fluorescencyjne)', + 'Unknown' => 'Automatyczny (nie wykryty)', + 'User-Selected' => 'Użytkownika', + }, + }, + 'WhiteBalanceSet' => 'Ustawienie balansu bieli', + 'WhitePoint' => 'Chromatyczność biaÅ‚ego punktu', + 'WorldTimeLocation' => { + Description => 'Miejsca czasu Å›wiatowego', + PrintConv => { + 'Destination' => 'Przeznaczenie', + 'Hometown' => 'Miasto domowe', + }, + }, + 'Writer-Editor' => 'Autor podpisu', + 'XClipPathUnits' => 'Jednostki wzdÅ‚uż osi X dla Å›cieżki obciÄ™cia', + 'XPosition' => 'WspółrzÄ™dna X', + 'XResolution' => 'Rozdzielczość obrazu w poziomie', + 'YCbCrCoefficients' => 'Współczynniki matrycy transformacji przestrzeni barwowej', + 'YCbCrPositioning' => { + Description => 'Pozycje Y i C', + PrintConv => { + 'Centered' => 'WyÅ›rodkowane', + 'Co-sited' => 'Obok siebie (?)', + }, + }, + 'YCbCrSubSampling' => 'Współczynnik podpróbkowania(?) Y do C', + 'YClipPathUnits' => 'Jednostki wzdÅ‚uż osi Y dla Å›cieżki obciÄ™cia', + 'YPosition' => 'WspółrzÄ™dna Y', + 'YResolution' => 'Rozdzielczość obrazu w pionie', + 'Year' => 'Rok', +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::pl.pm - ExifTool Polish language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke, Przemyslaw Sulek and Kacper Perschke for providing +this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/ru.pm b/ExifTool/lib/Image/ExifTool/Lang/ru.pm new file mode 100644 index 0000000..21943d9 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/ru.pm @@ -0,0 +1,5736 @@ +#------------------------------------------------------------------------------ +# File: ru.pm +# +# Description: ExifTool Russian language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::ru; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.08'; + +%Image::ExifTool::Lang::ru::Translate = ( + 'A100DataOffset' => 'Смещение данных (Sony A100)', + 'ABDate' => 'AB – Дата', + 'ABLabel' => 'AB – Метка', + 'ABRelatedNames' => 'AB – Похожие имена', + 'AB_UID' => 'AB – Уникальный ID', + 'AFAdjustment' => 'Регулировка автофокуÑа', + 'AIBuildNumber' => 'AI – Ðомер Ñборки', + 'AIColorModel' => 'AI – Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ð¼Ð¾Ð´ÐµÐ»ÑŒ', + 'AIColorUsage' => 'AI – ИÑпользование цвета', + 'AICreatorVersion' => 'AI – ВерÑÐ¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ', + 'AIFileFormat' => 'AI – Формат файла', + 'AINumLayers' => 'AI – КоличеÑтво Ñлоёв', + 'AIRulerUnits' => { + Description => 'AI – Единицы линейки', + PrintConv => { + 'Centimeters' => 'Сантиметры', + 'Inches' => 'Дюймы', + 'Millimeters' => 'Милиметры', + 'Picas' => 'Пики', + 'Pixels' => 'ПикÑели', + 'Points' => 'Точки', + }, + }, + 'AITargetResolution' => 'AI – Целевое разрешение', + 'APEVersion' => 'ВерÑÐ¸Ñ APE', + 'ARMIdentifier' => 'Идентификатор ARM', + 'ARMVersion' => 'ВерÑÐ¸Ñ ARM', + 'ATSCContent' => { + Description => 'Контент ATSC', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'About' => 'Об', + 'AbsoluteAltitude' => 'ÐбÑÐ¾Ð»ÑŽÑ‚Ð½Ð°Ñ Ð²Ñ‹Ñота', + 'Abstract' => 'Краткое опиÑание', + 'AbstractFileName' => 'Файл Ñ Ð°Ð±Ñтрактной информацией', + 'Acceleration' => 'УÑкорение', + 'AccelerationVector' => 'Вектор уÑкорениÑ', + 'Accelerometer' => 'ÐкÑелерометр', + 'AccelerometerTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð°ÐºÑелерометра', + 'AccelerometerX' => 'ÐкÑелерометр – X', + 'AccelerometerY' => 'ÐкÑелерометр – Y', + 'AccelerometerZ' => 'ÐкÑелерометр – Z', + 'AccessDate' => 'Дата поÑледнего доÑтупа', + 'Acknowledged' => 'БлагодарноÑти', + 'Action' => 'ДейÑтвие', + 'ActionAdvised' => { + Description => 'Рекомендуемое дейÑтвие', + PrintConv => { + 'Object Append' => 'Добавить объект', + 'Object Kill' => 'Уничтожить объект', + 'Object Reference' => 'Сделать ÑÑылку на объект', + 'Object Replace' => 'Заменить объект', + }, + }, + 'ActiveArea' => 'ÐÐºÑ‚Ð¸Ð²Ð½Ð°Ñ Ð¾Ð±Ð»Ð°Ñть', + 'Actor' => 'Ðктёр', + 'Address' => 'ÐдреÑÑ', + 'AdoptedNeutral' => 'ПринÑтый как нейтральный', + 'Adult' => 'Ð”Ð»Ñ Ð²Ð·Ñ€Ð¾Ñлых', + 'AdultContentWarning' => { + Description => 'Предупреждение о Ñодержании Ð´Ð»Ñ Ð²Ð·Ñ€Ð¾Ñлых', + PrintConv => { + 'Adult Content Warning Required' => 'ТребуетÑÑ', + 'Not Required' => 'Ðе требуетÑÑ', + 'Unknown' => 'Ðе задано', + }, + }, + 'AdvancedSceneMode' => { + Description => 'РаÑширенный режим Ñцены', + PrintConv => { + 'Architecture' => 'Ðрхитектура', + 'Bleach Bypass' => 'Удержание Ñеребра', + 'Cinema' => 'Кино', + 'Color Select' => 'Выбранный цвет', + 'Creative Macro' => 'ТворчеÑкий макро', + 'Creative Night Scenery' => 'ТворчеÑкий ночной пейзаж', + 'Creative Portrait' => 'ТворчеÑкий портрет', + 'Creative Scenery' => 'ТворчеÑкий пейзаж', + 'Creative Sports' => 'ТворчеÑкий Ñпорт', + 'Cross Process' => 'КроÑÑпроцеÑÑ', + 'Dynamic Art' => 'Динамика', + 'Dynamic Monochrome' => 'Динамичное монохромное', + 'Elegant' => 'Элегантный', + 'Expressive' => 'Выразительный', + 'Fantasy' => 'ФÑнтези', + 'Flower' => 'Цветок', + 'HDR Art' => 'HDR', + 'HDR B&W' => 'Чёрно-Белый HDR', + 'Handheld Night Shot' => 'ÐÐ¾Ñ‡Ð½Ð°Ñ Ñъёмка Ñ Ñ€ÑƒÐºÐ¸', + 'High Dynamic' => 'Ð’Ñ‹ÑÐ¾ÐºÐ°Ñ Ð´Ð¸Ð½Ð°Ð¼Ð¸ÐºÐ°', + 'High Key' => 'Ð’ выÑоком ключе', + 'Illuminations' => 'Свеча', + 'Impressive Art' => 'ВпечатлÑющий', + 'Indoor Portrait' => 'Внутренний портрет', + 'Indoor Sports' => 'Спорт в помещении', + 'Low Key' => 'Ð’ низком ключе', + 'Miniature' => 'Миниатюра', + 'Monochrome' => 'Монохромный', + 'Nature' => 'Природа', + 'Objects' => 'Объекты', + 'Old Days' => 'Старые времена', + 'Outdoor Portrait' => 'Ðаружный портрет', + 'Outdoor Sports' => 'Спорт на открытом воздухе', + 'Pure' => 'ЧиÑтый', + 'Retro' => 'Ретро', + 'Rough Monochrome' => 'Грубый монохромный', + 'Sepia' => 'СепиÑ', + 'Silhouette' => 'СилуÑÑ‚', + 'Silky Monochrome' => 'ШелковиÑтый монохромный', + 'Soft' => 'МÑгкий', + 'Star' => 'Звёзды', + 'Sunshine' => 'Солнечный Ñвет', + 'Toy Effect' => 'Эффект ломографии', + 'Toy Pop' => 'Ð›Ð¾Ð¼Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ñ Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸ÐµÐ¼ цвета', + }, + }, + 'AdventRevision' => 'Проверка поÑвлениÑ', + 'AdventScale' => 'МаÑштаб поÑвлениÑ', + 'AffineA' => 'Ðфинное преобразование A', + 'AffineB' => 'Ðфинное преобразование B', + 'AffineC' => 'Ðфинное преобразование C', + 'AffineD' => 'Ðфинное преобразование D', + 'AffineTransformMat' => 'Матрица Ðфинного преобразованиÑ', + 'AffineX' => 'Ðфинное преобразование X', + 'AffineY' => 'Ðфинное преобразование Y', + 'AlarmUID' => 'Уникальный ID Ñигнала', + 'Album' => 'Ðльбом', + 'AlbumArtist' => 'ИÑполнитель альбома', + 'AliasLayerMetadata' => 'ПÑевдоним метаданных ÑлоÑ', + 'Alignment' => 'Выравнивание', + 'AllColorFlatField1' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð¿Ð»Ð¾Ñкого Ð¿Ð¾Ð»Ñ â€“ Ð’Ñе цвета 1', + 'AllColorFlatField2' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð¿Ð»Ð¾Ñкого Ð¿Ð¾Ð»Ñ â€“ Ð’Ñе цвета 2', + 'AllColorFlatField3' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð¿Ð»Ð¾Ñкого Ð¿Ð¾Ð»Ñ â€“ Ð’Ñе цвета 3', + 'AllDayEvent' => 'Событие на веÑÑŒ день', + 'Alpha' => { + Description => 'Ðльфа-канал', + PrintConv => { + 'Alpha Exists (W color component)' => 'Ðльфа-канал приÑутÑтвует (цветной компонент W)', + 'Alpha Exists (color not premultiplied)' => 'Ðльфа-канал приÑутÑтвует (не предварительно умноженный цвет)', + 'Alpha Exists (color premultiplied)' => 'Ðльфа-канал приÑутÑтвует (предварительно умноженный цвет)', + 'No Alpha Plane' => 'Ðльфа-канал отÑутÑтвует', + }, + }, + 'AlphaByteCount' => 'Размер альфа-канала (байт)', + 'AlphaChannelsNames' => 'ÐÐ°Ð·Ð²Ð°Ð½Ð¸Ñ Ð°Ð»ÑŒÑ„Ð°-каналов', + 'AlphaDataDiscard' => { + Description => 'Отброшенные данные альфа-канала', + PrintConv => { + 'Flexbits Discarded' => 'Гибкие биты отброшены', + 'Full Resolution' => 'Полное разрешение', + 'HighPass Frequency Data Discarded' => 'Данные выÑоких чаÑтот отброшены', + 'Highpass and LowPass Frequency Data Discarded' => 'Данные выÑоких и низких чаÑтот отброшены', + }, + }, + 'AlphaIdentifiers' => 'Идентификаторы альфа-канала', + 'AlphaMask' => 'МаÑка Ðльфа-канала', + 'AlphaOffset' => 'Смещение альфа-канала', + 'AlternateDuotoneColors' => 'Ðльтернативные цвета Duotone', + 'AlternateSpotColors' => 'Ðльтернативные плашечные цвета', + 'AmbientInfrared' => 'Внешний инфракраÑный Ñвет', + 'AmbientLight' => 'Внешнее оÑвещение', + 'AmbientTemperature' => 'Температура окружающей Ñреды', + 'AmbientTemperatureFahrenheit' => 'Температура окружающей Ñреды по Фаренгейту', + 'AnalogBalance' => 'Ðналоговый Ð±Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾', + 'AngleInfoRoll' => 'Угол Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ (крен)', + 'AngleInfoYaw' => 'Угол поворота (рыÑкание)', + 'AnimationControl' => 'Управление анимацией', + 'AnimationFrames' => 'Кадры анимации', + 'AnimationIterations' => 'Повторений анимации', + 'AnimationPlays' => 'Проигрывание анимации', + 'Anniversary' => 'Годовщина', + 'Annotation' => 'ÐннотациÑ', + 'AnnotationUsageRights' => 'Права на иÑпользование Ðннотации', + 'Annotations' => 'Ðннотации', + 'Announce' => 'Трекер', + 'AnnounceList1' => 'СпиÑок трекеров 1', + 'AntiAliasStrength' => 'Сила ÑглаживаниÑ', + 'Aperture' => 'Диафрагма', + 'ApertureValue' => 'Диафрагма', + 'AppleKeywords' => 'Apple – Ключевые Ñлова', + 'AppleMailDateReceived' => 'Apple Mail – Дата получениÑ', + 'AppleMailDateSent' => 'Apple Mail – Дата отправки', + 'AppleMailFlagged' => 'Apple Mail – Отмечено флажком', + 'AppleMailIsRemoteAttachment' => 'Apple Mail – Ðаличие удалённого вложениÑ', + 'AppleMailMessageID' => 'Apple Mail – ID ÑообщениÑ', + 'AppleMailPriority' => 'Apple Mail – Приоритет', + 'AppleMailRead' => 'Apple Mail – Прочитано', + 'AppleMailRepliedTo' => 'Apple Mail – Отвечено', + 'Application' => 'Приложение', + 'ApplicationData' => 'Данные об уÑтройÑтве', + 'ApplicationNotes' => 'ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ', + 'ApplicationRecordVersion' => 'ВерÑÐ¸Ñ Application Record', + 'AppointmentSequence' => 'ПоÑледовательноÑть назначениÑ', + 'ApproximateFocusDistance' => 'Приблизительное раÑÑтоÑние фокуÑировки', + 'ArchivedFileName' => 'Ðазвание заархивированного файла', + 'Artist' => 'ИÑполнитель', + 'ArtistLen' => 'ИÑполнитель Len', + 'AsShotICCProfile' => 'ICC-профиль при Ñъёмке', + 'AsShotNeutral' => 'Ðейтральный цвет при Ñъёмке', + 'AsShotPreProfileMatrix' => 'Предварительный профиль матрицы при Ñъёмке', + 'AsShotProfileName' => 'Ðазвание Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¿Ñ€Ð¸ Ñъёмке', + 'AsShotWhiteXY' => 'XY белого при Ñъёмке', + 'Ascender' => 'ÐадÑтрочный интервал', + 'Ascent' => 'ÐадÑтрочный интервал', + 'AspectRatio' => 'Соотношение Ñторон', + 'AssetID' => 'ID актива', + 'Association' => 'ÐÑÑоциациÑ', + 'AssumedDisplaySize' => 'Размер отображаемого ÑтереоизображениÑ', + 'AssumedDistanceView' => 'РаÑÑтоÑние от позиции проÑмотра до диÑплеÑ', + 'Attachment' => 'Вложение', + 'Attendee' => 'УчаÑтник', + 'Attitude' => 'Положение', + 'AttitudeTarget' => 'Целевое положение', + 'Audiences' => 'ÐудиториÑ', + 'AudioBitrate' => 'Битрейт аудио', + 'AudioBitsPerSample' => 'Ð‘Ð¸Ñ‚Ð¾Ð²Ð°Ñ Ð³Ð»ÑƒÐ±Ð¸Ð½Ð° аудио', + 'AudioBytes' => 'Ðудио байтов', + 'AudioChannels' => 'КоличеÑтво аудиоканалов', + 'AudioCodec' => 'Кодек аудио', + 'AudioCodecID' => 'ID аудиокодека', + 'AudioData' => 'Ðудиоданные', + 'AudioDuration' => 'ПродолжительноÑть аудио', + 'AudioEncoding' => { + Description => 'Кодирование звука', + PrintConv => { + 'Device-specific sound' => 'ЗавиÑÑщий от уÑтройÑтва звук', + 'G.711 A-law logarithmic PCM' => 'G.711 A-law логарифмичеÑкий PCM', + 'G.711 mu-law logarithmic PCM' => 'G.711 mu-law логарифмичеÑкий PCM', + 'Nellymoser 16kHz Mono' => 'Nellymoser 16 кГц, моно', + 'Nellymoser 8kHz Mono' => 'Nellymoser 8kHz, моно', + 'PCM-BE (uncompressed)' => 'PCM-BE (без ÑжатиÑ)', + 'PCM-LE (uncompressed)' => 'PCM-LE (без ÑжатиÑ)', + }, + }, + 'AudioFileSize' => 'Размер аудио файла', + 'AudioFormat' => 'Формат аудио', + 'AudioFrameSize' => 'Размер аудио фрейма', + 'AudioLayer' => 'Уровень аудио', + 'AudioMimeType' => 'MIME-тип аудио', + 'AudioMode' => 'Режим аудио', + 'AudioOutcue' => 'Заключительные Ñлова аудио', + 'AudioSampleRate' => 'ЧаÑтота диÑкретизации звука', + 'AudioSampleSize' => 'КоличеÑтво бит на канал', + 'AudioSamplingRate' => 'ЧаÑтота диÑкретизации', + 'AudioSamplingResolution' => 'РазрÑдноÑть аудио', + 'AudioSetting' => 'ÐаÑтройка звука', + 'AudioSize' => 'Размер аудиофайла', + 'AudioType' => { + Description => 'Тип аудио', + PrintConv => { + 'Mono Actuality' => 'Событие – Моно', + 'Mono Music' => 'Музыка – Моно', + 'Mono Question and Answer Session' => 'ВопроÑÑ‹ и ответы – Моно', + 'Mono Raw Sound' => 'ÐÐµÐ¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚Ð°Ð½Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ – Моно', + 'Mono Response to a Question' => 'Ответ на Ð²Ð¾Ð¿Ñ€Ð¾Ñ â€“ Моно', + 'Mono Scener' => 'ОбÑтоÑтельÑтва ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ â€“ Моно', + 'Mono Voicer' => 'ГолоÑÐ¾Ð²Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ – Моно', + 'Mono Wrap' => 'Итог ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ â€“ Моно', + 'Stereo Actuality' => 'Событие – Стерео', + 'Stereo Music' => 'Музыка – Стерео', + 'Stereo Question and Answer Session' => 'ВопроÑÑ‹ и ответы – Стерео', + 'Stereo Raw Sound' => 'ÐÐµÐ¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚Ð°Ð½Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ – Стерео', + 'Stereo Response to a Question' => 'Ответ на Ð²Ð¾Ð¿Ñ€Ð¾Ñ â€“ Стерео', + 'Stereo Scener' => 'ОбÑтоÑтельÑтва ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ â€“ Стерео', + 'Stereo Voicer' => 'ГолоÑÐ¾Ð²Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ – Стерео', + 'Stereo Wrap' => 'Итог ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ â€“ Стерео', + 'Text Only' => 'Только текÑÑ‚', + }, + }, + 'AuthenticationTime' => 'Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ – Ð’Ñ€ÐµÐ¼Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ð¸', + 'AuthenticationType' => 'Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ – Тип аутентификации', + 'Author' => 'Ðвтор', + 'AuthorLen' => 'Ðвтор Len', + 'AuthorsPosition' => 'ДолжноÑть автора', + 'AutoFocus' => { + Description => 'ÐвтофокуÑ', + PrintConv => { + 'Off' => 'Ðе включён', + 'On' => 'Включен', + }, + }, + 'AutoISOMax' => 'МакÑимально значение автоматичеÑкого ISO', + 'AutoISOMin' => 'Минимальное значение автоматичеÑкого ISO', + 'AutoLowLightDuration' => 'ÐвтоÑъёмка при низком оÑвещении – ПродолжительноÑть', + 'AutoRotation' => { + Description => 'ÐвтоматичеÑкий поворот', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑки', + 'Down' => 'Вниз', + 'Up' => 'Вверх', + }, + }, + 'AutoSaveFilePath' => 'Путь к файлу автоÑохранениÑ', + 'AutoSaveFormat' => 'Формат автоÑохранениÑ', + 'AvgBitrate' => 'Средний битрейт', + 'AvgPacketSize' => 'Средний размер пакета', + 'AvgWidth' => 'СреднÑÑ ÑˆÐ¸Ñ€Ð¸Ð½Ð° глифа', + 'AxisDistanceX' => 'ДиÑÑ‚Ð°Ð½Ñ†Ð¸Ñ Ð¿Ð¾ оÑи X', + 'AxisDistanceY' => 'ДиÑÑ‚Ð°Ð½Ñ†Ð¸Ñ Ð¿Ð¾ оÑи Y', + 'AxisDistanceZ' => 'ДиÑÑ‚Ð°Ð½Ñ†Ð¸Ñ Ð¿Ð¾ оÑи Z', + 'BMPVersion' => { + Description => 'ВерÑÐ¸Ñ BMP', + PrintConv => { + 'AVI BMP structure?' => 'Структура AVI BMP?', + }, + }, + 'BWFilter' => 'Чёрно-Белый фильтр', + 'BW_HalftoningInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ тонировании – Чёрно-Белое', + 'BW_TransferFunc' => 'Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¿ÐµÑ€ÐµÐ½Ð¾Ñа – Чёрно-Белое', + 'Background' => 'Фон', + 'BackgroundColor' => 'Цвет фона', + 'BackgroundColorIndicator' => { + Description => 'Цвет фона', + PrintConv => { + 'Specified Background Color' => 'Указан', + 'Unspecified Background Color' => 'Ðе указан', + }, + }, + 'BackgroundColorValue' => 'Значение фонового цвета', + 'BackupTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ', + 'BadFaxLines' => 'КоличеÑтво битых Ñтрок', + 'BaseViewpointNum' => 'Ðомер базовой точки проÑмотра', + 'BaselineExposure' => 'Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ ÑкÑпозициÑ', + 'BaselineExposureOffset' => 'Сдвиг базовой ÑкÑпозиции', + 'BaselineLength' => 'Длина базовой линии', + 'BaselineNoise' => 'Базовый уровень шума', + 'BaselineSharpness' => 'Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ Ñ‡Ñ‘Ñ‚ÐºÐ¾Ñть', + 'BatteryCapacity' => 'ÐмкоÑть аккумулÑтора', + 'BatteryCurrent' => 'Ток аккумулÑтора', + 'BatteryLevel' => 'Уровень зарÑда аккумулÑтора', + 'BatteryTemperature' => 'Температура аккумулÑтора', + 'BatteryTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ от аккумулÑтора', + 'BatteryType' => 'Тип аккумулÑтора', + 'BatteryVoltage' => 'ÐапрÑжение аккумулÑтора', + 'BatteryVoltage1' => 'ÐапрÑжение аккумулÑтора 1', + 'BatteryVoltage2' => 'ÐапрÑжение аккумулÑтора 2', + 'BatteryVoltage3' => 'ÐапрÑжение аккумулÑтора 3', + 'BatteryVoltage4' => 'ÐапрÑжение аккумулÑтора 4', + 'BatteryVoltageAvg' => 'ÐапрÑжение аккумулÑтора (Ñреднее)', + 'BayerGreenSplit' => 'Разделение зелёных каналов в матрице Байера', + 'BayerPattern' => 'Фильтр Байера', + 'BestQualityScale' => 'Оптимальный маÑштаб', + 'BibligraphicFileName' => 'Файл Ñ Ð±Ð¸Ð±Ð»Ð¸Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ‡ÐµÑкой информацией', + 'BinaryFilter' => 'Бинарный фильтр', + 'Birthday' => 'День рождениÑ', + 'BitDepth' => { + Description => 'Ð‘Ð¸Ñ‚Ð¾Ð²Ð°Ñ Ð³Ð»ÑƒÐ±Ð¸Ð½Ð°', + PrintConv => { + 'Custom' => 'Ð—Ð°Ð´Ð°Ð½Ð½Ð°Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÐµÐ¼', + }, + }, + 'Bitrate' => 'Битрейт', + 'BitsPerComponent' => 'КоличеÑтво бит на компонент', + 'BitsPerExtendedRunLength' => 'КоличеÑтво бит на раÑширенную длину Ñерий', + 'BitsPerPixel' => 'КоличеÑтво бит на пикÑель', + 'BitsPerRunLength' => 'КоличеÑтво бит на длину Ñерий', + 'BitsPerSample' => 'КоличеÑтво бит на компонент', + 'BlackLevel' => 'Уровень чёрного', + 'BlackLevelBlue' => 'Уровень чёрного – Синий', + 'BlackLevelData' => 'Данные ÑƒÑ€Ð¾Ð²Ð½Ñ Ñ‡ÐµÑ€Ð½Ð¾Ð³Ð¾', + 'BlackLevelDeltaH' => 'Уровень чёрного – Дельта H', + 'BlackLevelDeltaV' => 'Уровень чёрного – Дельта V', + 'BlackLevelGreen' => 'Уровень чёрного – Зелёный', + 'BlackLevelRed' => 'Уровень чёрного – КраÑный', + 'BlackLevelRepeatDim' => 'Уровень чёрного – Размер шаблона повторений', + 'BlacksAdj' => 'Регулировка ÑƒÑ€Ð¾Ð²Ð½Ñ Ñ‡Ñ‘Ñ€Ð½Ð¾Ð³Ð¾', + 'BlockSizeMax' => 'МакÑимальный размер блока', + 'BlockSizeMin' => 'Минимальный размер блока', + 'BlocksPerFrame' => 'Блоков на кадр', + 'BlueBalance' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ñинего', + 'BlueEndpoint' => 'ÐšÐ¾Ð½ÐµÑ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° Синего', + 'BlueMask' => 'МаÑка Синего', + 'BluePrimary' => 'ОÑновной Ñиний', + 'BlueX' => 'Синий по X', + 'BlueY' => 'Синий по Y', + 'BookName' => 'Ðазвание книги', + 'BookTitle' => 'Ðазвание книги', + 'BookType' => 'Тип книги', + 'BookVersion' => 'ВерÑÐ¸Ñ ÐºÐ½Ð¸Ð³Ð¸', + 'BootIdentifier' => 'Идентификатор загрузки', + 'BootLoaderVersion' => 'ВерÑÐ¸Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·Ñ‡Ð¸ÐºÐ°', + 'BootSystem' => 'СиÑтема загрузки', + 'BorderColor' => 'Цвет границы', + 'BorderInformation' => 'Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ границе', + 'BoundingBox' => 'Габаритный прÑмоугольник', + 'Brain' => 'Модуль Brain', + 'BrandingImageID' => 'Бренд – ID изображениÑ', + 'BrandingName' => 'Бренд – Ðазвание', + 'BreakChar' => 'Символ определÑющий границы Ñлов', + 'Brightness' => 'ЯркоÑть', + 'BrightnessAdj' => 'Регулировка ÑркоÑти', + 'BrightnessValue' => 'Значение ÑркоÑти', + 'BurstID' => 'ID Ñерии Ñнимков', + 'BurstPrimary' => 'Первый кадр Ñерии', + 'BurstUUID' => 'Уникальный ID Ð´Ð»Ñ Ñерии Ñнимков', + 'BusyStatus' => 'СоÑтоÑние занÑтоÑти', + 'By-line' => 'Ðвтор', + 'By-lineTitle' => 'Титул автора', + 'ByteLength' => 'Длина в байтах', + 'ByteOrder' => { + Description => 'ПорÑдок байтов', + PrintConv => { + 'Big-endian' => 'ПорÑдок от Ñтаршего к младшему', + 'Little-endian' => 'ПорÑдок от младшего к Ñтаршему', + }, + }, + 'BytesPerLine' => 'КоличеÑтво байт на Ñтроку', + 'BytesPerMinute' => 'Байт в минуту', + 'CDDBDiscPlayTime' => 'CDDB – Ð’Ñ€ÐµÐ¼Ñ Ð²Ð¾ÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð´Ð¸Ñка', + 'CDDBDiscTracks' => 'CDDB – Треки на диÑке', + 'CDEType' => 'Тип CDE', + 'CFALayout' => { + Description => 'Макет CFA', + PrintConv => { + 'Even columns offset down 1/2 row' => 'Чётные Ñтолбцы Ñмещены вниз на 1/2 Ñтроки', + 'Even columns offset up 1/2 row' => 'Чётные Ñтолбцы Ñмещены вверх 1/2 Ñтроки', + 'Even rows offset down by 1/2 row, even columns offset left by 1/2 column' => 'Чётные Ñтроки Ñмещены вниз на 1/2 Ñтроки, чётные Ñтолбцы Ñмещены влево на 1/2 Ñтолбца', + 'Even rows offset down by 1/2 row, even columns offset right by 1/2 column' => 'Чётные Ñтроки Ñмещены вниз на 1/2 Ñтроки, чётные Ñтолбцы Ñмещены вправо на 1/2 Ñтолбца', + 'Even rows offset left 1/2 column' => 'Чётные Ñтроки Ñмещены влево на 1/2 Ñтолбца', + 'Even rows offset right 1/2 column' => 'Чётные Ñтроки Ñмещены вправо на 1/2 Ñтолбца', + 'Even rows offset up by 1/2 row, even columns offset left by 1/2 column' => 'Чётные Ñтроки Ñмещены вверх на 1/2 Ñтроки, чётные Ñтолбцы Ñмещены влево на 1/2 Ñтолбца', + 'Even rows offset up by 1/2 row, even columns offset right by 1/2 column' => 'Чётные Ñтроки Ñмещены вверх на 1/2 Ñтроки, чётные Ñтолбцы Ñмещены вправо на 1/2 Ñтолбца', + 'Rectangular' => 'ПрÑÐ¼Ð¾ÑƒÐ³Ð¾Ð»ÑŒÐ½Ð°Ñ ÐºÐ¾Ð¼Ð¿Ð¾Ð½Ð¾Ð²ÐºÐ°', + }, + }, + 'CFAPattern' => { + Description => 'Шаблон CFA', + PrintConv => { + '[Blue,Green][Green,Red]' => '[Синий, Зелёный][Зелёный, КраÑный]', + '[Green,Blue][Red,Green]' => '[Зелёный, Синий][КраÑный, Зелёный]', + '[Green,Red][Blue,Green]' => '[Зелёный, КраÑный][Синий, Зелёный]', + '[Red,Green][Green,Blue]' => '[КраÑный, Зелёный][Зелёный, Синий]', + 'n/a' => 'Ðет данных', + }, + }, + 'CFAPattern2' => 'Шаблон CFA 2', + 'CFAPlaneColor' => 'Цветовые плоÑкоÑти CFA', + 'CFARepeatPatternDim' => 'Шаблон Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÐµÐ½Ð¸Ñ CFA', + 'CIP3DataFile' => 'CIP3 – Файл данных', + 'CIP3Sheet' => 'CIP3 – ЛиÑÑ‚', + 'CIP3Side' => 'CIP3 – Сторона', + 'CMYKEquivalent' => 'Эквивалент CMYK', + 'CPUVersions' => 'ВерÑии CPU', + 'CR2CFAPattern' => { + Description => 'CR2 – Шаблон CFA', + PrintConv => { + '[Blue,Green][Green,Red]' => '[Синий, Зеленый][Зеленый, КраÑный]', + '[Green,Blue][Red,Green]' => '[Зеленый, Синий][КраÑный, Зеленый]', + '[Green,Red][Blue,Green]' => '[Зеленый, КраÑный][Синий, Зеленый]', + '[Red,Green][Green,Blue]' => '[КраÑный, Зеленый][Зеленый, Синий]', + }, + }, + 'CacheControl' => 'Контроль кешированиÑ', + 'CacheVersion' => 'ВерÑÐ¸Ñ ÐºÑша', + 'CalendarColor' => 'Цвет календарÑ', + 'CalendarDescription' => 'ОпиÑание календарÑ', + 'CalendarID' => 'ID календарÑ', + 'CalendarName' => 'Ðазвание календарÑ', + 'CalendarScale' => 'ÐšÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ð½Ð°Ñ ÑиÑтема', + 'CalibratedFocalLength' => 'Откалирброванное фокуÑное раÑÑтоÑние', + 'CalibratedOpticalCenterX' => 'Откалирброванный оптичеÑкий центр по X', + 'CalibratedOpticalCenterY' => 'Откалирброванный оптичеÑкий центр по Y', + 'CalibrationIlluminant1' => { + Description => 'Калибровочное оÑвещение 1', + PrintConv => { + 'Cloudy' => 'Облачно (6500 К)', + 'Cool White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Холодный Ñвет (4150 К)', + 'D50' => 'D50 (5000 К)', + 'D55' => 'D55 (5500 К)', + 'D65' => 'D65 (6500 К)', + 'D75' => 'D75 (7500 К)', + 'Day White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Дневной белый (5050 К)', + 'Daylight' => 'Дневной Ñвет (5500 К)', + 'Daylight Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° дневного Ñвета (6400 К)', + 'Fine Weather' => 'ЯÑÐ½Ð°Ñ Ð¿Ð¾Ð³Ð¾Ð´Ð° (5500 К)', + 'Flash' => 'Ð’Ñпышка (5500 К)', + 'Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° (4150 К)', + 'ISO Studio Tungsten' => 'Ð¡Ñ‚ÑƒÐ´Ð¸Ð¹Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° Ð½Ð°ÐºÐ°Ð»Ð¸Ð²Ð°Ð½Ð¸Ñ (3200 К)', + 'Other' => 'Другой иÑточник Ñвета', + 'Shade' => 'Тень (7500 К)', + 'Standard Light A' => 'Стандартное оÑвещение A (2850 К)', + 'Standard Light B' => 'Стандартное оÑвещение B (5500 К)', + 'Standard Light C' => 'Стандартное оÑвещение C (6500 К)', + 'Tungsten (Incandescent)' => 'Лампа Ð½Ð°ÐºÐ°Ð»Ð¸Ð²Ð°Ð½Ð¸Ñ (2850 К)', + 'Unknown' => 'ÐеизвеÑтно', + 'Warm White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Тёплый Ñвет (2925 К)', + 'White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Белый Ñвет (3525 К)', + }, + }, + 'CalibrationIlluminant2' => { + Description => 'Калибровочное оÑвещение 2', + PrintConv => { + 'Cloudy' => 'Облачно (6500 К)', + 'Cool White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Холодный Ñвет (4150 К)', + 'D50' => 'D50 (5000 К)', + 'D55' => 'D55 (5500 К)', + 'D65' => 'D65 (6500 К)', + 'D75' => 'D75 (7500 К)', + 'Day White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Дневной белый (5050 К)', + 'Daylight' => 'Дневной Ñвет (5500 К)', + 'Daylight Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° дневного Ñвета (6400 К)', + 'Fine Weather' => 'ЯÑÐ½Ð°Ñ Ð¿Ð¾Ð³Ð¾Ð´Ð° (5500 К)', + 'Flash' => 'Ð’Ñпышка (5500 К)', + 'Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° (4150 К)', + 'ISO Studio Tungsten' => 'Ð¡Ñ‚ÑƒÐ´Ð¸Ð¹Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° Ð½Ð°ÐºÐ°Ð»Ð¸Ð²Ð°Ð½Ð¸Ñ (3200 К)', + 'Other' => 'Другой иÑточник Ñвета', + 'Shade' => 'Тень (7500 К)', + 'Standard Light A' => 'Стандартное оÑвещение A (2850 К)', + 'Standard Light B' => 'Стандартное оÑвещение B (5500 К)', + 'Standard Light C' => 'Стандартное оÑвещение C (6500 К)', + 'Tungsten (Incandescent)' => 'Лампа Ð½Ð°ÐºÐ°Ð»Ð¸Ð²Ð°Ð½Ð¸Ñ (2850 К)', + 'Unknown' => 'ÐеизвеÑтное', + 'Warm White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Тёплый Ñвет (2925 К)', + 'White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Белый Ñвет (3525 К)', + }, + }, + 'CallForImage' => 'Звонок по изображению', + 'CamReverse' => 'Камера заднего вида', + 'CameraArrangementInterval' => 'Интервал раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ ÐºÐ°Ð¼ÐµÑ€Ñ‹', + 'CameraCalibration1' => 'ÐšÐ°Ð»Ð¸Ð±Ñ€Ð¾Ð²Ð¾Ñ‡Ð½Ð°Ñ Ð¼Ð°Ñ‚Ñ€Ð¸Ñ†Ð° фотокамеры 1', + 'CameraCalibration2' => 'ÐšÐ°Ð»Ð¸Ð±Ñ€Ð¾Ð²Ð¾Ñ‡Ð½Ð°Ñ Ð¼Ð°Ñ‚Ñ€Ð¸Ñ†Ð° фотокамеры 2', + 'CameraCalibrationSig' => 'Сигнатура калибровки камеры', + 'CameraDateTime' => 'Дата и Ð’Ñ€ÐµÐ¼Ñ ÐºÐ°Ð¼ÐµÑ€Ñ‹', + 'CameraElevationAngle' => 'Угол обзора камеры', + 'CameraFilename' => 'Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° Ñозданное камерой', + 'CameraID' => 'ID камеры', + 'CameraLabel' => 'Ðазвание камеры', + 'CameraMakeModel' => 'Модель камеры', + 'CameraModel' => 'Модель камеры', + 'CameraModelID' => 'ID модели камеры', + 'CameraOperator' => 'Кинооператор', + 'CameraOrientation' => { + Description => 'ÐžÑ€Ð¸ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ ÐºÐ°Ð¼ÐµÑ€Ñ‹', + PrintConv => { + 'Horizontal (normal)' => 'ГоризонтальнаÑ', + 'Rotate 180' => 'Поверот на 180°', + 'Rotate 270 CW' => 'Поворот на 270° по чаÑовой Ñтрелке', + 'Rotate 90 CW' => 'Поворот на 90° по чаÑовой Ñтрелке', + }, + }, + 'CameraPitch' => 'Тангаж камеры (Ðаклон)', + 'CameraRoll' => 'Крен камеры (Вращение)', + 'CameraSerialNumber' => 'Серийный номер камеры', + 'CameraTemperature' => 'Температура камеры', + 'CameraType' => 'Тип камеры', + 'CameraYaw' => 'РыÑкание камеры (Поворот)', + 'CanSeekOnTime' => 'ВозможноÑть навигации по времени', + 'CanSeekToEnd' => 'ВозможноÑть перейти к концу', + 'CapHeight' => 'Ð’Ñ‹Ñота пропиÑных букв', + 'Caption' => 'Заголовок', + 'Caption-Abstract' => 'Подробное опиÑание', + 'CaptionWriter' => 'Ðвтор подпиÑи', + 'CaptureSoftware' => 'ЗапиÑÑŒ изображений выполнен в', + 'CaptureXResolution' => 'Разрешение захвата по X', + 'CaptureXResolutionUnit' => { + Description => 'Единицы Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð·Ð°Ñ…Ð²Ð°Ñ‚Ð° по X', + PrintConv => { + '0.01 mm' => '0.01 мм', + '0.1 mm' => '0.1 мм', + '10 cm' => '10 Ñм', + '10 m' => '10 м', + '100 m' => '100 м', + 'cm' => 'Ñм', + 'km' => 'км', + 'm' => 'м', + 'mm' => 'мм', + 'um' => 'мкм', + }, + }, + 'CaptureYResolution' => 'Разрешение захвата по Y', + 'CaptureYResolutionUnit' => { + Description => 'Единицы Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð·Ð°Ñ…Ð²Ð°Ñ‚Ð° по Y', + PrintConv => { + '0.01 mm' => '0.01 мм', + '0.1 mm' => '0.1 мм', + '10 cm' => '10 Ñм', + '10 m' => '10 м', + '100 m' => '100 м', + 'cm' => 'Ñм', + 'km' => 'км', + 'm' => 'м', + 'mm' => 'мм', + 'um' => 'мкм', + }, + }, + 'CatalogSets' => 'Ðаборы каталога', + 'Categories' => 'Категории', + 'Category' => 'Группа', + 'CellLength' => 'Строк в матрице дизеринга или полутонированиÑ', + 'CellWidth' => 'Колонок в матрице дизеринга или полутонированиÑ', + 'ChannelMode' => { + Description => 'Режим канала', + PrintConv => { + 'Dual Channel' => 'Двухканальное', + 'Joint Stereo' => 'Объединённое Ñтерео', + 'Single Channel' => 'Моно', + 'Stereo' => 'Стерео', + }, + }, + 'ChannelUsage' => 'ИÑпользование канала', + 'Channels' => 'Каналов', + 'Chapter' => 'ЧаÑть', + 'ChapterCount' => 'КоличеÑтво глав', + 'ChapterName' => 'Ðазвание главы', + 'ChapterNumber' => 'Ðомер главы', + 'CharacterSet' => 'Ðабор Ñимволов', + 'Characters' => 'КоличеÑтво Ñимволов', + 'CharactersWithSpaces' => 'КоличеÑтво Ñимволов, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð¿Ñ€Ð¾Ð±ÐµÐ»Ñ‹', + 'ChromaBlurRadius' => 'Ð Ð°Ð´Ð¸ÑƒÑ ÑÐ³Ð»Ð°Ð¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ñ†Ð²ÐµÑ‚Ð½Ð¾Ñти', + 'ChromaticAberrationCorrParams' => 'Параметры коррекции хроматичеÑкой аберрации', + 'ChromaticAberrationCorrection' => { + Description => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ñ…Ñ€Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкой аберрации', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкаÑ', + 'No correction params available' => 'ÐедоÑтупна', + 'Off' => 'Ðе включена', + 'On' => 'Включена', + }, + }, + 'Chromaticities' => 'ОÑновные цвета и Точка белого', + 'CircleOfConfusion' => 'Круг нерезкоÑти', + 'City' => 'Город проиÑÑ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…', + 'Class' => 'КлаÑÑ', + 'Classification' => 'КлаÑÑификации', + 'ClassifyState' => 'КлаÑÐ¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñтруктуры', + 'CleanFaxData' => { + Description => 'Ðаличие битых Ñтрок', + PrintConv => { + 'Clean' => 'Без битых Ñтрок', + 'Regenerated' => 'Битые Ñтроки воÑÑтановлены', + 'Unclean' => 'Битые Ñтроки не воÑÑтановлены', + }, + }, + 'ClipPath' => 'Обтравочный контур', + 'ClippingLimit' => 'ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð½Ð° копирование', + 'ClippingPathName' => 'Ðазвание обтравочного контура', + 'CodePage' => { + Description => 'ÐšÐ¾Ð´Ð¾Ð²Ð°Ñ Ñтраница', + PrintConv => { + 'Windows Latin 1 (Western European)' => 'Windows Latin 1 (ЗападноевропейÑкаÑ)', + }, + }, + 'CodecFlavorID' => 'ID кодека Flavor', + 'CodedCharacterSet' => 'Кодовый набор Ñимволов', + 'CodedFrameSize' => 'Размер кодированного кадра', + 'CodestreamRegistration' => 'РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ ÐºÐ¾Ð´Ð¾Ð²Ð¾Ð³Ð¾ потока', + 'CodingMethods' => { + Description => 'Метод ÑжатиÑ', + PrintConv => { + 'Baseline JPEG' => 'Базовое JPEG', + 'JBIG color' => 'Цветное JBIG', + 'Modified Huffman' => 'Одномерное – ÐœÐ¾Ð´Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð¥Ð°Ñ„Ñ„Ð¼Ð°Ð½Ð°', + 'Modified MR' => 'Двумерное – Modified Modified READ', + 'Modified Read' => 'Двумерное – Modified READ', + 'Unspecified compression' => 'ÐеизвеÑтное Ñжатие', + }, + }, + 'Collection' => 'КоллекциÑ', + 'CollectionName' => 'Ðазвание коллекции', + 'CollectionURI' => 'URI коллекции', + 'Collections' => 'Коллекции', + 'ColorAdjustmentMode' => { + Description => 'Режим регулировки цвета', + PrintConv => { + 'Off' => 'Ðе включён', + 'On' => 'Включен', + }, + }, + 'ColorCalibrationMatrix' => 'Матрица калибровки цвета', + 'ColorCharacterization' => 'Спецификации цвета', + 'ColorClass' => { + Description => 'Цветовой клаÑÑ', + PrintConv => { + '0 (None)' => '0 (Ðе указан)', + '1 (Winner)' => '1 (Лучшее)', + '2 (Winner alt)' => '2 (Лучшее Alt)', + '3 (Superior)' => '3 (Хорошее)', + '4 (Superior alt)' => '4 (Хорошее Alt)', + '5 (Typical)' => '5 (Обычноеl)', + '6 (Typical alt)' => '6 (Обычное Alt)', + '7 (Extras)' => '7 (ЭкÑтра)', + '8 (Trash)' => '8 (Хлам)', + }, + }, + 'ColorComponents' => 'Цветовых компонентов', + 'ColorCorrection' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ñ†Ð²ÐµÑ‚Ð°', + 'ColorEffect' => { + PrintConv => { + 'Sepia' => 'СепиÑ', + }, + }, + 'ColorFilter' => 'Цветовой фильтр', + 'ColorGroup' => 'Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð°', + 'ColorHalftoningInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ тонировании – Цветное', + 'ColorMap' => 'Карта цветов', + 'ColorMatrix1' => 'Матрица Ð¿Ñ€ÐµÐ¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ†Ð²ÐµÑ‚Ð¾Ð² 1', + 'ColorMatrix2' => 'Матрица Ð¿Ñ€ÐµÐ¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ†Ð²ÐµÑ‚Ð¾Ð² 2', + 'ColorMode' => { + Description => 'Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ð¼Ð¾Ð´ÐµÐ»ÑŒ', + PrintConv => { + 'Autumn Leaves' => 'ОÑенние лиÑтьÑ', + 'B&W' => 'Чёрно-белое', + 'Bitmap' => 'МонохромнаÑ', + 'Black & White' => 'Чёрно-белый', + 'Clear' => 'Прозрачный', + 'Color Palette' => 'Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ð¿Ð°Ð»Ð¸Ñ‚Ñ€Ð°', + 'Deep' => 'Глубокий', + 'Duotone' => 'ДвуцветнаÑ', + 'Embed Adobe RGB' => 'Ð’Ñтроенный Adobe RGB', + 'Evening' => 'Вечер', + 'Grayscale' => 'Ð’ градациÑÑ… Ñерого', + 'Indexed' => 'ИндекÑированные цвета', + 'Indexed Color' => 'ИндекÑированный цвет', + 'Landscape' => 'Пейзаж', + 'Light' => 'Бледный', + 'Multichannel' => 'МногоканальнаÑ', + 'Natural' => 'Ðатуральный', + 'Natural color' => 'Ðатуральный цвет', + 'Natural sRGB' => 'Ðатуральный sRGB', + 'Natural+ sRGB' => 'Ðатуральный+ sRGB', + 'Neutral' => 'Ðейтральный', + 'Night Portrait' => 'Ðочной портрет', + 'Night Scene' => 'ÐÐ¾Ñ‡Ð½Ð°Ñ Ñцена', + 'Night View' => 'Ðочной вид', + 'Night View/Portrait' => 'Ðочной вид/портрет', + 'Portrait' => 'Портрет', + 'RGB Color' => 'Цвет RGB', + 'Sepia' => 'СепиÑ', + 'Solarization' => 'СолÑризациÑ', + 'Standard' => 'Стандартный', + 'Sunset' => 'Закат', + 'Vivid' => 'Яркий цвет', + 'Vivid color' => 'Яркий цвет', + 'n/a' => 'Ðет данных', + }, + }, + 'ColorNoiseReduction' => 'Щумодав – Уменьшение цветных шумов', + 'ColorPalette' => 'Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ð¿Ð°Ð»Ð¸Ñ‚Ñ€Ð°', + 'ColorPlanes' => 'Цветовые плоÑкоÑти', + 'ColorPrimaries' => 'Праймериз цвета', + 'ColorRepresentation' => { + Description => 'Цветовое предÑтавление', + PrintConv => { + '3 Components, Frame Sequential in Multiple Objects' => '3 компонента. ПоÑледовательноÑть кадров в неÑкольких объектах', + '3 Components, Frame Sequential in One Object' => '3 компонента. ПоÑледовательноÑть кадров в одном объекте', + '3 Components, Line Sequential' => '3 компонента. ПоÑÐ»ÐµÐ´Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñтрока', + '3 Components, Pixel Sequential' => '3 компонента. ПоÑледовательноÑть пикÑелей', + '3 Components, Single Frame' => '3 компонента. Один кадр', + '3 Components, Special Interleaving' => '3 компонента. Специальное чередование', + '4 Components, Frame Sequential in Multiple Objects' => '4 компонента. ПоÑледовательноÑть кадров в неÑкольких объектах', + '4 Components, Frame Sequential in One Object' => '4 компонента. ПоÑледовательноÑть кадров в одном объекте', + '4 Components, Line Sequential' => '4 компонента. ПоÑÐ»ÐµÐ´Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñтрока', + '4 Components, Pixel Sequential' => '4 компонента. ПоÑледовательноÑть пикÑелей', + '4 Components, Single Frame' => '4 компонента. Один кадр', + '4 Components, Special Interleaving' => '4 компонента. Специальное чередование', + 'Monochrome, Single Frame' => 'Монохромное. Один кадр', + 'No Image, Single Frame' => 'Ðет изображениÑ. Один кадр', + }, + }, + 'ColorResolutionDepth' => 'Глубина цвета', + 'ColorResponseUnit' => 'Единицы цветовой чувÑтвительноÑти', + 'ColorSamplersResource' => 'РеÑÑƒÑ€Ñ Ð±Ñ€Ð°Ð·Ñ†Ð¾Ð² цвета', + 'ColorSamplersResource2' => 'РеÑÑƒÑ€Ñ Ð±Ñ€Ð°Ð·Ñ†Ð¾Ð² цвета 2', + 'ColorSequence' => 'ПоÑледовательноÑть цветов', + 'ColorSpace' => { + Description => 'Цветовое проÑтранÑтво', + PrintConv => { + 'BT 2020 Constant Luminance' => 'ПоÑтоÑÐ½Ð½Ð°Ñ ÑркоÑть BT 2020', + 'Calibrated RGB' => 'Калиброванный RGB', + 'Device CMYK' => 'УÑтройÑтво CMYK', + 'Device RGB' => 'УÑтройÑтво RGB', + 'Embedded Color Profile' => 'Ð’Ñтроенный цветовой профиль', + 'Grayscale' => 'Оттенки Ñерого', + 'ICC Profile' => 'ICC-профиль', + 'Linked Color Profile' => 'СвÑзанный цветовой профиль', + 'Uncalibrated' => 'Ðекалиброванное', + 'Undefined' => 'Ðе определено', + 'Windows Color Space' => 'Цветовое проÑтранÑтво Windows', + }, + }, + 'ColorSpecApproximation' => { + Description => 'Ð¡Ð¿ÐµÑ†Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ†Ð²ÐµÑ‚Ð° – ÐппрокÑимациÑ', + PrintConv => { + 'Accurate' => 'ТочнаÑ', + 'Exceptional Quality' => 'Лучшее качеÑтво', + 'Not Specified' => 'Ðе указана', + 'Poor Quality' => 'Ðизкое качеÑтво', + 'Reasonable Quality' => 'Приемлемое качеÑтво', + }, + }, + 'ColorSpecData' => 'Ð¡Ð¿ÐµÑ†Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ†Ð²ÐµÑ‚Ð° – Данные', + 'ColorSpecMethod' => { + Description => 'Ð¡Ð¿ÐµÑ†Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ†Ð²ÐµÑ‚Ð° – Метод', + PrintConv => { + 'Any ICC' => 'Любой ICC', + 'Enumerated' => 'ПеречиÑлимый', + 'Restricted ICC' => 'Ограниченный ICC', + 'Vendor Color' => 'Цвет поÑтавщика', + }, + }, + 'ColorSpecPrecedence' => 'Ð¡Ð¿ÐµÑ†Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ†Ð²ÐµÑ‚Ð° – Precedence', + 'ColorTable' => 'Таблица цвета', + 'ColorTemperature' => 'Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð°', + 'ColorTemperatureAdj' => 'Регулировка температуры цвета', + 'ColorTemperatures' => 'Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð°', + 'ColorTransferFuncs' => 'Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¿ÐµÑ€ÐµÐ½Ð¾Ñа – Цветное', + 'ColorType' => { + Description => 'Тип цвета', + PrintConv => { + 'Grayscale' => 'Ð“Ñ€Ð°Ð´Ð°Ñ†Ð¸Ñ Ñерого', + 'Grayscale with Alpha' => 'Ð“Ñ€Ð°Ð´Ð°Ñ†Ð¸Ñ Ñерого Ñ Ðльфа-каналом', + 'Palette' => 'Палитра', + 'RGB with Alpha' => 'RGB Ñ Ðльфа-каналом', + }, + }, + 'ColorimetricReference' => 'КолориметричеÑкий Ñталон', + 'Colorimetry' => 'КолориметриÑ', + 'Colors' => 'Цвета', + 'Command' => 'Команда', + 'CommandLineArguments' => 'Ðргументы командной Ñтроки', + 'Comment' => 'Комментарий', + 'CommentLen' => 'Комментарий Len', + 'CommentTime' => 'Ð’Ñ€ÐµÐ¼Ñ ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ', + 'Comments' => 'Комментарии', + 'CommonNetworkRelLink' => 'ÐžÐ±Ñ‰Ð°Ñ Ñеть Rel Link', + 'CommonPathSuffix' => 'Ð¡ÑƒÑ„Ñ„Ð¸ÐºÑ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ пути', + 'Company' => 'ОрганизациÑ', + 'CompatibleBrands' => 'СовмеÑтимые марки', + 'CompatibleFontName' => 'Ðазвание ÑовмеÑтимого шрифта', + 'ComponentDefinition' => 'Определение компонента', + 'ComponentMapping' => 'СопоÑтавление компонентов', + 'ComponentsConfiguration' => { + Description => 'ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñ‚Ð¾Ð²', + PrintConv => { + 'Alpha (matte)' => 'Ðльфа-канал (маÑка)', + 'Alpha, B, G, R' => 'Ðльфа-канал, B, G, R', + 'Blue (B)' => 'Синий (B)', + 'Chrominance (Cb, Cr, subsampled by two)' => 'ЦветноÑть (Cb, Cr, ÑубдиÑÐºÑ€ÐµÑ‚Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° два)', + 'Composite video' => 'Композитное видео', + 'Depth (Z)' => 'Глубина (Z)', + 'Green (G)' => 'Зелёный (G)', + 'Luminance (Y)' => 'ЯркоÑть (Y)', + 'R, G, B, Alpha' => 'R, G, B, Ðльфа-канал', + 'Red (R)' => 'КраÑный (R)', + 'User-defined 2 component element' => '2-компонентный Ñлемент определÑемый пользователем', + 'User-defined 3 component element' => '3-компонентный Ñлемент определÑемый пользователем', + 'User-defined 4 component element' => '4-компонентный Ñлемент определÑемый пользователем', + 'User-defined 5 component element' => '5-компонентный Ñлемент определÑемый пользователем', + 'User-defined 6 component element' => '6-компонентный Ñлемент определÑемый пользователем', + 'User-defined 7 component element' => '7-компонентный Ñлемент определÑемый пользователем', + 'User-defined 8 component element' => '8-компонентный Ñлемент определÑемый пользователем', + 'User-defined single component' => 'Один компонент определÑемый пользователем', + }, + }, + 'Composer' => 'Композитор', + 'CompositeImage' => { + Description => 'СоÑтавное изображение', + PrintConv => { + 'Composite Image Captured While Shooting' => 'Полученное во Ð²Ñ€ÐµÐ¼Ñ Ñъёмки', + 'General Composite Image' => 'Созданное в редакторе', + 'Not a Composite Image' => 'Ðет', + 'Unknown' => 'ГеизвеÑтно', + }, + }, + 'CompositeImageCount' => 'СоÑтавное изображение – КоличеÑтво', + 'CompositeImageExposureTimes' => 'СоÑтавное изображение – Выдержка', + 'Composition' => 'КомпозициÑ', + 'CompositionOptions' => 'Варианты композиции', + 'Compressed' => { + Description => 'Сжатый', + PrintConv => { + 'False' => 'Ðет', + 'True' => 'Да', + }, + }, + 'CompressedBitsPerPixel' => 'Сжатых бит на пикÑель', + 'CompressedSize' => 'Сжатый размер', + 'Compression' => { + Description => 'Метод ÑжатиÑ', + PrintConv => { + '4-Bit RLE' => '4-битное RLE', + '8-Bit RLE' => '8-битное RLE', + 'Bitfields' => 'Битовые полÑ', + 'Fractal' => 'Фрактальное', + 'JPEG' => 'JPEG Ñжатие', + 'JPEG (old-style)' => 'JPEG (Ñтарый Ñтиль)', + 'Modified Huffman' => 'Модифицированное кодирование Хаффмана', + 'Modified Modified READ' => 'Modified Modified READ (MMR)', + 'Modified READ' => 'Modified READ (MR)', + 'None' => 'Без ÑжатиÑ', + 'RLE Encoding' => 'Кодирование RLE', + 'Uncompressed' => 'ÐеÑжатый', + 'ZIP with prediction' => 'ZIP Ñ Ð¿Ñ€ÐµÐ´Ñказанием', + 'ZIP without prediction' => 'ZIP без предÑказаниÑ', + }, + }, + 'CompressionFactor' => 'КоÑффициент ÑжатиÑ', + 'CompressionLevel' => 'Уровень ÑжатиÑ', + 'CompressionType' => 'Тип ÑжатиÑ', + 'CompressorName' => 'Ðазвание компреÑÑора', + 'ConditionalFEC' => 'КомпенÑÐ°Ñ†Ð¸Ñ ÑкÑпозиции при Ñъёмке Ñо вÑпышкой', + 'Confidence' => 'КонфидециальноÑть', + 'ConfidenceLevel' => 'Интервал раÑÐ¿Ð¾Ð·Ð½Ð°Ð²Ð°Ð½Ð¸Ñ Ð»Ð¸Ñ†Ð°', + 'ConfidenceMime' => 'MIME-тип конфидециального изображениÑ', + 'ConfirmedObjectSize' => 'Подтверждённый размер объекта', + 'ConsecutiveBadFaxLines' => 'КоличеÑтво поÑледовательных битых Ñтрок', + 'ConstrainedCropHeight' => 'ÐŸÑ€Ð¾Ð¿Ð¾Ñ€Ñ†Ð¸Ð¾Ð½Ð°Ð»ÑŒÐ½Ð°Ñ Ð¾Ð±Ñ€ÐµÐ·ÐºÐ° – Ð’Ñ‹Ñота', + 'ConstrainedCropWidth' => 'ÐŸÑ€Ð¾Ð¿Ð¾Ñ€Ñ†Ð¸Ð¾Ð½Ð°Ð»ÑŒÐ½Ð°Ñ Ð¾Ð±Ñ€ÐµÐ·ÐºÐ° – Ширина', + 'Contact' => 'Контакт', + 'ContainerVersion' => 'ВерÑÐ¸Ñ ÐºÐ¾Ð½Ñ‚ÐµÐ¹Ð½ÐµÑ€Ð°', + 'ContentDisposition' => 'РаÑпорÑжение Ñодержимым', + 'ContentIdentifier' => 'Идентификатор контента', + 'ContentLanguage' => 'Язык контента', + 'ContentLocationCode' => 'Контент – Код Ñтраны', + 'ContentLocationName' => 'Контент – Страна', + 'ContentProtected' => { + Description => 'Контент защищен', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'ContentProtectedPercent' => 'Контент защищен – Процент', + 'ContentRating' => { + Description => 'ВозраÑтной рейтинг контента', + PrintConv => { + 'Adult Supervision Recommended' => 'Под наблюдением взроÑлых', + 'Adults Only' => 'Только Ð´Ð»Ñ Ð²Ð·Ñ€Ð¾Ñлых', + 'All Ages' => 'Ð”Ð»Ñ Ð»ÑŽÐ±Ð¾Ð³Ð¾ возраÑта', + 'No Rating' => 'ВозраÑÑ‚Ð½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° не указана', + 'Older Children' => 'Старшим детÑм', + 'Older Teens' => 'Старшим подроÑткам', + 'Younger Teens' => 'Младшим подроÑткам', + }, + }, + 'ContentScriptType' => 'Язык Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñценариев', + 'ContentStyleType' => 'Язык таблицы Ñтилей', + 'ContentType' => 'MIME-тип документа', + 'ContiguousCodestream' => 'Смежный кодовый поток', + 'Contrast' => { + Description => 'КонтраÑтноÑть', + PrintConv => { + 'High' => 'Ð’Ñ‹ÑокаÑ', + 'Low' => 'ÐизкаÑ', + 'Normal' => 'СтандартнаÑ', + }, + }, + 'ContrastAdj' => 'Регулировка контраÑта', + 'Contributor' => 'Соавтор', + 'Controller' => 'Контроллер', + 'ConvergenceAngle' => 'Угол ÑхождениÑ', + 'ConvergenceBaseImage' => { + Description => 'СходимоÑть базового изображениÑ', + PrintConv => { + 'Equivalent for Both Viewpoints' => 'Эквивалент Ð´Ð»Ñ Ð¾Ð±ÐµÐ¸Ñ… точек проÑмотра', + 'Left Viewpoint' => 'Ð›ÐµÐ²Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° проÑмотра', + 'Right Viewpoint' => 'ÐŸÑ€Ð°Ð²Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° проÑмотра', + }, + }, + 'ConvergenceDistance' => 'РаÑÑтоÑние ÑходимоÑти', + 'Converter' => 'Конвертер', + 'Copyright' => 'ÐвторÑкое право', + 'CopyrightFileName' => 'Ðазвание файла авторÑкого права', + 'CopyrightFlag' => { + Description => 'Флаг авторÑкого права', + PrintConv => { + 'False' => 'Ðет', + 'True' => 'ЕÑть', + }, + }, + 'CopyrightLen' => 'ÐвторÑкое право Len', + 'CopyrightNotice' => 'ÐвторÑкое право', + 'CopyrightOwner' => 'Правообладатель', + 'CopyrightOwnerID' => 'PLUS-ID правообладателÑ', + 'CopyrightOwnerImageID' => 'ID Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ñвоенный правообладателем', + 'CopyrightOwnerName' => 'Ð˜Ð¼Ñ Ð¿Ñ€Ð°Ð²Ð¾Ð¾Ð±Ð»Ð°Ð´Ð°Ñ‚ÐµÐ»Ñ', + 'CopyrightRegistrationNumber' => 'РегиÑтрационный номер авторÑкого права', + 'CopyrightStatus' => { + Description => 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ', + PrintConv => { + 'Protected' => 'Защищено', + 'Public Domain' => 'Ð’Ñеобщее доÑтоÑние', + 'Unknown' => 'Ðе указано', + }, + }, + 'CountInfo' => 'Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ подщёте', + 'Country' => 'Страна', + 'Country-PrimaryLocationCode' => 'Код Ñтраны', + 'Country-PrimaryLocationName' => 'Страна', + 'CountryCode' => 'Код Ñтраны', + 'CoverArt' => 'Обложка', + 'CoverArtMIMEType' => 'MIME тип обложки', + 'CoverArtType' => 'Тип обложки', + 'Coverage' => 'Охват', + 'CreateDate' => 'Дата оцифровки', + 'CreatedBy' => 'Создан', + 'CreationDate' => 'Дата ÑозданиÑ', + 'CreationTime' => 'Ð’Ñ€ÐµÐ¼Ñ ÑозданиÑ', + 'CreativeStyleWasChanged' => { + Description => 'ТворчеÑкий Ñтиль был изменён', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'Creator' => 'Создатель', + 'CreatorAppID' => { + Description => 'ID Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ñоздавшего файл', + PrintConv => { + 'Unknown' => 'ÐеизвеÑтное', + }, + }, + 'CreatorAppVersion' => 'ВерÑÐ¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ', + 'CreatorBuildNumber' => 'Приложение – Ðомер Ñборки', + 'CreatorBuildNumber2' => 'Приложение – Ðомер Ñборки 2', + 'CreatorMajorVersion' => 'Приложение – ÐœÐ°Ð¶Ð¾Ñ€Ð½Ð°Ñ Ð²ÐµÑ€ÑиÑ', + 'CreatorMinorVersion' => 'Приложение – ÐœÐ¸Ð½Ð¾Ñ€Ð½Ð°Ñ Ð²ÐµÑ€ÑиÑ', + 'CreatorOpenWithUIOptions' => 'Открыт Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð°Ð¼Ð¸ пользовательÑкого интерфейÑа', + 'CreatorSoftware' => 'Создан в приложении', + 'CreatorTool' => 'Создано в', + 'CreatorVersion' => 'ВерÑÐ¸Ñ Ð¸Ñходного приложениÑ', + 'Credit' => 'ПоÑтавщик', + 'CreditLineRequired' => { + Description => 'Политика кредитованиÑ', + PrintConv => { + 'Credit Adjacent To Image' => 'Кредит Ñ€Ñдом Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸ÐµÐ¼', + 'Credit in Credits Area' => 'Кредит в облаÑти кредитов', + 'Credit on Image' => 'Кредит на изображение', + 'Not Required' => 'Кредит не требуетÑÑ', + }, + }, + 'CropArea' => 'ОблаÑть кадрированиÑ', + 'CropBottom' => 'Обрезка Ñнизу', + 'CropH' => 'Ð’Ñ‹Ñота кадрированиÑ', + 'CropLeft' => 'Обрезка Ñлева', + 'CropRight' => 'Обрезка Ñправа', + 'CropRotation' => 'Поворот рамки кадрированиÑ', + 'CropTop' => 'Обрезка Ñверху', + 'CropW' => 'Ширина кадрированиÑ', + 'CropX' => 'Координаты ÐºÐ°Ð´Ñ€Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ оÑи X', + 'CropXCommonOffset' => { + Description => 'Обрезка по X – Общее Ñмещение', + PrintConv => { + 'Common Offset Setting' => 'ÐžÐ±Ñ‰Ð°Ñ Ð½Ð°Ñтройка ÑмещениÑ', + 'Individual Offset Setting' => 'Ð˜Ð½Ð´Ð¸Ð²Ð¸Ð´ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð½Ð°Ñтройка ÑмещениÑ', + }, + }, + 'CropXOffset' => 'Обрезка по X – Смещение', + 'CropXOffset2' => 'Обрезка по X – Смещение 2', + 'CropXSize' => 'Размер обрезки по X', + 'CropXViewpointNumber' => 'Обрезка по X – Ðомер точки проÑмотра', + 'CropXViewpointNumber2' => 'Обрезка по X – Ðомер точки проÑмотра 2', + 'CropY' => 'Координаты ÐºÐ°Ð´Ñ€Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ оÑи Y', + 'CropYCommonOffset' => { + Description => 'Обрезка по Y – Общее Ñмещение', + PrintConv => { + 'Common Offset Setting' => 'ÐžÐ±Ñ‰Ð°Ñ Ð½Ð°Ñтройка ÑмещениÑ', + 'Individual Offset Setting' => 'Ð˜Ð½Ð´Ð¸Ð²Ð¸Ð´ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð½Ð°Ñтройка ÑмещениÑ', + }, + }, + 'CropYOffset' => 'Обрезка по Y – Смещение', + 'CropYOffset2' => 'Обрезка по Y – Смещение 2', + 'CropYSize' => 'Размер обрезки по Y', + 'CropYViewpointNumber' => 'Обрезка по Y – Ðомер точки проÑмотра', + 'CropYViewpointNumber2' => 'Обрезка по Y – Ðомер точки проÑмотра 2', + 'CroppedAreaImageHeightPixels' => 'ИÑÑ…Ð¾Ð´Ð½Ð°Ñ Ð²Ñ‹Ñота Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð² пикÑелÑÑ…', + 'CroppedAreaImageWidthPixels' => 'ИÑÑ…Ð¾Ð´Ð½Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ð° Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð² пикÑелÑÑ…', + 'CroppedAreaLeftPixels' => 'ÐžÐ±Ñ€ÐµÐ·Ð°Ð½Ð½Ð°Ñ Ð¾Ð±Ð»Ð°Ñть Ñлева в пикÑелÑÑ…', + 'CroppedAreaTopPixels' => 'ÐžÐ±Ñ€ÐµÐ·Ð°Ð½Ð½Ð°Ñ Ð¾Ð±Ð»Ð°Ñть Ñверху в пикÑелÑÑ…', + 'Cropping' => { + Description => 'Кадрирование', + PrintConv => { + 'Off' => 'Ðе включено', + 'On' => 'Включено', + }, + }, + 'Cross-Reference' => 'ПерекреÑÑ‚Ð½Ð°Ñ ÑÑылка', + 'CueSheet' => 'Файл разметки (Cue Sheet)', + 'CurrentICCProfile' => 'Текущий ICC-профиль', + 'CurrentIPTCDigest' => 'Хеш Ñумма текущего IPTC', + 'CurrentPreProfileMatrix' => 'Текущий предварительный профиль матрицы', + 'CurrentVersion' => 'Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ€ÑиÑ', + 'CursorSize' => 'Размер курÑора', + 'Curve0x' => 'ÐšÑ€Ð¸Ð²Ð°Ñ 0x', + 'Curve0y' => 'ÐšÑ€Ð¸Ð²Ð°Ñ 0y', + 'Curve1x' => 'ÐšÑ€Ð¸Ð²Ð°Ñ 1x', + 'Curve1y' => 'ÐšÑ€Ð¸Ð²Ð°Ñ 1y', + 'Curve2x' => 'ÐšÑ€Ð¸Ð²Ð°Ñ 2x', + 'Curve2y' => 'ÐšÑ€Ð¸Ð²Ð°Ñ 2y', + 'Curve3x' => 'ÐšÑ€Ð¸Ð²Ð°Ñ 3x', + 'Curve3y' => 'ÐšÑ€Ð¸Ð²Ð°Ñ 3y', + 'Curve4x' => 'ÐšÑ€Ð¸Ð²Ð°Ñ 4x', + 'Curve4y' => 'ÐšÑ€Ð¸Ð²Ð°Ñ 4y', + 'Custom1' => 'Дополнительное поле 1', + 'Custom10' => 'Дополнительное поле 10', + 'Custom2' => 'Дополнительное поле 2', + 'Custom3' => 'Дополнительное поле 3', + 'Custom4' => 'Дополнительное поле 4', + 'Custom5' => 'Дополнительное поле 5', + 'Custom6' => 'Дополнительное поле 6', + 'Custom7' => 'Дополнительное поле 7', + 'Custom8' => 'Дополнительное поле 8', + 'Custom9' => 'Дополнительное поле 9', + 'CustomRendered' => { + Description => 'ПользовательÑкий рендеринг', + PrintConv => { + 'Custom' => 'ПользовательÑкий', + 'Normal' => 'Обычный', + 'Panorama' => 'Панорама', + 'Portrait' => 'Портрет', + }, + }, + 'D-RangeOptimizerHighlight' => 'ÐžÐ¿Ñ‚Ð¸Ð¼Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð´Ð¸Ð½Ð°Ð¼Ð¸Ñ‡ÐµÑкого диапазона – Света', + 'D-RangeOptimizerMode' => { + Description => 'ÐžÐ¿Ñ‚Ð¸Ð¼Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð´Ð¸Ð½Ð°Ð¼Ð¸Ñ‡ÐµÑкого диапазона – Режим', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкий', + 'Manual' => 'Ручной', + 'Off' => 'Ðе включён', + }, + }, + 'D-RangeOptimizerShadow' => 'ÐžÐ¿Ñ‚Ð¸Ð¼Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð´Ð¸Ð½Ð°Ð¼Ð¸Ñ‡ÐµÑкого диапазона – Тени', + 'D-RangeOptimizerValue' => 'ÐžÐ¿Ñ‚Ð¸Ð¼Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð´Ð¸Ð½Ð°Ð¼Ð¸Ñ‡ÐµÑкого диапазона – Значение', + 'DCContinent' => 'DC – Континент', + 'DCCoordinatePrecision' => 'DC – ТочноÑть координат', + 'DCCoordinateUncertaintyInMeters' => 'DC – ПогрешноÑть Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¾Ñ€Ð´Ð¸Ð½Ð°Ñ‚ (м)', + 'DCCountry' => 'DC – Страна', + 'DCCountryCode' => 'DC – Код Ñтраны', + 'DCCounty' => 'DC – Район', + 'DCDecimalLatitude' => 'DC – ГеографичеÑÐºÐ°Ñ ÑˆÐ¸Ñ€Ð¾Ñ‚Ð° меÑта находки', + 'DCDecimalLongitude' => 'DC – ГеографичеÑÐºÐ°Ñ Ð´Ð¾Ð»Ð³Ð¾Ñ‚Ð° меÑта находки', + 'DCEvent' => 'Событие/Явление', + 'DCFootprintSRS' => 'DC – Footprint SRS', + 'DCFootprintSpatialFit' => 'DC – Footprint Spatial Fit', + 'DCFootprintWKT' => 'DC – Footprint WKT', + 'DCGeodeticDatum' => 'DC – ГеографичеÑÐºÐ°Ñ ÑиÑтема координат', + 'DCGeoreferenceProtocol' => 'DC – ОпиÑание метода Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¾Ñ€Ð´Ð¸Ð½Ð°Ñ‚', + 'DCGeoreferenceRemarks' => 'DC – Комментарии к геопривÑзке', + 'DCGeoreferenceSources' => 'DC – ИÑточники геопривÑзки', + 'DCGeoreferenceVerificationStatus' => 'DC – Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ геопривÑзки', + 'DCGeoreferencedBy' => 'DC – Люди Ñоздавшие геопривÑзку', + 'DCGeoreferencedDate' => 'DC – Дата геопривÑзки', + 'DCHigherGeography' => 'DC – Крупные географичеÑкие объекты', + 'DCHigherGeographyID' => 'DC – ID географичеÑкого региона', + 'DCIsland' => 'DC – ОÑтров', + 'DCIslandGroup' => 'DC – Группа оÑтровов', + 'DCLocality' => 'DC – ОпиÑание меÑта находки', + 'DCLocationAccordingTo' => 'DC – ИÑточник данных о меÑтоположении', + 'DCLocationID' => 'DC – ID набора данных о меÑтоположении', + 'DCLocationRemarks' => 'DC – Комментарии к опиÑанию меÑтоположениÑ', + 'DCMaximumDepthInMeters' => 'DC – МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð³Ð»ÑƒÐ±Ð¸Ð½Ð° (м)', + 'DCMaximumDistanceAboveSurfaceInMeters' => 'DC – МакÑимальное диÑÑ‚Ð°Ð½Ñ†Ð¸Ñ Ð½Ð°Ð´ поверхноÑтью (м)', + 'DCMaximumElevationInMeters' => 'DC – МакÑимальное выÑота (м)', + 'DCMinimumDepthInMeters' => 'DC – ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð³Ð»ÑƒÐ±Ð¸Ð½Ð° (м)', + 'DCMinimumDistanceAboveSurfaceInMeters' => 'DC – Минимальное диÑÑ‚Ð°Ð½Ñ†Ð¸Ñ Ð½Ð°Ð´ поверхноÑтью (м)', + 'DCMinimumElevationInMeters' => 'DC – ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð²Ñ‹Ñота (м)', + 'DCMunicipality' => 'DC – Район', + 'DCPointRadiusSpatialFit' => 'DC – Point Radius Spatial Fit', + 'DCStateProvince' => 'DC – ОблаÑть', + 'DCTermsLocation' => 'DC – МеÑтоположение', + 'DCVerbatimCoordinateSystem' => 'DC – СиÑтема координат по первоиÑточнику', + 'DCVerbatimCoordinates' => 'DC – Координаты меÑта находки по первоиÑточнику', + 'DCVerbatimDepth' => 'DC – ОпиÑание глубины по первоиÑточнику', + 'DCVerbatimElevation' => 'DC – ОпиÑание выÑоты по первоиÑточнику', + 'DCVerbatimLatitude' => 'DC – Широта меÑÑ‚Ð¾Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾ первоиÑточнику', + 'DCVerbatimLocality' => 'DC – ОпиÑание меÑта по первоиÑточнику', + 'DCVerbatimLongitude' => 'DC – Долгота меÑÑ‚Ð¾Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾ первоиÑточнику', + 'DCVerbatimSRS' => 'DC – SRS по первоиÑточнику', + 'DCWaterBody' => 'DC – Водоём', + 'DNGAdobeData' => 'DNG – Данные Adobe', + 'DNGBackwardVersion' => 'СовмеÑÑ‚Ð¸Ð¼Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ DNG', + 'DNGLensInfo' => 'DNG – Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð± объективе', + 'DNGPrivateData' => 'DNG – Данные производителÑ', + 'DNGVersion' => 'ВерÑÐ¸Ñ DNG', + 'DOF' => 'Глубина резкоÑти', + 'DPP' => 'Digital Photo Professional (DPP)', + 'DPXFileSize' => 'Размер файла DPX', + 'DRMCommerceID' => 'DRM – КоммерчеÑкий ID', + 'DRMServerID' => 'DRM – ID Ñервера', + 'DRM_E-BookBaseID' => 'DRM – ID базы Ñлектронных книг', + 'DTVContent' => { + Description => 'Контент DTV', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'DataCompressionMethod' => 'Метод ÑÐ¶Ð°Ñ‚Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…', + 'DataDump' => 'Вывод данных', + 'DataLocation' => { + Description => 'РаÑположение данных', + PrintConv => { + 'Downloaded Separately' => 'СкачиваетÑÑ Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ð¾', + 'Local Music File' => 'Локальный музыкальный файл', + }, + }, + 'DataOffset' => 'Смещение данных', + 'DataOffsets' => 'Ð¡Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…', + 'DataPreparer' => 'Подготовка данных', + 'DataReference' => 'СÑылка на данные', + 'DataSign' => { + Description => 'ПодпиÑÑŒ данных', + PrintConv => { + 'Signed' => 'ПодпиÑан', + 'Unsigned' => 'ÐеподпиÑан', + }, + }, + 'DataSize' => 'Размер данных', + 'DataType' => { + Description => 'Тип данных', + PrintConv => { + 'Artwork' => 'Обложка', + }, + }, + 'DataWindow' => 'Окно данных', + 'DatabaseName' => 'Ðазвание базы данных', + 'Date' => 'Дата', + 'DateAcquired' => 'Дата приобретениÑ', + 'DateCreated' => 'Дата ÑозданиÑ', + 'DateIdentified' => 'Дата определениÑ', + 'DateSent' => 'Дата отправлениÑ', + 'DateTime' => 'Дата и времÑ', + 'DateTimeCompleted' => 'Дата завершениÑ', + 'DateTimeCreated' => 'Дата и Ð²Ñ€ÐµÐ¼Ñ ÑозданиÑ', + 'DateTimeDue' => 'Срок Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸', + 'DateTimeEnd' => 'Дата Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ ÑобытиÑ', + 'DateTimeOriginal' => 'Дата Ñъёмки', + 'DateTimeStamp' => 'Дата ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÑобытиÑ', + 'DateTimeStart' => 'Дата начала ÑобытиÑ', + 'DayOfWeek' => { + Description => 'День недели', + PrintConv => { + 'Friday' => 'ПÑтница', + 'Monday' => 'Понедельник', + 'Saturday' => 'Суббота', + 'Sunday' => 'ВоÑкреÑенье', + 'Thursday' => 'Четверг', + 'Tuesday' => 'Вторник', + 'Wednesday' => 'Среда', + }, + }, + 'Decode' => 'Декодирование', + 'DefaultAlarm' => 'Сигнал по-умолчанию', + 'DefaultBlackRender' => { + Description => 'Обработка чёрного по-умолчанию', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкаÑ', + 'None' => 'Ðе обрабатывать', + }, + }, + 'DefaultChar' => 'Символ замены', + 'DefaultCropOrigin' => 'Положение обрезанного Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾-умолчанию', + 'DefaultCropSize' => 'Размер обрезанного Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾-умолчанию', + 'DefaultImageColor' => 'Цвет Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾-умолчанию', + 'DefaultScale' => 'МаÑÑштаб по-умолчанию', + 'DefaultStyle' => 'Стиль по-умолчанию', + 'DefaultUserCrop' => 'ОблаÑть обрезки по-умолчанию', + 'DefineQuantizationTable' => 'Определение таблиц квантованиÑ', + 'Delay' => 'Задержка', + 'DelayTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð·Ð°Ð´ÐµÑ€Ð¶ÐºÐ¸', + 'DependentImage1EntryNumber' => 'ЗавиÑимое изображение 1 – Ðомер входа', + 'DependentImage2EntryNumber' => 'ЗавиÑимое изображение 2 – Ðомер входа', + 'Depth' => 'Глубина цвета', + 'DepthImage' => 'Глубина изображениÑ', + 'DepthMapTiff' => 'Карта глубины Tiff', + 'Descender' => 'ПодÑтрочный интервал', + 'Description' => 'ОпиÑание', + 'Designer' => 'Дизайнер', + 'DesignerURL' => 'URL дизайнера', + 'Destination' => 'Ðазначение', + 'DeviceID' => 'ID уÑтройÑтва', + 'DeviceName' => 'Ðазвание уÑтройÑтва', + 'DeviceSettingDescription' => 'ОпиÑание предуÑтановок камеры', + 'DewarpData' => 'КомпенÑÐ°Ñ†Ð¸Ñ Ð¾Ð¿Ñ‚Ð¸Ñ‡ÐµÑкого иÑÐºÐ°Ð¶ÐµÐ½Ð¸Ñ â€“ Данные', + 'DewarpFlag' => 'КомпенÑÐ°Ñ†Ð¸Ñ Ð¾Ð¿Ñ‚Ð¸Ñ‡ÐµÑкого иÑÐºÐ°Ð¶ÐµÐ½Ð¸Ñ â€“ Флаг', + 'DictionaryShortName' => 'Сокращенное название ÑловарÑ', + 'DigitalCreationDate' => 'Дата оцифровки файла', + 'DigitalCreationDateTime' => 'Дата и Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ†Ð¸Ñ„Ñ€Ð¾Ð²ÐºÐ¸ файла', + 'DigitalCreationTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¾Ñ†Ð¸Ñ„Ñ€Ð¾Ð²ÐºÐ¸ файла', + 'DigitalSignature' => 'Ð¦Ð¸Ñ„Ñ€Ð¾Ð²Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ', + 'DigitalZoom' => { + Description => 'Цифровой зум', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'DigitalZoomOn' => { + Description => 'Цифровой зум', + PrintConv => { + 'Off' => 'Ðе включён', + 'On' => 'Включен', + }, + }, + 'DigitalZoomRatio' => 'КоÑффициент цифрового увеличениÑ', + 'Director' => 'Директор', + 'Directory' => 'Каталог', + 'DirectoryNumber' => 'Ðомер каталога', + 'DisallowCounterProposal' => 'Запретить вÑтречное предложение', + 'Disclaimer' => 'Отказ от ответÑтвенноÑти', + 'DisplayWindow' => 'Окно отображениÑ', + 'DisplayXResolution' => 'Разрешение Ñкрана по X', + 'DisplayXResolutionUnit' => { + Description => 'Единицы Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ñкрана по X', + PrintConv => { + '0.01 mm' => '0.01 мм', + '0.1 mm' => '0.1 мм', + '10 cm' => '10 Ñм', + '10 m' => '10 м', + '100 m' => '100 м', + 'cm' => 'Ñм', + 'km' => 'км', + 'm' => 'м', + 'mm' => 'мм', + 'um' => 'мкм', + }, + }, + 'DisplayYResolution' => 'Разрешение Ñкрана по Y', + 'DisplayYResolutionUnit' => { + Description => 'Единицы Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ñкрана по Y', + PrintConv => { + '0.01 mm' => '0.01 мм', + '0.1 mm' => '0.1 мм', + '10 cm' => '10 Ñм', + '10 m' => '10 м', + '100 m' => '100 м', + 'cm' => 'Ñм', + 'km' => 'км', + 'm' => 'м', + 'mm' => 'мм', + 'um' => 'мкм', + }, + }, + 'DisplayedUnitsX' => { + Description => 'Отображаемые единицы по X', + PrintConv => { + 'cm' => 'Сантиметры', + 'inches' => 'Дюймы', + }, + }, + 'DisplayedUnitsY' => { + Description => 'Отображаемые единицы по Y', + PrintConv => { + 'cm' => 'Сантиметры', + 'inches' => 'Дюймы', + }, + }, + 'Dispose' => 'Управление ÑлоÑми', + 'DistortionCompensation' => { + Description => 'КомпенÑÐ°Ñ†Ð¸Ñ Ð´Ð¸ÑторÑии', + PrintConv => { + 'Off' => 'Ðе включена', + 'On' => 'Включена', + 'n/a' => 'Ðет данных', + }, + }, + 'DistortionCorrParams' => 'Параметры коррекции диÑторÑии', + 'DistortionCorrection' => { + Description => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð´Ð¸ÑторÑии', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкаÑ', + 'Auto fixed by lens' => 'ÐвтоматичеÑÐºÐ°Ñ Ð¿Ð¾ объективу', + 'No correction params available' => 'Ðе доÑтупна', + 'Off' => 'Ðе включена', + }, + }, + 'DistortionCorrectionAlreadyApplied' => 'Применена ÐºÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð´Ð¸ÑторÑии', + 'Distribution' => 'ОпиÑание', + 'DittoKey' => { + Description => 'Ключь Ditto', + PrintConv => { + 'New' => 'Заново', + 'Same' => 'Так же', + }, + }, + 'DjVuVersion' => 'ВерÑÐ¸Ñ DjVu', + 'DoNotForwardMeeting' => 'Ðе передвигать вÑтречу', + 'DocClass' => 'СоÑтоÑние документа', + 'DocRights' => 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð°Ð²Ñ‚Ð¾Ñ€Ñкого права', + 'DocType' => 'Тип документа', + 'Document' => 'Документ', + 'DocumentAncestors' => 'Предки документа', + 'DocumentHistory' => 'ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°', + 'DocumentName' => 'Ðазвание документа', + 'DocumentNotes' => 'Комментарии к документу', + 'DocumentUsageRights' => 'Права на иÑпользование Документа', + 'DotRange' => 'Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñ‚Ð° ÑоответÑтвующих точке', + 'DriveMode' => { + Description => 'Режим ÑпуÑка', + PrintConv => { + 'Continuous Shooting' => 'Ð¡ÐµÑ€Ð¸Ð¹Ð½Ð°Ñ Ñъёмка', + 'Self-timer Operation' => 'Съёмка Ñ Ð°Ð²Ñ‚Ð¾ÑпуÑком', + 'Single-frame Shooting' => 'ÐŸÐ¾ÐºÐ°Ð´Ñ€Ð¾Ð²Ð°Ñ Ñъёмка', + }, + }, + 'DriveSerialNumber' => 'Серийный номер привода', + 'DriveType' => { + Description => 'Тип накопителÑ', + PrintConv => { + 'Fixed Disk' => 'УÑтановленный диÑк', + 'Invalid Root Path' => 'Ðеверный корневой путь', + 'Ram Disk' => 'Ram диÑк', + 'Remote Drive' => 'Удаленный диÑк', + 'Removable Media' => 'Съемный диÑк', + 'Unknown' => 'ÐеизвеÑтный', + }, + }, + 'DuotoneHalftoningInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ тонировании – Двуцветное', + 'DuotoneImageInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ двухцветном изображении', + 'DuotoneTransferFuncs' => 'Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¿ÐµÑ€ÐµÐ½Ð¾Ñа – Двуцветное', + 'Duration' => 'ПродолжительноÑть', + 'DynamicRangeOptimizer' => { + Description => 'ÐžÐ¿Ñ‚Ð¸Ð¼Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð´Ð¸Ð½Ð°Ð¼Ð¸Ñ‡ÐµÑкого диапазона', + PrintConv => { + 'Advanced Auto' => 'РаÑширеннаÑ. ÐвтоматичеÑкаÑ', + 'Advanced Lv1' => 'РаÑширеннаÑ. Уровень 1', + 'Advanced Lv2' => 'РаÑширеннаÑ. Уровень 2', + 'Advanced Lv3' => 'РаÑширеннаÑ. Уровень 3', + 'Advanced Lv4' => 'РаÑширеннаÑ. Уровень 4', + 'Advanced Lv5' => 'РаÑширеннаÑ. Уровень 5', + 'Auto' => 'ÐвтоматичеÑкаÑ', + 'Off' => 'Ðе включена', + 'Standard' => 'СтандартнаÑ', + }, + }, + 'EPSOptions' => 'Параметры EPS', + 'EXRVersion' => 'ВерÑÐ¸Ñ EXR', + 'EarliestAgeOrLowestStage' => 'Самый ранний век/Ðизший ÑруÑ', + 'EarliestEonOrLowestEonothem' => 'Самый ранний Ñон/ÐÐ¸Ð·ÑˆÐ°Ñ Ñонотема', + 'EarliestEpochOrLowestSeries' => 'Ð¡Ð°Ð¼Ð°Ñ Ñ€Ð°Ð½Ð½ÑÑ Ñпоха/Ðизший отдел', + 'EarliestEraOrLowestErathem' => 'Ð¡Ð°Ð¼Ð°Ñ Ñ€Ð°Ð½Ð½ÑÑ Ñра/ÐÐ¸Ð·ÑˆÐ°Ñ Ñратема', + 'EarliestPeriodOrLowestSystem' => 'Самый ранний период/ÐÐ¸Ð·ÑˆÐ°Ñ ÑиÑтема', + 'EdgeNoiseReduction' => 'Щумоподавление – Уменьшение контурных шумов', + 'EditStatus' => 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ', + 'Edition' => 'Издание', + 'EditorialUpdate' => { + Description => 'Тип обновлениÑ', + PrintConv => { + 'Additional language' => 'Дополнительный Ñзык', + }, + }, + 'EffectiveBW' => 'Эффективный Чёрно-Белый', + 'EffectsVisible' => 'ВидимоÑть Ñффектов', + 'ElectronicImageStabilization' => 'Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ ÑÑ‚Ð°Ð±Ð¸Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ', + 'EmbeddedFileName' => 'Ðазвание вÑтроенного файла', + 'EmbeddedFileUsageRights' => 'Права на иÑпользование вÑтроенных файлов', + 'EmbeddedImage' => 'Ð’Ñтроенное изображение', + 'EmbeddedImageColorSpace' => 'Ð’Ñтроенное изображение – Цветовое проÑтранÑтво', + 'EmbeddedImageFilter' => 'Ð’Ñтроенное изображение – Фильтр', + 'EmbeddedImageHeight' => 'Ð’Ñтроенное изображение – Ð’Ñ‹Ñота', + 'EmbeddedImageWidth' => 'Ð’Ñтроенное изображение – Ширина', + 'EmbeddedJPG' => 'Ð’Ñтроенный JPG', + 'EmbeddedPNG' => 'Ð’Ñтроенный PNG', + 'EmbeddedVideo' => 'Ð’Ñтроенное видео', + 'EmbeddedXMPDigest' => 'Хеш-Ñумма вÑтроенного XMP', + 'Emphasis' => { + Description => 'Ðкцент', + PrintConv => { + 'None' => 'Ðет', + }, + }, + 'EncodeTime' => 'Ð’Ñ€ÐµÐ¼Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ', + 'EncodedBy' => 'Закодировал', + 'EncodedUsing' => 'Закодировано Ñ Ð¸Ñпользованием', + 'Encoder' => 'Кодер', + 'EncoderOptions' => 'ÐаÑтройки кодера', + 'EncoderVersion' => 'ВерÑÐ¸Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ñ‰Ð¸ÐºÐ°', + 'Encoding' => 'Кодирование', + 'EncodingProcess' => { + Description => 'ПроцеÑÑ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ', + PrintConv => { + 'Baseline DCT, Huffman coding' => 'Базовое DCT, кодирование Хаффмана', + 'Extended sequential DCT, Huffman coding' => 'РаÑширенное поÑледовательное DCT, кодирование Хаффмана', + 'Extended sequential DCT, arithmetic coding' => 'РаÑширенное поÑледовательное DCT, арифметичеÑкое кодирование', + 'Lossless, Differential Huffman coding' => 'Без потерь, дифференциальное кодирование Хаффмана', + 'Lossless, Huffman coding' => 'Без потерь, кодирование Хаффмана', + 'Lossless, arithmetic coding' => 'Без потерь, арифметичеÑкое кодирование', + 'Lossless, differential arithmetic coding' => 'Без потерь, дифференциальное арифметичеÑкое кодирование', + 'Progressive DCT, Huffman coding' => 'ПрогреÑÑивное DCT, кодирование Хаффмана', + 'Progressive DCT, arithmetic coding' => 'ПрогреÑÑивное DCT, арифметичеÑкое кодирование', + 'Progressive DCT, differential Huffman coding' => 'ПрогреÑÑивное DCT, дифференциальное кодирование Хаффмана', + 'Progressive DCT, differential arithmetic coding' => 'ПрогреÑÑивное DCT, дифференциальное арифметичеÑкое кодирование', + 'Sequential DCT, differential Huffman coding' => 'ПоÑледовательное DCT, Дифференциальное кодирование Хаффмана', + 'Sequential DCT, differential arithmetic coding' => 'ПоÑледовательное DCT, дифференциальное арифметичеÑкое кодирование', + }, + }, + 'EncodingScheme' => 'Схема кодированиÑ', + 'Encryption' => { + Description => 'Шифрование', + PrintConv => { + 'None' => 'Ðет', + 'Old Mobipocket' => 'Старый Mobipocket', + }, + }, + 'EncryptionKey' => 'Ключ шифрованиÑ', + 'EndPoints' => 'Конечные точки', + 'EndTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ', + 'EndUser' => 'Конечный пользователь', + 'EndUserID' => 'PLUS-ID пользователÑ', + 'EndUserName' => 'Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ', + 'EnvelopeNumber' => 'Ðомер конверта', + 'EnvelopePriority' => { + Description => 'Приоритет конверта', + PrintConv => { + '0 (reserved)' => '0 (Ð’ резерве)', + '1 (most urgent)' => '1 (Ðеотложное)', + '5 (normal urgency)' => '5 (Обычное)', + '8 (least urgent)' => '8 (Ðе Ñрочное)', + '9 (user-defined priority)' => '9 (ПользовательÑкий приоритет)', + }, + }, + 'EnvelopeRecordVersion' => 'ВерÑÐ¸Ñ Envelope Record', + 'EnvironmentMap' => { + Description => 'Карта окружениÑ', + PrintConv => { + 'Cube' => 'Куб', + 'Latitude/Longitude' => 'Широта/Долгота', + }, + }, + 'EquipmentInstitution' => 'Оборудование учреждениÑ', + 'EquipmentManufacturer' => 'Производитель оборудованиÑ', + 'Error' => 'Ошибка', + 'EscChar' => 'Escape-Ñимвол', + 'EscapeStatus' => 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¾Ð±ÐµÐ³Ð°', + 'Event' => 'Событие', + 'EventDate' => 'Дата ÑобытиÑ', + 'EventDay' => 'День(и) ÑобытиÑ', + 'EventEarliestDate' => 'Дата начала ÑобытиÑ', + 'EventEndDayOfYear' => 'День года Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ ÑобытиÑ', + 'EventFieldNotes' => 'Полевые заметки о Ñобытии', + 'EventFieldNumber' => 'Ðомер полевых заметок о Ñобытии', + 'EventHabitat' => 'Ðреал ÑобытиÑ', + 'EventID' => 'ID ÑобытиÑ', + 'EventLatestDate' => 'Дата Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ ÑобытиÑ', + 'EventMonth' => 'МеÑÑц ÑобытиÑ', + 'EventNumber' => 'Ðомер ÑобытиÑ', + 'EventParentEventID' => 'ID оÑновного ÑобытиÑ', + 'EventRemarks' => 'Комментарии к Ñобытию', + 'EventSampleSizeUnit' => 'Событие – Единицы размера', + 'EventSampleSizeValue' => 'Событие – Размер образца', + 'EventSamplingEffort' => 'Событие – Затраченные уÑилиÑ', + 'EventSamplingProtocol' => 'Событие – Метод иÑÑледованиÑ', + 'EventStartDayOfYear' => 'День года начала ÑобытиÑ', + 'EventTime' => 'Ð’Ñ€ÐµÐ¼Ñ ÑобытиÑ', + 'EventVerbatimEventDate' => 'Дата ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¿Ð¾ первоиÑточнику', + 'EventYear' => 'Год ÑобытиÑ', + 'ExceptionDateTimes' => 'Дата иÑключениÑ', + 'ExclusiveCoverage' => 'ЭкÑклюзивный репортаж', + 'ExcursionTolerance' => { + Description => 'Выход за пределы диапазона', + PrintConv => { + 'Allowed' => 'ДопуÑкаетÑÑ', + 'Not Allowed' => 'Ðе допуÑкаетÑÑ', + }, + }, + 'ExifByteOrder' => { + Description => 'Exif – ПорÑдок байтов', + PrintConv => { + 'Big-endian (Motorola, MM)' => 'ПорÑдок от Ñтаршего к младшему (Motorola, MM)', + 'Little-endian (Intel, II)' => 'ПорÑдок от младшего к Ñтаршему (Intel, II)', + }, + }, + 'ExifCameraInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ камере из Exif', + 'ExifImageHeight' => 'Exif – Ð’Ñ‹Ñота изображениÑ', + 'ExifImageWidth' => 'Exif – Ширина изображениÑ', + 'ExifInfo2' => 'Exif – Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ 2', + 'ExifOffset' => 'Смещение Exif Sub IFD', + 'ExifToolVersion' => 'ВерÑÐ¸Ñ ExifTool', + 'ExifUnicodeByteOrder' => { + Description => 'Exif – ПорÑдок байтов Unicode', + PrintConv => { + 'Big-endian (Motorola, MM)' => 'ПорÑдок от Ñтаршего к младшему (Motorola, MM)', + 'Little-endian (Intel, II)' => 'ПорÑдок от младшего к Ñтаршему (Intel, II)', + }, + }, + 'ExifVersion' => 'ВерÑÐ¸Ñ Exif', + 'ExpandFilm' => 'Expand – Плёнка', + 'ExpandFilterLens' => 'Expand – Фильтр объектива', + 'ExpandFlashLamp' => 'Expand – Ð’Ñпышка', + 'ExpandLens' => 'Expand – Объектив', + 'ExpandScanner' => 'Expand – Сканер', + 'ExpandSoftware' => 'Expand – Приложение', + 'ExpirationDate' => 'Дата иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ñрока', + 'ExpirationSpan' => 'Срок дейÑÑ‚Ð²Ð¸Ñ Ð¸ÑтечениÑ', + 'ExpirationTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¸ÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ñрока', + 'Expires' => 'Дата уÑÑ‚Ð°Ñ€ÐµÐ²Ð°Ð½Ð¸Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°', + 'Exposure' => 'ЭкÑпозициÑ', + 'ExposureCompensation' => 'КомпенÑÐ°Ñ†Ð¸Ñ ÑкÑпозиции', + 'ExposureIndex' => 'Ð˜Ð½Ð´ÐµÐºÑ ÑкÑпозиции', + 'ExposureLockUsed' => 'ИÑпользование блокировки параметров ÑкÑпозиции', + 'ExposureMode' => { + Description => 'Режим ÑкÑпозиции', + PrintConv => { + 'Auto' => 'ÐвтоÑкÑпозициÑ', + 'Auto bracket' => 'Ðвтобрекетинг', + 'Manual' => 'Ð ÑƒÑ‡Ð½Ð°Ñ ÑкÑпозициÑ', + }, + }, + 'ExposureProgram' => { + Description => 'Программа ÑкÑпозиции', + PrintConv => { + 'Action (High speed)' => 'Спорт', + 'Aperture-priority AE' => 'ÐвтоÑкÑÐ¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ Ñ Ð¿Ñ€Ð¸Ð¾Ñ€Ð¸Ñ‚ÐµÑ‚Ð¾Ð¼ Диафрагмы', + 'Bulb' => 'Выдержка от руки', + 'Creative (Slow speed)' => 'ТворчеÑкаÑ', + 'Landscape' => 'Пейзаж', + 'Manual' => 'РучнаÑ', + 'Not Defined' => 'Ðе определена', + 'Portrait' => 'Портрет', + 'Program AE' => 'ÐŸÑ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð½Ð°Ñ Ð°Ð²Ñ‚Ð¾ÑкÑпозициÑ', + 'Shutter speed priority AE' => 'ÐвтоÑкÑÐ¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ Ñ Ð¿Ñ€Ð¸Ð¾Ñ€Ð¸Ñ‚ÐµÑ‚Ð¾Ð¼ Выдержки', + }, + }, + 'ExposureTime' => 'Выдержка', + 'ExposureTimes' => 'Выдержка2', + 'ExtCache' => 'Ðазвание альтернативного кÑша', + 'ExtenderStatus' => { + Description => 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÑкÑтендера', + PrintConv => { + 'Attached' => 'Прикреплен', + 'Not attached' => 'Ðе прикреплен', + 'Removed' => 'Удалён', + }, + }, + 'ExternalLeading' => 'Внешний зазор', + 'ExtraFlags' => { + Description => 'Дополнительные флаги', + PrintConv => { + '(none)' => 'ОтÑутÑтвуют', + 'Fastest Algorithm' => 'БыÑтрый алгоритм', + 'Maximum Compression' => 'МакÑимальное Ñжатие', + }, + }, + 'ExtraSamples' => { + Description => 'ОпиÑание дополнительных компонентов', + PrintConv => { + 'Associated Alpha' => 'СвÑзаные альфа-данные', + 'Unassociated Alpha' => 'ÐеÑвÑзанные альфа-данные', + 'Unspecified' => 'Данные без указаниÑ', + }, + }, + 'FNumber' => 'Диафрагменное чиÑло', + 'FOV' => 'Поле зрениÑ', + 'FSType' => 'Тип Ð²Ð½ÐµÐ´Ñ€ÐµÐ½Ð¸Ñ ÑˆÑ€Ð¸Ñ„Ñ‚Ð° в документ', + 'FaceBalanceOrigI' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð»Ð¸Ñ†Ð° – ИÑходный I', + 'FaceBalanceOrigQ' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð»Ð¸Ñ†Ð° – ИÑходный Q', + 'FaceBalanceStrength' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð»Ð¸Ñ†Ð° – ИнтенÑивноÑть', + 'FaceBalanceWarmth' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð»Ð¸Ñ†Ð° – Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð°', + 'FaceDetected' => 'Обнаружение лица', + 'FaceID' => 'Идентификатор лица', + 'FaceNumbers' => 'КоличеÑтво лиц', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => 'Положительное направление', + 'Rotate 180' => 'Поворот на 180°', + 'Rotate 270 CW' => 'Поворот на 270° по чаÑовой Ñтрелке', + 'Rotate 90 CW' => 'Поворот на 90° по чаÑовой Ñтрелке', + }, + }, + 'FacePosition' => 'Положение лица', + 'FacesDetected' => 'Обнаружены лица', + 'Far' => 'Дальнее значение карты глубины', + 'FastSeek' => { + Description => 'БыÑтрый поиÑк', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'FaxProfile' => { + Description => 'ФакÑ-профиль', + PrintConv => { + 'Extended B&W lossless, F' => 'РаÑширенный чёрно-белый без потерь, профиль F', + 'Lossless JBIG B&W, J' => 'JBIG чёрно-белый без потерь, профиль J', + 'Lossless color and grayscale, L' => 'Цветной и Ñ Ð¾Ñ‚Ñ‚ÐµÐ½ÐºÐ°Ð¼Ð¸ Ñерого без потерь, профиль L', + 'Lossy color and grayscale, C' => 'Цветной и Ñ Ð¾Ñ‚Ñ‚ÐµÐ½ÐºÐ°Ð¼Ð¸ Ñерого Ñ Ð¿Ð¾Ñ‚ÐµÑ€Ñми, профиль C', + 'Minimal B&W lossless, S' => 'Минимальный чёрно-белый без потерь, профиль S', + 'Mixed raster content, M' => 'Смешанное Ñодержимое раÑтра, профиль M', + 'Multi Profiles' => 'ÐеÑколько профилей', + 'Profile T' => 'Профиль T', + 'Unknown' => 'Ðе ÑоответÑтвует профилю факÑимильной ÑвÑзи', + }, + }, + 'FaxRecvParams' => 'Параметры Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñ„Ð°ÐºÑов', + 'FaxRecvTime' => 'Требуемое Ð²Ñ€ÐµÐ¼Ñ Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñ„Ð°ÐºÑа', + 'FaxSubAddress' => 'ÐŸÐ¾Ð´Ð°Ð´Ñ€ÐµÑ Ñ„Ð°ÐºÑа', + 'FieldOfView' => { + Description => 'Угол обзора', + PrintConv => { + 'Linear' => 'Линейный', + 'Wide' => 'Широкий', + }, + }, + 'FieldPermissions' => { + Description => 'Ð Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð½Ð° изменение Полей', + PrintConv => { + 'Allow changes to specified form fields' => 'Разрешить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² указанных полÑÑ… формы', + 'Disallow changes to all form fields' => 'Запретить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð²Ð¾ вÑех полÑÑ… формы', + 'Disallow changes to specified form fields' => 'Запретить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² указанных полÑÑ… формы', + }, + }, + 'File1Duration' => 'Файл 1 – ПродолжительноÑть', + 'File1Length' => 'Файл 1 – Размер', + 'File1MD5Sum' => 'Файл 1 – Сумма MD5', + 'File1Media' => 'Файл 1 – Медиа', + 'File1Path' => 'Файл 1 – Путь', + 'File1PathUTF-8' => 'Файл 1 – Путь в кодировке UTF-8', + 'FileAccessDate' => 'Дата поÑледнего доÑтупа к файлу', + 'FileAttributes' => { + Description => 'Ðтрибуты файла', + PrintConv => { + 'Archive' => 'Ðрхивный', + 'BSD Whiteout' => 'BSD – Скрытый', + 'Block' => 'Блок', + 'Character' => 'Символ', + 'Compressed' => 'Сжатый', + 'Device' => 'УÑтройÑтво', + 'Directory' => 'Каталог', + 'Encrypted' => 'Зашифрованный', + 'Encrypted?' => 'Зашифрованные?', + 'FIFO' => 'FIFO (именованный канал)', + 'Hidden' => 'Скрытый', + 'Mux Block' => 'Блок Mux', + 'Mux Character' => 'Символ Mux', + 'Normal' => 'Без атрибутов', + 'Not Content Indexed' => 'Ðе проиндекÑированный', + 'Not indexed' => 'Ðе индекÑированный', + 'Offline' => 'Ðвтономный', + 'Read Only' => 'Только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ', + 'Read-only' => 'Только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ', + 'Regular' => 'Обычный', + 'Reparse Point' => 'Точки повторной обработки', + 'Reparse point' => 'Точки повторной обработки', + 'Set Group ID' => 'УÑтановка ID группы', + 'Set User ID' => 'УÑтановка ID пользователÑ', + 'Socket' => 'Сокет', + 'Solaris Door' => 'Дверь (в Solaris)', + 'Solaris Shadow Inode' => 'Теневой Ð¸Ð½Ð´ÐµÐºÑ Solaris', + 'Sparse' => 'Разрежённый', + 'Sparse File' => 'Разрежённый файл', + 'Symbolic Link' => 'СимволичеÑÐºÐ°Ñ ÑÑылка', + 'System' => 'СиÑтемный', + 'Temporary' => 'Временный', + 'Unknown' => 'ÐеизвеÑтный', + 'Volume' => 'Том', + 'Volume Label' => 'Метка тома', + 'VxFS Compressed' => 'Сжатый VxFS', + 'XENIX Named' => 'Именованный (в XENIX)', + }, + }, + 'FileBlockCount' => 'КоличеÑтво блоков в файле', + 'FileBlockSize' => 'Размер блока в файле', + 'FileCreateDate' => 'Дата ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°', + 'FileDeviceID' => 'ID уÑтройÑтва файла', + 'FileDeviceNumber' => 'Ðомер уÑтройÑтва файла', + 'FileFormat' => { + Description => 'Формат файла', + PrintConv => { + 'AppleSingle (Apple Computer Inc)' => 'AppleSingle (Apple)', + 'Audio Interchange File Format AIFF (Apple Computer Inc)' => 'Ðудиофайл формата AIFF (Apple)', + 'Bit Mapped Graphics File [.BMP] (Microsoft)' => 'Битовый графичеÑкий файл [.BMP] (Microsoft)', + 'Compressed Binary File [.ZIP] (PKWare Inc)' => 'Сжатый двоичный файл [.ZIP] (ÐšÐ¾Ð¼Ð¿Ð°Ð½Ð¸Ñ PKWare)', + 'Digital Audio File [.WAV] (Microsoft & Creative Labs)' => 'Ðудиофайл формата [.WAV] (Microsoft и Creative Labs)', + 'Hypertext Markup Language [.HTML] (The Internet Society)' => 'Язык гипертекÑтовой разметки [.HTML] (The Internet Society)', + 'IPTC Unstructured Character Oriented File Format (UCOFF)' => 'ÐеÑтруктурированный Ñимвольно-ориентированный формат файла IPTC (UCOFF)', + 'IPTC7901 Recommended Message Format' => 'IPTC7901 Рекомендуемый формат ÑообщениÑ', + 'Illustrator (Adobe Graphics data)' => 'Illustrator (ГрафичеÑкие данные Adobe)', + 'JPEG File Interchange (JFIF)' => 'Формат обмена файлами Ñтандарта JPEG (JFIF)', + 'MPEG 2 Audio Layer 2 (Musicom), ISO/IEC' => 'Ðудио второго ÑƒÑ€Ð¾Ð²Ð½Ñ MPEG 2 (Musicom), ISO/IEC', + 'MPEG 2 Audio Layer 3, ISO/IEC' => 'Ðудио третьего ÑƒÑ€Ð¾Ð²Ð½Ñ MPEG 2 , ISO/IEC', + 'News Industry Text Format (NITF)' => 'ТекÑтовый формат новоÑтной индуÑтрии (NITF)', + 'No ObjectData' => 'Данные объекта отÑутÑтвуют', + 'PC DOS/Windows Executable Files [.COM][.EXE]' => 'ИÑполнÑемый файл PC DOS/Windows [.COM][.EXE]', + 'RIFF Wave (Microsoft Corporation)' => 'RIFF Wave (Microsoft)', + 'Ritzaus Bureau NITF version (RBNITF DTD)' => 'Ritzaus Bureau вариант NITF (RBNITF DTD)', + 'Tagged Image File Format (Adobe/Aldus Image data)' => 'Формат файла Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ñ Ñ‚ÐµÐ³Ð°Ð¼Ð¸ (Adobe/Aldus данные изображениÑ)', + 'Tape Archive [.TAR]' => 'Формат файла архива [.TAR]', + 'Tidningarnas Telegrambyra NITF version (TTNITF DTD)' => 'Tidningarnas Telegrambyra вариант NITF (TTNITF DTD)', + 'United Press International ANPA 1312 variant' => 'United Press International – Вариант ANPA 1312', + 'United Press International Down-Load Message' => 'United Press International – Down-Load Message', + }, + }, + 'FileGroupID' => 'ID группы файла', + 'FileHardLinks' => 'ЖёÑткие ÑÑылки файла', + 'FileID' => 'ID файла', + 'FileInfoLen' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ файле – Len', + 'FileInfoLen2' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ файле – Len 2', + 'FileInfoVersion' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ файле – ВерÑиÑ', + 'FileInodeChangeDate' => 'Дата Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ð³Ð¾ индекÑа', + 'FileInodeNumber' => 'Ðомер индекÑа файла', + 'FileModifyDate' => 'Дата Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°', + 'FileName' => 'Ðазвание файла', + 'FileNameAsDelivered' => 'Ðазвание предоÑтавленного файла', + 'FileNameLength' => 'Длина Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°', + 'FileNumber' => 'Ðомер файла', + 'FilePath' => 'Путь к файлу', + 'FilePermissions' => 'Ð Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°', + 'FileSequence' => 'ПоÑледовательноÑть файлов', + 'FileSize' => 'Размер файла', + 'FileSizeBytes' => 'Размер файла в байтах', + 'FileSource' => { + Description => 'ИÑточник файла', + PrintConv => { + 'Digital Camera' => 'Ð¦Ð¸Ñ„Ñ€Ð¾Ð²Ð°Ñ Ñ„Ð¾Ñ‚Ð¾ÐºÐ°Ð¼ÐµÑ€Ð°', + 'Film Scanner' => 'Плёночный Ñканер', + 'Reflection Print Scanner' => 'Планшетный Ñканер', + 'Sigma Digital Camera' => 'Ð¦Ð¸Ñ„Ñ€Ð¾Ð²Ð°Ñ Ñ„Ð¾Ñ‚Ð¾ÐºÐ°Ð¼ÐµÑ€Ð° Sigma', + }, + }, + 'FileType' => 'Тип файла', + 'FileTypeExtension' => 'РаÑширение файла', + 'FileUserID' => 'ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñ„Ð°Ð¹Ð»Ð°', + 'FileVersion' => 'ВерÑÐ¸Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð°', + 'Filename' => 'Ðазвание файла', + 'Files' => 'Файлы', + 'FillAttributes' => 'Ðттрибуты заполнениÑ', + 'FillOrder' => { + Description => 'ПорÑдок битов в байте', + PrintConv => { + 'Normal' => 'Обычный', + 'Reversed' => 'Обратный', + }, + }, + 'Filter' => { + Description => 'Фильтр', + PrintConv => { + 'Adaptive' => 'Ðдаптивный', + }, + }, + 'FinalFrameBlocks' => 'Блоков в поÑледнем кадре', + 'Firmware' => 'Прошивка', + 'FirmwareDate' => 'Дата прошивки', + 'FirmwareID' => 'ID прошивки', + 'FirmwareVersion' => 'ВерÑÐ¸Ñ Ð¿Ñ€Ð¾ÑˆÐ¸Ð²ÐºÐ¸', + 'FirmwareVersions' => 'ВерÑÐ¸Ñ Ð¿Ñ€Ð¾ÑˆÐ¸Ð²ÐºÐ¸', + 'FirstChar' => 'Первый Ñимвол шрифта', + 'FirstPhotoDate' => 'Дата ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿ÐµÑ€Ð²Ð¾Ð³Ð¾ изображениÑ', + 'FirstPublicationDate' => 'Дата первой публикации изображениÑ', + 'FixtureIdentifier' => 'Узнаваемый идентификатор', + 'Flags' => { + Description => 'Флаги', + PrintConv => { + 'Allow Download' => 'Разрешить загрузку', + 'Allow Recording' => 'Разрешить запиÑÑŒ', + 'Animation' => 'ÐнимациÑ', + 'Comment' => 'Комментарий', + 'Description' => 'ОпиÑание', + 'Extension Present' => 'ÐаÑтоÑщее времÑ', + 'ExtraFields' => 'Дополнительное поле', + 'FileName' => 'Ðазвание файлов', + 'IDList' => 'СпиÑок ID', + 'IconFile' => 'Иконка файла', + 'Limited Range' => 'Ограниченный диапазон', + 'LinkInfo' => 'Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑÑылке', + 'LinkToLink' => 'СÑылка на ÑÑылку', + 'Live' => 'Ð’ прÑмом Ñфире', + 'NoLinkInfo' => 'Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑÑылке отÑутÑтвует', + 'NoLinkPathTracking' => 'ОтÑлеживание пути без ÑÑылки', + 'NoLinkTrack' => 'Без ÑÑылки на трек', + 'Perfect Play' => 'ВоÑпроизводить поÑле загрузки', + 'RelativePath' => 'ОтноÑительный путь', + 'RunAsUser' => 'ЗапуÑк от имени пользователÑ', + 'TargetMetadata' => 'Целевые метаданные', + 'Text' => 'ТекÑÑ‚', + 'Unicode' => 'Юникод', + 'WorkingDir' => 'Ð Ð°Ð±Ð¾Ñ‡Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°', + }, + }, + 'Flash' => { + Description => 'СоÑтоÑние вÑпышки при Ñъёмке', + PrintConv => { + 'Auto, Did not fire' => 'ÐвтоматичеÑкий режим. Ð’Ñпышка не Ñработала', + 'Auto, Did not fire, Red-eye reduction' => 'ÐвтоматичеÑкий режим. Ð’Ñпышка не Ñработала. Включён режим ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ñффекта «краÑных глаз»', + 'Auto, Fired' => 'ÐвтоматичеÑкий режим. Ð’Ñпышка Ñработала', + 'Auto, Fired, Red-eye reduction' => 'ÐвтоматичеÑкий режим. Ð’Ñпышка Ñработала', + 'Auto, Fired, Red-eye reduction, Return detected' => 'ÐвтоматичеÑкий режим. Ð’Ñпышка Ñработала. Включён режим ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ñффекта «краÑных глаз». Отражённый Ñвет обнаружен', + 'Auto, Fired, Red-eye reduction, Return not detected' => 'ÐвтоматичеÑкий режим. Ð’Ñпышка Ñработала. Включён режим ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ñффекта «краÑных глаз». Отражённый Ñвет не обнаружен', + 'Auto, Fired, Return detected' => 'ÐвтоматичеÑкий режим. Ð’Ñпышка Ñработала. Отражённый Ñвет обнаружен', + 'Auto, Fired, Return not detected' => 'ÐвтоматичеÑкий режим. Ð’Ñпышка Ñработала. Отражённый Ñвет не обнаружен', + 'Did not fire' => 'Ð’Ñпышка не Ñработала', + 'Fired' => 'Ð’Ñпышка Ñработала', + 'Fired, Red-eye reduction' => 'Ð’Ñпышка Ñработала. Включён режим ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ñффекта «краÑных глаз»', + 'Fired, Red-eye reduction, Return detected' => 'Ð’Ñпышка Ñработала. Включён режим ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ñффекта «краÑных глаз». Отражённый Ñвет обнаружен', + 'Fired, Red-eye reduction, Return not detected' => 'Ð’Ñпышка Ñработала. Включён режим ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ñффекта «краÑных глаз». Отражённый Ñвет не обнаружен', + 'Fired, Return detected' => 'Ð’Ñпышка Ñработала. Отражённый Ñвет обнаружен', + 'Fired, Return not detected' => 'Ð’Ñпышка Ñработала. Отражённый Ñвет не обнаружен', + 'No Flash' => 'Без вÑпышки', + 'No flash function' => 'Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð²Ñпышки не поддерживаетÑÑ', + 'Off' => 'Ðе включена', + 'Off, Did not fire' => 'Ðе включена. Ð’Ñпышка не Ñработала', + 'Off, Did not fire, Return not detected' => 'Ðе включена. Ð’Ñпышка не Ñработала. Отражённый Ñвет не обнаружен', + 'Off, No flash function' => 'Ðе включена. Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð²Ñпышки не поддерживаетÑÑ', + 'Off, Red-eye reduction' => 'Ðе включена. Включён режим ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ñффекта «краÑных глаз»', + 'On' => 'Включена', + 'On, Did not fire' => 'Принудительный режим. Ð’Ñпышка не Ñработала', + 'On, Fired' => 'Принудительный режим. Ð’Ñпышка Ñработала', + 'On, Red-eye reduction' => 'Принудительный режим. Включён режим ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ñффекта «краÑных глаз»', + 'On, Red-eye reduction, Return detected' => 'Принудительный режим. Включён режим ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ñффекта «краÑных глаз». Отражённый Ñвет обнаружен', + 'On, Red-eye reduction, Return not detected' => 'Принудительный режим. Включён режим ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ñффекта «краÑных глаз». Отражённый Ñвет не обнаружен', + 'On, Return detected' => 'Принудительный режим. Отражённый Ñвет обнаружен', + 'On, Return not detected' => 'Принудительный режим. Отражённый Ñвет не обнаружен', + }, + }, + 'FlashAttributes' => { + Description => 'Flash аттрибуты', + PrintConv => { + 'UseNetwork' => 'ИÑпользовать Ñеть', + }, + }, + 'FlashCompensation' => 'КомпенÑÐ°Ñ†Ð¸Ñ Ð²Ñпышки', + 'FlashEnergy' => 'МощноÑть вÑпышки', + 'FlashExposureComp' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð²Ñпышки', + 'FlashManufacturer' => 'Производитель вÑпышки', + 'FlashMode' => { + Description => 'Режим вÑпышки', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкий', + 'Disabled' => 'Ð’Ñпышка не включена', + 'Force' => 'Принудительный', + 'Red eye' => 'КраÑные глаза', + }, + }, + 'FlashModel' => 'Модель вÑпышки', + 'FlashType' => { + Description => 'Тип вÑпышки', + PrintConv => { + 'Built-In Flash' => 'Ð’ÑтроеннаÑ', + 'External' => 'ВнешнÑÑ', + }, + }, + 'FlashVersion' => 'ВерÑÐ¸Ñ Flash', + 'FlashpixVersion' => 'ВерÑÐ¸Ñ Flashpix', + 'FlickerReduce' => { + Description => 'Уменьшение мерцаниÑ', + PrintConv => { + 'Off' => 'Ðе включено', + 'On' => 'Включено', + }, + }, + 'FlightPitchDegree' => 'Тангаж полета (наклон)', + 'FlightRollDegree' => 'Крен полета (вращение)', + 'FlightXSpeed' => 'СкороÑть полёта по оÑи X', + 'FlightYSpeed' => 'СкороÑть полёта по оÑи Y', + 'FlightYawDegree' => 'РыÑкание полета (поворот)', + 'FlightZSpeed' => 'СкороÑть полёта по оÑи Z', + 'FocalDistance' => 'ФокуÑное раÑÑтоÑние', + 'FocalLength' => 'ФокуÑное раÑÑтоÑние', + 'FocalLength35efl' => 'ФокуÑное раÑÑтоÑние', + 'FocalLengthIn35mmFormat' => 'ФокуÑное раÑÑтоÑние Ð´Ð»Ñ 35-мм формата', + 'FocalPlaneResolutionUnit' => { + Description => 'Единицы Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð² фокальной плоÑкоÑти', + PrintConv => { + 'None' => 'Ðе указано', + 'cm' => 'Ñм', + 'inches' => 'дюймы', + 'mm' => 'мм', + 'um' => 'мкм', + }, + }, + 'FocalPlaneXResolution' => 'Разрешение в фокальной плоÑкоÑти по X', + 'FocalPlaneYResolution' => 'Разрешение в фокальной плоÑкоÑти по Y', + 'FocalPointX' => 'Точка фокуÑа по оÑи X', + 'FocalPointY' => 'Точка фокуÑа по оÑи Y', + 'FocusDistance' => 'ФокуÑное раÑÑтоÑние', + 'FocusDistance2' => 'ФокуÑное раÑÑтоÑние 2', + 'FocusMode' => 'Режим фокуÑировки', + 'FocusPos' => 'ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ Ñ„Ð¾ÐºÑƒÑа', + 'FontFamily' => { + Description => 'СемейÑтво шрифта', + PrintConv => { + 'Decorative' => 'Декоративный', + 'Don\'t Care' => 'Без разницы', + 'Modern' => 'Модерн', + 'Script' => 'Скрипт', + }, + }, + 'FontName' => 'Ðазвание шрифта', + 'FontSize' => 'Размер шрифта', + 'FontSubfamily' => 'Стиль шрифта', + 'FontSubfamilyID' => 'ID ÑÑ‚Ð¸Ð»Ñ ÑˆÑ€Ð¸Ñ„Ñ‚Ð°', + 'FontType' => 'Тип шрифта', + 'FontWeight' => 'Толщина шрифта', + 'Footnotes' => 'СноÑки', + 'For' => 'ДлÑ', + 'FormExtraUsageRights' => 'Права на иÑпользование Форм Extra', + 'FormFields' => 'ÐŸÐ¾Ð»Ñ Ñ„Ð¾Ñ€Ð¼', + 'FormUsageRights' => 'Права на иÑпользование Форм', + 'Format' => { + Description => 'Формат', + PrintConv => { + 'RangeInverse' => 'Диапазон инверÑии', + 'RangeLinear' => 'Диапазон линейноÑти', + }, + }, + 'FormatVersionTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð²ÐµÑ€Ñии формата', + 'FormattedName' => 'Отформатированное имÑ', + 'Formatter' => 'Форматировщик', + 'ForwardMatrix1' => 'ПрÑÐ¼Ð°Ñ Ð¼Ð°Ñ‚Ñ€Ð¸Ñ†Ð° 1', + 'ForwardMatrix2' => 'ПрÑÐ¼Ð°Ñ Ð¼Ð°Ñ‚Ñ€Ð¸Ñ†Ð° 2', + 'FossilSpecimen' => 'ИÑкопаемый образец', + 'FossilSpecimenMaterialSampleID' => 'ID материала иÑкопаемого образца', + 'FovCot' => 'Угол обзора и КотангенÑ', + 'FractalParameters' => 'Фрактальные параметры', + 'FragmentList' => 'СпиÑок фрагментов', + 'FragmentTable' => 'Таблица фрагментов', + 'FrameCount' => 'КоличеÑтво кадров', + 'FrameExposureTime' => 'Ð’Ñ€ÐµÐ¼Ñ ÑкÑпозиции кадра', + 'FrameID' => 'ID кадра', + 'FrameNum' => 'Ðомер кадра', + 'FrameRate' => 'ЧаÑтота кадров', + 'FrameSize' => 'Размер кадра', + 'FrameSizeMax' => 'МакÑимальный размер кадра', + 'FrameSizeMin' => 'Минимальный размер кадра', + 'FramesPerSecond' => 'Кадров в Ñекунду', + 'Free' => 'Свободный', + 'FreeBusyTime' => 'Свободное/ЗанÑтое времÑ', + 'FreeByteCounts' => 'КоличеÑтво байтов в Ñтроке неиÑпользуемых байтов', + 'FreeOffsets' => 'Смещение к Ñтроке неиÑпользуемых байтов', + 'FullName' => 'Полное название шрифта', + 'FullPanoHeightPixels' => 'ИÑÑ…Ð¾Ð´Ð½Ð°Ñ Ð¿Ð¾Ð»Ð½Ð°Ñ Ð²Ñ‹Ñота до обрезки изображениÑ', + 'FullPanoWidthPixels' => 'ИÑÑ…Ð¾Ð´Ð½Ð°Ñ Ð¿Ð¾Ð»Ð½Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ð° до обрезки изображениÑ', + 'FullScreen' => 'ПолноÑкранный', + 'GDALMetadata' => 'GDAL – Метаданные', + 'GDALNoData' => 'GDAL – ПрозрачноÑть', + 'GEMake' => 'GE – Сделан', + 'GEModel' => 'GE – Модель', + 'GIFApplicationExtension' => 'РаÑширение GIF-приложениÑ', + 'GIFGraphicControlExtension' => 'РаÑширение ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð³Ñ€Ð°Ñ„Ð¸ÐºÐ¾Ð¹ GIF', + 'GIFPlainTextExtension' => 'РаÑширение обычного текÑта GIF', + 'GIFTFtpPriority' => 'Приоритет GIFT Ftp', + 'GIFVersion' => 'ВерÑÐ¸Ñ GIF', + 'GLPI_Unknown4' => 'GLPI – ÐеизвеÑтный 4', + 'GPRI_Unknown4' => 'GPRI – ÐеизвеÑтный – 4', + 'GPRI_Unknown5' => 'GPRI – ÐеизвеÑтный – 5', + 'GPRI_Unknown8' => 'GPRI – ÐеизвеÑтный – 8', + 'GPRI_Unknown9' => 'GPRI – ÐеизвеÑтный – 9', + 'GPSAltitude' => 'GPS – Ð’Ñ‹Ñота', + 'GPSAltitudeRaw' => 'GPS – Ð’Ñ‹Ñота – Raw', + 'GPSAltitudeRef' => { + Description => 'GPS – Ð˜Ð½Ð´ÐµÐºÑ Ð²Ñ‹Ñоты', + PrintConv => { + 'Above Sea Level' => 'Ðад уровнем морÑ', + 'Below Sea Level' => 'Ðиже ÑƒÑ€Ð¾Ð²Ð½Ñ Ð¼Ð¾Ñ€Ñ', + }, + }, + 'GPSAreaInformation' => 'GPS – Ðазвание облаÑти', + 'GPSDOP' => 'GPS – ТочноÑть измерениÑ', + 'GPSDateStamp' => 'GPS – Дата и времÑ', + 'GPSDateTime' => 'GPS – Дата/ВремÑ', + 'GPSDateTimeRaw' => 'GPS – Дата/Ð’Ñ€ÐµÐ¼Ñ â€“ Raw', + 'GPSDestBearing' => 'GPS – Пеленг объекта Ñъёмки', + 'GPSDestBearingRef' => { + Description => 'GPS – Ориентир пеленга объекта Ñъёмки', + PrintConv => { + 'Magnetic North' => 'Ðа магнитный Ñеверный полюÑ', + 'True North' => 'Ðа географичеÑкий Ñеверный полюÑ', + }, + }, + 'GPSDestDistance' => 'GPS – РаÑÑтоÑние до объекта Ñъёмки', + 'GPSDestDistanceRef' => { + Description => 'GPS – Единицы Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ñ€Ð°ÑÑтоÑниÑ', + PrintConv => { + 'Kilometers' => 'Километры', + 'Miles' => 'Мили', + 'Nautical Miles' => 'МорÑкие мили', + }, + }, + 'GPSDestLatitude' => 'GPS – Широта объекта Ñъёмки', + 'GPSDestLatitudeRef' => { + Description => 'GPS – Ð˜Ð½Ð´ÐµÐºÑ ÑˆÐ¸Ñ€Ð¾Ñ‚Ñ‹ объекта Ñъёмки', + PrintConv => { + 'North' => 'Ð¡ÐµÐ²ÐµÑ€Ð½Ð°Ñ ÑˆÐ¸Ñ€Ð¾Ñ‚Ð°', + 'South' => 'Ð®Ð¶Ð½Ð°Ñ ÑˆÐ¸Ñ€Ð¾Ñ‚Ð°', + }, + }, + 'GPSDestLongitude' => 'GPS – Долгота объекта Ñъёмки', + 'GPSDestLongitudeRef' => { + Description => 'GPS – Ð˜Ð½Ð´ÐµÐºÑ Ð´Ð¾Ð»Ð³Ð¾Ñ‚Ñ‹ объекта Ñъёмки', + PrintConv => { + 'East' => 'ВоÑÑ‚Ð¾Ñ‡Ð½Ð°Ñ Ð´Ð¾Ð»Ð³Ð¾Ñ‚Ð°', + 'West' => 'Ð—Ð°Ð¿Ð°Ð´Ð½Ð°Ñ Ð´Ð¾Ð»Ð³Ð¾Ñ‚Ð°', + }, + }, + 'GPSDifferential' => { + Description => 'GPS – Ð”Ð¸Ñ„Ñ„ÐµÑ€ÐµÐ½Ñ†Ð¸Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ð¾Ð¿Ñ€Ð°Ð²ÐºÐ°', + PrintConv => { + 'Differential Corrected' => 'Ð”Ð¸Ñ„Ñ„ÐµÑ€ÐµÐ½Ñ†Ð¸Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ð¾Ð¿Ñ€Ð°Ð²ÐºÐ° применена', + 'No Correction' => 'Измерение без дифференциальной поправки', + }, + }, + 'GPSHPositioningError' => 'GPS – Ошибка горизонтального позиционированиÑ', + 'GPSImgDirection' => 'GPS – ÐÐ°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ°Ð¼ÐµÑ€Ñ‹ при Ñъёмке', + 'GPSImgDirectionRef' => { + Description => 'GPS – Ориентир Ð½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ°Ð¼ÐµÑ€Ñ‹', + PrintConv => { + 'Magnetic North' => 'Ðа магнитный Ñеверный полюÑ', + 'True North' => 'Ðа географичеÑкий Ñеверный полюÑ', + }, + }, + 'GPSInfo' => 'IFD указатель информации GPS', + 'GPSLatitude' => 'GPS – Широта', + 'GPSLatitudeRaw' => 'GPS – Широта – Raw', + 'GPSLatitudeRef' => { + Description => 'GPS – Ð˜Ð½Ð´ÐµÐºÑ ÑˆÐ¸Ñ€Ð¾Ñ‚Ñ‹', + PrintConv => { + 'North' => 'Ð¡ÐµÐ²ÐµÑ€Ð½Ð°Ñ ÑˆÐ¸Ñ€Ð¾Ñ‚Ð°', + 'South' => 'Ð®Ð¶Ð½Ð°Ñ ÑˆÐ¸Ñ€Ð¾Ñ‚Ð°', + }, + }, + 'GPSLongitude' => 'GPS – Долгота', + 'GPSLongitudeRaw' => 'GPS – Долгота – Raw', + 'GPSLongitudeRef' => { + Description => 'GPS – Ð˜Ð½Ð´ÐµÐºÑ Ð´Ð¾Ð»Ð³Ð¾Ñ‚Ñ‹', + PrintConv => { + 'East' => 'ВоÑÑ‚Ð¾Ñ‡Ð½Ð°Ñ Ð´Ð¾Ð»Ð³Ð¾Ñ‚Ð°', + 'West' => 'Ð—Ð°Ð¿Ð°Ð´Ð½Ð°Ñ Ð´Ð¾Ð»Ð³Ð¾Ñ‚Ð°', + }, + }, + 'GPSLongtitude' => 'GPS – Долгота', + 'GPSMapDatum' => 'GPS – СиÑтема координат', + 'GPSMeasureMode' => { + Description => 'GPS – Режим Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ GPS', + PrintConv => { + '2-Dimensional Measurement' => '2-Ð¼ÐµÑ€Ð½Ð°Ñ Ð½Ð°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ', + '3-Dimensional Measurement' => '3-Ð¼ÐµÑ€Ð½Ð°Ñ Ð½Ð°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ', + }, + }, + 'GPSPosition' => 'GPS – МеÑтоположение', + 'GPSProcessingMethod' => 'GPS – Метод вычиÑÐ»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ', + 'GPSSatellites' => 'GPS – ИÑпользуемые Ñпутники', + 'GPSSpeed' => 'GPS – СкороÑть передвижениÑ', + 'GPSSpeed3D' => 'GPS – СкороÑть 3D', + 'GPSSpeedRaw' => 'GPS – ÑкороÑть – Raw', + 'GPSSpeedRef' => { + Description => 'GPS – Единицы Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ ÑкороÑти', + PrintConv => { + 'km/h' => 'Км/ч', + 'knots' => 'Узлы', + 'mph' => 'Миль/ч', + }, + }, + 'GPSSpeedX' => 'GPS – СкороÑть по X', + 'GPSSpeedY' => 'GPS – СкороÑть по Y', + 'GPSSpeedZ' => 'GPS – СкороÑть по Z', + 'GPSStatus' => { + Description => 'GPS – СоÑтоÑние приёмника во Ð²Ñ€ÐµÐ¼Ñ Ñъёмки', + PrintConv => { + 'Measurement Active' => 'Координаты актуальные', + 'Measurement Void' => 'Ðктуальных координат нету', + }, + }, + 'GPSTimeStamp' => 'GPS – Ð’Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿Ð¸Ñанных координат', + 'GPSTrack' => 'GPS – Трек', + 'GPSTrackRaw' => 'GPS – Трек – Raw', + 'GPSTrackRef' => { + Description => 'GPS – Ориентир направлениÑ', + PrintConv => { + 'Magnetic North' => 'Ðа магнитный Ñеверный полюÑ', + 'True North' => 'Ðа географичеÑкий Ñеверный полюÑ', + }, + }, + 'GPSVersionID' => 'GPS – ВерÑÐ¸Ñ Ñ‚ÐµÐ³Ð¾Ð²', + 'GainControl' => { + Description => 'Управление уÑилением', + PrintConv => { + 'High gain down' => 'С большим шагом вниз', + 'High gain up' => 'С большим шагом вверх', + 'Low gain down' => 'С малым шагом вниз', + 'Low gain up' => 'С малым шагом вверх', + 'None' => 'ОтÑутÑтвует', + }, + }, + 'Gamma' => 'Гамма', + 'GammaBlue' => 'Гамма Синего', + 'GammaCompensatedValue' => 'Значение гамма-компенÑации', + 'GammaGreen' => 'Гамма Зелёного', + 'GammaRed' => 'Гамма КраÑного', + 'Gapless' => { + Description => 'Без зазора', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'Gender' => 'Пол', + 'Generator' => 'Сгенерирован в', + 'GeneratorVersion' => 'ВерÑÐ¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ', + 'Genr' => 'Жанр', + 'Genre' => 'Жанр', + 'GeoTiffAsciiParams' => 'Geo Tiff – Параметры Ascii', + 'GeoTiffDirectory' => 'Geo Tiff – Каталог', + 'GeoTiffDoubleParams' => 'Geo Tiff – Параметры дублированиÑ', + 'Geolocation' => 'ГеолокациÑ', + 'GeologicalContext' => 'ГеологичеÑкий контекÑÑ‚', + 'GeologicalContextBed' => 'Ðазвание литоÑтратиграфичеÑкого ÑлоÑ', + 'GeologicalContextFormation' => 'Ðазвание литоÑтратиграфичеÑкой Ñвиты', + 'GeologicalContextGroup' => 'Ðазвание литоÑтратиграфичеÑкой Ñерии', + 'GeologicalContextID' => 'ID геологичеÑкого контекÑта', + 'GeologicalContextMember' => 'Ðазвание литоÑтратиграфичеÑкой пачки', + 'Geosync' => 'Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð¸ меÑтоположениÑ', + 'Geotag' => 'Журнал пройденного маршрута', + 'Geotime' => 'Дата/Ð’Ñ€ÐµÐ¼Ñ Ð¼ÐµÑтоположениÑ', + 'GimbalPitchDegree' => 'Тангаж шарнира (наклон)', + 'GimbalReverse' => 'Оборот шарнира', + 'GimbalRollDegree' => 'Крен шарнира (вращение)', + 'GimbalYawDegree' => 'РыÑкание шарнира (поворот)', + 'GlobalAltitude' => 'Ð“Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð°Ñ Ð²Ñ‹Ñота', + 'GlobalAngle' => 'Глобальный угол', + 'GoogleBot' => 'ПоиÑковый робот Google', + 'GooglePlusUploadCode' => 'Код загрузки Google Plus', + 'Gradation' => 'ВпечатлÑющ.режим', + 'GrayPoint' => 'Точка Ñерого', + 'GrayResponseCurve' => 'ОптичеÑÐºÐ°Ñ Ð¿Ð»Ð¾Ñ‚Ð½Ð¾Ñть Ñерого', + 'GrayResponseUnit' => 'Единицы плотноÑти Ñерого', + 'GreenEndpoint' => 'ÐšÐ¾Ð½ÐµÑ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° Зелёного', + 'GreenMask' => 'МаÑка Зелёного', + 'GreenPrimary' => 'ОÑновной зелёный', + 'GreenX' => 'Зелёный по X', + 'GreenY' => 'Зелёный по Y', + 'GridGuidesInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ Ñетке и направлÑющих', + 'Gyroscope' => 'ГироÑкоп', + 'HCUsage' => 'Тип информации HC-файла', + 'HDContent' => { + Description => 'Контент HD', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'HDR' => { + Description => 'ÐвтоматичеÑкий HDR', + PrintConv => { + 'Off' => 'Ðе включён', + }, + }, + 'HDRImageType' => { + Description => 'Тип HDR изображениÑ', + PrintConv => { + 'HDR Image' => 'HDR изображение', + 'Original Image' => 'Оригинальное изображение', + }, + }, + 'HDRSetting' => 'Праметры HDR', + 'HDRToningInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ HDR-тонировании', + 'HTTPHostHeader' => 'Заголовок HTTP-хоÑта', + 'HalftoneHints' => 'Ð£ÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‚Ð¾Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ', + 'HardLink' => 'ЖёÑÑ‚ÐºÐ°Ñ ÑÑылка', + 'HasAudio' => 'Имеет аудио', + 'HasColorMap' => { + Description => 'Имеет цветовую карту', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'HasCuePoints' => 'Имеет ключевые точки', + 'HasKeyFrames' => 'Имеет ключевые кадры', + 'HasMetadata' => 'Имеет метаданные', + 'HasRealMergedData' => { + Description => 'Имеет Ñовмещённые данные', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'HasVideo' => 'Имеет видео', + 'HasXFA' => { + Description => 'XFA', + PrintConv => { + 'No' => 'ОтÑутÑтвует', + 'Yes' => 'ЕÑть', + }, + }, + 'HeaderSize' => 'Размер заголовка', + 'HeaderVersion' => 'ВерÑÐ¸Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ¾Ð²', + 'Headline' => 'Заголовок', + 'HeightResolution' => 'Разрешение по выÑоте (PPI)', + 'HierarchicalKeywords' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов', + 'HierarchicalKeywords1' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 1', + 'HierarchicalKeywords1Applied' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 1 – Применена', + 'HierarchicalKeywords1Children' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 1 – ДочернÑÑ', + 'HierarchicalKeywords2' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 2', + 'HierarchicalKeywords2Applied' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 2 – Применена', + 'HierarchicalKeywords2Children' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 2 – ДочернÑÑ', + 'HierarchicalKeywords3' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 3', + 'HierarchicalKeywords3Applied' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 3 – Применена', + 'HierarchicalKeywords3Children' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 3 – ДочернÑÑ', + 'HierarchicalKeywords4' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 4', + 'HierarchicalKeywords4Applied' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 4 – Применена', + 'HierarchicalKeywords4Children' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 4 – ДочернÑÑ', + 'HierarchicalKeywords5' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 5', + 'HierarchicalKeywords5Applied' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 5 – Применена', + 'HierarchicalKeywords5Children' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 5 – ДочернÑÑ', + 'HierarchicalKeywords6' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 6', + 'HierarchicalKeywords6Applied' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… Ñлов 6 – Применена', + 'HierarchicalSubject' => 'Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ Ñ‚ÐµÐ¼', + 'HighISOMultiplierBlue' => 'Множитель ISO – Синий', + 'HighISOMultiplierGreen' => 'Множитель ISO – Зелёный', + 'HighISOMultiplierRed' => 'Множитель ISO – КраÑный', + 'HighISONoiseReduction' => { + Description => 'Шумоподавление при выÑоком ISO', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкое', + 'High' => 'Сильное', + 'Low' => 'Слабое', + 'Normal' => 'Стандартное', + 'Off' => 'Ðе включено', + 'Standard' => 'Стандартное', + 'Strong' => 'Сильное', + }, + }, + 'HighestBiostratigraphicZone' => 'Ð’Ñ‹ÑÑˆÐ°Ñ Ð±Ð¸Ð¾ÑтратиграфичеÑÐºÐ°Ñ Ð·Ð¾Ð½Ð°', + 'HighlightColorDistortReduct' => { + Description => 'Уменьшение иÑкажений Ñветлых цветов', + PrintConv => { + 'Advanced' => 'РаÑширенный', + 'Standard' => 'Стандартный', + }, + }, + 'Highlights' => 'Света', + 'HighlightsAdj' => 'Регулировка Ñветов', + 'History' => 'ИÑториÑ', + 'HistoryBufferSize' => 'Размер буфера иÑтории', + 'HostComputer' => 'Компьютер/СиÑтема', + 'HotKey' => { + Description => 'ГорÑÑ‡Ð°Ñ ÐºÐ»Ð°Ð²Ð¸ÑˆÐ°', + PrintConv => { + '(none)' => 'ОтÑутÑтвует', + }, + }, + 'HowPublished' => 'Метод публикации', + 'Hue' => 'Оттенок', + 'HueAdj' => 'Регулировка оттенка', + 'HumanObservation' => 'Визуальное наблюдение', + 'HumanObservationDay' => 'День меÑÑца визуального наблюдениÑ', + 'HumanObservationEarliestDate' => 'Дата начала визуального наблюдениÑ', + 'HumanObservationEndDayOfYear' => 'День года Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð²Ð¸Ð·ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ наблюдениÑ', + 'HumanObservationEventDate' => 'Дата визуального наблюдениÑ', + 'HumanObservationEventID' => 'ID визуального наблюдениÑ', + 'HumanObservationEventRemarks' => 'Комментарии к визуальному наблюдению', + 'HumanObservationEventTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð²Ð¸Ð·ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ наблюдениÑ', + 'HumanObservationFieldNotes' => 'Полевые заметки о визуальном наблюдении', + 'HumanObservationFieldNumber' => 'Ðомер полевых заметок о визуальном наблюдении', + 'HumanObservationHabitat' => 'Ðреал визуального наблюдениÑ', + 'HumanObservationLatestDate' => 'Дата Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð²Ð¸Ð·ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ наблюдениÑ', + 'HumanObservationMonth' => 'МеÑÑц визуального наблюдениÑ', + 'HumanObservationParentEventID' => 'ID оÑновного визуального наблюдениÑ', + 'HumanObservationSampleSizeUnit' => 'Визуальное наблюдение – Единицы размера', + 'HumanObservationSampleSizeValue' => 'Визуальное наблюдение – Размер образца', + 'HumanObservationSamplingEffort' => 'Визуальное наблюдение – Затраченные уÑилиÑ', + 'HumanObservationSamplingProtocol' => 'Визуальное наблюдение – Метод иÑÑледованиÑ', + 'HumanObservationStartDayOfYear' => 'День года начала визуального наблюдениÑ', + 'HumanObservationVerbatimEventDate' => 'Дата визуального Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð´Ð¾Ñловно', + 'HumanObservationYear' => 'Год визуального наблюдениÑ', + 'Humidity' => 'ВлажноÑть', + 'HyperfocalDistance' => 'Гиперфокальное раÑÑтоÑние', + 'HyperlinkBase' => 'База гиперÑÑылок', + 'ICCProfileName' => 'Ðазвание ICC-профилÑ', + 'ICC_Profile' => 'ICC-профиль', + 'ICC_Untagged' => 'ICC без метки', + 'ID3Size' => 'Размер блока данных ID3', + 'IDCCreativeStyle' => { + Description => 'IDC – ТворчеÑкий Ñтиль', + PrintConv => { + 'A100 Standard' => 'Стандарт A100', + 'Autumn Leaves' => 'ОÑенние лиÑтьÑ', + 'B&W' => 'Чёрно-Белое', + 'Camera Setting' => 'ÐаÑтройка камеры', + 'Clear' => 'ЧиÑтый', + 'Deep' => 'Глубокий', + 'Landscape' => 'Пейзаж', + 'Light' => 'Светлый', + 'Neutral' => 'Ðейтральный', + 'Night View' => 'Ðочной вид', + 'Portrait' => 'Портрет', + 'Real' => 'Реальный', + 'Sepia' => 'СепиÑ', + 'Standard' => 'Стандарт', + 'Sunset' => 'Закат', + 'Vivid' => 'Яркий', + }, + }, + 'IDCPreviewImage' => 'IDC – файл предпроÑмотра', + 'IDCPreviewLength' => 'IDC – Строк в файле предпроÑмотра', + 'IDCPreviewStart' => 'IDC – Ðачало файла предпроÑмотра', + 'IDsBaseValue' => 'ОÑновные Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¸Ð½Ð´Ð¸ÐºÐ°Ñ‚Ð¾Ñ€Ð¾Ð²', + 'INGRReserved' => 'Intergraph – Резерв', + 'IPTCBitsPerSample' => 'IPTC – КоличеÑтво бит на компонент', + 'IPTCDigest' => 'Хеш IPTC', + 'IPTCImageHeight' => 'IPTC – Ð’Ñ‹Ñота изображениÑ', + 'IPTCImageRotation' => { + Description => 'IPTC – Вращение изображениÑ', + PrintConv => { + '0' => 'Ðе вращать', + '180' => 'Повернуть вправо на 180°', + '270' => 'Повернуть вправо на 270°', + '90' => 'Повернуть вправо на 90°', + }, + }, + 'IPTCImageWidth' => 'IPTC – Ширина изображениÑ', + 'IPTCPictureNumber' => 'IPTC – Ðомер изображениÑ', + 'IPTCPixelHeight' => 'IPTC – Ð’Ñ‹Ñота (в пикÑелÑÑ…)', + 'IPTCPixelWidth' => 'IPTC – Ширина (в пикÑелÑÑ…)', + 'ISOSetting' => { + Description => 'ISO2', + PrintConv => { + '200 (Zone Matching High)' => '200 (СоглаÑование зон. Ð’ выÑоком ключе)', + '80 (Zone Matching Low)' => '80 (СоглаÑование зон. Ð’ низком ключе)', + 'Auto' => 'ÐвтоматичеÑки', + }, + }, + 'ISOSpeed' => 'ЧувÑтвительноÑть ISO', + 'ISOSpeedLatitudeyyy' => 'ISO – ФотографичеÑÐºÐ°Ñ ÑˆÐ¸Ñ€Ð¾Ñ‚Ð° yyy', + 'ISOSpeedLatitudezzz' => 'ISO – ФотографичеÑÐºÐ°Ñ ÑˆÐ¸Ñ€Ð¾Ñ‚Ð° zzz', + 'ISOSpeeds' => 'ISO4', + 'ISRCNumber' => 'Ðомер ISRC', + 'IT8Header' => 'IT8-заголовок', + 'IconFileName' => 'Ðазвание файла значка', + 'IconIndex' => 'Ð˜Ð½Ð´ÐµÐºÑ Ð¸ÐºÐ¾Ð½ÐºÐ¸', + 'Identification' => 'Определение', + 'IdentificationID' => 'ID определениÑ', + 'IdentificationQualifier' => 'Уточнение к определению', + 'IdentificationReferences' => 'ИÑточники иÑпользованные Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ', + 'IdentificationRemarks' => 'Комментарии к определению', + 'IdentificationVerificationStatus' => 'Код Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ', + 'IdentifiedBy' => 'Люди определившие образец', + 'Identifier' => 'Идентификатор', + 'Illumination' => { + Description => 'ПодÑветка', + PrintConv => { + 'Off' => 'Ðе включена', + 'On' => 'Включена', + }, + }, + 'Image2Description' => 'Изображение 2 – ОпиÑание', + 'Image3Description' => 'Изображение 3 – ОпиÑание', + 'Image4Description' => 'Изображение 4 – ОпиÑание', + 'Image5Description' => 'Изображение 5 – ОпиÑание', + 'Image6Description' => 'Изображение 6 – ОпиÑание', + 'Image7Description' => 'Изображение 7 – ОпиÑание', + 'Image8Description' => 'Изображение 8 – ОпиÑание', + 'Image::ExifTool::AIFF::Comment' => 'Комментарии AIFF', + 'Image::ExifTool::AIFF::Common' => 'Общее AIFF', + 'Image::ExifTool::AIFF::FormatVers' => 'ВерÑÐ¸Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð° AIFF', + 'Image::ExifTool::APE::NewHeader' => 'Ðовый заголовок APE', + 'Image::ExifTool::APE::OldHeader' => 'Старый заголовок APE', + 'Image::ExifTool::APP12::PictureInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ APP12', + 'Image::ExifTool::Apple::RunTime' => 'Apple – Ð’Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ', + 'Image::ExifTool::Audible::cvrx' => 'Обложка Audible', + 'Image::ExifTool::Audible::meta' => 'Метаданные Audible', + 'Image::ExifTool::BPG::Extensions' => 'РаÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ BPG', + 'Image::ExifTool::DjVu::Form' => 'Форма DjVu', + 'Image::ExifTool::DjVu::Info' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ DjVu', + 'Image::ExifTool::DjVu::Meta' => 'DjVu метаданные', + 'Image::ExifTool::FLAC::Picture' => 'Изображение FLAC', + 'Image::ExifTool::FLAC::StreamInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ потоке FLAC', + 'Image::ExifTool::Flash::Audio' => 'Flash аудио', + 'Image::ExifTool::Flash::Meta' => 'Flash метаданные', + 'Image::ExifTool::Flash::Video' => 'Flash видео', + 'Image::ExifTool::Font::Main' => 'Шрифт', + 'Image::ExifTool::Font::Name' => 'Ðазвание шрифта', + 'Image::ExifTool::Font::PFM' => 'Шрифт PFM', + 'Image::ExifTool::Font::PSInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ PostScript шрифте', + 'Image::ExifTool::FotoStation::SoftEdit' => 'FotoStation – Редактор', + 'Image::ExifTool::GE::Main' => 'General Imaging (GE)', + 'Image::ExifTool::GIF::Animation' => 'GIF анимациÑ', + 'Image::ExifTool::GIF::Extensions' => 'GIF РаÑширениÑ', + 'Image::ExifTool::GIF::MIDIControl' => 'GIF MIDIControl', + 'Image::ExifTool::GIF::Main' => 'GIF', + 'Image::ExifTool::GIF::Screen' => 'GIF Screen', + 'Image::ExifTool::GIMP::Header' => 'Заголовок GIMP', + 'Image::ExifTool::GIMP::Parasite' => 'GIMP – Паразитный Ñлемент', + 'Image::ExifTool::GIMP::Resolution' => 'GIMP – Разешение', + 'Image::ExifTool::HP::Type2' => 'HP – Тип 2', + 'Image::ExifTool::HP::Type4' => 'HP – Тип 4', + 'Image::ExifTool::HP::Type6' => 'HP – Тип 6', + 'Image::ExifTool::IPTC::ObjectData' => 'IPTC – Данные объекта', + 'Image::ExifTool::IPTC::PostObjectData' => 'IPTC – Данные Post объекта', + 'Image::ExifTool::IPTC::PreObjectData' => 'IPTC – Данные Pre объекта', + 'Image::ExifTool::ITC::Header' => 'Заголовок ITC', + 'Image::ExifTool::ITC::Item' => 'Элемент ITC', + 'Image::ExifTool::JFIF::Extension' => 'JFIF Дополнение', + 'Image::ExifTool::JVC::Text' => 'JVC – ТекÑÑ‚', + 'Image::ExifTool::Jpeg2000::ColorSpec' => 'Jpeg2000 – Ð¡Ð¿ÐµÑ†Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ†Ð²ÐµÑ‚Ð°', + 'Image::ExifTool::Jpeg2000::DisplayResolution' => 'Jpeg2000 – Разрешение Ñкрана', + 'Image::ExifTool::Jpeg2000::FileType' => 'Jpeg2000 – Тип файла', + 'Image::ExifTool::Jpeg2000::ImageHeader' => 'Jpeg2000 – Заголовок изображениÑ', + 'Image::ExifTool::MPEG::Audio' => 'MPEG аудио', + 'Image::ExifTool::MPEG::Video' => 'MPEG видео', + 'Image::ExifTool::Microsoft::MP' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ Microsoft Photo', + 'Image::ExifTool::PDF::Encrypt' => 'PDF зашифрован', + 'Image::ExifTool::PDF::Info' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ PDF-файле', + 'Image::ExifTool::PDF::Pages' => 'Страниц в PDF', + 'Image::ExifTool::PDF::Root' => 'ОÑновные параметры PDF', + 'Image::ExifTool::PDF::Signature' => 'Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ PDF', + 'Image::ExifTool::PDF::TransformParams' => 'Параметры Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ PDF-файла', + 'Image::ExifTool::PNG::ImageHeader' => 'Заголовки PNG-изображениÑ', + 'Image::ExifTool::PNG::PrimaryChromaticities' => 'ОÑновные цвета PNG', + 'Image::ExifTool::PNG::StereoImage' => 'Стереоизображние PNG', + 'Image::ExifTool::PSP::Creator' => 'Создатель PSP', + 'Image::ExifTool::PSP::Image' => 'Изображение PSP', + 'Image::ExifTool::PhaseOne::SensorCalibration' => 'PhaseOne – ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора', + 'Image::ExifTool::PhotoMechanic::SoftEdit' => 'PhotoMechanic', + 'Image::ExifTool::Photoshop::Header' => 'Заголовок Photoshop', + 'Image::ExifTool::Photoshop::JPEG_Quality' => 'КачеÑтво JPEG в Photoshop', + 'Image::ExifTool::Photoshop::Layers' => 'Слои Photoshop', + 'Image::ExifTool::Photoshop::PrintScaleInfo' => 'Photoshop – МаÑштабирование при печати', + 'Image::ExifTool::Photoshop::Resolution' => 'Photoshop – Разрешение', + 'Image::ExifTool::PrintIM::Main' => 'Print Image Matching (PrintIM)', + 'Image::ExifTool::RTF::Main' => 'RTF', + 'Image::ExifTool::Rawzor::Main' => 'Rawzor', + 'Image::ExifTool::Real::Metadata' => 'Метаданные Real', + 'Image::ExifTool::Real::Metafile' => 'Метафайл Real', + 'Image::ExifTool::Real::Properties' => 'СвойÑтва Real', + 'Image::ExifTool::Sanyo::Thumbnail' => 'Миниатюра Sanyo', + 'Image::ExifTool::Stim::CropX' => 'Stim – Обрезка по X', + 'Image::ExifTool::Stim::CropY' => 'Stim – Обрезка по Y', + 'Image::ExifTool::Theora::Identification' => 'Ð˜Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Theora', + 'Image::ExifTool::Torrent::Files' => 'Торрент-файлы', + 'Image::ExifTool::Torrent::Info' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ торренте', + 'Image::ExifTool::Torrent::Main' => 'Торрент', + 'Image::ExifTool::Torrent::Profiles' => 'Торрент профили', + 'Image::ExifTool::Vorbis::Comments' => 'Комментарии Vorbis', + 'Image::ExifTool::Vorbis::Identification' => 'Идентификатор Vorbis', + 'Image::ExifTool::ZIP::GZIP' => 'ZIP GZIP', + 'Image::ExifTool::ZIP::Main' => 'ZIP', + 'Image::ExifTool::ZIP::RAR' => 'ZIP RAR', + 'ImageAlterationConstraints' => { + Description => 'Ограничение на редактирование', + PrintConv => { + 'No Colorization' => 'Ðе раÑкрашивать', + 'No Cropping' => 'Ðе кадрировать', + 'No De-Colorization' => 'Ðе обеÑцвечивать', + 'No Flipping' => 'Ðе отражать', + 'No Merging' => 'Ðе объединÑть', + 'No Retouching' => 'Ðе ретушировать', + }, + }, + 'ImageArrangement' => { + Description => 'РаÑположение Ñтереоизображений', + PrintConv => { + 'Cross View Alignment' => 'ПереÑечное раÑположение', + 'Parallel View Alignment' => 'Параллельное раÑположение', + }, + }, + 'ImageByteCount' => 'Размер Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ (байт)', + 'ImageColorIndicator' => { + Description => 'Цвет переднего плана или прозрачноÑти', + PrintConv => { + 'Specified Image Color' => 'Указан', + 'Unspecified Image Color' => 'Ðе указан', + }, + }, + 'ImageColorValue' => 'Значение цвета переднего плана или прозрачноÑти', + 'ImageCreator' => 'Создатель изображеениÑ', + 'ImageCreatorID' => 'PLUS-ID ÑÐ¾Ð·Ð´Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ', + 'ImageCreatorImageID' => 'ID Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ñвоенный Ñоздателем', + 'ImageCreatorName' => 'Ð˜Ð¼Ñ ÑÐ¾Ð·Ð´Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ', + 'ImageData' => 'Данные изображениÑ', + 'ImageDataDiscard' => { + Description => 'Отброшенные данные изображениÑ', + PrintConv => { + 'Flexbits Discarded' => 'Гибкие биты отброшены', + 'Full Resolution' => 'Полное разрешение', + 'HighPass Frequency Data Discarded' => 'Данные выÑоких чаÑтот отброшены', + 'Highpass and LowPass Frequency Data Discarded' => 'Данные выÑоких и низких чаÑтот отброшены', + }, + }, + 'ImageDepth' => 'Глубина изображениÑ', + 'ImageDescription' => 'ОпиÑание изображениÑ', + 'ImageDuplicationConstraints' => { + Description => 'Ограничение на дублирование', + PrintConv => { + 'Duplication Only as Necessary Under License' => 'Дублировать только при необходимоÑти в ÑоответÑтвии Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸ÐµÐ¹', + 'No Duplication' => 'Дублирование запрещено', + 'No Duplication Constraints' => 'ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð½Ð° дублирование отÑутÑтвуют', + }, + }, + 'ImageElements' => 'Элементы изображениÑ', + 'ImageFileConstraints' => { + Description => 'ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð½Ð° файл', + PrintConv => { + 'Maintain File Name' => 'ЗапрещаетÑÑ Ð¿ÐµÑ€ÐµÐ¸Ð¼ÐµÐ½Ð¾Ð²Ð°Ð½Ð¸Ðµ файла', + 'Maintain File Type' => 'ЗапрещаетÑÑ Ð¸Ð·Ð¼ÐµÐ½Ñть тип файла', + 'Maintain ID in File Name' => 'ЗапрещаетÑÑ Ð¸Ð·Ð¼ÐµÐ½Ñть ID в имени файла', + 'Maintain Metadata' => 'ЗапрещаетÑÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ðµ метаданных', + }, + }, + 'ImageFileFormatAsDelivered' => { + Description => 'ИÑходный формат файла', + PrintConv => { + 'Other' => 'Другой формат', + }, + }, + 'ImageFileName' => 'Ðазвание файла изображениÑ', + 'ImageFileSizeAsDelivered' => { + Description => 'ИÑходный размер файла', + PrintConv => { + 'Greater than 50 MB' => 'Более 50 МБ', + 'Up to 1 MB' => 'До 1 МБ', + 'Up to 10 MB' => 'До 10 МБ', + 'Up to 30 MB' => 'До 30 МБ', + 'Up to 50 MB' => 'До 50 МБ', + }, + }, + 'ImageFullHeight' => 'ÐŸÐ¾Ð»Ð½Ð°Ñ Ð²Ñ‹Ñота изображениÑ', + 'ImageFullWidth' => 'ÐŸÐ¾Ð»Ð½Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ð° изображениÑ', + 'ImageHeight' => 'Ð’Ñ‹Ñота изображениÑ', + 'ImageHistory' => 'ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ', + 'ImageID' => 'ID изображениÑ', + 'ImageLayer' => 'Слой изображениÑ', + 'ImageLength' => 'Строк в изображении', + 'ImageLimitExposureBias' => 'Лимит поправки ÑкÑпозиции', + 'ImageMimeType' => 'MIME-тип изображениÑ', + 'ImageModulationExposureBias' => 'МодулÑÑ†Ð¸Ñ Ð¿Ð¾Ð¿Ñ€Ð°Ð²ÐºÐ¸ ÑкÑпозиции', + 'ImageNumber' => 'Ðомер изображениÑ', + 'ImageOffset' => 'Смещение изображениÑ', + 'ImageOrientation' => { + Description => 'ÐžÑ€Ð¸ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ', + PrintConv => { + 'Landscape' => 'ÐльбомнаÑ', + 'Portrait' => 'ПортретнаÑ', + 'Square' => 'КвадратнаÑ', + }, + }, + 'ImageRank' => 'Рейтинг изображениÑ', + 'ImageReadyDataSets' => 'Ðаборы данных', + 'ImageReadyVariables' => 'Переменные', + 'ImageReferencePoints' => 'Опорные точки изображениÑ', + 'ImageResolution' => 'Разрешение изображениÑ', + 'ImageRotation' => { + Description => 'Поворот/Ð ÐµÐ²ÐµÑ€Ñ Ñ‚Ð¾Ñ‡ÐµÐº проÑмотра изображениÑ', + PrintConv => { + 'None' => 'Ðет', + }, + }, + 'ImageSensorGain' => 'КоÑффициент уÑÐ¸Ð»ÐµÐ½Ð¸Ñ Ð´Ð°Ñ‚Ñ‡Ð¸ÐºÐ° изображениÑ', + 'ImageSize' => 'Размер изображениÑ', + 'ImageSourceData' => 'ИÑходные данные изображениÑ', + 'ImageStabilization' => { + PrintConv => { + 'CCD Shift' => 'ОптичеÑкий Ñтабилизатор', + 'High Sensitivity' => 'Движение объекта Ñъёмки', + 'Off' => 'Выкл', + 'Off (1)' => 'Выкл (1)', + }, + }, + 'ImageSupplier' => 'ПоÑтавщик изображениÑ', + 'ImageSupplierID' => 'PLUS-ID поÑтавщика изображениÑ', + 'ImageSupplierImageID' => 'ID Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ñвоенный поÑтавщиком', + 'ImageSupplierName' => 'Ð˜Ð¼Ñ Ð¿Ð¾Ñтавщика изображениÑ', + 'ImageToolbar' => 'Панель команд Ð´Ð»Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ', + 'ImageType' => { + Description => 'Тип изображениÑ', + PrintConv => { + 'Grayscale (interlaced)' => 'Ð“Ñ€Ð°Ð´Ð°Ñ†Ð¸Ñ Ñерого (череÑÑтрочнаÑ)', + 'Grayscale (non-interlaced)' => 'Ð“Ñ€Ð°Ð´Ð°Ñ†Ð¸Ñ Ñерого (без чередованиÑ)', + 'Grayscale Animation (interlaced)' => 'ÐÐ½Ð¸Ð¼Ð°Ñ†Ð¸Ñ Ð² градациÑÑ… Ñерого (череÑÑтрочнаÑ)', + 'Grayscale Animation (non-interlaced)' => 'ÐÐ½Ð¸Ð¼Ð°Ñ†Ð¸Ñ Ð² градациÑÑ… Ñерого (без чередованиÑ)', + 'Illustrated Image' => 'ГрафичеÑкое изображение', + 'Multimedia or Composited Image' => 'Мультимедийное или композитное изображение', + 'Other' => 'Другой тип', + 'Page' => 'Страница', + 'Photographic Image' => 'ФотографиÑ', + 'Preview' => 'ПредпроÑмотр', + 'RGB (interlaced)' => 'RGB (череÑÑтрочнаÑ)', + 'RGB (non-interlaced)' => 'RGB (без чередованиÑ)', + 'RGB Animation (interlaced)' => 'ÐÐ½Ð¸Ð¼Ð°Ñ†Ð¸Ñ RGB (череÑÑтрочнаÑ)', + 'RGB Animation (non-interlaced)' => 'ÐÐ½Ð¸Ð¼Ð°Ñ†Ð¸Ñ RGB (без чередованиÑ)', + 'RGBA (interlaced)' => 'RGBA (череÑÑтрочнаÑ)', + 'RGBA (non-interlaced)' => 'RGBA (без чередованиÑ)', + 'RGBA Animation (interlaced)' => 'ÐÐ½Ð¸Ð¼Ð°Ñ†Ð¸Ñ RGBA (череÑÑтрочнаÑ)', + 'RGBA Animation (non-interlaced)' => 'ÐÐ½Ð¸Ð¼Ð°Ñ†Ð¸Ñ RGBA (без чередованиÑ)', + 'Video' => 'Видео', + }, + }, + 'ImageUIDList' => 'СпиÑок уникальных ID изображений', + 'ImageUniqueID' => 'Уникальный ID изображениÑ', + 'ImageWidth' => 'Ширина изображениÑ', + 'Importance' => { + Description => 'ВажноÑть', + PrintConv => { + 'High' => 'Ð’Ñ‹ÑокаÑ', + 'Low' => 'ÐизкаÑ', + 'Normal' => 'ОбычнаÑ', + }, + }, + 'Imprint' => 'ИздательÑтво', + 'InclinationAngle' => 'Угол наклона', + 'InclinationCorrection' => { + Description => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð½Ð°ÐºÐ»Ð¾Ð½Ð°', + PrintConv => { + 'Off' => 'Ðе включена', + 'On' => 'Включена', + }, + }, + 'IncludedFileID' => 'ID вÑтроенного файла', + 'Index' => 'ИндекÑ', + 'IndexOffset' => 'Ð˜Ð½Ð´ÐµÐºÑ ÑмещениÑ', + 'Indexable' => { + Description => 'ИндекÑированный', + PrintConv => { + 'False' => 'Ðет', + 'True' => 'Да', + }, + }, + 'Indexed' => { + Description => 'ИндекÑированный', + PrintConv => { + 'Indexed' => 'Да', + 'Not indexed' => 'Ðет', + }, + }, + 'IndexedColorTableCount' => 'КоличеÑтво индекÑированных цветов', + 'InfraredIlluminator' => { + Description => 'ИнфракраÑÐ½Ð°Ñ Ð¿Ð¾Ð´Ñветка', + PrintConv => { + 'Off' => 'Ðе включена', + 'On' => 'Включена', + }, + }, + 'InitialCameraDolly' => 'ИÑходное положение вдоль линии взглÑда', + 'InitialDisplayEffect' => { + Description => 'Ðачальный Ñффект отображениÑ', + PrintConv => { + 'Off' => 'Ðе включён', + 'On' => 'Включен', + }, + }, + 'InitialHorizontalFOVDegrees' => 'ИÑходное горизонтальное поле Ð·Ñ€ÐµÐ½Ð¸Ñ (°)', + 'InitialViewHeadingDegrees' => 'КурÑовой угол Ð´Ð»Ñ Ð¸Ñходного предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ (°)', + 'InitialViewPitchDegrees' => 'Угол продольного наклона Ð´Ð»Ñ Ð¸Ñходного предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ (°)', + 'InitialViewRollDegrees' => 'Угол поперечного наклона Ð´Ð»Ñ Ð¸Ñходного предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ (°)', + 'InkNames' => 'ÐÐ°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ‡ÐµÑ€Ð½Ð¸Ð»', + 'InkSet' => { + Description => 'Ðабор чернил', + PrintConv => { + 'Not CMYK' => 'Ðе CMYK', + }, + }, + 'InputDeviceName' => 'Ðазвание уÑтройÑтва ввода', + 'InputDeviceSerialNumber' => 'Серийный номер уÑтройÑтва ввода', + 'InsertMode' => 'Режим вÑтавки', + 'InstanceType' => { + Description => 'Тип ÑобытиÑ', + PrintConv => { + 'Exception to Recurring Appointment' => 'ИÑключение Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÑющегоÑÑ ÑобытиÑ', + 'Non-recurring Appointment' => 'ÐеповторÑющиеÑÑ Ñобытие', + 'Recurring Appointment' => 'ПовторÑющиеÑÑ Ñобытие', + 'Single Instance of Recurring Appointment' => 'Событие Ñ ÐµÐ´Ð¸Ð½Ð¾ÐºÑ€Ð°Ñ‚Ð½Ñ‹Ð¼ повторением', + }, + }, + 'Institution' => 'ИнÑтитут', + 'InstructionSet' => 'Ðабор инÑтрукций', + 'Instructions' => 'ИнÑтрукции', + 'Instrument' => 'ИнÑтрумент', + 'IntendedBusyStatus' => 'Предполагаемый ÑÑ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ð½ÑтоÑти', + 'IntensityStereo' => 'ИнтенÑивное Ñтерео', + 'InterchangeColorSpace' => { + Description => 'Смена цветового проÑтранÑтва', + PrintConv => { + 'CMY (K) Device Dependent' => 'Ðппаратно-завиÑимый CMY (K)', + 'RGB Device Dependent' => 'Ðппаратно-завиÑимый RGB', + }, + }, + 'IntergraphFlagRegisters' => 'Intergraph – Флаг региÑтров', + 'IntergraphMatrix' => 'Intergraph – Матрица', + 'IntergraphPacketData' => 'Intergraph – Данные пакета', + 'Interlace' => { + Description => 'ЧереÑÑÑ‚Ñ€Ð¾Ñ‡Ð½Ð°Ñ Ñ€Ð°Ð·Ð²Ñ‘Ñ€Ñ‚ÐºÐ°', + PrintConv => { + 'Adam7 Interlace' => 'ЧереÑÑÑ‚Ñ€Ð¾Ñ‡Ð½Ð°Ñ Adam7', + 'Noninterlaced' => 'ПрогреÑÑивнаÑ', + }, + }, + 'InternalIDNumber' => 'Внутренний ID-номер', + 'InternalLeading' => 'Внутренний зазор', + 'InternalVersionNumber' => 'ВнутреннÑÑ Ð²ÐµÑ€ÑÐ¸Ñ Ñ„Ð°Ð¹Ð»Ð°', + 'InteropIndex' => { + Description => 'Ð˜Ð½Ð´ÐµÐºÑ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ð¹ ÑовмеÑтимоÑти', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03 – Дополнительный файл DCF (Adobe RGB)', + 'R98 - DCF basic file (sRGB)' => 'R98 – ОÑновной файл DCF (sRGB)', + 'THM - DCF thumbnail file' => 'THM – Файл Ñ Ð¼Ð¸Ð½Ð¸Ð°Ñ‚ÑŽÑ€Ð°Ð¼Ð¸ DCF', + }, + }, + 'InteropOffset' => 'Тег взаимной ÑовмеÑтимоÑти', + 'InteropVersion' => 'ВерÑÐ¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ð¹ ÑовмеÑтимоÑти', + 'IsBaseFont' => 'Базовый шрифт', + 'IsFixedPitch' => 'Моноширинный шрифт', + 'IsMergedHDR' => 'Объединённый HDR', + 'IsMergedPanorama' => 'ÐžÐ±ÑŠÐµÐ´Ð¸Ð½Ñ‘Ð½Ð½Ð°Ñ Ð¿Ð°Ð½Ð¾Ñ€Ð°Ð¼Ð°', + 'Italic' => 'КурÑив', + 'ItalicAngle' => 'Угол наклона шрифта', + 'ItemSubType' => 'Подтип Ñлемента', + 'Iterations' => 'Повторов', + 'JBIGOptions' => 'Параметры JBIG', + 'JFIFVersion' => 'ВерÑÐ¸Ñ JFIF', + 'JP2Signature' => 'JP2 ПодпиÑÑŒ', + 'JPEGACTables' => 'Таблицы JPEGAC', + 'JPEGDCTables' => 'Таблицы JPEGDC', + 'JPEGDigest' => 'JPEG – Хеш-Ñумма', + 'JPEGImageLength' => 'JPEG – Строк в изображении', + 'JPEGLosslessPredictors' => 'Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¿Ñ€ÐµÐ´Ð¸ÐºÑ‚Ð¾Ñ€Ð° JPEG без потерь', + 'JPEGPointTransforms' => 'Преобразование точек JPEG', + 'JPEGProc' => { + Description => 'JPEG-Ñжатие в Ñтаром Ñтиле', + PrintConv => { + 'Baseline' => 'Базовое поÑледовательное', + 'Lossless' => 'Без потерь Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸ÐµÐ¼ Хаффмана', + }, + }, + 'JPEGQTables' => 'Таблицы JPEGQ', + 'JPEGQuality' => { + Description => 'КачеÑтво изображениÑ', + PrintConv => { + 'Extra Fine' => 'СверхвыÑокое', + 'Fine' => 'Ð’Ñ‹Ñокое', + 'Standard' => 'Стандартное качеÑтво', + }, + }, + 'JPEGQualityEstimate' => 'JPEG – Оценка качеÑтва', + 'JPEGRestartInterval' => 'Интервал перезапуÑка JPEG', + 'JPEGTables' => 'JPEG-таблицы', + 'JSONMetadata' => 'Метаданные JSON', + 'JobID' => 'ID заданиÑ', + 'JobTitle' => 'ДолжноÑть', + 'Journal' => 'Журнал', + 'JpgFromRaw' => 'Jpg-файл, вÑтроенный в Raw-файл', + 'JpgFromRawLength' => 'Строк в JPG-файле, вÑтроенного в RAW-файл', + 'JpgFromRawStart' => 'Смещение JPG-файла, вÑтроенного в RAW-файл', + 'JumpToXPEP' => 'Переход к XPEP', + 'KBAT_Unknown10' => 'KBAT – ÐеизвеÑтный 10', + 'KBAT_Unknown11' => 'KBAT – ÐеизвеÑтный 11', + 'KBAT_Unknown12' => 'KBAT – ÐеизвеÑтный 12', + 'KBAT_Unknown13' => 'KBAT – ÐеизвеÑтный 13', + 'KBAT_Unknown2' => 'KBAT – ÐеизвеÑтный 2', + 'KBAT_Unknown9' => 'KBAT – ÐеизвеÑтный 9', + 'KByteSize' => 'Общий размер в килобайтах', + 'KF8CoverURI' => 'KF8 – URI обложки', + 'Key' => 'Код', + 'KeyCode' => 'Код ключа', + 'KeyFramePositions' => 'Положение ключевых кадров', + 'KeyFramesTimes' => 'Ð’Ñ€ÐµÐ¼Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ñ… кадров', + 'KeywordInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ ключевых Ñловах', + 'Keywords' => 'Ключевые Ñлова', + 'Label' => 'Метка', + 'LameBitrate' => 'Lame – Битрейт', + 'LameLowPassFilter' => 'Lame – Фильтр нижних чаÑтот', + 'LameMethod' => { + Description => 'Lame – Метод кодированиÑ', + PrintConv => { + 'ABR' => 'ABR – Средний битрейт', + 'ABR (2-pass)' => 'ABR – Средний битрейт (2 прохода)', + 'CBR' => 'CBR – ПоÑтоÑнный битрейт', + 'CBR (2-pass)' => 'CBR – ПоÑтоÑнный битрейт (2 прохода)', + 'VBR' => 'VBR – переменный битрейт', + 'VBR (new/mtrh)' => 'VBR – переменный битрейт (новый/mtrh)', + 'VBR (old/rh)' => 'VBR – переменный битрейт (Ñтарый/rh)', + }, + }, + 'LameQuality' => 'Lame – КачеÑтво', + 'LameStereoMode' => { + Description => 'Lame – Режим Ñтерео', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкий', + 'Dual Channels' => 'Двуканальный режим', + 'Forced Joint Stereo' => 'Принудительное объединённое Ñтерео', + 'Intensity Stereo' => 'ИнтенÑивное Ñтерео', + 'Joint Stereo' => 'Объединённое Ñтерео', + 'Mono' => 'Моно', + 'Stereo' => 'Стерео', + }, + }, + 'LameVBRQuality' => 'Lame – КачеÑтво переменного битрейта', + 'Language' => 'Язык', + 'LanguageIdentifier' => 'Идентификатор Ñзыка', + 'LargestValidInteriorRectHeight' => 'МакÑимально Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð°Ñ Ð²Ñ‹Ñота прÑмоугольника', + 'LargestValidInteriorRectLeft' => 'ОтÑтуп Ñлева Ð´Ð»Ñ Ð¼Ð°ÐºÑимально возможного прÑмоугольника', + 'LargestValidInteriorRectTop' => 'ОтÑтуп Ñверху Ð´Ð»Ñ Ð¼Ð°ÐºÑимально возможного прÑмоугольника', + 'LargestValidInteriorRectWidth' => 'МакÑимально Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ð° прÑмоугольника', + 'LastAuthor' => 'ПоÑледнее редактирование Ñделал', + 'LastBackupDate' => 'Дата поÑледнего резервного копированиÑ', + 'LastChar' => 'ПоÑледний Ñимвол шрифта', + 'LastKeyFrameTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ñледнего ключевого кадра', + 'LastKeywordIPTC' => 'ПоÑледнее ключевое Ñлово IPTC', + 'LastKeywordXMP' => 'ПоÑледнее ключевое Ñлово XMP', + 'LastModifiedBy' => 'ПоÑледнее изменение Ñделал', + 'LastPhotoDate' => 'Дата ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ð¾Ñледнего изображениÑ', + 'LastPrinted' => 'Дата поÑледней печати', + 'LastTimeStamp' => 'ПоÑледнÑÑ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°ÌÑ Ð¼ÐµÑ‚ÐºÐ°', + 'LastUpdateTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ñледнего обновлениÑ', + 'LateralChromaticAberrationCorrectionAlreadyApplied' => 'Применена ÐºÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ñ…Ñ€Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкой аберрации', + 'LatestAgeOrHighestStage' => 'ПоÑледний век/Ð’Ñ‹Ñший ÑруÑ', + 'LatestEonOrHighestEonothem' => 'ПоÑледний Ñон/Ð’Ñ‹ÑÑˆÐ°Ñ Ñонотема', + 'LatestEpochOrHighestSeries' => 'ПоÑледнÑÑ Ñпоха/Ð’Ñ‹Ñший отдел', + 'LatestEraOrHighestErathem' => 'ПоÑледнÑÑ Ñра/Ð’Ñ‹ÑÑˆÐ°Ñ Ñратема', + 'LatestPeriodOrHighestSystem' => 'ПоÑледний период/Ð’Ñ‹ÑÑˆÐ°Ñ ÑиÑтема', + 'Latitude' => 'Широта', + 'LayerBlendModes' => { + Description => 'Режим Ð½Ð°Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ñлоёв', + PrintConv => { + 'Color' => 'ЦветноÑть', + 'Color Burn' => 'Затемнение оÑновы', + 'Color Dodge' => 'ОÑветление оÑновы', + 'Darken' => 'Затемнение', + 'Darker Color' => 'Темнее', + 'Difference' => 'Разница', + 'Dissolve' => 'Затухание', + 'Divide' => 'Разделить', + 'Exclusion' => 'ИÑключение', + 'Hard Light' => 'ЖёÑткий Ñвет', + 'Hard Mix' => 'ЖёÑткое Ñмешение', + 'Hue' => 'Цветовой тон', + 'Lighten' => 'Замена Ñветлым', + 'Lighter Color' => 'Светлее', + 'Linear Burn' => 'Линейный затемнитель', + 'Linear Dodge' => 'Линейный оÑветлитель', + 'Linear Light' => 'Линейный Ñвет', + 'Luminosity' => 'ЯркоÑть', + 'Multiply' => 'Умножение', + 'Normal' => 'Обычный', + 'Overlay' => 'Перекрытие', + 'Pass Through' => 'ПропуÑтить', + 'Pin Light' => 'Точечный Ñвет', + 'Saturation' => 'ÐаÑыщенноÑть', + 'Screen' => 'Экран', + 'Soft Light' => 'МÑгкий Ñвет', + 'Subtract' => 'Вычитание', + 'Vivid Light' => 'Яркий Ñвет', + }, + }, + 'LayerComps' => 'Композиции Ñлоёв', + 'LayerCount' => 'КоличеÑтво Ñлоёв', + 'LayerGroupsEnabledID' => 'ID включенных групп Ñлоёв', + 'LayerIDs' => 'ID Ñлоёв', + 'LayerModifyDates' => 'Дата Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÑлоÑ', + 'LayerNames' => 'ÐÐ°Ð·Ð²Ð°Ð½Ð¸Ñ Ñлоёв', + 'LayerOpacities' => 'ÐепрозрачноÑть ÑлоÑ', + 'LayerRectangles' => 'ПрÑмоугольники ÑлоÑ', + 'LayerSelectionIDs' => 'Идентификаторы выборанного ÑлоÑ', + 'LayerUnicodeNames' => 'Юникодные Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ñлоёв', + 'LayersGroupInfo' => 'Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ группе Ñлоёв', + 'Layout' => { + Description => 'Макет', + PrintConv => { + 'Scan Lines' => 'Линии ÑканированиÑ', + 'Tiles' => 'Тайлы', + }, + }, + 'LeftMargin' => 'ОтÑтуп Ñлева', + 'LegacyIPTCDigest' => 'Хеш-Ñумма прежней верÑии IPTC', + 'Length' => 'Общий размер в байтах', + 'Lens' => 'Объектив', + 'Lens35efl' => 'Объектив', + 'LensDistortInfo' => 'ДиÑторÑÐ¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¸Ð²Ð°', + 'LensID' => 'ID объектива', + 'LensInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð± объективе', + 'LensMake' => 'Производитель объектива', + 'LensManufacturer' => 'Производитель объектива', + 'LensModel' => 'Модель объектива', + 'LensNumber' => 'Серийный номер объектива', + 'LensSerialNumber' => 'Серийный номер объектива', + 'LensShading' => 'Виньетирование объектива', + 'LensSpec' => 'Параметры объектива', + 'LensTemperature' => 'Температура объектива', + 'LibraryID' => 'ID библиотеки', + 'License' => 'ЛицензиÑ', + 'LicenseEndDate' => 'Дата Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ð¸', + 'LicenseID' => 'PLUS-ID лицензии', + 'LicenseInfoURL' => 'URL лицензии', + 'LicenseStartDate' => 'Дата вÑÑ‚ÑƒÐ¿Ð»ÐµÐ½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ð¸ в Ñилу', + 'LicenseTransactionDate' => 'Дата лицензионной Ñделки', + 'Licensee' => 'Ð›Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð¿Ñ€ÐµÐ´Ð¾Ñтавлена длÑ', + 'LicenseeID' => 'PLUS-ID лицензиата', + 'LicenseeImageID' => 'ID Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ñвоенный лицензиатом', + 'LicenseeImageNotes' => 'ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ð°Ñ‚Ð°', + 'LicenseeName' => 'Ð˜Ð¼Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ð°Ñ‚Ð°', + 'LicenseeProjectReference' => 'Ðазвание проека приÑвоенное лицензиатом', + 'LicenseeTransactionID' => 'ID транзакции приÑвоенное лицензиатом', + 'Licensor' => 'Лицензор', + 'LicensorCity' => 'Город Ð¿Ñ€Ð¾Ð¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¾Ñ€Ð°', + 'LicensorCountry' => 'Страна Ð¿Ñ€Ð¾Ð¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¾Ñ€Ð°', + 'LicensorEmail' => 'ÐÐ´Ñ€ÐµÑ Ñлектронной почты лицензора', + 'LicensorExtendedAddress' => 'Дополнительные адреÑа Ñлектронной почты лицензора', + 'LicensorID' => 'PLUS-ID лицензора', + 'LicensorImageID' => 'ID Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ð¾Ðµ лицензором', + 'LicensorName' => 'Ð˜Ð¼Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¾Ñ€Ð°', + 'LicensorNotes' => 'Коментарии лицензора', + 'LicensorPostalCode' => 'Почтовый Ð¸Ð½Ð´ÐµÐºÑ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ð°Ñ€Ð°', + 'LicensorRegion' => 'ОблаÑть Ð¿Ñ€Ð¾Ð¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¾Ñ€Ð°', + 'LicensorStreetAddress' => 'Улица Ð¿Ñ€Ð¾Ð¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¾Ñ€Ð°', + 'LicensorTelephone1' => 'Телефон 1 лицензора', + 'LicensorTelephone2' => 'Телефон 2 лицензора', + 'LicensorTelephoneType1' => { + Description => 'Тип телефона 1', + PrintConv => { + 'Cell' => 'Мобильный', + 'FAX' => 'ФÐКС', + 'Home' => 'Домашний', + 'Pager' => 'Пейджер', + 'Work' => 'Рабочий', + }, + }, + 'LicensorTelephoneType2' => { + Description => 'Тип телефона 2', + PrintConv => { + 'Cell' => 'Мобильный', + 'FAX' => 'ФÐКС', + 'Home' => 'Домашний', + 'Pager' => 'Пейджер', + 'Work' => 'Рабочий', + }, + }, + 'LicensorTransactionID' => 'ID транзакции приÑвоенный лицензором', + 'LicensorURL' => 'URL лицензора', + 'LightSource' => { + Description => 'Тип оÑвещениÑ', + PrintConv => { + 'Cloudy' => 'Облачно (6500 К)', + 'Cool White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Холодный Ñвет (4150 К)', + 'Day White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Дневной белый (5050 К)', + 'Daylight' => 'Дневной Ñвет (5500 К)', + 'Daylight Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° дневного Ñвета (6400 К)', + 'Fine Weather' => 'ЯÑÐ½Ð°Ñ Ð¿Ð¾Ð³Ð¾Ð´Ð° (5500 К)', + 'Flash' => 'Ð’Ñпышка (5500 К)', + 'Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° (4150 К)', + 'ISO Studio Tungsten' => 'ISO Ð´Ð»Ñ Ñтудийных ламп накаливаниÑ', + 'Other' => 'Другой иÑточник Ñвета', + 'Shade' => 'Тень (7500 К)', + 'Standard Light A' => 'Стандартный Ñвет A', + 'Standard Light B' => 'Стандартный Ñвет B', + 'Standard Light C' => 'Стандартный Ñвет C', + 'Tungsten (Incandescent)' => 'Лампа Ð½Ð°ÐºÐ°Ð»Ð¸Ð²Ð°Ð½Ð¸Ñ (2850 К)', + 'Unknown' => 'ÐеизвеÑтный', + 'Warm White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Тёплый Ñвет (2925 К)', + 'White Fluorescent' => 'ФлуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð° – Белый Ñвет (3525 К)', + }, + }, + 'LightSourceSpecial' => { + Description => 'Специальный иÑточник Ñвета', + PrintConv => { + 'Off' => 'Ðе иÑпользовалÑÑ', + 'On' => 'ИÑпользовалÑÑ', + }, + }, + 'LightValue' => 'Световое чиÑло', + 'Lightness' => 'ОÑвещенноÑть', + 'LightroomWorkflow' => 'Рабочий процеÑÑ Lightroom', + 'LineOrder' => { + Description => 'ПорÑдок Ñтрок', + PrintConv => { + 'Decreasing Y' => 'По убыванию Y', + 'Increasing Y' => 'По возраÑтанию Y', + 'Random Y' => 'Случайный Y', + }, + }, + 'LinearResponseLimit' => 'Предел линейного отклика', + 'LinearityLimitBlue' => 'Предел линейноÑти – Синий', + 'LinearityLimitGreen' => 'Предел линейноÑти – Зелёный', + 'LinearityLimitRed' => 'Предел линейноÑти – КраÑный', + 'LinearizationCoefficients1' => 'КоÑффициенты линеаризации 1', + 'LinearizationCoefficients2' => 'КоÑффициенты линеаризации 2', + 'LinearizationTable' => 'Таблица линеаризации', + 'Linearized' => { + Description => 'ЛинеаризациÑ', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'Lines' => 'Строк', + 'LinkedProfileName' => 'Ðазвание ÑвÑзанного профилÑ', + 'LithostratigraphicTerms' => 'ЛитоÑтратиграфичеÑкие Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾Ñ€Ð¾Ð´', + 'LivingSpecimen' => 'Живой образец', + 'LivingSpecimenMaterialSampleID' => 'Живой образец – ID образца материала', + 'LocalBasePath' => 'Локальный базовый путь', + 'LocalCaption' => 'ОпиÑание меÑта', + 'LocalDefaultAlarm' => 'Локальный Ñигнал по-умолчанию', + 'LocalPositionNED' => 'Ð›Ð¾ÐºÐ°Ð»ÑŒÐ½Ð°Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ NED', + 'LocalizedCameraModel' => 'Локализованное название модели камеры', + 'Location' => 'МеÑтоположение', + 'Logo' => 'Логотип', + 'LongExposureNoiseReduction' => { + Description => 'Шумоподавление при длительной выдержке', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкое', + 'Off' => 'Ðе включено', + 'On' => 'Включено', + }, + }, + 'Longitude' => 'Долгота', + 'LookModTransform' => 'Преобразование вида ACES-ACES', + 'LookupTable' => 'Таблица подÑтановки', + 'LowestBiostratigraphicZone' => 'ÐÐ¸Ð·ÑˆÐ°Ñ Ð±Ð¸Ð¾ÑтратиграфичеÑÐºÐ°Ñ Ð·Ð¾Ð½Ð°', + 'MD5Signature' => 'MD5', + 'MD5Sum' => 'Сумма MD5', + 'MDColorTable' => 'MD – Таблица цветов', + 'MDFileTag' => 'MD – Формат иÑходных данных', + 'MDFileUnits' => 'MD – Единицы данных файла', + 'MDItemAccountHandles' => 'MD Item – ДеÑкрипторы аккаунта', + 'MDItemAccountIdentifier' => 'MD Item – Идентификатор аккаунта', + 'MDItemAcquisitionMake' => 'MD Item – Производитель уÑтройÑтва', + 'MDItemAcquisitionModel' => 'MD Item – Модель уÑтройÑтва', + 'MDItemAltitude' => 'MD Item – Ð’Ñ‹Ñота', + 'MDItemAperture' => 'MD Item – Диафрагма', + 'MDItemAudioBitRate' => 'MD Item – Битрейт аудио', + 'MDItemAudioChannelCount' => 'MD Item – КоличеÑтво аудиоканалов', + 'MDItemAuthorEmailAddresses' => 'MD Item – ÐÐ´Ñ€ÐµÑ Ñлектронной почты автора', + 'MDItemAuthors' => 'MD Item – Ðвторы', + 'MDItemBitsPerSample' => 'MD Item – КоличеÑтво бит на образец', + 'MDItemBundleIdentifier' => 'MD Item – Идентификатор комплекта', + 'MDItemCity' => 'MD Item – Город', + 'MDItemCodecs' => 'MD Item – Кодеки', + 'MDItemColorSpace' => 'MD Item – Цветовое проÑтранÑтво', + 'MDItemComment' => 'MD Item – Комментарий', + 'MDItemContentCreationDate' => 'MD Item – Дата ÑозданиÑ', + 'MDItemContentCreationDateRanking' => 'MD Item – Дата ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ â€“ Рейтинг', + 'MDItemContentCreationDate_Ranking' => 'MD Item – Дата ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ â€“ Рейтинг', + 'MDItemContentModificationDate' => 'MD Item – Дата редактированиÑ', + 'MDItemContentType' => 'MD Item – Тип контента', + 'MDItemContentTypeTree' => 'MD Item – Тип контента – Древо', + 'MDItemContributors' => 'MD Item – ВнеÑшие вклад', + 'MDItemCopyright' => 'MD Item – ÐвторÑкое право', + 'MDItemCountry' => 'MD Item – Страна', + 'MDItemCreator' => 'MD Item – Создано в', + 'MDItemDateAdded' => 'MD Item – Дата добавлениÑ', + 'MDItemDateAdded_Ranking' => 'MD Item – Дата Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ â€“ Рейтинг', + 'MDItemDescription' => 'MD Item – ОпиÑание', + 'MDItemDisplayName' => 'MD Item – Локализованное название файла', + 'MDItemDownloadedDate' => 'MD Item – Дата загрузки', + 'MDItemDurationSeconds' => 'MD Item – ПродолжительноÑть в Ñекундах', + 'MDItemEXIFGPSVersion' => 'MD Item – EXIF – ВерÑÐ¸Ñ GPS', + 'MDItemEXIFVersion' => 'MD Item – ВерÑÐ¸Ñ EXIF', + 'MDItemEmailConversationID' => 'MD Item – ID перепиÑки по Ñлектронной почте', + 'MDItemEncodingApplications' => 'MD Item – Кодировано в приложении', + 'MDItemExposureMode' => 'MD Item – Режим ÑкÑпозиции', + 'MDItemExposureProgram' => 'MD Item – Программа ÑкÑпозиции', + 'MDItemExposureTimeSeconds' => 'MD Item – Выдержка', + 'MDItemFNumber' => 'MD Item – Диафрагменное чиÑло', + 'MDItemFSContentChangeDate' => 'MD Item – FS – Дата редактированиÑ', + 'MDItemFSCreationDate' => 'MD Item – FS – Дата ÑозданиÑ', + 'MDItemFSCreatorCode' => 'MD Item – FS – Код программы, Ñоздавшей файл', + 'MDItemFSFinderFlags' => 'MD Item – FS – Ðтрибуты файла', + 'MDItemFSHasCustomIcon' => 'MD Item – FS – Ðаличие иконки файла', + 'MDItemFSInvisible' => 'MD Item – FS – Скрытый', + 'MDItemFSIsExtensionHidden' => 'MD Item – FS – РаÑширение файла Ñкрыто', + 'MDItemFSIsStationery' => 'MD Item – FS – Шаблон', + 'MDItemFSLabel' => { + Description => 'MD Item – FS – Ð¦Ð²ÐµÑ‚Ð½Ð°Ñ Ð¼ÐµÑ‚ÐºÐ° файла', + PrintConv => { + '0 (none)' => '0 (Без метки)', + '1 (Gray)' => '1 (СераÑ)', + '2 (Green)' => '2 (ЗелёнаÑ)', + '3 (Purple)' => '3 (ПурпурнаÑ)', + '4 (Blue)' => '4 (СинÑÑ)', + '5 (Yellow)' => '5 (ЖёлтаÑ)', + '6 (Red)' => '6 (КраÑнаÑ)', + '7 (Orange)' => '7 (ОранжеваÑ)', + }, + }, + 'MDItemFSName' => 'MD Item – FS – Ðазвание файла', + 'MDItemFSNodeCount' => 'MD Item – FS – КоличеÑтво файлов в каталоге', + 'MDItemFSOwnerGroupID' => 'MD Item – FS – ID группы владельца файла', + 'MDItemFSOwnerUserID' => 'MD Item – FS – ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†Ð° файла', + 'MDItemFSSize' => 'MD Item – FS – Размер файла', + 'MDItemFSTypeCode' => 'MD Item – FS – Код типа файла', + 'MDItemFinderComment' => 'MD Item – Комментарий к файлу', + 'MDItemFlashOnOff' => 'MD Item – Срабатывание вÑпышки', + 'MDItemFocalLength' => 'MD Item – ФокуÑное раÑÑтоÑние', + 'MDItemGPSDateStamp' => 'MD Item – GPS – Дата и Ð²Ñ€ÐµÐ¼Ñ Ð¼ÐµÑтоположениÑ', + 'MDItemGPSStatus' => 'MD Item – GPS – СоÑтоÑние приемника во Ð²Ñ€ÐµÐ¼Ñ Ñъёмки', + 'MDItemGPSTrack' => 'MD Item – GPS – Трек маршрута', + 'MDItemHasAlphaChannel' => 'MD Item – Ðаличие Ðльфа-канала', + 'MDItemISOSpeed' => 'MD Item – ISO', + 'MDItemIdentifier' => 'MD Item – Идентификатор', + 'MDItemImageDirection' => 'MD Item – ÐÐ°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ°Ð¼ÐµÑ€Ñ‹ при Ñъёмке', + 'MDItemInterestingDateRanking' => 'MD Item – Дата интереÑных меÑÑ‚ – Рейтинг', + 'MDItemInterestingDate_Ranking' => 'MD Item – Дата интереÑных меÑÑ‚ – Рейтинг', + 'MDItemIsApplicationManaged' => 'MD Item – УправлÑетÑÑ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸ÐµÐ¼', + 'MDItemIsExistingThread' => 'MD Item – Цепочка Ñообщений', + 'MDItemIsLikelyJunk' => 'MD Item – Спам', + 'MDItemKeywords' => 'MD Item – Ключевые Ñлова', + 'MDItemKind' => 'MD Item – Тип файла', + 'MDItemLastUsedDate' => 'MD Item – Дата поÑледнего иÑпользованиÑ', + 'MDItemLastUsedDate_Ranking' => 'MD Item – Дата поÑледнего иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ â€“ Рейтинг', + 'MDItemLatitude' => 'MD Item – Широта', + 'MDItemLensModel' => 'MD Item – Модель объектива', + 'MDItemLogicalSize' => 'MD Item – ЛогичеÑкий размер', + 'MDItemLongitude' => 'MD Item – Долгота', + 'MDItemMailDateReceived_Ranking' => 'MD Item – Дата Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¿Ð¾Ñ‡Ñ‚Ñ‹ – Рейтинг', + 'MDItemMailboxes' => 'MD Item – Почтовые Ñщики', + 'MDItemMediaTypes' => 'MD Item – Типы медиафайлов', + 'MDItemNumberOfPages' => 'MD Item – КоличеÑтво Ñтраниц', + 'MDItemOrientation' => 'MD Item – ОриентациÑ', + 'MDItemOriginApplicationIdentifier' => 'MD Item – Идентификатор приложениÑ', + 'MDItemOriginMessageID' => 'MD Item – ID ÑообщениÑ', + 'MDItemOriginSenderDisplayName' => 'MD Item – Ð˜Ð¼Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð¸Ñ‚ÐµÐ»Ñ', + 'MDItemOriginSenderHandle' => 'MD Item – ДеÑкриптор отправителÑ', + 'MDItemOriginSubject' => 'MD Item – Тема ÑообщениÑ', + 'MDItemPageHeight' => 'MD Item – Ð’Ñ‹Ñота Ñтраницы', + 'MDItemPageWidth' => 'MD Item – Ширина Ñтраницы', + 'MDItemPhysicalSize' => 'MD Item – ФизичеÑкий размер', + 'MDItemPixelCount' => 'MD Item – КоличеÑтво пикÑелей', + 'MDItemPixelHeight' => 'MD Item – Ð’Ñ‹Ñота в пикÑелÑÑ…', + 'MDItemPixelWidth' => 'MD Item – Ширина в пикÑелÑÑ…', + 'MDItemPrimaryRecipientEmailAddresses' => 'MD Item – ÐÐ´Ñ€ÐµÑ Ñлектронной почты оÑновного получателÑ', + 'MDItemProfileName' => 'MD Item – Ðазвание цветового профилÑ', + 'MDItemRecipients' => 'MD Item – Получатели', + 'MDItemRedEyeOnOff' => 'MD Item – Уменьшение Ñффекта "краÑных глаз"', + 'MDItemResolutionHeightDPI' => 'MD Item – Разрешение по выÑоте в DPI', + 'MDItemResolutionWidthDPI' => 'MD Item – Разрешение по ширине в DPI', + 'MDItemSecurityMethod' => 'MD Item – Метод шифрованиÑ', + 'MDItemSpeed' => 'MD Item – СкороÑть передвижениÑ', + 'MDItemStateOrProvince' => 'MD Item – ОблаÑть/Район', + 'MDItemStreamable' => 'MD Item – Потоковый', + 'MDItemSubject' => 'MD Item – Тема', + 'MDItemTimestamp' => 'MD Item – ВременнаÌÑ Ð¼ÐµÑ‚ÐºÐ°', + 'MDItemTitle' => 'MD Item – Ðазвание файла', + 'MDItemTotalBitRate' => 'MD Item – Общий битрейт', + 'MDItemUseCount' => 'MD Item – Счётчик иÑпользованиÑ', + 'MDItemUsedDates' => 'MD Item – ИÑпользуемые даты', + 'MDItemUserDownloadedDate' => 'MD Item – Пользователь – Дата загрузки', + 'MDItemUserDownloadedUserHandle' => 'MD Item – Пользователь – ДеÑкриптор загрузки пользователÑ', + 'MDItemUserSharedReceivedDate' => 'MD Item – User Shared Received Date', + 'MDItemUserSharedReceivedRecipient' => 'MD Item – User Shared Received Recipient', + 'MDItemUserSharedReceivedRecipientHandle' => 'MD Item – User Shared Received Recipient Handle', + 'MDItemUserSharedReceivedSender' => 'MD Item – User Shared Received Sender', + 'MDItemUserSharedReceivedSenderHandle' => 'MD Item – User Shared Received Sender Handle', + 'MDItemUserSharedReceivedTransport' => 'MD Item – ÐžÐ±Ñ‰Ð°Ñ Ð¿Ð°Ð¿ÐºÐ° Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ â€“ Received Transport', + 'MDItemUserTags' => 'MD Item – ПользовательÑкие теги', + 'MDItemVersion' => 'MD Item – ВерÑÐ¸Ñ Ñ„Ð°Ð¹Ð»Ð°', + 'MDItemVideoBitRate' => 'MD Item – Битрейт видео', + 'MDItemWhereFroms' => 'MD Item – ИÑточник файла', + 'MDItemWhiteBalance' => 'MD Item – Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾', + 'MDLabName' => 'MD – Создатель файла', + 'MDPrepDate' => 'MD – Дата ÑозданиÑ', + 'MDPrepTime' => 'MD – Ð’Ñ€ÐµÐ¼Ñ ÑозданиÑ', + 'MDSampleInfo' => 'MD – ОпиÑание файла', + 'MDScalePixel' => 'MD – Коефициент маÑÑˆÑ‚Ð°Ð±Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¸ÐºÑелÑ', + 'MIDIControlVersion' => 'ВерÑÐ¸Ñ MIDI Control', + 'MIDISong' => 'MIDI композициÑ', + 'MIMEType' => 'MIME тип', + 'MOIVersion' => 'ВерÑÐ¸Ñ MOI', + 'MPEG7Binary' => 'Бинарный MPEG7', + 'MPEGAudioVersion' => 'ВерÑÐ¸Ñ MPEG аудио', + 'MPFVersion' => 'ВерÑÐ¸Ñ MPF', + 'MPImage' => 'Изображение MP', + 'MPImageFlags' => { + Description => 'MP изображение – Флаги', + PrintConv => { + 'Dependent child image' => 'ЗавиÑимое вложенное изображение', + 'Dependent parent image' => 'ЗавиÑимое родительÑкое изображение', + 'Representative image' => 'Репрезентативное изображение', + }, + }, + 'MPImageFormat' => 'MP изображение – Формат', + 'MPImageLength' => 'MP изображение – КоличеÑтво Ñтрок', + 'MPImageStart' => 'MP изображение – Ðачало', + 'MPImageType' => { + Description => 'MP изображение – Тип', + PrintConv => { + 'Baseline MP Primary Image' => 'Базовое первичное изображение MP', + 'Large Thumbnail (VGA equivalent)' => 'Большой ÑÑкиз (Ñквивалент VGA)', + 'Large Thumbnail (full HD equivalent)' => 'Большой ÑÑкиз (Ñквивалент Full HD)', + 'Multi-angle' => 'МногоракурÑное', + 'Multi-frame Disparity' => 'Многокадровое Ñмещённое (Disparity)', + 'Multi-frame Panorama' => 'ÐœÐ½Ð¾Ð³Ð¾ÐºÐ°Ð´Ñ€Ð¾Ð²Ð°Ñ Ð¿Ð°Ð½Ð¾Ñ€Ð°Ð¼Ð°', + 'Undefined' => 'Ðеопределённый', + }, + }, + 'MPIndividualNum' => 'Индивидуальный номер MP', + 'MSDocumentText' => 'MS – ТекÑтовый документ', + 'MSDocumentTextPosition' => 'MS – ТекÑтовый документ – ПозициÑ', + 'MSPropertySetStorage' => 'MS – Хранилище ÑвойÑтв', + 'MSStereo' => { + Description => 'MS Ñтерео', + PrintConv => { + 'Off' => 'Ðе включено', + 'On' => 'Включено', + }, + }, + 'MachineID' => 'ID машины', + 'MachineObservation' => 'Машинное наблюдение', + 'MachineObservationDay' => 'День меÑÑца машинного наблюдениÑ', + 'MachineObservationEarliestDate' => 'Дата начала машинного наблюдениÑ', + 'MachineObservationEndDayOfYear' => 'День года Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð½Ð¾Ð³Ð¾ наблюдениÑ', + 'MachineObservationEventDate' => 'Дата машинного наблюдениÑ', + 'MachineObservationEventID' => 'ID машинного наблюдениÑ', + 'MachineObservationEventRemarks' => 'Комментарии к машинному наблюдению', + 'MachineObservationEventTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð½Ð¾Ð³Ð¾ наблюдениÑ', + 'MachineObservationFieldNotes' => 'Полевые заметки о машинном наблюдении', + 'MachineObservationFieldNumber' => 'Ðомер полевых заметок о машинном наблюдении', + 'MachineObservationHabitat' => 'Ðреал машинного наблюдениÑ', + 'MachineObservationLatestDate' => 'Дата Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð½Ð¾Ð³Ð¾ наблюдениÑ', + 'MachineObservationMonth' => 'МеÑÑц машинного наблюдениÑ', + 'MachineObservationParentEventID' => 'ID оÑновного машинного наблюдениÑ', + 'MachineObservationSampleSizeUnit' => 'Машинное наблюдение – Единицы размера', + 'MachineObservationSampleSizeValue' => 'Машинное наблюдение – Размер образца', + 'MachineObservationSamplingEffort' => 'Машинное наблюдение – Затраченные уÑилиÑ', + 'MachineObservationSamplingProtocol' => 'Машинное наблюдение – Метод иÑÑледованиÑ', + 'MachineObservationStartDayOfYear' => 'День года начала машинного наблюдениÑ', + 'MachineObservationVerbatimEventDate' => 'Дата машинного Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð´Ð¾Ñловно', + 'MachineObservationYear' => 'Год машинного наблюдениÑ', + 'MacintoshNSPrintInfo' => 'Macintosh NS – Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ печати', + 'MacintoshPrintInfo' => 'Macintosh – Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ печати', + 'Macro' => { + Description => 'Макро', + PrintConv => { + 'Macro' => 'Макро', + 'Manual' => 'Ручной', + 'Normal' => 'Обычный', + 'Off' => 'Выключен', + 'On' => 'Включен', + }, + }, + 'Magnetometer' => 'Магнитометр', + 'MajorBrand' => { + Description => 'ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ñ‚Ð¾Ñ€Ð³Ð¾Ð²Ð°Ñ Ð¼Ð°Ñ€ÐºÐ°', + PrintConv => { + 'JPEG 2000 Compound Image (.JPM)' => 'СоÑтавное изображение JPEG 2000 (.JPM)', + 'JPEG 2000 Image (.JP2)' => 'Изображение JPEG 2000 (.JP2)', + 'JPEG 2000 with extensions (.JPX)' => 'Улучшенный JPEG 2000 (.JPX)', + }, + }, + 'MajorVersion' => 'ÐœÐ°Ð¶Ð¾Ñ€Ð½Ð°Ñ Ð²ÐµÑ€ÑиÑ', + 'Make' => 'Производитель', + 'MakerNote' => 'Приватные данные DNG', + 'MakerNoteApple' => 'Комментарии Apple', + 'MakerNoteCanon' => 'Комментарии Canon', + 'MakerNoteCasio' => 'Комментарии Casio', + 'MakerNoteCasio2' => 'Комментарии Casio 2', + 'MakerNoteDJI' => 'Комментарии DJI', + 'MakerNoteFLIR' => 'Комментарии FLIR', + 'MakerNoteFujiFilm' => 'Комментарии Fuji Film', + 'MakerNoteGE' => 'Комментарии GE', + 'MakerNoteGE2' => 'Комментарии GE2', + 'MakerNoteHP' => 'Комментарии HP', + 'MakerNoteHP2' => 'Комментарии HP2', + 'MakerNoteHP4' => 'Комментарии HP4', + 'MakerNoteHP6' => 'Комментарии HP6', + 'MakerNoteHasselblad' => 'Комментарии Hasselblad', + 'MakerNoteISL' => 'Комментарии ISL', + 'MakerNoteJVC' => 'Комментарии JVC', + 'MakerNoteJVCText' => 'Комментарии JVC – ТекÑÑ‚', + 'MakerNoteKodak10' => 'Комментарии Kodak 10', + 'MakerNoteKodak11' => 'Комментарии Kodak 11', + 'MakerNoteKodak12' => 'Комментарии Kodak 12', + 'MakerNoteKodak1a' => 'Комментарии Kodak 1a', + 'MakerNoteKodak1b' => 'Комментарии Kodak 1b', + 'MakerNoteKodak2' => 'Комментарии Kodak 2', + 'MakerNoteKodak3' => 'Комментарии Kodak 3', + 'MakerNoteKodak4' => 'Комментарии Kodak 4', + 'MakerNoteKodak5' => 'Комментарии Kodak 5', + 'MakerNoteKodak6a' => 'Комментарии Kodak 6a', + 'MakerNoteKodak6b' => 'Комментарии Kodak 6b', + 'MakerNoteKodak7' => 'Комментарии Kodak 7', + 'MakerNoteKodak8a' => 'Комментарии Kodak 8a', + 'MakerNoteKodak8b' => 'Комментарии Kodak 8b', + 'MakerNoteKodak8c' => 'Комментарии Kodak 8c', + 'MakerNoteKodak9' => 'Комментарии Kodak 9', + 'MakerNoteKodakUnknown' => 'Комментарии Kodak – ÐеизвеÑтно', + 'MakerNoteKyocera' => 'Комментарии Kyocera', + 'MakerNoteLeica' => 'Комментарии Leica', + 'MakerNoteLeica10' => 'Комментарии Leica 10', + 'MakerNoteLeica2' => 'Комментарии Leica 2', + 'MakerNoteLeica3' => 'Комментарии Leica 3', + 'MakerNoteLeica4' => 'Комментарии Leica 4', + 'MakerNoteLeica5' => 'Комментарии Leica 5', + 'MakerNoteLeica6' => 'Комментарии Leica 6', + 'MakerNoteLeica7' => 'Комментарии Leica 7', + 'MakerNoteLeica8' => 'Комментарии Leica 8', + 'MakerNoteLeica9' => 'Комментарии Leica 9', + 'MakerNoteMinolta' => 'Комментарии Minolta', + 'MakerNoteMinolta2' => 'Комментарии Minolta 2', + 'MakerNoteMinolta3' => 'Комментарии Minolta 3', + 'MakerNoteMotorola' => 'Комментарии Motorola', + 'MakerNoteNikon' => 'Комментарии Nikon', + 'MakerNoteNikon2' => 'Комментарии Nikon 2', + 'MakerNoteNikon3' => 'Комментарии Nikon 3', + 'MakerNoteNintendo' => 'Комментарии Nintendo', + 'MakerNoteOffset' => 'Смещение заметки производителÑ', + 'MakerNoteOlympus' => 'Комментарии Olympus', + 'MakerNoteOlympus2' => 'Комментарии Olympus 2', + 'MakerNotePanasonic' => 'Комментарии Panasonic', + 'MakerNotePanasonic2' => 'Комментарии Panasonic 2', + 'MakerNotePanasonic3' => 'Комментарии Panasonic 3', + 'MakerNotePentax' => 'Комментарии Pentax', + 'MakerNotePentax2' => 'Комментарии Pentax 2', + 'MakerNotePentax3' => 'Комментарии Pentax 3', + 'MakerNotePentax4' => 'Комментарии Pentax 4', + 'MakerNotePentax5' => 'Комментарии Pentax 5', + 'MakerNotePentax6' => 'Комментарии Pentax 6', + 'MakerNotePhaseOne' => 'Комментарии Phase One', + 'MakerNoteReconyx' => 'Комментарии Reconyx', + 'MakerNoteReconyx2' => 'Комментарии Reconyx 2', + 'MakerNoteReconyx3' => 'Комментарии Reconyx 3', + 'MakerNoteRicoh' => 'Комментарии Ricoh', + 'MakerNoteRicoh2' => 'Комментарии Ricoh 2', + 'MakerNoteRicohPentax' => 'Комментарии Ricoh Pentax', + 'MakerNoteRicohText' => 'Комментарии Ricoh – ТекÑÑ‚', + 'MakerNoteSafety' => { + Description => 'ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð±ÐµÐ·Ð¾Ð¿Ð°ÑноÑти', + PrintConv => { + 'Safe' => 'БезопаÑно', + 'Unsafe' => 'ÐебезопаÑно', + }, + }, + 'MakerNoteSamsung1a' => 'Комментарии Samsung 1a', + 'MakerNoteSamsung1b' => 'Комментарии Samsung 1b', + 'MakerNoteSamsung2' => 'Комментарии Samsung 2', + 'MakerNoteSanyo' => 'Комментарии Sanyo', + 'MakerNoteSanyoC4' => 'Комментарии Sanyo C4', + 'MakerNoteSanyoPatch' => 'Комментарии Sanyo Patch', + 'MakerNoteSigma' => 'Комментарии Sigma', + 'MakerNoteSigma3' => 'Комментарии Sigma 3', + 'MakerNoteSony' => 'Комментарии Sony', + 'MakerNoteSony2' => 'Комментарии Sony 2', + 'MakerNoteSony3' => 'Комментарии Sony 3', + 'MakerNoteSony4' => 'Комментарии Sony 4', + 'MakerNoteSony5' => 'Комментарии Sony 5', + 'MakerNoteSonyEricsson' => 'Комментарии Sony Ericsson', + 'MakerNoteSonySRF' => 'Комментарии Sony SRF', + 'MakerNoteUnknown' => 'Комментарии неизвеÑтного производителÑ', + 'MakerNoteUnknownBinary' => 'Комментарии неизвеÑтного Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»Ñ â€“ Бинарный', + 'MakerNoteUnknownText' => 'Комментарии неизвеÑтного Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»Ñ â€“ ТекÑтовый', + 'MakerNoteVersion' => 'Заметка Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»Ñ â€“ ВерÑиÑ', + 'MakerNotes' => 'Данные производителей', + 'Manager' => 'Руководитель', + 'ManualFocusDistance' => 'РаÑÑтоÑние ручной фокуÑировки', + 'Manufacturer' => 'Разработчик', + 'MappingScheme' => 'Схема подÑтановки отÑутÑтвующего шрифта', + 'MarkerID' => 'ID маркера', + 'MaskedAreas' => 'МаÑкированные облаÑти', + 'MasterDocumentID' => 'ОÑновной ID документа', + 'MaterialSample' => 'Материал образца', + 'MaterialSampleID' => 'ID материала образца', + 'MatrixWorldToCamera' => 'Матрица мира на камеру', + 'MatrixWorldToScreen' => 'Матрица мира на Ñкран', + 'MattColor' => 'Matt цвет', + 'Matte' => 'Matte (МаÑка?)', + 'Matteing' => 'Матирование', + 'MaxAperture' => 'МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð¸Ð°Ñ„Ñ€Ð°Ð³Ð¼Ð° объектива', + 'MaxApertureValue' => 'МакÑимальное значение диафрагмы', + 'MaxBand' => 'МакÑимальный диапазон', + 'MaxBitrate' => 'МакÑимальный битрейт', + 'MaxPacketSize' => 'Размер макÑимального пакета', + 'MaxSampleValue' => 'МакÑимальное значение компонента', + 'MaxSubfileSize' => 'МакÑимальный размер подфайла', + 'MaxVal' => 'МакÑимальное значение пикÑелÑ', + 'MaxWidth' => 'МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ð° глифа', + 'MaximumBitrate' => 'МакÑимальный битрейт', + 'MaximumDensityRange' => 'МакÑимальный диапазон плотноÑти', + 'MaximumObjectSize' => 'МакÑимальный размер объекта', + 'MeasureType' => { + Description => 'Тип Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ð³Ð»ÑƒÐ±Ð¸Ð½Ñ‹', + PrintConv => { + 'OpticalAxis' => 'ОптичеÑÐºÐ°Ñ Ð¾ÑÑŒ', + 'OpticalRay' => 'ОптичеÑкий луч', + }, + }, + 'MeasurementAccuracy' => 'ÐŸÐ¾Ñ‚ÐµÐ½Ñ†Ð¸Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ð¾Ð³Ñ€ÐµÑˆÐ½Ð¾Ñть измерениÑ', + 'MeasurementDeterminedBy' => 'Люди определившие значение измерениÑ', + 'MeasurementDeterminedDate' => 'Дата Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ', + 'MeasurementID' => 'ID измерениÑ', + 'MeasurementMethod' => 'Метод измерениÑ', + 'MeasurementOrFact' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð± измерении', + 'MeasurementRemarks' => 'Заметки измерениÑ', + 'MeasurementScale' => 'Шкала измерений', + 'MeasurementType' => 'Тип измерениÑ', + 'MeasurementUnit' => 'Единицы измерениÑ', + 'MeasurementValue' => 'Значение измерениÑ', + 'MediaConstraints' => 'ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð½Ð° медиареÑурÑÑ‹', + 'MediaData' => 'Медиа-данные', + 'MediaEventIdDate' => 'Дата ÑобытиÑ', + 'MediaIsDelay' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaIsFinale' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaIsLive' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaIsMovie' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaIsPremiere' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaIsRepeat' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaIsSAP' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaIsSport' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaIsStereo' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaIsSubtitled' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaIsTape' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'MediaSummaryCode' => 'Сводный код медиареÑурÑов', + 'MeetingLocations' => 'МеÑто вÑтречи', + 'Megapixels' => 'МегапикÑелей', + 'MelodicPolyphony' => 'ПолифоничеÑÐºÐ°Ñ Ð¼ÐµÐ»Ð¾Ð´Ð¸Ñ', + 'Message' => 'Сообщение', + 'MetadataCreator' => 'Метаданные – Создатель', + 'MetadataDate' => 'Метаданные – Дата', + 'MeteringMode' => { + Description => 'ЭкÑпозамер', + PrintConv => { + 'Average' => 'УÑреднённый', + 'Center-weighted average' => 'Центрально-взвешенный', + 'Multi-segment' => 'Оценочный/Многозонный', + 'Multi-spot' => 'Многоточечный', + 'Other' => 'Другой', + 'Partial' => 'ЧаÑтичный', + 'Spot' => 'Точечный', + 'Unknown' => 'ÐеизвеÑтный', + }, + }, + 'Method' => 'Метод', + 'Micro1Version' => 'Micro 1 – ВерÑиÑ', + 'Micro2Version' => 'Micro 2 – ВерÑиÑ', + 'Mime' => 'MIME-тип', + 'MinApertureValue' => 'Минимальное чиÑло диафрагмы объектива', + 'MinSampleValue' => 'Минимальное значение компонента', + 'MinimumBitrate' => 'Минимальный битрейт', + 'MinimumVersion' => 'ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑиÑ', + 'MinorModelAgeDisclosure' => { + Description => 'ВозраÑÑ‚ Ñамой молодой модели на Ñнимке', + PrintConv => { + 'Age 14 or Under' => '14 лет и меньше', + 'Age 15' => '15 лет', + 'Age 16' => '16 лет', + 'Age 17' => '17 лет', + 'Age 18' => '18 лет', + 'Age 19' => '19 лет', + 'Age 20' => '20 лет', + 'Age 21' => '21 год', + 'Age 22' => '22 года', + 'Age 23' => '23 года', + 'Age 24' => '24 года', + 'Age 25 or Over' => '25 лет и выше', + 'Age Unknown' => 'ВозраÑÑ‚ неизвеÑтен', + }, + }, + 'MinorVersion' => 'ÐœÐ¸Ð½Ð¾Ñ€Ð½Ð°Ñ Ð²ÐµÑ€ÑиÑ', + 'MobiType' => { + Description => 'Тип Mobi', + PrintConv => { + 'Audio' => 'Ðудио', + 'KF8: generated by kindlegen2' => 'KF8: Ñгенерированный kindlegen 2', + 'Mobipocket Book' => 'Книга Mobipocket', + 'News' => 'ÐовоÑти', + 'News_Feed' => 'ÐовоÑÑ‚Ð½Ð°Ñ Ð»ÐµÐ½Ñ‚Ð°', + 'News_Magazine' => 'Журнал новоÑтей', + 'PalmDoc Book' => 'Книга PalmDoc', + 'mobipocket? generated by kindlegen1.2' => 'Mobipocket? Сгенерированный kindlegen 1.2', + }, + }, + 'MobiVersion' => 'ВерÑÐ¸Ñ Mobi', + 'ModDate' => 'Дата редактированиÑ', + 'ModeExtension' => { + Description => 'РаÑширение режима канала', + PrintConv => { + 'Bands 12-31' => 'ПолоÑÑ‹ 12-32', + 'Bands 16-31' => 'ПолоÑÑ‹ 16-31', + 'Bands 4-31' => 'ПолоÑÑ‹ 4-31', + 'Bands 8-31' => 'ПолоÑÑ‹ 8-31', + }, + }, + 'ModeNumber' => 'Режим Ñтандарта факÑ-профилÑ', + 'Model' => 'Модель камеры', + 'Model2' => 'Модель 2', + 'ModelReleaseID' => 'ID релиза модели', + 'ModelReleaseStatus' => { + Description => 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ñ€ÐµÐ»Ð¸Ð·Ð° модели', + PrintConv => { + 'Limited or Incomplete Model Releases' => 'Ограниченные или неполные верÑии моделей', + 'None' => 'Ðе обозначено', + 'Not Applicable' => 'Ðе дейÑтвующий', + 'Unlimited Model Releases' => 'Ðеограниченное количеÑтво релизов модели', + }, + }, + 'ModelTiePoint' => 'ПроÑтранÑÑ‚Ð²ÐµÐ½Ð½Ð°Ñ Ð¿Ñ€Ð¸Ð²Ñзка', + 'ModelTransform' => 'Модель преобразованиÑ', + 'ModificationNumber' => 'Ðомер модификации', + 'ModificationPermissions' => { + Description => 'Ð Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð½Ð° Редактирование', + PrintConv => { + 'Do not restrict applications to reader permissions' => 'Ðе ограничивать вÑем приложениÑм права на чтение', + 'Fill forms, Create page templates, Sign' => 'РазрешаетÑÑ: Заполнение форм, Создание шаблонов Ñтраниц, Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ', + 'Fill forms, Create page templates, Sign, Create/Delete/Edit annotations' => 'РазрешаетÑÑ: Заполнение форм, Создание шаблонов Ñтраниц, Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ, Создание/Удаление/Редактирование аннотаций', + 'No changes permitted' => 'Редактирование запрещено', + 'Restrict all applications to reader permissions' => 'Ограничивать вÑем приложениÑм права на чтение', + }, + }, + 'ModifyDate' => 'Дата редактированиÑ', + 'MoireFilter' => 'Фильтр муара', + 'Montage' => 'Монтаж', + 'Month' => 'МеÑÑц', + 'MoonPhase' => { + Description => 'Фаза луны', + PrintConv => { + 'First Quarter' => 'ÐŸÐµÑ€Ð²Ð°Ñ Ñ‡ÐµÑ‚Ð²ÐµÑ€Ñ‚ÑŒ', + 'Full' => 'Полнолуние', + 'Last Quarter' => 'ПоÑледнÑÑ Ñ‡ÐµÑ‚Ð²ÐµÑ€Ñ‚ÑŒ', + 'New' => 'Ðоволуние', + 'New Crescent' => 'ÐœÐ¾Ð»Ð¾Ð´Ð°Ñ Ð»ÑƒÐ½Ð°', + 'Old Crescent' => 'Ð¡Ñ‚Ð°Ñ€Ð°Ñ Ð»ÑƒÐ½Ð°', + 'Waning Gibbous' => 'Ð£Ð±Ñ‹Ð²Ð°ÑŽÑ‰Ð°Ñ Ð»ÑƒÐ½Ð°', + 'Waxing Gibbous' => 'РаÑÑ‚ÑƒÑ‰Ð°Ñ Ð»ÑƒÐ½Ð°', + }, + }, + 'MotionSensitivity' => 'Обнаружение движениÑ', + 'MultiFrameNoiseReduction' => { + Description => 'Многокадровое шумоподавление', + PrintConv => { + 'Off' => 'Ðе включено', + 'On' => 'Включено', + }, + }, + 'MultiProfiles' => { + Description => 'Мультипрофили', + PrintConv => { + 'JBIG2 Profile M' => 'JBIG2 профиль M', + 'N Layer Profile M' => 'N Ñлой Профиль M', + 'Profile C' => 'Профиль C', + 'Profile F' => 'Профиль F', + 'Profile J' => 'Профиль J', + 'Profile L' => 'Профиль L', + 'Profile M' => 'Профиль M', + 'Profile S' => 'Профиль S', + 'Profile T' => 'Профиль T', + 'Resolution/Image Width' => 'Разрешение/Ширина изображениÑ', + 'Shared Data' => 'Общие данные', + }, + }, + 'MultimediaType' => 'Тип мультимедиа', + 'Multishot' => { + Description => 'МультиÑъёмка', + PrintConv => { + 'Off' => 'Ðе включена', + 'Pixel Shift' => 'Сдвиг пикÑелей', + }, + }, + 'Name' => 'Ðазвание файла', + 'NameTableVersion' => 'Таблица имён – ВерÑиÑ', + 'NameUTF-8' => 'Ðазвание в кодировке UTF-8', + 'Narrator' => 'Ð˜Ð¼Ñ Ð´Ð¸ÐºÑ‚Ð¾Ñ€Ð°', + 'Near' => 'Ближайшее значение карты глубины', + 'NetName' => 'Сетевое имÑ', + 'NetProviderType' => 'Тип Ñетевого провайдера', + 'NewGUID' => 'Ðовый GUID', + 'NewRawImageDigest' => 'Хеш-Ñумма RAW-Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð² новом формате', + 'NewsPhotoVersion' => 'ВерÑÐ¸Ñ News Photo', + 'Nickname' => 'Прозвище', + 'NoMSSmartTags' => 'Смарт-теги MS отÑутÑтвуют', + 'Noise' => 'Уровень шума', + 'NoiseProfile' => 'Профиль – Уровень шума', + 'NoiseReduction' => 'Шумоподавление', + 'NoiseReductionApplied' => 'Применённое шумоподавление', + 'NoiseReductionMode' => { + Description => 'Режим шумоподавлениÑ', + PrintConv => { + 'Off' => 'Ðе включён', + 'On' => 'Включён', + }, + }, + 'NoiseReductionParams' => 'Параметры шумоподавлениÑ', + 'NoiseReductionValue' => 'Значение шумоподавлениÑ', + 'NominalBitrate' => 'Ðоминальный битрейт', + 'NominalVideoBitrate' => { + Description => 'Ðоминальный битрейт видео', + PrintConv => { + 'Unspecified' => 'Ðеопределённый', + }, + }, + 'Note' => 'Заметка', + 'Notes' => 'Комментарии', + 'Notice' => 'Заметка', + 'Now' => 'Текущие Дата и ВремÑ', + 'NumChannels' => 'КоличеÑтво каналов', + 'NumColors' => 'КоличеÑтво цветов', + 'NumFonts' => 'КоличеÑтво шрифтов в коллекции', + 'NumImportantColors' => 'КоличеÑтво важных цветов', + 'NumIndexEntries' => 'КоличеÑтво индекÑов', + 'NumPackets' => 'КоличеÑтво пакетов', + 'NumProperties' => 'КоличеÑтво ÑвойÑтв', + 'NumRules' => 'КоличеÑтво правил', + 'NumSampleFrames' => 'КоличеÑтво кадров', + 'NumSlices' => 'КоличеÑтво фрагментов', + 'NumStreams' => 'КоличеÑтво потоков', + 'Number' => 'Ðомер', + 'NumberList' => 'Ðумерованный ÑпиÑок', + 'NumberOfComponents' => 'КоличеÑтво компонентов', + 'NumberOfImages' => 'КоличеÑтво изображений', + 'NumberofInks' => 'КоличеÑтво чернил', + 'OPIProxy' => { + Description => 'ПодÑтановка изображениÑ', + PrintConv => { + 'Higher resolution image does not exist' => 'Изображение большего Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¾Ñ‚ÑутÑтвует', + 'Higher resolution image exists' => 'ПриÑутÑтвует изображение Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ð¼ разрешением', + }, + }, + 'Object' => 'Объект', + 'ObjectAttributeReference' => 'СÑылка на атрибут объекта', + 'ObjectCycle' => { + Description => 'Суточный цикл', + PrintConv => { + 'Both Morning and Evening' => 'Утро и вечер', + 'Evening' => 'Вечер', + 'Morning' => 'Утро', + }, + }, + 'ObjectName' => 'Ðазвание объекта', + 'ObjectPreviewData' => 'ПредпроÑмотр объекта – Данные', + 'ObjectPreviewFileFormat' => { + Description => 'ПредпроÑмотр объекта – Формат файла', + PrintConv => { + 'AppleSingle (Apple Computer Inc)' => 'AppleSingle (Apple)', + 'Audio Interchange File Format AIFF (Apple Computer Inc)' => 'Ðудиофайл формата AIFF (Apple)', + 'Bit Mapped Graphics File [.BMP] (Microsoft)' => 'Битовый графичеÑкий файл [.BMP] (Microsoft)', + 'Compressed Binary File [.ZIP] (PKWare Inc)' => 'Сжатый двоичный файл [.ZIP] (ÐšÐ¾Ð¼Ð¿Ð°Ð½Ð¸Ñ PKWare)', + 'Digital Audio File [.WAV] (Microsoft & Creative Labs)' => 'Ðудиофайл формата [.WAV] (Microsoft и Creative Labs)', + 'Hypertext Markup Language [.HTML] (The Internet Society)' => 'Язык гипертекÑтовой разметки [.HTML] (The Internet Society)', + 'IPTC Unstructured Character Oriented File Format (UCOFF)' => 'ÐеÑтруктурированный Ñимвольно-ориентированный формат файла IPTC (UCOFF)', + 'IPTC7901 Recommended Message Format' => 'IPTC7901 Рекомендуемый формат ÑообщениÑ', + 'Illustrator (Adobe Graphics data)' => 'Illustrator (ГрафичеÑкие данные Adobe)', + 'JPEG File Interchange (JFIF)' => 'Формат обмена файлами Ñтандарта JPEG (JFIF)', + 'MPEG 2 Audio Layer 2 (Musicom), ISO/IEC' => 'Ðудио второго ÑƒÑ€Ð¾Ð²Ð½Ñ MPEG 2 (Musicom), ISO/IEC', + 'MPEG 2 Audio Layer 3, ISO/IEC' => 'Ðудио третьего ÑƒÑ€Ð¾Ð²Ð½Ñ MPEG 2 , ISO/IEC', + 'News Industry Text Format (NITF)' => 'ТекÑтовый формат новоÑтной индуÑтрии (NITF)', + 'No ObjectData' => 'Данные объекта отÑутÑтвуют', + 'PC DOS/Windows Executable Files [.COM][.EXE]' => 'ИÑполнÑемый файл PC DOS/Windows [.COM][.EXE]', + 'RIFF Wave (Microsoft Corporation)' => 'RIFF Wave (Microsoft)', + 'Ritzaus Bureau NITF version (RBNITF DTD)' => 'Ritzaus Bureau вариант NITF (RBNITF DTD)', + 'Tagged Image File Format (Adobe/Aldus Image data)' => 'Формат файла Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ñ Ñ‚ÐµÐ³Ð°Ð¼Ð¸ (Adobe/Aldus данные изображениÑ)', + 'Tape Archive [.TAR]' => 'Формат файла архива [.TAR]', + 'Tidningarnas Telegrambyra NITF version (TTNITF DTD)' => 'Tidningarnas Telegrambyra вариант NITF (TTNITF DTD)', + 'United Press International ANPA 1312 variant' => 'United Press International – Вариант ANPA 1312', + 'United Press International Down-Load Message' => 'United Press International – Down-Load Message', + }, + }, + 'ObjectPreviewFileVersion' => 'ПредпроÑмотр объекта – ВерÑиÑ', + 'ObjectSizeAnnounced' => 'ЗаÑвленный размер объекта', + 'ObjectType' => 'Тип объекта', + 'ObjectTypeReference' => 'СÑылка на тип объекта', + 'ObservationDate' => 'Дата наблюдениÑ', + 'ObservationDateEnd' => 'Дата Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ', + 'ObservationTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ', + 'ObservationTimeEnd' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ', + 'Observer' => 'Ðаблюдатель', + 'ObsoletePhotoshopTag1' => 'Photoshop – УÑтаревший тег 1', + 'ObsoletePhotoshopTag2' => 'Photoshop – УÑтаревший тег 2', + 'ObsoletePhotoshopTag3' => 'Photoshop – УÑтаревший тег 3', + 'Occurrence' => 'Ðаходка', + 'OccurrenceAssociatedMedia' => 'МедиареÑурÑÑ‹ ÑвÑзанные Ñ Ð½Ð°Ñ…Ð¾Ð´ÐºÐ¾Ð¹', + 'OccurrenceAssociatedOccurrences' => 'СвÑзанные находки', + 'OccurrenceAssociatedReferences' => 'СÑылки ÑвÑзанные Ñ Ð½Ð°Ñ…Ð¾Ð´ÐºÐ¾Ð¹', + 'OccurrenceAssociatedSequences' => 'ГенетичеÑкие поÑледовательноÑти ÑвÑзанные Ñ Ð½Ð°Ñ…Ð¾Ð´ÐºÐ¾Ð¹', + 'OccurrenceAssociatedTaxa' => 'ТакÑоны ÑвÑзанные Ñ Ð½Ð°Ñ…Ð¾Ð´ÐºÐ¾Ð¹', + 'OccurrenceBehavior' => 'ÐŸÐ¾Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ñоби во Ð²Ñ€ÐµÐ¼Ñ Ð½Ð°Ñ…Ð¾Ð´ÐºÐ¸', + 'OccurrenceCatalogNumber' => 'Ðомер набора данных', + 'OccurrenceDetails' => 'ПодробноÑти о находке', + 'OccurrenceDisposition' => 'СоÑтоÑние образца по отношению к коллекции', + 'OccurrenceEstablishmentMeans' => 'СпоÑоб оÑÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ Ð½Ð° меÑтноÑти', + 'OccurrenceID' => 'ID находки', + 'OccurrenceIndividualCount' => 'КоличеÑтво найденных оÑобей', + 'OccurrenceIndividualID' => 'ID найденных оÑобей', + 'OccurrenceLifeStage' => 'Жизненные циклы найденных оÑобей', + 'OccurrenceOrganismQuantity' => 'ЧиÑло учётных единиц или обилие вида', + 'OccurrenceOrganismQuantityType' => 'Тип ÑиÑтемы количеÑтвенного определениÑ', + 'OccurrenceOtherCatalogNumbers' => 'Другие номера каталога Ð´Ð»Ñ Ð½Ð°Ñ…Ð¾Ð´ÐºÐ¸', + 'OccurrencePreparations' => 'Методы Ð¿Ñ€ÐµÐ¿Ð°Ñ€Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ конÑÐµÑ€Ð²Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ð·Ñ†Ð°', + 'OccurrencePreviousIdentifications' => 'Предыдущие Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð½Ð°Ñ…Ð¾Ð´ÐºÐ¸', + 'OccurrenceRecordNumber' => 'Ðомер приÑвоенный объекту в момент находки', + 'OccurrenceRecordedBy' => 'Люди ответÑтвенные за запиÑÑŒ о находке', + 'OccurrenceRemarks' => 'Комментарии к находке', + 'OccurrenceReproductiveCondition' => 'Репродуктивное ÑоÑтоÑние оÑобей во Ð²Ñ€ÐµÐ¼Ñ Ð½Ð°Ñ…Ð¾Ð´ÐºÐ¸', + 'OccurrenceSex' => 'Пол найденных оÑобей', + 'OccurrenceStatus' => 'ПриÑутÑтвие вида в данной меÑтноÑти', + 'OceApplicationSelector' => 'Задатчик Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Oce', + 'OceIDNumber' => 'ID-номер Oce', + 'OceImageLogic' => 'Логика Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Oce', + 'OceScanjobDesc' => 'ОпиÑание Ð·Ð°Ð´Ð°Ð½Ð¸Ñ ÑÐºÐ°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Oce', + 'OffsetSchema' => 'Схема ÑмещениÑ', + 'OffsetTime' => 'Смещение времени', + 'OffsetTimeDigitized' => 'Ð¡Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð¸ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°', + 'OffsetTimeOriginal' => 'Смещение времени Ñъёмки', + 'OldSubfileType' => { + Description => 'Тип подфайла (Ñтарый)', + PrintConv => { + 'Full-resolution image' => 'Изображение Ñ Ð¿Ð¾Ð»Ð½Ñ‹Ð¼ разрешением', + 'Reduced-resolution image' => 'Изображение Ñ Ð¿Ð¾Ð½Ð¸Ð¶ÐµÐ½Ð½Ñ‹Ð¼ разрешением', + 'Single page of multi-page image' => 'Одна Ñтраница из многоÑтраничного изображениÑ', + }, + }, + 'OnionSkins' => 'Обрамление Ñцены', + 'Opacity' => 'ÐепрозрачноÑть', + 'OpcodeList1' => 'СпиÑок кодов операций обработки изображений 1', + 'OpcodeList2' => 'СпиÑок кодов операций обработки изображений 2', + 'OpcodeList3' => 'СпиÑок кодов операций обработки изображений 3', + 'OperatingSystem' => { + Description => 'ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ð¾Ð½Ð½Ð°Ñ ÑиÑтема', + PrintConv => { + 'FAT filesystem (MS-DOS, OS/2, NT/Win32)' => 'Ð¤Ð°Ð¹Ð»Ð¾Ð²Ð°Ñ ÑиÑтема FAT (MS-DOS, OS/2, NT/Win32)', + 'HPFS filesystem (OS/2, NT)' => 'Ð¤Ð°Ð¹Ð»Ð¾Ð²Ð°Ñ ÑиÑтема HPFS (OS/2, NT)', + 'NTFS filesystem (NT)' => 'Ð¤Ð°Ð¹Ð»Ð¾Ð²Ð°Ñ ÑиÑтема NTFS (NT)', + 'VMS (or OpenVMS)' => 'VMS (или OpenVMS)', + 'Z-System' => 'Z/OS', + 'unknown' => 'ÐеизвеÑтнаÑ', + }, + }, + 'OpticalZoomOn' => { + Description => 'ОптичеÑкий зум', + PrintConv => { + 'Off' => 'Ðе включён', + 'On' => 'Включен', + }, + }, + 'Opto-ElectricConvFactor' => 'Фактор оптико-ÑлектричеÑкого преобразованиÑ', + 'Organism' => 'Организм', + 'OrganismAssociatedOccurrences' => 'СвÑзанные находки Ñ Ñтим организмом', + 'OrganismAssociatedOrganisms' => 'Другие организмы ÑвÑзанные Ñ Ñтим организмом', + 'OrganismID' => 'ID организма', + 'OrganismName' => 'Ðазвание приÑвоенное организму', + 'OrganismPreviousIdentifications' => 'Предыдущие Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¾Ñ€Ð³Ð°Ð½Ð¸Ð·Ð¼Ð°', + 'OrganismRemarks' => 'Комментарии к ÑкземплÑру организма', + 'OrganismScope' => 'Вид ÑкземплÑра организма', + 'Organization' => 'ОрганизациÑ', + 'Organizer' => 'Организатор', + 'Orientation' => { + Description => 'ОриентациÑ', + PrintConv => { + 'Horizontal (normal)' => 'ГоризонтальнаÑ', + 'Mirror horizontal' => 'Отражение по горизонтали', + 'Mirror horizontal and rotate 270 CW' => 'Отражение по горизонтали и поворот на 270° по чаÑовой Ñтрелке', + 'Mirror horizontal and rotate 90 CW' => 'Отражение по горизонтали и поворот на 90° по чаÑовой Ñтрелке', + 'Mirror vertical' => 'Отражение по вертикали', + 'Rotate 180' => 'Поворот на 180°', + 'Rotate 270 CW' => 'Поворот на 270° по чаÑовой Ñтрелке', + 'Rotate 90 CW' => 'Поворот на 90° по чаÑовой Ñтрелке', + }, + }, + 'OriginPathInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð± иÑходном обтравочном контуре', + 'OriginalBestQualitySize' => 'Размер более качеÑтвенного изображениÑ', + 'OriginalCreateDateTime' => 'ИÑходные дата и Ð²Ñ€ÐµÐ¼Ñ ÑозданиÑ', + 'OriginalDecisionData' => 'Ð¦Ð¸Ñ„Ñ€Ð¾Ð²Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ оригинальноÑти Ñнимка (ODD)', + 'OriginalDefaultCropSize' => 'ИÑходный размер кадрированного изображениÑ', + 'OriginalDefaultFinalSize' => 'Полный размер иÑходного изображениÑ', + 'OriginalFileName' => 'ИÑходное название файла', + 'OriginalFileSize' => 'Размер иÑходного файла', + 'OriginalFileType' => 'Тип иÑходного файла', + 'OriginalFrameRate' => 'ИÑÑ…Ð¾Ð´Ð½Ð°Ñ Ñ‡Ð°Ñтота кадров', + 'OriginalImageHeight' => 'ИÑÑ…Ð¾Ð´Ð½Ð°Ñ Ð²Ñ‹Ñотаи зображениÑ', + 'OriginalImageWidth' => 'ИÑÑ…Ð¾Ð´Ð½Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ð° изображениÑ', + 'OriginalMedia' => { + Description => 'Оригинальный ноÑитель', + PrintConv => { + 'False' => 'Ðет', + 'True' => 'Да', + }, + }, + 'OriginalRawFileData' => 'Данные иÑходного RAW-файла', + 'OriginalRawFileDigest' => 'Хеш-Ñумма иÑходного RAW-файла', + 'OriginalRawFileName' => 'Ðазвание иÑходного RAW-файла', + 'OriginalReleaseTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð° оригинала', + 'OriginalTransmissionReference' => 'ИÑÑ…Ð¾Ð´Ð½Ð°Ñ ÑÑылка на передачу', + 'OriginatingProgram' => 'Создано в риложении', + 'Originator' => 'Контент Ñоздан в', + 'OtherConditions' => 'Другие уÑловиÑ', + 'OtherConstraints' => 'Другие ограничениÑ', + 'OtherDate1' => 'Другое – Дата 1', + 'OtherDate2' => 'Другое – Дата 2', + 'OtherDate3' => 'Другое – Дата 3', + 'OtherFirmware' => 'Ð”Ñ€ÑƒÐ³Ð°Ñ Ð¿Ñ€Ð¾ÑˆÐ¸Ð²ÐºÐ°', + 'OtherImage' => 'Другое изображение', + 'OtherImageInfo' => 'Другие ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð± изображении', + 'OtherImageLength' => 'Строк в файле предпроÑмотра', + 'OtherImageStart' => 'Смещение другого изображениÑ', + 'OtherLicenseDocuments' => 'Другие документы лицензии', + 'OtherLicenseInfo' => 'Дополнительные ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ лицензии', + 'OtherLicenseRequirements' => 'Другие лицензионные требованиÑ', + 'OtherSerialNumber' => 'Другой Серийный номер', + 'Owner' => 'Владелец', + 'OwnerAppointmentID' => 'ID владельца ÑобытиÑ', + 'OwnerID' => 'ID владельца', + 'OwnerName' => 'Ð˜Ð¼Ñ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†Ð° камеры', + 'PDFVersion' => 'ВерÑÐ¸Ñ PDF', + 'PFMVersion' => 'ВерÑÐ¸Ñ PFM', + 'PGFVersion' => 'ВерÑÐ¸Ñ PGF', + 'PLUSVersion' => 'ВерÑÐ¸Ñ PLUS', + 'PMVersion' => 'ВерÑÐ¸Ñ PhotoMechanic', + 'PNGWarning' => 'Предупреждение PNG', + 'PStringCaption' => 'P – Строка заголовка', + 'PXC Viewer Info' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ñ‚ PDF-XChange Viewer', + 'Packets' => 'Пакетов', + 'PackingMethod' => { + Description => 'Метод ÑжатиÑ', + PrintConv => { + 'Best Compression' => 'МакÑимальный', + 'Fast' => 'БыÑтрый', + 'Fastest' => 'СкороÑтной', + 'Good Compression' => 'Хороший', + 'Normal' => 'Обычный', + 'Stored' => 'Без ÑжатиÑ', + }, + }, + 'Padding' => 'Внутренние отÑтупы', + 'Page' => 'Размер и раÑположение холÑта', + 'PageCount' => 'КоличеÑтво Ñтраниц', + 'PageEnter' => 'Эффект при заходе на Ñтраницу', + 'PageExit' => 'Эффект при уходе Ñо Ñтраницы', + 'PageFront' => 'КоличеÑтво фронтальных Ñтраниц', + 'PageLayout' => 'Макет Ñтраницы', + 'PageMode' => 'Режим Ñтраницы', + 'PageName' => 'Ðазвание Ñтраницы', + 'PageNormal' => 'КоличеÑтво обычных Ñтраниц', + 'PageNumber' => 'Ðомер Ñтраницы', + 'PageSpecial' => 'КоличеÑтво Ñпециальных Ñтраниц', + 'Pages' => 'Страниц', + 'Palette' => 'Палитра', + 'PaletteHistogram' => 'ГиÑтограмма палитры', + 'PalmFileType' => 'Тип файла Palm', + 'PanOrientation' => { + Description => 'ÐžÑ€Ð¸ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð¿Ð°Ð½Ð¾Ñ€Ð°Ð¼Ñ‹', + PrintConv => { + 'Bottom to top' => 'Снизу вверх', + 'Clockwise' => 'По чаÑовой Ñтрелке', + 'Counter clockwise' => 'Против чаÑовой Ñтрелки', + 'Left to right' => 'Слева направо', + 'Right to left' => 'Справа налево', + 'Start at bottom left' => 'Ðачало внизу Ñлева', + 'Start at bottom right' => 'Ðачало внизу Ñправа', + 'Start at top left' => 'Ðачало Ñверху Ñлева', + 'Start at top right' => 'Ðачало Ñверху Ñправа', + 'Top to bottom' => 'Сверху вниз', + 'Zigzag (column start)' => 'Зигзаг (начало колонки)', + 'Zigzag (row start)' => 'Зигзаг (начало Ñтроки)', + '[unused]' => 'ÐеиÑпользуетÑÑ', + }, + }, + 'PanOverlapH' => 'Горизонтальное перекрытие панорамы', + 'PanOverlapV' => 'Вертикальное перекрытие панорамы', + 'PanasonicRawVersion' => 'Panasonic – ВерÑÐ¸Ñ RAW', + 'PanasonicTitle' => 'Panasonic – Ðазвание', + 'PanasonicTitle2' => 'Panasonic – Ðазвание 2', + 'PanoramicStitchCameraMotion' => { + Description => 'Метод ÑÑˆÐ¸Ð²Ð°Ð½Ð¸Ñ Ð¿Ð°Ð½Ð¾Ñ€Ð°Ð¼Ñ‹', + PrintConv => { + '3D Rotation' => '3D-вращение', + 'Affine' => 'Ðффинное преобразование', + 'Homography' => 'ГомографиÑ', + 'Rigid Scale' => 'ЖёÑÑ‚ÐºÐ°Ñ ÑˆÐºÐ°Ð»Ð°', + }, + }, + 'PanoramicStitchMapType' => { + Description => 'Тип карты', + PrintConv => { + 'Horizontal Cylindrical' => 'Ð“Ð¾Ñ€Ð¸Ð·Ð¾Ð½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ñ†Ð¸Ð»Ð¸Ð½Ð´Ñ€Ð¸Ñ‡ÐµÑкаÑ', + 'Horizontal Spherical' => 'Ð“Ð¾Ñ€Ð¸Ð·Ð¾Ð½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ ÑферичеÑкаÑ', + 'Perspective' => 'ПерÑпектива', + 'Vertical Cylindrical' => 'Ð’ÐµÑ€Ñ‚Ð¸ÐºÐ°Ð»ÑŒÐ½Ð°Ñ Ñ†Ð¸Ð»Ð¸Ð½Ð´Ñ€Ð¸Ñ‡ÐµÑкаÑ', + 'Vertical Spherical' => 'Ð’ÐµÑ€Ñ‚Ð¸ÐºÐ°Ð»ÑŒÐ½Ð°Ñ ÑферичеÑкаÑ', + }, + }, + 'PanoramicStitchPhi0' => 'Угол Phi 0', + 'PanoramicStitchPhi1' => 'Угол Phi 1', + 'PanoramicStitchTheta0' => 'Угол Theta 0', + 'PanoramicStitchTheta1' => 'Угол Theta 1', + 'Paragraphs' => 'Ðбзацев', + 'ParentMEID' => 'РодительÑкий MEID', + 'ParentMediaEventID' => 'ID ÑобытиÑ', + 'PathSelectionState' => 'СоÑтоÑние выбранного обтравочного контура', + 'PathTableLocation' => 'РаÑположение таблицы путей', + 'PathTableSize' => 'Размер таблицы путей', + 'PatientBirthDate' => 'Дата Ñ€Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ†Ð¸ÐµÐ½Ñ‚Ð°', + 'PatientID' => 'ID пациента', + 'PatientName' => 'Ð˜Ð¼Ñ Ð¿Ð°Ñ†Ð¸ÐµÐ½Ñ‚Ð°', + 'PatientSex' => 'Пол пациента', + 'PeakSpectralSensitivity' => 'ÐŸÐ¸ÐºÐ¾Ð²Ð°Ñ ÑÐ¿ÐµÐºÑ‚Ñ€Ð°Ð»ÑŒÐ½Ð°Ñ Ñ‡ÑƒÐ²ÑтвительноÑть', + 'People' => 'Люди', + 'PercentComplete' => 'Процент завершениÑ', + 'PercussivePolyphony' => 'ПолифоничеÑÐºÐ°Ñ Ð¿ÐµÑ€ÐºÑƒÑÑиÑ', + 'Performer' => 'ИÑполнитель', + 'PeripheralIllumCentralRadius' => 'КомпенÑÐ°Ñ†Ð¸Ñ Ð¿Ð°Ñ€Ð°Ð·Ð¸Ñ‚Ð½Ð¾Ð³Ð¾ Ñигнала – Центральный радиуÑ', + 'PeripheralIllumCentralValue' => 'КомпенÑÐ°Ñ†Ð¸Ñ Ð¿Ð°Ñ€Ð°Ð·Ð¸Ñ‚Ð½Ð¾Ð³Ð¾ Ñигнала – Ð¦ÐµÐ½Ñ‚Ñ€Ð°Ð»ÑŒÐ½Ð°Ñ Ð½Ð°ÑыщенноÑть', + 'PeripheralIllumPeriphValue' => 'КомпенÑÐ°Ñ†Ð¸Ñ Ð¿Ð°Ñ€Ð°Ð·Ð¸Ñ‚Ð½Ð¾Ð³Ð¾ Ñигнала – ÐŸÐµÑ€Ð¸Ñ„ÐµÑ€Ð¸Ð¹Ð½Ð°Ñ Ð½Ð°ÑыщенноÑть', + 'Personality' => 'ЛичноÑти', + 'Photo' => 'ФотографиÑ', + 'PhotoResolution' => 'Разрешение Ñнимка', + 'PhotometricInterpretation' => { + Description => 'Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ð¼Ð¾Ð´ÐµÐ»ÑŒ', + PrintConv => { + 'BlackIsZero' => 'Чёрный равен нулю', + 'Color Filter Array' => 'МаÑÑив цветных фильтров (CFA)', + 'RGB Palette' => 'Палитра RGB', + 'Transparency Mask' => 'МаÑка прозрачноÑти', + 'WhiteIsZero' => 'Белый равен нулю', + }, + }, + 'Photoshop2ColorTable' => 'Photoshop 2 – Таблица цветов', + 'Photoshop2Info' => 'Photoshop 2 – СведениÑ', + 'PhotoshopBGRThumbnail' => 'Photoshop – BGR миниатюра', + 'PhotoshopFormat' => { + Description => 'Photoshop – Формат', + PrintConv => { + 'Optimized' => 'Оптимизированный', + 'Progressive' => 'ПрогреÑÑивный', + 'Standard' => 'Стандартный', + }, + }, + 'PhotoshopQuality' => 'Photoshop – КачеÑтво', + 'PhotoshopThumbnail' => 'Photoshop – Миниатюра', + 'PhysicalStreamNumberMap' => 'ФизичеÑкий поток – Number Map', + 'PhysicalStreamNumbers' => 'Ðомера физичеÑких потоков', + 'PhysicalStreams' => 'ФизичеÑких потоков', + 'PicsLabel' => 'Метка взроÑлоÑти контента', + 'PictInfo' => 'Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð± изображении', + 'Picture' => 'Картина', + 'PictureBitsPerPixel' => 'КоличеÑтво бит на пикÑель картинки', + 'PictureDescription' => 'ОпиÑание изображениÑ', + 'PictureHeight' => 'Ð’Ñ‹Ñота картинки', + 'PictureIndexedColors' => 'ИндекÑированные цвета изображениÑ', + 'PictureLength' => 'Строк в картинке', + 'PictureMIMEType' => 'MIME тип изображениÑ', + 'PictureType' => { + Description => 'Тип изображениÑ', + PrintConv => { + '32x32 PNG Icon' => '32x32 иконка PNG', + 'Artist' => 'ИÑполнитель', + 'Back Cover' => 'ЗаднÑÑ Ð¾Ð±Ð»Ð¾Ð¶ÐºÐ°', + 'Band' => 'Группа', + 'Band Logo' => 'Логотип группы/иÑполнителÑ', + 'Bright(ly) Colored Fish' => 'Ярко Ð¾ÐºÑ€Ð°ÑˆÐµÐ½Ð½Ð°Ñ Ñ€Ñ‹Ð±Ð°', + 'Capture from Movie or Video' => 'Захват из фильма или видео', + 'Composer' => 'Композитор', + 'Conductor' => 'Дирижер', + 'Front Cover' => 'ПереднÑÑ Ð¾Ð±Ð»Ð¾Ð¶ÐºÐ°', + 'Illustration' => 'ИллюÑтрациÑ', + 'Lead Artist' => 'Ведущий ÐртиÑÑ‚/ИÑполнитель/СолиÑÑ‚', + 'Leaflet' => 'ЛиÑтовка', + 'Lyricist' => 'Ðвтор текÑта', + 'Media' => 'ÐоÑитель', + 'Other' => 'Другое', + 'Other Icon' => 'Ð”Ñ€ÑƒÐ³Ð°Ñ Ð¸ÐºÐ¾Ð½ÐºÐ°', + 'Performance' => 'ПредÑтавление', + 'Publisher Logo' => 'Логотип издателÑ/Ñтудии', + 'Recording Session' => 'ЗапиÑÑŒ ÑеÑÑии', + 'Recording Studio or Location' => 'Ð¡Ñ‚ÑƒÐ´Ð¸Ñ Ð·Ð²ÑƒÐºÐ¾Ð·Ð°Ð¿Ð¸Ñи или меÑтоположение', + }, + }, + 'PictureWidth' => 'Ширина картинки', + 'PieceLength' => 'Размер блока в байтах', + 'Pieces' => 'Блоков', + 'PipelineVersion' => 'ВерÑÐ¸Ñ Pipeline', + 'Pitch' => 'Тангаж (Ðаклон)', + 'PitchAndFamily' => 'Шаг и ÑемейÑтво шрифта', + 'PitchAngle' => 'Угол наклона (Тангаж)', + 'PixHeight' => 'Ð’Ñ‹Ñота в пикÑелÑÑ…', + 'PixWidth' => 'Ширина в пикÑелÑÑ…', + 'PixelAspectRatio' => 'Соотношение Ñторон пикÑелей', + 'PixelCalibration' => 'Калибровка по пикÑелÑм', + 'PixelFormat' => { + Description => 'Формат пикÑелÑ', + PrintConv => { + '112-bit 6 Channels Alpha' => '112-битный, 6 каналов, альфа-канал', + '112-bit 7 Channels' => '112-битный, 7 каналов', + '128-bit 7 Channels Alpha' => '128-битный, 7 каналов, альфа-канал', + '128-bit 8 Channels' => '128-битный, 8 каналов', + '128-bit PRGBA Float' => '128-битный, PRGBA Ñ Ð¿Ð»Ð°Ð²Ð°ÑŽÑ‰ÐµÐ¹ точкой', + '128-bit RGB Float' => '128-битный, RGB Ñ Ð¿Ð»Ð°Ð²Ð°ÑŽÑ‰ÐµÐ¹ точкой', + '128-bit RGBA Fixed Point' => '128-битный, RGBA Ñ Ñ„Ð¸ÐºÑированной точкой', + '128-bit RGBA Float' => '128-битный, RGBA Ñ Ð¿Ð»Ð°Ð²Ð°ÑŽÑ‰ÐµÐ¹ точкой', + '144-bit 8 Channels Alpha' => '144-битный, 8 каналов, альфа-канал', + '16-bit BGR555' => '16-битный, BGR555', + '16-bit BGR565' => '16-битный, BGR565', + '16-bit Gray' => '16-битный, Серый', + '16-bit Gray Half' => '16-битный, половина Ñерого', + '24-bit 3 Channels' => '24-битный, 3 канала', + '24-bit BGR' => '24-битный, BGR', + '24-bit RGB' => '24-битный, RGB', + '32-bit 3 Channels Alpha' => '32-битный, 3 канала, альфа-канал', + '32-bit 4 Channels' => '32-битный, 4 канала', + '32-bit BGR' => '32-битный, BGR', + '32-bit BGR101010' => '32-битный, BGR101010', + '32-bit BGRA' => '32-битный, BGRA', + '32-bit CMYK' => '32-битный, CMYK', + '32-bit Gray Fixed Point' => '32-битный, Ñерый Ñ Ñ„Ð¸ÐºÑированной точкой', + '32-bit Gray Float' => '32-битный, Серый Ñ Ð¿Ð»Ð°Ð²Ð°ÑŽÑ‰ÐµÐ¹ точкой', + '32-bit PBGRA' => '32-битный, PBGRA', + '32-bit RGBE' => '32-битный, RGBE', + '40-bit 4 Channels Alpha' => '40-битный, 4 канала, альфа-канал', + '40-bit 5 Channels' => '40-битный, 5 каналов', + '40-bit CMYK Alpha' => '40-битный, CMYK, альфа-канал', + '48-bit 3 Channels' => '48-битный, 3 канала', + '48-bit 5 Channels Alpha' => '48-битный, 5 каналов, альфа-канал', + '48-bit 6 Channels' => '48-битный, 6 каналов', + '48-bit RGB' => '48-битный, RGB', + '48-bit RGB Fixed Point' => '48-битный, RGB Ñ Ñ„Ð¸ÐºÑированной точкой', + '48-bit RGB Half' => '48-битный, RGB Half', + '4:2:0 (chroma at 0, 0.5)' => '4:2:0 (цветноÑть в 0, 0.5)', + '4:2:0 (chroma at 0.5, 0.5)' => '4:2:0 (цветноÑть в 0.5, 0.5)', + '4:2:2 (chroma at 0, 0)' => '4:2:2 (цветноÑть в 0, 0)', + '4:2:2 (chroma at 0.5, 0)' => '4:2:2 (цветноÑть в 0.5, 0)', + '56-bit 6 Channels Alpha' => '56-битный, 6 каналов, альфа-канал', + '56-bit 7 Channels' => '56-битный, 7 каналов', + '64-bit 3 Channels Alpha' => '64-битный, 3 канала, альфа-канал', + '64-bit 4 Channels' => '64-битный, 4 канала', + '64-bit 7 Channels Alpha' => '64-битный, 7 каналов, альфа-канал', + '64-bit 8 Channels' => '64-битный, 8 каналов', + '64-bit CMYK' => '64-битный, CMYK', + '64-bit PRGBA' => '64-битный, PRGBA', + '64-bit RGBA' => '64-битный, RGBA', + '64-bit RGBA Fixed Point' => '64-битный, RGBA Ñ Ñ„Ð¸ÐºÑированной точкой', + '64-bit RGBA Half' => '64-битный, RGBA Half', + '72-bit 8 Channels Alpha' => '72-битный, 8 каналов, альфа-канал', + '8-bit Gray' => '8-битный, Серый', + '80-bit 4 Channels Alpha' => '80-битный, 4 канала, альфа-канал', + '80-bit 5 Channels' => '80-битный, 5 канаов', + '80-bit CMYK Alpha' => '80-битный, CMYK, альфа-канал', + '96-bit 5 Channels Alpha' => '96-битный, 5 каналов, альфа-канал', + '96-bit 6 Channels' => '96-битный, 6 канаов', + '96-bit RGB Fixed Point' => '96-битный, RGB Ñ Ñ„Ð¸ÐºÑированной точкой', + 'Black & White' => 'Чёрно-белый', + 'Grayscale' => 'Оттенки Ñерого', + }, + }, + 'PixelIntensityRange' => 'Диапазон интенÑивноÑти пикÑелей (%)', + 'PixelMagicJBIGOptions' => 'Pixel Magic – Параметры JBIG', + 'PixelScale' => 'МаÑштаб пикÑелÑ', + 'PixelUnits' => { + Description => 'ПикÑельные единицы', + PrintConv => { + 'Unknown' => 'ÐеизвеÑтно', + 'meters' => 'Метры', + }, + }, + 'PixelsPerMeterX' => 'ПикÑелей на метр по X', + 'PixelsPerMeterY' => 'ПикÑелей на метр по Y', + 'PixelsPerUnitX' => 'ПикÑелей на единицу по X', + 'PixelsPerUnitY' => 'ПикÑелей на единицу по Y', + 'PlanarConfiguration' => { + Description => 'Принцип организации данных', + PrintConv => { + 'Chunky' => 'Ðепрерывный (RGBRGB)', + 'Planar' => 'По каналам (RRGGBB)', + }, + }, + 'Planes' => 'ПлоÑкоÑти', + 'PointSize' => 'Размер точки', + 'PopupFillAttributes' => 'Ðттрибуты Ð·Ð°Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð²Ñплывающего окна', + 'PortraitVersion' => 'ÐŸÐ¾Ñ€Ñ‚Ñ€ÐµÑ‚Ð½Ð°Ñ Ð¾Ñ€Ð¸ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ', + 'PoseHeadingDegrees' => 'Ðаправление по компаÑу Ð´Ð»Ñ Ñ†ÐµÐ½Ñ‚Ñ€Ð° Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ (°)', + 'PosePitchDegrees' => 'Продольный наклон Ð´Ð»Ñ Ñ†ÐµÐ½Ñ‚Ñ€Ð° Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ (°)', + 'PoseRollDegrees' => 'Поперечный наклон Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ (°)', + 'PostScriptFontName' => 'PostScript название шрифта', + 'Pragma' => 'Контроль кÑшированиÑ', + 'Predictor' => { + Description => 'Предиктор', + PrintConv => { + 'Horizontal differencing' => 'Горизонтальное дифференцирование', + 'None' => 'Ðе иÑпользовалÑÑ', + }, + }, + 'PreferredFamily' => 'Предпочитаемое ÑемейÑтво шрифта', + 'PreferredSubfamily' => 'Предпочитаемый Ñтиль шрифта', + 'Prefs' => 'ÐаÑтройки', + 'PreservedSpecimen' => 'Сохраненный образец', + 'PreservedSpecimenMaterialSampleID' => 'ID материала Ñохранённого образца', + 'PresetWhiteBalance' => { + Description => 'ПредуÑтановка баланÑа белого', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкий', + 'Camera Setting' => 'ÐаÑтройка камеры', + 'Cloudy' => 'Облачно', + 'Color Temperature' => 'Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð°', + 'Cool White Fluorescent' => 'Холодный белый (флуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð°)', + 'Day Light Fluorescent' => 'Дневной Ñвет (флуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð°)', + 'Day White Fluorescent' => 'Дневной белый (флуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð°)', + 'Daylight' => 'Дневной Ñвет', + 'Flash' => 'Ð’Ñпышка', + 'Shade' => 'Тень', + 'Specify Gray Point' => 'Ð£ÐºÐ°Ð·Ð°Ð½Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° Ñерого', + 'Tungsten' => 'Лампа накаливаниÑ', + 'Warm White Fluorescent' => 'Тёплый белый (флуореÑÑ†ÐµÐ½Ñ‚Ð½Ð°Ñ Ð»Ð°Ð¼Ð¿Ð°)', + }, + }, + 'PresetWhiteBalanceAdj' => 'ПредуÑтановка баланÑа белого', + 'Pressure' => 'Давление', + 'Preview' => 'ПредпроÑмотр', + 'PreviewApplicationName' => 'Файл предпроÑмотра – Ðазвание приложениÑ', + 'PreviewApplicationVersion' => 'Файл предпроÑмотра – ВерÑÐ¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ', + 'PreviewColorSpace' => { + Description => 'Файл предпроÑмотра – Цветовое проÑтранÑтво', + PrintConv => { + 'Unknown' => 'ÐеизвеÑтно', + }, + }, + 'PreviewCropBottom' => 'ПредпроÑмотр – Обрезка Ñнизу', + 'PreviewCropLeft' => 'ПредпроÑмотр – Обрезка Ñлева', + 'PreviewCropRight' => 'ПредпроÑмотр – Обрезка Ñправа', + 'PreviewCropTop' => 'ПредпроÑмотр – Обрезка Ñверху', + 'PreviewDateTime' => 'Файл предпроÑмотра – Дата и ВремÑ', + 'PreviewImage' => 'Файл предпроÑмотра', + 'PreviewImageLength' => 'Строк в файле предпроÑмотра', + 'PreviewImageSize' => 'Размер файла предпроÑмотра', + 'PreviewImageStart' => 'Смещение файла предпроÑмотра', + 'PreviewPDF' => 'Файл предпроÑмотра PDF', + 'PreviewPNG' => 'Файл предпроÑмотра PNG', + 'PreviewSettingsDigest' => 'Файл предпроÑмотра – Идентификатор параметров преобразованиÑ', + 'PreviewSettingsName' => 'Файл предпроÑмотра – Ðазвание параметров преобразованиÑ', + 'PreviewTIFF' => 'Файл предпроÑмотра TIFF', + 'PreviewWMF' => 'Файл предпроÑмотра WMF', + 'PrimaryChromaticities' => 'ЦветноÑть оÑновных цветов', + 'PrimaryFTP' => 'ОÑновной FTP', + 'PrintFlags' => 'Флаги печати', + 'PrintFlagsInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ флагах печати', + 'PrintIMVersion' => 'ВерÑÐ¸Ñ PrintIM', + 'PrintInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ печати', + 'PrintInfo2' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ печати 2', + 'PrintPosition' => 'Положение при печати', + 'PrintScale' => 'МаÑштаб при печати', + 'PrintStyle' => { + Description => 'Стиль печати', + PrintConv => { + 'Centered' => 'По центру', + 'Size to Fit' => 'Подогнать под формат лиÑта', + 'User Defined' => 'Определённый пользователем', + }, + }, + 'Priority' => 'Приоритет', + 'Private' => 'ЧаÑтный', + 'PrivateRTKInfo' => 'ЧаÑÑ‚Ð½Ð°Ñ Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ RTK', + 'ProTune' => { + PrintConv => { + 'Off' => 'Ðе включён', + 'On' => 'Включен', + }, + }, + 'ProcessingSoftware' => 'Редактировано в приложении', + 'ProcessingTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸', + 'ProdNotes' => 'КоличеÑтво заметок производителÑ', + 'ProducedDate' => 'Дата производÑтва', + 'Producer' => 'Производитель', + 'ProductID' => 'ID продукта', + 'ProductOrServiceConstraints' => 'ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð½Ð° продукты или уÑлуги', + 'Profile' => 'Профиль', + 'Profile1AudioCodec' => 'Профиль 1 – Кодек аудио', + 'Profile1Height' => 'Профиль 1 – Ð’Ñ‹Ñота', + 'Profile1VideoCodec' => 'Профиль 1 – Кодек видео', + 'Profile1Width' => 'Профиль 1 – Ширина', + 'ProfileCalibrationSig' => 'Сигнатура калибровки профилÑ', + 'ProfileCopyright' => 'ÐвторÑкое право профилÑ', + 'ProfileDataOffset' => 'Смещение данных профилÑ', + 'ProfileEmbedPolicy' => { + Description => 'Политика Ð²Ð½ÐµÐ´Ñ€ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ', + PrintConv => { + 'Allow Copying' => 'Разрешено копирование', + 'Embed if Used' => 'Внедрить еÑли иÑпользуетÑÑ', + 'Never Embed' => 'Ðикогда не внедрÑть', + 'No Restrictions' => 'Без ограничений', + }, + }, + 'ProfileHueSatMapData1' => 'Профиль – Карта оттенков/наÑыщенноÑти – Данные 1', + 'ProfileHueSatMapData2' => 'Профиль – Карта оттенков/наÑыщенноÑти – Данные 2', + 'ProfileHueSatMapDims' => 'Профиль – Карта оттенков/наÑыщенноÑти – Затемнение', + 'ProfileHueSatMapEncoding' => { + Description => 'Профиль – Карта оттенков/наÑыщенноÑти – Кодирование', + PrintConv => { + 'Linear' => 'Линейное', + }, + }, + 'ProfileLookTableData' => 'Профиль – Таблица подÑтановки – Данные', + 'ProfileLookTableDims' => 'Профиль – Таблица подÑтановки – Затемнение', + 'ProfileLookTableEncoding' => { + Description => 'Профиль – Таблица подÑтановки – Кодирование', + PrintConv => { + 'Linear' => 'Линейное', + }, + }, + 'ProfileName' => 'Ðазвание профилÑ', + 'ProfileSize' => 'Размер профилÑ', + 'ProfileToneCurve' => 'Профиль – Ð¢Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÑ€Ð¸Ð²Ð°Ñ', + 'ProfileType' => { + Description => 'Тип профилÑ', + PrintConv => { + 'Unspecified' => 'Ðе указан', + }, + }, + 'ProgID' => 'ID приложениÑ', + 'ProgramID' => 'ID программы', + 'ProgramMode' => { + Description => 'Режим программы', + PrintConv => { + 'Night Portrait' => 'Ðочной портрет', + 'None' => 'ОтÑутÑтвует', + 'Portrait' => 'Портрет', + 'Sports' => 'Спорт', + 'Sunset' => 'Закат', + 'Text' => 'ТекÑÑ‚', + }, + }, + 'ProgramVersion' => 'ВерÑÐ¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ', + 'ProgressiveScans' => { + Description => 'ПрогреÑÑивное Ñканирование', + PrintConv => { + '3 Scans' => '3 прохода', + '4 Scans' => '4 прохода', + '5 Scans' => '5 проходов', + }, + }, + 'Project' => 'Проект', + 'ProjectionType' => 'Тип проекции', + 'PropertyReleaseID' => 'ID ÑвойÑтв релиза', + 'PropertyReleaseStatus' => { + Description => 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÑвойÑтв релиза', + PrintConv => { + 'Limited or Incomplete Property Releases' => 'Ограниченные или неполные релизы', + 'None' => 'Ðе обозначено', + 'Not Applicable' => 'Ðе иÑпользуетÑÑ', + 'Unlimited Property Releases' => 'Ðеограниченные релизы ÑвойÑтв', + }, + }, + 'Protect' => 'Защита', + 'Provider' => 'ПоÑтавщик', + 'ProviderCopyright' => 'ПоÑтавщик – ÐвторÑкое право', + 'ProviderRating' => 'Провайдер – Рейтинг', + 'Province-State' => 'ОблаÑть/район', + 'PublishDate' => 'Дата публикации', + 'PublishDateStart' => 'Дата начала публикации', + 'Publisher' => 'Издатель', + 'PublisherLimit' => 'ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð¸Ð·Ð´Ð°Ñ‚ÐµÐ»Ñ', + 'PxShiftPeriphEdgeNR' => { + PrintConv => { + 'Off' => 'Ðе включён', + 'On' => 'Включен', + }, + }, + 'PyramidLevels' => 'Уровни пирамиды', + 'Quality' => { + Description => 'КачеÑтво', + PrintConv => { + '2 (Telephone)' => '2 (Телефон)', + '3 (Thumb)' => '3 (ЭÑкиз)', + '4 (Radio)' => '4 (Радио)', + '5 (Standard)' => '5 (Стандартное)', + '6 (Xtreme)' => '6 (ЭкÑтремальное)', + '7 (Insane)' => '7 (СумаÑшедшее)', + '8 (BrainDead)' => '8 (Полный улёт)', + 'Compressed RAW' => 'cRAW', + 'Compressed RAW + JPEG' => 'cRAW+JPEG', + 'Extra Fine' => 'СверхвыÑокое', + 'Fine' => 'Ð’Ñ‹Ñокое', + 'Low' => 'Ðизкое качеÑтво', + 'Normal' => 'Стандартное качеÑтво', + 'RAW + JPEG' => 'RAW+JPEG', + 'Standard' => 'Стандартное', + 'Unstable/Experimental' => 'ÐеÑтабильное/ЭкÑпериментальное', + }, + }, + 'QuantizationMethod' => { + Description => 'Метод квантованиÑ', + PrintConv => { + 'Color Space Specific' => 'Специфичное цветовое проÑтранÑтво', + 'Compression Method Specific' => 'Специфичный метод ÑжатиÑ', + 'Gamma Compensated' => 'КомпенÑÐ°Ñ†Ð¸Ñ Ð³Ð°Ð¼Ð¼Ñ‹', + 'Linear Density' => 'Ð›Ð¸Ð½ÐµÐ¹Ð½Ð°Ñ Ð¿Ð»Ð¾Ñ‚Ð½Ð¾Ñть', + 'Linear Reflectance/Transmittance' => 'Линейное Отражение/ПропуÑкание', + }, + }, + 'QuickEdit' => 'БыÑтрое редактирование', + 'QuickMaskInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ быÑтрой маÑке', + 'QuickShot' => { + Description => 'БыÑтрый Ñнимок', + PrintConv => { + 'Off' => 'Ðе включён', + 'On' => 'Включен', + }, + }, + 'RAFVersion' => 'ВерÑÐ¸Ñ RAF', + 'RGBCurves' => 'Кривые RGB', + 'ROIDescription' => 'ОпиÑание ROI', + 'RPP' => 'Raw Photo Processor (RPP)', + 'RasterPadding' => { + Description => 'РаÑтровое заполнение', + PrintConv => { + 'Byte' => 'Байт', + 'Long Sector' => 'Длинный Ñектор', + 'Long Word' => 'Длинное Ñлово', + 'Sector' => 'Сектор', + 'Word' => 'Слово', + }, + }, + 'RasterizedCaption' => 'РаÑтрированное опиÑание', + 'Rate' => 'Темп', + 'Rating' => 'Рейтинг', + 'RatingPercent' => 'Рейтинг (%)', + 'RawCropBottom' => 'Raw – Обрезка Ñнизу', + 'RawCropLeft' => 'Raw – Обрезка Ñлева', + 'RawCropRight' => 'Raw – Обрезка Ñправа', + 'RawCropTop' => 'Raw – Обрезка Ñверху', + 'RawData' => 'Raw-данные', + 'RawDataLength' => 'Строк в Raw-данных', + 'RawDataOffset' => 'Смещение RAW-данных', + 'RawDataUniqueID' => 'Уникальный ID RAW-данных', + 'RawDepth' => 'Глубина Raw-изображениÑ', + 'RawFile' => 'RAW-файл', + 'RawFormat' => 'RAW-формат', + 'RawImageDigest' => 'Хеш-Ñумма RAW-изображениÑ', + 'RawImageMode' => 'Режим Raw-изображениÑ', + 'RawImageSegmentation' => 'Ð¡ÐµÐ³Ð¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ RAW-изображениÑ', + 'RawToPreviewGain' => 'КоÑффициент ÑƒÐ²ÐµÐ»Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ Raw и файлом предпроÑмотра', + 'Rawrppused' => 'ИÑпользовалÑÑ RPP', + 'RawzorCreatorVersion' => 'ВерÑÐ¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ', + 'RawzorRequiredVersion' => 'Ð¢Ñ€ÐµÐ±ÑƒÐµÐ¼Ð°Ñ Ð²ÐµÑ€ÑиÑ', + 'ReaderName' => 'Ðазвание проÑмотрщика', + 'ReaderRequirements' => 'Ð¢Ñ€ÐµÐ±Ð¾Ð²Ð°Ð½Ð¸Ñ Ðº ридеру', + 'RecEngineer' => 'Инженер', + 'RecLocation' => 'ЛокациÑ', + 'RecommendedExposureIndex' => 'Рекомендуемый Ð¸Ð½Ð´ÐµÐºÑ ÑкÑпозиции', + 'Record' => 'ЗапиÑÑŒ', + 'RecordBasisOfRecord' => 'ОÑнование запиÑи', + 'RecordCollectionCode' => 'Код/Ðазвание коллекции запиÑи', + 'RecordCollectionID' => 'ID коллекции запиÑи', + 'RecordDataGeneralizations' => 'Обобщение данных запиÑи', + 'RecordDatasetID' => 'ID базы данных запиÑи', + 'RecordDatasetName' => 'Ðазвание базы данных запиÑи', + 'RecordDynamicProperties' => 'ДинамичеÑкие ÑвойÑтва запиÑи', + 'RecordInformationWithheld' => 'Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²ÑƒÑŽÑ‰Ð°Ñ Ð² запиÑи', + 'RecordInstitutionCode' => 'ÐžÑ€Ð³Ð°Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ð·Ñ†Ð° или информации', + 'RecordInstitutionID' => 'ID организации Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ð·Ñ†Ð° или информации', + 'RecordMode' => 'Режим запиÑи', + 'RecordOwnerInstitutionCode' => 'ОрганизациÑ-владелец образца или информации', + 'RecordShutterRelease' => { + Description => 'СпуÑк затвора при запиÑи', + PrintConv => { + 'Press start, press stop' => 'Ðажать «Старт», нажать «Стоп»', + 'Record while down' => 'ЗапиÑÑŒ пока нажата кнопка', + }, + }, + 'RecurrenceDateTimes' => 'Дата/Ð’Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÐµÐ½Ð¸Ñ ÑобытиÑ', + 'RecurrenceID' => 'ID повторÑющегоÑÑ ÑобытиÑ', + 'RecurrenceRule' => 'Правило повторениÑ', + 'RedBalance' => 'Ð‘Ð°Ð»Ð°Ð½Ñ ÐºÑ€Ð°Ñного', + 'RedBlueFlatField' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð¿Ð»Ð¾Ñкого Ð¿Ð¾Ð»Ñ â€“ КраÑный и Синий', + 'RedEndpoint' => 'ÐšÐ¾Ð½ÐµÑ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° КраÑного', + 'RedEyeReduction' => { + Description => 'Уменьшение Ñффекта краÑных глаз', + PrintConv => { + 'Off' => 'Ðе включено', + 'On' => 'Включено', + }, + }, + 'RedMask' => 'МаÑка КраÑного', + 'RedPrimary' => 'ОÑновной краÑный', + 'RedX' => 'КраÑный по X', + 'RedY' => 'КраÑный по Y', + 'RedcodeVersion' => 'ВерÑÐ¸Ñ Redcode', + 'ReductionMatrix1' => 'Матрица редукции 1', + 'ReductionMatrix2' => 'Матрица редукции 2', + 'ReelName' => 'Ðазвание ноÑÐ¸Ñ‚ÐµÐ»Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¸', + 'ReelNumber' => 'Ðомер запиÑи', + 'ReelTimecode' => 'Таймкод запиÑи', + 'Reference' => 'СÑылка', + 'ReferenceBlackWhite' => 'ИÑходные Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ñ‡Ñ‘Ñ€Ð½Ð¾Ð³Ð¾ и белого', + 'ReferenceDate' => 'Дата предыдущего конверта', + 'ReferenceNumber' => 'Ðомер предыдущего конверта', + 'ReferenceService' => 'СÑылка на предыдущий конверт', + 'Refresh' => 'Задержка Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ñтраницы', + 'RegionAppliedToDimensions' => 'Размеры Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ которых применÑлÑÑ Ñ€ÐµÐ³Ð¸Ð¾Ð½', + 'RegionAppliedToDimensionsH' => 'Ð’Ñ‹Ñота Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ которой применÑÐ»Ñ Ñ€ÐµÐ³Ð¸Ð¾Ð½', + 'RegionAppliedToDimensionsUnit' => 'Единицы размеров изображениÑ', + 'RegionAppliedToDimensionsW' => 'Ширина Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ которой применÑÐ»Ñ Ñ€ÐµÐ³Ð¸Ð¾Ð½', + 'RegionArea' => 'ОблаÑть региона', + 'RegionAreaD' => 'Диаметр круглой облаÑти', + 'RegionAreaH' => 'Ð’Ñ‹Ñота прÑмоугольной облаÑи', + 'RegionAreaUnit' => 'Единицы Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ð¾Ð±Ð»Ð°Ñти', + 'RegionAreaW' => 'Ширина прÑмоугольной облаÑти', + 'RegionAreaX' => 'Координаты центра облаÑти по X', + 'RegionAreaY' => 'Координаты центра облаÑти по Y', + 'RegionBarCodeValue' => 'Значение штрихкода региона', + 'RegionConstraints' => 'Региональные ограничениÑ', + 'RegionDescription' => 'ОпиÑание региона', + 'RegionExtensions' => 'Дополнительное ÑвойÑтво региона', + 'RegionFocusUsage' => { + Description => 'ИÑпользование фокуÑа', + PrintConv => { + 'Evaluated, Not Used' => 'УчитывалÑÑ, но не иÑпользовалÑÑ', + 'Evaluated, Used' => 'УчитывалÑÑ Ð¸ иÑпользовалÑÑ', + 'Not Evaluated, Not Used' => 'Ðе учитывалÑÑ Ð¸ не иÑпользовалÑÑ', + }, + }, + 'RegionInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð± регионе', + 'RegionInfoDateRegionsValid' => 'Дата ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ð¾Ñледнего региона', + 'RegionInfoMP' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ регионе в Microsoft Photo', + 'RegionInfoRegions' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ регионах', + 'RegionList' => 'СпиÑок Ñтруктуры регионов', + 'RegionName' => 'Ðазвание региона', + 'RegionPersonDisplayName' => 'Ð˜Ð¼Ñ Ñ‡ÐµÐ»Ð¾Ð²ÐµÐºÐ° отмеченное регионом', + 'RegionPersonEmailDigest' => 'Хеш-Ñумма Ñлектронной почты Windows Live пользователÑ', + 'RegionPersonLiveIdCID' => 'ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Windows Live CID', + 'RegionPersonSourceID' => 'ИÑходный ID пользователÑ', + 'RegionRectangle' => 'ПрÑмоугольник региона', + 'RegionRotation' => 'Вращение региона', + 'RegionSeeAlso' => 'Смотрите также', + 'RegionType' => { + Description => 'Тип региона', + PrintConv => { + 'BarCode' => 'Штрихкод', + 'Face' => 'Лицо', + 'Focus' => 'ФокуÑ', + 'Pet' => 'Животное', + }, + }, + 'RegionXformTackPoint' => 'Точки-модификаторы облаÑти Ð¿Ñ€ÐµÐ¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ Xform', + 'RelatedImageFileFormat' => 'Формат файла ÑвÑзанного изображениÑ', + 'RelatedImageHeight' => 'Ð’Ñ‹Ñота ÑвÑзанного изображениÑ', + 'RelatedImageWidth' => 'Ширина ÑвÑзанного изображениÑ', + 'RelatedResourceID' => 'ID ÑвÑзанного реÑурÑа', + 'RelatedSoundFile' => 'СвÑзанный аудиофайл', + 'RelatedTo' => 'ОтноÑитÑÑ Ðº', + 'Relation' => 'СÑылка на ÑвÑзанный реÑурÑ', + 'RelationshipAccordingTo' => 'СвÑзь между реÑурÑами Ñоздал', + 'RelationshipEstablishedDate' => 'Дата ÑвÑзи между реÑурÑами', + 'RelationshipOfResource' => 'СвÑзь реÑурÑов', + 'RelationshipRemarks' => 'Комментарии к ÑвÑзи реÑурÑов', + 'RelativeAltitude' => 'ОтноÑÐ¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð²Ñ‹Ñота', + 'RelativePath' => 'ОтноÑительный путь', + 'ReleaseDate' => 'Дата публикации', + 'ReleaseTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¿ÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ð¸', + 'RemoveHistoryDuplicates' => 'Удалить иÑторию дубликатов', + 'RenderingIntent' => { + Description => 'Методы цветового преобразованиÑ', + PrintConv => { + 'Absolute Colorimetric (LCS_GM_ABS_COLORIMETRIC)' => 'ÐбÑолютный колориметричеÑкий (LCS_GM_ABS_COLORIMETRIC)', + 'Graphic (LCS_GM_BUSINESS)' => 'По наÑыщенноÑти (LCS_GM_BUSINESS)', + 'Picture (LCS_GM_IMAGES)' => 'Перцепционный (LCS_GM_IMAGES)', + 'Proof (LCS_GM_GRAPHICS)' => 'ОтноÑительный колориметричеÑкий (LCS_GM_GRAPHICS)', + }, + }, + 'RenderingTransform' => 'Преобразование цвета', + 'RentalExpirationDate' => 'Дата Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð°Ñ€ÐµÐ½Ð´Ñ‹', + 'RentalFlag' => 'Флаг аренды', + 'Repeat' => 'КоличеÑтво повторов', + 'ReplayGainAlbumGain' => 'Replay Gain – ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð³Ñ€Ð¾Ð¼ÐºÐ¾Ñти альбома', + 'ReplayGainAlbumPeak' => 'Replay Gain – Пиковый уровень альбома', + 'ReplayGainTrackGain' => 'Replay Gain – ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð³Ñ€Ð¾Ð¼ÐºÐ¾Ñти трека', + 'ReplayGainTrackPeak' => 'Replay Gain – Пиковый уровень трека', + 'ReplyTo' => 'E-Mail вебмаÑтера', + 'RepresentativeDisparityFar' => 'Ð ÐµÐ¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ‚Ð¸Ð²Ð½Ð°Ñ Ð½ÐµÑоглаÑованноÑть – Вдали', + 'RepresentativeDisparityNear' => 'Ð ÐµÐ¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ‚Ð¸Ð²Ð½Ð°Ñ Ð½ÐµÑоглаÑованноÑть – РÑдом', + 'RepresentativeImage' => { + Description => 'Репрезентативное изображение', + PrintConv => { + 'Left Viewpoint' => 'Ð›ÐµÐ²Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° проÑмотра', + 'Right Viewpoint' => 'ÐŸÑ€Ð°Ð²Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° проÑмотра', + }, + }, + 'RequestID' => 'ID запрÑа', + 'RequestStatus' => 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ð¿Ñ€Ð¾Ñа', + 'Resaved' => { + Description => 'Повторное Ñохранение', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'Reserved5' => 'Зарезервировано 5', + 'Resolution' => 'Разрешение изображениÑ', + 'ResolutionUnit' => { + Description => 'Единицы Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¿Ð¾ X и Y', + PrintConv => { + 'None' => 'Ðе указаны', + 'cm' => 'Ñм', + 'inches' => 'дюймы', + }, + }, + 'ResourceCount' => 'КоличеÑтво реÑурÑов', + 'ResourceForkSize' => 'Размер Ñ€Ð°Ð·Ð²ÐµÑ‚Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€ÐµÑурÑа', + 'ResourceID' => 'ID реÑурÑа', + 'ResourceRelationship' => 'СвÑзи реÑурÑов', + 'ResourceRelationshipID' => 'ID ÑвÑзи между реÑурÑами', + 'ResourceType' => 'Тип реÑурÑа', + 'Resources' => 'РеÑурÑÑ‹', + 'RetailPrice' => 'Ð Ð¾Ð·Ð½Ð¸Ñ‡Ð½Ð°Ñ Ñ†ÐµÐ½Ð°', + 'RetailPriceCurrency' => 'Валюта розничной цены', + 'Reuse' => { + Description => 'Повторное иÑпользование', + PrintConv => { + 'Not Applicable' => 'Запрещено', + 'Repeat Use' => 'Разрешено', + }, + }, + 'Review' => 'Обзор', + 'Revision' => 'Дата Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ vCard', + 'RevisionDate' => 'Дата ревизии', + 'RevisionNumber' => 'КоличеÑтво редакций файла', + 'RevisitAfter' => 'Повторно поÑетить через', + 'RicohPitch' => 'Ricoh – Тангаж (Ðаклон)', + 'RicohRoll' => 'Ricoh – Крен (Вращение)', + 'RightAlbedo' => 'Правильное ÑтереоÑкопичеÑкое изображение', + 'Rights' => 'Права', + 'Robots' => 'Политика обработки Ñтраницы роботами', + 'Roll' => 'Крен (Вращение)', + 'RollAngle' => 'Угол Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ (Крен)', + 'RootDirectoryCreateDate' => 'Дата ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÐºÐ¾Ñ€Ð½ÐµÐ²Ð¾Ð³Ð¾ каталога', + 'Rotation' => 'Вращение', + 'RoundTripVersion' => 'ВерÑÐ¸Ñ Round Trip', + 'Routing' => 'Роутинг', + 'RoutingDestinations' => 'Ðазначение маршрутизации', + 'RoutingExclusions' => 'ИÑÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¼Ð°Ñ€ÑˆÑ€ÑƒÑ‚Ð¸Ð·Ð°Ñ†Ð¸Ð¸', + 'RowInterleaveFactor' => 'КоÑффициент Ñ‡ÐµÑ€ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ñтрок', + 'RowsPerStrip' => 'КоличеÑтво Ñтрок на полоÑу', + 'RtkFlag' => 'Rtk – Флаг', + 'RtkStdHgt' => 'Rtk – Стандартное отклонение выÑоты', + 'RtkStdLat' => 'Rtk – Стандартное отклонение широты', + 'RtkStdLon' => 'Rtk – Стандартное отклонение долготы', + 'RunTimeEpoch' => 'Ð’Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ â€“ Ðомер Ñпохи', + 'RunTimeFlags' => { + Description => 'Ð’Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ â€“ Флаги', + PrintConv => { + 'Has been rounded' => 'Округленное времÑ', + 'Indefinite' => 'Ðеопределённое времÑ', + 'Negative infinity' => 'ÐœÐ¸Ð½ÑƒÑ Ð±ÐµÑконечноÑть', + 'Positive infinity' => 'ÐŸÐ»ÑŽÑ Ð±ÐµÑконечноÑть', + 'Valid' => 'ДейÑтвительное времÑ', + }, + }, + 'RunTimeScale' => 'Ð’Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ â€“ Шкала времени', + 'RunTimeSincePowerUp' => 'Ð’Ñ€ÐµÐ¼Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° включениÑ', + 'RunTimeValue' => 'Ð’Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ â€“ Значение', + 'RunWindow' => { + Description => 'ЗапуÑтить окно', + PrintConv => { + 'Hide' => 'Скрытый', + 'Minimized' => 'Минимизировать', + 'Normal' => 'Обычный', + 'Restore' => 'ВоÑÑтановить', + 'Show' => 'Показывать', + 'Show Default' => 'Отображать по-умолчанию', + 'Show Maximized' => 'Отображать развёрнутым', + 'Show Minimized' => 'Отображать Ñвернутым', + 'Show Minimized No Activate' => 'Отображать Ñвернутым и не активвным', + 'Show No Activate' => 'Отображать', + }, + }, + 'SEMInfo' => 'SEM – ОпиÑание', + 'SIUnits' => 'Единицы SI', + 'SMaxSampleValue' => 'МакÑимальное значение компонента', + 'SMinSampleValue' => 'Минимальное значение компонента', + 'SRGBRendering' => { + Description => 'SRGB-рендеринг', + PrintConv => { + 'Absolute Colorimetric' => 'ÐбÑолютно колориметричеÑкий', + 'Perceptual' => 'Перцепционный', + 'Relative Colorimetric' => 'ОтноÑительно колориметричеÑкий', + 'Saturation' => 'ÐаÑыщенный', + }, + }, + 'SRawType' => 'Тип SRaw', + 'SampleFlag' => 'Флаг образца', + 'SampleFormat' => { + Description => 'Формат компонента', + PrintConv => { + 'Complex float' => 'КомплекÑные данные Ñ Ð¿Ð»Ð°Ð²Ð°ÑŽÑ‰ÐµÐ¹ точкой IEEE', + 'Complex int' => 'КомплекÑные целые данные', + 'Float' => 'Данные Ñ Ð¿Ð»Ð°Ð²Ð°ÑŽÑ‰ÐµÐ¹ точкой IEEE', + 'Signed' => 'ЦелочиÑленные данные Ñо знаком', + 'Undefined' => 'Ðеопределенный формат данных', + 'Unsigned' => 'Беззнаковые целочиÑленные данные', + }, + }, + 'SampleRate' => 'ЧаÑтота диÑкретизации', + 'SampleRate2' => 'ЧаÑтота диÑкретизации 2', + 'SampleSize' => 'Размер', + 'SampleStructure' => { + Description => 'Структура компонента', + PrintConv => { + 'CompressionDependent' => 'ЗавиÑÐ¸Ð¼Ð°Ñ Ð¾Ñ‚ ÑжатиÑ', + 'Orthogonal4-2-2Sampling' => 'ÐžÑ€Ñ‚Ð¾Ð³Ð¾Ð½Ð°Ð»ÑŒÐ½Ð°Ñ Ñ Ñ‡Ð°Ñтотами диÑкретизации в Ñоотношении 4: 2: 2: (4)', + 'OrthogonalConstangSampling' => 'ÐžÑ€Ñ‚Ð¾Ð³Ð¾Ð½Ð°Ð»ÑŒÐ½Ð°Ñ Ñ Ñ‚ÐµÐ¼Ð¸ же отноÑительными чаÑтотами диÑкретизации в каждом компоненте', + }, + }, + 'SampleText' => 'Пример текÑта', + 'SamplesPerPixel' => 'КоличеÑтво компонентов на пикÑель', + 'SamsungRawByteOrder' => 'Samsung RAW – ПорÑдок байтов', + 'SamsungRawPointersLength' => 'Samsung RAW – Длина указателей', + 'SamsungRawPointersOffset' => 'Samsung RAW – Смещение указателей', + 'SamsungRawUnknown' => 'Samsung RAW – ÐеизвеÑтно', + 'SanyoQuality' => { + Description => 'Sanyo – КачеÑтво', + PrintConv => { + 'Fine/High' => 'Хорошее/Ð’Ñ‹Ñокое', + 'Fine/Low' => 'Хорошее/Ðизкое', + 'Fine/Medium' => 'Хорошее/Среднее', + 'Fine/Medium High' => 'Хорошее/Выше Ñреднего', + 'Fine/Medium Low' => 'Хорошее/Ðиже Ñреднего', + 'Fine/Super High' => 'Хорошее/Лучшее', + 'Fine/Very High' => 'Хорошее/Очень выÑокое', + 'Fine/Very Low' => 'Хорошее/Очень низкое', + 'Normal/High' => 'Ðормальное/Ð’Ñ‹Ñокое', + 'Normal/Low' => 'Ðормальное/Ðизкое', + 'Normal/Medium' => 'Ðормальное/Среднее', + 'Normal/Medium High' => 'Ðормальное/Выше Ñреднего', + 'Normal/Medium Low' => 'Ðормальное/Ðиже Ñреднего', + 'Normal/Super High' => 'Ðормальное/Лучшее', + 'Normal/Very High' => 'Ðормальное/Очень выÑокое', + 'Normal/Very Low' => 'Ðормальное/Очень низкое', + 'Super Fine/High' => 'Лучшее/Ð’Ñ‹Ñокое', + 'Super Fine/Low' => 'Лучшее/Ðизкое', + 'Super Fine/Medium' => 'Лучшее/Среднее', + 'Super Fine/Medium High' => 'Лучшее/Выше Ñреднего', + 'Super Fine/Medium Low' => 'Лучшее/Ðиже Ñреднего', + 'Super Fine/Super High' => 'Лучшее/Лучшее', + 'Super Fine/Very High' => 'Лучшее/Очень выÑокое', + 'Super Fine/Very Low' => 'Лучшее/Очень низкое', + }, + }, + 'SanyoThumbnail' => 'Sanyo – Миниатюра', + 'Saturation' => { + Description => 'ÐаÑыщенноÑть', + PrintConv => { + 'High' => 'Ð’Ñ‹ÑокаÑ', + 'Low' => 'ÐизкаÑ', + 'Normal' => 'СтандартнаÑ', + }, + }, + 'SaturationAdj' => 'Регулировка наÑыщенноÑти', + 'ScaleFactor' => 'КоÑффициент маÑштабированиÑ', + 'ScaleFactor35efl' => 'КоÑффициент маÑÑˆÑ‚Ð°Ð±Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ðº 35-мм формату', + 'ScaledIMU' => 'МаÑштабируемый IMU', + 'ScaledPressure' => 'ВычиÑленное давление', + 'ScalingFactor' => 'КоÑффициент маÑштабированиÑ', + 'ScanningDirection' => { + Description => 'Ðаправление ÑканированиÑ', + PrintConv => { + 'Bottom-Top, L-R' => 'Снизу вверх, Ñлева направо', + 'Bottom-Top, R-L' => 'Снизу вверх, Ñправа налево', + 'L-R, Bottom-Top' => 'Слева направо, Ñнизу вверх', + 'L-R, Top-Bottom' => 'Слева направо, Ñверху вниз', + 'R-L, Bottom-Top' => 'Справа налево, Ñнизу вверх', + 'R-L, Top-Bottom' => 'Справа налево, Ñверху вниз', + 'Top-Bottom, L-R' => 'Сверху вниз, Ñлева направо', + 'Top-Bottom, R-L' => 'Сверху вниз, Ñправа налево', + }, + }, + 'Scene' => 'Ðомер изображениÑ', + 'SceneCaptureType' => { + Description => 'Тип Ñнимаемой Ñцены', + PrintConv => { + 'Landscape' => 'Пейзаж', + 'Night' => 'ÐÐ¾Ñ‡Ð½Ð°Ñ Ñъёмка', + 'Other' => 'Другой', + 'Portrait' => 'Портрет', + 'Standard' => 'Стандартный режим', + }, + }, + 'SceneMode' => { + Description => 'Выбор Ñцены', + PrintConv => { + '3D Sweep Panorama' => '3D', + 'Anti Motion Blur' => 'УÑтранение ÑмазываниÑ', + 'Auto' => 'Ðвто', + 'Cont. Priority AE' => 'ÐвтоÑкÑÐ¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ Ñ Ð¿Ñ€Ð¸Ð¾Ñ€Ð¸Ñ‚ÐµÑ‚Ð¾Ð¼ Ñерийной Ñъёмки', + 'Handheld Night Shot' => 'ÐÐ¾Ñ‡Ð½Ð°Ñ Ñъёмка Ñ Ñ€ÑƒÐº', + 'Landscape' => 'Пейзаж', + 'Macro' => 'МакроÑъёмка', + 'Night Portrait' => 'Ðочной портрет', + 'Night Scene' => 'Ðочной вид', + 'Night View/Portrait' => 'Ðочной вид/портрет', + 'Portrait' => 'Портрет', + 'Sports' => 'Спортивные Ñцены', + 'Sunset' => 'Закат', + 'Sweep Panorama' => 'Панорамный обзор', + }, + }, + 'SceneSelect' => { + Description => 'Выбор Ñцены', + PrintConv => { + 'Lamp' => 'Лампа', + 'Night' => 'Ðочь', + 'Off' => 'Ðе задейÑтвован', + 'Sport' => 'Спорт', + 'User 1' => 'ПользовательÑÐºÐ°Ñ 1', + 'User 2' => 'ПользовательÑÐºÐ°Ñ 2', + }, + }, + 'SceneType' => { + Description => 'Тип Ñцены', + PrintConv => { + 'Directly photographed' => 'Сфотографировано цифровой камерой', + }, + }, + 'School' => 'Школа', + 'ScreenBufferSize' => 'Размер буфера Ñкрана', + 'ScreenHeight' => 'Ð’Ñ‹Ñота Ñкрана', + 'ScreenWidth' => 'Ширина Ñкрана', + 'ScreenWindowCenter' => 'Окно по центру Ñкрана', + 'ScreenWindowWidth' => 'Окно по ширине Ñкрана', + 'SecondaryFTP' => 'Дополнительный FTP', + 'SecurityClassification' => { + Description => 'КлаÑÑÐ¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð±ÐµÐ·Ð¾Ð¿Ð°ÑноÑти', + PrintConv => { + 'Confidential' => 'Личное', + 'Restricted' => 'Ð”Ð»Ñ ÑƒÐ·ÐºÐ¾Ð³Ð¾ круга', + 'Secret' => 'Секретно', + 'Top Secret' => 'Совершенно Ñекретно', + 'Unclassified' => 'Без клаÑÑификации', + }, + }, + 'SeekTable' => 'Таблица поиÑка', + 'SelfData' => 'СобÑтвенные данные', + 'SelfTimer' => { + Description => 'Режим автоÑпуÑка затвора', + PrintConv => { + 'Off' => 'Ðе задейÑтвован', + 'On' => 'Включен', + }, + }, + 'SelfTimerMode' => 'Режим таймера ÑпуÑка затвора', + 'SensingMethod' => { + Description => 'Тип датчика', + PrintConv => { + 'Color sequential area' => 'Цветной поÑледовательный ÑенÑор', + 'Color sequential linear' => 'Цветной поÑледовательный линейный ÑенÑор', + 'Monochrome area' => 'Монохромный ÑенÑор', + 'Monochrome linear' => 'Монохромный линейный ÑенÑор', + 'Not defined' => 'Ðе опредеен', + 'One-chip color area' => 'Одночиповый цветной ÑенÑор', + 'Three-chip color area' => 'Трехчиповый цветной ÑенÑор', + 'Trilinear' => 'Трехлинейный цветной ÑенÑор', + 'Two-chip color area' => 'Двухчиповый цветной ÑенÑор', + }, + }, + 'SensitivityType' => { + Description => 'Тип чувÑтвительноÑти', + PrintConv => { + 'ISO Speed' => 'ЧувÑтвительноÑть ISO', + 'Recommended Exposure Index' => 'Рекомендуемый Ð¸Ð½Ð´ÐµÐºÑ ÑкÑпозиции', + 'Recommended Exposure Index and ISO Speed' => 'Рекомендуемый Ð¸Ð½Ð´ÐµÐºÑ ÑкÑпозиции и ISO', + 'Standard Output Sensitivity' => 'Ð¡Ñ‚Ð°Ð½Ð´Ð°Ñ€Ñ‚Ð½Ð°Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð½Ð°Ñ Ñ‡ÑƒÐ²ÑтвительноÑть', + 'Standard Output Sensitivity and ISO Speed' => 'Ð¡Ñ‚Ð°Ð½Ð´Ð°Ñ€Ñ‚Ð½Ð°Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð½Ð°Ñ Ñ‡ÑƒÐ²ÑтвительноÑть и ISO', + 'Standard Output Sensitivity and Recommended Exposure Index' => 'Ð¡Ñ‚Ð°Ð½Ð´Ð°Ñ€Ñ‚Ð½Ð°Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð½Ð°Ñ Ñ‡ÑƒÐ²ÑтвительноÑть и рекомендуемый Ð¸Ð½Ð´ÐµÐºÑ ÑкÑпозиции', + 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed' => 'Ð¡Ñ‚Ð°Ð½Ð´Ð°Ñ€Ñ‚Ð½Ð°Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð½Ð°Ñ Ñ‡ÑƒÐ²ÑтвительноÑть и рекомендуемый Ð¸Ð½Ð´ÐµÐºÑ ÑкÑпозиции и ISO', + 'Unknown' => 'ÐеизвеÑтно', + }, + }, + 'Sensor' => 'СенÑор', + 'SensorBottomBorder' => 'ÐижнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° ÑенÑора', + 'SensorCalibration_0x0404' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора 0x0404', + 'SensorCalibration_0x0405' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора 0x0405', + 'SensorCalibration_0x0406' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора 0x0406', + 'SensorCalibration_0x0408' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора 0x0408', + 'SensorCalibration_0x040f' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора 0x040f', + 'SensorCalibration_0x0413' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора 0x0413', + 'SensorCalibration_0x0414' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора 0x0414', + 'SensorCalibration_0x0418' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора 0x0418', + 'SensorCalibration_0x041c' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора 0x041c', + 'SensorCalibration_0x041e' => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ ÑенÑора 0x041e', + 'SensorDefects' => 'Дефекты ÑенÑора', + 'SensorHeight' => 'Ð’Ñ‹Ñота ÑенÑора', + 'SensorLeftBorder' => 'Ð›ÐµÐ²Ð°Ñ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° ÑенÑора', + 'SensorLeftMargin' => 'Матрица – ОтÑтуп Ñлева', + 'SensorRightBorder' => 'ÐŸÑ€Ð°Ð²Ð°Ñ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° ÑенÑора', + 'SensorSerialNumber' => 'Серийный номер ÑенÑора', + 'SensorTemperature' => 'Датчик температуры', + 'SensorTemperature2' => 'Датчик температуры 2', + 'SensorTopBorder' => 'ВерхнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° ÑенÑора', + 'SensorTopMargin' => 'Матрица – ОтÑтуп Ñправа', + 'SensorWidth' => 'Ширина ÑенÑора', + 'Sequence' => 'ПоÑледовательноÑть', + 'SequenceNumber' => 'ПорÑдковый номер', + 'SequenceShotInterval' => { + Description => 'Интервал поÑледовательных Ñнимков', + PrintConv => { + '10 frames/s' => '10 кадров/Ñ', + '15 frames/s' => '15 кадров/Ñ', + '20 frames/s' => '20 кадров/Ñ', + '5 frames/s' => '5 кадров/Ñ', + }, + }, + 'SequentialShot' => { + Description => 'ПоÑледовательноÑть Ñнимков', + PrintConv => { + 'Adjust Exposure' => 'По Ñдвигу ÑкÑпозиции', + 'Best' => 'Лучший Ñнимок', + 'None' => 'Ðет', + 'Standard' => 'СтандартнаÑ', + }, + }, + 'SerialNumber' => 'Серийный номер камеры', + 'Series' => 'СериÑ', + 'SeriesDateTime' => 'Дата Ñерии', + 'SeriesDescription' => 'ОпиÑание Ñрии', + 'SeriesModality' => 'МодальноÑть Ñерии', + 'SeriesNumber' => 'Ðомер Ñерии', + 'ServiceID' => 'ID ÑервиÑа', + 'ServiceIdentifier' => 'Идентификатор поÑтавщика и продукта', + 'SetCookie' => 'ÐаÑтройки cookie', + 'ShadingCompensation' => 'УÑтранение теней', + 'ShadowScale' => 'Диапазон теней', + 'Shadows' => 'Тени', + 'ShadowsAdj' => 'Регулировка теней', + 'SharedData' => 'Общие данные', + 'Sharpness' => { + Description => 'РезкоÑть', + PrintConv => { + 'Hard' => 'СильнаÑ', + 'Normal' => 'СтандартнаÑ', + 'Soft' => 'СлабаÑ', + }, + }, + 'SharpnessAdj' => 'Регулировка резкоÑтти', + 'SharpnessOvershoot' => 'РезкоÑть – Положительное отклонение', + 'SharpnessThreshold' => 'РезкоÑть – Порог', + 'SharpnessUndershoot' => 'РезкоÑть – Отрицательное отклонение', + 'ShootingCount' => 'КоличеÑтво Ñнимков Ñ Ð¾Ð±ÐµÐ¸Ñ… точек проÑмотра', + 'ShootingMode' => 'Режим Ñъёмки', + 'ShortDocumentID' => 'Краткий ID документа', + 'ShutterCurtainHack' => { + Description => 'Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ ÑˆÑ‚Ð¾Ñ€ÐºÐ¸ затвора', + PrintConv => { + '1st-curtain sync' => 'Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¿Ð¾ 1-й шторке', + '2nd-curtain sync' => 'Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¿Ð¾ 2-й шторке', + }, + }, + 'ShutterSpeed' => 'СкороÑть ÑÑ€Ð°Ð±Ð°Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð·Ð°Ñ‚Ð²Ð¾Ñ€Ð°', + 'ShutterSpeedValue' => 'СкороÑть ÑÑ€Ð°Ð±Ð°Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð·Ð°Ñ‚Ð²Ð¾Ñ€Ð°', + 'Sidebars' => 'КоличеÑтво боковых панелей', + 'SidecarForExtension' => 'РаÑширение файла', + 'Signature' => 'ПодпиÑÑŒ', + 'SignatureUsageRights' => 'Права на иÑпользование ПодпиÑи', + 'SignerContactInfo' => 'Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ – ÐšÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ', + 'SignificantBits' => 'Старшие биты', + 'SigningAuthority' => 'Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ – Орган', + 'SigningDate' => 'Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ – Дата подпиÑи', + 'SigningLocation' => 'Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ – МеÑтоположение', + 'SigningReason' => 'Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ – Причина подпиÑаниÑ', + 'SimilarityIndex' => 'Ð˜Ð½Ð´ÐµÐºÑ ÑходÑтва', + 'Site' => 'Сайт', + 'SiteEnter' => 'Эффект при заходе на Ñайт', + 'SiteExit' => 'Эффект при уходе Ñ Ñайта', + 'SizeMode' => { + Description => 'Режим размера', + PrintConv => { + 'Size Known' => 'Размер извеÑтный', + 'Size Not Known' => 'Размер не извеÑтен', + }, + }, + 'SlateInformation' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ Slate', + 'SlicesGroupName' => 'Ðазвание группы фрагментов', + 'Smoothness' => 'Сглаживание', + 'Snapshots' => 'Скриншоты', + 'SocTemperature' => 'Температура на чипе (Soc)', + 'SocialProfile' => 'Социальный профиль', + 'Software' => { + Description => 'Приложение', + PrintConv => { + 'PC Paintbrush 2.8 (with palette)' => 'PC Paintbrush 2.8 (Ñ Ð¿Ð°Ð»Ð¸Ñ‚Ñ€Ð¾Ð¹)', + 'PC Paintbrush 2.8 (without palette)' => 'PC Paintbrush 2.8 (без палитры)', + 'PC Paintbrush for Windows' => 'PC Paintbrush Ð´Ð»Ñ Windows', + }, + }, + 'SoftwareVersion' => 'ВерÑÐ¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ', + 'SonyCropSize' => 'Sony – Размер рамки кадрированиÑ', + 'SonyCropTopLeft' => 'Sony – Верхний левый угол рамки кадрированиÑ', + 'SonyRawFileType' => { + Description => 'Sony – Тип RAW-файла', + PrintConv => { + 'Sony Compressed RAW' => 'Sony – Ñжатый RAW', + 'Sony Lossless Compressed RAW' => 'Sony – Ñжатый без потерь RAW', + 'Sony Uncompressed 12-bit RAW' => 'Sony – 12-битный неÑжатый RAW', + 'Sony Uncompressed 14-bit RAW' => 'Sony – 14-битный неÑжатый RAW', + }, + }, + 'SonyToneCurve' => 'Sony – Ð¢Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÑ€Ð¸Ð²Ð°Ñ', + 'Sound' => 'ЗвукозапиÑÑŒ', + 'Source' => 'ИÑточник', + 'SourceCount' => 'КоличеÑтво иÑпользованых камер', + 'SourceCreateDate' => 'ИÑÑ…Ð¾Ð´Ð½Ð°Ñ Ð´Ð°Ñ‚Ð° ÑозданиÑ', + 'SourceData' => 'ИÑходные данные', + 'SourceDate' => 'Дата публикации', + 'SourceEdition' => 'Печатное издание', + 'SourceFileName' => 'Ðазвание иÑходного файла', + 'SourcePhotosCount' => 'КоличеÑтво иÑходных изображений', + 'SourcePublisher' => 'ИздательÑтво', + 'SourceRights' => 'Права печатного изданиÑ', + 'SourceTitle' => 'Ðазвание издательÑтва', + 'SpatialFrequencyResponse' => 'Отклик проÑтранÑтвенной чаÑтоты', + 'SpatialResolution' => 'ПроÑтранÑтвенное разрешение', + 'SpecialInstructions' => 'Специальные указаниÑ', + 'SpecialMode' => 'Специальный режим', + 'SpecialTypeID' => 'ID Ñпециального типа', + 'SpectralSensitivity' => 'Ð¡Ð¿ÐµÐºÑ‚Ñ€Ð°Ð»ÑŒÐ½Ð°Ñ Ñ‡ÑƒÐ²ÑтвительноÑть', + 'SpeedX' => 'СкороÑть по оÑи X', + 'SpeedY' => 'СкороÑть по оÑи Y', + 'SpeedZ' => 'СкороÑть по оÑи Z', + 'Spherical' => 'СферичеÑкое видео', + 'SplitColumn' => 'Ð Ð°Ð·Ð´ÐµÐ»ÐµÐ½Ð½Ð°Ñ ÐºÐ¾Ð»Ð¾Ð½ÐºÐ°', + 'SpotHalftone' => 'Точка полутона', + 'StandardOutputSensitivity' => 'Ð¡Ñ‚Ð°Ð½Ð´Ð°Ñ€Ñ‚Ð½Ð°Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð½Ð°Ñ Ñ‡ÑƒÐ²ÑтвительноÑть', + 'StartEdgeCode' => 'Edge код Ñтарта', + 'StartReading' => 'Ðачало чтениÑ', + 'StartTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð½Ð°Ñ‡Ð°Ð»Ð°', + 'StartTimecode' => 'Таймкод Ñтарта', + 'State' => 'ОблаÑть', + 'Status' => 'СтатуÑ', + 'Stereo' => 'Стерео', + 'StereoMode' => 'Режим Ñтерео', + 'StimVersion' => 'ВерÑÐ¸Ñ Stim', + 'Stitched' => 'Ð¡ÑˆÐ¸Ñ‚Ð°Ñ Ð²Ð¸Ð´ÐµÐ¾Ð¿Ð°Ð½Ð¾Ñ€Ð°Ð¼Ð°', + 'StitchingSoftware' => 'Сшито в программе', + 'StoNits' => 'ОÑвещенноÑть (коÑффициент переÑчета кд/м2)', + 'StorageFormatDate' => 'ПамÑть – Дата форматированиÑ', + 'StorageFormatTime' => 'ПамÑть – Ð’Ñ€ÐµÐ¼Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ', + 'StorageMethod' => 'СпоÑоб хранениÑ', + 'StorageModel' => 'ПамÑть – Модель', + 'StorageSerialNumber' => 'ПамÑть – Серийный номер', + 'StorageType' => 'ПамÑть – Тип', + 'StreamAvgBitrate' => 'Средний битрейт потока', + 'StreamAvgPacketSize' => 'Размер Ñреднего битрейта потока', + 'StreamDuration' => 'ПродолжительноÑть потока', + 'StreamMaxBitrate' => 'МакÑимальный битрейт потока', + 'StreamMaxPacketSize' => 'Размер макÑимального битрейта потока', + 'StreamMimeLen' => 'Mime потока Len', + 'StreamMimeType' => 'Mime-тип потока', + 'StreamName' => 'Ðазвание потока', + 'StreamNameLen' => 'Ðазвание потока Len', + 'StreamNumber' => 'Ðомер потока', + 'StreamPreroll' => 'Предварительный проÑмотр потока', + 'StreamStartTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ð½Ð°Ñ‡Ð°Ð»Ð° потока', + 'StreamType' => 'Тип потока', + 'Strikeout' => 'Перечёркнутый', + 'StripByteCounts' => 'КоличеÑтво байт на полоÑу', + 'StripOffsets' => 'Смещение Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ полоÑÑ‹ изображениÑ', + 'StripRowCounts' => 'КоличеÑтво полоÑ', + 'StrobeTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ñтроба', + 'StructureType' => 'Тип конÑтрукции', + 'StudyDateTime' => 'Дата обÑледованиÑ', + 'StudyDescription' => 'ОпиÑание обÑледованиÑ', + 'StudyID' => 'ID иÑÑледованиÑ', + 'StudyPhysician' => 'Врач проводивший обÑледование', + 'Sub-location' => 'МеÑтоположение в городе', + 'SubFile' => 'Подфайл', + 'SubPacketH' => 'Субпакет H', + 'SubPacketSize' => 'Размер Ñубпакета', + 'SubSecCreateDate' => 'Дата, Ð²Ñ€ÐµÐ¼Ñ Ð¸ милиÑекунды ÑозданиÑ', + 'SubSecDateTimeOriginal' => 'ИÑходные дата, Ð²Ñ€ÐµÐ¼Ñ Ð¸ милиÑекунды ÑозданиÑ', + 'SubSecModifyDate' => 'Дата, Ð²Ñ€ÐµÐ¼Ñ Ð¸ милиÑекунды редактированиÑ', + 'SubSecTime' => 'Ð’Ñ€ÐµÐ¼Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² милиÑекундах', + 'SubSecTimeDigitized' => 'Ð’Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° в милиÑекундах', + 'SubSecTimeOriginal' => 'Ð’Ñ€ÐµÐ¼Ñ Ñъёмки в милиÑекундах', + 'SubTileBlockSize' => 'Размер блока подзаголовка', + 'SubfileType' => { + Description => 'Тип подфайла', + PrintConv => { + 'Alternate reduced-resolution image' => 'Ðльтернативное изображение Ñ Ð¿Ð¾Ð½Ð¸Ð¶ÐµÐ½Ð½Ñ‹Ð¼ разрешением', + 'Color IW44' => 'Цветной IW44', + 'Full-resolution image' => 'Изображение Ñ Ð¿Ð¾Ð»Ð½Ñ‹Ð¼ разрешением', + 'Grayscale IW44' => 'Ð“Ñ€Ð°Ð´Ð°Ñ†Ð¸Ñ Ñерого IW44', + 'Multi-page document' => 'МногоÑтраничный документ', + 'Reduced-resolution image' => 'Изображение Ñ Ð¿Ð¾Ð½Ð¸Ð¶ÐµÐ½Ð½Ñ‹Ð¼ разрешением', + 'Shared component' => 'Общий компонент', + 'Single page of multi-page image' => 'Одна Ñтраница из многоÑтраничного изображениÑ', + 'Single page of multi-page reduced-resolution image' => 'Одна Ñтраница из многоÑтраничного Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð½Ð¸Ð¶ÐµÐ½Ð½Ñ‹Ð¼ разрешением', + 'Single-page image' => 'ОдноÑтраничное изображение', + 'TIFF-FX mixed raster content' => 'Смешанное раÑтровое Ñодержимое TIFF-FX', + 'TIFF/IT final page' => 'ПоÑледнÑÑ Ñтраница TIFF/IT', + 'Thumbnail image' => 'Миниатюра', + 'Transparency mask' => 'МаÑка прозрачноÑти изображениÑ', + 'Transparency mask of multi-page image' => 'МаÑка прозрачноÑти многоÑтраничного изображениÑ', + 'Transparency mask of reduced-resolution image' => 'МаÑка прозрачноÑти Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð½Ð¸Ð¶ÐµÐ½Ð½Ñ‹Ð¼ разрешением', + 'Transparency mask of reduced-resolution multi-page image' => 'МаÑка прозрачноÑти многоÑтраничного Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð½Ð¸Ð¶ÐµÐ½Ð½Ñ‹Ð¼ разрешением', + 'invalid' => 'Ðекорректный', + }, + }, + 'Subject' => 'Тема', + 'SubjectArea' => 'ОблаÑть объекта', + 'SubjectCode' => 'Предметный код', + 'SubjectDistance' => 'ДиÑÑ‚Ð°Ð½Ñ†Ð¸Ñ Ðº фокуÑируемому объекту', + 'SubjectDistanceRange' => { + Description => 'РаÑÑтоÑние до Ñнимаемого объекта', + PrintConv => { + 'Close' => 'Съёмка Ñ Ð±Ð»Ð¸Ð·ÐºÐ¾Ð³Ð¾ раÑÑтоÑниÑ', + 'Distant' => 'Съёмка Ñ Ð´Ð°Ð»ÑŒÐ½ÐµÐ³Ð¾ раÑÑтоÑниÑ', + 'Macro' => 'Макро Ñъёмка', + 'Unknown' => 'ÐеизвеÑтно', + }, + }, + 'SubjectLocation' => 'РаÑположение объекта', + 'SubjectPixelHeight' => 'Ð’Ñ‹Ñота объекта в пикÑелÑÑ…', + 'SubjectPixelWidth' => 'Ширина объекта в пикÑелÑÑ…', + 'SubjectReference' => 'СÑылка на предмет', + 'SubjectUnits' => { + Description => 'Единицы объекта', + PrintConv => { + 'meters' => 'Метры', + 'radians' => 'Радианы', + }, + }, + 'Subtitle' => 'Субтитры', + 'SubtitleDescription' => 'Субтитры – ОпиÑание', + 'SuggestedPalette' => 'Ð ÐµÐºÐ¾Ð¼ÐµÐ½Ð´Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð¿Ð°Ð»Ð¸Ñ‚Ñ€Ð°', + 'Summary' => 'Резюме', + 'SupplementalCategories' => 'Дополнительные категории', + 'SupplementalType' => { + Description => 'Тип дополнениÑ', + PrintConv => { + 'Logo' => 'Логотип', + 'Main Image' => 'ОÑновное изображение', + 'Rasterized Caption' => 'РаÑтрированный заголовок', + 'Reduced Resolution Image' => 'Изображение Ñ Ð¿Ð¾Ð½Ð¸Ð¶ÐµÐ½Ð½Ñ‹Ð¼ разрешением', + }, + }, + 'SymLink' => 'СимволичеÑÐºÐ°Ñ ÑÑылка', + 'System' => 'СиÑтема', + 'SystemTime' => 'СиÑтемное времÑ', + 'T4Options' => { + Description => 'Параметры ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ T4', + PrintConv => { + '2-Dimensional encoding' => 'Двумерное кодирование', + 'Fill bits added' => 'Добавление заполнÑющих битов', + 'Uncompressed' => 'Без ÑжатиÑ', + }, + }, + 'T6Options' => { + Description => 'Параметры ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ T6', + PrintConv => { + 'Uncompressed' => 'Без ÑжатиÑ', + }, + }, + 'T82Options' => 'Параметры T82', + 'T88Options' => 'Параметры T88', + 'TIFF-EPStandardID' => 'ID Ñтандарта TIFF-EP', + 'TIFFPreview' => 'ПредпроÑмотр TIFF', + 'TIFF_FXExtensions' => { + Description => 'Ð Ð°Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ TIFF_FX', + PrintConv => { + 'JBIG2 Profile M' => 'JBIG2 профиль M', + 'N Layer Profile M' => 'N Ñлой Профиль M', + 'Resolution/Image Width' => 'Разрешение/Ширина изображениÑ', + 'Shared Data' => 'Общие данные', + }, + }, + 'TOCItems' => 'КоличеÑтво точек навигации', + 'TStop' => 'Ð˜Ð½Ð´ÐµÐºÑ ÑветопропуÑÐºÐ°Ð½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¸Ð²Ð° (T-Stop)', + 'Tagged' => { + Description => 'Тегированный', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'TaggedPDF' => { + Description => 'Тегированный PDF', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'Take' => 'Дубль', + 'Tamper-proofKeys' => 'Ключи от взлома', + 'TargetAudiences' => 'Ð¦ÐµÐ»ÐµÐ²Ð°Ñ Ð°ÑƒÐ´Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ', + 'TargetFileDOSName' => 'DOS название целевого файла', + 'TargetFileSize' => 'Размер целевого файла', + 'TargetLayerID' => 'ID целевого ÑлоÑ', + 'TargetPrinter' => 'Среда печати', + 'Tattoo' => 'Татуировка', + 'Taxon' => 'ТакÑон', + 'TaxonAcceptedNameUsage' => 'ОбщепринÑтое название такÑона', + 'TaxonAcceptedNameUsageID' => 'ID общепринÑтого Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ‚Ð°ÐºÑона', + 'TaxonClass' => 'КлаÑÑ Ñ‚Ð°ÐºÑона', + 'TaxonConceptID' => 'ID такÑономичеÑкой концепции', + 'TaxonFamily' => 'СемейÑтво такÑона', + 'TaxonGenus' => 'Род такÑона', + 'TaxonHigherClassification' => 'ТакÑоны рангом выше', + 'TaxonID' => 'ID такÑона', + 'TaxonInfraspecificEpithet' => 'Ðизший внутривидовой Ñпитет', + 'TaxonKingdom' => 'ЦарÑтво такÑона', + 'TaxonNameAccordingTo' => 'ИÑточник опиÑÐ°Ð½Ð¸Ñ Ð³Ñ€Ð°Ð½Ð¸Ñ† такÑона', + 'TaxonNameAccordingToID' => 'ID иÑточника опиÑÐ°Ð½Ð¸Ñ Ð³Ñ€Ð°Ð½Ð¸Ñ† такÑона', + 'TaxonNamePublishedIn' => 'Ðазвание такÑона впервые опубликовано в', + 'TaxonNamePublishedInID' => 'ID первой публикации Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ‚Ð°ÐºÑона', + 'TaxonNamePublishedInYear' => 'Год первой публикации Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ‚Ð°ÐºÑона', + 'TaxonNomenclaturalCode' => 'Код номенклатуры такÑона', + 'TaxonNomenclaturalStatus' => 'Ðоменклатурный ÑÑ‚Ð°Ñ‚ÑƒÑ Ñ‚Ð°ÐºÑона', + 'TaxonOrder' => 'ПорÑдок такÑона', + 'TaxonOriginalNameUsage' => 'ПервоиÑточник Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ‚Ð°ÐºÑона', + 'TaxonOriginalNameUsageID' => 'ID первоиÑточника Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ‚Ð°ÐºÑона', + 'TaxonParentNameUsage' => 'РодительÑкий такÑон', + 'TaxonParentNameUsageID' => 'ID родительÑкого такÑона', + 'TaxonPhylum' => 'Тип такÑона', + 'TaxonRank' => 'Ранг такÑона', + 'TaxonRemarks' => 'Комментарии к такÑону', + 'TaxonScientificName' => 'Ðаучное название такÑона', + 'TaxonScientificNameAuthorship' => 'Ðвтор научного Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ‚Ð°ÐºÑона', + 'TaxonScientificNameID' => 'ID научного Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ‚Ð°ÐºÑона', + 'TaxonSpecificEpithet' => 'Видовой Ñпитет такÑона', + 'TaxonSubgenus' => 'Подрод такÑона', + 'TaxonTaxonomicStatus' => 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ñ‚Ð°ÐºÑона', + 'TaxonVerbatimTaxonRank' => 'Ранг такÑона по первоиÑточнику', + 'TaxonVernacularName' => 'Ðародное название такÑона', + 'Telephone' => 'Ðомер телефона', + 'Telescope' => 'ТелеÑкоп', + 'Template' => 'Шаблон', + 'TermsAndConditionsText' => 'ТекÑÑ‚ лицензионного ÑоглашениÑ', + 'TermsAndConditionsURL' => 'СÑылка на лицензионное Ñоглашение', + 'TestName' => 'ТеÑтовое название файла', + 'Text' => 'ТекÑÑ‚', + 'TextLayerName' => 'ÐÐ°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ‚ÐµÐºÑтовых Ñлоёв', + 'TextLayerText' => 'ТекÑÑ‚ текÑтового ÑлоÑ', + 'TextLayers' => 'ТекÑтовые Ñлои', + 'TextToSpeech' => { + Description => 'ТекÑÑ‚ в речь', + PrintConv => { + 'Disabled' => 'Ðе включеён', + 'Enabled' => 'Включен', + }, + }, + 'TextureFormat' => 'Формат текÑтуры', + 'TheoraVersion' => 'ВерÑÐ¸Ñ Theora', + 'Thresholding' => { + Description => 'ÐŸÐ¾Ñ€Ð¾Ð³Ð¾Ð²Ð°Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ°', + PrintConv => { + 'No dithering or halftoning' => 'Дизеринг или полутонирование не применÑлоÑÑŒ', + 'Ordered dither or halftone' => 'Заказной дизеринг или полутонирование', + 'Randomized dither' => 'Случайный дизеринг', + }, + }, + 'ThumbnailBPG' => 'Миниатюра BPG', + 'ThumbnailHeight' => 'Ð’Ñ‹Ñота миниатюры', + 'ThumbnailImage' => 'Миниатюра изображениÑ', + 'ThumbnailImageSize' => 'Размер миниатюри', + 'ThumbnailLength' => 'Строк в миниатюре', + 'ThumbnailOffset' => 'Смещение миниатюры', + 'ThumbnailTIFF' => 'TIFF миниатюра', + 'ThumbnailWidth' => 'Ширина миниатюры', + 'TileByteCounts' => 'КоличеÑтво байт в тайле', + 'TileDepth' => 'Глубина тайла', + 'TileLength' => 'Строк в тайле', + 'TileOffsets' => 'Смещение тайла', + 'TileWidth' => 'Колонок в тайле', + 'Tiles' => 'Тайлы', + 'Time' => 'ВремÑ', + 'TimeCode' => 'Код времени', + 'TimeCodes' => 'Таймкоды', + 'TimeCreated' => 'Ð’Ñ€ÐµÐ¼Ñ ÑозданиÑ', + 'TimeSent' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ', + 'TimeShot' => 'Ð’Ñ€ÐµÐ¼Ñ Ñъёмки', + 'TimeStamp' => 'ВременнаÌÑ Ð¼ÐµÑ‚ÐºÐ°', + 'TimeTransparency' => 'ПрозрачноÑть времени', + 'TimeZone' => 'ЧаÑовой поÑÑ', + 'TimeZone2' => 'ЧаÑовой поÑÑ 2', + 'TimeZoneOffset' => 'Смещение чаÑового поÑÑа', + 'TimeZoneURL' => 'URL ЧаÑового поÑÑа', + 'TimelineInfo' => 'Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ шкале времени', + 'TimezoneID' => 'ID чаÑового поÑÑа', + 'TimezoneName' => 'Ðазвание чаÑового поÑÑа', + 'TimezoneOffsetFrom' => 'Смещение чаÑового поÑÑа Ñ', + 'TimezoneOffsetTo' => 'Смещение чаÑового поÑÑа к', + 'Title' => 'Ðазвание', + 'TitleLen' => 'Ðазвание Len', + 'ToneCurveBlueX' => 'Ð¢Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÑ€Ð¸Ð²Ð°Ñ â€“ Синий X', + 'ToneCurveBlueY' => 'Ð¢Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÑ€Ð¸Ð²Ð°Ñ â€“ Синий Y', + 'ToneCurveBrightnessX' => 'Ð¢Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÑ€Ð¸Ð²Ð°Ñ â€“ ЯркоÑть X', + 'ToneCurveBrightnessY' => 'Ð¢Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÑ€Ð¸Ð²Ð°Ñ â€“ ЯркоÑть Y', + 'ToneCurveGreenX' => 'Ð¢Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÑ€Ð¸Ð²Ð°Ñ â€“ Зелёный X', + 'ToneCurveGreenY' => 'Ð¢Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÑ€Ð¸Ð²Ð°Ñ â€“ Зелёный Y', + 'ToneCurveRedX' => 'Ð¢Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÑ€Ð¸Ð²Ð°Ñ â€“ КраÑный X', + 'ToneCurveRedY' => 'Ð¢Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÑ€Ð¸Ð²Ð°Ñ â€“ КраÑный Y', + 'ToolName' => 'Приложение', + 'ToolVersion' => 'ВерÑÐ¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ', + 'TopMargin' => 'ОтÑтуп Ñверху', + 'TotalBitrate' => 'Общий битрейт', + 'TotalDataRate' => 'ÐžÐ±Ñ‰Ð°Ñ ÑкороÑть передачи данных', + 'TotalDuration' => 'ÐžÐ±Ñ‰Ð°Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ñть', + 'TotalEditTime' => 'Общее Ð²Ñ€ÐµÐ¼Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ', + 'TotalFrames' => 'Ð’Ñего кадров', + 'TotalSamples' => 'Ð’Ñего образцов', + 'Track' => 'Трек', + 'TrackCategory' => 'ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ñ‚Ñ€ÐµÐºÐ°', + 'TrackComments' => 'Комментарии к треку', + 'TrackID' => 'ID трека', + 'TrackLyrics' => 'ТекÑÑ‚ пеÑни', + 'TrackNumber' => 'Ðомер трека', + 'Trademark' => 'Ð¢Ð¾Ñ€Ð³Ð¾Ð²Ð°Ñ Ð¼Ð°Ñ€ÐºÐ°', + 'TransferFunction' => 'Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¿ÐµÑ€ÐµÐ½Ð¾Ñа', + 'TransferRange' => 'Диапазон функции переноÑа', + 'Transformation' => { + Description => 'ТранÑформациÑ', + PrintConv => { + 'Horizontal (normal)' => 'Горизонтально', + 'Mirror horizontal' => 'Отразить по горизонтали', + 'Mirror horizontal and rotate 270 CW' => 'Отражение по горизонтали и поворот на 270° по чаÑовой Ñтрелке', + 'Mirror horizontal and rotate 90 CW' => 'Отражение по горизонтали и поворот на 90° по чаÑовой Ñтрелке', + 'Mirror vertical' => 'Отразить по вертикали', + 'Rotate 180' => 'Повернуть на 180°', + 'Rotate 270 CW' => 'Поворот на 270° по чаÑовой Ñтрелке', + 'Rotate 90 CW' => 'Поворот на 90° по чаÑовой Ñтрелке', + }, + }, + 'TransmissionReference' => 'СÑылка на иÑточник', + 'Transparency' => 'ПрозрачноÑть', + 'TransparencyIndicator' => 'Ðаличие прозрачноÑти', + 'TransparentIndex' => 'Ð˜Ð½Ð´ÐµÐºÑ Ð¿Ñ€Ð¾Ð·Ñ€Ð°Ñ‡Ð½Ð¾Ñти', + 'TrapIndicator' => 'Применен ли треппинг к файлу', + 'Trapped' => 'Треппинг', + 'Trigger' => 'Ð’Ñ€ÐµÐ¼Ñ ÑÑ€Ð°Ð±Ð°Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ñигнала', + 'TriggerMode' => { + Description => 'Режим триггера', + PrintConv => { + 'CodeLoc Not Entered' => 'Код блокировки не задан', + 'External Sensor' => 'Внешний датчик', + 'Motion Detection' => 'Обнаружение движениÑ', + 'Point and Shoot' => 'Ðаведи и Ñнимай', + 'Time Lapse' => 'ТаймлапÑ', + }, + }, + 'Type' => 'Тип', + 'TypeStatus' => 'СпиÑок номенклатурных типов', + 'UIC1Tag' => 'UIC – Тег 1', + 'UIC2Tag' => 'UIC – Тег 2', + 'UIC3Tag' => 'UIC – Тег 3', + 'UIC4Tag' => 'UIC – Тег 4', + 'UID' => 'Уникальный ID', + 'URLList1' => 'СпиÑок URL 1', + 'URL_List' => 'СпиÑок URL', + 'USPTOOriginalContentType' => { + Description => 'USPTO – ИÑходный тип контента', + PrintConv => { + 'Color' => 'Цвет', + 'Grayscale' => 'Оттенки Ñерого', + 'Text or Drawing' => 'ТекÑÑ‚ или риÑунок', + }, + }, + 'UUID-Unknown' => 'ÐеизвеÑтный UUID', + 'UUIDList' => 'СпиÑок UUID', + 'Uncompressed' => { + Description => 'Ðе Ñжато', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'UncompressedSize' => 'ÐеÑжатый размер', + 'UncompressedTextLength' => 'Длина неÑжатого текÑта', + 'Underline' => 'Подчёркнутый', + 'UnderlinePosition' => 'ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ Ð¿Ð¾Ð´Ñ‡Ñ‘Ñ€ÐºÐ¸Ð²Ð°Ð½Ð¸Ñ', + 'UnderlineThickness' => 'Толщина подчёркиваниÑ', + 'UnicodeAlphaNames' => 'Юникодное название альфа-канала', + 'UniqueCameraModel' => 'Уникальное название модели камеры', + 'UniqueDocumentID' => 'Уникальный ID документа', + 'UniqueObjectName' => 'Уникальное название объекта', + 'Units' => { + Description => 'Единицы измерениÑ', + PrintConv => { + 'Inches' => 'Дюймы', + 'Picas' => 'Пики', + 'Points' => 'Точки', + 'mm' => 'Милиметры', + }, + }, + 'Unknown' => 'ÐеизвеÑтный', + 'UnknownDate' => 'ÐеизвеÑÑ‚Ð½Ð°Ñ Ð´Ð°Ñ‚Ð°', + 'UpdatedTitle' => 'Обновленное название', + 'Urgency' => { + Description => 'Приоритет обработки', + PrintConv => { + '0 (reserved)' => '0 (Зарезервированно)', + '1 (most urgent)' => '1 (Срочно)', + '5 (normal urgency)' => '5 (Обычный)', + '8 (least urgent)' => '8 (Ðе Ñрочно)', + '9 (user-defined priority)' => '9 (ПользовательÑкий приоритет)', + }, + }, + 'UsageRightsMessage' => 'Сообщение о правах иÑпользованиÑ', + 'UsePanoramaViewer' => 'ИÑпользовать проÑмотрщик панорам', + 'UserAccess' => { + Description => 'РазрешаетÑÑ', + PrintConv => { + 'Annotate' => 'Ðннотирование', + 'Assemble' => 'Сборка', + 'Copy' => 'Копирование', + 'Extract' => 'Извлечение', + 'Fill forms' => 'Заполнение форм', + 'Modify' => 'Редактирование', + 'Print' => 'Печать', + 'Print high-res' => 'Печать в выÑоком разрешении', + }, + }, + 'UserComment' => 'Комментарии пользователÑ', + 'UserFields' => 'Дополнительное поле', + 'UserID' => 'ID пользователÑ', + 'UserLabel' => 'Ярлык пользователÑ', + 'VBRBytes' => 'Переменный битрейт – Бит', + 'VBRFrames' => 'Переменный битрейт – КоличеÑтво фреймов', + 'VBRScale' => 'Переменный битрейт – Шкала', + 'VCalendarVersion' => 'ВерÑÐ¸Ñ VCalendar', + 'VCardVersion' => 'ВерÑÐ¸Ñ VCard', + 'Vary' => 'Ðльтернативный HTTP-заголовок', + 'Vendor' => 'ПоÑтавщик', + 'VendorURL' => 'URL поÑтавщика', + 'Version' => 'ВерÑиÑ', + 'Version2' => 'ВерÑÐ¸Ñ 2', + 'VersionCreateDate' => 'ВерÑÐ¸Ñ â€“ Дата ÑозданиÑ', + 'VersionModifyDate' => 'ВерÑÐ¸Ñ â€“ Дата редактированиÑ', + 'VersionYear' => 'Год Ñтандарта факÑ-профилÑ', + 'VerticalDivergence' => 'Угол вертикального раÑхождениÑ', + 'Vibrance' => 'КраÑочноÑть', + 'VideoBitrate' => 'Битрейт видео', + 'VideoClosedCaptioning' => { + Description => 'Видео Ñ Ñубтитрами', + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'VideoCodecID' => 'ID видеокодека', + 'VideoEncoding' => 'Видеокодирование', + 'VideoFormat' => 'Формат видео', + 'VideoMode' => 'Режим видео', + 'VideoQuality' => 'КачеÑтво видео', + 'VideoScanType' => 'Тип ÑÐºÐ°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð²Ð¸Ð´ÐµÐ¾', + 'VideoSize' => 'Размер видео', + 'View' => 'ПроÑмотр', + 'ViewType' => { + Description => 'Тип проÑмотра', + PrintConv => { + 'No Pop-up Effect' => 'Без вÑплывающего Ñффекта', + 'Pop-up Effect' => 'С вÑплывающим Ñффектом', + }, + }, + 'Viewfinder' => 'ВидоиÑкатель', + 'VignetteCorrectionAlreadyApplied' => 'Применена ÐºÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð²Ð¸Ð½ÑŒÐµÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ', + 'VignettingCorrParams' => 'Параметры коррекции виньетированиÑ', + 'VignettingCorrection' => { + Description => 'ÐšÐ¾Ñ€Ñ€ÐµÐºÑ†Ð¸Ñ Ð²Ð¸Ð½ÑŒÐµÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкаÑ', + 'No correction params available' => 'ÐедоÑтупна', + 'Off' => 'Ðе включена', + }, + }, + 'VirtualImageHeight' => 'Ð’Ñ‹Ñота виртуального изображениÑ', + 'VirtualImageWidth' => 'Ширина виртуального изображениÑ', + 'VirtualPageUnits' => 'Единицы виртуальной Ñтраницы', + 'VisualFlightRulesHUD' => 'Визуальные правила полетов HUD', + 'VoiceMemo' => { + Description => 'ГолоÑовые заметки', + PrintConv => { + 'Off' => 'Ðе включено', + 'On' => 'Включено', + }, + }, + 'Volume' => 'Том', + 'VolumeBlockCount' => 'КоличеÑтво блоков в томе', + 'VolumeBlockSize' => 'Размер блока в томе', + 'VolumeCreateDate' => 'Дата ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ‚Ð¾Ð¼Ð°', + 'VolumeEffectiveDate' => 'Дата начала иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ‚Ð¾Ð¼Ð°', + 'VolumeExpirationDate' => 'Дата уÑÑ‚Ð°Ñ€ÐµÐ²Ð°Ð½Ð¸Ñ Ñ‚Ð¾Ð¼Ð°', + 'VolumeID' => 'ID тома', + 'VolumeLabel' => 'Метка тома', + 'VolumeModifyDate' => 'Дата Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ‚Ð¾Ð¼Ð°', + 'VolumeName' => 'Ðазвание тома', + 'VolumeSetDiskCount' => 'КоличеÑтво диÑков в наборе томов', + 'VolumeSetDiskNumber' => 'Ðомер диÑка в наборе томов', + 'VolumeSetName' => 'Ðазвание набора томов', + 'VolumeSize' => 'Размер тома', + 'VorbisVersion' => 'ВерÑÐ¸Ñ Vorbis', + 'WBBlueLevel' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð‘ÐµÐ»Ð¾Ð³Ð¾ – Уровень Ñинего', + 'WBGreenLevel' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð‘ÐµÐ»Ð¾Ð³Ð¾ – Уровень зелёного', + 'WBMode' => 'Режим баланÑа белого', + 'WBRedLevel' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð‘ÐµÐ»Ð¾Ð³Ð¾ – Уровень краÑного', + 'WBScale' => 'Чёрно-Белое – Шкала', + 'WB_GBRGLevels' => 'Чёрно-Белое – Уровни GBRG', + 'WB_GRGBLevels' => 'Уровни WB_GRGB', + 'WB_RBLevelsCloudy' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ – Уровни RB – Облачно', + 'WB_RBLevelsCoolWhiteF' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ – Уровни RB – Холодный белый F', + 'WB_RBLevelsDayWhiteF' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ – Уровни RB – Дневной белый F', + 'WB_RBLevelsDaylight' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ – Уровни RB – Дневной Ñвет', + 'WB_RBLevelsDaylightF' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ – Уровни RB – Дневной Ñвет F', + 'WB_RBLevelsFlash' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ – Уровни RB – Ð’Ñпышка', + 'WB_RBLevelsShade' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ – Уровни RB – Тень', + 'WB_RBLevelsTungsten' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ – Уровни RB – Лампа накаливаниÑ', + 'WB_RBLevelsUnknown' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ – Уровни RB – ÐеизвеÑтный иÑточник Ñвета', + 'WB_RBLevelsWhiteF' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ – Уровни RB – Белый', + 'WB_RGBLevels' => 'Уровни WB RGB', + 'WB_RGGBLevels' => 'Чёрно-Белое – Уровни RGGB', + 'WWSFamilyName' => 'Ðазвание ÑемейÑтва WWS', + 'WWSSubfamilyName' => 'Ðазвание ÑÑ‚Ð¸Ð»Ñ WWS', + 'WangAnnotation' => 'Wang Imaging – ÐннотациÑ', + 'WangTag1' => 'Wang Imaging – Тег 1', + 'WangTag3' => 'Wang Imaging – Тег 3', + 'WangTag4' => 'Wang Imaging – Тег 4', + 'Warning' => 'Уведомление', + 'WarpQuadrilateral' => 'Ð”ÐµÑ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ñ‡ÐµÑ‚Ñ‹Ñ€Ñ‘Ñ…ÑƒÐ³Ð¾Ð»ÑŒÐ½Ð¸ÐºÐ°', + 'Watched' => { + PrintConv => { + 'No' => 'Ðет', + 'Yes' => 'Да', + }, + }, + 'WaterDepth' => 'Глубина воды', + 'Watermark' => 'ВодÑной знак', + 'Weight' => 'Толщина шрифта', + 'WhiteBalance' => { + Description => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾', + PrintConv => { + 'Auto' => 'ÐвтоматичеÑкий', + 'Black & White' => 'Монохром', + 'Cloudy' => 'ОблачноÑть', + 'Color Temperature/Color Filter' => 'Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð°/Цветовой фильтр', + 'Cool White Fluorescent' => 'ФлуореÑцентный белый холодный', + 'Custom' => 'Пользователь', + 'Custom 1' => 'ПЕРСОÐÐЛЬÐЫЙ 1', + 'Custom 2' => 'ПЕРСОÐÐЛЬÐЫЙ 2', + 'Custom 3' => 'ПЕРСОÐÐЛЬÐЫЙ 3', + 'Custom 4' => 'ПЕРСОÐÐЛЬÐЫЙ 4', + 'Day White Fluorescent' => 'ФлуореÑцентный белый дневной', + 'Daylight' => 'Дневной Ñвет', + 'Daylight Fluorescent' => 'ФлуореÑцентный дневной', + 'Flash' => 'Ð’Ñпышка', + 'Fluorescent' => 'ФлуореÑцентный', + 'Manual' => 'Ручной', + 'Shade' => 'Тень', + 'Tungsten' => 'Лампа накаливаниÑ', + 'Unknown' => 'неизвеÑтно', + 'Warm White Fluorescent' => 'ФлуореÑцентный теплый белый', + 'White Fluorescent' => 'ФлуореÑцентный белый', + }, + }, + 'WhiteBalance0' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ 0', + 'WhiteBalance1' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ 1', + 'WhiteBalance2' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ 2', + 'WhiteBalanceRGB' => 'Ð‘Ð°Ð»Ð°Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾ RGB', + 'WhiteLevel' => 'Уровень белого', + 'WhiteLuminance' => 'ЯркоÑть белого', + 'WhitePoint' => 'ЦветноÑть белой точки', + 'WhitePointX' => 'Ð‘ÐµÐ»Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° по X', + 'WhitePointY' => 'Ð‘ÐµÐ»Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° по Y', + 'WhitesAdj' => 'Регулировка ÑƒÑ€Ð¾Ð²Ð½Ñ Ð±ÐµÐ»Ð¾Ð³Ð¾', + 'WideRange' => { + Description => 'Широкий диапазон', + PrintConv => { + 'Off' => 'Ðе включён', + 'On' => 'Включен', + }, + }, + 'WidthBytes' => 'Ширина в байтах', + 'WidthResolution' => 'Разрешение по ширине (PPI)', + 'WindowOrigin' => 'Ðачало координат окна', + 'WindowOriginAuto' => 'Ðачало координат окна – ÐвтоматичеÑки', + 'WindowSize' => 'Размер окна', + 'WindowTarget' => 'Целевое окно', + 'Words' => 'Слов', + 'WorkflowURL' => 'URL рабочего процеÑÑа', + 'WorkingDirectory' => 'Рабочий каталог', + 'WorkingPath' => 'Рабочий контур', + 'WorldToCamera' => 'Мир на камеру', + 'WorldToNDC' => 'Мир на NDC', + 'WrapModes' => 'Режим обёртываниÑ', + 'Writer-Editor' => 'ПиÑатель/Редактор', + 'WriterName' => 'Ðазвание редактора', + 'XAttrAppleMailDateReceived' => 'X Attr – Почта Apple – Дата получениÑ', + 'XAttrAppleMailDateSent' => 'X Attr – Почта Apple – Дата отправки', + 'XAttrAppleMailIsRemoteAttachment' => 'X Attr – Почта Apple – Ðаличие удалённого вложениÑ', + 'XAttrFinderInfo' => 'X Attr – СвойÑтва файла', + 'XAttrLastUsedDate' => 'X Attr – Дата поÑледнего иÑпользованиÑ', + 'XAttrMDItemDownloadedDate' => 'X Attr – MD Item – Дата загрузки', + 'XAttrMDItemFinderComment' => 'X Attr – MD Item – Комментарий к файлу', + 'XAttrMDItemWhereFroms' => 'X Attr – MD Item – ИÑточник файла', + 'XAttrMDLabel' => 'X Attr – MD – Ð¦Ð²ÐµÑ‚Ð½Ð°Ñ Ð¼ÐµÑ‚ÐºÐ° файла', + 'XAttrQuarantine' => 'X Attr – Карантинный', + 'XAttrResourceFork' => 'X Attr – Вилка реÑурÑов', + 'XCFVersion' => 'ВерÑÐ¸Ñ XCF', + 'XClipPathUnits' => 'Единицы обтравочного контура по X', + 'XHeight' => 'Ð’Ñ‹Ñота Ñтрочных букв', + 'XMLData' => 'Данные XML', + 'XOffset' => 'Смещение по X', + 'XPAuthor' => 'XP – Ðвтор', + 'XPComment' => 'XP – Комментарии', + 'XPKeywords' => 'XP – Ключевые Ñлова', + 'XPSubject' => 'XP – Тема', + 'XPTitle' => 'XP – Ðазвание', + 'XPosition' => 'Положение Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾ X', + 'XResolution' => 'Разрешение по X', + 'XYResolution' => 'Разрешение по X и Y', + 'YCbCrCoefficients' => 'КоÑффициенты Ð¿Ñ€ÐµÐ¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸Ð· RGB в Y Cb Cr', + 'YCbCrPositioning' => { + Description => 'Положение точки, определÑющей цвет в Y Cb Cr', + PrintConv => { + 'Centered' => 'Центрованный', + 'Co-sited' => 'СовмеÑтимый', + }, + }, + 'YCbCrSubSampling' => 'КоÑффициент ÑубдиÑкретизации Y Cb Cr', + 'YClipPathUnits' => 'Единицы обтравочного контура по Y', + 'YLevel' => 'Уровень – Y', + 'YOffset' => 'Смещение по Y', + 'YPosition' => 'Положение Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾ Y', + 'YResolution' => 'Разрешение по Y', + 'YTarget' => 'Цель – Y', + 'Yaw' => 'РыÑкание (Поворот)', + 'YawAngle' => 'Угол поворота (РыÑкание)', + 'Year' => 'Год', + 'ZipBitFlag' => 'Zip – Битовый флаг', + 'ZipCRC' => 'Zip – CRC', + 'ZipCompressedSize' => 'Zip – Сжатый размер', + 'ZipCompression' => { + Description => 'Zip – Сжатие', + PrintConv => { + 'Enhanced Deflate using Deflate64(tm)' => 'Улучшенный Deflate Ñ Ð¸Ñпользованием Deflate64(tm)', + 'IBM LZ77 z Architecture (PFS)' => 'IBM LZ77 z/Architecture (PFS)', + 'IBM TERSE (new)' => 'IBM TERSE (новый)', + 'Imploded (old IBM TERSE)' => 'Imploded (Ñтарый IBM TERSE)', + 'None' => 'Без ÑжатиÑ', + 'PPMd version I, Rev 1' => 'PPMd верÑÐ¸Ñ I, Ð ÐµÐ²Ð¸Ð·Ð¸Ñ 1', + 'Reduced with compression factor 1' => 'Сжатый Ñ ÐºÐ¾Ñффициентом ÑÐ¶Ð°Ñ‚Ð¸Ñ 1', + 'Reduced with compression factor 2' => 'Сжатый Ñ ÐºÐ¾Ñффициентом ÑÐ¶Ð°Ñ‚Ð¸Ñ 2', + 'Reduced with compression factor 3' => 'Сжатый Ñ ÐºÐ¾Ñффициентом ÑÐ¶Ð°Ñ‚Ð¸Ñ 3', + 'Reduced with compression factor 4' => 'Сжатый Ñ ÐºÐ¾Ñффициентом ÑÐ¶Ð°Ñ‚Ð¸Ñ 4', + 'WavPack compressed' => 'WavPack', + }, + }, + 'ZipFileName' => 'Zip – Ðазвание файла', + 'ZipFileNameLength' => 'Zip – Длина Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°', + 'ZipModifyDate' => 'Zip – Дата редактированиÑ', + 'ZipRequiredVersion' => 'Zip – Ð¢Ñ€ÐµÐ±ÑƒÐµÐ¼Ð°Ñ Ð²ÐµÑ€ÑиÑ', + 'ZipUncompressedSize' => 'Zip – ÐеÑжатый размер', + 'ZoneMatching' => { + Description => 'СоглаÑование зон', + PrintConv => { + 'High Key' => 'Ð’ выÑоком ключе', + 'ISO Setting Used' => 'ИÑпользуютÑÑ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ñ‹ ISO', + 'Low Key' => 'Ð’ низком ключе', + }, + }, + 'Zoom' => 'Зум', + 'ZoomPos' => 'ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ Ð·ÑƒÐ¼Ð°', + 'ZoomedPreviewImage' => 'Увеличенный предпроÑмотр изображениÑ', + 'iTunesMediaType' => 'Тип iTunes медиа', +); + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::ru.pm - ExifTool Russian language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke, Sergey Shemetov, Dmitry Yerokhin, Anton Sukhinov and +Alexander for providing this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/sk.pm b/ExifTool/lib/Image/ExifTool/Lang/sk.pm new file mode 100644 index 0000000..491a1d8 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/sk.pm @@ -0,0 +1,1806 @@ +#------------------------------------------------------------------------------ +# File: sk.pm +# +# Description: ExifTool Slovak language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::sk; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.01'; + +%Image::ExifTool::Lang::sk::Translate = ( + 'A100DataOffset' => 'Korekcia údajov A100', + 'ARMIdentifier' => 'Identifikátor ARM', + 'ARMVersion' => 'Verzia ARM', + 'Acceleration' => 'Zrýchlenie', + 'ActionAdvised' => { + Description => 'OdporúÄané opatrenia', + PrintConv => { + 'Object Append' => 'Pripojenie objektu', + 'Object Kill' => 'ZniÄenie objektu', + 'Object Reference' => 'Odkaz na objekt', + 'Object Replace' => 'Nahradenie objektu', + }, + }, + 'ActiveArea' => 'Aktívna oblasÅ¥', + 'AdventRevision' => 'Kontrola výskytu', + 'AdventScale' => 'Rozsah výskytu', + 'AffineTransformMat' => 'Afinná transformaÄná matica', + 'AliasLayerMetadata' => 'Alias ​​vrstvy metadát', + 'AlphaByteCount' => 'VeľkosÅ¥ kanála alfa (v bajtoch)', + 'AlphaDataDiscard' => { + Description => 'Vyradené údaje kanálu alfa', + PrintConv => { + 'Flexbits Discarded' => 'Vyradené flexibilné bity', + 'Full Resolution' => 'Úplné rozlíšenie', + 'HighPass Frequency Data Discarded' => 'Vyradené údaje s vysokou frekvenciou', + 'Highpass and LowPass Frequency Data Discarded' => 'Vyradené údaje o vysokej a nízkej frekvencii', + }, + }, + 'AlphaOffset' => 'Odsadenie alfa kanála', + 'AmbientTemperature' => 'Teplota okolia', + 'AnalogBalance' => 'Analógové vyváženie', + 'Annotations' => 'Anotácie', + 'AntiAliasStrength' => 'Relatívna sila antialiasingu', + 'ApertureValue' => 'Hodnota clony', + 'ApplicationNotes' => 'Poznámky aplikácie', + 'ApplicationRecordVersion' => 'Verzia záznamu aplikácie', + 'Artist' => 'Realizátor', + 'AsShotICCProfile' => 'Profil ICC v Äase snímania', + 'AsShotNeutral' => 'Neutrálna farba v Äase snímania', + 'AsShotPreProfileMatrix' => 'Profil matrice v Äase snímania', + 'AsShotProfileName' => 'Názov profilu v Äase snímania', + 'AsShotWhiteXY' => 'Biele XY v Äase snímania', + 'AudioDuration' => 'Dĺžka trvania zvuku', + 'AudioOutcue' => 'Koncový zvukový signál', + 'AudioSamplingRate' => 'Vzorkovacia frekvencia zvuku', + 'AudioSamplingResolution' => 'Dátový tok zvuku', + 'AudioType' => { + Description => 'Typ zvuku', + PrintConv => { + 'Mono Actuality' => 'Monofónna realita', + 'Mono Music' => 'Monofónna hudba', + 'Mono Question and Answer Session' => 'Monofónne zasadnutie otázok a odpovedí', + 'Mono Raw Sound' => 'Monofonický surový zvuk', + 'Mono Response to a Question' => 'Monofónna odpoveÄ na otázku', + 'Mono Scener' => 'Monofónny snímaÄ', + 'Mono Voicer' => 'Monofónny hlasový záznamník', + 'Mono Wrap' => 'Mono zábal', + 'Stereo Actuality' => 'Stereofónna realita', + 'Stereo Music' => 'Stereofónna hudba', + 'Stereo Question and Answer Session' => 'Stereofónne zasadnutie otázok a odpovedí', + 'Stereo Raw Sound' => 'Stereofonický surový zvuk', + 'Stereo Response to a Question' => 'Stereofónna odpoveÄ na otázku', + 'Stereo Scener' => 'Stereofónny snímaÄ', + 'Stereo Voicer' => 'Stereofónny hlasový záznamník', + 'Stereo Wrap' => 'Stereofónny zábal', + 'Text Only' => 'Len text', + }, + }, + 'BackgroundColorIndicator' => { + Description => 'Indikátor farby pozadia', + PrintConv => { + 'Specified Background Color' => 'Å pecifikovaná farba pozadia', + 'Unspecified Background Color' => 'NeÅ¡pecifikovaná farba pozadia', + }, + }, + 'BackgroundColorValue' => 'Hodnota farby pozadia', + 'BadFaxLines' => 'Zlé faxové linky', + 'BaselineExposure' => 'Základná expozícia', + 'BaselineExposureOffset' => 'Korekcia základnej expozície', + 'BaselineNoise' => 'Základný Å¡um', + 'BaselineSharpness' => 'Základná ostrosÅ¥', + 'BatteryLevel' => 'Úroveň nabitia batérie', + 'BayerGreenSplit' => 'Oddelenie zelených kanálov v Bayerovej matici', + 'BestQualityScale' => 'Optimálna stupnica', + 'BitsPerComponent' => 'PoÄet bitov na súÄasÅ¥', + 'BitsPerExtendedRunLength' => 'PoÄet bitov na rozšírenú dĺžku radu', + 'BitsPerRunLength' => 'PoÄet bitov na dĺžku radu', + 'BitsPerSample' => 'PoÄet bitov na vzorku', + 'BlackLevel' => 'Úroveň Äiernej', + 'BlackLevelBlue' => 'ÄŒierna úroveň modrej', + 'BlackLevelDeltaH' => 'Delta úroveň Äiernej H', + 'BlackLevelDeltaV' => 'Delta úroveň Äiernej V', + 'BlackLevelGreen' => 'ÄŒierna úroveň zelenej', + 'BlackLevelRed' => 'ÄŒierna úroveň Äervenej', + 'BlackLevelRepeatDim' => 'Úroveň stlmenia Äiernej farby', + 'BlueBalance' => 'Rovnováha modrej farby', + 'Brightness' => 'Jas', + 'BrightnessValue' => 'Hodnota jasu', + 'By-line' => 'Meno autora', + 'By-lineTitle' => 'Autorský názov', + 'CFALayout' => { + Description => 'Rozloženie matice farebných filtrov', + PrintConv => { + 'Even columns offset down 1/2 row' => 'Párne stĺpce posunuté o 1/2 riadku nadol', + 'Even columns offset up 1/2 row' => 'Párne stĺpce posunuté o 1/2 riadku nahor', + 'Even rows offset down by 1/2 row, even columns offset left by 1/2 column' => 'Párne riadky posunuté nadol o 1/2 riadka, párne stĺpce posunuté zľava o 1/2 stĺpca', + 'Even rows offset down by 1/2 row, even columns offset right by 1/2 column' => 'Párne riadky posunuté nadol o 1/2 riadku, párne stĺpce posunuté vpravo o 1/2 stĺpca', + 'Even rows offset left 1/2 column' => 'Párne riadky posunuté vľavo o 1/2 stĺpca', + 'Even rows offset right 1/2 column' => 'Párne riadky posunuté vpravo o 1/2 stĺpca', + 'Even rows offset up by 1/2 row, even columns offset left by 1/2 column' => 'Párne riadky posunuté nahor o 1/2 riadku, párne stĺpce posunuté doľava o 1/2 stĺpca', + 'Even rows offset up by 1/2 row, even columns offset right by 1/2 column' => 'Párne riadky posunuté nahor o 1/2 riadku, párne stĺpce posunuté doprava o 1/2 stĺpca', + 'Rectangular' => 'Obdĺžnikové', + }, + }, + 'CFAPattern' => { + Description => 'Matica farebných filtrov', + PrintConv => { + '[Blue,Green][Green,Red]' => '[Modrá,Zelená][Zelená,ÄŒervená]', + '[Green,Blue][Red,Green]' => '[Zelená,Modrá][ÄŒervená,Zelená]', + '[Green,Red][Blue,Green]' => '[Zelená,ÄŒervená][Modrá,Zelená]', + '[Red,Green][Green,Blue]' => '[ÄŒervená,Zelená][Zelená,Modrá]', + }, + }, + 'CFAPattern2' => 'Matica farebných filtrov 2', + 'CFAPlaneColor' => 'Farba roviny CFA', + 'CFARepeatPatternDim' => 'Vzor opakovania CFA Dim', + 'CIP3DataFile' => 'CIP3 - Dátový súbor', + 'CIP3Sheet' => 'CIP3 - List', + 'CIP3Side' => 'CIP3 - Strana', + 'CMYKEquivalent' => 'Ekvivalent CMYK', + 'CR2CFAPattern' => { + Description => 'Vzorka CR2 CFA', + PrintConv => { + '[Blue,Green][Green,Red]' => '[Modrá,Zelená][Zelená,ÄŒervená]', + '[Green,Blue][Red,Green]' => '[Zelená,Modrá][ÄŒervená,Zelená]', + '[Green,Red][Blue,Green]' => '[Zelená,ÄŒervená][Modrá,Zelená]', + '[Red,Green][Green,Blue]' => '[ÄŒervená,Zelená][Zelená,Modrá]', + }, + }, + 'CacheVersion' => 'Verzia vyrovnávacej pamäte', + 'CalibrationIlluminant1' => { + Description => 'KalibraÄné osvetlenie 1', + PrintConv => { + 'Cloudy' => 'ZamraÄené', + 'Cool White Fluorescent' => 'Studená biela žiarivka', + 'Day White Fluorescent' => 'Denná biela žiarivka', + 'Daylight' => 'Denné svetlo', + 'Daylight Fluorescent' => 'Denné svetlo žiarivky', + 'Fine Weather' => 'Pekné poÄasie', + 'Flash' => 'Blesk', + 'Fluorescent' => 'Žiarivka', + 'ISO Studio Tungsten' => 'Å túdio ISO Tungsten', + 'Other' => 'Iné', + 'Shade' => 'Tieň', + 'Standard Light A' => 'Å tandardné svetlo A', + 'Standard Light B' => 'Å tandardné svetlo B', + 'Standard Light C' => 'Å tandardné svetlo C', + 'Tungsten (Incandescent)' => 'Volfrámová (žiarovka)', + 'Unknown' => 'Neznáme', + 'Warm White Fluorescent' => 'Teplá biela žiarivka', + 'White Fluorescent' => 'Biela žiarivka', + }, + }, + 'CalibrationIlluminant2' => { + Description => 'KalibraÄné osvetlenie 2', + PrintConv => { + 'Cloudy' => 'ZamraÄené', + 'Cool White Fluorescent' => 'Studená biela žiarivka', + 'Day White Fluorescent' => 'Denná biela žiarivka', + 'Daylight' => 'Denné svetlo', + 'Daylight Fluorescent' => 'Denné svetlo žiarivky', + 'Fine Weather' => 'Pekné poÄasie', + 'Flash' => 'Blesk', + 'Fluorescent' => 'Žiarivka', + 'ISO Studio Tungsten' => 'Å túdio ISO Tungsten', + 'Other' => 'Iné', + 'Shade' => 'Tieň', + 'Standard Light A' => 'Å tandardné svetlo A', + 'Standard Light B' => 'Å tandardné svetlo B', + 'Standard Light C' => 'Å tandardné svetlo C', + 'Tungsten (Incandescent)' => 'Volfrámová (žiarovka)', + 'Unknown' => 'Neznáme', + 'Warm White Fluorescent' => 'Teplá biela žiarivka', + 'White Fluorescent' => 'Biela žiarivka', + }, + }, + 'CalibrationIlluminant3' => { + Description => 'KalibraÄné osvetlenie 3', + PrintConv => { + 'Cloudy' => 'ZamraÄené', + 'Cool White Fluorescent' => 'Studená biela žiarivka', + 'Day White Fluorescent' => 'Denná biela žiarivka', + 'Daylight' => 'Denné svetlo', + 'Daylight Fluorescent' => 'Denné svetlo žiarivky', + 'Fine Weather' => 'Pekné poÄasie', + 'Flash' => 'Blesk', + 'Fluorescent' => 'Žiarivka', + 'ISO Studio Tungsten' => 'Å túdio ISO Tungsten', + 'Other' => 'Iné', + 'Shade' => 'Tieň', + 'Standard Light A' => 'Å tandardné svetlo A', + 'Standard Light B' => 'Å tandardné svetlo B', + 'Standard Light C' => 'Å tandardné svetlo C', + 'Tungsten (Incandescent)' => 'Volfrámová (žiarovka)', + 'Warm White Fluorescent' => 'Teplá biela žiarivka', + 'White Fluorescent' => 'Biela žiarivka', + }, + }, + 'CameraCalibration1' => 'KalibraÄný snímaÄ kamery 1', + 'CameraCalibration2' => 'KalibraÄný snímaÄ kamery 2', + 'CameraCalibration3' => 'KalibraÄný snímaÄ kamery 3', + 'CameraCalibrationSig' => 'KalibraÄný signál fotoaparátu', + 'CameraElevationAngle' => 'Uhol pohľadu fotoaparátu', + 'CameraLabel' => 'OznaÄenie fotoaparátu', + 'CameraSerialNumber' => 'Sériové Äíslo fotoaparátu', + 'Caption-Abstract' => 'Podrobný popis', + 'CatalogSets' => 'Katalógové sady', + 'Category' => 'Kategória', + 'CellLength' => 'Dĺžka bunky', + 'CellWidth' => 'Šírka bunky', + 'ChromaBlurRadius' => 'Polomer vyhladzovania farieb', + 'ChromaticAberrationCorrParams' => 'Parametre korekcie chromatickej aberácie', + 'ChromaticAberrationCorrection' => { + Description => 'Korekcia chromatickej aberácie', + PrintConv => { + 'Auto' => 'Automatická', + 'No correction params available' => 'Nie sú dostupné žiadne parametre korekcie', + 'Off' => 'Vypnutá', + }, + }, + 'City' => 'Mesto', + 'ClassifyState' => 'Klasifikácia Å¡truktúry', + 'CleanFaxData' => { + Description => 'PrítomnosÅ¥ preruÅ¡ovaných Äiar', + PrintConv => { + 'Clean' => 'ÄŒisté', + 'Regenerated' => 'Regenerované', + 'Unclean' => 'NeÄisté', + }, + }, + 'ClipPath' => 'Obrys farbenia', + 'CodedCharacterSet' => 'Sada kódovaných znakov', + 'CodingMethods' => { + Description => 'Metódy kódovania', + PrintConv => { + 'Baseline JPEG' => 'Základný formát JPEG', + 'JBIG color' => 'FarebnosÅ¥ JBIG', + 'Modified Huffman' => 'Upravený Huffman', + 'Modified MR' => 'Upravená MR', + 'Modified Read' => 'Upravené Äítanie', + 'Unspecified compression' => 'NeÅ¡pecifikovaná kompresia', + }, + }, + 'ColorCalibrationMatrix' => 'Farebná kalibraÄná matrica', + 'ColorCharacterization' => 'Farebná charakteristika', + 'ColorMap' => 'Farebná mapa', + 'ColorMatrix1' => 'Farebná matrica 1', + 'ColorMatrix2' => 'Farebná matrica 2', + 'ColorMatrix3' => 'Farebná matrica 3', + 'ColorPalette' => 'Paleta farieb', + 'ColorRepresentation' => { + Description => 'Zastúpenie farieb', + PrintConv => { + '3 Components, Frame Sequential in Multiple Objects' => '3 komponenty, sekvenÄné snímky vo viacerých objektoch', + '3 Components, Frame Sequential in One Object' => '3 komponenty, sekvenÄné snímky v jednom objekte', + '3 Components, Line Sequential' => '3 komponenty, sekvenÄná línia', + '3 Components, Pixel Sequential' => '3 komponenty, sekvenÄný pixel', + '3 Components, Single Frame' => '3 komponenty, jeden snímok', + '3 Components, Special Interleaving' => '3 komponenty, Å¡peciálne prelínanie', + '4 Components, Frame Sequential in Multiple Objects' => '4 komponenty, sekvenÄné snímky vo viacerých objektoch', + '4 Components, Frame Sequential in One Object' => '4 komponenty, sekvenÄné snímky v jednom objekte', + '4 Components, Line Sequential' => '4 komponenty, sekvenÄná línia', + '4 Components, Pixel Sequential' => '4 komponenty, sekvenÄný pixel', + '4 Components, Single Frame' => '4 komponenty, jeden snímok', + '4 Components, Special Interleaving' => '4 komponenty, Å¡peciálne prelínanie', + 'Monochrome, Single Frame' => 'Monochromatický, jeden snímok', + 'No Image, Single Frame' => 'Žiadny obrázok, jeden snímok', + }, + }, + 'ColorResponseUnit' => 'Jednotky farebnej citlivosti', + 'ColorSequence' => 'Farebná postupnosÅ¥', + 'ColorSpace' => { + Description => 'Farebný priestor', + PrintConv => { + 'ICC Profile' => 'Profil ICC', + 'Uncalibrated' => 'Nekalibrovaný', + 'Wide Gamut RGB' => 'Å iroký rozsah RGB', + }, + }, + 'ColorTable' => 'Tabuľka farieb', + 'ColorimetricReference' => 'Kolorimetrická referencia', + 'ComponentsConfiguration' => 'Konfigurácia komponentov', + 'CompositeImage' => { + Description => 'Zložený obrázok', + PrintConv => { + 'Composite Image Captured While Shooting' => 'Zložený obrázok zachytený poÄas snímania', + 'General Composite Image' => 'VÅ¡eobecný zložený obrázok', + 'Not a Composite Image' => 'Nie je to zložený obrázok', + 'Unknown' => 'Neznáme', + }, + }, + 'CompositeImageCount' => 'PoÄet zložených obrázkov', + 'CompositeImageExposureTimes' => 'Celková expozícia vÅ¡etkých zložených obrázkov', + 'CompressedBitsPerPixel' => 'Kompresia bitov na pixel', + 'Compression' => { + Description => 'Kompresia', + PrintConv => { + 'JBIG B&W' => 'JBIG Äiernobiely', + 'JBIG Color' => 'JBIG Farebný', + 'JPEG (old-style)' => 'JPEG (zastaralý)', + 'Kodak DCR Compressed' => 'Komprimovaný Kodak DCR', + 'Kodak KDC Compressed' => 'Komprimovaný Kodak KDC', + 'Lossy JPEG' => 'Stratový JPEG', + 'Microsoft Document Imaging (MDI) Binary Level Codec' => 'Binárny kodek Microsoft Document Imaging (MDI)', + 'Microsoft Document Imaging (MDI) Progressive Transform Codec' => 'Kodek progresívnej transformácie Microsoft Document Imaging (MDI)', + 'Microsoft Document Imaging (MDI) Vector' => 'Microsoft Document Imaging (MDI) Vektor', + 'Next' => 'Kódovanie NeXT', + 'Nikon NEF Compressed' => 'Komprimovaný Nikon NEF', + 'Packed RAW' => 'Balený RAW', + 'Pentax PEF Compressed' => 'Komprimovaný Pentax PEF', + 'Samsung SRW Compressed' => 'Komprimovaný Samsung SRW', + 'Samsung SRW Compressed 2' => 'Komprimovaný 2 Samsung SRW', + 'Sony ARW Compressed' => 'Komprimovaný Sony ARW', + 'Uncompressed' => 'Bez kompresie', + }, + }, + 'ConfirmedObjectSize' => 'Potvrdená veľkosÅ¥ predmetu', + 'ConsecutiveBadFaxLines' => 'PoÄet po sebe idúcich zalomených riadkov', + 'Contact' => 'Kontakt', + 'ContentLocationCode' => 'Kód umiestnenia obsahu', + 'ContentLocationName' => 'Názov umiestnenia obsahu', + 'Contrast' => { + Description => 'Kontrast', + PrintConv => { + 'High' => 'Vysoký', + 'Low' => 'Nízky', + 'Normal' => 'Normálny', + }, + }, + 'Converter' => 'Konvertor', + 'Copyright' => 'Autorské právo', + 'CopyrightNotice' => 'Oznámenie o autorských právach', + 'Country-PrimaryLocationCode' => 'Kód lokality krajiny', + 'Country-PrimaryLocationName' => 'Názov lokality krajiny', + 'CreateDate' => 'Dátum digitalizácie', + 'Credit' => 'Kredit', + 'CropBottom' => 'Orezávanie zospodu', + 'CropLeft' => 'Orezávanie vľavo', + 'CropRight' => 'Orezávanie vpravo', + 'CropTop' => 'Orezávanie zhora', + 'CurrentICCProfile' => 'Aktuálny profil ICC', + 'CurrentPreProfileMatrix' => 'Aktuálna predprofilová matrica', + 'CustomRendered' => { + Description => 'Vlastné vykresľovanie', + PrintConv => { + 'Custom' => 'Vlastné', + 'HDR (no original saved)' => 'HDR (nie je uložený žiadny originál)', + 'HDR (original saved)' => 'HDR (originál uložený)', + 'Normal' => 'Normálne', + 'Original (for HDR)' => 'Originál (pre HDR)', + 'Panorama' => 'Panoráma', + 'Portrait' => 'Portrét', + 'Portrait HDR' => 'Portrét HDR', + }, + }, + 'DNGAdobeData' => 'DNG - Údaje Adobe', + 'DNGBackwardVersion' => 'Spätná verzia DNG', + 'DNGLensInfo' => 'Informácie o objektíve DNG', + 'DNGPrivateData' => 'DNG - Súkromné ​​údaje', + 'DNGVersion' => 'Verzia DNG', + 'DataCompressionMethod' => 'Spôsob kompresie údajov', + 'DataType' => 'Typ údajov', + 'DateCreated' => 'Dátum vytvorenia', + 'DateSent' => 'Dátum odoslania', + 'DateTimeOriginal' => 'Dátum/ÄŒas zhotovenia', + 'Decode' => 'Dekódovanie', + 'DefaultBlackRender' => { + Description => 'Predvolené vykresľovanie Äiernej', + PrintConv => { + 'Auto' => 'Automatické', + 'None' => 'Žiadne', + }, + }, + 'DefaultCropOrigin' => 'Predvolená veľkosÅ¥ orezaného originálu', + 'DefaultCropSize' => 'Predvolená veľkosÅ¥ orezaného obrázku', + 'DefaultImageColor' => 'Predvolená farba obrázku', + 'DefaultScale' => 'Predvolená stupnica', + 'DefaultUserCrop' => 'Predvolená oblasÅ¥ orezania', + 'DepthFar' => 'Maximálna hĺbka vody od fotoaparátu', + 'DepthFormat' => { + Description => 'Formát hĺbky', + PrintConv => { + 'Inverse' => 'Inverzný', + 'Linear' => 'Lineárny', + 'Unknown' => 'Neznámy', + }, + }, + 'DepthMeasureType' => { + Description => 'Typ merania hĺbky', + PrintConv => { + 'Optical Axis' => 'Optická os', + 'Optical Ray' => 'Optický lúÄ', + 'Unknown' => 'Neznáme', + }, + }, + 'DepthNear' => 'Hĺbka vody od fotoaparátu', + 'DepthUnits' => { + Description => 'Jednotky hĺbky', + PrintConv => { + 'Meters' => 'Metre', + 'Unknown' => 'Neznáme', + }, + }, + 'Destination' => 'Destinácia', + 'DeviceSettingDescription' => 'Popis predvolieb fotoaparátu', + 'DigitalCreationDate' => 'Dátum digitalizácie súboru', + 'DigitalCreationTime' => 'ÄŒas digitalizácie súboru', + 'DigitalZoomRatio' => 'Pomer digitálneho zoomu', + 'DistortionCorrParams' => 'Parametre korekcie skreslenia', + 'DistortionCorrection' => { + Description => 'Korekcia skreslenia', + PrintConv => { + 'Auto' => 'Automatická', + 'Auto fixed by lens' => 'Automaticky fixovaná objektívom', + 'No correction params available' => 'Nie sú dostupné žiadne parametre korekcie', + 'Off' => 'Vypnutá', + }, + }, + 'DocumentHistory' => 'História dokumentu', + 'DocumentName' => 'Názov dokumentu', + 'DocumentNotes' => 'Pripomienky k dokumentu', + 'DotRange' => 'Rozsah bodov', + 'EditStatus' => 'Stav úprav', + 'EditorialUpdate' => { + Description => 'Druh aktualizácie', + PrintConv => { + 'Additional language' => 'Doplnkový jazyk', + }, + }, + 'EndPoints' => 'Koncové body', + 'EnhanceParams' => 'VylepÅ¡ené paramentre', + 'EnvelopeNumber' => 'Číslo obálky', + 'EnvelopePriority' => { + Description => 'Priorita obálky', + PrintConv => { + '0 (reserved)' => '0 (rezervované)', + '1 (most urgent)' => '1 (najnaliehavejÅ¡ie)', + '5 (normal urgency)' => '5 (bežná naliehavosÅ¥)', + '8 (least urgent)' => '8 (najmenej naliehavé)', + '9 (user-defined priority)' => '9 (priorita definovaná užívateľom)', + }, + }, + 'EnvelopeRecordVersion' => 'Verzia záznamu obálky', + 'ExcursionTolerance' => { + Description => 'Tolerancia exkurzie', + PrintConv => { + 'Allowed' => 'Povolené', + 'Not Allowed' => 'Nie je povolené', + }, + }, + 'ExifCameraInfo' => 'Informácie Exif o fotoaparáte', + 'ExifImageHeight' => 'Exif - Výška obrázku', + 'ExifImageWidth' => 'Exif - Šírka obrázku', + 'ExifVersion' => 'Verzia Exif', + 'ExpandFilm' => 'Rozšírenie filmu', + 'ExpandFilterLens' => 'Rozšírenie filtra objektívu', + 'ExpandFlashLamp' => 'Rozšírenie bleskovej lampy', + 'ExpandLens' => 'Rozšírenie objektívu', + 'ExpandScanner' => 'Rozšírenie skenera', + 'ExpandSoftware' => 'Softvérové rozšírenie', + 'ExpirationDate' => 'Dátum ukonÄenia platnosti', + 'ExpirationTime' => 'ÄŒas skonÄenia platnosti', + 'Exposure' => 'Expozícia', + 'ExposureCompensation' => 'Kompenzácia expozície', + 'ExposureIndex' => 'Index expozície', + 'ExposureMode' => { + Description => 'ExpoziÄný režim', + PrintConv => { + 'Auto' => 'Automatický', + 'Auto bracket' => 'Automatické stupňovanie', + 'Manual' => 'Manuálny', + }, + }, + 'ExposureProgram' => { + Description => 'ExpoziÄný program', + PrintConv => { + 'Action (High speed)' => 'AkÄný (vysoká rýchlosÅ¥) ', + 'Aperture-priority AE' => 'AE s prioritou clony', + 'Bulb' => 'Žiarovka', + 'Creative (Slow speed)' => 'Kreatívny (pomalá rýchlosÅ¥)', + 'Landscape' => 'Krajina ', + 'Manual' => 'Manuálny', + 'Not Defined' => 'Nedefinovaný', + 'Portrait' => 'Portrét ', + 'Shutter speed priority AE' => 'Priorita rýchlosti uzávierky AE', + }, + }, + 'ExposureTime' => 'ÄŒas expozície', + 'ExtraSamples' => { + Description => 'Popis voliteľných súÄastí', + PrintConv => { + 'Associated Alpha' => 'Pridružené alfa', + 'Unassociated Alpha' => 'Nepripojený alfa', + 'Unspecified' => 'NeÅ¡pecifikované', + }, + }, + 'FNumber' => 'Clonové Äíslo', + 'FaxProfile' => { + Description => 'Faxový profil', + PrintConv => { + 'Extended B&W lossless, F' => 'Rozšírené Äiernobiele bezstratové, F', + 'Lossless JBIG B&W, J' => 'Bezstratový JBIG Äiernobiely, J', + 'Lossless color and grayscale, L' => 'Bezstratové farby a odtiene sivej, L', + 'Lossy color and grayscale, C' => 'Stratové farby a odtiene Å¡edej, C', + 'Minimal B&W lossless, S' => 'Minimálne Äiernobiele bezstratové, S', + 'Mixed raster content, M' => 'ZmieÅ¡aný obsah rastra, M', + 'Multi Profiles' => 'Viacnásobné profily', + 'Profile T' => 'Profil T', + 'Unknown' => 'Neznámy', + }, + }, + 'FaxRecvParams' => 'Parametre prijímania faxov', + 'FaxRecvTime' => 'ÄŒas potrebný na prijatie faxu', + 'FaxSubAddress' => 'Podadresa faxu', + 'FileFormat' => { + Description => 'Formát súboru', + PrintConv => { + 'Compressed Binary File [.ZIP] (PKWare Inc)' => 'Komprimovaný binárny súbor [.ZIP] (PKWare Inc)', + 'IPTC Unstructured Character Oriented File Format (UCOFF)' => 'Formát neÅ¡truktúrovaných znakovo orientovaných súborov IPTC (UCOFF)', + 'IPTC-NAA Digital Newsphoto Parameter Record' => 'Záznam parametrov digitálnych novín IPTC-NAA', + 'IPTC7901 Recommended Message Format' => 'OdporúÄaný formát správy IPTC7901', + 'News Industry Text Format (NITF)' => 'Textový formát pre spravodajstvo (NITF)', + 'No ObjectData' => 'Žiadne údaje o objekte', + 'Ritzaus Bureau NITF version (RBNITF DTD)' => 'Verzia Ritzaus Bureau NITF (RBNITF DTD)', + 'Tagged Image File Format (Adobe/Aldus Image data)' => 'OznaÄený formát obrazového súboru (obrazové údaje Adobe/Aldus)', + }, + }, + 'FileSource' => { + Description => 'Zdroj súboru', + PrintConv => { + 'Digital Camera' => 'Digitálny fotoaparát', + 'Film Scanner' => 'Filmový skener', + 'Reflection Print Scanner' => 'Odrazový skener', + 'Sigma Digital Camera' => 'Digitálny fotoaparát Sigma', + }, + }, + 'FileVersion' => 'Verzia súboru', + 'FillOrder' => { + Description => 'Poradie výplne', + PrintConv => { + 'Normal' => 'Normálne', + 'Reversed' => 'Obrátené', + }, + }, + 'FixtureIdentifier' => 'Identifikátor prísluÅ¡enstva', + 'Flash' => { + Description => 'Stav blesku pri snímaní', + PrintConv => { + 'Auto, Did not fire' => 'Automaticky, nebol odpálený', + 'Auto, Did not fire, Red-eye reduction' => 'Automaticky, nebol odpálený, redukcia Äervených oÄí', + 'Auto, Fired' => 'Automaticky, odpálený', + 'Auto, Fired, Red-eye reduction' => 'Zapnutý, odpálený, redukcia Äervených oÄí', + 'Auto, Fired, Red-eye reduction, Return detected' => 'Zapnutý, odpálený, redukcia Äervených oÄí, návrat bol zistený', + 'Auto, Fired, Red-eye reduction, Return not detected' => 'Zapnutý, odpálený, redukcia Äervených oÄí, návrat nebol zistený', + 'Auto, Fired, Return detected' => 'Automaticky, odpálený, návrat bol zistený', + 'Auto, Fired, Return not detected' => 'Automaticky, odpálený, návrat nebol zistený', + 'Fired' => 'Odpálený', + 'Fired, Red-eye reduction' => 'Odpálený, redukcia Äervených oÄí', + 'Fired, Red-eye reduction, Return detected' => 'Odpálený, redukcia Äervených oÄí, návrat bol zistený', + 'Fired, Red-eye reduction, Return not detected' => 'Odpálený, redukcia Äervených oÄí, návrat nebol zistený', + 'Fired, Return detected' => 'Odpálený, návrat bol zistený', + 'Fired, Return not detected' => 'Odpálený, návrat nebol zistený', + 'No Flash' => 'Bez blesku', + 'No flash function' => 'Žiadna funkcia blesku', + 'Off, Did not fire' => 'Vypnutý, nebol odpálený', + 'Off, Did not fire, Return not detected' => 'Vypnutý, nebol odpálený, návrat nebol zistený', + 'Off, No flash function' => 'Výpnutý, žiadna funkcia blesku', + 'Off, Red-eye reduction' => 'Vypnutý, redukcia Äervených oÄí', + 'On, Did not fire' => 'Zapnutý, nebol odpálený', + 'On, Fired' => 'Zapnutý, odpálený', + 'On, Red-eye reduction' => 'Zapnutý, redukcia Äervených oÄí', + 'On, Red-eye reduction, Return detected' => 'Zapnutý, redukcia Äervených oÄí, návrat bol zistený', + 'On, Red-eye reduction, Return not detected' => 'Zapnutý, redukcia Äervených oÄí, návrat nebol zistený', + 'On, Return detected' => 'Zapnutý, návrat bol zistený ', + 'On, Return not detected' => 'Zapnutý, návrat nebol zistený ', + }, + }, + 'FlashEnergy' => 'Energia blesku', + 'FlashpixVersion' => 'Verzia Flashpix', + 'FocalLength' => 'Ohnisková vzdialenosÅ¥', + 'FocalLengthIn35mmFormat' => 'Ohnisková vzdialenosÅ¥ vo formáte 35 mm', + 'FocalPlaneResolutionUnit' => { + Description => 'Jednotka rozlíšenia ohniskovej roviny', + PrintConv => { + 'None' => 'žiadne', + 'inches' => 'palce', + }, + }, + 'FocalPlaneXResolution' => 'Horizontálne rozlíšenie ohniskovej roviny', + 'FocalPlaneYResolution' => 'Vertikálne rozlíšenie ohniskovej roviny', + 'ForwardMatrix1' => 'Priama matrica 1', + 'ForwardMatrix2' => 'Priama matrica 2', + 'ForwardMatrix3' => 'Priama matrica 3', + 'FovCot' => 'Zobrazovací uhol', + 'FrameRate' => 'PoÄet snímok za sekundu', + 'FreeByteCounts' => 'PoÄet voľných bajtov', + 'FreeOffsets' => 'Voľné korekcie', + 'GDALMetadata' => 'GDAL - Metadáta', + 'GDALNoData' => 'GDAL - PriehľadnosÅ¥', + 'GPSAltitude' => 'Nadmorská výška', + 'GPSAltitudeRef' => { + Description => 'Ref. nadmorskej výšky', + PrintConv => { + 'Above Sea Level' => 'Nad hladinou mora', + 'Below Sea Level' => 'Pod hladinou mora', + }, + }, + 'GPSAreaInformation' => 'Informácie o oblasti', + 'GPSDOP' => 'Stupeň presnosti merania GPS', + 'GPSDateStamp' => 'Dátum a Äas GPS', + 'GPSDestBearing' => 'Azimut k cieľovému bodu', + 'GPSDestBearingRef' => { + Description => 'Ref. azimut k cieľovému bodu', + PrintConv => { + 'Magnetic North' => 'Magnetický sever', + 'True North' => 'SkutoÄný sever', + }, + }, + 'GPSDestDistance' => 'VzdialenosÅ¥ k cieľu', + 'GPSDestDistanceRef' => { + Description => 'Ref. vzdialenosÅ¥ k cieľu', + PrintConv => { + 'Kilometers' => 'Kilometre', + 'Miles' => 'Míle', + 'Nautical Miles' => 'Námorné míle', + }, + }, + 'GPSDestLatitude' => 'Zemepisná šírka cieľa', + 'GPSDestLatitudeRef' => { + Description => 'Ref. pre zemepisnú šírku cieľa', + PrintConv => { + 'North' => 'Severná šírka', + 'South' => 'Južná šírka', + }, + }, + 'GPSDestLongitude' => 'Zemepisná dĺžka cieľa', + 'GPSDestLongitudeRef' => { + Description => 'Ref. pre zemepisnú dĺžku cieľa', + PrintConv => { + 'East' => 'Východná dĺžka', + 'West' => 'Západná dĺžka', + }, + }, + 'GPSDifferential' => { + Description => 'Diferenciálna korekcia', + PrintConv => { + 'Differential Corrected' => 'Diferenciálna korekcia', + 'No Correction' => 'Žiadna korekcia', + }, + }, + 'GPSHPositioningError' => 'Chyba horizontálneho polohovania', + 'GPSImgDirection' => 'Smer GPS obrázku', + 'GPSImgDirectionRef' => { + Description => 'Ref. smer obrázku', + PrintConv => { + 'Magnetic North' => 'Magnetický sever', + 'True North' => 'SkutoÄný sever', + }, + }, + 'GPSLatitude' => 'Zemepisná šírka', + 'GPSLatitudeRef' => { + Description => 'Ref. zemepisnej šírky', + PrintConv => { + 'North' => 'Severná šírka', + 'South' => 'Južná šírka', + }, + }, + 'GPSLongitude' => 'Zemepisná dĺžka', + 'GPSLongitudeRef' => { + Description => 'Ref. zemepisnej dĺžky', + PrintConv => { + 'East' => 'Východná dĺžka', + 'West' => 'Západná dĺžka', + }, + }, + 'GPSMapDatum' => 'Údaje geodetického zamerania', + 'GPSMeasureMode' => { + Description => 'Režim merania GPS', + PrintConv => { + '2-Dimensional Measurement' => '2-rozmerné meranie', + '3-Dimensional Measurement' => '3-rozmerné meranie', + }, + }, + 'GPSProcessingMethod' => 'Metóda spracovania GPS', + 'GPSSatellites' => 'Satelity použité na meranie', + 'GPSSpeed' => 'RýchlosÅ¥ pohybu prijímaÄa GPS', + 'GPSSpeedRef' => { + Description => 'Jednotky na meranie rýchlosti', + PrintConv => { + 'knots' => 'uzly', + }, + }, + 'GPSStatus' => { + Description => 'Stav prijímaÄa GPS', + PrintConv => { + 'Measurement Active' => 'Meranie je aktívne', + 'Measurement Void' => 'Meranie je nesprávne', + }, + }, + 'GPSTimeStamp' => 'ÄŒas GPS (atómové hodiny)', + 'GPSTrack' => 'Smer pohybu prijímaÄa GPS', + 'GPSTrackRef' => { + Description => 'Ref. na urÄenie smeru pohybu prijímaÄa', + PrintConv => { + 'Magnetic North' => 'Magnetický sever', + 'True North' => 'SkutoÄný sever', + }, + }, + 'GPSVersionID' => 'Identifikátor verzie GPS', + 'GainControl' => { + Description => 'Ovládanie zosilnenia', + PrintConv => { + 'High gain down' => 'Vysoké zosilnenie nižšie', + 'High gain up' => 'Vysoké zosilnenie', + 'Low gain down' => 'Nízke zosilnenie nižšie', + 'Low gain up' => 'Nízke zosilnenie', + 'None' => 'Žiadna', + }, + }, + 'Gamma' => 'Gama', + 'GammaCompensatedValue' => 'Hodnota kompenzácie gama', + 'GeoTiffAsciiParams' => 'Geo Tiff - Parametre Ascii', + 'GeoTiffDirectory' => 'Adresár Geo Tiff', + 'GeoTiffDoubleParams' => 'Geo Tiff - Parametre duplikovania', + 'GooglePlusUploadCode' => 'Kód na odosielanie služby Google Plus', + 'GrayResponseCurve' => 'Optická hustota v stupňoch Å¡edej', + 'GrayResponseUnit' => 'Jednotky hustoty v stupňoch Å¡edej', + 'HCUsage' => { + Description => 'Využitie HCU', + PrintConv => { + 'Line Art' => 'Umenie línie', + 'Trap' => 'Zachytenie', + }, + }, + 'HalftoneHints' => 'Poltónové rady', + 'HasselbladExif' => 'Exif Hasselblad', + 'HasselbladRawImage' => 'Obrázok Hasselblad Raw', + 'Headline' => 'Titulok', + 'HeightResolution' => 'Rozlíšenie na výšku (PPI)', + 'HighISOMultiplierBlue' => 'Vysoký multiplikátor ISO modrej farby', + 'HighISOMultiplierGreen' => 'Vysoký multiplikátor ISO zelenej farby', + 'HighISOMultiplierRed' => 'Vysoký multiplikátor ISO Äervenej farby', + 'HostComputer' => 'Hostiteľský poÄítaÄ', + 'Humidity' => 'VlhkosÅ¥', + 'ICC_Profile' => 'Profil ICC', + 'INGRReserved' => 'INGR Rezervované', + 'IPTCBitsPerSample' => 'IPTC - Bitov na vzorku', + 'IPTCImageHeight' => 'IPTC - Výška obrázku', + 'IPTCImageRotation' => { + Description => 'IPTC OtoÄenie obrázku', + PrintConv => { + '0' => '0°', + '180' => '180°', + '270' => '270°', + '90' => '90°', + }, + }, + 'IPTCImageWidth' => 'IPTC - Šírka obrázku', + 'IPTCPictureNumber' => 'IPTC - Číslo obrázku', + 'IPTCPixelHeight' => 'IPTC - Výška pixelov', + 'IPTCPixelWidth' => 'IPTC - Šírka pixelov', + 'ISO' => 'CitlivosÅ¥ ISO', + 'ISOSpeed' => 'RýchlosÅ¥ citlivosti ISO', + 'ISOSpeedLatitudeyyy' => 'Hodnota zemepisnej šírky citlivosti ISO YYY', + 'ISOSpeedLatitudezzz' => 'Hodnota zemepisnej šírky citlivosti ISO ZZZ', + 'IT8Header' => 'HlaviÄka IT8', + 'IlluminantData1' => 'Údaje o osvetlení 1', + 'IlluminantData2' => 'Údaje o osvetlení 2', + 'IlluminantData3' => 'Údaje o osvetlení 3', + 'Image::ExifTool::Composite' => 'Zloženie', + 'Image::ExifTool::DNG::OriginalRaw' => 'Originálny Raw DNG', + 'Image::ExifTool::Exif::Main' => 'Exif', + 'Image::ExifTool::GPS::Main' => 'GPS', + 'Image::ExifTool::IPTC::ApplicationRecord' => 'Záznam aplikácie IPTC', + 'Image::ExifTool::IPTC::EnvelopeRecord' => 'IPTC Záznam obálky', + 'Image::ExifTool::IPTC::NewsPhoto' => 'Novinky IPTC Foto', + 'Image::ExifTool::IPTC::ObjectData' => 'IPTC - Údaje o objekte', + 'Image::ExifTool::IPTC::PostObjectData' => 'IPTC - Predbežné údaje o objekte', + 'Image::ExifTool::IPTC::PreObjectData' => 'IPTC - Údaje pred objektom', + 'ImageByteCount' => 'VeľkosÅ¥ obrázku (v bajtoch)', + 'ImageColorIndicator' => { + Description => 'Indikátor farby obrázku', + PrintConv => { + 'Specified Image Color' => 'Å pecifikovaná farba obrázku', + 'Unspecified Image Color' => 'NeÅ¡pecifikovaná farba obrázku', + }, + }, + 'ImageColorValue' => 'Hodnota farby obrázku', + 'ImageDataDiscard' => { + Description => 'Zahodený obrazový údaj', + PrintConv => { + 'Flexbits Discarded' => 'Vyradené flexibilné bity', + 'Full Resolution' => 'Úplné rozlíšenie', + 'HighPass Frequency Data Discarded' => 'Vyradené údaje s vysokou frekvenciou', + 'Highpass and LowPass Frequency Data Discarded' => 'Vyradené údaje o vysokej a nízkej frekvencii', + }, + }, + 'ImageDepth' => 'Hĺbka obrázku', + 'ImageDescription' => 'Popis obrázku', + 'ImageFullHeight' => 'Celá výška obrázku', + 'ImageFullWidth' => 'Celá šírka obrázku', + 'ImageHeight' => 'Výška obrázku', + 'ImageHistory' => 'História obrázku', + 'ImageID' => 'ID obrázku', + 'ImageLayer' => 'Obrazová vrstva', + 'ImageNumber' => 'Číslo obrázku', + 'ImageOffset' => 'Korekcia obrázku', + 'ImageOrientation' => { + Description => 'Orientácia obrázku', + PrintConv => { + 'Landscape' => 'Krajina', + 'Portrait' => 'Portrét', + 'Square' => 'Å tvorec', + }, + }, + 'ImageReferencePoints' => 'ReferenÄné body obrázku', + 'ImageSourceData' => 'Surové obrazové údaje', + 'ImageType' => { + Description => 'Typ obrázku', + PrintConv => { + 'Page' => 'Stránka', + 'Preview' => 'Náhľad', + }, + }, + 'ImageUniqueID' => 'JedineÄný identifikátor obrázku', + 'ImageWidth' => 'Šírka obrázku', + 'Indexed' => { + Description => 'Indexovanie', + PrintConv => { + 'Indexed' => 'Indexované', + 'Not indexed' => 'Neindexované', + }, + }, + 'InkNames' => 'Názvy atramentov', + 'InkSet' => { + Description => 'Sada atramentov', + PrintConv => { + 'Not CMYK' => 'Nie CMYK', + }, + }, + 'InterchangeColorSpace' => 'Zmena farebného priestoru', + 'IntergraphFlagRegisters' => 'Intergraph - RegistraÄná znaÄka', + 'IntergraphMatrix' => 'Intergraph - Matrica', + 'IntergraphPacketData' => 'Intergraph - Údaje o balíku', + 'Interlace' => 'Prelínanie', + 'InteropIndex' => { + Description => 'Index kompatibility súborov', + PrintConv => { + 'R03 - DCF option file (Adobe RGB)' => 'R03 - súbor možností DCF (Adobe RGB)', + 'R98 - DCF basic file (sRGB)' => 'R98 - základný súbor DCF (sRGB)', + 'THM - DCF thumbnail file' => 'THM - súbor miniatúry DCF', + }, + }, + 'InteropVersion' => 'Verzia kompatibility súborov', + 'JBIGOptions' => 'Voľby JBIG', + 'JPEGACTables' => 'Tabuľky JPEGAC', + 'JPEGDCTables' => 'Tabuľky JPEGDC', + 'JPEGLosslessPredictors' => 'Bezstratové prediktory JPEG', + 'JPEGPointTransforms' => 'Bodové transformácie JPEG', + 'JPEGProc' => { + Description => 'Kompresia JPEG v starom Å¡týle', + PrintConv => { + 'Baseline' => 'Základná línia', + 'Lossless' => 'Bezstratové', + }, + }, + 'JPEGQTables' => 'Tabuľky JPEGQ', + 'JPEGRestartInterval' => 'Interval opätovného spustenia JPEG', + 'JPEGTables' => 'Tabuľky JPEG', + 'JobID' => 'ID úlohy', + 'JpgFromRaw' => 'Súbor Jpg vložený do súboru Raw', + 'JpgFromRawLength' => 'Ťahy v súbore JPG vloženom do súboru Raw', + 'JpgFromRawStart' => 'Posun súboru JPG vloženého do súboru Raw', + 'Keywords' => 'KľúÄové slová', + 'LanguageIdentifier' => 'Identifikátor jazyka', + 'Lens' => 'Objektív', + 'LensInfo' => 'Informácie o objektíve', + 'LensMake' => 'Výrobca objektívu', + 'LensModel' => 'Model objektívu', + 'LensSerialNumber' => 'Sériové Äíslo objektívu', + 'LightSource' => { + Description => 'Zdroj svetla', + PrintConv => { + 'Cloudy' => 'ZamraÄené', + 'Cool White Fluorescent' => 'Studená biela žiarivka', + 'Day White Fluorescent' => 'Denná biela žiarivka', + 'Daylight' => 'Denné svetlo', + 'Daylight Fluorescent' => 'Denné svetlo žiarivky', + 'Fine Weather' => 'SlneÄno', + 'Flash' => 'Blesk', + 'Fluorescent' => 'Žiarivka', + 'ISO Studio Tungsten' => 'Å túdio ISO Tungsten', + 'Other' => 'Iné osvetlenie', + 'Shade' => 'Tieň', + 'Standard Light A' => 'Å tandardné svetlo A', + 'Standard Light B' => 'Å tandardné svetlo B', + 'Standard Light C' => 'Å tandardné svetlo C', + 'Tungsten (Incandescent)' => 'Volfrámové (žiarovky)', + 'Unknown' => 'Neznáme', + 'Warm White Fluorescent' => 'Teplá biela žiarivka', + 'White Fluorescent' => 'Biela žiarivka', + }, + }, + 'LinearResponseLimit' => 'Limit lineárnej odozvy', + 'LinearityLimitBlue' => 'Limit linearity modrej farby', + 'LinearityLimitGreen' => 'Limit linearity zelenej farby', + 'LinearityLimitRed' => 'Limit linearity Äervenej farby', + 'LinearizationTable' => 'Tabuľka linearizácie', + 'LocalCaption' => 'Miestny titulok', + 'LocalizedCameraModel' => 'Lokalizovaný model fotoaparátu', + 'LookupTable' => 'Tabuľka náhrad', + 'MDColorTable' => 'MD - Tabuľka farieb', + 'MDFileTag' => 'MD - OznaÄenie súboru', + 'MDFileUnits' => 'MD - Dátové jednotky súboru', + 'MDLabName' => 'MD - Tvorca súborov', + 'MDPrepDate' => 'MD - Dátum prípravy', + 'MDPrepTime' => 'MD - ÄŒas prípravy', + 'MDSampleInfo' => 'MD - Informácie o vzorke', + 'MDScalePixel' => 'MD - Mierka pixelov', + 'MSDocumentText' => 'MS - Textový dokument', + 'MSDocumentTextPosition' => 'MS - Pozícia textu v dokumente', + 'MSPropertySetStorage' => 'MS - Vlastnosti pamäťovej skupiny', + 'Make' => 'Výrobca', + 'MakerNoteApple' => 'Poznámka výrobcu Apple', + 'MakerNoteCanon' => 'Poznámka výrobcu Canon', + 'MakerNoteCasio' => 'Poznámka výrobcu Casio', + 'MakerNoteCasio2' => 'Poznámka výrobcu Casio 2', + 'MakerNoteDJI' => 'Poznámka výrobcu DJI', + 'MakerNoteDJIInfo' => 'Poznámka výrobcu DJI Info', + 'MakerNoteFLIR' => 'Poznámka výrobcu FLIR', + 'MakerNoteFujiFilm' => 'Poznámka výrobcu Fuji Film', + 'MakerNoteGE' => 'Poznámka výrobcu GE', + 'MakerNoteGE2' => 'Poznámka výrobcu GE2', + 'MakerNoteHP' => 'Poznámka výrobcu HP', + 'MakerNoteHP2' => 'Poznámka výrobcu HP2', + 'MakerNoteHP4' => 'Poznámka výrobcu HP4', + 'MakerNoteHP6' => 'Poznámka výrobcu HP6', + 'MakerNoteHasselblad' => 'Poznámka výrobcu Hasselblad', + 'MakerNoteISL' => 'Poznámka výrobcu ISL', + 'MakerNoteJVC' => 'Poznámka výrobcu JVC', + 'MakerNoteJVCText' => 'Poznámka výrobcu JVC - Textovo', + 'MakerNoteKodak10' => 'Poznámka výrobcu Kodak 10', + 'MakerNoteKodak11' => 'Poznámka výrobcu Kodak 11', + 'MakerNoteKodak12' => 'Poznámka výrobcu Kodak 12', + 'MakerNoteKodak1a' => 'Poznámka výrobcu Kodak 1a', + 'MakerNoteKodak1b' => 'Poznámka výrobcu Kodak 1b', + 'MakerNoteKodak2' => 'Poznámka výrobcu Kodak 2', + 'MakerNoteKodak3' => 'Poznámka výrobcu Kodak 3', + 'MakerNoteKodak4' => 'Poznámka výrobcu Kodak 4', + 'MakerNoteKodak5' => 'Poznámka výrobcu Kodak 5', + 'MakerNoteKodak6a' => 'Poznámka výrobcu Kodak 6a', + 'MakerNoteKodak6b' => 'Poznámka výrobcu Kodak 6b', + 'MakerNoteKodak7' => 'Poznámka výrobcu Kodak 7', + 'MakerNoteKodak8a' => 'Poznámka výrobcu Kodak 8a', + 'MakerNoteKodak8b' => 'Poznámka výrobcu Kodak 8b', + 'MakerNoteKodak8c' => 'Poznámka výrobcu Kodak 8c', + 'MakerNoteKodak9' => 'Poznámka výrobcu Kodak 9', + 'MakerNoteKodakUnknown' => 'Poznámka výrobcu Kodak - Neznáme', + 'MakerNoteKyocera' => 'Poznámka výrobcu Kyocera', + 'MakerNoteLeica' => 'Poznámka výrobcu Leica', + 'MakerNoteLeica10' => 'Poznámka výrobcu Leica 10', + 'MakerNoteLeica2' => 'Poznámka výrobcu Leica 2', + 'MakerNoteLeica3' => 'Poznámka výrobcu Leica 3', + 'MakerNoteLeica4' => 'Poznámka výrobcu Leica 4', + 'MakerNoteLeica5' => 'Poznámka výrobcu Leica 5', + 'MakerNoteLeica6' => 'Poznámka výrobcu Leica 6', + 'MakerNoteLeica7' => 'Poznámka výrobcu Leica 7', + 'MakerNoteLeica8' => 'Poznámka výrobcu Leica 8', + 'MakerNoteLeica9' => 'Poznámka výrobcu Leica 9', + 'MakerNoteMinolta' => 'Poznámka výrobcu Minolta', + 'MakerNoteMinolta2' => 'Poznámka výrobcu Minolta 2', + 'MakerNoteMinolta3' => 'Poznámka výrobcu Minolta 3', + 'MakerNoteMotorola' => 'Poznámka výrobcu Motorola', + 'MakerNoteNikon' => 'Poznámka výrobcu Nikon', + 'MakerNoteNikon2' => 'Poznámka výrobcu Nikon 2', + 'MakerNoteNikon3' => 'Poznámka výrobcu Nikon 3', + 'MakerNoteNintendo' => 'Poznámka výrobcu Nintendo', + 'MakerNoteOlympus' => 'Poznámka výrobcu Olympus', + 'MakerNoteOlympus2' => 'Poznámka výrobcu Olympus 2', + 'MakerNoteOlympus3' => 'Poznámka výrobcu Olympus 3', + 'MakerNotePanasonic' => 'Poznámka výrobcu Panasonic', + 'MakerNotePanasonic2' => 'Poznámka výrobcu Panasonic 2', + 'MakerNotePanasonic3' => 'Poznámka výrobcu Panasonic 3', + 'MakerNotePentax' => 'Poznámka výrobcu Pentax', + 'MakerNotePentax2' => 'Poznámka výrobcu Pentax 2', + 'MakerNotePentax3' => 'Poznámka výrobcu Pentax 3', + 'MakerNotePentax4' => 'Poznámka výrobcu Pentax 4', + 'MakerNotePentax5' => 'Poznámka výrobcu Pentax 5', + 'MakerNotePentax6' => 'Poznámka výrobcu Pentax 6', + 'MakerNotePhaseOne' => 'Poznámka výrobcu Phase One', + 'MakerNoteReconyx' => 'Poznámka výrobcu Reconyx', + 'MakerNoteReconyx2' => 'Poznámka výrobcu Reconyx 2', + 'MakerNoteReconyx3' => 'Poznámka výrobcu Reconyx 3', + 'MakerNoteRicoh' => 'Poznámka výrobcu Ricoh', + 'MakerNoteRicoh2' => 'Poznámka výrobcu Ricoh 2', + 'MakerNoteRicohPentax' => 'Poznámka výrobcu Ricoh Pentax', + 'MakerNoteRicohText' => 'Poznámka výrobcu Ricoh - Textovo', + 'MakerNoteSafety' => { + Description => 'BezpeÄnostná poznámka výrobcu', + PrintConv => { + 'Safe' => 'BezpeÄný', + 'Unsafe' => 'NebezpeÄný', + }, + }, + 'MakerNoteSamsung1a' => 'Poznámka výrobcu Samsung 1a', + 'MakerNoteSamsung1b' => 'Poznámka výrobcu Samsung 1b', + 'MakerNoteSamsung2' => 'Poznámka výrobcu Samsung 2', + 'MakerNoteSanyo' => 'Poznámka výrobcu Sanyo', + 'MakerNoteSanyoC4' => 'Poznámka výrobcu Sanyo C4', + 'MakerNoteSanyoPatch' => 'Poznámka výrobcu Sanyo Sanyo Patch', + 'MakerNoteSigma' => 'Poznámka výrobcu Sigma', + 'MakerNoteSony' => 'Poznámka výrobcu Sony', + 'MakerNoteSony2' => 'Poznámka výrobcu Sony 2', + 'MakerNoteSony3' => 'Poznámka výrobcu Sony 3', + 'MakerNoteSony4' => 'Poznámka výrobcu Sony 4', + 'MakerNoteSony5' => 'Poznámka výrobcu Sony 5', + 'MakerNoteSonyEricsson' => 'Poznámka výrobcu Sony Ericsson', + 'MakerNoteSonySRF' => 'Poznámka výrobcu Sony SRF', + 'MakerNoteUnknown' => 'Poznámky neznámeho výrobcu', + 'MakerNoteUnknownBinary' => 'Poznámky neznámeho výrobcu - Binárne', + 'MakerNoteUnknownText' => 'Poznámky neznámeho výrobcu - Textovo', + 'MaskSubArea' => 'PodoblasÅ¥ masky', + 'MaskedAreas' => 'Maskované oblasti', + 'MasterDocumentID' => 'Základný identifikátor dokumentu', + 'MatrixWorldToCamera' => 'Matrica zobrazenia medzi svetom a fotoaparátom', + 'MatrixWorldToScreen' => 'Matrica zobrazenia medzi svetom a obrazovkou', + 'Matteing' => 'ZrnitosÅ¥', + 'MaxApertureValue' => 'Maximálna hodnota clony', + 'MaxSampleValue' => 'Maximálna hodnota súÄasti', + 'MaxSubfileSize' => 'Maximálna veľkosÅ¥ podsúboru', + 'MaximumDensityRange' => 'Maximálny rozsah hustoty', + 'MaximumObjectSize' => 'Maximálna veľkosÅ¥ objektu', + 'MeteringMode' => { + Description => 'Režim merania', + PrintConv => { + 'Average' => 'Priemerné', + 'Center-weighted average' => 'Stredovo vyvážený priemer', + 'Multi-segment' => 'Viacsegmentovo', + 'Multi-spot' => 'Viacbodovo', + 'Other' => 'Iné', + 'Partial' => 'ÄŒiastoÄne', + 'Spot' => 'Bodovo', + 'Unknown' => 'Neznáme', + }, + }, + 'MinSampleValue' => 'Minimálna hodnota súÄasti', + 'ModeNumber' => 'Číslo režimu', + 'Model' => 'Názov modelu fotoaparátu', + 'ModelTiePoint' => 'Modelový bod väzby', + 'ModelTransform' => 'Model transformácie', + 'ModifyDate' => 'Dátum úpravy', + 'MoireFilter' => 'Moiré filter', + 'MultiProfiles' => { + Description => 'Viacnásobné profily', + PrintConv => { + 'JBIG2 Profile M' => 'Profil JBIG2 M', + 'N Layer Profile M' => 'N Profil vrstvy M', + 'Profile C' => 'Profil C', + 'Profile F' => 'Profil F', + 'Profile J' => 'Profil J', + 'Profile L' => 'Profil L', + 'Profile M' => 'Profil M', + 'Profile S' => 'Profil S', + 'Profile T' => 'Profil T', + 'Resolution/Image Width' => 'Rozlíšenie/Šírka obrázku', + 'Shared Data' => 'Zdieľaný dátový', + }, + }, + 'Multishot' => { + Description => 'Viacnásobné snímanie', + PrintConv => { + 'Off' => 'Vypnuté', + 'Pixel Shift' => 'Posun pixelov', + }, + }, + 'NewRawImageDigest' => 'Hash obrázku RAW v novom formáte', + 'NewsPhotoVersion' => 'Verzia News Photo', + 'Noise' => 'Å um', + 'NoiseProfile' => 'Profil Å¡umu', + 'NoiseReductionApplied' => 'Použitá redukcia Å¡umu', + 'NoiseReductionParams' => 'Parametre redukciu Å¡umu', + 'NumIndexEntries' => 'PoÄet záznamov v indexe', + 'NumberofInks' => 'PoÄet atramentov', + 'OPIProxy' => { + PrintConv => { + 'Higher resolution image does not exist' => 'Obrázok s vyšším rozlíšením neexistuje', + 'Higher resolution image exists' => 'Obrázok s vyšším rozlíšením existuje', + }, + }, + 'ObjectAttributeReference' => 'Odkaz na atribút objektu', + 'ObjectCycle' => { + Description => 'Denný cyklus', + PrintConv => { + 'Both Morning and Evening' => 'Ráno aj veÄer', + 'Evening' => 'VeÄer', + 'Morning' => 'Ráno', + }, + }, + 'ObjectName' => 'Názov objektu', + 'ObjectPreviewData' => 'Objekt náhľadu - Údaje', + 'ObjectPreviewFileFormat' => { + Description => 'Objekt náhľadu - Formát súboru', + PrintConv => { + 'Compressed Binary File [.ZIP] (PKWare Inc)' => 'Komprimovaný binárny súbor [.ZIP] (PKWare Inc)', + 'IPTC Unstructured Character Oriented File Format (UCOFF)' => 'Formát neÅ¡truktúrovaných znakovo orientovaných súborov IPTC (UCOFF)', + 'IPTC-NAA Digital Newsphoto Parameter Record' => 'Záznam parametrov digitálnych novín IPTC-NAA', + 'IPTC7901 Recommended Message Format' => 'OdporúÄaný formát správy IPTC7901', + 'News Industry Text Format (NITF)' => 'Textový formát pre spravodajstvo (NITF)', + 'No ObjectData' => 'Žiadne údaje o objekte', + 'Ritzaus Bureau NITF version (RBNITF DTD)' => 'Verzia Ritzaus Bureau NITF (RBNITF DTD)', + 'Tagged Image File Format (Adobe/Aldus Image data)' => 'OznaÄený formát obrazového súboru (obrazové údaje Adobe/Aldus)', + }, + }, + 'ObjectPreviewFileVersion' => 'Objekt náhľadu - Verzia súboru', + 'ObjectSizeAnnounced' => 'Deklarovaná veľkosÅ¥ objektu', + 'ObjectTypeReference' => 'Odkaz na typ objektu', + 'OceApplicationSelector' => 'Výber aplikácie Oce', + 'OceIDNumber' => 'IdentifikaÄné Äíslo Oce', + 'OceImageLogic' => 'Logika obrazu Oce', + 'OceScanjobDesc' => 'Popis úlohy skenovania Oce', + 'OffsetSchema' => 'Schéma korekcie', + 'OffsetTime' => 'Korekcia Äasu', + 'OffsetTimeDigitized' => 'Korekcia digitalizovaného Äasu', + 'OffsetTimeOriginal' => 'Korekcia pôvodného Äasu', + 'OldSubfileType' => { + Description => 'Starý typ podsúboru', + PrintConv => { + 'Full-resolution image' => 'Obrázok v plnom rozlíšení', + 'Reduced-resolution image' => 'Obrázok so zníženým rozlíšením', + 'Single page of multi-page image' => 'Jedna strana viacstranového obrázku', + }, + }, + 'OpcodeList1' => 'Zoznam kódov operácií 1', + 'OpcodeList2' => 'Zoznam kódov operácií 2', + 'OpcodeList3' => 'Zoznam kódov operácií 3', + 'Opto-ElectricConvFactor' => 'Optoelektrický konverzný faktor', + 'Orientation' => { + Description => 'Orientácia', + PrintConv => { + 'Horizontal (normal)' => 'Horizontálna (normálne)', + 'Mirror horizontal' => 'Zrkadlená horizontálne', + 'Mirror horizontal and rotate 270 CW' => 'Zrkadlená horizontálne a otoÄená o 270° v smere hodinových ruÄiÄiek', + 'Mirror horizontal and rotate 90 CW' => 'Zrkadlená horizontálne a otoÄená o 90° v smere hodinových ruÄiÄiek', + 'Mirror vertical' => 'Zrkadlená vertikálne', + 'Rotate 180' => 'OtoÄená o 180°', + 'Rotate 270 CW' => 'OtoÄená o 270° v smere hodinových ruÄiÄiek', + 'Rotate 90 CW' => 'OtoÄená o 90° v smere hodinových ruÄiÄiek', + }, + }, + 'OriginalBestQualitySize' => 'Pôvodná veľkosÅ¥ najlepÅ¡ej kvality', + 'OriginalDefaultCropSize' => 'Pôvodná predvolená veľkosÅ¥ orezania', + 'OriginalDefaultFinalSize' => 'Plná veľkosÅ¥ pôvodného obrázku', + 'OriginalFileName' => 'Názov pôvodného súboru', + 'OriginalRawCreator' => 'Autor pôvodného Raw', + 'OriginalRawFileData' => 'Pôvodné údaje súboru Raw', + 'OriginalRawFileDigest' => 'Originálny súbor Raw Digest', + 'OriginalRawFileName' => 'Pôvodný názov súboru Raw', + 'OriginalRawFileType' => 'Pôvodný typ súboru Raw', + 'OriginalRawImage' => 'Pôvodný Raw obrázok', + 'OriginalRawResource' => 'Pôvodný zdroj Raw', + 'OriginalTHMCreator' => 'Pôvodný autor THM', + 'OriginalTHMFileType' => 'Pôvodný typ súboru THM', + 'OriginalTHMImage' => 'Pôvodný obrázok THM', + 'OriginalTHMResource' => 'Pôvodný zdroj THM', + 'OriginalTransmissionReference' => 'Pôvodné prepojenie na odovzdanie', + 'OriginatingProgram' => 'Pôvodný program', + 'OtherImage' => 'Iný obrázok', + 'OtherImageLength' => 'Dĺžka iného obrázku', + 'OtherImageStart' => 'Odsadenie iného obrázku', + 'OwnerID' => 'Identifikátor vlastníka', + 'OwnerName' => 'Meno vlastníka', + 'Padding' => 'Odsadenie', + 'PageName' => 'Názov strany', + 'PageNumber' => 'Číslo strany', + 'PanasonicRawVersion' => 'Verzia Panasonic Raw', + 'PanasonicTitle' => 'Panasonic - Názov', + 'PanasonicTitle2' => 'Panasonic - Názov 2', + 'PhotometricInterpretation' => { + Description => 'Farebný model', + PrintConv => { + 'BlackIsZero' => 'ÄŒierna je nula', + 'Color Filter Array' => 'Pole farebných filtrov', + 'Depth Map' => 'Hĺbková mapa', + 'Linear Raw' => 'Surový lineárny', + 'RGB Palette' => 'Paleta RGB', + 'Semantic Mask' => 'Sémantická maska', + 'Sequential Color Filter' => 'SekvenÄný farebný filter', + 'Transparency Mask' => 'Priehľadná maska', + 'WhiteIsZero' => 'Biela je nula', + }, + }, + 'PixelFormat' => { + Description => 'Formát pixelu', + PrintConv => { + '112-bit 6 Channels Alpha' => '112-bitový so 6 alfa kanálmi', + '112-bit 7 Channels' => '112-bitový so 7 kanálmi', + '128-bit 7 Channels Alpha' => '128-bitový so 7 alfa kanálmi', + '128-bit 8 Channels' => '128-bitový s 8 kanálmi', + '128-bit PRGBA Float' => '128-bitové PRGBA plávajúce', + '128-bit RGB Float' => '128-bitové RGB plávajúce', + '128-bit RGBA Fixed Point' => '128-bitové RGBA s pevným bodom', + '128-bit RGBA Float' => '128-bitové RGBA plávajúce', + '144-bit 8 Channels Alpha' => '144-bitový s 8 alfa kanálmi', + '16-bit BGR555' => '16-bitový BGR555', + '16-bit BGR565' => '16-bitový BGR565', + '16-bit Gray' => '16-bitový Å¡edý', + '16-bit Gray Half' => '16-bitový Å¡edý poloviÄný', + '24-bit 3 Channels' => '24-bitový s 3 kanálmi', + '24-bit BGR' => '24-bitový BGR', + '24-bit RGB' => '24-bitové RGB', + '32-bit 3 Channels Alpha' => '32-bitový s 3 alfa kanálmi', + '32-bit 4 Channels' => '32-bitový so 4 kanálmi', + '32-bit BGR' => '32-bitový BGR', + '32-bit BGR101010' => '32-bitové BGR101010', + '32-bit BGRA' => '32-bitové BGRA', + '32-bit CMYK' => '32-bitový CMYK', + '32-bit Gray Fixed Point' => '32-bitový Å¡edý s pevným bodom', + '32-bit Gray Float' => '32-bitový Å¡edý plávajúci', + '32-bit PBGRA' => '32-bitové PBGRA', + '32-bit RGBE' => '32-bitový RGBE ', + '40-bit 4 Channels Alpha' => '40-bitový so 4 alfa kanálmi', + '40-bit 5 Channels' => '40-bitový s 5 kanálmi', + '40-bit CMYK Alpha' => '40-bitový alfa CMYK', + '48-bit 3 Channels' => '48-bitový s 3 kanálmi', + '48-bit 5 Channels Alpha' => '48-bitový s 5 alfa kanálmi', + '48-bit 6 Channels' => '48-bitový so 6 kanálmi', + '48-bit RGB' => '48--bitové RGB', + '48-bit RGB Fixed Point' => '48-bitové RGB s pevným bodom', + '48-bit RGB Half' => '48-bitové RGB poloviÄné', + '56-bit 6 Channels Alpha' => '56-bitový s 6 alfa kanálmi', + '56-bit 7 Channels' => '56-bitový so 7 kanálmi', + '64-bit 3 Channels Alpha' => '64-bitový s 3 alfa kanálmi', + '64-bit 4 Channels' => '64-bitový so 4 kanálmi', + '64-bit 7 Channels Alpha' => '64-bitový so 7 alfa kanálmi', + '64-bit 8 Channels' => '64-bitový s 8 kanálmi', + '64-bit CMYK' => '64-bitový CMYK', + '64-bit PRGBA' => '64--bitové PRGBA', + '64-bit RGBA' => '64--bitové RGBA', + '64-bit RGBA Fixed Point' => '64-bitové RGBA s pevným bodom', + '64-bit RGBA Half' => '64-bitové RGBA poloviÄné', + '72-bit 8 Channels Alpha' => '72-bitový s 8 alfa kanálmi', + '8-bit Gray' => '8-bitový sivý', + '80-bit 4 Channels Alpha' => '80-bitový s 4 alfa kanálmi', + '80-bit 5 Channels' => '80-bitový s 5 kanálmi', + '80-bit CMYK Alpha' => '80-bitový alfa CMYK', + '96-bit 5 Channels Alpha' => '96-bitový s 5 alfa kanálmi', + '96-bit 6 Channels' => '96-bitový so 6 kanálmi', + '96-bit RGB Fixed Point' => '96-bitové RGB s pevným bodom', + 'Black & White' => 'ÄŒiernobiely', + }, + }, + 'PixelIntensityRange' => 'Rozsah intenzity pixelov', + 'PixelMagicJBIGOptions' => 'Voľby PixelMagicJBIGO', + 'PixelScale' => 'Pixelová mierka', + 'PlanarConfiguration' => { + Description => 'Princíp organizácie údajov', + PrintConv => { + 'Chunky' => 'Hrubá', + 'Planar' => 'PloÅ¡ná', + }, + }, + 'Predictor' => { + Description => 'Predikcia', + PrintConv => { + 'Floating point' => 'Plávajúci bod', + 'Floating point X2' => 'Plávajúci bod X2', + 'Floating point X4' => 'Plávajúci bod X4', + 'Horizontal difference X2' => 'Horizontálne rozliÅ¡ovanie X2', + 'Horizontal difference X4' => 'Horizontálne rozliÅ¡ovanie X4', + 'Horizontal differencing' => 'Horizontálne rozliÅ¡ovanie', + 'None' => 'Žiadna', + }, + }, + 'Prefs' => 'Predvoľby', + 'Pressure' => 'Tlak', + 'PreviewApplicationName' => 'Názov náhľadu aplikácie', + 'PreviewApplicationVersion' => 'Verzia náhľadu aplikácie', + 'PreviewColorSpace' => { + Description => 'Náhľad farebného priestoru', + PrintConv => { + 'Gray Gamma 2.2' => 'Sivá gama 2,2', + 'Unknown' => 'Neznámy', + }, + }, + 'PreviewDateTime' => 'Dátum a Äas náhľadu', + 'PreviewImage' => 'Náhľad obrázku', + 'PreviewImageLength' => 'Náhľad dĺžky obrázku', + 'PreviewImageStart' => 'Posun náhľadu obrázku', + 'PreviewSettingsDigest' => 'Prehľad nastavení Digest', + 'PreviewSettingsName' => 'Názov nastavenia náhľadu', + 'PrimaryChromaticities' => 'FarebnosÅ¥ základných farieb', + 'PrintIM' => 'ZhodnosÅ¥ tlaÄových obrázkov', + 'ProcessingSoftware' => 'Spracovateľský softvér', + 'ProductID' => 'Identifikátor produktu', + 'ProfileCalibrationSig' => 'Profil kalibraÄného signálu', + 'ProfileCopyright' => 'Autorské práva k profilu', + 'ProfileEmbedPolicy' => { + Description => 'Zásady vkladania profilov', + PrintConv => { + 'Allow Copying' => 'PovoliÅ¥ kopírovanie', + 'Embed if Used' => 'VložiÅ¥, ak sa používa', + 'Never Embed' => 'Nikdy nevkladaÅ¥', + 'No Restrictions' => 'Bez obmedzení', + }, + }, + 'ProfileGainTableMap' => 'Profil mapy tabuľky zosilnenia', + 'ProfileHueSatMapData1' => 'Profil - Mapa údajov a odtieňoch a sýtosti 1', + 'ProfileHueSatMapData2' => 'Profil - Mapa údajov a odtieňoch a sýtosti 2', + 'ProfileHueSatMapData3' => 'Údaje o profile odtieňa 3', + 'ProfileHueSatMapDims' => 'Profil - Mapa odtieňov', + 'ProfileHueSatMapEncoding' => { + Description => 'Kódovanie mapy profilov Hue Sat', + PrintConv => { + 'Linear' => 'Lineárne', + }, + }, + 'ProfileLookTableData' => 'Profil - Údaje v tabuľke vzhľadu', + 'ProfileLookTableDims' => 'Profil - Rozdelenie odtieňov', + 'ProfileLookTableEncoding' => { + Description => 'Kódovanie vzhľadovej tabuľky profilu', + PrintConv => { + 'Linear' => 'Lineárne', + }, + }, + 'ProfileName' => 'Názov profilu', + 'ProfileToneCurve' => 'Profil - Tónová krivka', + 'ProfileType' => { + Description => 'Typ profilu', + PrintConv => { + 'Group 3 FAX' => 'Skupina 3 FAX', + 'Unspecified' => 'NeÅ¡pecifikovaný', + }, + }, + 'ProgramVersion' => 'Verzia programu', + 'Province-State' => 'Krajina - Å tát', + 'QuantizationMethod' => { + Description => 'KvantizaÄná metóda', + PrintConv => { + 'AP Domestic Analogue' => 'Domáce analógové AP', + 'Color Space Specific' => 'Å pecifický farebný priestor', + 'Compression Method Specific' => 'Å pecifická metóda kompresie', + 'Gamma Compensated' => 'Kompenzácia gama', + 'Linear Density' => 'Lineárna hustota', + 'Linear Dot Percent' => 'Lineárne bodové percento', + 'Linear Reflectance/Transmittance' => 'Lineárna odrazivosÅ¥/priepustnosÅ¥', + }, + }, + 'RGBTables' => 'Tabuľky RGB', + 'RasterPadding' => { + Description => 'Výplň rastra', + PrintConv => { + 'Byte' => 'Bajt', + 'Long Sector' => 'Dlhý sektor', + 'Long Word' => 'Dlhé slovo', + 'Sector' => 'Sektor', + 'Word' => 'Slovo', + }, + }, + 'RasterizedCaption' => 'Rastrovaný titulok', + 'Rating' => 'Hodnotenie', + 'RatingPercent' => 'Hodnotenie v percentách', + 'RawDataOffset' => 'Korekcia údajov Raw', + 'RawDataUniqueID' => 'JedineÄný identifikátor údajov Raw', + 'RawFile' => 'Súbor Raw', + 'RawFormat' => 'Formát Raw', + 'RawImageDigest' => 'Súbor Raw Digest', + 'RawImageSegmentation' => 'Segmentácia obrázku Raw', + 'RawToPreviewGain' => 'Faktor zväÄÅ¡enia medzi súborom Raw a náhľadom', + 'RecommendedExposureIndex' => 'OdporúÄaný index expozície', + 'RedBalance' => 'Rovnováha Äervenej farby', + 'ReductionMatrix1' => 'RedukÄná matrica 1', + 'ReductionMatrix2' => 'RedukÄná matrica 2', + 'ReductionMatrix3' => 'RedukÄná matrica 3', + 'ReelName' => 'Názov nosiÄa', + 'ReferenceBlackWhite' => 'PoÄiatoÄné hodnoty Äiernej a bielej', + 'ReferenceDate' => 'ReferenÄný dátum', + 'ReferenceNumber' => 'ReferenÄné Äíslo', + 'ReferenceService' => 'ReferenÄná služba', + 'RegionXformTackPoint' => 'Bod návratu regiónu Xform', + 'RelatedImageFileFormat' => 'Formát prepojeného obrazového súboru', + 'RelatedImageHeight' => 'Výška súvisiaceho obrázku', + 'RelatedImageWidth' => 'Šírka súvisiaceho obrázku', + 'RelatedSoundFile' => 'Súvisiaci zvukový súbor', + 'ReleaseDate' => 'Dátum vydania', + 'ReleaseTime' => 'ÄŒas vydania', + 'ResolutionUnit' => { + Description => 'Jednotka rozlíšenia', + PrintConv => { + 'None' => 'žiadne', + 'cm' => 'centimetre', + 'inches' => 'palce', + }, + }, + 'RowInterleaveFactor' => 'Faktor prekladania riadkov', + 'RowsPerStrip' => 'Riadkov na pruh', + 'SEMInfo' => 'Informácie o SEM', + 'SMaxSampleValue' => 'S max. hodnota vzorky', + 'SMinSampleValue' => 'S min. hodnota vzorky', + 'SRawType' => 'Typ SRaw', + 'SampleFormat' => { + Description => 'Formát súÄasti', + PrintConv => { + 'Complex float' => 'Komplexné Äíslo s pohyblivou Äiarkou', + 'Complex int' => 'Komplexné celé Äíslo', + 'Float' => 'Pohyblivá desatinná Äiarka', + 'Signed' => 'Celé Äíslo so znamienkom dvojkovej sústavy', + 'Undefined' => 'Nedefinovaný', + 'Unsigned' => 'Celé Äíslo bez znamienka', + }, + }, + 'SampleStructure' => 'Å truktúra súÄastí', + 'SamplesPerPixel' => 'PoÄet vzoriek na pixel', + 'SamsungRawByteOrder' => 'Samsung RAW - Poradie bajtov', + 'SamsungRawPointersLength' => 'Samsung RAW - Ukazovateľ dĺžky', + 'SamsungRawPointersOffset' => 'Samsung RAW - Posun ukazovateľa', + 'SamsungRawUnknown' => 'Samsung RAW - Neznámy', + 'Saturation' => { + Description => 'SýtosÅ¥', + PrintConv => { + 'High' => 'Vysoká', + 'Low' => 'Nízka', + 'Normal' => 'Normálna', + }, + }, + 'ScanningDirection' => { + Description => 'Smer snímania', + PrintConv => { + 'Bottom-Top, L-R' => 'Dole-hore, L-R', + 'Bottom-Top, R-L' => 'Dole-hore, R-L', + 'L-R, Bottom-Top' => 'L-R, odspodu nahor', + 'L-R, Top-Bottom' => 'L-R, zhora nadol', + 'R-L, Bottom-Top' => 'R-L, odspodu nahor', + 'R-L, Top-Bottom' => 'R-L, zhora nadol', + 'Top-Bottom, L-R' => 'Hore-dole, L-R', + 'Top-Bottom, R-L' => 'Hore-dole, R-L', + }, + }, + 'SceneCaptureType' => { + Description => 'Typ zachytenia scény', + PrintConv => { + 'Landscape' => 'Na šírku', + 'Night' => 'Noc', + 'Other' => 'Iný', + 'Portrait' => 'Na výšku', + 'Standard' => 'Å tandardný', + }, + }, + 'SceneType' => { + Description => 'Typ scény', + PrintConv => { + 'Directly photographed' => 'Priamo odfotografované', + }, + }, + 'SecurityClassification' => { + Description => 'BezpeÄnostná klasifikácia', + PrintConv => { + 'Confidential' => 'Dôverné', + 'Restricted' => 'Vyhradené', + 'Secret' => 'Tajné', + 'Top Secret' => 'Prísne tajné', + 'Unclassified' => 'Neklasifikované', + }, + }, + 'SelfTimerMode' => 'Režim samospúšte', + 'SemanticInstanceID' => 'Identifikátor sémantickej inÅ¡tancie', + 'SemanticName' => 'Sémantický názov', + 'SensingMethod' => { + Description => 'Spôsob snímania', + PrintConv => { + 'Color sequential area' => 'Farebná sekvenÄná oblasÅ¥', + 'Color sequential linear' => 'Farebný sekvenÄný lineárny', + 'Monochrome area' => 'Jednofarebná oblasÅ¥', + 'Monochrome linear' => 'Monochromatické lineárne', + 'Not defined' => 'Nie je definované', + 'One-chip color area' => 'JednoÄipová farebná oblasÅ¥', + 'Three-chip color area' => 'TrojÄipová farebná oblasÅ¥', + 'Trilinear' => 'Trilineárna', + 'Two-chip color area' => 'DvojÄipová farebná oblasÅ¥', + }, + }, + 'SensitivityType' => { + Description => 'Typ citlivosti', + PrintConv => { + 'ISO Speed' => 'RýchlosÅ¥ ISO', + 'Recommended Exposure Index' => 'OdporúÄaný index expozície', + 'Recommended Exposure Index and ISO Speed' => 'OdporúÄaný index expozície a rýchlosÅ¥ ISO', + 'Standard Output Sensitivity' => 'Å tandardná výstupná citlivosÅ¥', + 'Standard Output Sensitivity and ISO Speed' => 'Å tandardná výstupná citlivosÅ¥ a citlivosÅ¥ ISO ', + 'Standard Output Sensitivity and Recommended Exposure Index' => 'Å tandardná výstupná citlivosÅ¥ a odporúÄaný index expozície', + 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed' => 'Å tandardná výstupná citlivosÅ¥, odporúÄaný expoziÄný index a citlivosÅ¥ ISO', + 'Unknown' => 'Neznámy', + }, + }, + 'SensorBottomBorder' => 'Spodný okraj snímaÄa', + 'SensorHeight' => 'Výška snímaÄa', + 'SensorLeftBorder' => 'Ľavý okraj snímaÄa', + 'SensorRightBorder' => 'Pravý okraj snímaÄa', + 'SensorTopBorder' => 'Horný okraj snímaÄa', + 'SensorWidth' => 'Šírka snímaÄa', + 'SerialNumber' => 'Sériové Äíslo', + 'ServiceIdentifier' => 'Identifikátor služby', + 'ShadowScale' => 'Tieňová stupnica', + 'Shadows' => 'Tiene', + 'SharedData' => 'Zdieľané údaje', + 'Sharpness' => { + Description => 'OstrosÅ¥', + PrintConv => { + 'Hard' => 'Tvrdá', + 'Normal' => 'Normálna', + 'Soft' => 'Mäkká', + }, + }, + 'ShortDocumentID' => 'StruÄný identifikátor dokumentu', + 'ShutterSpeedValue' => 'Hodnota rýchlosti uzávierky', + 'SimilarityIndex' => 'Index podobnosti', + 'Site' => 'Miesto', + 'SizeMode' => { + Description => 'Režim veľkosti', + PrintConv => { + 'Size Known' => 'Známa veľkosÅ¥', + 'Size Not Known' => 'VeľkosÅ¥ neznáma', + }, + }, + 'Smoothness' => 'HladkosÅ¥', + 'Software' => 'Softvér', + 'SonyCropSize' => 'VeľkosÅ¥ orezu Sony', + 'SonyCropTopLeft' => 'Orez Sony vľavo hore', + 'SonyRawFileType' => { + Description => 'Typ súboru Sony Raw', + PrintConv => { + 'Sony Compressed RAW' => 'Sony komprimovaný RAW', + 'Sony Lossless Compressed RAW' => 'Sony bezstratový komprimovaný RAW', + 'Sony Lossless Compressed RAW 2' => 'Sony bezstratový komprimovaný RAW 2', + 'Sony Uncompressed 12-bit RAW' => 'Sony nekomprimovaný 12-bitový RAW', + 'Sony Uncompressed 14-bit RAW' => 'Sony nekomprimovaný 14-bitový RAW', + }, + }, + 'SonyToneCurve' => 'Tónová krivka Sony', + 'Source' => 'Zdroj', + 'SpatialFrequencyResponse' => 'Priestorový frekvenÄný rozsah', + 'SpecialInstructions' => 'Å peciálne pokyny', + 'SpectralSensitivity' => 'Spektrálna citlivosÅ¥', + 'StandardOutputSensitivity' => 'Å tandardná výstupná citlivosÅ¥', + 'StoNits' => 'Intenzita osvetlenia (konverzný faktor cd/m2)', + 'StripByteCounts' => 'PoÄet bajtov na pruh', + 'StripOffsets' => 'Korekcia pásov', + 'StripRowCounts' => 'PoÄet riadkov v páse', + 'Sub-location' => 'Poloha v meste', + 'SubFile' => 'Podsúbor', + 'SubSecTime' => 'ÄŒas úprav v milisekundách', + 'SubSecTimeDigitized' => 'ÄŒas vytvorenia v milisekundách', + 'SubSecTimeOriginal' => 'ÄŒas snímania v milisekundách', + 'SubTileBlockSize' => 'VeľkosÅ¥ bloku pod dlaždicou', + 'SubfileType' => { + Description => 'Typ podsúboru', + PrintConv => { + 'Alternate reduced-resolution image' => 'Alternatívny obrázok so zníženým rozlíšením', + 'Depth map' => 'Hĺbková mapa', + 'Depth map of reduced-resolution image' => 'Hĺbková mapa obrázku so zníženým rozlíšením', + 'Enhanced image data' => 'VylepÅ¡ené údaje obrázku', + 'Full-resolution image' => 'Obrázok v plnom rozlíšení', + 'Reduced-resolution image' => 'Obrázok so zníženým rozlíšením', + 'Semantic Mask' => 'Sémantická maska', + 'Single page of multi-page image' => 'Jedna strana viacstranového obrázku', + 'Single page of multi-page reduced-resolution image' => 'Jedna strana viacstranového obrázku so zníženým rozlíšením', + 'Transparency mask' => 'Maska priehľadnosti', + 'Transparency mask of multi-page image' => 'Maska priehľadnosti viacstránkového obrázku', + 'Transparency mask of reduced-resolution image' => 'Maska priehľadnosti obrázok so zníženým rozlíšením', + 'Transparency mask of reduced-resolution multi-page image' => 'Maska priehľadnosti viacstránkového obrázku so zníženým rozlíšením', + 'invalid' => 'neplatný', + }, + }, + 'SubjectArea' => 'OblasÅ¥ objektu', + 'SubjectDistance' => 'VzdialenosÅ¥ k zaostrenému objektu', + 'SubjectDistanceRange' => { + Description => 'VzdialenosÅ¥ od snímaného objektu', + PrintConv => { + 'Close' => 'Blízky', + 'Distant' => 'Vzdialený', + 'Macro' => 'Makro', + 'Unknown' => 'Neznámy', + }, + }, + 'SubjectLocation' => 'Umiestnenie predmetu', + 'SubjectReference' => 'Odkaz na tému', + 'SupplementalCategories' => 'Doplnkové kategórie', + 'SupplementalType' => { + Description => 'Typ prídavku', + PrintConv => { + 'Main Image' => 'Hlavný obrázok', + 'Rasterized Caption' => 'Rastrovaný titulok', + 'Reduced Resolution Image' => 'Obrázok so zníženým rozlíšením', + }, + }, + 'T4Options' => { + Description => 'Voľby T4', + PrintConv => { + '2-Dimensional encoding' => 'Dvojrozmerné kódovanie', + 'Fill bits added' => 'Pridanie bitov výplne', + 'Uncompressed' => 'Nekomprimované', + }, + }, + 'T6Options' => { + Description => 'Voľby T6', + PrintConv => { + 'Uncompressed' => 'Nekomprimované', + }, + }, + 'T82Options' => 'Voľby T82', + 'T88Options' => 'Voľby T88', + 'TIFF-EPStandardID' => 'Å tandardné ID TIFF-EP', + 'TIFF_FXExtensions' => { + Description => 'Rozšírenia TIFF FX', + PrintConv => { + 'B&W JBIG2' => 'ÄŒB JBIG2', + 'JBIG2 Profile M' => 'Profil JBIG2 M', + 'N Layer Profile M' => 'N profil vrstvy M', + 'Resolution/Image Width' => 'Rozlíšenie/šírka obrazu', + 'Shared Data' => 'Zdieľaný dátový', + }, + }, + 'TStop' => 'Prenosový index objektívu (T-stop)', + 'TargetPrinter' => 'Cieľová tlaÄiareň', + 'TextureFormat' => 'Formát textúry', + 'Thresholding' => { + Description => 'Prahová úprava', + PrintConv => { + 'No dithering or halftoning' => 'Žiadne rozostrenie alebo polotónovanie', + 'Ordered dither or halftone' => 'Usporiadaný rozklad alebo poltón', + 'Randomized dither' => 'Náhodný rozklad', + }, + }, + 'ThumbnailImage' => 'Miniatúra obrázku', + 'ThumbnailLength' => 'Dĺžka miniatúry', + 'ThumbnailOffset' => 'Posun miniatúry', + 'TileByteCounts' => 'PoÄet bajtov v dlaždici', + 'TileDepth' => 'Hĺbka dlaždice', + 'TileLength' => 'Dĺžka dlaždice', + 'TileOffsets' => 'Posunutie dlaždíc', + 'TileWidth' => 'Šírka dlaždice', + 'TimeCodes' => 'ÄŒasové kódy', + 'TimeCreated' => 'ÄŒas vytvorenia', + 'TimeSent' => 'ÄŒas odoslania', + 'TimeZoneOffset' => 'Posun Äasového pásma', + 'TransferFunction' => 'Prenosová funkcia', + 'TransferRange' => 'Rozsah prenosovej funkcie', + 'Transformation' => { + Description => 'Transformácia', + PrintConv => { + 'Horizontal (normal)' => 'Horizontálne (normálne)', + 'Mirror horizontal' => 'Zrkadlené horizontálne ', + 'Mirror horizontal and rotate 270 CW' => 'Zrkadlené horizontálne a otoÄené 270° doprava', + 'Mirror horizontal and rotate 90 CW' => 'Zrkadlené horizontálne a otoÄené 90° doprava', + 'Mirror vertical' => 'Zrkadlené vertikálne', + 'Rotate 180' => 'OtoÄené o 180°', + 'Rotate 270 CW' => 'OtoÄené o 270° doprava', + 'Rotate 90 CW' => 'OtoÄené o 90° doprava', + }, + }, + 'TransparencyIndicator' => 'Ukazovateľ priehľadnosti', + 'TrapIndicator' => 'Indikátor zachytenia', + 'UIC1Tag' => 'Tag UIC1', + 'UIC2Tag' => 'Tag UIC2', + 'UIC3Tag' => 'Tag UIC3', + 'UIC4Tag' => 'Tag UIC4', + 'USPTOMiscellaneous' => 'Rôzne USPTO', + 'USPTOOriginalContentType' => { + Description => 'USPTO - Typ pôvodného obsahu', + PrintConv => { + 'Color' => 'Farba', + 'Grayscale' => 'Odtiene Å¡edej', + 'Text or Drawing' => 'Text alebo kresba', + }, + }, + 'Uncompressed' => { + Description => 'Nekomprimovaný', + PrintConv => { + 'No' => 'Nie', + 'Yes' => 'Ãno', + }, + }, + 'UniqueCameraModel' => 'JedineÄný model fotoaparátu', + 'UniqueDocumentID' => 'JedineÄný identifikátor dokumentu', + 'UniqueObjectName' => 'JedineÄný názov objektu', + 'Urgency' => { + Description => 'Priorita spracovania', + PrintConv => { + '0 (reserved)' => '0 (rezervované)', + '1 (most urgent)' => '1 (najnaliehavejÅ¡ie)', + '5 (normal urgency)' => '5 (bežná naliehavosÅ¥)', + '8 (least urgent)' => '8 (najmenej naliehavé)', + '9 (user-defined priority)' => '9 (priorita definovaná užívateľom)', + }, + }, + 'UserComment' => 'Komentár užívateľa', + 'VersionYear' => 'Rok verzie', + 'VignettingCorrParams' => 'Parametre korekcie vinetácie', + 'VignettingCorrection' => { + Description => 'Korekcia vinetácie', + PrintConv => { + 'Auto' => 'Automatická', + 'Auto (ILCE-1)' => 'Automatická (ILCE-1)', + 'No correction params available' => 'Nie sú dostupné žiadne parametre korekcie', + 'Off' => 'Vypnutá', + }, + }, + 'WBBlueLevel' => 'Vyváženie bielej farby - Úroveň modrej', + 'WBGreenLevel' => 'Vyváženie bielej farby - Úroveň zelenej', + 'WBRedLevel' => 'Vyváženie bielej farby - Úroveň Äervenej', + 'WB_GRGBLevels' => 'Úrovne vyváženia bielej GRGB', + 'WangAnnotation' => 'Wangová anotácia', + 'WangTag1' => 'Wangová znaÄka 1', + 'WangTag3' => 'Wangová znaÄka 3', + 'WangTag4' => 'Wangová znaÄka 4', + 'WarpQuadrilateral' => 'Deformácia Å¡tvoruholníka', + 'WaterDepth' => 'Hĺbka vody', + 'WhiteBalance' => { + Description => 'Vyváženie bielej', + PrintConv => { + 'Auto' => 'Automatické', + 'Manual' => 'Manuálne', + }, + }, + 'WhiteLevel' => 'Úroveň bielej', + 'WhitePoint' => 'Farba bieleho bodu', + 'WidthResolution' => 'Rozlíšenie na šírku (PPI)', + 'WrapModes' => 'Režimy balenia', + 'Writer-Editor' => 'Spisovateľ-Redaktor', + 'XClipPathUnits' => 'Orezanie obrysových jednotiek X', + 'XPAuthor' => 'XP - Autor', + 'XPComment' => 'XP - Komentár', + 'XPKeywords' => 'XP - KľúÄová slová', + 'XPSubject' => 'XP - Téma', + 'XPTitle' => 'XP - Názov', + 'XPosition' => 'Pozícia obrázku X', + 'XResolution' => 'Horizontálne rozlíšenie', + 'YCbCrCoefficients' => 'Konverzné faktory Y Cb Cr', + 'YCbCrPositioning' => { + Description => 'Umiestnenie Y Cb Cr', + PrintConv => { + 'Centered' => 'Vycentrované', + 'Co-sited' => 'SpoloÄné umiestnenie', + }, + }, + 'YCbCrSubSampling' => 'Faktor Äiastkového vzorkovania Y Cb Cr', + 'YClipPathUnits' => 'Orezanie obrysových jednotiek Y', + 'YPosition' => 'Pozícia obrázku Y', + 'YResolution' => 'Vertikálne rozlíšenie', +); + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::sk.pm - ExifTool Slovak language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Peter Bagin for providing this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/sv.pm b/ExifTool/lib/Image/ExifTool/Lang/sv.pm new file mode 100644 index 0000000..df84ebb --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/sv.pm @@ -0,0 +1,638 @@ +#------------------------------------------------------------------------------ +# File: sv.pm +# +# Description: ExifTool Swedish language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::sv; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.05'; + +%Image::ExifTool::Lang::sv::Translate = ( + 'Aperture' => 'Bländare', + 'ApertureValue' => 'Bländare', + 'Artist' => 'Upphovsman', + 'Author' => 'Upphovsman', + 'AuthorsPosition' => 'Författarens befattning', + 'BitsPerSample' => 'Antal bitar per komponent', + 'Brightness' => 'Ljusstyrka', + 'By-line' => 'Upphovsman', + 'CFAPattern' => 'CFA-mönster', + 'CalibrationIlluminant1' => { + PrintConv => { + 'Cloudy' => 'Mulet', + 'Cool White Fluorescent' => 'Kalljusrör (W 3800 - 4500 k)', + 'Day White Fluorescent' => 'Dagljusrör, högdager (N 4600 - 5500k)', + 'Daylight' => 'Dagsljus', + 'Daylight Fluorescent' => 'Dagljusrör (D 5700 - 7100 k)', + 'Fine Weather' => 'Fint väder', + 'Flash' => 'Blixt', + 'Fluorescent' => 'Lysrörsbelysning', + 'ISO Studio Tungsten' => 'ISO tungsten studiobelysning', + 'Other' => 'Annan ljuskälla', + 'Shade' => 'Skugga', + 'Standard Light A' => 'Standardljus A', + 'Standard Light B' => 'Standardljus B', + 'Standard Light C' => 'Standardljus C', + 'Tungsten (Incandescent)' => 'Tungsten', + 'Unknown' => 'Okänt', + 'Warm White Fluorescent' => 'Varmt vitt fluorescerande (L 2600 - 3250k)', + 'White Fluorescent' => 'Vit lysrörsbelysning (WW 3250 - 3800 k)', + }, + }, + 'CalibrationIlluminant2' => { + PrintConv => { + 'Cloudy' => 'Mulet', + 'Cool White Fluorescent' => 'Kalljusrör (W 3800 - 4500 k)', + 'Day White Fluorescent' => 'Dagljusrör, högdager (N 4600 - 5500k)', + 'Daylight' => 'Dagsljus', + 'Daylight Fluorescent' => 'Dagljusrör (D 5700 - 7100 k)', + 'Fine Weather' => 'Fint väder', + 'Flash' => 'Blixt', + 'Fluorescent' => 'Lysrörsbelysning', + 'ISO Studio Tungsten' => 'ISO tungsten studiobelysning', + 'Other' => 'Annan ljuskälla', + 'Shade' => 'Skugga', + 'Standard Light A' => 'Standardljus A', + 'Standard Light B' => 'Standardljus B', + 'Standard Light C' => 'Standardljus C', + 'Tungsten (Incandescent)' => 'Tungsten', + 'Unknown' => 'Okänt', + 'Warm White Fluorescent' => 'Varmt vitt fluorescerande (L 2600 - 3250k)', + 'White Fluorescent' => 'Vit lysrörsbelysning (WW 3250 - 3800 k)', + }, + }, + 'Caption-Abstract' => 'Bildtext', + 'CaptionWriter' => 'Bildtextredigerare', + 'Categories' => 'Kategorier', + 'Category' => 'Kategorier', + 'City' => 'Ort', + 'ColorFilter' => 'Färgfilter', + 'ColorMode' => { + Description => 'Kreativa inst.', + PrintConv => { + 'Autumn Leaves' => 'Höstlöv', + 'B&W' => 'Svartvitt', + 'Clear' => 'Klart', + 'Deep' => 'Djupt', + 'Landscape' => 'Landskap', + 'Light' => 'Ljust', + 'Neutral' => 'Neutralt', + 'Night View' => 'Nattvy', + 'Night View/Portrait' => 'Nattporträtt', + 'Portrait' => 'Porträtt', + 'Sunset' => 'SolnedgÃ¥ng', + 'Vivid' => 'Levande färg', + }, + }, + 'ColorSpace' => { + Description => 'FärgomrÃ¥desinformation', + PrintConv => { + 'ICC Profile' => 'ICC-profil', + 'Uncalibrated' => 'Ej kalibrerat', + }, + }, + 'ColorTemperature' => 'Färgtemperatur', + 'Comment' => 'Kommentar', + 'ComponentsConfiguration' => 'Enskilda komponenters betydelse', + 'CompressedBitsPerPixel' => 'Bildkomprimeringsläge', + 'Compression' => { + Description => 'Komprimeringsschema', + PrintConv => { + 'JPEG' => 'JPEG-komprimering', + 'Uncompressed' => 'Ingen komprimering', + }, + }, + 'Contrast' => { + Description => 'Kontrast', + PrintConv => { + 'High' => 'HÃ¥rd', + 'Low' => 'Mjuk', + 'Normal' => 'Standard', + }, + }, + 'Copyright' => 'Copyright-innehavare', + 'CopyrightNotice' => 'Copyrightmeddelande', + 'Country' => 'Land', + 'Country-PrimaryLocationName' => 'Land', + 'CreateDate' => 'Skapat datum', + 'CreationDate' => 'Skapad datum', + 'Credit' => 'Medverkande', + 'CustomRendered' => { + Description => 'Anpassad bildbearbetning', + PrintConv => { + 'Custom' => 'Anpassad process', + 'Normal' => 'Normal process', + }, + }, + 'DateCreated' => 'Skapat datum', + 'DateTimeOriginal' => 'Ursprungligt datum & tid', + 'DeviceSettingDescription' => 'Beskrivning av enhetsinställning', + 'DigitalZoomRatio' => 'Digitalt zoomomfÃ¥ng', + 'Directory' => 'Filplats', + 'DriveMode' => 'Enhetsläge', + 'DynamicRangeOptimizer' => { + Description => 'Opt.av dyn.omr.', + PrintConv => { + 'Advanced Auto' => 'Avancerad auto', + 'Advanced Lv1' => 'Avancerad niv1', + 'Advanced Lv2' => 'Avancerad niv2', + 'Advanced Lv3' => 'Avancerad niv3', + 'Advanced Lv4' => 'Avancerad niv4', + 'Advanced Lv5' => 'Avancerad niv5', + 'Auto' => 'Automatisk', + 'Off' => 'Av', + }, + }, + 'ExifImageHeight' => 'Giltig bildhöjd', + 'ExifImageWidth' => 'Giltig bildbredd', + 'ExifOffset' => 'IFD-pekare för Exif', + 'ExifVersion' => 'Exif-version', + 'ExposureCompensation' => 'Exponeringsförskjutning', + 'ExposureIndex' => 'Exponeringsindex', + 'ExposureMode' => { + Description => 'Exponeringsläge', + PrintConv => { + 'Auto' => 'Automatisk exponering', + 'Auto bracket' => 'Automatisk alternativexponering', + 'Manual' => 'Manuell exponering', + }, + }, + 'ExposureProgram' => { + Description => 'Exponeringsprogram', + PrintConv => { + 'Action (High speed)' => 'Sport', + 'Aperture-priority AE' => 'Bländarprioritet', + 'Bulb' => 'Glödlampa', + 'Creative (Slow speed)' => 'Kreativ', + 'Landscape' => 'Liggande', + 'Manual' => 'Manuell exponering', + 'Portrait' => 'StÃ¥ende', + 'Program AE' => 'Normalt program', + 'Shutter speed priority AE' => 'Slutarprioritet', + }, + }, + 'ExposureTime' => 'Exponeringstid', + 'FNumber' => 'Bländare', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => 'Positiv riktning', + 'Rotate 90 CW' => 'Rotera 90° medurs', + }, + }, + 'FileFormat' => 'Format', + 'FileModifyDate' => 'Uppdateringsdatum', + 'FileName' => 'Filnamn', + 'FileSize' => 'Filstorlek', + 'FileSource' => { + Description => 'Filkälla', + PrintConv => { + 'Digital Camera' => 'DSC', + 'Film Scanner' => 'Skanner av transparent typ', + 'Reflection Print Scanner' => 'Skanner av reflextyp', + }, + }, + 'FileType' => 'Filtyp', + 'Filename' => 'Filnamn', + 'Flash' => { + Description => 'Blixt', + PrintConv => { + 'Auto, Fired' => 'PÃ… (automatisk blixt)', + 'Auto, Fired, Red-eye reduction' => '"PÃ… (automatisk blixt, reducering av röda ögon)"', + 'Auto, Fired, Red-eye reduction, Return detected' => '"PÃ… (automatisk blixt, reducering av röda ögon, reflekterat ljus)"', + 'Auto, Fired, Return detected' => '"PÃ… (automatisk blixt, reflekterat ljus)"', + 'Did not fire' => 'Blixten utlöstes inte', + 'Fired' => 'Blixten utlöstes', + 'Fired, Red-eye reduction' => 'PÃ… (reducering av röda ögon)', + 'Fired, Red-eye reduction, Return detected' => '"PÃ… (reducering av röda ögon, reflekterat ljus)"', + 'Fired, Return detected' => 'PÃ… (reflekterat ljus)', + 'No Flash' => 'Ingen blixtfunktion', + 'On, Fired' => 'PÃ… (upplättnad)', + 'On, Red-eye reduction' => '"PÃ… (upplättnad, reducering av röda ögon)"', + 'On, Red-eye reduction, Return detected' => '"PÃ… upplättnad reducering av röda ögon, reflekterat ljus)"', + 'On, Return detected' => '"PÃ… (upplättnad, reflekterat ljus)"', + }, + }, + 'FlashEnergy' => 'Blixtenergi', + 'FlashExposureComp' => 'Blixtkompensering', + 'FlashpixVersion' => 'Flashpix-version som stöds', + 'FocalLength' => 'Brännvidd', + 'FocalLength35efl' => 'Brännvidd (i 35 mm format)', + 'FocalLengthIn35mmFormat' => 'Brännvidd för 35 mm-film', + 'FocalPlaneResolutionUnit' => { + PrintConv => { + 'inches' => 'tum', + }, + }, + 'FocusMode' => 'Fokustyp', + 'FrameRate' => 'Bildfrekvens', + 'FrameSize' => 'Bildstorlek', + 'GPSAltitude' => 'Höjd', + 'GPSAltitudeRef' => { + Description => 'Höjdreferens', + PrintConv => { + 'Above Sea Level' => 'HavsnivÃ¥', + 'Below Sea Level' => 'HavsnivÃ¥ref. (negativt värde)', + }, + }, + 'GPSAreaInformation' => 'Namn pÃ¥ GPS-omrÃ¥de', + 'GPSDOP' => 'MÃ¥ttprecision', + 'GPSDateStamp' => 'GPS-datum', + 'GPSDestBearing' => 'Destination', + 'GPSDestBearingRef' => 'Referens för destination', + 'GPSDestDistance' => 'AvstÃ¥nd till destination', + 'GPSDestDistanceRef' => 'Referens för avstÃ¥nd till destination', + 'GPSDestLatitude' => 'Destinationsbredd', + 'GPSDestLatitudeRef' => 'Referens för destionationsbredd', + 'GPSDestLongitude' => 'Destionationslängd', + 'GPSDestLongitudeRef' => 'Referens för destinationslängd', + 'GPSDifferential' => { + Description => 'GPS-differentialkorrigering', + PrintConv => { + 'Differential Corrected' => 'Differentialkorrigering använd', + 'No Correction' => 'Mätning utan differentialkorrigering', + }, + }, + 'GPSImgDirection' => 'Bildriktning', + 'GPSImgDirectionRef' => 'Referens för bildriktning', + 'GPSInfo' => 'IFD-pekare för GPS-information', + 'GPSLatitude' => 'Bredd', + 'GPSLatitudeRef' => { + Description => 'Nordlig eller sydlig bredd', + PrintConv => { + 'North' => 'Nordlig bredd', + 'South' => 'Sydlig bredd', + }, + }, + 'GPSLongitude' => 'Längd', + 'GPSLongitudeRef' => { + Description => 'Östlig eller västlig längd', + PrintConv => { + 'East' => 'Östlig längd', + 'West' => 'Västlig längd', + }, + }, + 'GPSMapDatum' => 'Geodetiska mätningsdata använda', + 'GPSMeasureMode' => { + Description => 'GPS-mätningsläge', + PrintConv => { + '3-Dimensional Measurement' => '3D-mätning', + }, + }, + 'GPSProcessingMethod' => 'Namn pÃ¥ GPS-bearbetningsmetod', + 'GPSSatellites' => 'GPS-satelliter använda för mätning', + 'GPSSpeed' => 'Hastighet pÃ¥ GPS-mottagare', + 'GPSSpeedRef' => { + Description => 'Hastighetsenhet', + PrintConv => { + 'km/h' => 'Km/tim', + 'knots' => 'Knop', + 'mph' => 'Miles/tim', + }, + }, + 'GPSStatus' => { + Description => 'GPS-mottagarstatus', + PrintConv => { + 'Measurement Active' => 'Mätning pÃ¥gÃ¥r', + 'Measurement Void' => 'Mätningssamverkan', + }, + }, + 'GPSTimeStamp' => 'GPS-tid (atomur)', + 'GPSTrack' => 'Rörelseriktning', + 'GPSTrackRef' => { + Description => 'Referens för rörelseriktning', + PrintConv => { + 'Magnetic North' => 'Magnetisk riktning', + 'True North' => 'Verklig riktning', + }, + }, + 'GPSVersionID' => 'GPS tag version', + 'GainControl' => { + Description => 'Förstärkningskontroll', + PrintConv => { + 'High gain down' => 'Hög förstärkning ned', + 'High gain up' => 'Hög förstärkning upp', + 'Low gain down' => 'LÃ¥g förstärkning ned', + 'Low gain up' => 'LÃ¥g förstärkning upp', + 'None' => 'Ingen', + }, + }, + 'Gradation' => 'Effektfull', + 'HDR' => { + Description => 'Auto HDR', + PrintConv => { + 'Off' => 'Av', + }, + }, + 'Headline' => 'Rubrik', + 'HighISONoiseReduction' => { + Description => 'Hög-ISO brusred', + PrintConv => { + 'Auto' => 'Automatisk', + 'High' => 'Hög', + 'Low' => 'LÃ¥g', + 'Off' => 'Av', + }, + }, + 'Hue' => 'Nyans', + 'ICCProfile' => 'ICC profil', + 'ISO' => 'ISO värde', + 'ImageHeight' => 'Bildhöjd', + 'ImageSize' => 'Bildstorlek', + 'ImageUniqueID' => 'Unikt bild-ID', + 'ImageWidth' => 'Bildbredd', + 'Index' => 'SmÃ¥bilder', + 'Instructions' => 'Instruktioner', + 'InteropIndex' => 'Interoperability Identification', + 'InteropOffset' => 'Interoperability tag', + 'InteropVersion' => 'Interoperability Version', + 'JPEGQuality' => { + Description => 'Bildkvalitet', + PrintConv => { + 'Extra Fine' => 'Extra fin', + 'Fine' => 'Fin', + 'Standard' => 'Standardkvalitet', + }, + }, + 'Keywords' => 'Nyckelord', + 'Lens' => 'Objektiv', + 'LensInfo' => 'Objektivinformation', + 'LightSource' => { + Description => 'Ljuskälla', + PrintConv => { + 'Cloudy' => 'Mulet', + 'Cool White Fluorescent' => 'Kalljusrör (W 3800 - 4500 k)', + 'Day White Fluorescent' => 'Dagljusrör, högdager (N 4600 - 5500k)', + 'Daylight' => 'Dagsljus', + 'Daylight Fluorescent' => 'Dagljusrör (D 5700 - 7100 k)', + 'Fine Weather' => 'Fint väder', + 'Flash' => 'Blixt', + 'Fluorescent' => 'Lysrörsbelysning', + 'ISO Studio Tungsten' => 'ISO tungsten studiobelysning', + 'Other' => 'Annan ljuskälla', + 'Shade' => 'Skugga', + 'Standard Light A' => 'Standardljus A', + 'Standard Light B' => 'Standardljus B', + 'Standard Light C' => 'Standardljus C', + 'Tungsten (Incandescent)' => 'Tungsten', + 'Unknown' => 'Okänt', + 'Warm White Fluorescent' => 'Varmt vitt fluorescerande (L 2600 - 3250k)', + 'White Fluorescent' => 'Vit lysrörsbelysning (WW 3250 - 3800 k)', + }, + }, + 'Lightness' => 'Ljushet', + 'Location' => 'Plats', + 'LongExposureNoiseReduction' => { + Description => 'LÃ¥ngexp.brusred', + PrintConv => { + 'Off' => 'Av', + 'On' => 'PÃ¥', + }, + }, + 'Make' => 'Tillverkare', + 'MakerNotes' => 'Tillverkare', + 'MaxAperture' => 'Största bländare', + 'MeteringMode' => { + Description => 'Mätningstyp', + PrintConv => { + 'Average' => 'Genomsnitt', + 'Center-weighted average' => 'Centrumvägd genomsnittsmätning', + 'Multi-segment' => 'Mönster', + 'Multi-spot' => 'MultiSpot', + 'Other' => 'Annat', + 'Partial' => 'Delvis', + 'Unknown' => 'Okänt', + }, + }, + 'Model' => 'Kamera', + 'ModifyDate' => 'Filändringsdatum och -tid', + 'MultiFrameNoiseReduction' => { + Description => 'Multi Frame brusred.', + PrintConv => { + 'Off' => 'Av', + 'On' => 'PÃ¥', + }, + }, + 'NoiseReduction' => 'Brusreducering', + 'Opto-ElectricConvFactor' => 'Optoelektrisk konverteringsfaktor', + 'Orientation' => { + Description => 'Orientering', + PrintConv => { + 'Horizontal (normal)' => 'Positiv riktning', + 'Rotate 90 CW' => 'Rotera 90° medurs', + }, + }, + 'PhotometricInterpretation' => 'Pixelsammansättning', + 'PlanarConfiguration' => { + Description => 'Bilddataordning', + PrintConv => { + 'Chunky' => 'Kompakt format', + 'Planar' => 'Planar format', + }, + }, + 'PrimaryChromaticities' => 'Kromaticitet för primärfärger', + 'Province-State' => 'Län/provins', + 'Quality' => { + Description => 'Bildkvalitet', + PrintConv => { + 'Compressed RAW' => 'cRAW', + 'Compressed RAW + JPEG' => 'cRAW+JPEG', + 'Extra Fine' => 'Extra fin', + 'Fine' => 'Fin', + 'Low' => 'LÃ¥g kvalitet', + 'Normal' => 'Standardkvalitet', + 'RAW + JPEG' => 'RAW+JPEG', + }, + }, + 'RecordMode' => 'Inspelningsläge', + 'ReferenceBlackWhite' => 'Par av svartvita referensvärden', + 'RelatedImageFileFormat' => 'Tillhörande bildfilsformat', + 'RelatedImageHeight' => 'Tillhörande bildlängd', + 'RelatedImageWidth' => 'Tillhörande bildbredd', + 'RelatedSoundFile' => 'Tillhörande ljudfil', + 'ResolutionUnit' => { + Description => 'Enhet för X- och Y-upplösning', + PrintConv => { + 'cm' => 'centimeter', + 'inches' => 'tum', + }, + }, + 'RowsPerStrip' => 'Antal rader', + 'SamplesPerPixel' => 'Antal komponenter', + 'Saturation' => { + Description => 'Mättnad', + PrintConv => { + 'High' => 'Hög mättnad', + 'Low' => 'LÃ¥g mättnad', + 'Normal' => 'Standard', + }, + }, + 'SceneCaptureType' => { + Description => 'Motivtyp', + PrintConv => { + 'Landscape' => 'Landskap', + 'Night' => 'Nattmotiv', + 'Portrait' => 'Porträtt', + }, + }, + 'SceneMode' => { + Description => 'Scenval', + PrintConv => { + '3D Sweep Panorama' => '3D', + 'Anti Motion Blur' => 'Anti-rörelseoskärpa', + 'Auto' => 'Automatisk', + 'Cont. Priority AE' => 'Kont. prioritet AE', + 'Handheld Night Shot' => 'Manuell nattbild', + 'Landscape' => 'Landskap', + 'Macro' => 'Makro', + 'Night Portrait' => 'Nattporträtt', + 'Night Scene' => 'Nattvy', + 'Night View/Portrait' => 'Nattvy/porträtt', + 'Portrait' => 'Porträtt', + 'Sports' => 'Sporthändelse', + 'Sunset' => 'SolnedgÃ¥ng', + 'Sweep Panorama' => 'Panorering', + }, + }, + 'SceneType' => { + Description => 'Motivtyp', + PrintConv => { + 'Directly photographed' => 'En direktfotograferad bild', + }, + }, + 'SensingMethod' => { + Description => 'Avkänningsmetod', + PrintConv => { + 'Color sequential area' => 'Color sequential area sensor', + 'Color sequential linear' => 'Color sequential linear sensor', + 'One-chip color area' => 'One-chip color area sensor', + 'Three-chip color area' => 'Three-chip color area sensor', + 'Trilinear' => 'Trilinear sensor', + 'Two-chip color area' => 'Two-chip color area sensor', + }, + }, + 'SerialNumber' => 'Kamera-ID', + 'ShadingCompensation' => 'Skuggkompensering', + 'Sharpness' => { + Description => 'Skärpa', + PrintConv => { + 'Hard' => 'HÃ¥rd', + 'Normal' => 'Standard', + 'Soft' => 'Mjuk', + }, + }, + 'ShootingMode' => 'Fotograferingsläge', + 'ShutterSpeed' => 'Exponeringstid', + 'ShutterSpeedValue' => 'Slutartid', + 'Software' => 'Programvara', + 'Source' => 'Källa', + 'SpatialFrequencyResponse' => 'FrekvensomfÃ¥ng', + 'SpectralSensitivity' => 'Spektral känslighet', + 'State' => 'Län', + 'StripByteCounts' => 'Byte komprimerade', + 'StripOffsets' => 'Plats för bilddata', + 'SubSecTime' => 'DateTime subseconds', + 'SubSecTimeDigitized' => 'DateTimeDigitized subseconds', + 'SubSecTimeOriginal' => 'DateTimeOriginal subseconds', + 'SubjectArea' => 'Motivyta', + 'SubjectDistance' => 'MotivavstÃ¥nd', + 'SubjectDistanceRange' => { + Description => 'MotivavstÃ¥nd', + PrintConv => { + 'Close' => 'Nära hÃ¥ll', + 'Distant' => 'LÃ¥ngt hÃ¥ll', + 'Macro' => 'Makro', + }, + }, + 'SubjectLocation' => 'Motivets placering', + 'SupplementalCategories' => 'Tilläggskategorier', + 'ThumbnailImage' => 'Miniatyr', + 'ThumbnailImageSize' => 'Miniatyrstorlek', + 'Title' => 'Titel', + 'TransferFunction' => 'Överföringsfunktion', + 'TransmissionReference' => 'Sändningsreferens', + 'Urgency' => 'Prioritet', + 'UserComment' => 'Användarkommentarer', + 'WhiteBalance' => { + Description => 'Vitbalans', + PrintConv => { + 'Auto' => 'Automatisk vitbalans', + 'Black & White' => 'Monokrom', + 'Cloudy' => 'Mulet', + 'Color Temperature/Color Filter' => 'Färgtemperatur / Färgfilter', + 'Cool White Fluorescent' => 'Kalljusrör', + 'Custom' => 'Special', + 'Custom 1' => 'ANPASSNING1', + 'Custom 2' => 'ANPASSNING2', + 'Custom 3' => 'ANPASSNING3', + 'Custom 4' => 'ANPASSNING4', + 'Day White Fluorescent' => '"Dagljusrör, högdager"', + 'Daylight' => 'Dagsljus', + 'Daylight Fluorescent' => 'Dagljusrör', + 'Flash' => 'Blixt', + 'Fluorescent' => 'Lysrörsbelysning', + 'Manual' => 'Manuell vitbalans', + 'Shade' => 'Skugga', + 'Tungsten' => 'Glödlampa', + 'Unknown' => 'Okänt', + 'Warm White Fluorescent' => 'Varmt vitt fluorescerande', + 'White Fluorescent' => 'Vit lysrörsbelysning', + }, + }, + 'WhitePoint' => 'Vitpunktskromaticitet', + 'Writer-Editor' => 'Bildtextredigerare', + 'YCbCrCoefficients' => 'Koefficienter för färgomrÃ¥desomvandling', + 'YCbCrPositioning' => { + Description => 'Y- och C-placering', + PrintConv => { + 'Centered' => 'Centrerad', + }, + }, + 'YCbCrSubSampling' => 'Subsampling ratio of Y to C', + 'ZoneMatching' => { + Description => 'Zonmatchning', + PrintConv => { + 'High Key' => 'Hög', + 'ISO Setting Used' => 'Av', + 'Low Key' => 'LÃ¥g', + }, + }, +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::sv.pm - ExifTool Swedish language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke and BjE<ouml>rn SE<ouml>derstrE<ouml>m for providing +this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/tr.pm b/ExifTool/lib/Image/ExifTool/Lang/tr.pm new file mode 100644 index 0000000..110465c --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/tr.pm @@ -0,0 +1,546 @@ +#------------------------------------------------------------------------------ +# File: tr.pm +# +# Description: ExifTool Turkish language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::tr; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.05'; + +%Image::ExifTool::Lang::tr::Translate = ( + 'Album' => 'Albüm', + 'Aperture' => 'Açıklık', + 'ApertureValue' => 'Açıklık', + 'Artist' => 'Sanatçı', + 'Author' => 'Yazar', + 'AuthorsPosition' => 'Yazarın Pozisyonu', + 'BitsPerSample' => 'Komponent başına bit sayısı', + 'Brightness' => 'Parlaklık', + 'By-line' => 'Yazar', + 'CFAPattern' => 'CFA deseni', + 'CalibrationIlluminant1' => { + PrintConv => { + 'Cloudy' => 'Bulutlu Hava', + 'Cool White Fluorescent' => 'SoÄŸuk beyaz floresan (W 3800 - 4500K)', + 'Day White Fluorescent' => 'Gün beyaz floresan (N 4600 - 5500K)', + 'Daylight' => 'Günışığı', + 'Daylight Fluorescent' => 'Günışığı floresan (D 5700 - 7100K)', + 'Fine Weather' => 'İyi hava', + 'Flash' => 'FlaÅŸ', + 'Fluorescent' => 'Floresan', + 'ISO Studio Tungsten' => 'ISO stüdyo tungsten', + 'Other' => 'DiÄŸer ışık kaynağı', + 'Shade' => 'Gölge', + 'Standard Light A' => 'Standard Işık A', + 'Standard Light B' => 'Standard Işık B', + 'Standard Light C' => 'Standard Işık C', + 'Tungsten (Incandescent)' => 'Tungsten', + 'Unknown' => 'Bilinmeyen', + 'White Fluorescent' => 'Beyaz floresan (WW 3250 - 3800K)', + }, + }, + 'CalibrationIlluminant2' => { + PrintConv => { + 'Cloudy' => 'Bulutlu Hava', + 'Cool White Fluorescent' => 'SoÄŸuk beyaz floresan (W 3800 - 4500K)', + 'Day White Fluorescent' => 'Gün beyaz floresan (N 4600 - 5500K)', + 'Daylight' => 'Günışığı', + 'Daylight Fluorescent' => 'Günışığı floresan (D 5700 - 7100K)', + 'Fine Weather' => 'İyi hava', + 'Flash' => 'FlaÅŸ', + 'Fluorescent' => 'Floresan', + 'ISO Studio Tungsten' => 'ISO stüdyo tungsten', + 'Other' => 'DiÄŸer ışık kaynağı', + 'Shade' => 'Gölge', + 'Standard Light A' => 'Standard Işık A', + 'Standard Light B' => 'Standard Işık B', + 'Standard Light C' => 'Standard Işık C', + 'Tungsten (Incandescent)' => 'Tungsten', + 'Unknown' => 'Bilinmeyen', + 'White Fluorescent' => 'Beyaz floresan (WW 3250 - 3800K)', + }, + }, + 'Caption-Abstract' => 'Açıklama', + 'CaptionWriter' => 'Açıklama Yazarı', + 'Categories' => 'Kategoriler', + 'Category' => 'Kategoriler', + 'City' => 'Åžehir', + 'ColorFilter' => 'Renk Filtresi', + 'ColorSpace' => { + Description => 'Renk alanı bilgisi', + PrintConv => { + 'Uncalibrated' => 'Kalibre edilmemiÅŸ', + }, + }, + 'ColorTemperature' => 'Renk Sıcaklığı', + 'Comment' => 'Yorum', + 'ComponentsConfiguration' => 'Her komponentin anlamı', + 'CompressedBitsPerPixel' => 'İmaj sıkıştıma modu', + 'Compression' => { + Description => 'Sıkıştırma planı', + PrintConv => { + 'JPEG' => 'JPEG Sıkıştırma', + 'Uncompressed' => 'Sıkıştırılmamış', + }, + }, + 'Contrast' => { + Description => 'Kontrast', + PrintConv => { + 'High' => 'Sert', + 'Low' => 'YumuÅŸak', + 'Normal' => 'Standard', + }, + }, + 'Copyright' => 'Telif hakkı sahibi', + 'CopyrightNotice' => 'Telif Hakkı Uyarısı', + 'Country' => 'Ülke', + 'Country-PrimaryLocationName' => 'Ülke', + 'CreateDate' => 'OluÅŸturma Tarihi', + 'CreationDate' => 'Yaratılış tarihi', + 'Credit' => 'Jenerik', + 'CustomRendered' => { + Description => 'Özel imaj iÅŸleme', + PrintConv => { + 'Custom' => 'Özel iÅŸlem', + 'Normal' => 'Normal iÅŸlem', + }, + }, + 'DateCreated' => 'OluÅŸturma Tarihi', + 'DateTimeOriginal' => 'Orjinal Tarih & Zaman', + 'DeviceSettingDescription' => 'Cihaz ayar tanımları', + 'DigitalZoom' => 'Dijital Zoom', + 'DigitalZoomRatio' => 'Dijital zoom oranı', + 'Directory' => 'Dosya Konumu', + 'DriveMode' => 'Sürüş Modu', + 'ExifImageHeight' => 'Geçerli imaj yüksekliÄŸi', + 'ExifImageWidth' => 'Geçerli imaj eni', + 'ExifOffset' => 'Exif IFD İmleci', + 'ExifVersion' => 'Exif sürüm', + 'ExposureCompensation' => 'Pozlama Sapması', + 'ExposureIndex' => 'Pozlama indeksi', + 'ExposureMode' => { + Description => 'Pozlama modu', + PrintConv => { + 'Auto' => 'Otomatik', + 'Auto bracket' => 'Otomatik çerçeve', + 'Manual' => 'Manuel pozlama', + }, + }, + 'ExposureProgram' => { + Description => 'Pozlama program', + PrintConv => { + 'Action (High speed)' => 'Hareket programı', + 'Aperture-priority AE' => 'Apertür önceliÄŸi', + 'Bulb' => 'Ampul', + 'Creative (Slow speed)' => 'Yaratıcı program', + 'Landscape' => 'Manzara', + 'Manual' => 'Manuel Pozlama', + 'Portrait' => 'Portre', + 'Program AE' => 'Normal program', + 'Shutter speed priority AE' => 'Deklanşör önceliÄŸi', + }, + }, + 'ExposureTime' => 'Poz süresi', + 'FNumber' => 'Açıklık', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => 'Pozitif yön', + 'Rotate 90 CW' => 'Saat yönünde 90° döndür', + }, + }, + 'FileFormat' => 'Format', + 'FileModifyDate' => 'Güncellenen Tarih', + 'FileName' => 'Dosya adı', + 'FileSize' => 'Dosya boyutu', + 'FileSource' => { + Description => 'Dosya kaynağı', + PrintConv => { + 'Digital Camera' => 'DSC', + 'Film Scanner' => 'Transparan tip tarayıcı', + 'Reflection Print Scanner' => 'Refleks tipi tarayıcı', + }, + }, + 'FileType' => 'Dosya türü', + 'Filename' => 'Dosya adı', + 'Flash' => { + Description => 'FlaÅŸ', + PrintConv => { + 'Auto, Fired' => 'AÇIK (Otom-flaÅŸ)', + 'Auto, Fired, Red-eye reduction' => '"AÇIK (Otom-flaÅŸ, Kırmızı-Göz Azaltma)"', + 'Auto, Fired, Red-eye reduction, Return detected' => '"AÇIK (Otom-flaÅŸ, Kırmızı-Göz Azaltma, Geri dönen ışık bulundu)"', + 'Auto, Fired, Return detected' => '"AÇIK (Otom-flaÅŸ, Geri dönen ışık bulundu)"', + 'Did not fire' => 'FlaÅŸ patlamadı', + 'Fired' => 'FlaÅŸ patladı', + 'Fired, Red-eye reduction' => 'AÇIK (Kırmızı-Göz Azaltma)', + 'Fired, Red-eye reduction, Return detected' => '"AÇIK (Kırmızı-Göz Azaltma, Geri dönen ışık bulundu)"', + 'Fired, Return detected' => 'AÇIK (Geri dönen ışık bulundu)', + 'No Flash' => 'FlaÅŸ fonksiyonu yok', + 'On, Fired' => 'AÇIK (Doldurma)', + 'On, Red-eye reduction' => '"AÇIK (Doldurma, Kırmızı-Göz Önleme)"', + 'On, Red-eye reduction, Return detected' => '"AÇIK (Doldurma, Kırmızı-Göz Önleme, Geri dönen ışık bulundu)"', + 'On, Return detected' => '"AÇIK (Doldurma, Geri dönen ışık bulundu)"', + }, + }, + 'FlashEnergy' => 'FlaÅŸ enerjisi', + 'FlashExposureComp' => 'FlaÅŸ Telafisi', + 'FlashpixVersion' => 'Desteklenen Flashpix sürümü', + 'FocalLength' => 'Odak uzunluÄŸu', + 'FocalLength35efl' => 'Odak Uzaklığı (35 mm dengi)', + 'FocalLengthIn35mmFormat' => '35mm filmde odak uzaklığı', + 'FocalPlaneResolutionUnit' => { + Description => 'Odak düzlemi çözünürlük birimi', + PrintConv => { + 'inches' => 'inç', + }, + }, + 'FocalPlaneXResolution' => 'Odak düzlemi X çözünürlüğü', + 'FocalPlaneYResolution' => 'Odak düzlemi Y çözünürlüğü', + 'FocusMode' => 'Odak modu', + 'FrameRate' => 'Kare Hızı', + 'FrameSize' => 'Kare Boyutu', + 'GPSAltitude' => 'Yükseklik', + 'GPSAltitudeRef' => { + Description => 'Yükselti referansı', + PrintConv => { + 'Above Sea Level' => 'Deniz düzeyi', + 'Below Sea Level' => 'Deniz düzeyi referansı (negatif deÄŸer)', + }, + }, + 'GPSAreaInformation' => 'GPS Alanının ismi', + 'GPSDOP' => 'Ölçüm hassaslığı', + 'GPSDateStamp' => 'GPS alanı', + 'GPSDestBearing' => 'Varış yönü', + 'GPSDestBearingRef' => 'Varış yönü için referans', + 'GPSDestDistance' => 'Varışa uzaklık', + 'GPSDestDistanceRef' => 'Varışa uzaklık için referans', + 'GPSDestLatitude' => 'Varışın enlemi', + 'GPSDestLatitudeRef' => 'Varışın enlemi için referans', + 'GPSDestLongitude' => 'Varışın boylamı', + 'GPSDestLongitudeRef' => 'Varışın boylamı için referans', + 'GPSDifferential' => { + Description => 'GPS diferansiyel düzeltme', + PrintConv => { + 'Differential Corrected' => 'Difrensiyel düzeltme uygulandı', + 'No Correction' => 'Difrensiyel düzeltme olmadan ölçüm', + }, + }, + 'GPSImgDirection' => 'İmajın yönü', + 'GPSImgDirectionRef' => 'İmajın yönü için referans', + 'GPSInfo' => 'GPS Info IDF İmleci', + 'GPSLatitude' => 'Enlem', + 'GPSLatitudeRef' => { + Description => 'Kuzey veya Güney Enlemi', + PrintConv => { + 'North' => 'Kuzey enlemi', + 'South' => 'Güney enlemi', + }, + }, + 'GPSLongitude' => 'Boylam', + 'GPSLongitudeRef' => { + Description => 'DoÄŸu veya Batı Boylamı', + PrintConv => { + 'East' => 'DoÄŸu boylamı', + 'West' => 'Batı boylamı', + }, + }, + 'GPSMapDatum' => 'Geodetik veri kullanıldı', + 'GPSMeasureMode' => { + Description => 'GPS ölçüm modu', + PrintConv => { + '3-Dimensional Measurement' => '3-boyutlu ölçüm', + }, + }, + 'GPSProcessingMethod' => 'GPS iÅŸlem metodunun ismi', + 'GPSSatellites' => 'Ölçüm için kullanılan GPS uyduları', + 'GPSSpeed' => 'GSP alıcının hızı', + 'GPSSpeedRef' => { + Description => 'Hız Ünitesi', + PrintConv => { + 'km/h' => 'km / saat', + 'knots' => 'Knot', + 'mph' => 'Mil / saat', + }, + }, + 'GPSStatus' => { + Description => 'GPS alıcı durumu', + PrintConv => { + 'Measurement Active' => 'İlerlemeli ölçüm', + 'Measurement Void' => 'Ölçü Birlikte İşlerliÄŸi', + }, + }, + 'GPSTimeStamp' => 'GPS saati (atomik saat)', + 'GPSTrack' => 'Hareket yönü', + 'GPSTrackRef' => { + Description => 'Hareket yönü için referans', + PrintConv => { + 'Magnetic North' => 'Manyetik yön', + 'True North' => 'Gerçek yön', + }, + }, + 'GPSVersionID' => 'GSP etiket sürümü', + 'GainControl' => { + Description => 'Kontrol Kazan', + PrintConv => { + 'High gain down' => 'Yüksek kazanç aÅŸağı', + 'High gain up' => 'Yüksek kazanç yukarı', + 'Low gain down' => 'Düşek kazanç aÅŸağı', + 'Low gain up' => 'Düşük kazanç yukarı', + 'None' => 'Hiçbiri', + }, + }, + 'Gradation' => 'Dereceleme', + 'Headline' => 'BaÅŸlık', + 'Hue' => 'Renk', + 'ICCProfile' => 'ICC Profili', + 'ISO' => 'ISO deÄŸeri', + 'ImageHeight' => 'İmaj yüksekliÄŸi', + 'ImageSize' => 'İmaj Boyutu', + 'ImageUniqueID' => 'Kendine has imaj ID', + 'ImageWidth' => 'İmaj geniÅŸliÄŸi', + 'Index' => 'İndeks', + 'Instructions' => 'Talimatlar', + 'InteropIndex' => 'Interoperabilite Tanımı', + 'InteropOffset' => 'Interoperabilite etiketi', + 'InteropVersion' => 'Interoperabilite Sürümü', + 'JPEGQuality' => { + PrintConv => { + 'Standard' => 'Standart Kalite', + }, + }, + 'Keywords' => 'Anahtar sözcükler', + 'Lens' => 'Objektif', + 'LensInfo' => 'Lens Bilgisi', + 'LightSource' => { + Description => 'Işık kaynağı', + PrintConv => { + 'Cloudy' => 'Bulutlu Hava', + 'Cool White Fluorescent' => 'SoÄŸuk beyaz floresan (W 3800 - 4500K)', + 'Day White Fluorescent' => 'Gün beyaz floresan (N 4600 - 5500K)', + 'Daylight' => 'Günışığı', + 'Daylight Fluorescent' => 'Günışığı floresan (D 5700 - 7100K)', + 'Fine Weather' => 'İyi hava', + 'Flash' => 'FlaÅŸ', + 'Fluorescent' => 'Floresan', + 'ISO Studio Tungsten' => 'ISO stüdyo tungsten', + 'Other' => 'DiÄŸer ışık kaynağı', + 'Shade' => 'Gölge', + 'Standard Light A' => 'Standard Işık A', + 'Standard Light B' => 'Standard Işık B', + 'Standard Light C' => 'Standard Işık C', + 'Tungsten (Incandescent)' => 'Tungsten', + 'Unknown' => 'Bilinmeyen', + 'White Fluorescent' => 'Beyaz floresan (WW 3250 - 3800K)', + }, + }, + 'Lightness' => 'Işık', + 'Location' => 'Mevki', + 'Make' => 'Üretici', + 'MakerNotes' => 'Üretici notları', + 'MaxAperture' => 'Maksimum lens açıklığı', + 'MeteringMode' => { + Description => 'Ölçü modu', + PrintConv => { + 'Average' => 'Ortalama', + 'Center-weighted average' => 'CenterWeightedAverage', + 'Multi-segment' => 'Desen', + 'Multi-spot' => 'Multispot', + 'Other' => 'DiÄŸer', + 'Partial' => 'Kısmen', + 'Unknown' => 'Bilinmeyen', + }, + }, + 'Model' => 'Kamera', + 'ModifyDate' => 'Dosya deÄŸiÅŸim tarih ve zamanı', + 'NoiseReduction' => 'Parazit azaltımı', + 'Opto-ElectricConvFactor' => 'Optoelektrik çevrim faktörü', + 'Orientation' => { + Description => 'Yönelim', + PrintConv => { + 'Horizontal (normal)' => 'Pozitif yön', + 'Rotate 90 CW' => 'Saat yönünde 90° döndür', + }, + }, + 'PhotometricInterpretation' => 'Piksel kompozisyonu', + 'PlanarConfiguration' => { + Description => 'İmaj veri aranjmanı', + PrintConv => { + 'Chunky' => 'Chunky format', + 'Planar' => 'Planar format', + }, + }, + 'PrimaryChromaticities' => 'İlklerin kromatikleri', + 'Province-State' => 'Eyalet/Bölge', + 'Quality' => { + PrintConv => { + 'Low' => 'Düşük Kalite', + 'Normal' => 'Standart Kalite', + }, + }, + 'RecordMode' => 'Kayıt modu', + 'ReferenceBlackWhite' => 'Bir çift siyah ve beyaz referans deÄŸerleri', + 'RelatedImageFileFormat' => 'İlgili Görüntü Dosya Formatı', + 'RelatedImageHeight' => 'İlgili İmaj YüksekliÄŸi', + 'RelatedImageWidth' => 'İlgili İmaj GeniÅŸliÄŸi', + 'RelatedSoundFile' => 'İlgili ses dosyası', + 'ResolutionUnit' => { + Description => 'X ve Y birim çözünürlüğü', + PrintConv => { + 'cm' => 'santimetre', + 'inches' => 'inç', + }, + }, + 'RowsPerStrip' => 'Åžerit başına satır sayısı', + 'SamplesPerPixel' => 'Komponent sayısı', + 'Saturation' => { + Description => 'Doyma', + PrintConv => { + 'High' => 'Yüksek Doyma', + 'Low' => 'Düşük Doyma', + 'Normal' => 'Standard', + }, + }, + 'SceneCaptureType' => { + Description => 'Senaryo yakalama tipi', + PrintConv => { + 'Landscape' => 'Manzara', + 'Night' => 'Gece çekimi', + 'Portrait' => 'Portre', + }, + }, + 'SceneMode' => { + PrintConv => { + 'Sunset' => 'Günbatımı', + }, + }, + 'SceneType' => { + Description => 'Senaryo tipi', + PrintConv => { + 'Directly photographed' => 'DoÄŸrudan fotograflanmış imaj', + }, + }, + 'SensingMethod' => { + Description => 'Alıcı metodu', + PrintConv => { + 'Color sequential area' => 'Renk ardaşık alan alıcısı', + 'Color sequential linear' => 'Renk ardışık çizgisel alıcı', + 'One-chip color area' => 'Tek-çip renk alanı alıcı', + 'Three-chip color area' => 'Üç-çip renk alanı alıcı', + 'Trilinear' => 'Triple çizgisel alıcı', + 'Two-chip color area' => 'İki-çip renk alanı alıcı', + }, + }, + 'SerialNumber' => 'Kamera ID', + 'ShadingCompensation' => 'Gölgelendirme Telafisi', + 'Sharpness' => { + Description => 'Keskinlik', + PrintConv => { + 'Hard' => 'Sert', + 'Normal' => 'Standard', + 'Soft' => 'YumuÅŸak', + }, + }, + 'ShootingMode' => 'Çekim Modu', + 'ShutterSpeed' => 'Poz süresi', + 'ShutterSpeedValue' => 'Deklanşör hızı', + 'Software' => 'Kullanılan yazılım', + 'Source' => 'Kaynak', + 'SpatialFrequencyResponse' => 'Uzaysal frekans cevabı', + 'SpectralSensitivity' => 'Uzaysal hassaslık', + 'State' => 'Eyalet', + 'StripByteCounts' => 'Sıkıştırılmış ÅŸerit başına bayt', + 'StripOffsets' => 'İmaj veri konumu', + 'SubSecTime' => 'TarihZaman altsaniyeler', + 'SubSecTimeDigitized' => 'TarihZamanDijitize altsaniyeler', + 'SubSecTimeOriginal' => 'TarihZamanOrjinal altsaniyeler', + 'SubjectArea' => 'Obje alanı', + 'SubjectDistance' => 'Obje uzaklığı', + 'SubjectDistanceRange' => { + Description => 'Obje uzaklık menzili', + PrintConv => { + 'Close' => 'Yakın görüntü', + 'Distant' => 'Uzak görüntü', + 'Macro' => 'Makro', + }, + }, + 'SubjectLocation' => 'Obje konumu', + 'SupplementalCategories' => 'Tamamlayıcı Kategoriler', + 'ThumbnailImage' => 'Küçük Resim', + 'ThumbnailImageSize' => 'Küçük resim boyutu', + 'Title' => 'BaÅŸlık', + 'TransferFunction' => 'Transfer fonksiyonu', + 'TransmissionReference' => 'İletim BaÅŸvurusu', + 'Urgency' => 'Acil', + 'UserComment' => 'Kullanıcı yorumları', + 'WhiteBalance' => { + Description => 'Beyaz ayarı', + PrintConv => { + 'Auto' => 'Otomatik beyaz ayarı', + 'Black & White' => 'Monokrom', + 'Cloudy' => 'Bulutlu hava', + 'Cool White Fluorescent' => 'SoÄŸuk beyaz floresan', + 'Custom 1' => 'ÖZEL1', + 'Custom 2' => 'ÖZEL2', + 'Custom 3' => 'ÖZEL3', + 'Custom 4' => 'ÖZEL4', + 'Day White Fluorescent' => 'Gün beyaz floresan', + 'Daylight' => 'Günışığı', + 'Daylight Fluorescent' => 'Günışığı floresan', + 'Fluorescent' => 'Floresan', + 'Manual' => 'Elle beyaz ayarı', + 'Shade' => 'Gölge', + 'Tungsten' => 'Tungsten ışık', + }, + }, + 'WhitePoint' => 'Beya nokta kromatik', + 'Writer-Editor' => 'Açıklama Yazarı', + 'XResolution' => 'X çözünürlüğü', + 'YCbCrCoefficients' => 'Renk alanı transformasyon matriks katsayısı', + 'YCbCrPositioning' => { + Description => 'Y ve C konumlama', + PrintConv => { + 'Centered' => 'Ortalanmış', + 'Co-sited' => 'Birlikte-konumlanmış', + }, + }, + 'YCbCrSubSampling' => 'Y den C\'ye alt örnekleme oranı', + 'YResolution' => 'Y çözünürlüğü', +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::tr.pm - ExifTool Turkish language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke, Hasan Yildirim and Cihan Ulusoy for providing this +translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/zh_cn.pm b/ExifTool/lib/Image/ExifTool/Lang/zh_cn.pm new file mode 100644 index 0000000..30cbd99 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/zh_cn.pm @@ -0,0 +1,1330 @@ +#------------------------------------------------------------------------------ +# File: zh_cn.pm +# +# Description: ExifTool Simplified Chinese language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::zh_cn; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.08'; + +%Image::ExifTool::Lang::zh_cn::Translate = ( + 'AEProgramMode' => { + PrintConv => { + 'Landscape' => '风景', + 'Portrait' => '人物', + }, + }, + 'AFAreaIllumination' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'AFPointIllumination' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'AFPointMode' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'AFPointRegistration' => { + PrintConv => { + 'Automatic' => '自动', + }, + }, + 'AFPointSelected' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'AFPointSelected2' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'AFPointsUnknown2' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'APEVersion' => 'APE 版本', + 'AdultContentWarning' => { + PrintConv => { + 'Unknown' => '未知', + }, + }, + 'Album' => '相册', + 'Anti-Blur' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'Aperture' => '光圈数', + 'ApertureValue' => '光圈', + 'Artist' => '图åƒä½œè€…', + 'Author' => '作者', + 'AuthorsPosition' => 'èŒä½', + 'AutoLightingOptimizer' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'AutoRotate' => { + PrintConv => { + 'Rotate 180' => '180° (底/å³)', + 'Rotate 270 CW' => '90° CW (å·¦/底)', + 'Rotate 90 CW' => '90° CCW (å³/上)', + 'n/a' => '未知', + }, + }, + 'BatteryLevel' => '电池电é‡', + 'BitsPerSample' => 'æ¯ä¸ªç»„件的比特数', + 'BracketShotNumber' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'Brightness' => '亮度', + 'BrightnessValue' => '亮度', + 'By-line' => '作者', + 'CFAPattern' => 'CFA 模å¼', + 'CalibrationIlluminant1' => { + PrintConv => { + 'Cloudy' => '阴天', + 'Cool White Fluorescent' => '冷白色è§å…‰ç¯ï¼ˆW3800-4500K)', + 'Day White Fluorescent' => '日光白色è§å…‰ç¯ï¼ˆN4600-5500K)', + 'Daylight' => '太阳光', + 'Daylight Fluorescent' => '日光色è§å…‰ç¯ï¼ˆD5700-7100K)', + 'Fine Weather' => '晴天', + 'Flash' => '闪光', + 'Fluorescent' => 'è§å…‰', + 'ISO Studio Tungsten' => 'ISO相室白炽ç¯', + 'Other' => 'å…¶ä»–å…‰æº', + 'Shade' => '阴影', + 'Standard Light A' => '标准光A', + 'Standard Light B' => '标准光B', + 'Standard Light C' => '标准光C', + 'Tungsten (Incandescent)' => '白炽ç¯', + 'Unknown' => '未知', + 'Warm White Fluorescent' => '暖白è§å…‰ç¯ï¼ˆL2600-3250K)', + 'White Fluorescent' => '白色è§å…‰ç¯ï¼ˆWW3250-3800K)', + }, + }, + 'CalibrationIlluminant2' => { + PrintConv => { + 'Cloudy' => '阴天', + 'Cool White Fluorescent' => '冷白色è§å…‰ç¯ï¼ˆW3800-4500K)', + 'Day White Fluorescent' => '日光白色è§å…‰ç¯ï¼ˆN4600-5500K)', + 'Daylight' => '太阳光', + 'Daylight Fluorescent' => '日光色è§å…‰ç¯ï¼ˆD5700-7100K)', + 'Fine Weather' => '晴天', + 'Flash' => '闪光', + 'Fluorescent' => 'è§å…‰', + 'ISO Studio Tungsten' => 'ISO相室白炽ç¯', + 'Other' => 'å…¶ä»–å…‰æº', + 'Shade' => '阴影', + 'Standard Light A' => '标准光A', + 'Standard Light B' => '标准光B', + 'Standard Light C' => '标准光C', + 'Tungsten (Incandescent)' => '白炽ç¯', + 'Unknown' => '未知', + 'Warm White Fluorescent' => '暖白è§å…‰ç¯ï¼ˆL2600-3250K)', + 'White Fluorescent' => '白色è§å…‰ç¯ï¼ˆWW3250-3800K)', + }, + }, + 'CameraOrientation' => { + Description => '图åƒå–å‘', + PrintConv => { + 'Horizontal (normal)' => '0° (上/å·¦)', + 'Rotate 270 CW' => '90° CW (å·¦/底)', + 'Rotate 90 CW' => '90° CCW (å³/上)', + }, + }, + 'CanonExposureMode' => { + PrintConv => { + 'Aperture-priority AE' => '光圈优先', + 'Manual' => '手动', + 'Shutter speed priority AE' => '快门优先', + }, + }, + 'CanonFlashMode' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'Caption-Abstract' => '说明', + 'CaptionWriter' => '说明作者', + 'CaptureXResolutionUnit' => { + PrintConv => { + 'um' => 'µm (微米)', + }, + }, + 'CaptureYResolutionUnit' => { + PrintConv => { + 'um' => 'µm (微米)', + }, + }, + 'Categories' => '类别', + 'Category' => '类别', + 'City' => '城市', + 'ColorEffect' => { + PrintConv => { + 'Sepia' => '怀旧深咖啡色', + }, + }, + 'ColorFilter' => '颜色滤镜', + 'ColorMode' => { + Description => '创æ„风格', + PrintConv => { + 'Autumn Leaves' => '红å¶', + 'B&W' => '黑白', + 'Clear' => '清澈', + 'Deep' => '深色', + 'Landscape' => '风景', + 'Light' => '轻淡', + 'Neutral' => '中性', + 'Night View' => '夜景', + 'Night View/Portrait' => '夜晚肖åƒ', + 'Portrait' => '人物', + 'Standard' => '标准', + 'Sunset' => '黄æ˜', + 'Vivid' => '生动色彩', + }, + }, + 'ColorSpace' => { + Description => '色彩空间信æ¯', + PrintConv => { + 'ICC Profile' => '色彩特性文件', + 'Uncalibrated' => '自å‘', + }, + }, + 'ColorTemperature' => '色温', + 'CommanderGroupAMode' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'CommanderGroupBMode' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'CommanderInternalFlash' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'Comment' => '注释', + 'ComponentsConfiguration' => 'å„分组的å«ä¹‰', + 'CompressedBitsPerPixel' => '图åƒåŽ‹ç¼©æ¨¡å¼', + 'Compression' => { + Description => '压缩方案', + PrintConv => { + 'JPEG' => 'JEPG压缩率', + 'JPEG (old-style)' => 'JPEG (æ—§æ ·å¼)', + 'Kodak DCR Compressed' => '柯达 DCR 压缩', + 'Kodak KDC Compressed' => '柯达 KDC 压缩', + 'Next' => 'NeXT 2比特编ç ', + 'Nikon NEF Compressed' => '尼康 NEF 压缩', + 'Pentax PEF Compressed' => '宾得 PEF 压缩', + 'SGILog' => 'SGI 32比特对数亮度编ç ', + 'SGILog24' => 'SGI 24比特对数亮度编ç ', + 'Sony ARW Compressed' => '索尼 ARW 压缩', + 'Thunderscan' => 'ThunderScan 4比特编ç ', + 'Uncompressed' => '未压缩', + }, + }, + 'Contrast' => { + Description => '对比度', + PrintConv => { + 'High' => '硬调', + 'Low' => '软调', + 'Normal' => '标准', + }, + }, + 'ControlMode' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'Copyright' => '专利拥有者', + 'CopyrightNotice' => '版æƒä¿¡æ¯', + 'CopyrightStatus' => { + PrintConv => { + 'Unknown' => '未知', + }, + }, + 'Country' => '国家', + 'Country-PrimaryLocationName' => '国家', + 'CreateDate' => 'æ•°å­—æ•°æ®äº§ç”Ÿçš„æ—¥æœŸå’Œæ—¶é—´', + 'Credit' => '作者', + 'CustomRendered' => { + Description => '用户自定义图åƒå¤„ç†', + PrintConv => { + 'Custom' => '自定义处ç†', + 'Normal' => '普通模å¼', + }, + }, + 'DateCreated' => '创建日期', + 'DateTime' => '文件改å˜çš„æ—¥æœŸå’Œæ—¶é—´', + 'DateTimeOriginal' => '原始数æ®äº§ç”Ÿçš„æ—¥æœŸå’Œæ—¶é—´', + 'DeviceSettingDescription' => '设备设定说明', + 'DigitalZoom' => 'æ•°ç å˜ç„¦', + 'DigitalZoomRatio' => 'æ•°ç å˜ç„¦æ¯”', + 'Directory' => '文件存储ä½ç½®', + 'DisplayXResolutionUnit' => { + PrintConv => { + 'um' => 'µm (微米)', + }, + }, + 'DisplayYResolutionUnit' => { + PrintConv => { + 'um' => 'µm (微米)', + }, + }, + 'DjVuVersion' => 'DjVu 版本', + 'DriveMode' => '驱动模å¼', + 'DynamicRangeOptimizer' => { + Description => '动æ€èŒƒå›´ä¼˜åŒ–', + PrintConv => { + 'Advanced Auto' => '高级自动', + 'Advanced Lv1' => '高级优化程度 1', + 'Advanced Lv2' => '高级优化程度 2', + 'Advanced Lv3' => '高级优化程度 3', + 'Advanced Lv4' => '高级优化程度 4', + 'Advanced Lv5' => '高级优化程度 5', + 'Auto' => '自动', + 'Off' => 'å…³', + 'Standard' => '标准', + }, + }, + 'EasyMode' => { + PrintConv => { + 'Landscape' => '风景', + 'Manual' => '手动', + 'Night' => '夜景', + 'Portrait' => '人物', + }, + }, + 'ExifImageHeight' => 'åƒé«˜', + 'ExifImageWidth' => 'åƒå®½', + 'ExifOffset' => 'Exif IFD 指针', + 'ExifToolVersion' => 'ExifTool 版本', + 'ExifVersion' => 'Exif 版本', + 'ExpandFilm' => '胶片扩展', + 'ExpandFilterLens' => '滤镜扩展', + 'ExpandFlashLamp' => 'é—ªå…‰ç¯æ‰©å±•', + 'ExpandLens' => '镜头扩展', + 'ExpandScanner' => '扫æä»ªæ‰©å±•', + 'ExpandSoftware' => '软件扩展', + 'ExposureCompensation' => 'æ›å…‰åå·®', + 'ExposureIndex' => 'æ›å…‰ç´¢å¼•', + 'ExposureMode' => { + Description => 'æ›å…‰æ¨¡å¼', + PrintConv => { + 'Aperture Priority' => '光圈优先', + 'Aperture-priority AE' => '光圈优先', + 'Auto' => '自动æ›å…‰', + 'Auto bracket' => '自动支架', + 'Landscape' => '风景', + 'Manual' => '手动æ›å…‰', + 'Portrait' => '人物', + 'Shutter Priority' => '快门优先', + 'Shutter speed priority AE' => '快门优先', + 'n/a' => '未设置', + }, + }, + 'ExposureProgram' => { + Description => 'æ›å…‰ç¨‹åº', + PrintConv => { + 'Action (High speed)' => 'è¿åŠ¨æ‘„å½±', + 'Aperture Priority' => '光圈优先', + 'Aperture-priority AE' => '光圈优先', + 'Creative (Slow speed)' => 'åˆ›æ„æ‘„å½±', + 'Landscape' => '风景', + 'Manual' => '手动', + 'Portrait' => '人物', + 'Program AE' => '一般程åº', + 'Shutter Priority' => '快门优先', + 'Shutter speed priority AE' => '快门优先', + }, + }, + 'ExposureTime' => 'æ›å…‰æ—¶é—´', + 'ExposureTime2' => 'æ›å…‰æ—¶é—´ 2', + 'ExternalFlashBounce' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'ExternalFlashExposureComp' => { + PrintConv => { + 'n/a' => '未设置 (Off or Auto Modes)', + 'n/a (Manual Mode)' => '未设置 (Manual Mode)', + }, + }, + 'FNumber' => '光圈数', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => '0° (上/å·¦)', + 'Rotate 180' => '180° (底/å³)', + 'Rotate 270 CW' => '90° CW (å·¦/底)', + 'Rotate 90 CW' => '90° CCW (å³/上)', + }, + }, + 'FaxProfile' => { + PrintConv => { + 'Unknown' => '未知', + }, + }, + 'FileFormat' => 'æ ¼å¼', + 'FileModifyDate' => '更新日期', + 'FileName' => '文件å', + 'FileSize' => '文件大å°', + 'FileSource' => { + Description => 'æ–‡ä»¶æ¥æº', + PrintConv => { + 'Digital Camera' => 'æ•°ç ç›¸æœº', + 'Film Scanner' => '胶片扫æä»ª', + 'Reflection Print Scanner' => 'å射型扫æä»ª', + }, + }, + 'FileType' => '文件格å¼', + 'FilterEffect' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'Flash' => { + Description => '闪光', + PrintConv => { + 'Auto, Fired' => 'å¼€ (自动闪光)', + 'Auto, Fired, Red-eye reduction' => 'å¼€ (自动闪光,å‡è½»çº¢çœ¼)', + 'Auto, Fired, Red-eye reduction, Return detected' => 'å¼€ (强制闪光,å‡è½»çº¢çœ¼ï¼Œæ£€æµ‹è¿”回光)', + 'Auto, Fired, Return detected' => 'å¼€ (自动闪光,检测返回光)', + 'Did not fire' => 'é—ªå…‰ç¯æœªäº®', + 'Fired' => '闪光ç¯äº®', + 'Fired, Red-eye reduction' => 'å¼€ (å‡è½»çº¢çœ¼)', + 'Fired, Red-eye reduction, Return detected' => 'å¼€ (å‡è½»çº¢çœ¼ï¼Œæ£€æµ‹è¿”回光)', + 'Fired, Return detected' => 'å¼€ (检测返回光)', + 'No Flash' => '无闪光功能', + 'On, Fired' => 'å¼€ (强制闪光)', + 'On, Red-eye reduction' => 'å¼€ (强制闪光(å‡è½»çº¢çœ¼))', + 'On, Red-eye reduction, Return detected' => 'å¼€ (强制闪光,å‡è½»çº¢çœ¼ï¼Œæ£€æµ‹è¿”回光)', + 'On, Return detected' => 'å¼€ (强制闪光,检测返回光)', + }, + }, + 'FlashControlMode' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'FlashEnergy' => '闪光强度', + 'FlashExposureComp' => '闪光补å¿', + 'FlashGroupAControlMode' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'FlashGroupBControlMode' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'FlashGroupCControlMode' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'FlashMode' => { + PrintConv => { + 'Auto' => '自动', + 'Unknown' => '未知', + }, + }, + 'FlashOptions' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'FlashOptions2' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'FlashSyncSpeedAv' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'FlashpixVersion' => '支æŒçš„ Flashpix 版本', + 'FocalLength' => '焦è·', + 'FocalLength35efl' => '焦点è·ç¦»(35 mm æ¢ç®—)', + 'FocalLengthIn35mmFormat' => '35 mm æ¢ç®—镜头焦点è·ç¦»', + 'FocalPlaneResolutionUnit' => { + Description => '焦平é¢åˆ†è¾¨çއå•ä½', + PrintConv => { + 'inches' => '英寸', + 'um' => 'µm (微米)', + }, + }, + 'FocalPlaneXResolution' => 'ç„¦å¹³é¢ X 分辨率', + 'FocalPlaneYResolution' => 'ç„¦å¹³é¢ Y 分辨率', + 'Focus' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'FocusContinuous' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'FocusMode' => { + Description => '对焦模å¼', + PrintConv => { + 'Auto' => '自动', + 'Manual' => '手动', + }, + }, + 'FocusMode2' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'FocusModeSetting' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'FocusRange' => { + PrintConv => { + 'Auto' => '自动', + 'Manual' => '手动', + }, + }, + 'FrameRate' => '更新速率', + 'FrameSize' => 'ç”»é¢å°ºå¯¸', + 'FreeByteCounts' => '空白字节数', + 'FujiFlashMode' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'GIFVersion' => 'GIF 版本', + 'GPSAltitude' => '海拔高度', + 'GPSAltitudeRef' => { + Description => '高度基准', + PrintConv => { + 'Above Sea Level' => 'æµ·å¹³é¢', + 'Below Sea Level' => 'æµ·æ‹”(负值)', + }, + }, + 'GPSAreaInformation' => 'GPS区域å', + 'GPSDOP' => '测é‡ç²¾åº¦', + 'GPSDateStamp' => 'GPS日期', + 'GPSDestBearing' => '目的地方ä½', + 'GPSDestBearingRef' => '目的地方ä½å‚ç…§', + 'GPSDestDistance' => '离终点的è·ç¦»', + 'GPSDestDistanceRef' => { + Description => 'è·ç›®çš„地è·ç¦»å‚ç…§', + PrintConv => { + 'Kilometers' => '公里数', + 'Miles' => '英里数', + 'Nautical Miles' => '节数', + }, + }, + 'GPSDestLatitude' => '终点纬度', + 'GPSDestLatitudeRef' => { + Description => '目的地的北纬或å—纬', + PrintConv => { + 'North' => '北纬', + 'South' => 'å—纬', + }, + }, + 'GPSDestLongitude' => '终点ç»åº¦', + 'GPSDestLongitudeRef' => { + Description => 'ç›®çš„åœ°çš„ä¸œç»æˆ–西ç»', + PrintConv => { + 'East' => '东ç»', + 'West' => '西ç»', + }, + }, + 'GPSDifferential' => { + Description => 'GPS 差分校正', + PrintConv => { + 'Differential Corrected' => '差分校正定ä½', + 'No Correction' => 'éžå·®åˆ†æ ¡æ­£æµ‹é‡', + }, + }, + 'GPSImgDirection' => 'å›¾åƒæ–¹å‘', + 'GPSImgDirectionRef' => 'å›¾åƒæ–¹å‘å‚ç…§', + 'GPSInfo' => 'GPS Info IFD 指针', + 'GPSLatitude' => '纬度', + 'GPSLatitudeRef' => { + Description => '北纬或者å—纬', + PrintConv => { + 'North' => '北纬', + 'South' => 'å—纬', + }, + }, + 'GPSLongitude' => 'ç»åº¦', + 'GPSLongitudeRef' => { + Description => 'ä¸œç»æˆ–者西ç»', + PrintConv => { + 'East' => '东ç»', + 'West' => '西ç»', + }, + }, + 'GPSMapDatum' => 'ä½¿ç”¨çš„å¤§åœ°æµ‹é‡æ•°æ®', + 'GPSMeasureMode' => { + Description => 'GPSæµ‹é‡æ¨¡å¼', + PrintConv => { + '2-D' => '2维测é‡', + '2-Dimensional' => '2维测é‡', + '2-Dimensional Measurement' => '2维测é‡', + '3-D' => '3维测é‡', + '3-Dimensional' => '3维测é‡', + '3-Dimensional Measurement' => '3维测é‡', + }, + }, + 'GPSProcessingMethod' => 'å®šä½æ–¹å¼åç§°', + 'GPSSatellites' => '用于定ä½çš„ GPS 嫿˜Ÿä¿¡å·', + 'GPSSpeed' => 'GPS接收器的速度', + 'GPSSpeedRef' => { + Description => '速度å•ä½', + PrintConv => { + 'km/h' => '公里/å°æ—¶', + 'knots' => '海里/å°æ—¶', + 'mph' => '英里/å°æ—¶', + }, + }, + 'GPSStatus' => { + Description => 'GPS 接收器状æ€', + PrintConv => { + 'Measurement Active' => '正在测é‡ä¸­', + 'Measurement Void' => '测é‡ä¸­æ–­', + }, + }, + 'GPSTimeStamp' => 'GPS æ—¶é—´(原å­é’Ÿ)', + 'GPSTrack' => 'è¿åŠ¨æ–¹å‘', + 'GPSTrackRef' => { + Description => 'è¿åŠ¨æ–¹å‘å‚ç…§', + PrintConv => { + 'Magnetic North' => 'ç£åœºæ–¹å‘', + 'True North' => '真方å‘', + }, + }, + 'GPSVersionID' => 'GPS标签版本', + 'GainControl' => { + Description => '增益控制', + PrintConv => { + 'High gain down' => '强å‡å°‘', + 'High gain up' => '强增益', + 'Low gain down' => 'å¼±å‡å°‘', + 'Low gain up' => '弱增益', + 'None' => 'æ— ', + }, + }, + 'Gamma' => '对比系数', + 'Gradation' => 'ç°é˜¶', + 'HDR' => { + Description => '自动HDR', + PrintConv => { + 'Off' => 'å…³', + }, + }, + 'Headline' => '标题', + 'HighISONoiseReduction' => { + Description => '高ISOé™å™ª', + PrintConv => { + 'Auto' => '自动', + 'High' => '强', + 'Low' => 'å¼±', + 'Normal' => '标准', + 'Off' => 'å…³', + }, + }, + 'Hue' => '色相', + 'ICCProfile' => 'ICC 规范', + 'IPTC-NAA' => 'IPTC-NAA 元数æ®', + 'ISOSetting' => { + PrintConv => { + 'Auto' => '自动', + 'Manual' => '手动', + }, + }, + 'ImageDescription' => 'å›¾åƒæ ‡é¢˜', + 'ImageHeight' => 'åƒé«˜', + 'ImageOrientation' => { + PrintConv => { + 'Portrait' => '人物', + }, + }, + 'ImageSize' => '图åƒå°ºå¯¸', + 'ImageTone' => { + PrintConv => { + 'Landscape' => '风景', + 'Portrait' => '人物', + }, + }, + 'ImageUniqueID' => '图åƒå”¯ä¸€æ ‡è¯†', + 'ImageWidth' => 'åƒå®½', + 'Index' => '索引', + 'Instructions' => '指示', + 'InternalFlash' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'InteropIndex' => { + Description => '互用标识', + PrintConv => { + 'THM - DCF thumbnail file' => 'THM: DCF 缩略图文件', + }, + }, + 'InteropOffset' => '互用指针', + 'InteropVersion' => '互用版本', + 'JFIFVersion' => 'JFIF 版本', + 'JPEGQuality' => { + Description => '图åƒè´¨é‡', + PrintConv => { + 'Extra Fine' => '超精细', + 'Fine' => '精细', + 'Standard' => '标准画质', + 'n/a' => '未设置', + }, + }, + 'Keyword' => '关键è¯', + 'Keywords' => '关键字', + 'Lens' => '镜头', + 'LensInfo' => '镜头信æ¯', + 'LensType' => '未设置', + 'LicenseType' => { + PrintConv => { + 'Unknown' => '未知', + }, + }, + 'LightSource' => { + Description => 'å…‰æº', + PrintConv => { + 'Cloudy' => '阴天', + 'Cool White Fluorescent' => '冷白色è§å…‰ç¯ï¼ˆW3800-4500K)', + 'Day White Fluorescent' => '日光白色è§å…‰ç¯ï¼ˆN4600-5500K)', + 'Daylight' => '太阳光', + 'Daylight Fluorescent' => '日光色è§å…‰ç¯ï¼ˆD5700-7100K)', + 'Fine Weather' => '晴天', + 'Flash' => '闪光', + 'Fluorescent' => 'è§å…‰', + 'ISO Studio Tungsten' => 'ISO相室白炽ç¯', + 'Other' => 'å…¶ä»–å…‰æº', + 'Shade' => '阴影', + 'Standard Light A' => '标准光A', + 'Standard Light B' => '标准光B', + 'Standard Light C' => '标准光C', + 'Tungsten (Incandescent)' => '白炽ç¯', + 'Unknown' => '未知', + 'Warm White Fluorescent' => '暖白è§å…‰ç¯ï¼ˆL2600-3250K)', + 'White Fluorescent' => '白色è§å…‰ç¯ï¼ˆWW3250-3800K)', + }, + }, + 'Lightness' => '明暗度', + 'LongExposureNoiseReduction' => { + Description => 'é•¿æ—¶é—´æ›å…‰é™å™ª', + PrintConv => { + 'Auto' => '自动', + 'Off' => 'å…³', + 'On' => 'å¼€', + 'n/a' => '未设置', + }, + }, + 'MIEVersion' => 'MIE 版本', + 'Macro' => { + PrintConv => { + 'Manual' => '手动', + 'n/a' => '未设置', + }, + }, + 'Make' => '厂商', + 'MakerNote' => '厂商注释', + 'MakerNotes' => '制造商记录', + 'ManualFlashOutput' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'MaxAperture' => '镜头光圈最大值', + 'MaxApertureValue' => '最大镜头光圈', + 'MeteringMode' => { + Description => 'æµ‹é‡æ–¹å¼', + PrintConv => { + 'Average' => 'å¹³å‡', + 'Center-weighted average' => '中央é‡ç‚¹', + 'Multi-segment' => '分割测光', + 'Multi-spot' => '多点', + 'Other' => '其它', + 'Partial' => '部分测光', + 'Spot' => '点测光', + 'Unknown' => '未知', + }, + }, + 'Model' => 'åž‹å·', + 'ModifiedPictureStyle' => { + PrintConv => { + 'Landscape' => '风景', + 'Portrait' => '人物', + }, + }, + 'ModifiedSharpnessFreq' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'ModifiedToneCurve' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'ModifiedWhiteBalance' => { + PrintConv => { + 'Auto' => '自动', + 'Cloudy' => '阴天', + 'Daylight' => '太阳光', + 'Flash' => '闪光', + 'Fluorescent' => 'è§å…‰', + 'Shade' => '阴影', + }, + }, + 'ModifyDate' => '文件改å˜çš„æ—¥æœŸå’Œæ—¶é—´', + 'MultiFrameNoiseReduction' => { + Description => '多帧é™å™ª', + PrintConv => { + 'Off' => 'å…³', + 'On' => 'å¼€', + }, + }, + 'NEFCompression' => { + PrintConv => { + 'Uncompressed' => '未压缩', + }, + }, + 'Noise' => '噪声', + 'NoiseReduction' => { + Description => 'å‡å°‘噪声', + PrintConv => { + 'Auto' => '自动', + }, + }, + 'ObjectFileType' => { + PrintConv => { + 'Unknown' => '未知', + }, + }, + 'Opto-ElectricConvFactor' => '光电转æ¢å› å­', + 'Orientation' => { + Description => '图åƒå–å‘', + PrintConv => { + 'Horizontal (normal)' => '0° (上/å·¦)', + 'Mirror horizontal' => '0° (上/å³)', + 'Mirror horizontal and rotate 270 CW' => '90° CW (å·¦/上)', + 'Mirror horizontal and rotate 90 CW' => '90° CCW (å³/底)', + 'Mirror vertical' => '180° (底/å·¦)', + 'Rotate 180' => '180° (底/å³)', + 'Rotate 270 CW' => '90° CW (å·¦/底)', + 'Rotate 90 CW' => '90° CCW (å³/上)', + }, + }, + 'PEFVersion' => 'PEF 版本', + 'PageNumber' => '页数', + 'PhotometricInterpretation' => { + Description => 'åƒç´ æ–¹æ¡ˆ', + PrintConv => { + 'BlackIsZero' => '零黑色', + 'Color Filter Array' => 'CFA (颜色滤镜矩阵)', + 'Pixar LogL' => 'CIE Log2(L) (对数亮度)', + 'Pixar LogLuv' => 'CIE Log2(L)(u\',v\') (对数亮度和色度)', + 'RGB Palette' => '调色æ¿é¢œè‰²', + 'Transparency Mask' => '逿˜Žè’™æ¿', + 'WhiteIsZero' => '零白色', + }, + }, + 'PictureFinish' => { + PrintConv => { + 'Portrait' => '人物', + }, + }, + 'PictureMode' => { + PrintConv => { + 'Aperture-priority AE' => '光圈优先', + 'Auto' => '自动', + 'Landscape' => '风景', + 'Manual' => '手动', + 'Portrait' => '人物', + 'Shutter speed priority AE' => '快门优先', + }, + }, + 'PictureMode2' => { + PrintConv => { + 'Aperture Priority' => '光圈优先', + 'Manual' => '手动', + 'Shutter Speed Priority' => '快门优先', + }, + }, + 'PictureModeBWFilter' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'PictureModeTone' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'PictureStyle' => { + PrintConv => { + 'Landscape' => '风景', + 'Portrait' => '人物', + }, + }, + 'PixelUnits' => { + PrintConv => { + 'Unknown' => '未知', + }, + }, + 'PlanarConfiguration' => { + Description => 'å›¾åƒæ•°æ®æŽ’列', + PrintConv => { + 'Chunky' => 'ç‚¹é¡ºåºæ ¼å¼', + 'Planar' => '平颿 ¼å¼', + }, + }, + 'PreviewColorSpace' => { + PrintConv => { + 'Unknown' => '未知', + }, + }, + 'PrimaryChromaticities' => 'åŽŸè‰²è‰²åº¦åæ ‡å€¼', + 'ProgramMode' => { + PrintConv => { + 'Portrait' => '人物', + }, + }, + 'Province-State' => 'å·žï¼çœ', + 'Quality' => { + Description => '图åƒè´¨é‡', + PrintConv => { + 'Compressed RAW' => 'cRAW', + 'Compressed RAW + JPEG' => 'cRAW+JPEG', + 'Extra Fine' => '超精细', + 'Fine' => '精细', + 'Low' => '低画质', + 'Normal' => '标准画质', + 'RAW + JPEG' => 'RAW+JPEG', + 'Standard' => '标准', + 'n/a' => '未设置', + }, + }, + 'RAFVersion' => 'RAF 版本', + 'Rating' => '评分', + 'RatingPercent' => '百分比评级', + 'RecordMode' => { + Description => '记录模å¼', + PrintConv => { + 'Aperture Priority' => '光圈优先', + 'Manual' => '手动', + 'Shutter Priority' => '快门优先', + }, + }, + 'RecordingMode' => { + PrintConv => { + 'Auto' => '自动', + 'Landscape' => '风景', + 'Manual' => '手动', + 'Portrait' => '人物', + }, + }, + 'RedEyeCorrection' => { + PrintConv => { + 'Automatic' => '自动', + }, + }, + 'ReferenceBlackWhite' => '黑白å‚照值对', + 'RelatedImageFileFormat' => 'ç›¸å…³å›¾åƒæ–‡ä»¶æ ¼å¼', + 'RelatedImageHeight' => '相关图åƒé«˜åº¦', + 'RelatedImageWidth' => '相关图åƒå®½åº¦', + 'RelatedSoundFile' => '相关的音频文件', + 'ResolutionUnit' => { + Description => '图åƒé«˜å®½åˆ†è¾¨çއå•ä½', + PrintConv => { + 'cm' => 'åƒç´ /厘米', + 'inches' => '英寸', + }, + }, + 'Rotation' => { + PrintConv => { + 'Horizontal' => '0° (上/å·¦)', + 'Horizontal (Normal)' => '0° (上/å·¦)', + 'Horizontal (normal)' => '0° (上/å·¦)', + 'Rotate 180' => '180° (底/å³)', + 'Rotate 270 CW' => '90° CW (å·¦/底)', + 'Rotate 90 CW' => '90° CCW (å³/上)', + 'Rotated 180' => '180° (底/å³)', + 'Rotated 270 CW' => '90° CW (å·¦/底)', + 'Rotated 90 CW' => '90° CCW (å³/上)', + }, + }, + 'RowsPerStrip' => 'æ¯æ¡å¸¦çš„行数', + 'SPIFFVersion' => 'SPIFF 版本', + 'SRAWQuality' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'SVGVersion' => 'SVG 版本', + 'SamplesPerPixel' => '组件数', + 'Saturation' => { + Description => '饱和度', + PrintConv => { + 'High' => '高饱和度', + 'Low' => '低饱和度', + 'None' => '未设置', + 'Normal' => '标准', + }, + }, + 'SceneCaptureType' => { + Description => 'å–æ™¯ç±»åž‹', + PrintConv => { + 'Landscape' => '风景', + 'Night' => '夜景', + 'Portrait' => '人物', + 'Standard' => '标准', + }, + }, + 'SceneMode' => { + Description => '场景选择', + PrintConv => { + '3D Sweep Panorama' => '3D', + 'Anti Motion Blur' => '动作防抖', + 'Aperture Priority' => '光圈优先', + 'Auto' => '自动', + 'Cont. Priority AE' => 'è¿žç»­æ‹æ‘„优先AE', + 'Handheld Night Shot' => '手æŒå¤œæ™¯æ‹æ‘„', + 'Landscape' => '风景', + 'Macro' => 'å¾®è·', + 'Manual' => '手动', + 'Night Portrait' => '夜晚肖åƒ', + 'Night Scene' => '夜景', + 'Night View/Portrait' => '夜景/è‚–åƒ', + 'Portrait' => '人物', + 'Shutter Priority' => '快门优先', + 'Sports' => 'è¿åŠ¨æ¨¡å¼', + 'Sunset' => '夕阳', + 'Sweep Panorama' => '扫æå…¨æ™¯', + }, + }, + 'SceneModeUsed' => { + PrintConv => { + 'Aperture Priority' => '光圈优先', + 'Landscape' => '风景', + 'Manual' => '手动', + 'Portrait' => '人物', + 'Shutter Priority' => '快门优先', + }, + }, + 'SceneSelect' => { + PrintConv => { + 'Night' => '夜景', + }, + }, + 'SceneType' => { + Description => '场景类型', + PrintConv => { + 'Directly photographed' => 'ç›´æŽ¥æ‹æ‘„的图åƒ', + }, + }, + 'SensingMethod' => { + Description => '感应方法', + PrintConv => { + 'Color sequential area' => '色彩连续区感应器', + 'Color sequential linear' => '色彩连续线性感应器', + 'One-chip color area' => 'å•片色彩区感应器', + 'Three-chip color area' => '3 片色彩区传感器', + 'Trilinear' => '3 线传感器', + 'Two-chip color area' => '2 片色彩区传感器', + }, + }, + 'SerialNumber' => '照相机标识', + 'ShadingCompensation' => '阴影补å¿', + 'Sharpness' => { + Description => 'é”度', + PrintConv => { + 'Hard' => '强', + 'Normal' => '标准', + 'Soft' => 'å¼±', + 'n/a' => '未设置', + }, + }, + 'SharpnessFrequency' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'ShootingInfoDisplay' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'ShootingMode' => { + Description => 'æ‹æ‘„模å¼', + PrintConv => { + 'Aperture Priority' => '光圈优先', + 'Manual' => '手动', + 'Portrait' => '人物', + 'Shutter Priority' => '快门优先', + }, + }, + 'ShutterMode' => { + PrintConv => { + 'Aperture Priority' => '光圈优先', + 'Auto' => '自动', + }, + }, + 'ShutterSpeed' => 'æ›å…‰æ—¶é—´', + 'ShutterSpeedValue' => '快门速度', + 'Software' => '使用软件', + 'Source' => 'æ¥æº', + 'SpatialFrequencyResponse' => '空间频率å“应', + 'SpectralSensitivity' => 'å…‰è°±çµæ•度', + 'State' => 'å·ž', + 'StripByteCounts' => 'æ¯åŽ‹ç¼©æ¡å¸¦çš„字节数', + 'StripOffsets' => 'å›¾åƒæ•°æ®ä½ç½®', + 'SubSecCreateDate' => 'æ•°å­—æ•°æ®äº§ç”Ÿçš„æ—¥æœŸå’Œæ—¶é—´', + 'SubSecDateTimeOriginal' => '原始数æ®äº§ç”Ÿçš„æ—¥æœŸå’Œæ—¶é—´', + 'SubSecModifyDate' => '文件改å˜çš„æ—¥æœŸå’Œæ—¶é—´', + 'SubSecTime' => '时间的次秒', + 'SubSecTimeDigitized' => '数字化时间的次秒', + 'SubSecTimeOriginal' => '原始时间的次秒', + 'Subject' => '主题', + 'SubjectArea' => '被摄对象区域', + 'SubjectDistance' => '目标è·ç¦»', + 'SubjectDistanceRange' => { + Description => '被摄对象è·ç¦»èŒƒå›´', + PrintConv => { + 'Close' => '近景', + 'Distant' => '远景', + 'Macro' => 'å¾®è·', + 'Unknown' => '未知', + }, + }, + 'SubjectLocation' => '被摄对象ä½ç½®', + 'SubjectProgram' => { + PrintConv => { + 'Portrait' => '人物', + }, + }, + 'Subsystem' => { + PrintConv => { + 'Unknown' => '未知', + }, + }, + 'SupplementalCategories' => '追加类别', + 'SupplementalType' => { + PrintConv => { + 'Main Image' => '未设置', + }, + }, + 'T4Options' => '未压缩', + 'T6Options' => 'T6 选项', + 'ThumbnailImage' => '缩略图', + 'ThumbnailImageSize' => '缩图尺寸', + 'Title' => '标题', + 'ToneCurve' => { + PrintConv => { + 'Manual' => '手动', + }, + }, + 'ToningEffect' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'TransferFunction' => 'ä¼ é€åŠŸèƒ½', + 'Transformation' => { + PrintConv => { + 'Horizontal (normal)' => '0° (上/å·¦)', + 'Mirror horizontal' => '0° (上/å³)', + 'Mirror horizontal and rotate 270 CW' => '90° CW (å·¦/上)', + 'Mirror horizontal and rotate 90 CW' => '90° CCW (å³/底)', + 'Mirror vertical' => '180° (底/å·¦)', + 'Rotate 180' => '180° (底/å³)', + 'Rotate 270 CW' => '90° CW (å·¦/底)', + 'Rotate 90 CW' => '90° CCW (å³/上)', + }, + }, + 'TransmissionReference' => '传输记录', + 'Trapped' => { + PrintConv => { + 'Unknown' => '未知', + }, + }, + 'Urgency' => '紧急度', + 'UserComment' => '用户注释', + 'UserDef1PictureStyle' => { + PrintConv => { + 'Landscape' => '风景', + 'Portrait' => '人物', + }, + }, + 'UserDef2PictureStyle' => { + PrintConv => { + 'Landscape' => '风景', + 'Portrait' => '人物', + }, + }, + 'UserDef3PictureStyle' => { + PrintConv => { + 'Landscape' => '风景', + 'Portrait' => '人物', + }, + }, + 'VRDVersion' => 'VRD 版本', + 'Version' => '版本', + 'VibrationReduction' => { + PrintConv => { + 'n/a' => '未设置', + }, + }, + 'WBAdjLighting' => { + PrintConv => { + 'Daylight (direct sunlight)' => '太阳光 (0)', + 'Daylight (shade)' => '太阳光 (1)', + 'Daylight (cloudy)' => '太阳光 (2)', + 'Flash' => '闪光', + }, + }, + 'WBMode' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'WhiteBalance' => { + Description => '白平衡', + PrintConv => { + 'Auto' => '自动', + 'Black & White' => '黑白', + 'Cloudy' => '阴天', + 'Color Temperature/Color Filter' => '色温 / 彩色滤光片', + 'Cool White Fluorescent' => '白色è§å…‰ç¯', + 'Custom' => '自定义', + 'Custom 1' => '自定义1', + 'Custom 2' => '自定义2', + 'Custom 3' => '自定义3', + 'Custom 4' => '自定义4', + 'Day White Fluorescent' => '日光白色è§å…‰ç¯', + 'Daylight' => '太阳光', + 'Daylight Fluorescent' => '日光色è§å…‰ç¯', + 'Flash' => '闪光', + 'Fluorescent' => 'è§å…‰', + 'Manual' => '手动', + 'Shade' => '阴影', + 'Tungsten' => '白炽ç¯', + 'Unknown' => '未知', + 'Warm White Fluorescent' => '暖白è§å…‰ç¯', + 'White Fluorescent' => '白色è§å…‰ç¯', + }, + }, + 'WhiteBalance2' => { + PrintConv => { + 'Auto' => '自动', + }, + }, + 'WhiteBalanceAdj' => { + PrintConv => { + 'Auto' => '自动', + 'Cloudy' => '阴天', + 'Daylight' => '太阳光', + 'Flash' => '闪光', + 'Fluorescent' => 'è§å…‰', + 'Shade' => '阴影', + }, + }, + 'WhiteBalanceMode' => { + PrintConv => { + 'Unknown' => '未知', + }, + }, + 'WhiteBalanceSet' => { + PrintConv => { + 'Auto' => '自动', + 'Cloudy' => '阴天', + 'Daylight' => '太阳光', + 'Flash' => '闪光', + 'Manual' => '手动', + 'Shade' => '阴影', + }, + }, + 'WhitePoint' => '白点色度', + 'Writer-Editor' => '说明作者', + 'XMP' => 'XMP 元数æ®', + 'XPAuthor' => '作者', + 'XPComment' => '注释', + 'XPKeywords' => '关键è¯', + 'XPSubject' => '主题', + 'XPTitle' => '标题', + 'YCbCrCoefficients' => 'è‰²å½©ç©ºé—´å˜æ¢çŸ©é˜µç³»æ•°', + 'YCbCrPositioning' => { + Description => 'YCC åƒç´ ç»“æž„(Y å’Œ C çš„ä½ç½®)', + PrintConv => { + 'Centered' => '中心的', + 'Co-sited' => '一致', + }, + }, + 'YCbCrSubSampling' => 'YCC åƒç´ ç»“æž„(Y 至 C çš„å­é‡‡æ ·çއ)', + 'ZoneMatching' => { + Description => '区域匹é…', + PrintConv => { + 'High Key' => '强', + 'ISO Setting Used' => 'å…³', + 'Low Key' => 'å¼±', + }, + }, +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::zh_cn.pm - ExifTool Simplified Chinese language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke and Haibing Zhong for providing this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lang/zh_tw.pm b/ExifTool/lib/Image/ExifTool/Lang/zh_tw.pm new file mode 100644 index 0000000..82c31b9 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lang/zh_tw.pm @@ -0,0 +1,801 @@ +#------------------------------------------------------------------------------ +# File: zh_tw.pm +# +# Description: ExifTool Traditional Chinese language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::zh_tw; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.06'; + +%Image::ExifTool::Lang::zh_tw::Translate = ( + 'Album' => '相簿', + 'Aperture' => '光圈', + 'ApertureValue' => '光圈', + 'Artist' => 'å½±åƒå»ºç«‹è€…', + 'Author' => '作者', + 'AuthorsPosition' => 'è·ä½', + 'BatteryLevel' => '電池容é‡', + 'BitsPerSample' => 'æ¯å€‹å…ƒä»¶ bits 組æˆçš„æ•¸é‡', + 'Brightness' => '亮度', + 'BrightnessValue' => '亮度', + 'By-line' => '作者', + 'CFAPattern' => '彩色濾é¡é™£åˆ—圖', + 'CFAPattern2' => 'CFA æ¨¡å¼ 2', + 'CFARepeatPatternDim' => 'CFA é‡è¤‡æ¨¡å¼å°ºå¯¸', + 'CalibrationIlluminant1' => { + PrintConv => { + 'Cloudy' => '多雲', + 'Cool White Fluorescent' => '冷白色熒光燈 (W 3800 - 4500K)', + 'Day White Fluorescent' => '日光白色熒光燈 (N 4600 - 5500K)', + 'Daylight' => '日光', + 'Daylight Fluorescent' => '日光熒光燈 (D 5700 - 7100K)', + 'Fine Weather' => '晴天', + 'Flash' => '閃光燈', + 'Fluorescent' => '日光燈', + 'ISO Studio Tungsten' => 'ISO æ”影棚鎢燈', + 'Other' => 'å…¶ä»–å…‰æº', + 'Shade' => '陰天', + 'Standard Light A' => '標準燈光 A', + 'Standard Light B' => '標準燈光 B', + 'Standard Light C' => '標準燈光 C', + 'Tungsten (Incandescent)' => '鎢絲燈', + 'Unknown' => '未知', + 'Warm White Fluorescent' => '暖白螢光燈 (L 2600 - 3250K)', + 'White Fluorescent' => '白色熒光燈 (WW 3250 - 3800K)', + }, + }, + 'CalibrationIlluminant2' => { + PrintConv => { + 'Cloudy' => '多雲', + 'Cool White Fluorescent' => '冷白色熒光燈 (W 3800 - 4500K)', + 'Day White Fluorescent' => '日光白色熒光燈 (N 4600 - 5500K)', + 'Daylight' => '日光', + 'Daylight Fluorescent' => '日光熒光燈 (D 5700 - 7100K)', + 'Fine Weather' => '晴天', + 'Flash' => '閃光燈', + 'Fluorescent' => '日光燈', + 'ISO Studio Tungsten' => 'ISO æ”影棚鎢燈', + 'Other' => 'å…¶ä»–å…‰æº', + 'Shade' => '陰天', + 'Standard Light A' => '標準燈光 A', + 'Standard Light B' => '標準燈光 B', + 'Standard Light C' => '標準燈光 C', + 'Tungsten (Incandescent)' => '鎢絲燈', + 'Unknown' => '未知', + 'Warm White Fluorescent' => '暖白螢光燈 (L 2600 - 3250K)', + 'White Fluorescent' => '白色熒光燈 (WW 3250 - 3800K)', + }, + }, + 'Caption-Abstract' => '說明', + 'CaptionWriter' => '說明作者', + 'Categories' => '類別', + 'Category' => '類別', + 'CellLength' => '元件長度', + 'CellWidth' => '元件寬度', + 'City' => '城市', + 'ColorEffect' => { + PrintConv => { + 'Sepia' => '懷舊深咖啡色', + }, + }, + 'ColorFilter' => 'é¡è‰²æ¿¾é¡', + 'ColorMap' => '色彩地圖', + 'ColorMode' => { + Description => '風格設定', + PrintConv => { + 'Autumn Leaves' => '秋葉', + 'B&W' => '黑白', + 'Clear' => '逿˜Ž', + 'Deep' => '深色', + 'Landscape' => '風景', + 'Light' => '淡色', + 'Neutral' => '中性', + 'Night View' => '夜景', + 'Night View/Portrait' => '夜間肖åƒ', + 'Portrait' => 'è‚–åƒ', + 'Standard' => '標準', + 'Sunset' => 'æ—¥è½', + 'Vivid' => '鮮明色彩', + }, + }, + 'ColorResponseUnit' => 'é¡è‰²å應單ä½', + 'ColorSpace' => { + Description => '色彩空間', + PrintConv => { + 'ICC Profile' => '色彩æè¿°æª”', + 'Uncalibrated' => '未校準', + }, + }, + 'ColorTemperature' => '色溫', + 'Comment' => '註解', + 'ComponentsConfiguration' => 'æ¯å€‹çµ„æˆéƒ¨åˆ†çš„æ„ç¾©', + 'CompressedBitsPerPixel' => 'å½±åƒå£“縮模å¼', + 'Compression' => { + Description => '壓縮方å¼', + PrintConv => { + 'JPEG' => 'JPEG壓縮率', + 'JPEG (old-style)' => 'JPEG (舊å¼)', + 'Kodak DCR Compressed' => 'Kodak DCR 壓縮', + 'Kodak KDC Compressed' => 'Kodak KDC 壓縮', + 'Next' => 'NeXT 2-bit 編碼', + 'Nikon NEF Compressed' => 'Nikon NEF 壓縮', + 'Pentax PEF Compressed' => 'Pentax PEF 壓縮', + 'SGILog' => 'SGI 32-bit Log Luminance 編碼', + 'SGILog24' => 'SGI 24-bit Log Luminance 編碼', + 'Sony ARW Compressed' => 'Sony ARW 壓縮', + 'Thunderscan' => 'ThunderScan 4-bit 編碼', + 'Uncompressed' => '未壓縮', + }, + }, + 'Contrast' => { + Description => 'å°æ¯”', + PrintConv => { + 'High' => '硬', + 'Low' => '軟', + 'Normal' => '標準', + }, + }, + 'Copyright' => 'ç‰ˆæ¬Šæ“æœ‰äºº', + 'CopyrightNotice' => 'ç‰ˆæ¬Šè²æ˜Ž', + 'Country' => '國家', + 'Country-PrimaryLocationName' => '國家', + 'CreateDate' => '數ä½åŒ–的日期時間', + 'CreationDate' => '建立日期', + 'Credit' => '作者', + 'CustomRendered' => { + Description => '自訂影åƒè™•ç†', + PrintConv => { + 'Custom' => '自訂程åº', + 'Normal' => '正常程åº', + }, + }, + 'DateCreated' => '建立日期', + 'DateTimeOriginal' => 'åŽŸå§‹å½±åƒæ—¥æœŸæ™‚é–“', + 'DeviceSettingDescription' => 'è£å‚™è¨­å®šèªªæ˜Ž', + 'DigitalZoom' => '數碼變焦', + 'DigitalZoomRatio' => '數ä½è®Šç„¦æ¯”率', + 'Directory' => '儲存擋案ä½ç½®', + 'DocumentName' => '文件å稱', + 'DriveMode' => '激勵模å¼', + 'DynamicRangeOptimizer' => { + Description => 'å‹•æ…‹ç¯„åœæœ€ä½³åŒ–', + PrintConv => { + 'Advanced Auto' => '進階自動', + 'Advanced Lv1' => '進階等級 1', + 'Advanced Lv2' => '進階等級 2', + 'Advanced Lv3' => '進階等級 3', + 'Advanced Lv4' => '進階等級 4', + 'Advanced Lv5' => '進階等級 5', + 'Auto' => '自動', + 'Off' => '關閉', + 'Standard' => '標準', + }, + }, + 'ExifImageHeight' => 'å½±åƒé«˜åº¦', + 'ExifImageWidth' => 'å½±åƒå¯¬åº¦', + 'ExifOffset' => 'Exif IFD指é‡', + 'ExifVersion' => 'Exif 版本', + 'ExpandFilm' => '展開相片', + 'ExpandFilterLens' => '展開濾é¡', + 'ExpandFlashLamp' => '展開閃光燈', + 'ExpandLens' => '展開é¡é ­', + 'ExpandScanner' => '展開掃æå™¨', + 'ExpandSoftware' => '展開軟體', + 'ExposureCompensation' => 'æ›å…‰è£œå„Ÿ', + 'ExposureIndex' => 'æ›å…‰æŒ‡æ•¸', + 'ExposureMode' => { + Description => 'æ›å…‰æ¨¡å¼', + PrintConv => { + 'Auto' => '自動æ›å…‰', + 'Auto bracket' => 'è‡ªå‹•åŒ…åœæ›å…‰', + 'Manual' => '手動æ›å…‰', + }, + }, + 'ExposureProgram' => { + Description => 'æ‹æ”模å¼', + PrintConv => { + 'Action (High speed)' => 'å‹•æ…‹æ¨¡å¼ (高速快門)', + 'Aperture-priority AE' => '光圈優先', + 'Creative (Slow speed)' => '景深優先', + 'Landscape' => '風景模å¼', + 'Manual' => '手動', + 'Not Defined' => '未定義', + 'Portrait' => 'è‚–åƒæ¨¡å¼ (背景在焦è·ä»¥å¤–的特寫照片)', + 'Program AE' => '正常', + 'Shutter speed priority AE' => '快門優先', + }, + }, + 'ExposureTime' => 'æ›å…‰æ™‚é–“', + 'ExtraSamples' => 'é¡å¤–的樣本', + 'FNumber' => '光圈', + 'FaceOrientation' => { + PrintConv => { + 'Horizontal (normal)' => '0° (頂端/左邊)', + 'Rotate 180' => '180° (底部/å³é‚Š)', + 'Rotate 270 CW' => '90° CW (左邊/底部)', + 'Rotate 90 CW' => '90° CCW (å³é‚Š/頂端)', + }, + }, + 'FaxRecvParams' => 'å‚³çœŸæŽ¥æ”¶åƒæ•¸', + 'FaxRecvTime' => '傳真接收時間', + 'FaxSubAddress' => '傳真附屬地å€', + 'FileFormat' => 'æ ¼å¼', + 'FileModifyDate' => '更新日期', + 'FileName' => '檔案å稱', + 'FileSize' => '檔案大å°', + 'FileSource' => { + Description => '檔案來æº', + PrintConv => { + 'Digital Camera' => '數ä½ç›¸æ©Ÿ', + 'Film Scanner' => '底片掃æå™¨', + 'Reflection Print Scanner' => 'åå°„åˆ—å°æŽƒæå™¨', + }, + }, + 'FileType' => '檔案格å¼', + 'Filename' => '檔案å稱', + 'FillOrder' => '填寫訂單', + 'Flash' => { + Description => '閃光燈', + PrintConv => { + 'Auto, Did not fire' => '閃光燈未擊發, 自動模å¼', + 'Auto, Did not fire, Red-eye reduction' => '自動, 閃光燈未擊發, 防紅眼模å¼', + 'Auto, Fired' => '閃光燈擊發, 自動模å¼', + 'Auto, Fired, Red-eye reduction' => '閃光燈擊發, 自動模å¼, 防紅眼模å¼', + 'Auto, Fired, Red-eye reduction, Return detected' => '閃光燈擊發, 自動模å¼, 嵿¸¬åˆ°åå°„å…‰, 防紅眼模å¼', + 'Auto, Fired, Red-eye reduction, Return not detected' => '閃光燈擊發, 自動模å¼, æœªåµæ¸¬åˆ°åå°„å…‰, 防紅眼模å¼', + 'Auto, Fired, Return detected' => '閃光燈擊發, 自動模å¼, 嵿¸¬åˆ°åå°„å…‰', + 'Auto, Fired, Return not detected' => '閃光燈擊發, 自動模å¼, æœªåµæ¸¬åˆ°åå°„å…‰', + 'Did not fire' => '閃光燈未亮', + 'Fired' => '閃光燈擊發', + 'Fired, Red-eye reduction' => '閃光燈擊發, 防紅眼模å¼', + 'Fired, Red-eye reduction, Return detected' => '閃光燈擊發, 防紅眼模å¼, 嵿¸¬åˆ°åå°„å…‰', + 'Fired, Red-eye reduction, Return not detected' => '閃光燈擊發, 防紅眼模å¼, æœªåµæ¸¬åˆ°åå°„å…‰', + 'Fired, Return detected' => '嵿¸¬åˆ° Strobe åå°„å…‰', + 'Fired, Return not detected' => 'æœªåµæ¸¬åˆ° Strobe åå°„å…‰', + 'No Flash' => '閃光燈未擊發', + 'No flash function' => '沒有閃光功能', + 'Off, Did not fire' => '閃光燈未擊發, 強制閃光模å¼', + 'Off, Did not fire, Return not detected' => '關閉, 閃光燈未擊發, åå°„æœªåµæ¸¬', + 'Off, No flash function' => '關閉, 沒有閃光功能', + 'Off, Red-eye reduction' => '關閉, 防紅眼模å¼', + 'On, Did not fire' => '開啟, 閃光燈未擊發', + 'On, Fired' => '閃光燈擊發, 強制閃光模å¼', + 'On, Red-eye reduction' => '閃光燈擊發, 強制閃光模å¼, 防紅眼模å¼', + 'On, Red-eye reduction, Return detected' => '閃光燈擊發, 強制閃光模å¼, 防紅眼模å¼, 嵿¸¬åˆ°åå°„å…‰', + 'On, Red-eye reduction, Return not detected' => '閃光燈擊發, 強制閃光模å¼, 防紅眼模å¼, æœªåµæ¸¬åˆ°åå°„å…‰', + 'On, Return detected' => '閃光燈擊發, 強制閃光模å¼, 嵿¸¬åˆ°åå°„å…‰', + 'On, Return not detected' => '閃光燈擊發, 強制閃光模å¼, æœªåµæ¸¬åˆ°åå°„å…‰', + }, + }, + 'FlashEnergy' => '閃光能é‡', + 'FlashExposureComp' => '閃光補償', + 'FlashpixVersion' => 'æ”¯æ´ Flashpix 版本', + 'FocalLength' => '焦è·', + 'FocalLength35efl' => '焦è·(35mmæ›ç®—)', + 'FocalLengthIn35mmFormat' => '35mm 相機等效焦è·', + 'FocalPlaneResolutionUnit' => { + Description => '焦平é¢åˆ†è¾¨çއ單ä½', + PrintConv => { + 'None' => 'ç„¡', + 'inches' => '英å‹', + 'um' => 'µm (微米)', + }, + }, + 'FocalPlaneXResolution' => 'X軸焦平é¢åˆ†è¾¨çއ', + 'FocalPlaneYResolution' => 'Y軸焦平é¢åˆ†è¾¨çއ', + 'FocusMode' => 'å°ç„¦æ¨¡å¼', + 'FrameRate' => '書é¢é€Ÿçއ', + 'FrameSize' => 'ç•«é¢å°ºå¯¸', + 'GPSAltitude' => '海拔高度', + 'GPSAltitudeRef' => { + Description => '海拔高度åƒè€ƒ', + PrintConv => { + 'Above Sea Level' => 'æµ·å¹³é¢', + 'Below Sea Level' => 'æµ·å¹³é¢åƒè€ƒ(負值)', + }, + }, + 'GPSAreaInformation' => 'GPSå€åŸŸå稱', + 'GPSDOP' => '測é‡ç²¾åº¦', + 'GPSDateStamp' => 'GPS 日期', + 'GPSDestBearing' => '目的地方ä½', + 'GPSDestBearingRef' => { + Description => '目的地方ä½ä¾æ“š', + PrintConv => { + 'Magnetic North' => 'ç£æ€§çš„æ–¹ä½', + 'True North' => '真實的方ä½', + }, + }, + 'GPSDestDistance' => '目的地è·é›¢', + 'GPSDestDistanceRef' => { + Description => '目的地è·é›¢ä¾æ“š', + PrintConv => { + 'Kilometers' => '公里', + 'Miles' => '英里', + 'Nautical Miles' => '節', + }, + }, + 'GPSDestLatitude' => '目的地緯度', + 'GPSDestLatitudeRef' => { + Description => 'ç›®çš„åœ°çš„ç·¯åº¦ä¾æ“š', + PrintConv => { + 'North' => '北緯', + 'South' => 'å—ç·¯', + }, + }, + 'GPSDestLongitude' => '目的地經度', + 'GPSDestLongitudeRef' => { + Description => 'ç›®çš„åœ°çš„ç¶“åº¦ä¾æ“š', + PrintConv => { + 'East' => 'æ±ç¶“', + 'West' => '西經', + }, + }, + 'GPSDifferential' => { + Description => 'GPS 定ä½å差修正', + PrintConv => { + 'Differential Corrected' => '定ä½å差修正', + 'No Correction' => '無定ä½å差修正', + }, + }, + 'GPSImgDirection' => 'å½±åƒæ–¹ä½', + 'GPSImgDirectionRef' => { + Description => 'å½±åƒæ–¹ä½ä¾æ“š', + PrintConv => { + 'Magnetic North' => 'ç£æ€§çš„æ–¹ä½ ', + 'True North' => '真實的方ä½', + }, + }, + 'GPSInfo' => 'GPS Info IFD指é‡', + 'GPSLatitude' => '緯度', + 'GPSLatitudeRef' => { + Description => '北/å—ç·¯', + PrintConv => { + 'North' => '北緯', + 'South' => 'å—ç·¯', + }, + }, + 'GPSLongitude' => '經度', + 'GPSLongitudeRef' => { + Description => 'æ±/西經', + PrintConv => { + 'East' => 'æ±ç¶“', + 'West' => '西經', + }, + }, + 'GPSMapDatum' => 'ä½¿ç”¨å¤§åœ°æ¸¬é‡æ•¸æ“š', + 'GPSMeasureMode' => { + Description => 'GPS æ¸¬é‡æ¨¡å¼', + PrintConv => { + '2-Dimensional Measurement' => '2-三維測é‡', + '3-Dimensional Measurement' => '3-三維測é‡', + }, + }, + 'GPSProcessingMethod' => 'GPS處ç†å稱', + 'GPSSatellites' => '用於測é‡çš„å…¨çƒè¡›æ˜Ÿå®šä½ç³»çµ±è¡›æ˜Ÿ', + 'GPSSpeed' => 'GPS 接收機的速度', + 'GPSSpeedRef' => { + Description => '速度單ä½', + PrintConv => { + 'km/h' => '時速', + 'knots' => '節', + 'mph' => '英里', + }, + }, + 'GPSStatus' => { + Description => 'GPS接收機的狀態', + PrintConv => { + 'Measurement Active' => 'æ¸¬é‡æœ‰æ•ˆ', + 'Measurement Void' => '測é‡ç„¡æ•ˆ', + }, + }, + 'GPSTimeStamp' => 'GPS 時間 (原å­é˜)', + 'GPSTrack' => '移動方ä½', + 'GPSTrackRef' => { + Description => '移動方ä½ä¾æ“š', + PrintConv => { + 'Magnetic North' => 'ç£æ€§çš„æ–¹ä½ ', + 'True North' => '真實的方ä½', + }, + }, + 'GPSVersionID' => 'GPS 標籤版本', + 'GainControl' => { + Description => '增益控制', + PrintConv => { + 'High gain down' => '高衰減', + 'High gain up' => '高增益', + 'Low gain down' => '低衰減', + 'Low gain up' => '低增益', + 'None' => 'ç„¡', + }, + }, + 'Gamma' => 'å°æ¯”系數', + 'Gradation' => 'ç°éšŽ', + 'GrayResponseCurve' => 'ç°è‰²å應曲線', + 'GrayResponseUnit' => 'ç°è‰²å應單ä½', + 'HDR' => { + Description => 'Auto HDR', + PrintConv => { + 'Off' => '關閉', + }, + }, + 'Headline' => '標題', + 'HighISONoiseReduction' => { + Description => '高 ISO 雜訊消除', + PrintConv => { + 'Auto' => '自動', + 'High' => '高', + 'Low' => '低', + 'Normal' => '一般', + 'Off' => '關閉', + }, + }, + 'HostComputer' => '主機', + 'Hue' => '色相', + 'ICCProfile' => 'ICC 色彩設定檔', + 'IPTC-NAA' => 'IPTC-NAA 元資料', + 'ImageDescription' => 'å½±åƒæ¨™é¡Œ', + 'ImageHeight' => 'å½±åƒé«˜åº¦', + 'ImageHistory' => 'å½±åƒæ­·å²', + 'ImageNumber' => 'å½±åƒç·¨è™Ÿ', + 'ImageSize' => 'å½±åƒå°ºå¯¸', + 'ImageSourceData' => 'å½±åƒä¾†æºè³‡æ–™', + 'ImageUniqueID' => 'ç¨ç‰¹çš„å½±åƒID', + 'ImageWidth' => 'å½±åƒå¯¬åº¦', + 'Index' => '索引', + 'Instructions' => '說明', + 'Interlace' => '交錯', + 'InteropIndex' => '互通性鑑定', + 'InteropOffset' => '互用標記', + 'InteropVersion' => '互通性版本', + 'JPEGQuality' => { + Description => 'å½±åƒå“質', + PrintConv => { + 'Extra Fine' => '超精細', + 'Fine' => '精細', + 'Standard' => '標準å“質', + }, + }, + 'Keyword' => 'é—œéµå­—', + 'Keywords' => 'é—œéµå­—', + 'Lens' => 'é¡é ­', + 'LensInfo' => 'é¡é ­è³‡è¨Š', + 'LightSource' => { + Description => 'å…‰æº', + PrintConv => { + 'Cloudy' => '多雲', + 'Cool White Fluorescent' => '冷白色熒光燈 (W 3800 - 4500K)', + 'Day White Fluorescent' => '日光白色熒光燈 (N 4600 - 5500K)', + 'Daylight' => '日光', + 'Daylight Fluorescent' => '日光熒光燈 (D 5700 - 7100K)', + 'Fine Weather' => '晴天', + 'Flash' => '閃光燈', + 'Fluorescent' => '日光燈', + 'ISO Studio Tungsten' => 'ISO æ”影棚鎢燈', + 'Other' => 'å…¶ä»–å…‰æº', + 'Shade' => '陰天', + 'Standard Light A' => '標準燈光 A', + 'Standard Light B' => '標準燈光 B', + 'Standard Light C' => '標準燈光 C', + 'Tungsten (Incandescent)' => '鎢絲燈', + 'Unknown' => '未知', + 'Warm White Fluorescent' => '暖白螢光燈 (L 2600 - 3250K)', + 'White Fluorescent' => '白色熒光燈 (WW 3250 - 3800K)', + }, + }, + 'Lightness' => '明暗度', + 'Location' => '地å€', + 'LongExposureNoiseReduction' => { + Description => '消除長æ›é›œè¨Š', + PrintConv => { + 'Off' => '關閉', + 'On' => '開啟', + }, + }, + 'Make' => '製造商', + 'MakerNote' => '製造商註解', + 'MakerNotes' => '製造商記錄', + 'MaxAperture' => 'é¡é ­å…‰åœˆæœ€å¤§å€¼', + 'MaxApertureValue' => 'é¡é ­æœ€å¤§å…‰åœˆ', + 'MaxSampleValue' => '大樣å“值', + 'MeteringMode' => { + Description => '測光模å¼', + PrintConv => { + 'Average' => '平凿¸¬å…‰', + 'Center-weighted average' => '中央é‡é»žå¹³å‡æ¸¬å…‰', + 'Multi-segment' => '評價測光', + 'Multi-spot' => '多點測光', + 'Other' => 'å…¶ä»–', + 'Partial' => '局部測光', + 'Spot' => '點測光', + 'Unknown' => '未知', + }, + }, + 'MinSampleValue' => 'å°æ¨£å“值', + 'Model' => '相機型號', + 'Model2' => '第二影åƒè¼¸å…¥è¨­å‚™', + 'ModifyDate' => 'æª”æ¡ˆå»ºç«‹æ—¥æœŸåŠæ™‚é–“', + 'MultiFrameNoiseReduction' => { + Description => '多框雜訊消除', + PrintConv => { + 'Off' => '關閉', + 'On' => '開啟', + }, + }, + 'Noise' => '雜訊', + 'NoiseReduction' => '雜訊抑制', + 'Opto-ElectricConvFactor' => '光電轉æ›å› å­', + 'Orientation' => { + Description => 'å½±åƒçš„æ–¹å‘', + PrintConv => { + 'Horizontal (normal)' => '0° (頂端/左邊)', + 'Mirror horizontal' => '0° (頂端/å³é‚Š)', + 'Mirror horizontal and rotate 270 CW' => '90° CW (左邊/頂端)', + 'Mirror horizontal and rotate 90 CW' => '90° CCW (å³é‚Š/底部)', + 'Mirror vertical' => '180° (底部/左邊)', + 'Rotate 180' => '180° (底部/å³é‚Š)', + 'Rotate 270 CW' => '90° CW (左邊/底部)', + 'Rotate 90 CW' => '90° CCW (å³é‚Š/頂端)', + }, + }, + 'Padding' => 'å¡«å……', + 'PageName' => 'å稱', + 'PageNumber' => 'é æ¬¡', + 'PhotometricInterpretation' => { + Description => 'åƒç´ æ ¼å¼', + PrintConv => { + 'BlackIsZero' => '黑色為零', + 'Color Filter Array' => 'CFA (彩色濾光片矩陣)', + 'RGB Palette' => '調色æ¿çš„é¡è‰²', + 'Transparency Mask' => '逿˜Žé®ç½©', + 'WhiteIsZero' => '白色為零', + }, + }, + 'PlanarConfiguration' => { + Description => 'å½±åƒè³‡æ–™ç·¨æŽ’æ–¹å¼', + PrintConv => { + 'Chunky' => 'Chunky æ ¼å¼ (交錯型)', + 'Planar' => 'Planar æ ¼å¼ (å¹³é¢åž‹)', + }, + }, + 'Predictor' => { + Description => 'é æ¸¬', + PrintConv => { + 'Horizontal differencing' => 'æ°´å¹³å€åˆ¥', + 'None' => '沒有使用éŽé æ¸¬ç·¨ç¢¼æ–¹æ¡ˆ', + }, + }, + 'PrimaryChromaticities' => 'é é¸çš„色度', + 'ProcessingSoftware' => '處ç†è»Ÿé«”', + 'Province-State' => 'å·ž/çœ', + 'Quality' => { + Description => 'å½±åƒå“質', + PrintConv => { + 'Compressed RAW' => 'cRAW', + 'Compressed RAW + JPEG' => 'cRAW+JPEG', + 'Extra Fine' => '超精細', + 'Fine' => '精細', + 'Low' => '低å“質', + 'Normal' => '標準å“質', + 'RAW + JPEG' => 'RAW+JPEG', + 'Standard' => '標準', + }, + }, + 'Rating' => '評分', + 'RatingPercent' => '評分的百分比', + 'RecordMode' => '記錄模å¼', + 'ReferenceBlackWhite' => 'å°é»‘色和白色的åƒè€ƒåƒ¹å€¼', + 'RelatedImageFileFormat' => 'ç›¸é—œçš„å½±åƒæª”案格å¼', + 'RelatedImageHeight' => '相關的影åƒé«˜åº¦', + 'RelatedImageWidth' => '相關的影åƒå¯¬åº¦', + 'RelatedSoundFile' => '相關的音頻檔案', + 'ResolutionUnit' => { + Description => '寬與高的單ä½', + PrintConv => { + 'None' => 'ç„¡', + 'cm' => '公分', + 'inches' => '英å‹', + }, + }, + 'RowsPerStrip' => 'æ¯æ¢åˆ—數', + 'SamplesPerPixel' => '元件數é‡', + 'Saturation' => { + Description => '飽和度', + PrintConv => { + 'High' => '高飽和度', + 'Low' => '低飽和度', + 'Normal' => '標準', + }, + }, + 'SceneCaptureType' => { + Description => '場景擷å–類型', + PrintConv => { + 'Landscape' => '風景', + 'Night' => '夜景', + 'Portrait' => 'è‚–åƒ', + 'Standard' => '標準', + }, + }, + 'SceneMode' => { + Description => 'å ´æ™¯é¸æ“‡', + PrintConv => { + '3D Sweep Panorama' => '3D', + 'Anti Motion Blur' => '防止移動模糊', + 'Auto' => '自動', + 'Cont. Priority AE' => '連æ‹å„ªå…ˆAE', + 'Handheld Night Shot' => '手æŒå¤œæ‹', + 'Landscape' => '風景', + 'Macro' => 'è¶…è¿‘æ‹æ”', + 'Night Portrait' => '夜間肖åƒ', + 'Night Scene' => '夜景', + 'Night View/Portrait' => '夜景/è‚–åƒ', + 'Portrait' => 'è‚–åƒ', + 'Sports' => 'é‹å‹•å ´é¢', + 'Sunset' => '夕陽', + 'Sweep Panorama' => '全景æ”å½±', + }, + }, + 'SceneType' => { + Description => '場景類型', + PrintConv => { + 'Directly photographed' => 'ç›´æŽ¥æ‹æ”的影åƒ', + }, + }, + 'SecurityClassification' => { + Description => '安全分類', + PrintConv => { + 'Confidential' => '秘密', + 'Restricted' => 'é™åˆ¶', + 'Secret' => '機密', + 'Top Secret' => '最高機密', + 'Unclassified' => '未分類', + }, + }, + 'SelfTimerMode' => 'å€’æ•¸è‡ªæ‹æ¨¡å¼', + 'SensingMethod' => { + Description => '感測器類型', + PrintConv => { + 'Color sequential area' => '連續彩色感測器', + 'Color sequential linear' => '連續彩色線性感測器', + 'Monochrome area' => '單色感測器', + 'Monochrome linear' => '單色線性感測器', + 'Not defined' => '未定義', + 'One-chip color area' => '單晶片彩色感測器', + 'Three-chip color area' => '三晶片彩色感測器', + 'Trilinear' => '三線性感測器', + 'Two-chip color area' => '雙晶片彩色感測器', + }, + }, + 'SerialNumber' => '照相機ID', + 'ShadingCompensation' => '陰影補償', + 'Sharpness' => { + Description => '銳利度', + PrintConv => { + 'Hard' => '硬', + 'Normal' => '標準', + 'Soft' => '軟', + }, + }, + 'ShootingMode' => 'æ‹æ”模å¼', + 'ShutterSpeed' => 'æ›å…‰æ™‚é–“', + 'ShutterSpeedValue' => 'å¿«é–€', + 'Software' => '軟體', + 'Source' => '來æº', + 'SpatialFrequencyResponse' => '空間頻率響應', + 'SpectralSensitivity' => 'å…‰è­œéˆæ•度', + 'State' => 'å·ž', + 'StripByteCounts' => 'æ­¤è³‡æ–™å€æ®µçš„容é‡', + 'StripOffsets' => 'å½±åƒè³‡æ–™ä½å€', + 'SubSecTime' => '日期時間秒', + 'SubSecTimeDigitized' => '數ä½åŒ–的日期時間秒', + 'SubSecTimeOriginal' => 'åŽŸå§‹å½±åƒæ—¥æœŸæ™‚é–“ç§’', + 'SubfileType' => 'æ–°çš„ subfile 類型', + 'Subject' => '主旨', + 'SubjectArea' => '主題地å€', + 'SubjectDistance' => '主體è·é›¢ç¯„åœ', + 'SubjectDistanceRange' => { + Description => '主體è·é›¢ç¯„åœ', + PrintConv => { + 'Close' => 'è¿‘', + 'Distant' => 'é ', + 'Macro' => 'å¾®è·', + 'Unknown' => '未知', + }, + }, + 'SubjectLocation' => '主題ä½ç½®', + 'SupplementalCategories' => '補充類別', + 'T4Options' => '未壓縮', + 'ThumbnailImage' => '縮略圖', + 'ThumbnailImageSize' => '縮圖尺寸', + 'Title' => '標題', + 'TransferFunction' => '傳éžå‡½æ•¸', + 'TransmissionReference' => '傳輸附註', + 'Urgency' => '緊急度', + 'UserComment' => '使用者註解', + 'WhiteBalance' => { + Description => '白平衡', + PrintConv => { + 'Auto' => '自動', + 'Black & White' => '黑白', + 'Cloudy' => '多雲', + 'Color Temperature/Color Filter' => '色溫 / 彩色濾é¡', + 'Cool White Fluorescent' => '冷白色螢光燈', + 'Custom' => '自設', + 'Custom 1' => '用戶自定1', + 'Custom 2' => '用戶自定2', + 'Custom 3' => '用戶自定3', + 'Custom 4' => '用戶自定4', + 'Day White Fluorescent' => '日光白色螢光燈', + 'Daylight' => '日光', + 'Daylight Fluorescent' => '日光色螢光燈', + 'Flash' => '閃光燈', + 'Fluorescent' => '熒光燈', + 'Manual' => '手動', + 'Shade' => '陰天', + 'Tungsten' => '白熾燈', + 'Warm White Fluorescent' => '暖白螢光燈', + 'White Fluorescent' => '白色熒光燈', + }, + }, + 'WhitePoint' => '白點色度', + 'Writer-Editor' => '說明作者', + 'XMP' => 'XMP 元資料', + 'XPosition' => 'X ä½ç½®', + 'XResolution' => '水平解æžåº¦', + 'YCbCrCoefficients' => 'é¡è‰²ç©ºé–“變化矩陣系數', + 'YCbCrPositioning' => { + Description => 'Y åŠ C 的設定', + PrintConv => { + 'Centered' => '中心', + 'Co-sited' => '一致', + }, + }, + 'YCbCrSubSampling' => 'Y 到 C 的抽樣比率', + 'YPosition' => 'Y ä½ç½®', + 'YResolution' => '垂直解æžåº¦', + 'ZoneMatching' => { + Description => 'å€åŸŸåŒ¹é…', + PrintConv => { + 'High Key' => '高', + 'ISO Setting Used' => '關閉', + 'Low Key' => '低', + }, + }, +); + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Lang::zh_tw.pm - ExifTool Traditional Chinese language translations + +=head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke and MikeF for providing this translation. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Leaf.pm b/ExifTool/lib/Image/ExifTool/Leaf.pm new file mode 100644 index 0000000..2d697fb --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Leaf.pm @@ -0,0 +1,517 @@ +#------------------------------------------------------------------------------ +# File: Leaf.pm +# +# Description: Read Creo Leaf EXIF meta information +# +# Revisions: 09/28/2005 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::Leaf; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.07'; + +sub ProcessLeaf($$$); + +%Image::ExifTool::Leaf::Main = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Camera' }, + NOTES => q{ + These tags are found in .MOS images from Leaf digital camera backs as + written by Creo Leaf Capture. They exist within the Leaf-specific directory + structure of EXIF tag 0x8606. The tables below list observed Leaf tags, + however ExifTool will extract any tags found in the Leaf directories even if + they don't appear in these tables. + }, + icc_camera_profile => { + Name => 'ICC_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + }, + icc_rgb_ws_profile => { + Name => 'RGB_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + }, + camera_profile => { + Name => 'CameraProfile', + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::CameraProfile', + }, + }, + JPEG_preview_data => { + %Image::ExifTool::previewImageTagInfo, + Groups => { 2 => 'Preview' }, + }, + JPEG_preview_info => 'PreviewInfo', + icc_camera_to_tone_space_flow => { + Name => 'ToneSpaceFlow', + Description => 'ICC To Tone Space Flow', + Format => 'int16u', + }, + icc_camera_to_tone_matrix => { + Name => 'ToneMatrix', + Description => 'ICC To Tone Matrix', + Format => 'int8u', + Binary => 1, + }, + PDA_histogram_data => { + Name => 'PDAHistogram', + Binary => 1, + }, + pattern_ratation_angle => { + Name => 'PatternAngle', + Description => 'Pattern Rotation Angle', + Format => 'int16u', + Notes => '"ratation" is not a typo', + }, + back_serial_number => { + Name => 'BackSerial', + Description => 'Back Serial Number', + PrintConv => '$val =~ s/ .*//s; $val', + }, + image_offset => { Format => 'int16u' }, +); + +%Image::ExifTool::Leaf::CameraProfile = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Camera' }, + CamProf_version => 'CameraProfileVersion', + CamProf_name => 'CameraName', + CamProf_type => 'CameraType', + CamProf_back_type => 'CameraBackType', + CamProf_back_type => { + Name => 'CameraBackType', + }, + CamProf_capture_profile => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::CaptureProfile', + }, + }, + CamProf_image_profile => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::ImageProfile', + }, + }, +); + +%Image::ExifTool::Leaf::CaptureProfile = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Image' }, + CaptProf_version => {}, + CaptProf_name => {}, + CaptProf_type => {}, + CaptProf_back_type => {}, + CaptProf_serial_number => { + Name => 'CaptureSerial', + Description => 'Capture Serial Number', + PrintConv => '$val =~ s/ .*//s; $val', + }, + CaptProf_image_offset => {}, + CaptProf_luminance_consts => {}, + CaptProf_xy_offset_info => 'XYOffsetInfo', + CaptProf_color_matrix => {}, + CaptProf_reconstruction_type=> {}, + CaptProf_image_fields => {}, + CaptProf_image_bounds => {}, + CaptProf_number_of_planes => {}, + CaptProf_raw_data_rotation => {}, + CaptProf_color_averages => {}, + CaptProf_mosaic_pattern => {}, + CaptProf_dark_correction_type=>{}, + CaptProf_right_dark_rect => {}, + CaptProf_left_dark_rect => {}, + CaptProf_center_dark_rect => {}, + CaptProf_CCD_rect => {}, + CaptProf_CCD_valid_rect => {}, + CaptProf_CCD_video_rect => {}, +); + +%Image::ExifTool::Leaf::ImageProfile = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Image' }, + ImgProf_version => {}, + ImgProf_name => {}, + ImgProf_type => {}, + ImgProf_back_type => {}, + ImgProf_shoot_setup => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::ShootSetup', + }, + }, + ImgProf_image_status => {}, + ImgProf_rotation_angle => {}, +); + +%Image::ExifTool::Leaf::ShootSetup = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Image' }, + ShootObj_version => {}, + ShootObj_name => {}, + ShootObj_type => {}, + ShootObj_back_type => {}, + ShootObj_capture_setup => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::CaptureSetup', + }, + }, + ShootObj_color_setup => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::ColorSetup', + }, + }, + ShootObj_save_setup => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::SaveSetup', + }, + }, + ShootObj_camera_setup => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::CameraSetup', + }, + }, + ShootObj_look_header => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::LookHeader', + }, + }, +); + +%Image::ExifTool::Leaf::CaptureSetup = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Image' }, + CaptureObj_version => {}, + CaptureObj_name => {}, + CaptureObj_type => {}, + CaptureObj_back_type => {}, + CaptureObj_neutals => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::Neutrals', + }, + }, + CaptureObj_selection => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::Selection', + }, + }, + CaptureObj_tone_curve => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::ToneCurve', + }, + }, + CaptureObj_sharpness => { + SubDirectory => { + TagTable => 'Image::ExifTool::Leaf::Sharpness', + }, + }, + CaptureObj_single_quality => {}, + CaptureObj_Multi_quality => {}, +); + +%Image::ExifTool::Leaf::Neutrals = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Image' }, + NeutObj_version => {}, + NeutObj_name => {}, + NeutObj_type => {}, + NeutObj_back_type => {}, + NeutObj_neutrals => {}, + NeutObj_color_casts => {}, + NeutObj_shadow_end_points => {}, + NeutObj_highlight_end_points => {}, +); + +%Image::ExifTool::Leaf::Selection = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Image' }, + SelObj_version => {}, + SelObj_name => {}, + SelObj_type => {}, + SelObj_back_type => {}, + SelObj_rect => {}, + SelObj_resolution => {}, + SelObj_scale => {}, + SelObj_locks => {}, + SelObj_orientation => {}, +); + +%Image::ExifTool::Leaf::ToneCurve = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Image' }, + ToneObj_version => {}, + ToneObj_name => {}, + ToneObj_type => {}, + ToneObj_back_type => {}, + ToneObj_npts => {}, + ToneObj_tones => {}, + ToneObj_gamma => {}, +); + +%Image::ExifTool::Leaf::Sharpness = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Image' }, + SharpObj_version => {}, + SharpObj_name => {}, + SharpObj_type => {}, + SharpObj_back_type => {}, + SharpObj_sharp_method => {}, + SharpObj_data_len => {}, + SharpObj_sharp_info => {}, +); + +%Image::ExifTool::Leaf::ColorSetup = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Image' }, + ColorObj_version => {}, + ColorObj_name => {}, + ColorObj_type => {}, + ColorObj_back_type => {}, + ColorObj_has_ICC => {}, + ColorObj_input_profile => {}, + ColorObj_output_profile => {}, + ColorObj_color_mode => {}, + ColorObj_color_type => {}, +); + +%Image::ExifTool::Leaf::SaveSetup = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Other' }, + SaveObj_version => {}, + SaveObj_name => {}, + SaveObj_type => {}, + SaveObj_back_type => {}, + SaveObj_leaf_auto_active=> {}, + SaveObj_leaf_hot_folder => {}, + SaveObj_leaf_output_file_type => {}, + SaveObj_leaf_auto_base_name => {}, + SaveObj_leaf_save_selection => {}, + SaveObj_leaf_open_proc_HDR => {}, + SaveObj_std_auto_active => {}, + SaveObj_std_hot_folder => {}, + SaveObj_std_output_file_type => {}, + SaveObj_std_output_color_mode => {}, + SaveObj_std_output_bit_depth => {}, + SaveObj_std_base_name => {}, + SaveObj_std_save_selection => {}, + SaveObj_std_oxygen => {}, + SaveObj_std_open_in_photoshop => {}, + SaveObj_std_scaled_output => {}, + SaveObj_std_sharpen_output => {}, +); + +%Image::ExifTool::Leaf::CameraSetup = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Camera' }, + CameraObj_version => {}, + CameraObj_name => {}, + CameraObj_type => {}, + CameraObj_back_type => {}, + CameraObj_ISO_speed => {}, + CameraObj_strobe => {}, + CameraObj_camera_type => {}, + CameraObj_lens_type => {}, + CameraObj_lens_ID => {}, +); + +%Image::ExifTool::Leaf::LookHeader = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Other' }, + LookHead_version => {}, + LookHead_name => {}, + LookHead_type => {}, + LookHead_back_type => {}, +); + +# tag table for any unknown Leaf directories +%Image::ExifTool::Leaf::Unknown = ( + PROCESS_PROC => \&ProcessLeaf, + GROUPS => { 0 => 'Leaf', 2 => 'Unknown' }, +); + +# table for Leaf SubIFD entries +%Image::ExifTool::Leaf::SubIFD = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'LeafSubIFD', 2 => 'Image'}, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + NOTES => q{ + Leaf writes a TIFF-format sub-IFD inside IFD0 of a MOS image. No tags in + this sub-IFD are currently known, except for tags 0x8602 and 0x8606 which + really shouldn't be here anyway (so they don't appear in the table below) + because they duplicate references to the same data from tags with the same + ID in IFD0. + }, +); + +# prepare Leaf tables by generating tag 'Name' and table 'NOTES' +{ + my @tableList = ( 'Image::ExifTool::Leaf::Main' ); + my ($tag, %doneTable); + # keep prefix in tag name of common tags + my %keepPrefix = ( Version=>1, Name=>1, Type=>1, BackType=>1 ); + while (@tableList) { + my $table = shift @tableList; + next if $doneTable{$table}; + my $prefix = ($table =~ /::Main$/) ? undef : ''; + $doneTable{$table} = 1; + no strict 'refs'; + $table = \%$table; + use strict 'refs'; + foreach $tag (keys %$table) { + my $tagInfo = $$table{$tag}; + next unless ref $tagInfo eq 'HASH'; + next if $tag eq 'GROUPS'; + if (defined $prefix and not $prefix) { + ($prefix = $tag) =~ s/_.*//; + } + unless ($$tagInfo{Name}) { + my $name; + ($name = $tag) =~ s/_(.)/\U$1/g; + if ($prefix) { + $name =~ s/^$prefix//; + $name = $prefix . $name if $keepPrefix{$name}; + } + $$tagInfo{Name} = ucfirst($name); + } + next unless $$tagInfo{SubDirectory}; + my $subTable = $tagInfo->{SubDirectory}->{TagTable}; + next unless $subTable =~ /::Leaf::/; + push @tableList, $subTable; + } + next unless $prefix; + $$table{NOTES} = "All B<Tag ID>'s in the following table have a " . + "leading '${prefix}_' which\nhas been removed.\n"; + } +} + +#------------------------------------------------------------------------------ +# Process Leaf information +# Inputs: 0) ExifTool object reference +# 1) Reference to directory information hash +# 2) Pointer to tag table for this directory +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessLeaf($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || $$dirInfo{DataLen} - $dirStart; + my $dirEnd = $dirStart + $dirLen; + my $verbose = $et->Options('Verbose'); + my $pos = $dirStart; + my $hdrLen = 52; # header length for PKTS information + my $success; + + $verbose and $et->VerboseDir('Leaf'); + for (;;) { + last if $pos + $hdrLen > $dirEnd; + my $header = substr($$dataPt, $pos, $hdrLen); + last unless substr($header, 0, 4) eq 'PKTS'; + $success = 1; + my $size = Get32u(\$header, 48); + $pos += $hdrLen; + if ($pos + $size > $dirEnd) { + $et->Warn('Truncated Leaf data'); + last; + } + my $tag = substr($header, 8, 40); + $tag =~ s/\0.*//s; + next unless $tag; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + # generate tag info for unknown tags + my $val; + if ($tagInfo and $$tagInfo{Format}) { + $val = ReadValue($dataPt, $pos, $$tagInfo{Format}, undef, $size); + } else { + $val = substr($$dataPt, $pos, $size); + } + unless ($tagInfo) { + my $name = ucfirst($tag); + $name =~ s/_(.)/\U$1/g; + if ($val =~ /^PKTS\0\0\0\x01/) { + # also unpack unknown directories + $tagInfo = { + Name => $name, + SubDirectory => { TagTable => 'Image::ExifTool::Leaf::Unknown' }, + }; + } elsif ($tagTablePtr ne \%Image::ExifTool::Leaf::Main or + $et->Options('Unknown')) + { + $tagInfo = { + Name => $name, + Writable => 0, + PrintConv => 'length($val) > 60 ? substr($val,0,55) . "[...]" : $val', + }; + # make tags in main table unknown because they tend to be binary + $$tagInfo{Unknown} = 1 if $tagTablePtr eq \%Image::ExifTool::Leaf::Main; + } + $tagInfo and AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + if ($verbose) { + $et->VerboseInfo($tag, $tagInfo, + Table => $tagTablePtr, + Value => $val, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Size => $size, + Start => $pos, + ); + } + if ($tagInfo) { + if ($$tagInfo{SubDirectory}) { + my %subdirInfo = ( + DataPt => $dataPt, + DirLen => $size, + DirStart => $pos, + DataPos => $$dirInfo{DataPos}, + DirName => 'Leaf PKTS', + ); + my $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + $et->ProcessDirectory(\%subdirInfo, $subTable); + } else { + $val =~ tr/\n/ /; # translate newlines to spaces + $val =~ s/\0+$//; # remove null terminators + $et->FoundTag($tagInfo, $val); + } + } + $pos += $size; + } + $success or $et->Warn('Bad format Leaf data'); + return $success; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Leaf - Read Creo Leaf EXIF meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +meta information from Leaf digital camera backs written by Creo Leaf +Capture. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Leaf Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Lytro.pm b/ExifTool/lib/Image/ExifTool/Lytro.pm new file mode 100644 index 0000000..5dae8b2 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Lytro.pm @@ -0,0 +1,214 @@ +#------------------------------------------------------------------------------ +# File: Lytro.pm +# +# Description: Read Lytro LFP files +# +# Revisions: 2014-07-17 - P. Harvey Created +# +# References: 1) http://optics.miloush.net/lytro/TheFileFormat.aspx +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lytro; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Import; + +$VERSION = '1.03'; + +sub ExtractTags($$$); + +# Lytro LFP tags (ref PH) +%Image::ExifTool::Lytro::Main = ( + GROUPS => { 2 => 'Camera' }, + VARS => { NO_ID => 1 }, + NOTES => q{ + Tag definitions for Lytro Light Field Picture (LFP) files. ExifTool + extracts the full JSON metadata blocks, as well as breaking them down into + individual tags. All available tags are extracted from the JSON metadata, + even if they don't appear in the table below. + }, + JSONMetadata => { + Notes => 'the full JSON-format metadata blocks', + Binary => 1, + List => 1, + }, + EmbeddedImage => { + Notes => 'JPEG image embedded in LFP files written by Lytro Desktop', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + Type => { Name => 'CameraType' }, + CameraMake => { Name => 'Make' }, + CameraModel => { Name => 'Model', Description => 'Camera Model Name' }, + CameraSerialNumber => { Name => 'SerialNumber'}, + CameraFirmware => { Name => 'FirmwareVersion'}, + DevicesAccelerometerSampleArrayTime => { Name => 'AccelerometerTime'}, + DevicesAccelerometerSampleArrayX => { Name => 'AccelerometerX'}, + DevicesAccelerometerSampleArrayY => { Name => 'AccelerometerY'}, + DevicesAccelerometerSampleArrayZ => { Name => 'AccelerometerZ'}, + DevicesClockZuluTime => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + DevicesLensFNumber => { + Name => 'FNumber', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + DevicesLensFocalLength => { + Name => 'FocalLength', + ValueConv => '$val * 1000', # convert from metres to mm + PrintConv => 'sprintf("%.1f mm",$val)', + }, + DevicesLensTemperature => { + Name => 'LensTemperature', + PrintConv => 'sprintf("%.1f C",$val)', + }, + DevicesSocTemperature => { + Name => 'SocTemperature', + PrintConv => 'sprintf("%.1f C",$val)', + }, + DevicesShutterFrameExposureDuration => { + Name => 'FrameExposureTime', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + DevicesShutterPixelExposureDuration => { + Name => 'ExposureTime', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + DevicesSensorPixelPitch => { + Name => 'FocalPlaneXResolution', + Notes => 'Y resolution is the same as X resolution', + ValueConv => '25.4 / $val / 1000', # convert from metres to pixels/inch + }, + DevicesSensorSensorSerial => { Name => 'SensorSerialNumber'}, + DevicesSensorIso => { Name => 'ISO' }, + ImageLimitExposureBias => { Groups => { 2 => 'Image' }, PrintConv => 'sprintf("%+.1f", $val)' }, + ImageModulationExposureBias => { Groups => { 2 => 'Image' }, PrintConv => 'sprintf("%+.1f", $val)' }, + ImageOrientation => { + Name => 'Orientation', + Groups => { 2 => 'Image' }, + PrintConv => { + 1 => 'Horizontal (normal)', + }, + }, +); + +#------------------------------------------------------------------------------ +# Extract tags from a parsed JSON hash +# Inputs: 0) ExifTool ref, 1) tag hash ref, 2) base tag name +sub ExtractTags($$$) +{ + my ($et, $meta, $parent) = @_; + ref $meta eq 'HASH' or $et->Warn('Invalid LFP metadata'), return; + my ($key, $val, $name, $tagTablePtr); + foreach $key (sort keys %$meta) { + my $tag = $parent . ucfirst($key); + foreach $val (ref $$meta{$key} eq 'ARRAY' ? @{$$meta{$key}} : $$meta{$key}) { + ref $val eq 'HASH' and ExtractTags($et, $val, $tag), next; + $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main'); + unless ($$tagTablePtr{$tag}) { + ($name = $tag) =~ s/[^-_a-zA-Z0-9](.?)/\U$1/g; + $name =~ s/ParametersVendorContentComLytroTags//; + my %tagInfo; + $tagInfo{Groups} = { 2 => 'Image' } unless $name =~ s/^Devices//; + $tagInfo{List} = 1 if ref $$meta{$key} eq 'ARRAY'; + $tagInfo{Name} = $name; + my $str = $tag eq $name ? '' : " as $name"; + $et->VPrint(0, " [adding $tag$str]\n"); + AddTagToTable($tagTablePtr, $tag, \%tagInfo); + } + $et->HandleTag($tagTablePtr, $tag, $val); + } + } +} + +#------------------------------------------------------------------------------ +# Process segments from a Lytro LFP image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid Lytro image +sub ProcessLFP($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my ($buff, $id); + + # validate the Lytro file header + return 0 unless $raf->Read($buff, 16) == 16 and $buff =~ /^\x89LFP\x0d\x0a\x1a\x0a/; + $et->SetFileType(); # set the FileType tag + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main'); + while ($raf->Read($buff, 16) == 16) { + $buff =~ /^\x89LF/ or $et->Warn('LFP format error'), last; + my $size = Get32u(\$buff, 12); + $size & 0x80000000 and $et->Warn('Invalid LFP segment size'), last; + $raf->Read($id, 80) == 80 or $et->Warn('Truncated LFP segment'), last; # ignore the sha1 + if ($verbose) { + $id =~ s/\0.*//s; + $et->VPrint(0, substr($buff,1,3), " segment ($size bytes, $id)\n"); + } + if ($size > 20000000) { + $raf->Seek($size, 1) or $et->Warn('Seek error in LFP file'), last; + } else { + $raf->Read($buff,$size) == $size or $et->Warn('Truncated LFP data'), last; + $et->VerboseDump(\$buff, Addr=>$raf->Tell()-$size); + if ($buff =~ /^\{\s+"/) { # JSON metadata? + pos($buff) = 0; + $et->HandleTag($tagTablePtr, 'JSONMetadata', $buff); + my $meta = Image::ExifTool::Import::ReadJSONObject(undef, \$buff); + ExtractTags($et, $meta, ''); + } elsif ($buff =~ /^\xff\xd8\xff/) { # embedded JPEG image? + $et->HandleTag($tagTablePtr, 'EmbeddedImage', $buff); + } + } + # skip padding if necessary + my $pad = 16 - ($size % 16); + $raf->Seek($pad, 1) if $pad != 16; + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Lytro - Read Lytro LFP files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read metadata +from Lytro Light Field Picture (LFP) files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://optics.miloush.net/lytro/TheFileFormat.aspx> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Lytro Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/M2TS.pm b/ExifTool/lib/Image/ExifTool/M2TS.pm new file mode 100644 index 0000000..0fec6c9 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/M2TS.pm @@ -0,0 +1,972 @@ +#------------------------------------------------------------------------------ +# File: M2TS.pm +# +# Description: Read M2TS (AVCHD) meta information +# +# Revisions: 2009/07/03 - P. Harvey Created +# +# References: 1) http://neuron2.net/library/mpeg2/iso13818-1.pdf +# 2) http://www.blu-raydisc.com/Assets/Downloadablefile/BD-RE_Part3_V2.1_WhitePaper_080406-15271.pdf +# 3) http://www.videohelp.com/forum/archive/reading-avchd-playlist-files-bdmv-playlist-mpl-t358888.html +# 4) http://en.wikipedia.org/wiki/MPEG_transport_stream +# 5) http://www.dunod.com/documents/9782100493463/49346_DVB.pdf +# 6) http://trac.handbrake.fr/browser/trunk/libhb/stream.c +# 7) http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=04560141 +# 8) http://www.w6rz.net/xport.zip +# 9) https://en.wikipedia.org/wiki/Program-specific_information +# +# Notes: Variable names containing underlines are the same as in ref 1. +# +# Glossary: PES = Packetized Elementary Stream +# PAT = Program Association Table +# PMT = Program Map Table +# PCR = Program Clock Reference +# PID = Packet Identifier +# +# To Do: - parse PCR to obtain average bitrates? +#------------------------------------------------------------------------------ + +package Image::ExifTool::M2TS; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.23'; + +# program map table "stream_type" lookup (ref 6/1/9) +my %streamType = ( + 0x00 => 'Reserved', + 0x01 => 'MPEG-1 Video', + 0x02 => 'MPEG-2 Video', + 0x03 => 'MPEG-1 Audio', + 0x04 => 'MPEG-2 Audio', + 0x05 => 'ISO 13818-1 private sections', + 0x06 => 'ISO 13818-1 PES private data', + 0x07 => 'ISO 13522 MHEG', + 0x08 => 'ISO 13818-1 DSM-CC', + 0x09 => 'ISO 13818-1 auxiliary', + 0x0A => 'ISO 13818-6 multi-protocol encap', + 0x0B => 'ISO 13818-6 DSM-CC U-N msgs', + 0x0C => 'ISO 13818-6 stream descriptors', + 0x0D => 'ISO 13818-6 sections', + 0x0E => 'ISO 13818-1 auxiliary', + 0x0F => 'MPEG-2 AAC Audio', + 0x10 => 'MPEG-4 Video', + 0x11 => 'MPEG-4 LATM AAC Audio', + 0x12 => 'MPEG-4 generic', + 0x13 => 'ISO 14496-1 SL-packetized', + 0x14 => 'ISO 13818-6 Synchronized Download Protocol', + 0x15 => 'Packetized metadata', + 0x16 => 'Sectioned metadata', + 0x17 => 'ISO/IEC 13818-6 DSM CC Data Carousel metadata', + 0x18 => 'ISO/IEC 13818-6 DSM CC Object Carousel metadata', + 0x19 => 'ISO/IEC 13818-6 Synchronized Download Protocol metadata', + 0x1a => 'ISO/IEC 13818-11 IPMP', + 0x1b => 'H.264 (AVC) Video', + 0x1c => 'ISO/IEC 14496-3 (MPEG-4 raw audio)', + 0x1d => 'ISO/IEC 14496-17 (MPEG-4 text)', + 0x1e => 'ISO/IEC 23002-3 (MPEG-4 auxiliary video)', + 0x1f => 'ISO/IEC 14496-10 SVC (MPEG-4 AVC sub-bitstream)', + 0x20 => 'ISO/IEC 14496-10 MVC (MPEG-4 AVC sub-bitstream)', + 0x21 => 'ITU-T Rec. T.800 and ISO/IEC 15444 (JPEG 2000 video)', + 0x24 => 'H.265 (HEVC) Video', #PH + 0x42 => 'Chinese Video Standard', + 0x7f => 'ISO/IEC 13818-11 IPMP (DRM)', + 0x80 => 'DigiCipher II Video', + 0x81 => 'A52/AC-3 Audio', + 0x82 => 'HDMV DTS Audio', + 0x83 => 'LPCM Audio', + 0x84 => 'SDDS Audio', + 0x85 => 'ATSC Program ID', + 0x86 => 'DTS-HD Audio', + 0x87 => 'E-AC-3 Audio', + 0x8a => 'DTS Audio', + 0x90 => 'PGS Audio', #https://www.avsforum.com/threads/bass-eq-for-filtered-movies.2995212/page-399 + 0x91 => 'A52b/AC-3 Audio', + 0x92 => 'DVD_SPU vls Subtitle', + 0x94 => 'SDDS Audio', + 0xa0 => 'MSCODEC Video', + 0xea => 'Private ES (VC-1)', + # 0x80-0xFF => 'User Private', +); + +# "table_id" values (ref 5) +my %tableID = ( + 0x00 => 'Program Association', + 0x01 => 'Conditional Access', + 0x02 => 'Program Map', + 0x03 => 'Transport Stream Description', + 0x40 => 'Actual Network Information', + 0x41 => 'Other Network Information', + 0x42 => 'Actual Service Description', + 0x46 => 'Other Service Description', + 0x4a => 'Bouquet Association', + 0x4e => 'Actual Event Information - Present/Following', + 0x4f => 'Other Event Information - Present/Following', + 0x50 => 'Actual Event Information - Schedule', #(also 0x51-0x5f) + 0x60 => 'Other Event Information - Schedule', # (also 0x61-0x6f) + 0x70 => 'Time/Date', + 0x71 => 'Running Status', + 0x72 => 'Stuffing', + 0x73 => 'Time Offset', + 0x7e => 'Discontinuity Information', + 0x7f => 'Selection Information', + # 0x80-0xfe => 'User Defined', +); + +# PES stream ID's for which a syntax field does not exist +my %noSyntax = ( + 0xbc => 1, # program_stream_map + 0xbe => 1, # padding_stream + 0xbf => 1, # private_stream_2 + 0xf0 => 1, # ECM_stream + 0xf1 => 1, # EMM_stream + 0xf2 => 1, # DSMCC_stream + 0xf8 => 1, # ITU-T Rec. H.222.1 type E stream + 0xff => 1, # program_stream_directory +); + +my $knotsToKph = 1.852; # knots --> km/h + +# information extracted from the MPEG-2 transport stream +%Image::ExifTool::M2TS::Main = ( + GROUPS => { 2 => 'Video' }, + VARS => { NO_ID => 1 }, + NOTES => q{ + The MPEG-2 transport stream is used as a container for many different + audio/video formats (including AVCHD). This table lists information + extracted from M2TS files. + }, + VideoStreamType => { + PrintHex => 1, + PrintConv => \%streamType, + SeparateTable => 'StreamType', + }, + AudioStreamType => { + PrintHex => 1, + PrintConv => \%streamType, + SeparateTable => 'StreamType', + }, + Duration => { + Notes => q{ + the -fast option may be used to avoid scanning to the end of file to + calculate the Duration + }, + ValueConv => '$val / 27000000', # (clock is 27MHz) + PrintConv => 'ConvertDuration($val)', + }, + # the following tags are for documentation purposes only + _AC3 => { SubDirectory => { TagTable => 'Image::ExifTool::M2TS::AC3' } }, + _H264 => { SubDirectory => { TagTable => 'Image::ExifTool::H264::Main' } }, + _MISB => { SubDirectory => { TagTable => 'Image::ExifTool::MISB::Main' } }, +); + +# information extracted from AC-3 audio streams +%Image::ExifTool::M2TS::AC3 = ( + GROUPS => { 1 => 'AC3', 2 => 'Audio' }, + VARS => { NO_ID => 1 }, + NOTES => 'Tags extracted from AC-3 audio streams.', + AudioSampleRate => { + PrintConv => { + 0 => '48000', + 1 => '44100', + 2 => '32000', + }, + }, + AudioBitrate => { + PrintConvColumns => 2, + ValueConv => { + 0 => 32000, + 1 => 40000, + 2 => 48000, + 3 => 56000, + 4 => 64000, + 5 => 80000, + 6 => 96000, + 7 => 112000, + 8 => 128000, + 9 => 160000, + 10 => 192000, + 11 => 224000, + 12 => 256000, + 13 => 320000, + 14 => 384000, + 15 => 448000, + 16 => 512000, + 17 => 576000, + 18 => 640000, + 32 => '32000 max', + 33 => '40000 max', + 34 => '48000 max', + 35 => '56000 max', + 36 => '64000 max', + 37 => '80000 max', + 38 => '96000 max', + 39 => '112000 max', + 40 => '128000 max', + 41 => '160000 max', + 42 => '192000 max', + 43 => '224000 max', + 44 => '256000 max', + 45 => '320000 max', + 46 => '384000 max', + 47 => '448000 max', + 48 => '512000 max', + 49 => '576000 max', + 50 => '640000 max', + }, + PrintConv => 'ConvertBitrate($val)', + }, + SurroundMode => { + PrintConv => { + 0 => 'Not indicated', + 1 => 'Not Dolby surround', + 2 => 'Dolby surround', + }, + }, + AudioChannels => { + PrintConvColumns => 2, + PrintConv => { + 0 => '1 + 1', + 1 => 1, + 2 => 2, + 3 => 3, + 4 => '2/1', + 5 => '3/1', + 6 => '2/2', + 7 => '3/2', + 8 => 1, + 9 => '2 max', + 10 => '3 max', + 11 => '4 max', + 12 => '5 max', + 13 => '6 max', + }, + }, +); + +#------------------------------------------------------------------------------ +# Extract information from AC-3 audio stream +# Inputs: 0) ExifTool ref, 1) data ref +# Reference: http://www.atsc.org/standards/a_52b.pdf +sub ParseAC3Audio($$) +{ + my ($et, $dataPt) = @_; + if ($$dataPt =~ /\x0b\x77..(.)/sg) { + my $sampleRate = ord($1) >> 6; + my $tagTablePtr = GetTagTable('Image::ExifTool::M2TS::AC3'); + $et->HandleTag($tagTablePtr, AudioSampleRate => $sampleRate); + } +} + +#------------------------------------------------------------------------------ +# Extract information from AC-3 stream descriptor +# Inputs: 0) ExifTool ref, 1) data ref +# Reference: http://www.atsc.org/standards/a_52b.pdf +# Note: This information is duplicated in the Audio stream, but it +# is somewhat easier to extract it from the descriptor instead +sub ParseAC3Descriptor($$) +{ + my ($et, $dataPt) = @_; + return if length $$dataPt < 3; + my @v = unpack('C3', $$dataPt); + my $tagTablePtr = GetTagTable('Image::ExifTool::M2TS::AC3'); + # $et->HandleTag($tagTablePtr, 'AudioSampleRate', $v[0] >> 5); + $et->HandleTag($tagTablePtr, 'AudioBitrate', $v[1] >> 2); + $et->HandleTag($tagTablePtr, 'SurroundMode', $v[1] & 0x03); + $et->HandleTag($tagTablePtr, 'AudioChannels', ($v[2] >> 1) & 0x0f); + # don't (yet) decode any more (language codes, etc) +} + +#------------------------------------------------------------------------------ +# Parse PID stream data +# Inputs: 0) ExifTool ref, 1) PID number, 2) PID type, 3) PID name, 4) data ref +# Returns: 0=stream parsed OK, +# 1=stream parsed but we want to parse more of these, +# -1=can't parse yet because we don't know the type +sub ParsePID($$$$$) +{ + my ($et, $pid, $type, $pidName, $dataPt) = @_; + # can't parse until we know the type (Program Map Table may be later in the stream) + return -1 unless defined $type; + my $verbose = $et->Options('Verbose'); + if ($verbose > 1) { + my $out = $et->Options('TextOut'); + printf $out "Parsing stream 0x%.4x (%s) %d bytes\n", $pid, $pidName, length($$dataPt); + $et->VerboseDump($dataPt); + } + my $more = 0; + if ($type == 0x01 or $type == 0x02) { + # MPEG-1/MPEG-2 Video + require Image::ExifTool::MPEG; + Image::ExifTool::MPEG::ParseMPEGAudioVideo($et, $dataPt); + } elsif ($type == 0x03 or $type == 0x04) { + # MPEG-1/MPEG-2 Audio + require Image::ExifTool::MPEG; + Image::ExifTool::MPEG::ParseMPEGAudio($et, $dataPt); + } elsif ($type == 0x1b) { + # H.264 Video + require Image::ExifTool::H264; + $more = Image::ExifTool::H264::ParseH264Video($et, $dataPt); + # force parsing additional H264 frames with ExtractEmbedded option + if ($$et{OPTIONS}{ExtractEmbedded}) { + $more = 1; + } elsif (not $$et{OPTIONS}{Validate}) { + $et->WarnOnce('The ExtractEmbedded option may find more tags in the video data',3); + } + } elsif ($type == 0x81 or $type == 0x87 or $type == 0x91) { + # AC-3 audio + ParseAC3Audio($et, $dataPt); + } elsif ($type == 0x15) { + # packetized metadata (look for MISB code starting after 5-byte header) + if ($$dataPt =~ /^.{5}\x06\x0e\x2b\x34/s) { + $more = Image::ExifTool::MISB::ParseMISB($et, $dataPt, GetTagTable('Image::ExifTool::MISB::Main')); + if (not $$et{OPTIONS}{ExtractEmbedded}) { + $more = 0; # extract from only the first packet unless ExtractEmbedded is used + } elsif ($$et{OPTIONS}{ExtractEmbedded} > 2) { + $more = 1; # read past unknown 0x15 packets if ExtractEmbedded > 2 + } + } + } elsif ($type < 0) { + if ($$dataPt =~ /^(.{164})?(.{24})A[NS][EW]/s) { + # (Blueskysea B4K, Novatek NT96670) + # 0000: 01 00 ff 00 30 31 32 33 34 35 37 38 61 62 63 64 [....01234578abcd] + # 0010: 65 66 67 0a 00 00 00 00 00 00 00 00 00 00 00 00 [efg.............] + # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0040: 00 00 00 00 30 31 32 33 34 35 37 38 71 77 65 72 [....01234578qwer] + # 0050: 74 79 75 69 6f 70 0a 00 00 00 00 00 00 00 00 00 [tyuiop..........] + # 0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0080: 00 00 00 00 63 38 61 61 32 35 63 66 34 35 65 65 [....c8aa25cf45ee] + # 0090: 61 39 65 32 34 34 32 66 61 65 62 35 65 30 39 39 [a9e2442faeb5e099] + # 00a0: 30 37 64 34 15 00 00 00 10 00 00 00 1b 00 00 00 [07d4............] + # 00b0: 15 00 00 00 01 00 00 00 09 00 00 00 41 4e 57 00 [............ANW.] + # 00c0: 82 9a 57 45 98 b2 00 46 66 66 e4 41 d7 e3 14 43 [..WE...Fff.A...C] + # 00d0: 01 00 02 00 03 00 04 00 05 00 06 00 [............] + # (Viofo A119V3) + # 0000: 08 00 00 00 07 00 00 00 18 00 00 00 15 00 00 00 [................] + # 0010: 03 00 00 00 0b 00 00 00 41 4e 45 00 01 f2 ac 45 [........ANE....E] + # 0020: 2d 7f 6e 45 b8 1e 97 41 d7 23 46 43 00 00 00 00 [-.nE...A.#FC....] + # pad with dummy header and parse with existing FreeGPS code (minimum 92 bytes) + my $dat = ("\0" x 16) . substr($$dataPt, length($1 || '')) . ("\0" x 20); + my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + Image::ExifTool::QuickTime::ProcessFreeGPS($et, { DataPt => \$dat }, $tbl); + $more = 1; + } elsif ($$dataPt =~ /^A([NS])([EW])\0/s) { + # INNOVV TS video (same format is INNOVV MP4) + SetByteOrder('II'); + my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + while ($$dataPt =~ /(A[NS][EW]\0.{28})/g) { + my $dat = $1; + my $lat = abs(GetFloat(\$dat, 4)); # (abs just to be safe) + my $lon = abs(GetFloat(\$dat, 8)); # (abs just to be safe) + my $spd = GetFloat(\$dat, 12) * $knotsToKph; + my $trk = GetFloat(\$dat, 16); + my @acc = unpack('x20V3', $dat); + map { $_ = $_ - 4294967296 if $_ >= 0x80000000 } @acc; + Image::ExifTool::QuickTime::ConvertLatLon($lat, $lon); + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $et->HandleTag($tagTbl, GPSLatitude => abs($lat) * (substr($dat,1,1) eq 'S' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSLongitude => abs($lon) * (substr($dat,2,1) eq 'W' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSSpeed => $spd); + $et->HandleTag($tagTbl, GPSSpeedRef => 'K'); + $et->HandleTag($tagTbl, GPSTrack => $trk); + $et->HandleTag($tagTbl, GPSTrackRef => 'T'); + $et->HandleTag($tagTbl, Accelerometer => "@acc"); + } + SetByteOrder('MM'); + $more = 1; + } elsif ($$dataPt =~ /^\$(GPSINFO|GSNRINFO),/) { + # $GPSINFO,0x0004,2021.08.09 13:27:36,2341.54561,12031.70135,8.0,51,153,0,0,\x0d + # $GSNRINFO,0.01,0.04,0.25\0 + $$dataPt =~ tr/\x0d/\x0a/; + $$dataPt =~ tr/\0//d; + my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + my @lines = split /\x0a/, $$dataPt; + my ($line, $lastTime); + foreach $line (@lines) { + if ($line =~ /^\$GPSINFO/) { + my @a = split /,/, $lines[0]; + next unless @a > 7; + # ignore duplicate fixes + next if $lastTime and $a[2] eq $lastTime; + $lastTime = $a[2]; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $a[2] =~ tr/./:/; + # (untested, and probably doesn't work for S/W hemispheres) + my ($lat, $lon) = @a[3,4]; + Image::ExifTool::QuickTime::ConvertLatLon($lat, $lon); + # $a[0] - flags? values: '0x0001','0x0004','0x0008','0x0010' + $et->HandleTag($tagTbl, GPSDateTime => $a[2]); + $et->HandleTag($tagTbl, GPSLatitude => $lat); + $et->HandleTag($tagTbl, GPSLongitude => $lon); + $et->HandleTag($tagTbl, GPSSpeed => $a[5]); + $et->HandleTag($tagTbl, GPSSpeedRef => 'K'); + # $a[6] - values: 48-60 + $et->HandleTag($tagTbl, GPSTrack => $a[7]); + $et->HandleTag($tagTbl, GPSTrackRef => 'T'); + # #a[8,9] - always 0 + } elsif ($line =~ /^\$GSNRINFO/) { + my @a = split /,/, $line; + shift @a; + $et->HandleTag($tagTbl, Accelerometer => "@a"); + } + } + $more = 1; + } elsif ($$dataPt =~ /\$GPRMC,/) { + # Jomise T860S-GM dashcam + # $GPRMC,hhmmss.ss,A,ddmm.mmmmm,N,dddmm.mmmmm,W,spd-kts,dir-dg,DDMMYY,,*cs + # $GPRMC,172255.00,A,:985.95194,N,17170.14674,W,029.678,170.68,240822,,,D*7B + # $GPRMC,172355.00,A,:984.76779,N,17170.00473,W,032.219,172.04,240822,,,D*7B + # ddmm.mmmm: from 4742.2568 12209.2028 (should be) + # to 4741.7696 12209.1056 + # stamped on video: 47.70428N, 122.15338W, 35mph (dd.ddddd) + # to 47.69616N, 122.15176W, 37mph + my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + while ($$dataPt =~ /\$[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(.{2})(\d{2}\.\d+),([NS]),(.{3})(\d{2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/g and + # do some basic sanity checks on the date + $13 <= 31 and $14 <= 12 and $15 <= 99) + { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + my $year = $15 + ($15 >= 70 ? 1900 : 2000); + $et->HandleTag($tagTbl, GPSDateTime => sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3)); + #(not this simple) + #$et->HandleTag($tagTbl, GPSLatitude => (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1)); + #$et->HandleTag($tagTbl, GPSLongitude => (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1)); + $et->HandleTag($tagTbl, GPSSpeed => $11 * $knotsToKph) if length $11; + $et->HandleTag($tagTbl, GPSTrack => $12) if length $12; + # it looks like maybe the degrees are xor-ed with something, + # and the minutes have some scaling factor and offset? + # (the code below is approximately correct for my only sample) + my @chars = unpack('C*', $5 . $8); + my @xor = (0x0e,0x0e,0x00,0x05,0x03); # (empirical based on 1 sample; may be completely off base) + my $bad; + foreach (@chars) { + $_ ^= shift(@xor); + $bad = 1 if $_ < 0x30 or $_ > 0x39; + } + if ($bad) { + $et->WarnOnce('Error decrypting GPS degrees'); + } else { + my $la = pack('C*', @chars[0,1]); + my $lo = pack('C*', @chars[2,3,4]); + $et->WarnOnce('Decryption of this GPS is highly experimental. More testing samples are required'); + $et->HandleTag($tagTbl, GPSLatitude => (($la || 0) + (($6-85.95194)/2.43051724137931+42.2568)/60) * ($7 eq 'N' ? 1 : -1)); + $et->HandleTag($tagTbl, GPSLongitude => (($lo || 0) + (($9-70.14674)/1.460987654320988+9.2028)/60) * ($10 eq 'E' ? 1 : -1)); + } + } + } elsif ($$dataPt =~ /^.{44}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s and length($$dataPt) >= 84) { + #forum11320 + SetByteOrder('II'); + my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + my $lat = abs(GetFloat($dataPt, 48)); # (abs just to be safe) + my $lon = abs(GetFloat($dataPt, 56)); # (abs just to be safe) + my $spd = GetFloat($dataPt, 64); + my $trk = GetFloat($dataPt, 68); + $et->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong'); + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + my @date = unpack('x32V3x28V3', $$dataPt); + $date[3] += 2000; + $et->HandleTag($tagTbl, GPSDateTime => sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @date[3..5,0..2])); + $et->HandleTag($tagTbl, GPSLatitude => abs($lat) * ($1 eq 'S' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSLongitude => abs($lon) * ($2 eq 'W' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSSpeed => $spd); + $et->HandleTag($tagTbl, GPSSpeedRef => 'K'); + $et->HandleTag($tagTbl, GPSTrack => $trk); + $et->HandleTag($tagTbl, GPSTrackRef => 'T'); + SetByteOrder('MM'); + $more = 1; + } + delete $$et{DOC_NUM}; + } + return $more; +} + +#------------------------------------------------------------------------------ +# Extract information from a M2TS file +# Inputs: 0) ExifTool object reference, 1) DirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid M2TS file +sub ProcessM2TS($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $pLen, $upkPrefix, $j, $fileType, $eof); + my (%pmt, %pidType, %data, %sectLen, %packLen, %fromStart); + my ($startTime, $endTime, $fwdTime, $backScan, $maxBack); + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + + # read first packet + return 0 unless $raf->Read($buff, 8) == 8; + # test for magic number (sync byte is the only thing we can safely check) + return 0 unless $buff =~ /^(....)?\x47/s; + unless ($1) { + $pLen = 188; # no timecode + $fileType = 'M2T'; # (just as a way to tell there is no timecode) + $upkPrefix = 'N'; + } else { + $pLen = 192; # 188-byte transport packet + leading 4-byte timecode (ref 4) + $upkPrefix = 'x4N'; + } + my $prePos = $pLen - 188; # byte position of packet prefix + my $readSize = 64 * $pLen; # size of our read buffer + $raf->Seek(0,0); # rewind to start + $raf->Read($buff, $readSize) >= $pLen * 4 or return 0; # require at least 4 packets + # validate the sync byte in the next 3 packets + for ($j=1; $j<4; ++$j) { + return 0 unless substr($buff, $prePos + $pLen * $j, 1) eq 'G'; # (0x47) + } + $et->SetFileType($fileType); + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::M2TS::Main'); + + # PID lookup strings (will add to this with entries from program map table) + my %pidName = ( + 0 => 'Program Association Table', + 1 => 'Conditional Access Table', + 2 => 'Transport Stream Description Table', + 0x1fff => 'Null Packet', + ); + my %didPID = ( 1 => 0, 2 => 0, 0x1fff => 0 ); + my %needPID = ( 0 => 1 ); # lookup for stream PID's that we still need to parse + # PID's that may contain GPS info + my %gpsPID = ( + 0x0300 => 1, # Novatek INNOVV + 0x01e4 => 1, # vsys a6l dashcam + 0x0e1b => 1, # Jomise T860S-GM dashcam + ); + my $pEnd = 0; + + # scan entire file for GPS programs if ExtractEmbedded option is 3 or higher + # (some dashcams write these programs but don't include it in the PMT) + if (($et->Options('ExtractEmbedded') || 0) > 2) { + foreach (keys %gpsPID) { + $needPID{$_} = 1; + $pidType{$_} = -1; + $pidName{$_} ='unregistered dashcam GPS'; + } + } + + # parse packets from MPEG-2 Transport Stream + for (;;) { + + unless (%needPID) { + last unless defined $startTime; + # reconfigure to seek backwards for last PCR + unless (defined $backScan) { + my $saveTime = $endTime; + undef $endTime; + last if $et->Options('FastScan'); + $verbose and print $out "[Starting backscan for last PCR]\n"; + # remember how far we got when reading forward through the file + my $fwdPos = $raf->Tell() - length($buff) + $pEnd; + # determine the position of the last packet relative to the EOF + $raf->Seek(0, 2) or last; + my $fsize = $raf->Tell(); + $backScan = int($fsize / $pLen) * $pLen - $fsize; + # set limit on how far back we will go + $maxBack = $fwdPos - $fsize; + # scan back a maximum of 512k (have seen last PCR at -276k) + my $nMax = int(512000 / $pLen); # max packets to backscan + if ($nMax < int(-$maxBack / $pLen)) { + $maxBack = $backScan - $nMax * $pLen; + } else { + # use this time if none found in all remaining packets + $fwdTime = $saveTime; + } + $pEnd = 0; + } + } + my $pos; + # read more if necessary + if (defined $backScan) { + last if defined $endTime; + $pos = $pEnd = $pEnd - 2 * $pLen; # step back to previous packet + if ($pos < 0) { + # read another buffer from end of file + last if $backScan <= $maxBack; + my $buffLen = $backScan - $maxBack; + $buffLen = $readSize if $buffLen > $readSize; + $backScan -= $buffLen; + $raf->Seek($backScan, 2) or last; + $raf->Read($buff, $buffLen) == $buffLen or last; + $pos = $pEnd = $buffLen - $pLen; + } + } else { + $pos = $pEnd; + if ($pos + $pLen > length $buff) { + $raf->Read($buff, $readSize) >= $pLen or $eof = 1, last; + $pos = $pEnd = 0; + } + } + $pEnd += $pLen; + # decode the packet prefix + $pos += $prePos; + my $prefix = unpack("x${pos}N", $buff); # (use unpack instead of Get32u for speed) + # validate sync byte + unless (($prefix & 0xff000000) == 0x47000000) { + $et->Warn('M2TS synchronization error') unless defined $backScan; + last; + } + # my $transport_error_indicator = $prefix & 0x00800000; + my $payload_unit_start_indicator = $prefix & 0x00400000; + # my $transport_priority = $prefix & 0x00200000; + my $pid =($prefix & 0x001fff00) >> 8; # packet ID + # my $transport_scrambling_control = $prefix & 0x000000c0; + my $adaptation_field_exists = $prefix & 0x00000020; + my $payload_data_exists = $prefix & 0x00000010; + # my $continuity_counter = $prefix & 0x0000000f; + if ($verbose > 1) { + my $i = ($raf->Tell() - length($buff) + $pEnd) / $pLen - 1; + print $out "Transport packet $i:\n"; + $et->VerboseDump(\$buff, Len => $pLen, Addr => $i * $pLen, Start => $pos - $prePos); + my $str = $pidName{$pid} ? " ($pidName{$pid})" : ' <not in Program Map Table!>'; + printf $out " Timecode: 0x%.4x\n", Get32u(\$buff, $pos - $prePos) if $pLen == 192; + printf $out " Packet ID: 0x%.4x$str\n", $pid; + printf $out " Start Flag: %s\n", $payload_unit_start_indicator ? 'Yes' : 'No'; + } + + $pos += 4; + # handle adaptation field + if ($adaptation_field_exists) { + my $len = Get8u(\$buff, $pos++); + $pos + $len > $pEnd and $et->Warn('Invalid adaptation field length'), last; + # read PCR value for calculation of Duration + if ($len > 6) { + my $flags = Get8u(\$buff, $pos); + if ($flags & 0x10) { # PCR_flag + # combine 33-bit program_clock_reference_base and 9-bit extension + my $pcrBase = Get32u(\$buff, $pos + 1); + my $pcrExt = Get16u(\$buff, $pos + 5); + # ignore separate programs (PID's) and store just the + # first and last timestamps found in the file (is this OK?) + $endTime = 300 * (2 * $pcrBase + ($pcrExt >> 15)) + ($pcrExt & 0x01ff); + $startTime = $endTime unless defined $startTime; + } + } + $pos += $len; + } + + # all done with this packet unless it carries a payload + # or if we are just looking for the last timestamp + next unless $payload_data_exists and not defined $backScan; + + # decode payload data + if ($pid == 0 or # program association table + defined $pmt{$pid}) # program map table(s) + { + # must interpret pointer field if payload_unit_start_indicator is set + my $buf2; + if ($payload_unit_start_indicator) { + # skip to start of section + my $pointer_field = Get8u(\$buff, $pos); + $pos += 1 + $pointer_field; + $pos >= $pEnd and $et->Warn('Bad pointer field'), last; + $buf2 = substr($buff, $pEnd-$pLen, $pLen); + $pos -= $pEnd - $pLen; + } else { + # not the start of a section + next unless $sectLen{$pid}; + my $more = $sectLen{$pid} - length($data{$pid}); + my $size = $pLen - $pos; + $size = $more if $size > $more; + $data{$pid} .= substr($buff, $pos, $size); + next unless $size == $more; + # we have the complete section now, so put into $buf2 for parsing + $buf2 = $data{$pid}; + $pos = 0; + delete $data{$pid}; + delete $fromStart{$pid}; + delete $sectLen{$pid}; + } + my $slen = length($buf2); # section length + $pos + 8 > $slen and $et->Warn('Truncated payload'), last; + # validate table ID + my $table_id = Get8u(\$buf2, $pos); + my $name = ($tableID{$table_id} || sprintf('Unknown (0x%x)',$table_id)) . ' Table'; + my $expectedID = $pid ? 0x02 : 0x00; + unless ($table_id == $expectedID) { + $verbose > 1 and print $out " (skipping $name)\n"; + delete $needPID{$pid}; + $didPID{$pid} = 1; + next; + } + # validate section syntax indicator for parsed tables (PAT, PMT) + my $section_syntax_indicator = Get8u(\$buf2, $pos + 1) & 0xc0; + $section_syntax_indicator == 0x80 or $et->Warn("Bad $name"), last; + my $section_length = Get16u(\$buf2, $pos + 1) & 0x0fff; + $section_length > 1021 and $et->Warn("Invalid $name length"), last; + if ($slen < $section_length + 3) { # (3 bytes for table_id + section_length) + # must wait until we have the full section + $data{$pid} = substr($buf2, $pos); + $sectLen{$pid} = $section_length + 3; + next; + } + my $program_number = Get16u(\$buf2, $pos + 3); + my $section_number = Get8u(\$buf2, $pos + 6); + my $last_section_number = Get8u(\$buf2, $pos + 7); + if ($verbose > 1) { + print $out " $name length: $section_length\n"; + print $out " Program No: $program_number\n" if $pid; + printf $out " Stream ID: 0x%x\n", $program_number if not $pid; + print $out " Section No: $section_number\n"; + print $out " Last Sect.: $last_section_number\n"; + } + my $end = $pos + $section_length + 3 - 4; # (don't read 4-byte CRC) + $pos += 8; + if ($pid == 0) { + # decode PAT (Program Association Table) + while ($pos <= $end - 4) { + my $program_number = Get16u(\$buf2, $pos); + my $program_map_PID = Get16u(\$buf2, $pos + 2) & 0x1fff; + $pmt{$program_map_PID} = $program_number; # save our PMT PID's + my $str = "Program $program_number Map"; + $pidName{$program_map_PID} = $str; + $needPID{$program_map_PID} = 1 unless $didPID{$program_map_PID}; + $verbose and printf $out " PID(0x%.4x) --> $str\n", $program_map_PID; + $pos += 4; + } + } else { + # decode PMT (Program Map Table) + $pos + 4 > $slen and $et->Warn('Truncated PMT'), last; + my $pcr_pid = Get16u(\$buf2, $pos) & 0x1fff; + my $program_info_length = Get16u(\$buf2, $pos + 2) & 0x0fff; + my $str = "Program $program_number Clock Reference"; + $pidName{$pcr_pid} = $str; + $verbose and printf $out " PID(0x%.4x) --> $str\n", $pcr_pid; + $pos += 4; + $pos + $program_info_length > $slen and $et->Warn('Truncated program info'), last; + # dump program information descriptors if verbose + if ($verbose > 1) { for ($j=0; $j<$program_info_length-2; ) { + my $descriptor_tag = Get8u(\$buf2, $pos + $j); + my $descriptor_length = Get8u(\$buf2, $pos + $j + 1); + $j += 2; + last if $j + $descriptor_length > $program_info_length; + my $desc = substr($buf2, $pos+$j, $descriptor_length); + $j += $descriptor_length; + $desc =~ s/([\x00-\x1f\x80-\xff])/sprintf("\\x%.2x",ord $1)/eg; + printf $out " Program Descriptor: Type=0x%.2x \"$desc\"\n", $descriptor_tag; + }} + $pos += $program_info_length; # skip descriptors (for now) + while ($pos <= $end - 5) { + my $stream_type = Get8u(\$buf2, $pos); + my $elementary_pid = Get16u(\$buf2, $pos + 1) & 0x1fff; + my $es_info_length = Get16u(\$buf2, $pos + 3) & 0x0fff; + my $str = $streamType{$stream_type}; + $str or $str = ($stream_type < 0x7f ? 'Reserved' : 'Private'); + $str = sprintf('%s (0x%.2x)', $str, $stream_type); + $str = "Program $program_number $str"; + $verbose and printf $out " PID(0x%.4x) --> $str\n", $elementary_pid; + if ($str =~ /(Audio|Video)/) { + unless ($pidName{$elementary_pid}) { + $et->HandleTag($tagTablePtr, $1 . 'StreamType', $stream_type) + } + # we want to parse all Audio and Video streams + $needPID{$elementary_pid} = 1 unless $didPID{$elementary_pid}; + } + # save PID type and name string + $pidName{$elementary_pid} = $str; + $pidType{$elementary_pid} = $stream_type; + $pos += 5; + $pos + $es_info_length > $slen and $et->Warn('Truncated ES info'), $pos = $end, last; + # parse elementary stream descriptors + for ($j=0; $j<$es_info_length-2; ) { + my $descriptor_tag = Get8u(\$buf2, $pos + $j); + my $descriptor_length = Get8u(\$buf2, $pos + $j + 1); + $j += 2; + last if $j + $descriptor_length > $es_info_length; + my $desc = substr($buf2, $pos+$j, $descriptor_length); + $j += $descriptor_length; + if ($verbose > 1) { + my $dstr = $desc; + $dstr =~ s/([\x00-\x1f\x80-\xff])/sprintf("\\x%.2x",ord $1)/eg; + printf $out " ES Descriptor: Type=0x%.2x \"$dstr\"\n", $descriptor_tag; + } + # parse type-specific descriptor information (once) + unless ($didPID{$pid}) { + if ($descriptor_tag == 0x81) { # AC-3 + ParseAC3Descriptor($et, \$desc); + } + } + } + $pos += $es_info_length; + } + } + # $pos = $end + 4; # skip CRC + + } elsif (not defined $didPID{$pid}) { + + # save data from the start of each elementary stream + if ($payload_unit_start_indicator) { + if (defined $data{$pid}) { + # we must have a whole section, so parse now + my $more = ParsePID($et, $pid, $pidType{$pid}, $pidName{$pid}, \$data{$pid}); + # start fresh even if we couldn't process this PID yet + delete $data{$pid}; + delete $fromStart{$pid}; + unless ($more) { + delete $needPID{$pid}; + $didPID{$pid} = 1; + next; + } + # set flag indicating we found this PID but we still want more + $needPID{$pid} = -1; + } + # check for a PES header + next if $pos + 6 > $pEnd; + my $start_code = Get32u(\$buff, $pos); + next unless ($start_code & 0xffffff00) == 0x00000100; + my $stream_id = $start_code & 0xff; + my $pes_packet_length = Get16u(\$buff, $pos + 4); + if ($verbose > 1) { + printf $out " Stream ID: 0x%.2x\n", $stream_id; + print $out " Packet Len: $pes_packet_length\n"; + } + $pos += 6; + unless ($noSyntax{$stream_id}) { + next if $pos + 3 > $pEnd; + # validate PES syntax + my $syntax = Get8u(\$buff, $pos) & 0xc0; + $syntax == 0x80 or $et->Warn('Bad PES syntax'), next; + # skip PES header + my $pes_header_data_length = Get8u(\$buff, $pos + 2); + $pos += 3 + $pes_header_data_length; + next if $pos >= $pEnd; + } + $data{$pid} = substr($buff, $pos, $pEnd-$pos); + # set flag that we read this payload from the start + $fromStart{$pid} = 1; + # save the packet length + if ($pes_packet_length > 8) { + $packLen{$pid} = $pes_packet_length - 8; # (where are the 8 extra bytes? - PH) + } else { + delete $packLen{$pid}; + } + } else { + unless (defined $data{$pid}) { + # (vsys a6l dashcam GPS record doesn't have a start indicator) + next unless $gpsPID{$pid}; + $data{$pid} = ''; + } + # accumulate data for each elementary stream + $data{$pid} .= substr($buff, $pos, $pEnd-$pos); + } + # save only the first 256 bytes of most streams, except for + # unknown, H.264 or metadata streams where we save up to 1 kB + my $saveLen; + if (not $pidType{$pid} or $pidType{$pid} == 0x1b) { + $saveLen = 1024; + } elsif ($pidType{$pid} == 0x15) { + # use 1024 or actual size of metadata packet if smaller + $saveLen = 1024; + $saveLen = $packLen{$pid} if defined $packLen{$pid} and $saveLen > $packLen{$pid}; + } else { + $saveLen = 256; + } + if (length($data{$pid}) >= $saveLen) { + my $more = ParsePID($et, $pid, $pidType{$pid}, $pidName{$pid}, \$data{$pid}); + next if $more < 0; # wait for program map table (hopefully not too long) + # don't stop parsing if we weren't successful and may have missed the start + $more = 1 if not $more and not $fromStart{$pid}; + delete $data{$pid}; + delete $fromStart{$pid}; + $more and $needPID{$pid} = -1, next; # parse more of these + delete $needPID{$pid}; + $didPID{$pid} = 1; + } + next; + } + if ($needPID{$pid}) { + # we found and parsed a section with this PID, so + # delete from the lookup of PID's we still need to parse + delete $needPID{$pid}; + $didPID{$pid} = 1; + } + } + + # calculate Duration if available + $endTime = $fwdTime unless defined $endTime; + if (defined $startTime and defined $endTime) { + $endTime += 0x80000000 * 1200 if $startTime > $endTime; # handle 33-bit wrap + $et->HandleTag($tagTablePtr, 'Duration', $endTime - $startTime); + } + + if ($verbose) { + my @need; + foreach (keys %needPID) { + push @need, sprintf('0x%.2x',$_) if $needPID{$_} > 0; + } + if (@need) { + @need = sort @need; + print $out "End of file. Missing PID(s): @need\n"; + } else { + my $what = $eof ? 'of file' : 'scan'; + print $out "End $what. All PID's parsed.\n"; + } + } + + # parse any remaining partial PID streams + my $pid; + foreach $pid (sort keys %data) { + ParsePID($et, $pid, $pidType{$pid}, $pidName{$pid}, \$data{$pid}); + delete $data{$pid}; + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::M2TS - Read M2TS (AVCHD) meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +information from MPEG-2 transport streams, such as those used by AVCHD +video. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://neuron2.net/library/mpeg2/iso13818-1.pdf> + +=item L<http://www.blu-raydisc.com/Assets/Downloadablefile/BD-RE_Part3_V2.1_WhitePaper_080406-15271.pdf> + +=item L<http://www.videohelp.com/forum/archive/reading-avchd-playlist-files-bdmv-playlist-mpl-t358888.html> + +=item L<http://en.wikipedia.org/wiki/MPEG_transport_stream> + +=item L<http://www.dunod.com/documents/9782100493463/49346_DVB.pdf> + +=item L<http://trac.handbrake.fr/browser/trunk/libhb/stream.c> + +=item L<http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=04560141> + +=item L<http://www.w6rz.net/xport.zip> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/M2TS Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MIE.pm b/ExifTool/lib/Image/ExifTool/MIE.pm new file mode 100644 index 0000000..6085f48 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MIE.pm @@ -0,0 +1,2574 @@ +#------------------------------------------------------------------------------ +# File: MIE.pm +# +# Description: Read/write MIE meta information +# +# Revisions: 11/18/2005 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::MIE; + +use strict; +use vars qw($VERSION %tableDefaults); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::GPS; + +$VERSION = '1.51'; + +sub ProcessMIE($$); +sub ProcessMIEGroup($$$); +sub WriteMIEGroup($$$); +sub CheckMIE($$$); +sub GetLangInfo($$); + +# local variables +my $hasZlib; # 1=Zlib available, 0=no Zlib +my %mieCode; # reverse lookup for MIE format names +my $doneMieMap; # flag indicating we added user-defined groups to %mieMap + +# MIE format codes +my %mieFormat = ( + 0x00 => 'undef', + 0x10 => 'MIE', + 0x18 => 'MIE', + 0x20 => 'string', # ASCII (ISO 8859-1) + 0x28 => 'utf8', + 0x29 => 'utf16', + 0x2a => 'utf32', + 0x30 => 'string_list', + 0x38 => 'utf8_list', + 0x39 => 'utf16_list', + 0x3a => 'utf32_list', + 0x40 => 'int8u', + 0x41 => 'int16u', + 0x42 => 'int32u', + 0x43 => 'int64u', + 0x48 => 'int8s', + 0x49 => 'int16s', + 0x4a => 'int32s', + 0x4b => 'int64s', + 0x52 => 'rational32u', + 0x53 => 'rational64u', + 0x5a => 'rational32s', + 0x5b => 'rational64s', + 0x61 => 'fixed16u', + 0x62 => 'fixed32u', + 0x69 => 'fixed16s', + 0x6a => 'fixed32s', + 0x72 => 'float', + 0x73 => 'double', + 0x80 => 'free', +); + +# map of MIE directory locations +my %mieMap = ( + 'MIE-Meta' => 'MIE', + 'MIE-Audio' => 'MIE-Meta', + 'MIE-Camera' => 'MIE-Meta', + 'MIE-Doc' => 'MIE-Meta', + 'MIE-Geo' => 'MIE-Meta', + 'MIE-Image' => 'MIE-Meta', + 'MIE-MakerNotes' => 'MIE-Meta', + 'MIE-Preview' => 'MIE-Meta', + 'MIE-Thumbnail' => 'MIE-Meta', + 'MIE-Video' => 'MIE-Meta', + 'MIE-Flash' => 'MIE-Camera', + 'MIE-Lens' => 'MIE-Camera', + 'MIE-Orient' => 'MIE-Camera', + 'MIE-Extender' => 'MIE-Lens', + 'MIE-GPS' => 'MIE-Geo', + 'MIE-UTM' => 'MIE-Geo', + 'MIE-Canon' => 'MIE-MakerNotes', + EXIF => 'MIE-Meta', + XMP => 'MIE-Meta', + IPTC => 'MIE-Meta', + ICC_Profile => 'MIE-Meta', + ID3 => 'MIE-Meta', + CanonVRD => 'MIE-Canon', + IFD0 => 'EXIF', + IFD1 => 'IFD0', + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', +); + +# convenience variables for common tagInfo entries +my %binaryConv = ( + Writable => 'undef', + Binary => 1, +); +my %dateInfo = ( + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', +); +my %noYes = ( 0 => 'No', 1 => 'Yes' ); +my %offOn = ( 0 => 'Off', 1 => 'On' ); + +# default entries for MIE tag tables +%tableDefaults = ( + PROCESS_PROC => \&ProcessMIE, + WRITE_PROC => \&ProcessMIE, + CHECK_PROC => \&CheckMIE, + LANG_INFO => \&GetLangInfo, + WRITABLE => 'string', + PREFERRED => 1, +); + +# MIE info +%Image::ExifTool::MIE::Main = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Main' }, + WRITE_GROUP => 'MIE-Main', + NOTES => q{ + MIE is a flexible format which may be used as a stand-alone meta information + format, for encapsulation of other files and information, or as a trailer + appended to other file formats. The tables below represent currently + defined MIE tags, however ExifTool will also extract any other information + present in a MIE file. + + When writing MIE information, some special features are supported: + + 1) String values may be written as ASCII (ISO 8859-1) or UTF-8. ExifTool + automatically detects the presence of wide characters and treats the string + appropriately. Internally, UTF-8 text may be converted to UTF-16 or UTF-32 + and stored in this format in the file if it is more compact. + + 2) All MIE string-value tags support localized text. Localized values are + written by adding a language/country code to the tag name in the form + C<TAG-xx_YY>, where C<TAG> is the tag name, C<xx> is a 2-character lower + case ISO 639-1 language code, and C<YY> is a 2-character upper case ISO + 3166-1 alpha 2 country code (eg. C<Title-en_US>). But as usual, the user + interface is case-insensitive, and ExifTool will write the correct case to + the file. + + 3) Some numerical MIE tags allow units of measurement to be specified. For + these tags, units may be added in brackets immediately following the value + (eg. C<55(mi/h)>). If no units are specified, the default units are + written. + + 4) ExifTool writes compressed metadata to MIE files if the L<Compress|../ExifTool.html#Compress> (-z) + option is used and Compress::Zlib is available. + + See L<https://exiftool.org/MIE1.1-20070121.pdf> for the official MIE + specification. + }, + '0Type' => { + Name => 'SubfileType', + Notes => q{ + the capitalized common extension for this type of file. If the extension + has a dot-3 abbreviation, then the longer version is used here. For + instance, JPEG and TIFF are used, not JPG and TIF + }, + }, + '0Vers' => { + Name => 'MIEVersion', + Notes => 'version 1.1 is assumed if not specified', + }, + '1Directory' => { + Name => 'SubfileDirectory', + Notes => 'original directory for the file', + }, + '1Name' => { + Name => 'SubfileName', + Notes => 'the file name, including extension if it exists', + }, + '2MIME' => { Name => 'SubfileMIMEType' }, + Meta => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Meta', + DirName => 'MIE-Meta', + }, + }, + data => { + Name => 'SubfileData', + Notes => 'the subfile data', + %binaryConv, + }, + rsrc => { + Name => 'SubfileResource', + Notes => 'subfile resource fork if it exists', + %binaryConv, + }, + zmd5 => { + Name => 'MD5Digest', + Notes => q{ + 16-byte MD5 digest written in binary form or as a 32-character hex-encoded + ASCII string. Value is an MD5 digest of the entire 0MIE group as it would be + with the digest value itself set to all null bytes + }, + }, + zmie => { + Name => 'TrailerSignature', + Writable => 'undef', + Notes => q{ + used as the last element in the main "0MIE" group to identify a MIE trailer + when appended to another type of file. ExifTool will create this tag if set + to any value, but always with an empty data block + }, + ValueConvInv => '""', # data block must be empty + }, +); + +# MIE meta information group +%Image::ExifTool::MIE::Meta = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Meta', 2 => 'Image' }, + WRITE_GROUP => 'MIE-Meta', + Audio => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Audio', + DirName => 'MIE-Audio', + }, + }, + Camera => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Camera', + DirName => 'MIE-Camera', + }, + }, + Document => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Doc', + DirName => 'MIE-Doc', + }, + }, + EXIF => { + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + }, + }, + Geo => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Geo', + DirName => 'MIE-Geo', + }, + }, + ICCProfile => { + Name => 'ICC_Profile', + SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' }, + }, + ID3 => { SubDirectory => { TagTable => 'Image::ExifTool::ID3::Main' } }, + IPTC => { SubDirectory => { TagTable => 'Image::ExifTool::IPTC::Main' } }, + Image => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Image', + DirName => 'MIE-Image', + }, + }, + MakerNotes => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::MakerNotes', + DirName => 'MIE-MakerNotes', + }, + }, + Preview => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Preview', + DirName => 'MIE-Preview', + }, + }, + Thumbnail => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Thumbnail', + DirName => 'MIE-Thumbnail', + }, + }, + Video => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Video', + DirName => 'MIE-Video', + }, + }, + XMP => { SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' } }, +); + +# MIE document information +%Image::ExifTool::MIE::Doc = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Doc', 2 => 'Document' }, + WRITE_GROUP => 'MIE-Doc', + NOTES => 'Information describing the main document, image or file.', + Author => { Groups => { 2 => 'Author' } }, + Comment => { }, + Contributors=> { Groups => { 2 => 'Author' }, List => 1 }, + Copyright => { Groups => { 2 => 'Author' } }, + CreateDate => { Groups => { 2 => 'Time' }, %dateInfo }, + EMail => { Name => 'Email', Groups => { 2 => 'Author' } }, + Keywords => { List => 1 }, + ModifyDate => { Groups => { 2 => 'Time' }, %dateInfo }, + OriginalDate=> { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + %dateInfo, + }, + Phone => { Name => 'PhoneNumber', Groups => { 2 => 'Author' } }, + References => { List => 1 }, + Software => { }, + Title => { }, + URL => { }, +); + +# MIE geographic information +%Image::ExifTool::MIE::Geo = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Geo', 2 => 'Location' }, + WRITE_GROUP => 'MIE-Geo', + NOTES => 'Information related to geographic location.', + Address => { }, + City => { }, + Country => { }, + GPS => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::GPS', + DirName => 'MIE-GPS', + }, + }, + PostalCode => { }, + State => { Notes => 'state or province' }, + UTM => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::UTM', + DirName => 'MIE-UTM', + }, + }, +); + +# MIE GPS information +%Image::ExifTool::MIE::GPS = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-GPS', 2 => 'Location' }, + WRITE_GROUP => 'MIE-GPS', + Altitude => { + Name => 'GPSAltitude', + Writable => 'rational64s', + Units => [ qw(m ft) ], + Notes => q{'m' above sea level unless 'ft' specified}, + }, + Bearing => { + Name => 'GPSDestBearing', + Writable => 'rational64s', + Units => [ qw(deg deg{mag}) ], + Notes => q{'deg' CW from true north unless 'deg{mag}' specified}, + }, + Datum => { Name => 'GPSMapDatum', Notes => 'WGS-84 assumed if not specified' }, + Differential => { + Name => 'GPSDifferential', + Writable => 'int8u', + PrintConv => { + 0 => 'No Correction', + 1 => 'Differential Corrected', + }, + }, + Distance => { + Name => 'GPSDestDistance', + Writable => 'rational64s', + Units => [ qw(km mi nmi) ], + Notes => q{'km' unless 'mi' or 'nmi' specified}, + }, + Heading => { + Name => 'GPSTrack', + Writable => 'rational64s', + Units => [ qw(deg deg{mag}) ], + Notes => q{'deg' CW from true north unless 'deg{mag}' specified}, + }, + Latitude => { + Name => 'GPSLatitude', + Writable => 'rational64s', + Count => -1, + Notes => q{ + 1 to 3 numbers: degrees, minutes then seconds. South latitudes are stored + as all negative numbers, but may be entered as positive numbers with a + trailing 'S' for convenience. For example, these are all equivalent: "-40 + -30", "-40.5", "40 30 0.00 S" + }, + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)', + ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 3)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")', + }, + Longitude => { + Name => 'GPSLongitude', + Writable => 'rational64s', + Count => -1, + Notes => q{ + 1 to 3 numbers: degrees, minutes then seconds. West longitudes are + negative, but may be entered as positive numbers with a trailing 'W' + }, + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)', + ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 3)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")', + }, + MeasureMode => { + Name => 'GPSMeasureMode', + Writable => 'int8u', + PrintConv => { 2 => '2-D', 3 => '3-D' }, + }, + Satellites => 'GPSSatellites', + Speed => { + Name => 'GPSSpeed', + Writable => 'rational64s', + Units => [ qw(km/h mi/h m/s kn) ], + Notes => q{'km/h' unless 'mi/h', 'm/s' or 'kn' specified}, + }, + DateTime => { Name => 'GPSDateTime', Groups => { 2 => 'Time' }, %dateInfo }, +); + +# MIE UTM information +%Image::ExifTool::MIE::UTM = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-UTM', 2 => 'Location' }, + WRITE_GROUP => 'MIE-UTM', + Datum => { Name => 'UTMMapDatum', Notes => 'WGS-84 assumed if not specified' }, + Easting => { Name => 'UTMEasting' }, + Northing => { Name => 'UTMNorthing' }, + Zone => { Name => 'UTMZone', Writable => 'int8s' }, +); + +# MIE image information +%Image::ExifTool::MIE::Image = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Image', 2 => 'Image' }, + WRITE_GROUP => 'MIE-Image', + '0Type' => { Name => 'FullSizeImageType', Notes => 'JPEG if not specified' }, + '1Name' => { Name => 'FullSizeImageName' }, + BitDepth => { Name => 'BitDepth', Writable => 'int16u' }, + ColorSpace => { Notes => 'standard ColorSpace values are "sRGB" and "Adobe RGB"' }, + Components => { + Name => 'ComponentsConfiguration', + Notes => 'string composed of R, G, B, Y, Cb and Cr', + }, + Compression => { Name => 'CompressionRatio', Writable => 'rational32u' }, + OriginalImageSize => { # PH added 2022-09-28 + Writable => 'int16u', + Count => -1, + Notes => 'size of original image before cropping', + PrintConv => '$val=~tr/ /x/;$val', + PrintConvInv => '$val=~tr/x/ /;$val', + }, + ImageSize => { + Writable => 'int16u', + Count => -1, + Notes => '2 or 3 values, for number of XY or XYZ pixels', + PrintConv => '$val=~tr/ /x/;$val', + PrintConvInv => '$val=~tr/x/ /;$val', + }, + Resolution => { + Writable => 'rational64u', + Units => [ qw(/in /cm /deg /arcmin /arcsec), '' ], + Count => -1, + Notes => q{ + 1 to 3 values. A single value for equal resolution in all directions, or + separate X, Y and Z values if necessary. Units are '/in' unless '/cm', + '/deg', '/arcmin', '/arcsec' or '' specified + }, + PrintConv => '$val=~tr/ /x/;$val', + PrintConvInv => '$val=~tr/x/ /;$val', + }, + data => { + Name => 'FullSizeImage', + Groups => { 2 => 'Preview' }, + %binaryConv, + RawConv => '$self->ValidateImage(\$val,$tag)', + }, +); + +# MIE preview image +%Image::ExifTool::MIE::Preview = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Preview', 2 => 'Image' }, + WRITE_GROUP => 'MIE-Preview', + '0Type' => { Name => 'PreviewImageType', Notes => 'JPEG if not specified' }, + '1Name' => { Name => 'PreviewImageName' }, + ImageSize => { + Name => 'PreviewImageSize', + Writable => 'int16u', + Count => -1, + Notes => '2 or 3 values, for number of XY or XYZ pixels', + PrintConv => '$val=~tr/ /x/;$val', + PrintConvInv => '$val=~tr/x/ /;$val', + }, + data => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + %binaryConv, + RawConv => '$self->ValidateImage(\$val,$tag)', + }, +); + +# MIE thumbnail image +%Image::ExifTool::MIE::Thumbnail = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Thumbnail', 2 => 'Image' }, + WRITE_GROUP => 'MIE-Thumbnail', + '0Type' => { Name => 'ThumbnailImageType', Notes => 'JPEG if not specified' }, + '1Name' => { Name => 'ThumbnailImageName' }, + ImageSize => { + Name => 'ThumbnailImageSize', + Writable => 'int16u', + Count => -1, + Notes => '2 or 3 values, for number of XY or XYZ pixels', + PrintConv => '$val=~tr/ /x/;$val', + PrintConvInv => '$val=~tr/x/ /;$val', + }, + data => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + %binaryConv, + RawConv => '$self->ValidateImage(\$val,$tag)', + }, +); + +# MIE audio information +%Image::ExifTool::MIE::Audio = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Audio', 2 => 'Audio' }, + WRITE_GROUP => 'MIE-Audio', + NOTES => q{ + For the Audio group (and any other group containing a 'data' element), tags + refer to the contained data if present, otherwise they refer to the main + SubfileData. The C<0Type> and C<1Name> elements should exist only if C<data> + is present. + }, + '0Type' => { Name => 'RelatedAudioFileType', Notes => 'MP3 if not specified' }, + '1Name' => { Name => 'RelatedAudioFileName' }, + SampleBits => { Writable => 'int16u' }, + Channels => { Writable => 'int8u' }, + Compression => { Name => 'AudioCompression' }, + Duration => { Writable => 'rational64u', PrintConv => 'ConvertDuration($val)' }, + SampleRate => { Writable => 'int32u' }, + data => { Name => 'RelatedAudioFile', %binaryConv }, +); + +# MIE video information +%Image::ExifTool::MIE::Video = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Video', 2 => 'Video' }, + WRITE_GROUP => 'MIE-Video', + '0Type' => { Name => 'RelatedVideoFileType', Notes => 'MOV if not specified' }, + '1Name' => { Name => 'RelatedVideoFileName' }, + Codec => { }, + Duration => { Writable => 'rational64u', PrintConv => 'ConvertDuration($val)' }, + data => { Name => 'RelatedVideoFile', %binaryConv }, +); + +# MIE camera information +%Image::ExifTool::MIE::Camera = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Camera', 2 => 'Camera' }, + WRITE_GROUP => 'MIE-Camera', + Brightness => { Writable => 'int8s' }, + ColorTemperature=> { Writable => 'int32u' }, + ColorBalance => { + Writable => 'rational64u', + Count => 3, + Notes => 'RGB scaling factors', + }, + Contrast => { Writable => 'int8s' }, + DigitalZoom => { Writable => 'rational64u' }, + ExposureComp => { Name => 'ExposureCompensation', Writable => 'rational64s' }, + ExposureMode => { }, + ExposureTime => { + Writable => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => '$val', + }, + Flash => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Flash', + DirName => 'MIE-Flash', + }, + }, + FirmwareVersion => { }, + FocusMode => { }, + ISO => { Writable => 'int16u' }, + ISOSetting => { + Writable => 'int16u', + Notes => '0 = Auto, otherwise manual ISO speed setting', + }, + ImageNumber => { Writable => 'int32u' }, + ImageQuality => { Notes => 'Economy, Normal, Fine, Super Fine or Raw' }, + ImageStabilization => { Writable => 'int8u', %offOn }, + Lens => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Lens', + DirName => 'MIE-Lens', + }, + }, + Make => { }, + MeasuredEV => { Writable => 'rational64s' }, + Model => { }, + OwnerName => { }, + Orientation => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Orient', + DirName => 'MIE-Orient', + }, + }, + Saturation => { Writable => 'int8s' }, + SensorSize => { + Writable => 'rational64u', + Count => 2, + Notes => 'width and height of active sensor area in mm', + }, + SerialNumber => { }, + Sharpness => { Writable => 'int8s' }, + ShootingMode => { }, +); + +# Camera orientation information +%Image::ExifTool::MIE::Orient = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Orient', 2 => 'Camera' }, + WRITE_GROUP => 'MIE-Orient', + NOTES => 'These tags describe the camera orientation.', + Azimuth => { + Writable => 'rational64s', + Units => [ qw(deg deg{mag}) ], + Notes => q{'deg' CW from true north unless 'deg{mag}' specified}, + }, + Declination => { Writable => 'rational64s' }, + Elevation => { Writable => 'rational64s' }, + RightAscension => { Writable => 'rational64s' }, + Rotation => { + Writable => 'rational64s', + Notes => 'CW rotation angle of camera about lens axis', + }, +); + +# MIE camera lens information +%Image::ExifTool::MIE::Lens = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Lens', 2 => 'Camera' }, + WRITE_GROUP => 'MIE-Lens', + NOTES => q{ + All recorded lens parameters (focal length, aperture, etc) include the + effects of the extender if present. + }, + Extender => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Extender', + DirName => 'MIE-Extender', + }, + }, + FNumber => { Writable => 'rational64u' }, + FocalLength => { Writable => 'rational64u', Notes => 'all focal lengths in mm' }, + FocusDistance => { + Writable => 'rational64u', + Units => [ qw(m ft) ], + Notes => q{'m' unless 'ft' specified}, + }, + Make => { Name => 'LensMake' }, + MaxAperture => { Writable => 'rational64u' }, + MaxApertureAtMaxFocal => { Writable => 'rational64u' }, + MaxFocalLength => { Writable => 'rational64u' }, + MinAperture => { Writable => 'rational64u' }, + MinFocalLength => { Writable => 'rational64u' }, + Model => { Name => 'LensModel' }, + OpticalZoom => { Writable => 'rational64u' }, + SerialNumber => { Name => 'LensSerialNumber' }, +); + +# MIE lens extender information +%Image::ExifTool::MIE::Extender = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Extender', 2 => 'Camera' }, + WRITE_GROUP => 'MIE-Extender', + Magnification => { Name => 'ExtenderMagnification', Writable => 'rational64s' }, + Make => { Name => 'ExtenderMake' }, + Model => { Name => 'ExtenderModel' }, + SerialNumber => { Name => 'ExtenderSerialNumber' }, +); + +# MIE camera flash information +%Image::ExifTool::MIE::Flash = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Flash', 2 => 'Camera' }, + WRITE_GROUP => 'MIE-Flash', + ExposureComp => { Name => 'FlashExposureComp', Writable => 'rational64s' }, + Fired => { Name => 'FlashFired', Writable => 'int8u', PrintConv => \%noYes }, + GuideNumber => { Name => 'FlashGuideNumber' }, + Make => { Name => 'FlashMake' }, + Mode => { Name => 'FlashMode' }, + Model => { Name => 'FlashModel' }, + SerialNumber => { Name => 'FlashSerialNumber' }, + Type => { Name => 'FlashType', Notes => '"Internal" or "External"' }, +); + +# MIE maker notes information +%Image::ExifTool::MIE::MakerNotes = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-MakerNotes' }, + WRITE_GROUP => 'MIE-MakerNotes', + NOTES => q{ + MIE maker notes are contained within separate groups for each manufacturer + to avoid name conflicts. + }, + Canon => { + SubDirectory => { + TagTable => 'Image::ExifTool::MIE::Canon', + DirName => 'MIE-Canon', + }, + }, + Casio => { SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, + FujiFilm => { SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, + Kodak => { SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, + KonicaMinolta=>{ SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, + Nikon => { SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, + Olympus => { SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, + Panasonic => { SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, + Pentax => { SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, + Ricoh => { SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, + Sigma => { SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, + Sony => { SubDirectory => { TagTable => 'Image::ExifTool::MIE::Unknown' } }, +); + +# MIE Canon-specific information +%Image::ExifTool::MIE::Canon = ( + %tableDefaults, + GROUPS => { 1 => 'MIE-Canon' }, + WRITE_GROUP => 'MIE-Canon', + VRD => { + Name => 'CanonVRD', + SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::Main' }, + }, +); + +%Image::ExifTool::MIE::Unknown = ( + PROCESS_PROC => \&ProcessMIE, + GROUPS => { 1 => 'MIE-Unknown' }, +); + +#------------------------------------------------------------------------------ +# Add user-defined MIE groups to %mieMap +# Inputs: none; Returns: nothing, but sets $doneMieMap flag +sub UpdateMieMap() +{ + $doneMieMap = 1; # set flag so we only do this once + return unless %Image::ExifTool::UserDefined; + my ($tableName, @tables, %doneTable, $tagID); + # get list of top-level MIE tables with user-defined tags + foreach $tableName (keys %Image::ExifTool::UserDefined) { + next unless $tableName =~ /^Image::ExifTool::MIE::/; + my $userTable = $Image::ExifTool::UserDefined{$tableName}; + my $tagTablePtr = GetTagTable($tableName) or next; + # copy the WRITE_GROUP from the actual table + $$userTable{WRITE_GROUP} = $$tagTablePtr{WRITE_GROUP}; + # add to list of tables to process + $doneTable{$tableName} = 1; + push @tables, [$tableName, $userTable]; + } + # recursively add all user-defined groups to MIE map + while (@tables) { + my ($tableName, $tagTablePtr) = @{shift @tables}; + my $parent = $$tagTablePtr{WRITE_GROUP}; + $parent or warn("No WRITE_GROUP for $tableName\n"), next; + $mieMap{$parent} or warn("$parent is not in MIE map\n"), next; + foreach $tagID (TagTableKeys($tagTablePtr)) { + my $tagInfo = $$tagTablePtr{$tagID}; + next unless ref $tagInfo eq 'HASH' and $$tagInfo{SubDirectory}; + my $subTableName = $tagInfo->{SubDirectory}->{TagTable}; + my $subTablePtr = GetTagTable($subTableName) or next; + # only care about MIE tables + next unless $$subTablePtr{PROCESS_PROC} and + $$subTablePtr{PROCESS_PROC} eq \&ProcessMIE; + my $group = $$subTablePtr{WRITE_GROUP}; + $group or warn("No WRITE_GROUP for $subTableName\n"), next; + if ($mieMap{$group} and $mieMap{$group} ne $parent) { + warn("$group already has different parent ($mieMap{$group})\n"), next; + } + $mieMap{$group} = $parent; # add to map + # process tables within this one too + $doneTable{$subTableName} and next; + $doneTable{$subTableName} = 1; + push @tables, [$subTableName, $subTablePtr]; + } + } +} + +#------------------------------------------------------------------------------ +# Get localized version of tagInfo hash +# Inputs: 0) tagInfo hash ref, 1) locale code (eg. "en_CA") +# Returns: new tagInfo hash ref, or undef if invalid +sub GetLangInfo($$) +{ + my ($tagInfo, $langCode) = @_; + # check for properly formatted language code + return undef unless $langCode =~ /^[a-z]{2}([-_])[A-Z]{2}$/; + # use '_' as a separator, but recognize '_' or '-' + $langCode =~ tr/-/_/ if $1 eq '-'; + # can only set locale on string types + return undef if $$tagInfo{Writable} and $$tagInfo{Writable} ne 'string'; + return Image::ExifTool::GetLangInfo($tagInfo, $langCode); +} + +#------------------------------------------------------------------------------ +# return true if we have Zlib::Compress +# Inputs: 0) ExifTool object ref, 1) verb for what you want to do with the info +# Returns: 1 if Zlib available, 0 otherwise +sub HasZlib($$) +{ + unless (defined $hasZlib) { + $hasZlib = eval { require Compress::Zlib }; + unless ($hasZlib) { + $hasZlib = 0; + $_[0]->Warn("Install Compress::Zlib to $_[1] compressed information"); + } + } + return $hasZlib; +} + +#------------------------------------------------------------------------------ +# Get format code for MIE group element with current byte order +# Inputs: 0) [optional] true to convert result to chr() +# Returns: format code +sub MIEGroupFormat(;$) +{ + my $chr = shift; + my $format = GetByteOrder() eq 'MM' ? 0x10 : 0x18; + return $chr ? chr($format) : $format; +} + +#------------------------------------------------------------------------------ +# ReadValue() with added support for UTF formats (utf8, utf16 and utf32) +# Inputs: 0) data reference, 1) value offset, 2) format string, +# 3) number of values (or undef to use all data) +# 4) valid data length relative to offset, 5) returned rational ref +# Returns: converted value, or undefined if data isn't there +# or list of values in list context +# Notes: all string formats are converted to UTF8 +sub ReadMIEValue($$$$$;$) +{ + my ($dataPt, $offset, $format, $count, $size, $ratPt) = @_; + my $val; + if ($format =~ /^(utf(8|16|32)|string)/) { + if ($1 eq 'utf8' or $1 eq 'string') { + # read the 8-bit string + $val = substr($$dataPt, $offset, $size); + # (as of ExifTool 7.62, leave string values unconverted) + } else { + # convert to UTF8 + my $fmt; + if (GetByteOrder() eq 'MM') { + $fmt = ($1 eq 'utf16') ? 'n' : 'N'; + } else { + $fmt = ($1 eq 'utf16') ? 'v' : 'V'; + } + my @unpk = unpack("x$offset$fmt$size",$$dataPt); + if ($] >= 5.006001) { + $val = pack('C0U*', @unpk); + } else { + $val = Image::ExifTool::PackUTF8(@unpk); + } + } + # truncate at null unless this is a list + # (strings shouldn't have a null, but just in case) + $val =~ s/\0.*//s unless $format =~ /_list$/; + } else { + $format = 'undef' if $format eq 'free'; # read 'free' as 'undef' + return ReadValue($dataPt, $offset, $format, $count, $size, $ratPt); + } + return $val; +} + +#------------------------------------------------------------------------------ +# validate raw values for writing +# Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref +# Returns: error string or undef (and possibly changes value) on success +sub CheckMIE($$$) +{ + my ($et, $tagInfo, $valPtr) = @_; + my $format = $$tagInfo{Writable} || $tagInfo->{Table}->{WRITABLE}; + my $err; + + return 'No writable format' if not $format or $format eq '1'; + # handle units if supported by this tag + my $ulist = $$tagInfo{Units}; + if ($ulist and $$valPtr =~ /(.*)\((.*)\)$/) { + my ($val, $units) = ($1, $2); + ($units) = grep /^$units$/i, @$ulist; + defined $units or return 'Allowed units: (' . join('|', @$ulist) . ')'; + $err = Image::ExifTool::CheckValue(\$val, $format, $$tagInfo{Count}); + # add units back onto value + $$valPtr = "$val($units)" unless $err; + } elsif ($format !~ /^(utf|string|undef)/ and $$valPtr =~ /\)$/) { + return 'Units not supported'; + } else { + if ($format eq 'string' and $$et{OPTIONS}{Charset} ne 'UTF8' and + $$valPtr =~ /[\x80-\xff]/) + { + # convert from Charset to UTF-8 + $$valPtr = $et->Encode($$valPtr,'UTF8'); + } + $err = Image::ExifTool::CheckValue($valPtr, $format, $$tagInfo{Count}); + } + return $err; +} + +#------------------------------------------------------------------------------ +# Rewrite a MIE directory +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ptr +# Returns: undef on success, otherwise error message (empty message if nothing to write) +sub WriteMIEGroup($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $outfile = $$dirInfo{OutFile}; + my $dirName = $$dirInfo{DirName}; + my $toWrite = $$dirInfo{ToWrite} || ''; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my $optCompress = $et->Options('Compress'); + my $out = $et->Options('TextOut'); + my ($msg, $err, $ok, $sync, $delGroup); + my $tag = ''; + my $deletedTag = ''; + + # count each MIE directory found and make name for this specific instance + my ($grp1, %isWriting); + my $cnt = $$et{MIE_COUNT}; + my $grp = $tagTablePtr->{GROUPS}->{1}; + my $n = $$cnt{'MIE-Main'} || 0; + if ($grp eq 'MIE-Main') { + $$cnt{$grp} = ++$n; + ($grp1 = $grp) =~ s/MIE-/MIE$n-/; + } else { + ($grp1 = $grp) =~ s/MIE-/MIE$n-/; + my $m = $$cnt{$grp1} = ($$cnt{$grp1} || 0) + 1; + $isWriting{"$grp$m"} = 1; # eg. 'MIE-Doc2' + $isWriting{$grp1} = 1; # eg. 'MIE1-Doc' + $grp1 .= $m; + } + # build lookup for all valid group names for this MIE group + $isWriting{$grp} = 1; # eg. 'MIE-Doc' + $isWriting{$grp1} = 1; # eg. 'MIE1-Doc2' + $isWriting{"MIE$n"} = 1; # eg. 'MIE1' + + # determine if we are deleting this group + if (%{$$et{DEL_GROUP}}) { + $delGroup = 1 if $$et{DEL_GROUP}{MIE} or + $$et{DEL_GROUP}{$grp} or + $$et{DEL_GROUP}{$grp1} or + $$et{DEL_GROUP}{"MIE$n"}; + } + + # prepare lookups and lists for writing + my $newTags = $et->GetNewTagInfoHash($tagTablePtr); + my ($addDirs, $editDirs) = $et->GetAddDirHash($tagTablePtr, $dirName); + my @editTags = sort keys %$newTags, keys %$editDirs; + $verbose and print $out $raf ? 'Writing' : 'Creating', " $grp1:\n"; + + # loop through elements in MIE group + MieElement: for (;;) { + my ($format, $tagLen, $valLen, $units, $oldHdr, $buff); + my $lastTag = $tag; + if ($raf) { + # read first 4 bytes of element header + my $n = $raf->Read($oldHdr, 4); + if ($n != 4) { + last if $n or defined $sync; + undef $raf; # all done reading + $ok = 1; + } + } + if ($raf) { + ($sync, $format, $tagLen, $valLen) = unpack('aC3', $oldHdr); + $sync eq '~' or $msg = 'Invalid sync byte', last; + + # read tag name + if ($tagLen) { + $raf->Read($tag, $tagLen) == $tagLen or last; + $oldHdr .= $tag; # add tag to element header + $et->Warn("MIE tag '${tag}' out of sequence") if $tag lt $lastTag; + # separate units from tag name if they exist + $units = $1 if $tag =~ s/\((.*)\)$//; + } else { + $tag = ''; + } + + # get multi-byte value length if necessary + if ($valLen > 252) { + # calculate number of bytes in extended DataLength + my $n = 1 << (256 - $valLen); + $raf->Read($buff, $n) == $n or last; + $oldHdr .= $buff; # add to old header + my $fmt = 'int' . ($n * 8) . 'u'; + $valLen = ReadValue(\$buff, 0, $fmt, 1, $n); + if ($valLen > 0x7fffffff) { + $msg = "Can't write $tag (DataLength > 2GB not yet supported)"; + last; + } + } + # don't rewrite free bytes or information in deleted groups + if ($format == 0x80 or ($delGroup and $tagLen and ($format & 0xf0) != 0x10)) { + $raf->Seek($valLen, 1) or $msg = 'Seek error', last; + if ($verbose > 1) { + my $free = ($format == 0x80) ? ' free' : ''; + print $out " - $grp1:$tag ($valLen$free bytes)\n"; + } + ++$$et{CHANGED} if $delGroup; + next; + } + } else { + # no more elements to read + $tagLen = $valLen = 0; + $tag = ''; + } +# +# write necessary new tags and process directories +# + while (@editTags) { + last if $tagLen and $editTags[0] gt $tag; + # we are writing the new tag now + my ($newVal, $writable, $oldVal, $newFormat, $compress); + my $newTag = shift @editTags; + length($newTag) > 255 and $et->Warn('Tag name too long'), next; # (just to be safe) + my $newInfo = $$editDirs{$newTag}; + if ($newInfo) { + # create the new subdirectory or rewrite existing non-MIE directory + my $subTablePtr = GetTagTable($newInfo->{SubDirectory}->{TagTable}); + unless ($subTablePtr) { + $et->Warn("No tag table for $newTag $$newInfo{Name}"); + next; + } + my %subdirInfo; + my $isMieGroup = ($$subTablePtr{WRITE_PROC} and + $$subTablePtr{WRITE_PROC} eq \&ProcessMIE); + + if ($newTag eq $tag) { + # make sure that either both or neither old and new tags are MIE groups + if ($isMieGroup xor ($format & 0xf3) == 0x10) { + $et->Warn("Tag '${tag}' not expected type"); + next; # don't write our new tag + } + # uncompress existing directory into $oldVal since we are editing it + if ($format & 0x04) { + last unless HasZlib($et, 'edit'); + $raf->Read($oldVal, $valLen) == $valLen or last MieElement; + my $stat; + my $inflate = Compress::Zlib::inflateInit(); + $inflate and ($oldVal, $stat) = $inflate->inflate($oldVal); + unless ($inflate and $stat == Compress::Zlib::Z_STREAM_END()) { + $msg = "Error inflating $tag"; + last MieElement; + } + $compress = 1; + $valLen = length $oldVal; # uncompressed value length + } + } else { + # don't create this directory unless necessary + next unless $$addDirs{$newTag}; + } + + if ($isMieGroup) { + my $hdr; + if ($newTag eq $tag) { + # rewrite existing directory later unless it was compressed + last unless $compress; + # rewrite directory to '$newVal' + $newVal = ''; + %subdirInfo = ( + OutFile => \$newVal, + RAF => new File::RandomAccess(\$oldVal), + ); + } elsif ($optCompress and not $$dirInfo{IsCompressed}) { + # write to memory so we can compress the new MIE group + $compress = 1; + %subdirInfo = ( + OutFile => \$newVal, + ); + } else { + $hdr = '~' . MIEGroupFormat(1) . chr(length($newTag)) . + "\0" . $newTag; + %subdirInfo = ( + OutFile => $outfile, + ToWrite => $toWrite . $hdr, + ); + } + $subdirInfo{DirName} = $newInfo->{SubDirectory}->{DirName} || $newTag; + $subdirInfo{Parent} = $dirName; + # don't compress elements of an already compressed group + $subdirInfo{IsCompressed} = $$dirInfo{IsCompressed} || $compress; + $msg = WriteMIEGroup($et, \%subdirInfo, $subTablePtr); + last MieElement if $msg; + # message is defined but empty if nothing was written + if (defined $msg) { + undef $msg; # not a problem if nothing was written + next; + } elsif (not $compress) { + # group was written already + $toWrite = ''; + next; + } elsif (length($newVal) <= 4) { # terminator only? + $verbose and print $out "Deleted compressed $grp1 (empty)\n"; + next MieElement if $newTag eq $tag; # deleting the directory + next; # not creating the new directory + } + $writable = 'undef'; + $newFormat = MIEGroupFormat(); + } else { + if ($newTag eq $tag) { + unless ($compress) { + # read and edit existing directory + $raf->Read($oldVal, $valLen) == $valLen or last MieElement; + } + %subdirInfo = ( + DataPt => \$oldVal, + DataLen => $valLen, + DirName => $$newInfo{Name}, + DataPos => $$dirInfo{IsCompressed} ? undef : $raf->Tell() - $valLen, + DirStart=> 0, + DirLen => $valLen, + ); + # write Compact subdirectories if we will compress the data + if (($compress or $optCompress or $$dirInfo{IsCompressed}) and + eval { require Compress::Zlib }) + { + $subdirInfo{Compact} = 1; + $subdirInfo{ReadOnly} = 1; # because XMP is not writable in place + } + } + $subdirInfo{Parent} = $dirName; + my $writeProc = $newInfo->{SubDirectory}->{WriteProc}; + # reset processed lookup to avoid errors in case of multiple EXIF blocks + $$et{PROCESSED} = { }; + $newVal = $et->WriteDirectory(\%subdirInfo, $subTablePtr, $writeProc); + if (defined $newVal) { + if ($newVal eq '') { + next MieElement if $newTag eq $tag; # deleting the directory + next; # not creating the new directory + } + } else { + next unless defined $oldVal; + $newVal = $oldVal; # just copy over the old directory + } + $writable = 'undef'; + $newFormat = 0x00; # all other directories are 'undef' format + } + } else { + + # get the new tag information + $newInfo = $$newTags{$newTag}; + my $nvHash = $et->GetNewValueHash($newInfo); + my @newVals; + + # write information only to specified group + my $writeGroup = $$nvHash{WriteGroup}; + last unless $isWriting{$writeGroup}; + + # if tag existed, must decide if we want to overwrite the value + if ($newTag eq $tag) { + my $isOverwriting; + my $isList = $$newInfo{List}; + if ($isList) { + last if $$nvHash{CreateOnly}; + $isOverwriting = -1; # force processing list elements individually + } else { + $isOverwriting = $et->IsOverwriting($nvHash); + last unless $isOverwriting; + } + my ($val, $cmpVal); + if ($isOverwriting < 0 or $verbose > 1) { + # check to be sure we can uncompress the value if necessary + HasZlib($et, 'edit') or last if $format & 0x04; + # read the old value + $raf->Read($oldVal, $valLen) == $valLen or last MieElement; + # uncompress if necessary + if ($format & 0x04) { + my $stat; + my $inflate = Compress::Zlib::inflateInit(); + # must save original compressed value in case we decide + # not to overwrite it later + $cmpVal = $oldVal; + $inflate and ($oldVal, $stat) = $inflate->inflate($oldVal); + unless ($inflate and $stat == Compress::Zlib::Z_STREAM_END()) { + $msg = "Error inflating $tag"; + last MieElement; + } + $valLen = length $oldVal; # update value length + } + # convert according to specified format + my $formatStr = $mieFormat{$format & 0xfb} || 'undef'; + $val = ReadMIEValue(\$oldVal, 0, $formatStr, undef, $valLen); + if ($isOverwriting < 0 and defined $val) { + # handle list values individually + if ($isList) { + my (@vals, $v); + if ($formatStr =~ /_list$/) { + @vals = split "\0", $val; + } else { + @vals = $val; + } + # keep any list items that we aren't overwriting + foreach $v (@vals) { + next if $et->IsOverwriting($nvHash, $v); + push @newVals, $v; + } + } else { + # test to see if we really want to overwrite the value + $isOverwriting = $et->IsOverwriting($nvHash, $val); + } + } + } + if ($isOverwriting) { + # skip the old value if we didn't read it already + unless (defined $oldVal) { + $raf->Seek($valLen, 1) or $msg = 'Seek error'; + } + if ($verbose > 1) { + $val .= "($units)" if defined $units; + $et->VerboseValue("- $grp1:$$newInfo{Name}", $val); + } + $deletedTag = $tag; # remember that we deleted this tag + ++$$et{CHANGED}; # we deleted the old value + } else { + if (defined $oldVal) { + # write original compressed value + $oldVal = $cmpVal if defined $cmpVal; + } else { + $raf->Read($oldVal, $valLen) == $valLen or last MieElement; + } + # write the old value now + Write($outfile, $toWrite, $oldHdr, $oldVal) or $err = 1; + $toWrite = ''; + next MieElement; + } + unless (@newVals) { + # unshift the new tag info to write it later + unshift @editTags, $newTag; + next MieElement; # get next element from file + } + } else { + # write new value if creating, or if List and list existed, or + # if tag was previously deleted + next unless $$nvHash{IsCreating} or + ($newTag eq $lastTag and ($$newInfo{List} or $deletedTag eq $lastTag)); + } + # get the new value to write (undef to delete) + push @newVals, $et->GetNewValue($nvHash); + next unless @newVals; + $writable = $$newInfo{Writable} || $$tagTablePtr{WRITABLE}; + if ($writable eq 'string') { + # join multiple values into a single string + $newVal = join "\0", @newVals; + # write string as UTF-8,16 or 32 if value contains valid UTF-8 codes + my $isUTF8 = Image::ExifTool::IsUTF8(\$newVal); + if ($isUTF8 > 0) { + $writable = 'utf8'; + # write UTF-16 or UTF-32 if it is more compact + my $to = $isUTF8 > 1 ? 'UCS4' : 'UCS2'; + my $tmp = Image::ExifTool::Decode(undef,$newVal,'UTF8',undef,$to); + if (length $tmp < length $newVal) { + $newVal = $tmp; + $writable = ($isUTF8 > 1) ? 'utf32' : 'utf16'; + } + } + # write as a list if we have multiple values + $writable .= '_list' if @newVals > 1; + } else { + # should only be one element in the list + $newVal = shift @newVals; + } + $newFormat = $mieCode{$writable}; + unless (defined $newFormat) { + $msg = "Bad format '${writable}' for $$newInfo{Name}"; + next MieElement; + } + } + + # write the new or edited element + while (defined $newFormat) { + my $valPt = \$newVal; + # remove units from value and add to tag name if supported by this tag + if ($$newInfo{Units}) { + my $val2; + if ($$valPt =~ /(.*)\((.*)\)$/) { + $val2 = $1; + $newTag .= "($2)"; + } else { + $val2 = $$valPt; + # add default units + my $ustr = '(' . $newInfo->{Units}->[0] . ')'; + $newTag .= $ustr; + $$valPt .= $ustr; + } + $valPt = \$val2; + } + # convert value if necessary + if ($writable !~ /^(utf|string|undef)/) { + my $val3 = WriteValue($$valPt, $writable, $$newInfo{Count}); + defined $val3 or $et->Warn("Error writing $newTag"), last; + $valPt = \$val3; + } + my $len = length $$valPt; + # compress value before writing if required + if (($compress or $optCompress) and not $$dirInfo{IsCompressed} and + HasZlib($et, 'write')) + { + my $deflate = Compress::Zlib::deflateInit(); + my $val4; + if ($deflate) { + $val4 = $deflate->deflate($$valPt); + $val4 .= $deflate->flush() if defined $val4; + } + if (defined $val4) { + my $len4 = length $val4; + my $saved = $len - $len4; + # only use compressed data if it is smaller + if ($saved > 0) { + $verbose and print $out " [$newTag compression saved $saved bytes]\n"; + $newFormat |= 0x04; # set compressed bit + $len = $len4; # set length + $valPt = \$val4; # set value pointer + } elsif ($verbose) { + print $out " [$newTag compression saved $saved bytes -- written uncompressed]\n"; + } + } else { + $et->Warn("Error deflating $newTag (written uncompressed)"); + } + } + # calculate the DataLength code + my $extLen; + if ($len < 253) { + $extLen = ''; + } elsif ($len < 65536) { + $extLen = Set16u($len); + $len = 255; + } elsif ($len <= 0x7fffffff) { + $extLen = Set32u($len); + $len = 254; + } else { + $et->Warn("Can't write $newTag (DataLength > 2GB not yet supported)"); + last; # don't write this tag + } + # write this element (with leading MIE group element if not done already) + my $hdr = $toWrite . '~' . chr($newFormat) . chr(length $newTag); + Write($outfile, $hdr, chr($len), $newTag, $extLen, $$valPt) or $err = 1; + $toWrite = ''; + # we changed a tag unless just editing a subdirectory + unless ($$editDirs{$newTag}) { + $et->VerboseValue("+ $grp1:$$newInfo{Name}", $newVal); + ++$$et{CHANGED}; + } + last; # didn't want to loop anyway + } + next MieElement if defined $oldVal; + } +# +# rewrite existing element or descend into uncompressed MIE group +# + # all done this MIE group if we reached the terminator element + unless ($tagLen) { + # skip over existing terminator data (if any) + last if $valLen and not $raf->Seek($valLen, 1); + $ok = 1; + # write group terminator if necessary + unless ($toWrite) { + # write end-of-group terminator element + my $term = "~\0\0\0"; + unless ($$dirInfo{Parent}) { + # write extended terminator for file-level group + my $len = ref $outfile eq 'SCALAR' ? length($$outfile) : tell $outfile; + $len += 10; # include length of terminator itself + if ($len and $len <= 0x7fffffff) { + $term = "~\0\0\x06" . Set32u($len) . MIEGroupFormat(1) . "\x04"; + } + } + Write($outfile, $term) or $err = 1; + } + last; + } + + # descend into existing uncompressed MIE group + if ($format == 0x10 or $format == 0x18) { + my ($subTablePtr, $dirName); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo and $$tagInfo{SubDirectory}) { + $dirName = $tagInfo->{SubDirectory}->{DirName}; + my $subTable = $tagInfo->{SubDirectory}->{TagTable}; + $subTablePtr = $subTable ? GetTagTable($subTable) : $tagTablePtr; + } else { + $subTablePtr = GetTagTable('Image::ExifTool::MIE::Unknown'); + } + my $hdr = '~' . chr($format) . chr(length $tag) . "\0" . $tag; + my %subdirInfo = ( + DirName => $dirName || $tag, + RAF => $raf, + ToWrite => $toWrite . $hdr, + OutFile => $outfile, + Parent => $dirName, + IsCompressed => $$dirInfo{IsCompressed}, + ); + my $oldOrder = GetByteOrder(); + SetByteOrder($format & 0x08 ? 'II' : 'MM'); + $msg = WriteMIEGroup($et, \%subdirInfo, $subTablePtr); + SetByteOrder($oldOrder); + last if $msg; + if (defined $msg) { + undef $msg; # no problem if nothing written + } else { + $toWrite = ''; + } + next; + } + # just copy existing element + my $oldVal; + $raf->Read($oldVal, $valLen) == $valLen or last; + if ($toWrite) { + Write($outfile, $toWrite) or $err = 1; + $toWrite = ''; + } + Write($outfile, $oldHdr, $oldVal) or $err = 1; + } + # return error message + if ($err) { + $msg = 'Error writing file'; + } elsif (not $ok and not $msg) { + $msg = 'Unexpected end of file'; + } elsif (not $msg and $toWrite) { + $msg = ''; # flag for nothing written + $verbose and print $out "Deleted $grp1 (empty)\n"; + } + return $msg; +} + +#------------------------------------------------------------------------------ +# Process MIE directory +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref +# Returns: undef on success, or error message if there was a problem +# Notes: file pointer is positioned at the MIE end on entry +sub ProcessMIEGroup($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my $notUTF8 = ($$et{OPTIONS}{Charset} ne 'UTF8'); + my ($msg, $buff, $ok, $oldIndent, $mime); + my $lastTag = ''; + + # get group 1 names: $grp doesn't have numbers (eg. 'MIE-Doc'), + # and $grp1 does (eg. 'MIE1-Doc1') + my $cnt = $$et{MIE_COUNT}; + my $grp1 = $tagTablePtr->{GROUPS}->{1}; + my $n = $$cnt{'MIE-Main'} || 0; + if ($grp1 eq 'MIE-Main') { + $$cnt{$grp1} = ++$n; + $grp1 =~ s/MIE-/MIE$n-/ if $n > 1; + } else { + $grp1 =~ s/MIE-/MIE$n-/ if $n > 1; + $$cnt{$grp1} = ($$cnt{$grp1} || 0) + 1; + $grp1 .= $$cnt{$grp1} if $$cnt{$grp1} > 1; + } + # set group1 name for all tags extracted from this group + $$et{SET_GROUP1} = $grp1; + + if ($verbose) { + $oldIndent = $$et{INDENT}; + $$et{INDENT} .= '| '; + $et->VerboseDir($grp1); + } + my $wasCompressed = $$dirInfo{WasCompressed}; + + # process all MIE elements + for (;;) { + $raf->Read($buff, 4) == 4 or last; + my ($sync, $format, $tagLen, $valLen) = unpack('aC3', $buff); + $sync eq '~' or $msg = 'Invalid sync byte', last; + + # read tag name + my ($tag, $units); + if ($tagLen) { + $raf->Read($tag, $tagLen) == $tagLen or last; + $et->Warn("MIE tag '${tag}' out of sequence") if $tag lt $lastTag; + $lastTag = $tag; + # separate units from tag name if they exist + $units = $1 if $tag =~ s/\((.*)\)$//; + } else { + $tag = ''; + } + + # get multi-byte value length if necessary + if ($valLen > 252) { + my $n = 1 << (256 - $valLen); + $raf->Read($buff, $n) == $n or last; + my $fmt = 'int' . ($n * 8) . 'u'; + $valLen = ReadValue(\$buff, 0, $fmt, 1, $n); + if ($valLen > 0x7fffffff) { + $msg = "Can't read $tag (DataLength > 2GB not yet supported)"; + last; + } + } + + # all done if we reached the group terminator + unless ($tagLen) { + # skip over terminator data block + $ok = 1 unless $valLen and not $raf->Seek($valLen, 1); + last; + } + + # get tag information hash unless this is free space + my ($tagInfo, $value); + while ($format != 0x80) { + $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + last if $tagInfo; + # extract tags with locale code + if ($tag =~ /\W/) { + if ($tag =~ /^(\w+)-([a-z]{2}_[A-Z]{2})$/) { + my ($baseTag, $langCode) = ($1, $2); + $tagInfo = $et->GetTagInfo($tagTablePtr, $baseTag); + $tagInfo = GetLangInfo($tagInfo, $langCode) if $tagInfo; + last if $tagInfo; + } else { + $et->Warn('Invalid MIE tag name'); + last; + } + } + # extract unknown tags if specified + $tagInfo = { + Name => $tag, + Writable => 0, + PrintConv => 'length($val) > 60 ? substr($val,0,55) . "[...]" : $val', + }; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + last; + } + + # read value and uncompress if necessary + my $formatStr = $mieFormat{$format & 0xfb} || 'undef'; + if ($tagInfo or ($formatStr eq 'MIE' and $format & 0x04)) { + $raf->Read($value, $valLen) == $valLen or last; + if ($format & 0x04) { + if ($verbose) { + print $out "$$et{INDENT}\[Tag '${tag}' $valLen bytes compressed]\n"; + } + next unless HasZlib($et, 'decode'); + my $stat; + my $inflate = Compress::Zlib::inflateInit(); + $inflate and ($value, $stat) = $inflate->inflate($value); + unless ($inflate and $stat == Compress::Zlib::Z_STREAM_END()) { + $et->Warn("Error inflating $tag"); + next; + } + $valLen = length $value; + $wasCompressed = 1; + } + } + + # process this tag + if ($formatStr eq 'MIE') { + # process MIE directory + my ($subTablePtr, $dirName); + if ($tagInfo and $$tagInfo{SubDirectory}) { + $dirName = $tagInfo->{SubDirectory}->{DirName}; + my $subTable = $tagInfo->{SubDirectory}->{TagTable}; + $subTablePtr = $subTable ? GetTagTable($subTable) : $tagTablePtr; + } else { + $subTablePtr = GetTagTable('Image::ExifTool::MIE::Unknown'); + } + if ($verbose) { + my $order = ', byte order ' . GetByteOrder(); + $et->VerboseInfo($tag, $tagInfo, Size => $valLen, Extra => $order); + } + my %subdirInfo = ( + DirName => $dirName || $tag, + RAF => $raf, + Parent => $$dirInfo{DirName}, + WasCompressed => $wasCompressed, + ); + # read from uncompressed data instead if necessary + $subdirInfo{RAF} = new File::RandomAccess(\$value) if $valLen; + + my $oldOrder = GetByteOrder(); + SetByteOrder($format & 0x08 ? 'II' : 'MM'); + $msg = ProcessMIEGroup($et, \%subdirInfo, $subTablePtr); + SetByteOrder($oldOrder); + $$et{SET_GROUP1} = $grp1; # restore this group1 name + last if $msg; + } else { + # process MIE data format types + if ($tagInfo) { + my $rational; + # extract tag value + my $val = ReadMIEValue(\$value, 0, $formatStr, undef, $valLen, \$rational); + unless (defined $val) { + $et->Warn("Error reading $tag value"); + $val = '<err>'; + } + # save type or mime type + $mime = $val if $tag eq '0Type' or $tag eq '2MIME'; + if ($verbose) { + my $count; + my $s = Image::ExifTool::FormatSize($formatStr); + if ($s and $formatStr !~ /^(utf|string|undef)/) { + $count = $valLen / $s; + } + $et->VerboseInfo($lastTag, $tagInfo, + DataPt => \$value, + DataPos => $wasCompressed ? undef : $raf->Tell() - $valLen, + Size => $valLen, + Format => $formatStr, + Value => $val, + Count => $count, + ); + } + if ($$tagInfo{SubDirectory}) { + my $subTablePtr = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + my %subdirInfo = ( + DirName => $$tagInfo{Name}, + DataPt => \$value, + DataLen => $valLen, + DirStart=> 0, + DirLen => $valLen, + Parent => $$dirInfo{DirName}, + WasCompressed => $wasCompressed, + ); + # set DataPos and Base for uncompressed information only + unless ($wasCompressed) { + $subdirInfo{DataPos} = 0; # (relative to Base) + $subdirInfo{Base} = $raf->Tell() - $valLen; + } + # reset PROCESSED lookup for each MIE directory + # (there is no possibility of double-processing a MIE directory) + $$et{PROCESSED} = { }; + my $processProc = $tagInfo->{SubDirectory}->{ProcessProc}; + delete $$et{SET_GROUP1}; + delete $$et{NO_LIST}; + $et->ProcessDirectory(\%subdirInfo, $subTablePtr, $processProc); + $$et{SET_GROUP1} = $grp1; + $$et{NO_LIST} = 1; + } else { + # convert to specified character set if necessary + if ($notUTF8 and $formatStr =~ /^(utf|string)/) { + $val = $et->Decode($val, 'UTF8'); + } + if ($formatStr =~ /_list$/) { + # split list value into separate strings + my @vals = split "\0", $val; + $val = \@vals; + } + if (defined $units) { + $val = "@$val" if ref $val; # convert string list to number list + # add units to value if specified + $val .= "($units)" if defined $units; + } + my $key = $et->FoundTag($tagInfo, $val); + $$et{RATIONAL}{$key} = $rational if defined $rational and defined $key; + } + } else { + # skip over unknown information or free bytes + $raf->Seek($valLen, 1) or $msg = 'Seek error', last; + $verbose and $et->VerboseInfo($tag, undef, Size => $valLen); + } + } + } + # modify MIME type if necessary + $mime and not $$dirInfo{Parent} and $et->ModifyMimeType($mime); + + $ok or $msg or $msg = 'Unexpected end of file'; + $verbose and $$et{INDENT} = $oldIndent; + return $msg; +} + +#------------------------------------------------------------------------------ +# Read/write a MIE file +# Inputs: 0) ExifTool object reference, 1) DirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid MIE file, or -1 on write error +# - process as a trailer if "Trailer" flag set in dirInfo +sub ProcessMIE($$) +{ + my ($et, $dirInfo) = @_; + return 1 unless defined $et; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my ($buff, $err, $msg, $pos, $end, $isCreating); + my $numDocs = 0; +# +# process as a trailer (from end of file) if specified +# + if ($$dirInfo{Trailer}) { + my $offset = $$dirInfo{Offset} || 0; # offset from end of file + $raf->Seek(-10 - $offset, 2) or return 0; + for (;;) { + # read and validate last 10 bytes + $raf->Read($buff, 10) == 10 or last; + last unless $buff =~ /~\0\0\x06.{4}(\x10|\x18)(\x04)$/s or + $buff =~ /(\x10|\x18)(\x08)$/s; + SetByteOrder($1 eq "\x10" ? 'MM' : 'II'); + my $len = ($2 eq "\x04") ? Get32u(\$buff, 4) : Get64u(\$buff, 0); + my $curPos = $raf->Tell() or last; + last if $len < 12 or $len > $curPos; + # validate element header if 8-byte offset was used + if ($2 eq "\x08") { + last if $len < 14; + $raf->Seek($curPos - 14, 0) and $raf->Read($buff, 4) or last; + last unless $buff eq "~\0\0\x0a"; + } + # looks like a good group, so remember start position + $pos = $curPos - $len; + $end = $curPos unless $end; + # seek to 10 bytes from end of previous group + $raf->Seek($pos - 10, 0) or last; + } + # seek to start of first MIE group + return 0 unless defined $pos and $raf->Seek($pos, 0); + # update DataPos and DirLen for ProcessTrailers() + $$dirInfo{DataPos} = $pos; + $$dirInfo{DirLen} = $end - $pos; + if ($outfile and $$et{DEL_GROUP}{MIE}) { + # delete the trailer + $et->VPrint(0," Deleting MIE trailer\n"); + ++$$et{CHANGED}; + return 1; + } elsif ($et->Options('Verbose') or $$et{HTML_DUMP}) { + $et->DumpTrailer($dirInfo); + } + } +# +# loop through all documents in MIE file +# + for (;;) { + # look for "0MIE" group element + my $num = $raf->Read($buff, 8); + if ($num == 8) { + # verify file identifier + if ($buff =~ /^~(\x10|\x18)\x04(.)0MIE/s) { + SetByteOrder($1 eq "\x10" ? 'MM' : 'II'); + my $len = ord($2); + # skip extended DataLength if it exists + if ($len > 252 and not $raf->Seek(1 << (256 - $len), 1)) { + $msg = 'Seek error'; + last; + } + } else { + return 0 unless $numDocs; # not a MIE file + if ($buff =~ /^~/) { + $msg = 'Non-standard file-level MIE element'; + } else { + $msg = 'Invalid MIE file-level data'; + } + } + } elsif ($numDocs) { + last unless $num; # OK, all done with file + $msg = 'Truncated MIE element header'; + } else { + return 0 if $num or not $outfile; + # we have the ability to create a MIE file from scratch + $buff = ''; # start from nothing + # set byte order according to preferences + $et->SetPreferredByteOrder(); + $isCreating = 1; + } + if ($msg) { + last if $$dirInfo{Trailer}; # allow other trailers after MIE + if ($outfile) { + $et->Error($msg); + } else { + $et->Warn($msg); + } + last; + } + # this is a new MIE document -- increment document count + unless ($numDocs) { + # this is a valid MIE file (unless a trailer on another file) + $et->SetFileType(); + $$et{NO_LIST} = 1; # handle lists ourself + $$et{MIE_COUNT} = { }; + undef $hasZlib; + } + ++$numDocs; + + # process the MIE groups recursively, beginning with the main MIE group + my $tagTablePtr = GetTagTable('Image::ExifTool::MIE::Main'); + + my %subdirInfo = ( + DirName => 'MIE', + RAF => $raf, + OutFile => $outfile, + # don't define Parent so WriteMIEGroup() writes extended terminator + ); + if ($outfile) { + # generate lookup for MIE format codes if not done already + unless (%mieCode) { + foreach (keys %mieFormat) { + $mieCode{$mieFormat{$_}} = $_; + } + } + # update %mieMap with user-defined MIE groups + UpdateMieMap() unless $doneMieMap; + # initialize write directories, with MIE tags taking priority + # (note that this may re-initialize directories when writing trailer + # to another type of image, but this is OK because we are done writing + # the other format by the time we start writing the trailer) + $et->InitWriteDirs(\%mieMap, 'MIE'); + $subdirInfo{ToWrite} = '~' . MIEGroupFormat(1) . "\x04\xfe0MIE\0\0\0\0"; + $msg = WriteMIEGroup($et, \%subdirInfo, $tagTablePtr); + if ($msg) { + $et->Error($msg); + $err = 1; + last; + } elsif (defined $msg and $isCreating) { + last; + } + } else { + $msg = ProcessMIEGroup($et, \%subdirInfo, $tagTablePtr); + if ($msg) { + $et->Warn($msg); + last; + } + } + } + delete $$et{NO_LIST}; + delete $$et{MIE_COUNT}; + delete $$et{SET_GROUP1}; + return $err ? -1 : 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MIE - Read/write MIE meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read and write +information in MIE files. + +=head1 WHAT IS MIE? + +MIE stands for "Meta Information Encapsulation". The MIE format is an +extensible, dedicated meta information format which supports storage of +binary as well as textual meta information. MIE can be used to encapsulate +meta information from many sources and bundle it together with any type of +file. + +=head2 Features + +Below is very subjective score card comparing the features of a number of +common file and meta information formats, and comparing them to MIE. The +following features are rated for each format with a score of 0 to 10: + + 1) Extensible (can incorporate user-defined information). + 2) Meaningful tag ID's (hint to meaning of unknown information). + 3) Sequential read/write ability (streamable). + 4) Hierarchical information structure. + 5) Easy to implement reader/writer/editor. + 6) Order of information well defined. + 7) Large data lengths supported: >64kB (+5) and >4GB (+5). + 8) Localized text strings. + 9) Multiple documents in a single file. + 10) Compact format doesn't squander disk space or bandwidth. + 11) Compressed meta information supported. + 12) Relocatable data elements (ie. no fixed offsets). + 13) Binary meta information (+7) with variable byte order (+3). + 14) Mandatory tags not required (an unnecessary complication). + 15) Append information to end of file without editing. + + Feature number Total + Format 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Score + ------ --------------------------------------------- ----- + MIE 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 150 + PDF 10 10 0 10 0 0 10 0 10 10 10 0 7 10 10 97 + PNG 10 10 10 0 8 0 5 10 0 10 10 10 0 10 0 93 + XMP 10 10 10 10 2 0 10 10 10 0 0 10 0 10 0 92 + AIFF 0 5 10 10 10 0 5 0 0 10 0 10 7 10 0 77 + RIFF 0 5 10 10 10 0 5 0 0 10 0 10 7 10 0 77 + JPEG 10 0 10 0 10 0 0 0 0 10 0 10 7 10 0 67 + EPS 10 10 10 0 0 0 10 0 10 0 0 5 0 10 0 65 + CIFF 0 0 0 10 10 0 5 0 0 10 0 10 10 10 0 65 + TIFF 0 0 0 10 5 10 5 0 10 10 0 0 10 0 0 60 + EXIF 0 0 0 10 5 10 0 0 0 10 0 0 10 0 0 45 + IPTC 0 0 10 0 8 0 0 0 0 10 0 10 7 0 0 45 + +By design, MIE ranks highest by a significant margin. Other formats with +reasonable scores are PDF, PNG and XMP, but each has significant weak +points. What may be surprising is that TIFF, EXIF and IPTC rank so low. + +As well as scoring high in all these features, the MIE format has the unique +ability to encapsulate any other type of file, and provides a non-invasive +method of adding meta information to a file. The meta information is +logically separated from the original file data, which is extremely +important because meta information is routinely lost when files are edited. + +Also, the MIE format supports multiple files by simple concatenation, +enabling all kinds of wonderful features such as linear databases, edit +histories or non-intrusive file updates. This ability can also be leveraged +to allow MIE-format trailers to be added to some other file types. + +=head1 MIE 1.1 FORMAT SPECIFICATION (2007-01-21) + +=head2 File Structure + +A MIE file consists of a series of MIE elements. A MIE element may contain +either data or a group of MIE elements, providing a hierarchical format for +storing data. Each MIE element is identified by a human-readable tag name, +and may store data from zero to 2^64-1 bytes in length. + +=head2 File Signature + +The first element in the MIE file must be an uncompressed MIE group element +with a tag name of "0MIE". This restriction allows the first 8 bytes of a +MIE file to be used to identify a MIE format file. The following table +lists the two possible initial byte sequences for a MIE-format file (the +first for big-endian, and the second for little-endian byte ordering): + + Byte Number: 0 1 2 3 4 5 6 7 + + C Characters: ~ \x10 \x04 ? 0 M I E + or ~ \x18 \x04 ? 0 M I E + + Hexadecimal: 7e 10 04 ? 30 4d 49 45 + or 7e 18 04 ? 30 4d 49 45 + + Decimal: 126 16 4 ? 48 77 73 69 + or 126 24 4 ? 48 77 73 69 + +Note that byte 1 may have one of the two possible values (0x10 or 0x18), and +byte 3 may have any value (0x00 to 0xff). + +=head2 Element Structure + + 1 byte SyncByte = 0x7e (decimal 126, character '~') + 1 byte FormatCode (see below) + 1 byte TagLength (T) + 1 byte DataLength (gives D if DataLength < 253) + T bytes TagName (T given by TagLength) + 2 bytes DataLength2 [exists only if DataLength == 255 (0xff)] + 4 bytes DataLength4 [exists only if DataLength == 254 (0xfe)] + 8 bytes DataLength8 [exists only if DataLength == 253 (0xfd)] + D bytes DataBlock (D given by DataLength) + +The minimum element length is 4 bytes (for a group terminator). The maximum +DataBlock size is 2^64-1 bytes. TagLength and DataLength are unsigned +integers, and the byte ordering for multi-byte DataLength fields is +specified by the containing MIE group element. The SyncByte is byte +aligned, so no padding is added to align on an N-byte boundary. + +=head3 FormatCode + +The format code is a bitmask that defines the format of the data: + + 7654 3210 + ++++ ---- FormatType + ---- +--- TypeModifier + ---- -+-- Compressed + ---- --++ FormatSize + +=over 4 + +=item FormatType (bitmask 0xf0): + + 0x00 - other (or unknown) data + 0x10 - MIE group + 0x20 - text string + 0x30 - list of null-separated text strings + 0x40 - integer + 0x50 - rational + 0x60 - fixed point + 0x70 - floating point + 0x80 - free space + +=item TypeModifier (bitmask 0x08): + +Modifies the meaning of certain FormatTypes (0x00-0x60): + + 0x08 - other data sensitive to MIE group byte order + 0x18 - MIE group with little-endian byte ordering + 0x28 - UTF encoded text string + 0x38 - UTF encoded text string list + 0x48 - signed integer + 0x58 - signed rational (denominator is always unsigned) + 0x68 - signed fixed-point + +=item Compressed (bitmask 0x04): + +If this bit is set, the data block is compressed using Zlib deflate. An +entire MIE group may be compressed, with the exception of file-level groups. + +=item FormatSize (bitmask 0x03): + +Gives the byte size of each data element: + + 0x00 - 8 bits (1 byte) + 0x01 - 16 bits (2 bytes) + 0x02 - 32 bits (4 bytes) + 0x03 - 64 bits (8 bytes) + +The number of bytes in a single value for this format is given by +2**FormatSize (or 1 << FormatSize). The number of values is the data length +divided by this number of bytes. It is an error if the data length is not +an even multiple of the format size in bytes. + +=back + +The following is a list of all currently defined MIE FormatCode values for +uncompressed data (add 0x04 to each value for compressed data): + + 0x00 - other data (insensitive to MIE group byte order) (1) + 0x01 - other 16-bit data (may be byte swapped) + 0x02 - other 32-bit data (may be byte swapped) + 0x03 - other 64-bit data (may be byte swapped) + 0x08 - other data (sensitive to MIE group byte order) (1) + 0x10 - MIE group with big-endian values (1) + 0x18 - MIE group with little-endian values (1) + 0x20 - ASCII (ISO 8859-1) string (2,3) + 0x28 - UTF-8 string (2,3,4) + 0x29 - UTF-16 string (2,3,4) + 0x2a - UTF-32 string (2,3,4) + 0x30 - ASCII (ISO 8859-1) string list (3,5) + 0x38 - UTF-8 string list (3,4,5) + 0x39 - UTF-16 string list (3,4,5) + 0x3a - UTF-32 string list (3,4,5) + 0x40 - unsigned 8-bit integer + 0x41 - unsigned 16-bit integer + 0x42 - unsigned 32-bit integer + 0x43 - unsigned 64-bit integer (6) + 0x48 - signed 8-bit integer + 0x49 - signed 16-bit integer + 0x4a - signed 32-bit integer + 0x4b - signed 64-bit integer (6) + 0x52 - unsigned 32-bit rational (16-bit numerator then denominator) (7) + 0x53 - unsigned 64-bit rational (32-bit numerator then denominator) (7) + 0x5a - signed 32-bit rational (denominator is unsigned) (7) + 0x5b - signed 64-bit rational (denominator is unsigned) (7) + 0x61 - unsigned 16-bit fixed-point (high 8 bits is integer part) (8) + 0x62 - unsigned 32-bit fixed-point (high 16 bits is integer part) (8) + 0x69 - signed 16-bit fixed-point (high 8 bits is signed integer) (8) + 0x6a - signed 32-bit fixed-point (high 16 bits is signed integer) (8) + 0x72 - 32-bit IEEE float (not recommended for portability reasons) + 0x73 - 64-bit IEEE double (not recommended for portability reasons) (6) + 0x80 - free space (value data does not contain useful information) + +Notes: + +=over 4 + +=item 1. + +The byte ordering specified by the MIE group TypeModifier applies to the MIE +group element as well as all elements within the group. Data for all +FormatCodes except 0x08 (other data, sensitive to byte order) may be +transferred between MIE groups with different byte order by byte swapping +the uncompressed data according to the specified data format. The following +list illustrates the byte-swapping pattern, based on FormatSize, for all +format types except rational (FormatType 0x50). + + FormatSize Change in Byte Sequence + -------------- ----------------------------------- + 0x00 (8 bits) 0 1 2 3 4 5 6 7 --> 0 1 2 3 4 5 6 7 (no change) + 0x01 (16 bits) 0 1 2 3 4 5 6 7 --> 1 0 3 2 5 4 7 6 + 0x02 (32 bits) 0 1 2 3 4 5 6 7 --> 3 2 1 0 7 6 5 4 + 0x03 (64 bits) 0 1 2 3 4 5 6 7 --> 7 6 5 4 3 2 1 0 + +Rational values consist of two integers, so they are swapped as the next +lower FormatSize. For example, a 32-bit rational (FormatSize 0x02, and +FormatCode 0x52 or 0x5a) is swapped as two 16-bit values (ie. as if it had +FormatSize 0x01). + +=item 2. + +The TagName of a string element may have an 6-character suffix to indicate a +specific locale. (eg. "Title-en_US", or "Keywords-de_DE"). + +=item 3. + +Text strings are not normally null terminated, however they may be padded +with one or more null characters to the end of the data block to allow +strings to be edited within fixed-length data blocks. Newlines in the text +are indicated by a single LF (0x0a) character. + +=item 4. + +UTF strings must not begin with a byte order mark (BOM) since the byte order +and byte size are specified by the MIE format. If a BOM is found, it should +be treated as a zero-width non-breaking space. + +=item 5. + +A list of text strings separated by null characters. These lists must not +be null padded or null terminated, since this would be interpreted as +additional zero-length strings. For ASCII and UTF-8 strings, the null +character is a single zero (0x00) byte. For UTF-16 or UTF-32 strings, the +null character is 2 or 4 zero bytes respectively. + +=item 6. + +64-bit integers and doubles are subject to the specified byte ordering for +both 32-bit words and bytes within these words. For instance, the high +order byte is always the first byte if big-endian, and the eighth byte if +little-endian. This means that some swapping is always necessary for these +values on systems where the byte order differs from the word order (eg. some +ARM systems), regardless of the endian-ness of the stored values. + +=item 7. + +Rational values are treated as two separate integers. The numerator always +comes first regardless of the byte ordering. In a signed rational value, +only the numerator is signed. The denominator of all rational values is +unsigned (eg. a signed 64-bit rational of 0x80000000/0x80000000 evaluates to +-1, not +1). + +=item 8. + +32-bit fixed point values are converted to floating point by treating them +as an integer and dividing by an appropriate value. eg) + + 16-bit fixed value = 16-bit integer value / 256.0 + 32-bit fixed value = 32-bit integer value / 65536.0 + +=back + +=head3 TagLength + +Gives the length of the TagName string. Any value between 0 and 255 is +valid, but the TagLength of 0 is valid only for the MIE group terminator. + +=head3 DataLength + +DataLength is an unsigned byte that gives the number of bytes in the data +block. A value between 0 and 252 gives the data length directly, and +numbers from 253 to 255 are reserved for extended DataLength codes. Codes +of 255, 254 and 253 indicate that the element contains an additional 2, 4 or +8 byte unsigned integer representing the data length. + + 0-252 - length of data block + 255 (0xff) - use DataLength2 + 254 (0xfe) - use DataLength4 + 253 (0xfd) - use DataLength8 + +A DataLength of zero is valid for any element except a compressed MIE group. +A zero DataLength for an uncompressed MIE group indicates that the group +length is unknown. For other elements, a zero length indicates there is no +associated data. A terminator element must have a DataLength of 0, 6 or 10, +and may not use an extended DataLength. + +=head3 TagName + +The TagName string is 0 to 255 bytes long, and is composed of the ASCII +characters A-Z, a-z, 0-9 and underline ('_'). Also, a dash ('-') is used to +separate the language/country code in the TagName of a localized text +string, and a units string (possibly containing other ASCII characters) may +be appear in brackets at the end of the TagName. The TagName string is NOT +null terminated. A MIE element with a tag string of zero length is reserved +for the group terminator. + +MIE elements are sorted alphabetically by TagName within each group. +Multiple elements with the same TagName are allowed, even within the same +group. + +TagNames should be meaningful. Case is significant. Words should be +lowercase with an uppercase first character, and acronyms should be all +upper case. The underline ("_") is provided to allow separation of two +acronyms or two numbers, but it shouldn't be used unless necessary. No +separation is necessary between an acronym and a word (eg. "ISOSetting"). + +All TagNames should start with an uppercase letter. An exception to this +rule allows tags to begin with a digit (0-9) if they must come before other +tags in the sort order, or a lowercase letter (a-z) if they must come after. +For instance, the '0Type' element begins with a digit so it comes before, +and the 'data' element begins with a lowercase letter so that it comes after +meta information tags in the main "0MIE" group. + +Tag names for localized text strings have an 6-character suffix with the +following format: The first character is a dash ('-'), followed by a +2-character lower case ISO 639-1 language code, then an underline ('_'), and +ending with a 2-character upper case ISO 3166-1 alpha 2 country code. (eg. +"-en_US", "-en_GB", "-de_DE" or "-fr_FR". Note that "GB", and not "UK" is +the code for Great Britain, although "UK" should be recognized for +compatibility reasons.) The suffix is included when sorting the tags +alphabetically, so the default locale (with no tag-name suffix) always comes +first. If the country is unknown or not applicable, a country code of "XX" +should be used. + +Tags with numerical values may allow units of measurement to be specified. +The units string is stored in brackets at the end of the tag name, and is +composed of zero or more ASCII characters in the range 0x21 to 0x7d, +excluding the bracket characters 0x28 and 0x29. (eg. "Resolution(/cm)" or +"SpecificHeat(J/kg.K)".) See L<Image::ExifTool::MIEUnits> for details. Unit +strings are not localized, and may not be used in combination with localized +text strings. + +Sets of tags which would require a common prefix should be added in a +separate MIE group instead of adding the prefix to all tag names. For +example, instead of these TagName's: + + ExternalFlashType + ExternalFlashSerialNumber + ExternalFlashFired + +one would instead designate a separate "ExternalFlash" MIE group to contain +the following elements: + + Type + SerialNumber + Fired + +=head3 DataLength2/4/8 + +These extended DataLength fields exist only if DataLength is 255, 254 or +253, and are respectively 2, 4 or 8 byte unsigned integers giving the data +block length. One of these values must be used if the data block is larger +than 252 bytes, but they may be used if desired for smaller blocks too +(although this may add a few unnecessary bytes to the MIE element). + +=head3 DataBlock + +The data value for the MIE element. The format of the data is given by the +FormatCode. For MIE group elements, the data includes all contained +elements and the group terminator. + +=head2 MIE groups + +All MIE data elements must be contained within a group. A group begins with +a MIE group element, and ends with a group terminator. Groups may be nested +in a hierarchy to arbitrary depth. + +A MIE group element is identified by a format code of 0x10 (big endian byte +ordering) or 0x18 (little endian). The group terminator is distinguished by +a zero TagLength (it is the only element allowed to have a zero TagLength), +and has a FormatCode of 0x00. + +The MIE group element is permitted to have a zero DataLength only if the +data is uncompressed. This special value indicates that the group length is +unknown (otherwise the minimum value for DataLength is 4, corresponding the +the minimum group size which includes a terminator of at least 4 bytes). If +DataLength is zero, all elements in the group must be parsed until the group +terminator is found. If non-zero, DataLength includes the length of all +elements contained within the group, including the group terminator. Use of +a non-zero DataLength is encouraged because it allows readers quickly skip +over entire MIE groups. For compressed groups DataLength must be non-zero, +and is the length of the compressed group data (which includes the +compressed group terminator). + +=head3 Group Terminator + +The group terminator has a FormatCode and TagLength of zero. The terminator +DataLength must be 0, 6 or 10 bytes, and extended DataLength codes may not +be used. With a zero DataLength, the byte sequence for a terminator is "7e +00 00 00" (hex). With a DataLength of 6 or 10 bytes, the terminator data +block contains information about the length and byte ordering of the +preceding group. This additional information is recommended for file-level +groups, and is used in multi-document MIE files and MIE trailers to allow +the file to be scanned backwards from the end. (This may also allow some +documents to be recovered if part of the file is corrupted.) The structure +of this optional terminator data block is as follows: + + 4 or 8 bytes GroupLength (unsigned integer) + 1 byte ByteOrder (0x10 or 0x18, same as MIE group) + 1 byte GroupLengthSize (0x04 or 0x08) + +The ByteOrder and GroupLengthSize values give the byte ordering and size of +the GroupLength integer. The GroupLength value is the total length of the +entire MIE group ending with this terminator, including the opening MIE +group element and the terminator itself. + +=head3 File-level MIE groups + +File-level MIE groups may NOT be compressed. + +All elements in a MIE file are contained within a special group with a +TagName of "0MIE". The purpose of the "OMIE" group is to provide a unique +signature at the start of the file, and to encapsulate information allowing +files to be easily combined. The "0MIE" group must be terminated like any +other group, but it is recommended that the terminator of a file-level group +include the optional data block (defined above) to provide information about +the group length and byte order. + +It is valid to have more than one "0MIE" group at the file level, allowing +multiple documents in a single MIE file. Furthermore, the MIE structure +enables multi-document files to be generated by simply concatenating two or +more MIE files. + +=head2 Scanning Backwards through a MIE File + +The steps below give an algorithm to quickly locate the last document in a +MIE file: + +=over 4 + +=item 1. + +Read the last 10 bytes of the file. (Note that a valid MIE file may be as +short as 12 bytes long, but a file this length contains only an an empty MIE +group.) + +=item 2. + +If the last byte of the file is zero, then it is not possible to scan +backward through the file, so the file must be scanned from the beginning. +Otherwise, proceed to the next step. + +=item 3. + +If the last byte is 4 or 8, the terminator contains information about the +byte ordering and length of the group. Otherwise, stop here because this +isn't a valid MIE file. + +=item 4. + +The next-to-last byte must be either 0x10 indicating big-endian byte +ordering or 0x18 for little-endian ordering, otherwise this isn't a valid +MIE file. + +=item 5. + +The value of the preceding 4 or 8 bytes gives the length of the complete +file-level MIE group (GroupLength). This length includes both the leading +MIE group element and the terminator element itself. The value is an +unsigned integer with a byte length given in step 3), and a byte order from +step 4). From the current file position (at the end of the data read in +step 1), seek backward by this number of bytes to find the start of the MIE +group element for this document. + +=back + +This algorithm may be repeated again beginning at this point in the file to +locate the next-to-last document, etc. + +The table below lists all 5 valid patterns for the last 14 bytes of a +file-level MIE group, with all numbers in hex. The comments indicate the +length and byte ordering of GroupLength (xx) if available: + + ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 7e 00 00 00 - (no GroupLength) + ?? ?? ?? ?? 7e 00 00 06 xx xx xx xx 10 04 - 4 bytes, big endian + ?? ?? ?? ?? 7e 00 00 06 xx xx xx xx 18 04 - 4 bytes, little endian + 7e 00 00 0a xx xx xx xx xx xx xx xx 10 08 - 8 bytes, big endian + 7e 00 00 0a xx xx xx xx xx xx xx xx 18 08 - 8 bytes, little endian + +=head2 Trailer Signature + +The MIE format may be used for trailer information appended to other types +of files. When this is done, a signature must appear at the end of the main +MIE group to uniquely identify the MIE format trailer. To achieve this, a +"zmie" trailer signature is written as the last element in the main "0MIE" +group. This element has a FormatCode of 0, a TagLength of 4, a DataLength +of 0, and a TagName of "zmie". With this signature, the hex byte sequence +"7e 00 04 00 7a 6d 69 65" appears immediately before the final group +terminator, and the last 22 bytes of the trailer correspond to one of the +following 4 patterns (where the trailer length is given by "xx", as above): + + ?? ?? ?? ?? 7e 00 04 00 7a 6d 69 65 7e 00 00 06 xx xx xx xx 10 04 + ?? ?? ?? ?? 7e 00 04 00 7a 6d 69 65 7e 00 00 06 xx xx xx xx 18 04 + 7e 00 04 00 7a 6d 69 65 7e 00 00 0a xx xx xx xx xx xx xx xx 10 08 + 7e 00 04 00 7a 6d 69 65 7e 00 00 0a xx xx xx xx xx xx xx xx 18 08 + +Note that the zero-DataLength terminator may not be used here because the +trailer length must be known for seeking backwards from the end of the file. + +Multiple trailers may be appended to the same file using this technique. + +=head2 MIE Data Values + +MIE data values for a given tag are usually not restricted to a specific +FormatCode. Any value may be represented in any appropriate format, +including numbers represented in string (ASCII or UTF) form. + +It is preferred that closely related values with the same format are written +to a single tag instead of using multiple tags. This improves localization +of like values and decreases MIE element overhead. For instance, instead of +separate ImageWidth and ImageHeight tags, a single ImageSize tag is defined. + +Tags which may take on a discrete set of values should have meaningful +values if possible. This improves the extensibility of the format and +allows a more reasonable interpretation of unrecognized values. + +=head3 Numerical Representation + +Integer and floating point numbers may be represented in binary or string +form. In string form, integers are a series of digits with an optional +leading sign (eg. "[+|-]DDDDDD"), and multiple values are separated by a +single space character (eg. "23 128 -32"). Floating point numbers are +similar but may also contain a decimal point and/or a signed exponent with a +leading 'e' character (eg. "[+|-]DD[.DDDDDD][e(+|-)EEE]"). The string "inf" +is used to represent infinity. One advantage of numerical strings is that +they can have an arbitrarily high precision because the possible number of +significant digits is virtually unlimited. + +Note that numerical values may have associated units of measurement which +are specified in the L</TagName> string. + +=head3 Date/Time Format + +All MIE dates are strings in the form "YYYY:mm:dd HH:MM:SS.ss+HH:MM". The +fractional seconds (".ss") are optional, and if included may contain any +number of significant digits (unlike all other fields which are a fixed +number of digits and must be padded with leading zeros if necessary). The +timezone ("+HH:MM" or "-HH:MM") is recommended but not required. If not +given, the local system timezone is assumed. + +=head2 MIME Type + +The basic MIME type for a MIE file is "application/x-mie", however the +specific MIME type depends on the type of subfile, and is obtained by adding +"x-mie-" to the MIME type of the subfile. For example, with a subfile of +type "image/jpeg", the MIE file MIME type is "image/x-mie-jpeg". But note +that the "x-" is not duplicated if the subfile MIME type already starts with +"x-". So a subfile with MIME type "image/x-raw" is contained within a MIE +file of type "image/x-mie-raw", not "image/x-mie-x-raw". In the case of +multiple documents in a MIE file, the MIME type is taken from the first +document. Regardless of the subfile type, all MIE-format files should have +a filename extension of ".MIE". + +=head2 Levels of Support + +Basic MIE reader/writer applications may choose not to provide support for +some advanced features of the MIE format. Features which may not be +supported by all software are: + +=over 4 + +=item Compression + +Software not supporting compression must ignore compressed elements and +groups, but should be able to process the remaining information. + +=item Large data lengths + +Some software may limit the maximum size of a MIE group or element. +Historically, a limit of 2GB may be imposed by some systems. However, +8-byte data lengths should be supported by all applications provided the +value doesn't exceed the system limit. (eg. For systems with a 2GB limit, +8-byte data lengths should be supported if the upper 17 bits are all zero.) +If a data length above the system limit is encountered, it may be necessary +for the application to stop processing if it can not seek to the next +element in the file. + +=back + +=head1 EXAMPLES + +This section gives examples for working with MIE information using ExifTool. + +=head2 Encapsulating Information with Data in a MIE File + +The following command encapsulates any file recognized by ExifTool inside a +MIE file, and initializes MIE tags from information within the file: + + exiftool -o new.mie -tagsfromfile FILE '-mie:all<all' \ + '-subfilename<filename' '-subfiletype<filetype' \ + '-subfilemimetype<mimetype' '-subfiledata<=FILE' + +where C<FILE> is the name of the file. + +For unrecognized files, this command may be used: + + exiftool -o new.mie -subfilename=FILE -subfiletype=TYPE \ + -subfilemimetype=MIME '-subfiledata<=FILE' + +where C<TYPE> and C<MIME> represent the source file type and MIME type +respectively. + +=head2 Adding a MIE Trailer to a File + +The MIE format may also be used to store information in a trailer appended +to another type of file. Beware that trailers may not be compatible with +all file formats, but JPEG and TIFF are two formats where additional trailer +information doesn't create any problems for normal parsing of the file. +Also note that this technique has the disadvantage that trailer information +is commonly lost if the file is subsequently edited by other software. + +Creating a MIE trailer with ExifTool is a two-step process since ExifTool +can't currently be used to add a MIE trailer directly. The example below +illustrates the steps for adding a MIE trailer with a small preview image +(C<small.jpg>) to a destination JPEG image (C<dst.jpg>). + +Step 1) Create a MIE file with a TrailerSignature containing the desired +information: + + exiftool -o new.mie -trailersignature=1 -tagsfromfile small.jpg \ + '-previewimagetype<filetype' '-previewimagesize<imagesize' \ + '-previewimagename<filename' '-previewimage<=small.jpg' + +Step 2) Append the MIE information to another file. In Unix, this can be +done with the 'cat' command: + + cat new.mie >> dst.jpg + +Once added, ExifTool may be used to edit or delete a MIE trailer in a JPEG +or TIFF image. + +=head2 Multiple MIE Documents in a Single File + +The MIE specification allows multiple MIE documents (or trailers) to exist +in a single file. A file like this may be created by simply concatenating +MIE documents. ExifTool may be used to access information in a specific +document by adding a copy number to the MIE group name. For example: + + # write the Author tag in the second MIE document + exiftool -mie2:author=phil test.mie + + # delete the first MIE document from a file + exiftool -mie1:all= test.mie + +=head2 Units of Measurement + +Some MIE tags allow values to be specified in different units of +measurement. In the MIE file format these units are combined with the tag +name, but when using ExifTool they are specified in brackets after the +value: + + exiftool -mie:gpsaltitude='7500(ft)' test.mie + +If no units are provided, the default units are written. + +=head2 Localized Text + +Localized text values are accessed by adding a language/country code to the +tag name. For example: + + exiftool -comment-en_us='this is a comment' test.mie + +=head1 REVISIONS + + 2010-04-05 - Fixed "Format Size" Note 7 to give the correct number of bits + in the example rational value + 2007-01-21 - Specified LF character (0x0a) for text newline sequence + 2007-01-19 - Specified ISO 8859-1 character set for extended ASCII codes + 2007-01-01 - Improved wording of Step 5 for scanning backwards in MIE file + 2006-12-30 - Added EXAMPLES section and note about UTF BOM + 2006-12-20 - MIE 1.1: Changed meaning of TypeModifier bit (0x08) for + unknown data (FormatType 0x00), and documented byte swapping + 2006-12-14 - MIE 1.0: Added Data Values and Numerical Representations + sections, and added ability to specify units in tag names + 2006-11-09 - Added Levels of Support section + 2006-11-03 - Added Trailer Signature + 2005-11-18 - Original specification created + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. The MIE format itself is also +copyright Phil Harvey, and is covered by the same free-use license. + +=head1 REFERENCES + +=over 4 + +=item L<https://exiftool.org/MIE1.1-20070121.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MIE Tags>, L<Image::ExifTool::MIEUnits>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MIEUnits.pod b/ExifTool/lib/Image/ExifTool/MIEUnits.pod new file mode 100644 index 0000000..d533059 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MIEUnits.pod @@ -0,0 +1,377 @@ + +=head1 NAME + +Image::ExifTool::MIEUnits - MIE units documentation + +=head1 DESCRIPTION + +The MIE format allows units of measurement to be specified in brackets at +the end of a MIE tag name (eg. "Volume(m3)"). This document describes the +standard MIE units abbreviations. + +=head1 SYNTAX + +The units string may contain any ASCII characters in the range 0x21 ('!') to +0x7d ('}'), excepting the bracket characters (0x28 and 0x29). An empty +string is allowed, and indicates a dimensionless value. L<Standard +units|/STANDARD UNITS> should be used where possible. In the standard +units, an underline ('_') is used to indicate a subscript, and multiple +words may be separated with a hyphen ('-'). + +Exponents should be positive, and require no separator (eg. "m2" for square +meters). L<Prefixes|/PREFIXES> may be added to the standard units (eg. +"cm") except when the resulting name conflicts with another standard unit. + +Multiplication is indicated by '.', and division by '/'. Reciprocal units +(ie. the multiplicative inverse) are obtained through division rather than +the use of negative exponents (eg. "/in", not "in-1"). In MIE units, +multiplication has precedence over division, so everything to the right of a +'/' is in the denominator. (See L</EXAMPLES> for a few examples.) + +Below is a table summarizing the special characters used in MIE units +strings. + + . multiplication + / division (used for negative exponents) + - word separator + _ subscript + {} annotation + [] used to avoid name conflicts if necessary + 0-9 exponentiation + +=head1 STANDARD UNITS + + dimensionless + [G] Newtonian constant of gravitation (unclassified) + [g] standard acceleration of free fall (acceleration) + [h] Planck constant (action) + [k] Boltzmann constant (unclassified) + {cfu} colony forming units (number) + {rbc} red blood cell count (number) + {tbl} tablets (number) + {tot} particles total count (number) + % percent (fraction) + 10^N the number ten for arbitrary powers (number) + A Ampere (electric current) + a year (time) + a_g mean Gregorian year (time) + a_j mean Julian year (time) + a_t tropical year (time) + acr acre, U.S. (area) + acr_br acre, British (area) + Ao Angstrom (length) + APL-U APL unit (biologic activity of anticardiolipin IgA) + ar are (area) + arb-U arbitrary unit (arbitrary) + arcmin minute of arc (plane angle) + arcsec second of arc (plane angle) + atm standard atmosphere (pressure) + att technical atmosphere (pressure) + AU astronomic unit (length) + b barn (action area) + B bel (level) + B-kW bel kilowatt (power level) + B-mV bel millivolt (electric potential level) + B-SPL bel sound pressure (pressure level) + B-uV bel microvolt (electric potential level) + B-V bel volt (electric potential level) + B-W bel watt (power level) + bar bar (pressure) + bbl barrel (fluid volume) + Bd baud (signal transmission rate) + bdsk-U Bodansky unit (biologic activity of phosphatase) + beth-U Bethesda unit (biologic activity of factor VIII inhibitor) + bf board foot (volume) + Bi Biot (electric current) + bit bit (amount of information) + Bq Becquerel (radioactivity) + Btu British thermal unit (energy) + Btu_39 British thermal unit at 39 degF (energy) + Btu_59 British thermal unit at 59 degF (energy) + Btu_60 British thermal unit at 60 degF (energy) + Btu_IT international table British thermal unit (energy) + Btu_m mean British thermal unit (energy) + Btu_th thermochemical British thermal unit (energy) + bu bushel, U.S. (dry volume) + bu_br bushel, British (volume) + By byte (amount of information) + C Coulomb (electric charge) + c velocity of light (velocity) + cal calorie (energy) + Cal nutrition label Calories (energy) + cal_15 calorie at 15 degC (energy) + cal_20 calorie at 20 degC (energy) + cal_IT international table calorie (energy) + cal_m mean calorie (energy) + cal_th thermochemical calorie (energy) + car_Au carat of gold alloys (mass fraction) + car_m metric carat (mass) + cd candela (luminous intensity) + Cel degree Celsius (temperature) + Ch Charriere (gauge of catheters) + ch Gunter's chain, U.S. (length) + ch_br Gunter's chain, British (length) + Ci Curie (radioactivity) + cicero cicero (length) + circ circle (plane angle) + cml circular mil, international (area) + cr cord, international (volume) + crd_us cord, U.S. (fluid volume) + cup_us cup (volume) + d day (time) + deg degree (plane angle) + deg{mag} degree from magnetic north (plane angle) + degF degree Fahrenheit (temperature) + didot didot (length) + diop diopter (refraction of a lens) + dpt dry pint, U.S. (dry volume) + dqt dry quart, U.S. (dry volume) + dr dram (mass) + dr_ap dram, apothecary (mass) + drp drop (volume) + dye-U Dye unit (biologic activity of amylase) + dyn dyne (force) + e elementary charge (electric charge) + eps_0 permittivity of vacuum (electric permittivity) + eq equivalents (amount of substance) + erg erg (energy) + eV electronvolt (energy) + F Farad (electric capacitance) + fdr fluid dram, U.S. (fluid volume) + fdr_br fluid dram, British (volume) + foz fluid ounce, U.S. (fluid volume) + foz_br fluid ounce, British (volume) + ft foot, international (length) + ft_br foot, British (length) + ft_us foot, U.S. (length) + fth fathom, international (length) + fth_br fathom, British (length) + fth_us fathom, U.S. (length) + fur furlong, U.S. (length) + G Gauss (magnetic flux density) + g gram (mass) + g.m/{H-B} gram meter per heartbeat (prop. to ventricular stroke work) + g% gram percent (mass fraction) + Gal Gal (acceleration) + gal gallon, U.S. (fluid volume) + gal_br gallon, British (volume) + gal_wi historical winchester gallon (dry volume) + Gb Gilbert (magnetic tension) + gf gram-force (force) + gil gill, U.S. (fluid volume) + gil_br gill, British (volume) + gon gon (plane angle) + GPL-U GPL unit (biologic activity of anticardiolipin IgG) + gr grain (mass) + Gy Gray (energy dose) + H Henry (inductance) + h hour (time) + hd hand, international (height of horses) + hnsf-U Hounsfield unit (x-ray attenuation) + HP horsepower (power) + hp_C homeopathic potency of centesimal series (homeopathic potency) + hp_X homeopathic potency of decimal series (homeopathic potency) + HPF high power field (view area in microscope) + Hz Herz (frequency) + in inch, international (length) + in_br inch, British (length) + in_us inch, U.S. (length) + in-H2O inch of water column (pressure) + in-Hg inch of mercury column (pressure) + iU international unit (arbitrary) + J Joule (energy) + K Kelvin (temperature) + ka-U King-Armstrong unit (biologic activity of phosphatase) + kat katal (catalytic activity) + kg{wet-tis} kilogram of wet tissue (mass) + kn knot, international (velocity) + kn_br knot, British (velocity) + knk-U Kunkel unit (arbitrary biologic activity) + Ky Kayser (lineic number) + l liter (volume) + L liter (volume) + lb pound (mass) + lb_ap pound, apothecary (mass) + lb_tr pound, troy (mass) + lbf pound force (force) + lcwt long hunderdweight (mass) + ligne ligne (length) + lk link for Gunter's chain, U.S. (length) + lk_br link for Gunter's chain, British (length) + lm lumen (luminous flux) + Lmb Lambert (brightness) + lne line (length) + LPF low power field (view area in microscope) + lton long ton (mass) + lx lux (illuminance) + ly light-year (length) + m meter (length) + m_e electron mass (mass) + m_p proton mass (mass) + m-H2O meter of water column (pressure) + m-Hg meter of mercury column (pressure) + mclg-U Mac Lagan unit (arbitrary biologic activity) + mesh mesh, international (lineic number) + MET metabolic equivalent (metabolic cost of physical activity) + mg{creat} milligram of creatinine (mass) + mho mho (electric conductance) + mi mile, international (statute mile) (length) + mi_br mile, British (length) + mi_us mile, U.S. (length) + mil mil, international (length) + mil_us mil, U.S. (length) + min minute (time) + min_br minim, British (volume) + min_us minim, U.S. (fluid volume) + mo month (time) + mo_g mean Gregorian month (time) + mo_j mean Julian month (time) + mo_s synodal month (time) + mol mole (amount of substance) + MPL-U MPL unit (biologic activity of anticardiolipin IgM) + mu_0 permeability of vacuum (magnetic permeability) + Mx Maxwell (flux of magnetic induction) + N Newton (force) + nmi nautical mile, international (length) + nmi_br nautical mile, British (length) + Np neper (level) + Oe Oersted (magnetic field intensity) + Ohm Ohm (electric resistance) + osm osmole of dissolved particles (amount of substance) + oz ounce (mass) + oz_ap ounce, apothecary (mass) + oz_tr ounce, troy (mass) + P Poise (dynamic viscosity) + Pa Pascal (pressure) + pc parsec (length) + pc_br pace (length) + pca pica (length) + pca_pr Printer's pica (length) + pH pH (acidity) + ph phot (illuminance) + pi the number pi (number) + pied pied (length) + pk peck, U.S. (dry volume) + pk_br peck, British (volume) + pnt point (length) + pnt_pr Printer's point (length) + pouce pouce (length) + ppb parts per billion (fraction) + ppm parts per million (fraction) + ppth parts per thousand (fraction) + pptr parts per trillion (fraction) + PRU peripheral vascular resistance unit (fluid resistance) + psi pound per square inch (pressure) + pt pint, U.S. (fluid volume) + pt_br pint, British (volume) + pwt_tr pennyweight (mass) + qt quart, U.S. (fluid volume) + qt_br quart, British (volume) + R Roentgen (ion dose) + rad radian (plane angle) + RAD radiation absorbed dose (energy dose) + rch Ramden's chain, U.S. (length) + rd rod, U.S. (length) + rd_br rod, British (length) + REM radiation equivalent man (dose equivalent) + rlk_us link for Ramden's chain (length) + s second (time) + S Siemens (electric conductance) + sb stilb (lum. intensity density) + sc_ap scruple, apothecary (mass) + sct section (area) + scwt short hundredweight (mass) + smgy-U Somogyi unit (biologic activity of amylase) + sph spere (solid angle) + sr streadian (solid angle) + st stere (volume) + St Stokes (kinematic viscosity) + ston short ton (mass) + stone stone (mass) + Sv Sievert (dose equivalent) + Sv-U Svedberg unit (sedimentation coefficient) + T Tesla (magnetic flux density) + t tonne (mass) + tb-U tuberculin unit (biologic activity of tuberculin) + tbs tablespoon, U.S. (volume) + todd-U Todd unit (biologic activity antistreptolysin O) + tsp teaspoon, U.S. (volume) + twp township (area) + u unified atomic mass unit (mass) + U Unit (catalytic activity) + USP-U U.S. Pharmacopeia unit (arbitrary) + V Volt (electric potential) + W Watt (power) + Wb Weber (magnetic flux) + wk week (time) + yd yard, international (length) + yd_br yard, British (length) + yd_us yard, U.S. (length) + +=head1 PREFIXES + +Standard SI prefixes: + + y yocto (10^-24) + z zepto (10^-21) + a atto (10^-18) + f femto (10^-15) + p pico (10^-12) + n nano (10^-9) + u micro (10^-6) + m milli (10^-3) + c centi (10^-2) + d deci (10^-1) + da deka (10) + h hecto (10^2) + k kilo (10^3) + G giga (10^9) + M mega (10^6) + T tera (10^12) + P peta (10^15) + E exa (10^18) + Z zetta (10^21) + Y yotta (10^24) + +Binary power prefixes: + + Ki kibi (2^10) + Mi mebi (2^20) + Gi gibi (2^30) + Ti tebi (2^40) + +=head1 EXAMPLES + +A few examples of combined units strings: + + /cm3 per cubic centimeter + in2 square inches + kg.m/s2 Newtons (N) + m3/kg.s2 units of [G] + 10^100 googols + +=head1 HISTORY + + 2006-12-14 - PH Created + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://aurora.regenstrief.org/UCUM/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::MIE(3pm)|Image::ExifTool::MIE>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/MIFF.pm b/ExifTool/lib/Image/ExifTool/MIFF.pm new file mode 100644 index 0000000..6fc40dc --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MIFF.pm @@ -0,0 +1,279 @@ +#------------------------------------------------------------------------------ +# File: MIFF.pm +# +# Description: Read Magick Image File Format meta information +# +# Revisions: 06/10/2005 - P. Harvey Created +# +# References: 1) http://www.imagemagick.org/script/miff.php +# 2) http://www.cs.uni.edu/Help/ImageMagick/www/miff.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::MIFF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.07'; + +# MIFF chunks +%Image::ExifTool::MIFF::Main = ( + GROUPS => { 2 => 'Image' }, + NOTES => q{ + The MIFF (Magick Image File Format) format allows aribrary tag names to be + used. Only the standard tag names are listed below, however ExifTool will + decode any tags found in the image. + }, + 'background-color' => 'BackgroundColor', + 'blue-primary' => 'BluePrimary', + 'border-color' => 'BorderColor', + 'matt-color' => 'MattColor', + class => 'Class', + colors => 'Colors', + colorspace => 'ColorSpace', + columns => 'ImageWidth', + compression => 'Compression', + delay => 'Delay', + depth => 'Depth', + dispose => 'Dispose', + gamma => 'Gamma', + 'green-primary' => 'GreenPrimary', + id => 'ID', + iterations => 'Iterations', + label => 'Label', + matte => 'Matte', + montage => 'Montage', + packets => 'Packets', + page => 'Page', + # profile tags. Note the SubDirectory is not used by ProcessMIFF(), + # but is inserted for documentation purposes only + 'profile-APP1' => [ + # [this list is just for the sake of the documentation] + { + Name => 'APP1_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + }, + }, + { + Name => 'APP1_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + }, + }, + ], + 'profile-exif' => { # haven't seen this, but it would make sense - PH + Name => 'EXIF_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + }, + }, + 'profile-icc' => { + Name => 'ICC_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + }, + 'profile-iptc' => { + Name => 'IPTC_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::Photoshop::Main', + }, + }, + 'profile-xmp' => { # haven't seen this, but it would make sense - PH + Name => 'XMP_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + }, + }, + 'red-primary' => 'RedPrimary', + 'rendering-intent' => 'RenderingIntent', + resolution => 'Resolution', + rows => 'ImageHeight', + scene => 'Scene', + signature => 'Signature', + units => 'Units', + 'white-point' => 'WhitePoint', +); + +#------------------------------------------------------------------------------ +# Extract meta information from a MIFF image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid MIFF image +sub ProcessMIFF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $$et{OPTIONS}{Verbose}; + my ($hdr, $buff); + + # validate the MIFF file (note: MIFF files _may_ begin with other + # characters, but this starting sequence is strongly suggested.) + return 0 unless $raf->Read($hdr, 14) == 14; + return 0 unless $hdr eq 'id=ImageMagick'; + $et->SetFileType(); # set the FileType tag + + # set end-of-line character sequence to read to end of the TEXT + # section for new-type MIFF files (text ends with Colon+Ctrl-Z) + # Old MIFF files end with Colon+Linefeed, so this will likely + # slurp those entire files, which will be slower, but will work + # OK except that the profile information won't be decoded + local $/ = ":\x1a"; + + my $mode = ''; + my @profiles; + if ($raf->ReadLine($buff)) { + chomp $buff; # remove end-of-line chars + my $tagTablePtr = GetTagTable('Image::ExifTool::MIFF::Main'); + my @entries = split ' ', $buff; + unshift @entries, $hdr; # put the ID back in + my ($tag, $val); + foreach (@entries) { + if ($mode eq 'com') { + $mode = '' if /\}$/; + next; + } elsif (/^\{/) { + $mode = 'com'; # read to the end of the comment + next; + } + if ($mode eq 'val') { + $val .= " $_"; # join back together with a space + next unless /\}$/; + $mode = ''; + $val =~ s/(^\{|\}$)//g; # remove braces + } elsif (/(.+)=(.+)/) { + ($tag, $val) = ($1, $2); + if ($val =~ /^\{/) { + $mode = 'val'; # read to the end of the value data + next; + } + } elsif (/^:/) { + # this could be the end of an old-style MIFF file + last; + } else { + # something we don't recognize -- stop parsing here + $et->Warn('Unrecognized MIFF data'); + last; + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + unless ($tagInfo) { + $tagInfo = { Name => $tag }; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + $verbose and $et->VerboseInfo($tag, $tagInfo, + Table => $tagTablePtr, + DataPt => \$val, + ); + # handle profile tags specially + if ($tag =~ /^profile-(.*)/) { + push @profiles, [$1, $val]; + } else { + $et->FoundTag($tagInfo, $val); + } + } + } + + # process profile information + foreach (@profiles) { + my ($type, $len) = @{$_}; + unless ($len =~ /^\d+$/) { + $et->Warn("Invalid length for $type profile"); + last; # don't try to read the rest + } + unless ($raf->Read($buff, $len) == $len) { + $et->Warn("Error reading $type profile ($len bytes)"); + next; + } + my $processed = 0; + my %dirInfo = ( + Parent => 'PNG', + DataPt => \$buff, + DataPos => $raf->Tell() - $len, + DataLen => $len, + DirStart => 0, + DirLen => $len, + ); + if ($type eq 'icc') { + # ICC Profile information + my $tagTablePtr = GetTagTable('Image::ExifTool::ICC_Profile::Main'); + $processed = $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($type eq 'iptc') { + if ($buff =~ /^8BIM/) { + # Photoshop information + my $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main'); + $processed = $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + # I haven't seen 'exif' or 'xmp' profile types yet, but I have seen them + # in newer PNG files so presumably they are possible here as well - PH + } elsif ($type eq 'APP1' or $type eq 'exif' or $type eq 'xmp') { + if ($buff =~ /^$Image::ExifTool::exifAPP1hdr/) { + # APP1 EXIF + my $hdrLen = length($Image::ExifTool::exifAPP1hdr); + $dirInfo{DirStart} += $hdrLen; + $dirInfo{DirLen} -= $hdrLen; + # use the usual position for EXIF data: 12 bytes from start of file + # (this may be wrong, but I can't see where the PNG stores this information) + $dirInfo{Base} = 12; # this is the usual value + $processed = $et->ProcessTIFF(\%dirInfo); + } elsif ($buff =~ /^$Image::ExifTool::xmpAPP1hdr/) { + # APP1 XMP + my $hdrLen = length($Image::ExifTool::xmpAPP1hdr); + my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); + $dirInfo{DirStart} += $hdrLen; + $dirInfo{DirLen} -= $hdrLen; + $processed = $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } + unless ($processed) { + $et->Warn("Unknown MIFF $type profile data"); + if ($verbose) { + $et->VerboseDir($type, 0, $len); + $et->VerboseDump(\$buff); + } + } + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MIFF - Read Magick Image File Format meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read MIFF +(Magick Image File Format) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.imagemagick.org/script/miff.php> + +=item L<http://www.cs.uni.edu/Help/ImageMagick/www/miff.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MIFF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MISB.pm b/ExifTool/lib/Image/ExifTool/MISB.pm new file mode 100644 index 0000000..2b4bf3b --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MISB.pm @@ -0,0 +1,494 @@ +#------------------------------------------------------------------------------ +# File: MISB.pm +# +# Description: Read Motion Industry Standards Board metadata +# +# Revisions: 2022/10/08 - P. Harvey Created +# +# References: 1) https://dokumen.tips/documents/nato-standardization-agreement-stanag-4609-ed-3.html +# 2) https://upload.wikimedia.org/wikipedia/commons/1/19/MISB_Standard_0601.pdf +# 3) https://dokumen.tips/documents/misb-st-010211-standard-security-metadata-universal-standard-describes-the-use.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::MISB; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.00'; + +sub ProcessKLV($$$); + +my %timeInfo = ( + Groups => { 2 => 'Time' }, + Format => 'int64u', + ValueConv => 'ConvertUnixTime($val/1e6, 0, 6) . "Z"', + PrintConv => '$self->ConvertDateTime($val)', +); +my %latInfo = ( + Format => 'int32s', + ValueConv => '$val * 90 / 0x7fffffff', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', +); +my %lonInfo = ( + Format => 'int32s', + ValueConv => '$val * 180 / 0x7fffffff', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', +); +my %altInfo = ( + Format => 'int16u', + ValueConv => '$val * 19900 / 0xffff - 900', + PrintConv => 'sprintf("%.2f m", $val)', +); + +# default format based on size of unknown KLV information +my %defaultFormat = ( + 1 => 'int8u', + 2 => 'int16u', + 4 => 'int32u', + 8 => 'int64u', +); + +%Image::ExifTool::MISB::Main = ( + GROUPS => { 0 => 'MISB', 1 => 'MISB', 2 => 'Other' }, + VARS => { LONG_TAGS => 2 }, + NOTES => q{ + These tags are extracted from STANAG-4609 MISB (Motion Industry Standards + Board) KLV-format metadata in M2TS videos. + }, + '060e2b34020b01010e01030101000000' => { + Name => 'UASDataLink', + SubDirectory => { TagTable => 'Image::ExifTool::MISB::UASDatalink' }, + }, + '060e2b3402030101434e415644494147' => { # "CNAVDIAG" written by ChurchillNavigation ION + Name => 'ChurchillNav', + SubDirectory => { + TagTable => 'Image::ExifTool::MISB::ChurchillNav', + ByteOrder => 'LittleEndian', # !! + }, + }, + '060E2B34030101010E01030302000000' => { # (NC) + Name => 'Security', + SubDirectory => { TagTable => 'Image::ExifTool::MISB::Security' }, + }, + '<other>' => { + Name => 'Unknown', + SubDirectory => { TagTable => 'Image::ExifTool::MISB::Unknown' }, + }, +); + +# UAS datalink local set tags (ref 2, MISB ST 0601.11) +%Image::ExifTool::MISB::UASDatalink = ( + GROUPS => { 0 => 'MISB', 1 => 'MISB', 2 => 'Location' }, + PROCESS_PROC => \&ProcessKLV, + NOTES => 'Tags extracted from the MISB ST 0601.11 UAS Datalink local set.', + 1 => { Name => 'Checksum', Format => 'int16u' }, + 2 => { Name => 'GPSDateTime', %timeInfo }, + 3 => { Name => 'MissionID', Format => 'string' }, + 4 => { Name => 'TailNumber', Format => 'string' }, + 5 => { Name => 'GPSTrack', Format => 'int16u', ValueConv => '$val * 360 / 0xffff' }, + 6 => { Name => 'PitchAngle', Format => 'int16s', ValueConv => '$val * 20 / 0x7fff' }, + 7 => { Name => 'RollAngle', Format => 'int16s', ValueConv => '$val * 50 / 0x7fff' }, + 8 => { Name => 'TrueAirspeed', Format => 'int8u', PrintConv => '"$val m/s"' }, + 9 => { Name => 'IndicatedAirspeed', Format => 'int8u', PrintConv => '"$val m/s"' }, + 10 => { Name => 'ProjectIDCode', Format => 'string' }, + 11 => { Name => 'SensorName', Format => 'string' }, + 12 => { Name => 'ImageCoordinateSystem', Format => 'string' }, + 13 => { Name => 'GPSLatitude', %latInfo }, + 14 => { Name => 'GPSLongitude', %lonInfo }, + 15 => { Name => 'GPSAltitude', %altInfo }, + 16 => { Name => 'HorizontalFieldOfView', Format => 'int16u', ValueConv => '$val * 180 / 0xffff' }, + 17 => { Name => 'VerticalFieldOfView', Format => 'int16u', ValueConv => '$val * 180 / 0xffff' }, + 18 => { Name => 'SensorRelativeAzimuthAngle',Format=> 'int32u', ValueConv => '$val * 360 / 0xffffffff' }, + 19 => { Name => 'SensorRelativeElevationAngle',Format=>'int32s',ValueConv => '$val * 180 / 0x7fffffff' }, + 20 => { Name => 'SensorRelativeRollAngle', Format => 'int32u', ValueConv => '$val * 360 / 0xffffffff' }, + 21 => { Name => 'SlantRange', Format => 'int32u', ValueConv => '$val * 5000000 / 0xffffffff' }, + 22 => { Name => 'TargetWidth', Format => 'int16u', ValueConv => '$val * 10000 / 0xffff' }, + 23 => { Name => 'FrameCenterLatitude', %latInfo }, + 24 => { Name => 'FrameCenterLongitude', %lonInfo }, + 25 => { Name => 'FrameCenterElevation', %altInfo }, + 26 => { Name => 'OffsetCornerLatitude1', Format => 'int16s', ValueConv => '$val * .075 / 0x7fff' }, + 27 => { Name => 'OffsetCornerLongitude1', Format => 'int16s', ValueConv => '$val * .075 / 0x7fff' }, + 28 => { Name => 'OffsetCornerLatitude2', Format => 'int16s', ValueConv => '$val * .075 / 0x7fff' }, + 29 => { Name => 'OffsetCornerLongitude2', Format => 'int16s', ValueConv => '$val * .075 / 0x7fff' }, + 30 => { Name => 'OffsetCornerLatitude3', Format => 'int16s', ValueConv => '$val * .075 / 0x7fff' }, + 31 => { Name => 'OffsetCornerLongitude3', Format => 'int16s', ValueConv => '$val * .075 / 0x7fff' }, + 32 => { Name => 'OffsetCornerLatitude4', Format => 'int16s', ValueConv => '$val * .075 / 0x7fff' }, + 33 => { Name => 'OffsetCornerLongitude4', Format => 'int16s', ValueConv => '$val * .075 / 0x7fff' }, + 34 => { Name => 'IcingDetected', Format => 'int8u', PrintConv => { 0 => 'n/a', 1 => 'No', 2 => 'Yes' } }, + 35 => { Name => 'WindDirection', Format => 'int16u', ValueConv => '$val * 360 / 0xffff' }, + 36 => { Name => 'WindSpeed', Format => 'int8u', ValueConv => '$val * 100 / 0xff', Notes => 'm/s' }, + 37 => { Name => 'StaticPressure', Format => 'int16u', ValueConv => '$val * 5000 / 0xffff', Notes => 'mbar' }, + 38 => { Name => 'DensityAltitude', Format => 'int16u', ValueConv => '$val * 19900 / 0xffff - 900' }, + 39 => { Name => 'AirTemperature', Format => 'int8s' }, + 40 => { Name => 'TargetLocationLatitude', %latInfo }, + 41 => { Name => 'TargetLocationLongitude', %lonInfo }, + 42 => { Name => 'TargetLocationElevation', %altInfo }, + 43 => { Name => 'TargetTrackGateWidth', Format => 'int8u' }, + 44 => { Name => 'TargetTrackGateHeight', Format => 'int8u' }, + 45 => { Name => 'TargetErrorEstimateCE90', Format => 'int16u' }, + 46 => { Name => 'TargetErrorEstimateLE90', Format => 'int16u' }, + 47 => { Name => 'GenericFlagData01', + Format => 'int8u', + PrintConv => { BITMASK => { + 0 => 'Laser range', + 1 => 'Auto-track', + 2 => 'IR polarity black', + 3 => 'Icing detected', + 4 => 'Slant range measured', + 5 => 'Image invalid', + }}, + }, + 48 => { Name => 'SecurityLocalMetadataSet', SubDirectory => { TagTable => 'Image::ExifTool::MISB::Security' } }, + 49 => { Name => 'DifferentialPressure', Format => 'int16u', ValueConv => '$val * 5000 / 0xffff' }, + 50 => { Name => 'AngleOfAttack', Format => 'int16s', ValueConv => '$val * 20 / 0x7fff' }, + 51 => { Name => 'VerticalSpeed', Format => 'int16s', ValueConv => '$val * 180 / 0x7fff', Notes => 'm/s' }, + 52 => { Name => 'SideslipAngle', Format => 'int16s', ValueConv => '$val * 20 / 0x7fff' }, + 53 => { Name => 'AirfieldBarometricPressure',Format=> 'int16u', ValueConv => '$val * 5000 / 0xffff' }, + 54 => { Name => 'AirfieldElevation', %altInfo }, + 55 => { Name => 'RelativeHumidity', Format => 'int8u', ValueConv => '$val * 100 / 0xff' }, + 56 => { Name => 'GPSSpeed', Format => 'int8u', Notes => 'm/s' }, + 57 => { Name => 'GroundRange', Format => 'int32u', ValueConv => '$val * 5000000 / 0xffffffff' }, + 58 => { Name => 'FuelRemaining', Format => 'int16u', ValueConv => '$val * 10000 / 0xffff', Notes => 'kg' }, + 59 => { Name => 'CallSign', Format => 'string' }, + 60 => { Name => 'WeaponLoad', Format => 'int16u', PrintConv => 'sprintf("0x%.4x",$val)' }, + 61 => { Name => 'WeaponFired', Format => 'int8u', PrintConv => 'sprintf("0x%.2x",$val)' }, + 62 => { Name => 'LaserPRFCode', Format => 'int16u' }, + 63 => { Name => 'SensorFieldOfViewName', Format => 'int8u', + PrintConv => { + 0 => 'Ultranarrow', + 1 => 'Narrow', + 2 => 'Medium', + 3 => 'Wide', + 4 => 'Ultrawide', + 5 => 'Narrow Medium', + 6 => '2x Ultranarrow', + 7 => '4x Ultranarrow', + }, + }, + 64 => { Name => 'MagneticHeading', Format => 'int16u', ValueConv => '$val * 360 / 0xffff' }, + 65 => { Name => 'UAS_LSVersionNumber', Format => 'int8u' }, + 66 => { Name => 'TargetLocationCovarianceMatrix', Format => 'undef', ValueConv => '\$val' }, + 67 => { Name => 'AlternateLatitude', %latInfo }, + 68 => { Name => 'AlternateLongitude', %lonInfo }, + 69 => { Name => 'AlternateAltitude', %altInfo }, + 70 => { Name => 'AlternateName', Format => 'string' }, + 71 => { Name => 'AlternateHeading', Format => 'int16u', ValueConv => '$val * 360 / 0xffff' }, + 72 => { Name => 'EventStartTime', %timeInfo }, + 73 => { Name => 'RVTLocalSet', SubDirectory => { TagTable => 'Image::ExifTool::MISB::Unknown' } }, + 74 => { Name => 'VMTIDataSet', SubDirectory => { TagTable => 'Image::ExifTool::MISB::Unknown' } }, + 75 => { Name => 'SensorEllipsoidHeight', %altInfo }, + 76 => { Name => 'AlternateEllipsoidHeight', %altInfo }, + 77 => { Name => 'OperationalMode', Format => 'int8u', + PrintConv => { + 0 => 'Other', + 1 => 'Operational', + 2 => 'Training', + 3 => 'Exercise', + 4 => 'Maintenance', + }, + }, + 78 => { Name => 'FrameCenterHeightAboveEllipsoid', %altInfo }, + 79 => { Name => 'SensorVelocityNorth', Format => 'int16s', ValueConv => '$val * 327 / 0x7fff' }, + 80 => { Name => 'SensorVelocityEast', Format => 'int16s', ValueConv => '$val * 327 / 0x7fff' }, + 81 => { Name => 'ImageHorizonPixelPack', Format => 'undef', ValueConv => '\$val' }, + 82 => { Name => 'CornerLatitude1', %latInfo }, + 83 => { Name => 'CornerLongitude1', %lonInfo }, + 84 => { Name => 'CornerLatitude2', %latInfo }, + 85 => { Name => 'CornerLongitude2', %lonInfo }, + 86 => { Name => 'CornerLatitude3', %latInfo }, + 87 => { Name => 'CornerLongitude3', %lonInfo }, + 88 => { Name => 'CornerLatitude4', %latInfo }, + 89 => { Name => 'CornerLongitude4', %lonInfo }, + 90 => { Name => 'FullPitchAngle', Format => 'int32s', ValueConv => '$val * 90 / 0x7fffffff' }, + 91 => { Name => 'FullRollAngle', Format => 'int32s', ValueConv => '$val * 90 / 0x7fffffff' }, + 92 => { Name => 'FullAngleOfAttack', Format => 'int32s', ValueConv => '$val * 90 / 0x7fffffff' }, + 93 => { Name => 'FullSideslipAngle', Format => 'int32s', ValueConv => '$val * 90 / 0x7fffffff' }, + 94 => { Name => 'MIISCoreIdentifier', Format => 'undef', ValueConv => '\$val' }, + 95 => { Name => 'SARMotionImageryData', SubDirectory => { TagTable => 'Image::ExifTool::MISB::Unknown' } }, + 96 => { Name => 'TargetWidthExtended', Format => 'undef', ValueConv => '\$val' }, # IMAPB format + 97 => { Name => 'RangeImageLocalSet', SubDirectory => { TagTable => 'Image::ExifTool::MISB::Unknown' } }, + 98 => { Name => 'GeoregistrationLocalSet', SubDirectory => { TagTable => 'Image::ExifTool::MISB::Unknown' } }, + 99 => { Name => 'CompositeImagingLocalSet', SubDirectory => { TagTable => 'Image::ExifTool::MISB::Unknown' } }, + 100=> { Name => 'SegmentLocalSet', SubDirectory => { TagTable => 'Image::ExifTool::MISB::Unknown' } }, + 101=> { Name => 'AmendLocalSet', SubDirectory => { TagTable => 'Image::ExifTool::MISB::Unknown' } }, + 102=> { Name => 'SDCC-FLP', Format => 'undef', ValueConv => '\$val' }, # IMAPB format + 103=> { Name => 'DensityAltitudeExtended', Format => 'undef', ValueConv => '\$val' }, # IMAPB format + 104=> { Name => 'SensorEllipsoidHeightExtended', Format => 'undef', ValueConv => '\$val' }, # IMAPB format + 105=> { Name => 'AlternateEllipsoidHeightExtended', Format => 'undef', ValueConv => '\$val' }, # IMAPB format +); + +# tags from MISB ST 0102.11 local set +%Image::ExifTool::MISB::Security = ( + GROUPS => { 0 => 'MISB', 1 => 'MISB', 2 => 'Document' }, + PROCESS_PROC => \&ProcessKLV, + NOTES => 'Tags extracted from the MISB ST 0102.11 Security Metadata local set.', + 1 => { Name => 'SecurityClassification', PrintConv => { + 1 => 'Unclassified', + 2 => 'Restricted', + 3 => 'Confidential', + 4 => 'Secret', + 5 => 'Top Secret', + }}, + 2 => { Name => 'ClassifyingCountryCodeMethod', PrintConv => { + 0x01 => 'ISO-3166 Two Letter', + 0x02 => 'ISO-3166 Three Letter', + 0x03 => 'FIPS 10-4 Two Letter', + 0x04 => 'FIPS 10-4 Four Letter', + 0x05 => 'ISO-3166 Numeric', + 0x06 => '1059 Two Letter', + 0x07 => '1059 Three Letter', + 0x0a => 'FIPS 10-4 Mixed', + 0x0b => 'ISO 3166 Mixed', + 0x0c => 'STANAG 1059 Mixed', + 0x0d => 'GENC Two Letter', + 0x0e => 'GENC Three Letter', + 0x0f => 'GENC Numeric', + 0x10 => 'GENC Mixed', + }}, + 3 => { Name => 'ClassifyingCountry', Format => 'string', PrintConv => '$val =~ s(^//)(); $val' }, + 4 => 'SecuritySCI-SHIInformation', + 5 => { Name => 'Caveats', Format => 'string' }, + 6 => { Name => 'ReleasingInstructions',Format => 'string' }, + 7 => { Name => 'ClassifiedBy', Format => 'string' }, + 8 => { Name => 'DerivedFrom', Format => 'string' }, + 9 => { Name => 'ClassificationReason', Format => 'string' }, + 10 => { + Name => 'DeclassificationDate', + Format => 'string', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{4})(\d{2})(\d{2})/$1:$2:$3/; $val', + }, + 11 => 'ClassificationAndMarkingSystem', + 12 => { Name => 'ObjectCountryCodingMethod', PrintConv => { + 0x01 => 'ISO-3166 Two Letter', + 0x02 => 'ISO-3166 Three Letter', + 0x03 => 'ISO-3166 Numeric', + 0x04 => 'FIPS 10-4 Two Letter', + 0x05 => 'FIPS 10-4 Four Letter', + 0x06 => '1059 Two Letter', + 0x07 => '1059 Three Letter', + 0x0d => 'GENC Two Letter', + 0x0e => 'GENC Three Letter', + 0x0f => 'GENC Numeric', + 0x40 => 'GENC AdminSub', + }}, + 13 => { Name => 'ObjectCountryCodes', Format => 'string', PrintConv => '$val =~ s(^//)(); $val' }, + 14 => { Name => 'ClassificationComments', Format => 'string' }, + 15 => 'UMID', + 16 => 'StreamID', + 17 => 'TransportStreamID', + 21 => 'ItemDesignatorID', + 22 => { Name => 'SecurityVersion', Format => 'int16u', PrintConv => '"0102.$val"' }, + 23 => { + Name => 'ClassifyingCountryCodingMethodDate', + Groups => { 2 => 'Time' }, + Format => 'string', + ValueConv => '$val=~tr/-/:/; $val', + }, + 24 => { + Name => 'ObjectCountryCodingMethodDate', + Groups => { 2 => 'Time' }, + Format => 'string', + ValueConv => '$val=~tr/-/:/; $val', + }, +); + +# I have seen these, but don't know what they are for - PH +# (they look interesting, but remain static through my sample video) +%Image::ExifTool::MISB::ChurchillNav = ( + GROUPS => { 0 => 'MISB', 1 => 'MISB', 2 => 'Other' }, + PROCESS_PROC => \&ProcessKLV, + TAG_PREFIX => 'ChurchillNav', + NOTES => q{ + Proprietary tags used by Churchill Navigation units. These tags are all + currently unknown, but extracted with the Unknown option. + }, + # Note: tag ID's are decimal (because the MISB specification uses decimal ID's) + 1 => { Name => 'ChurchillNav_0x0001', Format => 'double', Unknown => 1, Hidden => 1 }, + 2 => { Name => 'ChurchillNav_0x0002', Format => 'double', Unknown => 1, Hidden => 1 }, + 3 => { Name => 'ChurchillNav_0x0003', Format => 'double', Unknown => 1, Hidden => 1 }, + 4 => { Name => 'ChurchillNav_0x0004', Format => 'double', Unknown => 1, Hidden => 1 }, + 5 => { Name => 'ChurchillNav_0x0005', Format => 'double', Unknown => 1, Hidden => 1 }, + 6 => { Name => 'ChurchillNav_0x0006', Format => 'double', Unknown => 1, Hidden => 1 }, + 9 => { Name => 'ChurchillNav_0x0009', Format => 'double', Unknown => 1, Hidden => 1 }, + 10 => { Name => 'ChurchillNav_0x000a', Format => 'double', Unknown => 1, Hidden => 1 }, + 11 => { Name => 'ChurchillNav_0x000b', Format => 'string', Unknown => 1, Hidden => 1 }, + 12 => { Name => 'ChurchillNav_0x000c', Format => 'double', Unknown => 1, Hidden => 1 }, + 13 => { Name => 'ChurchillNav_0x000d', Format => 'double', Unknown => 1, Hidden => 1 }, + 14 => { Name => 'ChurchillNav_0x000e', Format => 'double', Unknown => 1, Hidden => 1 }, + 16 => { Name => 'ChurchillNav_0x0010', Format => 'double', Unknown => 1, Hidden => 1 }, + 17 => { Name => 'ChurchillNav_0x0011', Format => 'double', Unknown => 1, Hidden => 1 }, + 18 => { Name => 'ChurchillNav_0x0012', Format => 'double', Unknown => 1, Hidden => 1 }, + 20 => { Name => 'ChurchillNav_0x0014', Format => 'double', Unknown => 1, Hidden => 1 }, +); + +%Image::ExifTool::MISB::Unknown = ( + GROUPS => { 0 => 'MISB', 1 => 'MISB', 2 => 'Other' }, + PROCESS_PROC => \&ProcessKLV, + NOTES => 'Other tags are extracted with the Unknown option.', +); + +#------------------------------------------------------------------------------ +# Process KLV (Key-Length-Value) metadata +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 if anything was extracted +sub ProcessKLV($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dirEnd = $dirStart + $$dirInfo{DirLen}; + my $rtnVal = 0; + my $pos; + + $et->VerboseDir($$dirInfo{DirName}, undef, $$dirInfo{DirLen}); + + # loop through KLV packets + for ($pos=$dirStart; $pos<$dirEnd-1; ) { + my $tag = Get8u($dataPt, $pos++); + my $len = Get8u($dataPt, $pos++); + if ($len & 0x80) { + my $n = $len & 0x7f; + last if $pos + $n > $dirEnd; + $len = 0; + $len = $len * 256 + Get8u($dataPt, $pos++) foreach 1..$n; + } + last if $pos + $len > $dirEnd; + # best guess at decoding the value + my $val; + my $tagInfo = $$tagTablePtr{$tag} || { }; + my $format = $$tagInfo{Format} || $defaultFormat{$len}; + if ($format) { + $val = ReadValue($dataPt, $pos, $format, undef, $len); + } else { + # treat as string or binary data + $val = substr($$dataPt, $pos, $len); + if ($val !~ /^[\t\n\r\x20-\x7e]*$/) { + my $dat = $val; + $val = \$val; + } + } + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + Start => $pos, + Size => $len, + ); + $rtnVal = 1; + $pos += $len; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Parse MISB metadata +# Inputs: 0) ExifTool ref, 1) data ref, 2) tag table ref +# Returns: 1 if something was extracted, 0 otherwise +sub ParseMISB($$$) +{ + my ($et, $dataPt, $tagTablePtr) = @_; + my $end = length $$dataPt; + my $rtnVal = 0; + my $unknown = $$et{OPTIONS}{Unknown}; + my $verbose = $$et{OPTIONS}{Verbose}; + my $pos; + + # increment document number in case we find any tags + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $$et{INDENT} .= '| '; + + # skip the 5-byte header (ref 1 pg. 68) + # 0 - int8u: metadata service ID (0x00) + # 1 - int8u: sequence number (increments each packet) + # 2 - int8u: 0x0f (bits: cell fragmentation 00, decoder config 0, random access 0, reserved 1111) + # 3-4 - int16u: data length (ie. packet length - 5) + for ($pos = 5; $pos + 16 < $end; ) { + my $key = unpack('H*', substr($$dataPt, $pos, 16)); + $pos += 16; + my $len = Get8u($dataPt, $pos); + ++$pos; + if ($len & 0x80) { # is this a BER long form integer? (Basic Encoding Rules) + my $n = $len & 0x7f; + $len = 0; + return $rtnVal if $pos + $n > $end; + $len = $len * 256 + Get8u($dataPt, $pos++) foreach 1..$n; + } + my $tagInfo = $$tagTablePtr{$key}; + unless ($tagInfo) { + if ($verbose or $unknown) { + # (assume this is a data set, but it maybe be a simple tag) + $tagInfo = { Name => "MISB_$key", SubDirectory => { TagTable => 'Image::ExifTool::MISB::Unknown' } }; + $et->VPrint(0," [adding $$tagInfo{Name}]\n"); + AddTagToTable($tagTablePtr, $key, $tagInfo); + } else { + # skip this record + $pos += $len; + next; + } + } + if ($pos + $len > $end) { + $len = $end - $pos; + $et->VPrint(0, "$$et{INDENT}(truncated record, only $len bytes available)\n"); + } + my $dir = $$tagInfo{SubDirectory}; + SetByteOrder($$dir{ByteOrder}) if $$dir{ByteOrder}; + my %dirInfo = ( + DataPt => $dataPt, + DirStart => $pos, + DirLen => $len, + DirName => $$tagInfo{Name}, + ); + ProcessKLV($et, \%dirInfo, GetTagTable($$dir{TagTable})) and $rtnVal = 1; + SetByteOrder('MM'); + $pos += $len; + } + $$et{INDENT} = substr($$et{INDENT},0,-2); + delete $$et{DOC_NUM}; + --$$et{DOC_COUNT} unless $rtnVal; + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MISB - Read Motion Industry Standards Board metadata + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains code to extract STANAG-4609 Motion Industry Standards +Board (MISB) KLV-format metadata from M2TS videos. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://dokumen.tips/documents/nato-standardization-agreement-stanag-4609-ed-3.html> + +=item L<https://upload.wikimedia.org/wikipedia/commons/1/19/MISB_Standard_0601.pdf> + +=item L<https://dokumen.tips/documents/misb-st-010211-standard-security-metadata-universal-standard-describes-the-use.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MISB Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MNG.pm b/ExifTool/lib/Image/ExifTool/MNG.pm new file mode 100644 index 0000000..369e660 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MNG.pm @@ -0,0 +1,684 @@ +#------------------------------------------------------------------------------ +# File: MNG.pm +# +# Description: MNG and JNG meta information tags +# +# Revisions: 06/23/2005 - P. Harvey Created +# +# References: 1) http://www.libpng.org/pub/mng/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::MNG; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.00'; + +# MNG chunks +%Image::ExifTool::MNG::Main = ( + GROUPS => { 2 => 'Image' }, + NOTES => q{ + This table contains definitions for tags found in MNG and JNG images. MNG + is a superset of PNG and JNG, so a MNG image may contain any of these tags + as well as any PNG tags. Conversely, only some of these tags are valid for + JNG images. + }, + BACK => { + Name => 'Background', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::Background' }, + }, + BASI => { + Name => 'BasisObject', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::BasisObject' }, + }, + CLIP => { + Name => 'ClipObjects', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::ClipObjects' }, + }, + CLON => { + Name => 'CloneObject', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::CloneObject' }, + }, + DBYK => { + Name => 'DropByKeyword', + Binary => 1, + }, + DEFI => { + Name => 'DefineObject', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::DefineObject' }, + }, + DHDR => { + Name => 'DeltaPNGHeader', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::DeltaPNGHeader' }, + }, + DISC => { + Name => 'DiscardObjects', + ValueConv => 'join(" ",unpack("n*",$val))', + }, + DROP => { + Name => 'DropChunks', + ValueConv => 'join(" ",$val=~/..../g)', + }, +# ENDL + eXPi => { + Name => 'ExportImage', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::ExportImage' }, + }, + fPRI => { + Name => 'FramePriority', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::FramePriority' }, + }, + FRAM => { + Name => 'Frame', + Binary => 1, + }, +# IJNG +# IPNG +# JDAA (JNG) +# JDAT (JNG) + JHDR => { # (JNG) + Name => 'JNGHeader', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::JNGHeader' }, + }, +# JSEP (JNG) + LOOP => { + Name => 'Loop', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::Loop' }, + }, + MAGN => { + Name => 'MagnifyObject', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::MagnifyObject' }, + }, +# MEND + MHDR => { + Name => 'MNGHeader', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::MNGHeader' }, + }, + MOVE => { + Name => 'MoveObjects', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::MoveObjects' }, + }, + nEED => { + Name => 'ResourcesNeeded', + Binary => 1, + }, + ORDR => { + Name => 'OrderingRestrictions', + Binary => 1, + }, + PAST => { + Name => 'PasteImage', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::PasteImage' }, + }, + pHYg => { + Name => 'GlobalPixelSize', + SubDirectory => { TagTable => 'Image::ExifTool::PNG::PhysicalPixel' }, + }, + PPLT => { + Name => 'PartialPalette', + Binary => 1, + }, + PROM => { + Name => 'PromoteParent', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::PromoteParent' }, + }, + SAVE => { + Name => 'SaveObjects', + Binary => 1, + }, + SEEK => { + Name => 'SeekPoint', + ValueConv => '$val=~s/\0.*//s; $val', + }, + SHOW => { + Name => 'ShowObjects', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::ShowObjects' }, + }, + TERM => { + Name => 'TerminationAction', + SubDirectory => { TagTable => 'Image::ExifTool::MNG::TerminationAction' }, + }, +); + +# MNG MHDR chunk +%Image::ExifTool::MNG::MNGHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int32u', + 0 => 'ImageWidth', + 1 => 'ImageHeight', + 2 => 'TicksPerSecond', + 3 => 'NominalLayerCount', + 4 => 'NominalFrameCount', + 5 => 'NominalPlayTime', + 6 => { + Name => 'SimplicityProfile', + PrintConv => 'sprintf("0x%.8x", $val)', + }, +); + +# MNG BASI chunk +%Image::ExifTool::MNG::BasisObject = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'ImageWidth', + Format => 'int32u', + }, + 4 => { + Name => 'ImageHeight', + Format => 'int32u', + }, + 8 => 'BitDepth', + 9 => { + Name => 'ColorType', + RawConv => '$Image::ExifTool::PNG::colorType = $val', + PrintConv => { + 0 => 'Grayscale', + 2 => 'RGB', + 3 => 'Palette', + 4 => 'Grayscale with Alpha', + 6 => 'RGB with Alpha', + }, + }, + 10 => { + Name => 'Compression', + PrintConv => { 0 => 'Deflate/Inflate' }, + }, + 11 => { + Name => 'Filter', + PrintConv => { 0 => 'Adaptive' }, + }, + 12 => { + Name => 'Interlace', + PrintConv => { 0 => 'Noninterlaced', 1 => 'Adam7 Interlace' }, + }, + 13 => { + Name => 'RedSample', + Format => 'int32u', + }, + 17 => { + Name => 'GreenSample', + Format => 'int32u', + }, + 21 => { + Name => 'BlueSample', + Format => 'int32u', + }, + 25 => { + Name => 'AlphaSample', + Format => 'int32u', + }, + 26 => 'Viewable', +); + +# MNG LOOP chunk +%Image::ExifTool::MNG::Loop = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => 'NestLevel', + 1 => { + Name => 'IterationCount', + Format => 'int32u', + }, + 5 => { + Name => 'TerminationCondition', + PrintConv => { + 0 => 'Deterministic, not cacheable', + 1 => 'Decoder discretion, not cacheable', + 2 => 'User discretion, not cacheable', + 3 => 'External signal, not cacheable', + 4 => 'Deterministic, cacheable', + 5 => 'Decoder discretion, cacheable', + 6 => 'User discretion, cacheable', + 7 => 'External signal, cacheable', + }, + }, + 6 => { + Name => 'IterationMinMax', + Format => 'int32u[2]', + }, + 14 => { + Name => 'SignalNumber', + Format => 'int32u', + }, +); + +# MNG DEFI chunk +%Image::ExifTool::MNG::DefineObject = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'ObjectID', + Format => 'int16u', + }, + 2 => 'DoNotShow', + 3 => 'ConcreteFlag', + 4 => { + Name => 'XYLocation', + Format => 'int32u[2]', + }, + 12 => { + Name => 'ClippingBoundary', + Format => 'int32u[4]', + }, +); + +# MNG CLON chunk +%Image::ExifTool::MNG::CloneObject = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'SourceID', + Format => 'int16u', + }, + 2 => { + Name => 'CloneID', + Format => 'int16u', + }, + 4 => { + Name => 'CloneType', + PrintConv => { 0 => 'Full', 1 => 'Parital', 2 => 'Renumber object' }, + }, + 5 => 'DoNotShow', + 6 => 'ConcreteFlag', + 7 => { + Name => 'LocalDeltaType', + PrintConv => { 0 => 'Absolute', 1 => 'Relative' }, + }, + 8 => { + Name => 'DeltaXY', + Format => 'int32u[2]', + }, +); + +# MNG PAST chunk +%Image::ExifTool::MNG::PasteImage = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'DestinationID', + Format => 'int16u', + }, + 2 => { + Name => 'TargetDeltaType', + PrintConv => { 0 => 'Absolute', 1 => 'Relative' }, + }, + 3 => { + Name => 'TargetXY', + Format => 'int32u[2]', + }, + 11 => { + Name => 'SourceID', + Format => 'int16u', + }, + 13 => { + Name => 'CompositionMode', + PrintConv => { 0 => 'Over', 1 => 'Replace', 2 => 'Under' }, + }, + 14 => { + Name => 'Orientation', + PrintConv => { + 0 => 'Same as source', + 2 => 'Flipped left-right, then up-down', + 4 => 'Flipped left-right', + 6 => 'Flipped up-down', + 8 => 'Tiled', + }, + }, + 15 => { + Name => 'OffsetOrigin', + PrintConv => { 0 => 'Desination Origin', 1 => 'Target Origin' }, + }, + 16 => { + Name => 'OffsetXY', + Format => 'int32u[2]', + }, + 24 => { + Name => 'BoundaryOrigin', + PrintConv => { 0 => 'Desination Origin', 1 => 'Target Origin' }, + }, + 25 => { + Name => 'PastClippingBoundary', + Format => 'int32u[4]', + }, +); + +my %magMethod = ( + 0 => 'No Magnification', + 1 => 'Pixel Replication', + 2 => 'Linear Interpolation', + 3 => 'Closest Pixel', + 4 => 'Color Linear Interpolation and Alpha Closest Pixel', + 5 => 'Color Closest Pixel and Alpha Linear Interpolation', +); + +# MNG MAGN chunk +%Image::ExifTool::MNG::MagnifyObject = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'FirstObjectID', + Format => 'int16u', + }, + 2 => { + Name => 'LastObjectID', + Format => 'int16u', + }, + 4 => { + Name => 'XMethod', + PrintConv => \%magMethod, + }, + 5 => { + Name => 'XMag', + Format => 'int16u', + }, + 7 => { + Name => 'YMag', + Format => 'int16u', + }, + 9 => { + Name => 'LeftMag', + Format => 'int16u', + }, + 11 => { + Name => 'RightMag', + Format => 'int16u', + }, + 13 => { + Name => 'TopMag', + Format => 'int16u', + }, + 15 => { + Name => 'BottomMag', + Format => 'int16u', + }, + 17 => { + Name => 'YMethod', + PrintConv => \%magMethod, + }, +); + +# MNG TERM chunk +%Image::ExifTool::MNG::TerminationAction = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'TerminationAction', + PrintConv => { + 0 => 'Show Last Frame', + 1 => 'Display Nothing', + 2 => 'Show First Frame', + 3 => 'Repeat Sequence', + }, + }, + 1 => { + Name => 'IterationEndAction', + PrintConv => { + 0 => 'Show Last Frame', + 1 => 'Display Nothing', + 2 => 'Show First Frame', + }, + }, + 2 => { + Name => 'Delay', + Format => 'int32u', + }, + 6 => { + Name => 'IterationMax', + Format => 'int32u', + }, +); + +# MNG BACK chunk +%Image::ExifTool::MNG::Background = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'BackgroundColor', + Format => 'int16u[3]', + }, + 6 => { + Name => 'MandatoryBackground', + PrintConv => { + 0 => 'Color and Image Advisory', + 1 => 'Color Mandatory, Image Advisory', + 2 => 'Color Advisory, Image Mandatory', + 3 => 'Color and Image Mandatory', + }, + }, + 7 => { + Name => 'BackgroundImageID', + Format => 'int16u', + }, + 9 => { + Name => 'BackgroundTiling', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, +); + +# MNG MOVE chunk +%Image::ExifTool::MNG::MoveObjects = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'FirstObject', + Format => 'int16u', + }, + 2 => { + Name => 'LastObject', + Format => 'int16u', + }, + 4 => { + Name => 'DeltaType', + PrintConv => { 0 => 'Absolute', 1 => 'Relative' }, + }, + 5 => { + Name => 'DeltaXY', + Format => 'int32u[2]', + }, +); + +# MNG CLIP chunk +%Image::ExifTool::MNG::ClipObjects = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'FirstObject', + Format => 'int16u', + }, + 2 => { + Name => 'LastObject', + Format => 'int16u', + }, + 4 => { + Name => 'DeltaType', + PrintConv => { 0 => 'Absolute', 1 => 'Relative' }, + }, + 5 => { + Name => 'ClipBoundary', + Format => 'int32u[4]', + }, +); + +# MNG SHOW chunk +%Image::ExifTool::MNG::ShowObjects = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'FirstObject', + Format => 'int16u', + }, + 2 => { + Name => 'LastObject', + Format => 'int16u', + }, + 4 => 'ShowMode', +); + +# MNG eXPI chunk +%Image::ExifTool::MNG::ExportImage = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'SnapshotID', + Format => 'int16u', + }, + 2 => { + Name => 'SnapshotName', + Format => 'string', + }, +); + +# MNG fPRI chunk +%Image::ExifTool::MNG::FramePriority = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'DeltaType', + PrintConv => { 0 => 'Absolute', 1 => 'Relative' }, + }, + 2 => 'Priority', +); + +# MNG DHDR chunk +%Image::ExifTool::MNG::DeltaPNGHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'ObjectID', + Format => 'int16u', + }, + 2 => { + Name => 'ImageType', + PrintConv => { + 0 => 'Unspecified', + 1 => 'PNG', + 2 => 'JNG', + }, + }, + 3 => { + Name => 'DeltaType', + PrintConv => { + 0 => 'Full Replacement', + 1 => 'Pixel Addition', + 2 => 'Alpha Addition', + 3 => 'Color Addition', + 4 => 'Pixel Replacement', + 5 => 'Alpha Replacement', + 6 => 'Color Replacement', + 7 => 'No Change', + }, + }, + 4 => { + Name => 'BlockSize', + Format => 'int32u[2]', + }, + 12 => { + Name => 'BlockLocation', + Format => 'int32u[2]', + }, +); + +# MNG PROM chunk +%Image::ExifTool::MNG::PromoteParent = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => 'NewColorType', + 1 => 'NewBitDepth', + 2 => { + Name => 'FillMethod', + PrintConv => { 0 => 'Bit Replication', 1 => 'Zero Fill' }, + }, +); + +# JNG JHDR chunk +%Image::ExifTool::MNG::JNGHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'ImageWidth', + Format => 'int32u', + }, + 4 => { + Name => 'ImageHeight', + Format => 'int32u', + }, + 8 => { + Name => 'ColorType', + PrintConv => { + 8 => 'Gray', + 10 => 'Color', + 12 => 'Gray Alpha', + 14 => 'Color Alpha', + }, + }, + 9 => 'BitDepth', + 10 => { + Name => 'Compression', + PrintConv => { 8 => 'Huffman-coded baseline JPEG' }, + }, + 11 => { + Name => 'Interlace', + PrintConv => { 0 => 'Sequential', 8 => 'Progressive' }, + }, + 12 => 'AlphaBitDepth', + 13 => { + Name => 'AlphaCompression', + PrintConv => { + 0 => 'MNG Grayscale IDAT', + 8 => 'JNG 8-bit Grayscale JDAA', + }, + }, + 14 => { + Name => 'AlphaFilter', + PrintConv => { 0 => 'Adaptive MNG (N/A for JPEG)' }, + }, + 15 => { + Name => 'AlphaInterlace', + PrintConv => { 0 => 'Noninterlaced' }, + }, +); + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MNG - MNG and JNG meta information tags + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read MNG +(Multi-image Network Graphics) and JNG (JPEG Network Graphics) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.libpng.org/pub/mng/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MNG Tags>, +L<Image::ExifTool::TagNames/PNG Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MOI.pm b/ExifTool/lib/Image/ExifTool/MOI.pm new file mode 100644 index 0000000..a3b49fd --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MOI.pm @@ -0,0 +1,159 @@ +#------------------------------------------------------------------------------ +# File: MOI.pm +# +# Description: Read MOI meta information +# +# Revisions: 2014/12/15 - P. Harvey Created +# +# References: 1) https://en.wikipedia.org/wiki/MOI_(file_format) +#------------------------------------------------------------------------------ + +package Image::ExifTool::MOI; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.02'; + +# MOI tags (ref 1) +%Image::ExifTool::MOI::Main = ( + GROUPS => { 2 => 'Video' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + NOTES => q{ + MOI files store information about associated MOD or TOD files, and are + written by some JVC, Canon and Panasonic camcorders. + }, + 0x00 => { Name => 'MOIVersion', Format => 'string[2]' }, + # 0x02 => { Name => 'MOIFileSize', Format => 'int32u' }, + 0x06 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Format => 'undef[8]', + Groups => { 2 => 'Time' }, + ValueConv => sub { + my $val = shift; + return undef unless length($val) >= 8; + my @v = unpack('nCCCCn', $val); + $v[5] /= 1000; + return sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%06.3f', @v); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x0e => { + Name => 'Duration', + Format => 'int32u', + ValueConv => '$val / 1000', + PrintConv => 'ConvertDuration($val)', + }, + 0x80 => { + Name => 'AspectRatio', + Format => 'int8u', + PrintConv => q{ + my $lo = ($val & 0x0f); + my $hi = ($val >> 4); + my $aspect; + if ($lo < 2) { + $aspect = '4:3'; + } elsif ($lo == 4 or $lo == 5) { + $aspect = '16:9'; + } else { + $aspect = 'Unknown'; + } + if ($hi == 4) { + $aspect .= ' NTSC'; + } elsif ($hi == 5) { + $aspect .= ' PAL'; + } + return $aspect; + }, + }, + 0x84 => { + Name => 'AudioCodec', + Format => 'int16u', + Groups => { 2 => 'Audio' }, + PrintHex => 1, + PrintConv => { + 0x00c1 => 'AC3', + 0x4001 => 'MPEG', + }, + }, + 0x86 => { + Name => 'AudioBitrate', + Format => 'int8u', + Groups => { 2 => 'Audio' }, + ValueConv => '$val * 16000 + 48000', + PrintConv => 'ConvertBitrate($val)', + }, + 0xda => { + Name => 'VideoBitrate', + Format => 'int16u', + PrintHex => 1, + ValueConv => { + 0x5896 => '8500000', + 0x813d => '5500000', + }, + PrintConv => 'ConvertBitrate($val)', + }, +); + +#------------------------------------------------------------------------------ +# Validate and extract metadata from MOI file +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid MOI file +sub ProcessMOI($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + # read enough to allow skipping over run-in if it exists + $raf->Read($buff, 256) == 256 and $buff =~ /^V6/ or return 0; + if (defined $$et{VALUE}{FileSize}) { + my $size = unpack('x2N', $buff); + $size == $$et{VALUE}{FileSize} or return 0; + } + $et->SetFileType(); + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::MOI::Main'); + return $et->ProcessBinaryData({ DataPt => \$buff }, $tagTablePtr); +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MOI - Read MOI meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read meta +information from MOI files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://en.wikipedia.org/wiki/MOI_(file_format)> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MOI Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MPC.pm b/ExifTool/lib/Image/ExifTool/MPC.pm new file mode 100644 index 0000000..65d5e7a --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MPC.pm @@ -0,0 +1,156 @@ +#------------------------------------------------------------------------------ +# File: MPC.pm +# +# Description: Read Musepack audio meta information +# +# Revisions: 11/14/2006 - P. Harvey Created +# +# References: 1) http://www.musepack.net/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::MPC; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::FLAC; + +$VERSION = '1.01'; + +# MPC metadata blocks +%Image::ExifTool::MPC::Main = ( + PROCESS_PROC => \&Image::ExifTool::FLAC::ProcessBitStream, + GROUPS => { 2 => 'Audio' }, + NOTES => q{ + Tags used in Musepack (MPC) audio files. ExifTool also extracts ID3 and APE + information from these files. + }, + 'Bit032-063' => 'TotalFrames', + 'Bit080-081' => { + Name => 'SampleRate', + PrintConv => { + 0 => 44100, + 1 => 48000, + 2 => 37800, + 3 => 32000, + }, + }, + 'Bit084-087' => { + Name => 'Quality', + PrintConv => { + 1 => 'Unstable/Experimental', + 5 => '0', + 6 => '1', + 7 => '2 (Telephone)', + 8 => '3 (Thumb)', + 9 => '4 (Radio)', + 10 => '5 (Standard)', + 11 => '6 (Xtreme)', + 12 => '7 (Insane)', + 13 => '8 (BrainDead)', + 14 => '9', + 15 => '10', + }, + }, + 'Bit088-093' => 'MaxBand', + 'Bit096-111' => 'ReplayGainTrackPeak', + 'Bit112-127' => 'ReplayGainTrackGain', + 'Bit128-143' => 'ReplayGainAlbumPeak', + 'Bit144-159' => 'ReplayGainAlbumGain', + 'Bit179' => { + Name => 'FastSeek', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 'Bit191' => { + Name => 'Gapless', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 'Bit216-223' => { + Name => 'EncoderVersion', + PrintConv => '$val =~ s/(\d)(\d)(\d)$/$1.$2.$3/; $val', + }, +); + +#------------------------------------------------------------------------------ +# Extract information from an MPC file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# - Just looks for MPC trailer if FileType is already set +# Returns: 1 on success, 0 if this wasn't a valid MPC file +sub ProcessMPC($$) +{ + my ($et, $dirInfo) = @_; + + # must first check for leading ID3 information + unless ($$et{DoneID3}) { + require Image::ExifTool::ID3; + Image::ExifTool::ID3::ProcessID3($et, $dirInfo) and return 1; + } + my $raf = $$dirInfo{RAF}; + my $buff; + + # check MPC signature + $raf->Read($buff, 32) == 32 and $buff =~ /^MP\+(.)/s or return 0; + my $vers = ord($1) & 0x0f; + $et->SetFileType(); + + # extract audio information (currently only from version 7 MPC files) + if ($vers == 0x07) { + SetByteOrder('II'); + my $pos = $raf->Tell() - 32; + if ($et->Options('Verbose')) { + $et->VPrint(0, "MPC Header (32 bytes):\n"); + $et->VerboseDump(\$buff, DataPos => $pos); + } + my $tagTablePtr = GetTagTable('Image::ExifTool::MPC::Main'); + my %dirInfo = ( DataPt => \$buff, DataPos => $pos ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } else { + $et->Warn('Audio info currently not extracted from this version MPC file'); + } + + # process APE trailer if it exists + require Image::ExifTool::APE; + Image::ExifTool::APE::ProcessAPE($et, $dirInfo); + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MPC - Read Musepack audio meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Musepack (MPC) audio files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.musepack.net/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MPC Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MPEG.pm b/ExifTool/lib/Image/ExifTool/MPEG.pm new file mode 100644 index 0000000..843a2d6 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MPEG.pm @@ -0,0 +1,735 @@ +#------------------------------------------------------------------------------ +# File: MPEG.pm +# +# Description: Read MPEG-1 and MPEG-2 meta information +# +# Revisions: 05/11/2006 - P. Harvey Created +# +# References: 1) http://www.mp3-tech.org/ +# 2) http://www.getid3.org/ +# 3) http://dvd.sourceforge.net/dvdinfo/dvdmpeg.html +# 4) http://ffmpeg.org/ +# 5) http://sourceforge.net/projects/mediainfo/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::MPEG; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.17'; + +%Image::ExifTool::MPEG::Audio = ( + GROUPS => { 2 => 'Audio' }, + 'Bit11-12' => { + Name => 'MPEGAudioVersion', + RawConv => '$self->{MPEG_Vers} = $val', + PrintConv => { + 0 => 2.5, + 2 => 2, + 3 => 1, + }, + }, + 'Bit13-14' => { + Name => 'AudioLayer', + RawConv => '$self->{MPEG_Layer} = $val', + PrintConv => { + 1 => 3, + 2 => 2, + 3 => 1, + }, + }, + # Bit 15 indicates CRC protection + 'Bit16-19' => [ + { + Name => 'AudioBitrate', + Condition => '$self->{MPEG_Vers} == 3 and $self->{MPEG_Layer} == 3', + Notes => 'version 1, layer 1', + PrintConvColumns => 3, + ValueConv => { + 0 => 'free', + 1 => 32000, + 2 => 64000, + 3 => 96000, + 4 => 128000, + 5 => 160000, + 6 => 192000, + 7 => 224000, + 8 => 256000, + 9 => 288000, + 10 => 320000, + 11 => 352000, + 12 => 384000, + 13 => 416000, + 14 => 448000, + }, + PrintConv => 'ConvertBitrate($val)', + }, + { + Name => 'AudioBitrate', + Condition => '$self->{MPEG_Vers} == 3 and $self->{MPEG_Layer} == 2', + Notes => 'version 1, layer 2', + PrintConvColumns => 3, + ValueConv => { + 0 => 'free', + 1 => 32000, + 2 => 48000, + 3 => 56000, + 4 => 64000, + 5 => 80000, + 6 => 96000, + 7 => 112000, + 8 => 128000, + 9 => 160000, + 10 => 192000, + 11 => 224000, + 12 => 256000, + 13 => 320000, + 14 => 384000, + }, + PrintConv => 'ConvertBitrate($val)', + }, + { + Name => 'AudioBitrate', + Condition => '$self->{MPEG_Vers} == 3 and $self->{MPEG_Layer} == 1', + Notes => 'version 1, layer 3', + PrintConvColumns => 3, + ValueConv => { + 0 => 'free', + 1 => 32000, + 2 => 40000, + 3 => 48000, + 4 => 56000, + 5 => 64000, + 6 => 80000, + 7 => 96000, + 8 => 112000, + 9 => 128000, + 10 => 160000, + 11 => 192000, + 12 => 224000, + 13 => 256000, + 14 => 320000, + }, + PrintConv => 'ConvertBitrate($val)', + }, + { + Name => 'AudioBitrate', + Condition => '$self->{MPEG_Vers} != 3 and $self->{MPEG_Layer} == 3', + Notes => 'version 2 or 2.5, layer 1', + PrintConvColumns => 3, + ValueConv => { + 0 => 'free', + 1 => 32000, + 2 => 48000, + 3 => 56000, + 4 => 64000, + 5 => 80000, + 6 => 96000, + 7 => 112000, + 8 => 128000, + 9 => 144000, + 10 => 160000, + 11 => 176000, + 12 => 192000, + 13 => 224000, + 14 => 256000, + }, + PrintConv => 'ConvertBitrate($val)', + }, + { + Name => 'AudioBitrate', + Condition => '$self->{MPEG_Vers} != 3 and $self->{MPEG_Layer}', + Notes => 'version 2 or 2.5, layer 2 or 3', + PrintConvColumns => 3, + ValueConv => { + 0 => 'free', + 1 => 8000, + 2 => 16000, + 3 => 24000, + 4 => 32000, + 5 => 40000, + 6 => 48000, + 7 => 56000, + 8 => 64000, + 9 => 80000, + 10 => 96000, + 11 => 112000, + 12 => 128000, + 13 => 144000, + 14 => 160000, + }, + PrintConv => 'ConvertBitrate($val)', + }, + ], + 'Bit20-21' => [ + { + Name => 'SampleRate', + Condition => '$self->{MPEG_Vers} == 3', + Notes => 'version 1', + PrintConv => { + 0 => 44100, + 1 => 48000, + 2 => 32000, + }, + }, + { + Name => 'SampleRate', + Condition => '$self->{MPEG_Vers} == 2', + Notes => 'version 2', + PrintConv => { + 0 => 22050, + 1 => 24000, + 2 => 16000, + }, + }, + { + Name => 'SampleRate', + Condition => '$self->{MPEG_Vers} == 0', + Notes => 'version 2.5', + PrintConv => { + 0 => 11025, + 1 => 12000, + 2 => 8000, + }, + }, + ], + # Bit 22 - padding flag + # Bit 23 - private bit + 'Bit24-25' => { + Name => 'ChannelMode', + RawConv => '$self->{MPEG_Mode} = $val', + PrintConv => { + 0 => 'Stereo', + 1 => 'Joint Stereo', + 2 => 'Dual Channel', + 3 => 'Single Channel', + }, + }, + 'Bit26' => { + Name => 'MSStereo', + Condition => '$self->{MPEG_Layer} == 1', + Notes => 'layer 3', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 'Bit27' => { + Name => 'IntensityStereo', + Condition => '$self->{MPEG_Layer} == 1', + Notes => 'layer 3', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 'Bit26-27' => { + Name => 'ModeExtension', + Condition => '$self->{MPEG_Layer} > 1', + Notes => 'layer 1 or 2', + PrintConv => { + 0 => 'Bands 4-31', + 1 => 'Bands 8-31', + 2 => 'Bands 12-31', + 3 => 'Bands 16-31', + }, + }, + 'Bit28' => { + Name => 'CopyrightFlag', + PrintConv => { + 0 => 'False', + 1 => 'True', + }, + }, + 'Bit29' => { + Name => 'OriginalMedia', + PrintConv => { + 0 => 'False', + 1 => 'True', + }, + }, + 'Bit30-31' => { + Name => 'Emphasis', + PrintConv => { + 0 => 'None', + 1 => '50/15 ms', + 2 => 'reserved', + 3 => 'CCIT J.17', + }, + }, +); + +%Image::ExifTool::MPEG::Video = ( + GROUPS => { 2 => 'Video' }, + 'Bit00-11' => 'ImageWidth', + 'Bit12-23' => 'ImageHeight', + 'Bit24-27' => { + Name => 'AspectRatio', + ValueConv => { + 1 => 1, + 2 => 0.6735, + 3 => 0.7031, + 4 => 0.7615, + 5 => 0.8055, + 6 => 0.8437, + 7 => 0.8935, + 8 => 0.9157, + 9 => 0.9815, + 10 => 1.0255, + 11 => 1.0695, + 12 => 1.0950, + 13 => 1.1575, + 14 => 1.2015, + }, + PrintConv => { + 1 => '1:1', + 0.6735 => '0.6735', + 0.7031 => '16:9, 625 line, PAL', + 0.7615 => '0.7615', + 0.8055 => '0.8055', + 0.8437 => '16:9, 525 line, NTSC', + 0.8935 => '0.8935', + 0.9157 => '4:3, 625 line, PAL, CCIR601', + 0.9815 => '0.9815', + 1.0255 => '1.0255', + 1.0695 => '1.0695', + 1.0950 => '4:3, 525 line, NTSC, CCIR601', + 1.1575 => '1.1575', + 1.2015 => '1.2015', + }, + }, + 'Bit28-31' => { + Name => 'FrameRate', + ValueConv => { + 1 => 23.976, + 2 => 24, + 3 => 25, + 4 => 29.97, + 5 => 30, + 6 => 50, + 7 => 59.94, + 8 => 60, + }, + PrintConv => '"$val fps"', + }, + 'Bit32-49' => { + Name => 'VideoBitrate', + ValueConv => '$val eq 0x3ffff ? "Variable" : $val * 400', + PrintConv => 'ConvertBitrate($val)', + }, + # these tags not very interesting + #'Bit50' => 'MarkerBit', + #'Bit51-60' => 'VBVBufferSize', + #'Bit61' => 'ConstrainedParamFlag', + #'Bit62' => 'IntraQuantMatrixFlag', +); + +%Image::ExifTool::MPEG::Xing = ( + GROUPS => { 2 => 'Audio' }, + VARS => { NO_ID => 1 }, + NOTES => 'These tags are extracted from the Xing/Info frame.', + 1 => { Name => 'VBRFrames' }, + 2 => { Name => 'VBRBytes' }, + 3 => { Name => 'VBRScale' }, + 4 => { Name => 'Encoder' }, + 5 => { Name => 'LameVBRQuality' }, + 6 => { Name => 'LameQuality' }, + 7 => { # (for documentation only) + Name => 'LameHeader', + SubDirectory => { TagTable => 'Image::ExifTool::MPEG::Lame' }, + }, +); + +# Lame header tags (ref 5) +%Image::ExifTool::MPEG::Lame = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + NOTES => 'Tags extracted from Lame 3.90 or later header.', + 9 => { + Name => 'LameMethod', + Mask => 0x0f, + PrintConv => { + 1 => 'CBR', + 2 => 'ABR', + 3 => 'VBR (old/rh)', + 4 => 'VBR (new/mtrh)', + 5 => 'VBR (old/rh)', + 6 => 'VBR', + 8 => 'CBR (2-pass)', + 9 => 'ABR (2-pass)', + }, + }, + 10 => { + Name => 'LameLowPassFilter', + ValueConv => '$val * 100', + PrintConv => '($val / 1000) . " kHz"', + }, + # 19 - EncodingFlags + 20 => { + Name => 'LameBitrate', + ValueConv => '$val * 1000', + PrintConv => 'ConvertBitrate($val)', + }, + 24 => { + Name => 'LameStereoMode', + Mask => 0x1c, + PrintConv => { + 0 => 'Mono', + 1 => 'Stereo', + 2 => 'Dual Channels', + 3 => 'Joint Stereo', + 4 => 'Forced Joint Stereo', + 6 => 'Auto', + 7 => 'Intensity Stereo', + }, + }, +); + +# composite tags +%Image::ExifTool::MPEG::Composite = ( + Duration => { + Groups => { 2 => 'Video' }, + Require => { + 0 => 'FileSize', + }, + Desire => { + 1 => 'ID3Size', + 2 => 'MPEG:AudioBitrate', + 3 => 'MPEG:VideoBitrate', + 4 => 'MPEG:VBRFrames', + 5 => 'MPEG:SampleRate', + 6 => 'MPEG:MPEGAudioVersion', + }, + Priority => -1, # (don't want to replace any other Duration tag) + ValueConv => q{ + if ($val[4] and defined $val[5] and defined $val[6]) { + # calculate from number of VBR audio frames + my $mfs = $prt[5] / ($val[6] == 3 ? 144 : 72); + # calculate using VBR length + return 8 * $val[4] / $mfs; + } + # calculate duration as file size divided by total bitrate + # (note: this is only approximate!) + return undef unless $val[2] or $val[3]; + return undef if $val[2] and not $val[2] =~ /^\d+$/; + return undef if $val[3] and not $val[3] =~ /^\d+$/; + return (8 * ($val[0] - ($val[1]||0))) / (($val[2]||0) + ($val[3]||0)); + }, + PrintConv => 'ConvertDuration($val) . " (approx)"', + }, + AudioBitrate => { + Groups => { 2 => 'Audio' }, + Notes => 'calculated for variable-bitrate MPEG audio', + Require => { + 0 => 'MPEG:MPEGAudioVersion', + 1 => 'MPEG:SampleRate', + 2 => 'MPEG:VBRBytes', + 3 => 'MPEG:VBRFrames', + }, + ValueConv => q{ + return undef unless $val[3]; + my $mfs = $prt[1] / ($val[0] == 3 ? 144 : 72); + return $mfs * $val[2] / $val[3]; + }, + PrintConv => 'ConvertBitrate($val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::MPEG'); + + +#------------------------------------------------------------------------------ +# Process information in an MPEG audio or video frame header +# Inputs: 0) ExifTool object ref, 1) tag table ref, 2-N) list of 32-bit data words +sub ProcessFrameHeader($$@) +{ + my ($et, $tagTablePtr, @data) = @_; + my $tag; + foreach $tag (sort keys %$tagTablePtr) { + next unless $tag =~ /^Bit(\d{2})-?(\d{2})?/; + my ($b1, $b2) = ($1, $2 || $1); + my $index = int($b1 / 32); + my $word = $data[$index]; + my $mask = 0; + foreach (0 .. ($b2 - $b1)) { + $mask += (1 << $_); + } + my $val = ($word >> (31 + 32*$index - $b2)) & $mask; + $et->HandleTag($tagTablePtr, $tag, $val); + } +} + +#------------------------------------------------------------------------------ +# Read MPEG audio frame header +# Inputs: 0) ExifTool object reference, 1) Reference to audio data +# 2) flag set if we are trying to recognized MP3 file only +# Returns: 1 on success, 0 if no audio header was found +sub ParseMPEGAudio($$;$) +{ + my ($et, $buffPt, $mp3) = @_; + my ($word, $pos); + my $ext = $$et{FILE_EXT} || ''; + + for (;;) { + # find frame sync + return 0 unless $$buffPt =~ m{(\xff.{3})}sg; + $word = unpack('N', $1); # get audio frame header word + unless (($word & 0xffe00000) == 0xffe00000) { + pos($$buffPt) = pos($$buffPt) - 2; # next possible location for frame sync + next; + } + # validate header as much as possible + if (($word & 0x180000) == 0x080000 or # 01 is a reserved version ID + ($word & 0x060000) == 0x000000 or # 00 is a reserved layer description + ($word & 0x00f000) == 0x000000 or # 0000 is the "free" bitrate index + ($word & 0x00f000) == 0x00f000 or # 1111 is a bad bitrate index + ($word & 0x000c00) == 0x000c00 or # 11 is a reserved sampling frequency + ($word & 0x000003) == 0x000002 or # 10 is a reserved emphasis + (($mp3 and ($word & 0x060000) != 0x020000))) # must be layer 3 for MP3 + { + # give up easily unless this really should be an MP3 file + return 0 unless $ext eq 'MP3'; + pos($$buffPt) = pos($$buffPt) - 1; + next; + } + $pos = pos($$buffPt); + last; + } + # set file type if not done already + $et->SetFileType(); + + my $tagTablePtr = GetTagTable('Image::ExifTool::MPEG::Audio'); + ProcessFrameHeader($et, $tagTablePtr, $word); + + # extract the VBR information (ref MP3::Info) + my ($v, $m) = ($$et{MPEG_Vers}, $$et{MPEG_Mode}); + while (defined $v and defined $m) { + my $len = length $$buffPt; + $pos += $v == 3 ? ($m == 3 ? 17 : 32) : ($m == 3 ? 9 : 17); + last if $pos + 8 > $len; + my $buff = substr($$buffPt, $pos, 8); + last unless $buff =~ /^(Xing|Info)/; + my $xingTable = GetTagTable('Image::ExifTool::MPEG::Xing'); + my $vbrScale; + my $flags = unpack('x4N', $buff); + my $isVBR = ($buff !~ /^Info/); # Info frame is not VBR (ref 5) + $pos += 8; + if ($flags & 0x01) { # VBRFrames + last if $pos + 4 > $len; + $et->HandleTag($xingTable, 1, unpack("x${pos}N", $$buffPt)) if $isVBR; + $pos += 4; + } + if ($flags & 0x02) { # VBRBytes + last if $pos + 4 > $len; + $et->HandleTag($xingTable, 2, unpack("x${pos}N", $$buffPt)) if $isVBR; + $pos += 4; + } + if ($flags & 0x04) { # VBR_TOC + last if $pos + 100 > $len; + # (ignore toc for now) + $pos += 100; + } + if ($flags & 0x08) { # VBRScale + last if $pos + 4 > $len; + $vbrScale = unpack("x${pos}N", $$buffPt); + $et->HandleTag($xingTable, 3, $vbrScale) if $isVBR; + $pos += 4; + } + # process Lame header (ref 5) + if ($flags & 0x10) { # Lame + last if $pos + 348 > $len; + } elsif ($pos + 4 <= $len) { + my $lib = substr($$buffPt, $pos, 4); + unless ($lib eq 'LAME' or $lib eq 'GOGO') { + # attempt to identify other encoders + my $n; + if (index($$buffPt, 'RCA mp3PRO Encoder') >= 0) { + $lib = 'RCA mp3PRO'; + } elsif (($n = index($$buffPt, 'THOMSON mp3PRO Encoder')) >= 0) { + $lib = 'Thomson mp3PRO'; + $n += 22; + $lib .= ' ' . substr($$buffPt, $n, 6) if length($$buffPt) - $n >= 6; + } elsif (index($$buffPt, 'MPGE') >= 0) { + $lib = 'Gogo (<3.0)'; + } else { + last; + } + $et->HandleTag($xingTable, 4, $lib); + last; + } + } + my $lameLen = $len - $pos; + last if $lameLen < 9; + my $enc = substr($$buffPt, $pos, 9); + if ($enc ge 'LAME3.90') { + $et->HandleTag($xingTable, 4, $enc); + if ($vbrScale <= 100) { + $et->HandleTag($xingTable, 5, int((100 - $vbrScale) / 10)); + $et->HandleTag($xingTable, 6, (100 - $vbrScale) % 10); + } + my %dirInfo = ( + DataPt => $buffPt, + DirStart => $pos, + DirLen => length($$buffPt) - $pos, + ); + my $subTablePtr = GetTagTable('Image::ExifTool::MPEG::Lame'); + $et->ProcessDirectory(\%dirInfo, $subTablePtr); + } else { + $et->HandleTag($xingTable, 4, substr($$buffPt, $pos, 20)); + } + last; # (didn't want to loop anyway) + } + + return 1; +} + +#------------------------------------------------------------------------------ +# Read MPEG video frame header +# Inputs: 0) ExifTool object reference, 1) Reference to video data +# Returns: 1 on success, 0 if no video header was found +sub ProcessMPEGVideo($$) +{ + my ($et, $buffPt) = @_; + + return 0 unless length $$buffPt >= 4; + my ($w1, $w2) = unpack('N2', $$buffPt); + # validate as much as possible + if (($w1 & 0x000000f0) == 0x00000000 or # 0000 is a forbidden aspect ratio + ($w1 & 0x000000f0) == 0x000000f0 or # 1111 is a reserved aspect ratio + ($w1 & 0x0000000f) == 0 or # frame rate must be 1-8 + ($w1 & 0x0000000f) > 8) + { + return 0; + } + # set file type if not done already + $et->SetFileType('MPEG') unless $$et{FileType}; + + my $tagTablePtr = GetTagTable('Image::ExifTool::MPEG::Video'); + ProcessFrameHeader($et, $tagTablePtr, $w1, $w2); + return 1; +} + +#------------------------------------------------------------------------------ +# Read MPEG audio and video frame headers +# Inputs: 0) ExifTool object reference, 1) Reference to audio/video data +# Returns: 1 on success, 0 if no video header was found +# To Do: Properly parse MPEG streams: +# 0xb7 - sequence end +# 0xb9 - end code +# 0xba - pack start code +# 0xbb - system header +# 0xbc - program map <-- should parse this +# 0xbd - private stream 1 --> for VOB, this contains sub-streams: +# 0x20-0x3f - pictures +# 0x80-0x87 - audio (AC3,DTS,SDDS) +# 0xa0-0xa7 - audio (LPCM) +# 0xbe - padding +# 0xbf - private stream 2 +# 0xc0-0xdf - audio stream +# 0xe0-0xef - video stream +sub ParseMPEGAudioVideo($$) +{ + my ($et, $buffPt) = @_; + my (%found, $didHdr); + my $rtnVal = 0; + my %proc = ( audio => \&ParseMPEGAudio, video => \&ProcessMPEGVideo ); + + delete $$et{AudioBitrate}; + delete $$et{VideoBitrate}; + + while ($$buffPt =~ /\0\0\x01(\xb3|\xc0)/g) { + my $type = $1 eq "\xb3" ? 'video' : 'audio'; + unless ($didHdr) { + # make sure we didn't miss an audio frame sync before this (eg. MP3 file) + # (the last byte of the 4-byte MP3 audio frame header word may be zero, + # but the 2nd last must be non-zero, so we need only check to pos-3) + my $buff = substr($$buffPt, 0, pos($$buffPt) - 3); + $found{audio} = 1 if ParseMPEGAudio($et, \$buff); + $didHdr = 1; + } + next if $found{$type}; + my $len = length($$buffPt) - pos($$buffPt); + last if $len < 4; + $len > 256 and $len = 256; + my $dat = substr($$buffPt, pos($$buffPt), $len); + # process MPEG audio or video + if (&{$proc{$type}}($et, \$dat)) { + $rtnVal = 1; + $found{$type} = 1; + # done if we found audio and video + last if scalar(keys %found) == 2; + } + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Read information from an MPEG file +# Inputs: 0) ExifTool object reference, 1) Directory information reference +# Returns: 1 on success, 0 if this wasn't a valid MPEG file +sub ProcessMPEG($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + + $raf->Read($buff, 4) == 4 or return 0; + return 0 unless $buff =~ /^\0\0\x01[\xb0-\xbf]/; + $et->SetFileType(); + + $raf->Seek(0,0); + $raf->Read($buff, 65536*4) or return 0; + + return ParseMPEGAudioVideo($et, \$buff); +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MPEG - Read MPEG-1 and MPEG-2 meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read MPEG-1 +and MPEG-2 audio/video files. + +=head1 NOTES + +Since ISO charges money for the official MPEG specification, this module is +based on unofficial sources which may be incomplete, inaccurate or outdated. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.mp3-tech.org/> + +=item L<http://www.getid3.org/> + +=item L<http://dvd.sourceforge.net/dvdinfo/dvdmpeg.html> + +=item L<http://ffmpeg.org/> + +=item L<http://sourceforge.net/projects/mediainfo/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MPEG Tags>, +L<MP3::Info(3pm)|MP3::Info>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MPF.pm b/ExifTool/lib/Image/ExifTool/MPF.pm new file mode 100644 index 0000000..f87fb77 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MPF.pm @@ -0,0 +1,289 @@ +#------------------------------------------------------------------------------ +# File: MPF.pm +# +# Description: Read Multi-Picture Format information +# +# Revisions: 06/12/2009 - P. Harvey Created +# +# References: 1) http://www.cipa.jp/std/documents/e/DC-007_E.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::MPF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.15'; + +sub ProcessMPImageList($$$); + +# Tags found in APP2 MPF segment in JPEG images +%Image::ExifTool::MPF::Main = ( + GROUPS => { 0 => 'MPF', 1 => 'MPF0', 2 => 'Image'}, + NOTES => q{ + These tags are part of the CIPA Multi-Picture Format specification, and are + found in the APP2 "MPF" segment of JPEG images. MPImage data referenced + from this segment is stored as a JPEG trailer. The MPF tags are not + writable, however the MPF segment may be deleted as a group (with "MPF:All") + but then the JPEG trailer should also be deleted (with "Trailer:All"). See + L<https://web.archive.org/web/20190713230858/http://www.cipa.jp/std/documents/e/DC-007_E.pdf> + for the official specification. + }, + 0xb000 => 'MPFVersion', + 0xb001 => 'NumberOfImages', + 0xb002 => { + Name => 'MPImageList', + SubDirectory => { + TagTable => 'Image::ExifTool::MPF::MPImage', + ProcessProc => \&ProcessMPImageList, + }, + }, + 0xb003 => { + Name => 'ImageUIDList', + Binary => 1, + }, + 0xb004 => 'TotalFrames', + 0xb101 => 'MPIndividualNum', + 0xb201 => { + Name => 'PanOrientation', + PrintHex => 1, + Notes => 'long integer is split into 4 bytes', + ValueConv => 'join(" ",unpack("C*",pack("N",$val)))', + PrintConv => [ + '"$val rows"', + '"$val columns"', + { + 0 => '[unused]', + 1 => 'Start at top right', + 2 => 'Start at top left', + 3 => 'Start at bottom left', + 4 => 'Start at bottom right', + }, + { + 0x01 => 'Left to right', + 0x02 => 'Right to left', + 0x03 => 'Top to bottom', + 0x04 => 'Bottom to top', + 0x10 => 'Clockwise', + 0x20 => 'Counter clockwise', + 0x30 => 'Zigzag (row start)', + 0x40 => 'Zigzag (column start)', + }, + ], + }, + 0xb202 => 'PanOverlapH', + 0xb203 => 'PanOverlapV', + 0xb204 => 'BaseViewpointNum', + 0xb205 => 'ConvergenceAngle', + 0xb206 => 'BaselineLength', + 0xb207 => 'VerticalDivergence', + 0xb208 => 'AxisDistanceX', + 0xb209 => 'AxisDistanceY', + 0xb20a => 'AxisDistanceZ', + 0xb20b => 'YawAngle', + 0xb20c => 'PitchAngle', + 0xb20d => 'RollAngle', +); + +# Tags found in MPImage structure +%Image::ExifTool::MPF::MPImage = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + #WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + #CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + #WRITABLE => 1, + GROUPS => { 0 => 'MPF', 1 => 'MPImage', 2 => 'Image'}, + NOTES => q{ + The first MPF "Large Thumbnail" image is extracted as PreviewImage, and the + rest of the embedded MPF images are extracted as MPImage#. The + L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> (-ee) option may be used to extract information from these + embedded images. + }, + 0.1 => { + Name => 'MPImageFlags', + Format => 'int32u', + Mask => 0xf8000000, + PrintConv => { BITMASK => { + 2 => 'Representative image', + 3 => 'Dependent child image', + 4 => 'Dependent parent image', + }}, + }, + 0.2 => { + Name => 'MPImageFormat', + Format => 'int32u', + Mask => 0x07000000, + PrintConv => { + 0 => 'JPEG', + }, + }, + 0.3 => { + Name => 'MPImageType', + Format => 'int32u', + Mask => 0x00ffffff, + PrintHex => 1, + PrintConv => { + 0x000000 => 'Undefined', + 0x010001 => 'Large Thumbnail (VGA equivalent)', + 0x010002 => 'Large Thumbnail (full HD equivalent)', + 0x020001 => 'Multi-frame Panorama', + 0x020002 => 'Multi-frame Disparity', + 0x020003 => 'Multi-angle', + 0x030000 => 'Baseline MP Primary Image', + 0x040000 => 'Original Preservation Image', # (Exif 3.0) + }, + }, + 4 => { + Name => 'MPImageLength', + Format => 'int32u', + }, + 8 => { + Name => 'MPImageStart', + Format => 'int32u', + IsOffset => '$val', + }, + 12 => { + Name => 'DependentImage1EntryNumber', + Format => 'int16u', + }, + 14 => { + Name => 'DependentImage2EntryNumber', + Format => 'int16u', + }, +); + +# extract MP Images as composite tags +%Image::ExifTool::MPF::Composite = ( + GROUPS => { 2 => 'Preview' }, + MPImage => { + Require => { + 0 => 'MPImageStart', + 1 => 'MPImageLength', + 2 => 'MPImageType', + }, + Notes => q{ + the first MPF "Large Thumbnail" is extracted as PreviewImage, and the rest + of the embedded MPF images are extracted as MPImage#. The L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> + option may be used to extract information from these embedded images. + }, + # extract all MPF images (not just one) + RawConv => q{ + require Image::ExifTool::MPF; + @grps = $self->GetGroup($$val{0}); # set groups from input tag + Image::ExifTool::MPF::ExtractMPImages($self); + }, + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::MPF'); + +#------------------------------------------------------------------------------ +# Extract all MP images +# Inputs: 0) ExifTool object ref +# Returns: undef +sub ExtractMPImages($) +{ + my $et = shift; + my $ee = $et->Options('ExtractEmbedded'); + my $saveBinary = $et->Options('Binary'); + my ($i, $didPreview, $xtra); + + for ($i=1; $xtra or not defined $xtra; ++$i) { + # run through MP images in the same order they were extracted + $xtra = defined $$et{VALUE}{"MPImageStart ($i)"} ? " ($i)" : ''; + my $off = $et->GetValue("MPImageStart$xtra", 'ValueConv'); + my $len = $et->GetValue("MPImageLength$xtra", 'ValueConv'); + if ($off and $len) { + my $type = $et->GetValue("MPImageType$xtra", 'ValueConv'); + my $tag = "MPImage$i"; + # store first "Large Thumbnail" as a PreviewImage + if (not $didPreview and $type and ($type & 0x0f0000) == 0x010000) { + $tag = 'PreviewImage'; + $didPreview = 1; + } + $et->Options('Binary', 1) if $ee; + my $val = Image::ExifTool::Exif::ExtractImage($et, $off, $len, $tag); + $et->Options('Binary', $saveBinary) if $ee; + next unless defined $val; + unless ($Image::ExifTool::Extra{$tag}) { + AddTagToTable(\%Image::ExifTool::Extra, $tag, { + Name => $tag, + Groups => { 0 => 'Composite', 1 => 'Composite', 2 => 'Preview'}, + }); + } + my $key = $et->FoundTag($tag, $val, $et->GetGroup("MPImageStart$xtra")); + # extract information from MP images if ExtractEmbedded option used + if ($ee) { + my $oldBase = $$et{BASE}; + $$et{BASE} = $off; + $$et{DOC_NUM} = $i; + $et->ExtractInfo($val, { ReEntry => 1 }); + delete $$et{DOC_NUM}; + $$et{BASE} = $oldBase; + } + } + } + return undef; +} + +#------------------------------------------------------------------------------ +# Process MP Entry list +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessMPImageList($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $num = int($$dirInfo{DirLen} / 16); # (16 bytes per MP Entry) + $$dirInfo{DirLen} = 16; + my ($i, $success); + my $oldG1 = $$et{SET_GROUP1}; + for ($i=0; $i<$num; ++$i) { + $$et{SET_GROUP1} = '+' . ($i + 1); + $success = $et->ProcessBinaryData($dirInfo, $tagTablePtr); + $$dirInfo{DirStart} += 16; + } + $$et{SET_GROUP1} = $oldG1; + return $success; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MPF - Read Multi-Picture Format information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains tag definitions and routines to read Multi-Picture +Format (MPF) information from JPEG images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.cipa.jp/std/documents/e/DC-007_E.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MPF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MRC.pm b/ExifTool/lib/Image/ExifTool/MRC.pm new file mode 100644 index 0000000..2818cd0 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MRC.pm @@ -0,0 +1,341 @@ +#------------------------------------------------------------------------------ +# File: MRC.pm +# +# Description: Read MRC (Medical Research Council) image files +# +# Revisions: 2021-04-21 - P. Harvey Created +# +# References: 1) https://www.ccpem.ac.uk/mrc_format/mrc2014.php +# 2) http://legacy.ccp4.ac.uk/html/library.html +# 3) https://github.com/ccpem/mrcfile/blob/master/mrcfile/dtypes.py +# +# Notes: The header is basically identical to the older CCP4 file format +#------------------------------------------------------------------------------ + +package Image::ExifTool::MRC; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.00'; + +my %bool = ( + Format => 'int8u', + PrintConv => { 0 => 'No', 1 => 'Yes' } +); + +%Image::ExifTool::MRC::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + FORMAT => 'int32u', + NOTES => q{ + Tags extracted from Medical Research Council (MRC) format imaging files. + See L<https://www.ccpem.ac.uk/mrc_format/mrc2014.php> for the specification. + }, + 0 => 'ImageWidth', + 1 => 'ImageHeight', + 2 => { + Name => 'ImageDepth', + Notes => q{ + number of sections. Use ExtractEmbedded option to extract metadata for all + sections + }, + RawConv => '$$self{ImageDepth} = $val', + }, + 3 => { + Name => 'ImageMode', + PrintConv => { + 0 => '8-bit signed integer', + 1 => '16-bit signed integer', + 2 => '32-bit signed real', + 3 => 'complex 16-bit integer', + 4 => 'complex 32-bit real', + 6 => '16-bit unsigned integer', + }, + }, + 4 => { Name => 'StartPoint', Format => 'int32u[3]' }, + 7 => { Name => 'GridSize', Format => 'int32u[3]' }, + 10 => { Name => 'CellWidth', Format => 'float', Notes => 'cell size in angstroms' }, + 11 => { Name => 'CellHeight',Format => 'float' }, + 12 => { Name => 'CellDepth', Format => 'float' }, + 13 => { Name => 'CellAlpha', Format => 'float' }, + 14 => { Name => 'CellBeta', Format => 'float' }, + 15 => { Name => 'CellGamma', Format => 'float' }, + 16 => { Name => 'ImageWidthAxis', PrintConv => { 1 => 'X', 2 => 'Y', 3 => 'Z' } }, + 17 => { Name => 'ImageHeightAxis', PrintConv => { 1 => 'X', 2 => 'Y', 3 => 'Z' } }, + 18 => { Name => 'ImageDepthAxis', PrintConv => { 1 => 'X', 2 => 'Y', 3 => 'Z' } }, + 19 => { Name => 'DensityMin', Format => 'float' }, + 20 => { Name => 'DensityMax', Format => 'float' }, + 21 => { Name => 'DensityMean',Format => 'float' }, + 22 => 'SpaceGroupNumber', + 23 => { Name => 'ExtendedHeaderSize', RawConv => '$$self{ExtendedHeaderSize} = $val' }, + 26 => { Name => 'ExtendedHeaderType', Format => 'string[4]', RawConv => '$$self{ExtendedHeaderType} = $val' }, + 27 => 'MRCVersion', + 49 => { Name => 'Origin', Format => 'float[3]' }, + 53 => { Name => 'MachineStamp', Format => 'int8u[4]', PrintConv => 'sprintf("0x%.2x 0x%.2x 0x%.2x 0x%.2x",split " ", $val)' }, + 54 => { Name => 'RMSDeviation', Format => 'float' }, + 55 => { Name => 'NumberOfLabels', RawConv => '$$self{NLab} = $val' }, + 56 => { Name => 'Label0', Format => 'string[80]', Condition => '$$self{NLab} > 0' }, + 76 => { Name => 'Label1', Format => 'string[80]', Condition => '$$self{NLab} > 1' }, + 96 => { Name => 'Label2', Format => 'string[80]', Condition => '$$self{NLab} > 2' }, + 116 => { Name => 'Label3', Format => 'string[80]', Condition => '$$self{NLab} > 3' }, + 136 => { Name => 'Label4', Format => 'string[80]', Condition => '$$self{NLab} > 4' }, + 156 => { Name => 'Label5', Format => 'string[80]', Condition => '$$self{NLab} > 5' }, + 176 => { Name => 'Label6', Format => 'string[80]', Condition => '$$self{NLab} > 6' }, + 196 => { Name => 'Label7', Format => 'string[80]', Condition => '$$self{NLab} > 7' }, + 216 => { Name => 'Label8', Format => 'string[80]', Condition => '$$self{NLab} > 8' }, + 236 => { Name => 'Label9', Format => 'string[80]', Condition => '$$self{NLab} > 9' }, +); + +%Image::ExifTool::MRC::FEI12 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup (way too many!) + NOTES => 'Tags extracted from FEI1 and FEI2 extended headers.', + 0 => { Name => 'MetadataSize', Format => 'int32u', RawConv => '$$self{MetadataSize} = $val' }, + 4 => { Name => 'MetadataVersion', Format => 'int32u' }, + 8 => { + Name => 'Bitmask1', + Format => 'int32u', + RawConv => '$$self{BitM} = $val', + PrintConv => 'sprintf("0x%.8x", $val)', + }, + 12 => { + Name => 'TimeStamp', + Format => 'double', + Condition => '$$self{BitM} & 0x01', + Groups => { 2 => 'Time'}, + # shift from days since Dec 30, 1899 to Unix epoch of Jan 1, 1970 + # (my sample looks like local time, although it should be UTC) + ValueConv => 'ConvertUnixTime(($val-25569)*24*3600)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 20 => { Name => 'MicroscopeType', Format => 'string[16]', Condition => '$$self{BitM} & 0x02' }, + 36 => { Name => 'MicroscopeID', Format => 'string[16]', Condition => '$$self{BitM} & 0x04' }, + 52 => { Name => 'Application', Format => 'string[16]', Condition => '$$self{BitM} & 0x08' }, + 68 => { Name => 'AppVersion', Format => 'string[16]', Condition => '$$self{BitM} & 0x10' }, + 84 => { Name => 'HighTension', Format => 'double', Condition => '$$self{BitM} & 0x20', Notes => 'volts' }, + 92 => { Name => 'Dose', Format => 'double', Condition => '$$self{BitM} & 0x40', Notes => 'electrons/m2' }, + 100 => { Name => 'AlphaTilt', Format => 'double', Condition => '$$self{BitM} & 0x80' }, + 108 => { Name => 'BetaTilt', Format => 'double', Condition => '$$self{BitM} & 0x100' }, + 116 => { Name => 'XStage', Format => 'double', Condition => '$$self{BitM} & 0x200' }, + 124 => { Name => 'YStage', Format => 'double', Condition => '$$self{BitM} & 0x400' }, + 132 => { Name => 'ZStage', Format => 'double', Condition => '$$self{BitM} & 0x800' }, + 140 => { Name => 'TiltAxisAngle', Format => 'double', Condition => '$$self{BitM} & 0x1000' }, + 148 => { Name => 'DualAxisRot', Format => 'double', Condition => '$$self{BitM} & 0x2000' }, + 156 => { Name => 'PixelSizeX', Format => 'double', Condition => '$$self{BitM} & 0x4000' }, + 164 => { Name => 'PixelSizeY', Format => 'double', Condition => '$$self{BitM} & 0x8000' }, + 220 => { Name => 'Defocus', Format => 'double', Condition => '$$self{BitM} & 0x400000' }, + 228 => { Name => 'STEMDefocus', Format => 'double', Condition => '$$self{BitM} & 0x800000' }, + 236 => { Name => 'AppliedDefocus', Format => 'double', Condition => '$$self{BitM} & 0x1000000' }, + 244 => { Name => 'InstrumentMode', Format => 'int32u', Condition => '$$self{BitM} & 0x2000000', PrintConv => { 1 => 'TEM', 2 => 'STEM' } }, + 248 => { Name => 'ProjectionMode', Format => 'int32u', Condition => '$$self{BitM} & 0x4000000', PrintConv => { 1 => 'Diffraction', 2 => 'Imaging' } }, + 252 => { Name => 'ObjectiveLens', Format => 'string[16]', Condition => '$$self{BitM} & 0x8000000' }, + 268 => { Name => 'HighMagnificationMode', Format => 'string[16]', Condition => '$$self{BitM} & 0x10000000' }, + 284 => { Name => 'ProbeMode', Format => 'int32u', Condition => '$$self{BitM} & 0x20000000', PrintConv => { 1 => 'Nano', 2 => 'Micro' } }, + 288 => { Name => 'EFTEMOn', %bool, Condition => '$$self{BitM} & 0x40000000' }, + 289 => { Name => 'Magnification', Format => 'double', Condition => '$$self{BitM} & 0x80000000' }, + 297 => { + Name => 'Bitmask2', + Format => 'int32u', + RawConv => '$$self{BitM} = $val', + PrintConv => 'sprintf("0x%.8x", $val)', + }, + 301 => { Name => 'CameraLength', Format => 'double', Condition => '$$self{BitM} & 0x01' }, + 309 => { Name => 'SpotIndex', Format => 'int32u', Condition => '$$self{BitM} & 0x02' }, + 313 => { Name => 'IlluminationArea',Format => 'double', Condition => '$$self{BitM} & 0x04' }, + 321 => { Name => 'Intensity', Format => 'double', Condition => '$$self{BitM} & 0x08' }, + 329 => { Name => 'ConvergenceAngle',Format => 'double', Condition => '$$self{BitM} & 0x10' }, + 337 => { Name => 'IlluminationMode',Format => 'string[16]', Condition => '$$self{BitM} & 0x20' }, + 353 => { Name => 'WideConvergenceAngleRange', %bool, Condition => '$$self{BitM} & 0x40' }, + 354 => { Name => 'SlitInserted', %bool, Condition => '$$self{BitM} & 0x80' }, + 355 => { Name => 'SlitWidth', Format => 'double', Condition => '$$self{BitM} & 0x100' }, + 363 => { Name => 'AccelVoltOffset', Format => 'double', Condition => '$$self{BitM} & 0x200' }, + 371 => { Name => 'DriftTubeVolt', Format => 'double', Condition => '$$self{BitM} & 0x400' }, + 379 => { Name => 'EnergyShift', Format => 'double', Condition => '$$self{BitM} & 0x800' }, + 387 => { Name => 'ShiftOffsetX', Format => 'double', Condition => '$$self{BitM} & 0x1000' }, + 395 => { Name => 'ShiftOffsetY', Format => 'double', Condition => '$$self{BitM} & 0x2000' }, + 403 => { Name => 'ShiftX', Format => 'double', Condition => '$$self{BitM} & 0x4000' }, + 411 => { Name => 'ShiftY', Format => 'double', Condition => '$$self{BitM} & 0x8000' }, + 419 => { Name => 'IntegrationTime', Format => 'double', Condition => '$$self{BitM} & 0x10000' }, + 427 => { Name => 'BinningWidth', Format => 'int32u', Condition => '$$self{BitM} & 0x20000' }, + 431 => { Name => 'BinningHeight', Format => 'int32u', Condition => '$$self{BitM} & 0x40000' }, + 435 => { Name => 'CameraName', Format => 'string[16]', Condition => '$$self{BitM} & 0x80000' }, + 451 => { Name => 'ReadoutAreaLeft', Format => 'int32u', Condition => '$$self{BitM} & 0x100000' }, + 455 => { Name => 'ReadoutAreaTop', Format => 'int32u', Condition => '$$self{BitM} & 0x200000' }, + 459 => { Name => 'ReadoutAreaRight',Format => 'int32u', Condition => '$$self{BitM} & 0x400000' }, + 463 => { Name => 'ReadoutAreaBottom',Format=> 'int32u', Condition => '$$self{BitM} & 0x800000' }, + 467 => { Name => 'CetaNoiseReduct', %bool, Condition => '$$self{BitM} & 0x1000000' }, + 468 => { Name => 'CetaFramesSummed',Format => 'int32u', Condition => '$$self{BitM} & 0x2000000' }, + 472 => { Name => 'DirectDetElectronCounting', %bool, Condition => '$$self{BitM} & 0x4000000' }, + 473 => { Name => 'DirectDetAlignFrames', %bool, Condition => '$$self{BitM} & 0x8000000' }, + 490 => { + Name => 'Bitmask3', + Format => 'int32u', + RawConv => '$$self{BitM} = $val', + PrintConv => 'sprintf("0x%.8x", $val)', + }, + 518 => { Name => 'PhasePlate', %bool, Condition => '$$self{BitM} & 0x40' }, + 519 => { Name => 'STEMDetectorName',Format => 'string[16]', Condition => '$$self{BitM} & 0x80' }, + 535 => { Name => 'Gain', Format => 'double', Condition => '$$self{BitM} & 0x100' }, + 543 => { Name => 'Offset', Format => 'double', Condition => '$$self{BitM} & 0x200' }, + 571 => { Name => 'DwellTime', Format => 'double', Condition => '$$self{BitM} & 0x8000' }, + 579 => { Name => 'FrameTime', Format => 'double', Condition => '$$self{BitM} & 0x10000' }, + 587 => { Name => 'ScanSizeLeft', Format => 'int32u', Condition => '$$self{BitM} & 0x20000' }, + 591 => { Name => 'ScanSizeTop', Format => 'int32u', Condition => '$$self{BitM} & 0x40000' }, + 595 => { Name => 'ScanSizeRight', Format => 'int32u', Condition => '$$self{BitM} & 0x80000' }, + 599 => { Name => 'ScanSizeBottom', Format => 'int32u', Condition => '$$self{BitM} & 0x100000' }, + 603 => { Name => 'FullScanFOV_X', Format => 'double', Condition => '$$self{BitM} & 0x200000' }, + 611 => { Name => 'FullScanFOV_Y', Format => 'double', Condition => '$$self{BitM} & 0x400000' }, + 619 => { Name => 'Element', Format => 'string[16]', Condition => '$$self{BitM} & 0x800000' }, + 635 => { Name => 'EnergyIntervalLower', Format => 'double', Condition => '$$self{BitM} & 0x1000000' }, + 643 => { Name => 'EnergyIntervalHigher',Format => 'double', Condition => '$$self{BitM} & 0x2000000' }, + 651 => { Name => 'Method', Format=> 'int32u', Condition => '$$self{BitM} & 0x4000000' }, + 655 => { Name => 'IsDoseFraction', %bool, Condition => '$$self{BitM} & 0x8000000' }, + 656 => { Name => 'FractionNumber', Format => 'int32u', Condition => '$$self{BitM} & 0x10000000' }, + 660 => { Name => 'StartFrame', Format => 'int32u', Condition => '$$self{BitM} & 0x20000000' }, + 664 => { Name => 'EndFrame', Format => 'int32u', Condition => '$$self{BitM} & 0x40000000' }, + 668 => { Name =>'InputStackFilename',Format=> 'string[80]', Condition => '$$self{BitM} & 0x80000000' }, + 748 => { + Name => 'Bitmask4', + Format => 'int32u', + RawConv => '$$self{BitM} = $val', + PrintConv => 'sprintf("0x%.8x", $val)', + }, + 752 => { Name => 'AlphaTiltMin', Format => 'double', Condition => '$$self{BitM} & 0x01' }, + 760 => { Name => 'AlphaTiltMax', Format => 'double', Condition => '$$self{BitM} & 0x02' }, +# +# FEI2 header starts here +# + 768 => { Name => 'ScanRotation', Format => 'double', Condition => '$$self{BitM} & 0x04' }, + 776 => { Name => 'DiffractionPatternRotation',Format=>'double', Condition => '$$self{BitM} & 0x08' }, + 784 => { Name => 'ImageRotation', Format => 'double', Condition => '$$self{BitM} & 0x10' }, + 792 => { Name => 'ScanModeEnumeration',Format => 'int32u', Condition => '$$self{BitM} & 0x20', PrintConv => { 0 => 'Other', 1 => 'Raster', 2 => 'Serpentine' } }, + 796 => { + Name => 'AcquisitionTimeStamp', + Format => 'int64u', + Condition => '$$self{BitM} & 0x40', + Groups => { 2 => 'Time' }, + # microseconds since 1970 UTC + ValueConv => 'ConvertUnixTime($val / 1e6, 1, 6)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 804 => { Name => 'DetectorCommercialName', Format => 'string[16]', Condition => '$$self{BitM} & 0x80' }, + 820 => { Name => 'StartTiltAngle', Format => 'double', Condition => '$$self{BitM} & 0x100' }, + 828 => { Name => 'EndTiltAngle', Format => 'double', Condition => '$$self{BitM} & 0x200' }, + 836 => { Name => 'TiltPerImage', Format => 'double', Condition => '$$self{BitM} & 0x400' }, + 844 => { Name => 'TitlSpeed', Format => 'double', Condition => '$$self{BitM} & 0x800' }, + 852 => { Name => 'BeamCenterX', Format => 'int32u', Condition => '$$self{BitM} & 0x1000' }, + 856 => { Name => 'BeamCenterY', Format => 'int32u', Condition => '$$self{BitM} & 0x2000' }, + 860 => { + Name => 'CFEGFlashTimeStamp', + Format => 'int64u', + Condition => '$$self{BitM} & 0x4000', + Groups => { 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val / 1e6, 1, 6)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 868 => { Name => 'PhasePlatePosition',Format => 'int32u', Condition => '$$self{BitM} & 0x8000' }, + 872 => { Name => 'ObjectiveAperture', Format=>'string[16]',Condition => '$$self{BitM} & 0x10000' }, +); + +#------------------------------------------------------------------------------ +# Extract metadata from a MRC image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid MRC file +sub ProcessMRC($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $tagTablePtr, $i); + + # verify this is a valid MRC file + return 0 unless $raf->Read($buff, 1024) == 1024; + # validate axes, "MAP" file type and machine stamp + return 0 unless $buff =~ /^.{64}[\x01\x02\x03]\0\0\0[\x01\x02\x03]\0\0\0[\x01\x02\x03]\0\0\0.{132}MAP[\0 ](\x44\x44|\x44\x41|\x11\x11)\0\0/s; + + $et->SetFileType(); + SetByteOrder('II'); + my %dirInfo = ( + DataPt => \$buff, + DirStart => 0, + DirLen => length($buff), + ); + $tagTablePtr = GetTagTable('Image::ExifTool::MRC::Main'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + + # (I don't have any samples with extended headers for testing, so these are not yet decoded) + if ($$et{ExtendedHeaderSize} and $$et{ExtendedHeaderType} =~ /^FEI[12]/) { + unless ($raf->Read($buff,4)==4 and $raf->Seek(-4,1)) { # read metadata size + $et->Warn('Error reading extended header'); + return 1; + } + my $size = Get32u(\$buff, 0); + if ($size * $$et{ImageDepth} > $$et{ExtendedHeaderSize}) { + $et->Warn('Corrupted extended header'); + return 1; + } + $dirInfo{DirLen} = $size; + $tagTablePtr = GetTagTable('Image::ExifTool::MRC::FEI12'); + for ($i=0; ;) { + $dirInfo{DataPos} = $raf->Tell(); + $raf->Read($buff, $size) == $size or $et->Warn("Error reading extended header $i"), last; + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + last if ++$i >= $$et{ImageDepth}; + unless ($$et{OPTIONS}{ExtractEmbedded}) { + $et->Warn('Use the ExtractEmbedded option to read metadata for all frames',3); + last; + } + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + } + delete $$et{DOC_NUM}; + } + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MRC - Read MRC meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +metadata from Medical Research Council (MRC) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://www.ccpem.ac.uk/mrc_format/mrc2014.php> + +=item L<http://legacy.ccp4.ac.uk/html/library.html> + +=item L<https://github.com/ccpem/mrcfile/blob/master/mrcfile/dtypes.py> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MRC Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MWG.pm b/ExifTool/lib/Image/ExifTool/MWG.pm new file mode 100644 index 0000000..67e9d25 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MWG.pm @@ -0,0 +1,777 @@ +#------------------------------------------------------------------------------ +# File: MWG.pm +# +# Description: Metadata Working Group support +# +# Revisions: 2009/10/21 - P. Harvey Created +# +# References: 1) http://www.metadataworkinggroup.org/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::MWG; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::XMP; + +$VERSION = '1.24'; + +sub RecoverTruncatedIPTC($$$); +sub ListToString($); +sub StringToList($$); +sub OverwriteStringList($$$$); + +my $mwgLoaded; # flag set if we alreaded Load()ed the MWG tags + +# MWG Composite tags +%Image::ExifTool::MWG::Composite = ( + GROUPS => { 0 => 'Composite', 1 => 'MWG', 2 => 'Image' }, + VARS => { NO_ID => 1 }, + WRITE_PROC => \&Image::ExifTool::DummyWriteProc, + NOTES => q{ + The table below lists special Composite tags which are used to access other + tags based on the MWG 2.0 recommendations. These tags are only accessible + when explicitly loaded, but this is done automatically by the exiftool + application if MWG is specified as a group for any tag on the command line, + or manually with the C<-use MWG> option. Via the API, the MWG Composite + tags are loaded by calling "C<Image::ExifTool::MWG::Load()>". + + When reading, the value of each MWG tag is B<Derived From> the specified + tags based on the MWG guidelines. When writing, the appropriate associated + tags are written. The value of the IPTCDigest tag is updated automatically + when the IPTC is changed if either the IPTCDigest tag didn't exist + beforehand or its value agreed with the original IPTC digest (indicating + that the XMP is synchronized with the IPTC). IPTC information is written + only if the original file contained IPTC. + + Loading the MWG module activates "strict MWG conformance mode", which has + the effect of causing EXIF, IPTC and XMP in non-standard locations to be + ignored when reading, as per the MWG recommendations. Instead, a "Warning" + tag is generated when non-standard metadata is encountered. This feature + may be disabled by setting C<$Image::ExifTool::MWG::strict = 0> in the + L<ExifTool config file|../config.html> (or from your Perl script when using the API). Note + that the behaviour when writing is not changed: ExifTool always creates new + records only in the standard location, but writes new tags to any + EXIF/IPTC/XMP records that exist. + + Contrary to the EXIF specification, the MWG recommends that EXIF "ASCII" + string values be stored as UTF-8. To honour this, the exiftool application + sets the default internal EXIF string encoding to "UTF8" when the MWG module + is loaded, but via the API this must be done manually by setting the + L<CharsetEXIF|../ExifTool.html#CharsetEXIF> option. + + A complication of the MWG specification is that although the MWG:Creator + property may consist of multiple values, the associated EXIF tag + (EXIF:Artist) is only a simple string. To resolve this discrepancy the MWG + recommends a technique which allows a list of values to be stored in a + string by using a semicolon-space separator (with quotes around values if + necessary). When the MWG module is loaded, ExifTool automatically + implements this policy and changes EXIF:Artist to a list-type tag. + }, + Keywords => { + Flags => ['Writable','List'], + Desire => { + 0 => 'IPTC:Keywords', # (64-character limit) + 1 => 'XMP-dc:Subject', + 2 => 'CurrentIPTCDigest', + 3 => 'IPTCDigest', + }, + RawConv => q{ + return $val[1] if not defined $val[2] or (defined $val[1] and + (not defined $val[3] or $val[2] eq $val[3])); + return Image::ExifTool::MWG::RecoverTruncatedIPTC($val[0], $val[1], 64); + }, + DelCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteAlso => { + # only write Keywords if IPTC exists (eg. set EditGroup option) + 'IPTC:Keywords' => '$opts{EditGroup} = 1; $val', + 'XMP-dc:Subject' => '$val', + }, + }, + Description => { + Writable => 1, + Desire => { + 0 => 'EXIF:ImageDescription', + 1 => 'IPTC:Caption-Abstract', # (2000-character limit) + 2 => 'XMP-dc:Description', + 3 => 'CurrentIPTCDigest', + 4 => 'IPTCDigest', + }, + RawConv => q{ + return $val[0] if defined $val[0] and $val[0] !~ /^ *$/; + return $val[2] if not defined $val[3] or (defined $val[2] and + (not defined $val[4] or $val[3] eq $val[4])); + return Image::ExifTool::MWG::RecoverTruncatedIPTC($val[1], $val[2], 2000); + }, + DelCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteAlso => { + 'EXIF:ImageDescription' => '$val', + 'IPTC:Caption-Abstract' => '$opts{EditGroup} = 1; $val', + 'XMP-dc:Description' => '$val', + }, + }, + DateTimeOriginal => { + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Notes => '"specifies when a photo was taken" - MWG', + Writable => 1, + Shift => 0, # don't shift this tag + Desire => { + 0 => 'Composite:SubSecDateTimeOriginal', + 1 => 'EXIF:DateTimeOriginal', + 2 => 'IPTC:DateCreated', + 3 => 'IPTC:TimeCreated', + 4 => 'XMP-photoshop:DateCreated', + 5 => 'CurrentIPTCDigest', + 6 => 'IPTCDigest', + }, + # must check for validity in RawConv to avoid hiding a same-named tag, + # but IPTC dates use a ValueConv so we need to derive the value there + RawConv => q{ + (defined $val[0] or defined $val[1] or $val[2] or + (defined $val[4] and (not defined $val[5] or not defined $val[6] + or $val[5] eq $val[6]))) ? $val : undef + }, + ValueConv => q{ + return $val[0] if defined $val[0] and $val[0] !~ /^[: ]*$/; + return $val[1] if defined $val[1] and $val[1] !~ /^[: ]*$/; + return $val[4] if not defined $val[5] or (defined $val[4] and + (not defined $val[6] or $val[5] eq $val[6])); + return $val[3] ? "$val[2] $val[3]" : $val[2] if $val[2]; + return undef; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + DelCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteAlso => { + # set EXIF date/time values according to PrintConv option instead + # of defaulting to Type=ValueConv to allow reformatting to be applied + 'Composite:SubSecDateTimeOriginal' => 'delete $opts{Type}; $val', + 'IPTC:DateCreated' => '$opts{EditGroup} = 1; $val', + 'IPTC:TimeCreated' => '$opts{EditGroup} = 1; $val', + 'XMP-photoshop:DateCreated' => '$val', + }, + }, + CreateDate => { + Groups => { 2 => 'Time' }, + Notes => '"specifies when an image was digitized" - MWG', + Writable => 1, + Shift => 0, # don't shift this tag + Desire => { + 0 => 'Composite:SubSecCreateDate', + 1 => 'EXIF:CreateDate', + 2 => 'IPTC:DigitalCreationDate', + 3 => 'IPTC:DigitalCreationTime', + 4 => 'XMP-xmp:CreateDate', + 5 => 'CurrentIPTCDigest', + 6 => 'IPTCDigest', + }, + RawConv => q{ + (defined $val[0] or defined $val[1] or $val[2] or + (defined $val[4] and (not defined $val[5] or not defined $val[6] + or $val[5] eq $val[6]))) ? $val : undef + }, + ValueConv => q{ + return $val[0] if defined $val[0] and $val[0] !~ /^[: ]*$/; + return $val[1] if defined $val[1] and $val[1] !~ /^[: ]*$/; + return $val[4] if not defined $val[5] or (defined $val[4] and + (not defined $val[6] or $val[5] eq $val[6])); + return $val[3] ? "$val[2] $val[3]" : $val[2] if $val[2]; + return undef; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + DelCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteAlso => { + 'Composite:SubSecCreateDate' => 'delete $opts{Type}; $val', + 'IPTC:DigitalCreationDate' => '$opts{EditGroup} = 1; $val', + 'IPTC:DigitalCreationTime' => '$opts{EditGroup} = 1; $val', + 'XMP-xmp:CreateDate' => '$val', + }, + }, + ModifyDate => { + Groups => { 2 => 'Time' }, + Notes => '"specifies when a file was modified by the user" - MWG', + Writable => 1, + Shift => 0, # don't shift this tag + Desire => { + 0 => 'Composite:SubSecModifyDate', + 1 => 'EXIF:ModifyDate', + 2 => 'XMP-xmp:ModifyDate', + 3 => 'CurrentIPTCDigest', + 4 => 'IPTCDigest', + }, + RawConv => q{ + return $val[0] if defined $val[0] and $val[0] !~ /^[: ]*$/; + return $val[1] if defined $val[1] and $val[1] !~ /^[: ]*$/; + return $val[2] if not defined $val[3] or not defined $val[4] or $val[3] eq $val[4]; + return undef; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + # return empty string from check routines so this tag will never be set + # (only WriteAlso tags are written), the only difference is a -v2 message + DelCheck => '""', + WriteCheck => '""', + WriteAlso => { + 'Composite:SubSecModifyDate' => 'delete $opts{Type}; $val', + 'XMP-xmp:ModifyDate' => '$val', + }, + }, + Orientation => { + Writable => 1, + Require => 'EXIF:Orientation', + ValueConv => '$val', + PrintConv => \%Image::ExifTool::Exif::orientation, + DelCheck => '""', + WriteCheck => '""', + WriteAlso => { + 'EXIF:Orientation' => '$val', + }, + }, + Rating => { + Writable => 1, + Require => 'XMP-xmp:Rating', + ValueConv => '$val', + DelCheck => '""', + WriteCheck => '""', + WriteAlso => { + 'XMP-xmp:Rating' => '$val', + }, + }, + Copyright => { + Groups => { 2 => 'Author' }, + Writable => 1, + Desire => { + 0 => 'EXIF:Copyright', + 1 => 'IPTC:CopyrightNotice', # (128-character limit) + 2 => 'XMP-dc:Rights', + 3 => 'CurrentIPTCDigest', + 4 => 'IPTCDigest', + }, + RawConv => q{ + return $val[0] if defined $val[0] and $val[0] !~ /^ *$/; + return $val[2] if not defined $val[3] or (defined $val[2] and + (not defined $val[4] or $val[3] eq $val[4])); + return Image::ExifTool::MWG::RecoverTruncatedIPTC($val[1], $val[2], 128); + }, + DelCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteAlso => { + 'EXIF:Copyright' => q{ + # encode if necessary (not automatic because Format is 'undef') + my $enc = $self->Options('CharsetEXIF'); + if ($enc) { + my $v = $val; + $self->Encode($v,$enc); + return $v; + } + return $val; + }, + 'IPTC:CopyrightNotice' => '$opts{EditGroup} = 1; $val', + 'XMP-dc:Rights' => '$val', + }, + }, + Creator => { + Groups => { 2 => 'Author' }, + Flags => ['Writable','List'], + Desire => { + 0 => 'EXIF:Artist', + 1 => 'IPTC:By-line', # (32-character limit) + 2 => 'XMP-dc:Creator', + 3 => 'CurrentIPTCDigest', + 4 => 'IPTCDigest', + }, + RawConv => q{ + return $val[0] if defined $val[0] and $val[0] !~ /^ *$/; + return $val[2] if not defined $val[3] or (defined $val[2] and + (not defined $val[4] or $val[3] eq $val[4])); + return Image::ExifTool::MWG::RecoverTruncatedIPTC($val[1], $val[2], 32); + }, + DelCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteAlso => { + 'EXIF:Artist' => '$val', + 'IPTC:By-line' => '$opts{EditGroup} = 1; $val', + 'XMP-dc:Creator' => '$val', + }, + }, + Country => { + Groups => { 2 => 'Location' }, + Writable => 1, + Desire => { + 0 => 'IPTC:Country-PrimaryLocationName', # (64-character limit) + 1 => 'XMP-photoshop:Country', + 2 => 'XMP-iptcExt:LocationShownCountryName', + 3 => 'CurrentIPTCDigest', + 4 => 'IPTCDigest', + }, + RawConv => q{ + my $xmpVal = $val[2] || $val[1]; + return $xmpVal if not defined $val[3] or (defined $xmpVal and + (not defined $val[4] or $val[3] eq $val[4])); + return Image::ExifTool::MWG::RecoverTruncatedIPTC($val[0], $xmpVal, 64); + }, + DelCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteAlso => { + 'IPTC:Country-PrimaryLocationName' => '$opts{EditGroup} = 1; $val', + 'XMP-photoshop:Country' => '$val', # (legacy) + 'XMP-iptcExt:LocationShownCountryName' => '$val', + }, + }, + State => { + Groups => { 2 => 'Location' }, + Writable => 1, + Desire => { + 0 => 'IPTC:Province-State', # (32-character limit) + 1 => 'XMP-photoshop:State', + 2 => 'XMP-iptcExt:LocationShownProvinceState', + 3 => 'CurrentIPTCDigest', + 4 => 'IPTCDigest', + }, + RawConv => q{ + my $xmpVal = $val[2] || $val[1]; + return $xmpVal if not defined $val[3] or (defined $xmpVal and + (not defined $val[4] or $val[3] eq $val[4])); + return Image::ExifTool::MWG::RecoverTruncatedIPTC($val[0], $xmpVal, 32); + }, + DelCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteAlso => { + 'IPTC:Province-State' => '$opts{EditGroup} = 1; $val', + 'XMP-photoshop:State' => '$val', # (legacy) + 'XMP-iptcExt:LocationShownProvinceState' => '$val', + }, + }, + City => { + Groups => { 2 => 'Location' }, + Writable => 1, + Desire => { + 0 => 'IPTC:City', # (32-character limit) + 1 => 'XMP-photoshop:City', + 2 => 'XMP-iptcExt:LocationShownCity', + 3 => 'CurrentIPTCDigest', + 4 => 'IPTCDigest', + }, + RawConv => q{ + my $xmpVal = $val[2] || $val[1]; + return $xmpVal if not defined $val[3] or (defined $xmpVal and + (not defined $val[4] or $val[3] eq $val[4])); + return Image::ExifTool::MWG::RecoverTruncatedIPTC($val[0], $xmpVal, 32); + }, + DelCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteAlso => { + 'IPTC:City' => '$opts{EditGroup} = 1; $val', + 'XMP-photoshop:City' => '$val', # (legacy) + 'XMP-iptcExt:LocationShownCity' => '$val', + }, + }, + Location => { + Groups => { 2 => 'Location' }, + Writable => 1, + Desire => { + 0 => 'IPTC:Sub-location', # (32-character limit) + 1 => 'XMP-iptcCore:Location', + 2 => 'XMP-iptcExt:LocationShownSublocation', + 3 => 'CurrentIPTCDigest', + 4 => 'IPTCDigest', + }, + RawConv => q{ + my $xmpVal = $val[2] || $val[1]; + return $xmpVal if not defined $val[3] or (defined $xmpVal and + (not defined $val[4] or $val[3] eq $val[4])); + return Image::ExifTool::MWG::RecoverTruncatedIPTC($val[0], $xmpVal, 32); + }, + DelCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteCheck => 'Image::ExifTool::MWG::ReconcileIPTCDigest($self)', + WriteAlso => { + 'IPTC:Sub-location' => '$opts{EditGroup} = 1; $val', + 'XMP-iptcCore:Location' => '$val', # (legacy) + 'XMP-iptcExt:LocationShownSublocation' => '$val', + }, + }, +); + +# MWG XMP structures +my %sExtensions = ( + STRUCT_NAME => 'MWG Extensions', + NAMESPACE => undef, # variable namespace + NOTES => q{ + This structure may contain any top-level XMP tags, but none have been + pre-defined in ExifTool. Since no flattened tags have been pre-defined, + RegionExtensions is writable only as a structure (eg. + C<{xmp-dc:creator=me,rating=5}>). Fields for this structure are identified + using the standard ExifTool tag name (with optional leading group name, + and/or trailing language code, and/or trailing C<#> symbol to disable print + conversion). + }, +); +my %sRegionStruct = ( + STRUCT_NAME => 'MWG RegionStruct', + NAMESPACE => 'mwg-rs', + Area => { Struct => \%Image::ExifTool::XMP::sArea }, + Type => { + PrintConv => { + Face => 'Face', + Pet => 'Pet', + Focus => 'Focus', + BarCode => 'BarCode', + }, + }, + Name => { }, + Description => { }, + FocusUsage => { + PrintConv => { + EvaluatedUsed => 'Evaluated, Used', + EvaluatedNotUsed => 'Evaluated, Not Used', + NotEvaluatedNotUsed => 'Not Evaluated, Not Used', + }, + }, + BarCodeValue=> { }, + Extensions => { Struct => \%sExtensions }, + Rotation => { # (observed in LR6 XMP) + Writable => 'real', + Notes => 'not part of MWG 2.0 spec', + }, + seeAlso => { Namespace => 'rdfs', Resource => 1 }, +); +my %sKeywordStruct; +%sKeywordStruct = ( + STRUCT_NAME => 'MWG KeywordStruct', + NAMESPACE => 'mwg-kw', + Keyword => { }, + Applied => { Writable => 'boolean' }, + Children => { Struct => \%sKeywordStruct, List => 'Bag' }, +); + +# MWG 2.0 XMP region namespace tags +%Image::ExifTool::MWG::Regions = ( + %Image::ExifTool::XMP::xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-mwg-rs', 2 => 'Image' }, + NAMESPACE => 'mwg-rs', + NOTES => q{ + Image region metadata defined by the MWG 2.0 specification. These tags + may be accessed without the need to load the MWG Composite tags above. See + L<https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf> + for the official specification. + }, + Regions => { + Name => 'RegionInfo', + FlatName => 'Region', + Struct => { + STRUCT_NAME => 'MWG RegionInfo', + NAMESPACE => 'mwg-rs', + RegionList => { + FlatName => 'Region', + Struct => \%sRegionStruct, + List => 'Bag', + }, + AppliedToDimensions => { Struct => \%Image::ExifTool::XMP::sDimensions }, + }, + }, + RegionsRegionList => { Flat => 1, Name => 'RegionList' }, +); + +# MWG 2.0 XMP hierarchical keyword namespace tags +%Image::ExifTool::MWG::Keywords = ( + %Image::ExifTool::XMP::xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-mwg-kw', 2 => 'Image' }, + NAMESPACE => 'mwg-kw', + NOTES => q{ + Hierarchical keywords metadata defined by the MWG 2.0 specification. + ExifTool unrolls keyword structures to an arbitrary depth of 6 to allow + individual levels to be accessed with different tag names, and to avoid + infinite recursion. See + L<https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf> + for the official specification. + }, + # arbitrarily define only the first 6 levels of the keyword hierarchy + Keywords => { + Name => 'KeywordInfo', + Struct => { + STRUCT_NAME => 'MWG KeywordInfo', + NAMESPACE => 'mwg-kw', + Hierarchy => { Struct => \%sKeywordStruct, List => 'Bag' }, + }, + }, + KeywordsHierarchy => { Name => 'HierarchicalKeywords', Flat => 1 }, + KeywordsHierarchyKeyword => { Name => 'HierarchicalKeywords1', Flat => 1 }, + KeywordsHierarchyApplied => { Name => 'HierarchicalKeywords1Applied', Flat => 1 }, + KeywordsHierarchyChildren => { Name => 'HierarchicalKeywords1Children', Flat => 1 }, + KeywordsHierarchyChildrenKeyword => { Name => 'HierarchicalKeywords2', Flat => 1 }, + KeywordsHierarchyChildrenApplied => { Name => 'HierarchicalKeywords2Applied', Flat => 1 }, + KeywordsHierarchyChildrenChildren => { Name => 'HierarchicalKeywords2Children', Flat => 1 }, + KeywordsHierarchyChildrenChildrenKeyword => { Name => 'HierarchicalKeywords3', Flat => 1 }, + KeywordsHierarchyChildrenChildrenApplied => { Name => 'HierarchicalKeywords3Applied', Flat => 1 }, + KeywordsHierarchyChildrenChildrenChildren => { Name => 'HierarchicalKeywords3Children', Flat => 1 }, + KeywordsHierarchyChildrenChildrenChildrenKeyword => { Name => 'HierarchicalKeywords4', Flat => 1 }, + KeywordsHierarchyChildrenChildrenChildrenApplied => { Name => 'HierarchicalKeywords4Applied', Flat => 1 }, + KeywordsHierarchyChildrenChildrenChildrenChildren => { Name => 'HierarchicalKeywords4Children', Flat => 1 }, + KeywordsHierarchyChildrenChildrenChildrenChildrenKeyword => { Name => 'HierarchicalKeywords5', Flat => 1 }, + KeywordsHierarchyChildrenChildrenChildrenChildrenApplied => { Name => 'HierarchicalKeywords5Applied', Flat => 1 }, + KeywordsHierarchyChildrenChildrenChildrenChildrenChildren => { Name => 'HierarchicalKeywords5Children', Flat => 1, NoSubStruct => 1 }, # break infinite recursion + KeywordsHierarchyChildrenChildrenChildrenChildrenChildrenKeyword => { Name => 'HierarchicalKeywords6', Flat => 1 }, + KeywordsHierarchyChildrenChildrenChildrenChildrenChildrenApplied => { Name => 'HierarchicalKeywords6Applied', Flat => 1 }, +); + +# MWG 2.0 XMP collections namespace tags +%Image::ExifTool::MWG::Collections = ( + %Image::ExifTool::XMP::xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-mwg-coll', 2 => 'Image' }, + NAMESPACE => 'mwg-coll', + NOTES => q{ + Collections metadata defined by the MWG 2.0 specification. See + L<https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf> + for the official specification. + }, + Collections => { + FlatName => '', + List => 'Bag', + Struct => { + STRUCT_NAME => 'MWG CollectionInfo', + NAMESPACE => 'mwg-coll', + CollectionName => { }, + CollectionURI => { }, + }, + }, +); + + +#------------------------------------------------------------------------------ +# Load the MWG Composite tags +sub Load() +{ + return if $mwgLoaded; + + # add our composite tags + Image::ExifTool::AddCompositeTags('Image::ExifTool::MWG'); + # must also add to lookup so we can write them + # (since MWG tags aren't in the tag lookup by default) + Image::ExifTool::AddTagsToLookup(\%Image::ExifTool::MWG::Composite, + 'Image::ExifTool::Composite'); + + # modify EXIF:Artist to behave as a list-type tag + my $artist = $Image::ExifTool::Exif::Main{0x13b}; + $$artist{List} = 1; + $$artist{IsOverwriting} = \&OverwriteStringList; + $$artist{RawConv} = \&StringToList; + + # enable MWG strict mode if not set already + # (causes non-standard EXIF, IPTC and XMP to be ignored) + $Image::ExifTool::MWG::strict = 1 unless defined $Image::ExifTool::MWG::strict; + + $mwgLoaded = 1; +} + +#------------------------------------------------------------------------------ +# Change a list of values to a string using MWG rules +# Inputs: 0)reference to list of values +# Returns: string of values (and may reformat list entries) +sub ListToString($) +{ + my $vals = shift; + foreach (@$vals) { + # double all quotes in value and quote the value if it begins + # with a quote or contains a semicolon-space separator + if (/^"/ or /; /) { + s/"/""/g; # double all quotes + $_ = qq{"$_"}; # quote the value + } + } + return join('; ', @$vals); +} + +#------------------------------------------------------------------------------ +# Change a string value to a list of values using MWG rules +# Inputs: 0) string of values, 1) ExifTool ref +# Returns: value or list reference if more than one value +# Notes: Sets Warning tag on error +sub StringToList($$) +{ + my ($str, $et) = @_; + my (@vals, $inQuotes); + my @t = split '; ', $str, -1; + foreach (@t) { + my $wasQuotes = $inQuotes; + $inQuotes = 1 if not $inQuotes and s/^"//; + if ($inQuotes) { + # remove the last quote and reset the inQuotes flag if + # the value ended in an odd number of quotes + $inQuotes = 0 if s/((^|[^"])("")*)"$/$1/; + s/""/"/g; # un-double the contained quotes + } + if ($wasQuotes) { + # previous separator was quoted, so concatinate with previous value + $vals[-1] .= '; ' . $_; + } else { + push @vals, $_; + } + } + $et->Warn('Incorrectly quoted MWG string-list value') if $inQuotes; + return @vals > 1 ? \@vals : $vals[0]; +} + +#------------------------------------------------------------------------------ +# Handle logic for overwriting EXIF string-type list tag +# Inputs: 0) ExifTool ref, 1) new value hash ref, +# 2) old string value (or undef if it didn't exist), 3) new value ref +# Returns: 1 and sets the new value for the tag +sub OverwriteStringList($$$$) +{ + local $_; + my ($et, $nvHash, $val, $newValuePt) = @_; + my (@new, $delIndex); + my $writeMode = $et->Options('WriteMode'); + if ($writeMode ne 'wcg') { + if (defined $val) { + $writeMode =~ /w/i or return 0; + } else { + $writeMode =~ /c/i or return 0; + } + } + if ($$nvHash{DelValue} and defined $val) { + # preserve specified old values + my $old = StringToList($val, $et); + my @old = ref $old eq 'ARRAY' ? @$old : $old; + if (@{$$nvHash{DelValue}}) { + my %del; + $del{$_} = 1 foreach @{$$nvHash{DelValue}}; + foreach (@old) { + $del{$_} or push(@new, $_), next; + $delIndex or $delIndex = scalar @new; + } + } else { + push @new, @old; + } + } + # add new values (at location of deleted values, if any) + if ($$nvHash{Value}) { + if (defined $delIndex) { + splice @new, $delIndex, 0, @{$$nvHash{Value}}; + } else { + push @new, @{$$nvHash{Value}}; + } + } + if (@new) { + # convert back to string format + $$newValuePt = ListToString(\@new); + } else { + $$newValuePt = undef; # delete the tag + } + return 1; +} + +#------------------------------------------------------------------------------ +# Reconcile IPTC digest after writing an MWG tag +# Inputs: 0) ExifTool object ref +# Returns: empty string +sub ReconcileIPTCDigest($) +{ + my $et = shift; + + # set new value for IPTCDigest if not done already + unless ($Image::ExifTool::Photoshop::iptcDigestInfo and + $$et{NEW_VALUE}{$Image::ExifTool::Photoshop::iptcDigestInfo}) + { + # write new IPTCDigest only if it doesn't exist or + # is the same as the digest of the original IPTC + my @a; # (capture warning messages) + @a = $et->SetNewValue('Photoshop:IPTCDigest', 'old', Protected => 1, DelValue => 1); + @a = $et->SetNewValue('Photoshop:IPTCDigest', 'new', Protected => 1); + } + return ''; +} + +#------------------------------------------------------------------------------ +# Recover strings which were truncated by IPTC dataset length limit +# Inputs: 0) IPTC value, 1) XMP value, 2) length limit +# Notes: handles the case where IPTC and/or XMP values are lists +sub RecoverTruncatedIPTC($$$) +{ + my ($iptc, $xmp, $limit) = @_; + + return $iptc unless defined $xmp; + if (ref $iptc) { + $xmp = [ $xmp ] unless ref $xmp; + my ($i, @vals); + for ($i=0; $i<@$iptc; ++$i) { + push @vals, RecoverTruncatedIPTC($$iptc[$i], $$xmp[$i], $limit); + } + return \@vals; + } elsif (defined $iptc and length $iptc == $limit) { + $xmp = $$xmp[0] if ref $xmp; # take first element of list + return $xmp if length $xmp > $limit and $iptc eq substr($xmp, 0, $limit); + } + return $iptc; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MWG - Metadata Working Group support + +=head1 SYNOPSIS + + # enable MWG Composite tags + use Image::ExifTool::MWG; + Image::ExifTool::MWG::Load(); + + # enable MWG strict mode + $Image::ExifTool::MWG::strict = 1; + + # disable MWG strict mode + $Image::ExifTool::MWG::strict = 0; + +=head1 DESCRIPTION + +The MWG module contains Composite tag definitions which are designed to +simplify implementation of the Metadata Working Group guidelines. These +special MWG Composite tags are enabled by calling the Load() method: + + use Image::ExifTool::MWG; + Image::ExifTool::MWG::Load(); + +By default, loading the MWG Composite tags enables "strict MWG conformance" +unless previously enabled or disabled by the user. In this mode, ExifTool +will generate a Warning instead of extracting EXIF, IPTC and XMP from +non-standard locations. The strict mode may be disabled or enabled at any +time by setting the MWG "strict" flag to 0 or 1. eg) + + $Image::ExifTool::MWG::strict = 0; + +This module also contains the MWG XMP tags which are loaded automatically by +ExifTool as required, and are independent of the MWG Composite tags which +must be loaded explicitly as described above. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.metadataworkinggroup.org/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MWG Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/MXF.pm b/ExifTool/lib/Image/ExifTool/MXF.pm new file mode 100644 index 0000000..90f697d --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MXF.pm @@ -0,0 +1,3031 @@ +#------------------------------------------------------------------------------ +# File: MXF.pm +# +# Description: Read MXF meta information +# +# Revisions: 2010/12/15 - P. Harvey Created +# +# References: 1) http://sourceforge.net/projects/mxflib/ +# 2) http://www.aafassociation.org/downloads/whitepapers/MXFPhysicalview.pdf +# 3) http://archive.nlm.nih.gov/pubs/pearson/MJ2_Metadata2005.pdf +# 4) http://www.aafassociation.org/downloads/specifications/AMWA-AS-03-Delivery-Spec-1_0.pdf +# 5) http://paul-sampson.ca/private/s385m.pdf +# 6) http://avwiki.nl/documents/eg41.pdf +# 7) http://avwiki.nl/documents/eg42.pdf +# 8) http://rhea.tele.ucl.ac.be:8081/Plone/Members/egoray/thesaurus-dictionnaire-metadata/ +# a) S335M Dictionary structure.pdf +# b) S330M UMID.PDF +# 9) http://www.smpte-ra.org/mdd/RP210v12-publication-20100623.xls +# 10) http://www.amwa.tv/downloads/specifications/aafobjectspec-v1.1.pdf +# 11) http://www.mog-solutions.com/img_upload/PDF/XML%20Schema%20for%20MXF%20Metadata.pdf +# 12) http://www.freemxf.org/freemxf_board/viewtopic.php?p=545&sid=00a5c17e07d828c1e93ecdbaed3076f7 +# +# Notes: 1) The alternate language support is dependent on the serialization +# sequence. Specifically the InstanceUID's must come before any +# text in an alternate language set, and these sets must come +# after the language definitions. +# +# 2) UTF-16 surrogate pairs are not handled properly. +# +# 3) This code is not tested for files larger than 2 GB, +# but in theory this should be OK with 64-bit Perl. +#------------------------------------------------------------------------------ + +package Image::ExifTool::MXF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::GPS; + +$VERSION = '1.08'; + +sub ProcessPrimer($$$); +sub ProcessLocalSet($$$); +sub ConvLatLon($); +sub ReadMXFValue($$$); +sub SetGroups($$;$$); +sub ConvertDurations($$); + +# list of currently decoded MXF value types +my %knownType = ( + Alt => 1, Lon => 1, UL => 1, + AUID => 1, PackageID => 1, UMID => 1, + BatchOfUL => 1, Position => 1, 'UTF-16' => 1, + Boolean => 1, ProductVersion => 1, UUID => 1, + GUID => 1, StrongReference => 1, VersionType => 1, + Hex => 1, StrongReferenceArray => 1, WeakReference => 1, + Label => 1, StrongReferenceBatch => 1, + Lat => 1, Timestamp => 1, + Length => 1, UID => 1, +); + +# common tag info parameters +my %header = ( + IsHeader => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::MXF::Header', + ProcessProc => \&Image::ExifTool::ProcessBinaryData, + }, +); +my %localSet = ( + SubDirectory => { TagTable => 'Image::ExifTool::MXF::Main' }, +); +my %timestamp = ( + Type => 'Timestamp', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', +); +my %geoLat = ( + Groups => { 2 => 'Location' }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', +); +my %geoLon = ( + Groups => { 2 => 'Location' }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', +); +my %geoLatLon = ( + Groups => { 2 => 'Location' }, + PrintConv => q{ + my ($lat, $lon) = split ' ', $val; + $lat = Image::ExifTool::GPS::ToDMS($self, $lat, 1, 'N'); + $lon = Image::ExifTool::GPS::ToDMS($self, $lon, 1, 'E'); + return "$lat, $lon"; + }, +); +my %duration = ( + IsDuration => 1, # flag used to correct durations by the appropriate EditRate + RawConv => '$val > 1e18 ? undef : $val', # (all 0xff's) + PrintConv => 'ConvertDuration($val)', +); + +# ComponentDataDefinition values +my %componentDataDef = ( + PrintConv => { + '060e2b34.0401.0101.01030201.01000000' => 'SMPTE 12M Timecode Track', + '060e2b34.0401.0101.01030201.02000000' => 'SMPTE 12M Timecode Track with active user bits', + '060e2b34.0401.0101.01030201.03000000' => 'SMPTE 309M Timecode Track', + '060e2b34.0401.0101.01030201.10000000' => 'Descriptive Metadata Track', + '060e2b34.0401.0101.01030202.01000000' => 'Picture Essence Track', + '060e2b34.0401.0101.01030202.02000000' => 'Sound Essence Track', + '060e2b34.0401.0101.01030202.03000000' => 'Data Essence Track', + }, +); + +# MXF tags (ref 1) +# Note: The Binary flag is automatically set for all Unknown tags with unknown Type +%Image::ExifTool::MXF::Main = ( + GROUPS => { 2 => 'Video' }, + VARS => { NO_LOOKUP => 1, NO_ID => 1 }, # tag ID's are too bulky + NOTES => q{ + Tags extracted from Material Exchange Format files. Tag ID's are not listed + because they are bulky 16-byte binary values. + }, + # '060a2b34.0101.0101.01010100.00000000' => { Name => 'UMIDVideo', Type => 'Node' }, + # '060a2b34.0101.0101.01010110.00000000' => { Name => 'UMIDVideo', Unknown => 1 }, + # '060a2b34.0101.0101.01010111.00000000' => { Name => 'UMIDVideo', Unknown => 1 }, + # '060a2b34.0101.0101.01010112.00000000' => { Name => 'UMIDVideo', Unknown => 1 }, + # '060a2b34.0101.0101.01010120.00000000' => { Name => 'UMIDVideo', Unknown => 1 }, + # '060a2b34.0101.0101.01010121.00000000' => { Name => 'UMIDVideo', Unknown => 1 }, + # '060a2b34.0101.0101.01010122.00000000' => { Name => 'UMIDVideo', Unknown => 1 }, + # '060a2b34.0101.0101.01010200.00000000' => { Name => 'UMIDAudio', Type => 'Node' }, + # '060a2b34.0101.0101.01010210.00000000' => { Name => 'UMIDAudio', Unknown => 1 }, + # '060a2b34.0101.0101.01010211.00000000' => { Name => 'UMIDAudio', Unknown => 1 }, + # '060a2b34.0101.0101.01010212.00000000' => { Name => 'UMIDAudio', Unknown => 1 }, + # '060a2b34.0101.0101.01010220.00000000' => { Name => 'UMIDAudio', Unknown => 1 }, + # '060a2b34.0101.0101.01010221.00000000' => { Name => 'UMIDAudio', Unknown => 1 }, + # '060a2b34.0101.0101.01010222.00000000' => { Name => 'UMIDAudio', Unknown => 1 }, + # '060a2b34.0101.0101.01010300.00000000' => { Name => 'UMIDData', Type => 'Node' }, + # '060a2b34.0101.0101.01010310.00000000' => { Name => 'UMIDData', Unknown => 1 }, + # '060a2b34.0101.0101.01010311.00000000' => { Name => 'UMIDData', Unknown => 1 }, + # '060a2b34.0101.0101.01010312.00000000' => { Name => 'UMIDData', Unknown => 1 }, + # '060a2b34.0101.0101.01010320.00000000' => { Name => 'UMIDData', Unknown => 1 }, + # '060a2b34.0101.0101.01010321.00000000' => { Name => 'UMIDData', Unknown => 1 }, + # '060a2b34.0101.0101.01010322.00000000' => { Name => 'UMIDData', Unknown => 1 }, + # '060a2b34.0101.0101.01010400.00000000' => { Name => 'UMIDSystem', Type => 'Node' }, + # '060a2b34.0101.0101.01010410.00000000' => { Name => 'UMIDSystem', Unknown => 1 }, + # '060a2b34.0101.0101.01010411.00000000' => { Name => 'UMIDSystem', Unknown => 1 }, + # '060a2b34.0101.0101.01010412.00000000' => { Name => 'UMIDSystem', Unknown => 1 }, + # '060a2b34.0101.0101.01010420.00000000' => { Name => 'UMIDSystem', Unknown => 1 }, + # '060a2b34.0101.0101.01010421.00000000' => { Name => 'UMIDSystem', Unknown => 1 }, + # '060a2b34.0101.0101.01010422.00000000' => { Name => 'UMIDSystem', Unknown => 1 }, + + # '060e2b34.0101.0101.00000000.00000000' => { Name => 'Elements', Type => 'Node' }, + # '060e2b34.0101.0101.01000000.00000000' => { Name => 'Identifiers', Type => 'Node' }, + # '060e2b34.0101.0101.01010000.00000000' => { Name => 'GloballyUniqueIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0101.01011000.00000000' => { Name => 'InternationalBroadcastingOrganizationIdentifiers', Type => 'Node' }, + '060e2b34.0101.0101.01011001.00000000' => { Name => 'OrganizationID', Format => 'string' }, + # '060e2b34.0101.0101.01011003.00000000' => { Name => 'ProgramIdentifiers', Type => 'Node' }, + '060e2b34.0101.0101.01011003.01000000' => { Name => 'UPID', Unknown => 1 }, + '060e2b34.0101.0101.01011003.02000000' => { Name => 'UPN', Unknown => 1 }, + # '060e2b34.0101.0101.01011004.00000000' => { Name => 'PhysicalMediaIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0101.01011004.01000000' => { Name => 'TapeIdentifiers', Type => 'Node' }, + '060e2b34.0101.0101.01011004.01010000' => { Name => 'IBTN', Unknown => 1 }, + # '060e2b34.0101.0101.01011100.00000000' => { Name => 'InternationalStandardIdentifiers', Type => 'Node' }, + '060e2b34.0101.0101.01011101.00000000' => { Name => 'ISAN', Unknown => 1 }, + '060e2b34.0101.0101.01011102.00000000' => { Name => 'ISBN', Unknown => 1 }, + '060e2b34.0101.0101.01011103.00000000' => { Name => 'ISSN', Unknown => 1 }, + '060e2b34.0101.0101.01011104.00000000' => { Name => 'ISWC', Unknown => 1 }, + '060e2b34.0101.0101.01011105.00000000' => { Name => 'ISMN', Unknown => 1 }, + '060e2b34.0101.0101.01011106.00000000' => { Name => 'ISCI', Unknown => 1 }, + '060e2b34.0101.0101.01011107.00000000' => { Name => 'ISRC', Unknown => 1 }, + '060e2b34.0101.0101.01011108.00000000' => { Name => 'ISRN', Unknown => 1 }, + '060e2b34.0101.0101.01011109.00000000' => { Name => 'ISBD', Unknown => 1 }, + '060e2b34.0101.0101.0101110a.00000000' => { Name => 'ISTC', Unknown => 1 }, + # '060e2b34.0101.0101.01011300.00000000' => { Name => 'InternationalStandardCompoundIdentifiers', Type => 'Node' }, + '060e2b34.0101.0101.01011301.00000000' => { Name => 'SICI', Unknown => 1 }, + '060e2b34.0101.0101.01011302.00000000' => { Name => 'BICI', Unknown => 1 }, + '060e2b34.0101.0101.01011303.00000000' => { Name => 'AICI', Unknown => 1 }, + '060e2b34.0101.0101.01011304.00000000' => { Name => 'PII', Unknown => 1 }, + # '060e2b34.0101.0101.01011500.00000000' => { Name => 'ObjectIdentifiers', Type => 'Node' }, + '060e2b34.0101.0101.01011501.00000000' => { Name => 'DOI', Unknown => 1 }, + '060e2b34.0101.0101.01011502.00000000' => { Name => 'InstanceUID', Type => 'GUID', Unknown => 1 }, + '060e2b34.0101.0101.01011510.00000000' => { Name => 'PackageID', Type => 'PackageID', Unknown => 1 }, + # '060e2b34.0101.0101.01012000.00000000' => { Name => 'DeviceIdentifiers', Type => 'Node' }, + '060e2b34.0101.0101.01012001.00000000' => { Name => 'DeviceDesignation', Format => 'string' }, + '060e2b34.0101.0101.01012003.00000000' => { Name => 'DeviceModel', Format => 'string' }, + '060e2b34.0101.0101.01012004.00000000' => { Name => 'DeviceSerialNumber', Format => 'string' }, + # '060e2b34.0101.0101.01020000.00000000' => { Name => 'GloballyUniqueLocators', Type => 'Node' }, + # '060e2b34.0101.0101.01020100.00000000' => { Name => 'UniformResourceLocators', Type => 'Node' }, + '060e2b34.0101.0101.01020101.00000000' => { Name => 'URL', Format => 'string' }, + '060e2b34.0101.0101.01020101.01000000' => { Name => 'URL', Type => 'UTF-16' }, + '060e2b34.0101.0101.01020102.00000000' => { Name => 'PURL', Format => 'string' }, + '060e2b34.0101.0101.01020103.00000000' => { Name => 'URN', Format => 'string' }, + # '060e2b34.0101.0101.01030000.00000000' => { Name => 'LocallyUniqueIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0101.01030100.00000000' => { Name => 'AdministrativeIdentifiers', Type => 'Node' }, + '060e2b34.0101.0101.01030101.00000000' => { Name => 'TransmissionID', Format => 'string' }, + '060e2b34.0101.0101.01030102.00000000' => { Name => 'ArchiveID', Format => 'string' }, + '060e2b34.0101.0101.01030103.00000000' => { Name => 'ItemID', Format => 'string' }, + '060e2b34.0101.0101.01030104.00000000' => { Name => 'AccountingReferenceNumber', Format => 'string' }, + '060e2b34.0101.0101.01030105.00000000' => { Name => 'TrafficID', Format => 'string' }, + # '060e2b34.0101.0101.01030200.00000000' => { Name => 'LocalPhysicalMediaIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0101.01030201.00000000' => { Name => 'LocalFilmID', Type => 'Node' }, + '060e2b34.0101.0101.01030201.01000000' => { Name => 'ReelOrRollNumber', Format => 'string' }, + # '060e2b34.0101.0101.01030202.00000000' => { Name => 'LocalTapeIdentifiers', Type => 'Node' }, + '060e2b34.0101.0101.01030202.01000000' => { Name => 'LocalTapeNumber', Format => 'string' }, + # '060e2b34.0101.0101.01030300.00000000' => { Name => 'LocalObjectIdentifiers', Type => 'Node' }, + '060e2b34.0101.0101.01030301.00000000' => { Name => 'LUID', Format => 'int32u' }, + '060e2b34.0101.0101.01030302.01000000' => { Name => 'PackageName', Type => 'UTF-16' }, + # '060e2b34.0101.0101.01040000.00000000' => { Name => 'LocallyUniqueLocators', Type => 'Node' }, + # '060e2b34.0101.0101.01040100.00000000' => { Name => 'MediaLocators', Type => 'Node' }, + '060e2b34.0101.0101.01040101.00000000' => { Name => 'LocalFilePath', Format => 'string' }, + # '060e2b34.0101.0101.01040700.00000000' => { Name => 'FilmLocators', Type => 'Node' }, + '060e2b34.0101.0101.01040701.00000000' => { Name => 'EdgeCode', Format => 'string' }, + '060e2b34.0101.0101.01040702.00000000' => { Name => 'FrameCode', Format => 'string' }, + '060e2b34.0101.0101.01040703.00000000' => { Name => 'KeyCode', Type => 'KeyCode', Unknown => 1 }, + '060e2b34.0101.0101.01040704.00000000' => { Name => 'InkNumber', Format => 'string' }, + # '060e2b34.0101.0101.01041000.00000000' => { Name => 'ProxyLocators', Type => 'Node' }, + '060e2b34.0101.0101.01041001.00000000' => { Name => 'KeyText', Format => 'string' }, + '060e2b34.0101.0101.01041002.00000000' => { Name => 'KeyFrame', Format => 'string' }, + '060e2b34.0101.0101.01041003.00000000' => { Name => 'KeySound', Format => 'string' }, + '060e2b34.0101.0101.01041004.00000000' => { Name => 'KeyDataOrProgram', Format => 'string' }, + # '060e2b34.0101.0101.01050000.00000000' => { Name => 'Titles', Type => 'Node' }, + '060e2b34.0101.0101.01050100.00000000' => { Name => 'TitleKind', Format => 'string' }, + '060e2b34.0101.0101.01050200.00000000' => { Name => 'MainTitle', Format => 'string' }, + '060e2b34.0101.0101.01050300.00000000' => { Name => 'SecondaryTitle', Format => 'string' }, + '060e2b34.0101.0101.01050400.00000000' => { Name => 'SeriesNumber', Format => 'string' }, + '060e2b34.0101.0101.01050500.00000000' => { Name => 'EpisodeNumber', Format => 'string' }, + '060e2b34.0101.0101.01050600.00000000' => { Name => 'SceneNumber', Format => 'string' }, + '060e2b34.0101.0101.01050700.00000000' => { Name => 'TakeNumber', Format => 'int16u' }, + # '060e2b34.0101.0101.01100000.00000000' => { Name => 'IntellectualPropertyRightsIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0101.01100100.00000000' => { Name => 'SUISACISACIPI', Type => 'Node' }, + '060e2b34.0101.0101.01100101.00000000' => { Name => 'CISACLegalEntityID', Unknown => 1 }, + # '060e2b34.0101.0101.01100200.00000000' => { Name => 'AGICOAIdentifers', Type => 'Node' }, + '060e2b34.0101.0101.01100201.00000000' => { Name => 'AGICOAID', Unknown => 1 }, + # '060e2b34.0101.0101.02000000.00000000' => { Name => 'ADMINISTRATION', Type => 'Node' }, + # '060e2b34.0101.0101.02010000.00000000' => { Name => 'Supplier', Type => 'Node' }, + '060e2b34.0101.0101.02010100.00000000' => { Name => 'SourceOrganization', Format => 'string' }, + '060e2b34.0101.0101.02010200.00000000' => { Name => 'SupplyContractNumber', Format => 'string' }, + '060e2b34.0101.0101.02010300.00000000' => { Name => 'OriginalProducerName', Format => 'string' }, + # '060e2b34.0101.0101.02020000.00000000' => { Name => 'Product', Type => 'Node' }, + '060e2b34.0101.0101.02020100.00000000' => { Name => 'TotalEpisodeCount', Format => 'int16u' }, + # '060e2b34.0101.0101.02050000.00000000' => { Name => 'Rights', Type => 'Node' }, + # '060e2b34.0101.0101.02050100.00000000' => { Name => 'Copyright', Type => 'Node' }, + '060e2b34.0101.0101.02050101.00000000' => { Name => 'CopyrightStatus', Format => 'string' }, + '060e2b34.0101.0101.02050102.00000000' => { Name => 'CopyrightOwnerName', Format => 'string' }, + # '060e2b34.0101.0101.02050200.00000000' => { Name => 'IntellectualRights', Type => 'Node' }, + '060e2b34.0101.0101.02050201.00000000' => { Name => 'IntellectualPropertyDescription', Format => 'string' }, + '060e2b34.0101.0101.02050202.00000000' => { Name => 'IntellectualPropertyRights', Format => 'string' }, + # '060e2b34.0101.0101.02050300.00000000' => { Name => 'LegalPersonalities', Type => 'Node' }, + '060e2b34.0101.0101.02050301.00000000' => { Name => 'Rightsholder', Format => 'string' }, + '060e2b34.0101.0101.02050302.00000000' => { Name => 'RightsManagementAuthority', Format => 'string' }, + '060e2b34.0101.0101.02050303.00000000' => { Name => 'InterestedPartyName', Format => 'string' }, + # '060e2b34.0101.0101.02050400.00000000' => { Name => 'IntellectualPropertyRightsOptions', Type => 'Node' }, + '060e2b34.0101.0101.02050401.00000000' => { Name => 'MaximumUseCount', Format => 'int16u' }, + '060e2b34.0101.0101.02050402.00000000' => { Name => 'LicenseOptionsDescription', Format => 'string' }, + # '060e2b34.0101.0101.02060000.00000000' => { Name => 'FinancialInformation', Type => 'Node' }, + # '060e2b34.0101.0101.02060100.00000000' => { Name => 'Currencies', Type => 'Node' }, + '060e2b34.0101.0101.02060101.00000000' => { Name => 'CurrencyCode', Format => 'string' }, + # '060e2b34.0101.0101.02060200.00000000' => { Name => 'PaymentsAndCosts', Type => 'Node' }, + '060e2b34.0101.0101.02060201.00000000' => { Name => 'RoyaltyPaymentInformation', Format => 'string' }, + # '060e2b34.0101.0101.02060300.00000000' => { Name => 'Income', Type => 'Node' }, + '060e2b34.0101.0101.02060301.00000000' => { Name => 'RoyaltyIncomeInformation', Format => 'string' }, + # '060e2b34.0101.0101.02070000.00000000' => { Name => 'AccessControl', Type => 'Node' }, + '060e2b34.0101.0101.02070100.00000000' => { Name => 'RestrictionsonUse', Format => 'string' }, + '060e2b34.0101.0101.02070200.00000000' => { Name => 'ExCCIData', Type => 'DataBlock', Unknown => 1 }, + # '060e2b34.0101.0101.02080000.00000000' => { Name => 'Security', Type => 'Node' }, + # '060e2b34.0101.0101.02080100.00000000' => { Name => 'SystemAccess', Type => 'Node' }, + '060e2b34.0101.0101.02080101.00000000' => { Name => 'UserName', Format => 'string' }, + '060e2b34.0101.0101.02080101.01000000' => { Name => 'UserName', Type => 'UTF-16' }, + '060e2b34.0101.0101.02080102.00000000' => { Name => 'Password', Format => 'string' }, + '060e2b34.0101.0101.02080102.01000000' => { Name => 'Password', Type => 'UTF-16' }, + # '060e2b34.0101.0101.02090000.00000000' => { Name => 'Encryption', Type => 'Node' }, + # '060e2b34.0101.0101.02090100.00000000' => { Name => 'FilmEncryption', Type => 'Node' }, + # '060e2b34.0101.0101.02090101.00000000' => { Name => 'ScramblingKeys', Type => 'Node' }, + '060e2b34.0101.0101.02090101.01000000' => { Name => 'ScramblingKeyKind', Format => 'string' }, + '060e2b34.0101.0101.02090101.02000000' => { Name => 'ScramblingKeyValue', Format => 'int8u' }, + # '060e2b34.0101.0101.02100000.00000000' => { Name => 'PublicationOutlet', Type => 'Node' }, + # '060e2b34.0101.0101.02100100.00000000' => { Name => 'Broadcast', Type => 'Node' }, + # '060e2b34.0101.0101.02100101.00000000' => { Name => 'Broadcaster', Type => 'Node' }, + '060e2b34.0101.0101.02100101.01000000' => { Name => 'BroadcastOrganizationName', Format => 'string' }, + '060e2b34.0101.0101.02100101.02000000' => { Name => 'BroadcastChannel', Format => 'string' }, + '060e2b34.0101.0101.02100101.03000000' => { Name => 'BroadcastMediumKind', Format => 'string' }, + '060e2b34.0101.0101.02100101.05000000' => { Name => 'BroadcastRegion', Format => 'string' }, + # '060e2b34.0101.0101.02200000.00000000' => { Name => 'BroadcastAndRepeatInformation', Type => 'Node' }, + # '060e2b34.0101.0101.02200100.00000000' => { Name => 'BroadcastFlags', Type => 'Node' }, + '060e2b34.0101.0101.02200101.00000000' => { Name => 'FirstBroadcastFlag', Type => 'Boolean' }, + # '060e2b34.0101.0101.02200200.00000000' => { Name => 'RepeatNumbers', Type => 'Node' }, + '060e2b34.0101.0101.02200201.00000000' => { Name => 'CurrentRepeatNumber', Format => 'int16u' }, + '060e2b34.0101.0101.02200202.00000000' => { Name => 'PreviousRepeatNumber', Format => 'int16u' }, + # '060e2b34.0101.0101.02200300.00000000' => { Name => 'Ratings', Type => 'Node' }, + '060e2b34.0101.0101.02200301.00000000' => { Name => 'AudienceRating', Format => 'int32u' }, + '060e2b34.0101.0101.02200302.00000000' => { Name => 'AudienceReach', Format => 'int32u' }, + # '060e2b34.0101.0101.02300000.00000000' => { Name => 'ParticipatingParties', Type => 'Node' }, + # '060e2b34.0101.0101.02300100.00000000' => { Name => 'IndividualsAndGroups', Type => 'Node' }, + '060e2b34.0101.0101.02300101.00000000' => { Name => 'NatureOfPersonality', Format => 'string' }, + # '060e2b34.0101.0101.02300102.00000000' => { Name => 'Production', Type => 'Node' }, + '060e2b34.0101.0101.02300102.01000000' => { Name => 'ContributionStatus', Format => 'string' }, + # '060e2b34.0101.0101.02300103.00000000' => { Name => 'SupportAndAdministrationDetails', Type => 'Node' }, + '060e2b34.0101.0101.02300103.01000000' => { Name => 'SupportOrAdministrationStatus', Format => 'string' }, + # '060e2b34.0101.0101.02300200.00000000' => { Name => 'OrganizationsAndPublicBodies', Type => 'Node' }, + '060e2b34.0101.0101.02300201.00000000' => { Name => 'OrganizationKind', Format => 'string' }, + # '060e2b34.0101.0101.02300202.00000000' => { Name => 'ProductionOrganizationOrPublicBody', Type => 'Node' }, + '060e2b34.0101.0101.02300202.01000000' => { Name => 'ProductionOrganizationRole', Format => 'string' }, + # '060e2b34.0101.0101.02300203.00000000' => { Name => 'SupportAndAdministrationOrganizationOrPublicBody', Type => 'Node' }, + '060e2b34.0101.0101.02300203.01000000' => { Name => 'SupportOrganizationRole', Format => 'string' }, + # '060e2b34.0101.0101.02300500.00000000' => { Name => 'JobFunctionInformation', Type => 'Node' }, + '060e2b34.0101.0101.02300501.00000000' => { Name => 'JobFunctionName', Format => 'string' }, + '060e2b34.0101.0101.02300502.00000000' => { Name => 'RoleName', Format => 'string' }, + # '060e2b34.0101.0101.02300600.00000000' => { Name => 'ContactInformation', Type => 'Node' }, + '060e2b34.0101.0101.02300601.00000000' => { Name => 'ContactKind', Format => 'string' }, + '060e2b34.0101.0101.02300602.00000000' => { Name => 'ContactDepartmentName', Format => 'string' }, + # '060e2b34.0101.0101.02300603.00000000' => { Name => 'PersonOrOrganizationDetails', Type => 'Node' }, + # '060e2b34.0101.0101.02300603.01000000' => { Name => 'PersonNames', Type => 'Node' }, + '060e2b34.0101.0101.02300603.01010000' => { Name => 'FamilyName', Format => 'string' }, + '060e2b34.0101.0101.02300603.01020000' => { Name => 'FirstGivenName', Format => 'string' }, + '060e2b34.0101.0101.02300603.01030000' => { Name => 'SecondGivenName', Format => 'string' }, + '060e2b34.0101.0101.02300603.01040000' => { Name => 'ThirdGivenName', Format => 'string' }, + # '060e2b34.0101.0101.02300603.02000000' => { Name => 'GroupNames', Type => 'Node' }, + '060e2b34.0101.0101.02300603.02010000' => { Name => 'MainName', Format => 'string' }, + '060e2b34.0101.0101.02300603.02020000' => { Name => 'SupplementaryName', Format => 'string' }, + # '060e2b34.0101.0101.02300603.03000000' => { Name => 'OrganizationNames', Type => 'Node' }, + '060e2b34.0101.0101.02300603.03010000' => { Name => 'OrganizationMainName', Format => 'string' }, + '060e2b34.0101.0101.02300603.03020000' => { Name => 'SupplementaryOrganizationName', Format => 'string' }, + # '060e2b34.0101.0101.03000000.00000000' => { Name => 'Interpretive', Type => 'Node' }, + # '060e2b34.0101.0101.03010000.00000000' => { Name => 'Fundamental', Type => 'Node' }, + # '060e2b34.0101.0101.03010100.00000000' => { Name => 'CountriesAndLanguages', Type => 'Node' }, + # '060e2b34.0101.0101.03010101.00000000' => { Name => 'CountryAndRegionCodes', Type => 'Node' }, + '060e2b34.0101.0101.03010101.01000000' => { Name => 'ISO3166CountryCode', Format => 'string' }, + # '060e2b34.0101.0101.03010102.00000000' => { Name => 'LanguageCodes', Type => 'Node' }, + '060e2b34.0101.0101.03010102.01000000' => { Name => 'ISO639-1LanguageCode', Format => 'string' }, + # '060e2b34.0101.0101.03010201.00000000' => { Name => 'SystemInterpretations', Type => 'Node' }, + '060e2b34.0101.0101.03010201.01000000' => { Name => 'OperatingSystemInterpretations', Format => 'int8u' }, + '060e2b34.0101.0101.03010201.02000000' => { + Name => 'ByteOrder', #PH (was int16s, but I have seen "II") + Format => 'string', + PrintConv => { + II => 'Little-endian (Intel, II)', + MM => 'Big-endian (Motorola, MM)', + }, + }, + '060e2b34.0101.0101.03010201.03000000' => { Name => 'EssenceIsIdentified', Type => 'Boolean' }, + # '060e2b34.0101.0101.03010300.00000000' => { Name => 'FundamentalDimensions', Type => 'Node' }, + # '060e2b34.0101.0101.03010301.00000000' => { Name => 'Length', Type => 'Node' }, + '060e2b34.0101.0101.03010301.01000000' => { Name => 'LengthSystemName', Format => 'string' }, + '060e2b34.0101.0101.03010301.02000000' => { Name => 'LengthUnitKind', Format => 'string' }, + # '060e2b34.0101.0101.03010302.00000000' => { Name => 'Angles', Type => 'Node' }, + '060e2b34.0101.0101.03010302.01000000' => { Name => 'AngularUnitKind', Format => 'string' }, + # '060e2b34.0101.0101.03010303.00000000' => { Name => 'Time', Type => 'Node' }, + '060e2b34.0101.0101.03010303.01000000' => { Name => 'TimeSystemOffset', Format => 'string' }, + '060e2b34.0101.0101.03010303.02000000' => { Name => 'TimeUnitKind', Format => 'string' }, + # '060e2b34.0101.0101.03010304.00000000' => { Name => 'Mass', Type => 'Node' }, + # '060e2b34.0101.0101.03010305.00000000' => { Name => 'Energy', Type => 'Node' }, + # '060e2b34.0101.0101.03020000.00000000' => { Name => 'HumanAssignedDescriptors', Type => 'Node' }, + # '060e2b34.0101.0101.03020100.00000000' => { Name => 'Categorization', Type => 'Node' }, + # '060e2b34.0101.0101.03020101.00000000' => { Name => 'ContentClassification', Type => 'Node' }, + '060e2b34.0101.0101.03020101.01000000' => { Name => 'ContentCodingSystem', Format => 'string' }, + '060e2b34.0101.0101.03020101.02000000' => { Name => 'ProgramKind', Format => 'string' }, + '060e2b34.0101.0101.03020101.03000000' => { Name => 'Genre', Format => 'string' }, + '060e2b34.0101.0101.03020101.04000000' => { Name => 'TargetAudience', Format => 'string' }, + # '060e2b34.0101.0101.03020102.00000000' => { Name => 'CatalogingAndIndexing', Type => 'Node' }, + '060e2b34.0101.0101.03020102.01000000' => { Name => 'CatalogDataStatus', Format => 'string' }, + '060e2b34.0101.0101.03020102.02000000' => { Name => 'ThesaurusName', Format => 'string' }, + '060e2b34.0101.0101.03020102.03000000' => { Name => 'Theme', Format => 'string' }, + '060e2b34.0101.0101.03020102.04000000' => { Name => 'ContentClassification', Format => 'string' }, + '060e2b34.0101.0101.03020102.05000000' => { Name => 'Keywords', Format => 'string' }, + '060e2b34.0101.0101.03020102.06000000' => { Name => 'KeyFrames', Format => 'string' }, + '060e2b34.0101.0101.03020102.07000000' => { Name => 'KeySounds', Format => 'string' }, + '060e2b34.0101.0101.03020102.08000000' => { Name => 'KeyData', Format => 'string' }, + # '060e2b34.0101.0101.03020106.00000000' => { Name => 'TextualDescription', Type => 'Node' }, + '060e2b34.0101.0101.03020106.01000000' => { Name => 'Abstract', Format => 'string' }, + '060e2b34.0101.0101.03020106.02000000' => { Name => 'Purpose', Format => 'string' }, + '060e2b34.0101.0101.03020106.03000000' => { Name => 'Description', Format => 'string' }, + '060e2b34.0101.0101.03020106.04000000' => { Name => 'ColorDescriptor', Format => 'string' }, + '060e2b34.0101.0101.03020106.05000000' => { Name => 'FormatDescriptor', Format => 'string' }, + # '060e2b34.0101.0101.03020107.00000000' => { Name => 'Stratum', Type => 'Node' }, + '060e2b34.0101.0101.03020107.01000000' => { Name => 'StratumKind', Format => 'string' }, + # '060e2b34.0101.0101.03020200.00000000' => { Name => 'Assessments', Type => 'Node' }, + # '060e2b34.0101.0101.03020201.00000000' => { Name => 'Awards', Type => 'Node' }, + '060e2b34.0101.0101.03020201.01000000' => { Name => 'IndividualAwardName', Format => 'string' }, + '060e2b34.0101.0101.03020201.02000000' => { Name => 'ProgramAwardName', Format => 'string' }, + # '060e2b34.0101.0101.03020202.00000000' => { Name => 'QualitativeValues', Type => 'Node' }, + '060e2b34.0101.0101.03020202.01000000' => { Name => 'AssetValue', Format => 'string' }, + '060e2b34.0101.0101.03020202.02000000' => { Name => 'ContentValue', Format => 'string' }, + '060e2b34.0101.0101.03020202.03000000' => { Name => 'CulturalValue', Format => 'string' }, + '060e2b34.0101.0101.03020202.04000000' => { Name => 'AestheticValue', Format => 'string' }, + '060e2b34.0101.0101.03020202.05000000' => { Name => 'HistoricalValue', Format => 'string' }, + '060e2b34.0101.0101.03020202.06000000' => { Name => 'TechnicalValue', Format => 'string' }, + '060e2b34.0101.0101.03020202.07000000' => { Name => 'OtherValues', Format => 'string' }, + # '060e2b34.0101.0101.03020300.00000000' => { Name => 'TechnicalCommentsAndDescriptions', Type => 'Node' }, + # '060e2b34.0101.0101.03020301.00000000' => { Name => 'ObjectCommentsAndDescriptions', Type => 'Node' }, + # '060e2b34.0101.0101.03030000.00000000' => { Name => 'Machine-AssignedOrComputedDescriptions', Type => 'Node' }, + # '060e2b34.0101.0101.03030100.00000000' => { Name => 'AutomatedCategorization', Type => 'Node' }, + # '060e2b34.0101.0101.03030101.00000000' => { Name => 'AutomatedContentClassification', Type => 'Node' }, + # '060e2b34.0101.0101.03030102.00000000' => { Name => 'AutomatedCatalogingAndIndexing', Type => 'Node' }, + '060e2b34.0101.0101.03030102.01000000' => { Name => 'CatalogDataStatus', Format => 'string' }, + '060e2b34.0101.0101.03030102.02000000' => { Name => 'CatalogingSystemName', Format => 'string' }, + '060e2b34.0101.0101.03030102.06000000' => { Name => 'ComputedKeywords', Format => 'string' }, + '060e2b34.0101.0101.03030102.07000000' => { Name => 'ComputedKeyFrames', Format => 'string' }, + '060e2b34.0101.0101.03030102.08000000' => { Name => 'ComputedKeySounds', Format => 'string' }, + '060e2b34.0101.0101.03030102.09000000' => { Name => 'ComputedKeyData', Format => 'string' }, + # '060e2b34.0101.0101.03030106.00000000' => { Name => 'ComputedTextualDescription', Type => 'Node' }, + # '060e2b34.0101.0101.03030107.00000000' => { Name => 'AutomatedStratum', Type => 'Node' }, + '060e2b34.0101.0101.03030107.01000000' => { Name => 'ComputedStratumKind', Format => 'string' }, + # '060e2b34.0101.0101.03030300.00000000' => { Name => 'ComputedTechnicalCommentsAndDescriptions', Type => 'Node' }, + # '060e2b34.0101.0101.03030301.00000000' => { Name => 'ComputedObjectCommentsAndDescriptions', Type => 'Node' }, + # '060e2b34.0101.0101.04000000.00000000' => { Name => 'PARAMETRIC', Type => 'Node' }, + # '060e2b34.0101.0101.04010000.00000000' => { Name => 'VideoAndImageEssenceCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04010100.00000000' => { Name => 'FundamentalImageCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04010101.00000000' => { Name => 'AspectRatios', Type => 'Node' }, + '060e2b34.0101.0101.04010101.01000000' => { Name => 'PresentationAspectRatio', Format => 'rational64s' }, + '060e2b34.0101.0101.04010101.02000000' => { Name => 'CaptureAspectRatio', Format => 'string' }, + # '060e2b34.0101.0101.04010200.00000000' => { Name => 'ImageSourceCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04010201.00000000' => { Name => 'Opto-ElectronicFormulation', Type => 'Node' }, + # '060e2b34.0101.0101.04010201.01000000' => { Name => 'TransferCharacteristics', Type => 'Node' }, + '060e2b34.0101.0101.04010201.01010000' => { Name => 'CaptureGammaEquation', Format => 'string' }, + '060e2b34.0101.0101.04010201.01010100' => { Name => 'CaptureGammaEquation', Format => 'rational64s' }, + '060e2b34.0101.0101.04010201.01020000' => { Name => 'LumaEquation', Format => 'string' }, + '060e2b34.0101.0101.04010201.01030000' => { Name => 'ColorimetryCode', Format => 'string' }, + '060e2b34.0101.0101.04010201.01040000' => { Name => 'SignalFormCode', Format => 'string' }, + # '060e2b34.0101.0101.04010300.00000000' => { Name => 'VideoAndImageScanningParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04010301.00000000' => { Name => 'TemporalParameters', Type => 'Node' }, + '060e2b34.0101.0101.04010301.01000000' => { Name => 'ColorFieldCode', Format => 'int8u' }, + '060e2b34.0101.0101.04010301.02000000' => { Name => 'FieldRate', Format => 'int16u' }, + '060e2b34.0101.0101.04010301.03000000' => { Name => 'FrameRate', Format => 'int16u' }, + '060e2b34.0101.0101.04010301.04000000' => { Name => 'FrameLayout', Format => 'int8u' }, + '060e2b34.0101.0101.04010301.05000000' => { Name => 'SamplingStructureCode', Format => 'string' }, + # '060e2b34.0101.0101.04010302.00000000' => { Name => 'VerticalParameters', Type => 'Node' }, + '060e2b34.0101.0101.04010302.01000000' => { Name => 'TotalLinesperFrame', Format => 'int16u' }, + '060e2b34.0101.0101.04010302.02000000' => { Name => 'ActiveLinesperFrame', Format => 'int16u' }, + '060e2b34.0101.0101.04010302.03000000' => { Name => 'LeadingLines', Format => 'int32s' }, + '060e2b34.0101.0101.04010302.04000000' => { Name => 'TrailingLines', Format => 'int32s' }, + # '060e2b34.0101.0101.04010303.00000000' => { Name => 'HorizontalParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04010400.00000000' => { Name => 'AnalogVideoCodingCharacteristics', Type => 'Node' }, + '060e2b34.0101.0101.04010401.00000000' => { Name => 'AnalogVideoSystemName', Format => 'string' }, + # '060e2b34.0101.0101.04010500.00000000' => { Name => 'DigitalVideoAndImageCodingParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04010501.00000000' => { Name => 'DigitalVideoAndImageSamplingParameters', Type => 'Node' }, + '060e2b34.0101.0101.04010501.01000000' => { Name => 'LuminanceSampleRate', Format => 'int8u' }, + '060e2b34.0101.0101.04010501.02000000' => { Name => 'ActiveSamplesperLine', Format => 'int16u' }, + '060e2b34.0101.0101.04010501.03000000' => { Name => 'TotalSamplesperLine', Format => 'int16u' }, + '060e2b34.0101.0101.04010501.04000000' => { Name => 'SamplingHierarchyCode', Format => 'string' }, + '060e2b34.0101.0101.04010501.05000000' => { Name => 'HorizontalSubsampling', Format => 'int32u' }, + '060e2b34.0101.0101.04010501.06000000' => { Name => 'ColorSiting', Format => 'int8u' }, + '060e2b34.0101.0101.04010501.07000000' => { Name => 'SampledHeight', Format => 'int32u' }, + '060e2b34.0101.0101.04010501.08000000' => { Name => 'SampledWidth', Format => 'int32u' }, + '060e2b34.0101.0101.04010501.09000000' => { Name => 'SampledXOffset', Format => 'int32s' }, + '060e2b34.0101.0101.04010501.0a000000' => { Name => 'SampledYOffset', Format => 'int32s' }, + '060e2b34.0101.0101.04010501.0b000000' => { Name => 'DisplayHeight', Format => 'int32u' }, + '060e2b34.0101.0101.04010501.0c000000' => { Name => 'DisplayWidth', Format => 'int32u' }, + '060e2b34.0101.0101.04010501.0d000000' => { Name => 'DisplayXOffset', Format => 'int32s' }, + '060e2b34.0101.0101.04010501.0e000000' => { Name => 'DisplayYOffset', Format => 'int32s' }, + '060e2b34.0101.0101.04010501.0f000000' => { Name => 'FilteringCode', Format => 'string' }, + # '060e2b34.0101.0101.04010502.00000000' => { Name => 'DigitalVideoAndImageStorageParameters', Type => 'Node' }, + '060e2b34.0101.0101.04010502.01000000' => { Name => 'ImageHeight', Format => 'int32u' }, # (renamed from StoredHeight) + '060e2b34.0101.0101.04010502.02000000' => { Name => 'ImageWidth', Format => 'int32u' }, # (renamed from StoredWidth) + # '060e2b34.0101.0101.04010503.00000000' => { Name => 'DigitalQuantizationAndLevelParameters', Type => 'Node' }, + '060e2b34.0101.0101.04010503.01000000' => { Name => 'BitsPerPixel', Format => 'int8u' }, + '060e2b34.0101.0101.04010503.02000000' => { Name => 'RoundingMethodCode', Format => 'string' }, + '060e2b34.0101.0101.04010503.03000000' => { Name => 'BlackReferenceLevel', Format => 'int32u' }, + '060e2b34.0101.0101.04010503.04000000' => { Name => 'WhiteReferenceLevel', Format => 'int32u' }, + # '060e2b34.0101.0101.04010600.00000000' => { Name => 'DigitalVideoAndImageCompressionParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04010602.00000000' => { Name => 'MPEGCodingParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04010602.01000000' => { Name => 'MPEG-2CodingParameters', Type => 'Node' }, + '060e2b34.0101.0101.04010602.01010000' => { Name => 'FieldFrameTypeCode', Format => 'string' }, + # '060e2b34.0101.0101.04010800.00000000' => { Name => 'Film-to-VideoCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04010801.00000000' => { Name => 'FilmPulldownCharacteristics', Type => 'Node' }, + '060e2b34.0101.0101.04010801.01000000' => { Name => 'PulldownSequence', Type => 'PulldownKind', Unknown => 1 }, + '060e2b34.0101.0101.04010801.02000000' => { Name => 'PulldownFieldDominance', Type => 'Boolean' }, + '060e2b34.0101.0101.04010801.03000000' => { Name => 'VideoAndFilmFrameRelationship', Format => 'int8u' }, + # '060e2b34.0101.0101.04010802.00000000' => { Name => 'FilmFrameRates', Type => 'Node' }, + '060e2b34.0101.0101.04010802.01000000' => { Name => 'CaptureFilmFrameRate', Format => 'string' }, + '060e2b34.0101.0101.04010802.02000000' => { Name => 'TransferFilmFrameRate', Format => 'string' }, + # '060e2b34.0101.0101.04011000.00000000' => { Name => 'ImageTestParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04011001.00000000' => { Name => 'VideoTestParameters', Type => 'Node' }, + '060e2b34.0101.0101.04011001.01000000' => { Name => 'VideoTestParameter', Format => 'string' }, + '060e2b34.0101.0101.04011001.02000000' => { Name => 'VideoTestResult', Format => 'float' }, + '060e2b34.0101.0101.04011001.03000000' => { Name => 'VideoTestResult', Format => 'int32u' }, + # '060e2b34.0101.0101.04011002.00000000' => { Name => 'FilmTestParameters', Type => 'Node' }, + '060e2b34.0101.0101.04011002.01000000' => { Name => 'FilmTestParameter', Format => 'string' }, + '060e2b34.0101.0101.04011002.02000000' => { Name => 'FilmTestResult', Format => 'float' }, + '060e2b34.0101.0101.04011002.03000000' => { Name => 'FilmTestResult', Type => 'SIMSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.04020000.00000000' => { Name => 'AudioEssenceCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04020100.00000000' => { Name => 'FundamentalAudioCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04020101.00000000' => { Name => 'AudioFormulation', Type => 'Node' }, + '060e2b34.0101.0101.04020101.01000000' => { Name => 'ElectrospatialFormulation', Format => 'int8u', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.04020101.02000000' => { Name => 'FilteringApplied', Format => 'string', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.04020101.03000000' => { Name => 'AudioReferenceLevel', Format => 'int8s', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.04020101.10000000' => { Name => 'AudioMix', Type => 'Node' }, + '060e2b34.0101.0101.04020101.10010000' => { Name => 'AudioMonoChannelCount', Format => 'int8u', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.04020101.10020000' => { Name => 'AudioStereoChannelCount', Format => 'int8u', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.04020200.00000000' => { Name => 'AnalogAudioCodingParameters', Type => 'Node' }, + '060e2b34.0101.0101.04020201.00000000' => { Name => 'AnalogSystem', Format => 'string', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.04020300.00000000' => { Name => 'DigitalAudioCodingParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04020301.00000000' => { Name => 'DigitalSamplingParameters', Type => 'Node' }, + '060e2b34.0101.0101.04020301.01000000' => { Name => 'AudioSampleRate', Format => 'int8u', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.04020302.00000000' => { Name => 'DigitalAudioStorageParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04020303.00000000' => { Name => 'DigitalAudioQuantizationAndLevelParameters', Type => 'Node' }, + '060e2b34.0101.0101.04020303.01000000' => { Name => 'BitsPerSample', Format => 'int8u', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.04020303.02000000' => { Name => 'RoundingLaw', Format => 'string', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.04020303.03000000' => { Name => 'Dither', Format => 'string', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.04020400.00000000' => { Name => 'DigitalAudioCompressionParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04020403.00000000' => { Name => 'MPEGAudioCodingParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04020403.01000000' => { Name => 'MPEG-1AudioCodingParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04020800.00000000' => { Name => 'FilmSoundSource', Type => 'Node' }, + '060e2b34.0101.0101.04020801.00000000' => { Name => 'OpticalTrack', Format => 'string' }, + '060e2b34.0101.0101.04020802.00000000' => { Name => 'MagneticTrack', Format => 'string' }, + # '060e2b34.0101.0101.04021000.00000000' => { Name => 'AudioTestParameters', Type => 'Node' }, + '060e2b34.0101.0101.04021001.00000000' => { Name => 'Signal-to-NoiseRatio', Format => 'float', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.04021002.00000000' => { Name => 'Weighting', Format => 'string', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.04030000.00000000' => { Name => 'DataEssenceCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04030100.00000000' => { Name => 'FundamentalDataEssenceParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04030200.00000000' => { Name => 'AnalogDataEssenceCodingParameters', Type => 'Node' }, + '060e2b34.0101.0101.04030201.00000000' => { Name => 'AnalogDataCodingKind', Format => 'string' }, + # '060e2b34.0101.0101.04030300.00000000' => { Name => 'DigitalDataEssenceCodingParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04031000.00000000' => { Name => 'DataEssenceTestParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04040000.00000000' => { Name => 'MetadataCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04040100.00000000' => { Name => 'FundamentalMetadataCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04040101.00000000' => { Name => 'TimecodeCharacteristics', Type => 'Node' }, + '060e2b34.0101.0101.04040101.01000000' => { Name => 'TimecodeKind', Format => 'string' }, + '060e2b34.0101.0101.04040101.02000000' => { Name => 'TimecodeTimebase', Format => 'int8u' }, + '060e2b34.0101.0101.04040101.03000000' => { Name => 'TimecodeUserBitsFlag', Type => 'Boolean' }, + '060e2b34.0101.0101.04040101.04000000' => { Name => 'IncludeSync', Type => 'Boolean' }, + '060e2b34.0101.0101.04040101.05000000' => { Name => 'DropFrame', Type => 'Boolean' }, + # '060e2b34.0101.0101.04040200.00000000' => { Name => 'AnalogMetadataCodingCharacteristics', Type => 'Node' }, + '060e2b34.0101.0101.04040201.00000000' => { Name => 'TimecodeSourceKind', Format => 'int8u' }, + '060e2b34.0101.0101.04040202.00000000' => { Name => 'AnalogMetadataCarrier', Format => 'string' }, + # '060e2b34.0101.0101.04040300.00000000' => { Name => 'DigitalMetadataCodingCharacteristics', Type => 'Node' }, + '060e2b34.0101.0101.04040301.00000000' => { Name => 'DigitalMetadataCarrier', Format => 'string' }, + # '060e2b34.0101.0101.04041000.00000000' => { Name => 'MetadataTestParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04050000.00000000' => { Name => 'MonitoringAndControlCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04050100.00000000' => { Name => 'FundamentalMonitoringAndControlCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04050200.00000000' => { Name => 'AnalogMonitoringAndControlCodingCharacteristics', Type => 'Node' }, + '060e2b34.0101.0101.04050201.00000000' => { Name => 'AnalogMonitoringAndControlCodingKind', Format => 'string' }, + # '060e2b34.0101.0101.04050300.00000000' => { Name => 'DigitalMonitoringAndControlCodingParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04050301.00000000' => { Name => 'DigitalMonitoringAndControlSamplingParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04051000.00000000' => { Name => 'MonitoringAndControlTestParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04060000.00000000' => { Name => 'GeneralCodingCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04060100.00000000' => { Name => 'GeneralEssenceCodingCharacteristics', Type => 'Node' }, + '060e2b34.0101.0101.04060101.00000000' => { Name => 'SampleRate', Format => 'rational64s' }, + '060e2b34.0101.0101.04060102.00000000' => { Name => 'EssenceLength', Type => 'Length', %duration }, + # '060e2b34.0101.0101.04080000.00000000' => { Name => 'ObjectCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04100000.00000000' => { Name => 'MediumCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04100100.00000000' => { Name => 'StorageMediumParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04100101.00000000' => { Name => 'TapeMediumParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04100102.00000000' => { Name => 'DiscMediumParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04100103.00000000' => { Name => 'FilmMediumParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04100103.01000000' => { Name => 'GenericFilmMediumParameters', Type => 'Node' }, + '060e2b34.0101.0101.04100103.01010000' => { Name => 'FilmColorProcess', Format => 'string' }, + '060e2b34.0101.0101.04100103.01020000' => { Name => 'EdgeCodeFormat', Type => 'EdgeType', Unknown => 1 }, + '060e2b34.0101.0101.04100103.01040000' => { Name => 'FilmFormatName', Format => 'string' }, + '060e2b34.0101.0101.04100103.01050000' => { Name => 'FilmStockKind', Format => 'string' }, + '060e2b34.0101.0101.04100103.01060000' => { Name => 'FilmStockManufacturerName', Format => 'string' }, + # '060e2b34.0101.0101.04100103.02000000' => { Name => 'SpecificFilmMediumParameters', Type => 'Node' }, + '060e2b34.0101.0101.04100103.02010000' => { Name => 'PhysicalMediaLength', Type => 'UIMSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.04100103.02020000' => { Name => 'FilmCaptureAperture', Format => 'string' }, + # '060e2b34.0101.0101.04200000.00000000' => { Name => 'DeviceCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04200100.00000000' => { Name => 'CameraCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04200101.00000000' => { Name => 'ImageCharacteristics', Type => 'Node' }, + '060e2b34.0101.0101.04200101.01000000' => { Name => 'ImageCategory', Format => 'string' }, + # '060e2b34.0101.0101.04200102.01000000' => { Name => 'ImageDevices', Type => 'Node' }, + '060e2b34.0101.0101.04200102.01010000' => { Name => 'ImageSourceDeviceKind', Format => 'string' }, + # '060e2b34.0101.0101.04200200.00000000' => { Name => 'OpticalCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.04200201.00000000' => { Name => 'OpticalTestParameters', Type => 'Node' }, + # '060e2b34.0101.0101.04200201.01000000' => { Name => 'OpticalDeviceParameters', Type => 'Node' }, + '060e2b34.0101.0101.04200201.01010000' => { Name => 'OpticalTestParameterName', Format => 'string' }, + '060e2b34.0101.0101.04200201.01020000' => { Name => 'OpticalTestResult', Format => 'float' }, + '060e2b34.0101.0101.04200201.01030000' => { Name => 'OpticalTestResult', Format => 'int32s' }, + # '060e2b34.0101.0101.04200300.00000000' => { Name => 'MicrophoneCharacteristics', Type => 'Node' }, + '060e2b34.0101.0101.04200301.00000000' => { Name => 'SensorType', Format => 'string' }, + '060e2b34.0101.0101.04200302.00000000' => { Name => 'PolarCharacteristic', Format => 'string' }, + # '060e2b34.0101.0101.05000000.00000000' => { Name => 'PROCESS', Type => 'Node' }, + # '060e2b34.0101.0101.05010000.00000000' => { Name => 'GeneralProcessIndicators', Type => 'Node' }, + # '060e2b34.0101.0101.05010100.00000000' => { Name => 'ProcessFundamentals', Type => 'Node' }, + '060e2b34.0101.0101.05010101.00000000' => { Name => 'IntegrationIndication', Format => 'string' }, + '060e2b34.0101.0101.05010102.00000000' => { Name => 'EventIndication', Format => 'string' }, + '060e2b34.0101.0101.05010103.00000000' => { Name => 'QualityFlag', Type => 'Boolean' }, + '060e2b34.0101.0101.05010105.00000000' => { Name => 'PhysicalInstanceKind', Format => 'string' }, + # '060e2b34.0101.0101.05010200.00000000' => { Name => 'ContentCapture', Type => 'Node' }, + '060e2b34.0101.0101.05010201.00000000' => { Name => 'DigitalOrAnalogOrigination', Format => 'string' }, + # '060e2b34.0101.0101.05010202.00000000' => { Name => 'VideoOrImageCaptureProcess', Type => 'Node' }, + # '060e2b34.0101.0101.05010203.00000000' => { Name => 'FilmCaptureProcess', Type => 'Node' }, + # '060e2b34.0101.0101.05010204.00000000' => { Name => 'AudioCaptureProcess', Type => 'Node' }, + '060e2b34.0101.0101.05010204.01000000' => { Name => 'MicrophonePlacementTechniques', Format => 'string', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.05010205.00000000' => { Name => 'DataCaptureProcess', Type => 'Node' }, + # '060e2b34.0101.0101.05010300.00000000' => { Name => 'Manipulation', Type => 'Node' }, + '060e2b34.0101.0101.05010301.00000000' => { Name => 'SimpleFlaggingCount', Format => 'int16u' }, + '060e2b34.0101.0101.05010302.00000000' => { Name => 'CopyCount', Format => 'int8u' }, + '060e2b34.0101.0101.05010303.00000000' => { Name => 'CloneCount', Format => 'int8u' }, + '060e2b34.0101.0101.05010304.00000000' => { Name => 'Work-in-ProgressFlag', Type => 'Boolean' }, + # '060e2b34.0101.0101.05020000.00000000' => { Name => 'CompressionProcessing', Type => 'Node' }, + # '060e2b34.0101.0101.05020100.00000000' => { Name => 'VideoOrImageCompression', Type => 'Node' }, + '060e2b34.0101.0101.05020101.00000000' => { Name => 'VideoOrImageCompressionAlgorithm', Format => 'string' }, + # '060e2b34.0101.0101.05020102.00000000' => { Name => 'MPEGProcessing', Type => 'Node' }, + # '060e2b34.0101.0101.05020102.01000000' => { Name => 'MPEG-2Processing', Type => 'Node' }, + '060e2b34.0101.0101.05020102.01010000' => { Name => 'SplicingMetadata', Unknown => 1 }, + # '060e2b34.0101.0101.05020200.00000000' => { Name => 'AudioCompression', Type => 'Node' }, + '060e2b34.0101.0101.05020201.00000000' => { Name => 'AudioCompressionAlgorithm', Format => 'string', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.05020300.00000000' => { Name => 'DataEssenceCompression', Type => 'Node' }, + # '060e2b34.0101.0101.05020400.00000000' => { Name => 'MetadataCompression', Type => 'Node' }, + # '060e2b34.0101.0101.05030000.00000000' => { Name => 'NoiseReductionProcessing', Type => 'Node' }, + # '060e2b34.0101.0101.05030100.00000000' => { Name => 'VideoNoiseReduction', Type => 'Node' }, + '060e2b34.0101.0101.05030101.00000000' => { Name => 'VideoNoiseReductionAlgorithm', Format => 'string' }, + # '060e2b34.0101.0101.05030200.00000000' => { Name => 'AudioNoiseReduction', Type => 'Node' }, + '060e2b34.0101.0101.05030201.00000000' => { Name => 'AudioNoiseReductionAlgorithm', Format => 'string', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.05200000.00000000' => { Name => 'EnhancementOrModification', Type => 'Node' }, + # '060e2b34.0101.0101.05200100.00000000' => { Name => 'ImageEssenceProcessing', Type => 'Node' }, + '060e2b34.0101.0101.05200101.00000000' => { Name => 'EnhancementOrModificationDescription', Format => 'string' }, + # '060e2b34.0101.0101.05200200.00000000' => { Name => 'VideoProcessOrSettings', Type => 'Node' }, + '060e2b34.0101.0101.05200201.00000000' => { Name => 'VideoDeviceKind', Format => 'string' }, + '060e2b34.0101.0101.05200202.00000000' => { Name => 'VideoDeviceParameterName', Format => 'string' }, + '060e2b34.0101.0101.05200203.00000000' => { Name => 'VideoDeviceParameterSetting', Format => 'string' }, + # '060e2b34.0101.0101.05200300.00000000' => { Name => 'AudioEssenceProcessing', Type => 'Node' }, + '060e2b34.0101.0101.05200301.00000000' => { Name => 'AudioEnhancementOrModificationDescription', Format => 'string', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.05200302.00000000' => { Name => 'AudioFirstMix-DownProcess', Format => 'string', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.05200400.00000000' => { Name => 'AudioProcessorSettings', Type => 'Node' }, + '060e2b34.0101.0101.05200401.00000000' => { Name => 'AudioDeviceKind', Format => 'string', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.05200402.00000000' => { Name => 'AudioDeviceParameter', Format => 'string', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.05200403.00000000' => { Name => 'AudioDeviceParameterSetting', Format => 'string', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.05200500.00000000' => { Name => 'DataEssenceProcessing', Type => 'Node' }, + '060e2b34.0101.0101.05200501.00000000' => { Name => 'DataEnhancementOrModificationDescription', Format => 'string' }, + # '060e2b34.0101.0101.05200600.00000000' => { Name => 'DataProcessorSettings', Type => 'Node' }, + '060e2b34.0101.0101.05200601.00000000' => { Name => 'DataDeviceKind', Format => 'string' }, + '060e2b34.0101.0101.05200602.00000000' => { Name => 'DataDeviceParameterName', Format => 'string' }, + '060e2b34.0101.0101.05200603.00000000' => { Name => 'DataDeviceParameterSetting', Format => 'string' }, + # '060e2b34.0101.0101.05200700.00000000' => { Name => 'MetadataProcessing', Type => 'Node' }, + # '060e2b34.0101.0101.05200800.00000000' => { Name => 'MetadataProcessorSettings', Type => 'Node' }, + # '060e2b34.0101.0101.05300000.00000000' => { Name => 'EditingInformation', Type => 'Node' }, + # '060e2b34.0101.0101.05300100.00000000' => { Name => 'EditingVersionInformation', Type => 'Node' }, + # '060e2b34.0101.0101.05300200.00000000' => { Name => 'EditingDecisionInformation', Type => 'Node' }, + '060e2b34.0101.0101.05300201.00000000' => { Name => 'DefaultFadeType', Type => 'FadeType', Unknown => 1 }, + # '060e2b34.0101.0101.05300300.00000000' => { Name => 'EditingMatteInformation', Type => 'Node' }, + # '060e2b34.0101.0101.05300400.00000000' => { Name => 'EditingEventInformation', Type => 'Node' }, + '060e2b34.0101.0101.05300401.00000000' => { Name => 'ActiveState', Type => 'Boolean' }, + # '060e2b34.0101.0101.05300500.00000000' => { Name => 'EditingEffectInformation', Type => 'Node' }, + '060e2b34.0101.0101.05300501.00000000' => { Name => 'Fade-InType', Type => 'FadeType', Unknown => 1 }, + '060e2b34.0101.0101.05300502.00000000' => { Name => 'Fade-OutType', Type => 'FadeType', Unknown => 1 }, + '060e2b34.0101.0101.05300503.00000000' => { Name => 'SpeedChangeEffectFlag', Type => 'Boolean' }, + '060e2b34.0101.0101.05300504.00000000' => { Name => 'InputSegmentCount', Format => 'int32s' }, + '060e2b34.0101.0101.05300505.00000000' => { Name => 'Bypass', Format => 'int32u' }, + # '060e2b34.0101.0101.05300600.00000000' => { Name => 'EditingWebInformation', Type => 'Node' }, + # '060e2b34.0101.0101.05300700.00000000' => { Name => 'EditingUserNotes', Type => 'Node' }, + # '060e2b34.0101.0101.05400000.00000000' => { Name => 'ProcessingHistory', Type => 'Node' }, + # '060e2b34.0101.0101.05400100.00000000' => { Name => 'VideoCompressionHistory', Type => 'Node' }, + '060e2b34.0101.0101.05400101.00000000' => { Name => 'VideoCompressionAlgorithm', Format => 'string' }, + '060e2b34.0101.0101.05400102.00000000' => { Name => 'MPEGVideoRecodingDataset', Unknown => 1 }, + # '060e2b34.0101.0101.05400200.00000000' => { Name => 'AudioCompressionHistory', Type => 'Node' }, + '060e2b34.0101.0101.05400201.00000000' => { Name => 'UpstreamAudioCompressionAlgorithm', Format => 'string', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.05400202.00000000' => { Name => 'MPEGAudioRecodingDataset', Unknown => 1, Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.05400300.00000000' => { Name => 'DataCompressionHistory', Type => 'Node' }, + # '060e2b34.0101.0101.05400400.00000000' => { Name => 'MetadataCompressionHistory', Type => 'Node' }, + # '060e2b34.0101.0101.06000000.00000000' => { Name => 'RELATIONAL', Type => 'Node' }, + # '060e2b34.0101.0101.06010000.00000000' => { Name => 'GenericRelationships', Type => 'Node' }, + # '060e2b34.0101.0101.06010100.00000000' => { Name => 'EssenceAndMetadataRelationships', Type => 'Node' }, + # '060e2b34.0101.0101.06010101.00000000' => { Name => 'EssenceToEssenceRelationships', Type => 'Node' }, + # '060e2b34.0101.0101.06010102.00000000' => { Name => 'MetadataToEssenceRelationships', Type => 'Node' }, + # '060e2b34.0101.0101.06010103.00000000' => { Name => 'MetadataToMetadataRelationships', Type => 'Node' }, + # '060e2b34.0101.0101.06010104.00000000' => { Name => 'ObjectToObjectRelationships', Type => 'Node' }, + # '060e2b34.0101.0101.06010105.00000000' => { Name => 'MetadataToObjectRelationships', Type => 'Node' }, + # '060e2b34.0101.0101.06020000.00000000' => { Name => 'RelatedProductionMaterial', Type => 'Node' }, + '060e2b34.0101.0101.06020100.00000000' => { Name => 'ProgramSupportMaterialReference', Format => 'string' }, + '060e2b34.0101.0101.06020200.00000000' => { Name => 'AdvertisingMaterialReference', Format => 'string' }, + '060e2b34.0101.0101.06020300.00000000' => { Name => 'ProgramCommercialMaterialReference', Format => 'string' }, + # '060e2b34.0101.0101.06080000.00000000' => { Name => 'StreamAndStorageRelationships', Type => 'Node' }, + # '060e2b34.0101.0101.06080100.00000000' => { Name => 'StreamRelationships', Type => 'Node' }, + # '060e2b34.0101.0101.06080200.00000000' => { Name => 'StorageRelationships', Type => 'Node' }, + '060e2b34.0101.0101.06080201.00000000' => { Name => 'ContiguousDataFlag', Type => 'Boolean' }, + # '060e2b34.0101.0101.06100000.00000000' => { Name => 'NumericalSequence', Type => 'Node' }, + '060e2b34.0101.0101.06100100.00000000' => { Name => 'PositionInSequence', Format => 'int32u' }, + '060e2b34.0101.0101.06100200.00000000' => { Name => 'RelativePositionInSequenceOffset', Format => 'int32s' }, + '060e2b34.0101.0101.06100300.00000000' => { Name => 'RelativePositionInSequenceName', Format => 'string' }, + # '060e2b34.0101.0101.07000000.00000000' => { Name => 'SPATIO-TEMPORAL', Type => 'Node' }, + # '060e2b34.0101.0101.07010000.00000000' => { Name => 'PositionAndSpaceVectors', Type => 'Node' }, + # '060e2b34.0101.0101.07010100.00000000' => { Name => 'PositionalSystemInformation', Type => 'Node' }, + '060e2b34.0101.0101.07010101.00000000' => { Name => 'ImageCoordinateSystem', Format => 'string' }, + '060e2b34.0101.0101.07010102.00000000' => { Name => 'MapDatumUsed', Format => 'string' }, + # '060e2b34.0101.0101.07010200.00000000' => { Name => 'PositionalInformation', Type => 'Node' }, + # '060e2b34.0101.0101.07010201.00000000' => { Name => 'AbsolutePosition', Type => 'Node' }, + # '060e2b34.0101.0101.07010201.01000000' => { Name => 'LocalDatumAbsolutePosition', Type => 'Node' }, + '060e2b34.0101.0101.07010201.01010000' => { Name => 'LocalDatumAbsolutePositionAccuracy', Format => 'float' }, + # '060e2b34.0101.0101.07010201.02000000' => { Name => 'DeviceAbsolutePosition', Type => 'Node' }, + '060e2b34.0101.0101.07010201.02010000' => { Name => 'DeviceAbsolutePositionalAccuracy', Format => 'float' }, + '060e2b34.0101.0101.07010201.02020000' => { Name => 'DeviceAltitude', Format => 'float', Groups => { 2 => 'Location' }, PrintConv => '$val m' }, + '060e2b34.0101.0101.07010201.02020100' => { Name => 'DeviceAltitude', Type => 'Alt', Groups => { 2 => 'Location' }, PrintConv => '$val m' }, + '060e2b34.0101.0101.07010201.02040000' => { Name => 'DeviceLatitude', Format => 'float', %geoLat }, + '060e2b34.0101.0101.07010201.02040100' => { Name => 'DeviceLatitude', Type => 'Lat', %geoLat }, + '060e2b34.0101.0101.07010201.02060000' => { Name => 'DeviceLongitude', Format => 'float', %geoLon }, + '060e2b34.0101.0101.07010201.02060100' => { Name => 'DeviceLongitude', Type => 'Lon', %geoLon }, + '060e2b34.0101.0101.07010201.02100000' => { Name => 'DeviceXDimension', Format => 'float' }, + '060e2b34.0101.0101.07010201.02110000' => { Name => 'DeviceYDimension', Format => 'float' }, + # '060e2b34.0101.0101.07010201.03000000' => { Name => 'SubjectAbsolutePosition', Type => 'Node' }, + '060e2b34.0101.0101.07010201.03010000' => { Name => 'FramePositionalAccuracy', Format => 'float' }, + '060e2b34.0101.0101.07010201.03020000' => { Name => 'FrameCenterLatitude', Format => 'double', %geoLat }, + '060e2b34.0101.0101.07010201.03030000' => { Name => 'FrameCenterLatitude', Format => 'string', %geoLat, ValueConv => \&ConvLatLon }, + '060e2b34.0101.0101.07010201.03040000' => { Name => 'FrameCenterLongitude', Format => 'double', %geoLon }, + '060e2b34.0101.0101.07010201.03050000' => { Name => 'FrameCenterLongitude', Format => 'string', %geoLon, ValueConv => \&ConvLatLon }, + '060e2b34.0101.0101.07010201.03060000' => { Name => 'FrameCenterLatitudeLongitude', Format => 'string', %geoLatLon, ValueConv => \&ConvLatLon }, + # '060e2b34.0101.0101.07010202.00000000' => { Name => 'RelativePosition', Type => 'Node' }, + # '060e2b34.0101.0101.07010202.01000000' => { Name => 'LocalDatumRelativePosition', Type => 'Node' }, + '060e2b34.0101.0101.07010202.01010000' => { Name => 'LocalDatumRelativePositionAccuracy', Format => 'float' }, + # '060e2b34.0101.0101.07010202.02000000' => { Name => 'DeviceRelativePosition', Type => 'Node' }, + '060e2b34.0101.0101.07010202.02010000' => { Name => 'DeviceRelativePositionalAccuracy', Format => 'float' }, + '060e2b34.0101.0101.07010202.02020000' => { Name => 'DeviceRelativePositionX', Format => 'float' }, + '060e2b34.0101.0101.07010202.02030000' => { Name => 'DeviceRelativePositionY', Format => 'float' }, + '060e2b34.0101.0101.07010202.02040000' => { Name => 'DeviceRelativePositionZ', Format => 'float' }, + # '060e2b34.0101.0101.07010202.03000000' => { Name => 'SubjectRelativePosition', Type => 'Node' }, + '060e2b34.0101.0101.07010202.03010000' => { Name => 'SubjectRelativePositionalAccuracy', Format => 'float' }, + # '060e2b34.0101.0101.07010203.00000000' => { Name => 'ImagePositionalInformation', Type => 'Node' }, + '060e2b34.0101.0101.07010203.01000000' => { Name => 'PositionWithinViewportImageXCoordinate', Format => 'int16s' }, + '060e2b34.0101.0101.07010203.02000000' => { Name => 'PositionWithinViewportImageYCoordinate', Format => 'int16s' }, + '060e2b34.0101.0101.07010203.03000000' => { Name => 'SourceImageCenterXCoordinate', Format => 'int16s' }, + '060e2b34.0101.0101.07010203.04000000' => { Name => 'SourceImageCenterYCoordinate', Format => 'int16s' }, + '060e2b34.0101.0101.07010203.05000000' => { Name => 'ViewportImageCenterCCoordinate', Format => 'int16s' }, + '060e2b34.0101.0101.07010203.06000000' => { Name => 'ViewportImageCenterYCoordinate', Format => 'int16s' }, + # '060e2b34.0101.0101.07010300.00000000' => { Name => 'RateAndDirectionOfPositionalChange', Type => 'Node' }, + # '060e2b34.0101.0101.07010301.00000000' => { Name => 'AbsoluteRateAndDirectionOfPositionalChange', Type => 'Node' }, + # '060e2b34.0101.0101.07010301.01000000' => { Name => 'DeviceRateAndDirectionOfPositionalChange', Type => 'Node' }, + '060e2b34.0101.0101.07010301.01010000' => { Name => 'DeviceAbsoluteSpeed', Format => 'float' }, + '060e2b34.0101.0101.07010301.01020000' => { Name => 'DeviceAbsoluteHeading', Format => 'float' }, + # '060e2b34.0101.0101.07010301.02000000' => { Name => 'SubjectRateAndDirectionOfPositionalChange', Type => 'Node' }, + '060e2b34.0101.0101.07010301.02010000' => { Name => 'SubjectAbsoluteSpeed', Format => 'float' }, + '060e2b34.0101.0101.07010301.02020000' => { Name => 'SubjectAbsoluteHeading', Format => 'float' }, + # '060e2b34.0101.0101.07010302.00000000' => { Name => 'RelativeRateAndDirectionOfPositionalChange', Type => 'Node' }, + # '060e2b34.0101.0101.07010302.01000000' => { Name => 'DeviceRelativeRateAndDirectionOfPositionalChange', Type => 'Node' }, + '060e2b34.0101.0101.07010302.01010000' => { Name => 'DeviceRelativeSpeed', Format => 'float' }, + '060e2b34.0101.0101.07010302.01020000' => { Name => 'DeviceRelativeHeading', Format => 'float' }, + # '060e2b34.0101.0101.07010302.02000000' => { Name => 'SubjectRelativeRateAndDirectionOfPositionalChange', Type => 'Node' }, + '060e2b34.0101.0101.07010302.02010000' => { Name => 'SubjectRelativeSpeed', Format => 'float' }, + '060e2b34.0101.0101.07010302.02020000' => { Name => 'SubjectRelativeHeading', Format => 'float' }, + # '060e2b34.0101.0101.07010800.00000000' => { Name => 'DistanceMeasurements', Type => 'Node' }, + # '060e2b34.0101.0101.07010801.00000000' => { Name => 'DeviceToSubjectDistance', Type => 'Node' }, + '060e2b34.0101.0101.07010801.01000000' => { Name => 'SlantRange', Format => 'float' }, + # '060e2b34.0101.0101.07010900.00000000' => { Name => 'Dimensions', Type => 'Node' }, + # '060e2b34.0101.0101.07010901.00000000' => { Name => 'DeviceDimensions', Type => 'Node' }, + # '060e2b34.0101.0101.07010902.00000000' => { Name => 'SubjectDimensions', Type => 'Node' }, + '060e2b34.0101.0101.07010902.01000000' => { Name => 'TargetWidth', Format => 'float' }, + # '060e2b34.0101.0101.07010903.00000000' => { Name => 'LocationDimensions', Type => 'Node' }, + # '060e2b34.0101.0101.07010904.00000000' => { Name => 'MediaDimensions', Type => 'Node' }, + # '060e2b34.0101.0101.07010904.01000000' => { Name => 'ImageDimensions', Type => 'Node' }, + # '060e2b34.0101.0101.07010904.01010000' => { Name => 'Pan-and-ScanImageDimensions', Type => 'Node' }, + '060e2b34.0101.0101.07010904.01010100' => { Name => 'ViewportHeight', Format => 'int16u' }, + '060e2b34.0101.0101.07010904.01010200' => { Name => 'ViewportWidth', Format => 'int16u' }, + # '060e2b34.0101.0101.07011000.00000000' => { Name => 'AngularSpecifications', Type => 'Node' }, + # '060e2b34.0101.0101.07011001.00000000' => { Name => 'DeviceAngles', Type => 'Node' }, + '060e2b34.0101.0101.07011001.01000000' => { Name => 'SensorRollAngle', Format => 'float' }, + '060e2b34.0101.0101.07011001.02000000' => { Name => 'AngleToNorth', Format => 'float' }, + '060e2b34.0101.0101.07011001.03000000' => { Name => 'ObliquityAngle', Format => 'float' }, + # '060e2b34.0101.0101.07011002.00000000' => { Name => 'SubjectAngles', Type => 'Node' }, + # '060e2b34.0101.0101.07012000.00000000' => { Name => 'AbstractLocations', Type => 'Node' }, + # '060e2b34.0101.0101.07012001.00000000' => { Name => 'PlaceNames', Type => 'Node' }, + # '060e2b34.0101.0101.07012001.01000000' => { Name => 'AbstractNames', Type => 'Node' }, + '060e2b34.0101.0101.07012001.01010000' => { Name => 'PlaceKeyword', Format => 'string' }, + # '060e2b34.0101.0101.07012001.02000000' => { Name => 'CountryCodes', Type => 'Node' }, + '060e2b34.0101.0101.07012001.02010000' => { Name => 'ObjectCountryCode', Format => 'string' }, + '060e2b34.0101.0101.07012001.02020000' => { Name => 'ShootingCountryCode', Format => 'string' }, + '060e2b34.0101.0101.07012001.02030000' => { Name => 'SettingCountryCode', Format => 'string' }, + '060e2b34.0101.0101.07012001.02040000' => { Name => 'CopyrightLicenseCountryCode', Format => 'string' }, + '060e2b34.0101.0101.07012001.02050000' => { Name => 'IntellectualPropertyLicenseCountryCode', Format => 'string' }, + # '060e2b34.0101.0101.07012001.03000000' => { Name => 'Regions', Type => 'Node' }, + '060e2b34.0101.0101.07012001.03010000' => { Name => 'ObjectRegionCode', Format => 'string' }, + '060e2b34.0101.0101.07012001.03020000' => { Name => 'ShootingRegionCode', Format => 'string' }, + '060e2b34.0101.0101.07012001.03030000' => { Name => 'SettingRegionCode', Format => 'string' }, + '060e2b34.0101.0101.07012001.03040000' => { Name => 'CopyrightLicenseRegionCode', Format => 'string' }, + '060e2b34.0101.0101.07012001.03050000' => { Name => 'IntellectualPropertyLicenseRegionCode', Format => 'string' }, + # '060e2b34.0101.0101.07012001.04000000' => { Name => 'Addresses', Type => 'Node' }, + # '060e2b34.0101.0101.07012001.04010000' => { Name => 'PostalAddresses', Type => 'Node' }, + '060e2b34.0101.0101.07012001.04010100' => { Name => 'RoomNumber', Format => 'string' }, + '060e2b34.0101.0101.07012001.04010200' => { Name => 'StreetNumber', Format => 'string' }, + '060e2b34.0101.0101.07012001.04010300' => { Name => 'StreetName', Format => 'string' }, + '060e2b34.0101.0101.07012001.04010400' => { Name => 'PostalTown', Format => 'string' }, + '060e2b34.0101.0101.07012001.04010500' => { Name => 'CityName', Format => 'string' }, + '060e2b34.0101.0101.07012001.04010600' => { Name => 'StateOrProvinceOrCountyName', Format => 'string' }, + '060e2b34.0101.0101.07012001.04010700' => { Name => 'PostalCode', Format => 'string' }, + '060e2b34.0101.0101.07012001.04010800' => { Name => 'CountryName', Format => 'string' }, + # '060e2b34.0101.0101.07012001.04020000' => { Name => 'SettingAddresses', Type => 'Node' }, + '060e2b34.0101.0101.07012001.04020100' => { Name => 'SettingRoomNumber', Format => 'string' }, + '060e2b34.0101.0101.07012001.04020200' => { Name => 'SettingStreetNumberOrBuildingName', Format => 'string' }, + '060e2b34.0101.0101.07012001.04020300' => { Name => 'SettingStreetName', Format => 'string' }, + '060e2b34.0101.0101.07012001.04020400' => { Name => 'SettingTownName', Format => 'string' }, + '060e2b34.0101.0101.07012001.04020500' => { Name => 'SettingCityName', Format => 'string' }, + '060e2b34.0101.0101.07012001.04020600' => { Name => 'SettingStateOrProvinceOrCountyName', Format => 'string' }, + '060e2b34.0101.0101.07012001.04020700' => { Name => 'SettingPostalCode', Format => 'string' }, + '060e2b34.0101.0101.07012001.04020800' => { Name => 'SettingCountryName', Format => 'string' }, + # '060e2b34.0101.0101.07012001.10030000' => { Name => 'ElectronicAddressInformation', Type => 'Node' }, + '060e2b34.0101.0101.07012001.10030100' => { Name => 'TelephoneNumber', Format => 'string' }, + '060e2b34.0101.0101.07012001.10030200' => { Name => 'FaxNumber', Format => 'string' }, + '060e2b34.0101.0101.07012001.10030300' => { Name => 'E-mailAddress', Format => 'string' }, + # '060e2b34.0101.0101.07012002.00000000' => { Name => 'PlaceDescriptions', Type => 'Node' }, + '060e2b34.0101.0101.07012002.01000000' => { Name => 'SettingDescription', Format => 'string' }, + # '060e2b34.0101.0101.07020000.00000000' => { Name => 'Temporal', Type => 'Node' }, + # '060e2b34.0101.0101.07020100.00000000' => { Name => 'DatesAndTimes', Type => 'Node' }, + # '060e2b34.0101.0101.07020101.00000000' => { Name => 'GeneralDatesAndTimes', Type => 'Node' }, + # '060e2b34.0101.0101.07020101.01000000' => { Name => 'UserDateTime', Type => 'Node' }, + '060e2b34.0101.0101.07020101.01010000' => { Name => 'UTCUserDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020101.01020000' => { Name => 'LocalUserDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020101.01030000' => { Name => 'SMPTE309MUserDateTime', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020101.01040000' => { Name => 'SMPTE12MUserDateTime', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020102.00000000' => { Name => 'AbsoluteDatesAndTimes', Type => 'Node' }, + # '060e2b34.0101.0101.07020102.01000000' => { Name => 'MaterialStartTrueDateTime', Type => 'Node' }, + '060e2b34.0101.0101.07020102.01010000' => { Name => 'UTCStartDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020102.01020000' => { Name => 'LocalStartDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020102.02000000' => { Name => 'MaterialStartTimeAddress', Type => 'Node' }, + '060e2b34.0101.0101.07020102.02010000' => { Name => 'TimecodeStartDateTime', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020102.03000000' => { Name => 'MaterialEndTrueDateTime', Type => 'Node' }, + '060e2b34.0101.0101.07020102.03010000' => { Name => 'UTCEndDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020102.03020000' => { Name => 'LocalEndDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020102.04000000' => { Name => 'MaterialEndTimeAddress', Type => 'Node' }, + '060e2b34.0101.0101.07020102.04010000' => { Name => 'TimecodeEndDateTime', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020102.05000000' => { Name => 'MaterialOccurrenceTrueDateTime', Type => 'Node' }, + '060e2b34.0101.0101.07020102.05010000' => { Name => 'UTCLastModifyDate', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020102.05020000' => { Name => 'LocalLastModifyDate', Format => 'string', Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020102.06000000' => { Name => 'MaterialOccurrenceTimeAddress', Type => 'Node' }, + '060e2b34.0101.0101.07020102.06010000' => { Name => 'TimecodeLastModifyDate', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020102.07000000' => { Name => 'EventStartTrueDateTime', Type => 'Node' }, + '060e2b34.0101.0101.07020102.07010000' => { Name => 'UTCEventStartDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020102.07020000' => { Name => 'LocalEventStartDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020102.08000000' => { Name => 'EventStartTimeAddress', Type => 'Node' }, + '060e2b34.0101.0101.07020102.08010000' => { Name => 'TimecodeEventStartDateTime', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020102.09000000' => { Name => 'EventEndTrueDateTime', Type => 'Node' }, + '060e2b34.0101.0101.07020102.09010000' => { Name => 'UTCEventEndDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020102.09020000' => { Name => 'LocalEventEndDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020102.0a000000' => { Name => 'EventEndTimeAddress', Type => 'Node' }, + '060e2b34.0101.0101.07020102.0a010000' => { Name => 'TimecodeEventEndDateTime', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020103.00000000' => { Name => 'RelativeTimes', Type => 'Node' }, + # '060e2b34.0101.0101.07020103.01000000' => { Name => 'MaterialStartRelativeTimes', Type => 'Node' }, + '060e2b34.0101.0101.07020103.01010000' => { Name => 'StartTimeRelativeToReference', Format => 'string' }, + '060e2b34.0101.0101.07020103.01020000' => { Name => 'StartTimecodeRelativeToReference', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020103.02000000' => { Name => 'MaterialEndRelativeTimes', Type => 'Node' }, + '060e2b34.0101.0101.07020103.02010000' => { Name => 'MaterialEndTimeOffset', Format => 'string' }, + '060e2b34.0101.0101.07020103.02020000' => { Name => 'MaterialEndTimecodeOffset', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020103.03000000' => { Name => 'EventStartRelativeTimes', Type => 'Node' }, + '060e2b34.0101.0101.07020103.03010000' => { Name => 'EventStartTimeOffset', Format => 'string' }, + '060e2b34.0101.0101.07020103.03020000' => { Name => 'EventStartTimecodeOffset', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020103.04000000' => { Name => 'EventEndRelativeTimes', Type => 'Node' }, + '060e2b34.0101.0101.07020103.04010000' => { Name => 'EventEndTimeOffset', Format => 'string' }, + '060e2b34.0101.0101.07020103.04020000' => { Name => 'EventEndTimecodeOffset', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020103.10000000' => { Name => 'Offsets', Type => 'Node' }, + # '060e2b34.0101.0101.07020103.10010000' => { Name => 'MaterialOffsets', Type => 'Node' }, + '060e2b34.0101.0101.07020103.10010100' => { Name => 'FrameCountOffset', Format => 'int32u' }, + # '060e2b34.0101.0101.07020108.00000000' => { Name => 'SettingDateAndTime', Type => 'Node' }, + '060e2b34.0101.0101.07020108.01000000' => { Name => 'TimePeriodName', Format => 'string' }, + # '060e2b34.0101.0101.07020110.00000000' => { Name => 'ProcessDateTime', Type => 'Node' }, + # '060e2b34.0101.0101.07020110.01000000' => { Name => 'CreateDate', Type => 'Node' }, + '060e2b34.0101.0101.07020110.01010000' => { Name => 'LocalCreationDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020110.01020000' => { Name => 'TimecodeCreationDateTime', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020110.02000000' => { Name => 'ModifyDate', Type => 'Node' }, + '060e2b34.0101.0101.07020110.02010000' => { Name => 'LocalModifyDate', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020110.02020000' => { Name => 'TimecodeModifyDate', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020200.00000000' => { Name => 'Durations', Type => 'Node' }, + # '060e2b34.0101.0101.07020201.00000000' => { Name => 'AbsoluteDurations', Type => 'Node' }, + # '060e2b34.0101.0101.07020201.01000000' => { Name => 'EditTimelineDurations', Type => 'Node' }, + '060e2b34.0101.0101.07020201.01010000' => { Name => 'FrameCount', Format => 'int32u' }, + # '060e2b34.0101.0101.07020201.01040000' => { Name => 'VideoDurations', Type => 'Node' }, + # '060e2b34.0101.0101.07020201.01050000' => { Name => 'AudioDurations', Type => 'Node' }, + # '060e2b34.0101.0101.07020201.02000000' => { Name => 'MaterialAbsoluteDurations', Type => 'Node' }, + '060e2b34.0101.0101.07020201.02010000' => { Name => 'MaterialAbsoluteDuration', Format => 'string' }, + '060e2b34.0101.0101.07020201.02020000' => { Name => 'MaterialAbsoluteDuration', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + '060e2b34.0101.0101.07020201.02030000' => { Name => 'TextlessBlackDuration', Format => 'int32u' }, + # '060e2b34.0101.0101.07020201.03000000' => { Name => 'EventAbsoluteDurations', Type => 'Node' }, + '060e2b34.0101.0101.07020201.03010000' => { Name => 'EventAbsoluteDurationFrameCount', Format => 'int32u' }, + '060e2b34.0101.0101.07020201.03020000' => { Name => 'EventAbsoluteDuration', Format => 'string' }, + '060e2b34.0101.0101.07020201.03030000' => { Name => 'EventAbsoluteDuration', Type => 'UILSBF', Unknown => 1, Groups => { 2 => 'Time' } }, + # '060e2b34.0101.0101.07020202.00000000' => { Name => 'RelativeScalingDurations', Type => 'Node' }, + # '060e2b34.0101.0101.07020300.00000000' => { Name => 'Delay', Type => 'Node' }, + # '060e2b34.0101.0101.07020301.00000000' => { Name => 'EncodingAndDecoding', Type => 'Node' }, + # '060e2b34.0101.0101.07020301.01000000' => { Name => 'CodecDelay', Type => 'Node' }, + # '060e2b34.0101.0101.07020301.02000000' => { Name => 'EncodingDelay', Type => 'Node' }, + # '060e2b34.0101.0101.07020301.03000000' => { Name => 'DecodingDelay', Type => 'Node' }, + '060e2b34.0101.0101.07020301.03010000' => { Name => 'BufferDelay', Unknown => 1 }, + # '060e2b34.0101.0101.07020500.00000000' => { Name => 'Latency', Type => 'Node' }, + # '060e2b34.0101.0101.07020600.00000000' => { Name => 'ShutterCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.07020601.00000000' => { Name => 'ShutterCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0101.07020601.01000000' => { Name => 'ShutterSpeed', Type => 'Node' }, + # '060e2b34.0101.0101.07020601.02000000' => { Name => 'ShutterGating', Type => 'Node' }, + # '060e2b34.0101.0101.0d000000.00000000' => { Name => 'UserOrganizationRegisteredForPublicUse', Type => 'Node' }, + # '060e2b34.0101.0101.0d010000.00000000' => { Name => 'AAFAssociation', Type => 'Node' }, + # '060e2b34.0101.0101.0d010100.00000000' => { Name => 'AAFAttributes', Type => 'Node' }, + # '060e2b34.0101.0101.0d010101.00000000' => { Name => 'AAFInformationAttributes', Type => 'Node' }, + # '060e2b34.0101.0101.0d010101.01000000' => { Name => 'AAFInformationAttributesVersion1', Type => 'Node' }, + # '060e2b34.0101.0101.0d010101.01010000' => { Name => 'EnumeratedAttributes', Type => 'Node' }, + + # tags from ref 4 (untested) + '060e2b34.0101.0101.0d010401.03010100' => { Name => 'ProgramIdentifier', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d010401.03010200' => { Name => 'ProgramIdentifierString', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d010401.03010300' => { Name => 'ShimName', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d010401.03010400' => { Name => 'SignalStandard', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d010401.03010500' => { Name => 'IntendedAFD', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d010401.03010600' => { Name => 'SlateTitle', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d010401.03010700' => { Name => 'NOLACode', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d010401.03010800' => { Name => 'Rating', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d010401.03010900' => { Name => 'NielsenStreamIdentifier', Type => 'UTF-16' }, + + # '060e2b34.0101.0101.0d0b0100.00000000' => { Name => 'ProductionFramework', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0101.00000000' => { Name => 'IsRecording', Type => 'Boolean' }, + '060e2b34.0101.0101.0d0b0102.00000000' => { Name => 'IsLiveProduction', Type => 'Boolean' }, + '060e2b34.0101.0101.0d0b0103.00000000' => { Name => 'IsLiveTransmission', Type => 'Boolean' }, + '060e2b34.0101.0101.0d0b0104.00000000' => { Name => 'IsDubbed', Type => 'Boolean' }, + '060e2b34.0101.0101.0d0b0105.00000000' => { Name => 'IsVoiceover', Type => 'Boolean' }, + '060e2b34.0101.0101.0d0b0106.00000000' => { Name => 'HasAudioWatermark', Type => 'Boolean', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.0d0b0107.00000000' => { Name => 'AudioWatermarkKind', Type => 'UTF-16', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0101.0d0b0108.00000000' => { Name => 'HasVideoWatermark', Type => 'Boolean' }, + '060e2b34.0101.0101.0d0b0109.00000000' => { Name => 'VideoWatermarkKind', Type => 'UTF-16' }, + # '060e2b34.0101.0101.0d0b0200.00000000' => { Name => 'Subtitling', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0201.00000000' => { Name => 'SubtitlesPresent', Type => 'Boolean' }, + # '060e2b34.0101.0101.0d0b0300.00000000' => { Name => 'CaptionTitles', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0301.00000000' => { Name => 'CaptionTitles', Type => 'Boolean' }, + '060e2b34.0101.0101.0d0b0302.00000000' => { Name => 'CaptionsViaTeletext', Type => 'Boolean' }, + '060e2b34.0101.0101.0d0b0303.00000000' => { Name => 'TextlessMaterial', Type => 'Boolean' }, + # '060e2b34.0101.0101.0d0b0400.00000000' => { Name => 'AudioParameters', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0401.00000000' => { Name => 'AudioReferenceLevel', Format => 'string', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0101.0d0b0500.00000000' => { Name => 'StorageMedia', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0501.00000000' => { Name => 'StorageDeviceKind', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0502.00000000' => { Name => 'StorageMediaKind', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0503.00000000' => { Name => 'StorageMediaID', Type => 'UTF-16' }, + # '060e2b34.0101.0101.0d0b0600.00000000' => { Name => 'BroadcastScheduleInformation', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0601.00000000' => { Name => 'BroadcastDate', %timestamp }, + '060e2b34.0101.0101.0d0b0602.00000000' => { Name => 'BroadcastTime', %timestamp }, + '060e2b34.0101.0101.0d0b0603.00000000' => { Name => 'IsRepeat', Type => 'Boolean' }, + '060e2b34.0101.0101.0d0b0604.00000000' => { Name => 'FirstTransmissionInfo', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0605.00000000' => { Name => 'TeletextSubtitlesAvailable', Type => 'Boolean' }, + '060e2b34.0101.0101.0d0b0606.00000000' => { Name => 'SeasonEpisodeNumber', Format => 'string' }, + '060e2b34.0101.0101.0d0b0607.00000000' => { Name => 'SeasonEpisodeTitle', Format => 'string' }, + '060e2b34.0101.0101.0d0b0608.00000000' => { Name => 'EPGProgramSynopsis', Type => 'UTF-16' }, + # '060e2b34.0101.0101.0d0b0700.00000000' => { Name => 'Classification', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0701.00000000' => { Name => 'ContentClassification', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0702.00000000' => { Name => 'DVBParentalRating', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0703.00000000' => { Name => 'ContentMaturityRating', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0704.00000000' => { Name => 'ContentMaturityDescription', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0705.00000000' => { Name => 'ContentMaturityGraphic', Type => 'UTF-16' }, + # '060e2b34.0101.0101.0d0b0800.00000000' => { Name => 'Contract', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0801.00000000' => { Name => 'ContractEntity', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0802.00000000' => { Name => 'ContractTypeLink', Type => 'UTF-16' }, + # '060e2b34.0101.0101.0d0b0900.00000000' => { Name => 'Rights', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0901.00000000' => { Name => 'ConsumerRightsToCopy', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0902.00000000' => { Name => 'BroadcasterRightsToCopy', Type => 'UTF-16' }, + # '060e2b34.0101.0101.0d0b0a00.00000000' => { Name => 'ProductionKeyPeople', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0a01.00000000' => { Name => 'DirectorName', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0a02.00000000' => { Name => 'ProducerName', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0a03.00000000' => { Name => 'FemaleLeadActressName', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0a04.00000000' => { Name => 'MaleLeadActorName', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0a05.00000000' => { Name => 'PresenterName', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0a06.00000000' => { Name => 'MainSponsorName', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0a07.00000000' => { Name => 'VoiceTalentName', Type => 'UTF-16' }, + # '060e2b34.0101.0101.0d0b0b00.00000000' => { Name => 'Address', Type => 'Node' }, + '060e2b34.0101.0101.0d0b0b01.00000000' => { Name => 'PostboxNumber', Type => 'UTF-16' }, + '060e2b34.0101.0101.0d0b0b02.00000000' => { Name => 'PostCodeForPostbox', Type => 'UTF-16' }, + # '060e2b34.0101.0101.0e000000.00000000' => { Name => 'PrivateUse', Type => 'Node' }, + # '060e2b34.0101.0101.0e010000.00000000' => { Name => 'MISBSystems', Type => 'Node' }, + # '060e2b34.0101.0101.0e010100.00000000' => { Name => 'MISBSystemsStreams', Type => 'Node' }, + # '060e2b34.0101.0101.0e010200.00000000' => { Name => 'MISBSystemsAttributes', Type => 'Node' }, + # '060e2b34.0101.0101.0e010300.00000000' => { Name => 'MISBSystemsComposites', Type => 'Node' }, + # '060e2b34.0101.0101.0e010400.00000000' => { Name => 'MISBSystemsIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0101.0e020000.00000000' => { Name => 'ASPA', Type => 'Node' }, + # '060e2b34.0101.0101.0e020100.00000000' => { Name => 'ASPAStreams', Type => 'Node' }, + # '060e2b34.0101.0101.0e020200.00000000' => { Name => 'ASPAAttributes', Type => 'Node' }, + # '060e2b34.0101.0101.0e020201.00000000' => { Name => 'ASPARelationalAttributes', Type => 'Node' }, + # '060e2b34.0101.0101.0e020202.00000000' => { Name => 'ASPAInformationAttributes', Type => 'Node' }, + # '060e2b34.0101.0101.0e020300.00000000' => { Name => 'ASPAComposites', Type => 'Node' }, + # '060e2b34.0101.0101.0e020400.00000000' => { Name => 'ASPAIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0101.0e030000.00000000' => { Name => 'MISBClassified', Type => 'Node' }, + # '060e2b34.0101.0101.0e030100.00000000' => { Name => 'MISBClassifiedStreams', Type => 'Node' }, + # '060e2b34.0101.0101.0e030200.00000000' => { Name => 'MISBClassifiedAttributes', Type => 'Node' }, + # '060e2b34.0101.0101.0e030300.00000000' => { Name => 'MISBClassifiedComposites', Type => 'Node' }, + # '060e2b34.0101.0101.0e030400.00000000' => { Name => 'MISBClassifiedIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0101.0f000000.00000000' => { Name => 'EXPERIMENTALMETADATA', Type => 'Node' }, + # '060e2b34.0101.0101.43000000.00000000' => { Name => 'Legacy315M', Type => 'Node' }, + '060e2b34.0101.0102.01011003.03000000' => { Name => 'ProgramNumber', Format => 'string' }, + '060e2b34.0101.0102.01011503.00000000' => { Name => 'DefinitionObjectID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.01012005.00000000' => { Name => 'IEEEDeviceID', Format => 'int8u' }, + '060e2b34.0101.0102.01030106.00000000' => { Name => 'ProjectNumber', Format => 'string' }, + '060e2b34.0101.0102.01030201.02000000' => { Name => 'EdgeCodeHeader', Type => 'DataValue', Unknown => 1 }, + # '060e2b34.0101.0102.01030400.00000000' => { Name => 'NetworkAndStreamIdentifiers', Type => 'Node' }, + '060e2b34.0101.0102.01030401.00000000' => { Name => 'ChannelHandle', Format => 'int16s' }, + '060e2b34.0101.0102.01040102.00000000' => { Name => 'PhysicalMediaLocation', Format => 'string' }, + '060e2b34.0101.0102.01040102.01000000' => { Name => 'MediaLocation', Type => 'UTF-16' }, + '060e2b34.0101.0102.01040103.00000000' => { Name => 'TrackNumber', Format => 'int32u' }, + # '060e2b34.0101.0102.01040900.00000000' => { Name => 'SynchronizationLocators', Type => 'Node' }, + '060e2b34.0101.0102.01040901.00000000' => { Name => 'EdgeCodeStart', Type => 'Position', %duration }, + '060e2b34.0101.0102.01050800.00000000' => { Name => 'VersionTitle', Format => 'string' }, + # '060e2b34.0101.0102.01070000.00000000' => { Name => 'LocalIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0102.01070100.00000000' => { Name => 'PackageIdentifiers', Type => 'Node' }, + '060e2b34.0101.0102.01070101.00000000' => { Name => 'TrackID', Format => 'int32u' }, + '060e2b34.0101.0102.01070102.00000000' => { Name => 'TrackName', Format => 'string' }, + '060e2b34.0101.0102.01070102.01000000' => { Name => 'TrackName', Type => 'UTF-16' }, + '060e2b34.0101.0102.01070102.03000000' => { Name => 'DefinitionObjectName', Format => 'string' }, + '060e2b34.0101.0102.01070102.03010000' => { Name => 'DefinitionObjectName', Type => 'UTF-16' }, + '060e2b34.0101.0102.01070103.00000000' => { Name => 'ContentPackageMetadataLink', Format => 'int8u' }, + '060e2b34.0101.0102.01070104.00000000' => { Name => 'DefinedName', Format => 'string' }, + '060e2b34.0101.0102.01070104.01000000' => { Name => 'DefinedName', Type => 'UTF-16' }, + # '060e2b34.0101.0102.010a0000.00000000' => { Name => 'OrganizationIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0102.010a0100.00000000' => { Name => 'ManufacturerIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0102.010a0101.00000000' => { Name => 'ManufacturerIdentifiers', Type => 'Node' }, + '060e2b34.0101.0102.010a0101.01000000' => { Name => 'DeviceManufacturerName', Format => 'string' }, + '060e2b34.0101.0102.010a0101.01010000' => { Name => 'DeviceManufacturerName', Type => 'UTF-16' }, + '060e2b34.0101.0102.010a0101.03000000' => { Name => 'ManufacturerID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.010a0102.00000000' => { Name => 'IEEEManufacturerID', Type => 'Hex' }, + '060e2b34.0101.0102.010a0103.00000000' => { Name => 'AAFManufacturerID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.02010400.00000000' => { Name => 'SupplyingDepartmentName', Format => 'string' }, + '060e2b34.0101.0102.02200303.00000000' => { Name => 'AudienceShare', Format => 'float' }, + '060e2b34.0101.0102.02200304.00000000' => { Name => 'AudienceAppreciation', Format => 'float' }, + '060e2b34.0101.0102.02300603.01050000' => { Name => 'Salutation', Format => 'string' }, + '060e2b34.0101.0102.02300603.01060000' => { Name => 'HonorsAndQualifications', Format => 'string' }, + # '060e2b34.0101.0102.03010200.00000000' => { Name => 'DataInterpretationsAndDefinitions', Type => 'Node' }, + '060e2b34.0101.0102.03010201.04000000' => { Name => 'ObjectModelVersion', Format => 'int32u' }, + '060e2b34.0101.0102.03010201.05000000' => { Name => 'SDKVersion', Type => 'VersionType' }, + # '060e2b34.0101.0102.03010202.00000000' => { Name => 'PropertyDefinitions', Type => 'Node' }, + '060e2b34.0101.0102.03010202.01000000' => { Name => 'IsOptional', Type => 'Boolean' }, + '060e2b34.0101.0102.03010202.02000000' => { Name => 'IsSearchable', Type => 'Boolean' }, + # '060e2b34.0101.0102.03010202.03000000' => { Name => 'PropertyDefaults', Type => 'Node' }, + '060e2b34.0101.0102.03010202.03010000' => { Name => 'UseDefaultValue', Type => 'Boolean' }, + '060e2b34.0101.0102.03010202.03020000' => { Name => 'DefaultDataValue', Type => 'Indirect', Unknown => 1 }, + # '060e2b34.0101.0102.03010203.00000000' => { Name => 'TypeDefinition', Type => 'Node' }, + '060e2b34.0101.0102.03010203.01000000' => { Name => 'Size', Format => 'int8u' }, + '060e2b34.0101.0102.03010203.02000000' => { Name => 'IsSigned', Type => 'Boolean' }, + '060e2b34.0101.0102.03010203.03000000' => { Name => 'ElementCount', Format => 'int32u' }, + '060e2b34.0101.0102.03010203.04000000' => { Name => 'ElementNameList', Type => 'UTF-16' }, + '060e2b34.0101.0102.03010203.05000000' => { Name => 'TypeDefinitionElementValueList', Format => 'int64s' }, + '060e2b34.0101.0102.03010203.06000000' => { Name => 'MemberNameList', Type => 'UTF-16' }, + '060e2b34.0101.0102.03010203.07000000' => { Name => 'ExtendibleElementNameList', Type => 'UTF-16' }, + '060e2b34.0101.0102.03010203.08000000' => { Name => 'TypeDefinitionExtendibleElementValues', Type => 'AUIDArray', Unknown => 1 }, + '060e2b34.0101.0102.03010203.0b000000' => { Name => 'TargetSet', Type => 'AUIDArray', Unknown => 1 }, + # '060e2b34.0101.0102.03010210.00000000' => { Name => 'KLVInterpretations', Type => 'Node' }, + '060e2b34.0101.0102.03010210.01000000' => { Name => 'FillerData', Format => 'undef', Unknown => 1 }, + '060e2b34.0101.0102.03010210.02000000' => { Name => 'KLVDataValue', Type => 'Opaque', Unknown => 1 }, + '060e2b34.0101.0102.03010210.03000000' => { Name => 'PackageKLVData', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.03010210.04000000' => { Name => 'ComponentKLVData', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.03020102.09000000' => { Name => 'AssignedCategoryName', Format => 'string' }, + '060e2b34.0101.0102.03020102.09010000' => { Name => 'AssignedCategoryName', Type => 'UTF-16' }, + '060e2b34.0101.0102.03020102.0a000000' => { Name => 'AssignedCategoryValue', Format => 'string' }, + '060e2b34.0101.0102.03020102.0a010000' => { Name => 'AssignedCategoryValue', Type => 'UTF-16' }, + '060e2b34.0101.0102.03020102.0b000000' => { Name => 'ShotList', Format => 'string' }, + '060e2b34.0101.0102.03020102.0c000000' => { Name => 'PackageUserComments', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.03020102.0d000000' => { Name => 'Cue-InWords', Format => 'string' }, + '060e2b34.0101.0102.03020102.0e000000' => { Name => 'Cue-OutWords', Format => 'string' }, + '060e2b34.0101.0102.03020301.01000000' => { Name => 'ObjectKind', Format => 'string' }, + '060e2b34.0101.0102.03020301.01010000' => { Name => 'ObjectKind', Type => 'UTF-16' }, + '060e2b34.0101.0102.03020301.02000000' => { Name => 'ObjectDescription', Format => 'string' }, + '060e2b34.0101.0102.03020301.02010000' => { Name => 'ObjectDescription', Type => 'UTF-16' }, + # '060e2b34.0101.0102.03020400.00000000' => { Name => 'DescriptiveNames', Type => 'Node' }, + # '060e2b34.0101.0102.03020401.01000000' => { Name => 'GenericObjectNames', Type => 'Node' }, + '060e2b34.0101.0102.03020401.01010000' => { Name => 'ObjectName', Format => 'string' }, + '060e2b34.0101.0102.03020401.02010000' => { Name => 'MetadataItemName', Type => 'UTF-16' }, + # '060e2b34.0101.0102.03020500.00000000' => { Name => 'EditorialCommentsAndDescriptions', Type => 'Node' }, + '060e2b34.0101.0102.03020501.00000000' => { Name => 'ShotCommentKind', Format => 'string' }, + '060e2b34.0101.0102.03020502.00000000' => { Name => 'ShotComment', Format => 'string' }, + '060e2b34.0101.0102.03030301.01000000' => { Name => 'ComputedObjectKind', Format => 'string' }, + '060e2b34.0101.0102.03030301.01010000' => { Name => 'ComputedObjectKind', Type => 'UTF-16' }, + '060e2b34.0101.0102.03030301.02000000' => { Name => 'VersionNumberString', Format => 'string' }, + '060e2b34.0101.0102.03030301.02010000' => { Name => 'VersionNumberString', Type => 'UTF-16' }, + '060e2b34.0101.0102.03030301.03000000' => { Name => 'VersionNumber', Type => 'VersionType' }, + # '060e2b34.0101.0102.03030302.00000000' => { Name => 'DerivedSummaryInformation', Type => 'Node' }, + '060e2b34.0101.0102.03030302.01000000' => { Name => 'WAVESummary', Type => 'DataValue', Unknown => 1 }, + '060e2b34.0101.0102.03030302.02000000' => { Name => 'AIFCSummary', Type => 'DataValue', Unknown => 1 }, + '060e2b34.0101.0102.03030302.03000000' => { Name => 'TIFFSummary', Type => 'DataValue', Unknown => 1 }, + '060e2b34.0101.0102.04010101.03000000' => { Name => 'ViewportAspectRatio', Format => 'rational64s' }, + '060e2b34.0101.0102.04010201.01010200' => { Name => 'CaptureGammaEquation', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.0102.04010201.01030100' => { Name => 'ColorimetryCode', Type => 'ColorimetryCode', Unknown => 1 }, + '060e2b34.0101.0102.04010201.01100000' => { Name => 'PresentationGammaEquation', Format => 'string' }, + '060e2b34.0101.0102.04010201.01100100' => { Name => 'PresentationGammaEquation', Type => 'PresentationGamma', Unknown => 1 }, + '060e2b34.0101.0102.04010301.06000000' => { Name => 'FieldDominance', Format => 'int8u' }, + '060e2b34.0101.0102.04010302.05000000' => { Name => 'VideoLineMap', Format => 'int32s' }, + '060e2b34.0101.0102.04010401.01000000' => { Name => 'AnalogVideoSystemName', Type => 'VideoSignalType', Unknown => 1 }, + '060e2b34.0101.0102.04010501.10000000' => { Name => 'VerticalSub-sampling', Format => 'int32u' }, + '060e2b34.0101.0102.04010503.01010000' => { Name => 'BitsPerPixel', Format => 'int32u' }, + '060e2b34.0101.0102.04010503.05000000' => { Name => 'ColorRangeLevels', Format => 'int32u' }, + '060e2b34.0101.0102.04010503.06000000' => { Name => 'PixelLayout', Type => 'RGBALayout', Unknown => 1 }, + '060e2b34.0101.0102.04010503.07000000' => { Name => 'AlphaSampleDepth', Format => 'int32u' }, + '060e2b34.0101.0102.04010503.08000000' => { Name => 'Palette', Type => 'DataValue', Unknown => 1 }, + '060e2b34.0101.0102.04010503.09000000' => { Name => 'PaletteLayout', Type => 'RGBALayout', Unknown => 1 }, + '060e2b34.0101.0102.04010503.0a000000' => { Name => 'ComponentDepth', Format => 'int32u' }, + '060e2b34.0101.0102.04010601.00000000' => { Name => 'VideoCodingSchemeID', Type => 'AUID' }, + '060e2b34.0101.0102.04010802.03000000' => { Name => 'RoundedCaptureFilmFrameRate', Format => 'int32u' }, + '060e2b34.0101.0102.04020301.02000000' => { Name => 'AudioAverageBitrate', Format => 'float', PrintConv => 'ConvertBitrate($val)', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0102.04020301.03000000' => { Name => 'AudioFixedBitrateFlag', Type => 'Boolean', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0102.04020401.00000000' => { Name => 'CodingLawKind', Format => 'string' }, + '060e2b34.0101.0102.04020402.00000000' => { Name => 'AudioCodingSchemeID', Type => 'AUID', Unknown => 1, Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0102.04020403.01010000' => { Name => 'LayerNumber', Format => 'int8u' }, + '060e2b34.0101.0102.04040101.02010000' => { Name => 'TimecodeTimebase', Format => 'rational64s' }, + '060e2b34.0101.0102.04040101.02060000' => { Name => 'RoundedTimecodeTimebase', Format => 'int16u' }, + # '060e2b34.0101.0102.04070000.00000000' => { Name => 'GeneralEssenceAndDataParameters', Type => 'Node' }, + '060e2b34.0101.0102.04070100.00000000' => { Name => 'ComponentDataDefinition', Type => 'WeakReference', %componentDataDef }, + '060e2b34.0101.0102.04070200.00000000' => { Name => 'StreamData', Type => 'DataStream', Unknown => 1 }, + '060e2b34.0101.0102.04070300.00000000' => { Name => 'TimecodeStreamData', Type => 'DataStream', Unknown => 1 }, + '060e2b34.0101.0102.04090101.00000000' => { Name => 'RecordedFormat', Type => 'UTF-16' }, + '060e2b34.0101.0102.04100101.01000000' => { Name => 'TapeShellKind', Format => 'string' }, + '060e2b34.0101.0102.04100101.01010000' => { Name => 'TapeShellKind', Type => 'UTF-16' }, + '060e2b34.0101.0102.04100101.02000000' => { Name => 'TapeFormulation', Format => 'string' }, + '060e2b34.0101.0102.04100101.02010000' => { Name => 'TapeFormulation', Type => 'UTF-16' }, + '060e2b34.0101.0102.04100101.03000000' => { Name => 'TapeCapacity', Format => 'int32u' }, + '060e2b34.0101.0102.04100101.04000000' => { Name => 'TapeManufacturer', Format => 'string' }, + '060e2b34.0101.0102.04100101.04010000' => { Name => 'TapeManufacturer', Type => 'UTF-16' }, + '060e2b34.0101.0102.04100101.05000000' => { Name => 'TapeStock', Format => 'string' }, + '060e2b34.0101.0102.04100101.05010000' => { Name => 'TapeStock', Type => 'UTF-16' }, + '060e2b34.0101.0102.04100101.06000000' => { Name => 'TapeBatchNumber', Format => 'string' }, + '060e2b34.0101.0102.04100101.06010000' => { Name => 'TapeBatchNumber', Type => 'UTF-16' }, + '060e2b34.0101.0102.04100103.01030000' => { Name => 'PerforationsPerFrame', Format => 'int8u' }, + '060e2b34.0101.0102.04100103.01030100' => { Name => 'PerforationsPerFrame', Format => 'rational64s' }, + '060e2b34.0101.0102.04100103.01040100' => { Name => 'FilmFormatName', Type => 'UTF-16' }, + '060e2b34.0101.0102.04100103.01040200' => { Name => 'FilmFormatName', Type => 'FilmFormat', Unknown => 1 }, + '060e2b34.0101.0102.04100103.01050100' => { Name => 'FilmStockKind', Type => 'UTF-16' }, + '060e2b34.0101.0102.04100103.01060100' => { Name => 'FilmStockManufacturerName', Type => 'UTF-16' }, + '060e2b34.0101.0102.04100103.01070000' => { Name => 'FilmBatchNumber', Format => 'string' }, + '060e2b34.0101.0102.04100103.01070100' => { Name => 'FilmBatchNumber', Type => 'UTF-16' }, + '060e2b34.0101.0102.04100103.01080000' => { Name => 'FilmGauge', Type => 'FilmType', Unknown => 1 }, + '060e2b34.0101.0102.04100103.01090000' => { Name => 'EdgeCodeFilmGauge', Type => 'FilmType', Unknown => 1 }, + '060e2b34.0101.0102.04100103.02030000' => { Name => 'ExposedAspectRatio', Format => 'rational64s' }, + # '060e2b34.0101.0102.04180000.00000000' => { Name => 'MemoryStorageCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0102.04180100.00000000' => { Name => 'MemoryStorageAlignmentCharacteristics', Type => 'Node' }, + '060e2b34.0101.0102.04180101.00000000' => { Name => 'ImageAlignmentOffset', Format => 'int32u' }, + '060e2b34.0101.0102.04180102.00000000' => { Name => 'ImageStartOffset', Format => 'int32u' }, + '060e2b34.0101.0102.04180103.00000000' => { Name => 'ImageEndOffset', Format => 'int32u' }, + '060e2b34.0101.0102.04180104.00000000' => { Name => 'PaddingBits', Format => 'int16s' }, + '060e2b34.0101.0102.04200201.01040000' => { Name => 'FocalLength', Format => 'float', PrintConv => 'sprintf("%.1f mm",$val)' }, + '060e2b34.0101.0102.04200201.01050000' => { Name => 'SensorSize', Format => 'string' }, + '060e2b34.0101.0102.04200201.01060000' => { Name => 'FNumber', Format => 'float' }, + '060e2b34.0101.0102.04200201.01070000' => { Name => 'SensorTypeCode', Format => 'string' }, + '060e2b34.0101.0102.04200201.01080000' => { Name => 'FieldOfViewHorizontal', Format => 'float' }, + '060e2b34.0101.0102.04200201.01090000' => { Name => 'AnamorphicLensCharacteristic', Format => 'string' }, + # '060e2b34.0101.0102.05020103.00000000' => { Name => 'JPEGProcessing', Type => 'Node' }, + # '060e2b34.0101.0102.05020103.01000000' => { Name => 'TIFFJPEGProcessing', Type => 'Node' }, + '060e2b34.0101.0102.05020103.01010000' => { Name => 'UniformDataFlag', Type => 'Boolean' }, + '060e2b34.0101.0102.05020103.01020000' => { Name => 'JPEGTableID', Type => 'JPEGTableIDType', Unknown => 1 }, + # '060e2b34.0101.0102.05020103.02000000' => { Name => 'JFIF_JPEGProcessing', Type => 'Node' }, + '060e2b34.0101.0102.05200102.00000000' => { Name => 'AlphaTransparency', Format => 'int8u', + PrintConv => { 0 => 'Not Inverted', 1 => 'Inverted' }, + }, + # '060e2b34.0101.0102.05200701.00000000' => { Name => 'ModificationInformation', Type => 'Node' }, + '060e2b34.0101.0102.05200701.01000000' => { Name => 'GenerationID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.05200701.02000000' => { Name => 'ApplicationSupplierName', Format => 'string' }, + '060e2b34.0101.0102.05200701.02010000' => { Name => 'ApplicationSupplierName', Type => 'UTF-16' }, + '060e2b34.0101.0102.05200701.03000000' => { Name => 'ApplicationName', Format => 'string' }, + '060e2b34.0101.0102.05200701.03010000' => { Name => 'ApplicationName', Type => 'UTF-16' }, + '060e2b34.0101.0102.05200701.04000000' => { Name => 'ApplicationVersionNumber', Type => 'ProductVersion' }, + '060e2b34.0101.0102.05200701.05000000' => { Name => 'ApplicationVersionString', Format => 'string' }, + '060e2b34.0101.0102.05200701.05010000' => { Name => 'ApplicationVersionString', Type => 'UTF-16' }, + '060e2b34.0101.0102.05200701.06000000' => { Name => 'ApplicationPlatform', Format => 'string' }, + '060e2b34.0101.0102.05200701.06010000' => { Name => 'ApplicationPlatform', Type => 'UTF-16' }, + '060e2b34.0101.0102.05200701.07000000' => { Name => 'ApplicationProductID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.05200701.08000000' => { Name => 'LinkedGenerationID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.05200701.09000000' => { Name => 'ContainerVersion', Type => 'ProductVersion' }, + '060e2b34.0101.0102.05200701.0a000000' => { Name => 'ToolkitVersion', Type => 'ProductVersion' }, + # '060e2b34.0101.0102.05200900.00000000' => { Name => 'CodeProcessorSettings', Type => 'Node' }, + '060e2b34.0101.0102.05200901.00000000' => { Name => 'Plug-InCategoryID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.05200902.00000000' => { Name => 'Plug-InPlatformID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.05200903.00000000' => { Name => 'MinimumSupportedPlatformVersion', Type => 'VersionType' }, + '060e2b34.0101.0102.05200904.00000000' => { Name => 'MaximumSupportedPlatformVersion', Type => 'VersionType' }, + '060e2b34.0101.0102.05200905.00000000' => { Name => 'Plug-InEngineID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.05200906.00000000' => { Name => 'MinimumSupportedEngineVersion', Type => 'VersionType' }, + '060e2b34.0101.0102.05200907.00000000' => { Name => 'MaximumSupportedEngineVersion', Type => 'VersionType' }, + '060e2b34.0101.0102.05200908.00000000' => { Name => 'Plug-InAPIID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.05200909.00000000' => { Name => 'MinimumAPIVersion', Type => 'VersionType' }, + '060e2b34.0101.0102.0520090a.00000000' => { Name => 'MaximumAPIVersion', Type => 'VersionType' }, + '060e2b34.0101.0102.0520090b.00000000' => { Name => 'Software-OnlySupportFlag', Type => 'Boolean' }, + '060e2b34.0101.0102.0520090c.00000000' => { Name => 'HardwareAcceleratorFlag', Type => 'Boolean' }, + '060e2b34.0101.0102.0520090d.00000000' => { Name => 'Plug-InLocatorSet', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.0520090e.00000000' => { Name => 'AuthenticationFlag', Type => 'Boolean' }, + '060e2b34.0101.0102.0520090f.00000000' => { Name => 'AssociatedMetadataDefinition', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.05300402.00000000' => { Name => 'EventTrackEditRate', Format => 'rational64s' }, + '060e2b34.0101.0102.05300403.00000000' => { Name => 'DefaultFadeEditRate', Format => 'rational64s' }, + '060e2b34.0101.0102.05300404.00000000' => { Name => 'EditingEventComment', Format => 'string' }, + '060e2b34.0101.0102.05300404.01000000' => { Name => 'EditingEventComment', Type => 'UTF-16' }, + '060e2b34.0101.0102.05300405.00000000' => { Name => 'EditRate', Format => 'rational64s' }, + '060e2b34.0101.0102.05300506.00000000' => { Name => 'OperationDefinitionID', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.05300507.00000000' => { Name => 'Value', Type => 'Indirect', Unknown => 1 }, + '060e2b34.0101.0102.05300508.00000000' => { Name => 'EditHint', Type => 'EditHintType', Unknown => 1 }, + '060e2b34.0101.0102.05300509.00000000' => { Name => 'OperationDataDefinition', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.0530050a.00000000' => { Name => 'OperationCategory', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.0530050b.00000000' => { Name => 'DisplayUnits', Format => 'string' }, + '060e2b34.0101.0102.0530050b.01000000' => { Name => 'DisplayUnits', Type => 'UTF-16' }, + '060e2b34.0101.0102.0530050c.00000000' => { Name => 'BypassOverride', Format => 'int32u' }, + '060e2b34.0101.0102.0530050d.00000000' => { Name => 'TimepointValue', Type => 'Indirect', Unknown => 1 }, + '060e2b34.0101.0102.05300601.00000000' => { Name => 'BeginAnchor', Format => 'string' }, + '060e2b34.0101.0102.05300601.01000000' => { Name => 'BeginAnchor', Type => 'UTF-16' }, + '060e2b34.0101.0102.05300602.00000000' => { Name => 'EndAnchor', Format => 'string' }, + '060e2b34.0101.0102.05300602.01000000' => { Name => 'EndAnchor', Type => 'UTF-16' }, + # '060e2b34.0101.0102.05401000.00000000' => { Name => 'TransferHistory', Type => 'Node' }, + # '060e2b34.0101.0102.05401001.00000000' => { Name => 'ImageTransferHistory', Type => 'Node' }, + '060e2b34.0101.0102.05401001.01000000' => { Name => 'FilmToVideoTransferDirection', Type => 'PulldownDirection', Unknown => 1 }, + '060e2b34.0101.0102.05401001.02000000' => { Name => 'FilmToVideoTransferKind', Type => 'PulldownKind', Unknown => 1 }, + '060e2b34.0101.0102.05401001.03000000' => { Name => 'FilmToVideoTransferPhase', Type => 'PhaseFrameType', Unknown => 1 }, + '060e2b34.0101.0102.06010101.01000000' => { Name => 'TeletextSubtitlesFlag', Type => 'Boolean' }, + '060e2b34.0101.0102.06010101.02000000' => { Name => 'SubtitleDatafileFlag', Type => 'Boolean' }, + '060e2b34.0101.0102.06010101.03000000' => { Name => 'ClosedCaptionSubtitlesFlag', Type => 'Boolean' }, + '060e2b34.0101.0102.06010102.01000000' => { Name => 'SampleIndex', Type => 'DataStream', Unknown => 1 }, + '060e2b34.0101.0102.06010103.01000000' => { Name => 'SourcePackageID', Type => 'PackageID', Unknown => 1 }, + '060e2b34.0101.0102.06010103.02000000' => { Name => 'SourceTrackID', Format => 'int32u' }, + '060e2b34.0101.0102.06010103.03000000' => { Name => 'RelativeScope', Format => 'int32u' }, + '060e2b34.0101.0102.06010103.04000000' => { Name => 'RelativeTrack', Format => 'int32u' }, + # '060e2b34.0101.0102.06010104.01000000' => { Name => 'WeakReferences', Type => 'Node' }, + '060e2b34.0101.0102.06010104.01010000' => { Name => 'ObjectClass', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.01020000' => { Name => 'EssenceContainerFormat', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.01030000' => { Name => 'CodecDefinition', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.01040000' => { Name => 'ParameterDefinition', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.01050000' => { Name => 'Interpolation', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.01060000' => { Name => 'ParameterDataType', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.01070000' => { Name => 'CodecEssenceDescriptor', Type => 'WeakReference', Unknown => 1 }, + # '060e2b34.0101.0102.06010104.02000000' => { Name => 'StrongReferences', Type => 'Node' }, + '060e2b34.0101.0102.06010104.02010000' => { Name => 'ContentStorage', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.02020000' => { Name => 'Dictionary', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.02030000' => { Name => 'EssenceDescription', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.02040000' => { Name => 'Sequence', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.02050000' => { Name => 'TransitionEffect', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.02060000' => { Name => 'EffectRendering', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.02070000' => { Name => 'InputSegment', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.02080000' => { Name => 'StillFrame', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.02090000' => { Name => 'Selected', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.020a0000' => { Name => 'Annotation', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0102.06010104.020b0000' => { Name => 'ManufacturerInformationObject', Type => 'StrongReference', Unknown => 1 }, + # '060e2b34.0101.0102.06010104.03000000' => { Name => 'WeakReferencesBatches', Type => 'Node' }, + '060e2b34.0101.0102.06010104.03010000' => { Name => 'CodecEssenceKinds', Type => 'WeakReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010104.03020000' => { Name => 'OperationParameters', Type => 'WeakReferenceBatch', Unknown => 1 }, + # '060e2b34.0101.0102.06010104.04000000' => { Name => 'WeakReferencesArrays', Type => 'Node' }, + '060e2b34.0101.0102.06010104.04010000' => { Name => 'DegradedEffects', Type => 'WeakReferenceArray', Unknown => 1 }, + # '060e2b34.0101.0102.06010104.05000000' => { Name => 'StrongReferencesBatches', Type => 'Node' }, + '060e2b34.0101.0102.06010104.05010000' => { Name => 'Packages', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010104.05020000' => { Name => 'EssenceData', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010104.05030000' => { Name => 'OperationDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010104.05040000' => { Name => 'ParameterDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010104.05050000' => { Name => 'DataDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010104.05060000' => { Name => 'Plug-InDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010104.05070000' => { Name => 'CodecDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010104.05080000' => { Name => 'ContainerDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010104.05090000' => { Name => 'InterpolationDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + # '060e2b34.0101.0102.06010104.06000000' => { Name => 'StrongReferencesArrays', Type => 'Node' }, + '060e2b34.0101.0102.06010104.06010000' => { Name => 'AvailableRepresentations', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.06010104.06020000' => { Name => 'InputSegments', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.06010104.06030000' => { Name => 'EssenceLocators', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.06010104.06040000' => { Name => 'IdentificationList', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.06010104.06050000' => { Name => 'Tracks', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.06010104.06060000' => { Name => 'ControlPointList', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.06010104.06070000' => { Name => 'PackageTracks', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.06010104.06080000' => { Name => 'Alternates', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.06010104.06090000' => { Name => 'ComponentsInSequence', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.06010104.060a0000' => { Name => 'Parameters', Type => 'StrongReferenceBatch', Unknown => 1 }, + # '060e2b34.0101.0102.06010106.00000000' => { Name => 'EssenceToObjectRelationships', Type => 'Node' }, + '060e2b34.0101.0102.06010106.01000000' => { Name => 'LinkedPackageID', Type => 'PackageID', Unknown => 1 }, + # '060e2b34.0101.0102.06010107.00000000' => { Name => 'ObjectDictionaryToMetadataRelationships', Type => 'Node' }, + '060e2b34.0101.0102.06010107.01000000' => { Name => 'ParentClass', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.02000000' => { Name => 'Properties', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010107.03000000' => { Name => 'IsConcrete', Type => 'Boolean' }, + '060e2b34.0101.0102.06010107.04000000' => { Name => 'PropertyType', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.05000000' => { Name => 'LocalID', Format => 'int16u' }, + '060e2b34.0101.0102.06010107.06000000' => { Name => 'IsUniqueIdentifier', Type => 'Boolean' }, + '060e2b34.0101.0102.06010107.07000000' => { Name => 'ClassDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010107.08000000' => { Name => 'TypeDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0102.06010107.09000000' => { Name => 'TargetClassOfStrongReference', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.0a000000' => { Name => 'TargetClassOfWeakReference', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.0b000000' => { Name => 'EnumerationUnderlyingIntegerType', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.0c000000' => { Name => 'FixedArrayElementType', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.0d000000' => { Name => 'VariableArrayElementType', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.0e000000' => { Name => 'SetElementType', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.0f000000' => { Name => 'StringElementType', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.10000000' => { Name => 'StreamElementType', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.11000000' => { Name => 'MemberTypes', Type => 'WeakReferenceArray', Unknown => 1 }, + '060e2b34.0101.0102.06010107.12000000' => { Name => 'RenamedType', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0102.06010107.13000000' => { Name => 'DictionaryIdentifier', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0102.06010107.14000000' => { Name => 'DictionaryDescription', Format => 'string' }, + '060e2b34.0101.0102.06010107.14010000' => { Name => 'DictionaryDescription', Type => 'UTF-16' }, + # '060e2b34.0101.0102.06080101.00000000' => { Name => 'ContinuityCounts', Type => 'Node' }, + '060e2b34.0101.0102.06080101.01000000' => { Name => 'BlockContinuityCount', Format => 'int16u' }, + # '060e2b34.0101.0102.06080102.00000000' => { Name => 'StreamPositionalRelationships', Type => 'Node' }, + '060e2b34.0101.0102.06080102.01000000' => { Name => 'StreamPositionIndicator', Format => 'int8u' }, + # '060e2b34.0101.0102.06080202.00000000' => { Name => 'StorageOffsets', Type => 'Node' }, + '060e2b34.0101.0102.07020103.01030000' => { Name => 'Origin', Format => 'int64s', %duration }, + '060e2b34.0101.0102.07020103.01040000' => { Name => 'StartTimeRelativeToReference', Format => 'int64s', %duration }, + '060e2b34.0101.0102.07020103.01050000' => { Name => 'StartTimecode', Format => 'int64s', %duration }, + '060e2b34.0101.0102.07020103.01060000' => { Name => 'CutPoint', Format => 'int64s', %duration }, + '060e2b34.0101.0102.07020103.03030000' => { Name => 'EventStart', Type => 'Position', %duration }, + # '060e2b34.0101.0102.07020103.10020000' => { Name => 'EditOffsets', Type => 'Node' }, + '060e2b34.0101.0102.07020103.10020100' => { Name => 'ControlPointTime', Format => 'rational64s' }, + '060e2b34.0101.0102.07020110.01030000' => { Name => 'CreateDate', %timestamp }, + '060e2b34.0101.0102.07020110.02030000' => { Name => 'ModifyDate', %timestamp }, + '060e2b34.0101.0102.07020110.02040000' => { Name => 'ContainerLastModifyDate', %timestamp }, + '060e2b34.0101.0102.07020110.02050000' => { Name => 'PackageLastModifyDate', %timestamp }, + '060e2b34.0101.0102.07020201.01030000' => { Name => 'Duration', Type => 'Length', %duration }, + '060e2b34.0101.0102.07020201.01050100' => { Name => 'DefaultFadeDuration', Type => 'Length', %duration }, + '060e2b34.0101.0102.07020201.01050200' => { Name => 'Fade-InDuration', Type => 'Length', %duration }, + '060e2b34.0101.0102.07020201.01050300' => { Name => 'Fade-OutDuration', Type => 'Length', %duration }, + '060e2b34.0101.0102.0d010101.01010100' => { Name => 'TapeFormat', Type => 'TapeFormatType', Unknown => 1 }, + '060e2b34.0101.0103.01011001.01000000' => { Name => 'OrganizationID', Type => 'UTF-16' }, + '060e2b34.0101.0103.01011504.00000000' => { Name => 'GlobalNumber', Format => 'string' }, + '060e2b34.0101.0103.01012007.00000000' => { Name => 'DeviceIDKind', Format => 'string' }, + '060e2b34.0101.0103.01012008.00000000' => { Name => 'DeviceKind', Format => 'string' }, + '060e2b34.0101.0103.01012008.02000000' => { Name => 'DeviceKindCode', Format => 'string' }, + # '060e2b34.0101.0103.01012100.00000000' => { Name => 'PlatformIdentifiers', Type => 'Node' }, + '060e2b34.0101.0103.01012101.00000000' => { Name => 'PlatformDesignation', Format => 'string' }, + '060e2b34.0101.0103.01012102.00000000' => { Name => 'PlatformModel', Format => 'string' }, + '060e2b34.0101.0103.01012103.00000000' => { Name => 'PlatformSerialNumber', Format => 'string' }, + '060e2b34.0101.0103.01030107.00000000' => { Name => 'LocalTargetID', Format => 'string' }, + # '060e2b34.0101.0103.01030203.00000000' => { Name => 'DiskIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0103.01030203.01000000' => { Name => 'MagneticDisks', Type => 'Node' }, + '060e2b34.0101.0103.01030203.01010000' => { Name => 'MagneticDiskNumber', Format => 'string' }, + # '060e2b34.0101.0103.01030203.02000000' => { Name => 'OpticalDiscs', Type => 'Node' }, + '060e2b34.0101.0103.01030203.02010000' => { Name => 'OpticalDiscNumber', Format => 'string' }, + '060e2b34.0101.0103.01030402.00000000' => { Name => 'StreamID', Format => 'int8u' }, + '060e2b34.0101.0103.01030403.00000000' => { Name => 'TransportStreamID', Format => 'int16u' }, + # '060e2b34.0101.0103.01030500.00000000' => { Name => 'OrganizationalProgramIdentifiers', Type => 'Node' }, + '060e2b34.0101.0103.01030501.00000000' => { Name => 'OrganizationalProgramNumber', Format => 'string' }, + '060e2b34.0101.0103.01030501.01000000' => { Name => 'OrganizationalProgramNumber', Type => 'UTF-16' }, + # '060e2b34.0101.0103.01030600.00000000' => { Name => 'MetadataIdentifiers', Type => 'Node' }, + '060e2b34.0101.0103.01030601.00000000' => { Name => 'ItemDesignatorID', Type => 'SMPTE336M', Unknown => 1 }, + '060e2b34.0101.0103.01040101.01000000' => { Name => 'LocalFilePath', Type => 'UTF-16' }, + '060e2b34.0101.0103.01050101.00000000' => { Name => 'TitleKind', Type => 'UTF-16' }, + '060e2b34.0101.0103.01050201.00000000' => { Name => 'MainTitle', Type => 'UTF-16' }, + '060e2b34.0101.0103.01050301.00000000' => { Name => 'SecondaryTitle', Type => 'UTF-16' }, + '060e2b34.0101.0103.01050401.00000000' => { Name => 'SeriesNumber', Type => 'UTF-16' }, + '060e2b34.0101.0103.01050501.00000000' => { Name => 'EpisodeNumber', Type => 'UTF-16' }, + '060e2b34.0101.0103.01050601.00000000' => { Name => 'SceneNumber', Type => 'UTF-16' }, + '060e2b34.0101.0103.01050801.00000000' => { Name => 'VersionTitle', Type => 'UTF-16' }, + '060e2b34.0101.0103.01050900.00000000' => { Name => 'MissionID', Format => 'string' }, + '060e2b34.0101.0103.01050901.00000000' => { Name => 'MissionID', Type => 'UTF-16' }, + # '060e2b34.0101.0103.01100300.00000000' => { Name => 'MusicIndustryIdentifiers', Type => 'Node' }, + '060e2b34.0101.0103.01100301.00000000' => { Name => 'RecordingLabelName', Format => 'string' }, + '060e2b34.0101.0103.01100301.01000000' => { Name => 'RecordingLabelName', Type => 'UTF-16' }, + '060e2b34.0101.0103.01100302.00000000' => { Name => 'CollectionName', Format => 'string' }, + '060e2b34.0101.0103.01100302.01000000' => { Name => 'CollectionName', Type => 'UTF-16' }, + '060e2b34.0101.0103.01100303.00000000' => { Name => 'OriginCode', Format => 'string' }, + '060e2b34.0101.0103.01100304.00000000' => { Name => 'MainCatalogNumber', Format => 'string' }, + '060e2b34.0101.0103.01100305.00000000' => { Name => 'CatalogPrefixNumber', Format => 'string' }, + '060e2b34.0101.0103.01100306.00000000' => { Name => 'SideNumber', Format => 'string' }, + '060e2b34.0101.0103.01100307.00000000' => { Name => 'RecordedTrackNumber', Format => 'string' }, + '060e2b34.0101.0103.02020200.00000000' => { Name => 'SeriesinaSeriesGroupCount', Format => 'int16u' }, + '060e2b34.0101.0103.02020300.00000000' => { Name => 'ProgrammingGroupKind', Format => 'string' }, + # '060e2b34.0101.0103.02030000.00000000' => { Name => 'Purchaser', Type => 'Node' }, + '060e2b34.0101.0103.02030100.00000000' => { Name => 'PurchasingOrganizationName', Format => 'string' }, + '060e2b34.0101.0103.02030200.00000000' => { Name => 'SalesContractNumber', Format => 'string' }, + '060e2b34.0101.0103.02030400.00000000' => { Name => 'PurchasingDepartment', Format => 'string' }, + # '060e2b34.0101.0103.02040000.00000000' => { Name => 'ContractDescriptions', Type => 'Node' }, + '060e2b34.0101.0103.02040100.00000000' => { Name => 'ContractType', Format => 'string' }, + '060e2b34.0101.0103.02040101.00000000' => { Name => 'ContractTypeCode', Format => 'string' }, + '060e2b34.0101.0103.02040200.00000000' => { Name => 'ContractClauseDescription', Format => 'string' }, + '060e2b34.0101.0103.02040300.00000000' => { Name => 'ContractLineCode', Format => 'string' }, + '060e2b34.0101.0103.02040301.00000000' => { Name => 'ContractLineName', Format => 'string' }, + '060e2b34.0101.0103.02040400.00000000' => { Name => 'ContractTermsOfBusiness', Format => 'string' }, + '060e2b34.0101.0103.02040500.00000000' => { Name => 'ContractInstallmentPercentage', Format => 'float' }, + '060e2b34.0101.0103.02040600.00000000' => { Name => 'Jurisdiction', Format => 'string' }, + '060e2b34.0101.0103.02050101.01000000' => { Name => 'CopyrightStatus', Type => 'UTF-16' }, + '060e2b34.0101.0103.02050102.01000000' => { Name => 'CopyrightOwnerName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02050201.01000000' => { Name => 'IntellectualPropertyDescription', Type => 'UTF-16' }, + '060e2b34.0101.0103.02050202.01000000' => { Name => 'IntellectualPropertyRights', Type => 'UTF-16' }, + '060e2b34.0101.0103.02050301.01000000' => { Name => 'Rightsholder', Type => 'UTF-16' }, + '060e2b34.0101.0103.02050302.01000000' => { Name => 'RightsManagementAuthority', Type => 'UTF-16' }, + '060e2b34.0101.0103.02050403.00000000' => { Name => 'RightsConditionDescription', Format => 'string' }, + '060e2b34.0101.0103.02050403.01000000' => { Name => 'RightsConditionDescription', Type => 'UTF-16' }, + '060e2b34.0101.0103.02060101.01000000' => { Name => 'CurrencyName', Format => 'string' }, + '060e2b34.0101.0103.02060202.00000000' => { Name => 'TotalPayment', Format => 'string' }, + '060e2b34.0101.0103.02060203.00000000' => { Name => 'PayeeAccountName', Format => 'string' }, + '060e2b34.0101.0103.02060204.00000000' => { Name => 'PayeeAccountNumber', Format => 'string' }, + '060e2b34.0101.0103.02060205.00000000' => { Name => 'PayeeAccountSortCode', Format => 'string' }, + '060e2b34.0101.0103.02060302.00000000' => { Name => 'TotalIncome', Format => 'string' }, + '060e2b34.0101.0103.02060303.00000000' => { Name => 'PayerAccountName', Format => 'string' }, + '060e2b34.0101.0103.02060304.00000000' => { Name => 'PayerAccountNumber', Format => 'string' }, + '060e2b34.0101.0103.02060305.00000000' => { Name => 'PayerAccountSortCode', Format => 'string' }, + # '060e2b34.0101.0103.02080200.00000000' => { Name => 'Classification', Type => 'Node' }, + '060e2b34.0101.0103.02080201.00000000' => { Name => 'SecurityClassification', Format => 'string' }, + '060e2b34.0101.0103.02080202.00000000' => { Name => 'SecurityClassificationCaveats', Format => 'string' }, + '060e2b34.0101.0103.02080203.00000000' => { Name => 'ClassifiedBy', Format => 'string' }, + '060e2b34.0101.0103.02080204.00000000' => { Name => 'ClassificationReason', Format => 'string' }, + '060e2b34.0101.0103.02080205.00000000' => { Name => 'DeclassificationDate', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0103.02080206.00000000' => { Name => 'DerivedFrom', Format => 'string' }, + '060e2b34.0101.0103.02080207.00000000' => { Name => 'ClassificationComment', Format => 'string' }, + '060e2b34.0101.0103.02080208.00000000' => { Name => 'ClassificationAndMarkingSystem', Format => 'string' }, + '060e2b34.0101.0103.02100101.01010000' => { Name => 'BroadcastOrganizationName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02100101.02010000' => { Name => 'BroadcastServiceName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02100101.03020000' => { Name => 'BroadcastMediumCode', Format => 'string' }, + '060e2b34.0101.0103.02100101.04010000' => { Name => 'BroadcastRegion', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300101.01000000' => { Name => 'NatureOfPersonality', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300102.01010000' => { Name => 'ContributionStatus', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300103.01010000' => { Name => 'SupportOrAdministrationStatus', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300201.01000000' => { Name => 'OrganizationKind', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300202.01010000' => { Name => 'ProductionOrganizationRole', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300203.01010000' => { Name => 'SupportOrganizationRole', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300501.01000000' => { Name => 'JobFunctionName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300501.02000000' => { Name => 'JobFunctionCode', Format => 'string' }, + '060e2b34.0101.0103.02300502.01000000' => { Name => 'RoleName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300503.00000000' => { Name => 'JobTitle', Format => 'string' }, + '060e2b34.0101.0103.02300503.01000000' => { Name => 'JobTitle', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300601.01000000' => { Name => 'ContactKind', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300602.01000000' => { Name => 'ContactDepartmentName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300603.01010100' => { Name => 'FamilyName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300603.01020100' => { Name => 'FirstGivenName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300603.01030100' => { Name => 'SecondGivenName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300603.01040100' => { Name => 'ThirdGivenName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300603.01070000' => { Name => 'PersonDescription', Format => 'string' }, + '060e2b34.0101.0103.02300603.01070100' => { Name => 'PersonDescription', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300603.02010100' => { Name => 'MainName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300603.02020100' => { Name => 'SupplementaryName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300603.03010100' => { Name => 'OrganizationMainName', Type => 'UTF-16' }, + '060e2b34.0101.0103.02300603.03020100' => { Name => 'SupplementaryOrganizationName', Type => 'UTF-16' }, + '060e2b34.0101.0103.03010101.02000000' => { Name => 'RegionCode', Format => 'string' }, + # '060e2b34.0101.0103.03010101.10000000' => { Name => 'CountryAndRegionNames', Type => 'Node' }, + '060e2b34.0101.0103.03010101.10010000' => { Name => 'CountryName', Format => 'string' }, + '060e2b34.0101.0103.03010101.10010100' => { Name => 'CountryName', Type => 'UTF-16' }, + '060e2b34.0101.0103.03010101.10020000' => { Name => 'RegionName', Format => 'string' }, + '060e2b34.0101.0103.03010101.10020100' => { Name => 'RegionName', Type => 'UTF-16' }, + # '060e2b34.0101.0103.03010102.10000000' => { Name => 'LanguageNames', Type => 'Node' }, + '060e2b34.0101.0103.03010102.10010000' => { Name => 'LanguageName', Format => 'string' }, + '060e2b34.0101.0103.03010102.10010100' => { Name => 'LanguageName', Type => 'UTF-16' }, + '060e2b34.0101.0103.03010210.05000000' => { Name => 'TerminatingFillerData', Format => 'int8u' }, + '060e2b34.0101.0103.03010303.03000000' => { Name => 'TimingBiasCorrection', Format => 'float' }, + '060e2b34.0101.0103.03010303.04000000' => { Name => 'TimingBiasCorrectionDescription', Format => 'string' }, + '060e2b34.0101.0103.03020101.03010000' => { Name => 'Genre', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020101.04010000' => { Name => 'TargetAudience', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020101.10000000' => { Name => 'ProgramMaterialClassificationCode', Format => 'string' }, + '060e2b34.0101.0103.03020102.03010000' => { Name => 'Theme', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020102.04010000' => { Name => 'SubjectName', Format => 'string' }, + '060e2b34.0101.0103.03020102.04020000' => { Name => 'SubjectName', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020102.05010000' => { Name => 'Keywords', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020102.0f000000' => { Name => 'KeyFrameSampleCount', Format => 'int32u' }, + '060e2b34.0101.0103.03020106.01010000' => { Name => 'Abstract', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020106.02010000' => { Name => 'Purpose', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020106.03010000' => { Name => 'Description', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020106.04010000' => { Name => 'ColorDescriptor', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020106.05010000' => { Name => 'FormatDescriptor', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020106.06000000' => { Name => 'IntentDescriptor', Format => 'string' }, + '060e2b34.0101.0103.03020106.06010000' => { Name => 'IntentDescriptor', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020106.07000000' => { Name => 'TextualDescriptionKind', Format => 'string' }, + '060e2b34.0101.0103.03020106.07010000' => { Name => 'TextualDescriptionKind', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020201.03000000' => { Name => 'FestivalName', Format => 'string' }, + '060e2b34.0101.0103.03020301.02020000' => { Name => 'ObjectDescriptionCode', Format => 'string' }, + # '060e2b34.0101.0103.03020302.00000000' => { Name => 'GeneralComments', Type => 'Node' }, + '060e2b34.0101.0103.03020302.01000000' => { Name => 'DescriptionKind', Format => 'string' }, + '060e2b34.0101.0103.03020302.01010000' => { Name => 'DescriptionKind', Type => 'UTF-16' }, + '060e2b34.0101.0103.03020302.02000000' => { Name => 'DescriptiveComment', Format => 'string' }, + '060e2b34.0101.0103.03020302.02010000' => { Name => 'DescriptiveComment', Type => 'UTF-16' }, + # '060e2b34.0101.0103.03020401.00000000' => { Name => 'ObjectNames', Type => 'Node' }, + # '060e2b34.0101.0103.03020600.00000000' => { Name => 'Human-AssignedContextDescriptions', Type => 'Node' }, + '060e2b34.0101.0103.03020601.00000000' => { Name => 'ContextDescription', Format => 'string' }, + '060e2b34.0101.0103.03030102.06010000' => { Name => 'ComputedKeywords', Type => 'UTF-16' }, + '060e2b34.0101.0103.03030301.04000000' => { Name => 'ObjectIdentificationConfidence', Format => 'int16u' }, + '060e2b34.0101.0103.03030301.05000000' => { Name => 'ObjectHorizontalAverageDimension', Format => 'int32u' }, + '060e2b34.0101.0103.03030301.06000000' => { Name => 'ObjectVerticalAverageDimension', Format => 'int32u' }, + '060e2b34.0101.0103.03030301.07000000' => { Name => 'ObjectAreaDimension', Format => 'int32u' }, + '060e2b34.0101.0103.04010101.04000000' => { Name => 'HorizontalActionSafePercentage', Format => 'float' }, + '060e2b34.0101.0103.04010101.05000000' => { Name => 'VerticalActionSafePercentage', Format => 'float' }, + '060e2b34.0101.0103.04010101.06000000' => { Name => 'HorizontalGraphicsSafePercentage', Format => 'float' }, + '060e2b34.0101.0103.04010101.07000000' => { Name => 'VerticalGraphicsSafePercentage', Format => 'float' }, + '060e2b34.0101.0103.04010101.08000000' => { Name => 'PerceivedDisplayFormatName', Format => 'string' }, + '060e2b34.0101.0103.04010101.08010000' => { Name => 'PerceivedDisplayFormatCode', Format => 'string' }, + '060e2b34.0101.0103.04010201.01050000' => { Name => 'VideoColorKind', Format => 'string' }, + '060e2b34.0101.0103.04010301.07000000' => { Name => 'PictureDisplayRate', Format => 'int16u' }, + '060e2b34.0101.0103.04010501.11000000' => { Name => 'VideoAverageBitrate', Format => 'float', PrintConv => 'ConvertBitrate($val)' }, + '060e2b34.0101.0103.04010501.12000000' => { Name => 'VideoFixedBitrate', Type => 'Boolean' }, + # '060e2b34.0101.0103.04010b00.00000000' => { Name => 'VideoFileFormats', Type => 'Node' }, + '060e2b34.0101.0103.04010b01.00000000' => { Name => 'DigitalVideoFileFormat', Format => 'string' }, + '060e2b34.0101.0103.04020401.01000000' => { Name => 'CodingLawName', Format => 'string' }, + '060e2b34.0101.0103.04020402.01000000' => { Name => 'AudioCodingSchemeCode', Format => 'string', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0103.04020402.02000000' => { Name => 'AudioCodingSchemeName', Format => 'string', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0103.04030301.00000000' => { Name => 'DigitalEncodingBitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)' }, + '060e2b34.0101.0103.04030302.00000000' => { Name => 'DataEssenceCodingID', Type => 'AUID', Unknown => 1 }, + # '060e2b34.0101.0103.040f0000.00000000' => { Name => 'StorageCharacteristics', Type => 'Node' }, + '060e2b34.0101.0103.040f0100.00000000' => { Name => 'StorageKind', Format => 'string' }, + '060e2b34.0101.0103.040f0101.00000000' => { Name => 'StorageKind', Type => 'UTF-16' }, + '060e2b34.0101.0103.040f0102.00000000' => { Name => 'StorageKindCode', Format => 'string' }, + # '060e2b34.0101.0103.04100101.10000000' => { Name => 'TapeMediumFundamentalParameters', Type => 'Node' }, + '060e2b34.0101.0103.04100101.10010000' => { Name => 'TapePartitionCapacity', Format => 'int64u' }, + # '060e2b34.0101.0103.04100102.01000000' => { Name => 'DiscMediumFundamentalParameters', Type => 'Node' }, + '060e2b34.0101.0103.04100102.02000000' => { Name => 'DiscPartitionCapacity', Format => 'int64u' }, + '060e2b34.0101.0103.04200201.01040100' => { Name => 'FocalLength', Format => 'int32u', ValueConv => '$val/10' }, + '060e2b34.0101.0103.04200201.01080100' => { Name => 'FieldOfViewHorizontal', Format => 'int16u', ValueConv => '$val/10' }, + '060e2b34.0101.0103.04200201.010a0000' => { Name => 'FieldOfViewVertical', Format => 'int16u', ValueConv => '$val/10' }, + # '060e2b34.0101.0103.04300000.00000000' => { Name => 'SystemCharacteristics', Type => 'Node' }, + '060e2b34.0101.0103.04300100.00000000' => { Name => 'SystemNameOrNumber', Format => 'string' }, + '060e2b34.0101.0103.05010104.00000000' => { Name => 'LogoFlag', Type => 'Boolean' }, + '060e2b34.0101.0103.05010106.00000000' => { Name => 'GraphicKind', Format => 'string' }, + '060e2b34.0101.0103.05010107.00000000' => { Name => 'GraphicUsageKind', Format => 'string' }, + '060e2b34.0101.0103.05010401.00000000' => { Name => 'SignatureTuneFlag', Type => 'Boolean' }, + '060e2b34.0101.0103.05010402.00000000' => { Name => 'BackgroundMusicFlag', Type => 'Boolean' }, + # '060e2b34.0101.0103.06030000.00000000' => { Name => 'RelatedProductionContent', Type => 'Node' }, + # '060e2b34.0101.0103.06030500.00000000' => { Name => 'RelatedTextualContent', Type => 'Node' }, + '060e2b34.0101.0103.06030501.00000000' => { Name => 'ProductionScriptReference', Format => 'string' }, + '060e2b34.0101.0103.06030501.01000000' => { Name => 'ProductionScriptReference', Type => 'UTF-16' }, + '060e2b34.0101.0103.06030502.00000000' => { Name => 'TranscriptReference', Format => 'string' }, + '060e2b34.0101.0103.06030502.01000000' => { Name => 'TranscriptReference', Type => 'UTF-16' }, + '060e2b34.0101.0103.07010103.00000000' => { Name => 'HorizontalDatum', Format => 'string' }, + '060e2b34.0101.0103.07010104.00000000' => { Name => 'VerticalDatum', Format => 'string' }, + '060e2b34.0101.0103.07010201.02040200' => { Name => 'DeviceLatitude', Format => 'double', %geoLat }, + '060e2b34.0101.0103.07010201.02060200' => { Name => 'DeviceLongitude', Format => 'double', %geoLon }, + '060e2b34.0101.0103.07010201.03020200' => { Name => 'FrameCenterLatitude', Format => 'double', %geoLat }, + '060e2b34.0101.0103.07010201.03040200' => { Name => 'FrameCenterLongitude', Format => 'double', %geoLon }, + '060e2b34.0101.0103.07010201.03070000' => { Name => 'CornerLatitudePoint1', Format => 'string', %geoLat, ValueConv => \&ConvLatLon }, + '060e2b34.0101.0103.07010201.03070100' => { Name => 'CornerLatitudePoint1', Format => 'double', %geoLat }, + '060e2b34.0101.0103.07010201.03080000' => { Name => 'CornerLatitudePoint2', Format => 'string', %geoLat }, + '060e2b34.0101.0103.07010201.03080100' => { Name => 'CornerLatitudePoint2', Format => 'double', %geoLat }, + '060e2b34.0101.0103.07010201.03090000' => { Name => 'CornerLatitudePoint3', Format => 'string', %geoLat }, + '060e2b34.0101.0103.07010201.03090100' => { Name => 'CornerLatitudePoint3', Format => 'double', %geoLat }, + '060e2b34.0101.0103.07010201.030a0000' => { Name => 'CornerLatitudePoint4', Format => 'string' }, + '060e2b34.0101.0103.07010201.030a0100' => { Name => 'CornerLatitudePoint4', Format => 'double', %geoLat }, + '060e2b34.0101.0103.07010201.030b0000' => { Name => 'CornerLongitudePoint1', Format => 'string' }, + '060e2b34.0101.0103.07010201.030b0100' => { Name => 'CornerLongitudePoint1', Format => 'double', %geoLon }, + '060e2b34.0101.0103.07010201.030c0000' => { Name => 'CornerLongitudePoint2', Format => 'string' }, + '060e2b34.0101.0103.07010201.030c0100' => { Name => 'CornerLongitudePoint2', Format => 'double', %geoLon }, + '060e2b34.0101.0103.07010201.030d0000' => { Name => 'CornerLongitudePoint3', Format => 'string' }, + '060e2b34.0101.0103.07010201.030d0100' => { Name => 'CornerLongitudePoint3', Format => 'double', %geoLon }, + '060e2b34.0101.0103.07010201.030e0000' => { Name => 'CornerLongitudePoint4', Format => 'string' }, + '060e2b34.0101.0103.07010201.030e0100' => { Name => 'CornerLongitudePoint4', Format => 'double', %geoLon }, + '060e2b34.0101.0103.07010801.02000000' => { Name => 'SubjectDistance', Format => 'float' }, + '060e2b34.0101.0103.07012001.01010100' => { Name => 'PlaceKeyword', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.02010100' => { Name => 'ObjectCountryCode', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.02060000' => { Name => 'ObjectCountryCodeMethod', Format => 'string' }, + '060e2b34.0101.0103.07012001.02070000' => { Name => 'CountryCodeMethod', Format => 'string' }, + '060e2b34.0101.0103.07012001.02080000' => { Name => 'Non-USClassifyingCountryCode', Format => 'string' }, + '060e2b34.0101.0103.07012001.02090000' => { Name => 'ReleasableCountryCode', Format => 'string' }, + '060e2b34.0101.0103.07012001.03010100' => { Name => 'ObjectRegionName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.03020100' => { Name => 'ShootingRegionName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.03030100' => { Name => 'SettingRegionName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.03040100' => { Name => 'CopyrightLicenseRegionName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.03050100' => { Name => 'IntellectualPropertyLicenseRegionName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04010101' => { Name => 'RoomNumber', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04010201' => { Name => 'StreetNumber', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04010301' => { Name => 'StreetName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04010401' => { Name => 'PostalTown', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04010501' => { Name => 'CityName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04010601' => { Name => 'StateOrProvinceOrCountyName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04010701' => { Name => 'PostalCode', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04010801' => { Name => 'CountryName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04020101' => { Name => 'SettingRoomNumber', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04020201' => { Name => 'SettingStreetNumberOrBuildingName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04020301' => { Name => 'SettingStreetName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04020401' => { Name => 'SettingTownName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04020501' => { Name => 'SettingCityName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04020601' => { Name => 'SettingStateOrProvinceOrCountyName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04020701' => { Name => 'SettingPostalCode', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.04020801' => { Name => 'SettingCountryName', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.10030101' => { Name => 'TelephoneNumber', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.10030201' => { Name => 'FaxNumber', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012001.10030301' => { Name => 'E-mailAddress', Type => 'UTF-16' }, + '060e2b34.0101.0103.07012002.01010000' => { Name => 'SettingDescription', Type => 'UTF-16' }, + '060e2b34.0101.0103.07020101.01050000' => { Name => 'POSIXMicroseconds', Format => 'int64u' }, + # '060e2b34.0101.0103.07020103.10030000' => { Name => 'EventOffsets', Type => 'Node' }, + '060e2b34.0101.0103.07020103.10030100' => { Name => 'EventElapsedTimeToStart', Format => 'string' }, + '060e2b34.0101.0103.07020103.10030200' => { Name => 'EventElapsedTimeToEnd', Format => 'string' }, + '060e2b34.0101.0104.01011002.00000000' => { Name => 'OrganizationIDKind', Format => 'string' }, + '060e2b34.0101.0104.01011002.01000000' => { Name => 'OrganizationIDKind', Type => 'UTF-16' }, + # '060e2b34.0101.0104.01020200.00000000' => { Name => 'RegistryLocators', Type => 'Node' }, + '060e2b34.0101.0104.01020201.00000000' => { Name => 'SMPTEUL', Type => 'UL', Unknown => 1 }, + # '060e2b34.0101.0104.01020210.00000000' => { Name => 'RegistryLocatorGroups', Type => 'Node' }, + # '060e2b34.0101.0104.01020210.01000000' => { Name => 'RegistryLocatorOrderedGroup', Type => 'Node' }, + '060e2b34.0101.0104.01020210.01010000' => { Name => 'EssenceContainerArray', Type => 'Array of UL', Unknown => 1 }, + '060e2b34.0101.0104.01030404.00000000' => { Name => 'EssenceStreamID', Format => 'int32u' }, + '060e2b34.0101.0104.01030405.00000000' => { Name => 'IndexStreamID', Format => 'int32u' }, + '060e2b34.0101.0104.01050a00.00000000' => { Name => 'WorkingTitle', Format => 'string' }, + '060e2b34.0101.0104.01050a01.00000000' => { Name => 'WorkingTitle', Type => 'UTF-16' }, + '060e2b34.0101.0104.01050b00.00000000' => { Name => 'OriginalTitle', Format => 'string' }, + '060e2b34.0101.0104.01050b01.00000000' => { Name => 'OriginalTitle', Type => 'UTF-16' }, + '060e2b34.0101.0104.01050c00.00000000' => { Name => 'ClipNumber', Format => 'string' }, + '060e2b34.0101.0104.01050c01.00000000' => { Name => 'ClipNumber', Type => 'UTF-16' }, + '060e2b34.0101.0104.01070105.00000000' => { Name => 'DescriptiveMetadataTrackIDs', Format => 'int32u' }, + # '060e2b34.0101.0104.01080000.00000000' => { Name => 'GenericIdentifiers', Type => 'Node' }, + '060e2b34.0101.0104.01080100.00000000' => { Name => 'IdentifierKind', Format => 'string' }, + '060e2b34.0101.0104.01080200.00000000' => { Name => 'IdentifierValue', Format => 'int8u' }, + # '060e2b34.0101.0104.010a0200.00000000' => { Name => 'GeneralOrganizationIdentifiers', Type => 'Node' }, + '060e2b34.0101.0104.010a0201.00000000' => { Name => 'OrganizationCode', Format => 'string' }, + '060e2b34.0101.0104.010a0201.01000000' => { Name => 'OrganizationCode', Type => 'UTF-16' }, + '060e2b34.0101.0104.02010500.00000000' => { Name => 'SupplierIdentificationKind', Format => 'string' }, + '060e2b34.0101.0104.02010600.00000000' => { Name => 'SupplierIdentificationValue', Format => 'string' }, + '060e2b34.0101.0104.02010700.00000000' => { Name => 'SupplierAccountNumber', Format => 'string' }, + '060e2b34.0101.0104.02010800.00000000' => { Name => 'SupplierAccountName', Format => 'string' }, + '060e2b34.0101.0104.02010801.00000000' => { Name => 'SupplierAccountName', Type => 'UTF-16' }, + '060e2b34.0101.0104.02020400.00000000' => { Name => 'EpisodeStartNumber', Format => 'int16u' }, + '060e2b34.0101.0104.02020500.00000000' => { Name => 'EpisodeEndNumber', Format => 'int16u' }, + '060e2b34.0101.0104.02030500.00000000' => { Name => 'PurchaserIdentificationKind', Format => 'string' }, + '060e2b34.0101.0104.02030600.00000000' => { Name => 'PurchaserIdentificationValue', Format => 'string' }, + '060e2b34.0101.0104.02030700.00000000' => { Name => 'PurchaserAccountNumber', Format => 'string' }, + '060e2b34.0101.0104.02030800.00000000' => { Name => 'PurchaserAccountName', Format => 'string' }, + '060e2b34.0101.0104.02030801.00000000' => { Name => 'PurchaserAccountName', Type => 'UTF-16' }, + '060e2b34.0101.0104.02040102.00000000' => { Name => 'ContractType', Type => 'UTF-16' }, + '060e2b34.0101.0104.02040201.00000000' => { Name => 'ContractClauseDescription', Type => 'UTF-16' }, + '060e2b34.0101.0104.02040302.00000000' => { Name => 'ContractLineName', Type => 'UTF-16' }, + '060e2b34.0101.0104.02040401.00000000' => { Name => 'ContractTermsOfBusiness', Type => 'UTF-16' }, + '060e2b34.0101.0104.02040601.00000000' => { Name => 'Jurisdiction', Type => 'UTF-16' }, + '060e2b34.0101.0104.02060102.00000000' => { Name => 'TotalCurrencyAmount', Format => 'double' }, + '060e2b34.0101.0104.02060103.00000000' => { Name => 'InstallmentNumber', Format => 'int16u' }, + # '060e2b34.0101.0104.020a0000.00000000' => { Name => 'IdentifiersAndLocatorsAdministrationAuthorities', Type => 'Node' }, + '060e2b34.0101.0104.020a0100.00000000' => { Name => 'IdentifierIssuingAuthority', Format => 'string' }, + # '060e2b34.0101.0104.02100200.00000000' => { Name => 'Publication', Type => 'Node' }, + # '060e2b34.0101.0104.02100201.00000000' => { Name => 'GeneralPublication', Type => 'Node' }, + '060e2b34.0101.0104.02100201.01000000' => { Name => 'PublishingOrganizationName', Format => 'string' }, + '060e2b34.0101.0104.02100201.01010000' => { Name => 'PublishingOrganizationName', Type => 'UTF-16' }, + '060e2b34.0101.0104.02100201.02000000' => { Name => 'PublishingServiceName', Format => 'string' }, + '060e2b34.0101.0104.02100201.02010000' => { Name => 'PublishingServiceName', Type => 'UTF-16' }, + '060e2b34.0101.0104.02100201.03000000' => { Name => 'PublishingMediumName', Format => 'string' }, + '060e2b34.0101.0104.02100201.03010000' => { Name => 'PublishingMediumName', Type => 'UTF-16' }, + '060e2b34.0101.0104.02100201.04000000' => { Name => 'PublishingRegionName', Format => 'string' }, + '060e2b34.0101.0104.02100201.04010000' => { Name => 'PublishingRegionName', Type => 'UTF-16' }, + '060e2b34.0101.0104.02300603.01050100' => { Name => 'Salutation', Type => 'UTF-16' }, + '060e2b34.0101.0104.02300603.01060100' => { Name => 'HonorsAndQualifications', Type => 'UTF-16' }, + '060e2b34.0101.0104.02300603.01080000' => { Name => 'OtherGivenNames', Format => 'string' }, + '060e2b34.0101.0104.02300603.01080100' => { Name => 'OtherGivenNames', Type => 'UTF-16' }, + '060e2b34.0101.0104.02300603.01090000' => { Name => 'AlternateName', Format => 'string' }, + '060e2b34.0101.0104.02300603.01090100' => { Name => 'AlternateName', Type => 'UTF-16' }, + # '060e2b34.0101.0104.03010102.02000000' => { Name => 'TextLanguageCodes', Type => 'Node' }, + '060e2b34.0101.0104.03010102.02010000' => { Name => 'ISO639TextLanguageCode', Format => 'string', LanguageCode => 1 }, + '060e2b34.0101.0104.03010102.02020000' => { Name => 'ISO639CaptionsLanguageCode', Format => 'string' }, + # '060e2b34.0101.0104.03010102.03000000' => { Name => 'SpokenLanguageCodes', Type => 'Node' }, + '060e2b34.0101.0104.03010102.03010000' => { Name => 'PrimarySpokenLanguageCode', Format => 'string' }, + '060e2b34.0101.0104.03010102.03020000' => { Name => 'SecondarySpokenLanguageCode', Format => 'string' }, + '060e2b34.0101.0104.03010102.03030000' => { Name => 'PrimaryOriginalLanguageCode', Format => 'string' }, + '060e2b34.0101.0104.03010102.03040000' => { Name => 'SecondaryOriginalLanguageCode', Format => 'string' }, + '060e2b34.0101.0104.03010201.06000000' => { Name => 'MajorVersion', Format => 'int16u' }, + '060e2b34.0101.0104.03010201.07000000' => { Name => 'MinorVersion', Format => 'int16u' }, + '060e2b34.0101.0104.03010201.08000000' => { Name => 'SectorSize', Format => 'int32u' }, + '060e2b34.0101.0104.03010203.09000000' => { Name => 'ElementLength', Format => 'int32u' }, + '060e2b34.0101.0104.03020102.02010000' => { Name => 'ThesaurusName', Type => 'UTF-16' }, + '060e2b34.0101.0104.03020102.0d010000' => { Name => 'Cue-InWords', Type => 'UTF-16' }, + '060e2b34.0101.0104.03020102.0e010000' => { Name => 'Cue-OutWords', Type => 'UTF-16' }, + '060e2b34.0101.0104.03020102.10000000' => { Name => 'KeypointKind', Format => 'string' }, + '060e2b34.0101.0104.03020102.10010000' => { Name => 'KeypointKind', Type => 'UTF-16' }, + '060e2b34.0101.0104.03020102.11000000' => { Name => 'KeypointValue', Format => 'string' }, + '060e2b34.0101.0104.03020102.11010000' => { Name => 'KeypointValue', Type => 'UTF-16' }, + '060e2b34.0101.0104.03020201.03010000' => { Name => 'FestivalName', Type => 'UTF-16' }, + '060e2b34.0101.0104.03020201.04000000' => { Name => 'AwardName', Format => 'string' }, + '060e2b34.0101.0104.03020201.04010000' => { Name => 'AwardName', Type => 'UTF-16' }, + '060e2b34.0101.0104.03020201.05000000' => { Name => 'AwardCategory', Format => 'string' }, + '060e2b34.0101.0104.03020201.05010000' => { Name => 'AwardCategory', Type => 'UTF-16' }, + '060e2b34.0101.0104.03020503.00000000' => { Name => 'SlateInformation', Type => 'UTF-16' }, + '060e2b34.0101.0104.04020301.04000000' => { Name => 'LockedIndicator', Type => 'Boolean' }, + '060e2b34.0101.0104.04020303.04000000' => { Name => 'BitsPerAudioSample', Format => 'int32u', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0104.04030101.00000000' => { Name => 'CaptionKind', Format => 'string' }, + '060e2b34.0101.0104.04030101.01000000' => { Name => 'CaptionKind', Type => 'UTF-16' }, + # '060e2b34.0101.0104.04040400.00000000' => { Name => 'IndexingMetadataCodingCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0104.04040401.00000000' => { Name => 'IntraEditUnitIndexing', Type => 'Node' }, + '060e2b34.0101.0104.04040401.01000000' => { Name => 'SliceCount', Format => 'int8u' }, + '060e2b34.0101.0104.04040401.02000000' => { Name => 'SliceNumber', Format => 'int8u' }, + '060e2b34.0101.0104.04040401.03000000' => { Name => 'ElementDelta', Format => 'int32u' }, + '060e2b34.0101.0104.04040401.04000000' => { Name => 'PositionTableIndexing', Format => 'int8s' }, + '060e2b34.0101.0104.04040401.05000000' => { Name => 'SliceOffsetList', Type => 'UInt32Array', Unknown => 1 }, + '060e2b34.0101.0104.04040401.08000000' => { Name => 'PosTableArray', Unknown => 1 }, + # '060e2b34.0101.0104.04040402.00000000' => { Name => 'InterEditUnitIndexing', Type => 'Node' }, + '060e2b34.0101.0104.04040402.01000000' => { Name => 'StreamOffset', Format => 'int64u' }, + '060e2b34.0101.0104.04040402.02000000' => { Name => 'EditUnitFlags', Format => 'int8u' }, + '060e2b34.0101.0104.04040402.03000000' => { Name => 'TemporalOffset', Format => 'int8s' }, + '060e2b34.0101.0104.04040402.04000000' => { Name => 'AnchorOffset', Format => 'int8s' }, + # '060e2b34.0101.0104.04060200.00000000' => { Name => 'GeneralEssenceContainerCharacteristics', Type => 'Node' }, + '060e2b34.0101.0104.04060201.00000000' => { Name => 'EditUnitLength', Format => 'int32u' }, + # '060e2b34.0101.0104.04060800.00000000' => { Name => 'GeneralMetadataCodingCharacteristics', Type => 'Node' }, + # '060e2b34.0101.0104.04060900.00000000' => { Name => 'GeneralMetadataContainerCharacteristics', Type => 'Node' }, + '060e2b34.0101.0104.04060901.00000000' => { Name => 'HeaderByteCount', Format => 'int64u' }, + '060e2b34.0101.0104.04060902.00000000' => { Name => 'IndexByteCount', Format => 'int64u' }, + # '060e2b34.0101.0104.04061000.00000000' => { Name => 'GeneralDataCodingCharacteristics', Type => 'Node' }, + '060e2b34.0101.0104.04061001.00000000' => { Name => 'PackLength', Format => 'int32u' }, + # '060e2b34.0101.0104.04200102.00000000' => { Name => 'ImagerCharacteristics', Type => 'Node' }, + '060e2b34.0101.0104.05010101.01000000' => { Name => 'IntegrationIndication', Type => 'UTF-16' }, + '060e2b34.0101.0104.05010102.01000000' => { Name => 'EventIndication', Type => 'UTF-16' }, + '060e2b34.0101.0104.05010107.01000000' => { Name => 'GraphicUsageKind', Type => 'UTF-16' }, + '060e2b34.0101.0104.06010104.01080000' => { Name => 'PrimaryPackage', Type => 'WeakReference', Unknown => 1 }, + # '060e2b34.0101.0104.06010104.02400000' => { Name => 'StrongReferencingToDescriptiveMetadataSets', Type => 'Node' }, + '060e2b34.0101.0104.06010104.03030000' => { Name => 'DescriptiveMetadataSets', Type => 'WeakReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0104.06010104.04020000' => { Name => 'DescriptiveMetadataSetReferences', Type => 'WeakReferenceArray', Unknown => 1 }, + # '060e2b34.0101.0104.06010104.05400000' => { Name => 'StrongReferencesToDescriptiveMetadataBatches', Type => 'Node' }, + '060e2b34.0101.0104.06010104.060b0000' => { Name => 'FileDescriptors', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0104.06080102.01010000' => { Name => 'StreamPositionIndicator', Format => 'int16u' }, + '060e2b34.0101.0104.06080102.01020000' => { Name => 'StreamPositionIndicator', Format => 'int32u' }, + '060e2b34.0101.0104.06080102.01030000' => { Name => 'StreamPositionIndicator', Format => 'int64u' }, + '060e2b34.0101.0104.06080202.01000000' => { Name => 'OffsetToMetadata', Format => 'int32s' }, + '060e2b34.0101.0104.06080202.01010000' => { Name => 'OffsetToMetadata', Format => 'int64s' }, + '060e2b34.0101.0104.06080202.02000000' => { Name => 'OffsetToIndexTable', Format => 'int32s' }, + '060e2b34.0101.0104.06080202.02010000' => { Name => 'OffsetToIndexTable', Format => 'int64s' }, + # '060e2b34.0101.0104.06090000.00000000' => { Name => 'DataRelationships', Type => 'Node' }, + # '060e2b34.0101.0104.06090200.00000000' => { Name => 'LocalDataRelationships', Type => 'Node' }, + # '060e2b34.0101.0104.06090201.00000000' => { Name => 'DataOffsets', Type => 'Node' }, + '060e2b34.0101.0104.06090201.01000000' => { Name => 'ByteOffset', Format => 'int64u' }, + '060e2b34.0101.0104.06090201.02000000' => { Name => 'ReversePlay', Format => 'int64u' }, + # '060e2b34.0101.0104.06101000.00000000' => { Name => 'RelativeNumericalSequences', Type => 'Node' }, + '060e2b34.0101.0104.06101001.00000000' => { Name => 'FirstNumberInSequence', Format => 'int32u' }, + '060e2b34.0101.0104.06101001.01000000' => { Name => 'FirstNumberInSequence', Format => 'int64u' }, + '060e2b34.0101.0104.06101002.00000000' => { Name => 'PreviousNumberInSequence', Format => 'int32u' }, + '060e2b34.0101.0104.06101002.01000000' => { Name => 'PreviousNumberInSequence', Format => 'int64u' }, + '060e2b34.0101.0104.06101003.00000000' => { Name => 'CurrentNumberInSequence', Format => 'int32u' }, + '060e2b34.0101.0104.06101003.01000000' => { Name => 'CurrentNumberInSequence', Format => 'int64u' }, + '060e2b34.0101.0104.06101004.00000000' => { Name => 'NextNumberInSequence', Format => 'int32u' }, + '060e2b34.0101.0104.06101004.01000000' => { Name => 'NextNumberInSequence', Format => 'int64u' }, + '060e2b34.0101.0104.06101005.00000000' => { Name => 'LastNumberInSequence', Format => 'int32u' }, + '060e2b34.0101.0104.06101005.01000000' => { Name => 'LastNumberInSequence', Format => 'int64u' }, + # '060e2b34.0101.0104.07012001.10000000' => { Name => 'ElectronicAddressVarieties', Type => 'Node' }, + '060e2b34.0101.0104.07012001.10030400' => { Name => 'CentralTelephoneNumber', Format => 'string' }, + '060e2b34.0101.0104.07012001.10030500' => { Name => 'MobileTelephoneNumber', Format => 'string' }, + '060e2b34.0101.0104.07012001.10030600' => { Name => 'URL', Format => 'string' }, + '060e2b34.0101.0104.07012002.02000000' => { Name => 'LocationDescription', Format => 'string' }, + '060e2b34.0101.0104.07012002.02010000' => { Name => 'LocationDescription', Type => 'UTF-16' }, + '060e2b34.0101.0104.07012002.03000000' => { Name => 'LocationKind', Format => 'string' }, + '060e2b34.0101.0104.07012002.03010000' => { Name => 'LocationKind', Type => 'UTF-16' }, + '060e2b34.0101.0104.07020102.07010100' => { Name => 'UTCEventStartDateTime', %timestamp }, + '060e2b34.0101.0104.07020102.07020100' => { Name => 'LocalEventStartDateTime', %timestamp }, + '060e2b34.0101.0104.07020102.09010100' => { Name => 'UTCEventEndDateTime', %timestamp }, + '060e2b34.0101.0104.07020102.09020100' => { Name => 'LocalEventEndDateTime', %timestamp }, + '060e2b34.0101.0104.07020103.01070000' => { Name => 'KeyTimePoint', Format => 'int64u' }, + '060e2b34.0101.0104.07020108.01010000' => { Name => 'TimePeriodName', Type => 'UTF-16' }, + '060e2b34.0101.0104.07020108.02000000' => { Name => 'SettingDateTime', %timestamp }, + # '060e2b34.0101.0104.07020120.00000000' => { Name => 'AdministrativeDateTime', Type => 'Node' }, + '060e2b34.0101.0104.07020120.01000000' => { Name => 'ContractDateTime', %timestamp }, + '060e2b34.0101.0104.07020120.02000000' => { Name => 'RightsStartDateTime', %timestamp }, + '060e2b34.0101.0104.07020120.03000000' => { Name => 'RightsStopDateTime', %timestamp }, + '060e2b34.0101.0104.07020120.04000000' => { Name => 'PaymentDueDateTime', %timestamp }, + # '060e2b34.0101.0105.01010500.00000000' => { Name => 'UMIDPicture', Type => 'Node' }, + # '060e2b34.0101.0105.01010600.00000000' => { Name => 'UMIDMultiPicture', Type => 'Node' }, + # '060e2b34.0101.0105.01010800.00000000' => { Name => 'UMIDSound', Type => 'Node' }, + # '060e2b34.0101.0105.01010900.00000000' => { Name => 'UMIDMultiSound', Type => 'Node' }, + # '060e2b34.0101.0105.01010b00.00000000' => { Name => 'UMIDSingleData', Type => 'Node' }, + # '060e2b34.0101.0105.01010c00.00000000' => { Name => 'UMIDMultiData', Type => 'Node' }, + # '060e2b34.0101.0105.01010d00.00000000' => { Name => 'UMIDMixed', Type => 'Node' }, + # '060e2b34.0101.0105.01010f00.00000000' => { Name => 'UMIDGeneral', Type => 'Node' }, + '060e2b34.0101.0105.01011508.00000000' => { Name => 'ClipID', Type => 'UMID' }, + '060e2b34.0101.0105.01012008.01000000' => { Name => 'DeviceKind', Type => 'UTF-16' }, + '060e2b34.0101.0105.0101200c.00000000' => { Name => 'DeviceAssetNumber', Format => 'string' }, + '060e2b34.0101.0105.01020202.00000000' => { Name => 'IdentificationUL', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.0105.01020203.00000000' => { Name => 'OperationalPatternUL', Type => 'UL', Unknown => 1 }, + # '060e2b34.0101.0105.01020210.02000000' => { Name => 'RegistryLocatorUnorderedGroups', Type => 'Node' }, + '060e2b34.0101.0105.01020210.02010000' => { Name => 'EssenceContainers', Type => 'BatchOfUL', Unknown => 1 }, + '060e2b34.0101.0105.01020210.02020000' => { Name => 'DescriptiveMetadataSchemes', Type => 'BatchOfUL', Unknown => 1 }, + '060e2b34.0101.0105.01030108.00000000' => { Name => 'ProjectName', Format => 'string' }, + '060e2b34.0101.0105.01030108.01000000' => { Name => 'ProjectName', Type => 'UTF-16' }, + '060e2b34.0101.0105.01030602.00000000' => { Name => 'LocalTagValue', Format => 'int16u' }, + '060e2b34.0101.0105.01030603.00000000' => { Name => 'LocalTagUniqueID', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0105.01050d00.00000000' => { Name => 'BrandMainTitle', Format => 'string' }, + '060e2b34.0101.0105.01050d01.00000000' => { Name => 'BrandMainTitle', Type => 'UTF-16' }, + '060e2b34.0101.0105.01050e00.00000000' => { Name => 'BrandOriginalTitle', Format => 'string' }, + '060e2b34.0101.0105.01050e01.00000000' => { Name => 'BrandOriginalTitle', Type => 'UTF-16' }, + '060e2b34.0101.0105.01050f00.00000000' => { Name => 'FrameworkTitle', Format => 'string' }, + '060e2b34.0101.0105.01050f01.00000000' => { Name => 'FrameworkTitle', Type => 'UTF-16' }, + '060e2b34.0101.0105.01070106.00000000' => { Name => 'SourceTrackIDs', Format => 'int32u' }, + '060e2b34.0101.0105.01070107.00000000' => { Name => 'ShotTrackIDs', Format => 'int32u' }, + '060e2b34.0101.0105.02020301.00000000' => { Name => 'ProgrammingGroupKind', Type => 'UTF-16' }, + '060e2b34.0101.0105.02020600.00000000' => { Name => 'ProgrammingGroupTitle', Format => 'string' }, + '060e2b34.0101.0105.02020601.00000000' => { Name => 'ProgrammingGroupTitle', Type => 'UTF-16' }, + '060e2b34.0101.0105.020a0101.00000000' => { Name => 'IdentifierIssuingAuthority', Type => 'UTF-16' }, + '060e2b34.0101.0105.02300603.010a0000' => { Name => 'LinkingName', Format => 'string' }, + '060e2b34.0101.0105.02300603.010a0100' => { Name => 'LinkingName', Type => 'UTF-16' }, + '060e2b34.0101.0105.02300603.010b0000' => { Name => 'NameSuffix', Format => 'string' }, + '060e2b34.0101.0105.02300603.010b0100' => { Name => 'NameSuffix', Type => 'UTF-16' }, + '060e2b34.0101.0105.02300603.010c0000' => { Name => 'FormerFamilyName', Format => 'string' }, + '060e2b34.0101.0105.02300603.010c0100' => { Name => 'FormerFamilyName', Type => 'UTF-16' }, + '060e2b34.0101.0105.02300603.010d0000' => { Name => 'Nationality', Format => 'string' }, + '060e2b34.0101.0105.02300603.010d0100' => { Name => 'Nationality', Type => 'UTF-16' }, + '060e2b34.0101.0105.02300603.010e0000' => { Name => 'Citizenship', Format => 'string' }, + '060e2b34.0101.0105.02300603.010e0100' => { Name => 'Citizenship', Type => 'UTF-16' }, + '060e2b34.0101.0105.03010102.02030000' => { Name => 'FrameworkTextLanguageCode', Format => 'string', LanguageCode => 1 }, + '060e2b34.0101.0105.03010201.09000000' => { Name => 'KAGSize', Format => 'int32u' }, + '060e2b34.0101.0105.03010201.0a000000' => { Name => 'ReversedByteOrder', Type => 'Boolean' }, + # '060e2b34.0101.0105.0301020a.00000000' => { Name => 'NameValueConstructInterpretations', Type => 'Node' }, + '060e2b34.0101.0105.0301020a.01000000' => { Name => 'ItemName', Format => 'string' }, + '060e2b34.0101.0105.0301020a.01010000' => { Name => 'ItemName', Type => 'UTF-16' }, + '060e2b34.0101.0105.0301020a.02000000' => { Name => 'ItemValue', Format => 'string' }, + '060e2b34.0101.0105.0301020a.02010000' => { Name => 'ItemValue', Type => 'UTF-16' }, + # '060e2b34.0101.0105.03010220.00000000' => { Name => 'XMLConstructsAndInterpretations', Type => 'Node' }, + '060e2b34.0101.0105.03010220.01000000' => { Name => 'XMLDocumentText', Type => 'Indirect', Unknown => 1 }, + '060e2b34.0101.0105.03010220.01010000' => { Name => 'XMLDocumentText', Format => 'string' }, + '060e2b34.0101.0105.03010220.01020000' => { Name => 'XMLDocumentText', Type => 'UTF-16' }, + '060e2b34.0101.0105.03010220.01030000' => { Name => 'XMLDocumentText', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0105.03020102.15000000' => { Name => 'FrameworkThesaurusName', Format => 'string' }, + '060e2b34.0101.0105.03020102.15010000' => { Name => 'FrameworkThesaurusName', Type => 'UTF-16' }, + '060e2b34.0101.0105.03020106.08000000' => { Name => 'GroupSynopsis', Format => 'string' }, + '060e2b34.0101.0105.03020106.08010000' => { Name => 'GroupSynopsis', Type => 'UTF-16' }, + '060e2b34.0101.0105.03020106.09000000' => { Name => 'AnnotationSynopsis', Format => 'string' }, + '060e2b34.0101.0105.03020106.09010000' => { Name => 'AnnotationSynopsis', Type => 'UTF-16' }, + '060e2b34.0101.0105.03020106.0a000000' => { Name => 'AnnotationDescription', Format => 'string' }, + '060e2b34.0101.0105.03020106.0a010000' => { Name => 'AnnotationDescription', Type => 'UTF-16' }, + '060e2b34.0101.0105.03020106.0b000000' => { Name => 'ScriptingKind', Format => 'string' }, + '060e2b34.0101.0105.03020106.0b010000' => { Name => 'ScriptingKind', Type => 'UTF-16' }, + '060e2b34.0101.0105.03020106.0c000000' => { Name => 'ScriptingText', Format => 'string' }, + '060e2b34.0101.0105.03020106.0c010000' => { Name => 'ScriptingText', Type => 'UTF-16' }, + '060e2b34.0101.0105.03020106.0d000000' => { Name => 'ShotDescription', Format => 'string' }, + '060e2b34.0101.0105.03020106.0d010000' => { Name => 'ShotDescription', Type => 'UTF-16' }, + '060e2b34.0101.0105.03020106.0e000000' => { Name => 'AnnotationKind', Format => 'string' }, + '060e2b34.0101.0105.03020106.0e010000' => { Name => 'AnnotationKind', Type => 'UTF-16' }, + '060e2b34.0101.0105.03020106.0f000000' => { Name => 'RelatedMaterialDescription', Format => 'string' }, + '060e2b34.0101.0105.03020106.0f010000' => { Name => 'RelatedMaterialDescription', Type => 'UTF-16' }, + '060e2b34.0101.0105.03020504.00000000' => { Name => 'ClipKind', Type => 'UTF-16' }, + # '060e2b34.0101.0105.03030310.00000000' => { Name => 'DeviceCommentsAndDescriptions', Type => 'Node' }, + '060e2b34.0101.0105.03030310.01000000' => { Name => 'DeviceUsageDescription', Format => 'string' }, + '060e2b34.0101.0105.03030310.01010000' => { Name => 'DeviceUsageDescription', Type => 'UTF-16' }, + '060e2b34.0101.0105.04010302.07000000' => { Name => 'DisplayF2Offset', Format => 'int32s' }, + '060e2b34.0101.0105.04010302.08000000' => { Name => 'StoredF2Offset', Format => 'int32s' }, + '060e2b34.0101.0105.04010302.09000000' => { Name => 'ActiveFormatDescriptor', Format => 'int8u' }, + '060e2b34.0101.0105.04010302.0a000000' => { Name => 'LineNumber', Format => 'int32u' }, + # '060e2b34.0101.0105.04010404.00000000' => { Name => 'VideoScanningCharacteristics', Type => 'Node' }, + '060e2b34.0101.0105.04010404.01000000' => { Name => 'ScanningDirection', Format => 'int8u' }, + '060e2b34.0101.0105.04010503.0b000000' => { Name => 'ComponentMaximumRef', Format => 'int32u' }, + '060e2b34.0101.0105.04010503.0c000000' => { Name => 'ComponentMinimumRef', Format => 'int32u' }, + '060e2b34.0101.0105.04010503.0d000000' => { Name => 'AlphaMaximumRef', Format => 'int32u' }, + '060e2b34.0101.0105.04010503.0e000000' => { Name => 'AlphaMinimumRef', Format => 'int32u' }, + # '060e2b34.0101.0105.04010504.00000000' => { Name => 'DigitalVideoAndImageSignalTypeIdentifiers', Type => 'Node' }, + '060e2b34.0101.0105.04010504.01000000' => { Name => 'VideoPayloadIdentifier', Format => 'int8u' }, + '060e2b34.0101.0105.04010504.02000000' => { Name => 'VideoPayloadIdentifier2002', Format => 'int8u' }, + '060e2b34.0101.0105.04010602.01020000' => { Name => 'SingleSequenceFlag', Type => 'Boolean' }, + '060e2b34.0101.0105.04010602.01030000' => { Name => 'ConstantBPictureFlag', Type => 'Boolean' }, + '060e2b34.0101.0105.04010602.01040000' => { Name => 'CodedContentScanningKind', Type => 'int8u', + PrintConv => { 0 => 'Unknown', 1 => 'Progressive', 2 => 'Interlaced', 3 => 'Mixed' }, + }, + '060e2b34.0101.0105.04010602.01050000' => { Name => 'LowDelayIndicator', Type => 'Boolean' }, + '060e2b34.0101.0105.04010602.01060000' => { Name => 'ClosedGOPIndicator', Type => 'Boolean' }, + '060e2b34.0101.0105.04010602.01070000' => { Name => 'IdenticalGOPIndicator', Type => 'Boolean' }, + '060e2b34.0101.0105.04010602.01080000' => { Name => 'MaximumGOPSize', Format => 'int16u' }, + '060e2b34.0101.0105.04010602.01090000' => { Name => 'MaximumBPictureCount', Format => 'int16u' }, + '060e2b34.0101.0105.04010602.010a0000' => { Name => 'ProfileAndLevel', Format => 'int8u' }, + '060e2b34.0101.0105.04010602.010b0000' => { Name => 'Bitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)' }, + '060e2b34.0101.0105.04020101.04000000' => { Name => 'ChannelCount', Format => 'int32u', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0105.04020301.01010000' => { Name => 'AudioSampleRate', Format => 'rational64s', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0105.04020301.05000000' => { Name => 'PeakEnvelope', Format => 'int8u', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0105.04020301.0e000000' => { Name => 'PeakEnvelopeData', Type => 'Stream', Unknown => 1, Groups => { 2 => 'Audio' } }, + '060e2b34.0101.0105.04020302.01000000' => { Name => 'BlockAlign', Format => 'int16u' }, + '060e2b34.0101.0105.04020302.02000000' => { Name => 'SequenceOffset', Format => 'int8u' }, + '060e2b34.0101.0105.04020302.03000000' => { Name => 'BlockStartOffset', Format => 'int16u' }, + '060e2b34.0101.0105.04020302.05000000' => { Name => 'FileSecurityReport', Format => 'int32u' }, + '060e2b34.0101.0105.04020302.06000000' => { Name => 'FileSecurityWave', Format => 'int32u' }, + '060e2b34.0101.0105.04020303.05000000' => { Name => 'AverageBytesPerSecond', Format => 'int32u', Groups => { 2 => 'Audio' } }, + # '060e2b34.0101.0105.04020500.00000000' => { Name => 'DigitalAudioProcessingParameters', Type => 'Node' }, + # '060e2b34.0101.0105.04020501.00000000' => { Name => 'AES-3ProcessingParameters', Type => 'Node' }, + '060e2b34.0101.0105.04020501.01000000' => { Name => 'AuxiliaryBitsMode', Format => 'int8u' }, + '060e2b34.0101.0105.04020501.02000000' => { Name => 'ChannelStatusMode', Format => 'undef', + RawConv => 'length($val) > 8 ? join(" ",unpack("x8C*",$val)) : undef', + # convert just the first value for now + PrintConv => [{ + 0 => 'No Channel Status Data', + 1 => 'AES3 Minimum', + 2 => 'AES3 Standard', + 3 => 'Fixed 24 Bytes in FixedChannelStatusData', + 4 => 'Stream of Data in MXF Header Metadata', + 5 => 'Stream of Data Multiplexed within MXF Body', + }], + }, + '060e2b34.0101.0105.04020501.03000000' => { Name => 'FixedChannelStatusData', Type => 'Array of bytes', Unknown => 1 }, + '060e2b34.0101.0105.04020501.04000000' => { Name => 'UserDataMode', Type => 'EnumeratedArray', Unknown => 1 }, + '060e2b34.0101.0105.04020501.05000000' => { Name => 'FixedUserData', Type => 'Array of bytes', Unknown => 1 }, + '060e2b34.0101.0105.04020501.06000000' => { Name => 'Emphasis', Format => 'int8u' }, + # '060e2b34.0101.0105.04020502.00000000' => { Name => 'BWFProcessingParameters', Type => 'Node' }, + '060e2b34.0101.0105.04020502.01000000' => { Name => 'BextCodingHistory', Format => 'string' }, + '060e2b34.0101.0105.04020502.01010000' => { Name => 'BextCodingHistory', Type => 'UTF-16' }, + '060e2b34.0101.0105.04020502.02000000' => { Name => 'QltyBasicData', Format => 'string' }, + '060e2b34.0101.0105.04020502.02010000' => { Name => 'QltyBasicData', Type => 'UTF-16' }, + '060e2b34.0101.0105.04020502.03000000' => { Name => 'QltyStartOfModulation', Format => 'string' }, + '060e2b34.0101.0105.04020502.03010000' => { Name => 'QltyStartOfModulation', Type => 'UTF-16' }, + '060e2b34.0101.0105.04020502.04000000' => { Name => 'QltyQualityEvent', Format => 'string' }, + '060e2b34.0101.0105.04020502.04010000' => { Name => 'QltyQualityEvent', Type => 'UTF-16' }, + '060e2b34.0101.0105.04020502.05000000' => { Name => 'QltyEndOfModulation', Format => 'string' }, + '060e2b34.0101.0105.04020502.05010000' => { Name => 'QltyEndOfModulation', Type => 'UTF-16' }, + '060e2b34.0101.0105.04020502.06000000' => { Name => 'QltyQualityParameter', Format => 'string' }, + '060e2b34.0101.0105.04020502.06010000' => { Name => 'QltyQualityParameter', Type => 'UTF-16' }, + '060e2b34.0101.0105.04020502.07000000' => { Name => 'QltyOperatorComment', Format => 'string' }, + '060e2b34.0101.0105.04020502.07010000' => { Name => 'QltyOperatorComment', Type => 'UTF-16' }, + '060e2b34.0101.0105.04020502.08000000' => { Name => 'QltyCueSheet', Format => 'string' }, + '060e2b34.0101.0105.04020502.08010000' => { Name => 'QltyCueSheet', Type => 'UTF-16' }, + # '060e2b34.0101.0105.04020700.00000000' => { Name => 'GeneralProcessingParameters', Type => 'Node' }, + '060e2b34.0101.0105.04020701.00000000' => { Name => 'DialNorm', Format => 'int8s' }, + '060e2b34.0101.0105.04030302.00000000' => { Name => 'DataEssenceCoding', Type => 'Label' }, + # '060e2b34.0101.0105.04040102.00000000' => { Name => 'GeneralDateTimeCodingCharacteristics', Type => 'Node' }, + '060e2b34.0101.0105.04040102.01000000' => { Name => 'DateTimeRate', Format => 'rational64s', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0105.04040102.02000000' => { Name => 'DateTimeDropFrameFlag', Type => 'Boolean', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0105.04040102.03000000' => { Name => 'DateTimeEmbeddedFlag', Type => 'Boolean', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0105.04040102.04000000' => { Name => 'DateTimeKind', Type => 'UL', Unknown => 1, Groups => { 2 => 'Time' } }, + '060e2b34.0101.0105.04040401.06000000' => { Name => 'DeltaEntryArray', Type => 'ArrayOfDeltaEntry', Unknown => 1 }, + '060e2b34.0101.0105.04040401.07000000' => { Name => 'PositionTableCount', Format => 'int8u' }, + '060e2b34.0101.0105.04040401.08000000' => { Name => 'PositionTable', Type => 'ArrayOfRational', Unknown => 1 }, + '060e2b34.0101.0105.04040402.05000000' => { Name => 'IndexEntryArray', Type => 'ArrayOfIndexEntry', Unknown => 1 }, + '060e2b34.0101.0105.04050113.00000000' => { Name => 'SignalStandard', Format => 'int8u' }, + '060e2b34.0101.0105.04070101.00000000' => { Name => 'DataDefinition', Type => 'UL', Unknown => 1 }, + # '060e2b34.0101.0105.04090000.00000000' => { Name => 'FormatCharacteristics', Type => 'Node' }, + '060e2b34.0101.0105.04090100.00000000' => { Name => 'RecordedFormat', Format => 'string' }, + '060e2b34.0101.0105.05010302.01000000' => { Name => 'GenerationCopyNumber', Format => 'int16u' }, + '060e2b34.0101.0105.05010303.01000000' => { Name => 'GenerationCloneNumber', Format => 'int16u' }, + # '060e2b34.0101.0105.05010400.00000000' => { Name => 'MusicFlags', Type => 'Node' }, + '060e2b34.0101.0105.05010403.00000000' => { Name => 'ThemeMusicFlag', Type => 'Boolean' }, + '060e2b34.0101.0105.05010404.00000000' => { Name => 'InsertMusicFlag', Type => 'Boolean' }, + '060e2b34.0101.0105.05300406.00000000' => { Name => 'IndexEditRate', Format => 'rational64s' }, + '060e2b34.0101.0105.06010103.05000000' => { Name => 'LinkedTrackID', Format => 'int32u' }, + '060e2b34.0101.0105.06010104.01020100' => { Name => 'EssenceContainerFormat', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.0105.06010104.01030100' => { Name => 'CodecDefinition', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.0105.06010104.020c0000' => { Name => 'DescriptiveMetadataFramework', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0105.06010104.02400500' => { Name => 'GroupSet', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0105.06010104.02401c00' => { Name => 'BankDetailsSet', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0105.06010104.02401d00' => { Name => 'ImageFormatSet', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0105.06010104.02402000' => { Name => 'ProcessingSet', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0105.06010104.02402100' => { Name => 'ProjectSet', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0105.06010104.02402200' => { Name => 'ContactsListSet', Type => 'StrongReference', Unknown => 1 }, + # '060e2b34.0101.0105.06010104.02402300' => { Name => 'CueWordsSets', Type => 'Node' }, + '060e2b34.0101.0105.06010104.02402301' => { Name => 'AnnotationCueWordsSet', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0105.06010104.02402302' => { Name => 'ShotCueWordsSet', Type => 'StrongReference', Unknown => 1 }, + # '060e2b34.0101.0105.06010104.03400000' => { Name => 'WeakReferencingToDescriptiveMetadataSets', Type => 'Node' }, + # '060e2b34.0101.0105.06010104.03401300' => { Name => 'ParticipantRoleSets', Type => 'Node' }, + '060e2b34.0101.0105.06010104.03401301' => { Name => 'AwardParticipantSets', Type => 'GlobalReferenceBatch (Participant)', Unknown => 1 }, + '060e2b34.0101.0105.06010104.03401302' => { Name => 'ContractParticipantSets', Type => 'GlobalReferenceBatch (Participant)', Unknown => 1 }, + '060e2b34.0101.0105.06010104.03401400' => { Name => 'PersonSets', Type => 'GlobalReferenceBatch (Participant)', Unknown => 1 }, + # '060e2b34.0101.0105.06010104.03401500' => { Name => 'OrganizationSets', Type => 'Node' }, + '060e2b34.0101.0105.06010104.03401501' => { Name => 'ParticipantOrganizationSets', Type => 'GlobalReferenceBatch (Organisation)', Unknown => 1 }, + '060e2b34.0101.0105.06010104.03401502' => { Name => 'PersonOrganizationSets', Type => 'GlobalReferenceBatch (Organisation)', Unknown => 1 }, + '060e2b34.0101.0105.06010104.03401600' => { Name => 'LocationSets', Type => 'GlobalReferenceBatch (Location)', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400400' => { Name => 'TitlesSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400500' => { Name => 'GroupSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400600' => { Name => 'IdentificationSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400700' => { Name => 'EpisodicItemSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400800' => { Name => 'BrandingSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400900' => { Name => 'EventSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400a00' => { Name => 'PublicationSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400b00' => { Name => 'AwardSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400c00' => { Name => 'CaptionDescriptionSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400d00' => { Name => 'AnnotationSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + # '060e2b34.0101.0105.06010104.05400e00' => { Name => 'SettingPeriodSets', Type => 'Node' }, + '060e2b34.0101.0105.06010104.05400e01' => { Name => 'ProductionSettingPeriodSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400e02' => { Name => 'SceneSettingPeriodSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05400f00' => { Name => 'ScriptingSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401000' => { Name => 'ClassificationSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + # '060e2b34.0101.0105.06010104.05401100' => { Name => 'ShotSets', Type => 'Node' }, + '060e2b34.0101.0105.06010104.05401101' => { Name => 'SceneShotSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401102' => { Name => 'ClipShotSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401200' => { Name => 'KeyPointSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401300' => { Name => 'ShotParticipantRoleSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401400' => { Name => 'ShotPersonSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401500' => { Name => 'OrganizationSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401600' => { Name => 'ShotLocationSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401700' => { Name => 'AddressSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401800' => { Name => 'CommunicationSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401900' => { Name => 'ContractSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401a00' => { Name => 'RightsSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401b00' => { Name => 'PaymentsSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401e00' => { Name => 'DeviceParametersSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + # '060e2b34.0101.0105.06010104.05401f00' => { Name => 'NameValueSets', Type => 'Node' }, + '060e2b34.0101.0105.06010104.05401f01' => { Name => 'ClassificationNameValueSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401f02' => { Name => 'ContactNameValueSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.05401f03' => { Name => 'DeviceParameterNameValueSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0105.06010104.060c0000' => { Name => 'MetadataServerLocators', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0105.06010104.060d0000' => { Name => 'RelatedMaterialLocators', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0105.06010107.15000000' => { Name => 'LocalTagEntries', Type => 'LocalTagEntryBatch', Unknown => 1 }, + '060e2b34.0101.0105.06100400.00000000' => { Name => 'TotalNumberInSequence', Format => 'int32u' }, + '060e2b34.0101.0105.07012001.04011100' => { Name => 'RoomOrSuiteName', Format => 'string' }, + '060e2b34.0101.0105.07012001.04011101' => { Name => 'RoomOrSuiteName', Type => 'UTF-16' }, + '060e2b34.0101.0105.07012001.04011200' => { Name => 'BuildingName', Format => 'string' }, + '060e2b34.0101.0105.07012001.04011201' => { Name => 'BuildingName', Type => 'UTF-16' }, + '060e2b34.0101.0105.07012001.10030601' => { Name => 'URL', Type => 'UTF-16' }, + # '060e2b34.0101.0105.07020102.07100000' => { Name => 'DefinedEventStartTrueDateTime', Type => 'Node' }, + '060e2b34.0101.0105.07020102.07100100' => { Name => 'LocalFestivalDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0105.07020103.01090000' => { Name => 'ShotStartPosition', Type => 'Position', %duration }, + '060e2b34.0101.0105.07020103.010a0000' => { Name => 'IndexingStartPosition', Type => 'Position', %duration }, + '060e2b34.0101.0105.07020103.010b0000' => { Name => 'EventOrigin', Type => 'Position', %duration }, + '060e2b34.0101.0105.07020108.03000000' => { Name => 'SettingPeriodDescription', Format => 'string' }, + '060e2b34.0101.0105.07020108.03010000' => { Name => 'SettingPeriodDescription', Type => 'UTF-16' }, + '060e2b34.0101.0105.07020201.01020000' => { Name => 'IndexDuration', Type => 'Length', %duration }, + '060e2b34.0101.0105.07020201.02040000' => { Name => 'ShotDuration', Type => 'Length', %duration }, + # '060e2b34.0101.0105.0d020000.00000000' => { Name => 'EBU_UER', Type => 'Node' }, + # '060e2b34.0101.0105.0d030000.00000000' => { Name => 'Pro-MPEGForum', Type => 'Node' }, + # '060e2b34.0101.0106.0e040000.00000000' => { Name => 'Avid', Type => 'Node' }, + # '060e2b34.0101.0106.0e050000.00000000' => { Name => 'CNN', Type => 'Node' }, + # '060e2b34.0101.0106.0e050100.00000000' => { Name => 'CNNMediaIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0106.0e050101.00000000' => { Name => 'CNNLegacyMediaIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0106.0e050200.00000000' => { Name => 'CNNAttributes', Type => 'Node' }, + # '060e2b34.0101.0106.0e050201.00000000' => { Name => 'CNNRelationalAttributes', Type => 'Node' }, + # '060e2b34.0101.0106.0e050202.00000000' => { Name => 'CNNInformationAttributes', Type => 'Node' }, + # '060e2b34.0101.0106.0e050300.00000000' => { Name => 'CNNMetadataSets', Type => 'Node' }, + # '060e2b34.0101.0106.0e060000.00000000' => { Name => 'Sony', Type => 'Node' }, + '060e2b34.0101.0107.01011509.00000000' => { Name => 'ExtendedClipID', Type => 'UMID' }, + '060e2b34.0101.0107.0101150a.00000000' => { Name => 'ClipIDArray', Type => 'UMID Array', Unknown => 1 }, + '060e2b34.0101.0107.0101150b.00000000' => { Name => 'ExtendedClipIDArray', Type => 'UMID Array', Unknown => 1 }, + '060e2b34.0101.0107.01040104.00000000' => { Name => 'TrackNumberBatch', Format => 'int32u' }, + '060e2b34.0101.0107.03010102.02110000' => { Name => 'ExtendedTextLanguageCode', Format => 'string', LanguageCode => 1 }, + '060e2b34.0101.0107.03010102.02120000' => { Name => 'ExtendedCaptionsLanguageCode', Format => 'string' }, + '060e2b34.0101.0107.03010102.02130000' => { Name => 'FrameworkExtendedTextLanguageCode', Format => 'string', LanguageCode => 1 }, + '060e2b34.0101.0107.03010102.03110000' => { Name => 'PrimaryExtendedSpokenLanguageCode', Format => 'string' }, + '060e2b34.0101.0107.03010102.03120000' => { Name => 'SecondaryExtendedSpokenLanguageCode', Format => 'string' }, + '060e2b34.0101.0107.03010102.03130000' => { Name => 'OriginalExtendedSpokenPrimaryLanguageCode', Format => 'string' }, + '060e2b34.0101.0107.03010102.03140000' => { Name => 'SecondaryOriginalExtendedSpokenLanguageCode', Format => 'string' }, + '060e2b34.0101.0107.03010210.06000000' => { Name => 'KLVMetadataSequence', Type => 'Sequence of KLV packets', Unknown => 1 }, + '060e2b34.0101.0107.03010210.07000000' => { Name => 'PackageAttributes', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0107.03010210.08000000' => { Name => 'ComponentAttributes', Type => 'StrongReferenceArray', Unknown => 1 }, + # '060e2b34.0101.0107.03010220.02000000' => { Name => 'XMLBiMConstructsInMultipleStreams', Type => 'Node' }, + # '060e2b34.0101.0107.03010220.02010000' => { Name => 'MPEG7BiMDecoderInitFrames', Type => 'Node' }, + '060e2b34.0101.0107.03010220.02010100' => { Name => 'MPEG7BiMDecoderInitFrame1', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02010200' => { Name => 'MPEG7BiMDecoderInitFrame2', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02010300' => { Name => 'MPEG7BiMDecoderInitFrame3', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02010400' => { Name => 'MPEG7BiMDecoderInitFrame4', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02010500' => { Name => 'MPEG7BiMDecoderInitFrame5', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02010600' => { Name => 'MPEG7BiMDecoderInitFrame6', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02010700' => { Name => 'MPEG7BiMDecoderInitFrame7', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02010800' => { Name => 'MPEG7BiMDecoderInitFrame8', Type => 'ByteStream', Unknown => 1 }, + # '060e2b34.0101.0107.03010220.02020000' => { Name => 'MPEG7BiMAccessUnitFrames', Type => 'Node' }, + '060e2b34.0101.0107.03010220.02020100' => { Name => 'MPEG7BiMAccessUnitFrame1', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02020200' => { Name => 'MPEG7BiMAccessUnitFrame2', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02020300' => { Name => 'MPEG7BiMAccessUnitFrame3', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02020400' => { Name => 'MPEG7BiMAccessUnitFrame4', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02020500' => { Name => 'MPEG7BiMAccessUnitFrame5', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02020600' => { Name => 'MPEG7BiMAccessUnitFrame6', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02020700' => { Name => 'MPEG7BiMAccessUnitFrame7', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03010220.02020800' => { Name => 'MPEG7BiMAccessUnitFrame8', Type => 'ByteStream', Unknown => 1 }, + '060e2b34.0101.0107.03020102.16000000' => { Name => 'ComponentUserComments', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0107.03020501.01000000' => { Name => 'ShotCommentKind', Type => 'UTF-16' }, + '060e2b34.0101.0107.03020502.01000000' => { Name => 'ShotComment', Type => 'UTF-16' }, + # '060e2b34.0101.0107.04010202.00000000' => { Name => 'SensorParameters', Type => 'Node' }, + '060e2b34.0101.0107.04010202.01000000' => { Name => 'SensorMode', Format => 'string' }, + '060e2b34.0101.0107.04020101.05000000' => { Name => 'ChannelAssignment', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.0107.04040402.06000000' => { Name => 'ContentPackageIndexArray', Type => 'ArrayOfIndexEntry', Unknown => 1 }, + # '060e2b34.0101.0107.04040403.00000000' => { Name => 'VideoIndexParameters', Type => 'Node' }, + '060e2b34.0101.0107.04040403.01000000' => { Name => 'VideoIndexArray', Type => 'Array of bytes', Unknown => 1 }, + '060e2b34.0101.0107.04060202.00000000' => { Name => 'ApproximateImageContainerSize', Format => 'int32u' }, + '060e2b34.0101.0107.04060801.00000000' => { Name => 'MetadataEncodingSchemeCode', Format => 'string' }, + '060e2b34.0101.0107.04090200.00000000' => { Name => 'MIMEMediaType', Format => 'string' }, + '060e2b34.0101.0107.04090201.00000000' => { Name => 'MIMEMediaType', Type => 'UTF-16' }, + '060e2b34.0101.0107.04200201.010a0100' => { Name => 'FieldOfViewVerticalFP', Format => 'float' }, + '060e2b34.0101.0107.05010108.00000000' => { Name => 'PackageUsageKind', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.0107.06010103.06000000' => { Name => 'ChannelID', Format => 'int32u' }, + '060e2b34.0101.0107.06010103.07000000' => { Name => 'ChannelIDs', Format => 'int32u' }, + '060e2b34.0101.0107.06010104.01090000' => { Name => 'KLVDataType', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.0107.06010104.03040000' => { Name => 'KLVDataParentProperties', Type => 'WeakReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0107.06010104.03050000' => { Name => 'TaggedValueParentProperties', Type => 'WeakReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0107.06010104.03401303' => { Name => 'AnnotationParticipantSets', Type => 'GlobalReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0107.06010104.050a0000' => { Name => 'KLVDataDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0107.06010104.050b0000' => { Name => 'TaggedValueDefinitions', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0107.06010104.05401f04' => { Name => 'AddressNameValueSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0107.07010201.02300000' => { Name => 'NMEADocumentText', Format => 'string' }, + '060e2b34.0101.0107.07011001.04000000' => { Name => 'PlatformRollAngle', Format => 'float' }, + '060e2b34.0101.0107.07011001.05000000' => { Name => 'PlatformPitchAngle', Format => 'float' }, + '060e2b34.0101.0107.07011001.06000000' => { Name => 'PlatformHeadingAngle', Format => 'float' }, + '060e2b34.0101.0107.07012001.04011300' => { Name => 'AddressLine', Format => 'string' }, + '060e2b34.0101.0107.07012001.04011301' => { Name => 'AddressLine', Type => 'UTF-16' }, + '060e2b34.0101.0107.07012001.04011400' => { Name => 'PlaceName', Format => 'string' }, + '060e2b34.0101.0107.07012001.04011401' => { Name => 'PlaceName', Type => 'UTF-16' }, + '060e2b34.0101.0107.07012001.04011500' => { Name => 'GeographicalCoordinates', Type => '12-byte Spatial Coordinate', Unknown => 1 }, + '060e2b34.0101.0107.07012001.04011600' => { Name => 'AstronomicalBodyName', Format => 'string' }, + '060e2b34.0101.0107.07012001.04011601' => { Name => 'AstronomicalBodyName', Type => 'UTF-16' }, + '060e2b34.0101.0107.07020102.08020000' => { Name => 'TimecodeArray', Type => 'Array of timecodes', Unknown => 1 }, + '060e2b34.0101.0107.07020103.010c0000' => { Name => 'MarkIn', Type => 'Position', %duration }, + '060e2b34.0101.0107.07020103.010d0000' => { Name => 'UserPosition', Type => 'Position', %duration }, + '060e2b34.0101.0107.07020103.02030000' => { Name => 'MarkOut', Type => 'Position', %duration }, + '060e2b34.0101.0107.07020110.01040000' => { Name => 'ClipCreationDateTime', %timestamp }, + '060e2b34.0101.0107.07020201.02050000' => { Name => 'VideoClipDuration', Format => 'int32u' }, + # '060e2b34.0101.0107.0d040000.00000000' => { Name => 'BBC', Type => 'Node' }, + # '060e2b34.0101.0107.0d050000.00000000' => { Name => 'IRT', Type => 'Node' }, + # '060e2b34.0101.0107.0e070000.00000000' => { Name => 'IdeasUnlimitedTV', Type => 'Node' }, + # '060e2b34.0101.0107.0e080000.00000000' => { Name => 'IPV', Type => 'Node' }, + # '060e2b34.0101.0107.0e090000.00000000' => { Name => 'Dolby', Type => 'Node' }, + # '060e2b34.0101.0108.01011540.00000000' => { Name => 'GloballyUniqueObjectIdentifiers', Type => 'Node' }, + # '060e2b34.0101.0108.01011540.01000000' => { Name => 'GloballyUniqueHumanIdentifiers', Type => 'Node' }, + '060e2b34.0101.0108.01011540.01010000' => { Name => 'ParticipantID', Type => 'UID', Unknown => 1 }, + '060e2b34.0101.0108.01011540.01020000' => { Name => 'ContactID', Type => 'UID', Unknown => 1 }, + '060e2b34.0101.0108.01020104.00000000' => { Name => 'DefaultNamespaceURI', Format => 'string' }, + '060e2b34.0101.0108.01020104.01000000' => { Name => 'DefaultNamespaceURI', Type => 'UTF-16' }, + '060e2b34.0101.0108.01020105.00000000' => { Name => 'NamespaceURI', Format => 'string' }, + '060e2b34.0101.0108.01020105.01000000' => { Name => 'NamespaceURI', Type => 'UTF-16' }, + '060e2b34.0101.0108.01020106.00000000' => { Name => 'NamespaceURIs', Format => 'string' }, + '060e2b34.0101.0108.01020106.01000000' => { Name => 'NamespaceURIs', Type => 'UTF-16' }, + '060e2b34.0101.0108.01030604.00000000' => { Name => 'HTMLDOCTYPE', Format => 'string' }, + '060e2b34.0101.0108.01030604.01000000' => { Name => 'HTMLDOCTYPE', Type => 'UTF-16' }, + '060e2b34.0101.0108.01030605.00000000' => { Name => 'NamespacePrefix', Format => 'string' }, + '060e2b34.0101.0108.01030605.01000000' => { Name => 'NamespacePrefix', Type => 'UTF-16' }, + '060e2b34.0101.0108.01030606.00000000' => { Name => 'NamespacePrefixes', Format => 'string' }, + '060e2b34.0101.0108.01030606.01000000' => { Name => 'NamespacePrefixes', Type => 'UTF-16' }, + '060e2b34.0101.0108.02050404.00000000' => { Name => 'RightsComment', Format => 'string' }, + '060e2b34.0101.0108.02050404.01000000' => { Name => 'RightsComment', Type => 'UTF-16' }, + '060e2b34.0101.0108.03020201.06000000' => { Name => 'NominationCategory', Format => 'string' }, + '060e2b34.0101.0108.03020201.06010000' => { Name => 'NominationCategory', Type => 'UTF-16' }, + '060e2b34.0101.0108.04020301.06000000' => { Name => 'PeakEnvelopeVersion', Format => 'int32u' }, + '060e2b34.0101.0108.04020301.07000000' => { Name => 'PeakEnvelopeFormat', Format => 'int32u' }, + '060e2b34.0101.0108.04020301.08000000' => { Name => 'PointsPerPeakValue', Format => 'int32u' }, + '060e2b34.0101.0108.04020301.09000000' => { Name => 'PeakEnvelopeBlockSize', Format => 'int32u' }, + '060e2b34.0101.0108.04020301.0a000000' => { Name => 'PeakChannelCount', Format => 'int32u' }, + '060e2b34.0101.0108.04020301.0b000000' => { Name => 'PeakFrameCount', Format => 'int32u' }, + '060e2b34.0101.0108.04020301.0c000000' => { Name => 'PeakOfPeaksPosition', Type => 'Position', %duration }, + '060e2b34.0101.0108.04020301.0d000000' => { Name => 'PeakEnvelopeTimestamp', Format => 'int32u' }, + '060e2b34.0101.0108.04020301.0e000000' => { Name => 'PeakEnvelopeData', Type => 'Stream', Unknown => 1 }, + '060e2b34.0101.0108.04060802.00000000' => { Name => 'RIFFChunkID', Format => 'int32u' }, + '060e2b34.0101.0108.04060903.00000000' => { Name => 'RIFFChunkLength', Format => 'int32u' }, + '060e2b34.0101.0108.04070400.00000000' => { Name => 'RIFFChunkData', Type => 'DataStream', Unknown => 1 }, + '060e2b34.0101.0108.04090300.00000000' => { Name => 'MIMECharSet', Format => 'string' }, + '060e2b34.0101.0108.04090301.00000000' => { Name => 'MIMECharSet', Type => 'UTF-16' }, + '060e2b34.0101.0108.04090400.00000000' => { Name => 'MIMEEncoding', Format => 'string' }, + '060e2b34.0101.0108.04090401.00000000' => { Name => 'MIMEEncoding', Type => 'UTF-16' }, + '060e2b34.0101.0108.06010103.08000000' => { Name => 'MonoSourceTrackIDs', Format => 'int32u' }, + '060e2b34.0101.0108.06010104.010a0000' => { Name => 'CompositionRendering', Type => 'PackageID', Unknown => 1 }, + '060e2b34.0101.0108.06010104.03401304' => { Name => 'CaptionsDescriptionParticipantSets', Type => 'GlobalReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0108.06010104.05400d01' => { Name => 'EventAnnotationSets', Type => 'StrongReferenceBatch', Unknown => 1 }, + '060e2b34.0101.0108.06010104.060e0000' => { Name => 'ScriptingLocators', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0108.06010104.060f0000' => { Name => 'UnknownBWFChunks', Type => 'StrongReferenceArray', Unknown => 1 }, + # '060e2b34.0101.0108.0e0a0000.00000000' => { Name => 'SnellAndWilcox', Type => 'Node' }, + '060e2b34.0101.0109.01011511.00000000' => { Name => 'CryptographicContextID', Type => 'UUID', Unknown => 1 }, + '060e2b34.0101.0109.01012101.01000000' => { Name => 'PlatformDesignation', Type => 'UTF-16' }, + '060e2b34.0101.0109.01030107.01000000' => { Name => 'LocalTargetID', Type => 'UTF-16' }, + '060e2b34.0101.0109.01030109.00000000' => { Name => 'NITFLayerTargetID', Format => 'string' }, + '060e2b34.0101.0109.01030109.01000000' => { Name => 'NITFLayerTargetID', Type => 'UTF-16' }, + '060e2b34.0101.0109.01030302.00000000' => { Name => 'PackageName', Format => 'string' }, + '060e2b34.0101.0109.01030406.00000000' => { Name => 'RP217DataStreamPID', Format => 'int16u' }, + '060e2b34.0101.0109.01030407.00000000' => { Name => 'RP217VideoStreamPID', Format => 'int16u' }, + '060e2b34.0101.0109.02010101.00000000' => { Name => 'SourceOrganization', Type => 'UTF-16' }, + '060e2b34.0101.0109.02010301.00000000' => { Name => 'OriginalProducerName', Type => 'UTF-16' }, + '060e2b34.0101.0109.02080201.01000000' => { Name => 'SecurityClassification', Type => 'UTF-16' }, + '060e2b34.0101.0109.02080202.01000000' => { Name => 'SecurityClassificationCaveats', Type => 'UTF-16' }, + '060e2b34.0101.0109.02080207.01000000' => { Name => 'ClassificationComment', Type => 'UTF-16' }, + # '060e2b34.0101.0109.02090200.00000000' => { Name => 'DataEncryption', Type => 'Node' }, + # '060e2b34.0101.0109.02090201.00000000' => { Name => 'DataEncryptionAlgorithms', Type => 'Node' }, + # '060e2b34.0101.0109.02090202.00000000' => { Name => 'DataHashingAlgorithms', Type => 'Node' }, + # '060e2b34.0101.0109.02090300.00000000' => { Name => 'DigitalCinemaEncryption', Type => 'Node' }, + # '060e2b34.0101.0109.02090301.00000000' => { Name => 'DigitalCinemaEncryptionAlgorithms', Type => 'Node' }, + '060e2b34.0101.0109.02090301.01000000' => { Name => 'CipherAlgorithm', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.0109.02090301.02000000' => { Name => 'CryptographicKeyID', Type => 'UUID', Unknown => 1 }, + '060e2b34.0101.0109.02090301.03000000' => { Name => 'EncryptedSourceValue', Type => 'DataValue', Unknown => 1 }, + # '060e2b34.0101.0109.02090302.00000000' => { Name => 'DigitalCinemaHashingAlgorithms', Type => 'Node' }, + '060e2b34.0101.0109.02090302.01000000' => { Name => 'MICAlgorithm', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.0109.02090302.02000000' => { Name => 'MIC', Type => 'DataValue', Unknown => 1 }, + '060e2b34.0101.0109.03010102.01010000' => { Name => 'ISO639-1LanguageCode', Type => 'UTF-16' }, + '060e2b34.0101.0109.03020106.10000000' => { Name => 'JFIFMarkerDescription', Format => 'string' }, + '060e2b34.0101.0109.03020106.10010000' => { Name => 'JFIFMarkerDescription', Type => 'UTF-16' }, + '060e2b34.0101.0109.03020106.11000000' => { Name => 'HTMLMetaDescription', Format => 'string' }, + '060e2b34.0101.0109.03020106.11010000' => { Name => 'HTMLMetaDescription', Type => 'UTF-16' }, + '060e2b34.0101.0109.03020401.02000000' => { Name => 'MetadataItemName', Format => 'string' }, + '060e2b34.0101.0109.04010201.01060000' => { Name => 'ColorPrimaries', Format => 'string' }, + '060e2b34.0101.0109.04010201.01060100' => { Name => 'ColorPrimaries', Type => 'ColorPrimariesType', Unknown => 1 }, + '060e2b34.0101.0109.04060203.00000000' => { Name => 'ProductFormat', Format => 'string' }, + '060e2b34.0101.0109.04060203.01000000' => { Name => 'ProductFormat', Type => 'UTF-16' }, + '060e2b34.0101.0109.04061002.00000000' => { Name => 'SourceLength', Format => 'int64u' }, + '060e2b34.0101.0109.04200102.01010100' => { Name => 'ImageSourceDeviceKind', Type => 'UTF-16' }, + '060e2b34.0101.0109.06010102.02000000' => { Name => 'SourceContainerFormat', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.0109.06010102.03000000' => { Name => 'SourceKey', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.0109.06010103.09000000' => { Name => 'DynamicSourcePackageID', Type => 'PackageID', Unknown => 1 }, + '060e2b34.0101.0109.06010103.0a000000' => { Name => 'DynamicSourceTrackIDs', Format => 'int32u' }, + '060e2b34.0101.0109.06010103.0b000000' => { Name => 'SourceIndex', Type => 'Indirect', Unknown => 1 }, + '060e2b34.0101.0109.06010103.0c000000' => { Name => 'SourceSpecies', Type => 'Indirect', Unknown => 1 }, + '060e2b34.0101.0109.06010103.0d000000' => { Name => 'SourceValue', Type => 'Indirect', Unknown => 1 }, + '060e2b34.0101.0109.06010104.020d0000' => { Name => 'CryptographicContextObject', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.0109.06010104.06100000' => { Name => 'Sub-descriptors', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.0109.06010106.02000000' => { Name => 'EncryptedTrackFileID', Type => 'UUID', Unknown => 1 }, + '060e2b34.0101.0109.06010106.03000000' => { Name => 'CryptographicContextLink', Type => 'UUID', Unknown => 1 }, + '060e2b34.0101.0109.06090201.03000000' => { Name => 'PlaintextOffset', Format => 'int64u' }, + '060e2b34.0101.0109.06100500.00000000' => { Name => 'TripletSequenceNumber', Format => 'int64u' }, + '060e2b34.0101.0109.07010201.030f0000' => { Name => 'BoundingRectangle', Type => 'Geographic Rectangle', Unknown => 1 }, + '060e2b34.0101.0109.07010201.03100000' => { Name => 'GeographicLocation', Type => 'Geographic Polygon', Unknown => 1 }, + '060e2b34.0101.0109.07010201.03110000' => { Name => 'GeographicPolygonCoordinates', Type => 'Array of GeographicCoordinate', Unknown => 1 }, + '060e2b34.0101.0109.07010201.03120000' => { Name => 'GeographicAreaNorthwest', Type => 'GeographicCoordinate', Unknown => 1 }, + '060e2b34.0101.0109.07010201.03130000' => { Name => 'GeographicAreaSoutheast', Type => 'GeographicCoordinate', Unknown => 1 }, + '060e2b34.0101.0109.07010201.03140000' => { Name => 'GeographicAreaSourceDatum', Format => 'string' }, + '060e2b34.0101.0109.07010201.03150000' => { Name => 'GeographicPolygonSourceDatum', Format => 'string' }, + '060e2b34.0101.0109.07012001.02070100' => { Name => 'CountryCodeMethod', Type => 'UTF-16' }, + '060e2b34.0101.0109.07012001.02080100' => { Name => 'ClassifyingCountryCode', Type => 'UTF-16' }, + '060e2b34.0101.0109.07012001.02090100' => { Name => 'ReleasableCountryCode', Type => 'UTF-16' }, + '060e2b34.0101.0109.07020102.01010100' => { Name => 'UTCStartDateTime', Type => 'UTF-16', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0109.07020102.01030000' => { Name => 'UTCInstantDateTime', Format => 'string', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0109.07020102.01030100' => { Name => 'UTCInstantDateTime', Type => 'UTF-16', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0109.07020102.05010100' => { Name => 'UTCLastModifyDate', Type => 'UTF-16', Groups => { 2 => 'Time' } }, + '060e2b34.0101.0109.07020501.00000000' => { Name => 'ToleranceMode', Type => 'ToleranceModeType', Unknown => 1 }, + '060e2b34.0101.0109.07020502.00000000' => { Name => 'ToleranceWindow', Type => 'Indirect', Unknown => 1 }, + '060e2b34.0101.0109.07020503.00000000' => { Name => 'ToleranceInterpolationMethod', Type => 'WeakReferenceInterpolationDefinition', Unknown => 1 }, + # '060e2b34.0101.0109.0d060000.00000000' => { Name => 'ARIB', Type => 'Node' }, + # '060e2b34.0101.0109.0d070000.00000000' => { Name => 'AMIA', Type => 'Node' }, + # '060e2b34.0101.0109.0e0b0000.00000000' => { Name => 'OmneonVideoNetworks', Type => 'Node' }, + # '060e2b34.0101.0109.0e0c0000.00000000' => { Name => 'AscentMediaGroup', Type => 'Node' }, + # '060e2b34.0101.0109.0e0c0100.00000000' => { Name => 'Published', Type => 'Node' }, + # '060e2b34.0101.0109.0e0d0000.00000000' => { Name => 'Quantel', Type => 'Node' }, + # '060e2b34.0101.0109.0e0e0000.00000000' => { Name => 'Panasonic', Type => 'Node' }, + '060e2b34.0101.010a.04010502.03000000' => { Name => 'VBILineCount', Format => 'int16u' }, + '060e2b34.0101.010a.04010502.04000000' => { Name => 'StoredVBILineNumber', Format => 'int16u' }, + '060e2b34.0101.010a.04010502.05000000' => { Name => 'VBIWrappingType', Format => 'int8u' }, + '060e2b34.0101.010a.04010502.06000000' => { Name => 'VBIPayloadSampleCount', Format => 'int16u' }, + '060e2b34.0101.010a.04010502.07000000' => { Name => 'VBIPayloadByteArray', Format => 'int8u' }, + '060e2b34.0101.010a.04010502.08000000' => { Name => 'ANCPacketCount', Format => 'int16u' }, + '060e2b34.0101.010a.04010502.09000000' => { Name => 'StoredANCLineNumber', Format => 'int16u' }, + '060e2b34.0101.010a.04010502.0a000000' => { Name => 'ANCWrappingType', Format => 'int8u' }, + '060e2b34.0101.010a.04010502.0b000000' => { Name => 'ANCPayloadSampleCount', Format => 'int16u' }, + '060e2b34.0101.010a.04010502.0c000000' => { Name => 'ANCPayloadByteArray', Format => 'int8u' }, + '060e2b34.0101.010a.04010503.0f000000' => { Name => 'VBIPayloadSampleCoding', Format => 'int8u' }, + '060e2b34.0101.010a.04010503.10000000' => { Name => 'ANCPayloadSampleCoding', Format => 'int8u' }, + # '060e2b34.0101.010a.04010603.00000000' => { Name => 'JPEG2000CodingParameters', Type => 'Node' }, + '060e2b34.0101.010a.04010603.01000000' => { Name => 'Rsiz', Format => 'int16u' }, + '060e2b34.0101.010a.04010603.02000000' => { Name => 'Xsiz', Format => 'int32u' }, + '060e2b34.0101.010a.04010603.03000000' => { Name => 'Ysiz', Format => 'int32u' }, + '060e2b34.0101.010a.04010603.04000000' => { Name => 'XOsiz', Format => 'int32u' }, + '060e2b34.0101.010a.04010603.05000000' => { Name => 'YOsiz', Format => 'int32u' }, + '060e2b34.0101.010a.04010603.06000000' => { Name => 'XTsiz', Format => 'int32u' }, + '060e2b34.0101.010a.04010603.07000000' => { Name => 'YTsiz', Format => 'int32u' }, + '060e2b34.0101.010a.04010603.08000000' => { Name => 'XTOsiz', Format => 'int32u' }, + '060e2b34.0101.010a.04010603.09000000' => { Name => 'YTOsiz', Format => 'int32u' }, + '060e2b34.0101.010a.04010603.0a000000' => { Name => 'Csiz', Format => 'int16u' }, + '060e2b34.0101.010a.04010603.0b000000' => { Name => 'PictureComponentSizing', Type => 'J2K ComponentSizingArray', Unknown => 1 }, + '060e2b34.0101.010a.04010603.0c000000' => { Name => 'CodingStyleDefault', Type => 'J2K CodingStyleDefault', Unknown => 1 }, + '060e2b34.0101.010a.04010603.0d000000' => { Name => 'QuantizationDefault', Type => 'J2K QuantizationDefault', Unknown => 1 }, + '060e2b34.0101.010a.04020403.01020000' => { Name => 'MPEGAudioBitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)', Groups => { 2 => 'Audio' } }, + '060e2b34.0101.010a.04060204.00000000' => { Name => 'CBEStartOffset', Format => 'int64u' }, + '060e2b34.0101.010a.04060205.00000000' => { Name => 'VBEEndOffset', Format => 'int64u' }, + '060e2b34.0101.010a.06010104.02100000' => { Name => 'SubDescriptor', Unknown => 1 }, + '060e2b34.0101.010a.06010104.06100000' => { Name => 'SubDescriptors', Unknown => 1 }, + '060e2b34.0101.010a.06010107.16000000' => { Name => 'RootMetaDictionary', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.010a.06010107.17000000' => { Name => 'RootPreface', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.010a.06010107.18000000' => { Name => 'RootObjectDirectory', Type => 'Array of bytes', Unknown => 1 }, + '060e2b34.0101.010a.06010107.19000000' => { Name => 'RootFormatVersion', Format => 'int32u' }, + '060e2b34.0101.010a.07010201.03160000' => { Name => 'FrameCenterElevation', Format => 'float' }, + '060e2b34.0101.010a.07020103.010e0000' => { Name => 'PackageMarkInPosition', Type => 'Position', %duration }, + '060e2b34.0101.010a.07020103.02040000' => { Name => 'PackageMarkOutPosition', Type => 'Position', %duration }, + # '060e2b34.0101.010a.0d080000.00000000' => { Name => 'PBS', Type => 'Node' }, + # '060e2b34.0101.010a.0e0f0000.00000000' => { Name => 'GrassValley', Type => 'Node' }, + # '060e2b34.0101.010a.0e100000.00000000' => { Name => 'DoremiLabs', Type => 'Node' }, + # '060e2b34.0101.010a.0e110000.00000000' => { Name => 'EVSBroadcastEquipment', Type => 'Node' }, + # '060e2b34.0101.010a.0e120000.00000000' => { Name => 'TurnerBroadcastingSystem', Type => 'Node' }, + '060e2b34.0101.010c.0101110b.00000000' => { Name => 'Ad-ID', Unknown => 1 }, + '060e2b34.0101.010c.01011512.00000000' => { Name => 'ResourceID', Type => 'UUID', Unknown => 1 }, + '060e2b34.0101.010c.01011513.00000000' => { Name => 'AncillaryResourceID', Type => 'UUID', Unknown => 1 }, + '060e2b34.0101.010c.01020210.02030000' => { Name => 'ApplicationSchemeBatch', Type => 'BatchOfUL', Unknown => 1 }, + # '060e2b34.0101.010c.02100202.00000000' => { Name => 'RegisterPublicationInformation', Type => 'Node' }, + '060e2b34.0101.010c.02100202.01000000' => { Name => 'RegisterKind', Type => 'RegisterType', Unknown => 1 }, + '060e2b34.0101.010c.02100202.02000000' => { Name => 'RegisterVersion', Type => 'Hex' }, + '060e2b34.0101.010c.02100202.03000000' => { Name => 'RegisterEditorName', Type => 'UTF-16' }, + '060e2b34.0101.010c.02100202.04000000' => { Name => 'RegisterStatusKind', Type => 'RegisterStatusType', Unknown => 1 }, + # '060e2b34.0101.010c.02100203.00000000' => { Name => 'RegisterItem', Type => 'Node' }, + '060e2b34.0101.010c.02100203.01000000' => { Name => 'RegisterItemName', Type => 'UTF-16' }, + '060e2b34.0101.010c.02100203.02000000' => { Name => 'RegisterItemDefinition', Type => 'UTF-16' }, + '060e2b34.0101.010c.02100203.03000000' => { Name => 'RegisterItemSymbol', Type => 'SymbolType', Unknown => 1 }, + '060e2b34.0101.010c.02100203.04000000' => { Name => 'RegisterItemDefiningDocumentName', Type => 'UTF-16' }, + '060e2b34.0101.010c.02100203.05000000' => { Name => 'RegisterItemUL', Type => 'UniversalLabelType', Unknown => 1 }, + '060e2b34.0101.010c.02100203.06000000' => { Name => 'RegisterItemNotes', Type => 'UTF-16' }, + '060e2b34.0101.010c.02100203.07000000' => { Name => 'RegisterItemIntroductionVersion', Format => 'int8u' }, + '060e2b34.0101.010c.02100203.08000000' => { Name => 'RegisterItemHierarchyLevel', Format => 'int16u' }, + # '060e2b34.0101.010c.02100203.09000000' => { Name => 'RegisterWildcardFlag', Type => 'Node' }, + '060e2b34.0101.010c.02100203.0a000000' => { Name => 'RegisterEntryStatus', Type => 'EntryStatusType', Unknown => 1 }, + # '060e2b34.0101.010c.02100204.00000000' => { Name => 'RegisterAdministration', Type => 'Node' }, + '060e2b34.0101.010c.02100204.01000000' => { Name => 'RegisterAction', Type => 'UTF-16' }, + '060e2b34.0101.010c.02100204.02000000' => { Name => 'RegisterApproverName', Type => 'UTF-16' }, + '060e2b34.0101.010c.02100204.03000000' => { Name => 'RegisterCreationTime', %timestamp }, + '060e2b34.0101.010c.02100204.04000000' => { Name => 'RegistrantName', Type => 'UTF-16' }, + '060e2b34.0101.010c.02100204.05000000' => { Name => 'RegisterItemOriginatorName', Type => 'UTF-16' }, + '060e2b34.0101.010c.02100204.06000000' => { Name => 'RegisterUserName', Type => 'UTF-16' }, + '060e2b34.0101.010c.02100204.07000000' => { Name => 'RegisterUserTime', %timestamp }, + '060e2b34.0101.010c.02100204.08000000' => { Name => 'RegisterAdministrationNotes', Type => 'UTF-16' }, + '060e2b34.0101.010c.04010101.09000000' => { Name => 'AFDAndBarData', Unknown => 1 }, + '060e2b34.0101.010c.04010101.0a000000' => { Name => 'PanScanInformation', Unknown => 1 }, + # '060e2b34.0101.010c.04010604.00000000' => { Name => 'VC-1PictureEssenceDescriptors', Type => 'Node' }, + '060e2b34.0101.010c.04010604.01000000' => { Name => 'VC-1InitializationMetadata', Type => 'DataStream', Unknown => 1 }, + '060e2b34.0101.010c.04010604.02000000' => { Name => 'VC-1SingleSequence', Type => 'Boolean' }, + '060e2b34.0101.010c.04010604.03000000' => { Name => 'VC-1CodedContentType', Format => 'int8u' }, + '060e2b34.0101.010c.04010604.04000000' => { Name => 'VC-1IdenticalGOP', Type => 'Boolean' }, + '060e2b34.0101.010c.04010604.05000000' => { Name => 'VC-1MaximumGOP', Format => 'int16u' }, + '060e2b34.0101.010c.04010604.06000000' => { Name => 'VC-1BPictureCount', Format => 'int16u' }, + '060e2b34.0101.010c.04010604.07000000' => { Name => 'VC-1AverageBitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)' }, + '060e2b34.0101.010c.04010604.08000000' => { Name => 'VC-1MaximumBitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)' }, + '060e2b34.0101.010c.04010604.09000000' => { Name => 'VC-1Profile', Format => 'int8u' }, + '060e2b34.0101.010c.04010604.0a000000' => { Name => 'VC-1Level', Format => 'int8u' }, + '060e2b34.0101.010c.04020501.07000000' => { Name => 'LinkedTimecodeTrackID', Type => 'UInt32Array', Unknown => 1 }, + '060e2b34.0101.010c.04020501.08000000' => { Name => 'SMPTE337MDataStreamNumber', Format => 'int8u' }, + '060e2b34.0101.010c.04060803.00000000' => { Name => 'ApplicationScheme', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.010c.04060804.00000000' => { Name => 'DescriptiveMetadataScheme', Type => 'UL', Unknown => 1 }, + '060e2b34.0101.010c.04090500.00000000' => { Name => 'UCSEncoding', Type => 'UTF-16' }, + '060e2b34.0101.010c.05200701.0b000000' => { Name => 'LinkedApplicationPlug-InInstanceID', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.010c.05200701.0c000000' => { Name => 'LinkedDescriptiveFrameworkPlug-InID', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.010c.05200701.0d000000' => { Name => 'ApplicationPlug-InInstanceID', Type => 'UUID', Unknown => 1 }, + '060e2b34.0101.010c.05200701.0e000000' => { Name => 'DescriptiveMetadataPlug-InID', Type => 'UUID', Unknown => 1 }, + '060e2b34.0101.010c.05200701.0f000000' => { Name => 'ApplicationEnvironmentID', Type => 'UTF-16' }, + '060e2b34.0101.010c.05200701.10000000' => { Name => 'DescriptiveMetadataApplicationEnvironmentID', Type => 'UTF-16' }, + '060e2b34.0101.010c.05200701.11000000' => { Name => 'LinkedDescriptiveObjectPlug-InID', Type => 'WeakReference', Unknown => 1 }, + '060e2b34.0101.010c.06010103.0e000000' => { Name => 'TimebaseReferenceTrackID', Format => 'int32u' }, + '060e2b34.0101.010c.06010104.010b0000' => { Name => 'ObjectClassDefinition', Type => 'AUID', Unknown => 1 }, + '060e2b34.0101.010c.06010104.020e0000' => { Name => 'ApplicationPlug-InBatch', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.010c.06010104.020f0000' => { Name => 'PackageMarker', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.010c.06010104.02100000' => { Name => 'PackageTimelineMarkerRef', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.010c.06010104.02110000' => { Name => 'RegisterAdministrationObject', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.010c.06010104.02120000' => { Name => 'RegisterEntryAdministrationObject', Type => 'StrongReference', Unknown => 1 }, + '060e2b34.0101.010c.06010104.03060000' => { Name => 'GenericPayloads', Type => 'WeakReferenceBatch', Unknown => 1 }, + '060e2b34.0101.010c.06010104.06110000' => { Name => 'RegisterEntryArray', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.010c.06010104.06120000' => { Name => 'RegisterAdministrationArray', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.010c.06010104.06130000' => { Name => 'ApplicationInformationArray', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.010c.06010104.06140000' => { Name => 'RegisterChildEntryArray', Type => 'StrongReferenceArray', Unknown => 1 }, + '060e2b34.0101.010c.07020101.01060000' => { Name => 'RegisterReleaseDateTime', %timestamp }, + '060e2b34.0101.010c.07020101.01070000' => { Name => 'RegisterItemStatusChangeDateTime', %timestamp }, + # '060e2b34.0101.010c.0d090000.00000000' => { Name => 'ASC', Type => 'Node' }, + # '060e2b34.0101.010c.0d0a0000.00000000' => { Name => 'AES', Type => 'Node' }, + # '060e2b34.0101.010c.0d0b0000.00000000' => { Name => 'DutchGuild', Type => 'Node' }, + # '060e2b34.0101.010c.0e130000.00000000' => { Name => 'NLTechnologyLLC', Type => 'Node' }, + # '060e2b34.0101.010c.0e140000.00000000' => { Name => 'HarrisCorporation', Type => 'Node' }, + + # data types (not used as tag ID's, right?) + # '060e2b34.0104.0101.01010100.00000000' => { Name => 'UInt8' }, + # '060e2b34.0104.0101.01010200.00000000' => { Name => 'UInt16' }, + # '060e2b34.0104.0101.01010300.00000000' => { Name => 'UInt32' }, + # '060e2b34.0104.0101.01010400.00000000' => { Name => 'UInt64' }, + # '060e2b34.0104.0101.01010500.00000000' => { Name => 'Int8' }, + # '060e2b34.0104.0101.01010600.00000000' => { Name => 'Int16' }, + # '060e2b34.0104.0101.01010700.00000000' => { Name => 'Int32' }, + # '060e2b34.0104.0101.01010800.00000000' => { Name => 'Int64' }, + # '060e2b34.0104.0101.01012001.00000000' => { Name => 'Position' }, + # '060e2b34.0104.0101.01012002.00000000' => { Name => 'LengthType' }, + # '060e2b34.0104.0101.01030100.00000000' => { Name => 'AUID' }, + # '060e2b34.0104.0101.01030200.00000000' => { Name => 'PackageID' }, + # '060e2b34.0104.0101.01040100.00000000' => { Name => 'Boolean' }, + # '060e2b34.0104.0101.01100100.00000000' => { Name => 'UTF16' }, + # '060e2b34.0104.0101.01100200.00000000' => { Name => 'UTF16String' }, + # '060e2b34.0104.0101.01100300.00000000' => { Name => 'ISO7' }, + # '060e2b34.0104.0101.01100400.00000000' => { Name => 'ISO7String' }, + # '060e2b34.0104.0101.01200500.00000000' => { Name => 'UTF7String' }, + # '060e2b34.0104.0101.01300100.00000000' => { Name => 'UMID' }, + # '060e2b34.0104.0101.0201010e.00000000' => { Name => 'RGBACode' }, + # '060e2b34.0104.0101.02010125.00000000' => { Name => 'ChannelStatusModeType' }, + # '060e2b34.0104.0101.03010100.00000000' => { Name => 'Rational' }, + # '060e2b34.0104.0101.03010101.00000000' => { Name => 'Numerator' }, + # '060e2b34.0104.0101.03010102.00000000' => { Name => 'Denominator' }, + # '060e2b34.0104.0101.03010200.00000000' => { Name => 'ProductVersionType' }, + # '060e2b34.0104.0101.03010201.00000000' => { Name => 'Major' }, + # '060e2b34.0104.0101.03010202.00000000' => { Name => 'Minor' }, + # '060e2b34.0104.0101.03010203.00000000' => { Name => 'Patch' }, + # '060e2b34.0104.0101.03010204.00000000' => { Name => 'Build' }, + # '060e2b34.0104.0101.03010205.00000000' => { Name => 'Release' }, + # '060e2b34.0104.0101.03010300.00000000' => { Name => 'VersionType' }, + # '060e2b34.0104.0101.03010400.00000000' => { Name => 'RGBALayoutItem' }, + # '060e2b34.0104.0101.03010501.00000000' => { Name => 'Year' }, + # '060e2b34.0104.0101.03010502.00000000' => { Name => 'Month' }, + # '060e2b34.0104.0101.03010503.00000000' => { Name => 'Day' }, + # '060e2b34.0104.0101.03010601.00000000' => { Name => 'Hours' }, + # '060e2b34.0104.0101.03010602.00000000' => { Name => 'Minutes' }, + # '060e2b34.0104.0101.03010603.00000000' => { Name => 'Seconds' }, + # '060e2b34.0104.0101.03010604.00000000' => { Name => 'msBy4' }, + # '060e2b34.0104.0101.03010700.00000000' => { Name => 'Timestamp' }, + # '060e2b34.0104.0101.04010100.00000000' => { Name => 'UInt8Array' }, + # '060e2b34.0104.0101.04010300.00000000' => { Name => 'Int32Array' }, + # '060e2b34.0104.0101.04010400.00000000' => { Name => 'Int64Array' }, + # '060e2b34.0104.0101.04010600.00000000' => { Name => 'AUIDArray' }, + # '060e2b34.0104.0101.04020100.00000000' => { Name => 'RGBALayout' }, + # '060e2b34.0104.0101.04020200.00000000' => { Name => 'RationalArray' }, + # '060e2b34.0104.0101.04030200.00000000' => { Name => 'UInt32Batch' }, + # '060e2b34.0104.0101.04100100.00000000' => { Name => 'DataValue' }, + # '060e2b34.0104.0101.04100200.00000000' => { Name => 'Stream' }, + # '060e2b34.0104.0101.04100300.00000000' => { Name => 'Indirect' }, + # '060e2b34.0104.0101.04100400.00000000' => { Name => 'Opaque' }, + # '060e2b34.0104.0101.05010000.00000000' => { Name => 'WeakRef' }, + # '060e2b34.0104.0101.05020000.00000000' => { Name => 'StrongRef' }, + # '060e2b34.0104.0101.05021400.00000000' => { Name => 'StrongReferenceTrack' }, + # '060e2b34.0104.0101.05060500.00000000' => { Name => 'StrongReferenceVectorTrack' }, + # '060e2b34.0204.0107.0d010301.027e0100' => { Name => 'EncryptedTriplet' }, + + '060e2b34.0205.0101.0d010201.01020100' => { Name => 'OpenHeader', %header }, + '060e2b34.0205.0101.0d010201.01020200' => { Name => 'ClosedHeader', %header }, + '060e2b34.0205.0101.0d010201.01020300' => { Name => 'OpenCompleteHeader', %header }, + '060e2b34.0205.0101.0d010201.01020400' => { Name => 'ClosedCompleteHeader', %header }, + '060e2b34.0205.0101.0d010201.01030100' => { Name => 'OpenBodyPartition', Unknown => 1 }, + '060e2b34.0205.0101.0d010201.01030200' => { Name => 'ClosedBodyPartition', Unknown => 1 }, + '060e2b34.0205.0101.0d010201.01030300' => { Name => 'OpenCompleteBodyPartition', Unknown => 1 }, + '060e2b34.0205.0101.0d010201.01030400' => { Name => 'ClosedCompleteBodyPartition', Unknown => 1 }, + '060e2b34.0205.0101.0d010201.01040200' => { Name => 'Footer', Unknown => 1 }, + '060e2b34.0205.0101.0d010201.01040400' => { Name => 'CompleteFooter', Unknown => 1 }, + + '060e2b34.0205.0101.0d010201.01050100' => { Name => 'Primer', SubDirectory => { TagTable => 'Image::ExifTool::MXF::Main', ProcessProc => \&ProcessPrimer } }, + + '060e2b34.0205.0101.0d010201.01110000' => { Name => 'RandomIndexMetadataV10', Type => 'FixedPack', Unknown => 1 }, + '060e2b34.0205.0101.0d010201.01110100' => { Name => 'RandomIndexMetadata', Type => 'FixedPack', Unknown => 1 }, + '060e2b34.0206.0101.0d010200.00000000' => { Name => 'PartitionMetadata', Type => 'FixedPack', Unknown => 1 }, + + '060e2b34.0253.0101.0d010101.01010200' => { Name => 'StructuralComponent', %localSet }, + '060e2b34.0253.0101.0d010101.01010f00' => { Name => 'SequenceSet', %localSet }, + # Note: SourceClip is actually a local set, but it isn't decoded because it has a Duration + # tag which gets confused with the other Duration tags (also, my technique of determining + # the corresponding EditRate doesn't seem to work for this Duration) + '060e2b34.0253.0101.0d010101.01011100' => { Name => 'SourceClip', Unknown => 1 }, + '060e2b34.0253.0101.0d010101.01011400' => { Name => 'TimecodeComponent', %localSet }, + '060e2b34.0253.0101.0d010101.01011800' => { Name => 'ContentStorageSet', %localSet }, + '060e2b34.0253.0101.0d010101.01012300' => { Name => 'EssenceContainerDataSet', %localSet }, + '060e2b34.0253.0101.0d010101.01012500' => { Name => 'FileDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01012700' => { Name => 'GenericPictureEssenceDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01012800' => { Name => 'CDCIEssenceDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01012900' => { Name => 'RGBAEssenceDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01012f00' => { Name => 'Preface', %localSet }, + '060e2b34.0253.0101.0d010101.01013000' => { Name => 'Identification', %localSet }, + '060e2b34.0253.0101.0d010101.01013200' => { Name => 'NetworkLocator', %localSet }, + '060e2b34.0253.0101.0d010101.01013300' => { Name => 'TextLocator', %localSet }, + '060e2b34.0253.0101.0d010101.01013400' => { Name => 'GenericPackage', %localSet }, + '060e2b34.0253.0101.0d010101.01013600' => { Name => 'MaterialPackage', %localSet }, + '060e2b34.0253.0101.0d010101.01013700' => { Name => 'SourcePackage', %localSet }, + '060e2b34.0253.0101.0d010101.01013800' => { Name => 'GenericTrack', %localSet }, + '060e2b34.0253.0101.0d010101.01013900' => { Name => 'EventTrack', %localSet }, + '060e2b34.0253.0101.0d010101.01013a00' => { Name => 'StaticTrack', %localSet }, + '060e2b34.0253.0101.0d010101.01013b00' => { Name => 'Track', %localSet }, + '060e2b34.0253.0101.0d010101.01014100' => { Name => 'DMSegment', %localSet }, + '060e2b34.0253.0101.0d010101.01014200' => { Name => 'GenericSoundEssenceDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01014300' => { Name => 'GenericDataEssenceDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01014400' => { Name => 'MultipleDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01014500' => { Name => 'DMSourceClip', %localSet }, + '060e2b34.0253.0101.0d010101.01014700' => { Name => 'AES3PCMDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01014800' => { Name => 'WaveAudioDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01015100' => { Name => 'MPEG2VideoDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01015a00' => { Name => 'JPEG2000PictureSubDescriptor', %localSet }, + '060e2b34.0253.0101.0d010101.01015b00' => { Name => 'VBIDataDescriptor', %localSet }, + # ignore the index table sets because they contain no useful metadata + '060e2b34.0253.0101.0d010201.01100000' => { Name => 'V10IndexTableSegment', Unknown => 1 }, + '060e2b34.0253.0101.0d010201.01100100' => { Name => 'IndexTableSegment', Unknown => 1 }, + '060e2b34.0253.0101.0d010400.00000000' => { Name => 'DMSet', %localSet }, + '060e2b34.0253.0101.0d010401.00000000' => { Name => 'DMFramework', %localSet }, + + # DMS1 local sets (ref 12) + '060e2b34.0253.0101.0d010401.01010100' => { Name => 'ProductionFramework', %localSet }, + '060e2b34.0253.0101.0d010401.01010200' => { Name => 'ClipFramework', %localSet }, + '060e2b34.0253.0101.0d010401.01010300' => { Name => 'SceneFramework', %localSet }, + '060e2b34.0253.0101.0d010401.01100100' => { Name => 'Titles', %localSet }, + '060e2b34.0253.0101.0d010401.01110100' => { Name => 'Identification', %localSet }, + '060e2b34.0253.0101.0d010401.01120100' => { Name => 'GroupRelationship', %localSet }, + '060e2b34.0253.0101.0d010401.01130100' => { Name => 'Branding', %localSet }, + '060e2b34.0253.0101.0d010401.01140100' => { Name => 'Event', %localSet }, + '060e2b34.0253.0101.0d010401.01140200' => { Name => 'Publication', %localSet }, + '060e2b34.0253.0101.0d010401.01150100' => { Name => 'Award', %localSet }, + '060e2b34.0253.0101.0d010401.01160100' => { Name => 'CaptionDescription', %localSet }, + '060e2b34.0253.0101.0d010401.01170100' => { Name => 'Annotation', %localSet }, + '060e2b34.0253.0101.0d010401.01170200' => { Name => 'SettingPeriod', %localSet }, + '060e2b34.0253.0101.0d010401.01170300' => { Name => 'Scripting', %localSet }, + '060e2b34.0253.0101.0d010401.01170400' => { Name => 'Classification', %localSet }, + '060e2b34.0253.0101.0d010401.01170500' => { Name => 'Shot', %localSet }, + '060e2b34.0253.0101.0d010401.01170600' => { Name => 'KeyPoint', %localSet }, + '060e2b34.0253.0101.0d010401.01170800' => { Name => 'CueWords', %localSet }, + '060e2b34.0253.0101.0d010401.01180100' => { Name => 'Participant', %localSet }, + '060e2b34.0253.0101.0d010401.01190100' => { Name => 'ContactsList', %localSet }, + '060e2b34.0253.0101.0d010401.011a0200' => { Name => 'Person', %localSet }, + '060e2b34.0253.0101.0d010401.011a0300' => { Name => 'Organisation', %localSet }, + '060e2b34.0253.0101.0d010401.011a0400' => { Name => 'Location', %localSet }, + '060e2b34.0253.0101.0d010401.011b0100' => { Name => 'Address', %localSet }, + '060e2b34.0253.0101.0d010401.011b0200' => { Name => 'Communications', %localSet }, + '060e2b34.0253.0101.0d010401.011c0100' => { Name => 'Contract', %localSet }, + '060e2b34.0253.0101.0d010401.011c0200' => { Name => 'Rights', %localSet }, + '060e2b34.0253.0101.0d010401.011d0100' => { Name => 'PictureFormat', %localSet }, + '060e2b34.0253.0101.0d010401.011e0100' => { Name => 'DeviceParameters', %localSet }, + '060e2b34.0253.0101.0d010401.011f0100' => { Name => 'NameValue', %localSet }, + '060e2b34.0253.0101.0d010401.01200100' => { Name => 'Processing', %localSet }, + '060e2b34.0253.0101.0d010401.01200200' => { Name => 'Projects', %localSet }, + + '060e2b34.0253.0101.0d010401.02010000' => { Name => 'CryptographicFramework', %localSet }, + '060e2b34.0253.0101.0d010401.02020000' => { Name => 'CryptographicContext', %localSet }, + + '060e2b34.0253.0101.7f000000.00000000' => { Name => 'DefaultObject', Unknown => 1 }, + '060e2b34.0401.0107.02090201.01000000' => { Name => 'CipherAlgorithmAES128CBC', Type => 'Label', Unknown => 1 }, + '060e2b34.0401.0107.02090202.01000000' => { Name => 'HMACAlgorithmSHA1128', Type => 'Label', Unknown => 1 }, + '060e2b34.0401.0107.0d010301.020b0100' => { Name => 'EncryptedContainerLabel', Type => 'Label', Unknown => 1 }, + '060e2b34.0401.0107.0d010401.02010100' => { Name => 'CryptographicFrameworkLabel', Type => 'Label', Unknown => 1 }, +); + +# header information +%Image::ExifTool::MXF::Header = ( + GROUPS => { 2 => 'Video' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + 0 => { + Name => 'MXFVersion', + Format => 'int16u[2]', + ValueConv => '$val =~ tr/ /./; $val', + }, + # 4 - int32u: KAGSize + # 8 - int64u: bytes from first header + # 16 - int64u: bytes from previous partition + 24 => { # bytes to footer from start of header + Name => 'FooterPosition', + Format => 'int64u', + RawConv => '$$self{MXFInfo}{FooterPos} = $val; undef', + }, + 32 => { # number of bytes in header, beginning at the start of the Primer + Name => 'HeaderSize', + Format => 'int64u', + # use this opportinity to also save our header type + RawConv => q{ + $$self{MXFInfo}{HeaderType} = $$self{DIR_NAME}; + $$self{MXFInfo}{HeaderSize} = $val; + return undef; # I don't think anyone would care about this + }, + }, + # ...plus more stuff we don't care about +); + +#------------------------------------------------------------------------------ +# Convert 16 bytes to UL format +# Inputs: 0) 16-byte value +# Returns: UL string +sub UL($) +{ + return join('.', unpack('H8H4H4H8H8', shift));; +} + +#------------------------------------------------------------------------------ +# Convert latitude and/or longitude in [d]ddmmss(N|S|E|W) format +# Inputs: 0) string value to convert +# Returns: numerical lat and/or long +sub ConvLatLon($) +{ + my $val = shift; + my (@convVal, $ne); + foreach $ne ('NS','EW') { + next unless $val =~ /(\d{2,3})(\d{2})(\d{2})([$ne])/; + push @convVal, $1 + ($2 + $3 / 60) / 60 * (($4 eq 'N' or $4 eq 'E') ? 1 : -1); + } + return join ' ', @convVal; +} + +#------------------------------------------------------------------------------ +# Read MXF-specific Format types +# Inputs: 0) ExifTool ref, 1) value, 2) MXF value type +# Returns: formatted value +# Note: All types recognized here should be defined in the %knownType lookup +sub ReadMXFValue($$$) +{ + my ($et, $val, $type) = @_; + my $len = length($val); + local $_; + + if ($type eq 'UTF-16') { + $val = $et->Decode($val, 'UCS2'); # (until we handle UTF-16 properly) + } elsif ($type eq 'ProductVersion') { + my @a = unpack('n*', $val); + push @a, 0 while @a < 5; + $a[4] = { 0 => 'unknown', 1 => 'released', 2 => 'debug', 3 => 'patched', + 4 => 'beta', 5 => 'private build' }->{$a[4]} || "unknown $a[4]"; + $val = join('.', @a[0..3]) . ' ' . $a[4]; + } elsif ($type eq 'VersionType') { + $val = join('.',unpack('C*',$val)); + } elsif ($type eq 'Timestamp') { + my @a = unpack('nC*',$val); + my @max = (3000,12,31,24,59,59,249); + foreach (@a) { + last unless @max and $_ <= $max[0]; + shift @max; + } + if (@max) { + $val = 'Invalid (0x' . unpack('H*',$val) . ')'; + } else { + $a[6] *= 4; + $val = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d.%.3d', @a); + } + } elsif ($type eq 'Position' or $type eq 'Length') { + $val = Get64u(\$val, 0); + } elsif ($type eq 'Boolean') { + $val = $val eq "\0" ? 'False' : 'True'; + } elsif ($type =~ /^(Alt|Lat|Lon)$/ and $len == 4) { + # split into nibbles after swapping byte order (see reference 8b) + $val = unpack('H*', pack('N', unpack('V', $val))); + if ($type eq 'Alt') { + # drop satellite information and only support altitudes up to +/-99999 m + $val = "$val (from earth centre)" unless $val =~ s/^[abc]..// or $val =~ s/^[def]../-/; + } else { + $val =~ s/(...)/$1./; # insert decimal point after 3rd character + if ($type eq 'Lat') { + $val =~ s/^f/-/; # south with first digit = 0 + } else { + $val =~ s/^e/-/; # east with first digit = 0 + $val =~ s/^f/-1/; # east with first digit = 1 + } + } + } elsif ($type =~ /(Array|Batch)/ and $len > 16) { + my ($count, $size) = unpack('NN', $val); + # validate data length + $len == 8 + $count * $size or $et->WarnOnce("Bad array or batch size"); + my ($i, @a); + for ($i=0; $i<$count; ++$i) { + my $pos = 8 + $i * $size; + last if $pos + $size > $len; + push @a, substr($val, $pos, $size); + } + if ($type =~ /^StrongReference/) { + $_ = join('-', unpack('H8H4H4H4H12', $_)) foreach @a; + } elsif ($type eq 'BatchOfUL' or $type =~ /^WeakReference/) { + $_ = ReadMXFValue($et, $_, 'UL') foreach @a; + } + $val = \@a; + } elsif ($len == 32) { + # 32-byte types include UMID, PackageID + $val = join('.', unpack('H8H4H4H8', $val)) . ' ' . + join(' ', unpack('x12H2H6', $val)) . ' ' . + join('-', unpack('x16H8H4H4H4H12', $val)); + } elsif ($len == 16) { + # convert remaining 16-byte known types as GUID or UL + # - this covers all UID's, UL, Label, WeakReference, StrongReference + if ($type eq 'UL' or $type eq 'WeakReference') { + # reverse high/low word if a reversed GUID is stored in a UL type + # (see ref 8b, section 5.5.8) + # - a GUID will have high bit of byte 8 set (reversed, this is in byte 0) + return UL($val) unless unpack('C', $val) & 0x80; + $val = substr($val, 8) . substr($val, 0, 8); + } + $val = join('-', unpack('H8H4H4H4H12', $val)); # compact GUID format + } else { + # Note: a 64-byte extended UMID contains date/time, GPS coordinates, etc, + # which would could be decoded here (ref 8b, section 5)... + $val = unpack('H*', $val); + } + return $val; +} + +#------------------------------------------------------------------------------ +# Process MXF Primer (to build lookup for translating local tag ID's) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessPrimer($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $end = $$dirInfo{DirLen}; + return 0 unless $end > 8; + my $count = Get32u($dataPt, 0); + my $size = Get32u($dataPt, 4); + return 0 unless $size >= 18; + $et->VerboseDir('MXF Primer', $count); + my $verbose = $et->Options('Verbose'); + my $primer = $$et{MXFInfo}{Primer}; + my $pos = 8; + my $i; + for ($i=0; $i<$count; ++$i) { + last if $pos + $size > $end; + my $local = Get16u($dataPt, $pos); + my $global = UL(substr($$dataPt, $pos + 2, 16)); + $pos += $size; + # save this entry in the primer lookup table + $$primer{$local} = $global; + # print lookup details in verbose mode + next unless $verbose; + my $indx = $i . ')'; + $indx .= ' ' if length($indx) < 3; + $et->VPrint(0, sprintf(" | $indx 0x%.4x => '${global}'\n", $local)); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Read tags from an MXF local set +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessLocalSet($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + local $_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $end = $$dirInfo{DirLen}; + my $mxfInfo = $$et{MXFInfo}; + my $primer = $$mxfInfo{Primer}; + my (@strongRef, @groups, $instance, $editRate, $trackID, $langCode, $textLang); + + $et->VerboseDir('MXF LocalSet', undef, $end); + + # loop through all tags in this local set + my $pos = 0; + while ($pos + 4 < $end) { + my $loc = Get16u($dataPt, $pos); # get key (local tag ID) + my $len = Get16u($dataPt, $pos + 2); # get length + $pos += 4; + last if $pos + $len > $end; + my $tag = $$primer{$loc}; + my ($extra, $val, $type, $langInfo); + if ($tag and $$tagTablePtr{$tag}) { + $extra = sprintf(', Local 0x%.4x', $loc); + } else { + $tag = $loc; + # $et->WarnOnce('Missing local key for at least one tag'); + $extra = ', NOT IN PRIMER!'; + } + my $tagInfo = $$tagTablePtr{$tag}; + # handle our MXF-specific format types + if ($tagInfo) { + $type = $$tagInfo{Type}; + if ($type and $knownType{$type}) { + $val = ReadMXFValue($et, substr($$dataPt, $pos, $len), $type); + push @strongRef, (ref $val ? @$val : $val) if $type =~ /^StrongReference/; + # remember instance UID of this local set + if ($$tagInfo{Name} eq 'InstanceUID') { + $instance = $val; + # set language code for text + # (only works if InstanceUID comes before text) + $textLang = $$mxfInfo{$instance}{LangCode} if $$mxfInfo{$instance}; + } elsif ($type eq 'UTF-16' and $textLang) { + $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $textLang); + } + } + } + # get tagInfo ref the standard way to handle Unknown tags + $tagInfo = $langInfo || $et->GetTagInfo($tagTablePtr, $tag); + # set Binary flag to extract all unknown-format tags as Binary data + if ($tagInfo and $$tagInfo{Unknown} and not defined $$tagInfo{Binary}) { + $$tagInfo{Binary} = not ($$tagInfo{Format} or ($type and $knownType{$type})); + } + my $key = $et->HandleTag($tagTablePtr, $tag, $val, + Extra => $extra, + TagInfo => $tagInfo, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos, + Size => $len, + ProcessProc => \&ProcessLocalSet, + ); + $pos += $len; + next unless $key; + # save information to allow later fixup of durations and group1 names + # (necessary because we don't have all the information we need + # to do this on the fly when the file is parsed linearly) + push @groups, $$et{TAG_EXTRA}{$key}; + next unless $tagInfo; + my $name = $$tagInfo{Name}; + if ($$tagInfo{IsDuration}) { + $$mxfInfo{FixDuration}{$key} = 1; + } elsif ($$tagInfo{LanguageCode}) { + $langCode = $$et{VALUE}{$key}; + } elsif ($name eq 'EditRate') { + $editRate = $$et{VALUE}{$key}; + } elsif ($name =~ /TrackID$/) { + $trackID = $$et{VALUE}{$key}; + unless ($$mxfInfo{Group1}{$trackID}) { + # save lookup to convert TrackID to our group 1 name + $$mxfInfo{Group1}{$trackID} = 'Track' . ++$$mxfInfo{NumTracks}; + } + } + } + + # save object information now that we know the instance UID + if ($instance) { + my $objInfo = $$mxfInfo{$instance}; + if ($objInfo) { + push @{$$objInfo{StrongRef}}, @strongRef; + push @{$$objInfo{Groups}}, @groups; + } else { + $objInfo = $$mxfInfo{$instance} = { + StrongRef => \@strongRef, + Groups => \@groups, + }; + } + # save instance UID's in groups hash (used to remove duplicates later) + $$_{UID} = $instance foreach @groups; + $$objInfo{Name} = $$et{DIR_NAME}; + $$objInfo{TrackID} = $trackID if defined $trackID; + $$objInfo{EditRate} = $editRate if $editRate; + if ($langCode) { + $$objInfo{LangCode} = $langCode; + } else { + $langCode = $$objInfo{LangCode}; + } + if ($langCode) { + # pre-set language code in all children + my $ul; + foreach $ul (@{$$objInfo{StrongRef}}) { + my $obj = $$mxfInfo{$ul}; + $obj or $obj = $$mxfInfo{$ul} = { StrongRef => [], Groups => [], Name => 'XXX' }; + $$obj{LangCode} = $langCode; + } + } + # save instance UID's of Preface's + push @{$$mxfInfo{Preface}}, $instance if $$et{DIR_NAME} eq 'Preface'; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Walk MXF tree to set group names +# Inputs: 0) MXF information hash ref, 1) object instance UID, +# 2) path information hash ref (only for group 5 names), 3) track ID number +# Notes: also generates lookup table for EditRate based on group 1 name +# and the instance UID of the SequenceSet with the preferred Duration value +sub SetGroups($$;$$) +{ + my ($mxfInfo, $instance, $pathInfo, $trackID) = @_; + my $objInfo = $$mxfInfo{$instance}; + return unless $objInfo and not $$objInfo{DidGroups}; + $$objInfo{DidGroups} = 1; # avoid reprocessing this object + $trackID = $$objInfo{TrackID} if defined $$objInfo{TrackID}; + my ($ul, $g1, $g5, $groups, $path, $nameCount, $setSource); + # generate group 1 name for different tracks + if (defined $trackID) { + $$objInfo{TrackID} = $trackID; + $g1 = $$mxfInfo{Group1}{$trackID}; + # build a lookup to determine edit rates based on group 1 name + my $editRate = $$objInfo{EditRate}; + $$mxfInfo{EditRate}{$g1} = $editRate if defined $editRate; + # save the TimeCodeComponent instance UID (for determining Duration later) + if ($$objInfo{Name} eq 'TimecodeComponent') { + my $inWhat = $$mxfInfo{InSource} ? 'Source' : 'Other'; + $$mxfInfo{BestDuration}{$inWhat} = $instance; + } + } + # set flag if we are in the SourcePackage (contains our preferred TimecodeComponent) + my $name = $$objInfo{Name}; + $setSource = $$mxfInfo{InSource} = 1 if $name eq 'SourcePackage'; + + # generate group 5 path names if requested + if ($pathInfo) { + $nameCount = $$pathInfo{NameCount} || { }; + $path = $$pathInfo{Path}; + $$nameCount{$name} = ($$nameCount{$name} || 0) + 1; + push @$path, $name . $$nameCount{$name}; + $g5 = join '-', @$path; + $$pathInfo{NameCount} = { }; # use new name count for child objects + } + foreach $groups (@{$$objInfo{Groups}}) { + $$groups{G1} = $g1 if $g1; + $$groups{G5} = $g5 if $g5; + } + # walk through remaining objects in tree + foreach $ul (@{$$objInfo{StrongRef}}) { + SetGroups($mxfInfo, $ul, $pathInfo, $trackID); + } + if ($pathInfo) { + pop @$path; + $$pathInfo{NameCount} = $nameCount; + } + delete $$mxfInfo{InSource} if $setSource; +} + +#------------------------------------------------------------------------------ +# Convert all duration values to seconds +# Inputs: 0) ExifTool object ref, 1) MXF information hash ref +sub ConvertDurations($$) +{ + my ($et, $mxfInfo) = @_; + my $valueHash = $$et{VALUE}; + my $infoHash = $$et{TAG_INFO}; + my $tagExtra = $$et{TAG_EXTRA}; + my $editHash = $$mxfInfo{EditRate}; + my ($tag, $key, $i); + foreach $tag (keys %{$$mxfInfo{FixDuration}}) { + # loop through all instances of this tag name + for ($i=0, $key=$tag; ; ++$i, $key="$tag ($i)") { + my $tagInfo = $$infoHash{$key} or last; + next unless $$tagInfo{IsDuration}; # test IsDuration flag to be sure + my $g1 = $$tagExtra{$key}{G1} or next; + my $editRate = $$editHash{$g1}; + $$valueHash{$key} /= $editRate if $editRate; + } + } +} + +#------------------------------------------------------------------------------ +# Read information in a MXF file +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid MXF file +sub ProcessMXF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my $unknown = $et->Options('Unknown'); + my ($buff, $preface, $n, $headerEnd, $footerPos); + + # read enough to allow skipping over run-in if it exists + $raf->Read($buff, 65547) or return 0; + $buff =~ /\x06\x0e\x2b\x34\x02\x05\x01\x01\x0d\x01\x02/g or return 0; + my $start = pos($buff) - 11; + + $et->SetFileType(); + SetByteOrder('MM'); + $raf->Seek($start, 0) or $et->Warn('Seek error'), return 1; + my $tagTablePtr = GetTagTable('Image::ExifTool::MXF::Main'); + + # determine header length and type + + # initialize MXF information lookups + my %mxfInfo = ( + Primer => { }, # convert local keys to global UL + Group1 => { }, # group 1 names base on TrackID + NumTracks => 0, # counts number of tracks in file + FixDuration => { }, # names of all Duration tags that need fixing + Preface => [ ], # instance UID's for all Preface objects + ); + $$et{MXFInfo} = \%mxfInfo; + + # set group 1 name for all tags (so we can overwrite with track number later) + $$et{SET_GROUP1} = 'MXF'; + + for (;;) { + my $pos = $raf->Tell(); + # did we just finish parsing the header partition? + if ($headerEnd and $pos >= $headerEnd) { + # all done if it was a closed and complete header + last if $mxfInfo{HeaderType} eq 'ClosedCompleteHeader' and not $verbose; + undef $headerEnd; # (only test this once) + # skip directly to footer if possible + if ($footerPos and $footerPos > $pos and (not $verbose or not $unknown)) { + $et->VPrint(0, "[Skipping to footer. Use Unknown option to parse body partitions]\n"); + $raf->Seek($footerPos, 0) or last; + $pos = $footerPos; + } + } + # read the next KLV Key and first byte of Value + $raf->Read($buff, 17) == 17 or last; + my $tag = substr($buff, 0, 16); # get KLV Key (global tag ID) + my $len = Get8u(\$buff, 16); # get KLV Length + my $n; + if ($len >= 0x80) { + $n = $len & 0x7f; + $raf->Read($buff, $n) == $n or last; + $len = 0; + foreach $b (unpack 'C*', $buff) { + $len = $len * 256 + $b; + } + } else { + $n = 0; + } + # convert tag ID to ASCII UL notation + my $ul = UL($tag); + my $tagInfo = $$tagTablePtr{$ul}; + if (not $tagInfo and $ul =~ /^060e2b34\.0253\.0101\.(0d|0f)/ and + ($1 eq '0d' or $verbose or $unknown)) + { + # generate some unknown set tags automatically + my $name = $1 eq '0d' ? 'UserOrganizationPublicUse' : 'Experimental'; + $tagInfo = { Name => $name, %localSet }; + AddTagToTable($tagTablePtr, $ul, $tagInfo); + } + my ($val, $dataPt); + if ($tagInfo and not $$tagInfo{Unknown} and $len < 10000000) { + # save information about header/footer positions for skipping over body partitions + if ($$tagInfo{Name} eq 'Primer' and $mxfInfo{HeaderSize}) { + # footer position relative to header start and only valid if non-zero + $footerPos = $start + $mxfInfo{FooterPos} if $mxfInfo{FooterPos}; + # header length is relative to start of Primer + $headerEnd = $pos + $mxfInfo{HeaderSize}; + } elsif ($$tagInfo{IsHeader}) { + # save position of header start to allow calculation of footer position + $start = $pos; + } + $raf->Read($buff, $len) == $len or last; # get KLV Value + $dataPt = \$buff; + my $type = $$tagInfo{Type}; + $val = ReadMXFValue($et, $buff, $type) if $type and $knownType{$type}; + } elsif (($tagInfo and (not $$tagInfo{Unknown} or $unknown)) or $verbose) { + if ($tagInfo) { + # set Binary flag to extract all unknown-format tags as Binary data + if ($$tagInfo{Unknown} and not defined $$tagInfo{Binary}) { + my $type = $$tagInfo{Type}; + $$tagInfo{Binary} = not ($$tagInfo{Format} or ($type and $knownType{$type})); + } + } else { + my $id = unpack 'H*', $tag; + $tagInfo = { + Name => "MXF_$id", + Description => "MXF $id", + }; + # note: don't add unknown tags to table because we don't + # want them to be extracted with the Unknown option + } + # read the first 64kB max + my $n = $len < 65536 ? $len : 65536; + $raf->Read($val, $n) == $n or last; + $dataPt = \$val; + my $more = $len - $n; + $raf->Seek($more, 1) or last if $more; + } else { + $raf->Seek($len, 1) or last; # skip this value + next; + } + $et->HandleTag($tagTablePtr, $ul, $val, + TagInfo => $tagInfo, + DataPt => $dataPt, + DataPos => $pos + 17 + $n, + Size => $len, + ProcessProc => \&ProcessLocalSet, + ); + } + # walk entire MXF object tree to fix family 1 group names + my ($pathInfo, $tag, %did, %bestDur); + $pathInfo = { Path => [ 'MXF' ] } if $et->Options('SavePath'); + foreach $preface (@{$mxfInfo{Preface}}) { + SetGroups(\%mxfInfo, $preface, $pathInfo); + } + # convert Duration values to seconds based on the appropriate EditRate + ConvertDurations($et, \%mxfInfo); + + # remove tags to keep only the one from the most recent instance of the object + my $tagExtra = $$et{TAG_EXTRA}; + my $fileOrder = $$et{FILE_ORDER}; + # also determine our best Duration value + if ($mxfInfo{BestDuration}) { + my $instance = $mxfInfo{BestDuration}{Source} || $mxfInfo{BestDuration}{Other}; + $instance and $bestDur{"Duration $instance"} = 1; + } + # process tags in reverse order to preserve the last found of each identical tag + foreach $tag (sort { $$fileOrder{$b} <=> $$fileOrder{$a} } keys %$tagExtra) { + my $instance = $$tagExtra{$tag}{UID} or next; + delete $$tagExtra{$tag}{UID}; # (no longer needed) + $tag =~ /^(\S+)/; # get tag name without index number + my $utag = "$1 $instance"; # instance-specific tag name + if ($did{$utag}) { + Image::ExifTool::DeleteTag($et, $tag); # delete the duplicate + } else { + $did{$utag} = 1; + if ($bestDur{$utag}) { + # save best duration value + my $val = $$et{VALUE}{$tag}; + $et->HandleTag($tagTablePtr, '060e2b34.0101.0102.07020201.01030000', $val); + } + } + } + + # clean up and return + delete $$et{SET_GROUP1}; + delete $$et{MXFInfo}; + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MXF - Read MXF meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read meta +information from MXF (Material Exchange Format) files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://sourceforge.net/projects/mxflib/> + +=item L<http://www.aafassociation.org/downloads/whitepapers/MXFPhysicalview.pdf> + +=item L<http://archive.nlm.nih.gov/pubs/pearson/MJ2_Metadata2005.pdf> + +=item L<http://www.aafassociation.org/downloads/specifications/AMWA-AS-03-Delivery-Spec-1_0.pdf> + +=item L<http://paul-sampson.ca/private/s385m.pdf> + +=item L<http://avwiki.nl/documents/eg41.pdf> + +=item L<http://avwiki.nl/documents/eg42.pdf> + +=item L<http://www.amwa.tv/downloads/specifications/aafobjectspec-v1.1.pdf> + +=item L<http://www.smpte-ra.org/mdd/RP210v12-publication-20100623.xls> + +=item L<http://rhea.tele.ucl.ac.be:8081/Plone/Members/egoray/thesaurus-dictionnaire-metadata/> + +=item L<http://www.mog-solutions.com/img_upload/PDF/XML%20Schema%20for%20MXF%20Metadata.pdf> + +=item L<http://www.freemxf.org/freemxf_board/viewtopic.php?p=545&sid=00a5c17e07d828c1e93ecdbaed3076f7> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MXF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MacOS.pm b/ExifTool/lib/Image/ExifTool/MacOS.pm new file mode 100644 index 0000000..ab63573 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MacOS.pm @@ -0,0 +1,735 @@ +#------------------------------------------------------------------------------ +# File: MacOS.pm +# +# Description: Read/write MacOS system tags +# +# Revisions: 2017/03/01 - P. Harvey Created +# 2020/10/13 - PH Added ability to read MacOS "._" files +#------------------------------------------------------------------------------ + +package Image::ExifTool::MacOS; +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.12'; + +sub MDItemLocalTime($); +sub ProcessATTR($$$); + +my %mdDateInfo = ( + ValueConv => \&MDItemLocalTime, + PrintConv => '$self->ConvertDateTime($val)', +); + +# Information decoded from Mac OS sidecar files +%Image::ExifTool::MacOS::Main = ( + GROUPS => { 0 => 'File', 1 => 'MacOS' }, + NOTES => q{ + Note that on some filesystems, MacOS creates sidecar files with names that + begin with "._". ExifTool will read these files if specified, and extract + the information listed in the following table without the need for extra + options, but these files are not writable directly. + }, + 2 => { + Name => 'RSRC', + SubDirectory => { TagTable => 'Image::ExifTool::RSRC::Main' }, + }, + 9 => { + Name => 'ATTR', + SubDirectory => { + TagTable => 'Image::ExifTool::MacOS::XAttr', + ProcessProc => \&ProcessATTR, + }, + }, +); + +# "mdls" tags (ref PH) +%Image::ExifTool::MacOS::MDItem = ( + WRITE_PROC => \&Image::ExifTool::DummyWriteProc, + VARS => { NO_ID => 1 }, + GROUPS => { 0 => 'File', 1 => 'MacOS', 2 => 'Other' }, + NOTES => q{ + MDItem tags are extracted using the "mdls" utility. They are extracted if + any "MDItem*" tag or the MacOS group is specifically requested, or by + setting the API L<MDItemTags|../ExifTool.html#MDItemTags> option to 1 or the API L<RequestAll|../ExifTool.html#RequestAll> option to 2 or + higher. Note that these tags do not necessarily reflect the current + metadata of a file -- it may take some time for the MacOS mdworker daemon to + index the file after a metadata change. + }, + MDItemFinderComment => { + Writable => 1, + WritePseudo => 1, + Protected => 1, # (all writable pseudo tags must be protected) + }, + MDItemFSLabel => { + Writable => 1, + WritePseudo => 1, + Protected => 1, # (all writable pseudo tags must be protected) + WriteCheck => '$val =~ /^[0-7]$/ ? undef : "Not an integer in the range 0-7"', + PrintConv => { + 0 => '0 (none)', + 1 => '1 (Gray)', + 2 => '2 (Green)', + 3 => '3 (Purple)', + 4 => '4 (Blue)', + 5 => '5 (Yellow)', + 6 => '6 (Red)', + 7 => '7 (Orange)', + }, + }, + MDItemFSCreationDate => { + Writable => 1, + WritePseudo => 1, + DelCheck => q{"Can't delete"}, + Protected => 1, # (all writable pseudo tags must be protected) + Shift => 'Time', # (but not supported yet) + Notes => q{ + file creation date. Requires "setfile" for writing. Note that when + reading, it may take a few seconds after writing a file before this value + reflects the change. However, L<FileCreateDate|Extra.html> is updated immediately + }, + Groups => { 2 => 'Time' }, + ValueConv => \&MDItemLocalTime, + ValueConvInv => '$val', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + MDItemAcquisitionMake => { Groups => { 2 => 'Camera' } }, + MDItemAcquisitionModel => { Groups => { 2 => 'Camera' } }, + MDItemAltitude => { Groups => { 2 => 'Location' } }, + MDItemAperture => { Groups => { 2 => 'Camera' } }, + MDItemAudioBitRate => { Groups => { 2 => 'Audio' } }, + MDItemAudioChannelCount => { Groups => { 2 => 'Audio' } }, + MDItemAuthors => { Groups => { 2 => 'Author' } }, + MDItemBitsPerSample => { Groups => { 2 => 'Image' } }, + MDItemCity => { Groups => { 2 => 'Location' } }, + MDItemCodecs => { }, + MDItemColorSpace => { Groups => { 2 => 'Image' } }, + MDItemComment => { }, + MDItemContentCreationDate => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemContentCreationDateRanking => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemContentModificationDate => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemContentType => { }, + MDItemContentTypeTree => { }, + MDItemContributors => { }, + MDItemCopyright => { Groups => { 2 => 'Author' } }, + MDItemCountry => { Groups => { 2 => 'Location' } }, + MDItemCreator => { Groups => { 2 => 'Document' } }, + MDItemDateAdded => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemDescription => { }, + MDItemDisplayName => { }, + MDItemDownloadedDate => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemDurationSeconds => { PrintConv => 'ConvertDuration($val)' }, + MDItemEncodingApplications => { }, + MDItemEXIFGPSVersion => { Groups => { 2 => 'Location' }, Description => 'MD Item EXIF GPS Version' }, + MDItemEXIFVersion => { }, + MDItemExposureMode => { Groups => { 2 => 'Camera' } }, + MDItemExposureProgram => { Groups => { 2 => 'Camera' } }, + MDItemExposureTimeSeconds => { Groups => { 2 => 'Camera' } }, + MDItemFlashOnOff => { Groups => { 2 => 'Camera' } }, + MDItemFNumber => { Groups => { 2 => 'Camera' } }, + MDItemFocalLength => { Groups => { 2 => 'Camera' } }, + MDItemFSContentChangeDate => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemFSCreatorCode => { Groups => { 2 => 'Author' } }, + MDItemFSFinderFlags => { }, + MDItemFSHasCustomIcon => { }, + MDItemFSInvisible => { }, + MDItemFSIsExtensionHidden => { }, + MDItemFSIsStationery => { }, + MDItemFSName => { }, + MDItemFSNodeCount => { }, + MDItemFSOwnerGroupID => { }, + MDItemFSOwnerUserID => { }, + MDItemFSSize => { }, + MDItemFSTypeCode => { }, + MDItemGPSDateStamp => { Groups => { 2 => 'Time' } }, + MDItemGPSStatus => { Groups => { 2 => 'Location' } }, + MDItemGPSTrack => { Groups => { 2 => 'Location' } }, + MDItemHasAlphaChannel => { Groups => { 2 => 'Image' } }, + MDItemImageDirection => { Groups => { 2 => 'Location' } }, + MDItemInterestingDateRanking => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemISOSpeed => { Groups => { 2 => 'Camera' } }, + MDItemKeywords => { }, + MDItemKind => { }, + MDItemLastUsedDate => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemLastUsedDate_Ranking => { }, + MDItemLatitude => { Groups => { 2 => 'Location' } }, + MDItemLensModel => { }, + MDItemLogicalSize => { }, + MDItemLongitude => { Groups => { 2 => 'Location' } }, + MDItemMediaTypes => { }, + MDItemNumberOfPages => { }, + MDItemOrientation => { Groups => { 2 => 'Image' } }, + MDItemOriginApplicationIdentifier => { }, + MDItemOriginMessageID => { }, + MDItemOriginSenderDisplayName => { }, + MDItemOriginSenderHandle => { }, + MDItemOriginSubject => { }, + MDItemPageHeight => { Groups => { 2 => 'Image' } }, + MDItemPageWidth => { Groups => { 2 => 'Image' } }, + MDItemPhysicalSize => { Groups => { 2 => 'Image' } }, + MDItemPixelCount => { Groups => { 2 => 'Image' } }, + MDItemPixelHeight => { Groups => { 2 => 'Image' } }, + MDItemPixelWidth => { Groups => { 2 => 'Image' } }, + MDItemProfileName => { Groups => { 2 => 'Image' } }, + MDItemRedEyeOnOff => { Groups => { 2 => 'Camera' } }, + MDItemResolutionHeightDPI => { Groups => { 2 => 'Image' } }, + MDItemResolutionWidthDPI => { Groups => { 2 => 'Image' } }, + MDItemSecurityMethod => { }, + MDItemSpeed => { Groups => { 2 => 'Location' } }, + MDItemStateOrProvince => { Groups => { 2 => 'Location' } }, + MDItemStreamable => { }, + MDItemTimestamp => { Groups => { 2 => 'Time' } }, # (time only) + MDItemTitle => { }, + MDItemTotalBitRate => { }, + MDItemUseCount => { }, + MDItemUsedDates => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemUserDownloadedDate => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemUserDownloadedUserHandle=> { }, + MDItemUserSharedReceivedDate => { }, + MDItemUserSharedReceivedRecipient => { }, + MDItemUserSharedReceivedRecipientHandle => { }, + MDItemUserSharedReceivedSender=> { }, + MDItemUserSharedReceivedSenderHandle => { }, + MDItemUserSharedReceivedTransport => { }, + MDItemUserTags => { + List => 1, + Writable => 1, + WritePseudo => 1, + Protected => 1, # (all writable pseudo tags must be protected) + Notes => q{ + requires "tag" utility for writing -- install with "brew install tag". Note + that user tags may not contain a comma, and that duplicate user tags will + not be written + }, + }, + MDItemVersion => { }, + MDItemVideoBitRate => { Groups => { 2 => 'Video' } }, + MDItemWhereFroms => { }, + MDItemWhiteBalance => { Groups => { 2 => 'Image' } }, + # tags used by Apple Mail on .emlx files + com_apple_mail_dateReceived => { Name => 'AppleMailDateReceived', Groups => { 2 => 'Time' }, %mdDateInfo }, + com_apple_mail_dateSent => { Name => 'AppleMailDateSent', Groups => { 2 => 'Time' }, %mdDateInfo }, + com_apple_mail_flagged => { Name => 'AppleMailFlagged' }, + com_apple_mail_messageID => { Name => 'AppleMailMessageID' }, + com_apple_mail_priority => { Name => 'AppleMailPriority' }, + com_apple_mail_read => { Name => 'AppleMailRead' }, + com_apple_mail_repliedTo => { Name => 'AppleMailRepliedTo' }, + com_apple_mail_isRemoteAttachment => { Name => 'AppleMailIsRemoteAttachment' }, + MDItemAccountHandles => { }, + MDItemAccountIdentifier => { }, + MDItemAuthorEmailAddresses => { }, + MDItemBundleIdentifier => { }, + MDItemContentCreationDate_Ranking=>{Groups=> { 2 => 'Time' }, %mdDateInfo }, + MDItemDateAdded_Ranking => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemEmailConversationID => { }, + MDItemIdentifier => { }, + MDItemInterestingDate_Ranking => { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemIsApplicationManaged => { }, + MDItemIsExistingThread => { }, + MDItemIsLikelyJunk => { }, + MDItemMailboxes => { }, + MDItemMailDateReceived_Ranking=> { Groups => { 2 => 'Time' }, %mdDateInfo }, + MDItemPrimaryRecipientEmailAddresses => { }, + MDItemRecipients => { }, + MDItemSubject => { }, +); + +# "xattr" tags +%Image::ExifTool::MacOS::XAttr = ( + WRITE_PROC => \&Image::ExifTool::DummyWriteProc, + GROUPS => { 0 => 'File', 1 => 'MacOS', 2 => 'Other' }, + VARS => { NO_ID => 1 }, # (id's are too long) + NOTES => q{ + XAttr tags are extracted using the "xattr" utility. They are extracted if + any "XAttr*" tag or the MacOS group is specifically requested, or by setting + the API L<XAttrTags|../ExifTool.html#XAttrTags> option to 1 or the API L<RequestAll|../ExifTool.html#RequestAll> option to 2 or higher. + And they are extracted by default from MacOS "._" files when reading + these files directly. + }, + 'com.apple.FinderInfo' => { + Name => 'XAttrFinderInfo', + ConvertBinary => 1, + # ref https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-9A581/Finder.h + ValueConv => q{ + my @a = unpack('a4a4n3x10nx2N', $$val); + tr/\0//d, $_="'${_}'" foreach @a[0,1]; + return "@a"; + }, + PrintConv => q{ + $val =~ s/^('.*?') ('.*?') //s or return $val; + my ($type, $creator) = ($1, $2); + my ($flags, $y, $x, $exFlags, $putAway) = split ' ', $val; + my $label = ($flags >> 1) & 0x07; + my $flags = DecodeBits((($exFlags<<16) | $flags) & 0xfff1, { + 0 => 'OnDesk', + 6 => 'Shared', + 7 => 'HasNoInits', + 8 => 'Inited', + 10 => 'CustomIcon', + 11 => 'Stationery', + 12 => 'NameLocked', + 13 => 'HasBundle', + 14 => 'Invisible', + 15 => 'Alias', + # extended flags + 22 => 'HasRoutingInfo', + 23 => 'ObjectBusy', + 24 => 'CustomBadge', + 31 => 'ExtendedFlagsValid', + }); + my $str = "Type=$type Creator=$creator Flags=$flags Label=$label Pos=($x,$y)"; + $str .= " Putaway=$putAway" if $putAway; + return $str; + }, + }, + 'com.apple.quarantine' => { + Name => 'XAttrQuarantine', + Writable => 1, + WritePseudo => 1, + WriteCheck => '"May only delete this tag"', + Protected => 1, + Notes => q{ + quarantine information for files downloaded from the internet. May only be + deleted when writing + }, + # ($a[1] is the time when the quarantine tag was set) + PrintConv => q{ + my @a = split /;/, $val; + $a[0] = 'Flags=' . $a[0]; + $a[1] = 'set at ' . ConvertUnixTime(hex $a[1]); + $a[2] = 'by ' . $a[2]; + return join ' ', @a; + }, + PrintConvInv => '$val', + }, + 'com.apple.metadata:com_apple_mail_dateReceived' => { + Name => 'XAttrAppleMailDateReceived', + Groups => { 2 => 'Time' }, + }, + 'com.apple.metadata:com_apple_mail_dateSent' => { + Name => 'XAttrAppleMailDateSent', + Groups => { 2 => 'Time' }, + }, + 'com.apple.metadata:com_apple_mail_isRemoteAttachment' => { + Name => 'XAttrAppleMailIsRemoteAttachment', + }, + 'com.apple.metadata:kMDItemDownloadedDate' => { + Name => 'XAttrMDItemDownloadedDate', + Groups => { 2 => 'Time' }, + }, + 'com.apple.metadata:kMDItemFinderComment' => { Name => 'XAttrMDItemFinderComment' }, + 'com.apple.metadata:kMDItemWhereFroms' => { Name => 'XAttrMDItemWhereFroms' }, + 'com.apple.metadata:kMDLabel' => { Name => 'XAttrMDLabel', Binary => 1 }, + 'com.apple.ResourceFork' => { Name => 'XAttrResourceFork', Binary => 1 }, + 'com.apple.lastuseddate#PS' => { + Name => 'XAttrLastUsedDate', + Groups => { 2 => 'Time' }, + # (first 4 bytes are date/time. Not sure what remaining 12 bytes are for) + RawConv => 'ConvertUnixTime(unpack("V",$$val))', + PrintConv => '$self->ConvertDateTime($val)', + }, +); + +#------------------------------------------------------------------------------ +# Convert OS MDItem time string to standard EXIF-formatted local time +# Inputs: 0) time string (eg. "2017-02-21 17:21:43 +0000") +# Returns: EXIF-formatted local time string with timezone +sub MDItemLocalTime($) +{ + my $val = shift; + $val =~ tr/-/:/; + $val =~ s/ ?([-+]\d{2}):?(\d{2})/$1:$2/; + # convert from UTC to local time + if ($val =~ /\+00:00$/) { + my $time = Image::ExifTool::GetUnixTime($val); + $val = Image::ExifTool::ConvertUnixTime($time, 1) if $time; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Set MacOS MDItem and XAttr tags from new tag values +# Inputs: 0) ExifTool ref, 1) file name, 2) list of tags to set +# Returns: 1=something was set OK, 0=didn't try, -1=error (and warning set) +# Notes: There may be errors even if 1 is returned +sub SetMacOSTags($$$) +{ + my ($et, $file, $setTags) = @_; + my $result = 0; + my $tag; + + foreach $tag (@$setTags) { + my ($nvHash, $f, $v, $attr, $cmd, $err, $silentErr); + my $val = $et->GetNewValue($tag, \$nvHash); + next unless $nvHash; + my $overwrite = $et->IsOverwriting($nvHash); + unless ($$nvHash{TagInfo}{List}) { + next unless $overwrite; + if ($overwrite < 0) { + my $operation = $$nvHash{Shift} ? 'Shifting' : 'Conditional replacement'; + $et->Warn("$operation of MacOS $tag not yet supported"); + next; + } + } + if ($tag eq 'MDItemFSCreationDate' or $tag eq 'FileCreateDate') { + ($f = $file) =~ s/'/'\\''/g; + # convert to local time if value has a time zone + if ($val =~ /[-+Z]/) { + my $time = Image::ExifTool::GetUnixTime($val, 1); + $val = Image::ExifTool::ConvertUnixTime($time, 1) if $time; + } + $val =~ s{(\d{4}):(\d{2}):(\d{2})}{$2/$3/$1}; # reformat for setfile + $cmd = "/usr/bin/setfile -d '${val}' '${f}'"; + } elsif ($tag eq 'MDItemUserTags') { + # (tested with "tag" version 0.9.0) + ($f = $file) =~ s/'/'\\''/g; + my @vals = $et->GetNewValue($nvHash); + if ($overwrite < 0 and @{$$nvHash{DelValue}}) { + # delete specified tags + my @dels = @{$$nvHash{DelValue}}; + s/'/'\\''/g foreach @dels; + my $del = join ',', @dels; + $err = system "/usr/local/bin/tag -r '${del}' '${f}'>/dev/null 2>&1"; + unless ($err) { + $et->VerboseValue("- $tag", $del); + $result = 1; + undef $err if @vals; # more to do if there are tags to add + } + } + unless (defined $err) { + # add new tags, or overwrite or delete existing tags + s/'/'\\''/g foreach @vals; + my $opt = $overwrite > 0 ? '-s' : '-a'; + $val = @vals ? join(',', @vals) : ''; + $cmd = "/usr/local/bin/tag $opt '${val}' '${f}'"; + $et->VPrint(1," - $tag = (all)\n") if $overwrite > 0; + undef $val if $val eq ''; + } + } elsif ($tag eq 'XAttrQuarantine') { + ($f = $file) =~ s/'/'\\''/g; + $cmd = "/usr/bin/xattr -d com.apple.quarantine '${f}'"; + $silentErr = 256; # (will get this error if attribute doesn't exist) + } else { + ($f = $file) =~ s/(["\\])/\\$1/g; # escape necessary characters for script + $f =~ s/'/'"'"'/g; + if ($tag eq 'MDItemFinderComment') { + # (write finder comment using osascript instead of xattr + # because it is more work to construct the necessary bplist) + $val = '' unless defined $val; # set to empty string instead of deleting + $v = $et->Encode($val, 'UTF8'); + $v =~ s/(["\\])/\\$1/g; + $v =~ s/'/'"'"'/g; + $attr = 'comment'; + } else { # $tag eq 'MDItemFSLabel' + $v = $val ? 8 - $val : 0; # convert from label to label index (0 for no label) + $attr = 'label index'; + } + $cmd = qq(/usr/bin/osascript -e 'set fp to POSIX file "$f" as alias' -e \\ + 'tell application "Finder" to set $attr of file fp to "$v"'); + } + if (defined $cmd) { + $err = system $cmd . '>/dev/null 2>&1'; # (pipe all output to /dev/null) + } + if (not $err) { + $et->VerboseValue("+ $tag", $val) if defined $val; + $result = 1; + } elsif (not $silentErr or $err != $silentErr) { + $cmd =~ s/ .*//s; + $et->Warn(qq{Error $err running "$cmd" to set $tag}); + $result = -1 unless $result; + } + } + return $result; +} + +#------------------------------------------------------------------------------ +# Extract MacOS metadata item tags +# Inputs: 0) ExifTool object ref, 1) file name +sub ExtractMDItemTags($$) +{ + local $_; + my ($et, $file) = @_; + my ($fn, $tag, $val, $tmp); + + ($fn = $file) =~ s/([`"\$\\])/\\$1/g; # escape necessary characters + $et->VPrint(0, '(running mdls)'); + my @mdls = `/usr/bin/mdls "$fn" 2> /dev/null`; # get MacOS metadata + if ($? or not @mdls) { + $et->Warn('Error running "mdls" to extract MDItem tags'); + return; + } + my $tagTablePtr = GetTagTable('Image::ExifTool::MacOS::MDItem'); + $$et{INDENT} .= '| '; + $et->VerboseDir('MDItem'); + foreach (@mdls) { + chomp; + if (ref $val ne 'ARRAY') { + s/^k?(\w+)\s*= // or next; + $tag = $1; + $_ eq '(' and $val = [ ], next; # (start of a list) + $_ = '' if $_ eq '(null)'; + s/^"// and s/"$//; # remove quotes if they exist + $val = $_; + } elsif ($_ eq ')') { # (end of a list) + $_ = $$val[0]; + next unless defined $_; + } else { + # add item to list + s/^ //; # remove leading spaces + s/,$//; # remove trailing comma + $_ = '' if $_ eq '(null)'; + s/^"// and s/"$//; # remove quotes if they exist + s/\\"/"/g; # un-escape quotes + $_ = $et->Decode($_, 'UTF8'); + push @$val, $_; + next; + } + # add to Extra tags if not done already + unless ($$tagTablePtr{$tag}) { + # check for a date/time format + my %tagInfo; + %tagInfo = ( + Groups => { 2 => 'Time' }, + ValueConv => \&MDItemLocalTime, + PrintConv => '$self->ConvertDateTime($val)', + ) if /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/; + # change tags like "com_apple_mail_xxx" to "AppleMailXxx" + ($tmp = $tag) =~ s/^com_//; # remove leading "com_" + $tmp =~ s/_([a-z])/\u$1/g; # use CamelCase + $tagInfo{Name} = Image::ExifTool::MakeTagName($tmp); + $tagInfo{List} = 1 if ref $val eq 'ARRAY'; + $tagInfo{Groups}{2} = 'Audio' if $tag =~ /Audio/; + $tagInfo{Groups}{2} = 'Author' if $tag =~ /(Copyright|Author)/; + $et->VPrint(0, " [adding $tag]\n"); + AddTagToTable($tagTablePtr, $tag, \%tagInfo); + } + $val = $et->Decode($val, 'UTF8') unless ref $val; + $et->HandleTag($tagTablePtr, $tag, $val); + undef $val; + } + $$et{INDENT} =~ s/\| $//; +} + + +#------------------------------------------------------------------------------ +# Read MacOS XAttr value +# Inputs: 0) ExifTool object ref, 1) file name +sub ReadXAttrValue($$$$) +{ + my ($et, $tagTablePtr, $tag, $val) = @_; + # add to our table if necessary + unless ($$tagTablePtr{$tag}) { + my $name; + # generate tag name from attribute name + if ($tag =~ /^com\.apple\.(.*)$/) { + ($name = $1) =~ s/^metadata:_?k//; + $name =~ s/^metadata:(com_)?//; + } else { + $name = $tag; + } + $name =~ s/[.:_]([a-z])/\U$1/g; + $name = 'XAttr' . ucfirst $name; + my %tagInfo = ( Name => $name ); + $tagInfo{Groups} = { 2 => 'Time' } if $tag=~/Date$/; + $et->VPrint(0, " [adding $tag]\n"); + AddTagToTable($tagTablePtr, $tag, \%tagInfo); + } + if ($val =~ /^bplist0/) { + my %dirInfo = ( DataPt => \$val ); + require Image::ExifTool::PLIST; + if (Image::ExifTool::PLIST::ProcessBinaryPLIST($et, \%dirInfo, $tagTablePtr)) { + return undef if ref $dirInfo{Value} eq 'HASH'; + $val = $dirInfo{Value} + } else { + $et->Warn("Error decoding $$tagTablePtr{$tag}{Name}"); + return undef; + } + } + if (not ref $val and ($val =~ /\0/ or length($val) > 200) or $tag eq 'XAttrMDLabel') { + my $buff = $val; + $val = \$buff; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Read MacOS extended attribute tags using 'xattr' utility +# Inputs: 0) ExifTool object ref, 1) file name +sub ExtractXAttrTags($$) +{ + local $_; + my ($et, $file) = @_; + my ($fn, $tag, $val, $warn); + + ($fn = $file) =~ s/([`"\$\\])/\\$1/g; # escape necessary characters + $et->VPrint(0, '(running xattr)'); + my @xattr = `/usr/bin/xattr -lx "$fn" 2> /dev/null`; # get MacOS extended attributes + if ($? or not @xattr) { + $? and $et->Warn('Error running "xattr" to extract XAttr tags'); + return; + } + my $tagTablePtr = GetTagTable('Image::ExifTool::MacOS::XAttr'); + $$et{INDENT} .= '| '; + $et->VerboseDir('XAttr'); + push @xattr, ''; # (for a list terminator) + foreach (@xattr) { + chomp; + if (s/^[\dA-Fa-f]{8}//) { + $tag or $warn = 1, next; + s/\|.*//; + tr/ //d; + (/[^\dA-Fa-f]/ or length($_) & 1) and $warn = 2, next; + $val = '' unless defined $val; + $val .= pack('H*', $_); + next; + } elsif ($tag and defined $val) { + $val = ReadXAttrValue($et, $tagTablePtr, $tag, $val); + $et->HandleTag($tagTablePtr, $tag, $val) if defined $val; + undef $tag; + undef $val; + } + next unless length; + s/:$// or $warn = 3, next; # attribute name must have trailing ":" + defined $val and $warn = 4, undef $val; + # remove random ID after kMDLabel in tag ID + ($tag = $_) =~ s/^com.apple.metadata:kMDLabel_.*/com.apple.metadata:kMDLabel/s; + } + $warn and $et->Warn(qq{Error $warn parsing "xattr" output}); + $$et{INDENT} =~ s/\| $//; +} + +#------------------------------------------------------------------------------ +# Extract MacOS file creation date/time +# Inputs: 0) ExifTool object ref, 1) file name +sub GetFileCreateDate($$) +{ + local $_; + my ($et, $file) = @_; + my ($fn, $tag, $val, $tmp); + + ($fn = $file) =~ s/([`"\$\\])/\\$1/g; # escape necessary characters + $et->VPrint(0, '(running stat)'); + my $time = `/usr/bin/stat -f '%SB' -t '%Y:%m:%d %H:%M:%S%z' "$fn" 2> /dev/null`; + if ($? or not $time or $time !~ s/([-+]\d{2})(\d{2})\s*$/$1:$2/) { + $et->Warn('Error running "stat" to extract FileCreateDate'); + return; + } + $$et{SET_GROUP1} = 'MacOS'; + $et->FoundTag(FileCreateDate => $time); + delete $$et{SET_GROUP1}; +} + +#------------------------------------------------------------------------------ +# Read ATTR metadata from "._" file +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Return: 1 on success +# (ref https://www.swiftforensics.com/2018/11/the-dot-underscore-file-format.html) +sub ProcessATTR($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $dataLen = length $$dataPt; + + $dataLen >= 58 and $$dataPt =~ /^.{34}ATTR/s or $et->Warn('Invalid ATTR header'), return 0; + my $entries = Get32u($dataPt, 66); + $et->VerboseDir('ATTR', $entries); + # (Note: The RAF is not in $dirInfo because it would break RSRC reading -- + # the RSCR block uses relative offsets, while the ATTR block uses absolute! grrr!) + my $raf = $$et{RAF}; + my $pos = 70; # first entry is after ATTR header + my $i; + for ($i=0; $i<$entries; ++$i) { + $pos + 12 > $dataLen and $et->Warn('Truncated ATTR entry'), last; + my $off = Get32u($dataPt, $pos); + my $len = Get32u($dataPt, $pos + 4); + my $n = Get8u($dataPt, $pos + 10); # number of characters in tag name + $pos + 11 + $n > $dataLen and $et->Warn('Truncated ATTR name'), last; + $off -= $dataPos; # convert to relative offset (grrr!) + $off < 0 or $off > $dataLen and $et->Warn('Invalid ATTR offset'), last; + my $tag = substr($$dataPt, $pos + 11, $n); + $tag =~ s/\0+$//; # remove null terminator + # remove random ID after kMDLabel in tag ID + $tag =~ s/^com.apple.metadata:kMDLabel_.*/com.apple.metadata:kMDLabel/s; + $off + $len > $dataLen and $et->Warn('Truncated ATTR value'), last; + my $val = ReadXAttrValue($et, $tagTablePtr, $tag, substr($$dataPt, $off, $len)); + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $off, + Size => $len, + ) if defined $val; + $pos += (11 + $n + 3) & -4; # step to next entry (on even 4-byte boundary) + } + return 1; +} + +#------------------------------------------------------------------------------ +# Read information from a MacOS "._" sidecar file +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid "._" file +# (ref https://www.swiftforensics.com/2018/11/the-dot-underscore-file-format.html) +sub ProcessMacOS($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($hdr, $buff, $i); + + return 0 unless $raf->Read($hdr, 26) == 26 and $hdr =~ /^\0\x05\x16\x07\0(.)\0\0Mac OS X /s; + my $ver = ord $1; + # (extension may be anything, so just echo back the incoming file extension if it exists) + $et->SetFileType(undef, undef, $$et{FILE_EXT}); + $ver == 2 or $et->Warn("Unsupported file version $ver"), return 1; + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::MacOS::Main'); + my $entries = Get16u(\$hdr, 0x18); + $et->VerboseDir('MacOS', $entries); + $raf->Read($hdr, $entries * 12) == $entries * 12 or $et->Warn('Truncated header'), return 1; + for ($i=0; $i<$entries; ++$i) { + my $pos = $i * 12; + my $tag = Get32u(\$hdr, $pos); + my $off = Get32u(\$hdr, $pos + 4); + my $len = Get32u(\$hdr, $pos + 8); + $len > 100000000 and $et->Warn('Record size too large'), last; + $raf->Seek($off,0) and $raf->Read($buff,$len) == $len or $et->Warn('Truncated record'), last; + $et->HandleTag($tagTablePtr, $tag, undef, DataPt => \$buff, DataPos => $off, Index => $i); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MacOS - Read/write MacOS system tags + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract +MDItem* and XAttr* tags on MacOS systems using the "mdls" and "xattr" +utilities respectively. It also reads metadata directly from the MacOS "_." +sidecar files that are used on some filesystems to store file attributes. +Writable tags use "xattr", "setfile" or "osascript" for writing. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MacOS Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/MakerNotes.pm b/ExifTool/lib/Image/ExifTool/MakerNotes.pm new file mode 100644 index 0000000..1631c75 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MakerNotes.pm @@ -0,0 +1,1844 @@ +#------------------------------------------------------------------------------ +# File: MakerNotes.pm +# +# Description: Read and write EXIF maker notes +# +# Revisions: 11/11/2004 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::MakerNotes; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess); +use Image::ExifTool::Exif; + +sub ProcessUnknown($$$); +sub ProcessUnknownOrPreview($$$); +sub ProcessCanon($$$); +sub ProcessGE2($$$); +sub ProcessKodakPatch($$$); +sub WriteUnknownOrPreview($$$); +sub FixLeicaBase($$;$); + +$VERSION = '2.14'; + +my $debug; # set to 1 to enable debugging code + +# conditional list of maker notes +# Notes: +# - This is NOT a normal tag table! +# - All byte orders are now specified because we can now +# write maker notes into a file with different byte ordering! +# - Put these in alphabetical order to make TagNames documentation nicer. +@Image::ExifTool::MakerNotes::Main = ( + # decide which MakerNotes to use (based on makernote header and camera make/model) + { + Name => 'MakerNoteApple', + Condition => '$$valPt =~ /^Apple iOS\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Apple::Main', + Start => '$valuePtr + 14', + Base => '$start - 14', + ByteOrder => 'Unknown', + }, + }, + { + # this maker notes starts with a standard TIFF header at offset 0x0a + # (must check Nikon signature first because Nikon Capture NX can generate + # NEF images containing Nikon maker notes from JPEG images of any camera model) + Name => 'MakerNoteNikon', + Condition => '$$valPt=~/^Nikon\x00\x02/', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::Main', + Start => '$valuePtr + 18', + Base => '$start - 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteCanon', + # (starts with an IFD) + Condition => '$$self{Make} =~ /^Canon/', + SubDirectory => { + TagTable => 'Image::ExifTool::Canon::Main', + ProcessProc => \&ProcessCanon, + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteCasio', + # do negative lookahead assertion just to get tags + # in a nice order for documentation + # (starts with an IFD) + Condition => '$$self{Make}=~/^CASIO/ and $$valPt!~/^(QVC|DCI)\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Casio::Main', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteCasio2', + # (starts with "QVC\0" [Casio] or "DCI\0" [Concord]) + # (also found in AVI and MOV videos) + Condition => '$$valPt =~ /^(QVC|DCI)\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Casio::Type2', + Start => '$valuePtr + 6', + ByteOrder => 'Unknown', + FixBase => 1, # necessary for AVI and MOV videos + }, + }, + { + Name => 'MakerNoteDJIInfo', + Condition => '$$valPt =~ /^\[ae_dbg_info:/', + NotIFD => 1, + SubDirectory => { TagTable => 'Image::ExifTool::DJI::Info' }, + }, + { + Name => 'MakerNoteDJI', + Condition => '$$self{Make} eq "DJI" and $$valPt !~ /^(...\@AMBA|DJI)/s', + SubDirectory => { + TagTable => 'Image::ExifTool::DJI::Main', + Start => '$valuePtr', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteFLIR', + # (starts with IFD, Make is 'FLIR Systems AB' or 'FLIR Systems') + Condition => '$$self{Make} =~ /^FLIR Systems/', + SubDirectory => { + TagTable => 'Image::ExifTool::FLIR::Main', + Start => '$valuePtr', + ByteOrder => 'Unknown', + }, + }, + { + # The Fuji maker notes use a structure similar to a self-contained + # TIFF file, but with "FUJIFILM" instead of the standard TIFF header + Name => 'MakerNoteFujiFilm', + # (starts with "FUJIFILM" -- also used by some Leica, Minolta and Sharp models) + # (GE FujiFilm models start with "GENERALE") + Condition => '$$valPt =~ /^(FUJIFILM|GENERALE)/', + SubDirectory => { + TagTable => 'Image::ExifTool::FujiFilm::Main', + # there is an 8-byte maker tag (FUJIFILM) we must skip over + OffsetPt => '$valuePtr+8', + # the pointers are relative to the subdirectory start + # (before adding the offsetPt) - PH + Base => '$start', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'MakerNoteGE', + Condition => '$$valPt =~ /^GE(\0\0|NIC\0)/', + SubDirectory => { + TagTable => 'Image::ExifTool::GE::Main', + Start => '$valuePtr + 18', + FixBase => 1, + AutoFix => 1, + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteGE2', + Condition => '$$valPt =~ /^GE\x0c\0\0\0\x16\0\0\0/', + # Note: we will get a "Maker notes could not be parsed" warning when writing + # these maker notes because they aren't currently supported for writing + SubDirectory => { + TagTable => 'Image::ExifTool::FujiFilm::Main', + ProcessProc => \&ProcessGE2, + Start => '$valuePtr + 12', + Base => '$start - 6', + ByteOrder => 'LittleEndian', + # hard patch for crazy offsets + FixOffsets => '$valuePtr -= 210 if $tagID >= 0x1303', + }, + }, + { + Name => 'MakerNoteHasselblad', + Condition => '$$self{Make} eq "Hasselblad"', + SubDirectory => { + TagTable => 'Image::ExifTool::Unknown::Main', + ByteOrder => 'Unknown', + Start => '$valuePtr', + Base => 0, # (avoids warnings since maker notes are not self-contained) + }, + # 0x0011 - sensor code (ref IB) + # 0x0012 - camera model id? + # 0x0015 - camera model name + # 0x0016 - coating code (ref IB) + }, + # (the GE X5 has really messed up EXIF-like maker notes starting with + # "GENIC\x0c\0" --> currently not decoded) + { + Name => 'MakerNoteHP', # PhotoSmart 720 (also Vivitar 3705, 3705B and 3715) + Condition => '$$valPt =~ /^(Hewlett-Packard|Vivitar)/', + SubDirectory => { + TagTable => 'Image::ExifTool::HP::Main', + ProcessProc => \&ProcessUnknown, + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteHP2', # PhotoSmart E427 + # (this type of maker note also used by BenQ, Mustek, Sanyo, Traveler and Vivitar) + Condition => '$$valPt =~ /^610[\0-\4]/', + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::HP::Type2', + Start => '$valuePtr', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'MakerNoteHP4', # PhotoSmart M627 + Condition => '$$valPt =~ /^IIII\x04\0/', + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::HP::Type4', + Start => '$valuePtr', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'MakerNoteHP6', # PhotoSmart M425, M525 and M527 + Condition => '$$valPt =~ /^IIII\x06\0/', + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::HP::Type6', + Start => '$valuePtr', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'MakerNoteISL', # (used in Samsung GX20 samples) + Condition => '$$valPt =~ /^ISLMAKERNOTE000\0/', + # this maker notes starts with a TIFF-like header at offset 0x10 + SubDirectory => { + TagTable => 'Image::ExifTool::Unknown::Main', + Start => '$valuePtr + 24', + Base => '$start - 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteJVC', + Condition => '$$valPt=~/^JVC /', + SubDirectory => { + TagTable => 'Image::ExifTool::JVC::Main', + Start => '$valuePtr + 4', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteJVCText', + Condition => '$$self{Make}=~/^(JVC|Victor)/ and $$valPt=~/^VER:/', + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::JVC::Text', + }, + }, + { + Name => 'MakerNoteKodak1a', + Condition => '$$self{Make}=~/^EASTMAN KODAK/ and $$valPt=~/^KDK INFO/', + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Main', + Start => '$valuePtr + 8', + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'MakerNoteKodak1b', + Condition => '$$self{Make}=~/^EASTMAN KODAK/ and $$valPt=~/^KDK/', + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Main', + Start => '$valuePtr + 8', + ByteOrder => 'LittleEndian', + }, + }, + { + # used by various Kodak, HP, Pentax and Minolta models + Name => 'MakerNoteKodak2', + Condition => q{ + $$valPt =~ /^.{8}Eastman Kodak/s or + $$valPt =~ /^\x01\0[\0\x01]\0\0\0\x04\0[a-zA-Z]{4}/ + }, + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type2', + ByteOrder => 'BigEndian', + }, + }, + { + # not much to key on here, but we know the + # upper byte of the year should be 0x07: + Name => 'MakerNoteKodak3', + Condition => q{ + $$self{Make} =~ /^EASTMAN KODAK/ and + $$valPt =~ /^(?!MM|II).{12}\x07/s and + $$valPt !~ /^(MM|II|AOC)/ + }, + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type3', + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'MakerNoteKodak4', + Condition => q{ + $$self{Make} =~ /^Eastman Kodak/ and + $$valPt =~ /^.{41}JPG/s and + $$valPt !~ /^(MM|II|AOC)/ + }, + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type4', + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'MakerNoteKodak5', + Condition => q{ + $$self{Make}=~/^EASTMAN KODAK/ and + ($$self{Model}=~/CX(4200|4230|4300|4310|6200|6230)/ or + # try to pick up similar models we haven't tested yet + $$valPt=~/^\0(\x1a\x18|\x3a\x08|\x59\xf8|\x14\x80)\0/) + }, + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type5', + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'MakerNoteKodak6a', + Condition => q{ + $$self{Make}=~/^EASTMAN KODAK/ and + $$self{Model}=~/DX3215/ + }, + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type6', + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'MakerNoteKodak6b', + Condition => q{ + $$self{Make}=~/^EASTMAN KODAK/ and + $$self{Model}=~/DX3700/ + }, + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type6', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'MakerNoteKodak7', + # look for something that looks like a serial number + # (confirmed serial numbers have the format KXXXX########, but we also + # accept other strings from sample images that may be serial numbers) + Condition => q{ + $$self{Make}=~/Kodak/i and + $$valPt =~ /^[CK][A-Z\d]{3} ?[A-Z\d]{1,2}\d{2}[A-Z\d]\d{4}[ \0]/ + }, + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type7', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'MakerNoteKodak8a', + # IFD-format maker notes: look for reasonable number of + # entries and check format and count of first IFD entry + Condition => q{ + $$self{Make}=~/Kodak/i and + ($$valPt =~ /^\0[\x02-\x7f]..\0[\x01-\x0c]\0\0/s or + $$valPt =~ /^[\x02-\x7f]\0..[\x01-\x0c]\0..\0\0/s) + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type8', + ProcessProc => \&ProcessUnknown, + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteKodak8b', + # these maker notes have an extra 2 bytes after the entry count + # (this is handled by the patch). Also, the IFD uses a Format 13, + # which is some 2-byte format (not Float, as decoded by ExifTool) + # - written by the PixPro AZ251, AZ361, AZ262, AZ521 + Condition => q{ + $$self{Make}=~/Kodak/i and + $$valPt =~ /^MM\0\x2a\0\0\0\x08\0.\0\0/ + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type8', + ProcessProc => \&ProcessKodakPatch, + ByteOrder => 'BigEndian', + Start => '$valuePtr + 8', + Base => '$start - 8', + }, + }, + { + Name => 'MakerNoteKodak8c', + # TIFF-format maker notes + Condition => q{ + $$self{Make}=~/Kodak/i and + $$valPt =~ /^(MM\0\x2a\0\0\0\x08|II\x2a\0\x08\0\0\0)/ + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type8', + ProcessProc => \&ProcessUnknown, + ByteOrder => 'Unknown', + Start => '$valuePtr + 8', + Base => '$start - 8', + }, + }, + { + Name => 'MakerNoteKodak9', + # test header and Kodak:DateTimeOriginal + Condition => '$$valPt =~ m{^IIII[\x02\x03]\0.{14}\d{4}/\d{2}/\d{2} }s', + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type9', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'MakerNoteKodak10', + # yet another type of Kodak IFD-format maker notes: + # this type begins with a byte order indicator, + # followed immediately by the IFD + Condition => q{ + $$self{Make}=~/Kodak/i and + $$valPt =~ /^(MM\0[\x02-\x7f]|II[\x02-\x7f]\0)/ + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type10', + ProcessProc => \&ProcessUnknown, + ByteOrder => 'Unknown', + Start => '$valuePtr + 2', + }, + }, + { + Name => 'MakerNoteKodak11', + # these maker notes have a 4-byte entry count + # - written by the PixPro S-1 (Note: Make is "JK Imaging, Ltd.", so check Model for "Kodak") + Condition => q{ + $$self{Model}=~/(Kodak|PixPro)/i and + $$valPt =~ /^II\x2a\0\x08\0\0\0.\0\0\0/s + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type11', + ProcessProc => \&ProcessKodakPatch, + ByteOrder => 'LittleEndian', + Start => '$valuePtr + 8', + Base => '$start - 8', + }, + }, + { + Name => 'MakerNoteKodak12', + # these maker notes have a 4-byte entry count + # - written by the PixPro AZ901 (Note: Make is "JK Imaging, Ltd.", so check Model for "Kodak") + Condition => q{ + $$self{Model}=~/(Kodak|PixPro)/i and + $$valPt =~ /^MM\0\x2a\0\0\0\x08\0\0\0./s + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Type11', + ProcessProc => \&ProcessKodakPatch, + ByteOrder => 'BigEndian', + Start => '$valuePtr + 8', + Base => '$start - 8', + }, + }, + { + Name => 'MakerNoteKodakUnknown', + Condition => '$$self{Make}=~/Kodak/i and $$valPt!~/^AOC\0/', + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::Unknown', + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'MakerNoteKyocera', + # (starts with "KYOCERA") + Condition => '$$valPt =~ /^KYOCERA/', + SubDirectory => { + TagTable => 'Image::ExifTool::Unknown::Main', + Start => '$valuePtr + 22', + Base => '$start + 2', + EntryBased => 1, + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteMinolta', + Condition => q{ + $$self{Make}=~/^(Konica Minolta|Minolta)/i and + $$valPt !~ /^(MINOL|CAMER|MLY0|KC|\+M\+M|\xd7)/ + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::Main', + ByteOrder => 'Unknown', + }, + }, + { + # the DiMAGE E323 (MINOL) and E500 (CAMER), and some models + # of Mustek, Pentax, Ricoh and Vivitar (CAMER). + Name => 'MakerNoteMinolta2', + Condition => '$$valPt =~ /^(MINOL|CAMER)\0/ and $$self{OlympusCAMER} = 1', + SubDirectory => { + # these models use Olympus tags in the range 0x200-0x221 plus 0xf00 + TagTable => 'Image::ExifTool::Olympus::Main', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + }, + }, + { + # /^MLY0/ - DiMAGE G400, G500, G530, G600 + # /^KC/ - Revio KD-420Z, DiMAGE E203 + # /^+M+M/ - DiMAGE E201 + # /^\xd7/ - DiMAGE RD3000 + Name => 'MakerNoteMinolta3', + Condition => '$$self{Make} =~ /^(Konica Minolta|Minolta)/i', + Binary => 1, + Notes => 'not EXIF-based', + }, + { + Name => 'MakerNoteMotorola', + Condition => '$$valPt=~/^MOT\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Motorola::Main', + Start => '$valuePtr + 8', + Base => '$start - 8', + ByteOrder => 'Unknown', + }, + }, + { + # older Nikon maker notes + Name => 'MakerNoteNikon2', + Condition => '$$valPt=~/^Nikon\x00\x01/', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::Type2', + Start => '$valuePtr + 8', + ByteOrder => 'LittleEndian', + }, + }, + { + # headerless Nikon maker notes + Name => 'MakerNoteNikon3', + Condition => '$$self{Make}=~/^NIKON/i', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::Main', + ByteOrder => 'Unknown', # most are little-endian, but D1 is big + }, + }, + { + Name => 'MakerNoteNintendo', + # (starts with an IFD) + Condition => '$$self{Make} eq "Nintendo"', + SubDirectory => { + TagTable => 'Image::ExifTool::Nintendo::Main', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteOlympus', + # (if Make is 'SEIKO EPSON CORP.', starts with "EPSON\0") + # (if Make is 'OLYMPUS OPTICAL CO.,LTD' or 'OLYMPUS CORPORATION', + # starts with "OLYMP\0") + Condition => '$$valPt =~ /^(OLYMP|EPSON)\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::Main', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteOlympus2', + # new Olympus maker notes start with "OLYMPUS\0" + Condition => '$$valPt =~ /^OLYMPUS\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::Main', + Start => '$valuePtr + 12', + Base => '$start - 12', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteOlympus3', + # new Olympus maker notes start with "OLYMPUS\0" + Condition => '$$valPt =~ /^OM SYSTEM\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::Main', + Start => '$valuePtr + 16', + Base => '$start - 16', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteLeica', + # (starts with "LEICA\0\0\0") + Condition => '$$self{Make} eq "LEICA"', + SubDirectory => { + # many Leica models use the same format as Panasonic + TagTable => 'Image::ExifTool::Panasonic::Main', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteLeica2', # used by the M8 + # (starts with "LEICA\0\0\0") + Condition => '$$self{Make} =~ /^Leica Camera AG/ and $$valPt =~ /^LEICA\0\0\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Leica2', + # (the offset base is different in JPEG and DNG images, but we + # can copy makernotes from one to the other, so we need special + # logic to decide which base to apply) + ProcessProc => \&FixLeicaBase, + Start => '$valuePtr + 8', + Base => '$start', # (- 8 for DNG images!) + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteLeica3', # used by the R8 and R9 + # (starts with IFD) + Condition => q{ + $$self{Make} =~ /^Leica Camera AG/ and $$valPt !~ /^LEICA/ and + $$self{Model} ne "S2" and $$self{Model} ne "LEICA M (Typ 240)" + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Leica3', + Start => '$valuePtr', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteLeica4', # used by the M9/M-Monochrom + # (M9 and M Monochrom start with "LEICA0\x03\0") + Condition => '$$self{Make} =~ /^Leica Camera AG/ and $$valPt =~ /^LEICA0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Leica4', + Start => '$valuePtr + 8', + Base => '$start - 8', # (yay! Leica fixed the M8 problem) + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteLeica5', # used by the X1/X2/X VARIO/T/X-U + # (X1 starts with "LEICA\0\x01\0", Make is "LEICA CAMERA AG") + # (X2 starts with "LEICA\0\x05\0", Make is "LEICA CAMERA AG") + # (X VARIO starts with "LEICA\0\x04\0", Make is "LEICA CAMERA AG") + # (T (Typ 701) starts with "LEICA\0\0x6", Make is "LEICA CAMERA AG") + # (X (Typ 113) starts with "LEICA\0\0x7", Make is "LEICA CAMERA AG") + # (X-U (Typ 113) starts with "LEICA\0\x10\0", Make is "LEICA CAMERA AG") + Condition => '$$valPt =~ /^LEICA\0[\x01\x04\x05\x06\x07\x10\x1a]\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Leica5', + Start => '$valuePtr + 8', + Base => '$start - 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteLeica6', # used by the S2, M (Typ 240) and S (Typ 006) + # (starts with "LEICA\0\x02\xff", Make is "Leica Camera AG", but test the + # model names separately because the maker notes data may not be loaded + # at the time this is tested if they are in a JPEG trailer. Also, this + # header is used by the M Monochrom (Type 246), with different offsets.) + Condition => q{ + ($$self{Make} eq 'Leica Camera AG' and ($$self{Model} eq 'S2' or + $$self{Model} eq 'LEICA M (Typ 240)' or $$self{Model} eq 'LEICA S (Typ 006)')) + }, + DataTag => 'LeicaTrailer', # (generates fixup name for this tag) + LeicaTrailer => 1, # flag to special-case this tag in the Exif code + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Leica6', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + # NOTE: Leica uses absolute file offsets when this maker note is stored + # as a JPEG trailer -- this case is handled by ProcessLeicaTrailer in + # Panasonic.pm, and any "Base" defined here is ignored for this case. + # ExifTool may also create S2/M maker notes inside the APP1 segment when + # copying from other files, and for this the normal EXIF offsets are used, + # Base should not be defined! + }, + }, + { + Name => 'MakerNoteLeica7', # used by the M Monochrom (Typ 246) + # (starts with "LEICA\0\x02\xff", Make is "Leica Camera AG") + Condition => '$$valPt =~ /^LEICA\0\x02\xff/', + DataTag => 'LeicaTrailer', # (generates fixup name for this tag) + LeicaTrailer => 1, # flag to special-case this tag in the Exif code + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Leica6', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + Base => '-$base', # uses absolute file offsets (not based on TIFF header offset) + }, + }, + { + Name => 'MakerNoteLeica8', # used by the Q (Type 116) + # (Q (Typ 116) starts with "LEICA\0\x08\0", Make is "LEICA CAMERA AG") + # (SL (Typ 601) and CL start with "LEICA\0\x09\0", Make is "LEICA CAMERA AG") + Condition => '$$valPt =~ /^LEICA\0[\x08\x09]\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Leica5', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteLeica9', # used by the M10/S + # (M10 and S start with "LEICA0\x02\0") + Condition => '$$self{Make} =~ /^Leica Camera AG/ and $$valPt =~ /^LEICA\0\x02\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Leica9', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteLeica10', # used by the D-Lux7 + Condition => '$$valPt =~ /^LEICA CAMERA AG\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Main', + Start => '$valuePtr + 18', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNotePanasonic', + # (starts with "Panasonic\0") + Condition => '$$valPt=~/^Panasonic/ and $$self{Model} ne "DC-FT7"', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Main', + Start => '$valuePtr + 12', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNotePanasonic2', + # (starts with "Panasonic\0") + Condition => '$$self{Make}=~/^Panasonic/ and $$valPt=~/^MKE/', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Type2', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'MakerNotePanasonic3', # (DC-FT7) + # (starts with "Panasonic\0") + Condition => '$$valPt=~/^Panasonic/', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Main', + Start => '$valuePtr + 12', + Base => 12, # crazy! + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNotePentax', + # (starts with "AOC\0", but so does MakerNotePentax3) + # (also used by some Samsung models) + Condition => q{ + $$valPt=~/^AOC\0/ and + $$self{Model} !~ /^PENTAX Optio ?[34]30RS\s*$/ + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + # process as Unknown maker notes because the start offset and + # byte ordering are so variable + ProcessProc => \&ProcessUnknown, + # offsets can be totally whacky for Pentax maker notes, + # so attempt to fix the offset base if possible + FixBase => 1, + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNotePentax2', + # (starts with an IFD) + # Casio-like maker notes used only by the Optio 330 and 430 + Condition => '$$self{Make}=~/^Asahi/ and $$valPt!~/^AOC\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Type2', + ProcessProc => \&ProcessUnknown, + FixBase => 1, + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNotePentax3', + # (starts with "AOC\0", like the more common Pentax maker notes) + # Casio maker notes used only by the Optio 330RS and 430RS + Condition => '$$self{Make}=~/^Asahi/', + SubDirectory => { + TagTable => 'Image::ExifTool::Casio::Type2', + ProcessProc => \&ProcessUnknown, + FixBase => 1, + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNotePentax4', + # (starts with 3 or 4 digits) + # HP2-like text-based maker notes used by Optio E20 + Condition => '$$self{Make}=~/^PENTAX/ and $$valPt=~/^\d{3}/', + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Type4', + Start => '$valuePtr', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'MakerNotePentax5', + # (starts with "PENTAX \0") + # used by cameras such as the Q, Optio S1, RS1500 and WG-1 + Condition => '$$valPt=~/^PENTAX \0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + Start => '$valuePtr + 10', + Base => '$start - 10', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNotePentax6', + # (starts with "S1\0\0\0\0\0\0\x0c\0\0\0") + Condition => '$$valPt=~/^S1\0{6}\x0c\0{3}/', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::S1', + Start => '$valuePtr + 12', + Base => '$start - 12', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNotePhaseOne', + # Starts with: 'IIIITwaR' or 'IIIICwaR' (have seen both written by P25) + # (have also seen code which expects 'MMMMRawT') + Condition => q{ + return undef unless $$valPt =~ /^(IIII.waR|MMMMRaw.)/s; + $self->OverrideFileType($$self{TIFF_TYPE} = 'IIQ') if $count > 1000000; + return 1; + }, + NotIFD => 1, + IsPhaseOne => 1, # flag to rebuild these differently + SubDirectory => { TagTable => 'Image::ExifTool::PhaseOne::Main' }, + PutFirst => 1, # place immediately after TIFF header + }, + { + Name => 'MakerNoteReconyx', + Condition => q{ + $$valPt =~ /^\x01\xf1([\x02\x03]\x00)?/ and + ($1 or $$self{Make} eq "RECONYX") + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Reconyx::Main', + ByteOrder => 'Little-endian', + }, + }, + { + Name => 'MakerNoteReconyx2', + Condition => '$$valPt =~ /^RECONYXUF\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Reconyx::Type2', + ByteOrder => 'Little-endian', + }, + }, + { + Name => 'MakerNoteReconyx3', + Condition => '$$valPt =~ /^RECONYXH2\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Reconyx::Type3', + ByteOrder => 'Little-endian', + }, + }, + { + Name => 'MakerNoteRicohPentax', + # used by cameras such as the Ricoh GR III + Condition => '$$valPt=~/^RICOH\0(II|MM)/', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + Start => '$valuePtr + 8', + Base => '$start - 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteRicoh', + # (my test R50 image starts with " \x02\x01" - PH) + Condition => q{ + $$self{Make} =~ /^(PENTAX )?RICOH/ and + $$valPt =~ /^(Ricoh| |MM\0\x2a|II\x2a\0)/i and + $$valPt !~ /^(MM\0\x2a\0\0\0\x08\0.\0\0|II\x2a\0\x08\0\0\0.\0\0\0)/s and + $$self{Model} ne 'RICOH WG-M1' + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Ricoh::Main', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteRicoh2', + # (the Ricoh HZ15 starts with "MM\0\x2a" and the Pentax XG-1 starts with "II\x2a\0", + # but an extra 2 bytes of padding after the IFD entry count prevents these from + # being processed as a standard IFD. Note that the offsets for the HZ15 are all + # zeros, but they seem to be mostly OK for the XG-1) + Condition => q{ + $$self{Make} =~ /^(PENTAX )?RICOH/ and ($$self{Model} eq 'RICOH WG-M1' or + $$valPt =~ /^(MM\0\x2a\0\0\0\x08\0.\0\0|II\x2a\0\x08\0\0\0.\0\0\0)/s) + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Ricoh::Type2', + Start => '$valuePtr + 8', + Base => '$start - 8', + ByteOrder => 'Unknown', + ProcessProc => \&ProcessKodakPatch, + }, + }, + { + Name => 'MakerNoteRicohText', + Condition => '$$self{Make}=~/^RICOH/', + NotIFD => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Ricoh::Text', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteSamsung1a', + # Samsung STMN maker notes WITHOUT PreviewImage + Condition => '$$valPt =~ /^STMN\d{3}.\0{4}/s', + Binary => 1, + Notes => 'Samsung "STMN" maker notes without PreviewImage', + }, + { + Name => 'MakerNoteSamsung1b', + # Samsung STMN maker notes WITH PreviewImage + Condition => '$$valPt =~ /^STMN\d{3}/', + SubDirectory => { + TagTable => 'Image::ExifTool::Samsung::Main', + }, + }, + { + Name => 'MakerNoteSamsung2', + # Samsung EXIF-format maker notes ( + Condition => q{ + uc $$self{Make} eq 'SAMSUNG' and ($$self{TIFF_TYPE} eq 'SRW' or + $$valPt=~/^(\0.\0\x01\0\x07\0{3}\x04|.\0\x01\0\x07\0\x04\0{3})0100/s) + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Samsung::Type2', + # Samsung is very inconsistent here, and uses absolute offsets for some + # models and relative offsets for others, so process as Unknown + ProcessProc => \&ProcessUnknown, + FixBase => 1, + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteSanyo', + # (starts with "SANYO\0") + Condition => '$$self{Make}=~/^SANYO/ and $$self{Model}!~/^(C4|J\d|S\d)\b/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sanyo::Main', + Validate => '$val =~ /^SANYO/', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteSanyoC4', + # The C4 offsets are wrong by 12, so they must be fixed + Condition => '$$self{Make}=~/^SANYO/ and $$self{Model}=~/^C4\b/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sanyo::Main', + Validate => '$val =~ /^SANYO/', + Start => '$valuePtr + 8', + FixBase => 1, + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteSanyoPatch', + # The J1, J2, J4, S1, S3 and S4 offsets are completely screwy + Condition => '$$self{Make}=~/^SANYO/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sanyo::Main', + Validate => '$val =~ /^SANYO/', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + FixOffsets => 'Image::ExifTool::Sanyo::FixOffsets($valuePtr, $valEnd, $size, $tagID, $wFlag)', + }, + }, + { + Name => 'MakerNoteSigma', + Condition => q{ + return undef unless $$self{Make}=~/^(SIGMA|FOVEON)/; + # save version number in "MakerNoteSigmaVer" member variable + $$self{MakerNoteSigmaVer} = $$valPt=~/^SIGMA\0\0\0\0(.)/ ? ord($1) : -1; + return 1; + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Sigma::Main', + Validate => '$val =~ /^(SIGMA|FOVEON)/', + Start => '$valuePtr + 10', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteSony', + # (starts with "SONY DSC \0" or "SONY CAM \0") + # (TF1 starts with "\0\0SONY PIC\0") + # (Hasselblad models start with "VHAB \0") + Condition => '$$valPt=~/^(SONY (DSC|CAM|MOBILE)|\0\0SONY PIC\0|VHAB \0)/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::Main', + Start => '$valuePtr + 12', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteSony2', + # (starts with "SONY PI\0" -- DSC-S650/S700/S750) + Condition => '$$valPt=~/^SONY PI\0/ and $$self{OlympusCAMER}=1', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::Main', + Start => '$valuePtr + 12', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteSony3', + # (starts with "PREMI\0" -- DSC-S45/S500) + Condition => '$$valPt=~/^(PREMI)\0/ and $$self{OlympusCAMER}=1', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::Main', + Start => '$valuePtr + 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteSony4', + # (starts with "SONY PIC\0" -- DSC-H200/J20/W370/W510, MHS-TS20) + Condition => '$$valPt=~/^SONY PIC\0/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::PIC' }, + }, + { + Name => 'MakerNoteSony5', # used in SR2 and ARW images + Condition => '$$self{Make}=~/^SONY/ and $$valPt!~/^\x01\x00/', + Condition => q{ + ($$self{Make}=~/^SONY/ or ($$self{Make}=~/^HASSELBLAD/ and + $$self{Model}=~/^(HV|Stellar|Lusso|Lunar)/)) and $$valPt!~/^\x01\x00/ + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::Main', + Start => '$valuePtr', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteSonyEricsson', + Condition => '$$valPt =~ /^SEMC MS\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::Ericsson', + Start => '$valuePtr + 20', + Base => '$start - 8', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteSonySRF', + Condition => '$$self{Make}=~/^SONY/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::SRF', + Start => '$valuePtr', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MakerNoteUnknownText', + Condition => '$$valPt =~ /^[\x09\x0d\x0a\x20-\x7e]+\0*$/', + Notes => 'unknown text-based maker notes', + # show as binary if it is too long + ValueConv => 'length($val) > 64 ? \$val : $val', + ValueConvInv => '$val', + }, + { + Name => 'MakerNoteUnknownBinary', + # "LSI1\0" - SilverFast + Condition => '$$valPt =~ /^LSI1\0/', + Notes => 'unknown binary maker notes', + Binary => 1, + }, + { + Name => 'MakerNoteUnknown', + PossiblePreview => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::Unknown::Main', + ProcessProc => \&ProcessUnknownOrPreview, + WriteProc => \&WriteUnknownOrPreview, + ByteOrder => 'Unknown', + FixBase => 2, + }, + }, +); + +# insert writable properties so we can write our maker notes +my $tagInfo; +foreach $tagInfo (@Image::ExifTool::MakerNotes::Main) { + $$tagInfo{Writable} = 'undef'; + $$tagInfo{Format} = 'undef', # (make sure we don't convert this when reading) + $$tagInfo{WriteGroup} = 'ExifIFD'; + $$tagInfo{Groups} = { 1 => 'MakerNotes' }; + next unless $$tagInfo{SubDirectory}; + # make all SubDirectory tags block-writable + $$tagInfo{Binary} = 1, + $$tagInfo{MakerNotes} = 1; +} + +#------------------------------------------------------------------------------ +# Get normal offset of value data from end of maker note IFD +# Inputs: 0) ExifTool object reference +# Returns: Array: 0) relative flag (undef for no change) +# 1) normal offset from end of IFD to first value (empty if unknown) +# 2-N) other possible offsets used by some models +# Notes: Directory size should be validated before calling this routine +sub GetMakerNoteOffset($) +{ + my $et = shift; + # figure out where we expect the value data based on camera type + my $make = $$et{Make}; + my $model = $$et{Model}; + my ($relative, @offsets); + + # normally value data starts 4 bytes after end of directory, so this is the default. + # offsets of 0 and 4 are always allowed even if not specified, + # but the first offset specified is the one used when writing + if ($make =~ /^Canon/) { + push @offsets, ($model =~ /\b(20D|350D|REBEL XT|Kiss Digital N)\b/) ? 6 : 4; + # some Canon models (FV-M30, Optura50, Optura60) leave 24 unused bytes + # at the end of the IFD (2 spare IFD entries?) + push @offsets, 28 if $model =~ /\b(FV\b|OPTURA)/; + # some Canon PowerShot models leave 12 unused bytes + push @offsets, 16 if $model =~ /(PowerShot|IXUS|IXY)/; + } elsif ($make =~ /^CASIO/) { + # Casio AVI and MOV images use no padding, and their JPEG's use 4, + # except some models like the EX-S770,Z65,Z70,Z75 and Z700 which use 16, + # and the EX-Z35 which uses 2 (grrrr...) + push @offsets, $$et{FILE_TYPE} =~ /^(RIFF|MOV)$/ ? 0 : (4, 16, 2); + } elsif ($make =~ /^(General Imaging Co.|GEDSC IMAGING CORP.)/i) { + push @offsets, 0; + } elsif ($make =~ /^KYOCERA/) { + push @offsets, 12; + } elsif ($make =~ /^Leica Camera AG/) { + if ($model eq 'S2') { + # lots of empty space before first value in S2 images + push @offsets, 4, ($$et{FILE_TYPE} eq 'JPEG' ? 286 : 274); + } elsif ($model eq 'LEICA M MONOCHROM (Typ 246)') { + push @offsets, 4, 130; + } elsif ($model eq 'LEICA M (Typ 240)') { + push @offsets, 4, 118; + } elsif ($model =~ /^(R8|R9|M8)\b/) { + push @offsets, 6; + } else { + push @offsets, 4; + } + } elsif ($make =~ /^OLYMPUS/ and $model =~ /^E-(1|300|330)\b/) { + push @offsets, 16; + } elsif ($make =~ /^OLYMPUS/ and + # these Olympus models are just weird + $model =~ /^(C2500L|C-1Z?|C-5000Z|X-2|C720UZ|C725UZ|C150|C2Z|E-10|E-20|FerrariMODEL2003|u20D|u10D)\b/) + { + # no expected offset --> determine offset empirically via FixBase() + } elsif ($make =~ /^(Panasonic|JVC)\b/) { + push @offsets, 0; + } elsif ($make =~ /^SONY/) { + # earlier DSLR and "PREMI" models use an offset of 4 + if ($model =~ /^(DSLR-.*|SLT-A(33|35|55V)|NEX-(3|5|C3|VG10E))$/ or + $$et{OlympusCAMER}) + { + push @offsets, 4; + } else { + push @offsets, 0; + } + } elsif ($$et{TIFF_TYPE} eq 'SRW' and $make eq 'SAMSUNG' and $model eq 'EK-GN120') { + push @offsets, 40; # patch to read most of the maker notes, but breaks PreviewIFD + } elsif ($make eq 'FUJIFILM') { + # some models have offset of 6, so allow that too (A345,A350,A360,A370) + push @offsets, 4, 6; + } elsif ($make =~ /^TOSHIBA/) { + # similar to Canon, can also have 24 bytes of padding + push @offsets, 0, 24; + } elsif ($make =~ /^PENTAX/) { + push @offsets, 4; + # the Pentax addressing mode is determined automatically, but + # sometimes the algorithm gets it wrong, but Pentax always uses + # absolute addressing, so force it to be absolute + $relative = 0; + } elsif ($make =~ /^Konica Minolta/i) { + # patch for DiMAGE X50, Xg, Z2 and Z10 + push @offsets, 4, -16; + } elsif ($make =~ /^Minolta/) { + # patch for DiMAGE 7, X20 and Z1 + push @offsets, 4, -8, -12; + } else { + push @offsets, 4; # the normal offset + } + return ($relative, @offsets); +} + +#------------------------------------------------------------------------------ +# Get hash of value offsets / block sizes +# Inputs: 0) Data pointer, 1) offset to start of directory, +# 2) hash ref to return value pointers based on tag ID +# Returns: 0) hash reference: keys are offsets, values are block sizes +# 1) same thing, but with keys adjusted for value-based offsets +# Notes: Directory size should be validated before calling this routine +# - calculates MIN and MAX offsets in entry-based hash +sub GetValueBlocks($$;$) +{ + my ($dataPt, $dirStart, $tagPtr) = @_; + my $numEntries = Get16u($dataPt, $dirStart); + my ($index, $valPtr, %valBlock, %valBlkAdj, $end); + for ($index=0; $index<$numEntries; ++$index) { + my $entry = $dirStart + 2 + 12 * $index; + my $format = Get16u($dataPt, $entry+2); + last if $format < 1 or $format > 13; + my $count = Get32u($dataPt, $entry+4); + my $size = $count * $Image::ExifTool::Exif::formatSize[$format]; + next if $size <= 4; + $valPtr = Get32u($dataPt, $entry+8); + $tagPtr and $$tagPtr{Get16u($dataPt, $entry)} = $valPtr; + # save location and size of longest block at this offset + unless (defined $valBlock{$valPtr} and $valBlock{$valPtr} > $size) { + $valBlock{$valPtr} = $size; + } + # adjust for case of value-based offsets + $valPtr += 12 * $index; + unless (defined $valBlkAdj{$valPtr} and $valBlkAdj{$valPtr} > $size) { + $valBlkAdj{$valPtr} = $size; + my $end = $valPtr + $size; + if (defined $valBlkAdj{MIN}) { + # save minimum only if it has a value of 12 or greater + $valBlkAdj{MIN} = $valPtr if $valBlkAdj{MIN} < 12 or $valBlkAdj{MIN} > $valPtr; + $valBlkAdj{MAX} = $end if $valBlkAdj{MAX} > $end; + } else { + $valBlkAdj{MIN} = $valPtr; + $valBlkAdj{MAX} = $end; + } + } + } + return(\%valBlock, \%valBlkAdj); +} + +#------------------------------------------------------------------------------ +# Fix base for value offsets in maker notes IFD (if necessary) +# Inputs: 0) ExifTool object ref, 1) DirInfo hash ref +# Return: amount of base shift (and $dirInfo Base and DataPos are updated, +# FixedBy is set if offsets fixed, and Relative or EntryBased may be set) +sub FixBase($$) +{ + local $_; + my ($et, $dirInfo) = @_; + # don't fix base if fixing offsets individually or if we don't want to fix them + return 0 if $$dirInfo{FixOffsets} or $$dirInfo{NoFixBase}; + + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $entryBased = $$dirInfo{EntryBased}; + my $dirName = $$dirInfo{DirName}; + my $fixBase = $et->Options('FixBase'); + my $setBase = (defined $fixBase and $fixBase ne '') ? 1 : 0; + my ($fix, $fixedBy, %tagPtr); + + # get hash of value block positions + my ($valBlock, $valBlkAdj) = GetValueBlocks($dataPt, $dirStart, \%tagPtr); + return 0 unless %$valBlock; + # get sorted list of value offsets + my @valPtrs = sort { $a <=> $b } keys %$valBlock; +# +# handle special case of Canon maker notes with TIFF footer containing original offset +# + if ($$et{Make} =~ /^Canon/ and $$dirInfo{DirLen} > 8) { + my $footerPos = $dirStart + $$dirInfo{DirLen} - 8; + my $footer = substr($$dataPt, $footerPos, 8); + if ($footer =~ /^(II\x2a\0|MM\0\x2a)/ and # check for TIFF footer + substr($footer,0,2) eq GetByteOrder()) # validate byte ordering + { + my $oldOffset = Get32u(\$footer, 4); + my $newOffset = $dirStart + $dataPos; + if ($setBase) { + $fix = $fixBase; + } else { + $fix = $newOffset - $oldOffset; + return 0 unless $fix; + # Picasa and ACDSee have a bug where they update other offsets without + # updating the TIFF footer (PH - 2009/02/25), so test for this case: + # validate Canon maker note footer fix by checking offset of last value + my $maxPt = $valPtrs[-1] + $$valBlock{$valPtrs[-1]}; + # compare to end of maker notes, taking 8-byte footer into account + my $endDiff = $dirStart + $$dirInfo{DirLen} - ($maxPt - $dataPos) - 8; + # ignore footer offset only if end difference is exactly correct + # (allow for possible padding byte, although I have never seen this) + if (not $endDiff or $endDiff == 1) { + $et->Warn('Canon maker note footer may be invalid (ignored)',1); + return 0; + } + } + $et->Warn("Adjusted $dirName base by $fix",1); + $$dirInfo{FixedBy} = $fix; + $$dirInfo{Base} += $fix; + $$dirInfo{DataPos} -= $fix; + return $fix; + } + } +# +# analyze value offsets to see if they need fixing. The first task is to determine +# the minimum valid offset used (this is tricky, because we have to weed out bad +# offsets written by some cameras) +# + my $minPt = $$dirInfo{MinOffset} = $valPtrs[0]; # if life were simple, this would be it + my $ifdLen = 2 + 12 * Get16u($$dirInfo{DataPt}, $dirStart); + my $ifdEnd = $dirStart + $ifdLen; + my ($relative, @offsets) = GetMakerNoteOffset($et); + my $makeDiff = $offsets[0]; + my $verbose = $et->Options('Verbose'); + my ($diff, $shift); + + # calculate expected minimum value offset + my $expected = $dataPos + $ifdEnd + (defined $makeDiff ? $makeDiff : 4); + $debug and print "$expected expected\n"; + + # zero our counters + my ($countNeg12, $countZero, $countOverlap) = (0, 0, 0); + my ($valPtr, $last); + foreach $valPtr (@valPtrs) { + printf("%d - %d (%d)\n", $valPtr, $valPtr + $$valBlock{$valPtr}, + $valPtr - ($last || 0)) if $debug; + if (defined $last) { + my $gap = $valPtr - $last; + if ($gap == 0 or $gap == 1) { + ++$countZero; + } elsif ($gap == -12 and not $entryBased) { + # you get this when value offsets are relative to the IFD entry + ++$countNeg12; + } elsif ($gap < 0) { + # any other negative difference indicates overlapping values + ++$countOverlap if $valPtr; # (but ignore zero value pointers) + } elsif ($gap >= $ifdLen) { + # ignore previous minimum if we took a jump to near the expected value + # (some values can be stored before the IFD) + $minPt = $valPtr if abs($valPtr - $expected) <= 4; + } + # an offset less than 12 is surely garbage, so ignore it + $minPt = $valPtr if $minPt < 12; + } + $last = $valPtr + $$valBlock{$valPtr}; + } + # could this IFD be using entry-based offsets? + if ((($countNeg12 > $countZero and $$valBlkAdj{MIN} >= $ifdLen - 2) or + ($$valBlkAdj{MIN} == $ifdLen - 2 or $$valBlkAdj{MIN} == $ifdLen + 2) + ) and $$valBlkAdj{MAX} <= $$dirInfo{DirLen}-2) + { + # looks like these offsets are entry-based, so use the offsets + # which have been correcting for individual entry position + $entryBased = 1; + $verbose and $et->Warn("$dirName offsets are entry-based",1); + } else { + # calculate offset difference from end of IFD to first value + $diff = ($minPt - $dataPos) - $ifdEnd; + $shift = 0; + $countOverlap and $et->Warn("Overlapping $dirName values",1); + if ($entryBased) { + $et->Warn("$dirName offsets do NOT look entry-based",1); + undef $entryBased; + undef $relative; + } + # use PrintIM tag to do special check for correct absolute offsets + if ($tagPtr{0xe00}) { + my $ptr = $tagPtr{0xe00} - $dataPos; + return 0 if $ptr > 0 and $ptr <= length($$dataPt) - 8 and + substr($$dataPt, $ptr, 8) eq "PrintIM\0"; + } + # allow a range of reasonable differences for Unknown maker notes + if ($$dirInfo{FixBase} and $$dirInfo{FixBase} == 2) { + return 0 if $diff >=0 and $diff <= 24; + } + # ******** (used for testing to extract differences) ******** + # $et->FoundTag('Diff', $diff); + # $et->FoundTag('MakeDiff',$makeDiff); + } +# +# handle entry-based offsets +# + if ($entryBased) { + $debug and print "--> entry-based!\n"; + # most of my entry-based samples have first value immediately + # after last IFD entry (ie. no padding or next IFD pointer) + $makeDiff = 0; + push @offsets, 4; # but some do have a next IFD pointer + # corrected entry-based offsets are relative to start of first entry + my $expected = 12 * Get16u($$dirInfo{DataPt}, $dirStart); + $diff = $$valBlkAdj{MIN} - $expected; + # set up directory to read values with entry-based offsets + # (ignore everything and set base to start of first entry) + $shift = $dataPos + $dirStart + 2; + $$dirInfo{Base} += $shift; + $$dirInfo{DataPos} -= $shift; + $$dirInfo{EntryBased} = 1; + $$dirInfo{Relative} = 1; # entry-based offsets are relative + delete $$dirInfo{FixBase}; # no automatic base fix + undef $fixBase unless $setBase; + } +# +# return without doing shift if offsets look OK +# + unless ($setBase) { + # don't try to fix offsets for whacky cameras + return $shift unless defined $makeDiff; + # normal value data starts 4 bytes after IFD, but allow 0 or 4... + return $shift if $diff == 0 or $diff == 4; + # also check for allowed make-specific differences + foreach (@offsets) { + return $shift if $diff == $_; + } + } +# +# apply the fix, or issue a warning +# + # use default padding of 4 bytes unless already specified + $makeDiff = 4 unless defined $makeDiff; + $fix = $makeDiff - $diff; # assume standard diff for this make + + if ($$dirInfo{FixBase}) { + # set flag if offsets are relative (base is at or above directory start) + if ($dataPos - $fix + $dirStart <= 0) { + $$dirInfo{Relative} = (defined $relative) ? $relative : 1; + } + if ($setBase) { + $fixedBy = $fixBase; + $fix += $fixBase; + } + } elsif (defined $fixBase) { + $fix = $fixBase if $fixBase ne ''; + $fixedBy = $fix; + } else { + # print warning unless difference looks reasonable + if ($diff < 0 or $diff > 16 or ($diff & 0x01)) { + $et->Warn("Possibly incorrect maker notes offsets (fix by $fix?)",1); + } + # don't do the fix (but we already adjusted base if entry-based) + return $shift; + } + if (defined $fixedBy) { + $et->Warn("Adjusted $dirName base by $fixedBy",1); + $$dirInfo{FixedBy} = $fixedBy; + } + $$dirInfo{Base} += $fix; + $$dirInfo{DataPos} -= $fix; + return $fix + $shift; +} + +#------------------------------------------------------------------------------ +# Find start of IFD in unknown maker notes +# Inputs: 0) reference to directory information +# Returns: offset to IFD on success, undefined otherwise +# - dirInfo may contain TagInfo reference for tag associated with directory +# - on success, updates DirStart, DirLen, Base and DataPos in dirInfo +# - also sets Relative flag in dirInfo if offsets are relative to IFD +# Note: Changes byte ordering! +sub LocateIFD($$) +{ + my ($et, $dirInfo) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + # (ignore MakerNotes DirLen since sometimes this is incorrect) + my $size = $$dirInfo{DataLen} - $dirStart; + my $dirLen = defined $$dirInfo{DirLen} ? $$dirInfo{DirLen} : $size; + my $tagInfo = $$dirInfo{TagInfo}; + my $ifdOffsetPos; + # the IFD should be within the first 32 bytes + # (Kyocera sets the current record at 22 bytes) + my ($firstTry, $lastTry) = (0, 32); + + # make sure Base and DataPos are defined + $$dirInfo{Base} or $$dirInfo{Base} = 0; + $$dirInfo{DataPos} or $$dirInfo{DataPos} = 0; +# +# use tag information (if provided) to determine directory location +# + if ($tagInfo and $$tagInfo{SubDirectory}) { + my $subdir = $$tagInfo{SubDirectory}; + unless ($$subdir{ProcessProc} and + ($$subdir{ProcessProc} eq \&ProcessUnknown or + $$subdir{ProcessProc} eq \&ProcessUnknownOrPreview)) + { + # look for the IFD at the "Start" specified in our SubDirectory information + my $valuePtr = $dirStart; + my $newStart = $dirStart; + if (defined $$subdir{Start}) { + #### eval Start ($valuePtr) + $newStart = eval($$subdir{Start}); + } + if ($$subdir{Base}) { + # calculate subdirectory start relative to $base for eval + my $start = $newStart + $$dirInfo{DataPos}; + my $base = $$dirInfo{Base} || 0; + #### eval Base ($start,$base) + my $baseShift = eval($$subdir{Base}); + # shift directory base (note: we may do this again below + # if an OffsetPt is defined, but that doesn't matter since + # the base shift is relative to DataPos, which we set too) + $$dirInfo{Base} += $baseShift; + $$dirInfo{DataPos} -= $baseShift; + # this is a relative directory if Base depends on $start + if ($$subdir{Base} =~ /\$start\b/) { + $$dirInfo{Relative} = 1; + # hack to fix Leica quirk + if ($$subdir{ProcessProc} and $$subdir{ProcessProc} eq \&FixLeicaBase) { + my $oldStart = $$dirInfo{DirStart}; + $$dirInfo{DirStart} = $newStart; + FixLeicaBase($et, $dirInfo); + $$dirInfo{DirStart} = $oldStart; + } + } + } + # add offset to the start of the directory if necessary + if ($$subdir{OffsetPt}) { + if ($$subdir{ByteOrder} =~ /^Little/i) { + SetByteOrder('II'); + } elsif ($$subdir{ByteOrder} =~ /^Big/i) { + SetByteOrder('MM'); + } else { + warn "Can't have variable byte ordering for SubDirectories using OffsetPt\n"; + return undef; + } + #### eval OffsetPt ($valuePtr) + $ifdOffsetPos = eval($$subdir{OffsetPt}) - $dirStart; + } + # pinpoint position to look for this IFD + $firstTry = $lastTry = $newStart - $dirStart; + } + } +# +# scan for something that looks like an IFD +# + if ($dirLen >= 14 + $firstTry) { # minimum size for an IFD + my $offset; +IFD_TRY: for ($offset=$firstTry; $offset<=$lastTry; $offset+=2) { + last if $offset + 14 > $dirLen; # 14 bytes is minimum size for an IFD + my $pos = $dirStart + $offset; +# +# look for a standard TIFF header (Nikon uses it, others may as well), +# + if (SetByteOrder(substr($$dataPt, $pos, 2)) and + Get16u($dataPt, $pos + 2) == 0x2a) + { + $ifdOffsetPos = 4; + } + if (defined $ifdOffsetPos) { + # get pointer to IFD + my $ptr = Get32u($dataPt, $pos + $ifdOffsetPos); + if ($ptr >= $ifdOffsetPos + 4 and $ptr + $offset + 14 <= $dirLen) { + # shift directory start and shorten dirLen accordingly + $$dirInfo{DirStart} += $ptr + $offset; + $$dirInfo{DirLen} -= $ptr + $offset; + # shift pointer base to the start of the TIFF header + my $shift = $$dirInfo{DataPos} + $dirStart + $offset; + $$dirInfo{Base} += $shift; + $$dirInfo{DataPos} -= $shift; + $$dirInfo{Relative} = 1; # set "relative offsets" flag + return $ptr + $offset; + } + undef $ifdOffsetPos; + } +# +# look for a standard IFD (starts with 2-byte entry count) +# + my $num = Get16u($dataPt, $pos); + next unless $num; + # number of entries in an IFD should be between 1 and 255 + if (!($num & 0xff)) { + # lower byte is zero -- byte order could be wrong + ToggleByteOrder(); + $num >>= 8; + } elsif ($num & 0xff00) { + # upper byte isn't zero -- not an IFD + next; + } + my $bytesFromEnd = $size - ($offset + 2 + 12 * $num); + if ($bytesFromEnd < 4) { + next unless $bytesFromEnd == 2 or $bytesFromEnd == 0; + } + # do a quick validation of all format types + my $index; + for ($index=0; $index<$num; ++$index) { + my $entry = $pos + 2 + 12 * $index; + my $format = Get16u($dataPt, $entry+2); + my $count = Get32u($dataPt, $entry+4); + unless ($format) { + # patch for buggy Samsung NX200 JPEG MakerNotes entry count + if ($num == 23 and $index == 21 and $$et{Make} eq 'SAMSUNG') { + Set16u(21, $dataPt, $pos); # really 21 IFD entries! + $et->Warn('Fixed incorrect Makernote entry count', 1); + last; + } + # allow everything to be zero if not first entry + # because some manufacturers pad with null entries + next unless $count or $index == 0; + # patch for Canon EOS 40D firmware 1.0.4 bug: allow zero format for last entry + next if $index==$num-1 and $$et{Model}=~/EOS 40D/; + } + # patch for Sony cameras like the DSC-P10 that have invalid MakerNote entries + next if $num == 12 and $$et{Make} eq 'SONY' and $index >= 8; + # patch for Apple ProRaw DNG which uses format 16 in the maker notes + next if $format == 16 and $$et{Make} eq 'Apple'; + # (would like to verify tag ID, but some manufactures don't + # sort entries in order of tag ID so we don't have much of + # a handle to verify this field) + # verify format + next IFD_TRY if $format < 1 or $format > 13; + # count must be reasonable (can't test for zero count because + # cameras like the 1DmkIII use this value) + next IFD_TRY if $count & 0xff000000; + # extra tests to avoid mis-identifying Samsung makernotes (GT-I9000, etc) + next unless $num == 1; + my $valueSize = $count * $Image::ExifTool::Exif::formatSize[$format]; + if ($valueSize > 4) { + next IFD_TRY if $valueSize > $size; + my $valuePtr = Get32u($dataPt, $entry+8); + next IFD_TRY if $valuePtr > 0x10000; + } + } + $$dirInfo{DirStart} += $offset; # update directory start + $$dirInfo{DirLen} -= $offset; + return $offset; # success!! + } + } + return undef; +} + +#------------------------------------------------------------------------------ +# Fix base offset for Leica maker notes +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, and updates $dirInfo if necessary for new directory +sub FixLeicaBase($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + # get hash of value block positions + my ($valBlock, $valBlkAdj) = GetValueBlocks($dataPt, $dirStart); + if (%$valBlock) { + # get sorted list of value offsets + my @valPtrs = sort { $a <=> $b } keys %$valBlock; + my $numEntries = Get16u($dataPt, $dirStart); + my $diff = $valPtrs[0] - ($numEntries * 12 + 4); + if ($diff > 8) { + $$dirInfo{Base} -= 8; + $$dirInfo{DataPos} += 8; + } + } + my $success = 1; + if ($tagTablePtr) { + $success = Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $tagTablePtr); + } + return $success; +} + +#------------------------------------------------------------------------------ +# Process Canon maker notes +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessCanon($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + # identify Canon MakerNote footer in HtmlDump + # (this code moved from FixBase so it also works for Adobe MakN in DNG images) + if ($$et{HTML_DUMP} and $$dirInfo{DirLen} > 8) { + my $dataPos = $$dirInfo{DataPos}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $footerPos = $dirStart + $$dirInfo{DirLen} - 8; + my $footer = substr(${$$dirInfo{DataPt}}, $footerPos, 8); + if ($footer =~ /^(II\x2a\0|MM\0\x2a)/ and substr($footer,0,2) eq GetByteOrder()) { + my $oldOffset = Get32u(\$footer, 4); + my $newOffset = $dirStart + $dataPos; + my $str = sprintf('Original maker note offset: 0x%.4x', $oldOffset); + if ($oldOffset != $newOffset) { + $str .= sprintf("\nCurrent maker note offset: 0x%.4x", $newOffset); + } + my $filePos = ($$dirInfo{Base} || 0) + $dataPos + $footerPos; + $et->HDump($filePos, 8, '[Canon MakerNotes footer]', $str); + } + } + # process as normal + return Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Process GE type 2 maker notes +# Inputs: 0) ExifTool object ref, 1) DirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessGE2($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt} or return 0; + my $dirStart = $$dirInfo{DirStart} || 0; + + # these maker notes are missing the IFD entry count, but they + # always have 25 entries, so write the entry count manually + Set16u(25, $dataPt, $dirStart); + return Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Process broken Kodak type 8b maker notes +# Inputs: 0) ExifTool object ref, 1) DirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessKodakPatch($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt} or return 0; + my $dirStart = $$dirInfo{DirStart} || 0; + + # these maker notes have an 2 extra null bytes either before or after the entry count, + # so fix this by making a 2-byte entry count just before the first IFD entry + return 0 unless $$dirInfo{DirLen} >= 4; + my $t1 = Get16u($dataPt,$dirStart); + my $t2 = Get16u($dataPt,$dirStart+2); + Set16u($t1 || $t2, $dataPt, $dirStart+2); + $$dirInfo{DirStart} += 2; + return Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Process unknown maker notes or PreviewImage +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, and updates $dirInfo if necessary for new directory +sub ProcessUnknownOrPreview($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + # check to see if this is a preview image + if ($dirLen > 6 and substr($$dataPt, $dirStart, 3) eq "\xff\xd8\xff") { + $et->VerboseDir('PreviewImage'); + if ($$et{HTML_DUMP}) { + my $pos = $$dirInfo{DataPos} + $$dirInfo{Base} + $dirStart; + $et->HDump($pos, $dirLen, '(MakerNotes:PreviewImage data)', "Size: $dirLen bytes") + } + $et->FoundTag('PreviewImage', substr($$dataPt, $dirStart, $dirLen)); + return 1; + } + return ProcessUnknown($et, $dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Write unknown maker notes or PreviewImage +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: directory data, '' to delete, or undef on error +sub WriteUnknownOrPreview($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + my $newVal; + # check to see if this is a preview image + if ($dirLen > 6 and substr($$dataPt, $dirStart, 3) eq "\xff\xd8\xff") { + if ($$et{NEW_VALUE}{$Image::ExifTool::Extra{PreviewImage}}) { + # write or delete new preview (if deleted, it can't currently be added back again) + $newVal = $et->GetNewValue('PreviewImage') || ''; + if ($et->Options('Verbose') > 1) { + $et->VerboseValue("- MakerNotes:PreviewImage", substr($$dataPt, $dirStart, $dirLen)); + $et->VerboseValue("+ MakerNotes:PreviewImage", $newVal) if $newVal; + } + ++$$et{CHANGED}; + } else { + $newVal = substr($$dataPt, $dirStart, $dirLen); + } + } else { + # rewrite MakerNote IFD + $newVal = Image::ExifTool::Exif::WriteExif($et, $dirInfo, $tagTablePtr); + } + return $newVal; +} + +#------------------------------------------------------------------------------ +# Process unknown maker notes assuming it is in EXIF IFD format +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, and updates $dirInfo if necessary for new directory +sub ProcessUnknown($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $success = 0; + + my $loc = LocateIFD($et, $dirInfo); + if (defined $loc) { + $$et{UnknownByteOrder} = GetByteOrder(); + if ($et->Options('Verbose') > 1) { + my $out = $et->Options('TextOut'); + my $indent = $$et{INDENT}; + $indent =~ s/\| $/ /; + printf $out "${indent}Found IFD at offset 0x%.4x in maker notes:\n", + $$dirInfo{DirStart} + $$dirInfo{DataPos} + $$dirInfo{Base}; + } + $success = Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $tagTablePtr); + } else { + $$et{UnknownByteOrder} = ''; # indicates we tried but didn't set byte order + $et->Warn("Unrecognized $$dirInfo{DirName}", 1); + } + return $success; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MakerNotes - Read and write EXIF maker notes + +=head1 SYNOPSIS + +This module is required by Image::ExifTool. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +maker notes in EXIF information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames(3pm)|Image::ExifTool::TagNames>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Matroska.pm b/ExifTool/lib/Image/ExifTool/Matroska.pm new file mode 100644 index 0000000..ab3abfe --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Matroska.pm @@ -0,0 +1,1178 @@ +#------------------------------------------------------------------------------ +# File: Matroska.pm +# +# Description: Read meta information from Matroska multimedia files +# +# Revisions: 05/26/2010 - P. Harvey Created +# +# References: 1) http://www.matroska.org/technical/specs/index.html +# 2) https://www.matroska.org/technical/tagging.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::Matroska; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.15'; + +sub HandleStruct($$;$$$$); + +my %noYes = ( 0 => 'No', 1 => 'Yes' ); + +my %dateInfo = ( + Groups => { 2 => 'Time' }, + # the spec says to use "-" as a date separator, but my only sample uses ":", so + # convert to ":" if necessary, and avoid translating all "-" in case someone wants + # to include a negative time zone (although the spec doesn't mention time zones) + ValueConv => '$val =~ s/^(\d{4})-(\d{2})-/$1:$2:/; $val', + PrintConv => '$self->ConvertDateTime($val)', +); + +my %uidInfo = ( + Format => 'string', + ValueConv => 'unpack("H*",$val)' +); + +# Matroska tags +# Note: The tag ID's in the Matroska documentation include the length designation +# (the upper bits), which is not included in the tag ID's below +%Image::ExifTool::Matroska::Main = ( + GROUPS => { 2 => 'Video' }, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ + The following tags are extracted from Matroska multimedia container files. + This container format is used by file types such as MKA, MKV, MKS and WEBM. + For speed, by default ExifTool extracts tags only up to the first Cluster. + However, the L<Verbose|../ExifTool.html#Verbose> (-v) and L<Unknown|../ExifTool.html#Unknown> = 2 (-U) options force processing of + Cluster data, and the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> (-ee) option skips over Clusters to + read subsequent tags. See + L<http://www.matroska.org/technical/specs/index.html> for the official + Matroska specification. + }, + # supported Format's: signed, unsigned, float, date, string, utf8 + # (or undef by default) +# +# EBML Header +# + 0xa45dfa3 => { + Name => 'EBMLHeader', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x286 => { Name => 'EBMLVersion', Format => 'unsigned' }, + 0x2f7 => { Name => 'EBMLReadVersion', Format => 'unsigned' }, + 0x2f2 => { Name => 'EBMLMaxIDLength', Format => 'unsigned', Unknown => 1 }, + 0x2f3 => { Name => 'EBMLMaxSizeLength', Format => 'unsigned', Unknown => 1 }, + 0x282 => { + Name => 'DocType', + Format => 'string', + # override FileType for "webm" files + RawConv => '$self->OverrideFileType("WEBM") if $val eq "webm"; $val', + }, + 0x287 => { Name => 'DocTypeVersion', Format => 'unsigned' }, + 0x285 => { Name => 'DocTypeReadVersion',Format => 'unsigned' }, +# +# General +# + 0x3f => { Name => 'CRC-32', Format => 'unsigned', Unknown => 1 }, + 0x6c => { Name => 'Void', NoSave => 1, Unknown => 1 }, +# +# Signature +# + 0xb538667 => { + Name => 'SignatureSlot', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x3e8a => { Name => 'SignatureAlgo', Format => 'unsigned' }, + 0x3e9a => { Name => 'SignatureHash', Format => 'unsigned' }, + 0x3ea5 => { Name =>'SignaturePublicKey',Binary => 1, Unknown => 1 }, + 0x3eb5 => { Name => 'Signature', Binary => 1, Unknown => 1 }, + 0x3e5b => { + Name => 'SignatureElements', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x3e7b => { + Name => 'SignatureElementList', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x2532 => { Name => 'SignedElement', Binary => 1, Unknown => 1 }, +# +# Segment +# + 0x8538067 => { + Name => 'SegmentHeader', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x14d9b74 => { + Name => 'SeekHead', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0xdbb => { + Name => 'Seek', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x13ab => { Name => 'SeekID', Binary => 1, Unknown => 1 }, + 0x13ac => { Name => 'SeekPosition', Format => 'unsigned', Unknown => 1 }, +# +# Segment Info +# + 0x549a966 => { + Name => 'Info', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x33a4 => { Name => 'SegmentUID', %uidInfo, Unknown => 1 }, + 0x3384 => { Name => 'SegmentFileName', Format => 'utf8' }, + 0x1cb923 => { Name => 'PrevUID', %uidInfo, Unknown => 1 }, + 0x1c83ab => { Name => 'PrevFileName', Format => 'utf8' }, + 0x1eb923 => { Name => 'NextUID', %uidInfo, Unknown => 1 }, + 0x1e83bb => { Name => 'NextFileName', Format => 'utf8' }, + 0x0444 => { Name => 'SegmentFamily', Binary => 1, Unknown => 1 }, + 0x2924 => { + Name => 'ChapterTranslate', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x29fc => { Name => 'ChapterTranslateEditionUID', %uidInfo, Unknown => 1 }, + 0x29bf => { + Name => 'ChapterTranslateCodec', + Format => 'unsigned', + PrintConv => { 0 => 'Matroska Script', 1 => 'DVD Menu' }, + }, + 0x29a5 => { Name => 'ChapterTranslateID',Binary => 1, Unknown => 1 }, + 0xad7b1 => { + Name => 'TimecodeScale', + Format => 'unsigned', + RawConv => '$$self{TimecodeScale} = $val', + ValueConv => '$val / 1e9', + PrintConv => '($val * 1000) . " ms"', + }, + 0x489 => { + Name => 'Duration', + Format => 'float', + ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val', + PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val', + }, + 0x461 => { + Name => 'DateTimeOriginal', # called "DateUTC" by the spec + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Format => 'date', + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x3ba9 => { Name => 'Title', Format => 'utf8' }, + 0xd80 => { Name => 'MuxingApp', Format => 'utf8' }, + 0x1741 => { Name => 'WritingApp', Format => 'utf8' }, +# +# Cluster +# + 0xf43b675 => { + Name => 'Cluster', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x67 => { + Name => 'TimeCode', + Format => 'unsigned', + Unknown => 1, + ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val', + PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val', + }, + 0x1854 => { + Name => 'SilentTracks', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x18d7 => { Name => 'SilentTrackNumber',Format => 'unsigned' }, + 0x27 => { Name => 'Position', Format => 'unsigned' }, + 0x2b => { Name => 'PrevSize', Format => 'unsigned' }, + 0x23 => { Name => 'SimpleBlock', NoSave => 1, Unknown => 1 }, + 0x20 => { + Name => 'BlockGroup', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x21 => { Name => 'Block', NoSave => 1, Unknown => 1 }, + 0x22 => { Name => 'BlockVirtual', NoSave => 1, Unknown => 1 }, + 0x35a1 => { + Name => 'BlockAdditions', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x26 => { + Name => 'BlockMore', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x6e => { Name => 'BlockAddID', Format => 'unsigned', Unknown => 1 }, + 0x25 => { Name => 'BlockAdditional', NoSave => 1, Unknown => 1 }, + 0x1b => { + Name => 'BlockDuration', + Format => 'unsigned', + Unknown => 1, + ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val', + PrintConv => '$$self{TimecodeScale} ? "$val s" : $val', + }, + 0x7a => { Name => 'ReferencePriority',Format => 'unsigned', Unknown => 1 }, + 0x7b => { + Name => 'ReferenceBlock', + Format => 'signed', + Unknown => 1, + ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val', + PrintConv => '$$self{TimecodeScale} ? "$val s" : $val', + }, + 0x7d => { Name => 'ReferenceVirtual', Format => 'signed', Unknown => 1 }, + 0x24 => { Name => 'CodecState', Binary => 1, Unknown => 1 }, + 0x0e => { + Name => 'Slices', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x68 => { + Name => 'TimeSlice', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x4c => { Name => 'LaceNumber', Format => 'unsigned', Unknown => 1 }, + 0x4d => { Name => 'FrameNumber', Format => 'unsigned', Unknown => 1 }, + 0x4b => { Name => 'BlockAdditionalID',Format => 'unsigned', Unknown => 1 }, + 0x4e => { Name => 'Delay', Format => 'unsigned', Unknown => 1 }, + 0x4f => { Name => 'ClusterDuration', Format => 'unsigned', Unknown => 1 }, + 0x2f => { Name => 'EncryptedBlock', NoSave => 1, Unknown => 1 }, +# +# Tracks +# + 0x654ae6b => { + Name => 'Tracks', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x2e => { + Name => 'TrackEntry', + # reset TrackType member at the start of each track + Condition => 'delete $$self{TrackType}; 1', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x57 => { Name => 'TrackNumber', Format => 'unsigned' }, + 0x33c5 => { Name => 'TrackUID', %uidInfo }, + 0x03 => { + Name => 'TrackType', + Format => 'unsigned', + PrintHex => 1, + # remember types of all tracks encountered, as well as the current track type + RawConv => '$$self{TrackTypes}{$val} = 1; $$self{TrackType} = $val', + PrintConv => { + 0x01 => 'Video', + 0x02 => 'Audio', + 0x03 => 'Complex', # (audio+video) + 0x10 => 'Logo', + 0x11 => 'Subtitle', + 0x12 => 'Buttons', + 0x20 => 'Control', + }, + }, + 0x39 => { Name => 'TrackUsed', Format => 'unsigned', PrintConv => \%noYes }, + 0x08 => { Name => 'TrackDefault', Format => 'unsigned', PrintConv => \%noYes }, + 0x15aa => { Name => 'TrackForced', Format => 'unsigned', PrintConv => \%noYes }, + 0x1c => { + Name => 'TrackLacing', + Format => 'unsigned', + Unknown => 1, + PrintConv => \%noYes, + }, + 0x2de7 => { Name => 'MinCache', Format => 'unsigned', Unknown => 1 }, + 0x2df8 => { Name => 'MaxCache', Format => 'unsigned', Unknown => 1 }, + 0x3e383 => [ + { + Name => 'VideoFrameRate', + Condition => '$$self{TrackType} and $$self{TrackType} == 0x01', + Format => 'unsigned', + ValueConv => '$val ? 1e9 / $val : 0', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + },{ + Name => 'DefaultDuration', + Format => 'unsigned', + ValueConv => '$val / 1e9', + PrintConv => '($val * 1000) . " ms"', + } + ], + 0x3314f => { Name => 'TrackTimecodeScale',Format => 'float' }, + 0x137f => { Name => 'TrackOffset', Format => 'signed', Unknown => 1 }, + 0x15ee => { Name => 'MaxBlockAdditionID',Format => 'unsigned', Unknown => 1 }, + 0x136e => { Name => 'TrackName', Format => 'utf8' }, + 0x2b59c => { Name => 'TrackLanguage', Format => 'string' }, + 0x2b59d => { Name => 'TrackLanguageIETF', Format => 'string' }, + 0x06 => [ + { + Name => 'VideoCodecID', + Condition => '$$self{TrackType} and $$self{TrackType} == 0x01', + Format => 'string', + },{ + Name => 'AudioCodecID', + Condition => '$$self{TrackType} and $$self{TrackType} == 0x02', + Format => 'string', + },{ + Name => 'CodecID', + Format => 'string', + } + ], + 0x23a2 => { Name => 'CodecPrivate', Binary => 1, Unknown => 1 }, + 0x58688 => [ + { + Name => 'VideoCodecName', + Condition => '$$self{TrackType} and $$self{TrackType} == 0x01', + Format => 'utf8', + },{ + Name => 'AudioCodecName', + Condition => '$$self{TrackType} and $$self{TrackType} == 0x02', + Format => 'utf8', + },{ + Name => 'CodecName', + Format => 'utf8', + } + ], + 0x3446 => { Name => 'TrackAttachmentUID',%uidInfo }, + 0x1a9697=>{ Name => 'CodecSettings', Format => 'utf8' }, + 0x1b4040=>{ Name => 'CodecInfoURL', Format => 'string' }, + 0x6b240 =>{ Name => 'CodecDownloadURL', Format => 'string' }, + 0x2a => { Name => 'CodecDecodeAll', Format => 'unsigned', PrintConv => \%noYes }, + 0x2fab => { Name => 'TrackOverlay', Format => 'unsigned', Unknown => 1 }, + 0x2624 => { + Name => 'TrackTranslate', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x26fc => { Name => 'TrackTranslateEditionUID', %uidInfo, Unknown => 1 }, + 0x26bf => { + Name => 'TrackTranslateCodec', + Format => 'unsigned', + PrintConv => { 0 => 'Matroska Script', 1 => 'DVD Menu' }, + }, + 0x26a5 => { Name => 'TrackTranslateTrackID', Binary => 1, Unknown => 1 }, +# +# Video +# + 0x60 => { + Name => 'Video', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x1a => { + Name => 'VideoScanType', + Format => 'unsigned', + PrintConv => { + 0 => 'Progressive', + 1 => 'Interlaced', + }, + }, + 0x13b8 => { + Name => 'Stereo3DMode', + Format => 'unsigned', + Printconv => { + 0 => 'Mono', + 1 => 'Right Eye', + 2 => 'Left Eye', + 3 => 'Both Eyes', + }, + }, + 0x30 => { Name => 'ImageWidth', Format => 'unsigned' }, + 0x3a => { Name => 'ImageHeight', Format => 'unsigned' }, + 0x14aa => { Name => 'CropBottom', Format => 'unsigned' }, + 0x14bb => { Name => 'CropTop', Format => 'unsigned' }, + 0x14cc => { Name => 'CropLeft', Format => 'unsigned' }, + 0x14dd => { Name => 'CropRight', Format => 'unsigned' }, + 0x14b0 => { Name => 'DisplayWidth', Format => 'unsigned' }, + 0x14ba => { Name => 'DisplayHeight', Format => 'unsigned' }, + 0x14b2 => { + Name => 'DisplayUnit', + Format => 'unsigned', + PrintConv => { + 0 => 'Pixels', + 1 => 'cm', + 2 => 'inches', + 3 => 'Display Aspect Ratio', + 4 => 'Unknown', + }, + }, + 0x14b3 => { + Name => 'AspectRatioType', + Format => 'unsigned', + PrintConv => { + 0 => 'Free Resizing', + 1 => 'Keep Aspect Ratio', + 2 => 'Fixed', + }, + }, + 0xeb524 => { Name => 'ColorSpace', Binary => 1, Unknown => 1 }, + 0xfb523 => { Name => 'Gamma', Format => 'float' }, + 0x383e3 => { Name => 'FrameRate', Format => 'float' }, +# +# Audio +# + 0x61 => { + Name => 'Audio', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x35 => { Name => 'AudioSampleRate', Format => 'float', Groups => { 2 => 'Audio' } }, + 0x38b5 => { Name => 'OutputAudioSampleRate',Format => 'float', Groups => { 2 => 'Audio' } }, + 0x1f => { Name => 'AudioChannels', Format => 'unsigned', Groups => { 2 => 'Audio' } }, + 0x3d7b => { + Name => 'ChannelPositions', + Binary => 1, + Unknown => 1, + Groups => { 2 => 'Audio' }, + }, + 0x2264 => { Name => 'AudioBitsPerSample', Format => 'unsigned', Groups => { 2 => 'Audio' } }, +# +# Content Encoding +# + 0x2d80 => { + Name => 'ContentEncodings', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x2240 => { + Name => 'ContentEncoding', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x1031 => { Name => 'ContentEncodingOrder', Format => 'unsigned', Unknown => 1 }, + 0x1032 => { Name => 'ContentEncodingScope', Format => 'unsigned', Unknown => 1 }, + 0x1033 => { + Name => 'ContentEncodingType', + Format => 'unsigned', + PrintConv => { 0 => 'Compression', 1 => 'Encryption' }, + }, + 0x1034 => { + Name => 'ContentCompression', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x254 => { + Name => 'ContentCompressionAlgorithm', + Format => 'unsigned', + PrintConv => { + 0 => 'zlib', + 1 => 'bzlib', + 2 => 'lzo1x', + 3 => 'Header Stripping', + }, + }, + 0x255 => { Name => 'ContentCompressionSettings',Binary => 1, Unknown => 1 }, + 0x1035 => { + Name => 'ContentEncryption', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x7e1 => { + Name => 'ContentEncryptionAlgorithm', + Format => 'unsigned', + PrintConv => { + 0 => 'Not Encrypted', + 1 => 'DES', + 2 => '3DES', + 3 => 'Twofish', + 4 => 'Blowfish', + 5 => 'AES', + }, + }, + 0x7e2 => { Name => 'ContentEncryptionKeyID',Binary => 1, Unknown => 1 }, + 0x7e3 => { Name => 'ContentSignature', Binary => 1, Unknown => 1 }, + 0x7e4 => { Name => 'ContentSignatureKeyID', Binary => 1, Unknown => 1 }, + 0x7e5 => { + Name => 'ContentSignatureAlgorithm', + Format => 'unsigned', + PrintConv => { + 0 => 'Not Signed', + 1 => 'RSA', + }, + }, + 0x7e6 => { + Name => 'ContentSignatureHashAlgorithm', + Format => 'unsigned', + PrintConv => { + 0 => 'Not Signed', + 1 => 'SHA1-160', + 2 => 'MD5', + }, + }, +# +# Cues +# + 0xc53bb6b => { + Name => 'Cues', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x3b => { + Name => 'CuePoint', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x33 => { + Name => 'CueTime', + Format => 'unsigned', + Unknown => 1, + ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val', + PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val', + }, + 0x37 => { + Name => 'CueTrackPositions', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x77 => { Name => 'CueTrack', Format => 'unsigned', Unknown => 1 }, + 0x71 => { Name => 'CueClusterPosition',Format => 'unsigned', Unknown => 1 }, + 0x1378 => { Name => 'CueBlockNumber', Format => 'unsigned', Unknown => 1 }, + 0x6a => { Name => 'CueCodecState', Format => 'unsigned', Unknown => 1 }, + 0x5b => { + Name => 'CueReference', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x16 => { + Name => 'CueRefTime', + Format => 'unsigned', + Unknown => 1, + ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val', + PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val', + }, + 0x17 => { Name => 'CueRefCluster', Format => 'unsigned', Unknown => 1 }, + 0x135f=> { Name => 'CueRefNumber', Format => 'unsigned', Unknown => 1 }, + 0x6b => { Name => 'CueRefCodecState', Format => 'unsigned', Unknown => 1 }, +# +# Attachments +# + 0x941a469 => { + Name => 'Attachments', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x21a7 => { + Name => 'AttachedFile', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x67e => { Name => 'AttachedFileDescription',Format => 'utf8' }, + 0x66e => { Name => 'AttachedFileName', Format => 'utf8' }, + 0x660 => { Name => 'AttachedFileMIMEType', Format => 'string' }, + 0x65c => { Name => 'AttachedFileData', Binary => 1 }, + 0x6ae => { Name => 'AttachedFileUID', %uidInfo }, + 0x675 => { Name => 'AttachedFileReferral', Binary => 1, Unknown => 1 }, +# +# Chapters +# + 0x43a770 => { + Name => 'Chapters', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x5b9 => { + Name => 'EditionEntry', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x5bc => { Name => 'EditionUID', %uidInfo, Unknown => 1 }, + 0x5bd => { Name => 'EditionFlagHidden', Format => 'unsigned', Unknown => 1 }, + 0x5db => { Name => 'EditionFlagDefault',Format => 'unsigned', Unknown => 1 }, + 0x5dd => { Name => 'EditionFlagOrdered',Format => 'unsigned', Unknown => 1 }, + 0x36 => { + Name => 'ChapterAtom', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x33c4 => { Name => 'ChapterUID', %uidInfo, Unknown => 1 }, + 0x11 => { + Name => 'ChapterTimeStart', + Groups => { 1 => 'Chapter#' }, + Format => 'unsigned', + ValueConv => '$val / 1e9', + PrintConv => 'ConvertDuration($val)', + }, + 0x12 => { + Name => 'ChapterTimeEnd', + Format => 'unsigned', + ValueConv => '$val / 1e9', + PrintConv => 'ConvertDuration($val)', + }, + 0x18 => { Name => 'ChapterFlagHidden', Format => 'unsigned', Unknown => 1 }, + 0x598 => { Name => 'ChapterFlagEnabled',Format => 'unsigned', Unknown => 1 }, + 0x2e67=> { Name => 'ChapterSegmentUID', %uidInfo, Unknown => 1 }, + 0x2ebc=> { Name => 'ChapterSegmentEditionUID', %uidInfo, Unknown => 1 }, + 0x23c3 => { + Name => 'ChapterPhysicalEquivalent', + Format => 'unsigned', + PrintConv => { + 10 => 'Index', + 20 => 'Track', + 30 => 'Session', + 40 => 'Layer', + 50 => 'Side', + 60 => 'CD / DVD', + 70 => 'Set / Package', + }, + }, + 0x0f => { + Name => 'ChapterTrack', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x09 => { Name => 'ChapterTrackNumber', Format => 'unsigned', Unknown => 1 }, + 0x00 => { + Name => 'ChapterDisplay', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x05 => { Name => 'ChapterString', Format => 'utf8' }, + 0x37c => { Name => 'ChapterLanguage', Format => 'string' }, + 0x37e => { Name => 'ChapterCountry', Format => 'string' }, + 0x2944 => { + Name => 'ChapterProcess', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x2955 => { + Name => 'ChapterProcessCodecID', + Format => 'unsigned', + Unknown => 1, + PrintConv => { 0 => 'Matroska', 1 => 'DVD' }, + }, + 0x50d => { Name => 'ChapterProcessPrivate', Binary => 1, Unknown => 1 }, + 0x2911 => { + Name => 'ChapterProcessCommand', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x2922 => { + Name => 'ChapterProcessTime', + Format => 'unsigned', + Unknown => 1, + PrintConv => { + 0 => 'For Duration of Chapter', + 1 => 'Before Chapter', + 2 => 'After Chapter', + }, + }, + 0x2933 => { Name => 'ChapterProcessData', Binary => 1, Unknown => 1 }, +# +# Tags +# + 0x254c367 => { + Name => 'Tags', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x3373 => { + Name => 'Tag', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + 0x23c0 => { + Name => 'Targets', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + # Targets elements + 0x28ca => { Name => 'TargetTypeValue', Format => 'unsigned' }, + 0x23ca => { Name => 'TargetType', Format => 'string' }, + 0x23c5 => { Name => 'TagTrackUID', %uidInfo }, + 0x23c9 => { Name => 'TagEditionUID', %uidInfo }, + 0x23c4 => { Name => 'TagChapterUID', %uidInfo }, + 0x23c6 => { Name => 'TagAttachmentUID', %uidInfo }, + 0x27c8 => { + Name => 'SimpleTag', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' }, + }, + # SimpleTag elements + 0x5a3 => { Name => 'TagName', Format => 'utf8' }, + 0x47a => { Name => 'TagLanguage', Format => 'string' }, + 0x47a => { Name => 'TagLanguageBCP47', Format => 'string' }, + 0x484 => { Name => 'TagDefault', Format => 'unsigned', PrintConv => \%noYes }, + 0x487 => { Name => 'TagString', Format => 'utf8' }, + 0x485 => { Name => 'TagBinary', Binary => 1 }, +# +# Spherical Video V2 (untested) +# + 0x7670 => { + Name => 'Projection', + SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Projection' }, + }, +); + +# Spherical video v2 projection tags (ref https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md) +%Image::ExifTool::Matroska::Projection = ( + GROUPS => { 2 => 'Video' }, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ + Projection tags defined by the Spherical Video V2 specification. See + L<https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md> + for the specification. + }, + 0x7671 => { + Name => 'ProjectionType', + Format => 'unsigned', + DataMember => 'ProjectionType', + RawConv => '$$self{ProjectionType} = $val', + PrintConv => { + 0 => 'Rectangular', + 1 => 'Equirectangular', + 2 => 'Cubemap', + 3 => 'Mesh', + }, + }, + # ProjectionPrivate in the spec + 0x7672 => [{ + Name => 'EquirectangularProj', + Condition => '$$self{ProjectionType} == 1', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::equi' }, + },{ + Name => 'CubemapProj', + Condition => '$$self{ProjectionType} == 2', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::cbmp' }, + },{ # (don't decode 3 because it is a PITA) + Name => 'ProjectionPrivate', + Binary => 1, + }], + 0x7673 => { Name => 'ProjectionPoseYaw', Format => 'float' }, + 0x7674 => { Name => 'ProjectionPosePitch', Format => 'float' }, + 0x7675 => { Name => 'ProjectionPoseRoll', Format => 'float' }, +); + +# standardized tag names (ref 2) +%Image::ExifTool::Matroska::StdTag = ( + GROUPS => { 2 => 'Video' }, + VARS => { LONG_TAGS => 1 }, + NOTES => q{ + Standardized Matroska tags, stored in a SimpleTag structure (see + L<https://www.matroska.org/technical/tagging.html>). + }, + ORIGINAL => 'Original', # struct + SAMPLE => 'Sample', # struct + COUNTRY => 'Country', # struct (should deal with this properly!) + TOTAL_PARTS => 'TotalParts', + PART_NUMBER => 'PartNumber', + PART_OFFSET => 'PartOffset', + TITLE => 'Title', + SUBTITLE => 'Subtitle', + URL => 'URL', # nested + SORT_WITH => 'SortWith', # nested + INSTRUMENTS => 'Instruments', # nested + EMAIL => 'Email', # nested + ADDRESS => 'Address', # nested + FAX => 'FAX', # nested + PHONE => 'Phone', # nested + ARTIST => 'Artist', + LEAD_PERFORMER => 'LeadPerformer', + ACCOMPANIMENT => 'Accompaniment', + COMPOSER => 'Composer', + ARRANGER => 'Arranger', + LYRICS => 'Lyrics', + LYRICIST => 'Lyricist', + CONDUCTOR => 'Conductor', + DIRECTOR => 'Director', + ASSISTANT_DIRECTOR => 'AssistantDirector', + DIRECTOR_OF_PHOTOGRAPHY => 'DirectorOfPhotography', + SOUND_ENGINEER => 'SoundEngineer', + ART_DIRECTOR => 'ArtDirector', + PRODUCTION_DESIGNER => 'ProductionDesigner', + CHOREGRAPHER => 'Choregrapher', + COSTUME_DESIGNER => 'CostumeDesigner', + ACTOR => 'Actor', + CHARACTER => 'Character', + WRITTEN_BY => 'WrittenBy', + SCREENPLAY_BY => 'ScreenplayBy', + EDITED_BY => 'EditedBy', + PRODUCER => 'Producer', + COPRODUCER => 'Coproducer', + EXECUTIVE_PRODUCER => 'ExecutiveProducer', + DISTRIBUTED_BY => 'DistributedBy', + MASTERED_BY => 'MasteredBy', + ENCODED_BY => 'EncodedBy', + MIXED_BY => 'MixedBy', + REMIXED_BY => 'RemixedBy', + PRODUCTION_STUDIO => 'ProductionStudio', + THANKS_TO => 'ThanksTo', + PUBLISHER => 'Publisher', + LABEL => 'Label', + GENRE => 'Genre', + MOOD => 'Mood', + ORIGINAL_MEDIA_TYPE => 'OriginalMediaType', + CONTENT_TYPE => 'ContentType', + SUBJECT => 'Subject', + DESCRIPTION => 'Description', + KEYWORDS => 'Keywords', + SUMMARY => 'Summary', + SYNOPSIS => 'Synopsis', + INITIAL_KEY => 'InitialKey', + PERIOD => 'Period', + LAW_RATING => 'LawRating', + DATE_RELEASED => { Name => 'DateReleased', %dateInfo }, + DATE_RECORDED => { Name => 'DateTimeOriginal', %dateInfo, Description => 'Date/Time Original' }, + DATE_ENCODED => { Name => 'DateEncoded', %dateInfo }, + DATE_TAGGED => { Name => 'DateTagged', %dateInfo }, + DATE_DIGITIZED => { Name => 'CreateDate', %dateInfo }, + DATE_WRITTEN => { Name => 'DateWritten', %dateInfo }, + DATE_PURCHASED => { Name => 'DatePurchased', %dateInfo }, + RECORDING_LOCATION => 'RecordingLocation', + COMPOSITION_LOCATION => 'CompositionLocation', + COMPOSER_NATIONALITY => 'ComposerNationality', + COMMENT => 'Comment', + PLAY_COUNTER => 'PlayCounter', + RATING => 'Rating', + ENCODER => 'Encoder', + ENCODER_SETTINGS => 'EncoderSettings', + BPS => 'BPS', + FPS => 'FPS', + BPM => 'BPM', + MEASURE => 'Measure', + TUNING => 'Tuning', + REPLAYGAIN_GAIN => 'ReplaygainGain', + REPLAYGAIN_PEAK => 'ReplaygainPeak', + ISRC => 'ISRC', + MCDI => 'MCDI', + ISBN => 'ISBN', + BARCODE => 'Barcode', + CATALOG_NUMBER => 'CatalogNumber', + LABEL_CODE => 'LabelCode', + LCCN => 'Lccn', + IMDB => 'IMDB', + TMDB => 'TMDB', + TVDB => 'TVDB', + PURCHASE_ITEM => 'PurchaseItem', + PURCHASE_INFO => 'PurchaseInfo', + PURCHASE_OWNER => 'PurchaseOwner', + PURCHASE_PRICE => 'PurchasePrice', + PURCHASE_CURRENCY => 'PurchaseCurrency', + COPYRIGHT => 'Copyright', + PRODUCTION_COPYRIGHT => 'ProductionCopyright', + LICENSE => 'License', + TERMS_OF_USE => 'TermsOfUse', + # (the following are untested) + 'spherical-video' => { #https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md + Name => 'SphericalVideoXML', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + ProcessProc => 'Image::ExifTool::XMP::ProcessGSpherical', + }, + }, + 'SPHERICAL-VIDEO' => { #https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md + Name => 'SphericalVideoXML', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + ProcessProc => 'Image::ExifTool::XMP::ProcessGSpherical', + }, + }, +); + +#------------------------------------------------------------------------------ +# Handle MKV SimpleTag structure +# Inputs: 0) ExifTool ref, 1) structure ref, 2) parent tag ID, 3) parent tag Name, +# 4) language code, 5) country code +sub HandleStruct($$;$$$$) +{ + local $_; + my ($et, $struct, $pid, $pname, $lang, $ctry) = @_; + my $tagTbl = GetTagTable('Image::ExifTool::Matroska::StdTag'); + my $tag = $$struct{TagName}; + my $tagInfo = $$tagTbl{$tag}; + # create tag if necessary + unless (ref $tagInfo eq 'HASH') { + my $name = ucfirst lc $tag; + $name =~ tr/0-9a-zA-Z_//dc; + $name =~ s/_([a-z])/\U$1/g; + $name = "Tag_$name" if length $name < 2; + $tagInfo = AddTagToTable($tagTbl, $tag, { Name => $name }); + } + my ($id, $nm); + if ($pid) { + $id = "$pid/$tag"; + $nm = "$pname/$$tagInfo{Name}"; + unless ($$tagTbl{$id}) { + my %copy = %$tagInfo; + $copy{Name} = $nm; + $tagInfo = AddTagToTable($tagTbl, $id, \%copy); + } + } else { + ($id, $nm) = ($tag, $$tagInfo{Name}); + } + if (defined $$struct{TagString} or defined $$struct{TagBinary}) { + my $val = defined $$struct{TagString} ? $$struct{TagString} : \$$struct{TagBinary}; + $lang = $$struct{TagLanguageBCP47} || $$struct{TagLanguage} || $lang; + # (Note: not currently handling TagDefault attribute) + my $code = $lang; + $code = $lang ? "${lang}-${ctry}" : "eng-${ctry}" if $ctry; # ('eng' is default lang) + if ($code) { + $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $code); + $et->HandleTag($tagTbl, $$tagInfo{TagID}, $val); + } else { + $et->HandleTag($tagTbl, $id, $val); + } + # COUNTRY is handled as an attribute for contained tags + if ($tag eq 'COUNTRY') { + $ctry = $val; + ($id, $nm) = ($pid, $pname); + } + } + if ($$struct{struct}) { + # step into each contained structure + HandleStruct($et, $_, $id, $nm, $lang, $ctry) foreach @{$$struct{struct}}; + } +} + +#------------------------------------------------------------------------------ +# Get variable-length Matroska integer +# Inputs: 0) data buffer, 1) position in data +# Returns: integer value and updates position, -1 for unknown/reserved value, +# or undef if no data left +sub GetVInt($$) +{ + return undef if $_[1] >= length $_[0]; + my $val = ord(substr($_[0], $_[1]++)); + my $num = 0; + unless ($val) { + return undef if $_[1] >= length $_[0]; + $val = ord(substr($_[0], $_[1]++)); + return undef unless $val; # can't be this large! + $num += 7; # 7 more bytes to read (we just read one) + } + my $mask = 0x7f; + while ($val == ($val & $mask)) { + $mask >>= 1; + ++$num; + } + $val = ($val & $mask); + my $unknown = ($val == $mask); + return undef if $_[1] + $num > length $_[0]; + while ($num) { + my $b = ord(substr($_[0], $_[1]++)); + $unknown = 0 if $b != 0xff; + $val = $val * 256 + $b; + --$num; + } + return $unknown ? -1 : $val; +} + +#------------------------------------------------------------------------------ +# Read information from a Matroska multimedia file (MKV, MKA, MKS) +# Inputs: 0) ExifTool object reference, 1) Directory information reference +# Returns: 1 on success, 0 if this wasn't a valid Matroska file +sub ProcessMKV($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $buf2, @dirEnd, $trackIndent, %trackTypes, $struct); + + $raf->Read($buff, 4) == 4 or return 0; + return 0 unless $buff =~ /^\x1a\x45\xdf\xa3/; + + # read in 64kB blocks (already read 4 bytes) + $raf->Read($buff, 65532) or return 0; + my $dataLen = length $buff; + my ($pos, $dataPos) = (0, 4); + + # verify header length + my $hlen = GetVInt($buff, $pos); + return 0 unless $hlen and $hlen > 0; + $pos + $hlen > $dataLen and $et->Warn('Truncated Matroska header'), return 1; + $et->SetFileType(); + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::Matroska::Main'); + + # set flag to process entire file (otherwise we stop at the first Cluster) + my $verbose = $et->Options('Verbose'); + my $processAll = ($verbose or $et->Options('Unknown') > 1) ? 2 : 0; + ++$processAll if $et->Options('ExtractEmbedded'); + $$et{TrackTypes} = \%trackTypes; # store Track types reference + my $oldIndent = $$et{INDENT}; + my $chapterNum = 0; + my $dirName = 'MKV'; + + # loop over all Matroska elements + for (;;) { + while (@dirEnd) { + if ($pos + $dataPos >= $dirEnd[-1][0]) { + pop @dirEnd; + if ($struct) { + if (@dirEnd and $dirEnd[-1][2]) { + # save this nested structure + $dirEnd[-1][2]{struct} or $dirEnd[-1][2]{struct} = [ ]; + push @{$dirEnd[-1][2]{struct}}, $struct; + $struct = $dirEnd[-1][2]; + } else { + # handle completed structures now + HandleStruct($et, $struct); + undef $struct; + } + } + $dirName = @dirEnd ? $dirEnd[-1][1] : 'MKV'; + # use INDENT to decide whether or not we are done this Track element + delete $$et{SET_GROUP1} if $trackIndent and $trackIndent eq $$et{INDENT}; + $$et{INDENT} = substr($$et{INDENT}, 0, -2); + } else { + $dirName = $dirEnd[-1][1]; + last; + } + } + # read more if we are getting close to the end of our buffer + # (24 more bytes should be enough to read this element header) + if ($pos + 24 > $dataLen and $raf->Read($buf2, 65536)) { + $buff = substr($buff, $pos) . $buf2; + undef $buf2; + $dataPos += $pos; + $dataLen = length $buff; + $pos = 0; + } + my $tag = GetVInt($buff, $pos); + last unless defined $tag and $tag >= 0; + my $size = GetVInt($buff, $pos); + last unless defined $size; + my $unknownSize; + $size < 0 and $unknownSize = 1, $size = 1e20; + if (@dirEnd and $pos + $dataPos + $size > $dirEnd[-1][0]) { + $et->Warn("Invalid or corrupted $dirEnd[-1][1] master element"); + $pos = $dirEnd[-1][0] - $dataPos; + if ($pos < 0 or $pos > $dataLen) { + $buff = ''; + $dataPos += $pos; + $dataLen = 0; + $pos = 0; + $raf->Seek($dataPos, 0) or last; + } + next; + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + # just fall through into the contained EBML elements + if ($tagInfo) { + if ($$tagInfo{SubDirectory}) { + # stop processing at first cluster unless we are using -v -U or -ee + if ($$tagInfo{Name} eq 'Cluster' and $processAll < 2) { + last unless $processAll; + undef $tagInfo; # just skip the Cluster when -ee is used + } else { + $$et{INDENT} .= '| '; + $et->VerboseDir($$tagTablePtr{$tag}{Name}, undef, $size); + $dirName = $$tagInfo{Name}; + push @dirEnd, [ $pos + $dataPos + $size, $dirName, $struct ]; + $struct = { } if $dirName eq 'SimpleTag'; # keep track of SimpleTag elements + if ($$tagInfo{Name} eq 'ChapterAtom') { + $$et{SET_GROUP1} = 'Chapter' . (++$chapterNum); + $trackIndent = $$et{INDENT}; + } + next; + } + } + } elsif ($verbose) { + $et->VPrint(0,sprintf("$$et{INDENT}- Tag 0x%x (Unknown, %d bytes)\n", $tag, $size)); + } + last if $unknownSize; + if ($pos + $size > $dataLen) { + # how much more do we need to read? + my $more = $pos + $size - $dataLen; + # just skip unknown and large data blocks + if (not $tagInfo or $more > 10000000) { + # don't try to skip very large blocks unless LargeFileSupport is enabled + last if $more >= 0x80000000 and not $et->Options('LargeFileSupport'); + $raf->Seek($more, 1) or last; + $buff = ''; + $dataPos += $dataLen + $more; + $dataLen = 0; + $pos = 0; + next; + } else { + # read data in multiples of 64kB + $more = (int($more / 65536) + 1) * 65536; + if ($raf->Read($buf2, $more)) { + $buff = substr($buff, $pos) . $buf2; + undef $buf2; + $dataPos += $pos; + $dataLen = length $buff; + $pos = 0; + } + last if $pos + $size > $dataLen; + } + } + unless ($tagInfo) { + # ignore the element + $pos += $size; + next; + } + my $val; + if ($$tagInfo{Format}) { + my $fmt = $$tagInfo{Format}; + if ($fmt eq 'string' or $fmt eq 'utf8') { + ($val = substr($buff, $pos, $size)) =~ s/\0.*//s; + $val = $et->Decode($val, 'UTF8') if $fmt eq 'utf8'; + } elsif ($fmt eq 'float') { + if ($size == 4) { + $val = GetFloat(\$buff, $pos); + } elsif ($size == 8) { + $val = GetDouble(\$buff, $pos); + } else { + $et->Warn("Illegal float size ($size)"); + } + } else { + my @vals = unpack("x${pos}C$size", $buff); + $val = 0; + if ($fmt eq 'signed' or $fmt eq 'date') { + my $over = 1; + foreach (@vals) { + $val = $val * 256 + $_; + $over *= 256; + } + # interpret negative numbers + $val -= $over if $vals[0] & 0x80; + # convert dates (nanoseconds since 2001:01:01) + if ($fmt eq 'date') { + my $t = $val / 1e9; + my $f = $t - int($t); # fractional seconds + $f =~ s/^\d+//; # remove leading zero + # (8 leap days between 1970 and 2001) + $t += (((2001-1970)*365+8)*24*3600); + $val = Image::ExifTool::ConvertUnixTime($t) . $f . 'Z'; + } + } else { # must be unsigned + $val = $val * 256 + $_ foreach @vals; + } + } + # set group1 to Track/Chapter number + if ($$tagInfo{Name} eq 'TrackNumber') { + $$et{SET_GROUP1} = 'Track' . $val; + $trackIndent = $$et{INDENT}; + } + } + my %parms = ( + DataPt => \$buff, + DataPos => $dataPos, + Start => $pos, + Size => $size, + ); + if ($$tagInfo{NoSave} or $struct) { + $et->VerboseInfo($tag, $tagInfo, Value => $val, %parms) if $verbose; + $$struct{$$tagInfo{Name}} = $val if $struct; + } else { + $et->HandleTag($tagTablePtr, $tag, $val, %parms); + } + $pos += $size; # step to next element + } + $$et{INDENT} = $oldIndent; + delete $$et{SET_GROUP1}; + # override file type if necessary based on existing track types + unless ($trackTypes{0x01} or $trackTypes{0x03}) { # video or complex? + if ($trackTypes{0x02}) { # audio? + $et->OverrideFileType('MKA'); + } elsif ($trackTypes{0x11}) { # subtitle? + $et->OverrideFileType('MKS'); + } + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Matroska - Read meta information from Matroska files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read meta +information from Matroska multimedia files (MKA, MKV, MKS and WEBM). + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.matroska.org/technical/specs/index.html> + +=item L<https://www.matroska.org/technical/tagging.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Matroska Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Microsoft.pm b/ExifTool/lib/Image/ExifTool/Microsoft.pm new file mode 100644 index 0000000..cbfa843 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Microsoft.pm @@ -0,0 +1,1122 @@ +#------------------------------------------------------------------------------ +# File: Microsoft.pm +# +# Description: Definitions for custom Microsoft tags +# +# Revisions: 2010/10/01 - P. Harvey Created +# 2011/10/05 - PH Added ProcessXtra() +# 2021/02/23 - PH Added abiltity to write Xtra tags +# +# References: 1) http://research.microsoft.com/en-us/um/redmond/groups/ivm/hdview/hdmetadataspec.htm +#------------------------------------------------------------------------------ + +package Image::ExifTool::Microsoft; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::XMP; + +$VERSION = '1.23'; + +sub ProcessXtra($$$); +sub WriteXtra($$$); +sub CheckXtra($$$); + +# tags written by Microsoft HDView (ref 1) +%Image::ExifTool::Microsoft::Stitch = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'float', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => q{ + Information found in the Microsoft custom EXIF tag 0x4748, as written by + Windows Live Photo Gallery. + }, + 0 => { + Name => 'PanoramicStitchVersion', + Format => 'int32u', + }, + 1 => { + Name => 'PanoramicStitchCameraMotion', + Format => 'int32u', + PrintConv => { + 2 => 'Rigid Scale', + 3 => 'Affine', + 4 => '3D Rotation', + 5 => 'Homography', + }, + }, + 2 => { + Name => 'PanoramicStitchMapType', + Format => 'int32u', + PrintConv => { + 0 => 'Perspective', + 1 => 'Horizontal Cylindrical', + 2 => 'Horizontal Spherical', + 257 => 'Vertical Cylindrical', + 258 => 'Vertical Spherical', + }, + }, + 3 => 'PanoramicStitchTheta0', + 4 => 'PanoramicStitchTheta1', + 5 => 'PanoramicStitchPhi0', + 6 => 'PanoramicStitchPhi1', +); + +# Microsoft Photo schema properties (MicrosoftPhoto) (ref PH) +%Image::ExifTool::Microsoft::XMP = ( + %Image::ExifTool::XMP::xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-microsoft', 2 => 'Image' }, + NAMESPACE => 'MicrosoftPhoto', + TABLE_DESC => 'XMP Microsoft', + VARS => { NO_ID => 1 }, + NOTES => q{ + Microsoft Photo 1.0 schema XMP tags. This is likely not a complete list, + but represents tags which have been observed in sample images. The actual + namespace prefix is "MicrosoftPhoto", but ExifTool shortens this in the + family 1 group name. + }, + CameraSerialNumber => { }, + DateAcquired => { Groups => { 2 => 'Time' }, %Image::ExifTool::XMP::dateTimeInfo }, + FlashManufacturer => { }, + FlashModel => { }, + LastKeywordIPTC => { List => 'Bag' }, + LastKeywordXMP => { List => 'Bag' }, + LensManufacturer => { }, + LensModel => { Avoid => 1 }, + Rating => { + Name => 'RatingPercent', + Notes => q{ + XMP-xmp:Rating values of 1,2,3,4 and 5 stars correspond to RatingPercent + values of 1,25,50,75 and 99 respectively + }, + }, + CreatorAppId => { Name => 'CreatorAppID' }, + CreatorOpenWithUIOptions => { }, + ItemSubType => { }, +); + +# Microsoft Photo 1.1 schema properties (MP1 - written as 'prefix0' by MSPhoto) (ref PH) +%Image::ExifTool::Microsoft::MP1 = ( + %Image::ExifTool::XMP::xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-MP1', 2 => 'Image' }, + NAMESPACE => 'MP1', + TABLE_DESC => 'XMP Microsoft Photo', + VARS => { NO_ID => 1 }, + NOTES => q{ + Microsoft Photo 1.1 schema XMP tags which have been observed. + }, + PanoramicStitchCameraMotion => { + PrintConv => { + 'RigidScale' => 'Rigid Scale', + 'Affine' => 'Affine', + '3DRotation' => '3D Rotation', + 'Homography' => 'Homography', + }, + }, + PanoramicStitchMapType => { + PrintConv => { + 'Perspective' => 'Perspective', + 'Horizontal-Cylindrical' => 'Horizontal Cylindrical', + 'Horizontal-Spherical' => 'Horizontal Spherical', + 'Vertical-Cylindrical' => 'Vertical Cylindrical', + 'Vertical-Spherical' => 'Vertical Spherical', + }, + }, + PanoramicStitchPhi0 => { Writable => 'real' }, + PanoramicStitchPhi1 => { Writable => 'real' }, + PanoramicStitchTheta0 => { Writable => 'real' }, + PanoramicStitchTheta1 => { Writable => 'real' }, + WhiteBalance0 => { Writable => 'real' }, + WhiteBalance1 => { Writable => 'real' }, + WhiteBalance2 => { Writable => 'real' }, + Brightness => { Avoid => 1 }, + Contrast => { Avoid => 1 }, + CameraModelID => { Avoid => 1 }, + ExposureCompensation => { Avoid => 1 }, + PipelineVersion => { }, + StreamType => { }, +); + +# Microsoft Photo 1.2 schema properties (MP) (ref PH) +# (also ref http://msdn.microsoft.com/en-us/library/windows/desktop/ee719905(v=vs.85).aspx) +my %sRegions = ( + STRUCT_NAME => 'Microsoft Regions', + NAMESPACE => 'MPReg', + NOTES => q{ + Note that PersonLiveIdCID element is called PersonLiveCID according to the + Microsoft specification, but in practice their software actually writes + PersonLiveIdCID, so ExifTool uses this too. + }, + Rectangle => { }, + PersonDisplayName => { }, + PersonEmailDigest => { }, + PersonLiveIdCID => { }, # (see https://exiftool.org/forum/index.php?topic=4274.msg20368#msg20368) + PersonSourceID => { }, +); +%Image::ExifTool::Microsoft::MP = ( + %Image::ExifTool::XMP::xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-MP', 2 => 'Image' }, + NAMESPACE => 'MP', + TABLE_DESC => 'XMP Microsoft Photo', + VARS => { NO_ID => 1 }, + NOTES => q{ + Microsoft Photo 1.2 schema XMP tags which have been observed. + }, + RegionInfo => { + Name => 'RegionInfoMP', + Struct => { + STRUCT_NAME => 'Microsoft RegionInfo', + NAMESPACE => 'MPRI', + Regions => { Struct => \%sRegions, List => 'Bag' }, + DateRegionsValid => { + Writable => 'date', + Shift => 'Time', + Groups => { 2 => 'Time'}, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + }, + }, + }, + # remove "MP" from tag name (was added only to avoid conflict with XMP-mwg-rs:RegionInfo) + RegionInfoRegions => { Flat => 1, Name => 'RegionInfoRegions' }, + RegionInfoDateRegionsValid => { Flat => 1, Name => 'RegionInfoDateRegionsValid' }, + # shorten flattened Regions tag names to make them easier to use + RegionInfoRegionsRectangle => { Flat => 1, Name => 'RegionRectangle' }, + RegionInfoRegionsPersonDisplayName => { Flat => 1, Name => 'RegionPersonDisplayName' }, + RegionInfoRegionsPersonEmailDigest => { Flat => 1, Name => 'RegionPersonEmailDigest' }, + RegionInfoRegionsPersonLiveIdCID => { Flat => 1, Name => 'RegionPersonLiveIdCID' }, + RegionInfoRegionsPersonSourceID => { Flat => 1, Name => 'RegionPersonSourceID' }, +); + +# Xtra tags written in MP4 files written by Microsoft Windows Media Player +# (ref http://msdn.microsoft.com/en-us/library/windows/desktop/dd562330(v=VS.85).aspx) +# Note: These tags are closely related to tags in Image::ExifTool::ASF::ExtendedDescr +# and Image::ExifTool::WTV::Metadata +%Image::ExifTool::Microsoft::Xtra = ( + PROCESS_PROC => \&ProcessXtra, + WRITE_PROC => \&WriteXtra, + CHECK_PROC => \&CheckXtra, + WRITE_GROUP => 'Microsoft', + AVOID => 1, + GROUPS => { 0 => 'QuickTime', 2 => 'Video' }, + VARS => { NO_ID => 1 }, + NOTES => q{ + Tags found in the Microsoft "Xtra" atom of QuickTime videos. Tag ID's are + not shown because some are unruly GUID's. Currently most of these tags are + not writable because the Microsoft documentation is poor and samples were + not available, but more tags may be made writable in the future if samples + are provided. Note that writable tags in this table are are flagged to + "Avoid", which means that other more common tags will be written instead if + possible unless the Microsoft group is specified explicitly. + }, + Abstract => { }, + AcquisitionTime => { Groups => { 2 => 'Time' } }, + AcquisitionTimeDay => { Groups => { 2 => 'Time' } }, + AcquisitionTimeMonth => { Groups => { 2 => 'Time' } }, + AcquisitionTimeYear => { Groups => { 2 => 'Time' } }, + AcquisitionTimeYearMonth => { Groups => { 2 => 'Time' } }, + AcquisitionTimeYearMonthDay => { Groups => { 2 => 'Time' } }, + AlbumArtistSortOrder => { }, + AlbumID => { }, + AlbumIDAlbumArtist => { }, + AlbumTitleSortOrder => { }, + AlternateSourceURL => { }, + AudioBitrate => { }, + AudioFormat => { }, + Author => { Groups => { 2 => 'Author' } }, + AuthorSortOrder => { }, + AverageLevel => { }, + Bitrate => { }, + BuyNow => { }, + BuyTickets => { }, + CallLetters => { }, + CameraManufacturer => { }, + CameraModel => { }, + CDTrackEnabled => { }, + Channels => { }, + chapterNum => { }, + Comment => { }, + ContentDistributorDuration => { }, + Copyright => { Groups => { 2 => 'Author' } }, + Count => { }, + CurrentBitrate => { }, + Description => { }, + DisplayArtist => { }, + DLNAServerUDN => { }, + DLNASourceURI => { }, + DRMKeyID => { }, + DRMIndividualizedVersion => { }, + DTCPIPHost => { }, + DTCPIPPort => { }, + Duration => { }, + DVDID => { }, + Event => { }, + FileSize => { }, + FileType => { }, + FourCC => { }, + FormatTag => { }, + FrameRate => { }, + Frequency => { }, + IsNetworkFeed => { }, + Is_Protected => 'IsProtected', + IsVBR => { }, + LeadPerformer => { }, + LibraryID => { }, + LibraryName => { }, + Location => { }, + MediaContentTypes => { }, + MediaType => { }, + ModifiedBy => { }, + MoreInfo => { }, + PartOfSet => { }, + PeakValue => { }, + PixelAspectRatioX => { }, + PixelAspectRatioY => { }, + PlaylistIndex => { }, + Provider => { }, + ProviderLogoURL => { }, + ProviderURL => { }, + RadioBand => { }, + RadioFormat => { }, + RatingOrg => { }, + RecordingTime => { Groups => { 2 => 'Time' } }, + RecordingTimeDay => { Groups => { 2 => 'Time' } }, + RecordingTimeMonth => { Groups => { 2 => 'Time' } }, + RecordingTimeYear => { Groups => { 2 => 'Time' } }, + RecordingTimeYearMonth => { Groups => { 2 => 'Time' } }, + RecordingTimeYearMonthDay => { Groups => { 2 => 'Time' } }, + ReleaseDate => { Groups => { 2 => 'Time' } }, + ReleaseDateDay => { Groups => { 2 => 'Time' } }, + ReleaseDateMonth => { Groups => { 2 => 'Time' } }, + ReleaseDateYear => { Groups => { 2 => 'Time' } }, + ReleaseDateYearMonth => { Groups => { 2 => 'Time' } }, + ReleaseDateYearMonthDay => { Groups => { 2 => 'Time' } }, + RequestState => { }, + ShadowFilePath => { }, + SourceURL => { }, + Subject => { }, + SyncState => { }, + Sync01 => { }, + Sync02 => { }, + Sync03 => { }, + Sync04 => { }, + Sync05 => { }, + Sync06 => { }, + Sync07 => { }, + Sync08 => { }, + Sync09 => { }, + Sync10 => { }, + Sync11 => { }, + Sync12 => { }, + Sync13 => { }, + Sync14 => { }, + Sync15 => { }, + Sync16 => { }, + SyncOnly => { }, + Temporary => { }, + Title => { }, + titleNum => { }, + TitleSortOrder => { }, + TotalDuration => { }, + TrackingID => { }, + UserCustom1 => { }, + UserCustom2 => { }, + UserEffectiveRating => { }, + UserLastPlayedTime => { }, + UserPlayCount => { }, + UserPlaycountAfternoon => { }, + UserPlaycountEvening => { }, + UserPlaycountMorning => { }, + UserPlaycountNight => { }, + UserPlaycountWeekday => { }, + UserPlaycountWeekend => { }, + UserRating => { }, + UserServiceRating => { }, + VideoBitrate => { }, + VideoFormat => { }, + 'WM/AlbumArtist' => { Name => 'AlbumArtist', Writable => 'Unicode' }, # (NC) + 'WM/AlbumCoverURL' => { Name => 'AlbumCoverURL', Writable => 'Unicode' }, # (NC) + 'WM/AlbumTitle' => { Name => 'AlbumTitle', Writable => 'Unicode' }, # (NC) + 'WM/BeatsPerMinute' => 'BeatsPerMinute', + 'WM/Category' => { Name => 'Category', Writable => 'Unicode', List => 1 }, + 'WM/Composer' => { Name => 'Composer', Writable => 'Unicode' }, # (NC) + 'WM/Conductor' => { Name => 'Conductor', Writable => 'Unicode', List => 1 }, + 'WM/ContentDistributor' => { Name => 'ContentDistributor', Writable => 'Unicode' }, + 'WM/ContentDistributorType' => 'ContentDistributorType', + 'WM/ContentGroupDescription'=> 'ContentGroupDescription', + 'WM/Director' => { Name => 'Director', Writable => 'Unicode', List => 1 }, + 'WM/EncodingTime' => { + Name => 'EncodingTime', + Groups => { 2 => 'Time' }, + Shift => 'Time', + Writable => 'date', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + 'WM/Genre' => 'Genre', + 'WM/GenreID' => 'GenreID', + 'WM/InitialKey' => { Name => 'InitialKey', Writable => 'Unicode' }, + 'WM/Language' => 'Language', + 'WM/Lyrics' => 'Lyrics', + 'WM/MCDI' => 'MCDI', + 'WM/MediaClassPrimaryID' => { + Name => 'MediaClassPrimaryID', + Writable => 'GUID', + PrintConv => { #http://msdn.microsoft.com/en-us/library/windows/desktop/dd757960(v=vs.85).aspx + 'D1607DBC-E323-4BE2-86A1-48A42A28441E' => 'Music', + 'DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B' => 'Video', + '01CD0F29-DA4E-4157-897B-6275D50C4F11' => 'Audio (not music)', + 'FCF24A76-9A57-4036-990D-E35DD8B244E1' => 'Other (not audio or video)', + }, + }, + 'WM/MediaClassSecondaryID' => { + Name => 'MediaClassSecondaryID', + Writable => 'GUID', + PrintConv => { #http://msdn.microsoft.com/en-us/library/windows/desktop/dd757960(v=vs.85).aspx + 'E0236BEB-C281-4EDE-A36D-7AF76A3D45B5' => 'Audio Book', + '3A172A13-2BD9-4831-835B-114F6A95943F' => 'Spoken Word', + '6677DB9B-E5A0-4063-A1AD-ACEB52840CF1' => 'Audio News', + '1B824A67-3F80-4E3E-9CDE-F7361B0F5F1B' => 'Talk Show', + '1FE2E091-4E1E-40CE-B22D-348C732E0B10' => 'Video News', + 'D6DE1D88-C77C-4593-BFBC-9C61E8C373E3' => 'Web-based Video', + '00033368-5009-4AC3-A820-5D2D09A4E7C1' => 'Sound Clip from Game', + 'F24FF731-96FC-4D0F-A2F5-5A3483682B1A' => 'Song from Game', + 'E3E689E2-BA8C-4330-96DF-A0EEEFFA6876' => 'Music Video', + 'B76628F4-300D-443D-9CB5-01C285109DAF' => 'Home Movie', + 'A9B87FC9-BD47-4BF0-AC4F-655B89F7D868' => 'Feature Film', + 'BA7F258A-62F7-47A9-B21F-4651C42A000E' => 'TV Show', + '44051B5B-B103-4B5C-92AB-93060A9463F0' => 'Corporate Video', + '0B710218-8C0C-475E-AF73-4C41C0C8F8CE' => 'Home Video from Pictures', + '00000000-0000-0000-0000-000000000000' => 'Unknown Content', #PH + }, + }, + 'WM/MediaOriginalBroadcastDateTime' => { + Name => 'MediaOriginalBroadcastDateTime', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 'WM/MediaOriginalChannel' => 'MediaOriginalChannel', + 'WM/MediaStationName' => 'MediaStationName', + 'WM/Mood' => { Name => 'Mood', Writable => 'Unicode' }, + 'WM/OriginalAlbumTitle' => { Name => 'OriginalAlbumTitle', Writable => 'Unicode' }, # (NC) + 'WM/OriginalArtist' => { Name => 'OriginalArtist', Writable => 'Unicode' }, # (NC) + 'WM/OriginalLyricist' => { Name => 'OriginalLyricist', Writable => 'Unicode' }, # (NC) + 'WM/ParentalRating' => { Name => 'ParentalRating', Writable => 'Unicode' }, + 'WM/PartOfSet' => 'PartOfSet', + 'WM/Period' => { Name => 'Period', Writable => 'Unicode' }, + 'WM/Producer' => { Name => 'Producer', Writable => 'Unicode', List => 1 }, + 'WM/ProtectionType' => 'ProtectionType', + 'WM/Provider' => { Name => 'Provider', Writable => 'Unicode' }, # (NC) + 'WM/ProviderRating' => 'ProviderRating', + 'WM/ProviderStyle' => 'ProviderStyle', + 'WM/Publisher' => { Name => 'Publisher', Writable => 'Unicode' }, # (multiple entries separated by semicolon) + 'WM/SharedUserRating' => { Name => 'SharedUserRating', Writable => 'int64u' }, + 'WM/SubscriptionContentID' => 'SubscriptionContentID', + 'WM/SubTitle' => { Name => 'Subtitle', Writable => 'Unicode' }, + 'WM/SubTitleDescription' => 'SubtitleDescription', + 'WM/TrackNumber' => 'TrackNumber', + 'WM/UniqueFileIdentifier' => 'UniqueFileIdentifier', + 'WM/VideoFrameRate' => 'VideoFrameRate', + 'WM/VideoHeight' => 'VideoHeight', + 'WM/VideoWidth' => 'VideoWidth', + 'WM/WMCollectionGroupID' => 'WMCollectionGroupID', + 'WM/WMCollectionID' => 'WMCollectionID', + 'WM/WMContentID' => 'WMContentID', + 'WM/WMShadowFileSourceDRMType' => 'WMShadowFileSourceDRMType', + 'WM/WMShadowFileSourceFileType' => 'WMShadowFileSourceFileType', + 'WM/Writer' => { Name => 'Writer', Groups => { 2 => 'Author' }, Writable => 'Unicode' }, # (NC) + 'WM/Year' => { Name => 'Year', Groups => { 2 => 'Time' } }, + 'WM/PromotionURL' => { Name => 'PromotionURL',Writable => 'Unicode' }, + 'WM/AuthorURL' => { Name => 'AuthorURL', Groups => { 2 => 'Author' }, Writable => 'Unicode' }, + 'WM/EncodedBy', => { Name => 'EncodedBy', Writable => 'Unicode' }, + + # I can't find documentation for the following tags in videos, + # but the tag ID's correspond to Microsoft property GUID+ID's + # References: + # http://msdn.microsoft.com/en-us/library/cc251929%28v=prot.10%29.aspx + # http://multi-rename-script.googlecode.com/svn-history/r4/trunk/plugins/ShellDetails/ShellDetails.ini + # I have observed only 1 so far: + '{2CBAA8F5-D81F-47CA-B17A-F8D822300131} 100' => { + Name => 'DateAcquired', # (seems to be when videos are downloaded from the camera) + Groups => { 2 => 'Time' }, + Shift => 'Time', + Writable => 'vt_filetime', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef)', + }, + # the following have not yet been observed... + '{B725F130-47EF-101A-A5F1-02608C9EEBAC} 10' => 'Name', + '{B725F130-47EF-101A-A5F1-02608C9EEBAC} 12' => 'Size', + '{B725F130-47EF-101A-A5F1-02608C9EEBAC} 4' => 'Type', + '{B725F130-47EF-101A-A5F1-02608C9EEBAC} 14' => { + Name => 'DateModified', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{B725F130-47EF-101A-A5F1-02608C9EEBAC} 15' => { + Name => 'DateCreated', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{B725F130-47EF-101A-A5F1-02608C9EEBAC} 16' => { + Name => 'DateAccessed', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{B725F130-47EF-101A-A5F1-02608C9EEBAC} 13' => 'Attributes', + '{D8C3986F-813B-449C-845D-87B95D674ADE} 2' => 'Status', + '{9B174B34-40FF-11D2-A27E-00C04FC30871} 4' => 'Owner', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 4' => { + Name => 'Author', + Groups => { 2 => 'Author' }, + }, + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 2' => 'Title', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 3' => 'Subject', + '{D5CDD502-2E9C-101B-9397-08002B2CF9AE} 2' => 'Category', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 14' => 'Pages', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 6' => 'Comments', + '{64440492-4C8B-11D1-8B70-080036B11A03} 11' => { + Name => 'Copyright', + Groups => { 2 => 'Author' }, + }, + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 2' => 'Artist', + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 4' => 'AlbumTitle', + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 5' => { + Name => 'Year', + Groups => { 2 => 'Time' }, + }, + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 7' => 'TrackNumber', + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 11' => 'Genre', + '{64440490-4C8B-11D1-8B70-080036B11A03} 3' => 'Duration', + '{64440490-4C8B-11D1-8B70-080036B11A03} 4' => 'Bitrate', + '{AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED} 2' => 'Protected', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 272' => 'CameraModel', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 36867' => { + Name => 'DatePictureTaken', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{6444048F-4C8B-11D1-8B70-080036B11A03} 13' => 'Dimensions', + '{6444048F-4C8B-11D1-8B70-080036B11A03} 3' => 'Untitled0', + '{6444048F-4C8B-11D1-8B70-080036B11A03} 4' => 'Untitled1', + '{6D748DE2-8D38-4CC3-AC60-F009B057C557} 2' => 'EpisodeName', + '{6D748DE2-8D38-4CC3-AC60-F009B057C557} 3' => 'ProgramDescription', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 12' => 'Untitled2', + '{64440490-4C8B-11D1-8B70-080036B11A03} 6' => 'AudioSampleSize', + '{64440490-4C8B-11D1-8B70-080036B11A03} 5' => 'AudioSampleRate', + '{64440490-4C8B-11D1-8B70-080036B11A03} 7' => 'Channels', + '{D5CDD502-2E9C-101B-9397-08002B2CF9AE} 15' => 'Company', + '{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 3' => 'Description', + '{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 4' => 'FileVersion', + '{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 7' => 'ProductName', + '{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 8' => 'ProductVersion', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 5' => 'Keywords', + '{28636AA6-953D-11D2-B5D6-00C04FD918D0} 11' => 'Type', + '{6D24888F-4718-4BDA-AFED-EA0FB4386CD8} 100' => 'OfflineStatus', + '{A94688B6-7D9F-4570-A648-E3DFC0AB2B3F} 100' => 'OfflineAvailability', + '{28636AA6-953D-11D2-B5D6-00C04FD918D0} 9' => 'PerceivedType', + '{1E3EE840-BC2B-476C-8237-2ACD1A839B22} 3' => 'Kinds', + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 36' => 'Conductors', + '{64440492-4C8B-11D1-8B70-080036B11A03} 9' => 'Rating', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 271' => 'CameraMaker', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 18' => 'ProgramName', + '{293CA35A-09AA-4DD2-B180-1FE245728A52} 100' => 'Duration', + '{BFEE9149-E3E2-49A7-A862-C05988145CEC} 100' => 'IsOnline', + '{315B9C8D-80A9-4EF9-AE16-8E746DA51D70} 100' => 'IsRecurring', + '{F6272D18-CECC-40B1-B26A-3911717AA7BD} 100' => 'Location', + '{D55BAE5A-3892-417A-A649-C6AC5AAAEAB3} 100' => 'OptionalAttendeeAddresses', + '{09429607-582D-437F-84C3-DE93A2B24C3C} 100' => 'OptionalAttendees', + '{744C8242-4DF5-456C-AB9E-014EFB9021E3} 100' => 'OrganizerAddress', + '{AAA660F9-9865-458E-B484-01BC7FE3973E} 100' => 'OrganizerName', + '{72FC5BA4-24F9-4011-9F3F-ADD27AFAD818} 100' => 'ReminderTime', + '{0BA7D6C3-568D-4159-AB91-781A91FB71E5} 100' => 'RequiredAttendeeAddresses', + '{B33AF30B-F552-4584-936C-CB93E5CDA29F} 100' => 'RequiredAttendees', + '{00F58A38-C54B-4C40-8696-97235980EAE1} 100' => 'Resources', + '{5BF396D4-5EB2-466F-BDE9-2FB3F2361D6E} 100' => 'Free-busyStatus', + '{9B174B35-40FF-11D2-A27E-00C04FC30871} 3' => 'TotalSize', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 9' => 'AccountName', + '{28636AA6-953D-11D2-B5D6-00C04FD918D0} 5' => 'Computer', + '{9AD5BADB-CEA7-4470-A03D-B84E51B9949E} 100' => 'Anniversary', + '{CD102C9C-5540-4A88-A6F6-64E4981C8CD1} 100' => 'AssistantsName', + '{9A93244D-A7AD-4FF8-9B99-45EE4CC09AF6} 100' => 'AssistantsPhone', + '{176DC63C-2688-4E89-8143-A347800F25E9} 47' => 'Birthday', + '{730FB6DD-CF7C-426B-A03F-BD166CC9EE24} 100' => 'BusinessAddress', + '{402B5934-EC5A-48C3-93E6-85E86A2D934E} 100' => 'BusinessCity', + '{B0B87314-FCF6-4FEB-8DFF-A50DA6AF561C} 100' => 'BusinessCountry-Region', + '{BC4E71CE-17F9-48D5-BEE9-021DF0EA5409} 100' => 'BusinessPOBox', + '{E1D4A09E-D758-4CD1-B6EC-34A8B5A73F80} 100' => 'BusinessPostalCode', + '{446F787F-10C4-41CB-A6C4-4D0343551597} 100' => 'BusinessStateOrProvince', + '{DDD1460F-C0BF-4553-8CE4-10433C908FB0} 100' => 'BusinessStreet', + '{91EFF6F3-2E27-42CA-933E-7C999FBE310B} 100' => 'BusinessFax', + '{56310920-2491-4919-99CE-EADB06FAFDB2} 100' => 'BusinessHomePage', + '{6A15E5A0-0A1E-4CD7-BB8C-D2F1B0C929BC} 100' => 'BusinessPhone', + '{BF53D1C3-49E0-4F7F-8567-5A821D8AC542} 100' => 'CallbackNumber', + '{8FDC6DEA-B929-412B-BA90-397A257465FE} 100' => 'CarPhone', + '{D4729704-8EF1-43EF-9024-2BD381187FD5} 100' => 'Children', + '{8589E481-6040-473D-B171-7FA89C2708ED} 100' => 'CompanyMainPhone', + '{FC9F7306-FF8F-4D49-9FB6-3FFE5C0951EC} 100' => 'Department', + '{F8FA7FA3-D12B-4785-8A4E-691A94F7A3E7} 100' => 'E-mailAddress', + '{38965063-EDC8-4268-8491-B7723172CF29} 100' => 'E-mail2', + '{644D37B4-E1B3-4BAD-B099-7E7C04966ACA} 100' => 'E-mail3', + '{84D8F337-981D-44B3-9615-C7596DBA17E3} 100' => 'E-mailList', + '{CC6F4F24-6083-4BD4-8754-674D0DE87AB8} 100' => 'E-mailDisplayName', + '{F1A24AA7-9CA7-40F6-89EC-97DEF9FFE8DB} 100' => 'FileAs', + '{14977844-6B49-4AAD-A714-A4513BF60460} 100' => 'FirstName', + '{635E9051-50A5-4BA2-B9DB-4ED056C77296} 100' => 'FullName', + '{3C8CEE58-D4F0-4CF9-B756-4E5D24447BCD} 100' => 'Gender', + '{176DC63C-2688-4E89-8143-A347800F25E9} 70' => 'GivenName', + '{5DC2253F-5E11-4ADF-9CFE-910DD01E3E70} 100' => 'Hobbies', + '{98F98354-617A-46B8-8560-5B1B64BF1F89} 100' => 'HomeAddress', + '{176DC63C-2688-4E89-8143-A347800F25E9} 65' => 'HomeCity', + '{08A65AA1-F4C9-43DD-9DDF-A33D8E7EAD85} 100' => 'HomeCountry-Region', + '{7B9F6399-0A3F-4B12-89BD-4ADC51C918AF} 100' => 'HomePOBox', + '{8AFCC170-8A46-4B53-9EEE-90BAE7151E62} 100' => 'HomePostalCode', + '{C89A23D0-7D6D-4EB8-87D4-776A82D493E5} 100' => 'HomeStateOrProvince', + '{0ADEF160-DB3F-4308-9A21-06237B16FA2A} 100' => 'HomeStreet', + '{660E04D6-81AB-4977-A09F-82313113AB26} 100' => 'HomeFax', + '{176DC63C-2688-4E89-8143-A347800F25E9} 20' => 'HomePhone', + '{D68DBD8A-3374-4B81-9972-3EC30682DB3D} 100' => 'IMAddresses', + '{F3D8F40D-50CB-44A2-9718-40CB9119495D} 100' => 'Initials', + '{176DC63C-2688-4E89-8143-A347800F25E9} 6' => 'JobTitle', + '{97B0AD89-DF49-49CC-834E-660974FD755B} 100' => 'Label', + '{8F367200-C270-457C-B1D4-E07C5BCD90C7} 100' => 'LastName', + '{C0AC206A-827E-4650-95AE-77E2BB74FCC9} 100' => 'MailingAddress', + '{176DC63C-2688-4E89-8143-A347800F25E9} 71' => 'MiddleName', + '{176DC63C-2688-4E89-8143-A347800F25E9} 35' => 'CellPhone', + '{176DC63C-2688-4E89-8143-A347800F25E9} 74' => 'Nickname', + '{176DC63C-2688-4E89-8143-A347800F25E9} 7' => 'OfficeLocation', + '{508161FA-313B-43D5-83A1-C1ACCF68622C} 100' => 'OtherAddress', + '{6E682923-7F7B-4F0C-A337-CFCA296687BF} 100' => 'OtherCity', + '{8F167568-0AAE-4322-8ED9-6055B7B0E398} 100' => 'OtherCountry-Region', + '{8B26EA41-058F-43F6-AECC-4035681CE977} 100' => 'OtherPOBox', + '{95C656C1-2ABF-4148-9ED3-9EC602E3B7CD} 100' => 'OtherPostalCode', + '{71B377D6-E570-425F-A170-809FAE73E54E} 100' => 'OtherStateOrProvince', + '{FF962609-B7D6-4999-862D-95180D529AEA} 100' => 'OtherStreet', + '{D6304E01-F8F5-4F45-8B15-D024A6296789} 100' => 'Pager', + '{176DC63C-2688-4E89-8143-A347800F25E9} 69' => 'PersonalTitle', + '{C8EA94F0-A9E3-4969-A94B-9C62A95324E0} 100' => 'City', + '{E53D799D-0F3F-466E-B2FF-74634A3CB7A4} 100' => 'Country-Region', + '{DE5EF3C7-46E1-484E-9999-62C5308394C1} 100' => 'POBox', + '{18BBD425-ECFD-46EF-B612-7B4A6034EDA0} 100' => 'PostalCode', + '{F1176DFE-7138-4640-8B4C-AE375DC70A6D} 100' => 'StateOrProvince', + '{63C25B20-96BE-488F-8788-C09C407AD812} 100' => 'Street', + '{176DC63C-2688-4E89-8143-A347800F25E9} 48' => 'PrimaryE-mail', + '{176DC63C-2688-4E89-8143-A347800F25E9} 25' => 'PrimaryPhone', + '{7268AF55-1CE4-4F6E-A41F-B6E4EF10E4A9} 100' => 'Profession', + '{9D2408B6-3167-422B-82B0-F583B7A7CFE3} 100' => 'Spouse', + '{176DC63C-2688-4E89-8143-A347800F25E9} 73' => 'Suffix', + '{AAF16BAC-2B55-45E6-9F6D-415EB94910DF} 100' => 'TTY-TTDPhone', + '{C554493C-C1F7-40C1-A76C-EF8C0614003E} 100' => 'Telex', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 18' => 'Webpage', + '{D5CDD502-2E9C-101B-9397-08002B2CF9AE} 27' => 'Status', + '{D5CDD502-2E9C-101B-9397-08002B2CF9AE} 26' => 'ContentType', + '{43F8D7B7-A444-4F87-9383-52271C9B915C} 100' => { + Name => 'DateArchived', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{72FAB781-ACDA-43E5-B155-B2434F85E678} 100' => { + Name => 'DateCompleted', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 18258' => { + Name => 'DateImported', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{276D7BB0-5B34-4FB0-AA4B-158ED12A1809} 100' => 'ClientID', + '{F334115E-DA1B-4509-9B3D-119504DC7ABB} 100' => 'Contributors', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 11' => 'LastPrinted', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 13' => { + Name => 'DateLastSaved', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{1E005EE6-BF27-428B-B01C-79676ACD2870} 100' => 'Division', + '{E08805C8-E395-40DF-80D2-54F0D6C43154} 100' => 'DocumentID', + '{D5CDD502-2E9C-101B-9397-08002B2CF9AE} 7' => 'Slides', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 10' => 'TotalEditingTime', + '{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 15' => 'WordCount', + '{3F8472B5-E0AF-4DB2-8071-C53FE76AE7CE} 100' => 'DueDate', + '{C75FAA05-96FD-49E7-9CB4-9F601082D553} 100' => 'EndDate', + '{28636AA6-953D-11D2-B5D6-00C04FD918D0} 12' => 'FileCount', + '{41CF5AE0-F75A-4806-BD87-59C7D9248EB9} 100' => 'WindowsFileName', + '{67DF94DE-0CA7-4D6F-B792-053A3E4F03CF} 100' => 'FlagColor', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 12' => 'FlagStatus', + '{9B174B35-40FF-11D2-A27E-00C04FC30871} 2' => 'SpaceFree', + '{6444048F-4C8B-11D1-8B70-080036B11A03} 7' => 'BitDepth', + '{6444048F-4C8B-11D1-8B70-080036B11A03} 5' => 'HorizontalResolution', + '{6444048F-4C8B-11D1-8B70-080036B11A03} 6' => 'VerticalResolution', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 11' => 'Importance', + '{F23F425C-71A1-4FA8-922F-678EA4A60408} 100' => 'IsAttachment', + '{5CDA5FC8-33EE-4FF3-9094-AE7BD8868C4D} 100' => 'IsDeleted', + '{5DA84765-E3FF-4278-86B0-A27967FBDD03} 100' => 'HasFlag', + '{A6F360D2-55F9-48DE-B909-620E090A647C} 100' => 'IsCompleted', + '{346C8BD1-2E6A-4C45-89A4-61B78E8E700F} 100' => 'Incomplete', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 10' => 'ReadStatus', + '{EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902} 100' => 'Shared', + '{D0A04F0A-462A-48A4-BB2F-3706E88DBD7D} 100' => { + Name => 'Creator', + Groups => { 2 => 'Author' }, + }, + '{F7DB74B4-4287-4103-AFBA-F1B13DCD75CF} 100' => { + Name => 'Date', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{B725F130-47EF-101A-A5F1-02608C9EEBAC} 2' => 'FolderName', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 6' => 'FolderPath', + '{DABD30ED-0043-4789-A7F8-D013A4736622} 100' => 'Folder', + '{D4D0AA16-9948-41A4-AA85-D97FF9646993} 100' => 'Participants', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 7' => 'Path', + '{DEA7C82C-1D89-4A66-9427-A4E3DEBABCB1} 100' => 'ContactNames', + '{95BEB1FC-326D-4644-B396-CD3ED90E6DDF} 100' => 'EntryType', + '{D5CDD502-2E9C-101B-9397-08002B2CF9AE} 28' => 'Language', + '{5CBF2787-48CF-4208-B90E-EE5E5D420294} 23' => { + Name => 'DateVisited', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{5CBF2787-48CF-4208-B90E-EE5E5D420294} 21' => 'Description', + '{B9B4B3FC-2B51-4A42-B5D8-324146AFCF25} 3' => 'LinkStatus', + '{B9B4B3FC-2B51-4A42-B5D8-324146AFCF25} 2' => 'LinkTarget', + '{5CBF2787-48CF-4208-B90E-EE5E5D420294} 2' => 'URL', + '{2E4B640D-5019-46D8-8881-55414CC5CAA0} 100' => 'MediaCreated', + '{DE41CC29-6971-4290-B472-F59F2E2F31E2} 100' => { + Name => 'DateReleased', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{64440492-4C8B-11D1-8B70-080036B11A03} 36' => 'EncodedBy', + '{64440492-4C8B-11D1-8B70-080036B11A03} 22' => 'Producers', + '{64440492-4C8B-11D1-8B70-080036B11A03} 30' => 'Publisher', + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 38' => 'Subtitle', + '{64440492-4C8B-11D1-8B70-080036B11A03} 34' => 'UserWebURL', + '{64440492-4C8B-11D1-8B70-080036B11A03} 23' => 'Writers', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 21' => 'Attachments', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 2' => 'BccAddresses', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 3' => 'BccNames', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 4' => 'CcAddresses', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 5' => 'CcNames', + '{DC8F80BD-AF1E-4289-85B6-3DFC1B493992} 100' => 'ConversationID', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 20' => { + Name => 'DateReceived', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 19' => { + Name => 'DateSent', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 13' => 'FromAddresses', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 14' => 'FromNames', + '{9C1FCF74-2D97-41BA-B4AE-CB2E3661A6E4} 8' => 'HasAttachments', + '{0BE1C8E7-1981-4676-AE14-FDD78F05A6E7} 100' => 'SenderAddress', + '{0DA41CFA-D224-4A18-AE2F-596158DB4B3A} 100' => 'SenderName', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 15' => 'Store', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 16' => 'ToAddresses', + '{BCCC8A3C-8CEF-42E5-9B1C-C69079398BC7} 100' => 'ToDoTitle', + '{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 17' => 'ToNames', + '{FDF84370-031A-4ADD-9E91-0D775F1C6605} 100' => 'Mileage', + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 13' => 'AlbumArtist', + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 35' => 'Beats-per-minute', + '{64440492-4C8B-11D1-8B70-080036B11A03} 19' => 'Composers', + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 34' => 'InitialKey', + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 39' => 'Mood', + '{56A3372E-CE9C-11D2-9F0E-006097C686F6} 37' => 'PartOfSet', + '{64440492-4C8B-11D1-8B70-080036B11A03} 31' => 'Period', + '{4776CAFA-BCE4-4CB1-A23E-265E76D8EB11} 100' => 'Color', + '{64440492-4C8B-11D1-8B70-080036B11A03} 21' => 'ParentalRating', + '{10984E0A-F9F2-4321-B7EF-BAF195AF4319} 100' => 'ParentalRatingReason', + '{9B174B35-40FF-11D2-A27E-00C04FC30871} 5' => 'SpaceUsed', + '{D35F743A-EB2E-47F2-A286-844132CB1427} 100' => 'ExifVersion', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 18248' => 'Event', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 37380' => 'ExposureBias', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 34850' => 'ExposureProgram', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 33434' => 'ExposureTime', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 33437' => 'F-stop', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 37385' => 'FlashMode', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 37386' => 'FocalLength', + '{A0E74609-B84D-4F49-B860-462BD9971F98} 100' => 'FocalLength35mm', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 34855' => 'ISOSpeed', + '{E6DDCAF7-29C5-4F0A-9A68-D19412EC7090} 100' => 'LensMaker', + '{E1277516-2B5F-4869-89B1-2E585BD38B7A} 100' => 'LensModel', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 37384' => 'LightSource', + '{08F6D7C2-E3F2-44FC-AF1E-5AA5C81A2D3E} 100' => 'MaxAperture', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 37383' => 'MeteringMode', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 274' => 'Orientation', + '{6D217F6D-3F6A-4825-B470-5F03CA2FBE9B} 100' => 'ProgramMode', + '{49237325-A95A-4F67-B211-816B2D45D2E0} 100' => 'Saturation', + '{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 37382' => 'SubjectDistance', + '{EE3D3D8A-5381-4CFA-B13B-AAF66B5F4EC9} 100' => 'WhiteBalance', + '{9C1FCF74-2D97-41BA-B4AE-CB2E3661A6E4} 5' => 'Priority', + '{39A7F922-477C-48DE-8BC8-B28441E342E3} 100' => 'Project', + '{6D748DE2-8D38-4CC3-AC60-F009B057C557} 7' => 'ChannelNumber', + '{6D748DE2-8D38-4CC3-AC60-F009B057C557} 12' => 'ClosedCaptioning', + '{6D748DE2-8D38-4CC3-AC60-F009B057C557} 13' => 'Rerun', + '{6D748DE2-8D38-4CC3-AC60-F009B057C557} 14' => 'SAP', + '{4684FE97-8765-4842-9C13-F006447B178C} 100' => 'BroadcastDate', + '{A5477F61-7A82-4ECA-9DDE-98B69B2479B3} 100' => 'RecordingTime', + '{6D748DE2-8D38-4CC3-AC60-F009B057C557} 5' => 'StationCallSign', + '{1B5439E7-EBA1-4AF8-BDD7-7AF1D4549493} 100' => 'StationName', + '{560C36C0-503A-11CF-BAA1-00004C752A9A} 2' => 'AutoSummary', + '{560C36C0-503A-11CF-BAA1-00004C752A9A} 3' => 'Summary', + '{49691C90-7E17-101A-A91C-08002B2ECDA9} 3' => 'SearchRanking', + '{F8D3F6AC-4874-42CB-BE59-AB454B30716A} 100' => 'Sensitivity', + '{EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902} 200' => 'SharedWith', + '{668CDFA5-7A1B-4323-AE4B-E527393A1D81} 100' => 'Source', + '{48FD6EC8-8A12-4CDF-A03E-4EC5A511EDDE} 100' => 'StartDate', + '{D37D52C6-261C-4303-82B3-08B926AC6F12} 100' => 'BillingInformation', + '{084D8A0A-E6D5-40DE-BF1F-C8820E7C877C} 100' => 'Complete', + '{08C7CC5F-60F2-4494-AD75-55E3E0B5ADD0} 100' => 'TaskOwner', + '{28636AA6-953D-11D2-B5D6-00C04FD918D0} 14' => 'TotalFileSize', + '{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 9' => 'LegalTrademarks', + '{64440491-4C8B-11D1-8B70-080036B11A03} 10' => 'VideoCompression', + '{64440492-4C8B-11D1-8B70-080036B11A03} 20' => 'Directors', + '{64440491-4C8B-11D1-8B70-080036B11A03} 8' => 'DataRate', + '{64440491-4C8B-11D1-8B70-080036B11A03} 4' => 'FrameHeight', + '{64440491-4C8B-11D1-8B70-080036B11A03} 6' => 'FrameRate', + '{64440491-4C8B-11D1-8B70-080036B11A03} 3' => 'FrameWidth', + '{64440491-4C8B-11D1-8B70-080036B11A03} 43' => 'TotalBitrate', +); + +#------------------------------------------------------------------------------ +# check new value for Xtra tag +# Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref +# Returns: error string, or undef on success +sub CheckXtra($$$) +{ + my ($et, $tagInfo, $valPt) = @_; + my $format = $$tagInfo{Writable}; + return 'Unknown format' unless $format; + if ($format =~ /^int/) { + return 'Not an integer' unless Image::ExifTool::IsInt($$valPt); + } elsif ($format ne 'Unicode') { + my @vals = ($$valPt); + return 'Invalid format' unless WriteXtraValue($et, $tagInfo, \@vals); + } + return undef; +} + +#------------------------------------------------------------------------------ +# Decode value(s) in Microsoft Xtra tag +# Inputs: 0) ExifTool object ref, 1) value data +# Returns: Scalar context: decoded value, List context: 0) decoded value, 1) format string +sub ReadXtraValue($$) +{ + my ($et, $data) = @_; + my ($format, $i, @vals); + + return undef if length($data) < 10; + + # (version flags according to the reference, but looks more like a count - PH) + my $count = Get32u(\$data, 0); + # point to start of first value (after 4-byte count, 4-byte length and 2-byte type) + my $valPos = 10; + for ($i=0; ;) { + # (stored value includes size of $valLen and $valType, so subtract 6) + my $valLen = Get32u(\$data, $valPos - 6) - 6; + last if $valPos + $valLen > length($data); + my $valType = Get16u(\$data, $valPos - 2); + my $val = substr($data, $valPos, $valLen); + # Note: all dumb Microsoft values are little-endian inside a big-endian-format file + SetByteOrder('II'); + if ($valType == 8) { + $format = 'Unicode'; + $val = $et->Decode($val, 'UCS2'); + } elsif ($valType == 19 and $valLen == 8) { + $format = 'int64u'; + $val = Get64u(\$val, 0); + } elsif ($valType == 21 and $valLen == 8) { + $format = 'date'; + $val = Get64u(\$val, 0); + # convert time from 100 ns intervals since Jan 1, 1601 + $val = $val * 1e-7 - 11644473600 if $val; + # (the Nikon S100 uses UTC timezone, same as ASF - PH) + $val = Image::ExifTool::ConvertUnixTime($val, 1); + } elsif ($valType == 72 and $valLen == 16) { + $format = 'GUID'; + $val = uc unpack('H*',pack('NnnNN',unpack('VvvNN',$val))); + $val =~ s/(.{8})(.{4})(.{4})(.{4})/$1-$2-$3-$4-/; + } elsif ($valType == 65 and $valLen > 4) { #PH (empirical) + $format = 'variant'; + require Image::ExifTool::FlashPix; + my $vPos = 0; # (necessary because ReadFPXValue updates this) + # read entry as a VT_VARIANT (use FlashPix module for this) + $val = Image::ExifTool::FlashPix::ReadFPXValue($et, \$val, $vPos, + Image::ExifTool::FlashPix::VT_VARIANT(), $valLen, 1); + } else { + $format = "Unknown($valType)"; + } + SetByteOrder('MM'); # back to native QuickTime byte ordering + push @vals, $val; + last if ++$i >= $count; + $valPos += $valLen + 6; # step to next value + last if $valPos > length($data); + } + return wantarray ? (\@vals, $format) : \@vals; +} + +#------------------------------------------------------------------------------ +# Write a Microsoft Xtra value +# Inputs: 0) ExifTool object ref, 1) tagInfo ref, 2) reference to list of values +# Returns: new value binary data (or empty string) +sub WriteXtraValue($$$) +{ + my ($et, $tagInfo, $vals) = @_; + my $format = $$tagInfo{Writable}; + my $buff = ''; + my $count = 0; + my $val; + foreach $val (@$vals) { + SetByteOrder('II'); + my ($type, $dat); + if ($format eq 'Unicode') { + $dat = $et->Encode($val,'UCS2','II') . "\0\0"; # (must be null terminated) + $type = 8; + } elsif ($format eq 'int64u') { + if (Image::ExifTool::IsInt($val)) { + $dat = Set64u($val); + $type = 19; + } + } elsif ($format eq 'date') { + $dat = Image::ExifTool::GetUnixTime($val, 1); # (convert to UTC, NC) + if ($dat) { + # 100ns intervals since Jan 1, 1601 + $dat = Set64u(($dat + 11644473600) * 1e7); + $type = 21; + } + } elsif ($format eq 'vt_filetime') { # 'date' value inside a VT_VARIANT + $dat = Image::ExifTool::GetUnixTime($val); # (leave as local time, NC) + if ($dat) { + # 100ns intervals since Jan 1, 1601 + $dat = Set32u(64) . Set64u(($dat + 11644473600) * 1e7); + $type = 65; + } + } elsif ($format eq 'GUID') { + ($dat = $val) =~ tr/-//d; + if (length($dat) == 32) { + $dat = pack('VvvNN',unpack('NnnNN',pack('H*', $dat))); + $type = 72; + } + } else { + $et->WarnOnce("Error converting value for Microsoft:$$tagInfo{Name}"); + } + SetByteOrder('MM'); + if (defined $type) { + ++$count; + $buff .= Set32u(length($dat)+6) . Set16u($type) . $dat; + } + } + return $count ? Set32u($count) . $buff : ''; +} + +#------------------------------------------------------------------------------ +# Add new values to list +# Inputs: 0) ExifTool ref, 1) new value list ref, 2) nvHash ref +# Returns: true if something was added +sub AddNewValues($$$) +{ + my ($et, $vals, $nvHash) = @_; + my @newVals = $et->GetNewValue($nvHash) or return undef; + if ($$et{OPTIONS}{Verbose} > 1) { + $et->VPrint(1, " + Microsoft:$$nvHash{TagInfo}{Name} = $_\n") foreach @newVals; + } + push @$vals, @newVals; + return 1; +} + +#------------------------------------------------------------------------------ +# Write tags to a Microsoft Xtra MP4 atom +# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref +# Returns: Microsoft Xtra data block (may be empty if no Xtra data) or undef on error +sub WriteXtra($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + + my $delGroup = ($$et{DEL_GROUP} and $$et{DEL_GROUP}{Microsoft}); + my $newTags = $et->GetNewTagInfoHash($tagTablePtr); + + return undef unless $delGroup or %$newTags; # don't rewrite if nothing to do + + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = length $$dataPt; + my $newData = ''; + my $pos = 0; + my ($err, %done, $changed, $tag); + + if ($delGroup) { + $changed = 1 if $dataLen; + my $empty = ''; + $dataPt = $empty; + $dataLen = 0; + } + for (;;) { + last if $pos + 4 > $dataLen; + my $size = Get32u($dataPt, $pos); # (includes $size word) + ($size < 8 or $pos + $size > $dataLen) and $err=1, last; + my $tagLen = Get32u($dataPt, $pos + 4); + $tagLen + 18 > $size and $err=1, last; + $tag = substr($$dataPt, $pos + 8, $tagLen); + my @newVals; + while ($$newTags{$tag}) { + my $nvHash = $et->GetNewValueHash($$newTags{$tag}); + $$nvHash{CreateOnly} and delete($$newTags{$tag}), last; # don't edit this tag + my $valPos = $pos + 8 + $tagLen; + my $valLen = $size - 8 - $tagLen; + my $val = ReadXtraValue($et, substr($$dataPt, $valPos, $valLen)); + foreach $val (@$val) { + my $overwrite = $et->IsOverwriting($nvHash, $val); + $overwrite or push(@newVals, $val), next; + $et->VPrint(1, " - Microsoft:$$newTags{$tag}{Name} = $val\n"); + next if $done{$tag}; + $done{$tag} = 1; + AddNewValues($et, \@newVals, $nvHash); + } + # add to the end of the list if this was a List-type tag and we didn't delete anything + if (not $done{$tag} and $$newTags{$tag}{List}) { + AddNewValues($et, \@newVals, $nvHash) or last; + $done{$tag} = 1; + } + last; # (it was a cheap goto) + } + if ($done{$tag}) { + $changed = 1; + # write changed values + my $buff = WriteXtraValue($et, $$newTags{$tag}, \@newVals); + if (length $buff) { + $newData .= Set32u(8+length($tag)+length($buff)) . Set32u(length($tag)) . $tag . $buff; + } + } else { + # nothing changed; just copy over + $newData .= substr($$dataPt, $pos, $size); + } + $pos += $size; # step to next entry + } + if ($err) { + $et->Warn('Microsoft Xtra format error'); + return undef; + } + # add any new tags + foreach $tag (sort keys %$newTags) { + next if $done{$tag}; + my $nvHash = $et->GetNewValueHash($$newTags{$tag}); + next unless $$nvHash{IsCreating} and not $$nvHash{EditOnly}; + my @newVals; + AddNewValues($et, \@newVals, $nvHash) or next; + my $buff = WriteXtraValue($et, $$newTags{$tag}, \@newVals); + if (length $buff) { + $newData .= Set32u(8+length($tag)+length($buff)) . Set32u(length($tag)) . $tag . $buff; + $changed = 1; + } + } + if ($changed) { + ++$$et{CHANGED}; + } else { + undef $newData; + } + return $newData; +} + +#------------------------------------------------------------------------------ +# Extract information from Xtra MP4 atom +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# Reference: http://code.google.com/p/mp4v2/ [since removed from trunk] +sub ProcessXtra($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{Base} || 0; + my $dataLen = $$dirInfo{DataLen}; + my $pos = 0; + $et->VerboseDir('Xtra', 0, $dataLen); + for (;;) { + last if $pos + 4 > $dataLen; + my $size = Get32u($dataPt, $pos); # (includes $size word) + last if $size < 8 or $pos + $size > $dataLen; + my $tagLen = Get32u($dataPt, $pos + 4); + last if $tagLen + 18 > $size; + my $valLen = $size - 8 - $tagLen; + if ($tagLen > 0 and $valLen > 0) { + my $tag = substr($$dataPt, $pos + 8, $tagLen); + my $valPos = $pos + 8 + $tagLen; + my ($val, $format) = ReadXtraValue($et, substr($$dataPt, $valPos, $valLen)); + last unless defined $val; + $val = $$val[0] if @$val == 1; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + unless ($tagInfo) { + # generate tag information for unrecognized tags + my $name = $tag; + $name =~ s{^WM/}{}; + # $name =~ tr/-_A-Za-z0-9//dc; + if ($name =~ /^[-\w]+$/) { + $tagInfo = { Name => ucfirst($name) }; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + $et->VPrint(0, $$et{INDENT}, "[adding Microsoft:$tag]\n"); + } + } + my $count = ref $val ? scalar @$val : 1; + $et->HandleTag($tagTablePtr, $tag, $val, + TagInfo => $tagInfo, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $valPos, + Size => $valLen, + Format => $format, + Extra => " count=$count", + ); + } + $pos += $size; # step to next entry + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Microsoft - Definitions for custom Microsoft tags + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +Microsoft-specific EXIF and XMP tags, and routines to read/write Microsoft +Xtra tags in videos. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://research.microsoft.com/en-us/um/redmond/groups/ivm/hdview/hdmetadataspec.htm> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Microsoft Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Minolta.pm b/ExifTool/lib/Image/ExifTool/Minolta.pm new file mode 100644 index 0000000..d449f21 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Minolta.pm @@ -0,0 +1,2958 @@ +#------------------------------------------------------------------------------ +# File: Minolta.pm +# +# Description: Minolta EXIF maker notes tags +# +# Revisions: 04/06/2004 - P. Harvey Created +# 09/09/2005 - P. Harvey Added ability to write MRW files +# +# References: 1) http://www.dalibor.cz/minolta/makernote.htm +# 2) Jay Al-Saadi private communication (testing with A2) +# 3) Shingo Noguchi, PhotoXP (http://www.daifukuya.com/photoxp/) +# 5) http://www.cybercom.net/~dcoffin/dcraw/ +# 6) Pedro Corte-Real private communication +# 7) ExifTool forum post by bronek (http://www.cpanforum.com/posts/1118) +# 8) http://www.chauveau-central.net/mrw-format/ +# 9) CPAN Forum post by 'geve' (http://www.cpanforum.com/threads/2168) +# 10) http://homepage3.nifty.com/kamisaka/makernote/makernote_km.htm +# 11) http://www.dyxum.com/dforum/forum_posts.asp?TID=6371&PN=1 and +# http://www.dyxum.com/dAdmin/lenses/MakerNoteList_Public.asp?stro=makr +# http://dyxum.com/dforum/forum_posts.asp?TID=23435&PN=2 +# 12) http://www.minolta-forum.de/forum/index.php?showtopic=14914 +# 13) http://www.mhohner.de/minolta/lenses.php +# 14) Jeffery Small private communication (tests with 7D) +# 15) http://homepage3.nifty.com/kamisaka/makernote/makernote_sony.htm +# 16) Thomas Kassner private communication +# 17) Mladen Sever private communication +# 18) Olaf Ulrich private communication +# 19) Lukasz Stelmach private communication +# 20) Igal Milchtaich private communication (A100 firmware 1.04) +# 21) Jean-Michel Dubois private communication +# 22) http://www.mi-fo.de/forum/index.php?act=attach&type=post&id=6024 +# 23) Marcin Krol private communication +# 24) http://cpanforum.com/threads/12291 +# 26) https://exiftool.org/forum/index.php/topic,3521.0.html +# 27) https://exiftool.org/forum/index.php/topic,3833.0.html +# 28) Michael Reitinger private communication (RX100) +# 29) https://exiftool.org/forum/index.php/topic,4086.0.html +# IB) Iliah Borg private communication (LibRaw) +# JD) Jens Duttke private communication +# JR) Jos Roost private communication +# NJ) Niels Kristian Bech Jensen private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::Minolta; + +use strict; +use vars qw($VERSION %minoltaLensTypes %minoltaTeleconverters %minoltaColorMode + %sonyColorMode %minoltaSceneMode %afStatusInfo %metabonesID); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '2.88'; + +# Full list of product codes for Sony-compatible Minolta lenses +# (ref http://www.kb.sony.com/selfservice/documentLink.do?externalId=C1000570) +# NOTE: Unfortunately, these product codes sometimes do not match the first 4 +# digits of the lens ID +# 2578 AF 16mm F2.8 FISH-EYE +# 2579 AF 20mm F2.8 +# 2641 AF 20mm F2.8 NEW +# 2566 AF 24mm F2.8 +# 2642 AF 24mm F2.8 NEW +# 2596 AF 28mm F2 +# 2668 AF 28mm F2 NEW +# 2557 AF 28mm F2.8 +# 2591 AF 35mm F1.4 G +# 2666 AF 35mm F1.4 G NEW +# 2597 AF 35mm F2 +# 2667 AF 35mm F2 NEW +# 2562 AF 50mm F1.4 +# 2662 AF 50mm F1.4 NEW +# 2550 AF 50mm F1.7 +# 2613 AF 50mm F1.7 NEW +# 2592 AF 85mm F1.4 +# 2629 AF 85mm F1.4 G +# 2677 AF 85mm F1.4 G (D) +# 2689 AF 85mm F1.4 G (D) Limited +# 2598 AF 100mm F2 +# 2648 AF 100mm F2.8 SOFT +# 2556 AF 135mm F2.8 +# 2656 STF 135mm F2.8 [T4.5] +# 2593 AF APO 20 0mm F2.8 +# 2612 High Speed AF APO 200mm F2.8 G +# 2563 AF APO 300mm F2.8 +# 2608 High Speed AF APO 300mm F2.8 G +# 2674 AF APO 300mm F2.8 G(D) SSM +# 2640 AF APO 300mm F4 G +# 2651 AF APO 400mm F4.5 G +# 2572 AF REFLEX 500mm F8 +# 2565 AF APO 600mm F4 +# 2609 High Speed AF APO 600mm F4 G +# 2564 AF MACRO 50mm F2.8 +# 2638 AF MACRO 50mm F2.8 NEW +# 2675 AF MACRO 50mm F2.8 (D) +# 2646 AF MACRO 50mm F3.5 +# 2581 AF MACRO 100mm F2.8 +# 2639 AF MACRO 100mm F2.8 NEW +# 2676 AF MACRO 100mm F2.8 (D) +# 2658 AF APO TE LE MACRO 200mm F4 G +# 2594 AF MACRO ZOOM 1x-3x F1.7-2.8 +# 2695 AF 17-35mm F2.8-4 (D) +# 2654 AF 17-35mm F3.5 G +# 2657 AF 20-35mm F3.5-4.5 +# 2558 AF 24-50mm F4 +# 2632 AF 24-50mm F4 NEW +# 2636 AF 24-85mm F3.5-4.5 +# 2660 AF 24-85mm F3.5-4.5 NEW +# 2672 AF 24-105mm F3.5-4.5 (D) +# 2620 AF 28-70mm F2.8 G +# 2696 AF 28-75mm F2.8 (D) +# 2659 AF 28-80mm F3.5-5.6 +# 2670 AF 28-80mm F3.5-5.6 II +# 2683 AF 28-80mm F3.5-5.6 (D) +# 2633 AF 28-80mm F4-5.6 +# 2552 AF 28-85mm F3.5-4.5 +# 2586 AF 28-85mm F3.5-4.5 NEW +# 2692 AF 28-100mm F3.5-5.6 (D) +# 2635 AF 28-105mm F3.5-4.5 +# 2661 AF 28-105mm F3.5-4.5 NEW +# 2553 AF 28-135mm F4-4.5 +# 2551 AF 35-70mm F4 +# 2643 AF 35-70mm F3.5-4.5 +# 2652 AF 35-70mm F3.5-4.5 NEW +# 2605 AF 35-80mm F4-5.6 +# 2671 AF 35-80mm F4.5-5.6 II +# 2554 AF 35-105mm F3.5-4.5 +# 2585 AF 35-105mm F3.5-4.5 +# 2682 AF APO 70-200mm F2.8 G (D) SSM +# 2588 AF 70-210mm F4.5-5.6 +# 2555 AF 70-210mm F4 +# 2634 AF 70-210mm F4.5-5.6 NEW +# 2669 AF 70-210mm F4.5-5.6 II +# 2561 AF 75-300mm F4.5-5.6 +# 2649 AF 75-300mm F4.5-5.6 NEW +# 2665 AF 75-300mm F4.5-5.6 II +# 2684 AF 75-300mm F4.5-5.6 (D) +# 2589 AF APO 80-200mm F2.8 +# 2628 High-Speed AF APO 80-200mm F2.8 G +# 2604 AF 80-200mm F4.5-5.6 +# 2560 AF 100-200mm F4.5 +# 2606 AF 100-300mm F4.5-5.6 +# 2631 AF APO 100-300mm F4.5-5.6 +# 2681 AF APO 100-300mm F4.5-5.6 (D) +# 2644 AF APO 100-400mm F4.5-6.7 +# 2618 AF Xi 28-80mm F4-5.6 +# 2615 AF Xi 28-105mm F3.5-4.5 +# 2624 AF PZ 35-80mm F4-5.6 +# 2616 AF Xi 35-200mm F4.5-5.6 +# 2619 AF Xi 80-200mm F4.5-5.6 +# 2621 AF Xi 100-300mm F4.5-5.6 +# 2698 AF DT 11-18mm F4.5-5.6 (D) +# 2697 AF DT 18-70mm F3.5-5.6 (D) +# 2699 AF DT 18-200mm F3.5-6.3 (D) +# 2590 1.4x TELE CONVERTER APO +# 2601 2x TELE CONVERTER APO +# 2610 1.4x TELE CONVERTER APO II +# 2611 2x TELE CONVERTER APO II +# 2687 1.4x TELE CONVERTER APO (D) +# 2688 2x TELE CONVERTER APO (D) + +# high bytes in Sony LensID's identifying Metabones adapters and high bytes of Canon LensID's +%metabonesID = ( + # 0xef00 is used by Metabones, Fotodiox, Sigma and Viltrox adapters (JR) + 0xef00 => \ 'Canon EF Adapter', # with Canon LensID 0x00xx + 0xf000 => 0xef00, # with Canon LensID 0x01xx + 0xf100 => 0xef00, # with Canon LensID 0x02xx + 0xff00 => 0xef00, # with Canon LensID 0x10xx + 0x7700 => \ 'Metabones Speed Booster', # with Canon LensID 0x00xx + 0x7800 => 0x7700, # with Canon LensID 0x01xx + 0x7900 => 0x7700, # with Canon LensID 0x02xx + 0x8700 => 0x7700, # with Canon LensID 0x10xx + 0xbc00 => \ 'Metabones Speed Booster Ultra', # with Canon LensID 0x00xx + 0xbd00 => 0xbc00, # with Canon LensID 0x01xx + 0xbe00 => 0xbc00, # with Canon LensID 0x02xx + 0xcc00 => 0xbc00, # with Canon LensID 0x10xx +); + +# lens ID numbers (ref 3) +# ("New" and "II" appear in brackets if original version also has this LensType) +%minoltaLensTypes = ( + Notes => q{ + "New" or "II" appear in brackets if the original version of the lens has the + same LensType. Special logic is employed to identify the attached lens when + a Metabones Canon EF adapter is used. + }, + OTHER => sub { + my ($val, $inv) = @_; + return undef if $inv; + my $id = $val & 0xff00; + # Note: Metabones Smart Adapter firmware versions before 31 kill + # the high byte for 2-byte Canon LensType values, so the reported lens + # will be incorrect for these + my $mb = $metabonesID{$id}; + if ($mb) { + ref $mb or $id = $mb, $mb = $metabonesID{$id}; + require Image::ExifTool::Canon; + my $lens = $Image::ExifTool::Canon::canonLensTypes{$val - $id}; + return "$lens + $$mb" if $lens; + } elsif ($val >= 0x4900) { # test for Sigma MC-11 SA-E adapter with Sigma SA-mount lens + require Image::ExifTool::Sigma; + my $lens = $Image::ExifTool::Sigma::sigmaLensTypes{$val - 0x4900}; + return "$lens + MC-11 SA-E" if $lens; + } + return undef; + }, + 0 => 'Minolta AF 28-85mm F3.5-4.5 New', # New added (ref 13/18) + 1 => 'Minolta AF 80-200mm F2.8 HS-APO G', # white + 2 => 'Minolta AF 28-70mm F2.8 G', + 3 => 'Minolta AF 28-80mm F4-5.6', + 4 => 'Minolta AF 85mm F1.4G', #exiv2 0.23 + 5 => 'Minolta AF 35-70mm F3.5-4.5 [II]', # (original and II, ref 13) + 6 => 'Minolta AF 24-85mm F3.5-4.5 [New]', # (original and New, ref 13) + # 7 => 'AF 100-400mm F4.5-6.7 (D)', ?? + 7 => 'Minolta AF 100-300mm F4.5-5.6 APO [New] or 100-400mm or Sigma Lens', + 7.1 => 'Minolta AF 100-400mm F4.5-6.7 APO', #JD + 7.2 => 'Sigma AF 100-300mm F4 EX DG IF', #JD + 8 => 'Minolta AF 70-210mm F4.5-5.6 [II]', # (original and II, ref 13) + 9 => 'Minolta AF 50mm F3.5 Macro', + 10 => 'Minolta AF 28-105mm F3.5-4.5 [New]', # (original and New, ref 13) + 11 => 'Minolta AF 300mm F4 HS-APO G', + 12 => 'Minolta AF 100mm F2.8 Soft Focus', + 13 => 'Minolta AF 75-300mm F4.5-5.6 (New or II)', # (II and New, ref 13) + 14 => 'Minolta AF 100-400mm F4.5-6.7 APO', + 15 => 'Minolta AF 400mm F4.5 HS-APO G', + 16 => 'Minolta AF 17-35mm F3.5 G', + 17 => 'Minolta AF 20-35mm F3.5-4.5', + 18 => 'Minolta AF 28-80mm F3.5-5.6 II', + 19 => 'Minolta AF 35mm F1.4 G', # G added (ref 18), but not New as per ref 13 + 20 => 'Minolta/Sony 135mm F2.8 [T4.5] STF', + # 20 => 'Sony 135mm F2.8 [T4.5] STF (SAL135F28)', (ref JR) + 22 => 'Minolta AF 35-80mm F4-5.6 II', # II added (ref 13) + 23 => 'Minolta AF 200mm F4 Macro APO G', + 24 => 'Minolta/Sony AF 24-105mm F3.5-4.5 (D) or Sigma or Tamron Lens', + # 24 => 'Sony 24-105mm F3.5-4.5 (SAL24105)', (ref JR) + 24.1 => 'Sigma 18-50mm F2.8', + 24.2 => 'Sigma 17-70mm F2.8-4.5 DC Macro', # (changed "(D)" to "DC Macro", ref JR) + 24.3 => 'Sigma 20-40mm F2.8 EX DG Aspherical IF', #JD/22 + 24.4 => 'Sigma 18-200mm F3.5-6.3 DC', #22 + 24.5 => 'Sigma DC 18-125mm F4-5,6 D', #exiv2 0.23 + # 24.6 => 'Tamron SP AF 28-75mm F2.8 XR Di (IF) Macro', #JD + 24.6 => 'Tamron SP AF 28-75mm F2.8 XR Di LD Aspherical [IF] Macro', #NJ (Model A09) + 24.7 => 'Sigma 15-30mm F3.5-4.5 EX DG Aspherical', #JR + 25 => 'Minolta AF 100-300mm F4.5-5.6 APO (D) or Sigma Lens', + 25.1 => 'Sigma 100-300mm F4 EX (APO (D) or D IF)', #JD + 25.2 => 'Sigma 70mm F2.8 EX DG Macro', #JD + 25.3 => 'Sigma 20mm F1.8 EX DG Aspherical RF', #19 + 25.4 => 'Sigma 30mm F1.4 EX DC', #21/27 + 25.5 => 'Sigma 24mm F1.8 EX DG ASP Macro', #Florian Knorn + # 25 - also seen for an "old Sigma 50mm Macro" (forum2833) + 27 => 'Minolta AF 85mm F1.4 G (D)', # added (D) (ref 13) + # 27 => 'Venus Optics Laowa 105mm F2 STF', #IB (NC) + 28 => 'Minolta/Sony AF 100mm F2.8 Macro (D) or Tamron Lens', + # 28 => 'Sony 100mm F2.8 Macro (SAL100M28)', (ref 18/JR) + 28.1 => 'Tamron SP AF 90mm F2.8 Di Macro', #JD (Model 272E) + 28.2 => 'Tamron SP AF 180mm F3.5 Di LD [IF] Macro', #27 (Model B01) ("SP" moved - ref JR) + 29 => 'Minolta/Sony AF 75-300mm F4.5-5.6 (D)', # Sony added (ref 13) + # 29 => 'Sony 75-300mm F4.5-5.6 (SAL75300)', (ref JR) + 30 => 'Minolta AF 28-80mm F3.5-5.6 (D) or Sigma Lens', + 30.1 => 'Sigma AF 10-20mm F4-5.6 EX DC', #JD + 30.2 => 'Sigma AF 12-24mm F4.5-5.6 EX DG', + 30.3 => 'Sigma 28-70mm EX DG F2.8', #16 + 30.4 => 'Sigma 55-200mm F4-5.6 DC', #JD + 31 => 'Minolta/Sony AF 50mm F2.8 Macro (D) or F3.5', + # 31 => 'Sony 50mm F2.8 Macro (SAL50M28)', (ref JR) + 31.1 => 'Minolta/Sony AF 50mm F3.5 Macro', + 32 => 'Minolta/Sony AF 300mm F2.8 G or 1.5x Teleconverter', #13/18 + # 32 => 'Minolta AF 300mm F2.8 APO G (D) SSM', (ref 13) ("APO" added - ref JR) + # 32 => 'Sony 300mm F2.8 G (SAL300F28G)', (ref 18/JR) + 33 => 'Minolta/Sony AF 70-200mm F2.8 G', + # 33 => 'Sony 70-200mm F2.8 G (SAL70200G)', (ref JR) + # 33 => 'Minolta AF 70-200mm F2.8 APO G (D) SSM' (ref 13) ("APO" added - ref JR) + 35 => 'Minolta AF 85mm F1.4 G (D) Limited', + 36 => 'Minolta AF 28-100mm F3.5-5.6 (D)', + 38 => 'Minolta AF 17-35mm F2.8-4 (D)', # (Konica Minolta, ref 13) + 39 => 'Minolta AF 28-75mm F2.8 (D)', # (Konica Minolta, ref 13) + 40 => 'Minolta/Sony AF DT 18-70mm F3.5-5.6 (D)', # (Konica Minolta, ref 13) + # 40 => 'Sony DT 18-70mm F3.5-5.6 (SAL1870)', (ref JR) + #40.1 => 'Sony AF DT 18-200mm F3.5-6.3', #11 (anomaly? - PH) + 41 => 'Minolta/Sony AF DT 11-18mm F4.5-5.6 (D) or Tamron Lens', # (Konica Minolta, ref 13) + # 41 => 'Sony DT 11-18mm F4.5-5.6 (SAL1118)', (ref JR) + 41.1 => 'Tamron SP AF 11-18mm F4.5-5.6 Di II LD Aspherical IF', #JD (Model A13) + 42 => 'Minolta/Sony AF DT 18-200mm F3.5-6.3 (D)', # Sony added (ref 13) (Konica Minolta, ref 13) + # 42 => 'Sony DT 18-200mm F3.5-6.3 (SAL18200)', (ref JR) + 43 => 'Sony 35mm F1.4 G (SAL35F14G)', # changed from Minolta to Sony (ref 13/18/JR) (but ref 11 shows both!) + 44 => 'Sony 50mm F1.4 (SAL50F14)', # changed from Minolta to Sony (ref 13/18/JR) + 45 => 'Carl Zeiss Planar T* 85mm F1.4 ZA (SAL85F14Z)', #JR + 46 => 'Carl Zeiss Vario-Sonnar T* DT 16-80mm F3.5-4.5 ZA (SAL1680Z)', #JR + 47 => 'Carl Zeiss Sonnar T* 135mm F1.8 ZA (SAL135F18Z)', #JR + 48 => 'Carl Zeiss Vario-Sonnar T* 24-70mm F2.8 ZA SSM (SAL2470Z) or Other Lens', #11/JR + 48.1 => 'Carl Zeiss Vario-Sonnar T* 24-70mm F2.8 ZA SSM II (SAL2470Z2)', #JR + 48.2 => 'Tamron SP 24-70mm F2.8 Di USD', #IB (A007) (also with id 204) + 49 => 'Sony DT 55-200mm F4-5.6 (SAL55200)', #JD/JR + 50 => 'Sony DT 18-250mm F3.5-6.3 (SAL18250)', #11/JR + 51 => 'Sony DT 16-105mm F3.5-5.6 (SAL16105)', #11/JR + #51.1 => 'Sony AF DT 55-200mm F4-5.5', #11 (anomaly? - PH) + # LensType 52 also seen for Fringer Contax_N to E-mount adapter Ver.31 and Ver.21 (ref JR) + 52 => 'Sony 70-300mm F4.5-5.6 G SSM (SAL70300G) or G SSM II or Tamron Lens', #JD + 52.1 => 'Sony 70-300mm F4.5-5.6 G SSM II (SAL70300G2)', #JR + 52.2 => 'Tamron SP 70-300mm F4-5.6 Di USD', #JR,NJ (Model A005) + 53 => 'Sony 70-400mm F4-5.6 G SSM (SAL70400G)', #17(/w correction by Stephen Bishop)/JR + 54 => 'Carl Zeiss Vario-Sonnar T* 16-35mm F2.8 ZA SSM (SAL1635Z) or ZA SSM II', #17/JR + 54.1 => 'Carl Zeiss Vario-Sonnar T* 16-35mm F2.8 ZA SSM II (SAL1635Z2)', #JR + 55 => 'Sony DT 18-55mm F3.5-5.6 SAM (SAL1855) or SAM II', #PH + 55.1 => 'Sony DT 18-55mm F3.5-5.6 SAM II (SAL18552)', #JR + 56 => 'Sony DT 55-200mm F4-5.6 SAM (SAL55200-2)', #22/JR + 57 => 'Sony DT 50mm F1.8 SAM (SAL50F18) or Tamron Lens or Commlite CM-EF-NEX adapter', #22/JR + 57.1 => 'Tamron SP AF 60mm F2 Di II LD [IF] Macro 1:1', # (Model G005) (ref https://exiftool.org/forum/index.php/topic,3858.0.html) + 57.2 => 'Tamron 18-270mm F3.5-6.3 Di II PZD', #27 (Model B008) + # (note: the Commlite CM-EF-NEX adapter also appears to give LensType 57, ref JR) + 58 => 'Sony DT 30mm F2.8 Macro SAM (SAL30M28)', #22/JR + 59 => 'Sony 28-75mm F2.8 SAM (SAL2875)', #21/JR + 60 => 'Carl Zeiss Distagon T* 24mm F2 ZA SSM (SAL24F20Z)', #17/JR + 61 => 'Sony 85mm F2.8 SAM (SAL85F28)', #17/JR + 62 => 'Sony DT 35mm F1.8 SAM (SAL35F18)', #PH/JR + 63 => 'Sony DT 16-50mm F2.8 SSM (SAL1650)', #17/JR + 64 => 'Sony 500mm F4 G SSM (SAL500F40G)', #29 + 65 => 'Sony DT 18-135mm F3.5-5.6 SAM (SAL18135)', #JR + 66 => 'Sony 300mm F2.8 G SSM II (SAL300F28G2)', #29 + 67 => 'Sony 70-200mm F2.8 G SSM II (SAL70200G2)', #JR + 68 => 'Sony DT 55-300mm F4.5-5.6 SAM (SAL55300)', #29 + 69 => 'Sony 70-400mm F4-5.6 G SSM II (SAL70400G2)', #JR + 70 => 'Carl Zeiss Planar T* 50mm F1.4 ZA SSM (SAL50F14Z)', #JR + 128 => 'Tamron or Sigma Lens (128)', + 128.1 => 'Tamron AF 18-200mm F3.5-6.3 XR Di II LD Aspherical [IF] Macro', #JR (Model A14) + # was 128.1 => 'Tamron 18-200mm F3.5-6.3', + 128.2 => 'Tamron AF 28-300mm F3.5-6.3 XR Di LD Aspherical [IF] Macro', #JR (Model A061) + # was 128.2 => 'Tamron 28-300mm F3.5-6.3', + # (removed -- probably never existed, ref IB) 'Tamron 80-300mm F3.5-6.3', + 128.3 => 'Tamron AF 28-200mm F3.8-5.6 XR Di Aspherical [IF] Macro', #JD (Model A031) + # also Tamron AF 28-200mm F3.8-5.6 Aspherical', #IB (Model 71D) + # and 'Tamron AF 28-200mm F3.8-5.6 LD Aspherical [IF] Super', #IB (Model 171D) + 128.4 => 'Tamron SP AF 17-35mm F2.8-4 Di LD Aspherical IF', #JD (Model A05) + 128.5 => 'Sigma AF 50-150mm F2.8 EX DC APO HSM II', #JD + 128.6 => 'Sigma 10-20mm F3.5 EX DC HSM', #11 (Model 202-205) + 128.7 => 'Sigma 70-200mm F2.8 II EX DG APO MACRO HSM', #24 + 128.8 => 'Sigma 10mm F2.8 EX DC HSM Fisheye', #Florian Knorn + # (yes, '128.10'. My condolences to typed languages that use this database - PH) + 128.9 => 'Sigma 50mm F1.4 EX DG HSM', #Florian Knorn (Model A014, ref IB) + '128.10' => 'Sigma 85mm F1.4 EX DG HSM', #27 + '128.11' => 'Sigma 24-70mm F2.8 IF EX DG HSM', #27 + '128.12' => 'Sigma 18-250mm F3.5-6.3 DC OS HSM', #27 + '128.13' => 'Sigma 17-50mm F2.8 EX DC HSM', #Exiv2 + '128.14' => 'Sigma 17-70mm F2.8-4 DC Macro HSM', # (no OS for Sony mount, ref JR) (also C013 Model, ref IB) + '128.15' => 'Sigma 150mm F2.8 EX DG OS HSM APO Macro', #Marcus Holland-Moritz + '128.16' => 'Sigma 150-500mm F5-6.3 APO DG OS HSM', #IB + '128.17' => 'Tamron AF 28-105mm F4-5.6 [IF]', #IB (Model 179D) + '128.18' => 'Sigma 35mm F1.4 DG HSM', #JR + '128.19' => 'Sigma 18-35mm F1.8 DC HSM', #JR (Model A013, ref IB) + '128.20' => 'Sigma 50-500mm F4.5-6.3 APO DG OS HSM', #JR + '128.21' => 'Sigma 24-105mm F4 DG HSM | A', #JR (013) + '128.22' => 'Sigma 30mm F1.4', #IB + '128.23' => 'Sigma 35mm F1.4 DG HSM | A', #IB/JR (012) + '128.24' => 'Sigma 105mm F2.8 EX DG OS HSM Macro', #IB + '128.25' => 'Sigma 180mm F2.8 EX DG OS HSM APO Macro', #IB + '128.26' => 'Sigma 18-300mm F3.5-6.3 DC Macro HSM | C', #IB/JR (014) + '128.27' => 'Sigma 18-50mm F2.8-4.5 DC HSM', #IB + 129 => 'Tamron Lens (129)', + 129.1 => 'Tamron 200-400mm F5.6 LD', #12 (LD ref 23) + 129.2 => 'Tamron 70-300mm F4-5.6 LD', #12 + 131 => 'Tamron 20-40mm F2.7-3.5 SP Aspherical IF', #23 (Model 266D) + 135 => 'Vivitar 28-210mm F3.5-5.6', #16 + 136 => 'Tokina EMZ M100 AF 100mm F3.5', #JD + 137 => 'Cosina 70-210mm F2.8-4 AF', #11 + 138 => 'Soligor 19-35mm F3.5-4.5', #11 + 139 => 'Tokina AF 28-300mm F4-6.3', #IB + # (the following Cosina 70-300mm lens was also marketed as a Phoenix, Vivitar Series 1, and + # some sort of 3rd-party marketing as a Voightlander 70-300mm F4.5-5.6 SKOPAR AF, ref IB) + 142 => 'Cosina AF 70-300mm F4.5-5.6 MC', #IB (was 'Voigtlander 70-300mm F4.5-5.6', #JD) + 146 => 'Voigtlander Macro APO-Lanthar 125mm F2.5 SL', #JD + 194 => 'Tamron SP AF 17-50mm F2.8 XR Di II LD Aspherical [IF]', #23 (Model A16) + 202 => 'Tamron SP AF 70-200mm F2.8 Di LD [IF] Macro', #JR (Model A001) (see also 255.7) + 203 => 'Tamron SP 70-200mm F2.8 Di USD', #JR (Model A009) + 204 => 'Tamron SP 24-70mm F2.8 Di USD', #JR (Model A007) (also with id 48) + 212 => 'Tamron 28-300mm F3.5-6.3 Di PZD', #JR (Model A010) + 213 => 'Tamron 16-300mm F3.5-6.3 Di II PZD Macro', #JR (Model B016) + 214 => 'Tamron SP 150-600mm F5-6.3 Di USD', #JR (Model A011) + 215 => 'Tamron SP 15-30mm F2.8 Di USD', #JR (Model A012) + 216 => 'Tamron SP 45mm F1.8 Di USD', #forum8320 (F013) + 217 => 'Tamron SP 35mm F1.8 Di USD', #forum8320 (F012) + 218 => 'Tamron SP 90mm F2.8 Di Macro 1:1 USD (F017)', #JR (Model F017) + 220 => 'Tamron SP 150-600mm F5-6.3 Di USD G2', #forum8846 (Model A022) + 224 => 'Tamron SP 90mm F2.8 Di Macro 1:1 USD (F004)', #JR (Model F004) + 255 => 'Tamron Lens (255)', + 255.1 => 'Tamron SP AF 17-50mm F2.8 XR Di II LD Aspherical', # (Model A16) + 255.2 => 'Tamron AF 18-250mm F3.5-6.3 XR Di II LD', #JD (Model A18?) + #? 225.2 => 'Tamron AF 18-250mm F3.5-6.3 Di II LD Aspherical [IF] Macro', #JR (Model A18) + 255.3 => 'Tamron AF 55-200mm F4-5.6 Di II LD Macro', # (Model A15) (added "LD Macro", ref 23) + 255.4 => 'Tamron AF 70-300mm F4-5.6 Di LD Macro 1:2', # (Model A17) + 255.5 => 'Tamron SP AF 200-500mm F5.0-6.3 Di LD IF', # (Model A08) + 255.6 => 'Tamron SP AF 10-24mm F3.5-4.5 Di II LD Aspherical IF', #22 (Model B001) + 255.7 => 'Tamron SP AF 70-200mm F2.8 Di LD IF Macro', #22 (Model A001) + 255.8 => 'Tamron SP AF 28-75mm F2.8 XR Di LD Aspherical IF', #24 (Model A09) + 255.9 => 'Tamron AF 90-300mm F4.5-5.6 Telemacro', #Fredrik Agert + 18688 => 'Sigma MC-11 SA-E Mount Converter with not-supported Sigma lens', + # The MC-11 SA-E Mount Converter uses this 18688 offset for not-supported SIGMA mount lenses. + # The MC-11 EF-E Mount Converter uses the 61184 offset for not-supported CANON mount lenses, as also used by Metabones. + # Both MC-11 SA-E and EF-E Mount Converters use the 504xx LensType2 values for supported SA-mount or EF-mount Sigma lenses. + 25501 => 'Minolta AF 50mm F1.7', #7 + 25511 => 'Minolta AF 35-70mm F4 or Other Lens', + 25511.1 => 'Sigma UC AF 28-70mm F3.5-4.5', #12/16(HighSpeed-AF) + 25511.2 => 'Sigma AF 28-70mm F2.8', #JD + 25511.3 => 'Sigma M-AF 70-200mm F2.8 EX Aspherical', #12 + 25511.4 => 'Quantaray M-AF 35-80mm F4-5.6', #JD + 25511.5 => 'Tokina 28-70mm F2.8-4.5 AF', #IB + 25521 => 'Minolta AF 28-85mm F3.5-4.5 or Other Lens', # not New (ref 18) + 25521.1 => 'Tokina 19-35mm F3.5-4.5', #3 + 25521.2 => 'Tokina 28-70mm F2.8 AT-X', #7 + 25521.3 => 'Tokina 80-400mm F4.5-5.6 AT-X AF II 840', #JD + 25521.4 => 'Tokina AF PRO 28-80mm F2.8 AT-X 280', #JD + 25521.5 => 'Tokina AT-X PRO [II] AF 28-70mm F2.6-2.8 270', #24 (original + II versions) + 25521.6 => 'Tamron AF 19-35mm F3.5-4.5', #JD (Model A10) + 25521.7 => 'Angenieux AF 28-70mm F2.6', #JD + 25521.8 => 'Tokina AT-X 17 AF 17mm F3.5', #27 + 25521.9 => 'Tokina 20-35mm F3.5-4.5 II AF', #IB + 25531 => 'Minolta AF 28-135mm F4-4.5 or Other Lens', + 25531.1 => 'Sigma ZOOM-alpha 35-135mm F3.5-4.5', #16 + 25531.2 => 'Sigma 28-105mm F2.8-4 Aspherical', #JD + 25531.3 => 'Sigma 28-105mm F4-5.6 UC', #JR + 25531.4 => 'Tokina AT-X 242 AF 24-200mm F3.5-5.6', #IB + 25541 => 'Minolta AF 35-105mm F3.5-4.5', #13 + 25551 => 'Minolta AF 70-210mm F4 Macro or Sigma Lens', + 25551.1 => 'Sigma 70-210mm F4-5.6 APO', #7 + 25551.2 => 'Sigma M-AF 70-200mm F2.8 EX APO', #6 + 25551.3 => 'Sigma 75-200mm F2.8-3.5', #22 + 25561 => 'Minolta AF 135mm F2.8', + 25571 => 'Minolta/Sony AF 28mm F2.8', # Sony added (ref 18) + # 25571 => 'Sony 28mm F2.8 (SAL28F28)', (ref 18/JR) + 25581 => 'Minolta AF 24-50mm F4', + 25601 => 'Minolta AF 100-200mm F4.5', + 25611 => 'Minolta AF 75-300mm F4.5-5.6 or Sigma Lens', #13 + 25611.1 => 'Sigma 70-300mm F4-5.6 DL Macro', #12 (also DG version ref 27, and APO version ref JR) + 25611.2 => 'Sigma 300mm F4 APO Macro', #3/7 + 25611.3 => 'Sigma AF 500mm F4.5 APO', #JD + 25611.4 => 'Sigma AF 170-500mm F5-6.3 APO Aspherical', #JD + 25611.5 => 'Tokina AT-X AF 300mm F4', #JD + 25611.6 => 'Tokina AT-X AF 400mm F5.6 SD', #22 + 25611.7 => 'Tokina AF 730 II 75-300mm F4.5-5.6', #JD + 25611.8 => 'Sigma 800mm F5.6 APO', #https://exiftool.org/forum/index.php/topic,3472.0.html + 25611.9 => 'Sigma AF 400mm F5.6 APO Macro', #27 + '25611.10' => 'Sigma 1000mm F8 APO', #JR + 25621 => 'Minolta AF 50mm F1.4 [New]', # original and New, not Sony (ref 13/18) + 25631 => 'Minolta AF 300mm F2.8 APO or Sigma Lens', # changed G to APO (ref 13) + 25631.1 => 'Sigma AF 50-500mm F4-6.3 EX DG APO', #JD + 25631.2 => 'Sigma AF 170-500mm F5-6.3 APO Aspherical', #JD (also DG version, ref 27) + 25631.3 => 'Sigma AF 500mm F4.5 EX DG APO', #JD + 25631.4 => 'Sigma 400mm F5.6 APO', #22 + 25641 => 'Minolta AF 50mm F2.8 Macro or Sigma Lens', + 25641.1 => 'Sigma 50mm F2.8 EX Macro', #11 + 25651 => 'Minolta AF 600mm F4 APO', # ("APO" added - ref JR) + 25661 => 'Minolta AF 24mm F2.8 or Sigma Lens', + 25661.1 => 'Sigma 17-35mm F2.8-4 EX Aspherical', #https://exiftool.org/forum/index.php/topic,3789.msg17679.html#msg17679 + 25721 => 'Minolta/Sony AF 500mm F8 Reflex', + # 25721 => 'Sony 500mm F8 Reflex (SAL500F80)', (ref JR) + 25781 => 'Minolta/Sony AF 16mm F2.8 Fisheye or Sigma Lens', # Sony added (ref 13/18) + # 25781 => 'Sony 16mm F2.8 Fisheye (SAL16F28)', (ref 18/JR) + 25781.1 => 'Sigma 8mm F4 EX [DG] Fisheye', + 25781.2 => 'Sigma 14mm F3.5', + 25781.3 => 'Sigma 15mm F2.8 Fisheye', #JD (writes 16mm to EXIF) + 25791 => 'Minolta/Sony AF 20mm F2.8 or Tokina Lens', # Sony added (ref 11) + # 25791 => 'Sony 20mm F2.8 (SAL20F28)', (ref JR) + 25791.1 => 'Tokina AT-X Pro DX 11-16mm F2.8', #https://exiftool.org/forum/index.php/topic,3593.0.html + 25811 => 'Minolta AF 100mm F2.8 Macro [New] or Sigma or Tamron Lens', # not Sony (ref 13/18) + 25811.1 => 'Sigma AF 90mm F2.8 Macro', #JD + 25811.2 => 'Sigma AF 105mm F2.8 EX [DG] Macro', #JD + 25811.3 => 'Sigma 180mm F5.6 Macro', + 25811.4 => 'Sigma 180mm F3.5 EX DG Macro', #https://exiftool.org/forum/index.php/topic,3789.msg17679.html#msg17679 + 25811.5 => 'Tamron 90mm F2.8 Macro', + 25851 => 'Beroflex 35-135mm F3.5-4.5', #16 + 25858 => 'Minolta AF 35-105mm F3.5-4.5 New or Tamron Lens', + 25858.1 => 'Tamron 24-135mm F3.5-5.6', # (Model 190D) + 25881 => 'Minolta AF 70-210mm F3.5-4.5', + 25891 => 'Minolta AF 80-200mm F2.8 APO or Tokina Lens', # black + 25891.1 => 'Tokina 80-200mm F2.8', + # 25901 - Note: only get this with older 1.4x and lenses with 5-digit LensTypes (ref 27) + # 25901 - also "Minolta AF 200mm F2.8 HS-APO G + Minolta AF 1.4x APO" + 25901 => 'Minolta AF 200mm F2.8 G APO + Minolta AF 1.4x APO or Other Lens + 1.4x', #26 + 25901.1 => 'Minolta AF 600mm F4 HS-APO G + Minolta AF 1.4x APO', #27 + 25911 => 'Minolta AF 35mm F1.4', #(from Sony list) (not G as per ref 13) + 25921 => 'Minolta AF 85mm F1.4 G (D)', + 25931 => 'Minolta AF 200mm F2.8 APO', # (not "G", see 26121 - ref JR) + 25941 => 'Minolta AF 3x-1x F1.7-2.8 Macro', + 25961 => 'Minolta AF 28mm F2', + 25971 => 'Minolta AF 35mm F2 [New]', #13 + 25981 => 'Minolta AF 100mm F2', + # 26011 - Note: only get this with older 2x and lenses with 5-digit LensTypes (ref 27) + # 26011 - also "Minolta AF 200mm F2.8 HS-APO G + Minolta AF 2x APO" + 26011 => 'Minolta AF 200mm F2.8 G APO + Minolta AF 2x APO or Other Lens + 2x', #26 + 26011.1 => 'Minolta AF 600mm F4 HS-APO G + Minolta AF 2x APO', #27 + 26041 => 'Minolta AF 80-200mm F4.5-5.6', + 26051 => 'Minolta AF 35-80mm F4-5.6', #(from Sony list) + 26061 => 'Minolta AF 100-300mm F4.5-5.6', # not (D) (ref 13/18) + 26071 => 'Minolta AF 35-80mm F4-5.6', #13 + 26081 => 'Minolta AF 300mm F2.8 HS-APO G', # HS-APO added (ref 13/18) + 26091 => 'Minolta AF 600mm F4 HS-APO G', + 26121 => 'Minolta AF 200mm F2.8 HS-APO G', + 26131 => 'Minolta AF 50mm F1.7 New', + 26151 => 'Minolta AF 28-105mm F3.5-4.5 xi', # xi, not Power Zoom (ref 13/18) + 26161 => 'Minolta AF 35-200mm F4.5-5.6 xi', # xi, not Power Zoom (ref 13/18) + 26181 => 'Minolta AF 28-80mm F4-5.6 xi', # xi, not Power Zoom (ref 13/18) + 26191 => 'Minolta AF 80-200mm F4.5-5.6 xi', # xi, not Power Zoom (ref 13/18) + 26201 => 'Minolta AF 28-70mm F2.8 G', #11 + 26211 => 'Minolta AF 100-300mm F4.5-5.6 xi', # xi, not Power Zoom (ref 13/18) + 26241 => 'Minolta AF 35-80mm F4-5.6 Power Zoom', + 26281 => 'Minolta AF 80-200mm F2.8 HS-APO G', #11 ("HS-APO" added, white, probably same as 1, non-HS is 25891 - ref JR) + 26291 => 'Minolta AF 85mm F1.4 New', + 26311 => 'Minolta AF 100-300mm F4.5-5.6 APO', #11 (does not exist? https://www.dyxum.com/dforum/lens-data-requested_topic23435_page2.html) + 26321 => 'Minolta AF 24-50mm F4 New', + 26381 => 'Minolta AF 50mm F2.8 Macro New', + 26391 => 'Minolta AF 100mm F2.8 Macro', + 26411 => 'Minolta/Sony AF 20mm F2.8 New', # Sony added (ref 13) + 26421 => 'Minolta AF 24mm F2.8 New', + 26441 => 'Minolta AF 100-400mm F4.5-6.7 APO', #11 + 26621 => 'Minolta AF 50mm F1.4 New', + 26671 => 'Minolta AF 35mm F2 New', + 26681 => 'Minolta AF 28mm F2 New', + 26721 => 'Minolta AF 24-105mm F3.5-4.5 (D)', #11 + # 30464: newer firmware versions of the Speed Booster report type 30464 (=0x7700) + # - this is the base to which the Canon LensType is added + 30464 => 'Metabones Canon EF Speed Booster', #Metabones (to this, add Canon LensType) + 45671 => 'Tokina 70-210mm F4-5.6', #22 + 45681 => 'Tokina AF 35-200mm F4-5.6 Zoom SD', #IB (model 352) + 45701 => 'Tamron AF 35-135mm F3.5-4.5', #IB (model 40d) + 45711 => 'Vivitar 70-210mm F4.5-5.6', #IB + 45741 => '2x Teleconverter or Tamron or Tokina Lens', #18 + 45741.1 => 'Tamron SP AF 90mm F2.5', #JD + 45741.2 => 'Tokina RF 500mm F8.0 x2', #JD + 45741.3 => 'Tokina 300mm F2.8 x2', + 45751 => '1.4x Teleconverter', #18 + 45851 => 'Tamron SP AF 300mm F2.8 LD IF', #11 + 45861 => 'Tamron SP AF 35-105mm F2.8 LD Aspherical IF', #Fredrik Agert + 45871 => 'Tamron AF 70-210mm F2.8 SP LD', #Fabio Suprani + # 48128: the Speed Booster Ultra appears to report type 48128 (=0xbc00) + # - this is the base to which the Canon LensType is added + 48128 => 'Metabones Canon EF Speed Booster Ultra', #JR (to this, add Canon LensType) + # 61184: older firmware versions of both the Speed Booster and the Smart Adapter + # report type 61184 (=0xef00), and add only the lower byte of the Canon LensType (ref JR). + # For newer firmware versions this is only used by the Smart Adapter, and + # the full Canon LensType code is added - PH + # the metabones adapter translates Canon L -> G, II -> II, USM -> SSM, IS -> OSS (ref JR) + # This offset is used by Metabones, Fotodiox, Sigma MC-11 EF-E and Viltrox Canon EF adapters. + 61184 => 'Canon EF Adapter', #JR (to this, add Canon LensType) + # 65280 = 0xff00 + 65280 => 'Sigma 16mm F2.8 Filtermatic Fisheye', #IB + # all M42-type lenses give a value of 65535 (and FocalLength=0, FNumber=1) + 65535 => 'E-Mount, T-Mount, Other Lens or no lens', #JD/JR + '65535.1' => 'Arax MC 35mm F2.8 Tilt+Shift', #JD + '65535.2' => 'Arax MC 80mm F2.8 Tilt+Shift', #JD + '65535.3' => 'Zenitar MF 16mm F2.8 Fisheye M42', #JD + '65535.4' => 'Samyang 500mm Mirror F8.0', #19 + '65535.5' => 'Pentacon Auto 135mm F2.8', #19 + '65535.6' => 'Pentacon Auto 29mm F2.8', #19 + '65535.7' => 'Helios 44-2 58mm F2.0', #19 +); + +%minoltaTeleconverters = ( + 0x00 => 'None', + 0x04 => 'Minolta/Sony AF 1.4x APO (D) (0x04)', # (Andy Johnson, A77 APO and APO D) + 0x05 => 'Minolta/Sony AF 2x APO (D) (0x05)', # (Andy Johnson, A77 APO D) + 0x48 => 'Minolta/Sony AF 2x APO (D)', + # 0x48 => 'Sony 2x Teleconverter (SAL20TC)', (ref JR) + 0x50 => 'Minolta AF 2x APO II', + 0x60 => 'Minolta AF 2x APO',#26 + 0x88 => 'Minolta/Sony AF 1.4x APO (D)', + # 0x88 => 'Sony 1.4x Teleconverter (SAL14TC)', (ref JR) + 0x90 => 'Minolta AF 1.4x APO II', + 0xa0 => 'Minolta AF 1.4x APO',#26 +); + +%minoltaColorMode = ( + 0 => 'Natural color', + 1 => 'Black & White', + 2 => 'Vivid color', + 3 => 'Solarization', + 4 => 'Adobe RGB', + 5 => 'Sepia', #10 + 9 => 'Natural', #10 + 12 => 'Portrait', #10 + 13 => 'Natural sRGB', + 14 => 'Natural+ sRGB', + 15 => 'Landscape', #10 + 16 => 'Evening', #10 + 17 => 'Night Scene', #10 + 18 => 'Night Portrait', #10 + 0x84 => 'Embed Adobe RGB', +); + +%sonyColorMode = ( #15 + 0 => 'Standard', + 1 => 'Vivid', #PH + 2 => 'Portrait', + 3 => 'Landscape', + 4 => 'Sunset', + 5 => 'Night View/Portrait', #(portrait if flash is on) + 6 => 'B&W', + 7 => 'Adobe RGB', + 12 => 'Neutral', # Sony + 13 => 'Clear', #JR (NC) + 14 => 'Deep', #JR + 15 => 'Light', #JR (NC) + 16 => 'Autumn Leaves', #JR (NC) + 17 => 'Sepia', #JR + 18 => 'FL', #JR (7SM3) + 19 => 'Vivid 2', #JR (7SM3) + 20 => 'IN', #JR (7SM3) + 21 => 'SH', #JR (7SM3) + 100 => 'Neutral', #JD + 101 => 'Clear', #JD + 102 => 'Deep', #JD + 103 => 'Light', #JD + 104 => 'Night View', #JD + 105 => 'Autumn Leaves', #JD + 255 => 'Off', #JR (new for ILCE-7SM3, July 2020) + 0xffffffff => 'n/a', #PH +); + +%minoltaSceneMode = ( + 0 => 'Standard', + 1 => 'Portrait', + 2 => 'Text', + 3 => 'Night Scene', + 4 => 'Sunset', + 5 => 'Sports', + 6 => 'Landscape', + 7 => 'Night Portrait', #JD + 8 => 'Macro', + 9 => 'Super Macro', + 16 => 'Auto', # (RX100 'Intelligent Auto' - PH) + 17 => 'Night View/Portrait', + 18 => 'Sweep Panorama', #PH (SLT-A55V) + 19 => 'Handheld Night Shot', #PH + 20 => 'Anti Motion Blur', #PH + 21 => 'Cont. Priority AE', #PH + 22 => 'Auto+', + 23 => '3D Sweep Panorama', #PH (SLT-A55V) + 24 => 'Superior Auto', #28 + 25 => 'High Sensitivity', #28 + 26 => 'Fireworks', #28 + 27 => 'Food', #28 + 28 => 'Pet', #28 + 33 => 'HDR', #JR + 0xffff => 'n/a', #PH +); + +# tag information for AFStatus tags (ref 20) +%afStatusInfo = ( + Format => 'int16s', + # 0=in focus, -32768=out of focus, -ve=front focus, +ve=back focus + PrintConvColumns => 2, + PrintConv => { + 0 => 'In Focus', + -32768 => 'Out of Focus', + OTHER => sub { + my ($val, $inv) = @_; + $inv and $val =~ /([-+]?\d+)/, return $1; + return $val < 0 ? "Front Focus ($val)" : "Back Focus (+$val)"; + }, + }, +); + +my %exposureIndicator = ( + 0 => 'Not Indicated', + 1 => 'Under Scale', + 119 => 'Bottom of Scale', + 120 => '-2.0', + 121 => '-1.7', + 122 => '-1.5', + 123 => '-1.3', + 124 => '-1.0', + 125 => '-0.7', + 126 => '-0.5', + 127 => '-0.3', + 128 => '0', + 129 => '+0.3', + 130 => '+0.5', + 131 => '+0.7', + 132 => '+1.0', + 133 => '+1.3', + 134 => '+1.5', + 135 => '+1.7', + 136 => '+2.0', + 253 => 'Top of Scale', + 254 => 'Over Scale', +); + +my %onOff = ( 0 => 'On', 1 => 'Off' ); +my %offOn = ( 0 => 'Off', 1 => 'On' ); + +# Minolta tag table +%Image::ExifTool::Minolta::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0000 => { + Name => 'MakerNoteVersion', + Writable => 'undef', + Count => 4, + }, + 0x0001 => { + Name => 'MinoltaCameraSettingsOld', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::CameraSettings', + ByteOrder => 'BigEndian', + }, + }, + 0x0003 => { + Name => 'MinoltaCameraSettings', + # These camera settings are different for the DiMAGE X31 + Condition => '$self->{Model} ne "DiMAGE X31"', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::CameraSettings', + ByteOrder => 'BigEndian', + }, + }, + 0x0004 => { #8 + Name => 'MinoltaCameraSettings7D', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::CameraSettings7D', + ByteOrder => 'BigEndian', + }, + }, + 0x0010 => { #20 (count: 256) + Name => 'CameraInfoA100', + Condition => '$$self{Model} eq "DSLR-A100"', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::CameraInfoA100', + ByteOrder => 'LittleEndian', + }, + }, + # it appears that image stabilization is on if this tag exists (ref 2), + # but it is an 8kB binary data block! + 0x0018 => [ + { + Name => 'ISInfoA100', + Condition => '$self->{Model} eq "DSLR-A100"', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::ISInfoA100', + ByteOrder => 'BigEndian', + }, + },{ + Name => 'ImageStabilization', + Condition => '$self->{Model} =~ /^DiMAGE (A1|A2|X1)$/', + Notes => q{ + a block of binary data which exists in DiMAGE A2 (and A1/X1?) images only if + image stabilization is enabled + }, + ValueConv => '"On"', + }, + ], + 0x0020 => { + Name => 'WBInfoA100', + Condition => '$$self{Model} eq "DSLR-A100"', + Notes => 'currently decoded only for the Sony A100', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::WBInfoA100', + ByteOrder => 'BigEndian', + }, + }, + 0x0040 => { + Name => 'CompressedImageSize', + Writable => 'int32u', + }, + 0x0081 => { + # JPEG preview found in DiMAGE 7 images + %Image::ExifTool::previewImageTagInfo, + Groups => { 2 => 'Preview' }, + Permanent => 1, # don't add this to a file + }, + 0x0088 => { + Name => 'PreviewImageStart', + Flags => 'IsOffset', + OffsetPair => 0x0089, # point to associated byte count + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + # Note: Sony also uses this tag in A100 ARW images, but it points + # to the same data as JpgFromRaw + }, + 0x0089 => { + Name => 'PreviewImageLength', + OffsetPair => 0x0088, # point to associated offset + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x0100 => { #10 + Name => 'SceneMode', + Writable => 'int32u', + PrintConv => \%minoltaSceneMode, + }, + 0x0101 => [ + { + Name => 'ColorMode', + Condition => '$self->{Make} !~ /^SONY/', + Priority => 0, # Other ColorMode is more reliable for A2 + Writable => 'int32u', + PrintConv => \%minoltaColorMode, + }, + { #15 + Name => 'ColorMode', + Writable => 'int32u', + Notes => 'Sony models', + PrintConv => \%sonyColorMode, + }, + ], + 0x0102 => { + Name => 'MinoltaQuality', + Writable => 'int32u', + # PrintConv strings conform with Minolta reference manual (ref NJ) + # (note that Minolta calls an uncompressed TIFF image "Super fine") + PrintConv => { + 0 => 'Raw', + 1 => 'Super Fine', + 2 => 'Fine', + 3 => 'Standard', + 4 => 'Economy', + 5 => 'Extra fine', + }, + }, + # (0x0103 is the same as 0x0102 above) -- this is true for some + # cameras (A2/7Hi), but not others - PH + 0x0103 => [ + { + Name => 'MinoltaQuality', + Writable => 'int32u', + Condition => '$self->{Model} =~ /^DiMAGE (A2|7Hi)$/', + Notes => 'quality for DiMAGE A2/7Hi', + Priority => 0, # lower priority because this doesn't work for A200 + PrintConv => { #NJ + 0 => 'Raw', + 1 => 'Super Fine', + 2 => 'Fine', + 3 => 'Standard', + 4 => 'Economy', + 5 => 'Extra fine', + }, + }, + { #PH + Name => 'MinoltaImageSize', + Writable => 'int32u', + Condition => '$self->{Model} !~ /^DiMAGE A200$/', + Notes => 'image size for other models except A200', + PrintConv => { + 1 => '1600x1200', + 2 => '1280x960', + 3 => '640x480', + 5 => '2560x1920', + 6 => '2272x1704', + 7 => '2048x1536', + }, + }, + ], + 0x0104 => { #14 + Name => 'FlashExposureComp', + Description => 'Flash Exposure Compensation', + Writable => 'rational64s', + }, + 0x0105 => { #10 + Name => 'Teleconverter', + Writable => 'int32u', + PrintHex => 1, + PrintConv => \%minoltaTeleconverters, + }, + 0x0107 => { #8 + Name => 'ImageStabilization', + Writable => 'int32u', + PrintConv => { + 1 => 'Off', + 5 => 'On', + }, + }, + 0x0109 => { #20 + Name => 'RawAndJpgRecording', + Writable => 'int32u', + PrintConv => \%offOn, + }, + 0x010a => { + Name => 'ZoneMatching', + Writable => 'int32u', + PrintConv => { + 0 => 'ISO Setting Used', + 1 => 'High Key', + 2 => 'Low Key', + }, + }, + 0x010b => { + Name => 'ColorTemperature', + Writable => 'int32u', + }, + 0x010c => { #3 (Alpha 7) + Name => 'LensType', + Writable => 'int32u', + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%minoltaLensTypes, + PrintInt => 1, + }, + # 0x010e - WhiteBalance according to ref #10 + 0x0111 => { #20 + Name => 'ColorCompensationFilter', + Writable => 'int32s', + Notes => 'ranges from -2 for green to +2 for magenta', + }, + 0x0112 => { #PH (from Sony tags, NC) + Name => 'WhiteBalanceFineTune', + Format => 'int32s', + Writable => 'int32u', + }, + 0x0113 => { #PH + Name => 'ImageStabilization', + Condition => '$self->{Model} eq "DSLR-A100"', + Notes => 'valid for Sony A100 only', + Writable => 'int32u', + PrintConv => \%offOn, + }, + 0x0114 => [ + { #8 + Name => 'MinoltaCameraSettings5D', + Condition => '$self->{Model} =~ /^(DYNAX 5D|MAXXUM 5D|ALPHA SWEET)/', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::CameraSettings5D', + ByteOrder => 'BigEndian', + }, + }, + { #PH + Name => 'CameraSettingsA100', + Condition => '$self->{Model} eq "DSLR-A100"', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::CameraSettingsA100', + ByteOrder => 'BigEndian', # required because order differs for ARW and JPG images + }, + }, + ], + 0x0115 => { #20 + Name => 'WhiteBalance', + Writable => 'int32u', + PrintHex => 1, + PrintConv => { + 0x00 => 'Auto', + 0x01 => 'Color Temperature/Color Filter', + 0x10 => 'Daylight', + 0x20 => 'Cloudy', + 0x30 => 'Shade', + 0x40 => 'Tungsten', + 0x50 => 'Flash', + 0x60 => 'Fluorescent', + 0x70 => 'Custom', + }, + }, + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, + 0x0f00 => { + Name => 'MinoltaCameraSettings2', + Writable => 0, + Binary => 1, + }, +); + +%Image::ExifTool::Minolta::CameraSettings = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + PRIORITY => 0, # not as reliable as other tags + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + NOTES => q{ + There is some variability in CameraSettings information between different + models (and sometimes even between different firmware versions), so this + information may not be as reliable as it should be. Because of this, tags + in the following tables are set to lower priority to prevent them from + superseding the values of same-named tags in other locations when duplicate + tags are disabled. + }, + 1 => { + Name => 'ExposureMode', + PrintConv => { + 0 => 'Program', + 1 => 'Aperture Priority', + 2 => 'Shutter Priority', + 3 => 'Manual', + }, + }, + 2 => { + Name => 'FlashMode', + PrintConv => { + 0 => 'Fill flash', + 1 => 'Red-eye reduction', + 2 => 'Rear flash sync', + 3 => 'Wireless', + 4 => 'Off?', #PH + }, + }, + 3 => { + Name => 'WhiteBalance', + PrintConv => 'Image::ExifTool::Minolta::ConvertWhiteBalance($val)', + }, + 4 => { + Name => 'MinoltaImageSize', + PrintConv => { + 0 => 'Full', + 1 => '1600x1200', + 2 => '1280x960', + 3 => '640x480', + 6 => '2080x1560', #PH (A2) + 7 => '2560x1920', #PH (A2) + 8 => '3264x2176', #PH (A2) + }, + }, + 5 => { + Name => 'MinoltaQuality', + PrintConv => { #NJ + 0 => 'Raw', + 1 => 'Super Fine', + 2 => 'Fine', + 3 => 'Standard', + 4 => 'Economy', + 5 => 'Extra Fine', + }, + }, + 6 => { + Name => 'DriveMode', + PrintConv => { + 0 => 'Single', + 1 => 'Continuous', + 2 => 'Self-timer', + 4 => 'Bracketing', + 5 => 'Interval', + 6 => 'UHS continuous', + 7 => 'HS continuous', + }, + }, + 7 => { + Name => 'MeteringMode', + PrintConv => { + 0 => 'Multi-segment', + 1 => 'Center-weighted average', + 2 => 'Spot', + }, + }, + 8 => { + Name => 'ISO', + ValueConv => '2 ** (($val-48)/8) * 100', + ValueConvInv => '48 + 8*log($val/100)/log(2)', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 9 => { + Name => 'ExposureTime', + ValueConv => '2 ** ((48-$val)/8)', + ValueConvInv => '48 - 8*log($val)/log(2)', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 10 => { + Name => 'FNumber', + ValueConv => '2 ** (($val-8)/16)', + ValueConvInv => '8 + 16*log($val)/log(2)', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 11 => { + Name => 'MacroMode', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 12 => { + Name => 'DigitalZoom', + PrintConv => { + 0 => 'Off', + 1 => 'Electronic magnification', + 2 => '2x', + }, + }, + 13 => { + Name => 'ExposureCompensation', + ValueConv => '$val/3 - 2', + ValueConvInv => '($val + 2) * 3', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 14 => { + Name => 'BracketStep', + PrintConv => { + 0 => '1/3 EV', + 1 => '2/3 EV', + 2 => '1 EV', + }, + }, + 16 => 'IntervalLength', + 17 => 'IntervalNumber', + 18 => { + Name => 'FocalLength', + ValueConv => '$val / 256', + ValueConvInv => '$val * 256', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + 19 => { + Name => 'FocusDistance', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => '$val ? "$val m" : "inf"', + PrintConvInv => '$val eq "inf" ? 0 : $val =~ s/\s*m$//, $val', + }, + 20 => { + Name => 'FlashFired', + PrintConv => { + 0 => 'No', + 1 => 'Yes', + }, + }, + 21 => { + Name => 'MinoltaDate', + Groups => { 2 => 'Time' }, + Shift => 'Time', + ValueConv => 'sprintf("%4d:%.2d:%.2d",$val>>16,($val&0xff00)>>8,$val&0xff)', + ValueConvInv => 'my @a=($val=~/(\d+):(\d+):(\d+)/); @a ? ($a[0]<<16)+($a[1]<<8)+$a[2] : undef', + }, + 22 => { + Name => 'MinoltaTime', + Groups => { 2 => 'Time' }, + Shift => 'Time', + ValueConv => 'sprintf("%.2d:%.2d:%.2d",$val>>16,($val&0xff00)>>8,$val&0xff)', + ValueConvInv => 'my @a=($val=~/(\d+):(\d+):(\d+)/); @a ? ($a[0]<<16)+($a[1]<<8)+$a[2] : undef', + }, + 23 => { + Name => 'MaxAperture', + ValueConv => '2 ** (($val-8)/16)', + ValueConvInv => '8 + 16*log($val)/log(2)', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 26 => { + Name => 'FileNumberMemory', + PrintConv => \%offOn, + }, + 27 => 'LastFileNumber', + 28 => { + Name => 'ColorBalanceRed', + ValueConv => '$val / 256', + ValueConvInv => '$val * 256', + }, + 29 => { + Name => 'ColorBalanceGreen', + ValueConv => '$val / 256', + ValueConvInv => '$val * 256', + }, + 30 => { + Name => 'ColorBalanceBlue', + ValueConv => '$val / 256', + ValueConvInv => '$val * 256', + }, + 31 => { + Name => 'Saturation', + ValueConv => '$val - ($self->{Model}=~/DiMAGE A2/ ? 5 : 3)', + ValueConvInv => '$val + ($self->{Model}=~/DiMAGE A2/ ? 5 : 3)', + %Image::ExifTool::Exif::printParameter, + }, + 32 => { + Name => 'Contrast', + ValueConv => '$val - ($self->{Model}=~/DiMAGE A2/ ? 5 : 3)', + ValueConvInv => '$val + ($self->{Model}=~/DiMAGE A2/ ? 5 : 3)', + %Image::ExifTool::Exif::printParameter, + }, + 33 => { + Name => 'Sharpness', + PrintConv => { + 0 => 'Hard', + 1 => 'Normal', + 2 => 'Soft', + }, + }, + 34 => { + Name => 'SubjectProgram', + PrintConv => { + 0 => 'None', + 1 => 'Portrait', + 2 => 'Text', + 3 => 'Night portrait', + 4 => 'Sunset', + 5 => 'Sports action', + }, + }, + 35 => { + Name => 'FlashExposureComp', + Description => 'Flash Exposure Compensation', + ValueConv => '($val - 6) / 3', + ValueConvInv => '$val * 3 + 6', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 36 => { + Name => 'ISOSetting', + PrintConv => { + 0 => 100, + 1 => 200, + 2 => 400, + 3 => 800, + 4 => 'Auto', + 5 => 64, + }, + }, + 37 => { + Name => 'MinoltaModelID', + PrintConv => { + 0 => 'DiMAGE 7, X1, X21 or X31', + 1 => 'DiMAGE 5', + 2 => 'DiMAGE S304', + 3 => 'DiMAGE S404', + 4 => 'DiMAGE 7i', + 5 => 'DiMAGE 7Hi', + 6 => 'DiMAGE A1', + 7 => 'DiMAGE A2 or S414', + }, + }, + 38 => { + Name => 'IntervalMode', + PrintConv => { + 0 => 'Still Image', + 1 => 'Time-lapse Movie', + }, + }, + 39 => { + Name => 'FolderName', + PrintConv => { + 0 => 'Standard Form', + 1 => 'Data Form', + }, + }, + 40 => { + Name => 'ColorMode', + PrintConv => { + 0 => 'Natural color', + 1 => 'Black & White', + 2 => 'Vivid color', + 3 => 'Solarization', + 4 => 'Adobe RGB', + }, + }, + 41 => { + Name => 'ColorFilter', + ValueConv => '$val - ($self->{Model}=~/DiMAGE A2/ ? 5 : 3)', + ValueConvInv => '$val + ($self->{Model}=~/DiMAGE A2/ ? 5 : 3)', + }, + 42 => 'BWFilter', + 43 => { + Name => 'InternalFlash', + PrintConv => { + 0 => 'No', + 1 => 'Fired', + }, + }, + 44 => { + Name => 'Brightness', + ValueConv => '$val/8 - 6', + ValueConvInv => '($val + 6) * 8', + }, + 45 => 'SpotFocusPointX', + 46 => 'SpotFocusPointY', + 47 => { + Name => 'WideFocusZone', + PrintConv => { + 0 => 'No zone', + 1 => 'Center zone (horizontal orientation)', + 2 => 'Center zone (vertical orientation)', + 3 => 'Left zone', + 4 => 'Right zone', + }, + }, + 48 => { + Name => 'FocusMode', + PrintConv => { + 0 => 'AF', + 1 => 'MF', + }, + }, + 49 => { + Name => 'FocusArea', + PrintConv => { + 0 => 'Wide Focus (normal)', + 1 => 'Spot Focus', + }, + }, + 50 => { + Name => 'DECPosition', + PrintConv => { + 0 => 'Exposure', + 1 => 'Contrast', + 2 => 'Saturation', + 3 => 'Filter', + }, + }, + # 7Hi only: + 51 => { + Name => 'ColorProfile', + Condition => '$self->{Model} eq "DiMAGE 7Hi"', + Notes => 'DiMAGE 7Hi only', + PrintConv => { + 0 => 'Not Embedded', + 1 => 'Embedded', + }, + }, + # (the following may be entry 51 for other models?) + 52 => { + Name => 'DataImprint', + Condition => '$self->{Model} eq "DiMAGE 7Hi"', + Notes => 'DiMAGE 7Hi only', + PrintConv => { + 0 => 'None', + 1 => 'YYYY/MM/DD', + 2 => 'MM/DD/HH:MM', + 3 => 'Text', + 4 => 'Text + ID#', + }, + }, + 63 => { #9 + Name => 'FlashMetering', + PrintConv => { + 0 => 'ADI (Advanced Distance Integration)', + 1 => 'Pre-flash TTL', + 2 => 'Manual flash control', + }, + }, +); + +# Camera settings used by the 7D (ref 8) +%Image::ExifTool::Minolta::CameraSettings7D = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + 0x00 => { + Name => 'ExposureMode', + PrintConv => { + 0 => 'Program', + 1 => 'Aperture Priority', + 2 => 'Shutter Priority', + 3 => 'Manual', + 4 => 'Auto', + 5 => 'Program-shift A', + 6 => 'Program-shift S', + }, + }, + 0x02 => { #PH + Name => 'MinoltaImageSize', + PrintConv => { + 0 => 'Large', + 1 => 'Medium', + 2 => 'Small', + }, + }, + 0x03 => { + Name => 'MinoltaQuality', + PrintConv => { + 0 => 'RAW', + 16 => 'Fine', #PH + 32 => 'Normal', #PH + 34 => 'RAW+JPEG', + 48 => 'Economy', #PH + }, + }, + 0x04 => { + Name => 'WhiteBalance', + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Shade', + 3 => 'Cloudy', + 4 => 'Tungsten', + 5 => 'Fluorescent', + 0x100 => 'Kelvin', + 0x200 => 'Manual', + }, + }, + 0x0e => { + Name => 'FocusMode', + PrintConv => { + 0 => 'AF-S', + 1 => 'AF-C', + # Note: these two are reversed in ref 8 + 3 => 'Manual', #JD + 4 => 'AF-A', #JD + }, + }, + 0x10 => { + Name => 'AFPoints', + PrintConv => { + 0 => '(none)', + BITMASK => { + 0 => 'Center', + 1 => 'Top', + 2 => 'Top-right', + 3 => 'Right', + 4 => 'Bottom-right', + 5 => 'Bottom', + 6 => 'Bottom-left', + 7 => 'Left', + 8 => 'Top-left', + }, + }, + }, + 0x15 => { + Name => 'Flash', + PrintConv => \%offOn, + }, + 0x16 => { #10 + Name => 'FlashMode', + PrintConv => { + 0 => 'Normal', + 1 => 'Red-eye reduction', + 2 => 'Rear flash sync', + }, + }, + 0x1c => { + Name => 'ISOSetting', + PrintConv => { + 0 => 'Auto', #10 + 1 => 100, + 3 => 200, + 4 => 400, + 5 => 800, + 6 => 1600, + 7 => 3200, + }, + }, + 0x1e => { + Name => 'ExposureCompensation', + Format => 'int16s', + ValueConv => '$val / 24', + ValueConvInv => '$val * 24', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x25 => { + Name => 'ColorSpace', + PrintConv => { + 0 => 'Natural sRGB', + 1 => 'Natural+ sRGB', + 4 => 'Adobe RGB', + }, + }, + 0x26 => { + Name => 'Sharpness', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + }, + 0x27 => { + Name => 'Contrast', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + }, + 0x28 => { + Name => 'Saturation', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + }, + 0x2d => 'FreeMemoryCardImages', + 0x3f => { + Format => 'int16s', + Name => 'ColorTemperature', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + }, + 0x40 => { #10 + Name => 'HueAdjustment', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + }, + 0x46 => { + Name => 'Rotation', + PrintConv => { + 72 => 'Horizontal (normal)', + 76 => 'Rotate 90 CW', + 82 => 'Rotate 270 CW', + }, + }, + 0x47 => { + Name => 'FNumber', + ValueConv => '2 ** (($val-8)/16)', + ValueConvInv => '8 + 16*log($val)/log(2)', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x48 => { + Name => 'ExposureTime', + ValueConv => '2 ** ((48-$val)/8)', + ValueConvInv => '48 - 8*log($val)/log(2)', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x4a => 'FreeMemoryCardImages', + 0x5e => { + Name => 'ImageNumber', + Notes => q{ + this information may appear at index 98 (0x62), depending on firmware + version + }, + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x60 => { + Name => 'NoiseReduction', + PrintConv => \%offOn, + }, + 0x62 => { + Name => 'ImageNumber2', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0x71 => { + Name => 'ImageStabilization', + PrintConv => \%offOn, + }, + 0x75 => { + Name => 'ZoneMatchingOn', + PrintConv => \%offOn, + }, +); + +# Camera settings used by the 5D (ref 8) +%Image::ExifTool::Minolta::CameraSettings5D = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + 0x0a => { + Name => 'ExposureMode', + PrintConv => { + 0 => 'Program', + 1 => 'Aperture Priority', + 2 => 'Shutter Priority', + 3 => 'Manual', + 4 => 'Auto?', + 4131 => 'Connected Copying?', + }, + }, + 0x0c => { #PH + Name => 'MinoltaImageSize', + PrintConv => { + 0 => 'Large', + 1 => 'Medium', + 2 => 'Small', + }, + }, + 0x0d => { + Name => 'MinoltaQuality', + PrintConv => { + 0 => 'RAW', + 16 => 'Fine', #PH + 32 => 'Normal', #PH + 34 => 'RAW+JPEG', + 48 => 'Economy', #PH + }, + }, + 0x0e => { + Name => 'WhiteBalance', + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Cloudy', + 3 => 'Shade', + 4 => 'Tungsten', + 5 => 'Fluorescent', + 6 => 'Flash', + 0x100 => 'Kelvin', + 0x200 => 'Manual', + }, + }, + # 0x0f-0x11 something to do with WB RGB levels as shot? (PH) + # 0x12-0x17 RGB levels for other WB modes (with G missing)? (PH) + 0x1f => { #PH + Name => 'Flash', + PrintConv => { + 0 => 'Did not fire', + 1 => 'Fired', + }, + }, + 0x20 => { #10 + Name => 'FlashMode', + PrintConv => { + 0 => 'Normal', + 1 => 'Red-eye reduction', + 2 => 'Rear flash sync', + }, + }, + 0x25 => { + Name => 'MeteringMode', + PrintConv => { + 0 => 'Multi-segment', + 1 => 'Center-weighted average', + 2 => 'Spot', + }, + }, + 0x26 => { + Name => 'ISOSetting', + PrintConv => { + 0 => 'Auto', + 1 => 100, + 3 => 200, + 4 => 400, + 5 => 800, + 6 => 1600, + 7 => 3200, + 8 => '200 (Zone Matching High)', + 10 => '80 (Zone Matching Low)', + }, + }, +# looks wrong: +# 0x28 => { #10 +# Name => 'ExposureCompensation', +# ValueConv => '$val / 24', +# ValueConvInv => '$val * 24', +# }, + 0x2f => { #10 + Name => 'ColorSpace', + PrintConv => { + 0 => 'Natural sRGB', + 1 => 'Natural+ sRGB', + 2 => 'Monochrome', + 4 => 'Adobe RGB (ICC)', + 5 => 'Adobe RGB', + }, + }, + 0x30 => { + Name => 'Sharpness', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + }, + 0x31 => { + Name => 'Contrast', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + }, + 0x32 => { + Name => 'Saturation', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + }, + 0x35 => { #PH + Name => 'ExposureTime', + ValueConv => '2 ** ((48-$val)/8)', + ValueConvInv => '48 - 8*log($val)/log(2)', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x36 => { #PH + Name => 'FNumber', + ValueConv => '2 ** (($val-8)/16)', + ValueConvInv => '8 + 16*log($val)/log(2)', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x37 => 'FreeMemoryCardImages', + # 0x38 definitely not related to exposure comp as in ref 8 (PH) + 0x49 => { #PH + Name => 'ColorTemperature', + Format => 'int16s', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + }, + 0x4a => { #10 + Name => 'HueAdjustment', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + }, + 0x50 => { + Name => 'Rotation', + PrintConv => { + 72 => 'Horizontal (normal)', + 76 => 'Rotate 90 CW', + 82 => 'Rotate 270 CW', + }, + }, + 0x53 => { + Name => 'ExposureCompensation', + ValueConv => '$val / 100 - 3', + ValueConvInv => '($val + 3) * 100', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x54 => 'FreeMemoryCardImages', + 0x65 => { #10 + Name => 'Rotation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + }, + }, + # 0x66 maybe program mode or some setting like this? (PH) + 0x6e => { #10 + Name => 'ColorTemperature', + Format => 'int16s', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + }, + 0x71 => { #10 + Name => 'PictureFinish', + PrintConv => { + 0 => 'Natural', + 1 => 'Natural+', + 2 => 'Portrait', + 3 => 'Wind Scene', + 4 => 'Evening Scene', + 5 => 'Night Scene', + 6 => 'Night Portrait', + 7 => 'Monochrome', + 8 => 'Adobe RGB', + 9 => 'Adobe RGB (ICC)', + }, + }, + # 0x95 FlashStrength? (PH) + # 0xa4 similar information to 0x27, except with different values + 0xae => { + Name => 'ImageNumber', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 0xb0 => { + Name => 'NoiseReduction', + PrintConv => \%offOn, + }, + 0xbd => { + Name => 'ImageStabilization', + PrintConv => \%offOn, + }, +); + +# Camera settings used by the Sony DSLR-A100 (ref 20) +%Image::ExifTool::Minolta::CameraInfoA100 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + NOTES => 'Camera information for the Sony DSLR-A100.', + WRITABLE => 1, + PRIORITY => 0, # may not be as reliable as other information + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + 0x01 => { #PH + Name => 'AFSensorActive', + PrintConv => { + 0 => 'Top-right', + 1 => 'Bottom-right', + 2 => 'Bottom', + 3 => 'Middle Horizontal', + 4 => 'Center Vertical', + 5 => 'Top', + 6 => 'Top-left', + 7 => 'Bottom-left', + }, + }, + 0x02 => { + Name => 'AFStatusActiveSensor', + %afStatusInfo, + Notes => q{ + the focus status at shutter release. May not reflect the status after + focusing if the image is focused then recomposed + }, + }, + 0x04 => { Name => 'AFStatusTop-right', %afStatusInfo }, + 0x06 => { Name => 'AFStatusBottom-right', %afStatusInfo }, + 0x08 => { Name => 'AFStatusBottom', %afStatusInfo }, + 0x0a => { + Name => 'AFStatusMiddleHorizontal', + %afStatusInfo, + Notes => q{ + any of the three horizontal sensors at the middle of the focus frame: Left, + Center or Right + }, + }, + 0x0c => { Name => 'AFStatusCenterVertical', %afStatusInfo }, + 0x0e => { Name => 'AFStatusTop', %afStatusInfo }, + 0x10 => { Name => 'AFStatusTop-left', %afStatusInfo }, + 0x12 => { Name => 'AFStatusBottom-left', %afStatusInfo }, + 0x14 => { + Name => 'FocusLocked', + # (Focus can be locked in all modes other than Manual and Continuous, + # and the latter can be overridden by pushing the Spot AF button) + PrintConv => { + 0 => 'Manual Focus', + 4 => 'No', + 16 => 'Continuous Focus', + 64 => 'Yes', + }, + }, + 0x15 => { + Name => 'AFPoint', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Auto', + 1 => 'Center', + 2 => 'Top', + 3 => 'Top-right', + 4 => 'Right', + 5 => 'Bottom-right', + 6 => 'Bottom', + 7 => 'Bottom-left', + 8 => 'Left', + 9 => 'Top-left', + }, + }, + 0x16 => { + Name => 'AFMode', + PrintConv => { + 0 => 'DMF', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', + }, + }, + 0x2d => { Name => 'AFStatusLeft', %afStatusInfo }, + 0x2f => { Name => 'AFStatusCenterHorizontal',%afStatusInfo }, + 0x31 => { Name => 'AFStatusRight', %afStatusInfo }, + 0x33 => { + Name => 'AFAreaMode', + PrintConv => { + 0 => 'Wide', + 1 => 'Local', + 2 => 'Spot', + }, + }, +); + +# Image stabilization information used by the Sony DSLR-A100 (ref 20) +%Image::ExifTool::Minolta::ISInfoA100 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + NOTES => 'Image stabilization information for the Sony DSLR-A100.', + WRITABLE => 1, + PRIORITY => 0, # may not be as reliable as other information + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + 0 => { + Name => 'ImageStabilization', + Format => 'int16u', + PrintHex => 1, + PrintConv => { + 0x0000 => 'Off', + 0x2784 => 'On', + }, + }, +); + +# Camera settings used by the Sony DSLR-A100 (ref PH) +%Image::ExifTool::Minolta::CameraSettingsA100 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + NOTES => 'Camera settings information for the Sony DSLR-A100.', + WRITABLE => 1, + PRIORITY => 0, # may not be as reliable as other information + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + 0x00 => { #15 + Name => 'ExposureMode', + PrintHex => 1, + PrintConv => { + 0 => 'Program', + 1 => 'Aperture Priority', + 2 => 'Shutter Priority', + 3 => 'Manual', + 4 => 'Auto', + 5 => 'Program Shift A', #20 + 6 => 'Program Shift S', #20 + 0x1013 => 'Portrait', + 0x1023 => 'Sports', #20 + 0x1033 => 'Sunset', #20 + 0x1043 => 'Night View/Portrait', #20 + 0x1053 => 'Landscape', + 0x1083 => 'Macro', #20 + }, + }, + 0x01 => { #15 + Name => 'ExposureCompensationSetting', + # (differs from ExposureCompensation for exposure bracketing shots, ref 20) + ValueConv => '$val / 100 - 3', + ValueConvInv => 'int(($val + 3) * 100 + 0.5)', + }, + 0x05 => { #20 (requires external flash) + Name => 'HighSpeedSync', + PrintConv => \%offOn, + }, + 0x06 => { #20 + Name => 'ShutterSpeedSetting', + Notes => 'used only in M and S exposure modes', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x07 => { #20 + Name => 'ApertureSetting', + Notes => 'used only in M and A exposure modes', + ValueConv => '2 ** (($val/8 - 1) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }, + 0x08 => { #20 + Name => 'ExposureTime', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x09 => { #15/20 + Name => 'FNumber', + ValueConv => '2 ** (($val/8 - 1) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }, + 0x0a => { #20 + Name => 'DriveMode2', # (one of these is probably DriveModeSetting like Sony - PH) + PrintHex => 1, + PrintConv => { + 0x000 => 'Self-timer 10 sec', + 0x001 => 'Continuous', + 0x302 => 'Single-frame Bracketing Low', + 0x702 => 'Single-frame Bracketing High', + 0x303 => 'Continous Bracketing Low', + 0x703 => 'Continuous Bracketing High', + 0x004 => 'Self-timer 2 sec', + 0x005 => 'Single Frame', + 0x008 => 'White Balance Bracketing Low', + 0x009 => 'White Balance Bracketing High', + }, + }, + 0x0b => { #15 + Name => 'WhiteBalance', + PrintHex => 1, + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Cloudy', + 3 => 'Shade', + 4 => 'Tungsten', + 5 => 'Fluorescent', + 6 => 'Flash', + 0x100 => 'Kelvin', + 0x200 => 'Manual', + }, + }, + 0x0c => { #20 + Name => 'FocusMode', + PrintConv => { + 0 => 'AF-S', + 1 => 'AF-C', + 4 => 'AF-A', + 5 => 'Manual', + 6 => 'DMF', + }, + }, + 0x0d => { #20 + Name => 'AFPointSelected', # (v8.88: renamed from LocalAFAreaPoint) + # (9-point centre-cross AF system, ref JR) + PrintConv => { + 1 => 'Center', + 2 => 'Top', + 3 => 'Top-right', + 4 => 'Right', + 5 => 'Bottom-right', + 6 => 'Bottom', + 7 => 'Bottom-left', + 8 => 'Left', + 9 => 'Top-left', + }, + }, + 0x0e => { #20 + Name => 'AFAreaMode', + PrintConv => { + 0 => 'Wide', + 1 => 'Local', + 2 => 'Spot', + }, + }, + 0x0f => { #20 + Name => 'FlashMode', + PrintConv => { + 0 => 'Auto', + 2 => 'Rear Sync', + 3 => 'Wireless', + 4 => 'Fill Flash', + }, + }, + 0x10 => { #20 + Name => 'FlashExposureCompSet', + Description => 'Flash Exposure Comp. Setting', + # (may differ from FlashExposureComp for flash bracketing shots) + ValueConv => '$val / 100 - 3', + ValueConvInv => 'int(($val + 3) * 100 + 0.5)', + }, + 0x12 => { #15/20 + Name => 'MeteringMode', + PrintConv => { + 0 => 'Multi-segment', + 1 => 'Center-weighted average', + 2 => 'Spot', + }, + }, + 0x13 => { #15/20 + Name => 'ISOSetting', + PrintConv => { + 0 => 'Auto', + 48 => 100, + 56 => 200, + 64 => 400, + 72 => 800, + 80 => 1600, + 174 => '80 (Zone Matching Low)', + 184 => '200 (Zone Matching High)', + }, + }, + 0x14 => { #15/20 + Name => 'ZoneMatchingMode', + PrintConv => { + 0 => 'Off', + 1 => 'Standard', + 2 => 'Advanced', + }, + }, + 0x15 => { #15/20 + Name => 'DynamicRangeOptimizer', + # this and the Sony tag 0xb025 DynamicRangeOptimizer give the actual mode + # applied to the image. The Minolta CameraSettingsA100 0x0027 tag gives + # the setting. There is a longish list of scenarios in which, regardless + # of the latter, DRO is not applied (ref 20) + Notes => 'as applied to image', + PrintConv => { + 0 => 'Off', + 1 => 'Standard', + 2 => 'Advanced', + }, + }, + 0x16 => { #15 + Name => 'ColorMode', + PrintConv => { + 0 => 'Standard', + 1 => 'Vivid', + 2 => 'Portrait', + 3 => 'Landscape', + 4 => 'Sunset', + 5 => 'Night Scene', + 7 => 'B&W', + 8 => 'Adobe RGB', + }, + }, + 0x17 => { # 15/20 + Name => 'ColorSpace', + PrintConv => { + 0 => 'sRGB', + 2 => 'B&W', #PH (A100) + 5 => 'Adobe RGB', + }, + }, + 0x18 => { #15 + Name => 'Sharpness', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + %Image::ExifTool::Exif::printParameter, + }, + 0x19 => { #15 + Name => 'Contrast', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + %Image::ExifTool::Exif::printParameter, + }, + 0x1a => { #15 + Name => 'Saturation', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + %Image::ExifTool::Exif::printParameter, + }, + 0x1c => { #20 + Name => 'FlashMetering', + PrintConv => { + 0 => 'ADI (Advanced Distance Integration)', + 1 => 'Pre-flash TTL', + }, + }, + 0x1d => { #20 + Name => 'PrioritySetupShutterRelease', + PrintConv => { + 0 => 'AF', + 1 => 'Release', + }, + }, + 0x1e => { #PH + Name => 'DriveMode', + PrintConv => { + 0 => 'Single Frame', + 1 => 'Continuous', + 2 => 'Self-timer', + 3 => 'Continuous Bracketing', + 4 => 'Single-Frame Bracketing', + 5 => 'White Balance Bracketing', + }, + }, + 0x1f => { #20 + Name => 'SelfTimerTime', + PrintConv => { + 0 => '10 s', + 4 => '2 s', + }, + }, + 0x20 => { #20 + Name => 'ContinuousBracketing', + PrintHex => 1, + PrintConv => { + 0x303 => 'Low', + 0x703 => 'High', + }, + }, + 0x21 => { #20 + Name => 'SingleFrameBracketing', + PrintHex => 1, + PrintConv => { + 0x302 => 'Low', + 0x702 => 'High', + }, + }, + 0x22 => { #20 + Name => 'WhiteBalanceBracketing', + PrintHex => 1, + PrintConv => { + 0x08 => 'Low', + 0x09 => 'High', + }, + }, + 0x023 => { #20 + Name => 'WhiteBalanceSetting', + PrintHex => 1, + # (not sure what bit 0x8000 indicates) + PrintConv => { + 0 => 'Auto', + 1 => 'Preset', + 2 => 'Custom', + 3 => 'Color Temperature/Color Filter', + 0x8001 => 'Preset', + 0x8002 => 'Custom', + 0x8003 => 'Color Temperature/Color Filter', + }, + }, + 0x24 => { #20 + Name => 'PresetWhiteBalance', + PrintConv => { + 1 => 'Daylight', + 2 => 'Cloudy', + 3 => 'Shade', + 4 => 'Tungsten', + 5 => 'Fluorescent', + 6 => 'Flash', + }, + }, + 0x25 => { #20 + Name => 'ColorTemperatureSetting', + PrintConv => { + 0 => 'Temperature', + 2 => 'Color Filter', + }, + }, + 0x26 => { #20 + Name => 'CustomWBSetting', + PrintConv => { + 0 => 'Setup', + 1 => 'Recall', + }, + }, + 0x27 => { #20 + Name => 'DynamicRangeOptimizerSetting', + Notes => 'as set in camera', + PrintConv => { + 0 => 'Off', + 1 => 'Standard', + 2 => 'Advanced', + }, + }, + 0x32 => 'FreeMemoryCardImages', #20 + 0x34 => 'CustomWBRedLevel', #20 + 0x35 => 'CustomWBGreenLevel', #20 + 0x36 => 'CustomWBBlueLevel', #20 + 0x37 => { #20 + Name => 'CustomWBError', + PrintConv => { + 0 => 'OK', + 1 => 'Error', + }, + }, + 0x38 => { #20 + Name => 'WhiteBalanceFineTune', + Format => 'int16s', + }, + 0x39 => { #20 + Name => 'ColorTemperature', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + }, + 0x3a => { #20 + Name => 'ColorCompensationFilter', + Format => 'int16s', + Notes => 'ranges from -2 for green to +2 for magenta', + }, + 0x3b => { #20 + Name => 'SonyImageSize', + PrintConv => { + 0 => 'Standard', + 1 => 'Medium', + 2 => 'Small', + }, + }, + 0x3c => { #20 + Name => 'SonyQuality', + PrintConv => { + 0 => 'RAW', + 32 => 'Fine', + 34 => 'RAW + JPEG', + 48 => 'Standard', + }, + }, + 0x3d => { #20 + Name => 'InstantPlaybackTime', + PrintConv => '"$val s"', + PrintConvInv => '$val=~s/\s*s//; $val', + }, + 0x3e => { #20 + Name => 'InstantPlaybackSetup', + PrintConv => { + 0 => 'Image and Information', + 1 => 'Image Only', + # 2 appears to be unused + 3 => 'Image and Histogram', + }, + }, + 0x3f => { #PH + Name => 'NoiseReduction', + PrintConv => \%offOn, + }, + 0x40 => { #20 + Name => 'EyeStartAF', + PrintConv => \%onOff, + }, + 0x41 => { #20 + Name => 'RedEyeReduction', + PrintConv => \%offOn, + }, + 0x42 => { #20 + Name => 'FlashDefault', + PrintConv => { + 0 => 'Auto', + 1 => 'Fill Flash', + }, + }, + 0x43 => { #20 + Name => 'AutoBracketOrder', + PrintConv => { + 0 => '0 - +', + 1 => '- 0 +', + }, + }, + 0x44 => { #20 + Name => 'FocusHoldButton', + PrintConv => { + 0 => 'Focus Hold', + 1 => 'DOF Preview', + }, + }, + 0x45 => { #20 + Name => 'AELButton', + PrintConv => { + 0 => 'Hold', + 1 => 'Toggle', + 2 => 'Spot Hold', + 3 => 'Spot Toggle', + }, + }, + 0x46 => { #20 + Name => 'ControlDialSet', + PrintConv => { + 0 => 'Shutter Speed', + 1 => 'Aperture', + }, + }, + 0x47 => { #20 + Name => 'ExposureCompensationMode', + PrintConv => { + 0 => 'Ambient and Flash', + 1 => 'Ambient Only', + }, + }, + 0x48 => { #20 + Name => 'AFAssist', + PrintConv => \%onOff, + }, + 0x49 => { #20 + Name => 'CardShutterLock', + PrintConv => \%onOff, + }, + 0x4a => { #20 + Name => 'LensShutterLock', + PrintConv => \%onOff, + }, + 0x4b => { #20 + Name => 'AFAreaIllumination', + PrintConv => { + 0 => '0.3 s', + 1 => '0.6 s', + 2 => 'Off', + }, + }, + 0x4c => { #20 + Name => 'MonitorDisplayOff', + PrintConv => { + 0 => 'Automatic', + 1 => 'Manual', + }, + }, + 0x4d => { #20 + Name => 'RecordDisplay', + PrintConv => { + 0 => 'Auto Rotate', + 1 => 'Horizontal', + }, + }, + 0x4e => { #20 + Name => 'PlayDisplay', + PrintConv => { + 0 => 'Auto Rotate', + 1 => 'Manual Rotate', + }, + }, + 0x50 => { #20 + Name => 'ExposureIndicator', + SeparateTable => 'ExposureIndicator', + PrintConv => \%exposureIndicator, + }, + 0x51 => { #20 + Name => 'AELExposureIndicator', + Notes => 'also indicates exposure for next shot when bracketing', + SeparateTable => 'ExposureIndicator', + PrintConv => \%exposureIndicator, + }, + 0x52 => { #20 + Name => 'ExposureBracketingIndicatorLast', + Notes => 'indicator for last shot when bracketing', + SeparateTable => 'ExposureIndicator', + PrintConv => \%exposureIndicator, + }, + 0x53 => { #20 + Name => 'MeteringOffScaleIndicator', + Notes => 'two flashing triangles when under or over metering scale', + PrintConv => { + 0 => 'Within Range', + 1 => 'Under/Over Range', + 255 => 'Out of Range', + }, + }, + 0x54 => { #20 + Name => 'FlashExposureIndicator', + SeparateTable => 'ExposureIndicator', + PrintConv => \%exposureIndicator, + }, + 0x55 => { #20 + Name => 'FlashExposureIndicatorNext', + Notes => 'indicator for next shot when bracketing', + SeparateTable => 'ExposureIndicator', + PrintConv => \%exposureIndicator, + }, + 0x56 => { #20 + Name => 'FlashExposureIndicatorLast', + Notes => 'indicator for last shot when bracketing', + SeparateTable => 'ExposureIndicator', + PrintConv => \%exposureIndicator, + }, + 0x58 => { #20 + Name => 'FocusModeSwitch', + PrintConv => { + 0 => 'AF', + 1 => 'MF', + }, + }, + 0x59 => { #20 + Name => 'FlashType', + PrintConv => { + 0 => 'Off', + 1 => 'Built-in', # (also when built-in flash is a trigger in wireless mode) + 2 => 'External', + }, + }, + 0x5a => { #15 + Name => 'Rotation', + PrintConv => { + 0 => 'Horizontal (Normal)', + 1 => 'Rotate 270 CW', + 2 => 'Rotate 90 CW', + }, + }, + 0x5b => { #20 + Name => 'AELock', + PrintConv => \%offOn, + }, + 0x57 => { #15 + Name => 'ImageStabilization', + PrintConv => \%offOn, + }, + 0x5e => { #15 + Name => 'ColorTemperature', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + }, + 0x5f => { #20 + Name => 'ColorCompensationFilter', + Format => 'int16s', + Notes => 'ranges from -2 for green to +2 for magenta', + }, + 0x60 => { #20 + Name => 'BatteryState', + PrintConv => { + 3 => 'Very Low', + 4 => 'Low', + 5 => 'Half Full', + 6 => 'Sufficient Power Remaining', + }, + }, +); + +# white balance information stored by the Sony DSLR-A100 (ref 20) +%Image::ExifTool::Minolta::WBInfoA100 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + NOTES => 'White balance information for the Sony DSLR-A100.', + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + PRIORITY => 0, + 0x0e => { + Name => 'DriveMode', + PrintConv => { + 0 => 'Self-timer 10 sec', + 1 => 'Continuous', + 2 => 'Single-frame Exposure Bracketing', + 3 => 'Continuous Exposure Bracketing', + 4 => 'Self-Timer 2 sec', + 5 => 'Single Frame', + 8 => 'White Balance Bracketing Low', + 9 => 'White Balance Bracketing High', + }, + }, + 0x10 => { + Name => 'Rotation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 270 CW', + 2 => 'Rotate 90 CW', + }, + }, + 0x14 => { + Name => 'ImageStabilizationSetting', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x15 => { + Name => 'DynamicRangeOptimizerMode', + PrintConv => { + 0 => 'Off', + 1 => 'Standard', + 2 => 'Advanced', + }, + }, + 0x2a => { + Name => 'ExposureCompensationMode', + PrintConv => { + 0 => 'Ambient and Flash', + 1 => 'Ambient Only', + }, + }, + 0x2b => 'WBBracketShotNumber', + 0x2c => { + Name => 'WhiteBalanceBracketing', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'High', + }, + }, + 0x2d => 'ExposureBracketShotNumber', + 0x31 => { + Name => 'FlashFunction', + Format => 'int16u', + PrintHex => 1, + PrintConv => { + 0x0000 => 'No flash', + 0x0300 => 'Built-in flash', + # (the following refers to an external flash) + 0x1205 => 'Manual', + 0x120e => 'Strobe', + #0x122e => ? + 0x128e => 'Fill flash, Pre-flash TTL', + 0x12ae => 'Bounce flash', + 0x140e => 'Rear sync, ADI', + 0x148e => 'Fill flash, ADI', + 0x1580 => 'Wireless', + # 0x17ae => ? + 0x178e => 'HSS', + }, + }, + 0x34 => { + Name => 'ExposureMode', + Format => 'int16u', + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 0x0000 => 'Program', + 0x0001 => 'Aperture Priority', + 0x0002 => 'Shutter Priority', + 0x0003 => 'Manual', + 0x0004 => 'Auto', + 0x0005 => 'Program Shift A', + 0x0006 => 'Program Shift S', + 0x1013 => 'Portrait', + 0x1023 => 'Sports', + 0x1033 => 'Sunset', + 0x1043 => 'Night View/Portrait', + 0x1053 => 'Landscape', + 0x1083 => 'Macro', + }, + }, + 0x36 => { + Name => 'ColorMode', + Format => 'int16u', + PrintConv => { + 0x00 => 'Standard', + 0x01 => 'Vivid', + 0x02 => 'Portrait', + 0x03 => 'Landscape', + 0x04 => 'Sunset', + 0x05 => 'Night View', + 0x07 => 'B&W', + 0x08 => 'Adobe RGB', + }, + }, + 0x38 => { + Name => 'AverageLV', + Format => 'int16u', + Notes => 'arithmetic mean of the readings from the 40 honeycomb segments', + ValueConv => '($val-106)/8', + ValueConvInv => '$val * 8 + 106', + }, + # 0x3a - int16u: Approx FocusDistance in metres (0x0f50=inf) + 0x3c => { + Name => 'FrameNumber', + # Numbers > 1 appear in continuous and continuous bracketing drive modes, + # as well as WB bracketing. + }, + 0x96 => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + 0xae => { Name => 'WB_GBRGLevels', Format => 'int16u[4]' }, + 0xc0 => { + Name => 'WB_RedLevelsTungsten', + Notes => '7 values for adjustments of -3 through +3', + Format => 'int16u[7]', + }, + 0xce => { Name => 'WB_BlueLevelsTungsten', Format => 'int16u[7]' }, + 0xdc => { Name => 'WB_RedLevelsDaylight', Format => 'int16u[7]' }, + 0xea => { Name => 'WB_BlueLevelsDaylight', Format => 'int16u[7]' }, + 0xf8 => { Name => 'WB_RedLevelsCloudy', Format => 'int16u[7]' }, + 0x106 => { Name => 'WB_BlueLevelsCloudy', Format => 'int16u[7]' }, + 0x114 => { Name => 'WB_RedLevelsFlash', Format => 'int16u[7]' }, + 0x122 => { Name => 'WB_BlueLevelsFlash', Format => 'int16u[7]' }, + 0x14c => { + Name => 'WB_RedLevelsFluorescent', + Format => 'int16u[7]', + Notes => q{ + white balance red presets for fluorescent -2 through +4: -2=Fluorescent, + -1=WhiteFluorescent, 0=CoolWhiteFluorescent, +1=DayWhiteFluorescent and + +3=DaylightFluorescent + }, + }, + 0x15a => { Name => 'WB_BlueLevelsFluorescent', Format => 'int16u[7]' }, + 0x168 => { Name => 'WB_RedLevelsShade', Format => 'int16u[7]' }, + 0x176 => { Name => 'WB_BlueLevelsShade', Format => 'int16u[7]' }, + 0x188 => { Name => 'WB_RedLevel6500K', Format => 'int16u' }, + 0x18a => { Name => 'WB_BlueLevel6500K', Format => 'int16u' }, + 0x18c => { Name => 'WB_RedLevelCustom', Format => 'int16u' }, + 0x18e => { Name => 'WB_BlueLevelCustom', Format => 'int16u' }, + 0x198 => { Name => 'WB_RedLevel3500K', Format => 'int16u' }, + 0x19a => { Name => 'WB_BlueLevel3500K', Format => 'int16u' }, + 0x1be => { + Name => 'WB_RedLevelsKelvin', + Format => 'int16u[75]', + Notes => 'values for 2500-9900 K, in increments of 100 K', + }, + 0x254 => { Name => 'WB_BlueLevelsKelvin', Format => 'int16u[75]' }, + 0x304 => { Name => 'WB_RBLevelsFlash', Format => 'int16u[2]' }, + 0x308 => { Name => 'WB_RBLevelsCoolWhiteF', Format => 'int16u[2]' }, + 0x3e8 => { Name => 'WB_RBLevelsTungsten', Format => 'int16u[2]' }, + 0x3ec => { Name => 'WB_RBLevelsDaylight', Format => 'int16u[2]' }, + 0x3f0 => { Name => 'WB_RBLevelsCloudy', Format => 'int16u[2]' }, + 0x3f4 => { Name => 'WB_RBLevelsFlash', Format => 'int16u[2]' }, + 0x3fc => { Name => 'WB_RedLevelsFluorescent', Format => 'int16u[7]' }, + 0x40a => { Name => 'WB_BlueLevelsFluorescent', Format => 'int16u[7]' }, + 0x418 => { Name => 'WB_RBLevelsShade', Format => 'int16u[2]' }, + 0x420 => { Name => 'WB_RBLevels6500K', Format => 'int16u[2]' }, + 0x424 => { Name => 'WB_RBLevelsCustom', Format => 'int16u[2]' }, + 0x430 => { Name => 'WB_RBLevels3500K', Format => 'int16u[2]' }, + 0x528 => { Name => 'WB_RBLevelsDaylight', Format => 'int16u[2]' }, + 0x546 => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + 0x628 => { + Name => 'AEMeteringSegments', + Format => 'int8u[40]', + Notes => q{ + metering values from the 40 honeycomb segments, converted to LV. The first + value is for the outer cell, then the values are given row by row, from top + to bottom, with each row scanned left-to-right. The 21st value is the + middle cell, which gives the spot metering + }, + ValueConv => sub { join ' ', map( { ($_ - 106) / 8 } split(' ',$_[0]) ) }, + ValueConvInv => sub { join ' ', map( { int($_ * 8 + 106.5) } split(' ',$_[0]) ) }, + }, + 0x690 => { + Name => 'MeasuredLV', + Notes => 'measured light value based on MeteringMode', + ValueConv => '($val-106)/8', + ValueConvInv => '$val * 8 + 106', + }, + 0x691 => { + Name => 'BrightnessValue', + ValueConv => '($val-106)/8', + ValueConvInv => '$val * 8 + 106', + }, + # 0x87f - int8u: 33mm Equivalent magnification (FocusDistance = (1.5 * $val + 1) * FocalLength) (255=inf) + 0x104c => { # (9600 bytes: 4 sets of 40x30 int16u values in the range 0-8191) + Name => 'TiffMeteringImage', + Format => 'undef[9600]', + Notes => q{ + 13-bit RBGG (?) 40x30 pixels, presumably metering info, converted to a 16-bit + TIFF image; + }, + ValueConv => sub { + my ($val, $et) = @_; + return undef unless length $val >= 9600; + return \ "Binary data 7404 bytes" unless $et->Options('Binary'); + my @dat = unpack('n*', $val); # for Big-endian + # TIFF header for a 16-bit RGB 10dpi 40x30 image + $val = Image::ExifTool::MakeTiffHeader(40,30,3,16,10); + # re-order data to RGB pixels + my ($i, @val); + for ($i=0; $i<40*30; ++$i) { + # data is 13-bit (max 8191), shift left to fill 16 bits + # (typically, this gives a very dark image since the data should + # really be anti-logged to convert from EV to perceived brightness) +# push @val, $dat[$i]<<3, $dat[$i+2400]<<3, $dat[$i+1200]<<3; + push @val, int(5041.1*log($dat[$i]+1)/log(2)), int(5041.1*log($dat[$i+2400]+1)/log(2)), int(5041.1*log($dat[$i+1200]+1)/log(2)); + } + $val .= pack('v*', @val); # add TIFF strip data + return \$val; + }, + }, + 0x49b8 => { + Name => 'ExposureTime', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x49ba => { + Name => 'ISO', + ValueConv => '2 ** (($val-48)/8) * 100', + ValueConvInv => '48 + 8*log($val/100)/log(2)', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 0x49bb => { # (https://exiftool.org/forum/index.php/topic,3688.0.html) + # if this value is the 35mm equivalent magnification, then the formula could + # be (1.5 * 2**($val/16-5)+1) * FocalLength, but this tends to underestimate + # distance by about 18% (ref 20) (255=inf) + Name => 'FocusDistance', + ValueConv => '2**(($val-126)/16)', + ValueConvInv => 'log($val)/log(2)*16+126', + PrintConv => '$val > 266 ? "inf" : sprintf("%.2f m", $val)', + PrintConvInv => '$val=~s/ ?m//; $val=~/inf/i ? 267 : $val', + }, + 0x49bd => { + Name => 'LensType', + Format => 'int16uRev', + SeparateTable => 1, + ValueConvInv => 'int($val)', # (must truncate decimal part) + PrintConv => \%minoltaLensTypes, + PrintInt => 1, + }, + 0x49c0 => { + Name => 'ExposureCompensation', # (in exposure bracketing, this is the actual value used) + Format => 'int8s', + ValueConv => '$val / 8', + ValueConvInv => '$val * 8', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x49c1 => { + Name => 'FlashExposureComp', + Description => 'Flash Exposure Compensation', + Format => 'int8s', + ValueConv => '$val / 8', + ValueConvInv => '$val * 8', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x49c2 => { + Name => 'ImageStabilization', + PrintConv => \%offOn, + }, + 0x49c3 => { + Name => 'BrightnessValue', + ValueConv => '($val-106)/8', + ValueConvInv => '$val * 8 + 106', + }, + 0x49c5 => { + Name => 'MaxAperture', + ValueConv => '2 ** (($val-8)/16)', + ValueConvInv => '8 + 16*log($val)/log(2)', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + # 0x49c6 - gives focal length using same formula as 0x49bb + 0x49c7 => { + Name => 'FNumber', + ValueConv => '2 ** (($val-8)/16)', + ValueConvInv => '8 + 16*log($val)/log(2)', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x49dc => { + Name => 'InternalSerialNumber', + Format => 'string[12]', + }, +); + +# tags in Konica Minolta MOV videos (ref PH) +# (similar information in Kodak,Minolta,Nikon,Olympus,Pentax and Sanyo videos) +%Image::ExifTool::Minolta::MOV1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => q{ + This information is found in MOV videos from some Konica Minolta models such + as the DiMage Z10 and X50. + }, + 0 => { + Name => 'Make', + Format => 'string[32]', + }, + 0x20 => { + Name => 'ModelType', + Format => 'string[8]', + }, + # (01 00 at offset 0x28) + 0x2e => { + 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 => { + Name => 'ExposureCompensation', + Format => 'rational64s', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + # 0x4c => 'WhiteBalance', ? + 0x50 => { + Name => 'FocalLength', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + }, +); + +# tags in Minolta MOV videos (ref PH) +# (similar information in Kodak,Minolta,Nikon,Olympus,Pentax and Sanyo videos) +%Image::ExifTool::Minolta::MOV2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => q{ + This information is found in MOV videos from some Minolta models such as the + DiMAGE X and Xt. + }, + 0 => { + Name => 'Make', + Format => 'string[32]', + }, + 0x18 => { + Name => 'ModelType', + Format => 'string[8]', + }, + # (01 00 at offset 0x20) + 0x26 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val ? 10 / $val : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x2a => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x32 => { + Name => 'ExposureCompensation', + Format => 'rational64s', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + # 0x44 => 'WhiteBalance', ? + 0x48 => { + Name => 'FocalLength', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + }, +); + +# more tags in Minolta MOV videos (ref PH) +%Image::ExifTool::Minolta::MMA = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + This information is found in MOV videos from Minolta models such as the + DiMAGE A2, S414 and 7Hi. + }, + 0 => { + Name => 'Make', + Format => 'string[20]', + }, + 20 => { + Name => 'SoftwareVersion', + Format => 'string[16]', + }, +); + +# basic Minolta white balance lookup +my %minoltaWhiteBalance = ( + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Cloudy', + 3 => 'Tungsten', + 5 => 'Custom', + 7 => 'Fluorescent', + 8 => 'Fluorescent 2', + 11 => 'Custom 2', + 12 => 'Custom 3', + # the following come from tests with the A2 (ref 2) + 0x0800000 => 'Auto', + 0x1800000 => 'Daylight', + 0x2800000 => 'Cloudy', + 0x3800000 => 'Tungsten', + 0x4800000 => 'Flash', + 0x5800000 => 'Fluorescent', + 0x6800000 => 'Shade', + 0x7800000 => 'Custom1', + 0x8800000 => 'Custom2', + 0x9800000 => 'Custom3', +); + +#------------------------------------------------------------------------------ +# PrintConv for Minolta white balance +sub ConvertWhiteBalance($) +{ + my $val = shift; + my $printConv = $minoltaWhiteBalance{$val}; + unless (defined $printConv) { + if ($val & 0xffff0000) { + # the A2 values can be shifted by +- 3 settings, where + # each setting adds or subtracts 0x0010000 (ref 2) + my $type = ($val & 0xff000000) + 0x800000; + if ($minoltaWhiteBalance{$type}) { + $printConv = $minoltaWhiteBalance{$type} . + sprintf("%+.8g", ($val - $type) / 0x10000); + } else { + $printConv = sprintf("Unknown (0x%x)", $val); + } + } else { + $printConv = sprintf("Unknown ($val)"); + } + } + return $printConv; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Minolta - Minolta 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 +Minolta and Konica-Minolta maker notes in EXIF information, and to read +and write Minolta RAW (MRW) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.dalibor.cz/minolta/makernote.htm> + +=item L<http://www.cybercom.net/~dcoffin/dcraw/> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jay Al-Saadi, Niels Kristian Bech Jensen, Shingo Noguchi, Pedro +Corte-Real, Jeffery Small, Jens Duttke, Thomas Kassner, Mladen Sever, Olaf +Ulrich, Lukasz Stelmach, Igal Milchtaich, Jos Roost and Michael Reitinger +for the information they provided, and for everyone who helped with the +LensType list. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Minolta Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/MinoltaRaw.pm b/ExifTool/lib/Image/ExifTool/MinoltaRaw.pm new file mode 100644 index 0000000..7fa04e6 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/MinoltaRaw.pm @@ -0,0 +1,537 @@ +#------------------------------------------------------------------------------ +# File: MinoltaRaw.pm +# +# Description: Read/write Konica-Minolta RAW (MRW) meta information +# +# Revisions: 03/11/2006 - P. Harvey Split out from Minolta.pm +# +# References: 1) http://www.cybercom.net/~dcoffin/dcraw/ +# 2) http://www.chauveau-central.net/mrw-format/ +# 3) Igal Milchtaich private communication (A100) +#------------------------------------------------------------------------------ + +package Image::ExifTool::MinoltaRaw; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Minolta; + +$VERSION = '1.19'; + +sub ProcessMRW($$;$); +sub WriteMRW($$;$); + +# Minolta MRW tags +%Image::ExifTool::MinoltaRaw::Main = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::MinoltaRaw::ProcessMRW, + WRITE_PROC => \&Image::ExifTool::MinoltaRaw::WriteMRW, + NOTES => 'These tags are used in Minolta RAW format (MRW) images.', + "\0TTW" => { # TIFF Tags + Name => 'MinoltaTTW', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + # this EXIF information starts with a TIFF header + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + }, + }, + "\0PRD" => { # Raw Picture Dimensions + Name => 'MinoltaPRD', + SubDirectory => { TagTable => 'Image::ExifTool::MinoltaRaw::PRD' }, + }, + "\0WBG" => { # White Balance Gains + Name => 'MinoltaWBG', + SubDirectory => { TagTable => 'Image::ExifTool::MinoltaRaw::WBG' }, + }, + "\0RIF" => { # Requested Image Format + Name => 'MinoltaRIF', + SubDirectory => { TagTable => 'Image::ExifTool::MinoltaRaw::RIF' }, + }, + # "\0CSA" is padding +); + +# Minolta MRW PRD information (ref 2) +%Image::ExifTool::MinoltaRaw::PRD = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0 ], + FIRST_ENTRY => 0, + 0 => { + Name => 'FirmwareID', + Format => 'string[8]', + RawConv => '$$self{MinoltaPRD} = 1 if $$self{FILE_TYPE} eq "MRW"; $val', # used in decoding RIF info + }, + 8 => { + Name => 'SensorHeight', + Format => 'int16u', + }, + 10 => { + Name => 'SensorWidth', + Format => 'int16u', + }, + 12 => { + Name => 'ImageHeight', + Format => 'int16u', + }, + 14 => { + Name => 'ImageWidth', + Format => 'int16u', + }, + 16 => { + Name => 'RawDepth', + Format => 'int8u', + }, + 17 => { + Name => 'BitDepth', + Format => 'int8u', + }, + 18 => { + Name => 'StorageMethod', + Format => 'int8u', + PrintConv => { + 82 => 'Padded', + 89 => 'Linear', + }, + }, + 23 => { + Name => 'BayerPattern', + Format => 'int8u', + PrintConv => { + # 0 - seen in some Sony A850 ARW images + 1 => 'RGGB', + 4 => 'GBRG', + }, + }, +); + +# Minolta MRW WBG information (ref 2) +%Image::ExifTool::MinoltaRaw::WBG = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + 0 => { + Name => 'WBScale', + Format => 'int8u[4]', + }, + 4 => [ + { + Condition => '$$self{Model} =~ /DiMAGE A200\b/', + Name => 'WB_GBRGLevels', + Format => 'int16u[4]', + Notes => 'DiMAGE A200', + }, + { + Name => 'WB_RGGBLevels', + Format => 'int16u[4]', + Notes => 'other models', + }, + ], +); + +# Minolta MRW RIF information (ref 2) +%Image::ExifTool::MinoltaRaw::RIF = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FIRST_ENTRY => 0, + 1 => { + Name => 'Saturation', + Format => 'int8s', + }, + 2 => { + Name => 'Contrast', + Format => 'int8s', + }, + 3 => { + Name => 'Sharpness', + Format => 'int8s', + }, + 4 => { + Name => 'WBMode', + PrintConv => 'Image::ExifTool::MinoltaRaw::ConvertWBMode($val)', + }, + 5 => { + Name => 'ProgramMode', + PrintConv => { + 0 => 'None', + 1 => 'Portrait', + 2 => 'Text', + 3 => 'Night Portrait', + 4 => 'Sunset', + 5 => 'Sports', + # have seen these values in Sony ARW images: - PH + # 7, 128, 129, 160 + }, + }, + 6 => { + Name => 'ISOSetting', + RawConv => '$val == 255 ? undef : $val', + PrintConv => { #3 + 0 => 'Auto', + 48 => 100, + 56 => 200, + 64 => 400, + 72 => 800, + 80 => 1600, + 174 => '80 (Zone Matching Low)', + 184 => '200 (Zone Matching High)', + OTHER => sub { + my ($val, $inv) = @_; + return int(2 ** (($val-48)/8) * 100 + 0.5) unless $inv; + # (must check for zero below in inverse conversion) + return 48 + 8*log($val/100)/log(2) if Image::ExifTool::IsFloat($val) and $val != 0; + return undef; + }, + }, + #ValueConv => '2 ** (($val-48)/8) * 100', + #ValueConvInv => '48 + 8*log($val/100)/log(2)', + #PrintConv => 'int($val + 0.5)', + #PrintConvInv => '$val', + }, + 7 => [ + { + Name => 'ColorMode', + Condition => '$$self{Make} !~ /^SONY/', + Priority => 0, + Writable => 'int32u', + PrintConv => \%Image::ExifTool::Minolta::minoltaColorMode, + }, + { #3 + Name => 'ColorMode', + Condition => '$$self{Model} eq "DSLR-A100"', + Writable => 'int32u', + Notes => 'Sony A100', + Priority => 0, + PrintHex => 1, + PrintConv => \%Image::ExifTool::Minolta::sonyColorMode, + }, + ], + # NOTE: WB_RBLevels up to Custom also apply to Minolta models which write PRD info (ref IB) + 8 => { #3 + Name => 'WB_RBLevelsTungsten', + Condition => '$$self{Model} eq "DSLR-A100" or $$self{MinoltaPRD}', + Format => 'int16u[2]', + Notes => 'these WB_RBLevels currently decoded only for the Sony A100', + }, + 12 => { #3 + Name => 'WB_RBLevelsDaylight', + Condition => '$$self{Model} eq "DSLR-A100" or $$self{MinoltaPRD}', + Format => 'int16u[2]', + }, + 16 => { #3 + Name => 'WB_RBLevelsCloudy', + Condition => '$$self{Model} eq "DSLR-A100" or $$self{MinoltaPRD}', + Format => 'int16u[2]', + }, + 20 => { #3 + Name => 'WB_RBLevelsCoolWhiteF', + Condition => '$$self{Model} eq "DSLR-A100" or $$self{MinoltaPRD}', + Format => 'int16u[2]', + }, + 24 => { #3 + Name => 'WB_RBLevelsFlash', + Condition => '$$self{Model} eq "DSLR-A100" or $$self{MinoltaPRD}', + Format => 'int16u[2]', + }, + 28 => { #3 + Name => 'WB_RBLevelsCustom', #IB + Condition => '$$self{Model} eq "DSLR-A100" or $$self{MinoltaPRD}', + Format => 'int16u[2]', + }, + 32 => { #3 + Name => 'WB_RBLevelsShade', + Condition => '$$self{Model} eq "DSLR-A100"', + Format => 'int16u[2]', + }, + 36 => { #3 + Name => 'WB_RBLevelsDaylightF', + Condition => '$$self{Model} eq "DSLR-A100"', + Format => 'int16u[2]', + }, + 40 => { #3 + Name => 'WB_RBLevelsDayWhiteF', + Condition => '$$self{Model} eq "DSLR-A100"', + Format => 'int16u[2]', + }, + 44 => { #3 + Name => 'WB_RBLevelsWhiteF', + Condition => '$$self{Model} eq "DSLR-A100"', + Format => 'int16u[2]', + }, + 56 => { + Name => 'ColorFilter', + Condition => '$$self{Make} !~ /^SONY/', + Format => 'int8s', + Notes => 'Minolta models', + }, + 57 => 'BWFilter', + 58 => { + Name => 'ZoneMatching', + Condition => '$$self{Make} !~ /^SONY/', + Priority => 0, + Notes => 'Minolta models', + PrintConv => { + 0 => 'ISO Setting Used', + 1 => 'High Key', + 2 => 'Low Key', + }, + }, + 59 => { + Name => 'Hue', + Format => 'int8s', + }, + 60 => { + Name => 'ColorTemperature', + Condition => '$$self{Make} !~ /^SONY/', + Notes => 'Minolta models', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + }, + 74 => { #3 + Name => 'ZoneMatching', + Condition => '$$self{Make} =~ /^SONY/', + Priority => 0, + Notes => 'Sony models', + PrintConv => { + 0 => 'ISO Setting Used', + 1 => 'High Key', + 2 => 'Low Key', + }, + }, + 76 => { #3 + Name => 'ColorTemperature', + Condition => '$$self{Model} eq "DSLR-A100"', + Notes => 'A100', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + PrintConv => '$val ? $val : "Auto"', + PrintConvInv => '$val=~/Auto/i ? 0 : $val', + }, + 77 => { #3 + Name => 'ColorFilter', + Condition => '$$self{Model} eq "DSLR-A100"', + Notes => 'A100', + }, + 78 => { #3 + Name => 'ColorTemperature', + Condition => '$$self{Model} =~ /^DSLR-A(200|700)$/', + Notes => 'A200 and A700', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + PrintConv => '$val ? $val : "Auto"', + PrintConvInv => '$val=~/Auto/i ? 0 : $val', + }, + 79 => { #3 + Name => 'ColorFilter', + Condition => '$$self{Model} =~ /^DSLR-A(200|700)$/', + Notes => 'A200 and A700', + }, + 80 => { #3 + Name => 'RawDataLength', + Condition => '$$self{Model} eq "DSLR-A100"', + Format => 'int32u', + Notes => 'A100', + Writable => 0, + }, +); + +#------------------------------------------------------------------------------ +# PrintConv for WBMode +sub ConvertWBMode($) +{ + my $val = shift; + my %mrwWB = ( + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Cloudy', + 3 => 'Tungsten', + 4 => 'Flash/Fluorescent', + 5 => 'Fluorescent', + 6 => 'Shade', + 7 => 'User 1', + 8 => 'User 2', + 9 => 'User 3', + 10 => 'Temperature', + ); + my $lo = $val & 0x0f; + my $wbstr = $mrwWB{$lo} || "Unknown ($lo)"; + my $hi = $val >> 4; + $wbstr .= ' (' . ($hi - 8) . ')' if $hi >= 6 and $hi <=12; + return $wbstr; +} + +#------------------------------------------------------------------------------ +# Write MRW directory (eg. in ARW images) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) optional tag table ref +# Returns: new MRW data or undef on error +sub WriteMRW($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + my $buff = ''; + $$dirInfo{OutFile} = \$buff; + ProcessMRW($et, $dirInfo, $tagTablePtr) > 0 or undef $buff; + return $buff; +} + +#------------------------------------------------------------------------------ +# Read or write Minolta MRW file +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) optional tag table ref +# Returns: 1 on success, 0 if this wasn't a valid MRW file, or -1 on write error +# Notes: File pointer must be set to start of MRW in RAF upon entry +sub ProcessMRW($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my ($data, $err, $outBuff); + + if ($$dirInfo{DataPt}) { + # make a RAF object for MRW information extracted from other file types + $raf = new File::RandomAccess($$dirInfo{DataPt}); + # MRW information in DNG images may not start at beginning of data block + $raf->Seek($$dirInfo{DirStart}, 0) if $$dirInfo{DirStart}; + } + $raf->Read($data,8) == 8 or return 0; + # "\0MRM" for big-endian (MRW images), and + # "\0MRI" for little-endian (MRWInfo in ARW images) + $data =~ /^\0MR([MI])/ or return 0; + my $hdr = "\0MR$1"; + SetByteOrder($1 . $1); + $et->SetFileType(); + $tagTablePtr = GetTagTable('Image::ExifTool::MinoltaRaw::Main'); + if ($outfile) { + $et->InitWriteDirs('TIFF'); # use same write dirs as TIFF + $outBuff = ''; + } + my $pos = $raf->Tell(); + my $offset = Get32u(\$data, 4) + $pos; + my $rtnVal = 1; + $verbose and printf $out " [MRW Data Offset: 0x%x]\n", $offset; + # loop through MRW segments (ref 1) + while ($pos < $offset) { + $raf->Read($data,8) == 8 or $err = 1, last; + $pos += 8; + my $tag = substr($data, 0, 4); + my $len = Get32u(\$data, 4); + if ($verbose) { + print $out "MRW ",$et->Printable($tag)," segment ($len bytes):\n"; + if ($verbose > 2) { + $raf->Read($data,$len) == $len and $raf->Seek($pos,0) or $err = 1, last; + $et->VerboseDump(\$data); + } + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo and $$tagInfo{SubDirectory}) { + my $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + my $buff; + # save shift for values stored with wrong base offset + $$et{MRW_WrongBase} = -($raf->Tell()); + $raf->Read($buff, $len) == $len or $err = 1, last; + my %subdirInfo = ( + DataPt => \$buff, + DataLen => $len, + DataPos => $pos, + DirStart => 0, + DirLen => $len, + DirName => $$tagInfo{Name}, + Parent => 'MRW', + NoTiffEnd => 1, # no end-of-TIFF check + ); + if ($outfile) { + my $writeProc = $tagInfo->{SubDirectory}->{WriteProc}; + my $val = $et->WriteDirectory(\%subdirInfo, $subTable, $writeProc); + if (defined $val and length $val) { + # pad to an even 4 bytes (can't hurt, and it seems to be the standard) + $val .= "\0" x (4 - (length($val) & 0x03)) if length($val) & 0x03; + $outBuff .= $tag . Set32u(length $val) . $val; + } elsif (not defined $val) { + $outBuff .= $data . $buff; # copy over original information + } + } else { + my $processProc = $tagInfo->{SubDirectory}->{ProcessProc}; + $et->ProcessDirectory(\%subdirInfo, $subTable, $processProc); + } + } elsif ($outfile) { + # add this segment to the output buffer + my $buff; + $raf->Read($buff, $len) == $len or $err = 1, last; + $outBuff .= $data . $buff; + } else { + # skip this segment + $raf->Seek($pos+$len, 0) or $err = 1, last; + } + $pos += $len; + } + $pos == $offset or $err = 1; # meta information length check + + if ($outfile) { + # write the file header then the buffered meta information + Write($outfile, $hdr, Set32u(length $outBuff), $outBuff) or $rtnVal = -1; + # copy over image data + while ($raf->Read($outBuff, 65536)) { + Write($outfile, $outBuff) or $rtnVal = -1; + } + # Sony IDC utility corrupts MRWInfo when writing ARW images, + # so make this a minor error for these images + $err and $et->Error("MRW format error", $$et{TIFF_TYPE} eq 'ARW'); + } else { + $err and $et->Warn("MRW format error"); + $et->ImageDataHash($raf, undef, 'raw') unless $$et{A100DataOffset}; + } + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::MinoltaRaw - Read/write Konica-Minolta RAW (MRW) information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read and +write Konica-Minolta RAW (MRW) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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/> + +=item L<http://www.chauveau-central.net/mrw-format/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/MinoltaRaw Tags>, +L<Image::ExifTool::Minolta(3pm)|Image::ExifTool::Minolta>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Motorola.pm b/ExifTool/lib/Image/ExifTool/Motorola.pm new file mode 100644 index 0000000..466115e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Motorola.pm @@ -0,0 +1,178 @@ +#------------------------------------------------------------------------------ +# File: Motorola.pm +# +# Description: Read Motorola meta information +# +# Revisions: 2015/10/29 - P. Harvey Created +# +# References: 1) Neal Krawetz private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::Motorola; + +use strict; +use vars qw($VERSION); +use Image::ExifTool::Exif; + +$VERSION = '1.02'; + +# Motorola makernotes tags (ref PH) +%Image::ExifTool::Motorola::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + WRITABLE => 1, + # 0x54e0 - int8u: 1 + # 0x54f0 - float + 0x5500 => { Name => 'BuildNumber', Writable => 'string' }, #1 (eg. 'MPJ24.139-23.4') + 0x5501 => { Name => 'SerialNumber', Writable => 'string' }, #1 (eg. 'ZY2238TJ4V') + # 0x5502 - int8u + # 0x5503 - int8u + # 0x5510 - int8u: 0,1 + # 0x5511 - int32u (3 of these tags for some cameras) + # 0x5512 - int32u + # 0x5520 - int16s + # 0x5530 - string: 'continuous-picture','auto','continuous-video' + # 0x5540 - int8u: 95 + # 0x5550 - int8u: 85,90 + # 0x5560 - string: 'auto' + # 0x5570 - string: 'auto','auto-hdr' + # 0x5580 - int32s: 9000,14949,15000,24000 + # 0x5590 - int32s: 30000 + # 0x55a0 - int32s: 960,1280,1440 + # 0x55b0 - int32s: 720,960,1080 + # 0x55c0 - int32s: 30 + # 0x55d0 - string: 'yuv420sp' + # 0x55e0-0x55e9 - int16u + # 0x55f0 - int8u: 10 + # 0x55f1 - int8u: 13 + # 0x55f2-0x55f5 - int16u[130] + # 0x5600 - int8u: 0,16 + # 0x5601 - int16u[16] + # 0x5602 - int32u[3,50] + # 0x5603 - int32u: 0 + # 0x6400 - string: 'AUTO','ON','OFF' + # 0x6401 - string: 'HDR' + # 0x6410 - string: 'NO','YES' + # 0x6420 - int32s for some models: 0 (only exists in HDR images?) + 0x6420 => { #forum13731 + Condition => '$format eq "string"', + Name => 'CustomRendered', + Writable => 'string', + }, + # 0x6430 - float + # 0x6431 - int8u: 0,1 + # 0x6432 - int8u: 0,79,100 + # 0x6433 - int8u: 0,1 + # 0x6434 - int8u: 0,65,100 + # 0x6435 - int8u: 1,6,24 + # 0x6436 - int8u: 55,60 + # 0x6437 - int8u: 30,35,40 + # 0x6438 - int8u: 24,40 + # 0x6439 - int8u: 15,50 + # 0x643a - int8u: 0,20 + # 0x643b - string: '2,8,-4','2,10,-4','' + # 0x643c - int32s + # 0x643d - float + # 0x6440 - int8u[N]: 0's and 1's + # 0x6441 - int8u[N] + # 0x6442,0x6443 - int8u[N]: 0's and 1's + # 0x644d - string: 'YES' + # 0x644f - float + # 0x6450 - float + # 0x6451 - float: 0.699999988079071 + # 0x6452 - int8u: 1 + # 0x6470 - string: 'AUTO' + # 0x6471 - int8u: 1 + # 0x6473 - int8u: 24 + # 0x6474 - int8u: 10 + # 0x6475 - int32u[24] + # 0x6476 - int32u: 2 + # 0x6490 - int8u: 0 + # 0x64c0 - int32s: 0,2 + # 0x64c1 - int32u: 1,4,64 + # 0x64c2,0x64c3 - int32s + # 0x64c4 - int32s + # 0x64c5 - int32u + 0x64d0 => { Name => 'DriveMode', Writable => 'string' }, #forum13731 + # 0x6500 - int8u: 1 + # 0x6501 - string: 'Luma-Chroma Plane','Chroma only' or int8u: 0 + # 0x6502 - string: 'Luma-Chroma Plane','Chroma only','' or int8u: 1,255 + # 0x6504 - int32s + # 0x6530-0x6535 - int32s + # 0x6600-0x6605 - int8u + # 0x6606 - string: 'D50','TL84','5000' - illuminant? color temperature? + # 0x6607 - string: 'D50g','D65','3000' - illuminant? color temperature? + # 0x6608 - string: 'A' + # 0x6609-0x660e - float + # 0x660f - int12u + # 0x6612-0x661b - int16u + # 0x661d - int16u + # 0x661e-0x6635 - float + # 0x6637 - int8u[212] + # 0x6640,0x6641 - int8u + # 0x6642-0x6649 - int16u + # 0x664e - in8u + # 0x664f-0x6652 - int16u + # 0x6653 - string: 'QC','AL' + # 0x6654-0x6656 - int16u + # 0x665d - int8u: 0 + 0x665e => { Name => 'Sensor', Writable => 'string' }, # (eg. 'BACK,IMX230') + # 0x6700 - string: eg. 'eac040d0','333e1721','001b7b3a','000000000005040f' + # 0x6701 - string: eg. '14048001',940140230','940140190' + # 0x6702 - string: '0L','1L','0S','3S','32','33,'42' + # 0x6703 - string: 'PR','SEG','LIG','LI','SHV','SH','SO' + # 0x6704 - string: 'DO','GX1','GZ0','GZ1','GX2','VI','VI1','VI2','GU','' + 0x6705 => { Name => 'ManufactureDate', Writable => 'string' }, # (NC, eg. '03Jun2015') + # 0x6706 - string: eg. '30454e4e','42%','01%','01','1','904c2ca2' + # 0x6707 - string: eg. '1','25a2ca16','002371e1','69' + # 0x6708-0x670c - string or int16u (string may be firmware revision) + # 0x6800 - int32u: 1,2 + # 0x6801,0x6802 - float + # 0x6803 - int16u + # 0x6804,0x6805 - float + # 0x6806 - int16u,int32s + # 0x6807 - int32s,int32u[3] + # 0x6808 - int32u,int32u[3] + # 0x6809,0x680a - float[3] + # 0x680d - int8u: 0 + # 0x680e - float: 0 + # 0x7000 - int8u: 0,2 + # 0x7001,0x7002 - int16s + # 0x7003-0x7005 - int16u + # 0x7100 - string: '0-7' + # 0x7101 - string: '4-7','0-7' + # 0x7102 - string: '0-3','' + # 0x7103,0x7104 - string: comma-separated lists of numbers +); + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Motorola - Read Motorola meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains the definitions to read meta information from Motorola +cell phone images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Motorola Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Nikon.pm b/ExifTool/lib/Image/ExifTool/Nikon.pm new file mode 100644 index 0000000..89473cd --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Nikon.pm @@ -0,0 +1,13343 @@ +#------------------------------------------------------------------------------ +# File: Nikon.pm +# +# Description: Nikon EXIF maker notes tags +# +# Revisions: 12/09/2003 - P. Harvey Created +# 05/17/2004 - P. Harvey Added information from Joseph Heled +# 09/21/2004 - P. Harvey Changed tag 2 to ISOUsed & added PrintConv +# 12/01/2004 - P. Harvey Added default PRINT_CONV +# 01/01/2005 - P. Harvey Decode preview image and preview IFD +# 03/35/2005 - T. Christiansen additions +# 05/10/2005 - P. Harvey Decode encrypted lens data +# [ongoing] - P. Harvey Constantly decoding new information +# +# References: 1) http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html +# 2) Joseph Heled private communication (tests with D70) +# 3) Thomas Walter private communication (tests with Coolpix 5400) +# 4) http://www.cybercom.net/~dcoffin/dcraw/ +# 5) Brian Ristuccia private communication (tests with D70) +# 6) Danek Duvall private communication (tests with D70) +# 7) Tom Christiansen private communication (tests with D70) +# 8) Robert Rottmerhusen private communication +# 9) http://members.aol.com/khancock/pilot/nbuddy/ +# 10) Werner Kober private communication (D2H, D2X, D100, D70, D200, D90) +# 11) http://www.rottmerhusen.com/objektives/lensid/thirdparty.html +# 12) http://libexif.sourceforge.net/internals/mnote-olympus-tag_8h-source.html +# 13) Roger Larsson private communication (tests with D200) +# 14) http://homepage3.nifty.com/kamisaka/makernote/makernote_nikon.htm (2007/09/15) +# 15) http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp +# 16) Jeffrey Friedl private communication (D200 with firmware update) +# 17) http://www.wohlberg.net/public/software/photo/nstiffexif/ +# and Brendt Wohlberg private communication +# 18) Anonymous user private communication (D70, D200, D2x) +# 19) Bruce Stevens private communication +# 20) Vladimir Sauta private communication (D80) +# 21) Gregor Dorlars private communication (D300) +# 22) Tanel Kuusk private communication +# 23) Alexandre Naaman private communication (D3) +# 24) Geert De Soete private communication +# 26) Bozi (http://www.cpanforum.com/posts/8983) +# 27) Jens Kriese private communication +# 28) Warren Hatch private communication (D3v2.00 with SB-800 and SB-900) +# 29) Anonymous contribution 2011/05/25 (D700, D7000) +# 30) https://exiftool.org/forum/index.php/topic,3833.30.html +# 31) Michael Relt private communication +# 32) Stefan https://exiftool.org/forum/index.php/topic,4494.0.html +# 34) Stewart Bennett private communication (D4S, D810) +# 35) David Puschel private communication +# 36) Hayo Baann (forum10207) +# 37) Tom Lachecki, private communication +# 38) https://github.com/exiftool/exiftool/pull/40 (and forum10893) +# 39) Stefan Grube private communication (Z9) +# IB) Iliah Borg private communication (LibRaw) +# JD) Jens Duttke private communication +# NJ) Niels Kristian Bech Jensen private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::Nikon; + +use strict; +use vars qw($VERSION %nikonLensIDs %nikonTextEncoding); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::NikonCustom qw(%buttonsZ9); +use Image::ExifTool::Exif; +use Image::ExifTool::GPS; +use Image::ExifTool::XMP; + +$VERSION = '4.25'; + +sub LensIDConv($$$); +sub ProcessNikonAVI($$$); +sub ProcessNikonMOV($$$); +sub ProcessNikonEncrypted($$$); +sub FormatString($); +sub ProcessNikonCaptureEditVersions($$$); +sub PrintAFPoints($$); +sub PrintAFPointsInv($$); +sub PrintAFPointsGrid($$); +sub PrintAFPointsGridInv($$$); +sub GetAFPointGrid($$;$); + +# nikon lens ID numbers (ref 8/11) +%nikonLensIDs = ( + Notes => q{ + The Nikon LensID is constructed as a Composite tag from the raw hex values + of 8 other tags: LensIDNumber, LensFStops, MinFocalLength, MaxFocalLength, + MaxApertureAtMinFocal, MaxApertureAtMaxFocal, MCUVersion and LensType, in + that order. The user-defined "Lenses" list may be used to specify the lens + for ExifTool to choose in these cases (see the + L<sample config file|../config.html> for details). + }, + OTHER => \&LensIDConv, + # Note: Sync this list with Robert's Perl version at + # http://www.rottmerhusen.com/objektives/lensid/files/exif/fmountlens.p.txt + # (hex digits must be uppercase in keys below) + '01 58 50 50 14 14 02 00' => 'AF Nikkor 50mm f/1.8', + '01 58 50 50 14 14 05 00' => 'AF Nikkor 50mm f/1.8', + '02 42 44 5C 2A 34 02 00' => 'AF Zoom-Nikkor 35-70mm f/3.3-4.5', + '02 42 44 5C 2A 34 08 00' => 'AF Zoom-Nikkor 35-70mm f/3.3-4.5', + '03 48 5C 81 30 30 02 00' => 'AF Zoom-Nikkor 70-210mm f/4', + '04 48 3C 3C 24 24 03 00' => 'AF Nikkor 28mm f/2.8', + '05 54 50 50 0C 0C 04 00' => 'AF Nikkor 50mm f/1.4', + '06 54 53 53 24 24 06 00' => 'AF Micro-Nikkor 55mm f/2.8', + '07 40 3C 62 2C 34 03 00' => 'AF Zoom-Nikkor 28-85mm f/3.5-4.5', + '08 40 44 6A 2C 34 04 00' => 'AF Zoom-Nikkor 35-105mm f/3.5-4.5', + '09 48 37 37 24 24 04 00' => 'AF Nikkor 24mm f/2.8', + '0A 48 8E 8E 24 24 03 00' => 'AF Nikkor 300mm f/2.8 IF-ED', + '0A 48 8E 8E 24 24 05 00' => 'AF Nikkor 300mm f/2.8 IF-ED N', + '0B 48 7C 7C 24 24 05 00' => 'AF Nikkor 180mm f/2.8 IF-ED', + '0D 40 44 72 2C 34 07 00' => 'AF Zoom-Nikkor 35-135mm f/3.5-4.5', + '0E 48 5C 81 30 30 05 00' => 'AF Zoom-Nikkor 70-210mm f/4', + '0F 58 50 50 14 14 05 00' => 'AF Nikkor 50mm f/1.8 N', + '10 48 8E 8E 30 30 08 00' => 'AF Nikkor 300mm f/4 IF-ED', + '11 48 44 5C 24 24 08 00' => 'AF Zoom-Nikkor 35-70mm f/2.8', + '11 48 44 5C 24 24 15 00' => 'AF Zoom-Nikkor 35-70mm f/2.8', #Jakob Dettner + '12 48 5C 81 30 3C 09 00' => 'AF Nikkor 70-210mm f/4-5.6', + '13 42 37 50 2A 34 0B 00' => 'AF Zoom-Nikkor 24-50mm f/3.3-4.5', + '14 48 60 80 24 24 0B 00' => 'AF Zoom-Nikkor 80-200mm f/2.8 ED', + '15 4C 62 62 14 14 0C 00' => 'AF Nikkor 85mm f/1.8', + '17 3C A0 A0 30 30 0F 00' => 'Nikkor 500mm f/4 P ED IF', + '17 3C A0 A0 30 30 11 00' => 'Nikkor 500mm f/4 P ED IF', + '18 40 44 72 2C 34 0E 00' => 'AF Zoom-Nikkor 35-135mm f/3.5-4.5 N', + '1A 54 44 44 18 18 11 00' => 'AF Nikkor 35mm f/2', + '1B 44 5E 8E 34 3C 10 00' => 'AF Zoom-Nikkor 75-300mm f/4.5-5.6', + '1C 48 30 30 24 24 12 00' => 'AF Nikkor 20mm f/2.8', + '1D 42 44 5C 2A 34 12 00' => 'AF Zoom-Nikkor 35-70mm f/3.3-4.5 N', + '1E 54 56 56 24 24 13 00' => 'AF Micro-Nikkor 60mm f/2.8', + '1F 54 6A 6A 24 24 14 00' => 'AF Micro-Nikkor 105mm f/2.8', + '20 48 60 80 24 24 15 00' => 'AF Zoom-Nikkor 80-200mm f/2.8 ED', + '21 40 3C 5C 2C 34 16 00' => 'AF Zoom-Nikkor 28-70mm f/3.5-4.5', + '22 48 72 72 18 18 16 00' => 'AF DC-Nikkor 135mm f/2', + '23 30 BE CA 3C 48 17 00' => 'Zoom-Nikkor 1200-1700mm f/5.6-8 P ED IF', + '24 48 60 80 24 24 1A 02' => 'AF Zoom-Nikkor 80-200mm f/2.8D ED', + '25 48 44 5C 24 24 1B 02' => 'AF Zoom-Nikkor 35-70mm f/2.8D', + '25 48 44 5C 24 24 3A 02' => 'AF Zoom-Nikkor 35-70mm f/2.8D', + '25 48 44 5C 24 24 52 02' => 'AF Zoom-Nikkor 35-70mm f/2.8D', + '26 40 3C 5C 2C 34 1C 02' => 'AF Zoom-Nikkor 28-70mm f/3.5-4.5D', + '27 48 8E 8E 24 24 1D 02' => 'AF-I Nikkor 300mm f/2.8D IF-ED', + '27 48 8E 8E 24 24 F1 02' => 'AF-I Nikkor 300mm f/2.8D IF-ED + TC-14E', + '27 48 8E 8E 24 24 E1 02' => 'AF-I Nikkor 300mm f/2.8D IF-ED + TC-17E', + '27 48 8E 8E 24 24 F2 02' => 'AF-I Nikkor 300mm f/2.8D IF-ED + TC-20E', + '28 3C A6 A6 30 30 1D 02' => 'AF-I Nikkor 600mm f/4D IF-ED', + '28 3C A6 A6 30 30 F1 02' => 'AF-I Nikkor 600mm f/4D IF-ED + TC-14E', + '28 3C A6 A6 30 30 E1 02' => 'AF-I Nikkor 600mm f/4D IF-ED + TC-17E', + '28 3C A6 A6 30 30 F2 02' => 'AF-I Nikkor 600mm f/4D IF-ED + TC-20E', + '2A 54 3C 3C 0C 0C 26 02' => 'AF Nikkor 28mm f/1.4D', + '2B 3C 44 60 30 3C 1F 02' => 'AF Zoom-Nikkor 35-80mm f/4-5.6D', + '2C 48 6A 6A 18 18 27 02' => 'AF DC-Nikkor 105mm f/2D', + '2D 48 80 80 30 30 21 02' => 'AF Micro-Nikkor 200mm f/4D IF-ED', + '2E 48 5C 82 30 3C 22 02' => 'AF Nikkor 70-210mm f/4-5.6D', + '2E 48 5C 82 30 3C 28 02' => 'AF Nikkor 70-210mm f/4-5.6D', + '2F 48 30 44 24 24 29 02.1' => 'AF Zoom-Nikkor 20-35mm f/2.8D IF', + '30 48 98 98 24 24 24 02' => 'AF-I Nikkor 400mm f/2.8D IF-ED', + '30 48 98 98 24 24 F1 02' => 'AF-I Nikkor 400mm f/2.8D IF-ED + TC-14E', + '30 48 98 98 24 24 E1 02' => 'AF-I Nikkor 400mm f/2.8D IF-ED + TC-17E', + '30 48 98 98 24 24 F2 02' => 'AF-I Nikkor 400mm f/2.8D IF-ED + TC-20E', + '31 54 56 56 24 24 25 02' => 'AF Micro-Nikkor 60mm f/2.8D', + '32 54 6A 6A 24 24 35 02.1' => 'AF Micro-Nikkor 105mm f/2.8D', + '33 48 2D 2D 24 24 31 02' => 'AF Nikkor 18mm f/2.8D', + '34 48 29 29 24 24 32 02' => 'AF Fisheye Nikkor 16mm f/2.8D', + '35 3C A0 A0 30 30 33 02' => 'AF-I Nikkor 500mm f/4D IF-ED', + '35 3C A0 A0 30 30 F1 02' => 'AF-I Nikkor 500mm f/4D IF-ED + TC-14E', + '35 3C A0 A0 30 30 E1 02' => 'AF-I Nikkor 500mm f/4D IF-ED + TC-17E', + '35 3C A0 A0 30 30 F2 02' => 'AF-I Nikkor 500mm f/4D IF-ED + TC-20E', + '36 48 37 37 24 24 34 02' => 'AF Nikkor 24mm f/2.8D', + '37 48 30 30 24 24 36 02' => 'AF Nikkor 20mm f/2.8D', + '38 4C 62 62 14 14 37 02' => 'AF Nikkor 85mm f/1.8D', + '3A 40 3C 5C 2C 34 39 02' => 'AF Zoom-Nikkor 28-70mm f/3.5-4.5D', + '3B 48 44 5C 24 24 3A 02' => 'AF Zoom-Nikkor 35-70mm f/2.8D N', + '3C 48 60 80 24 24 3B 02' => 'AF Zoom-Nikkor 80-200mm f/2.8D ED', #NJ + '3D 3C 44 60 30 3C 3E 02' => 'AF Zoom-Nikkor 35-80mm f/4-5.6D', + '3E 48 3C 3C 24 24 3D 02' => 'AF Nikkor 28mm f/2.8D', + '3F 40 44 6A 2C 34 45 02' => 'AF Zoom-Nikkor 35-105mm f/3.5-4.5D', + '41 48 7C 7C 24 24 43 02' => 'AF Nikkor 180mm f/2.8D IF-ED', + '42 54 44 44 18 18 44 02' => 'AF Nikkor 35mm f/2D', + '43 54 50 50 0C 0C 46 02' => 'AF Nikkor 50mm f/1.4D', + '44 44 60 80 34 3C 47 02' => 'AF Zoom-Nikkor 80-200mm f/4.5-5.6D', + '45 40 3C 60 2C 3C 48 02' => 'AF Zoom-Nikkor 28-80mm f/3.5-5.6D', + '46 3C 44 60 30 3C 49 02' => 'AF Zoom-Nikkor 35-80mm f/4-5.6D N', + '47 42 37 50 2A 34 4A 02' => 'AF Zoom-Nikkor 24-50mm f/3.3-4.5D', + '48 48 8E 8E 24 24 4B 02' => 'AF-S Nikkor 300mm f/2.8D IF-ED', + '48 48 8E 8E 24 24 F1 02' => 'AF-S Nikkor 300mm f/2.8D IF-ED + TC-14E', + '48 48 8E 8E 24 24 E1 02' => 'AF-S Nikkor 300mm f/2.8D IF-ED + TC-17E', + '48 48 8E 8E 24 24 F2 02' => 'AF-S Nikkor 300mm f/2.8D IF-ED + TC-20E', + '49 3C A6 A6 30 30 4C 02' => 'AF-S Nikkor 600mm f/4D IF-ED', + '49 3C A6 A6 30 30 F1 02' => 'AF-S Nikkor 600mm f/4D IF-ED + TC-14E', + '49 3C A6 A6 30 30 E1 02' => 'AF-S Nikkor 600mm f/4D IF-ED + TC-17E', + '49 3C A6 A6 30 30 F2 02' => 'AF-S Nikkor 600mm f/4D IF-ED + TC-20E', + '4A 54 62 62 0C 0C 4D 02' => 'AF Nikkor 85mm f/1.4D IF', + '4B 3C A0 A0 30 30 4E 02' => 'AF-S Nikkor 500mm f/4D IF-ED', + '4B 3C A0 A0 30 30 F1 02' => 'AF-S Nikkor 500mm f/4D IF-ED + TC-14E', + '4B 3C A0 A0 30 30 E1 02' => 'AF-S Nikkor 500mm f/4D IF-ED + TC-17E', + '4B 3C A0 A0 30 30 F2 02' => 'AF-S Nikkor 500mm f/4D IF-ED + TC-20E', + '4C 40 37 6E 2C 3C 4F 02' => 'AF Zoom-Nikkor 24-120mm f/3.5-5.6D IF', + '4D 40 3C 80 2C 3C 62 02' => 'AF Zoom-Nikkor 28-200mm f/3.5-5.6D IF', + '4E 48 72 72 18 18 51 02' => 'AF DC-Nikkor 135mm f/2D', + '4F 40 37 5C 2C 3C 53 06' => 'IX-Nikkor 24-70mm f/3.5-5.6', + '50 48 56 7C 30 3C 54 06' => 'IX-Nikkor 60-180mm f/4-5.6', + '53 48 60 80 24 24 57 02' => 'AF Zoom-Nikkor 80-200mm f/2.8D ED', + '53 48 60 80 24 24 60 02' => 'AF Zoom-Nikkor 80-200mm f/2.8D ED', + '54 44 5C 7C 34 3C 58 02' => 'AF Zoom-Micro Nikkor 70-180mm f/4.5-5.6D ED', + '54 44 5C 7C 34 3C 61 02' => 'AF Zoom-Micro Nikkor 70-180mm f/4.5-5.6D ED', + '56 48 5C 8E 30 3C 5A 02' => 'AF Zoom-Nikkor 70-300mm f/4-5.6D ED', + '59 48 98 98 24 24 5D 02' => 'AF-S Nikkor 400mm f/2.8D IF-ED', + '59 48 98 98 24 24 F1 02' => 'AF-S Nikkor 400mm f/2.8D IF-ED + TC-14E', + '59 48 98 98 24 24 E1 02' => 'AF-S Nikkor 400mm f/2.8D IF-ED + TC-17E', + '59 48 98 98 24 24 F2 02' => 'AF-S Nikkor 400mm f/2.8D IF-ED + TC-20E', + '5A 3C 3E 56 30 3C 5E 06' => 'IX-Nikkor 30-60mm f/4-5.6', + '5B 44 56 7C 34 3C 5F 06' => 'IX-Nikkor 60-180mm f/4.5-5.6', + '5D 48 3C 5C 24 24 63 02' => 'AF-S Zoom-Nikkor 28-70mm f/2.8D IF-ED', + '5E 48 60 80 24 24 64 02' => 'AF-S Zoom-Nikkor 80-200mm f/2.8D IF-ED', + '5F 40 3C 6A 2C 34 65 02' => 'AF Zoom-Nikkor 28-105mm f/3.5-4.5D IF', + '60 40 3C 60 2C 3C 66 02' => 'AF Zoom-Nikkor 28-80mm f/3.5-5.6D', #(http://www.exif.org/forum/topic.asp?TOPIC_ID=16) + '61 44 5E 86 34 3C 67 02' => 'AF Zoom-Nikkor 75-240mm f/4.5-5.6D', + '63 48 2B 44 24 24 68 02' => 'AF-S Nikkor 17-35mm f/2.8D IF-ED', + '64 00 62 62 24 24 6A 02' => 'PC Micro-Nikkor 85mm f/2.8D', + '65 44 60 98 34 3C 6B 0A' => 'AF VR Zoom-Nikkor 80-400mm f/4.5-5.6D ED', + '66 40 2D 44 2C 34 6C 02' => 'AF Zoom-Nikkor 18-35mm f/3.5-4.5D IF-ED', + '67 48 37 62 24 30 6D 02' => 'AF Zoom-Nikkor 24-85mm f/2.8-4D IF', + '68 42 3C 60 2A 3C 6E 06' => 'AF Zoom-Nikkor 28-80mm f/3.3-5.6G', + '69 48 5C 8E 30 3C 6F 06' => 'AF Zoom-Nikkor 70-300mm f/4-5.6G', + '6A 48 8E 8E 30 30 70 02' => 'AF-S Nikkor 300mm f/4D IF-ED', + '6B 48 24 24 24 24 71 02' => 'AF Nikkor ED 14mm f/2.8D', + '6D 48 8E 8E 24 24 73 02' => 'AF-S Nikkor 300mm f/2.8D IF-ED II', + '6E 48 98 98 24 24 74 02' => 'AF-S Nikkor 400mm f/2.8D IF-ED II', + '6F 3C A0 A0 30 30 75 02' => 'AF-S Nikkor 500mm f/4D IF-ED II', + '70 3C A6 A6 30 30 76 02' => 'AF-S Nikkor 600mm f/4D IF-ED II', + '72 48 4C 4C 24 24 77 00' => 'Nikkor 45mm f/2.8 P', + '74 40 37 62 2C 34 78 06' => 'AF-S Zoom-Nikkor 24-85mm f/3.5-4.5G IF-ED', + '75 40 3C 68 2C 3C 79 06' => 'AF Zoom-Nikkor 28-100mm f/3.5-5.6G', + '76 58 50 50 14 14 7A 02' => 'AF Nikkor 50mm f/1.8D', + '77 48 5C 80 24 24 7B 0E' => 'AF-S VR Zoom-Nikkor 70-200mm f/2.8G IF-ED', + '78 40 37 6E 2C 3C 7C 0E' => 'AF-S VR Zoom-Nikkor 24-120mm f/3.5-5.6G IF-ED', + '79 40 3C 80 2C 3C 7F 06' => 'AF Zoom-Nikkor 28-200mm f/3.5-5.6G IF-ED', + '7A 3C 1F 37 30 30 7E 06.1' => 'AF-S DX Zoom-Nikkor 12-24mm f/4G IF-ED', + '7B 48 80 98 30 30 80 0E' => 'AF-S VR Zoom-Nikkor 200-400mm f/4G IF-ED', + '7D 48 2B 53 24 24 82 06' => 'AF-S DX Zoom-Nikkor 17-55mm f/2.8G IF-ED', + '7F 40 2D 5C 2C 34 84 06' => 'AF-S DX Zoom-Nikkor 18-70mm f/3.5-4.5G IF-ED', + '80 48 1A 1A 24 24 85 06' => 'AF DX Fisheye-Nikkor 10.5mm f/2.8G ED', + '81 54 80 80 18 18 86 0E' => 'AF-S VR Nikkor 200mm f/2G IF-ED', + '82 48 8E 8E 24 24 87 0E' => 'AF-S VR Nikkor 300mm f/2.8G IF-ED', + '83 00 B0 B0 5A 5A 88 04' => 'FSA-L2, EDG 65, 800mm F13 G', + '89 3C 53 80 30 3C 8B 06' => 'AF-S DX Zoom-Nikkor 55-200mm f/4-5.6G ED', + '8A 54 6A 6A 24 24 8C 0E' => 'AF-S VR Micro-Nikkor 105mm f/2.8G IF-ED', #10 + # when the TC-20 III 2x teleconverter is used with the above lens, the following have been observed: + # 8A 4D 6A 6A 24 24 8C 0E + # 8A 4E 6A 6A 24 24 8C 0E + # 8A 50 6A 6A 24 24 8C 0E + # 8A 51 6A 6A 24 24 8C 0E + # 8A 52 6A 6A 24 24 8C 0E + # 8A 53 6A 6A 24 24 8C 0E + # 8A 54 6A 6A 24 24 8C 0E (same as without the TC) + '8B 40 2D 80 2C 3C 8D 0E' => 'AF-S DX VR Zoom-Nikkor 18-200mm f/3.5-5.6G IF-ED', + '8B 40 2D 80 2C 3C FD 0E' => 'AF-S DX VR Zoom-Nikkor 18-200mm f/3.5-5.6G IF-ED [II]', #20 + '8C 40 2D 53 2C 3C 8E 06' => 'AF-S DX Zoom-Nikkor 18-55mm f/3.5-5.6G ED', + '8D 44 5C 8E 34 3C 8F 0E' => 'AF-S VR Zoom-Nikkor 70-300mm f/4.5-5.6G IF-ED', #10 + '8F 40 2D 72 2C 3C 91 06' => 'AF-S DX Zoom-Nikkor 18-135mm f/3.5-5.6G IF-ED', + '90 3B 53 80 30 3C 92 0E' => 'AF-S DX VR Zoom-Nikkor 55-200mm f/4-5.6G IF-ED', + '92 48 24 37 24 24 94 06' => 'AF-S Zoom-Nikkor 14-24mm f/2.8G ED', + '93 48 37 5C 24 24 95 06' => 'AF-S Zoom-Nikkor 24-70mm f/2.8G ED', + '94 40 2D 53 2C 3C 96 06' => 'AF-S DX Zoom-Nikkor 18-55mm f/3.5-5.6G ED II', #10 (D40) + '95 4C 37 37 2C 2C 97 02' => 'PC-E Nikkor 24mm f/3.5D ED', + '95 00 37 37 2C 2C 97 06' => 'PC-E Nikkor 24mm f/3.5D ED', #JD + '96 48 98 98 24 24 98 0E' => 'AF-S VR Nikkor 400mm f/2.8G ED', + '97 3C A0 A0 30 30 99 0E' => 'AF-S VR Nikkor 500mm f/4G ED', + '98 3C A6 A6 30 30 9A 0E' => 'AF-S VR Nikkor 600mm f/4G ED', + '99 40 29 62 2C 3C 9B 0E' => 'AF-S DX VR Zoom-Nikkor 16-85mm f/3.5-5.6G ED', + '9A 40 2D 53 2C 3C 9C 0E' => 'AF-S DX VR Zoom-Nikkor 18-55mm f/3.5-5.6G', + '9B 54 4C 4C 24 24 9D 02' => 'PC-E Micro Nikkor 45mm f/2.8D ED', + '9B 00 4C 4C 24 24 9D 06' => 'PC-E Micro Nikkor 45mm f/2.8D ED', + '9C 54 56 56 24 24 9E 06' => 'AF-S Micro Nikkor 60mm f/2.8G ED', + '9D 54 62 62 24 24 9F 02' => 'PC-E Micro Nikkor 85mm f/2.8D', + '9D 00 62 62 24 24 9F 06' => 'PC-E Micro Nikkor 85mm f/2.8D', + '9E 40 2D 6A 2C 3C A0 0E' => 'AF-S DX VR Zoom-Nikkor 18-105mm f/3.5-5.6G ED', #PH/10 + '9F 58 44 44 14 14 A1 06' => 'AF-S DX Nikkor 35mm f/1.8G', #27 + 'A0 54 50 50 0C 0C A2 06' => 'AF-S Nikkor 50mm f/1.4G', + 'A1 40 18 37 2C 34 A3 06' => 'AF-S DX Nikkor 10-24mm f/3.5-4.5G ED', + 'A1 40 2D 53 2C 3C CB 86' => 'AF-P DX Nikkor 18-55mm f/3.5-5.6G', #30 + 'A2 48 5C 80 24 24 A4 0E' => 'AF-S Nikkor 70-200mm f/2.8G ED VR II', + 'A3 3C 29 44 30 30 A5 0E' => 'AF-S Nikkor 16-35mm f/4G ED VR', + 'A4 54 37 37 0C 0C A6 06' => 'AF-S Nikkor 24mm f/1.4G ED', + 'A5 40 3C 8E 2C 3C A7 0E' => 'AF-S Nikkor 28-300mm f/3.5-5.6G ED VR', + 'A6 48 8E 8E 24 24 A8 0E' => 'AF-S Nikkor 300mm f/2.8G IF-ED VR II', + 'A7 4B 62 62 2C 2C A9 0E' => 'AF-S DX Micro Nikkor 85mm f/3.5G ED VR', + 'A8 48 80 98 30 30 AA 0E' => 'AF-S Zoom-Nikkor 200-400mm f/4G IF-ED VR II', #https://exiftool.org/forum/index.php/topic,3218.msg15495.html#msg15495 + 'A9 54 80 80 18 18 AB 0E' => 'AF-S Nikkor 200mm f/2G ED VR II', + 'AA 3C 37 6E 30 30 AC 0E' => 'AF-S Nikkor 24-120mm f/4G ED VR', + 'AC 38 53 8E 34 3C AE 0E' => 'AF-S DX Nikkor 55-300mm f/4.5-5.6G ED VR', + 'AD 3C 2D 8E 2C 3C AF 0E' => 'AF-S DX Nikkor 18-300mm f/3.5-5.6G ED VR', + 'AE 54 62 62 0C 0C B0 06' => 'AF-S Nikkor 85mm f/1.4G', + 'AF 54 44 44 0C 0C B1 06' => 'AF-S Nikkor 35mm f/1.4G', + 'B0 4C 50 50 14 14 B2 06' => 'AF-S Nikkor 50mm f/1.8G', + 'B1 48 48 48 24 24 B3 06' => 'AF-S DX Micro Nikkor 40mm f/2.8G', #27 + 'B2 48 5C 80 30 30 B4 0E' => 'AF-S Nikkor 70-200mm f/4G ED VR', #35 + 'B3 4C 62 62 14 14 B5 06' => 'AF-S Nikkor 85mm f/1.8G', + 'B4 40 37 62 2C 34 B6 0E' => 'AF-S Zoom-Nikkor 24-85mm f/3.5-4.5G IF-ED VR', #30 + 'B5 4C 3C 3C 14 14 B7 06' => 'AF-S Nikkor 28mm f/1.8G', #30 + 'B6 3C B0 B0 3C 3C B8 0E' => 'AF-S VR Nikkor 800mm f/5.6E FL ED', + 'B6 3C B0 B0 3C 3C B8 4E' => 'AF-S VR Nikkor 800mm f/5.6E FL ED', #PH + 'B7 44 60 98 34 3C B9 0E' => 'AF-S Nikkor 80-400mm f/4.5-5.6G ED VR', + 'B8 40 2D 44 2C 34 BA 06' => 'AF-S Nikkor 18-35mm f/3.5-4.5G ED', + 'A0 40 2D 74 2C 3C BB 0E' => 'AF-S DX Nikkor 18-140mm f/3.5-5.6G ED VR', #PH + 'A1 54 55 55 0C 0C BC 06' => 'AF-S Nikkor 58mm f/1.4G', #IB + 'A1 48 6E 8E 24 24 DB 4E' => 'AF-S Nikkor 120-300mm f/2.8E FL ED SR VR', #28 + 'A2 40 2D 53 2C 3C BD 0E' => 'AF-S DX Nikkor 18-55mm f/3.5-5.6G VR II', + 'A4 40 2D 8E 2C 40 BF 0E' => 'AF-S DX Nikkor 18-300mm f/3.5-6.3G ED VR', + 'A5 4C 44 44 14 14 C0 06' => 'AF-S Nikkor 35mm f/1.8G ED', #35 ("ED" ref 11) + 'A6 48 98 98 24 24 C1 0E' => 'AF-S Nikkor 400mm f/2.8E FL ED VR', + 'A7 3C 53 80 30 3C C2 0E' => 'AF-S DX Nikkor 55-200mm f/4-5.6G ED VR II', #IB + 'A8 48 8E 8E 30 30 C3 4E' => 'AF-S Nikkor 300mm f/4E PF ED VR', #35 + 'A8 48 8E 8E 30 30 C3 0E' => 'AF-S Nikkor 300mm f/4E PF ED VR', #30 + 'A9 4C 31 31 14 14 C4 06' => 'AF-S Nikkor 20mm f/1.8G ED', #30 + 'AA 48 37 5C 24 24 C5 4E' => 'AF-S Nikkor 24-70mm f/2.8E ED VR', + 'AA 48 37 5C 24 24 C5 0E' => 'AF-S Nikkor 24-70mm f/2.8E ED VR', + 'AB 3C A0 A0 30 30 C6 4E' => 'AF-S Nikkor 500mm f/4E FL ED VR', + 'AC 3C A6 A6 30 30 C7 4E' => 'AF-S Nikkor 600mm f/4E FL ED VR', #PH + 'AD 48 28 60 24 30 C8 4E' => 'AF-S DX Nikkor 16-80mm f/2.8-4E ED VR', + 'AD 48 28 60 24 30 C8 0E' => 'AF-S DX Nikkor 16-80mm f/2.8-4E ED VR', #PH + 'AE 3C 80 A0 3C 3C C9 4E' => 'AF-S Nikkor 200-500mm f/5.6E ED VR', #PH + 'AE 3C 80 A0 3C 3C C9 0E' => 'AF-S Nikkor 200-500mm f/5.6E ED VR', + 'A0 40 2D 53 2C 3C CA 8E' => 'AF-P DX Nikkor 18-55mm f/3.5-5.6G', #Yang You pvt communication + 'A0 40 2D 53 2C 3C CA 0E' => 'AF-P DX Nikkor 18-55mm f/3.5-5.6G VR', #PH + 'AF 4C 37 37 14 14 CC 06' => 'AF-S Nikkor 24mm f/1.8G ED', #IB + 'A2 38 5C 8E 34 40 CD 86' => 'AF-P DX Nikkor 70-300mm f/4.5-6.3G VR', #PH + 'A3 38 5C 8E 34 40 CE 8E' => 'AF-P DX Nikkor 70-300mm f/4.5-6.3G ED VR', + 'A3 38 5C 8E 34 40 CE 0E' => 'AF-P DX Nikkor 70-300mm f/4.5-6.3G ED', + 'A4 48 5C 80 24 24 CF 4E' => 'AF-S Nikkor 70-200mm f/2.8E FL ED VR', + 'A4 48 5C 80 24 24 CF 0E' => 'AF-S Nikkor 70-200mm f/2.8E FL ED VR', + 'A5 54 6A 6A 0C 0C D0 46' => 'AF-S Nikkor 105mm f/1.4E ED', #IB + 'A5 54 6A 6A 0C 0C D0 06' => 'AF-S Nikkor 105mm f/1.4E ED', #IB + 'A6 48 2F 2F 30 30 D1 46' => 'PC Nikkor 19mm f/4E ED', + 'A6 48 2F 2F 30 30 D1 06' => 'PC Nikkor 19mm f/4E ED', + 'A7 40 11 26 2C 34 D2 46' => 'AF-S Fisheye Nikkor 8-15mm f/3.5-4.5E ED', + 'A7 40 11 26 2C 34 D2 06' => 'AF-S Fisheye Nikkor 8-15mm f/3.5-4.5E ED', + 'A8 38 18 30 34 3C D3 8E' => 'AF-P DX Nikkor 10-20mm f/4.5-5.6G VR', #Yang You pvt communication + 'A8 38 18 30 34 3C D3 0E' => 'AF-P DX Nikkor 10-20mm f/4.5-5.6G VR', + 'A9 48 7C 98 30 30 D4 4E' => 'AF-S Nikkor 180-400mm f/4E TC1.4 FL ED VR', #IB + 'A9 48 7C 98 30 30 D4 0E' => 'AF-S Nikkor 180-400mm f/4E TC1.4 FL ED VR', + 'AA 48 88 A4 3C 3C D5 4E' => 'AF-S Nikkor 180-400mm f/4E TC1.4 FL ED VR + 1.4x TC', #IB + 'AA 48 88 A4 3C 3C D5 0E' => 'AF-S Nikkor 180-400mm f/4E TC1.4 FL ED VR + 1.4x TC', + 'AB 44 5C 8E 34 3C D6 CE' => 'AF-P Nikkor 70-300mm f/4.5-5.6E ED VR', + 'AB 44 5C 8E 34 3C D6 0E' => 'AF-P Nikkor 70-300mm f/4.5-5.6E ED VR', + 'AB 44 5C 8E 34 3C D6 4E' => 'AF-P Nikkor 70-300mm f/4.5-5.6E ED VR', #IB + 'AC 54 3C 3C 0C 0C D7 46' => 'AF-S Nikkor 28mm f/1.4E ED', + 'AC 54 3C 3C 0C 0C D7 06' => 'AF-S Nikkor 28mm f/1.4E ED', + 'AD 3C A0 A0 3C 3C D8 0E' => 'AF-S Nikkor 500mm f/5.6E PF ED VR', + 'AD 3C A0 A0 3C 3C D8 4E' => 'AF-S Nikkor 500mm f/5.6E PF ED VR', + '01 00 00 00 00 00 02 00' => 'TC-16A', + '01 00 00 00 00 00 08 00' => 'TC-16A', + '00 00 00 00 00 00 F1 0C' => 'TC-14E [II] or Sigma APO Tele Converter 1.4x EX DG or Kenko Teleplus PRO 300 DG 1.4x', + '00 00 00 00 00 00 F2 18' => 'TC-20E [II] or Sigma APO Tele Converter 2x EX DG or Kenko Teleplus PRO 300 DG 2.0x', + '00 00 00 00 00 00 E1 12' => 'TC-17E II', + 'FE 47 00 00 24 24 4B 06' => 'Sigma 4.5mm F2.8 EX DC HSM Circular Fisheye', #JD + '26 48 11 11 30 30 1C 02' => 'Sigma 8mm F4 EX Circular Fisheye', + '79 40 11 11 2C 2C 1C 06' => 'Sigma 8mm F3.5 EX Circular Fisheye', #JD + 'DB 40 11 11 2C 2C 1C 06' => 'Sigma 8mm F3.5 EX DG Circular Fisheye', #30 + 'DC 48 19 19 24 24 4B 06' => 'Sigma 10mm F2.8 EX DC HSM Fisheye', + 'C2 4C 24 24 14 14 4B 06' => 'Sigma 14mm F1.8 DG HSM | A', #IB + '48 48 24 24 24 24 4B 02' => 'Sigma 14mm F2.8 EX Aspherical HSM', + '02 3F 24 24 2C 2C 02 00' => 'Sigma 14mm F3.5', + '26 48 27 27 24 24 1C 02' => 'Sigma 15mm F2.8 EX Diagonal Fisheye', + 'EA 48 27 27 24 24 1C 02' => 'Sigma 15mm F2.8 EX Diagonal Fisheye', #30 + '26 58 31 31 14 14 1C 02' => 'Sigma 20mm F1.8 EX DG Aspherical RF', + '79 54 31 31 0C 0C 4B 06' => 'Sigma 20mm F1.4 DG HSM | A', #Rolf Probst + '26 58 37 37 14 14 1C 02' => 'Sigma 24mm F1.8 EX DG Aspherical Macro', + 'E1 58 37 37 14 14 1C 02' => 'Sigma 24mm F1.8 EX DG Aspherical Macro', + '02 46 37 37 25 25 02 00' => 'Sigma 24mm F2.8 Super Wide II Macro', + '7E 54 37 37 0C 0C 4B 06' => 'Sigma 24mm F1.4 DG HSM | A', #30 + '26 58 3C 3C 14 14 1C 02' => 'Sigma 28mm F1.8 EX DG Aspherical Macro', + '48 54 3E 3E 0C 0C 4B 06' => 'Sigma 30mm F1.4 EX DC HSM', + 'F8 54 3E 3E 0C 0C 4B 06' => 'Sigma 30mm F1.4 EX DC HSM', #JD + '91 54 44 44 0C 0C 4B 06' => 'Sigma 35mm F1.4 DG HSM', #30 + 'BD 54 48 48 0C 0C 4B 46' => 'Sigma 40mm F1.4 DG HSM | A', #30 + 'DE 54 50 50 0C 0C 4B 06' => 'Sigma 50mm F1.4 EX DG HSM', + '88 54 50 50 0C 0C 4B 06' => 'Sigma 50mm F1.4 DG HSM | A', + '02 48 50 50 24 24 02 00' => 'Sigma Macro 50mm F2.8', #https://exiftool.org/forum/index.php/topic,4027.0.html + '32 54 50 50 24 24 35 02' => 'Sigma Macro 50mm F2.8 EX DG', + 'E3 54 50 50 24 24 35 02' => 'Sigma Macro 50mm F2.8 EX DG', #https://exiftool.org/forum/index.php/topic,3215.0.html + '79 48 5C 5C 24 24 1C 06' => 'Sigma Macro 70mm F2.8 EX DG', #JD + '9B 54 62 62 0C 0C 4B 06' => 'Sigma 85mm F1.4 EX DG HSM', + 'C8 54 62 62 0C 0C 4B 46' => 'Sigma 85mm F1.4 DG HSM | A', #JamiBradley + 'C8 54 62 62 0C 0C 4B 06' => 'Sigma 85mm F1.4 DG HSM | A', #KennethCochran + '02 48 65 65 24 24 02 00' => 'Sigma Macro 90mm F2.8', + '32 54 6A 6A 24 24 35 02.2' => 'Sigma Macro 105mm F2.8 EX DG', #JD + 'E5 54 6A 6A 24 24 35 02' => 'Sigma Macro 105mm F2.8 EX DG', + '97 48 6A 6A 24 24 4B 0E' => 'Sigma Macro 105mm F2.8 EX DG OS HSM', + 'BE 54 6A 6A 0C 0C 4B 46' => 'Sigma 105mm F1.4 DG HSM | A', #30 + '48 48 76 76 24 24 4B 06' => 'Sigma APO Macro 150mm F2.8 EX DG HSM', + 'F5 48 76 76 24 24 4B 06' => 'Sigma APO Macro 150mm F2.8 EX DG HSM', #24 + '99 48 76 76 24 24 4B 0E' => 'Sigma APO Macro 150mm F2.8 EX DG OS HSM', #(Christian Hesse) + '48 4C 7C 7C 2C 2C 4B 02' => 'Sigma APO Macro 180mm F3.5 EX DG HSM', + '48 4C 7D 7D 2C 2C 4B 02' => 'Sigma APO Macro 180mm F3.5 EX DG HSM', + 'F4 4C 7C 7C 2C 2C 4B 02' => 'Sigma APO Macro 180mm F3.5 EX DG HSM', #Bruno + '94 48 7C 7C 24 24 4B 0E' => 'Sigma APO Macro 180mm F2.8 EX DG OS HSM', #MichaelTapes (HSM from ref 8) + '48 54 8E 8E 24 24 4B 02' => 'Sigma APO 300mm F2.8 EX DG HSM', + 'FB 54 8E 8E 24 24 4B 02' => 'Sigma APO 300mm F2.8 EX DG HSM', #26 + '26 48 8E 8E 30 30 1C 02' => 'Sigma APO Tele Macro 300mm F4', + '02 2F 98 98 3D 3D 02 00' => 'Sigma APO 400mm F5.6', + '26 3C 98 98 3C 3C 1C 02' => 'Sigma APO Tele Macro 400mm F5.6', + '02 37 A0 A0 34 34 02 00' => 'Sigma APO 500mm F4.5', #19 + '48 44 A0 A0 34 34 4B 02' => 'Sigma APO 500mm F4.5 EX HSM', + 'F1 44 A0 A0 34 34 4B 02' => 'Sigma APO 500mm F4.5 EX DG HSM', + '02 34 A0 A0 44 44 02 00' => 'Sigma APO 500mm F7.2', + '02 3C B0 B0 3C 3C 02 00' => 'Sigma APO 800mm F5.6', + '48 3C B0 B0 3C 3C 4B 02' => 'Sigma APO 800mm F5.6 EX HSM', + '9E 38 11 29 34 3C 4B 06' => 'Sigma 8-16mm F4.5-5.6 DC HSM', + 'A1 41 19 31 2C 2C 4B 06' => 'Sigma 10-20mm F3.5 EX DC HSM', + '48 3C 19 31 30 3C 4B 06' => 'Sigma 10-20mm F4-5.6 EX DC HSM', + 'F9 3C 19 31 30 3C 4B 06' => 'Sigma 10-20mm F4-5.6 EX DC HSM', #JD + '48 38 1F 37 34 3C 4B 06' => 'Sigma 12-24mm F4.5-5.6 EX DG Aspherical HSM', + 'F0 38 1F 37 34 3C 4B 06' => 'Sigma 12-24mm F4.5-5.6 EX DG Aspherical HSM', + '96 38 1F 37 34 3C 4B 06' => 'Sigma 12-24mm F4.5-5.6 II DG HSM', #Jurgen Sahlberg + 'CA 3C 1F 37 30 30 4B 46' => 'Sigma 12-24mm F4 DG HSM | A', #github issue#101 + 'C1 48 24 37 24 24 4B 46' => 'Sigma 14-24mm F2.8 DG HSM | A', #30 + '26 40 27 3F 2C 34 1C 02' => 'Sigma 15-30mm F3.5-4.5 EX DG Aspherical DF', + '48 48 2B 44 24 30 4B 06' => 'Sigma 17-35mm F2.8-4 EX DG Aspherical HSM', + '26 54 2B 44 24 30 1C 02' => 'Sigma 17-35mm F2.8-4 EX Aspherical', + '9D 48 2B 50 24 24 4B 0E' => 'Sigma 17-50mm F2.8 EX DC OS HSM', + '8F 48 2B 50 24 24 4B 0E' => 'Sigma 17-50mm F2.8 EX DC OS HSM', #http://dev.exiv2.org/boards/3/topics/1747 + '7A 47 2B 5C 24 34 4B 06' => 'Sigma 17-70mm F2.8-4.5 DC Macro Asp. IF HSM', + '7A 48 2B 5C 24 34 4B 06' => 'Sigma 17-70mm F2.8-4.5 DC Macro Asp. IF HSM', + '7F 48 2B 5C 24 34 1C 06' => 'Sigma 17-70mm F2.8-4.5 DC Macro Asp. IF', + '8E 3C 2B 5C 24 30 4B 0E' => 'Sigma 17-70mm F2.8-4 DC Macro OS HSM | C', + 'A0 48 2A 5C 24 30 4B 0E' => 'Sigma 17-70mm F2.8-4 DC Macro OS HSM', #https://exiftool.org/forum/index.php/topic,5170.0.html + '8B 4C 2D 44 14 14 4B 06' => 'Sigma 18-35mm F1.8 DC HSM', #30/NJ + '26 40 2D 44 2B 34 1C 02' => 'Sigma 18-35mm F3.5-4.5 Aspherical', + '26 48 2D 50 24 24 1C 06' => 'Sigma 18-50mm F2.8 EX DC', + '7F 48 2D 50 24 24 1C 06' => 'Sigma 18-50mm F2.8 EX DC Macro', #NJ + '7A 48 2D 50 24 24 4B 06' => 'Sigma 18-50mm F2.8 EX DC Macro', + 'F6 48 2D 50 24 24 4B 06' => 'Sigma 18-50mm F2.8 EX DC Macro', + 'A4 47 2D 50 24 34 4B 0E' => 'Sigma 18-50mm F2.8-4.5 DC OS HSM', + '26 40 2D 50 2C 3C 1C 06' => 'Sigma 18-50mm F3.5-5.6 DC', + '7A 40 2D 50 2C 3C 4B 06' => 'Sigma 18-50mm F3.5-5.6 DC HSM', + '26 40 2D 70 2B 3C 1C 06' => 'Sigma 18-125mm F3.5-5.6 DC', + 'CD 3D 2D 70 2E 3C 4B 0E' => 'Sigma 18-125mm F3.8-5.6 DC OS HSM', + '26 40 2D 80 2C 40 1C 06' => 'Sigma 18-200mm F3.5-6.3 DC', + 'FF 40 2D 80 2C 40 4B 06' => 'Sigma 18-200mm F3.5-6.3 DC', #30 + '7A 40 2D 80 2C 40 4B 0E' => 'Sigma 18-200mm F3.5-6.3 DC OS HSM', + 'ED 40 2D 80 2C 40 4B 0E' => 'Sigma 18-200mm F3.5-6.3 DC OS HSM', #JD + '90 40 2D 80 2C 40 4B 0E' => 'Sigma 18-200mm F3.5-6.3 II DC OS HSM', #JohnHelour + '89 30 2D 80 2C 40 4B 0E' => 'Sigma 18-200mm F3.5-6.3 DC Macro OS HS | C', #JoeSchonberg + 'A5 40 2D 88 2C 40 4B 0E' => 'Sigma 18-250mm F3.5-6.3 DC OS HSM', + # LensFStops varies with FocalLength for this lens (ref 2): + '92 2C 2D 88 2C 40 4B 0E' => 'Sigma 18-250mm F3.5-6.3 DC Macro OS HSM', #2 + '87 2C 2D 8E 2C 40 4B 0E' => 'Sigma 18-300mm F3.5-6.3 DC Macro HSM', #30 + # '92 2C 2D 88 2C 40 4B 0E' (250mm) + # '92 2B 2D 88 2C 40 4B 0E' (210mm) + # '92 2C 2D 88 2C 40 4B 0E' (185mm) + # '92 2D 2D 88 2C 40 4B 0E' (155mm) + # '92 2E 2D 88 2C 40 4B 0E' (130mm) + # '92 2F 2D 88 2C 40 4B 0E' (105mm) + # '92 30 2D 88 2C 40 4B 0E' (90mm) + # '92 32 2D 88 2C 40 4B 0E' (75mm) + # '92 33 2D 88 2C 40 4B 0E' (62mm) + # '92 35 2D 88 2C 40 4B 0E' (52mm) + # '92 37 2D 88 2C 40 4B 0E' (44mm) + # '92 39 2D 88 2C 40 4B 0E' (38mm) + # '92 3A 2D 88 2C 40 4B 0E' (32mm) + # '92 3E 2D 88 2C 40 4B 0E' (22mm) + # '92 40 2D 88 2C 40 4B 0E' (18mm) + '26 48 31 49 24 24 1C 02' => 'Sigma 20-40mm F2.8', + '7B 48 37 44 18 18 4B 06' => 'Sigma 24-35mm F2.0 DG HSM | A', #30 + '02 3A 37 50 31 3D 02 00' => 'Sigma 24-50mm F4-5.6 UC', + '26 48 37 56 24 24 1C 02' => 'Sigma 24-60mm F2.8 EX DG', + 'B6 48 37 56 24 24 1C 02' => 'Sigma 24-60mm F2.8 EX DG', + 'A6 48 37 5C 24 24 4B 06' => 'Sigma 24-70mm F2.8 IF EX DG HSM', #JD + 'C9 48 37 5C 24 24 4B 4E' => 'Sigma 24-70mm F2.8 DG OS HSM | A', #30 + '26 54 37 5C 24 24 1C 02' => 'Sigma 24-70mm F2.8 EX DG Macro', + '67 54 37 5C 24 24 1C 02' => 'Sigma 24-70mm F2.8 EX DG Macro', + 'E9 54 37 5C 24 24 1C 02' => 'Sigma 24-70mm F2.8 EX DG Macro', + '26 40 37 5C 2C 3C 1C 02' => 'Sigma 24-70mm F3.5-5.6 Aspherical HF', + '8A 3C 37 6A 30 30 4B 0E' => 'Sigma 24-105mm F4 DG OS HSM', #IB + '26 54 37 73 24 34 1C 02' => 'Sigma 24-135mm F2.8-4.5', + '02 46 3C 5C 25 25 02 00' => 'Sigma 28-70mm F2.8', + '26 54 3C 5C 24 24 1C 02' => 'Sigma 28-70mm F2.8 EX', + '26 48 3C 5C 24 24 1C 06' => 'Sigma 28-70mm F2.8 EX DG', + '79 48 3C 5C 24 24 1C 06' => 'Sigma 28-70mm F2.8 EX DG', #30 ("D" removed) + '26 48 3C 5C 24 30 1C 02' => 'Sigma 28-70mm F2.8-4 DG', + '02 3F 3C 5C 2D 35 02 00' => 'Sigma 28-70mm F3.5-4.5 UC', + '26 40 3C 60 2C 3C 1C 02' => 'Sigma 28-80mm F3.5-5.6 Mini Zoom Macro II Aspherical', + '26 40 3C 65 2C 3C 1C 02' => 'Sigma 28-90mm F3.5-5.6 Macro', + '26 48 3C 6A 24 30 1C 02' => 'Sigma 28-105mm F2.8-4 Aspherical', + '26 3E 3C 6A 2E 3C 1C 02' => 'Sigma 28-105mm F3.8-5.6 UC-III Aspherical IF', + '26 40 3C 80 2C 3C 1C 02' => 'Sigma 28-200mm F3.5-5.6 Compact Aspherical Hyperzoom Macro', + '26 40 3C 80 2B 3C 1C 02' => 'Sigma 28-200mm F3.5-5.6 Compact Aspherical Hyperzoom Macro', + '26 3D 3C 80 2F 3D 1C 02' => 'Sigma 28-300mm F3.8-5.6 Aspherical', + '26 41 3C 8E 2C 40 1C 02' => 'Sigma 28-300mm F3.5-6.3 DG Macro', + 'E6 41 3C 8E 2C 40 1C 02' => 'Sigma 28-300mm F3.5-6.3 DG Macro', #https://exiftool.org/forum/index.php/topic,3301.0.html + '26 40 3C 8E 2C 40 1C 02' => 'Sigma 28-300mm F3.5-6.3 Macro', + '02 3B 44 61 30 3D 02 00' => 'Sigma 35-80mm F4-5.6', + '02 40 44 73 2B 36 02 00' => 'Sigma 35-135mm F3.5-4.5 a', + 'CC 4C 50 68 14 14 4B 06' => 'Sigma 50-100mm F1.8 DC HSM | A', #30 + '7A 47 50 76 24 24 4B 06' => 'Sigma 50-150mm F2.8 EX APO DC HSM', + 'FD 47 50 76 24 24 4B 06' => 'Sigma 50-150mm F2.8 EX APO DC HSM II', + '98 48 50 76 24 24 4B 0E' => 'Sigma 50-150mm F2.8 EX APO DC OS HSM', #30 + '48 3C 50 A0 30 40 4B 02' => 'Sigma 50-500mm F4-6.3 EX APO RF HSM', + '9F 37 50 A0 34 40 4B 0E' => 'Sigma 50-500mm F4.5-6.3 DG OS HSM', #16 + '26 3C 54 80 30 3C 1C 06' => 'Sigma 55-200mm F4-5.6 DC', + '7A 3B 53 80 30 3C 4B 06' => 'Sigma 55-200mm F4-5.6 DC HSM', + '48 54 5C 80 24 24 4B 02' => 'Sigma 70-200mm F2.8 EX APO IF HSM', + '7A 48 5C 80 24 24 4B 06' => 'Sigma 70-200mm F2.8 EX APO DG Macro HSM II', + 'EE 48 5C 80 24 24 4B 06' => 'Sigma 70-200mm F2.8 EX APO DG Macro HSM II', #JD + '9C 48 5C 80 24 24 4B 0E' => 'Sigma 70-200mm F2.8 EX DG OS HSM', #Rolando Ruzic + 'BB 48 5C 80 24 24 4B 4E' => 'Sigma 70-200mm F2.8 DG OS HSM | S', #forum13207 + '02 46 5C 82 25 25 02 00' => 'Sigma 70-210mm F2.8 APO', #JD + '02 40 5C 82 2C 35 02 00' => 'Sigma APO 70-210mm F3.5-4.5', + '26 3C 5C 82 30 3C 1C 02' => 'Sigma 70-210mm F4-5.6 UC-II', + '02 3B 5C 82 30 3C 02 00' => 'Sigma Zoom-K 70-210mm F4-5.6', #30 + '26 3C 5C 8E 30 3C 1C 02' => 'Sigma 70-300mm F4-5.6 DG Macro', + '56 3C 5C 8E 30 3C 1C 02' => 'Sigma 70-300mm F4-5.6 APO Macro Super II', + 'E0 3C 5C 8E 30 3C 4B 06' => 'Sigma 70-300mm F4-5.6 APO DG Macro HSM', #22 + 'A3 3C 5C 8E 30 3C 4B 0E' => 'Sigma 70-300mm F4-5.6 DG OS', + '02 37 5E 8E 35 3D 02 00' => 'Sigma 75-300mm F4.5-5.6 APO', + '02 3A 5E 8E 32 3D 02 00' => 'Sigma 75-300mm F4.0-5.6', + '77 44 61 98 34 3C 7B 0E' => 'Sigma 80-400mm F4.5-5.6 EX OS', + '77 44 60 98 34 3C 7B 0E' => 'Sigma 80-400mm F4.5-5.6 APO DG D OS', + '48 48 68 8E 30 30 4B 02' => 'Sigma APO 100-300mm F4 EX IF HSM', + 'F3 48 68 8E 30 30 4B 02' => 'Sigma APO 100-300mm F4 EX IF HSM', + '26 45 68 8E 34 42 1C 02' => 'Sigma 100-300mm F4.5-6.7 DL', #30 + '48 54 6F 8E 24 24 4B 02' => 'Sigma APO 120-300mm F2.8 EX DG HSM', + '7A 54 6E 8E 24 24 4B 02' => 'Sigma APO 120-300mm F2.8 EX DG HSM', + 'FA 54 6E 8E 24 24 4B 02' => 'Sigma APO 120-300mm F2.8 EX DG HSM', #https://exiftool.org/forum/index.php/topic,2787.0.html + 'CF 38 6E 98 34 3C 4B 0E' => 'Sigma APO 120-400mm F4.5-5.6 DG OS HSM', + 'C3 34 68 98 38 40 4B 4E' => 'Sigma 100-400mm F5-6.3 DG OS HSM | C', #JR (017) + '8D 48 6E 8E 24 24 4B 0E' => 'Sigma 120-300mm F2.8 DG OS HSM Sports', + '26 44 73 98 34 3C 1C 02' => 'Sigma 135-400mm F4.5-5.6 APO Aspherical', + 'CE 34 76 A0 38 40 4B 0E' => 'Sigma 150-500mm F5-6.3 DG OS APO HSM', #JD + '81 34 76 A6 38 40 4B 0E' => 'Sigma 150-600mm F5-6.3 DG OS HSM | S', #Jaap Voets + '82 34 76 A6 38 40 4B 0E' => 'Sigma 150-600mm F5-6.3 DG OS HSM | C', + 'C4 4C 73 73 14 14 4B 46' => 'Sigma 135mm F1.8 DG HSM | A', #forum3833 + '26 40 7B A0 34 40 1C 02' => 'Sigma APO 170-500mm F5-6.3 Aspherical RF', + 'A7 49 80 A0 24 24 4B 06' => 'Sigma APO 200-500mm F2.8 EX DG', + '48 3C 8E B0 3C 3C 4B 02' => 'Sigma APO 300-800mm F5.6 EX DG HSM', + 'D2 3C 8E B0 3C 3C 4B 02' => 'Sigma APO 300-800mm F5.6 EX DG HSM', #forum10942 +# + '00 47 25 25 24 24 00 02' => 'Tamron SP AF 14mm f/2.8 Aspherical (IF) (69E)', + 'C8 54 44 44 0D 0D DF 46' => 'Tamron SP 35mm f/1.4 Di USD (F045)', #IB + 'E8 4C 44 44 14 14 DF 0E' => 'Tamron SP 35mm f/1.8 Di VC USD (F012)', #35 + 'E7 4C 4C 4C 14 14 DF 0E' => 'Tamron SP 45mm f/1.8 Di VC USD (F013)', + 'F4 54 56 56 18 18 84 06' => 'Tamron SP AF 60mm f/2.0 Di II Macro 1:1 (G005)', #24 + 'E5 4C 62 62 14 14 C9 4E' => 'Tamron SP 85mm f/1.8 Di VC USD (F016)', #30 + '1E 5D 64 64 20 20 13 00' => 'Tamron SP AF 90mm f/2.5 (52E)', + '20 5A 64 64 20 20 14 00' => 'Tamron SP AF 90mm f/2.5 Macro (152E)', + '22 53 64 64 24 24 E0 02' => 'Tamron SP AF 90mm f/2.8 Macro 1:1 (72E)', + '32 53 64 64 24 24 35 02' => 'Tamron SP AF 90mm f/2.8 [Di] Macro 1:1 (172E/272E)', + 'F8 55 64 64 24 24 84 06' => 'Tamron SP AF 90mm f/2.8 Di Macro 1:1 (272NII)', + 'F8 54 64 64 24 24 DF 06' => 'Tamron SP AF 90mm f/2.8 Di Macro 1:1 (272NII)', + 'FE 54 64 64 24 24 DF 0E' => 'Tamron SP 90mm f/2.8 Di VC USD Macro 1:1 (F004)', #Jurgen Sahlberg + 'E4 54 64 64 24 24 DF 0E' => 'Tamron SP 90mm f/2.8 Di VC USD Macro 1:1 (F017)', #Rolf Probst + '00 4C 7C 7C 2C 2C 00 02' => 'Tamron SP AF 180mm f/3.5 Di Model (B01)', + '21 56 8E 8E 24 24 14 00' => 'Tamron SP AF 300mm f/2.8 LD-IF (60E)', + '27 54 8E 8E 24 24 1D 02' => 'Tamron SP AF 300mm f/2.8 LD-IF (360E)', + 'E1 40 19 36 2C 35 DF 4E' => 'Tamron 10-24mm f/3.5-4.5 Di II VC HLD (B023)', + 'F6 3F 18 37 2C 34 84 06' => 'Tamron SP AF 10-24mm f/3.5-4.5 Di II LD Aspherical (IF) (B001)', + 'F6 3F 18 37 2C 34 DF 06' => 'Tamron SP AF 10-24mm f/3.5-4.5 Di II LD Aspherical (IF) (B001)', #30 + '00 36 1C 2D 34 3C 00 06' => 'Tamron SP AF 11-18mm f/4.5-5.6 Di II LD Aspherical (IF) (A13)', + 'E9 48 27 3E 24 24 DF 0E' => 'Tamron SP 15-30mm f/2.8 Di VC USD (A012)', #IB + 'CA 48 27 3E 24 24 DF 4E' => 'Tamron SP 15-30mm f/2.8 Di VC USD G2 (A041)', #IB + 'EA 40 29 8E 2C 40 DF 0E' => 'Tamron 16-300mm f/3.5-6.3 Di II VC PZD (B016)', # (removed AF designation, ref 37) + '07 46 2B 44 24 30 03 02' => 'Tamron SP AF 17-35mm f/2.8-4 Di LD Aspherical (IF) (A05)', + 'CB 3C 2B 44 24 31 DF 46' => 'Tamron 17-35mm f/2.8-4 Di OSD (A037)', #IB + '00 53 2B 50 24 24 00 06' => 'Tamron SP AF 17-50mm f/2.8 XR Di II LD Aspherical (IF) (A16)', #PH + '7C 54 2B 50 24 24 00 06' => 'Tamron SP AF 17-50mm f/2.8 XR Di II LD Aspherical (IF) (A16)', #PH (https://github.com/Exiv2/exiv2/issues/1155) + '00 54 2B 50 24 24 00 06' => 'Tamron SP AF 17-50mm f/2.8 XR Di II LD Aspherical (IF) (A16NII)', + 'FB 54 2B 50 24 24 84 06' => 'Tamron SP AF 17-50mm f/2.8 XR Di II LD Aspherical (IF) (A16NII)', #https://exiftool.org/forum/index.php/topic,3787.0.html + 'F3 54 2B 50 24 24 84 0E' => 'Tamron SP AF 17-50mm f/2.8 XR Di II VC LD Aspherical (IF) (B005)', + '00 3F 2D 80 2B 40 00 06' => 'Tamron AF 18-200mm f/3.5-6.3 XR Di II LD Aspherical (IF) (A14)', + '00 3F 2D 80 2C 40 00 06' => 'Tamron AF 18-200mm f/3.5-6.3 XR Di II LD Aspherical (IF) Macro (A14)', + 'EC 3E 3C 8E 2C 40 DF 0E' => 'Tamron 28-300mm f/3.5-6.3 Di VC PZD A010', #30 + '00 40 2D 80 2C 40 00 06' => 'Tamron AF 18-200mm f/3.5-6.3 XR Di II LD Aspherical (IF) Macro (A14NII)', #NJ + 'FC 40 2D 80 2C 40 DF 06' => 'Tamron AF 18-200mm f/3.5-6.3 XR Di II LD Aspherical (IF) Macro (A14NII)', #PH (NC) + 'E6 40 2D 80 2C 40 DF 0E' => 'Tamron 18-200mm f/3.5-6.3 Di II VC (B018)', #Tanel (removed AF designation, ref 37) + '00 40 2D 88 2C 40 62 06' => 'Tamron AF 18-250mm f/3.5-6.3 Di II LD Aspherical (IF) Macro (A18)', + '00 40 2D 88 2C 40 00 06' => 'Tamron AF 18-250mm f/3.5-6.3 Di II LD Aspherical (IF) Macro (A18NII)', #JD + 'F5 40 2C 8A 2C 40 40 0E' => 'Tamron AF 18-270mm f/3.5-6.3 Di II VC LD Aspherical (IF) Macro (B003)', + 'F0 3F 2D 8A 2C 40 DF 0E' => 'Tamron AF 18-270mm f/3.5-6.3 Di II VC PZD (B008)', + 'E0 40 2D 98 2C 41 DF 4E' => 'Tamron 18-400mm f/3.5-6.3 Di II VC HLD (B028)', # (removed AF designation, ref 37) + '07 40 2F 44 2C 34 03 02' => 'Tamron AF 19-35mm f/3.5-4.5 (A10)', + '07 40 30 45 2D 35 03 02.1' => 'Tamron AF 19-35mm f/3.5-4.5 (A10)', + '00 49 30 48 22 2B 00 02' => 'Tamron SP AF 20-40mm f/2.7-3.5 (166D)', + '0E 4A 31 48 23 2D 0E 02' => 'Tamron SP AF 20-40mm f/2.7-3.5 (166D)', + 'FE 48 37 5C 24 24 DF 0E' => 'Tamron SP 24-70mm f/2.8 Di VC USD (A007)', #24 + 'CE 47 37 5C 25 25 DF 4E' => 'Tamron SP 24-70mm f/2.8 Di VC USD G2 (A032)', #forum9110 + '45 41 37 72 2C 3C 48 02' => 'Tamron SP AF 24-135mm f/3.5-5.6 AD Aspherical (IF) Macro (190D)', + '33 54 3C 5E 24 24 62 02' => 'Tamron SP AF 28-75mm f/2.8 XR Di LD Aspherical (IF) Macro (A09)', + 'FA 54 3C 5E 24 24 84 06' => 'Tamron SP AF 28-75mm f/2.8 XR Di LD Aspherical (IF) Macro (A09NII)', #JD + 'FA 54 3C 5E 24 24 DF 06' => 'Tamron SP AF 28-75mm f/2.8 XR Di LD Aspherical (IF) Macro (A09NII)', + '10 3D 3C 60 2C 3C D2 02' => 'Tamron AF 28-80mm f/3.5-5.6 Aspherical (177D)', + '45 3D 3C 60 2C 3C 48 02' => 'Tamron AF 28-80mm f/3.5-5.6 Aspherical (177D)', + '00 48 3C 6A 24 24 00 02' => 'Tamron SP AF 28-105mm f/2.8 LD Aspherical IF (176D)', + '4D 3E 3C 80 2E 3C 62 02' => 'Tamron AF 28-200mm f/3.8-5.6 XR Aspherical (IF) Macro (A03N)', + '0B 3E 3D 7F 2F 3D 0E 00' => 'Tamron AF 28-200mm f/3.8-5.6 (71D)', + '0B 3E 3D 7F 2F 3D 0E 02' => 'Tamron AF 28-200mm f/3.8-5.6D (171D)', + '12 3D 3C 80 2E 3C DF 02' => 'Tamron AF 28-200mm f/3.8-5.6 AF Aspherical LD (IF) (271D)', + '4D 41 3C 8E 2B 40 62 02' => 'Tamron AF 28-300mm f/3.5-6.3 XR Di LD Aspherical (IF) (A061)', + '4D 41 3C 8E 2C 40 62 02' => 'Tamron AF 28-300mm f/3.5-6.3 XR LD Aspherical (IF) (185D)', + 'F9 40 3C 8E 2C 40 40 0E' => 'Tamron AF 28-300mm f/3.5-6.3 XR Di VC LD Aspherical (IF) Macro (A20)', + 'C9 3C 44 76 25 31 DF 4E' => 'Tamron 35-150mm f/2.8-4 Di VC OSD (A043)', #30 + '00 47 53 80 30 3C 00 06' => 'Tamron AF 55-200mm f/4-5.6 Di II LD (A15)', + 'F7 53 5C 80 24 24 84 06' => 'Tamron SP AF 70-200mm f/2.8 Di LD (IF) Macro (A001)', + 'FE 53 5C 80 24 24 84 06' => 'Tamron SP AF 70-200mm f/2.8 Di LD (IF) Macro (A001)', + 'F7 53 5C 80 24 24 40 06' => 'Tamron SP AF 70-200mm f/2.8 Di LD (IF) Macro (A001)', + # 'FE 54 5C 80 24 24 DF 0E' => 'Tamron SP AF 70-200mm f/2.8 Di VC USD (A009)', + 'FE 54 5C 80 24 24 DF 0E' => 'Tamron SP 70-200mm f/2.8 Di VC USD (A009)', #NJ + 'E2 47 5C 80 24 24 DF 4E' => 'Tamron SP 70-200mm f/2.8 Di VC USD G2 (A025)', #forum9549 + '69 48 5C 8E 30 3C 6F 02' => 'Tamron AF 70-300mm f/4-5.6 LD Macro 1:2 (572D/772D)', + '69 47 5C 8E 30 3C 00 02' => 'Tamron AF 70-300mm f/4-5.6 Di LD Macro 1:2 (A17N)', + '00 48 5C 8E 30 3C 00 06' => 'Tamron AF 70-300mm f/4-5.6 Di LD Macro 1:2 (A17NII)', #JD + 'F1 47 5C 8E 30 3C DF 0E' => 'Tamron SP 70-300mm f/4-5.6 Di VC USD (A005)', + 'CF 47 5C 8E 31 3D DF 0E' => 'Tamron SP 70-300mm f/4-5.6 Di VC USD (A030)', #forum9773 + 'CC 44 68 98 34 41 DF 0E' => 'Tamron 100-400mm f/4.5-6.3 Di VC USD', #30 + 'EB 40 76 A6 38 40 DF 0E' => 'Tamron SP AF 150-600mm f/5-6.3 VC USD (A011)', + 'E3 40 76 A6 38 40 DF 4E' => 'Tamron SP 150-600mm f/5-6.3 Di VC USD G2', #30 + 'E3 40 76 A6 38 40 DF 0E' => 'Tamron SP 150-600mm f/5-6.3 Di VC USD G2 (A022)', #forum3833 + '20 3C 80 98 3D 3D 1E 02' => 'Tamron AF 200-400mm f/5.6 LD IF (75D)', + '00 3E 80 A0 38 3F 00 02' => 'Tamron SP AF 200-500mm f/5-6.3 Di LD (IF) (A08)', + '00 3F 80 A0 38 3F 00 02' => 'Tamron SP AF 200-500mm f/5-6.3 Di (A08)', +# + '00 40 2B 2B 2C 2C 00 02' => 'Tokina AT-X 17 AF PRO (AF 17mm f/3.5)', + '00 47 44 44 24 24 00 06' => 'Tokina AT-X M35 PRO DX (AF 35mm f/2.8 Macro)', + '8D 54 68 68 24 24 87 02' => 'Tokina AT-X PRO 100mm F2.8 D Macro', #30 + '00 54 68 68 24 24 00 02' => 'Tokina AT-X M100 AF PRO D (AF 100mm f/2.8 Macro)', + '27 48 8E 8E 30 30 1D 02' => 'Tokina AT-X 304 AF (AF 300mm f/4.0)', + '00 54 8E 8E 24 24 00 02' => 'Tokina AT-X 300 AF PRO (AF 300mm f/2.8)', + '12 3B 98 98 3D 3D 09 00' => 'Tokina AT-X 400 AF SD (AF 400mm f/5.6)', + '00 40 18 2B 2C 34 00 06' => 'Tokina AT-X 107 AF DX Fisheye (AF 10-17mm f/3.5-4.5)', + '00 48 1C 29 24 24 00 06' => 'Tokina AT-X 116 PRO DX (AF 11-16mm f/2.8)', + '7A 48 1C 29 24 24 7E 06' => 'Tokina AT-X 116 PRO DX II (AF 11-16mm f/2.8)', + '80 48 1C 29 24 24 7A 06' => 'Tokina atx-i 11-16mm F2.8 CF', #exiv2 issue 1078 + '7A 48 1C 30 24 24 7E 06' => 'Tokina AT-X 11-20 F2.8 PRO DX (AF 11-20mm f/2.8)', + '8B 48 1C 30 24 24 85 06' => 'Tokina AT-X 11-20 F2.8 PRO DX (AF 11-20mm f/2.8)', #forum12687 + '00 3C 1F 37 30 30 00 06' => 'Tokina AT-X 124 AF PRO DX (AF 12-24mm f/4)', + '7A 3C 1F 37 30 30 7E 06.2' => 'Tokina AT-X 124 AF PRO DX II (AF 12-24mm f/4)', + '7A 3C 1F 3C 30 30 7E 06' => 'Tokina AT-X 12-28 PRO DX (AF 12-28mm f/4)', + '00 48 29 3C 24 24 00 06' => 'Tokina AT-X 16-28 AF PRO FX (AF 16-28mm f/2.8)', + '00 48 29 50 24 24 00 06' => 'Tokina AT-X 165 PRO DX (AF 16-50mm f/2.8)', + '00 40 2A 72 2C 3C 00 06' => 'Tokina AT-X 16.5-135 DX (AF 16.5-135mm F3.5-5.6)', + '00 3C 2B 44 30 30 00 06' => 'Tokina AT-X 17-35 F4 PRO FX (AF 17-35mm f/4)', + '2F 40 30 44 2C 34 29 02.2' => 'Tokina AF 193 (AF 19-35mm f/3.5-4.5)', + '2F 48 30 44 24 24 29 02.2' => 'Tokina AT-X 235 AF PRO (AF 20-35mm f/2.8)', + '2F 40 30 44 2C 34 29 02.1' => 'Tokina AF 235 II (AF 20-35mm f/3.5-4.5)', + '00 48 37 5C 24 24 00 06' => 'Tokina AT-X 24-70 F2.8 PRO FX (AF 24-70mm f/2.8)', + '00 40 37 80 2C 3C 00 02' => 'Tokina AT-X 242 AF (AF 24-200mm f/3.5-5.6)', + '25 48 3C 5C 24 24 1B 02.1' => 'Tokina AT-X 270 AF PRO II (AF 28-70mm f/2.6-2.8)', + '25 48 3C 5C 24 24 1B 02.2' => 'Tokina AT-X 287 AF PRO SV (AF 28-70mm f/2.8)', + '07 48 3C 5C 24 24 03 00' => 'Tokina AT-X 287 AF (AF 28-70mm f/2.8)', + '07 47 3C 5C 25 35 03 00' => 'Tokina AF 287 SD (AF 28-70mm f/2.8-4.5)', + '07 40 3C 5C 2C 35 03 00' => 'Tokina AF 270 II (AF 28-70mm f/3.5-4.5)', + '00 48 3C 60 24 24 00 02' => 'Tokina AT-X 280 AF PRO (AF 28-80mm f/2.8)', + '25 44 44 8E 34 42 1B 02' => 'Tokina AF 353 (AF 35-300mm f/4.5-6.7)', + '00 48 50 72 24 24 00 06' => 'Tokina AT-X 535 PRO DX (AF 50-135mm f/2.8)', + '00 3C 5C 80 30 30 00 0E' => 'Tokina AT-X 70-200 F4 FX VCM-S (AF 70-200mm f/4)', + '00 48 5C 80 30 30 00 0E' => 'Tokina AT-X 70-200 F4 FX VCM-S (AF 70-200mm f/4)', + '12 44 5E 8E 34 3C 09 00' => 'Tokina AF 730 (AF 75-300mm F4.5-5.6)', + '14 54 60 80 24 24 0B 00' => 'Tokina AT-X 828 AF (AF 80-200mm f/2.8)', + '24 54 60 80 24 24 1A 02' => 'Tokina AT-X 828 AF PRO (AF 80-200mm f/2.8)', + '24 44 60 98 34 3C 1A 02' => 'Tokina AT-X 840 AF-II (AF 80-400mm f/4.5-5.6)', + '00 44 60 98 34 3C 00 02' => 'Tokina AT-X 840 D (AF 80-400mm f/4.5-5.6)', + '14 48 68 8E 30 30 0B 00' => 'Tokina AT-X 340 AF (AF 100-300mm f/4)', + '8C 48 29 3C 24 24 86 06' => 'Tokina opera 16-28mm F2.8 FF', #30 +# + '06 3F 68 68 2C 2C 06 00' => 'Cosina AF 100mm F3.5 Macro', + '07 36 3D 5F 2C 3C 03 00' => 'Cosina AF Zoom 28-80mm F3.5-5.6 MC Macro', + '07 46 3D 6A 25 2F 03 00' => 'Cosina AF Zoom 28-105mm F2.8-3.8 MC', + '12 36 5C 81 35 3D 09 00' => 'Cosina AF Zoom 70-210mm F4.5-5.6 MC Macro', + '12 39 5C 8E 34 3D 08 02' => 'Cosina AF Zoom 70-300mm F4.5-5.6 MC Macro', + '12 3B 68 8D 3D 43 09 02' => 'Cosina AF Zoom 100-300mm F5.6-6.7 MC Macro', +# + '12 38 69 97 35 42 09 02' => 'Promaster Spectrum 7 100-400mm F4.5-6.7', +# + '00 40 31 31 2C 2C 00 00' => 'Voigtlander Color Skopar 20mm F3.5 SLII Aspherical', + '00 48 3C 3C 24 24 00 00' => 'Voigtlander Color Skopar 28mm F2.8 SL II', + '00 54 48 48 18 18 00 00' => 'Voigtlander Ultron 40mm F2 SLII Aspherical', + '00 54 55 55 0C 0C 00 00' => 'Voigtlander Nokton 58mm F1.4 SLII', + '00 40 64 64 2C 2C 00 00' => 'Voigtlander APO-Lanthar 90mm F3.5 SLII Close Focus', + '07 40 30 45 2D 35 03 02.2' => 'Voigtlander Ultragon 19-35mm F3.5-4.5 VMV', #NJ + '71 48 64 64 24 24 00 00' => 'Voigtlander APO-Skopar 90mm F2.8 SL IIs', #30 + 'FD 00 50 50 18 18 DF 00' => 'Voigtlander APO-Lanthar 50mm F2 Aspherical', #35 +# + '00 40 2D 2D 2C 2C 00 00' => 'Carl Zeiss Distagon T* 3.5/18 ZF.2', + '00 48 27 27 24 24 00 00' => 'Carl Zeiss Distagon T* 2.8/15 ZF.2', #MykytaKozlov + '00 48 32 32 24 24 00 00' => 'Carl Zeiss Distagon T* 2.8/21 ZF.2', + '00 54 38 38 18 18 00 00' => 'Carl Zeiss Distagon T* 2/25 ZF.2', + '00 54 3C 3C 18 18 00 00' => 'Carl Zeiss Distagon T* 2/28 ZF.2', + '00 54 44 44 0C 0C 00 00' => 'Carl Zeiss Distagon T* 1.4/35 ZF.2', + '00 54 44 44 18 18 00 00' => 'Carl Zeiss Distagon T* 2/35 ZF.2', + '00 54 50 50 0C 0C 00 00' => 'Carl Zeiss Planar T* 1.4/50 ZF.2', + '00 54 50 50 18 18 00 00' => 'Carl Zeiss Makro-Planar T* 2/50 ZF.2', + '00 54 62 62 0C 0C 00 00' => 'Carl Zeiss Planar T* 1.4/85 ZF.2', + '00 54 68 68 18 18 00 00' => 'Carl Zeiss Makro-Planar T* 2/100 ZF.2', + '00 54 72 72 18 18 00 00' => 'Carl Zeiss Apo Sonnar T* 2/135 ZF.2', + '00 54 53 53 0C 0C 00 00' => 'Zeiss Otus 1.4/55', #IB + '01 54 62 62 0C 0C 00 00' => 'Zeiss Otus 1.4/85', + '03 54 68 68 0C 0C 00 00' => 'Zeiss Otus 1.4/100', #IB + '52 54 44 44 18 18 00 00' => 'Zeiss Milvus 35mm f/2', + '53 54 50 50 0C 0C 00 00' => 'Zeiss Milvus 50mm f/1.4', #IB + '54 54 50 50 18 18 00 00' => 'Zeiss Milvus 50mm f/2 Macro', + '55 54 62 62 0C 0C 00 00' => 'Zeiss Milvus 85mm f/1.4', #IB + '56 54 68 68 18 18 00 00' => 'Zeiss Milvus 100mm f/2 Macro', +# + '00 54 56 56 30 30 00 00' => 'Coastal Optical Systems 60mm 1:4 UV-VIS-IR Macro Apo', +# + 'BF 4E 26 26 1E 1E 01 04' => 'Irix 15mm f/2.4 Firefly', #30 + 'BF 3C 1B 1B 30 30 01 04' => 'Irix 11mm f/4 Firefly', #30 +# + '4A 40 11 11 2C 0C 4D 02' => 'Samyang 8mm f/3.5 Fish-Eye CS', + '4A 48 24 24 24 0C 4D 02.1' => 'Samyang 10mm f/2.8 ED AS NCS CS', + '4A 48 1E 1E 24 0C 4D 02' => 'Samyang 12mm f/2.8 ED AS NCS Fish-Eye', #Jurgen Sahlberg + '4A 48 24 24 24 0C 4D 02.2' => 'Samyang AE 14mm f/2.8 ED AS IF UMC', #https://exiftool.org/forum/index.php/topic,3150.0.html + '4A 4C 24 24 1E 6C 4D 06' => 'Samyang 14mm f/2.4 Premium', + '4A 54 29 29 18 0C 4D 02' => 'Samyang 16mm f/2.0 ED AS UMC CS', #Jon Bloom (by email) + '4A 60 36 36 0C 0C 4D 02' => 'Samyang 24mm f/1.4 ED AS UMC', + '4A 60 44 44 0C 0C 4D 02' => 'Samyang 35mm f/1.4 AS UMC', + '4A 60 62 62 0C 0C 4D 02' => 'Samyang AE 85mm f/1.4 AS IF UMC', #https://exiftool.org/forum/index.php/topic,2888.0.html +# + '9A 4C 50 50 14 14 9C 06' => 'Yongnuo YN50mm F1.8N', + '9F 48 48 48 24 24 A1 06' => 'Yongnuo YN40mm F2.8N', #30 + '9F 54 68 68 18 18 A2 06' => 'Yongnuo YN100mm F2N', #30 +# + '02 40 44 5C 2C 34 02 00' => 'Exakta AF 35-70mm 1:3.5-4.5 MC', +# + '07 3E 30 43 2D 35 03 00' => 'Soligor AF Zoom 19-35mm 1:3.5-4.5 MC', + '03 43 5C 81 35 35 02 00' => 'Soligor AF C/D Zoom UMCS 70-210mm 1:4.5', + '12 4A 5C 81 31 3D 09 00' => 'Soligor AF C/D Auto Zoom+Macro 70-210mm 1:4-5.6 UMCS', + '12 36 69 97 35 42 09 00' => 'Soligor AF Zoom 100-400mm 1:4.5-6.7 MC', +# + '00 00 00 00 00 00 00 01' => 'Manual Lens No CPU', +# + '00 00 48 48 53 53 00 01' => 'Loreo 40mm F11-22 3D Lens in a Cap 9005', #PH + '00 47 10 10 24 24 00 00' => 'Fisheye Nikkor 8mm f/2.8 AiS', + '00 47 3C 3C 24 24 00 00' => 'Nikkor 28mm f/2.8 AiS', #35 + # '00 54 44 44 0C 0C 00 00' => 'Nikkor 35mm f/1.4 AiS', comment out in favour of Zeiss with same ID because this lens is rare (requires CPU upgrade) + '00 57 50 50 14 14 00 00' => 'Nikkor 50mm f/1.8 AI', #35 + '00 48 50 50 18 18 00 00' => 'Nikkor H 50mm f/2', + '00 48 68 68 24 24 00 00' => 'Series E 100mm f/2.8', + '00 4C 6A 6A 20 20 00 00' => 'Nikkor 105mm f/2.5 AiS', + '00 48 80 80 30 30 00 00' => 'Nikkor 200mm f/4 AiS', + '00 40 11 11 2C 2C 00 00' => 'Samyang 8mm f/3.5 Fish-Eye', + '00 58 64 64 20 20 00 00' => 'Soligor C/D Macro MC 90mm f/2.5', + '4A 58 30 30 14 0C 4D 02' => 'Rokinon 20mm f/1.8 ED AS UMC', #30 +# + 'A0 56 44 44 14 14 A2 06' => 'Sony FE 35mm F1.8', #IB (Techart adapter) + 'A0 37 5C 8E 34 3C A2 06' => 'Sony FE 70-300mm F4.5-5.6 G OSS', #IB (Techart adapter) +); + +# text encoding used in LocationInfo (ref PH) +%nikonTextEncoding = ( + 0 => 'n/a', + 1 => 'UTF8', + # UTF16 is a guess here: it could also be Unicode or JIS, + # but I chose UTF16 due to the similarity with the QuickTime stringEncoding + 2 => 'UTF16', +); + +# flash firmware decoding (ref JD) +my %flashFirmware = ( + '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)', #https://exiftool.org/forum/index.php/topic,5034.0.html + '7 1' => '7.01 (SB-910)', #PH + '14 3' => '14.03 (SB-5000)', #28 + OTHER => sub { + my ($val, $inv) = @_; + return sprintf('%d.%.2d (Unknown model)', split(' ', $val)) unless $inv; + return "$1 $2" if $val =~ /(\d+)\.(\d+)/; + return '0 0'; + }, +); + +# flash Guide Number (GN) distance settings (ref 28) +my %flashGNDistance = ( + 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', +); + +# flash color filter values (ref 28) +my %flashColorFilter = ( + 0x00 => 'None', + 1 => 'FL-GL1 or SZ-2FL Fluorescent', # (green) (SZ model ref PH) + 2 => 'FL-GL2', + 9 => 'TN-A1 or SZ-2TN Incandescent', # (orange) (SZ model ref PH) + 10 => 'TN-A2', + 65 => 'Red', + 66 => 'Blue', + 67 => 'Yellow', + 68 => 'Amber', + 128 => 'Incandescent', #SZ-4TN Incandescent +); + +# flash control mode values (ref JD) +my %flashControlMode = ( + 0x00 => 'Off', + 0x01 => 'iTTL-BL', + 0x02 => 'iTTL', + 0x03 => 'Auto Aperture', + 0x04 => 'Automatic', #28 + 0x05 => 'GN (distance priority)', #28 (Guide Number, but called "GN" in manual) + 0x06 => 'Manual', + 0x07 => 'Repeating Flash', +); + +my %activeDLightingZ7 = ( + 0 => 'Off', + 2 => 'Low', + 3 => 'Normal', + 4 => 'High', + 5 => 'Extra High', +); + +my %aFAreaModeZ9 = ( + 0 => 'Pinpoint', + 1 => 'Single', + 2 => 'Dynamic', + 3 => 'Wide (S)', + 4 => 'Wide (L)', + 5 => '3D', + 6 => 'Auto', + 11 => 'Subject Tracking', + 12 => 'Wide (C1)', + 13 => 'Wide (C2)', +); + +my %banksZ9 = ( + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', +); + +my %bracketIncrementZ9 = ( + 0 => '0.3', + 1 => '0.5', + 2 => '0.7', + 3 => '1.0', + 4 => '2.0', + 5 => '3.0', + 6 => '1.3', + 7 => '1.5', + 8 => '1.7', + 9 => '2.3', + 10 => '2.5', + 11 => '2.7', +); + +my %bracketSetZ9 = ( + 0 => 'AE/Flash', + 1 => 'AE', + 2 => 'Flash', + 3 => 'White Balance', + 4 => 'Active-D Lighting', +); + +my %bracketProgramZ9 = ( + 0 => 'Disabled', + 2 => '2F', + 3 => '3F', + 4 => '4F', + 5 => '5F', + 7 => '7F', + 9 => '9F', +); + +my %dynamicAfAreaModesZ9 = ( + 0 => 'Small', + 1 => 'Medium', + 2 => 'Large', +); + +my %flashControlModeZ7 = ( + 0 => 'TTL', + 1 => 'Auto External Flash', + 2 => 'GN (distance priority)', + 3 => 'Manual', + 4 => 'Repeating Flash', +); + +my %flashRemoteControlZ7 = ( + 0 => 'Group', + 1 => 'Quick Wireless', + 2 => 'Remote Repeating', +); + +my %flashWirelessOptionZ7 = ( + 0 => 'Off', + 1 => 'Optical AWL', + 2 => 'Optical/Radio AWL', + 3 => 'Radio AWL', +); + +my %focusModeZ7 = ( + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 4 => 'AF-F', # full frame +); + +my %hDMIOutputResolutionZ9 = ( + 0 => 'Auto', + 1 => '4320p', + 2 => '2160p', + 3 => '1080p', + #4 => '1080i', + 5 => '720p', + #6 => '576p', + #7 => '480p', +); + +my %hdrLevelZ8 = ( + 0 => 'Auto', + 1 => 'Extra High', + 2 => 'High', + 3 => 'Normal', + 4 => 'Low', +); + +my %highFrameRateZ9 = ( + 0 => 'Off', + 1 => 'CH', + 3 => 'C30', + 5 => 'C60', + 4 => 'C120', +); + +my %imageAreaZ9 = ( + 0 => 'FX', + 1 => 'DX', + 4 => '16:9', + 8 => '1:1', +); + +my %imageAreaZ9b = ( + 0 => 'FX', + 1 => 'DX', +); + +my %infoZSeries = ( + Condition => '$$self{Model} =~ /^NIKON Z (30|5|50|6|6_2|7|7_2|8|fc|9)\b/i', + Notes => 'Z Series cameras thru July 2023', +); + +my %iSOAutoHiLimitZ7 = ( + 0 => 'ISO 64', + 1 => 'ISO 80', + 2 => 'ISO 100', + 3 => 'ISO 125', + 4 => 'ISO 160', + 5 => 'ISO 200', + 6 => 'ISO 250', + 7 => 'ISO 320', + 8 => 'ISO 400', + 9 => 'ISO 500', + 10 => 'ISO 640', + 11 => 'ISO 800', + 12 => 'ISO 1000', + 13 => 'ISO 1250', + 14 => 'ISO 1600', + 15 => 'ISO 2000', + 16 => 'ISO 2500', + 17 => 'ISO 3200', + 18 => 'ISO 4000', + 19 => 'ISO 5000', + 20 => 'ISO 6400', + 21 => 'ISO 8000', + 22 => 'ISO 10000', + 23 => 'ISO 12800', + 24 => 'ISO 16000', + 25 => 'ISO 20000', + 26 => 'ISO 25600', + 27 => 'ISO Hi 0.3', + 28 => 'ISO Hi 0.7', + 29 => 'ISO Hi 1.0', + 32 => 'ISO Hi 2.0', +); + +my %iSOAutoShutterTimeZ9 = ( + -15 => 'Auto', #z9 firmware 1.00 maps both 'Auto' and '30 s' to -15 + -12 => '15 s', + -9 => '8 s', + -6 => '4 s', + -3 => '2 s', + 0 => '1 s', + 1 => '1/1.3 s', + 2 => '1/1.6 s', + 3 => '1/2 s', + 4 => '1/2.5 s', + 5 => '1/3 s', + 6 => '1/4 s', + 7 => '1/5 s', + 8 => '1/6 s', + 9 => '1/8 s', + 10 => '1/10 s', + 11 => '1/13 s', + 12 => '1/15 s', + 13 => '1/20 s', + 14 => '1/25 s', + 15 => '1/30 s', + 16 => '1/40 s', + 17 => '1/50 s', + 18 => '1/60 s', + 19 => '1/80 s', + 20 => '1/100 s', + 21 => '1/120 s', + 22 => '1/160 s', + 23 => '1/200 s', + 24 => '1/250 s', + 25 => '1/320 s', + 26 => '1/400 s', + 27 => '1/500 s', + 28 => '1/640 s', + 29 => '1/800 s', + 30 => '1/1000 s', + 31 => '1/1250 s', + 32 => '1/1600 s', + 33 => '1/2000 s', + 34 => '1/2500 s', + 35 => '1/3200 s', + 36 => '1/4000 s', + 37 => '1/5000 s', + 37.5 => '1/6000 s', + 38 => '1/6400 s', + 39 => '1/8000 s', + 40 => '1/10000 s', + 40.5 => '1/12000 s', + 41 => '1/13000 s', + 42 => '1/16000 s', +); + +my %languageZ9 = ( + 4 => 'English', + 5 => 'Spanish', + 7 => 'French', + 15 => 'Portuguese' +); + +my %meteringModeZ7 = ( + 0 => 'Matrix', + 1 => 'Center', + 2 => 'Spot', + 3 => 'Highlight' +); + +my %monitorBrightnessZ9 = ( + 0 => '-5', + 1 => '-4', + 2 => '-3', + 3 => '-2', + 4 => '-1', + 5 => '0', + 6 => '1', + 7 => '2', + 8 => '3', + 9 => '4', + 10 => '5', + 14 => 'Hi1', + 15 => 'Hi2', + 16 => 'Lo2', + 17 => 'Lo1', +); + +my %movieFlickerReductionZ9 = ( + 0 => 'Auto', + 1 => '50Hz', + 2 => '60Hz', +); + +my %movieFrameRateZ7 = ( + 0 => '120p', + 1 => '100p', + 2 => '60p', + 3 => '50p', + 4 => '30p', + 5 => '25p', + 6 => '24p', +); + +my %movieFrameSizeZ9 = ( + 1 => '1920x1080', + 2 => '3840x2160', + 3 => '7680x4320', +); + +my %movieToneMapZ9 = ( + 0 => 'SDR', + 1 => 'HLG', + 2 => 'N-Log', +); + +my %movieTypeZ9 = ( + 1 => 'H.265 8-bit (MP4)', + 2 => 'H.265 8-bit (MOV)', + 3 => 'H.265 10-bit (MOV)', + 4 => 'ProRes 422 HQ 10-bit (MOV)', + 5 => 'ProRes RAW HQ 12-bit (MOV)', + 6 => 'NRAW 12-bit (NEV)' +); + +my %multipleExposureModeZ9 = ( + 0 => 'Off', + 1 => 'On', + 2 => 'On (Series)', +); + +my %nonCPULensApertureZ8 = ( # 2**(val/6) rounded - non-CPU aperture interface, values and storage differ from the Z8 + 12 => 'f/1.2', + 24 => 'f/1.4', + 40 => 'f/1.8', + 48 => 'f/2.0', + 64 => 'f/2.5', + 72 => 'f/2.8', + 84 => 'f/3.3', + 88 => 'f/3.5', + 96 => 'f/4.0', + 104 => 'f/4.5', + 112 => 'f/5.0', + 120 => 'f/5.6', + 128 => 'f/6.3', + 136 => 'f/7.1', + 144 => 'f/8', + 156 => 'f/9.5', + 168 => 'f/11', + 180 => 'f/13', + 188 => 'f/15', + 192 => 'f/16', + 204 => 'f/19', + 216 => 'f/22', + 313 => 'N/A', #camera menu shows "--" indicating value has not been set for the lens +); + +my %offLowNormalHighZ7 = ( + 0 => 'Off', + 1 => 'Low', + 2 => 'Normal', + 3 => 'High', +); + +my %portraitImpressionBalanceZ8 = ( + 0 => 'Off', + 1 => 'Mode 1', + 2 => 'Mode 2', + 3 => 'Mode 3', +); + +my %releaseModeZ7 = ( + 0 => 'Continuous Low', + 1 => 'Continuous High', + 2 => 'Continuous High (Extended)', + 4 => 'Timer', + 5 => 'Single Frame', +); + +my %secondarySlotFunctionZ9 = ( + 0 => 'Overflow', + 1 => 'Backup', + 2 => 'NEF Primary + JPG Secondary', + 3 => 'JPG Primary + JPG Secondary', +); + +my %subjectDetectionZ9 = ( + 0 => 'Off', + 1 => 'Auto', + 2 => 'People', + 3 => 'Animals', + 4 => 'Vehicles', + 6 => 'Airplanes', +); + +my %timeZoneZ9 = ( + 3 => '+10:00 (Sydney)', + 5 => '+09:00 (Tokyo)', + 6 => '+08:00 (Beijing, Honk Kong, Sinapore)', + 10 => '+05:45 (Kathmandu)', + 11 => '+05:30 (New Dehli)', + 12 => '+05:00 (Islamabad)', + 13 => '+04:30 (Kabul)', + 14 => '+04:00 (Abu Dhabi)', + 15 => '+03:30 (Tehran)', + 16 => '+03:00 (Moscow, Nairobi)', + 17 => '+02:00 (Athens, Helsinki)', + 18 => '+01:00 (Madrid, Paris, Berlin)', + 19 => '+00:00 (London)', + 20 => '-01:00 (Azores)', + 21 => '-02:00 (Fernando de Noronha)', + 22 => '-03:00 (Buenos Aires, Sao Paulo)', + 23 => '-03:30 (Newfoundland)', + 24 => '-04:00 (Manaus, Caracas)', + 25 => '-05:00 (New York, Toronto, Lima)', + 26 => '-06:00 (Chicago, Mexico City)', + 27 => '-07:00 (Denver)', + 28 => '-08:00 (Los Angeles, Vancouver)', + 29 => '-09:00 (Anchorage)', + 30 => '-10:00 (Hawaii)', +); + + +my %vRModeZ9 = ( + 0 => 'Off', + 1 => 'Normal', + 2 => 'Sport', +); + +my %retouchValues = ( #PH + 0 => 'None', + 3 => 'B & W', + 4 => 'Sepia', + 5 => 'Trim', + 6 => 'Small Picture', + 7 => 'D-Lighting', + 8 => 'Red Eye', + 9 => 'Cyanotype', + 10 => 'Sky Light', + 11 => 'Warm Tone', + 12 => 'Color Custom', + 13 => 'Image Overlay', + 14 => 'Red Intensifier', + 15 => 'Green Intensifier', + 16 => 'Blue Intensifier', + 17 => 'Cross Screen', + 18 => 'Quick Retouch', + 19 => 'NEF Processing', + 23 => 'Distortion Control', + 25 => 'Fisheye', + 26 => 'Straighten', + 29 => 'Perspective Control', + 30 => 'Color Outline', + 31 => 'Soft Filter', + 32 => 'Resize', #31 + 33 => 'Miniature Effect', + 34 => 'Skin Softening', # (S9200) + 35 => 'Selected Frame', #31 (frame exported from MOV) + 37 => 'Color Sketch', #31 + 38 => 'Selective Color', # (S9200) + 39 => 'Glamour', # (S3500) + 40 => 'Drawing', # (S9200) + 44 => 'Pop', # (S3500) + 45 => 'Toy Camera Effect 1', # (S3500) + 46 => 'Toy Camera Effect 2', # (S3500) + 47 => 'Cross Process (red)', # (S3500) + 48 => 'Cross Process (blue)', # (S3500) + 49 => 'Cross Process (green)', # (S3500) + 50 => 'Cross Process (yellow)', # (S3500) + 51 => 'Super Vivid', # (S3500) + 52 => 'High-contrast Monochrome', # (S3500) + 53 => 'High Key', # (S3500) + 54 => 'Low Key', # (S3500) +); + +# AF point indices for models with 51 focus points, eg. D3 (ref JD/PH) +# A1 A2 A3 A4 A5 A6 A7 A8 A9 +# B1 B2 B3 B4 B5 B6 B7 B8 B9 B10 B11 +# C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 +# D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 +# E1 E2 E3 E4 E5 E6 E7 E8 E9 +my %afPoints51 = ( + 1 => 'C6', 11 => 'C5', 21 => 'C9', 31 => 'C11',41 => 'A2', 51 => 'D1', + 2 => 'B6', 12 => 'B5', 22 => 'B9', 32 => 'B11',42 => 'D3', + 3 => 'A5', 13 => 'A4', 23 => 'A8', 33 => 'D11',43 => 'E2', + 4 => 'D6', 14 => 'D5', 24 => 'D9', 34 => 'C4', 44 => 'C2', + 5 => 'E5', 15 => 'E4', 25 => 'E8', 35 => 'B4', 45 => 'B2', + 6 => 'C7', 16 => 'C8', 26 => 'C10',36 => 'A3', 46 => 'A1', + 7 => 'B7', 17 => 'B8', 27 => 'B10',37 => 'D4', 47 => 'D2', + 8 => 'A6', 18 => 'A7', 28 => 'A9', 38 => 'E3', 48 => 'E1', + 9 => 'D7', 19 => 'D8', 29 => 'D10',39 => 'C3', 49 => 'C1', + 10 => 'E6', 20 => 'E7', 30 => 'E9', 40 => 'B3', 50 => 'B1', +); + +# AF point indices for models with 39 focus points, eg. D7000 (ref 29) +# A1 A2 A3 +# B1 B2 B3 B4 B5 B6 B7 B8 B9 B10 B11 +# C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 +# D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 +# E1 E2 E3 +my %afPoints39 = ( + 1 => 'C6', 11 => 'C5', 21 => 'D9', 31 => 'C3', + 2 => 'B6', 12 => 'B5', 22 => 'C10',32 => 'B3', + 3 => 'A2', 13 => 'A1', 23 => 'B10',33 => 'D3', + 4 => 'D6', 14 => 'D5', 24 => 'D10',34 => 'C2', + 5 => 'E2', 15 => 'E1', 25 => 'C11',35 => 'B2', + 6 => 'C7', 16 => 'C8', 26 => 'B11',36 => 'D2', + 7 => 'B7', 17 => 'B8', 27 => 'D11',37 => 'C1', + 8 => 'A3', 18 => 'D8', 28 => 'C4', 38 => 'B1', + 9 => 'D7', 19 => 'C9', 29 => 'B4', 39 => 'D1', + 10 => 'E3', 20 => 'B9', 30 => 'D4', +); + +# AF point indices for models with 105 focus points, eg. D6 (ref 28) +# - 7 rows (A-G) with 15 columns (1-15), center is D8 +my %afPoints105 = ( + 1 => 'D8', 28 => 'G7', 55 => 'F13', 82 => 'E4', + 2 => 'C8', 29 => 'D6', 56 => 'G13', 83 => 'F4', + 3 => 'B8', 30 => 'C6', 57 => 'D14', 84 => 'G4', + 4 => 'A8', 31 => 'B6', 58 => 'C14', 85 => 'D3', + 5 => 'E8', 32 => 'A6', 59 => 'B14', 86 => 'C3', + 6 => 'F8', 33 => 'E6', 60 => 'A14', 87 => 'B3', + 7 => 'G8', 34 => 'F6', 61 => 'E14', 88 => 'A3', + 8 => 'D9', 35 => 'G6', 62 => 'F14', 89 => 'E3', + 9 => 'C9', 36 => 'D11', 63 => 'G14', 90 => 'F3', + 10 => 'B9', 37 => 'C11', 64 => 'D15', 91 => 'G3', + 11 => 'A9', 38 => 'B11', 65 => 'C15', 92 => 'D2', + 12 => 'E9', 39 => 'A11', 66 => 'B15', 93 => 'C2', + 13 => 'F9', 40 => 'E11', 67 => 'A15', 94 => 'B2', + 14 => 'G9', 41 => 'F11', 68 => 'E15', 95 => 'A2', + 15 => 'D10', 42 => 'G11', 69 => 'F15', 96 => 'E2', + 16 => 'C10', 43 => 'D12', 70 => 'G15', 97 => 'F2', + 17 => 'B10', 44 => 'C12', 71 => 'D5', 98 => 'G2', + 18 => 'A10', 45 => 'B12', 72 => 'C5', 99 => 'D1', + 19 => 'E10', 46 => 'A12', 73 => 'B5', 100 => 'C1', + 20 => 'F10', 47 => 'E12', 74 => 'A5', 101 => 'B1', + 21 => 'G10', 48 => 'F12', 75 => 'E5', 102 => 'A1', + 22 => 'D7', 49 => 'G12', 76 => 'F5', 103 => 'E1', + 23 => 'C7', 50 => 'D13', 77 => 'G5', 104 => 'F1', + 24 => 'B7', 51 => 'C13', 78 => 'D4', 105 => 'G1', + 25 => 'A7', 52 => 'B13', 79 => 'C4', + 26 => 'E7', 53 => 'A13', 80 => 'B4', + 27 => 'F7', 54 => 'E13', 81 => 'A4', +); + +# AF point indices for models with 135 focus points, eg. 1J1 (ref PH) +# - 9 rows (A-I) with 15 columns (1-15), center is E8 +# - odd columns, columns 2 and 14, and the remaining corner points are +# not used in 41-point mode +my %afPoints135 = ( + 1 => 'E8', 28 => 'E10', 55 => 'E13', 82 => 'E6', 109 => 'E3', + 2 => 'D8', 29 => 'D10', 56 => 'D13', 83 => 'D6', 110 => 'D3', + 3 => 'C8', 30 => 'C10', 57 => 'C13', 84 => 'C6', 111 => 'C3', + 4 => 'B8', 31 => 'B10', 58 => 'B13', 85 => 'B6', 112 => 'B3', + 5 => 'A8', 32 => 'A10', 59 => 'A13', 86 => 'A6', 113 => 'A3', + 6 => 'F8', 33 => 'F10', 60 => 'F13', 87 => 'F6', 114 => 'F3', + 7 => 'G8', 34 => 'G10', 61 => 'G13', 88 => 'G6', 115 => 'G3', + 8 => 'H8', 35 => 'H10', 62 => 'H13', 89 => 'H6', 116 => 'H3', + 9 => 'I8', 36 => 'I10', 63 => 'I13', 90 => 'I6', 117 => 'I3', + 10 => 'E9', 37 => 'E11', 64 => 'E14', 91 => 'E5', 118 => 'E2', + 11 => 'D9', 38 => 'D11', 65 => 'D14', 92 => 'D5', 119 => 'D2', + 12 => 'C9', 39 => 'C11', 66 => 'C14', 93 => 'C5', 120 => 'C2', + 13 => 'B9', 40 => 'B11', 67 => 'B14', 94 => 'B5', 121 => 'B2', + 14 => 'A9', 41 => 'A11', 68 => 'A14', 95 => 'A5', 122 => 'A2', + 15 => 'F9', 42 => 'F11', 69 => 'F14', 96 => 'F5', 123 => 'F2', + 16 => 'G9', 43 => 'G11', 70 => 'G14', 97 => 'G5', 124 => 'G2', + 17 => 'H9', 44 => 'H11', 71 => 'H14', 98 => 'H5', 125 => 'H2', + 18 => 'I9', 45 => 'I11', 72 => 'I14', 99 => 'I5', 126 => 'I2', + 19 => 'E7', 46 => 'E12', 73 => 'E15', 100 => 'E4', 127 => 'E1', + 20 => 'D7', 47 => 'D12', 74 => 'D15', 101 => 'D4', 128 => 'D1', + 21 => 'C7', 48 => 'C12', 75 => 'C15', 102 => 'C4', 129 => 'C1', + 22 => 'B7', 49 => 'B12', 76 => 'B15', 103 => 'B4', 130 => 'B1', + 23 => 'A7', 50 => 'A12', 77 => 'A15', 104 => 'A4', 131 => 'A1', + 24 => 'F7', 51 => 'F12', 78 => 'F15', 105 => 'F4', 132 => 'F1', + 25 => 'G7', 52 => 'G12', 79 => 'G15', 106 => 'G4', 133 => 'G1', + 26 => 'H7', 53 => 'H12', 80 => 'H15', 107 => 'H4', 134 => 'H1', + 27 => 'I7', 54 => 'I12', 81 => 'I15', 108 => 'I4', 135 => 'I1', +); + +# AF point indices for models with 153 focus points, eg. D5,D500 (ref PH) +# - 9 rows (A-I) with 17 columns (1-17), center is E9 +# - 55 of these are selectable cross points (odd rows and columns 1,3,4,6,7,9,11,12,14,15,17) +my %afPoints153 = ( + 1 => 'E9', 32 => 'A8', 63 => 'I13', 94 => 'B17', 125 => 'H4', + 2 => 'D9', 33 => 'F8', 64 => 'E14', 95 => 'A17', 126 => 'I4', + 3 => 'C9', 34 => 'G8', 65 => 'D14', 96 => 'F17', 127 => 'E3', + 4 => 'B9', 35 => 'H8', 66 => 'C14', 97 => 'G17', 128 => 'D3', + 5 => 'A9', 36 => 'I8', 67 => 'B14', 98 => 'H17', 129 => 'C3', + 6 => 'F9', 37 => 'E7', 68 => 'A14', 99 => 'I17', 130 => 'B3', + 7 => 'G9', 38 => 'D7', 69 => 'F14', 100 => 'E6', 131 => 'A3', + 8 => 'H9', 39 => 'C7', 70 => 'G14', 101 => 'D6', 132 => 'F3', + 9 => 'I9', 40 => 'B7', 71 => 'H14', 102 => 'C6', 133 => 'G3', + 10 => 'E10', 41 => 'A7', 72 => 'I14', 103 => 'B6', 134 => 'H3', + 11 => 'D10', 42 => 'F7', 73 => 'E15', 104 => 'A6', 135 => 'I3', + 12 => 'C10', 43 => 'G7', 74 => 'D15', 105 => 'F6', 136 => 'E2', + 13 => 'B10', 44 => 'H7', 75 => 'C15', 106 => 'G6', 137 => 'D2', + 14 => 'A10', 45 => 'I7', 76 => 'B15', 107 => 'H6', 138 => 'C2', + 15 => 'F10', 46 => 'E12', 77 => 'A15', 108 => 'I6', 139 => 'B2', + 16 => 'G10', 47 => 'D12', 78 => 'F15', 109 => 'E5', 140 => 'A2', + 17 => 'H10', 48 => 'C12', 79 => 'G15', 110 => 'D5', 141 => 'F2', + 18 => 'I10', 49 => 'B12', 80 => 'H15', 111 => 'C5', 142 => 'G2', + 19 => 'E11', 50 => 'A12', 81 => 'I15', 112 => 'B5', 143 => 'H2', + 20 => 'D11', 51 => 'F12', 82 => 'E16', 113 => 'A5', 144 => 'I2', + 21 => 'C11', 52 => 'G12', 83 => 'D16', 114 => 'F5', 145 => 'E1', + 22 => 'B11', 53 => 'H12', 84 => 'C16', 115 => 'G5', 146 => 'D1', + 23 => 'A11', 54 => 'I12', 85 => 'B16', 116 => 'H5', 147 => 'C1', + 24 => 'F11', 55 => 'E13', 86 => 'A16', 117 => 'I5', 148 => 'B1', + 25 => 'G11', 56 => 'D13', 87 => 'F16', 118 => 'E4', 149 => 'A1', + 26 => 'H11', 57 => 'C13', 88 => 'G16', 119 => 'D4', 150 => 'F1', + 27 => 'I11', 58 => 'B13', 89 => 'H16', 120 => 'C4', 151 => 'G1', + 28 => 'E8', 59 => 'A13', 90 => 'I16', 121 => 'B4', 152 => 'H1', + 29 => 'D8', 60 => 'F13', 91 => 'E17', 122 => 'A4', 153 => 'I1', + 30 => 'C8', 61 => 'G13', 92 => 'D17', 123 => 'F4', + 31 => 'B8', 62 => 'H13', 93 => 'C17', 124 => 'G4', +); + +# AF point indices for models with 81 focus points, eg. Z6/Z7/Z50 (ref 38) +# - 9 rows (A-I) with 9 columns (1-9), center is E5 +# +# 7 6 5 4 3 2 1 0 +# 00 : [H5][G5][F5][A5][B5][C5][D5][E5] +# 01 : [G6][F6][A6][B6][C6][D6][E6][I5] +# 02 : [F4][A4][B4][C4][D4][E4][I6][H6] +# 03 : [A7][B7][C7][D7][E7][I4][H4][G4] +# 04 : [B3][C3][D3][E3][I7][H7][G7][F7] +# 05 : [C8][D8][E8][I3][H3][G3][F3][A3] +# 06 : [D2][E2][I8][H8][G8][F8][A8][B8] +# 07 : [E9][I2][H2][G2][F2][A2][B2][C2] +# 08 : [I9][H9][G9][F9][A9][B9][C9][D9] +# 09 : [H1][G1][F1][A1][B1][C1][D1][E1] +# 0a : [ ][ ][ ][ ][ ][ ][ ][I1] +my %afPoints81 = ( + 1 => 'E5', 18 => 'I6', 35 => 'H7', 52 => 'G8', 69 => 'F9', + 2 => 'D5', 19 => 'E4', 36 => 'I7', 53 => 'H8', 70 => 'G9', + 3 => 'C5', 20 => 'D4', 37 => 'E3', 54 => 'I8', 71 => 'H9', + 4 => 'B5', 21 => 'C4', 38 => 'D3', 55 => 'E2', 72 => 'I9', + 5 => 'A5', 22 => 'B4', 39 => 'C3', 56 => 'D2', 73 => 'E1', + 6 => 'F5', 23 => 'A4', 40 => 'B3', 57 => 'C2', 74 => 'D1', + 7 => 'G5', 24 => 'F4', 41 => 'A3', 58 => 'B2', 75 => 'C1', + 8 => 'H5', 25 => 'G4', 42 => 'F3', 59 => 'A2', 76 => 'B1', + 9 => 'I5', 26 => 'H4', 43 => 'G3', 60 => 'F2', 77 => 'A1', + 10 => 'E6', 27 => 'I4', 44 => 'H3', 61 => 'G2', 78 => 'F1', + 11 => 'D6', 28 => 'E7', 45 => 'I3', 62 => 'H2', 79 => 'G1', + 12 => 'C6', 29 => 'D7', 46 => 'E8', 63 => 'I2', 80 => 'H1', + 13 => 'B6', 30 => 'C7', 47 => 'D8', 64 => 'E9', 81 => 'I1', + 14 => 'A6', 31 => 'B7', 48 => 'C8', 65 => 'D9', + 15 => 'F6', 32 => 'A7', 49 => 'B8', 66 => 'C9', + 16 => 'G6', 33 => 'F7', 50 => 'A8', 67 => 'B9', + 17 => 'H6', 34 => 'G7', 51 => 'F8', 68 => 'A9', +); + +# AF point indices for models with 493 focus points, eg. Z8/Z9 (ref 28) +# - Auto Area AF uses 15 of the 17 rows (A-Q) and 27 of the 29 columns (1-27), center is H14 (405 of the 493 focus points can be used by Auto-area AF) +# +my %afPoints493 = ( + 1 => 'A1', 28 => 'B1', 55 => 'C1', 82 => 'D1', 109 => 'E1', 136 => 'F1', 163 => 'G1', 190 => 'H1', + 2 => 'A2', 29 => 'B2', 56 => 'C2', 83 => 'D2', 110 => 'E2', 137 => 'F2', 164 => 'G2', 191 => 'H2', + 3 => 'A3', 30 => 'B3', 57 => 'C3', 84 => 'D3', 111 => 'E3', 138 => 'F3', 165 => 'G3', 192 => 'H3', + 4 => 'A4', 31 => 'B4', 58 => 'C4', 85 => 'D4', 112 => 'E4', 139 => 'F4', 166 => 'G4', 193 => 'H4', + 5 => 'A5', 32 => 'B5', 59 => 'C5', 86 => 'D5', 113 => 'E5', 140 => 'F5', 167 => 'G5', 194 => 'H5', + 6 => 'A6', 33 => 'B6', 60 => 'C6', 87 => 'D6', 114 => 'E6', 141 => 'F6', 168 => 'G6', 195 => 'H6', + 7 => 'A7', 34 => 'B7', 61 => 'C7', 88 => 'D7', 115 => 'E7', 142 => 'F7', 169 => 'G7', 196 => 'H7', + 8 => 'A8', 35 => 'B8', 62 => 'C8', 89 => 'D8', 116 => 'E8', 143 => 'F8', 170 => 'G8', 197 => 'H8', + 9 => 'A9', 36 => 'B9', 63 => 'C9', 90 => 'D9', 117 => 'E9', 144 => 'F9', 171 => 'G9', 198 => 'H9', + 10 => 'A10', 37 => 'B10', 64 => 'C10', 91 => 'D10', 118 => 'E10', 145 => 'F10', 172 => 'G10', 199 => 'H10', + 11 => 'A11', 38 => 'B11' , 65 => 'C11', 92 => 'D11', 119 => 'E11', 146 => 'F11', 173 => 'G11', 200 => 'H11', + 12 => 'A12', 39 => 'B12' , 66 => 'C12', 93 => 'D12', 120 => 'E12', 147 => 'F12', 174 => 'G12', 201 => 'H12', + 13 => 'A13', 40 => 'B13' , 67 => 'C13', 94 => 'D13', 121 => 'E13', 148 => 'F13', 175 => 'G13', 202 => 'H13', + 14 => 'A14', 41 => 'B14' , 68 => 'C14', 95 => 'D14', 122 => 'E14', 149 => 'F14', 176 => 'G14', 203 => 'H14', + 15 => 'A15', 42 => 'B15', 69 => 'C15', 96 => 'D15', 123 => 'E15', 150 => 'F15', 177 => 'G15', 204 => 'H15', + 16 => 'A16', 43 => 'B16' , 70 => 'C16', 97 => 'D16', 124 => 'E16', 151 => 'F16', 178 => 'G16', 205 => 'H16', + 17 => 'A17', 44 => 'B17', 71 => 'C17', 98 => 'D17', 125 => 'E17', 152 => 'F17', 179 => 'G17', 206 => 'H17', + 18 => 'A18', 45 => 'B18', 72 => 'C18', 99 => 'D18', 126 => 'E18', 153 => 'F18', 180 => 'G18', 207 => 'H18', + 19 => 'A19', 46 => 'B19', 73 => 'C19', 100 => 'D19', 127 => 'E19', 154 => 'F19', 181 => 'G19', 208 => 'H19', + 20 => 'A20', 47 => 'B20', 74 => 'C20', 101 => 'D20', 128 => 'E20', 155 => 'F20', 182 => 'G20', 209 => 'H20', + 21 => 'A21', 48 => 'B21', 75 => 'C21', 102 => 'D21', 129 => 'E21', 156 => 'F21', 183 => 'G21', 210 => 'H21', + 22 => 'A22', 49 => 'B22', 76 => 'C22', 103 => 'D22', 130 => 'E22', 157 => 'F22', 184 => 'G22', 211 => 'H22', + 23 => 'A23', 50 => 'B23', 77 => 'C23', 104 => 'D23', 131 => 'E23', 158 => 'F23', 185 => 'G23', 212 => 'H23', + 24 => 'A24', 51 => 'B24', 78 => 'C24', 105 => 'D24', 132 => 'E24', 159 => 'F24', 186 => 'G24', 213 => 'H24', + 25 => 'A25', 52 => 'B25', 79 => 'C25', 106 => 'D25', 133 => 'E25', 160 => 'F25', 187 => 'G25', 214 => 'H25', + 26 => 'A26', 53 => 'B26', 80 => 'C26', 107 => 'D26', 134 => 'E26', 161 => 'F26', 188 => 'G26', 215 => 'H26', + 27 => 'A27', 54 => 'B27', 81 => 'C27', 108 => 'D27', 135 => 'E27', 162 => 'F27', 189 => 'G27', 216 => 'H27', + + 217 => 'I1', 244 => 'J1', 271 => 'K1', 298 => 'L1', 325 => 'M1', 352 => 'N1', 379 => 'O1', + 218 => 'I2', 245 => 'J2', 272 => 'K2', 299 => 'L2', 326 => 'M2', 353 => 'N2', 380 => 'O2', + 219 => 'I3', 246 => 'J3', 273 => 'K3', 300 => 'L3', 327 => 'M3', 354 => 'N3', 381 => 'O3', + 220 => 'I4', 247 => 'J4', 274 => 'K4', 301 => 'L4', 328 => 'M4', 355 => 'N4', 382 => 'O4', + 221 => 'I5', 248 => 'J5', 275 => 'K5', 302 => 'L5', 329 => 'M5', 356 => 'N5', 383 => 'O5', + 222 => 'I6', 249 => 'J6', 276 => 'K6', 303 => 'L6', 330 => 'M6', 357 => 'N6', 384 => 'O6', + 223 => 'I7', 250 => 'J7', 277 => 'K7', 304 => 'L7', 331 => 'M7', 358 => 'N7', 385 => 'O7', + 224 => 'I8', 251 => 'J8', 278 => 'K8', 305 => 'L8', 332 => 'M8', 359 => 'N8', 386 => 'O8', + 225 => 'I9', 252 => 'J9', 279 => 'K9', 306 => 'L9', 333 => 'M9', 360 => 'N9', 387 => 'O9', + 226 => 'I10', 253 => 'J10', 280 => 'K10', 307 => 'L10', 334 => 'M10', 361 => 'N10', 388 => 'O10', + 227 => 'I11', 254 => 'J11', 281 => 'K11', 308 => 'L11', 335 => 'M11', 362 => 'N11', 389 => 'O11', + 228 => 'I12', 255 => 'J12', 282 => 'K12', 309 => 'L12', 336 => 'M12', 363 => 'N12', 390 => 'O12', + 229 => 'I13', 256 => 'J13', 283 => 'K13', 310 => 'L13', 337 => 'M13', 364 => 'N13', 391 => 'O13', + 230 => 'I14', 257 => 'J14', 284 => 'K14', 311 => 'L14', 338 => 'M14', 365 => 'N14', 392 => 'O14', + 231 => 'I15', 258 => 'J15', 285 => 'K15', 312 => 'L15', 339 => 'M15', 366 => 'N15', 393 => 'O15', + 232 => 'I16', 259 => 'J16', 286 => 'K16', 313 => 'L16', 340 => 'M16', 367 => 'N16', 394 => 'O16', + 233 => 'I17', 260 => 'J17', 287 => 'K17', 314 => 'L17', 341 => 'M17', 368 => 'N17', 395 => 'O17', + 234 => 'I18', 261 => 'J18', 288 => 'K18', 315 => 'L18', 342 => 'M18', 369 => 'N18', 396 => 'O18', + 235 => 'I19', 262 => 'J19', 289 => 'K19', 316 => 'L19', 343 => 'M19', 370 => 'N19', 397 => 'O19', + 236 => 'I20', 263 => 'J20', 290 => 'K20', 317 => 'L20', 344 => 'M20', 371 => 'N20', 398 => 'O20', + 237 => 'I21', 264 => 'J21', 291 => 'K21', 318 => 'L21', 345 => 'M21', 372 => 'N21', 399 => 'O21', + 238 => 'I22', 265 => 'J22', 292 => 'K22', 319 => 'L22', 346 => 'M22', 373 => 'N22', 400 => 'O22', + 239 => 'I23', 266 => 'J23', 293 => 'K23', 320 => 'L23', 347 => 'M23', 374 => 'N23', 401 => 'O23', + 240 => 'I24', 267 => 'J24', 294 => 'K24', 321 => 'L24', 348 => 'M24', 375 => 'N24', 402 => 'O24', + 241 => 'I25', 268 => 'J25', 295 => 'K25', 322 => 'L25', 349 => 'M25', 376 => 'N25', 403 => 'O25', + 242 => 'I26', 269 => 'J26', 296 => 'K26', 323 => 'L26', 350 => 'M26', 377 => 'N26', 404 => 'O26', + 243 => 'I27', 270 => 'J27', 297 => 'K27', 324 => 'L27', 351 => 'M27', 378 => 'N27', 405 => 'O27', +); +my %cropHiSpeed = ( #IB + 0 => 'Off', + 1 => '1.3x Crop', # (1.3x Crop, Large) + 2 => 'DX Crop', # (1.5x) + 3 => '5:4 Crop', + 4 => '3:2 Crop', # (1.2x, ref 36) + 6 => '16:9 Crop', + 8 => '2.7x Crop', #36 (D4/D500) + 9 => 'DX Movie Crop', # (DX during movie recording, Large) + 10 => '1.3x Movie Crop', #36 (D4/D500) + 11 => 'FX Uncropped', + 12 => 'DX Uncropped', + 13 => '2.8x Movie Crop', #28 (D5/D6) 5584/1936 + 14 => '1.4x Movie Crop', #28 (D5/D6) 5584/3856 + 15 => '1.5x Movie Crop', #36 (D4/D500) 5600/3872 + 17 => '1:1 Crop', + OTHER => sub { + my ($val, $inv, $conv) = @_; + return undef if $inv; + my @a = split ' ', $val; + return "Unknown ($val)" unless @a == 7; + $a[0] = $$conv{$a[0]} || "Unknown ($a[0])"; + return "$a[0] ($a[1]x$a[2] cropped to $a[3]x$a[4] at pixel $a[5],$a[6])"; + }, +); + +my %flashGroupOptionsMode = ( + 0 => 'TTL', + 1 => 'Manual', + 2 => 'Auto', + 3 => 'Off', +); + +my %nefCompression = ( #28 relocated to MakerNotes_0x51 at offset x'0a (Z9) + 1 => 'Lossy (type 1)', # (older models) + 2 => 'Uncompressed', #JD - D100 (even though TIFF compression is set!) + 3 => 'Lossless', + 4 => 'Lossy (type 2)', + 5 => 'Striped packed 12 bits', #IB + 6 => 'Uncompressed (reduced to 12 bit)', #IB + 7 => 'Unpacked 12 bits', #IB (padded to 16) + 8 => 'Small', #IB + 9 => 'Packed 12 bits', #IB (2 pixels in 3 bytes) + 10 => 'Packed 14 bits', #28 (4 pixels in 7 bytes, eg. D6 uncompressed 14 bit) + 13 => 'High Efficiency', #28 + 14 => 'High Efficiency*', #28 +); + +my %noYes = ( 0 => 'No' , 1 => 'Yes', ); +my %offOn = ( 0 => 'Off', 1 => 'On' ); +my %onOff = ( 0 => 'On', 1 => 'Off' ); + +# common attributes for writable BinaryData directories +my %binaryDataAttrs = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, +); + +my %base64bin = ( ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)' ); +my %base64int32u = ( ValueConv => 'my $val=Image::ExifTool::XMP::DecodeBase64($val); unpack("V",$$val)' ); +my %base64bytes = ( ValueConv => 'my $val=Image::ExifTool::XMP::DecodeBase64($val); join(".",unpack("C*",$$val))' ); +my %base64double = ( + ValueConv => q{ + my $val=Image::ExifTool::XMP::DecodeBase64($val); + my $saveOrder = GetByteOrder(); + SetByteOrder('II'); + $val = GetDouble($val,0); + SetByteOrder($saveOrder); + return $val; + }, +); +my %base64coord = ( + ValueConv => q{ + my $val=Image::ExifTool::XMP::DecodeBase64($val); + my $saveOrder = GetByteOrder(); + SetByteOrder('II'); + $val = GetDouble($val,0) + GetDouble($val,8)/60 + GetDouble($val,16)/3600; + SetByteOrder($saveOrder); + return $val; + }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', +); +# Nikon maker note tags +%Image::ExifTool::Nikon::Main = ( + PROCESS_PROC => \&Image::ExifTool::Nikon::ProcessNikon, + WRITE_PROC => \&Image::ExifTool::Nikon::ProcessNikon, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PRINT_CONV => \&FormatString, + 0x0001 => { #2 + # the format differs for different models. for D70, this is a string '0210', + # but for the E775 it is binary: "\x00\x01\x00\x00" + Name => 'MakerNoteVersion', + Writable => 'undef', + Count => 4, + # convert to string if binary + ValueConv => '$_=$val; /^[\x00-\x09]/ and $_=join("",unpack("CCCC",$_)); $_', + ValueConvInv => '$val', + PrintConv => '$_=$val;s/^(\d{2})/$1\./;s/^0//;$_', + PrintConvInv => '$_=$val;s/\.//;"0$_"', + }, + 0x0002 => { + # this is the ISO actually used by the camera + # (may be different than ISO setting if auto) + Name => 'ISO', + Writable => 'int16u', + Count => 2, + Priority => 0, # the EXIF ISO is more reliable + Groups => { 2 => 'Image' }, + # D300 sets this to undef with 4 zero bytes when LO ISO is used - PH + RawConv => '$val eq "\0\0\0\0" ? undef : $val', + # first number is 1 for "Hi ISO" modes (H0.3, H0.7 and H1.0 on D80) - PH + PrintConv => '$_=$val;s/^0 //;s/^1 (\d+)/Hi $1/;$_', + PrintConvInv => '$_=$val;/^\d+/ ? "0 $_" : (s/Hi ?//i ? "1 $_" : $_)', + }, + # Note: we attempt to fix the case of these string values (typically written in all caps) + 0x0003 => { Name => 'ColorMode', Writable => 'string' }, + 0x0004 => { Name => 'Quality', Writable => 'string' }, + 0x0005 => { Name => 'WhiteBalance', Writable => 'string' }, + 0x0006 => { Name => 'Sharpness', Writable => 'string' }, + 0x0007 => { + Name => 'FocusMode', + RawConv => '$$self{FocusMode} = $val', + Writable => 'string', + }, + # FlashSetting (better named FlashSyncMode, ref 28) values: + # "Normal", "Slow", "Rear Slow", "RED-EYE", "RED-EYE SLOW" + 0x0008 => { Name => 'FlashSetting', Writable => 'string' }, + # FlashType observed values: + # internal: "Built-in,TTL", "Built-in,RPT", "Comdr.", "NEW_TTL" + # external: "Optional,TTL", "Optional,RPT", "Optional,M", "Comdr." + # both: "Built-in,TTL&Comdr." + # no flash: "" + 0x0009 => { Name => 'FlashType', Writable => 'string' }, #2 (count varies by model - PH) + # 0x000a - rational values: 5.6 to 9.33 - found in Coolpix models - PH + # (seems constant for a given camera model, but not correlated with scale factor) + 0x000b => { #2 + Name => 'WhiteBalanceFineTune', + Writable => 'int16s', + Count => -1, # older models write 1 value, newer DSLR's write 2 - PH + }, + 0x000c => { # (D1X) + Name => 'WB_RBLevels', + Writable => 'rational64u', + Count => 4, # (not sure what the last 2 values are for) + }, + 0x000d => { #15 + Name => 'ProgramShift', + Writable => 'undef', + Count => 4, + ValueConv => 'my ($a,$b,$c)=unpack("c3",$val); $c ? $a*($b/$c) : 0', + ValueConvInv => q{ + my $a = int($val*6 + ($val>0 ? 0.5 : -0.5)); + $a<-128 or $a>127 ? undef : pack("c4",$a,1,6,0); + }, + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x000e => { + Name => 'ExposureDifference', + Writable => 'undef', + Count => 4, + ValueConv => 'my ($a,$b,$c)=unpack("c3",$val); $c ? $a*($b/$c) : 0', + ValueConvInv => q{ + my $a = int($val*12 + ($val>0 ? 0.5 : -0.5)); + $a<-128 or $a>127 ? undef : pack("c4",$a,1,12,0); + }, + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 0x000f => { Name => 'ISOSelection', Writable => 'string' }, #2 + 0x0010 => { + Name => 'DataDump', + Writable => 0, + Binary => 1, + }, + 0x0011 => { + Name => 'PreviewIFD', + Groups => { 1 => 'PreviewIFD', 2 => 'Image' }, + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::PreviewIFD', + Start => '$val', + }, + }, + 0x0012 => { #2 (camera setting: combination of command dial and menus - PH) + Name => 'FlashExposureComp', + Description => 'Flash Exposure Compensation', + Writable => 'undef', + Count => 4, + # (includes the built-in compensation for FlashType "Built-in,TTL&Comdr.") + Notes => q{ + may be set even if flash does not fire. Does not include the effect of + flash bracketing. + }, + ValueConv => 'my ($a,$b,$c)=unpack("c3",$val); $c ? $a*($b/$c) : 0', + ValueConvInv => q{ + my $a = int($val*6 + ($val>0 ? 0.5 : -0.5)); + $a<-128 or $a>127 ? undef : pack("c4",$a,1,6,0); + }, + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + # D70 - another ISO tag + 0x0013 => { #2 + Name => 'ISOSetting', + Writable => 'int16u', + Count => 2, + PrintConv => '$_=$val;s/^0 //;$_', + PrintConvInv => '"0 $val"', + }, + 0x0014 => [ + { #4 + Name => 'ColorBalanceA', + Condition => '$format eq "undef" and $count == 2560', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalanceA', + ByteOrder => 'BigEndian', + }, + }, + { #IB + Name => 'NRWData', + Condition => '$$valPt =~ /^NRW 0100/', + Drop => 1, # 'Drop' because it is large and not found in JPEG images + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalanceB', + }, + }, + { #IB + Name => 'NRWData', + Condition => '$$valPt =~ /^NRW /', + Drop => 1, # 'Drop' because it is large and not found in JPEG images + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalanceC', + }, + }, + ], + # 0x0015 - string[8]: "AUTO " + # D70 Image boundary?? top x,y bot-right x,y + 0x0016 => { #2 + Name => 'ImageBoundary', + Writable => 'int16u', + Count => 4, + }, + 0x0017 => { #28 + Name => 'ExternalFlashExposureComp', #PH (setting from external flash unit) + Writable => 'undef', + Count => 4, + ValueConv => 'my ($a,$b,$c)=unpack("c3",$val); $c ? $a*($b/$c) : 0', + ValueConvInv => q{ + my $a = int($val*6 + ($val>0 ? 0.5 : -0.5)); + $a<-128 or $a>127 ? undef : pack("c4",$a,1,6,0); + }, + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x0018 => { #5 + Name => 'FlashExposureBracketValue', + Writable => 'undef', + Count => 4, + ValueConv => 'my ($a,$b,$c)=unpack("c3",$val); $c ? $a*($b/$c) : 0', + ValueConvInv => q{ + my $a = int($val*6 + ($val>0 ? 0.5 : -0.5)); + $a<-128 or $a>127 ? undef : pack("c4",$a,1,6,0); + }, + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x0019 => { #5 + Name => 'ExposureBracketValue', + Writable => 'rational64s', + PrintConv => '$val !~ /undef/ ? Image::ExifTool::Exif::PrintFraction($val) : "n/a" ', #undef observed for Z9 jpgs at C30/C60/C90 [data is 0/0 rather than the usual 0/6] + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x001a => { #PH + Name => 'ImageProcessing', + Writable => 'string', + }, + 0x001b => { #15 + Name => 'CropHiSpeed', + Writable => 'int16u', + Count => 7, + PrintConv => \%cropHiSpeed, + }, + 0x001c => { #28 (D3 "the application of CSb6 to the selected metering mode") + Name => 'ExposureTuning', + Writable => 'undef', + Count => 3, + ValueConv => 'my ($a,$b,$c)=unpack("c3",$val); $c ? $a*($b/$c) : 0', + ValueConvInv => q{ + my $a = int($val*6 + ($val>0 ? 0.5 : -0.5)); + $a<-128 or $a>127 ? undef : pack("c3",$a,1,6); + }, + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x001d => { #4 + Name => 'SerialNumber', + # Note: this has been known to change even if the serial number on the body + # stays the same if some parts of the camera were replaced by Nikon service + Writable => 'string', + Protected => 1, + Notes => q{ + this value is used as a key to decrypt other information -- writing this tag + causes the other information to be re-encrypted with the new key + }, + PrintConv => undef, # disable default PRINT_CONV + }, + 0x001e => { #14 + Name => 'ColorSpace', + Writable => 'int16u', + PrintConv => { + 1 => 'sRGB', + 2 => 'Adobe RGB', + 4 => 'BT.2100', #observed on Z8 with Tone Mode set to HLG + }, + }, + 0x001f => { #PH + Name => 'VRInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::VRInfo' }, + }, + 0x0020 => { #16 + Name => 'ImageAuthentication', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x0021 => { #PH + Name => 'FaceDetect', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FaceDetect' }, + }, + 0x0022 => { #21 + Name => 'ActiveD-Lighting', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 3 => 'Normal', + 5 => 'High', + 7 => 'Extra High', #10 + 8 => 'Extra High 1', #PH + 9 => 'Extra High 2', #PH + 10 => 'Extra High 3', #PH + 11 => 'Extra High 4', #PH + 0xffff => 'Auto', #10 + }, + }, + 0x0023 => [ + { #PH (D300, but also found in D3,D3S,D3X,D90,D300S,D700,D3000,D5000) + Name => 'PictureControlData', + Condition => '$$valPt =~ /^01/', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::PictureControl' }, + },{ #28 + Name => 'PictureControlData', + Condition => '$$valPt =~ /^02/', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::PictureControl2' }, + },{ + Name => 'PictureControlData', + Condition => '$$valPt =~ /^03/', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::PictureControl3' }, + },{ + Name => 'PictureControlData', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::PictureControlUnknown' }, + }, + ], + 0x0024 => { #JD + Name => 'WorldTime', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::WorldTime', + # (CaptureNX does flip the byte order of this record) + }, + }, + 0x0025 => { #PH + Name => 'ISOInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ISOInfo', + ByteOrder => 'BigEndian', + }, + }, + 0x002a => { #23 (this tag added with D3 firmware 1.10 -- also written by Nikon utilities) + Name => 'VignetteControl', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 3 => 'Normal', + 5 => 'High', + }, + }, + 0x002b => { #PH + Name => 'DistortInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::DistortInfo', + }, + }, + 0x002c => { #29 (D7000) + Name => 'UnknownInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::UnknownInfo', + ByteOrder => 'BigEndian', #(NC) + }, + }, + # 0x2d - "512 0 0","512 3 10","512 1 14",... + 0x0032 => { #PH + Name => 'UnknownInfo2', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::UnknownInfo2', + ByteOrder => 'BigEndian', #(NC) + }, + }, + 0x0034 => { #forum9646 + Name => 'ShutterMode', + Writable => 'int16u', + RawConv => '$$self{ShutterMode} = $val', + DataMember => 'ShutterMode', + PrintConv => { + 0 => 'Mechanical', + 16 => 'Electronic', + # 33 => ? seen for 1J2 + 48 => 'Electronic Front Curtain', + 64 => 'Electronic (Movie)', #JanSkoda (Z6II) + 80 => 'Auto (Mechanical)', #JanSkoda (Z6II) + 81 => 'Auto (Electronic Front Curtain)', #JanSkoda (Z6II) + 96 => 'Electronic (High Speed)', #28 Z9 at C30/C60/C120 frame rates + }, + }, + 0x0035 => [{ #32 + Name => 'HDRInfo', + Condition => '$count != 6', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::HDRInfo' }, + },{ + Name => 'HDRInfo2', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::HDRInfo2' }, + }], + 0x0037 => { #XavierJubier + Name => 'MechanicalShutterCount', + Writable => 'int32u', + }, + 0x0039 => { + Name => 'LocationInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::LocationInfo' }, + }, + 0x003d => { #IB + Name => 'BlackLevel', + Writable => 'int16u', + Count => 4, + # (may need to divide by 4 for some images, eg. D3300/D5300, 12 bit - ref IB) + }, + 0x003e => { #28 + Name => 'ImageSizeRAW', + PrintConv => { + 1 => 'Large', + 2 => 'Medium', + 3 => 'Small', + }, + }, + 0x003f => { #https://github.com/darktable-org/darktable/issues/12282 + Name => 'WhiteBalanceFineTune', + Writable => 'rational64s', + Count => 2, + }, + 0x0044 => { #28 + Name => 'JPGCompression', + RawConv => '($val) ? $val : undef', # undef for raw files + PrintConv => { + 1 => 'Size Priority', + 3 => 'Optimal Quality', + }, + }, + 0x0045 => { #IB + Name => 'CropArea', + Notes => 'left, top, width, height', + Writable => 'int16u', + Count => 4, + }, + 0x004e => { #28 + Name => 'NikonSettings', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::NikonSettings::Main' }, + }, + 0x004f => { #IB (D850) + Name => 'ColorTemperatureAuto', + Writable => 'int16u', + }, + 0x0051 => { #28 (Z9) + Name => 'MakerNotes0x51', + Writable => 'undef', + Hidden => 1, + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::MakerNotes0x51' }, + }, + #0x0053 #28 possibly a secondary DistortionControl block (in addition to DistortInfo)? Certainly offset 0x04 within block contains tag AutoDistortionControl for Z72 and D6 (1=>On; 2=> Off) + 0x0056 => { #28 (Z9) + Name => 'MakerNotes0x56', + Writable => 'undef', + Hidden => 1, + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::MakerNotes0x56' }, + }, + #0x005e #28 possibly DiffractionCompensation block? Certainly offset 0x04 within block contains tag DiffractionCompensation + 0x0080 => { Name => 'ImageAdjustment', Writable => 'string' }, + 0x0081 => { Name => 'ToneComp', Writable => 'string' }, #2 + 0x0082 => { Name => 'AuxiliaryLens', Writable => 'string' }, + 0x0083 => { + Name => 'LensType', + Writable => 'int8u', + # credit to Tom Christiansen (ref 7) for figuring this out... + # (note that older models don't seem to set bits 4-7 (0xf0), so the + # LensType may be different with different models, ref Kenneth Cochran) + PrintConv => q[$_ = $val ? Image::ExifTool::DecodeBits($val, + { + 0 => 'MF', + 1 => 'D', + 2 => 'G', + 3 => 'VR', + 4 => '1', #PH + 5 => 'FT-1', #PH/IB + 6 => 'E', #PH (electromagnetic aperture mechanism) + 7 => 'AF-P', #PH/IB + }) : 'AF'; + # remove commas and change "D G" to just "G" + s/,//g; s/\bD G\b/G/; + s/ E\b// and s/^(G )?/E /; # put "E" at the start instead of "G" + s/ 1// and $_ = "1 $_"; # put "1" at start + s/FT-1 // and $_ .= ' FT-1'; # put "FT-1" at end + return $_; + ], + PrintConvInv => q[ + my $bits = 0; + $bits |= 0x01 if $val =~ /\bMF\b/i; # bit 0 + $bits |= 0x02 if $val =~ /\bD\b/i; # bit 1 + $bits |= 0x06 if $val =~ /\bG\b/i; # bits 1 and 2 + $bits |= 0x08 if $val =~ /\bVR\b/i; # bit 3 + $bits |= 0x10 if $val =~ /\b1\b/; # bit 4 + $bits |= 0x20 if $val =~ /\bFT-1/i; # bit 5 + $bits |= 0x80 if $val =~ /\bAF-P/i; # bit 7 (not used by all models) + $bits |= 0x46 if $val =~ /\bE\b/i; # bits 1, 2 and 6 + return $bits; + ], + }, + 0x0084 => { #2 + Name => "Lens", + Writable => 'rational64u', + Count => 4, + # short focal, long focal, aperture at short focal, aperture at long focal + PrintConv => \&Image::ExifTool::Exif::PrintLensInfo, + PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo, + }, + 0x0085 => { + Name => 'ManualFocusDistance', + Writable => 'rational64u', + }, + 0x0086 => { + Name => 'DigitalZoom', + Writable => 'rational64u', + }, + 0x0087 => { #5 + Name => 'FlashMode', + Writable => 'int8u', + PrintConv => { + 0 => 'Did Not Fire', + 1 => 'Fired, Manual', #14 + 3 => 'Not Ready', #28 + #5 observed on Z9 firing remote SB-5000 via WR-R11a optical awl + 7 => 'Fired, External', #14 + 8 => 'Fired, Commander Mode', + 9 => 'Fired, TTL Mode', + 18 => 'LED Light', #G.F. (movie LED light) + }, + }, + 0x0088 => [ + { + Name => 'AFInfo', + Condition => '$$self{Model} =~ /^NIKON D/i', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::AFInfo', + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'AFInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::AFInfo', + ByteOrder => 'LittleEndian', + }, + }, + ], + 0x0089 => { #5 + Name => 'ShootingMode', + Writable => 'int16u', + # the meaning of bit 5 seems to change: For the D200 it indicates "Auto ISO" - PH + Notes => 'for the D70, Bit 5 = Unused LE-NR Slowdown', + # credit to Tom Christiansen (ref 7) for figuring this out... + # The (new?) bit 5 seriously complicates our life here: after firmwareB's + # 1.03, bit 5 turns on when you ask for BUT DO NOT USE the long-range + # noise reduction feature, probably because even not using it, it still + # slows down your drive operation to 50% (1.5fps max not 3fps). But no + # longer does !$val alone indicate single-frame operation. - TC, D70 + # The following comments are from Warren Hatch: + # Bits 4,6,8 indicate bracketing mode. + # - all 0's => Bracketing Off + # - bit 4 on with bits 6,8 off => Exposure Bracketing On + # - bits 4,6 on => WB bracketing On + # - 4,8 on => ADL Bracketing On + # Bit 2 gives tethered status: Off=>Not Tethered; On=Tethered. + # - that this simply indicates a camera is connected via a cord to a PC + # (doesn't necessarily mean that the shutter was tripped by the computer) + # Bits 0,1,3,7,9 relate to how the shutter is tripped in concert with the + # Release Mode dial [although I cannot cause bit 7 to flip with any of my gear and + # I suspect it is no longer used for the D500]. Regardless, the ReleaseMode tag + # offers a superior decoding of this information for the D4s, D810 and D500. + # Bit 5 indicates whether or not AutoISO is enabled. + PrintConv => q[ + $_ = ''; + unless ($val & 0x87) { + return 'Single-Frame' unless $val; + $_ = 'Single-Frame, '; + } + return $_ . Image::ExifTool::DecodeBits($val, + { + 0 => 'Continuous', + 1 => 'Delay', + 2 => 'PC Control', + 3 => 'Self-timer', #forum6281 (NC) + 4 => 'Exposure Bracketing', + 5 => $$self{Model}=~/D70\b/ ? 'Unused LE-NR Slowdown' : 'Auto ISO', + 6 => 'White-Balance Bracketing', + 7 => 'IR Control', + 8 => 'D-Lighting Bracketing', #forum6281 (NC) + 11 => 'Pre-capture', #28 Z9 pre-release burst + }); + ], + }, + # 0x008a - called "AutoBracketRelease" by ref 15 [but this seems wrong] + # values: 0,255 (when writing NEF only), or 1,2 (when writing JPEG or JPEG+NEF) + # --> makes odd, repeating pattern in sequential NEF images (ref 28) + 0x008b => { #8 + Name => 'LensFStops', + ValueConv => 'my ($a,$b,$c)=unpack("C3",$val); $c ? $a*($b/$c) : 0', + ValueConvInv => 'my $a=int($val*12+0.5);$a<256 ? pack("C4",$a,1,12,0) : undef', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + Writable => 'undef', + Count => 4, + }, + 0x008c => { + Name => 'ContrastCurve', #JD + Writable => 'undef', + Flags => [ 'Binary', 'Protected', 'Drop' ], # (drop because not found in Nikon JPEG's) + }, + # ColorHue: MODE1/MODE1a=sRGB, MODE2=Adobe RGB, MODE3a=more saturated sRGB + # --> should really be called ColorSpace or ColorMode, but that would conflict with other tags + 0x008d => { Name => 'ColorHue' , Writable => 'string' }, #2 + # SceneMode takes on the following values: PORTRAIT, PARTY/INDOOR, NIGHT PORTRAIT, + # BEACH/SNOW, LANDSCAPE, SUNSET, NIGHT SCENE, MUSEUM, FIREWORKS, CLOSE UP, COPY, + # BACK LIGHT, PANORAMA ASSIST, SPORT, DAWN/DUSK + 0x008f => { Name => 'SceneMode', Writable => 'string' }, #2 + # LightSource shows 3 values COLORED SPEEDLIGHT NATURAL. + # (SPEEDLIGHT when flash goes. Have no idea about difference between other two.) + 0x0090 => { Name => 'LightSource', Writable => 'string' }, #2 + 0x0091 => [ #18 + { #PH + Condition => '$$valPt =~ /^0209/', + Name => 'ShotInfoD40', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD40', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { + Condition => '$$valPt =~ /^0208/', + Name => 'ShotInfoD80', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD80', + DecryptStart => 4, + # (Capture NX can change the makernote byte order, but this stays big-endian) + ByteOrder => 'BigEndian', + }, + }, + { #PH (D90, firmware 1.00) + Condition => '$$valPt =~ /^0213/', + Name => 'ShotInfoD90', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD90', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #PH (D3, firmware 0.37 and 1.00) + Condition => '$$valPt =~ /^0210/ and $count == 5399', + Name => 'ShotInfoD3a', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD3a', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #PH (D3, firmware 1.10, 2.00 and 2.01 [count 5408], and 2.02 [count 5412]) + Condition => '$$valPt =~ /^0210/ and ($count == 5408 or $count == 5412)', + Name => 'ShotInfoD3b', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD3b', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #PH (D3X, firmware 1.00) + Condition => '$$valPt =~ /^0214/ and $count == 5409', + Name => 'ShotInfoD3X', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD3X', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #PH (D3S, firmware 0.16 and 1.00) + Condition => '$$valPt =~ /^0218/ and ($count == 5356 or $count == 5388)', + Name => 'ShotInfoD3S', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD3S', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #JD (D300, firmware 0.25 and 1.00) + # D3 and D300 use the same version number, but the length is different + Condition => '$$valPt =~ /^0210/ and $count == 5291', + Name => 'ShotInfoD300a', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD300a', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #PH (D300, firmware version 1.10) + # yet again the same ShotInfoVersion for different data + Condition => '$$valPt =~ /^0210/ and $count == 5303', + Name => 'ShotInfoD300b', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD300b', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #PH (D300S, firmware version 1.00) + # yet again the same ShotInfoVersion for different data + Condition => '$$valPt =~ /^0216/ and $count == 5311', + Name => 'ShotInfoD300S', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD300S', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + # 0225 - D600 + { #29 (D700 firmware version 1.02f) + Condition => '$$valPt =~ /^0212/ and $count == 5312', + Name => 'ShotInfoD700', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD700', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #28 (D780 firmware version 1.00) + Condition => '$$valPt =~ /^0245/', + Name => 'ShotInfoD780', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD780', + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + { #28 (D7500 firmware version 1.00h) + Condition => '$$valPt =~ /^0242/', + Name => 'ShotInfoD7500', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD7500', + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + { #PH (D800 firmware 1.01a) + Condition => '$$valPt =~ /^0222/', + Name => 'ShotInfoD800', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD800', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #28 (D810 firmware 1.01) + Condition => '$$valPt =~ /^0233/', + Name => 'ShotInfoD810', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD810', + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + { #28 (D850 firmware 1.00b) + Condition => '$$valPt =~ /^0243/', + Name => 'ShotInfoD850', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD850', + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + # 0217 - D3000 + # 0219 - D3100 + # 0224 - D3200 + { #PH + Condition => '$$valPt =~ /^0215/ and $count == 6745', + Name => 'ShotInfoD5000', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD5000', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #PH + Condition => '$$valPt =~ /^0221/ and $count == 8902', + Name => 'ShotInfoD5100', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD5100', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #PH + Condition => '$$valPt =~ /^0226/ and $count == 11587', + Name => 'ShotInfoD5200', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD5200', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { #29 (D7000 firmware version 1.01b) + Condition => '$$valPt =~ /^0220/', + Name => 'ShotInfoD7000', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD7000', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { # (D4 firmware version 1.00g) + Condition => '$$valPt =~ /^0223/', + Name => 'ShotInfoD4', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD4', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { # (D4S firmware version 1.00d and 1.01a) + Condition => '$$valPt =~ /^0231/', + Name => 'ShotInfoD4S', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD4S', + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + { #28 (D500 firmware version 1.00 and D5 firmware version 1.10a) + Condition => '$$valPt =~ /^023[89]/', + Name => 'ShotInfoD500', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD500', + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + { # (D6 firmware version 1.00, ref 28) + Condition => '$$valPt =~ /^0246/', + Name => 'ShotInfoD6', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD6', + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + { # (D610 firmware version 1.00) + Condition => '$$valPt =~ /^0232/', + Name => 'ShotInfoD610', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoD610', + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { # (Z6_2 firmware version 1.00 and Z7II firmware versions 1.00 & 1.01, ref 28) + # 0800=Z6/Z7 0801=Z50 0802=Z5 0803=Z6II/Z7II 0804=Zfc 0807=Z30 + Condition => '$$valPt =~ /^080[012347]/', + Name => 'ShotInfoZ7II', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoZ7II', + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + { # (Z8 firmware version 1.00 ref 28) + Condition => '$$valPt =~ /^0806/', + Name => 'ShotInfoZ8', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoZ8', + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + { # (Z9 firmware version 1.00 ref 28) + Condition => '$$valPt =~ /^0805/', + Name => 'ShotInfoZ9', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfoZ9', + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + { # D7100=0227 + Condition => '$$valPt =~ /^0[28]/', + Name => 'ShotInfo02xx', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfo', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'ShotInfoUnknown', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShotInfo', + ByteOrder => 'BigEndian', + }, + }, + ], + 0x0092 => { #2 + Name => 'HueAdjustment', + Writable => 'int16s', + }, + # 0x0093 - ref 15 calls this Saturation, but this is wrong - PH + 0x0093 => { #21 + Name => 'NEFCompression', + Writable => 'int16u', + SeparateTable => 'NEFCompression', + PrintConv => \%nefCompression, + }, + 0x0094 => { Name => 'SaturationAdj', Writable => 'int16s' }, + 0x0095 => { Name => 'NoiseReduction', Writable => 'string' }, # ("Off" or "FPNR"=long exposure NR) + 0x0096 => { + Name => 'NEFLinearizationTable', # same table as DNG LinearizationTable (ref JD) + Writable => 'undef', + Flags => [ 'Binary', 'Protected' ], + }, + 0x0097 => [ #4 + # (NOTE: these are byte-swapped by NX when byte order changes) + { + Condition => '$$valPt =~ /^0100/', # (D100 and Coolpix models) + Name => 'ColorBalance0100', + SubDirectory => { + Start => '$valuePtr + 72', + TagTable => 'Image::ExifTool::Nikon::ColorBalance1', + }, + }, + { + Condition => '$$valPt =~ /^0102/', # (D2H) + Name => 'ColorBalance0102', + SubDirectory => { + Start => '$valuePtr + 10', + TagTable => 'Image::ExifTool::Nikon::ColorBalance2', + }, + }, + { + Condition => '$$valPt =~ /^0103/', # (D70/D70s) + Name => 'ColorBalance0103', + # D70: at file offset 'tag-value + base + 20', 4 16 bits numbers, + # v[0]/v[1] , v[2]/v[3] are the red/blue multipliers. + SubDirectory => { + Start => '$valuePtr + 20', + TagTable => 'Image::ExifTool::Nikon::ColorBalance3', + }, + }, + { + Condition => '$$valPt =~ /^0205/', # (D50) + Name => 'ColorBalance0205', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalance2', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + DirOffset => 14, # (start of directory relative to DecryptStart) + }, + }, + { # (D3/D3X/D300/D700=0209,D300S=0212,D3S=0214) + Condition => '$$valPt =~ /^02(09|12|14)/', + Name => 'ColorBalance0209', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalance4', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 284, + DirOffset => 10, + }, + }, + { # (D2X/D2Xs=0204,D2Hs=0206,D200=0207,D40/D40X/D80=0208,D60=0210) + Condition => '$$valPt =~ /^02(\d{2})/ and $1 < 11', + Name => 'ColorBalance02', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalance2', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 284, + DirOffset => 6, + }, + }, + { + Condition => '$$valPt =~ /^0211/', # (D90/D5000) + Name => 'ColorBalance0211', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalance4', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 284, + DirOffset => 16, + }, + }, + { + Condition => '$$valPt =~ /^0213/', # (D3000) + Name => 'ColorBalance0213', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalance2', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 284, + DirOffset => 10, + }, + }, + { # (D3100=0215,D7000/D5100=0216,D4/D600/D800/D800E/D3200=0217) + Condition => '$$valPt =~ /^021[567]/', + Name => 'ColorBalance0215', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalance4', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 284, + DirOffset => 4, + }, + }, + { # (D5200/D7100=0218, D5300=0219, D610/Df=0220, D3300=0221, CoolpixA=0601) + Name => 'ColorBalanceUnknown02', + Condition => '$$valPt =~ /^0[26]/', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalanceUnknown', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, # (necessary to recrypt this if serial number changed) + DecryptStart => 284, + }, + }, + { # (1J1/1J2/1V1=0400, 1V2=0401, 1J3/1S1=0402, 1AW1=0403, Z6/Z7=0800) + Name => 'ColorBalanceUnknown04', + Condition => '$$valPt =~ /^0[48]/', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ColorBalanceUnknown', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, # (necessary to recrypt this if serial number changed) + DecryptStart => 4, + }, + }, + { + # (CoolpixP7700/P7800=0500, CoolpixP330/P520=0502) + Name => 'ColorBalanceUnknown', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::ColorBalanceUnknown' }, + }, + ], + 0x0098 => [ + { #8 + Condition => '$$valPt =~ /^0100/', # D100, D1X - PH + Name => 'LensData0100', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::LensData00' }, + }, + { #8 + Condition => '$$valPt =~ /^0101/', # D70, D70s - PH + Name => 'LensData0101', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::LensData01' }, + }, + # note: this information is encrypted if the version is 02xx + { #8 + # 0201 - D200, D2Hs, D2X and D2Xs + # 0202 - D40, D40X and D80 + # 0203 - D300 + Condition => '$$valPt =~ /^020[1-3]/', + Name => 'LensData0201', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData01', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + { #PH + Condition => '$$valPt =~ /^0204/', # D90, D7000 + Name => 'LensData0204', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData0204', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + { + Condition => '$$valPt =~ /^040[01]/', # 0=1J1/1V1, 1=1J2 + Name => 'LensData0400', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData0400', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + { + Condition => '$$valPt =~ /^0402/', # 1J3/1S1/1V2 + Name => 'LensData0402', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData0402', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + { + Condition => '$$valPt =~ /^0403/', # 1J4,1J5 + Name => 'LensData0403', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData0403', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + { + Condition => '$$valPt =~ /^080[012]/', # Z6/Z7/Z9 + Name => 'LensData0800', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData0800', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'LensDataUnknown', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensDataUnknown', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + ], + 0x0099 => { #2/15 + Name => 'RawImageCenter', + Writable => 'int16u', + Count => 2, + }, + 0x009a => { #10 + Name => 'SensorPixelSize', + Writable => 'rational64u', + Count => 2, + PrintConv => '$val=~s/ / x /;"$val um"', + PrintConvInv => '$val=~tr/a-zA-Z/ /;$val', + }, + 0x009c => { #14 + # L2/L3 has these modes (from owner's manual): - PH + # Portrait Assist: FACE-PRIORITY AF,PORTRAIT,PORTRAIT LEFT,PORTRAIT RIGHT, + # PORTRAIT CLOSE-UP,PORTRAIT COUPLE,PORTRAIT-FIGURE + # Landscape Assist:LANDSCAPE,SCENIC VIEW,ARCHITECTURE,GROUP RIGHT,GROUP LEFT + # Sports Assist: SPORTS,SPORT SPECTATOR,SPORT COMPOSITE + # P7100 has test modes: - PH + # CREATIVE MONOCHROME,PAINTING,CROSS PROCESS,SOFT,NOSTALGIC SEPIA, + # HIGH KEY,LOW KEY,SELECTIVE COLOR,ZOOM EXPOSURE EXP.,DEFOCUS DURING + Name => 'SceneAssist', + Writable => 'string', + }, + 0x009d => { #NealKrawetz + Name => 'DateStampMode', + Writable => 'int16u', + Notes => 'feature to imprint date/time on image', + PrintConv => { #PH + 0 => 'Off', + 1 => 'Date & Time', + 2 => 'Date', + 3 => 'Date Counter', # (NC) (D3500) + }, + }, + 0x009e => { #JD + Name => 'RetouchHistory', + Writable => 'int16u', + Count => 10, + # trim off extra "None" values + ValueConv => '$val=~s/( 0)+$//; $val', + ValueConvInv => 'my $n=($val=~/ \d+/g);$n < 9 ? $val . " 0" x (9-$n) : $val', + PrintConvColumns => 2, + PrintConv => [ + \%retouchValues, + \%retouchValues, + \%retouchValues, + \%retouchValues, + \%retouchValues, + \%retouchValues, + \%retouchValues, + \%retouchValues, + \%retouchValues, + \%retouchValues, + ], + }, + 0x00a0 => { Name => 'SerialNumber', Writable => 'string' }, #2 + 0x00a2 => { # size of compressed image data plus EOI segment (ref 10) + Name => 'ImageDataSize', + Writable => 'int32u', + }, + # 0x00a3 - int8u: 0 (All DSLR's but D1,D1H,D1X,D100) + # 0x00a4 - version number found only in NEF images from DSLR models except the + # D1,D1X,D2H and D100. Value is "0200" for all available samples except images + # edited by Nikon Capture Editor 4.3.1 W and 4.4.2 which have "0100" - PH + 0x00a5 => { #15 + Name => 'ImageCount', + Writable => 'int32u', + }, + 0x00a6 => { #15 + Name => 'DeletedImageCount', + Writable => 'int32u', + }, + # the sum of 0xa5 and 0xa6 is equal to 0xa7 ShutterCount (D2X,D2Hs,D2H,D200, ref 10) + 0x00a7 => { # Number of shots taken by camera so far (ref 2) + Name => 'ShutterCount', + Writable => 'int32u', + Protected => 1, + PrintConv => '$val == 4294965247 ? "n/a" : $val', + PrintConvInv => '$val eq "n/a" ? 4294965247 : $val', + Notes => q{ + includes both mechanical and electronic shutter activations for models with + this feature. This value is used as a key to decrypt other information, and + writing this tag causes the other information to be re-encrypted with the + new key + }, + }, + 0x00a8 => [#JD + { + Name => 'FlashInfo0100', + Condition => '$$valPt =~ /^010[01]/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0100' }, + }, + { + Name => 'FlashInfo0102', + Condition => '$$valPt =~ /^0102/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0102' }, + }, + { + Name => 'FlashInfo0103', + # (0104 for D7000, 0105 for D800) + Condition => '$$valPt =~ /^010[345]/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0103' }, + }, + { + Name => 'FlashInfo0106', # (Df, D610, D3300, D5300, D7100, Coolpix A) + Condition => '$$valPt =~ /^0106/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0106' }, + }, + { + Name => 'FlashInfo0107', # (0107 for D4S/D750/D810/D5500/D7200, 0108 for D5/D500/D3400) + Condition => '$$valPt =~ /^010[78]/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0107' }, + }, + { + Name => 'FlashInfo0300', # (Z7II) + Condition => '$$valPt =~ /^030[01]/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0300' }, + }, + { + Name => 'FlashInfoUnknown', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfoUnknown' }, + }, + ], + 0x00a9 => { Name => 'ImageOptimization',Writable => 'string' },#2 + 0x00aa => { Name => 'Saturation', Writable => 'string' }, #2 + 0x00ab => { Name => 'VariProgram', Writable => 'string' }, #2 (scene mode for DSLR's - PH) + 0x00ac => { Name => 'ImageStabilization',Writable=> 'string' }, #14 + 0x00ad => { Name => 'AFResponse', Writable => 'string' }, #14 + 0x00b0 => [{ #PH + Name => 'MultiExposure', + Condition => '$$valPt =~ /^0100/', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MultiExposure', + # Note: this endianness varies with model, but Nikon software may change + # metadata endianness (although it is unknown how it affects this record), + # so for now don't specify ByteOrder although it may be wrong if the + # file is rewritten by Nikon software --> see comments for FileInfo + }, + },{ + Name => 'MultiExposure', + Condition => '$$valPt =~ /^0101/', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MultiExposure', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'MultiExposure2', + Condition => '$$valPt =~ /^010[23]/', # 0102 is NC (PH) + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MultiExposure2', + }, + }], + 0x00b1 => { #14/PH/JD (D80) + Name => 'HighISONoiseReduction', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Minimal', # for high ISO (>800) when setting is "Off" + 2 => 'Low', # Low,Normal,High take effect for ISO > 400 + 3 => 'Medium Low', + 4 => 'Normal', + 5 => 'Medium High', + 6 => 'High', + }, + }, + # 0x00b2 (string: "NORMAL ", 0xc3's, 0xff's or 0x20's) + 0x00b3 => { #14 + Name => 'ToningEffect', + Writable => 'string', + }, + 0x00b6 => { #PH + Name => 'PowerUpTime', + Groups => { 2 => 'Time' }, + Shift => 'Time', + # not clear whether "powered up" means "turned on" or "power applied" - PH + Notes => 'date/time when camera was last powered up', + Writable => 'undef', + # must use RawConv so byte order is correct + RawConv => sub { + my $val = shift; + return $val if length $val < 7; + my $shrt = GetByteOrder() eq 'II' ? 'v' : 'n'; + my @date = unpack("${shrt}C5", $val); + return sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @date); + }, + RawConvInv => sub { + my $val = shift; + my $shrt = GetByteOrder() eq 'II' ? 'v' : 'n'; + my @date = ($val =~ /\d+/g); + return pack("${shrt}C6", @date, 0); + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,0)', + }, + 0x00b7 => [{ + Name => 'AFInfo2', + Condition => '$$self{Model} =~ /^NIKON (Z 8|Z 9)\b/i', #AFInfo2Version 0400 + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::AFInfo2V0400' }, + },{ #JD + Name => 'AFInfo2', + # (this structure may be byte swapped when rewritten by CaptureNX) + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::AFInfo2' }, + }], + 0x00b8 => [{ #PH + Name => 'FileInfo', + # unfortunately, some newer models write this as little-endian + # (and CaptureNX can change the byte order of the maker notes, + # but leaves this structure unchanged) + # - it will be an ongoing pain to keep this list of models up-to-date, + # so if only one ordering yields valid DirectoryNumber and FileNumber values, + # use it, otherwise default to a-priori knowledge of the camera model + # (assume that a valid DirectoryNumber is 100-999, and a valid FileNumber + # is 0000-9999, although I have some samples with a DirectoryNumber of 99) + Condition => q{ + if (length($$valPt) >= 0) { + my ($dir, $file) = unpack('x6vv', $$valPt); + my $littleEndian = ($dir >= 100 and $dir <= 999 and $file <= 9999); + ($dir, $file) = unpack('x6nn', $$valPt); + my $bigEndian = ($dir >= 100 and $dir <= 999 and $file <= 9999); + return $littleEndian if $littleEndian xor $bigEndian; + } + return $$self{Model} =~ /^NIKON (D4S|D750|D810|D3300|D5200|D5300|D5500|D7100)$/; + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::FileInfo', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'FileInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::FileInfo', + ByteOrder => 'BigEndian', + }, + }], + 0x00b9 => { #28 + Name => 'AFTune', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::AFTune' }, + }, + # 0x00ba - custom curve data? (ref 28?) (only in NEF images) + 0x00bb => { #forum6281 + Name => 'RetouchInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::RetouchInfo' }, + }, + # 0x00bc - NEFThumbnail? (forum6281) + 0x00bd => { #PH (P6000) + Name => 'PictureControlData', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::PictureControl' }, + }, + 0x00bf => { + Name => 'SilentPhotography', + PrintConv => \%offOn, + }, + 0x00c3 => { + Name => 'BarometerInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::BarometerInfo', + # (little-endian in II EXIF, big-endian in MOV) + }, + }, + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, + # 0x0e01 - In D70 NEF files produced by Nikon Capture, the data for this tag extends 4 bytes + # past the end of the maker notes. Very odd. I hope these 4 bytes aren't useful because + # they will get lost by any utility that blindly copies the maker notes (not ExifTool) - PH + 0x0e01 => { + Name => 'NikonCaptureData', + Writable => 'undef', + Permanent => 0, + # (Drop because may be too large for JPEG images) + Flags => [ 'Binary', 'Protected', 'Drop' ], + Notes => q{ + this data is dropped when copying Nikon MakerNotes since it may be too large + to fit in the EXIF segment of a JPEG image, but it may be copied as a block + into existing Nikon MakerNotes later if desired + }, + SubDirectory => { + DirName => 'NikonCapture', + TagTable => 'Image::ExifTool::NikonCapture::Main', + }, + }, + # 0x0e05 written by Nikon Capture to NEF files, values of 1 and 2 - PH + 0x0e09 => { #12 + Name => 'NikonCaptureVersion', + Writable => 'string', + Permanent => 0, + PrintConv => undef, # (avoid applying default print conversion to string) + }, + # 0x0e0e is in D70 Nikon Capture files (not out-of-the-camera D70 files) - PH + 0x0e0e => { #PH + Name => 'NikonCaptureOffsets', + Writable => 'undef', + Permanent => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::CaptureOffsets', + Validate => '$val =~ /^0100/', + Start => '$valuePtr + 4', + }, + }, + 0x0e10 => { #17 + Name => 'NikonScanIFD', + Groups => { 1 => 'NikonScan', 2 => 'Image' }, + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::Scan', + Start => '$val', + }, + }, + 0x0e13 => [{ # PH/https://exiftool.org/forum/index.php/topic,2737.0.html + Name => 'NikonCaptureEditVersions', + Condition => '$self->Options("ExtractEmbedded")', + Notes => q{ + the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> option may be used to decode settings from the stored + edit versions, otherwise this is extracted as a binary data block + }, + Writable => 'undef', + Permanent => 0, + Drop => 1, # (may be too large for JPEG images) + SubDirectory => { + DirName => 'NikonCaptureEditVersions', + TagTable => 'Image::ExifTool::NikonCapture::Main', + ProcessProc => \&ProcessNikonCaptureEditVersions, + WriteProc => sub { return undef }, # (writing not yet supported) + }, + },{ + Name => 'NikonCaptureEditVersions', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected', 'Drop' ], + }], + 0x0e1d => { #JD + Name => 'NikonICCProfile', + Flags => [ 'Binary', 'Protected' ], + Writable => 'undef', # must be defined here so tag will be extracted if specified + WriteCheck => q{ + require Image::ExifTool::ICC_Profile; + return Image::ExifTool::ICC_Profile::ValidateICC(\$val); + }, + SubDirectory => { + DirName => 'NikonICCProfile', + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + }, + 0x0e1e => { #PH + Name => 'NikonCaptureOutput', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::CaptureOutput', + Validate => '$val =~ /^0100/', + }, + }, + 0x0e22 => { #28 + Name => 'NEFBitDepth', + Writable => 'int16u', + Count => 4, + Protected => 1, + PrintConv => { + '0 0 0 0' => 'n/a (JPEG)', + '8 8 8 0' => '8 x 3', # TIFF RGB + '16 16 16 0' => '16 x 3', # TIFF 16-bit RGB + '12 0 0 0' => 12, + '14 0 0 0' => 14, + }, + }, +); + +# NikonScan IFD entries (ref 17) +%Image::ExifTool::Nikon::Scan = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITE_GROUP => 'NikonScan', + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 1 => 'NikonScan', 2 => 'Image' }, + VARS => { MINOR_ERRORS => 1 }, # this IFD is non-essential and often corrupted + NOTES => 'This information is written by the Nikon Scan software.', + 0x02 => { Name => 'FilmType', Writable => 'string', }, + 0x40 => { Name => 'MultiSample', Writable => 'string' }, + 0x41 => { Name => 'BitDepth', Writable => 'int16u' }, + 0x50 => { + Name => 'MasterGain', + Writable => 'rational64s', + PrintConv => 'sprintf("%.2f",$val)', + PrintConvInv => '$val', + }, + 0x51 => { + Name => 'ColorGain', + Writable => 'rational64s', + Count => 3, + PrintConv => 'sprintf("%.2f %.2f %.2f",split(" ",$val))', + PrintConvInv => '$val', + }, + 0x60 => { + Name => 'ScanImageEnhancer', + Writable => 'int32u', + PrintConv => \%offOn, + }, + 0x100 => { Name => 'DigitalICE', Writable => 'string' }, + 0x110 => { + Name => 'ROCInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::ROC' }, + }, + 0x120 => { + Name => 'GEMInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::GEM' }, + }, + 0x200 => { Name => 'DigitalDEEShadowAdj', Writable => 'int32u' }, + 0x201 => { Name => 'DigitalDEEThreshold', Writable => 'int32u' }, + 0x202 => { Name => 'DigitalDEEHighlightAdj',Writable => 'int32u' }, +); + +# ref 17 +%Image::ExifTool::Nikon::ROC = ( + %binaryDataAttrs, + FORMAT => 'int32u', + GROUPS => { 0 => 'MakerNotes', 1 => 'NikonScan', 2 => 'Image' }, + 0 => { + Name => 'DigitalROC', + ValueConv => '$val / 10', + ValueConvInv => 'int($val * 10)', + }, +); + +# ref 17 +%Image::ExifTool::Nikon::GEM = ( + %binaryDataAttrs, + FORMAT => 'int32u', + GROUPS => { 0 => 'MakerNotes', 1 => 'NikonScan', 2 => 'Image' }, + 0 => { + Name => 'DigitalGEM', + ValueConv => '$val<95 ? $val/20-1 : 4', + ValueConvInv => '$val == 4 ? 95 : int(($val + 1) * 20)', + }, +); + +# Vibration Reduction information - PH (D300) +%Image::ExifTool::Nikon::VRInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'VRInfoVersion', + Format => 'undef[4]', + Writable => 0, + }, + 4 => { + Name => 'VibrationReduction', + PrintConv => { + 0 => 'n/a', # (1V1 with a non-VR lens) + 1 => 'On', + 2 => 'Off', + }, + }, + # 5 - values: 0, 1 (VR On), 2 (VR Off) + 6 => [{ + Name => 'VRMode', + PrintConv => { + 0 => 'Off', + 1 => 'Normal', #39 (was 'Sport') + 3 => 'Sport', #39 (was 'Normal') + }, + %infoZSeries, + },{ + Name => 'VRMode', + PrintConv => { + 0 => 'Normal', + 1 => 'On (1)', #PH (NC) + 2 => 'Active', # (1J1) + 3 => 'Sport', #PH (Z7) + }, + }], + # 7 - values: 0, 1 + 8 => { #39 + Name => 'VRType', + PrintConv => { + 2 => 'In-body', # (IBIS) + 3 => 'In-body + Lens', # (IBIS + VR) + }, + }, +); + +# Face detection information - PH (S8100) +%Image::ExifTool::Nikon::FaceDetect = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FORMAT => 'int16u', + DATAMEMBER => [ 0x03 ], + 0x01 => { + Name => 'FaceDetectFrameSize', + Format => 'int16u[2]', + }, + 0x03 => { + Name => 'FacesDetected', + DataMember => 'FacesDetected', + RawConv => '$$self{FacesDetected} = $val', + }, + 0x04 => { + Name => 'Face1Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 1 ? undef : $val', + Notes => q{ + top, left, width and height of face detect area in coordinates of + FaceDetectFrameSize + }, + }, + 0x08 => { + Name => 'Face2Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 2 ? undef : $val', + }, + 0x0c => { + Name => 'Face3Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 3 ? undef : $val', + }, + 0x10 => { + Name => 'Face4Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 4 ? undef : $val', + }, + 0x14 => { + Name => 'Face5Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 5 ? undef : $val', + }, + 0x18 => { + Name => 'Face6Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 6 ? undef : $val', + }, + 0x1c => { + Name => 'Face7Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 7 ? undef : $val', + }, + 0x20 => { + Name => 'Face8Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 8 ? undef : $val', + }, + 0x24 => { + Name => 'Face9Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 9 ? undef : $val', + }, + 0x28 => { + Name => 'Face10Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 10 ? undef : $val', + }, + 0x2c => { + Name => 'Face11Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 11 ? undef : $val', + }, + 0x30 => { + Name => 'Face12Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 12 ? undef : $val', + }, +); + +# Picture Control information - PH (D300,P6000) +%Image::ExifTool::Nikon::PictureControl = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'PictureControlVersion', + Format => 'undef[4]', + Writable => 0, + }, + 4 => { + Name => 'PictureControlName', + Format => 'string[20]', + # make lower case with a leading capital for each word + PrintConv => \&FormatString, + PrintConvInv => 'uc($val)', + }, + 24 => { + Name => 'PictureControlBase', + Format => 'string[20]', + PrintConv => \&FormatString, + PrintConvInv => 'uc($val)', + }, + # beginning at byte 44, there is some interesting information. + # here are the observed bytes for each PictureControlMode: + # 44 45 46 47 48 49 50 51 52 53 54 55 56 57 + # STANDARD 00 01 00 00 00 80 83 80 80 80 80 ff ff ff + # NEUTRAL 03 c2 00 00 00 ff 82 80 80 80 80 ff ff ff + # VIVID 00 c3 00 00 00 80 84 80 80 80 80 ff ff ff + # MONOCHROME 06 4d 00 01 02 ff 82 80 80 ff ff 80 80 ff + # Neutral2 03 c2 01 00 02 ff 80 7f 81 00 7f ff ff ff (custom) + # (note that up to 9 different custom picture controls can be stored) + # --> bytes 44 and 45 are swapped if CaptureNX changes the byte order + # + 48 => { #21 + Name => 'PictureControlAdjust', + PrintConv => { + 0 => 'Default Settings', + 1 => 'Quick Adjust', + 2 => 'Full Control', + }, + }, + 49 => { + Name => 'PictureControlQuickAdjust', + # settings: -2 to +2 (n/a for Neutral and Monochrome modes) + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, + 50 => { + Name => 'Sharpness', + # settings: 0 to 9, Auto + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"No Sharpening","%d")', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, + 51 => { + Name => 'Contrast', + # settings: -3 to +3, Auto + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, + 52 => { + Name => 'Brightness', + # settings: -1 to +1 + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, + 53 => { + Name => 'Saturation', + # settings: -3 to +3, Auto (n/a for Monochrome mode) + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, + 54 => { + Name => 'HueAdjustment', + # settings: -3 to +3 (n/a for Monochrome mode) + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None")', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, + 55 => { + Name => 'FilterEffect', + # settings: Off,Yellow,Orange,Red,Green (n/a for color modes) + DelValue => 0xff, + PrintHex => 1, + PrintConv => { + 0x80 => 'Off', + 0x81 => 'Yellow', + 0x82 => 'Orange', + 0x83 => 'Red', + 0x84 => 'Green', + 0xff => 'n/a', + }, + }, + 56 => { + Name => 'ToningEffect', + # settings: B&W,Sepia,Cyanotype,Red,Yellow,Green,Blue-Green,Blue, + # Purple-Blue,Red-Purple (n/a for color modes) + DelValue => 0xff, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 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', + # 0x04 - seen for D810 (PH) + }, + }, + 57 => { #21 + Name => 'ToningSaturation', + # settings: B&W,Sepia,Cyanotype,Red,Yellow,Green,Blue-Green,Blue, + # Purple-Blue,Red-Purple (n/a unless ToningEffect is used) + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => '$val==0x7f ? "n/a" : $val', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, +); + +# Picture Control information V2 (ref 28) +%Image::ExifTool::Nikon::PictureControl2 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'PictureControlVersion', + Format => 'undef[4]', + Writable => 0, + }, + 4 => { + Name => 'PictureControlName', + Format => 'string[20]', + # make lower case with a leading capital for each word + PrintConv => \&FormatString, + PrintConvInv => 'uc($val)', + }, + 24 => { + Name => 'PictureControlBase', + Format => 'string[20]', + PrintConv => \&FormatString, + PrintConvInv => 'uc($val)', + }, + # beginning at byte 44, there is some interesting information. + # here are the observed bytes for each PictureControlMode: + # 44 45 46 47 48 49 50 51 52 53 54 55 56 57 + # STANDARD 00 01 00 00 00 80 83 80 80 80 80 ff ff ff + # NEUTRAL 03 c2 00 00 00 ff 82 80 80 80 80 ff ff ff + # VIVID 00 c3 00 00 00 80 84 80 80 80 80 ff ff ff + # MONOCHROME 06 4d 00 01 02 ff 82 80 80 ff ff 80 80 ff + # Neutral2 03 c2 01 00 02 ff 80 7f 81 00 7f ff ff ff (custom) + # (note that up to 9 different custom picture controls can be stored) + # --> bytes 44 and 45 are swapped if CaptureNX changes the byte order + # + 48 => { #21 + Name => 'PictureControlAdjust', + PrintConv => { + 0 => 'Default Settings', + 1 => 'Quick Adjust', + 2 => 'Full Control', + }, + }, + 49 => { + Name => 'PictureControlQuickAdjust', + # settings: -2 to +2 (n/a for Neutral and Monochrome modes) + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, + 51 => { + Name => 'Sharpness', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + }, + 53 => { + Name => 'Clarity', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv2($val,4)', + }, + 55 => { + Name => 'Contrast', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + }, + 57 => { #21 + Name => 'Brightness', + # settings: -1 to +1 + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,undef,"%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + }, + 59 => { + Name => 'Saturation', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + }, + 61 => { + Name => 'Hue', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, + 63 => { + Name => 'FilterEffect', + # settings: Off,Yellow,Orange,Red,Green (n/a for color modes) + DelValue => 0xff, + PrintHex => 1, + PrintConv => { + 0x80 => 'Off', + 0x81 => 'Yellow', + 0x82 => 'Orange', + 0x83 => 'Red', + 0x84 => 'Green', + 0xff => 'n/a', + }, + }, + 64 => { + Name => 'ToningEffect', + # settings: B&W,Sepia,Cyanotype,Red,Yellow,Green,Blue-Green,Blue, + # Purple-Blue,Red-Purple (n/a for color modes) + DelValue => 0xff, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 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', + }, + }, + 65 => { + Name => 'ToningSaturation', + DelValue => 0xff, + ValueConv => '$val - 0x80', #$val == 0x7f (n/a) for "B&W" + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + + }, +); + +# Picture Control information V3 (ref PH, Z7) +%Image::ExifTool::Nikon::PictureControl3 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'PictureControlVersion', + Format => 'undef[4]', + Writable => 0, + }, + 8 => { + Name => 'PictureControlName', + Format => 'string[20]', + # make lower case with a leading capital for each word + PrintConv => \&FormatString, + PrintConvInv => 'uc($val)', + }, + # 48 - looks like PictureControl2 byte 45 + 28 => { + Name => 'PictureControlBase', + Format => 'string[20]', + PrintConv => \&FormatString, + PrintConvInv => 'uc($val)', + }, + 54 => { # (NC) + Name => 'PictureControlAdjust', + PrintConv => { + 0 => 'Default Settings', + 1 => 'Quick Adjust', + 2 => 'Full Control', + }, + }, + 55 => { # (NC) + Name => 'PictureControlQuickAdjust', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, + 57 => { + Name => 'Sharpness', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + }, + 59 => { + Name => 'MidRangeSharpness', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + }, + 61 => { + Name => 'Clarity', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv2($val,4)', + }, + 63 => { + Name => 'Contrast', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + }, + 65 => { #21 + Name => 'Brightness', + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,undef,"%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + }, + 67 => { + Name => 'Saturation', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + }, + 69 => { + Name => 'Hue', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val)', + }, + 71 => { # (NC) + Name => 'FilterEffect', + DelValue => 0xff, + PrintHex => 1, + PrintConv => { + 0x80 => 'Off', + 0x81 => 'Yellow', + 0x82 => 'Orange', + 0x83 => 'Red', + 0x84 => 'Green', + 0xff => 'n/a', + }, + }, + 72 => { # (NC) + Name => 'ToningEffect', + DelValue => 0xff, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 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', + }, + }, + 73 => { # (NC) + Name => 'ToningSaturation', + DelValue => 0xff, + ValueConv => '$val - 0x80', + ValueConvInv => '$val + 0x80', + PrintConv => 'Image::ExifTool::Nikon::PrintPC($val,"None","%.2f",4)', + PrintConvInv => 'Image::ExifTool::Nikon::PrintPCInv($val,4)', + + }, +); + +# Unknown Picture Control information +%Image::ExifTool::Nikon::PictureControlUnknown = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'PictureControlVersion', + Format => 'undef[4]', + Writable => 0, + }, +); + +# World Time information - JD (D300) +%Image::ExifTool::Nikon::WorldTime = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Time' }, + 0 => { + Name => 'TimeZone', + Format => 'int16s', + PrintConv => q{ + my $sign = $val < 0 ? '-' : '+'; + my $h = int(abs($val) / 60); + sprintf("%s%.2d:%.2d", $sign, $h, abs($val)-60*$h); + }, + PrintConvInv => q{ + $val =~ /Z$/ and return 0; + $val =~ /([-+])(\d{1,2}):?(\d{2})$/ and return $1 . ($2 * 60 + $3); + $val =~ /^(\d{2})(\d{2})$/ and return $1 * 60 + $2; + return undef; + }, + }, + 2 => { + Name => 'DaylightSavings', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 3 => { + Name => 'DateDisplayFormat', + PrintConv => { + 0 => 'Y/M/D', + 1 => 'M/D/Y', + 2 => 'D/M/Y', + }, + }, +); + +# ISO information - PH (D300) +%Image::ExifTool::Nikon::ISOInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'ISO', + Notes => 'val = 100 * 2**(raw/12-5)', + Priority => 0, # because people like to see rounded-off values if they exist + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + # 1 - 0x01 + # 2 - 0x0c (probably the ISO divisor above) + # 3 - 0x00 + 4 => { + Name => 'ISOExpansion', + Format => 'int16u', + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 0x000 => 'Off', + 0x101 => 'Hi 0.3', + 0x102 => 'Hi 0.5', + 0x103 => 'Hi 0.7', + 0x104 => 'Hi 1.0', + 0x105 => 'Hi 1.3', # (Hi 1.3-1.7 may be possible with future models) + 0x106 => 'Hi 1.5', + 0x107 => 'Hi 1.7', + 0x108 => 'Hi 2.0', #(NC) - D3 should have this mode + 0x109 => 'Hi 2.3', #IB + 0x10a => 'Hi 2.5', #IB + 0x10b => 'Hi 2.7', #IB + 0x10c => 'Hi 3.0', #IB + 0x10d => 'Hi 3.3', #IB + 0x10e => 'Hi 3.5', #IB + 0x10f => 'Hi 3.7', #IB + 0x110 => 'Hi 4.0', #IB + 0x111 => 'Hi 4.3', #IB + 0x112 => 'Hi 4.5', #IB + 0x113 => 'Hi 4.7', #IB + 0x114 => 'Hi 5.0', #IB + 0x201 => 'Lo 0.3', + 0x202 => 'Lo 0.5', + 0x203 => 'Lo 0.7', + 0x204 => 'Lo 1.0', + }, + }, + # bytes 6-11 same as 0-4 in my samples (why is this duplicated?) + 6 => { + Name => 'ISO2', + Notes => 'val = 100 * 2**(raw/12-5)', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + # 7 - 0x01 + # 8 - 0x0c (probably the ISO divisor above) + # 9 - 0x00 + 10 => { + Name => 'ISOExpansion2', + Format => 'int16u', + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 0x000 => 'Off', + 0x101 => 'Hi 0.3', + 0x102 => 'Hi 0.5', + 0x103 => 'Hi 0.7', + 0x104 => 'Hi 1.0', + 0x105 => 'Hi 1.3', # (Hi 1.3-1.7 may be possible with future models) + 0x106 => 'Hi 1.5', + 0x107 => 'Hi 1.7', + 0x108 => 'Hi 2.0', #(NC) - D3 should have this mode + 0x201 => 'Lo 0.3', + 0x202 => 'Lo 0.5', + 0x203 => 'Lo 0.7', + 0x204 => 'Lo 1.0', + }, + }, + # bytes 12-13: 00 00 +); + +# distortion information - PH (D5000) +%Image::ExifTool::Nikon::DistortInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'DistortionVersion', + Format => 'undef[4]', + Writable => 0, + Unknown => 1, + }, + 4 => { + Name => 'AutoDistortionControl', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (underwater)', # (1AW1) + }, + }, +); + +# unknown information - PH (D7000) +%Image::ExifTool::Nikon::UnknownInfo = ( + %binaryDataAttrs, + FORMAT => 'int32u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'UnknownInfoVersion', + Condition => '$$valPt =~ /^\d{4}/', + Format => 'undef[4]', + Writable => 0, + Unknown => 1, + }, + # (bytes 6/7 and 8/9 are swapped if CaptureNX changes the byte order) +); + +# more unknown information - PH (D7000) +%Image::ExifTool::Nikon::UnknownInfo2 = ( + %binaryDataAttrs, + FORMAT => 'int32u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'UnknownInfo2Version', + Condition => '$$valPt =~ /^\d{4}/', + Format => 'undef[4]', + Writable => 0, + Unknown => 1, + }, + # (byte 4 may be changed from 1 to 0 when rewritten by CaptureNX) +); + +# Nikon AF information (ref 13) +%Image::ExifTool::Nikon::AFInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'AFAreaMode', + PrintConv => { + 0 => 'Single Area', + 1 => 'Dynamic Area', + 2 => 'Dynamic Area (closest subject)', + 3 => 'Group Dynamic', + 4 => 'Single Area (wide)', + 5 => 'Dynamic Area (wide)', + }, + }, + 1 => { + Name => 'AFPoint', + Notes => 'in some focus modes this value is not meaningful', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Center', + 1 => 'Top', + 2 => 'Bottom', + 3 => 'Mid-left', + 4 => 'Mid-right', + 5 => 'Upper-left', + 6 => 'Upper-right', + 7 => 'Lower-left', + 8 => 'Lower-right', + 9 => 'Far Left', + 10 => 'Far Right', + # (have also seen values of 11 and 12 when AFPointsInFocus is "(none)" - PH S3500) + }, + }, + 2 => { + Name => 'AFPointsInFocus', + Format => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => '(none)', + 0x7ff => 'All 11 Points', + BITMASK => { + 0 => 'Center', + 1 => 'Top', + 2 => 'Bottom', + 3 => 'Mid-left', + 4 => 'Mid-right', + 5 => 'Upper-left', + 6 => 'Upper-right', + 7 => 'Lower-left', + 8 => 'Lower-right', + 9 => 'Far Left', + 10 => 'Far Right', + }, + }, + }, +); + +# Nikon AF information for D3 and D300 (ref JD) +%Image::ExifTool::Nikon::AFInfo2 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0, 4, 6 ], + NOTES => "These tags are written by Nikon DSLR's which have the live view feature.", + 0 => { + Name => 'AFInfo2Version', + Format => 'undef[4]', + Writable => 0, + RawConv => '$$self{AFInfo2Version} = $val', + }, + 4 => { #PH + Name => 'ContrastDetectAF', + RawConv => '$$self{ContrastDetectAF} = $val', + PrintConv => { + %offOn, + 2 => 'On (2)', #PH (Z7) + }, + Notes => 'this is Off for the hybrid AF used in Nikon 1 models', + }, + 5 => [ + { + Name => 'AFAreaMode', + Condition => 'not $$self{ContrastDetectAF}', + Notes => 'ContrastDetectAF Off', + PrintConv => { + 0 => 'Single Area', # (called "Single Point" in manual - PH) + 1 => 'Dynamic Area', #PH + 2 => 'Dynamic Area (closest subject)', #PH + 3 => 'Group Dynamic', #PH + 4 => 'Dynamic Area (9 points)', #JD/28 + 5 => 'Dynamic Area (21 points)', #28 + 6 => 'Dynamic Area (51 points)', #28 + 7 => 'Dynamic Area (51 points, 3D-tracking)', #PH/28 + 8 => 'Auto-area', + 9 => 'Dynamic Area (3D-tracking)', #PH (D5000 "3D-tracking (11 points)") + 10 => 'Single Area (wide)', #PH + 11 => 'Dynamic Area (wide)', #PH + 12 => 'Dynamic Area (wide, 3D-tracking)', #PH + 13 => 'Group Area', #PH + 14 => 'Dynamic Area (25 points)', #PH + 15 => 'Dynamic Area (72 points)', #PH + 16 => 'Group Area (HL)', #28 + 17 => 'Group Area (VL)', #28 + 18 => 'Dynamic Area (49 points)', #28 + 128 => 'Single', #PH (1J1,1J2,1J3,1J4,1S1,1S2,1V2,1V3) + 129 => 'Auto (41 points)', #PH (1J1,1J2,1J3,1J4,1S1,1S2,1V1,1V2,1V3,AW1) + 130 => 'Subject Tracking (41 points)', #PH (1J1,1J4,1J3) + 131 => 'Face Priority (41 points)', #PH (1J1,1J3,1S1,1V2,AW1) + # 134 - seen for 1V1[PhaseDetectAF=0] (PH) + # 135 - seen for 1J2[PhaseDetectAF=4] (PH) + 192 => 'Pinpoint', #PH (NC) + 193 => 'Single', #PH (NC) + 195 => 'Wide (S)', #PH (NC) + 196 => 'Wide (L)', #PH (NC) + 197 => 'Auto', #PH (NC) + }, + }, + { #PH (D3/D90/D5000) + Name => 'AFAreaMode', + Notes => 'ContrastDetectAF On', + PrintConv => { + 0 => 'Contrast-detect', # (D3) + 1 => 'Contrast-detect (normal area)', # (D90/D5000) + # (D90 and D5000 give value of 2 when set to 'Face Priority' and + # 'Subject Tracking', but I didn't have a face to shoot at or a + # moving subject to track so perhaps this value changes dynamically) + 2 => 'Contrast-detect (wide area)', # (D90/D5000) + 3 => 'Contrast-detect (face priority)', # (ViewNX) + 4 => 'Contrast-detect (subject tracking)', # (ViewNX) + 128 => 'Single', #PH (1V3) + 129 => 'Auto (41 points)', #PH (NC) + 130 => 'Subject Tracking (41 points)', #PH (NC) + 131 => 'Face Priority (41 points)', #PH (NC) + 192 => 'Pinpoint', #PH (Z7) + 193 => 'Single', #PH (Z7) + 194 => 'Dynamic', #PH (Z7) + 195 => 'Wide (S)', #PH (Z7) + 196 => 'Wide (L)', #PH (Z7) + 197 => 'Auto', #PH (Z7) + 198 => 'Auto (People)', #28 (Z7) #if no faces are detected, will record as 'Auto'. Camera setting recorded in AFAreaMode field in the MakerNotes area + 199 => 'Auto (Animal)', #28 (Z7) #if no animals are detected, will record as 'Auto'. Camera setting recorded in AFAreaMode field in the MakerNotes area + 200 => 'Normal-area AF', #28 (D6) + 201 => 'Wide-area AF', #28 (D6) + 202 => 'Face-priority AF', #28 (D6) + 203 => 'Subject-tracking AF', #28 (D6) + 204 => 'Dynamic Area (S)', #28 (Z9) + 205 => 'Dynamic Area (M)', #28 (Z9) + 206 => 'Dynamic Area (L)', #28 (Z9) + 207 => '3D-tracking', #28 (Z9) + 208 => 'Wide-Area (C1/C2)', #28 (Z8, Z9) + }, + }, + ], + 6 => { + Name => 'PhaseDetectAF', #JD(AutoFocus), PH(PhaseDetectAF) + Notes => 'PrimaryAFPoint and AFPointsUsed below are only valid when this is On', + RawConv => '$$self{PhaseDetectAF} = $val', + PrintConv => { + # [observed AFAreaMode values in square brackets for each PhaseDetectAF value] + 0 => 'Off', + 1 => 'On (51-point)', #PH + 2 => 'On (11-point)', #PH + 3 => 'On (39-point)', #29 (D7000) + 4 => 'On (73-point)', #PH (1J1[128/129],1J2[128/129/135],1J3/1S1/1V2[128/129/131],1V1[129],AW1[129/131]) + 5 => 'On (5)', #PH (1S2[128/129], 1J4/1V3[129]) + 6 => 'On (105-point)', #PH (1J4/1V3[128/130]) + 7 => 'On (153-point)', #PH (D5/D500/D850) + 8 => 'On (81-point)', #38 + 9 => 'On (105-point)', #28 (D6) + }, + }, + 7 => [ + { #PH/JD + Name => 'PrimaryAFPoint', + # PrimaryAFPoint may only be valid for PhaseDetect - certainly true on the D6, possibly other bodies? (ref 28) + Condition => '$$self{PhaseDetectAF} < 2 and $$self{AFInfo2Version} !~ /^03/', + Notes => q{ + models with 51-point AF -- 5 rows (A-E) and 11 columns (1-11): D3, D3S, D3X, + D4, D4S, D300, D300S, D700, D800, D800e and D810 + }, + PrintConvColumns => 5, + PrintConv => { + 0 => '(none)', + %afPoints51, + 1 => 'C6 (Center)', # (add " (Center)" to central point) + }, + }, + { #10 + Name => 'PrimaryAFPoint', + Notes => 'models with 11-point AF: D90, D3000, D3100, D5000 and D5100', + Condition => '$$self{PhaseDetectAF} == 2', + PrintConvColumns => 2, + PrintConv => { + 0 => '(none)', + 1 => 'Center', + 2 => 'Top', + 3 => 'Bottom', + 4 => 'Mid-left', + 5 => 'Upper-left', + 6 => 'Lower-left', + 7 => 'Far Left', + 8 => 'Mid-right', + 9 => 'Upper-right', + 10 => 'Lower-right', + 11 => 'Far Right', + }, + }, + { #29 + Name => 'PrimaryAFPoint', + Condition => '$$self{PhaseDetectAF} == 3', + Notes => 'models with 39-point AF: D600 and D7000', + PrintConvColumns => 5, + PrintConv => { + 0 => '(none)', + %afPoints39, + 1 => 'C6 (Center)', # (add " (Center)" to central point) + }, + }, + { #PH + Name => 'PrimaryAFPoint', + Condition => '$$self{PhaseDetectAF} == 4', + Notes => 'Nikon 1 models with older 135-point AF and 73-point phase-detect AF', + PrintConvColumns => 5, + PrintConv => { + 0 => '(none)', + %afPoints135, + 1 => 'E8 (Center)', # (add " (Center)" to central point) + }, + }, + { #PH (NC) + Name => 'PrimaryAFPoint', + Condition => '$$self{PhaseDetectAF} == 5', + Notes => q{ + Nikon 1 models with newer 135-point AF and 73-point phase-detect AF -- 9 + rows (B-J) and 15 columns (1-15), inside a grid of 11 rows by 15 columns. + The points are numbered sequentially, with F8 at the center + }, + PrintConv => { + 0 => '(none)', + 82 => 'F8 (Center)', + OTHER => sub { + my ($val, $inv) = @_; + return GetAFPointGrid($val, 15, $inv); + }, + }, + }, + { #PH + Name => 'PrimaryAFPoint', + Condition => '$$self{PhaseDetectAF} == 6', + Notes => q{ + Nikon 1 models with 171-point AF and 105-point phase-detect AF -- 9 rows + (B-J) and 19 columns (2-20), inside a grid of 11 rows by 21 columns. The + points are numbered sequentially, with F11 at the center + }, + PrintConv => { + 0 => '(none)', + #22 => 'B2 (Top-left)', + #40 => 'B20 (Top-right)', + 115 => 'F11 (Center)', + #190 => 'J2 (Bottom-left)', + #208 => 'J20 (Bottom-right)', + OTHER => sub { + my ($val, $inv) = @_; + return GetAFPointGrid($val, 21, $inv); + }, + }, + }, + { #PH + Name => 'PrimaryAFPoint', + Condition => '$$self{PhaseDetectAF} == 7 and $$self{AFInfo2Version} eq "0100"', + Notes => q{ + Nikon models with 153-point AF -- 9 rows (A-I) and 17 columns (1-17): D5, + D500 and D850 + }, + PrintConvColumns => 5, + PrintConv => { + 0 => '(none)', + %afPoints153, + 1 => 'E9 (Center)', + }, + }, + { + Name => 'PrimaryAFPoint', + Condition => '$$self{AFInfo2Version} eq "0100"', + Notes => 'future models?...', + PrintConv => { + 0 => '(none)', + 1 => 'Center', + }, + }, + ], + 8 => [ + { #JD/PH + Name => 'AFPointsUsed', + Condition => '$$self{PhaseDetectAF} < 2 and $$self{AFInfo2Version} !~ /^03/', + Notes => q{ + models with 51-point AF -- 5 rows: A1-9, B1-11, C1-11, D1-11, E1-9. Center + point is C6 + }, + Format => 'undef[7]', + ValueConv => 'join(" ", unpack("H2"x7, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPoints(shift, \%afPoints51); }, + PrintConvInv => sub { PrintAFPointsInv(shift, \%afPoints51); }, + }, + { #10 + Name => 'AFPointsUsed', + Condition => '$$self{PhaseDetectAF} == 2', + Notes => 'models with 11-point AF', + # read as int16u in little-endian byte order + Format => 'undef[2]', + ValueConv => 'unpack("v",$val)', + ValueConvInv => 'pack("v",$val)', + PrintConvColumns => 2, + PrintConv => { + 0 => '(none)', + 0x7ff => 'All 11 Points', + BITMASK => { + 0 => 'Center', + 1 => 'Top', + 2 => 'Bottom', + 3 => 'Mid-left', + 4 => 'Upper-left', + 5 => 'Lower-left', + 6 => 'Far Left', + 7 => 'Mid-right', + 8 => 'Upper-right', + 9 => 'Lower-right', + 10 => 'Far Right', + }, + }, + }, + { #29/PH + Name => 'AFPointsUsed', + Condition => '$$self{PhaseDetectAF} == 3', + Notes => q{ + models with 39-point AF -- 5 rows: A1-3, B1-11, C1-11, D1-11, E1-3. Center + point is C6 + }, + Format => 'undef[5]', + ValueConv => 'join(" ", unpack("H2"x5, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPoints(shift, \%afPoints39); }, + PrintConvInv => sub { PrintAFPointsInv(shift, \%afPoints39); }, + }, + { #PH (1AW1,1J1,1J2,1J3,1S1,1V1,1V2) + Name => 'AFPointsUsed', + Condition => '$$self{PhaseDetectAF} == 4', + Notes => q{ + older models with 135-point AF -- 9 rows (A-I) and 15 columns (1-15). + Center point is E8. The odd-numbered columns, columns 2 and 14, and the + remaining corner points are not used for 41-point AF mode + }, + Format => 'undef[17]', + ValueConv => 'join(" ", unpack("H2"x17, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPoints(shift, \%afPoints135); }, + PrintConvInv => sub { PrintAFPointsInv(shift, \%afPoints135); }, + }, + { #PH (1S2) + Name => 'AFPointsUsed', + Condition => '$$self{PhaseDetectAF} == 5', + Notes => q{ + newer models with 135-point AF -- 9 rows (B-J) and 15 columns (1-15). Center + point is F8 + }, + Format => 'undef[21]', + ValueConv => 'join(" ", unpack("H2"x21, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPointsGrid(shift, 15); }, + PrintConvInv => sub { PrintAFPointsGridInv(shift, 15, 21); }, + }, + { #PH (1J4,1V3) + Name => 'AFPointsUsed', + Condition => '$$self{PhaseDetectAF} == 6', + Notes => q{ + models with 171-point AF -- 9 rows (B-J) and 19 columns (2-20). Center + point is F10 + }, + Format => 'undef[29]', + ValueConv => 'join(" ", unpack("H2"x29, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPointsGrid(shift, 21); }, + PrintConvInv => sub { PrintAFPointsGridInv(shift, 21, 29); }, + }, + { #PH (D5,D500) + Name => 'AFPointsUsed', + Condition => '$$self{PhaseDetectAF} == 7', + Notes => q{ + models with 153-point AF -- 9 rows (A-I) and 17 columns (1-17). Center + point is E9 + }, + Format => 'undef[20]', + ValueConv => 'join(" ", unpack("H2"x20, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPoints(shift, \%afPoints153); }, + PrintConvInv => sub { PrintAFPointsInv(shift, \%afPoints153); }, + }, + { #PH + Name => 'AFPointsUsed', + # version 301 uses a separate field at offset 0x0a for this tag (ref 28) + Condition => '$$self{AFInfo2Version} !~ /^03/', + Format => 'undef[7]', + ValueConv => 'join(" ", unpack("H2"x7, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => '"Unknown ($val)"', + PrintConvInv => '$val=~s/Unknown \\((.*)\\)/$1/; $val', + }, + { #PH + Name => 'PrimaryAFPoint', + Condition => '$$self{PhaseDetectAF} == 1 and $$self{AFInfo2Version} =~ /^03/', + Notes => 'newer models with 51-point AF', + PrintConvColumns => 5, + PrintConv => { + 0 => '(none)', + %afPoints51, + 1 => 'C6 (Center)', # (add " (Center)" to central point) + }, + }, + { #PH (Z7) + Name => 'PrimaryAFPoint', + Condition => '$$self{PhaseDetectAF} == 8 and $$self{AFInfo2Version} =~ /^03/', + PrintConv => { + 0 => '(none)', + %afPoints81, + 1 => 'E5 (Center)', # (add " (Center)" to central point) + }, + }, + # this was wrong, but keep the code as a comment in case it may be useful later + #{ #PH (Z7) (NC) + # Name => 'PrimaryAFPoint', + # Condition => '$$self{PhaseDetectAF} == 8 and $$self{AFInfo2Version} =~ /^03/', + # Notes => q{ + # Nikon models with 493-point AF -- 17 rows (A-Q) and 29 columns (1-29), I15 + # at the center + # }, + # PrintConv => { + # 0 => '(none)', + # 246 => 'I15 (Center)', + # OTHER => sub { + # my ($val, $inv) = @_; + # return GetAFPointGrid($val, 29, $inv); + # }, + # }, + #}, + ], + 0x0a => [{ #PH (D780) + Name => 'AFPointsUsed', + Condition => '$$self{PhaseDetectAF} == 1 and $$self{AFInfo2Version} =~ /^03/', + Notes => 'newer models with 51-point AF', + Format => 'undef[7]', + ValueConv => 'join(" ", unpack("H2"x7, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPoints(shift, \%afPoints51); }, + PrintConvInv => sub { PrintAFPointsInv(shift, \%afPoints51); }, + },{ #38 (Z6/Z7/Z50) + Name => 'AFPointsUsed', + Condition => '$$self{PhaseDetectAF} == 8 and $$self{AFInfo2Version} =~ /^03/', + Notes => q{ + models with 81-selectable point AF -- 9 rows (A-I) and 9 columns (1-9) for + phase detect AF points. Center point is E5 + }, + Format => 'undef[11]', + ValueConv => 'join(" ", unpack("H2"x11, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPoints(shift, \%afPoints81); }, + PrintConvInv => sub { PrintAFPointsInv(shift, \%afPoints81); }, + },{ #28 (D6) in any of the 3 Group modes on the D6, the points specify the outer boundaries of the focus point area; otherwise the tag value is consistent with other Nikon bodies + Name => 'AFPointsUsed', + Condition => '$$self{PhaseDetectAF} == 9 and $$self{AFInfo2Version} =~ /^03/', + Notes => q{ + models with 105-point AF -- 7 rows (A-G) and 15 columns (1-15). Center + point is D8 + }, + Format => 'undef[14]', + ValueConv => 'join(" ", unpack("H2"x14, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPoints(shift, \%afPoints105); }, + PrintConvInv => sub { PrintAFPointsInv(shift, \%afPoints105); }, + }], + 0x10 => { #PH (D90 and D5000) + Name => 'AFImageWidth', + Condition => '$$self{AFInfo2Version} eq "0100"', + Format => 'int16u', + RawConv => '$val ? $val : undef', + Notes => 'this and the following tags are valid only for contrast-detect AF', + }, + 0x12 => { #PH + Name => 'AFImageHeight', + Condition => '$$self{AFInfo2Version} eq "0100"', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x14 => { #PH + Name => 'AFAreaXPosition', + Condition => '$$self{AFInfo2Version} eq "0100"', + Notes => 'center of AF area in AFImage coordinates', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x16 => { #PH + Name => 'AFAreaYPosition', + Condition => '$$self{AFInfo2Version} eq "0100"', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + # AFAreaWidth/Height for the D90 and D5000: + # 352x288 (AF normal area), + # 704x576 (AF face priority, wide area, subject tracking) + 0x18 => { #PH + Name => 'AFAreaWidth', + Condition => '$$self{AFInfo2Version} eq "0100"', + Format => 'int16u', + Notes => 'size of AF area in AFImage coordinates', + RawConv => '$val ? $val : undef', + }, + 0x1a => { #PH + Name => 'AFAreaHeight', + Condition => '$$self{AFInfo2Version} eq "0100"', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x1c => [ + { #PH + Name => 'ContrastDetectAFInFocus', + Condition => '$$self{AFInfo2Version} eq "0100"', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + },{ #PH (D500, see forum11190) + Name => 'AFPointsSelected', + Condition => '$$self{AFInfo2Version} eq "0101" and $$self{PhaseDetectAF} == 7', + Format => 'undef[20]', + ValueConv => 'join(" ", unpack("H2"x20, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPoints(shift, \%afPoints153); }, + PrintConvInv => sub { PrintAFPointsInv(shift, \%afPoints153); }, + }, + ], + # 0x1d - always zero (with or without live view) + 0x2a => { #PH (Z7) + Name => 'AFImageWidth', + Condition => '$$self{AFInfo2Version} =~ /^03/', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x2c => { #PH (Z7) + Name => 'AFImageHeight', + Condition => '$$self{AFInfo2Version} =~ /^03/', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x2e => { #PH (Z7) + Name => 'AFAreaXPosition', + Condition => q{ + $$self{ContrastDetectAF} == 2 and $$self{AFInfo2Version} =~ /^03/ or + $$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} =~ /^0301/ + }, + Format => 'int16u', # (decodes same byte as 0x2f) + }, + 0x2f => { #28 (Z7) Still photography range 1-17 for the 493 point Z7 (arranged in a 29x17 grid. Center at x=16, y=10). + Name => 'FocusPositionHorizontal', + Condition => q{ + $$self{ContrastDetectAF} == 2 and $$self{AFInfo2Version} =~ /^03/ or + $$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} =~ /^0301/ + }, + PrintConv => sub { my ($val) = @_; PrintAFPointsLeftRight($val, 29 ); }, + }, + 0x30 => [ + { #PH (Z7) + Name => 'AFAreaYPosition', + Condition => q{ + $$self{ContrastDetectAF} == 2 and $$self{AFInfo2Version} =~ /^03/ or + $$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} =~ /^0301/ + }, + Format => 'int16u', # (decodes same byte as 0x31) + },{ #PH (D500, see forum11190) + Name => 'AFPointsInFocus', + Condition => '$$self{AFInfo2Version} eq "0101" and $$self{PhaseDetectAF} == 7', + Notes => 'AF points in focus at the time time image was captured', + Format => 'undef[20]', + ValueConv => 'join(" ", unpack("H2"x20, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPoints(shift, \%afPoints153); }, + PrintConvInv => sub { PrintAFPointsInv(shift, \%afPoints153); }, + }, + ], + 0x31 => { #28 (Z7) + Name => 'FocusPositionVertical', + Condition => q{ + $$self{ContrastDetectAF} == 2 and $$self{AFInfo2Version} =~ /^03/ or + $$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} =~ /^0301/ + }, + PrintConv => sub { my ($val) = @_; PrintAFPointsUpDown($val, 17 ); }, + }, + 0x32 => { #PH (Z7) + Name => 'AFAreaWidth', + Condition => '$$self{AFInfo2Version} =~ /^03/', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x34 => { #PH (Z7) + Name => 'AFAreaHeight', + Condition => '$$self{AFInfo2Version} =~ /^03/', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x38 => { #28 + Name => 'PrimaryAFPoint', + Condition => '$$self{PhaseDetectAF} == 9 and $$self{AFInfo2Version} =~ /^03/', + Notes => q{ + Nikon models with 105-point AF -- 7 rows (A-G) and 15 columns (1-15): D6 + }, + PrintConvColumns => 5, + PrintConv => { + 0 => '(none)', + %afPoints105, + 1 => 'D8 (Center)', + }, + }, + 0x44 => [ + { + Name => 'PrimaryAFPoint', + Condition => '$$self{PhaseDetectAF} == 7 and $$self{AFInfo2Version} eq "0101"', + PrintConvColumns => 5, + PrintConv => { + 0 => '(none)', + %afPoints153, + 1 => 'E9 (Center)', + }, + }, + { #PH + Name => 'PrimaryAFPoint', + Notes => 'D3500', + Condition => '$$self{PhaseDetectAF} == 2 and $$self{AFInfo2Version} eq "0101"', + PrintConvColumns => 2, + PrintConv => { + 0 => '(none)', + 1 => 'Center', + 2 => 'Top', + 3 => 'Bottom', + 4 => 'Mid-left', + 5 => 'Upper-left', + 6 => 'Lower-left', + 7 => 'Far Left', + 8 => 'Mid-right', + 9 => 'Upper-right', + 10 => 'Lower-right', + 11 => 'Far Right', + }, + }, + { + Name => 'PrimaryAFPoint', + Condition => '$$self{AFInfo2Version} eq "0101"', + Notes => 'future models?...', + Priority => 0, + PrintConv => { + 0 => '(none)', + 1 => 'Center', + }, + }, + ], + 0x46 => { + Name => 'AFImageWidth', + Condition => '$$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} eq "0101"', + Format => 'int16u', + RawConv => '$val ? $val : undef', + Notes => 'this and the following tags are valid only for contrast-detect AF', + }, + 0x48 => { + Name => 'AFImageHeight', + Condition => '$$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} eq "0101"', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x4a => { + Name => 'AFAreaXPosition', + Condition => '$$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} eq "0101"', + Notes => 'center of AF area in AFImage coordinates', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x4c => { + Name => 'AFAreaYPosition', + Condition => '$$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} eq "0101"', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x4e => { + Name => 'AFAreaWidth', + Condition => '$$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} eq "0101"', + Format => 'int16u', + Notes => 'size of AF area in AFImage coordinates', + RawConv => '$val ? $val : undef', + }, + 0x50 => { + Name => 'AFAreaHeight', + Condition => '$$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} eq "0101"', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x52 => { + Name => 'ContrastDetectAFInFocus', + Condition => '$$self{ContrastDetectAF} == 1 and $$self{AFInfo2Version} eq "0101"', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, +); + +%Image::ExifTool::Nikon::AFInfo2V0400 = ( #V0400 related fields begin at x'3c' (Z9) + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0 ], + 0 => { + Name => 'AFInfo2Version', + Format => 'undef[4]', + Writable => 0, + RawConv => '$$self{AFInfo2Version} = $val', + }, + 5 => { #28 + Name => 'AFAreaMode', #reflects the mode active when the shutter is tripped, not the position of the Focus Mode button (which is recorded in MenuSettingsZ9 tag also named AfAreaMode) + PrintConv => { + 192 => 'Pinpoint', + 193 => 'Single', + 195 => 'Wide (S)', + 196 => 'Wide (L)', + 197 => 'Auto', + 204 => 'Dynamic Area (S)', + 205 => 'Dynamic Area (M)', + 206 => 'Dynamic Area (L)', + 207 => '3D-tracking', + 208 => 'Wide (C1/C2)', + }, + }, + 10 => { + Name => 'AFPointsUsed', + Condition => '$$self{AFAreaMode} == 6', #only valid for Auto AF Area mode. Other modes handled via AFAreaXPosition/AFAreaYPosition + Format => 'undef[51]', + ValueConv => 'join(" ", unpack("H2"x51, $val))', + ValueConvInv => '$val=~tr/ //d; pack("H*",$val)', + PrintConv => sub { PrintAFPoints(shift, \%afPoints493); }, + PrintConvInv => sub { PrintAFPointsInv(shift, \%afPoints493); }, + }, + 0x3e => { + Name => 'AFImageWidth', + Format => 'int16u', + }, + 0x40 => { + Name => 'AFImageHeight', + Format => 'int16u', + }, + 0x42 => { #28 + Name => 'AFAreaXPosition', #top left image corner is the origin + Format => 'int16u', # (decodes same byte as 0x43) + RawConv => '$val ? $val : undef', + }, + 0x43 => { + Name => 'FocusPositionHorizontal', + Notes => q{ + the focus points form a 29x17 grid, but the X,Y coordinate values run from 1,1 + to 30,19. The horizontal coordinate 11R (5) and the vertical coordinates 6U + (4) and 2D (12) are not used for some reason + }, + # 493 focus points for Z9 fall in a 30x19 grid + # (the 11R (5) position is not used, for a total of 29 columns, ref AlbertShan email) + PrintConv => sub { my ($val) = @_; PrintAFPointsLeftRight($val, 29); }, + }, + 0x44 => { #28 + Name => 'AFAreaYPosition', + Format => 'int16u', # (decodes same byte as 0x45) + RawConv => '$val ? $val : undef', + }, + 0x45 => { + Name => 'FocusPositionVertical', + # (the 6U (4) and 2D (12) are not used, for a total of 17 rows, ref AlbertShan email) + PrintConv => sub { my ($val) = @_; PrintAFPointsUpDown($val, 17); }, + }, + 0x46 => { + Name => 'AFAreaWidth', + Format => 'int16u', + Notes => 'size of AF area in AFImage pixels', + RawConv => '$val ? $val : undef', + }, + 0x48 => { + Name => 'AFAreaHeight', + Format => 'int16u', + RawConv => '$val ? $val : undef', + }, + 0x4a => { + Name => 'FocusResult', + # in Manual Foucs mode, reflects the state of viewfinder focus indicator. + # In AF-C or AF-S, reflects the result of the last AF operation. + PrintConv => { 0=> "Out of Focus", 1=>"Focus"}, + }, +); + +# Nikon AF fine-tune information (ref 28) +%Image::ExifTool::Nikon::AFTune = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'AFFineTune', + PrintConv => { + 0 => 'Off', + # (don't know what the difference between 1 and 2 is) + 1 => 'On (1)', + 2 => 'On (2)', + 3 => 'On (Zoom)', #28 + }, + }, + 1 => { + Name => 'AFFineTuneIndex', + Notes => 'index of saved lens', + PrintConv => '$val == 255 ? "n/a" : $val', + PrintConvInv => '$val eq "n/a" ? 255 : $val', + }, + 2 => { + # when AFFineTune = 3 (indicating a zoom lens), this Tag stores the tuning adjustment for the wide end of the zoom range (ref 28) + Name => 'AFFineTuneAdj', + Priority => 0, # so other value takes priority if it exists + Notes => 'may only be valid for saved lenses', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 3 => { + Name => 'AFFineTuneAdjTele', + # should probably insert a Condition that restricts this to AFFineTune = 3 (ref 28) + Notes => 'only valid for zoom lenses (ie, AFTune=3)', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, +); + +# Nikon NEF processing information (ref forum6281) +%Image::ExifTool::Nikon::RetouchInfo = ( + %binaryDataAttrs, + FORMAT => 'int8s', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0 ], + 0 => { + Name => 'RetouchInfoVersion', + Format => 'undef[4]', + Writable => 0, + RawConv => '$$self{RetouchInfoVersion} = $val', + }, + # 4 - RetouchExposureComp (+$val/6 or -$val/6?) + 5 => { + Name => 'RetouchNEFProcessing', + Condition => '$$self{RetouchInfoVersion} ge "0200"', + PrintConv => { + -1 => 'Off', + 1 => 'On', + }, + }, +); + +# Nikon File information - D60, D3 and D300 (ref PH) +%Image::ExifTool::Nikon::FileInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FORMAT => 'int16u', + 0 => { + Name => 'FileInfoVersion', + Format => 'undef[4]', + Writable => 0, + }, + 2 => 'MemoryCardNumber', + 3 => { + Name => 'DirectoryNumber', + PrintConv => 'sprintf("%.3d", $val)', + PrintConvInv => '$val', + }, + 4 => { + Name => 'FileNumber', + PrintConv => 'sprintf("%.4d", $val)', + PrintConvInv => '$val', + }, +); + +# ref PH +%Image::ExifTool::Nikon::BarometerInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Location' }, + 0 => { + Name => 'BarometerInfoVersion', + Format => 'undef[4]', + Writable => 0, + }, + 6 => { + Name => 'Altitude', + Format => 'int32s', + PrintConv => '"$val m"', # (always stored as metres) + PrintConvInv => '$val=~s/\s*m$//; $val', + }, + # 10: int16u - values: 0 (display in metres?), 18 (display in feet?) +); + +# ref PH +%Image::ExifTool::Nikon::CaptureOffsets = ( + PROCESS_PROC => \&ProcessNikonCaptureOffsets, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # (note that these are duplicates of offsets in the normal TIFF structure, + # and that these offsets are not updated when ExifTool rewrites the file) + 1 => 'IFD0_Offset', + 2 => 'PreviewIFD_Offset', + 3 => 'SubIFD_Offset', +); + +# ref PH (Written by capture NX) +%Image::ExifTool::Nikon::CaptureOutput = ( + %binaryDataAttrs, + FORMAT => 'int32u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + # 1 = 1 + 2 => 'OutputImageWidth', + 3 => 'OutputImageHeight', + 4 => 'OutputResolution', + # 5 = 1 +); + +# ref IB +%Image::ExifTool::Nikon::ColorBalanceA = ( + %binaryDataAttrs, + FORMAT => 'int16u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 624 => { #4 + Name => 'WB_RBLevels', + Notes => 'as shot', #IB + Format => 'int16u[2]', + Protected => 1, + }, + 626 => { + Name => 'WB_RBLevelsAuto', + Format => 'int16u[2]', + Protected => 1, + }, + 628 => { + Name => 'WB_RBLevelsDaylight', + Notes => 'red/blue levels for 0,+3,+2,+1,-1,-2,-3', + Format => 'int16u[14]', + Protected => 1, + }, + 642 => { + Name => 'WB_RBLevelsIncandescent', + Format => 'int16u[14]', + Protected => 1, + }, + 656 => { + Name => 'WB_RBLevelsFluorescent', + Format => 'int16u[6]', + Notes => 'red/blue levels for fluorescent W,N,D', + Protected => 1, + }, + 662 => { + Name => 'WB_RBLevelsCloudy', + Format => 'int16u[14]', + Protected => 1, + }, + 676 => { + Name => 'WB_RBLevelsFlash', + Format => 'int16u[14]', + Protected => 1, + }, + 690 => { + Name => 'WB_RBLevelsShade', + Condition => '$$self{Model} ne "E8700"', + Notes => 'not valid for E8700', + Format => 'int16u[14]', + Protected => 1, + }, +); + +my %nrwLevels = ( + Format => 'int32u[4]', + Protected => 1, + ValueConv => 'my @a=split " ",$val;$a[0]*=2;$a[3]*=2;"@a"', + ValueConvInv => 'my @a=split " ",$val;$a[0]/=2;$a[3]/=2;"@a"', +); + +# (ref IB) +%Image::ExifTool::Nikon::ColorBalanceB = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Color balance tags used by the P6000.', + 0x0004 => { + Name => 'ColorBalanceVersion', + Format => 'undef[4]', + }, + 0x13e8 => { Name => 'WB_RGGBLevels', %nrwLevels }, + 0x13f8 => { Name => 'WB_RGGBLevelsDaylight', %nrwLevels }, + 0x1408 => { Name => 'WB_RGGBLevelsCloudy', %nrwLevels }, + 0x1428 => { Name => 'WB_RGGBLevelsTungsten', %nrwLevels }, + 0x1438 => { Name => 'WB_RGGBLevelsFluorescentW',%nrwLevels }, + 0x1448 => { Name => 'WB_RGGBLevelsFlash', %nrwLevels }, + 0x1468 => { Name => 'WB_RGGBLevelsCustom', %nrwLevels, Notes => 'all zero if preset WB not used' }, + 0x1478 => { Name => 'WB_RGGBLevelsAuto', %nrwLevels }, +); + +# (ref IB) +%Image::ExifTool::Nikon::ColorBalanceC = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 4 ], + NOTES => 'Color balance tags used by the P1000, P7000, P7100 and B700.', + 0x0004 => { + Name => 'ColorBalanceVersion', + Format => 'undef[4]', + RawConv => '$$self{ColorBalanceVersion} = $val', + }, + 0x0020 => { Name => 'BlackLevel', Format => 'int16u' }, + 0x0038 => { Name => 'WB_RGGBLevels', %nrwLevels }, + 0x004c => { Name => 'WB_RGGBLevelsDaylight', %nrwLevels }, + 0x0060 => { Name => 'WB_RGGBLevelsCloudy', %nrwLevels }, + 0x0074 => { + Name => 'WB_RGGBLevelsShade', + Condition => '$$self{ColorBalanceVersion} ge "0104"', + Notes => 'valid only for some models', + %nrwLevels, + }, + 0x0088 => { Name => 'WB_RGGBLevelsTungsten', %nrwLevels }, + 0x009c => { Name => 'WB_RGGBLevelsFluorescentW',%nrwLevels }, + 0x00b0 => { Name => 'WB_RGGBLevelsFluorescentN',%nrwLevels }, + 0x00c4 => { Name => 'WB_RGGBLevelsFluorescentD',%nrwLevels }, + 0x00d8 => { Name => 'WB_RGGBLevelsHTMercury', %nrwLevels }, + 0x0100 => { Name => 'WB_RGGBLevelsCustom', %nrwLevels, Notes => 'all zero if preset WB not used' }, + 0x0114 => { Name => 'WB_RGGBLevelsAuto', %nrwLevels }, +); + +# ref 4 +%Image::ExifTool::Nikon::ColorBalance1 = ( + %binaryDataAttrs, + FORMAT => 'int16u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'WB_RBGGLevels', + Format => 'int16u[4]', + Protected => 1, + }, +); + +# ref 4 +%Image::ExifTool::Nikon::ColorBalance2 = ( + %binaryDataAttrs, + FORMAT => 'int16u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'This information is encrypted for most camera models.', + 0 => { + Name => 'WB_RGGBLevels', + Format => 'int16u[4]', + Protected => 1, + }, +); + +# ref 4 +%Image::ExifTool::Nikon::ColorBalance3 = ( + %binaryDataAttrs, + FORMAT => 'int16u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'WB_RGBGLevels', + Format => 'int16u[4]', + Protected => 1, + }, +); + +# ref 4 +%Image::ExifTool::Nikon::ColorBalance4 = ( + %binaryDataAttrs, + FORMAT => 'int16u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'WB_GRBGLevels', + Format => 'int16u[4]', + Protected => 1, + }, +); + +%Image::ExifTool::Nikon::ColorBalanceUnknown = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'ColorBalanceVersion', + Format => 'undef[4]', + }, +); + +%Image::ExifTool::Nikon::Type2 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0003 => 'Quality', + 0x0004 => 'ColorMode', + 0x0005 => 'ImageAdjustment', + 0x0006 => 'CCDSensitivity', + 0x0007 => 'WhiteBalance', + 0x0008 => 'Focus', + 0x000A => 'DigitalZoom', + 0x000B => 'Converter', +); + +# these are standard EXIF tags, but they are duplicated here so we can +# set the family 0 group to 'MakerNotes' and set the MINOR_ERRORS flag +%Image::ExifTool::Nikon::PreviewIFD = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'PreviewIFD', 2 => 'Image'}, + VARS => { MINOR_ERRORS => 1 }, # this IFD is non-essential and often corrupted + # (these tags are priority 0 by default because PreviewIFD is flagged in LOW_PRIORITY_DIR) + 0xfe => { # (not used by Nikon, but SRW images also use this table) + Name => 'SubfileType', + DataMember => 'SubfileType', + RawConv => '$$self{SubfileType} = $val', + PrintConv => \%Image::ExifTool::Exif::subfileType, + }, + 0x103 => { + Name => 'Compression', + SeparateTable => 'EXIF Compression', + PrintConv => \%Image::ExifTool::Exif::compression, + }, + 0x11a => 'XResolution', + 0x11b => 'YResolution', + 0x128 => { + Name => 'ResolutionUnit', + PrintConv => { + 1 => 'None', + 2 => 'inches', + 3 => 'cm', + }, + }, + 0x201 => { + Name => 'PreviewImageStart', + Flags => [ 'IsOffset', 'Permanent' ], + OffsetPair => 0x202, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x202 => { + Name => 'PreviewImageLength', + Flags => 'Permanent' , + OffsetPair => 0x201, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x213 => { + Name => 'YCbCrPositioning', + PrintConv => { + 1 => 'Centered', + 2 => 'Co-sited', + }, + }, +); + +# these are duplicated enough times to make it worthwhile to define them centrally +my %nikonApertureConversions = ( + ValueConv => '2**($val/24)', + ValueConvInv => '$val>0 ? 24*log($val)/log(2) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', +); + +my %nikonFocalConversions = ( + ValueConv => '5 * 2**($val/24)', + ValueConvInv => '$val>0 ? 24*log($val/5)/log(2) : 0', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//;$val', +); + +# Version 100 Nikon lens data +%Image::ExifTool::Nikon::LensData00 = ( + %binaryDataAttrs, + NOTES => 'This structure is used by the D100, and D1X with firmware version 1.1.', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0x00 => { + Name => 'LensDataVersion', + Format => 'undef[4]', + Writable => 0, + }, + 0x06 => { #8 + Name => 'LensIDNumber', + Notes => 'see LensID values below', + }, + 0x07 => { #8 + Name => 'LensFStops', + ValueConv => '$val / 12', + ValueConvInv => '$val * 12', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x08 => { #8/9 + Name => 'MinFocalLength', + %nikonFocalConversions, + }, + 0x09 => { #8/9 + Name => 'MaxFocalLength', + %nikonFocalConversions, + }, + 0x0a => { #8 + Name => 'MaxApertureAtMinFocal', + %nikonApertureConversions, + }, + 0x0b => { #8 + Name => 'MaxApertureAtMaxFocal', + %nikonApertureConversions, + }, + 0x0c => 'MCUVersion', #8 (MCU = Micro Controller Unit) +); + +# Nikon lens data (note: needs decrypting if LensDataVersion is 020x) +%Image::ExifTool::Nikon::LensData01 = ( + %binaryDataAttrs, + NOTES => q{ + Nikon encrypts the LensData information below if LensDataVersion is 0201 or + higher, but the decryption algorithm is known so the information can be + extracted. + }, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0x00 => { + Name => 'LensDataVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { #8 + Name => 'ExitPupilPosition', + ValueConv => '$val ? 2048 / $val : $val', + ValueConvInv => '$val ? 2048 / $val : $val', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//; $val', + }, + 0x05 => { #8 + Name => 'AFAperture', + %nikonApertureConversions, + }, + 0x08 => { #8 + # this seems to be 2 values: the upper nibble gives the far focus + # range and the lower nibble gives the near focus range. The values + # are in the range 1-N, where N is lens-dependent. A value of 0 for + # the far focus range indicates infinity. (ref JD) + Name => 'FocusPosition', + PrintConv => 'sprintf("0x%02x", $val)', + PrintConvInv => '$val', + }, + 0x09 => { #8/9 + # With older AF lenses this does not work... (ref 13) + # eg) AF Nikkor 50mm f/1.4 => 48 (0x30) + # AF Zoom-Nikkor 35-105mm f/3.5-4.5 => @35mm => 15 (0x0f), @105mm => 141 (0x8d) + Notes => 'this focus distance is approximate, and not very accurate for some lenses', + Name => 'FocusDistance', + ValueConv => '0.01 * 10**($val/40)', # in m + ValueConvInv => '$val>0 ? 40*log($val*100)/log(10) : 0', + PrintConv => '$val ? sprintf("%.2f m",$val) : "inf"', + PrintConvInv => '$val eq "inf" ? 0 : $val =~ s/\s*m$//, $val', + }, + 0x0a => { #8/9 + Name => 'FocalLength', + Priority => 0, + %nikonFocalConversions, + }, + 0x0b => { #8 + Name => 'LensIDNumber', + Notes => 'see LensID values below', + }, + 0x0c => { #8 + Name => 'LensFStops', + ValueConv => '$val / 12', + ValueConvInv => '$val * 12', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x0d => { #8/9 + Name => 'MinFocalLength', + %nikonFocalConversions, + }, + 0x0e => { #8/9 + Name => 'MaxFocalLength', + %nikonFocalConversions, + }, + 0x0f => { #8 + Name => 'MaxApertureAtMinFocal', + %nikonApertureConversions, + }, + 0x10 => { #8 + Name => 'MaxApertureAtMaxFocal', + %nikonApertureConversions, + }, + 0x11 => 'MCUVersion', #8 (MCU = Micro Controller Unit) + 0x12 => { #8 + Name => 'EffectiveMaxAperture', + %nikonApertureConversions, + }, +); + +# Nikon lens data (note: needs decrypting) +%Image::ExifTool::Nikon::LensData0204 = ( + %binaryDataAttrs, + NOTES => q{ + Nikon encrypts the LensData information below if LensDataVersion is 0201 or + higher, but the decryption algorithm is known so the information can be + extracted. + }, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0x00 => { + Name => 'LensDataVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { #8 + Name => 'ExitPupilPosition', + ValueConv => '$val ? 2048 / $val : $val', + ValueConvInv => '$val ? 2048 / $val : $val', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//; $val', + }, + 0x05 => { #8 + Name => 'AFAperture', + %nikonApertureConversions, + }, + 0x08 => { #8 + # this seems to be 2 values: the upper nibble gives the far focus + # range and the lower nibble gives the near focus range. The values + # are in the range 1-N, where N is lens-dependent. A value of 0 for + # the far focus range indicates infinity. (ref JD) + Name => 'FocusPosition', + PrintConv => 'sprintf("0x%02x", $val)', + PrintConvInv => '$val', + }, + # --> extra byte at position 0x09 in this version of LensData (PH) + 0x0a => { #8/9 + # With older AF lenses this does not work... (ref 13) + # eg) AF Nikkor 50mm f/1.4 => 48 (0x30) + # AF Zoom-Nikkor 35-105mm f/3.5-4.5 => @35mm => 15 (0x0f), @105mm => 141 (0x8d) + Notes => 'this focus distance is approximate, and not very accurate for some lenses', + Name => 'FocusDistance', + ValueConv => '0.01 * 10**($val/40)', # in m + ValueConvInv => '$val>0 ? 40*log($val*100)/log(10) : 0', + PrintConv => '$val ? sprintf("%.2f m",$val) : "inf"', + PrintConvInv => '$val eq "inf" ? 0 : $val =~ s/\s*m$//, $val', + }, + 0x0b => { #8/9 + Name => 'FocalLength', + Priority => 0, + %nikonFocalConversions, + }, + 0x0c => { #8 + Name => 'LensIDNumber', + Notes => 'see LensID values below', + }, + 0x0d => { #8 + Name => 'LensFStops', + ValueConv => '$val / 12', + ValueConvInv => '$val * 12', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x0e => { #8/9 + Name => 'MinFocalLength', + %nikonFocalConversions, + }, + 0x0f => { #8/9 + Name => 'MaxFocalLength', + %nikonFocalConversions, + }, + 0x10 => { #8 + Name => 'MaxApertureAtMinFocal', + %nikonApertureConversions, + }, + 0x11 => { #8 + Name => 'MaxApertureAtMaxFocal', + %nikonApertureConversions, + }, + 0x12 => 'MCUVersion', #8 (MCU = Micro Controller Unit) + 0x13 => { #8 + Name => 'EffectiveMaxAperture', + %nikonApertureConversions, + }, +); + +# Nikon lens data version 0400 (note: needs decrypting) (ref PH) +%Image::ExifTool::Nikon::LensData0400 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Tags extracted from the encrypted lens data of the Nikon 1J1/1V1/1J2.', + 0x00 => { + Name => 'LensDataVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x18a => { #PH + Name => 'LensModel', + Format => 'string[64]', + }, +); + +# Nikon lens data version 0402 (note: needs decrypting) (ref PH) +%Image::ExifTool::Nikon::LensData0402 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Tags extracted from the encrypted lens data of the Nikon 1J3/1S1/1V2.', + 0x00 => { + Name => 'LensDataVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x18b => { #PH + Name => 'LensModel', + Format => 'string[64]', + }, +); + +# Nikon lens data version 0403 (note: needs decrypting) (ref PH) +%Image::ExifTool::Nikon::LensData0403 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Tags extracted from the encrypted lens data of the Nikon 1J4/1J5.', + 0x00 => { + Name => 'LensDataVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x2ac => { #PH + Name => 'LensModel', + Format => 'string[64]', + }, +); + +# Nikon Z lens data (note: needs decrypting) (ref PH, based on LensData0204) +%Image::ExifTool::Nikon::LensData0800 = ( + %binaryDataAttrs, + NOTES => 'Tags found in the encrypted LensData from cameras such as the Z6 and Z7.', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0x03, 0x2f, 0x35, 0x4c, 0x56, 0x58 ], + 0x00 => { + Name => 'LensDataVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x03 => { # look forward to see if new old data exists... + Name => 'OldLensData', + Format => 'undef[17]', + RawConv => '$$self{OldLensData} = 1 unless $val =~ /^.\0+$/s; undef', + Hidden => 1, + }, + 0x04 => { + Name => 'ExitPupilPosition', + Condition => '$$self{OldLensData}', + ValueConv => '$val ? 2048 / $val : $val', + ValueConvInv => '$val ? 2048 / $val : $val', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//; $val', + }, + 0x05 => { + Name => 'AFAperture', + Condition => '$$self{OldLensData}', + %nikonApertureConversions, + }, + # --> another extra byte at position 0x08 in this version of LensData (PH) + #0x09 => { + # Name => 'FocusPosition', #28 - this appears to be copied from an older version of LensData and is no longer valid. Text with Z9 and Z7_2 with a variety of lenses + # Condition => '$$self{OldLensData}', + # PrintConv => 'sprintf("0x%02x", $val)', + # PrintConvInv => '$val', + #}, + 0x0b => { + Notes => 'this focus distance is approximate, and not very accurate for some lenses', + Name => 'FocusDistance', + Condition => '$$self{OldLensData}', + ValueConv => '0.01 * 10**($val/40)', # in m + ValueConvInv => '$val>0 ? 40*log($val*100)/log(10) : 0', + PrintConv => '$val ? sprintf("%.2f m",$val) : "inf"', + PrintConvInv => '$val eq "inf" ? 0 : $val =~ s/\s*m$//, $val', + }, + 0x0c => { + Name => 'FocalLength', + Condition => '$$self{OldLensData}', + Priority => 0, + %nikonFocalConversions, + }, + 0x0d => { + Name => 'LensIDNumber', + Condition => '$$self{OldLensData}', + Notes => 'see LensID values below', + }, + 0x0e => { + Name => 'LensFStops', + Condition => '$$self{OldLensData}', + ValueConv => '$val / 12', + ValueConvInv => '$val * 12', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x0f => { + Name => 'MinFocalLength', + Condition => '$$self{OldLensData}', + %nikonFocalConversions, + }, + 0x10 => { + Name => 'MaxFocalLength', + Condition => '$$self{OldLensData}', + %nikonFocalConversions, + }, + 0x11 => { + Name => 'MaxApertureAtMinFocal', + Condition => '$$self{OldLensData}', + %nikonApertureConversions, + }, + 0x12 => { + Name => 'MaxApertureAtMaxFocal', + Condition => '$$self{OldLensData}', + %nikonApertureConversions, + }, + 0x13 => { + Name => 'MCUVersion', + Condition => '$$self{OldLensData}', + }, + 0x14 => { + Name => 'EffectiveMaxAperture', + Condition => '$$self{OldLensData}', + %nikonApertureConversions, + }, +# +# ---- new LensData tags used by Nikkor Z cameras (ref PH/28). ---- +# (some fields are strictly for Z-series lenses, others apply to legacy F-mount as well, ref 28) +# + 0x2f => { # look forward to see if new lens data exists... + Name => 'NewLensData', + Format => 'undef[17]', + RawConv => '$$self{NewLensData} = 1 unless $val =~ /^.\0+$/s; undef', + #Hidden => 1, + }, + 0x30 => { #PH + Name => 'LensID', + Condition => '$$self{NewLensData}', + Notes => 'tags from here onward used for Nikkor Z lenses only', + Format => 'int16u', + PrintConv => { + 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', #IB + 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', #IB + 16 => 'Nikkor Z 70-200mm f/2.8 VR S', #IB + 17 => 'Nikkor Z 20mm f/1.8 S', #IB + 18 => 'Nikkor Z 24-200mm f/4-6.3 VR', #IB + 21 => 'Nikkor Z 50mm f/1.2 S', #IB + 22 => 'Nikkor Z 24-50mm f/4-6.3', #IB + 23 => 'Nikkor Z 14-24mm f/2.8 S', #IB + 24 => 'Nikkor Z MC 105mm f/2.8 VR S', #IB + 25 => 'Nikkor Z 40mm f/2', #28 + 26 => 'Nikkor Z DX 18-140mm f/3.5-6.3 VR', #IB + 27 => 'Nikkor Z MC 50mm f/2.8', #IB + 28 => 'Nikkor Z 100-400mm f/4.5-5.6 VR S', #28 + 29 => 'Nikkor Z 28mm f/2.8', #IB + 30 => 'Nikkor Z 400mm f/2.8 TC VR S', #28 + 31 => 'Nikkor Z 24-120 f/4', #28 + 32 => 'Nikkor Z 800mm f/6.3 VR S', #28 + 35 => 'Nikkor Z 28-75mm f/2.8', #IB + 36 => 'Nikkor Z 400mm f/4.5 VR S', #IB + 37 => 'Nikkor Z 600mm f/4 TC VR S', #28 + 38 => 'Nikkor Z 85mm f/1.2 S', #28 + 39 => 'Nikkor Z 17-28mm f/2.8', #IB + 32768 => 'Nikkor Z 400mm f/2.8 TC VR S TC-1.4x', #28 + 32769 => 'Nikkor Z 600mm f/4 TC VR S TC-1.4x', #28 + }, + }, + 0x35 => { #28 + Name => 'LensMountType', + RawConv => '$$self{LensMountType} = $val', # 0=> DSLR lens via FTZ style adapter; 1=> Native Z lens; + Format => 'int8u', + #Unknown => 1, + PrintConv => { + 0 => 'F-mount Lens', + 1 => 'Z-mount Lens', + }, + }, + 0x36 => { #PH + Name => 'MaxAperture', + Condition => '$$self{NewLensData}', + Format => 'int16u', + Priority => 0, + ValueConv => '2**($val/384-1)', + ValueConvInv => '384*(log($val)/log(2)+1)', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x38 => { #PH + Name => 'FNumber', + Condition => '$$self{NewLensData}', + Format => 'int16u', + Priority => 0, + ValueConv => '2**($val/384-1)', + ValueConvInv => '384*(log($val)/log(2)+1)', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x3c => { #PH + Name => 'FocalLength', + Condition => '$$self{NewLensData}', + Format => 'int16u', + Priority => 0, + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + 0x4c => { #28 + Name => 'FocusDistanceRangeWidth', #reflects the number of discrete absolute lens positions that are mapped to the reported FocusDistance. Will be 1 near CFD reflecting very narrow focus distance bands (i.e., quite accurate). Near Infinity will be something like 32. Note: 0 at infinity. + Format => 'int8u', + Condition => '$$self{NewLensData} and $$self{LensMountType} and $$self{LensMountType} == 1 and $$self{FocusMode} ne "Manual"', + RawConv => '$$self{FocusDistanceRangeWidth} = $val', + Unknown => 1, + }, + 0x4e => { #28 + Name => 'FocusDistance', + Format => 'int16u', + Condition => '$$self{NewLensData} and $$self{LensMountType} and $$self{LensMountType} == 1', + RawConv => '$val = $val/256', # 1st byte is the fractional component. This byte was not previously considered in the legacy calculation (which only used the 2nd byte). When 2nd byte < 80; distance is < 1 meter + ValueConv => '2**(($val-80)/12)', # in m #slighly more accurate than the legacy calcualtion of '0.01 * 10**($val/40)'. Tested at all focus positions using the 105mm,70-200mm & 600mm + ValueConvInv => '$val>0 ? log(12*($val+80)/log(2) : 0', #was '$val>0 ? 40*log($val*100)/log(10) : 0' + PrintConv => q{ + (defined $$self{FocusStepsFromInfinity} and not $$self{FocusStepsFromInfinity}) ? "Inf" : $val < 1 ? $val < 0.35 ? sprintf("%.4f m", $val): sprintf("%.3f m", $val): sprintf("%.2f m", $val), #distances less than 35mm are quite accurate with increasingly less precision past 1m + }, + }, + 0x56 => { #28 #not valif for focus mode M + Name => 'LensDriveEnd', # byte contains: 1 at CFD/MOD; 2 at Infinity; 0 otherwise + Condition => '$$self{NewLensData} and $$self{LensMountType} and $$self{LensMountType} == 1 and $$self{FocusMode} ne "Manual"', + Format => 'int8u', + RawConv => 'unless (defined $$self{FocusDistanceRangeWidth} and not $$self{FocusDistanceRangeWidth}) { if ($val == 0 ) {$$self{LensDriveEnd} = "No"} else { $$self{LensDriveEnd} = "CFD"}; } else{ $$self{LensDriveEnd} = "Inf"}', + Unknown => 1, + }, + 0x58 => { #28 + Name => 'FocusStepsFromInfinity', + Condition => '$$self{NewLensData} and $$self{LensMountType} and $$self{LensMountType} == 1', #valid for both AF and manual focus modes + Format => 'int8u', + RawConv => '$$self{FocusStepsFromInfinity} = $val', # 0 at Infinity, otherwise a small positive number monotonically increasing towards CFD. + Unknown => 1, + }, + 0x5a => { #28 + Name => 'LensPositionAbsolute', # <=0 at infinity. Typical value at CFD might be 58000. Only valid for Z-mount lenses. + Condition => '$$self{NewLensData} and $$self{LensMountType} and $$self{LensMountType} == 1', + Format => 'int32s', + #Unknown => 1, + }, +); + +# Unknown Nikon lens data (note: data may need decrypting after byte 4) +%Image::ExifTool::Nikon::LensDataUnknown = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x00 => { + Name => 'LensDataVersion', + Format => 'string[4]', + }, +); + +# shot information (encrypted in some cameras) - ref 18 +%Image::ExifTool::Nikon::ShotInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0 ], + NOTES => q{ + This information is encrypted for ShotInfoVersion 02xx, and some tags are + only valid for specific models. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + RawConv => '$$self{ShotInfoVersion} = $val; $val =~ /^\d+$/ ? $val : undef', + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + RawConv => '$val =~ /^\d\.\d+.$/ ? $val : undef', + }, + 0x10 => { + Name => 'DistortionControl', + Condition => '$$self{Model} =~ /P6000\b/', + Notes => 'P6000', + PrintConv => \%offOn, + }, + 0x66 => { + Name => 'VR_0x66', + Condition => '$$self{ShotInfoVersion} eq "0204"', + Format => 'int8u', + Unknown => 1, + Notes => 'D2X, D2Xs (unverified)', + PrintConv => { + 0 => 'Off', + 1 => 'On (normal)', + 2 => 'On (active)', + }, + }, + # 6a, 6e not correct for 0103 (D70), 0207 (D200) + 0x6a => { + Name => 'ShutterCount', + Condition => '$$self{ShotInfoVersion} eq "0204"', + Format => 'int32u', + Priority => 0, + Notes => 'D2X, D2Xs', + }, + 0x6e => { + Name => 'DeletedImageCount', + Condition => '$$self{ShotInfoVersion} eq "0204"', + Format => 'int32u', + Priority => 0, + Notes => 'D2X, D2Xs', + }, + 0x75 => { #JD + Name => 'VibrationReduction', + Condition => '$$self{ShotInfoVersion} eq "0207"', + Format => 'int8u', + Notes => 'D200', + PrintConv => { + 0 => 'Off', + # (not sure what the different values represent, but values + # of 1 and 2 have even been observed for non-VR lenses!) + 1 => 'On (1)', #PH + 2 => 'On (2)', #PH + 3 => 'On (3)', #PH (rare -- only seen once) + }, + }, + 0x82 => { # educated guess, needs verification + Name => 'VibrationReduction', + Condition => '$$self{ShotInfoVersion} eq "0204"', + Format => 'int8u', + Notes => 'D2X, D2Xs', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + # 0xac - int16u[600] TiffMeteringImage1: 30x20 image (ShotInfoVersion 0800, ref JR) + 0x157 => { #JD + Name => 'ShutterCount', + Condition => '$$self{ShotInfoVersion} eq "0205"', + Format => 'undef[2]', + Priority => 0, + Notes => 'D50', + # treat as a 2-byte big-endian integer + ValueConv => 'unpack("n", $val)', + ValueConvInv => 'pack("n",$val)', + }, + 0x1ae => { #JD + Name => 'VibrationReduction', + Condition => '$$self{ShotInfoVersion} eq "0205"', + Format => 'int8u', + Notes => 'D50', + PrintHex => 1, + PrintConv => { + 0x00 => 'n/a', + 0x0c => 'Off', + 0x0f => 'On', + }, + }, + 0x24d => { #PH + Name => 'ShutterCount', + Condition => '$$self{ShotInfoVersion} eq "0211"', + Notes => 'D60', + Format => 'int32u', + Priority => 0, + }, + # 0x55c - int16u[2400] TiffMeteringImage2: 60x40 image (ShotInfoVersion 0800, ref JR) + # 0x181c - int16u[1200] TiffMeteringImage?: 60x20 image for some NEF's (ShotInfoVersion 0800, ref JR) + # 0x217c - int16u[2400] TiffMeteringImage3: 60x40 image (ShotInfoVersion 0800, ref JR) + # 0x3d9c - int16u[2400] TiffMeteringImage4: 60x40 image (ShotInfoVersion 0800, ref JR) + # 0x59c0 - TiffMeteringImageWidth (ShotInfoVersion 0800, ref JR) + # 0x59c2 - TiffMeteringImageHeight (ShotInfoVersion 0800, ref JR) + # 0x59c4 - int16u[1800] TiffMeteringImage5: 30x20 RGB image (ShotInfoVersion 0800, ref JR) +); + +# shot information for D40 and D40X (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD40 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 729 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in D40 and D40X images.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 582 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 586.1 => { #JD + Name => 'VibrationReduction', + Mask => 0x08, + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 729 => { #JD + Name => 'CustomSettingsD40', + Format => 'undef[12]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD40', + }, + }, +); + +# shot information for D80 (encrypted) - ref JD +%Image::ExifTool::Nikon::ShotInfoD80 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 748 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in D80 images.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 586 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + # split 590 into a few different tags + 590.1 => { + Name => 'Rotation', + Mask => 0x07, + PrintConv => { + 0 => 'Horizontal', + 1 => 'Rotate 270 CW', + 2 => 'Rotate 90 CW', + 3 => 'Rotate 180', + }, + }, + 590.2 => { + Name => 'VibrationReduction', + Mask => 0x18, + PrintConv => { + 0 => 'Off', + 3 => 'On', + }, + }, + 590.3 => { + Name => 'FlashFired', + Mask => 0xe0, + PrintConv => { BITMASK => { + 1 => 'Internal', + 2 => 'External', + }}, + }, + 708 => { + Name => 'NikonImageSize', + Mask => 0xf0, + PrintConv => { + 0 => 'Large (10.0 M)', + 1 => 'Medium (5.6 M)', + 2 => 'Small (2.5 M)', + }, + }, + 708.1 => { + Name => 'ImageQuality', + Mask => 0x0f, + PrintConv => { + 0 => 'NEF (RAW)', + 1 => 'JPEG Fine', + 2 => 'JPEG Normal', + 3 => 'JPEG Basic', + 4 => 'NEF (RAW) + JPEG Fine', + 5 => 'NEF (RAW) + JPEG Normal', + 6 => 'NEF (RAW) + JPEG Basic', + }, + }, + 748 => { #JD + Name => 'CustomSettingsD80', + Format => 'undef[17]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD80', + }, + }, +); + +# shot information for D90 (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD90 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0x374 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D90 with + firmware 1.00. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + 0x2b5 => { #JD (same value found at offset 0x39, 0x2bf, 0x346) + Name => 'ISO2', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 0x2d5 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 0x374 => { + Name => 'CustomSettingsD90', + Format => 'undef[36]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD90', + }, + }, +); + +# shot information for the D3 firmware 0.37 and 1.00 (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD3a = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0x301 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D3 with + firmware 1.00 and earlier. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x256 => { #JD (same value found at offset 0x26b) + Name => 'ISO2', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 0x276 => { #JD + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 723.1 => { + Name => 'NikonImageSize', + Mask => 0x18, + PrintConv => { + 0 => 'Large', + 1 => 'Medium', + 2 => 'Small', + }, + }, + 723.2 => { + Name => 'ImageQuality', + Mask => 0x07, + PrintConv => { + 0 => 'NEF (RAW) + JPEG Fine', + 1 => 'NEF (RAW) + JPEG Norm', + 2 => 'NEF (RAW) + JPEG Basic', + 3 => 'NEF (RAW)', + 4 => 'TIF (RGB)', + 5 => 'JPEG Fine', + 6 => 'JPEG Normal', + 7 => 'JPEG Basic', + }, + }, + 0x301 => { #(NC) + Name => 'CustomSettingsD3', + Format => 'undef[24]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD3', + }, + }, +); + +# shot information for the D3 firmware 1.10, 2.00 and 2.01 (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD3b = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0x30a ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 4 ], + NOTES => q{ + These tags are extracted from encrypted data in images from the D3 with + firmware 1.10, 2.00, 2.01 and 2.02. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + 0x10 => { #28 + Name => 'ImageArea', + PrintConv => { + 0 => 'FX (36.0 x 23.9 mm)', + 1 => 'DX (23.5 x 15.6 mm)', + 2 => '5:4 (30.0 x 23.9 mm)', + }, + }, + 0x25d => { + Name => 'ISO2', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 0x27d => { + Name => 'ShutterCount', + Condition => '$$self{FirmwareVersion} =~ /^1\.01/', + Notes => 'firmware 1.10', + Format => 'int32u', + Priority => 0, + }, + 0x27f => { + Name => 'ShutterCount', + Condition => '$$self{FirmwareVersion} =~ /^2\.0/', + Notes => 'firmware 2.00, 2.01 and 2.02', + Format => 'int32u', + Priority => 0, + }, + 732.1 => { #28 + Name => 'NikonImageSize', + Mask => 0x18, + PrintConv => { + 0 => 'Large', + 1 => 'Medium', + 2 => 'Small', + }, + }, + 732.2 => { #28 + Name => 'ImageQuality', + Mask => 0x07, + PrintConv => { + 0 => 'NEF (RAW) + JPEG Fine', + 1 => 'NEF (RAW) + JPEG Norm', + 2 => 'NEF (RAW) + JPEG Basic', + 3 => 'NEF (RAW)', + 4 => 'TIF (RGB)', + 5 => 'JPEG Fine', + 6 => 'JPEG Normal', + 7 => 'JPEG Basic', + }, + }, + 0x28a => { #28 + Name => 'PreFlashReturnStrength', + Notes => 'valid in TTL and TTL-BL flash control modes', + # this is used to set the flash power using this relationship + # for the SB-800 and SB-900: + # $val < 140 ? 2**(0.08372*$val-12.352) : $val + }, + 0x30a => { # tested with firmware 2.00 + Name => 'CustomSettingsD3', + Format => 'undef[24]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD3', + }, + }, +); + +# shot information for the D3X firmware 1.00 (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD3X = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0x30b ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D3X with + firmware 1.00. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + 0x25d => { + Name => 'ISO2', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 0x280 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 0x30b => { #(NC) + Name => 'CustomSettingsD3X', + Format => 'undef[24]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD3', + }, + }, +); + +# shot information for the D3S firmware 0.16 and 1.00 (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD3S = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0x2ce ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D3S with + firmware 1.00 and earlier. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + 0x10 => { #28 + Name => 'ImageArea', + PrintConv => { + 0 => 'FX (36x24)', + 1 => 'DX (24x16)', + 2 => '5:4 (30x24)', + 3 => '1.2x (30x20)', + }, + }, + 0x221 => { + Name => 'ISO2', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 0x242 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 0x2ce => { #(NC) + Name => 'CustomSettingsD3S', + Format => 'undef[27]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD3', + }, + }, +); + +# shot information for the D300 firmware 1.00 (encrypted) - ref JD +%Image::ExifTool::Nikon::ShotInfoD300a = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 790 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D300 with + firmware 1.00 and earlier. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 604 => { + Name => 'ISO2', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 633 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 721 => { #PH + Name => 'AFFineTuneAdj', + Format => 'int16u', + PrintHex => 1, + PrintConvColumns => 3, + # thanks to Neil Nappe for the samples to decode this!... + # (have seen various unknown values here when flash is enabled, but + # these are yet to be decoded: 0x2e,0x619,0xd0d,0x103a,0x2029 - PH) + PrintConv => { + 0x403e => '+20', + 0x303e => '+19', + 0x203e => '+18', + 0x103e => '+17', + 0x003e => '+16', + 0xe03d => '+15', + 0xc03d => '+14', + 0xa03d => '+13', + 0x803d => '+12', + 0x603d => '+11', + 0x403d => '+10', + 0x203d => '+9', + 0x003d => '+8', + 0xc03c => '+7', + 0x803c => '+6', + 0x403c => '+5', + 0x003c => '+4', + 0x803b => '+3', + 0x003b => '+2', + 0x003a => '+1', + 0x0000 => '0', + 0x00c6 => '-1', + 0x00c5 => '-2', + 0x80c5 => '-3', + 0x00c4 => '-4', + 0x40c4 => '-5', + 0x80c4 => '-6', + 0xc0c4 => '-7', + 0x00c3 => '-8', + 0x20c3 => '-9', + 0x40c3 => '-10', + 0x60c3 => '-11', + 0x80c3 => '-12', + 0xa0c3 => '-13', + 0xc0c3 => '-14', + 0xe0c3 => '-15', + 0x00c2 => '-16', + 0x10c2 => '-17', + 0x20c2 => '-18', + 0x30c2 => '-19', + 0x40c2 => '-20', + }, + }, + 790 => { + Name => 'CustomSettingsD300', + Format => 'undef[24]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD3', + }, + }, +); + +# shot information for the D300 firmware 1.10 (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD300b = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + DATAMEMBER => [ 4 ], + IS_SUBDIR => [ 802 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D300 with + firmware 1.10. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { #PH + Name => 'FirmwareVersion', + DataMember => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + 613 => { + Name => 'ISO2', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 644 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 732 => [{ + Name => 'AFFineTuneAdj', + Condition => '$$self{FirmwareVersion} eq "1.10B"', + Notes => 'firmware version 1.10B', + Format => 'int16u', + PrintHex => 1, + PrintConvColumns => 3, + # thanks to Michael Tapes for the samples to decode this!... + PrintConv => { + 0xe03e => '+20', + 0xc83e => '+19', + 0xb03e => '+18', + 0x983e => '+17', + 0x803e => '+16', + 0x683e => '+15', + 0x503e => '+14', + 0x383e => '+13', + 0x203e => '+12', + 0x083e => '+11', + 0xe03d => '+10', + 0xb03d => '+9', + 0x803d => '+8', + 0x503d => '+7', + 0x203d => '+6', + 0xe03c => '+5', + 0x803c => '+4', + 0x203c => '+3', + 0x803b => '+2', + 0x803a => '+1', + 0x0000 => '0', + 0x80c6 => '-1', + 0x80c5 => '-2', + 0x20c4 => '-3', + 0x80c4 => '-4', + 0xe0c4 => '-5', + 0x20c3 => '-6', + 0x50c3 => '-7', + 0x80c3 => '-8', + 0xb0c3 => '-9', + 0xe0c3 => '-10', + 0x08c2 => '-11', + 0x20c2 => '-12', + 0x38c2 => '-13', + 0x50c2 => '-14', + 0x68c2 => '-15', + 0x80c2 => '-16', + 0x98c2 => '-17', + 0xb0c2 => '-18', + 0xc8c2 => '-19', + 0xe0c2 => '-20', + }, + },{ + Name => 'AFFineTuneAdj', + Notes => 'other versions', + Format => 'int16u', + PrintHex => 1, + PrintConvColumns => 3, + # thanks to Stuart Solomon for the samples to decode this!... + PrintConv => { + 0x903e => '+20', + 0x7c3e => '+19', + 0x683e => '+18', + 0x543e => '+17', + 0x403e => '+16', + 0x2c3e => '+15', + 0x183e => '+14', + 0x043e => '+13', + 0xe03d => '+12', + 0xb83d => '+11', + 0x903d => '+10', + 0x683d => '+9', + 0x403d => '+8', + 0x183d => '+7', + 0xe03c => '+6', + 0x903c => '+5', + 0x403c => '+4', + 0xe03b => '+3', + 0x403b => '+2', + 0x403a => '+1', + 0x0000 => '0', + 0x40c6 => '-1', + 0x40c5 => '-2', + 0xe0c5 => '-3', + 0x40c4 => '-4', + 0x90c4 => '-5', + 0xe0c4 => '-6', + 0x18c3 => '-7', + 0x40c3 => '-8', + 0x68c3 => '-9', + 0x90c3 => '-10', + 0xb8c3 => '-11', + 0xe0c3 => '-12', + 0x04c2 => '-13', + 0x18c2 => '-14', + 0x2cc2 => '-15', + 0x40c2 => '-16', + 0x54c2 => '-17', + 0x68c2 => '-18', + 0x7cc2 => '-19', + 0x90c2 => '-20', + }, + }], + 802 => { + Name => 'CustomSettingsD300', + Format => 'undef[24]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD3', + }, + }, +); + +# shot information for the D300S firmware 1.00 (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD300S = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 804 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D300S with + firmware 1.00. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + 613 => { + Name => 'ISO2', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 646 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 804 => { #(NC) + Name => 'CustomSettingsD300S', + Format => 'undef[24]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD3', + }, + }, +); + +# shot information for the D700 firmware 1.02f (encrypted) - ref 29 +%Image::ExifTool::Nikon::ShotInfoD700 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 804 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D700 with + firmware 1.02f. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + 613 => { # 0x265 + Name => 'ISO2', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 0x287 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 804 => { # 0x324 (NC) + Name => 'CustomSettingsD700', + Format => 'undef[48]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD700', + }, + }, +); + +# shot information for the D780 - ref #28 +%Image::ExifTool::Nikon::ShotInfoD780 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index', NIKON_OFFSETS => 0x24 }, + DATAMEMBER => [ 0x04 ], + IS_SUBDIR => [ 0x9c ], + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the D780.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + DataMember => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + 0x9c => { + Name => 'OrientOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::OrientationInfo', + Start => '$val', + }, + }, +); + +# shot information for the D5000 firmware 1.00 (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD5000 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0x378 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D5000 with + firmware 1.00. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + 0x2b5 => { # (also found at 0x2c0) + Name => 'ISO2', + ValueConv => '100*exp(($val/12-5)*log(2))', + ValueConvInv => '(log($val/100)/log(2)+5)*12', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 0x2d6 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 0x378 => { + Name => 'CustomSettingsD5000', + Format => 'undef[34]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD5000', + }, + }, +); + +# shot information for the D5100 firmware 1.00f (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD5100 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0x407 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + 0x321 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 0x407 => { + Name => 'CustomSettingsD5100', + Format => 'undef[34]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD5100', + }, + }, +); + +# shot information for the D5200 firmware 1.00 (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD5200 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0xcd5 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + # 0x101 - 2=VR Off, 3=VR On + # 0x13d - 0=VR On, 1=VR Off + 0xbd8 => { + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + # 0xcd2 - 12=VR Off, 15=VR On + 0xcd5 => { + Name => 'CustomSettingsD5200', + Format => 'undef[34]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD5200', + }, + }, +); + +# shot information for the D7000 firmware 1.01d (encrypted) - ref 29 +%Image::ExifTool::Nikon::ShotInfoD7000 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 1028 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D7000 with + firmware 1.01b. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + #613 => { + # Name => 'ISO2', + # ValueConv => '100*exp(($val/12-5)*log(2))', + # ValueConvInv => '(log($val/100)/log(2)+5)*12', + # PrintConv => 'int($val + 0.5)', + # PrintConvInv => '$val', + #}, + 0x320 => { # 800 + Name => 'ShutterCount', + Format => 'int32u', + Priority => 0, + }, + 0x404 => { # 1028 (NC) + Name => 'CustomSettingsD7000', + Format => 'undef[48]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD7000', + }, + }, +); + +# shot information for the D7500 - ref #28 +%Image::ExifTool::Nikon::ShotInfoD7500 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index', NIKON_OFFSETS => 0x0c }, + DATAMEMBER => [ 0x04 ], + IS_SUBDIR => [ 0xa0 ], + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the D7500.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + DataMember => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + 0xa0 => { + Name => 'OrientOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::OrientationInfo', + Start => '$val', + }, + }, +); + +# shot information for the D800 firmware 1.01a (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD800 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0x6ec ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the D800.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + 0x4c0 => { + Name => 'RepeatingFlashOutputExternal', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val > 0 ? -6*log($val)/log(2) : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x4c2 => { + Name => 'RepeatingFlashRateExternal', + DelValue => 0, + RawConv => '$val || undef', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 0x4c3 => { + Name => 'RepeatingFlashCountExternal', + DelValue => 0, + RawConv => '$val || undef', + }, + 0x4d2 => { + Name => 'FlashExposureComp2', + Notes => 'includes the effect of flash bracketing', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + # 0x4d4 - FEC again, doesn't include bracketing this time (internal?) +# (not fully decoded, and duplicated in custom settings) +# 0x4d9 => { +# Name => 'FlashControlBuilt-in', +# PrintConv => { +# 1 => 'TTL', +# 6 => 'Manual', +# 7 => 'Repeating Flash', +# }, +# }, + 0x4da => { + Name => 'RepeatingFlashRateBuilt-in', + DelValue => 0, + RawConv => '$val || undef', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 0x4db => { + Name => 'RepeatingFlashCountBuilt-in', + DelValue => 0, + RawConv => '$val || undef', + }, +# 1294.1 => { # (0x4dc) +# Name => 'FlashModeBuilt-in', +# Mask => 0x0f, +# PrintConv => { +# 0 => 'Front-curtain Sync', +# 1 => 'Red-eye Reduction', +# 2 => 'Redy-eye Reduction with Slow Sync', +# 3 => 'Slow Sync', +# 4 => 'Rear-curtain Sync', +# 5 => 'Rear-curtain Sync 2', # got this in P exposure mode +# }, +# }, +# 1294.2 => { # (0x4dc) +# Name => 'ExposureMode2', +# Mask => 0xf0, +# PrintConv => { +# 0 => 'Program', +# 1 => 'Aperture Priority', +# 3 => 'Manual', +# }, +# }, + # 0x511 - related to FlashSyncSpeed + 0x51c => 'SequenceNumber', + # 0x4ba+0x63 - interesting + # 0x4ba+0x68 - general downward trend + # 0x4ba+0x7b - FlashControlBuilt-in: 8=TTL, 72=Manual +# (not reliable) +# 1346.1 => { # (0x542) +# Name => 'RepeatingFlashOutputBuilt-in', +# DelValue => 112, +# Mask => 0xfc, +# RawConv => '$val == 0x1c ? undef : 2 ** ($val/3-7)', +# ValueConvInv => '$val > 0 ? (log($val)/log(2)+7)*3 : 0', +# PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', +# PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', +# }, + 0x5fb => { + Name => 'ShutterCount', + Format => 'int32u', + }, + 0x6ec => { + Name => 'CustomSettingsD800', + Format => 'undef[48]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD800', + }, + }, +); + +# shot information for the D5 firmware 1.10a and D500 firmware 1.01 (encrypted) - ref 28 +%Image::ExifTool::Nikon::ShotInfoD500 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index', NIKON_OFFSETS => 0x0c }, + DATAMEMBER => [ 0x04 ], + IS_SUBDIR => [ 0x10, 0x14, 0x2c, 0x50, 0x58, 0xa0, 0xa8 ], + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the D5 and D500.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + DataMember => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + 0x10 => { + Name => 'RotationInfoOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::RotationInfoD500', + Start => '$val', + } + }, + 0x14 => { + Name => 'JPGInfoOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::JPGInfoD500', + Start => '$val', + } + }, + 0x2c => { + Name => 'BracketingOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::BracketingInfoD500', + Start => '$val', + } + }, + 0x50 => { + Name => 'ShootingMenuOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ShootingMenuD500', + Start => '$val', + } + }, + 0x58 => { + Name => 'CustomSettingsOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::CustomSettingsD500', + Start => '$val', + } + }, + 0xa0 => { + Name => 'OrientationOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::OrientationInfo', + Start => '$val', + } + }, + 0xa8 => { + Name => 'OtherOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::OtherInfoD500', + Start => '$val', + } + }, +); + +%Image::ExifTool::Nikon::RotationInfoD500 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x1a => { + Name => 'Rotation', + Mask => 0x03, + PrintConv => { + 0 => 'Horizontal', + 1 => 'Rotate 270 CW', + 2 => 'Rotate 90 CW', + 3 => 'Rotate 180', + }, + }, + 0x20 => { + Name => 'Interval', + # prior version of the d% firmware do not support this tag, nor does the D500 (at least thru firmware 1.3) + Condition => '$$self{Model} eq "NIKON D5" and $$self{FirmwareVersion} ge "1.40"', + PrintConv => '$val > 0 ? sprintf("%.0f", $val) : ""', + }, + 0x24 => { + Name => 'IntervalFrame', + # prior version of the d% firmware do not support this tag, nor does the D500 (at least thru firmware 1.3) + Condition => '$$self{Model} eq "NIKON D5" and $$self{FirmwareVersion} ge "1.40"', + PrintConv => '$val > 0 ? sprintf("%.0f", $val) : ""', + }, + 0x0532 => { + Name => 'FlickerReductionIndicator', + Mask => 0x01, + PrintConv => { 0 => 'On', 1 => 'Off' }, + }, +); + +%Image::ExifTool::Nikon::JPGInfoD500 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x24 => { + Name => 'JPGCompression', + Mask => 0x01, + PrintConv => { + 0 => 'Size Priority', + 1 => 'Optimal Quality', + }, + }, +); + +%Image::ExifTool::Nikon::BracketingInfoD500 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0f => { + Name => 'AEBracketingSteps', + Condition => '$$self{FILE_TYPE} ne "TIFF"', # (covers NEF and TIFF) + Mask => 0xff, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 0x00 => 'AE Bracketing Disabled', + 0x20 => 'AE Bracketing Disabled', + 0x30 => 'AE Bracketing Disabled', + 0x40 => 'AE Bracketing Disabled', + 0x50 => 'AE Bracketing Disabled', + 0x81 => '+3F0.3', + 0x82 => '-3F0.3', + 0x83 => '+2F0.3', + 0x84 => '-2F0.3', + 0x85 => '3F0.3', + 0x86 => '5F0.3', + 0x87 => '7F0.3', + 0x88 => '9F0.3', + 0x91 => '+3F0.5', + 0x92 => '-3F0.5', + 0x93 => '+2F0.5', + 0x94 => '-2F0.5', + 0x95 => '3F0.5', + 0x96 => '5F0.5', + 0x97 => '7F0.5', + 0x98 => '9F0.5', + 0xa1 => '+3F0.7', + 0xa2 => '-3F0.7', + 0xa3 => '+2F0.7', + 0xa4 => '-2F0.7', + 0xa5 => '3F0.7', + 0xa6 => '5F0.7', + 0xa7 => '7F0.7', + 0xa8 => '9F0.7', + 0xb1 => '+3F1', + 0xb2 => '-3F1', + 0xb3 => '+2F1', + 0xb4 => '-2F1', + 0xb5 => '3F1', + 0xb6 => '5F1', + 0xb7 => '7F1', + 0xb8 => '9F1', + 0xc1 => '+3F2', + 0xc2 => '-3F2', + 0xc3 => '+2F2', + 0xc4 => '-2F2', + 0xc5 => '3F2', + 0xc6 => '5F2', + 0xd1 => '+3F3', + 0xd2 => '-3F3', + 0xd3 => '+2F3', + 0xd4 => '-2F3', + 0xd5 => '3F3', + 0xd6 => '5F3', + }, + }, + 0x10 => { + Name => 'WBBracketingSteps', + Condition => '$$self{FILE_TYPE} ne "TIFF"', # (covers NEF and TIFF) + Mask => 0xff, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 0x00 => 'WB Bracketing Disabled', + 0x01 => 'b3F 1', + 0x02 => 'A3F 1', + 0x03 => 'b2F 1', + 0x04 => 'A2F 1', + 0x05 => '3F 1', + 0x06 => '5F 1', + 0x07 => '7F 1', + 0x08 => '9F 1', + 0x10 => '0F 2', + 0x11 => 'b3F 2', + 0x12 => 'A3F 2', + 0x13 => 'b2F 2', + 0x14 => 'A2F 2', + 0x15 => '3F 2', + 0x16 => '5F 2', + 0x17 => '7F 2', + 0x18 => '9F 2', + 0x20 => '0F 3', + 0x21 => 'b3F 3', + 0x22 => 'A3F 3', + 0x23 => 'b2F 3', + 0x24 => 'A2F 3', + 0x25 => '3F 3', + 0x26 => '5F 3', + 0x27 => '7F 3', + 0x28 => '9F 3', + 0x22 => 'A3F 3', + 0x23 => 'b2F 3', + 0x24 => 'A2F 3', + 0x25 => '3F 3', + 0x26 => '5F 3', + 0x27 => '7F 3', + 0x28 => '9F 3', + }, + }, + 0x17 => { + Name => 'ADLBracketingStep', + Mask => 0xf0, + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Normal', + 3 => 'High', + 4 => 'Extra High', + 8 => 'Auto', + }, + }, + 0x18 => { + Name => 'ADLBracketingType', + Mask => 0x0f, + PrintConv => { + 0 => 'Off', + 1 => '2 Shots', + 2 => '3 Shots', + 3 => '4 Shots', + 4 => '5 Shots', + }, + }, +); + +%Image::ExifTool::Nikon::ShootingMenuD500 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x00 => { + Name => 'PhotoShootingMenuBank', + Mask => 0x03, + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 0x02 => { + Name => 'PrimarySlot', + Condition => '$$self{Model} =~ /\bD500\b/', + Notes => 'D500 only', + Mask => 0x80, + PrintConv => { + 0 => 'XQD Card', + 1 => 'SD Card', + }, + }, + 0x04 => { + Name => 'ISOAutoShutterTime', + Mask => 0x3f, + PrintConv => { + 0 => '1/4000 s', + 1 => '1/3200 s', + 2 => '1/2500 s', + 3 => '1/2000 s', + 4 => '1/1600 s', + 5 => '1/1250 s', + 6 => '1/1000 s', + 7 => '1/800 s', + 8 => '1/640 s', + 9 => '1/500 s', + 10 => '1/400 s', + 11 => '1/320 s', + 12 => '1/250 s', + 13 => '1/200 s', + 14 => '1/160 s', + 15 => '1/125 s', + 16 => '1/100 s', + 17 => '1/80 s', + 18 => '1/60 s', + 19 => '1/50 s', + 20 => '1/40 s', + 21 => '1/30 s', + 22 => '1/15 s', + 23 => '1/8 s', + 24 => '1/4 s', + 25 => '1/2 s', + 26 => '1 s', + 27 => '2 s', + 28 => '4 s', + 29 => '8 s', + 30 => '15 s', + 31 => '30 s', + 32 => 'Auto (Slowest)', + 33 => 'Auto (Slower)', + 34 => 'Auto', + 35 => 'Auto (Faster)', + 36 => 'Auto (Fastest)', + }, + }, + 0x05 => { + Name => 'ISOAutoHiLimit', + Mask => 0xff, + PrintHex => 1, + PrintConv => { + 0x24 => 'ISO 200', + 0x26 => 'ISO 250', + 0x27 => 'ISO 280', + 0x28 => 'ISO 320', + 0x2a => 'ISO 400', + 0x2c => 'ISO 500', + 0x2d => 'ISO 560', + 0x2e => 'ISO 640', + 0x30 => 'ISO 800', + 0x32 => 'ISO 1000', + 0x33 => 'ISO 1100', + 0x34 => 'ISO 1250', + 0x36 => 'ISO 1600', + 0x38 => 'ISO 2000', + 0x39 => 'ISO 2200', + 0x3a => 'ISO 2500', + 0x3c => 'ISO 3200', + 0x3e => 'ISO 4000', + 0x3f => 'ISO 4500', + 0x40 => 'ISO 5000', + 0x42 => 'ISO 6400', + 0x44 => 'ISO 8000', + 0x45 => 'ISO 9000', + 0x46 => 'ISO 10000', + 0x48 => 'ISO 12800', + 0x4a => 'ISO 16000', + 0x4b => 'ISO 18000', + 0x4c => 'ISO 20000', + 0x4e => 'ISO 25600', + 0x50 => 'ISO 32000', + 0x51 => 'ISO 36000', + 0x52 => 'ISO 40000', + 0x54 => 'ISO 51200', + 0x56 => 'ISO Hi 0.3', + 0x57 => 'ISO Hi 0.5', + 0x58 => 'ISO Hi 0.7', + 0x5a => 'ISO Hi 1.0', + 0x60 => 'ISO Hi 2.0', + 0x66 => 'ISO Hi 3.0', + 0x6c => 'ISO Hi 4.0', + 0x72 => 'ISO Hi 5.0', + }, + }, + 0x07 => { + Name => 'FlickerReduction', + Mask => 0x20, + PrintConv => { + 0 => 'Enable', + 1 => 'Disable', + }, + }, + 7.1 => { + Name => 'PhotoShootingMenuBankImageArea', + Mask => 0x07, + PrintConv => { + 0 => 'FX (36x24)', + 1 => 'DX (24x16)', + 2 => '5:4 (30x24)', + 3 => '1.2x (30x20)', + 4 => '1.3x (18x12)', + }, + }, +); + +%Image::ExifTool::Nikon::CustomSettingsD500 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 0x00 ], + VARS => { ALLOW_REPROCESS => 1 }, # (necessary because subdirectory is at offset 0) + 0x00 => [{ + Name => 'CustomSettingsD5', + Condition => '$$self{Model} =~ /\bD5\b/', + Format => 'undef[90]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD5', + }, + },{ + Name => 'CustomSettingsD500', + Format => 'undef[90]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD500', + }, + }], +# 0x7d => { #this decode works, but involves more bits than should be necessary +# Name => 'ShutterTrigger', +# Mask => 0xff, +# PrintConv => { +# 0 => 'Timer', +# 15 => 'Cable Release/Remote', +# 195 => 'Shutter Button', +# }, +# }, +); + +%Image::ExifTool::Nikon::OtherInfoD500 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # (needs testing) + #0x22 => { + # Name => 'ExtendedPhotoShootingBanks', + # Mask => 0x01, + # PrintConv => { + # 0 => 'On', + # 1 => 'Off', + # }, + #}, + # (may not be reliable and is found elsewhere) + #0x212 => { + # Name => 'Rotation', + # Condition => '$$self{Model} =~ /\bD500\b/', + # Notes => 'D500 firmware 1.1x', + # Mask => 0x30, + # PrintConv => { + # 0 => 'Horizontal', + # 1 => 'Rotate 270 CW', + # 2 => 'Rotate 90 CW', + # 3 => 'Rotate 180', + # }, + #}, + 0x214 => { #PH + Name => 'NikonMeteringMode', + Condition => '$$self{Model} =~ /\bD500\b/', # (didn't seem to work for D5, but I need more samples) + Notes => 'D500 only', + Mask => 0x03, + PrintConv => { + 0 => 'Matrix', + 1 => 'Center', + 2 => 'Spot', + 3 => 'Highlight' + }, + }, +); + +# shot information for the D6 firmware 1.00 (encrypted) - ref 28 +%Image::ExifTool::Nikon::ShotInfoD6 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index', NIKON_OFFSETS => 0x24 }, + IS_SUBDIR => [ 0x30, 0x9c, 0xa4 ], + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the D6.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[8]', + Writable => 0, + }, + 0x24 => { + Name => 'NumberOffsets', # (number of entries in offset table. offsets are from start of ShotInfo data) + Format => 'int32u', + Writable => 0, + Hidden => 1, + }, + 0x30 => { + Name => 'SequenceOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::SeqInfoD6', + Start => '$val', + }, + }, + 0x9c => { + Name => 'OrientationOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::OrientationInfo', + Start => '$val', + }, + }, + 0xa4 => { + Name => 'IntervalOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::IntervalInfoD6', + Start => '$val', + } + }, +); + +%Image::ExifTool::Nikon::SeqInfoD6 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0x24, 0x28 ], + 0x24 => { + Name => 'IntervalShooting', + RawConv => '$$self{IntervalShooting} = $val', + Format => 'int16u', + PrintConv => q{ + return 'Off' if $val == 0 ; + my $i = sprintf("Interval %.0f of %.0f",$val, $$self{IntervalShootingIntervals}||0); #something like "Interval 1 of 3" + my $f = ($$self{IntervalShootingShotsPerInterval}||0) > 1 ? sprintf(" Frame %.0f of %.0f",$$self{IntervalFrame}||0, $$self{IntervalShootingShotsPerInterval}||0): '' ; #something like "Frame 1 of 3" or blank + return "On: $i$f" + #$val == 0 ? 'Off' : sprintf("On: Interval %.0f of %.0f Frame %.0f of %.0f",$val, $$self{IntervalShootingIntervals}||0, $$self{IntervalFrame}||0, $$self{IntervalShootingShotsPerInterval}||0), + }, + }, + 0x28 => { + Name => 'IntervalFrame', + RawConv => '$$self{IntervalFrame} = $val', + Condition => '$$self{IntervalShooting} > 0', + Format => 'int16u', + Hidden => 1, + }, +); + +%Image::ExifTool::Nikon::IntervalInfoD6 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0x17c, 0x180, 0x214, 0x22c ], + 0x17c => { + Name => 'Intervals', + Format => 'int32u', + RawConv => '$$self{IntervalShootingIntervals} = $val', + Condition => '$$self{IntervalShooting} > 0', + }, + 0x180 => { + Name => 'ShotsPerInterval', + Format => 'int32u', + RawConv => '$$self{IntervalShootingShotsPerInterval} = $val', + Condition => '$$self{IntervalShooting} > 0', + }, + 0x184 => { + Name => 'IntervalExposureSmoothing', + Condition => '$$self{IntervalShooting} > 0', + Format => 'int8u', + PrintConv => \%offOn, + }, + 0x186 => { + Name => 'IntervalPriority', + Condition => '$$self{IntervalShooting} > 0', + Format => 'int8u', + PrintConv => \%offOn, + }, + 0x1a8 => { + Name => 'FocusShiftNumberShots', + }, + 0x1ac => { + Name => 'FocusShiftStepWidth', + }, + 0x1b0 => { + Name => 'FocusShiftInterval', + PrintConv => '$val == 1? "1 Second" : sprintf("%.0f Seconds",$val)', + }, + 0x1b4 => { + Name => 'FocusShiftExposureLock', + PrintConv => \%offOn, + }, + #0x20a => HighISONoiseReduction + 0x20e => { + Name => 'DiffractionCompensation', + Format => 'int8u', + PrintConv => \%offOn, + }, + #0x20f => {Name => 'FlickerReductionShooting',}, #redundant with tag in NikonSettings + 0x214 => { + Name => 'FlashControlMode', #this and nearby tag values for flash may be set from either the Photo Shooting Menu or using the Flash unit menu + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => { + 0 => 'TTL', + 1 => 'Auto External Flash', + 2 => 'GN (distance priority)', + 3 => 'Manual', + 4 => 'Repeating Flash', + }, + }, + 0x21a => { + Name => 'FlashGNDistance', + Condition => '$$self{FlashControlMode} == 2', + Unknown => 1, + ValueConv => '$val + 3', + PrintConv => \%flashGNDistance, + }, + 0x21e => { + Name => 'FlashOutput', #range[0,24] with 0=>Full; 1=>50%; then decreasing flash power in 1/3 stops to 0.39% (1/256 full power). #also found in FlashInfoUnknown at offset 0x0a (with different mappings) + Condition => '$$self{FlashControlMode} >= 3', + Unknown => 1, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val>0 ? -3*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.1f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + 0x228 => { + Name => 'FlashRemoteControl', + Unknown => 1, + PrintConv => { + 0 => 'Group', + 1 => 'Quick Wireless', + 2 => 'Remote Repeating', + }, + }, + 0x22c => { + Name => 'FlashMasterControlMode', #tag name chosen for compatibility with those found in FlashInfo0102 & FlashInfo0103 + RawConv => '$$self{FlashGroupOptionsMasterMode} = $val', + PrintConv => \%flashGroupOptionsMode, + }, + 0x22e => { + Name => 'FlashMasterCompensation', + Unknown => 1, + Format => 'int8s', + Condition => '$$self{FlashGroupOptionsMasterMode} != 3', #other than 'Off' + ValueConv => '$val/6', + ValueConvInv => '6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 0x232 => { + Name => 'FlashMasterOutput', + Unknown => 1, + Condition => '$$self{FlashGroupOptionsMasterMode} == 1', #only for Mode=M + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val>0 ? -3*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.1f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + 0x234 => { + Name => 'FlashWirelessOption', + Unknown => 1, + PrintConv => { + 0 => 'Optical AWL', + 1 => 'Off', + }, + }, + 0x2ca => { + Name => 'MovieType', + Unknown => 1, + PrintConv => { + 0 => 'MOV', + 1 => 'MP4', + }, + }, +); + +# shot information for the D610 firmware 1.00 (encrypted) - ref PH +%Image::ExifTool::Nikon::ShotInfoD610 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0x07cf ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the D610.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + 0x07cf => { + Name => 'CustomSettingsD610', + Format => 'undef[48]', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD610', + }, + }, +); + +# shot information for the D810 firmware 1.00(PH)/1.01 (encrypted) - ref 28 +%Image::ExifTool::Nikon::ShotInfoD810 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index', NIKON_OFFSETS => 0x0c }, + DATAMEMBER => [ 0x04 ], + IS_SUBDIR => [ 0x10, 0x24, 0x38, 0x40, 0x84 ], + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the D810.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + DataMember => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + # 0x0c - number of entries in offset table (= 0x21) + # 0x10 - int32u[val 0x0c]: offset table + 0x10 => { + Name => 'SettingsOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::SettingsInfoD810', + Start => '$val', + }, + }, + 0x24 => { + Name => 'BracketingOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::BracketingInfoD810', + Start => '$val', + }, + }, + 0x38 => { + Name => 'ISOAutoOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ISOAutoInfoD810', + Start => '$val', + }, + }, + 0x40 => { + Name => 'CustomSettingsOffset', # (relative offset from start of ShotInfo data) + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD810', + Start => '$val', + }, + }, + 0x84 => { + Name => 'OrientationOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::OrientationInfo', + Start => '$val', + } + }, + # (moves around too much and doesn't fit cleanly in the offset table) + #0x38be => { + # Name => 'Rotation', + # Condition => '$$self{FirmwareVersion} =~ /^1\.0/', + # Mask => 0x30, + # PrintConv => { + # 0 => 'Horizontal', + # 1 => 'Rotate 270 CW', + # 2 => 'Rotate 90 CW', + # 3 => 'Rotate 180', + # }, + #}, +); + +%Image::ExifTool::Nikon::SettingsInfoD810 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x13c => { + Name => 'SecondarySlotFunction', + Mask => 0x03, + PrintConv => { + 0 => 'Overflow', + 2 => 'Backup', + 3 => 'NEF Primary + JPG Secondary', + }, + }, +); + +%Image::ExifTool::Nikon::BracketingInfoD810 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0f => { + Name => 'AEBracketingSteps', + Mask => 0xff, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 0x00 => 'AE Bracketing Disabled', + 0x20 => 'AE Bracketing Disabled', + 0x30 => 'AE Bracketing Disabled', + 0x40 => 'AE Bracketing Disabled', + 0x50 => 'AE Bracketing Disabled', + 0x81 => '+3F0.3', + 0x82 => '-3F0.3', + 0x83 => '+2F0.3', + 0x84 => '-2F0.3', + 0x85 => '3F0.3', + 0x86 => '5F0.3', + 0x87 => '7F0.3', + 0x88 => '9F0.3', + 0x91 => '+3F0.5', + 0x92 => '-3F0.5', + 0x93 => '+2F0.5', + 0x94 => '-2F0.5', + 0x95 => '3F0.5', + 0x96 => '5F0.5', + 0x97 => '7F0.5', + 0x98 => '9F0.5', + 0xa1 => '+3F0.7', + 0xa2 => '-3F0.7', + 0xa3 => '+2F0.7', + 0xa4 => '-2F0.7', + 0xa5 => '3F0.7', + 0xa6 => '5F0.7', + 0xa7 => '7F0.7', + 0xa8 => '9F0.7', + 0xb1 => '+3F1', + 0xb2 => '-3F1', + 0xb3 => '+2F1', + 0xb4 => '-2F1', + 0xb5 => '3F1', + 0xb6 => '5F1', + 0xb7 => '7F1', + 0xb8 => '9F1', + 0xc1 => '+3F2', + 0xc2 => '-3F2', + 0xc3 => '+2F2', + 0xc4 => '-2F2', + 0xc5 => '3F2', + 0xc6 => '5F2', + 0xd1 => '+3F3', + 0xd2 => '-3F3', + 0xd3 => '+2F3', + 0xd4 => '-2F3', + 0xd5 => '3F3', + 0xd6 => '5F3', + }, + }, + 0x10 => { + Name => 'WBBracketingSteps', + Condition => '$$self{FILE_TYPE} ne "TIFF"', # (covers NEF and TIFF) + Mask => 0xff, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 0x00 => 'WB Bracketing Disabled', + 0x01 => 'b3F 1', + 0x02 => 'A3F 1', + 0x03 => 'b2F 1', + 0x04 => 'A2F 1', + 0x05 => '3F 1', + 0x06 => '5F 1', + 0x07 => '7F 1', + 0x08 => '9F 1', + 0x10 => '0F 2', + 0x11 => 'b3F 2', + 0x12 => 'A3F 2', + 0x13 => 'b2F 2', + 0x14 => 'A2F 2', + 0x15 => '3F 2', + 0x16 => '5F 2', + 0x17 => '7F 2', + 0x18 => '9F 2', + 0x20 => '0F 3', + 0x21 => 'b3F 3', + 0x22 => 'A3F 3', + 0x23 => 'b2F 3', + 0x24 => 'A2F 3', + 0x25 => '3F 3', + 0x26 => '5F 3', + 0x27 => '7F 3', + 0x28 => '9F 3', + 0x22 => 'A3F 3', + 0x23 => 'b2F 3', + 0x24 => 'A2F 3', + 0x25 => '3F 3', + 0x26 => '5F 3', + 0x27 => '7F 3', + 0x28 => '9F 3', + }, + }, + 0x17 => { + Name => 'NikonMeteringMode', + Mask => 0x03, + PrintConv => { + 0 => 'Matrix', + 1 => 'Center', + 2 => 'Spot', + 3 => 'Highlight' + }, + }, +); + +%Image::ExifTool::Nikon::ISOAutoInfoD810 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x04 => { + Name => 'ISOAutoShutterTime', + Mask => 0x3f, + PrintConv => { + 0 => '1/4000 s', + 1 => '1/3200 s', + 2 => '1/2500 s', + 3 => '1/2000 s', + 4 => '1/1600 s', + 5 => '1/1250 s', + 6 => '1/1000 s', + 7 => '1/800 s', + 8 => '1/640 s', + 9 => '1/500 s', + 10 => '1/400 s', + 11 => '1/320 s', + 12 => '1/250 s', + 13 => '1/200 s', + 14 => '1/160 s', + 15 => '1/125 s', + 16 => '1/100 s', + 17 => '1/80 s', + 18 => '1/60 s', + 19 => '1/50 s', + 20 => '1/40 s', + 21 => '1/30 s', + 22 => '1/15 s', + 23 => '1/8 s', + 24 => '1/4 s', + 25 => '1/2 s', + 26 => '1 s', + 27 => '2 s', + 28 => '4 s', + 29 => '8 s', + 30 => '15 s', + 31 => '30 s', + 32 => 'Auto (Slowest)', + 33 => 'Auto (Slower)', + 34 => 'Auto', + 35 => 'Auto (Faster)', + 36 => 'Auto (Fastest)', + }, + }, + 0x05 => { + Name => 'ISOAutoHiLimit', + Mask => 0xff, + PrintHex => 1, + PrintConv => { + 0x24 => 'ISO 200', + 0x26 => 'ISO 250', + 0x27 => 'ISO 280', + 0x28 => 'ISO 320', + 0x2a => 'ISO 400', + 0x2c => 'ISO 500', + 0x2d => 'ISO 560', + 0x2e => 'ISO 640', + 0x30 => 'ISO 800', + 0x32 => 'ISO 1000', + 0x33 => 'ISO 1100', + 0x34 => 'ISO 1250', + 0x36 => 'ISO 1600', + 0x38 => 'ISO 2000', + 0x39 => 'ISO 2200', + 0x3a => 'ISO 2500', + 0x3c => 'ISO 3200', + 0x3e => 'ISO 4000', + 0x3f => 'ISO 4500', + 0x40 => 'ISO 5000', + 0x42 => 'ISO 6400', + 0x44 => 'ISO 8000', + 0x45 => 'ISO 9000', + 0x46 => 'ISO 10000', + 0x48 => 'ISO 12800', + 0x4a => 'ISO 16000', + 0x4b => 'ISO 18000', + 0x4c => 'ISO 20000', + 0x4e => 'ISO 25600', + 0x50 => 'ISO 32000', + 0x51 => 'ISO 36000', + 0x52 => 'ISO 40000', + 0x54 => 'ISO 51200', + 0x56 => 'ISO Hi 0.3', + 0x57 => 'ISO Hi 0.5', + 0x58 => 'ISO Hi 0.7', + 0x5a => 'ISO Hi 1.0', + 0x60 => 'ISO Hi 2.0', + 0x66 => 'ISO Hi 3.0', + 0x6c => 'ISO Hi 4.0', + 0x72 => 'ISO Hi 5.0', + }, + }, +); + +# shot information for the D850 firmware 1.00b (encrypted) - ref 28 +%Image::ExifTool::Nikon::ShotInfoD850 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index', NIKON_OFFSETS => 0x0c }, + DATAMEMBER => [ 0x04 ], + IS_SUBDIR => [ 0x10, 0x4c, 0x58, 0xa0 ], + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the D850.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + DataMember => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + 0x10 => { + Name => 'MenuSettingsOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MenuSettingsD850', + Start => '$val', + }, + }, + 0x4c => { + Name => 'MoreSettingsOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MoreSettingsD850', + Start => '$val', + }, + }, + 0x58 => { + Name => 'CustomSettingsOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCustom::SettingsD850', + Start => '$val', + }, + }, + 0xa0 => { + Name => 'OrientationOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::OrientationInfo', + Start => '$val', + }, + }, +); + +%Image::ExifTool::Nikon::MenuSettingsD850 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x06dd => { + Name => 'PhotoShootingMenuBankImageArea', + Mask => 0x07, + PrintConv => { + 0 => 'FX (36x24)', + 1 => 'DX (24x16)', + 2 => '5:4 (30x24)', + 3 => '1.2x (30x20)', + 4 => '1:1 (24x24)', + }, + }, +); + +%Image::ExifTool::Nikon::MoreSettingsD850 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x24 => { + Name => 'PhotoShootingMenuBank', + Condition => '$$self{FILE_TYPE} eq "JPEG"', + Notes => 'valid for JPEG images only', + Mask => 0x03, + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 0x25 => { + Name => 'PrimarySlot', + Mask => 0x80, + PrintConv => { + 0 => 'XQD Card', + 1 => 'SD Card', + }, + }, +); + +# shot information for the D4 firmware 1.00g (ref PH) +%Image::ExifTool::Nikon::ShotInfoD4 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + IS_SUBDIR => [ 0x0751 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are extracted from encrypted data in images from the D4. + }, + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + }, + 0x0751 => { #PH (NC) + Name => 'CustomSettingsD4', + # (seems to work for 1.00g and 1.02b) + Format => 'undef[56]', + SubDirectory => { TagTable => 'Image::ExifTool::NikonCustom::SettingsD4' }, + }, +); + +# shot information for the D4S firmware 1.01a (ref 28, encrypted) +%Image::ExifTool::Nikon::ShotInfoD4S = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index' }, + DATAMEMBER => [ 4 ], + IS_SUBDIR => [ 0x189d, 0x193d, 0x350b ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the D4S.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + DataMember => 'FirmwareVersion', + Format => 'string[5]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + 0x01d0 => { + Name => 'SecondarySlotFunction', + Mask => 0x03, + PrintConv => { + 0 => 'Overflow', + 2 => 'Backup', + 3 => 'NEF Primary + JPG Secondary', + }, + }, + 0x174c => { + Name => 'AEBracketingSteps', + Mask => 0xff, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 0x00 => 'AE Bracketing Disabled', + 0x20 => 'AE Bracketing Disabled', + 0x30 => 'AE Bracketing Disabled', + 0x40 => 'AE Bracketing Disabled', + 0x50 => 'AE Bracketing Disabled', + 0x81 => '+3F0.3', + 0x82 => '-3F0.3', + 0x83 => '+2F0.3', + 0x84 => '-2F0.3', + 0x85 => '3F0.3', + 0x86 => '5F0.3', + 0x87 => '7F0.3', + 0x88 => '9F0.3', + 0x91 => '+3F0.5', + 0x92 => '-3F0.5', + 0x93 => '+2F0.5', + 0x94 => '-2F0.5', + 0x95 => '3F0.5', + 0x96 => '5F0.5', + 0x97 => '7F0.5', + 0x98 => '9F0.5', + 0xa1 => '+3F0.7', + 0xa2 => '-3F0.7', + 0xa3 => '+2F0.7', + 0xa4 => '-2F0.7', + 0xa5 => '3F0.7', + 0xa6 => '5F0.7', + 0xa7 => '7F0.7', + 0xa8 => '9F0.7', + 0xb1 => '+3F1', + 0xb2 => '-3F1', + 0xb3 => '+2F1', + 0xb4 => '-2F1', + 0xb5 => '3F1', + 0xb6 => '5F1', + 0xb7 => '7F1', + 0xb8 => '9F1', + 0xc1 => '+3F2', + 0xc2 => '-3F2', + 0xc3 => '+2F2', + 0xc4 => '-2F2', + 0xc5 => '3F2', + 0xc6 => '5F2', + 0xd1 => '+3F3', + 0xd2 => '-3F3', + 0xd3 => '+2F3', + 0xd4 => '-2F3', + 0xd5 => '3F3', + 0xd6 => '5F3', + }, + }, + 0x174d => { + Name => 'WBBracketingSteps', + Condition => '$$self{FILE_TYPE} ne "TIFF"', # (covers NEF and TIFF) + Mask => 0xff, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 0x00 => 'WB Bracketing Disabled', + 0x01 => 'b3F 1', + 0x02 => 'A3F 1', + 0x03 => 'b2F 1', + 0x04 => 'A2F 1', + 0x05 => '3F 1', + 0x06 => '5F 1', + 0x07 => '7F 1', + 0x08 => '9F 1', + 0x10 => '0F 2', + 0x11 => 'b3F 2', + 0x12 => 'A3F 2', + 0x13 => 'b2F 2', + 0x14 => 'A2F 2', + 0x15 => '3F 2', + 0x16 => '5F 2', + 0x17 => '7F 2', + 0x18 => '9F 2', + 0x20 => '0F 3', + 0x21 => 'b3F 3', + 0x22 => 'A3F 3', + 0x23 => 'b2F 3', + 0x24 => 'A2F 3', + 0x25 => '3F 3', + 0x26 => '5F 3', + 0x27 => '7F 3', + 0x28 => '9F 3', + 0x22 => 'A3F 3', + 0x23 => 'b2F 3', + 0x24 => 'A2F 3', + 0x25 => '3F 3', + 0x26 => '5F 3', + 0x27 => '7F 3', + 0x28 => '9F 3', + }, + }, + 0x184d => { + Name => 'ReleaseMode', + Mask => 0xff, + PrintConv => { + 0 => 'Single Frame', + 1 => 'Continuous High Speed', + 3 => 'Continuous Low Speed', + 4 => 'Timer', + 32 => 'Mirror-Up', + 64 => 'Quiet', + }, + }, + 0x189d => { #PH (NC) + Name => 'CustomSettingsD4S', + Condition => '$$self{FirmwareVersion} =~ /^1\.00/', + Notes => 'firmware version 1.00', + Format => 'undef[56]', + SubDirectory => { TagTable => 'Image::ExifTool::NikonCustom::SettingsD4' }, + }, + 0x18c2 => { # CSf1-c (no idea why it is so far away from the rest of the settings) + Name => 'MultiSelectorLiveViewMode', + Groups => { 1 => 'NikonCustom' }, + Condition => '$$self{FirmwareVersion} !~ /^1\.00/', + Mask => 0xc0, + PrintConv => { + 0 => 'Reset', + 1 => 'Zoom', + 3 => 'None', + }, + }, + 0x18ea => { + Name => 'ISOAutoShutterTime', + Mask => 0x3f, + PrintConv => { + 0 => '1/4000 s', + 1 => '1/3200 s', + 2 => '1/2500 s', + 3 => '1/2000 s', + 4 => '1/1600 s', + 5 => '1/1250 s', + 6 => '1/1000 s', + 7 => '1/800 s', + 8 => '1/640 s', + 9 => '1/500 s', + 10 => '1/400 s', + 11 => '1/320 s', + 12 => '1/250 s', + 13 => '1/200 s', + 14 => '1/160 s', + 15 => '1/125 s', + 16 => '1/100 s', + 17 => '1/80 s', + 18 => '1/60 s', + 19 => '1/50 s', + 20 => '1/40 s', + 21 => '1/30 s', + 22 => '1/15 s', + 23 => '1/8 s', + 24 => '1/4 s', + 25 => '1/2 s', + 26 => '1 s', + 27 => '2 s', + 28 => '4 s', + 29 => '8 s', + 30 => '15 s', + 31 => '30 s', + 32 => 'Auto (Slowest)', + 33 => 'Auto (Slower)', + 34 => 'Auto', + 35 => 'Auto (Faster)', + 36 => 'Auto (Fastest)', + }, + }, + 0x18eb => { + Name => 'ISOAutoHiLimit', + Mask => 0xff, + PrintHex => 1, + PrintConv => { + 0x24 => 'ISO 200', + 0x26 => 'ISO 250', + 0x27 => 'ISO 280', + 0x28 => 'ISO 320', + 0x2a => 'ISO 400', + 0x2c => 'ISO 500', + 0x2d => 'ISO 560', + 0x2e => 'ISO 640', + 0x30 => 'ISO 800', + 0x32 => 'ISO 1000', + 0x33 => 'ISO 1100', + 0x34 => 'ISO 1250', + 0x36 => 'ISO 1600', + 0x38 => 'ISO 2000', + 0x39 => 'ISO 2200', + 0x3a => 'ISO 2500', + 0x3c => 'ISO 3200', + 0x3e => 'ISO 4000', + 0x3f => 'ISO 4500', + 0x40 => 'ISO 5000', + 0x42 => 'ISO 6400', + 0x44 => 'ISO 8000', + 0x45 => 'ISO 9000', + 0x46 => 'ISO 10000', + 0x48 => 'ISO 12800', + 0x4a => 'ISO 16000', + 0x4b => 'ISO 18000', + 0x4c => 'ISO 20000', + 0x4e => 'ISO 25600', + 0x50 => 'ISO 32000', + 0x51 => 'ISO 36000', + 0x52 => 'ISO 40000', + 0x54 => 'ISO 51200', + 0x56 => 'ISO Hi 0.3', + 0x57 => 'ISO Hi 0.5', + 0x58 => 'ISO Hi 0.7', + 0x5a => 'ISO Hi 1.0', + 0x60 => 'ISO Hi 2.0', + 0x66 => 'ISO Hi 3.0', + 0x6c => 'ISO Hi 4.0', + 0x72 => 'ISO Hi 5.0', + }, + }, + 0x193d => { + Name => 'CustomSettingsD4S', + Condition => '$$self{FirmwareVersion} !~ /^1\.00/', + Notes => 'firmware version 1.01', + Format => 'undef[56]', + SubDirectory => { TagTable => 'Image::ExifTool::NikonCustom::SettingsD4' }, + }, +# 0x1978 => { # this decode works, but involves more bits than should be necessary +# Name => 'ShutterTrigger', +# Mask => 0xff, +# PrintConv => { +# 0 => 'Timer', +# 15 => 'Cable Release/Remote', +# 195 => 'Shutter Button', +# }, +# }, + 0x350b => { + Name => 'OrientationInfo', + Format => 'undef[12]', + SubDirectory => { + # Note: pitch angle may be wrong sign for this model? + # (pitch sign was changed without verification to use same decoding as other models) + TagTable => 'Image::ExifTool::Nikon::OrientationInfo', + }, + }, + 0x3693 => { + Name => 'Rotation', + Mask => 0x30, + PrintConv => { + 0 => 'Horizontal', + 1 => 'Rotate 270 CW', + 2 => 'Rotate 90 CW', + 3 => 'Rotate 180', + }, + }, +); + +# shot information for the Z7II firmware 1.00 (encrypted) - ref 28 +%Image::ExifTool::Nikon::ShotInfoZ7II = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index', NIKON_OFFSETS => 0x24 }, + DATAMEMBER => [ 0x04 ], + IS_SUBDIR => [ 0x30, 0x38, 0x98, 0xa0 ], + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the Z7II.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + DataMember => 'FirmwareVersion', + Format => 'string[8]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + 0x0e => { + Name => 'FirmwareVersion2', + Format => 'string[8]', + Writable => 0, + Hidden => 1, + }, + 0x18 => { + Name => 'FirmwareVersion3', + Format => 'string[8]', + Writable => 0, + Hidden => 1, + }, + 0x24 => { + Name => 'NumberOffsets', # number of entries in offset table. offsets are from start of ShotInfo data. + Format => 'int32u', + Writable => 0, + Hidden => 1, + }, + 0x30 => { + Name => 'IntervalOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::IntervalInfoZ7II', + Start => '$val', + } + }, + 0x38 => { + Name => 'PortraitOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::PortraitInfoZ7II', + Start => '$val', + } + }, + 0x98 => { + Name => 'OrientationOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::OrientationInfo', + Start => '$val', + } + }, + 0xa0 => { + Name => 'MenuOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MenuInfoZ7II', + Start => '$val', + }, + }, +); + +%Image::ExifTool::Nikon::IntervalInfoZ7II = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0x24, 0x28 ], + 0x24 => { + Name => 'IntervalShooting', + RawConv => '$$self{IntervalShooting} = $val', + Format => 'int16u', + PrintConv => q{ + return 'Off' if $val == 0 ; + my $i = sprintf("Interval %.0f of %.0f",$val, $$self{IntervalShootingIntervals}||0); # something like "Interval 1 of 3" + my $f = ($$self{IntervalShootingShotsPerInterval}||0) > 1 ? sprintf(" Frame %.0f of %.0f",$$self{IntervalFrame}||0, $$self{IntervalShootingShotsPerInterval}||0): '' ; # something like "Frame 1 of 3" or blank + return "On: $i$f" + #$val == 0 ? 'Off' : sprintf("On: Interval %.0f of %.0f Frame %.0f of %.0f",$val, $$self{IntervalShootingIntervals}||0, $$self{IntervalFrame}||0, $$self{IntervalShootingShotsPerInterval}||0), + }, + }, + 0x28 => { + Name => 'IntervalFrame', + RawConv => '$$self{IntervalFrame} = $val', + Condition => '$$self{IntervalShooting} > 0', + Format => 'int16u', + Hidden => 1, + }, +); + +%Image::ExifTool::Nikon::PortraitInfoZ7II = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0xa0 => { #28 + Name => 'PortraitImpressionBalance', # will be 0 for firmware 1.21 and earlier; firmware 1.30 onward: will be set by Photo Shooting Menu entry Portrait Impression Balance + # offset5+160; 128 is neutral; >128 increases Yellow; <128 increases Magenta; increments of 4 result from 1 full unit adjustment on the camera + # offset5+161 128 is neutral; >128 increases Brightness; <128 decreases Brightness + # with firmware 1.30 when 'Off' is selected in the Shooting menu, offsets 160 & 161 will contain 255. Selecting Mode 1,2, or 3 will populate offsets 160 & 161 with values in the range [116,141] + Format => 'int8u[2]', + Condition => '$$self{FirmwareVersion} ge "01.30"', + PrintConv => q{ + return 'Off' if $val eq '0 0' or $val eq '255 255'; + my @v = split ' ', $val; + my $brightness = $v[1]==128 ? 'Brightness: Neutral' : sprintf('Brightness: %+.1f',($v[1]-128)/4); + my $color = $v[0]==128 ? 'Color: Neutral' : sprintf('%s: %.1f', $v[0]>128 ? 'Yellow' : 'Magenta', abs($v[0]-128)/4); + # will return something like: 'Magenta: 1.0 Brightness: Neutral' + return "$color $brightness" + }, + }, +); + +%Image::ExifTool::Nikon::MenuInfoZ7II = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 0x10 ], + 0x10 => { + Name => 'MenuSettingsOffsetZ7II', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MenuSettingsZ7II', + Start => '$dirStart + $val', + }, + }, +); + +# shot information for the Z8 firmware 1.00 (encrypted) - ref 28 +%Image::ExifTool::Nikon::ShotInfoZ8 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index', NIKON_OFFSETS => 0x24 }, + DATAMEMBER => [ 0x04 ], + IS_SUBDIR => [ 0x30, 0x84, 0x8c ], + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the Z8.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + DataMember => 'FirmwareVersion', + Format => 'string[8]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + 0x0e => { + Name => 'FirmwareVersion2', + Format => 'string[8]', + Writable => 0, + Hidden => 1, + }, + 0x18 => { + Name => 'FirmwareVersion3', + Format => 'string[8]', + Writable => 0, + Hidden => 1, + }, + 0x24 => { + Name => 'NumberOffsets', # number of entries in offset table. offsets are from start of ShotInfo data. + Format => 'int32u', + Writable => 0, + Hidden => 1, + }, + # subdirectories, referenced by offsets (not processed if offset is zero) + 0x30 => { + Name => 'SequenceOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::SeqInfoZ9', + Start => '$val', + }, + }, + 0x84 => { + Name => 'OrientOffset', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96', #not valid for C30/C60/C120 + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::OrientationInfo', + Start => '$val', + }, + }, + 0x8c => { + Name => 'MenuOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MenuInfoZ8', + Start => '$val', + }, + }, +); + +# shot information for the Z9 firmware 1.00 (encrypted) - ref 28 +%Image::ExifTool::Nikon::ShotInfoZ9 = ( + PROCESS_PROC => \&ProcessNikonEncrypted, + WRITE_PROC => \&ProcessNikonEncrypted, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + VARS => { ID_LABEL => 'Index', NIKON_OFFSETS => 0x24 }, + DATAMEMBER => [ 0x04 ], + IS_SUBDIR => [ 0x30, 0x58, 0x80, 0x84, 0x8c ], + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are extracted from encrypted data in images from the Z9.', + 0x00 => { + Name => 'ShotInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 0x04 => { + Name => 'FirmwareVersion', + DataMember => 'FirmwareVersion', + Format => 'string[8]', + Writable => 0, + RawConv => '$$self{FirmwareVersion} = $val', + }, + 0x0e => { + Name => 'FirmwareVersion2', + Format => 'string[8]', + Writable => 0, + Hidden => 1, + }, + 0x18 => { + Name => 'FirmwareVersion3', + Format => 'string[8]', + Writable => 0, + Hidden => 1, + }, + 0x24 => { + Name => 'NumberOffsets', # number of entries in offset table. offsets are from start of ShotInfo data. + Format => 'int32u', + Writable => 0, + Hidden => 1, + }, + # subdirectories, referenced by offsets (not processed if offset is zero) + 0x30 => { + Name => 'SequenceOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::SeqInfoZ9', + Start => '$val', + }, + }, + 0x58 => { + Name => 'Offset13', #offset13 - length x'8f80 (Z9 firmware 3.01 NEF), using currently for a few focus related tags. Might be premature to give the offset a more meaningful name at this point. + Condition => '$$self{FirmwareVersion} and $$self{FirmwareVersion} ge "03.01"', + Format => 'int32u', + AlwaysDecrypt => 1, # (necessary because FirmwareVersion is extracted after decryption time) + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::Offset13InfoZ9', + Start => '$val', + }, + }, + 0x80 => { + Name => 'AutoCaptureOffset', + Condition => '$$self{FirmwareVersion} and $$self{FirmwareVersion} ge "04.00"', + Format => 'int32u', + AlwaysDecrypt => 1, # (necessary because FirmwareVersion is extracted after decryption time) + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::AutoCaptureInfo', + Start => '$val', + }, + }, + 0x84 => { + Name => 'OrientOffset', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96', #not valid for C30/C60/C120 + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::OrientationInfo', + Start => '$val', + }, + }, + 0x8c => { + Name => 'MenuOffset', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MenuInfoZ9', + Start => '$val', + }, + }, +); + +# ref 28 +%Image::ExifTool::Nikon::SeqInfoZ9 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0x20, 0x28, 0x2a ], + #0x0019 => HDRFrame # For JPG 0=> Not HDR; 1=> file is the blended exposure. For raw files: 0=> Not from an HDR capture sequence; otherwise frame number in the HDR capture sequence -- 'Save Individual Pictures (RAW)' must be enabled. + #0x001A => MultipleExposureFrame # For JPG 0=> Not a multiple exposure; 1=> file is the blended exposure. For raw files: 0=> Not a multiple exposure capture; otherwise frame number in the capture sequence -- 'Save Individual Pictures (RAW)' must be enabled. + 0x0020 => { + Name => 'FocusShiftShooting', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96', #not valid for C30/C60/C120 + RawConv => '$$self{FocusShiftShooting} = $val', + PrintConv => q{ + return 'Off' if $val == 0 ; + my $i = sprintf("Frame %.0f of %.0f",$val, $$self{FocusShiftNumberShots}); # something like Frame 1 of 100" + return "On: $i" + }, + }, + 0x0028 => { + Name => 'IntervalShooting', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96', #not valid for C30/C60/C120 + RawConv => '$$self{IntervalShooting} = $val', + Format => 'int16u', + PrintConv => q{ + return 'Off' if $val == 0 ; + my $i = sprintf("Interval %.0f of %.0f",$val, $$self{IntervalShootingIntervals}||0); # something like "Interval 1 of 3" + my $f = ($$self{IntervalShootingShotsPerInterval}||0) > 1 ? sprintf(" Frame %.0f of %.0f",$$self{IntervalFrame}||0, $$self{IntervalShootingShotsPerInterval}||0): '' ; # something like "Frame 1 of 3" or blank + return "On: $i$f" + #$val == 0 ? 'Off' : sprintf("On: Interval %.0f of %.0f Frame %.0f of %.0f",$val, $$self{IntervalShootingIntervals}||0, $$self{IntervalFrame}||0, $$self{IntervalShootingShotsPerInterval}||0), + }, + }, + 0x002a => { + Name => 'IntervalFrame', + RawConv => '$$self{IntervalFrame} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + Format => 'int16u', + Hidden => 1, + }, +); + +# ref 28 +%Image::ExifTool::Nikon::Offset13InfoZ9 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0x0bea, 0x0beb ], + 0x0be8 => { + Name => 'AFAreaInitialXPosition', #stored as a representation of the horizontal position of the center of the portion of the focus box positioned top left when in Wide Area (L/S/C1/C2) focus modes (before subject detection potentially refines focus) + Condition => '$$self{ShutterMode} ne 96 and $$self{AFAreaMode} < 2 ', #not valid for C30/C60/C120 or for Area Modes 1:1 and 16:19 + Format => 'int8s', + PrintConv => q{ + #in FX mode and Single-point, the 29 horizontal focus points are spaced 259 pixels apart starting at pixel 502 and ending at 7754. Spacing is the same for Wide(L/C1/C2) with different start points. + #in FX mode and Dynamic(L), the 27 horizontal focus points are spaced 259 pixels apart starting at pixel 761 and ending at 7495 + #in FX mode and Dynamic(M), the 29 horizontal focus points are spaced 259 pixels apart starting at pixel 502 and ending at 7754 + #in DX mode and Single-point, the 19 horizontal focus points are spaced 388 pixels apart starting at pixel 636 and ending at 7620. [These correspond to FX positions and match the corresponding values in AFAreaMode tag AFAreaXPosition]. + #in DX mode and Wide(S), the 17 horizontal focus points are spaced 393 pixels apart starting at pixel 591 and ending at 7272. + #in DX mode and Dynamic(L), the 17 horizontal focus points are spaced 388 pixels apart starting at pixel 1024 and ending at 7232 + #in DX mode and Dynamic(M), the 19 horizontal focus points are spaced 388 pixels apart starting at pixel 636 and ending at 7620 + + my $areaMode = $$self{VALUE}{PhotoShootingMenuBankImageArea}; + my $afAreaMode = $$self{VALUE}{AFAreaMode}; + my $dynamicAFAreaSize = $$self{VALUE}{DynamicAFAreaSize}; + + my $FX = 0; + my $DX = 1; + + my $Single = 1; + my $Dynamic = 2; + my $WideS = 3; + my $WideL = 4; + my $ThreeD = 5; + my $Auto = 6; + my $WideC1 = 12; + + my $DynamicS = 0; + my $DynamicM = 1; + my $DynamicL = 2; + + my $start = 502; #FX - all flavors + $start = 636 if $areaMode == $DX and ($afAreaMode == $Dynamic or $afAreaMode == $WideL or $afAreaMode == $ThreeD or $afAreaMode == $Auto or $afAreaMode >= $WideC1); #DX Wide(L/C1/C2) + Dynamic (L/M/S) + 3D + Auto + $start = 591 if $areaMode == $DX and $afAreaMode == $WideS ; #DX Wide(S) + + my $increment = 259; #FX - all flavors + $increment = 388 if $areaMode == $DX and ($afAreaMode == $Dynamic or $afAreaMode == $WideL or $afAreaMode == $ThreeD or $afAreaMode == $Auto or $afAreaMode >= $WideC1); #DX Wide(L/C1/C2) + Dynamic (L/M/S) + 3D + Auto + $increment = 393 if $areaMode == $DX and $afAreaMode == $WideS ; #DX Wide(S) + + my $divisor = 4; + $divisor = 6 if $areaMode == $DX ; + + my $offsetVal = 0; + $offsetVal = 12 if $areaMode == $FX and $afAreaMode == $Dynamic ; #FX Dynamic (L/M) - force positive values so perl rounding toward zero isn't an issue + $offsetVal = 18 if $areaMode == $DX and $afAreaMode == $Dynamic ; #DX Dynamic (L/M) + + my $offsetSum = -1; + $offsetSum = -4 if $afAreaMode == $Dynamic ; # Dynamic (L/M) + + my $ncol = $$self{AFAreaInitialWidth}; + $ncol = int($ncol * 2 / 3) if $areaMode == $DX ; #DX + + #some sample mappings: + #FX Wide(S/L/C1/C2) [6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117] to 502, 761, 1020, 1279, 1538, 1797, 2056, 2315, 2574, 2833, 3092, 3351, 3610, 3869, 4128, 4387, 4646, 4905, 5164, 5423, 5682, 5941, 6200, 6459, 6718, 6977, 7236, 7495, 7754] + #DX Wide(L/C1/C2) map for Wide(L)/C1/C2 [6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 67, 73, 79, 85, 91, 97, 103, 109, 115] to [636, 1024, 1412, 1800, 2188, 2576, 2964, 3352, 3740, 4128, 4516, 4904, 5292, 5680, 6068, 6456, 6844, 7232, 7620] + #DX Wide(S) for Wide(S) [6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 67, 73, 79, 85, 91, 97, 103] to [984, 1377, 1770, 2163, 2556, 2949, 3342, 3735, 4128, 4521, 4914, 5307, 5700, 6093, 6486, 6879, 7272] + #FX Dynamic (L) map [-9, -5, -1, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93] to [761, 1020, 1279, 1538, 1797, 2056, 2315, 2574, 2833, 3092, 3351, 3610, 3869, 4128, 4387, 4646, 4905, 5164, 5423, 5682, 5941, 6200, 6459, 6718, 6977, 7236, 7495] + + return $start + $increment * (int(($val + $offsetVal) / $divisor) + int($ncol / 2) + $offsetSum) ; #do not use simple int() becuase it rounds negative fractions toward zero resulting in duplicate values - instead use the 10xdivisor to force positive values + }, + }, + 0x0be9 => { + Name =>'AFAreaInitialYPosition', #stored as a representation of the vertical position of the center of the portion of the focus box positioned top left when in Wide Area (L/S/C1/C2) focus modes (before subject detection potentially refines focus) + Condition => '$$self{ShutterMode} ne 96 and $$self{AFAreaMode} < 2', #not valid for C30/C60/C120 or for Area Modes 1:1 and 16:19 + Format => 'int8s', + PrintConv => q{ + #in FX mode and Single-point, the 17 vertical focus points are spaced 291 pixels apart starting at pixel 424 and ending at 5080. Spacing is the same for Wide(L/C1/C2) + #in FX mode and Dynamic(L), the 15 vertical focus points are spaced 291 pixels apart starting at pixel 715 and ending at 4789 + #in FX mode and Dynamic(M), the 17 vertical l focus points are spaced 291 pixels apart starting at pixel 424 and ending at 5080 + #in DX mode and Single-point, the 11 vertical focus points are spaced 436 pixels apart starting at pixel 572 and ending at 4932. [These correspond to FX positions and match the corresponding values in AFAreaMode tag AFAreaYPosition]. + #in DX Mode and Wide(S) the 9 vertical focus points are spaced 442 pixels apart starting at pixel 542 and ending at 4520 + #in DX mode and Dynamic(L), the 9 vertical focus points are spaced 436 pixels apart starting at pixel 1008 and ending at 4496 + + my $areaMode = $$self{VALUE}{PhotoShootingMenuBankImageArea}; + my $afAreaMode = $$self{VALUE}{AFAreaMode}; + my $dynamicAFAreaSize = $$self{VALUE}{DynamicAFAreaSize}; + + my $FX = 0; + my $DX = 1; + + my $Single = 1; + my $Dynamic = 2; + my $WideS = 3; + my $WideL = 4; + my $ThreeD = 5; + my $Auto = 6; + my $WideC1 = 12; + + my $DynamicS = 0; + my $DynamicM = 1; + my $DynamicL = 2; + + my $start = 424; #FX - all flavors + $start = 572 if $areaMode == $DX and ($afAreaMode == $Dynamic or $afAreaMode == $WideL or $afAreaMode == $ThreeD or $afAreaMode == $Auto or $afAreaMode >= $WideC1); #DX Wide(L/C1/C2) + Dynamic(L/M/S) + 3D + Auto + $start = 542 if $areaMode == $DX and $afAreaMode == 3 ; #DX Wide(S) + + my $increment = 291; #FX - all flavors + $increment = 436 if $areaMode == $DX and ($afAreaMode == $Dynamic or $afAreaMode == $WideL or $afAreaMode == $ThreeD or $afAreaMode == $Auto or $afAreaMode >= $WideC1); #DX Wide(L/C1/C2) + Dynamic (L/M/S) +3D + Auto + $increment = 442 if $areaMode == $DX and $afAreaMode == 3 ; #DX Wide(S) + + my $divisor = 7; + $divisor = 10 if $areaMode == $DX ; #DX + + my $offsetVal = -1; + $offsetVal = 39 if $afAreaMode == $Dynamic and ( $dynamicAFAreaSize == $DynamicL ) ; #Dynamic (L) - force positive values so perl rounding toward zero isn't an issue + $offsetVal = 40 if $afAreaMode == $Dynamic and $dynamicAFAreaSize == $DynamicM ; #Dynamic (M) + $offsetVal = 40 if $areaMode == $FX and (($afAreaMode == $Dynamic and $dynamicAFAreaSize == $DynamicS) or $afAreaMode == $ThreeD) ; #FX Dynamic (S) or 3D + $offsetVal = 38 if $areaMode == $DX and ($afAreaMode == $Dynamic and $dynamicAFAreaSize == $DynamicS ) ; #DX Dynamic (S)or 3D + + my $offsetSum = 0; + $offsetSum = -6 if $areaMode == $FX and ($afAreaMode == $Dynamic or $afAreaMode == $ThreeD); #FX Dynamic (L/M/S) or 3D + $offsetSum = -4 if $areaMode == $DX and ($afAreaMode == $Dynamic or $afAreaMode == $ThreeD ); #DX Dynamic (L/M/S) or 3D + + my $nrow = $$self{AFAreaInitialHeight}; + $nrow = int($nrow * 2 / 3) if $areaMode == $DX; #DX + + #some sample mappings: + #FX Wide(S/L/C1/C2) map [7, 13, 20, 27, 33, 40, 47, 53, 60, 67, 74, 80, 87, 94, 100, 107, 114] to [424, 715, 1006, 1297, 1588, 1879, 2170, 2461, 2752, 3043, 3334, 3625, 3916, 4207, 4498, 4789, 5080] + #DX Wide(L/C1/C2) map [7, 17, 28, 38, 48, 58, 69, 79, 89, 100, 110] to [572, 1008, 1444, 1880, 2316, 2752, 3188, 3624, 4060, 4496, 4932] + #DX Wide(S) map for Wide(S) [7, 17, 28, 38, 48, 58, 69, 79, 89] to [984, 1426, 1868, 2310, 2752, 3194, 3636, 4078, 4520] + #FX Dynamic (L) map [-19, -13, -6, 0, 7, 13, 20, 27, 33, 40, 47, 53, 60, 67, 74] to [715, 1006, 1297, 1588, 1879, 2170, 2461, 2752, 3043, 3334, 3625, 3916, 4207, 4498, 4789] + + return $start + $increment * (int(($val + $offsetVal) / $divisor) + int($nrow / 2) + $offsetSum) ;; + }, + }, + 0x0bea => { + Name => 'AFAreaInitialWidth', + Condition => '$$self{ShutterMode} ne 96', #not valid for C30/C60/C120 + ValueConv => '$$self{VALUE}{PhotoShootingMenuBankImageArea} eq 0 ? $val : int($val * 2 / 3)', #DX mode requires scaling down TODO: add support ImageAreas 1:1 and 16:9 + RawConv => '$$self{AFAreaInitialWidth} = 1 + int ($val / 4)', #convert from [3, 11, 19, 35, 51, 75] to [1, 3, 5, 9 13, 19] to match camera options for C1/C2 focus modes .. input/output of 11/3 is for Wide(S) + }, + 0x0beb => { + Name => 'AFAreaInitialHeight', + Condition => '$$self{ShutterMode} ne 96', #not valid for C30/C60/C120 + ValueConv => '$$self{VALUE}{PhotoShootingMenuBankImageArea} eq 0 ? $val : int($val * 2 / 3)', #DX mode requires scaling down TODO: add support ImageAreas 1:1 and 16:9 + RawConv => '$$self{AFAreaInitialHeight} = 1 + int ($val / 7) ', #convert from [6, 20, 33, 46, 73] to [1, 3, 5, 7, 11] to match camera options for C1/C2 focus modes .. input/output of 33/5 is for Wide(L) + }, +); + +%Image::ExifTool::Nikon::MenuInfoZ8 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 0x10 ], + # 0x00 - int32u size of this directory + 0x10 => [ + { + Name => 'MenuSettingsOffsetZ8', + Format => 'int32u', + Notes => 'Firmware versions 1.0.0', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MenuSettingsZ8', + Start => '$dirStart + $val', + }, + }, + ], +); + +%Image::ExifTool::Nikon::MenuInfoZ9 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 0x10 ], + # 0x00 - int32u size of this directory + 0x10 => [ + { + Name => 'MenuSettingsOffsetZ9', + Condition => '$$self{FirmwareVersion} and $$self{FirmwareVersion} lt "03.00"', + Format => 'int32u', + Notes => 'Firmware versions 2.11 and earlier', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MenuSettingsZ9', + Start => '$dirStart + $val', + }, + }, + { + Name => 'MenuSettingsOffsetZ9v3', + Condition => '$$self{FirmwareVersion} and $$self{FirmwareVersion} lt "04.00"', + Notes => 'Firmware versions 3.0 and v3.10', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MenuSettingsZ9v3', + Start => '$dirStart + $val', + }, + }, + { + Name => 'MenuSettingsOffsetZ9v4', + Notes => 'Firmware versions 4.0 and later', + Format => 'int32u', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MenuSettingsZ9v4', + Start => '$dirStart + $val', + }, + }, + ], +); + +%Image::ExifTool::Nikon::AutoCaptureInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0 ], + 0 => { + Name => 'AutoCapturedFrame', + RawConv => '$$self{AutoCapturedFrame} = $val', + PrintConv => { + 0 => 'No', + 5 => 'Yes', + }, + }, + 1 => { + Name => 'AutoCaptureCriteria', + Condition => '$$self{AutoCapturedFrame} and $$self{AutoCapturedFrame} ne 0', + PrintConv => q[ + $_ = ''; + return $_ . Image::ExifTool::DecodeBits($val, + { + 0 => 'Distance', + 1 => 'Motion', + 2 => 'Subject Detection', + }); + ], + }, + # offsets 3-52 contain a bitmap of the focus points enabled when AutoArea is the AF-Area Mode. 0=> disabled, 1=> enabled. Focus points are in a grid with dimensions 25x15. + 55 => { + Name => 'AutoCaptureRecordingTime', + Condition => '$$self{AutoCapturedFrame} and $$self{AutoCapturedFrame} ne 0', + PrintConv => { + 0 => '1 Sec', + 1 => '3 Sec', + 2 => '5 Sec', + #3 => '', + 4 => '30 Sec', + 5 => 'No Limit', + 6 => '2 Sec', + 7 => '10 Sec', + 8 => '20 Sec', + 9 => '1 Min', + 10 => '3 Min', + 11 => '5 Min', + 12 => '10 Min', + 13 => '30 Min', + }, + }, + 56 => { + Name => 'AutoCaptureWaitTime', + Condition => '$$self{AutoCapturedFrame} and $$self{AutoCapturedFrame} ne 0', + PrintConv => { + 0 => 'No Wait', + 1 => '10 Sec', + 2 => '30 Sec', + 3 => '1 Min', + 4 => '5 Min', + 5 => '10 Min', + 6 => '30 Min', + 7 => '1 Sec', + 8 => '2 Sec', + 9 => '3 Sec', + 10 => '5 Sec', + 11 => '20 Sec', + 12 => '3 Min', + }, + }, + 74 => { + Name => 'AutoCaptureDistanceFar', + Condition => '$$self{AutoCapturedFrame} and $$self{AutoCapturedFrame} ne 0', + PrintConv => 'sprintf("%.1f m", $val/10)', + }, + 78 => { + Name => 'AutoCaptureDistanceNear', + Condition => '$$self{AutoCapturedFrame} and $$self{AutoCapturedFrame} ne 0', + PrintConv => 'sprintf("%.1f m", $val/10)', + }, + 95 => { + Name => 'AutoCaptureCriteriaMotionDirection', + Condition => '$$self{AutoCapturedFrame} and $$self{AutoCapturedFrame} ne 0', + PrintConv => q[ + return 'All' if $val eq 255; + $_ = ''; + return $_ . Image::ExifTool::DecodeBits($val, + { + 0 => 'Top Left', + 1 => 'Top Right', + 2 => 'Bottom Left', + 3 => 'Bottom Right', + 4 => 'Left', + 5 => 'Right', + 6 => 'Top Center', + 7 => 'Bottom Center', + }); + ], + }, + 99 => { + Name => 'AutoCaptureCriteriaMotionSpeed', #1-5 + Condition => '$$self{AutoCapturedFrame} and $$self{AutoCapturedFrame} ne 0', + }, + 100 => { + Name => 'AutoCaptureCriteriaMotionSize', #1-5 + Condition => '$$self{AutoCapturedFrame} and $$self{AutoCapturedFrame} ne 0', + }, + 105 => { + Name => 'AutoCaptureCriteriaSubjectSize', #1-5 + Condition => '$$self{AutoCapturedFrame} and $$self{AutoCapturedFrame} ne 0', + }, + 106 => { + Name => 'AutoCaptureCriteriaSubjectType', + Condition => '$$self{AutoCapturedFrame} and $$self{AutoCapturedFrame} ne 0', + PrintConv => { + 0 => 'Auto (all)', + 1 => 'People', + 2 => 'Animals', + 3 => 'Vehicle' + }, + }, +); + +%Image::ExifTool::Nikon::OrientationInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'RollAngle', + Format => 'fixed32u', + Notes => 'converted to degrees of clockwise camera roll', + ValueConv => '$val <= 180 ? $val : $val - 360', + ValueConvInv => '$val >= 0 ? $val : $val + 360', + PrintConv => 'sprintf("%.1f", $val)', + PrintConvInv => '$val', + }, + 4 => { + Name => 'PitchAngle', + Format => 'fixed32u', + Notes => 'converted to degrees of upward camera tilt', + ValueConv => '$val <= 180 ? $val : $val - 360', + ValueConvInv => '$val >= 0 ? $val : $val + 360', + PrintConv => 'sprintf("%.1f", $val)', + PrintConvInv => '$val', + }, + 8 => { + Name => 'YawAngle', + Format => 'fixed32u', + Notes => 'the camera yaw angle when shooting in portrait orientation', + ValueConv => '$val <= 180 ? $val : $val - 360', + ValueConvInv => '$val >= 0 ? $val : $val + 360', + PrintConv => 'sprintf("%.1f", $val)', + PrintConvInv => '$val', + }, +); + +%Image::ExifTool::Nikon::MenuSettingsZ7II = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 90, 176, 180, 328, 352, 858 ], + NOTES => 'These tags are used by the Z5, Z6, Z7, Z6II, Z7II, Z50 and Zfc.', + #48 SelfTimer' #0=> no 1=> yes works for Z7II firmware 1.40, but not 1.30. Follow-up required. + 90 => { + Name => 'SingleFrame', #0=> Single Frame 1=> one of the continuous modes + Hidden => 1, + RawConv => '$$self{SingleFrame} = $val', + }, + 92 => { + Name => 'ReleaseMode', + #ValueConv => '$$self{SelfTimer} == 1 ? 4 : $$self{SingleFrame} == 0 ? 5 : $val', #map single frame and timer to a unique values for PrintConv. Activate when SelfTimer tag is clarified for cameras other than Z7II fw 1.40 + ValueConv => '$$self{SingleFrame} == 0 ? 5 : $val', #map single frame to a unique value for PrintConv + PrintConv => \%releaseModeZ7, + }, + 160 => { + Name => 'IntervalDurationHours', + Format => 'int32u', + #Condition => '$$self{IntervalShooting} > 0', + }, + 164 => { + Name => 'IntervalDurationMinutes', + Format => 'int32u', + #Condition => '$$self{IntervalShooting} > 0', + }, + 168 => { + Name => 'IntervalDurationSeconds', + Format => 'int32u', + #Condition => '$$self{IntervalShooting} > 0', + }, + 176 => { + Name => 'Intervals', + Format => 'int32u', + RawConv => '$$self{IntervalShootingIntervals} = $val', + #Condition => '$$self{IntervalShooting} > 0', + }, + 180 => { + Name => 'ShotsPerInterval', + Format => 'int32u', + RawConv => '$$self{IntervalShootingShotsPerInterval} = $val', + #Condition => '$$self{IntervalShooting} > 0', + }, + 184 => { + Name => 'IntervalExposureSmoothing', + #Condition => '$$self{IntervalShooting} > 0', + Format => 'int8u', + PrintConv => \%offOn, + }, + 186 => { + Name => 'IntervalPriority', + #Condition => '$$self{IntervalShooting} > 0', + Format => 'int8u', + PrintConv => \%offOn, + }, + 220 => { + Name => 'FocusShiftNumberShots', + }, + 224 => { + Name => 'FocusShiftStepWidth', + }, + 228 => { + Name => 'FocusShiftInterval', + PrintConv => '$val == 1? "1 Second" : sprintf("%.0f Seconds",$val)', + }, + 232 => { + Name => 'FocusShiftExposureLock', + PrintConv => \%offOn, + }, + #304 => White Balance - Kelvin Temp + #312 => ColorSpace + #314 => ActiveD-Lighting + #318 => HighISONoiseReduction + 322 => { + Name => 'DiffractionCompensation', + Format => 'int8u', + PrintConv => \%offOn, + }, + 323 => { + Name => 'AutoDistortionControl', + Format => 'int8u', + PrintConv => \%offOn, + }, + #324 => {Name => 'FlickerReductionShooting',}, # redundant with tag in NikonSettings + 326 => { + Name => 'NikonMeteringMode', + Unknown => 1, + PrintConv => { + 0 => 'Matrix', + 1 => 'Center', + 2 => 'Spot', + 3 => 'Highlight' + }, + }, + 326 => { Name => 'NikonMeteringMode', PrintConv => \%meteringModeZ7}, + 328 => { + Name => 'FlashControlMode', # this and nearby tag values for flash may be set from either the Photo Shooting Menu or using the Flash unit menu + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => \%flashControlModeZ7, + }, + 334 => { + Name => 'FlashGNDistance', + Condition => '$$self{FlashControlMode} == 2', + Unknown => 1, + ValueConv => '$val + 3', + PrintConv => \%flashGNDistance, + }, + 338 => { + Name => 'FlashOutput', # range[0,24] with 0=>Full; 1=>50%; then decreasing flash power in 1/3 stops to 0.39% (1/256 full power). also found in FlashInfoUnknown at offset 0x0a (with different mappings) + Condition => '$$self{FlashControlMode} >= 3', + Unknown => 1, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val>0 ? -3*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.1f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + 346 => { Name => 'FlashWirelessOption', PrintConv => \%flashWirelessOptionZ7, Unknown => 1 }, + 348 => { Name => 'FlashRemoteControl', PrintConv => \%flashRemoteControlZ7, Unknown => 1 }, + 352 => { + Name => 'FlashMasterControlMode', # tag name chosen for compatibility with those found in FlashInfo0102 & FlashInfo0103 + RawConv => '$$self{FlashGroupOptionsMasterMode} = $val', + PrintConv => \%flashGroupOptionsMode, + }, + 354 => { + Name => 'FlashMasterCompensation', + Format => 'int8s', + Condition => '$$self{FlashGroupOptionsMasterMode} != 3', # other than 'Off' + Unknown => 1, + ValueConv => '$val/6', + ValueConvInv => '6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 358 => { + Name => 'FlashMasterOutput', + Unknown => 1, + Condition => '$$self{FlashGroupOptionsMasterMode} == 1', # only for Mode=M + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val>0 ? -3*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.1f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + #360 => { Name => 'FlashGroupAControlMode' }, # commented out to reduce output volume - mapping follows FlashMasterControlMode with FlashGroupACompensation at 362 and FlashGroupAOutput at 368 + #368 => { Name => 'FlashGroupBControlMode' }, # commented out to reduce output volume - mapping follows FlashMasterControlMode with FlashGroupBCompensation at 370 and FlashGroupBOutput at 374 + #376 => { Name => 'FlashGroupCControlMode' }, # commented out to reduce output volume - mapping follows FlashMasterControlMode with FlashGroupCCompensation at 378 and FlashGroupCOutput at 382 + #384 => { Name => 'FlashGroupDControlMode' }, # commented out to reduce output volume - mapping follows FlashMasterControlMode with FlashGroupDCompensation at 386 and FlashGroupDOutput at 390 + #392 => { Name => 'FlashGroupEControlMode' }, # commented out to reduce output volume - mapping follows FlashMasterControlMode with FlashGroupECompensation at 394 and FlashGroupEOutput at 398 + #400 => { Name => 'FlashGroupFControlMode' }, # commented out to reduce output volume - mapping follows FlashMasterControlMode with FlashGroupFCompensation at 402 and FlashGroupFOutput at 406 + #434 => FocusMode + #436 => AFAreaMode + #438 => VibrationReduction + #442 => BracketSet + #444 => BracketProgram + #446 => BracketIncrement + #463 => SilentPhotography + 502 => { Name => 'MovieFrameSize', PrintConv => \%movieFrameSizeZ9, Unknown => 1 }, + 504 => { Name => 'MovieFrameRate', PrintConv => \%movieFrameRateZ7, Unknown => 1 }, + 506 => { + Name => 'MovieSlowMotion', + Unknown => 1, + PrintConv => { + 0 => 'Off', + 1 => 'On (4x)', # 120p recording with playback @ 30p [1920 x 1080; 30p x 4] or 100p recording with playback @ 25p [1920 x 1080; 25p x 4] + 2 => 'On (5x)', # 120p recording with playback @ 24p [1920 x 1080; 20p x 5] + }, + }, + 510 => { + Name => 'MovieType', + Unknown => 1, + PrintConv => { + 0 => 'MOV', + 1 => 'MP4', + }, + }, + #512 => MovieISOAutoHiLimit + 516 => { + Name => 'MovieISOAutoManualMode', + Condition => '$$self{Model} =~ /^NIKON 7/', #ISO ranges vary by model. These mappings are for the Z7 and Z7II + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + #520 => MovieWhiteBalanceSameAsPhoto + 568 => { Name => 'MovieActiveD-Lighting', PrintConv => \%activeDLightingZ7, Unknown => 1 }, + 572 => { Name => 'MovieHighISONoiseReduction', PrintConv => \%offLowNormalHighZ7, Unknown => 1 }, + 574 => { Name => 'MovieVignetteControl', PrintConv => \%offLowNormalHighZ7, Unknown => 1 }, + 576 => { + Name => 'MovieVignetteControlSameAsPhoto', + Unknown => 1, + PrintConv => \%noYes + }, + 577 => { + Name => 'MovieDiffractionCompensation', + Unknown => 1, + PrintConv => \%offOn + }, + 578 => { + Name => 'MovieAutoDistortionControl', + Unknown => 1, + PrintConv => \%offOn + }, + 584 => { Name => 'MovieFocusMode', PrintConv => \%focusModeZ7, Unknown => 1 }, + #586 => MovieAFAreaMode + 590 => { + Name => 'MovieVibrationReduction', + Unknown => 1, + PrintConv => { + 0 => 'Off', + 1 => 'On (Normal)', + 2 => 'On (Sport)', + }, + }, + 591 => { + Name => 'MovieVibrationReductionSameAsPhoto', + Unknown => 1, + PrintConv => \%noYes + }, + #848 => HDMIOutputResolution + #850 => HDMIOutputRange + #854 => HDMIExternalRecorder + #856 => HDMIBitDepth + 858 => { + Name => 'HDMIOutputN-Log', # one of the choices under SettingsMenu/HDMI/Advanced. Curiously,the HDR/HLC output option which is controlled by the same sub-menu is decoded thru NikonSettings + Condition => '$$self{HDMIBitDepth} and $$self{HDMIBitDepth} == 2', # only for 10 bit + RawConv => '$$self{HDMIOutputNLog} = $val', + Unknown => 1, + PrintConv => \%offOn, + }, + #859 => HDMIViewAssist +); + +%Image::ExifTool::Nikon::MenuSettingsZ8 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 152, 200, 204, 244, 440, 548, 554, 570, 596 ], + IS_SUBDIR => [ 943 ], + NOTES => 'These tags are used by the Z8 firmware 1.00.', + 72 => { + Name => 'HighFrameRate', #CH and C30/C60/C120 but not CL + PrintConv => \%highFrameRateZ9, + }, + 152 => { + Name => 'MultipleExposureMode', + RawConv => '$$self{MultipleExposureMode} = $val', + PrintConv => \%multipleExposureModeZ9, + }, + 154 => {Name => 'MultiExposureShots', Condition => '$$self{MultipleExposureMode} != 0'}, #range 2-9 + 184 => { + Name => 'IntervalDurationHours', + Format => 'int32u', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', + }, + 188 => { + Name => 'IntervalDurationMinutes', + Format => 'int32u', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', + }, + 192 => { + Name => 'IntervalDurationSeconds', + Format => 'int32u', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', + }, + 200 => { + Name => 'Intervals', + Format => 'int32u', + RawConv => '$$self{IntervalShootingIntervals} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', + }, + 204 => { + Name => 'ShotsPerInterval', + Format => 'int32u', + RawConv => '$$self{IntervalShootingShotsPerInterval} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', + }, + 208 => { + Name => 'IntervalExposureSmoothing', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', + Format => 'int8u', + PrintConv => \%offOn, + }, + 210 => { + Name => 'IntervalPriority', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', + Format => 'int8u', + PrintConv => \%offOn, + }, + 244 => { + Name => 'FocusShiftNumberShots', #1-300 + RawConv => '$$self{FocusShiftNumberShots} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 248 => { + Name => 'FocusShiftStepWidth', #1(Narrow) to 10 (Wide) + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 252 => { + Name => 'FocusShiftInterval', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + PrintConv => '$val == 1? "1 Second" : sprintf("%.0f Seconds",$val)', + }, + 256 => { + Name => 'FocusShiftExposureLock', + Unknown => 1, + PrintConv => \%offOn, + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 286 => { Name => 'PhotoShootingMenuBank', PrintConv => \%banksZ9 }, + 288 => { Name => 'ExtendedMenuBanks', PrintConv => \%offOn }, # single tag from both Photo & Video menus + 324 => { Name => 'PhotoShootingMenuBankImageArea', PrintConv => \%imageAreaZ9 }, + 338 => { Name => 'AutoISO', PrintConv => \%offOn }, + 340 => { + Name => 'ISOAutoHiLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 342 => { + Name => 'ISOAutoFlashLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 350 => { + Name => 'ISOAutoShutterTime', # shutter speed is 2 ** (-$val/24) + ValueConv => '$val / 8', + Format => 'int16s', + PrintConv => \%iSOAutoShutterTimeZ9, + }, + 432 => { Name => 'MovieVignetteControl', PrintConv => \%offLowNormalHighZ7, Unknown => 1 }, + 434 => { Name => 'DiffractionCompensation', PrintConv => \%offOn }, # value can be set from both the Photo Shoot Menu and the Video Shooting Menu + 436 => { Name => 'FlickerReductionShooting',PrintConv => \%offOn }, + 440 => { + Name => 'FlashControlMode', # this and nearby tag values for flash may be set from either the Photo Shooting Menu or using the Flash unit menu + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => \%flashControlModeZ7, + }, + 548 => { Name => 'AFAreaMode', RawConv => '$$self{AFAreaMode} = $val', PrintConv => \%aFAreaModeZ9}, + 550 => { Name => 'VRMode', PrintConv => \%vRModeZ9}, + 554 => { + Name => 'BracketSet', + RawConv => '$$self{BracketSet} = $val', + PrintConv => \%bracketSetZ9, + }, + 556 => { + Name => 'BracketProgram', + Condition => '$$self{BracketSet} < 3', + Notes => 'AE and/or Flash Bracketing', + PrintConv => \%bracketProgramZ9, + }, + 558 => { + Name => 'BracketIncrement', + Condition => '$$self{BracketSet} < 3', + Notes => 'AE and/or Flash Bracketing', + PrintConv => \%bracketIncrementZ9, + }, + 570 => { Name => 'HDR', RawConv => '$$self{HDR} = $val', PrintConv => \%multipleExposureModeZ9 }, + #572 HDRSaveRaw 0=> No; 1=> Yes + 576 => { Name => 'SecondarySlotFunction', PrintConv => \%secondarySlotFunctionZ9 }, + 582 => { Name => 'HDRLevel', Condition => '$$self{HDR} ne 0', PrintConv => \%hdrLevelZ8 }, + 586 => { Name => 'Slot2JpgSize', PrintConv => { 0 => 'Large (8256x5504)', 1 => 'Medium (6192x4128)', 2 => 'Small (4128x2752)' }, Unknown => 1}, + 592 => { Name => 'DXCropAlert', PrintConv => \%offOn }, + 594 => { Name => 'SubjectDetection', PrintConv => \%subjectDetectionZ9 }, + 596 => { + Name => 'DynamicAFAreaSize', + Condition => '$$self{AFAreaMode} == 2', + RawConv => '$$self{DynamicAFAreaSize} = $val', + PrintConv => \%dynamicAfAreaModesZ9, + }, + 618 => { Name => 'ToneMap', PrintConv => { 0 => 'SDR', 1 => 'HLG' }, Unknown => 1 }, + 622 => { Name => 'PortraitImpressionBalance', PrintConv => \%portraitImpressionBalanceZ8 }, + 636 => { Name => 'HighFrequencyFlickerReductionShooting', PrintConv => \%offOn, Unknown => 1 }, # new with firmware 3.0 + 730 => { + Name => 'MovieImageArea', + Unknown => 1, + Mask => 0x01, # without the mask 4 => 'FX' 5 => DX only the 2nd Z-series field encountered with a mask. + PrintConv => \%imageAreaZ9b, + }, + 740 => { Name => 'MovieType', PrintConv => \%movieTypeZ9, Unknown => 1 }, + 742 => { + Name => 'MovieISOAutoHiLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 744 => { Name => 'MovieISOAutoControlManualMode', PrintConv => \%offOn, Unknown => 1 }, + 746 => { + Name => 'MovieISOAutoManualMode', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 820 => { Name => 'MovieActiveD-Lighting', PrintConv => \%activeDLightingZ7, Unknown => 1 }, + 822 => { Name => 'MovieHighISONoiseReduction', PrintConv => \%offLowNormalHighZ7, Unknown => 1 }, + 828 => { Name => 'MovieFlickerReduction', PrintConv => \%movieFlickerReductionZ9 }, + 830 => { Name => 'MovieMeteringMode', PrintConv => \%meteringModeZ7, Unknown => 1 }, + 832 => { Name => 'MovieFocusMode', PrintConv => \%focusModeZ7, Unknown => 1 }, + 834 => { Name => 'MovieAFAreaMode', PrintConv => \%aFAreaModeZ9 }, + 836 => { Name => 'MovieVRMode', PrintConv => \%vRModeZ9, Unknown => 1 }, + 840 => { Name => 'MovieElectronicVR', PrintConv => \%offOn, Unknown => 1 }, # distinct from MoveieVRMode + 842 => { Name => 'MovieSoundRecording', PrintConv => { 0 => 'Off', 1 => 'Auto', 2 => 'Manual' }, Unknown => 1 }, + 844 => { Name => 'MicrophoneSensitivity', Unknown => 1 }, # 1-20 + 846 => { Name => 'MicrophoneAttenuator', PrintConv => \%offOn, Unknown => 1 }, # distinct from MoveieVRMode + 848 => { Name => 'MicrophoneFrequencyResponse',PrintConv => { 0 => 'Wide Range', 1 => 'Vocal Range' }, Unknown => 1 }, + 850 => { Name => 'WindNoiseReduction', PrintConv => \%offOn, Unknown => 1 }, + 882 => { Name => 'MovieFrameSize', PrintConv => \%movieFrameSizeZ9, Unknown => 1 }, + 884 => { Name => 'MovieFrameRate', PrintConv => \%movieFrameRateZ7, Unknown => 1 }, + 886 => { Name => 'MicrophoneJackPower', PrintConv => \%offOn, Unknown => 1 }, + 887 => { Name => 'MovieDXCropAlert', PrintConv => \%offOn, Unknown => 1 }, + 888 => { Name => 'MovieSubjectDetection', PrintConv => \%subjectDetectionZ9, Unknown => 1 }, + 896 => { Name => 'MovieHighResZoom', PrintConv => \%offOn, Unknown => 1 }, + 943 => { + Name => 'CustomSettingsZ8', + Format => 'undef[730]', + SubDirectory => { TagTable => 'Image::ExifTool::NikonCustom::SettingsZ8' }, + }, + 1682 => { Name => 'Language', PrintConv => \%languageZ9, Unknown => 1 }, + 1684 => { Name => 'TimeZone', PrintConv => \%timeZoneZ9 }, + 1690 => { Name => 'MonitorBrightness', PrintConv => \%monitorBrightnessZ9, Unknown => 1 }, # settings: -5 to +5. Added with firmware 3.0: Lo1, Lo2, Hi1, Hi2 + 1712 => { Name => 'AFFineTune', PrintConv => \%offOn, Unknown => 1 }, + 1716 => { Name => 'NonCPULens1FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, #should probably hide altogther if $val is 0 + 1718 => { Name => 'NonCPULens2FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1720 => { Name => 'NonCPULens3FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1722 => { Name => 'NonCPULens4FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1724 => { Name => 'NonCPULens5FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1726 => { Name => 'NonCPULens6FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1728 => { Name => 'NonCPULens7FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1730 => { Name => 'NonCPULens8FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1732 => { Name => 'NonCPULens9FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1734 => { Name => 'NonCPULens10FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1736 => { Name => 'NonCPULens11FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1738 => { Name => 'NonCPULens12FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1740 => { Name => 'NonCPULens13FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1742 => { Name => 'NonCPULens14FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1744 => { Name => 'NonCPULens15FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1746 => { Name => 'NonCPULens16FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1748 => { Name => 'NonCPULens17FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1750 => { Name => 'NonCPULens18FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1752 => { Name => 'NonCPULens19FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1754 => { Name => 'NonCPULens20FocalLength', Format => 'int16u', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1756 => { Name => 'NonCPULens1MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1758 => { Name => 'NonCPULens2MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1760 => { Name => 'NonCPULens3MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1762 => { Name => 'NonCPULens4MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1764 => { Name => 'NonCPULens5MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1766 => { Name => 'NonCPULens6MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1768 => { Name => 'NonCPULens7MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1770 => { Name => 'NonCPULens8MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1772 => { Name => 'NonCPULens9MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1774 => { Name => 'NonCPULens10MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1776 => { Name => 'NonCPULens11MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1778 => { Name => 'NonCPULens12MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1780 => { Name => 'NonCPULens13MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1782 => { Name => 'NonCPULens14MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1784 => { Name => 'NonCPULens15MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1786 => { Name => 'NonCPULens16MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1788 => { Name => 'NonCPULens17MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1790 => { Name => 'NonCPULens18MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1792 => { Name => 'NonCPULens19MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1794 => { Name => 'NonCPULens20MaxAperture', Format => 'int16u', PrintConv => \%nonCPULensApertureZ8, Unknown => 1}, + 1808 => { Name => 'HDMIOutputResolution', PrintConv => \%hDMIOutputResolutionZ9 }, + 1826 => { Name => 'AirplaneMode', PrintConv => \%offOn, Unknown => 1 }, + 1827 => { Name => 'EmptySlotRelease', PrintConv => { 0 => 'Disable Release', 1 => 'Enable Release' }, Unknown => 1 }, + 1862 => { Name => 'EnergySavingMode', PrintConv => \%offOn, Unknown => 1 }, + 1890 => { Name => 'USBPowerDelivery', PrintConv => \%offOn, Unknown => 1 }, + 1899 => { Name => 'SensorShield', PrintConv => { 0 => 'Stays Open', 1 => 'Closes' }, Unknown => 1 }, +); +%Image::ExifTool::Nikon::MenuSettingsZ9 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 140, 188, 192, 232, 424, 528, 534, 576 ], + IS_SUBDIR => [ 799 ], + NOTES => 'These tags are used by the Z9.', + #90 ISO + 140 => { + Name => 'MultipleExposureMode', + RawConv => '$$self{MultipleExposureMode} = $val', + PrintConv => \%multipleExposureModeZ9, + }, + 142 => {Name => 'MultiExposureShots', Condition => '$$self{MultipleExposureMode} != 0' }, #range 2-9 + 188 => { + Name => 'Intervals', + Format => 'int32u', + RawConv => '$$self{IntervalShootingIntervals} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', #not valid for C30/C60/C120 + }, + 192 => { + Name => 'ShotsPerInterval', + Format => 'int32u', + RawConv => '$$self{IntervalShootingShotsPerInterval} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', #not valid for C30/C60/C120 + }, + #220 NEFCompression 0=> 'Lossless' 1=> 'High Efficiency*' 4=> 'High Efficientcy' + 232 => { + Name => 'FocusShiftNumberShots', #1-300 + RawConv => '$$self{FocusShiftNumberShots} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 236 => { + Name => 'FocusShiftStepWidth', #1(Narrow) to 10 (Wide) + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 240 => { + Name => 'FocusShiftInterval', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + PrintConv => '$val == 1? "1 Second" : sprintf("%.0f Seconds",$val)', + }, + 244 => { + Name => 'FocusShiftExposureLock', + Unknown => 1, + PrintConv => \%offOn, + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 274 => { Name => 'PhotoShootingMenuBank', PrintConv => \%banksZ9 }, + 276 => { Name => 'ExtendedMenuBanks', PrintConv => \%offOn }, #single tag from both Photo & Video menus + 308 => { Name => 'PhotoShootingMenuBankImageArea', PrintConv => \%imageAreaZ9 }, + #310 ImageQuality + 322 => { Name => 'AutoISO', PrintConv => \%offOn }, + 324 => { + Name => 'ISOAutoHiLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 326 => { + Name => 'ISOAutoFlashLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + #332 ISOAutoShutterTime - Auto setting 0=> 'Auto (Slowest)', 1 => 'Auto (Slower)', 2=> 'Auto', 3=> 'Auto (Faster)', 4=> 'Auto (Fastest)' + 334 => { + Name => 'ISOAutoShutterTime', #shutter speed is 2 ** (-$val/24) + ValueConv => '$val / 8', + Format => 'int16s', + PrintConv => \%iSOAutoShutterTimeZ9, + }, + #336 WhiteBalance + #406 PictureControl + #408 ColorSpace + #410 ActiveD-Lighting + #412 => { Name => 'NoiseReduction', PrintConv => \%offOn }, #Long Exposure Noise Reduction + #414 HighISONoiseReduction + #414 VignetteControl + 416 => { Name => 'MovieVignetteControl', PrintConv => \%offLowNormalHighZ7, Unknown => 1 }, + 418 => { Name => 'DiffractionCompensation', PrintConv => \%offOn }, #value can be set from both the Photo Shoot Menu and the Video Shooting Menu + #419 AutoDistortionControl #value can be set from both the Photo Shoot Menu and the Video Shooting Menu + 420 => { Name => 'FlickerReductionShooting', PrintConv => \%offOn }, + #422 MeteringMode + 424 => { + Name => 'FlashControlMode', # this and nearby tag values for flash may be set from either the Photo Shooting Menu or using the Flash unit menu + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => \%flashControlModeZ7, + }, + 426 => { + Name => 'FlashMasterCompensation', + Format => 'int8s', + Unknown => 1, + ValueConv => '$val/6', + ValueConvInv => '6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 430 => { + Name => 'FlashGNDistance', + Condition => '$$self{FlashControlMode} == 2', + Unknown => 1, + ValueConv => '$val + 3', + PrintConv => \%flashGNDistance, + }, + 434 => { + Name => 'FlashOutput', # range[0,24] with 0=>Full; 1=>50%; then decreasing flash power in 1/3 stops to 0.39% (1/256 full power). also found in FlashInfoUnknown at offset 0x0a (with different mappings) + Condition => '$$self{FlashControlMode} >= 3', + Unknown => 1, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val>0 ? -3*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.1f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + #442 flash wirelss control 0=> 'Off' 1=> 'CMD' + 444 => { Name => 'FlashRemoteControl', PrintConv => \%flashRemoteControlZ7, Unknown => 1 }, + 456 => { Name => 'FlashWirelessOption', PrintConv => \%flashWirelessOptionZ7, Unknown => 1 }, + #526 FocusMode + 528 => { Name => 'AFAreaMode', RawConv => '$$self{AFAreaMode} = $val', PrintConv => \%aFAreaModeZ9}, + 530 => { Name => 'VRMode', PrintConv => \%vRModeZ9 }, + 534 => { + Name => 'BracketSet', + RawConv => '$$self{BracketSet} = $val', + PrintConv => \%bracketSetZ9, + }, + 536 => { + Name => 'BracketProgram', + Condition => '$$self{BracketSet} < 3', + Notes => 'AE and/or Flash Bracketing', + PrintConv => \%bracketProgramZ9, + }, + 538 => { + Name => 'BracketIncrement', + Condition => '$$self{BracketSet} < 3', + Notes => 'AE and/or Flash Bracketing', + PrintConv => \%bracketIncrementZ9, + }, + #544 BracketProgram for ADL + 556 => { Name => 'SecondarySlotFunction', PrintConv => \%secondarySlotFunctionZ9 }, + 572 => { Name => 'DXCropAlert', PrintConv => \%offOn }, + 574 => { Name => 'SubjectDetection', PrintConv => \%subjectDetectionZ9 }, + 576 => { + Name => 'DynamicAFAreaSize', + Condition => '$$self{AFAreaMode} == 2', + RawConv => '$$self{DynamicAFAreaSize} = $val', + PrintConv => \%dynamicAfAreaModesZ9, + }, + 604 => { + Name => 'MovieImageArea', + Unknown => 1, + Mask => 0x01, # without the mask 4 => 'FX', 5 => DX. only the 2nd Z-series field encountered with a mask + PrintConv => \%imageAreaZ9b, + }, + 614 => { Name => 'MovieType', PrintConv => \%movieTypeZ9, Unknown => 1 }, + 616 => { + Name => 'MovieISOAutoHiLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 618 => { Name => 'MovieISOAutoControlManualMode', PrintConv => \%offOn, Unknown => 1 }, + 620 => { + Name => 'MovieISOAutoManualMode', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 696 => { Name => 'MovieActiveD-Lighting', PrintConv => \%activeDLightingZ7, Unknown => 1 }, + 698 => { Name => 'MovieHighISONoiseReduction', PrintConv => \%offLowNormalHighZ7, Unknown => 1 }, + 704 => { Name => 'MovieFlickerReduction', PrintConv => \%movieFlickerReductionZ9 }, + 706 => { Name => 'MovieMeteringMode', PrintConv => \%meteringModeZ7, Unknown => 1 }, + 708 => { Name => 'MovieFocusMode', PrintConv => \%focusModeZ7, Unknown => 1 }, + 710 => { Name => 'MovieAFAreaMode', PrintConv => \%aFAreaModeZ9 }, + 712 => { Name => 'MovieVRMode', PrintConv => \%vRModeZ9, Unknown => 1 }, + 716 => { Name => 'MovieElectronicVR', PrintConv => \%offOn, Unknown => 1 }, #distinct from MoveieVRMode + 718 => { Name => 'MovieSoundRecording', PrintConv => { 0 => 'Off', 1 => 'Auto', 2 => 'Manual' }, Unknown => 1 }, + 720 => { Name => 'MicrophoneSensitivity', Unknown => 1 }, #1-20 + 722 => { Name => 'MicrophoneAttenuator', PrintConv => \%offOn, Unknown => 1 }, #distinct from MoveieVRMode + 724 => { Name => 'MicrophoneFrequencyResponse', PrintConv => { 0 => 'Wide Range', 1 => 'Vocal Range' }, Unknown => 1 }, + 726 => { Name => 'WindNoiseReduction', PrintConv => \%offOn, Unknown => 1 }, + 748 => { Name => 'MovieToneMap', PrintConv => \%movieToneMapZ9, Unknown => 1 }, + 754 => { Name => 'MovieFrameSize', PrintConv => \%movieFrameSizeZ9, Unknown => 1 }, + 756 => { Name => 'MovieFrameRate', PrintConv => \%movieFrameRateZ7, Unknown => 1 }, + 762 => { Name => 'MicrophoneJackPower', PrintConv => \%offOn, Unknown => 1 }, + 763 => { Name => 'MovieDXCropAlert', PrintConv => \%offOn, Unknown => 1 }, + 764 => { Name => 'MovieSubjectDetection', PrintConv => \%subjectDetectionZ9, Unknown => 1 }, + 799 => { + Name => 'CustomSettingsZ9', + Format => 'undef[608]', + SubDirectory => { TagTable => 'Image::ExifTool::NikonCustom::SettingsZ9' }, + }, + 1426 => { Name => 'Language', PrintConv => \%languageZ9, Unknown => 1 }, + 1428 => { Name => 'TimeZone', PrintConv => \%timeZoneZ9 }, + 1434 => { Name => 'MonitorBrightness', ValueConv => '$val - 5', Unknown => 1 }, # settings: -5 to +5 + 1456 => { Name => 'AFFineTune', PrintConv => \%offOn, Unknown => 1 }, + 1552 => { Name => 'HDMIOutputResolution', PrintConv => \%hDMIOutputResolutionZ9 }, + 1565 => { Name => 'SetClockFromLocationData', PrintConv => \%offOn, Unknown => 1 }, + 1572 => { Name => 'AirplaneMode', PrintConv => \%offOn, Unknown => 1 }, + 1573 => { Name => 'EmptySlotRelease', PrintConv => { 0 => 'Disable Release', 1 => 'Enable Release' }, Unknown => 1 }, + 1608 => { Name => 'EnergySavingMode', PrintConv => \%offOn, Unknown => 1 }, + 1632 => { Name => 'RecordLocationData', PrintConv => \%offOn, Unknown => 1 }, + 1636 => { Name => 'USBPowerDelivery', PrintConv => \%offOn, Unknown => 1 }, + 1645 => { Name => 'SensorShield', PrintConv => { 0 => 'Stays Open', 1 => 'Closes' }, Unknown => 1 }, +); + +%Image::ExifTool::Nikon::MenuSettingsZ9v3 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 154, 204, 208, 248, 444, 548, 554, 596 ], + IS_SUBDIR => [ 847 ], + NOTES => 'These tags are used by the Z9 firmware 3.00.', + 72 => { + Name => 'HighFrameRate', #CH and C30/C60/C120 but not CL + PrintConv => \%highFrameRateZ9, + }, + 154 => { + Name => 'MultipleExposureMode', + RawConv => '$$self{MultipleExposureMode} = $val', + PrintConv => \%multipleExposureModeZ9, + }, + 156 => {Name => 'MultiExposureShots', Condition => '$$self{MultipleExposureMode} != 0'}, #range 2-9 + 204 => { + Name => 'Intervals', + Format => 'int32u', + RawConv => '$$self{IntervalShootingIntervals} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', #not valid for C30/C60/C120 + }, + 208 => { + Name => 'ShotsPerInterval', + Format => 'int32u', + RawConv => '$$self{IntervalShootingShotsPerInterval} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', #not valid for C30/C60/C120 + }, + 248 => { + Name => 'FocusShiftNumberShots', #1-300 + RawConv => '$$self{FocusShiftNumberShots} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 252 => { + Name => 'FocusShiftStepWidth', #1(Narrow) to 10 (Wide) + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 256 => { + Name => 'FocusShiftInterval', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + PrintConv => '$val == 1? "1 Second" : sprintf("%.0f Seconds",$val)', + }, + 260 => { + Name => 'FocusShiftExposureLock', + Unknown => 1, + PrintConv => \%offOn, + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 290 => { Name => 'PhotoShootingMenuBank', PrintConv => \%banksZ9 }, + 292 => { Name => 'ExtendedMenuBanks', PrintConv => \%offOn }, # single tag from both Photo & Video menus + 328 => { Name => 'PhotoShootingMenuBankImageArea', PrintConv => \%imageAreaZ9 }, + 342 => { Name => 'AutoISO', PrintConv => \%offOn }, + 344 => { + Name => 'ISOAutoHiLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 346 => { + Name => 'ISOAutoFlashLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 354 => { + Name => 'ISOAutoShutterTime', # shutter speed is 2 ** (-$val/24) + ValueConv => '$val / 8', + Format => 'int16s', + PrintConv => \%iSOAutoShutterTimeZ9, + }, + 436 => { Name => 'MovieVignetteControl', PrintConv => \%offLowNormalHighZ7, Unknown => 1 }, + 438 => { Name => 'DiffractionCompensation', PrintConv => \%offOn }, # value can be set from both the Photo Shoot Menu and the Video Shooting Menu + 440 => { Name => 'FlickerReductionShooting',PrintConv => \%offOn }, + 444 => { + Name => 'FlashControlMode', # this and nearby tag values for flash may be set from either the Photo Shooting Menu or using the Flash unit menu + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => \%flashControlModeZ7, + }, + 446 => { + Name => 'FlashMasterCompensation', + Format => 'int8s', + Unknown => 1, + ValueConv => '$val/6', + ValueConvInv => '6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 450 => { + Name => 'FlashGNDistance', + Condition => '$$self{FlashControlMode} == 2', + Unknown => 1, + ValueConv => '$val + 3', + PrintConv => \%flashGNDistance, + }, + 454 => { + Name => 'FlashOutput', # range[0,24] with 0=>Full; 1=>50%; then decreasing flash power in 1/3 stops to 0.39% (1/256 full power). also found in FlashInfoUnknown at offset 0x0a (with different mappings) + Condition => '$$self{FlashControlMode} >= 3', + Unknown => 1, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val>0 ? -3*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.1f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + #462 flash wireless control 0=> 'Off' 1=> 'Optical AWL' + #464 => { Name => 'FlashRemoteControl', PrintConv => \%flashRemoteControlZ7, Unknown => 1 }, + #476 => { Name => 'FlashWirelessOption', PrintConv => \%flashWirelessOptionZ7, Unknown => 1 }, + 548 => { Name => 'AFAreaMode', RawConv => '$$self{AFAreaMode} = $val', PrintConv => \%aFAreaModeZ9}, + 550 => { Name => 'VRMode', PrintConv => \%vRModeZ9}, + 554 => { + Name => 'BracketSet', + RawConv => '$$self{BracketSet} = $val', + PrintConv => \%bracketSetZ9, + }, + 556 => { + Name => 'BracketProgram', + Condition => '$$self{BracketSet} < 3', + Notes => 'AE and/or Flash Bracketing', + PrintConv => \%bracketProgramZ9, + }, + 558 => { + Name => 'BracketIncrement', + Condition => '$$self{BracketSet} < 3', + Notes => 'AE and/or Flash Bracketing', + PrintConv => \%bracketIncrementZ9, + }, + 576 => { Name => 'SecondarySlotFunction', PrintConv => \%secondarySlotFunctionZ9 }, + 592 => { Name => 'DXCropAlert', PrintConv => \%offOn }, + 594 => { Name => 'SubjectDetection', PrintConv => \%subjectDetectionZ9 }, + 596 => { + Name => 'DynamicAFAreaSize', + Condition => '$$self{AFAreaMode} == 2', + RawConv => '$$self{DynamicAFAreaSize} = $val', + PrintConv => \%dynamicAfAreaModesZ9, + }, + 636 => { Name => 'HighFrequencyFlickerReductionShooting', PrintConv => \%offOn, Unknown => 1 }, # new with firmware 3.0 + 646 => { + Name => 'MovieImageArea', + Unknown => 1, + Mask => 0x01, # without the mask 4 => 'FX' 5 => DX only the 2nd Z-series field encountered with a mask. + PrintConv => \%imageAreaZ9b, + }, + 656 => { Name => 'MovieType', PrintConv => \%movieTypeZ9, Unknown => 1 }, + 658 => { + Name => 'MovieISOAutoHiLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 660 => { Name => 'MovieISOAutoControlManualMode', PrintConv => \%offOn, Unknown => 1 }, + 662 => { + Name => 'MovieISOAutoManualMode', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 736 => { Name => 'MovieActiveD-Lighting', PrintConv => \%activeDLightingZ7, Unknown => 1 }, + 738 => { Name => 'MovieHighISONoiseReduction', PrintConv => \%offLowNormalHighZ7, Unknown => 1 }, + 744 => { Name => 'MovieFlickerReduction', PrintConv => \%movieFlickerReductionZ9 }, + 746 => { Name => 'MovieMeteringMode', PrintConv => \%meteringModeZ7, Unknown => 1 }, + 748 => { Name => 'MovieFocusMode', PrintConv => \%focusModeZ7, Unknown => 1 }, + 750 => { Name => 'MovieAFAreaMode', PrintConv => \%aFAreaModeZ9 }, + 752 => { Name => 'MovieVRMode', PrintConv => \%vRModeZ9, Unknown => 1 }, + 756 => { Name => 'MovieElectronicVR', PrintConv => \%offOn, Unknown => 1 }, # distinct from MoveieVRMode + 758 => { Name => 'MovieSoundRecording', PrintConv => { 0 => 'Off', 1 => 'Auto', 2 => 'Manual' }, Unknown => 1 }, + 760 => { Name => 'MicrophoneSensitivity', Unknown => 1 }, # 1-20 + 762 => { Name => 'MicrophoneAttenuator', PrintConv => \%offOn, Unknown => 1 }, # distinct from MoveieVRMode + 764 => { Name => 'MicrophoneFrequencyResponse',PrintConv => { 0 => 'Wide Range', 1 => 'Vocal Range' }, Unknown => 1 }, + 766 => { Name => 'WindNoiseReduction', PrintConv => \%offOn, Unknown => 1 }, + 788 => { Name => 'MovieToneMap', PrintConv => \%movieToneMapZ9, Unknown => 1 }, + 794 => { Name => 'MovieFrameSize', PrintConv => \%movieFrameSizeZ9, Unknown => 1 }, + 796 => { Name => 'MovieFrameRate', PrintConv => \%movieFrameRateZ7, Unknown => 1 }, + 802 => { Name => 'MicrophoneJackPower', PrintConv => \%offOn, Unknown => 1 }, + 803 => { Name => 'MovieDXCropAlert', PrintConv => \%offOn, Unknown => 1 }, + 804 => { Name => 'MovieSubjectDetection', PrintConv => \%subjectDetectionZ9, Unknown => 1 }, + 812 => { Name => 'MovieHighResZoom', PrintConv => \%offOn, Unknown => 1 }, + 847 => { + Name => 'CustomSettingsZ9', + Format => 'undef[608]', + SubDirectory => { TagTable => 'Image::ExifTool::NikonCustom::SettingsZ9' }, + }, + 1474 => { Name => 'Language', PrintConv => \%languageZ9, Unknown => 1 }, + 1476 => { Name => 'TimeZone', PrintConv => \%timeZoneZ9 }, + 1482 => { Name => 'MonitorBrightness', PrintConv => \%monitorBrightnessZ9, Unknown => 1 }, # settings: -5 to +5. Added with firmware 3.0: Lo1, Lo2, Hi1, Hi2 + 1504 => { Name => 'AFFineTune', PrintConv => \%offOn, Unknown => 1 }, + 1600 => { Name => 'HDMIOutputResolution', PrintConv => \%hDMIOutputResolutionZ9 }, + 1613 => { Name => 'SetClockFromLocationData', PrintConv => \%offOn, Unknown => 1 }, + 1620 => { Name => 'AirplaneMode', PrintConv => \%offOn, Unknown => 1 }, + 1621 => { Name => 'EmptySlotRelease', PrintConv => { 0 => 'Disable Release', 1 => 'Enable Release' }, Unknown => 1 }, + 1656 => { Name => 'EnergySavingMode', PrintConv => \%offOn, Unknown => 1 }, + 1680 => { Name => 'RecordLocationData', PrintConv => \%offOn, Unknown => 1 }, + 1684 => { Name => 'USBPowerDelivery', PrintConv => \%offOn, Unknown => 1 }, + 1693 => { Name => 'SensorShield', PrintConv => { 0 => 'Stays Open', 1 => 'Closes' }, Unknown => 1 }, + 1754 => { + Name => 'FocusShiftAutoReset', + Unknown => 1, + PrintConv => \%offOn, + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 1810 => { #CSd4-a + Name => 'PreReleaseBurstLength', + PrintConv => { + 0 => 'None', + 1 => '0.3 Sec', + 2 => '0.5 Sec', + 3 => '1 Sec', + }, + }, + 1812 => { #CSd4-b + Name => 'PostReleaseBurstLength', + PrintConv => { + 0 => '1 Sec', + 1 => '2 Sec', + 2 => '3 Sec', + 3 => 'Max', + }, + }, + #1824 ReleaseTimingIndicatorTypeADelay CSd14-b 0 => '1/200' ... 15 => '1/6' + #1826 VerticalISOButton CSf2 + #1828 ExposureCompensationButton CSf2 + #1830 ISOButton CSf2 + #1890 ViewModeShowEffectsOfSettings CSd9-a 0=>'Always', 1=> 'Only When Flash Not Used' + #1892 DispButton CSf2 + #1936 FocusPointDisplayOption3DTrackingColor CSa11-d 0=> 'White', 1= => 'Red' +); + +%Image::ExifTool::Nikon::MenuSettingsZ9v4 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 154, 204, 208, 248, 444, 548, 554, 570, 596 ], + IS_SUBDIR => [ 847 ], + NOTES => 'These tags are used by the Z9 firmware 3.00.', + 72 => { + Name => 'HighFrameRate', #CH and C30/C60/C120 but not CL + PrintConv => \%highFrameRateZ9, + }, + 154 => { + Name => 'MultipleExposureMode', + RawConv => '$$self{MultipleExposureMode} = $val', + PrintConv => \%multipleExposureModeZ9, + }, + 156 => {Name => 'MultiExposureShots', Condition => '$$self{MultipleExposureMode} != 0'}, #range 2-9 + 204 => { + Name => 'Intervals', + Format => 'int32u', + RawConv => '$$self{IntervalShootingIntervals} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', #not valid for C30/C60/C120 + }, + 208 => { + Name => 'ShotsPerInterval', + Format => 'int32u', + RawConv => '$$self{IntervalShootingShotsPerInterval} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{IntervalShooting} > 0', #not valid for C30/C60/C120 + }, + 248 => { + Name => 'FocusShiftNumberShots', #1-300 + RawConv => '$$self{FocusShiftNumberShots} = $val', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 252 => { + Name => 'FocusShiftStepWidth', #1(Narrow) to 10 (Wide) + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 256 => { + Name => 'FocusShiftInterval', + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + PrintConv => '$val == 1? "1 Second" : sprintf("%.0f Seconds",$val)', + }, + 260 => { + Name => 'FocusShiftExposureLock', + Unknown => 1, + PrintConv => \%offOn, + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 290 => { Name => 'PhotoShootingMenuBank', PrintConv => \%banksZ9 }, + 292 => { Name => 'ExtendedMenuBanks', PrintConv => \%offOn }, # single tag from both Photo & Video menus + 328 => { Name => 'PhotoShootingMenuBankImageArea', PrintConv => \%imageAreaZ9 }, + #334 JPGCompression 0 => 'Size Priority', 1 => 'Optimal Quality', + 342 => { Name => 'AutoISO', PrintConv => \%offOn }, + 344 => { + Name => 'ISOAutoHiLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 346 => { + Name => 'ISOAutoFlashLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 354 => { + Name => 'ISOAutoShutterTime', # shutter speed is 2 ** (-$val/24) + ValueConv => '$val / 8', + Format => 'int16s', + PrintConv => \%iSOAutoShutterTimeZ9, + }, + 436 => { Name => 'MovieVignetteControl', PrintConv => \%offLowNormalHighZ7, Unknown => 1 }, + 438 => { Name => 'DiffractionCompensation', PrintConv => \%offOn }, # value can be set from both the Photo Shoot Menu and the Video Shooting Menu + 440 => { Name => 'FlickerReductionShooting',PrintConv => \%offOn }, + 444 => { + Name => 'FlashControlMode', # this and nearby tag values for flash may be set from either the Photo Shooting Menu or using the Flash unit menu + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => \%flashControlModeZ7, + }, + 446 => { + Name => 'FlashMasterCompensation', + Format => 'int8s', + Unknown => 1, + ValueConv => '$val/6', + ValueConvInv => '6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 450 => { + Name => 'FlashGNDistance', + Condition => '$$self{FlashControlMode} == 2', + Unknown => 1, + ValueConv => '$val + 3', + PrintConv => \%flashGNDistance, + }, + 454 => { + Name => 'FlashOutput', # range[0,24] with 0=>Full; 1=>50%; then decreasing flash power in 1/3 stops to 0.39% (1/256 full power). also found in FlashInfoUnknown at offset 0x0a (with different mappings) + Condition => '$$self{FlashControlMode} >= 3', + Unknown => 1, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val>0 ? -3*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.1f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + #462 flash wireless control 0=> 'Off' 1=> 'Optical AWL' + #464 => { Name => 'FlashRemoteControl', PrintConv => \%flashRemoteControlZ7, Unknown => 1 }, + #476 => { Name => 'FlashWirelessOption', PrintConv => \%flashWirelessOptionZ7, Unknown => 1 }, + 548 => { Name => 'AFAreaMode', RawConv => '$$self{AFAreaMode} = $val', PrintConv => \%aFAreaModeZ9}, + 550 => { Name => 'VRMode', PrintConv => \%vRModeZ9}, + 554 => { + Name => 'BracketSet', + RawConv => '$$self{BracketSet} = $val', + PrintConv => \%bracketSetZ9, + }, + 556 => { + Name => 'BracketProgram', + Condition => '$$self{BracketSet} < 3', + Notes => 'AE and/or Flash Bracketing', + PrintConv => \%bracketProgramZ9, + }, + 558 => { + Name => 'BracketIncrement', + Condition => '$$self{BracketSet} < 3', + Notes => 'AE and/or Flash Bracketing', + PrintConv => \%bracketIncrementZ9, + }, + 570 => { Name => 'HDR', RawConv => '$$self{HDR} = $val', PrintConv => \%multipleExposureModeZ9 }, + 576 => { Name => 'SecondarySlotFunction', PrintConv => \%secondarySlotFunctionZ9 }, + 582 => { Name => 'HDRLevel', Condition => '$$self{HDR} ne 0', PrintConv => \%hdrLevelZ8 }, + 586 => { Name => 'Slot2JpgSize', PrintConv => { 0 => 'Large (8256x5504)', 1 => 'Medium (6192x4128)', 2 => 'Small (4128x2752)' }, Unknown => 1}, + 592 => { Name => 'DXCropAlert', PrintConv => \%offOn }, + 594 => { Name => 'SubjectDetection', PrintConv => \%subjectDetectionZ9 }, + 596 => { + Name => 'DynamicAFAreaSize', + Condition => '$$self{AFAreaMode} == 2', + RawConv => '$$self{DynamicAFAreaSize} = $val', + PrintConv => \%dynamicAfAreaModesZ9, + }, + 636 => { Name => 'HighFrequencyFlickerReductionShooting', PrintConv => \%offOn, Unknown => 1 }, # new with firmware 3.0 + 646 => { + Name => 'MovieImageArea', + Unknown => 1, + Mask => 0x01, # without the mask 4 => 'FX' 5 => DX only the 2nd Z-series field encountered with a mask. + PrintConv => \%imageAreaZ9b, + }, + 656 => { Name => 'MovieType', PrintConv => \%movieTypeZ9, Unknown => 1 }, + 658 => { + Name => 'MovieISOAutoHiLimit', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 660 => { Name => 'MovieISOAutoControlManualMode', PrintConv => \%offOn, Unknown => 1 }, + 662 => { + Name => 'MovieISOAutoManualMode', + Format => 'int16u', + Unknown => 1, + ValueConv => '($val-104)/8', + ValueConvInv => '8 * ($val + 104)', + PrintConv => \%iSOAutoHiLimitZ7, + }, + 736 => { Name => 'MovieActiveD-Lighting', PrintConv => \%activeDLightingZ7, Unknown => 1 }, + 738 => { Name => 'MovieHighISONoiseReduction', PrintConv => \%offLowNormalHighZ7, Unknown => 1 }, + 744 => { Name => 'MovieFlickerReduction', PrintConv => \%movieFlickerReductionZ9 }, + 746 => { Name => 'MovieMeteringMode', PrintConv => \%meteringModeZ7, Unknown => 1 }, + 748 => { Name => 'MovieFocusMode', PrintConv => \%focusModeZ7, Unknown => 1 }, + 750 => { Name => 'MovieAFAreaMode', PrintConv => \%aFAreaModeZ9 }, + 752 => { Name => 'MovieVRMode', PrintConv => \%vRModeZ9, Unknown => 1 }, + 756 => { Name => 'MovieElectronicVR', PrintConv => \%offOn, Unknown => 1 }, # distinct from MoveieVRMode + 758 => { Name => 'MovieSoundRecording', PrintConv => { 0 => 'Off', 1 => 'Auto', 2 => 'Manual' }, Unknown => 1 }, + 760 => { Name => 'MicrophoneSensitivity', Unknown => 1 }, # 1-20 + 762 => { Name => 'MicrophoneAttenuator', PrintConv => \%offOn, Unknown => 1 }, # distinct from MoveieVRMode + 764 => { Name => 'MicrophoneFrequencyResponse',PrintConv => { 0 => 'Wide Range', 1 => 'Vocal Range' }, Unknown => 1 }, + 766 => { Name => 'WindNoiseReduction', PrintConv => \%offOn, Unknown => 1 }, + 788 => { Name => 'MovieToneMap', PrintConv => \%movieToneMapZ9, Unknown => 1 }, + 794 => { Name => 'MovieFrameSize', PrintConv => \%movieFrameSizeZ9, Unknown => 1 }, + 796 => { Name => 'MovieFrameRate', PrintConv => \%movieFrameRateZ7, Unknown => 1 }, + 802 => { Name => 'MicrophoneJackPower', PrintConv => \%offOn, Unknown => 1 }, + 803 => { Name => 'MovieDXCropAlert', PrintConv => \%offOn, Unknown => 1 }, + 804 => { Name => 'MovieSubjectDetection', PrintConv => \%subjectDetectionZ9, Unknown => 1 }, + 812 => { Name => 'MovieHighResZoom', PrintConv => \%offOn, Unknown => 1 }, + 847 => { + Name => 'CustomSettingsZ9v4', + Format => 'undef[632]', + SubDirectory => { TagTable => 'Image::ExifTool::NikonCustom::SettingsZ9v4' }, + }, + 1498 => { Name => 'Language', PrintConv => \%languageZ9, Unknown => 1 }, + 1500 => { Name => 'TimeZone', PrintConv => \%timeZoneZ9 }, + 1506 => { Name => 'MonitorBrightness', PrintConv => \%monitorBrightnessZ9, Unknown => 1 }, # settings: -5 to +5. Added with firmware 3.0: Lo1, Lo2, Hi1, Hi2 + 1528 => { Name => 'AFFineTune', PrintConv => \%offOn, Unknown => 1 }, + 1532 => { Name => 'NonCPULens1FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, #should probably hide altogther if $val is 0 + 1536 => { Name => 'NonCPULens2FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1540 => { Name => 'NonCPULens3FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1544 => { Name => 'NonCPULens4FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1548 => { Name => 'NonCPULens5FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1552 => { Name => 'NonCPULens6FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1556 => { Name => 'NonCPULens7FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1560 => { Name => 'NonCPULens8FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1564 => { Name => 'NonCPULens9FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1568 => { Name => 'NonCPULens10FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1572 => { Name => 'NonCPULens11FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1576 => { Name => 'NonCPULens12FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1580 => { Name => 'NonCPULens13FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1584 => { Name => 'NonCPULens14FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1588 => { Name => 'NonCPULens15FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1592 => { Name => 'NonCPULens16FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1596 => { Name => 'NonCPULens17FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1600 => { Name => 'NonCPULens18FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1604 => { Name => 'NonCPULens19FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1608 => { Name => 'NonCPULens20FocalLength', Format => 'int16s', PrintConv => 'sprintf("%.1fmm",$val/10)', Unknown => 1}, + 1612 => { Name => 'NonCPULens1MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, #non-CPU aperture interface, values and storage differ from the Z8 + 1616 => { Name => 'NonCPULens2MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1620 => { Name => 'NonCPULens3MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1624 => { Name => 'NonCPULens4MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1628 => { Name => 'NonCPULens5MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1632 => { Name => 'NonCPULens6MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1636 => { Name => 'NonCPULens7MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1640 => { Name => 'NonCPULens8MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1644 => { Name => 'NonCPULens9MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1648 => { Name => 'NonCPULens10MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1652 => { Name => 'NonCPULens11MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1656 => { Name => 'NonCPULens12MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1660 => { Name => 'NonCPULens13MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1664 => { Name => 'NonCPULens14MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1668 => { Name => 'NonCPULens15MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1672 => { Name => 'NonCPULens16MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1676 => { Name => 'NonCPULens17MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1680 => { Name => 'NonCPULens18MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1684 => { Name => 'NonCPULens19MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1688 => { Name => 'NonCPULens20MaxAperture', Format => 'int16s', PrintConv => 'sprintf("f/%.1f",$val/100)', Unknown => 1}, + 1704 => { Name => 'HDMIOutputResolution', PrintConv => \%hDMIOutputResolutionZ9 }, + 1717 => { Name => 'SetClockFromLocationData', PrintConv => \%offOn, Unknown => 1 }, + 1724 => { Name => 'AirplaneMode', PrintConv => \%offOn, Unknown => 1 }, + 1725 => { Name => 'EmptySlotRelease', PrintConv => { 0 => 'Disable Release', 1 => 'Enable Release' }, Unknown => 1 }, + 1760 => { Name => 'EnergySavingMode', PrintConv => \%offOn, Unknown => 1 }, + 1784 => { Name => 'RecordLocationData', PrintConv => \%offOn, Unknown => 1 }, + 1788 => { Name => 'USBPowerDelivery', PrintConv => \%offOn, Unknown => 1 }, + 1797 => { Name => 'SensorShield', PrintConv => { 0 => 'Stays Open', 1 => 'Closes' }, Unknown => 1 }, + 1862 => { + Name => 'AutoCapturePreset', + PrintConv => { + 0 => '1', + 1 => '2', + 2 => '3', + 3 => '4', + 4 => '5', + }, + }, + 1864 => { + Name => 'FocusShiftAutoReset', + Unknown => 1, + PrintConv => \%offOn, + Condition => '$$self{ShutterMode} and $$self{ShutterMode} ne 96 and $$self{FocusShiftShooting} > 0', #not valid for C30/C60/C120 + }, + 1922 => { #CSd4-a + Name => 'PreReleaseBurstLength', + PrintConv => { + 0 => 'None', + 1 => '0.3 Sec', + 2 => '0.5 Sec', + 3 => '1 Sec', + }, + }, + 1924 => { #CSd4-b + Name => 'PostReleaseBurstLength', + PrintConv => { + 0 => '1 Sec', + 1 => '2 Sec', + 2 => '3 Sec', + 3 => 'Max', + }, + }, + 1938 => { Name => 'VerticalISOButton', %buttonsZ9}, #CSf2 + 1940 => { Name => 'ExposureCompensationButton', %buttonsZ9}, #CSf2 + 1942 => { Name => 'ISOButton', %buttonsZ9}, #CSf2 + 2002 => { Name => 'ViewModeShowEffectsOfSettings', PrintConv => { 0=>'Always', 1=> 'Only When Flash Not Used'}, Unknown => 1 }, #CSd9-a + 2004 => { Name => 'DispButton', %buttonsZ9}, #CSf2 + 2048 => { #CSd6 + Name => 'ExposureDelay', + Format => 'fixed32u', + PrintConv => '$val ? sprintf("%.1f sec",$val/1000) : "Off"', + }, + 2056 => { Name => 'PlaybackButton', %buttonsZ9}, #CSf2 + 2058 => { Name => 'WBButton', %buttonsZ9}, #CSf2 + 2060 => { Name => 'BracketButton', %buttonsZ9}, #CSf2 + 2062 => { Name => 'FlashModeButton', %buttonsZ9}, #CSf2 + 2064 => { Name => 'LensFunc1ButtonPlaybackMode', %buttonsZ9}, #CSf2 + 2066 => { Name => 'LensFunc2ButtonPlaybackMode', %buttonsZ9}, #CSf2 + 2068 => { Name => 'PlaybackButtonPlaybackMode', %buttonsZ9}, #CSf2 + 2070 => { Name => 'BracketButtonPlaybackMode', %buttonsZ9}, #CSf2 + 2072 => { Name => 'FlashModeButtonPlaybackMode', %buttonsZ9}, #CSf2 +); + +# Flash information (ref JD) +%Image::ExifTool::Nikon::FlashInfo0100 = ( + %binaryDataAttrs, + DATAMEMBER => [ 9.2, 15, 16 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are used by the D2H, D2Hs, D2X, D2Xs, D50, D70, D70s, D80 and + D200. + }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'FlashInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 4 => { #PH + Name => 'FlashSource', + PrintConv => { + 0 => 'None', + 1 => 'External', + 2 => 'Internal', + }, + }, + # 5 - values: 46,48,50,54,78 + 6 => { + Format => 'int8u[2]', + Name => 'ExternalFlashFirmware', + SeparateTable => 'FlashFirmware', + PrintConv => \%flashFirmware, + }, + 8 => { + Name => 'ExternalFlashFlags', + PrintConv => { 0 => '(none)', + BITMASK => { + 0 => 'Fired', #28 + 2 => 'Bounce Flash', #PH + 4 => 'Wide Flash Adapter', + 5 => 'Dome Diffuser', #28 + }, + }, + }, + 9.1 => { + Name => 'FlashCommanderMode', + Mask => 0x80, + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 9.2 => { + Name => 'FlashControlMode', + Mask => 0x7f, + DataMember => 'FlashControlMode', + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 10 => [ + { + Name => 'FlashOutput', + Condition => '$$self{FlashControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashCompensation', + Format => 'int8s', + Priority => 0, + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + ], + 11 => { + Name => 'FlashFocalLength', + RawConv => '$val ? $val : undef', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 12 => { + Name => 'RepeatingFlashRate', + RawConv => '$val ? $val : undef', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 13 => { + Name => 'RepeatingFlashCount', + RawConv => '$val ? $val : undef', + }, + 14 => { #PH + Name => 'FlashGNDistance', + SeparateTable => 1, + PrintConv => \%flashGNDistance, + }, + 15 => { + Name => 'FlashGroupAControlMode', + Mask => 0x0f, + DataMember => 'FlashGroupAControlMode', + RawConv => '$$self{FlashGroupAControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 16 => { + Name => 'FlashGroupBControlMode', + Mask => 0x0f, + DataMember => 'FlashGroupBControlMode', + RawConv => '$$self{FlashGroupBControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 17 => [ + { + Name => 'FlashGroupAOutput', + Condition => '$$self{FlashGroupAControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupACompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 18 => [ + { + Name => 'FlashGroupBOutput', + Condition => '$$self{FlashGroupBControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupBCompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], +); + +# Flash information for D40, D40x, D3 and D300 (ref JD) +%Image::ExifTool::Nikon::FlashInfo0102 = ( + %binaryDataAttrs, + DATAMEMBER => [ 9.2, 16.1, 17.1, 17.2 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are used by the D3 (firmware 1.x), D40, D40X, D60 and D300 + (firmware 1.00). + }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'FlashInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 4 => { #PH + Name => 'FlashSource', + PrintConv => { + 0 => 'None', + 1 => 'External', + 2 => 'Internal', + }, + }, + # 5 - values: 46,48,50,54,78 + 6 => { + Format => 'int8u[2]', + Name => 'ExternalFlashFirmware', + SeparateTable => 'FlashFirmware', + PrintConv => \%flashFirmware, + }, + 8 => { + Name => 'ExternalFlashFlags', + PrintConv => { BITMASK => { + 0 => 'Fired', #28 + 2 => 'Bounce Flash', #PH + 4 => 'Wide Flash Adapter', + 5 => 'Dome Diffuser', #28 + }}, + }, + 9.1 => { + Name => 'FlashCommanderMode', + Mask => 0x80, + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 9.2 => { + Name => 'FlashControlMode', + Mask => 0x7f, + DataMember => 'FlashControlMode', + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 10 => [ + { + Name => 'FlashOutput', + Condition => '$$self{FlashControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashCompensation', + # this is the compensation from the camera (0x0012) for "Built-in" FlashType, or + # the compensation from the external unit (0x0017) for "Optional" FlashType - PH + Format => 'int8s', + Priority => 0, + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + ], + 12 => { + Name => 'FlashFocalLength', + RawConv => '$val ? $val : undef', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 13 => { + Name => 'RepeatingFlashRate', + RawConv => '$val ? $val : undef', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 14 => { + Name => 'RepeatingFlashCount', + RawConv => '$val ? $val : undef', + }, + 15 => { #PH + Name => 'FlashGNDistance', + SeparateTable => 1, + PrintConv => \%flashGNDistance, + }, + 16.1 => { + Name => 'FlashGroupAControlMode', + Mask => 0x0f, + Notes => 'note: group A tags may apply to the built-in flash settings for some models', + DataMember => 'FlashGroupAControlMode', + RawConv => '$$self{FlashGroupAControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 17.1 => { + Name => 'FlashGroupBControlMode', + Mask => 0xf0, + Notes => 'note: group B tags may apply to group A settings for some models', + DataMember => 'FlashGroupBControlMode', + RawConv => '$$self{FlashGroupBControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 17.2 => { #PH + Name => 'FlashGroupCControlMode', + Mask => 0x0f, + Notes => 'note: group C tags may apply to group B settings for some models', + DataMember => 'FlashGroupCControlMode', + RawConv => '$$self{FlashGroupCControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 18 => [ + { + Name => 'FlashGroupAOutput', + Condition => '$$self{FlashGroupAControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupACompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 19 => [ + { + Name => 'FlashGroupBOutput', + Condition => '$$self{FlashGroupBControlMode} >= 0x60', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupBCompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 20 => [ #PH + { + Name => 'FlashGroupCOutput', + Condition => '$$self{FlashGroupCControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupCCompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], +); + +# Flash information for D90 and D700 (ref PH) +# - confirmed in detail for D800 (0105) - PH +%Image::ExifTool::Nikon::FlashInfo0103 = ( + %binaryDataAttrs, + DATAMEMBER => [ 9.2, 17.1, 18.1, 18.2 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are used by the D3 (firmware 2.x), D3X, D3S, D4, D90, D300 + (firmware 1.10), D300S, D600, D700, D800, D3000, D3100, D3200, D5000, D5100, + D5200, D7000. + }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'FlashInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 4 => { #PH + Name => 'FlashSource', + PrintConv => { + 0 => 'None', + 1 => 'External', + 2 => 'Internal', + }, + }, + # 5 - values: 46,48,50,54,78 + 6 => { + Format => 'int8u[2]', + Name => 'ExternalFlashFirmware', + SeparateTable => 'FlashFirmware', + PrintConv => \%flashFirmware, + }, + 8 => { + Name => 'ExternalFlashFlags', + PrintConv => { BITMASK => { + 0 => 'Fired', #28 + 2 => 'Bounce Flash', #PH + 4 => 'Wide Flash Adapter', + 5 => 'Dome Diffuser', #28 + }}, + }, + 9.1 => { + Name => 'FlashCommanderMode', + Mask => 0x80, + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 9.2 => { + Name => 'FlashControlMode', + Mask => 0x7f, + DataMember => 'FlashControlMode', + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 10 => [ + { + Name => 'FlashOutput', + Condition => '$$self{FlashControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashCompensation', + # this is the compensation from the camera (0x0012) for "Built-in" FlashType, or + # the compensation from the external unit (0x0017) for "Optional" FlashType - PH + Format => 'int8s', + Priority => 0, + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + ], + 12 => { #JD + Name => 'FlashFocalLength', + RawConv => '($val and $val != 255) ? $val : undef', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 13 => { #JD + Name => 'RepeatingFlashRate', + RawConv => '($val and $val != 255) ? $val : undef', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 14 => { #JD + Name => 'RepeatingFlashCount', + RawConv => '($val and $val != 255) ? $val : undef', + }, + 15 => { #28 + Name => 'FlashGNDistance', + SeparateTable => 1, + PrintConv => \%flashGNDistance, + }, + 16 => { #28 + Name => 'FlashColorFilter', + SeparateTable => 1, + PrintConv => \%flashColorFilter, + }, + 17.1 => { + Name => 'FlashGroupAControlMode', + Mask => 0x0f, + Notes => 'note: group A tags may apply to the built-in flash settings for some models', + DataMember => 'FlashGroupAControlMode', + RawConv => '$$self{FlashGroupAControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 18.1 => { + Name => 'FlashGroupBControlMode', + Mask => 0xf0, + Notes => 'note: group B tags may apply to group A settings for some models', + DataMember => 'FlashGroupBControlMode', + RawConv => '$$self{FlashGroupBControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 18.2 => { #PH + Name => 'FlashGroupCControlMode', + Mask => 0x0f, + Notes => 'note: group C tags may apply to group B settings for some models', + DataMember => 'FlashGroupCControlMode', + RawConv => '$$self{FlashGroupCControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 0x13 => [ + { + Name => 'FlashGroupAOutput', + Condition => '$$self{FlashGroupAControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupACompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 0x14 => [ + { + Name => 'FlashGroupBOutput', + Condition => '$$self{FlashGroupBControlMode} >= 0x60', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupBCompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 0x15 => [ #PH + { + Name => 'FlashGroupCOutput', + Condition => '$$self{FlashGroupCControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupCCompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 0x1b => { #PH + Name => 'ExternalFlashCompensation', + Format => 'int8s', + Priority => 0, + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x1d => { #PH + Name => 'FlashExposureComp3', + Format => 'int8s', + # (does not include the built-in compensation for FlashType "Built-in,TTL&Comdr.") + Notes => 'does not include the effect of flash bracketing', + Priority => 0, + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x27 => { #PH (same as ShotInfoD800 0x4d2 but also valid for repeating flash) + Name => 'FlashExposureComp4', + Format => 'int8s', + Notes => 'includes the effect of flash bracketing. Valid for repeating flash', + Priority => 0, + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + # 0x2b - related to flash power (PH, D800, 96=full,62=1/4,2=1/128) +); + +# Flash information for the D7100 (ref PH) +# (this is VERY similar to FlashInfo0107, but there are a few differences that +# would need to be resolved if these two definitions were to be combined) +%Image::ExifTool::Nikon::FlashInfo0106 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 9.2, 17.1, 18.1, 18.2 ], + NOTES => 'These tags are used by the Df, D610, D3300, D5300, D7100 and Coolpix A.', + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'FlashInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 4 => { + Name => 'FlashSource', + PrintConv => { + 0 => 'None', + 1 => 'External', + 2 => 'Internal', + }, + }, + 6 => { + Format => 'int8u[2]', + Name => 'ExternalFlashFirmware', + SeparateTable => 'FlashFirmware', + PrintConv => \%flashFirmware, + }, + 8 => { + Name => 'ExternalFlashFlags', + PrintConv => { BITMASK => { + 0 => 'Fired', + 2 => 'Bounce Flash', + 4 => 'Wide Flash Adapter', + 5 => 'Dome Diffuser', # (NC, not true for the SB-910 anyway) + # 7 - ? (set for SB-910 when an advanced option is used, eg. diff pattern) + }}, + }, + 9.1 => { # (NC) + Name => 'FlashCommanderMode', + Mask => 0x80, + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 9.2 => { + Name => 'FlashControlMode', + Mask => 0x7f, + DataMember => 'FlashControlMode', + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + # 10 - similar to 0x27 but zero sometimes when I don't think it should be + 12 => { + Name => 'FlashFocalLength', + Notes => 'only valid if flash pattern is "Standard Illumination"', + RawConv => '($val and $val != 255) ? $val : undef', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 13 => { + Name => 'RepeatingFlashRate', + RawConv => '($val and $val != 255) ? $val : undef', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 14 => { + Name => 'RepeatingFlashCount', + RawConv => '($val and $val != 255) ? $val : undef', + }, + 15 => { # (NC) + Name => 'FlashGNDistance', + SeparateTable => 1, + PrintConv => \%flashGNDistance, + }, + 16 => { + Name => 'FlashColorFilter', + SeparateTable => 1, + PrintConv => \%flashColorFilter, + }, + 17.1 => { + Name => 'FlashGroupAControlMode', + Mask => 0x0f, + DataMember => 'FlashGroupAControlMode', + RawConv => '$$self{FlashGroupAControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 18.1 => { + Name => 'FlashGroupBControlMode', + Mask => 0xf0, + DataMember => 'FlashGroupBControlMode', + RawConv => '$$self{FlashGroupBControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 18.2 => { + Name => 'FlashGroupCControlMode', + Mask => 0x0f, + DataMember => 'FlashGroupCControlMode', + RawConv => '$$self{FlashGroupCControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + # 0x13 - same as 0x28 but not zero when flash group A is Off + # 0x14 - same as 0x29 but not zero when flash group B is Off + # 0x15 - same as 0x2a but not zero when flash group B is Off + # 0x1a - changes with illumination pattern (0=normal,1=narrow,2=wide), but other values seen + # 0x26 - changes when diffuser is used + 0x27 => [ + { + Name => 'FlashOutput', + Condition => '$$self{FlashControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashCompensation', + Format => 'int8s', + Priority => 0, + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + ], + 0x28 => [ + { + Name => 'FlashGroupAOutput', + Condition => '$$self{FlashGroupAControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupACompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 0x29 => [ + { + Name => 'FlashGroupBOutput', + Condition => '$$self{FlashGroupBControlMode} >= 0x60', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupBCompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 0x2a => [ + { + Name => 'FlashGroupCOutput', + Condition => '$$self{FlashGroupCControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupCCompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], +); + +# Flash information for the D4S/D750/D810/D5500/D7200 (0107) +# and D5/D500/D850/D3400 (0108) (ref 28) +%Image::ExifTool::Nikon::FlashInfo0107 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 17.1, 18.1, 18.2 ], + NOTES => q{ + These tags are used by the D4S, D750, D810, D5500, D7200 (FlashInfoVersion + 0107) and the D5, D500, D850 and D3400 (FlashInfoVersion 0108). + }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'FlashInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 4 => { + Name => 'FlashSource', + PrintConv => { + 0 => 'None', + 1 => 'External', + 2 => 'Internal', + }, + }, + 6 => { + Format => 'int8u[2]', + Name => 'ExternalFlashFirmware', + SeparateTable => 'FlashFirmware', + PrintConv => \%flashFirmware, + }, + 8.1 => { + Name => 'ExternalFlashZoomOverride', + Mask => 0x80, + Notes => 'indicates that the user has overridden the flash zoom distance', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 8.2 => { + Name => 'ExternalFlashStatus', + Mask => 0x01, + PrintConv => { + 0 => 'Flash Not Attached', + 1 => 'Flash Attached', + }, + }, + 9.1 => { + Name => 'ExternalFlashReadyState', + Mask => 0x07, + PrintConv => { + 0 => 'n/a', + 1 => 'Ready', + 6 => 'Not Ready', + }, + }, + 10 => { + Name => 'FlashCompensation', + # this is the compensation from the camera (0x0012) for "Built-in" FlashType, or + # the compensation from the external unit (0x0017) for "Optional" FlashType - PH + Format => 'int8s', + Priority => 0, + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 12 => { + Name => 'FlashFocalLength', + Notes => 'only valid if flash pattern is "Standard Illumination"', #illumination pattern no no supported starting with the SB-910 + RawConv => '($val and $val != 255) ? $val : undef', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 13 => { + Name => 'RepeatingFlashRate', + RawConv => '($val and $val != 255) ? $val : undef', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 14 => { + Name => 'RepeatingFlashCount', + RawConv => '($val and $val != 255) ? $val : undef', + }, + 15 => { + Name => 'FlashGNDistance', + SeparateTable => 1, + PrintConv => \%flashGNDistance, + }, + 17.1 => { #PH + Name => 'FlashGroupAControlMode', + Mask => 0x0f, + Notes => 'note: group A tags may apply to the built-in flash settings for some models', + DataMember => 'FlashGroupAControlMode', + RawConv => '$$self{FlashGroupAControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 18.1 => { #PH + Name => 'FlashGroupBControlMode', + Mask => 0xf0, + Notes => 'note: group B tags may apply to group A settings for some models', + DataMember => 'FlashGroupBControlMode', + RawConv => '$$self{FlashGroupBControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 18.2 => { #PH + Name => 'FlashGroupCControlMode', + Mask => 0x0f, + Notes => 'note: group C tags may apply to group B settings for some models', + DataMember => 'FlashGroupCControlMode', + RawConv => '$$self{FlashGroupCControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + # 0x13 - very similar to 0x28 + # 0x18 or 0x19 may indicate flash illumination pattern (Standard, Center-weighted, Even) + 0x28 => [ #PH + { + Name => 'FlashGroupAOutput', + Condition => '$$self{FlashGroupAControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupACompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 0x29 => [ #PH + { + Name => 'FlashGroupBOutput', + Condition => '$$self{FlashGroupBControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupBCompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 0x2a => [ #PH + { + Name => 'FlashGroupCOutput', + Condition => '$$self{FlashGroupCControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupCCompensation', + Format => 'int8s', + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], +); + +# Flash information for the Z7II (ref 28) +# (likey similar to FlashInfo010 and FlashInfo0108 with addition of support for radio controlled units such as the SB-5000? +%Image::ExifTool::Nikon::FlashInfo0300 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 9.2, 17.1, 18.1, 18.2 ], + 0 => { + Name => 'FlashInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 4 => { + Name => 'FlashSource', + PrintConv => { + 0 => 'None', + 1 => 'External', + 2 => 'Internal', + }, + }, + 6 => { + Name => 'ExternalFlashFirmware', + Format => 'int8u[2]', + SeparateTable => 'FlashFirmware', + PrintConv => \%flashFirmware, + }, + 8 => { + Name => 'ExternalFlashFlags', + PrintConv => { BITMASK => { + 0 => 'Flash Ready', #flash status is 'Not Ready' when this bit is off and FlashSource is non-zero + # 1 - ? (observed with SB-900) + 2 => 'Bounce Flash', + 4 => 'Wide Flash Adapter', + 7 => 'Zoom Override', #override takes place when the Wide Flash Adapter is dropped in place and/or the zoom level is overriden on via flash menu + }}, + }, + 9.1 => { + Name => 'FlashCommanderMode', + Mask => 0x80, + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 9.2 => { + Name => 'FlashControlMode', + Mask => 0x7f, + DataMember => 'FlashControlMode', + RawConv => '$$self{FlashControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 10 => { + Name => 'FlashCompensation', + # this is the compensation from the camera (0x0012) for "Built-in" FlashType, or + # the compensation from the external unit (0x0017) for "Optional" FlashType - PH + Condition => '$$self{FlashControlMode} == 0x01 or $$self{FlashControlMode} == 0x02', #only valid for TTL and TTL-BL modes + Format => 'int8s', + Priority => 0, + ValueConv => '-$val/6', + ValueConvInv => '-6 * $val', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 13 => { + Name => 'RepeatingFlashRate', + RawConv => '($val and $val != 255) ? $val : undef', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 14 => { + Name => 'RepeatingFlashCount', + RawConv => '($val and $val != 255) ? $val : undef', + }, + 15 => { + Name => 'FlashGNDistance', + SeparateTable => 1, + PrintConv => \%flashGNDistance, + }, + 16 => { + Name => 'FlashColorFilter', + SeparateTable => 1, + PrintConv => \%flashColorFilter, + }, + 17.1 => { #PH + Name => 'FlashGroupAControlMode', + Mask => 0x0f, + Notes => 'note: group A tags may apply to the built-in flash settings for some models', + DataMember => 'FlashGroupAControlMode', + RawConv => '$$self{FlashGroupAControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 18.1 => { #PH + Name => 'FlashGroupBControlMode', + Mask => 0xf0, + Notes => 'note: group B tags may apply to group A settings for some models', + DataMember => 'FlashGroupBControlMode', + RawConv => '$$self{FlashGroupBControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 18.2 => { #PH + Name => 'FlashGroupCControlMode', + Mask => 0x0f, + Notes => 'note: group C tags may apply to group B settings for some models', + DataMember => 'FlashGroupCControlMode', + RawConv => '$$self{FlashGroupCControlMode} = $val', + PrintConv => \%flashControlMode, + SeparateTable => 'FlashControlMode', + }, + 33 => { + Name => 'FlashOutput', + Condition => '$$self{FlashControlMode} >= 0x06', #only valid for M mode + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + 37 => { + Name => 'FlashIlluminationPattern', + PrintConv => { + 0 => 'Standard', + 1 => 'Center-weighted', + 2 => 'Even', + }, + }, + 38 => { + Name => 'FlashFocalLength', + Notes => 'only valid if flash pattern is "Standard Illumination"', + RawConv => '($val and $val != 255) ? $val : undef', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 40 => [ #PH + { + Name => 'FlashGroupAOutput', + Condition => '$$self{FlashGroupAControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupACompensation', + Format => 'int8s', + ValueConv => '-($val-2)/6', + ValueConvInv => '-6 * $val + 2', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 41 => [ #PH + { + Name => 'FlashGroupBOutput', + Condition => '$$self{FlashGroupBControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupBCompensation', + Format => 'int8s', + ValueConv => '-($val-2)/6', + ValueConvInv => '-6 * $val + 2', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], + 42 => [ #PH + { + Name => 'FlashGroupCOutput', + Condition => '$$self{FlashGroupCControlMode} >= 0x06', + ValueConv => '2 ** (-$val/6)', + ValueConvInv => '$val>0 ? -6*log($val)/log(2) : 0', + PrintConv => '$val>0.99 ? "Full" : sprintf("%.0f%%",$val*100)', + PrintConvInv => '$val=~/(\d+)/ ? $1/100 : 1', + }, + { + Name => 'FlashGroupCCompensation', + Format => 'int8s', + ValueConv => '-($val-2)/6', + ValueConvInv => '-6 * $val + 2', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + ], +); +# Unknown Flash information +%Image::ExifTool::Nikon::FlashInfoUnknown = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'FlashInfoVersion', + Format => 'string[4]', + Writable => 0, + }, +); + +# Multi exposure / image overlay information (ref PH) +%Image::ExifTool::Nikon::MultiExposure = ( + %binaryDataAttrs, + FORMAT => 'int32u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'MultiExposureVersion', + Format => 'string[4]', + Writable => 0, + }, + 1 => { + Name => 'MultiExposureMode', + PrintConv => { + 0 => 'Off', + 1 => 'Multiple Exposure', + 2 => 'Image Overlay', + 3 => 'HDR', #31 + }, + }, + 2 => 'MultiExposureShots', + 3 => { + Name => 'MultiExposureAutoGain', + PrintConv => \%offOn, + }, +); + +# Multi exposure2 / image overlay information (ref 39) +%Image::ExifTool::Nikon::MultiExposure2 = ( + %binaryDataAttrs, + FORMAT => 'int32u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'MultiExposureVersion', + Format => 'string[4]', + Writable => 0, + }, + 1 => { + Name => 'MultiExposureMode', + PrintConv => { + 0 => 'Off', + 1 => 'Multiple Exposure', + 3 => 'HDR', + }, + }, + 2 => 'MultiExposureShots', + 3 => { + Name => 'MultiExposureOverlayMode', + PrintConv => { + 0 => 'Add', + 1 => 'Average', + 2 => 'Light', + 3 => 'Dark', + }, + }, +); + +# HDR information (ref 32) +%Image::ExifTool::Nikon::HDRInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + # NOTE: Must set ByteOrder in SubDirectory if any multi-byte integer tags added + 0 => { + Name => 'HDRInfoVersion', + Format => 'string[4]', + Writable => 0, + }, + 4 => { + Name => 'HDR', + PrintConv => { + 0 => 'Off', + 1 => 'On (normal)', + 48 => 'Auto', #PH (NC) + }, + }, + 5 => { + Name => 'HDRLevel', + PrintConv => { + 0 => 'Auto', + 1 => '1 EV', + 2 => '2 EV', + 3 => '3 EV', + # 5 - seen for 1J4 + 255 => 'n/a', #PH + }, + }, + 6 => { + Name => 'HDRSmoothing', + PrintConv => { + 0 => 'Off', + 1 => 'Normal', + 2 => 'Low', + 3 => 'High', + 48 => 'Auto', #PH (NC) + 255 => 'n/a', #PH + }, + }, + 7 => { #PH (P330, HDRInfoVersion=0101) + Name => 'HDRLevel2', + PrintConv => { + 0 => 'Auto', + 1 => '1 EV', + 2 => '2 EV', + 3 => '3 EV', + 255 => 'n/a', + }, + }, +); + +# ref 39 (Z9) +%Image::ExifTool::Nikon::HDRInfo2 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => { + Name => 'HDRInfoVersion', # 0200 + Format => 'string[4]', + Writable => 0, + }, + 4 => { + Name => 'HDR', + PrintConv => { + 0 => 'Off', + 1 => 'On (normal)', + }, + }, + 5 => { + Name => 'HDRLevel', + PrintConv => { + 0 => 'n/a', + 1 => 'Normal', + 2 => 'Low', + 3 => 'High', + 4 => 'High+', + 5 => 'Auto', + }, + }, +); + +# location information (ref PH) +%Image::ExifTool::Nikon::LocationInfo = ( + %binaryDataAttrs, + DATAMEMBER => [ 4 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Location' }, + NOTES => 'Tags written by some Nikon GPS-equipped cameras like the AW100.', + 0 => { + Name => 'LocationInfoVersion', + Format => 'undef[4]', + }, + 4 => { + Name => 'TextEncoding', + DataMember => 'TextEncoding', + RawConv => q{ + $$self{TextEncoding} = $Image::ExifTool::Nikon::nikonTextEncoding{$val} if $val; + return $val; + }, + PrintConv => \%Image::ExifTool::Nikon::nikonTextEncoding, + }, + # (the CountryCode and Location tag names chosen to correspond with XMP::iptcCore) + 5 => { + Name => 'CountryCode', + Format => 'undef[3]', + ValueConv => '$val=~s/\0.*//s; $val', # truncate at null + ValueConvInv => '$val', + }, + 8 => 'POILevel', #forum5782 + 9 => { + Name => 'Location', + Format => 'undef[70]', + RawConv => '$$self{TextEncoding} ? $self->Decode($val,$$self{TextEncoding},"MM") : $val', + RawConvInv => '$$self{TextEncoding} ? $self->Encode($val,$$self{TextEncoding},"MM") : $val', + }, +); + +# MakerNotes0x51 - compression info for Z8 and Z9 +%Image::ExifTool::Nikon::MakerNotes0x51 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes' }, + 0 => { + Name => 'FirmwareVersion51', + Format => 'string[8]', + Writable => 0, + }, + 10 => { + Name => 'NEFCompression', + Writable => 'int16u', + SeparateTable => 'NEFCompression', + PrintConv => \%nefCompression, + }, +); + +# MakerNotes0x56 - burst info for Z9 +%Image::ExifTool::Nikon::MakerNotes0x56 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes' }, + 0 => { + Name => 'FirmwareVersion', + Format => 'string[4]', + Writable => 0, + }, + 4 => { + Name => 'BurstGroupID', #all frames shot within a burst (using CL/CH/C30/C60/C120) will share the same BurstGroupID. Value will be > 0 for all images shot in continuous modes. 0 for single-frame. + Format => 'int16u' + }, +); + +# extra info found in IFD0 of NEF files (ref PH, Z6/Z7) +%Image::ExifTool::Nikon::NEFInfo = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + As-yet unknown information found in SubIFD1 tag 0xc7d5 of NEF images from + cameras such as the Z6 and Z7, and NRW images from some Coolpix cameras. + }, + # 0x01 - undef[12] + # 0x02 - undef[148] + # 0x03 - undef[284] + # 0x04 - undef[148,212] + # 0x05 - undef[84] (barrel distortion params at offsets 0x14,0x1c,0x24, ref 28) + # 0x06 - undef[116] (vignette correction params at offsets 0x24,0x34,0x44, ref 28) + # 0x07 - undef[104] + # 0x08 - undef[24] + # 0x09 - undef[36] +); + +# tags in Nikon QuickTime videos (PH - observations with Coolpix S3) +# (similar information in Kodak,Minolta,Nikon,Olympus,Pentax and Sanyo videos) +%Image::ExifTool::Nikon::MOV = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => q{ + This information is found in MOV and QT videos from some Nikon cameras. + }, + 0x00 => { + Name => 'Make', + Format => 'string[24]', + }, + 0x18 => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[8]', + }, + # (01 00 at offset 0x20) + 0x26 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val ? 10 / $val : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x2a => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x32 => { + Name => 'ExposureCompensation', + Format => 'rational64s', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + 0x44 => { + Name => 'WhiteBalance', + Format => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Shade', + 3 => 'Fluorescent', #2 + 4 => 'Tungsten', + 5 => 'Manual', + }, + }, + 0x48 => { + Name => 'FocalLength', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + }, + 0xaf => { + Name => 'Software', + Format => 'string[16]', + }, + 0xdf => { # (this is a guess ... could also be offset 0xdb) + Name => 'ISO', + Format => 'int16u', + RawConv => '$val < 50 ? undef : $val', # (not valid for Coolpix L10) + }, +); + +# Nikon metadata in AVI videos (PH) +%Image::ExifTool::Nikon::AVI = ( + NOTES => 'Nikon-specific RIFF tags found in AVI videos.', + GROUPS => { 0 => 'MakerNotes', 2 => 'Video' }, + nctg => { + Name => 'NikonTags', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::AVITags' }, + }, + ncth => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + ncvr => { + Name => 'NikonVers', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::AVIVers' }, + }, + ncvw => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + RawConv => 'length($val) ? $val : undef', + Binary => 1, + }, +); + +# version information in AVI videos (PH) +%Image::ExifTool::Nikon::AVIVers = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Video' }, + PROCESS_PROC => \&ProcessNikonAVI, + FORMAT => 'string', + 0x01 => 'MakerNoteType', + 0x02 => { + Name => 'MakerNoteVersion', + Format => 'int8u', + ValueConv => 'my @a = reverse split " ", $val; join ".", @a;', + }, +); + +# tags in AVI videos (PH) +%Image::ExifTool::Nikon::AVITags = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessNikonAVI, + FORMAT => 'string', + NOTES => q{ + These tags and the AVIVer tags below are found in proprietary-format records + of Nikon AVI videos. + }, + 0x03 => 'Make', + 0x04 => 'Model', + 0x05 => { + Name => 'Software', + Format => 'undef', + ValueConv => '$val =~ tr/\0//d; $val', + }, + 0x06 => 'Equipment', # "NIKON DIGITAL CAMERA" + 0x07 => { # (guess) + Name => 'Orientation', + Format => 'int16u', + Groups => { 2 => 'Image' }, + PrintConv => \%Image::ExifTool::Exif::orientation, + }, + 0x08 => { + Name => 'ExposureTime', + Format => 'rational64u', + Groups => { 2 => 'Image' }, + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x09 => { + Name => 'FNumber', + Format => 'rational64u', + Groups => { 2 => 'Image' }, + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x0a => { + Name => 'ExposureCompensation', + Format => 'rational64s', + Groups => { 2 => 'Image' }, + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + 0x0b => { + Name => 'MaxApertureValue', + Format => 'rational64u', + ValueConv => '2 ** ($val / 2)', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x0c => { + Name => 'MeteringMode', # (guess) + Format => 'int16u', + PrintConv => { + 0 => 'Unknown', + 1 => 'Average', + 2 => 'Center-weighted average', + 3 => 'Spot', + 4 => 'Multi-spot', + 5 => 'Multi-segment', + 6 => 'Partial', + 255 => 'Other', + }, + }, + 0x0d => { # val: 0 + Name => 'Nikon_AVITags_0x000d', + Format => 'int16u', + Flags => [ 'Hidden', 'Unknown' ], + }, + 0x0e => { # val: 0 + Name => 'Nikon_AVITags_0x000e', + Format => 'int16u', + Flags => [ 'Hidden', 'Unknown' ], + }, + 0x0f => { + Name => 'FocalLength', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + }, + 0x10 => { + Name => 'XResolution', + Format => 'rational64u', + Groups => { 2 => 'Image' }, + }, + 0x11 => { + Name => 'YResolution', + Format => 'rational64u', + Groups => { 2 => 'Image' }, + }, + 0x12 => { + Name => 'ResolutionUnit', + Format => 'int16u', + Groups => { 2 => 'Image' }, + PrintConv => { + 1 => 'None', + 2 => 'inches', + 3 => 'cm', + }, + }, + 0x13 => { + Name => 'DateTimeOriginal', # (guess) + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x14 => { + Name => 'CreateDate', # (guess) + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x15 => { + Name => 'Nikon_AVITags_0x0015', + Format => 'int16u', + Flags => [ 'Hidden', 'Unknown' ], + }, + 0x16 => { + Name => 'Duration', + Format => 'rational64u', + PrintConv => '"$val s"', + }, + 0x17 => { # val: 1 + Name => 'Nikon_AVITags_0x0017', + Format => 'int16u', + Flags => [ 'Hidden', 'Unknown' ], + }, + 0x18 => 'FocusMode', + 0x19 => { # vals: -5, -2, 3, 5, 6, 8, 11, 12, 14, 20, 22 + Name => 'Nikon_AVITags_0x0019', + Format => 'int32s', + Flags => [ 'Hidden', 'Unknown' ], + }, + 0x1b => { # vals: 1 (640x480), 1.25 (320x240) + Name => 'DigitalZoom', + Format => 'rational64u', + }, + 0x1c => { # (same as Nikon_0x000a) + Name => 'Nikon_AVITags_0x001c', + Format => 'rational64u', + Flags => [ 'Hidden', 'Unknown' ], + }, + 0x1d => 'ColorMode', + 0x1e => { # string[8] - val: "AUTO" + Name => 'Sharpness', # (guess, could also be ISOSelection) + }, + 0x1f => { # string[16] - val: "AUTO" + Name => 'WhiteBalance', # (guess, could also be ImageAdjustment) + }, + 0x20 => { # string[4] - val: "OFF" + Name => 'NoiseReduction', # (guess) + }, + 0x801a => { # val: 0 (why is the 0x8000 bit set in the ID?) + Name => 'Nikon_AVITags_0x801a', + Format => 'int32s', + Flags => [ 'Hidden', 'Unknown' ], + } +); + +# Nikon NCDT atoms (ref PH) +%Image::ExifTool::Nikon::NCDT = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Nikon', 2 => 'Video' }, + NOTES => q{ + Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from + various Nikon models. + }, + NCHD => { + Name => 'MakerNoteVersion', + Format => 'undef', + ValueConv => q{ + $val =~ s/\0$//; # remove trailing null + $val =~ s/([\0-\x1f])/'.'.ord($1)/ge; + $val =~ s/\./ /; return $val; + }, + }, + NCTG => { + Name => 'NikonTags', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::NCTG' }, + }, + NCTH => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Format => 'undef', + Binary => 1, + }, + NCVW => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Format => 'undef', + Binary => 1, + }, + NCDB => { # (often 0 bytes long, or 4 null bytes) + Name => 'NikonNCDB', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::NCDB' }, + }, + NCM1 => { + Name => 'PreviewImage1', + Groups => { 2 => 'Preview' }, + Format => 'undef', + Binary => 1, + RawConv => 'length $val ? $val : undef', + }, + NCM2 => { #PH (guess - have only seen 0 bytes) + Name => 'PreviewImage2', + Groups => { 2 => 'Preview' }, + Format => 'undef', + Binary => 1, + RawConv => 'length $val ? $val : undef', + }, +); + +# Nikon NCDB tags from MOV videos (ref PH) +%Image::ExifTool::Nikon::NCDB = ( + GROUPS => { 0 => 'MakerNotes', 1 => 'Nikon', 2 => 'Video' }, + # the following probably contain encrypted data -- look into decryping these! + # OP01 - 320 bytes, starts with "0200" (D600,D610,D810,D3200,D5200) + # - 638 bytes, starts with "0200" (D7100) + # OP02 - 2048 bytes, starts with "0200" (D810) +); + +# Nikon NCTG tags from MOV videos (ref PH) +%Image::ExifTool::Nikon::NCTG = ( + PROCESS_PROC => \&ProcessNikonMOV, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are found in proprietary-format records of the NCTG atom in MOV + videos from some Nikon cameras. + }, + 0x01 => 'Make', + 0x02 => 'Model', + 0x03 => 'Software', + 0x11 => { + Name => 'CreateDate', #(guess, but matches QuickTime CreateDate) + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x12 => { + Name => 'DateTimeOriginal', #(guess, but time is 1 sec before tag 0x11) + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x13 => { + Name => 'FrameCount', + # int32u[2]: "467 0", "1038 0", "1127 0" + ValueConv => '$val =~ s/ 0$//; $val', # (not sure what the extra "0" is for) + }, + # 0x14 - int32u[2]: "0 0" + # 0x15 - int32u[2]: "0 0" + 0x16 => { + Name => 'FrameRate', + Groups => { 2 => 'Video' }, + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + # 0x17 - rational62u: same value as FrameRate + # 0x18 - int16u: 1, 2 + 0x19 => { + Name => 'TimeZone', + Groups => { 2 => 'Time' }, + }, + # 0x21 - int16u: 1, 2 + 0x22 => { + Name => 'FrameWidth', + Groups => { 2 => 'Video' }, + }, + 0x23 => { + Name => 'FrameHeight', + Groups => { 2 => 'Video' }, + }, + # 0x24 - int16u: 1, 2 + # 0x31 - int16u: 0, 1, 2 + 0x32 => { #(guess) + Name => 'AudioChannels', + Groups => { 2 => 'Audio' }, + }, + 0x33 => { + Name => 'AudioBitsPerSample', + Groups => { 2 => 'Audio' }, + }, + 0x34 => { + Name => 'AudioSampleRate', + Groups => { 2 => 'Audio' }, + }, + # 0x101 - int16u[4]: "160 120 1280 720", "160 120 3840 2160" + # 0x102 - int16u[8]: "640 360 0 0 0 0 0 0", "640 360 1920 1080 0 0 0 0" + # 0x1001 - int16s: 0 + 0x1002 => { + Name => 'NikonDateTime', #? + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + # 0x1011 - int32u: 0 + # 0x1012 - int32u: 0 + 0x1013 => { #HayoBaan + Name => 'ElectronicVR', + PrintConv => \%offOn, + }, + # 0x1014 - int16u: 1 + # 0x1021 - int32u[32]: all zeros +# +# 0x110**** tags correspond to 0x**** tags in Exif::Main +# + 0x110829a => { #34 + Name => 'ExposureTime', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x110829d => { #34 + Name => 'FNumber', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + 0x1108822 => { + Name => 'ExposureProgram', + PrintConv => { + 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', + # 9 => 'Bulb', # (non-standard Canon value) + }, + }, + 0x1109204 => { + Name => 'ExposureCompensation', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + 0x1109207 => { + Name => 'MeteringMode', + PrintConv => { + 0 => 'Unknown', + 1 => 'Average', + 2 => 'Center-weighted average', + 3 => 'Spot', + 4 => 'Multi-spot', + 5 => 'Multi-segment', + 6 => 'Partial', + 255 => 'Other', + }, + }, + 0x110920a => { #34 + Name => 'FocalLength', + PrintConv => 'sprintf("%.1f mm",$val)', + }, + 0x110a431 => 'SerialNumber', + 0x110a432 => { + Name => 'LensInfo', + PrintConv => \&Image::ExifTool::Exif::PrintLensInfo, + }, + 0x110a433 => 'LensMake', + 0x110a434 => 'LensModel', + 0x110a435 => 'LensSerialNumber', +# +# 0x120**** tags correspond to 0x**** tags in GPS::Main +# + 0x1200000 => { + Name => 'GPSVersionID', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => '$val =~ tr/ /./; $val', + }, + 0x1200001 => { + Name => 'GPSLatitudeRef', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + N => 'North', + S => 'South', + }, + }, + 0x1200002 => { + Name => 'GPSLatitude', + Groups => { 1 => 'GPS', 2 => 'Location' }, + ValueConv => q{ + require Image::ExifTool::GPS; + Image::ExifTool::GPS::ToDegrees($val); + }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', + }, + 0x1200003 => { + Name => 'GPSLongitudeRef', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + E => 'East', + W => 'West', + }, + }, + 0x1200004 => { + Name => 'GPSLongitude', + Groups => { 1 => 'GPS', 2 => 'Location' }, + ValueConv => q{ + require Image::ExifTool::GPS; + Image::ExifTool::GPS::ToDegrees($val); + }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', + }, + 0x1200005 => { + Name => 'GPSAltitudeRef', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + 0 => 'Above Sea Level', + 1 => 'Below Sea Level', + }, + }, + 0x1200006 => { + Name => 'GPSAltitude', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"', + }, + 0x1200007 => { + Name => 'GPSTimeStamp', + Groups => { 1 => 'GPS', 2 => 'Time' }, + ValueConv => q{ + require Image::ExifTool::GPS; + Image::ExifTool::GPS::ConvertTimeStamp($val); + }, + PrintConv => 'Image::ExifTool::GPS::PrintTimeStamp($val)', + }, + 0x1200008 => { + Name => 'GPSSatellites', + Groups => { 1 => 'GPS', 2 => 'Location' }, + }, + 0x1200010 => { + Name => 'GPSImgDirectionRef', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + 0x1200011 => { + Name => 'GPSImgDirection', + Groups => { 1 => 'GPS', 2 => 'Location' }, + }, + 0x1200012 => { + Name => 'GPSMapDatum', + Groups => { 1 => 'GPS', 2 => 'Location' }, + }, + 0x120001d => { + Name => 'GPSDateStamp', + Groups => { 1 => 'GPS', 2 => 'Time' }, + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + }, +# +# 0x200**** tags correspond to 0x**** tags in Nikon::Main +# (must be duplicated here so tagInfo "Table" entry will point to correct table. +# Also there would be a problem with the PRINT_CONV from the Main table) +# + 0x2000001 => { + Name => 'MakerNoteVersion', + PrintConv => '$_=$val;s/^(\d{2})/$1\./;s/^0//;$_', + }, + 0x2000005 => 'WhiteBalance', + 0x2000007 => { Name => 'FocusMode', Writable => 'string' }, #34 + 0x200000b => 'WhiteBalanceFineTune', + 0x200001b => { + Name => 'CropHiSpeed', + Writable => 'int16u', + Count => 7, + PrintConv => \%cropHiSpeed, + }, + 0x200001e => { + Name => 'ColorSpace', + PrintConv => { + 1 => 'sRGB', + 2 => 'Adobe RGB', + }, + }, + 0x200001f => { + Name => 'VRInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::VRInfo' }, + }, + 0x2000022 => { + Name => 'ActiveD-Lighting', + Writable => 'int16u', + PrintConv => { + 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', + 0xffff => 'Auto', + }, + }, + 0x2000023 => [ + { #PH (D300, but also found in D3,D3S,D3X,D90,D300S,D700,D3000,D5000) + Name => 'PictureControlData', + Condition => '$$valPt =~ /^01/', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::PictureControl' }, + },{ #28 + Name => 'PictureControlData', + Condition => '$$valPt =~ /^02/', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::PictureControl2' }, + },{ + Name => 'PictureControlData', + Condition => '$$valPt =~ /^03/', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::PictureControl3' }, + },{ + Name => 'PictureControlData', + Writable => 'undef', + Permanent => 0, + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::PictureControlUnknown' }, + }, + ], + 0x2000024 => { + Name => 'WorldTime', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::WorldTime' }, + }, + 0x2000025 => { #34 + Name => 'ISOInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::ISOInfo', + ByteOrder => 'BigEndian', # (BigEndian even for D810, which is a little-endian camera) + }, + }, + 0x200002a => { #23 (this tag added with D3 firmware 1.10 -- also written by Nikon utilities) + Name => 'VignetteControl', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 3 => 'Normal', + 5 => 'High', + }, + }, + 0x200002c => { + Name => 'UnknownInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::UnknownInfo' }, + }, + # 0x200002d - int16u[3]: "512 0 0" + 0x2000032 => { + Name => 'UnknownInfo2', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::UnknownInfo2' }, + }, + 0x2000039 => { + Name => 'LocationInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::LocationInfo' }, + }, + 0x200003f => 'WhiteBalanceFineTune', + # 0x200003f - rational64s[2]: "0 0" + # 0x2000042 - undef[6]: "0100\x03\0" + # 0x2000043 - undef[12]: all zeros + 0x200004e => { + Name => 'NikonSettings', + SubDirectory => { TagTable => 'Image::ExifTool::NikonSettings::Main' }, + }, + 0x2000083 => { + Name => 'LensType', + # credit to Tom Christiansen (ref 7) for figuring this out... + PrintConv => q[$_ = $val ? Image::ExifTool::DecodeBits($val, + { + 0 => 'MF', + 1 => 'D', + 2 => 'G', + 3 => 'VR', + 4 => '1', #PH + # bit 5 set for FT-1 adapter? - PH + 6 => 'E', #PH (electromagnetic aperture mechanism) + # bit 7 set for AF-P lenses? - PH + }) : 'AF'; + # remove commas and change "D G" to just "G" + s/,//g; s/\bD G\b/G/; + s/ E\b// and s/^(G )?/E /; # put "E" at the start instead of "G" + s/ 1// and $_ = "1 $_"; # put "1" at start + return $_; + ], + }, + 0x2000084 => { + Name => "Lens", + # short focal, long focal, aperture at short focal, aperture at long focal + PrintConv => \&Image::ExifTool::Exif::PrintLensInfo, + }, + 0x2000087 => { + Name => 'FlashMode', + Writable => 'int8u', + PrintConv => { + 0 => 'Did Not Fire', + 1 => 'Fired, Manual', #14 + 3 => 'Not Ready', #28 + 7 => 'Fired, External', #14 + 8 => 'Fired, Commander Mode', + 9 => 'Fired, TTL Mode', + 18 => 'LED Light', #G.F. (movie LED light) + }, + }, + 0x2000098 => [ + { #8 + Condition => '$$valPt =~ /^0100/', # D100, D1X - PH + Name => 'LensData0100', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::LensData00' }, + }, + { #8 + Condition => '$$valPt =~ /^0101/', # D70, D70s - PH + Name => 'LensData0101', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::LensData01' }, + }, + # note: this information is encrypted if the version is 02xx + { #8 + # 0201 - D200, D2Hs, D2X and D2Xs + # 0202 - D40, D40X and D80 + # 0203 - D300 + Condition => '$$valPt =~ /^020[1-3]/', + Name => 'LensData0201', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData01', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + { #PH + Condition => '$$valPt =~ /^0204/', # D90, D7000 + Name => 'LensData0204', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData0204', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + { + Condition => '$$valPt =~ /^040[01]/', # 0=1J1/1V1, 1=1J2 + Name => 'LensData0400', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData0400', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + { + Condition => '$$valPt =~ /^0402/', # 1J3/1S1/1V2 + Name => 'LensData0402', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData0402', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + { + Condition => '$$valPt =~ /^0403/', # 1J4,1J5 + Name => 'LensData0403', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData0403', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + { + Condition => '$$valPt =~ /^080[012]/', # Z6/Z7/Z9 + Name => 'LensData0800', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensData0800', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + ByteOrder => 'LittleEndian', + # 0x5a0c - NikonMeteringMode for some Z6 ver1.00 samples (ref PH) + }, + }, + { + Name => 'LensDataUnknown', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::LensDataUnknown', + ProcessProc => \&ProcessNikonEncrypted, + WriteProc => \&ProcessNikonEncrypted, + DecryptStart => 4, + }, + }, + ], + 0x20000a7 => { # Number of shots taken by camera so far (ref 2) + Name => 'ShutterCount', + PrintConv => '$val == 4294965247 ? "n/a" : $val', + }, + 0x20000a8 => [ + { + Name => 'FlashInfo0100', + Condition => '$$valPt =~ /^010[01]/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0100' }, + }, + { + Name => 'FlashInfo0102', + Condition => '$$valPt =~ /^0102/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0102' }, + }, + { + Name => 'FlashInfo0103', + # (0104 for D7000, 0105 for D800) + Condition => '$$valPt =~ /^010[345]/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0103' }, + }, + { + Name => 'FlashInfo0106', # (0106 for D7100) + Condition => '$$valPt =~ /^0106/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0106' }, + }, + { + Name => 'FlashInfo0107', # (0107 for D4S/D750/D810/D5500/D7200, 0108 for D5/D500/D3400) + Condition => '$$valPt =~ /^010[78]/', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfo0107' }, + }, + { + Name => 'FlashInfoUnknown', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::FlashInfoUnknown' }, + }, + ], + 0x20000ab => { Name => 'VariProgram', Writable => 'string' }, #2 (scene mode for DSLR's - PH) + 0x20000b1 => { #34 + Name => 'HighISONoiseReduction', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Minimal', # for high ISO (>800) when setting is "Off" + 2 => 'Low', # Low,Normal,High take effect for ISO > 400 + 3 => 'Medium Low', + 4 => 'Normal', + 5 => 'Medium High', + 6 => 'High', + }, + }, + 0x20000b7 => { + Name => 'AFInfo2', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::AFInfo2' }, + }, + # 0x20000c0 - undef[8] + 0x20000c3 => { + Name => 'BarometerInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::BarometerInfo', + # (little-endian in II EXIF, big-endian in MOV) + }, + }, +); + +# Nikon XMP tags written in NKSC metadata +%Image::ExifTool::Nikon::ast = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-ast', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP, + NAMESPACE => 'ast', + VARS => { NO_ID => 1 }, + NOTES => 'Tags used by Nikon NX Studio in Nikon NKSC sidecar files and trailers.', + about => { }, + version => { }, + XMLPackets => { + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + Encoding => 'Base64', + Binary => 1, + }, + IPTC => { + SubDirectory => { TagTable => 'Image::ExifTool::IPTC::Main' }, + Encoding => 'Base64', + Binary => 1, + }, + GPSVersionID => { Groups => { 2 => 'Location' }, %base64bytes }, + GPSLatitudeRef => { + Groups => { 2 => 'Location' }, + %base64int32u, + PrintConv => { 0 => 'North', 1 => 'South' }, #PH (NC) + }, + GPSLatitude => { Groups => { 2 => 'Location' }, %base64coord }, + GPSLongitudeRef => { + Groups => { 2 => 'Location' }, + %base64int32u, + PrintConv => { 2 => 'East', 3 => 'West' }, #PH (NC) + }, + GPSLongitude => { Groups => { 2 => 'Location' }, %base64coord }, + GPSAltitudeRef => { + Groups => { 2 => 'Location' }, + %base64bytes, + PrintConv => { + 0 => 'Above Sea Level', + 1 => 'Below Sea Level', + }, + }, + GPSAltitude => { + Groups => { 2 => 'Location' }, + %base64double, + PrintConv => '"$val m"', + }, + GPSMapDatum => { Groups => { 2 => 'Location' } }, + GPSImgDirection => { + Groups => { 2 => 'Location' }, + %base64double, + PrintConv => 'sprintf("%.2f", $val)', + }, + GPSImgDirectionRef => { + Groups => { 2 => 'Location' }, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, +); +%Image::ExifTool::Nikon::sdc = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-sdc', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP, + NAMESPACE => 'sdc', + VARS => { NO_ID => 1 }, + about => { }, + version => { }, + appversion => { Name => 'AppVersion' }, + appname => { Name => 'AppName' }, +); +%Image::ExifTool::Nikon::nine = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-nine', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP, + NAMESPACE => 'nine', + VARS => { NO_ID => 1 }, + about => { }, + version => { }, + Label => { }, + Rating => { }, + Trim => { %base64bin }, + NineEdits => { + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::NineEdits', + IgnoreProp => { userData => 1 }, # remove "UserData" from already overly long tag names + }, + Binary => 1, + }, +); +%Image::ExifTool::Nikon::NineEdits = ( + GROUPS => { 0 => 'XML', 1 => 'NineEdits', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP, + VARS => { NO_ID => 1 }, + NOTES => 'XML-based tags used to store editing information.', + filterParametersBinary => { %base64bin }, + filterParametersExportExportData => { %base64bin }, + filterParametersCustomCustomData => { %base64bin }, +); + +# Nikon composite tags +%Image::ExifTool::Nikon::Composite = ( + GROUPS => { 2 => 'Camera' }, + LensSpec => { + Require => { + 0 => 'Nikon:Lens', + 1 => 'Nikon:LensType', + }, + ValueConv => '"$val[0] $val[1]"', + PrintConv => '"$prt[0] $prt[1]"', + }, + LensID => { + SeparateTable => 'Nikon LensID', # print values in a separate table + Require => { + 0 => 'Nikon:LensIDNumber', + 1 => 'LensFStops', + 2 => 'MinFocalLength', + 3 => 'MaxFocalLength', + 4 => 'MaxApertureAtMinFocal', + 5 => 'MaxApertureAtMaxFocal', + 6 => 'MCUVersion', + 7 => 'Nikon:LensType', + }, + # construct lens ID string as per ref 11 + ValueConv => 'sprintf("%.2X"." %.2X"x7, @raw)', + PrintConv => \%nikonLensIDs, + PrintInt => 1, + }, + AutoFocus => { + Require => { + 0 => 'Nikon:PhaseDetectAF', + 1 => 'Nikon:ContrastDetectAF', + }, + ValueConv => '($val[0] or $val[1]) ? 1 : 0', + PrintConv => \%offOn, + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Nikon'); + +#------------------------------------------------------------------------------ +# Process Nikon AVI tags (D5000 videos) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessNikonAVI($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart} || 0; + my $dirEnd = $pos + $$dirInfo{DirLen}; + $et->VerboseDir($dirInfo, undef, $$dirInfo{DirLen}); + SetByteOrder('II'); + while ($pos + 4 <= $dirEnd) { + my $tag = Get16u($dataPt, $pos); + my $size = Get16u($dataPt, $pos + 2); + $pos += 4; + last if $pos + $size > $dirEnd; + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => $dataPt, + Start => $pos, + Size => $size, + ); + $pos += $size; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Print conversion for Nikon AF points +# Inputs: 0) value to convert (as a string of hex bytes), +# 1) lookup for AF point bit number (starting at 1) +sub PrintAFPoints($$) +{ + my ($val, $afPoints) = @_; + my ($i, $j, @points); + $val =~ tr/ //d; # remove spaces from hex string + my @dat = unpack 'C*', pack 'H*', $val; # convert to array of bytes + # loop through all bytes to find active AF points + for ($i=0; $i<=@dat; ++$i) { + next unless $dat[$i]; + for ($j=0; $j<8; ++$j) { + next unless $dat[$i] & (1 << $j); + my $point = $$afPoints{$i*8 + $j + 1}; + push @points, $point if defined $point; + } + } + return '(none)' unless @points; + # sort the points and return as comma-separated string + return join ',', sort { + return $a cmp $b if length($a) == length($b); + return substr($a,0,1).'0'.substr($a,1,1) cmp $b if length($a) == 2; + return $a cmp substr($b,0,1).'0'.substr($b,1,1); + } @points; +} + +#------------------------------------------------------------------------------ +# Inverse print conversion for AF points +# Inputs: 0) AF point string, 1) AF point lookup +# Returns: AF point data as a string of hex bytes +sub PrintAFPointsInv($$) +{ + my ($val, $afPoints) = @_; + my @points = ($val =~ /[A-Za-z]\d+/g); + my $size = int((scalar(keys %$afPoints) + 7) / 8); + my @dat = (0) x $size; + if (@points) { + my (%bitNum, $point); + $bitNum{$$afPoints{$_}} = $_ foreach keys %$afPoints; # build reverse lookup + foreach $point (@points) { + my $bitNum = $bitNum{uc $point} or next; + my $byte = int(($bitNum - 1) / 8); + $dat[$byte] |= (1 << (($bitNum - 1) % 8)); + } + } + return join(" ", unpack("H2"x$size, pack('C*', @dat))); +} + +#------------------------------------------------------------------------------ +# Get AF point name for grid-type AF +# Inputs: 0) AF point number, 1) number of columns, 2) true for inverse conversion +# Returns: AF point name, or undef +sub GetAFPointGrid($$;$) +{ + my ($val, $ncol, $inv) = @_; + if ($inv) { + return undef unless $val =~ /^([A-J])(\d+)$/i; + return (ord(uc($1))-65) * $ncol + $2 - 1; + } else { + my $row = int(($val + 0.5) / $ncol) & 0xff; + my $col = $val - $ncol * $row + 1; + return chr(65+$row) . $col; + } +} + +#------------------------------------------------------------------------------ +# Print conversion for grid-type AF points +# Inputs: 0) value to convert (as a string of hex bytes), +# 1) number of columns in grid +sub PrintAFPointsGrid($$) +{ + my ($val, $ncol) = @_; + my ($i, $j, @points); + $val =~ tr/ //d; # remove spaces from hex string + my @dat = unpack 'C*', pack 'H*', $val; # convert to array of bytes + # loop through all bytes to find active AF points + for ($i=0; $i<@dat; ++$i) { + next unless $dat[$i]; + for ($j=0; $j<8; ++$j) { + next unless $dat[$i] & (1 << $j); + my $point = GetAFPointGrid($i*8 + $j, $ncol); + push @points, $point if defined $point; + } + } + return '(none)' unless @points; + return join ',', @points; # return as comma-separated string +} + +#------------------------------------------------------------------------------ +# Inverse print conversion for AF points +# Inputs: 0) AF point string, 1) number of columns, 2) size of data +# Returns: AF point data as a string of hex bytes +sub PrintAFPointsGridInv($$$) +{ + my ($val, $ncol, $size) = @_; + my @points = ($val =~ /[A-Za-z]\d+/g); + my @dat = (0) x $size; + foreach (@points) { + my $n = GetAFPointGrid($_, $ncol, 1); + next unless defined $n; + my $byte = int($n / 8); + next if $byte > $size; + $dat[$byte] |= (1 << ($n - $byte * 8)); + } + return join(" ", unpack("H2"x$size, pack('C*', @dat))); +} + +#------------------------------------------------------------------------------ +# Print conversion for relative Left/Right AF points (ref 28) +# Inputs: 0) column, 1) number of columns +# Returns: AF point data as a string (e.g. '2L of Center' or 'C' or '3R of Center') +sub PrintAFPointsLeftRight($$) +{ + my ($col, $ncol) = @_; + my $center = 1 + ($ncol + 1)/2; + return 'n/a' if $col == 0; #out of focus + return 'C' if $col == $center; + return sprintf('%d', $center - $col) . 'L of Center' if $col < $center; + return sprintf('%d', $col - $center) . 'R of Center' if $col > $center; +} + +#------------------------------------------------------------------------------ +# Print conversion for relative Up/Down AF points (ref 28) +# Inputs: 0) row, 1) number of rows +# Returns: AF point data as a string (e.g. '2U from Center' or 'C' or '3D from Center') +sub PrintAFPointsUpDown($$) +{ + my ($row, $nrow) = @_; + my $center = 1 + ($nrow + 1)/2; + return 'n/a' if $row == 0; #out of focus + return 'C' if $row == $center; + return sprintf('%d', $center - $row) . 'U from Center' if $row < $center; + return sprintf('%d', $row - $center) . 'D from Center' if $row > $center; +} + +#------------------------------------------------------------------------------ +# Print PictureControl value +# Inputs: 0) value (with 0x80 subtracted), +# 1) 'Normal' (0 value) string (default 'Normal') +# 2) format string for numbers (default '%+d'), 3) v2 divisor +# Returns: PrintConv value +sub PrintPC($;$$$) +{ + my ($val, $norm, $fmt, $div) = @_; + return $norm || 'Normal' if $val == 0; + return 'n/a' if $val == 0x7f; + return 'Auto' if $val == -128; + # -127 = custom curve created in Camera Control Pro (show as "User" by D3) - ref 28 + return 'User' if $val == -127; #28 + return sprintf($fmt || '%+d', $val / ($div || 1)); +} + +#------------------------------------------------------------------------------ +# Inverse of PrintPC +# Inputs: 0) PrintConv value (after subracting 0x80 from raw value), 1) v2 divisor +# Returns: unconverted value +# Notes: raw values: 0=Auto, 1=User, 0xff=n/a, ... 0x7f=-1, 0x80=0, 0x81=1, ... +sub PrintPCInv($;$) +{ + my ($val, $div) = @_; + return $val * ($div || 1) if $val =~ /^[-+]?\d+(\.\d+)?$/; + return 0x7f if $val =~ /n\/a/i; + return -128 if $val =~ /auto/i; + return -127 if $val =~ /user/i; #28 + return 0; +} + +#------------------------------------------------------------------------------ +# Convert unknown LensID values +# Inputs: 0) value, 1) flag for inverse conversion, 2) PrintConv hash ref +sub LensIDConv($$$) +{ + my ($val, $inv, $conv) = @_; + return undef if $inv; + # multiple lenses with the same LensID are distinguished by decimal values + if ($$conv{"$val.1"}) { + my ($i, @vals, @user); + for ($i=1; ; ++$i) { + my $lens = $$conv{"$val.$i"} or last; + if ($Image::ExifTool::userLens{$lens}) { + push @user, $lens; + } else { + push @vals, $lens; + } + } + return join(' or ', @user) if @user; + return join(' or ', @vals); + } + # Sigma has been changing the LensIDNumber on some new lenses + # and with some Sigma lenses the LensFStops changes! (argh!) + # Also, older cameras my not set bits 4-7 of LensType + my $pat = $val; + $pat =~ s/^\w+ \w+/.. ../; # ignore LensIDNumber and LensFStops + $pat =~ s/\w(\w)$/.$1/; # ignore bits 4-7 of LensType + my @ids = sort grep /^$pat$/, keys %$conv; + if (@ids) { + # first try different LensFStops (2nd value) + ($pat = $val) =~ s/ \w+/ ../; + my @good = grep /^$pat$/, @ids; + return $$conv{$good[0]} if @good; + # then try different LensIDNumber (1st value) + ($pat = $val) =~ s/^\w+/../; + @good = grep /^$pat$/, @ids; + return "Unknown ($val) $$conv{$good[0]} ?" if @good; + # older cameras may not set bits 4-7 of LensType + ($pat = $val) =~ s/\w(\w)$/.$1/; + @good = grep /^$pat$/, @ids; + return "Unknown ($val) $$conv{$good[0]} ?" if @good; + } + return undef; +} + +#------------------------------------------------------------------------------ +# Clean up formatting of string values +# Inputs: 0) string value +# Returns: formatted string value +# - removes trailing spaces and changes case to something more sensible +sub FormatString($) +{ + my $str = shift; + # limit string length (can be very long for some unknown tags) + if (length($str) > 60) { + $str = substr($str,0,55) . "[...]"; + } else { + $str =~ s/\s+$//; # remove trailing white space + # Don't change case of non-words (no vowels) + if ($str =~ /[AEIOUY]/) { + # change all letters but the first to lower case, + # but only in words containing a vowel + if ($str =~ s/\b([AEIOUY])([A-Z]+)/$1\L$2/g) { + $str =~ s/\bAf\b/AF/; # patch for "AF" + # patch for a number of models that write improper string + # terminator for ImageStabilization (VR-OFF, VR-ON) + $str =~ s/ +.$//s; + } + if ($str =~ s/\b([A-Z])([A-Z]*[AEIOUY][A-Z]*)/$1\L$2/g) { + $str =~ s/\bRaw\b/RAW/; # patch for "RAW" + } + } + } + return $str; +} + +#------------------------------------------------------------------------------ +# decoding tables from ref 4 +my @xlat = ( + [ 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 ], + [ 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 ] +); + +my ($ci0, $cj0, $ck0, $decryptStart); # decryption parameters + +# Decrypt Nikon data block (ref 4) +# Inputs: 0) reference to data block, 1) optional start offset (default 0) +# 2) optional number of bytes to decode (default to the end of the data) +# 3) optional serial number key (undef to continue previous decryption) +# 4) optional shutter count key +# Returns: data block with specified data decrypted +# Notes: The first time this is called for a given encrypted data block the serial/count +# keys must be defined, and $start must be the offset for initialization of the +# decryption parameters (ie. the beginning of the encrypted data, which isn't +# necessarily inside the data block if $len is zero). Subsequent calls for +# the same data block do not specify the serial/count keys, and may be used +# to decrypt data at any start point within the full data block. +sub Decrypt($;$$$$) +{ + my ($dataPt, $start, $len, $serial, $count) = @_; + my ($ch, $cj, $ck); + + $start or $start = 0; + my $maxLen = length($$dataPt) - $start; + $len = $maxLen if not defined $len or $len > $maxLen; + if (defined $serial and defined $count) { + # initialize decryption parameters + my $key = 0; + $key ^= ($count >> ($_*8)) & 0xff foreach 0..3; + $ci0 = $xlat[0][$serial & 0xff]; + $cj0 = $xlat[1][$key]; + $ck0 = 0x60; + undef $decryptStart; + } + if (defined $decryptStart) { + # initialize decryption parameters for this start position + my $n = $start - $decryptStart; + $cj = ($cj0 + $ci0 * ($n * $ck0 + ($n * ($n - 1))/2)) & 0xff; + $ck = ($ck0 + $n) & 0xff; + } else { + $decryptStart = $start; + ($cj, $ck) = ($cj0, $ck0); + } + return $$dataPt if $len <= 0; + my @data = unpack('C*', substr($$dataPt, $start, $len)); + foreach $ch (@data) { + $cj = ($cj + $ci0 * $ck) & 0xff; + $ck = ($ck + 1) & 0xff; + $ch ^= $cj; + } + return substr($$dataPt, 0, $start) . pack('C*', @data) . substr($$dataPt, $start+$len); +} + +#------------------------------------------------------------------------------ +# Get serial number for use as a decryption key +# Inputs: 0) ExifTool object ref, 1) serial number string +# Returns: serial key integer or undef if no serial number provided +sub SerialKey($$) +{ + my ($et, $serial) = @_; + # use serial number as key if integral + return $serial if not defined $serial or $serial =~ /^\d+$/; + return 0x22 if $$et{Model} =~ /\bD50$/; # D50 (ref 8) + return 0x60; # D200 (ref 10), D40X (ref PH), etc +} + +#------------------------------------------------------------------------------ +# Extract information from "NIKON APP" trailer (ref PH) +# Inputs: 0) ExifTool ref, 1) Optional dirInfo ref for returning trailer info +# Returns: true on success +sub ProcessNikonApp($;$) +{ + local $_; + my ($et, $dirInfo) = @_; + my $raf = $$et{RAF}; + my $offset = $dirInfo ? $$dirInfo{Offset} || 0 : 0; + my $buff; + + return 0 unless $raf->Seek(-20-$offset, 2) and $raf->Read($buff, 20) == 20 and + substr($buff,-16) eq "\0\0\0\0\0\0/NIKON APP"; # check magic number + + my $verbose = $et->Options('Verbose'); + my $fileEnd = $raf->Tell(); + my $trailerLen = unpack('N', $buff); + $trailerLen > $fileEnd and $et->Warn('Bad NikonApp trailer size'), return 0; + if ($dirInfo) { + $$dirInfo{DirLen} = $trailerLen; + $$dirInfo{DataPos} = $fileEnd - $trailerLen; + if ($$dirInfo{OutFile}) { + if ($$et{DEL_GROUP}{NikonApp}) { + $et->VPrint(0, " Deleting NikonApp trailer ($trailerLen bytes)\n"); + ++$$et{CHANGED}; + # just copy the trailer when writing (read directly into output buffer) + } elsif ($trailerLen > $fileEnd or not $raf->Seek($$dirInfo{DataPos}, 0) or + $raf->Read(${$$dirInfo{OutFile}}, $trailerLen) != $trailerLen) + { + return 0; + } + return 1; + } + $et->DumpTrailer($dirInfo) if $verbose or $$et{HTML_DUMP}; + } + unless ($trailerLen >= 0x40 and $raf->Seek($fileEnd - $trailerLen, 0) and + $raf->Read($buff, 0x40) == 0x40 and $buff =~ m(NIKON APP\0)) + { + $et->Warn('Error reading NikonApp trailer'); + return 0; + } + $$et{SET_GROUP0} = 'NikonApp'; + while ($raf->Read($buff, 8) == 8) { + my ($id, $len) = unpack('N2', $buff); + if ($len & 0x80000000) { + $et->Warn('Invalid NikonApp record length'); + last; + } + last if $id == 0 and $len == 0; + unless ($raf->Read($buff, $len) == $len) { + $et->Warn('Truncated NikonApp record'); + last; + } + if ($id == 1) { + require Image::ExifTool::XMP; + Image::ExifTool::XMP::ProcessXMP($et, { DataPt => \$buff }); + } else { # (haven't seen any other types of records) + $et->Warn("Unknown NikonApp record $id"); + last; + } + } + delete $$et{SET_GROUP0}; + return 1; +} + +#------------------------------------------------------------------------------ +# Read Nikon NCTG tags in MOV videos +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessNikonMOV($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my %needTags = ( 0x110a431 => 0, 0x20000a7 => undef ); # needed for decryption + $et->VerboseDir($$dirInfo{DirName}, 0, $$dirInfo{DirLen}); + my $pass; + # do two passes so we can pre-scan for necessary decryption keys + for ($pass=0; $pass<2; ++$pass) { + my $pos = $$dirInfo{DirStart}; + my $end = $pos + $$dirInfo{DirLen}; + while ($pos + 8 < $end) { + my $tag = Get32u($dataPt, $pos); + my $fmt = Get16u($dataPt, $pos + 4); # (same format code as EXIF) + my $count = Get16u($dataPt, $pos + 6); + $pos += 8; + my $fmtStr = $Image::ExifTool::Exif::formatName[$fmt]; + unless ($fmtStr) { + $et->Warn(sprintf("Unknown format ($fmt) for $$dirInfo{DirName} tag 0x%x",$tag)) if $pass; + last; + } + my $size = $count * $Image::ExifTool::Exif::formatSize[$fmt]; + if ($pos + $size > $end) { + $et->Warn(sprintf("Truncated data for $$dirInfo{DirName} tag 0x%x",$tag)) if $pass; + last; + } + if ($pass) { + my $rational; + my $val = ReadValue($dataPt, $pos, $fmtStr, $count, $size, \$rational); + my $key = $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $dataPos, + Format => $fmtStr, + Start => $pos, + Size => $size, + Base => $$dirInfo{Base}, + ); + $$et{RATIONAL}{$key} = $rational if $rational and $key; + } elsif (exists $needTags{$tag}) { + $needTags{$tag} = ReadValue($dataPt, $pos, $fmtStr, $count, $size); + $$et{NikonSerialKey} = SerialKey($et, $needTags{0x110a431}); + $$et{NikonCountKey} = $needTags{0x20000a7}; + } + $pos += $size; # is this padded to an even offset???? + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Get offset of end-of-data for a tag +# Inputs: 0) tag table ref, 1) tag ID, 2) true to not calculate end for a SubDirectory +# Returns: offset of tag value end, undef if it can't be determined +sub GetTagEnd($$;$) +{ + my ($tagTablePtr, $tagID, $ignoreSubdir) = @_; + my $tagInfo = $$tagTablePtr{$tagID}; + $tagInfo = $$tagInfo[0] if ref $tagInfo eq 'ARRAY'; + # (can't pre-determine position of offset-based subdirectories) + return undef if $ignoreSubdir and $$tagInfo{SubDirectory}; + my $fmt = $$tagInfo{Format} || $$tagTablePtr{FORMAT} || 'int8u'; + my $nm = $fmt =~ s/\[(\d+)\]$// ? $1 : 1; + my $sz = Image::ExifTool::FormatSize($fmt) or return undef; + return int($tagID) + $sz * $nm; +} + +#------------------------------------------------------------------------------ +# Initialize SubDirectory KnownStart/KnownEnd limits of known tags (used in decryption) +# Inputs: 0) tagInfo ref containing this SubDirectory, 2) tag table ref for encrypted subdir +# Notes: KnownStart/KnownEnd are relative to the SubDirectory Start. If KnownStart/KnownEnd +# aren't set then the entire data is decrypted, so all of this effort is just for speed. +sub InitEncryptedSubdir($$) +{ + my ($tagInfo, $tagTablePtr) = @_; +# +# for encrypted NIKON_OFFSETS tables we loop through all SubDirectory tags in this table +# and set the KnownEnd for each of these according to the last tag in the child tables +# + my $vars = $$tagTablePtr{VARS}; + $vars or $vars = $$tagTablePtr{VARS} = { }; + if ($$vars{NIKON_OFFSETS} and not $$vars{NIKON_INITIALIZED}) { + $$vars{NIKON_INITIALIZED} = 1; + my $tagID; + foreach $tagID (TagTableKeys($tagTablePtr)) { + my $tagInfo = $$tagTablePtr{$tagID}; + next unless ref $tagInfo eq 'HASH'; + my $subdir = $$tagInfo{SubDirectory} or next; + my $tbl = GetTagTable($$subdir{TagTable}); + my ($last) = sort { $b <=> $a } TagTableKeys($tbl); # (reverse sort) + $$subdir{KnownEnd} = GetTagEnd($tbl, $last, 1); + } + } +# +# for other encrypted Nikon tables we set the KnownStart/KnownEnd entries in the +# SubDirectory of the parent tag +# + unless ($$tagInfo{NikonInitialized}) { + $$tagInfo{NikonInitialized} = 1; + my $subdir = $$tagInfo{SubDirectory}; + my $start = $$subdir{DecryptStart} || 0; + my $off = $$subdir{DirOffset}; + my @tagIDs = sort { $a <=> $b } TagTableKeys($tagTablePtr); + if (defined $off) { + $off += $start; # (DirOffset, if specified, is relative to DecryptStart) + } else { + # ignore tags that come before the start of encryption + shift @tagIDs while @tagIDs and $tagIDs[0] < $start; + $off = 0; + } + if (@tagIDs) { + my ($first, $last) = @tagIDs[0,-1]; + my $lastInfo = $$tagTablePtr{$last}; + $lastInfo = $$lastInfo[0] if ref $lastInfo eq 'ARRAY'; + $$subdir{KnownStart} = int($first) + $off if $first + $off > $start; + $$subdir{KnownEnd} = GetTagEnd($tagTablePtr, $last); + if (defined $$subdir{KnownEnd}) { + $$subdir{KnownEnd} += $off; + } else { + warn "Internal error setting KnownEnd for $$tagTablePtr{SHORT_NAME}\n"; + } + } else { + $$subdir{KnownStart} = $$subdir{KnownEnd} = $start; + } + } +} + +#------------------------------------------------------------------------------ +# Prepare to process NIKON_OFFSETS directory and decrypt necessary data +# Inputs: 0) ExifTool ref, 1) data ref, 2) tag table ref, 3) decrypt start, +# 4) decrypt mode (0=piecewise, 1=continuous to end of last known section, 2=all) +# Returns: end of decrypted data (or undef for piecewise decryption) +sub PrepareNikonOffsets($$$$$) +{ + my ($et, $dataPt, $tagTablePtr, $start, $decryptMode) = @_; + my $offset = $$tagTablePtr{VARS}{NIKON_OFFSETS}; + my $dataLen = length $$dataPt; + return undef if $offset + 4 > $dataLen or $offset < $start; + my $serial = $$et{NikonSerialKey}; + my $count = $$et{NikonCountKey}; + my $dpos = $offset + 4; # decrypt up to NumberOffsets + $$dataPt = Decrypt($dataPt, $start, $dpos - $start, $serial, $count); + my $numOffsets = Get32u($dataPt, $offset); + my $more = $numOffsets * 4; # more bytes to decrypt entire offsets table + return undef if $offset + 4 + $more > $dataLen; + $$dataPt = Decrypt($dataPt, $dpos, $more); + $dpos += $more; + my $unknown = $et->Options('Unknown'); + my ($i, @offInfo, $end); + # extract non-zero offsets and create unknown subdirectories if Unknown > 1 + for ($i=0; $i<$numOffsets; ++$i) { + my $pos = $offset + 4 + 4 * $i; + my $off = Get32u($dataPt, $pos) or next; + my $tagInfo = $$tagTablePtr{$pos}; + my $known = 0; + if ($tagInfo) { + $known = 1 if ref $tagInfo ne 'HASH' or not $$tagInfo{Unknown}; + } elsif ($unknown > 1) { + # create new table for unknown information + my $tbl = sprintf('Image::ExifTool::Nikon::UnknownInfo%.2x', $pos); + no strict 'refs'; + unless (%$tbl) { + %$tbl = ( %binaryDataAttrs, GROUPS => { 0=>'MakerNotes', 2=>'Unknown' } ); + GetTagTable($tbl); + } + # add unknown entry in offset table for this subdirectory + $tagInfo = AddTagToTable($tagTablePtr, $pos, { + Name => sprintf('UnknownOffset%.2x', $pos), + Format => 'int32u', + SubDirectory => { TagTable => $tbl }, + Unknown => 2, + }); + } + push @offInfo, [ $pos, $off, $known ]; # save parameters for non-zero offsets + } + # sort offsets in ascending order, and use the differences to calculate + # directory lengths and update the SubDirectory DirLen's accordingly + my @sorted = sort { $$a[1] <=> $$b[1] or $$a[0] <=> $$b[0] } @offInfo; + push @sorted, [ 0, length($$dataPt), 0 ]; + for ($i=0; $i<@sorted-1; ++$i) { + my $pos = $sorted[$i][0]; + my $len = $sorted[$i+1][1] - $sorted[$i][1]; + my $tagInfo = $$tagTablePtr{$pos}; + $tagInfo = $et->GetTagInfo($tagTablePtr, $pos) if $tagInfo and + not (ref $tagInfo eq 'HASH' and $$tagInfo{AlwaysDecrypt}); + # set DirLen in SubDirectory entry + my $subdir; + $$subdir{DirLen} = $len if ref $tagInfo eq 'HASH' and defined($subdir=$$tagInfo{SubDirectory}); + if ($decryptMode) { + # keep track of end of last known directory + $end = $sorted[$i+1][1] if $sorted[$i][2]; + } elsif ($tagInfo and (ref $tagInfo ne 'HASH' or not $$tagInfo{Unknown})) { + # decrypt data piecewise as necessary + my $n = $len; + if ($subdir and $$subdir{KnownEnd}) { + $n = $$subdir{KnownEnd}; + if ($n > $len) { + $et->Warn("Data too short for $$tagInfo{Name}",1) unless $$tagInfo{AlwaysDecrypt}; + $n = $len; + } + } + $$dataPt = Decrypt($dataPt, $sorted[$i][1], $n); + } + } + if ($decryptMode) { + # decrypt the remaining required data + $end = length $$dataPt if $decryptMode == 2 or not $end or $end < $dpos; + $$dataPt = Decrypt($dataPt, $dpos, $end - $dpos); + } + return $end; +} + +#------------------------------------------------------------------------------ +# Read/Write Nikon Encrypted data block +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success when reading, or new directory when writing (IsWriting set) +sub ProcessNikonEncrypted($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + my $serial = $$et{NikonSerialKey}; + my $count = $$et{NikonCountKey}; + unless (defined $serial and defined $count and $serial =~ /^\d+$/ and $count =~ /^\d+$/) { + if (defined $serial or defined $count) { + my $msg; + if (defined $serial and defined $count) { + $msg = $serial =~ /^\d+$/ ? 'invalid ShutterCount' : 'invalid SerialNumber'; + } else { + $msg = defined $serial ? 'no ShutterCount' : 'no SerialNumber'; + } + $et->Warn("Can't decrypt Nikon information ($msg key)"); + } + delete $$et{NikonSerialKey}; + delete $$et{NikonCountKey}; + return 0; + } + my $oldOrder = GetByteOrder(); + my $isWriting = $$dirInfo{IsWriting}; + my $verbose = $isWriting ? 0 : $et->Options('Verbose'); + my $tagInfo = $$dirInfo{TagInfo}; + my $dirStart = $$dirInfo{DirStart}; + my $data = substr(${$$dirInfo{DataPt}}, $dirStart, $$dirInfo{DirLen}); + + my ($start, $len, $offset, $recrypt, $newSerial, $newCount, $didDecrypt); + + # must re-encrypt when writing if serial number or shutter count changes + if ($isWriting) { + if ($$et{NewNikonSerialKey}) { + $newSerial = $$et{NewNikonSerialKey}; + $recrypt = 1; + } + if ($$et{NewNikonCountKey}) { + $newCount = $$et{NewNikonCountKey}; + $recrypt = 1; + } + } + if ($tagInfo and $$tagInfo{SubDirectory}) { + # initialize SubDirectory entries used in encryption (KnownStart, KnownEnd) + InitEncryptedSubdir($tagInfo, $tagTablePtr); + my $subdir = $$tagInfo{SubDirectory}; + $start = $$subdir{DecryptStart} || 0; + # DirOffset, if specified, is the offset to the start of the + # directory relative to start of encrypted data + $offset = defined $$subdir{DirOffset} ? $$subdir{DirOffset} + $start : 0; + # must set byte ordering before calling PrepareNikonOffsets() + SetByteOrder($$subdir{ByteOrder}) if $$subdir{ByteOrder}; + # prepare for processing NIKON_OFFSETS directory if necessary + my $unknown = $verbose > 2 || $et->Options('Unknown') > 1; + # decrypt mode: 0=piecewise, 1=continuous to end of last known section, 2=all + my $dMode = $isWriting ? ($recrypt ? 2 : 1) : ($unknown ? 2 : 0); + if ($$tagTablePtr{VARS}{NIKON_OFFSETS}) { + $len = PrepareNikonOffsets($et, \$data, $tagTablePtr, $start, $dMode); + $didDecrypt = 1; + } elsif ($dMode < 2) { + if ($dMode == 0 and $$subdir{KnownStart}) { + # initialize decryption parameters for address DecryptStart address + Decrypt(\$data, $start, 0, $serial, $count); + # reset serial/count keys so we don't re-initialize below + undef $serial; + undef $count; + # change decryption start to skip unnecessary data + $start = $$subdir{KnownStart}; + } + $len = $$subdir{KnownEnd} - $start if $$subdir{KnownEnd}; + } + } else { + $start = $offset = 0; + } + my $maxLen = length($data) - $start; + # decrypt all the data unless the length was specified + $len = $maxLen unless $len and $len < $maxLen; + + $data = Decrypt(\$data, $start, $len, $serial, $count) unless $didDecrypt; + + if ($verbose > 2) { + $et->VerboseDir("Decrypted $$tagInfo{Name}"); + $et->VerboseDump(\$data, + Prefix => $$et{INDENT} . ' ', + # (remove this because it is useful to have decrypted offsets start at 0) + #DataPos => $dirStart + $$dirInfo{DataPos} + ($$dirInfo{Base} || 0), + ); + } + # process the decrypted information + my %subdirInfo = ( + DataPt => \$data, + DirStart => $offset, + DirLen => length($data) - $offset, + DirName => $$dirInfo{DirName}, + DataPos => $$dirInfo{DataPos} + $dirStart, + Base => $$dirInfo{Base}, + ); + my $rtnVal; + if ($isWriting) { + my $changed = $$et{CHANGED}; + $rtnVal = $et->WriteBinaryData(\%subdirInfo, $tagTablePtr); + # must re-encrypt if serial number or shutter count changes + if ($recrypt) { + $serial = $newSerial if defined $newSerial; + $count = $newCount if defined $newCount; + ++$$et{CHANGED}; + } + if ($changed == $$et{CHANGED}) { + undef $rtnVal; # nothing changed so use original data + } else { + # add back any un-encrypted data at start + $rtnVal = substr($data, 0, $offset) . $rtnVal if $offset; + # re-encrypt data (symmetrical algorithm) + $rtnVal = Decrypt(\$rtnVal, $start, $len, $serial, $count); + $et->VPrint(2, $$et{INDENT}, " [recrypted $$tagInfo{Name}]"); + } + } else { + $rtnVal = $et->ProcessBinaryData(\%subdirInfo, $tagTablePtr); + } + SetByteOrder($oldOrder); + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Pre-scan EXIF directory to extract specific tags +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) required tagID hash ref +# Returns: 1 if directory was scanned successfully +sub PrescanExif($$$) +{ + my ($et, $dirInfo, $tagHash) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos} || 0; + my $dataLen = $$dirInfo{DataLen}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $base = $$dirInfo{Base} || 0; + my $raf = $$dirInfo{RAF}; + my ($index, $numEntries, $data, $buff); + + # get number of entries in IFD + if ($dirStart >= 0 and $dirStart <= $dataLen-2) { + $numEntries = Get16u($dataPt, $dirStart); + # reset $numEntries to read from file if necessary + undef $numEntries if $dirStart + 2 + 12 * $numEntries > $dataLen; + } + # read IFD from file if necessary + unless ($numEntries) { + $raf or return 0; + $dataPos += $dirStart; # read data from the start of the directory + $raf->Seek($dataPos + $base, 0) and $raf->Read($data, 2) == 2 or return 0; + $numEntries = Get16u(\$data, 0); + my $len = 12 * $numEntries; + $raf->Read($buff, $len) == $len or return 0; + $data .= $buff; + # update variables for the newly loaded IFD (already updated dataPos) + $dataPt = \$data; + $dataLen = length $data; + $dirStart = 0; + } + # loop through Nikon MakerNote IFD entries + for ($index=0; $index<$numEntries; ++$index) { + my $entry = $dirStart + 2 + 12 * $index; + my $tagID = Get16u($dataPt, $entry); + next unless exists $$tagHash{$tagID}; # only extract required tags + my $format = Get16u($dataPt, $entry+2); + next if $format < 1 or $format > 13; + my $count = Get32u($dataPt, $entry+4); + my $size = $count * $Image::ExifTool::Exif::formatSize[$format]; + my $formatStr = $Image::ExifTool::Exif::formatName[$format]; + my $valuePtr = $entry + 8; # pointer to value within $$dataPt + if ($size > 4) { + next if $size > 0x1000000; # set a reasonable limit on data size (16MB) + $valuePtr = Get32u($dataPt, $valuePtr); + # convert offset to pointer in $$dataPt + # (don't yet handle EntryBased or FixOffsets) + $valuePtr -= $dataPos; + if ($valuePtr < 0 or $valuePtr+$size > $dataLen) { + next unless $raf and $raf->Seek($base + $valuePtr + $dataPos,0) and + $raf->Read($buff,$size) == $size; + $$tagHash{$tagID} = ReadValue(\$buff,0,$formatStr,$count,$size); + next; + } + } + $$tagHash{$tagID} = ReadValue($dataPt,$valuePtr,$formatStr,$count,$size); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process Nikon Capture history data +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessNikonCaptureEditVersions($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + require Image::ExifTool::NikonCapture; + return Image::ExifTool::NikonCapture::ProcessNikonCaptureEditVersions($et, $dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Process Nikon Capture Offsets IFD (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# Notes: This isn't a normal IFD, but is close... +sub ProcessNikonCaptureOffsets($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + my $success = 0; + return 0 unless $dirLen > 2; + my $count = Get16u($dataPt, $dirStart); + return 0 unless $count and $count * 12 + 2 <= $dirLen; + if ($et->Options('Verbose')) { + $et->VerboseDir('NikonCaptureOffsets', $count); + } + my $index; + for ($index=0; $index<$count; ++$index) { + my $pos = $dirStart + 12 * $index + 2; + my $tagID = Get32u($dataPt, $pos); + my $value = Get32u($dataPt, $pos + 4); + $et->HandleTag($tagTablePtr, $tagID, $value, + Index => $index, + DataPt => $dataPt, + Start => $pos, + Size => 12, + ) and $success = 1; + } + return $success; +} + +#------------------------------------------------------------------------------ +# Read/write Nikon MakerNotes directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning when reading +# or new directory when writing (IsWriting set in dirInfo) +sub ProcessNikon($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + + # pre-scan IFD to get SerialNumber (0x001d) and ShutterCount (0x00a7) for use in decryption + my %needTags = ( 0x001d => 0, 0x00a7 => undef ); + PrescanExif($et, $dirInfo, \%needTags); + $$et{NikonSerialKey} = SerialKey($et, $needTags{0x001d}); + $$et{NikonCountKey} = $needTags{0x00a7}; + + # process Nikon makernotes + my $rtnVal; + if ($$dirInfo{IsWriting}) { + # get new decryptino keys if they are being changed + my $serial = $et->GetNewValue($Image::ExifTool::Nikon::Main{0x001d}); + my $count = $et->GetNewValue($Image::ExifTool::Nikon::Main{0x00a7}); + $$et{NewNikonSerialKey} = SerialKey($et, $serial); + $$et{NewNikonCountKey} = $count; + $rtnVal = Image::ExifTool::Exif::WriteExif($et, $dirInfo, $tagTablePtr); + delete $$et{NewNikonSerialKey}; + delete $$et{NewNikonCountKey}; + } else { + $rtnVal = Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $tagTablePtr); + } + delete $$et{NikonSerialKey}; + delete $$et{NikonCountKey}; + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Nikon - Nikon 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 +Nikon maker notes in EXIF information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html> + +=item L<http://www.cybercom.net/~dcoffin/dcraw/> + +=item L<http://members.aol.com/khancock/pilot/nbuddy/> + +=item L<http://www.rottmerhusen.com/objektives/lensid/thirdparty.html> + +=item L<http://homepage3.nifty.com/kamisaka/makernote/makernote_nikon.htm> + +=item L<http://www.wohlberg.net/public/software/photo/nstiffexif/> + +=item (...plus lots of testing with Best Buy store demos!) + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Joseph Heled, Thomas Walter, Brian Ristuccia, Danek Duvall, Tom +Christiansen, Robert Rottmerhusen, Werner Kober, Roger Larsson, Jens Duttke, +Gregor Dorlars, Neil Nappe, Alexandre Naaman, Brendt Wohlberg and Warren +Hatch for their help figuring out some Nikon tags, and to everyone who +helped contribute to the LensID list. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Nikon Tags>, +L<Image::ExifTool::TagNames/NikonCapture Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/NikonCapture.pm b/ExifTool/lib/Image/ExifTool/NikonCapture.pm new file mode 100644 index 0000000..df83271 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/NikonCapture.pm @@ -0,0 +1,940 @@ +#------------------------------------------------------------------------------ +# File: NikonCapture.pm +# +# Description: Read/write Nikon Capture information +# +# Revisions: 11/08/2005 - P. Harvey Created +# 10/10/2008 - P. Harvey Updated for Capture NX 2 +# 16/04/2011 - P. Harvey Decode NikonCaptureEditVersions +# +# References: 1) http://www.cybercom.net/~dcoffin/dcraw/ +# IB) Iliah Borg private communication (LibRaw) +#------------------------------------------------------------------------------ + +package Image::ExifTool::NikonCapture; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.14'; + +sub ProcessNikonCapture($$$); + +# common print conversions +my %offOn = ( 0 => 'Off', 1 => 'On' ); +my %noYes = ( 0 => 'No', 1 => 'Yes' ); +my %unsharpColor = ( + 0 => 'RGB', + 1 => 'Red', + 2 => 'Green', + 3 => 'Blue', + 4 => 'Yellow', + 5 => 'Magenta', + 6 => 'Cyan', +); + +# Nikon Capture data (ref PH) +%Image::ExifTool::NikonCapture::Main = ( + PROCESS_PROC => \&ProcessNikonCapture, + WRITE_PROC => \&WriteNikonCapture, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => q{ + This information is written by the Nikon Capture software in tag 0x0e01 of + the maker notes of NEF images. + }, + # 0x007ddc9d contains contrast information + 0x008ae85e => { + Name => 'LCHEditor', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x0c89224b => { + Name => 'ColorAberrationControl', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x116fea21 => { + Name => 'HighlightData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::HighlightData', + }, + }, + 0x2175eb78 => { + Name => 'D-LightingHQ', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x2fc08431 => { + Name => 'StraightenAngle', + Writable => 'double', + }, + 0x374233e0 => { + Name => 'CropData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::CropData', + }, + }, + 0x39c456ac => { + Name => 'PictureCtrl', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::PictureCtrl', + }, + }, + 0x3cfc73c6 => { + Name => 'RedEyeData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::RedEyeData', + }, + }, + 0x3d136244 => { + Name => 'EditVersionName', + Writable => 'string', # (null terminated) + }, + # 0x3e726567 added when I rotated by 90 degrees + 0x416391c6 => { + Name => 'QuickFix', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x56a54260 => { + Name => 'Exposure', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::Exposure', + }, + }, + 0x5f0e7d23 => { + Name => 'ColorBooster', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x6a6e36b6 => { + Name => 'D-LightingHQSelected', + Writable => 'int8u', + PrintConv => \%noYes, + }, + 0x753dcbc0 => { + Name => 'NoiseReduction', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x76a43200 => { + Name => 'UnsharpMask', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x76a43201 => { + Name => 'Curves', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x76a43202 => { + Name => 'ColorBalanceAdj', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x76a43203 => { + Name => 'AdvancedRaw', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x76a43204 => { + Name => 'WhiteBalanceAdj', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x76a43205 => { + Name => 'VignetteControl', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0x76a43206 => { + Name => 'FlipHorizontal', + Writable => 'int8u', + PrintConv => \%noYes, + }, + 0x76a43207 => { # rotation angle in degrees + Name => 'Rotation', + Writable => 'int16u', + }, + 0x083a1a25 => { + Name => 'HistogramXML', + Writable => 'undef', + Binary => 1, + AdjustSize => 4, # patch Nikon bug + }, + 0x84589434 => { + Name => 'BrightnessData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::Brightness', + }, + }, + # 0x88f55e48 - related to QuickFix + 0x890ff591 => { + Name => 'D-LightingHQData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::DLightingHQ', + }, + }, + 0x926f13e0 => { + Name => 'NoiseReductionData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::NoiseReduction', + }, + }, + 0x9ef5f6e0 => { + Name => 'IPTCData', + SubDirectory => { + TagTable => 'Image::ExifTool::IPTC::Main', + }, + }, + # 0xa7264a72 - related to QuickFix + 0xab5eca5e => { + Name => 'PhotoEffects', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0xac6bd5c0 => { + Name => 'VignetteControlIntensity', + Writable => 'int16s', + }, + 0xb0384e1e => { + Name => 'PhotoEffectsData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::PhotoEffects', + }, + }, + 0xb999a36f => { + Name => 'ColorBoostData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::ColorBoost', + }, + }, + 0xbf3c6c20 => { + Name => 'WBAdjData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::WBAdjData', + }, + }, + 0xce5554aa => { + Name => 'D-LightingHS', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0xe2173c47 => { + Name => 'PictureControl', + Writable => 'int8u', + PrintConv => \%offOn, + }, + 0xe37b4337 => { + Name => 'D-LightingHSData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::DLightingHS', + }, + }, + 0xe42b5161 => { + Name => 'UnsharpData', + SubDirectory => { + TagTable => 'Image::ExifTool::NikonCapture::UnsharpData', + }, + }, + 0xe9651831 => { + Name => 'PhotoEffectHistoryXML', + Binary => 1, + Writable => 'undef', + }, + 0xfe28a44f => { + Name => 'AutoRedEye', + Writable => 'int8u', + PrintConv => \%offOn, # (have seen a value of 28 here for older software?) + }, + 0xfe443a45 => { + Name => 'ImageDustOff', + Writable => 'int8u', + PrintConv => \%offOn, + }, +); + +%Image::ExifTool::NikonCapture::UnsharpData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => 'UnsharpCount', + 19 => { Name => 'Unsharp1Color', Format => 'int16u', PrintConv => \%unsharpColor }, + 23 => { Name => 'Unsharp1Intensity', Format => 'int16u' }, + 25 => { Name => 'Unsharp1HaloWidth', Format => 'int16u' }, + 27 => 'Unsharp1Threshold', + 46 => { Name => 'Unsharp2Color', Format => 'int16u', PrintConv => \%unsharpColor }, + 50 => { Name => 'Unsharp2Intensity', Format => 'int16u' }, + 52 => { Name => 'Unsharp2HaloWidth', Format => 'int16u' }, + 54 => 'Unsharp2Threshold', + 73 => { Name => 'Unsharp3Color', Format => 'int16u', PrintConv => \%unsharpColor }, + 77 => { Name => 'Unsharp3Intensity', Format => 'int16u' }, + 79 => { Name => 'Unsharp3HaloWidth', Format => 'int16u' }, + 81 => 'Unsharp3Threshold', + 100 => { Name => 'Unsharp4Color', Format => 'int16u', PrintConv => \%unsharpColor }, + 104 => { Name => 'Unsharp4Intensity', Format => 'int16u' }, + 106 => { Name => 'Unsharp4HaloWidth', Format => 'int16u' }, + 108 => 'Unsharp4Threshold', + # there could be more, but I grow bored of this... :P +); + +%Image::ExifTool::NikonCapture::DLightingHS = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => 'D-LightingHSAdjustment', + 1 => 'D-LightingHSColorBoost', +); + +%Image::ExifTool::NikonCapture::DLightingHQ = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => 'D-LightingHQShadow', + 1 => 'D-LightingHQHighlight', + 2 => 'D-LightingHQColorBoost', +); + +%Image::ExifTool::NikonCapture::ColorBoost = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => { + Name => 'ColorBoostType', + PrintConv => { + 0 => 'Nature', + 1 => 'People', + }, + }, + 1 => { + Name => 'ColorBoostLevel', + Format => 'int32u', + }, +); + +%Image::ExifTool::NikonCapture::WBAdjData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x00 => { + Name => 'WBAdjRedBalance', + Format => 'double', + }, + 0x08 => { + Name => 'WBAdjBlueBalance', + Format => 'double', + }, + 0x10 => { + Name => 'WBAdjMode', + PrintConv => { + 1 => 'Use Gray Point', + 2 => 'Recorded Value', + 3 => 'Use Temperature', + 4 => 'Calculate Automatically', + 5 => 'Auto2', #IB + 6 => 'Underwater', #IB + 7 => 'Auto1', + }, + }, + 0x14 => { #IB + Name => 'WBAdjLighting', + Format => 'int16u', + PrintHex => 1, + PrintConv => { + 0x000 => 'None', + 0x100 => 'Incandescent', + 0x200 => 'Daylight (direct sunlight)', + 0x201 => 'Daylight (shade)', + 0x202 => 'Daylight (cloudy)', + 0x300 => 'Standard Fluorescent (warm white)', + 0x301 => 'Standard Fluorescent (3700K)', + 0x302 => 'Standard Fluorescent (cool white)', + 0x303 => 'Standard Fluorescent (5000K)', + 0x304 => 'Standard Fluorescent (daylight)', + 0x305 => 'Standard Fluorescent (high temperature mercury vapor)', + 0x400 => 'High Color Rendering Fluorescent (warm white)', + 0x401 => 'High Color Rendering Fluorescent (3700K)', + 0x402 => 'High Color Rendering Fluorescent (cool white)', + 0x403 => 'High Color Rendering Fluorescent (5000K)', + 0x404 => 'High Color Rendering Fluorescent (daylight)', + 0x500 => 'Flash', + 0x501 => 'Flash (FL-G1 filter)', + 0x502 => 'Flash (FL-G2 filter)', + 0x503 => 'Flash (TN-A1 filter)', + 0x504 => 'Flash (TN-A2 filter)', + 0x600 => 'Sodium Vapor Lamps', + # 0x1002 => seen for WBAdjMode modes of Underwater and Calculate Automatically + }, + }, + 0x18 => { + Name => 'WBAdjTemperature', + Format => 'int16u', + }, + 0x25 => { + Name => 'WBAdjTint', + Format => 'int32s', + }, +); + +%Image::ExifTool::NikonCapture::PhotoEffects = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => { + Name => 'PhotoEffectsType', + PrintConv => { + 0 => 'None', + 1 => 'B&W', + 2 => 'Sepia', + 3 => 'Tinted', + }, + }, + 4 => { + Name => 'PhotoEffectsRed', + Format => 'int16s', + }, + 6 => { + Name => 'PhotoEffectsGreen', + Format => 'int16s', + }, + 8 => { + Name => 'PhotoEffectsBlue', + Format => 'int16s', + }, +); + +%Image::ExifTool::NikonCapture::Brightness = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => { + Name => 'BrightnessAdj', + Format => 'double', + ValueConv => '$val * 50', + ValueConvInv => '$val / 50', + }, + 8 => { + Name => 'EnhanceDarkTones', + PrintConv => \%offOn, + }, +); + +%Image::ExifTool::NikonCapture::NoiseReduction = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x04 => { + Name => 'EdgeNoiseReduction', + PrintConv => \%offOn, + }, + 0x05 => { + Name => 'ColorMoireReductionMode', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Medium', + 3 => 'High', + }, + }, + 0x09 => { + Name => 'NoiseReductionIntensity', + Format => 'int32u', + }, + 0x0d => { + Name => 'NoiseReductionSharpness', + Format => 'int32u', + }, + 0x11 => { + Name => 'NoiseReductionMethod', + Format => 'int16u', + PrintConv => { + 0 => 'Faster', + 1 => 'Better Quality', + 2 => 'Better Quality 2013', + }, + }, + 0x15 => { + Name => 'ColorMoireReduction', + PrintConv => \%offOn, + }, + 0x17 => { + Name => 'NoiseReduction', + PrintConv => \%offOn, + }, + 0x18 => { + Name => 'ColorNoiseReductionIntensity', + Format => 'int32u', + }, + 0x1c => { + Name => 'ColorNoiseReductionSharpness', + Format => 'int32u', + }, +); + +%Image::ExifTool::NikonCapture::CropData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x1e => { + Name => 'CropLeft', + Format => 'double', + ValueConv => '$val / 2', + ValueConvInv => '$val * 2', + }, + 0x26 => { + Name => 'CropTop', + Format => 'double', + ValueConv => '$val / 2', + ValueConvInv => '$val * 2', + }, + 0x2e => { + Name => 'CropRight', + Format => 'double', + ValueConv => '$val / 2', + ValueConvInv => '$val * 2', + }, + 0x36 => { + Name => 'CropBottom', + Format => 'double', + ValueConv => '$val / 2', + ValueConvInv => '$val * 2', + }, + 0x8e => { + Name => 'CropOutputWidthInches', + Format => 'double', + }, + 0x96 => { + Name => 'CropOutputHeightInches', + Format => 'double', + }, + 0x9e => { + Name => 'CropScaledResolution', + Format => 'double', + }, + 0xae => { + Name => 'CropSourceResolution', + Format => 'double', + ValueConv => '$val / 2', + ValueConvInv => '$val * 2', + }, + 0xb6 => { + Name => 'CropOutputResolution', + Format => 'double', + }, + 0xbe => { + Name => 'CropOutputScale', + Format => 'double', + }, + 0xc6 => { + Name => 'CropOutputWidth', + Format => 'double', + }, + 0xce => { + Name => 'CropOutputHeight', + Format => 'double', + }, + 0xd6 => { + Name => 'CropOutputPixels', + Format => 'double', + }, +); + +%Image::ExifTool::NikonCapture::PictureCtrl = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x00 => { + Name => 'PictureControlActive', + PrintConv => \%offOn, + }, + 0x13 => { + Name => 'PictureControlMode', + Format => 'string[16]', + }, + # 0x29 changes with Hue and Sharpening + 0x2a => { + Name => 'QuickAdjust', + ValueConv => '$val - 128', + ValueConvInv => '$val + 128', + }, + 0x2b => { + Name => 'SharpeningAdj', + ValueConv => '$val ? $val - 128 : "Auto"', + ValueConvInv => '$val=~/\d/ ? $val + 128 : 0', + }, + 0x2c => { + Name => 'ContrastAdj', + ValueConv => '$val ? $val - 128 : "Auto"', + ValueConvInv => '$val=~/\d/ ? $val + 128 : 0', + }, + 0x2d => { + Name => 'BrightnessAdj', + ValueConv => '$val ? $val - 128 : "Auto"', # no "Auto" mode (yet) for this setting + ValueConvInv => '$val=~/\d/ ? $val + 128 : 0', + }, + 0x2e => { + Name => 'SaturationAdj', + ValueConv => '$val ? $val - 128 : "Auto"', + ValueConvInv => '$val=~/\d/ ? $val + 128 : 0', + }, + 0x2f => { + Name => 'HueAdj', + ValueConv => '$val - 128', + ValueConvInv => '$val + 128', + }, + # 0x37 changed from 0 to 2 when Picture Control is enabled (and no active DLighting) +); + +%Image::ExifTool::NikonCapture::RedEyeData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => { + Name => 'RedEyeCorrection', + PrintConv => { + 0 => 'Off', + 1 => 'Automatic', + 2 => 'Click on Eyes', + }, + }, +); + +%Image::ExifTool::NikonCapture::Exposure = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x00 => { + Name => 'ExposureAdj', + Format => 'int16s', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + }, + 0x12 => { + Name => 'ExposureAdj2', + Format => 'double', + PrintConv => 'sprintf("%.4f", $val)', + PrintConvInv => '$val', + }, + 0x24 => { + Name => 'ActiveD-Lighting', + PrintConv => \%offOn, + }, + 0x25 => { + Name => 'ActiveD-LightingMode', + PrintConv => { + 0 => 'Unchanged', + 1 => 'Off', + 2 => 'Low', + 3 => 'Normal', + 4 => 'High', + 6 => 'Extra High', + 7 => 'Extra High 1', + 8 => 'Extra High 2', + }, + }, +); + +%Image::ExifTool::NikonCapture::HighlightData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int8s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => 'ShadowProtection', + 1 => 'SaturationAdj', + 6 => 'HighlightProtection', +); + +#------------------------------------------------------------------------------ +# write Nikon Capture data (ref 1) +# Inputs: 0) ExifTool object reference, 1) reference to directory information +# 2) pointer to tag table +# Returns: 1 on success +sub WriteNikonCapture($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + + # no need to edit this information unless necessary + unless ($$et{EDIT_DIRS}{MakerNotes} or $$et{EDIT_DIRS}{IPTC}) { + return undef; + } + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + if ($dirLen < 22) { + $et->Warn('Short Nikon Capture Data',1); + return undef; + } + # make sure the capture data is properly contained + SetByteOrder('II'); + my $tagID = Get32u($dataPt, $dirStart); + # sometimes size includes 18 header bytes, and other times it doesn't (eg. ViewNX 2.1.1) + my $size = Get32u($dataPt, $dirStart + 18); + my $pad = $dirLen - $size - 18; + unless ($tagID == 0x7a86a940 and ($pad >= 0 or $pad == -18)) { + $et->Warn('Unrecognized Nikon Capture Data header'); + return undef; + } + # determine if there is any data after this block + if ($pad > 0) { + $pad = substr($$dataPt, $dirStart + 18 + $size, $pad); + $dirLen = $size + 18; + } else { + $pad = ''; + } + my $outBuff = ''; + my $pos; + my $newTags = $et->GetNewTagInfoHash($tagTablePtr); + my $dirEnd = $dirStart + $dirLen; + + # loop through all entries in the Nikon Capture data + for ($pos=$dirStart+22; $pos+22<$dirEnd; $pos+=22+$size) { + $tagID = Get32u($dataPt, $pos); + $size = Get32u($dataPt, $pos + 18) - 4; + last if $size < 0 or $pos + 22 + $size > $dirEnd; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID); + if ($tagInfo) { + my $newVal; + if ($$tagInfo{SubDirectory}) { + # rewrite the subdirectory + my %subdirInfo = ( + DataPt => $dataPt, + DirStart => $pos + 22, + DirLen => $size, + ); + my $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + # ignore minor errors in IPTC since there is typically trailing garbage + my $oldSetting = $et->Options('IgnoreMinorErrors'); + $$tagInfo{Name} =~ /IPTC/ and $et->Options(IgnoreMinorErrors => 1); + # rewrite the directory + $newVal = $et->WriteDirectory(\%subdirInfo, $subTable); + # restore our original options + $et->Options(IgnoreMinorErrors => $oldSetting); + } elsif ($$newTags{$tagID}) { + # get new value for this tag if we are writing it + my $format = $$tagInfo{Format} || $$tagInfo{Writable}; + my $oldVal = ReadValue($dataPt,$pos+22,$format,1,$size); + my $nvHash = $et->GetNewValueHash($tagInfo); + if ($et->IsOverwriting($nvHash, $oldVal)) { + my $val = $et->GetNewValue($tagInfo); + $newVal = WriteValue($val, $$tagInfo{Writable}) if defined $val; + if (defined $newVal and length $newVal) { + ++$$et{CHANGED}; + } else { + undef $newVal; + $et->Warn("Can't delete $$tagInfo{Name}"); + } + } + } + if (defined $newVal) { + next unless length $newVal; # don't write zero length information + # write the new value + $outBuff .= substr($$dataPt, $pos, 18); + $outBuff .= Set32u(length($newVal) + 4); + $outBuff .= $newVal; + next; + } + } + # rewrite the existing information + $outBuff .= substr($$dataPt, $pos, 22 + $size); + } + unless ($pos == $dirEnd) { + if ($pos == $dirEnd - 4) { + # it seems that sometimes (NX2) the main block size is wrong by 4 bytes + # (did they forget to include the size word?) + $outBuff .= substr($$dataPt, $pos, 4); + } else { + $et->Warn('Nikon Capture Data improperly terminated',1); + return undef; + } + } + # add the header and return the new directory + return substr($$dataPt, $dirStart, 18) . + Set32u(length($outBuff) + 4) . + $outBuff . $pad; +} + +#------------------------------------------------------------------------------ +# process Nikon Capture data (ref 1) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessNikonCaptureEditVersions($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + my $dirEnd = $dirStart + $dirLen; + my $verbose = $et->Options('Verbose'); + SetByteOrder('II'); + return 0 unless $dirLen > 4; + my $num = Get32u($dataPt, $dirStart); + my $pos = $dirStart + 4; + $verbose and $et->VerboseDir('NikonCaptureEditVersions', $num); + while ($num) { + last if $pos + 4 > $dirEnd; + my $len = Get32u($dataPt, $pos); + last if $pos + $len + 4 > $dirEnd; + my %dirInfo = ( + DirName => 'NikonCapture', + Parent => 'NikonCaptureEditVersions', + DataPt => $dataPt, + DirStart => $pos + 4, + DirLen => $len, + ); + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + --$num; + $pos += $len + 4; + } + delete $$et{DOC_NUM}; + return 1; +} + +#------------------------------------------------------------------------------ +# process Nikon Capture data (ref 1) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessNikonCapture($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + my $dirEnd = $dirStart + $dirLen; + my $verbose = $et->Options('Verbose'); + my $success = 0; + SetByteOrder('II'); + $verbose and $et->VerboseDir('NikonCapture', 0, $dirLen); + my $pos; + for ($pos=$dirStart+22; $pos+22<$dirEnd; ) { + my $tagID = Get32u($dataPt, $pos); + my $size = Get32u($dataPt, $pos + 18) - 4; + $pos += 22; + last if $size < 0 or $pos + $size > $dirEnd; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID); + if ($tagInfo or $verbose) { + my ($format, $value); + # (note that Writable will be 0 for Unknown tags) + $tagInfo and $format = ($$tagInfo{Format} || $$tagInfo{Writable}); + # generate a reasonable default format type for short values + if (not $format and ($size == 1 or $size == 2 or $size == 4)) { + $format = 'int' . ($size * 8) . 'u'; + } + if ($format) { + my $count = 1; + if ($format eq 'string' or $format eq 'undef') { + # patch Nikon bug in size of some values (HistogramXML) + $size += $$tagInfo{AdjustSize} if $tagInfo and $$tagInfo{AdjustSize}; + $count = $size; + } + $value = ReadValue($dataPt,$pos,$format,$count,$size); + } elsif ($size == 1) { + $value = substr($$dataPt, $pos, $size); + } + $et->HandleTag($tagTablePtr, $tagID, $value, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Base => $$dirInfo{Base}, + Start => $pos, + Size => $size, + ) and $success = 1; + } + $pos += $size; + } + return $success; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::NikonCapture - Read/write Nikon Capture information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains routines to read and write Nikon Capture information in +the maker notes of NEF images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 SEE ALSO + +L<Image::ExifTool::TagNames/NikonCapture Tags>, +L<Image::ExifTool::TagNames/Nikon Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/NikonCustom.pm b/ExifTool/lib/Image/ExifTool/NikonCustom.pm new file mode 100644 index 0000000..85d81b0 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/NikonCustom.pm @@ -0,0 +1,10918 @@ +#------------------------------------------------------------------------------ +# File: NikonCustom.pm +# +# Description: Read and write Nikon Custom settings +# +# Revisions: 2009/11/25 - P. Harvey Created +# +# References: 1) Warren Hatch private communication (D3 and Z9 with SB-800 and SB-900) +# 2) Anonymous contribution 2011/05/25 (D700, D7000) +# JD) Jens Duttke private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::NikonCustom; + +use strict; +use vars qw($VERSION @ISA @EXPORT_OK %buttonsZ9); + +$VERSION = '1.24'; + +@ISA = qw(Exporter); +@EXPORT_OK = qw(%buttonsZ9); + +%buttonsZ9= ( + SeparateTable => 'ButtonsZ9', + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 3 => 'FV Lock', + 4 => 'AE/AF Lock', + 5 => 'AE Lock Only', + 6 => 'AE Lock (reset on release)', + 7 => 'AE Lock (hold)', + 8 => 'AF Lock Only', + 9 => 'AF-On', + 10 => 'Flash Disable/Enable', + 11 => 'Bracketing Burst', + 12 => '+NEF(RAW)', + 18 => 'Virtual Horizon', + 19 => 'Synchronized Release', + 20 => 'My Menu', + 21 => 'My Menu Top Item', + 22 => 'Playback', + 23 => 'Rating', + 24 => 'Protect', + 25 => 'Zoom', + 26 => 'Focus Peaking', + 27 => 'Flash Mode/Compensation', + 28 => 'Image Area', + 30 => 'Non-CPU Lens', + 31 => 'Active-D Lighting', + 32 => 'Exposure Delay Mode', + 33 => '1 Stop Speed/Aperture', + 34 => 'White Balance', + 35 => 'Metering', + 36 => 'Auto Bracketing', + 37 => 'Multiple Exposure', + 38 => 'HDR Overlay', + 39 => 'Picture Control', + 40 => 'Quality', + 41 => 'Focus Mode/AF AreaMode', + 42 => 'Select Center Focus Point', + 44 => 'Record Movie', + 45 => 'Thumbnail On/Off', + 46 => 'View Histograms', + 47 => 'Choose Folder', + 48 => 'Power Aperture (Open)', + 49 => 'Power Aperture (Close)', + 52 => 'Microphone Sensitivity', + 53 => 'Release Mode', + 57 => 'Preset Focus Point', + 58 => 'AE/AWB Lock (hold)', + 59 => 'AF-AreaMode', + 60 => 'AF-AreaMode + AF-On', + 61 => 'Recall Shooting Functions', + 64 => 'Filtered Playback', + 65 => 'Same as AF-On', + 66 => 'Voice Memo', + 70 => 'Photo Shooting Bank', + 71 => 'ISO', + 73 => 'Exposure Compensation', + 76 => 'Silent Mode', + 78 => 'LiveView Information', + 79 => 'AWB Lock (hold)', + 80 => 'Grid Display', + 81 => 'Starlight View', + 82 => 'Select To Send (PC)', + 83 => 'Select To Send (FTP)', + 84 => 'Pattern Tone Range', + 85 => 'Control Lock', + 86 => 'Save Focus Position', + 87 => 'Recall Focus Position', + 88 => 'Recall Shooting Functions (Hold)', + 97 => 'High Frequency Flicker Reduction', + 98 => 'Switch FX/DX', + 99 => 'View Mode (Photo LV)', + 100 => 'Photo Flicker Reduction', + 101 => 'Filtered Playback (Select Criteria)', + 103 => 'Start Series Playback', + 104 => 'View Assist', + 105 => 'Hi-Res Zoom+', + 106 => 'Hi-Res Zoom-', + 108 => 'Override Other Cameras', + 109 => 'DISP - Cycle Information Display (shooting)', # Shooting Mode + 110 => 'DISP - Cycle Information Display (playback)', # Playback mode + 111 => 'Resume Shooting', + 112 => 'Switch Eyes', + 115 => 'Delete', + }, +); +my %dialsZ9 = ( + 0 => '1 Frame', + 1 => '10 Frames', + 2 => '50 Frames', + 3 => 'Folder', + 4 => 'Protect', + 5 => 'Photos Only', + 6 => 'Videos Only', + 7 => 'Rating', + 8 => 'Page', + 9 => 'Skip To First Shot In Series', +); +my %evfGridsZ9 = ( + 0 => '3x3', + 1 => '4x4', + 2 => '2.35:1', + 3 => '1.85:1', + 4 => '5:4', + 5 => '4:3', + 6 => '1:1', + 7 => '16:9', + 8 => '90%', +); +my %flicksZ9 = ( + 0 => 'Rating', + 1 => 'Select To Send (PC)', + 2 => 'Select To Send (FTP)', + 3 => 'Protect', + 4 => 'Voice Memo', + 5 => 'None', +); +my %focusModeRestrictionsZ9 = ( + 0 => 'AF-S', + 1 => 'AF-C', + 2 => 'Full-time AF', + 3 => 'Manual', + 4 => 'No Restrictions', +); +my %powerOffDelayTimesZ9 = ( + SeparateTable => 'DelaysZ9', + PrintConv => { + 0 => '2 s', + 1 => '4 s', + 3 => '10 s', + 4 => '20 s', + 5 => '30 s', + 6 => '1 min', + 7 => '5 min', + 8 => '10 min', + 11 => '30 min', + 12 => 'No Limit', + }, +); +my %thirdHalfFull = ( + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', +); + +my %limitNolimit = ( 0 => 'Limit', 1 => 'No Limit' ); +my %offOn = ( 0 => 'Off', 1 => 'On' ); +my %onOff = ( 0 => 'On', 1 => 'Off' ); +my %noYes = ( 0 => 'No', 1 => 'Yes' ); + +# custom settings for the D80 (encrypted) - ref JD +%Image::ExifTool::NikonCustom::SettingsD80 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the Nikon D80.', + 0.1 => { # CS1 + Name => 'Beep', + Mask => 0x80, + PrintConv => \%onOff, + }, + 0.2 => { # CS4 + Name => 'AFAssist', + Mask => 0x40, + PrintConv => \%onOff, + }, + 0.3 => { # CS5 + Name => 'NoMemoryCard', + Mask => 0x20, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 0.4 => { # CS6 + Name => 'ImageReview', + Mask => 0x10, + PrintConv => \%onOff, + }, + 0.5 => { # CS17 + Name => 'Illumination', + Mask => 0x08, + PrintConv => \%offOn, + }, + 0.6 => { # CS11 + Name => 'MainDialExposureComp', + Mask => 0x04, + PrintConv => \%offOn, + }, + 0.7 => { # CS10 + Name => 'EVStepSize', + Mask => 0x01, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + }, + }, + 1.1 => { # CS7 + Name => 'AutoISO', + Mask => 0x40, + PrintConv => \%offOn, + }, + 1.2 => { # CS7-a + Name => 'AutoISOMax', + Mask => 0x30, + PrintConv => { + 0 => 200, + 1 => 400, + 2 => 800, + 3 => 1600, + }, + }, + 1.3 => { # CS7-b + Name => 'AutoISOMinShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/125 s', + 1 => '1/100 s', + 2 => '1/80 s', + 3 => '1/60 s', + 4 => '1/40 s', + 5 => '1/30 s', + 6 => '1/15 s', + 7 => '1/8 s', + 8 => '1/4 s', + 9 => '1/2 s', + 10 => '1 s', + }, + }, + 2.1 => { # CS13 + Name => 'AutoBracketSet', + Mask => 0xc0, + PrintConv => { + 0 => 'AE & Flash', + 1 => 'AE Only', + 2 => 'Flash Only', + 3 => 'WB Bracketing', + }, + }, + 2.2 => { # CS14 + Name => 'AutoBracketOrder', + Mask => 0x20, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }, + 3.1 => { # CS27 + Name => 'MonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '5 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 3.2 => { # CS28 + Name => 'MeteringTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 1 => '6 s', + 2 => '8 s', + 3 => '16 s', + 4 => '30 s', + 5 => '30 min', + }, + }, + 3.3 => { # CS29 + Name => 'SelfTimerTime', + Mask => 0x03, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 4.1 => { # CS18 + Name => 'AELockButton', + Mask => 0x1e, + PrintConv => { + 0 => 'AE/AF Lock', + 1 => 'AE Lock Only', + 2 => 'AF Lock Only', + 3 => 'AE Lock (hold)', + 4 => 'AF-ON', + 5 => 'FV Lock', + 6 => 'Focus Area Selection', + 7 => 'AE-L/AF-L/AF Area', + 8 => 'AE-L/AF Area', + 9 => 'AF-L/AF Area', + 10 => 'AF-ON/AF Area', + }, + }, + 4.2 => { # CS19 + Name => 'AELock', + Mask => 0x01, + PrintConv => \%offOn, + }, + 4.3 => { # CS30 + Name => 'RemoteOnDuration', + Mask => 0xc0, + PrintConv => { + 0 => '1 min', + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + }, + }, + 5.1 => { # CS15 + Name => 'CommandDials', + Mask => 0x80, + PrintConv => { + 0 => 'Standard (Main Shutter, Sub Aperture)', + 1 => 'Reversed (Main Aperture, Sub Shutter)', + }, + }, + 5.2 => { # CS16 + Name => 'FunctionButton', + Mask => 0x78, + PrintConv => { + 0 => 'ISO Display', + 1 => 'Framing Grid', + 2 => 'AF-area Mode', + 3 => 'Center AF Area', + 4 => 'FV Lock', + 5 => 'Flash Off', + 6 => 'Matrix Metering', + 7 => 'Center-weighted', + 8 => 'Spot Metering', + }, + }, + 6.1 => { # CS8 + Name => 'GridDisplay', + Mask => 0x80, + PrintConv => \%offOn, + }, + 6.2 => { # CS9 + Name => 'ViewfinderWarning', + Mask => 0x40, + PrintConv => \%onOff, + }, + 6.3 => { # CS12 + Name => 'CenterWeightedAreaSize', + Mask => 0x0c, + PrintConv => { + 0 => '6 mm', + 1 => '8 mm', + 2 => '10 mm', + }, + }, + 6.4 => { # CS31 + Name => 'ExposureDelayMode', + Mask => 0x20, + PrintConv => \%offOn, + }, + 6.5 => { # CS32 + Name => 'MB-D80Batteries', + Mask => 0x03, + PrintConv => { + 0 => 'LR6 (AA Alkaline)', + 1 => 'HR6 (AA Ni-MH)', + 2 => 'FR6 (AA Lithium)', + 3 => 'ZR6 (AA Ni-Mg)', + }, + }, + 7.1 => { # CS23 + Name => 'FlashWarning', + Mask => 0x80, + PrintConv => \%onOff, + }, + 7.2 => { # CS24 + Name => 'FlashShutterSpeed', + Mask => 0x78, + ValueConv => '2 ** ($val - 6)', + ValueConvInv => '$val>0 ? int(log($val)/log(2)+6+0.5) : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 7.3 => { # CS25 + Name => 'AutoFP', + Mask => 0x04, + PrintConv => \%offOn, + }, + 7.4 => { # CS26 + Name => 'ModelingFlash', + Mask => 0x02, + PrintConv => \%offOn, + }, + 8.1 => { # CS22 + Name => 'InternalFlash', + Mask => 0xc0, + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + 2 => 'Repeating Flash', + 3 => 'Commander Mode', + }, + }, + 8.2 => { # CS22-a + Name => 'ManualFlashOutput', + Mask => 0x07, + ValueConv => '2 ** (-$val)', + ValueConvInv => '$val > 0 ? -log($val)/log(2) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.1 => { # CS22-b + Name => 'RepeatingFlashOutput', + Mask => 0x70, + ValueConv => '2 ** (-$val-2)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2)-2+0.5) : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.2 => { # CS22-c + Name => 'RepeatingFlashCount', + Mask => 0x0f, + ValueConv => '$val < 10 ? $val + 1 : 5 * ($val - 7)', + ValueConvInv => '$val <= 10 ? $val - 1 : $val / 5 + 7', + }, + 10.1 => { # CS22-d + Name => 'RepeatingFlashRate', + Mask => 0xf0, + ValueConv => '$val < 10 ? $val + 1 : 10 * ($val - 8)', + ValueConvInv => 'int(($val <= 10 ? $val - 1 : $val / 10 + 8) + 0.5)', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 10.2 => { # CS22-n + Name => 'CommanderChannel', + Mask => 0x03, + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 11.1 => { # CS22-e + Name => 'CommanderInternalFlash', + Mask => 0xc0, + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + 2 => 'Off', + }, + }, + 11.2 => { # CS22-h + Name => 'CommanderGroupAMode', + Mask => 0x30, + PrintConv => { + 0 => 'TTL', + 1 => 'Auto Aperture', + 2 => 'Manual', + 3 => 'Off', + }, + }, + 11.3 => { # CS22-k + Name => 'CommanderGroupBMode', + Mask => 0x0c, + PrintConv => { + 0 => 'TTL', + 1 => 'Auto Aperture', + 2 => 'Manual', + 3 => 'Off', + }, + }, + 12.1 => { # CS22-f + Name => 'CommanderInternalTTLComp', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 12.2 => { # CS22-g + Name => 'CommanderInternalManualOutput', + Mask => 0xe0, + ValueConv => '2 ** (-$val)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2)+0.5) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 13.1 => { # CS22-i + Name => 'CommanderGroupA_TTL-AAComp', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 13.2 => { # CS22-j + Name => 'CommanderGroupAManualOutput', + Mask => 0xe0, + ValueConv => '2 ** (-$val)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2)+0.5) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 14.1 => { # CS22-l + Name => 'CommanderGroupB_TTL-AAComp', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 14.2 => { # CS22-m + Name => 'CommanderGroupBManualOutput', + Mask => 0xe0, + ValueConv => '2 ** (-$val)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2)+0.5) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 15.1 => { # CS3 + Name => 'CenterAFArea', + Mask => 0x80, + PrintConv => { + 0 => 'Normal Zone', + 1 => 'Wide Zone', + }, + }, + 15.2 => { # CS20 + Name => 'FocusAreaSelection', + Mask => 0x04, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 15.3 => { # CS21 + Name => 'AFAreaIllumination', + Mask => 0x03, + PrintConv => { + 0 => 'Auto', + 1 => 'Off', + 2 => 'On', + }, + }, + 16.1 => { # CS2 + Name => 'AFAreaModeSetting', + Mask => 0xc0, + PrintConv => { + 0 => 'Single Area', + 1 => 'Dynamic Area', + 2 => 'Auto-area', + }, + }, +); + +# custom settings for the D40 (encrypted) - ref JD +%Image::ExifTool::NikonCustom::SettingsD40 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the Nikon D40.', + 0.1 => { # CS1 + Name => 'Beep', + Mask => 0x80, + PrintConv => \%onOff, + }, + 0.2 => { # CS9 + Name => 'AFAssist', + Mask => 0x40, + PrintConv => \%onOff, + }, + 0.3 => { # CS6 + Name => 'NoMemoryCard', + Mask => 0x20, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 0.4 => { # CS7 + Name => 'ImageReview', + Mask => 0x10, + PrintConv => \%onOff, + }, + 1.1 => { # CS10-a + Name => 'AutoISO', + Mask => 0x80, + PrintConv => \%offOn, + }, + 1.2 => { # CS10-b + Name => 'AutoISOMax', + Mask => 0x30, + PrintConv => { + 1 => 400, + 2 => 800, + 3 => 1600, + }, + }, + 1.3 => { # CS10-c + Name => 'AutoISOMinShutterSpeed', + Mask => 0x07, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/125 s', + 1 => '1/60 s', + 2 => '1/30 s', + 3 => '1/15 s', + 4 => '1/8 s', + 5 => '1/4 s', + 6 => '1/2 s', + 7 => '1 s', + }, + }, + 2.1 => { # CS15-b + Name => 'ImageReviewTime', + Mask => 0x07, + PrintConv => { + 0 => '4 s', + 1 => '8 s', + 2 => '20 s', + 3 => '1 min', + 4 => '10 min', + }, + }, + 3.1 => { # CS15-a + Name => 'MonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '8 s', + 2 => '20 s', + 3 => '1 min', + 4 => '10 min', + }, + }, + 3.2 => { # CS15-c + Name => 'MeteringTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 1 => '8 s', + 2 => '20 s', + 3 => '1 min', + 4 => '30 min', + }, + }, + 3.3 => { # CS16 + Name => 'SelfTimerTime', + Mask => 0x03, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 3.4 => { # CS17 + Name => 'RemoteOnDuration', + Mask => 0xc0, + PrintConv => { + 0 => '1 min', + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + }, + }, + 4.1 => { # CS12 + Name => 'AELockButton', + Mask => 0x0e, + PrintConv => { + 0 => 'AE/AF Lock', + 1 => 'AE Lock Only', + 2 => 'AF Lock Only', + 3 => 'AE Lock (hold)', + 4 => 'AF-ON', + }, + }, + 4.2 => { # CS13 + Name => 'AELock', + Mask => 0x01, + PrintConv => \%offOn, + }, + 5.1 => { # CS4 + Name => 'ShootingModeSetting', + Mask => 0x70, + PrintConv => { + 0 => 'Single Frame', + 1 => 'Continuous', + 2 => 'Self-timer', + 3 => 'Delayed Remote', + 4 => 'Quick-response Remote', + }, + }, + 5.2 => { # CS11 + Name => 'TimerFunctionButton', + Mask => 0x07, + PrintConv => { + 0 => 'Shooting Mode', + 1 => 'Image Quality/Size', + 2 => 'ISO', + 3 => 'White Balance', + 4 => 'Self-timer', + }, + }, + 6.1 => { # CS5 + Name => 'Metering', + Mask => 0x03, + PrintConv => { + 0 => 'Matrix', + 1 => 'Center-weighted', + 2 => 'Spot', + }, + }, + 8.1 => { # CS14-a + Name => 'InternalFlash', + Mask => 0x10, + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + }, + }, + 8.2 => { # CS14-b + Name => 'ManualFlashOutput', + Mask => 0x07, + ValueConv => '2 ** (-$val)', + ValueConvInv => '$val > 0 ? -log($val)/log(2) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9 => { # CS8 + Name => 'FlashLevel', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => '$val * 6', + PrintConv => 'sprintf("%+.1f",$val)', + PrintConvInv => '$val', + }, + 10.1 => { # CS2 + Name => 'FocusModeSetting', + # (may differ from FocusMode if lens switch is set to Manual) + Mask => 0xc0, + PrintConv => { + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', + }, + }, + 11.1 => { # CS3 + Name => 'AFAreaModeSetting', + # (may differ from AFAreaMode for Manual focus) + Mask => 0x30, + PrintConv => { + 0 => 'Single Area', + 1 => 'Dynamic Area', + 2 => 'Closest Subject', + }, + } +); + +# D90 custom settings (ref PH) +%Image::ExifTool::NikonCustom::SettingsD90 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D90.', + # Missing: + # CSe2 RepeatingFlashRate (needs verification) + # CommanderInternalFlash, CommanderGroupAMode, CommanderGroupBMode, + # CommanderChannel, CommanderInternalManualOutput, + # CommanderGroupAManualOutput, CommanderGroupBManualOutput + # CommanderGroupA_TTL-AAComp, CommanderGroupB_TTL-AAComp, + # CSe4 AutoBracketSet (some values need verification) + # CSf2 OKButton ("Not Used" value needs verification) + # CSf5-b CommandDialsChangeMainSub + # CSf5-c CommandDialsMenuAndPlayback + 0.1 => { # CSf1 + Name => 'LightSwitch', + Mask => 0x08, + PrintConv => { + 0 => 'LCD Backlight', + 1 => 'LCD Backlight and Shooting Information', + }, + }, + 2.1 => { # CSa1 + Name => 'AFAreaModeSetting', + Mask => 0x60, + PrintConv => { + 0 => 'Single Area', + 1 => 'Dynamic Area', + 2 => 'Auto-area', + 3 => '3D-tracking (11 points)', + }, + }, + 2.2 => { # CSa2 + Name => 'CenterFocusPoint', + Mask => 0x10, + PrintConv => { + 0 => 'Normal Zone', + 1 => 'Wide Zone', + }, + }, + 2.3 => { # CSa3 + Name => 'AFAssist', + Mask => 0x01, + PrintConv => \%onOff, + }, + 2.4 => { # CSa4 + Name => 'AFPointIllumination', + Mask => 0x06, + PrintConv => { + 0 => 'Auto', + 1 => 'On', + 2 => 'Off', + }, + }, + 2.5 => { # CSa5 + Name => 'FocusPointWrap', + Mask => 0x08, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 3.1 => { # CSa6 + Name => 'AELockForMB-D80', + Mask => 0x1c, + PrintConv => { + 0 => 'AE Lock Only', + 1 => 'AF Lock Only', + 2 => 'AE Lock (hold)', + 3 => 'AF-On', + 4 => 'FV Lock', + 5 => 'Focus Point Selection', + 7 => 'AE/AF Lock', + }, + }, + 3.2 => { # CSd12 + Name => 'MB-D80BatteryType', + Mask => 0x03, + PrintConv => { + 0 => 'LR6 (AA alkaline)', + 1 => 'HR6 (AA Ni-MH)', + 2 => 'FR6 (AA lithium)', + 3 => 'ZR6 (AA Ni-Mn)', + }, + }, + 4.1 => { # CSd1 + Name => 'Beep', + Mask => 0x40, + PrintConv => \%offOn, + }, + 4.2 => { # CSd2 + Name => 'GridDisplay', + Mask => 0x02, + PrintConv => \%offOn, + }, + 4.3 => { # CSd3 + Name => 'ISODisplay', + Mask => 0x0c, + PrintConv => { + 0 => 'Show ISO/Easy ISO', + 1 => 'Show ISO Sensitivity', + 3 => 'Show Frame Count', + }, + }, + 4.4 => { # CSd4 + Name => 'ViewfinderWarning', + Mask => 0x01, + PrintConv => \%onOff, + }, + 4.5 => { # CSf6 + Name => 'NoMemoryCard', + Mask => 0x20, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 5.1 => { # CSd5 + Name => 'ScreenTips', + Mask => 0x04, + PrintConv => \%offOn, + }, + 5.2 => { # CSd7 + Name => 'FileNumberSequence', + Mask => 0x08, + PrintConv => \%onOff, + }, + 5.3 => { # CSd8 + Name => 'ShootingInfoDisplay', + Mask => 0xc0, + PrintConv => { + 0 => 'Auto', + 2 => 'Manual (dark on light)', + 3 => 'Manual (light on dark)', + }, + }, + 5.4 => { # CSd9 + Name => 'LCDIllumination', + Mask => 0x20, + PrintConv => \%offOn, + }, + 6.1 => { # CSb2 + Name => 'EasyExposureComp', + Mask => 0x01, + PrintConv => \%offOn, + }, + 6.2 => { # CSf7 + Name => 'ReverseIndicators', + Mask => 0x80, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 7.1 => { # CSb1 + Name => 'ExposureControlStepSize', + Mask => 0x40, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + }, + }, + 8.1 => { # CSb3 + Name => 'CenterWeightedAreaSize', + Mask => 0x60, + PrintConv => { + 0 => '6 mm', + 1 => '8 mm', + 2 => '10 mm', + }, + }, + 8.2 => { # CSb4-a + Name => 'FineTuneOptMatrixMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.1 => { # CSb4-b + Name => 'FineTuneOptCenterWeighted', + Mask => 0xf0, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.2 => { # CSb4-c + Name => 'FineTuneOptSpotMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 11.1 => { # CSd6 + Name => 'CLModeShootingSpeed', + Mask => 0x07, + PrintConv => '"$val fps"', + PrintConvInv => '$val=~s/\s*fps//i; $val', + }, + 11.2 => { # CSd10 + Name => 'ExposureDelayMode', + Mask => 0x40, + PrintConv => \%offOn, + }, + 13.1 => { # CSe4 + Name => 'AutoBracketSet', + Mask => 0xe0, #(NC) + PrintConv => { + 0 => 'AE & Flash', # default + 1 => 'AE Only', + 2 => 'Flash Only', #(NC) + 3 => 'WB Bracketing', #(NC) + 4 => 'Active D-Lighting', #(NC) + }, + }, + 13.2 => { # CSe6 + Name => 'AutoBracketOrder', + Mask => 0x10, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }, + 14.1 => { # CSf3 + Name => 'FuncButton', + Mask => 0x78, + PrintConv => { + 1 => 'Framing Grid', + 2 => 'AF-area Mode', + 3 => 'Center Focus Point', + 4 => 'FV Lock', # default + 5 => 'Flash Off', + 6 => 'Matrix Metering', + 7 => 'Center-weighted Metering', + 8 => 'Spot Metering', + 9 => 'My Menu Top', + 10 => '+ NEF (RAW)', + }, + }, + 16.1 => { # CSf2 + Name => 'OKButton', + Mask => 0x18, + PrintConv => { + 1 => 'Select Center Focus Point', + 2 => 'Highlight Active Focus Point', + 3 => 'Not Used', #(NC) + 0 => 'Not Used', #(NC) + }, + }, + 17.1 => { # CSf4 + Name => 'AELockButton', + Mask => 0x38, + PrintConv => { + 0 => 'AE/AF Lock', + 1 => 'AE Lock Only', + 2 => 'AF Lock Only', #(NC) + 3 => 'AE Lock (hold)', #(NC) + 4 => 'AF-ON', #(NC) + 5 => 'FV Lock', #(NC) + }, + }, + 18.1 => { # CSf5-a + Name => 'CommandDialsReverseRotation', + Mask => 0x80, + PrintConv => \%noYes, + }, + 18.2 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x02, + PrintConv => \%offOn, + }, + 19.1 => { # CSc2 + Name => 'MeteringTime', + Mask => 0xf0, + PrintConvColumns => 2, + PrintConv => { + 0 => '4 s', + 1 => '6 s', # default + 2 => '8 s', + 3 => '16 s', + 4 => '30 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + 8 => '30 min', + }, + }, + 19.2 => { # CSc5 + Name => 'RemoteOnDuration', + Mask => 0x03, + PrintConv => { + 0 => '1 min', + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + }, + }, + 20.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', # default + 3 => '20 s', + }, + }, + 20.2 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x1e, + }, + 21.1 => { # CSc4-a + Name => 'PlaybackMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 1 => '10 s', # default + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 21.2 => { # CSc4-d + Name => 'ImageReviewTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', # default + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 22.1 => { # CSc4-b + Name => 'MenuMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', # default + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 22.2 => { # CSc4-c + Name => 'ShootingInfoMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 1 => '10 s', # default + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 23.1 => { # CSe1 + Name => 'FlashShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/60 s', # default + 1 => '1/30 s', + 2 => '1/15 s', + 3 => '1/8 s', + 4 => '1/4 s', + 5 => '1/2 s', + 6 => '1 s', + 7 => '2 s', + 8 => '4 s', + 9 => '8 s', + 10 => '15 s', + 11 => '30 s', + }, + }, + 24.1 => { # CSe2-a + Name => 'InternalFlash', + Mask => 0xc0, + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + 2 => 'Repeating Flash', + 3 => 'Commander Mode', + }, + }, + 24.2 => { # CSe2-b + Name => 'ManualFlashOutput', + Mask => 0x1f, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val > 0 ? -3*log($val)/log(2) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 25.1 => { # CSe2-ca + Name => 'RepeatingFlashOutput', + Mask => 0x70, + ValueConv => '2 ** (-$val-2)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2)-2+0.5) : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 25.2 => { # CSe2-cb + Name => 'RepeatingFlashCount', + Mask => 0x0f, + ValueConv => '$val < 10 ? $val + 1 : 5 * ($val - 7)', + ValueConvInv => '$val <= 10 ? $val - 1 : $val / 5 + 7', + }, + 26.1 => { # CSe2-cc (NC) + Name => 'RepeatingFlashRate', + Mask => 0xf0, + ValueConv => '$val < 10 ? $val + 1 : 10 * ($val - 8)', + ValueConvInv => 'int(($val <= 10 ? $val - 1 : $val / 10 + 8) + 0.5)', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 31.1 => { # CSd11 + Name => 'FlashWarning', + Mask => 0x80, + PrintConv => \%onOff, + }, + 31.2 => { # CSe2-ea + Name => 'CommanderInternalTTLComp', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 31.3 => { # CSe3 + Name => 'ModelingFlash', + Mask => 0x20, + PrintConv => \%onOff, + }, + 31.4 => { # CSe5 + Name => 'AutoFP', + Mask => 0x40, + PrintConv => \%offOn, + }, + 32.1 => { # CSe2-eb + Name => 'CommanderGroupA_TTLComp', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 33.1 => { # CSe2-ec + Name => 'CommanderGroupB_TTLComp', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 34.1 => { # CSa7 + Name => 'LiveViewAF', + Mask => 0xc0, + PrintConv => { + 0 => 'Face Priority', #(NC) + 1 => 'Wide Area', + 2 => 'Normal Area', + }, + }, +); + +# D300 (ref JD) and D3 (ref 1/PH) custom settings +%Image::ExifTool::NikonCustom::SettingsD3 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D3, D3S, D3X, D300 and D300S.', + # these settings have been decoded using the D3 and D300, and + # extrapolated to the other models, but these haven't yet been + # verified, and the following custom settings are missing: + # CSf1-d (D3X,D3S) MultiSelectorLiveView + # CSf1 (D300S) LightSwitch + 0.1 => { #1 + Name => 'CustomSettingsBank', + Mask => 0x03, + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 0.2 => { #1 + Name => 'CustomSettingsAllDefault', + Notes => '"No" if any custom setting for this bank was changed from the default', + Mask => 0x80, + PrintConv => { 0 => 'Yes', 1 => 'No' }, + }, + 1.1 => { # CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0xc0, + PrintConv => { + 0 => 'Release', + 1 => 'Release + Focus', + 2 => 'Focus', + }, + }, + 1.2 => { # CSa2 + Name => 'AF-SPrioritySelection', + Mask => 0x20, + PrintConv => { + 0 => 'Focus', + 1 => 'Release', + }, + }, + 1.3 => { # CSa8 + Name => 'AFPointSelection', + Mask => 0x10, + PrintConv => { + 0 => '51 Points', + 1 => '11 Points', + }, + }, + 1.4 => { # CSa3 + Name => 'DynamicAFArea', + Mask => 0x0c, + PrintConv => { + 0 => '9 Points', + 1 => '21 Points', + 2 => '51 Points', + 3 => '51 Points (3D-tracking)', + }, + }, + 1.5 => { # CSa4 + Name => 'FocusTrackingLockOn', + Condition => '$$self{Model} !~ /D3S\b/', + Notes => 'not D3S', + Mask => 0x03, + PrintConv => { + 0 => 'Long', + 1 => 'Normal', + 2 => 'Short', + 3 => 'Off', + }, + }, + 2.1 => { # CSa5 + Name => 'AFActivation', + Mask => 0x80, + PrintConv => { + 0 => 'Shutter/AF-On', + 1 => 'AF-On Only', + }, + }, + 2.2 => { # CSa7 + Name => 'FocusPointWrap', + Mask => 0x08, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 2.3 => [ # CSa6 + { + Name => 'AFPointIllumination', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0x60, + PrintConv => { + 0 => 'On in Continuous Shooting and Manual Focusing', + 1 => 'On During Manual Focusing', + 2 => 'On in Continuous Shooting Modes', + 3 => 'Off', + }, + }, + { + Name => 'AFPointIllumination', + Notes => 'D300', + Mask => 0x06, + PrintConv => { + 0 => 'Auto', + 1 => 'Off', + 2 => 'On', + }, + }, + ], + 2.4 => { # CSa6-b (D3, added by firmware update) + Name => 'AFPointBrightness', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3 only', + Mask => 0x06, + PrintConv => { + 0 => 'Low', + 1 => 'Normal', + 2 => 'High', + 3 => 'Extra High', + }, + }, + 2.5 => { # CSa9 (D300) + Name => 'AFAssist', + Condition => '$$self{Model} =~ /D300S?\b/', + Notes => 'D300 only', + Mask => 0x01, + PrintConv => \%onOff, + }, + 3.1 => { # CSa9 (D3) + Name => 'AFOnButton', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3 only', + Mask => 0x07, + PrintConv => { + 0 => 'AF On', + 1 => 'AE/AF Lock', + 2 => 'AE Lock Only', + 3 => 'AE Lock (reset on release)', + 4 => 'AE Lock (hold)', + 5 => 'AF Lock Only', + }, + }, + 3.2 => { # CSa10 (D3) + Name => 'VerticalAFOnButton', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3 only', + Mask => 0x70, + PrintConv => { + 0 => 'AF On', + 1 => 'AE/AF Lock', + 2 => 'AE Lock Only', + 3 => 'AE Lock (reset on release)', + 4 => 'AE Lock (hold)', + 5 => 'AF Lock Only', + 7 => 'Same as AF On', + }, + }, + 3.3 => { # CSa10 (D300) + Name => 'AF-OnForMB-D10', + Condition => '$$self{Model} =~ /D300S?\b/', + Notes => 'D300 only', + Mask => 0x70, + PrintConv => { + 0 => 'AF-On', + 1 => 'AE/AF Lock', + 2 => 'AE Lock Only', + 3 => 'AE Lock (reset on release)', + 4 => 'AE Lock (hold)', + 5 => 'AF Lock Only', + 6 => 'Same as FUNC Button', + }, + }, + 4.1 => { # CSa4 (D3S) + Name => 'FocusTrackingLockOn', + Condition => '$$self{Model} =~ /D3S\b/', + Notes => 'D3S only', + Mask => 0x07, + PrintConv => { + 0 => '5 (Long)', + 1 => '4', + 2 => '3 (Normal)', + 3 => '2', + 4 => '1 (Short)', + 5 => 'Off', + }, + }, + 4.2 => { # CSf7 (D3S) + Name => 'AssignBktButton', + Condition => '$$self{Model} =~ /D3S\b/', + Notes => 'D3S only', + Mask => 0x08, + PrintConv => { + 0 => 'Auto Bracketing', + 1 => 'Multiple Exposure', + }, + }, + 4.3 => { # CSf1-c (D3S) (ref 1) + Name => 'MultiSelectorLiveView', + Condition => '$$self{Model} =~ /D3S\b/', + Notes => 'D3S only', + Mask => 0xc0, + PrintConv => { + 0 => 'Reset', + 1 => 'Zoom On/Off', + 2 => 'Start Movie Recording', + 3 => 'Not Used', + }, + }, + 4.4 => { # CSf1-c2 (D3S) (ref 1) + Name => 'InitialZoomLiveView', + Condition => '$$self{Model} =~ /D3S\b/', + Notes => 'D3S only', + Mask => 0x30, + PrintConv => { + 0 => 'Low Magnification', + 1 => 'Medium Magnification', + 2 => 'High Magnification', + }, + }, + 6.1 => { # CSb1 + Name => 'ISOStepSize', + Mask => 0xc0, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 6.2 => { # CSb2 + Name => 'ExposureControlStepSize', + Mask => 0x30, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 6.3 => { # CSb3 + Name => 'ExposureCompStepSize', + Mask => 0x0c, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 6.4 => { # CSb4 + Name => 'EasyExposureCompensation', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (auto reset)', + }, + }, + 7.1 => [ # CSb5 + { + Name => 'CenterWeightedAreaSize', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0xe0, + PrintConv => { + 0 => '8 mm', + 1 => '12 mm', + 2 => '15 mm', + 3 => '20 mm', + 4 => 'Average', + }, + }, + { + Name => 'CenterWeightedAreaSize', + Notes => 'D300', + Mask => 0xe0, + PrintConv => { + 0 => '6 mm', + 1 => '8 mm', + 2 => '10 mm', + 3 => '13 mm', + 4 => 'Average', + }, + }, + ], + 7.2 => { # CSb6-b + Name => 'FineTuneOptCenterWeighted', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 8.1 => { # CSb6-a + Name => 'FineTuneOptMatrixMetering', + Mask => 0xf0, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 8.2 => { # CSb6-c + Name => 'FineTuneOptSpotMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.1 => { # CSf1-a, CSf2-a (D300S) + Name => 'MultiSelectorShootMode', + Mask => 0xc0, + PrintConv => { + 0 => 'Select Center Focus Point', + 1 => 'Highlight Active Focus Point', + 2 => 'Not Used', + }, + }, + 9.2 => { # CSf1-b, CSf2-b (D300S) + Name => 'MultiSelectorPlaybackMode', + Condition => '$$self{Model} !~ /D3S\b/', + Notes => 'all models except D3S', # (not confirmed for D3X) + Mask => 0x30, + PrintConv => { + 0 => 'Thumbnail On/Off', + 1 => 'View Histograms', + 2 => 'Zoom On/Off', + 3 => 'Choose Folder', + }, + }, + 9.3 => [ # CSf1-b2, CSf2-b2 (D300S) + { + Name => 'InitialZoomSetting', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0x0c, + PrintConv => { #1 + 0 => 'High Magnification', + 1 => 'Medium Magnification', + 2 => 'Low Magnification', + }, + }, + { + Name => 'InitialZoomSetting', + Notes => 'D300', + Mask => 0x0c, + PrintConv => { #JD + 0 => 'Low Magnification', + 1 => 'Medium Magnification', + 2 => 'High Magnification', + }, + }, + ], + 9.4 => { # CSf2 (D300,D3), CSf3 (D300S) + Name => 'MultiSelector', + Mask => 0x01, + PrintConv => { + 0 => 'Do Nothing', + 1 => 'Reset Meter-off Delay', + }, + }, + 10.1 => { # CSd9 (D300,D3S), CSd10 (D300S), CSd8 (D3) + Name => 'ExposureDelayMode', + Mask => 0x40, + PrintConv => \%offOn, + }, + 10.2 => { # CSd4 (D300), CDs5 (D300S), CSd2-a (D3) + Name => 'CLModeShootingSpeed', + Mask => 0x07, + PrintConv => '"$val fps"', + PrintConvInv => '$val=~s/\s*fps//i; $val', + }, + 10.3 => { # (D3 CSd2-b) + Name => 'CHModeShootingSpeed', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3 only', + Mask => 0x30, + PrintConv => { + 0 => '9 fps', + 1 => '10 fps', + 2 => '11 fps', + }, + }, + 11 => { # CSd5 (D300), CSd6 (D300S), CSd3 (D3) + Name => 'MaxContinuousRelease', + # values: 1-100 (D300), 1-130 (D3) + }, + 12.1 => { # CSf10, CSf11 (D3S,D300S) + Name => 'ReverseIndicators', + Mask => 0x20, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 12.2 => [ # CSd6 (D300), CSd7 (D300S), CSd4 (D3) + { + Name => 'FileNumberSequence', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0x02, + PrintConv => \%onOff, + }, + { + Name => 'FileNumberSequence', + Notes => 'D300', + Mask => 0x08, + PrintConv => \%onOff, + }, + ], + 12.3 => { # CSd5-a (D3) + Name => 'RearDisplay', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3 only', + Mask => 0x80, + PrintConv => { + 0 => 'ISO', + 1 => 'Exposures Remaining', + }, + }, + 12.4 => { # CSd5-b (D3) + Name => 'ViewfinderDisplay', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3 only', + Mask => 0x40, + PrintConv => { + 0 => 'Frame Count', + 1 => 'Exposures Remaining', + }, + }, + 12.5 => { # CSd11 (D300), CSd12 (D300S) + Name => 'BatteryOrder', + Condition => '$$self{Model} =~ /D300S?\b/', + Notes => 'D300 only', + Mask => 0x04, + PrintConv => { + 0 => 'MB-D10 First', + 1 => 'Camera Battery First', + }, + }, + 12.6 => { # CSd10 (D300), CSd11 (D300S) + Name => 'MB-D10Batteries', + Condition => '$$self{Model} =~ /D300S?\b/', + Notes => 'D300 only', + Mask => 0x03, + PrintConv => { + 0 => 'LR6 (AA alkaline)', + 1 => 'HR6 (AA Ni-MH)', + 2 => 'FR6 (AA lithium)', + 3 => 'ZR6 (AA Ni-Mn)', + }, + }, + 12.7 => { # CSd7 (D3S), CSd4, (D300S) + Name => 'ScreenTips', + Condition => '$$self{Model} =~ /(D3S|D300S)\b/', + Mask => 0x10, + PrintConv => \%onOff, + }, + 13.1 => { # CSd1 + Name => 'Beep', + Mask => 0xc0, + PrintConv => { + 0 => 'High', + 1 => 'Low', + 2 => 'Off', + }, + }, + 13.2 => { # CSd7 (D300), CSd8 (D300S), CSd6 (D3) + Name => 'ShootingInfoDisplay', + Mask => 0x30, + PrintConv => { + 0 => 'Auto', #JD (D300) + 1 => 'Auto', #1 (D3) + 2 => 'Manual (dark on light)', + 3 => 'Manual (light on dark)', + }, + }, + 13.3 => { # CSd2 (D300) + Name => 'GridDisplay', + Condition => '$$self{Model} =~ /D300S?\b/', + Notes => 'D300 only', + Mask => 0x02, + PrintConv => \%offOn, + }, + 13.4 => { # CSd3 (D300) + Name => 'ViewfinderWarning', + Condition => '$$self{Model} =~ /D300S?\b/', + Notes => 'D300 only', + Mask => 0x01, + PrintConv => \%onOff, + }, + 13.5 => { # CSf1-b (D3S) (ref 1) + Name => 'MultiSelectorPlaybackMode', + Condition => '$$self{Model} =~ /D3S\b/', + Notes => 'D3S only', + Mask => 0x03, + PrintConv => { + 0 => 'Thumbnail On/Off', + 1 => 'View Histograms', + 2 => 'Zoom On/Off', + }, + }, + 14.1 => [ # CSf5-a (ref 1), CSf6-a (D300S) + { + Name => 'PreviewButton', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'Flash Off', + 9 => 'Bracketing Burst', + 10 => 'Matrix Metering', + 11 => 'Center-weighted Metering', + 12 => 'Spot Metering', + 13 => 'Virtual Horizon', + # 14 not used + 15 => 'Playback', + 16 => 'My Menu Top', + }, + }, + { #PH + Name => 'FuncButton', + Notes => 'D300', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + # 8 not used + 9 => 'Flash Off', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', #PH (guess) + 15 => 'My Menu Top', #PH (guess) + 16 => '+ NEF (RAW)', #PH (guess) + }, + }, + ], + 14.2 => [ # CSf5-b (PH,NC), CSf6-b (D300S) + { + Name => 'PreviewButtonPlusDials', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'One Step Speed/Aperture', + 3 => 'Choose Non-CPU Lens Number', + # n/a 4 => 'Focus Point Selection', + 5 => 'Choose Image Area (FX/DX)', + 6 => 'Shooting Bank Menu', + 7 => 'Dynamic AF Area', #PH (D3S,D3X,NC) + }, + }, + { #PH + Name => 'FuncButtonPlusDials', + Notes => 'D300', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 2 => 'One Step Speed/Aperture', + 3 => 'Choose Non-CPU Lens Number', + 5 => 'Auto Bracketing', + 6 => 'Dynamic AF Area', + }, + }, + ], + 15.1 => [ # CSf4-a (ref 1), CSf5-a (D300S) + { + Name => 'FuncButton', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'Flash Off', + 9 => 'Bracketing Burst', + 10 => 'Matrix Metering', + 11 => 'Center-weighted Metering', + 12 => 'Spot Metering', + 13 => 'Virtual Horizon', + # 14 not used + 15 => 'Playback', + 16 => 'My Menu Top', + }, + }, + { #PH + Name => 'PreviewButton', + Notes => 'D300', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + # 8 not used + 9 => 'Flash Off', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', #PH (guess) + 15 => 'My Menu Top', #PH (guess) + 16 => '+ NEF (RAW)', #PH (guess) + }, + }, + ], + 15.2 => [ # CSf4-b (ref 1), CSf5-b (D300S) + { + Name => 'FuncButtonPlusDials', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'One Step Speed/Aperture', + 3 => 'Choose Non-CPU Lens Number', + 4 => 'Focus Point Selection', #(NC) + 5 => 'Choose Image Area (FX/DX)', + 6 => 'Shooting Bank Menu', + 7 => 'Dynamic AF Area', #PH (D3S,D3X,NC) + }, + }, + { #PH + Name => 'PreviewButtonPlusDials', + Notes => 'D300', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 2 => 'One Step Speed/Aperture', + 3 => 'Choose Non-CPU Lens Number', + 5 => 'Auto Bracketing', + 6 => 'Dynamic AF Area', + }, + }, + ], + 16.1 => [ # CSf6-a (ref 1), CSf7-a (D300S) + { + Name => 'AELockButton', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'Flash Off', + 9 => 'Bracketing Burst', + 10 => 'Matrix Metering', + 11 => 'Center-weighted Metering', + 12 => 'Spot Metering', + 13 => 'Virtual Horizon', + 14 => 'AF On', # (AE-L/AF-L button only) + 15 => 'Playback', + 16 => 'My Menu Top', + }, + }, + { #PH + Name => 'AELockButton', + Notes => 'D300', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF On', # (AE-L/AF-L button only) + 9 => 'Flash Off', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', #PH (guess) + 15 => 'My Menu Top', #PH (guess) + 16 => '+ NEF (RAW)', #PH (guess) + }, + }, + ], + 16.2 => [ # CSf6-b (ref 1), CSf7-b (D300S) + { + Name => 'AELockButtonPlusDials', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'One Step Speed/Aperture', + 3 => 'Choose Non-CPU Lens Number', + # n/c 4 => 'Focus Point Selection', #(NC) + 5 => 'Choose Image Area (FX/DX)', + 6 => 'Shooting Bank Menu', + 7 => 'Dynamic AF Area', #PH (D3S,D3X,NC) + }, + }, + { #PH + Name => 'AELockButtonPlusDials', + Notes => 'D300', + Mask => 0x07, + PrintConv => { + 0 => 'None', + # n/a 2 => 'One Step Speed/Aperture', + 3 => 'Choose Non-CPU Lens Number', + 5 => 'Auto Bracketing', #(NC) + 6 => 'Dynamic AF Area', + }, + }, + ], + 17.1 => { # CSf7-a, CSf8-a (D3S,D300S) + Name => 'CommandDialsReverseRotation', + Mask => 0x80, + PrintConv => \%noYes, + }, + 17.2 => { # CSf7-b, CSf8-b (D3S,D300S) + Name => 'CommandDialsChangeMainSub', + Mask => 0x40, + PrintConv => \%offOn, + }, + 17.3 => { # CSf7-c, CSf8-c (D3S,D300S) + Name => 'CommandDialsApertureSetting', + Mask => 0x20, + PrintConv => { + 0 => 'Sub-command Dial', + 1 => 'Aperture Ring', + }, + }, + 17.4 => { # CSf7-d, CSf8-d (D3S,D300S) + Name => 'CommandDialsMenuAndPlayback', + Mask => 0x10, + PrintConv => \%offOn, + }, + 17.5 => { # CSd8 (D300,D3S), CSd9 (D300S), CSd7 (D3) + Name => 'LCDIllumination', + Mask => 0x08, + PrintConv => \%offOn, + }, + 17.6 => { # CSf3, CSf4 (D300S) + Name => 'PhotoInfoPlayback', + Mask => 0x04, + PrintConv => { + 0 => 'Info Up-down, Playback Left-right', + 1 => 'Info Left-right, Playback Up-down', + }, + }, + 17.7 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x02, + PrintConv => \%offOn, + }, + 17.8 => { # CSf8, CSf9 (D3S,D300S) + Name => 'ReleaseButtonToUseDial', + Mask => 0x01, + PrintConv => \%noYes, + }, + 18.1 => { # CSc3 + Name => 'SelfTimerTime', + Mask => 0x18, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 18.2 => { # CSc4 + Name => 'MonitorOffTime', + # NOTE: The D3S and D300S have separate settings for Playback, + # Image Review, Menus, and Information Display times + Mask => 0x07, + PrintConv => { + 0 => '10 s', + 1 => '20 s', + 2 => '1 min', + 3 => '5 min', + 4 => '10 min', + }, + }, + 20.1 => [ # CSe1 + { + Name => 'FlashSyncSpeed', + Condition => '$$self{Model} =~ /D3[SX]?\b/', + Notes => 'D3', + Mask => 0xe0, + PrintConv => { + 0 => '1/250 s (auto FP)', + 1 => '1/250 s', + 2 => '1/200 s', + 3 => '1/160 s', + 4 => '1/125 s', + 5 => '1/100 s', + 6 => '1/80 s', + 7 => '1/60 s', + }, + }, + { + Name => 'FlashSyncSpeed', + Notes => 'D300', + Mask => 0xf0, + PrintConv => { + 0 => '1/320 s (auto FP)', + 1 => '1/250 s (auto FP)', + 2 => '1/250 s', + 3 => '1/200 s', + 4 => '1/160 s', + 5 => '1/125 s', + 6 => '1/100 s', + 7 => '1/80 s', + 8 => '1/60 s', + }, + }, + ], + 20.2 => { # CSe2 + Name => 'FlashShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/60 s', + 1 => '1/30 s', + 2 => '1/15 s', + 3 => '1/8 s', + 4 => '1/4 s', + 5 => '1/2 s', + 6 => '1 s', + 7 => '2 s', + 8 => '4 s', + 9 => '8 s', + 10 => '15 s', + 11 => '30 s', + }, + }, + 21.1 => [{ # CSe5 (D300), CSe4 (D3) + Name => 'AutoBracketSet', + Condition => '$$self{Model} !~ /(D3S|D300S)\b/', + Notes => 'D3 and D300', + Mask => 0xc0, + PrintConv => { + 0 => 'AE & Flash', + 1 => 'AE Only', + 2 => 'Flash Only', + 3 => 'WB Bracketing', + }, + },{ # CSe4 (D3S) (NC for D300S) + Name => 'AutoBracketSet', + Notes => 'D3S and D300S', + Mask => 0xe0, + PrintConv => { + 0 => 'AE & Flash', + 1 => 'AE Only', + 2 => 'Flash Only', + 3 => 'WB Bracketing', + # D3S/D300S have an "ADL Bracketing" setting - PH + 4 => 'ADL Bracketing', + }, + }], + 21.2 => [{ # CSe6 (D300), CSe5 (D3) + Name => 'AutoBracketModeM', + Condition => '$$self{Model} !~ /(D3S|D300S)\b/', + Notes => 'D3 and D300', + Mask => 0x30, + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + }, + },{ # CSe5 (D3S) + Name => 'AutoBracketModeM', + Notes => 'D3S and D300S', + Mask => 0x18, + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + }, + }], + 21.3 => [{ # CSe7 (D300), CSe6 (D3) + Name => 'AutoBracketOrder', + Condition => '$$self{Model} !~ /(D3S|D300S)\b/', + Notes => 'D3 and D300', + Mask => 0x08, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + },{ # CSe6 (D3S) + Name => 'AutoBracketOrder', + Notes => 'D3S and D300S', + Mask => 0x04, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }], + 21.4 => { # CSe4 (D300), CSe3 (D3) + Name => 'ModelingFlash', + Mask => 0x01, + PrintConv => \%onOff, + }, + 22.1 => { # CSf9, CSf10 (D3S,D300S) + Name => 'NoMemoryCard', + Mask => 0x80, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 22.2 => { # CSc2 + Name => 'MeteringTime', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '4 s', + 1 => '6 s', + 2 => '8 s', + 3 => '16 s', + 4 => '30 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + 8 => '30 min', + 9 => 'No Limit', + }, + }, + 23.1 => { # CSe3 + Name => 'InternalFlash', + Mask => 0xc0, + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + 2 => 'Repeating Flash', + 3 => 'Commander Mode', + }, + }, + 25.1 => { #1 CSc4-d (D3S) + Name => 'ImageReviewTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 25.2 => { #1 CSc4-a (D3S) + Name => 'PlaybackMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 26.1 => { #1 CSc4-b (D3S) + Name => 'MenuMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 26.2 => { #1 CSc4-c (D3S) + Name => 'ShootingInfoMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, +); + +# D700 custom settings (ref 2) +%Image::ExifTool::NikonCustom::SettingsD700 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + DATAMEMBER => [ 16.1 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D700.', + 0.1 => { #1 + Name => 'CustomSettingsBank', + Mask => 0x03, + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 0.2 => { #1 + Name => 'CustomSettingsAllDefault', + Notes => '"No" if any custom setting for this bank was changed from the default', + Mask => 0x80, + PrintConv => { 0 => 'Yes', 1 => 'No' }, + }, + 1.1 => { # CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0xc0, + PrintConv => { + 0 => 'Release', + 1 => 'Release + Focus', + 2 => 'Focus', + }, + }, + 1.2 => { # CSa2 + Name => 'AF-SPrioritySelection', + Mask => 0x20, + PrintConv => { + 0 => 'Focus', + 1 => 'Release', + }, + }, + 1.3 => { # CSa8 + Name => 'AFPointSelection', + Mask => 0x10, + PrintConv => { + 0 => '51 Points', + 1 => '11 Points', + }, + }, + 1.4 => { # CSa3 + Name => 'DynamicAFArea', + Mask => 0x0c, + PrintConv => { + 0 => '9 Points', + 1 => '21 Points', + 2 => '51 Points', + 3 => '51 Points (3D-tracking)', + }, + }, + 2.1 => { # CSa5 + Name => 'AFActivation', + Mask => 0x80, + PrintConv => { + 0 => 'Shutter/AF-On', + 1 => 'AF-On Only', + }, + }, + 2.2 => { # CSa7 + Name => 'FocusPointWrap', + Mask => 0x08, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 2.3 => { # CSa6 + Name => 'AFPointIllumination', + Mask => 0x06, + PrintConv => { + 0 => 'Auto', + 1 => 'Off', + 2 => 'On', + }, + }, + 2.4 => { # CSa9 + Name => 'AFAssist', + Mask => 0x01, + PrintConv => \%onOff, + }, + 3.1 => { # CSa4 + Name => 'FocusTrackingLockOn', + Mask => 0x07, + PrintConv => { + 0 => '3 Normal', + 1 => '4', + 2 => '5 Long', + 3 => '2', + 4 => '1 Short', + 5 => 'Off', + }, + }, + 3.2 => { # CSa10 + Name => 'AF-OnForMB-D10', + Mask => 0x70, + PrintConv => { + 0 => 'AF-On', + 1 => 'AE/AF Lock', + 2 => 'AE Lock Only', + 3 => 'AE Lock (reset on release)', + 4 => 'AE Lock (hold)', + 5 => 'AF Lock Only', + 6 => 'Same as FUNC Button', + }, + }, + 4.1 => { # CSb1 + Name => 'ISOStepSize', + Mask => 0xc0, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 4.2 => { # CSb2 + Name => 'ExposureControlStepSize', + Mask => 0x30, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 4.3 => { # CSb3 + Name => 'ExposureCompStepSize', + Mask => 0x0c, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 4.4 => { # CSb4 + Name => 'EasyExposureCompensation', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (auto reset)', + }, + }, + 5.1 => { # CSb5 + Name => 'CenterWeightedAreaSize', + Mask => 0x70, + PrintConv => { + 0 => '8 mm', + 1 => '12 mm', + 2 => '15 mm', + 3 => '20 mm', + 4 => 'Average', + }, + }, + 6.1 => { # CSb6-a + Name => 'FineTuneOptMatrixMetering', + Mask => 0xf0, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 6.2 => { # CSb6-c + Name => 'FineTuneOptSpotMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 7.1 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x80, + PrintConv => \%offOn, + }, + 7.2 => { # CSc3 + Name => 'SelfTimerTime', + Mask => 0x30, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 7.3 => { # CSc2 + Name => 'MeteringTime', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '4 s', + 1 => '6 s', + 2 => '8 s', + 3 => '16 s', + 4 => '30 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + 8 => '30 min', + 9 => 'No Limit', + }, + }, + 8.1 => { # CSc4-a + Name => 'PlaybackMonitorOffTime', + Mask => 0x38, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 8.2 => { # CSc4-b + Name => 'MenuMonitorOffTime', + Mask => 0x07, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 9.1 => { # CSc4-c + Name => 'ShootingInfoMonitorOffTime', + Mask => 0x38, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 9.2 => { # CSc4-d + Name => 'ImageReviewTime', + Mask => 0x07, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 10.1 => { # CSd1 + Name => 'Beep', + Mask => 0xc0, + PrintConv => { + 0 => 'High', + 1 => 'Low', + 2 => 'Off', + }, + }, + 10.2 => { # CSd7 + Name => 'ShootingInfoDisplay', + Mask => 0x30, + PrintConv => { + 0 => 'Auto', #JD (D300) + 1 => 'Auto', #1 (D3) + 2 => 'Manual (dark on light)', + 3 => 'Manual (light on dark)', + }, + }, + 10.3 => { # CSd8 + Name => 'LCDIllumination', + Mask => 0x08, + PrintConv => \%offOn, + }, + 10.4 => { # CSd9 + Name => 'ExposureDelayMode', + Mask => 0x04, + PrintConv => \%offOn, + }, + 10.5 => { # CSd2 + Name => 'GridDisplay', + Mask => 0x02, + PrintConv => \%offOn, + }, + 11.1 => { # CSd6 + Name => 'FileNumberSequence', + Mask => 0x40, + PrintConv => \%onOff, + }, + 11.2 => { # CSd4 + Name => 'CLModeShootingSpeed', + Mask => 0x07, + PrintConv => '"$val fps"', + PrintConvInv => '$val=~s/\s*fps//i; $val', + }, + 12 => { # CSd5 + Name => 'MaxContinuousRelease', + # values: 1-100 + }, + 13.1 => { # CSd3 + Name => 'ScreenTips', + Mask => 0x08, + PrintConv => \%onOff, + }, + 13.2 => { # CSd11 + Name => 'BatteryOrder', + Mask => 0x04, + PrintConv => { + 0 => 'MB-D10 First', + 1 => 'Camera Battery First', + }, + }, + 13.3 => { # CSd10 + Name => 'MB-D10BatteryType', + Mask => 0x03, + PrintConv => { + 0 => 'LR6 (AA alkaline)', + 1 => 'HR6 (AA Ni-MH)', + 2 => 'FR6 (AA lithium)', + 3 => 'ZR6 (AA Ni-Mn)', + }, + }, + 15.1 => { # CSe1 + Name => 'FlashSyncSpeed', + Mask => 0xf0, + PrintConv => { + 0 => '1/320 s (auto FP)', + 1 => '1/250 s (auto FP)', + 2 => '1/250 s', + 3 => '1/200 s', + 4 => '1/160 s', + 5 => '1/125 s', + 6 => '1/100 s', + 7 => '1/80 s', + 8 => '1/60 s', + }, + }, + 15.2 => { # CSe2 + Name => 'FlashShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/60 s', + 1 => '1/30 s', + 2 => '1/15 s', + 3 => '1/8 s', + 4 => '1/4 s', + 5 => '1/2 s', + 6 => '1 s', + 7 => '2 s', + 8 => '4 s', + 9 => '8 s', + 10 => '15 s', + 11 => '30 s', + }, + }, + 16.1 => { # CSe3 + Name => 'FlashControlBuilt-in', + # Note If set the Manual, Repeating Flash, Commander Mode + # does not decode the detail settings. + Mask => 0xc0, + RawConv => '$$self{FlashControlBuiltin} = $val', + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + 2 => 'Repeating Flash', + 3 => 'Commander Mode', + }, + }, + 16.2 => { # CSe3-b + Name => 'ManualFlashOutput', + Condition => '$$self{FlashControlBuiltin} == 1', + Mask => 0x1f, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val > 0 ? -3*log($val)/log(2) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 17.1 => { # CSe3-ca + Name => 'RepeatingFlashOutput', + Condition => '$$self{FlashControlBuiltin} == 2', + Mask => 0x70, + ValueConv => '2 ** (-$val-2)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2)-2+0.5) : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 17.2 => { # CSe3-cb + Name => 'RepeatingFlashCount', + Condition => '$$self{FlashControlBuiltin} == 2', + Mask => 0x0f, + ValueConv => '$val < 10 ? $val + 1 : 5 * ($val - 7)', + ValueConvInv => '$val <= 10 ? $val - 1 : $val / 5 + 7', + }, + 18.1 => { # CSe3-cc (NC) + Name => 'RepeatingFlashRate', + Condition => '$$self{FlashControlBuiltin} == 2', + Mask => 0xf0, + ValueConv => '$val < 10 ? $val + 1 : 10 * ($val - 8)', + ValueConvInv => 'int(($val <= 10 ? $val - 1 : $val / 10 + 8) + 0.5)', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 18.2 => { # CSe3-dd + Name => 'CommanderInternalTTLChannel', + Condition => '$$self{FlashControlBuiltin} == 3', + Mask => 0x03, + PrintConv => { + 0 => '1 ch', + 1 => '2 ch', + 2 => '3 ch', + 3 => '4 ch', + }, + }, + 20.1 => { # CSe3-da + Name => 'CommanderInternalTTLCompBuiltin', + Condition => '$$self{FlashControlBuiltin} == 3', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 21.1 => { # CSe3-db + Name => 'CommanderInternalTTLCompGroupA', + Condition => '$$self{FlashControlBuiltin} == 3', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 22.1 => { # CSe3-dc + Name => 'CommanderInternalTTLCompGroupB', + Condition => '$$self{FlashControlBuiltin} == 3', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 26.1 => { # CSe5 + Name => 'AutoBracketSet', + Mask => 0xc0, + PrintConv => { + 0 => 'AE & Flash', + 1 => 'AE Only', + 2 => 'Flash Only', + 3 => 'WB Bracketing', + }, + }, + 26.2 => { # CSe6 + Name => 'AutoBracketModeM', + Mask => 0x30, + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + }, + }, + 26.3 => { # CSe7 + Name => 'AutoBracketOrder', + Mask => 0x08, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }, + 26.4 => { # CSe4 + Name => 'ModelingFlash', + Mask => 0x01, + PrintConv => \%onOff, + }, + 27.1 => { # CSf2-a + Name => 'MultiSelectorShootMode', + Mask => 0xc0, + PrintConv => { + 0 => 'Select Center Focus Point', + 1 => 'Highlight Active Focus Point', + 2 => 'Not Used', + }, + }, + 27.2 => { # CSf2-b + Name => 'MultiSelectorPlaybackMode', + Mask => 0x30, + PrintConv => { + 0 => 'Thumbnail On/Off', + 1 => 'View Histograms', + 2 => 'Zoom On/Off', + 3 => 'Choose Folder', + }, + }, + 27.3 => { # CSf2-b2 + Name => 'InitialZoomSetting', + Mask => 0x0c, + PrintConv => { #1 + 0 => 'Low Magnification', + 1 => 'Medium Magnification', + 2 => 'High Magnification', + }, + }, + 27.4 => { # CSf3 + Name => 'MultiSelector', + Mask => 0x01, + PrintConv => { + 0 => 'Do Nothing', + 1 => 'Reset Meter-off Delay', + }, + }, + 28.1 => { # CSf5-a + Name => 'FuncButton', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + # 8 not used + 9 => 'Flash Off', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'My Menu Top', + 15 => 'Live View', + 16 => '+ NEF (RAW)', + 17 => 'Virtual Horizon', + }, + }, + 29.1 => { # CSf6-a + Name => 'PreviewButton', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-ON', + 9 => 'Flash Off', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'My Menu Top', + 15 => 'Live View', + 16 => '+ NEF (RAW)', + 17 => 'Virtual Horizon', + }, + }, + 30.1 => { # CSf7-a + Name => 'AELockButton', + Notes => 'D300', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-ON', + 9 => 'Flash Off', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'My Menu Top', + 15 => 'Live View', + 16 => '+ NEF (RAW)', + 17 => 'Virtual Horizon', + }, + }, + 31.1 => { # CSf5-b + Name => 'FuncButtonPlusDials', + Mask => 0x70, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'One Step Speed/Aperture', + 3 => 'Choose Non-CPU Lens Number', + # n/a 4 => 'Focus Point Selection', + 5 => 'Auto bracketing', + 6 => 'Dynamic AF Area', + 7 => 'Shutter speed & Aperture lock', + }, + }, + 31.2 => { # CSf6-b + Name => 'PreviewButtonPlusDials', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'One Step Speed/Aperture', + 3 => 'Choose Non-CPU Lens Number', + # n/a 4 => 'Focus Point Selection', + 5 => 'Auto bracketing', + 6 => 'Dynamic AF Area', + 7 => 'Shutter speed & Aperture lock', + }, + }, + 32.1 => { # CSf7-b + Name => 'AELockButtonPlusDials', + Mask => 0x70, + Prinonv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'One Step Speed/Aperture', + 3 => 'Choose Non-CPU Lens Number', + # n/a 4 => 'Focus Point Selection', + 5 => 'Auto bracketing', + 6 => 'Dynamic AF Area', + 7 => 'Shutter speed & Aperture lock', + }, + }, + 33.1 => { # CSf9-a + Name => 'CommandDialsReverseRotation', + Mask => 0x80, + PrintConv => \%noYes, + }, + 33.2 => { # CSf9-b + Name => 'CommandDialsChangeMainSub', + Mask => 0x40, + PrintConv => \%offOn, + }, + 33.3 => { # CSf9-c + Name => 'CommandDialsApertureSetting', + Mask => 0x20, + PrintConv => { + 0 => 'Sub-command Dial', + 1 => 'Aperture Ring', + }, + }, + 33.4 => { # CSf9-d + Name => 'CommandDialsMenuAndPlayback', + Mask => 0x10, + PrintConv => \%offOn, + }, + 33.5 => { # CSf12 + Name => 'ReverseIndicators', + Mask => 0x08, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 33.6 => { # CSf4 + Name => 'PhotoInfoPlayback', + Mask => 0x04, + PrintConv => \%offOn, + }, + 33.7 => { # CSf11 + Name => 'NoMemoryCard', + Mask => 0x02, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 33.8 => { # CSf10 + Name => 'ReleaseButtonToUseDial', + Mask => 0x01, + PrintConv => \%noYes, + }, +); + +# D800 custom settings (ref PH) +%Image::ExifTool::NikonCustom::SettingsD800 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + DATAMEMBER => [ 23.1 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D800 and D800E.', + 12.1 => { # CSe5 + Name => 'AutoBracketingSet', + Mask => 0xe0, #(NC) + PrintConv => { + 0 => 'AE & Flash', # default + 1 => 'AE Only', #(NC) + 2 => 'Flash Only', + 3 => 'WB Bracketing', #(NC) + 4 => 'Active D-Lighting', #(NC) + }, + }, + 12.2 => { # CSe7 + Name => 'AutoBracketOrder', + Mask => 0x10, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }, + 12.3 => { # CSe6 + Name => 'AutoBracketingMode', + Mask => 0x0c, + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + }, + }, + # 21 - 100 (MaxContinuousRelease?) + 22.1 => { # CSe1 + Name => 'FlashSyncSpeed', + Mask => 0xf0, + PrintConv => { + 0 => '1/320 s (auto FP)', + 1 => '1/250 s (auto FP)', + 2 => '1/250 s', + 3 => '1/200 s', + 4 => '1/160 s', + 5 => '1/125 s', + 6 => '1/100 s', + 7 => '1/80 s', + 8 => '1/60 s', + }, + }, + 22.2 => { # CSe2 + Name => 'FlashShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/60 s', + 1 => '1/30 s', + 2 => '1/15 s', + 3 => '1/8 s', + 4 => '1/4 s', + 5 => '1/2 s', + 6 => '1 s', + 7 => '2 s', + 8 => '4 s', + 9 => '8 s', + 10 => '15 s', + 11 => '30 s', + }, + }, + 23.1 => { # CSe3 + Name => 'FlashControlBuilt-in', + Mask => 0xc0, + RawConv => '$$self{FlashControlBuiltin} = $val', + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + 2 => 'Repeating Flash', + 3 => 'Commander Mode', + }, + }, + 23.2 => { # CSe3-b + Name => 'ManualFlashOutput', + Condition => '$$self{FlashControlBuiltin} == 1', + Mask => 0x1f, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val > 0 ? -3*log($val)/log(2) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 24.1 => { # CSe3-ca + Name => 'RepeatingFlashOutput', + Condition => '$$self{FlashControlBuiltin} == 2', + Mask => 0x70, + ValueConv => '2 ** (-$val-2)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2)-2+0.5) : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 24.2 => { # CSe3-cb + Name => 'RepeatingFlashCount', + Condition => '$$self{FlashControlBuiltin} == 2', + Mask => 0x0f, + ValueConv => '$val < 10 ? $val + 1 : 5 * ($val - 7)', + ValueConvInv => '$val <= 10 ? $val - 1 : $val / 5 + 7', + }, + 25.1 => { # CSe3-cc + Name => 'RepeatingFlashRate', + Condition => '$$self{FlashControlBuiltin} == 2', + Mask => 0xf0, + ValueConv => '$val < 10 ? $val + 1 : 10 * ($val - 8)', + ValueConvInv => 'int(($val <= 10 ? $val - 1 : $val / 10 + 8) + 0.5)', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 25.2 => { # CSe3 + Name => 'CommanderChannel', + Mask => 0x03, + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', + }, + 27.1 => { # CSe3 + Name => 'CommanderInternalFlash', + Mask => 0xc0, + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + 2 => 'Off', + }, + }, + 27.2 => { # CSe3 + Name => 'CommanderInternalManualOutput', + Mask => 0x1f, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2) * 3 + 0.5): 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 28.1 => { # CSe3 + Name => 'CommanderGroupAMode', + Mask => 0xc0, + PrintConv => { + 0 => 'TTL', + 1 => 'Auto Aperture', + 2 => 'Manual', + 3 => 'Off', + }, + }, + 28.2 => { # CSe3 + Name => 'CommanderGroupAManualOutput', + Mask => 0x1f, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2) * 3 + 0.5): 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 29.1 => { # CSe3 + Name => 'CommanderGroupBMode', + Mask => 0xc0, + PrintConv => { + 0 => 'TTL', + 1 => 'Auto Aperture', + 2 => 'Manual', + 3 => 'Off', + }, + }, + 29.2 => { # CSe3 + Name => 'CommanderGroupBManualOutput', + Mask => 0x1f, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2) * 3 + 0.5): 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 30.1 => { # CSe4 + Name => 'ModelingFlash', + Mask => 0x20, + PrintConv => \%onOff, + }, + 30.2 => { # CSe3 + Name => 'CommanderInternalTTLComp', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 31.1 => { # CSe3 + Name => 'CommanderGroupA_TTL-AAComp', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 32.1 => { # CSe3 + Name => 'CommanderGroupB_TTL-AAComp', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + # 47 - related to flash +); + +# D5 custom settings (ref 1) +%Image::ExifTool::NikonCustom::SettingsD5 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D5.', + 0.1 => { + Name => 'CustomSettingsBank', + Mask => 0x03, + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 1.1 => { # CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0xc0, + PrintConv => { + 0 => 'Release', + 1 => 'Release + Focus', + 2 => 'Focus', + 3 => 'Focus + Release', + }, + }, + 1.2 => { # CSa2 + Name => 'AF-SPrioritySelection', + Mask => 0x20, + PrintConv => { + 0 => 'Focus', + 1 => 'Release', + }, + }, + 1.3 => { # CSa6 + Name => 'NumberOfFocusPoints', + Mask => 0x10, + PrintConv => { + 0 => '55 Points', + 1 => '15 Points', + }, + }, + 1.4 => { # CSa4 + Name => 'Three-DTrackingFaceDetection', + Mask => 0x08, + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 1.5 => { # CSa3-a + Name => 'BlockShotAFResponse', + Mask => 0x07, + #values 1-5 + }, + 2.1 => { # CSa11 + Name => 'FocusPointWrap', + Mask => 0x08, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 2.2 => { # CSa12-d + Name => 'AFPointBrightness', + Mask => 0x06, + PrintConv => { + 0 => 'Auto', + 1 => 'On', + 2 => 'Off', + }, + }, + 4.1 => { # CSd3 + Name => 'ISODisplay', + Mask => 0x08, + PrintConv => { + 0 => 'Show ISO Sensitivity', + 1 => 'Show Frame Count', + }, + }, + 4.2 => { # CSd8 + Name => 'GridDisplay', + Mask => 0x02, + PrintConv => \%onOff, + }, + 5.1 => { # CSd9 + Name => 'LCDIllumination', + Mask => 0x20, + PrintConv => \%offOn, + }, + 5.2 => { # CSd6 + Name => 'ElectronicFront-CurtainShutter', + Mask => 0x08, + PrintConv => \%offOn, + }, + 6.1 => { # CSf7 + Name => 'ReverseIndicators', + Mask => 0x80, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 6.2 => { # CSf4-a + Name => 'CommandDialsReverseRotation', + Mask => 0x18, + PrintConv => { + 0 => 'No', + 1 => 'Shutter Speed & Aperture', + 2 => 'Exposure Compensation', + 3 => 'Exposure Compensation, Shutter Speed & Aperture', + }, + }, + 6.3 => { # CSb4 + Name => 'EasyExposureCompensation', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (auto reset)', + }, + }, + 7.1 => { # CSb2 + Name => 'ExposureControlStepSize', + Mask => 0xc0, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 7.2 => { # CSb1 + Name => 'ISOStepSize', + Mask => 0x30, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 7.3 => { # CSb3 + Name => 'ExposureCompStepSize', + Mask => 0x0c, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 8.1 => { # CSb6 + Name => 'CenterWeightedAreaSize', + Mask => 0xe0, + PrintConv => { + 0 => '8 mm', + 1 => '12 mm', + 2 => '15 mm', + 3 => '20 mm', + 4 => 'Average', + }, + }, + 8.2 => { # CSb7-a + Name => 'FineTuneOptMatrixMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.1 => { # CSb7-b + Name => 'FineTuneOptCenterWeighted', + Mask => 0xf0, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.2 => { # CSb7-c + Name => 'FineTuneOptSpotMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 10.1 => { # CSf2-a + Name => 'MultiSelectorShootMode', + Mask => 0xe0, + PrintConv => { + 0 => 'Select Center Focus Point (Reset)', + 1 => 'Zoom On/Off', # this is the documented (and actual) default value, but the choice does not appear on the camera menu + 2 => 'Preset Focus Point (Pre)', + 4 => 'Not Used (None)', + }, + }, +# 10.2 => { # CSf2-b # moved from the D500 position to Nikon_ShotInfoD5_0x0ab1 with the Mask and PrintConv as specified below. Further research required. +# Name => 'MultiSelectorPlaybackMode', +# Mask => 0x70, +# PrintConv => { +# 0 => 'Zoom On/Off', +# 1 => 'Choose Folder', +# 6 => 'Thumbnail On/Off', +# 7 => 'View Histograms', +# }, +# }, + 10.3 => { # CSf5 + Name => 'MultiSelector', + Mask => 0x01, + PrintConv => { + 0 => 'Do Nothing', + 1 => 'Reset Meter-off Delay', + }, + }, + 11.1 => { # CSd5 + Name => 'ExposureDelayMode', + Mask => 0xc0, + PrintConv => { + 0 => 'Off', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 11.2 => { # CSd1 + Name => 'CLModeShootingSpeed', + Mask => 0x0f, + PrintConv => '"$val fps"', + PrintConvInv => '$val=~s/\s*fps//i; $val', + }, + 12.1 => { # CSd2 + Name => 'MaxContinuousRelease', + # values: 1-100 + }, + 13.1 => { # CSe7 + Name => 'AutoBracketOrder', + Mask => 0x10, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }, + 13.2 => { # CSe6 + Name => 'AutoBracketModeM', + Mask => 0x0c, + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + }, + }, + 14.1 => { # CSf1-c + Name => 'Func1Button', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 19 => 'Grid Display', + 20 => 'My Menu', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 58 => 'AF-Area Mode + AF-On (Group Area AF - HL)', + 59 => 'AF-Area Mode + AF-On (Group Area AF - VL)', + }, + }, + 15.1 => { # CSf1-a + Name => 'PreviewButton', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 19 => 'Grid Display', + 20 => 'My Menu', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 58 => 'AF-Area Mode + AF-On (Group Area AF - HL)', + 59 => 'AF-Area Mode + AF-On (Group Area AF - VL)', + }, + }, + 16.1 => { # CSf1-p + Name => 'AssignBktButton', + Mask => 0x07, + PrintConv => { + 0 => 'Auto Bracketing', + 1 => 'Multiple Exposure', + 2 => 'HDR (high dynamic range)', + 3 => 'None', + }, + }, + 18.1 => { # CSf4-b + Name => 'CommandDialsChangeMainSub', + Mask => 0xe0, + PrintConv => { + 0 => 'Autofocus Off, Exposure Off', + 1 => 'Autofocus Off, Exposure On', + 2 => 'Autofocus Off, Exposure On (Mode A)', + 4 => 'Autofocus On, Exposure Off', + 5 => 'Autofocus On, Exposure On', + 6 => 'Autofocus On, Exposure On (Mode A)', + }, + }, + 18.2 => { # CSf4-d + Name => 'CommandDialsMenuAndPlayback', + Mask => 0x18, + PrintConv => { + 0 => 'On', + 1 => 'Off', + 2 => 'On (Image Review Excluded)', + }, + }, + 18.3 => { # CSf4-c + Name => 'CommandDialsApertureSetting', + Mask => 0x04, + PrintConv => { + 0 => 'Sub-command Dial', + 1 => 'Aperture Ring', + }, + }, + 18.4 => { # CSf6 + Name => 'ReleaseButtonToUseDial', + Mask => 0x01, + PrintConv => \%noYes, + }, + 19.1 => { # CSc2 + Name => 'StandbyTimer', + Mask => 0xf0, + PrintConv => { + 0 => '4 s', + 1 => '6 s', + 3 => '10 s', + 5 => '30 s', + 6 => '1 min', + 7 => '5 min', + 8 => '10 min', + 9 => '30 min', + 10 => 'No Limit', + }, + }, + 20.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 20.2 => { # CSc3-c + Name => 'SelfTimerShotInterval', + Mask => 0x30, + PrintConv => { + 0 => '0.5 s', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 20.3 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x0f, + }, + 21.1 => { # CSc4-d + Name => 'ImageReviewMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '2 s', + 1 => '4 s', + 3 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 21.2 => { # CSc4-e + Name => 'LiveViewMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + 4 => '20 min', + 5 => '30 min', + 6 => 'No Limit', + }, + }, + 22.1 => { # CSc4-b + Name => 'MenuMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 2 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 22.2 => { # CSc4-c + Name => 'ShootingInfoMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 2 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 23.1 => { # CSe1 + Name => 'FlashSyncSpeed', + Mask => 0xf0, + PrintConv => { + 2 => '1/250 s (auto FP)', + 3 => '1/250 s', + 5 => '1/200 s', + 6 => '1/160 s', + 7 => '1/125 s', + 8 => '1/100 s', + 9 => '1/80 s', + 10 => '1/60 s', + }, + }, + 23.2 => { # CSe2 + Name => 'FlashShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/60 s', + 1 => '1/30 s', + 2 => '1/15 s', + 3 => '1/8 s', + 4 => '1/4 s', + 5 => '1/2 s', + 6 => '1 s', + 7 => '2 s', + 8 => '4 s', + 9 => '8 s', + 10 => '15 s', + 11 => '30 s', + }, + }, + 31.1 => { # CSe5 + Name => 'ModelingFlash', + Mask => 0x20, + PrintConv => \%onOff, + }, + 36.1 => { # CSc4-a + Name => 'PlaybackMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 37.1 => { # CSf2-c + Name => 'MultiSelectorLiveView', + Mask => 0xc0, + PrintConv => { + 0 => 'Reset', + 1 => 'Zoom', + 3 => 'Not Used', + }, + }, + 38.1 => { # CSf3-a + Name => 'ShutterSpeedLock', + Mask => 0x80, + PrintConv => \%offOn, + }, + 38.2 => { # CSf3-b + Name => 'ApertureLock', + Mask => 0x40, + PrintConv => \%offOn, + }, + 38.3 => { # CSg1-h + Name => 'MovieShutterButton', + Mask => 0x10, + PrintConv => { + 0 => 'Take Photo', + 1 => 'Record Movies', + }, + }, + 38.4 => { # CSe3 + Name => 'FlashExposureCompArea', + Mask => 0x04, + PrintConv => { + 0 => 'Entire Frame', + 1 => 'Background Only', + }, + }, + 38.5 => { # CSe4 + Name => 'AutoFlashISOSensitivity', + Mask => 0x02, + PrintConv => { + 0 => 'Subject and Background', + 1 => 'Subject Only', + }, + }, + 41.1 => { # CSg1-c + Name => 'MovieFunc1Button', + Mask => 0xf0, + PrintConv => { + 0 => 'None', + 2 => 'Power Aperture (close)', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + 11 => 'Exposure Compensation -', + }, + }, + 41.2 => { # CSg1-a + Name => 'MoviePreviewButton', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Power Aperture (open)', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + 10 => 'Exposure Compensation +', + }, + }, + 42.1 => { # CSf1-d + Name => 'Func1ButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 7 => 'Photo Shooting Menu Bank', + 8 => 'Exposure Delay Mode', + }, + }, + 43.1 => { # CSf1-b + Name => 'PreviewButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 7 => 'Photo Shooting Menu Bank', # new with D500 + 8 => 'Exposure Delay Mode', + }, + }, + 45.1 => { # CSf1-q + Name => 'AssignMovieRecordButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 7 => 'Photo Shooting Menu Bank', + 11 => 'Exposure Mode', + }, + }, + 46.1 => { # CSb7-d + Name => 'FineTuneOptHighlightWeighted', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 47.1 => { # CSa12-b + Name => 'DynamicAreaAFDisplay', + Mask => 0x80, + PrintConv => \%offOn, + }, + 47.2 => { # CSa12-a + Name => 'AFPointIllumination', + Mask => 0x40, + PrintConv => { + 0 => 'Off', + 1 => 'On During Manual Focusing', + }, + }, + 47.3 => { # CSa7 + Name => 'StoreByOrientation', + Mask => 0x18, + PrintConv => { + 0 => 'Off', + 1 => 'Focus Point', + 2 => 'Focus Point and AF-area Mode', + }, + }, + 48.1 => { # CSb5 + Name => 'MatrixMetering', + Mask => 0x80, + PrintConv => { + 0 => 'Face Detection On', + 1 => 'Face Detection Off', + }, + }, + 48.2 => { # CSf8 + Name => 'LiveViewButtonOptions', + Mask => 0x30, + PrintConv => { + 0 => 'Enable', + 1 => 'Enable (Standby Timer Active)', # new with D500 + 2 => 'Disable', + }, + }, + 48.3 => { # CSa10 + Name => 'AFModeRestrictions', + Mask => 0x03, + PrintConv => { + 0 => 'No Restrictions', + 1 => 'AF-C', + 2 => 'AF-S', + }, + }, + 49.1 => { # CSa9 + Name => 'LimitAFAreaModeSelection', + Mask => 0x7e, + PrintConv => { + 0 => 'No Restrictions', + BITMASK => { + 0 => 'Auto-area', + 1 => 'Group-area', + 2 => '3D-tracking', + 3 => 'Dynamic area (153 points)', + 4 => 'Dynamic area (72 points)', + 5 => 'Dynamic area (25 points)', + }, + }, + }, + 52.1 => { # CSf1-r + Name => 'LensFocusFunctionButtons', + Mask => 0x3f, + PrintConv => { + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 24 => 'Preset Focus Point', + 26 => 'Flash Disable/Enable', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + }, + }, + 66.1 => { # CSf1-o + Name => 'VerticalMultiSelector', + Mask => 0xff, + PrintHex => 1, + PrintConv => { + 0x00 => 'Same as Multi-Selector with Info(U/D) & Playback(R/L)', + 0x08 => 'Same as Multi-Selector with Info(R/L) & Playback(U/D)', + 0x80 => 'Focus Point Selection', + }, + }, + 67.1 => { # CSf1-g + Name => 'VerticalFuncButton', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 18 => 'Reset Focus Point', + 19 => 'Grid Display', + 20 => 'My Menu', + 22 => 'Remote Release Only', + 23 => 'Preset Focus Point', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 54 => 'Highlight Active Focus Point', + }, + }, + 68.1 => { # CSf1-h + Name => 'VerticalFuncPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 7 => 'Photo Shooting Menu Bank', + 8 => 'Exposure Delay Mode', + 10 => 'ISO Sensitivity', + 11 => 'Exposure Mode', + 12 => 'Exposure Compensation', + 13 => 'Metering', + }, + }, + 70.1 => { # CSf1-j + Name => 'AF-OnButton', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + }, + }, + 71.1 => { # CSf1-k + Name => 'SubSelector', + Mask => 0x80, + PrintConv => { + 0 => 'Focus Point Selection', + 1 => 'Same as MultiSelector', + }, + }, + 72.1 => { # CSf1-l + Name => 'SubSelectorCenter', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 18 => 'Reset Focus Point', + 19 => 'Grid Display', + 20 => 'My Menu', + 22 => 'Remote Release Only', + 23 => 'Preset Focus Point', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 54 => 'Highlight Active Focus Point', + }, + }, + 73.1 => { # CSf1-m + Name => 'SubSelectorPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 4 => 'Choose Non-CPU Lens Number', + 7 => 'Photo Shooting Menu Bank', + }, + }, + 74.1 => { # CSg1-f + Name => 'AssignMovieSubselector', + Mask => 0xf0, + PrintConv => { + 0 => 'None', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + 5 => 'AE/AF Lock', + 6 => 'AE Lock (Only)', + 7 => 'AE Lock (Hold)', + 8 => 'AF Lock (Only)', + }, + }, + 75.1 => { # CSg1-d + Name => 'AssignMovieFunc1ButtonPlusDials', + Mask => 0x10, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + }, + }, + 75.2 => { # CSg1-b + Name => 'AssignMoviePreviewButtonPlusDials', + Mask => 0x01, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (DX/1.3x)', + }, + }, + 76.1 => { # CSg1-g + Name => 'AssignMovieSubselectorPlusDials', + Mask => 0x10, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + }, + }, + 77.1 => { # CSd4 + Name => 'SyncReleaseMode', # new with D500 + Mask => 0x80, + PrintConv => { + 0 => 'No Sync', + 1 => 'Sync', + }, + }, + 78.1 => { # CSa5 + Name => 'Three-DTrackingWatchArea', # new with D500 + Mask => 0x80, + PrintConv => { + 0 => 'Wide', + 1 => 'Normal', + }, + }, + 78.2 => { # CSa3-b + Name => 'SubjectMotion', + Mask => 0x60, + PrintConv => { + 0 => 'Steady', + 1 => 'Middle', + 2 => 'Erratic', + }, + }, + 78.3 => { # CSa8 + Name => 'AFActivation', + Mask => 0x08, + PrintConv => { + 0 => 'Shutter/AF-On', + 1 => 'AF-On Only', + }, + }, + 78.4 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On (Half Press)', + 2 => 'On (Burst Mode)' + }, + }, + 79.1 => { # CSf1-n + Name => 'VerticalAFOnButton', + Mask => 0x7f, + PrintConv => { + 0 => 'None', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 100 => 'Same as AF-On', + }, + }, + 80.1 => { # CSf1-e + Name => 'Func2Button', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 19 => 'Grid Display', + 20 => 'My Menu', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + }, + }, + 81.1 => { # CSf1-f + Name => 'Func2ButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 7 => 'Photo Shooting Menu Bank', + 8 => 'Exposure Delay Mode', + }, + }, + 82.1 => { # CSg1-e + Name => 'AssignMovieFunc2Button', + Mask => 0x70, + PrintConv => { + 0 => 'None', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + }, + }, + 83.1 => { # CSf1-i + Name => 'Func3Button', + Mask => 0x03, + PrintConv => { + 0 => 'None', + 1 => 'Voice Memo', + 2 => 'Rating', + 3 => 'Connect To Network', + }, + }, +); + +# D500 custom settings (ref 1) +%Image::ExifTool::NikonCustom::SettingsD500 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D500.', + 0.1 => { + Name => 'CustomSettingsBank', + Mask => 0x03, + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 1.1 => { # CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0xc0, + PrintConv => { + 0 => 'Release', + 1 => 'Release + Focus', + 2 => 'Focus', + 3 => 'Focus + Release', + }, + }, + 1.2 => { # CSa2 + Name => 'AF-SPrioritySelection', + Mask => 0x20, + PrintConv => { + 0 => 'Focus', + 1 => 'Release', + }, + }, + 1.3 => { # CSa6 + Name => 'NumberOfFocusPoints', + Mask => 0x10, + PrintConv => { + 0 => '55 Points', + 1 => '15 Points', + }, + }, + 1.4 => { # CSa4 + Name => 'Three-DTrackingFaceDetection', + Mask => 0x08, + PrintConv => \%offOn, + }, + 1.5 => { # CSa3-a + Name => 'BlockShotAFResponse', + Mask => 0x07, + #values 1-5 + }, + 2.1 => { # CSa11 + Name => 'FocusPointWrap', + Mask => 0x08, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 2.2 => { # CSa12-c + Name => 'AFPointBrightness', + Mask => 0x06, + PrintConv => { + 0 => 'Auto', + 1 => 'On', + 2 => 'Off', + }, + }, + 4.1 => { # CSd3 + Name => 'ISODisplay', + Mask => 0x08, + PrintConv => { + 0 => 'Show ISO Sensitivity', + 1 => 'Show Frame Count', + }, + }, + 4.2 => { # CSd8 + Name => 'GridDisplay', + Mask => 0x02, + PrintConv => \%onOff, + }, + 5.1 => { # CSd9 + Name => 'LCDIllumination', + Mask => 0x20, + PrintConv => \%offOn, + }, + 5.2 => { # CSd6 + Name => 'ElectronicFront-CurtainShutter', + Mask => 0x08, + PrintConv => \%offOn, + }, + 6.1 => { # CSf7 + Name => 'ReverseIndicators', + Mask => 0x80, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 6.2 => { # CSf4-a + Name => 'CommandDialsReverseRotation', + Mask => 0x18, + PrintConv => { + 0 => 'No', + 1 => 'Shutter Speed & Aperture', + 2 => 'Exposure Compensation', + 3 => 'Exposure Compensation, Shutter Speed & Aperture', + }, + }, + 6.3 => { # CSb4 + Name => 'EasyExposureCompensation', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (auto reset)', + }, + }, + 7.1 => { # CSb2 + Name => 'ExposureControlStepSize', + Mask => 0xc0, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 7.2 => { # CSb1 + Name => 'ISOStepSize', + Mask => 0x30, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 7.3 => { # CSb3 + Name => 'ExposureCompStepSize', + Mask => 0x0c, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 8.1 => { # CSb6 + Name => 'CenterWeightedAreaSize', + Mask => 0xe0, + PrintConv => { + 0 => '6 mm', + 1 => '8 mm', + 2 => '10 mm', + 3 => '13 mm', + 4 => 'Average', + }, + }, + 8.2 => { # CSb7-a + Name => 'FineTuneOptMatrixMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.1 => { # CSb7-b + Name => 'FineTuneOptCenterWeighted', + Mask => 0xf0, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.2 => { # CSb7-c + Name => 'FineTuneOptSpotMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 10.1 => { # CSf2-a + Name => 'MultiSelectorShootMode', + Mask => 0xe0, #same offset and settings as D810 but with a different decoding + PrintConv => { + 0 => 'Select Center Focus Point (Reset)', + 2 => 'Preset Focus Point (Pre)', + 3 => 'Highlight Active Focus Point', + 4 => 'Not Used (None)', + }, + }, + 10.2 => { # CSf2-b #same offset and settings as D810 but with a different decoding + Name => 'MultiSelectorPlaybackMode', + Mask => 0x0c, + PrintConv => { + 0 => 'Thumbnail On/Off', + 1 => 'View Histograms', + 2 => 'Zoom On/Off', + 3 => 'Choose Folder', + }, + }, + 10.3 => { # CSf5 + Name => 'MultiSelector', + Mask => 0x01, + PrintConv => { + 0 => 'Do Nothing', + 1 => 'Reset Meter-off Delay', + }, + }, + 11.1 => { # CSd5 + Name => 'ExposureDelayMode', + Mask => 0xc0, + PrintConv => { + 0 => 'Off', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 11.2 => { # CSd1 + Name => 'CLModeShootingSpeed', + Mask => 0x0f, + PrintConv => '"$val fps"', + PrintConvInv => '$val=~s/\s*fps//i; $val', + }, + 12.1 => { # CSd2 + Name => 'MaxContinuousRelease', + # values: 1-100 + }, + 13.1 => { # CSe7 + Name => 'AutoBracketOrder', + Mask => 0x10, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }, + 13.2 => { # CSe6 + Name => 'AutoBracketModeM', + Mask => 0x0c, + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + }, + }, + 14.1 => { # CSf1-c + Name => 'Func1Button', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 19 => 'Grid Display', + 20 => 'My Menu', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + }, + }, + 15.1 => { # CSf1-a + Name => 'PreviewButton', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 19 => 'Grid Display', + 20 => 'My Menu', +# 21 => 'Disable Synchronized Release', # removed with D500 + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', # new with D500 + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', # new with D500 + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', # new with D500 + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', # new with D500 + 40 => 'AF-Area Mode (Group Area AF)', # new with D500 + 41 => 'AF-Area Mode (Auto Area AF)', # new with D500 + 42 => 'AF-Area Mode + AF-On (Single)', # new with D500 + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', # new with D500 + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', # new with D500 + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)',# new with D500 + 46 => 'AF-Area Mode + AF-On (Group Area AF)', # new with D500 + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', # new with D500 + 49 => 'Sync Release (Master Only)', # new with D500 + 50 => 'Sync Release (Remote Only)', # new with D500 + }, + }, + 16.1 => { # CSf1-j + Name => 'AssignBktButton', + Mask => 0x07, + PrintConv => { + 0 => 'Auto Bracketing', + 1 => 'Multiple Exposure', + 2 => 'HDR (high dynamic range)', + 3 => 'None', + }, + }, + 18.1 => { # CSf4-b + Name => 'CommandDialsChangeMainSub', + Mask => 0xe0, + PrintConv => { + 0 => 'Autofocus Off, Exposure Off', + 1 => 'Autofocus Off, Exposure On', + 2 => 'Autofocus Off, Exposure On (Mode A)', + 4 => 'Autofocus On, Exposure Off', + 5 => 'Autofocus On, Exposure On', + 6 => 'Autofocus On, Exposure On (Mode A)', + }, + }, + 18.2 => { # CSf4-d + Name => 'CommandDialsMenuAndPlayback', + Mask => 0x18, + PrintConv => { + 0 => 'On', + 1 => 'Off', + 2 => 'On (Image Review Excluded)', + }, + }, + 18.3 => { # CSf4-c + Name => 'CommandDialsApertureSetting', + Mask => 0x04, + PrintConv => { + 0 => 'Sub-command Dial', + 1 => 'Aperture Ring', + }, + }, + 18.4 => { # CSf6 + Name => 'ReleaseButtonToUseDial', + Mask => 0x01, + PrintConv => \%noYes, + }, + 19.1 => { # CSc2 + Name => 'StandbyTimer', + Mask => 0xf0, + PrintConv => { + 0 => '4 s', + 1 => '6 s', + 3 => '10 s', + 5 => '30 s', + 6 => '1 min', + 7 => '5 min', + 8 => '10 min', + 9 => '30 min', + 10 => 'No Limit', + }, + }, + 20.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 20.2 => { # CSc3-c + Name => 'SelfTimerShotInterval', + Mask => 0x30, + PrintConv => { + 0 => '0.5 s', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 20.3 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x0f, + }, + 21.1 => { # CSc4-d + Name => 'ImageReviewMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '2 s', + 1 => '4 s', + 3 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 21.2 => { # CSc4-e + Name => 'LiveViewMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + 4 => '20 min', + 5 => '30 min', + 6 => 'No Limit', + }, + }, + 22.1 => { # CSc4-b + Name => 'MenuMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 2 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 22.2 => { # CSc4-c + Name => 'ShootingInfoMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 2 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 23.1 => { # CSe1 + Name => 'FlashSyncSpeed', + Mask => 0xf0, + PrintConv => { + 2 => '1/250 s (auto FP)', + 3 => '1/250 s', + 5 => '1/200 s', + 6 => '1/160 s', + 7 => '1/125 s', + 8 => '1/100 s', + 9 => '1/80 s', + 10 => '1/60 s', + }, + }, + 23.2 => { # CSe2 + Name => 'FlashShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/60 s', + 1 => '1/30 s', + 2 => '1/15 s', + 3 => '1/8 s', + 4 => '1/4 s', + 5 => '1/2 s', + 6 => '1 s', + 7 => '2 s', + 8 => '4 s', + 9 => '8 s', + 10 => '15 s', + 11 => '30 s', + }, + }, + 31.1 => { # CSe5 + Name => 'ModelingFlash', + Mask => 0x20, + PrintConv => \%onOff, + }, + 36.1 => { # CSc4-a + Name => 'PlaybackMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 37.1 => { # CSf2-c + Name => 'MultiSelectorLiveView', + Mask => 0xc0, + PrintConv => { + 0 => 'Reset', + 1 => 'Zoom', + 3 => 'Not Used', + }, + }, + 38.1 => { # CSf3-a + Name => 'ShutterSpeedLock', + Mask => 0x80, + PrintConv => \%offOn, + }, + 38.2 => { # CSf3-b + Name => 'ApertureLock', + Mask => 0x40, + PrintConv => \%offOn, + }, + 38.3 => { # CSg1-h + Name => 'MovieShutterButton', + Mask => 0x10, + PrintConv => { + 0 => 'Take Photo', + 1 => 'Record Movies', + }, + }, + 38.4 => { # CSe3 + Name => 'FlashExposureCompArea', + Mask => 0x04, + PrintConv => { + 0 => 'Entire Frame', + 1 => 'Background Only', + }, + }, + 38.5 => { # CSe4 + Name => 'AutoFlashISOSensitivity', + Mask => 0x02, + PrintConv => { + 0 => 'Subject and Background', + 1 => 'Subject Only', + }, + }, + 41.1 => { # CSg1-c + Name => 'MovieFunc1Button', + Mask => 0xf0, + PrintConv => { + 0 => 'None', + 2 => 'Power Aperture (close)', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + 11 => 'Exposure Compensation -', + }, + }, + 41.2 => { # CSg1-a + Name => 'MoviePreviewButton', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Power Aperture (open)', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + 10 => 'Exposure Compensation +', + }, + }, + 42.1 => { # CSf1-d + Name => 'Func1ButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (DX/1.3x)', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 7 => 'Photo Shooting Menu Bank', + 8 => 'Exposure Delay Mode', + }, + }, + 43.1 => { # CSf1-b + Name => 'PreviewButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (DX/1.3x)', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 7 => 'Photo Shooting Menu Bank', # new with D500 + 8 => 'Exposure Delay Mode', + }, + }, + 45.1 => { # CSf1-k + Name => 'AssignMovieRecordButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (DX/1.3x)', + 2 => 'Shutter Speed & Aperture Lock', + 7 => 'Photo Shooting Menu Bank', + 11 => 'Exposure Mode', + }, + }, + 46.1 => { # CSb7-d + Name => 'FineTuneOptHighlightWeighted', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 47.1 => { # CSa12-b + Name => 'DynamicAreaAFDisplay', + Mask => 0x80, + PrintConv => \%offOn, + }, + 47.2 => { # CSa12-a + Name => 'AFPointIllumination', + Mask => 0x40, + PrintConv => { + 0 => 'Off', + 1 => 'On During Manual Focusing', + }, + }, + 47.3 => { # CSa7 + Name => 'StoreByOrientation', + Mask => 0x18, + PrintConv => { + 0 => 'Off', + 1 => 'Focus Point', + 2 => 'Focus Point and AF-area Mode', + }, + }, + 47.4 => { # CSa12-c + Name => 'GroupAreaAFIllumination', + Mask => 0x04, + PrintConv => { + 0 => 'Squares', + 1 => 'Dots', + }, + }, + 48.1 => { # CSb5 + Name => 'MatrixMetering', + Mask => 0x80, + PrintConv => { + 0 => 'Face Detection On', + 1 => 'Face Detection Off', + }, + }, + 48.2 => { # CSf8 + Name => 'LiveViewButtonOptions', + Mask => 0x30, + PrintConv => { + 0 => 'Enable', + 1 => 'Enable (Standby Timer Active)', # new with D500 + 2 => 'Disable', + }, + }, + 48.3 => { # CSa10 + Name => 'AFModeRestrictions', + Mask => 0x03, + PrintConv => { + 0 => 'No Restrictions', + 1 => 'AF-C', + 2 => 'AF-S', + }, + }, + 49.1 => { # CSa9 + Name => 'LimitAFAreaModeSelection', + Mask => 0x7e, + PrintConv => { + 0 => 'No Restrictions', + BITMASK => { + 0 => 'Auto-area', + 1 => 'Group-area', + 2 => '3D-tracking', + 3 => 'Dynamic area (153 points)', + 4 => 'Dynamic area (72 points)', + 5 => 'Dynamic area (25 points)', + }, + }, + }, + 52.1 => { # CSf1-l + Name => 'LensFocusFunctionButtons', + Mask => 0x3f, + PrintConv => { + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 24 => 'Preset Focus Point', + 26 => 'Flash Disable/Enable', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + }, + }, + 66.1 => { # CSf10-d + Name => 'VerticalMultiSelector', + Mask => 0xff, + PrintHex => 1, + PrintConv => { + 0x00 => 'Same as Multi-Selector with Info(U/D) & Playback(R/L)', + 0x08 => 'Same as Multi-Selector with Info(R/L) & Playback(U/D)', + 0x80 => 'Focus Point Selection', + }, + }, + 67.1 => { # CSf10-a + Name => 'AssignMB-D17FuncButton', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 18 => 'Reset Focus Point', + 19 => 'Grid Display', + 20 => 'My Menu', + 22 => 'Remote Release Only', + 23 => 'Preset Focus Point', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 54 => 'Highlight Active Focus Point', + }, + }, + 68.1 => { # CSf10-b + Name => 'AssignMB-D17FuncButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (DX/1.3x)', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 7 => 'Photo Shooting Menu Bank', + 8 => 'Exposure Delay Mode', + 10 => 'ISO Sensitivity', + 11 => 'Exposure Mode', + 12 => 'Exposure Compensation', + 13 => 'Metering Mode', + }, + }, + 70.1 => { # CSf1-f + Name => 'AF-OnButton', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + }, + }, + 71.1 => { # CSf1-g + Name => 'SubSelector', + Mask => 0x80, + PrintConv => { + 0 => 'Focus Point Selection', + 1 => 'Same as MultiSelector', + }, + }, + 72.1 => { # CSf1-h + Name => 'SubSelectorCenter', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 18 => 'Reset Focus Point', + 19 => 'Grid Display', + 20 => 'My Menu', + 22 => 'Remote Release Only', + 23 => 'Preset Focus Point', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 54 => 'Highlight Active Focus Point', + }, + }, + 73.1 => { # CSf1-i + Name => 'SubSelectorPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (DX/1.3x)', + 2 => 'Shutter Speed & Aperture Lock', + 4 => 'Choose Non-CPU Lens Number', + 7 => 'Photo Shooting Menu Bank', + }, + }, + 74.1 => { # CSg1-f + Name => 'AssignMovieSubselector', + Mask => 0xf0, + PrintConv => { + 0 => 'None', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + 5 => 'AE/AF Lock', + 6 => 'AE Lock (Only)', + 7 => 'AE Lock (Hold)', + 8 => 'AF Lock (Only)', + }, + }, + 75.1 => { # CSg1-d + Name => 'AssignMovieFunc1ButtonPlusDials', + Mask => 0x10, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (DX/1.3x)', + }, + }, + 75.2 => { # CSg1-b + Name => 'AssignMoviePreviewButtonPlusDials', + Mask => 0x01, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (DX/1.3x)', + }, + }, + 76.1 => { # CSg1-g + Name => 'AssignMovieSubselectorPlusDials', + Mask => 0x10, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (DX/1.3x)', + }, + }, + 77.1 => { # CSd4 + Name => 'SyncReleaseMode', # new with D500 + Mask => 0x80, + PrintConv => { + 0 => 'No Sync', + 1 => 'Sync', + }, + }, + 78.1 => { # CSa5 + Name => 'Three-DTrackingWatchArea', # new with D500 + Mask => 0x80, + PrintConv => { + 0 => 'Wide', + 1 => 'Normal', + }, + }, + 78.2 => { # CSa3-b + Name => 'SubjectMotion', + Mask => 0x60, + PrintConv => { + 0 => 'Steady', + 1 => 'Middle', + 2 => 'Erratic', + }, + }, + 78.3 => { # CSa8 + Name => 'AFActivation', + Mask => 0x08, + PrintConv => { + 0 => 'Shutter/AF-On', + 1 => 'AF-On Only', + }, + }, + 78.4 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On (Half Press)', + 2 => 'On (Burst Mode)' + }, + }, + 79.1 => { # CSf10-c + Name => 'AssignMB-D17AF-OnButton', + Mask => 0x7f, + PrintConv => { + 0 => 'None', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 152 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 152 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 100 => 'Same as Camera AF-On Button', + }, + }, + 80.1 => { # CSf1-e + Name => 'Func2Button', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 15 => 'My Menu Top Item', + 20 => 'My Menu', + 55 => 'Rating', + }, + }, + 82.1 => { # CSg1-e + Name => 'AssignMovieFunc2Button', + Mask => 0x70, + PrintConv => { + 0 => 'None', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + }, + }, +); + +# D610 custom settings (ref forum6942) +%Image::ExifTool::NikonCustom::SettingsD610 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D610.', + 0.1 => { #CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0x80, + PrintConv => { + 0 => 'Release', + 1 => 'Focus', + }, + }, + 0.2 => { #CSa2 + Name => 'AF-SPrioritySelection', + Mask => 0x20, + PrintConv => { + 0 => 'Release', + 1 => 'Focus', + }, + }, + 0.3 => { # CSa6 + Name => 'NumberOfFocusPoints', + Mask => 0x10, + PrintConv => { + 0 => '39 Points', + 1 => '11 Points', + }, + }, + 0.4 => { # CSa3 + Name => 'FocusTrackingLockOn', + Mask => 0x07, + PrintConv => { + 0 => 'Off', + 1 => '1 Short', + 2 => '2', + 3 => '3 Normal', + 4 => '4', + 5 => '5 Long', + }, + }, + 1.1 => { # CSa5 + Name => 'FocusPointWrap', + Mask => 0x08, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 1.2 => { # CSa4 + Name => 'AFPointIllumination', + Mask => 0x06, + PrintConv => { + 0 => 'Auto', + 1 => 'On', + 2 => 'Off', + }, + }, + 1.3 => { # CSa7 + Name => 'AFAssist', + Mask => 0x01, + PrintConv => \%onOff, + }, + 5.1 => { # CSb3 + Name => 'EasyExposureCompensation', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On Auto Reset', + }, + }, + 6.1 => { # CSb2 + Name => 'ExposureControlStep', + Mask => 0x40, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + }, + }, + 6.2 => { # CSb1 + Name => 'ISOSensitivityStep', + Mask => 0x10, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + }, + }, + 7.1 => { # CSb4 + Name => 'CenterWeightedAreaSize', + Mask => 0xe0, + PrintConv => { + 0 => '8 mm', + 1 => '12 mm', + 2 => '15 mm', + 3 => '20 mm', + 4 => 'Average', + }, + }, + 7.2 => { # CSb5-a + Name => 'FineTuneOptMatrixMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 8.1 => { # CSb5-b + Name => 'FineTuneOptCenterWeighted', + Mask => 0xf0, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 8.2 => { # CSb5-c + Name => 'FineTuneOptSpotMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 17.1 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x02, + PrintConv => \%offOn, + }, + 18.1 => { # CSc2 + Name => 'StandbyTimer', + Mask => 0xf0, + PrintConv => { + 0 => '4 s', + 1 => '6 s', + 2 => '10 s', + 3 => '30 s', + 4 => '1 min', + 5 => '5 min', + 6 => '10 min', + 7 => '30 min', + 8 => 'No Limit', + }, + }, + 18.2 => { # CSc5 + Name => 'RemoteOnDuration', + Mask => 0x03, + PrintConv => { + 0 => '1 min', + 1 => '5 min', + 2 => '10 min', + 3 => '20 min', + }, + }, + 19.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 19.2 => { # CSc3-c + Name => 'SelfTimerShotInterval', + Mask => 0x30, + PrintConv => { + 0 => '0.5 s', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 19.3 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x0f, + }, + 20.1 => { # CSc4-d + Name => 'ImageReviewMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '2 s', + 1 => '4 s', + 2 => '10 s', + 3 => '20 s', + 4 => '1 min', + 5 => '5 min', + 6 => '10 min', + }, + }, + 20.2 => { # CSc4-e + Name => 'LiveViewMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '5 min', + 1 => '10 min', + 2 => '15 min', + 3 => '20 min', + 4 => '30 min', + 5 => 'No Limit', + }, + }, + 21.1 => { # CSc4-b + Name => 'MenuMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', # default + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 21.2 => { # CSc4-c + Name => 'ShootingInfoMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 1 => '10 s', # default + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 35.1 => { # CSc4-a + Name => 'PlaybackMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, +); + +# D810 custom settings (ref 1) +%Image::ExifTool::NikonCustom::SettingsD810 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + DATAMEMBER => [ 24.1 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D810.', + 0.1 => { # CSf1 + Name => 'LightSwitch', + Mask => 0x08, + PrintConv => { + 0 => 'LCD Backlight', + 1 => 'LCD Backlight and Shooting Information', + }, + }, + 0.2 => { + Name => 'CustomSettingsBank', + Mask => 0x03, + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 1.1 => { #CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0xc0, + PrintConv => { + 0 => 'Release', + 1 => 'Release + Focus', + 2 => 'Focus', + }, + }, + 1.2 => { # CSa2 + Name => 'AF-SPrioritySelection', + Mask => 0x20, + PrintConv => { + 0 => 'Focus', + 1 => 'Release', + }, + }, + 1.3 => { # CSa7 + Name => 'AFPointSelection', + Mask => 0x10, + PrintConv => { + 0 => '51 Points', + 1 => '11 Points', + }, + }, + 1.4 => { # CSa3 + Name => 'FocusTrackingLockOn', + Mask => 0x07, + PrintConv => { + 0 => 'Off', + 1 => '1 (Short)', + 2 => '2', + 3 => '3 (Normal)', + 4 => '4', + 5 => '5 (Long)', + }, + }, + 2.1 => { # CSa4 + Name => 'AFActivation', + Mask => 0x80, + PrintConv => { + 0 => 'Shutter/AF-On', + 1 => 'AF-On Only', + }, + }, + 2.2 => { # CSa7 + Name => 'FocusPointWrap', + Mask => 0x08, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 2.3 => { # CSa6 + Name => 'AFPointBrightness', + Mask => 0x06, + PrintConv => { + 0 => 'Auto', + 1 => 'On', + 2 => 'Off', + }, + }, + 2.4 => { # CSa10 + Name => 'AFAssist', + Mask => 0x01, + PrintConv => \%onOff, + }, + 3.1 => { # CSd13 + Name => 'BatteryOrder', + Mask => 0x40, + PrintConv => { + 0 => 'MB-D12 First', + 1 => 'Camera Battery First', + }, + }, + 3.2 => { # CSd12 + Name => 'MB-D12BatteryType', + Mask => 0x03, + PrintConv => { + 0 => 'LR6 (AA alkaline)', + 1 => 'HR6 (AA Ni-MH)', + 2 => 'FR6 (AA lithium)', + }, + }, + 4.1 => { # CSd1-b + Name => 'Pitch', + Mask => 0x40, + PrintConv => { 0 => 'High', 1 => 'Low' }, + }, + 4.2 => { # CSf11 + Name => 'NoMemoryCard', + Mask => 0x20, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 4.3 => { # CSd8 + Name => 'ISODisplay', + Mask => 0x0c, + PrintConv => { + 0 => 'Show ISO/Easy ISO', + 1 => 'Show ISO Sensitivity', + 3 => 'Show Frame Count', + }, + }, + 4.4 => { # CSd7 + Name => 'GridDisplay', + Mask => 0x02, + PrintConv => \%onOff, + }, + 5.1 => { # CSd10 + Name => 'ShootingInfoDisplay', + Mask => 0xc0, + PrintConv => { + 0 => 'Not Set', # observed on a new camera prior to applying a setting for the first time + 1 => 'Auto', + 2 => 'Manual (dark on light)', + 3 => 'Manual (light on dark)', + }, + }, + 5.2 => { # CSd11 + Name => 'LCDIllumination', + Mask => 0x20, + PrintConv => \%offOn, + }, + 5.3 => { # CSd5 + Name => 'ElectronicFront-CurtainShutter', + Mask => 0x08, + PrintConv => \%offOn, + }, + 5.4 => { # CSd9 + Name => 'ScreenTips', + Mask => 0x04, + PrintConv => \%offOn, + }, + 5.5 => { # CSd1-a + Name => 'Beep', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Medium', + 3 => 'High', + }, + }, + 6.1 => { # CSf12 + Name => 'ReverseIndicators', + Mask => 0x80, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 6.2 => { # CSf9-a + Name => 'CommandDialsReverseRotation', + Mask => 0x18, + PrintConv => { + 0 => 'No', + 1 => 'Shutter Speed & Aperture', + 2 => 'Exposure Compensation', + 3 => 'Exposure Compensation, Shutter Speed & Aperture', + }, + }, + 6.3 => { # CSb4 + Name => 'EasyExposureCompensation', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (auto reset)', + }, + }, + 7.1 => { # CSb2 + Name => 'ExposureControlStepSize', + Mask => 0xc0, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 7.2 => { # CSb1 + Name => 'ISOStepSize', + Mask => 0x30, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 7.3 => { # CSb3 + Name => 'ExposureCompStepSize', + Mask => 0x0c, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 8.1 => { # CSb6 + Name => 'CenterWeightedAreaSize', + Mask => 0xe0, + PrintConv => { + 0 => '8 mm', + 1 => '12 mm', + 2 => '15 mm', + 3 => '20 mm', + 4 => 'Average', + }, + }, + 8.2 => { # CSb7-a + Name => 'FineTuneOptMatrixMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.1 => { # CSb7-b + Name => 'FineTuneOptCenterWeighted', + Mask => 0xf0, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.2 => { # CSb7-c + Name => 'FineTuneOptSpotMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 10.1 => { # CSf2-a + Name => 'MultiSelectorShootMode', + Mask => 0xc0, + PrintConv => { + 0 => 'Select Center Focus Point (Reset)', + 1 => 'Highlight Active Focus Point', + 2 => 'Preset Focus Point (Pre)', + 3 => 'Not Used (None)', + }, + }, + 10.2 => { # CSf2-b + Name => 'MultiSelectorPlaybackMode', + Mask => 0x30, + PrintConv => { + 0 => 'Thumbnail On/Off', + 1 => 'View Histograms', + 2 => 'Zoom On/Off', + 3 => 'Choose Folder', + }, + }, + 10.3 => { # CSf3 + Name => 'MultiSelector', + Mask => 0x01, + PrintConv => { + 0 => 'Do Nothing', + 1 => 'Reset Meter-off Delay', + }, + }, + 11.1 => { # CSd4 + Name => 'ExposureDelayMode', + Mask => 0xc0, + PrintConv => { + 0 => 'Off', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 11.2 => { # CSd2 + Name => 'CLModeShootingSpeed', + Mask => 0x0f, + PrintConv => '"$val fps"', + PrintConvInv => '$val=~s/\s*fps//i; $val', + }, + 12.1 => { # CSd3 + Name => 'MaxContinuousRelease', + # values: 1-100 + }, + 13.1 => { # CSe6 + Name => 'AutoBracketSet', + Mask => 0xe0, + PrintConv => { + 0 => 'AE & Flash', + 1 => 'AE Only', + 2 => 'Flash Only', + 3 => 'WB Bracketing', + 4 => 'Active D-Lighting', + }, + }, + 13.2 => { # CSe8 + Name => 'AutoBracketOrder', + Mask => 0x10, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }, + 13.3 => { # CSe7 + Name => 'AutoBracketModeM', + Mask => 0x0c, + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + }, + }, + 14.1 => { # CSf4-a + Name => 'FuncButton', + Mask => 0x1f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 19 => 'Grid Display', # values 19 and 20 are swapped from the D4s encodings + 20 => 'My Menu', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', # new value with D810 + }, + }, + 15.1 => { # CSf5-a + Name => 'PreviewButton', + Mask => 0x1f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 19 => 'Grid Display', # values 19 and 20 are swapped from the D4s encodings + 20 => 'My Menu', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', # new value with D810 + }, + }, + 16.1 => { # CSf8 + Name => 'AssignBktButton', + Mask => 0x07, + PrintConv => { + 0 => 'Auto Bracketing', + 1 => 'Multiple Exposure', + 2 => 'HDR (high dynamic range)', + 3 => 'None', + }, + }, + 17.1 => { # CSf6-a + Name => 'AELockButton', + Mask => 0x1f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 19 => 'Grid Display', # values 19 and 20 are swapped from the D4s encodings + 20 => 'My Menu', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', # new value with D810 + }, + }, + 18.1 => { # CSf9-b + Name => 'CommandDialsChangeMainSub', + Mask => 0xe0, + PrintConv => { + 0 => 'Autofocus Off, Exposure Off', + 1 => 'Autofocus Off, Exposure On', + 2 => 'Autofocus Off, Exposure On (Mode A)', + 4 => 'Autofocus On, Exposure Off', + 5 => 'Autofocus On, Exposure On', + 6 => 'Autofocus On, Exposure On (Mode A)', + }, + }, + 18.2 => { # CSf9-d + Name => 'CommandDialsMenuAndPlayback', + Mask => 0x18, + PrintConv => { + 0 => 'On', + 1 => 'Off', + 2 => 'On (Image Review Excluded)', + }, + }, + 18.3 => { # CSf9-c + Name => 'CommandDialsApertureSetting', + Mask => 0x04, + PrintConv => { + 0 => 'Sub-command Dial', + 1 => 'Aperture Ring', + }, + }, + 18.4 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x02, + PrintConv => \%offOn, + }, + 18.5 => { # CSf10 + Name => 'ReleaseButtonToUseDial', + Mask => 0x01, + PrintConv => \%noYes, + }, + 19.1 => { # CSc2 + Name => 'StandbyTimer', + Mask => 0xf0, + PrintConv => { + 0 => '4 s', + 1 => '6 s', + 3 => '10 s', + 5 => '30 s', + 6 => '1 min', + 7 => '5 min', + 8 => '10 min', + 9 => '30 min', + 10 => 'No Limit', #1 + }, + }, + 20.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 20.2 => { # CSc3-c + Name => 'SelfTimerShotInterval', + Mask => 0x30, + PrintConv => { + 0 => '0.5 s', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 20.3 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x0f, + }, + 21.1 => { # CSc4-d + Name => 'ImageReviewMonitorOffTime', # note: decode changed from D4s + Mask => 0xe0, + PrintConv => { + 0 => '2 s', + 1 => '4 s', + 3 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 21.2 => { # CSc4-e # note: decode changed from D4s + Name => 'LiveViewMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + 4 => '20 min', + 5 => '30 min', + 6 => 'No Limit', + }, + }, + 22.1 => { # CSc4-b # note: decode changed from D4s + Name => 'MenuMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 2 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 22.2 => { # CSc4-c # note: decode changed from D4s + Name => 'ShootingInfoMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 2 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 23.1 => { # CSe1 + Name => 'FlashSyncSpeed', + Mask => 0xf0, + PrintConv => { + 0 => '1/320 s (auto FP)', + 2 => '1/250 s (auto FP)', + 3 => '1/250 s', + 5 => '1/200 s', + 6 => '1/160 s', + 7 => '1/125 s', + 8 => '1/100 s', + 9 => '1/80 s', + 10 => '1/60 s', + }, + }, + 23.2 => { # CSe2 + Name => 'FlashShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/60 s', + 1 => '1/30 s', + 2 => '1/15 s', + 3 => '1/8 s', + 4 => '1/4 s', + 5 => '1/2 s', + 6 => '1 s', + 7 => '2 s', + 8 => '4 s', + 9 => '8 s', + 10 => '15 s', + 11 => '30 s', + }, + }, + 24.1 => { # CSe3 + Name => 'FlashControlBuilt-in', + Mask => 0xc0, + RawConv => '$$self{FlashControlBuiltin} = $val', + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + 2 => 'Repeating Flash', + 3 => 'Commander Mode', + }, + }, + 31.1 => { # CSe5 + Name => 'ModelingFlash', + Mask => 0x20, + PrintConv => \%onOff, + }, + 36.1 => { # CSc4-a + Name => 'PlaybackMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 37.1 => { # CSf2-c + Name => 'MultiSelectorLiveView', + Mask => 0xc0, + PrintConv => { + 0 => 'Reset', + 1 => 'Zoom', + 3 => 'Not Used', + }, + }, + 38.1 => { # CSf7-a + Name => 'ShutterSpeedLock', + Mask => 0x80, + PrintConv => \%offOn, + }, + 38.2 => { # CSf7-b + Name => 'ApertureLock', + Mask => 0x40, + PrintConv => \%offOn, + }, + 38.3 => { # CSg4 + Name => 'MovieShutterButton', + Mask => 0x20, + PrintConv => { + 0 => 'Take Photo', + 1 => 'Record Movies', + }, + }, + 38.4 => { # CSe4 + Name => 'FlashExposureCompArea', + Mask => 0x04, + PrintConv => { + 0 => 'Entire frame', + 1 => 'Background only', + }, + }, + 40.1 => { # CSg3 + Name => 'MovieAELockButtonAssignment', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + 5 => 'AE/AF Lock', + 6 => 'AE Lock Only', + 7 => 'AE Lock (hold)', + 8 => 'AF Lock Only', + }, + }, + 41.1 => { # CSg1 + Name => 'MovieFunctionButton', + Mask => 0x70, + PrintConv => { + 0 => 'None', + 1 => 'Power Aperture (open)', # bit '02' is also toggled on for this setting + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + }, + }, + 41.2 => { # CSg2 + Name => 'MoviePreviewButton', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 2 => 'Power Aperture (open)', # bit '10' is also toggled on for this setting + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + }, + }, + 42.1 => { # CSf4-b + Name => 'FuncButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 8 => 'Exposure Delay Mode', + }, + }, + 43.1 => { # CSf5-b + Name => 'PreviewButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 8 => 'Exposure Delay Mode', + }, + }, + 44.1 => { # CSf6-b + Name => 'AELockButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'Shutter Speed & Aperture Lock', + 4 => 'Choose Non-CPU Lens Number', + 8 => 'Exposure Delay Mode', + }, + }, + 45.1 => { # CSf13 + Name => 'AssignMovieRecordButton', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'Shutter Speed & Aperture Lock', + 9 => 'White Balance', + 10 => 'ISO Sensitivity', + }, + }, + 46.1 => { # CSb7-d + Name => 'FineTuneOptHighlightWeighted', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 47.1 => { # CSa5-b + Name => 'DynamicAreaAFDisplay', + Mask => 0x80, + PrintConv => \%offOn, + }, + 47.2 => { # CSa5-a # moved with D810 + Name => 'AFPointIllumination', + Mask => 0x40, + PrintConv => { + 0 => 'Off', + 1 => 'On During Manual Focusing', + }, + }, + 47.3 => { # CSa9 + Name => 'StoreByOrientation', + Mask => 0x18, + PrintConv => { + 0 => 'Off', + 1 => 'Focus Point', + 2 => 'Focus Point and AF-area mode', + }, + }, + 47.4 => { # CSa5-c + Name => 'GroupAreaAFIllumination', + Mask => 0x04, + PrintConv => { + 0 => 'Squares', # moved with D810 + 1 => 'Dots', + }, + }, + 48.1 => { # CSb5 + Name => 'MatrixMetering', + Mask => 0x80, + PrintConv => { + 0 => 'Face Detection On', + 1 => 'Face Detection Off', + }, + }, + 48.2 => { # CSf14 + Name => 'LiveViewButtonOptions', + Mask => 0x30, + PrintConv => { + 0 => 'Enable', + 2 => 'Disable', + }, + }, + 48.3 => { # CSa12 + Name => 'AFModeRestrictions', + Mask => 0x03, + PrintConv => { + 0 => 'No Restrictions', + 1 => 'AF-C', + 2 => 'AF-S', + }, + }, + 49.1 => { # CSa11 + Name => 'LimitAFAreaModeSelection', + Mask => 0x7e, + PrintConv => { + 0 => 'No Restrictions', + BITMASK => { + 0 => 'Auto-area', + 1 => 'Group-area', + 2 => '3D-tracking', + 3 => 'Dynamic area (51 points)', + 4 => 'Dynamic area (21 points)', + 5 => 'Dynamic area (9 points)', + }, + }, + }, + 50.1 => { # CSf15 + Name => 'AF-OnForMB-D12', + Mask => 0x07, + PrintConv => { + 0 => 'AE/AF Lock', + 1 => 'AE Lock Only', + 2 => 'AF Lock Only', + 3 => 'AE Lock (hold)', + 4 => 'AE Lock (reset)', + 5 => 'AF-On', + 6 => 'FV Lock', + 7 => 'Same As Fn Button', + }, + }, + 51.1 => { # CSf16 + Name => 'AssignRemoteFnButton', + Mask => 0x1f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 16 => '+NEF(RAW)', + 25 => 'Live View', + 26 => 'Flash Disable/Enable', + }, + }, + 52.1 => { # CSf17 + Name => 'LensFocusFunctionButtons', + Mask => 0x3f, + PrintConv => { + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 7 => 'AF Lock Only', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 24 => 'Preset focus Point', + 26 => 'Flash Disable/Enable', + 32 => 'AF-Area Mode: Single-point AF', + 33 => 'AF-Area Mode: Dynamic-area AF (9 points)', + 34 => 'AF-Area Mode: Dynamic-area AF (21 points)', + 35 => 'AF-Area Mode: Dynamic-area AF (51 points)', + 36 => 'AF-Area Mode: Group-area AF', + 37 => 'AF-Area Mode: Auto area AF', + }, + }, +); + +# D850 custom settings (ref 1) +%Image::ExifTool::NikonCustom::SettingsD850 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D850.', + 0.2 => { + Name => 'CustomSettingsBank', + Mask => 0x03, + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 1.1 => { #CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0xc0, + PrintConv => { + 0 => 'Release', + 1 => 'Release + Focus', + 2 => 'Focus', + 3 => 'Focus + Release', + }, + }, + 1.2 => { # CSa2 + Name => 'AF-SPrioritySelection', + Mask => 0x20, + PrintConv => { + 0 => 'Focus', + 1 => 'Release', + }, + }, + 1.3 => { # CSa6 + Name => 'AFPointSelection', + Mask => 0x10, + PrintConv => { + 0 => '55 Points', + 1 => '15 Points', + }, + }, + 1.4 => { # CSa4 + Name => 'Three-DTrackingFaceDetection', + Mask => 0x08, + PrintConv => \%offOn, + }, + 1.5 => { # CSa3-a + Name => 'BlockShotAFResponse', + Mask => 0x07, + PrintConv => { + 1 => '1 (Quick)', + 2 => '2', + 3 => '3 (Normal)', + 4 => '4', + 5 => '5 (Delay)', + }, + }, + 2.1 => { # CSa11 + Name => 'FocusPointWrap', + Mask => 0x08, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 2.2 => { # CSa12-a + Name => 'AFPointBrightness', + Mask => 0x06, + PrintConv => { + 0 => 'Auto', + 1 => 'On', + 2 => 'Off', + }, + }, + 4.1 => { # CSd3 + Name => 'ISODisplay', + Mask => 0x08, + PrintConv => { + 0 => 'Show ISO Sensitivity', + 1 => 'Show Frame Count', + }, + }, + 4.2 => { # CSd9 + Name => 'GridDisplay', + Mask => 0x02, + PrintConv => \%onOff, + }, + 5.1 => { # CSd10 + Name => 'LCDIllumination', + Mask => 0x20, + PrintConv => \%offOn, + }, + 5.2 => { # CSd6 + Name => 'ElectronicFront-CurtainShutter', + Mask => 0x08, + PrintConv => \%offOn, + }, + 6.1 => { # CSf7 + Name => 'ReverseIndicators', + Mask => 0x80, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 6.2 => { # CSf4-a + Name => 'CommandDialsReverseRotation', + Mask => 0x18, + PrintConv => { + 0 => 'No', + 1 => 'Shutter Speed & Aperture', + 2 => 'Exposure Compensation', + 3 => 'Exposure Compensation, Shutter Speed & Aperture', + }, + }, + 6.3 => { # CSb4 + Name => 'EasyExposureCompensation', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (Auto Reset)', + }, + }, + 7.1 => { # CSb2 + Name => 'ExposureControlStepSize', + Mask => 0xc0, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 7.2 => { # CSb1 + Name => 'ISOStepSize', + Mask => 0x30, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 7.3 => { # CSb3 + Name => 'ExposureCompStepSize', + Mask => 0x0c, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 8.1 => { # CSb6 + Name => 'CenterWeightedAreaSize', + Mask => 0xe0, + PrintConv => { + 0 => '8 mm', + 1 => '12 mm', + 2 => '15 mm', + 3 => '20 mm', + 4 => 'Average', + }, + }, + 8.2 => { # CSb7-a + Name => 'FineTuneOptMatrixMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.1 => { # CSb7-b + Name => 'FineTuneOptCenterWeighted', + Mask => 0xf0, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.2 => { # CSb7-c + Name => 'FineTuneOptSpotMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 10.1 => { # CSf2-a + Name => 'MultiSelectorShootMode', + Mask => 0xe0, + PrintConv => { + 0 => 'Select Center Focus Point (Reset)', + 2 => 'Preset Focus Point (Pre)', + 3 => 'Highlight Active Focus Point', + 4 => 'Not Used (None)', + }, + }, + 10.2 => { # CSf2-b + Name => 'MultiSelectorPlaybackMode', + Mask => 0x0c, + PrintConv => { + 0 => 'Thumbnail On/Off', + 1 => 'View Histograms', + 2 => 'Zoom On/Off', + 3 => 'Choose Folder', + }, + }, + 10.3 => { # CSf5 + Name => 'MultiSelector', + Mask => 0x01, + PrintConv => { + 0 => 'Do Nothing', + 1 => 'Reset Meter-off Delay', + }, + }, + 11.1 => { # CSd5 + Name => 'ExposureDelayMode', + Mask => 0xe0, + PrintConv => { + 0 => 'Off', + 1 => '0.2 s', #new with the D850 + 2 => '0.5 s', #new with the D850 + 3 => '1 s', + 4 => '2 s', + 5 => '3 s', + }, + }, + 11.2 => { # CSd1 + Name => 'CLModeShootingSpeed', + Mask => 0x0f, + PrintConv => '"$val fps"', + PrintConvInv => '$val=~s/\s*fps//i; $val', + }, + 12.1 => { # CSd2 + Name => 'MaxContinuousRelease', + # values: 1-100 + }, + 13.1 => { # CSe7 + Name => 'AutoBracketOrder', + Mask => 0x10, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }, + 13.2 => { # CSe6 + Name => 'AutoBracketModeM', + Mask => 0x0c, + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + }, + }, + 14.1 => { # CSf1-c + Name => 'Func1Button', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 19 => 'Grid Display', + 20 => 'My Menu', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 153 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 153 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 56 => 'AF-Area Mode (Dynamic Area 9 Points)', + 57 => 'AF-Area Mode + AF-On (Dynamic Area 9 Points)', + }, + }, + 15.1 => { # CSf1-a + Name => 'PreviewButton', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 19 => 'Grid Display', + 20 => 'My Menu', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 153 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 153 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 56 => 'AF-Area Mode (Dynamic Area 9 Points)', + 57 => 'AF-Area Mode + AF-On (Dynamic Area 9 Points)', + }, + }, + 16.1 => { # CSf1-j + Name => 'AssignBktButton', + Mask => 0x07, + PrintConv => { + 0 => 'Auto Bracketing', + 1 => 'Multiple Exposure', + 2 => 'HDR (high dynamic range)', + 3 => 'None', + }, + }, + 18.1 => { # CSf4-b + Name => 'CommandDialsChangeMainSub', + Mask => 0xe0, + PrintConv => { + 0 => 'Autofocus Off, Exposure Off', + 1 => 'Autofocus Off, Exposure On', + 2 => 'Autofocus Off, Exposure On (Mode A)', + 4 => 'Autofocus On, Exposure Off', + 5 => 'Autofocus On, Exposure On', + 6 => 'Autofocus On, Exposure On (Mode A)', + }, + }, + 18.2 => { # CSf4-d + Name => 'CommandDialsMenuAndPlayback', + Mask => 0x18, + PrintConv => { + 0 => 'On', + 1 => 'Off', + 2 => 'On (Image Review Excluded)', + }, + }, + 18.3 => { # CSf4-c + Name => 'CommandDialsApertureSetting', + Mask => 0x04, + PrintConv => { + 0 => 'Sub-command Dial', + 1 => 'Aperture Ring', + }, + }, + 18.4 => { # CSf6 + Name => 'ReleaseButtonToUseDial', + Mask => 0x01, + PrintConv => \%noYes, + }, + 19.1 => { # CSc2 + Name => 'StandbyTimer', + Mask => 0xf0, + PrintConv => { + 0 => '4 s', + 1 => '6 s', + 3 => '10 s', + 5 => '30 s', + 6 => '1 min', + 7 => '5 min', + 8 => '10 min', + 9 => '30 min', + 10 => 'No Limit', + }, + }, + 20.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 20.2 => { # CSc3-c + Name => 'SelfTimerShotInterval', + Mask => 0x30, + PrintConv => { + 0 => '0.5 s', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 20.3 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x0f, + }, + 21.1 => { # CSc4-d + Name => 'ImageReviewMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '2 s', + 1 => '4 s', + 3 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 21.2 => { # CSc4-e + Name => 'LiveViewMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + 4 => '20 min', + 5 => '30 min', + 6 => 'No Limit', + }, + }, + 22.1 => { # CSc4-b + Name => 'MenuMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 2 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 22.2 => { # CSc4-c + Name => 'ShootingInfoMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 2 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 23.1 => { # CSe1 + Name => 'FlashSyncSpeed', + Mask => 0xf0, + PrintConv => { + 2 => '1/250 s (auto FP)', + 3 => '1/250 s', + 5 => '1/200 s', + 6 => '1/160 s', + 7 => '1/125 s', + 8 => '1/100 s', + 9 => '1/80 s', + 10 => '1/60 s', + }, + }, + 23.2 => { # CSe2 + Name => 'FlashShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/60 s', + 1 => '1/30 s', + 2 => '1/15 s', + 3 => '1/8 s', + 4 => '1/4 s', + 5 => '1/2 s', + 6 => '1 s', + 7 => '2 s', + 8 => '4 s', + 9 => '8 s', + 10 => '15 s', + 11 => '30 s', + }, + }, + 31.1 => { # CSe5 + Name => 'ModelingFlash', + Mask => 0x20, + PrintConv => \%onOff, + }, + 36.1 => { # CSc4-a + Name => 'PlaybackMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 37.1 => { # CSf2-c + Name => 'MultiSelectorLiveView', + Mask => 0xc0, + PrintConv => { + 0 => 'Reset', + 1 => 'Zoom', + 3 => 'Not Used', + }, + }, + 38.1 => { # CSf3-a + Name => 'ShutterSpeedLock', + Mask => 0x80, + PrintConv => \%offOn, + }, + 38.2 => { # CSf3-b + Name => 'ApertureLock', + Mask => 0x40, + PrintConv => \%offOn, + }, + 38.3 => { # CSg1-h + Name => 'MovieShutterButton', + Mask => 0x10, + PrintConv => { + 0 => 'Take Photo', + 1 => 'Record Movies', + }, + }, + 38.4 => { # CSe3 + Name => 'FlashExposureCompArea', + Mask => 0x04, + PrintConv => { + 0 => 'Entire Frame', + 1 => 'Background Only', + }, + }, + 38.5 => { # CSe4 + Name => 'AutoFlashISOSensitivity', + Mask => 0x02, + PrintConv => { + 0 => 'Subject and Background', + 1 => 'Subject Only', + }, + }, + 41.1 => { # CSg1-c + Name => 'MovieFunc1Button', + Mask => 0xf0, + PrintConv => { + 0 => 'None', + 2 => 'Power Aperture (close)', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + 11 => 'Exposure Compensation -', + }, + }, + 41.2 => { # CSg1-a + Name => 'MoviePreviewButton', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Power Aperture (open)', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + 10 => 'Exposure Compensation +', + }, + }, + 42.1 => { # CSf1-d + Name => 'Func1ButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 7 => 'Photo Shooting Menu Bank', + 8 => 'Exposure Delay Mode', + }, + }, + 43.1 => { # CSf1-b + Name => 'PreviewButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 7 => 'Photo Shooting Menu Bank', + 8 => 'Exposure Delay Mode', + }, + }, + 45.1 => { # CSf1-k + Name => 'AssignMovieRecordButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 7 => 'Photo Shooting Menu Bank', + 11 => 'Exposure Mode', + }, + }, + 46.1 => { # CSb7-d + Name => 'FineTuneOptHighlightWeighted', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 47.1 => { # CSa12-c + Name => 'DynamicAreaAFDisplay', + Mask => 0x80, + PrintConv => \%offOn, + }, + 47.2 => { # CSa12-b + Name => 'AFPointIllumination', + Mask => 0x40, + PrintConv => { + 0 => 'Off', + 1 => 'On During Manual Focusing', + }, + }, + 47.3 => { # CSa7 + Name => 'StoreByOrientation', + Mask => 0x18, + PrintConv => { + 0 => 'Off', + 1 => 'Focus Point', + 2 => 'Focus Point and AF-area Mode', + }, + }, + 48.1 => { # CSb5 + Name => 'MatrixMetering', + Mask => 0x80, + PrintConv => { + 0 => 'Face Detection On', + 1 => 'Face Detection Off', + }, + }, + 48.2 => { # CSf8 + Name => 'LiveViewButtonOptions', + Mask => 0x30, + PrintConv => { + 0 => 'Enable', + 1 => 'Enable (Standby Timer Active)', + 2 => 'Disable', + }, + }, + 48.3 => { # CSa10 + Name => 'AFModeRestrictions', + Mask => 0x03, + PrintConv => { + 0 => 'No Restrictions', + 1 => 'AF-C', + 2 => 'AF-S', + }, + }, + 49.1 => { # CSa9 + Name => 'LimitAFAreaModeSelection', #note that 'Dynamic area (9 points)' can be selected from the camera menu but the setting is not written to the EXIF data. + Mask => 0x7e, #...This AF Mode was added to the D5 firmware several months after the camera's initial release which may help explain the inconsistency. + PrintConv => { + 0 => 'No Restrictions', + BITMASK => { + 0 => 'Auto-area', + 1 => 'Group-area', + 2 => '3D-tracking', + 3 => 'Dynamic area (153 points)', + 4 => 'Dynamic area (72 points)', + 5 => 'Dynamic area (25 points)', + }, + }, + }, + 52.1 => { # CSf1-l + Name => 'LensFocusFunctionButtons', + Mask => 0x3f, + PrintConv => { + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 24 => 'Preset Focus Point', + 26 => 'Flash Disable/Enable', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 153 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 153 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 56 => 'AF-Area Mode (Dynamic Area 9 Points)', + 57 => 'AF-Area Mode + AF-On (Dynamic Area 9 Points)', + }, + }, + 66.1 => { # CSf10-d + Name => 'VerticalMultiSelector', + Mask => 0xff, + PrintHex => 1, + PrintConv => { + 0x00 => 'Same as Multi-Selector with Info(U/D) & Playback(R/L)', + 0x08 => 'Same as Multi-Selector with Info(R/L) & Playback(U/D)', + 0x80 => 'Focus Point Selection', + }, + }, + 67.1 => { # CSf10-a + Name => 'AssignMB-D18FuncButton', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 18 => 'Reset Focus Point', + 19 => 'Grid Display', + 20 => 'My Menu', + 22 => 'Remote Release Only', + 23 => 'Preset Focus Point', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 153 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 153 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 54 => 'Highlight Active Focus Point', + 56 => 'AF-Area Mode (Dynamic Area 9 Points)', + 57 => 'AF-Area Mode + AF-On (Dynamic Area 9 Points)', + }, + }, + 68.1 => { # CSf10-b + Name => 'AssignMB-D18FuncButtonPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 7 => 'Photo Shooting Menu Bank', + 8 => 'Exposure Delay Mode', + 10 => 'ISO Sensitivity', + 11 => 'Exposure Mode', + 12 => 'Exposure Compensation', + 13 => 'Metering Mode', + }, + }, + 70.1 => { # CSf1-f + Name => 'AF-OnButton', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 153 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 153 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 56 => 'AF-Area Mode (Dynamic Area 9 Points)', + 57 => 'AF-Area Mode + AF-On (Dynamic Area 9 Points)', + }, + }, + 71.1 => { # CSf1-g + Name => 'SubSelector', + Mask => 0x80, + PrintConv => { + 0 => 'Focus Point Selection', + 1 => 'Same as MultiSelector', + }, + }, + 72.1 => { # CSf1-h + Name => 'SubSelectorCenter', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 18 => 'Reset Focus Point', + 19 => 'Grid Display', + 20 => 'My Menu', + 22 => 'Remote Release Only', + 23 => 'Preset Focus Point', + 26 => 'Flash Disable/Enable', + 27 => 'Highlight-weighted Metering', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 153 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 153 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 49 => 'Sync Release (Master Only)', + 50 => 'Sync Release (Remote Only)', + 54 => 'Highlight Active Focus Point', + 56 => 'AF-Area Mode (Dynamic Area 9 Points)', + 57 => 'AF-Area Mode + AF-On (Dynamic Area 9 Points)', + }, + }, + 73.1 => { # CSf1-i + Name => 'SubSelectorPlusDials', + Mask => 0x0f, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + 2 => 'Shutter Speed & Aperture Lock', + 4 => 'Choose Non-CPU Lens Number', + 7 => 'Photo Shooting Menu Bank', + }, + }, + 74.1 => { # CSg1-f + Name => 'AssignMovieSubselector', + Mask => 0xf0, + PrintConv => { + 0 => 'None', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + 5 => 'AE/AF Lock', + 6 => 'AE Lock (Only)', + 7 => 'AE Lock (Hold)', + 8 => 'AF Lock (Only)', + }, + }, + 75.1 => { # CSg1-d + Name => 'AssignMovieFunc1ButtonPlusDials', + Mask => 0x10, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + }, + }, + 75.2 => { # CSg1-b + Name => 'AssignMoviePreviewButtonPlusDials', + Mask => 0x01, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + }, + }, + 76.1 => { # CSg1-g + Name => 'AssignMovieSubselectorPlusDials', + Mask => 0x10, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + }, + }, + 77.1 => { # CSd4 + Name => 'SyncReleaseMode', + Mask => 0x80, + PrintConv => { + 0 => 'No Sync', + 1 => 'Sync', + }, + }, + 77.2 => { # CSd11 (new with D850) + Name => 'ContinuousModeLiveView', + Mask => 0x40, + PrintConv => \%offOn, + }, + 78.1 => { # CSa5 + Name => 'Three-DTrackingWatchArea', + Mask => 0x80, + PrintConv => { + 0 => 'Wide', + 1 => 'Normal', + }, + }, + 78.2 => { # CSa3-b + Name => 'SubjectMotion', + Mask => 0x60, + PrintConv => { + 0 => 'Steady', + 1 => 'Middle', + 2 => 'Erratic', + }, + }, + 78.3 => { # CSa8 + Name => 'AFActivation', + Mask => 0x08, + PrintConv => { + 0 => 'Shutter/AF-On', + 1 => 'AF-On Only', + }, + }, + 78.4 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On (Half Press)', + 2 => 'On (Burst Mode)' + }, + }, + 79.1 => { # CSf10-c + Name => 'AssignMB-D18AF-OnButton', + Mask => 0x7f, + PrintConv => { + 0 => 'None', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 36 => 'AF-Area Mode (Single)', + 37 => 'AF-Area Mode (Dynamic Area 25 Points)', + 38 => 'AF-Area Mode (Dynamic Area 72 Points)', + 39 => 'AF-Area Mode (Dynamic Area 153 Points)', + 40 => 'AF-Area Mode (Group Area AF)', + 41 => 'AF-Area Mode (Auto Area AF)', + 42 => 'AF-Area Mode + AF-On (Single)', + 43 => 'AF-Area Mode + AF-On (Dynamic Area 25 Points)', + 44 => 'AF-Area Mode + AF-On (Dynamic Area 72 Points)', + 45 => 'AF-Area Mode + AF-On (Dynamic Area 153 Points)', + 46 => 'AF-Area Mode + AF-On (Group Area AF)', + 47 => 'AF-Area Mode + AF-On (Auto Area AF)', + 56 => 'AF-Area Mode (Dynamic Area 9 Points)', + 57 => 'AF-Area Mode + AF-On (Dynamic Area 9 Points)', + 100 => 'Same as Camera AF-On Button', + }, + }, + 80.1 => { # CSf1-e + Name => 'Func2Button', + Mask => 0x3f, + PrintConv => { + 0 => 'None', + 15 => 'My Menu Top Item', + 20 => 'My Menu', + 55 => 'Rating', + }, + }, + 82.1 => { # CSg1-e + Name => 'AssignMovieFunc2Button', + Mask => 0x70, + PrintConv => { + 0 => 'None', + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + }, + }, +); + +# D5000 custom settings (ref PH) +%Image::ExifTool::NikonCustom::SettingsD5000 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D5000.', + # Missing/Incomplete settings: + # CSd7 - LiveViewDisplayOptions [couldn't find in data - try again with live view shots] + 0.1 => { # CSa1 + Name => 'AFAreaModeSetting', + Mask => 0x60, + PrintConv => { + 0 => 'Single Area', + 1 => 'Dynamic Area', + 2 => 'Auto-area', + 3 => '3D-tracking (11 points)', + }, + }, + 0.2 => { # CSa2 + Name => 'AFAssist', + Mask => 0x01, + PrintConv => \%onOff, + }, + 2.1 => { # CSd1 + Name => 'Beep', + Mask => 0xc0, + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'High', + }, + }, + 2.2 => { # CSd2 + Name => 'GridDisplay', + Mask => 0x02, + PrintConv => \%onOff, + }, + 2.3 => { # CSd3 + Name => 'ISODisplay', + Mask => 0x08, + PrintConv => \%onOff, + }, + 2.4 => { # CSf4 + Name => 'NoMemoryCard', + Mask => 0x20, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 3.1 => { # CSd4 + Name => 'FileNumberSequence', + Mask => 0x08, + PrintConv => \%onOff, + }, + 4.1 => { # CSa4 + Name => 'RangeFinder', + Mask => 0x10, + PrintConv => \%offOn, + }, + 4.2 => { # CSd6 + Name => 'DateImprint', + Mask => 0x08, + PrintConv => \%offOn, + }, + 4.3 => { # CSf5 + Name => 'ReverseIndicators', + Mask => 0x80, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 5.1 => { # CSb1 + Name => 'EVStepSize', + Mask => 0x40, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + }, + }, + 9.1 => { # CSd5 + Name => 'ExposureDelayMode', + Mask => 0x40, + PrintConv => \%offOn, + }, + 11.1 => { # CSe2 + Name => 'AutoBracketSet', + Mask => 0xc0, + PrintConv => { + 0 => 'Exposure', + # (NOTE: the following are reversed in the D5100 -- is this correct?) + 1 => 'Active D-Lighting', #(NC) + 2 => 'WB Bracketing', + }, + }, + 12.1 => { # CSf1 + Name => 'TimerFunctionButton', + Mask => 0x38, + PrintConv => { + 0 => 'Self-timer', + 1 => 'Release Mode', + 2 => 'Image Quality/Size', #(NC) + 3 => 'ISO', #(NC) + 4 => 'White Balance', #(NC) + 5 => 'Active D-Lighting', #(NC) + 6 => '+ NEF (RAW)', + 7 => 'Auto Bracketing', + }, + }, + 15.1 => { # CSf2 + Name => 'AELockButton', + Mask => 0x38, + PrintConv => { + 0 => 'AE/AF Lock', + 1 => 'AE Lock Only', #(NC) + 2 => 'AF Lock Only', #(NC) + 3 => 'AE Lock (hold)', + 4 => 'AF-ON', + }, + }, + 16.1 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x02, + PrintConv => \%offOn, + }, + 16.2 => { # CSf3 + Name => 'CommandDialsReverseRotation', + Mask => 0x80, + PrintConv => \%noYes, + }, + 17.1 => { # CSc2-c + Name => 'MeteringTime', + Mask => 0x70, + PrintConv => { + 0 => '4 s', + 1 => '8 s', + 2 => '20 s', + 3 => '1 min', + 4 => '30 min', + }, + }, + 17.2 => { # CSc4 + Name => 'RemoteOnDuration', + Mask => 0x03, + PrintConv => { + 0 => '1 min', + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + }, + }, + 18.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 18.2 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x1e, + }, + 19.1 => { # CSc2-b + Name => 'ImageReviewTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '8 s', + 2 => '20 s', + 3 => '1 min', + 4 => '10 min', + }, + }, + 20.1 => { # CSc2-a + Name => 'PlaybackMenusTime', + Mask => 0xe0, + PrintConv => { + 0 => '8 s', + 1 => '12 s', + 2 => '20 s', + 3 => '1 min', + 4 => '10 min', + }, + }, + 22.1 => { # CSe1-a + Name => 'InternalFlash', + Mask => 0xc0, + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + }, + }, + 22.2 => { # CSe1-b + Name => 'ManualFlashOutput', + Mask => 0x1f, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val > 0 ? -3*log($val)/log(2) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 32.1 => { # CSa3 + Name => 'LiveViewAF', + Mask => 0x60, + PrintConv => { + 0 => 'Face Priority', + 1 => 'Wide Area', + 2 => 'Normal Area', + 3 => 'Subject Tracking', + }, + }, +); + +# D5100 custom settings (ref PH) +%Image::ExifTool::NikonCustom::SettingsD5100 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D5100.', + 0.1 => { # CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0x80, + PrintConv => { + 0 => 'Release', + 1 => 'Focus', + }, + }, + 1.1 => { # CSa2 + Name => 'AFAssist', + Mask => 0x01, + PrintConv => \%onOff, + }, + 3.1 => { # CSd1 + Name => 'Beep', + Mask => 0xc0, + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'High', + }, + }, + 3.2 => { # CSf4 + Name => 'NoMemoryCard', + Mask => 0x20, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 3.3 => { # CSd2 + Name => 'ISODisplay', + Mask => 0x08, + PrintConv => \%onOff, + }, + 4.1 => { # CSd3 + Name => 'FileNumberSequence', + Mask => 0x08, + PrintConv => \%onOff, + }, + 5.1 => { # CSa3 + Name => 'RangeFinder', + Mask => 0x10, + PrintConv => \%offOn, + }, + # (it looks like CSd5 DateImprint is not stored) + 5.2 => { # CSf5 + Name => 'ReverseIndicators', + Mask => 0x80, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 6.1 => { # CSb1 + Name => 'EVStepSize', + Mask => 0x40, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + }, + }, + 10.1 => { # CSd4 + Name => 'ExposureDelayMode', + Mask => 0x40, + PrintConv => \%offOn, + }, + 12.1 => { # CSe2 + Name => 'AutoBracketSet', + Mask => 0xc0, + PrintConv => { + 0 => 'Exposure', + # (NOTE: the following are reversed from the D5000 -- is D5000 correct?) + 1 => 'WB Bracketing', + 2 => 'Active D-Lighting', + }, + }, + 13.1 => { # CSf1 + Name => 'TimerFunctionButton', + Mask => 0x38, + PrintConv => { + 0 => 'Self-timer', + 1 => 'Release Mode', + 2 => 'Image Quality/Size', + 3 => 'ISO', + 4 => 'White Balance', + 5 => 'Active D-Lighting', + 6 => '+ NEF (RAW)', + 7 => 'Auto Bracketing', + }, + }, + 16.1 => { # CSf2 + Name => 'AELockButton', + Mask => 0x38, + PrintConv => { + 0 => 'AE/AF Lock', + 1 => 'AE Lock Only', + 2 => 'AF Lock Only', + 3 => 'AE Lock (hold)', + 4 => 'AF-ON', + }, + }, + 17.1 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x02, + PrintConv => \%offOn, + }, + 17.2 => { # CSf3 + Name => 'CommandDialsReverseRotation', + Mask => 0x80, + PrintConv => \%noYes, + }, + 18.1 => { # CSc2-d + Name => 'MeteringTime', + Mask => 0x70, + PrintConv => { + 0 => '4 s', + 1 => '8 s', + 2 => '20 s', #(NC) + 3 => '1 min', + 4 => '30 min', #(NC) + }, + }, + 18.2 => { # CSc4 + Name => 'RemoteOnDuration', + Mask => 0x03, + PrintConv => { + 0 => '1 min', + 1 => '5 min', + 2 => '10 min', #(NC) + 3 => '20 min', # (but picture in manual shows 15 min) + }, + }, + 19.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 19.2 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x0f, + }, + 20.1 => { # CSc2-b + Name => 'ImageReviewTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '8 s', #(NC) + 2 => '20 s', + 3 => '1 min', #(NC) + 4 => '10 min', #(NC) + }, + }, + 20.2 => { # CSc2-c + Name => 'LiveViewMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '3 min', + 1 => '5 min', #(NC) + 2 => '10 min', + 3 => '15 min', #(NC) + 4 => '20 min', #(NC) + 5 => '30 min', #(NC) + }, + }, + 21.1 => { # CSc2-a + Name => 'PlaybackMenusTime', + Mask => 0xe0, + PrintConv => { + 0 => '8 s', #(NC) + 1 => '12 s', + 2 => '20 s', + 3 => '1 min', + 4 => '10 min', #(NC) + }, + }, + 23.1 => { # CSe1-a + Name => 'InternalFlash', + Mask => 0xc0, + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + }, + }, + 23.1 => { # CSe1-b + Name => 'ManualFlashOutput', + Mask => 0x1f, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val > 0 ? -3*log($val)/log(2) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, +); + +# D5200 custom settings (ref PH) +%Image::ExifTool::NikonCustom::SettingsD5200 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D5200.', + 0.1 => { # CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0x80, + PrintConv => { + 0 => 'Release', + 1 => 'Focus', + }, + }, + 0.2 => { # CSa2 + Name => 'NumberOfFocusPoints', + Mask => 0x10, + PrintConv => { + 0 => '39 Points', + 1 => '11 Points', + }, + }, + 1.1 => { # CSa3 + Name => 'AFAssist', + Mask => 0x01, + PrintConv => \%onOff, + }, + 3.1 => { # CSd1 + Name => 'Beep', + Mask => 0xc0, + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'High', + }, + }, + 3.2 => { # CSf4 + Name => 'NoMemoryCard', + Mask => 0x20, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 3.3 => { # CSd3 + Name => 'ISODisplay', + Mask => 0x08, + PrintConv => \%onOff, + }, + 4.1 => { # CSd3 + Name => 'FileNumberSequence', + Mask => 0x08, + PrintConv => \%onOff, + }, + 5.1 => { # CSa4 + Name => 'RangeFinder', + Mask => 0x04, + PrintConv => \%offOn, + }, + 5.2 => { # CSf3-a + Name => 'ReverseExposureCompDial', + Mask => 0x10, + PrintConv => \%noYes, + }, + 5.3 => { # CSf3-b + Name => 'ReverseShutterSpeedAperture', + Mask => 0x08, + PrintConv => \%noYes, + }, + 5.4 => { # CSf5 + Name => 'ReverseIndicators', + Mask => 0x80, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 6.1 => { # CSb1 + Name => 'EVStepSize', + Mask => 0x40, # (bit 0x04 also changes) + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + }, + }, + 10.1 => { # CSd5 + Name => 'ExposureDelayMode', + Mask => 0x40, + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 12.1 => { # CSe2 + Name => 'AutoBracketSet', + Mask => 0xc0, + PrintConv => { + 0 => 'Exposure', + # (NOTE: the following are reversed from the D5000 -- is D5000 correct?) + 1 => 'WB Bracketing', + 2 => 'Active D-Lighting', + }, + }, + 13.1 => { # CSf1 + Name => 'FunctionButton', + Mask => 0x1f, + PrintConv => { + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-ON', + 16 => '+ NEF (RAW)', + 18 => 'Active D-Lighting', + 25 => 'Live View', + 26 => 'Image Quality', + 27 => 'ISO', + 28 => 'White Balance', + 29 => 'HDR', + 30 => 'Auto Bracketing', + 31 => 'AF-area Mode', + }, + }, + 16.1 => { # CSf2 + Name => 'AELockButton', + Mask => 0x0f, + PrintConv => { + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-ON', + }, + }, + 17.1 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x02, + PrintConv => \%offOn, + }, + 18.1 => { # CSc2-d + Name => 'StandbyTimer', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '8 s', + 2 => '20 s', + 3 => '1 min', + 4 => '30 min', + }, + }, + 18.2 => { # CSc4 + Name => 'RemoteOnDuration', + Mask => 0x03, + PrintConv => { + 0 => '1 min', + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + }, + }, + 19.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 19.2 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x0f, + }, + 20.1 => { # CSc2-b + Name => 'ImageReviewTime', + Mask => 0xe0, + PrintConv => { + 1 => '4 s', + 2 => '8 s', + 4 => '20 s', + 5 => '1 min', + 7 => '10 min', + }, + }, + 20.2 => { # CSc2-c + Name => 'LiveViewMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + 4 => '20 min', + 5 => '30 min', + }, + }, + 21.1 => { # CSc2-a + Name => 'PlaybackMenusTime', + Mask => 0xe0, + PrintConv => { + 1 => '8 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 23.1 => { # CSe1-a + Name => 'InternalFlash', + Mask => 0xc0, + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + }, + }, + 23.2 => { # CSe1-b + Name => 'ManualFlashOutput', + Mask => 0x1f, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val > 0 ? -3*log($val)/log(2) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, +); + +# D7000 custom settings (ref 2) +%Image::ExifTool::NikonCustom::SettingsD7000 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + DATAMEMBER => [ 23.1 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D7000.', + 0.1 => { # CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0x80, + PrintConv => { + 0 => 'Release', + 1 => 'Focus', + }, + }, + 0.2 => { # CSa2 + Name => 'AF-SPrioritySelection', + Mask => 0x20, + PrintConv => { + 0 => 'Focus', + 1 => 'Release', + }, + }, + 0.3 => { # CSa6 + Name => 'NumberOfFocusPoints', + Mask => 0x10, + PrintConv => { + 0 => '39 Points', + 1 => '11 Points', + }, + }, + 0.4 => { # CSa3 + Name => 'FocusTrackingLockOn', + Mask => 0x07, + PrintConv => { + 0 => 'Off', + 1 => '1 Short', + 2 => '2', + 3 => '3 Normal', + 4 => '4', + 5 => '5 Long', + }, + }, + 1.1 => { # CSa5 + Name => 'FocusPointWrap', + Mask => 0x08, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 1.2 => { # CSa4 + Name => 'AFPointIllumination', + Mask => 0x06, + PrintConv => { + 0 => 'Auto', + 1 => 'On', + 2 => 'Off', + }, + }, + 1.3 => { # CSa7 + Name => 'AFAssist', + Mask => 0x01, + PrintConv => \%onOff, + }, + 2.1 => { # CSd14 + Name => 'BatteryOrder', + Mask => 0x40, + PrintConv => { + 0 => 'MB-D11 First', + 1 => 'Camera Battery First', + }, + }, + 2.2 => { # CSa10 + Name => 'AF-OnForMB-D11', + Mask => 0x1c, + PrintConv => { + 0 => 'AE/AF Lock', + 1 => 'AE Lock Only', + 2 => 'AF Lock Only', + 3 => 'AE Lock (hold)', + 4 => 'AF-ON', + 5 => 'FV Lock', + 6 => 'Same as FUNC Button', + }, + }, + 2.3 => { # CSd13 + Name => 'MB-D11BatteryType', + Mask => 0x03, + PrintConv => { + 0 => 'LR6 (AA alkaline)', + 1 => 'Ni-MH (AA Ni-MH)', + 2 => 'FR6 (AA lithium)', + }, + }, + 3.1 => { # CSd1-b + Name => 'BeepPitch', + Mask => 0xc0, + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'High', + }, + }, + 3.2 => { # CSf8 + Name => 'NoMemoryCard', + Mask => 0x20, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 3.3 => { # CSd3 + Name => 'ISODisplay', + Mask => 0x0c, + PrintConv => { + 0 => 'Show ISO/Easy ISO', + 1 => 'Show ISO Sensitivity', + 3 => 'Show Frame Count', + }, + }, + 3.4 => { # CSd2 + Name => 'GridDisplay', + Mask => 0x02, + PrintConv => \%onOff, + }, + 3.5 => { # CSd4 + Name => 'ViewfinderWarning', + Mask => 0x01, + PrintConv => \%onOff, + }, + 4.1 => { # CSd9 + Name => 'ShootingInfoDisplay', + Mask => 0xc0, + PrintConv => { + 0 => 'Auto', + 2 => 'Manual (dark on light)', + 3 => 'Manual (light on dark)', + }, + }, + 4.2 => { # CSd10 + Name => 'LCDIllumination', + Mask => 0x20, + PrintConv => \%offOn, + }, + 4.3 => { # CSd8 + Name => 'FileNumberSequence', + Mask => 0x08, + PrintConv => \%onOff, + }, + 4.4 => { # CSd5 + Name => 'ScreenTips', + Mask => 0x04, + PrintConv => \%offOn, + }, + 4.5 => { # CSd1-a + Name => 'BeepVolume', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => '1', + 2 => '2', + 3 => '3', + }, + }, + 5.1 => { # CSf9 + Name => 'ReverseIndicators', + Mask => 0x80, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 5.2 => { # CSb3 + Name => 'EasyExposureCompensation', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On Auto Reset', + }, + }, + 6.1 => { # CSb2 + Name => 'ExposureControlStep', + Mask => 0x40, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + }, + }, + 6.2 => { # CSb1 + Name => 'ISOSensitivityStep', + Mask => 0x10, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + }, + }, + 7.1 => { # CSb4 + Name => 'CenterWeightedAreaSize', + Mask => 0xe0, + PrintConv => { + 0 => '6 mm', + 1 => '8 mm', + 2 => '10 mm', + 3 => '13 mm', + 4 => 'Average', + }, + }, + 10.1 => { # CSd11 + Name => 'ExposureDelayMode', + Mask => 0x40, + PrintConv => \%offOn, + }, + 10.2 => { # CSd6 + Name => 'CLModeShootingSpeed', + Mask => 0x07, + PrintConv => '"$val fps"', + PrintConvInv => '$val=~s/\s*fps//i; $val', + }, + 11 => { # CSd7 + Name => 'MaxContinuousRelease', + # values: 1-100 + }, + 12.1 => { # CSe5 + Name => 'AutoBracketSet', + Mask => 0xe0, #(NC) + PrintConv => { + 0 => 'AE & Flash', # default + 1 => 'AE Only', + 2 => 'Flash Only', #(NC) + 3 => 'WB Bracketing', #(NC) + 4 => 'Active D-Lighting', #(NC) + }, + }, + 12.2 => { # CSe6 + Name => 'AutoBracketOrder', + Mask => 0x10, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }, + 13.1 => { # CSf3 + Name => 'FuncButton', + Mask => 0xf8, + PrintConv => { + 0 => 'Grid Display', + 1 => 'FV Lock', + 2 => 'Flash Off', + 3 => 'Matrix Metering', + 4 => 'Center-weighted Metering', + 5 => 'Spot Metering', + 6 => 'My Menu Top', + 7 => '+ NEF (RAW)', + 8 => 'Active D-Lighting', + 9 => 'Preview', + 10 => 'AE/AF Lock', + 11 => 'AE Lock Only', + 12 => 'AF Lock Only', + 13 => 'AE Lock (hold)', + 14 => 'Bracketing Burst', + 15 => 'Playback', + 16 => '1EV Step Speed/Aperture', + 17 => 'Choose Non-CPU Lens', + 18 => 'Virtual Horizon', + 19 => 'Start Movie Recording', + }, + }, + 14.1 => { # CSf4 + Name => 'PreviewButton', + Mask => 0xf8, + PrintConv => { + 0 => 'Grid Display', + 1 => 'FV Lock', + 2 => 'Flash Off', + 3 => 'Matrix Metering', + 4 => 'Center-weighted Metering', + 5 => 'Spot Metering', + 6 => 'My Menu Top', + 7 => '+ NEF (RAW)', + 8 => 'Active D-Lighting', + 9 => 'Preview', + 10 => 'AE/AF Lock', + 11 => 'AE Lock Only', + 12 => 'AF Lock Only', + 13 => 'AE Lock (hold)', + 14 => 'Bracketing Burst', + 15 => 'Playback', + 16 => '1EV Step Speed/Aperture', + 17 => 'Choose Non-CPU Lens', + 18 => 'Virtual Horizon', + 19 => 'Start Movie Recording', + }, + }, + 16.1 => { # CSf5 + Name => 'AELockButton', + Mask => 0x38, + PrintConv => { + 0 => 'AE/AF Lock', + 1 => 'AE Lock Only', + 2 => 'AF Lock Only', + 3 => 'AE Lock (hold)', + 4 => 'AF-ON', + 5 => 'FV Lock', + }, + }, + 15.1 => { # CSf2 + Name => 'OKButton', + Mask => 0x18, + PrintConv => { + 1 => 'Select Center Focus Point', + 2 => 'Highlight Active Focus Point', + 3 => 'Not Used', #(NC) + 0 => 'Off', #(NC) + }, + }, + 17.1 => { # CSf6-a + Name => 'CommandDialsReverseRotation', + Mask => 0x80, + PrintConv => \%noYes, + }, + 17.2 => { # CSf6-b + Name => 'CommandDialsChangeMainSub', + Mask => 0x60, + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (A mode only)', + }, + }, + 17.3 => { # CSf6-c + Name => 'CommandDialsApertureSetting', + Mask => 0x04, + PrintConv => { + 0 => 'Sub-command Dial', + 1 => 'Aperture Ring', + }, + }, + 17.4 => { # CSf6-d + Name => 'CommandDialsMenuAndPlayback', + Mask => 0x18, + PrintConv => { + 0 => 'On', + 2 => 'On (Image Review Exclude)', + 1 => 'Off', + }, + }, + 17.5 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x02, + PrintConv => \%offOn, + }, + 17.6 => { # CSf7 + Name => 'ReleaseButtonToUseDial', + Mask => 0x01, + PrintConv => \%noYes, + }, + 18.1 => { # CSc2 + Name => 'MeteringTime', + Mask => 0xf0, + PrintConvColumns => 2, + PrintConv => { + 0 => '4 s', + 1 => '6 s', # default + 2 => '8 s', + 3 => '16 s', + 4 => '30 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + 8 => '30 min', + 9 => 'No Limit', + }, + }, + 18.2 => { # CSc5 + Name => 'RemoteOnDuration', + Mask => 0x03, + PrintConv => { + 0 => '1 min', + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + }, + }, + 19.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', # default + 3 => '20 s', + }, + }, + 19.2 => { # CSc3-c + Name => 'SelfTimerInterval', + Mask => 0x30, + PrintConv => { + 0 => '0.5 s', + 1 => '1 s', + 2 => '2 s', # default + 3 => '3 s', + }, + }, + 19.3 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x0f, + }, + 20.1 => { # CSc4-d + Name => 'ImageReviewTime', + Mask => 0xe0, + PrintConv => { #(NC) + 0 => '4 s', + 1 => '10 s', # default + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 20.2 => { # CSc4-e + Name => 'LiveViewMonitorOffTime', + Mask => 0x1c, + PrintConv => { #(NC) + 0 => '4 s', + 1 => '10 s', # default + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 21.1 => { # CSc4-b + Name => 'MenuMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', # default + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 21.2 => { # CSc4-c + Name => 'ShootingInfoMonitorOffTime', + Mask => 0x1c, + PrintConv => { #(NC) + 0 => '4 s', + 1 => '10 s', # default + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 22.1 => { # CSe1 + Name => 'FlashSyncSpeed', + Mask => 0xf0, + PrintConv => { + 0 => '1/320 s (auto FP)', + 1 => '1/250 s (auto FP)', + 2 => '1/250 s', + 3 => '1/200 s', + 4 => '1/160 s', + 5 => '1/125 s', + 6 => '1/100 s', + 7 => '1/80 s', + 8 => '1/60 s', + }, + }, + 22.2 => { # CSe2 + Name => 'FlashShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/60 s', + 1 => '1/30 s', + 2 => '1/15 s', + 3 => '1/8 s', + 4 => '1/4 s', + 5 => '1/2 s', + 6 => '1 s', + 7 => '2 s', + 8 => '4 s', + 9 => '8 s', + 10 => '15 s', + 11 => '30 s', + }, + }, + 23.1 => { # CSe3 + Name => 'FlashControlBuilt-in', + Mask => 0xc0, + RawConv => '$$self{FlashControlBuiltin} = $val', + PrintConv => { + 0 => 'TTL', + 1 => 'Manual', + 2 => 'Repeating Flash', + 3 => 'Commander Mode', + }, + }, + 23.2 => { # CSe3-b + Name => 'ManualFlashOutput', + Condition => '$$self{FlashControlBuiltin} == 1', + Mask => 0x1f, + ValueConv => '2 ** (-$val/3)', + ValueConvInv => '$val > 0 ? -3*log($val)/log(2) : 0', + PrintConv => q{ + return 'Full' if $val > 0.99; + Image::ExifTool::Exif::PrintExposureTime($val); + }, + PrintConvInv => '$val=~/F/i ? 1 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 24.1 => { # CSe3-ca + Name => 'RepeatingFlashOutput', + Condition => '$$self{FlashControlBuiltin} == 2', + Mask => 0x70, + ValueConv => '2 ** (-$val-2)', + ValueConvInv => '$val > 0 ? int(-log($val)/log(2)-2+0.5) : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 24.2 => { # CSe3-cb + Name => 'RepeatingFlashCount', + Condition => '$$self{FlashControlBuiltin} == 2', + Mask => 0x0f, + ValueConv => '$val < 10 ? $val + 1 : 5 * ($val - 7)', + ValueConvInv => '$val <= 10 ? $val - 1 : $val / 5 + 7', + }, + 25.1 => { # CSe3-cc (NC) + Name => 'RepeatingFlashRate', + Condition => '$$self{FlashControlBuiltin} == 2', + Mask => 0xf0, + ValueConv => '$val < 10 ? $val + 1 : 10 * ($val - 8)', + ValueConvInv => 'int(($val <= 10 ? $val - 1 : $val / 10 + 8) + 0.5)', + PrintConv => '"$val Hz"', + PrintConvInv => '$val=~/(\d+)/; $1 || 0', + }, + 26.1 => { # CSe3-da + Name => 'CommanderInternalTTLCompBuiltin', + Condition => '$$self{FlashControlBuiltin} == 3', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 27.1 => { # CSe3-db + Name => 'CommanderInternalTTLCompGroupA', + Condition => '$$self{FlashControlBuiltin} == 3', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 28.1 => { # CSe3-dc + Name => 'CommanderInternalTTLCompGroupB', + Condition => '$$self{FlashControlBuiltin} == 3', + Mask => 0x1f, + ValueConv => '($val - 9) / 3', + ValueConvInv => '$val * 3 + 9', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 30.1 => { # CSd11 + Name => 'FlashWarning', + Mask => 0x80, + PrintConv => \%onOff, + }, + 30.2 => { # CSe4 + Name => 'ModelingFlash', + Mask => 0x20, + PrintConv => \%onOff, + }, + 34.1 => { # CSa8-b + Name => 'LiveViewAFAreaMode', + Mask => 0x60, + PrintConv => { + 0 => 'Face-Priority', + 1 => 'NormalArea', + 2 => 'WideArea', + 3 => 'SubjectTracking', + }, + }, + 34.2 => { # CSa8-a + Name => 'LiveViewAFMode', + Mask => 0x02, + PrintConv => { + 0 => 'AF-C', + 1 => 'AF-F', + }, + }, + 35.1 => { # CSc4-a + Name => 'PlaybackMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', # default + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, +); + +# D4/D4S custom settings (ref 1, decoded from D4S) +%Image::ExifTool::NikonCustom::SettingsD4 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the D4 and D4S.', + 0.1 => { + Name => 'CustomSettingsBank', + Mask => 0x03, + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 1.1 => { #CSa1 + Name => 'AF-CPrioritySelection', + Mask => 0xc0, + PrintConv => { + 0 => 'Release', + 1 => 'Release + Focus', + 2 => 'Focus', + 3 => 'Focus + Release', + }, + }, + 1.2 => { # CSa2 + Name => 'AF-SPrioritySelection', + Mask => 0x20, + PrintConv => { + 0 => 'Focus', + 1 => 'Release', + }, + }, + 1.3 => { # CSa7 + Name => 'AFPointSelection', + Mask => 0x10, + PrintConv => { + 0 => '51 Points', + 1 => '11 Points', + }, + }, + 1.4 => { # CSa3 + Name => 'FocusTrackingLockOn', + Mask => 0x07, + PrintConv => { + 0 => 'Off', + 1 => '1 (Short)', + 2 => '2', + 3 => '3 (Normal)', + 4 => '4', + 5 => '5 (Long)', + }, + }, + 2.1 => { # CSa4 + Name => 'AFActivation', + Mask => 0x80, + PrintConv => { + 0 => 'Shutter/AF-On', + 1 => 'AF-On Only', + }, + }, + 2.2 => { # CSa6 + Name => 'FocusPointWrap', + Mask => 0x08, + PrintConv => { + 0 => 'No Wrap', + 1 => 'Wrap', + }, + }, + 4.1 => { # CSd1-b + Name => 'Pitch', + Mask => 0x40, + PrintConv => { 0 => 'High', 1 => 'Low' }, + }, + 4.2 => { # CSf12 + Name => 'NoMemoryCard', + Mask => 0x20, + PrintConv => { + 0 => 'Release Locked', + 1 => 'Enable Release', + }, + }, + 4.3 => { # CSd6 + Name => 'GridDisplay', + Mask => 0x02, + PrintConv => \%onOff, + }, + 5.1 => { # CSd9 + Name => 'ShootingInfoDisplay', + Mask => 0xc0, + PrintConv => { + # 0 - seen for D4 (PH) + 1 => 'Auto', + 2 => 'Manual (dark on light)', + 3 => 'Manual (light on dark)', + }, + }, + 5.2 => { # CSd10 + Name => 'LCDIllumination', + Mask => 0x20, + PrintConv => \%offOn, + }, + 5.3 => { # CSd8 + Name => 'ScreenTips', + Mask => 0x04, + PrintConv => \%offOn, + }, + 5.4 => { # CSd1-a + Name => 'Beep', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Medium', + 3 => 'High', + }, + }, + 6.1 => { # CSf13 + Name => 'ReverseIndicators', + Mask => 0x80, + PrintConv => { + 0 => '+ 0 -', + 1 => '- 0 +', + }, + }, + 6.2 => { # CSd7-a + Name => 'RearDisplay', + Mask => 0x40, + PrintConv => { + 0 => 'ISO', + 1 => 'Exposures Remaining', + }, + }, + 6.3 => { # CSd7-b + Name => 'ViewfinderDisplay', + Mask => 0x20, + PrintConv => { + 0 => 'Frame Count', + 1 => 'Exposures Remaining', + }, + }, + 6.4 => { # CSd10-a + Name => 'CommandDialsReverseRotation', + Mask => 0x18, + PrintConv => { + 0 => 'No', + 1 => 'Shutter Speed & Aperture', + 2 => 'Exposure Compensation', + 3 => 'Exposure Compensation, Shutter Speed & Aperture', + }, + }, + 6.5 => { # CSb4 + Name => 'EasyExposureCompensation', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (auto reset)', + }, + }, + 7.1 => { # CSb2 + Name => 'ExposureControlStepSize', + Mask => 0xc0, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 7.2 => { # CSb1 + Name => 'ISOStepSize', + Mask => 0x30, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 7.3 => { # CSb3 + Name => 'ExposureCompStepSize', + Mask => 0x0c, + PrintConv => { + 0 => '1/3 EV', + 1 => '1/2 EV', + 2 => '1 EV', + }, + }, + 8.1 => { # CSb6 (CSb5 for D4) + Name => 'CenterWeightedAreaSize', + Mask => 0xe0, + PrintConv => { + 0 => '8 mm', + 1 => '12 mm', + 2 => '15 mm', + 3 => '20 mm', + 4 => 'Average', + }, + }, + 8.2 => { # CSb7-a + Name => 'FineTuneOptMatrixMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.1 => { # CSb7-b + Name => 'FineTuneOptCenterWeighted', + Mask => 0xf0, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 9.2 => { # CSb7-c + Name => 'FineTuneOptSpotMetering', + Mask => 0x0f, + ValueConv => '($val > 0x7 ? $val - 0x10 : $val) / 6', + ValueConvInv => 'int($val*6+($val>0?0.5:-0.5))', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 10.1 => { # CSf1-a + Name => 'MultiSelectorShootMode', + Mask => 0xc0, + PrintConv => { + 0 => 'Select Center Focus Point (Reset)', + 2 => 'Preset Focus Point (Pre)', + 3 => 'Not Used (None)', + }, + }, + 10.2 => { # CSf1-b + Name => 'MultiSelectorPlaybackMode', + Mask => 0x30, + PrintConv => { + 0 => 'Thumbnail On/Off', + 1 => 'View Histograms', + 2 => 'Zoom On/Off', + 3 => 'Choose Folder', + }, + }, + 10.3 => { # CSf2 + Name => 'MultiSelector', + Mask => 0x01, + PrintConv => { + 0 => 'Do Nothing', + 1 => 'Reset Meter-off Delay', + }, + }, + 11.1 => { # CSd4 + Name => 'ExposureDelayMode', + Mask => 0xc0, + PrintConv => { + 0 => 'Off', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 11.2 => { # CSd2-a + Name => 'CHModeShootingSpeed', + Mask => 0x10, + PrintConv => { + 0 => '10 fps', + 1 => '11 fps', + }, + }, + 11.3 => { # CSd2-b + Name => 'CLModeShootingSpeed', + Mask => 0x0f, + PrintConv => '"$val fps"', + PrintConvInv => '$val=~s/\s*fps//i; $val', + }, + 12 => { # CSd3 + Name => 'MaxContinuousRelease', + # values: 1-200 + }, + 13.1 => { # CSe6 + Name => 'AutoBracketSet', + Mask => 0xe0, + PrintConv => { + 0 => 'AE & Flash', + 1 => 'AE Only', + 2 => 'Flash Only', + 3 => 'WB Bracketing', + 4 => 'Active D-Lighting', + }, + }, + 13.2 => { # CSe8 + Name => 'AutoBracketOrder', + Mask => 0x10, + PrintConv => { + 0 => '0,-,+', + 1 => '-,0,+', + }, + }, + 13.3 => { # CSe7 + Name => 'AutoBracketModeM', + Mask => 0x0c, + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + }, + }, + 14.1 => { # CSf3-a + Name => 'FuncButton', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 18 => 'My Menu', + 20 => 'Grid Display', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + }, + }, + 14.2 => { # CSf3-b + Name => 'FuncButtonPlusDials', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 6 => 'Shooting Bank Menu', + }, + }, + 15.1 => { # CSf4-a + Name => 'PreviewButton', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 18 => 'My Menu', + 20 => 'Grid Display', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 26 => 'Flash Disable/Enable', + }, + }, + 15.2 => { # CSf4-b + Name => 'PreviewButtonPlusDials', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 6 => 'Shooting Bank Menu', + }, + }, + 16.1 => { # CSf9 + Name => 'AssignBktButton', + Mask => 0x07, + PrintConv => { + 0 => 'Auto Bracketing', + 1 => 'Multiple Exposure', + 2 => 'HDR (high dynamic range)', + 3 => 'None', + }, + }, + 18.1 => { # CSf10-b + Name => 'CommandDialsChangeMainSub', + Mask => 0xe0, + PrintConv => { + 0 => 'Autofocus Off, Exposure Off', + 1 => 'Autofocus Off, Exposure On', + 2 => 'Autofocus Off, Exposure On (Mode A)', + 4 => 'Autofocus On, Exposure Off', + 5 => 'Autofocus On, Exposure On', + 6 => 'Autofocus On, Exposure On (Mode A)', + }, + }, + 18.2 => { # CSf10-d + Name => 'CommandDialsMenuAndPlayback', + Mask => 0x18, + PrintConv => { + 0 => 'On', + 1 => 'Off', + 2 => 'On (Image Review Excluded)', + }, + }, + 18.3 => { # CSf10-c + Name => 'CommandDialsApertureSetting', + Mask => 0x04, + PrintConv => { + 0 => 'Sub-command Dial', + 1 => 'Aperture Ring', + }, + }, + 18.4 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + Mask => 0x02, + PrintConv => \%offOn, + }, + 18.5 => { # CSf11 + Name => 'ReleaseButtonToUseDial', + Mask => 0x01, + PrintConv => \%noYes, + }, + 19.1 => { # CSc2 + Name => 'StandbyTimer', + Mask => 0xf0, + PrintConv => { + 0 => '4 s', + 1 => '6 s', + 3 => '10 s', + 5 => '30 s', + 6 => '1 min', + 7 => '5 min', + 8 => '10 min', + 9 => '30 min', + }, + }, + 20.1 => { # CSc3-a + Name => 'SelfTimerTime', + Mask => 0xc0, + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 20.2 => { # CSc3-b + Name => 'SelfTimerShotCount', + Mask => 0x0f, + }, + 20.3 => { # CSc3-c + Name => 'SelfTimerShotInterval', + Mask => 0x30, + PrintConv => { + 0 => '0.5 s', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 21.1 => { # CSc4-d + Name => 'ImageReviewMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '2 s', + 1 => '4 s', + 2 => '10 s', + 3 => '20 s', + 4 => '1 min', + 5 => '5 min', + 6 => '10 min', + + }, + }, + 21.2 => { # CSc4-e + Name => 'LiveViewMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '5 min', + 1 => '10 min', + 2 => '15 min', + 3 => '20 min', + 4 => '30 min', + 5 => 'No Limit', + }, + }, + 22.1 => { # CSc4-b + Name => 'MenuMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 22.2 => { # CSc4-c + Name => 'ShootingInfoMonitorOffTime', + Mask => 0x1c, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 23.1 => { # CSe1 + Name => 'FlashSyncSpeed', + Mask => 0xf0, + PrintConv => { + # 0x00 - seen for D4 (PH) + 1 => '1/250 s (auto FP)', + 2 => '1/250 s', + 3 => '1/200 s', + 4 => '1/160 s', + 5 => '1/125 s', + 6 => '1/100 s', + 7 => '1/80 s', + 8 => '1/60 s', + }, + }, + 23.2 => { # CSe2 + Name => 'FlashShutterSpeed', + Mask => 0x0f, + PrintConvColumns => 2, + PrintConv => { + 0 => '1/60 s', + 1 => '1/30 s', + 2 => '1/15 s', + 3 => '1/8 s', + 4 => '1/4 s', + 5 => '1/2 s', + 6 => '1 s', + 7 => '2 s', + 8 => '4 s', + 9 => '8 s', + 10 => '15 s', + 11 => '30 s', + }, + }, + 31.1 => { # CSe5 + Name => 'ModelingFlash', + Mask => 0x20, + PrintConv => \%onOff, + }, + 36.1 => { # CSc4-a + Name => 'PlaybackMonitorOffTime', + Mask => 0xe0, + PrintConv => { + 0 => '4 s', + 1 => '10 s', + 2 => '20 s', + 3 => '1 min', + 4 => '5 min', + 5 => '10 min', + }, + }, + 37.1 => { # CSf15 + Name => 'PlaybackZoom', + Mask => 0x01, + PrintConv => { + 0 => 'Use Separate Zoom Buttons', + 1 => 'Use Either Zoom Button with Command Dial', + }, + }, + 38.1 => { # CSf8-a + Name => 'ShutterSpeedLock', + Mask => 0x80, + PrintConv => \%offOn, + }, + 38.2 => { # CSf8-b + Name => 'ApertureLock', + Mask => 0x40, + PrintConv => \%offOn, + }, + 38.3 => { # CSg4 + Name => 'MovieShutterButton', + Mask => 0x30, + PrintConv => { + 0 => 'Take Photo', + 1 => 'Record Movies', + 2 => 'Live Frame Grab', + }, + }, + 38.4 => { # CSe4 + Name => 'FlashExposureCompArea', + Mask => 0x04, + PrintConv => { + 0 => 'Entire frame', + 1 => 'Background only', + }, + }, + 41.1 => { # CSg1-a + Name => 'MovieFunctionButton', + Mask => 0x70, + PrintConv => { + 0 => 'None', + 1 => 'Power Aperture (open)', # bit '02' is also toggled on for this setting + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + }, + }, + 41.2 => { # CSg2-a + Name => 'MoviePreviewButton', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 2 => 'Power Aperture (open)', # bit '10' is also toggled on for this setting + 3 => 'Index Marking', + 4 => 'View Photo Shooting Info', + }, + }, + 42.1 => { # CSf14 + Name => 'VerticalMultiSelector', + Mask => 0x60, + PrintConv => { + 0 => 'Same as Multi-Selector with Info(U/D) & Playback(R/L)', + 1 => 'Same as Multi-Selector with Info(R/L) & Playback(U/D)', + 2 => 'Focus Point Selection', + }, + }, + 42.2 => { # CSf7-a + Name => 'VerticalFuncButton', + Mask => 0x1f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 18 => 'My Menu', + 20 => 'Grid Display', + 26 => 'Flash Disable/Enable', + }, + }, + 43.1 => { # CSf7-b + Name => 'VerticalFuncButtonPlusDials', + Mask => 0xf0, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'One Step Speed / Aperture', + 4 => 'Choose Non-CPU Lens Number', + 5 => 'Active D-Lighting', + 6 => 'Shooting Bank Menu', + 7 => 'ISO Sensitivity', + 8 => 'Exposure Mode', + 9 => 'Exposure Compensation', + 10 => 'Metering', + }, + }, + 43.2 => { # CSf16 + Name => 'AssignMovieRecordButton', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'Shutter Speed & Aperture Lock', + 3 => 'ISO Sensitivity', + 4 => 'Shooting Bank Menu', + }, + }, + 46.1 => { # CSa5-c + Name => 'DynamicAreaAFDisplay', + Mask => 0x80, + PrintConv => \%offOn, + }, + 46.2 => { # CSa5-a + Name => 'AFPointIllumination', + Mask => 0x60, + PrintConv => { + 0 => 'Off', + 1 => 'On in Continuous Shooting Modes', + 2 => 'On During Manual Focusing', + 3 => 'On in Continuous Shooting and Manual Focusing', + }, + }, + 46.3 => { # CSa10 (D4 is slightly different -- needs checking) + Name => 'StoreByOrientation', + Mask => 0x18, + PrintConv => { + 0 => 'Off', + 1 => 'Focus Point', + 2 => 'Focus Point and AF-area mode', + }, + }, + 46.4 => { # CSa5-d + Name => 'GroupAreaAFIllumination', + Mask => 0x04, + PrintConv => { + 0 => 'Squares', + 1 => 'Dots', + }, + }, + 46.5 => { # CSa5-b + Name => 'AFPointBrightness', + Mask => 0x03, + PrintConv => { + 0 => 'Low', + 1 => 'Normal', + 2 => 'High', + 3 => 'Extra High', + }, + }, + 47.1 => { # CSa8 + Name => 'AFOnButton', + Mask => 0x70, + PrintConv => { + 0 => 'AF On', + 1 => 'AE/AF Lock', + 2 => 'AE Lock Only', + 3 => 'AE Lock (reset on release)', + 4 => 'AE Lock (hold)', + 5 => 'AF Lock Only', + 6 => 'None', + }, + }, + 47.2 => { # CSa9 + Name => 'VerticalAFOnButton', + Mask => 0x07, + PrintConv => { + 0 => 'Same as AF On', + 1 => 'AF On', + 2 => 'AE/AF Lock', + 3 => 'AE Lock Only', + 4 => 'AE Lock (reset on release)', + 5 => 'AE Lock (hold)', + 6 => 'AF Lock Only', + 7 => 'None', + }, + }, + 48.1 => { # CSf5 + Name => 'SubSelectorAssignment', + Mask => 0x80, + PrintConv => { + 0 => 'Focus Point Selection', + 1 => 'Same As Multi-selector', + }, + }, + 48.2 => { # CSg3-a + Name => 'MovieSubSelectorAssignment', + Mask => 0x07, + PrintConv => { + 0 => 'None', + 1 => 'Index Marking', + 2 => 'AE/AF Lock', + 3 => 'AE Lock Only', + 4 => 'AE Lock (hold)', + 5 => 'AF Lock Only', + 6 => 'View Photo Shooting Info', + }, + }, + 49.1 => { # CSf6-a + Name => 'SubSelector', + Mask => 0xf8, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock (hold)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + # 9 - seen for D4 (PH) + 10 => 'Bracketing Burst', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Playback', + 15 => 'My Menu Top Item', + 16 => '+NEF(RAW)', + 17 => 'Virtual Horizon', + 18 => 'My Menu', + 19 => 'Reset', # value appears to be specific to this control at this time + 20 => 'Grid Display', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 23 => 'Preview', # value appears to be specific to this control at this time + 26 => 'Flash Disable/Enable', + }, + }, + 49.2 => { # CSf6-b + Name => 'SubSelectorPlusDials', + Mask => 0x07, + PrintConv => { + # (not all values from CSf3-b/CSf4-b are available for CSf6-b) + 0 => 'None', + 1 => 'Choose Image Area (FX/DX/5:4)', + 2 => 'Shutter Speed & Aperture Lock', + # 3 => 'One Step Speed / Aperture', # (not available) + 4 => 'Choose Non-CPU Lens Number', + # 5 => 'Active D-Lighting', # (not available) + 6 => 'Shooting Bank Menu', + }, + }, + 50.1 => { # CSb5 + Name => 'MatrixMetering', + Condition => '$$self{Model} =~ /\bD4S/', + Notes => 'D4S only', + Mask => 0x80, + PrintConv => { + 0 => 'Face Detection On', + 1 => 'Face Detection Off', + }, + }, + 50.2 => { # CSf17 + Name => 'LiveViewButtonOptions', + Condition => '$$self{Model} =~ /\bD4S/', + Notes => 'D4S only', + Mask => 0x30, + PrintConv => { + 0 => 'Enable', + 1 => 'Enable (standby time active)', + 2 => 'Disable', + }, + }, + 50.3 => { # CSa12 + Name => 'AFModeRestrictions', + Condition => '$$self{Model} =~ /\bD4S/', + Notes => 'D4S only', + Mask => 0x03, + PrintConv => { + 0 => 'Off', + 1 => 'AF-C', + 2 => 'AF-S', + }, + }, + 51.1 => { # CSa11 + Name => 'LimitAFAreaModeSelection', + Condition => '$$self{Model} =~ /\bD4S/', + Notes => 'D4S only', + Mask => 0x7e, + PrintConv => { + 0 => 'No Restrictions', + BITMASK => { + 0 => 'Auto-area', + 1 => 'Group-area', + 2 => '3D-tracking', + 3 => 'Dynamic area (51 points)', + 4 => 'Dynamic area (21 points)', + 5 => 'Dynamic area (9 points)', + }, + }, + }, + 52.1 => { # CSg1-b + Name => 'MovieFunctionButtonPlusDials', + Mask => 0x10, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + }, + }, + 52.2 => { # CSg2-b + Name => 'MoviePreviewButtonPlusDials', + Mask => 0x01, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + }, + }, + 53.1 => { # CSg3-b + Name => 'MovieSubSelectorAssignmentPlusDials', + Mask => 0x10, + PrintConv => { + 0 => 'None', + 1 => 'Choose Image Area', + }, + }, + 54.1 => { # CSf18 + Name => 'AssignRemoteFnButton', + Condition => '$$self{Model} =~ /\bD4S/', + Notes => 'D4S only', + Mask => 0x1f, + PrintConv => { + 0 => 'None', + 1 => 'Preview', + 2 => 'FV Lock', + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 5 => 'AE Lock (reset on release)', + 7 => 'AF Lock Only', + 8 => 'AF-On', + 16 => '+NEF(RAW)', + 25 => 'Live View', + 26 => 'Flash Disable/Enable', + }, + }, + 55.1 => { # CSf19 + Name => 'LensFocusFunctionButtons', + Condition => '$$self{Model} =~ /\bD4S/', + Notes => 'D4S only', + Mask => 0x3f, + PrintConv => { + 3 => 'AE/AF Lock', + 4 => 'AE Lock Only', + 7 => 'AF Lock Only', + 21 => 'Disable Synchronized Release', + 22 => 'Remote Release Only', + 24 => 'Preset focus Point', + 26 => 'Flash Disable/Enable', + 32 => 'AF-Area Mode: Single-point AF', + 33 => 'AF-Area Mode: Dynamic-area AF (9 points)', + 34 => 'AF-Area Mode: Dynamic-area AF (21 points)', + 35 => 'AF-Area Mode: Dynamic-area AF (51 points)', + 36 => 'AF-Area Mode: Group-area AF', + 37 => 'AF-Area Mode: Auto area AF', + }, + }, +); + +# Z8 custom settings (ref 1) #base at offset26 + 1195 (firmware 1.0.0) +%Image::ExifTool::NikonCustom::SettingsZ8 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + DATAMEMBER => [ 185, 529 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the Z8.', + 1 => { + Name => 'CustomSettingsBank', + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 3 => { # CSa1 + Name => 'AF-CPrioritySelection', + PrintConv => { + 0 => 'Release', + 1 => 'Release + Focus', + 3 => 'Focus', + }, + }, + 5 => { Name => 'AF-SPrioritySelection', PrintConv => {0 => 'Release',1 => 'Focus'}}, #CSa2 + 7 => { # CSa3-a #when AFAreaMode is 3D-tracking, blocked shot response will be 3, regardless of this setting + Name => 'BlockShotAFResponse', + PrintConv => { + 1 => '1 (Quick)', + 2 => '2', + 3 => '3 (Normal)', + 4 => '4', + 5 => '5 (Delayed)', + }, + }, + 11 => {Name => 'AFPointSel',PrintConv => { 0 => 'Use All',1 => 'Use Half' }}, # CSa4 + 13 => { # CSa5 + Name => 'StoreByOrientation', + PrintConv => { + 0 => 'Off', + 1 => 'Focus Point', + 2 => 'Focus Point and AF-area mode', + }, + }, + 15 => { Name => 'AFActivation', PrintConv => {0 => 'AF-On Only', 1 => 'Shutter/AF-On'}}, # CSa6-a + 16 => { Name => 'AF-OnOutOfFocusRelease', PrintConv => {0 => 'Disable', 1 => 'Enable'}, Unknown => 1}, # CSa6-b + 17 => { Name => 'LimitAF-AreaModeSelPinpoint', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 19 => { Name => 'LimitAF-AreaModeSelWideAF_S', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 20 => { Name => 'LimitAF-AreaModeSelWideAF_L', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 21 => { Name => 'LimitAFAreaModeSelAuto', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 #left out hypen to retain compatibility with tag name in NikonSettings + 22 => { Name => 'FocusPointWrap', PrintConv => { 0 => 'No Wrap', 1 => 'Wrap' }, Unknown => 1 }, # CSa10 + 23 => { Name => 'ManualFocusPointIllumination', PrintConv => {0 => 'On During Focus Point Selection Only', 1 => 'On', }, Unknown => 1 }, # CSa11a + 24 => { Name => 'DynamicAreaAFAssist', PrintConv => { 0 => 'Focus Point Only',1 => 'Focus and Surrounding Points',}, Unknown => 1 }, # CSa11b + 25 => { Name => 'AF-AssistIlluminator', PrintConv => \%offOn }, # CSa12 + 26 => { Name => 'ManualFocusRingInAFMode', PrintConv => \%offOn }, # CSa15 + 27 => { Name => 'ExposureControlStepSize', PrintConv => \%thirdHalfFull }, # CSb2 + 29 => { # CSb3 + Name => 'EasyExposureCompensation', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (auto reset)', + }, + }, + 31 => { # CSb5 + Name => 'CenterWeightedAreaSize', + PrintConv => { + 0 => '8 mm', + 1 => '12 mm', + 4 => 'Average', + }, + }, + 33 => { # CSb6-a + Name => 'FineTuneOptMatrixMetering', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 35 => { # CSb6-b + Name => 'FineTuneOptCenterWeighted', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 37 => { # CSb6-c + Name => 'FineTuneOptSpotMetering', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 39 => { # CSb6-d + Name => 'FineTuneOptHighlightWeighted', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 41 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + PrintConv => { + 0 => 'Off', + 1 => 'On (Half Press)', + 2 => 'On (Burst Mode)', + }, + }, + 43 => { # CSc2-a + Name => 'SelfTimerTime', + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 45 => { Name => 'SelfTimerShotCount', }, # CSc2-b 1-9 + 49 => { # CSc2-c + Name => 'SelfTimerShotInterval', + PrintConv => { + 0 => '0.5 s', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 51 => { Name => 'PlaybackMonitorOffTime', %powerOffDelayTimesZ9 }, # CSc3-a + 53 => { Name => 'MenuMonitorOffTime', %powerOffDelayTimesZ9 }, # CSc3-b + 55 => { Name => 'ShootingInfoMonitorOffTime',%powerOffDelayTimesZ9 }, # CSc3-c + 57 => { Name => 'ImageReviewMonitorOffTime', %powerOffDelayTimesZ9 }, # CSc3-d + 59 => { Name => 'CLModeShootingSpeed', ValueConv => '$val + 1', ValueConvInv => '$val - 1', PrintConv => '"$val fps"', PrintConvInv => '$val=~s/\s*fps//i; $val' }, # CSd1b + 61 => { # CSd2 # values: 1-200 & 'No Limit' + Name => 'MaxContinuousRelease', + Format => 'int16s', + ValueConv => '($val eq -1 ? \'No Limit\' : $val ) ', + }, + 65 => { Name => 'SyncReleaseMode', PrintConv => { 0 => 'No Sync', 1 => 'Sync' }, Unknown => 1 }, # CSd4 + 69 => { Name => 'LimitSelectableImageAreaDX', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd6-1 + 70 => { Name => 'LimitSelectableImageArea1To1', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd6-2 + 71 => { Name => 'LimitSelectableImageArea16To9', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd6-3 + 72 => { Name => 'FileNumberSequence', PrintConv => \%offOn }, # CSd7 + 73 => { #CSa13b + Name => 'FocusPeakingLevel', + Unknown => 1, + PrintConv => { + 0 => 'High Sensitivity', + 1 => 'Standard Sensitivity', + 2 => 'Low Sensitivity', + }, + }, + 75 => { #CSa13c + Name => 'FocusPeakingHighlightColor', + Unknown => 1, + PrintConv => { + 0 => 'Red', + 1 => 'Yellow', + 2 => 'Blue', + 3 => 'White', + }, + }, + + 81 => { Name => 'ContinuousModeDisplay', PrintConv => \%offOn }, # CSd12 + 83 => { # CSe1-a Previous cameras reported this with HighSpeedSync indicator appended as '(Auto FP)'. Z9 separated the 2 fields. + Name => 'FlashSyncSpeed', + ValueConv => '($val-144)/8', + PrintConv => { + 0 => '1/60 s', + 1 => '1/80 s', + 2 => '1/100 s', + 3 => '1/125 s', + 4 => '1/160 s', + 5 => '1/200 s', + 6 => '1/250 s', + }, + }, + 85 => { Name => 'HighSpeedSync', PrintConv => \%offOn }, # CSe1-b + 87 => { # CSe2 + Name => 'FlashShutterSpeed', + ValueConv => 'my $t = ($val - 16) % 24; $t ? $val / 24 : 2 + ($val - 16) / 24', #unusual decode perhaps due to need to accomodate 4 new values? + PrintConv => { + 0 => '1 s', + 1 => '1/2 s', + 2 => '1/4 s', + 3 => '1/8 s', + 4 => '1/15 s', + 5 => '1/30 s', + 6 => '1/60 s', + 7 => '30 s', + 8 => '15 s', + 9 => '8 s', + 10 => '4 s', + 11 => '2 s', + }, + }, + 89 => { Name => 'FlashExposureCompArea', PrintConv => { 0 => 'Entire Frame', 1 => 'Background Only' } }, # CSe3 + 91 => { Name => 'AutoFlashISOSensitivity', PrintConv => { 0 => 'Subject and Background',1 => 'Subject Only'} }, # CSe4 + 93 => { Name => 'ModelingFlash', PrintConv => \%offOn }, # CSe5 + 95 => { # CSe6 + Name => 'AutoBracketModeM', + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + 4 => 'Flash/ISO', + }, + }, + 97 => { Name => 'AutoBracketOrder', PrintConv => { 0 => '0,-,+',1 => '-,0,+' } }, # CSe7 + 99 => { Name => 'Func1Button', %buttonsZ9}, # CSf2-a + 115 => { Name => 'Func2Button', %buttonsZ9}, # CSf2-b + 131 => { Name => 'AFOnButton', %buttonsZ9}, # CSf2-c + 143 => { Name => 'SubSelector', %buttonsZ9, Unknown => 1}, # CSf2-g + 155 => { Name => 'AssignMovieRecordButton', %buttonsZ9, Unknown => 1}, # CSf2-m + 159 => { Name => 'LensFunc1Button', %buttonsZ9}, # CSf2-o + 167 => { Name => 'LensFunc2Button', %buttonsZ9}, # CSf2-p + 173 => { # CSf2-q + Name => 'LensControlRing', + PrintConv => { + 0 => 'None (Disabled)', + 1 => 'Focus (M/A)', + 2 => 'ISO Sensitivity', + 3 => 'Exposure Compensation', + 4 => 'Aperture', + }, + }, + 175 => { Name => 'MultiSelectorShootMode', %buttonsZ9}, # CSf2-h called the OK button in camera, tag name retained for compatibility + 179 => { Name => 'MultiSelectorPlaybackMode', %buttonsZ9}, # CSf3f + 183 => { Name => 'ShutterSpeedLock', PrintConv => \%offOn }, # CSf4-a + 184 => { Name => 'ApertureLock', PrintConv => \%offOn }, # CSf4-b + 185 => { # CSf5-a Previous cameras reported this tag as part of CmdDialsReverseRotation. Blend with CSf5-b separate settings together to match extant tag name and values + Name => 'CmdDialsReverseRotExposureComp', + RawConv => '$$self{CmdDialsReverseRotExposureComp} = $val', + Hidden => 1, + }, + 186 => [{ # CSf5-a (continued from above) + Name => 'CmdDialsReverseRotation', + Condition => '$$self{CmdDialsReverseRotExposureComp} == 0', + PrintConv => { + 0 => 'No', + 1 => 'Shutter Speed & Aperture', + }, + },{ + Name => 'CmdDialsReverseRotation', + PrintConv => { + 0 => 'Exposure Compensation', + 1 => 'Exposure Compensation, Shutter Speed & Aperture', + }, + }], + 191 => { Name => 'UseDialWithoutHold', PrintConv => \%offOn, Unknown => 1 }, # CSf6 + 193 => { Name => 'ReverseIndicators', PrintConv => { 0 => '+ 0 -', 1 => '- 0 +' }, Unknown => 1 }, # CSf7 + 195 => { Name => 'MovieFunc1Button', %buttonsZ9}, # CSg2-a + 199 => { Name => 'MovieFunc2Button', %buttonsZ9}, # CSg2-b + + 203 => { Name => 'MovieAF-OnButton', %buttonsZ9}, # CSg2-f + 215 => { # CSg2-z + Name => 'MovieLensControlRing', + PrintConv => { + 0 => 'None (Disabled)', + 2 => 'ISO Sensitivity', + 3 => 'Exposure Compensation', + 4 => 'Power Aperture', + 5 => 'Hi-Res Zoom', + }, + }, + 217 => { Name => 'MovieMultiSelector', %buttonsZ9, Unknown => 1}, # CSg2-h + 221 => { Name => 'MovieAFSpeed', ValueConv => '$val - 5', ValueConvInv => '$val + 5' }, # CSg6-a + 223 => { Name => 'MovieAFSpeedApply', PrintConv => {0 => 'Always', 1 => 'Only During Recording'},}, # CSg6-b + 225 => { # CSg7 + Name => 'MovieAFTrackingSensitivity', + PrintConv => { + 0 => '1 (High)', + 1 => '2', + 2 => '3', + 3 => '4 (Normal)', + 4 => '5', + 5 => '6', + 6 => '7 (Low)', + }, + }, + 257 => { Name => 'LCDIllumination', PrintConv => \%offOn, Unknown => 1 }, # CSd11 + 258 => { Name => 'ExtendedShutterSpeeds', PrintConv => \%offOn }, # CSd5 + 259 => { Name => 'SubjectMotion', PrintConv => {0 => 'Erratic', 1 => 'Steady'} }, # CSa3-b + 261 => { Name => 'FocusPointPersistence', PrintConv => {0 => 'Auto', 1 => 'Off'} }, # CSa7 + 263 => { Name => 'AutoFocusModeRestrictions', PrintConv => \%focusModeRestrictionsZ9, Unknown => 1}, # CSa9 + 267 => { Name => 'CHModeShootingSpeed', ValueConv => '$val + 1', ValueConvInv => '$val - 1', PrintConv => '"$val fps"', PrintConvInv => '$val=~s/\s*fps//i; $val' }, # CSd1a + 273 => { Name => 'FlashBurstPriority', PrintConv => { 0 => 'Frame Rate',1 => 'Exposure'}, Unknown => 1 }, # CSe8 + 335 => { Name => 'LimitAF-AreaModeSelDynamic_S', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 336 => { Name => 'LimitAF-AreaModeSelDynamic_M', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 337 => { Name => 'LimitAF-AreaModeSelDynamic_L', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 339 => { Name => 'LimitAF-AreaModeSel3DTracking', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 341 => { Name => 'PlaybackFlickUp', PrintConv => \%flicksZ9, Unknown => 1}, # CSf12-a + 345 => { Name => 'PlaybackFlickDown', PrintConv => \%flicksZ9, Unknown => 1}, # CSf12-b + 349 => { Name => 'ISOStepSize', PrintConv => \%thirdHalfFull }, # CSb1 + 355 => { Name => 'ReverseFocusRing', PrintConv => { 0 => 'Not Reversed', 1 => 'Reversed' } }, # CSf8 + 356 => { Name => 'EVFImageFrame', PrintConv => \%offOn, Unknown => 1 }, # CSd14 + 357 => { Name => 'EVFGrid', PrintConv => \%evfGridsZ9, Unknown => 1 }, # CSd15 + 359 => { Name => 'VirtualHorizonStyle', PrintConv => {0 => 'Type A (Cockpit)', 1 => 'Type B (Sides)' }, Unknown => 1}, #CSd16 + 421 => { Name => 'Func1ButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-a + 423 => { Name => 'Func2ButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-b + 437 => { Name => 'MovieRecordButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-m + 467 => { Name => 'FocusPointLock', PrintConv => \%offOn, Unknown => 1}, # CSf4-c + 459 => { Name => 'CommandDialPlaybackMode', PrintConv => \%dialsZ9, Unknown => 1}, # CSf3-k + 463 => { Name => 'SubCommandDialPlaybackMode', PrintConv => \%dialsZ9, Unknown => 1}, # CSf3-l + 469 => { Name => 'ControlRingResponse', PrintConv => { 0 => 'High', 1 => 'Low' } }, # CSf10 + + 515 => { Name => 'MovieAFAreaMode', %buttonsZ9, Unknown => 1}, # CSg2-e + 529 => { # CSg9-a + Name => 'ZebraPatternToneRange', + Unknown => 1, + RawConv => '$$self{ZebraPatternToneRange} = $val', + PrintConv => { + 0 => 'Off', + 1 => 'Highlights', + 2 => 'Midtones', + }, + }, + 531 => { Name => 'MovieZebraPattern', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} != 0', PrintConv => {0 => 'Pattern 1', 1 => 'Pattern 2'}, Unknown => 1}, # CSg12-b + 533 => { Name => 'MovieHighlightDisplayThreshold', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} == 1', Unknown => 1 }, # CSg12-c 120-25 when highlights are the selected tone range + 535 => { Name => 'MovieMidtoneDisplayValue', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} == 2', Unknown => 1 }, # CSg12-d1 when midtones are the selected tone range + 537 => { Name => 'MovieMidtoneDisplayRange', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} == 2', PrintConv => '"+/-$val"', Unknown => 1 }, # CSg12-d1 when midtones are the selected tone range + 541 => { Name => 'MovieEVFGrid', PrintConv => \%evfGridsZ9, Unknown => 1 }, # CSg11 + 549 => { Name => 'MovieShutterSpeedLock', PrintConv => \%offOn, Unknown => 1}, # CSg4-a + 550 => { Name => 'MovieFocusPointLock', PrintConv => \%offOn, Unknown => 1}, # CSf4-c + 563 => { Name => 'MatrixMetering', PrintConv => { 0 => 'Face Detection Off', 1 => 'Face Detection On' }, Unknown => 1 }, # CSb4 + 564 => { Name => 'AF-CFocusDisplay', PrintConv => \%offOn }, # CSa11c + 565 => { Name => 'FocusPeakingDisplay', PrintConv => \%offOn, Unknown => 1}, # CSa13a + 567 => { # CSb7 + Name => 'KeepExposure', + PrintConv => { + 0 => 'Off', + 1 => 'Shutter Speed', + 2 => 'ISO', + }, + }, + 585 => { Name => 'StarlightView', PrintConv => \%offOn, Unknown => 1 }, # CSd9 + 587 => { # CSd10-a + Name => 'EVFWarmDisplayMode', + Unknown => 1, + PrintConv => { + 0 => 'Off', + 1 => 'Mode 1', + 2 => 'Mode 2', + }, + }, + 589 => { # CSd10-b values in range -3 to +3 + Name => 'EVFWarmDisplayBrightness', + Format => 'int8s', + Unknown => 1, + }, + 591 => { #CSd13 + Name => 'EVFReleaseIndicator', + Unknown => 1, + PrintConv => { + 0 => 'Off', + 1 => 'Type A (Dark)', + 2 => 'Type B (Border)', + 3 => 'Type C (Sides)', + }, + }, + 601 => { Name => 'MovieApertureLock', PrintConv => \%offOn, Unknown => 1 }, # CSg4-b + 607 => { Name => 'FlickAdvanceDirection', PrintConv => { 0 => 'Left to Right', 1 => 'Right to Left' }, Unknown => 1 }, # CSf11-c + 647 => { #CSd4-a + Name => 'PreReleaseBurstLength', + PrintConv => { + 0 => 'None', + 1 => '0.3 Sec', + 2 => '0.5 Sec', + 3 => '1 Sec', + }, + }, + 649 => { #CSd4-b + Name => 'PostReleaseBurstLength', + PrintConv => { + 0 => '1 Sec', + 1 => '2 Sec', + 2 => '3 Sec', + 3 => 'Max', + }, + }, + 681 => { Name => 'ViewModeShowEffectsOfSettings', PrintConv => { 0=>'Always', 1=> 'Only When Flash Not Used'}, Unknown => 1 }, #CS8-a + 683 => { Name => 'DispButton', %buttonsZ9}, #CSf2 +); + +# Z9 custom settings (ref 1) #base at offset26 + 1035 (firmware 1.0.0) +%Image::ExifTool::NikonCustom::SettingsZ9 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + DATAMEMBER => [ 185, 529 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the Z9.', + 1 => { + Name => 'CustomSettingsBank', + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 3 => { # CSa1 + Name => 'AF-CPrioritySelection', + PrintConv => { + 0 => 'Release', + 1 => 'Release + Focus', + 3 => 'Focus', + }, + }, + 5 => { Name => 'AF-SPrioritySelection', PrintConv => {0 => 'Release',1 => 'Focus'}}, #CSa2 + 7 => { # CSa3-a #when AFAreaMode is 3D-tracking, blocked shot response will be 3, regardless of this setting + Name => 'BlockShotAFResponse', + PrintConv => { + 1 => '1 (Quick)', + 2 => '2', + 3 => '3 (Normal)', + 4 => '4', + 5 => '5 (Delayed)', + }, + }, + 11 => {Name => 'AFPointSel',PrintConv => { 0 => 'Use All',1 => 'Use Half' }}, # CSa4 + 13 => { # CSa5 + Name => 'StoreByOrientation', + PrintConv => { + 0 => 'Off', + 1 => 'Focus Point', + 2 => 'Focus Point and AF-area mode', + }, + }, + 15 => { Name => 'AFActivation', PrintConv => {0 => 'AF-On Only', 1 => 'Shutter/AF-On'}}, # CSa6-a + 16 => { Name => 'AF-OnOutOfFocusRelease', PrintConv => {0 => 'Disable', 1 => 'Enable'}, Unknown => 1}, # CSa6-b + 17 => { Name => 'LimitAF-AreaModeSelPinpoint', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 19 => { Name => 'LimitAF-AreaModeSelWideAF_S', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 20 => { Name => 'LimitAF-AreaModeSelWideAF_L', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 21 => { Name => 'LimitAFAreaModeSelAuto', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 #left out hypen to retain compatibility with tag name in NikonSettings + 22 => { Name => 'FocusPointWrap', PrintConv => { 0 => 'No Wrap', 1 => 'Wrap' }, Unknown => 1 }, # CSa10 + 23 => { Name => 'ManualFocusPointIllumination', PrintConv => {0 => 'On During Focus Point Selection Only', 1 => 'On', }, Unknown => 1 }, # CSa11a + 24 => { Name => 'DynamicAreaAFAssist', PrintConv => { 0 => 'Focus Point Only',1 => 'Focus and Surrounding Points',}, Unknown => 1 }, # CSa11b + 25 => { Name => 'AF-AssistIlluminator', PrintConv => \%offOn }, # CSa12 + 26 => { Name => 'ManualFocusRingInAFMode', PrintConv => \%offOn }, # CSa14 + 27 => { Name => 'ExposureControlStepSize', PrintConv => \%thirdHalfFull }, # CSb2 + 29 => { # CSb3 + Name => 'EasyExposureCompensation', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (auto reset)', + }, + }, + 31 => { # CSb5 + Name => 'CenterWeightedAreaSize', + PrintConv => { + 0 => '8 mm', + 1 => '12 mm', + 4 => 'Average', + }, + }, + 33 => { # CSb6-a + Name => 'FineTuneOptMatrixMetering', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 35 => { # CSb6-b + Name => 'FineTuneOptCenterWeighted', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 37 => { # CSb6-c + Name => 'FineTuneOptSpotMetering', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 39 => { # CSb6-d + Name => 'FineTuneOptHighlightWeighted', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 41 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + PrintConv => { + 0 => 'Off', + 1 => 'On (Half Press)', + 2 => 'On (Burst Mode)', + }, + }, + 43 => { # CSc3-a + Name => 'SelfTimerTime', + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 45 => { Name => 'SelfTimerShotCount', }, # CSc3-b 1-9 + 49 => { # CSc3-c + Name => 'SelfTimerShotInterval', + PrintConv => { + 0 => '0.5 s', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 51 => { Name => 'PlaybackMonitorOffTime', %powerOffDelayTimesZ9 }, # CSc4-a + 53 => { Name => 'MenuMonitorOffTime', %powerOffDelayTimesZ9 }, # CSc4-b + 55 => { Name => 'ShootingInfoMonitorOffTime',%powerOffDelayTimesZ9 }, # CSc4-c + 57 => { Name => 'ImageReviewMonitorOffTime', %powerOffDelayTimesZ9 }, # CSc4-d + 59 => { Name => 'CLModeShootingSpeed', ValueConv => '$val + 1', ValueConvInv => '$val - 1', PrintConv => '"$val fps"', PrintConvInv => '$val=~s/\s*fps//i; $val' }, # CSd1b + 61 => { # CSd2 # values: 1-200 & 'No Limit' + Name => 'MaxContinuousRelease', + Format => 'int16s', + ValueConv => '($val eq -1 ? \'No Limit\' : $val ) ', + }, + 65 => { Name => 'SyncReleaseMode', PrintConv => { 0 => 'No Sync', 1 => 'Sync' }, Unknown => 1 }, # CSd4 + 69 => { Name => 'LimitSelectableImageAreaDX', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd6-1 + 70 => { Name => 'LimitSelectableImageArea1To1', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd6-2 + 71 => { Name => 'LimitSelectableImageArea16To9', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd6-3 + 72 => { Name => 'FileNumberSequence', PrintConv => \%offOn }, # CSd7 + 73 => { #CSa13b + Name => 'FocusPeakingLevel', + Unknown => 1, + PrintConv => { + 0 => 'High Sensitivity', + 1 => 'Standard Sensitivity', + 2 => 'Low Sensitivity', + }, + }, + 75 => { #CSa13c + Name => 'FocusPeakingHighlightColor', + Unknown => 1, + PrintConv => { + 0 => 'Red', + 1 => 'Yellow', + 2 => 'Blue', + 3 => 'White', + }, + }, + 81 => { Name => 'ContinuousModeDisplay', PrintConv => \%offOn }, # CSd12 + 83 => { # CSe1-a Previous cameras reported this with HighSpeedSync indicator appended as '(Auto FP)'. Z9 separated the 2 fields. + Name => 'FlashSyncSpeed', + ValueConv => '($val-144)/8', + PrintConv => { + 0 => '1/60 s', + 1 => '1/80 s', + 2 => '1/100 s', + 3 => '1/125 s', + 4 => '1/160 s', + 5 => '1/200 s', + 6 => '1/250 s', + }, + }, + 85 => { Name => 'HighSpeedSync', PrintConv => \%offOn }, # CSe1-b + 87 => { # CSe2 + Name => 'FlashShutterSpeed', + ValueConv => 'my $t = ($val - 16) % 24; $t ? $val / 24 : 2 + ($val - 16) / 24', #unusual decode perhaps due to need to accomodate 4 new values? + PrintConv => { + 0 => '1 s', + 1 => '1/2 s', + 2 => '1/4 s', + 3 => '1/8 s', + 4 => '1/15 s', + 5 => '1/30 s', + 6 => '1/60 s', + 7 => '30 s', + 8 => '15 s', + 9 => '8 s', + 10 => '4 s', + 11 => '2 s', + }, + }, + 89 => { Name => 'FlashExposureCompArea', PrintConv => { 0 => 'Entire Frame', 1 => 'Background Only' } }, # CSe3 + 91 => { Name => 'AutoFlashISOSensitivity', PrintConv => { 0 => 'Subject and Background',1 => 'Subject Only'} }, # CSe4 + 93 => { Name => 'ModelingFlash', PrintConv => \%offOn }, # CSe5 + 95 => { # CSe6 + Name => 'AutoBracketModeM', + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + 4 => 'Flash/ISO', + }, + }, + 97 => { Name => 'AutoBracketOrder', PrintConv => { 0 => '0,-,+',1 => '-,0,+' } }, # CSe7 + 99 => { Name => 'Func1Button', %buttonsZ9}, # CSf2-a + #101 Func1Button submenu: Preview 0 => 'Press To Recall', 1=> 'Hold To Recall' # CSf2-a + #103 Func1Button submenu: AreaMode 0-7 => S, Dyn-S, Dyn-M, Dyn-L, Wide-S, Wide-L, 3D, Auto; 11=>n/a # CSf2-a + #105 Func1Button submenu: AreaMode+AF-On 0-7 => S, Dyn-S, Dyn-M, Dyn-L, Wide-S, Wide-L, 3D, Auto; 11=>n/a # CSf2-a + #109 Func1Button submenu: SynchronizedRelease 1=>'Master', 2=>'Remote' # CSf2-a + #111 Func1Button submenu: Zoom 0=>'Zoom (Low)', 2=>'Zoom (1:1)', 2=>'Zoom (High)' # CSf2-a + #113 Func1Button & Func1ButtonPlayback submenu: Rating # CSf2-a & CSf3a 0=>'Candidate For Deletion' 6=>''None' + 115 => { Name => 'Func2Button', %buttonsZ9}, # CSf2-b + #117 Func2Button submenu: Preview 0 => 'Press To Recall', 1=> 'Hold To Recall' # CSf2-b + #119 Func2Button submenu: AreaMode 0-7 => S, Dyn-S, Dyn-M, Dyn-L, Wide-S, Wide-L, 3D, Auto; 11=>n/a # CSf2-b + #121 Func2Button submenu: AreaMode+AF-On 0-7 => S, Dyn-S, Dyn-M, Dyn-L, Wide-S, Wide-L, 3D, Auto; 11=>n/a # CSf2-b + #125 Func2Button submenu: SynchronizedRelease 1=>'Master', 2=>'Remote' # CSf2-b + #127 Func2Button submenu: Zoom 0=>'Zoom (Low)', 2=>'Zoom (1:1)', 2=>'Zoom (High)' # CSf2-b + #129 Func2Button & Func2ButtonPlayback submenu: Rating # CSf2-b & CSf3b 0=>'Candidate For Deletion' 6=>''None' + 131 => { Name => 'AFOnButton', %buttonsZ9}, # CSf2-c + 143 => { Name => 'SubSelector', %buttonsZ9, Unknown => 1}, # CSf2-g + 155 => { Name => 'AssignMovieRecordButton', %buttonsZ9, Unknown => 1}, # CSf2-m + 159 => { Name => 'LensFunc1Button', %buttonsZ9}, # CSf2-o + 167 => { Name => 'LensFunc2Button', %buttonsZ9}, # CSf2-p + 173 => { # CSf2-q + Name => 'LensControlRing', + PrintConv => { + 0 => 'None (Disabled)', + 1 => 'Focus (M/A)', + 2 => 'ISO Sensitivity', + 3 => 'Exposure Compensation', + 4 => 'Aperture', + }, + }, + 175 => { Name => 'MultiSelectorShootMode', %buttonsZ9}, # CSf2-h called the OK button in camera, tag name retained for compatibility + 179 => { Name => 'MultiSelectorPlaybackMode', %buttonsZ9}, # CSf3f + 183 => { Name => 'ShutterSpeedLock', PrintConv => \%offOn }, # CSf4-a + 184 => { Name => 'ApertureLock', PrintConv => \%offOn }, # CSf4-b + 185 => { # CSf5-a Previous cameras reported this tag as part of CmdDialsReverseRotation. Blend with CSf5-b separate settings together to match extant tag name and values + Name => 'CmdDialsReverseRotExposureComp', + RawConv => '$$self{CmdDialsReverseRotExposureComp} = $val', + Hidden => 1, + }, + 186 => [{ # CSf5-a (continued from above) + Name => 'CmdDialsReverseRotation', + Condition => '$$self{CmdDialsReverseRotExposureComp} == 0', + PrintConv => { + 0 => 'No', + 1 => 'Shutter Speed & Aperture', + }, + },{ + Name => 'CmdDialsReverseRotation', + PrintConv => { + 0 => 'Exposure Compensation', + 1 => 'Exposure Compensation, Shutter Speed & Aperture', + }, + }], + 191 => { Name => 'UseDialWithoutHold', PrintConv => \%offOn, Unknown => 1 }, # CSf6 + 193 => { Name => 'ReverseIndicators', PrintConv => { 0 => '+ 0 -', 1 => '- 0 +' }, Unknown => 1 }, # CSf7 + 195 => { Name => 'MovieFunc1Button', %buttonsZ9}, # CSg2-a + 199 => { Name => 'MovieFunc2Button', %buttonsZ9}, # CSg2-b + 203 => { Name => 'MovieAF-OnButton', %buttonsZ9}, # CSg2-f + 207 => { Name => 'MovieMultiSelector', %buttonsZ9, Unknown => 1}, # CSg2-h + 215 => { # CSg2-z + Name => 'MovieLensControlRing', + PrintConv => { + 0 => 'None (Disabled)', + 2 => 'ISO Sensitivity', + 3 => 'Exposure Compensation', + 4 => 'Power Aperture', + 5 => 'Hi-Res Zoom', + }, + }, + 221 => { Name => 'MovieAFSpeed', ValueConv => '$val - 5', ValueConvInv => '$val + 5' }, # CSg6-a + 223 => { Name => 'MovieAFSpeedApply', PrintConv => {0 => 'Always', 1 => 'Only During Recording'},}, # CSg6-b + 225 => { # CSg7 + Name => 'MovieAFTrackingSensitivity', + PrintConv => { + 0 => '1 (High)', + 1 => '2', + 2 => '3', + 3 => '4 (Normal)', + 4 => '5', + 5 => '6', + 6 => '7 (Low)', + }, + }, + 257 => { Name => 'LCDIllumination', PrintConv => \%offOn, Unknown => 1 }, # CSd11 + 258 => { Name => 'ExtendedShutterSpeeds', PrintConv => \%offOn }, # CSd5 + 259 => { Name => 'SubjectMotion', PrintConv => {0 => 'Erratic', 1 => 'Steady'} }, # CSa3-b + 261 => { Name => 'FocusPointPersistence', PrintConv => {0 => 'Auto', 1 => 'Off'} }, # CSa7 + 263 => { Name => 'AutoFocusModeRestrictions', PrintConv => \%focusModeRestrictionsZ9, Unknown => 1}, # CSa9 + 267 => { Name => 'CHModeShootingSpeed', ValueConv => '$val + 1', ValueConvInv => '$val - 1', PrintConv => '"$val fps"', PrintConvInv => '$val=~s/\s*fps//i; $val' }, # CSd1a + 269.1 => { Name => 'LimitReleaseModeSelCL', Mask => 0x02, PrintConv => \%limitNolimit, Unknown => 1 }, # CSd3-a + 269.2 => { Name => 'LimitReleaseModeSelCH', Mask => 0x04, PrintConv => \%limitNolimit, Unknown => 1 }, # CSd3-b + 269.3 => { Name => 'LimitReleaseModeSelC30', Mask => 0x10, PrintConv => \%limitNolimit, Unknown => 1 }, # CSd3-e + 269.4 => { Name => 'LimitReleaseModeSelC120', Mask => 0x40, PrintConv => \%limitNolimit, Unknown => 1 }, # CSd3-c + 269.5 => { Name => 'LimitReleaseModeSelSelf', Mask => 0x80, PrintConv => \%limitNolimit, Unknown => 1 }, # CSd3-d + 273 => { Name => 'FlashBurstPriority', PrintConv => { 0 => 'Frame Rate',1 => 'Exposure'}, Unknown => 1 }, # CSe8 + 277 => { Name => 'VerticalFuncButton', %buttonsZ9}, # CSf2-c + 281 => { Name => 'Func3Button', %buttonsZ9}, # CSf2-c + 285 => { Name => 'VerticalAFOnButton', %buttonsZ9}, # CSf2-l + 293 => { Name => 'VerticalMultiSelectorPlaybackMode', PrintConv => { 0 => 'Image Scroll L/R', 1 => 'Image Scroll Up/Down' }, Unknown => 1}, # CSf3-j + 295 => { Name => 'MovieFunc3Button', %buttonsZ9}, # CSg2-c + 335 => { Name => 'LimitAF-AreaModeSelDynamic_S', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 336 => { Name => 'LimitAF-AreaModeSelDynamic_M', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 337 => { Name => 'LimitAF-AreaModeSelDynamic_L', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 339 => { Name => 'LimitAF-AreaModeSel3DTracking', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 341 => { Name => 'PlaybackFlickUp', PrintConv => \%flicksZ9, Unknown => 1}, # CSf11-a + 345 => { Name => 'PlaybackFlickDown', PrintConv => \%flicksZ9, Unknown => 1}, # CSf11-b + 349 => { Name => 'ISOStepSize', PrintConv => \%thirdHalfFull }, # CSb1 + 355 => { Name => 'ReverseFocusRing', PrintConv => { 0 => 'Not Reversed', 1 => 'Reversed' } }, # CSf8 + 356 => { Name => 'EVFImageFrame', PrintConv => \%offOn, Unknown => 1 }, # CSd14 + 357 => { Name => 'EVFGrid', PrintConv => \%evfGridsZ9, Unknown => 1 }, # CSd15 + 359 => { Name => 'VirtualHorizonStyle', PrintConv => {0 => 'Type A (Cockpit)', 1 => 'Type B (Sides)' }, Unknown => 1}, #CSd16 + 373 => { Name => 'Func4Button', %buttonsZ9, Unknown => 1}, # CSf2-e + 379 => { Name => 'AudioButton', %buttonsZ9, Unknown => 1}, # CSf2-i + 381 => { Name => 'QualityButton', %buttonsZ9, Unknown => 1}, # CSf2-j + 399 => { Name => 'VerticalMultiSelector', %buttonsZ9, Unknown => 1}, # CSf2-k + 421 => { Name => 'Func1ButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-a + 423 => { Name => 'Func2ButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-b + 425 => { Name => 'Func3ButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-c + 431 => { Name => 'Func4ButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-e + 437 => { Name => 'MovieRecordButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-m + 439 => { Name => 'VerticalFuncButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-d + 441 => { Name => 'AudioButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-g + 447 => { Name => 'QualityButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-h + 467 => { Name => 'FocusPointLock', PrintConv => \%offOn, Unknown => 1}, # CSf4-c + 453 => { Name => 'WhiteBalanceButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-i + 459 => { Name => 'CommandDialPlaybackMode', PrintConv => \%dialsZ9, Unknown => 1}, # CSf3-k + 463 => { Name => 'SubCommandDialPlaybackMode', PrintConv => \%dialsZ9, Unknown => 1}, # CSf3-l + 469 => { Name => 'ControlRingResponse', PrintConv => { 0 => 'High', 1 => 'Low' } }, # CSf10 + 481 => { Name => 'VerticalMovieFuncButton', %buttonsZ9, Unknown => 1}, # CSg2-d + 505 => { Name => 'VerticalMovieAFOnButton', %buttonsZ9, Unknown => 1}, # CSg2-l + 515 => { Name => 'MovieAFAreaMode', %buttonsZ9, Unknown => 1}, # CSg2-e + #521 => { Name => 'MovieLimitAF-AreaModeSelWideAF_S', PrintConv => \%limitNolimit, Unknown => 1 }, # CSg4-a + #522 => { Name => 'MovieLimitAF-AreaModeSelWideAF_W', PrintConv => \%limitNolimit, Unknown => 1 }, # CSg4-b + #523 => { Name => 'MovieLimitAF-AreaModeSelSubjectTrack', PrintConv => \%limitNolimit, Unknown => 1 }, # CSg4-c + #524 => { Name => 'MovieLimitAFAreaModeSelAuto', PrintConv => \%limitNolimit, Unknown => 1 }, # CSg4-d + #525 => { Name => 'MovieAutoFocusModeRestrictions', PrintConv => \%focusModeRestrictionsZ9, Unknown => 1}, # CSa9 + 527 => { Name => 'HDMIViewAssist', PrintConv => \%offOn, Unknown => 1 }, # CSg8 + 529 => { # CSg9-a + Name => 'ZebraPatternToneRange', + Unknown => 1, + RawConv => '$$self{ZebraPatternToneRange} = $val', + PrintConv => { + 0 => 'Off', + 1 => 'Highlights', + 2 => 'Midtones', + }, + }, + 531 => { Name => 'MovieZebraPattern', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} != 0', PrintConv => {0 => 'Pattern 1', 1 => 'Pattern 2'}, Unknown => 1}, # CSg9-b + 533 => { Name => 'MovieHighlightDisplayThreshold', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} == 1', Unknown => 1 }, # CSg9-c 120-25 when highlights are the selected tone range + 535 => { Name => 'MovieMidtoneDisplayValue', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} == 2', Unknown => 1 }, # CSg9-d1 when midtones are the selected tone range + 537 => { Name => 'MovieMidtoneDisplayRange', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} == 2', PrintConv => '"+/-$val"', Unknown => 1 }, # CSg9-d1 when midtones are the selected tone range + #539 CS g-10 LimitZebraPatternToneRange 0=>'No Restrictions', 1=> 'Highlights', 2=> 'Midtones' + 541 => { Name => 'MovieEVFGrid', PrintConv => \%evfGridsZ9, Unknown => 1 }, # CSg11 + 549 => { Name => 'MovieShutterSpeedLock', PrintConv => \%offOn, Unknown => 1}, # CSg4-a + 550 => { Name => 'MovieFocusPointLock', PrintConv => \%offOn, Unknown => 1}, # CSf4-c + 563 => { Name => 'MatrixMetering', PrintConv => { 0 => 'Face Detection Off', 1 => 'Face Detection On' }, Unknown => 1 }, # CSb4 + 564 => { Name => 'AF-CFocusDisplay', PrintConv => \%offOn }, # CSa11c + 565 => { Name => 'FocusPeakingDisplay', PrintConv => \%offOn, Unknown => 1}, # CSa13a + 567 => { # CSb7 + Name => 'KeepExposure', + PrintConv => { + 0 => 'Off', + 1 => 'Shutter Speed', + 2 => 'ISO', + }, + }, + #569 CDd8 (adjust viewfinder/monitor hue, brightness and/or white balance settings) 0=>'No adjustment', 1=>'Adjust'. Related ontrols & adjustments stored in following dozen bytes or so. + 585 => { Name => 'StarlightView', PrintConv => \%offOn, Unknown => 1 }, # CSd9 + 587 => { # CSd10-a + Name => 'EVFWarmDisplayMode', + Unknown => 1, + PrintConv => { + 0 => 'Off', + 1 => 'Mode 1', + 2 => 'Mode 2', + }, + }, + 589 => { # CSd10-b values in range -3 to +3 + Name => 'EVFWarmDisplayBrightness', + Format => 'int8s', + Unknown => 1, + }, + 591 => { #CSd13 + Name => 'EVFReleaseIndicator', + Unknown => 1, + PrintConv => { + 0 => 'Off', + 1 => 'Type A (Dark)', + 2 => 'Type B (Border)', + 3 => 'Type C (Sides)', + }, + }, + 601 => { Name => 'MovieApertureLock', PrintConv => \%offOn, Unknown => 1 }, # CSg4-b + 607 => { Name => 'FlickAdvanceDirection', PrintConv => { 0 => 'Left to Right', 1 => 'Right to Left' }, Unknown => 1 }, # CSf11-c +); + +# Z9 custom settings (ref 1) #base at offset26 + 1095 (firmware 4.0) +%Image::ExifTool::NikonCustom::SettingsZ9v4 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + DATAMEMBER => [ 185, 553 ], + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Custom settings for the Z9.', + 1 => { + Name => 'CustomSettingsBank', + PrintConv => { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + }, + }, + 3 => { # CSa1 + Name => 'AF-CPrioritySelection', + PrintConv => { + 0 => 'Release', + 1 => 'Release + Focus', + 3 => 'Focus', + }, + }, + 5 => { Name => 'AF-SPrioritySelection', PrintConv => {0 => 'Release',1 => 'Focus'}}, #CSa2 + 7 => { # CSa3-a #when AFAreaMode is 3D-tracking, blocked shot response will be 3, regardless of this setting + Name => 'BlockShotAFResponse', + PrintConv => { + 1 => '1 (Quick)', + 2 => '2', + 3 => '3 (Normal)', + 4 => '4', + 5 => '5 (Delayed)', + }, + }, + 11 => {Name => 'AFPointSel',PrintConv => { 0 => 'Use All',1 => 'Use Half' }}, # CSa4 + 13 => { # CSa5 + Name => 'StoreByOrientation', + PrintConv => { + 0 => 'Off', + 1 => 'Focus Point', + 2 => 'Focus Point and AF-area mode', + }, + }, + 15 => { Name => 'AFActivation', PrintConv => {0 => 'AF-On Only', 1 => 'Shutter/AF-On'}}, # CSa6-a + 16 => { Name => 'AF-OnOutOfFocusRelease', PrintConv => {0 => 'Disable', 1 => 'Enable'}, Unknown => 1}, # CSa6-b + 17 => { Name => 'LimitAF-AreaModeSelPinpoint', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 19 => { Name => 'LimitAF-AreaModeSelWideAF_S', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 20 => { Name => 'LimitAF-AreaModeSelWideAF_L', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 21 => { Name => 'LimitAFAreaModeSelAuto', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 #left out hypen to retain compatibility with tag name in NikonSettings + 22 => { Name => 'FocusPointWrap', PrintConv => { 0 => 'No Wrap', 1 => 'Wrap' }, Unknown => 1 }, # CSa10 + 23 => { Name => 'ManualFocusPointIllumination', PrintConv => {0 => 'On During Focus Point Selection Only', 1 => 'On', }, Unknown => 1 }, # CSa11a + 24 => { Name => 'DynamicAreaAFAssist', PrintConv => { 0 => 'Focus Point Only',1 => 'Focus and Surrounding Points',}, Unknown => 1 }, # CSa11b + 25 => { Name => 'AF-AssistIlluminator', PrintConv => \%offOn }, # CSa12 + 26 => { Name => 'ManualFocusRingInAFMode', PrintConv => \%offOn }, # CSa14 + 27 => { Name => 'ExposureControlStepSize', PrintConv => \%thirdHalfFull }, # CSb2 + 29 => { # CSb3 + Name => 'EasyExposureCompensation', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (auto reset)', + }, + }, + 31 => { # CSb5 + Name => 'CenterWeightedAreaSize', + PrintConv => { + 0 => '8 mm', + 1 => '12 mm', + 4 => 'Average', + }, + }, + 33 => { # CSb6-a + Name => 'FineTuneOptMatrixMetering', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 35 => { # CSb6-b + Name => 'FineTuneOptCenterWeighted', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 37 => { # CSb6-c + Name => 'FineTuneOptSpotMetering', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 39 => { # CSb6-d + Name => 'FineTuneOptHighlightWeighted', + Format => 'int8s', + ValueConv => '$val / 6', + ValueConvInv => 'int($val*6)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 41 => { # CSc1 + Name => 'ShutterReleaseButtonAE-L', + PrintConv => { + 0 => 'Off', + 1 => 'On (Half Press)', + 2 => 'On (Burst Mode)', + }, + }, + 43 => { # CSc3-a + Name => 'SelfTimerTime', + PrintConv => { + 0 => '2 s', + 1 => '5 s', + 2 => '10 s', + 3 => '20 s', + }, + }, + 45 => { Name => 'SelfTimerShotCount', }, # CSc3-b 1-9 + 49 => { # CSc3-c + Name => 'SelfTimerShotInterval', + PrintConv => { + 0 => '0.5 s', + 1 => '1 s', + 2 => '2 s', + 3 => '3 s', + }, + }, + 51 => { Name => 'PlaybackMonitorOffTime', %powerOffDelayTimesZ9 }, # CSc4-a + 53 => { Name => 'MenuMonitorOffTime', %powerOffDelayTimesZ9 }, # CSc4-b + 55 => { Name => 'ShootingInfoMonitorOffTime',%powerOffDelayTimesZ9 }, # CSc4-c + 57 => { Name => 'ImageReviewMonitorOffTime', %powerOffDelayTimesZ9 }, # CSc4-d + 59 => { Name => 'CLModeShootingSpeed', ValueConv => '$val + 1', ValueConvInv => '$val - 1', PrintConv => '"$val fps"', PrintConvInv => '$val=~s/\s*fps//i; $val' }, # CSd1b + 61 => { # CSd2 # values: 1-200 & 'No Limit' + Name => 'MaxContinuousRelease', + Format => 'int16s', + ValueConv => '($val eq -1 ? \'No Limit\' : $val ) ', + }, + 65 => { Name => 'SyncReleaseMode', PrintConv => { 0 => 'No Sync', 1 => 'Sync' }, Unknown => 1 }, # CSd4 + 69 => { Name => 'LimitSelectableImageAreaDX', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd6-1 + 70 => { Name => 'LimitSelectableImageArea1To1', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd6-2 + 71 => { Name => 'LimitSelectableImageArea16To9', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd6-3 + 72 => { Name => 'FileNumberSequence', PrintConv => \%offOn }, # CSd7 + 73 => { #CSa13b + Name => 'FocusPeakingLevel', + Unknown => 1, + PrintConv => { + 0 => 'High Sensitivity', + 1 => 'Standard Sensitivity', + 2 => 'Low Sensitivity', + }, + }, + 75 => { #CSa13c + Name => 'FocusPeakingHighlightColor', + Unknown => 1, + PrintConv => { + 0 => 'Red', + 1 => 'Yellow', + 2 => 'Blue', + 3 => 'White', + }, + }, + 81 => { Name => 'ContinuousModeDisplay', PrintConv => \%offOn }, # CSd12 + 83 => { # CSe1-a Previous cameras reported this with HighSpeedSync indicator appended as '(Auto FP)'. Z9 separated the 2 fields. + Name => 'FlashSyncSpeed', + ValueConv => '($val-144)/8', + PrintConv => { + 0 => '1/60 s', + 1 => '1/80 s', + 2 => '1/100 s', + 3 => '1/125 s', + 4 => '1/160 s', + 5 => '1/200 s', + 6 => '1/250 s', + }, + }, + 85 => { Name => 'HighSpeedSync', PrintConv => \%offOn }, # CSe1-b + 87 => { # CSe2 + Name => 'FlashShutterSpeed', + ValueConv => 'my $t = ($val - 16) % 24; $t ? $val / 24 : 2 + ($val - 16) / 24', #unusual decode perhaps due to need to accomodate 4 new values? + PrintConv => { + 0 => '1 s', + 1 => '1/2 s', + 2 => '1/4 s', + 3 => '1/8 s', + 4 => '1/15 s', + 5 => '1/30 s', + 6 => '1/60 s', + 7 => '30 s', + 8 => '15 s', + 9 => '8 s', + 10 => '4 s', + 11 => '2 s', + }, + }, + 89 => { Name => 'FlashExposureCompArea', PrintConv => { 0 => 'Entire Frame', 1 => 'Background Only' } }, # CSe3 + 91 => { Name => 'AutoFlashISOSensitivity', PrintConv => { 0 => 'Subject and Background',1 => 'Subject Only'} }, # CSe4 + 93 => { Name => 'ModelingFlash', PrintConv => \%offOn }, # CSe5 + 95 => { # CSe6 + Name => 'AutoBracketModeM', + PrintConv => { + 0 => 'Flash/Speed', + 1 => 'Flash/Speed/Aperture', + 2 => 'Flash/Aperture', + 3 => 'Flash Only', + 4 => 'Flash/ISO', + }, + }, + 97 => { Name => 'AutoBracketOrder', PrintConv => { 0 => '0,-,+',1 => '-,0,+' } }, # CSe7 + 99 => { Name => 'Func1Button', %buttonsZ9}, # CSf2-a + #101 Func1Button submenu: Preview 0 => 'Press To Recall', 1=> 'Hold To Recall' # CSf2-a + #103 Func1Button submenu: AreaMode 0-7 => S, Dyn-S, Dyn-M, Dyn-L, Wide-S, Wide-L, 3D, Auto; 11=>n/a # CSf2-a + #105 Func1Button submenu: AreaMode+AF-On 0-7 => S, Dyn-S, Dyn-M, Dyn-L, Wide-S, Wide-L, 3D, Auto; 11=>n/a # CSf2-a + #109 Func1Button submenu: SynchronizedRelease 1=>'Master', 2=>'Remote' # CSf2-a + #111 Func1Button submenu: Zoom 0=>'Zoom (Low)', 2=>'Zoom (1:1)', 2=>'Zoom (High)' # CSf2-a + #113 Func1Button & Func1ButtonPlayback submenu: Rating # CSf2-a & CSf3a 0=>'Candidate For Deletion' 6=>''None' + 115 => { Name => 'Func2Button', %buttonsZ9}, # CSf2-b + #117 Func2Button submenu: Preview 0 => 'Press To Recall', 1=> 'Hold To Recall' # CSf2-b + #119 Func2Button submenu: AreaMode 0-7 => S, Dyn-S, Dyn-M, Dyn-L, Wide-S, Wide-L, 3D, Auto; 11=>n/a # CSf2-b + #121 Func2Button submenu: AreaMode+AF-On 0-7 => S, Dyn-S, Dyn-M, Dyn-L, Wide-S, Wide-L, 3D, Auto; 11=>n/a # CSf2-b + #125 Func2Button submenu: SynchronizedRelease 1=>'Master', 2=>'Remote' # CSf2-b + #127 Func2Button submenu: Zoom 0=>'Zoom (Low)', 2=>'Zoom (1:1)', 2=>'Zoom (High)' # CSf2-b + #129 Func2Button & Func2ButtonPlayback submenu: Rating # CSf2-b & CSf3b 0=>'Candidate For Deletion' 6=>''None' + 131 => { Name => 'AFOnButton', %buttonsZ9}, # CSf2-c + 143 => { Name => 'SubSelector', %buttonsZ9, Unknown => 1}, # CSf2-g + 155 => { Name => 'AssignMovieRecordButton', %buttonsZ9, Unknown => 1}, # CSf2-m + 159 => { Name => 'LensFunc1Button', %buttonsZ9}, # CSf2-o + 167 => { Name => 'LensFunc2Button', %buttonsZ9}, # CSf2-p + 173 => { # CSf2-q + Name => 'LensControlRing', + PrintConv => { + 0 => 'None (Disabled)', + 1 => 'Focus (M/A)', + 2 => 'ISO Sensitivity', + 3 => 'Exposure Compensation', + 4 => 'Aperture', + }, + }, + 175 => { Name => 'MultiSelectorShootMode', %buttonsZ9}, # CSf2-h called the OK button in camera, tag name retained for compatibility + 179 => { Name => 'MultiSelectorPlaybackMode', %buttonsZ9}, # CSf3f + 183 => { Name => 'ShutterSpeedLock', PrintConv => \%offOn }, # CSf4-a + 184 => { Name => 'ApertureLock', PrintConv => \%offOn }, # CSf4-b + 185 => { # CSf5-a Previous cameras reported this tag as part of CmdDialsReverseRotation. Blend with CSf5-b separate settings together to match extant tag name and values + Name => 'CmdDialsReverseRotExposureComp', + RawConv => '$$self{CmdDialsReverseRotExposureComp} = $val', + Hidden => 1, + }, + 186 => [{ # CSf5-a (continued from above) + Name => 'CmdDialsReverseRotation', + Condition => '$$self{CmdDialsReverseRotExposureComp} == 0', + PrintConv => { + 0 => 'No', + 1 => 'Shutter Speed & Aperture', + }, + },{ + Name => 'CmdDialsReverseRotation', + PrintConv => { + 0 => 'Exposure Compensation', + 1 => 'Exposure Compensation, Shutter Speed & Aperture', + }, + }], + 191 => { Name => 'UseDialWithoutHold', PrintConv => \%offOn, Unknown => 1 }, # CSf6 + 193 => { Name => 'ReverseIndicators', PrintConv => { 0 => '+ 0 -', 1 => '- 0 +' }, Unknown => 1 }, # CSf7 + 195 => { Name => 'MovieFunc1Button', %buttonsZ9}, # CSg2-a + 199 => { Name => 'MovieFunc2Button', %buttonsZ9}, # CSg2-b + 203 => { Name => 'MovieAF-OnButton', %buttonsZ9}, # CSg2-f + 207 => { Name => 'MovieMultiSelector', %buttonsZ9, Unknown => 1}, # CSg2-h + 215 => { # CSg2-z + Name => 'MovieLensControlRing', + PrintConv => { + 0 => 'None (Disabled)', + 2 => 'ISO Sensitivity', + 3 => 'Exposure Compensation', + 4 => 'Power Aperture', + 5 => 'Hi-Res Zoom', + }, + }, + 221 => { Name => 'MovieAFSpeed', ValueConv => '$val - 5', ValueConvInv => '$val + 5' }, # CSg6-a + 223 => { Name => 'MovieAFSpeedApply', PrintConv => {0 => 'Always', 1 => 'Only During Recording'},}, # CSg6-b + 225 => { # CSg7 + Name => 'MovieAFTrackingSensitivity', + PrintConv => { + 0 => '1 (High)', + 1 => '2', + 2 => '3', + 3 => '4 (Normal)', + 4 => '5', + 5 => '6', + 6 => '7 (Low)', + }, + }, + 279 => { Name => 'LCDIllumination', PrintConv => \%offOn, Unknown => 1 }, # CSd11 + 280 => { Name => 'ExtendedShutterSpeeds', PrintConv => \%offOn }, # CSd5 + 281 => { Name => 'SubjectMotion', PrintConv => {0 => 'Erratic', 1 => 'Steady'} }, # CSa3-b + 283 => { Name => 'FocusPointPersistence', PrintConv => {0 => 'Auto', 1 => 'Off'} }, # CSa7 + 285 => { Name => 'AutoFocusModeRestrictions', PrintConv => \%focusModeRestrictionsZ9, Unknown => 1}, # CSa9 + 289 => { Name => 'CHModeShootingSpeed', ValueConv => '$val + 1', ValueConvInv => '$val - 1', PrintConv => '"$val fps"', PrintConvInv => '$val=~s/\s*fps//i; $val' }, # CSd1a + 293.1 => { Name => 'LimitReleaseModeSelCL', Mask => 0x02, PrintConv => \%limitNolimit, Unknown => 1 }, # CSd3-a + 293.2 => { Name => 'LimitReleaseModeSelCH', Mask => 0x04, PrintConv => \%limitNolimit, Unknown => 1 }, # CSd3-b + 293.3 => { Name => 'LimitReleaseModeSelC30', Mask => 0x10, PrintConv => \%limitNolimit, Unknown => 1 }, # CSd3-e + 293.4 => { Name => 'LimitReleaseModeSelC120', Mask => 0x40, PrintConv => \%limitNolimit, Unknown => 1 }, # CSd3-c + 293.5 => { Name => 'LimitReleaseModeSelSelf', Mask => 0x80, PrintConv => \%limitNolimit, Unknown => 1 }, # CSd3-d + 297 => { Name => 'FlashBurstPriority', PrintConv => { 0 => 'Frame Rate',1 => 'Exposure'}, Unknown => 1 }, # CSe8 + 301 => { Name => 'VerticalFuncButton', %buttonsZ9}, # CSf2-c + 305 => { Name => 'Func3Button', %buttonsZ9}, # CSf2-c + 309 => { Name => 'VerticalAFOnButton', %buttonsZ9}, # CSf2-l + 317 => { Name => 'VerticalMultiSelectorPlaybackMode', PrintConv => { 0 => 'Image Scroll L/R', 1 => 'Image Scroll Up/Down' }, Unknown => 1}, # CSf3-j + 319 => { Name => 'MovieFunc3Button', %buttonsZ9}, # CSg2-c + 359 => { Name => 'LimitAF-AreaModeSelDynamic_S', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 360 => { Name => 'LimitAF-AreaModeSelDynamic_M', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 361 => { Name => 'LimitAF-AreaModeSelDynamic_L', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 363 => { Name => 'LimitAF-AreaModeSel3DTracking', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa8 + 365 => { Name => 'PlaybackFlickUp', PrintConv => \%flicksZ9, Unknown => 1}, # CSf11-a + 369 => { Name => 'PlaybackFlickDown', PrintConv => \%flicksZ9, Unknown => 1}, # CSf11-b + 373 => { Name => 'ISOStepSize', PrintConv => \%thirdHalfFull }, # CSb1 + 379 => { Name => 'ReverseFocusRing', PrintConv => { 0 => 'Not Reversed', 1 => 'Reversed' } }, # CSf8 + 380 => { Name => 'EVFImageFrame', PrintConv => \%offOn, Unknown => 1 }, # CSd14 + 381 => { Name => 'EVFGrid', PrintConv => \%evfGridsZ9, Unknown => 1 }, # CSd15 + 383 => { Name => 'VirtualHorizonStyle', PrintConv => {0 => 'Type A (Cockpit)', 1 => 'Type B (Sides)' }, Unknown => 1}, #CSd16 + 397 => { Name => 'Func4Button', %buttonsZ9, Unknown => 1}, # CSf2-e + 403 => { Name => 'AudioButton', %buttonsZ9, Unknown => 1}, # CSf2-i + 405 => { Name => 'QualityButton', %buttonsZ9, Unknown => 1}, # CSf2-j + 423 => { Name => 'VerticalMultiSelector', %buttonsZ9, Unknown => 1}, # CSf2-k + 445 => { Name => 'Func1ButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-a + 447 => { Name => 'Func2ButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-b + 449 => { Name => 'Func3ButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-c + 455 => { Name => 'Func4ButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-e + 461 => { Name => 'MovieRecordButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-m + 463 => { Name => 'VerticalFuncButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-d + 465 => { Name => 'AudioButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-g + 471 => { Name => 'QualityButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-h + 477 => { Name => 'WhiteBalanceButtonPlaybackMode', %buttonsZ9, Unknown => 1}, # CSf3-i + 483 => { Name => 'CommandDialPlaybackMode', PrintConv => \%dialsZ9, Unknown => 1}, # CSf3-k + 487 => { Name => 'SubCommandDialPlaybackMode', PrintConv => \%dialsZ9, Unknown => 1}, # CSf3-l + 491 => { Name => 'FocusPointLock', PrintConv => \%offOn, Unknown => 1}, # CSf4-c + 493 => { Name => 'ControlRingResponse', PrintConv => { 0 => 'High', 1 => 'Low' } }, # CSf10 + 505 => { Name => 'VerticalMovieFuncButton', %buttonsZ9, Unknown => 1}, # CSg2-d + 529 => { Name => 'VerticalMovieAFOnButton', %buttonsZ9, Unknown => 1}, # CSg2-l + 539 => { Name => 'MovieAFAreaMode', %buttonsZ9, Unknown => 1}, # CSg2-e + #545 => { Name => 'MovieLimitAF-AreaModeSelWideAF_S', PrintConv => \%limitNolimit, Unknown => 1 }, # CSg4-a + #546 => { Name => 'MovieLimitAF-AreaModeSelWideAF_W', PrintConv => \%limitNolimit, Unknown => 1 }, # CSg4-b + #547 => { Name => 'MovieLimitAF-AreaModeSelSubjectTrack', PrintConv => \%limitNolimit, Unknown => 1 }, # CSg4-c + #548 => { Name => 'MovieLimitAFAreaModeSelAuto', PrintConv => \%limitNolimit, Unknown => 1 }, # CSg4-d + #549 => { Name => 'MovieAutoFocusModeRestrictions', PrintConv => \%focusModeRestrictionsZ9, Unknown => 1}, # CSa9 + 551 => { Name => 'HDMIViewAssist', PrintConv => \%offOn, Unknown => 1 }, # CSg12 + 553 => { # CSg13-a + Name => 'ZebraPatternToneRange', + Unknown => 1, + RawConv => '$$self{ZebraPatternToneRange} = $val', + PrintConv => { + 0 => 'Off', + 1 => 'Highlights', + 2 => 'Midtones', + }, + }, + 555 => { Name => 'MovieZebraPattern', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} != 0', PrintConv => {0 => 'Pattern 1', 1 => 'Pattern 2'}, Unknown => 1}, # CSg13-b + 557 => { Name => 'MovieHighlightDisplayThreshold', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} == 1', Unknown => 1 }, # CSg13-c 120-25 when highlights are the selected tone range + 559 => { Name => 'MovieMidtoneDisplayValue', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} == 2', Unknown => 1 }, # CSg13-d1 when midtones are the selected tone range + 561 => { Name => 'MovieMidtoneDisplayRange', Condition => '$$self{ZebraPatternToneRange} and $$self{ZebraPatternToneRange} == 2', PrintConv => '"+/-$val"', Unknown => 1 }, # CSg13-d2 when midtones are the selected tone range + #563 CS g-14 LimitZebraPatternToneRange 0=>'No Restrictions', 1=> 'Highlights', 2=> 'Midtones' + 565 => { Name => 'MovieEVFGrid', PrintConv => \%evfGridsZ9, Unknown => 1 }, # CSg15 + 573 => { Name => 'MovieShutterSpeedLock', PrintConv => \%offOn, Unknown => 1}, # CSg3-a + 574 => { Name => 'MovieFocusPointLock', PrintConv => \%offOn, Unknown => 1}, # CSg3-c + 587 => { Name => 'MatrixMetering', PrintConv => { 0 => 'Face Detection Off', 1 => 'Face Detection On' }, Unknown => 1 }, # CSb4 + 588 => { Name => 'AF-CFocusDisplay', PrintConv => \%offOn }, # CSa11c + 589 => { Name => 'FocusPeakingDisplay', PrintConv => \%offOn, Unknown => 1}, # CSa13a + 591 => { # CSb7 + Name => 'KeepExposure', + PrintConv => { + 0 => 'Off', + 1 => 'Shutter Speed', + 2 => 'ISO', + }, + }, + #593 CSd (adjust viewfinder/monitor hue, brightness and/or white balance settings) 0=>'No adjustment', 1=>'Adjust'. Related ontrols & adjustments stored in following dozen bytes or so. + 609 => { Name => 'StarlightView', PrintConv => \%offOn, Unknown => 1 }, # CSd11 + 611 => { # CSd12-a + Name => 'EVFWarmDisplayMode', + Unknown => 1, + PrintConv => { + 0 => 'Off', + 1 => 'Mode 1', + 2 => 'Mode 2', + }, + }, + 613 => { # CSd12-b values in range -3 to +3 + Name => 'EVFWarmDisplayBrightness', + Format => 'int8s', + Unknown => 1, + }, + 615 => { #CSd15 + Name => 'EVFReleaseIndicator', + Unknown => 1, + PrintConv => { + 0 => 'Off', + 1 => 'Type A (Dark)', + 2 => 'Type B (Border)', + 3 => 'Type C (Sides)', + }, + }, + 625 => { Name => 'MovieApertureLock', PrintConv => \%offOn, Unknown => 1 }, # CSg3-b + 631 => { Name => 'FlickAdvanceDirection', PrintConv => { 0 => 'Left to Right', 1 => 'Right to Left' }, Unknown => 1 }, # CSf13-c +); +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::NikonCustom - Read and Write Nikon custom settings + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +The Nikon custom functions are very specific to the camera model (and +sometimes even change with firmware version). The information is stored as +unformatted binary data in the ShotInfo record of the Nikon MakerNotes. +This module contains the definitions necessary for Image::ExifTool to decode +this information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Jens Duttke and Warren Hatch for their help decoding the D300, D3 +and Z9 custom settings. And thanks to the customer service personnel at +Best Buy for not bugging me while I spent lots of time playing with their +cameras. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Nikon Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/NikonSettings.pm b/ExifTool/lib/Image/ExifTool/NikonSettings.pm new file mode 100644 index 0000000..20b0724 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/NikonSettings.pm @@ -0,0 +1,2095 @@ +#------------------------------------------------------------------------------ +# File: NikonSettings.pm +# +# Description: Read Nikon user settings +# +# Revisions: 2021/01/08 - Warren Hatch Created +# 2021/01/12 - PH Created ProcessNikonSettings() to extract tags +# by ID instead of using fixed offsets +# +# References: 1) Warren Hatch private communication (D6 and Z7_2) +# +#------------------------------------------------------------------------------ + +package Image::ExifTool::NikonSettings; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.07'; + +sub ProcessNikonSettings($$$); + +my %enableDisable = ( 1 => 'Enable', 2 => 'Disable' ); + +my %funcButtonZ7m2 = ( + 1 => 'AF-On', + 2 => 'AF Lock Only', + 3 => 'AE Lock (hold)', + 4 => 'AE Lock (reset on release)', + 5 => 'AE Lock Only', + 6 => 'AE/AF Lock', + 7 => 'FV Lock', + 8 => 'Flash Disable/Enable', + 9 => 'Preview', + 10 => 'Matrix Metering', + 11 => 'Center-weighted Metering', + 12 => 'Spot Metering', + 13 => 'Highlight-weighted Metering', + 14 => 'Bracketing Burst', + 15 => 'Synchronized Release (Master)', + 16 => 'Synchronized Release (Remote)', # no settings map to 17 or 18 + 19 => '+NEF(RAW)', + 20 => 'Subject Tracking', + 21 => 'Silent Photography', + 22 => 'LiveView Info Display On/Off', + 23 => 'Grid Display', + 24 => 'Zoom (Low)', + 25 => 'Zoom (1:1)', + 26 => 'Zoom (High)', + 27 => 'My Menu', + 28 => 'My Menu Top Item', + 29 => 'Playback', + 30 => 'Protect', + 31 => 'Image Area', + 32 => 'Image Quality', + 33 => 'White Balance', + 34 => 'Picture Control', + 35 => 'Active-D Lighting', + 36 => 'Metering', + 37 => 'Flash Mode', + 38 => 'Focus Mode', + 39 => 'Auto Bracketing', + 40 => 'Multiple Exposure', + 41 => 'HDR', + 42 => 'Exposure Delay Mode', + 43 => 'Shutter/Aperture Lock', + 44 => 'Focus Peaking', + 45 => 'Rating 0', + 46 => 'Rating 5', + 47 => 'Rating 4', + 48 => 'Rating 3', + 49 => 'Rating 2', + 50 => 'Rating 1', # no settings map to x'51 (possibly intended for 'Candidate for Delection' + 52 => 'Non-CPU Lens', + 52 => 'None', +); + +my %flickUpDownD6 = ( + 1 => 'Rating', + 2 => 'Select To Send', + 3 => 'Protect', + 4 => 'Voice Memo', + 5 => 'None', +); + +my %flickUpDownRatingD6 = ( + 1 => 'Rating 5', + 2 => 'Rating 4', + 3 => 'Rating 3', + 4 => 'Rating 2', + 5 => 'Rating 1', + 6 => 'Candidate for Deletion', +); + +my %groupAreaCustom = ( + 1 => '1x7', + 2 => '1x5', + 3 => '3x7', + 4 => '3x5', + 5 => '3x3', + 6 => '5x7', + 7 => '5x5', + 8 => '5x3', + 9 => '5x1', + 10 => '7x7', + 11 => '7x5', + 12 => '7x3', + 13 => '7x1', + 14 => '11x3', + 15 => '11x1', + 16 => '15x3', + 17 => '15x1', +); + +my %iSOAutoHiLimitD6 = ( + 1 => 'ISO 200', + 2 => 'ISO 250', + 3 => 'ISO 280', + 4 => 'ISO 320', + 5 => 'ISO 400', + 6 => 'ISO 500', + 7 => 'ISO 560', + 8 => 'ISO 640', + 9 => 'ISO 800', + 10 => 'ISO 1000', + 11 => 'ISO 1100', + 12 => 'ISO 1250', + 13 => 'ISO 1600', + 14 => 'ISO 2000', + 15 => 'ISO 2200', + 16 => 'ISO 2500', + 17 => 'ISO 3200', + 18 => 'ISO 4000', + 19 => 'ISO 4500', + 20 => 'ISO 5000', + 21 => 'ISO 6400', + 22 => 'ISO 8000', + 23 => 'ISO 9000', + 24 => 'ISO 10000', + 25 => 'ISO 12800', + 26 => 'ISO 16000', + 27 => 'ISO 18000', + 28 => 'ISO 20000', + 29 => 'ISO 25600', + 30 => 'ISO 32000', + 31 => 'ISO 36000', + 32 => 'ISO 40000', + 33 => 'ISO 51200', + 34 => 'ISO 64000', + 35 => 'ISO 72000', + 36 => 'ISO 81200', + 37 => 'ISO 102400', + 38 => 'ISO Hi 0.3', + 39 => 'ISO Hi 0.5', + 40 => 'ISO Hi 0.7', + 41 => 'ISO Hi 1.0', + 42 => 'ISO Hi 2.0', + 43 => 'ISO Hi 3.0', + 44 => 'ISO Hi 4.0', + 45 => 'ISO Hi 5.0', +); + +my %iSOAutoHiLimitZ7 = ( + 1 => 'ISO 100', + 2 => 'ISO 125', + 4 => 'ISO 160', + 5 => 'ISO 200', + 6 => 'ISO 250', + 8 => 'ISO 320', + 9 => 'ISO 400', + 10 => 'ISO 500', + 12 => 'ISO 640', + 13 => 'ISO 800', + 14 => 'ISO 1000', + 16 => 'ISO 1250', + 17 => 'ISO 1600', + 18 => 'ISO 2000', + 20 => 'ISO 2500', + 21 => 'ISO 3200', + 22 => 'ISO 4000', + 24 => 'ISO 5000', + 25 => 'ISO 6400', + 26 => 'ISO 8000', + 28 => 'ISO 10000', + 29 => 'ISO 12800', + 30 => 'ISO 16000', + 32 => 'ISO 20000', + 33 => 'ISO 25600', + 38 => 'ISO Hi 0.3', + 39 => 'ISO Hi 0.5', + 40 => 'ISO Hi 0.7', + 41 => 'ISO Hi 1.0', + 42 => 'ISO Hi 2.0', +); + +my %lensFuncButtonZ7m2 = ( + 1 => 'AF-On', + 2 => 'AF Lock Only', + 3 => 'AE Lock (hold)', + 4 => 'AE Lock (reset on release)', + 5 => 'AE Lock Only', + 6 => 'AE/AF Lock', + 7 => 'FV Lock', + 8 => 'Flash Disable/Enable', + 9 => 'Preview', + 10 => 'Matrix Metering', + 11 => 'Center-weighted Metering', + 12 => 'Spot Metering', + 13 => 'Highlight-weighted Metering', + 14 => 'Bracketing Burst', + 15 => 'Synchronized Release (Master)', + 16 => 'Synchronized Release (Remote)', # no settings map to 17 or 18 (Z7II fw 1.01) + 19 => '+NEF(RAW)', + 20 => 'Subject Tracking', + 21 => 'Grid Display', + 22 => 'Zoom (Low)', + 23 => 'Zoom (1:1)', + 24 => 'Zoom (High)', + 25 => 'My Menu', + 26 => 'My Menu Top Item', + 27 => 'Playback', + 28 => 'None', +); + +my %limitNolimit = ( 1 => 'Limit', 2 => 'No Limit' ); + +my %limtReleaseModeSel = ( + 0 => 'No Limit', # not sure why 0 and 2 both map to 'No Limit', but they do + 1 => 'Limit', + 2 => 'No Limit', +); + +my %menuBank = ( + 1 => 'A', + 2 => 'B', + 3 => 'C', + 4 => 'D', +); + +my %noYes = ( 1 => 'No', 2 => 'Yes' ); +my %offOn = ( 1 => 'Off', 2 => 'On' ); +my %onOff = ( 1 => 'On', 2 => 'Off' ); + +my %previewButtonD6 = ( + 1 => 'Preset Focus Point - Press To Recall', + 2 => 'Preset Focus Point - Hold To Recall', + 3 => 'AF-AreaMode S', + 4 => 'AF-AreaMode D9', + 5 => 'AF-AreaMode D25', + 6 => 'AF-AreaMode D49', + 7 => 'AF-AreaMode D105', + 8 => 'AF-AreaMode 3D', + 9 => 'AF-AreaMode Group', + 10 => 'AF-AreaMode Group C1', + 11 => 'AF-AreaMode Group C2', + 12 => 'AF-AreaMode Auto Area', + 13 => 'AF-AreaMode + AF-On S', + 14 => 'AF-AreaMode + AF-On D9', + 15 => 'AF-AreaMode + AF-On D25', + 16 => 'AF-AreaMode + AF-On D49', + 17 => 'AF-AreaMode + AF-On D105', + 18 => 'AF-AreaMode + AF-On 3D', + 19 => 'AF-AreaMode + AF-On Group', + 20 => 'AF-AreaMode + AF-On Group C1', + 21 => 'AF-AreaMode + AF-On Group C2', + 22 => 'AF-AreaMode + AF-On Auto Area', + 23 => 'AF-On', + 24 => 'AF Lock Only', + 25 => 'AE Lock (hold)', + 26 => 'AE/WB Lock (hold)', + 27 => 'AE Lock (reset on release)', + 28 => 'AE Lock Only', + 29 => 'AE/AF Lock', + 30 => 'FV Lock', + 31 => 'Flash Disable/Enable', + 32 => 'Preview', + 33 => 'Recall Shooting Functions', + 34 => 'Bracketing Burst', + 35 => 'Synchronized Release (Master)', + 36 => 'Synchronized Release (Remote)', # no settings map to 37 or 38 + 39 => '+NEF(RAW)', + 40 => 'Grid Display', + 41 => 'Virtual Horizon', + 42 => 'Voice Memo', + 43 => 'Wired LAN', + 44 => 'My Menu', + 45 => 'My Menu Top Item', + 46 => 'Playback', + 47 => 'Filtered Playback', + 48 => 'Photo Shooting Bank', + 49 => 'AF Mode/AF Area Mode', + 50 => 'Image Area', + 51 => 'Active-D Lighting', + 52 => 'Exposure Delay Mode', + 53 => 'Shutter/Aperture Lock', + 54 => '1 Stop Speed/Aperture', + 55 => 'Non-CPU Lens', + 56 => 'None', +); + +my %releaseFocus = ( + 1 => 'Release', + 2 => 'Focus', +); + +my %tagMultiSelector = ( + 1 => 'Restart Standby Timer', + 2 => 'Do Nothing', +); + +my %tagSecondarySlotFunction = ( + 1 => 'Overflow', + 2 => 'Backup', + 3 => 'NEF Primary + JPG Secondary', + 4 => 'JPG Primary + JPG Secondary', +); + +my %tagSubSelector = ( + 1 => 'Same as MultiSelector', + 2 => 'Focus Point Selection', +); + +my %thirdHalfFull = ( + 1 => '1/3 EV', + 2 => '1/2 EV', + 3 => '1 EV', +); + +my %times4s10s20s1m5m20m = ( + 1 => '4 s', + 2 => '10 s', + 3 => '20 s', + 4 => '1 min', + 5 => '5 min', + 6 => '10 min', +); + +my %yesNo = ( 1 => 'Yes', 2 => 'No' ); + +my %infoD6 = ( + Condition => '$$self{Model} =~ /^NIKON D6\b/i', + Notes => 'D6', +); + +my %infoZ7 = ( + Condition => '$$self{Model} =~ /^NIKON Z (7|7_2)\b/i', + Notes => 'Z7 and Z7_2', +); + +my %infoZSeries = ( + Condition => '$$self{Model} =~ /^NIKON Z (5|50|6|6_2|7|7_2|fc)\b/i', + Notes => 'Z Series cameras thru November 2021', +); + +# Nikon Settings tags (ref 1, tag ID's ref PH) +%Image::ExifTool::NikonSettings::Main = ( + PROCESS_PROC => \&ProcessNikonSettings, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + User settings for newer Nikon models. A number of the tags are marked as + Unknown only to reduce the volume of the normal output. + }, + 0x001 => [{ + Name => 'ISOAutoHiLimit', + PrintConv => \%iSOAutoHiLimitD6, + %infoD6, + },{ + Name => 'ISOAutoHiLimit', + PrintConv => \%iSOAutoHiLimitZ7, + %infoZ7, + }], + 0x002 => [{ + Name => 'ISOAutoFlashLimit', + PrintConv => { + 1 => 'Same As Without Flash', + 2 => 'ISO 200', + 3 => 'ISO 250', + 5 => 'ISO 320', + 6 => 'ISO 400', + 7 => 'ISO 500', + 9 => 'ISO 640', + 10 => 'ISO 800', + 11 => 'ISO 1000', + 13 => 'ISO 1250', + 14 => 'ISO 1600', + 15 => 'ISO 2000', + 17 => 'ISO 2500', + 18 => 'ISO 3200', + 19 => 'ISO 4000', + 21 => 'ISO 5000', + 22 => 'ISO 6400', + 23 => 'ISO 8000', + 25 => 'ISO 10000', + 26 => 'ISO 12800', + 27 => 'ISO 16000', + 29 => 'ISO 20000', + 30 => 'ISO 25600', + 31 => 'ISO 32000', + 33 => 'ISO 40000', + 34 => 'ISO 51200', + 35 => 'ISO 64000', + 36 => 'ISO 72000', + 37 => 'ISO 81200', + 38 => 'ISO 102400', + 39 => 'ISO Hi 0.3', + 40 => 'ISO Hi 0.5', + 41 => 'ISO Hi 0.7', + 42 => 'ISO Hi 1.0', + 43 => 'ISO Hi 2.0', + 44 => 'ISO Hi 3.0', + 45 => 'ISO Hi 4.0', + 46 => 'ISO Hi 5.0', + }, + %infoD6, + },{ + Name => 'ISOAutoFlashLimit', + PrintConv => { + 1 => 'Same As Without Flash', + 2 => 'ISO 100', + 3 => 'ISO 125', + 5 => 'ISO 160', + 6 => 'ISO 200', + 7 => 'ISO 250', + 9 => 'ISO 320', + 10 => 'ISO 400', + 11 => 'ISO 500', + 13 => 'ISO 640', + 14 => 'ISO 800', + 15 => 'ISO 1000', + 17 => 'ISO 1250', + 18 => 'ISO 1600', + 19 => 'ISO 2000', + 21 => 'ISO 2500', + 22 => 'ISO 3200', + 23 => 'ISO 4000', + 25 => 'ISO 5000', + 26 => 'ISO 6400', + 27 => 'ISO 8000', + 29 => 'ISO 10000', + 30 => 'ISO 12800', + 31 => 'ISO 16000', + 33 => 'ISO 20000', + 34 => 'ISO 25600', + 39 => 'ISO Hi 0.3', + 40 => 'ISO Hi 0.5', + 41 => 'ISO Hi 0.7', + 42 => 'ISO Hi 1.0', + 43 => 'ISO Hi 2.0', + }, + %infoZ7, + }], + 0x003 => { # (D6/Z7_2) + Name => 'ISOAutoShutterTime', + PrintConv => { + 1 => 'Auto (Slowest)', + 2 => 'Auto (Slower)', + 3 => 'Auto', + 4 => 'Auto (Faster)', + 5 => 'Auto (Fastest)', + 6 => '1/4000 s', + 7 => '1/3200 s', + 8 => '1/2500 s', + 9 => '1/2000 s', + 10 => '1/1600 s', + 11 => '1/1250 s', + 12 => '1/1000 s', + 13 => '1/800 s', + 14 => '1/640 s', + 15 => '1/500 s', + 16 => '1/400 s', + 17 => '1/320 s', + 18 => '1/250 s', + 19 => '1/200 s', + 20 => '1/160 s', + 21 => '1/125 s', + 22 => '1/100 s', + 23 => '1/80 s', + 24 => '1/60 s', + 25 => '1/50 s', + 26 => '1/40 s', + 27 => '1/30 s', + 28 => '1/25 s', + 29 => '1/20 s', + 30 => '1/15 s', + 31 => '1/13 s', + 32 => '1/10 s', + 33 => '1/8 s', + 34 => '1/6 s', + 35 => '1/5 s', + 36 => '1/4 s', + 37 => '1/3 s', + 38 => '1/2.5 s', + 39 => '1/2 s', + 40 => '1/1.6 s', + 41 => '1/1.3 s', + 42 => '1 s', + 43 => '1.3 s', + 44 => '1.6 s', + 45 => '2 s', + 46 => '2.5 s', + 47 => '3 s', + 48 => '4 s', + 49 => '5 s', + 50 => '6 s', + 51 => '8 s', + 52 => '10 s', + 53 => '13 s', + 54 => '15 s', + 55 => '20 s', + 56 => '25 s', + 57 => '30 s', + }, + }, + 0x00b => { Name => 'FlickerReductionShooting', PrintConv => \%enableDisable }, # (D6/Z7_2) + 0x00c => { Name => 'FlickerReductionIndicator',PrintConv => \%enableDisable }, # (D6) + 0x00d => [{ + Name => 'MovieISOAutoHiLimit', + PrintConv => \%iSOAutoHiLimitD6, + %infoD6, + },{ + Name => 'MovieISOAutoHiLimit', + PrintConv => { + 1 => 'ISO 200', + 2 => 'ISO 250', + 4 => 'ISO 320', + 5 => 'ISO 400', + 6 => 'ISO 500', + 8 => 'ISO 640', + 9 => 'ISO 800', + 10 => 'ISO 1000', + 12 => 'ISO 1250', + 13 => 'ISO 1600', + 14 => 'ISO 2000', + 16 => 'ISO 2500', + 17 => 'ISO 3200', + 18 => 'ISO 4000', + 20 => 'ISO 5000', + 21 => 'ISO 6400', + 22 => 'ISO 8000', + 24 => 'ISO 10000', + 25 => 'ISO 12800', + 26 => 'ISO 16000', + 28 => 'ISO 20000', + 29 => 'ISO 25600', + 34 => 'ISO Hi 0.3', + 35 => 'ISO Hi 0.5', + 36 => 'ISO Hi 0.7', + 37 => 'ISO Hi 1.0', + 38 => 'ISO Hi 2.0', + }, + %infoZ7, + }], + 0x00e => { Name => 'MovieISOAutoControlManualMode',PrintConv => \%onOff }, # (D6/Z7_2) + 0x00f => { Name => 'MovieWhiteBalanceSameAsPhoto', PrintConv => \%yesNo }, # (D6/Z7_2) + 0x01d => [{ # CSa1 (D6) + Name => 'AF-CPrioritySel', + PrintConv => { # valid for cameras with 4 options for CS1, otherwise 1=Release, 2=Focus + 1 => 'Release', + 2 => 'Release + Focus', + 3 => 'Focus + Release', + 4 => 'Focus', + }, + %infoD6, + },{ # CSa1 (Z7_2) + Name => 'AF-CPrioritySel', + PrintConv => \%releaseFocus, + %infoZSeries, + }], + 0x01e => { Name => 'AF-SPrioritySel', PrintConv => \%releaseFocus }, # CSa2 (D6), CSa2 (Z7_2) + 0x020 => [{ # CSa4 (D6) + Name => 'AFPointSel', + PrintConv => { + 1 => '105 Points', + 2 => '27 Points', + 3 => '15 Points', + }, + %infoD6, + },{ # CSa4 (Z7_2) + Name => 'AFPointSel', + PrintConv => { 1 => 'Use All', 2 => 'Use Half' }, + %infoZSeries, + }], + 0x022 => { Name => 'AFActivation', PrintConv => { 1 => 'Shutter/AF-On', 2 => 'AF-On Only' } }, # CSa6-a (D6/Z7_2) (missing enable/disable out of focus release) # (D6) + 0x023 => { Name => 'FocusPointWrap', PrintConv => { 1 => 'Wrap', 2 => 'No Wrap' } }, # CSa16 (D6), CSa8 (Z7_2) + 0x025 => { # CSa17-a (D6), CSa9-a (Z7_2) + Name => 'ManualFocusPointIllumination', + PrintConv => { + 1 => 'On', + 2 => 'On During Focus Point Selection Only', + }, + }, + 0x026 => { Name => 'AF-AssistIlluminator', PrintConv => \%onOff }, # CSa11 (Z7_2) + 0x027 => { Name => 'ManualFocusRingInAFMode', PrintConv => \%onOff }, # CSa12 (D6,Z7_2) capability documented in manual, but visibility (& ability to test) requires a compatible lens + 0x029 => { Name => 'ISOStepSize', PrintConv => \%thirdHalfFull }, # CSb1 (D6) + 0x02a => { Name => 'ExposureControlStepSize', PrintConv => \%thirdHalfFull }, # CSb2 (D6), CSb1 (Z7_2) + 0x02b => { # CSb4 (D6), CSb2 (Z7_2) + Name => 'EasyExposureCompensation', + PrintConv => { + 1 => 'On (auto reset)', + 2 => 'On', + 3 => 'Off', + }, + }, + 0x02c => { Name => 'MatrixMetering', PrintConv => { 1 => 'Face Detection On', 2 => 'Face Detection Off' } }, # CSb5 (D6) + 0x02d => [{ # CSb6 (D6) + Name => 'CenterWeightedAreaSize', + PrintConv => { + 1 => '8 mm', + 2 => '12 mm', + 3 => '15 mm', + 4 => '20 mm', + 5 => 'Average', + }, + %infoD6 + },{ # CSb3 (Z7_2) + Name => 'CenterWeightedAreaSize', + PrintConv => { 1 => '12 mm', 2 => 'Average' }, + %infoZSeries, + }], + 0x02f => { # CSb7-a (D6), CSb4-a (Z7_2) + Name => 'FineTuneOptMatrixMetering', + ValueConv => '($val - 7) / 6', + ValueConvInv => 'int($val*6+7)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x030 => { # CSb7-b (D6), CSb4-b (Z7_2) + Name => 'FineTuneOptCenterWeighted', + ValueConv => '($val - 7) / 6', + ValueConvInv => 'int($val*6+7)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x031 => { # CSb7-c (D6), CSb4-c (Z7_2) + Name => 'FineTuneOptSpotMetering', + ValueConv => '($val - 7) / 6', + ValueConvInv => 'int($val*6+7)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x032 => { # CSb7-d (D6), CSb4-d (Z7_2) + Name => 'FineTuneOptHighlightWeighted', + ValueConv => '($val - 7) / 6', + ValueConvInv => 'int($val*6+7)', + PrintConv => '$val ? sprintf("%+.2f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x033 => { # CSc1 (D6), CSc1 (Z7_2) + Name => 'ShutterReleaseButtonAE-L', + PrintConv => { + 1 => 'On (Half Press)', + 2 => 'On (Burst Mode)', + 3 => 'Off', + }, + }, + 0x034 => [{ # CSc2 (D6) + Name => 'StandbyMonitorOffTime', + PrintConv => { + 1 => '4 s', + 2 => '6 s', + 3 => '10 s', + 4 => '30 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + 8 => '30 min', + 9 => 'No Limit', + }, + %infoD6, + },{ # CSc4-d (Z7_2) + Name => 'StandbyMonitorOffTime', + PrintConv => { + 1 => '10 s', + 2 => '20 s', + 3 => '30 s', + 4 => '1 min', + 5 => '5 min', + 6 => '10 min', + 7 => '30 min', + 8 => 'No Limit', + }, + %infoZSeries, + }], + 0x035 => { # CSc3-a (D6), CSc2-a (Z7_2) + Name => 'SelfTimerTime', + PrintConv => { + 1 => '2 s', + 2 => '5 s', + 3 => '10 s', + 4 => '20 s', + }, + }, + 0x036 => { Name => 'SelfTimerShotCount', ValueConv => '10 - $val', ValueConvInv => '10 + $val' }, # CSc3-b (D6), CSc2-b (Z7_2) + 0x037 => { # CSc3-c (D6), CSc2-c (Z7_2) + Name => 'SelfTimerShotInterval', + PrintConv => { + 1 => '0.5 s', + 2 => '1 s', + 3 => '2 s', + 4 => '3 s', + }, + }, + 0x038 => { Name => 'PlaybackMonitorOffTime', PrintConv => \%times4s10s20s1m5m20m }, # CSc4-a (D6), CSc3-a (Z7_2) + 0x039 => { Name => 'MenuMonitorOffTime', PrintConv => \%times4s10s20s1m5m20m }, # CSc4-b (D6), CSc3-b (Z7_2) + 0x03a => { Name => 'ShootingInfoMonitorOffTime',PrintConv => \%times4s10s20s1m5m20m }, # CSc4-c (D6) + 0x03b => { # CSc4-d (D6), CSc3-c (Z7_2) + Name => 'ImageReviewMonitorOffTime', + PrintConv => { + 1 => '2 s', + 2 => '4 s', + 3 => '10 s', + 4 => '20 s', + 5 => '1 min', + 6 => '5 min', + 7 => '10 min', + }, + }, + 0x03c => { # CSc4-e (D6) + Name => 'LiveViewMonitorOffTime', + PrintConv => { + 1 => '5 min', + 2 => '10 min', + 3 => '15 min', + 4 => '20 min', + 5 => '30 min', + 6 => 'No Limit', + }, + }, + 0x03e => { Name => 'CLModeShootingSpeed', ValueConv => '6 - $val', ValueConvInv => '6 + $val', PrintConv => '"$val fps"', PrintConvInv => '$val=~s/\s*fps//i; $val' }, # CSd1 (Z7_2) + 0x03f => { Name => 'MaxContinuousRelease' }, # CSd2 # values: 1-200 # (D6/Z7_2) + 0x040 => { # CSd5 (D6), CSd4 (Z7_2) + Name => 'ExposureDelayMode', + PrintConv => { + 1 => '3 s', + 2 => '2 s', + 3 => '1 s', + 4 => '0.5 s', + 5 => '0.2 s', + 6 => 'Off', + }, + }, + 0x041 => { Name => 'ElectronicFront-CurtainShutter',PrintConv => \%onOff }, # CSd6 (D6) + 0x042 => { Name => 'FileNumberSequence', PrintConv => \%onOff }, # CSd9 (D6), CSd8 (Z7_2) + 0x043 => { Name => 'FramingGridDisplay', PrintConv => \%onOff }, # CSd11 (D6), CSd10 (Z7_2), new tag with implementation of NikonSettings # (Z7_2) + 0x045 => { Name => 'LCDIllumination', PrintConv => \%onOff }, # CSd13 (D6) + 0x046 => { Name => 'OpticalVR', PrintConv => \%onOff }, # CSd15 (D6) + 0x047 => [{ # CSe1 (D6) + Name => 'FlashSyncSpeed', + PrintConv => { + 1 => '1/250 s (auto FP)', + 2 => '1/250 s', + 3 => '1/200 s', + 4 => '1/160 s', + 5 => '1/125 s', + 6 => '1/100 s', + 7 => '1/80 s', + 8 => '1/60 s', + }, + %infoD6, + },{ # CSe1 (Z7_2) + Name => 'FlashSyncSpeed', + PrintConv => { + 1 => '1/200 s (auto FP)', + 2 => '1/200 s', + 3 => '1/160 s', + 4 => '1/125 s', + 5 => '1/100 s', + 6 => '1/80 s', + 7 => '1/60 s', + }, + %infoZSeries, + }], + 0x048 => { # CSe2 (D6/Z7_2) + Name => 'FlashShutterSpeed', + PrintConv => { + 1 => '1/60 s', + 2 => '1/30 s', + 3 => '1/15 s', + 4 => '1/8 s', + 5 => '1/4 s', + 6 => '1/2 s', + 7 => '1 s', + 8 => '2 s', + }, + }, + 0x049 => { Name => 'FlashExposureCompArea', PrintConv => { 1 => 'Entire Frame', 2 => 'Background Only' } }, # CSe3 (D6/Z7_2) + 0x04a => { # CSe4 (D6/Z7_2) + Name => 'AutoFlashISOSensitivity', + PrintConv => { + 1 => 'Subject and Background', + 2 => 'Subject Only', + }, + }, + 0x051 => { # CSf3-l (D6) + Name => 'AssignBktButton', + PrintConv => { + 1 => 'Auto Bracketing', + 2 => 'Multiple Exposure', + 3 => 'HDR (high dynamic range)', + 4 => 'None', + }, + }, + 0x052 => [{ # CSf3-m (D6) + Name => 'AssignMovieRecordButton', + PrintConv => { + 1 => 'Voice Memo', + 2 => 'Photo Shooting Bank', + 3 => 'Exposure Mode', + 4 => 'AF Mode/AF Area Mode', + 5 => 'Image Area', + 6 => 'Shutter/Aperture Lock', + 7 => 'None', + }, + %infoD6, + },{ # CSf2-f (Z7_2) + Name => 'AssignMovieRecordButton', + PrintConv => { + 1 => 'AE Lock (hold)', + 2 => 'AE Lock (reset on release)', + 3 => 'Preview', + 4 => '+NEF(RAW)', + 5 => 'LiveView Info Display On/Off', + 6 => 'Grid Display', + 7 => 'Zoom (Low)', + 8 => 'Zoom (1:1)', + 9 => 'Zoom (High)', + 10 => 'My Menu', + 11 => 'My Menu Top Item', + 12 => 'Image Area', + 13 => 'Image Quality', + 14 => 'White Balance', + 15 => 'Picture Control', + 16 => 'Active-D Lighting', + 17 => 'Metering', + 18 => 'Flash Mode', + 19 => 'Focus Mode', + 20 => 'Auto Bracketing', + 21 => 'Multiple Exposure', + 22 => 'HDR', + 23 => 'Exposure Delay Mode', + 24 => 'Shutter/Aperture Lock', + 25 => 'Non-CPU Lens', + 26 => 'None', + }, + %infoZSeries, + }], + 0x053 => [{ # CSf4-a (D6) + Name => 'MultiSelectorShootMode', + PrintConv => { + 1 => 'Select Center Focus Point', + 2 => 'Preset Focus Point - Press To Recall', + 3 => 'Preset Focus Point - Hold To Recall', + 4 => 'None', + }, + %infoD6, + },{ # CSf3-a (Z7_2) also called the OK button on this camera + Name => 'MultiSelectorShootMode', + PrintConv => { + 1 => 'Select Center Focus Point', + 2 => 'Zoom (Low)', + 3 => 'Zoom (1:1)', + 4 => 'Zoom (High)', + 5 => 'None', + }, + %infoZSeries, + }], + 0x054 => [{ # CSf4-c (D6) + Name => 'MultiSelectorPlaybackMode', + PrintConv => { + 1 => 'Filtered Playback', + 2 => 'View Histograms', + 3 => 'Zoom (Low)', + 4 => 'Zoom (1:1)', + 5 => 'Zoom (High)', + 6 => 'Choose Folder', + }, + %infoD6, + },{ # CSf3-b (Z7_2) # these decodes are correct. However, a new camera (or one following a 'reset all settings') will report Zoom (Low), despite the camera exhibiting the expected behavior (Zoom 1:1). Any change to CSf3-b corrects the reporting. + Name => 'MultiSelectorPlaybackMode', + PrintConv => { + 1 => 'Thumbnail On/Off', + 2 => 'View Histograms', + 3 => 'Zoom (Low)', + 4 => 'Zoom (1:1)', + 5 => 'Zoom (High)', + 6 => 'Choose Folder', + }, + %infoZSeries, + }], + 0x056 => { # CSf4-b (D6) + Name => 'MultiSelectorLiveView', + PrintConv => { + 1 => 'Select Center Focus Point', + 2 => 'Zoom (Low)', + 3 => 'Zoom (1:1)', + 4 => 'Zoom (High)', + 5 => 'None', + }, + }, + 0x058 => { # CSf6-a-1 and CSf6-a-2 (D6), CSf5-a-1 and CSf5-a-2 (Z7_2), Previous cameras reported these 2 in a single tag (CmdDialsReverseRotation). Blend the separate settings together to match extant tag name and values + Name => 'CmdDialsReverseRotExposureComp', + RawConv => '$$self{CmdDialsReverseRotExposureComp} = $val', + Unknown => 1, + }, + 0x059 => { # CSf6-b-1 and CSf6-b-2 (D6), CSf5-b-1 and CSf5-b-2 (Z7_2), Previous cameras reported these 2 in a single tag (CmdDialsChangeMainSub). Blend the separate settings together to match extant tag name and values + Name => 'CmdDialsChangeMainSubExposure', + RawConv => '$$self{CmdDialsChangeMainSubExposure} = $val', + Unknown => 1, + }, + 0x05a => [{ # CSf6-b-1 and CSf6-b-2 (D6), CSf5-b-1 and CSf5-b-2 (Z7_2), (continued from above) + Name => 'CmdDialsChangeMainSub', + Condition => '$$self{CmdDialsChangeMainSubExposure} and $$self{CmdDialsChangeMainSubExposure} == 1', + PrintConv => { + 1 => 'Autofocus On, Exposure On', + 2 => 'Autofocus Off, Exposure On', + }, + },{ + Name => 'CmdDialsChangeMainSub', + Condition => '$$self{CmdDialsChangeMainSubExposure} and $$self{CmdDialsChangeMainSubExposure} == 2', + PrintConv => { + 1 => 'Autofocus On, Exposure On (Mode A)', + 2 => 'Autofocus Off, Exposure On (Mode A)', + }, + },{ + Name => 'CmdDialsChangeMainSub', + PrintConv => { + 1 => 'Autofocus On, Exposure Off', + 2 => 'Autofocus Off, Exposure Off', + }, + }], + 0x05b => { Name => 'CmdDialsMenuAndPlayback', PrintConv => { 1 => 'On', 2 => 'On (Image Review Excluded)', 3 => 'Off' } }, # CSf5-c (D6), CSf5-c (Z7_2) + 0x05c => { # CSf6-d (D6), CSf5-d (Z7_2) + Name => 'SubDialFrameAdvance', + PrintConv => { + 1 => '10 Frames', + 2 => '50 Frames', + 3 => 'Rating', + 4 => 'Protect', + 5 => 'Stills Only', + 6 => 'Movies Only', + 7 => 'Folder', + }, + }, + 0x05d => { Name => 'ReleaseButtonToUseDial', PrintConv => \%yesNo }, # CSf8 (D6), CSf6 (Z7_2) + 0x05e => { Name => 'ReverseIndicators', PrintConv => { 1 => '+ 0 -', 2 => '- 0 +' } }, # CSf9 (D6), CSf7 (Z7_2) + 0x062 => { # CSg2-f (D6), CSg2-e (Z7_2) + Name => 'MovieShutterButton', + PrintConv => { + 1 => 'Take Photo', + 2 => 'Record Movie', + }, + }, + 0x063 => { # Settings menu # (D6,Z7_2) + Name => 'Language', + PrintConv => { + 5 => 'English', + 6 => 'Spanish', + 8 => 'French', + 15 => 'Portuguese (Br)', + }, + }, + 0x06c => [{ + Name => 'ShootingInfoDisplay', + PrintConv => { + 1 => 'Auto', + 2 => 'Manual (dark on light)', + 3 => 'Manual (light on dark)', + }, + %infoD6, + },{ + Name => 'ShootingInfoDisplay', + PrintConv => { + 1 => 'Manual (dark on light)', + 2 => 'Manual (light on dark)', + }, + %infoZSeries, + }], + 0x074 => { Name => 'FlickAdvanceDirection', PrintConv => { 1 => 'Right to Left', 2 => 'Left to Right' } }, # CSf12-3 (D6) + 0x075 => { # Settings menu # (D6,Z7_2) + Name => 'HDMIOutputResolution', + PrintConv => { + 1 => 'Auto', + 2 => '2160p', + 3 => '1080p', + 4 => '1080i', + 5 => '720p', + 6 => '576p', + 7 => '480p', + }, + }, + 0x077 => { # Settings menu # (D6,Z7_2) + Name => 'HDMIOutputRange', + PrintConv => { + 1 => 'Auto', + 2 => 'Limit', + 3 => 'Full', + }, + }, + 0x080 => [{ + Name => 'RemoteFuncButton', + PrintConv => { + 1 => 'AF-On', + 2 => 'AF Lock Only', + 3 => 'AE Lock (reset on release)', + 4 => 'AE Lock Only', + 5 => 'AE/AF Lock', + 6 => 'FV Lock', + 7 => 'Flash Disable/Enable', + 8 => 'Preview', + 9 => '+NEF(RAW)', + 10 => 'LiveView Info Display On/Off', + 11 => 'Recall Shooting Functions', + 12 => 'None', + }, + %infoD6, + },{ + Name => 'RemoteFuncButton', + PrintConv => { + 1 => 'AF-On', + 2 => 'AF Lock Only', + 3 => 'AE Lock (reset on release)', + 4 => 'AE Lock Only', + 5 => 'AE/AF Lock', + 6 => 'FV Lock', + 7 => 'Flash Disable/Enable', + 8 => 'Preview', + 9 => '+NEF(RAW)', + 10 => 'None', + 11 => 'LiveView Info Display On/Off', + }, + %infoZSeries, + }], + 0x08b => [{ # CSf6-a-1 and CSf6-a-2 (D6), CSf5-a-1 and CSf5-a-2 (Z7_2), (continued from above) + Name => 'CmdDialsReverseRotation', + Condition => '$$self{CmdDialsReverseRotExposureComp} and $$self{CmdDialsReverseRotExposureComp} == 1', + PrintConv => { + 1 => 'No', + 2 => 'Shutter Speed & Aperture', + }, + },{ + Name => 'CmdDialsReverseRotation', + PrintConv => { + 1 => 'Exposure Compensation', + 2 => 'Exposure Compensation, Shutter Speed & Aperture', + }, + }], + 0x08d => { # CSd10-b (D6), CSd11-b (Z7_2) + Name => 'FocusPeakingHighlightColor', + PrintConv => { + 1 => 'Red', + 2 => 'Yellow', + 3 => 'Blue', + 4 => 'White', + }, + }, + 0x08e => { Name => 'ContinuousModeDisplay', PrintConv => \%onOff }, # CSd14 (D6), CSd12 (Z7_2) + 0x08f => { Name => 'ShutterSpeedLock', PrintConv => \%onOff }, # CSf54-a (D6), CSf4-a (Z7_2) + 0x090 => { Name => 'ApertureLock', PrintConv => \%onOff }, # CSf5-b (D6), CSf4-b (Z7_2 could not select) + 0x091 => { # CSg4-b (D6), CSg6-b (Z7_2) + Name => 'MovieHighlightDisplayThreshold', + PrintConv => { + 1 => '255', + 2 => '248', + 3 => '235', + 4 => '224', + 5 => '213', + 6 => '202', + 7 => '191', + 8 => '180', + }, + }, + 0x092 => { Name => 'HDMIExternalRecorder', PrintConv => \%onOff }, # Settings Menu/HDMI/Advanced entry (D6 & Z7_2) + 0x093 => { # CSa3-a (D6), CSa3 (Z7_2) + Name => 'BlockShotAFResponse', + PrintConv => { + 1 => '1 (Quick)', + 2 => '2', + 3 => '3 (Normal)', + 4 => '4', + 5 => '5 (Delay)', + }, + }, + 0x094 => { Name => 'SubjectMotion', PrintConv => { 1 => 'Erratic', 2 => 'Steady' } }, # CSa3-b (D6) + 0x095 => { Name => 'Three-DTrackingFaceDetection', PrintConv => \%onOff }, # CSa8 (D6) + 0x097 => [{ # CSa5 (D6) + Name => 'StoreByOrientation', + PrintConv => { + 1 => 'Focus Point', + 2 => 'Focus Point and AF-area mode', + 3 => 'Off', + }, + %infoD6, + },{ # CSa5 (Z7_2) + Name => 'StoreByOrientation', + PrintConv => { + 1 => 'Focus Point', + 2 => 'Off', + }, + %infoZSeries, + }], + 0x099 => { Name => 'DynamicAreaAFAssist',PrintConv => \%onOff }, # CSa17-c (D6), CSa9-b (Z7_2) + 0x09a => { Name => 'ExposureCompStepSize', PrintConv => \%thirdHalfFull }, # CSb3 (D6) + 0x09b => { Name => 'SyncReleaseMode', PrintConv => { 1 => 'Sync', 2 => 'No Sync' } }, # CSd4 (D6), CSd3 (Z7_2) + 0x09c => { Name => 'ModelingFlash', PrintConv => \%onOff }, # CSe6 (D6), CSe5 (Z7_2) + 0x09d => { # CSe7 (D6), CSe6 (Z7_2) + Name => 'AutoBracketModeM', + PrintConv => { + 1 => 'Flash/Speed', + 2 => 'Flash/Speed/Aperture', + 3 => 'Flash/Aperture', + 4 => 'Flash Only', + }, + }, + 0x09e => { Name => 'PreviewButton', PrintConv => \%previewButtonD6 }, # CSf3-a (D6) + 0x0a0 => [{ # CSf3-b (D6) + Name => 'Func1Button', + PrintConv => \%previewButtonD6, + %infoD6, + },{ # CSf2-a (Z7_2) + Name => 'Func1Button', + PrintConv => \%funcButtonZ7m2, + %infoZSeries, + }], + 0x0a2 => [{ # CSf3-c (D6) + Name => 'Func2Button', + PrintConv => \%previewButtonD6, + %infoD6, + },{ # CSf2-b (Z7_2) + Name => 'Func2Button', + PrintConv => \%funcButtonZ7m2, + %infoZSeries, + }], + 0x0a3 => [{ # CSf3-f (D6) + Name => 'AF-OnButton', + PrintConv => { + 1 => 'AF-AreaMode S', + 2 => 'AF-AreaMode D9', + 3 => 'AF-AreaMode D25', + 4 => 'AF-AreaMode D49', + 5 => 'AF-AreaMode D105', + 6 => 'AF-AreaMode 3D', + 7 => 'AF-AreaMode Group', + 8 => 'AF-AreaMode Group C1', + 9 => 'AF-AreaMode Group C2', + 10 => 'AF-AreaMode Auto Area', + 11 => 'AF-AreaMode + AF-On S', + 12 => 'AF-AreaMode + AF-On D9', + 13 => 'AF-AreaMode + AF-On D25', + 14 => 'AF-AreaMode + AF-On D49', + 15 => 'AF-AreaMode + AF-On D105', + 16 => 'AF-AreaMode + AF-On 3D', + 17 => 'AF-AreaMode + AF-On Group', + 18 => 'AF-AreaMode + AF-On Group C1', + 19 => 'AF-AreaMode + AF-On Group C2', + 20 => 'AF-AreaMode + AF-On Auto Area', + 21 => 'AF-On', + 22 => 'AF Lock Only', + 23 => 'AE Lock (hold)', + 24 => 'AE/WB Lock (hold)', + 25 => 'AE Lock (reset on release)', + 26 => 'AE Lock Only', + 27 => 'AE/AF Lock', + 28 => 'Recall Shooting Functions', + 29 => 'None', + }, + %infoD6, + },{ # CSf2-c (Z7_2) + Name => 'AF-OnButton', + PrintConv => { + 1 => 'Center Focus Point', + 2 => 'AF-On', + 3 => 'AF Lock Only', + 4 => 'AE Lock (hold)', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock Only', + 7 => 'AE/AF Lock', + 8 => 'LiveView Info Display On/Off', + 9 => 'Zoom (Low)', + 10 => 'Zoom (1:1)', + 11 => 'Zoom (High)', + 12 => 'None' + }, + %infoZSeries, + }], + 0x0a4 => { Name => 'SubSelector', PrintConv => \%tagSubSelector }, # CSf3-g-1 # (D6), CSf2-d-1 # (Z7_2) + 0x0a5 => [{ # CSf3-h (D6) + Name => 'SubSelectorCenter', + PrintConv => { + 1 => 'Preset Focus Point - Press To Recall', + 2 => 'Preset Focus Point - Hold To Recall', + 3 => 'Center Focus Point', + 4 => 'AF-AreaMode S', + 5 => 'AF-AreaMode D9', + 6 => 'AF-AreaMode D25', + 7 => 'AF-AreaMode D49', + 8 => 'AF-AreaMode D105', + 9 => 'AF-AreaMode 3D', + 10 => 'AF-AreaMode Group', + 11 => 'AF-AreaMode Group C1', + 12 => 'AF-AreaMode Group C2', + 13 => 'AF-AreaMode Auto Area', + 14 => 'AF-AreaMode + AF-On S', + 15 => 'AF-AreaMode + AF-On D9', + 16 => 'AF-AreaMode + AF-On D25', + 17 => 'AF-AreaMode + AF-On D49', + 18 => 'AF-AreaMode + AF-On D105', + 19 => 'AF-AreaMode + AF-On 3D', + 20 => 'AF-AreaMode + AF-On Group', + 21 => 'AF-AreaMode + AF-On Group C1', + 22 => 'AF-AreaMode + AF-On Group C2', + 23 => 'AF-AreaMode + AF-On Auto Area', + 24 => 'AF-On', + 25 => 'AF Lock Only', + 26 => 'AE Lock (hold)', + 27 => 'AE/WB Lock (hold)', + 28 => 'AE Lock (reset on release)', + 29 => 'AE Lock Only', + 30 => 'AE/AF Lock', + 31 => 'FV Lock', + 32 => 'Flash Disable/Enable', + 33 => 'Preview', + 34 => 'Recall Shooting Functions', + 35 => 'Bracketing Burst', + 36 => 'Synchronized Release (Master)', + 37 => 'Synchronized Release (Remote)', + 38 => 'None', + }, + %infoD6, + },{ # CSf2-e (Z7_2) + Name => 'SubSelectorCenter', + PrintConv => { + 1 => 'Center Focus Point', + 2 => 'AF-On', + 3 => 'AF Lock Only', + 4 => 'AE Lock (hold)', + 5 => 'AE Lock (reset on release)', + 6 => 'AE Lock Only', + 7 => 'AE/AF Lock', + 8 => 'FV Lock', + 9 => 'Flash Disable/Enable', + 10 => 'Preview', + 11 => 'Matrix Metering', + 12 => 'Center-weighted Metering', + 13 => 'Spot Metering', + 14 => 'Highlight-weighted Metering', + 15 => 'Bracketing Burst', + 16 => 'Synchronized Release (Master)', + 17 => 'Synchronized Release (Remote)', + 20 => '+NEF(RAW)', + 21 => 'LiveView Info Display On/Off', + 22 => 'Grid Display', + 23 => 'Image Area', + 24 => 'Non-CPU Lens', + 25 => 'None', + }, + %infoZSeries, + }], + 0x0a7 => [{ # CSf3-n (D6) + Name => 'LensFunc1Button', + PrintConv => { + 1 => 'Preset Focus Point - Press To Recall', + 2 => 'Preset Focus Point - Hold To Recall', + 3 => 'AF-AreaMode S', + 4 => 'AF-AreaMode D9', + 5 => 'AF-AreaMode D25', + 6 => 'AF-AreaMode D49', + 7 => 'AF-AreaMode D105', + 8 => 'AF-AreaMode 3D', + 9 => 'AF-AreaMode Group', + 10 => 'AF-AreaMode Group C1', + 11 => 'AF-AreaMode Group C2', + 12 => 'AF-AreaMode Auto Area', + 13 => 'AF-AreaMode + AF-On S', + 14 => 'AF-AreaMode + AF-On D9', + 15 => 'AF-AreaMode + AF-On D25', + 16 => 'AF-AreaMode + AF-On D49', + 17 => 'AF-AreaMode + AF-On D105', + 18 => 'AF-AreaMode + AF-On 3D', + 19 => 'AF-AreaMode + AF-On Group', + 20 => 'AF-AreaMode + AF-On Group C1', + 21 => 'AF-AreaMode + AF-On Group C2', + 22 => 'AF-AreaMode + AF-On Auto Area', + 23 => 'AF-On', + 24 => 'AF Lock Only', + 25 => 'AE Lock Only', + 26 => 'AE/AF Lock', + 27 => 'Flash Disable/Enable', + 28 => 'Recall Shooting Functions', + 29 => 'Synchronized Release (Master)', + 30 => 'Synchronized Release (Remote)', + }, + %infoD6, + },{ # CSf2-g (Z7_2) + Name => 'LensFunc1Button', + PrintConv => \%lensFuncButtonZ7m2, + %infoZSeries, + }], + 0x0a8 => { Name => 'CmdDialsApertureSetting', PrintConv => { 1 => 'Sub-command Dial', 2 => 'Aperture Ring' } }, # CSf6-c (D6) + 0x0a9 => { Name => 'MultiSelector', PrintConv => \%tagMultiSelector }, # CSf7 (D6) + 0x0aa => { # CSf10 (D6) + Name => 'LiveViewButtonOptions', + PrintConv => { + 1 => 'Enable', + 2 => 'Enable (Standby Timer Active)', + 3 => 'Disable', + }, + }, + 0x0ab => { # CSf11 (D6) + Name => 'LightSwitch', + PrintConv => { + 1 => 'LCD Backlight', + 2 => 'LCD Backlight and Shooting Information', + }, + }, + 0x0b1 => [{ # CSg2-a (D6) + Name => 'MoviePreviewButton', + PrintConv => { + 1 => 'Power Aperture (Open)', + 2 => 'Exposure Compensation', + 3 => 'Grid Display', + 4 => 'Zoom (Low)', + 5 => 'Zoom (1:1)', + 6 => 'Zoom (High)', + 7 => 'Image Area', + 8 => 'Microphone Sensitivity', + 9 => 'None', + }, + %infoD6, + },{ # CSg2-a (Z7_2) + Name => 'MovieFunc1Button', + PrintConv => { + 1 => 'Power Aperture (Open)', + 2 => 'Exposure Compensation', + 3 => 'Subject Tracking', + 4 => 'LiveView Info Display On/Off', + 5 => 'Grid Display', + 6 => 'Zoom (Low)', + 7 => 'Zoom (1:1)', + 8 => 'Zoom (High)', + 9 => 'Protect', + 10 => 'Image Area', + 11 => 'White Balance', + 12 => 'Picture Control', + 13 => 'Active-D Lighting', + 14 => 'Metering', + 15 => 'Focus Mode', + 16 => 'Microphone Sensitivity', + 17 => 'Focus Peaking', + 18 => 'Rating (None)', + 19 => 'Rating (5)', + 20 => 'Rating (4)', + 21 => 'Rating (3)', + 22 => 'Rating (2)', + 23 => 'Rating (1)', # no mapping for 24 on the Z7_2. Possibly intended for Rating = 'Candidate for Deletion'? + 25 => 'None', + }, + %infoZSeries, + }], + 0x0b3 => [{ # CSg2-b (D6) + Name => 'MovieFunc1Button', + PrintConv => { + 1 => 'Power Aperture (Close)', + 2 => 'Exposure Compensation', + 3 => 'Grid Display', + 4 => 'Zoom (Low)', + 5 => 'Zoom (1:1)', + 6 => 'Zoom (High)', + 7 => 'Image Area', + 8 => 'Microphone Sensitivity', + 9 => 'None', + }, + %infoD6, + },{ # CSg2-b (Z7_2) + Name => 'MovieFunc2Button', + PrintConv => { # TODO: simplify Func1 and Func2 movies buttons - identical except for the 1st entry + 1 => 'Power Aperture (Close)', + 2 => 'Exposure Compensation', + 3 => 'Subject Tracking', + 4 => 'LiveView Info Display On/Off', + 5 => 'Grid Display', + 6 => 'Zoom (Low)', + 7 => 'Zoom (1:1)', + 8 => 'Zoom (High)', + 9 => 'Protect', + 10 => 'Image Area', + 11 => 'White Balance', + 12 => 'Picture Control', + 13 => 'Active-D Lighting', + 14 => 'Metering', + 15 => 'Focus Mode', + 16 => 'Microphone Sensitivity', + 17 => 'Focus Peaking', + 18 => 'Rating (None)', + 19 => 'Rating (5)', + 20 => 'Rating (4)', + 21 => 'Rating (3)', + 22 => 'Rating (2)', + 23 => 'Rating (1)', # no mapping for 24 on the Z7_2. Possibly intended for Rating = 'Candidate for Deletion'? + 25 => 'None', + }, + %infoZSeries, + }], + 0x0b5 => { # CSg2-c (D6) + Name => 'MovieFunc2Button', + PrintConv => { + 1 => 'Grid Display', + 2 => 'Zoom (Low)', + 3 => 'Zoom (1:1)', + 4 => 'Zoom (High)', + 5 => 'Image Area', + 6 => 'Microphone Sensitivity', + 7 => 'None', + }, + }, + 0x0b6 => [{ # CSg2-e (D6) + Name => 'AssignMovieSubselector', + PrintConv => { + 1 => 'Center Focus Point', + 2 => 'AF Lock Only', + 3 => 'AE Lock (hold)', + 4 => 'AE/WB Lock (hold)', + 5 => 'AE Lock Only', + 6 => 'AE/AF Lock', + 7 => 'Zoom (Low)', + 8 => 'Zoom (1:1)', + 9 => 'Zoom (High)', + 10 => 'Record Movie', + 11 => 'None', + }, + %infoD6, + },{ # CSg2-d (Z7_2) + Name => 'AssignMovieSubselector', + PrintConv => { + 1 => 'Center Focus Point', + 2 => 'AF Lock Only', + 3 => 'AE Lock (hold)', + 4 => 'AE Lock Only', + 5 => 'AE/AF Lock', + 6 => 'LiveView Info Display On/Off', + 7 => 'Grid Display', + 8 => 'Zoom (Low)', + 9 => 'Zoom (1:1)', + 10 => 'Zoom (High)', + 11 => 'Record Movie', + 12 => 'Image Area', + 13 => 'None', + }, + %infoZSeries, + }], + 0x0b8 => { Name => 'LimitAFAreaModeSelD9', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa14-a (D6) + 0x0b9 => { Name => 'LimitAFAreaModeSelD25', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa14-b (D6) + 0x0bc => { Name => 'LimitAFAreaModeSel3D', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa14-e (D6) + 0x0bd => { Name => 'LimitAFAreaModeSelGroup', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa14-f (D6) + 0x0be => { Name => 'LimitAFAreaModeSelAuto', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa14-i (D6), CSa7-g (Z7_2) + # 0x0bf => { Name => 'LimitSelectableImageArea30x20', PrintConv => \%limitNolimit }, # CSd8-1 (D6) further investigation & testing of CSd8-1 and CSd8-2 required. The other CSd8 tags are fine. + # 0x0c0 => { Name => 'LimitSelectableImageAreaDX', PrintConv => \%limitNolimit }, # CSd8-2 (D6) further investigation & testing of CSd8-1 and CSd8-2 required. The other CSd8 tags are fine. + 0x0c1 => { Name => 'LimitSelectableImageArea5To4', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd8-3 (D6) + 0x0c2 => { Name => 'LimitSelectableImageArea1To1', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd8-4 (D6) + # 0x0d3 => CSf3-g-2 (D6), CSf2-d-2 (Z7_2) SelectorPlaybackRole 'Scroll' and 'Display Next/Previous' (skipped to reduce volume of output) + 0x0d4 => { Name => 'PhotoShootingMenuBank', PrintConv => \%menuBank }, # (D6) + 0x0d5 => { Name => 'CustomSettingsBank', PrintConv => \%menuBank }, # (D6) + 0x0d6 => { Name => 'LimitAF-AreaModeSelPinpoint', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa7-a (Z7_2) + 0x0d7 => { Name => 'LimitAF-AreaModeSelDynamic', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa7-b (Z7_2) + 0x0d8 => { Name => 'LimitAF-AreaModeSelWideAF_S', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa7-c (Z7_2) + 0x0d9 => { Name => 'LimitAF-AreaModeSelWideAF_L', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa7-d (Z7_2) + 0x0da => { Name => 'LowLightAF', PrintConv => \%onOff }, # CSa10 (Z7_2) + 0x0db => { Name => 'LimitSelectableImageAreaDX', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd7-a (Z7_2) + 0x0dc => { Name => 'LimitSelectableImageArea5To4', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd7-b (Z7_2) + 0x0dd => { Name => 'LimitSelectableImageArea1To1', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd7-c (Z7_2) + 0x0de => { Name => 'LimitSelectableImageArea16To9', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd7-d (Z7_2) + 0x0df => { Name => 'ApplySettingsToLiveView', PrintConv => \%onOff }, # CSd9 # new tag with Z7_2 # (Z7_2) + 0x0e0 => { # CSd10-a (D6), CSd11-a (Z7_2) + Name => 'FocusPeakingLevel', + PrintConv => { + 1 => 'High Sensitivity', + 2 => 'Standard Sensitivity', + 3 => 'Low Sensitivity', + 4 => 'Off', + }, + }, + 0x0ea => { # CSf2-i #option for 'Focus (M/A)' is documented, but not available from camera menu + Name => 'LensControlRing', + PrintConv => { + 1 => 'Aperture', + 2 => 'Exposure Compensation', + 3 => 'ISO Sensitivity', + 4 => 'None (Disabled)', + }, + }, + # 0x0eb MovieLensControlRing ? # (Z7_2) + # 0x0ec # (Z7_2) + 0x0ed => [{ # CSg3 (D6) + Name => 'MovieMultiSelector', + PrintConv => { + 1 => 'Center Focus Point', + 2 => 'Zoom (Low)', + 3 => 'Zoom (1:1)', + 4 => 'Zoom (High)', + 5 => 'Record Movie', + 6 => 'None', + }, + %infoD6, + },{ # labels Ok button on camera # (Z7_2) + Name => 'MovieMultiSelector', + PrintConv => { # Z7 has codes for both OK and MultiSelector - although only the OK value can be changed. [The multiselector served as the OK button on these cameras]. + 1 => 'Center Focus Point', + 2 => 'Zoom (Low)', + 3 => 'Zoom (1:1)', + 4 => 'Zoom (High)', + 5 => 'Record Movie', + 6 => 'None', + }, + }], + 0x0ee => { Name => 'MovieAFSpeed', ValueConv => '$val - 6', ValueConvInv => '$val + 6' }, # CSg4-a (Z7_2) + 0x0ef => { # CSg4-b (Z7_2) + Name => 'MovieAFSpeedApply', + PrintConv => { + 1 => 'Always', + 2 => 'Only During Recording', + }, + }, + 0x0f0 => { # CSg5 (Z7_2) + Name => 'MovieAFTrackingSensitivity', + PrintConv => { + 1 => '1 (High)', + 2 => '2', + 3 => '3', + 4 => '4 (Normal)', + 5 => '5', + 6 => '6', + 7 => '7 (Low)', + }, + }, + 0x0f1 => { # CSg4-a (D6), CSg6-a (Z7_2) + Name => 'MovieHighlightDisplayPattern', + PrintConv => { + 1 => 'Pattern 1', + 2 => 'Pattern 2', + 3 => 'Off', + }, + }, + 0x0f2 => { Name => 'SubDialFrameAdvanceRating5', PrintConv => \%noYes, Unknown => 1 }, # (D6,Z7_2) + 0x0f3 => { Name => 'SubDialFrameAdvanceRating4', PrintConv => \%noYes, Unknown => 1 }, # (D6,Z7_2) + 0x0f4 => { Name => 'SubDialFrameAdvanceRating3', PrintConv => \%noYes, Unknown => 1 }, # (D6,Z7_2) + 0x0f5 => { Name => 'SubDialFrameAdvanceRating2', PrintConv => \%noYes, Unknown => 1 }, # (D6,Z7_2) + 0x0f6 => { Name => 'SubDialFrameAdvanceRating1', PrintConv => \%noYes, Unknown => 1 }, # (D6,Z7_2) + 0x0f7 => { Name => 'SubDialFrameAdvanceRating0', PrintConv => \%noYes, Unknown => 1 }, # (D6,Z7_2) + # 0x0f8 ?? looks like it should be part of the above, but nothing seems to cause the value to change. Possibly intended for Rating='Candidate for Deletion' # (D6) + 0x0f9 => { # CSg2-c (Z7_2) + Name => 'MovieAF-OnButton', + PrintConv => { + 1 => 'Center Focus Point', + 2 => 'AF-On', + 3 => 'AF Lock Only', + 4 => 'AE Lock (hold)', + 5 => 'AE Lock Only', + 6 => 'AE/AF Lock', + 7 => 'LiveView Info Display On/Off', + 8 => 'Zoom (Low)', + 9 => 'Zoom (1:1)', + 10 => 'Zoom (High)', + 11 => 'Record Movie', + 12 => 'None', + }, + }, + 0x0fb => { Name => 'SecondarySlotFunction', PrintConv => \%tagSecondarySlotFunction }, # tag name selected to maintain compatibility with older cameras # (Z7_2) + 0x0fb => { Name => 'SecondarySlotFunction', PrintConv => \%tagSecondarySlotFunction }, # (D6) + 0x0fc => { Name => 'SilentPhotography', PrintConv => \%onOff }, # (D6,Z7_2) # tag is associated with Silent LiveView Photography (as distinguisehed from Silent Interval or Silent Focus Shift) + 0x0fd => { Name => 'ExtendedShutterSpeeds', PrintConv => \%onOff }, # CSd7 (D6), CSd6 (Z7_2) + 0x102 => { # (Z7_2) + Name => 'HDMIBitDepth', + RawConv => '$$self{HDMIBitDepth} = $val', + PrintConv => { + 1 => '8 Bit', + 2 => '10 Bit', + #5 => 'Auto', #observed on the Z50 - needs confirmation + }, + }, + 0x103 => { # (Z7_2) + Name => 'HDMIOutputHDR', + Condition => '$$self{HDMIBitDepth} == 2', # HDR(HLC) output option only available only for 10 bit + RawConv => '$$self{HDMIOutputHDR} = $val', + PrintConv => { + 2 => 'On', # unusual decode perhaps due to sharing sub-menu with tag HDMIOutputN-Log? + 3 => 'Off', + }, + }, + 0x104 => { # valid for 10 bit with either N-Log or HDR/HLG selected CSg5 (Z7_2) + Name => 'HDMIViewAssist', + Condition => '$$self{HDMIBitDepth} == 2', + PrintConv => \%onOff + }, + 0x109 => { # (D6,Z7_2) + Name => 'BracketSet', + RawConv => '$$self{BracketSet} = $val', + PrintConv => { + 1 => 'AE/Flash', + 2 => 'AE', + 3 => 'Flash', + 4 => 'White Balance', + 5 => 'Active-D Lighting', + }, + }, + 0x10a => [{ # (D6/Z7_2) + Name => 'BracketProgram', + Condition => '$$self{BracketSet} < 4', + Notes => 'AE and/or Flash Bracketing', + RawConv => '$$self{BracketProgram} = $val', + PrintConv => { + 15 => '+3F', + 16 => '-3F', + 17 => '+2F', + 18 => '-2F', + 19 => 'Disabled', + 20 => '3F', + 21 => '5F', + 22 => '7F', + 23 => '9F', + }, + },{ + Name => 'BracketProgram', + Condition => '$$self{BracketSet} and $$self{BracketSet} == 4', + Notes => 'White Balance Bracketing', + RawConv => '$$self{BracketProgram} = $val', + PrintConv => { + 1 => 'B3F', + 2 => 'A3F', + 3 => 'B2F', + 4 => 'A2F', + 5 => 'Disabled', + 6 => '3F', + 7 => '5F', + 8 => '7F', + 9 => '9F', + 19 => 'N/A' # observed when shooting other than JPG + }, + },{ + Name => 'BracketProgram', + Condition => '$$self{BracketSet} and $$self{BracketSet} == 5', + Notes => 'Active-D Bracketing', + RawConv => '$$self{BracketProgram} = $val', + Mask => 0x0f, + PrintConv => { + 10 => 'Disabled', + 11 => '2 Exposures', + 12 => '3 Exposures', + 13 => '4 Exposures', + 14 => '5 Exposures', + }, + }], + 0x10b => [{ # (D6/Z7_2) + Name => 'BracketIncrement', + Condition => '$$self{BracketSet} < 4 and $$self{BracketProgram} ne 19', + Notes => 'AE and/or Flash Bracketing enabled', + PrintConv => { + 0x01 => '0.3', + 0x03 => '0.5', + 0x04 => '1.0', + 0x05 => '2.0', + 0x06 => '3.0', + }, + },{ + Name => 'BracketIncrement', + Condition => '$$self{BracketSet} == 4 and $$self{BracketProgram} ne 5', + Notes => 'White Balance Bracketing enabled', + PrintConv => '$val-6', # TODO: qualify amber/blue direction of increment (eg, '1A' vs.'1B' vs '1A,1B') + }], + 0x10c => { # (D6/Z7_2) + Name => 'BracketIncrement', + Condition => '$$self{BracketSet} == 5 and $$self{BracketProgram} ne 10', + Notes => 'Active-D Bracketing enabled', + PrintConv => { + 0 => 'Off', + 1 => 'Off, Low', + 2 => 'Off, Normal', + 3 => 'Off, High', + 4 => 'Off, Extra High', + 5 => 'Off, Auto', + 6 => 'Off, Low, Normal', + 7 => 'Off, Low, Normal, High', + 8 => 'Off, Low, Normal, High, Extra High', + }, + }, + 0x10e => { # (D6/Z7_2) + Name => 'MonitorBrightness', + # settings: -5 to +5 + ValueConv => '$val - 6', + }, + 0x116 => { Name => 'GroupAreaC1', PrintConv =>\%groupAreaCustom }, # CSa10-a (new with D6) # (D6) + 0x117 => { Name => 'AutoAreaAFStartingPoint', PrintConv => \%enableDisable }, # CSa12 (D6) + 0x118 => { Name => 'FocusPointPersistence', PrintConv => { 1 => 'Auto', 2 => 'Off' } }, # CSa13 (new with D6) # (D6) + 0x119 => { Name => 'LimitAFAreaModeSelD49', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa14-c (D6) + 0x11a => { Name => 'LimitAFAreaModeSelD105', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa14-d (D6) + 0x11b => { Name => 'LimitAFAreaModeSelGroupC1', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa14-g (D6) + 0x11c => { Name => 'LimitAFAreaModeSelGroupC2', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa14-h (D6) + 0x11d => { # CSa15 (D6) + Name => 'AutoFocusModeRestrictions', + PrintConv => { + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'No Limit', + }, + }, + 0x11e => { # CSa17-b (D6) + Name => 'FocusPointBrightness', + PrintConv => { + 1 => 'Extra High', + 2 => 'High', + 3 => 'Normal', + 4 => 'Low', + }, + }, + 0x11f => { Name => 'CHModeShootingSpeed', ValueConv => '15 - $val', ValueConvInv => '15 + $val', PrintConv => '"$val fps"', PrintConvInv => '$val=~s/\s*fps//i; $val' }, # CSd1-a (D6) + 0x120 => { Name => 'CLModeShootingSpeed', ValueConv => '11 - $val', ValueConvInv => '11 + $val', PrintConv => '"$val fps"', PrintConvInv => '$val=~s/\s*fps//i; $val' }, # CSd1-b (D6) + 0x121 => { # CSd1-c (new with D6) + Name => 'QuietShutterShootingSpeed', + PrintConv => { + 1 => 'Single', + 2 => '5 fps', + 3 => '4 fps', + 4 => '3 fps', + 5 => '2 fps', + 6 => '1 fps', + }, + }, + 0x122 => { Name => 'LimitReleaseModeSelCL', PrintConv => \%limtReleaseModeSel, Unknown => 1 }, # CSd3-a (D6) + 0x123 => { Name => 'LimitReleaseModeSelCH', PrintConv => \%limtReleaseModeSel, Unknown => 1 }, # CSd3-b (D6) + 0x124 => { Name => 'LimitReleaseModeSelQ', PrintConv => \%limtReleaseModeSel, Unknown => 1 }, # CSd3-c (D6) + 0x125 => { Name => 'LimitReleaseModeSelTimer', PrintConv => \%limtReleaseModeSel, Unknown => 1 }, # CSd3-d (D6) + 0x126 => { Name => 'LimitReleaseModeSelMirror-Up', PrintConv => \%limtReleaseModeSel, Unknown => 1 }, # CSd3-e (D6) + 0x127 => { Name => 'LimitSelectableImageArea16To9', PrintConv => \%limitNolimit, Unknown => 1 }, # CSd8-5 (D6) + 0x128 => { Name => 'RearControPanelDisplay', PrintConv => { 1 => 'Release Mode', 2 => 'Frame Count' } }, # CSd12 # new tag with D6 # (D6) + 0x129 => { Name => 'FlashBurstPriority', PrintConv => { 1 => 'Frame Rate', 2 => 'Exposure' } }, # CSE5 # new tag with D6 # (D6) + 0x12a => { Name => 'RecallShootFuncExposureMode', PrintConv => \%offOn }, # (D6) + 0x12b => { Name => 'RecallShootFuncShutterSpeed', PrintConv => \%offOn }, # (D6) + 0x12c => { Name => 'RecallShootFuncAperture', PrintConv => \%offOn }, # (D6) + 0x12d => { Name => 'RecallShootFuncExposureComp', PrintConv => \%offOn }, # (D6) + 0x12e => { Name => 'RecallShootFuncISO', PrintConv => \%offOn }, # (D6) + 0x12f => { Name => 'RecallShootFuncMeteringMode', PrintConv => \%offOn }, # (D6) + 0x130 => { Name => 'RecallShootFuncWhiteBalance', PrintConv => \%offOn }, # (D6) + 0x131 => { Name => 'RecallShootFuncAFAreaMode', PrintConv => \%offOn }, # (D6) + 0x132 => { Name => 'RecallShootFuncFocusTracking', PrintConv => \%offOn }, # (D6) + 0x133 => { Name => 'RecallShootFuncAF-On', PrintConv => \%offOn }, # (D6) + 0x134 => { # CSf3-d (D6) + Name => 'VerticalFuncButton', + PrintConv => { + 1 => 'Preset Focus Point', + 2 => 'AE Lock (hold)', + 3 => 'AE/WB Lock (hold)', + 4 => 'AE Lock (reset on release)', + 5 => 'FV Lock', + 6 => 'Preview', + 7 => '+NEF(RAW)', + 8 => 'Grid Display', + 9 => 'Virtual Horizon', + 10 => 'Voice Memo', + 11 => 'Playback', + 12 => 'Filtered Playback', + 13 => 'Photo Shooting Bank', + 14 => 'Exposure Mode', + 15 => 'Exposure Comp', + 16 => 'AF Mode/AF Area Mode', + 17 => 'Image Area', + 18 => 'ISO', + 19 => 'Active-D Lighting', + 20 => 'Metering', + 21 => 'Exposure Delay Mode', + 22 => 'Shutter/Aperture Lock', + 23 => '1 Stop Speed/Aperture', + 24 => 'Rating 0', + 25 => 'Rating 5', + 26 => 'Rating 4', + 27 => 'Rating 3', + 28 => 'Rating 2', + 29 => 'Rating 1', + 30 => 'Candidate For Deletion', + 31 => 'Non-CPU Lens', + 32 => 'None', + }, + }, + 0x135 => { # CSf3-e (D6) + Name => 'Func3Button', + PrintConv => { + 1 => 'Voice Memo', + 2 => 'Select To Send', + 3 => 'Wired LAN', + 4 => 'My Menu', + 5 => 'My Menu Top Item', + 6 => 'Filtered Playback', + 7 => 'Rating 0', + 8 => 'Rating 5', + 9 => 'Rating 4', + 10 => 'Rating 3', + 11 => 'Rating 2', + 12 => 'Rating 1', + 13 => 'Candidate For Deletion', + 14 => 'None', + }, + }, + 0x136 => { # CSf3-i (D6) + Name => 'VerticalAF-OnButton', + PrintConv => { + 1 => 'AF-AreaMode S', + 2 => 'AF-AreaMode D9', + 3 => 'AF-AreaMode D25', + 4 => 'AF-AreaMode D49', + 5 => 'AF-AreaMode D105', + 6 => 'AF-AreaMode 3D', + 7 => 'AF-AreaMode Group', + 8 => 'AF-AreaMode Group C1', + 9 => 'AF-AreaMode Group C2', + 10 => 'AF-AreaMode Auto Area', + 11 => 'AF-AreaMode + AF-On S', + 12 => 'AF-AreaMode + AF-On D9', + 13 => 'AF-AreaMode + AF-On D25', + 14 => 'AF-AreaMode + AF-On D49', + 15 => 'AF-AreaMode + AF-On D105', + 16 => 'AF-AreaMode + AF-On 3D', + 17 => 'AF-AreaMode + AF-On Group', + 18 => 'AF-AreaMode + AF-On Group C1', + 19 => 'AF-AreaMode + AF-On Group C2', + 20 => 'AF-AreaMode + AF-On Auto Area', + 21 => 'Same as AF-On', + 22 => 'AF-On', + 23 => 'AF Lock Only', + 24 => 'AE Lock (hold)', + 25 => 'AE/WB Lock (hold)', + 26 => 'AE Lock (reset on release)', + 27 => 'AE Lock Only', + 28 => 'AE/AF Lock', + 29 => 'Recall Shooting Functions', + 30 => 'None', + }, + }, + 0x137 => { Name => 'VerticalMultiSelector', PrintConv => \%tagSubSelector }, # CSf3-j-1 # (D6) + 0x138 => { # CSf3-k (D6) + Name => 'MeteringButton', + PrintConv => { + 1 => 'Photo Shooting Bank', + 2 => 'Image Area', + 3 => 'Active-D Lighting', + 4 => 'Metering', + 5 => 'Exposure Delay Mode', + 6 => 'Shutter/Aperture Lock', + 7 => '1 Stop Speed/Aperture', + 8 => 'Non-CPU Lens', + 9 => 'None', + }, + }, + 0x139 => { Name => 'PlaybackFlickUp', RawConv => '$$self{PlaybackFlickUp} = $val', PrintConv => \%flickUpDownD6 }, # CSf12-1-a # (D6) + 0x13a => { Name => 'PlaybackFlickUpRating', Condition => '$$self{PlaybackFlickUp} and $$self{PlaybackFlickUp} == 1', Notes => 'Meaningful only when PlaybackFlickUp is Rating', PrintConv => \%flickUpDownRatingD6 }, # CSf12-1-b # (D6) + 0x13b => { Name => 'PlaybackFlickDown', RawConv => '$$self{PlaybackFlickDown} = $val', PrintConv => \%flickUpDownD6 }, # CSf12-2-a # (D6) + 0x13c => { Name => 'PlaybackFlickDownRating', Condition => '$$self{PlaybackFlickDown} and $$self{PlaybackFlickDown} == 1', Notes => 'Meaningful only when PlaybackFlickDown is Rating', PrintConv => \%flickUpDownRatingD6 }, # CSf12-2-b # (D6) + 0x13d => { # CSg2-d (D6) + Name => 'MovieFunc3Button', + PrintConv => { + 1 => 'Record Movie', + 2 => 'My Menu', + 3 => 'My Menu Top Item', + 4 => 'None', + }, + }, + 0x150 => { # CSd5 (Z7_2) new with Z series + Name => 'ShutterType', + PrintConv => { + 1 => 'Auto', + 2 => 'Mechanical', + 3 => 'Electronic', + }, + }, + 0x151 => { Name => 'LensFunc2Button', PrintConv => \%lensFuncButtonZ7m2 }, # CSf2-h (Z7_2) + #0x153 => { Name => 'ViewfinderBrightness', } #(Z7_2) # commented out to reduce output volume. Range [-5,+5]. PrintConv matches MonitorBrightness. + 0x158 => { Name => 'USBPowerDelivery', PrintConv => \%enableDisable }, # (Z7_2) + 0x159 => { Name => 'EnergySavingMode', PrintConv =>\%onOff }, # (Z7_2) + 0x15c => { Name => 'BracketingBurstOptions',PrintConv => \%enableDisable }, # CSe9 (D6) + # 0x15d => CSf3-j-2 (D6) 'Same as Multi-Selector with Info(U/D) & Playback(R/L)' and 'Same as Multi-Selector with Info(R/L) & Playback(U/D)' (skipped to reduce volume of output) + 0x15e => { Name => 'PrimarySlot', PrintConv => { 1 => 'CFexpress/XQD Card', 2 => 'SD Card' } }, # (Z7_2) + 0x15f => { Name => 'ReverseFocusRing', PrintConv => { 1 => 'Not Reversed', 2 => 'Reversed' } }, # CSf8 (Z7_2) + 0x160 => { # CSf9-a (Z7_2) + Name => 'VerticalFuncButton', + PrintConv => { + 1 => 'AE Lock (hold)', + 2 => 'AE Lock (reset on release)', + 3 => 'FV Lock', + 4 => 'Preview', + 5 => '+NEF(RAW)', + 6 => 'Subject Tracking', + 7 => 'Silent Photography', + 8 => 'LiveView Info Display On/Off', + 9 => 'Playback', + 10 => 'Image Area', + 11 => 'Metering', + 12 => 'Flash Mode', + 13 => 'Focus Mode', + 14 => 'Exposure Delay Mode', + 15 => 'Shutter/Aperture Lock', + 16 => 'Exposure Compensation', + 17 => 'ISO Sensitivity', + 18 => 'None', + }, + }, + 0x161 => { # CSf9-b (Z7_2) + Name => 'VerticalAFOnButton', + PrintConv => { + 1 => 'Same as AF-On Button', + 2 => 'Select Center Focus Point', + 3 => 'AF-On', + 4 => 'AF Lock Only', + 5 => 'AE Lock (hold)', + 6 => 'AE Lock (reset on release)', + 7 => 'AE Lock Only', + 8 => 'AE/AF Lock', + 9 => 'LiveView Info Display On/Off', + 10 => 'Zoom (Low)', + 11 => 'Zoom (1:1)', + 12 => 'Zoom (High)', + 13 => 'None', + }, + }, + 0x162 => { Name => 'VerticalMultiSelector', PrintConv => \%tagSubSelector }, # CSf9-c (Z7_2) + # 0x163 => CSf9-c-2 (Z7_2) 'Same as Multi-Selector with Info(U/D) & Playback(R/L)' and 'Same as Multi-Selector with Info(R/L) & Playback(U/D)' (skipped to reduce volume of output) + 0x164 => { # CSg7-a (Z7_2) + Name => 'VerticalMovieFuncButton', + PrintConv => { + 1 => 'LiveView Info Display On/Off', + 2 => 'Record Movie', + 3 => 'Exposure Compensation', + 4 => 'ISO', + 5 => 'None', + }, + }, + 0x165 => { # CSg7-b (Z7_2) + Name => 'VerticalMovieAFOnButton', + PrintConv => { + 1 => 'Same as AF-On', + 2 => 'Center Focus Point', + 3 => 'AF-On', + 4 => 'AF Lock Only', + 5 => 'AE Lock (hold)', + 6 => 'AE Lock Only', + 7 => 'AE/AF Lock', + 8 => 'LiveView Info Display On/Off', + 9 => 'Zoom (Low)', + 10 => 'Zoom (1:1)', + 11 => 'Zoom (High)', + 12 => 'Record Movie', + 13 => 'None', + }, + }, + #0x168 => { Name => 'ControlPanelBrightness', # (Z7_2) #commented to reduce output volume + 0x169 => { Name => 'LimitAF-AreaModeSelAutoPeople', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa7-h (Z7_2) + 0x16a => { Name => 'LimitAF-AreaModeSelAutoAnimals', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa7-h (Z7_2) + 0x16b => { Name => 'LimitAF-AreaModeSelWideLPeople', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa7-e (Z7_2) + 0x16c => { Name => 'LimitAF-AreaModeSelWideLAnimals', PrintConv => \%limitNolimit, Unknown => 1 }, # CSa7-f (Z7_2) + 0x16d => { Name => 'SaveFocus', PrintConv => \%onOff }, # (Z7_2) + 0x16e => { # (Z7_2) + Name => 'AFAreaMode', + RawConv => '$$self{AFAreaMode} = $val', + PrintConv => { + 2 => 'Single-point', + 3 => 'Dynamic-area', + 4 => 'Wide (S)', + 5 => 'Wide (L)', + 6 => 'Wide (L-people)', + 7 => 'Wide (L-animals)', + 8 => 'Auto', + 9 => 'Auto (People)', + 10 => 'Auto (Animals)', + }, + }, + 0x16f => { # (Z7_2) + Name => 'MovieAFAreaMode', + PrintConv => { + 1 => 'Single-point', + 2 => 'Wide (S)', + 3 => 'Wide (L)', + 4 => 'Wide (L-people)', + 5 => 'Wide (L-animals)', + 6 => 'Auto', + 7 => 'Auto (People)', + 8 => 'Auto (Animals)', + }, + }, + 0x170 => { Name => 'PreferSubSelectorCenter', PrintConv => \%offOn }, # CSf13 (D6 firmware v1.2.0) + 0x171 => { # CSb8 (D6 firmware v1.3.0) + Name => 'KeepExposureWithTeleconverter', + PrintConv => { + 1 => 'Off', + 2 => 'Shutter Speed', + 3 => 'ISO', + }, + }, + 0x174 => { # CSa17-d (D6 firmware v1.2.0) + Name => 'FocusPointSelectionSpeed', + PrintConv => { + 1 => 'Normal', + 2 => 'High', + 3 => 'Very High', + }, + }, +); + +#------------------------------------------------------------------------------ +# Process NikonSettings directory (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessNikonSettings($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + + # NikonSettings directory has a 24-byte header: + # 0x00 - undef[4]: '0100' + # 0x04 - int32u: 1 (D-models), 2 (Z-models) + # 0x08 - undef[4]: '0100' + # 0x0c - int32u: 1 (Z7), 2 (D850), 3 (D780,Z50), 4 (D6,Z5), 5(Z7m2) + # 0x10 - undef[4]: firmware version (eg. '0110' for firmware 1.10) + # 0x14 - int32u: number of entries in directory + + return 0 if $$dirInfo{DirLen} < 24; # sanity check + + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart}; + my $num = Get32u($dataPt, $start + 0x14); + + $et->VerboseDir('NikonSettings', $num); + + my $n = int(($$dirInfo{DirLen} - 0x18) / 8); + if ($n < $num) { + $et->Warn('Missing '.($num-$n).' NikonSettings entries', 1); + $num = $n; + } elsif ($n > $num) { + $et->Warn('Unused space in NikonSettings directory', 1); + } + my $i; + for ($i=0; $i<$num; ++$i) { + my $entry = $start + 0x18 + $i * 8; + my $tag = Get16u($dataPt, $entry); + # this is odd, but either the format is 16-bit and always big-endian, + # or it is 8-bit and we have an unknown byte in the entry... + my $fmt = Get8u($dataPt, $entry + 3); + my $val = Get32u($dataPt, $entry + 4); + # abort if the tag has a format that we haven't yet seen + # (assuming this is a size/format code. So far we have only seen a code of 4) + $fmt == 4 or $et->Warn(sprintf('Unknown format $fmt for NikonSettings tag 0x%.4x',$tag)), last; + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Base => $$dirInfo{Base}, + Start => $entry + 4, + Size => 4, + Format => 'int32u', + Index => $i, + ); + } + return 1; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::NikonSettings - Read Nikon user settings + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains the definitions necessary to read the user settings for +Nikon cameras such as the D6 and Z7mk2. + + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Thanks to Warren Hatch for his work decoding settings for the D6 and Z7mkII, +and for providing the original source code for this module. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Nikon Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Nintendo.pm b/ExifTool/lib/Image/ExifTool/Nintendo.pm new file mode 100644 index 0000000..11a0c43 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Nintendo.pm @@ -0,0 +1,128 @@ +#------------------------------------------------------------------------------ +# File: Nintendo.pm +# +# Description: Nintendo EXIF maker notes tags +# +# Revisions: 2014/03/25 - P. Harvey Created +# +# References: 1) http://3dbrew.org/wiki/MPO +#------------------------------------------------------------------------------ + +package Image::ExifTool::Nintendo; + +use strict; +use vars qw($VERSION); +use Image::ExifTool::Exif; + +$VERSION = '1.00'; + +%Image::ExifTool::Nintendo::Main = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + # 0x1000 - undef[28] + # 0x1001 - undef[8] + # 0x1100 - undef[80] (found in MPO files) + 0x1101 => { + Name => 'CameraInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Nintendo::CameraInfo', + ByteOrder => 'Little-endian', + }, + }, +); + +# Nintendo MPO info (ref 1) +%Image::ExifTool::Nintendo::CameraInfo = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + PRIORITY => 0, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + 0x00 => { # "3DS1" + Name => 'ModelID', + Format => 'undef[4]', + }, + # 0x04 - int32u: 1,2,4,5 + 0x08 => { + Name => 'TimeStamp', + Format => 'int32u', + Groups => { 2 => 'Time' }, + Shift => 'Time', + # zero time is 2000/01/01 (10957 days after Unix time zero) + ValueConv => 'ConvertUnixTime($val + 10957 * 24 * 3600)', + ValueConvInv => 'GetUnixTime($val) - 10957 * 24 * 3600', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + # 0x10 - int32u: title ID low + # 0x14 - int32u: flags + 0x18 => { + Name => 'InternalSerialNumber', + Groups => { 2 => 'Camera' }, + Format => 'undef[4]', + ValueConv => '"0x" . unpack("H*",$val)', + ValueConvInv => '$val=~s/^0x//; pack("H*",$val)', + }, + 0x28 => { + Name => 'Parallax', + Format => 'float', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x30 => { + Name => 'Category', + Format => 'int16u', + PrintHex => 1, + PrintConv => { + 0x0000 => '(none)', + 0x1000 => 'Mii', + 0x2000 => 'Man', + 0x4000 => 'Woman', + }, + }, + # 0x32 - int16u: filter +); + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Nintendo - Nintendo 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 Nintendo maker notes EXIF meta information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://3dbrew.org/wiki/MPO> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Nintendo Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/OOXML.pm b/ExifTool/lib/Image/ExifTool/OOXML.pm new file mode 100644 index 0000000..347d968 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/OOXML.pm @@ -0,0 +1,410 @@ +#------------------------------------------------------------------------------ +# File: OOXML.pm +# +# Description: Read Office Open XML+ZIP files +# +# Revisions: 2009/10/31 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::OOXML; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::XMP; +use Image::ExifTool::ZIP; + +$VERSION = '1.08'; + +# test for recognized OOXML document extensions +my %isOOXML = ( + DOCX => 1, DOCM => 1, + DOTX => 1, DOTM => 1, + POTX => 1, POTM => 1, + PPAX => 1, PPAM => 1, + PPSX => 1, PPSM => 1, + PPTX => 1, PPTM => 1, THMX => 1, + XLAM => 1, + XLSX => 1, XLSM => 1, XLSB => 1, + XLTX => 1, XLTM => 1, +); + +# generate reverse lookup for file type based on MIME +my %fileType; +{ + my $type; + foreach $type (keys %isOOXML) { + $fileType{$Image::ExifTool::mimeType{$type}} = $type; + } +} + +# XML attributes to queue +my %queuedAttrs; +my %queueAttrs = ( + fmtid => 1, + pid => 1, + name => 1, +); + +# keep track of items in a vector (to accumulate as a list) +my $vectorCount; +my @vectorVals; + +# Office Open XML tags +%Image::ExifTool::OOXML::Main = ( + GROUPS => { 0 => 'XML', 1 => 'XML', 2 => 'Document' }, + PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP, + VARS => { NO_ID => 1 }, + NOTES => q{ + The Office Open XML (OOXML) format was introduced with Microsoft Office 2007 + and is used by file types such as DOCX, PPTX and XLSX. These are + essentially ZIP archives containing XML files. The table below lists some + tags which have been observed in OOXML documents, but ExifTool will extract + any tags found from XML files of the OOXML document properties ("docProps") + directory. + + B<Tips:> + + 1) Structural ZIP tags may be ignored (if desired) with C<--ZIP:all> on the + command line. + + 2) Tags may be grouped by their document number in the ZIP archive with the + C<-g3> or C<-G3> option. + }, + # These tags all have 1:1 correspondence with FlashPix tags except for: + # OOXML FlashPix + # --------------- ------------- + # DocSecurity Security + # Application Software + # dc:Description Comments + # dc:Creator Author + Application => { }, + AppVersion => { }, + category => { }, + Characters => { }, + CharactersWithSpaces => { }, + CheckedBy => { }, + Client => { }, + Company => { }, + created => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + Format => 'date', + PrintConv => '$self->ConvertDateTime($val)', + }, + createdType => { Hidden => 1, RawConv => 'undef' }, # ignore this XML type name + DateCompleted => { + Groups => { 2 => 'Time' }, + Format => 'date', + PrintConv => '$self->ConvertDateTime($val)', + }, + Department => { }, + Destination => { }, + Disposition => { }, + Division => { }, + DocSecurity => { + # (http://msdn.microsoft.com/en-us/library/documentformat.openxml.extendedproperties.documentsecurity.aspx) + PrintConv => { + 0 => 'None', + 1 => 'Password protected', + 2 => 'Read-only recommended', + 4 => 'Read-only enforced', + 8 => 'Locked for annotations', + }, + }, + DocumentNumber=> { }, + Editor => { Groups => { 2 => 'Author'} }, + ForwardTo => { }, + Group => { }, + HeadingPairs=> { }, + HiddenSlides=> { }, + HyperlinkBase=>{ }, + HyperlinksChanged => { PrintConv => { 'false' => 'No', 'true' => 'Yes' } }, + keywords => { }, + Language => { }, + lastModifiedBy => { Groups => { 2 => 'Author'} }, + lastPrinted => { + Groups => { 2 => 'Time' }, + Format => 'date', + PrintConv => '$self->ConvertDateTime($val)', + }, + Lines => { }, + LinksUpToDate=>{ PrintConv => { 'false' => 'No', 'true' => 'Yes' } }, + Mailstop => { }, + Manager => { }, + Matter => { }, + MMClips => { }, + modified => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + Format => 'date', + PrintConv => '$self->ConvertDateTime($val)', + }, + modifiedType=> { Hidden => 1, RawConv => 'undef' }, # ignore this XML type name + Notes => { }, + Office => { }, + Owner => { Groups => { 2 => 'Author'} }, + Pages => { }, + Paragraphs => { }, + PresentationFormat => { }, + Project => { }, + Publisher => { }, + Purpose => { }, + ReceivedFrom=> { }, + RecordedBy => { }, + RecordedDate=> { + Groups => { 2 => 'Time' }, + Format => 'date', + PrintConv => '$self->ConvertDateTime($val)', + }, + Reference => { }, + revision => { Name => 'RevisionNumber' }, + ScaleCrop => { PrintConv => { 'false' => 'No', 'true' => 'Yes' } }, + SharedDoc => { PrintConv => { 'false' => 'No', 'true' => 'Yes' } }, + Slides => { }, + Source => { }, + Status => { }, + TelephoneNumber => { }, + Template => { }, + TitlesOfParts=>{ }, + TotalTime => { + Name => 'TotalEditTime', + PrintConv => 'ConvertTimeSpan($val, 60)', + }, + Typist => { }, + Words => { }, +); + +#------------------------------------------------------------------------------ +# Generate a tag ID for this XML tag +# Inputs: 0) tag property name list ref +# Returns: tagID and outtermost interesting namespace (or '' if no namespace) +sub GetTagID($) +{ + my $props = shift; + my ($tag, $prop, $namespace); + foreach $prop (@$props) { + # split name into namespace and property name + # (Note: namespace can be '' for property qualifiers) + my ($ns, $nm) = ($prop =~ /(.*?):(.*)/) ? ($1, $2) : ('', $prop); + next if $ns eq 'vt'; # ignore 'vt' properties + if (defined $tag) { + $tag .= ucfirst($nm); # add to tag name + } elsif ($prop ne 'Properties' and $prop ne 'cp:coreProperties' and + $prop ne 'property') + { + $tag = $nm; + # save namespace of first property to contribute to tag name + $namespace = $ns unless $namespace; + } + } + return ($tag, $namespace || ''); +} + +#------------------------------------------------------------------------------ +# We found an XMP property name/value +# Inputs: 0) ExifTool object ref, 1) tag table ref +# 2) reference to array of XMP property names (last is current property) +# 3) property value, 4) attribute hash ref (not used here) +# Returns: 1 if valid tag was found +sub FoundTag($$$$;$) +{ + my ($et, $tagTablePtr, $props, $val, $attrs) = @_; + return 0 unless @$props; + my $verbose = $et->Options('Verbose'); + + my $tag = $$props[-1]; + $et->VPrint(0, " | - Tag '", join('/',@$props), "'\n") if $verbose > 1; + + # un-escape XML character entities + $val = Image::ExifTool::XMP::UnescapeXML($val); + # convert OOXML-escaped characters (eg. "_x0000d_" is a newline) + $val =~ s/_x([0-9a-f]{4})_/Image::ExifTool::PackUTF8(hex($1))/gie; + # convert from UTF8 to ExifTool Charset + $val = $et->Decode($val, 'UTF8'); + # queue this attribute for later if necessary + if ($queueAttrs{$tag}) { + $queuedAttrs{$tag} = $val; + return 0; + } + my $ns; + ($tag, $ns) = GetTagID($props); + if (not $tag) { + # all properties are in ignored namespaces + # so 'name' from our queued attributes for the tag + my $name = $queuedAttrs{name} or return 0; + $name =~ s/(^| )([a-z])/$1\U$2/g; # start words with uppercase + ($tag = $name) =~ tr/-_a-zA-Z0-9//dc; + return 0 unless length $tag; + unless ($$tagTablePtr{$tag}) { + my %tagInfo = ( + Name => $tag, + Description => $name, + ); + # format as a date/time value if type is 'vt:filetime' + if ($$props[-1] eq 'vt:filetime') { + $tagInfo{Groups} = { 2 => 'Time' }, + $tagInfo{Format} = 'date', + $tagInfo{PrintConv} = '$self->ConvertDateTime($val)'; + } + $et->VPrint(0, " | [adding $tag]\n") if $verbose; + AddTagToTable($tagTablePtr, $tag, \%tagInfo); + } + } elsif ($tag eq 'xmlns') { + # ignore namespaces (for now) + return 0; + } elsif (ref $Image::ExifTool::XMP::Main{$ns} eq 'HASH' and + $Image::ExifTool::XMP::Main{$ns}{SubDirectory}) + { + # use standard XMP table if it exists + my $table = $Image::ExifTool::XMP::Main{$ns}{SubDirectory}{TagTable}; + no strict 'refs'; + if ($table and %$table) { + $tagTablePtr = Image::ExifTool::GetTagTable($table); + } + } elsif (@$props > 2 and grep /^vt:vector$/, @$props) { + # handle vector properties (accumulate as lists) + if ($$props[-1] eq 'vt:size') { + $vectorCount = $val; + undef @vectorVals; + return 0; + } elsif ($$props[-1] eq 'vt:baseType') { + return 0; # ignore baseType + } elsif ($vectorCount) { + --$vectorCount; + if ($vectorCount) { + push @vectorVals, $val; + return 0; + } + $val = [ @vectorVals, $val ] if @vectorVals; + # Note: we will lose any improper-sized vector elements here + } + } + # add any unknown tags to table + if ($$tagTablePtr{$tag}) { + my $tagInfo = $$tagTablePtr{$tag}; + if (ref $tagInfo eq 'HASH') { + # reformat date/time values + my $fmt = $$tagInfo{Format} || $$tagInfo{Writable} || ''; + $val = Image::ExifTool::XMP::ConvertXMPDate($val) if $fmt eq 'date'; + } + } else { + $et->VPrint(0, " [adding $tag]\n") if $verbose; + AddTagToTable($tagTablePtr, $tag, { Name => ucfirst $tag }); + } + # save the tag + $et->HandleTag($tagTablePtr, $tag, $val); + + # start fresh for next tag + undef $vectorCount; + undef %queuedAttrs; + + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from an OOXML file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 +# Notes: Upon entry to this routine, the file type has already been verified +# and the dirInfo hash contains 2 elements unique to this process proc: +# MIME - mime type of main document from "[Content_Types].xml" +# ZIP - reference to Archive::Zip object for this file +sub ProcessDOCX($$) +{ + my ($et, $dirInfo) = @_; + my $zip = $$dirInfo{ZIP}; + my $tagTablePtr = GetTagTable('Image::ExifTool::OOXML::Main'); + my $mime = $$dirInfo{MIME} || $Image::ExifTool::mimeType{DOCX}; + + # set the file type ('DOCX' by default) + my $fileType = $fileType{$mime}; + if ($fileType) { + # THMX is a special case because its contents.main MIME types is PPTX + if ($fileType eq 'PPTX' and $$et{FILE_EXT} and $$et{FILE_EXT} eq 'THMX') { + $fileType = 'THMX'; + } + } else { + $et->VPrint(0, "Unrecognized MIME type: $mime\n"); + # get MIME type according to file extension + $fileType = $$et{FILE_EXT}; + # default to 'DOCX' if this isn't a known OOXML extension + $fileType = 'DOCX' unless $fileType and $isOOXML{$fileType}; + } + $et->SetFileType($fileType); + + # must catch all Archive::Zip warnings + local $SIG{'__WARN__'} = \&Image::ExifTool::ZIP::WarnProc; + # extract meta information from all files in ZIP "docProps" directory + my $docNum = 0; + my @members = $zip->members(); + my $member; + foreach $member (@members) { + # get filename of this ZIP member + my $file = $member->fileName(); + next unless defined $file; + $et->VPrint(0, "File: $file\n"); + # set the document number and extract ZIP tags + $$et{DOC_NUM} = ++$docNum; + Image::ExifTool::ZIP::HandleMember($et, $member); + # process only XML and JPEG/WMF thumbnail images in "docProps" directory + next unless $file =~ m{^docProps/(.*\.xml|(thumbnail\.(jpe?g|wmf)))$}i; + # get the file contents (CAREFUL! $buff MUST be local since we hand off a value ref) + my ($buff, $status) = $zip->contents($member); + $status and $et->Warn("Error extracting $file"), next; + # extract docProps/thumbnail.(jpg|mwf) as PreviewImage|PreviewMWF + if ($file =~ /\.(jpe?g|wmf)$/i) { + my $tag = $file =~ /\.wmf$/i ? 'PreviewWMF' : 'PreviewImage'; + $et->FoundTag($tag, \$buff); + next; + } + # process XML files (docProps/app.xml, docProps/core.xml, docProps/custom.xml) + my %dirInfo = ( + DataPt => \$buff, + DirLen => length $buff, + DataLen => length $buff, + XMPParseOpts => { + FoundProc => \&FoundTag, + }, + ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + undef $buff; # (free memory now) + } + delete $$et{DOC_NUM}; + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::OOXML - Read Office Open XML+ZIP files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Office Open XML files. This is the format of Word, Excel +and PowerPoint files written by Microsoft Office 2007 -- essentially ZIP +archives of XML files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/OOXML Tags>, +L<Image::ExifTool::TagNames/FlashPix Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Ogg.pm b/ExifTool/lib/Image/ExifTool/Ogg.pm new file mode 100644 index 0000000..6cfbe94 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Ogg.pm @@ -0,0 +1,240 @@ +#------------------------------------------------------------------------------ +# File: Ogg.pm +# +# Description: Read Ogg meta information +# +# Revisions: 2011/07/13 - P. Harvey Created (split from Vorbis.pm) +# 2016/07/14 - PH Added Ogg Opus support +# +# References: 1) http://www.xiph.org/vorbis/doc/ +# 2) http://flac.sourceforge.net/ogg_mapping.html +# 3) http://www.theora.org/doc/Theora.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::Ogg; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.02'; + +my $MAX_PACKETS = 2; # maximum packets to scan from each stream at start of file + +# Information types recognizedi in Ogg files +%Image::ExifTool::Ogg::Main = ( + NOTES => q{ + ExifTool extracts the following types of information from Ogg files. See + L<http://www.xiph.org/vorbis/doc/> for the Ogg specification. + }, + # (these are for documentation purposes only, and aren't used by the code below) + vorbis => { SubDirectory => { TagTable => 'Image::ExifTool::Vorbis::Main' } }, + theora => { SubDirectory => { TagTable => 'Image::ExifTool::Theora::Main' } }, + Opus => { SubDirectory => { TagTable => 'Image::ExifTool::Opus::Main' } }, + FLAC => { SubDirectory => { TagTable => 'Image::ExifTool::FLAC::Main' } }, + ID3 => { SubDirectory => { TagTable => 'Image::ExifTool::ID3::Main' } }, +); + +#------------------------------------------------------------------------------ +# Process Ogg packet +# Inputs: 0) ExifTool object ref, 1) data ref +# Returns: 1 on success +sub ProcessPacket($$) +{ + my ($et, $dataPt) = @_; + my $rtnVal = 0; + if ($$dataPt =~ /^(.)(vorbis|theora)/s or $$dataPt =~ /^(OpusHead|OpusTags)/) { + my ($tag, $type, $pos) = $2 ? (ord($1), ucfirst($2), 7) : ($1, 'Opus', 8); + # this is an OGV file if it contains Theora video + $et->OverrideFileType('OGV') if $type eq 'Theora' and $$et{FILE_TYPE} eq 'OGG'; + $et->OverrideFileType('OPUS') if $type eq 'Opus' and $$et{FILE_TYPE} eq 'OGG'; + my $tagTablePtr = GetTagTable("Image::ExifTool::${type}::Main"); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + return 0 unless $tagInfo and $$tagInfo{SubDirectory}; + my $subdir = $$tagInfo{SubDirectory}; + my %dirInfo = ( + DataPt => $dataPt, + DirName => $$tagInfo{Name}, + DirStart => $pos, + ); + my $table = GetTagTable($$subdir{TagTable}); + # set group1 so Theoris comments can be distinguised from Vorbis comments + $$et{SET_GROUP1} = $type if $type eq 'Theora'; + SetByteOrder($$subdir{ByteOrder}) if $$subdir{ByteOrder}; + $rtnVal = $et->ProcessDirectory(\%dirInfo, $table); + SetByteOrder('II'); + delete $$et{SET_GROUP1}; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Extract information from an Ogg file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid Ogg file +sub ProcessOGG($$) +{ + my ($et, $dirInfo) = @_; + + # must first check for leading/trailing ID3 information + unless ($$et{DoneID3}) { + require Image::ExifTool::ID3; + Image::ExifTool::ID3::ProcessID3($et, $dirInfo) and return 1; + } + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my ($success, $page, $packets, $streams, $stream) = (0,0,0,0,''); + my ($buff, $flag, %val, $numFlac, %streamPage); + + for (;;) { + # must read ahead to next page to see if it is a continuation + # (this code would be a lot simpler if the continuation flag + # was on the leading instead of the trailing page!) + if ($raf and $raf->Read($buff, 28) == 28) { + # validate magic number + unless ($buff =~ /^OggS/) { + $success and $et->Warn('Lost synchronization'); + last; + } + unless ($success) { + # set file type and initialize on first page + $success = 1; + $et->SetFileType(); + SetByteOrder('II'); + } + $flag = Get8u(\$buff, 5); # page flag + $stream = Get32u(\$buff, 14); # stream serial number + if ($flag & 0x02) { + ++$streams; # count start-of-stream pages + $streamPage{$stream} = $page = 0; + } else { + $page = $streamPage{$stream}; + } + ++$packets unless $flag & 0x01; # keep track of packet count + } else { + # all done unless we have to process our last packet + last unless %val; + ($stream) = sort keys %val; # take a stream + $flag = 0; # no continuation + undef $raf; # flag for done reading + } + + if (defined $numFlac) { + # stop to process FLAC headers if we hit the end of file + last unless $raf; + --$numFlac; # one less header packet to read + } else { + # can finally process previous packet from this stream + # unless this is a continuation page + if (defined $val{$stream} and not $flag & 0x01) { + ProcessPacket($et, \$val{$stream}); + delete $val{$stream}; + # only read the first $MAX_PACKETS packets from each stream + if ($packets > $MAX_PACKETS * $streams or not defined $raf) { + last unless %val; # all done (success!) + } + } + # stop processing Ogg if we have scanned enough packets + last if $packets > $MAX_PACKETS * $streams and not %val; + } + + # continue processing the current page + my $pageNum = Get32u(\$buff, 18); # page sequence number + my $nseg = Get8u(\$buff, 26); # number of segments + # calculate total data length + my $dataLen = Get8u(\$buff, 27); + if ($nseg) { + $raf->Read($buff, $nseg-1) == $nseg-1 or last; + my @segs = unpack('C*', $buff); + # could check that all these (but the last) are 255... + foreach (@segs) { $dataLen += $_ } + } + if (defined $page) { + if ($page == $pageNum) { + $streamPage{$stream} = ++$page; + } else { + $et->Warn('Missing page(s) in Ogg file'); + undef $page; + delete $streamPage{$stream}; + } + } + # read page data + $raf->Read($buff, $dataLen) == $dataLen or last; + if ($verbose > 1) { + printf $out "Page %d, stream 0x%x, flag 0x%x (%d bytes)\n", + $pageNum, $stream, $flag, $dataLen; + $et->VerboseDump(\$buff, DataPos => $raf->Tell() - $dataLen); + } + if (defined $val{$stream}) { + $val{$stream} .= $buff; # add this continuation page + } elsif (not $flag & 0x01) { # ignore remaining pages of a continued packet + # ignore the first page of any packet we aren't parsing + if ($buff =~ /^(.(vorbis|theora)|Opus(Head|Tags))/s) { + $val{$stream} = $buff; # save this page + } elsif ($buff =~ /^\x7fFLAC..(..)/s) { + $numFlac = unpack('n',$1); + $val{$stream} = substr($buff, 9); + } + } + if (defined $numFlac) { + # stop to process FLAC headers if we have them all + last if $numFlac <= 0; + } elsif (defined $val{$stream} and $flag & 0x04) { + # process Ogg packet now if end-of-stream bit is set + ProcessPacket($et, \$val{$stream}); + delete $val{$stream}; + } + } + if (defined $numFlac and defined $val{$stream}) { + # process FLAC headers as if it was a complete FLAC file + require Image::ExifTool::FLAC; + my %dirInfo = ( RAF => new File::RandomAccess(\$val{$stream}) ); + Image::ExifTool::FLAC::ProcessFLAC($et, \%dirInfo); + } + return $success; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Ogg - Read Ogg meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Ogg bitstream container files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.xiph.org/vorbis/doc/> + +=item L<http://flac.sourceforge.net/ogg_mapping.html> + +=item L<http://www.theora.org/doc/Theora.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Ogg Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Olympus.pm b/ExifTool/lib/Image/ExifTool/Olympus.pm new file mode 100644 index 0000000..19565f3 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Olympus.pm @@ -0,0 +1,4209 @@ +#------------------------------------------------------------------------------ +# File: Olympus.pm +# +# Description: Olympus/Epson EXIF maker notes tags +# +# Revisions: 12/09/2003 - P. Harvey Created +# 11/11/2004 - P. Harvey Added Epson support +# +# References: 1) http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html +# 2) http://www.cybercom.net/~dcoffin/dcraw/ +# 3) http://www.ozhiker.com/electronics/pjmt/jpeg_info/olympus_mn.html +# 4) Markku Hanninen private communication (tests with E-1) +# 5) Remi Guyomarch from http://forums.dpreview.com/forums/read.asp?forum=1022&message=12790396 +# 6) Frank Ledwon private communication (tests with E/C-series cameras) +# 7) Michael Meissner private communication +# 8) Shingo Noguchi, PhotoXP (http://www.daifukuya.com/photoxp/) +# 9) Mark Dapoz private communication +# 10) Lilo Huang private communication (E-330) +# 11) http://olypedia.de/Olympus_Makernotes (May 30, 2013) +# 12) Ioannis Panagiotopoulos private communication (E-510) +# 13) Chris Shaw private communication (E-3) +# 14) Viktor Lushnikov private communication (E-400) +# 15) Yrjo Rauste private communication (E-30) +# 16) Godfrey DiGiorgi private communication (E-P1) + http://forums.dpreview.com/forums/read.asp?message=33187567 +# 17) Martin Hibers private communication +# 18) Tomasz Kawecki private communication +# 19) Brad Grier private communication +# 22) Herbert Kauer private communication +# 23) Daniel Pollock private communication (PEN-F) +# 24) Sebastian private communication (E-M1 Mark III) +# IB) Iliah Borg private communication (LibRaw) +# NJ) Niels Kristian Bech Jensen private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::Olympus; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::APP12; + +$VERSION = '2.81'; + +sub PrintLensInfo($$$); + +my %offOn = ( 0 => 'Off', 1 => 'On' ); + +# lookup for Olympus LensType values +# (as of ExifTool 9.15, this was the complete list of chipped lenses at www.four-thirds.org) +my %olympusLensTypes = ( + Notes => q{ + The numerical values below are given in hexadecimal. (Prior to ExifTool + 9.15 these were in decimal.) + }, + '0 00 00' => 'None', + # Olympus lenses (also Kenko Tokina) + '0 01 00' => 'Olympus Zuiko Digital ED 50mm F2.0 Macro', + '0 01 01' => 'Olympus Zuiko Digital 40-150mm F3.5-4.5', #8 + '0 01 10' => 'Olympus M.Zuiko Digital ED 14-42mm F3.5-5.6', #PH (E-P1 pre-production) + '0 02 00' => 'Olympus Zuiko Digital ED 150mm F2.0', + '0 02 10' => 'Olympus M.Zuiko Digital 17mm F2.8 Pancake', #PH (E-P1 pre-production) + '0 03 00' => 'Olympus Zuiko Digital ED 300mm F2.8', + '0 03 10' => 'Olympus M.Zuiko Digital ED 14-150mm F4.0-5.6 [II]', #11 (The second version of this lens seems to have the same lens ID number as the first version #NJ) + '0 04 10' => 'Olympus M.Zuiko Digital ED 9-18mm F4.0-5.6', #11 + '0 05 00' => 'Olympus Zuiko Digital 14-54mm F2.8-3.5', + '0 05 01' => 'Olympus Zuiko Digital Pro ED 90-250mm F2.8', #9 + '0 05 10' => 'Olympus M.Zuiko Digital ED 14-42mm F3.5-5.6 L', #11 (E-PL1) + '0 06 00' => 'Olympus Zuiko Digital ED 50-200mm F2.8-3.5', + '0 06 01' => 'Olympus Zuiko Digital ED 8mm F3.5 Fisheye', #9 + '0 06 10' => 'Olympus M.Zuiko Digital ED 40-150mm F4.0-5.6', #PH + '0 07 00' => 'Olympus Zuiko Digital 11-22mm F2.8-3.5', + '0 07 01' => 'Olympus Zuiko Digital 18-180mm F3.5-6.3', #6 + '0 07 10' => 'Olympus M.Zuiko Digital ED 12mm F2.0', #PH + '0 08 01' => 'Olympus Zuiko Digital 70-300mm F4.0-5.6', #7 (seen as release 1 - PH) + '0 08 10' => 'Olympus M.Zuiko Digital ED 75-300mm F4.8-6.7', #PH + '0 09 10' => 'Olympus M.Zuiko Digital 14-42mm F3.5-5.6 II', #PH (E-PL2) + '0 10 01' => 'Kenko Tokina Reflex 300mm F6.3 MF Macro', #NJ + '0 10 10' => 'Olympus M.Zuiko Digital ED 12-50mm F3.5-6.3 EZ', #PH + '0 11 10' => 'Olympus M.Zuiko Digital 45mm F1.8', #17 + '0 12 10' => 'Olympus M.Zuiko Digital ED 60mm F2.8 Macro', #NJ + '0 13 10' => 'Olympus M.Zuiko Digital 14-42mm F3.5-5.6 II R', #PH/NJ + '0 14 10' => 'Olympus M.Zuiko Digital ED 40-150mm F4.0-5.6 R', #19 + # '0 14 10.1' => 'Olympus M.Zuiko Digital ED 14-150mm F4.0-5.6 II', #11 (questionable & unconfirmed -- all samples I can find are '0 3 10' - PH) + '0 15 00' => 'Olympus Zuiko Digital ED 7-14mm F4.0', + '0 15 10' => 'Olympus M.Zuiko Digital ED 75mm F1.8', #PH + '0 16 10' => 'Olympus M.Zuiko Digital 17mm F1.8', #NJ + '0 17 00' => 'Olympus Zuiko Digital Pro ED 35-100mm F2.0', #7 + '0 18 00' => 'Olympus Zuiko Digital 14-45mm F3.5-5.6', + '0 18 10' => 'Olympus M.Zuiko Digital ED 75-300mm F4.8-6.7 II', #NJ + '0 19 10' => 'Olympus M.Zuiko Digital ED 12-40mm F2.8 Pro', #PH + '0 20 00' => 'Olympus Zuiko Digital 35mm F3.5 Macro', #9 + '0 20 10' => 'Olympus M.Zuiko Digital ED 40-150mm F2.8 Pro', #NJ + '0 21 10' => 'Olympus M.Zuiko Digital ED 14-42mm F3.5-5.6 EZ', #NJ + '0 22 00' => 'Olympus Zuiko Digital 17.5-45mm F3.5-5.6', #9 + '0 22 10' => 'Olympus M.Zuiko Digital 25mm F1.8', #NJ + '0 23 00' => 'Olympus Zuiko Digital ED 14-42mm F3.5-5.6', #PH + '0 23 10' => 'Olympus M.Zuiko Digital ED 7-14mm F2.8 Pro', #NJ + '0 24 00' => 'Olympus Zuiko Digital ED 40-150mm F4.0-5.6', #PH + '0 24 10' => 'Olympus M.Zuiko Digital ED 300mm F4.0 IS Pro', #NJ + '0 25 10' => 'Olympus M.Zuiko Digital ED 8mm F1.8 Fisheye Pro', #NJ + '0 26 10' => 'Olympus M.Zuiko Digital ED 12-100mm F4.0 IS Pro', #IB/NJ + '0 27 10' => 'Olympus M.Zuiko Digital ED 30mm F3.5 Macro', #IB/NJ + '0 28 10' => 'Olympus M.Zuiko Digital ED 25mm F1.2 Pro', #IB/NJ + '0 29 10' => 'Olympus M.Zuiko Digital ED 17mm F1.2 Pro', #IB + '0 30 00' => 'Olympus Zuiko Digital ED 50-200mm F2.8-3.5 SWD', #7 + '0 30 10' => 'Olympus M.Zuiko Digital ED 45mm F1.2 Pro', #IB + '0 31 00' => 'Olympus Zuiko Digital ED 12-60mm F2.8-4.0 SWD', #7 + '0 32 00' => 'Olympus Zuiko Digital ED 14-35mm F2.0 SWD', #PH + '0 32 10' => 'Olympus M.Zuiko Digital ED 12-200mm F3.5-6.3', #IB + '0 33 00' => 'Olympus Zuiko Digital 25mm F2.8', #PH + '0 33 10' => 'Olympus M.Zuiko Digital 150-400mm F4.5 TC1.25x IS Pro', #IB + '0 34 00' => 'Olympus Zuiko Digital ED 9-18mm F4.0-5.6', #7 + '0 34 10' => 'Olympus M.Zuiko Digital ED 12-45mm F4.0 Pro', #IB + '0 35 00' => 'Olympus Zuiko Digital 14-54mm F2.8-3.5 II', #PH + '0 35 10' => 'Olympus M.Zuiko 100-400mm F5.0-6.3', #IB + '0 36 10' => 'Olympus M.Zuiko Digital ED 8-25mm F4 Pro', #IB + '0 37 10' => 'Olympus M.Zuiko Digital ED 40-150mm F4.0 Pro', #forum3833 + '0 39 10' => 'Olympus M.Zuiko Digital ED 90mm F3.5 Macro IS Pro', #forum3833 + # Sigma lenses + '1 01 00' => 'Sigma 18-50mm F3.5-5.6 DC', #8 + '1 01 10' => 'Sigma 30mm F2.8 EX DN', #NJ + '1 02 00' => 'Sigma 55-200mm F4.0-5.6 DC', + '1 02 10' => 'Sigma 19mm F2.8 EX DN', #NJ + '1 03 00' => 'Sigma 18-125mm F3.5-5.6 DC', + '1 03 10' => 'Sigma 30mm F2.8 DN | A', #NJ + '1 04 00' => 'Sigma 18-125mm F3.5-5.6 DC', #7 + '1 04 10' => 'Sigma 19mm F2.8 DN | A', #NJ + '1 05 00' => 'Sigma 30mm F1.4 EX DC HSM', #10 + '1 05 10' => 'Sigma 60mm F2.8 DN | A', #NJ + '1 06 00' => 'Sigma APO 50-500mm F4.0-6.3 EX DG HSM', #6 + '1 06 10' => 'Sigma 30mm F1.4 DC DN | C', #NJ + '1 07 00' => 'Sigma Macro 105mm F2.8 EX DG', #PH + '1 07 10' => 'Sigma 16mm F1.4 DC DN | C (017)', #IB + '1 08 00' => 'Sigma APO Macro 150mm F2.8 EX DG HSM', #PH + '1 09 00' => 'Sigma 18-50mm F2.8 EX DC Macro', #NJ + '1 10 00' => 'Sigma 24mm F1.8 EX DG Aspherical Macro', #PH + '1 11 00' => 'Sigma APO 135-400mm F4.5-5.6 DG', #11 + '1 12 00' => 'Sigma APO 300-800mm F5.6 EX DG HSM', #11 + '1 13 00' => 'Sigma 30mm F1.4 EX DC HSM', #11 + '1 14 00' => 'Sigma APO 50-500mm F4.0-6.3 EX DG HSM', #11 + '1 15 00' => 'Sigma 10-20mm F4.0-5.6 EX DC HSM', #11 + '1 16 00' => 'Sigma APO 70-200mm F2.8 II EX DG Macro HSM', #11 + '1 17 00' => 'Sigma 50mm F1.4 EX DG HSM', #11 + # Panasonic/Leica lenses + '2 01 00' => 'Leica D Vario Elmarit 14-50mm F2.8-3.5 Asph.', #11 + '2 01 10' => 'Lumix G Vario 14-45mm F3.5-5.6 Asph. Mega OIS', #16 + '2 02 00' => 'Leica D Summilux 25mm F1.4 Asph.', #11 + '2 02 10' => 'Lumix G Vario 45-200mm F4.0-5.6 Mega OIS', #16 + '2 03 00' => 'Leica D Vario Elmar 14-50mm F3.8-5.6 Asph. Mega OIS', #11 + '2 03 01' => 'Leica D Vario Elmar 14-50mm F3.8-5.6 Asph.', #14 (L10 kit) + '2 03 10' => 'Lumix G Vario HD 14-140mm F4.0-5.8 Asph. Mega OIS', #16 + '2 04 00' => 'Leica D Vario Elmar 14-150mm F3.5-5.6', #13 + '2 04 10' => 'Lumix G Vario 7-14mm F4.0 Asph.', #PH (E-P1 pre-production) + '2 05 10' => 'Lumix G 20mm F1.7 Asph.', #16 + '2 06 10' => 'Leica DG Macro-Elmarit 45mm F2.8 Asph. Mega OIS', #PH + '2 07 10' => 'Lumix G Vario 14-42mm F3.5-5.6 Asph. Mega OIS', #NJ + '2 08 10' => 'Lumix G Fisheye 8mm F3.5', #PH + '2 09 10' => 'Lumix G Vario 100-300mm F4.0-5.6 Mega OIS', #11 + '2 10 10' => 'Lumix G 14mm F2.5 Asph.', #17 + '2 11 10' => 'Lumix G 12.5mm F12 3D', #NJ (H-FT012) + '2 12 10' => 'Leica DG Summilux 25mm F1.4 Asph.', #NJ + '2 13 10' => 'Lumix G X Vario PZ 45-175mm F4.0-5.6 Asph. Power OIS', #NJ + '2 14 10' => 'Lumix G X Vario PZ 14-42mm F3.5-5.6 Asph. Power OIS', #NJ + '2 15 10' => 'Lumix G X Vario 12-35mm F2.8 Asph. Power OIS', #PH + '2 16 10' => 'Lumix G Vario 45-150mm F4.0-5.6 Asph. Mega OIS', #NJ + '2 17 10' => 'Lumix G X Vario 35-100mm F2.8 Power OIS', #PH + '2 18 10' => 'Lumix G Vario 14-42mm F3.5-5.6 II Asph. Mega OIS', #NJ + '2 19 10' => 'Lumix G Vario 14-140mm F3.5-5.6 Asph. Power OIS', #NJ + '2 20 10' => 'Lumix G Vario 12-32mm F3.5-5.6 Asph. Mega OIS', #NJ + '2 21 10' => 'Leica DG Nocticron 42.5mm F1.2 Asph. Power OIS', #NJ + '2 22 10' => 'Leica DG Summilux 15mm F1.7 Asph.', #NJ + '2 23 10' => 'Lumix G Vario 35-100mm F4.0-5.6 Asph. Mega OIS', #NJ + '2 24 10' => 'Lumix G Macro 30mm F2.8 Asph. Mega OIS', #NJ + '2 25 10' => 'Lumix G 42.5mm F1.7 Asph. Power OIS', #NJ + '2 26 10' => 'Lumix G 25mm F1.7 Asph.', #NJ + '2 27 10' => 'Leica DG Vario-Elmar 100-400mm F4.0-6.3 Asph. Power OIS', #NJ + '2 28 10' => 'Lumix G Vario 12-60mm F3.5-5.6 Asph. Power OIS', #NJ + '2 29 10' => 'Leica DG Summilux 12mm F1.4 Asph.', #IB + '2 30 10' => 'Leica DG Vario-Elmarit 12-60mm F2.8-4 Asph. Power OIS', #IB + '2 31 10' => 'Lumix G Vario 45-200mm F4.0-5.6 II', #forum3833 + '2 32 10' => 'Lumix G Vario 100-300mm F4.0-5.6 II', #PH + '2 33 10' => 'Lumix G X Vario 12-35mm F2.8 II Asph. Power OIS', #IB + '2 34 10' => 'Lumix G Vario 35-100mm F2.8 II', #forum3833 + '2 35 10' => 'Leica DG Vario-Elmarit 8-18mm F2.8-4 Asph.', #IB + '2 36 10' => 'Leica DG Elmarit 200mm F2.8 Power OIS', #IB + '2 37 10' => 'Leica DG Vario-Elmarit 50-200mm F2.8-4 Asph. Power OIS', #IB + '2 38 10' => 'Leica DG Vario-Summilux 10-25mm F1.7 Asph.', #IB + '2 40 10' => 'Leica DG Vario-Summilux 25-50mm F1.7 Asph.', #IB (H-X2550) + '3 01 00' => 'Leica D Vario Elmarit 14-50mm F2.8-3.5 Asph.', #11 + '3 02 00' => 'Leica D Summilux 25mm F1.4 Asph.', #11 + # Tamron lenses + '5 01 10' => 'Tamron 14-150mm F3.5-5.8 Di III', #NJ (model C001) + # '65535 07 40' - Seen for LUMIX S 16-35/F4 on Panasonic DC-S1H (ref PH) + # Other makes + '24 01 10' => 'Venus Optics Laowa 50mm F2.8 2x Macro', #DonKomarechka +); + +# lookup for Olympus camera types (ref PH) +my %olympusCameraTypes = ( + Notes => q{ + These values are currently decoded only for Olympus models. Models with + Olympus-style maker notes from other brands such as Acer, BenQ, Hitachi, HP, + Premier, Konica-Minolta, Maginon, Ricoh, Rollei, SeaLife, Sony, Supra, + Vivitar are not listed. + }, + D4028 => 'X-2,C-50Z', + D4029 => 'E-20,E-20N,E-20P', + D4034 => 'C720UZ', + D4040 => 'E-1', + D4041 => 'E-300', + D4083 => 'C2Z,D520Z,C220Z', + D4106 => 'u20D,S400D,u400D', + D4120 => 'X-1', + D4122 => 'u10D,S300D,u300D', + D4125 => 'AZ-1', + D4141 => 'C150,D390', + D4193 => 'C-5000Z', + D4194 => 'X-3,C-60Z', + D4199 => 'u30D,S410D,u410D', + D4205 => 'X450,D535Z,C370Z', + D4210 => 'C160,D395', + D4211 => 'C725UZ', + D4213 => 'FerrariMODEL2003', + D4216 => 'u15D', + D4217 => 'u25D', + D4220 => 'u-miniD,Stylus V', + D4221 => 'u40D,S500,uD500', + D4231 => 'FerrariMODEL2004', + D4240 => 'X500,D590Z,C470Z', + D4244 => 'uD800,S800', + D4256 => 'u720SW,S720SW', + D4261 => 'X600,D630,FE5500', + D4262 => 'uD600,S600', + D4301 => 'u810/S810', # (yes, "/". Olympus is not consistent in the notation) + D4302 => 'u710,S710', + D4303 => 'u700,S700', + D4304 => 'FE100,X710', + D4305 => 'FE110,X705', + D4310 => 'FE-130,X-720', + D4311 => 'FE-140,X-725', + D4312 => 'FE150,X730', + D4313 => 'FE160,X735', + D4314 => 'u740,S740', + D4315 => 'u750,S750', + D4316 => 'u730/S730', + D4317 => 'FE115,X715', + D4321 => 'SP550UZ', + D4322 => 'SP510UZ', + D4324 => 'FE170,X760', + D4326 => 'FE200', + D4327 => 'FE190/X750', # (also SX876) + D4328 => 'u760,S760', + D4330 => 'FE180/X745', # (also SX875) + D4331 => 'u1000/S1000', + D4332 => 'u770SW,S770SW', + D4333 => 'FE240/X795', + D4334 => 'FE210,X775', + D4336 => 'FE230/X790', + D4337 => 'FE220,X785', + D4338 => 'u725SW,S725SW', + D4339 => 'FE250/X800', + D4341 => 'u780,S780', + D4343 => 'u790SW,S790SW', + D4344 => 'u1020,S1020', + D4346 => 'FE15,X10', + D4348 => 'FE280,X820,C520', + D4349 => 'FE300,X830', + D4350 => 'u820,S820', + D4351 => 'u1200,S1200', + D4352 => 'FE270,X815,C510', + D4353 => 'u795SW,S795SW', + D4354 => 'u1030SW,S1030SW', + D4355 => 'SP560UZ', + D4356 => 'u1010,S1010', + D4357 => 'u830,S830', + D4359 => 'u840,S840', + D4360 => 'FE350WIDE,X865', + D4361 => 'u850SW,S850SW', + D4362 => 'FE340,X855,C560', + D4363 => 'FE320,X835,C540', + D4364 => 'SP570UZ', + D4366 => 'FE330,X845,C550', + D4368 => 'FE310,X840,C530', + D4370 => 'u1050SW,S1050SW', + D4371 => 'u1060,S1060', + D4372 => 'FE370,X880,C575', + D4374 => 'SP565UZ', + D4377 => 'u1040,S1040', + D4378 => 'FE360,X875,C570', + D4379 => 'FE20,X15,C25', + D4380 => 'uT6000,ST6000', + D4381 => 'uT8000,ST8000', + D4382 => 'u9000,S9000', + D4384 => 'SP590UZ', + D4385 => 'FE3010,X895', + D4386 => 'FE3000,X890', + D4387 => 'FE35,X30', + D4388 => 'u550WP,S550WP', + D4390 => 'FE5000,X905', + D4391 => 'u5000', + D4392 => 'u7000,S7000', + D4396 => 'FE5010,X915', + D4397 => 'FE25,X20', + D4398 => 'FE45,X40', + D4401 => 'XZ-1', + D4402 => 'uT6010,ST6010', + D4406 => 'u7010,S7010 / u7020,S7020', + D4407 => 'FE4010,X930', + D4408 => 'X560WP', + D4409 => 'FE26,X21', + D4410 => 'FE4000,X920,X925', + D4411 => 'FE46,X41,X42', + D4412 => 'FE5020,X935', + D4413 => 'uTough-3000', + D4414 => 'StylusTough-6020', + D4415 => 'StylusTough-8010', + D4417 => 'u5010,S5010', + D4418 => 'u7040,S7040', + D4419 => 'u9010,S9010', + D4423 => 'FE4040', + D4424 => 'FE47,X43', + D4426 => 'FE4030,X950', + D4428 => 'FE5030,X965,X960', + D4430 => 'u7030,S7030', + D4432 => 'SP600UZ', + D4434 => 'SP800UZ', + D4439 => 'FE4020,X940', + D4442 => 'FE5035', + D4448 => 'FE4050,X970', + D4450 => 'FE5050,X985', + D4454 => 'u-7050', + D4464 => 'T10,X27', + D4470 => 'FE5040,X980', + D4472 => 'TG-310', + D4474 => 'TG-610', + D4476 => 'TG-810', + D4478 => 'VG145,VG140,D715', + D4479 => 'VG130,D710', + D4480 => 'VG120,D705', + D4482 => 'VR310,D720', + D4484 => 'VR320,D725', + D4486 => 'VR330,D730', + D4488 => 'VG110,D700', + D4490 => 'SP-610UZ', + D4492 => 'SZ-10', + D4494 => 'SZ-20', + D4496 => 'SZ-30MR', + D4498 => 'SP-810UZ', + D4500 => 'SZ-11', + D4504 => 'TG-615', + D4508 => 'TG-620', + D4510 => 'TG-820', + D4512 => 'TG-1', + D4516 => 'SH-21', + D4519 => 'SZ-14', + D4520 => 'SZ-31MR', + D4521 => 'SH-25MR', + D4523 => 'SP-720UZ', + D4529 => 'VG170', + D4531 => 'XZ-2', + D4535 => 'SP-620UZ', + D4536 => 'TG-320', + D4537 => 'VR340,D750', + D4538 => 'VG160,X990,D745', + D4541 => 'SZ-12', + D4545 => 'VH410', + D4546 => 'XZ-10', #IB + D4547 => 'TG-2', + D4548 => 'TG-830', + D4549 => 'TG-630', + D4550 => 'SH-50', + D4553 => 'SZ-16,DZ-105', + D4562 => 'SP-820UZ', + D4566 => 'SZ-15', + D4572 => 'STYLUS1', + D4574 => 'TG-3', + D4575 => 'TG-850', + D4579 => 'SP-100EE', + D4580 => 'SH-60', + D4581 => 'SH-1', + D4582 => 'TG-835', + D4585 => 'SH-2 / SH-3', + D4586 => 'TG-4', + D4587 => 'TG-860', + D4591 => 'TG-870', + D4593 => 'TG-5', #IB + D4603 => 'TG-6', #IB + D4809 => 'C2500L', + D4842 => 'E-10', + D4856 => 'C-1', + D4857 => 'C-1Z,D-150Z', + DCHC => 'D500L', + DCHT => 'D600L / D620L', + K0055 => 'AIR-A01', + S0003 => 'E-330', + S0004 => 'E-500', + S0009 => 'E-400', + S0010 => 'E-510', + S0011 => 'E-3', + S0013 => 'E-410', + S0016 => 'E-420', + S0017 => 'E-30', + S0018 => 'E-520', + S0019 => 'E-P1', + S0023 => 'E-620', + S0026 => 'E-P2', + S0027 => 'E-PL1', + S0029 => 'E-450', + S0030 => 'E-600', + S0032 => 'E-P3', + S0033 => 'E-5', + S0034 => 'E-PL2', + S0036 => 'E-M5', + S0038 => 'E-PL3', + S0039 => 'E-PM1', + S0040 => 'E-PL1s', + S0042 => 'E-PL5', + S0043 => 'E-PM2', + S0044 => 'E-P5', + S0045 => 'E-PL6', + S0046 => 'E-PL7', #IB + S0047 => 'E-M1', + S0051 => 'E-M10', + S0052 => 'E-M5MarkII', #IB + S0059 => 'E-M10MarkII', + S0061 => 'PEN-F', #forum7005 + S0065 => 'E-PL8', + S0067 => 'E-M1MarkII', + S0068 => 'E-M10MarkIII', + S0076 => 'E-PL9', #IB + S0080 => 'E-M1X', #IB + S0085 => 'E-PL10', #IB + S0089 => 'E-M5MarkIII', + S0092 => 'E-M1MarkIII', #IB + S0093 => 'E-P7', #IB + S0095 => 'OM-1', #IB + S0101 => 'OM-5', #IB + SR45 => 'D220', + SR55 => 'D320L', + SR83 => 'D340L', + SR85 => 'C830L,D340R', + SR852 => 'C860L,D360L', + SR872 => 'C900Z,D400Z', + SR874 => 'C960Z,D460Z', + SR951 => 'C2000Z', + SR952 => 'C21', + SR953 => 'C21T.commu', + SR954 => 'C2020Z', + SR955 => 'C990Z,D490Z', + SR956 => 'C211Z', + SR959 => 'C990ZS,D490Z', + SR95A => 'C2100UZ', + SR971 => 'C100,D370', + SR973 => 'C2,D230', + SX151 => 'E100RS', + SX351 => 'C3000Z / C3030Z', + SX354 => 'C3040Z', + SX355 => 'C2040Z', + SX357 => 'C700UZ', + SX358 => 'C200Z,D510Z', + SX374 => 'C3100Z,C3020Z', + SX552 => 'C4040Z', + SX553 => 'C40Z,D40Z', + SX556 => 'C730UZ', + SX558 => 'C5050Z', + SX571 => 'C120,D380', + SX574 => 'C300Z,D550Z', + SX575 => 'C4100Z,C4000Z', + SX751 => 'X200,D560Z,C350Z', + SX752 => 'X300,D565Z,C450Z', + SX753 => 'C750UZ', + SX754 => 'C740UZ', + SX755 => 'C755UZ', + SX756 => 'C5060WZ', + SX757 => 'C8080WZ', + SX758 => 'X350,D575Z,C360Z', + SX759 => 'X400,D580Z,C460Z', + SX75A => 'AZ-2ZOOM', + SX75B => 'D595Z,C500Z', + SX75C => 'X550,D545Z,C480Z', + SX75D => 'IR-300', + SX75F => 'C55Z,C5500Z', + SX75G => 'C170,D425', + SX75J => 'C180,D435', + SX771 => 'C760UZ', + SX772 => 'C770UZ', + SX773 => 'C745UZ', + SX774 => 'X250,D560Z,C350Z', + SX775 => 'X100,D540Z,C310Z', + SX776 => 'C460ZdelSol', + SX777 => 'C765UZ', + SX77A => 'D555Z,C315Z', + SX851 => 'C7070WZ', + SX852 => 'C70Z,C7000Z', + SX853 => 'SP500UZ', + SX854 => 'SP310', + SX855 => 'SP350', + SX873 => 'SP320', + SX875 => 'FE180/X745', # (also D4330) + SX876 => 'FE190/X750', # (also D4327) +# other brands +# 4MP9Q3 => 'Camera 4MP-9Q3' +# 4MP9T2 => 'BenQ DC C420 / Camera 4MP-9T2' +# 5MP9Q3 => 'Camera 5MP-9Q3', +# 5MP9X9 => 'Camera 5MP-9X9', +# '5MP-9T'=> 'Camera 5MP-9T3', +# '5MP-9Y'=> 'Camera 5MP-9Y2', +# '6MP-9U'=> 'Camera 6MP-9U9', +# 7MP9Q3 => 'Camera 7MP-9Q3', +# '8MP-9U'=> 'Camera 8MP-9U4', +# CE5330 => 'Acer CE-5330', +# 'CP-853'=> 'Acer CP-8531', +# CS5531 => 'Acer CS5531', +# DC500 => 'SeaLife DC500', +# DC7370 => 'Camera 7MP-9GA', +# DC7371 => 'Camera 7MP-9GM', +# DC7371 => 'Hitachi HDC-751E', +# DC7375 => 'Hitachi HDC-763E / Rollei RCP-7330X / Ricoh Caplio RR770 / Vivitar ViviCam 7330', +# 'DC E63'=> 'BenQ DC E63+', +# 'DC P86'=> 'BenQ DC P860', +# DS5340 => 'Maginon Performic S5 / Premier 5MP-9M7', +# DS5341 => 'BenQ E53+ / Supra TCM X50 / Maginon X50 / Premier 5MP-9P8', +# DS5346 => 'Premier 5MP-9Q2', +# E500 => 'Konica Minolta DiMAGE E500', +# MAGINO => 'Maginon X60', +# Mz60 => 'HP Photosmart Mz60', +# Q3DIGI => 'Camera 5MP-9Q3', +# SLIMLI => 'Supra Slimline X6', +# V8300s => 'Vivitar V8300s', +); + +# ArtFilter, ArtFilterEffect and MagicFilter values (ref PH) +my %filters = ( + 0 => 'Off', + 1 => 'Soft Focus', # (XZ-1) + 2 => 'Pop Art', # (SZ-10 magic filter 1,SZ-31MR,E-M5,E-PL3) + 3 => 'Pale & Light Color', + 4 => 'Light Tone', + 5 => 'Pin Hole', # (SZ-10 magic filter 2,SZ-31MR,E-PL3) + 6 => 'Grainy Film', + 9 => 'Diorama', + 10 => 'Cross Process', + 12 => 'Fish Eye', # (SZ-10 magic filter 3) + 13 => 'Drawing', # (SZ-10 magic filter 4) + 14 => 'Gentle Sepia', # (E-5) + 15 => 'Pale & Light Color II', #forum6269 ('Tender Light' ref 11) + 16 => 'Pop Art II', #11 (E-PL3 "(dark)" - PH) + 17 => 'Pin Hole II', #11 (E-PL3 "(color 2)" - PH) + 18 => 'Pin Hole III', #11 (E-M5, E-PL3 "(color 3)" - PH) + 19 => 'Grainy Film II', #11 + 20 => 'Dramatic Tone', # (XZ-1,SZ-31MR) + 21 => 'Punk', # (SZ-10 magic filter 6) + 22 => 'Soft Focus 2', # (SZ-10 magic filter 5) + 23 => 'Sparkle', # (SZ-10 magic filter 7) + 24 => 'Watercolor', # (SZ-10 magic filter 8) + 25 => 'Key Line', # (E-M5) + 26 => 'Key Line II', #forum6269 + 27 => 'Miniature', # (SZ-31MR) + 28 => 'Reflection', # (TG-820,SZ-31MR) + 29 => 'Fragmented', # (TG-820,SZ-31MR) + 31 => 'Cross Process II', #forum6269 + 32 => 'Dramatic Tone II', #forum6269 (Dramatic Tone B&W for E-M5) + 33 => 'Watercolor I', # ('Watercolor I' for EM-1 ref forum6269, 'Watercolor II' for E-PM2 ref PH) + 34 => 'Watercolor II', #forum6269 + 35 => 'Diorama II', #forum6269 + 36 => 'Vintage', #forum6269 + 37 => 'Vintage II', #forum6269 + 38 => 'Vintage III', #forum6269 + 39 => 'Partial Color', #forum6269 + 40 => 'Partial Color II', #forum6269 + 41 => 'Partial Color III', #forum6269 +); + +my %toneLevelType = ( + 0 => '0', + -31999 => 'Highlights', + -31998 => 'Shadows', + -31997 => 'Midtones', +); + +# tag information for WAV "Index" tags +my %indexInfo = ( + Format => 'int32u', + RawConv => '$val == 0xffffffff ? undef : $val', + ValueConv => '$val / 1000', + PrintConv => 'ConvertDuration($val)', +); + +# Olympus tags +%Image::ExifTool::Olympus::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, +# +# Tags 0x0000 through 0x0103 are the same as Konica/Minolta cameras (ref 3) +# (removed 0x0101-0x0103 because they weren't supported by my samples - PH) +# + 0x0000 => { + Name => 'MakerNoteVersion', + Writable => 'undef', + }, + 0x0001 => { + Name => 'MinoltaCameraSettingsOld', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::CameraSettings', + ByteOrder => 'BigEndian', + }, + }, + 0x0003 => { + Name => 'MinoltaCameraSettings', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::CameraSettings', + ByteOrder => 'BigEndian', + }, + }, + 0x0040 => { + Name => 'CompressedImageSize', + Writable => 'int32u', + }, + 0x0081 => { + Name => 'PreviewImageData', + Binary => 1, + Writable => 0, + }, + 0x0088 => { + Name => 'PreviewImageStart', + Flags => 'IsOffset', + OffsetPair => 0x0089, # point to associated byte count + DataTag => 'PreviewImage', + Writable => 0, + Protected => 2, + }, + 0x0089 => { + Name => 'PreviewImageLength', + OffsetPair => 0x0088, # point to associated offset + DataTag => 'PreviewImage', + Writable => 0, + Protected => 2, + }, + 0x0100 => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Writable => 'undef', + WriteCheck => '$self->CheckImage(\$val)', + Binary => 1, + }, + 0x0104 => { Name => 'BodyFirmwareVersion', Writable => 'string' }, #11 +# +# end Konica/Minolta tags +# + 0x0200 => { + Name => 'SpecialMode', + Notes => q{ + 3 numbers: 1. Shooting mode: 0=Normal, 2=Fast, 3=Panorama; + 2. Sequence Number; 3. Panorama Direction: 1=Left-right, + 2=Right-left, 3=Bottom-Top, 4=Top-Bottom + }, + Writable => 'int32u', + Count => 3, + PrintConv => sub { #3 + my $val = shift; + my @v = split ' ', $val; + return $val unless @v >= 3; + my @v0 = ('Normal','Unknown (1)','Fast','Panorama'); + my @v2 = ('(none)','Left to Right','Right to Left','Bottom to Top','Top to Bottom'); + $val = $v0[$v[0]] || "Unknown ($v[0])"; + $val .= ", Sequence: $v[1]"; + $val .= ', Panorama: ' . ($v2[$v[2]] || "Unknown ($v[2])"); + return $val; + }, + }, + 0x0201 => { + Name => 'Quality', + Writable => 'int16u', + Notes => q{ + Quality values are decoded based on the CameraType tag. All types + represent SQ, HQ and SHQ as sequential integers, but in general + SX-type cameras start with a value of 0 for SQ while others start + with 1 + }, + # These values are different for different camera types + # (can't have Condition based on CameraType because it isn't known + # when this tag is extracted) + PrintConv => sub { + my ($val, $self) = @_; + my %t1 = ( # all SX camera types except SX151 + 0 => 'SQ (Low)', + 1 => 'HQ (Normal)', + 2 => 'SHQ (Fine)', + 6 => 'RAW', #PH - C5050WZ + ); + my %t2 = ( # all other types (except D4322, ref 22) + 1 => 'SQ (Low)', + 2 => 'HQ (Normal)', + 3 => 'SHQ (Fine)', + 4 => 'RAW', + 5 => 'Medium-Fine', #PH + 6 => 'Small-Fine', #PH + 33 => 'Uncompressed', #PH - C2100Z + ); + my $conv = $self->{CameraType} =~ /^(SX(?!151\b)|D4322)/ ? \%t1 : \%t2; + return $$conv{$val} ? $$conv{$val} : "Unknown ($val)"; + }, + # (no PrintConvInv because we don't know CameraType at write time) + }, + 0x0202 => { + Name => 'Macro', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'Super Macro', #6 + }, + }, + 0x0203 => { #6 + Name => 'BWMode', + Description => 'Black And White Mode', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 6 => '(none)', #22 + }, + }, + 0x0204 => { + Name => 'DigitalZoom', + Writable => 'rational64u', + PrintConv => '$val=~/\./ or $val.=".0"; $val', + PrintConvInv => '$val', + }, + 0x0205 => { #6 + Name => 'FocalPlaneDiagonal', + Writable => 'rational64u', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + 0x0206 => { Name => 'LensDistortionParams', Writable => 'int16s', Count => 6 }, #6 + 0x0207 => { #PH (was incorrectly FirmwareVersion, ref 1/3) + Name => 'CameraType', + Condition => '$$valPt ne "NORMAL"', # FE240, SP510, u730 and u1000 write this + Writable => 'string', + DataMember => 'CameraType', + RawConv => '$self->{CameraType} = $val', + SeparateTable => 'CameraType', + ValueConv => '$val =~ s/\s+$//; $val', # ("SX151 " has trailing space) + ValueConvInv => '$val', + PrintConv => \%olympusCameraTypes, + Priority => 0, + # 'NORMAL' for some models: u730,SP510UZ,u1000,FE240 + }, + 0x0208 => { + Name => 'TextInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::TextInfo', + }, + }, + 0x0209 => { + Name => 'CameraID', + Format => 'string', # this really should have been a string + }, + 0x020b => { Name => 'EpsonImageWidth', Writable => 'int32u' }, #PH + 0x020c => { Name => 'EpsonImageHeight', Writable => 'int32u' }, #PH + 0x020d => { Name => 'EpsonSoftware', Writable => 'string' }, #PH + 0x0280 => { #PH + %Image::ExifTool::previewImageTagInfo, + Groups => { 2 => 'Preview' }, + Notes => 'found in ERF and JPG images from some Epson models', + Format => 'undef', + Writable => 'int8u', + }, + 0x0300 => { Name => 'PreCaptureFrames', Writable => 'int16u' }, #6 + 0x0301 => { Name => 'WhiteBoard', Writable => 'int16u' }, #11 + 0x0302 => { #6 + Name => 'OneTouchWB', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'On (Preset)', + }, + }, + 0x0303 => { Name => 'WhiteBalanceBracket', Writable => 'int16u' }, #11 + 0x0304 => { Name => 'WhiteBalanceBias', Writable => 'int16u' }, #11 + # 0x0305 => 'PrintMatching', ? #11 + 0x0400 => { #IB + Name => 'SensorArea', + Condition => '$$self{TIFF_TYPE} eq "ERF"', + Writable => 'undef', + Format => 'int16u', + Count => 4, + Notes => 'found in Epson ERF images', + }, + 0x0401 => { #IB + Name => 'BlackLevel', + Condition => '$$self{TIFF_TYPE} eq "ERF"', + Writable => 'int32u', + Count => 4, + Notes => 'found in Epson ERF images', + }, + # 0x0402 - BitCodedAutoFocus (ref 11) + 0x0403 => { #11 + Name => 'SceneMode', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Normal', + 1 => 'Standard', + 2 => 'Auto', + 3 => 'Intelligent Auto', #PH (guess, u7040) + 4 => 'Portrait', + 5 => 'Landscape+Portrait', + 6 => 'Landscape', + 7 => 'Night Scene', + 8 => 'Night+Portrait', + 9 => 'Sport', + 10 => 'Self Portrait', + 11 => 'Indoor', + 12 => 'Beach & Snow', + 13 => 'Beach', + 14 => 'Snow', + 15 => 'Self Portrait+Self Timer', + 16 => 'Sunset', + 17 => 'Cuisine', + 18 => 'Documents', + 19 => 'Candle', + 20 => 'Fireworks', + 21 => 'Available Light', + 22 => 'Vivid', + 23 => 'Underwater Wide1', + 24 => 'Underwater Macro', + 25 => 'Museum', + 26 => 'Behind Glass', + 27 => 'Auction', + 28 => 'Shoot & Select1', + 29 => 'Shoot & Select2', + 30 => 'Underwater Wide2', + 31 => 'Digital Image Stabilization', + 32 => 'Face Portrait', + 33 => 'Pet', + 34 => 'Smile Shot', + 35 => 'Quick Shutter', + 43 => 'Hand-held Starlight', #PH (SH-21) + 100 => 'Panorama', #PH (SH-21) + 101 => 'Magic Filter', #PH + 103 => 'HDR', #PH (XZ-2) + }, + }, + 0x0404 => { Name => 'SerialNumber', Writable => 'string' }, #PH (D595Z, C7070WZ) + 0x0405 => { Name => 'Firmware', Writable => 'string' }, #11 + 0x0e00 => { # (AFFieldCoord for models XZ-2 and XZ-10, ref 11) + Name => 'PrintIM', + Description => 'Print Image Matching', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, + # 0x0e80 - undef[256] - offset 0x30: uint16[2] WB_RGBLevels = val[0]*561,65536,val[1]*431 (ref IB) + 0x0f00 => { + Name => 'DataDump', + Writable => 0, + Binary => 1, + }, + 0x0f01 => { #6 + Name => 'DataDump2', + Writable => 0, + Binary => 1, + }, + 0x0f04 => { + Name => 'ZoomedPreviewStart', + # NOTE: this tag is currently not updated properly when the image is rewritten! + OffsetPair => 0xf05, + DataTag => 'ZoomedPreviewImage', + Writable => 'int32u', + Protected => 2, + }, + 0x0f05 => { + Name => 'ZoomedPreviewLength', + OffsetPair => 0xf04, + DataTag => 'ZoomedPreviewImage', + Writable => 'int32u', + Protected => 2, + }, + 0x0f06 => { + Name => 'ZoomedPreviewSize', + Writable => 'int16u', + Count => 2, + }, + 0x1000 => { #6 + Name => 'ShutterSpeedValue', + Writable => 'rational64s', + Priority => 0, + ValueConv => 'abs($val)<100 ? 2**(-$val) : 0', + ValueConvInv => '$val>0 ? -log($val)/log(2) : -100', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x1001 => { #6 + Name => 'ISOValue', + Writable => 'rational64s', + Priority => 0, + ValueConv => '100 * 2 ** ($val - 5)', + ValueConvInv => '$val>0 ? log($val/100)/log(2)+5 : 0', + PrintConv => 'int($val * 100 + 0.5) / 100', + PrintConvInv => '$val', + }, + 0x1002 => { #6 + Name => 'ApertureValue', + Writable => 'rational64s', + Priority => 0, + ValueConv => '2 ** ($val / 2)', + ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x1003 => { #6 + Name => 'BrightnessValue', + Writable => 'rational64s', + Priority => 0, + }, + 0x1004 => { #3 + Name => 'FlashMode', + Writable => 'int16u', + PrintConv => { + 2 => 'On', #PH + 3 => 'Off', #PH + }, + }, + 0x1005 => { #6 + Name => 'FlashDevice', + Writable => 'int16u', + PrintConv => { + 0 => 'None', + 1 => 'Internal', + 4 => 'External', + 5 => 'Internal + External', + }, + }, + 0x1006 => { #6 + Name =>'ExposureCompensation', + Writable => 'rational64s', + }, + 0x1007 => { Name => 'SensorTemperature',Writable => 'int16s' }, #6 (E-10, E-20 and C2500L - numbers usually around 30-40) + 0x1008 => { Name => 'LensTemperature', Writable => 'int16s' }, #6 + 0x1009 => { Name => 'LightCondition', Writable => 'int16u' }, #11 + 0x100a => { #11 + Name => 'FocusRange', + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'Macro', + }, + }, + 0x100b => { #6 + Name => 'FocusMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Manual', + }, + }, + 0x100c => { #6 + Name => 'ManualFocusDistance', + Writable => 'rational64u', + PrintConv => '"$val mm"', #11 + PrintConvInv => '$val=~s/\s*mm$//; $val', + }, + 0x100d => { Name => 'ZoomStepCount', Writable => 'int16u' }, #6 + 0x100e => { Name => 'FocusStepCount', Writable => 'int16u' }, #6 + 0x100f => { #6 + Name => 'Sharpness', + Writable => 'int16u', + Priority => 0, + PrintConv => { + 0 => 'Normal', + 1 => 'Hard', + 2 => 'Soft', + }, + }, + 0x1010 => { Name => 'FlashChargeLevel', Writable => 'int16u' }, #6 + 0x1011 => { #3 + Name => 'ColorMatrix', + Writable => 'int16u', + Format => 'int16s', + Count => 9, + }, + 0x1012 => { Name => 'BlackLevel', Writable => 'int16u', Count => 4 }, #3 + 0x1013 => { #11 + Name => 'ColorTemperatureBG', + Writable => 'int16u', + Unknown => 1, # (doesn't look like a temperature) + }, + 0x1014 => { #11 + Name => 'ColorTemperatureRG', + Writable => 'int16u', + Unknown => 1, # (doesn't look like a temperature) + }, + 0x1015 => { #6 + Name => 'WBMode', + Writable => 'int16u', + Count => 2, + PrintConvColumns => 2, + PrintConv => { + '1' => 'Auto', + '1 0' => 'Auto', + '1 2' => 'Auto (2)', + '1 4' => 'Auto (4)', + '2 2' => '3000 Kelvin', + '2 3' => '3700 Kelvin', + '2 4' => '4000 Kelvin', + '2 5' => '4500 Kelvin', + '2 6' => '5500 Kelvin', + '2 7' => '6500 Kelvin', + '2 8' => '7500 Kelvin', + '3 0' => 'One-touch', + }, + }, + 0x1017 => { #2 + Name => 'RedBalance', + Writable => 'int16u', + Count => 2, + ValueConv => '$val=~s/ .*//; $val / 256', + ValueConvInv => '$val*=256;"$val 64"', + }, + 0x1018 => { #2 + Name => 'BlueBalance', + Writable => 'int16u', + Count => 2, + ValueConv => '$val=~s/ .*//; $val / 256', + ValueConvInv => '$val*=256;"$val 64"', + }, + 0x1019 => { Name => 'ColorMatrixNumber', Writable => 'int16u' }, #11 + # 0x101a is same as CameraID ("OLYMPUS DIGITAL CAMERA") for C2500L - PH + 0x101a => { Name => 'SerialNumber', Writable => 'string' }, #3 + 0x101b => { #11 + Name => 'ExternalFlashAE1_0', + Writable => 'int32u', + Unknown => 1, # (what are these?) + }, + 0x101c => { Name => 'ExternalFlashAE2_0', Writable => 'int32u', Unknown => 1 }, #11 + 0x101d => { Name => 'InternalFlashAE1_0', Writable => 'int32u', Unknown => 1 }, #11 + 0x101e => { Name => 'InternalFlashAE2_0', Writable => 'int32u', Unknown => 1 }, #11 + 0x101f => { Name => 'ExternalFlashAE1', Writable => 'int32u', Unknown => 1 }, #11 + 0x1020 => { Name => 'ExternalFlashAE2', Writable => 'int32u', Unknown => 1 }, #11 + 0x1021 => { Name => 'InternalFlashAE1', Writable => 'int32u', Unknown => 1 }, #11 + 0x1022 => { Name => 'InternalFlashAE2', Writable => 'int32u', Unknown => 1 }, #11 + 0x1023 => { Name => 'FlashExposureComp', Writable => 'rational64s' }, #6 + 0x1024 => { Name => 'InternalFlashTable', Writable => 'int16u' }, #11 + 0x1025 => { Name => 'ExternalFlashGValue', Writable => 'rational64s' }, #11 + 0x1026 => { #6 + Name => 'ExternalFlashBounce', + Writable => 'int16u', + PrintConv => { + 0 => 'No', + 1 => 'Yes', + }, + }, + 0x1027 => { Name => 'ExternalFlashZoom', Writable => 'int16u' }, #6 + 0x1028 => { Name => 'ExternalFlashMode', Writable => 'int16u' }, #6 + 0x1029 => { #3 + Name => 'Contrast', + Writable => 'int16u', + PrintConv => { #PH (works with E1) + 0 => 'High', + 1 => 'Normal', + 2 => 'Low', + }, + }, + 0x102a => { Name => 'SharpnessFactor', Writable => 'int16u' }, #3 + 0x102b => { Name => 'ColorControl', Writable => 'int16u', Count => 6 }, #3 + 0x102c => { Name => 'ValidBits', Writable => 'int16u', Count => 2 }, #3 + 0x102d => { Name => 'CoringFilter', Writable => 'int16u' }, #3 + 0x102e => { Name => 'OlympusImageWidth', Writable => 'int32u' }, #PH + 0x102f => { Name => 'OlympusImageHeight', Writable => 'int32u' }, #PH + 0x1030 => { Name => 'SceneDetect', Writable => 'int16u' }, #11 + 0x1031 => { #11 + Name => 'SceneArea', + Writable => 'int32u', + Count => 8, + Unknown => 1, # (numbers don't make much sense?) + }, + # 0x1032 HAFFINAL? #11 + 0x1033 => { #11 + Name => 'SceneDetectData', + Writable => 'int32u', + Count => 720, + Binary => 1, + Unknown => 1, # (but what does it mean?) + }, + 0x1034 => { Name => 'CompressionRatio', Writable => 'rational64u' }, #3 + 0x1035 => { #6 + Name => 'PreviewImageValid', + Writable => 'int32u', + DelValue => 0, + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x1036 => { #6 + Name => 'PreviewImageStart', + Flags => 'IsOffset', + OffsetPair => 0x1037, # point to associated byte count + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x1037 => { #6 + # (may contain data from multiple previews - PH, FE320) + Name => 'PreviewImageLength', + OffsetPair => 0x1036, # point to associated offset + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x1038 => { Name => 'AFResult', Writable => 'int16u' }, #11 + 0x1039 => { #6 + Name => 'CCDScanMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Interlaced', + 1 => 'Progressive', + }, + }, + 0x103a => { #6 + Name => 'NoiseReduction', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x103b => { Name => 'FocusStepInfinity', Writable => 'int16u' }, #6 + 0x103c => { Name => 'FocusStepNear', Writable => 'int16u' }, #6 + 0x103d => { Name => 'LightValueCenter', Writable => 'rational64s' }, #11 + 0x103e => { Name => 'LightValuePeriphery', Writable => 'rational64s' }, #11 + 0x103f => { #11 + Name => 'FieldCount', + Writable => 'int16u', + Unknown => 1, # (but what does it mean?) + }, +# +# Olympus really screwed up the format of the following subdirectories (for the +# E-1 and E-300 anyway). Not only is the subdirectory value data not included in +# the size, but also the count is 2 bytes short for the subdirectory itself +# (presumably the Olympus programmers forgot about the 2-byte entry count at the +# start of the subdirectory). This mess is straightened out and these subdirs +# are written properly when ExifTool rewrites the file. Note that this problem +# has been fixed by Olympus in the new-style IFD maker notes since a standard +# SubIFD offset value is used. As written by the camera, the old style +# directories have format 'undef' or 'string', and the new style has format +# 'ifd'. However, some older versions of exiftool may have rewritten the new +# style as 'int32u', so handle both cases. - PH +# + 0x2010 => [ #PH + { + Name => 'Equipment', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, # (so HtmlDump doesn't show these as double-referenced) + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::Equipment', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'EquipmentIFD', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::Equipment', + Start => '$val', + }, + }, + ], + 0x2020 => [ #PH + { + Name => 'CameraSettings', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::CameraSettings', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'CameraSettingsIFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::CameraSettings', + Start => '$val', + }, + }, + ], + 0x2030 => [ #PH + { + Name => 'RawDevelopment', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::RawDevelopment', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'RawDevelopmentIFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::RawDevelopment', + Start => '$val', + }, + }, + ], + 0x2031 => [ #11 + { + Name => 'RawDev2', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::RawDevelopment2', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'RawDev2IFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::RawDevelopment2', + Start => '$val', + }, + }, + ], + 0x2040 => [ #PH + { + Name => 'ImageProcessing', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::ImageProcessing', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'ImageProcessingIFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::ImageProcessing', + Start => '$val', + }, + }, + ], + 0x2050 => [ #PH + { + Name => 'FocusInfo', + Condition => '$format ne "ifd" and $format ne "int32u" and not $$self{OlympusCAMER}', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FocusInfo', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'FocusInfoIFD', + Condition => 'not $$self{OlympusCAMER}', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FocusInfo', + Start => '$val', + }, + }, + { + # ASCII-based camera parameters if makernotes starts with "CAMER\0" + # (or for Sony models starting with "SONY PI\0" or "PREMI\0") + Name => 'CameraParameters', + Writable => 'undef', + Binary => 1, + }, + ], + 0x2100 => [ + { #11 + Name => 'Olympus2100', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + }, + }, + { #PH + Name => 'Olympus2100IFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + Start => '$val', + }, + }, + ], + 0x2200 => [ + { #11 + Name => 'Olympus2200', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + }, + }, + { #PH + Name => 'Olympus2200IFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + Start => '$val', + }, + }, + ], + 0x2300 => [ + { #11 + Name => 'Olympus2300', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + }, + }, + { #PH + Name => 'Olympus2300IFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + Start => '$val', + }, + }, + ], + 0x2400 => [ + { #11 + Name => 'Olympus2400', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + }, + }, + { #PH + Name => 'Olympus2400IFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + Start => '$val', + }, + }, + ], + 0x2500 => [ + { #11 + Name => 'Olympus2500', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + }, + }, + { #PH + Name => 'Olympus2500IFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + Start => '$val', + }, + }, + ], + 0x2600 => [ + { #11 + Name => 'Olympus2600', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + }, + }, + { #PH + Name => 'Olympus2600IFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + Start => '$val', + }, + }, + ], + 0x2700 => [ + { #11 + Name => 'Olympus2700', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + }, + }, + { #PH + Name => 'Olympus2700IFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + Start => '$val', + }, + }, + ], + 0x2800 => [ + { #11 + Name => 'Olympus2800', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + }, + }, + { #PH + Name => 'Olympus2800IFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + Start => '$val', + }, + }, + ], + 0x2900 => [ + { #11 + Name => 'Olympus2900', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + }, + }, + { #PH + Name => 'Olympus2900IFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::FETags', + ByteOrder => 'Unknown', + Start => '$val', + }, + }, + ], + 0x3000 => [ + { #6 + Name => 'RawInfo', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::RawInfo', + ByteOrder => 'Unknown', + }, + }, + { #PH + Name => 'RawInfoIFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::RawInfo', + Start => '$val', + }, + }, + ], + 0x4000 => [ #PH + { + Name => 'MainInfo', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::Main', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'MainInfoIFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::Main', + Start => '$val', + }, + }, + ], + 0x5000 => [ #PH + { + Name => 'UnknownInfo', + Condition => '$format ne "ifd" and $format ne "int32u"', + NestedHtmlDump => 2, + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::UnknownInfo', + ByteOrder => 'Unknown', + }, + }, + { + Name => 'UnknownInfoIFD', + Groups => { 1 => 'MakerNotes' }, + Flags => 'SubIFD', + FixFormat => 'ifd', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::UnknownInfo', + Start => '$val', + }, + }, + ], +); + +# TextInfo tags +%Image::ExifTool::Olympus::TextInfo = ( + PROCESS_PROC => \&Image::ExifTool::APP12::ProcessAPP12, + NOTES => q{ + This information is in text format (similar to APP12 information, but with + spaces instead of linefeeds). Below are tags which have been observed, but + any information found here will be extracted, even if the tag is not listed. + }, + GROUPS => { 0 => 'MakerNotes', 1 => 'Olympus', 2 => 'Image' }, + Resolution => { }, + Type => { + Name => 'CameraType', + Groups => { 2 => 'Camera' }, + DataMember => 'CameraType', + RawConv => '$self->{CameraType} = $val', + SeparateTable => 'CameraType', + PrintConv => \%olympusCameraTypes, + }, +); + +# Olympus Equipment IFD +%Image::ExifTool::Olympus::Equipment = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x000 => { #PH + Name => 'EquipmentVersion', + Writable => 'undef', + RawConv => '$val=~s/\0+$//; $val', # (may be null terminated) + Count => 4, + }, + 0x100 => { #6 + Name => 'CameraType2', + Writable => 'string', + Count => 6, + SeparateTable => 'CameraType', + PrintConv => \%olympusCameraTypes, + }, + 0x101 => { #PH + Name => 'SerialNumber', + Writable => 'string', + Count => 32, + PrintConv => '$val=~s/\s+$//;$val', + PrintConvInv => 'pack("A31",$val)', # pad with spaces to 31 chars + }, + 0x102 => { #6 + Name => 'InternalSerialNumber', + Notes => '16 digits: 0-3=model, 4=year, 5-6=month, 8-12=unit number', + Writable => 'string', + Count => 32, + }, + 0x103 => { #6 + Name => 'FocalPlaneDiagonal', + Writable => 'rational64u', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + 0x104 => { #6 + Name => 'BodyFirmwareVersion', + Writable => 'int32u', + PrintConv => '$val=sprintf("%x",$val);$val=~s/(.{3})$/\.$1/;$val', + PrintConvInv => '$val=sprintf("%.3f",$val);$val=~s/\.//;hex($val)', + }, + 0x201 => { #6 + Name => 'LensType', + Writable => 'int8u', + Count => 6, + Notes => q{ + 6 numbers: 1. Make, 2. Unknown, 3. Model, 4. Sub-model, 5-6. Unknown. Only + the Make, Model and Sub-model are used to identify the lens type + }, + SeparateTable => 'LensType', + # Have seen these values for the unknown numbers: + # 2: 0 + # 5: 0, 2(Olympus lenses for which I have also seen 0 for this number) + # 6: 0, 16(new Lumix lenses) + ValueConv => 'my @a=split(" ",$val); sprintf("%x %.2x %.2x",@a[0,2,3])', + # set unknown values to zero when writing + ValueConvInv => 'my @a=split(" ",$val); hex($a[0])." 0 ".hex($a[1])." ".hex($a[2])." 0 0"', + PrintConv => \%olympusLensTypes, + }, + # apparently the first 3 digits of the lens s/n give the type (ref 4): + # 010 = 50macro + # 040 = EC-14 + # 050 = 14-54 + # 060 = 50-200 + # 080 = EX-25 + # 101 = FL-50 + # 272 = EC-20 #7 + 0x202 => { #PH + Name => 'LensSerialNumber', + Writable => 'string', + Count => 32, + PrintConv => '$val=~s/\s+$//;$val', + PrintConvInv => 'pack("A31",$val)', # pad with spaces to 31 chars + }, + 0x203 => { Name => 'LensModel', Writable => 'string' }, #17 + 0x204 => { #6 + Name => 'LensFirmwareVersion', + Writable => 'int32u', + PrintConv => '$val=sprintf("%x",$val);$val=~s/(.{3})$/\.$1/;$val', + PrintConvInv => '$val=sprintf("%.3f",$val);$val=~s/\.//;hex($val)', + }, + 0x205 => { #11 + Name => 'MaxApertureAtMinFocal', + Writable => 'int16u', + ValueConv => '$val ? sqrt(2)**($val/256) : 0', + ValueConvInv => '$val>0 ? int(512*log($val)/log(2)+0.5) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x206 => { #5 + Name => 'MaxApertureAtMaxFocal', + Writable => 'int16u', + ValueConv => '$val ? sqrt(2)**($val/256) : 0', + ValueConvInv => '$val>0 ? int(512*log($val)/log(2)+0.5) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x207 => { Name => 'MinFocalLength', Writable => 'int16u' }, #PH + 0x208 => { Name => 'MaxFocalLength', Writable => 'int16u' }, #PH + 0x20a => { #9 + Name => 'MaxAperture', # (at current focal length) + Writable => 'int16u', + ValueConv => '$val ? sqrt(2)**($val/256) : 0', + ValueConvInv => '$val>0 ? int(512*log($val)/log(2)+0.5) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x20b => { #11 + Name => 'LensProperties', + Writable => 'int16u', + PrintConv => 'sprintf("0x%x",$val)', + PrintConvInv => '$val', + }, + 0x301 => { #6 + Name => 'Extender', + Writable => 'int8u', + Count => 6, + Notes => q{ + 6 numbers: 1. Make, 2. Unknown, 3. Model, 4. Sub-model, 5-6. Unknown. Only + the Make and Model are used to identify the extender + }, + ValueConv => 'my @a=split(" ",$val); sprintf("%x %.2x",@a[0,2])', + ValueConvInv => 'my @a=split(" ",$val); hex($a[0])." 0 ".hex($a[1])." 0 0 0"', + PrintConv => { + '0 00' => 'None', + '0 04' => 'Olympus Zuiko Digital EC-14 1.4x Teleconverter', + '0 08' => 'Olympus EX-25 Extension Tube', + '0 10' => 'Olympus Zuiko Digital EC-20 2.0x Teleconverter', #7 + }, + }, + 0x302 => { Name => 'ExtenderSerialNumber', Writable => 'string', Count => 32 }, #4 + 0x303 => { Name => 'ExtenderModel', Writable => 'string' }, #9 + 0x304 => { #6 + Name => 'ExtenderFirmwareVersion', + Writable => 'int32u', + PrintConv => '$val=sprintf("%x",$val);$val=~s/(.{3})$/\.$1/;$val', + PrintConvInv => '$val=sprintf("%.3f",$val);$val=~s/\.//;hex($val)', + }, + 0x403 => { #http://dev.exiv2.org/issues/870 + Name => 'ConversionLens', + Writable => 'string', + # (observed values: '','TCON','FCON','WCON') + }, + 0x1000 => { #6 + Name => 'FlashType', + Writable => 'int16u', + PrintConv => { + 0 => 'None', + 2 => 'Simple E-System', + 3 => 'E-System', + 4 => 'E-System (body powered)', #forum9740 + }, + }, + 0x1001 => { #6 + Name => 'FlashModel', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'None', + 1 => 'FL-20', # (or subtronic digital or Inon UW flash, ref 11) + 2 => 'FL-50', # (or Metzblitz+SCA or Cullmann 34, ref 11) + 3 => 'RF-11', + 4 => 'TF-22', + 5 => 'FL-36', + 6 => 'FL-50R', #11 (or Metz mecablitz digital) + 7 => 'FL-36R', #11 + 9 => 'FL-14', #11 + 11 => 'FL-600R', #11 + 13 => 'FL-LM3', #forum9740 + 15 => 'FL-900R', #7 + }, + }, + 0x1002 => { #6 + Name => 'FlashFirmwareVersion', + Writable => 'int32u', + PrintConv => '$val=sprintf("%x",$val);$val=~s/(.{3})$/\.$1/;$val', + PrintConvInv => '$val=sprintf("%.3f",$val);$val=~s/\.//;hex($val)', + }, + 0x1003 => { Name => 'FlashSerialNumber', Writable => 'string', Count => 32 }, #4 +); + +# Olympus camera settings IFD +%Image::ExifTool::Olympus::CameraSettings = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x000 => { #PH + Name => 'CameraSettingsVersion', + Writable => 'undef', + RawConv => '$val=~s/\0+$//; $val', # (may be null terminated) + Count => 4, + }, + 0x100 => { #6 + Name => 'PreviewImageValid', + Writable => 'int32u', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x101 => { #PH + Name => 'PreviewImageStart', + Flags => 'IsOffset', + OffsetPair => 0x102, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x102 => { #PH + Name => 'PreviewImageLength', + OffsetPair => 0x101, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x200 => { #4 + Name => 'ExposureMode', + Writable => 'int16u', + PrintConv => { + 1 => 'Manual', + 2 => 'Program', #6 + 3 => 'Aperture-priority AE', + 4 => 'Shutter speed priority AE', + 5 => 'Program-shift', #6 + } + }, + 0x201 => { #6 + Name => 'AELock', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x202 => { #PH/4 + Name => 'MeteringMode', + Writable => 'int16u', + PrintConv => { + 2 => 'Center-weighted average', + 3 => 'Spot', + 5 => 'ESP', + 261 => 'Pattern+AF', #6 + 515 => 'Spot+Highlight control', #6 + 1027 => 'Spot+Shadow control', #6 + }, + }, + 0x203 => { Name => 'ExposureShift', Writable => 'rational64s' }, #11 (some models only) + 0x204 => { #11 (XZ-1) + Name => 'NDFilter', + PrintConv => \%offOn, + }, + 0x300 => { #6 + Name => 'MacroMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'Super Macro', #11 + }, + }, + 0x301 => { #6 + Name => 'FocusMode', + Writable => 'int16u', + Count => -1, + Notes => '1 or 2 values', + PrintConv => [{ + 0 => 'Single AF', + 1 => 'Sequential shooting AF', + 2 => 'Continuous AF', + 3 => 'Multi AF', + 4 => 'Face Detect', #11 + 10 => 'MF', + }, { + 0 => '(none)', + BITMASK => { #11 + 0 => 'S-AF', + 2 => 'C-AF', + 4 => 'MF', + 5 => 'Face Detect', + 6 => 'Imager AF', + 7 => 'Live View Magnification Frame', + 8 => 'AF sensor', + 9 => 'Starry Sky AF', #24 + }, + }], + }, + 0x302 => { #6 + Name => 'FocusProcess', + Writable => 'int16u', + Count => -1, + Notes => '1 or 2 values', + PrintConv => [{ + 0 => 'AF Not Used', + 1 => 'AF Used', + }], + # 2nd value written only by some models (u1050SW, u9000, uT6000, uT6010, + # uT8000, E-30, E-420, E-450, E-520, E-620, E-P1 and E-P2): - PH + # observed values when "AF Not Used": 0, 16 + # observed values when "AF Used": 64, 96(face detect on), 256 + }, + 0x303 => { #6 + Name => 'AFSearch', + Writable => 'int16u', + PrintConv => { + 0 => 'Not Ready', + 1 => 'Ready', + }, + }, + 0x304 => { #PH/4 + Name => 'AFAreas', + Notes => 'coordinates range from 0 to 255', + Writable => 'int32u', + Count => 64, + PrintConv => 'Image::ExifTool::Olympus::PrintAFAreas($val)', + }, + 0x0305 => { #PH + Name => 'AFPointSelected', + Notes => 'coordinates expressed as a percent', + Writable => 'rational64s', + Count => 5, + ValueConv => '$val =~ s/\S* //; $val', # ignore first undefined value + ValueConvInv => '"undef $val"', + PrintConv => q{ + return 'n/a' if $val =~ /undef/; + sprintf("(%d%%,%d%%) (%d%%,%d%%)", map {$_ * 100} split(" ",$val)); + }, + PrintConvInv => q{ + return 'undef undef undef undef' if $val eq 'n/a'; + my @nums = $val =~ /\d+(?:\.\d+)?/g; + return undef unless @nums == 4; + join ' ', map {$_ / 100} @nums; + }, + }, + 0x306 => { #11 + Name => 'AFFineTune', + Writable => 'int8u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x307 => { #15 + Name => 'AFFineTuneAdj', + Writable => 'int16s', + Count => 3, # not sure what the 3 values mean + }, + 0x308 => { #forum11578 + Name => 'FocusBracketStepSize', + Writable => 'int8u', + }, + 0x309 => { #forum13341 + Name => 'AISubjectTrackingMode', + Writable => 'int16u', + ValueConv => '($val >> 8) . " " . ($val & 0xff)', + ValueConvInv => 'my @a = split " ", $val; $val = $a[0]*256 + $a[1]', + PrintConv => [{ + 0 => 'Off', + 1 => 'Motorsports', + 2 => 'Airplanes', + 3 => 'Trains', + 4 => 'Birds', + 5 => 'Dogs & Cats', + },{ + 0 => 'Object Not Found', + 1 => 'Object Found', + }], + }, + 0x400 => { #6 + Name => 'FlashMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + BITMASK => { + 0 => 'On', + 1 => 'Fill-in', + 2 => 'Red-eye', + 3 => 'Slow-sync', + 4 => 'Forced On', + 5 => '2nd Curtain', + }, + }, + }, + 0x401 => { Name => 'FlashExposureComp', Writable => 'rational64s' }, #6 + # 0x402 - FlashMode? bit0=TTL, bit1=auto, bit2=SuperFP (ref 11) + 0x403 => { #11 + Name => 'FlashRemoteControl', + Writable => 'int16u', + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 0x01 => 'Channel 1, Low', + 0x02 => 'Channel 2, Low', + 0x03 => 'Channel 3, Low', + 0x04 => 'Channel 4, Low', + 0x09 => 'Channel 1, Mid', + 0x0a => 'Channel 2, Mid', + 0x0b => 'Channel 3, Mid', + 0x0c => 'Channel 4, Mid', + 0x11 => 'Channel 1, High', + 0x12 => 'Channel 2, High', + 0x13 => 'Channel 3, High', + 0x14 => 'Channel 4, High', + }, + }, + 0x404 => { #11 + Name => 'FlashControlMode', + Writable => 'int16u', + Count => -1, + Notes => '3 or 4 values', + PrintConv => [{ + 0 => 'Off', + 3 => 'TTL', + 4 => 'Auto', + 5 => 'Manual', + }], + }, + 0x405 => { #11 + Name => 'FlashIntensity', + Writable => 'rational64s', + Count => -1, + Notes => '3 or 4 values', + PrintConv => { + OTHER => sub { shift }, + 'undef undef undef' => 'n/a', + 'undef undef undef undef' => 'n/a (x4)', + }, + }, + 0x406 => { #11 + Name => 'ManualFlashStrength', + Writable => 'rational64s', + Count => -1, + Notes => '3 or 4 values', + PrintConv => { + OTHER => sub { shift }, + 'undef undef undef' => 'n/a', + 'undef undef undef undef' => 'n/a (x4)', + }, + }, + 0x500 => { #6 + Name => 'WhiteBalance2', + Writable => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Auto (Keep Warm Color Off)', #IB + 16 => '7500K (Fine Weather with Shade)', + 17 => '6000K (Cloudy)', + 18 => '5300K (Fine Weather)', + 20 => '3000K (Tungsten light)', + 21 => '3600K (Tungsten light-like)', + 22 => 'Auto Setup', #IB + 23 => '5500K (Flash)', #IB + 33 => '6600K (Daylight fluorescent)', + 34 => '4500K (Neutral white fluorescent)', + 35 => '4000K (Cool white fluorescent)', + 36 => 'White Fluorescent', #IB + 48 => '3600K (Tungsten light-like)', + 67 => 'Underwater', #IB + 256 => 'One Touch WB 1', #IB + 257 => 'One Touch WB 2', #IB + 258 => 'One Touch WB 3', #IB + 259 => 'One Touch WB 4', #IB + 512 => 'Custom WB 1', #IB + 513 => 'Custom WB 2', #IB + 514 => 'Custom WB 3', #IB + 515 => 'Custom WB 4', #IB + }, + }, + 0x501 => { #PH/4 + Name => 'WhiteBalanceTemperature', + Writable => 'int16u', + PrintConv => '$val ? $val : "Auto"', + PrintConvInv => '$val=~/^\d+$/ ? $val : 0', + }, + 0x502 => { #PH/4 + Name => 'WhiteBalanceBracket', + Writable => 'int16s', + }, + 0x503 => { #PH/4/6 + Name => 'CustomSaturation', + Writable => 'int16s', + Count => 3, + Notes => '3 numbers: 1. CS Value, 2. Min, 3. Max', + PrintConv => q{ + my ($a,$b,$c)=split ' ',$val; + if ($self->{Model} =~ /^E-1\b/) { + $a-=$b; $c-=$b; + return "CS$a (min CS0, max CS$c)"; + } else { + return "$a (min $b, max $c)"; + } + }, + }, + 0x504 => { #PH/4 + Name => 'ModifiedSaturation', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'CM1 (Red Enhance)', + 2 => 'CM2 (Green Enhance)', + 3 => 'CM3 (Blue Enhance)', + 4 => 'CM4 (Skin Tones)', + }, + }, + 0x505 => { #PH/4 + Name => 'ContrastSetting', + Writable => 'int16s', + Count => 3, + Notes => 'value, min, max', + PrintConv => 'my @v=split " ",$val; "$v[0] (min $v[1], max $v[2])"', + PrintConvInv => '$val=~tr/-0-9 //dc;$val', + }, + 0x506 => { #PH/4 + Name => 'SharpnessSetting', + Writable => 'int16s', + Count => 3, + Notes => 'value, min, max', + PrintConv => 'my @v=split " ",$val; "$v[0] (min $v[1], max $v[2])"', + PrintConvInv => '$val=~tr/-0-9 //dc;$val', + }, + 0x507 => { #PH/4 + Name => 'ColorSpace', + Writable => 'int16u', + PrintConv => { #6 + 0 => 'sRGB', + 1 => 'Adobe RGB', + 2 => 'Pro Photo RGB', + }, + }, + 0x509 => { #6 + Name => 'SceneMode', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Standard', + 6 => 'Auto', #6 + 7 => 'Sport', + 8 => 'Portrait', + 9 => 'Landscape+Portrait', + 10 => 'Landscape', + 11 => 'Night Scene', + 12 => 'Self Portrait', #11 + 13 => 'Panorama', #6 + 14 => '2 in 1', #11 + 15 => 'Movie', #11 + 16 => 'Landscape+Portrait', #6 + 17 => 'Night+Portrait', + 18 => 'Indoor', #11 (Party - PH) + 19 => 'Fireworks', + 20 => 'Sunset', + 21 => 'Beauty Skin', #PH + 22 => 'Macro', + 23 => 'Super Macro', #11 + 24 => 'Food', #11 + 25 => 'Documents', + 26 => 'Museum', + 27 => 'Shoot & Select', #11 + 28 => 'Beach & Snow', + 29 => 'Self Protrait+Timer', #11 + 30 => 'Candle', + 31 => 'Available Light', #11 + 32 => 'Behind Glass', #11 + 33 => 'My Mode', #11 + 34 => 'Pet', #11 + 35 => 'Underwater Wide1', #6 + 36 => 'Underwater Macro', #6 + 37 => 'Shoot & Select1', #11 + 38 => 'Shoot & Select2', #11 + 39 => 'High Key', + 40 => 'Digital Image Stabilization', #6 + 41 => 'Auction', #11 + 42 => 'Beach', #11 + 43 => 'Snow', #11 + 44 => 'Underwater Wide2', #6 + 45 => 'Low Key', #6 + 46 => 'Children', #6 + 47 => 'Vivid', #11 + 48 => 'Nature Macro', #6 + 49 => 'Underwater Snapshot', #11 + 50 => 'Shooting Guide', #11 + 54 => 'Face Portrait', #11 + 57 => 'Bulb', #11 + 59 => 'Smile Shot', #11 + 60 => 'Quick Shutter', #11 + 63 => 'Slow Shutter', #11 + 64 => 'Bird Watching', #11 + 65 => 'Multiple Exposure', #11 + 66 => 'e-Portrait', #11 + 67 => 'Soft Background Shot', #11 + 142 => 'Hand-held Starlight', #PH (SH-21) + 154 => 'HDR', #PH (XZ-2) + 197 => 'Panning', #forum11631 (EM5iii) + 203 => 'Light Trails', #forum11631 (EM5iii) + 204 => 'Backlight HDR', #forum11631 (EM5iii) + 205 => 'Silent', #forum11631 (EM5iii) + 206 => 'Multi Focus Shot', #forum11631 (EM5iii) + }, + }, + 0x50a => { #PH/4/6 + Name => 'NoiseReduction', + Writable => 'int16u', + PrintConv => { + 0 => '(none)', + BITMASK => { + 0 => 'Noise Reduction', + 1 => 'Noise Filter', + 2 => 'Noise Filter (ISO Boost)', + 3 => 'Auto', #11 + }, + }, + }, + 0x50b => { #6 + Name => 'DistortionCorrection', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x50c => { #PH/4 + Name => 'ShadingCompensation', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x50d => { Name => 'CompressionFactor', Writable => 'rational64u' }, #PH/4 + 0x50f => { #6 + Name => 'Gradation', + Writable => 'int16s', + Notes => '3 or 4 values', + Count => -1, + Relist => [ [0..2], 3 ], # join values 0-2 for PrintConv + PrintConv => [{ + '0 0 0' => 'n/a', #PH (?) + '-1 -1 1' => 'Low Key', + '0 -1 1' => 'Normal', + '1 -1 1' => 'High Key', + },{ + 0 => 'User-Selected', + 1 => 'Auto-Override', + }], + }, + 0x520 => { #6 + Name => 'PictureMode', + Writable => 'int16u', + Notes => '1 or 2 values', + Count => -1, + PrintConv => [{ + 1 => 'Vivid', + 2 => 'Natural', + 3 => 'Muted', + 4 => 'Portrait', + 5 => 'i-Enhance', #11 + 6 => 'e-Portrait', #23 + 7 => 'Color Creator', #23 + 9 => 'Color Profile 1', #23 + 10 => 'Color Profile 2', #23 + 11 => 'Color Profile 3', #23 + 12 => 'Monochrome Profile 1', #23 + 13 => 'Monochrome Profile 2', #23 + 14 => 'Monochrome Profile 3', #23 + 256 => 'Monotone', + 512 => 'Sepia', + }], + }, + 0x521 => { #6 + Name => 'PictureModeSaturation', + Writable => 'int16s', + Count => 3, + Notes => 'value, min, max', + PrintConv => 'my @v=split " ",$val; "$v[0] (min $v[1], max $v[2])"', + PrintConvInv => '$val=~tr/-0-9 //dc;$val', + }, + 0x522 => { #6 + Name => 'PictureModeHue', + Writable => 'int16s', + Unknown => 1, # (needs verification) + }, + 0x523 => { #6 + Name => 'PictureModeContrast', + Writable => 'int16s', + Count => 3, + Notes => 'value, min, max', + PrintConv => 'my @v=split " ",$val; "$v[0] (min $v[1], max $v[2])"', + PrintConvInv => '$val=~tr/-0-9 //dc;$val', + }, + 0x524 => { #6 + Name => 'PictureModeSharpness', + # verified as the Sharpness setting in the Picture Mode menu for the E-410 + Writable => 'int16s', + Count => 3, + Notes => 'value, min, max', + PrintConv => 'my @v=split " ",$val; "$v[0] (min $v[1], max $v[2])"', + PrintConvInv => '$val=~tr/-0-9 //dc;$val', + }, + 0x525 => { #6 + Name => 'PictureModeBWFilter', + Writable => 'int16s', + PrintConvColumns => 2, + PrintConv => { + 0 => 'n/a', + 1 => 'Neutral', + 2 => 'Yellow', + 3 => 'Orange', + 4 => 'Red', + 5 => 'Green', + }, + }, + 0x526 => { #6 + Name => 'PictureModeTone', + Writable => 'int16s', + PrintConvColumns => 2, + PrintConv => { + 0 => 'n/a', + 1 => 'Neutral', + 2 => 'Sepia', + 3 => 'Blue', + 4 => 'Purple', + 5 => 'Green', + }, + }, + 0x527 => { #12 + Name => 'NoiseFilter', + Writable => 'int16s', + Count => 3, + PrintConv => { + '0 0 0' => 'n/a', #PH (?) + '-2 -2 1' => 'Off', + '-1 -2 1' => 'Low', + '0 -2 1' => 'Standard', + '1 -2 1' => 'High', + }, + }, + 0x529 => { #PH + Name => 'ArtFilter', + Writable => 'int16u', + Count => 4, + PrintConvColumns => 2, + PrintConv => [ \%filters ], + }, + 0x52c => { #PH + Name => 'MagicFilter', + Writable => 'int16u', + Count => 4, # (2nd number is 0, 1280 or 1792, 3rd/4th are 0) + # (1792 observed for E-5 Gentle Sepia and XZ-1 Dramatic Tone) + PrintConvColumns => 2, + PrintConv => [ \%filters ], + }, + 0x52d => { #11 + Name => 'PictureModeEffect', + Writable => 'int16s', + Count => 3, + PrintConv => { + '0 0 0' => 'n/a', #PH (?) + '-1 -1 1' => 'Low', + '0 -1 1' => 'Standard', + '1 -1 1' => 'High', + }, + }, + 0x52e => { #11/PH + Name => 'ToneLevel', + PrintConv => [ + \%toneLevelType, + undef, # (highlights value) + undef, # (highlights min) + undef, # (highlights max) + \%toneLevelType, + undef, # (shadows value) + undef, # (shadows min) + undef, # (shadows max) + \%toneLevelType, + undef, # (midtones value) + undef, # (midtones min) + undef, # (midtones max) + \%toneLevelType, + undef, + undef, + undef, + \%toneLevelType, + undef, + undef, + undef, + \%toneLevelType, + undef, + undef, + undef, + \%toneLevelType, + undef, + undef, + undef, + ] + }, + 0x52f => { #PH + Name => 'ArtFilterEffect', + Writable => 'int16u', + Count => 20, + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => [ + \%filters, + undef, + undef, + '"Partial Color $val"', #23 + { # there are 5 available art filter effects for the E-PL3... + 0x0000 => 'No Effect', + 0x8010 => 'Star Light', + 0x8020 => 'Pin Hole', + 0x8030 => 'Frame', + 0x8040 => 'Soft Focus', + 0x8050 => 'White Edge', + 0x8060 => 'B&W', # (NC - E-PL2 with "Grainy Film" filter) + 0x8080 => 'Blur Top and Bottom', #23 + 0x8081 => 'Blur Left and Right', #23 + # (E-PL2 also has "Pict. Tone" effect) + }, + undef, + { #23 + 0 => 'No Color Filter', + 1 => 'Yellow Color Filter', + 2 => 'Orange Color Filter', + 3 => 'Red Color Filter', + 4 => 'Green Color Filter', + }, + ], + }, + 0x532 => { #23 + Name => 'ColorCreatorEffect', + Writable => 'int16s', + Count => 6, + PrintConv => [ + '"Color $val"', + undef, # (Color min) + undef, # (Color max) + '"Strength $val"', + undef, # (Strength min) + undef, # (Strength max) + ], + }, + 0x537 => { #23 + Name => 'MonochromeProfileSettings', + Writable => 'int16s', + Count => 6, + PrintConv => [ + { + 0 => 'No Filter', + 1 => 'Yellow Filter', + 2 => 'Orange Filter', + 3 => 'Red Filter', + 4 => 'Magenta Filter', + 5 => 'Blue Filter', + 6 => 'Cyan Filter', + 7 => 'Green Filter', + 8 => 'Yellow-green Filter', + }, + undef, # (Filter number min) + undef, # (Filter number max) + '"Strength $val"', + undef, # (Strength min) + undef, # (Strength max) + ], + }, + 0x538 => { #23 + Name => 'FilmGrainEffect', + Writable => 'int16s', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Medium', + 3 => 'High', + }, + }, + 0x539 => { #23 + Name => 'ColorProfileSettings', + Writable => 'int16s', + Count => 14, + PrintConv => [ + '"Min $val"', + '"Max $val"', + '"Yellow $val"', + '"Orange $val"', + '"Orange-red $val"', + '"Red $val"', + '"Magenta $val"', + '"Violet $val"', + '"Blue $val"', + '"Blue-cyan $val"', + '"Cyan $val"', + '"Green-cyan $val"', + '"Green $val"', + '"Yellow-green $val"', + ], + }, + 0x53a => { #23 + Name => 'MonochromeVignetting', + Writable => 'int16s', + Notes => '-5 to +5: positive is white vignetting, negative is black vignetting', + }, + 0x53b => { #23 + Name => 'MonochromeColor', + Writable => 'int16s', + PrintConv => { + 0 => '(none)', + 1 => 'Normal', + 2 => 'Sepia', + 3 => 'Blue', + 4 => 'Purple', + 5 => 'Green', + }, + }, + 0x600 => { #PH/4/22 + Name => 'DriveMode', + Writable => 'int16u', + Count => -1, + Notes => '2, 3 or 5 numbers: 1. Mode, 2. Shot number, 3. Mode bits, 5. Shutter mode', + PrintConv => q{ + my ($a,$b,$c,$d,$e) = split ' ',$val; + if ($e) { + $e = '; ' . ({ 2 => 'Anti-shock 0', 4 => 'Electronic shutter' }->{$e} || "Unknown ($e)"); + } else { + $e = ''; + } + return "Single Shot$e" unless $a; + if ($a == 5 and defined $c) { + $a = DecodeBits($c, { #6 + 0 => 'AE', + 1 => 'WB', + 2 => 'FL', + 3 => 'MF', + 4 => 'ISO', #forum8906 + 5 => 'AE Auto', #forum8906 + 6 => 'Focus', #PH + }) . ' Bracketing'; + $a =~ s/, /+/g; + } else { + my %a = ( + 1 => 'Continuous Shooting', + 2 => 'Exposure Bracketing', + 3 => 'White Balance Bracketing', + 4 => 'Exposure+WB Bracketing', #6 + ); + $a = $a{$a} || "Unknown ($a)"; + } + return "$a, Shot $b$e"; + }, + }, + 0x601 => { #6 + Name => 'PanoramaMode', + Writable => 'int16u', + Notes => '2 numbers: 1. Mode, 2. Shot number', + PrintConv => q{ + my ($a,$b) = split ' ',$val; + return 'Off' unless $a; + my %a = ( + 1 => 'Left to Right', + 2 => 'Right to Left', + 3 => 'Bottom to Top', + 4 => 'Top to Bottom', + ); + return(($a{$a} || "Unknown ($a)") . ', Shot ' . $b); + }, + }, + 0x603 => { #PH/4 + Name => 'ImageQuality2', + Writable => 'int16u', + PrintConv => { + 1 => 'SQ', + 2 => 'HQ', + 3 => 'SHQ', + 4 => 'RAW', + 5 => 'SQ (5)', # (E-500) + }, + }, + 0x604 => { #PH + Name => 'ImageStabilization', + Writable => 'int32u', + DataMember => 'ImageStabilization', + RawConv => '$$self{ImageStabilization} = $val', + PrintConv => { + 0 => 'Off', + 1 => 'On, Mode 1', + 2 => 'On, Mode 2', + 3 => 'On, Mode 3', + 4 => 'On, Mode 4', # (NC, E-P5) + }, + }, + 0x804 => { #PH (E-M1 with firmware update) + Name => 'StackedImage', + Writable => 'int32u', + Count => 2, + PrintConv => { + '0 0' => 'No', + '1 *' => 'Live Composite (* images)', #24 + '4 *' => 'Live Time/Bulb (* images)', #24 + '3 2' => 'ND2 (1EV)', #IB + '3 4' => 'ND4 (2EV)', #IB + '3 8' => 'ND8 (3EV)', #IB + '3 16' => 'ND16 (4EV)', #IB + '3 32' => 'ND32 (5EV)', #IB + '3 64' => 'ND64 (6EV)', #forum13341 + '5 4' => 'HDR1', #forum8906 + '6 4' => 'HDR2', #forum8906 + '8 8' => 'Tripod high resolution', #IB + '9 *' => 'Focus-stacked (* images)', #IB (* = 2-15) + '11 12' => 'Hand-held high resolution (11 12)', #forum13341 (OM-1) + '11 16' => 'Hand-held high resolution (11 16)', #IB (perhaps '11 15' would be possible, ref 24) + OTHER => sub { + my ($val, $inv, $conv) = @_; + if ($inv) { + $val = lc $val; + return undef unless $val =~ s/(\d+) images/\* images/; + my $num = $1; + foreach (keys %$conv) { + next unless $val eq lc $$conv{$_}; + ($val = $_) =~ s/\*/$num/ or return undef; + return $val; + } + } else { + return "Unknown ($_[0])" unless $val =~ s/ (\d+)/ \*/ and $$conv{$val}; + my $num = $1; + ($val = $$conv{$val}) =~ s/\*/$num/; + return $val; + } + }, + }, + }, + 0x900 => { #11 + Name => 'ManometerPressure', + Writable => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => '"$val kPa"', + PrintConvInv => '$val=~s/ ?kPa//i; $val', + }, + 0x901 => { #PH (u770SW) + # 2 numbers: 1st looks like meters above sea level, 2nd is usually 3x the 1st (feet?) + Name => 'ManometerReading', + Writable => 'int32s', + Count => 2, + ValueConv => 'my @a=split(" ",$val); $_ /= 10 foreach @a; "@a"', + ValueConvInv => 'my @a=split(" ",$val); $_ *= 10 foreach @a; "@a"', + PrintConv => '$val=~s/(\S+) (\S+)/$1 m, $2 ft/; $val', + PrintConvInv => '$val=~s/ ?(m|ft)//gi; $val', + }, + 0x902 => { #11 + Name => 'ExtendedWBDetect', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x903 => { #11 + Name => 'RollAngle', + Notes => 'converted to degrees of clockwise camera rotation', + Writable => 'int16s', + Count => 2, # (second value is 0 if level gauge is off) + # negate to express as clockwise rotation + ValueConv => '$val=~s/ 1$// ? -$val/10 : "n/a"', + ValueConvInv => 'IsFloat($val) ? sprintf("%.0f 1",-$val*10) : "0 0"', + }, + 0x904 => { #11 + Name => 'PitchAngle', + Notes => 'converted to degrees of upward camera tilt', + Writable => 'int16s', + Count => 2, # (second value is 0 if level gauge is off) + ValueConv => '$val =~ s/ 1$// ? $val / 10 : "n/a"', + ValueConvInv => 'IsFloat($val) ? sprintf("%.0f 1",$val*10) : "0 0"', + }, + 0x908 => { #PH (NC, E-M1) + Name => 'DateTimeUTC', + Writable => 'string', + Groups => { 2 => 'Time' }, + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + }, +); + +# Olympus RAW processing IFD (ref 6) +%Image::ExifTool::Olympus::RawDevelopment = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x000 => { #PH + Name => 'RawDevVersion', + Writable => 'undef', + RawConv => '$val=~s/\0+$//; $val', # (may be null terminated) + Count => 4, + }, + 0x100 => { Name => 'RawDevExposureBiasValue', Writable => 'rational64s' }, + 0x101 => { Name => 'RawDevWhiteBalanceValue', Writable => 'int16u' }, + 0x102 => { Name => 'RawDevWBFineAdjustment', Writable => 'int16s' }, + 0x103 => { Name => 'RawDevGrayPoint', Writable => 'int16u', Count => 3 }, + 0x104 => { Name => 'RawDevSaturationEmphasis', Writable => 'int16s', Count => 3 }, + 0x105 => { Name => 'RawDevMemoryColorEmphasis', Writable => 'int16u' }, + 0x106 => { Name => 'RawDevContrastValue', Writable => 'int16s', Count => 3 }, + 0x107 => { Name => 'RawDevSharpnessValue', Writable => 'int16s', Count => 3 }, + 0x108 => { + Name => 'RawDevColorSpace', + Writable => 'int16u', + PrintConv => { #11 + 0 => 'sRGB', + 1 => 'Adobe RGB', + 2 => 'Pro Photo RGB', + }, + }, + 0x109 => { + Name => 'RawDevEngine', + Writable => 'int16u', + PrintConv => { #11 + 0 => 'High Speed', + 1 => 'High Function', + 2 => 'Advanced High Speed', + 3 => 'Advanced High Function', + }, + }, + 0x10a => { + Name => 'RawDevNoiseReduction', + Writable => 'int16u', + PrintConv => { #11 + 0 => '(none)', + BITMASK => { + 0 => 'Noise Reduction', + 1 => 'Noise Filter', + 2 => 'Noise Filter (ISO Boost)', + }, + }, + }, + 0x10b => { + Name => 'RawDevEditStatus', + Writable => 'int16u', + PrintConv => { #11 + 0 => 'Original', + 1 => 'Edited (Landscape)', + 6 => 'Edited (Portrait)', + 8 => 'Edited (Portrait)', + }, + }, + 0x10c => { + Name => 'RawDevSettings', + Writable => 'int16u', + PrintConv => { #11 + 0 => '(none)', + BITMASK => { + 0 => 'WB Color Temp', + 1 => 'WB Gray Point', + 2 => 'Saturation', + 3 => 'Contrast', + 4 => 'Sharpness', + 5 => 'Color Space', + 6 => 'High Function', + 7 => 'Noise Reduction', + }, + }, + }, +); + +# Olympus RAW processing B IFD (ref 11) +%Image::ExifTool::Olympus::RawDevelopment2 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x000 => { + Name => 'RawDevVersion', + Writable => 'undef', + RawConv => '$val=~s/\0+$//; $val', # (may be null terminated) + Count => 4, + }, + 0x100 => { Name => 'RawDevExposureBiasValue', Writable => 'rational64s' }, + 0x101 => { + Name => 'RawDevWhiteBalance', + Writable => 'int16u', + PrintConv => { + 1 => 'Color Temperature', + 2 => 'Gray Point', + }, + }, + 0x102 => { Name => 'RawDevWhiteBalanceValue', Writable => 'int16u' }, + 0x103 => { Name => 'RawDevWBFineAdjustment', Writable => 'int16s' }, + 0x104 => { Name => 'RawDevGrayPoint', Writable => 'int16u', Count => 3 }, + 0x105 => { Name => 'RawDevContrastValue', Writable => 'int16s', Count => 3 }, + 0x106 => { Name => 'RawDevSharpnessValue', Writable => 'int16s', Count => 3 }, + 0x107 => { Name => 'RawDevSaturationEmphasis', Writable => 'int16s', Count => 3 }, + 0x108 => { Name => 'RawDevMemoryColorEmphasis', Writable => 'int16u' }, + 0x109 => { + Name => 'RawDevColorSpace', + Writable => 'int16u', + PrintConv => { + 0 => 'sRGB', + 1 => 'Adobe RGB', + 2 => 'Pro Photo RGB', + }, + }, + 0x10a => { + Name => 'RawDevNoiseReduction', + Writable => 'int16u', + PrintConv => { + 0 => '(none)', + BITMASK => { + 0 => 'Noise Reduction', + 1 => 'Noise Filter', + 2 => 'Noise Filter (ISO Boost)', + }, + }, + }, + 0x10b => { + Name => 'RawDevEngine', + Writable => 'int16u', + PrintConv => { + 0 => 'High Speed', + 1 => 'High Function', + }, + }, + 0x10c => { + Name => 'RawDevPictureMode', + Writable => 'int16u', + PrintConv => { + 1 => 'Vivid', + 2 => 'Natural', + 3 => 'Muted', + 256 => 'Monotone', + 512 => 'Sepia', + }, + }, + 0x10d => { Name => 'RawDevPMSaturation', Writable => 'int16s', Count => 3 }, + 0x10e => { Name => 'RawDevPMContrast', Writable => 'int16s', Count => 3 }, + 0x10f => { Name => 'RawDevPMSharpness', Writable => 'int16s', Count => 3 }, + 0x110 => { + Name => 'RawDevPM_BWFilter', + Writable => 'int16u', + PrintConv => { + 1 => 'Neutral', + 2 => 'Yellow', + 3 => 'Orange', + 4 => 'Red', + 5 => 'Green', + }, + }, + 0x111 => { + Name => 'RawDevPMPictureTone', + Writable => 'int16u', + PrintConv => { + 1 => 'Neutral', + 2 => 'Sepia', + 3 => 'Blue', + 4 => 'Purple', + 5 => 'Green', + }, + }, + 0x112 => { Name => 'RawDevGradation', Writable => 'int16s', Count => 3 }, + 0x113 => { Name => 'RawDevSaturation3', Writable => 'int16s', Count => 3 }, #(NC Count) + 0x119 => { Name => 'RawDevAutoGradation', Writable => 'int16u', PrintConv => \%offOn }, + 0x120 => { Name => 'RawDevPMNoiseFilter', Writable => 'int16u' }, #(NC format) + 0x121 => { #PH (E-P5) + Name => 'RawDevArtFilter', + Writable => 'int16u', + Count => 4, + PrintConvColumns => 2, + PrintConv => [ \%filters ], + }, +); + +# Olympus Image processing IFD +%Image::ExifTool::Olympus::ImageProcessing = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x000 => { #PH + Name => 'ImageProcessingVersion', + Writable => 'undef', + RawConv => '$val=~s/\0+$//; $val', # (may be null terminated) + Count => 4, + }, + 0x100 => { Name => 'WB_RBLevels', Writable => 'int16u', Count => 2 }, #6 + # 0x101 - in-camera AutoWB unless it is all 0's or all 256's (ref IB) + 0x102 => { Name => 'WB_RBLevels3000K', Writable => 'int16u', Count => 2 }, #11 + 0x103 => { Name => 'WB_RBLevels3300K', Writable => 'int16u', Count => 2 }, #11 + 0x104 => { Name => 'WB_RBLevels3600K', Writable => 'int16u', Count => 2 }, #11 + 0x105 => { Name => 'WB_RBLevels3900K', Writable => 'int16u', Count => 2 }, #11 + 0x106 => { Name => 'WB_RBLevels4000K', Writable => 'int16u', Count => 2 }, #11 + 0x107 => { Name => 'WB_RBLevels4300K', Writable => 'int16u', Count => 2 }, #11 + 0x108 => { Name => 'WB_RBLevels4500K', Writable => 'int16u', Count => 2 }, #11 + 0x109 => { Name => 'WB_RBLevels4800K', Writable => 'int16u', Count => 2 }, #11 + 0x10a => { Name => 'WB_RBLevels5300K', Writable => 'int16u', Count => 2 }, #11 + 0x10b => { Name => 'WB_RBLevels6000K', Writable => 'int16u', Count => 2 }, #11 + 0x10c => { Name => 'WB_RBLevels6600K', Writable => 'int16u', Count => 2 }, #11 + 0x10d => { Name => 'WB_RBLevels7500K', Writable => 'int16u', Count => 2 }, #11 + 0x10e => { Name => 'WB_RBLevelsCWB1', Writable => 'int16u', Count => 2 }, #11 + 0x10f => { Name => 'WB_RBLevelsCWB2', Writable => 'int16u', Count => 2 }, #11 + 0x110 => { Name => 'WB_RBLevelsCWB3', Writable => 'int16u', Count => 2 }, #11 + 0x111 => { Name => 'WB_RBLevelsCWB4', Writable => 'int16u', Count => 2 }, #11 + 0x113 => { Name => 'WB_GLevel3000K', Writable => 'int16u' }, #11 + 0x114 => { Name => 'WB_GLevel3300K', Writable => 'int16u' }, #11 + 0x115 => { Name => 'WB_GLevel3600K', Writable => 'int16u' }, #11 + 0x116 => { Name => 'WB_GLevel3900K', Writable => 'int16u' }, #11 + 0x117 => { Name => 'WB_GLevel4000K', Writable => 'int16u' }, #11 + 0x118 => { Name => 'WB_GLevel4300K', Writable => 'int16u' }, #11 + 0x119 => { Name => 'WB_GLevel4500K', Writable => 'int16u' }, #11 + 0x11a => { Name => 'WB_GLevel4800K', Writable => 'int16u' }, #11 + 0x11b => { Name => 'WB_GLevel5300K', Writable => 'int16u' }, #11 + 0x11c => { Name => 'WB_GLevel6000K', Writable => 'int16u' }, #11 + 0x11d => { Name => 'WB_GLevel6600K', Writable => 'int16u' }, #11 + 0x11e => { Name => 'WB_GLevel7500K', Writable => 'int16u' }, #11 + 0x11f => { Name => 'WB_GLevel', Writable => 'int16u' }, #11 + # 0x121 = WB preset for flash (about 6000K) (ref IB) + # 0x125 = WB preset for underwater (ref IB) + 0x200 => { #6 + Name => 'ColorMatrix', + Writable => 'int16u', + Format => 'int16s', + Count => 9, + }, + # color matrices (ref 11): + # 0x0201-0x020d are sRGB color matrices + # 0x020e-0x021a are Adobe RGB color matrices + # 0x021b-0x0227 are ProPhoto RGB color matrices + # 0x0228 and 0x0229 are ColorMatrix for E-330 + # 0x0250-0x0252 are sRGB color matrices + # 0x0253-0x0255 are Adobe RGB color matrices + # 0x0256-0x0258 are ProPhoto RGB color matrices + 0x300 => { Name => 'Enhancer', Writable => 'int16u' }, #11 + 0x301 => { Name => 'EnhancerValues', Writable => 'int16u', Count => 7 }, #11 + 0x310 => { Name => 'CoringFilter', Writable => 'int16u' }, #11 + 0x311 => { Name => 'CoringValues', Writable => 'int16u', Count => 7 }, #11 + 0x600 => { Name => 'BlackLevel2', Writable => 'int16u', Count => 4 }, #11 + 0x610 => { Name => 'GainBase', Writable => 'int16u' }, #11 + 0x611 => { Name => 'ValidBits', Writable => 'int16u', Count => 2 }, #4/6 + 0x612 => { Name => 'CropLeft', Writable => 'int16u', Count => 2 }, #11 + 0x613 => { Name => 'CropTop', Writable => 'int16u', Count => 2 }, #11 + 0x614 => { Name => 'CropWidth', Writable => 'int32u' }, #PH/11 + 0x615 => { Name => 'CropHeight', Writable => 'int32u' }, #PH/11 + 0x635 => { #PH (data starts with "CMIO\x01\0") + Name => 'UnknownBlock1', + Writable => 'undef', + Notes => 'large unknown data block in ORF images but not JPG images', + # 'Drop' because too large for APP1 in JPEG images + Flags => [ 'Unknown', 'Binary', 'Drop' ], + }, + 0x636 => { #PH (data starts with "CMIO\x01\0") + Name => 'UnknownBlock2', + Writable => 'undef', + Notes => 'large unknown data block in ORF images but not JPG images', + # 'Drop' because too large for APP1 in JPEG images + Flags => [ 'Unknown', 'Binary', 'Drop' ], + }, + # 0x800 LensDistortionParams, float[9] (ref 11) + # 0x801 LensShadingParams, int16u[16] (ref 11) + 0x0805 => { #IB + Name => 'SensorCalibration', + Notes => '2 numbers: 1. Recommended maximum, 2. Calibration midpoint', + Writable => 'int16s', + Count => 2, + }, + # 0x1010-0x1012 are the processing options used in camera or in + # Olympus software, which 0x050a-0x050c are in-camera only (ref 6) + 0x1010 => { #PH/4 + Name => 'NoiseReduction2', + Writable => 'int16u', + PrintConv => { + 0 => '(none)', + BITMASK => { + 0 => 'Noise Reduction', + 1 => 'Noise Filter', + 2 => 'Noise Filter (ISO Boost)', + }, + }, + }, + 0x1011 => { #6 + Name => 'DistortionCorrection2', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x1012 => { #PH/4 + Name => 'ShadingCompensation2', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x101c => { #11 + Name => 'MultipleExposureMode', + Writable => 'int16u', + Count => 2, + PrintConv => [{ + 0 => 'Off', + 1 => 'Live Composite', #github issue#61 + 2 => 'On (2 frames)', + 3 => 'On (3 frames)', + }], + }, + 0x1103 => { #PH + Name => 'UnknownBlock3', + Writable => 'undef', + Notes => 'large unknown data block in ORF images but not JPG images', + # 'Drop' because too large for APP1 in JPEG images + Flags => [ 'Unknown', 'Binary', 'Drop' ], + }, + 0x1104 => { #PH (overlaps data for 0x1103 in E-M5 ORF images) + Name => 'UnknownBlock4', + Writable => 'undef', + Notes => 'large unknown data block in ORF images but not JPG images', + # 'Drop' because too large for APP1 in JPEG images + Flags => [ 'Unknown', 'Binary', 'Drop' ], + }, + 0x1112 => { #11 + Name => 'AspectRatio', + Writable => 'int8u', + Count => 2, + PrintConv => { + # '0 0' - have seen this with a 16:9 XZ-1 image - PH + '1 1' => '4:3', + '1 4' => '1:1', #PH (E-P5 Storyboard effect, does this indicate 4:3 converted to 6:6?) + '2 1' => '3:2 (RAW)', #forum6285 + '2 2' => '3:2', + '3 1' => '16:9 (RAW)', #forum6285 + '3 3' => '16:9', + '4 1' => '1:1 (RAW)', #forum6285 + '4 4' => '6:6', + '5 5' => '5:4', + '6 6' => '7:6', + '7 7' => '6:5', + '8 8' => '7:5', + '9 1' => '3:4 (RAW)', #forum6285 + '9 9' => '3:4', + }, + }, + 0x1113 => { Name => 'AspectFrame', Writable => 'int16u', Count => 4 }, #11 + 0x1200 => { #11/PH + Name => 'FacesDetected', + Writable => 'int32u', + Count => -1, + Notes => '2 or 3 values', + }, + 0x1201 => { #11/PH + Name => 'FaceDetectArea', + Writable => 'int16s', + Count => -1, # (varies with model) + Binary => 1, # (too long) + Notes => q{ + for models with 2 values in FacesDetected this gives X/Y coordinates in the + FaceDetectFrame for all 4 corners of the face rectangle. For models with 3 + values in FacesDetected this gives X/Y coordinates, size and rotation angle + of the face detect square + }, + }, + 0x1202 => { Name => 'MaxFaces', Writable => 'int32u', Count => 3 }, #PH + 0x1203 => { #PH + Name => 'FaceDetectFrameSize', + Writable => 'int16u', + Count => 6, + Notes => 'width/height of the full face detect frame', + }, + 0x1207 => { #PH + Name => 'FaceDetectFrameCrop', + Writable => 'int16s', + Count => 12, + Notes => 'X/Y offset and width/height of the cropped face detect frame', + }, + 0x1306 => { #PH (NC, E-M1) + Name => 'CameraTemperature', + Writable => 'int16u', + Format => 'int16s', #(NC) + ValueConv => '$val ? $val : undef', # zero for some models (how to differentiate from 0 C?) + Notes => 'this seems to be in degrees C only for some models', + }, + 0x1900 => { #23 + Name => 'KeystoneCompensation', + Writable => 'int8u', + Count => 2, + PrintConv => { + '0 0' => 'Off', + '0 1' => 'On', + }, + }, + 0x1901 => { #23 + Name => 'KeystoneDirection', + Writable => 'int8u', + Count => 2, + PrintConv => { + 0 => 'Vertical', + 1 => 'Horizontal', + }, + }, + # 0x1905 - focal length (PH, E-M1) + 0x1906 => { #23 + Name => 'KeystoneValue', + Writable => 'int16s', + Count => 3, + # (use in conjunction with KeystoneDirection, -ve is Top or Right, +ve is Bottom or Left) + Notes => '3 numbers: 1. Keystone Value, 2. Min, 3. Max', + }, +); + +# Olympus Focus Info IFD +%Image::ExifTool::Olympus::FocusInfo = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x000 => { #PH + Name => 'FocusInfoVersion', + Writable => 'undef', + RawConv => '$val=~s/\0+$//; $val', # (may be null terminated) + Count => 4, + }, + 0x209 => { #PH/4 + Name => 'AutoFocus', + Writable => 'int16u', + PrintConv => \%offOn, + Unknown => 1, #6 + }, + 0x210 => { Name => 'SceneDetect', Writable => 'int16u' }, #11 + 0x211 => { #11 + Name => 'SceneArea', + Writable => 'int32u', + Count => 8, + Unknown => 1, # (numbers don't make much sense?) + }, + 0x212 => { #11 + Name => 'SceneDetectData', + Writable => 'int32u', + Count => 720, + Binary => 1, + Unknown => 1, # (but what does it mean?) + }, + # 0x214 - int16u: normally 0, but 1 for E-M1 focus-bracketing, and have seen 1 and 256 at other times + 0x300 => { Name => 'ZoomStepCount', Writable => 'int16u' }, #6 + 0x301 => { Name => 'FocusStepCount', Writable => 'int16u' }, #11 + 0x303 => { Name => 'FocusStepInfinity', Writable => 'int16u' }, #11 + 0x304 => { Name => 'FocusStepNear', Writable => 'int16u' }, #11 + 0x305 => { #4 + Name => 'FocusDistance', + Writable => 'rational64u', + # this rational value looks like it is in mm when the denominator is + # 1 (E-1), and cm when denominator is 10 (E-300), so if we ignore the + # denominator we are consistently in mm - PH + Format => 'int32u', + Count => 2, + ValueConv => q{ + my ($a,$b) = split ' ',$val; + return 0 if $a == 0xffffffff; + return $a / 1000; + }, + ValueConvInv => q{ + return '4294967295 1' unless $val; + $val = int($val * 1000 + 0.5); + return "$val 1"; + }, + PrintConv => '$val ? "$val m" : "inf"', + PrintConvInv => '$val eq "inf" ? 0 : $val=~s/\s*m$//, $val', + }, + 0x308 => [ # NEED A BETTER WAY TO DETERMINE WHICH MODELS USE WHICH ENCODING! + { + Name => 'AFPoint', + Condition => '$$self{Model} =~ /E-(3|5|30)\b/', + Writable => 'int16u', + PrintHex => 1, + # decoded by ref 6 + Notes => q{ + for the E-3, E-5 and E-30 the value is separated into 2 parts: low 5 bits + give AF point, upper bits give AF target selection mode + }, + ValueConv => '($val & 0x1f) . " " . ($val & 0xffe0)', + ValueConvInv => 'my @v=split(" ",$val); @v == 2 ? $v[0] + $v[1] : $val', + PrintConvColumns => 2, + PrintConv => [ + { + 0x00 => '(none)', + 0x01 => 'Top-left (horizontal)', + 0x02 => 'Top-center (horizontal)', + 0x03 => 'Top-right (horizontal)', + 0x04 => 'Left (horizontal)', + 0x05 => 'Mid-left (horizontal)', + 0x06 => 'Center (horizontal)', + 0x07 => 'Mid-right (horizontal)', + 0x08 => 'Right (horizontal)', + 0x09 => 'Bottom-left (horizontal)', + 0x0a => 'Bottom-center (horizontal)', + 0x0b => 'Bottom-right (horizontal)', + 0x0c => 'Top-left (vertical)', + 0x0d => 'Top-center (vertical)', + 0x0e => 'Top-right (vertical)', + 0x0f => 'Left (vertical)', + 0x10 => 'Mid-left (vertical)', + 0x11 => 'Center (vertical)', + 0x12 => 'Mid-right (vertical)', + 0x13 => 'Right (vertical)', + 0x14 => 'Bottom-left (vertical)', + 0x15 => 'Bottom-center (vertical)', + 0x16 => 'Bottom-right (vertical)', + 0x1f => 'n/a', #PH (NC, E-3) + }, + { + 0x00 => 'Single Target', + 0x40 => 'All Target', + 0x80 => 'Dynamic Single Target', + 0xe0 => 'n/a', #PH (NC, E-3) + } + ], + },{ #PH (models with 7-point AF) + Name => 'AFPoint', + Condition => '$$self{Model} =~ /E-(520|600|620)\b/', + Notes => 'models with 7-point AF', + Writable => 'int16u', + PrintHex => 1, + ValueConv => '($val & 0x1f) . " " . ($val & 0xffe0)', + ValueConvInv => 'my @v=split(" ",$val); @v == 2 ? $v[0] + $v[1] : $val', + PrintConv => [ # herb values added: + # based on code of W.P. in https://exiftool.org/forum/index.php?topic=14144.0 + { + # 0x00 => '(none)', + # 0x01 => 'Center', + # need to fill this in... + 0x00 => '(none)', + 0x02 => 'Top-center (horizontal)', + 0x04 => 'Right (horizontal)', + 0x05 => 'Mid-right (horizontal)', + 0x06 => 'Center (horizontal)', + 0x07 => 'Mid-left (horizontal)', + 0x08 => 'Left (horizontal)', + 0x0a => 'Bottom-center (horizontal)', + 0x0c => 'Top-center (vertical)', + 0x0f => 'Right (vertical)', + 0x15 => 'Bottom-center (vertical)', + 0x10 => 'Mid-right (vertical)', + 0x11 => 'Center (vertical)', + 0x12 => 'Mid-left (vertical)', + 0x13 => 'Left (vertical)', + }, + { + 0x00 => 'Single Target', + 0x40 => 'All Target', # (guess) + }, + ] + },{ #herb all camera model except E-Mxxx and OM-x + Name => 'AFPoint', + Condition => '$$self{Model} !~ /^(E-M|OM-)/ ', + Writable => 'int16u', + Notes => 'models other than E-Mxxx and OM-x', + RawConv => '($val or $$self{Model} ne "E-P1") ? $val : undef', + PrintConv => { + # (E-P1 always writes 0, maybe other models do too - PH) + 0 => 'Left (or n/a)', + 1 => 'Center (horizontal)', #6 (E-510) + 2 => 'Right', + 3 => 'Center (vertical)', #6 (E-510) + 255 => 'None', + }, + },{ #herb all newer models E-Mxxx and OM-x; we do not know details + Name => 'AFPoint', + Writable => 'int16u', + Notes => 'other models', + } + ], + # 0x31a Continuous AF parameters? + 0x31b => [ #herb, based on investigations of abgestumpft: https://exiftool.org/forum/index.php?topic=14527.0 + # for newer models E-Mxxx and OM-x + { + Name => 'AFPointDetails', + Condition => '$$self{Model} =~ m/^E-M|^OM-/ ', + Writable => 'int16u', + Notes => 'models E-Mxxx and OM-x', + PrintHex => 1, + ValueConv => '(($val >> 13) & 0x7) . " " . (($val >> 12) & 0x1) . " " . (($val >> 11) & 0x1) . " " . + # subject detect face and eye half press + (($val >> 8) & 0x3) . " " . (($val >> 7) & 0x1) . " " . (($val >> 5) & 0x1) . " " . + # eye AF face detect x-AF with MF + (($val >> 4) & 0x1) . " " . (($val >> 3) & 0x1) . " " . ($val & 0x7)', + # release object found MF... + PrintConvColumns => 4, + PrintConv => [ + { + # should be identical to AISubjectTrackingMode + 0 => 'No Subject Detection', + 1 => 'Motorsports', + 2 => 'Airplanes', + 3 => 'Trains', + 4 => 'Birds', + 5 => 'Dogs & Cats', + },{ + 0 => 'Face Priority', + 1 => 'Target Priority', + },{ + 0 => 'Normal AF', + 1 => 'AF on Half Press', + },{ + 0 => 'No Eye-AF', + 1 => 'Right Eye Priority', + 2 => 'Left Eye Priority', + 3 => 'Both Eyes Priority', + },{ + 0 => 'No Face Detection', + 1 => 'Face Detection', + },{ + 0 => 'No MF', + 1 => 'With MF', + },{ + 0 => 'AF Priority', + 1 => 'Release Priority', + },{ + 0 => 'No Object found', + 1 => 'Object found', + },{ + 0 => 'MF', + 1 => 'S-AF', + 2 => 'C-AF', + 6 => 'C-AF + TR', + }, + ], + },{ # for older models + Name => 'AFPointDetails', + Writable => 'int16u', + Notes => 'other models', + } + ], + 0x328 => { #PH + Name => 'AFInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::AFInfo' }, + }, + # 0x1200-0x1209 Flash information: + 0x1201 => { #6 + Name => 'ExternalFlash', + Writable => 'int16u', + Count => 2, + PrintConv => { + '0 0' => 'Off', + '1 0' => 'On', + }, + }, + 0x1203 => { #11 + Name => 'ExternalFlashGuideNumber', + Writable => 'rational64s', + Unknown => 1, # (needs verification) + }, + 0x1204 => { #11(reversed)/7 + Name => 'ExternalFlashBounce', + Writable => 'int16u', + PrintConv => { + 0 => 'Bounce or Off', + 1 => 'Direct', + }, + }, + 0x1205 => { Name => 'ExternalFlashZoom', Writable => 'rational64u' }, #11 (ref converts to mm using table) + 0x1208 => { #6 + Name => 'InternalFlash', + Writable => 'int16u', + Count => -1, + PrintConv => { + '0' => 'Off', + '1' => 'On', + '0 0' => 'Off', + '1 0' => 'On', + }, + }, + 0x1209 => { #6 + Name => 'ManualFlash', + Writable => 'int16u', + Count => 2, + Notes => '2 numbers: 1. 0=Off, 1=On, 2. Flash strength', + PrintConv => q{ + my ($a,$b) = split ' ',$val; + return 'Off' unless $a; + $b = ($b == 1) ? 'Full' : "1/$b"; + return "On ($b strength)"; + }, + }, + 0x120a => { #PH + Name => 'MacroLED', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x1500 => [{ #6 + Name => 'SensorTemperature', + # (Stylus 1 stores values like "34 0 0") + Condition => '$$self{Model} =~ /E-(1|M5)\b/ || $count != 1', + Writable => 'int16s', + PrintConv => '$val=~s/ 0 0$//; "$val C"', + PrintConvInv => '$val=~s/ ?C$//; $val', + },{ + Name => 'SensorTemperature', + Writable => 'int16s', + RawConv => '($val and $val ne "-32768") ? $val : undef', # ignore 0 and -32768 + # ValueConv => '-2*(($val/135)**2)+55', #11 + ValueConv => '84 - 3 * $val / 26', #https://exiftool.org/forum/index.php/topic,5423.0.html + ValueConvInv => 'int((84 - $val) * 26 / 3 + 0.5)', + PrintConv => 'sprintf("%.1f C",$val)', + PrintConvInv => '$val=~s/ ?C$//; $val', + # data from test shots by Eric Sibert: + # E-510 E-620 + # Raw Ambient Raw Ambient + # --- ------- --- ------- + # 534 22.7 518 22.7 + # 550 20.6 531 19.3 + # 552 20.8 533 17.9 + # 558 19.3 582 17.2 + # 564 19.1 621 12.3 + # 567 17.8 634 9.7 + # 576 18.6 650 8.0 + # 582 17.2 660 7.7 + # 599 13.8 703 3.3 + # 631 10.7 880 -20.6 + # 642 12.4 880 -20.6 + # 652 9.6 892 -24.4 + # 692 5.2 892 -22.7 + # 714 3.3 + # 895 -19.8 + # 895 -19.2 + # 900 -21.7 + }], + 0x1600 => { # ref http://fourthirdsphoto.com/vbb/showpost.php?p=107607&postcount=15 + Name => 'ImageStabilization', + # (the other value is more reliable, so ignore this totally if the other exists) + Condition => 'not defined $$self{ImageStabilization}', + Writable => 'undef', + # if the first 4 bytes are non-zero, then bit 0x01 of byte 44 + # gives the stabilization mode + PrintConv => q{ + $val =~ /^\0{4}/ ? 'Off' : 'On, ' . + (unpack('x44C',$val) & 0x01 ? 'Mode 1' : 'Mode 2') + }, + }, + # 0x102a same as Subdir4-0x300 +); + +# AF information (ref PH) +%Image::ExifTool::Olympus::AFInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + # 0x2a - int8u: ImagerAFMode? 0=Manual, 1=Auto + # 0x30 - int16u: AFAreaXPosition? + # 0x32 - int16u: AFAreaWidth? (202) + # 0x34 - int16u: AFAreaYPosition? + # 0x36 - int16u: AFAreaHeight? (50) + # (AF area positions above give the top-left coordinates of the AF area in the + # AF frame. Increasing Y is downwards, and the AF frame size is about 1280x256) +); + +# Olympus raw information tags (ref 6) +%Image::ExifTool::Olympus::RawInfo = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + NOTES => 'These tags are found only in ORF images of some models (eg. C8080WZ).', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x000 => { + Name => 'RawInfoVersion', + Writable => 'undef', + RawConv => '$val=~s/\0+$//; $val', # (may be null terminated) + Count => 4, + }, + 0x100 => { Name => 'WB_RBLevelsUsed', Writable => 'int16u', Count => 2 }, + 0x110 => { Name => 'WB_RBLevelsAuto', Writable => 'int16u', Count => 2 }, + 0x120 => { Name => 'WB_RBLevelsShade', Writable => 'int16u', Count => 2 }, + 0x121 => { Name => 'WB_RBLevelsCloudy', Writable => 'int16u', Count => 2 }, + 0x122 => { Name => 'WB_RBLevelsFineWeather', Writable => 'int16u', Count => 2 }, + 0x123 => { Name => 'WB_RBLevelsTungsten', Writable => 'int16u', Count => 2 }, + 0x124 => { Name => 'WB_RBLevelsEveningSunlight',Writable => 'int16u', Count => 2 }, + 0x130 => { Name => 'WB_RBLevelsDaylightFluor', Writable => 'int16u', Count => 2 }, + 0x131 => { Name => 'WB_RBLevelsDayWhiteFluor', Writable => 'int16u', Count => 2 }, + 0x132 => { Name => 'WB_RBLevelsCoolWhiteFluor', Writable => 'int16u', Count => 2 }, + 0x133 => { Name => 'WB_RBLevelsWhiteFluorescent',Writable => 'int16u', Count => 2 }, + 0x200 => { + Name => 'ColorMatrix2', + Format => 'int16s', + Writable => 'int16u', + Count => 9, + }, + # 0x240 => 'ColorMatrixDefault', ? + # 0x250 => 'ColorMatrixSaturation', ? + # 0x251 => 'ColorMatrixHue', ? + # 0x252 => 'ColorMatrixContrast', ? + # 0x300 => sharpness-related + # 0x301 => list of sharpness-related values + 0x310 => { Name => 'CoringFilter', Writable => 'int16u' }, + 0x311 => { Name => 'CoringValues', Writable => 'int16u', Count => 11 }, + 0x600 => { Name => 'BlackLevel2', Writable => 'int16u', Count => 4 }, + 0x601 => { + Name => 'YCbCrCoefficients', + Notes => 'stored as int16u[6], but extracted as rational32u[3]', + Format => 'rational32u', + }, + 0x611 => { Name => 'ValidPixelDepth', Writable => 'int16u', Count => 2 }, + 0x612 => { Name => 'CropLeft', Writable => 'int16u' }, #11 + 0x613 => { Name => 'CropTop', Writable => 'int16u' }, #11 + 0x614 => { Name => 'CropWidth', Writable => 'int32u' }, + 0x615 => { Name => 'CropHeight', Writable => 'int32u' }, + 0x1000 => { + Name => 'LightSource', + Writable => 'int16u', + PrintConv => { + 0 => 'Unknown', + 16 => 'Shade', + 17 => 'Cloudy', + 18 => 'Fine Weather', + 20 => 'Tungsten (Incandescent)', + 22 => 'Evening Sunlight', + 33 => 'Daylight Fluorescent', + 34 => 'Day White Fluorescent', + 35 => 'Cool White Fluorescent', + 36 => 'White Fluorescent', + 256 => 'One Touch White Balance', + 512 => 'Custom 1-4', + }, + }, + # the following 5 tags all have 3 values: val, min, max + 0x1001 => { Name => 'WhiteBalanceComp', Writable => 'int16s', Count => 3 }, + 0x1010 => { Name => 'SaturationSetting', Writable => 'int16s', Count => 3 }, + 0x1011 => { Name => 'HueSetting', Writable => 'int16s', Count => 3 }, + 0x1012 => { Name => 'ContrastSetting', Writable => 'int16s', Count => 3 }, + 0x1013 => { Name => 'SharpnessSetting', Writable => 'int16s', Count => 3 }, + # settings written by Camedia Master 4.x + 0x2000 => { Name => 'CMExposureCompensation', Writable => 'rational64s' }, + 0x2001 => { Name => 'CMWhiteBalance', Writable => 'int16u' }, + 0x2002 => { Name => 'CMWhiteBalanceComp', Writable => 'int16s' }, + 0x2010 => { Name => 'CMWhiteBalanceGrayPoint', Writable => 'int16u', Count => 3 }, + 0x2020 => { Name => 'CMSaturation', Writable => 'int16s', Count => 3 }, + 0x2021 => { Name => 'CMHue', Writable => 'int16s', Count => 3 }, + 0x2022 => { Name => 'CMContrast', Writable => 'int16s', Count => 3 }, + 0x2023 => { Name => 'CMSharpness', Writable => 'int16s', Count => 3 }, +); + +# Olympus unknown information tags +%Image::ExifTool::Olympus::UnknownInfo = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, +); + +# Tags found only in some FE models +%Image::ExifTool::Olympus::FETags = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + Some FE models write a large number of tags here, but most of this + information remains unknown. + }, + 0x0100 => { + Name => 'BodyFirmwareVersion', + Writable => 'string', + }, +); + +# tags in Olympus QuickTime videos (ref PH) +# (similar information in Kodak,Minolta,Nikon,Olympus,Pentax and Sanyo videos) +%Image::ExifTool::Olympus::MOV1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => q{ + This information is found in MOV videos from Olympus models such as the + D540Z, D595Z, FE100, FE110, FE115, FE170 and FE200. + }, + 0x00 => { + Name => 'Make', + Format => 'string[24]', + }, + 0x18 => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[8]', + SeparateTable => 'CameraType', + PrintConv => \%olympusCameraTypes, + }, + # (01 00 at offset 0x20) + 0x26 => { + Name => 'ExposureUnknown', + Unknown => 1, + Format => 'int32u', + # this conversion doesn't work for all models (eg. gives "1/100000") + ValueConv => '$val ? 10 / $val : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x2a => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + 0x32 => { #(NC) + Name => 'ExposureCompensation', + Format => 'rational64s', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + # 0x44 => WhiteBalance ? + 0x48 => { + Name => 'FocalLength', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + }, + # 0xb1 => 'ISO', #(I don't think this works - PH) +); + +# tags in Olympus QuickTime videos (ref PH) +# (similar information in Kodak,Minolta,Nikon,Olympus,Pentax and Sanyo videos) +%Image::ExifTool::Olympus::MOV2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => q{ + This information is found in MOV videos from Olympus models such as the + FE120, FE140 and FE190. + }, + 0x00 => { + Name => 'Make', + Format => 'string[24]', + }, + 0x18 => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[24]', + Notes => 'the actual model name, no decoding necessary', + }, + # (01 00 at offset 0x30) + 0x36 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val ? 10 / $val : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x3a => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + 0x42 => { #(NC) + Name => 'ExposureCompensation', + Format => 'rational64s', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + 0x58 => { + Name => 'FocalLength', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + }, + 0xc1 => { + Name => 'ISO', + Format => 'int16u', + }, +); + +# tags in Olympus MP4 videos (ref PH) +%Image::ExifTool::Olympus::MP4 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => q{ + This information is found in MP4 videos from Olympus models such as the + u7040 and u9010. + }, + 0x00 => { + Name => 'Make', + Format => 'string[24]', + }, + 0x18 => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[24]', + Notes => 'oddly different than CameraType values in JPEG images by the same camera', + PrintConv => { + SG472 => 'u7040,S7040', + SG473 => 'u9010,S9010', + SG475 => 'SP800UZ', + SG551 => 'SZ-30MR', + SG553 => 'SP-610UZ', + SG554 => 'SZ-10', + SG555 => 'SZ-20', + SG573 => 'SZ-14', + SG575 => 'SP-620UZ', + }, + }, + 0x28 => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + 0x30 => { #(NC) + Name => 'ExposureCompensation', + Format => 'rational64s', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + # 0x38 - int32u: 3 + # 0x3c - int32u: 1 + # 0x40 - int16u: 5 + # 0x42 - int16u: 0,4,9 + # 0x64 - int32u: 0,6000,12000 + # 0x48 - int32u: 100 (ISO?) + 0x68 => { + Name => 'MovableInfo', + Condition => '$$valPt =~ /^DIGI/', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::MovableInfo' }, + }, + 0x72 => { + Name => 'MovableInfo', + Condition => '$$valPt =~ /^DIGI/', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::MovableInfo' }, + }, +); + +# yet a different QuickTime TAGS format (PH, E-M5) +%Image::ExifTool::Olympus::MOV3 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'QuickTime information found in the TAGS atom of MOV videos from the E-M5.', + OLYM => { + Name => 'OlympusAtom', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::OLYM2' }, + }, +); + +# yet a different QuickTime OLYM atom format (PH, E-M5) +%Image::ExifTool::Olympus::OLYM2 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + prms => { + Name => 'MakerNotes', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::prms' }, + }, + thmb =>{ + Name => 'ThumbInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::thmb2' }, + }, + scrn =>{ + Name => 'PreviewInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::scrn2' }, + }, +); + +# the "prms" atom in E-M5 MOV videos (PH, E-M5) +%Image::ExifTool::Olympus::prms = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => q{ + Information extracted from the "prms" atom in MOV videos from Olympus models + such as the OM E-M5. + }, + 0x12 => { + Name => 'Make', + Format => 'string[24]', + }, + 0x2c => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[24]', + SeparateTable => 'CameraType', + PrintConv => \%olympusCameraTypes, + }, + 0x83 => { + Name => 'DateTime1', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + }, + 0x9d => { + Name => 'DateTime2', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + }, + 0x17f => { + Name => 'LensModel', + Format => 'string[32]' + }, +); + +# yet a different "thmb" atom format (PH, E-M5) +%Image::ExifTool::Olympus::thmb2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'ThumbnailWidth', + Format => 'int16u', + }, + 2 => { + Name => 'ThumbnailHeight', + Format => 'int16u', + }, + 4 => { + Name => 'ThumbnailLength', + Format => 'int32u', + }, + 8 => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Format => 'undef[$val{4}]', + Notes => '160x120 JPEG thumbnail image', + RawConv => '$self->ValidateImage(\$val,$tag)', + }, +); + +# yet a different "scrn" atom format (PH, E-M5) +%Image::ExifTool::Olympus::scrn2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # 0 => int16u: 1 - number of preview images? + 2 => { + Name => 'OlympusPreview', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::scrn' }, + }, +); + +# movable information found in MP4 videos +%Image::ExifTool::Olympus::MovableInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + 0x04 => { #(NC) + Name => 'ISO', + Format => 'int32u', + }, + 0x2c => { + Name => 'EncoderVersion', + Format => 'string[16]', + }, + 0x3c => { + Name => 'DecoderVersion', + Format => 'string[16]', + }, + 0x83 => { + Name => 'Thumbnail', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::Thumbnail', + Base => '$start', # (use a separate table because of this) + }, + }, +); + +# thumbnail image information found in MP4 videos (similar in Olympus,Samsung,Sanyo) (ref PH) +%Image::ExifTool::Olympus::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 }, +); + +# thumbnail information found in 'thmb' atom of MP4 videos from the TG-810 (ref PH) +%Image::ExifTool::Olympus::thmb = ( + NOTES => 'Information extracted from the "thmb" atom of Olympus MP4 videos.', + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'ThumbnailLength', + Format => 'int32u', + }, + 4 => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Format => 'undef[$val{0}]', + Notes => '160x120 JPEG thumbnail image', + RawConv => '$self->ValidateImage(\$val,$tag)', + }, +); + +# thumbnail information found in 'scrn' atom of MP4 videos from the TG-810 (ref PH) +%Image::ExifTool::Olympus::scrn = ( + NOTES => 'Information extracted from the "scrn" atom of Olympus MP4 videos.', + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'PreviewImageLength', + Format => 'int32u', + }, + 4 => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Format => 'undef[$val{0}]', + Notes => '640x480 JPEG preview image', + RawConv => '$self->ValidateImage(\$val,$tag)', + }, +); + +# information in OLYM atom of MP4 videos from the TG-810 (ref PH) +%Image::ExifTool::Olympus::OLYM = ( + NOTES => 'Tags found in the OLYM atom of MP4 videos from the TG-810.', + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0e => { + Name => 'Make', + Format => 'string[26]', + }, + 0x28 => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[24]', + SeparateTable => 'CameraType', + PrintConv => \%olympusCameraTypes, + }, + 0x5a => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + 0x7f => { + Name => 'DateTimeOriginal', #(NC) + Description => 'Date/Time Original', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x99 => { + Name => 'DateTime2', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + }, + 0x109 => { + Name => 'ThumbnailWidth', + Format => 'int16u', + }, + 0x10b => { + Name => 'ThumbnailHeight', + Format => 'int16u', + }, +); + +# tags in Olympus AVI videos (ref PH) +# (very similar to Pentax::Junk2 tags) +%Image::ExifTool::Olympus::AVI = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => 'This information is found in Olympus AVI videos.', + 0x12 => { + Name => 'Make', + Format => 'string[24]', + }, + 0x2c => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[24]', + SeparateTable => 'CameraType', + PrintConv => \%olympusCameraTypes, + }, + 0x5e => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + 0x83 => { + Name => 'DateTime1', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + }, + 0x9d => { + Name => 'DateTime2', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + }, + 0x129 => { + Name => 'ThumbInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::thmb2' }, + }, +); + +# tags in WAV files from Olympus PCM linear recorders (ref 18) +%Image::ExifTool::Olympus::WAV = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Audio' }, + FIRST_ENTRY => 0, + NOTES => q{ + This information is found in WAV files from Olympus PCM linear recorders + like the LS-5, LS-10, LS-11. + }, + 0x0c => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[16]', + }, + 0x1c => { + Name => 'FileNumber', + Format => 'int32u', + PrintConv => 'sprintf("%.4d", $val)', + }, + 0x26 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Format => 'undef[12]', + Notes => 'time at start of recording', + ValueConv => q{ + return undef unless $val =~ /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/; + my $y = $1 < 70 ? "20$1" : "19$1"; + return "$y:$2:$3 $4:$5:$6"; + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x32 => { + Name => 'DateTimeEnd', + Groups => { 2 => 'Time' }, + Format => 'undef[12]', + Notes => 'time at end of recording', + ValueConv => q{ + return undef unless $val =~ /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/; + my $y = $1 < 70 ? "20$1" : "19$1"; + return "$y:$2:$3 $4:$5:$6"; + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x3e => { + Name => 'RecordingTime', + Format => 'undef[6]', + ValueConv => '$val =~ s/^(\d{2})(\d{2})/$1:$2:/; $val', + }, + 0x200 => { + Name => 'Duration', + Format => 'int32u', + ValueConv => '$val / 1000', + PrintConv => 'ConvertDuration($val)', + }, + 0x20a => { Name => 'Index01', %indexInfo }, + 0x214 => { Name => 'Index02', %indexInfo }, + 0x21e => { Name => 'Index03', %indexInfo }, + 0x228 => { Name => 'Index04', %indexInfo }, + 0x232 => { Name => 'Index05', %indexInfo }, + 0x23c => { Name => 'Index06', %indexInfo }, + 0x246 => { Name => 'Index07', %indexInfo }, + 0x250 => { Name => 'Index08', %indexInfo }, + 0x25a => { Name => 'Index09', %indexInfo }, + 0x264 => { Name => 'Index10', %indexInfo }, + 0x26e => { Name => 'Index11', %indexInfo }, + 0x278 => { Name => 'Index12', %indexInfo }, + 0x282 => { Name => 'Index13', %indexInfo }, + 0x28c => { Name => 'Index14', %indexInfo }, + 0x296 => { Name => 'Index15', %indexInfo }, + 0x2a0 => { Name => 'Index16', %indexInfo }, +); + +# DSS information written by Olympus voice recorders (ref PH) +%Image::ExifTool::Olympus::DSS = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Audio' }, + FIRST_ENTRY => 0, + NOTES => q{ + Information extracted from DSS/DS2 files and the ID3 XOLY frame of MP3 files + written by some Olympus voice recorders. + }, + # 0 - file format: + # "\x02dss"(DSS file and XOLY frame in MP3 file) + # "\x03ds2"(DS2 file) + # "\x03mp3"(ID3 XOLY frame in MP3 file) + 12 => { Name => 'Model', Format => 'string[16]' }, # (name truncated by some models) + 38 => { + Name => 'StartTime', + Format => 'string[12]', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/20$1:$2:$3 $4:$5:$6/; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + 50 => { + Name => 'EndTime', + Format => 'string[12]', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/20$1:$2:$3 $4:$5:$6/; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + 62 => { + Name => 'Duration', + Format => 'string[6]', + ValueConv => '$val =~ /(\d{2})(\d{2})(\d{2})/ ? ($1 * 60 + $2) * 60 + $3 : undef', + PrintConv => 'ConvertDuration($val)', + }, + 798 => { # (ref http://search.cpan.org/~rgibson/Audio-DSS-0.02/) + Name => 'Comment', + Format => 'string[100]', + }, +); + +# Olympus composite tags +%Image::ExifTool::Olympus::Composite = ( + GROUPS => { 2 => 'Camera' }, + ExtenderStatus => { + Notes => q{ + Olympus cameras have the quirk that they may retain the extender settings + after the extender is removed until the camera is powered off. This tag is + an attempt to represent the actual status of the extender. + }, + Require => { + 0 => 'Olympus:Extender', + 1 => 'Olympus:LensType', + 2 => 'MaxApertureValue', + }, + ValueConv => 'Image::ExifTool::Olympus::ExtenderStatus($val[0],$prt[1],$val[2])', + PrintConv => { + 0 => 'Not attached', + 1 => 'Attached', + 2 => 'Removed', + }, + }, + ZoomedPreviewImage => { + Groups => { 2 => 'Preview' }, + Require => { + 0 => 'ZoomedPreviewStart', + 1 => 'ZoomedPreviewLength', + }, + RawConv => q{ + @grps = $self->GetGroup($$val{0}); # set groups from input tag + Image::ExifTool::Exif::ExtractImage($self,$val[0],$val[1],"ZoomedPreviewImage"); + }, + }, + # this is actually for PanasonicRaw tags, but it uses the lens lookup here + LensType => { + Require => { + 0 => 'LensTypeMake', + 1 => 'LensTypeModel', + }, + Notes => 'based on tags found in some Panasonic RW2 images', + SeparateTable => 'Olympus LensType', + ValueConv => '"$val[0] $val[1]"', + PrintConv => \%olympusLensTypes, + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Olympus'); + + +#------------------------------------------------------------------------------ +# Determine if the extender (EX-25/EC-14) was really attached (ref 9) +# Inputs: 0) Extender, 1) LensType string, 2) MaxApertureAtMaxFocal +# Returns: 0=not attached, 1=attached, 2=could have been removed +# Notes: Olympus has a bug in the in-camera firmware which results in the +# extender information being cached and written into the EXIF data even after +# the extender has been removed. You must power cycle the camera to prevent it +# from writing the cached extender information into the EXIF data. +sub ExtenderStatus($$$) +{ + my ($extender, $lensType, $maxAperture) = @_; + my @info = split ' ', $extender; + # validate that extender identifier is reasonable + return 0 unless @info >= 2 and hex($info[1]); + # if it's not an EC-14 (id '0 04') then assume it was really attached + # (other extenders don't seem to affect the reported max aperture) + return 1 if "$info[0] $info[1]" ne '0 04'; + # get the maximum aperture for this lens (in $1) + $lensType =~ / F(\d+(\.\d+)?)/ or return 1; + # If the maximum aperture at the maximum focal length is greater than the + # known max/max aperture of the lens, then the extender must be attached + return(($maxAperture - $1 > 0.2) ? 1 : 2); +} + +#------------------------------------------------------------------------------ +# Print AF points +# Inputs: 0) AF point data (string of integers) +# Notes: I'm just guessing that the 2nd and 4th bytes are the Y coordinates, +# and that more AF points will show up in the future (derived from E-1 images, +# and the E-1 uses just one of 3 possible AF points, all centered in Y) - PH +sub PrintAFAreas($) +{ + my $val = shift; + my @points = split ' ', $val; + my %afPointNames = ( + 0x36794285 => 'Left', + 0x79798585 => 'Center', + 0xBD79C985 => 'Right', + ); + $val = ''; + my $pt; + foreach $pt (@points) { + next unless $pt; + $val and $val .= ', '; + $afPointNames{$pt} and $val .= $afPointNames{$pt} . ' '; + my @coords = unpack('C4',pack('N',$pt)); + $val .= "($coords[0],$coords[1])-($coords[2],$coords[3])"; + } + $val or $val = 'none'; + return $val; +} + +#------------------------------------------------------------------------------ +# Extract information from a DSS/DS2 voice recorder audio file or ID3 XOLY frame +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success +sub ProcessDSS($$;$) +{ + my ($et, $dirInfo) = @_; + + # allow this to be called with either RAF or DataPt + my $raf = $$dirInfo{RAF}; + if ($raf) { + my $buff; + $raf->Read($buff, 898) > 68 or return 0; + $buff =~ /^(\x02dss|\x03ds2)/ or return 0; + $dirInfo = { DataPt => \$buff }; + $et->SetFileType(uc substr $buff, 1, 3); + } + my $tagTablePtr = GetTagTable('Image::ExifTool::Olympus::DSS'); + return $et->ProcessBinaryData($dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Process ORF file +# Inputs: 0) ExifTool object reference, 1) directory information reference +# Returns: 1 if this looked like a valid ORF file, 0 otherwise +sub ProcessORF($$) +{ + my ($et, $dirInfo) = @_; + return $et->ProcessTIFF($dirInfo); +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Olympus - Olympus/Epson 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 +Olympus or Epson maker notes in EXIF information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html> + +=item L<http://www.cybercom.net/~dcoffin/dcraw/> + +=item L<http://www.ozhiker.com/electronics/pjmt/jpeg_info/olympus_mn.html> + +=item L<http://olypedia.de/Olympus_Makernotes> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Markku Hanninen, Remi Guyomarch, Frank Ledwon, Michael Meissner, +Mark Dapoz, Ioannis Panagiotopoulos and Tomasz Kawecki for their help +figuring out some Olympus tags, and Lilo Huang, Chris Shaw and Viktor +Lushnikov for adding to the LensType list. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Olympus Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/OpenEXR.pm b/ExifTool/lib/Image/ExifTool/OpenEXR.pm new file mode 100644 index 0000000..ef44a18 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/OpenEXR.pm @@ -0,0 +1,343 @@ +#------------------------------------------------------------------------------ +# File: OpenEXR.pm +# +# Description: Read OpenEXR meta information +# +# Revisions: 2011/12/10 - P. Harvey Created +# 2023/01/31 - PH Added support for multipart images +# +# References: 1) http://www.openexr.com/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::OpenEXR; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::GPS; + +$VERSION = '1.04'; + +# supported EXR value format types (other types are extracted as undef binary data) +my %formatType = ( + box2f => 'float[4]', + box2i => 'int32s[4]', + chlist => 1, + chromaticities => 'float[8]', + compression => 'int8u', + double => 'double', + envmap => 'int8u', + float => 'float', + 'int' => 'int32s', + keycode => 'int32s[7]', + lineOrder => 'int8u', + m33f => 'float[9]', + m44f => 'float[16]', + rational => 'rational64s', + string => 'string', # incorrect in specification! (no leading int) + stringvector => 1, + tiledesc => 1, + timecode => 'int32u[2]', + v2f => 'float[2]', + v2i => 'int32s[2]', + v3f => 'float[3]', + v3i => 'int32s[3]', +); + +# OpenEXR tags +%Image::ExifTool::OpenEXR::Main = ( + GROUPS => { 2 => 'Image' }, + NOTES => q{ + Information extracted from EXR images. Use the ExtractEmbedded option to + extract information from all frames of a multipart image. See + L<http://www.openexr.com/> for the official specification. + }, + _ver => { Name => 'EXRVersion', Notes => 'low byte of Flags word' }, + _flags => { Name => 'Flags', + PrintConv => { BITMASK => { + 9 => 'Tiled', + 10 => 'Long names', + 11 => 'Deep data', + 12 => 'Multipart', + }}, + }, + adoptedNeutral => { }, + altitude => { + Name => 'GPSAltitude', + Groups => { 2 => 'Location' }, + PrintConv => q{ + $val = int($val * 10) / 10; + return(($val =~ s/^-// ? "$val m Below" : "$val m Above") . " Sea Level"); + }, + }, + aperture => { PrintConv => 'sprintf("%.1f",$val)' }, + channels => { }, + chromaticities => { }, + capDate => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + comments => { }, + compression => { + PrintConvColumns => 2, + PrintConv => { + 0 => 'None', + 1 => 'RLE', + 2 => 'ZIPS', + 3 => 'ZIP', + 4 => 'PIZ', + 5 => 'PXR24', + 6 => 'B44', + 7 => 'B44A', + }, + }, + dataWindow => { }, + displayWindow => { }, + envmap => { + Name => 'EnvironmentMap', + PrintConv => { + 0 => 'Latitude/Longitude', + 1 => 'Cube', + }, + }, + expTime => { + Name => 'ExposureTime', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + focus => { + Name => 'FocusDistance', + PrintConv => '"$val m"', + }, + framesPerSecond => { }, + keyCode => { }, + isoSpeed => { Name => 'ISO' }, + latitude => { + Name => 'GPSLatitude', + Groups => { 2 => 'Location' }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + lineOrder => { + PrintConv => { + 0 => 'Increasing Y', + 1 => 'Decreasing Y', + 2 => 'Random Y', + }, + }, + longitude => { + Name => 'GPSLongitude', + Groups => { 2 => 'Location' }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + lookModTransform => { }, + multiView => { }, + owner => { Groups => { 2 => 'Author' } }, + pixelAspectRatio => { }, + preview => { }, + renderingTransform => { }, + screenWindowCenter => { }, + screenWindowWidth => { }, + tiles => { }, + timeCode => { }, + utcOffset => { + Name => 'TimeZone', + Groups => { 2 => 'Time' }, + PrintConv => 'TimeZoneString($val / 60)', + }, + whiteLuminance => { }, + worldToCamera => { }, + worldToNDC => { }, + wrapmodes => { Name => 'WrapModes' }, + xDensity => { Name => 'XResolution' }, + name => { }, + type => { }, + version => { }, + chunkCount => { }, + # also observed: + # ilut +); + +#------------------------------------------------------------------------------ +# Extract information from an OpenEXR file +# Inputs: 0) ExifTool object reference, 1) DirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid OpenEXR file +sub ProcessEXR($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my $binary = $et->Options('Binary') || $verbose; + my ($buff, $dim); + + # verify this is a valid RIFF file + return 0 unless $raf->Read($buff, 8) == 8; + return 0 unless $buff =~ /^\x76\x2f\x31\x01/s; + $et->SetFileType(); + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::OpenEXR::Main'); + + # extract information from header + my $flags = unpack('x4V', $buff); + $et->HandleTag($tagTablePtr, '_ver', $flags & 0xff); + $et->HandleTag($tagTablePtr, '_flags', $flags & 0xffffff00); + my $maxLen = ($flags & 0x400) ? 255 : 31; + my $multi = $flags & 0x1000; + + # extract attributes + for (;;) { + $raf->Read($buff, ($maxLen + 1) * 2 + 5) or last; + if ($buff =~ /^\0/) { + last unless $multi and $et->Options('ExtractEmbedded'); + # remove null and process the next frame header as a sub-document + # (second null is end of all headers) + last if $buff =~ s/^(\0+)// and length($1) > 1; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + } + unless ($buff =~ /^([^\0]{1,$maxLen})\0([^\0]{1,$maxLen})\0(.{4})/sg) { + $et->Warn('EXR format error'); + last; + } + my ($tag, $type, $size) = ($1, $2, unpack('V', $3)); + unless ($raf->Seek(pos($buff) - length($buff), 1)) { + $et->Warn('Seek error'); + last; + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + unless ($tagInfo) { + my $name = ucfirst $tag; + $name =~ s/([^a-zA-Z])([a-z])/$1\U$2/g; # capitalize first letter of each word + $name =~ tr/-_a-zA-Z0-9//dc; + if (length $name <= 1) { + if (length $name) { + $name = "Tag$name"; + } else { + $name = 'Invalid'; + } + } + $tagInfo = { Name => $name }; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + $et->VPrint(0, $$et{INDENT}, "[adding $tag]\n"); + } + my ($val, $success); + my $format = $formatType{$type}; + if ($format or $binary) { + $raf->Read($buff, $size) == $size and $success = 1; + if (not $format) { + $val = \$buff; # treat as undef binary data + } elsif ($format ne '1') { + # handle formats which map nicely into ExifTool format codes + if ($format =~ /^(\w+)\[?(\d*)/) { + my ($fmt, $cnt) = ($1, $2); + $cnt = $fmt eq 'string' ? $size : 1 unless $cnt; + $val = ReadValue(\$buff, 0, $fmt, $cnt, $size); + } + # handle other format types + } elsif ($type eq 'tiledesc') { + if ($size >= 9) { + my $x = Get32u(\$buff, 0); + my $y = Get32u(\$buff, 4); + my $mode = Get8u(\$buff, 8); + my $lvl = { 0 => 'One Level', 1 => 'MIMAP Levels', 2 => 'RIPMAP Levels' }->{$mode & 0x0f}; + $lvl or $lvl = 'Unknown Levels (' . ($mode & 0xf) . ')'; + my $rnd = { 0 => 'Round Down', 1 => 'Round Up' }->{$mode >> 4}; + $rnd or $rnd = 'Unknown Rounding (' . ($mode >> 4) . ')'; + $val = "${x}x$y; $lvl; $rnd"; + } + } elsif ($type eq 'chlist') { + $val = [ ]; + while ($buff =~ /\G([^\0]{1,31})\0(.{16})/sg) { + my ($str, $dat) = ($1, $2); + my ($pix,$lin,$x,$y) = unpack('VCx3VV', $dat); + $pix = { 0 => 'int8u', 1 => 'half', 2 => 'float' }->{$pix} || "unknown($pix)"; + push @$val, "$str $pix" . ($lin ? ' linear' : '') . " $x $y"; + } + } elsif ($type eq 'stringvector') { + $val = [ ]; + my $pos = 0; + while ($pos + 4 <= length($buff)) { + my $len = Get32u(\$buff, $pos); + last if $pos + 4 + $len > length($buff); + push @$val, substr($buff, $pos + 4, $len); + $pos += 4 + $len; + } + } else { + $val = \$buff; # (shouldn't happen) + } + } else { + # avoid loading binary data + $val = \ "Binary data $size bytes"; + $success = $raf->Seek($size, 1); + } + unless ($success) { + $et->Warn('Truncated or corrupted EXR file'); + last; + } + $val = '<bad>' unless defined $val; + + # take image dimensions from dataWindow (with displayWindow as backup) + if (($tag eq 'dataWindow' or (not $dim and $tag eq 'displayWindow')) and + $val =~ /^(-?\d+) (-?\d+) (-?\d+) (-?\d+)$/ and not $$et{DOC_NUM}) + { + $dim = [$3 - $1 + 1, $4 - $2 + 1]; + } + if ($verbose) { + my $dataPt = ref $val ? $val : \$val, + $et->VerboseInfo($tag, $tagInfo, + Table => $tagTablePtr, + Value => $val, + Size => $size, + Format => $type, + DataPt => \$buff, + Addr => $raf->Tell() - $size, + ); + } + $et->FoundTag($tagInfo, $val); + } + delete $$et{DOC_NUM}; + if ($dim) { + $et->FoundTag('ImageWidth', $$dim[0]); + $et->FoundTag('ImageHeight', $$dim[1]); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::OpenEXR - Read OpenEXR meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from OpenEXR images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.openexr.com/documentation.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/OpenEXR Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Opus.pm b/ExifTool/lib/Image/ExifTool/Opus.pm new file mode 100644 index 0000000..f3450f5 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Opus.pm @@ -0,0 +1,98 @@ +#------------------------------------------------------------------------------ +# File: Opus.pm +# +# Description: Read Ogg Opus audio meta information +# +# Revisions: 2016/07/14 - P. Harvey Created +# +# References: 1) https://www.opus-codec.org/docs/ +# 2) https://wiki.xiph.org/OggOpus +# 3) https://tools.ietf.org/pdf/rfc7845.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::Opus; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.00'; + +# Opus metadata types +%Image::ExifTool::Opus::Main = ( + NOTES => q{ + Information extracted from Ogg Opus files. See + L<https://www.opus-codec.org/docs/> for the specification. + }, + 'OpusHead' => { + Name => 'Header', + SubDirectory => { TagTable => 'Image::ExifTool::Opus::Header' }, + }, + 'OpusTags' => { + Name => 'Comments', + SubDirectory => { TagTable => 'Image::ExifTool::Vorbis::Comments' }, + }, +); + +%Image::ExifTool::Opus::Header = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + 0 => 'OpusVersion', + 1 => 'AudioChannels', + # 2 => 'PreSkip' (int16u) + 4 => { + Name => 'SampleRate', + Format => 'int32u', + }, + 8 => { + Name => 'OutputGain', + Format => 'int16u', + ValueConv => '10 ** ($val/5120)', + }, +); + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Opus - Read Ogg Opus audio meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Ogg Opus audio files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://www.opus-codec.org/docs/> + +=item L<https://wiki.xiph.org/OggOpus> + +=item L<https://tools.ietf.org/pdf/rfc7845.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Opus Tags>, +L<Image::ExifTool::TagNames/Ogg Tags>, +L<Image::ExifTool::TagNames/Vorbis Tags>, +L<Image::ExifTool::TagNames/FLAC Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Other.pm b/ExifTool/lib/Image/ExifTool/Other.pm new file mode 100644 index 0000000..4890cab --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Other.pm @@ -0,0 +1,93 @@ +#------------------------------------------------------------------------------ +# File: Other.pm +# +# Description: Read meta information from other uncommon formats +# +# Revisions: 2021/07/16 - P. Harvey Created +# +# References: 1) PFM - http://www.pauldebevec.com/Research/HDR/PFM/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::Other; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.00'; + +# Other info +%Image::ExifTool::Other::PFM = ( + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + VARS => { NO_ID => 1 }, + NOTES => q{ + Tags extracted from Portable FloatMap images. See + L<http://www.pauldebevec.com/Research/HDR/PFM/> for the specification. + }, + ColorSpace => { PrintConv => { PF => 'RGB', 'Pf' => 'Monochrome'} }, + ImageWidth => { }, + ImageHeight => { }, + ByteOrder => { PrintConv => '$val > 0 ? "Big-endian" : "Little-endian"' }, +); + +#------------------------------------------------------------------------------ +# Extract information from a Portable FloatMap image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid PFM file +sub ProcessPFM2($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + return 0 unless $raf->Read($buff, 256) and $buff =~ /^(P[Ff])\x0a(\d+) (\d+)\x0a([-+0-9.]+)\x0a/; + $et->SetFileType('PFM', 'image/x-pfm'); + my $tagTablePtr = GetTagTable('Image::ExifTool::Other::PFM'); + $et->HandleTag($tagTablePtr, ColorSpace => $1); + $et->HandleTag($tagTablePtr, ImageWidth => $2); + $et->HandleTag($tagTablePtr, ImageHeight => $3); + $et->HandleTag($tagTablePtr, ByteOrder => $4); + # hack to set proper file description (extension is the same for Printer Font Metrics files) + $Image::ExifTool::static_vars{OverrideFileDescription}{PFM} = 'Portable FloatMap', + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Other - Read meta information from other uncommon formats + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +information from Portable FloatMap (PFM) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 PFM L<http://www.pauldebevec.com/Research/HDR/PFM/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Other Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/PCX.pm b/ExifTool/lib/Image/ExifTool/PCX.pm new file mode 100644 index 0000000..61febab --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PCX.pm @@ -0,0 +1,138 @@ +#------------------------------------------------------------------------------ +# File: PCX.pm +# +# Description: Read metadata from PC Paintbrush files +# +# Revisions: 2018/12/12 - P. Harvey Created +# +# References: 1) http://qzx.com/pc-gpe/pcx.txt +# 2) https://www.fileformat.info/format/pcx/corion.htm +#------------------------------------------------------------------------------ + +package Image::ExifTool::PCX; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.00'; + +# PCX info +%Image::ExifTool::PCX::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + NOTES => 'Tags extracted from PC Paintbrush images.', + DATAMEMBER => [ 0x04, 0x05 ], + 0x00 => { + Name => 'Manufacturer', + PrintConv => { 10 => 'ZSoft' }, + }, + 0x01 => { + Name => 'Software', + PrintConv => { + 0 => 'PC Paintbrush 2.5', + 2 => 'PC Paintbrush 2.8 (with palette)', + 3 => 'PC Paintbrush 2.8 (without palette)', + 4 => 'PC Paintbrush for Windows', + 5 => 'PC Paintbrush 3.0+', + }, + }, + 0x02 => { Name => 'Encoding', PrintConv => { 1 => 'RLE' } }, + 0x03 => 'BitsPerPixel', + 0x04 => { + Name => 'LeftMargin', + Format => 'int16u', + RawConv => '$$self{LeftMargin} = $val', + }, + 0x06 => { + Name => 'TopMargin', + Format => 'int16u', + RawConv => '$$self{TopMargin} = $val', + }, + 0x08 => { + Name => 'ImageWidth', + Format => 'int16u', + Notes => 'adjusted for LeftMargin', + ValueConv => '$val - $$self{LeftMargin} + 1', + }, + 0x0a => { + Name => 'ImageHeight', + Format => 'int16u', + Notes => 'adjusted for TopMargin', + ValueConv => '$val - $$self{TopMargin} + 1', + }, + 0x0c => 'XResolution', + 0x0e => 'YResolution', + 0x41 => 'ColorPlanes', + 0x42 => { Name => 'BytesPerLine', Format => 'int16u' }, + 0x44 => { + Name => 'ColorMode', + PrintConv => { + 0 => 'n/a', + 1 => 'Color Palette', + 2 => 'Grayscale', + }, + }, + 0x46 => { Name => 'ScreenWidth', Format => 'int16u', RawConv => '$val or undef' }, + 0x48 => { Name => 'ScreenHeight', Format => 'int16u', RawConv => '$val or undef' }, +); + +#------------------------------------------------------------------------------ +# Extract information from a PCX image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid PCX file +sub ProcessPCX($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + return 0 unless $raf->Read($buff, 0x50) == 0x50 and + $buff =~ /^\x0a[\0-\x05]\x01[\x01\x02\x04\x08].{64}[\0-\x02]/s; + SetByteOrder('II'); + $et->SetFileType(); + my %dirInfo = ( DirName => 'PCX', DataPt => \$buff ); + my $tagTablePtr = GetTagTable('Image::ExifTool::PCX::Main'); + return $et->ProcessBinaryData(\%dirInfo, $tagTablePtr); +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PCX - Read metadata from PC Paintbrush files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +information from PC Paintbrush (PCX) files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://qzx.com/pc-gpe/pcx.txt> + +=item L<https://www.fileformat.info/format/pcx/corion.htm> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PCX Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/PDF.pm b/ExifTool/lib/Image/ExifTool/PDF.pm new file mode 100644 index 0000000..b7bf397 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PDF.pm @@ -0,0 +1,2447 @@ +#------------------------------------------------------------------------------ +# File: PDF.pm +# +# Description: Read PDF meta information +# +# Revisions: 07/11/2005 - P. Harvey Created +# 07/25/2005 - P. Harvey Add support for encrypted documents +# +# References: 1) http://www.adobe.com/devnet/pdf/pdf_reference.html +# 2) http://search.cpan.org/dist/Crypt-RC4/ +# 3) http://www.adobe.com/devnet/acrobat/pdfs/PDF32000_2008.pdf +# 4) http://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/adobe_supplement_iso32000.pdf +# 5) http://tools.ietf.org/search/rfc3454 +# 6) http://www.armware.dk/RFC/rfc/rfc4013.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::PDF; + +use strict; +use vars qw($VERSION $AUTOLOAD $lastFetched); +use Image::ExifTool qw(:DataAccess :Utils); +require Exporter; + +$VERSION = '1.57'; + +sub FetchObject($$$$); +sub ExtractObject($$;$$); +sub ReadToNested($;$); +sub ProcessDict($$$$;$$); +sub ProcessAcroForm($$$$;$$); +sub ExpandArray($); +sub ReadPDFValue($); +sub CheckPDF($$$); + +# $lastFetched - last fetched object reference (used for decryption) +# (undefined if fetched object was already decrypted, eg. object from stream) + +my $cryptInfo; # encryption object reference (plus additional information) +my $cryptString; # flag that strings are encrypted +my $cryptStream; # flag that streams are encrypted +my $lastOffset; # last fetched object offset +my %streamObjs; # hash of stream objects +my %fetched; # dicts fetched in verbose mode (to avoid cyclical recursion) +my $pdfVer; # version of PDF file being processed (from header) + +# filters supported in DecodeStream() +my %supportedFilter = ( + '/FlateDecode' => 1, + '/Crypt' => 1, + '/Identity' => 1, # (not filtered) + '/DCTDecode' => 1, # (JPEG image - not filtered) + '/JPXDecode' => 1, # (Jpeg2000 image - not filtered) + '/LZWDecode' => 1, # (usually a bitmapped image) + '/ASCIIHexDecode' => 1, + '/ASCII85Decode' => 1, + # other standard filters that we currently don't support + #'/JBIG2Decode' => 0, # (JBIG2 image format not supported) + #'/CCITTFaxDecode' => 0, + #'/RunLengthDecode' => 0, +); + +# tags in main PDF directories +%Image::ExifTool::PDF::Main = ( + GROUPS => { 2 => 'Document' }, + VARS => { CAPTURE => ['Main','Prev'] }, + Info => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Info' }, + # Adobe Acrobat 10.1.5 will create a duplicate Info dictionary with + # a different object number when metadata is edited. This flag + # is part of a patch to ignore this duplicate information (unless + # the IgnoreMinorErrors option is used) + IgnoreDuplicates => 1, + }, + Root => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Root' }, + }, + Encrypt => { + NoProcess => 1, # don't process normally (processed in advance) + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Encrypt' }, + }, + _linearized => { + Name => 'Linearized', + Notes => 'flag set if document is linearized for fast web display; not a real Tag ID', + PrintConv => { 'true' => 'Yes', 'false' => 'No' }, + }, +); + +# tags in PDF Info dictionary +%Image::ExifTool::PDF::Info = ( + GROUPS => { 2 => 'Document' }, + VARS => { CAPTURE => ['Info'] }, + EXTRACT_UNKNOWN => 1, # extract all unknown tags in this directory + WRITE_PROC => \&Image::ExifTool::DummyWriteProc, + CHECK_PROC => \&CheckPDF, + WRITABLE => 'string', + # set PRIORITY to 0 so most recent Info dictionary takes precedence + # (Acrobat Pro bug? doesn't use same object/generation number for + # new Info dictionary when doing incremental update) + PRIORITY => 0, + NOTES => q{ + As well as the tags listed below, the PDF specification allows for + user-defined tags to exist in the Info dictionary. These tags, which should + have corresponding XMP-pdfx entries in the XMP of the PDF XML Metadata + object, are also extracted by ExifTool. + + B<Writable> specifies the value format, and may be C<string>, C<date>, + C<integer>, C<real>, C<boolean> or C<name> for PDF tags. + }, + Title => { }, + Author => { Groups => { 2 => 'Author' } }, + Subject => { }, + Keywords => { + List => 'string', # this is a string list + Notes => q{ + stored as a string but treated as a comma- or semicolon-separated list of + items when reading if the string contains commas or semicolons, whichever is + more numerous, otherwise it is treated a space-separated list of items. + Written as a comma-separated list. The list behaviour may be defeated by + setting the API NoPDFList option. Note that the corresponding + XMP-pdf:Keywords tag is not treated as a list, so the NoPDFList option + should be used when copying between these two. + }, + }, + Creator => { }, + Producer => { }, + CreationDate => { + Name => 'CreateDate', + Writable => 'date', + PDF2 => 1, # not deprecated in PDF 2.0 + Groups => { 2 => 'Time' }, + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + ModDate => { + Name => 'ModifyDate', + Writable => 'date', + PDF2 => 1, # not deprecated in PDF 2.0 + Groups => { 2 => 'Time' }, + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + Trapped => { + Protected => 1, + # remove leading '/' from '/True' or '/False' + ValueConv => '$val=~s{^/}{}; $val', + ValueConvInv => '"/$val"', + }, + 'AAPL:Keywords' => { #PH + Name => 'AppleKeywords', + List => 'array', # this is an array of values + Notes => q{ + keywords written by Apple utilities, although they seem to use PDF:Keywords + when reading + }, + }, +); + +# tags in the PDF Root document catalog +%Image::ExifTool::PDF::Root = ( + GROUPS => { 2 => 'Document' }, + # note: can't capture previous versions of Root since they are not parsed + VARS => { CAPTURE => ['Root'] }, + NOTES => 'This is the PDF document catalog.', + MarkInfo => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::MarkInfo' }, + }, + Metadata => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Metadata' }, + }, + Pages => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Pages' }, + }, + Perms => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Perms' }, + }, + AcroForm => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::AcroForm' }, + }, + Lang => 'Language', + PageLayout => { }, + PageMode => { }, + Version => { + Name => 'PDFVersion', + RawConv => '$$self{PDFVersion} = $val if $$self{PDFVersion} < $val; $val', + }, +); + +# tags extracted from the PDF Encrypt dictionary +%Image::ExifTool::PDF::Encrypt = ( + GROUPS => { 2 => 'Document' }, + NOTES => 'Tags extracted from the document Encrypt dictionary.', + Filter => { + Name => 'Encryption', + Notes => q{ + extracted value is actually a combination of the Filter, SubFilter, V, R and + Length information from the Encrypt dictionary + }, + }, + P => { + Name => 'UserAccess', + ValueConv => '$val & 0x0f3c', # ignore reserved bits + PrintConvColumns => 2, + PrintConv => { BITMASK => { + 2 => 'Print', + 3 => 'Modify', + 4 => 'Copy', + 5 => 'Annotate', + 8 => 'Fill forms', + 9 => 'Extract', + 10 => 'Assemble', + 11 => 'Print high-res', + }}, + }, +); + +# tags in PDF Pages dictionary +%Image::ExifTool::PDF::Pages = ( + GROUPS => { 2 => 'Document' }, + Count => 'PageCount', + Kids => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Kids' }, + }, +); + +# tags in PDF Perms dictionary +%Image::ExifTool::PDF::Perms = ( + NOTES => 'Additional document permissions imposed by digital signatures.', + DocMDP => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Signature' }, + }, + FieldMDP => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Signature' }, + }, + UR3 => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Signature' }, + }, +); + +# tags in PDF Perms dictionary +%Image::ExifTool::PDF::AcroForm = ( + PROCESS_PROC => \&ProcessAcroForm, + _has_xfa => { + Name => 'HasXFA', + Notes => q{ + this tag is defined if a document contains form fields, and is true if it + uses XML Forms Architecture; not a real Tag ID + }, + PrintConv => { 'true' => 'Yes', 'false' => 'No' }, + }, +); + +# tags in PDF Kids dictionary +%Image::ExifTool::PDF::Kids = ( + Metadata => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Metadata' }, + }, + PieceInfo => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::PieceInfo' }, + }, + Resources => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Resources' }, + }, + Kids => { + Condition => '$self->Options("ExtractEmbedded")', + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Kids' }, + }, +); + +# tags in PDF Resources dictionary +%Image::ExifTool::PDF::Resources = ( + ColorSpace => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::ColorSpace' }, + }, + XObject => { + Condition => '$self->Options("ExtractEmbedded")', + SubDirectory => { TagTable => 'Image::ExifTool::PDF::XObject' }, + }, + Properties => { + Condition => '$self->Options("ExtractEmbedded")', + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Properties' }, + }, +); + +# tags in PDF ColorSpace dictionary +%Image::ExifTool::PDF::ColorSpace = ( + DefaultRGB => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::DefaultRGB' }, + ConvertToDict => 1, # (not seen yet, but just in case) + }, + DefaultCMYK => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::DefaultRGB' }, + # hack: this is stored as an array instead of a dictionary in my + # sample, so convert to a dictionary to extract the ICCBased element + ConvertToDict => 1, + }, + Cs1 => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::DefaultRGB' }, + ConvertToDict => 1, # (just in case) + }, + CS0 => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::DefaultRGB' }, + ConvertToDict => 1, # (just in case) + }, +); + +# tags in PDF DefaultRGB dictionary +%Image::ExifTool::PDF::DefaultRGB = ( + ICCBased => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::ICCBased' }, + }, +); + +# tags in PDF ICCBased, Cs1 and CS0 dictionaries +%Image::ExifTool::PDF::ICCBased = ( + _stream => { + SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' }, + }, +); + +# tags in PDF XObject dictionary (parsed only if ExtractEmbedded is enabled) +%Image::ExifTool::PDF::XObject = ( + EXTRACT_UNKNOWN => 0, # extract known but numbered tags (Im1, Im2, etc) + Im => { + Notes => q{ + the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> option enables information to be extracted from these + embedded images + }, + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Im' }, + }, +); + +# tags in PDF Im# dictionary +%Image::ExifTool::PDF::Im = ( + NOTES => q{ + Information extracted from embedded images with the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> option. + The EmbeddedImage and its metadata are extracted only for JPEG and Jpeg2000 + image formats. + }, + Width => 'EmbeddedImageWidth', + Height => 'EmbeddedImageHeight', + Filter => { Name => 'EmbeddedImageFilter', List => 1 }, + ColorSpace => { + Name => 'EmbeddedImageColorSpace', + List => 1, + RawConv => 'ref $val ? undef : $val', # (ignore color space data) + }, + Image_stream => { + Name => 'EmbeddedImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, +); + +# tags in PDF Properties dictionary +%Image::ExifTool::PDF::Properties = ( + EXTRACT_UNKNOWN => 0, # extract known but numbered tags (MC0, MC1, etc) + MC => { + Notes => q{ + the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> option enables information to be extracted from these + embedded metadata dictionaries + }, + SubDirectory => { TagTable => 'Image::ExifTool::PDF::MC' }, + } +); + +# tags in PDF MC# dictionary +%Image::ExifTool::PDF::MC = ( + Metadata => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Metadata' }, + } +); + +# tags in PDF PieceInfo dictionary +%Image::ExifTool::PDF::PieceInfo = ( + AdobePhotoshop => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::AdobePhotoshop' }, + }, + Illustrator => { + # assume this is an illustrator file if it contains this directory + # and doesn't have a ".PDF" extension + Condition => q{ + $self->OverrideFileType("AI") unless $$self{FILE_EXT} and $$self{FILE_EXT} eq 'PDF'; + return 1; + }, + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Illustrator' }, + }, +); + +# tags in PDF AdobePhotoshop dictionary +%Image::ExifTool::PDF::AdobePhotoshop = ( + Private => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Private' }, + }, +); + +# tags in PDF Illustrator dictionary +%Image::ExifTool::PDF::Illustrator = ( + Private => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::AIPrivate' }, + }, +); + +# tags in PDF Private dictionary +%Image::ExifTool::PDF::Private = ( + ImageResources => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::ImageResources' }, + }, +); + +# tags in PDF AI Private dictionary +%Image::ExifTool::PDF::AIPrivate = ( + GROUPS => { 2 => 'Document' }, + EXTRACT_UNKNOWN => 0, # extract known but numbered tags + AIMetaData => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::AIMetaData' }, + }, + AIPrivateData => { + Notes => q{ + the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> option enables information to be extracted from embedded + PostScript documents in the AIPrivateData# and AIPDFPrivateData# streams + }, + JoinStreams => 1, # join streams from numbered tags and process as one + SubDirectory => { TagTable => 'Image::ExifTool::PostScript::Main' }, + }, + AIPDFPrivateData => { + JoinStreams => 1, # join streams from numbered tags and process as one + SubDirectory => { TagTable => 'Image::ExifTool::PostScript::Main' }, + }, + RoundTripVersion => { }, + ContainerVersion => { }, + CreatorVersion => { }, +); + +# tags in PDF AIMetaData dictionary +%Image::ExifTool::PDF::AIMetaData = ( + _stream => { + SubDirectory => { TagTable => 'Image::ExifTool::PostScript::Main' }, + }, +); + +# tags in PDF ImageResources dictionary +%Image::ExifTool::PDF::ImageResources = ( + _stream => { + SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Main' }, + }, +); + +# tags in PDF MarkInfo dictionary +%Image::ExifTool::PDF::MarkInfo = ( + GROUPS => { 2 => 'Document' }, + Marked => { + Name => 'TaggedPDF', + Notes => "not a Tagged PDF if this tag is missing", + PrintConv => { 'true' => 'Yes', 'false' => 'No' }, + }, +); + +# tags in PDF Metadata dictionary +%Image::ExifTool::PDF::Metadata = ( + GROUPS => { 2 => 'Document' }, + XML_stream => { # this is the stream for a Subtype /XML dictionary (not a real tag) + Name => 'XMP', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, +); + +# tags in PDF signature directories (DocMDP, FieldMDP or UR3) +%Image::ExifTool::PDF::Signature = ( + GROUPS => { 2 => 'Document' }, + ContactInfo => 'SignerContactInfo', + Location => 'SigningLocation', + M => { + Name => 'SigningDate', + Format => 'date', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + Name => 'SigningAuthority', + Reason => 'SigningReason', + Reference => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Reference' }, + }, + Prop_AuthTime => { + Name => 'AuthenticationTime', + PrintConv => 'ConvertTimeSpan($val) . " ago"', + }, + Prop_AuthType => 'AuthenticationType', +); + +# tags in PDF Reference dictionary +%Image::ExifTool::PDF::Reference = ( + TransformParams => { + SubDirectory => { TagTable => 'Image::ExifTool::PDF::TransformParams' }, + }, +); + +# tags in PDF TransformParams dictionary +%Image::ExifTool::PDF::TransformParams = ( + GROUPS => { 2 => 'Document' }, + Annots => { + Name => 'AnnotationUsageRights', + Notes => q{ + possible values are Create, Delete, Modify, Copy, Import and Export; + additional values for UR3 signatures are Online and SummaryView + }, + List => 1, + }, + Document => { + Name => 'DocumentUsageRights', + Notes => 'only possible value is FullSave', + List => 1, + }, + Form => { + Name => 'FormUsageRights', + Notes => q{ + possible values are FillIn, Import, Export, SubmitStandalone and + SpawnTemplate; additional values for UR3 signatures are BarcodePlaintext and + Online + }, + List => 1, + }, + FormEX => { + Name => 'FormExtraUsageRights', + Notes => 'UR signatures only; only possible value is BarcodePlaintext', + List => 1, + }, + Signature => { + Name => 'SignatureUsageRights', + Notes => 'only possible value is Modify', + List => 1, + }, + EF => { + Name => 'EmbeddedFileUsageRights', + Notes => 'possible values are Create, Delete, Modify and Import', + List => 1, + }, + Msg => 'UsageRightsMessage', + P => { + Name => 'ModificationPermissions', + Notes => q{ + 1-3 for DocMDP signatures, default 2; true/false for UR3 signatures, default + false + }, + PrintConv => { + 1 => 'No changes permitted', + 2 => 'Fill forms, Create page templates, Sign', + 3 => 'Fill forms, Create page templates, Sign, Create/Delete/Edit annotations', + 'true' => 'Restrict all applications to reader permissions', + 'false' => 'Do not restrict applications to reader permissions', + }, + }, + Action => { + Name => 'FieldPermissions', + Notes => 'FieldMDP signatures only', + PrintConv => { + 'All' => 'Disallow changes to all form fields', + 'Include' => 'Disallow changes to specified form fields', + 'Exclude' => 'Allow changes to specified form fields', + }, + }, + Fields => { + Notes => 'FieldMDP signatures only', + Name => 'FormFields', + List => 1, + }, +); + +# unknown tags for use in verbose option +%Image::ExifTool::PDF::Unknown = ( + GROUPS => { 2 => 'Unknown' }, +); + +#------------------------------------------------------------------------------ +# AutoLoad our writer routines when necessary +# +sub AUTOLOAD +{ + return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); +} + +#------------------------------------------------------------------------------ +# Convert from PDF to EXIF-style date/time +# Inputs: 0) PDF date/time string (D:YYYYmmddHHMMSS+HH'MM') +# Returns: EXIF date string (YYYY:mm:dd HH:MM:SS+HH:MM) +sub ConvertPDFDate($) +{ + my $date = shift; + # remove optional 'D:' prefix + $date =~ s/^D://; + # fill in default values if necessary + # YYYYmmddHHMMSS + my $default = '00000101000000'; + if (length $date < length $default) { + $date .= substr($default, length $date); + } + $date =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(.*)/ or return $date; + $date = "$1:$2:$3 $4:$5:$6"; + if ($7) { + my $tz = $7; + if ($tz =~ /^\s*Z/i) { + # ignore any "HH'mm'" after the Z (OS X 10.6 does this) + $date .= 'Z'; + # tolerate some improper formatting in timezone specification + } elsif ($tz =~ /^\s*([-+])\s*(\d+)[': ]+(\d*)/) { + $date .= $1 . $2 . ':' . ($3 || '00'); + } + } + return $date; +} + +#------------------------------------------------------------------------------ +# Locate any object in the XRef tables (including compressed objects) +# Inputs: 0) XRef reference, 1) object reference string (or free object number) +# Returns: offset to object in file or compressed object reference string, +# 0 if object is free, or undefined on error +sub LocateAnyObject($$) +{ + my ($xref, $ref) = @_; + return undef unless $xref; + return $$xref{$ref} if exists $$xref{$ref}; + # get the object number + return undef unless $ref =~ /^(\d+)/; + my $objNum = $1; + # return 0 if the object number has been reused (old object is free) + return 0 if defined $$xref{$objNum}; +# +# scan our XRef stream dictionaries for this object +# + return undef unless $$xref{dicts}; + my $dict; + foreach $dict (@{$$xref{dicts}}) { + # quick check to see if the object is in the range for this xref stream + next if $objNum >= $$dict{Size}; + my $index = $$dict{Index}; + next if $objNum < $$index[0]; + # scan the tables for the specified object + my $size = $$dict{_entry_size}; + my $num = scalar(@$index) / 2; + my $tot = 0; + my $i; + for ($i=0; $i<$num; ++$i) { + my $start = $$index[$i*2]; + my $count = $$index[$i*2+1]; + # table is in ascending order, so quit if we have passed the object + last if $objNum < $start; + if ($objNum < $start + $count) { + my $offset = $size * ($objNum - $start + $tot); + last if $offset + $size > length $$dict{_stream}; + my @c = unpack("x$offset C$size", $$dict{_stream}); + # extract values from this table entry + # (can be 1, 2, 3, 4, etc.. bytes per value) + my (@t, $j, $k); + my $w = $$dict{W}; + for ($j=0; $j<3; ++$j) { + # use default value if W entry is 0 (as per spec) + # - 0th element defaults to 1, others default to 0 + $$w[$j] or $t[$j] = ($j ? 0 : 1), next; + $t[$j] = shift(@c); + for ($k=1; $k < $$w[$j]; ++$k) { + $t[$j] = 256 * $t[$j] + shift(@c); + } + } + # by default, use "o g R" as the xref key + # (o = object number, g = generation number) + my $ref2 = "$objNum $t[2] R"; + if ($t[0] == 1) { + # normal object reference: + # $t[1]=offset of object from start, $t[2]=generation number + $$xref{$ref2} = $t[1]; + } elsif ($t[0] == 2) { + # compressed object reference: + # $t[1]=stream object number, $t[2]=index of object in stream + $ref2 = "$objNum 0 R"; + $$xref{$ref2} = "I$t[2] $t[1] 0 R"; + } elsif ($t[0] == 0) { + # free object: + # $t[1]=next free object in linked list, $t[2]=generation number + $$xref{$ref2} = 0; + } else { + # treat as a null object + $$xref{$ref2} = undef; + } + $$xref{$objNum} = $t[1]; # remember offsets by object number too + return $$xref{$ref} if $ref eq $ref2; + return 0; # object is free or was reused + } + $tot += $count; + } + } + return undef; +} + +#------------------------------------------------------------------------------ +# Locate a regular object in the XRef tables (does not include compressed objects) +# Inputs: 0) XRef reference, 1) object reference string (or free object number) +# Returns: offset to object in file, 0 if object is free, +# or undef on error or if object was compressed +sub LocateObject($$) +{ + my ($xref, $ref) = @_; + my $offset = LocateAnyObject($xref, $ref); + return undef if $offset and $offset =~ /^I/; + return $offset; +} + +#------------------------------------------------------------------------------ +# Check that the correct object is located at the specified file offset +# Inputs: 0) ExifTool ref, 1) object name, 2) object reference string, 3) file offset +# Returns: first non-blank line at start of object, or undef on error +sub CheckObject($$$$) +{ + my ($et, $tag, $ref, $offset) = @_; + my ($data, $obj, $dat, $pat); + + my $raf = $$et{RAF}; + $raf->Seek($offset+$$et{PDFBase}, 0) or $et->Warn("Bad $tag offset"), return undef; + # verify that we are reading the expected object + ($obj = $ref) =~ s/R/obj/; + for (;;) { + $raf->ReadLine($data) or $et->Warn("Error reading $tag data"), return undef; + last if $data =~ s/^$obj//; + next if $data =~ /^\s+$/; # keep reading if this was a blank line + # handle cases where other whitespace characters are used in the object ID string + while ($data =~ /^\d+(\s+\d+)?\s*$/) { + $raf->ReadLine($dat); + $data .= $dat; + } + ($pat = $obj) =~ s/ /\\s+/g; + unless ($data =~ s/$pat//) { + $tag = ucfirst $tag; + $et->Warn("$tag object ($obj) not found at offset $offset"); + return undef; + } + last; + } + # read the first line of data from the object (ignoring blank lines and comments) + for (;;) { + last if $data =~ /\S/ and $data !~ /^\s*%/; + $raf->ReadLine($data) or $et->Warn("Error reading $tag data"), return undef; + } + return $data; +} + +#------------------------------------------------------------------------------ +# Fetch indirect object from file (from inside a stream if required) +# Inputs: 0) ExifTool object reference, 1) object reference string, +# 2) xref lookup, 3) object name (for warning messages) +# Returns: object data or undefined on error +# Notes: sets $lastFetched to the object reference, or undef if the object +# was extracted from an encrypted stream +sub FetchObject($$$$) +{ + my ($et, $ref, $xref, $tag) = @_; + $lastFetched = $ref; # save this for decoding if necessary + my $offset = LocateAnyObject($xref, $ref); + $lastOffset = $offset; + unless ($offset) { + $et->Warn("Bad $tag reference") unless defined $offset; + return undef; + } + my ($data, $obj); + if ($offset =~ s/^I(\d+) //) { + my $index = $1; # object index in stream + my ($objNum) = split ' ', $ref; # save original object number + $ref = $offset; # now a reference to the containing stream object + $obj = $streamObjs{$ref}; + unless ($obj) { + # don't try to load the same object stream twice + return undef if defined $obj; + $streamObjs{$ref} = ''; + # load the parent object stream + $obj = FetchObject($et, $ref, $xref, $tag); + # make sure it contains everything we need + return undef unless defined $obj and ref($obj) eq 'HASH'; + return undef unless $$obj{First} and $$obj{N}; + return undef unless DecodeStream($et, $obj); + # add a special '_table' entry to this dictionary which contains + # the list of object number/offset pairs from the stream header + my $num = $$obj{N} * 2; + my @table = split ' ', $$obj{_stream}, $num; + return undef unless @table == $num; + # remove everything before first object in stream + $$obj{_stream} = substr($$obj{_stream}, $$obj{First}); + $table[$num-1] =~ s/^(\d+).*/$1/s; # trim excess from last number + $$obj{_table} = \@table; + # save the object stream so we don't have to re-load it later + $streamObjs{$ref} = $obj; + } + # verify that we have the specified object + my $i = 2 * $index; + my $table = $$obj{_table}; + unless ($index < $$obj{N} and $$table[$i] == $objNum) { + $et->Warn("Bad index for stream object $tag"); + return undef; + } + # extract the object at the specified index in the stream + # (offsets in table are in sequential order, so we can subtract from + # the next offset to get the object length) + $offset = $$table[$i + 1]; + my $len = ($$table[$i + 3] || length($$obj{_stream})) - $offset; + $data = substr($$obj{_stream}, $offset, $len); + # avoid re-decrypting data in already decrypted streams + undef $lastFetched if $cryptStream; + return ExtractObject($et, \$data); + } + # load the start of the object + $data = CheckObject($et, $tag, $ref, $offset); + return undef unless defined $data; + + return ExtractObject($et, \$data, $$et{RAF}, $xref); +} + +#------------------------------------------------------------------------------ +# Convert PDF value to something readable +# Inputs: 0) PDF object data +# Returns: converted object +sub ReadPDFValue($) +{ + my $str = shift; + # decode all strings in an array + if (ref $str eq 'ARRAY') { + # create new list to not alter the original data when rewriting + my ($val, @vals); + foreach $val (@$str) { + push @vals, ReadPDFValue($val); + } + return \@vals; + } + length $str or return $str; + my $delim = substr($str, 0, 1); + if ($delim eq '(') { # literal string + $str = $1 if $str =~ /^.*?\((.*)\)/s; # remove brackets + # decode escape sequences in literal strings + while ($str =~ /\\(.)/sg) { + my $n = pos($str) - 2; + my $c = $1; + my $r; + if ($c =~ /[0-7]/) { + # get up to 2 more octal digits + $c .= $1 if $str =~ /\G([0-7]{1,2})/g; + # convert octal escape code + $r = chr(oct($c) & 0xff); + } elsif ($c eq "\x0d") { + # the string is continued if the line ends with '\' + # (also remove "\x0d\x0a") + $c .= $1 if $str =~ /\G(\x0a)/g; + $r = ''; + } elsif ($c eq "\x0a") { + $r = ''; + } else { + # convert escaped characters + ($r = $c) =~ tr/nrtbf/\n\r\t\b\f/; + } + substr($str, $n, length($c)+1) = $r; + # continue search after this character + pos($str) = $n + length($r); + } + Crypt(\$str, $lastFetched) if $cryptString; + } elsif ($delim eq '<') { # hex string + # decode hex data + $str =~ tr/0-9A-Fa-f//dc; + $str .= '0' if length($str) & 0x01; # (by the spec) + $str = pack('H*', $str); + Crypt(\$str, $lastFetched) if $cryptString; + } elsif ($delim eq '/') { # name + $str = substr($str, 1); + # convert escape codes (PDF 1.2 or later) + $str =~ s/#([0-9a-f]{2})/chr(hex($1))/sgei if $pdfVer >= 1.2; + } + return $str; +} + +#------------------------------------------------------------------------------ +# Extract PDF object from combination of buffered data and file +# Inputs: 0) ExifTool object reference, 1) data reference, +# 2) optional raf reference, 3) optional xref table +# Returns: converted PDF object or undef on error +# a) dictionary object --> hash reference +# b) array object --> array reference +# c) indirect reference --> scalar reference +# d) string, name, integer, boolean, null --> scalar value +# - updates $$dataPt on return to contain unused data +# - creates two bogus entries ('_stream' and '_tags') in dictionaries to represent +# the stream data and a list of the tags (not including '_stream' and '_tags') +# in their original order +sub ExtractObject($$;$$) +{ + my ($et, $dataPt, $raf, $xref) = @_; + my (@tags, $data, $objData); + my $dict = { }; + my $delim; + + for (;;) { + if ($$dataPt =~ /^\s*(<{1,2}|\[|\()/s) { + $delim = $1; + $$dataPt =~ s/^\s+//; # remove leading white space + $objData = ReadToNested($dataPt, $raf); + return undef unless defined $objData; + last; + } elsif ($$dataPt =~ s{^\s*(\S[^[(/<>\s]*)\s*}{}s) { +# +# extract boolean, numerical, string, name, null object or indirect reference +# + $objData = $1; + # look for an indirect reference + if ($objData =~ /^\d+$/ and $$dataPt =~ s/^(\d+)\s+R//s) { + $objData .= "$1 R"; + $objData = \$objData; # return scalar reference + } + return $objData; # return simple scalar or scalar reference + } + $raf and $raf->ReadLine($data) or return undef; + $$dataPt .= $data; + } +# +# return literal string or hex string without parsing +# + if ($delim eq '(' or $delim eq '<') { + return $objData; +# +# extract array +# + } elsif ($delim eq '[') { + $objData =~ /^.*?\[(.*)\]/s or return undef; + my $data = $1; # brackets removed + my @list; + for (;;) { + last unless $data =~ m{\s*(\S[^[(/<>\s]*)}sg; + my $val = $1; + if ($val =~ /^(<{1,2}|\[|\()/) { + my $pos = pos($data) - length($val); + # nested dict, array, literal string or hex string + my $buff = substr($data, $pos); + $val = ReadToNested(\$buff); + last unless defined $val; + pos($data) = $pos + length($val); + $val = ExtractObject($et, \$val); + } elsif ($val =~ /^\d/) { + my $pos = pos($data); + if ($data =~ /\G\s+(\d+)\s+R/g) { + $val = \ "$val $1 R"; # make a reference + } else { + pos($data) = $pos; + } + } + push @list, $val; + } + return \@list; + } +# +# extract dictionary +# + # Note: entries are not necessarily separated by whitespace (doh!) + # eg) "/Tag/Name", "/Tag(string)", "/Tag[array]", etc are legal! + # Also, they may be separated by a comment (eg. "/Tag%comment\nValue"), + # but comments have already been removed + while ($objData =~ m{(\s*)/([^/[\]()<>{}\s]+)\s*(\S[^[(/<>\s]*)}sg) { + my $tag = $2; + my $val = $3; + if ($val =~ /^(<{1,2}|\[|\()/) { + # nested dict, array, literal string or hex string + $objData = substr($objData, pos($objData)-length($val)); + $val = ReadToNested(\$objData, $raf); + last unless defined $val; + $val = ExtractObject($et, \$val); + pos($objData) = 0; + } elsif ($val =~ /^\d/) { + my $pos = pos($objData); + if ($objData =~ /\G\s+(\d+)\s+R/sg) { + $val = \ "$val $1 R"; # make a reference + } else { + pos($objData) = $pos; + } + } + if ($$dict{$tag}) { + # duplicate dictionary entries are not allowed + $et->Warn("Duplicate '${tag}' entry in dictionary (ignored)"); + } else { + # save the entry + push @tags, $tag; + $$dict{$tag} = $val; + } + } + return undef unless @tags; + $$dict{_tags} = \@tags; + return $dict unless $raf; # direct objects can not have streams +# +# extract the stream object +# + # dictionary must specify stream Length + my $length = $$dict{Length} or return $dict; + if (ref $length) { + $length = $$length; + my $oldpos = $raf->Tell(); + # get the location of the object specifying the length + # (compressed objects are not allowed) + my $offset = LocateObject($xref, $length) or return $dict; + $offset or $et->Warn('Bad stream Length object'), return $dict; + $data = CheckObject($et, 'stream Length', $length, $offset); + defined $data or return $dict; + $data =~ /^\s*(\d+)/ or $et->Warn('Stream Length not found'), return $dict; + $length = $1; + $raf->Seek($oldpos, 0); # restore position to start of stream + } + # extract the trailing stream data + for (;;) { + # find the stream token + if ($$dataPt =~ /(\S+)/) { + last unless $1 eq 'stream'; + # read an extra line because it may contain our \x0a + $$dataPt .= $data if $raf->ReadLine($data); + # remove our stream header + $$dataPt =~ s/^\s*stream(\x0a|\x0d\x0a)//s; + my $more = $length - length($$dataPt); + if ($more > 0) { + unless ($raf->Read($data, $more) == $more) { + $et->Warn('Error reading stream data'); + $$dataPt = ''; + return $dict; + } + $$dict{_stream} = $$dataPt . $data; + $$dataPt = ''; + } elsif ($more < 0) { + $$dict{_stream} = substr($$dataPt, 0, $length); + $$dataPt = substr($$dataPt, $length); + } else { + $$dict{_stream} = $$dataPt; + $$dataPt = ''; + } + last; + } + $raf->ReadLine($data) or last; + $$dataPt .= $data; + } + return $dict; +} + +#------------------------------------------------------------------------------ +# Read to nested delimiter +# Inputs: 0) data reference, 1) optional raf reference +# Returns: data up to and including matching delimiter (or undef on error) +# - updates data reference with trailing data +# - unescapes characters in literal strings +my %closingDelim = ( # lookup for matching delimiter + '(' => ')', + '[' => ']', + '<' => '>', + '<<' => '>>', +); +sub ReadToNested($;$) +{ + my ($dataPt, $raf) = @_; + my @delim = (''); # closing delimiter list, most deeply nested first + pos($$dataPt) = 0; # begin at start of data + for (;;) { + unless ($$dataPt =~ /(\\*)(\(|\)|<{1,2}|>{1,2}|\[|\]|%)/g) { + # must read some more data + my $buff; + last unless $raf and $raf->ReadLine($buff); + $$dataPt .= $buff; + pos($$dataPt) = length($$dataPt) - length($buff); + next; + } + # are we in a literal string? + if ($delim[0] eq ')') { + # ignore escaped delimiters (preceded by odd number of \'s) + next if length($1) & 0x01; + # ignore all delimiters but unescaped braces + next unless $2 eq '(' or $2 eq ')'; + } elsif ($2 eq '%') { + # ignore the comment + my $pos = pos($$dataPt) - 1; + # remove everything from '%' up to but not including newline + $$dataPt =~ /.*/g; + my $end = pos($$dataPt); + $$dataPt = substr($$dataPt, 0, $pos) . substr($$dataPt, $end); + pos($$dataPt) = $pos; + next; + } + if ($closingDelim{$2}) { + # push the corresponding closing delimiter + unshift @delim, $closingDelim{$2}; + next; + } + unless ($2 eq $delim[0]) { + # handle the case where we find a ">>>" and interpret it + # as ">> >" instead of "> >>" + next unless $2 eq '>>' and $delim[0] eq '>'; + pos($$dataPt) = pos($$dataPt) - 1; + } + shift @delim; # remove from nesting list + next if $delim[0]; # keep going if we have more nested delimiters + my $pos = pos($$dataPt); + my $buff = substr($$dataPt, 0, $pos); + $$dataPt = substr($$dataPt, $pos); + return $buff; # success! + } + return undef; # didn't find matching delimiter +} + +#------------------------------------------------------------------------------ +# Decode LZW-encoded data (ref 1) +# Inputs: 0) data reference +# Returns: true on success and data is decoded, or false and data is untouched +sub DecodeLZW($) +{ + my $dataPt = shift; + return 0 if length $$dataPt < 4; + my @lzw = (map(chr, 0..255), undef, undef); # LZW code table + my $mask = 0x01ff; # mask for least-significant 9 bits + my @dat = unpack 'n*', $$dataPt . "\0"; + my $word = ($dat[0] << 16) | $dat[1]; + my ($bit, $pos, $bits, $out) = (0, 2, 9, ''); + my $lastVal; + for (;;) { + # bits are packed MSB first in PDF LZW (the PDF spec doesn't mention this) + my $shift = 32 - ($bit + $bits); + if ($shift < 0) { + return 0 if $pos >= @dat; # missing EOD marker + $word = (($word & 0xffff) << 16) | $dat[$pos++]; # read next word + $bit -= 16; + $shift += 16; + }; + my $code = ($word >> $shift) & $mask; + $bit += $bits; + my $val = $lzw[$code]; + if (defined $val) { + # store new code as previous sequence plus 1st char of new sequence + push @lzw, $lastVal . substr($val, 0, 1) if defined $lastVal; + } elsif ($code == @lzw) { # new code + return 0 unless defined $lastVal; + # we are using the code that we are about to generate, so the last + # character in the new sequence must be the same as the first + # character in the previous sequence (makes sense if you think about it) + $val = $lastVal . substr($lastVal, 0, 1); + push @lzw, $val; + } elsif ($code == 256) { # clear table + splice @lzw, 258; + $bits = 9; + $mask = 0x1ff; + undef $lastVal; + next; + } elsif ($code == 257) { # EOD marker + last; # all done! + } else { + return 0; + } + $out .= $val; # add this byte sequence to the output + # we added a new entry to the LZW table, so we must increase + # the bit width if necessary, up to a maximum of 12 + @lzw >= $mask and $bits < 12 and ++$bits, $mask |= $mask << 1; + $lastVal = $val; + } + $$dataPt = $out; # return decompressed data + return 1; +} + +#------------------------------------------------------------------------------ +# Decode filtered stream +# Inputs: 0) ExifTool object reference, 1) dictionary reference +# Returns: true if stream has been decoded OK +sub DecodeStream($$) +{ + local $_; + my ($et, $dict) = @_; + + return 0 unless $$dict{_stream}; # no stream to decode + + # get list of filters + my (@filters, @decodeParms, $filter); + if (ref $$dict{Filter} eq 'ARRAY') { + @filters = @{$$dict{Filter}}; + } elsif (defined $$dict{Filter}) { + @filters = ($$dict{Filter}); + } + # be sure we can process all the filters before we take the time to do the decryption + foreach $filter (@filters) { + next if $supportedFilter{$filter}; + $et->WarnOnce("Unsupported Filter $filter"); + return 0; + } + # apply decryption first if required (and if the default encryption + # has not been overridden by a Crypt filter. Note: the Crypt filter + # must be first in the Filter array: ref 3, page 38) + unless (defined $$dict{_decrypted} or ($filters[0] and $filters[0] eq '/Crypt')) { + CryptStream($dict, $lastFetched); + } + return 1 unless $$dict{Filter}; # Filter entry is mandatory + return 0 if defined $$dict{_filtered}; # avoid double-filtering + $$dict{_filtered} = 1; # set flag to prevent double-filtering + + # get array of DecodeParms dictionaries + if (ref $$dict{DecodeParms} eq 'ARRAY') { + @decodeParms = @{$$dict{DecodeParms}}; + } else { + @decodeParms = ($$dict{DecodeParms}); + } + + foreach $filter (@filters) { + my $decodeParms = shift @decodeParms; + + if ($filter eq '/FlateDecode') { + # make sure we support the predictor (if used) before decoding + my $pre; + if (ref $decodeParms eq 'HASH') { + $pre = $$decodeParms{Predictor}; + if ($pre and $pre ne '1' and $pre ne '12') { + $et->WarnOnce("FlateDecode Predictor $pre currently not supported"); + return 0; + } + } + if (eval { require Compress::Zlib }) { + my $inflate = Compress::Zlib::inflateInit(); + my ($buff, $stat); + $inflate and ($buff, $stat) = $inflate->inflate($$dict{_stream}); + if ($inflate and $stat == Compress::Zlib::Z_STREAM_END()) { + $$dict{_stream} = $buff; + } else { + $et->Warn('Error inflating stream'); + return 0; + } + } else { + $et->WarnOnce('Install Compress::Zlib to process filtered streams'); + return 0; + } + next unless $pre and $pre eq '12'; # 12 = 'up' prediction + + # apply anti-predictor + my $cols = $$decodeParms{Columns}; + unless ($cols) { + # currently only support 'up' prediction + $et->WarnOnce('No Columns for decoding stream'); + return 0; + } + my @bytes = unpack('C*', $$dict{_stream}); + my @pre = (0) x $cols; # initialize predictor array + my $buff = ''; + while (@bytes > $cols) { + unless (($_ = shift @bytes) == 2) { + $et->WarnOnce("Unsupported PNG filter $_"); # (yes, PNG) + return 0; + } + foreach (@pre) { + $_ = ($_ + shift(@bytes)) & 0xff; + } + $buff .= pack('C*', @pre); + } + $$dict{_stream} = $buff; + + } elsif ($filter eq '/Crypt') { + + # (we shouldn't have to check the _decrypted flag since we + # already checked the _filtered flag, but what the heck...) + next if defined $$dict{_decrypted}; + # assume Identity filter (the default) if DecodeParms are missing + next unless ref $decodeParms eq 'HASH'; + my $name = $$decodeParms{Name}; + next unless defined $name or $name eq 'Identity'; + if ($name ne 'StdCF') { + $et->WarnOnce("Unsupported Crypt Filter $name"); + return 0; + } + unless ($cryptInfo) { + $et->WarnOnce('Missing Encrypt StdCF entry'); + return 0; + } + # decrypt the stream manually because we want to: + # 1) ignore $cryptStream (StmF) setting + # 2) ignore EncryptMetadata setting (I can't find mention of how to + # reconcile this in the spec., but this would make sense) + # 3) avoid adding the crypt key extension (ref 3, page 58, Algorithm 1b) + # 4) set _decrypted flag so we will recrypt according to StmF when + # writing (since we don't yet write Filter'd streams) + Crypt(\$$dict{_stream}, 'none'); + $$dict{_decrypted} = ($cryptStream ? 1 : 0); + + } elsif ($filter eq '/LZWDecode') { + + # make sure we don't have any unsupported decoding parameters + if (ref $decodeParms eq 'HASH') { + if ($$decodeParms{Predictor}) { + $et->WarnOnce("LZWDecode Predictor $$decodeParms{Predictor} currently not supported"); + return 0; + } elsif ($$decodeParms{EarlyChange}) { + $et->WarnOnce("LZWDecode EarlyChange currently not supported"); + return 0; + } + } + unless (DecodeLZW(\$$dict{_stream})) { + $et->WarnOnce('LZW decompress error'); + return 0; + } + + } elsif ($filter eq '/ASCIIHexDecode') { + + $$dict{_stream} =~ s/>.*//; # truncate at '>' (end of data mark) + $$dict{_stream} =~ tr/0-9a-zA-Z//d; # remove illegal characters + $$dict{_stream} = pack 'H*', $$dict{_stream}; + + } elsif ($filter eq '/ASCII85Decode') { + + my ($err, @out, $i); + my ($n, $val) = (0, 0); + foreach (split //, $$dict{_stream}) { + if ($_ ge '!' and $_ le 'u') {; + $val = 85 * $val + ord($_) - 33; + next unless ++$n == 5; + } elsif ($_ eq '~') { + $n == 1 and $err = 1; # error to have a single char in the last group of 5 + for ($i=$n; $i<5; ++$i) { $val *= 85; } + } elsif ($_ eq 'z') { + $n and $err = 2, last; # error if 'z' isn't the first char + $n = 5; + } else { + next if /^\s$/; # ignore white space + $err = 3, last; # any other character is an error + } + $val = unpack('V', pack('N', $val)); # reverse byte order + while (--$n > 0) { + push @out, $val & 0xff; + $val >>= 8; + } + last if $_ eq '~'; + # (both $n and $val are zero again now) + } + $err and $et->WarnOnce("ASCII85Decode error $err"); + $$dict{_stream} = pack('C*', @out); + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Initialize state for RC4 en/decryption (ref 2) +# Inputs: 0) RC4 key string +# Returns: RC4 key hash reference +sub RC4Init($) +{ + my @key = unpack('C*', shift); + my @state = (0 .. 255); + my ($i, $j) = (0, 0); + while ($i < 256) { + my $st = $state[$i]; + $j = ($j + $st + $key[$i % scalar(@key)]) & 0xff; + $state[$i++] = $state[$j]; + $state[$j] = $st; + } + return { State => \@state, XY => [ 0, 0 ] }; +} + +#------------------------------------------------------------------------------ +# Apply RC4 en/decryption (ref 2) +# Inputs: 0) data reference, 1) RC4 key hash reference or RC4 key string +# - can call this method directly with a key string, or with with the key +# reference returned by RC4Init +# - RC4 is a symmetric algorithm, so encryption is the same as decryption +sub RC4Crypt($$) +{ + my ($dataPt, $key) = @_; + $key = RC4Init($key) unless ref $key eq 'HASH'; + my $state = $$key{State}; + my ($x, $y) = @{$$key{XY}}; + + my @data = unpack('C*', $$dataPt); + foreach (@data) { + $x = ($x + 1) & 0xff; + my $stx = $$state[$x]; + $y = ($stx + $y) & 0xff; + my $sty = $$state[$x] = $$state[$y]; + $$state[$y] = $stx; + $_ ^= $$state[($stx + $sty) & 0xff]; + } + $$key{XY} = [ $x, $y ]; + $$dataPt = pack('C*', @data); +} + +#------------------------------------------------------------------------------ +# Update AES cipher with a bit of data +# Inputs: 0) data +# Returns: encrypted data +my $cipherMore; +sub CipherUpdate($) +{ + my $dat = shift; + my $pos = 0; + $dat = $cipherMore . $dat if length $dat; + while ($pos + 16 <= length($dat)) { + substr($dat,$pos,16) = Image::ExifTool::AES::Cipher(substr($dat,$pos,16)); + $pos += 16; + } + if ($pos < length $dat) { + $cipherMore = substr($dat,$pos); + $dat = substr($dat,0,$pos); + } else { + $cipherMore = ''; + } + return $dat; +} + +#------------------------------------------------------------------------------ +# Get encrypted hash +# Inputs: 0) Password, 1) salt, 2) vector, 3) encryption revision +# Returns: hash +sub GetHash($$$$) +{ + my ($password, $salt, $vector, $rev) = @_; + + # return Rev 5 hash + return Digest::SHA::sha256($password, $salt, $vector) if $rev == 5; + + # compute Rev 6 hardened hash + # (ref http://code.google.com/p/origami-pdf/source/browse/lib/origami/encryption.rb) + my $blockSize = 32; + my $input = Digest::SHA::sha256($password, $salt, $vector) . ("\0" x 32); + my $key = substr($input, 0, 16); + my $iv = substr($input, 16, 16); + my $h; + my $x = ''; + my $i = 0; + while ($i < 64 or $i < ord(substr($x,-1,1))+32) { + + my $block = substr($input, 0, $blockSize); + $x = ''; + Image::ExifTool::AES::Crypt(\$x, $key, $iv, 1); + $cipherMore = ''; + + my ($j, $digest); + for ($j=0; $j<64; ++$j) { + $x = ''; + $x .= CipherUpdate($password) if length $password; + $x .= CipherUpdate($block); + $x .= CipherUpdate($vector) if length $vector; + if ($j == 0) { + my @a = unpack('C16', $x); + my $sum = 0; + $sum += $_ foreach @a; + # set SHA block size (32, 48 or 64 bytes = SHA-256, 384 or 512) + $blockSize = 32 + ($sum % 3) * 16; + $digest = Digest::SHA->new($blockSize * 8); + } + $digest->add($x); + } + + $h = $digest->digest(); + $key = substr($h, 0, 16); + substr($input,0,16) = $h; + $iv = substr($h, 16, 16); + ++$i; + } + return substr($h, 0, 32); +} + +#------------------------------------------------------------------------------ +# Initialize decryption +# Inputs: 0) ExifTool object reference, 1) Encrypt dictionary reference, +# 2) ID from file trailer dictionary +# Returns: error string or undef on success (and sets $cryptInfo) +sub DecryptInit($$$) +{ + local $_; + my ($et, $encrypt, $id) = @_; + + undef $cryptInfo; + unless ($encrypt and ref $encrypt eq 'HASH') { + return 'Error loading Encrypt object'; + } + my $filt = $$encrypt{Filter}; + unless ($filt and $filt =~ s/^\///) { + return 'Encrypt dictionary has no Filter!'; + } + # extract some interesting tags + my $ver = $$encrypt{V} || 0; + my $rev = $$encrypt{R} || 0; + my $enc = "$filt V$ver"; + $enc .= ".$rev" if $filt eq 'Standard'; + $enc .= " ($1)" if $$encrypt{SubFilter} and $$encrypt{SubFilter} =~ /^\/(.*)/; + $enc .= ' (' . ($$encrypt{Length} || 40) . '-bit)' if $filt eq 'Standard'; + my $tagTablePtr = GetTagTable('Image::ExifTool::PDF::Encrypt'); + $et->HandleTag($tagTablePtr, 'Filter', $enc); + if ($filt ne 'Standard') { + return "Encryption filter $filt currently not supported"; + } elsif (not defined $$encrypt{R}) { + return 'Standard security handler missing revision'; + } + unless ($$encrypt{O} and $$encrypt{P} and $$encrypt{U}) { + return 'Incomplete Encrypt specification'; + } + if ("$ver.$rev" >= 5.6) { + # apologize for poor performance (AES is a pure Perl implementation) + $et->Warn('Decryption is very slow for encryption V5.6 or higher', 3); + } + $et->HandleTag($tagTablePtr, 'P', $$encrypt{P}); + + my %parm; # optional parameters extracted from Encrypt dictionary + + if ($ver == 1 or $ver == 2) { + $cryptString = $cryptStream = 1; + } elsif ($ver == 4 or $ver == 5) { + # initialize our $cryptString and $cryptStream flags + foreach ('StrF', 'StmF') { + my $flagPt = $_ eq 'StrF' ? \$cryptString : \$cryptStream; + $$flagPt = $$encrypt{$_}; + undef $$flagPt if $$flagPt and $$flagPt eq '/Identity'; + return "Unsupported $_ encryption $$flagPt" if $$flagPt and $$flagPt ne '/StdCF'; + } + if ($cryptString or $cryptStream) { + return 'Missing or invalid Encrypt StdCF entry' unless ref $$encrypt{CF} eq 'HASH' and + ref $$encrypt{CF}{StdCF} eq 'HASH' and $$encrypt{CF}{StdCF}{CFM}; + my $cryptMeth = $$encrypt{CF}{StdCF}{CFM}; + unless ($cryptMeth =~ /^\/(V2|AESV2|AESV3)$/) { + return "Unsupported encryption method $cryptMeth"; + } + # set "_aesv2" or "_aesv3" flag in %$encrypt hash if AES encryption was used + $$encrypt{'_' . lc($1)} = 1 if $cryptMeth =~ /^\/(AESV2|AESV3)$/; + } + if ($ver == 5) { + # validate OE and UE entries + foreach ('OE', 'UE') { + return "Missing Encrypt $_ entry" unless $$encrypt{$_}; + $parm{$_} = ReadPDFValue($$encrypt{$_}); + return "Invalid Encrypt $_ entry" unless length $parm{$_} == 32; + } + require Image::ExifTool::AES; # will need this later + } + } else { + return "Encryption version $ver currently not supported"; + } + $id or return "Can't decrypt (no document ID)"; + + # make sure we have the necessary libraries available + if ($ver < 5) { + unless (eval { require Digest::MD5 }) { + return "Install Digest::MD5 to process encrypted PDF"; + } + } else { + unless (eval { require Digest::SHA }) { + return "Install Digest::SHA to process AES-256 encrypted PDF"; + } + } + + # calculate file-level en/decryption key + my $pad = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08". + "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A"; + my $o = ReadPDFValue($$encrypt{O}); + my $u = ReadPDFValue($$encrypt{U}); + + # set flag indicating whether metadata is encrypted + # (in version 4 and higher, metadata streams may not be encrypted) + if ($ver < 4 or not $$encrypt{EncryptMetadata} or $$encrypt{EncryptMetadata} !~ /false/i) { + $$encrypt{_meta} = 1; + } + # try no password first, then try provided password if available + my ($try, $key); + for ($try=0; ; ++$try) { + my $password; + if ($try == 0) { + $password = ''; + } elsif ($try == 1) { + $password = $et->Options('Password'); + return 'Document is password protected (use Password option)' unless defined $password; + # make sure there is no UTF-8 flag on the password + if ($] >= 5.006 and (eval { require Encode; Encode::is_utf8($password) } or $@)) { + # repack by hand if Encode isn't available + $password = $@ ? pack('C*',unpack($] < 5.010000 ? 'U0C*' : 'C0C*',$password)) : Encode::encode('utf8',$password); + } + } else { + return 'Incorrect password'; + } + if ($ver < 5) { + if (length $password) { + # password must be encoding in PDFDocEncoding (ref iso32000) + $password = $et->Encode($password, 'PDFDoc'); + # truncate or pad the password to exactly 32 bytes + if (length($password) > 32) { + $password = substr($password, 0, 32); + } elsif (length($password) < 32) { + $password .= substr($pad, 0, 32-length($password)); + } + } else { + $password = $pad; + } + $key = $password . $o . pack('V', $$encrypt{P}) . $id; + my $rep = 1; + if ($rev == 3 or $rev == 4) { + # must add this if metadata not encrypted + $key .= "\xff\xff\xff\xff" unless $$encrypt{_meta}; + $rep += 50; # repeat MD5 50 more times if revision is 3 or greater + } + my ($len, $i, $dat); + if ($ver == 1) { + $len = 5; + } else { + $len = $$encrypt{Length} || 40; + $len >= 40 or return 'Bad Encrypt Length'; + $len = int($len / 8); + } + for ($i=0; $i<$rep; ++$i) { + $key = substr(Digest::MD5::md5($key), 0, $len); + } + # decrypt U to see if a user password is required + if ($rev >= 3) { + $dat = Digest::MD5::md5($pad . $id); + RC4Crypt(\$dat, $key); + for ($i=1; $i<=19; ++$i) { + my @key = unpack('C*', $key); + foreach (@key) { $_ ^= $i; } + RC4Crypt(\$dat, pack('C*', @key)); + } + $dat .= substr($u, 16); + } else { + $dat = $pad; + RC4Crypt(\$dat, $key); + } + last if $dat eq $u; # all done if this was the correct key + } else { + return 'Invalid O or U Encrypt entries' if length($o) < 48 or length($u) < 48; + if (length $password) { + # Note: this should be good for passwords containing reasonable characters, + # but to be bullet-proof we need to apply the SASLprep (IETF RFC 4013) profile + # of stringprep (IETF RFC 3454) to the password before encoding in UTF-8 + $password = $et->Encode($password, 'UTF8'); + $password = substr($password, 0, 127) if length($password) > 127; + } + # test for the owner password + my $sha = GetHash($password, substr($o,32,8), substr($u,0,48), $rev); + if ($sha eq substr($o, 0, 32)) { + $key = GetHash($password, substr($o,40,8), substr($u,0,48), $rev); + my $dat = ("\0" x 16) . $parm{OE}; + # decrypt with no padding + my $err = Image::ExifTool::AES::Crypt(\$dat, $key, 0, 1); + return $err if $err; + $key = $dat; # use this as the file decryption key + last; + } + # test for the user password + $sha = GetHash($password, substr($u,32,8), '', $rev); + if ($sha eq substr($u, 0, 32)) { + $key = GetHash($password, substr($u,40,8), '', $rev); + my $dat = ("\0" x 16) . $parm{UE}; + my $err = Image::ExifTool::AES::Crypt(\$dat, $key, 0, 1); + return $err if $err; + $key = $dat; # use this as the file decryption key + last; + } + } + } + $$encrypt{_key} = $key; # save the file-level encryption key + $cryptInfo = $encrypt; # save reference to the file-level Encrypt object + return undef; # success! +} + +#------------------------------------------------------------------------------ +# Decrypt/Encrypt data +# Inputs: 0) data ref +# 1) PDF object reference to use as crypt key extension (may be 'none' to +# avoid extending the encryption key, as for streams with Crypt Filter) +# 2) encrypt flag (false for decryption) +sub Crypt($$;$) +{ + return unless $cryptInfo; + my ($dataPt, $keyExt, $encrypt) = @_; + # do not decrypt if the key extension object is undefined + # (this doubles as a flag to disable decryption/encryption) + return unless defined $keyExt; + my $key = $$cryptInfo{_key}; + # apply the necessary crypt key extension + unless ($$cryptInfo{_aesv3}) { + unless ($keyExt eq 'none') { + # extend crypt key using object and generation number + unless ($keyExt =~ /^(I\d+ )?(\d+) (\d+)/) { + $$cryptInfo{_error} = 'Invalid object reference for encryption'; + return; + } + $key .= substr(pack('V', $2), 0, 3) . substr(pack('V', $3), 0, 2); + } + # add AES-128 salt if necessary (this little gem is conveniently + # omitted from the Adobe PDF 1.6 documentation, causing me to + # waste 12 hours trying to figure out why this wasn't working -- + # it appears in ISO32000 though, so I should have been using that) + $key .= 'sAlT' if $$cryptInfo{_aesv2}; + my $len = length($key); + $key = Digest::MD5::md5($key); # get 16-byte MD5 digest + $key = substr($key, 0, $len) if $len < 16; # trim if necessary + } + # perform the decryption/encryption + if ($$cryptInfo{_aesv2} or $$cryptInfo{_aesv3}) { + require Image::ExifTool::AES; + my $err = Image::ExifTool::AES::Crypt($dataPt, $key, $encrypt); + $err and $$cryptInfo{_error} = $err; + } else { + RC4Crypt($dataPt, $key); + } +} + +#------------------------------------------------------------------------------ +# Decrypt/Encrypt stream data +# Inputs: 0) dictionary ref, 1) PDF object reference to use as crypt key extension +sub CryptStream($$) +{ + return unless $cryptStream; + my ($dict, $keyExt) = @_; + my $type = $$dict{Type} || ''; + # XRef streams are not encrypted (ref 3, page 50), + # and Metadata may or may not be encrypted + if ($cryptInfo and $type ne '/XRef' and + ($$cryptInfo{_meta} or $type ne '/Metadata')) + { + Crypt(\$$dict{_stream}, $keyExt, $$dict{_decrypted}); + # toggle _decrypted flag + $$dict{_decrypted} = ($$dict{_decrypted} ? undef : 1); + } else { + $$dict{_decrypted} = 0; # stream should never be encrypted + } +} + +#------------------------------------------------------------------------------ +# Generate a new PDF tag (based on its ID) and add it to a tag table +# Inputs: 0) tag table ref, 1) tag ID +# Returns: tag info ref +sub NewPDFTag($$) +{ + my ($tagTablePtr, $tag) = @_; + my $name = $tag; + # translate URL-like escape sequences + $name =~ s/#([0-9a-f]{2})/chr(hex($1))/ige; + $name =~ s/[^-\w]+/_/g; # translate invalid characters to an underline + $name =~ s/(^|_)([a-z])/\U$2/g; # start words with upper case + my $tagInfo = { Name => $name }; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + return $tagInfo; +} + +#------------------------------------------------------------------------------ +# Process AcroForm dictionary to set HasXMLFormsArchitecture flag +# Inputs: Same as ProcessDict +sub ProcessAcroForm($$$$;$$) +{ + my ($et, $tagTablePtr, $dict, $xref, $nesting, $type) = @_; + $et->HandleTag($tagTablePtr, '_has_xfa', $$dict{XFA} ? 'true' : 'false'); + return ProcessDict($et, $tagTablePtr, $dict, $xref, $nesting, $type); +} + +#------------------------------------------------------------------------------ +# Expand array into a string +# Inputs: 0) array ref +# Return: string +sub ExpandArray($) +{ + my $val = shift; + my @list = @$val; + foreach (@list) { + ref $_ eq 'SCALAR' and $_ = "ref($$_)", next; + ref $_ eq 'ARRAY' and $_ = ExpandArray($_), next; + defined $_ or $_ = '<undef>', next; + } + return '[' . join(',',@list) . ']'; +} + +#------------------------------------------------------------------------------ +# Process PDF dictionary extract tag values +# Inputs: 0) ExifTool object reference, 1) tag table reference +# 2) dictionary reference, 3) cross-reference table reference, +# 4) nesting depth, 5) dictionary capture type +sub ProcessDict($$$$;$$) +{ + local $_; + my ($et, $tagTablePtr, $dict, $xref, $nesting, $type) = @_; + my $verbose = $et->Options('Verbose'); + my $unknown = $$tagTablePtr{EXTRACT_UNKNOWN}; + my $embedded = (defined $unknown and not $unknown and $et->Options('ExtractEmbedded')); + my @tags = @{$$dict{_tags}}; + my ($next, %join, $validInfo); + my $index = 0; + + $nesting = ($nesting || 0) + 1; + if ($nesting > 50) { + $et->WarnOnce('Nesting too deep (directory ignored)'); + return; + } + # save entire dictionary for rewriting if specified + if ($$et{PDF_CAPTURE} and $$tagTablePtr{VARS} and + $tagTablePtr->{VARS}->{CAPTURE}) + { + my $name; + foreach $name (@{$tagTablePtr->{VARS}->{CAPTURE}}) { + next if $$et{PDF_CAPTURE}{$name}; + # make sure we load the right type if indicated + next if $type and $type ne $name; + $$et{PDF_CAPTURE}{$name} = $dict; + last; + } + } + $validInfo = ($et->Options('Validate') and $tagTablePtr eq \%Image::ExifTool::PDF::Info); +# +# extract information from all tags in the dictionary +# + for (;;) { + my ($tag, $isSubDoc); + if (@tags) { + $tag = shift @tags; + } elsif (defined $next and not $next) { + $tag = 'Next'; + $next = 1; + } else { + last; + } + my $val = $$dict{$tag}; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo) { + undef $tagInfo if $$tagInfo{NoProcess}; + } elsif ($embedded and $tag =~ /^(.*?)(\d+)$/ and + $$tagTablePtr{$1} and (ref $val ne 'SCALAR' or not $fetched{$$val})) + { + my ($name, $num) = ($1, $2); + $tagInfo = $et->GetTagInfo($tagTablePtr, $name); + if (ref $tagInfo eq 'HASH' and $$tagInfo{JoinStreams}) { + $fetched{$$val} = 1; + my $obj = FetchObject($et, $$val, $xref, $tag); + $join{$name} = [] unless $join{$name}; + next unless ref $obj eq 'HASH' and $$obj{_stream}; + # save all the stream data to join later + DecodeStream($et, $obj); + $join{$name}->[$num] = $$obj{_stream}; + undef $tagInfo; # don't process + } else { + $isSubDoc = 1; # treat as a sub-document + } + } + if ($validInfo and $$et{PDFVersion} >= 2.0 and (not $tagInfo or not $$tagInfo{PDF2})) { + my $name = $tagInfo ? ":$$tagInfo{Name}" : " Info tag '${tag}'"; + $et->Warn("PDF$name is deprecated in PDF 2.0"); + } + if ($verbose) { + my ($val2, $extra); + if (ref $val eq 'SCALAR') { + $extra = ", indirect object ($$val)"; + if ($fetched{$$val}) { + $val2 = "ref($$val)"; + } elsif ($tag eq 'Next' and not $next) { + # handle 'Next' links after all others + $next = 0; + next; + } else { + $fetched{$$val} = 1; + $val = FetchObject($et, $$val, $xref, $tag); + unless (defined $val) { + my $str; + if (defined $lastOffset) { + $val2 = '<free>'; + $str = 'Object was freed'; + } else { + $val2 = '<err>'; + $str = 'Error reading object'; + } + $et->VPrint(0, "$$et{INDENT}${str}:\n"); + } + } + } elsif (ref $val eq 'HASH') { + $extra = ', direct dictionary'; + } elsif (ref $val eq 'ARRAY') { + $extra = ', direct array of ' . scalar(@$val) . ' objects'; + } else { + $extra = ', direct object'; + } + my $isSubdir; + if (ref $val eq 'HASH') { + $isSubdir = 1; + } elsif (ref $val eq 'ARRAY') { + # recurse into objects in arrays only if they are lists of + # dictionaries or indirect objects which could be dictionaries + $isSubdir = 1 if @$val; + foreach (@$val) { + next if ref $_ eq 'HASH' or ref $_ eq 'SCALAR'; + undef $isSubdir; + last; + } + } + if ($isSubdir) { + # create bogus subdirectory to recurse into this dict + $tagInfo or $tagInfo = { + Name => $tag, + SubDirectory => { TagTable => 'Image::ExifTool::PDF::Unknown' }, + }; + } else { + $val2 = ExpandArray($val) if ref $val eq 'ARRAY'; + # generate tag info if we will use it later + if (not $tagInfo and defined $val and $unknown) { + $tagInfo = NewPDFTag($tagTablePtr, $tag); + } + } + $et->VerboseInfo($tag, $tagInfo, + Value => $val2 || $val, + Extra => $extra, + Index => $index++, + ); + next unless defined $val; + } + unless ($tagInfo) { + # add any tag found in Info dictionary to table + next unless $unknown; + $tagInfo = NewPDFTag($tagTablePtr, $tag); + } + # increment document number if necessary + my ($oldDocNum, $oldNumTags); + if ($isSubDoc) { + $oldDocNum = $$et{DOC_NUM}; + $oldNumTags = $$et{NUM_FOUND}; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + } + if ($$tagInfo{SubDirectory}) { + # process the subdirectory + my @subDicts; + if (ref $val eq 'ARRAY') { + # hack to convert array to dictionary if necessary + if ($$tagInfo{ConvertToDict} and @$val == 2 and not ref $$val[0]) { + my $tg = $$val[0]; + $tg =~ s(^/)(); # remove name + my %dict = ( _tags => [ $tg ], $tg => $$val[1] ); + @subDicts = ( \%dict ); + } else { + @subDicts = @{$val}; + } + } else { + @subDicts = ( $val ); + } + # loop through all values of this tag + for (;;) { + my $subDict = shift @subDicts or last; + # save last fetched object in case we fetch another one here + my $prevFetched = $lastFetched; + if (ref $subDict eq 'SCALAR') { + # only fetch once (other copies are obsolete) + next if $fetched{$$subDict}; + if ($$tagInfo{IgnoreDuplicates}) { + my $flag = "ProcessedPDF_$tag"; + if ($$et{$flag}) { + next if $et->WarnOnce("Ignored duplicate $tag dictionary", 2); + } else { + $$et{$flag} = 1; + } + } + # load dictionary via an indirect reference + $fetched{$$subDict} = 1; + my $obj = FetchObject($et, $$subDict, $xref, $tag); + unless (defined $obj) { + unless (defined $lastOffset) { + $et->Warn("Error reading $tag object ($$subDict)"); + } + next; + } + $subDict = $obj; + } + if (ref $subDict eq 'ARRAY') { + # convert array of key/value pairs to a hash + next if @$subDict < 2; + my %hash = ( _tags => [] ); + while (@$subDict >= 2) { + my $key = shift @$subDict; + $key =~ s/^\///; + push @{$hash{_tags}}, $key; + $hash{$key} = shift @$subDict; + } + $subDict = \%hash; + } else { + next unless ref $subDict eq 'HASH'; + } + # set flag to re-crypt all strings when rewriting if the dictionary + # came from an encrypted stream + $$subDict{_needCrypt}{'*'} = 1 unless $lastFetched; + my $subTablePtr = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + if (not $verbose) { + my $proc = $$subTablePtr{PROCESS_PROC} || \&ProcessDict; + &$proc($et, $subTablePtr, $subDict, $xref, $nesting); + } elsif ($next) { + # handle 'Next' links at this level to avoid deep recursion + undef $next; + $index = 0; + $tagTablePtr = $subTablePtr; + $dict = $subDict; + @tags = @{$$subDict{_tags}}; + $et->VerboseDir($tag, scalar(@tags)); + } else { + my $oldIndent = $$et{INDENT}; + my $oldDir = $$et{DIR_NAME}; + $$et{INDENT} .= '| '; + $$et{DIR_NAME} = $tag; + $et->VerboseDir($tag, scalar(@{$$subDict{_tags}})); + ProcessDict($et, $subTablePtr, $subDict, $xref, $nesting); + $$et{INDENT} = $oldIndent; + $$et{DIR_NAME} = $oldDir; + } + $lastFetched = $prevFetched; + } + } else { + # fetch object if necessary + # (OS X 10.6 writes indirect objects in the Info dictionary!) + if (ref $val eq 'SCALAR') { + my $prevFetched = $lastFetched; + # (note: fetching the same object multiple times is OK here) + $val = FetchObject($et, $$val, $xref, $tag); + if (defined $val) { + $val = ReadPDFValue($val); + # set flag to re-encrypt if necessary if rewritten + $$dict{_needCrypt}{$tag} = ($lastFetched ? 0 : 1) if $cryptString; + $lastFetched = $prevFetched; # restore last fetched object reference + } + } else { + $val = ReadPDFValue($val); + } + if (ref $val) { + if (ref $val eq 'ARRAY') { + delete $$et{LIST_TAGS}{$tagInfo} if $$tagInfo{List}; + my $v; + foreach $v (@$val) { + $et->FoundTag($tagInfo, $v); + } + } + } elsif (defined $val) { + # convert from UTF-16 (big endian) to UTF-8 or Latin if necessary + # unless this is binary data (hex-encoded strings would not have been converted) + my $format = $$tagInfo{Format} || $$tagInfo{Writable} || 'string'; + $val = ConvertPDFDate($val) if $format eq 'date'; + if (not $$tagInfo{Binary} and $val =~ /[\x18-\x1f\x80-\xff]/) { + # text string is already in Unicode if it starts with "\xfe\xff", + # otherwise we must first convert from PDFDocEncoding + $val = $et->Decode($val, ($val=~s/^\xfe\xff// ? 'UCS2' : 'PDFDoc'), 'MM'); + } + if ($$tagInfo{List} and not $$et{OPTIONS}{NoPDFList}) { + # separate tokens in comma or whitespace delimited lists + my $comma = $val =~ tr/,/,/; + my $semi = $val =~ tr/;/;/; + my $split; + if ($comma or $semi) { + $split = $comma > $semi ? ',+\\s*' : ';+\\s*'; + } else { + $split = ' '; + } + my @values = split $split, $val; + $et->FoundTag($tagInfo, $_) foreach @values; + } else { + # a simple tag value + $et->FoundTag($tagInfo, $val); + } + } + } + if ($isSubDoc) { + # restore original document number + $$et{DOC_NUM} = $oldDocNum; + --$$et{DOC_COUNT} if $oldNumTags == $$et{NUM_FOUND}; + } + } +# +# extract information from joined streams if necessary +# + + if (%join) { + my ($tag, $i); + foreach $tag (sort keys %join) { + my $list = $join{$tag}; + last unless defined $$list[1] and $$list[1] =~ /^%.*?([\x0d\x0a]*)/; + my $buff = "%!PS-Adobe-3.0$1"; # add PS header with same line break + for ($i=1; defined $$list[$i]; ++$i) { + $buff .= $$list[$i]; + undef $$list[$i]; # free memory + } + # increment document number for tags extracted from embedded EPS + my $oldDocNum = $$et{DOC_NUM}; + my $oldNumTags = $$et{NUM_FOUND}; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + # extract PostScript information + $et->HandleTag($tagTablePtr, $tag, $buff); + $$et{DOC_NUM} = $oldDocNum; + # revert document counter if we didn't add any new tags + --$$et{DOC_COUNT} if $oldNumTags == $$et{NUM_FOUND}; + delete $$et{DOC_NUM}; + } + } +# +# extract information from stream object if it exists (eg. Metadata stream) +# + for (;;) { # (cheap goto) + last unless $$dict{_stream}; + my $tag = '_stream'; + # add Subtype (if it exists) to stream name and remove leading '/' + ($tag = $$dict{Subtype} . $tag) =~ s/^\/// if $$dict{Subtype}; + last unless $$tagTablePtr{$tag}; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag) or last; + unless ($$tagInfo{SubDirectory}) { + # don't build filter lists across different images + delete $$et{LIST_TAGS}{$$tagTablePtr{Filter}}; + # we arrive here only when extracting embedded images + # - only extract known image types and ignore others + my $filter = $$dict{Filter} || ''; + $filter = @$filter[-1] if ref $filter eq 'ARRAY'; # (get last Filter type) + my $result; + if ($filter eq '/DCTDecode' or $filter eq '/JPXDecode') { + DecodeStream($et, $dict) or last; + # save the image itself + $et->FoundTag($tagInfo, \$$dict{_stream}); + # extract information from embedded image + $result = $et->ExtractInfo(\$$dict{_stream}, { ReEntry => 1 }); + } + unless ($result) { + $et->FoundTag('FileType', defined $result ? '(unknown)' : '(unsupported)'); + } + last; + } + # decode stream if necessary + DecodeStream($et, $dict) or last; + if ($verbose > 2) { + $et->VPrint(2,"$$et{INDENT}$$et{DIR_NAME} stream data\n"); + $et->VerboseDump(\$$dict{_stream}); + } + # extract information from stream + my %dirInfo = ( + DataPt => \$$dict{_stream}, + DataLen => length $$dict{_stream}, + DirStart => 0, + DirLen => length $$dict{_stream}, + Parent => 'PDF', + ); + my $subTablePtr = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + unless ($et->ProcessDirectory(\%dirInfo, $subTablePtr)) { + $et->Warn("Error processing $$tagInfo{Name} information"); + } + last; + } +} + +#------------------------------------------------------------------------------ +# Extract information from PDF file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 0 if not a PDF file, 1 on success, otherwise a negative error number +sub ReadPDF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my ($buff, $encrypt, $id); +# +# validate PDF file +# + # (linearization dictionary must be in the first 1024 bytes of the file) + $raf->Read($buff, 1024) >= 8 or return 0; + $buff =~ /^(\s*)%PDF-(\d+\.\d+)/ or return 0; + $$et{PDFBase} = length $1 and $et->Warn('PDF header is not at start of file',1); + $pdfVer = $$et{PDFVersion} = $2; + $et->SetFileType(); # set the FileType tag + # store PDFVersion tag + my $tagTablePtr = GetTagTable('Image::ExifTool::PDF::Root'); + $et->HandleTag($tagTablePtr, 'Version', $pdfVer); + $tagTablePtr = GetTagTable('Image::ExifTool::PDF::Main'); +# +# check for a linearized PDF (only if reading) +# + my $capture = $$et{PDF_CAPTURE}; + unless ($capture) { + my $lin = 'false'; + if ($buff =~ /<</g) { + $buff = substr($buff, pos($buff) - 2); + my $dict = ExtractObject($et, \$buff); + if (ref $dict eq 'HASH' and $$dict{Linearized} and $$dict{L}) { + if (not $$et{VALUE}{FileSize}) { + undef $lin; # can't determine if it is linearized + } elsif ($$dict{L} == $$et{VALUE}{FileSize} - $$et{PDFBase}) { + $lin = 'true'; + } + } + } + $et->HandleTag($tagTablePtr, '_linearized', $lin) if $lin; + } +# +# read the xref tables referenced from startxref at the end of the file +# + my @xrefOffsets; + $raf->Seek(0, 2) or return -2; + # the %%EOF must occur within the last 1024 bytes of the file (PDF spec, appendix H) + my $len = $raf->Tell(); + $len = 1024 if $len > 1024; + $raf->Seek(-$len, 2) or return -2; + $raf->Read($buff, $len) == $len or return -3; + # find the LAST xref table in the file (may be multiple %%EOF marks, + # and comments between "startxref" and "%%EOF") + $buff =~ /^.*startxref(\s+)(\d+)(\s+)(%[^\x0d\x0a]*\s+)*%%EOF/s or return -4; + my $ws = $1 . $3; + my $xr = $2; + push @xrefOffsets, $xr, 'Main'; + # set input record separator + local $/ = $ws =~ /(\x0d\x0a|\x0d|\x0a)/ ? $1 : "\x0a"; + my (%xref, @mainDicts, %loaded, $mainFree); + my ($xrefSize, $mainDictSize) = (0, 0); + # initialize variables to capture when rewriting + if ($capture) { + $capture->{startxref} = $xr; + $capture->{xref} = \%xref; + $capture->{newline} = $/; + $capture->{mainFree} = $mainFree = { }; + } +XRef: + while (@xrefOffsets) { + my $offset = shift @xrefOffsets; + my $type = shift @xrefOffsets; + next if $loaded{$offset}; # avoid infinite recursion + unless ($raf->Seek($offset+$$et{PDFBase}, 0)) { + %loaded or return -5; + $et->Warn('Bad offset for secondary xref table'); + next; + } + # Note: care must be taken because ReadLine may read more than we want if + # the newline sequence for this table is different than the rest of the file + for (;;) { + unless ($raf->ReadLine($buff)) { + %loaded or return -6; + $et->Warn('Bad offset for secondary xref table'); + next XRef; + } + last if $buff =~/\S/; # skip blank lines + } + my $loadXRefStream; + if ($buff =~ s/^\s*xref\s+//s) { + # load xref table + for (;;) { + # read another line if necessary (skipping blank lines) + $raf->ReadLine($buff) or return -6 until $buff =~ /\S/; + last if $buff =~ s/^\s*trailer([\s<[(])/$1/s; + $buff =~ s/^\s*(\d+)\s+(\d+)\s+//s or return -4; + my ($start, $num) = ($1, $2); + $raf->Seek(-length($buff), 1) or return -4; + my $i; + for ($i=0; $i<$num; ++$i) { + $raf->Read($buff, 20) == 20 or return -6; + $buff =~ /^\s*(\d{10}) (\d{5}) (f|n)/s or return -4; + my $num = $start + $i; + $xrefSize = $num if $num > $xrefSize; + # locate object to generate entry from stream if necessary + # (must do this before we test $xref{$num}) + LocateAnyObject(\%xref, $num) if $xref{dicts}; + # save offset for newest copy of all objects + # (or next object number for free objects) + unless (defined $xref{$num}) { + my ($offset, $gen) = (int($1), int($2)); + $xref{$num} = $offset; + if ($3 eq 'f') { + # save free objects in last xref table for rewriting + $$mainFree{$num} = [ $offset, $gen, 'f' ] if $mainFree; + next; + } + # also save offset keyed by object reference string + $xref{"$num $gen R"} = $offset; + } + } + # (I have a sample from Adobe which has an empty xref table) + # %xref or return -4; # xref table may not be empty + $buff = ''; + } + undef $mainFree; # only do this for the last xref table + } elsif ($buff =~ s/^\s*(\d+)\s+(\d+)\s+obj//s) { + # this is a PDF-1.5 cross-reference stream dictionary + $loadXRefStream = 1; + } else { + %loaded or return -4; + $et->Warn('Invalid secondary xref table'); + next; + } + my $mainDict = ExtractObject($et, \$buff, $raf, \%xref); + unless (ref $mainDict eq 'HASH') { + %loaded or return -8; + $et->Warn('Error loading secondary dictionary'); + next; + } + # keep track of total trailer dictionary Size + $mainDictSize = $$mainDict{Size} if $$mainDict{Size} and $$mainDict{Size} > $mainDictSize; + if ($loadXRefStream) { + # decode and save our XRef stream from PDF-1.5 file + # (but parse it later as required to save time) + # Note: this technique can potentially result in an old object + # being used if the file was incrementally updated and an older + # object from an xref table was replaced by a newer object in an + # xref stream. But doing so isn't a good idea (if allowed at all) + # because a PDF 1.4 consumer would also make this same mistake. + if ($$mainDict{Type} eq '/XRef' and $$mainDict{W} and + @{$$mainDict{W}} > 2 and $$mainDict{Size} and + DecodeStream($et, $mainDict)) + { + # create Index entry if it doesn't exist + $$mainDict{Index} or $$mainDict{Index} = [ 0, $$mainDict{Size} ]; + # create '_entry_size' entry for internal use + my $w = $$mainDict{W}; + my $size = 0; + foreach (@$w) { $size += $_; } + $$mainDict{_entry_size} = $size; + # save this stream dictionary to use later if required + $xref{dicts} = [] unless $xref{dicts}; + push @{$xref{dicts}}, $mainDict; + } else { + %loaded or return -9; + $et->Warn('Invalid xref stream in secondary dictionary'); + } + } + $loaded{$offset} = 1; + # load XRef stream in hybrid file if it exists + push @xrefOffsets, $$mainDict{XRefStm}, 'XRefStm' if $$mainDict{XRefStm}; + $encrypt = $$mainDict{Encrypt} if $$mainDict{Encrypt}; + undef $encrypt if $encrypt and $encrypt eq 'null'; # (have seen "null") + if ($$mainDict{ID} and ref $$mainDict{ID} eq 'ARRAY') { + $id = ReadPDFValue($mainDict->{ID}->[0]); + } + push @mainDicts, $mainDict, $type; + # load previous xref table if it exists + push @xrefOffsets, $$mainDict{Prev}, 'Prev' if $$mainDict{Prev}; + } + if ($xrefSize > $mainDictSize) { + my $str = "Objects in xref table ($xrefSize) exceed trailer dictionary Size ($mainDictSize)"; + $capture ? $et->Error($str) : $et->Warn($str); + } +# +# extract encryption information if necessary +# + if ($encrypt) { + if (ref $encrypt eq 'SCALAR') { + $encrypt = FetchObject($et, $$encrypt, \%xref, 'Encrypt'); + } + # generate Encryption tag information + my $err = DecryptInit($et, $encrypt, $id); + if ($err) { + $et->Warn($err); + $$capture{Error} = $err if $capture; + return -1; + } + } +# +# extract the information beginning with each of the main dictionaries +# + my $i = 0; + my $num = (scalar @mainDicts) / 2; + while (@mainDicts) { + my $dict = shift @mainDicts; + my $type = shift @mainDicts; + if ($verbose) { + ++$i; + my $n = scalar(@{$$dict{_tags}}); + $et->VPrint(0, "PDF dictionary ($i of $num) with $n entries:\n"); + } + ProcessDict($et, $tagTablePtr, $dict, \%xref, 0, $type); + } + # handle any decryption errors + if ($encrypt) { + my $err = $$encrypt{_error}; + if ($err) { + $et->Warn($err); + $$capture{Error} = $err if $capture; + return -1; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# ReadPDF() warning strings for each error return value +my %pdfWarning = ( + # -1 is reserved as error return value with no associated warning + -2 => 'Error seeking in file', + -3 => 'Error reading file', + -4 => 'Invalid xref table', + -5 => 'Invalid xref offset', + -6 => 'Error reading xref table', + -7 => 'Error reading trailer', + -8 => 'Error reading main dictionary', + -9 => 'Invalid xref stream in main dictionary', +); + +#------------------------------------------------------------------------------ +# Extract information from PDF file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 if this was a valid PDF file +sub ProcessPDF($$) +{ + my ($et, $dirInfo) = @_; + + undef $cryptInfo; # (must not delete after returning so writer can use it) + undef $cryptStream; + undef $cryptString; + my $result = ReadPDF($et, $dirInfo); + if ($result < 0) { + $et->Warn($pdfWarning{$result}) if $pdfWarning{$result}; + $result = 1; + } + # clean up and return + undef %streamObjs; + undef %fetched; + return $result; +} + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::PDF - Read PDF meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This code reads meta information from PDF (Adobe Portable Document Format) +files. It supports object streams introduced in PDF-1.5 but only with a +limited set of Filter and Predictor algorithms, however all standard +encryption methods through PDF-2.0 are supported, including AESV2 (AES-128) +and AESV3 (AES-256). + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://partners.adobe.com/public/developer/pdf/index_reference.html> + +=item L<Crypt::RC4|Crypt::RC4> + +=item L<http://www.adobe.com/devnet/acrobat/pdfs/PDF32000_2008.pdf> + +=item L<http://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/adobe_supplement_iso32000.pdf> + +=item L<http://tools.ietf.org/search/rfc3454> + +=item L<http://www.armware.dk/RFC/rfc/rfc4013.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PDF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/PGF.pm b/ExifTool/lib/Image/ExifTool/PGF.pm new file mode 100644 index 0000000..381b2bb --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PGF.pm @@ -0,0 +1,143 @@ +#------------------------------------------------------------------------------ +# File: PGF.pm +# +# Description: Read Progressive Graphics File meta information +# +# Revisions: 2011/01/25 - P. Harvey Created +# +# References: 1) http://www.libpgf.org/ +# 2) http://www.exiv2.org/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::PGF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.02'; + +# PGF header information +%Image::ExifTool::PGF::Main = ( + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + PRIORITY => 2, # (to take precedence over PNG tags from embedded image) + NOTES => q{ + The following table lists information extracted from the header of + Progressive Graphics File (PGF) images. As well, information is extracted + from the embedded PNG metadata image if it exists. See + L<http://www.libpgf.org/> for the PGF specification. + }, + 3 => { + Name => 'PGFVersion', + PrintConv => 'sprintf("0x%.2x", $val)', + # this is actually a bitmask (ref digikam PGFtypes.h): + # 0x02 - data structure PGFHeader of major version 2 + # 0x04 - 32-bit values + # 0x08 - supports regions of interest + # 0x10 - new coding scheme since major version 5 + # 0x20 - new HeaderSize: 32 bits instead of 16 bits + }, + 8 => { Name => 'ImageWidth', Format => 'int32u' }, + 12 => { Name => 'ImageHeight', Format => 'int32u' }, + 16 => 'PyramidLevels', + 17 => 'Quality', + 18 => 'BitsPerPixel', + 19 => 'ColorComponents', + 20 => { + Name => 'ColorMode', + RawConv => '$$self{PGFColorMode} = $val', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Bitmap', + 1 => 'Grayscale', + 2 => 'Indexed', + 3 => 'RGB', + 4 => 'CMYK', + 7 => 'Multichannel', + 8 => 'Duotone', + 9 => 'Lab', + }, + }, + 21 => { Name => 'BackgroundColor', Format => 'int8u[3]' }, +); + +#------------------------------------------------------------------------------ +# Extract information from a PGF image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid PGF file +sub ProcessPGF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + + # read header and check magic number + return 0 unless $raf->Read($buff, 24) == 24 and $buff =~ /^PGF(.)/s; + my $ver = ord $1; + $et->SetFileType(); + SetByteOrder('II'); + + # currently support only version 0x36 + unless ($ver == 0x36) { + $et->Error(sprintf('Unsupported PGF version 0x%.2x', $ver)); + return 1; + } + # extract information from the PGF header + my $tagTablePtr = GetTagTable('Image::ExifTool::PGF::Main'); + $et->ProcessDirectory({ DataPt => \$buff, DataPos => 0 }, $tagTablePtr); + + my $len = Get32u(\$buff, 4) - 16; # length of post-header data + + # skip colour table if necessary + $len -= $raf->Seek(1024, 1) ? 1024 : $len if $$et{PGFColorMode} == 2; + + # extract information from the embedded metadata image (PNG format) + if ($len > 0 and $len < 0x1000000 and $raf->Read($buff, $len) == $len) { + $et->ExtractInfo(\$buff, { ReEntry => 1 }); + } + return 1; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PGF - Read Progressive Graphics File meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Progressive Graphics File (PGF) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.libpgf.org/> + +=item L<http://www.exiv2.org/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PGF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/PICT.pm b/ExifTool/lib/Image/ExifTool/PICT.pm new file mode 100644 index 0000000..123c3e0 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PICT.pm @@ -0,0 +1,1260 @@ +#------------------------------------------------------------------------------ +# File: PICT.pm +# +# Description: Read PICT meta information +# +# Revisions: 10/10/2005 - P. Harvey Created +# +# Notes: Extraction of PICT opcodes is still experimental +# +# - size difference in PixPat color table?? (imagemagick reads only 1 long per entry) +# - other differences in the way imagemagick reads 16-bit images +# +# References: 1) http://developer.apple.com/documentation/mac/QuickDraw/QuickDraw-2.html +# 2) http://developer.apple.com/documentation/QuickTime/INMAC/QT/iqImageCompMgr.a.htm +#------------------------------------------------------------------------------ + +package Image::ExifTool::PICT; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.05'; + +sub ReadPictValue($$$;$); + +my ($vers, $extended); # PICT version number, and extended flag +my ($verbose, $out, $indent); # used in verbose mode + +# ranges of reserved opcodes. +# opcodes at the start of each range must be defined in the tag table +my @reserved = ( + 0x0017 => 0x0019, 0x0024 => 0x0027, 0x0035 => 0x0037, 0x003d => 0x003f, + 0x0045 => 0x0047, 0x004d => 0x004f, 0x0055 => 0x0057, 0x005d => 0x005f, + 0x0065 => 0x0067, 0x006d => 0x006f, 0x0075 => 0x0077, 0x007d => 0x007f, + 0x0085 => 0x0087, 0x008d => 0x008f, 0x0092 => 0x0097, 0x00a2 => 0x00af, + 0x00b0 => 0x00cf, 0x00d0 => 0x00fe, 0x0100 => 0x01ff, 0x0300 => 0x0bfe, + 0x0c01 => 0x7eff, 0x7f00 => 0x7fff, 0x8000 => 0x80ff, 0x8100 => 0x81ff, + 0x8201 => 0xffff, +); + +# Apple data structures in PICT images +my %structs = ( + Arc => [ + rect => 'Rect', + startAng => 'int16s', + arcAng => 'int16s', + ], + BitMap => [ + # (no baseAddr) + rowBytes => 'int16u', + bounds => 'Rect', + ], + # BitsRect data for PICT version 1 + BitsRect1 => [ + bitMap => 'BitMap', + srcRect => 'Rect', + dstRect => 'Rect', + mode => 'int16u', + dataSize => 'int16u', + bitData => 'binary[$val{dataSize}]', + ], + # BitsRect data for PICT version 2 + BitsRect2 => [ + pixMap => 'PixMap', + colorTable => 'ColorTable', + srcRect => 'Rect', + dstRect => 'Rect', + mode => 'int16u', + pixData => \ 'GetPixData($val{pixMap}, $raf)', + ], + # BitsRgn data for PICT version 1 + BitsRgn1 => [ + bitMap => 'BitMap', + srcRect => 'Rect', + dstRect => 'Rect', + mode => 'int16u', + maskRgn => 'Rgn', + dataSize => 'int16u', + bitData => 'binary[$val{dataSize}]', + ], + # BitsRgn data for PICT version 2 + BitsRgn2 => [ + pixMap => 'PixMap', + colorTable => 'ColorTable', + srcRect => 'Rect', + dstRect => 'Rect', + mode => 'int16u', + maskRgn => 'Rgn', + pixData => \ 'GetPixData($val{pixMap}, $raf)', + ], + ColorSpec => [ + value => 'int16u', + rgb => 'RGBColor', + ], + ColorTable => [ + ctSeed => 'int32u', + ctFlags => 'int16u', + ctSize => 'int16u', + ctTable => 'ColorSpec[$val{ctSize}+1]', + ], + # http://developer.apple.com/documentation/QuickTime/INMAC/QT/iqImageCompMgr.a.htm + CompressedQuickTime => [ + size => 'int32u', # size NOT including size word + version => 'int16u', + matrix => 'int32u[9]', + matteSize => 'int32u', + matteRect => 'Rect', + mode => 'int16u', + srcRect => 'Rect', + accuracy => 'int32u', + maskSize => 'int32u', + matteDescr => 'Int32uData[$val{matteSize} ? 1 : 0]', + matteData => 'int8u[$val{matteSize}]', + maskRgn => 'int8u[$val{maskSize}]', + imageDescr => 'ImageDescription', + # size should be $val{imageDescr}->{dataSize}, but this is unreliable + imageData => q{binary[$val{size} - 68 - $val{maskSize} - $val{imageDescr}->{size} - + ($val{matteSize} ? $val{mattSize} + $val{matteDescr}->{size} : 0)] + }, + ], + DirectBitsRect => [ + baseAddr => 'int32u', + pixMap => 'PixMap', + srcRect => 'Rect', + dstRect => 'Rect', + mode => 'int16u', + pixData => \ 'GetPixData($val{pixMap}, $raf)', + ], + DirectBitsRgn => [ + baseAddr => 'int32u', + pixMap => 'PixMap', + srcRect => 'Rect', + dstRect => 'Rect', + mode => 'int16u', + maskRgn => 'Rgn', + pixData => \ 'GetPixData($val{pixMap}, $raf)', + ], + # http://developer.apple.com/technotes/qd/qd_01.html + FontName => [ + size => 'int16u', # size NOT including size word + oldFontID => 'int16u', + nameLen => 'int8u', + fontName => 'string[$val{nameLen}]', + padding => 'binary[$val{size} - $val{nameLen} - 3]', + ], + # http://developer.apple.com/documentation/QuickTime/APIREF/imagedescription.htm + ImageDescription => [ + size => 'int32u', # size INCLUDING size word + cType => 'string[4]', + res1 => 'int32u', + res2 => 'int16u', + dataRefIndex => 'int16u', + version => 'int16u', + revision => 'int16u', + vendor => 'string[4]', + temporalQuality => 'int32u', + quality => 'int32u', + width => 'int16u', + height => 'int16u', + hRes => 'fixed32u', + vRes => 'fixed32u', + dataSize => 'int32u', + frameCount => 'int16u', + nameLen => 'int8u', + compressor => 'string[31]', + depth => 'int16u', + clutID => 'int16u', + clutData => 'binary[$val{size}-86]', + ], + Int8uText => [ + val => 'int8u', + count => 'int8u', + text => 'string[$val{count}]', + ], + Int8u2Text => [ + val => 'int8u[2]', + count => 'int8u', + text => 'string[$val{count}]', + ], + Int16Data => [ + size => 'int16u', # size NOT including size word + data => 'int8u[$val{size}]', + ], + Int32uData => [ + size => 'int32u', # size NOT including size word + data => 'int8u[$val{size}]', + ], + LongComment => [ + kind => 'int16u', + size => 'int16u', # size of data only + data => 'binary[$val{size}]', + ], + PixMap => [ + # Note: does not contain baseAddr + # (except for DirectBits opcodes in which it is loaded separately) + rowBytes => 'int16u', + bounds => 'Rect', + pmVersion => 'int16u', + packType => 'int16u', + packSize => 'int32u', + hRes => 'fixed32s', + vRes => 'fixed32s', + pixelType => 'int16u', + pixelSize => 'int16u', + cmpCount => 'int16u', + cmpSize => 'int16u', + planeBytes => 'int32u', + pmTable => 'int32u', + pmReserved => 'int32u', + ], + PixPat => [ + patType => 'int16u', # 1 = non-dithered, 2 = dithered + pat1Data => 'int8u[8]', + # dithered PixPat has RGB entry + RGB => 'RGBColor[$val{patType} == 2 ? 1 : 0]', + # non-dithered PixPat has other stuff instead + nonDithered=> 'PixPatNonDithered[$val{patType} == 2 ? 0 : 1]', + ], + PixPatNonDithered => [ + pixMap => 'PixMap', + colorTable => 'ColorTable', + pixData => \ 'GetPixData($val{pixMap}, $raf)', + ], + Point => [ + v => 'int16s', + h => 'int16s', + ], + PointText => [ + txLoc => 'Point', + count => 'int8u', + text => 'string[$val{count}]', + ], + Polygon => [ + polySize => 'int16u', + polyBBox => 'Rect', + polyPoints => 'int16u[($val{polySize}-10)/2]', + ], + Rect => [ + topLeft => 'Point', + botRight => 'Point', + ], + RGBColor => [ + red => 'int16u', + green => 'int16u', + blue => 'int16u', + ], + Rgn => [ + rgnSize => 'int16u', + rgnBBox => 'Rect', + data => 'int8u[$val{rgnSize}-10]', + ], + ShortLine => [ + pnLoc => 'Point', + dh => 'int8s', + dv => 'int8s', + ], + # http://developer.apple.com/documentation/QuickTime/INMAC/QT/iqImageCompMgr.a.htm + UncompressedQuickTime => [ + size => 'int32u', # size NOT including size word + version => 'int16u', + matrix => 'int32u[9]', + matteSize => 'int32u', + matteRect => 'Rect', + matteDescr => 'Int32uData[$val{matteSize} ? 1 : 0]', + matteData => 'binary[$val{matteSize}]', + subOpcodeData => q{ + binary[ $val{size} - 50 - + ($val{matteSize} ? $val{mattSize} + $val{matteDescr}->{size} : 0)] + }, + ], +); + +# PICT image opcodes +%Image::ExifTool::PICT::Main = ( + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ +The PICT format contains no true meta information, except for the possible +exception of the LongComment opcode. By default, only ImageWidth, +ImageHeight and X/YResolution are extracted from a PICT image. Tags in the +following table represent image opcodes. Extraction of these tags is +experimental, and is only enabled with the Verbose or Unknown options. + }, + 0x0000 => { + Name => 'Nop', + Description => 'No Operation', + Format => 'null', + }, + 0x0001 => { + Name => 'ClipRgn', + Description => 'Clipping Region', + Format => 'Rgn', + }, + 0x0002 => { + Name => 'BkPat', + Description => 'Background Pattern', + Format => 'int8u[8]', + }, + 0x0003 => { + Name => 'TxFont', + Description => 'Font Number', + Format => 'int16u', + }, + 0x0004 => { + Name => 'TxFace', + Description => 'Text Font Style', + Format => 'int8u', + }, + 0x0005 => { + Name => 'TxMode', + Description => 'Text Source Mode', + Format => 'int16u', + }, + 0x0006 => { + Name => 'SpExtra', + Description => 'Extra Space', + Format => 'fixed32s', + }, + 0x0007 => { + Name => 'PnSize', + Description => 'Pen Size', + Format => 'Point', + }, + 0x0008 => { + Name => 'PnMode', + Description => 'Pen Mode', + Format => 'int16u', + }, + 0x0009 => { + Name => 'PnPat', + Description => 'Pen Pattern', + Format => 'int8u[8]', + }, + 0x000a => { + Name => 'FillPat', + Description => 'Fill Pattern', + Format => 'int8u[8]', + }, + 0x000b => { + Name => 'OvSize', + Description => 'Oval Size', + Format => 'Point', + }, + 0x000c => { + Name => 'Origin', + Format => 'Point', + }, + 0x000d => { + Name => 'TxSize', + Description => 'Text Size', + Format => 'int16u', + }, + 0x000e => { + Name => 'FgColor', + Description => 'Foreground Color', + Format => 'int32u', + }, + 0x000f => { + Name => 'BkColor', + Description => 'Background Color', + Format => 'int32u', + }, + 0x0010 => { + Name => 'TxRatio', + Description => 'Text Ratio', + Format => 'Rect', + }, + 0x0011 => { + Name => 'VersionOp', + Description => 'Version', + Format => 'int8u', + }, + 0x0012 => { + Name => 'BkPixPat', + Description => 'Background Pixel Pattern', + Format => 'PixPat', + }, + 0x0013 => { + Name => 'PnPixPat', + Description => 'Pen Pixel Pattern', + Format => 'PixPat', + }, + 0x0014 => { + Name => 'FillPixPat', + Description => 'Fill Pixel Pattern', + Format => 'PixPat', + }, + 0x0015 => { + Name => 'PnLocHFrac', + Description => 'Fractional Pen Position', + Format => 'int16u', + }, + 0x0016 => { + Name => 'ChExtra', + Description => 'Added Width for NonSpace Characters', + Format => 'int16u', + }, + 0x0017 => { + Name => 'Reserved', + Format => 'Unknown', + }, + 0x001a => { + Name => 'RGBFgCol', + Description => 'Foreground Color', + Format => 'RGBColor', + }, + 0x001b => { + Name => 'RGBBkCol', + Description => 'Background Color', + Format => 'RGBColor', + }, + 0x001c => { + Name => 'HiliteMode', + Description => 'Highlight Mode Flag', + Format => 'null', + }, + 0x001d => { + Name => 'HiliteColor', + Description => 'Highlight Color', + Format => 'RGBColor', + }, + 0x001e => { + Name => 'DefHilite', + Description => 'Use Default Highlight Color', + Format => 'null', + }, + 0x001f => { + Name => 'OpColor', + Format => 'RGBColor', + }, + 0x0020 => { + Name => 'Line', + Format => 'Rect', + }, + 0x0021 => { + Name => 'LineFrom', + Format => 'Point', + }, + 0x0022 => { + Name => 'ShortLine', + Format => 'ShortLine', + }, + 0x0023 => { + Name => 'ShortLineFrom', + Format => 'int8u[2]', + }, + 0x0024 => { + Name => 'Reserved', + Format => 'Int16Data', + }, + 0x0028 => { + Name => 'LongText', + Format => 'PointText', + }, + 0x0029 => { + Name => 'DHText', + Format => 'Int8uText', + }, + 0x002a => { + Name => 'DVText', + Format => 'Int8uText', + }, + 0x002b => { + Name => 'DHDVText', + Format => 'Int8u2Text', + }, + 0x002c => { + Name => 'FontName', + Format => 'FontName', + }, + 0x002d => { + Name => 'LineJustify', + Format => 'int8u[10]', + }, + 0x002e => { + Name => 'GlyphState', + Format => 'int8u[8]', + }, + 0x002f => { + Name => 'Reserved', + Format => 'Int16Data', + }, + 0x0030 => { + Name => 'FrameRect', + Format => 'Rect', + }, + 0x0031 => { + Name => 'PaintRect', + Format => 'Rect', + }, + 0x0032 => { + Name => 'EraseRect', + Format => 'Rect', + }, + 0x0033 => { + Name => 'InvertRect', + Format => 'Rect', + }, + 0x0034 => { + Name => 'FillRect', + Format => 'Rect', + }, + 0x0035 => { + Name => 'Reserved', + Format => 'Rect', + }, + 0x0038 => { + Name => 'FrameSameRect', + Format => 'null', + }, + 0x0039 => { + Name => 'PaintSameRect', + Format => 'null', + }, + 0x003a => { + Name => 'EraseSameRect', + Format => 'null', + }, + 0x003b => { + Name => 'InvertSameRect', + Format => 'null', + }, + 0x003c => { + Name => 'FillSameRect', + Format => 'null', + }, + 0x003d => { + Name => 'Reserved', + Format => 'null', + }, + 0x0040 => { + Name => 'FrameRRect', + Format => 'Rect', + }, + 0x0041 => { + Name => 'PaintRRect', + Format => 'Rect', + }, + 0x0042 => { + Name => 'EraseRRect', + Format => 'Rect', + }, + 0x0043 => { + Name => 'InvertRRect', + Format => 'Rect', + }, + 0x0044 => { + Name => 'FillRRect', + Format => 'Rect', + }, + 0x0045 => { + Name => 'Reserved', + Format => 'Rect', + }, + 0x0048 => { + Name => 'FrameSameRRect', + Format => 'null', + }, + 0x0049 => { + Name => 'PaintSameRRect', + Format => 'null', + }, + 0x004a => { + Name => 'EraseSameRRect', + Format => 'null', + }, + 0x004b => { + Name => 'InvertSameRRect', + Format => 'null', + }, + 0x004c => { + Name => 'FillSameRRect', + Format => 'null', + }, + 0x004d => { + Name => 'Reserved', + Format => 'null', + }, + 0x0050 => { + Name => 'FrameOval', + Format => 'Rect', + }, + 0x0051 => { + Name => 'PaintOval', + Format => 'Rect', + }, + 0x0052 => { + Name => 'EraseOval', + Format => 'Rect', + }, + 0x0053 => { + Name => 'InvertOval', + Format => 'Rect', + }, + 0x0054 => { + Name => 'FillOval', + Format => 'Rect', + }, + 0x0055 => { + Name => 'Reserved', + Format => 'Rect', + }, + 0x0058 => { + Name => 'FrameSameOval', + Format => 'null', + }, + 0x0059 => { + Name => 'PaintSameOval', + Format => 'null', + }, + 0x005a => { + Name => 'EraseSameOval', + Format => 'null', + }, + 0x005b => { + Name => 'InvertSameOval', + Format => 'null', + }, + 0x005c => { + Name => 'FillSameOval', + Format => 'null', + }, + 0x005d => { + Name => 'Reserved', + Format => 'null', + }, + 0x0060 => { + Name => 'FrameArc', + Format => 'Arc', + }, + 0x0061 => { + Name => 'PaintArc', + Format => 'Arc', + }, + 0x0062 => { + Name => 'EraseArc', + Format => 'Arc', + }, + 0x0063 => { + Name => 'InvertArc', + Format => 'Arc', + }, + 0x0064 => { + Name => 'FillArc', + Format => 'Arc', + }, + 0x0065 => { + Name => 'Reserved', + Format => 'Arc', + }, + 0x0068 => { + Name => 'FrameSameArc', + Format => 'Point', + }, + 0x0069 => { + Name => 'PaintSameArc', + Format => 'Point', + }, + 0x006a => { + Name => 'EraseSameArc', + Format => 'Point', + }, + 0x006b => { + Name => 'InvertSameArc', + Format => 'Point', + }, + 0x006c => { + Name => 'FillSameArc', + Format => 'Point', + }, + 0x006d => { + Name => 'Reserved', + Format => 'int32u', + }, + 0x0070 => { + Name => 'FramePoly', + Format => 'Polygon', + }, + 0x0071 => { + Name => 'PaintPoly', + Format => 'Polygon', + }, + 0x0072 => { + Name => 'ErasePoly', + Format => 'Polygon', + }, + 0x0073 => { + Name => 'InvertPoly', + Format => 'Polygon', + }, + 0x0074 => { + Name => 'FillPoly', + Format => 'Polygon', + }, + 0x0075 => { + Name => 'Reserved', + Format => 'Polygon', + }, + 0x0078 => { + Name => 'FrameSamePoly', + Format => 'null', + }, + 0x0079 => { + Name => 'PaintSamePoly', + Format => 'null', + }, + 0x007a => { + Name => 'EraseSamePoly', + Format => 'null', + }, + 0x007b => { + Name => 'InvertSamePoly', + Format => 'null', + }, + 0x007c => { + Name => 'FillSamePoly', + Format => 'null', + }, + 0x007d => { + Name => 'Reserved', + Format => 'null', + }, + 0x0080 => { + Name => 'FrameRgn', + Format => 'Rgn', + }, + 0x0081 => { + Name => 'PaintRgn', + Format => 'Rgn', + }, + 0x0082 => { + Name => 'EraseRgn', + Format => 'Rgn', + }, + 0x0083 => { + Name => 'InvertRgn', + Format => 'Rgn', + }, + 0x0084 => { + Name => 'FillRgn', + Format => 'Rgn', + }, + 0x0085 => { + Name => 'Reserved', + Format => 'Rgn', + }, + 0x0088 => { + Name => 'FrameSameRgn', + Format => 'null', + }, + 0x0089 => { + Name => 'PaintSameRgn', + Format => 'null', + }, + 0x008a => { + Name => 'EraseSameRgn', + Format => 'null', + }, + 0x008b => { + Name => 'InvertSameRgn', + Format => 'null', + }, + 0x008c => { + Name => 'FillSameRgn', + Format => 'null', + }, + 0x008d => { + Name => 'Reserved', + Format => 'null', + }, + 0x0090 => { + Name => 'BitsRect', + Description => 'CopyBits with Clipped Rectangle', + Format => 'BitsRect#', # (version-dependent format) + }, + 0x0091 => { + Name => 'BitsRgn', + Description => 'CopyBits with Clipped Region', + Format => 'BitsRgn#', # (version-dependent format) + }, + 0x0092 => { + Name => 'Reserved', + Format => 'Int16Data', + }, + 0x0098 => { + Name => 'PackBitsRect', + Description => 'Packed CopyBits with Clipped Rectangle', + Format => 'BitsRect#', # (version-dependent format) + }, + 0x0099 => { + Name => 'PackBitsRgn', + Description => 'Packed CopyBits with Clipped Region', + Format => 'BitsRgn#', # (version-dependent format) + }, + 0x009a => { + Name => 'DirectBitsRect', + Format => 'DirectBitsRect', + }, + 0x009b => { + Name => 'DirectBitsRgn', + Format => 'DirectBitsRgn', + }, + 0x009c => { + Name => 'Reserved', + Format => 'Int16Data', + }, + 0x009d => { + Name => 'Reserved', + Format => 'Int16Data', + }, + 0x009e => { + Name => 'Reserved', + Format => 'Int16Data', + }, + 0x009f => { + Name => 'Reserved', + Format => 'Int16Data', + }, + 0x00a0 => { + Name => 'ShortComment', + Format => 'int16u', + }, + 0x00a1 => [ + # this list for documentation only [currently not extracted] + { + # (not actually a full Photohop IRB record it appears, but it does start + # with '8BIM', and does contain resolution information at offset 0x0a) + Name => 'LongComment', # kind = 498 + Format => 'LongComment', + SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Main' }, + }, + { + Name => 'LongComment', # kind = 224 + Format => 'LongComment', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + Start => '$valuePtr + 4', + }, + }, + ], + 0x00a2 => { + Name => 'Reserved', + Format => 'Int16Data', + }, + 0x00b0 => { + Name => 'Reserved', + Format => 'null', + }, + 0x00d0 => { + Name => 'Reserved', + Format => 'Int32uData', + }, + 0x00ff => { + Name => 'OpEndPic', + Description => 'End of picture', + Format => 'null', # 2 for version 2!? + }, + 0x0100 => { + Name => 'Reserved', + Format => 'int16u', + }, + 0x0200 => { + Name => 'Reserved', + Format => 'int32u', + }, + 0x02ff => { + Name => 'Version', + Description => 'Version number of picture', + Format => 'int16u', + }, + 0x0300 => { + Name => 'Reserved', + Format => 'int16u', + }, + 0x0bff => { + Name => 'Reserved', + Format => 'int8u[22]', + }, + 0x0c00 => { + Name => 'HeaderOp', + Format => 'int16u[12]', + }, + 0x0c01 => { + Name => 'Reserved', + Format => 'int8u[24]', + }, + 0x7f00 => { + Name => 'Reserved', + Format => 'int8u[254]', + }, + 0x8000 => { + Name => 'Reserved', + Format => 'null', + }, + 0x8100 => { + Name => 'Reserved', + Format => 'Int32uData', + }, + 0x8200 => { + Name => 'CompressedQuickTime', + Format => 'CompressedQuickTime', + }, + 0x8201 => { + Name => 'UncompressedQuickTime', + Format => 'Int32uData', + }, + 0xffff => { + Name => 'Reserved', + Format => 'Int32uData', + }, +); + +# picture comment 'kind' codes +# http://developer.apple.com/technotes/qd/qd_10.html +my %commentKind = ( + 150 => 'TextBegin', + 151 => 'TextEnd', + 152 => 'StringBegin', + 153 => 'StringEnd', + 154 => 'TextCenter', + 155 => 'LineLayoutOff', + 156 => 'LineLayoutOn', + 157 => 'ClientLineLayout', + 160 => 'PolyBegin', + 161 => 'PolyEnd', + 163 => 'PolyIgnore', + 164 => 'PolySmooth', + 165 => 'PolyClose', + 180 => 'DashedLine', + 181 => 'DashedStop', + 182 => 'SetLineWidth', + 190 => 'PostScriptBegin', + 191 => 'PostScriptEnd', + 192 => 'PostScriptHandle', + 193 => 'PostScriptFile', + 194 => 'TextIsPostScript', + 195 => 'ResourcePS', + 196 => 'PSBeginNoSave', + 197 => 'SetGrayLevel', + 200 => 'RotateBegin', + 201 => 'RotateEnd', + 202 => 'RotateCenter', + 210 => 'FormsPrinting', + 211 => 'EndFormsPrinting', + 224 => '<ICC Profile>', + 498 => '<Photoshop Data>', + 1000 => 'BitMapThinningOff', + 1001 => 'BitMapThinningOn', +); + +#------------------------------------------------------------------------------ +# Get PixData data +# Inputs: 0) reference to PixMap, 1) RAF reference +# Returns: reference to PixData or undef on error +sub GetPixData($$) +{ + my ($pixMap, $raf) = @_; + my $packType = $pixMap->{packType}; + my $rowBytes = $pixMap->{rowBytes} & 0x3fff; # remove flags bits + my $height = $pixMap->{bounds}->{botRight}->{v} - + $pixMap->{bounds}->{topLeft}->{v}; + my ($data, $size, $buff, $i); + + if ($packType == 1 or $rowBytes < 8) { # unpacked data + $size = $rowBytes * $height; + return undef unless $raf->Read($data, $size) == $size; + } elsif ($packType == 2) { # pad byte dropped + $size = int($rowBytes * $height * 3 / 4 + 0.5); + return undef unless $raf->Read($data, $size) == $size; + } else { + $data = ''; + for ($i=0; $i<$height; ++$i) { + if ($rowBytes > 250) { + $raf->Read($buff,2) == 2 or return undef; + $size = unpack('n',$buff); + } else { + $raf->Read($buff,1) == 1 or return undef; + $size = unpack('C',$buff); + } + $data .= $buff; + $raf->Read($buff,$size) == $size or return undef; + $data .= $buff; + } + } + return \$data; +} + +#------------------------------------------------------------------------------ +# Read value from PICT file +# Inputs: 0) RAF reference, 1) tag, 2) format, 3) optional count +# Returns: value, reference to structure hash, or undef on error +sub ReadPictValue($$$;$) +{ + my ($raf, $tag, $format, $count) = @_; + return undef unless $format; + unless (defined $count) { + if ($format =~ /(.+)\[(.+)\]/s) { + $format = $1; + $count = $2; + } else { + $count = 1; # count undefined: assume 1 + } + } + my $cntStr = ($count == 1) ? '' : "[$count]"; + # no size if count is 0 + my $size = $count ? Image::ExifTool::FormatSize($format) : 0; + if (defined $size or $format eq 'null') { + my $val; + if ($size) { + my $buff; + $size *= $count; + $raf->Read($buff, $size) == $size or return undef; + $val = ReadValue(\$buff, 0, $format, $count, $size); + } else { + $val = ''; + } + if ($verbose) { + print $out "${indent}$tag ($format$cntStr)"; + if ($size) { + if (not defined $val) { + print $out " = <undef>\n"; + } elsif ($format eq 'binary') { + print $out " = <binary data>\n"; + if ($verbose > 2) { + my %parms = ( Out => $out ); + $parms{MaxLen} = 96 if $verbose < 4; + HexDump(\$val, undef, %parms); + } + } else { + print $out " = $val\n"; + } + } else { + print $out "\n"; + } + } + return \$val if $format eq 'binary' and defined $val; + return $val; + } + $verbose and print $out "${indent}$tag ($format$cntStr):\n"; + my $struct = $structs{$format} or return undef; + my ($c, @vals); + for ($c=0; $c<$count; ++$c) { + my (%val, $i); + for ($i=0; ; $i+=2) { + my $tag = $$struct[$i] or last; + my $fmt = $$struct[$i+1]; + my ($cnt, $val); + $indent .= ' '; + if (ref $fmt) { + $val = eval $$fmt; + $@ and warn $@; + if ($verbose and defined $val) { + printf $out "${indent}$tag (binary[%d]) = <binary data>\n",length($$val); + if ($verbose > 2) { + my %parms = ( Out => $out ); + $parms{MaxLen} = 96 if $verbose < 4; + HexDump($val, undef, %parms); + } + } + } elsif ($fmt =~ /(.+)\[(.+)\]/s) { + $fmt = $1; + $cnt = eval $2; + $@ and warn $@; + $val = ReadPictValue($raf, $tag, $fmt, $cnt); + } else { + $val = ReadPictValue($raf, $tag, $fmt); + } + $indent = substr($indent, 2); + return undef unless defined $val; + $val{$tag} = $val; + } + return \%val if $count == 1; + push @vals, \%val; + } + return \@vals; +} + +#------------------------------------------------------------------------------ +# Extract meta information from a PICT image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid PICT image +sub ProcessPICT($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + $verbose = $et->Options('Verbose'); + $out = $et->Options('TextOut'); + $indent = ''; + my ($buff, $tried, @hdr, $op, $hRes, $vRes); + + # recognize both PICT files and PICT resources (PICT files have a + # 512-byte header that we ignore, but PICT resources do not) + for (;;) { + $raf->Read($buff, 12) == 12 or return 0; + @hdr = unpack('x2n5', $buff); + $op = pop @hdr; + # check for PICT version 1 format + if ($op == 0x1101) { + $vers = 1; + undef $extended; + last; + } + # check for PICT version 2 format + if ($op == 0x0011) { + $raf->Read($buff, 28) == 28 or return 0; + if ($buff =~ /^\x02\xff\x0c\x00\xff\xff/) { + $vers = 2; + undef $extended; + last; + } + if ($buff =~ /^\x02\xff\x0c\x00\xff\xfe/) { + $vers = 2; + $extended = 1; + ($hRes, $vRes) = unpack('x8N2', $buff); + last; + } + } + return 0 if $tried; + $tried = 1; + $raf->Seek(512, 0) or return 0; + } + # make the bounding rect signed + foreach (@hdr) { + $_ >= 0x8000 and $_ -= 0x10000; + } + my $w = $hdr[3] - $hdr[1]; + my $h = $hdr[2] - $hdr[0]; + return 0 unless $w > 0 and $h > 0; + + SetByteOrder('MM'); + + if ($extended) { + # extended version 2 pictures contain resolution information + # and image bounds are in 72-dpi equivalent units + $hRes = GetFixed32s(\$buff, 8); + $vRes = GetFixed32s(\$buff, 12); + return 0 unless $hRes and $vRes; + $w = int($w * $hRes / 72 + 0.5); + $h = int($h * $vRes / 72 + 0.5); + } + $et->SetFileType(); + $et->FoundTag('ImageWidth', $w); + $et->FoundTag('ImageHeight', $h); + $et->FoundTag('XResolution', $hRes) if $hRes; + $et->FoundTag('YResolution', $vRes) if $vRes; + + # don't extract image opcodes unless verbose + return 1 unless $verbose or $et->Options('Unknown'); + + $verbose and printf $out "PICT version $vers%s\n", $extended ? ' extended' : ''; + + my $tagTablePtr = GetTagTable('Image::ExifTool::PICT::Main'); + + my $success; + for (;;) { + if ($vers == 1) { + $raf->Read($buff, 1) == 1 or last; + $op = ord($buff); + } else { + # must start version 2 opcode on an even byte + $raf->Read($buff, 1) if $raf->Tell() & 0x01; + $raf->Read($buff, 2) == 2 or last; + $op = unpack('n', $buff); + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $op); + unless ($tagInfo) { + my $i; + # search for reserved tag info + for ($i=0; $i<scalar(@reserved); $i+=2) { + next unless $op >= $reserved[$i]; + last if $op > $reserved[$i+1]; + $tagInfo = $et->GetTagInfo($tagTablePtr, $reserved[$i]); + last; + } + last unless $tagInfo; + } + if ($op == 0xff) { + $verbose and print $out "End of picture\n"; + $success = 1; + last; + } + my $format = $$tagInfo{Format}; + unless ($format) { + $et->Warn("Missing format for $$tagInfo{Name}"); + last; + } + # replace version number for version-dependent formats + $format =~ s/#$/$vers/; + my $wid = $vers * 2; + $verbose and printf $out "Tag 0x%.${wid}x, ", $op; + my $val = ReadPictValue($raf, $$tagInfo{Name}, $format); + unless (defined $val) { + $et->Warn("Error reading $$tagInfo{Name} information"); + last; + } + if (ref $val eq 'HASH') { + # extract JPEG image from CompressedQuickTime imageData + if ($$tagInfo{Name} eq 'CompressedQuickTime' and + ref $val->{imageDescr} eq 'HASH' and + $val->{imageDescr}->{compressor} and + $val->{imageDescr}->{compressor} eq 'Photo - JPEG' and + ref $val->{imageData} eq 'SCALAR' and + $et->ValidateImage($val->{imageData}, 'PreviewImage')) + { + $et->FoundTag('PreviewImage', $val->{imageData}); + } + } else { + # $et->FoundTag($tagInfo, $val); + } + } + $success or $et->Warn('End of picture not found'); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PICT - Read PICT meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read PICT +(Apple Picture) images. + +=head1 NOTES + +Extraction of PICT opcodes is experimental, and is only enabled with the +Verbose or the Unknown option. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://developer.apple.com/documentation/mac/QuickDraw/QuickDraw-2.html> + +=item L<http://developer.apple.com/documentation/QuickTime/INMAC/QT/iqImageCompMgr.a.htm> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PICT Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/PLIST.pm b/ExifTool/lib/Image/ExifTool/PLIST.pm new file mode 100644 index 0000000..419827b --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PLIST.pm @@ -0,0 +1,474 @@ +#------------------------------------------------------------------------------ +# File: PLIST.pm +# +# Description: Read Apple PLIST information +# +# Revisions: 2013-02-01 - P. Harvey Created +# +# References: 1) http://www.apple.com/DTDs/PropertyList-1.0.dtd +# 2) http://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c +# +# Notes: - Sony MODD files also use XML PLIST format, but with a few quirks +# +# - Decodes both the binary and XML-based PLIST formats +#------------------------------------------------------------------------------ + +package Image::ExifTool::PLIST; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::XMP; +use Image::ExifTool::GPS; + +$VERSION = '1.11'; + +sub ExtractObject($$;$); +sub Get24u($$); + +# access routines to read various-sized integer/real values (add 0x100 to size for reals) +my %readProc = ( + 1 => \&Get8u, + 2 => \&Get16u, + 3 => \&Get24u, + 4 => \&Get32u, + 8 => \&Get64u, + 0x104 => \&GetFloat, + 0x108 => \&GetDouble, +); + +# recognize different types of PLIST files based on certain tags +my %plistType = ( + adjustmentBaseVersion => 'AAE', +); + +# PLIST tags (generated on-the-fly for most tags) +%Image::ExifTool::PLIST::Main = ( + PROCESS_PROC => \&ProcessPLIST, + GROUPS => { 0 => 'PLIST', 1 => 'XML', 2 => 'Document' }, + VARS => { LONG_TAGS => 4 }, + NOTES => q{ + Apple Property List tags. ExifTool reads both XML and binary-format PLIST + files, and will extract any existing tags even if they aren't listed below. + These tags belong to the family 0 "PLIST" group, but family 1 group may be + either "XML" or "PLIST" depending on whether the format is XML or binary. + }, +# +# tags found in PLIST information of QuickTime iTunesInfo iTunMOVI atom (ref PH) +# + 'cast//name' => { Name => 'Cast', List => 1 }, + 'directors//name' => { Name => 'Directors', List => 1 }, + 'producers//name' => { Name => 'Producers', List => 1 }, + 'screenwriters//name' => { Name => 'Screenwriters', List => 1 }, + 'codirectors//name' => { Name => 'Codirectors', List => 1 }, # (NC) + 'studio//name' => { Name => 'Studio', List => 1 }, # (NC) +# +# tags found in MODD files (ref PH) +# + 'MetaDataList//DateTimeOriginal' => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + # Sony uses a "real" here -- number of days since Dec 31, 1899 + ValueConv => 'IsFloat($val) ? ConvertUnixTime(($val - 25569) * 24 * 3600) : $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + 'MetaDataList//Duration' => { + Name => 'Duration', + Groups => { 2 => 'Video' }, + PrintConv => 'ConvertDuration($val)', + }, + 'MetaDataList//Geolocation/Latitude' => { + Name => 'GPSLatitude', + Groups => { 2 => 'Location' }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 'MetaDataList//Geolocation/Longitude' => { + Name => 'GPSLongitude', + Groups => { 2 => 'Location' }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 'MetaDataList//Geolocation/MapDatum' => { + Name => 'GPSMapDatum', + Groups => { 2 => 'Location' }, + }, + XMLFileType => { + # recognize MODD files by their content + RawConv => q{ + if ($val eq 'ModdXML' and $$self{FILE_TYPE} eq 'XMP') { + $self->OverrideFileType('MODD'); + } + return $val; + }, + }, +); + +#------------------------------------------------------------------------------ +# We found a PLIST XML property name/value +# Inputs: 0) ExifTool object ref, 1) tag table ref +# 2) reference to array of XML property names (last is current property) +# 3) property value, 4) attribute hash ref (not used here) +# Returns: 1 if valid tag was found +sub FoundTag($$$$;$) +{ + my ($et, $tagTablePtr, $props, $val, $attrs) = @_; + return 0 unless @$props; + my $verbose = $et->Options('Verbose'); + my $keys = $$et{PListKeys} || ( $$et{PListKeys} = [] ); + + my $prop = $$props[-1]; + if ($verbose > 1) { + $et->VPrint(0, $$et{INDENT}, '[', join('/',@$props), ' = ', + $et->Printable($val), "]\n"); + } + # un-escape XML character entities + $val = Image::ExifTool::XMP::UnescapeXML($val); + + # handle the various PLIST properties + if ($prop eq 'data') { + if ($val =~ /^[0-9a-f]+$/ and not length($val) & 0x01) { + # MODD files use ASCII-hex encoded "data"... + my $buff = pack('H*', $val); + $val = \$buff; + } else { + # ...but the PLIST DTD specifies Base64 encoding + $val = Image::ExifTool::XMP::DecodeBase64($val); + } + } elsif ($prop eq 'date') { + $val = Image::ExifTool::XMP::ConvertXMPDate($val); + } elsif ($prop eq 'true' or $prop eq 'false') { + $val = ucfirst $prop; + } else { + # convert from UTF8 to ExifTool Charset + $val = $et->Decode($val, 'UTF8'); + if ($prop eq 'key') { + if (@$props <= 3) { # top-level key should be plist/dict/key + @$keys = ( $val ); + } else { + # save key names to be used in tag name + push @$keys, '' while @$keys < @$props - 3; + pop @$keys while @$keys > @$props - 2; + $$keys[@$props - 3] = $val; + } + return 0; + } + } + + return 0 unless @$keys; # can't store value if no associated key + + my $tag = join '/', @$keys; # generate tag ID from 'key' values + my $tagInfo = $$tagTablePtr{$tag}; + unless ($tagInfo) { + $et->VPrint(0, $$et{INDENT}, "[adding $tag]\n") if $verbose; + # generate tag name from ID + my $name = $tag; + $name =~ s{^MetaDataList//}{}; # shorten long MODD metadata tag names + $name =~ s{//name$}{}; # remove unnecessary MODD "name" property + $name =~ s/([^A-Za-z])([a-z])/$1\u$2/g; # capitalize words + $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters + $tagInfo = { Name => ucfirst($name), List => 1 }; + if ($prop eq 'date') { + $$tagInfo{Groups}{2} = 'Time'; + $$tagInfo{PrintConv} = '$self->ConvertDateTime($val)'; + } + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + # allow list-behaviour only for consecutive tags with the same ID + if ($$et{LastPListTag} and $$et{LastPListTag} ne $tagInfo) { + delete $$et{LIST_TAGS}{$$et{LastPListTag}}; + } + $$et{LastPListTag} = $tagInfo; + # override file type if applicable + $et->OverrideFileType($plistType{$tag}) if $plistType{$tag} and $$et{FILE_TYPE} eq 'XMP'; + # save the tag + $et->HandleTag($tagTablePtr, $tag, $val); + + return 1; +} + +#------------------------------------------------------------------------------ +# Get big-endian 24-bit integer +# Inputs: 0) data ref, 1) offset +# Returns: integer value +sub Get24u($$) +{ + my ($dataPt, $off) = @_; + return unpack 'N', "\0" . substr($$dataPt, $off, 3); +} + +#------------------------------------------------------------------------------ +# Extract object from binary PLIST file at the current file position (ref 2) +# Inputs: 0) ExifTool ref, 1) PLIST info ref, 2) parent tag ID (undef for top) +# Returns: the object, or undef on error +sub ExtractObject($$;$) +{ + my ($et, $plistInfo, $parent) = @_; + my $raf = $$plistInfo{RAF}; + my ($buff, $val); + + $raf->Read($buff, 1) == 1 or return undef; + my $type = ord($buff) >> 4; + my $size = ord($buff) & 0x0f; + if ($type == 0) { # null/bool/fill + $val = { 0x00=>'<null>', 0x08=>'True', 0x09=>'False', 0x0f=>'<fill>' }->{$size}; + } elsif ($type == 1 or $type == 2 or $type == 3) { # int, float or date + $size = 1 << $size; + my $proc = ($type == 1 ? $readProc{$size} : $readProc{$size + 0x100}) or return undef; + $val = &$proc(\$buff, 0) if $raf->Read($buff, $size) == $size; + if ($type == 3 and defined $val) { # date + # dates are referenced to Jan 1, 2001 (11323 days from Unix time zero) + $val = Image::ExifTool::ConvertUnixTime($val + 11323 * 24 * 3600, 1); + $$plistInfo{DateFormat} = 1; + } + } elsif ($type == 8) { # UID + ++$size; + $raf->Read($buff, $size) == $size or return undef; + my $proc = $readProc{$size}; + if ($proc) { + $val = &$proc(\$buff, 0); + } elsif ($size == 16) { + require Image::ExifTool::ASF; + $val = Image::ExifTool::ASF::GetGUID($buff); + } else { + $val = "0x" . unpack 'H*', $buff; + } + } else { + # $size is the size of the remaining types + if ($size == 0x0f) { + # size is stored in extra integer object + $size = ExtractObject($et, $plistInfo); + return undef unless defined $size and $size =~ /^\d+$/; + } + if ($type == 4) { # data + if ($size < 1000000 or $et->Options('Binary')) { + $raf->Read($buff, $size) == $size or return undef; + } else { + $buff = "Binary data $size bytes"; + } + $val = \$buff; # (return reference for binary data) + } elsif ($type == 5) { # ASCII string + $raf->Read($val, $size) == $size or return undef; + } elsif ($type == 6) { # UCS-2BE string + $size *= 2; + $raf->Read($buff, $size) == $size or return undef; + $val = $et->Decode($buff, 'UCS2'); + } elsif ($type == 10 or $type == 12 or $type == 13) { # array, set or dict + # the remaining types store a list of references + my $refSize = $$plistInfo{RefSize}; + my $refProc = $$plistInfo{RefProc}; + my $num = $type == 13 ? $size * 2 : $size; + my $len = $num * $refSize; + $raf->Read($buff, $len) == $len or return undef; + my $table = $$plistInfo{Table}; + my ($i, $ref, @refs, @array); + for ($i=0; $i<$num; ++$i) { + my $ref = &$refProc(\$buff, $i * $refSize); + return 0 if $ref >= @$table; + push @refs, $ref; + } + if ($type == 13) { # dict + # prevent infinite recursion + if (defined $parent and length $parent > 1000) { + $et->WarnOnce('Possible deep recursion while parsing PLIST'); + return undef; + } + my $tagTablePtr = $$plistInfo{TagTablePtr}; + my $verbose = $et->Options('Verbose'); + $val = { }; # initialize return dictionary (will stay empty if tags are saved) + for ($i=0; $i<$size; ++$i) { + # get the entry key + $raf->Seek($$table[$refs[$i]], 0) or return undef; + my $key = ExtractObject($et, $plistInfo); + next unless defined $key and length $key; # silently ignore bad dict entries + # get the entry value + $raf->Seek($$table[$refs[$i+$size]], 0) or return undef; + # generate an ID for this tag + my $tag = defined $parent ? "$parent/$key" : $key; + undef $$plistInfo{DateFormat}; + my $obj = ExtractObject($et, $plistInfo, $tag); + next if not defined $obj; + unless ($tagTablePtr) { + # make sure this is a valid structure field name + if (not defined $key or $key !~ /^[-_a-zA-Z0-9]+$/) { + $key = "Tag$i"; # (generate fake tag name if it had illegal characters) + } elsif ($key !~ /^[_a-zA-Z]/) { + $key = "_$key"; # (must begin with alpha or underline) + } + $$val{$key} = $obj if defined $obj; + next; + } + next if ref($obj) eq 'HASH'; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + unless ($tagInfo) { + $et->VPrint(0, $$et{INDENT}, "[adding $tag]\n") if $verbose; + my $name = $tag; + $name =~ s/([^A-Za-z])([a-z])/$1\u$2/g; # capitalize words + $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters + $name = "Tag$name" if length($name) < 2 or $name =~ /^[-0-9]/; + $tagInfo = { Name => ucfirst($name), List => 1 }; + if ($$plistInfo{DateFormat}) { + $$tagInfo{Groups}{2} = 'Time'; + $$tagInfo{PrintConv} = '$self->ConvertDateTime($val)'; + } + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + # allow list-behaviour only for consecutive tags with the same ID + if ($$et{LastPListTag} and $$et{LastPListTag} ne $tagInfo) { + delete $$et{LIST_TAGS}{$$et{LastPListTag}}; + } + $$et{LastPListTag} = $tagInfo; + $et->HandleTag($tagTablePtr, $tag, $obj); + } + } else { + # extract the referenced objects + foreach $ref (@refs) { + $raf->Seek($$table[$ref], 0) or return undef; # seek to this object + $val = ExtractObject($et, $plistInfo, $parent); + next unless defined $val and ref $val ne 'HASH'; + push @array, $val; + } + $val = \@array; + } + } + } + return $val; +} + +#------------------------------------------------------------------------------ +# Process binary PLIST data (ref 2) +# Inputs: 0) ExifTool object ref, 1) DirInfo ref, 2) tag table ref +# Returns: 1 on success (and returns plist value as $$dirInfo{Value}) +sub ProcessBinaryPLIST($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my ($i, $buff, @table); + my $dataPt = $$dirInfo{DataPt}; + + $et->VerboseDir('Binary PLIST'); + SetByteOrder('MM'); + + if ($dataPt) { + my $start = $$dirInfo{DirStart}; + if ($start or ($$dirInfo{DirLen} and $$dirInfo{DirLen} != length $$dataPt)) { + my $buf2 = substr($$dataPt, $start || 0, $$dirInfo{DirLen}); + $$dirInfo{RAF} = new File::RandomAccess(\$buf2); + } else { + $$dirInfo{RAF} = new File::RandomAccess($dataPt); + } + my $strt = $$dirInfo{DirStart} || 0; + } + # read and parse the trailer + my $raf = $$dirInfo{RAF} or return 0; + $raf->Seek(-32,2) and $raf->Read($buff,32)==32 or return 0; + my $intSize = Get8u(\$buff, 6); + my $refSize = Get8u(\$buff, 7); + my $numObj = Get64u(\$buff, 8); + my $topObj = Get64u(\$buff, 16); + my $tableOff = Get64u(\$buff, 24); + + return 0 if $topObj >= $numObj; + my $intProc = $readProc{$intSize} or return 0; + my $refProc = $readProc{$refSize} or return 0; + + # read and parse the offset table + my $tableSize = $intSize * $numObj; + $raf->Seek($tableOff, 0) and $raf->Read($buff, $tableSize) == $tableSize or return 0; + for ($i=0; $i<$numObj; ++$i) { + push @table, &$intProc(\$buff, $i * $intSize); + } + my %plistInfo = ( + RAF => $raf, + RefSize => $refSize, + RefProc => $refProc, + Table => \@table, + TagTablePtr => $tagTablePtr, + ); + # position file pointer at the top object, and extract it + $raf->Seek($table[$topObj], 0) or return 0; + $$dirInfo{Value} = ExtractObject($et, \%plistInfo); + return defined $$dirInfo{Value} ? 1 : 0; +} + +#------------------------------------------------------------------------------ +# Extract information from a PLIST file +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, 0 if this wasn't valid PLIST +sub ProcessPLIST($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + + # process XML PLIST data using the XMP module + $$dirInfo{XMPParseOpts}{FoundProc} = \&FoundTag; + my $result = Image::ExifTool::XMP::ProcessXMP($et, $dirInfo, $tagTablePtr); + delete $$dirInfo{XMPParseOpts}; + + unless ($result) { + my $buff; + my $raf = $$dirInfo{RAF} or return 0; + $raf->Seek(0,0) and $raf->Read($buff, 64) or return 0; + if ($buff =~ /^bplist0/) { + # binary PLIST file + my $tagTablePtr = GetTagTable('Image::ExifTool::PLIST::Main'); + $et->SetFileType('PLIST', 'application/x-plist'); + $$et{SET_GROUP1} = 'PLIST'; + unless (ProcessBinaryPLIST($et, $dirInfo, $tagTablePtr)) { + $et->Error('Error reading binary PLIST file'); + } + delete $$et{SET_GROUP1}; + $result = 1; + } elsif ($$et{FILE_EXT} and $$et{FILE_EXT} eq 'PLIST' and + $buff =~ /^\xfe\xff\x00/) + { + # (have seen very old PLIST files encoded as UCS-2BE with leading BOM) + $et->Error('Old PLIST format currently not supported'); + $result = 1; + } + } + return $result; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PLIST - Read Apple PLIST information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains the routines used by Image::ExifTool to extract +information from Apple Property List files. + +=head1 NOTES + +This module decodes both the binary and XML-based PLIST format. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.apple.com/DTDs/PropertyList-1.0.dtd> + +=item L<http://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PLIST Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/PLUS.pm b/ExifTool/lib/Image/ExifTool/PLUS.pm new file mode 100644 index 0000000..1df2e46 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PLUS.pm @@ -0,0 +1,2622 @@ +#------------------------------------------------------------------------------ +# File: PLUS.pm +# +# Description: PLUS (Picture Licensing Universal System) tags +# +# Revisions: 2016/05/18 - P. Harvey Created +# +# References: 1) http://www.useplus.com/useplus/standards.asp +#------------------------------------------------------------------------------ + +package Image::ExifTool::PLUS; + +use strict; +use vars qw($VERSION); +use Image::ExifTool::XMP; + +$VERSION = '1.02'; + +sub ValidateMediaSummary($); + +#------------------------------------------------------------------------------ +# PLUS (Picture Licensing Universal System) + +# PLUS vocabulary conversions +my %plusVocab = ( + ValueConv => '$val =~ s{http://ns.useplus.org/ldf/vocab/}{}; $val', + ValueConvInv => '"http://ns.useplus.org/ldf/vocab/$val"', +); + +# PLUS License Data Format 1.2.1 structures +# (this seems crazy to me -- why did they define different ID/Name structures +# for each field rather than just re-using the same structure?) +my %plusLicensee = ( + STRUCT_NAME => 'Licensee', + NAMESPACE => 'plus', + # all "rdf:type" properties removed in version 1.2.1 + # (idiots. Why did they put them there in the first place? -- it required + # a special patch for ExifTool to support these, and now they are gone!) + # TYPE => 'plus:LicenseeDetail', (removed in 1.2.1) + LicenseeID => { }, + LicenseeName=> { }, +); +my %plusEndUser = ( + STRUCT_NAME => 'EndUser', + NAMESPACE => 'plus', + # TYPE => 'plus:EndUserDetail', (removed in 1.2.1) + EndUserID => { }, + EndUserName => { }, +); +my %plusLicensor = ( + STRUCT_NAME => 'Licensor', + NAMESPACE => 'plus', + # TYPE => 'plus:LicensorDetail', (removed in 1.2.1) + LicensorID => { }, + LicensorName => { }, + LicensorStreetAddress => { }, + LicensorExtendedAddress => { }, + LicensorCity => { }, + LicensorRegion => { }, + LicensorPostalCode => { }, + LicensorCountry => { }, + LicensorTelephoneType1 => { + %plusVocab, + PrintConv => { + 'work' => 'Work', + 'cell' => 'Cell', + 'fax' => 'FAX', + 'home' => 'Home', + 'pager' => 'Pager', + }, + }, + LicensorTelephone1 => { }, + LicensorTelephoneType2 => { + %plusVocab, + PrintConv => { + 'work' => 'Work', + 'cell' => 'Cell', + 'fax' => 'FAX', + 'home' => 'Home', + 'pager' => 'Pager', + }, + }, + LicensorTelephone2 => { }, + LicensorEmail => { }, + LicensorURL => { }, +); +my %plusCopyrightOwner = ( + STRUCT_NAME => 'CopyrightOwner', + NAMESPACE => 'plus', + # TYPE => 'plus:CopyrightOwnerDetail', (removed in 1.2.1) + CopyrightOwnerID => { }, + CopyrightOwnerName => { }, +); +my %plusImageCreator = ( + STRUCT_NAME => 'ImageCreator', + NAMESPACE => 'plus', + # TYPE => 'plus:ImageCreatorDetail', (removed in 1.2.1) + ImageCreatorID => { }, + ImageCreatorName => { }, +); +my %plusImageSupplier = ( + STRUCT_NAME => 'ImageSupplier', + NAMESPACE => 'plus', + # TYPE => 'plus:ImageSupplierDetail', (removed in 1.2.1) + ImageSupplierID => { }, + ImageSupplierName => { }, +); + +# Media Matrix 1.1.1 decoding +my %mediaMatrix = ( + Notes => q{ + The lookup below is used to add human-readable descriptions to Media Matrix + ID's in PLUS Media Summary Codes. + }, + OTHER => sub { + my ($val, $inv, $conv) = @_; + $val = uc $val; # convert to upper case + if ($inv) { + # prepare MediaSummaryCode for writing + $val =~ s/\(.*?\)//sg; # remove descriptions + $val =~ s/^\s*?PLUS\s+(V[0-9]+)\s+(U[0-9]+)\s*;/|PLUS|$1|$2|/s; # reformat header + $val =~ tr/;/|/; # use "|" as separator instead of ";" + $val =~ tr/0-9A-Z|//dc; # remove extraneous characters + $val .= '|' unless $val =~ /\|$/; # add terminating "|" + ValidateMediaSummary($val) or return undef; + } elsif ($val =~ /^\|PLUS\|(.*?)\|(.*?)\|(.*)/s) { + # add human-readable descriptions + my ($ver,$num,$code) = ($1,$2,$3); + $ver .= " (LDF Version $1.$2)" if $ver =~ /V0*(\d+)(\d{2})$/; + $num .= " ($1 Media Usages:)" if $num =~ /U0*(\d+)/; + $code =~ tr/0-9A-Z|//dc; # remove extraneous characters + $val = "PLUS $ver $num"; + while ($code =~ /(\d[A-Z]{3})/g) { + my $mmid = $1; + if (defined $$conv{$mmid}) { + $val .= " $mmid ($$conv{$mmid})"; + } elsif ($mmid =~ /^1I([A-Z])([A-Z])/) { # decode Usage Item count + my $n = (ord($1)-65) * 26 + ord($2)-65 + 1; + # (add a separator before each new Media Usage Code) + $val .= "; $mmid ($n Usage Items:)"; + } elsif ($mmid =~ /^1UN([A-Z])/) { # decode Usage Number + $val .= " (Usage Number $1)"; + } else { + $val .= " $mmid"; + } + } + } + return $val; + }, + # 1 - Usage + '1IAA' => '1 Usage Item:', + '1IAB' => '2 Usage Items:', + '1IAC' => '3 Usage Items:', + '1IAD' => '4 Usage Items:', + '1IAE' => '5 Usage Items:', + # ... + '1UNA' => 'Usage Number A', + '1UNB' => 'Usage Number B', + '1UNC' => 'Usage Number C', + '1UND' => 'Usage Number D', + '1UNE' => 'Usage Number E', + # ... + # 2 - Media + '2BOB' => 'Advertising|Periodicals|Magazine, Consumer|Printed', + '2ACE' => 'Advertising|Periodicals|Magazine, Consumer|Internet Website', + '2GEN' => 'Advertising|Periodicals|Magazine, Consumer|Internet Downloadable File', + '2ADS' => 'Advertising|Periodicals|Magazine, Consumer|Internet Email', + '2GIB' => 'Advertising|Periodicals|Magazine, Consumer|All Internet Distribution Formats', + '2GHI' => 'Advertising|Periodicals|Magazine, Consumer|Recordable Media', + '2GEY' => 'Advertising|Periodicals|Magazine, Consumer|All Electronic Distribution Formats', + '2HAT' => 'Advertising|Periodicals|Magazine, Trade|Printed', + '2HAG' => 'Advertising|Periodicals|Magazine, Trade|Internet Website', + '2HAE' => 'Advertising|Periodicals|Magazine, Trade|Internet Downloadable File', + '2AFT' => 'Advertising|Periodicals|Magazine, Trade|Internet Email', + '2HAJ' => 'Advertising|Periodicals|Magazine, Trade|All Internet Distribution Formats', + '2AGE' => 'Advertising|Periodicals|Magazine, Trade|Recordable Media', + '2HAH' => 'Advertising|Periodicals|Magazine, Trade|All Electronic Distribution Formats', + '2JAR' => 'Advertising|Periodicals|Magazine, Corporate|Printed', + '2GIN' => 'Advertising|Periodicals|Magazine, Corporate|Internet Website', + '2GIE' => 'Advertising|Periodicals|Magazine, Corporate|Internet Downloadable File', + '2AGO' => 'Advertising|Periodicals|Magazine, Corporate|Internet Email', + '2GNU' => 'Advertising|Periodicals|Magazine, Corporate|All Internet Distribution Formats', + '2GIP' => 'Advertising|Periodicals|Magazine, Corporate|Recordable Media', + '2GIT' => 'Advertising|Periodicals|Magazine, Corporate|All Electronic Distribution Formats', + '2DEB' => 'Advertising|Periodicals|Magazine, Education|Printed', + '2GUM' => 'Advertising|Periodicals|Magazine, Education|Internet Website', + '2GUL' => 'Advertising|Periodicals|Magazine, Education|Internet Downloadable File', + '2AHA' => 'Advertising|Periodicals|Magazine, Education|Internet Email', + '2GYP' => 'Advertising|Periodicals|Magazine, Education|All Internet Distribution Formats', + '2GUV' => 'Advertising|Periodicals|Magazine, Education|Recordable Media', + '2GUY' => 'Advertising|Periodicals|Magazine, Education|All Electronic Distribution Formats', + '2CUS' => 'Advertising|Periodicals|Magazine, Custom Published|Printed', + '2GOR' => 'Advertising|Periodicals|Magazine, Custom Published|Internet Website', + '2GOS' => 'Advertising|Periodicals|Magazine, Custom Published|Internet Downloadable File', + '2AIL' => 'Advertising|Periodicals|Magazine, Custom Published|Internet Email', + '2GOX' => 'Advertising|Periodicals|Magazine, Custom Published|All Internet Distribution Formats', + '2ALP' => 'Advertising|Periodicals|Magazine, Custom Published|Recordable Media', + '2AMI' => 'Advertising|Periodicals|Magazine, Custom Published|All Electronic Distribution Formats', + '2BEN' => 'Advertising|Periodicals|Magazine, Advertorial|Printed', + '2BAP' => 'Advertising|Periodicals|Magazine, Advertorial|Internet Website', + '2BAM' => 'Advertising|Periodicals|Magazine, Advertorial|Internet Downloadable File', + '2BOP' => 'Advertising|Periodicals|Magazine, Advertorial|Internet Email', + '2GOB' => 'Advertising|Periodicals|Magazine, Advertorial|All Internet Distribution Formats', + '2DUH' => 'Advertising|Periodicals|Magazine, Advertorial|All Electronic Distribution Formats', + '2DUP' => 'Advertising|Periodicals|Magazine Reprints, All Types|Printed', + '2HAS' => 'Advertising|Periodicals|Magazine Reprints, All Types|Internet Website', + '2HAP' => 'Advertising|Periodicals|Magazine Reprints, All Types|Internet Downloadable File', + '2AMP' => 'Advertising|Periodicals|Magazine Reprints, All Types|Internet Email', + '2HEH' => 'Advertising|Periodicals|Magazine Reprints, All Types|All Internet Distribution Formats', + '2HAW' => 'Advertising|Periodicals|Magazine Reprints, All Types|Recordable Media', + '2HAY' => 'Advertising|Periodicals|Magazine Reprints, All Types|All Electronic Distribution Formats', + '2MOO' => 'Advertising|Periodicals|Magazine, All Types|Printed', + '2ANA' => 'Advertising|Periodicals|Magazine, All Types|Internet Website', + '2GOO' => 'Advertising|Periodicals|Magazine, All Types|Recordable Media', + '2ANY' => 'Advertising|Periodicals|Magazine, All Types|Internet Downloadable File', + '2APE' => 'Advertising|Periodicals|Magazine, All Types|Internet Email', + '2APT' => 'Advertising|Periodicals|Magazine, All Types|All Internet Distribution Formats', + '2ARK' => 'Advertising|Periodicals|Magazine, All Types|All Electronic Distribution Formats', + '2KEG' => 'Advertising|Periodicals|Newspaper, Weekly Supplement|Printed', + '2HOB' => 'Advertising|Periodicals|Newspaper, Weekly Supplement|Internet Website', + '2HOD' => 'Advertising|Periodicals|Newspaper, Weekly Supplement|Internet Downloadable File', + '2ARM' => 'Advertising|Periodicals|Newspaper, Weekly Supplement|Internet Email', + '2HMM' => 'Advertising|Periodicals|Newspaper, Weekly Supplement|All Internet Distribution Formats', + '2HOE' => 'Advertising|Periodicals|Newspaper, Weekly Supplement|Recordable Media', + '2HIS' => 'Advertising|Periodicals|Newspaper, Weekly Supplement|All Electronic Distribution Formats', + '2KEN' => 'Advertising|Periodicals|Newspaper, Tabloid|Printed', + '2HOY' => 'Advertising|Periodicals|Newspaper, Tabloid|Internet Website', + '2HUH' => 'Advertising|Periodicals|Newspaper, Tabloid|All Internet Distribution Formats', + '2ASH' => 'Advertising|Periodicals|Newspaper, Tabloid|Internet Downloadable File', + '2BAY' => 'Advertising|Periodicals|Newspaper, Tabloid|Internet Email', + '2HUE' => 'Advertising|Periodicals|Newspaper, Tabloid|Recordable Media', + '2HOW' => 'Advertising|Periodicals|Newspaper, Tabloid|All Electronic Distribution Formats', + '2NEW' => 'Advertising|Periodicals|Newspaper, All Types|Printed', + '2HID' => 'Advertising|Periodicals|Newspaper, All Types|Internet Website', + '2HIE' => 'Advertising|Periodicals|Newspaper, All Types|Internet Downloadable File', + '2BED' => 'Advertising|Periodicals|Newspaper, All Types|Internet Email', + '2HIC' => 'Advertising|Periodicals|Newspaper, All Types|All Internet Distribution Formats', + '2HIM' => 'Advertising|Periodicals|Newspaper, All Types|Recordable Media', + '2HEY' => 'Advertising|Periodicals|Newspaper, All Types|All Electronic Distribution Formats', + '2JAY' => 'Advertising|Periodicals|Newsletter, All Types|Printed', + '2HES' => 'Advertising|Periodicals|Newsletter, All Types|Internet Website', + '2HEP' => 'Advertising|Periodicals|Newsletter, All Types|Internet Downloadable File', + '2HEW' => 'Advertising|Periodicals|Newsletter, All Types|All Internet Distribution Formats', + '2BID' => 'Advertising|Periodicals|Newsletter, All Types|Internet Email', + '2HET' => 'Advertising|Periodicals|Newsletter, All Types|Recordable Media', + '2HEN' => 'Advertising|Periodicals|Newsletter, All Types|All Electronic Distribution Formats', + '2BAH' => 'Advertising|Periodicals|Quarterly Report|Printed', + '2HUT' => 'Advertising|Periodicals|Quarterly Report|Internet Website', + '2HUP' => 'Advertising|Periodicals|Quarterly Report|Internet Downloadable File', + '2BAL' => 'Advertising|Periodicals|Quarterly Report|Internet Email', + '2JEE' => 'Advertising|Periodicals|Quarterly Report|All Internet Distribution Formats', + '2HYP' => 'Advertising|Periodicals|Quarterly Report|Recordable Media', + '2JAG' => 'Advertising|Periodicals|Quarterly Report|All Electronic Distribution Formats', + '2ANN' => 'Advertising|Periodicals|Annual Report|Printed', + '2FON' => 'Advertising|Periodicals|Annual Report|Internet Website', + '2FOH' => 'Advertising|Periodicals|Annual Report|Internet Downloadable File', + '2FOP' => 'Advertising|Periodicals|Annual Report|Internet Email', + '2FOY' => 'Advertising|Periodicals|Annual Report|All Internet Distribution Formats', + '2BAS' => 'Advertising|Periodicals|Annual Report|Recordable Media', + '2FOU' => 'Advertising|Periodicals|Annual Report|All Electronic Distribution Formats', + '2KIP' => 'Advertising|Periodicals|Program Advertising|Printed', + '2DEE' => 'Advertising|Periodicals|Wrapper|Printed', + '2FUD' => 'Advertising|Periodicals|Cover Wrap|Printed', + '2FUB' => 'Advertising|Periodicals|Belly Band|Printed', + '2BAT' => 'Advertising|Periodicals|Free Standing Insert, All Insert Types|Printed', + '2HIP' => 'Advertising|Periodicals|Free Standing Insert, Advertorial Insert|Printed', + '2BAG' => 'Advertising|Periodicals|All Periodical Types|Printed', + '2FIZ' => 'Advertising|Periodicals|All Periodical Types|Internet Website', + '2BOD' => 'Advertising|Periodicals|All Periodical Types|Internet Downloadable File', + '2BOW' => 'Advertising|Periodicals|All Periodical Types|Internet Email', + '2FOB' => 'Advertising|Periodicals|All Periodical Types|All Internet Distribution Formats', + '2FIR' => 'Advertising|Periodicals|All Periodical Types|Recordable Media', + '2FLU' => 'Advertising|Periodicals|All Periodical Types|All Electronic Distribution Formats', + '2EDH' => 'Advertising|Marketing Materials|Artist\'s Reference, All Types|Printed', + '2ECU' => 'Advertising|Marketing Materials|Artist\'s Reference, All Types|All Internet Distribution Formats', + '2DOT' => 'Advertising|Marketing Materials|Artist\'s Reference, All Types|All Electronic Distribution Formats', + '2MMA' => 'Advertising|Marketing Materials|Bill Insert|Printed', + '2MMB' => 'Advertising|Marketing Materials|Blow In Card|Printed', + '2MMC' => 'Advertising|Marketing Materials|Bound-in Insert|Printed', + '2MMD' => 'Advertising|Marketing Materials|Broadside|Printed', + '2EGG' => 'Advertising|Marketing Materials|Brochure|Printed', + '2EFF' => 'Advertising|Marketing Materials|Brochure|Internet Downloadable File', + '2EFS' => 'Advertising|Marketing Materials|Brochure|Internet Email', + '2EFT' => 'Advertising|Marketing Materials|Brochure|Recordable Media', + '2MME' => 'Advertising|Marketing Materials|Buckslip|Printed', + '2MMF' => 'Advertising|Marketing Materials|Business Card|Printed', + '2MMG' => 'Advertising|Marketing Materials|Business Envelope|Printed', + '2BIZ' => 'Advertising|Marketing Materials|Card, Business Greeting Card|Printed', + '2MMI' => 'Advertising|Marketing Materials|Business Invitation|Printed', + '2MMJ' => 'Advertising|Marketing Materials|Business Reply Card|Printed', + '2MMK' => 'Advertising|Marketing Materials|Business Reply Envelope|Printed', + '2MML' => 'Advertising|Marketing Materials|Business Stationery|Printed', + '2FIG' => 'Advertising|Marketing Materials|Catalog|Printed', + '2ELL' => 'Advertising|Marketing Materials|Catalog|Internet Downloadable File', + '2ELS' => 'Advertising|Marketing Materials|Catalog|Internet Email', + '2ELF' => 'Advertising|Marketing Materials|Catalog|Recordable Media', + '2EDU' => 'Advertising|Marketing Materials|CD ROM|Recordable Media', + '2MMM' => 'Advertising|Marketing Materials|Compliment Slip|Printed', + '2MMN' => 'Advertising|Marketing Materials|Coupon|Printed', + '2EGO' => 'Advertising|Marketing Materials|Coupon|Internet Downloadable File', + '2ENG' => 'Advertising|Marketing Materials|Coupon|Internet Email', + '2ENS' => 'Advertising|Marketing Materials|Coupon|Recordable Media', + '2MMP' => 'Advertising|Marketing Materials|Coupon Packs|Printed', + '2DVA' => 'Advertising|Marketing Materials|DVD|Recordable Media', + '2MMQ' => 'Advertising|Marketing Materials|Flyaway Card|Printed', + '2FLY' => 'Advertising|Marketing Materials|Flyer|Printed', + '2MMR' => 'Advertising|Marketing Materials|Leaflet|Printed', + '2MMS' => 'Advertising|Marketing Materials|Magalog|Printed', + '2END' => 'Advertising|Marketing Materials|Magalog|Internet Downloadable File', + '2EON' => 'Advertising|Marketing Materials|Magalog|Internet Email', + '2FAX' => 'Advertising|Marketing Materials|Magalog|Recordable Media', + '2MMT' => 'Advertising|Marketing Materials|One Sheet|Printed', + '2MMU' => 'Advertising|Marketing Materials|Onsert|Printed', + '2MMV' => 'Advertising|Marketing Materials|Polybag|Printed', + '2BUN' => 'Advertising|Marketing Materials|Promo Card|Printed', + '2MMW' => 'Advertising|Marketing Materials|Promotional Calendar, One Page|Printed', + '2FAS' => 'Advertising|Marketing Materials|Promotional Calendar, One Page|Internet Downloadable File', + '2FAY' => 'Advertising|Marketing Materials|Promotional Calendar, One Page|Internet Email', + '2GUT' => 'Advertising|Marketing Materials|Promotional Calendar, One Page|Recordable Media', + '2EWE' => 'Advertising|Marketing Materials|Promotional Calendar, One Page|All Electronic Distribution Formats', + '2YAM' => 'Advertising|Marketing Materials|Promotional Calendar, Multi-Page|Printed', + '2ESS' => 'Advertising|Marketing Materials|Promotional Calendar, Multi-Page|Internet Downloadable File', + '2ETA' => 'Advertising|Marketing Materials|Promotional Calendar, Multi-Page|Internet Email', + '2HAD' => 'Advertising|Marketing Materials|Promotional Calendar, Multi-Page|Recordable Media', + '2FET' => 'Advertising|Marketing Materials|Promotional E-card|Internet Email', + '2FEU' => 'Advertising|Marketing Materials|Promotional E-card|All Internet Distribution Formats', + '2FEH' => 'Advertising|Marketing Materials|Promotional E-card|Recordable Media', + '2FEN' => 'Advertising|Marketing Materials|Promotional E-card|Internet Downloadable File', + '2MMX' => 'Advertising|Marketing Materials|Promotional Envelope|Printed', + '2NUT' => 'Advertising|Marketing Materials|Promotional Postcard|Printed', + '2KAF' => 'Advertising|Marketing Materials|Public Relations, Press Kit|Printed', + '2JUT' => 'Advertising|Marketing Materials|Public Relations, Press Kit|Internet Website', + '2JUN' => 'Advertising|Marketing Materials|Public Relations, Press Kit|Internet Downloadable File', + '2JUS' => 'Advertising|Marketing Materials|Public Relations, Press Kit|Internet Email', + '2JOW' => 'Advertising|Marketing Materials|Public Relations, Press Kit|All Internet Distribution Formats', + '2JOB' => 'Advertising|Marketing Materials|Public Relations, Press Kit|Recordable Media', + '2JOE' => 'Advertising|Marketing Materials|Public Relations, Press Kit|All Electronic Distribution Formats', + '2KAB' => 'Advertising|Marketing Materials|Public Relations, Press Kit|Television Broadcast', + '2KEX' => 'Advertising|Marketing Materials|Public Relations, Press Release|Printed', + '2KEA' => 'Advertising|Marketing Materials|Public Relations, Press Release|Internet Website', + '2KAT' => 'Advertising|Marketing Materials|Public Relations, Press Release|Internet Downloadable File', + '2KAY' => 'Advertising|Marketing Materials|Public Relations, Press Release|Internet Email', + '2KAS' => 'Advertising|Marketing Materials|Public Relations, Press Release|Recordable Media', + '2KID' => 'Advertising|Marketing Materials|Public Relations, Press Release|All Electronic Distribution Formats', + '2KEF' => 'Advertising|Marketing Materials|Public Relations, Press Release|Television Broadcast', + '2JIB' => 'Advertising|Marketing Materials|Public Relations, All Types|Printed', + '2MMY' => 'Advertising|Marketing Materials|Sales Kit|Printed', + '2MMZ' => 'Advertising|Marketing Materials|Self Mailer|Printed', + '2JAM' => 'Advertising|Marketing Materials|All Marketing Material Types|Printed', + '2HAM' => 'Advertising|Marketing Materials|All Marketing Material Types|Internet Downloadable File', + '2DYE' => 'Advertising|Marketing Materials|All Marketing Material Types|Internet Email', + '2DUO' => 'Advertising|Marketing Materials|All Marketing Material Types|Recordable Media', + '2BEG' => 'Advertising|Book|Retail Book, Directory|Printed', + '2EAC' => 'Advertising|Book|Retail Book, Directory|E-Book in Internet Website', + '2EAD' => 'Advertising|Book|Retail Book, Directory|E-Book in Internet Downloadable File', + '2EAE' => 'Advertising|Book|Retail Book, Directory|All E-Book Internet Distribution Formats', + '2EAF' => 'Advertising|Book|Retail Book, Directory|E-Book on Recordable Media', + '2EAG' => 'Advertising|Book|Retail Book, Directory|All E-Book Distribution Formats', + '2ELM' => 'Advertising|Book|Textbook, All Types|Printed', + '2EAH' => 'Advertising|Book|Textbook, All Types|E-Book in Internet Website', + '2EAJ' => 'Advertising|Book|Textbook, All Types|E-Book in Internet Downloadable File', + '2EAK' => 'Advertising|Book|Textbook, All Types|All E-Book Internet Distribution Formats', + '2EAL' => 'Advertising|Book|Textbook, All Types|E-Book on Recordable Media', + '2EAM' => 'Advertising|Book|Textbook, All Types|All E-Book Distribution Formats', + '2HOG' => 'Advertising|Book|All Book Types|Printed', + '2EAN' => 'Advertising|Book|All Book Types|All E-Book Internet Distribution Formats', + '2EAP' => 'Advertising|Book|All Book Types|All E-Book Distribution Formats', + '2HEM' => 'Advertising|Display|Billboard, Bulletin|Printed', + '2BUR' => 'Advertising|Display|Billboard, Bulletin|Electronic Display', + '2DAG' => 'Advertising|Display|Billboard, Spectacular|Printed', + '2DAD' => 'Advertising|Display|Billboard, Spectacular|Electronic Display', + '2DAK' => 'Advertising|Display|Billboard, Wallscape|Printed', + '2DAH' => 'Advertising|Display|Billboard, Wallscape|Electronic Display', + '2DAB' => 'Advertising|Display|Billboard, Rotating Billboard|Printed', + '2BLD' => 'Advertising|Display|Billboard, Building Wrap|Printed', + '2BYS' => 'Advertising|Display|Billboard, Mobile Billboard|Printed', + '2BYE' => 'Advertising|Display|Billboard, Mobile Billboard|Electronic Display', + '2BIL' => 'Advertising|Display|Billboard, All Types|Printed', + '2BUD' => 'Advertising|Display|Billboard, All Types|Electronic Display', + '2BRR' => 'Advertising|Display|Banner, Backdrop|Printed', + '2BUB' => 'Advertising|Display|Banner, Background|Printed', + '2BRA' => 'Advertising|Display|Banner, Airborne Display|Printed', + '2BRO' => 'Advertising|Display|Banner, All Types|Printed', + '2BAN' => 'Advertising|Display|Banner, All Types|Electronic Display', + '2CAR' => 'Advertising|Display|Shopping Cart|Printed', + '2CRT' => 'Advertising|Display|Shopping Cart|Electronic Display', + '2FAD' => 'Advertising|Display|Poster, Movie Poster|Printed', + '2HON' => 'Advertising|Display|Poster, Movie Poster|Electronic Display', + '2DIB' => 'Advertising|Display|Poster, Door Side Poster|Printed', + '2DEY' => 'Advertising|Display|Poster, Door Side Poster|Electronic Display', + '2DEV' => 'Advertising|Display|Poster, Corporate Poster|Printed', + '2DEN' => 'Advertising|Display|Poster, Corporate Poster|Electronic Display', + '2ELE' => 'Advertising|Display|Poster, Elevator Advertising|Printed', + '2DID' => 'Advertising|Display|Poster, Elevator Advertising|Electronic Display', + '2DRY' => 'Advertising|Display|Poster, Restroom Poster|Printed', + '2WET' => 'Advertising|Display|Poster, Restroom Poster|Electronic Display', + '2DEL' => 'Advertising|Display|Poster, Backlit Print|Printed', + '2DEX' => 'Advertising|Display|Poster, Display Chrome|Printed', + '2FAT' => 'Advertising|Display|Poster, All Types|Printed', + '2DAW' => 'Advertising|Display|Poster, All Types|Electronic Display', + '2MOB' => 'Advertising|Display|Store Display, In-Store Poster|Printed', + '2DIT' => 'Advertising|Display|Store Display, In-Store Poster|Electronic Display', + '2FOG' => 'Advertising|Display|Store Display, All Display Types|Printed', + '2DIN' => 'Advertising|Display|Store Display, All Display Types|Electronic Display', + '2HOT' => 'Advertising|Display|Terminal Advertising, Airport Display|Printed', + '2JOU' => 'Advertising|Display|Terminal Advertising, Airport Display|Electronic Display', + '2DOM' => 'Advertising|Display|Terminal Advertising, Bus Stop Advertising|Printed', + '2DOL' => 'Advertising|Display|Terminal Advertising, Bus Stop Advertising|Electronic Display', + '2DOR' => 'Advertising|Display|Terminal Advertising, Ferry Terminal Advertising|Printed', + '2DON' => 'Advertising|Display|Terminal Advertising, Ferry Terminal Advertising|Electronic Display', + '2TAN' => 'Advertising|Display|Terminal Advertising, Shelter Advertising|Printed', + '2DOS' => 'Advertising|Display|Terminal Advertising, Shelter Advertising|Electronic Display', + '2DUB' => 'Advertising|Display|Terminal Advertising, Station Poster|Printed', + '2DOW' => 'Advertising|Display|Terminal Advertising, Station Poster|Electronic Display', + '2DUG' => 'Advertising|Display|Terminal Advertising, Subway Terminal Advertising|Printed', + '2DUE' => 'Advertising|Display|Terminal Advertising, Subway Terminal Advertising|Electronic Display', + '2DUN' => 'Advertising|Display|Terminal Advertising, Train Terminal Advertising|Printed', + '2DUI' => 'Advertising|Display|Terminal Advertising, Train Terminal Advertising|Electronic Display', + '2JOY' => 'Advertising|Display|Terminal Advertising, All Types|Printed', + '2DOE' => 'Advertising|Display|Terminal Advertising, All Types|Electronic Display', + '2TAX' => 'Advertising|Display|Transit Advertising, Taxi Advertising|Printed', + '2BUS' => 'Advertising|Display|Transit Advertising, Bus Panel|Printed', + '2TRC' => 'Advertising|Display|Transit Advertising, Bus Panel|Electronic Display', + '2TRE' => 'Advertising|Display|Transit Advertising, Bus Poster|Printed', + '2BPE' => 'Advertising|Display|Transit Advertising, Bus Poster|Electronic Display', + '2TRG' => 'Advertising|Display|Transit Advertising, Bus Rear Display|Printed', + '2TRF' => 'Advertising|Display|Transit Advertising, Bus Rear Display|Electronic Display', + '2TRN' => 'Advertising|Display|Transit Advertising, Bus Wrap|Printed', + '2TRM' => 'Advertising|Display|Transit Advertising, Bus Wrap|Electronic Display', + '2TRJ' => 'Advertising|Display|Transit Advertising, Subway Advertising|Printed', + '2TRI' => 'Advertising|Display|Transit Advertising, Subway Advertising|Electronic Display', + '2TRL' => 'Advertising|Display|Transit Advertising, Train Advertising|Printed', + '2TRK' => 'Advertising|Display|Transit Advertising, Train Advertising|Electronic Display', + '2TRQ' => 'Advertising|Display|Transit Advertising, Commercial Vehicles|Printed', + '2TRR' => 'Advertising|Display|Transit Advertising, Commercial Vehicles|Electronic Display', + '2FER' => 'Advertising|Display|Transit Advertising, Ferry Advertising|Printed', + '2TRH' => 'Advertising|Display|Transit Advertising, Ferry Advertising|Electronic Display', + '2TRA' => 'Advertising|Display|Transit Advertising, All Types|Printed', + '2TRB' => 'Advertising|Display|Transit Advertising, All Types|Electronic Display', + '2DAM' => 'Advertising|Display|Event, Stadium Advertising|Printed', + '2DAL' => 'Advertising|Display|Event, Stadium Advertising|Electronic Display', + '2DIS' => 'Advertising|Display|Event, Trade Show Display|Printed', + '2DAP' => 'Advertising|Display|Event, Trade Show Display|Electronic Display', + '2BIG' => 'Advertising|Display|All Display Types|Printed', + '2BOG' => 'Advertising|Display|All Display Types|Electronic Display', + '2BOY' => 'Advertising|Art|Art Display, Display Print|Printed', + '2ART' => 'Advertising|Art|Art Display, All Art Types|Printed', + '2BEL' => 'Advertising|Art|Art Display, All Art Types|Internet Website', + '2BEY' => 'Advertising|Art|Art Display, All Art Types|All Internet Distribution Formats', + '2BOS' => 'Advertising|Art|Art Display, All Art Types|Electronic Display', + '2BIS' => 'Advertising|Art|Art Display, All Art Types|All Electronic Distribution Formats', + '2ADH' => 'Advertising|Point of Purchase|Adhesive Tag|Printed', + '2BOT' => 'Advertising|Point of Purchase|Bottlenecker|Printed', + '2CAS' => 'Advertising|Point of Purchase|Case Card|Printed', + '2COU' => 'Advertising|Point of Purchase|Counter Card|Printed', + '2BUY' => 'Advertising|Point of Purchase|Floor Graphic|Printed', + '2TAG' => 'Advertising|Point of Purchase|Hang Tag|Printed', + '2GYM' => 'Advertising|Point of Purchase|Kiosk, Interactive Kiosk|Printed', + '2GUN' => 'Advertising|Point of Purchase|Kiosk, Interactive Kiosk|Electronic Display', + '2JUG' => 'Advertising|Point of Purchase|Kiosk, Telephone Kiosk|Printed', + '2FIN' => 'Advertising|Point of Purchase|Kiosk, All Types|Printed', + '2KIO' => 'Advertising|Point of Purchase|Kiosk, All Types|Electronic Display', + '2MEN' => 'Advertising|Point of Purchase|Menu|Printed', + '2TAL' => 'Advertising|Point of Purchase|Shelf Talker|Printed', + '2TIN' => 'Advertising|Point of Purchase|Slip Case|Printed', + '2WAX' => 'Advertising|Point of Purchase|Table Tent|Printed', + '2BIN' => 'Advertising|Point of Purchase|All Point of Purchase Types|Printed', + '2BIB' => 'Advertising|Point of Purchase|All Point of Purchase Types|Electronic Display', + '2BAR' => 'Advertising|Point of Purchase|All Point of Purchase Types|All Electronic Distribution Formats', + '2MAR' => 'Advertising|Website|Web Page, Design Element|Internet Website', + '2MAX' => 'Advertising|Website|Web Page, Design Element|All Internet Distribution Formats', + '2MAS' => 'Advertising|Website|Web Page, Design Element|Recordable Media', + '2MAW' => 'Advertising|Website|Web Page, Design Element|All Electronic Distribution Formats', + '2MED' => 'Advertising|Website|Web Page, Web Banner Ad|Internet Website', + '2MEW' => 'Advertising|Website|Web Page, Web Banner Ad|All Internet Distribution Formats', + '2MEG' => 'Advertising|Website|Web Page, Web Banner Ad|Recordable Media', + '2MEM' => 'Advertising|Website|Web Page, Web Banner Ad|All Electronic Distribution Formats', + '2JWL' => 'Advertising|Website|Web Page, Web Interstitial Ad|Internet Website', + '2MIG' => 'Advertising|Website|Web Page, Web Interstitial Ad|All Internet Distribution Formats', + '2MIB' => 'Advertising|Website|Web Page, Web Interstitial Ad|Recordable Media', + '2MID' => 'Advertising|Website|Web Page, Web Interstitial Ad|All Electronic Distribution Formats', + '2WEB' => 'Advertising|Website|Web Page, All Types|Internet Website', + '2KUE' => 'Advertising|Website|Web Page, All Types|All Internet Distribution Formats', + '2MAC' => 'Advertising|Website|Web Page, All Types|Recordable Media', + '2MAE' => 'Advertising|Website|Web Page, All Types|All Electronic Distribution Formats', + '2MIL' => 'Advertising|Website|Webcast, All Types|Internet Website', + '2EMA' => 'Advertising|Email|All Email Types|Internet Email', + '2ZUS' => 'Advertising|Mobile|All Mobile Types|Mobile', + '2FUR' => 'Advertising|Live Presentation|Sales Presentation|Projected Display', + '2EYE' => 'Advertising|Live Presentation|Panel Presentation|Projected Display', + '2TOE' => 'Advertising|Live Presentation|Trade Show Presentation|Projected Display', + '2JAW' => 'Advertising|Live Presentation|Stage Performance|Projected Display', + '2EAR' => 'Advertising|Live Presentation|All Live Presentation Types|All Electronic Distribution Formats', + '2FID' => 'Advertising|Merchandise|Apparel, T-Shirts|Printed or Woven', + '2FEZ' => 'Advertising|Merchandise|Apparel, General Apparel|Printed or Woven', + '2FIE' => 'Advertising|Merchandise|Folder|Printed', + '2AAY' => 'Advertising|All Media Types|Promotional Reproduction of Licensed Usage in Context|All Distribution Formats', + '2AAA' => 'Advertising|All Media Types|All Formats|All Distribution Formats', + '2GEM' => 'Editorial|Periodicals|Magazine, Consumer|Printed', + '2GDW' => 'Editorial|Periodicals|Magazine, Consumer|Internet Website', + '2GDY' => 'Editorial|Periodicals|Magazine, Consumer|Internet Downloadable File', + '2KIN' => 'Editorial|Periodicals|Magazine, Consumer|Internet Email', + '2GEA' => 'Editorial|Periodicals|Magazine, Consumer|All Internet Distribution Formats', + '2GDV' => 'Editorial|Periodicals|Magazine, Consumer|Recordable Media', + '2GDZ' => 'Editorial|Periodicals|Magazine, Consumer|All Electronic Distribution Formats', + '2MOM' => 'Editorial|Periodicals|Magazine, Trade|Printed', + '2MOW' => 'Editorial|Periodicals|Magazine, Trade|Internet Website', + '2MUT' => 'Editorial|Periodicals|Magazine, Trade|Internet Downloadable File', + '2NAB' => 'Editorial|Periodicals|Magazine, Trade|Internet Email', + '2NAG' => 'Editorial|Periodicals|Magazine, Trade|All Internet Distribution Formats', + '2GFD' => 'Editorial|Periodicals|Magazine, Trade|Recordable Media', + '2NOB' => 'Editorial|Periodicals|Magazine, Trade|All Electronic Distribution Formats', + '2MAY' => 'Editorial|Periodicals|Magazine, Education|Printed', + '2GEP' => 'Editorial|Periodicals|Magazine, Education|Internet Website', + '2GEQ' => 'Editorial|Periodicals|Magazine, Education|Internet Downloadable File', + '2NOW' => 'Editorial|Periodicals|Magazine, Education|Internet Email', + '2GES' => 'Editorial|Periodicals|Magazine, Education|All Internet Distribution Formats', + '2GEK' => 'Editorial|Periodicals|Magazine, Education|Recordable Media', + '2GER' => 'Editorial|Periodicals|Magazine, Education|All Electronic Distribution Formats', + '2MOP' => 'Editorial|Periodicals|Magazine, Custom Published|Printed', + '2GEF' => 'Editorial|Periodicals|Magazine, Custom Published|Internet Website', + '2GEG' => 'Editorial|Periodicals|Magazine, Custom Published|Internet Downloadable File', + '2NUN' => 'Editorial|Periodicals|Magazine, Custom Published|Internet Email', + '2GEI' => 'Editorial|Periodicals|Magazine, Custom Published|All Internet Distribution Formats', + '2GEC' => 'Editorial|Periodicals|Magazine, Custom Published|Recordable Media', + '2GEH' => 'Editorial|Periodicals|Magazine, Custom Published|All Electronic Distribution Formats', + '2MEL' => 'Editorial|Periodicals|Magazine, Partworks|Printed', + '2GEV' => 'Editorial|Periodicals|Magazine, Partworks|Internet Website', + '2GEW' => 'Editorial|Periodicals|Magazine, Partworks|Internet Downloadable File', + '2TAB' => 'Editorial|Periodicals|Magazine, Partworks|Internet Email', + '2GFB' => 'Editorial|Periodicals|Magazine, Partworks|All Internet Distribution Formats', + '2GEZ' => 'Editorial|Periodicals|Magazine, Partworks|Recordable Media', + '2GFA' => 'Editorial|Periodicals|Magazine, Partworks|All Electronic Distribution Formats', + '2WIG' => 'Editorial|Periodicals|Scholarly Journal|Printed', + '2GGF' => 'Editorial|Periodicals|Scholarly Journal|Internet Website', + '2GGG' => 'Editorial|Periodicals|Scholarly Journal|Internet Downloadable File', + '2TAD' => 'Editorial|Periodicals|Scholarly Journal|Internet Email', + '2GGI' => 'Editorial|Periodicals|Scholarly Journal|All Internet Distribution Formats', + '2GGD' => 'Editorial|Periodicals|Scholarly Journal|Recordable Media', + '2GGH' => 'Editorial|Periodicals|Scholarly Journal|All Electronic Distribution Formats', + '2MAG' => 'Editorial|Periodicals|Magazine, All Types|Printed', + '2GDP' => 'Editorial|Periodicals|Magazine, All Types|Internet Website', + '2GDM' => 'Editorial|Periodicals|Magazine, All Types|Internet Downloadable File', + '2GDQ' => 'Editorial|Periodicals|Magazine, All Types|Internet Email', + '2GDT' => 'Editorial|Periodicals|Magazine, All Types|All Internet Distribution Formats', + '2GDR' => 'Editorial|Periodicals|Magazine, All Types|Recordable Media', + '2GDS' => 'Editorial|Periodicals|Magazine, All Types|All Electronic Distribution Formats', + '2JET' => 'Editorial|Periodicals|Newspaper, All Types|Printed', + '2GFN' => 'Editorial|Periodicals|Newspaper, All Types|Internet Website', + '2GFP' => 'Editorial|Periodicals|Newspaper, All Types|Internet Downloadable File', + '2TAJ' => 'Editorial|Periodicals|Newspaper, All Types|Internet Email', + '2GFR' => 'Editorial|Periodicals|Newspaper, All Types|All Internet Distribution Formats', + '2GFM' => 'Editorial|Periodicals|Newspaper, All Types|Recordable Media', + '2GFQ' => 'Editorial|Periodicals|Newspaper, All Types|All Electronic Distribution Formats', + '2TUB' => 'Editorial|Periodicals|Newspaper, Weekly Supplement|Printed', + '2GFW' => 'Editorial|Periodicals|Newspaper, Weekly Supplement|Internet Website', + '2GFY' => 'Editorial|Periodicals|Newspaper, Weekly Supplement|Internet Downloadable File', + '2TAO' => 'Editorial|Periodicals|Newspaper, Weekly Supplement|Internet Email', + '2GFV' => 'Editorial|Periodicals|Newspaper, Weekly Supplement|All Internet Distribution Formats', + '2GFS' => 'Editorial|Periodicals|Newspaper, Weekly Supplement|Recordable Media', + '2GFU' => 'Editorial|Periodicals|Newspaper, Weekly Supplement|All Electronic Distribution Formats', + '2FAN' => 'Editorial|Periodicals|Newspaper, Tabloid|Printed', + '2TEX' => 'Editorial|Periodicals|Newspaper, Tabloid|Internet Website', + '2GGA' => 'Editorial|Periodicals|Newspaper, Tabloid|Internet Downloadable File', + '2TIP' => 'Editorial|Periodicals|Newspaper, Tabloid|Internet Email', + '2TON' => 'Editorial|Periodicals|Newspaper, Tabloid|All Internet Distribution Formats', + '2TIE' => 'Editorial|Periodicals|Newspaper, Tabloid|Recordable Media', + '2TOT' => 'Editorial|Periodicals|Newspaper, Tabloid|All Electronic Distribution Formats', + '2TOM' => 'Editorial|Periodicals|Newsletter|Printed', + '2GFH' => 'Editorial|Periodicals|Newsletter|Internet Website', + '2GFI' => 'Editorial|Periodicals|Newsletter|Internet Downloadable File', + '2NEL' => 'Editorial|Periodicals|Newsletter|Internet Email', + '2GFK' => 'Editorial|Periodicals|Newsletter|All Internet Distribution Formats', + '2GFG' => 'Editorial|Periodicals|Newsletter|Recordable Media', + '2GFJ' => 'Editorial|Periodicals|Newsletter|All Electronic Distribution Formats', + '2BUG' => 'Editorial|Book|Retail Book, Children\'s Book|Printed', + '2EPC' => 'Editorial|Book|Retail Book, Children\'s Book|E-Book in Internet Website', + '2EBE' => 'Editorial|Book|Retail Book, Children\'s Book|E-Book in Internet Downloadable File', + '2EGB' => 'Editorial|Book|Retail Book, Children\'s Book|E-Book in Internet Email', + '2EKB' => 'Editorial|Book|Retail Book, Children\'s Book|All E-Book Internet Distribution Formats', + '2ERB' => 'Editorial|Book|Retail Book, Children\'s Book|E-Book on Recordable Media', + '2EGC' => 'Editorial|Book|Retail Book, Children\'s Book|All E-Book Distribution Formats', + '2TAE' => 'Editorial|Book|Retail Book, Coffee Table Book|Printed', + '2TAV' => 'Editorial|Book|Retail Book, Concept Book|Printed', + '2EPD' => 'Editorial|Book|Retail Book, Concept Book|E-Book in Internet Website', + '2EBF' => 'Editorial|Book|Retail Book, Concept Book|E-Book in Internet Downloadable File', + '2EGD' => 'Editorial|Book|Retail Book, Concept Book|E-Book in Internet Email', + '2EKC' => 'Editorial|Book|Retail Book, Concept Book|All E-Book Internet Distribution Formats', + '2ERC' => 'Editorial|Book|Retail Book, Concept Book|E-Book on Recordable Media', + '2EGE' => 'Editorial|Book|Retail Book, Concept Book|All E-Book Distribution Formats', + '2DIR' => 'Editorial|Book|Retail Book, Directory|Printed', + '2EPE' => 'Editorial|Book|Retail Book, Directory|E-Book in Internet Website', + '2EBG' => 'Editorial|Book|Retail Book, Directory|E-Book in Internet Downloadable File', + '2ERD' => 'Editorial|Book|Retail Book, Directory|E-Book on Recordable Media', + '2YEA' => 'Editorial|Book|Retail Book, Directory|All Electronic Distribution Formats', + '2EAB' => 'Editorial|Book|Retail Book, Directory|All E-Book Distribution Formats', + '2HAN' => 'Editorial|Book|Retail Book, Handbook|Printed', + '2EPF' => 'Editorial|Book|Retail Book, Handbook|E-Book in Internet Website', + '2EBH' => 'Editorial|Book|Retail Book, Handbook|E-Book in Internet Downloadable File', + '2EGH' => 'Editorial|Book|Retail Book, Handbook|E-Book in Internet Email', + '2EKD' => 'Editorial|Book|Retail Book, Handbook|All E-Book Internet Distribution Formats', + '2ERF' => 'Editorial|Book|Retail Book, Handbook|E-Book on Recordable Media', + '2EGJ' => 'Editorial|Book|Retail Book, Handbook|All E-Book Distribution Formats', + '2HIL' => 'Editorial|Book|Retail Book, Hi-lo Book|Printed', + '2EPG' => 'Editorial|Book|Retail Book, Hi-lo Book|E-Book in Internet Website', + '2EBI' => 'Editorial|Book|Retail Book, Hi-lo Book|E-Book in Internet Downloadable File', + '2EGK' => 'Editorial|Book|Retail Book, Hi-lo Book|E-Book in Internet Email', + '2EKE' => 'Editorial|Book|Retail Book, Hi-lo Book|All E-Book Internet Distribution Formats', + '2ERG' => 'Editorial|Book|Retail Book, Hi-lo Book|E-Book on Recordable Media', + '2EGL' => 'Editorial|Book|Retail Book, Hi-lo Book|All E-Book Distribution Formats', + '2WAB' => 'Editorial|Book|Retail Book, Illustrated Book|Printed', + '2EPH' => 'Editorial|Book|Retail Book, Illustrated Book|E-Book in Internet Website', + '2EBJ' => 'Editorial|Book|Retail Book, Illustrated Book|E-Book in Internet Downloadable File', + '2EGM' => 'Editorial|Book|Retail Book, Illustrated Book|E-Book in Internet Email', + '2EKF' => 'Editorial|Book|Retail Book, Illustrated Book|All E-Book Internet Distribution Formats', + '2ERH' => 'Editorial|Book|Retail Book, Illustrated Book|E-Book on Recordable Media', + '2EGN' => 'Editorial|Book|Retail Book, Illustrated Book|All E-Book Distribution Formats', + '2WHA' => 'Editorial|Book|Retail Book, Illustrated Guide|Printed', + '2EPJ' => 'Editorial|Book|Retail Book, Illustrated Guide|E-Book in Internet Website', + '2EBL' => 'Editorial|Book|Retail Book, Illustrated Guide|E-Book in Internet Downloadable File', + '2EGP' => 'Editorial|Book|Retail Book, Illustrated Guide|E-Book in Internet Email', + '2EKG' => 'Editorial|Book|Retail Book, Illustrated Guide|All E-Book Internet Distribution Formats', + '2ERI' => 'Editorial|Book|Retail Book, Illustrated Guide|E-Book on Recordable Media', + '2EGQ' => 'Editorial|Book|Retail Book, Illustrated Guide|All E-Book Distribution Formats', + '2EGR' => 'Editorial|Book|Retail Book, Illustrated Guide|All E-Book Distribution Formats', + '2MAN' => 'Editorial|Book|Retail Book, Manual|Printed', + '2EPK' => 'Editorial|Book|Retail Book, Manual|E-Book in Internet Website', + '2EBM' => 'Editorial|Book|Retail Book, Manual|E-Book in Internet Downloadable File', + '2EGS' => 'Editorial|Book|Retail Book, Manual|E-Book in Internet Email', + '2EKH' => 'Editorial|Book|Retail Book, Manual|All E-Book Internet Distribution Formats', + '2ERJ' => 'Editorial|Book|Retail Book, Manual|E-Book on Recordable Media', + '2EGT' => 'Editorial|Book|Retail Book, Manual|All E-Book Distribution Formats', + '2YAP' => 'Editorial|Book|Retail Book, Novelty Book|Printed', + '2EPL' => 'Editorial|Book|Retail Book, Novelty Book|E-Book in Internet Website', + '2EBN' => 'Editorial|Book|Retail Book, Novelty Book|E-Book in Internet Downloadable File', + '2EGV' => 'Editorial|Book|Retail Book, Novelty Book|E-Book in Internet Email', + '2EKJ' => 'Editorial|Book|Retail Book, Novelty Book|All E-Book Internet Distribution Formats', + '2ERK' => 'Editorial|Book|Retail Book, Novelty Book|E-Book on Recordable Media', + '2EGW' => 'Editorial|Book|Retail Book, Novelty Book|All E-Book Distribution Formats', + '2YEN' => 'Editorial|Book|Retail Book, Postcard Book|Printed', + '2EPM' => 'Editorial|Book|Retail Book, Postcard Book|E-Book in Internet Website', + '2EBP' => 'Editorial|Book|Retail Book, Postcard Book|E-Book in Internet Downloadable File', + '2EGY' => 'Editorial|Book|Retail Book, Postcard Book|E-Book in Internet Email', + '2EKK' => 'Editorial|Book|Retail Book, Postcard Book|All E-Book Internet Distribution Formats', + '2ERL' => 'Editorial|Book|Retail Book, Postcard Book|E-Book on Recordable Media', + '2EGZ' => 'Editorial|Book|Retail Book, Postcard Book|All E-Book Distribution Formats', + '2YOK' => 'Editorial|Book|Retail Book, Young Adult Book|Printed', + '2EPN' => 'Editorial|Book|Retail Book, Young Adult Book|E-Book in Internet Website', + '2EBQ' => 'Editorial|Book|Retail Book, Young Adult Book|E-Book in Internet Downloadable File', + '2EJB' => 'Editorial|Book|Retail Book, Young Adult Book|E-Book in Internet Email', + '2EKL' => 'Editorial|Book|Retail Book, Young Adult Book|All E-Book Internet Distribution Formats', + '2ERM' => 'Editorial|Book|Retail Book, Young Adult Book|E-Book on Recordable Media', + '2EJC' => 'Editorial|Book|Retail Book, Young Adult Book|All E-Book Distribution Formats', + '2BOO' => 'Editorial|Book|Retail Book, All Types|Printed', + '2EPB' => 'Editorial|Book|Retail Book, All Types|E-Book in Internet Website', + '2EBC' => 'Editorial|Book|Retail Book, All Types|E-Book in Internet Downloadable File', + '2EJD' => 'Editorial|Book|Retail Book, All Types|E-Book in Internet Email', + '2EKM' => 'Editorial|Book|Retail Book, All Types|All E-Book Internet Distribution Formats', + '2ERN' => 'Editorial|Book|Retail Book, All Types|E-Book on Recordable Media', + '2EJE' => 'Editorial|Book|Retail Book, All Types|All E-Book Distribution Formats', + '2EEL' => 'Editorial|Book|Textbook, Compendium|Printed', + '2EPQ' => 'Editorial|Book|Textbook, Compendium|E-Book in Internet Website', + '2EBS' => 'Editorial|Book|Textbook, Compendium|E-Book in Internet Downloadable File', + '2EJF' => 'Editorial|Book|Textbook, Compendium|E-Book in Internet Email', + '2EKN' => 'Editorial|Book|Textbook, Compendium|All E-Book Internet Distribution Formats', + '2ERP' => 'Editorial|Book|Textbook, Compendium|E-Book on Recordable Media', + '2EJJ' => 'Editorial|Book|Textbook, Compendium|All E-Book Distribution Formats', + '2EMU' => 'Editorial|Book|Textbook, Course Pack|Printed', + '2GBN' => 'Editorial|Book|Textbook, Course Pack|Internet Website', + '2GBM' => 'Editorial|Book|Textbook, Course Pack|Internet Downloadable File', + '2ZOO' => 'Editorial|Book|Textbook, Course Pack|Internet Email', + '2GBQ' => 'Editorial|Book|Textbook, Course Pack|All Internet Distribution Formats', + '2ZOA' => 'Editorial|Book|Textbook, Course Pack|Recordable Media', + '2GBP' => 'Editorial|Book|Textbook, Course Pack|All Distribution Formats', + '2BEE' => 'Editorial|Book|Textbook, Middle Reader|Printed', + '2EPR' => 'Editorial|Book|Textbook, Middle Reader|E-Book in Internet Website', + '2EBT' => 'Editorial|Book|Textbook, Middle Reader|E-Book in Internet Downloadable File', + '2EJK' => 'Editorial|Book|Textbook, Middle Reader|E-Book in Internet Email', + '2EKP' => 'Editorial|Book|Textbook, Middle Reader|All E-Book Internet Distribution Formats', + '2ERQ' => 'Editorial|Book|Textbook, Middle Reader|E-Book on Recordable Media', + '2EJL' => 'Editorial|Book|Textbook, Middle Reader|All E-Book Distribution Formats', + '2BOA' => 'Editorial|Book|Textbook, Student Edition|Printed', + '2EPS' => 'Editorial|Book|Textbook, Student Edition|E-Book in Internet Website', + '2EBV' => 'Editorial|Book|Textbook, Student Edition|E-Book in Internet Downloadable File', + '2EJM' => 'Editorial|Book|Textbook, Student Edition|E-Book in Internet Email', + '2EKQ' => 'Editorial|Book|Textbook, Student Edition|All E-Book Internet Distribution Formats', + '2ERS' => 'Editorial|Book|Textbook, Student Edition|E-Book on Recordable Media', + '2EJN' => 'Editorial|Book|Textbook, Student Edition|All E-Book Distribution Formats', + '2FOX' => 'Editorial|Book|Textbook, All Types|Printed', + '2EPP' => 'Editorial|Book|Textbook, All Types|E-Book in Internet Website', + '2EBR' => 'Editorial|Book|Textbook, All Types|E-Book in Internet Downloadable File', + '2EGA' => 'Editorial|Book|Textbook, All Types|E-Book in Internet Email', + '2EKR' => 'Editorial|Book|Textbook, All Types|All E-Book Internet Distribution Formats', + '2ERT' => 'Editorial|Book|Textbook, All Types|E-Book on Recordable Media', + '2EJP' => 'Editorial|Book|Textbook, All Types|All E-Book Distribution Formats', + '2GBZ' => 'Editorial|Book|Textbook Ancillary Materials, Educational Film Set|Projected Display', + '2YET' => 'Editorial|Book|Textbook Ancillary Materials, Packaging For Recordable Media|Printed', + '2YUM' => 'Editorial|Book|Textbook Ancillary Materials, Lab Manual|Printed', + '2EPZ' => 'Editorial|Book|Textbook Ancillary Materials, Lab Manual|E-Book in Internet Website', + '2EBA' => 'Editorial|Book|Textbook Ancillary Materials, Lab Manual|E-Book in Internet Downloadable File', + '2EJQ' => 'Editorial|Book|Textbook Ancillary Materials, Lab Manual|E-Book in Internet Email', + '2EKS' => 'Editorial|Book|Textbook Ancillary Materials, Lab Manual|All E-Book Internet Distribution Formats', + '2ERU' => 'Editorial|Book|Textbook Ancillary Materials, Lab Manual|E-Book on Recordable Media', + '2EJR' => 'Editorial|Book|Textbook Ancillary Materials, Lab Manual|All E-Book Distribution Formats', + '2YAK' => 'Editorial|Book|Textbook Ancillary Materials, Teachers\' Edition|Printed', + '2EPT' => 'Editorial|Book|Textbook Ancillary Materials, Teachers\' Edition|E-Book in Internet Website', + '2EBW' => 'Editorial|Book|Textbook Ancillary Materials, Teachers\' Edition|E-Book in Internet Downloadable File', + '2EJS' => 'Editorial|Book|Textbook Ancillary Materials, Teachers\' Edition|E-Book in Internet Email', + '2EKT' => 'Editorial|Book|Textbook Ancillary Materials, Teachers\' Edition|All E-Book Internet Distribution Formats', + '2ERV' => 'Editorial|Book|Textbook Ancillary Materials, Teachers\' Edition|E-Book on Recordable Media', + '2EJT' => 'Editorial|Book|Textbook Ancillary Materials, Teachers\' Edition|All E-Book Distribution Formats', + '2ZOT' => 'Editorial|Book|Textbook Ancillary Materials, Teacher\'s Manual|Printed', + '2EPW' => 'Editorial|Book|Textbook Ancillary Materials, Teacher\'s Manual|E-Book in Internet Website', + '2EBD' => 'Editorial|Book|Textbook Ancillary Materials, Teacher\'s Manual|E-Book in Internet Downloadable File', + '2EJV' => 'Editorial|Book|Textbook Ancillary Materials, Teacher\'s Manual|E-Book in Internet Email', + '2EKU' => 'Editorial|Book|Textbook Ancillary Materials, Teacher\'s Manual|All E-Book Internet Distribution Formats', + '2ERW' => 'Editorial|Book|Textbook Ancillary Materials, Teacher\'s Manual|E-Book on Recordable Media', + '2EJW' => 'Editorial|Book|Textbook Ancillary Materials, Teacher\'s Manual|All E-Book Distribution Formats', + '2GEE' => 'Editorial|Book|Textbook Ancillary Materials, Workbook|Printed', + '2EPY' => 'Editorial|Book|Textbook Ancillary Materials, Workbook|E-Book in Internet Website', + '2EBB' => 'Editorial|Book|Textbook Ancillary Materials, Workbook|E-Book in Internet Downloadable File', + '2EJY' => 'Editorial|Book|Textbook Ancillary Materials, Workbook|E-Book in Internet Email', + '2EKV' => 'Editorial|Book|Textbook Ancillary Materials, Workbook|All E-Book Internet Distribution Formats', + '2ERY' => 'Editorial|Book|Textbook Ancillary Materials, Workbook|E-Book on Recordable Media', + '2EJZ' => 'Editorial|Book|Textbook Ancillary Materials, Workbook|All E-Book Distribution Formats', + '2ELK' => 'Editorial|Book|Textbook Ancillary Materials, All Ancillary Types|Printed', + '2GBY' => 'Editorial|Book|Textbook Ancillary Materials, All Ancillary Types|All Internet Distribution Formats', + '2GBW' => 'Editorial|Book|Textbook Ancillary Materials, All Ancillary Types|All Distribution Formats', + '2NAY' => 'Editorial|Book|Reference Book, Encyclopedia|Printed', + '2EAQ' => 'Editorial|Book|Reference Book, Encyclopedia|E-Book in Internet Website', + '2EMB' => 'Editorial|Book|Reference Book, Encyclopedia|E-Book in Internet Downloadable File', + '2EMC' => 'Editorial|Book|Reference Book, Encyclopedia|All E-Book Internet Distribution Formats', + '2EMD' => 'Editorial|Book|Reference Book, Encyclopedia|E-Book on Recordable Media', + '2NAH' => 'Editorial|Book|Reference Book, Encyclopedia|All Electronic Distribution Formats', + '2NIP' => 'Editorial|Book|Reference Book, Telephone Book|Printed', + '2EMF' => 'Editorial|Book|Reference Book, Telephone Book|E-Book in Internet Website', + '2EMG' => 'Editorial|Book|Reference Book, Telephone Book|E-Book in Internet Downloadable File', + '2EMH' => 'Editorial|Book|Reference Book, Telephone Book|All E-Book Internet Distribution Formats', + '2EMI' => 'Editorial|Book|Reference Book, Telephone Book|E-Book on Recordable Media', + '2EMJ' => 'Editorial|Book|Reference Book, Telephone Book|All E-Book Distribution Formats', + '2DOG' => 'Editorial|Book|Reference Book, All Types|Printed', + '2EMK' => 'Editorial|Book|Reference Book, All Types|E-Book in Internet Website', + '2EML' => 'Editorial|Book|Reference Book, All Types|E-Book in Internet Downloadable File', + '2EMM' => 'Editorial|Book|Reference Book, All Types|All E-Book Internet Distribution Formats', + '2EMN' => 'Editorial|Book|Reference Book, All Types|E-Book on Recordable Media', + '2EMP' => 'Editorial|Book|Reference Book, All Types|All E-Book Distribution Formats', + '2DEW' => 'Editorial|Book|Trade Book, All Types|Printed', + '2EPV' => 'Editorial|Book|Trade Book, All Types|E-Book in Internet Website', + '2EMQ' => 'Editorial|Book|Trade Book, All Types|E-Book in Internet Downloadable File', + '2EMR' => 'Editorial|Book|Trade Book, All Types|E-Book in Internet Email', + '2EMS' => 'Editorial|Book|Trade Book, All Types|All E-Book Internet Distribution Formats', + '2ERA' => 'Editorial|Book|Trade Book, All Types|E-Book on Recordable Media', + '2EMT' => 'Editorial|Book|Trade Book, All Types|All E-Book Distribution Formats', + '2MOG' => 'Editorial|Book|All Book Types|Printed', + '2EPA' => 'Editorial|Book|All Book Types|E-Book in Internet Website', + '2EBK' => 'Editorial|Book|All Book Types|E-Book in Internet Downloadable File', + '2EJU' => 'Editorial|Book|All Book Types|E-Book in Internet Email', + '2EKA' => 'Editorial|Book|All Book Types|All E-Book Internet Distribution Formats', + '2ERZ' => 'Editorial|Book|All Book Types|E-Book on Recordable Media', + '2EJA' => 'Editorial|Book|All Book Types|All E-Book Distribution Formats', + '2MOR' => 'Editorial|Book|Artist\'s Reference, All Types|Printed', + '2EKW' => 'Editorial|Book|Artist\'s Reference, All Types|All E-Book Internet Distribution Formats', + '2EMV' => 'Editorial|Book|Artist\'s Reference, All Types|All E-Book Distribution Formats', + '2GAL' => 'Editorial|Display|Gallery Exhibition|Printed', + '2GDJ' => 'Editorial|Display|Gallery Exhibition|Electronic Display', + '2MUS' => 'Editorial|Display|Museum Display|Printed', + '2GDK' => 'Editorial|Display|Museum Display|Electronic Display', + '2EAU' => 'Editorial|Display|Poster, Educational Poster|Printed', + '2GHG' => 'Editorial|Website|Web Page, Body Content|Internet Website', + '2GHK' => 'Editorial|Website|Web Page, Body Content|All Internet Distribution Formats', + '2GHH' => 'Editorial|Website|Web Page, Body Content|Recordable Media', + '2GHJ' => 'Editorial|Website|Web Page, Body Content|All Electronic Distribution Formats', + '2GHB' => 'Editorial|Website|Web Page, All Types|Internet Website', + '2GHF' => 'Editorial|Website|Web Page, All Types|All Internet Distribution Formats', + '2GHC' => 'Editorial|Website|Web Page, All Types|Recordable Media', + '2GHD' => 'Editorial|Website|Web Page, All Types|All Electronic Distribution Formats', + '2GHA' => 'Editorial|Website|Webcast, All Types|Internet Website', + '2ZAG' => 'Editorial|Mobile|All Mobile Types|Mobile', + '2GGM' => 'Editorial|Merchandise|CD ROM|Recordable Media', + '2DVE' => 'Editorial|Merchandise|DVD|Recordable Media', + '2AAE' => 'Editorial|All Media Types|All Formats|All Distribution Formats', + '2ZIG' => 'Products|Mobile|Wallpaper|Mobile', + '2ZIP' => 'Products|Mobile|Game, All Types|Mobile', + '2ZAP' => 'Products|Mobile|Entertainment Programming|Mobile', + '2ZEN' => 'Products|Mobile|Computer Software|Mobile', + '2ZAM' => 'Products|Mobile|All Mobile Types|Mobile', + '2ADD' => 'Products|Merchandise|Address Book|Printed', + '2ANT' => 'Products|Merchandise|Anthology|Printed', + '2APP' => 'Products|Merchandise|Apparel, General Apparel|Printed or Woven', + '2TST' => 'Products|Merchandise|Apparel, T-Shirts|Printed or Woven', + '2BIO' => 'Products|Merchandise|Birthday Book|Printed', + '2BLA' => 'Products|Merchandise|Blank Note Book|Printed', + '2ATM' => 'Products|Merchandise|Card, ATM Card|Printed', + '2BNK' => 'Products|Merchandise|Card, Bank Card|Printed', + '2CRE' => 'Products|Merchandise|Card, Credit Card|Printed', + '2BIT' => 'Products|Merchandise|Card, Debit Card|Printed', + '2GFT' => 'Products|Merchandise|Card, Gift Card|Printed', + '2GRE' => 'Products|Merchandise|Card, Greeting Card|Printed', + '2HER' => 'Products|Merchandise|Card, Hero Card|Printed', + '2CRD' => 'Products|Merchandise|Card, Other Card|Printed', + '2ATT' => 'Products|Merchandise|Card, Phone Card|Printed', + '2CDR' => 'Products|Merchandise|CD ROM|Recordable Media', + '2CHK' => 'Products|Merchandise|Check|Printed', + '2BAK' => 'Products|Merchandise|Computer Software|Internet Downloadable File', + '2BAU' => 'Products|Merchandise|Computer Software|Internet Email', + '2BAV' => 'Products|Merchandise|Computer Software|All Internet Distribution Formats', + '2BAJ' => 'Products|Merchandise|Computer Software|Recordable Media', + '2WAR' => 'Products|Merchandise|Computer Software|All Electronic Distribution Formats', + '2GIG' => 'Products|Merchandise|Datebook|Printed', + '2DIA' => 'Products|Merchandise|Diary|Printed', + '2BAZ' => 'Products|Merchandise|Diary|Internet Downloadable File', + '2BBA' => 'Products|Merchandise|Diary|Internet Email', + '2BAW' => 'Products|Merchandise|Diary|Recordable Media', + '2HUB' => 'Products|Merchandise|Double Postcard|Printed', + '2DVD' => 'Products|Merchandise|DVD|Recordable Media', + '2EAT' => 'Products|Merchandise|Edible Media|Printed', + '2FOL' => 'Products|Merchandise|Folder|Printed', + '2GAB' => 'Products|Merchandise|Game, Computer Game|Internet Downloadable File', + '2GAH' => 'Products|Merchandise|Game, Computer Game|Internet Email', + '2GAE' => 'Products|Merchandise|Game, Computer Game|All Internet Distribution Formats', + '2GAG' => 'Products|Merchandise|Game, Computer Game|Recordable Media', + '2GAN' => 'Products|Merchandise|Game, Computer Game|All Electronic Distribution Formats', + '2GAP' => 'Products|Merchandise|Game, All Types|Printed', + '2GAT' => 'Products|Merchandise|Game, All Types|Internet Downloadable File', + '2GAS' => 'Products|Merchandise|Game, All Types|Internet Email', + '2GAD' => 'Products|Merchandise|Game, All Types|All Internet Distribution Formats', + '2GAR' => 'Products|Merchandise|Game, All Types|Recordable Media', + '2GAM' => 'Products|Merchandise|Game, All Types|All Electronic Distribution Formats', + '2BOX' => 'Products|Merchandise|Gift Box|Printed', + '2GIF' => 'Products|Merchandise|Gift Certificate|Printed', + '2WRP' => 'Products|Merchandise|Gift Wrap|Printed', + '2JIG' => 'Products|Merchandise|Jigsaw Puzzle|Printed', + '2JIN' => 'Products|Merchandise|Jigsaw Puzzle|Internet Downloadable File', + '2JIL' => 'Products|Merchandise|Jigsaw Puzzle|Recordable Media', + '2JRN' => 'Products|Merchandise|Journal|Printed', + '2MAP' => 'Products|Merchandise|Map|Printed', + '2MAD' => 'Products|Merchandise|Map|Internet Downloadable File', + '2MET' => 'Products|Merchandise|Map|Internet Email', + '2MIM' => 'Products|Merchandise|Map|All Internet Distribution Formats', + '2MIR' => 'Products|Merchandise|Map|Recordable Media', + '2MAB' => 'Products|Merchandise|Map|All Electronic Distribution Formats', + '2MOU' => 'Products|Merchandise|Mouse Pad|Printed', + '2MUG' => 'Products|Merchandise|Mugs|Printed', + '2NOV' => 'Products|Merchandise|Novelty Products|Printed', + '2MCH' => 'Products|Merchandise|Other Merchandise|Printed', + '2ALB' => 'Products|Merchandise|Photo Album|Printed', + '2MAT' => 'Products|Merchandise|Placemat|Printed', + '2MRP' => 'Products|Merchandise|Plates|Printed', + '2TOP' => 'Products|Merchandise|Poster, Retail Poster|Printed', + '2FUN' => 'Products|Merchandise|Playing Cards|Printed', + '2CLO' => 'Products|Merchandise|Retail Calendar, One Page|Printed', + '2CAL' => 'Products|Merchandise|Retail Calendar, Multi-Page|Printed', + '2BCJ' => 'Products|Merchandise|Retail Calendar, Multi-Page|Internet Downloadable File', + '2BCI' => 'Products|Merchandise|Retail Calendar, Multi-Page|Internet Email', + '2BCH' => 'Products|Merchandise|Retail Calendar, Multi-Page|All Internet Distribution Formats', + '2BCF' => 'Products|Merchandise|Retail Calendar, Multi-Page|Recordable Media', + '2BCG' => 'Products|Merchandise|Retail Calendar, Multi-Page|All Electronic Distribution Formats', + '2FAR' => 'Products|Merchandise|Retail Postcard|Printed', + '2FIB' => 'Products|Merchandise|Screen Saver|Internet Downloadable File', + '2GEL' => 'Products|Merchandise|Screen Saver|Internet Email', + '2TOD' => 'Products|Merchandise|Screen Saver|All Internet Distribution Formats', + '2WRY' => 'Products|Merchandise|Screen Saver|Recordable Media', + '2HEX' => 'Products|Merchandise|Screen Saver|All Electronic Distribution Formats', + '2KEP' => 'Products|Merchandise|Souvenir|Printed', + '2WEE' => 'Products|Merchandise|Stamp|Printed', + '2WIT' => 'Products|Merchandise|Stationery|Printed', + '2TIC' => 'Products|Merchandise|Sticker|Printed', + '2FEM' => 'Products|Merchandise|Textiles|Printed or Woven', + '2TKT' => 'Products|Merchandise|Ticket|Printed', + '2TOY' => 'Products|Merchandise|Toy|Printed', + '2TRD' => 'Products|Merchandise|Trading Cards|Printed', + '2TUA' => 'Products|Merchandise|Virtual Reality|Recordable Media', + '2WAL' => 'Products|Merchandise|Wallpaper|Printed', + '2MER' => 'Products|Merchandise|All Merchandise Types|Printed', + '2BAB' => 'Products|Art|Artist\'s Reference, Tattoo|Printed', + '2DVL' => 'Products|Product Packaging|Packaging For Recordable Media, Liner Notes|Printed', + '2DVP' => 'Products|Product Packaging|Packaging For Recordable Media, All Packaging Types|Printed', + '2FRA' => 'Products|Product Packaging|Picture Frame Insert|Printed', + '2EVE' => 'Products|Product Packaging|Retail Packaging, All Packaging Types|Printed', + '2WHO' => 'Products|Product Packaging|Wholesale Packaging, All Packaging Types|Printed', + '2DAY' => 'Products|Product Packaging|All Product Packaging Types|Printed', + '2AAP' => 'Products|All Media Types|All Formats|All Distribution Formats', + '2TUX' => 'Internal Company Use|Periodicals|Magazine, Custom Published|Printed', + '2BIC' => 'Internal Company Use|Periodicals|Magazine, Custom Published|Intranet and Extranet Website', + '2BIF' => 'Internal Company Use|Periodicals|Magazine, Custom Published|Intranet and Extranet Downloadable File', + '2FOE' => 'Internal Company Use|Periodicals|Magazine, Custom Published|Internet Email', + '2BII' => 'Internal Company Use|Periodicals|Magazine, Custom Published|All Intranet and Extranet Distribution Formats', + '2BIA' => 'Internal Company Use|Periodicals|Magazine, Custom Published|Recordable Media', + '2BIH' => 'Internal Company Use|Periodicals|Magazine, Custom Published|All Electronic Distribution Formats', + '2FRO' => 'Internal Company Use|Comp Use|All Comp Types|Printed', + '2FPO' => 'Internal Company Use|Comp Use|All Comp Types|All Electronic Distribution Formats', + '2GED' => 'Internal Company Use|Internal Review|All Review Types|Printed', + '2GID' => 'Internal Company Use|Internal Review|All Review Types|All Electronic Distribution Formats', + '2GOA' => 'Internal Company Use|Promotional Materials|Corporate Brochure|Printed', + '2BHH' => 'Internal Company Use|Promotional Materials|Corporate Brochure|Intranet and Extranet Downloadable File', + '2BHI' => 'Internal Company Use|Promotional Materials|Corporate Brochure|Intranet and Extranet Email', + '2BHG' => 'Internal Company Use|Promotional Materials|Corporate Brochure|Recordable Media', + '2BHN' => 'Internal Company Use|Promotional Materials|Corporate Calendar|Printed', + '2BHL' => 'Internal Company Use|Promotional Materials|Corporate Calendar|Intranet and Extranet Downloadable File', + '2BHM' => 'Internal Company Use|Promotional Materials|Corporate Calendar|Intranet and Extranet Email', + '2BHK' => 'Internal Company Use|Promotional Materials|Corporate Calendar|Recordable Media', + '2BHY' => 'Internal Company Use|Promotional Materials|Corporate Folder|Printed', + '2BHQ' => 'Internal Company Use|Promotional Materials|Card, Corporate Card|Printed', + '2BHT' => 'Internal Company Use|Promotional Materials|Card, Corporate Card|Intranet and Extranet Downloadable File', + '2BHU' => 'Internal Company Use|Promotional Materials|Card, Corporate Card|Intranet and Extranet Email', + '2BHS' => 'Internal Company Use|Promotional Materials|Card, Corporate Card|Recordable Media', + '2BHZ' => 'Internal Company Use|Promotional Materials|CD ROM|Recordable Media', + '2DVI' => 'Internal Company Use|Promotional Materials|DVD|Recordable Media', + '2BHV' => 'Internal Company Use|Promotional Materials|Sales Kit|All Electronic Distribution Formats', + '2BHW' => 'Internal Company Use|Promotional Materials|Training Materials|Printed', + '2BJN' => 'Internal Company Use|Website|Web Page, Content Body|Intranet and Extranet Website', + '2FAB' => 'Internal Company Use|Website|Web Page, Content Body|Recordable Media', + '2FAC' => 'Internal Company Use|Website|Web Page, Content Body|All Electronic Distribution Formats', + '2FAH' => 'Internal Company Use|Website|Web Page, Design Element|Intranet and Extranet Website', + '2FAI' => 'Internal Company Use|Website|Web Page, Design Element|Recordable Media', + '2FAJ' => 'Internal Company Use|Website|Web Page, Design Element|All Electronic Distribution Formats', + '2NET' => 'Internal Company Use|Website|Web Page, All Types|Intranet and Extranet Website', + '2BJL' => 'Internal Company Use|Website|Web Page, All Types|Recordable Media', + '2BJK' => 'Internal Company Use|Website|Web Page, All Types|All Electronic Distribution Formats', + '2BJH' => 'Internal Company Use|Website|Webcast, All Types|Intranet and Extranet Website', + '2WAN' => 'Internal Company Use|Website|All Website Types|Intranet and Extranet Website', + '2BHD' => 'Internal Company Use|Email|All Email Types|Intranet and Extranet Email', + '2ZUM' => 'Internal Company Use|Mobile|All Mobile Types|Mobile', + '2BHF' => 'Internal Company Use|Live Presentation|Internal Presentation|Projected Display', + '2BHB' => 'Internal Company Use|Art|Art Display, Display Print|Printed', + '2GOY' => 'Internal Company Use|Art|Art Display, All Art Types|Printed', + '2HAO' => 'Internal Company Use|Art|Art Display, All Art Types|Electronic Display', + '2AAI' => 'Internal Company Use|All Media Types|All Formats|All Distribution Formats', + '2TVC' => 'Motion Picture & TV|Television Programming|Commercial|Television Broadcast', + '2TEB' => 'Motion Picture & TV|Television Programming|Commercial|Internet Downloadable File', + '2TEC' => 'Motion Picture & TV|Television Programming|Commercial|All Internet Distribution Formats', + '2TEG' => 'Motion Picture & TV|Television Programming|Commercial|Recordable Media', + '2TVD' => 'Motion Picture & TV|Television Programming|Commercial|All Electronic Distribution Formats', + '2TVE' => 'Motion Picture & TV|Television Programming|Infomercial|Television Broadcast', + '2TEH' => 'Motion Picture & TV|Television Programming|Infomercial|Internet Downloadable File', + '2TEJ' => 'Motion Picture & TV|Television Programming|Infomercial|All Internet Distribution Formats', + '2TEK' => 'Motion Picture & TV|Television Programming|Infomercial|Recordable Media', + '2TVF' => 'Motion Picture & TV|Television Programming|Infomercial|All Electronic Distribution Formats', + '2TVG' => 'Motion Picture & TV|Television Programming|On-Air Promotion|Television Broadcast', + '2TEL' => 'Motion Picture & TV|Television Programming|On-Air Promotion|Internet Downloadable File', + '2TEM' => 'Motion Picture & TV|Television Programming|On-Air Promotion|All Internet Distribution Formats', + '2TEP' => 'Motion Picture & TV|Television Programming|On-Air Promotion|Recordable Media', + '2TVH' => 'Motion Picture & TV|Television Programming|On-Air Promotion|All Electronic Distribution Formats', + '2TVI' => 'Motion Picture & TV|Television Programming|Documentary Program|Television Broadcast', + '2TER' => 'Motion Picture & TV|Television Programming|Documentary Program|Internet Downloadable File', + '2TES' => 'Motion Picture & TV|Television Programming|Documentary Program|All Internet Distribution Formats', + '2TET' => 'Motion Picture & TV|Television Programming|Documentary Program|Recordable Media', + '2TVJ' => 'Motion Picture & TV|Television Programming|Documentary Program|All Electronic Distribution Formats', + '2TVA' => 'Motion Picture & TV|Television Programming|All Television Advertising Types|Television Broadcast', + '2TEA' => 'Motion Picture & TV|Television Programming|All Television Advertising Types|Internet Downloadable File', + '2TEQ' => 'Motion Picture & TV|Television Programming|All Television Advertising Types|All Internet Distribution Formats', + '2TED' => 'Motion Picture & TV|Television Programming|All Television Advertising Types|Recordable Media', + '2TVB' => 'Motion Picture & TV|Television Programming|All Television Advertising Types|All Electronic Distribution Formats', + '2TVK' => 'Motion Picture & TV|Television Programming|Educational Program|Television Broadcast', + '2TEU' => 'Motion Picture & TV|Television Programming|Educational Program|Internet Downloadable File', + '2TEV' => 'Motion Picture & TV|Television Programming|Educational Program|All Internet Distribution Formats', + '2TEW' => 'Motion Picture & TV|Television Programming|Educational Program|Recordable Media', + '2TVL' => 'Motion Picture & TV|Television Programming|Educational Program|All Electronic Distribution Formats', + '2TVM' => 'Motion Picture & TV|Television Programming|Entertainment Program|Television Broadcast', + '2TEY' => 'Motion Picture & TV|Television Programming|Entertainment Program|Internet Downloadable File', + '2TEZ' => 'Motion Picture & TV|Television Programming|Entertainment Program|All Internet Distribution Formats', + '2TLA' => 'Motion Picture & TV|Television Programming|Entertainment Program|Recordable Media', + '2TVN' => 'Motion Picture & TV|Television Programming|Entertainment Program|All Electronic Distribution Formats', + '2TVY' => 'Motion Picture & TV|Television Programming|Made For TV Movie|Television Broadcast', + '2TLC' => 'Motion Picture & TV|Television Programming|Made For TV Movie|Internet Downloadable File', + '2TLB' => 'Motion Picture & TV|Television Programming|Made For TV Movie|All Internet Distribution Formats', + '2TLD' => 'Motion Picture & TV|Television Programming|Made For TV Movie|Recordable Media', + '2TVP' => 'Motion Picture & TV|Television Programming|Made For TV Movie|All Electronic Distribution Formats', + '2TEF' => 'Motion Picture & TV|Television Programming|News Program, Flash|Television Broadcast', + '2TVS' => 'Motion Picture & TV|Television Programming|News Program|Television Broadcast', + '2TLE' => 'Motion Picture & TV|Television Programming|News Program|Internet Downloadable File', + '2TLF' => 'Motion Picture & TV|Television Programming|News Program|All Internet Distribution Formats', + '2TLG' => 'Motion Picture & TV|Television Programming|News Program|Recordable Media', + '2TVT' => 'Motion Picture & TV|Television Programming|News Program|All Electronic Distribution Formats', + '2TLH' => 'Motion Picture & TV|Television Programming|Non Broadcast Pilot|Recordable Media', + '2TLJ' => 'Motion Picture & TV|Television Programming|Non Broadcast Pilot|Projected Display', + '2TVU' => 'Motion Picture & TV|Television Programming|Non-Profit Program|Television Broadcast', + '2TLK' => 'Motion Picture & TV|Television Programming|Non-Profit Program|Internet Downloadable File', + '2TLL' => 'Motion Picture & TV|Television Programming|Non-Profit Program|All Internet Distribution Formats', + '2TLM' => 'Motion Picture & TV|Television Programming|Non-Profit Program|Recordable Media', + '2TVV' => 'Motion Picture & TV|Television Programming|Non-Profit Program|All Electronic Distribution Formats', + '2TLR' => 'Motion Picture & TV|Television Programming|Prop|Television Broadcast', + '2TLN' => 'Motion Picture & TV|Television Programming|Prop|Internet Downloadable File', + '2TLP' => 'Motion Picture & TV|Television Programming|Prop|All Internet Distribution Formats', + '2TLQ' => 'Motion Picture & TV|Television Programming|Prop|Recordable Media', + '2TVW' => 'Motion Picture & TV|Television Programming|Prop|All Electronic Distribution Formats', + '2TLV' => 'Motion Picture & TV|Television Programming|Set Decor|Television Broadcast', + '2TLS' => 'Motion Picture & TV|Television Programming|Set Decor|Internet Downloadable File', + '2TLT' => 'Motion Picture & TV|Television Programming|Set Decor|All Internet Distribution Formats', + '2TLU' => 'Motion Picture & TV|Television Programming|Set Decor|Recordable Media', + '2TVQ' => 'Motion Picture & TV|Television Programming|Set Decor|All Electronic Distribution Formats', + '2KOA' => 'Motion Picture & TV|Television Programming|Artist\'s Reference, All Types|Projected Display', + '2KOB' => 'Motion Picture & TV|Television Programming|Artist\'s Reference, All Types|Internet Downloadable File', + '2KOI' => 'Motion Picture & TV|Television Programming|Artist\'s Reference, All Types|All Internet Distribution Formats', + '2KOP' => 'Motion Picture & TV|Television Programming|Artist\'s Reference, All Types|Recordable Media', + '2KOR' => 'Motion Picture & TV|Television Programming|Artist\'s Reference, All Types|All Electronic Distribution Formats', + '2KOS' => 'Motion Picture & TV|Television Programming|Artist\'s Reference, All Types|Television Broadcast', + '2TEE' => 'Motion Picture & TV|Television Programming|All Editorial Television Types|Television Broadcast', + '2TLW' => 'Motion Picture & TV|Television Programming|All Editorial Television Types|Internet Downloadable File', + '2TLY' => 'Motion Picture & TV|Television Programming|All Editorial Television Types|All Internet Distribution Formats', + '2TLZ' => 'Motion Picture & TV|Television Programming|All Editorial Television Types|Recordable Media', + '2TVZ' => 'Motion Picture & TV|Television Programming|All Editorial Television Types|All Electronic Distribution Formats', + '2JOT' => 'Motion Picture & TV|Motion Picture|In Theater Commercial|Projected Display', + '2ATE' => 'Motion Picture & TV|Motion Picture|Movie Trailer|Projected Display', + '2MPP' => 'Motion Picture & TV|Motion Picture|Movie Trailer|Internet Downloadable File', + '2BET' => 'Motion Picture & TV|Motion Picture|Movie Trailer|All Internet Distribution Formats', + '2DIG' => 'Motion Picture & TV|Motion Picture|Movie Trailer|Recordable Media', + '2AID' => 'Motion Picture & TV|Motion Picture|Movie Trailer|All Electronic Distribution Formats', + '2MPF' => 'Motion Picture & TV|Motion Picture|Movie Trailer|Television Broadcast', + '2JOG' => 'Motion Picture & TV|Motion Picture|Documentary Film|Projected Display', + '2MPM' => 'Motion Picture & TV|Motion Picture|Documentary Film|Internet Downloadable File', + '2TUG' => 'Motion Picture & TV|Motion Picture|Documentary Film|All Internet Distribution Formats', + '2TRY' => 'Motion Picture & TV|Motion Picture|Documentary Film|Recordable Media', + '2DOC' => 'Motion Picture & TV|Motion Picture|Documentary Film|All Electronic Distribution Formats', + '2MPD' => 'Motion Picture & TV|Motion Picture|Documentary Film|Television Broadcast', + '2FEA' => 'Motion Picture & TV|Motion Picture|Feature Film|Projected Display', + '2MPN' => 'Motion Picture & TV|Motion Picture|Feature Film|Internet Downloadable File', + '2WAG' => 'Motion Picture & TV|Motion Picture|Feature Film|All Internet Distribution Formats', + '2WIN' => 'Motion Picture & TV|Motion Picture|Feature Film|Recordable Media', + '2FIL' => 'Motion Picture & TV|Motion Picture|Feature Film|All Electronic Distribution Formats', + '2MPE' => 'Motion Picture & TV|Motion Picture|Feature Film|Television Broadcast', + '2WIZ' => 'Motion Picture & TV|Motion Picture|Short Film|Projected Display', + '2MPS' => 'Motion Picture & TV|Motion Picture|Short Film|Internet Downloadable File', + '2ASK' => 'Motion Picture & TV|Motion Picture|Short Film|All Internet Distribution Formats', + '2AIM' => 'Motion Picture & TV|Motion Picture|Short Film|Recordable Media', + '2FIT' => 'Motion Picture & TV|Motion Picture|Short Film|All Electronic Distribution Formats', + '2MPI' => 'Motion Picture & TV|Motion Picture|Short Film|Television Broadcast', + '2MPG' => 'Motion Picture & TV|Motion Picture|Prop|Television Broadcast', + '2HOP' => 'Motion Picture & TV|Motion Picture|Prop|Projected Display', + '2MPQ' => 'Motion Picture & TV|Motion Picture|Prop|Internet Downloadable File', + '2HIT' => 'Motion Picture & TV|Motion Picture|Prop|All Internet Distribution Formats', + '2FRY' => 'Motion Picture & TV|Motion Picture|Prop|Recordable Media', + '2EFX' => 'Motion Picture & TV|Motion Picture|Prop|All Electronic Distribution Formats', + '2MPH' => 'Motion Picture & TV|Motion Picture|Set Decor|Television Broadcast', + '2MPR' => 'Motion Picture & TV|Motion Picture|Set Decor|Internet Downloadable File', + '2JAB' => 'Motion Picture & TV|Motion Picture|Set Decor|All Internet Distribution Formats', + '2HUG' => 'Motion Picture & TV|Motion Picture|Set Decor|Recordable Media', + '2HUM' => 'Motion Picture & TV|Motion Picture|Set Decor|All Electronic Distribution Formats', + '2ARC' => 'Motion Picture & TV|Motion Picture|Set Decor|Projected Display', + '2TAP' => 'Motion Picture & TV|Motion Picture|Artist\'s Reference, All Types|Projected Display', + '2MPL' => 'Motion Picture & TV|Motion Picture|Artist\'s Reference, All Types|Internet Downloadable File', + '2NOD' => 'Motion Picture & TV|Motion Picture|Artist\'s Reference, All Types|All Internet Distribution Formats', + '2MIX' => 'Motion Picture & TV|Motion Picture|Artist\'s Reference, All Types|Recordable Media', + '2NAP' => 'Motion Picture & TV|Motion Picture|Artist\'s Reference, All Types|All Electronic Distribution Formats', + '2MPC' => 'Motion Picture & TV|Motion Picture|Artist\'s Reference, All Types|Television Broadcast', + '2FIX' => 'Motion Picture & TV|Motion Picture|All Motion Picture Types|Projected Display', + '2MPJ' => 'Motion Picture & TV|Motion Picture|All Motion Picture Types|Internet Downloadable File', + '2DIP' => 'Motion Picture & TV|Motion Picture|All Motion Picture Types|All Internet Distribution Formats', + '2MOV' => 'Motion Picture & TV|Motion Picture|All Motion Picture Types|Recordable Media', + '2MPA' => 'Motion Picture & TV|Motion Picture|All Motion Picture Types|Television Broadcast', + '2ACT' => 'Motion Picture & TV|Motion Picture|All Motion Picture Types|All Electronic Distribution Formats', + '2MPB' => 'Motion Picture & TV|Music Video|All Music Video Types|Television Broadcast', + '2MPK' => 'Motion Picture & TV|Music Video|All Music Video Types|Internet Downloadable File', + '2BER' => 'Motion Picture & TV|Music Video|All Music Video Types|Recordable Media', + '2BES' => 'Motion Picture & TV|Music Video|All Music Video Types|All Internet Distribution Formats', + '2TVR' => 'Motion Picture & TV|Music Video|All Music Video Types|All Electronic Distribution Formats', + '2AAM' => 'Motion Picture & TV|All Media Types|All Formats|All Distribution Formats', + '2WED' => 'Personal Use|Personal Review|All Review Types|Printed', + '2DIM' => 'Personal Use|Personal Review|All Review Types|All Electronic Distribution Formats', + '2BFR' => 'Personal Use|Website|Web Page, All Types|Internet Website', + '2BFU' => 'Personal Use|Website|Web Page, All Types|All Internet Distribution Formats', + '2BFS' => 'Personal Use|Website|Web Page, All Types|Recordable Media', + '2BFT' => 'Personal Use|Website|Web Page, All Types|All Electronic Distribution Formats', + '2ZIT' => 'Personal Use|Mobile|Wallpaper|Mobile', + '2ZOB' => 'Personal Use|Mobile|All Mobile Types|Mobile', + '2BFN' => 'Personal Use|Art|Art Display, Display Print|Printed', + '2KIT' => 'Personal Use|Art|Art Display, All Art Types|Printed', + '2BFJ' => 'Personal Use|Art|Art Display, All Art Types|Internet Website', + '2BFI' => 'Personal Use|Art|Art Display, All Art Types|All Internet Distribution Formats', + '2BFK' => 'Personal Use|Art|Art Display, All Art Types|Electronic Display', + '2BFH' => 'Personal Use|Art|Art Display, All Art Types|All Electronic Distribution Formats', + '2TAT' => 'Personal Use|Art|Artist\'s Reference, Tattoo|Printed', + '2BFP' => 'Personal Use|Art|Study Print, Educational|Printed', + '2AAU' => 'Personal Use|All Media Types|All Formats|All Distribution Formats', + '2AAB' => 'All Categories|Book|All Book Types|All Distribution Formats', + '2AAD' => 'All Categories|Display|All Display Types|All Distribution Formats', + '2AAT' => 'All Categories|Marketing Materials|All Marketing Material Types|All Distribution Formats', + '2AAH' => 'All Categories|Merchandise|All Merchandise Types|All Distribution Formats', + '2AAL' => 'All Categories|Mobile|All Mobile Types|All Distribution Formats', + '2AAX' => 'All Categories|Motion Picture|All Motion Picture Types|All Distribution Formats', + '2AAV' => 'All Categories|Music Video|All Music Video Types|All Distribution Formats', + '2AAC' => 'All Categories|Periodicals|All Periodical Types|All Distribution Formats', + '2AAR' => 'All Categories|Point of Purchase|All Point Of Purchase Types|All Distribution Formats', + '2AAG' => 'All Categories|Product Packaging|All Product Packaging Types|All Distribution Formats', + '2AAN' => 'All Categories|Television Programming|All Television Programming Types|All Distribution Formats', + '2BAA' => 'All Categories|Website|All Web Page Types|All Distribution Formats', + '2ALL' => 'All Categories|All Media Types|All Formats|All Distribution Formats', + '2UNL' => 'Unlicensed|Not Applicable|Not Applicable|Not Applicable', + # 3P - Placement + '3PAA' => 'Any Placements on All Pages', + '3PXX' => 'Not Applicable or None', + '3PUL' => 'Any Placements', + '3PNQ' => 'Single Placement in Content Body', + '3PPU' => 'Single Placement on Title Page', + '3PQT' => 'Single Placement on Table Of Contents', + '3PQA' => 'Single Placement as Frontispiece', + '3PQI' => 'Single Placement on Preface', + '3PPW' => 'Single Placement as Forward', + '3PSA' => 'Single Placement as Front Matter', + '3PNI' => 'Single Placement as Chapter Opener', + '3PQN' => 'Single Placement as Vignette', + '3PRT' => 'Single Placement on Pop Up', + '3PNZ' => 'Single Placement in Bibliography', + '3PQZ' => 'Single Placement on Index', + '3PQG' => 'Single Placement on Any Interior Page', + '3PSR' => 'Single Placement on Inside Cover', + '3PQE' => 'Single Placement on Spine', + '3PSL' => 'Single Placement on Flap', + '3PSP' => 'Single Placement on Back Cover', + '3PNN' => 'Single Placement on Front Cover', + '3PTL' => 'Single Placement on Front Cover And Back Cover', + '3PPA' => 'Single Placement on Dust Jacket', + '3PTD' => 'Single Placements on Interior, Covers and Jacket', + '3PRY' => 'Multiple Placements in Content Body', + '3PRL' => 'Multiple Placements on Any Interior Pages', + '3PSU' => 'Multiple Placements on Inside Cover', + '3PQS' => 'Multiple Placements on Back Cover', + '3PRZ' => 'Multiple Placements on Front Cover', + '3PTM' => 'Multiple Placements on Front Cover And Back Cover', + '3PTC' => 'Multiple Placements on Dust Jacket', + '3PTE' => 'Multiple Placements on Interior, Covers and Jacket', + '3PST' => 'Single Placement on Any Interior Page', + '3PPK' => 'Single Placement on Inside Cover', + '3PNR' => 'Single Placement on Back Cover', + '3PRB' => 'Single Placement on Front Cover', + '3PUD' => 'Single Placement on Back Cover Or Interior Page', + '3PTB' => 'Single Placement on Front Cover And Interior Page', + '3PUF' => 'Single Placement on Front Cover Or Back Cover', + '3PNF' => 'Single Placement on Front Cover And Back Cover', + '3PTP' => 'Single Placement on Any Cover And Interior Page', + '3PQW' => 'Multiple Placements on Any Interior Pages', + '3PNY' => 'Multiple Placements on Inside Cover', + '3PNC' => 'Multiple Placements on Back Cover', + '3PPV' => 'Multiple Placements on Front Cover', + '3PRG' => 'Multiple Placements on Front Cover And Back Cover', + '3PTN' => 'Multiple Placements on Any Covers And Interior Pages', + '3PQX' => 'Single Placement on Any Interior Page', + '3PTF' => 'Single Placement on Section Opener Page', + '3PNT' => 'Single Placement on Front Page', + '3PTH' => 'Single Placement on Section Opener and Front Page', + '3PSY' => 'Multiple Placements on Any Interior Pages', + '3PTG' => 'Multiple Placements on Section Opener Page', + '3PTI' => 'Multiple Placements on Section Opener and Front Page', + '3PPM' => 'Multiple Placements on Any Pages', + '3PQJ' => 'Single Placement on Wrap Around Cover', + '3PQB' => 'Multiple Placements on Wrap Around Cover', + '3PRS' => 'Single Placement in Content Body', + '3PRI' => 'Single Placement as Colophon', + '3PRC' => 'Single Placement on Table Of Contents', + '3PQQ' => 'Single Placement as Unit Opener', + '3PND' => 'Single Placement as Chapter Opener', + '3PQF' => 'Single Placements in Bibliography', + '3PRN' => 'Single Placement on Any Interior Page', + '3PQH' => 'Single Placement on Back Cover', + '3PSJ' => 'Single Placement on Front Cover', + '3PTJ' => 'Single Placement on Any Covers And Interior Pages', + '3PRD' => 'Multiple Placements in Content Body', + '3PSM' => 'Multiple Placements on Any Interior Pages', + '3PSN' => 'Multiple Placements on Back Cover', + '3PPF' => 'Multiple Placements on Front Cover', + '3PTK' => 'Multiple Placements on Any Covers And Interior Pages', + '3PNE' => 'Single Placement on Front Side', + '3PQC' => 'Single Placement on Back Side', + '3PPL' => 'Single Placement on Both Sides', + '3PNV' => 'Multiple Placements on Front Side', + '3PSB' => 'Multiple Placements on Back Side', + '3PQK' => 'Multiple Placements on Both Sides', + '3PTA' => 'Single Placement on One Side', + '3PNB' => 'Multiple Placements on One Side', + '3PRQ' => 'Single Placement on Screen', + '3PSD' => 'Multiple Placements on Screen', + '3PPH' => 'Single Placement on Inside', + '3PSQ' => 'Single Placement on Back Side', + '3PRU' => 'Single Placement on Front Side', + '3PNP' => 'Single Placement on Both Sides', + '3PRK' => 'Multiple Placements on Inside', + '3PQM' => 'Multiple Placements on Back Side', + '3PNK' => 'Multiple Placements on Front Side', + '3PRV' => 'Multiple Placements on Both Sides', + '3PRH' => 'Single Placement on Any Interior Page', + '3PSH' => 'Single Placement on Back Cover', + '3PQY' => 'Single Placement on Front Cover', + '3PRM' => 'Single Placement on Front Cover And Back Cover', + '3PTQ' => 'Single Placement on Any Covers And Interior Pages', + '3PRF' => 'Multiple Placements on Any Interior Pages', + '3PSF' => 'Multiple Placements on Back Cover', + '3PRJ' => 'Multiple Placements on Front Cover', + '3PPP' => 'Multiple Placements on Front Cover And Interior Pages', + '3PSS' => 'Multiple Placements on Front Cover And Back Cover', + '3PTR' => 'Multiple Placements on Any Covers And Interior Pages', + '3PSW' => 'Single Placement on Item', + '3PNL' => 'Multiple Placements on Item', + '3PPX' => 'Single Placement on Packaging Exterior|Front', + '3PUJ' => 'Single Placement on Packaging Exterior|Back or Side', + '3PPD' => 'Single Placement in Packaging Interior', + '3PSI' => 'Multiple Placements on Packaging Exterior|Front', + '3PUH' => 'Multiple Placements on Packaging Exterior|Back or Side', + '3PPC' => 'Multiple Placements in Packaging Interior', + '3PTS' => 'Single Placement Anywhere On Packaging', + '3PTT' => 'Multiple Placements Anywhere On Packaging', + '3PNW' => 'Single Placement in Body Of Program', + '3PQU' => 'Single Placement in Closing Sequence', + '3PRR' => 'Single Placement in Title Sequence', + '3PTV' => 'Single Placement in Any Part', + '3PPZ' => 'Multiple Placements in Body Of Program', + '3PPN' => 'Multiple Placements in Closing Sequence', + '3PSV' => 'Multiple Placements in Title Sequence', + '3PTU' => 'Multiple Placements in Any Part', + '3PRP' => 'Single Placement in Body Of Advertisement', + '3PPJ' => 'Multiple Placements in Body Of Advertisement', + '3PPR' => 'Single Placement in Any Part', + '3PRW' => 'Multiple Placements in Any Part', + '3PQD' => 'Single Placement on Secondary Page', + '3PQP' => 'Single Placement on Pop Up', + '3PPS' => 'Single Placement on Landing Page', + '3PPG' => 'Single Placement on Splash Page', + '3PPY' => 'Single Placement on Home Page', + '3PTY' => 'Single Placement on Home Page And Secondary Pages', + '3PTW' => 'Single Placement on Any Pages', + '3PSZ' => 'Multiple Placements on Secondary Pages', + '3PNJ' => 'Multiple Placements on Pop Ups', + '3PPI' => 'Multiple Placements on Landing Pages', + '3PNS' => 'Multiple Placements on Splash Pages', + '3PNU' => 'Multiple Placements on Home Page', + '3PPQ' => 'Multiple Placements on Home Page And Secondary Pages', + '3PTZ' => 'Multiple Placements on Any Pages', + '3PPE' => 'Single Placement as Flash', + '3PNM' => 'Single Placement in Body Of Program', + '3PPB' => 'Single Placement in Closing Sequence', + '3PSC' => 'Single Placement in Title Sequence', + '3PUC' => 'Single Placement in Any Part', + '3PNX' => 'Multiple Placements as Flash', + '3PNH' => 'Multiple Placements in Body Of Program', + '3PQL' => 'Multiple Placements in Closing Sequence', + '3PSK' => 'Multiple Placements in Title Sequence', + '3PUB' => 'Multiple Placements in Any Part', + # 4S - Size + '4SAA' => 'Any Size Image|Any Size Media', + '4SXX' => 'Not Applicable or None', + '4SUL' => 'Any Sizes', + '4SJA' => 'Up To 1/16 Page|Any Size Page', + '4SBN' => 'Up To 1/8 Page Image|Any Size Page', + '4SJB' => 'Up To 1/4 Page Image|Any Size Page', + '4SEU' => 'Up To 1/3 Page Image|Any Size Page', + '4SEJ' => 'Up To 1/2 Page Image|Any Size Page', + '4SMG' => 'Up To 3/4 Page Image|Any Size Page', + '4SAG' => 'Up To Full Page Image|Any Size Page', + '4SFI' => 'Up To 2 Page Image|Any Size Pages', + '4SJZ' => 'Up To 3 Page Image|Any Size Pages', + '4SFG' => 'Up To 4 Page Image|Any Size Pages', + '4SBP' => 'Any Size Image|Any Size Page', + '4SGF' => 'Up To 1/4 Page Image|Up To 1/4 Page Ad', + '4SCH' => 'Up To 1/4 Page Image|Up To 1/2 Page Ad', + '4SIY' => 'Up To 1/4 Page Image|Up To Full Page Ad', + '4SCV' => 'Up To 1/2 Page Image|Up To 1/2 Page Ad', + '4SLY' => 'Up To 1/2 Page Image|Up To Full Page Ad', + '4SBU' => 'Up To 1/2 Page Image|Up To 2 Page Ad', + '4SCP' => 'Up To Full Page Image|Up To Full Page Ad', + '4SGL' => 'Up To Full Page Image|Up To 2 Page Ad', + '4SGM' => 'Up To Full Page Image|Any Size Ad', + '4SMB' => 'Up To 2 Page Image|Up To 2 Page Ad', + '4SFR' => 'Up To 2 Page Image|Any Size Ad', + '4SKP' => 'Any Size Image|Any Size Ad', + '4SBK' => 'Up To 1/16 Page Image|Any Size Page', + '4SJS' => 'Up To 1/8 Page Image|Any Size Page', + '4SLV' => 'Up To 1/4 Page Image|Any Size Page', + '4SGW' => 'Up To 1/2 Page Image|Any Size Page', + '4SKD' => 'Up To Full Page Image|Any Size Page', + '4SFJ' => 'Up To 2 Page Image|Any Size Pages', + '4SLA' => 'Any Size Image|Any Size Pages', + '4SKE' => 'Up To 1/8 Page Image|Any Size Page', + '4SAK' => 'Up To 1/4 Page Image|Any Size Page', + '4SLD' => 'Up To 1/2 Page Image|Any Size Page', + '4SJV' => 'Up To Full Page Image|Any Size Page', + '4SJU' => 'Up To 2 Page Image|Any Size Pages', + '4SIW' => 'Any Size Image|Any Size Pages', + '4SAL' => 'Up To 1/4 Area Image|Up To Full Area Media', + '4SJM' => 'Up To 1/2 Area Image|Up To Full Area Media', + '4SKZ' => 'Up To Full Area Image|Up To Full Area Media', + '4SCN' => 'Up To 1/8 Page Image|Up To Full Page Media', + '4SLW' => 'Up To 1/4 Page Image|Up To Full Page Media', + '4SIZ' => 'Up To 1/2 Page Image|Up To Full Page Media', + '4SAC' => 'Up To Full Page Image|Up To Full Page Media', + '4SFX' => 'Up To 8 x 10 Inch Image|Any Size Media', + '4SFP' => 'Up To 11 x 14 Inch Image|Any Size Media', + '4SMV' => 'Any Size Image|Any Size Media', + '4SJD' => 'Up To 21 x 30 cm Image|Any Size Media', + '4SFQ' => 'Up To 30 x 42 cm Image|Any Size Media', + '4SJK' => 'Any Size Image|Up To Full Card', + '4SEQ' => 'Up To 5 x 5 Inch Image|Up To 40 x 40 Inch Media', + '4SIV' => 'Up To 10 x 10 Inch Image|Up To 40 x 40 Inch Media', + '4SCE' => 'Up To 20 x 20 Inch Image|Up To 40 x 40 Inch Media', + '4SDP' => 'Up To 30x 30 Inch Image|Up To 40 x 40 Inch Media', + '4SCT' => 'Up To 40 x 40 Inch Image|Up To 40 x 40 Inch Media', + '4SBJ' => 'Up To 20 x 20 cm Image|Up To 100 x 100 cm Media', + '4SKI' => 'Up To 30 x 30 cm Image|Up To 100 x 100 cm Media', + '4SJW' => 'Up To 40 x 40 cm Image|Up To 100 x 100 cm Media', + '4SCB' => 'Up To 50 x 50 cm Image|Up To 100 x 100 cm Media', + '4SAT' => 'Up To 75 x 75 cm Image|Up To 100 x 100 cm Media', + '4SCI' => 'Up To 100 x 100 cm Image|Up To 100 x 100 cm Media', + '4SGP' => 'Any Size Image|Any Size Media', + '4SIX' => 'Up To 1/4 Area Image|Up To 11 x 36 Foot Display', + '4SHA' => 'Up To 1/4 Area Image|Up To 10 x 40 Foot Display', + '4SGU' => 'Up To 1/4 Area Image|Up To 14 x 48 Foot Display', + '4SAJ' => 'Up To 1/2 Area Image|Up To 11 x 36 Foot Display', + '4SDX' => 'Up To 1/2 Area Image|Up To 10 x 40 Foot Display', + '4SCR' => 'Up To 1/2 Area Image|Up To 14 x 48 Foot Display', + '4SFM' => 'Up To Full Area Image|Up To 11 x 36 Foot Display', + '4SKV' => 'Up To Full Area Image|Up To 10 x 40 Foot Display', + '4SGE' => 'Up To Full Area Image|Up To 14 x 48 Foot Display', + '4SMW' => 'Up To Full Area Image|Any Size Display', + '4SJI' => 'Up To 1/4 Area Image|Up To 4 x 8 Foot Media', + '4SBI' => 'Up To 1/4 Area Image|Up To 10 x 8 Foot Media', + '4SIM' => 'Up To 1/4 Area Image|Up To 12 x 24 Foot Media', + '4SFF' => 'Up To 1/4 Area Image|Any Size Media', + '4SAX' => 'Up To 1/2 Area Image|Up To 4 x 8 Foot Media', + '4SLI' => 'Up To 1/2 Area Image|Up To 10 x 8 Foot Media', + '4SLS' => 'Up To 1/2 Area Image|Up To 12 x 24 Foot Media', + '4SMK' => 'Up To 1/2 Area Image|Any Size Media', + '4SLG' => 'Up To Full Area Image|Up To 4 x 8 Foot Media', + '4SAN' => 'Up To Full Area Image|Up To 10 x 8 Foot Media', + '4SDI' => 'Up To Full Area Image|Up To 12 x 24 Foot Media', + '4SML' => 'Up To Full Area Image|Any Size Media', + '4SKF' => 'Up To 1/4 Area Image|Up To 672 Square Foot Display', + '4SKR' => 'Up To 1/4 Area Image|Up To 1,200 Square Foot Display', + '4SLK' => 'Up To 1/4 Area Image|Up To 2,400 Square Foot Display', + '4SJQ' => 'Up To 1/4 Area Image|Up To 4,800 Square Foot Display', + '4SEY' => 'Up To 1/4 Area Image|Up To 10,000 Square Foot Display', + '4SFE' => 'Up To 1/4 Area Image|Any Size Display', + '4SKS' => 'Up To 1/2 Area Image|Up To 672 Square Foot Display', + '4SGS' => 'Up To 1/2 Area Image|Up To 1,200 Square Foot Display', + '4SMA' => 'Up To 1/2 Area Image|Up To 2,400 Square Foot Display', + '4SAF' => 'Up To 1/2 Area Image|Up To 4,800 Square Foot Display', + '4SAW' => 'Up To 1/2 Area Image|Up To 10,000 Square Foot Display', + '4SAQ' => 'Up To 1/2 Area Image|Any Size Display', + '4SLN' => 'Up To Full Area Image|Up To 672 Square Foot Display', + '4SDS' => 'Up To Full Area Image|Up To 1,200 Square Foot Display', + '4SIT' => 'Up To Full Area Image|Up To 2,400 Square Foot Display', + '4SGX' => 'Up To Full Area Image|Up To 4,800 Square Foot Display', + '4SBD' => 'Up To Full Area Image|Up To 10,000 Square Foot Display', + '4SBF' => 'Up To Full Area Image|Any Size Display', + '4SDD' => 'Up To 1/4 Area Image|Up To 4 Sheet Display', + '4SBC' => 'Up To 1/4 Area Image|Up To 8 Sheet Display', + '4SLJ' => 'Up To 1/4 Area Image|Up To 12 Sheet Display', + '4SAD' => 'Up To 1/4 Area Image|Up To 16 Sheet Display', + '4SEA' => 'Up To 1/4 Area Image|Up To 30 Sheet Display', + '4SCD' => 'Up To 1/4 Area Image|Up To 48 Sheet Display', + '4SKX' => 'Up To 1/4 Area Image|Up To 96 Sheet Display', + '4SMN' => 'Up To 1/4 Area Image|Any Size Display', + '4SDU' => 'Up To 1/2 Area Image|Up To 4 Sheet Display', + '4SBL' => 'Up To 1/2 Area Image|Up To 8 Sheet Display', + '4SER' => 'Up To 1/2 Area Image|Up To 12 Sheet Display', + '4SEN' => 'Up To 1/2 Area Image|Up To 16 Sheet Display', + '4SLP' => 'Up To 1/2 Area Image|Up To 30 Sheet Display', + '4SGQ' => 'Up To 1/2 Area Image|Up To 48 Sheet Display', + '4SDF' => 'Up To 1/2 Area Image|Up To 96 Sheet Display', + '4SMP' => 'Up To 1/2 Area Image|Any Size Display', + '4SFL' => 'Up To Full Area Image|Up To 4 Sheet Display', + '4SFS' => 'Up To Full Area Image|Up To 8 Sheet Display', + '4SIF' => 'Up To Full Area Image|Up To 12 Sheet Display', + '4SIS' => 'Up To Full Area Image|Up To 16 Sheet Display', + '4SFD' => 'Up To Full Area Image|Up To 30 Sheet Display', + '4SFU' => 'Up To Full Area Image|Up To 48 Sheet Display', + '4SDK' => 'Up To Full Area Image|Up To 96 Sheet Display', + '4SMQ' => 'Up To Full Area Image|Any Size Display', + '4SKA' => 'Up To 1/4 Area Image|Up To 20 x 30 Inch Media', + '4SIK' => 'Up To 1/4 Area Image|Up To 24 x 36 Inch Media', + '4SFK' => 'Up To 1/4 Area Image|Up To 30 x 40 Inch Media', + '4SDV' => 'Up To 1/4 Area Image|Up To 40 x 60 Inch Media', + '4SMX' => 'Up To 1/4 Area Image|Any Size Media', + '4SEF' => 'Up To 1/4 Area Image|Up To A1 Media', + '4SCJ' => 'Up To 1/4 Area Image|Up To B1 Media', + '4SJG' => 'Up To 1/4 Area Image|Up To A0 Media', + '4SIP' => 'Up To 1/4 Area Image|Up To B0 Media', + '4SKU' => 'Up To 1/2 Area Image|Up To 20 x 30 Inch Media', + '4SAS' => 'Up To 1/2 Area Image|Up To 24 x 36 Inch Media', + '4SGB' => 'Up To 1/2 Area Image|Up To 30 x 40 Inch Media', + '4SBQ' => 'Up To 1/2 Area Image|Up To 40 x 60 Inch Media', + '4SMY' => 'Up To 1/2 Area Image|Any Size Media', + '4SKJ' => 'Up To 1/2 Area Image|Up To A1 Media', + '4SAM' => 'Up To 1/2 Area Image|Up To B1 Media', + '4SDZ' => 'Up To 1/2 Area Image|Up To A0 Media', + '4SGT' => 'Up To 1/2 Area Image|Up To B0 Media', + '4SKN' => 'Up To Full Area Image|Up To 20 x 30 Inch Media', + '4SIQ' => 'Up To Full Area Image|Up To 24 x 36 Inch Media', + '4SLT' => 'Up To Full Area Image|Up To 30 x 40 Inch Media', + '4SDN' => 'Up To Full Area Image|Up To 40 x 60 Inch Media', + '4SMZ' => 'Up To Full Area Image|Any Size Media', + '4SBH' => 'Up To Full Area Image|Up To A1 Media', + '4SLU' => 'Up To Full Area Image|Up To B1 Media', + '4SEL' => 'Up To Full Area Image|Up To A0 Media', + '4SBM' => 'Up To Full Area Image|Up To B0 Media', + '4SIH' => 'Up To 1/4 Area Image|Up To 24 x 30 Inch Display', + '4SKW' => 'Up To 1/4 Area Image|Up To 30 x 40 Inch Display', + '4SDM' => 'Up To 1/4 Area Image|Up To 27 x 85 Inch Display', + '4SJP' => 'Up To 1/4 Area Image|Up To 60 x 40 Inch Display', + '4SIB' => 'Up To 1/4 Area Image|Up To 69 x 48 Inch Display', + '4SDH' => 'Up To 1/4 Area Image|Up To 27 x 141 Inch Display', + '4SJL' => 'Up To 1/4 Area Image|Up To 30 x 240 Inch Display', + '4SLQ' => 'Up To 1/4 Area Image|Up To B1 Display', + '4SLL' => 'Up To 1/4 Area Image|Up To A0 Display', + '4SEI' => 'Up To 1/4 Area Image|Up To B0 Display', + '4SBA' => 'Up To 1/2 Area Image|Up To 24 x 30 Inch Display', + '4SCL' => 'Up To 1/2 Area Image|Up To 30 x 40 Inch Display', + '4SIC' => 'Up To 1/2 Area Image|Up To 27 x 85 Inch Display', + '4SBY' => 'Up To 1/2 Area Image|Up To 60 x 40 Inch Display', + '4SFZ' => 'Up To 1/2 Area Image|Up To 27 x 141 Inch Display', + '4SAU' => 'Up To 1/2 Area Image|Up To 26 x 241 Inch Display', + '4SBR' => 'Up To 1/2 Area Image|Up To 30 x 240 Inch Display', + '4SCC' => 'Up To 1/2 Area Image|Up To B1 Display', + '4SAB' => 'Up To 1/2 Area Image|Up To A0 Display', + '4SLX' => 'Up To 1/2 Area Image|Up To B0 Display', + '4SGI' => 'Up To Full Area Image|Up To 24 x 30 Inch Display', + '4SBT' => 'Up To Full Area Image|Up To 30 x 40 Inch Display', + '4SGK' => 'Up To Full Area Image|Up To 27 x 85 Inch Display', + '4SIL' => 'Up To Full Area Image|Up To 60 x 40 Inch Display', + '4SAH' => 'Up To Full Area Image|Up To 69 x 48 Inch Display', + '4SGA' => 'Up To Full Area Image|Up To 27 x 141 Inch Display', + '4SDR' => 'Up To Full Area Image|Up To 26 x 241 Inch Display', + '4SID' => 'Up To Full Area Image|Up To 30 x 240 Inch Display', + '4SCX' => 'Up To Full Area Image|Any Size Display', + '4SGR' => 'Up To Full Area Image|Up To B1 Display', + '4SEC' => 'Up To Full Area Image|Up To A0 Display', + '4SET' => 'Up To Full Area Image|Up To B0 Display', + '4SKT' => 'Up To 1/4 Area Image|Up To 43 x 62 Inch Display', + '4SEW' => 'Up To 1/4 Area Image|Up To 48 x 71 Inch Display', + '4SEH' => 'Up To 1/4 Area Image|Up To 43 x 126 Inch Display', + '4SBS' => 'Up To 1/4 Area Image|Up To 83 x 135 Inch Display', + '4SMR' => 'Up To 1/4 Area Image|Any Size Display', + '4SJX' => 'Up To 1/4 Area Image|Up To B0 Display', + '4SAY' => 'Up To 1/2 Area Image|Up To 43 x 62 Inch Display', + '4SKY' => 'Up To 1/2 Area Image|Up To 48 x 71 Inch Display', + '4SBV' => 'Up To 1/2 Area Image|Up To 43 x 126 Inch Display', + '4SEP' => 'Up To 1/2 Area Image|Up To 83 x 135 Inch Display', + '4SNG' => 'Up To 1/2 Area Image|Any Size Display', + '4SAZ' => 'Up To 1/2 Area Image|Up To B0 Display', + '4SDW' => 'Up To Full Area Image|Up To 43 x 62 Inch Display', + '4SFW' => 'Up To Full Area Image|Up To 48 x 71 Inch Display', + '4SGN' => 'Up To Full Area Image|Up To 43 x 126 Inch Display', + '4SBX' => 'Up To Full Area Image|Up To 83 x 135 Inch Display', + '4SNO' => 'Up To Full Area Image|Any Size Display', + '4SKK' => 'Up To Full Area Image|Up To B0 Display', + '4SAP' => 'Up To 1/4 Screen Image|Up To 32 Inch Screen', + '4SEK' => 'Up To 1/4 Screen Image|Up To 63 Inch Screen', + '4SLZ' => 'Up To 1/4 Screen Image|Up To 10 Diagonal Foot Screen', + '4SIE' => 'Up To 1/4 Screen Image|Up To 30 Diagonal Foot Screen', + '4SEM' => 'Up To 1/4 Screen Image|Up To 100 Diagonal Foot Screen', + '4SGJ' => 'Up To 1/2 Screen Image|Up To 32 Inch Screen', + '4SBE' => 'Up To 1/2 Screen Image|Up To 63 Inch Screen', + '4SIJ' => 'Up To 1/2 Screen Image|Up To 10 Diagonal Foot Screen', + '4SGY' => 'Up To 1/2 Screen Image|Up To 30 Diagonal Foot Screen', + '4SDQ' => 'Up To 1/2 Screen Image|Up To 100 Diagonal Foot Screen', + '4SBZ' => 'Up To Full Screen Image|Up To 32 Inch Screen', + '4SJF' => 'Up To Full Screen Image|Up To 63 Inch Screen', + '4SLE' => 'Up To Full Screen Image|Up To 10 Diagonal Foot Screen', + '4SJY' => 'Up To Full Screen Image|Up To 30 Diagonal Foot Screen', + '4SKC' => 'Up To Full Screen Image|Up To 100 Diagonal Foot Screen', + '4SDL' => 'Up To Full Screen Image|Any Size Screen', + '4SFA' => 'Up To 1/4 Area Image|Any Size Item', + '4SCG' => 'Up To 1/2 Area Image|Any Size Item', + '4SLR' => 'Up To Full Area Image|Any Size Item', + '4SGZ' => 'Up To 3 x 4.5 Inch Image|Any Size Media', + '4SFH' => 'Up To 5 x 7 Inch Image|Any Size Media', + '4SCU' => 'Up To 6 x 9 Inch Image|Any Size Media', + '4SLF' => 'Up To 8 x 10 Inch Image|Any Size Media', + '4SAV' => 'Up To 8 x 12 Inch Image|Any Size Media', + '4SKB' => 'Up To 11 x 14 Inch Image|Any Size Media', + '4SAE' => 'Up To 14 x 20 Inch Image|Any Size Media', + '4SEZ' => 'Up To 16 x 20 Inch Image|Any Size Media', + '4SDB' => 'Up To 20 x 24 Inch Image|Any Size Media', + '4SDG' => 'Up To 24 x 30 Inch Image|Any Size Media', + '4SDE' => 'Up To 30 x 40 Inch Image|Any Size Media', + '4SJE' => 'Up To 38 x 50 Inch Image|Any Size Media', + '4SCM' => 'Up To 40 x 60 Inch Image|Any Size Media', + '4SIN' => 'Any Size Image|Any Size Media', + '4SDA' => 'Up To 11 x 15 cm Image|Any Size Media', + '4SME' => 'Up To 13 x 18 cm Image|Any Size Media', + '4SFC' => 'Up To 15 x 21 cm Image|Any Size Media', + '4SIG' => 'Up To 18 x 25 cm Image|Any Size Media', + '4SEV' => 'Up To 21 x 30 cm Image|Any Size Media', + '4SJJ' => 'Up To 25 x 36 cm Image|Any Size Media', + '4SMD' => 'Up To 25 x 36 cm Image|Any Size Media', + '4SLH' => 'Up To 30 x 42 cm Image|Any Size Media', + '4SEE' => 'Up To 42 x 60 cm Image|Any Size Media', + '4SDC' => 'Up To 50 x 71 cm Image|Any Size Media', + '4SDY' => 'Up To 60 x 85 cm Image|Any Size Media', + '4SCQ' => 'Up To 70 x 100 cm Image|Any Size Media', + '4SMF' => 'Up To 85 x 119 cm Image|Any Size Media', + '4SJC' => 'Up To 100 x 142 cm Image|Any Size Media', + '4SFN' => 'Up To 1/4 Area Image|Up To 25 x 13 Inch Media', + '4SLC' => 'Up To 1/4 Area Image|Up To 50 x 24 Inch Media', + '4SIR' => 'Up To 1/4 Area Image|Up To 26 x 53 Inch Media', + '4SDT' => 'Up To 1/4 Area Image|Up To 46 x 60 Inch Media', + '4SFB' => 'Up To 1/4 Area Image|Up To 138 x 53 Inch Media', + '4SGV' => 'Up To 1/2 Area Image|Up To 25 x 13 Inch Media', + '4SJR' => 'Up To 1/2 Area Image|Up To 50 x 24 Inch Media', + '4SKL' => 'Up To 1/2 Area Image|Up To 26 x 53 Inch Media', + '4SEX' => 'Up To 1/2 Area Image|Up To 46 x 60 Inch Media', + '4SBW' => 'Up To 1/2 Area Image|Up To 138 x 53 Inch Media', + '4SJN' => 'Up To Full Area Image|Up To 25 x 13 Inch Media', + '4SKH' => 'Up To Full Area Image|Up To 50 x 24 Inch Media', + '4SKM' => 'Up To Full Area Image|Up To 26 x 53 Inch Media', + '4SGG' => 'Up To Full Area Image|Up To 46 x 60 Inch Media', + '4SDJ' => 'Up To Full Area Image|Up To 138 x 53 Inch Media', + '4SCZ' => 'Up To 1/4 Screen Image|Up To 15 Inch Screen', + '4SCS' => 'Up To 1/4 Screen Image|Up To 21 Inch Screen', + '4SMS' => 'Up To 1/4 Screen Image|Any Size Screen', + '4SED' => 'Up To 1/2 Screen Image|Up To 15 Inch Screen', + '4SFV' => 'Up To 1/2 Screen Image|Up To 21 Inch Screen', + '4SMT' => 'Up To 1/2 Screen Image|Any Size Screen', + '4SCY' => 'Up To Full Screen Image|Up To 15 Inch Screen', + '4SEB' => 'Up To Full Screen Image|Up To 21 Inch Screen', + '4SMU' => 'Up To Full Screen Image|Any Size Screen', + '4SGH' => 'Up To 1/4 Screen Image|Any Size Screen', + '4SGC' => 'Up To 1/2 Screen Image|Any Size Screen', + '4SES' => 'Up To Full Screen Image|Any Size Screen', + '4SKQ' => 'Up To 1/4 Area Image|Up To 180 x 150 Pixels Ad', + '4SCA' => 'Up To 1/4 Area Image|Up To 468 x 60 Pixels Ad', + '4SCK' => 'Up To 1/4 Area Image|Up To 728 x 90 Pixels Ad', + '4SCF' => 'Up To 1/4 Area Image|Up To 300 x 600 Pixels Ad', + '4SIU' => 'Up To 1/4 Area Image|Up To Full Screen Ad', + '4SAI' => 'Up To 1/2 Area Image|Up To 180 x 150 Pixels Ad', + '4SII' => 'Up To 1/2 Area Image|Up To 468 x 60 Pixels Ad', + '4SCW' => 'Up To 1/2 Area Image|Up To 728 x 90 Pixels Ad', + '4SLM' => 'Up To 1/2 Area Image|Up To 300 x 600 Pixels Ad', + '4SFT' => 'Up To 1/2 Area Image|Up To Full Screen Ad', + '4SJT' => 'Up To Full Area Image|Up To 180 x 150 Pixels Ad', + '4SGD' => 'Up To Full Area Image|Up To 468 x 60 Pixels Ad', + '4SAR' => 'Up To Full Area Image|Up To 728 x 90 Pixels Ad', + '4SEG' => 'Up To Full Area Image|Up To 300 x 600 Pixels Ad', + '4SBG' => 'Any Size Image|Up To Full Screen Ad', + '4SMH' => 'Up To 150 x 150 Pixels Image|Any Size Screen', + '4SMJ' => 'Up To 300 x 600 Pixels Image|Any Size Screen', + '4SKG' => 'Any Size Image|Any Size Screen', + # 5V - Version + '5VAA' => 'All Versions', + '5VXX' => 'Not Applicable or None', + '5VUL' => 'Any Versions', + '5VVM' => 'Single Print Version', + '5VVB' => 'Multiple Print Versions', + '5VUP' => 'Single Version', + '5VUY' => 'Multiple Versions', + '5VVC' => 'Single Paperback Edition', + '5VUK' => 'Single Hardcover Edition', + '5VVH' => 'Single Edition in All Binding Formats', + '5VVK' => 'Multiple Paperback Editions', + '5VUU' => 'Multiple Hardcover Editions', + '5VVL' => 'Multiple Editions in All Binding Formats', + '5VUZ' => 'Single Issue', + '5VVJ' => 'Multiple Issues', + '5VUG' => 'Single Edition', + '5VVG' => 'Multiple Editions', + # 6Q - Quantity + '6QAA' => 'Any Quantity', + '6QXX' => 'Not Applicable or None', + '6QUL' => 'Any Quantity', + '6QAU' => 'One|Print Run', + '6QDA' => 'Up To 10|Print Run', + '6QEY' => 'Up To 100|Print Run', + '6QBA' => 'Up To 1,000|Print Run', + '6QCW' => 'Up To 5,000|Print Run', + '6QEC' => 'Up To 10,000|Print Run', + '6QFD' => 'Up To 25,000|Print Run', + '6QBH' => 'Up To 40,000|Print Run', + '6QFR' => 'Up To 50,000|Print Run', + '6QDQ' => 'Up To 100,000|Print Run', + '6QBG' => 'Up To 250,000|Print Run', + '6QEN' => 'Up To 500,000|Print Run', + '6QDL' => 'Any Quantity Of|Print Run', + '6QBU' => 'One|Copy', + '6QDR' => 'Up To 10|Total Circulation', + '6QFU' => 'Up To 25|Total Circulation', + '6QFV' => 'Up To 50|Total Circulation', + '6QEJ' => 'Up To 100|Total Circulation', + '6QFW' => 'Up To 250|Total Circulation', + '6QFY' => 'Up To 500|Total Circulation', + '6QCD' => 'Up To 1,000|Total Circulation', + '6QFZ' => 'Up To 2,500|Total Circulation', + '6QAT' => 'Up To 5,000|Total Circulation', + '6QBB' => 'Up To 10,000|Total Circulation', + '6QFS' => 'Up To 25,000|Total Circulation', + '6QAN' => 'Up To 50,000|Total Circulation', + '6QCS' => 'Up To 100,000|Total Circulation', + '6QDT' => 'Up To 250,000|Total Circulation', + '6QCI' => 'Up To 500,000|Total Circulation', + '6QCN' => 'Up To 1 Million|Total Circulation', + '6QCK' => 'Up To 2 Million|Total Circulation', + '6QCL' => 'Up To 3 Million|Total Circulation', + '6QAV' => 'Up To 5 Million|Total Circulation', + '6QDK' => 'Up To 10 Million|Total Circulation', + '6QGB' => 'Up To 25 Million|Total Circulation', + '6QGC' => 'Up To 50 Million|Total Circulation', + '6QEV' => 'Any Quantity Of|Circulation', + '6QFI' => 'One|Reprint', + '6QDG' => 'Up To 10|Reprints', + '6QCB' => 'Up To 100|Reprints', + '6QAY' => 'Up To 1,000|Reprints', + '6QBF' => 'Up To 10,000|Reprints', + '6QCU' => 'Any Quantity Of|Reprints', + '6QEH' => 'One|Print Run', + '6QDH' => 'Up To 10|Print Run', + '6QAZ' => 'Up To 100|Print Run', + '6QFB' => 'Up To 1,000|Print Run', + '6QAQ' => 'Up To 5,000|Print Run', + '6QEG' => 'Up To 10,000|Print Run', + '6QBV' => 'Up To 25,000|Print Run', + '6QCG' => 'Up To 50,000|Print Run', + '6QAH' => 'Up To 100,000|Print Run', + '6QDE' => 'Up To 250,000|Print Run', + '6QDJ' => 'Up To 500,000|Print Run', + '6QCZ' => 'Up To 1 Million|Print Run', + '6QCT' => 'Up To 2 Million|Print Run', + '6QAR' => 'Up To 3 Million|Print Run', + '6QDC' => 'Up To 5 Million|Print Run', + '6QDU' => 'Up To 10 Million|Print Run', + '6QEU' => 'Any Quantity Of|Print Run', + '6QFT' => 'One|Copy', + '6QDI' => 'One|Display', + '6QDY' => 'Up To 5|Displays', + '6QFJ' => 'Up To 10|Displays', + '6QEL' => 'Up To 25|Displays', + '6QBD' => 'Up To 50|Displays', + '6QAE' => 'Up To 100|Displays', + '6QCE' => 'Up To 250|Displays', + '6QAP' => 'Up To 500|Displays', + '6QBY' => 'Up To 1,000|Displays', + '6QBC' => 'Up To 2,500|Displays', + '6QET' => 'Up To 5,000|Displays', + '6QFF' => 'Up To 10,000|Displays', + '6QDS' => 'Up To 25,000|Displays', + '6QES' => 'Up To 50,000|Displays', + '6QDZ' => 'Up To 100,000|Displays', + '6QBW' => 'Up To 250,000|Displays', + '6QAX' => 'Up To 500,000|Displays', + '6QDB' => 'Up To 1 Million|Displays', + '6QEK' => 'Up To 2 Million|Displays', + '6QFA' => 'Up To 3 Million|Displays', + '6QCJ' => 'Up To 5 Million|Displays', + '6QFQ' => 'Any Quantity Of|Displays', + '6QCX' => 'One|Display', + '6QBP' => 'Up To 5|Displays', + '6QAL' => 'Up To 10|Displays', + '6QDX' => 'Up To 25|Displays', + '6QEM' => 'Up To 50|Displays', + '6QEA' => 'Up To 100|Displays', + '6QFG' => 'Up To 250|Displays', + '6QAG' => 'Up To 500|Displays', + '6QEI' => 'Any Quantity Of|Displays', + '6QEP' => 'One|Copy', + '6QEX' => 'Two|Copies', + '6QCF' => 'Three|Copies', + '6QBK' => 'Four|Copies', + '6QBL' => 'Five|Copies', + '6QCP' => 'Up To 10|Copies', + '6QDV' => 'Up To 50|Copies', + '6QCM' => 'Up To 100|Copies', + '6QAI' => 'Up To 500|Copies', + '6QAS' => 'Up To 1,000|Copies', + '6QDW' => 'Up To 5,000|Copies', + '6QCQ' => 'Up To 10,000|Copies', + '6QAM' => 'Any Quantity Of|Copies', + '6QBE' => 'One|Copy', + '6QFC' => 'Up To 5|Copies', + '6QEB' => 'Up To 10|Copies', + '6QGD' => 'Up To 25|Copies', + '6QGE' => 'Up To 50|Copies', + '6QCR' => 'Up To 100|Copies', + '6QCA' => 'Up To 250|Copies', + '6QBS' => 'Up To 500|Copies', + '6QDF' => 'Up To 1,000|Copies', + '6QEW' => 'Up To 2,500|Copies', + '6QFE' => 'Up To 5,000|Copies', + '6QEE' => 'Up To 10,000|Copies', + '6QBT' => 'Up To 25,000|Copies', + '6QDP' => 'Up To 50,000|Copies', + '6QDN' => 'Up To 100,000|Copies', + '6QFN' => 'Any Quantity Of|Copies', + '6QFH' => 'Up To 10,000|Viewers', + '6QCV' => 'Up To 100,000|Viewers', + '6QBN' => 'Up To 1 Million|Viewers', + '6QFP' => 'Up To 10 Million|Viewers', + '6QBI' => 'Up To 100 Million|Viewers', + '6QBQ' => 'Any Quantity Of|Viewers', + '6QAB' => 'Up To 10,000|Impressions', + '6QBR' => 'Up To 100,000|Impressions', + '6QCY' => 'Up To 1 Million|Impressions', + '6QBM' => 'Up To 10 Million|Impressions', + '6QFM' => 'Any Quantity Of|Impressions', + '6QDD' => 'Up To 100|Viewers', + '6QEQ' => 'Up To 1,000|Viewers', + '6QEZ' => 'Up To 10,000|Viewers', + '6QBZ' => 'Up To 100,000|Viewers', + '6QCC' => 'Any Quantity Of|Viewers', + '6QCH' => 'One|Copy', + '6QAW' => 'Up To 5|Copies', + '6QAK' => 'Up To 10|Copies', + '6QFL' => 'Up To 100|Copies', + '6QAC' => 'Up To 1,000|Copies', + '6QAD' => 'Up To 10,000|Copies', + '6QEF' => 'Up To 25,000|Copies', + '6QAF' => 'Up To 50,000|Copies', + '6QED' => 'Up To 100,000|Copies', + '6QFK' => 'Up To 250,000|Copies', + '6QDM' => 'Up To 500,000|Copies', + '6QBX' => 'Up To 1 Million|Copies', + '6QER' => 'Any Quantity Of|Copies', + # 7D - Duration + '7DAA' => 'In Perpetuity', + '7DXX' => 'Not Applicable or None', + '7DUL' => 'Any Durations', + '7DUT' => 'Up To 10 Years', + '7DUS' => 'Up To 6 Months', + '7DUV' => 'Up To 6 Months', + '7DUW' => 'Up To 1 Year', + '7DUY' => 'Up To 3 Years', + '7DUZ' => 'Up To 5 Years', + '7DUQ' => 'Up To 10 Years', + '7DXC' => 'Up To 1 Day', + '7DXL' => 'Up To 1 Week', + '7DXH' => 'Life Of Publication', + '7DYF' => 'Up To 1 Day', + '7DWL' => 'Up To 1 Week', + '7DYJ' => 'Up To 2 Weeks', + '7DXF' => 'Up To 1 Month', + '7DZF' => 'Up To 2 Months', + '7DWC' => 'Up To 3 Months', + '7DWW' => 'Up To 6 Months', + '7DZA' => 'Up To 1 Year', + '7DZP' => 'Up To 2 Years', + '7DZJ' => 'Up To 3 Years', + '7DZK' => 'Up To 5 Years', + '7DZL' => 'Up To 10 Years', + '7DWS' => 'Life Of Publication', + '7DXZ' => 'Up To 3 Months', + '7DXW' => 'Up To 6 Months', + '7DXD' => 'Up To 1 Year', + '7DYS' => 'Life Of Publication', + '7DYM' => 'Up To 1 Year', + '7DWE' => 'Up To 2 Years', + '7DYL' => 'Up To 3 Years', + '7DWV' => 'Up To 5 Years', + '7DWB' => 'Up To 7 Years', + '7DYY' => 'Up To 10 Years', + '7DWG' => 'Up To 15 Years', + '7DXA' => 'Full Term Of Copyright', + '7DXT' => 'Up To 1 Day', + '7DXB' => 'Up To 1 Week', + '7DYI' => 'Up To 1 Month', + '7DWI' => 'Up To 1 Year', + '7DZM' => 'Up To 2 Years', + '7DZN' => 'Up To 3 Years', + '7DYV' => 'Life Of Event', + '7DXQ' => 'Up To 1 Year', + '7DZH' => 'Up To 2 Years', + '7DXP' => 'Up To 3 Years', + '7DYD' => 'Up To 5 Years', + '7DWU' => 'Up To 7 Years', + '7DXG' => 'Up To 10 Years', + '7DXV' => 'Up To 15 Years', + '7DWR' => 'In Perpetuity', + '7DXM' => 'Up To 1 Year', + '7DWK' => 'Up To 2 Years', + '7DXS' => 'In Perpetuity', + '7DYG' => 'Up To 1 Day', + '7DZD' => 'Up To 1 Week', + '7DWP' => 'Up To 13 Weeks', + '7DYT' => 'Up To 26 Weeks', + '7DWT' => 'Up To 52 Weeks', + '7DYA' => 'Up To 5 Years', + '7DXR' => 'Up To 10 Years', + '7DXI' => 'In Perpetuity', + '7DYW' => 'Up To 1 Day', + '7DYP' => 'Up To 1 Week', + '7DWJ' => 'Up To 1 Month', + '7DYE' => 'Up To 3 Months', + '7DYX' => 'Up To 6 Months', + '7DXE' => 'Up To 1 Year', + '7DWZ' => 'Up To 2 Years', + '7DZB' => 'Up To 3 Years', + '7DYN' => 'Up To 5 Years', + '7DYC' => 'Up To 10 Years', + '7DXY' => 'Up To 15 Years', + '7DWM' => 'In Perpetuity', + '7DYZ' => 'Up To 1 Day', + '7DXK' => 'Up To 1 Month', + '7DWY' => 'Up To 3 Months', + '7DYB' => 'Up To 6 Months', + '7DZG' => 'Up To 1 Year', + '7DYK' => 'Up To 2 Years', + '7DYQ' => 'In Perpetuity', + # 8R - Region + '8RAA' => 'Worldwide', + '8RXX' => 'Not Applicable or None', + '8RUL' => 'Any Regions', + '8RWA' => 'Broad International Region|Worldwide Excluding Northern America', + '8RWB' => 'Broad International Region|Worldwide Excluding USA', + '8RWC' => 'Broad International Region|Worldwide Excluding USA and Europe', + '8RWD' => 'Broad International Region|Worldwide Excluding Europe', + '8RWE' => 'Broad International Region|Worldwide Excluding USA and UK', + '8RWF' => 'Broad International Region|Worldwide Excluding UK', + '8RWG' => 'Broad International Region|All English Speaking Countries', + '8RWH' => 'Broad International Region|All English Speaking Countries Excluding USA', + '8RAH' => 'Broad International Region|All Spanish Speaking Countries', + '8RWI' => 'Broad International Region|All Spanish Speaking Countries Excluding USA', + '8RFY' => 'Broad International Region|All Americas', + '8REK' => 'Broad International Region|Europe, Middle East and Africa', + '8RWJ' => 'Broad International Region|USA, Canada and Mexico', + '8RQV' => 'Northern America|One Minor City, Up To 250,000 Population', + '8RQT' => 'Northern America|One Major City, Over 250,000 Population', + '8RQU' => 'Northern America|One Metropolitan Area, Adjoining Cities', + '8RRJ' => 'Northern America|One State Or Province', + '8RYK' => 'Northern America|Up To 3 States Or Provinces', + '8RCD' => 'Northern America|Up To 5 States Or Provinces', + '8RGJ' => 'Northern America|All Northern American Countries', + '8RCE' => 'Northern America|USA and Canada', + '8RHQ' => 'Northern America|USA', + '8RCA' => 'Northern America|Canada', + '8RHJ' => 'Northern America|USA-Central', + '8RHP' => 'Northern America|USA-Midwest', + '8RHR' => 'Northern America|USA-Northeast', + '8RHS' => 'Northern America|USA-Pacific Northwest', + '8RHW' => 'Northern America|USA-Southeast', + '8RHX' => 'Northern America|USA-Southwest', + '8RIA' => 'Northern America|USA-West', + '8RHY' => 'Northern America|USA-Minor Outlying Islands', + '8RUK' => 'Northern America|USA-All Territories, Protectorates, Dependencies, Outposts', + '8RBK' => 'Northern America|Canada-British Columbia', + '8RUM' => 'Northern America|Canada-Prairies', + '8RUN' => 'Northern America|Canada-Atlantic Provinces', + '8RCJ' => 'Northern America|Canada-Ontario', + '8RUP' => 'Northern America|Canada-Quebec', + '8RUQ' => 'Northern America|Canada-Northern Territories', + '8RBM' => 'Northern America|Bermuda', + '8RGL' => 'Northern America|Greenland', + '8RPM' => 'Northern America|Saint Pierre and Miquelon', + '8RQL' => 'Europe|One Minor City, Up To 250,000 Population', + '8RQJ' => 'Europe|One Major City, Over 250,000 Population', + '8RQK' => 'Europe|One Metropolitan Area, Adjoining Cities', + '8RRF' => 'Europe|One State Or Province', + '8RYF' => 'Europe|Up To 3 States Or Provinces', + '8RYG' => 'Europe|Up To 5 States Or Provinces', + '8RDU' => 'Europe|All Europe', + '8REL' => 'Europe|All European Union Countries', + '8REI' => 'Europe|All United Kingdom', + '8REJ' => 'Europe|All Western Europe', + '8RED' => 'Europe|All Northern Europe', + '8REB' => 'Europe|All European Mediterranean Countries', + '8REA' => 'Europe|All Eastern Europe', + '8RDW' => 'Europe|All Baltic States', + '8RDX' => 'Europe|All Benelux', + '8RDY' => 'Europe|All Caucasian States', + '8REF' => 'Europe|All Scandinavia', + '8RAX' => 'Europe|Aland Islands', + '8RAL' => 'Europe|Albania', + '8RAD' => 'Europe|Andorra', + '8RAM' => 'Europe|Armenia', + '8RAT' => 'Europe|Austria', + '8RAZ' => 'Europe|Azerbaijan', + '8RBY' => 'Europe|Belarus', + '8RBE' => 'Europe|Belgium', + '8RBA' => 'Europe|Bosnia and Herzegovina', + '8RBG' => 'Europe|Bulgaria', + '8RDR' => 'Europe|Croatia', + '8RCY' => 'Europe|Cyprus', + '8RCZ' => 'Europe|Czech Republic', + '8RDK' => 'Europe|Denmark', + '8REM' => 'Europe|England', + '8REE' => 'Europe|Estonia', + '8RDS' => 'Europe|Faeroe Islands', + '8RFI' => 'Europe|Finland', + '8RFR' => 'Europe|France', + '8RGE' => 'Europe|Georgia', + '8RDE' => 'Europe|Germany', + '8RGI' => 'Europe|Gibraltar', + '8RGR' => 'Europe|Greece', + '8RGG' => 'Europe|Guernsey', + '8RHU' => 'Europe|Hungary', + '8RIS' => 'Europe|Iceland', + '8RIE' => 'Europe|Ireland', + '8RIM' => 'Europe|Isle Of Man', + '8RIT' => 'Europe|Italy', + '8RJE' => 'Europe|Jersey', + '8RLV' => 'Europe|Latvia', + '8RLI' => 'Europe|Liechtenstein', + '8RLT' => 'Europe|Lithuania', + '8RLU' => 'Europe|Luxembourg', + '8RMK' => 'Europe|Macedonia', + '8RMT' => 'Europe|Malta', + '8RMD' => 'Europe|Moldova', + '8RMC' => 'Europe|Monaco', + '8RNL' => 'Europe|Netherlands', + '8RUH' => 'Europe|Northern Ireland', + '8RNO' => 'Europe|Norway', + '8RPL' => 'Europe|Poland', + '8RPT' => 'Europe|Portugal', + '8RRO' => 'Europe|Romania', + '8RRU' => 'Europe|Russian Federation', + '8RSM' => 'Europe|San Marino', + '8REN' => 'Europe|Scotland', + '8RCS' => 'Europe|Serbia and Montenegro', + '8RSK' => 'Europe|Slovakia', + '8RSI' => 'Europe|Slovenia', + '8RES' => 'Europe|Spain', + '8RSE' => 'Europe|Sweden', + '8RCH' => 'Europe|Switzerland', + '8RUA' => 'Europe|Ukraine', + '8RUI' => 'Europe|Wales', + '8RDT' => 'Europe|Vatican City State', + '8RQF' => 'Asia|One Minor City, Up To 250,000 Population', + '8RQD' => 'Asia|One Major City, Over 250,000 Population', + '8RQE' => 'Asia|One Metropolitan Area, Adjoining Cities', + '8RRC' => 'Asia|One State Or Province', + '8RAV' => 'Asia|Up To 3 States Or Provinces', + '8RYD' => 'Asia|Up To 5 States Or Provinces', + '8RDB' => 'Asia|All Asia', + '8RDC' => 'Asia|All Central Asia', + '8RCP' => 'Asia|All Eastern Asia', + '8RDG' => 'Asia|All Southern Asia', + '8RDH' => 'Asia|All Southeastern Asia', + '8RBD' => 'Asia|Bangladesh', + '8RBT' => 'Asia|Bhutan', + '8RBN' => 'Asia|Brunei Darussalam', + '8RKH' => 'Asia|Cambodia', + '8RCN' => 'Asia|All China', + '8RUB' => 'Asia|China-East', + '8RAY' => 'Asia|China-Northeast', + '8RUC' => 'Asia|China-North', + '8RUD' => 'Asia|China-South Central', + '8RUF' => 'Asia|China-Southwest', + '8RHK' => 'Asia|Hong Kong', + '8RIN' => 'Asia|India', + '8RID' => 'Asia|Indonesia', + '8RJP' => 'Asia|Japan', + '8RKZ' => 'Asia|Kazakhstan', + '8RKP' => 'Asia|North Korea', + '8RKR' => 'Asia|South Korea', + '8RKG' => 'Asia|Kyrgyzstan', + '8RLA' => 'Asia|Laos', + '8RMO' => 'Asia|Macao', + '8RMY' => 'Asia|Malaysia', + '8RMV' => 'Asia|Maldives', + '8RMN' => 'Asia|Mongolia', + '8RMM' => 'Asia|Myanmar', + '8RNP' => 'Asia|Nepal', + '8RPK' => 'Asia|Pakistan', + '8RPH' => 'Asia|Philippines', + '8RSG' => 'Asia|Singapore', + '8RTW' => 'Asia|Taiwan', + '8RTJ' => 'Asia|Tajikistan', + '8RTH' => 'Asia|Thailand', + '8RDA' => 'Asia|Tibet', + '8RTL' => 'Asia|Timor-Leste', + '8RTM' => 'Asia|Turkmenistan', + '8RUZ' => 'Asia|Uzbekistan', + '8RVN' => 'Asia|Viet Nam', + '8RQO' => 'Latin America and Caribbean|One Minor City, Up To 250,000 Population', + '8RQM' => 'Latin America and Caribbean|One Major City, Over 250,000 Population', + '8RQN' => 'Latin America and Caribbean|One Metropolitan Area, Adjoining Cities', + '8RRG' => 'Latin America and Caribbean|One State Or Province', + '8RYH' => 'Latin America and Caribbean|Up To 3 States Or Provinces', + '8RYI' => 'Latin America and Caribbean|Up To 5 States Or Provinces', + '8RAC' => 'Latin America and Caribbean|All Latin America and Caribbean', + '8RUJ' => 'Latin America and Caribbean|All Latin America', + '8RFZ' => 'Latin America and Caribbean|All Caribbean', + '8RFS' => 'Latin America and Caribbean|All South America', + '8RGC' => 'Latin America and Caribbean|All Central America', + '8RFT' => 'Latin America and Caribbean|All Andean Countries', + '8RFU' => 'Latin America and Caribbean|All Southern Cone', + '8RFV' => 'Latin America and Caribbean|All Amazonia', + '8RAI' => 'Latin America and Caribbean|Anguilla', + '8RAG' => 'Latin America and Caribbean|Antigua and Barbuda', + '8RAR' => 'Latin America and Caribbean|Argentina', + '8RAW' => 'Latin America and Caribbean|Aruba', + '8RBS' => 'Latin America and Caribbean|Bahamas', + '8RBB' => 'Latin America and Caribbean|Barbados', + '8RBZ' => 'Latin America and Caribbean|Belize', + '8RGZ' => 'Latin America and Caribbean|Bequia', + '8RBO' => 'Latin America and Caribbean|Bolivia', + '8RHA' => 'Latin America and Caribbean|Bonaire', + '8RBR' => 'Latin America and Caribbean|Brazil', + '8RHB' => 'Latin America and Caribbean|British Virgin Islands', + '8RKY' => 'Latin America and Caribbean|Cayman Islands', + '8RCL' => 'Latin America and Caribbean|Chile', + '8RCO' => 'Latin America and Caribbean|Colombia', + '8RCR' => 'Latin America and Caribbean|Costa Rica', + '8RCU' => 'Latin America and Caribbean|Cuba', + '8RHC' => 'Latin America and Caribbean|Curacao', + '8RDM' => 'Latin America and Caribbean|Dominica', + '8RDO' => 'Latin America and Caribbean|Dominican Republic', + '8REC' => 'Latin America and Caribbean|Ecuador', + '8RSV' => 'Latin America and Caribbean|El Salvador', + '8RFK' => 'Latin America and Caribbean|Falkland Islands, Malvinas', + '8RGF' => 'Latin America and Caribbean|French Guiana', + '8RGD' => 'Latin America and Caribbean|Grenada', + '8RGP' => 'Latin America and Caribbean|Guadeloupe', + '8RGT' => 'Latin America and Caribbean|Guatemala', + '8RGY' => 'Latin America and Caribbean|Guyana', + '8RHT' => 'Latin America and Caribbean|Haiti', + '8RHN' => 'Latin America and Caribbean|Honduras', + '8RJM' => 'Latin America and Caribbean|Jamaica', + '8RMQ' => 'Latin America and Caribbean|Martinique', + '8RMX' => 'Latin America and Caribbean|Mexico', + '8RMS' => 'Latin America and Caribbean|Montserrat', + '8RAN' => 'Latin America and Caribbean|Netherlands Antilles', + '8RNI' => 'Latin America and Caribbean|Nicaragua', + '8RPA' => 'Latin America and Caribbean|Panama', + '8RPY' => 'Latin America and Caribbean|Paraguay', + '8RFQ' => 'Latin America and Caribbean|Patagonia', + '8RPE' => 'Latin America and Caribbean|Peru', + '8RPR' => 'Latin America and Caribbean|Puerto Rico', + '8RHD' => 'Latin America and Caribbean|Saba', + '8RHE' => 'Latin America and Caribbean|Saint Barthelemy', + '8RHF' => 'Latin America and Caribbean|Saint Eustatius', + '8RKN' => 'Latin America and Caribbean|Saint Kitts and Nevis', + '8RLC' => 'Latin America and Caribbean|Saint Lucia', + '8RHG' => 'Latin America and Caribbean|Saint Martin', + '8RVC' => 'Latin America and Caribbean|Saint Vincent and The Grenadines', + '8RSR' => 'Latin America and Caribbean|Suriname', + '8RTT' => 'Latin America and Caribbean|Trinidad and Tobago', + '8RTC' => 'Latin America and Caribbean|Turks and Caicos Islands', + '8RHH' => 'Latin America and Caribbean|U.S. Virgin Islands', + '8RUY' => 'Latin America and Caribbean|Uruguay', + '8RVE' => 'Latin America and Caribbean|Venezuela', + '8RQZ' => 'Oceania|One Minor City, Up To 250,000 Population', + '8RQW' => 'Oceania|One Major City, Over 250,000 Population', + '8RQY' => 'Oceania|One Metropolitan Area, Adjoining Cities', + '8RRK' => 'Oceania|One State Or Province', + '8RYL' => 'Oceania|Up To 3 States Or Provinces', + '8RYM' => 'Oceania|Up To 5 States Or Provinces', + '8RCT' => 'Oceania|All Oceania', + '8RUR' => 'Oceania|All Australia and New Zealand', + '8RUS' => 'Oceania|All Oceania excluding Australia and New Zealand', + '8RAU' => 'Oceania|Australia', + '8RAS' => 'Oceania|American Samoa', + '8RCX' => 'Oceania|Christmas Island', + '8RCC' => 'Oceania|Cocos, Keeling Islands', + '8RKM' => 'Oceania|Comoros', + '8RCK' => 'Oceania|Cook Islands', + '8RFJ' => 'Oceania|Fiji', + '8RPF' => 'Oceania|French Polynesia', + '8RGU' => 'Oceania|Guam', + '8RKI' => 'Oceania|Kiribati', + '8RMG' => 'Oceania|Madagascar', + '8RMH' => 'Oceania|Marshall Islands', + '8RMU' => 'Oceania|Mauritius', + '8RFM' => 'Oceania|Micronesia', + '8RFF' => 'Oceania|Midway Islands', + '8RNR' => 'Oceania|Nauru', + '8RNC' => 'Oceania|New Caledonia', + '8RNZ' => 'Oceania|New Zealand', + '8RNU' => 'Oceania|Niue', + '8RNF' => 'Oceania|Norfolk Island', + '8RMP' => 'Oceania|Northern Mariana Islands', + '8RPW' => 'Oceania|Palau', + '8RPG' => 'Oceania|Papua New Guinea', + '8RPN' => 'Oceania|Pitcairn Islands', + '8RFH' => 'Oceania|Rapa Nui, Easter Island', + '8RWS' => 'Oceania|Samoa', + '8RSC' => 'Oceania|Seychelles', + '8RSB' => 'Oceania|Solomon Islands', + '8RLK' => 'Oceania|Sri Lanka', + '8RFL' => 'Oceania|Tahiti', + '8RTK' => 'Oceania|Tokelau', + '8RTO' => 'Oceania|Tonga', + '8RTV' => 'Oceania|Tuvalu', + '8RVU' => 'Oceania|Vanuatu', + '8RFP' => 'Oceania|Wallis and Futuna', + '8RQS' => 'Middle East|One Minor City, Up To 250,000 Population', + '8RQP' => 'Middle East|One Major City, Over 250,000 Population', + '8RQR' => 'Middle East|One Metropolitan Area, Adjoining Cities', + '8RRH' => 'Middle East|One State Or Province', + '8RYJ' => 'Middle East|Up To 3 States Or Provinces', + '8RBX' => 'Middle East|Up To 5 States Or Provinces', + '8REX' => 'Middle East|All Middle East', + '8REY' => 'Middle East|All Middle Eastern Gulf States', + '8RIB' => 'Middle East|All Middle Eastern Mediterranean Countries', + '8RAF' => 'Middle East|Afghanistan', + '8RBH' => 'Middle East|Bahrain', + '8RIR' => 'Middle East|Iran', + '8RIQ' => 'Middle East|Iraq', + '8RIL' => 'Middle East|Israel', + '8RJO' => 'Middle East|Jordan', + '8RKW' => 'Middle East|Kuwait', + '8RLB' => 'Middle East|Lebanon', + '8ROM' => 'Middle East|Oman', + '8REV' => 'Middle East|Palestinian Authority', + '8RQA' => 'Middle East|Qatar', + '8RSA' => 'Middle East|Saudi Arabia', + '8RSY' => 'Middle East|Syria', + '8RTR' => 'Middle East|Turkey', + '8RAE' => 'Middle East|United Arab Emirates', + '8RYE' => 'Middle East|Yemen', + '8RQC' => 'Africa|One Minor City, Up To 250,000 Population', + '8RQX' => 'Africa|One Major City, Over 250,000 Population', + '8RQB' => 'Africa|One Metropolitan Area, Adjoining Cities', + '8RRB' => 'Africa|One State Or Province', + '8RYB' => 'Africa|Up To 3 States Or Provinces', + '8RYC' => 'Africa|Up To 5 States Or Provinces', + '8RAJ' => 'Africa|All Africa', + '8RAK' => 'Africa|All African Mediterranean Countries', + '8RAP' => 'Africa|All Central Africa', + '8RAQ' => 'Africa|All Eastern Africa', + '8RBC' => 'Africa|All Southern Africa', + '8RBL' => 'Africa|All Western Africa', + '8RDZ' => 'Africa|Algeria', + '8RAO' => 'Africa|Angola', + '8RBP' => 'Africa|Ascension Island', + '8RBJ' => 'Africa|Benin', + '8RBW' => 'Africa|Botswana', + '8RBF' => 'Africa|Burkina Faso', + '8RBI' => 'Africa|Burundi', + '8RCM' => 'Africa|Cameroon', + '8RCV' => 'Africa|Cape Verde', + '8RCF' => 'Africa|Central African Republic', + '8RTD' => 'Africa|Chad', + '8RCG' => 'Africa|Congo', + '8RCI' => 'Africa|Cote D\'Ivoire', + '8RDJ' => 'Africa|Djibouti', + '8REG' => 'Africa|Egypt', + '8RGQ' => 'Africa|Equatorial Guinea', + '8RER' => 'Africa|Eritrea', + '8RET' => 'Africa|Ethiopia', + '8RGA' => 'Africa|Gabon', + '8RGM' => 'Africa|Gambia', + '8RGH' => 'Africa|Ghana', + '8RGN' => 'Africa|Guinea', + '8RGW' => 'Africa|Guinea-Bissau', + '8RKE' => 'Africa|Kenya', + '8RLS' => 'Africa|Lesotho', + '8RLR' => 'Africa|Liberia', + '8RLY' => 'Africa|Libyan Arab Jamahiriya', + '8RMW' => 'Africa|Malawi', + '8RML' => 'Africa|Mali', + '8RMR' => 'Africa|Mauritania', + '8RYT' => 'Africa|Mayotte', + '8RMA' => 'Africa|Morocco', + '8RMZ' => 'Africa|Mozambique', + '8RNA' => 'Africa|Namibia', + '8RNE' => 'Africa|Niger', + '8RNG' => 'Africa|Nigeria', + '8RRE' => 'Africa|Reunion', + '8RRW' => 'Africa|Rwanda', + '8RSH' => 'Africa|Saint Helena', + '8RST' => 'Africa|Sao Tome and Principe', + '8RSN' => 'Africa|Senegal', + '8RSL' => 'Africa|Sierra Leone', + '8RSO' => 'Africa|Somalia', + '8RZA' => 'Africa|South Africa', + '8RSD' => 'Africa|Sudan', + '8RSZ' => 'Africa|Swaziland', + '8RTZ' => 'Africa|Tanzania, United Republic Of', + '8RTG' => 'Africa|Togo', + '8RTN' => 'Africa|Tunisia', + '8RUG' => 'Africa|Uganda', + '8REH' => 'Africa|Western Sahara', + '8RZM' => 'Africa|Zambia', + '8RZW' => 'Africa|Zimbabwe', + '8RBQ' => 'Other Regions|Antarctica', + '8RCB' => 'Other Regions|All Arctic and Arctic Ocean Islands', + '8RFB' => 'Other Regions|All Northern Atlantic Ocean Islands', + '8RFW' => 'Other Regions|All Southern Atlantic Ocean Islands', + '8RFX' => 'Other Regions|All Southern Indian Ocean Islands', + '8REU' => 'Other Regions|All French Southern Territories', + '8RDQ' => 'Other Regions|All British Indian Ocean Territories', + # 8L - Language + '8LAA' => 'All Languages', + '8LXX' => 'Not Applicable or None', + '8LUL' => 'Any Languages', + '8LOL' => 'Any One Language', + '8LEN' => 'English', + '8LAF' => 'Afrikaans', + '8LAR' => 'Arabic', + '8LBO' => 'Bosnian', + '8LBU' => 'Bulgarian', + '8LCA' => 'Chinese-Cantonese', + '8LCH' => 'Chinese-Mandarin', + '8LCP' => 'Chinese-Other', + '8LCR' => 'Croatian', + '8LCZ' => 'Czech', + '8LDA' => 'Danish', + '8LDU' => 'Dutch', + '8LES' => 'Estonian', + '8LFI' => 'Finnish', + '8LFR' => 'French', + '8LGE' => 'German', + '8LGR' => 'Greek', + '8LHE' => 'Hebrew', + '8LHI' => 'Hindi', + '8LHU' => 'Hungarian', + '8LIC' => 'Icelandic', + '8LIN' => 'Indonesian', + '8LIG' => 'Irish Gaelic', + '8LIT' => 'Italian', + '8LJA' => 'Japanese', + '8LKO' => 'Korean', + '8LLA' => 'Latvian', + '8LMG' => 'Mongolian', + '8LNO' => 'Norwegian', + '8LPO' => 'Polish', + '8LPR' => 'Portuguese', + '8LRO' => 'Romanian', + '8LRU' => 'Russian', + '8LSG' => 'Scottish Gaelic', + '8LSE' => 'Serbian', + '8LSI' => 'Sindhi', + '8LSV' => 'Slovakian', + '8LSL' => 'Slovenian', + '8LSP' => 'Spanish', + '8LSH' => 'Swahili', + '8LSZ' => 'Swazi', + '8LSW' => 'Swedish', + '8LTA' => 'Tagalog', + '8LTH' => 'Thai', + '8LTU' => 'Turkish', + '8LUR' => 'Ukrainian', + '8LYI' => 'Yiddish', + '8LOT' => 'Other Language', + # 8I - Industry + '8IAA' => 'All Industries', + '8IXX' => 'Not Applicable or None', + '8IUL' => 'Any Industries', + '8IAD' => 'Advertising and Marketing', + '8IAG' => 'Agriculture, Farming and Horticulture', + '8IAT' => 'Airline Transportation', + '8IAL' => 'Alcohol', + '8IAR' => 'Architecture and Engineering', + '8IAE' => 'Arts and Entertainment', + '8IAU' => 'Automotive', + '8IAV' => 'Aviation', + '8IBA' => 'Baby and Childcare', + '8IBE' => 'Beauty and Personal Care', + '8IBI' => 'Biotechnology', + '8IBR' => 'Broadcast Media', + '8ICO' => 'Business Consulting and Services', + '8ICH' => 'Chemicals', + '8ICE' => 'Communications Equipment and Services', + '8IHS' => 'Computer Hardware, Software and Peripherals', + '8ICC' => 'Construction and Contracting', + '8IAP' => 'Consumer Appliances and Electronics', + '8ICG' => 'Counseling', + '8IEC' => 'Ecology, Environmental and Conservation', + '8IED' => 'Education', + '8IEM' => 'Employment Training and Recruitment', + '8IEN' => 'Energy, Utilities and Fuel', + '8IEV' => 'Events and Conventions', + '8IFA' => 'Fashion', + '8IFI' => 'Financial Services and Banking', + '8IFB' => 'Food and Beverage Processing', + '8IFL' => 'Food and Beverage Retail', + '8IFS' => 'Food Services', + '8IFO' => 'Forestry and Wood Products', + '8IFR' => 'Freight and Warehousing', + '8IFU' => 'Furniture', + '8IGA' => 'Games, Toys and Hobbies', + '8IGI' => 'Gaming Industry', + '8IGL' => 'Gardening and Landscaping', + '8IGO' => 'Government and Politics', + '8IGR' => 'Graphic Design', + '8IGC' => 'Greeting Card', + '8IHI' => 'Heavy Industry', + '8IHO' => 'Home Improvement', + '8IHH' => 'Hotels and Hospitality', + '8IHA' => 'Household Appliances', + '8IHC' => 'Household Cleaning Products', + '8IIM' => 'Industry and Manufacturing', + '8IIT' => 'Information Technologies', + '8IIN' => 'Insurance', + '8IIS' => 'Internet Services', + '8ILS' => 'Legal Services', + '8IME' => 'Medical and Healthcare', + '8IMS' => 'Microelectronics and Semiconductors', + '8IMW' => 'Military and Weapons', + '8IMM' => 'Mining and Metals', + '8IMU' => 'Music', + '8INP' => 'Not For Profit, Social, Charitable', + '8IOP' => 'Office Products', + '8IOG' => 'Oil and Gas', + '8IOI' => 'Other Industry', + '8IPO' => 'Personal Use Only', + '8IPP' => 'Pet Products and Services', + '8IPS' => 'Pharmaceuticals and Supplements', + '8IPT' => 'Printing and Reprographics', + '8IPR' => 'Public Relations', + '8IPM' => 'Publishing Media', + '8IRE' => 'Real Estate', + '8IRR' => 'Religion and Religious Services', + '8ISM' => 'Retail Sales and Marketing', + '8IRM' => 'Retail Merchandise', + '8ISS' => 'Safety and Security', + '8ISC' => 'Sciences', + '8ISH' => 'Shipping', + '8ISO' => 'Software', + '8ISF' => 'Sports, Fitness and Recreation', + '8ITE' => 'Telecommunications', + '8ITX' => 'Textiles and Apparel', + '8ITB' => 'Tobacco', + '8ITR' => 'Travel and Tourism', + # 9E - Exclusivity + '9EXX' => 'Not Applicable or None', + '9ENE' => 'Non-Exclusive', + '9EXC' => 'All Exclusive', + '9EIN' => 'Exclusivity For Industry', + '9EME' => 'Exclusivity For Media', + '9ELA' => 'Exclusivity For Language', + '9ERE' => 'Exclusivity For Region' +); + +# PLUS License Data Format 1.2.0 (plus) (ref 1) +%Image::ExifTool::PLUS::XMP = ( + %Image::ExifTool::XMP::xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-plus', 2 => 'Author' }, + NAMESPACE => 'plus', + NOTES => q{ + PLUS (Picture Licensing Universal System) License Data Format 2.0.1 XMP + tags. Note that all controlled-vocabulary tags in this table (ie. tags with + a fixed set of values) have raw values which begin with + "http://ns.useplus.org/ldf/vocab/", but to reduce clutter this prefix has + been removed from the values shown below, and from the values read and + written with the -n option. See L<http://ns.useplus.org/> for the complete + specification. + }, + Version => { Name => 'PLUSVersion' }, + Licensee => { + FlatName => '', + Struct => \%plusLicensee, + List => 'Seq', + }, + EndUser => { + FlatName => '', + Struct => \%plusEndUser, + List => 'Seq', + }, + Licensor => { + FlatName => '', + Struct => \%plusLicensor, + List => 'Seq', + }, + LicensorNotes => { Writable => 'lang-alt' }, + MediaSummaryCode => { + SeparateTable => 'MediaMatrix', + PrintConv => \%mediaMatrix, + }, + LicenseStartDate => { %Image::ExifTool::XMP::dateTimeInfo, Groups => { 2 => 'Time'} }, + LicenseEndDate => { %Image::ExifTool::XMP::dateTimeInfo, Groups => { 2 => 'Time'} }, + MediaConstraints => { Writable => 'lang-alt' }, + RegionConstraints => { Writable => 'lang-alt' }, + ProductOrServiceConstraints => { Writable => 'lang-alt' }, + ImageFileConstraints => { + List => 'Bag', + %plusVocab, + PrintConv => { + 'IF-MFN' => 'Maintain File Name', + 'IF-MID' => 'Maintain ID in File Name', + 'IF-MMD' => 'Maintain Metadata', + 'IF-MFT' => 'Maintain File Type', + }, + }, + ImageAlterationConstraints => { + List => 'Bag', + %plusVocab, + PrintConv => { + 'AL-CRP' => 'No Cropping', + 'AL-FLP' => 'No Flipping', + 'AL-RET' => 'No Retouching', + 'AL-CLR' => 'No Colorization', + 'AL-DCL' => 'No De-Colorization', + 'AL-MRG' => 'No Merging', + }, + }, + ImageDuplicationConstraints => { + %plusVocab, + PrintConv => { + 'DP-NDC' => 'No Duplication Constraints', + 'DP-LIC' => 'Duplication Only as Necessary Under License', + 'DP-NOD' => 'No Duplication', + }, + }, + ModelReleaseStatus => { + %plusVocab, + PrintConv => { + 'MR-NON' => 'None', + 'MR-NAP' => 'Not Applicable', + 'MR-UMR' => 'Unlimited Model Releases', + 'MR-LMR' => 'Limited or Incomplete Model Releases', + }, + }, + ModelReleaseID => { List => 'Bag' }, + MinorModelAgeDisclosure => { + %plusVocab, + PrintConv => { + 'AG-UNK' => 'Age Unknown', + 'AG-A25' => 'Age 25 or Over', + 'AG-A24' => 'Age 24', + 'AG-A23' => 'Age 23', + 'AG-A22' => 'Age 22', + 'AG-A21' => 'Age 21', + 'AG-A20' => 'Age 20', + 'AG-A19' => 'Age 19', + 'AG-A18' => 'Age 18', + 'AG-A17' => 'Age 17', + 'AG-A16' => 'Age 16', + 'AG-A15' => 'Age 15', + 'AG-U14' => 'Age 14 or Under', + }, + }, + PropertyReleaseStatus => { + %plusVocab, + PrintConv => { + 'PR-NON' => 'None', + 'PR-NAP' => 'Not Applicable', + 'PR-UPR' => 'Unlimited Property Releases', + 'PR-LPR' => 'Limited or Incomplete Property Releases', + }, + }, + PropertyReleaseID => { List => 'Bag' }, + OtherConstraints => { Writable => 'lang-alt' }, + CreditLineRequired => { + %plusVocab, + PrintConv => { + 'CR-NRQ' => 'Not Required', + 'CR-COI' => 'Credit on Image', + 'CR-CAI' => 'Credit Adjacent To Image', + 'CR-CCA' => 'Credit in Credits Area', + }, + }, + AdultContentWarning => { + %plusVocab, + PrintConv => { + 'CW-NRQ' => 'Not Required', + 'CW-AWR' => 'Adult Content Warning Required', + 'CW-UNK' => 'Unknown', + }, + }, + OtherLicenseRequirements => { Writable => 'lang-alt' }, + TermsAndConditionsText => { Writable => 'lang-alt' }, + TermsAndConditionsURL => { }, + OtherConditions => { Writable => 'lang-alt' }, + ImageType => { + %plusVocab, + PrintConv => { + 'TY-PHO' => 'Photographic Image', + 'TY-ILL' => 'Illustrated Image', + 'TY-MCI' => 'Multimedia or Composited Image', + 'TY-VID' => 'Video', + 'TY-OTR' => 'Other', + }, + }, + LicensorImageID => { }, + FileNameAsDelivered => { }, + ImageFileFormatAsDelivered => { + %plusVocab, + PrintConv => { + 'FF-JPG' => 'JPEG Interchange Formats (JPG, JIF, JFIF)', + 'FF-TIF' => 'Tagged Image File Format (TIFF)', + 'FF-GIF' => 'Graphics Interchange Format (GIF)', + 'FF-RAW' => 'Proprietary RAW Image Format', + 'FF-DNG' => 'Digital Negative (DNG)', + 'FF-EPS' => 'Encapsulated PostScript (EPS)', + 'FF-BMP' => 'Windows Bitmap (BMP)', + 'FF-PSD' => 'Photoshop Document (PSD)', + 'FF-PIC' => 'Macintosh Picture (PICT)', + 'FF-PNG' => 'Portable Network Graphics (PNG)', + 'FF-WMP' => 'Windows Media Photo (HD Photo)', + 'FF-OTR' => 'Other', + }, + }, + ImageFileSizeAsDelivered => { + %plusVocab, + PrintConv => { + 'SZ-U01' => 'Up to 1 MB', + 'SZ-U10' => 'Up to 10 MB', + 'SZ-U30' => 'Up to 30 MB', + 'SZ-U50' => 'Up to 50 MB', + 'SZ-G50' => 'Greater than 50 MB', + }, + }, + CopyrightStatus => { + %plusVocab, + PrintConv => { + 'CS-PRO' => 'Protected', + 'CS-PUB' => 'Public Domain', + 'CS-UNK' => 'Unknown', + }, + }, + CopyrightRegistrationNumber => { }, + FirstPublicationDate => { %Image::ExifTool::XMP::dateTimeInfo, Groups => { 2 => 'Time'} }, + CopyrightOwner => { + FlatName => '', + Struct => \%plusCopyrightOwner, + List => 'Seq', + }, + CopyrightOwnerImageID => { }, + ImageCreator => { + FlatName => '', + Struct => \%plusImageCreator, + List => 'Seq', + }, + ImageCreatorImageID => { }, + ImageSupplier => { + FlatName => '', + Struct => \%plusImageSupplier, + List => 'Seq', + }, + ImageSupplierImageID => { }, + LicenseeImageID => { }, + LicenseeImageNotes => { Writable => 'lang-alt' }, + OtherImageInfo => { Writable => 'lang-alt' }, + LicenseID => { }, + LicensorTransactionID => { List => 'Bag' }, + LicenseeTransactionID => { List => 'Bag' }, + LicenseeProjectReference=> { List => 'Bag' }, + LicenseTransactionDate => { %Image::ExifTool::XMP::dateTimeInfo, Groups => { 2 => 'Time'} }, + Reuse => { + %plusVocab, + PrintConv => { + 'RE-REU' => 'Repeat Use', + 'RE-NAP' => 'Not Applicable', + }, + }, + OtherLicenseDocuments => { List => 'Bag' }, + OtherLicenseInfo => { Writable => 'lang-alt' }, + # Note: these are Bag's of lang-alt lists -- a nested list tag! + Custom1 => { List => 'Bag', Writable => 'lang-alt' }, + Custom2 => { List => 'Bag', Writable => 'lang-alt' }, + Custom3 => { List => 'Bag', Writable => 'lang-alt' }, + Custom4 => { List => 'Bag', Writable => 'lang-alt' }, + Custom5 => { List => 'Bag', Writable => 'lang-alt' }, + Custom6 => { List => 'Bag', Writable => 'lang-alt' }, + Custom7 => { List => 'Bag', Writable => 'lang-alt' }, + Custom8 => { List => 'Bag', Writable => 'lang-alt' }, + Custom9 => { List => 'Bag', Writable => 'lang-alt' }, + Custom10 => { List => 'Bag', Writable => 'lang-alt' }, + DataMining => { + %plusVocab, + PrintConv => { + 'DMI-UNSPECIFIED' => 'Unspecified - no prohibition defined', + 'DMI-ALLOWED' => 'Allowed', + 'DMI-PROHIBITED-AIMLTRAINING' => 'Prohibited for AI/ML training', + 'DMI-PROHIBITED-GENAIMLTRAINING' => 'Prohibited for Generative AI/ML training', + 'DMI-PROHIBITED-EXCEPTSEARCHENGINEINDEXING' => 'Prohibited except for search engine indexing', + 'DMI-PROHIBITED' => 'Prohibited', + 'DMI-PROHIBITED-SEECONSTRAINT' => 'Prohibited, see plus:OtherConstraints', + 'DMI-PROHIBITED-SEEEMBEDDEDRIGHTSEXPR' => 'Prohibited, see iptcExt:EmbdEncRightsExpr', + 'DMI-PROHIBITED-SEELINKEDRIGHTSEXPR' => 'Prohibited, see iptcExt:LinkedEncRightsExpr', + }, + }, +); + +#------------------------------------------------------------------------------ +# Validate Media Summary Code +# Inputs: 0) Media Usage Code +# Returns: true if OK, false on severe error +# - issues warning for detected format problems +# - repairs some repairable problems +sub ValidateMediaSummary($) +{ + my $val = shift; + + my @a = split /\|/, $val; + @a >= 4 and $a[0] eq '' or warn("Not a valid Media Summary Code\n"), return 0; + $a[1] eq 'PLUS' or warn("Unrecognized Media Usage standard\n"), return 0; + $a[2] =~ /^V(\d+)/ or warn("Unrecognized Media Usage version\n"); + $a[3] =~ /^U(\d+)/ or warn("Invalid Media Usage count\n"), return 0; + my $numUsages = $1; + my ($i, $j); + unless ($numUsages == @a - 4) { + warn("Fixed incorrect number of Media Usages\n"); + $numUsages = @a - 4; + $a[3] = sprintf('U%.3d', $numUsages); + } + for ($i=1; $i<=$numUsages; ++$i) { + my $usage = $a[$i + 3]; + $usage =~ /^1I([A-Z])([A-Z])/ or warn("Missing Media Usage $i item count\n"), return 0; + length($usage) % 4 and warn("Incorrect Media Usage $i length\n"), return 0; + my $numItems = (ord($1)-65) * 26 + ord($2)-65 + 1; + unless (length($usage) == 4 * ($numItems + 1)) { + $numItems = length($usage) / 4 - 1; + warn("Fixed incorrect Media Usage $i item count\n"); + $a[$i+3] = '1I' . chr(65 + int($numItems / 26)) . chr($numItems % 26) . substr($usage, 4); + } + for ($j=1; $j<=$numItems; ++$j) { + my $item = substr($usage, $j*4, 4); + $item =~ /^\d[A-Z]{3}$/ or warn(qq(Invalid item "$item" for Media Usage $i\n)), return 0; + } + } + $_[0] = join('|', @a) . '|' if $Image::ExifTool::evalWarning; + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PLUS - PLUS (Picture Licensing Universal System) tags + +=head1 DESCRIPTION + +Definitions for PLUS (Picture Licensing Universal System) tags. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.useplus.com/useplus/standards.asp> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PLUS Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/PNG.pm b/ExifTool/lib/Image/ExifTool/PNG.pm new file mode 100644 index 0000000..d39c3ce --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PNG.pm @@ -0,0 +1,1683 @@ +#------------------------------------------------------------------------------ +# File: PNG.pm +# +# Description: Read and write PNG meta information +# +# Revisions: 06/10/2005 - P. Harvey Created +# 06/23/2005 - P. Harvey Added MNG and JNG support +# 09/16/2005 - P. Harvey Added write support +# +# References: 1) http://www.libpng.org/pub/png/spec/1.2/ +# 2) http://www.faqs.org/docs/png/ +# 3) http://www.libpng.org/pub/mng/ +# 4) http://www.libpng.org/pub/png/spec/register/ +# 5) ftp://ftp.simplesystems.org/pub/png/documents/pngext-1.4.0-pdg.html +# 6) ftp://ftp.simplesystems.org/pub/png/documents/pngext-1.5.0.html +# +# Notes: Writing meta information in PNG images is a pain in the butt +# for a number of reasons: One biggie is that you have to +# decompress then decode the ASCII/hex profile information before +# you can edit it, then you have to ASCII/hex-encode, recompress +# and calculate a CRC before you can write it out again. gaaaak. +# +# Although XMP is allowed after the IDAT chunk according to the +# PNG specifiction, some apps (Apple Spotlight and Preview for +# OS X 10.8.5 and Adobe Photoshop CC 14.0) ignore it unless it +# comes before IDAT. As of version 11.58, ExifTool uses a 2-pass +# writing algorithm to allow it to be compatible with XMP after +# IDAT while writing it before IDAT. (PNG and EXIF are still +# written after IDAT.) As of version 11.63, this strategy is +# applied to all text chunks (tEXt, zTXt and iTXt). +#------------------------------------------------------------------------------ + +package Image::ExifTool::PNG; + +use strict; +use vars qw($VERSION $AUTOLOAD %stdCase); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.64'; + +sub ProcessPNG_tEXt($$$); +sub ProcessPNG_iTXt($$$); +sub ProcessPNG_eXIf($$$); +sub ProcessPNG_Compressed($$$); +sub CalculateCRC($;$$$); +sub HexEncode($); +sub AddChunks($$;@); +sub Add_iCCP($$); +sub DoneDir($$$;$); +sub GetLangInfo($$); +sub BuildTextChunk($$$$$); +sub ConvertPNGDate($$); +sub InversePNGDate($$); + +# translate lower-case to actual case used for eXIf/zXIf chunks +%stdCase = ( 'zxif' => 'zxIf', exif => 'eXIf' ); + +my $noCompressLib; + +# look up for file type, header chunk and end chunk, based on file signature +my %pngLookup = ( + "\x89PNG\r\n\x1a\n" => ['PNG', 'IHDR', 'IEND' ], + "\x8aMNG\r\n\x1a\n" => ['MNG', 'MHDR', 'MEND' ], + "\x8bJNG\r\n\x1a\n" => ['JNG', 'JHDR', 'IEND' ], +); + +# map for directories in PNG images +my %pngMap = ( + IFD1 => 'IFD0', + EXIF => 'IFD0', # to write EXIF as a block + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', + IFD0 => 'PNG', + XMP => 'PNG', + ICC_Profile => 'PNG', + Photoshop => 'PNG', + 'PNG-pHYs' => 'PNG', + IPTC => 'Photoshop', + MakerNotes => 'ExifIFD', +); + +# color type of current image +$Image::ExifTool::PNG::colorType = -1; + +# data and text chunk types +my %isDatChunk = ( IDAT => 1, JDAT => 1, JDAA => 1 ); +my %isTxtChunk = ( tEXt => 1, zTXt => 1, iTXt => 1, eXIf => 1 ); + +# chunks that we shouldn't move other chunks across (ref 3) +my %noLeapFrog = ( SAVE => 1, SEEK => 1, IHDR => 1, JHDR => 1, IEND => 1, MEND => 1, + DHDR => 1, BASI => 1, CLON => 1, PAST => 1, SHOW => 1, MAGN => 1 ); + +# PNG chunks +%Image::ExifTool::PNG::Main = ( + WRITE_PROC => \&Image::ExifTool::DummyWriteProc, + GROUPS => { 2 => 'Image' }, + PREFERRED => 1, # always add these tags when writing + NOTES => q{ + Tags extracted from PNG images. See + L<http://www.libpng.org/pub/png/spec/1.2/> for the official PNG 1.2 + specification. + + According to the specification, a PNG file should end at the IEND chunk, + however ExifTool will preserve any data found after this when writing unless + it is specifically deleted with C<-Trailer:All=>. When reading, a minor + warning is issued if this trailer exists, and ExifTool will attempt to parse + this data as additional PNG chunks. + + Also according to the PNG specification, there is no restriction on the + location of text-type chunks (tEXt, zTXt and iTXt). However, certain + utilities (including some Apple and Adobe utilities) won't read the XMP iTXt + chunk if it comes after the IDAT chunk, and at least one utility won't read + other text chunks here. For this reason, when writing, ExifTool 11.63 and + later create new text chunks (including XMP) before IDAT, and move existing + text chunks to before IDAT. + + The PNG format contains CRC checksums that are validated when reading with + either the L<Verbose|../ExifTool.html#Verbose> or L<Validate|../ExifTool.html#Validate> option. When writing, these checksums are + validated by default, but the L<FastScan|../ExifTool.html#FastScan> option may be used to bypass this + check if speed is more of a concern. + }, + bKGD => { + Name => 'BackgroundColor', + ValueConv => 'join(" ",unpack(length($val) < 2 ? "C" : "n*", $val))', + }, + cHRM => { + Name => 'PrimaryChromaticities', + SubDirectory => { TagTable => 'Image::ExifTool::PNG::PrimaryChromaticities' }, + }, + dSIG => { + Name => 'DigitalSignature', + Binary => 1, + }, + fRAc => { + Name => 'FractalParameters', + Binary => 1, + }, + gAMA => { + Name => 'Gamma', + Writable => 1, + Protected => 1, + Notes => q{ + ExifTool reports the gamma for decoding the image, which is consistent with + the EXIF convention, but is the inverse of the stored encoding gamma + }, + ValueConv => 'my $a=unpack("N",$val);$a ? int(1e9/$a+0.5)/1e4 : $val', + ValueConvInv => 'pack("N", int(1e5/$val+0.5))', + }, + gIFg => { + Name => 'GIFGraphicControlExtension', + Binary => 1, + }, + gIFt => { + Name => 'GIFPlainTextExtension', + Binary => 1, + }, + gIFx => { + Name => 'GIFApplicationExtension', + Binary => 1, + }, + hIST => { + Name => 'PaletteHistogram', + Binary => 1, + }, + iCCP => { + Name => 'ICC_Profile', + Notes => q{ + this is where ExifTool will write a new ICC_Profile. When creating a new + ICC_Profile, the SRGBRendering tag should be deleted if it exists + }, + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + ProcessProc => \&ProcessPNG_Compressed, + }, + }, + 'iCCP-name' => { + Name => 'ProfileName', + Writable => 1, + FakeTag => 1, # (not a real PNG tag, so don't try to write it) + Notes => q{ + not a real tag ID, this tag represents the iCCP profile name, and may only + be written when the ICC_Profile is written + }, + }, +# IDAT +# IEND + IHDR => { + Name => 'ImageHeader', + SubDirectory => { TagTable => 'Image::ExifTool::PNG::ImageHeader' }, + }, + iTXt => { + Name => 'InternationalText', + SubDirectory => { + TagTable => 'Image::ExifTool::PNG::TextualData', + ProcessProc => \&ProcessPNG_iTXt, + }, + }, + oFFs => { + Name => 'ImageOffset', + ValueConv => q{ + my @a = unpack("NNC",$val); + $a[2] = ($a[2] ? "microns" : "pixels"); + return "$a[0], $a[1] ($a[2])"; + }, + }, + pCAL => { + Name => 'PixelCalibration', + Binary => 1, + }, + pHYs => { + Name => 'PhysicalPixel', + SubDirectory => { + TagTable => 'Image::ExifTool::PNG::PhysicalPixel', + DirName => 'PNG-pHYs', # (needed for writing) + }, + }, + PLTE => { + Name => 'Palette', + ValueConv => 'length($val) <= 3 ? join(" ",unpack("C*",$val)) : \$val', + }, + sBIT => { + Name => 'SignificantBits', + ValueConv => 'join(" ",unpack("C*",$val))', + }, + sCAL => { # png 1.4.0 + Name => 'SubjectScale', + SubDirectory => { TagTable => 'Image::ExifTool::PNG::SubjectScale' }, + }, + sPLT => { + Name => 'SuggestedPalette', + Binary => 1, + PrintConv => 'split("\0",$$val,1)', # extract palette name + }, + sRGB => { + Name => 'SRGBRendering', + Writable => 1, + Protected => 1, + Notes => 'this chunk should not be present if an iCCP chunk exists', + ValueConv => 'unpack("C",$val)', + ValueConvInv => 'pack("C",$val)', + PrintConv => { + 0 => 'Perceptual', + 1 => 'Relative Colorimetric', + 2 => 'Saturation', + 3 => 'Absolute Colorimetric', + }, + }, + sTER => { # png 1.4.0 + Name => 'StereoImage', + SubDirectory => { TagTable => 'Image::ExifTool::PNG::StereoImage' }, + }, + tEXt => { + Name => 'TextualData', + SubDirectory => { TagTable => 'Image::ExifTool::PNG::TextualData' }, + }, + tIME => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + Writable => 1, + Shift => 'Time', + ValueConv => 'sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d", unpack("nC5", $val))', + ValueConvInv => q{ + my @a = ($val=~/^(\d+):(\d+):(\d+)\s+(\d+):(\d+):(\d+)/); + @a == 6 or warn('Invalid date'), return undef; + return pack('nC5', @a); + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + tRNS => { + Name => 'Transparency', + # may have as many entries as the PLTE table, but who wants to see all that? + ValueConv => q{ + return \$val if length($val) > 6; + join(" ",unpack($Image::ExifTool::PNG::colorType == 3 ? "C*" : "n*", $val)); + }, + }, + tXMP => { + Name => 'XMP', + Notes => 'obsolete location specified by a September 2001 XMP draft', + NonStandard => 'XMP', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, + vpAg => { # private imagemagick chunk + Name => 'VirtualPage', + SubDirectory => { TagTable => 'Image::ExifTool::PNG::VirtualPage' }, + }, + zTXt => { + Name => 'CompressedText', + SubDirectory => { + TagTable => 'Image::ExifTool::PNG::TextualData', + ProcessProc => \&ProcessPNG_Compressed, + }, + }, + # animated PNG (ref https://wiki.mozilla.org/APNG_Specification) + acTL => { + Name => 'AnimationControl', + SubDirectory => { + TagTable => 'Image::ExifTool::PNG::AnimationControl', + }, + }, + # eXIf (ref 6) + $stdCase{exif} => { + Name => $stdCase{exif}, + Notes => 'this is where ExifTool will create new EXIF', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + DirName => 'EXIF', # (to write as a block) + ProcessProc => \&ProcessPNG_eXIf, + }, + }, + # zXIf + $stdCase{zxif} => { + Name => $stdCase{zxif}, + Notes => 'a once-proposed chunk for compressed EXIF', + NonStandard => 'EXIF', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + DirName => 'EXIF', # (to write as a block) + ProcessProc => \&ProcessPNG_eXIf, + }, + }, + # fcTL - animation frame control for each frame + # fdAT - animation data for each frame + iDOT => { # (ref NealKrawetz) + Name => 'AppleDataOffsets', + Binary => 1, + # Apple offsets into data relative to start of iDOT chunk: + # int32u Divisor [only ever seen 2] + # int32u Unknown [always 0] + # int32u TotalDividedHeight [image height from IDHR/Divisor] + # int32u Size [always 40 / 0x28; size of this chunk] + # int32u DividedHeight1 + # int32u DividedHeight2 + # int32u IDAT_Offset2 [location of IDAT with start of DividedHeight2 segment] + }, + caBX => { # C2PA metadata + Name => 'JUMBF', + SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::Main' }, + }, + cICP => { + Name => 'CICodePoints', + SubDirectory => { + TagTable => 'Image::ExifTool::PNG::CICodePoints', + }, + }, +); + +# PNG IHDR chunk +%Image::ExifTool::PNG::ImageHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'ImageWidth', + Format => 'int32u', + }, + 4 => { + Name => 'ImageHeight', + Format => 'int32u', + }, + 8 => 'BitDepth', + 9 => { + Name => 'ColorType', + RawConv => '$Image::ExifTool::PNG::colorType = $val', + PrintConv => { + 0 => 'Grayscale', + 2 => 'RGB', + 3 => 'Palette', + 4 => 'Grayscale with Alpha', + 6 => 'RGB with Alpha', + }, + }, + 10 => { + Name => 'Compression', + PrintConv => { 0 => 'Deflate/Inflate' }, + }, + 11 => { + Name => 'Filter', + PrintConv => { 0 => 'Adaptive' }, + }, + 12 => { + Name => 'Interlace', + PrintConv => { 0 => 'Noninterlaced', 1 => 'Adam7 Interlace' }, + }, +); + +# PNG cHRM chunk +%Image::ExifTool::PNG::PrimaryChromaticities = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int32u', + 0 => { Name => 'WhitePointX', ValueConv => '$val / 100000' }, + 1 => { Name => 'WhitePointY', ValueConv => '$val / 100000' }, + 2 => { Name => 'RedX', ValueConv => '$val / 100000' }, + 3 => { Name => 'RedY', ValueConv => '$val / 100000' }, + 4 => { Name => 'GreenX', ValueConv => '$val / 100000' }, + 5 => { Name => 'GreenY', ValueConv => '$val / 100000' }, + 6 => { Name => 'BlueX', ValueConv => '$val / 100000' }, + 7 => { Name => 'BlueY', ValueConv => '$val / 100000' }, +); + +# PNG pHYs chunk +%Image::ExifTool::PNG::PhysicalPixel = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + WRITABLE => 1, + GROUPS => { 1 => 'PNG-pHYs', 2 => 'Image' }, + WRITE_GROUP => 'PNG-pHYs', + NOTES => q{ + These tags are found in the PNG pHYs chunk and belong to the PNG-pHYs family + 1 group. They are all created together with default values if necessary + when any of these tags is written, and may only be deleted as a group. + }, + 0 => { + Name => 'PixelsPerUnitX', + Format => 'int32u', + Notes => 'default 2834', + }, + 4 => { + Name => 'PixelsPerUnitY', + Format => 'int32u', + Notes => 'default 2834', + }, + 8 => { + Name => 'PixelUnits', + PrintConv => { 0 => 'Unknown', 1 => 'meters' }, + Notes => 'default meters', + }, +); + +# PNG cICP chunk +%Image::ExifTool::PNG::CICodePoints = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 1 => 'PNG-cICP', 2 => 'Image' }, + NOTES => q{ + These tags are found in the PNG cICP chunk and belong to the PNG-cICP family + 1 group. + }, + # (same as tags in QuickTime::ColorRep) + 0 => { + Name => 'ColorPrimaries', + PrintConv => { + 1 => 'BT.709', + 2 => 'Unspecified', + 4 => 'BT.470 System M (historical)', + 5 => 'BT.470 System B, G (historical)', + 6 => 'BT.601', + 7 => 'SMPTE 240', + 8 => 'Generic film (color filters using illuminant C)', + 9 => 'BT.2020, BT.2100', + 10 => 'SMPTE 428 (CIE 1921 XYZ)', + 11 => 'SMPTE RP 431-2', + 12 => 'SMPTE EG 432-1', + 22 => 'EBU Tech. 3213-E', + }, + }, + 1 => { + Name => 'TransferCharacteristics', + PrintConv => { + 0 => 'For future use (0)', + 1 => 'BT.709', + 2 => 'Unspecified', + 3 => 'For future use (3)', + 4 => 'BT.470 System M (historical)', + 5 => 'BT.470 System B, G (historical)', + 6 => 'BT.601', + 7 => 'SMPTE 240 M', + 8 => 'Linear', + 9 => 'Logarithmic (100 : 1 range)', + 10 => 'Logarithmic (100 * Sqrt(10) : 1 range)', + 11 => 'IEC 61966-2-4', + 12 => 'BT.1361', + 13 => 'sRGB or sYCC', + 14 => 'BT.2020 10-bit systems', + 15 => 'BT.2020 12-bit systems', + 16 => 'SMPTE ST 2084, ITU BT.2100 PQ', + 17 => 'SMPTE ST 428', + 18 => 'BT.2100 HLG, ARIB STD-B67', + }, + }, + 2 => { + Name => 'MatrixCoefficients', + PrintConv => { + 0 => 'Identity matrix', + 1 => 'BT.709', + 2 => 'Unspecified', + 3 => 'For future use (3)', + 4 => 'US FCC 73.628', + 5 => 'BT.470 System B, G (historical)', + 6 => 'BT.601', + 7 => 'SMPTE 240 M', + 8 => 'YCgCo', + 9 => 'BT.2020 non-constant luminance, BT.2100 YCbCr', + 10 => 'BT.2020 constant luminance', + 11 => 'SMPTE ST 2085 YDzDx', + 12 => 'Chromaticity-derived non-constant luminance', + 13 => 'Chromaticity-derived constant luminance', + 14 => 'BT.2100 ICtCp', + }, + }, + 3 => 'VideoFullRangeFlag', +); + +# PNG sCAL chunk +%Image::ExifTool::PNG::SubjectScale = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'SubjectUnits', + PrintConv => { 1 => 'meters', 2 => 'radians' }, + }, + 1 => { + Name => 'SubjectPixelWidth', + Format => 'var_string', + }, + 2 => { + Name => 'SubjectPixelHeight', + Format => 'var_string', + }, +); + +# PNG vpAg chunk +%Image::ExifTool::PNG::VirtualPage = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int32u', + 0 => 'VirtualImageWidth', + 1 => 'VirtualImageHeight', + 2 => { + Name => 'VirtualPageUnits', + Format => 'int8u', + # what is the conversion for this? + }, +); + +# PNG sTER chunk +%Image::ExifTool::PNG::StereoImage = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'StereoMode', + PrintConv => { + 0 => 'Cross-fuse Layout', + 1 => 'Diverging-fuse Layout', + }, + }, +); + +my %unreg = ( Notes => 'unregistered' ); + +# Tags for PNG tEXt zTXt and iTXt chunks +# (NOTE: ValueConv is set dynamically, so don't set it here!) +%Image::ExifTool::PNG::TextualData = ( + PROCESS_PROC => \&ProcessPNG_tEXt, + WRITE_PROC => \&Image::ExifTool::DummyWriteProc, + WRITABLE => 'string', + PREFERRED => 1, # always add these tags when writing + GROUPS => { 2 => 'Image' }, + LANG_INFO => \&GetLangInfo, + NOTES => q{ + The PNG TextualData format allows arbitrary tag names to be used. The tags + listed below are the only ones that can be written (unless new user-defined + tags are added via the configuration file), however ExifTool will extract + any other TextualData tags that are found. All TextualData tags (including + tags not listed below) are removed when deleting all PNG tags. + + These tags may be stored as tEXt, zTXt or iTXt chunks in the PNG image. By + default ExifTool writes new string-value tags as as uncompressed tEXt, or + compressed zTXt if the L<Compress|../ExifTool.html#Compress> (-z) option is used and Compress::Zlib is + available. Alternate language tags and values containing special characters + (unless the Latin character set is used) are written as iTXt, and compressed + if the L<Compress|../ExifTool.html#Compress> option is used and Compress::Zlib is available. Raw profile + information is always created as compressed zTXt if Compress::Zlib is + available, or tEXt otherwise. Standard XMP is written as uncompressed iTXt. + User-defined tags may set an 'iTXt' flag in the tag definition to be written + only as iTXt. + + Alternate languages are accessed by suffixing the tag name with a '-', + followed by an RFC 3066 language code (eg. "PNG:Comment-fr", or + "Title-en-US"). See L<http://www.ietf.org/rfc/rfc3066.txt> for the RFC 3066 + specification. + + Some of the tags below are not registered as part of the PNG specification, + but are included here because they are generated by other software such as + ImageMagick. + }, + Title => { }, + Author => { Groups => { 2 => 'Author' } }, + Description => { }, + Copyright => { Groups => { 2 => 'Author' } }, + 'Creation Time' => { + Name => 'CreationTime', + Groups => { 2 => 'Time' }, + Shift => 'Time', + Notes => 'stored in RFC-1123 format and converted to/from EXIF format by ExifTool', + RawConv => \&ConvertPNGDate, + ValueConvInv => \&InversePNGDate, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + }, + Software => { }, + Disclaimer => { }, + # change name to differentiate from ExifTool Warning + Warning => { Name => 'PNGWarning', }, + Source => { }, + Comment => { }, + Collection => { }, # (PNG extensions, 2004) +# +# The following tags are not part of the original PNG specification, +# but are written by ImageMagick and other software +# + Artist => { %unreg, Groups => { 2 => 'Author' } }, + Document => { %unreg }, + Label => { %unreg }, + Make => { %unreg, Groups => { 2 => 'Camera' } }, + Model => { %unreg, Groups => { 2 => 'Camera' } }, + # parameters (written by Stable Diffusion) + # aesthetic_score (written by Stable Diffusion) + 'create-date'=> { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + Shift => 'Time', + %unreg, + ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)', + ValueConvInv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::FormatXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + }, + 'modify-date'=> { + Name => 'ModDate', # (to distinguish from tIME chunk "ModifyDate") + Groups => { 2 => 'Time' }, + Shift => 'Time', + %unreg, + ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)', + ValueConvInv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::FormatXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + }, + TimeStamp => { %unreg, Groups => { 2 => 'Time' }, Shift => 'Time' }, + URL => { %unreg }, + 'XML:com.adobe.xmp' => { + Name => 'XMP', + Notes => q{ + unregistered, but this is the location according to the June 2002 or later + XMP specification, and is where ExifTool will add a new XMP chunk if the + image didn't already contain XMP + }, + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, + 'Raw profile type APP1' => [ + { + # EXIF table must come first because we key on this in ProcessProfile() + # (No condition because this is just for BuildTagLookup) + Name => 'APP1_Profile', + %unreg, + NonStandard => 'EXIF', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&ProcessProfile, + }, + }, + { + Name => 'APP1_Profile', + NonStandard => 'XMP', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + ProcessProc => \&ProcessProfile, + }, + }, + ], + 'Raw profile type exif' => { + Name => 'EXIF_Profile', + %unreg, + NonStandard => 'EXIF', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&ProcessProfile, + }, + }, + 'Raw profile type icc' => { + Name => 'ICC_Profile', + %unreg, + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + ProcessProc => \&ProcessProfile, + }, + }, + 'Raw profile type icm' => { + Name => 'ICC_Profile', + %unreg, + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + ProcessProc => \&ProcessProfile, + }, + }, + 'Raw profile type iptc' => { + Name => 'IPTC_Profile', + Notes => q{ + unregistered. May be either IPTC IIM or Photoshop IRB format. This is + where ExifTool will add new IPTC, inside a Photoshop IRB container + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Photoshop::Main', + ProcessProc => \&ProcessProfile, + }, + }, + 'Raw profile type xmp' => { + Name => 'XMP_Profile', + %unreg, + NonStandard => 'XMP', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + ProcessProc => \&ProcessProfile, + }, + }, + 'Raw profile type 8bim' => { + Name => 'Photoshop_Profile', + %unreg, + SubDirectory => { + TagTable => 'Image::ExifTool::Photoshop::Main', + ProcessProc => \&ProcessProfile, + }, + }, +); + +# Animation control +%Image::ExifTool::PNG::AnimationControl = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int32u', + NOTES => q{ + Tags found in the Animation Control chunk. See + L<https://wiki.mozilla.org/APNG_Specification> for details. + }, + 0 => { + Name => 'AnimationFrames', + RawConv => '$self->OverrideFileType("APNG", undef, "PNG"); $val', + }, + 1 => { + Name => 'AnimationPlays', + PrintConv => '$val || "inf"', + }, +); + +#------------------------------------------------------------------------------ +# AutoLoad our writer routines when necessary +# +sub AUTOLOAD +{ + return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); +} + +#------------------------------------------------------------------------------ +# Get standard case for language code (this routine copied from XMP.pm) +# Inputs: 0) Language code +# Returns: Language code in standard case +sub StandardLangCase($) +{ + my $lang = shift; + # make 2nd subtag uppercase only if it is 2 letters + return lc($1) . uc($2) . lc($3) if $lang =~ /^([a-z]{2,3}|[xi])(-[a-z]{2})\b(.*)/i; + return lc($lang); +} + +#------------------------------------------------------------------------------ +# Convert date from PNG to EXIF format +# Inputs: 0) Date/time in PNG format, 1) ExifTool ref +# Returns: EXIF formatted date/time string +my %monthNum = ( + Jan=>1, Feb=>2, Mar=>3, Apr=>4, May=>5, Jun=>6, + Jul=>7, Aug=>8, Sep=>9, Oct=>10,Nov=>11,Dec=>12 +); +my %tzConv = ( + UT => '+00:00', GMT => '+00:00', UTC => '+00:00', # (UTC not in spec -- PH addition) + EST => '-05:00', EDT => '-04:00', + CST => '-06:00', CDT => '-05:00', + MST => '-07:00', MDT => '-06:00', + PST => '-08:00', PDT => '-07:00', + A => '-01:00', N => '+01:00', + B => '-02:00', O => '+02:00', + C => '-03:00', P => '+03:00', + D => '-04:00', Q => '+04:00', + E => '-05:00', R => '+05:00', + F => '-06:00', S => '+06:00', + G => '-07:00', T => '+07:00', + H => '-08:00', U => '+08:00', + I => '-09:00', V => '+09:00', + K => '-10:00', W => '+10:00', + L => '-11:00', X => '+11:00', + M => '-12:00', Y => '+12:00', + Z => '+00:00', +); +sub ConvertPNGDate($$) +{ + my ($val, $et) = @_; + # standard format is like "Mon, 1 Jan 2018 12:10:22 EST" (RFC-1123 section 5.2.14) + while ($val =~ /(\d+)\s*(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s*(\d+)\s+(\d+):(\d{2})(:\d{2})?\s*(\S*)/i) { + my ($day,$mon,$yr,$hr,$min,$sec,$tz) = ($1,$2,$3,$4,$5,$6,$7); + $yr += $yr > 70 ? 1900 : 2000 if $yr < 100; # boost year to 4 digits if necessary + $mon = $monthNum{ucfirst lc $mon} or return $val; + if (not $tz) { + $tz = ''; + } elsif ($tzConv{uc $tz}) { + $tz = $tzConv{uc $tz}; + } elsif ($tz =~ /^([-+]\d+):?(\d{2})/) { + $tz = $1 . ':' . $2; + } else { + last; # (non-standard date) + } + return sprintf("%.4d:%.2d:%.2d %.2d:%.2d%s%s",$yr,$mon,$day,$hr,$min,$sec||':00',$tz); + } + if (($et->Options('StrictDate') and not $$et{TAGS_FROM_FILE}) or $et->Options('Validate')) { + $et->Warn('Non standard PNG date/time format', 1); + } + return $val; +} + +#------------------------------------------------------------------------------ +# Convert EXIF date/time to PNG format +# Inputs: 0) Date/time in EXIF format, 1) ExifTool ref +# Returns: PNG formatted date/time string +sub InversePNGDate($$) +{ + my ($val, $et) = @_; + if ($et->Options('StrictDate')) { + my $err; + if ($val =~ /^(\d{4}):(\d{2}):(\d{2}) (\d{2})(:\d{2})(:\d{2})?(?:\.\d*)?\s*(\S*)/) { + my ($yr,$mon,$day,$hr,$min,$sec,$tz) = ($1,$2,$3,$4,$5,$6,$7); + $sec or $sec = ''; + my %monName = map { $monthNum{$_} => $_ } keys %monthNum; + $mon = $monName{$mon + 0} or $err = 1; + if (length $tz) { + $tz =~ /^(Z|[-+]\d{2}:?\d{2})/ or $err = 1; + $tz =~ tr/://d; + $tz = ' ' . $tz; + } + $val = "$day $mon $yr $hr$min$sec$tz" unless $err; + } + if ($err) { + warn "Invalid date/time (use YYYY:mm:dd HH:MM:SS[.ss][+/-HH:MM|Z])\n"; + undef $val; + } + } + return $val; +} + +#------------------------------------------------------------------------------ +# Get localized version of tagInfo hash +# Inputs: 0) tagInfo hash ref, 1) language code (eg. "x-default") +# Returns: new tagInfo hash ref, or undef if invalid +sub GetLangInfo($$) +{ + my ($tagInfo, $lang) = @_; + $lang =~ tr/_/-/; # RFC 3066 specifies '-' as a separator + # no alternate languages for XMP or raw profile directories + return undef if $$tagInfo{SubDirectory}; + # language code must normalized for use in tag ID + return Image::ExifTool::GetLangInfo($tagInfo, StandardLangCase($lang)); +} + +#------------------------------------------------------------------------------ +# Found a PNG tag -- extract info from subdirectory or decompress data if necessary +# Inputs: 0) ExifTool object reference, 1) Pointer to tag table, +# 2) Tag ID, 3) Tag value, 4) [optional] compressed data flag: +# 0=not compressed, 1=unknown compression, 2-N=compression with type N-2 +# 5) optional output buffer ref, 6) character encoding (tEXt/zTXt/iTXt only) +# 6) optional language code +# Returns: 1 on success +sub FoundPNG($$$$;$$$$) +{ + my ($et, $tagTablePtr, $tag, $val, $compressed, $outBuff, $enc, $lang) = @_; + return 0 unless defined $val; + my $verbose = $et->Options('Verbose'); + my $id = $tag; # generate tag ID which includes language code + if ($lang) { + # case of language code must be normalized since they are case insensitive + $lang = StandardLangCase($lang); + $id .= '-' . $lang; + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $id) || + # (some software forgets to capitalize first letter) + $et->GetTagInfo($tagTablePtr, ucfirst($id)); + # create alternate language tag if necessary + if (not $tagInfo and $lang) { + $tagInfo = $et->GetTagInfo($tagTablePtr, $tag) || + $et->GetTagInfo($tagTablePtr, ucfirst($tag)); + $tagInfo = GetLangInfo($tagInfo, $lang) if $tagInfo; + } +# +# uncompress data if necessary +# + my ($wasCompressed, $deflateErr); + if ($compressed and $compressed > 1) { + if ($compressed == 2) { # Inflate/Deflate compression + if (eval { require Compress::Zlib }) { + my ($v2, $stat); + my $inflate = Compress::Zlib::inflateInit(); + $inflate and ($v2, $stat) = $inflate->inflate($val); + if ($inflate and $stat == Compress::Zlib::Z_STREAM_END()) { + $val = $v2; + $compressed = 0; + $wasCompressed = 1; + } else { + $deflateErr = "Error inflating $tag"; + } + } elsif (not $noCompressLib) { + $deflateErr = "Install Compress::Zlib to read compressed information"; + } else { + $deflateErr = ''; # flag deflate error but no warning + } + } else { + $compressed -= 2; + $deflateErr = "Unknown compression method $compressed for $tag"; + } + if ($compressed and $verbose and $tagInfo and $$tagInfo{SubDirectory}) { + $et->VerboseDir("Unable to decompress $$tagInfo{Name}", 0, length($val)); + } + # issue warning if relevant + if ($deflateErr and not $outBuff) { + $et->Warn($deflateErr); + $noCompressLib = 1 if $deflateErr =~ /^Install/; + } + } + # translate character encoding if necessary (tEXt/zTXt/iTXt string values only) + if ($enc and not $compressed and not ($tagInfo and $$tagInfo{SubDirectory})) { + $val = $et->Decode($val, $enc); + } +# +# extract information from subdirectory if available +# + if ($tagInfo) { + my $tagName = $$tagInfo{Name}; + my $processed; + if ($$tagInfo{SubDirectory}) { + if ($$et{OPTIONS}{Validate} and $$tagInfo{NonStandard}) { + $et->WarnOnce("Non-standard $$tagInfo{NonStandard} in PNG $tag chunk", 1); + } + my $subdir = $$tagInfo{SubDirectory}; + my $dirName = $$subdir{DirName} || $tagName; + if (not $compressed) { + my $len = length $val; + if ($verbose and $$et{INDENT} ne ' ') { + if ($wasCompressed and $verbose > 2) { + my $name = $tagName; + $wasCompressed and $name = "Decompressed $name"; + $et->VerboseDir($name, 0, $len); + $et->VerboseDump(\$val); + } + # don't indent next directory (since it is really the same data) + $$et{INDENT} =~ s/..$//; + } + my $processProc = $$subdir{ProcessProc}; + # nothing more to do if writing and subdirectory is not writable + my $subTable = GetTagTable($$subdir{TagTable}); + return 1 if $outBuff and not $$subTable{WRITE_PROC}; + my $dirName = $$subdir{DirName} || $tagName; + my %subdirInfo = ( + DataPt => \$val, + DirStart => 0, + DataLen => $len, + DirLen => $len, + DirName => $dirName, + TagInfo => $tagInfo, + ReadOnly => 1, # (used only by WriteXMP) + OutBuff => $outBuff, + ); + # no need to re-decompress if already done + undef $processProc if $wasCompressed and $processProc and $processProc eq \&ProcessPNG_Compressed; + # rewrite this directory if necessary (but always process TextualData normally) + if ($outBuff and not $processProc and $subTable ne \%Image::ExifTool::PNG::TextualData) { + return 1 unless $$et{EDIT_DIRS}{$dirName}; + $$outBuff = $et->WriteDirectory(\%subdirInfo, $subTable); + if ($tagName eq 'XMP' and $$outBuff) { + # make sure the XMP is marked as read-only + Image::ExifTool::XMP::ValidateXMP($outBuff,'r'); + } + DoneDir($et, $dirName, $outBuff, $$tagInfo{NonStandard}); + } else { + $processed = $et->ProcessDirectory(\%subdirInfo, $subTable, $processProc); + } + $compressed = 1; # pretend this is compressed since it is binary data + } elsif ($outBuff) { + if ($$et{DEL_GROUP}{$dirName} or ($dirName eq 'EXIF' and $$et{DEL_GROUP}{IFD0})) { + $$outBuff = ''; + ++$$et{CHANGED}; + $et->VPrint(0, " Deleting $tag chunk"); + } else { + if ($$et{EDIT_DIRS}{$dirName} or ($dirName eq 'EXIF' and $$et{EDIT_DIRS}{IFD0})) { + $et->Warn("Can't write $dirName. Requires Compress::Zlib"); + } + # pretend we did this directory so we don't try to recreate it + DoneDir($et, $dirName, $outBuff, $$tagInfo{NonStandard}); + } + } + } + if ($outBuff) { + my $writable = $$tagInfo{Writable}; + my $isOverwriting; + if ($writable or ($$tagTablePtr{WRITABLE} and + not defined $writable and not $$tagInfo{SubDirectory})) + { + # write new value for this tag if necessary + my $newVal; + if ($$et{DEL_GROUP}{PNG}){ + # remove this tag now, but keep in ADD_PNG list to add back later + $isOverwriting = 1; + } else { + # remove this from the list of PNG tags to add + delete $$et{ADD_PNG}{$id}; + # (also handle case of tEXt tags written with lowercase first letter) + delete $$et{ADD_PNG}{ucfirst($id)}; + my $nvHash = $et->GetNewValueHash($tagInfo); + $isOverwriting = $et->IsOverwriting($nvHash); + if (defined $deflateErr) { + $newVal = $et->GetNewValue($nvHash); + # can only write tag now if always overwriting + if ($isOverwriting > 0) { + $val = '<deflate error>'; + } elsif ($isOverwriting) { + $isOverwriting = 0; # can't overwrite + $et->Warn($deflateErr) if $deflateErr; + } + } else { + if ($isOverwriting < 0) { + $isOverwriting = $et->IsOverwriting($nvHash, $val); + } + # (must get new value after IsOverwriting() in case it was shifted) + $newVal = $et->GetNewValue($nvHash); + } + } + if ($isOverwriting) { + $$outBuff = (defined $newVal) ? $newVal : ''; + ++$$et{CHANGED}; + $et->VerboseValue("- PNG:$tagName", $val); + $et->VerboseValue("+ PNG:$tagName", $newVal) if defined $newVal; + } + } + if (defined $$outBuff and length $$outBuff) { + if ($enc) { # must be tEXt/zTXt/iTXt if $enc is set + $$outBuff = BuildTextChunk($et, $tag, $tagInfo, $$outBuff, $lang); + } elsif ($wasCompressed) { + # re-compress the output data + my $len = length $$outBuff; + my $deflate = Compress::Zlib::deflateInit(); + if ($deflate) { + $$outBuff = $deflate->deflate($$outBuff); + $$outBuff .= $deflate->flush() if defined $$outBuff; + } else { + undef $$outBuff; + } + if (not $$outBuff) { + $et->Warn("PNG:$tagName not written (compress error)"); + } elsif (lc $tag eq 'zxif') { + $$outBuff = "\0" . pack('N',$len) . $$outBuff; # add zXIf header + } + } + } + return 1; + } + return 1 if $processed; + } elsif ($outBuff) { + if ($$et{DEL_GROUP}{PNG} and $tagTablePtr eq \%Image::ExifTool::PNG::TextualData) { + # delete all TextualData tags if deleting the PNG group + $$outBuff = ''; + ++$$et{CHANGED}; + $et->VerboseValue("- PNG:$tag", $val); + } + return 1; + } else { + my $name; + ($name = $tag) =~ s/\s+(.)/\u$1/g; # remove white space from tag name + $tagInfo = { Name => $name }; + $$tagInfo{LangCode} = $lang if $lang; + # make unknown profiles binary data type + $$tagInfo{Binary} = 1 if $tag =~ /^Raw profile type /; + $verbose and $et->VPrint(0, " [adding $tag]\n"); + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } +# +# store this tag information +# + if ($verbose) { + # temporarily remove subdirectory so it isn't printed in verbose information + # since we aren't decoding it anyway; + my $subdir = $$tagInfo{SubDirectory}; + delete $$tagInfo{SubDirectory}; + $et->VerboseInfo($tag, $tagInfo, + Table => $tagTablePtr, + DataPt => \$val, + ); + $$tagInfo{SubDirectory} = $subdir if $subdir; + } + # set the RawConv dynamically depending on whether this is binary or not + my $delRawConv; + if ($compressed and not defined $$tagInfo{ValueConv}) { + $$tagInfo{RawConv} = '\$val'; + $delRawConv = 1; + } + $et->FoundTag($tagInfo, $val); + delete $$tagInfo{RawConv} if $delRawConv; + return 1; +} + +#------------------------------------------------------------------------------ +# Process encoded PNG profile information +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) Pointer to tag table +# Returns: 1 on success +sub ProcessProfile($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $tagInfo = $$dirInfo{TagInfo}; + my $outBuff = $$dirInfo{OutBuff}; + my $tagName = $$tagInfo{Name}; + + # ImageMagick 5.3.6 writes profiles with the following headers: + # "\nICC Profile\n", "\nIPTC profile\n", "\n\xaa\x01{generic prof\n" + # and "\ngeneric profile\n" + return 0 unless $$dataPt =~ /^\n(.*?)\n\s*(\d+)\n(.*)/s; + my ($profileType, $len) = ($1, $2); + # data is encoded in hex, so change back to binary + my $buff = pack('H*', join('',split(' ',$3))); + my $actualLen = length $buff; + if ($len ne $actualLen) { + $et->Warn("$tagName is wrong size (should be $len bytes but is $actualLen)"); + $len = $actualLen; + } + my $verbose = $et->Options('Verbose'); + if ($verbose) { + if ($verbose > 2) { + $et->VerboseDir("Decoded $tagName", 0, $len); + $et->VerboseDump(\$buff); + } + # don't indent next directory (since it is really the same data) + $$et{INDENT} =~ s/..$//; + } + my %dirInfo = ( + Parent => 'PNG', + DataPt => \$buff, + DataLen => $len, + DirStart => 0, + DirLen => $len, + Base => 0, + OutFile => $outBuff, + ); + $$et{PROCESSED} = { }; # reset processed directory offsets + my $processed = 0; + my $oldChanged = $$et{CHANGED}; + my $exifTable = GetTagTable('Image::ExifTool::Exif::Main'); + my $editDirs = $$et{EDIT_DIRS}; + + if ($tagTablePtr ne $exifTable) { + # this is unfortunate, but the "IPTC" profile may be stored as either + # IPTC IIM or a Photoshop IRB resource, so we must test for this + if ($tagName eq 'IPTC_Profile' and $buff =~ /^\x1c/) { + $tagTablePtr = GetTagTable('Image::ExifTool::IPTC::Main'); + } + # process non-EXIF and non-APP1 profile as-is + if ($outBuff) { + # no need to rewrite this if not editing tags in this directory + my $dir = $tagName; + $dir =~ s/_Profile// unless $dir =~ /^ICC/; + return 1 unless $$editDirs{$dir}; + $$outBuff = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + DoneDir($et, $dir, $outBuff, $$tagInfo{NonStandard}); + } else { + $processed = $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } elsif ($buff =~ /^$Image::ExifTool::exifAPP1hdr/) { + # APP1 EXIF information + return 1 if $outBuff and not $$editDirs{IFD0}; + my $hdrLen = length($Image::ExifTool::exifAPP1hdr); + $dirInfo{DirStart} += $hdrLen; + $dirInfo{DirLen} -= $hdrLen; + if ($outBuff) { + # delete non-standard EXIF if recreating from scratch + if ($$et{DEL_GROUP}{EXIF} or $$et{DEL_GROUP}{IFD0}) { + $$outBuff = ''; + $et->VPrint(0, ' Deleting non-standard APP1 EXIF information'); + return 1; + } + $$outBuff = $et->WriteDirectory(\%dirInfo, $tagTablePtr, + \&Image::ExifTool::WriteTIFF); + $$outBuff = $Image::ExifTool::exifAPP1hdr . $$outBuff if $$outBuff; + DoneDir($et, 'IFD0', $outBuff, $$tagInfo{NonStandard}); + } else { + $processed = $et->ProcessTIFF(\%dirInfo); + } + } elsif ($buff =~ /^$Image::ExifTool::xmpAPP1hdr/) { + # APP1 XMP information + my $hdrLen = length($Image::ExifTool::xmpAPP1hdr); + my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); + $dirInfo{DirStart} += $hdrLen; + $dirInfo{DirLen} -= $hdrLen; + if ($outBuff) { + return 1 unless $$editDirs{XMP}; + $$outBuff = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + $$outBuff and $$outBuff = $Image::ExifTool::xmpAPP1hdr . $$outBuff; + DoneDir($et, 'XMP', $outBuff, $$tagInfo{NonStandard}); + } else { + $processed = $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } elsif ($buff =~ /^(MM\0\x2a|II\x2a\0)/) { + # TIFF information + return 1 if $outBuff and not $$editDirs{IFD0}; + if ($outBuff) { + # delete non-standard EXIF if recreating from scratch + if ($$et{DEL_GROUP}{EXIF} or $$et{DEL_GROUP}{IFD0}) { + $$outBuff = ''; + $et->VPrint(0, ' Deleting non-standard EXIF/TIFF information'); + return 1; + } + $$outBuff = $et->WriteDirectory(\%dirInfo, $tagTablePtr, + \&Image::ExifTool::WriteTIFF); + DoneDir($et, 'IFD0', $outBuff, $$tagInfo{NonStandard}); + } else { + $processed = $et->ProcessTIFF(\%dirInfo); + } + } else { + my $profName = $profileType; + $profName =~ tr/\x00-\x1f\x7f-\xff/./; + $et->Warn("Unknown raw profile '${profName}'"); + } + if ($outBuff and defined $$outBuff and length $$outBuff) { + if ($$et{CHANGED} != $oldChanged) { + my $hdr = sprintf("\n%s\n%8d\n", $profileType, length($$outBuff)); + # hex encode the data + $$outBuff = $hdr . HexEncode($outBuff); + } else { + undef $$outBuff; + } + } + return $processed; +} + +#------------------------------------------------------------------------------ +# Process PNG compressed zTXt or iCCP chunk +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) Pointer to tag table +# Returns: 1 on success +# Notes: writes new chunk data to ${$$dirInfo{OutBuff}} if writing tag +sub ProcessPNG_Compressed($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my ($tag, $val) = split /\0/, ${$$dirInfo{DataPt}}, 2; + return 0 unless defined $val; + # set compressed to 2 + compression method to decompress the data + my $compressed = 2 + unpack('C', $val); + my $hdr = $tag . "\0" . substr($val, 0, 1); + $val = substr($val, 1); # remove compression method byte + my $success; + my $outBuff = $$dirInfo{OutBuff}; + my $tagInfo = $$dirInfo{TagInfo}; + # use the PNG chunk tag instead of the embedded tag name for iCCP chunks + if ($tagInfo and $$tagInfo{Name} eq 'ICC_Profile') { + $et->VerboseDir('iCCP'); + $tagTablePtr = \%Image::ExifTool::PNG::Main; + FoundPNG($et, $tagTablePtr, 'iCCP-name', $tag) if length($tag) and not $outBuff; + $success = FoundPNG($et, $tagTablePtr, 'iCCP', $val, $compressed, $outBuff); + if ($outBuff and $$outBuff) { + my $profileName = $et->GetNewValue($Image::ExifTool::PNG::Main{'iCCP-name'}); + if (defined $profileName) { + $hdr = $profileName . substr($hdr, length $tag); + $et->VerboseValue("+ PNG:ProfileName", $profileName); + } + $$outBuff = $hdr . $$outBuff; + } + } else { + $success = FoundPNG($et, $tagTablePtr, $tag, $val, $compressed, $outBuff, 'Latin'); + } + return $success; +} + +#------------------------------------------------------------------------------ +# Process PNG tEXt chunk +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) Pointer to tag table +# Returns: 1 on success +# Notes: writes new chunk data to ${$$dirInfo{OutBuff}} if writing tag +sub ProcessPNG_tEXt($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my ($tag, $val) = split /\0/, ${$$dirInfo{DataPt}}, 2; + my $outBuff = $$dirInfo{OutBuff}; + $$et{INDENT} = substr($$et{INDENT}, 0, -2) if $$et{OPTIONS}{Verbose}; + return FoundPNG($et, $tagTablePtr, $tag, $val, undef, $outBuff, 'Latin'); +} + +#------------------------------------------------------------------------------ +# Process PNG iTXt chunk +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) Pointer to tag table +# Returns: 1 on success +# Notes: writes new chunk data to ${$$dirInfo{OutBuff}} if writing tag +sub ProcessPNG_iTXt($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my ($tag, $dat) = split /\0/, ${$$dirInfo{DataPt}}, 2; + return 0 unless defined $dat and length($dat) >= 4; + my ($compressed, $meth) = unpack('CC', $dat); + my ($lang, $trans, $val) = split /\0/, substr($dat, 2), 3; + # set compressed flag so we will decompress it in FoundPNG() + $compressed and $compressed = 2 + $meth; + my $outBuff = $$dirInfo{OutBuff}; + $$et{INDENT} = substr($$et{INDENT}, 0, -2) if $$et{OPTIONS}{Verbose}; + return FoundPNG($et, $tagTablePtr, $tag, $val, $compressed, $outBuff, 'UTF8', $lang); +} + +#------------------------------------------------------------------------------ +# Process PNG eXIf/zXIf chunk +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) Pointer to tag table +# Returns: 1 on success +# Notes: writes new chunk data to ${$$dirInfo{OutBuff}} if writing tag +sub ProcessPNG_eXIf($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $outBuff = $$dirInfo{OutBuff}; + my $dataPt = $$dirInfo{DataPt}; + my $tagInfo = $$dirInfo{TagInfo}; + my $tag = $$tagInfo{TagID}; + my $del = $outBuff && ($$et{DEL_GROUP}{EXIF} or $$et{DEL_GROUP}{IFD0}); + my $type; + + if ($$dataPt =~ /^Exif\0\0/) { + $et->Warn('Improper "Exif00" header in EXIF chunk'); + $$dataPt = substr($$dataPt, 6); + $$dirInfo{DataLen} = length $$dataPt; + $$dirInfo{DirLen} -= 6 if $$dirInfo{DirLen}; + } + if ($$dataPt =~ /^(\0|II|MM)/) { + $type = $1; + } elsif ($del) { + $et->VPrint(0, " Deleting invalid $tag chunk"); + $$outBuff = ''; + ++$$et{CHANGED}; + return 1; + } else { + $et->Warn("Invalid $tag chunk"); + return 0; + } + if ($type eq "\0") { # is this compressed EXIF? + my $buf = substr($$dataPt, 5); + # go around again to uncompress the data + $tagTablePtr = GetTagTable('Image::ExifTool::PNG::Main'); + return FoundPNG($et, $tagTablePtr, $$tagInfo{TagID}, \$buf, 2, $outBuff); + } elsif (not $outBuff) { + return $et->ProcessTIFF($dirInfo); + # (zxIf was not adopted) + #} elsif ($del and ($et->Options('Compress') xor lc($tag) eq 'zxif')) { + } elsif ($del and lc($tag) eq 'zxif') { + $et->VPrint(0, " Deleting $tag chunk"); + $$outBuff = ''; + ++$$et{CHANGED}; + } elsif ($$et{EDIT_DIRS}{IFD0}) { + $$outBuff = $et->WriteDirectory($dirInfo, $tagTablePtr, + \&Image::ExifTool::WriteTIFF); + DoneDir($et, 'IFD0', $outBuff, $$tagInfo{NonStandard}); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract meta information from a PNG image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid PNG image, or -1 on write error +sub ProcessPNG($$) +{ + my ($et, $dirInfo) = @_; + my $outfile = $$dirInfo{OutFile}; + my $raf = $$dirInfo{RAF}; + my $datChunk = ''; + my $datCount = 0; + my $datBytes = 0; + my $fastScan = $et->Options('FastScan'); + my $hash = $$et{ImageDataHash}; + my ($n, $sig, $err, $hbuf, $dbuf, $cbuf); + my ($wasHdr, $wasEnd, $wasDat, $doTxt, @txtOffset); + + # check to be sure this is a valid PNG/MNG/JNG image + return 0 unless $raf->Read($sig,8) == 8 and $pngLookup{$sig}; + + if ($outfile) { + delete $$et{TextChunkType}; + Write($outfile, $sig) or $err = 1 if $outfile; + # can only add tags in Main and TextualData tables + $$et{ADD_PNG} = $et->GetNewTagInfoHash( + \%Image::ExifTool::PNG::Main, + \%Image::ExifTool::PNG::TextualData); + # initialize with same directories, with PNG tags taking priority + $et->InitWriteDirs(\%pngMap,'PNG'); + } else { + # disable buffering in FastScan mode + $$raf{NoBuffer} = 1 if $fastScan; + } + my ($fileType, $hdrChunk, $endChunk) = @{$pngLookup{$sig}}; + $et->SetFileType($fileType); # set the FileType tag + SetByteOrder('MM'); # PNG files are big-endian + my $tagTablePtr = GetTagTable('Image::ExifTool::PNG::Main'); + my $mngTablePtr; + if ($fileType ne 'PNG') { + $mngTablePtr = GetTagTable('Image::ExifTool::MNG::Main'); + } + my $verbose = $et->Options('Verbose'); + my $validate = $et->Options('Validate'); + my $out = $et->Options('TextOut'); + + # scan ahead to find offsets of all text chunks after IDAT + if ($outfile) { + while ($raf->Read($hbuf,8) == 8) { + my ($len, $chunk) = unpack('Na4',$hbuf); + last if $len > 0x7fffffff; + if ($wasDat) { + last if $noLeapFrog{$chunk}; # (don't move text across these chunks) + push @txtOffset, $raf->Tell() - 8 if $isTxtChunk{$chunk}; + } elsif ($isDatChunk{$chunk}) { + $wasDat = $chunk; + } + $raf->Seek($len + 4, 1) or last; # skip chunk data + } + $raf->Seek(8,0) or $et->Error('Error seeking in file'), return -1; + undef $wasDat; + } + + # process the PNG/MNG/JNG chunks + undef $noCompressLib; + for (;;) { + if ($doTxt) { + # read text chunks that were found after IDAT so we can write them before + $raf->Seek(shift(@txtOffset), 0) or $et->Error('Seek error'), last; + # (this is the IDAT offset if @txtOffset is now empty) + undef $doTxt unless @txtOffset; + } + $n = $raf->Read($hbuf,8); # read chunk header + + if ($wasEnd) { + last unless $n; # stop now if normal end of PNG + $et->WarnOnce("Trailer data after $fileType $endChunk chunk", 1); + last if $n < 8; + $$et{SET_GROUP1} = 'Trailer'; + } elsif ($n != 8) { + $et->Warn("Truncated $fileType image") unless $wasEnd; + last; + } + my ($len, $chunk) = unpack('Na4',$hbuf); + if ($len > 0x7fffffff) { + $et->Warn("Invalid $fileType chunk size") unless $wasEnd; + last; + } + if ($verbose) { + print $out " Moving $chunk from after IDAT ($len bytes)\n" if $doTxt; + # don't dump image data chunks in verbose mode (only give count instead) + if ($datCount and $chunk ne $datChunk) { + my $s = $datCount > 1 ? 's' : ''; + print $out "$fileType $datChunk ($datCount chunk$s, total $datBytes bytes)\n"; + print $out "$$et{INDENT}(ImageDataHash: $datBytes bytes of $datChunk data)\n" if $hash; + $datCount = $datBytes = 0; + } + } + unless ($wasHdr) { + if ($chunk eq $hdrChunk) { + $wasHdr = 1; + } elsif ($hdrChunk eq 'IHDR' and $chunk eq 'CgBI') { + $et->Warn('Non-standard PNG image (Apple iPhone format)'); + } else { + $et->WarnOnce("$fileType image did not start with $hdrChunk"); + } + } + if ($outfile and ($isDatChunk{$chunk} or $chunk eq $endChunk) and @txtOffset) { + # continue processing here after we move the text chunks from after IDAT + push @txtOffset, $raf->Tell() - 8; + $doTxt = 1; # process text chunks now + next; + } + if ($isDatChunk{$chunk}) { + if ($fastScan and $fastScan >= 2) { + $et->VPrint(0,"End processing at $chunk chunk due to FastScan=$fastScan setting"); + last; + } + $datChunk = $chunk; + $datCount++; + $datBytes += $len; + $wasDat = $chunk; + } else { + $datChunk = ''; + } + if ($outfile) { + # add text chunks (including XMP) before any data chunk end chunk + if ($datChunk or $chunk eq $endChunk) { + # write iCCP chunk now if requested because AddChunks will try + # to add it as a text profile chunk if this isn't successful + # (ie. if Compress::Zlib wasn't available) + Add_iCCP($et, $outfile); + AddChunks($et, $outfile) or $err = 1; # add all text chunks + AddChunks($et, $outfile, 'IFD0') or $err = 1; # and eXIf chunk + } elsif ($chunk eq 'PLTE') { + # iCCP chunk must come before PLTE (and IDAT, handled above) + # (ignore errors -- will add later as text profile if this fails) + Add_iCCP($et, $outfile); + } + } + if ($chunk eq $endChunk) { + # read CRC + unless ($raf->Read($cbuf,4) == 4) { + $et->Warn("Truncated $fileType $endChunk chunk") unless $wasEnd; + last; + } + $verbose and print $out "$fileType $chunk (end of image)\n"; + $wasEnd = 1; + if ($outfile) { + # write the IEND/MEND chunk with CRC + Write($outfile, $hbuf, $cbuf) or $err = 1; + if ($$et{DEL_GROUP}{Trailer}) { + if ($raf->Read($hbuf, 1)) { + $verbose and printf $out " Deleting PNG trailer\n"; + ++$$et{CHANGED}; + } + } else { + # copy over any existing trailer data + my $tot = 0; + for (;;) { + $n = $raf->Read($hbuf, 65536) or last; + $tot += $n; + Write($outfile, $hbuf) or $err = 1; + } + $tot and $verbose and printf $out " Copying PNG trailer ($tot bytes)\n"; + } + last; + } + next; + } + if ($datChunk) { + my $chunkSizeLimit = 10000000; # largest chunk to read into memory + if ($outfile) { + # avoid loading very large data chunks into memory + if ($len > $chunkSizeLimit) { + Write($outfile, $hbuf) or $err = 1; + Image::ExifTool::CopyBlock($raf, $outfile, $len+4) or $et->Error("Error copying $datChunk"); + next; + } + # skip over data chunks if possible/necessary + } elsif (not $validate or $len > $chunkSizeLimit) { + if ($hash) { + $et->ImageDataHash($raf, $len); + $raf->Read($cbuf, 4) == 4 or $et->Warn('Truncated data'), last; + } else { + $raf->Seek($len + 4, 1) or $et->Warn('Seek error'), last; + } + next; + } + } elsif ($wasDat and $isTxtChunk{$chunk}) { + my $msg; + if (not $outfile) { + $msg = 'may be ignored by some readers'; + } elsif (defined $doTxt) { # $doTxt == 0 if we crossed a noLeapFrog chunk + $msg = "can't be moved"; # (but could be deleted then added back again) + } else { + $msg = 'fixed'; + } + $et->WarnOnce("Text/EXIF chunk(s) found after $$et{FileType} $wasDat ($msg)", 1); + } + # read chunk data and CRC + unless ($raf->Read($dbuf,$len)==$len and $raf->Read($cbuf, 4)==4) { + $et->Warn("Corrupted $fileType image") unless $wasEnd; + last; + } + $hash->add($dbuf) if $hash and $datChunk; # add to hash if necessary + if ($verbose or $validate or ($outfile and not $fastScan)) { + # check CRC when in verbose mode (since we don't care about speed) + my $crc = CalculateCRC(\$hbuf, undef, 4); + $crc = CalculateCRC(\$dbuf, $crc); + unless ($crc == unpack('N',$cbuf)) { + my $msg = "Bad CRC for $chunk chunk"; + $outfile ? $et->Error($msg, 1) : $et->Warn($msg); + } + if ($datChunk) { + Write($outfile, $hbuf, $dbuf, $cbuf) or $err = 1 if $outfile; + next; + } + # just skip over any text chunk found after IDAT + if ($outfile and $wasDat) { + if ($isTxtChunk{$chunk} and not defined $doTxt) { + ++$$et{CHANGED} if $$et{FORCE_WRITE}{PNG}; + print $out " Deleting $chunk that was moved ($len bytes)\n" if $verbose; + next; + } + # done moving text if we hit one of these chunks + $doTxt = 0 if $noLeapFrog{$chunk}; + } + if ($verbose) { + print $out "$fileType $chunk ($len bytes):\n"; + $et->VerboseDump(\$dbuf, Addr => $raf->Tell() - $len - 4) if $verbose > 2; + } + } + # translate case of chunk names that have changed since the first implementation + if (not $$tagTablePtr{$chunk} and $stdCase{lc $chunk}) { + my $stdChunk = $stdCase{lc $chunk}; + if ($outfile and ($$et{EDIT_DIRS}{IFD0} or $stdChunk !~ /^[ez]xif$/i)) { + $et->Warn("Changed $chunk chunk to $stdChunk", 1); + ++$$et{CHANGED}; + } else { + $et->Warn("$chunk chunk should be $stdChunk", 1); + } + $chunk = $stdCase{lc $chunk}; + } + # only extract information from chunks in our tables + my ($theBuff, $outBuff); + $outBuff = \$theBuff if $outfile; + if ($$tagTablePtr{$chunk}) { + FoundPNG($et, $tagTablePtr, $chunk, $dbuf, undef, $outBuff); + } elsif ($mngTablePtr and $$mngTablePtr{$chunk}) { + FoundPNG($et, $mngTablePtr, $chunk, $dbuf, undef, $outBuff); + } + if ($outfile) { + if (defined $theBuff) { + next unless length $theBuff; # empty if we deleted the information + # change chunk type if necessary + if ($$et{TextChunkType}) { + $chunk = $$et{TextChunkType}; + delete $$et{TextChunkType}; + } + $hbuf = pack('Na4', length($theBuff), $chunk); + $dbuf = $theBuff; + my $crc = CalculateCRC(\$hbuf, undef, 4); + $crc = CalculateCRC(\$dbuf, $crc); + $cbuf = pack('N', $crc); + } + Write($outfile, $hbuf, $dbuf, $cbuf) or $err = 1; + } + } + delete $$et{SET_GROUP1}; + return -1 if $outfile and ($err or not $wasEnd); + return 1; # this was a valid PNG/MNG/JNG image +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PNG - Read and write PNG meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read and +write PNG (Portable Network Graphics), MNG (Multi-image Network Graphics) +and JNG (JPEG Network Graphics) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.libpng.org/pub/png/spec/1.2/> + +=item L<http://www.faqs.org/docs/png/> + +=item L<http://www.libpng.org/pub/mng/> + +=item L<http://www.libpng.org/pub/png/spec/register/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PNG Tags>, +L<Image::ExifTool::TagNames/MNG Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/PPM.pm b/ExifTool/lib/Image/ExifTool/PPM.pm new file mode 100644 index 0000000..321f661 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PPM.pm @@ -0,0 +1,169 @@ +#------------------------------------------------------------------------------ +# File: PPM.pm +# +# Description: Read and write PPM meta information +# +# Revisions: 09/03/2005 - P. Harvey Created +# +# References: 1) http://netpbm.sourceforge.net/doc/ppm.html +# 2) http://netpbm.sourceforge.net/doc/pgm.html +# 3) http://netpbm.sourceforge.net/doc/pbm.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::PPM; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.10'; + +#------------------------------------------------------------------------------ +# Read or write information in a PPM/PGM/PBM image +# Inputs: 0) ExifTool object reference, 1) Directory information reference +# Returns: 1 on success, 0 if this wasn't a valid PPM file, -1 on write error +sub ProcessPPM($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my ($buff, $num, $type, %info); +# +# read as much of the image as necessary to extract the header and comments +# + for (;;) { + if (defined $buff) { + # need to read some more data + my $tmp; + return 0 unless $raf->Read($tmp, 1024); + $buff .= $tmp; + } else { + return 0 unless $raf->Read($buff, 1024); + } + # verify this is a valid PPM file + return 0 unless $buff =~ /^P([1-6])\s+/g; + $num = $1; + # note: may contain comments starting with '#' + if ($buff =~ /\G#/gc) { + # must read more if we are in the middle of a comment + next unless $buff =~ /\G ?(.*[\n\r]+(#.*[\n\r]+)*)\s*/g; + $info{Comment} = $1; + next if $buff =~ /\G#/gc; + } else { + delete $info{Comment}; + } + next unless $buff =~ /\G(\S+)\s+(\S+)\s+/g; + $info{ImageWidth} = $1; + $info{ImageHeight} = $2; + $type = [qw{PPM PBM PGM}]->[$num % 3]; + last if $type eq 'PBM'; # (no MaxVal for PBM images) + if ($buff =~ /\G\s*#/gc) { + next unless $buff =~ /\G ?(.*[\n\r]+(#.*[\n\r]+)*)\s*/g; + $info{Comment} = '' unless exists $info{Comment}; + $info{Comment} .= $1; + next if $buff =~ /\G#/gc; + } + next unless $buff =~ /\G(\S+)\s/g; + $info{MaxVal} = $1; + last; + } + # validate numerical values + foreach (keys %info) { + next if $_ eq 'Comment'; + return 0 unless $info{$_} =~ /^\d+$/; + } + if (defined $info{Comment}) { + $info{Comment} =~ s/^# ?//mg; # remove "# " at the start of each line + $info{Comment} =~ s/[\n\r]+$//; # remove trailing newline + } + $et->SetFileType($type); + my $len = pos($buff); +# +# rewrite the file if requested +# + if ($outfile) { + my $nvHash; + my $newComment = $et->GetNewValue('Comment', \$nvHash); + my $oldComment = $info{Comment}; + if ($et->IsOverwriting($nvHash, $oldComment)) { + ++$$et{CHANGED}; + $et->VerboseValue('- Comment', $oldComment) if defined $oldComment; + $et->VerboseValue('+ Comment', $newComment) if defined $newComment; + } else { + $newComment = $oldComment; # use existing comment + } + my $hdr = "P$num\n"; + if (defined $newComment) { + $newComment =~ s/\n/\n# /g; + $hdr .= "# $newComment\n"; + } + $hdr .= "$info{ImageWidth} $info{ImageHeight}\n"; + $hdr .= "$info{MaxVal}\n" if $type ne 'PBM'; + # write header and start of image + Write($outfile, $hdr, substr($buff, $len)) or return -1; + # copy over the rest of the image + while ($raf->Read($buff, 0x10000)) { + Write($outfile, $buff) or return -1; + } + return 1; + } +# +# save extracted information +# + if ($verbose > 2) { + print $out "$type header ($len bytes):\n"; + $et->VerboseDump(\$buff, Len => $len); + } + my $tag; + foreach $tag (qw{Comment ImageWidth ImageHeight MaxVal}) { + $et->FoundTag($tag, $info{$tag}) if defined $info{$tag}; + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PPM - Read and write PPM meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read and +write PPM (Portable Pixel Map), PGM (Portable Gray Map) and PBM (Portable +BitMap) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://netpbm.sourceforge.net/doc/ppm.html> + +=item L<http://netpbm.sourceforge.net/doc/pgm.html> + +=item L<http://netpbm.sourceforge.net/doc/pbm.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PPM Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/PSP.pm b/ExifTool/lib/Image/ExifTool/PSP.pm new file mode 100644 index 0000000..aa88236 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PSP.pm @@ -0,0 +1,306 @@ +#------------------------------------------------------------------------------ +# File: PSP.pm +# +# Description: Read Paint Shop Pro meta information +# +# Revisions: 2010/01/23 - P. Harvey Created +# +# References: 1) http://www.jasc.com/support/kb/articles/pspspec.asp +#------------------------------------------------------------------------------ + +package Image::ExifTool::PSP; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.05'; + +sub ProcessExtData($$$); + +# PSP info +%Image::ExifTool::PSP::Main = ( + GROUPS => { 2 => 'Image' }, + VARS => { ALPHA_FIRST => 1 }, + NOTES => q{ + Tags extracted from Paint Shop Pro images (PSP, PSPIMAGE, PSPFRAME, + PSPSHAPE, PSPTUBE and TUB extensions). + }, + # FileVersions: + # 3.0 => PSP 5 + # 4.0 => PSP 6 + # 5.0 => PSP 7 + # 6.0 => PSP 8 + # 7.0 => PSP 9 + # ? => PSP X + # ? => PSP X1 (is this the same as X?) + # ? => PSP X2 + # 10.0 => PSP X3 (= PSP 13) + FileVersion => { PrintConv => '$val=~tr/ /./; $val' }, + 0 => [ + { + Condition => '$$self{PSPFileVersion} > 3', + Name => 'ImageInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::PSP::Image', + Start => 4, + }, + }, + { + Name => 'ImageInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::PSP::Image', + }, + }, + ], + 1 => { + Name => 'CreatorInfo', + SubDirectory => { TagTable => 'Image::ExifTool::PSP::Creator' }, + }, + 10 => { + Name => 'ExtendedInfo', + SubDirectory => { TagTable => 'Image::ExifTool::PSP::Ext' }, + }, + # this is inside the composite image bank block (16), which I don't want to parse... + #18 => { + # Name => 'PreviewImage', + # Groups => { 2 => 'Preview' }, + # RawConv => '$self->ValidateImage(\$val,$tag)', + #}, +); + +# the PSP image block +%Image::ExifTool::PSP::Image = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { Name => 'ImageWidth', Format => 'int32u' }, + 4 => { Name => 'ImageHeight', Format => 'int32u' }, + 8 => { Name => 'ImageResolution', Format => 'double' }, + 16 => { + Name => 'ResolutionUnit', + Format => 'int8u', + PrintConv => { + 0 => 'None', + 1 => 'inches', + 2 => 'cm', + }, + }, + 17 => { + Name => 'Compression', + Format => 'int16u', + PrintConv => { + 0 => 'None', + 1 => 'RLE', + 2 => 'LZ77', + 3 => 'JPEG', + }, + }, + 19 => { Name => 'BitsPerSample',Format => 'int16u' }, + 21 => { Name => 'Planes', Format => 'int16u' }, + 23 => { Name => 'NumColors', Format => 'int32u' }, +); + +# the PSP creator data block +%Image::ExifTool::PSP::Creator = ( + PROCESS_PROC => \&ProcessExtData, + GROUPS => { 2 => 'Image' }, + PRIORITY => 0, # prefer EXIF if it exists + 0 => 'Title', + 1 => { + Name => 'CreateDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + ValueConv => 'Image::ExifTool::ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 2 => { + Name => 'ModifyDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + ValueConv => 'Image::ExifTool::ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 3 => { + Name => 'Artist', + Groups => { 2 => 'Author' }, + }, + 4 => { + Name => 'Copyright', + Groups => { 2 => 'Author' }, + }, + 5 => 'Description', + 6 => { + Name => 'CreatorAppID', + Format => 'int32u', + PrintConv => { + 0 => 'Unknown', + 1 => 'Paint Shop Pro', + }, + }, + 7 => { + Name => 'CreatorAppVersion', + Format => 'int8u', + Count => 4, + ValueConv => 'join(" ",reverse split " ", $val)', # low byte first + PrintConv => '$val=~tr/ /./; $val', + }, +); + +# the PSP extended data block +%Image::ExifTool::PSP::Ext = ( + PROCESS_PROC => \&ProcessExtData, + GROUPS => { 2 => 'Image' }, + 3 => { + Name => 'EXIFInfo', #(don't change this name, it is used in the code) + SubDirectory => { TagTable => 'Image::ExifTool::Exif::Main' }, + }, +); + +#------------------------------------------------------------------------------ +# Extract information from the extended data block +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessExtData($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + my $pos = 0; + # loop through sub-blocks + while ($pos + 10 < $dirLen) { + unless (substr($$dataPt, $pos, 4) eq "~FL\0") { + $et->Warn('Lost synchronization while reading sub blocks'); + last; + } + my $tag = Get16u($dataPt, $pos + 4); + my $len = Get32u($dataPt, $pos + 6); + $pos += 10 + $len; + if ($pos > $dirLen) { + $et->Warn("Truncated sub block ID=$tag len=$len"); + last; + } + next unless $$tagTablePtr{$tag}; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag) or next; + my $start = $pos - $len; + unless ($$tagInfo{Name} eq 'EXIFInfo') { + $et->HandleTag($tagTablePtr, $tag, undef, + TagInfo => $tagInfo, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + DataLen => length $$dataPt, + Start => $start, + Size => $len, + ); + next; + } + # validate EXIF block header and set byte order + next unless $len > 14 and substr($$dataPt, $pos - $len, 6) eq "Exif\0\0"; + next unless SetByteOrder(substr($$dataPt, $start + 6, 2)); + # This is REALLY annoying... They use a standard TIFF offset to point to + # the first IFD, but after that the offsets are relative to the start of + # the IFD instead of the TIFF base, which means that I must handle it as a + # special case. Dumb, dumb... + $start += 14; + my %dirInfo = ( + DirName => 'EXIF', + Parent => 'PSP', + DataPt => $dataPt, + DataPos => -$start, # data position relative to Base + DataLen => length $$dataPt, + DirStart => $start, + Base => $start + $$dirInfo{DataPos}, # absolute base offset + Multi => 0, + ); + my $exifTable = GetTagTable($$tagInfo{SubDirectory}{TagTable}); + Image::ExifTool::Exif::ProcessExif($et, \%dirInfo, $exifTable); + SetByteOrder('II'); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from a PSP file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid PSP file +sub ProcessPSP($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $tag, $len, $err); + return 0 unless $raf->Read($buff, 32) == 32 and + $buff eq "Paint Shop Pro Image File\x0a\x1a\0\0\0\0\0" and + $raf->Read($buff, 4) == 4; + $et->SetFileType(); + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::PSP::Main'); + my @a = unpack('v*', $buff); + # figure out block header length for this format PSP file + my $hlen = $a[0] > 3 ? 10 : 14; + $$et{PSPFileVersion} = $a[0]; # save for use in Condition + $et->HandleTag($tagTablePtr, FileVersion => "@a"); + # loop through blocks in file + my $pos = 36; + for (;;) { + last unless $raf->Read($buff, $hlen) == $hlen; + unless ($buff =~ /^~BK\0/) { + $et->Warn('Lost synchronization while reading main PSP blocks'); + last; + } + $tag = Get16u(\$buff, 4); + $len = Get32u(\$buff, $hlen - 4); + $pos += $hlen + $len; + unless ($$tagTablePtr{$tag}) { + $raf->Seek($len, 1) or $err=1, last; + next; + } + $raf->Read($buff, $len) == $len or $err=1, last; + $et->HandleTag($tagTablePtr, $tag, $buff, + DataPt => \$buff, + DataPos => $pos - $len, + Size => $len, + ); + } + $err and $et->Warn("Truncated main block ID=$tag len=$len"); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PSP - Read Paint Shop Pro meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +information from Paint Shop Pro images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.jasc.com/support/kb/articles/pspspec.asp> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PSP Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Palm.pm b/ExifTool/lib/Image/ExifTool/Palm.pm new file mode 100644 index 0000000..f47db86 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Palm.pm @@ -0,0 +1,403 @@ +#------------------------------------------------------------------------------ +# File: Palm.pm +# +# Description: Read Palm Database files +# +# Revisions: 2014/05/28 - P. Harvey Created +# +# References: 1) http://wiki.mobileread.com/wiki/PDB +# 2) http://wiki.mobileread.com/wiki/MOBI +#------------------------------------------------------------------------------ + +package Image::ExifTool::Palm; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.00'; + +sub ProcessEXTH($$$); + +# type/creator ID's for Palm database files +my %palmTypes = ( + '.pdfADBE' => 'Adobe Reader', + 'TEXtREAd' => 'PalmDOC', + 'BVokBDIC' => 'BDicty', + 'DB99DBOS' => 'DB (Database program)', + 'PNRdPPrs' => 'eReader', + 'DataPPrs' => 'eReader', + 'vIMGView' => 'FireViewer (ImageViewer)', + 'PmDBPmDB' => 'HanDBase', + 'InfoINDB' => 'InfoView', + 'ToGoToGo' => 'iSilo', + 'SDocSilX' => 'iSilo 3', + 'JbDbJBas' => 'JFile', + 'JfDbJFil' => 'JFile Pro', + 'DATALSdb' => 'LIST', + 'Mdb1Mdb1' => 'MobileDB', + 'BOOKMOBI' => 'Mobipocket', + 'DataPlkr' => 'Plucker', + 'DataSprd' => 'QuickSheet', + 'SM01SMem' => 'SuperMemo', + 'TEXtTlDc' => 'TealDoc', + 'InfoTlIf' => 'TealInfo', + 'DataTlMl' => 'TealMeal', + 'DataTlPt' => 'TealPaint', + 'dataTDBP' => 'ThinkDB', + 'TdatTide' => 'Tides', + 'ToRaTRPW' => 'TomeRaider', + 'zTXTGPlm' => 'Weasel', + 'BDOCWrdS' => 'WordSmith', +); + +my %dateTimeInfo = ( + # like QuickTime, the time zero should be Jan 1, 1904, but not all software writes this, + # so assume a time zero of Jan 1, 1970 if the date is before this + RawConv => q{ + my $offset = (66 * 365 + 17) * 24 * 3600; + return $val - $offset if $val >= $offset; + return $val; + }, + ValueConv => 'ConvertUnixTime($val, 1)', # (UTC written by "EPUB Converter", ref PH) + PrintConv => '$self->ConvertDateTime($val)', +); + +# Palm Database header information +%Image::ExifTool::Palm::Main = ( + GROUPS => { 0 => 'Palm', 1 => 'Palm', 2 => 'Document' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int32u', + NOTES => q{ + Information extracted from Palm database files (PDB and PRC extensions), + Mobipocket electronic books (MOBI), and Amazon Kindle KF7 and KF8 books (AZW + and AZW3). + }, + 0 => { Name => 'DatabaseName', Format => 'string[32]' }, + # 8 - int16u: file attributes (not very useful) + # 8.5 - int16u: version + 9 => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + %dateTimeInfo, + }, + 10 => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + %dateTimeInfo, + }, + 11 => { + Name => 'LastBackupDate', + Groups => { 2 => 'Time' }, + %dateTimeInfo, + }, + 12 => 'ModificationNumber', + 15 => { + Name => 'PalmFileType', + Format => 'undef[8]', + PrintConv => \%palmTypes, + }, +); + + +# MOBI header tags +%Image::ExifTool::Palm::MOBI = ( + GROUPS => { 0 => 'Palm', 1 => 'MOBI', 2 => 'Document' }, + NOTES => q{ + Information extracted from the MOBI header of Mobipocket and Amazon Kindle + KF7 and KF8 files. + }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int32u', + 0 => { + Name => 'Compression', + Format => 'int16u', + PrintConv => { + 1 => 'None', + 2 => 'PalmDOC', + 17480 => 'HUFF/CDIC', + }, + }, + 1 => { + Name => 'UncompressedTextLength', + PrintConv => \&Image::ExifTool::ConvertFileSize, + }, + 3 => { + Name => 'Encryption', + PrintConv => { + 0 => 'None', + 1 => 'Old Mobipocket', + 2 => 'Mobipocket', + }, + }, + 6 => { + Name => 'MobiType', + PrintConv => { + 2 => 'Mobipocket Book', + 3 => 'PalmDoc Book', + 4 => 'Audio', + 232 => 'mobipocket? generated by kindlegen1.2', + 248 => 'KF8: generated by kindlegen2', + 257 => 'News', + 258 => 'News_Feed', + 259 => 'News_Magazine', + 513 => 'PICS', + 514 => 'WORD', + 515 => 'XLS', + 516 => 'PPT', + 517 => 'TEXT', + 518 => 'HTML', + }, + }, + 7 => { + Name => 'CodePage', + RawConv => '$$self{CodePage} = $val', + PrintConv => { + # just define commonly used code pages + # (a much more complete list may be found in FlashPix.pm) + 1252 => 'Windows Latin 1 (Western European)', + 65001 => 'Unicode (UTF-8)', + }, + }, + 9 => 'MobiVersion', + 21 => 'BookName', # this is actually an offset, but replace it with the string later + 26 => 'MinimumVersion', +); + +# MOBI extended header tags +%Image::ExifTool::Palm::EXTH = ( + GROUPS => { 0 => 'Palm', 1 => 'MOBI', 2 => 'Document' }, + FORMAT => 'string', + NOTES => 'Information extracted from the MOBI extended header.', + PROCESS_PROC => \&ProcessEXTH, + 1 => 'DRMServerID', + 2 => 'DRMCommerceID', + 3 => 'DRM_E-BookBaseID', + 100 => { Name => 'Author', Groups => { 2 => 'Author' } }, + 101 => 'Publisher', + 102 => 'Imprint', + 103 => 'Description', + 104 => 'ISBN', + 105 => { Name => 'Subject', List => 1 }, + 106 => { + Name => 'PublishDate', + Groups => { 2 => 'Time' }, + ValueConv => q{ + require Image::ExifTool::XMP; + Image::ExifTool::XMP::ConvertXMPDate($val, 1); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 107 => 'Review', + 108 => 'Contributor', + 109 => { Name => 'Rights', Groups => { 2 => 'Author' } }, + 110 => 'SubjectCode', + 111 => 'BookType', + 112 => 'Source', + 113 => 'ASIN', + 114 => 'BookVersion', + 115 => { Name => 'SampleFlag', Format => 'int32u' }, + 116 => { Name => 'StartReading', Format => 'int32u' }, + 117 => 'Adult', + 118 => 'RetailPrice', + 119 => 'RetailPriceCurrency', + # 121 => 'KF8BoundaryOffset', + 125 => { Name => 'ResourceCount', Format => 'int32u' }, + 129 => 'KF8CoverURI', + 200 => 'DictionaryShortName', + # 201 => { Name => 'CoverOffset', Format => 'int32u' }, + # 202 => { Name => 'ThumbOffset', Format => 'int32u' }, + # 203 => 'HasFakeCover', + 204 => { + Name => 'CreatorSoftware', + Format => 'int32u', + PrintConv => { + 1 => 'Mobigen', + 2 => 'Mobipocket', + 200 => 'Kindlegen (Windows)', + 201 => 'Kindlegen (Linux)', + 202 => 'Kindlegen (Mac)', + }, + }, + 205 => { Name => 'CreatorMajorVersion', Format => 'int32u' }, + 206 => { Name => 'CreatorMinorVersion', Format => 'int32u' }, + 207 => { Name => 'CreatorBuildNumber', Format => 'int32u' }, + 208 => 'Watermark', + 209 => 'Tamper-proofKeys', + # 300 => 'FontSignature', + 401 => { Name => 'ClippingLimit', Format => 'int8u' }, + 402 => 'PublisherLimit', + 404 => { + Name => 'TextToSpeech', + Format => 'int8u', + PrintConv => { 0 => 'Enabled', 1 => 'Disabled' }, + }, + 405 => { Name => 'RentalFlag', Format => 'int8u' }, #? + 406 => 'RentalExpirationDate', + 501 => { Name => 'CDEType', Format => 'int32u' }, + 502 => 'LastUpdateTime', + 503 => 'UpdatedTitle', + 504 => 'ASIN2', + 524 => 'Language', + 525 => 'Alignment', + 535 => 'CreatorBuildNumber2', +); + +#------------------------------------------------------------------------------ +# Process the MOBI extended header +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 (EXTH should have already been validated) +sub ProcessEXTH($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $enc = $$dirInfo{Encoding} || 'UTF8'; + my $dirLen = length $$dataPt; + my ($index, $pos); + + $et->VerboseDir('EXTH', $$dirInfo{NumEntries}, $dirLen); + + # process the EXTH entries + for ($index=0, $pos=0; ; ++$index) { + last if $pos + 8 > $dirLen; + my $tag = Get32u($dataPt, $pos); + my $len = Get32u($dataPt, $pos + 4); + last if $len < 8 or $pos + $len > $dirLen; + my $key = $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos + 8, + Size => $len - 8, + Index => $index, + ); + # recode text if necessary + $$et{VALUE}{$key} = $et->Decode($$et{VALUE}{$key}, $enc) if $key; + $pos += $len; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from a Palm DB file +# Inputs: 0) ExifTool ref, 1) dirInfo reference +# Returns: 1 if this was a recognized PDB file, 0 otherwise +sub ProcessPDB($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $buf2, $size, $enc); + my $verbose = $et->Options('Verbose'); + + # verify this is a valid Palm DB file + return 0 unless $raf->Read($buff, 86) == 86; + my $type = $palmTypes{substr($buff, 60, 8)}; + return 0 unless $type; +# +# Read and process the Palm DB file header +# + $et->SetFileType($type eq 'Mobipocket' ? 'MOBI' : 'PDB'); + SetByteOrder('MM'); + + my $tagTablePtr = GetTagTable('Image::ExifTool::Palm::Main'); + $et->ProcessDirectory({ DataPt => \$buff }, $tagTablePtr); + + return 1 unless $type eq 'Mobipocket' and Get16u(\$buff, 76); +# +# Read and process MOBI header (should be the first record) +# + my $offset = Get32u(\$buff, 78); # get offset to first record + unless ($raf->Seek($offset, 0) and $raf->Read($buff, 274) == 274) { + $et->Warn('Truncated MOBI header'); + return 1; + } + unless (substr($buff, 16, 4) eq 'MOBI') { + $et->Warn('Invalid MOBI header'); + return 1; + } + $tagTablePtr = GetTagTable('Image::ExifTool::Palm::MOBI'); + $et->ProcessDirectory({ DataPt => \$buff }, $tagTablePtr); + + # get text encoding + $enc = $Image::ExifTool::charsetName{"cp$$et{CodePage}"} if $$et{CodePage}; + $enc = 'UTF8' unless $enc; + + # extract the BookName string + my $off = Get32u(\$buff, 84); + my $len = Get32u(\$buff, 88); + + $raf->Seek($offset+$off, 0) and $raf->Read($buf2, $len) == $len or $buf2 = '<err>'; + $$et{VALUE}{BookName} = $et->Decode($buf2, $enc); +# +# Process the MOBI extended header if it exists +# + # first, check the flag bit to see if the EXTH record should exist + my $flag = Get32u(\$buff, 128); + return 1 unless $flag & 0x40; # check extended header flag + + $len = Get32u(\$buff, 20) + 16; # MOBI header length (including PalmDOC header) + + unless ($raf->Seek($offset+$len, 0) and $raf->Read($buf2, 12) == 12 and + substr($buf2,0,4) eq 'EXTH' and ($size = Get32u(\$buf2, 4)) > 12) + { + $et->Warn('Invalid MOBI extended header'); + return 1; + } + + # read and process the MOBI extended header + $size -= 12; + $raf->Read($buff, $size) == $size or $et->Warn('Truncated MOBI extended header'), return 1; + my %dirInfo = ( + DataPt => \$buff, + DataPos => $offset + $len + 12, + NumEntries => Get32u(\$buf2, 8), + Encoding => $enc, + ); + $tagTablePtr = GetTagTable('Image::ExifTool::Palm::EXTH'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Palm - Read Palm Database files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains code to extract metadata from Palm database files (PDB +and PRC extensions), Mobipocket electronic books (MOBI), and Amazon Kindle +KF7 and KF8 books (AZW and AZW3). + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://wiki.mobileread.com/wiki/PDB> + +=item L<http://wiki.mobileread.com/wiki/MOBI> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Palm Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Panasonic.pm b/ExifTool/lib/Image/ExifTool/Panasonic.pm new file mode 100644 index 0000000..01feeab --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Panasonic.pm @@ -0,0 +1,2899 @@ +#------------------------------------------------------------------------------ +# File: Panasonic.pm +# +# Description: Panasonic/Leica maker notes tags +# +# Revisions: 11/10/2004 - P. Harvey Created +# +# References: 1) http://www.compton.nu/panasonic.html (based on FZ10) +# 2) Derived from DMC-FZ3 samples from dpreview.com +# 3) http://johnst.org/sw/exiftags/ +# 4) Tels (http://bloodgate.com/) private communication (tests with FZ5) +# 7) http://homepage3.nifty.com/kamisaka/makernote/makernote_pana.htm (2007/10/02) +# 8) Marcel Coenen private communication (DMC-FZ50) +# 9) http://forums.dpreview.com/forums/read.asp?forum=1033&message=22756430 +# 10) http://bretteville.com/pdfs/M8Metadata_v2.pdf +# 11) http://www.digital-leica.com/lens_codes/index.html +# (now https://www.l-camera-forum.com/leica-news/leica-lens-codes/) +# 12) Joerg - http://www.cpanforum.com/threads/11602 (LX3 firmware 2.0) +# 13) Michael Byczkowski private communication (Leica M9) +# 14) Carl Bretteville private communication (M9) +# 15) Zdenek Mihula private communication (TZ8) +# 16) Olaf Ulrich private communication +# 17) https://exiftool.org/forum/index.php/topic,4922.0.html +# 18) Thomas Modes private communication (G6) +# 19) https://exiftool.org/forum/index.php/topic,5533.0.html +# 20) Bernd-Michael Kemper private communication (DMC-GX80/85) +# 21) Klaus Homeister forum post +# 22) Daniel Beichl private communication (G9) +# 23) Tim Gray private communication (M10 Monochrom) +# JD) Jens Duttke private communication (TZ3,FZ30,FZ50) +#------------------------------------------------------------------------------ + +package Image::ExifTool::Panasonic; + +use strict; +use vars qw($VERSION %leicaLensTypes); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '2.18'; + +sub ProcessLeicaLEIC($$$); +sub WhiteBalanceConv($;$$); + +# Leica lens types (ref 10) +%leicaLensTypes = ( + OTHER => sub { + my ($val, $inv, $conv) = @_; + return undef if $inv or not $val =~ s/ .*//; + return $$conv{$val}; + }, + Notes => q{ + the LensType value is obtained by splitting the stored value into 2 + integers: The stored value divided by 4, and its lower 2 bits. The second + number is used only if necessary to identify certain manually coded lenses + on the M9, or the focal length of some multi-focal lenses. + }, + # All M9 codes (two numbers: first the LensID then the lower 2 bits) + # are ref PH with samples from ref 13. From ref 10, the lower 2 bits of + # the LensType value give the frame selector position for most lenses, + # although for the 28-35-50mm (at least) it gives the focal length selection. + # The M9 also gives the focal length selection but for other lenses the + # lower 3 bits don't change with frame selector position except for the lens + # shows as uncoded for certain lenses and some incorrect positions of the + # frame selector. The bits are zero for uncoded lenses when manually coding + # from the menu on the M9. - PH + # Frame selector bits (from ref 10, M8): + # 1 => '28/90mm frame lines engaged', + # 2 => '24/35mm frame lines engaged', + # 3 => '50/75mm frame lines engaged', + '0 0' => 'Uncoded lens', +# +# NOTE: MUST ADD ENTRY TO %frameSelectorBits below when a new lens is added!!!! +# + # model number(s): + 1 => 'Elmarit-M 21mm f/2.8', # 11134 + 3 => 'Elmarit-M 28mm f/2.8 (III)', # 11804 + 4 => 'Tele-Elmarit-M 90mm f/2.8 (II)', # 11800 + 5 => 'Summilux-M 50mm f/1.4 (II)', # 11868/11856/11114 + 6 => 'Summicron-M 35mm f/2 (IV)', # 11310/11311 + '6 0' => 'Summilux-M 35mm f/1.4', # 11869/11870/11860 + 7 => 'Summicron-M 90mm f/2 (II)', # 11136/11137 + 9 => 'Elmarit-M 135mm f/2.8 (I/II)', # 11829 + '9 0' => 'Apo-Telyt-M 135mm f/3.4', # 11889 + 11 => 'Summaron-M 28mm f/5.6', # ? (ref IB) + 12 => 'Thambar-M 90mm f/2.2', # ? (ref IB) + 16 => 'Tri-Elmar-M 16-18-21mm f/4 ASPH.',# 11626 + '16 1' => 'Tri-Elmar-M 16-18-21mm f/4 ASPH. (at 16mm)', + '16 2' => 'Tri-Elmar-M 16-18-21mm f/4 ASPH. (at 18mm)', + '16 3' => 'Tri-Elmar-M 16-18-21mm f/4 ASPH. (at 21mm)', + 23 => 'Summicron-M 50mm f/2 (III)', # 11817, version (I) in camera menu + 24 => 'Elmarit-M 21mm f/2.8 ASPH.', # 11135/11897 + 25 => 'Elmarit-M 24mm f/2.8 ASPH.', # 11878/11898 + 26 => 'Summicron-M 28mm f/2 ASPH.', # 11604 + 27 => 'Elmarit-M 28mm f/2.8 (IV)', # 11809 + 28 => 'Elmarit-M 28mm f/2.8 ASPH.', # 11606 + 29 => 'Summilux-M 35mm f/1.4 ASPH.', # 11874/11883 + '29 0' => 'Summilux-M 35mm f/1.4 ASPHERICAL', # 11873 (different from "ASPH." model!) + 30 => 'Summicron-M 35mm f/2 ASPH.', # 11879/11882 + 31 => 'Noctilux-M 50mm f/1', # 11821/11822 + '31 0' => 'Noctilux-M 50mm f/1.2', # 11820 + 32 => 'Summilux-M 50mm f/1.4 ASPH.', # 11891/11892 + 33 => 'Summicron-M 50mm f/2 (IV, V)', # 11819/11825/11826/11816, version (II,III) in camera menu + 34 => 'Elmar-M 50mm f/2.8', # 11831/11823/11825 + 35 => 'Summilux-M 75mm f/1.4', # 11814/11815/11810 + 36 => 'Apo-Summicron-M 75mm f/2 ASPH.', # 11637 + 37 => 'Apo-Summicron-M 90mm f/2 ASPH.', # 11884/11885 + 38 => 'Elmarit-M 90mm f/2.8', # 11807/11808, version (II) in camera menu + 39 => 'Macro-Elmar-M 90mm f/4', # 11633/11634 + '39 0' => 'Tele-Elmar-M 135mm f/4 (II)',# 11861 + 40 => 'Macro-Adapter M', # 14409 + 41 => 'Apo-Summicron-M 50mm f/2 ASPH.', #IB + '41 3' => 'Apo-Summicron-M 50mm f/2 ASPH.', #16 + 42 => 'Tri-Elmar-M 28-35-50mm f/4 ASPH.',# 11625 + '42 1' => 'Tri-Elmar-M 28-35-50mm f/4 ASPH. (at 28mm)', + '42 2' => 'Tri-Elmar-M 28-35-50mm f/4 ASPH. (at 35mm)', + '42 3' => 'Tri-Elmar-M 28-35-50mm f/4 ASPH. (at 50mm)', + 43 => 'Summarit-M 35mm f/2.5', # ? (ref PH) + 44 => 'Summarit-M 50mm f/2.5', # ? (ref PH) + 45 => 'Summarit-M 75mm f/2.5', # ? (ref PH) + 46 => 'Summarit-M 90mm f/2.5', # ? + 47 => 'Summilux-M 21mm f/1.4 ASPH.', # ? (ref 11) + 48 => 'Summilux-M 24mm f/1.4 ASPH.', # ? (ref 11) + 49 => 'Noctilux-M 50mm f/0.95 ASPH.', # ? (ref 11) + 50 => 'Elmar-M 24mm f/3.8 ASPH.', # ? (ref 11) + 51 => 'Super-Elmar-M 21mm f/3.4 Asph', # ? (ref 16, frameSelectorBits=1) + '51 2' => 'Super-Elmar-M 14mm f/3.8 Asph', # ? (ref 16) + 52 => 'Apo-Telyt-M 18mm f/3.8 ASPH.', # ? (ref PH/11) + 53 => 'Apo-Telyt-M 135mm f/3.4', #IB + '53 2' => 'Apo-Telyt-M 135mm f/3.4', #16 + '53 3' => 'Apo-Summicron-M 50mm f/2 (VI)', #LR + 58 => 'Noctilux-M 75mm f/1.25 ASPH.', # ? (ref IB) +); + +# M9 frame selector bits for each lens +# 1 = towards lens = 28/90mm or 21mm or Adapter (or Elmarit-M 135mm f/2.8) +# 2 = away from lens = 24/35mm (or 35/135mm on the M9) +# 3 = middle position = 50/75mm or 18mm +my %frameSelectorBits = ( + 1 => 1, + 3 => 1, + 4 => 1, + 5 => 3, + 6 => 2, + 7 => 1, + 9 => 1, # (because lens has special magnifier for the rangefinder) + 16 => 1, # or 2 or 3 + 23 => 3, + 24 => 1, + 25 => 2, + 26 => 1, + 27 => 1, + 28 => 1, + 29 => 2, + 30 => 2, + 31 => 3, + 32 => 3, + 33 => 3, + 34 => 3, + 35 => 3, + 36 => 3, + 37 => 1, + 38 => 1, + 39 => 1, + 40 => 1, + 42 => 1, # or 2 or 3 + 43 => 2, # (NC) + 44 => 3, # (NC) + 45 => 3, + 46 => 1, # (NC) + 47 => 1, # (NC) + 48 => 2, # (NC) + 49 => 3, # (NC) + 50 => 2, # (NC) + 51 => 1, # or 2 (ref 16) + 52 => 3, + 53 => 2, #16 +); + +# conversions for ShootingMode and SceneMode +my %shootingMode = ( + 1 => 'Normal', + 2 => 'Portrait', + 3 => 'Scenery', + 4 => 'Sports', + 5 => 'Night Portrait', + 6 => 'Program', + 7 => 'Aperture Priority', + 8 => 'Shutter Priority', + 9 => 'Macro', + 10 => 'Spot', #7 + 11 => 'Manual', + 12 => 'Movie Preview', #PH (LZ6) + 13 => 'Panning', + 14 => 'Simple', #PH (LZ6) + 15 => 'Color Effects', #7 + 16 => 'Self Portrait', #PH (TZ5) + 17 => 'Economy', #7 + 18 => 'Fireworks', + 19 => 'Party', + 20 => 'Snow', + 21 => 'Night Scenery', + 22 => 'Food', #7 + 23 => 'Baby', #JD + 24 => 'Soft Skin', #PH (LZ6) + 25 => 'Candlelight', #PH (LZ6) + 26 => 'Starry Night', #PH (LZ6) + 27 => 'High Sensitivity', #7 (LZ6) + 28 => 'Panorama Assist', #7 + 29 => 'Underwater', #7 + 30 => 'Beach', #PH (LZ6) + 31 => 'Aerial Photo', #PH (LZ6) + 32 => 'Sunset', #PH (LZ6) + 33 => 'Pet', #JD + 34 => 'Intelligent ISO', #PH (LZ6) + 35 => 'Clipboard', #7 + 36 => 'High Speed Continuous Shooting', #7 + 37 => 'Intelligent Auto', #7 + 39 => 'Multi-aspect', #PH (TZ5) + 41 => 'Transform', #PH (FS7) + 42 => 'Flash Burst', #PH (FZ28) + 43 => 'Pin Hole', #PH (FZ28) + 44 => 'Film Grain', #PH (FZ28) + 45 => 'My Color', #PH (GF1) + 46 => 'Photo Frame', #PH (FS7) + 48 => 'Movie', #PH (GM1) + # 49 - seen for FS4 (snow?) + 51 => 'HDR', #12 + 52 => 'Peripheral Defocus', #Horst Wandres + 55 => 'Handheld Night Shot', #PH (FZ47) + 57 => '3D', #PH (3D1) + 59 => 'Creative Control', #PH (FZ47) + 60 => 'Intelligent Auto Plus', #20 + 62 => 'Panorama', #17 + 63 => 'Glass Through', #17 + 64 => 'HDR', #17 + 66 => 'Digital Filter', #PH (GF5 "Impressive Art", "Cross Process", "Color Select", "Star") + 67 => 'Clear Portrait', #18 + 68 => 'Silky Skin', #18 + 69 => 'Backlit Softness', #18 + 70 => 'Clear in Backlight', #18 + 71 => 'Relaxing Tone', #18 + 72 => "Sweet Child's Face", #18 + 73 => 'Distinct Scenery', #18 + 74 => 'Bright Blue Sky', #18 + 75 => 'Romantic Sunset Glow', #18 + 76 => 'Vivid Sunset Glow', #18 + 77 => 'Glistening Water', #18 + 78 => 'Clear Nightscape', #18 + 79 => 'Cool Night Sky', #18 + 80 => 'Warm Glowing Nightscape', #18 + 81 => 'Artistic Nightscape', #18 + 82 => 'Glittering Illuminations', #18 + 83 => 'Clear Night Portrait', #18 + 84 => 'Soft Image of a Flower', #18 + 85 => 'Appetizing Food', #18 + 86 => 'Cute Dessert', #18 + 87 => 'Freeze Animal Motion', #18 + 88 => 'Clear Sports Shot', #18 + 89 => 'Monochrome', #18 + 90 => 'Creative Control', #18 + 92 => 'Handheld Night Shot', #forum11523 +); + +%Image::ExifTool::Panasonic::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + WRITABLE => 1, + 0x01 => { + Name => 'ImageQuality', + Writable => 'int16u', + Notes => 'quality of the main image, which may be in a different file', + PrintConv => { + 1 => 'TIFF', #PH (FZ20) + 2 => 'High', + 3 => 'Normal', + # 5 - seen this for 1920x1080, 30fps SZ7 video - PH + 6 => 'Very High', #3 (Leica) + 7 => 'RAW', #3 (Leica) + 9 => 'Motion Picture', #PH (LZ6) + 11 => 'Full HD Movie', #PH (V-LUX) + 12 => '4k Movie', #PH (V-LUX) + }, + }, + 0x02 => { + Name => 'FirmwareVersion', + Writable => 'undef', + Notes => q{ + for some camera models such as the FZ30 this may be an internal production + reference number and not the actual firmware version + }, # (ref http://www.stevesforums.com/forums/view_topic.php?id=87764&forum_id=23&) + # (can be either binary or ascii -- add decimal points if binary) + ValueConv => '$val=~/[\0-\x2f]/ ? join(" ",unpack("C*",$val)) : $val', + ValueConvInv => q{ + $val =~ /(\d+ ){3}\d+/ and $val = pack('C*',split(' ', $val)); + length($val) == 4 or warn "Version must be 4 numbers\n"; + return $val; + }, + PrintConv => '$val=~tr/ /./; $val', + PrintConvInv => '$val=~tr/./ /; $val', + }, + 0x03 => { + Name => 'WhiteBalance', + Writable => 'int16u', + PrintConv => { + 1 => 'Auto', + 2 => 'Daylight', + 3 => 'Cloudy', + 4 => 'Incandescent', #PH + 5 => 'Manual', + 8 => 'Flash', + 10 => 'Black & White', #3 (Leica) + 11 => 'Manual 2', #PH (FZ8) + 12 => 'Shade', #PH (FS7) + 13 => 'Kelvin', #PeterK (NC) + 14 => 'Manual 3', #forum9296 + 15 => 'Manual 4', #forum9296 + # also seen 18,26 (forum9296) + 19 => 'Auto (cool)', #PH (Leica C-Lux) + }, + }, + 0x07 => { + Name => 'FocusMode', + Writable => 'int16u', + PrintConv => { + 1 => 'Auto', + 2 => 'Manual', + 4 => 'Auto, Focus button', #4 + 5 => 'Auto, Continuous', #4 + 6 => 'AF-S', #18 (also seen for GF1 - PH) + 7 => 'AF-C', #18 + 8 => 'AF-F', #18 (auto-focus flexible) + }, + }, + 0x0f => [ + { + Name => 'AFAreaMode', + Condition => '$$self{Model} =~ /DMC-FZ10\b/', #JD + Writable => 'int8u', + Count => 2, + Notes => 'DMC-FZ10', + PrintConv => { + '0 1' => 'Spot Mode On', + '0 16' => 'Spot Mode Off', + }, + },{ + Name => 'AFAreaMode', + Writable => 'int8u', + Count => 2, + Notes => 'other models', + PrintConv => { #PH + '0 1' => '9-area', # (FS7) + '0 16' => '3-area (high speed)', # (FZ8) + '0 23' => '23-area', #PH (FZ47,NC) + '0 49' => '49-area', #20 + '0 225' => '225-area', #22 + '1 0' => 'Spot Focusing', # (FZ8) + '1 1' => '5-area', # (FZ8) + '16' => 'Normal?', # (only mode for DMC-LC20) + '16 0' => '1-area', # (FZ8) + '16 16' => '1-area (high speed)', # (FZ8) + # '32 0' is Face Detect for FS7, and Face Detect or Focus Tracking + # for the DMC-FZ200 (ref 17), and Auto is DMC-L1 guess, + '32 0' => 'Tracking', + '32 1' => '3-area (left)?', # (DMC-L1 guess) + '32 2' => '3-area (center)?', # (DMC-L1 guess) + '32 3' => '3-area (right)?', # (DMC-L1 guess) + '64 0' => 'Face Detect', + '64 1' => 'Face Detect (animal detect on)', #forum11194 + '64 2' => 'Face Detect (animal detect off)', #forum11194 + '128 0' => 'Pinpoint focus', #18/forum11194 + '240 0' => 'Tracking', #22 + }, + }, + ], + 0x1a => { + Name => 'ImageStabilization', + Writable => 'int16u', + PrintConv => { + 2 => 'On, Optical', + 3 => 'Off', + 4 => 'On, Mode 2', + 5 => 'On, Optical Panning', #18 + # GF1 also has a "Mode 3" - PH + 6 => 'On, Body-only', #PH (GX7, sensor shift?) + 7 => 'On, Body-only Panning', #forum11194 + 9 => 'Dual IS', #20 + 10 => 'Dual IS Panning', #forum11194 + 11 => 'Dual2 IS', #forum9298 + 12 => 'Dual2 IS Panning', #forum11194 + }, + }, + 0x1c => { + Name => 'MacroMode', + Writable => 'int16u', + PrintConv => { + 1 => 'On', + 2 => 'Off', + 0x101 => 'Tele-Macro', #7 + 0x201 => 'Macro Zoom', #PH (FS7) + }, + }, + 0x1f => { + Name => 'ShootingMode', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => \%shootingMode, + }, + 0x20 => { + Name => 'Audio', + Writable => 'int16u', + PrintConv => { + 1 => 'Yes', + 2 => 'No', + 3 => 'Stereo', #PH (NC) + }, + }, + 0x21 => { #2 + Name => 'DataDump', + Writable => 0, + Binary => 1, + }, + # 0x22 - normally 0, but 2 for 'Simple' ShootingMode in LZ6 sample - PH + 0x23 => { + Name => 'WhiteBalanceBias', + Format => 'int16s', + Writable => 'int16s', + ValueConv => '$val / 3', + ValueConvInv => '$val * 3', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x24 => { + Name => 'FlashBias', + Format => 'int16s', + Writable => 'int16s', + ValueConv => '$val / 3', #17 (older models may not have factor of 3? - PH) + ValueConvInv => '$val * 3', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x25 => { #PH + Name => 'InternalSerialNumber', + Writable => 'undef', + Count => 16, + Notes => q{ + this number is unique, and contains the date of manufacture, but is not the + same as the number printed on the camera body + }, + PrintConv => q{ + return $val unless $val=~/^([A-Z][0-9A-Z]{2})(\d{2})(\d{2})(\d{2})(\d{4})/; + my $yr = $2 + ($2 < 70 ? 2000 : 1900); + return "($1) $yr:$3:$4 no. $5"; + }, + PrintConvInv => '$_=$val; tr/A-Z0-9//dc; s/(.{3})(19|20)/$1/; $_', + }, + 0x26 => { #PH + Name => 'PanasonicExifVersion', + Writable => 'undef', + }, + 0x27 => { + Name => 'VideoFrameRate', + Writable => 'int16u', + Notes => 'only valid for older models', + PrintConv => { + OTHER => sub { shift }, + 0 => 'n/a', + }, + }, + 0x28 => { + Name => 'ColorEffect', + Writable => 'int16u', + # FX30 manual: (ColorMode) natural, vivid, cool, warm, b/w, sepia + PrintConv => { + 1 => 'Off', + 2 => 'Warm', + 3 => 'Cool', + 4 => 'Black & White', + 5 => 'Sepia', + 6 => 'Happy', #PH (FX70) (yes, really. you wouldn't want sad colors now would you?) + 8 => 'Vivid', #PH (SZ3) + }, + }, + 0x29 => { #JD + Name => 'TimeSincePowerOn', + Writable => 'int32u', + Notes => q{ + time in 1/100 s from when the camera was powered on to when the image is + written to memory card + }, + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => sub { # convert to format "[DD days ]HH:MM:SS.ss" + my $val = shift; + my $str = ''; + if ($val >= 24 * 3600) { + my $d = int($val / (24 * 3600)); + $str .= "$d days "; + $val -= $d * 24 * 3600; + } + my $h = int($val / 3600); + $val -= $h * 3600; + my $m = int($val / 60); + $val -= $m * 60; + my $ss = sprintf('%05.2f', $val); + if ($ss >= 60) { + $ss = '00.00'; + ++$m >= 60 and $m -= 60, ++$h; + } + return sprintf("%s%.2d:%.2d:%s",$str,$h,$m,$ss); + }, + PrintConvInv => sub { + my $val = shift; + my @vals = ($val =~ /\d+(?:\.\d*)?/g); + my $sec = 0; + $sec += 24 * 3600 * shift(@vals) if @vals > 3; + $sec += 3600 * shift(@vals) if @vals > 2; + $sec += 60 * shift(@vals) if @vals > 1; + $sec += shift(@vals) if @vals; + return $sec; + }, + }, + 0x2a => { #4 + Name => 'BurstMode', + Writable => 'int16u', + Notes => 'decoding may be different for some models', + PrintConv => { + 0 => 'Off', + 1 => 'On', #PH (TZ5) [was "Low/High Quality" from ref 4] + 2 => 'Auto Exposure Bracketing (AEB)', #17 + 3 => 'Focus Bracketing', #forum11194 + 4 => 'Unlimited', #PH (TZ5) + 8 => 'White Balance Bracketing', #18 + 17 => 'On (with flash)', #forum5597 + 18 => 'Aperture Bracketing', #forum11194 + }, + }, + 0x2b => { #4 + Name => 'SequenceNumber', + Writable => 'int32u', + }, + 0x2c => [ + { + Name => 'ContrastMode', + Condition => q{ + $$self{Model} !~ /^DMC-(FX10|G1|L1|L10|LC80|GF\d+|G2|TZ10|ZS7)$/ and + # tested for DC-GH6, but rule out other DC- models just in case - PH + $$self{Model} !~ /^DC-/ + }, + Flags => 'PrintHex', + Writable => 'int16u', + Notes => q{ + this decoding seems to work for some models such as the LC1, LX2, FZ7, FZ8, + FZ18 and FZ50, but may not be correct for other models such as the FX10, G1, L1, + L10 and LC80 + }, + PrintConv => { + 0x00 => 'Normal', + 0x01 => 'Low', + 0x02 => 'High', + # 0x03 - observed with LZ6 and TZ5 in Fireworks mode + # and GX7 in Fantasy/Retro/OldDays/HighKey - PH + # 0x04 - observed in MP4 movie with GM1 (EXIF and 0x39 Contrast "Normal") - PH + 0x05 => 'Normal 2', #forum1194 + 0x06 => 'Medium Low', #PH (FZ18) + 0x07 => 'Medium High', #PH (FZ18) + # 0x08 - GX7 in DynamicMonochrome mode + 0x0d => 'High Dynamic', #PH (FZ47 in ?) + # 0x13 - seen for LX100 (PH) + 0x18 => 'Dynamic Range (film-like)', #forum11194 + 0x2e => 'Match Filter Effects Toy', #forum11194 + 0x37 => 'Match Photo Style L. Monochrome', #forum11194 + # DMC-LC1 values: + 0x100 => 'Low', + 0x110 => 'Normal', + 0x120 => 'High', + } + },{ + Name => 'ContrastMode', + Condition => '$$self{Model} =~ /^DMC-(GF\d+|G2)$/', + Notes => 'these values are used by the G2, GF1, GF2, GF3, GF5 and GF6', + Writable => 'int16u', + PrintConv => { # (decoded for GF1 unless otherwise noted) + 0 => '-2', + 1 => '-1', + 2 => 'Normal', + 3 => '+1', + 4 => '+2', + # Note: Other Contrast tags will be "Normal" in any of these modes: + 5 => 'Normal 2', # 5 - seen for Portrait (FX80) and Normal (GF6) + 7 => 'Nature (Color Film)', # (GF1,G2; GF3 "Miniature") + 9 => 'Expressive', #(GF3) + 12 => 'Smooth (Color Film) or Pure (My Color)', #(GF1,G2 "Smooth Color") + 17 => 'Dynamic (B&W Film)', #(GF1,G2) + 22 => 'Smooth (B&W Film)', #(GF1,G2) + 25 => 'High Dynamic', #(GF5) + 26 => 'Retro', #(GF5) + 27 => 'Dynamic (Color Film)', #(GF1,G2) (GF3 "High Key") + 28 => 'Low Key', #(GF5) + 29 => 'Toy Effect', #(GF5) + 32 => 'Vibrant (Color Film) or Expressive (My Color)', # (GF1; G2 "Vibrant"; GF2,GF5 "Expressive") + 33 => 'Elegant (My Color)', + 37 => 'Nostalgic (Color Film)', # (GF1,G2; GF5 "Sepia") + 41 => 'Dynamic Art (My Color)', # (GF5 "High Key") + 42 => 'Retro (My Color)', + 45 => 'Cinema', #(GF2) + 47 => 'Dynamic Mono', #(GF5) + 50 => 'Impressive Art', #(GF5) + 51 => 'Cross Process', #(GF5) + 100 => 'High Dynamic 2', #Exiv2 (G6) + 101 => 'Retro 2', #Exiv2 (G6) + 102 => 'High Key 2', #Exiv2 (G6) + 103 => 'Low Key 2', #Exiv2 (G6) + 104 => 'Toy Effect 2', #Exiv2 (G6) + 107 => 'Expressive 2', #(GF6) + 112 => 'Sepia', #Exiv2 (G6) + 117 => 'Miniature', #Exiv2 (G6) + 122 => 'Dynamic Monochrome', #(GF6) + 127 => 'Old Days', #Exiv2 (G6) + 132 => 'Dynamic Monochrome 2', #Exiv2 (G6) + 135 => 'Impressive Art 2', #Exiv2 (G6) + 136 => 'Cross Process 2', #Exiv2 (G6) + 137 => 'Toy Pop', #Exiv2 (G6) + 138 => 'Fantasy', #Exiv2 (G6) + 256 => 'Normal 3', #Exiv2 (G6) + 272 => 'Standard', #Exiv2 (G6) + 288 => 'High', #Exiv2 (G6) + # more new modes for GF6: + # ? => 'Old Days', + # ? => 'Toy Pop', + # ? => 'Bleach Bypass', + # ? => 'Fantasy', + # ? => 'Star Filter', + # ? => 'One Point Color', + # ? => 'Sunshine', + }, + },{ + Name => 'ContrastMode', + Condition => '$$self{Model} =~ /^DMC-(TZ10|ZS7)$/', + Notes => 'these values are used by the TZ10 and ZS7', + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => '-2', + 2 => '+2', + 5 => '-1', + 6 => '+1', + }, + },{ + Name => 'ContrastMode', + Writable => 'int16u', + }, + ], + 0x2d => { + Name => 'NoiseReduction', + Writable => 'int16u', + Notes => 'the encoding for this value is not consistent between models', + PrintConv => { + 0 => 'Standard', + 1 => 'Low (-1)', + 2 => 'High (+1)', + 3 => 'Lowest (-2)', #JD + 4 => 'Highest (+2)', #JD + 5 => '+5', #PH (NC) + 6 => '+6', # (NC) seen for DC-S1/S1R (IB) + 65531 => '-5', # LX100/FZ2500 "NR1" test shots at imaging-resource (PH) + 65532 => '-4', + 65533 => '-3', + 65534 => '-2', + 65535 => '-1', + }, + }, + 0x2e => { #4 + Name => 'SelfTimer', + Writable => 'int16u', + PrintConv => { + 0 => 'Off (0)', #forum11529 + 1 => 'Off', + 2 => '10 s', + 3 => '2 s', + 4 => '10 s / 3 pictures', #17 + 258 => '2 s after shutter pressed', #forum11194 + 266 => '10 s after shutter pressed', #forum11194 + 778 => '3 photos after 10 s', #forum11194 + }, + }, + # 0x2f - values: 1 (LZ6,FX10K) + 0x30 => { #7 + Name => 'Rotation', + Writable => 'int16u', + PrintConv => { + 1 => 'Horizontal (normal)', + 3 => 'Rotate 180', #PH + 6 => 'Rotate 90 CW', #PH (ref 7 gives 270 CW) + 8 => 'Rotate 270 CW', #PH (ref 7 gives 90 CW) + }, + }, + 0x31 => { #PH (FS7) + Name => 'AFAssistLamp', + Writable => 'int16u', + PrintConv => { + 1 => 'Fired', + 2 => 'Enabled but Not Used', + 3 => 'Disabled but Required', + 4 => 'Disabled and Not Required', + # have seen a value of 5 - PH + # values possibly related to FOC-L? - JD + }, + }, + 0x32 => { #7 + Name => 'ColorMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'Natural', + 2 => 'Vivid', + # have seen 3 for GF2 - PH + }, + }, + 0x33 => { #JD + Name => 'BabyAge', + Writable => 'string', + Notes => 'or pet age', #PH + PrintConv => '$val eq "9999:99:99 00:00:00" ? "(not set)" : $val', + PrintConvInv => '$val =~ /^\d/ ? $val : "9999:99:99 00:00:00"', + }, + 0x34 => { #7/PH + Name => 'OpticalZoomMode', + Writable => 'int16u', + PrintConv => { + 1 => 'Standard', + 2 => 'Extended', + }, + }, + 0x35 => { #9 + Name => 'ConversionLens', + Writable => 'int16u', + PrintConv => { #PH (unconfirmed) + 1 => 'Off', + 2 => 'Wide', + 3 => 'Telephoto', + 4 => 'Macro', + }, + }, + 0x36 => { #8 + Name => 'TravelDay', + Writable => 'int16u', + PrintConv => '$val == 65535 ? "n/a" : $val', + PrintConvInv => '$val =~ /(\d+)/ ? $1 : $val', + }, + # 0x37 - values: 0,1,2 (LZ6, 0 for movie preview); 257 (FX10K); 0,256 (TZ5, 0 for movie preview) + # --> may indicate battery power (forum11388) + 0x38 => { #forum11388 + Name => 'BatteryLevel', + Writable => 'int16u', + PrintConv => { + 1 => 'Full', + 2 => 'Medium', + 3 => 'Low', + 4 => 'Near Empty', + 7 => 'Near Full', + 8 => 'Medium Low', + 256 => 'n/a', + }, + }, + 0x39 => { #7 (L1/L10) + Name => 'Contrast', + Format => 'int16s', + Writable => 'int16u', + %Image::ExifTool::Exif::printParameter, + }, + 0x3a => { + Name => 'WorldTimeLocation', + Writable => 'int16u', + PrintConv => { + 1 => 'Home', + 2 => 'Destination', + }, + }, + 0x3b => { #PH (TZ5/FS7) + # (tags 0x3b, 0x3e, 0x8008 and 0x8009 have the same values in all my samples - PH) + Name => 'TextStamp', + Writable => 'int16u', + PrintConv => { 1 => 'Off', 2 => 'On' }, + }, + 0x3c => { #PH + Name => 'ProgramISO', # (maybe should rename this ISOSetting?) + Writable => 'int16u', # (new models store a long here) + PrintConv => { + OTHER => sub { shift }, + 65534 => 'Intelligent ISO', #PH (FS7) + 65535 => 'n/a', + -1 => 'n/a', + }, + }, + 0x3d => { #PH + Name => 'AdvancedSceneType', + Writable => 'int16u', + Notes => 'used together with SceneMode to derive Composite AdvancedSceneMode', + # see forum11194 for more info + }, + 0x3e => { #PH (TZ5/FS7) + # (tags 0x3b, 0x3e, 0x8008 and 0x8009 have the same values in all my samples - PH) + Name => 'TextStamp', + Writable => 'int16u', + PrintConv => { 1 => 'Off', 2 => 'On' }, + }, + 0x3f => { #PH (TZ7) + Name => 'FacesDetected', + Writable => 'int16u', + }, + 0x40 => { #7 (L1/L10) + Name => 'Saturation', + Format => 'int16s', + Writable => 'int16u', + %Image::ExifTool::Exif::printParameter, + }, + 0x41 => { #7 (L1/L10) + Name => 'Sharpness', + Format => 'int16s', + Writable => 'int16u', + %Image::ExifTool::Exif::printParameter, + }, + 0x42 => { #7 (DMC-L1) + Name => 'FilmMode', + Writable => 'int16u', + PrintConv => { + 0 => 'n/a', #PH (eg. FZ100 "Photo Frame" ShootingMode) + 1 => 'Standard (color)', + 2 => 'Dynamic (color)', + 3 => 'Nature (color)', + 4 => 'Smooth (color)', + 5 => 'Standard (B&W)', + 6 => 'Dynamic (B&W)', + 7 => 'Smooth (B&W)', + # 8 => 'My Film 1'? (from owner manual) + # 9 => 'My Film 2'? + 10 => 'Nostalgic', #(GH1) + 11 => 'Vibrant', #(GH1) + # 12 => 'Multi Film'? (in the GH1 specs) + }, + }, + 0x43 => { #forum9369 + Name => 'JPEGQuality', + Writable => 'int16u', + PrintConv => { + 0 => 'n/a (Movie)', + 2 => 'High', + 3 => 'Standard', + 6 => 'Very High', + 255 => 'n/a (RAW only)', + }, + }, + 0x44 => { + Name => 'ColorTempKelvin', + Format => 'int16u', + }, + 0x45 => { #19 + Name => 'BracketSettings', + Writable => 'int16u', + PrintConv => { + 0 => 'No Bracket', + 1 => '3 Images, Sequence 0/-/+', + 2 => '3 Images, Sequence -/0/+', + 3 => '5 Images, Sequence 0/-/+', + 4 => '5 Images, Sequence -/0/+', + 5 => '7 Images, Sequence 0/-/+', + 6 => '7 Images, Sequence -/0/+', + }, + }, + 0x46 => { #PH/JD + Name => 'WBShiftAB', + Format => 'int16s', + Writable => 'int16u', + Notes => 'positive is a shift toward blue', + }, + 0x47 => { #PH/JD + Name => 'WBShiftGM', + Format => 'int16s', + Writable => 'int16u', + Notes => 'positive is a shift toward green', + }, + 0x48 => { #17 + Name => 'FlashCurtain', + Writable => 'int16u', + PrintConv => { + 0 => 'n/a', + 1 => '1st', + 2 => '2nd', + }, + }, + 0x49 => { #19 + Name => 'LongExposureNoiseReduction', # (indicates availability, forum11194) + Writable => 'int16u', + PrintConv => { + 1 => 'Off', + 2 => 'On' + } + }, + # 0x4a - int16u: 0 + 0x4b => { #PH + Name => 'PanasonicImageWidth', + Writable => 'int32u', + }, + 0x4c => { #PH + Name => 'PanasonicImageHeight', + Writable => 'int32u', + }, + 0x4d => { #PH (FS7) + Name => 'AFPointPosition', + Writable => 'rational64u', + Count => 2, + Notes => 'X Y coordinates of primary AF area center, in the range 0.0 to 1.0', + PrintConv => q{ + return 'none' if $val eq '16777216 16777216'; + my @a = split ' ', $val; + sprintf("%.2g %.2g",@a); + }, + PrintConvInv => '$val eq "none" ? "16777216 16777216" : $val', + }, + 0x4e => { #PH + Name => 'FaceDetInfo', + PrintConv => 'length $val', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::FaceDetInfo', + }, + }, + # 0x4f,0x50 - int16u: 0 + 0x51 => { + Name => 'LensType', + Writable => 'string', + ValueConv => '$val=~s/ +$//; $val', # trim trailing spaces + ValueConvInv => '$val', + }, + 0x52 => { #7 (DMC-L1) + Name => 'LensSerialNumber', + Writable => 'string', + ValueConv => '$val=~s/ +$//; $val', # trim trailing spaces + ValueConvInv => '$val', + }, + 0x53 => { #7 (DMC-L1) + Name => 'AccessoryType', + Writable => 'string', + ValueConv => '$val=~s/ +$//; $val', # trim trailing spaces + ValueConvInv => '$val', + }, + 0x54 => { #19 + Name => 'AccessorySerialNumber', + Writable => 'string', + ValueConv => '$val=~s/ +$//; $val', # trim trailing spaces + ValueConvInv => '$val', + }, + # 0x55 - int16u: 1 (see forum9372) + # 0x57 - int16u: 0 + 0x59 => { #PH (FS7) + Name => 'Transform', + Writable => 'undef', + Notes => 'decoded as two 16-bit signed integers', + Format => 'int16s', + Count => 2, + PrintConv => { + '-3 2' => 'Slim High', + '-1 1' => 'Slim Low', + '0 0' => 'Off', + '1 1' => 'Stretch Low', + '3 2' => 'Stretch High', + }, + }, + # 0x5a - int16u: 0,2 + # 0x5b - int16u: 0 + # 0x5c - int16u: 0,2 + 0x5d => { #PH (GF1, FZ35) + Name => 'IntelligentExposure', + Notes => 'not valid for some models', # (doesn't change in ZS7 and GH1 images) + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Standard', + 3 => 'High', + }, + }, + # 0x5e,0x5f - undef[4] + 0x60 => { #18 + Name => 'LensFirmwareVersion', + Writable => 'undef', + Format => 'int8u', + Count => 4, + PrintConv => '$val=~tr/ /./; $val', + PrintConvInv => '$val=~tr/./ /; $val', + }, + 0x61 => { #PH + Name => 'FaceRecInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::FaceRecInfo', + }, + }, + 0x62 => { #PH (FS7) + Name => 'FlashWarning', + Writable => 'int16u', + PrintConv => { 0 => 'No', 1 => 'Yes (flash required but disabled)' }, + }, + 0x63 => { #PH + # not sure exactly what this means, but in my samples this is + # FacesRecognized bytes of 0x01, padded with 0x00 to a length of 4 - PH + Name => 'RecognizedFaceFlags', + Format => 'int8u', + Count => 4, + Writable => 'undef', + Unknown => 1, + }, + 0x65 => { #15 + Name => 'Title', + Format => 'string', + Writable => 'undef', # (Count 64) + }, + 0x66 => { #15 + Name => 'BabyName', + Notes => 'or pet name', + Format => 'string', + Writable => 'undef', # (Count 64) + }, + 0x67 => { #15 + Name => 'Location', + Groups => { 2 => 'Location' }, + Format => 'string', + Writable => 'undef', # (Count 64) + }, + # 0x68 - int8u: 1 + 0x69 => { #PH (ZS7) + Name => 'Country', # (Country/Region) + Groups => { 2 => 'Location' }, + Format => 'string', + Writable => 'undef', # (Count 72) + }, + # 0x6a - int8u: 1 + 0x6b => { #PH (ZS7) + Name => 'State', # (State/Province/Count -- what is Count?) + Groups => { 2 => 'Location' }, + Format => 'string', + Writable => 'undef', # (Count 72) + }, + # 0x6c - int8u: 1 + 0x6d => { #PH (ZS7) (also see forum5997) + Name => 'City', # (City/Town) + Groups => { 2 => 'Location' }, + Format => 'string', + Writable => 'undef', # (Count 72) + Notes => 'City/Town as stored by some models, or County/Township for others', + }, + # 0x6e - int8u: 1 + 0x6f => { #PH (ZS7) + Name => 'Landmark', # (Landmark) + Groups => { 2 => 'Location' }, + Format => 'string', + Writable => 'undef', # (Count 128) + }, + 0x70 => { #PH (ZS7) + Name => 'IntelligentResolution', + Writable => 'int8u', + PrintConv => { + 0 => 'Off', + # Note: I think these values make sense for the GH2, but meanings + # may be different for other models + 1 => 'Low', + 2 => 'Standard', + 3 => 'High', + 4 => 'Extended', + }, + }, + # 0x71 - undef[128] (maybe text stamp text?) + 0x77 => { #18 + Name => 'BurstSpeed', + Writable => 'int16u', + Notes => 'images per second', + }, + # 0x72,0x73,0x74,0x75,0x77,0x78: 0 + # 0x76: 0, (3 for G6 with HDR on, ref 18) + 0x76 => { #18/21 + Name => 'HDRShot', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 3 => 'On' }, + }, + 0x79 => { #PH (GH2) + Name => 'IntelligentD-Range', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Standard', + 3 => 'High', + }, + }, + # 0x7a,0x7b: 0 + 0x7c => { #18 + Name => 'ClearRetouch', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x80 => { #forum5997 (seen garbage here for SZ5 - PH) + Name => 'City2', # (City/Town/Village) + Groups => { 2 => 'Location' }, + Format => 'string', + Writable => 'undef', # (Count 72) + Notes => 'City/Town/Village as stored by some models', + }, + # 0x81 - undef[72]: "---" + # 0x82 - undef[72]: "---" + # 0x83 - undef[72]: "---" + # 0x84 - undef[72]: "---" + # 0x85 - undef[128]: "---" + 0x86 => { #http://dev.exiv2.org/issues/825 + Name => 'ManometerPressure', + Writable => 'int16u', + RawConv => '$val==65535 ? undef : $val', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f kPa",$val)', + PrintConvInv => '$val=~s/ ?kPa//i; $val', + }, + 0x89 => { + Name => 'PhotoStyle', + Writable => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Standard or Custom', + 2 => 'Vivid', + 3 => 'Natural', + 4 => 'Monochrome', + 5 => 'Scenery', + 6 => 'Portrait', + 8 => 'Cinelike D', #forum11194 + 9 => 'Cinelike V', #forum11194 + 11 => 'L. Monochrome', #forum11194 + 12 => 'Like709', #forum14033 + 15 => 'L. Monochrome D', #forum11194 + 17 => 'V-Log', #forum14033 + 18 => 'Cinelike D2', #forum14033 + }, + }, + 0x8a => { #18 + Name => 'ShadingCompensation', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On' + } + }, + 0x8b => { #21 + Name => 'WBShiftIntelligentAuto', + Writable => 'int16u', + Format => 'int16s', + Notes => 'value is -9 for blue to +9 for amber. Valid for Intelligent-Auto modes', + }, + 0x8c => { + Name => 'AccelerometerZ', + Writable => 'int16u', + Format => 'int16s', + Notes => 'positive is acceleration upwards', + }, + 0x8d => { + Name => 'AccelerometerX', + Writable => 'int16u', + Format => 'int16s', + Notes => 'positive is acceleration to the left', + }, + 0x8e => { + Name => 'AccelerometerY', + Writable => 'int16u', + Format => 'int16s', + Notes => 'positive is acceleration backwards', + }, + 0x8f => { #18 + Name => 'CameraOrientation', + Writable => 'int8u', + PrintConv => { + 0 => 'Normal', + 1 => 'Rotate CW', + 2 => 'Rotate 180', + 3 => 'Rotate CCW', + 4 => 'Tilt Upwards', + 5 => 'Tilt Downwards' + } + }, + 0x90 => { + Name => 'RollAngle', + Writable => 'int16u', + Format => 'int16s', + Notes => 'converted to degrees of clockwise camera rotation', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + }, + 0x91 => { + Name => 'PitchAngle', + Writable => 'int16u', + Format => 'int16s', + Notes => 'converted to degrees of upward camera tilt', + ValueConv => '-$val / 10', + ValueConvInv => '-$val * 10', + }, + 0x92 => { #21 (forum9453) (more to decode in forum11194) + Name => 'WBShiftCreativeControl', + Writable => 'int8u', + Format => 'int8s', + Notes => 'WB shift or style strength. Valid for Creative-Control modes', + }, + 0x93 => { #18 + Name => 'SweepPanoramaDirection', + Writable => 'int8u', + PrintConv => { + 0 => 'Off', + 1 => 'Left to Right', + 2 => 'Right to Left', + 3 => 'Top to Bottom', + 4 => 'Bottom to Top' + } + }, + 0x94 => { #18 + Name => 'SweepPanoramaFieldOfView', + Writable => 'int16u' + }, + 0x96 => { #18 + Name => 'TimerRecording', + Writable => 'int8u', + PrintConv => { + 0 => 'Off', + 1 => 'Time Lapse', + 2 => 'Stop-motion Animation', + 3 => 'Focus Bracketing', #forum11194 + }, + }, + 0x9d => { #18 + Name => 'InternalNDFilter', + Writable => 'rational64u' + }, + 0x9e => { #18 + Name => 'HDR', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 100 => '1 EV', + 200 => '2 EV', + 300 => '3 EV', + 32868 => '1 EV (Auto)', + 32968 => '2 EV (Auto)', + 33068 => '3 EV (Auto)', + }, + }, + 0x9f => { #18 + Name => 'ShutterType', + Writable => 'int16u', + PrintConv => { + 0 => 'Mechanical', + 1 => 'Electronic', + 2 => 'Hybrid', #PH (GM1, 1st curtain electronic, 2nd curtain mechanical) + }, + }, + # 0xa0 - undef[32]: AWB gains and black levels (ref forum9303) + 0xa1 => { #forum11194 + Name => 'FilterEffect', + Writable => 'rational64u', + Format => 'int32u', + PrintConv => { + # '0 0' => 'Expressive', #forum11194 + '0 0' => 'Off', #forum14033 (GH6) + '0 1' => 'Expressive', #forum14033 (GH6) (have also seen this for XS1) + '0 2' => 'Retro', + '0 4' => 'High Key', + '0 8' => 'Sepia', + '0 16' => 'High Dynamic', + '0 32' => 'Miniature Effect', + '0 256' => 'Low Key', + '0 512' => 'Toy Effect', + '0 1024' => 'Dynamic Monochrome', + '0 2048' => 'Soft Focus', + '0 4096' => 'Impressive Art', + '0 8192' => 'Cross Process', + '0 16384' => 'One Point Color', + '0 32768' => 'Star Filter', + '0 524288' => 'Old Days', + '0 1048576' => 'Sunshine', + '0 2097152' => 'Bleach Bypass', + '0 4194304' => 'Toy Pop', + '0 8388608' => 'Fantasy', + '0 33554432' => 'Monochrome', + '0 67108864' => 'Rough Monochrome', + '0 134217728' => 'Silky Monochrome', + }, + }, + 0xa3 => { #18 + Name => 'ClearRetouchValue', + Writable => 'rational64u', + # undef if ClearRetouch is off, 0 if it is on + }, + 0xa7 => { #forum9374 (conversion table for 14- to 16-bit mapping) + Name => 'OutputLUT', + Binary => 1, + Notes => q{ + 2-column by 432-row binary lookup table of unsigned short values for + converting to 16-bit output (1st column) from 14 bits (2nd column) with + camera contrast + }, + }, + 0xab => { #18 + Name => 'TouchAE', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0xac => { #forum11194 + Name => 'MonochromeFilterEffect', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'Yellow', 2 => 'Orange', 3 => 'Red', 4 => 'Green' }, + }, + 0xad => { #forum9360 + Name => 'HighlightShadow', + Writable => 'int16u', + Format => 'int16s', #forum11194 + Count => 2, + }, + 0xaf => { #PH (is this in UTC maybe? -- sometimes different time zone other times) + Name => 'TimeStamp', + Writable => 'string', + Groups => { 2 => 'Time' }, + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + 0xb3 => { #forum11194 + Name => 'VideoBurstResolution', + Writable => 'int16u', + PrintConv => { 1 => 'Off or 4K', 4 => '6K' }, + }, + 0xb4 => { #forum9429 + Name => 'MultiExposure', + Writable => 'int16u', + PrintConv => { 0 => 'n/a', 1 => 'Off', 2 => 'On' }, + }, + 0xb9 => { #forum9425 + Name => 'RedEyeRemoval', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0xbb => { #forum9282 + Name => 'VideoBurstMode', + Writable => 'int32u', + PrintHex => 1, + PrintConv => { + 0x01 => 'Off', + 0x04 => 'Post Focus', + 0x18 => '4K Burst', + 0x28 => '4K Burst (Start/Stop)', + 0x48 => '4K Pre-burst', + 0x108 => 'Loop Recording', + 0x810 => '6K Burst', + 0x820 => '6K Burst (Start/Stop)', + 0x408 => 'Focus Stacking', #forum11563 + 0x1001 => 'High Resolution Mode', + }, + }, + 0xbc => { #forum9282 + Name => 'DiffractionCorrection', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'Auto' }, + }, + 0xbd => { #forum11194 + Name => 'FocusBracket', + Notes => 'positive is further, negative is closer', + Writable => 'int16u', + Format => 'int16s', + }, + 0xbe => { #forum11194 + Name => 'LongExposureNRUsed', + Writable => 'int16u', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0xbf => { #forum11194 + Name => 'PostFocusMerging', + Format => 'int32u', + Count => 2, + PrintConv => { '0 0' => 'Post Focus Auto Merging or None' }, + }, + 0xc1 => { #forum11194 + Name => 'VideoPreburst', + Writable => 'int16u', + PrintConv => { 0 => 'No', 1 => '4K or 6K' }, + }, + 0xca => { #forum11459 + Name => 'SensorType', + Writable => 'int16u', + PrintConv => { + 0 => 'Multi-aspect', + 1 => 'Standard', + }, + }, + # Note: LensTypeMake and LensTypeModel are combined into a Composite LensType tag + # defined in Olympus.pm which has the same values as Olympus:LensType + 0xc4 => { #PH + Name => 'LensTypeMake', + Condition => '$format eq "int16u" and $$valPt ne "\xff\xff"', # (ignore make 65535 for now) + Writable => 'int16u', + }, + 0xc5 => { #PH + Name => 'LensTypeModel', + Condition => '$format eq "int16u"', + Writable => 'int16u', + RawConv => q{ + return undef unless $val; + require Image::ExifTool::Olympus; # (to load Composite LensID) + return $val; + }, + ValueConv => '$_=sprintf("%.4x",$val); s/(..)(..)/$2 $1/; $_', + ValueConvInv => '$val =~ s/(..) (..)/$2$1/; hex($val)', + }, + 0xd1 => { #PH + Name => 'ISO', + RawConv => '$val > 0xfffffff0 ? undef : $val', + Writable => 'int32u', + }, + 0xd2 => { #forum11194 + Name => 'MonochromeGrainEffect', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Standard', + 3 => 'High', + }, + }, + 0xd6 => { #PH (DC-S1) + Name => 'NoiseReductionStrength', + Writable => 'rational64s', + }, + 0xe4 => { #IB + Name => 'LensTypeModel', + Condition => '$format eq "int16u"', + Writable => 'int16u', + RawConv => q{ + return undef unless $val; + require Image::ExifTool::Olympus; # (to load Composite LensID) + return $val; + }, + ValueConv => '$_=sprintf("%.4x",$val); s/(..)(..)/$2 $1/; $_', + ValueConvInv => '$val =~ s/(..) (..)/$2$1/; hex($val)', + }, + 0xe8 => { #PH (DC-GH6) + Name => 'MinimumISO', + Writable => 'int32u', + }, + 0xee => { #PH (DC-GH6) + Name => 'DynamicRangeBoost', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::PrintIM::Main' }, + }, + 0x2003 => { #21 + Name => 'TimeInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Panasonic::TimeInfo' }, + }, + 0x8000 => { #PH + Name => 'MakerNoteVersion', + Format => 'undef', + }, + 0x8001 => { #7/PH/JD + Name => 'SceneMode', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + %shootingMode, + }, + }, + 0x8002 => { #21 + Name => 'HighlightWarning', + Writable => 'int16u', + PrintConv => { 0 => 'Disabled', 1 => 'No', 2 => 'Yes' }, + }, + 0x8003 => { #21 + Name => 'DarkFocusEnvironment', + Writable => 'int16u', + PrintConv => { 1 => 'No', 2 => 'Yes' }, + }, + 0x8004 => { #PH/JD + Name => 'WBRedLevel', + Writable => 'int16u', + }, + 0x8005 => { #PH/JD + Name => 'WBGreenLevel', + Writable => 'int16u', + }, + 0x8006 => { #PH/JD + Name => 'WBBlueLevel', + Writable => 'int16u', + }, + #0x8007 => { #PH - questionable [disabled because it conflicts with EXIF in too many samples] + # Name => 'FlashFired', + # Writable => 'int16u', + # PrintConv => { 0 => 'Yes', 1 => 'No' }, + #}, + 0x8008 => { #PH (TZ5/FS7) + # (tags 0x3b, 0x3e, 0x8008 and 0x8009 have the same values in all my samples - PH) + Name => 'TextStamp', + Writable => 'int16u', + PrintConv => { 1 => 'Off', 2 => 'On' }, + }, + 0x8009 => { #PH (TZ5/FS7) + # (tags 0x3b, 0x3e, 0x8008 and 0x8009 have the same values in all my samples - PH) + Name => 'TextStamp', + Writable => 'int16u', + PrintConv => { 1 => 'Off', 2 => 'On' }, + }, + 0x8010 => { #PH + Name => 'BabyAge', + Writable => 'string', + Notes => 'or pet age', + PrintConv => '$val eq "9999:99:99 00:00:00" ? "(not set)" : $val', + PrintConvInv => '$val =~ /^\d/ ? $val : "9999:99:99 00:00:00"', + }, + 0x8012 => { #PH (FS7) + Name => 'Transform', + Writable => 'undef', + Notes => 'decoded as two 16-bit signed integers', + Format => 'int16s', + Count => 2, + PrintConv => { + '-3 2' => 'Slim High', + '-1 1' => 'Slim Low', + '0 0' => 'Off', + '1 1' => 'Stretch Low', + '3 2' => 'Stretch High', + }, + }, +); + +# Leica type2 maker notes (ref 10) +%Image::ExifTool::Panasonic::Leica2 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + WRITABLE => 1, + NOTES => 'These tags are used by the Leica M8.', + 0x300 => { + Name => 'Quality', + Writable => 'int16u', + PrintConv => { + 1 => 'Fine', + 2 => 'Basic', + }, + }, + 0x302 => { + Name => 'UserProfile', + Writable => 'int32u', + PrintConv => { + 1 => 'User Profile 1', + 2 => 'User Profile 2', + 3 => 'User Profile 3', + 4 => 'User Profile 0 (Dynamic)', + }, + }, + 0x303 => { + Name => 'SerialNumber', + Writable => 'int32u', + PrintConv => 'sprintf("%.7d", $val)', + PrintConvInv => '$val', + }, + 0x304 => { + Name => 'WhiteBalance', + Writable => 'int16u', + Notes => 'values above 0x8000 are converted to Kelvin color temperatures', + PrintConv => { + 0 => 'Auto or Manual', + 1 => 'Daylight', + 2 => 'Fluorescent', + 3 => 'Tungsten', + 4 => 'Flash', + 10 => 'Cloudy', + 11 => 'Shade', + OTHER => \&WhiteBalanceConv, + }, + }, + 0x310 => { + Name => 'LensType', + Writable => 'int32u', + SeparateTable => 1, + ValueConv => '($val >> 2) . " " . ($val & 0x3)', + ValueConvInv => \&LensTypeConvInv, + PrintConv => \%leicaLensTypes, + }, + 0x311 => { + Name => 'ExternalSensorBrightnessValue', + Format => 'rational64s', # (incorrectly unsigned in JPEG images) + Writable => 'rational64s', + Notes => '"blue dot" measurement', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x312 => { + Name => 'MeasuredLV', + Format => 'rational64s', # (incorrectly unsigned in JPEG images) + Writable => 'rational64s', + Notes => 'imaging sensor or TTL exposure meter measurement', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x313 => { + Name => 'ApproximateFNumber', + Writable => 'rational64u', + PrintConv => 'sprintf("%.1f", $val)', + PrintConvInv => '$val', + }, + 0x320 => { + Name => 'CameraTemperature', + Writable => 'int32s', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 0x321 => { Name => 'ColorTemperature', Writable => 'int32u' }, + 0x322 => { Name => 'WBRedLevel', Writable => 'rational64u' }, + 0x323 => { Name => 'WBGreenLevel', Writable => 'rational64u' }, + 0x324 => { Name => 'WBBlueLevel', Writable => 'rational64u' }, + 0x325 => { + Name => 'UV-IRFilterCorrection', + Description => 'UV/IR Filter Correction', + Writable => 'int32u', + PrintConv => { + 0 => 'Not Active', + 1 => 'Active', + }, + }, + 0x330 => { Name => 'CCDVersion', Writable => 'int32u' }, + 0x331 => { Name => 'CCDBoardVersion', Writable => 'int32u' }, + 0x332 => { Name => 'ControllerBoardVersion', Writable => 'int32u' }, + 0x333 => { Name => 'M16CVersion', Writable => 'int32u' }, + 0x340 => { Name => 'ImageIDNumber', Writable => 'int32u' }, +); + +# Leica type3 maker notes (ref PH) +%Image::ExifTool::Panasonic::Leica3 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + WRITABLE => 1, + NOTES => 'These tags are used by the Leica R8 and R9 digital backs.', + 0x0b => { #IB + Name => 'SerialInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Panasonic::SerialInfo' }, + }, + 0x0d => { + Name => 'WB_RGBLevels', + Writable => 'int16u', + Count => 3, + }, +); + +# Leica serial number info (ref IB) +%Image::ExifTool::Panasonic::SerialInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + TAG_PREFIX => 'Leica_SerialInfo', + FIRST_ENTRY => 0, + 4 => { + Name => 'SerialNumber', + Format => 'string[8]', + } +); + +# Leica type4 maker notes (ref PH) (M9) +%Image::ExifTool::Panasonic::Leica4 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + WRITABLE => 1, + NOTES => 'This information is written by the M9.', + 0x3000 => { + Name => 'Subdir3000', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Subdir', + ByteOrder => 'Unknown', + }, + }, + 0x3100 => { + Name => 'Subdir3100', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Subdir', + ByteOrder => 'Unknown', + }, + }, + 0x3400 => { + Name => 'Subdir3400', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Subdir', + ByteOrder => 'Unknown', + }, + }, + 0x3900 => { + Name => 'Subdir3900', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Subdir', + ByteOrder => 'Unknown', + }, + }, +); + +# Leica M9 SubDirectory tags (ref PH) +%Image::ExifTool::Panasonic::Subdir = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + TAG_PREFIX => 'Leica_Subdir', + WRITABLE => 1, + # 0x3001 - normally 0 but value of 2 when manual coding is used + # with a coded lens (but only tested with Elmar-M 50mm f/2.8) - PH + 0x300a => { + Name => 'Contrast', + Writable => 'int32u', + PrintConv => { + 0 => 'Low', + 1 => 'Medium Low', + 2 => 'Normal', + 3 => 'Medium High', + 4 => 'High', + }, + }, + 0x300b => { + Name => 'Sharpening', + Writable => 'int32u', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Normal', + 3 => 'Medium High', + 4 => 'High', + }, + }, + 0x300d => { + Name => 'Saturation', + Writable => 'int32u', + PrintConv => { + 0 => 'Low', + 1 => 'Medium Low', + 2 => 'Normal', + 3 => 'Medium High', + 4 => 'High', + 5 => 'Black & White', + 6 => 'Vintage B&W', + }, + }, + # 0x3032 - some sort of RGB coefficients? (zeros unless Kelvin WB, but same for all Color Temps) + 0x3033 => { + Name => 'WhiteBalance', + Writable => 'int32u', + PrintConv => { #13 + 0 => 'Auto', + 1 => 'Tungsten', + 2 => 'Fluorescent', + 3 => 'Daylight Fluorescent', + 4 => 'Daylight', + 5 => 'Flash', + 6 => 'Cloudy', + 7 => 'Shade', + 8 => 'Manual', + 9 => 'Kelvin', + }, + }, + 0x3034 => { + Name => 'JPEGQuality', + Writable => 'int32u', + PrintConv => { + 94 => 'Basic', + 97 => 'Fine', + }, + }, + # 0x3035 (int32u): -1 unless Manual WB (2 in my Manual sample) + 0x3036 => { + Name => 'WB_RGBLevels', + Writable => 'rational64u', + Count => 3, + }, + 0x3038 => { + Name => 'UserProfile', # (CameraProfile according to ref 14) + Writable => 'string', + }, + 0x303a => { + Name => 'JPEGSize', + Writable => 'int32u', + PrintConv => { + 0 => '5216x3472', + 1 => '3840x2592', + 2 => '2592x1728', + 3 => '1728x1152', + 4 => '1280x864', + }, + }, + 0x3103 => { #13 (valid for FW 1.116 and later) + Name => 'SerialNumber', + Writable => 'string', + }, + # 0x3104 body-dependent string ("00012905000000") (not serial number) + # 0x3105 body-dependent string ("00012905000000") + # 0x3107 - body-dependent string ("4H205800116800") (not serial number) + 0x3109 => { + Name => 'FirmwareVersion', + Writable => 'string', + }, + 0x312a => { #14 (NC) + Name => 'BaseISO', + Writable => 'int32u', + }, + 0x312b => { + Name => 'SensorWidth', + Writable => 'int32u', + }, + 0x312c => { + Name => 'SensorHeight', + Writable => 'int32u', + }, + 0x312d => { #14 (NC) + Name => 'SensorBitDepth', + Writable => 'int32u', + }, + 0x3402 => { #PH/13 + Name => 'CameraTemperature', + Writable => 'int32s', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 0x3405 => { + Name => 'LensType', + Writable => 'int32u', + SeparateTable => 1, + ValueConv => '($val >> 2) . " " . ($val & 0x3)', + ValueConvInv => \&LensTypeConvInv, + PrintConv => \%leicaLensTypes, + }, + 0x3406 => { #PH/13 + Name => 'ApproximateFNumber', + Writable => 'rational64u', + PrintConv => 'sprintf("%.1f", $val)', + PrintConvInv => '$val', + }, + 0x3407 => { #14 + Name => 'MeasuredLV', + Writable => 'int32s', + Notes => 'imaging sensor or TTL exposure meter measurement', + ValueConv => '$val / 1e5', #PH (NC) + ValueConvInv => '$val * 1e5', #PH (NC) + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x3408 => { #14 + Name => 'ExternalSensorBrightnessValue', + Writable => 'int32s', + Notes => '"blue dot" measurement', + ValueConv => '$val / 1e5', #PH (NC) + ValueConvInv => '$val * 1e5', #PH (NC) + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x3901 => { + Name => 'Data1', + SubDirectory => { TagTable => 'Image::ExifTool::Panasonic::Data1' }, + }, + 0x3902 => { + Name => 'Data2', + SubDirectory => { TagTable => 'Image::ExifTool::Panasonic::Data2' }, + }, + # 0x3903 - larger binary data block +); + +# time stamp information (ref 21) +%Image::ExifTool::Panasonic::TimeInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'Panasonic', 2 => 'Image' }, + FIRST_ENTRY => 0, + WRITABLE => 1, + 0 => { + Name => 'PanasonicDateTime', + Groups => { 2 => 'Time' }, + Shift => 'Time', + Format => 'undef[8]', + RawConv => '$val =~ /^\0/ ? undef : $val', + ValueConv => 'sprintf("%s:%s:%s %s:%s:%s.%s", unpack "H4H2H2H2H2H2H2", $val)', + ValueConvInv => q{ + $val =~ s/[-+].*//; # remove time zone + $val =~ tr/0-9//dc; # remove non-digits + $val = pack("H*",$val); + $val .= "\0" while length $val < 8; + return $val; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + # 8 - 8 bytes usually 8 x 0xff (spot for another date/time?) + 16 => { + Name => 'TimeLapseShotNumber', + Format => 'int32u', + }, +); + +%Image::ExifTool::Panasonic::Data1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + WRITABLE => 1, + TAG_PREFIX => 'Leica_Data1', + FIRST_ENTRY => 0, + 0x0016 => { + Name => 'LensType', + Format => 'int32u', + Priority => 0, + SeparateTable => 1, + ValueConv => '(($val >> 2) & 0xffff) . " " . ($val & 0x3)', + ValueConvInv => \&LensTypeConvInv, + PrintConv => \%leicaLensTypes, + }, +); + +%Image::ExifTool::Panasonic::Data2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + TAG_PREFIX => 'Leica_Data2', + FIRST_ENTRY => 0, +); + +# Leica type5 maker notes (ref PH) (X1) +%Image::ExifTool::Panasonic::Leica5 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + WRITABLE => 1, + PRIORITY => 0, + NOTES => 'This information is written by the X1, X2, X VARIO and T.', + 0x0303 => { + Name => 'LensType', + Condition => '$format eq "string"', + Notes => 'Leica T only', + Writable => 'string', + }, + # 0x0304 - int8u[1]: may be M-lens ID for Leica SL, mounted through "M-adapter L" (ref IB) + # --> int8u[4] for some models (maybe not lens ID for these?) - PH + # (see http://us.leica-camera.com/Photography/Leica-APS-C/Lenses-for-Leica-TL/L-Adapters/M-Adapter-L) + # 58 = 'Leica Noctilux-M 75mm F1.25 ASPH (Typ 601) on Leica SL + 0x0305 => { #IB + Name => 'SerialNumber', + Writable => 'int32u', + }, + # 0x0406 - saturation or sharpness + 0x0407 => { Name => 'OriginalFileName', Writable => 'string' }, + 0x0408 => { Name => 'OriginalDirectory',Writable => 'string' }, + 0x040a => { #IB + Name => 'FocusInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Panasonic::FocusInfo' }, + }, + # 0x040b - related to white balance + 0x040d => { + Name => 'ExposureMode', + Format => 'int8u', + Count => 4, + PrintConv => { + '0 0 0 0' => 'Program AE', + # '0 1 0 0' - seen for X (Typ 113) - PH + '1 0 0 0' => 'Aperture-priority AE', + '1 1 0 0' => 'Aperture-priority AE (1)', # (see for Leica T) + '2 0 0 0' => 'Shutter speed priority AE', #(guess) + '3 0 0 0' => 'Manual', + }, + }, + 0x0410 => { + Name => 'ShotInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Panasonic::ShotInfo' }, + }, + # 0x0410 - int8u[16]: first byte is FileNumber + # 0x0411 - int8u[4]: first number is FilmMode (1=Standard,2=Vivid,3=Natural,4=BW Natural,5=BW High Contrast) + 0x0412 => { Name => 'FilmMode', Writable => 'string' }, + 0x0413 => { Name => 'WB_RGBLevels', Writable => 'rational64u', Count => 3 }, + 0x0500 => { + Name => 'InternalSerialNumber', + Writable => 'undef', + PrintConv => q{ + return $val unless $val=~/^(.{3})(\d{2})(\d{2})(\d{2})(\d{4})/; + my $yr = $2 + ($2 < 70 ? 2000 : 1900); + return "($1) $yr:$3:$4 no. $5"; + }, + PrintConvInv => '$_=$val; tr/A-Z0-9//dc; s/(.{3})(19|20)/$1/; $_', + }, +); + +# Leica type5 ShotInfo (ref PH) (X2) +%Image::ExifTool::Panasonic::ShotInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + TAG_PREFIX => 'Leica_ShotInfo', + FIRST_ENTRY => 0, + WRITABLE => 1, + 0 => { + Name => 'FileIndex', + Format => 'int16u', + }, +); + +# Leica type5 FocusInfo (ref IB) +%Image::ExifTool::Panasonic::FocusInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + TAG_PREFIX => 'Leica_FocusInfo', + FIRST_ENTRY => 0, + WRITABLE => 1, + FORMAT => 'int16u', + 0 => { + Name => 'FocusDistance', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => '$val < 65535 ? "$val m" : "inf"', + PrintConvInv => '$val =~ s/ ?m$//; IsFloat($val) ? $val : 65535', + }, + 1 => { + Name => 'FocalLength', + Priority => 0, + RawConv => '$val ? $val : undef', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, +); + +# Leica type6 maker notes (ref PH) (S2) +%Image::ExifTool::Panasonic::Leica6 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + NOTES => q{ + This information is written by the S2 and M (Typ 240), as a trailer in JPEG + images. + }, + 0x300 => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Writable => 'undef', + Notes => 'S2 and M (Typ 240)', + DataTag => 'PreviewImage', + RawConv => q{ + return \$val if $val =~ /^Binary/; + return \$val if $val =~ /^\xff\xd8\xff/; + $$self{PreviewError} = 1 unless $val eq 'none'; + return undef; + }, + ValueConvInv => '$val || "none"', + WriteCheck => 'return $val=~/^(none|\xff\xd8\xff)/s ? undef : "Not a valid image"', + ChangeBase => '$dirStart + $dataPos - 8', + }, + 0x301 => { + Name => 'UnknownBlock', + Notes => 'unknown 320kB block, not copied to JPEG images', + Flags => [ 'Unknown', 'Binary', 'Drop' ], + }, + # 0x302 - same value as 4 unknown bytes at the end of JPEG or after the DNG TIFF header (ImageID, ref IB) + 0x303 => { + Name => 'LensType', + Writable => 'string', + ValueConv => '$val=~s/ +$//; $val', # trim trailing spaces + ValueConvInv => '$val', + }, + 0x304 => { #IB + Name => 'FocusDistance', + Notes => 'focus distance in mm for most models, but cm for others', + Writable => 'int32u', + }, + 0x311 => { + Name => 'ExternalSensorBrightnessValue', + Condition => '$$self{Model} =~ /Typ 006/', + Notes => 'Leica S only', + Format => 'rational64s', # (may be incorrectly unsigned in JPEG images) + Writable => 'rational64s', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x312 => { + Name => 'MeasuredLV', + Condition => '$$self{Model} =~ /Typ 006/', + Notes => 'Leica S only', + Format => 'rational64s', # (may be incorrectly unsigned in JPEG images) + Writable => 'rational64s', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x320 => { + Name => 'FirmwareVersion', + Condition => '$$self{Model} =~ /Typ 006/', + Notes => 'Leica S only', + Writable => 'int8u', + Count => 4, + PrintConv => '$val=~tr/ /./; $val', + PrintConvInv => '$val=~tr/./ /; $val', + }, + 0x321 => { #IB + Name => 'LensSerialNumber', + Condition => '$$self{Model} =~ /Typ 006/', + Notes => 'Leica S only', + Writable => 'int32u', + PrintConv => 'sprintf("%.10d",$val)', + PrintConvInv => '$val', + }, + # 0x321 - SerialNumber for Leica S? (ref IB) + # 0x340 - same as 0x302 (ImageID, ref IB) +); + +# Leica type9 maker notes (ref IB) (S) +%Image::ExifTool::Panasonic::Leica9 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'Leica', 2 => 'Camera' }, + NOTES => 'This information is written by the Leica S (Typ 007) and M10 models.', + 0x304 => { + Name => 'FocusDistance', + Notes => 'focus distance in mm for most models, but cm for others', + Writable => 'int32u', + }, + 0x311 => { + Name => 'ExternalSensorBrightnessValue', + Format => 'rational64s', # (may be incorrectly unsigned in JPEG images) + Writable => 'rational64s', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + 0x312 => { + Name => 'MeasuredLV', + Format => 'rational64s', # (may be incorrectly unsigned in JPEG images) + Writable => 'rational64s', + PrintConv => 'sprintf("%.2f", $val)', + PrintConvInv => '$val', + }, + # 0x340 - ImageUniqueID + 0x34c => { #23 + Name => 'UserProfile', + Writable => 'string', + }, + # 0x357 int32u - 0=DNG, 3162=JPG (ref 23) + 0x359 => { #23 + Name => 'ISOSelected', + Writable => 'int32s', + PrintConv => { + 0 => 'Auto', + OTHER => sub { return shift; }, + }, + }, + 0x35a => { #23 + Name => 'FNumber', + Writable => 'int32s', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => 'sprintf("%.1f", $val)', + PrintConvInv => '$val', + }, + 0x035b => { #IB + Name => 'CorrelatedColorTemp', # (in Kelvin) + Writable => 'int16u', + }, + 0x035c => { #IB + Name => 'ColorTint', # (same units as Adobe is using) + Writable => 'int16s', + }, + 0x035d => { #IB + Name => 'WhitePoint', # (x/y) + Writable => 'rational64u', + Count => 2, + }, +); + +# Type 2 tags (ref PH) +%Image::ExifTool::Panasonic::Type2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FIRST_ENTRY => 0, + FORMAT => 'int16u', + NOTES => q{ + This type of maker notes is used by models such as the NV-DS65, PV-D2002, + PV-DC3000, PV-DV203, PV-DV401, PV-DV702, PV-L2001, PV-SD4090, PV-SD5000 and + iPalm. + }, + 0 => { + Name => 'MakerNoteType', + Format => 'string[4]', + }, + # seems to vary inversely with amount of light, so I'll call it 'Gain' - PH + # (minimum is 16, maximum is 136. Value is 0 for pictures captured from video) + 3 => 'Gain', +); + +# Face detection position information (ref PH) +%Image::ExifTool::Panasonic::FaceDetInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + WRITABLE => 1, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + DATAMEMBER => [ 0 ], + NOTES => 'Face detection position information.', + 0 => { + Name => 'NumFacePositions', + Format => 'int16u', + DataMember => 'NumFacePositions', + RawConv => '$$self{NumFacePositions} = $val', + Notes => q{ + number of detected face positions stored in this record. May be less than + FacesDetected + }, + }, + 1 => { + Name => 'Face1Position', + Format => 'int16u[4]', + RawConv => '$$self{NumFacePositions} < 1 ? undef : $val', + Notes => q{ + 4 numbers: X/Y coordinates of the face center and width/height of face. + Coordinates are relative to an image twice the size of the thumbnail, or 320 + pixels wide + }, + }, + 5 => { + Name => 'Face2Position', + Format => 'int16u[4]', + RawConv => '$$self{NumFacePositions} < 2 ? undef : $val', + }, + 9 => { + Name => 'Face3Position', + Format => 'int16u[4]', + RawConv => '$$self{NumFacePositions} < 3 ? undef : $val', + }, + 13 => { + Name => 'Face4Position', + Format => 'int16u[4]', + RawConv => '$$self{NumFacePositions} < 4 ? undef : $val', + }, + 17 => { + Name => 'Face5Position', + Format => 'int16u[4]', + RawConv => '$$self{NumFacePositions} < 5 ? undef : $val', + }, +); + +# Face recognition information from DMC-TZ7 (ref PH) +%Image::ExifTool::Panasonic::FaceRecInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + WRITABLE => 1, + FIRST_ENTRY => 0, + DATAMEMBER => [ 0 ], + NOTES => q{ + Tags written by cameras with facial recognition. These cameras not only + detect faces in an image, but also recognize specific people based a + user-supplied set of known faces. + }, + 0 => { + Name => 'FacesRecognized', + Format => 'int16u', + DataMember => 'FacesRecognized', + RawConv => '$$self{FacesRecognized} = $val', + }, + 4 => { + Name => 'RecognizedFace1Name', + Format => 'string[20]', + RawConv => '$$self{FacesRecognized} < 1 ? undef : $val', + }, + 24 => { + Name => 'RecognizedFace1Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesRecognized} < 1 ? undef : $val', + Notes => 'coordinates in same format as face detection tags above', + }, + 32 => { + Name => 'RecognizedFace1Age', + Format => 'string[20]', + RawConv => '$$self{FacesRecognized} < 1 ? undef : $val', + }, + 52 => { + Name => 'RecognizedFace2Name', + Format => 'string[20]', + RawConv => '$$self{FacesRecognized} < 2 ? undef : $val', + }, + 72 => { + Name => 'RecognizedFace2Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesRecognized} < 2 ? undef : $val', + }, + 80 => { + Name => 'RecognizedFace2Age', + Format => 'string[20]', + RawConv => '$$self{FacesRecognized} < 2 ? undef : $val', + }, + 100 => { + Name => 'RecognizedFace3Name', + Format => 'string[20]', + RawConv => '$$self{FacesRecognized} < 3 ? undef : $val', + }, + 120 => { + Name => 'RecognizedFace3Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesRecognized} < 3 ? undef : $val', + }, + 128 => { + Name => 'RecognizedFace3Age', + Format => 'string[20]', + RawConv => '$$self{FacesRecognized} < 3 ? undef : $val', + }, +); + +# PANA atom found in user data of MP4 videos (ref PH) +%Image::ExifTool::Panasonic::PANA = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => q{ + Tags extracted from the PANA and LEIC user data found in MP4 videos from + various Panasonic and Leica models. + }, + 0x00 => { + Name => 'Make', + Condition => '$$valPt =~ /^(LEICA|Panasonic)/', # (only seen "LEICA") + Groups => { 2 => 'Camera' }, + Format => 'string[22]', + RawConv => '$$self{LeicaLEIC} = 1;$$self{Make} = $val', + }, + 0x04 => { + Name => 'Model', + Condition => '$$valPt =~ /^[^\0]{6}/ and not $$self{LeicaLEIC}', + Description => 'Camera Model Name', + Groups => { 2 => 'Camera' }, + Format => 'string[16]', + RawConv => '$$self{Model} = $val', + }, + 0x0c => { # (FZ1000) + Name => 'Model', + Condition => '$$valPt =~ /^[^\0]{6}/ and not $$self{LeicaLEIC} and not $$self{Model}', + Description => 'Camera Model Name', + Groups => { 2 => 'Camera' }, + Format => 'string[16]', + RawConv => '$$self{Model} = $val', + }, + 0x10 => { # (DC-FT7) + Name => 'JPEG-likeData', + # looks like a JPEG preview, but not a well-formed JPEG file + Condition => '$$valPt =~ /^\xff\xd8\xff\xe1..Exif\0\0/s', + Format => 'undef[$size-0x10]', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + Start => 12, + }, + }, + 0x16 => { + Name => 'Model', + Condition => '$$self{LeicaLEIC}', + Description => 'Camera Model Name', + Groups => { 2 => 'Camera' }, + Format => 'string[30]', + RawConv => '$$self{Model} = $val', + }, + 0x40 => { + Name => 'ThumbnailTest', + Format => 'undef[0x600]', + Hidden => 1, + RawConv => q{ + if (substr($val,0x1c,3) eq "\xff\xd8\xff") { # offset 0x5c + $$self{ThumbType} = 1; + } elsif (substr($val,0x506,3) eq "\xff\xd8\xff") { # offset 0x546 + $$self{ThumbType} = 2; + } elsif (substr($val,0x51e,3) eq "\xff\xd8\xff") { # offset 0x55e (Leica T) + $$self{ThumbType} = 3; + } else { + $$self{ThumbType} = 0; + } + return undef; + }, + }, + 0x34 => { + Name => 'Version1', + Condition => '$$self{LeicaLEIC}', + Format => 'string[14]', + }, + 0x3e => { + Name => 'Version2', + Condition => '$$self{LeicaLEIC}', + Format => 'string[14]', + }, + 0x50 => { + Name => 'MakerNoteLeica5', + Condition => '$$self{LeicaLEIC}', + SubDirectory => { + TagTable => 'Image::ExifTool::Panasonic::Leica5', + ProcessProc => \&ProcessLeicaLEIC, + }, + }, + 0x58 => { + Name => 'ThumbnailWidth', + Condition => '$$self{ThumbType} == 1', + Notes => 'Panasonic models', + Format => 'int16u', + }, + 0x5a => { + Name => 'ThumbnailHeight', + Condition => '$$self{ThumbType} == 1', + Format => 'int16u', + }, + 0x5c => { + Name => 'ThumbnailImage', + Condition => '$$self{ThumbType} == 1', + Groups => { 2 => 'Preview' }, + Format => 'undef[16384]', + ValueConv => '$val=~s/\0*$//; \$val', # remove trailing zeros + }, + # 0x5c - there is some messed-up EXIF-IFD-looking data starting here in + # Leica X VARIO MP4 videos, but it doesn't quite make sense + 0x536 => { # (Leica X VARIO) + Name => 'ThumbnailWidth', + Condition => '$$self{ThumbType} == 2', + Notes => 'Leica X Vario', + Format => 'int32uRev', # (little-endian) + }, + 0x53a => { # (Leica X VARIO) + Name => 'ThumbnailHeight', + Condition => '$$self{ThumbType} == 2', + Format => 'int32uRev', # (little-endian) + }, + 0x53e => { # (Leica X VARIO) + Name => 'ThumbnailLength', + Condition => '$$self{ThumbType} == 2', + Format => 'int32uRev', # (little-endian) + }, + 0x546 => { # (Leica X VARIO) + Name => 'ThumbnailImage', + Condition => '$$self{ThumbType} == 2', + Groups => { 2 => 'Preview' }, + Format => 'undef[$val{0x53e}]', + Binary => 1, + }, + 0x54e => { # (Leica T) + Name => 'ThumbnailWidth', + Condition => '$$self{ThumbType} == 3', + Notes => 'Leica X Vario', + Format => 'int32uRev', # (little-endian) + }, + 0x552 => { # (Leica T) + Name => 'ThumbnailHeight', + Condition => '$$self{ThumbType} == 3', + Format => 'int32uRev', # (little-endian) + }, + 0x556 => { # (Leica T) + Name => 'ThumbnailLength', + Condition => '$$self{ThumbType} == 3', + Format => 'int32uRev', # (little-endian) + }, + 0x55e => { # (Leica T) + Name => 'ThumbnailImage', + Condition => '$$self{ThumbType} == 3', + Groups => { 2 => 'Preview' }, + Format => 'undef[$val{0x556}]', + Binary => 1, + }, + 0x4068 => { + Name => 'ExifData', + Condition => '$$valPt =~ /^\xff\xd8\xff\xe1..Exif\0\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + Start => 12, + }, + }, + 0x4080 => { # (FZ1000) + Name => 'ExifData', + Condition => '$$valPt =~ /^\xff\xd8\xff\xe1..Exif\0\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + Start => 12, + }, + }, + 0x200080 => { # (GH6) + Name => 'ExifData', + Condition => '$$valPt =~ /^\xff\xd8\xff\xe1..Exif\0\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + Start => 12, + }, + }, +); + +# Panasonic Composite tags +%Image::ExifTool::Panasonic::Composite = ( + GROUPS => { 2 => 'Camera' }, + AdvancedSceneMode => { + SeparateTable => 'Panasonic AdvancedSceneMode', # print values in a separate table + Require => { + 0 => 'Model', + 1 => 'SceneMode', + 2 => 'AdvancedSceneType', + }, + ValueConv => '"$val[0] $val[1] $val[2]"', + PrintConv => { #PH + OTHER => sub { + my ($val,$flag,$conv) = @_; + $val =~ s/.* (\d+ \d+)/$1/; # drop model name + return $$conv{$val} if $$conv{$val}; + my @v = split ' ', $val; + my $prt = $shootingMode{$v[0]}; + # AdvancedSceneType=1 for non-automatic modes P,A,S,SCN (ref 19) + # AdvancedSceneType=5 for automatic mode iA (ref 19) + if ($prt) { + return $prt if $v[1] == 1; + return "$prt (intelligent auto)" if $v[1] == 5; #forum11523 + return "$prt (intelligent auto plus)" if $v[1] == 7; #forum11523 + return "$prt ($v[1])"; + } + return "Unknown ($val)"; + }, + Notes => 'A Composite tag derived from Model, SceneMode and AdvancedSceneType.', + '0 1' => 'Off', + # '0 7' - seen this for V-LUX movies (PH) + # '0 8' - seen for D-LUX(Typ104) movies (PH) + '2 2' => 'Outdoor Portrait', #(FZ28) + '2 3' => 'Indoor Portrait', #(FZ28) + '2 4' => 'Creative Portrait', #(FZ28) + '3 2' => 'Nature', #(FZ28) + '3 3' => 'Architecture', #(FZ28) + '3 4' => 'Creative Scenery', #(FZ28) + #'3 5' - ? (FT1) + '4 2' => 'Outdoor Sports', #(FZ28) + '4 3' => 'Indoor Sports', #(FZ28) + '4 4' => 'Creative Sports', #(FZ28) + '9 2' => 'Flower', #(FZ28) + '9 3' => 'Objects', #(FZ28) + '9 4' => 'Creative Macro', #(FZ28) + #'9 5' - ? (GF3) + '18 1' => 'High Sensitivity', #forum11523 (TZ5) + '20 1' => 'Fireworks', #forum11523 (TZ5) + '21 2' => 'Illuminations', #(FZ28) + '21 4' => 'Creative Night Scenery', #(FZ28) + #'21 5' - ? (LX3) + '26 1' => 'High-speed Burst (shot 1)', #forum11523 (TZ5) + '27 1' => 'High-speed Burst (shot 2)', #forum11523 (TZ5) + '29 1' => 'Snow', #forum11523 (TZ5) + '30 1' => 'Starry Sky', #forum11523 (TZ5) + '31 1' => 'Beach', #forum11523 (TZ5) + '36 1' => 'High-speed Burst (shot 3)', #forum11523 (TZ5) + #'37 5' - ? (various) + '39 1' => 'Aerial Photo / Underwater / Multi-aspect', #forum11523 (TZ5) + '45 2' => 'Cinema', #(GF2) + '45 7' => 'Expressive', #(GF1,GF2) + '45 8' => 'Retro', #(GF1,GF2) + '45 9' => 'Pure', #(GF1,GF2) + '45 10' => 'Elegant', #(GF1,GF2) + '45 12' => 'Monochrome', #(GF1,GF2) + '45 13' => 'Dynamic Art', #(GF1,GF2) + '45 14' => 'Silhouette', #(GF1,GF2) + '51 2' => 'HDR Art', #12 + '51 3' => 'HDR B&W', #12 + '59 1' => 'Expressive', #(GF5) + '59 2' => 'Retro', #(GF5) + '59 3' => 'High Key', #(GF5) + '59 4' => 'Sepia', #(GF3,GF5) + '59 5' => 'High Dynamic', #(GF3,GF5) + '59 6' => 'Miniature', #(GF3) + '59 9' => 'Low Key', #(GF5) + '59 10' => 'Toy Effect', #(GF5) + '59 11' => 'Dynamic Monochrome', #(GF5) + '59 12' => 'Soft', #(GF5) + '66 1' => 'Impressive Art', #19 + '66 2' => 'Cross Process', #(GF5) + '66 3' => 'Color Select', #(GF5) (called "One Point Color" by some other models - PH) + '66 4' => 'Star', #(GF5) + '90 3' => 'Old Days', #18 + '90 4' => 'Sunshine', #18 + '90 5' => 'Bleach Bypass', #18 + '90 6' => 'Toy Pop', #18 + '90 7' => 'Fantasy', #18 + '90 8' => 'Monochrome', #PH (GX7) + '90 9' => 'Rough Monochrome', #PH (GX7) + '90 10' => 'Silky Monochrome', #PH (GX7) + '92 1' => 'Handheld Night Shot', #Horst Wandres (FZ1000) + # TZ40 Creative Control modes (ref 19) + 'DMC-TZ40 90 1' => 'Expressive', + 'DMC-TZ40 90 2' => 'Retro', + 'DMC-TZ40 90 3' => 'High Key', + 'DMC-TZ40 90 4' => 'Sepia', + 'DMC-TZ40 90 5' => 'High Dynamic', + 'DMC-TZ40 90 6' => 'Miniature', + 'DMC-TZ40 90 9' => 'Low Key', + 'DMC-TZ40 90 10' => 'Toy Effect', + 'DMC-TZ40 90 11' => 'Dynamic Monochrome', + 'DMC-TZ40 90 12' => 'Soft', + }, + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Panasonic'); + +#------------------------------------------------------------------------------ +# Inverse conversion for Leica M9 lens codes +# Inputs: 0) value +# Returns: Converted value, or undef on error +sub LensTypeConvInv($) +{ + my $val = shift; + if ($val =~ /^(\d+) (\d+)$/) { + return ($1 << 2) + ($2 & 0x03); + } elsif ($val =~ /^\d+$/) { + my $bits = $frameSelectorBits{$val}; + return undef unless defined $bits; + return ($val << 2) | $bits; + } else { + return undef; + } +} + +#------------------------------------------------------------------------------ +# Convert Leica Kelvin white balance +# Inputs: 0) value, 1) flag to perform inverse conversion +# Returns: Converted value, or undef on error +sub WhiteBalanceConv($;$$) +{ + my ($val, $inv) = @_; + if ($inv) { + return $1 + 0x8000 if $val =~ /(\d+)/; + } else { + return ($val - 0x8000) . ' Kelvin' if $val > 0x8000; + } + return undef; +} + +#------------------------------------------------------------------------------ +# Process Leica makernotes in LEIC atom of MP4 videos (Leica T and X Vario) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessLeicaLEIC($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart); + return 0 if $dirLen < 6; + SetByteOrder('II'); + my $numEntries = Get16u($dataPt, $dirStart); + return 0 if $numEntries < 1 or $numEntries > 255; + my $size = Get32u($dataPt, $dirStart + 2); + return 0 if $size < $numEntries * 12 or $size + 6 > $dirLen; + # the Leica programmers want to make things difficult, so they store + # the entry count before the directory size, making it impossible to + # process as a standard TIFF IFD without a bit of reorganization... + Set16u($numEntries, $dataPt, $dirStart + 4); + my %dirInfo = %$dirInfo; + $dirInfo{DirStart} = $dirStart + 4; + $dirInfo{DirLen} = $size - 4; + $dirInfo{DataPos} -= $dirStart; + $dirInfo{Base} += $dirStart; + return Image::ExifTool::Exif::ProcessExif($et, \%dirInfo, $tagTablePtr); + return 1; +} + +#------------------------------------------------------------------------------ +# Process MakerNote trailer written by Leica S2 +# Inputs: 0) ExifTool object ref, 1) new absolute position of Leica trailer when writing +# Returns: On success: 1 when reading, directory data when writing; otherwise undef +# Notes: +# - may be called twice for a file if the first call doesn't succeed +# - must leave RAF position unchanged +# - uses information from LeicaTrailer member: +# TagInfo = tag info ref for MakerNote SubDirectory +# Offset/Size = value offset/size from MakerNote IFD +# TrailStart/TrailLen = actual JPEG trailer position/size (2nd call only) +# - deletes LeicaTrailer member and sets LeicaTrailerPos when successful +sub ProcessLeicaTrailer($;$) +{ + my ($et, $newPos) = @_; + my $trail = $$et{LeicaTrailer}; + my $raf = $$et{RAF}; + my $trailPos = $$trail{TrailPos}; + my $pos = $trailPos || $$trail{Offset}; + my $len = $$trail{TrailLen} || $$trail{Size}; + my ($buff, $result, %tagPtr); + + delete $$et{LeicaTrailer} if $trailPos; # done after this + unless ($len > 0) { + $et->Warn('Missing Leica MakerNote trailer', 1) if $trailPos; + delete $$et{LeicaTrailer}; + return undef; + } + my $oldPos = $raf->Tell(); + my $ok = ($raf->Seek($pos, 0) and $raf->Read($buff, $len) == $len); + $raf->Seek($oldPos, 0); + unless ($ok) { + $et->Warn('Error reading Leica MakerNote trailer', 1) if $trailPos; + return undef; + } + # look for Leica MakerNote header (should be at start of + # trailer, but allow up to 256 bytes of garbage just in case) + if ($buff !~ /^(.{0,256})LEICA\0..../sg) { + my $what = $trailPos ? 'trailer' : 'offset'; + $et->Warn("Invalid Leica MakerNote $what", 1); + return undef; + } + my $junk = $1; + my $start = pos($buff) - 10; + if ($start and not $trailPos) { + $et->Warn('Invalid Leica MakerNote offset', 1); + return undef; + } +# +# all checks passed -- go ahead and process the trailer now +# + my $hdrLen = 8; + my $dirStart = $start + $hdrLen; + my $tagInfo = $$trail{TagInfo}; + if ($$et{HTML_DUMP}) { + my $name = $$tagInfo{Name}; + $et->HDump($pos+$start, $len-$start, "$name value", 'Leica MakerNote trailer', 4); + $et->HDump($pos+$start, $hdrLen, "MakerNotes header", $name); + } elsif ($et->Options('Verbose')) { + my $where = sprintf('at offset 0x%x', $pos); + $et->VPrint(0, "Leica MakerNote trailer ($len bytes $where):\n"); + } + # delete LeicaTrailer member so we don't try to process it again + delete $$et{LeicaTrailer}; + $$et{LeicaTrailerPos} = $pos + $start; # return actual start position of Leica trailer + + my $oldOrder = GetByteOrder(); + my $num = Get16u(\$buff, $dirStart); # get entry count + ToggleByteOrder() if ($num>>8) > ($num&0xff); # set byte order + + # use specialized algorithm to automatically fix offsets + my $valStart = $dirStart + 2 + 12 * $num + 4; + my $fix = 0; + if ($valStart < $len) { + my $valBlock = Image::ExifTool::MakerNotes::GetValueBlocks(\$buff, $dirStart, \%tagPtr); + # find the minimum offset (excluding the PreviewImage tag 0x300 and 0x301) + my $minPtr; + foreach (keys %tagPtr) { + my $ptr = $tagPtr{$_}; + next if $_ == 0x300 or $_ == 0x301 or not $ptr or $ptr == 0xffffffff; + $minPtr = $ptr if not defined $minPtr or $minPtr > $ptr; + } + if ($minPtr) { + my $diff = $minPtr - ($valStart + $pos); + pos($buff) = $valStart; + my $expect; + if ($$et{Model} eq 'S2') { + # scan value data for the first non-zero byte + if ($buff =~ /[^\0]/g) { + my $n = pos($buff) - 1 - $valStart; # number of zero bytes + # S2 writes 282 bytes of zeros, exiftool writes none + $expect = $n >= 282 ? 282 : 0; + } + } else { # M (Type 240) + # scan for the lens type (M writes 114 bytes of garbage first) + if ($buff =~ /\G.{114}([\x20-\x7f]*\0*)/sg and length($1) >= 50) { + $expect = 114; + } + } + my $fixBase = $et->Options('FixBase'); + if (not defined $expect) { + $et->Warn('Unrecognized Leica trailer structure'); + } elsif ($diff != $expect or defined $fixBase) { + $fix = $expect - $diff; + if (defined $fixBase) { + $fix = $fixBase if $fixBase ne ''; + $et->Warn("Adjusted MakerNotes base by $fix",1); + } else { + $et->Warn("Possibly incorrect maker notes offsets (fixed by $fix)",1); + } + } + } + } + # generate dirInfo for Leica MakerNote directory + my %dirInfo = ( + Name => $$tagInfo{Name}, + Base => $fix, + DataPt => \$buff, + DataPos => $pos - $fix, + DataLen => $len, + DirStart => $dirStart, + DirLen => $len - $dirStart, + DirName => 'MakerNotes', + Parent => 'ExifIFD', + TagInfo => $tagInfo, + ); + my $tagTablePtr = GetTagTable($$tagInfo{SubDirectory}{TagTable}); + if ($newPos) { # are we writing? + if ($$et{Model} ne 'S2') { + $et->Warn('Leica MakerNote trailer too messed up to edit. Copying as a block', 1); + return $buff; + } + # set position of new MakerNote IFD (+ 8 for Leica MakerNote header) + $dirInfo{NewDataPos} = $newPos + $start + 8; + $result = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + # write preview image last if necessary and fix up the preview offsets + my $previewInfo = $$et{PREVIEW_INFO}; + delete $$et{PREVIEW_INFO}; + if ($result) { + if ($previewInfo) { + my $fixup = $previewInfo->{Fixup}; + # set preview offset (relative to start of makernotes, + 8 for makernote header) + $fixup->SetMarkerPointers(\$result, 'PreviewImage', length($result) + 8); + $result .= $$previewInfo{Data}; + } + return $junk . substr($buff, $start, $hdrLen) . $result; + } + } else { + # extract information + $result = $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + # also extract as a block if necessary + if ($et->Options('MakerNotes') or + $$et{REQ_TAG_LOOKUP}{lc($$tagInfo{Name})}) + { + # makernote header must be included in RebuildMakerNotes call + $dirInfo{DirStart} -= 8; + $dirInfo{DirLen} += 8; + $$et{MAKER_NOTE_BYTE_ORDER} = GetByteOrder(); + # rebuild maker notes (creates $$et{MAKER_NOTE_FIXUP}) + my $val = Image::ExifTool::Exif::RebuildMakerNotes($et, \%dirInfo, $tagTablePtr); + unless (defined $val) { + $et->Warn('Error rebuilding maker notes (may be corrupt)') if $len > 4; + $val = $buff, + } + my $key = $et->FoundTag($tagInfo, $val); + $et->SetGroup($key, 'ExifIFD'); + } + } + SetByteOrder($oldOrder); + return $result; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Panasonic - Panasonic/Leica 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 +Panasonic and Leica maker notes in EXIF information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.compton.nu/panasonic.html> + +=item L<http://johnst.org/sw/exiftags/> + +=item L<http://homepage3.nifty.com/kamisaka/makernote/makernote_pana.htm> + +=item L<http://bretteville.com/pdfs/M8Metadata_v2.pdf> + +=item L<http://www.digital-leica.com/lens_codes/index.html> + +=item (...plus lots of testing with store demos and my wife's DMC-FS7!) + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Tels, Marcel Coenen, Jens Duttke, Joerg, Michael Byczkowski, Carl +Bretteville, Zdenek Mihula and Olaf Ulrich for their contributions. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Panasonic Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/PanasonicRaw.pm b/ExifTool/lib/Image/ExifTool/PanasonicRaw.pm new file mode 100644 index 0000000..ee389dc --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PanasonicRaw.pm @@ -0,0 +1,955 @@ +#------------------------------------------------------------------------------ +# File: PanasonicRaw.pm +# +# Description: Read/write Panasonic/Leica RAW/RW2/RWL meta information +# +# Revisions: 2009/03/24 - P. Harvey Created +# 2009/05/12 - PH Added RWL file type (same format as RW2) +# +# References: 1) https://exiftool.org/forum/index.php/topic,1542.0.html +# 2) http://www.cybercom.net/~dcoffin/dcraw/ +# 3) http://syscall.eu/#pana +# 4) Klaus Homeister private communication +# IB) Iliah Borg private communication (LibRaw) +# JD) Jens Duttke private communication (TZ3,FZ30,FZ50) +#------------------------------------------------------------------------------ + +package Image::ExifTool::PanasonicRaw; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.28'; + +sub ProcessJpgFromRaw($$$); +sub WriteJpgFromRaw($$$); +sub WriteDistortionInfo($$$); +sub ProcessDistortionInfo($$$); + +my %jpgFromRawMap = ( + IFD1 => 'IFD0', + EXIF => 'IFD0', # to write EXIF as a block + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', + IFD0 => 'APP1', + MakerNotes => 'ExifIFD', + Comment => 'COM', +); + +my %wbTypeInfo = ( + PrintConv => \%Image::ExifTool::Exif::lightSource, + SeparateTable => 'EXIF LightSource', +); + +my %panasonicWhiteBalance = ( #forum9396 + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Cloudy', + 3 => 'Tungsten', + 4 => 'n/a', + 5 => 'Flash', + 6 => 'n/a', + 7 => 'n/a', + 8 => 'Custom#1', + 9 => 'Custom#2', + 10 => 'Custom#3', + 11 => 'Custom#4', + 12 => 'Shade', + 13 => 'Kelvin', + 16 => 'AWBc', # GH5 and G9 (Makernotes WB==19) +); + +# Tags found in Panasonic RAW/RW2/RWL images (ref PH) +%Image::ExifTool::PanasonicRaw::Main = ( + GROUPS => { 0 => 'EXIF', 1 => 'IFD0', 2 => 'Image'}, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITE_GROUP => 'IFD0', # default write group + NOTES => 'These tags are found in IFD0 of Panasonic/Leica RAW, RW2 and RWL images.', + 0x01 => { + Name => 'PanasonicRawVersion', + Writable => 'undef', + }, + 0x02 => 'SensorWidth', #1/PH + 0x03 => 'SensorHeight', #1/PH + 0x04 => 'SensorTopBorder', #JD + 0x05 => 'SensorLeftBorder', #JD + 0x06 => 'SensorBottomBorder', #PH + 0x07 => 'SensorRightBorder', #PH + # observed values for unknown tags - PH + # 0x08: 1 + # 0x09: 1,3,4 + # 0x0a: 12 + # (IB gave 0x08-0x0a as BlackLevel tags, but Klaus' decoding makes more sense) + 0x08 => { Name => 'SamplesPerPixel', Writable => 'int16u', Protected => 1 }, #4 + 0x09 => { #4 + Name => 'CFAPattern', + Writable => 'int16u', + Protected => 1, + PrintConv => { + 0 => 'n/a', + 1 => '[Red,Green][Green,Blue]', # (CM-1, FZ70) + 2 => '[Green,Red][Blue,Green]', # (LX-7) + 3 => '[Green,Blue][Red,Green]', # (ZS100, FZ2500, FZ1000, ...) + 4 => '[Blue,Green][Green,Red]', # (LC-100, G-7, V-LUX1, ...) + }, + }, + 0x0a => { Name => 'BitsPerSample', Writable => 'int16u', Protected => 1 }, #4 + 0x0b => { #4 + Name => 'Compression', + Writable => 'int16u', + Protected => 1, + PrintConv => { + 34316 => 'Panasonic RAW 1', # (most models - RAW/RW2/RWL) + 34826 => 'Panasonic RAW 2', # (DIGILUX 2 - RAW) + 34828 => 'Panasonic RAW 3', # (D-LUX2,D-LUX3,FZ30,LX1 - RAW) + 34830 => 'Panasonic RAW 4', #IB (Leica DIGILUX 3, Panasonic DMC-L1) + }, + }, + # 0x0c: 2 (only Leica Digilux 2) + # 0x0d: 0,1 + # 0x0e,0x0f,0x10: 4095 + 0x0e => { Name => 'LinearityLimitRed', Writable => 'int16u' }, #IB + 0x0f => { Name => 'LinearityLimitGreen', Writable => 'int16u' }, #IB + 0x10 => { Name => 'LinearityLimitBlue', Writable => 'int16u' }, #IB + 0x11 => { #JD + Name => 'RedBalance', + Writable => 'int16u', + ValueConv => '$val / 256', + ValueConvInv => 'int($val * 256 + 0.5)', + Notes => 'found in Digilux 2 RAW images', + }, + 0x12 => { #JD + Name => 'BlueBalance', + Writable => 'int16u', + ValueConv => '$val / 256', + ValueConvInv => 'int($val * 256 + 0.5)', + }, + 0x13 => { #IB + Name => 'WBInfo', + SubDirectory => { TagTable => 'Image::ExifTool::PanasonicRaw::WBInfo' }, + }, + 0x17 => { #1 + Name => 'ISO', + Writable => 'int16u', + }, + # 0x18,0x19,0x1a: 0 + 0x18 => { #IB + Name => 'HighISOMultiplierRed', + Writable => 'int16u', + ValueConv => '$val / 256', + ValueConvInv => 'int($val * 256 + 0.5)', + }, + 0x19 => { #IB + Name => 'HighISOMultiplierGreen', + Writable => 'int16u', + ValueConv => '$val / 256', + ValueConvInv => 'int($val * 256 + 0.5)', + }, + 0x1a => { #IB + Name => 'HighISOMultiplierBlue', + Writable => 'int16u', + ValueConv => '$val / 256', + ValueConvInv => 'int($val * 256 + 0.5)', + }, + # 0x1b: [binary data] (something to do with the camera ISO cababilities: int16u count N, + # followed by table of N entries: int16u ISO, int16u[3] RGB gains - ref IB) + 0x1c => { Name => 'BlackLevelRed', Writable => 'int16u' }, #IB + 0x1d => { Name => 'BlackLevelGreen', Writable => 'int16u' }, #IB + 0x1e => { Name => 'BlackLevelBlue', Writable => 'int16u' }, #IB + 0x24 => { #2 + Name => 'WBRedLevel', + Writable => 'int16u', + }, + 0x25 => { #2 + Name => 'WBGreenLevel', + Writable => 'int16u', + }, + 0x26 => { #2 + Name => 'WBBlueLevel', + Writable => 'int16u', + }, + 0x27 => { #IB + Name => 'WBInfo2', + SubDirectory => { TagTable => 'Image::ExifTool::PanasonicRaw::WBInfo2' }, + }, + # 0x27,0x29,0x2a,0x2b,0x2c: [binary data] + 0x2d => { #IB + Name => 'RawFormat', + Writable => 'int16u', + Protected => 1, + # 2 - RAW DMC-FZ8/FZ18 + # 3 - RAW DMC-L10 + # 4 - RW2 for most other models, including G9 in "pixel shift off" mode and YUNEEC CGO4 + # (must add 15 to black levels for RawFormat == 4) + # 5 - RW2 DC-GH5s; G9 in "pixel shift on" mode + # 6 - RW2 DC-S1, DC-S1r in "pixel shift off" mode + # 7 - RW2 DC-S1r (and probably DC-S1, have no raw samples) in "pixel shift on" mode + # not used - DMC-LX1/FZ30/FZ50/L1/LX1/LX2 + # (modes 5 and 7 are lossless) + }, + 0x2e => { #JD + Name => 'JpgFromRaw', # (writable directory!) + Groups => { 2 => 'Preview' }, + Writable => 'undef', + # protect this tag because it contains all the metadata + Flags => [ 'Binary', 'Protected', 'NestedHtmlDump', 'BlockExtract' ], + Notes => 'processed as an embedded document because it contains full EXIF', + WriteCheck => '$val eq "none" ? undef : $self->CheckImage(\$val)', + DataTag => 'JpgFromRaw', + RawConv => '$self->ValidateImage(\$val,$tag)', + SubDirectory => { + # extract information from embedded image since it is metadata-rich, + # unless HtmlDump option set (note that the offsets will be relative, + # not absolute like they should be in verbose mode) + TagTable => 'Image::ExifTool::JPEG::Main', + WriteProc => \&WriteJpgFromRaw, + ProcessProc => \&ProcessJpgFromRaw, + }, + }, + 0x2f => { Name => 'CropTop', Writable => 'int16u' }, + 0x30 => { Name => 'CropLeft', Writable => 'int16u' }, + 0x31 => { Name => 'CropBottom', Writable => 'int16u' }, + 0x32 => { Name => 'CropRight', Writable => 'int16u' }, + # 0x44 - may contain another pointer to the raw data starting at byte 2 in this data (DC-GH6) + 0x10f => { + Name => 'Make', + Groups => { 2 => 'Camera' }, + Writable => 'string', + DataMember => 'Make', + # save this value as an ExifTool member variable + RawConv => '$self->{Make} = $val', + }, + 0x110 => { + Name => 'Model', + Description => 'Camera Model Name', + Groups => { 2 => 'Camera' }, + Writable => 'string', + DataMember => 'Model', + # save this value as an ExifTool member variable + RawConv => '$self->{Model} = $val', + }, + 0x111 => { + Name => 'StripOffsets', + # (this value is 0xffffffff for some models, and RawDataOffset must be used) + Flags => [ 'IsOffset', 'PanasonicHack' ], + OffsetPair => 0x117, # point to associated byte counts + ValueConv => 'length($val) > 32 ? \$val : $val', + }, + 0x112 => { + Name => 'Orientation', + Writable => 'int16u', + PrintConv => \%Image::ExifTool::Exif::orientation, + Priority => 0, # so IFD1 doesn't take precedence + }, + 0x116 => { + Name => 'RowsPerStrip', + Priority => 0, + }, + 0x117 => { + Name => 'StripByteCounts', + # (note that this value may represent something like uncompressed byte count + # for RAW/RW2/RWL images from some models, and is zero for some other models) + OffsetPair => 0x111, # point to associated offset + ValueConv => 'length($val) > 32 ? \$val : $val', + }, + 0x118 => { + Name => 'RawDataOffset', #PH (RW2/RWL) + IsOffset => '$$et{TIFF_TYPE} =~ /^(RW2|RWL)$/', # (invalid in DNG-converted files) + PanasonicHack => 1, + OffsetPair => 0x117, # (use StripByteCounts as the offset pair) + NotRealPair => 1, # (to avoid Validate warning) + IsImageData => 1, + }, + 0x119 => { + Name => 'DistortionInfo', + SubDirectory => { TagTable => 'Image::ExifTool::PanasonicRaw::DistortionInfo' }, + }, + # 0x11b - chromatic aberration correction (ref 3) (also see forum9366) + 0x11c => { #forum9373 + Name => 'Gamma', + Writable => 'int16u', + # unfortunately it seems that the scaling factor varies with model... + ValueConv => '$val / ($val >= 1024 ? 1024 : ($val >= 256 ? 256 : 100))', + ValueConvInv => 'int($val * 256 + 0.5)', + }, + 0x120 => { + Name => 'CameraIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::PanasonicRaw::CameraIFD', + Base => '$start', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + }, + }, + 0x121 => { #forum9295 + Name => 'Multishot', + Writable => 'int32u', + PrintConv => { + 0 => 'Off', + 65536 => 'Pixel Shift', + }, + }, + # 0x122 - int32u: RAWDataOffset for the GH5s/GX9, or pointer to end of raw data for G9 (forum9295) + 0x127 => { #github193 (newer models) + Name => 'JpgFromRaw2', + Groups => { 2 => 'Preview' }, + DataTag => 'JpgFromRaw2', + RawConv => '$self->ValidateImage(\$val,$tag)', + }, + 0x13b => { + Name => 'Artist', + Groups => { 2 => 'Author' }, + Permanent => 1, # (so we don't add it if the model doesn't write it) + Writable => 'string', + WriteGroup => 'IFD0', + RawConv => '$val =~ s/\s+$//; $val', # trim trailing blanks + }, + 0x2bc => { # PH Extension!! + Name => 'ApplicationNotes', # (writable directory!) + Writable => 'int8u', + Format => 'undef', + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { + DirName => 'XMP', + TagTable => 'Image::ExifTool::XMP::Main', + }, + }, + 0x001b => { #forum9250 + Name => 'NoiseReductionParams', + Writable => 'undef', + Format => 'int16u', + Count => -1, + Flags => 'Protected', + Notes => q{ + the camera's default noise reduction setup. The first number is the number + of entries, then for each entry there are 4 numbers: an ISO speed, and + noise-reduction strengths the R, G and B channels + }, + }, + 0x8298 => { #github193 + Name => 'Copyright', + Groups => { 2 => 'Author' }, + Permanent => 1, # (so we don't add it if the model doesn't write it) + Format => 'undef', + Writable => 'string', + WriteGroup => 'IFD0', + RawConv => $Image::ExifTool::Exif::Main{0x8298}{RawConv}, + RawConvInv => $Image::ExifTool::Exif::Main{0x8298}{RawConvInv}, + PrintConvInv => $Image::ExifTool::Exif::Main{0x8298}{PrintConvInv}, + }, + 0x83bb => { # PH Extension!! + Name => 'IPTC-NAA', # (writable directory!) + Format => 'undef', # convert binary values as undef + Writable => 'int32u', # but write int32u format code in IFD + WriteGroup => 'IFD0', + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { + DirName => 'IPTC', + TagTable => 'Image::ExifTool::IPTC::Main', + }, + }, + 0x8769 => { + Name => 'ExifOffset', + Groups => { 1 => 'ExifIFD' }, + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + DirName => 'ExifIFD', + Start => '$val', + }, + }, + 0x8825 => { + Name => 'GPSInfo', + Groups => { 1 => 'GPS' }, + Flags => 'SubIFD', + SubDirectory => { + DirName => 'GPS', + TagTable => 'Image::ExifTool::GPS::Main', + Start => '$val', + }, + }, + # 0xffff => 'DCSHueShiftValues', #exifprobe (NC) +); + +# white balance information (ref IB) +# (PanasonicRawVersion<200: Digilux 2) +%Image::ExifTool::PanasonicRaw::WBInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + 0 => 'NumWBEntries', + 1 => { Name => 'WBType1', %wbTypeInfo }, + 2 => { Name => 'WB_RBLevels1', Format => 'int16u[2]' }, + 4 => { Name => 'WBType2', %wbTypeInfo }, + 5 => { Name => 'WB_RBLevels2', Format => 'int16u[2]' }, + 7 => { Name => 'WBType3', %wbTypeInfo }, + 8 => { Name => 'WB_RBLevels3', Format => 'int16u[2]' }, + 10 => { Name => 'WBType4', %wbTypeInfo }, + 11 => { Name => 'WB_RBLevels4', Format => 'int16u[2]' }, + 13 => { Name => 'WBType5', %wbTypeInfo }, + 14 => { Name => 'WB_RBLevels5', Format => 'int16u[2]' }, + 16 => { Name => 'WBType6', %wbTypeInfo }, + 17 => { Name => 'WB_RBLevels6', Format => 'int16u[2]' }, + 19 => { Name => 'WBType7', %wbTypeInfo }, + 20 => { Name => 'WB_RBLevels7', Format => 'int16u[2]' }, +); + +# white balance information (ref IB) +# (PanasonicRawVersion>=200: D-Lux2, D-Lux3, DMC-FZ18/FZ30/LX1/L10) +%Image::ExifTool::PanasonicRaw::WBInfo2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + 0 => 'NumWBEntries', + 1 => { Name => 'WBType1', %wbTypeInfo }, + 2 => { Name => 'WB_RGBLevels1', Format => 'int16u[3]' }, + 5 => { Name => 'WBType2', %wbTypeInfo }, + 6 => { Name => 'WB_RGBLevels2', Format => 'int16u[3]' }, + 9 => { Name => 'WBType3', %wbTypeInfo }, + 10 => { Name => 'WB_RGBLevels3', Format => 'int16u[3]' }, + 13 => { Name => 'WBType4', %wbTypeInfo }, + 14 => { Name => 'WB_RGBLevels4', Format => 'int16u[3]' }, + 17 => { Name => 'WBType5', %wbTypeInfo }, + 18 => { Name => 'WB_RGBLevels5', Format => 'int16u[3]' }, + 21 => { Name => 'WBType6', %wbTypeInfo }, + 22 => { Name => 'WB_RGBLevels6', Format => 'int16u[3]' }, + 25 => { Name => 'WBType7', %wbTypeInfo }, + 26 => { Name => 'WB_RGBLevels7', Format => 'int16u[3]' }, +); + +# lens distortion information (ref 3) +# (distortion correction equation: Ru = scale*(Rd + a*Rd^3 + b*Rd^5 + c*Rd^7), ref 3) +%Image::ExifTool::PanasonicRaw::DistortionInfo = ( + PROCESS_PROC => \&ProcessDistortionInfo, + WRITE_PROC => \&WriteDistortionInfo, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + # (don't make this family 0 MakerNotes because we don't want it to be a deletable group) + GROUPS => { 0 => 'PanasonicRaw', 1 => 'PanasonicRaw', 2 => 'Image'}, + WRITABLE => 1, + FORMAT => 'int16s', + FIRST_ENTRY => 0, + NOTES => 'Lens distortion correction information.', + # 0,1 - checksums + 2 => { + Name => 'DistortionParam02', + ValueConv => '$val / 32768', + ValueConvInv => '$val * 32768', + }, + # 3 - usually 0, but seen 0x026b when value 5 is non-zero + 4 => { + Name => 'DistortionParam04', + ValueConv => '$val / 32768', + ValueConvInv => '$val * 32768', + }, + 5 => { + Name => 'DistortionScale', + ValueConv => '1 / (1 + $val/32768)', + ValueConvInv => '(1/$val - 1) * 32768', + }, + # 6 - seen 0x0000-0x027f + 7.1 => { + Name => 'DistortionCorrection', + Mask => 0x0f, + # (have seen the upper 4 bits set for GF5 and GX1, giving a value of -4095 - PH) + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 8 => { + Name => 'DistortionParam08', + ValueConv => '$val / 32768', + ValueConvInv => '$val * 32768', + }, + 9 => { + Name => 'DistortionParam09', + ValueConv => '$val / 32768', + ValueConvInv => '$val * 32768', + }, + # 10 - seen 0xfc,0x0101,0x01f4,0x021d,0x0256 + 11 => { + Name => 'DistortionParam11', + ValueConv => '$val / 32768', + ValueConvInv => '$val * 32768', + }, + 12 => { + Name => 'DistortionN', + Unknown => 1, + }, + # 13 - seen 0x0000,0x01f9-0x02b2 + # 14,15 - checksums +); + +# Panasonic RW2 camera IFD written by GH5 (ref PH) +# (doesn't seem to be valid for the GF7 or GM5 -- encrypted?) +%Image::ExifTool::PanasonicRaw::CameraIFD = ( + GROUPS => { 0 => 'PanasonicRaw', 1 => 'CameraIFD', 2 => 'Camera'}, + # (don't know what format codes 0x101 and 0x102 are for, so just + # map them into 4 = int32u for now) + VARS => { MAP_FORMAT => { 0x101 => 4, 0x102 => 4 } }, + 0x1001 => { #forum9388 + Name => 'MultishotOn', + Writable => 'int32u', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x1100 => { #forum9274 + Name => 'FocusStepNear', + Writable => 'int16s', + }, + 0x1101 => { #forum9274 (was forum8484) + Name => 'FocusStepCount', + Writable => 'int16s', + }, + 0x1102 => { #forum9417 + Name => 'FlashFired', + Writable => 'int32u', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + # 0x1104 - set when camera shoots on lowest possible Extended-ISO (forum9290) + 0x1105 => { #forum9392 + Name => 'ZoomPosition', + Notes => 'in the range 0-255 for most cameras', + Writable => 'int32u', + }, + 0x1200 => { #forum9278 + Name => 'LensAttached', + Notes => 'many CameraIFD tags are invalid if there is no lens attached', + Writable => 'int32u', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + # Note: LensTypeMake and LensTypeModel are combined into a Composite LensType tag + # defined in Olympus.pm which has the same values as Olympus:LensType + 0x1201 => { #IB + Name => 'LensTypeMake', + Condition => '$format eq "int16u"', + Writable => 'int16u', + # when format is int16u, these values have been observed: + # 0 - Olympus or unknown lens + # 2 - Leica or Lumix lens + # when format is int32u (S models), these values have been observed (ref IB): + # 256 - Leica lens + # 257 - Lumix lens + # 258 - ? (seen once) + }, + 0x1202 => { #IB + Name => 'LensTypeModel', + Condition => '$format eq "int16u"', + Writable => 'int16u', + RawConv => q{ + return undef unless $val; + require Image::ExifTool::Olympus; # (to load Composite LensID) + return $val; + }, + ValueConv => '$_=sprintf("%.4x",$val); s/(..)(..)/$2 $1/; $_', + ValueConvInv => '$val =~ s/(..) (..)/$2$1/; hex($val)', + }, + 0x1203 => { #4 + Name => 'FocalLengthIn35mmFormat', + Writable => 'int16u', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + # 0x1300 - incident light value? (ref forum11395) + 0x1301 => { #forum11395 + Name => 'ApertureValue', + Writable => 'int16s', + Priority => 0, + ValueConv => '2 ** ($val / 512)', + ValueConvInv => '$val>0 ? 512*log($val)/log(2) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x1302 => { #forum11395 + Name => 'ShutterSpeedValue', + Writable => 'int16s', + Priority => 0, + ValueConv => 'abs($val/256)<100 ? 2**(-$val/256) : 0', + ValueConvInv => '$val>0 ? -256*log($val)/log(2) : -25600', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x1303 => { #forum11395 + Name => 'SensitivityValue', + Writable => 'int16s', + ValueConv => '$val / 256', + ValueConvInv => 'int($val * 256)', + }, + 0x1305 => { #forum9384 + Name => 'HighISOMode', + Writable => 'int16u', + RawConv => '$val || undef', + PrintConv => { 1 => 'On', 2 => 'Off' }, + }, + # 0x1306 EV for some models like the GX8 (forum11395) + # 0x140b - scaled overall black level? (ref forum9281) + # 0x1411 - scaled black level per channel difference (ref forum9281) + 0x1412 => { #forum11397 + Name => 'FacesDetected', + Writable => 'int8u', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + # 0x2000 - WB tungsten=3, daylight=4 (ref forum9467) + # 0x2009 - scaled black level per channel (ref forum9281) + # 0x3000-0x310b - red/blue balances * 1024 (ref forum9467) + # 0x3000 modifiedTungsten-Red (-2?) + # 0x3001 modifiedTungsten-Blue (-2?) + # 0x3002 modifiedDaylight-Red (-2?) + # 0x3003 modifiedDaylight-Blue (-2?) + # 0x3004 modifiedTungsten-Red (-1?) + # 0x3005 modifiedTungsten-Blue (-1?) + # 0x3006 modifiedDaylight-Red (-1?) + # 0x3007 modifiedDaylight-Blue (-1?) + # 0x3100 DefaultTungsten-Red + # 0x3101 DefaultTungsten-Blue + # 0x3102 DefaultDaylight-Red + # 0x3103 DefaultDaylight-Blue + # 0x3104 modifiedTungsten-Red (+1?) + # 0x3105 modifiedTungsten-Blue (+1?) + # 0x3106 modifiedDaylight-Red (+1?) + # 0x3107 modifiedDaylight-Blue (+1?) + # 0x3108 modifiedTungsten-Red (+2?) + # 0x3109 modifiedTungsten-Blue (+2?) + # 0x310a modifiedDaylight-Red (+2?) + # 0x310b modifiedDaylight-Blue (+2?) + 0x3200 => { #forum9275 + Name => 'WB_CFA0_LevelDaylight', + Writable => 'int16u', + }, + 0x3201 => { #forum9275 + Name => 'WB_CFA1_LevelDaylight', + Writable => 'int16u', + }, + 0x3202 => { #forum9275 + Name => 'WB_CFA2_LevelDaylight', + Writable => 'int16u', + }, + 0x3203 => { #forum9275 + Name => 'WB_CFA3_LevelDaylight', + Writable => 'int16u', + }, + # 0x3204-0x3207 - user multipliers * 1024 ? (ref forum9275) + # 0x320a - scaled maximum value of raw data (scaling = 4x) (ref forum9281) + # 0x3209 - gamma (x256) (ref forum9281) + 0x3300 => { #forum9296/9396 + Name => 'WhiteBalanceSet', + Writable => 'int8u', + PrintConv => \%panasonicWhiteBalance, + SeparateTable => 'WhiteBalance', + }, + 0x3420 => { #forum9276 + Name => 'WB_RedLevelAuto', + Writable => 'int16u', + }, + 0x3421 => { #forum9276 + Name => 'WB_BlueLevelAuto', + Writable => 'int16u', + }, + 0x3501 => { #4 + Name => 'Orientation', + Writable => 'int8u', + PrintConv => \%Image::ExifTool::Exif::orientation, + }, + # 0x3504 = Tag 0x1301+0x1302-0x1303 (Bv = Av+Tv-Sv) (forum11395) + # 0x3505 - same as 0x1300 (forum11395) + 0x3600 => { #forum9396 + Name => 'WhiteBalanceDetected', + Writable => 'int8u', + PrintConv => \%panasonicWhiteBalance, + SeparateTable => 'WhiteBalance', + }, +); + +# PanasonicRaw composite tags +%Image::ExifTool::PanasonicRaw::Composite = ( + ImageWidth => { + Require => { + 0 => 'IFD0:SensorLeftBorder', + 1 => 'IFD0:SensorRightBorder', + }, + ValueConv => '$val[1] - $val[0]', + }, + ImageHeight => { + Require => { + 0 => 'IFD0:SensorTopBorder', + 1 => 'IFD0:SensorBottomBorder', + }, + ValueConv => '$val[1] - $val[0]', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::PanasonicRaw'); + + +#------------------------------------------------------------------------------ +# checksum algorithm for lens distortion correction information (ref 3) +# Inputs: 0) data ref, 1) start position, 2) number of bytes, 3) incement +# Returns: checksum value +sub Checksum($$$$) +{ + my ($dataPt, $start, $num, $inc) = @_; + my $csum = 0; + my $i; + for ($i=0; $i<$num; ++$i) { + $csum = (73 * $csum + Get8u($dataPt, $start + $i * $inc)) % 0xffef; + } + return $csum; +} + +#------------------------------------------------------------------------------ +# Read lens distortion information +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessDistortionInfo($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart} || 0; + my $size = $$dirInfo{DirLen} || (length($$dataPt) - $start); + if ($size == 32) { + # verify the checksums (ref 3) + my $csum1 = Checksum($dataPt, $start + 4, 12, 1); + my $csum2 = Checksum($dataPt, $start + 16, 12, 1); + my $csum3 = Checksum($dataPt, $start + 2, 14, 2); + my $csum4 = Checksum($dataPt, $start + 3, 14, 2); + my $res = $csum1 ^ Get16u($dataPt, $start + 2) ^ + $csum2 ^ Get16u($dataPt, $start + 28) ^ + $csum3 ^ Get16u($dataPt, $start + 0) ^ + $csum4 ^ Get16u($dataPt, $start + 30); + $et->Warn('Invalid DistortionInfo checksum',1) if $res; + } else { + $et->Warn('Invalid DistortionInfo',1); + } + return $et->ProcessBinaryData($dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Write lens distortion information +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: updated distortion information or undef on error +sub WriteDistortionInfo($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # (allow dummy access) + my $dat = $et->WriteBinaryData($dirInfo, $tagTablePtr); + if (defined $dat and length($dat) == 32) { + # fix checksums (ref 3) + Set16u(Checksum(\$dat, 4, 12, 1), \$dat, 2); + Set16u(Checksum(\$dat, 16, 12, 1), \$dat, 28); + Set16u(Checksum(\$dat, 2, 14, 2), \$dat, 0); + Set16u(Checksum(\$dat, 3, 14, 2), \$dat, 30); + } else { + $et->Warn('Error wriing DistortionInfo',1); + } + return $dat; +} + +#------------------------------------------------------------------------------ +# Patch for writing non-standard Panasonic RAW/RW2/RWL raw data +# Inputs: 0) offset info ref, 1) raf ref, 2) IFD number +# Returns: error string, or undef on success +# OffsetInfo is a hash by tag ID of lists with the following elements: +# 0 - tag info ref +# 1 - pointer to int32u offset in IFD or value data +# 2 - value count +# 3 - reference to list of original offset values +# 4 - IFD format number +# 5 - (pointer to StripOffsets value added by this PatchRawDataOffset routine) +# 6 - flag set if this is a fixed offset (Panasonic GH6 fixed-offset hack) +sub PatchRawDataOffset($$$) +{ + my ($offsetInfo, $raf, $ifd) = @_; + my $stripOffsets = $$offsetInfo{0x111}; + my $stripByteCounts = $$offsetInfo{0x117}; + my $rawDataOffset = $$offsetInfo{0x118}; + my $err; + $err = 1 unless $ifd == 0; + if ($stripOffsets or $stripByteCounts) { + $err = 1 unless $stripOffsets and $stripByteCounts and $$stripOffsets[2] == 1; + } else { + # the DC-GH6 and DC-GH5M2 write RawDataOffset with no Strip tags, so we need + # to create fake StripByteCounts information for copying the data + if ($$offsetInfo{0x118}) { # (just to be safe) + $stripByteCounts = $$offsetInfo{0x117} = [ $PanasonicRaw::Main{0x117}, 0, 1, [ 0 ], 4 ]; + # set flag so the offset will be fixed (GH6 hack, see https://exiftool.org/forum/index.php?topic=13861.0) + # (of course, fixing up the offset is now unnecessary, but continue to do this even + # though the fixup adjustment will be 0 because this allows us to delete the following + # line to remove the fix-offset restriction if Panasonic ever sees the light, but note + # that in this case we should investigate the purpose of the seemily-duplicate raw + # data offset contained within PanasonicRaw_0x0044) + $$offsetInfo{0x118}[6] = 1; + } + } + if ($rawDataOffset and not $err) { + $err = 1 unless $$rawDataOffset[2] == 1; + if ($stripOffsets) { + $err = 1 unless $$stripOffsets[3][0] == 0xffffffff or $$stripByteCounts[3][0] == 0; + } + } + $err and return 'Unsupported Panasonic/Leica RAW variant'; + if ($rawDataOffset) { + # update StripOffsets along with this tag if it contains a reasonable value + if ($stripOffsets and $$stripOffsets[3][0] != 0xffffffff) { + # save pointer to StripOffsets value for updating later + push @$rawDataOffset, $$stripOffsets[1]; + } + # handle via RawDataOffset instead of StripOffsets + $stripOffsets = $$offsetInfo{0x111} = $rawDataOffset; + delete $$offsetInfo{0x118}; + } + # determine the length of the raw data + my $pos = $raf->Tell(); + $raf->Seek(0, 2) or $err = 1; # seek to end of file + my $len = $raf->Tell() - $$stripOffsets[3][0]; + $raf->Seek($pos, 0); + # quick check to be sure the raw data length isn't unreasonable + # (the 22-byte length is for '<Dummy raw image data>' in our tests) + $err = 1 if ($len < 1000 and $len != 22) or $len & 0x80000000; + $err and return 'Error reading Panasonic raw data'; + # update StripByteCounts info with raw data length + # (note that the original value is maintained in the file) + $$stripByteCounts[3][0] = $len; + + return undef; +} + +#------------------------------------------------------------------------------ +# Write meta information to Panasonic JpgFromRaw in RAW/RW2/RWL image +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: updated image data, or undef if nothing changed +sub WriteJpgFromRaw($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $byteOrder = GetByteOrder(); + my $fileType = $$et{TIFF_TYPE}; # RAW, RW2 or RWL + my $dirStart = $$dirInfo{DirStart}; + if ($dirStart) { # DirStart is non-zero in DNG-converted RW2/RWL + my $dirLen = $$dirInfo{DirLen} | length($$dataPt) - $dirStart; + my $buff = substr($$dataPt, $dirStart, $dirLen); + $dataPt = \$buff; + } + my $raf = new File::RandomAccess($dataPt); + my $outbuff; + my %dirInfo = ( + RAF => $raf, + OutFile => \$outbuff, + ); + $$et{BASE} = $$dirInfo{DataPos}; + $$et{FILE_TYPE} = $$et{TIFF_TYPE} = 'JPEG'; + # use a specialized map so we don't write XMP or IPTC (or other junk) into the JPEG + my $editDirs = $$et{EDIT_DIRS}; + my $addDirs = $$et{ADD_DIRS}; + $et->InitWriteDirs(\%jpgFromRawMap); + # don't add XMP segment (IPTC won't get added because it is in Photoshop record) + delete $$et{ADD_DIRS}{XMP}; + my $result = $et->WriteJPEG(\%dirInfo); + # restore variables we changed + $$et{BASE} = 0; + $$et{FILE_TYPE} = 'TIFF'; + $$et{TIFF_TYPE} = $fileType; + $$et{EDIT_DIRS} = $editDirs; + $$et{ADD_DIRS} = $addDirs; + SetByteOrder($byteOrder); + return $result > 0 ? $outbuff : $$dataPt; +} + +#------------------------------------------------------------------------------ +# Extract meta information from an Panasonic JpgFromRaw +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid JpgFromRaw image +sub ProcessJpgFromRaw($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $byteOrder = GetByteOrder(); + my $fileType = $$et{TIFF_TYPE}; # RAW, RW2 or RWL + my $tagInfo = $$dirInfo{TagInfo}; + my $verbose = $et->Options('Verbose'); + my ($indent, $out); + $tagInfo or $et->Warn('No tag info for Panasonic JpgFromRaw'), return 0; + my $dirStart = $$dirInfo{DirStart}; + if ($dirStart) { # DirStart is non-zero in DNG-converted RW2/RWL + my $dirLen = $$dirInfo{DirLen} | length($$dataPt) - $dirStart; + my $buff = substr($$dataPt, $dirStart, $dirLen); + $dataPt = \$buff; + } + $$et{BASE} = $$dirInfo{DataPos} + ($dirStart || 0); + $$et{FILE_TYPE} = $$et{TIFF_TYPE} = 'JPEG'; + $$et{DOC_NUM} = 1; + # extract information from embedded JPEG + my %dirInfo = ( + Parent => 'RAF', + RAF => new File::RandomAccess($dataPt), + ); + if ($verbose) { + my $indent = $$et{INDENT}; + $$et{INDENT} = ' '; + $out = $et->Options('TextOut'); + print $out '--- DOC1:JpgFromRaw ',('-'x56),"\n"; + } + # fudge HtmlDump base offsets to show as a stand-alone JPEG + $$et{BASE_FUDGE} = $$et{BASE}; + my $rtnVal = $et->ProcessJPEG(\%dirInfo); + $$et{BASE_FUDGE} = 0; + # restore necessary variables for continued RW2/RWL processing + $$et{BASE} = 0; + $$et{FILE_TYPE} = 'TIFF'; + $$et{TIFF_TYPE} = $fileType; + delete $$et{DOC_NUM}; + SetByteOrder($byteOrder); + if ($verbose) { + $$et{INDENT} = $indent; + print $out ('-'x76),"\n"; + } + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PanasonicRaw - Read/write Panasonic/Leica RAW/RW2/RWL meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read and +write meta information in Panasonic/Leica RAW, RW2 and RWL images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 SEE ALSO + +L<Image::ExifTool::TagNames/PanasonicRaw Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Parrot.pm b/ExifTool/lib/Image/ExifTool/Parrot.pm new file mode 100644 index 0000000..9bf0be1 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Parrot.pm @@ -0,0 +1,846 @@ +#------------------------------------------------------------------------------ +# File: Parrot.pm +# +# Description: Read timed metadata from Parrot drone videos +# +# Revisions: 2019-10-23 - P. Harvey Created +# +# References: 1) https://developer.parrot.com/docs/pdraw/metadata.html +# --> changed to https://developer.parrot.com/docs/pdraw/video-metadata.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::Parrot; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.02'; + +sub Process_mett($$$); + +# tags found in Parrot 'mett' timed metadata (ref 1) +%Image::ExifTool::Parrot::mett = ( + PROCESS_PROC => \&Process_mett, + # put the 'P' records first in the documentation + VARS => { + SORT_PROC => sub { my ($a,$b)=@_; $a=~s/P/A/; $b=~s/P/A/; $a cmp $b }, + LONG_TAGS => 1 + }, + NOTES => q{ + Streaming metadata found in Parrot drone videos. See + L<https://developer.parrot.com/docs/pdraw/metadata.html> for the + specification. + }, + P1 => { + Name => 'ParrotV1', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::V1' }, + }, + P2 => { + Name => 'ParrotV2', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::V2' }, + }, + P3 => { + Name => 'ParrotV3', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::V3' }, + }, + E1 => { + Name => 'ParrotTimeStamp', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::TimeStamp' }, + }, + E2 => { + Name => 'ParrotFollowMe', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::FollowMe' }, + }, + E3 => { + Name => 'ParrotAutomation', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::Automation' }, + }, + # timed metadata written by ARCore (see forum13653) + 'application/arcore-accel' => { + Name => 'ARCoreAccel', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::ARCoreAccel', ByteOrder => 'II' }, + }, + 'application/arcore-gyro' => { + Name => 'ARCoreGyro', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::ARCoreGyro', ByteOrder => 'II' }, + }, + 'application/arcore-video-0' => { + Name => 'ARCoreVideo', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::ARCoreVideo', ByteOrder => 'II' }, + }, + 'application/arcore-custom-event' => { + Name => 'ARCoreCustom', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::ARCoreCustom', ByteOrder => 'II' }, + }, +); + +# tags found in the Parrot 'mett' V1 timed metadata (ref 1) [untested] +%Image::ExifTool::Parrot::V1 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + NOTES => 'Parrot version 1 streaming metadata.', + GROUPS => { 2 => 'Location' }, + 4 => { + Name => 'DroneYaw', + Format => 'int16s', + ValueConv => '$val / 0x1000 * 180 / 3.14159', # convert from rad to deg + }, + 6 => { + Name => 'DronePitch', + Format => 'int16s', + ValueConv => '$val / 0x1000 * 180 / 3.14159', + }, + 8 => { + Name => 'DroneRoll', + Format => 'int16s', + ValueConv => '$val / 0x1000 * 180 / 3.14159', + }, + 10 => { + Name => 'CameraPan', + Format => 'int16s', + ValueConv => '$val / 0x1000 * 180 / 3.14159', + }, + 12 => { + Name => 'CameraTilt', + Format => 'int16s', + ValueConv => '$val / 0x1000 * 180 / 3.14159', + }, + 14 => { + Name => 'FrameView', # (W,X,Y,Z) + Format => 'int16s[4]', + ValueConv => 'my @a = split " ",$val; $_ /= 0x1000 foreach @a; "@a"', + }, + 22 => { + Name => 'ExposureTime', + Groups => { 2 => 'Camera' }, + Format => 'int16s', + ValueConv => '$val / 0x100 / 1000', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 24 => { + Name => 'ISO', + Groups => { 2 => 'Camera' }, + Format => 'int16s', + }, + 26 => { + Name => 'WifiRSSI', + Groups => { 2 => 'Device' }, + Format => 'int8s', + PrintConv => '"$val dBm"', + }, + 27 => { + Name => 'Battery', + Groups => { 2 => 'Device' }, + PrintConv => '"$val %"', + }, + 28 => { + Name => 'GPSLatitude', + Format => 'int32s', + ValueConv => '$val / 0x100000', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 32 => { + Name => 'GPSLongitude', + Format => 'int32s', + ValueConv => '$val / 0x100000', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 36 => { + Name => 'GPSAltitude', + Format => 'int32s', + Mask => 0xffffff00, + ValueConv => '$val / 0x100', + PrintConv => 'sprintf("%.3f m", $val)', + }, + 36.1 => { + Name => 'GPSSatellites', # (SV count) + Format => 'int32s', + Mask => 0xff, + }, + 40 => { + Name => 'AltitudeFromTakeOff', + Format => 'int32s', + ValueConv => '$val / 0x10000', + PrintConv => 'sprintf("%.3f m", $val)', + }, + 44 => { + Name => 'DistanceFromHome', + Format => 'int32u', + ValueConv => '$val / 0x10000', + }, + 48 => { + Name => 'SpeedX', + Format => 'int16s', + ValueConv => '$val / 0x100', + }, + 50 => { + Name => 'SpeedY', + Format => 'int16s', + ValueConv => '$val / 0x100', + }, + 52 => { + Name => 'SpeedZ', + Format => 'int16s', + ValueConv => '$val / 0x100', + }, + 54 => { + Name => 'Binning', + Groups => { 2 => 'Device' }, + Mask => 0x80, + }, + 54.1 => { + Name => 'FlyingState', + Groups => { 2 => 'Device' }, + Mask => 0x7f, + PrintConv => { + 0 => 'Landed', + 1 => 'Taking Off', + 2 => 'Hovering', + 3 => 'Flying', + 4 => 'Landing', + 5 => 'Emergency', + }, + }, + 55 => { + Name => 'Animation', + Groups => { 2 => 'Device' }, + Mask => 0x80, + }, + 55.1 => { + Name => 'PilotingMode', + Groups => { 2 => 'Device' }, + Mask => 0x7f, + PrintConv => { + 0 => 'Manual', + 1 => 'Return Home', + 2 => 'Flight Plan', + 3 => 'Follow Me', + }, + }, +); + +# tags found in the Parrot 'mett' V2 timed metadata (ref 1) [untested] +%Image::ExifTool::Parrot::V2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + Groups => { 2 => 'Location' }, + NOTES => 'Parrot version 2 basic streaming metadata.', + 4 => { + Name => 'Elevation', + Notes => 'estimated distance from ground', + Format => 'int32s', + ValueConv => '$val / 0x10000', + PrintConv => 'sprintf("%.3f m", $val)', + }, + 8 => { + Name => 'GPSLatitude', + Format => 'int32s', + ValueConv => '$val / 0x400000', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 12 => { + Name => 'GPSLongitude', + Format => 'int32s', + ValueConv => '$val / 0x400000', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 16 => { + Name => 'GPSAltitude', + Format => 'int32s', + Mask => 0xffffff00, + ValueConv => '$val / 0x100', + PrintConv => 'sprintf("%.3f m", $val)', + }, + 16.1 => { + Name => 'GPSSatellites', # (SV count) + Format => 'int32s', + Mask => 0xff, + }, + 20 => { + Name => 'GPSVelocityNorth', # (m/s) + Format => 'int16s', + ValueConv => '$val / 0x100', + }, + 22 => { + Name => 'GPSVelocityEast', # (m/s) + Format => 'int16s', + ValueConv => '$val / 0x100', + }, + 24 => { + Name => 'GPSVelocityDown', # (m/s) + Format => 'int16s', + ValueConv => '$val / 0x100', + }, + 26 => { + Name => 'AirSpeed', # (m/s) + Format => 'int16s', + RawConv => '$val < 0 ? undef : $val', + ValueConv => '$val / 0x100', + }, + 28 => { + Name => 'DroneQuaternion', # (W,X,Y,Z) + Format => 'int16s[4]', + ValueConv => 'my @a = split " ",$val; $_ /= 0x4000 foreach @a; "@a"', + }, + 36 => { + Name => 'FrameView', # (W,X,Y,Z) + Format => 'int16s[4]', + ValueConv => 'my @a = split " ",$val; $_ /= 0x4000 foreach @a; "@a"', + }, + 44 => { + Name => 'CameraPan', + Format => 'int16s', + ValueConv => '$val / 0x1000 * 180 / 3.14159', # convert from rad to deg + }, + 46 => { + Name => 'CameraTilt', + Format => 'int16s', + ValueConv => '$val / 0x1000 * 180 / 3.14159', + }, + 48 => { + Name => 'ExposureTime', + Groups => { 2 => 'Camera' }, + Format => 'int16u', + ValueConv => '$val / 0x100 / 1000', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 50 => { + Name => 'ISO', + Groups => { 2 => 'Camera' }, + Format => 'int16u', + }, + 52 => { + Name => 'Binning', + Groups => { 2 => 'Device' }, + Mask => 0x80, + }, + 52.1 => { + Name => 'FlyingState', + Groups => { 2 => 'Device' }, + Mask => 0x7f, + PrintConv => { + 0 => 'Landed', + 1 => 'Taking Off', + 2 => 'Hovering', + 3 => 'Flying', + 4 => 'Landing', + 5 => 'Emergency', + 6 => 'User Takeoff', + 7 => 'Motor Ramping', + 8 => 'Emergency Landing', + }, + }, + 53 => { + Name => 'Animation', + Groups => { 2 => 'Device' }, + Mask => 0x80, + }, + 53.1 => { + Name => 'PilotingMode', + Groups => { 2 => 'Device' }, + Mask => 0x7f, + PrintConv => { + 0 => 'Manual', + 1 => 'Return Home', + 2 => 'Flight Plan', + 3 => 'Follow Me / Tracking', # (same as 'Tracking') + 4 => 'Magic Carpet', + 5 => 'Move To', + }, + }, + 54 => { + Name => 'WifiRSSI', + Groups => { 2 => 'Device' }, + Format => 'int8s', + PrintConv => '"$val dBm"', + }, + 55 => { + Name => 'Battery', + Groups => { 2 => 'Device' }, + PrintConv => '"$val %"', + }, +); + +# tags found in the Parrot 'mett' V3 timed metadata (ref 1) +%Image::ExifTool::Parrot::V3 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + NOTES => 'Parrot version 3 basic streaming metadata.', + 4 => { + Name => 'Elevation', + Notes => 'estimated distance from ground', + Format => 'int32s', + ValueConv => '$val / 0x10000', + PrintConv => 'sprintf("%.3f m", $val)', + }, + 8 => { + Name => 'GPSLatitude', + Format => 'int32s', + ValueConv => '$val / 0x400000', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 12 => { + Name => 'GPSLongitude', + Format => 'int32s', + ValueConv => '$val / 0x400000', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 16 => { + Name => 'GPSAltitude', + Format => 'int32s', + Mask => 0xffffff00, + ValueConv => '$val / 0x100', + PrintConv => 'sprintf("%.3f m", $val)', + }, + 16.1 => { + Name => 'GPSSatellites', # (SV count) + Format => 'int32s', + Mask => 0xff, + }, + 20 => { + Name => 'GPSVelocityNorth', # (m/s) + Format => 'int16s', + ValueConv => '$val / 0x100', + }, + 22 => { + Name => 'GPSVelocityEast', # (m/s) + Format => 'int16s', + ValueConv => '$val / 0x100', + }, + 24 => { + Name => 'GPSVelocityDown', # (m/s) + Format => 'int16s', + ValueConv => '$val / 0x100', + }, + 26 => { + Name => 'AirSpeed', # (m/s) + Format => 'int16s', + RawConv => '$val < 0 ? undef : $val', + ValueConv => '$val / 0x100', + }, + 28 => { + Name => 'DroneQuaternion', # (W,X,Y,Z) + Format => 'int16s[4]', + ValueConv => 'my @a = split " ",$val; $_ /= 0x4000 foreach @a; "@a"', + }, + 36 => { + Name => 'FrameBaseView', # (W,X,Y,Z without pan/tilt) + Format => 'int16s[4]', + ValueConv => 'my @a = split " ",$val; $_ /= 0x4000 foreach @a; "@a"', + }, + 44 => { + Name => 'FrameView', # (W,X,Y,Z) + Format => 'int16s[4]', + ValueConv => 'my @a = split " ",$val; $_ /= 0x4000 foreach @a; "@a"', + }, + 52 => { + Name => 'ExposureTime', + Groups => { 2 => 'Camera' }, + Format => 'int16u', + ValueConv => '$val / 0x100 / 1000', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 54 => { + Name => 'ISO', + Groups => { 2 => 'Camera' }, + Format => 'int16u', + }, + 56 => { + Name => 'RedBalance', + Groups => { 2 => 'Camera' }, + Format => 'int16u', + ValueConv => '$val / 0x4000', + }, + 58 => { + Name => 'BlueBalance', + Groups => { 2 => 'Camera' }, + Format => 'int16u', + ValueConv => '$val / 0x4000', + }, + 60 => { + Name => 'FOV', # (degrees) + Description => 'Field Of View', + Notes => 'horizontal and vertical field of view in degrees', + Groups => { 2 => 'Image' }, + Format => 'int16u[2]', + ValueConv => 'my @a = split " ",$val; $_ /= 0x100 foreach @a; "@a"', + }, + 64 => { + Name => 'LinkGoodput', + Groups => { 2 => 'Device' }, + Format => 'int32u', + Mask => 0xffffff00, + PrintConv => '"$val kbit/s"', + }, + 64.1 => { + Name => 'LinkQuality', + Groups => { 2 => 'Device' }, + Format => 'int32u', + Notes => '0-5', + Mask => 0xff, + }, + 68 => { + Name => 'WifiRSSI', + Groups => { 2 => 'Device' }, + Format => 'int8s', + PrintConv => '"$val dBm"', + }, + 69 => { + Name => 'Battery', + Groups => { 2 => 'Device' }, + PrintConv => '"$val %"', + }, + 70 => { + Name => 'Binning', + Groups => { 2 => 'Device' }, + Mask => 0x80, + }, + 70.1 => { + Name => 'FlyingState', + Groups => { 2 => 'Device' }, + Mask => 0x7f, + PrintConv => { + 0 => 'Landed', + 1 => 'Taking Off', + 2 => 'Hovering', + 3 => 'Flying', + 4 => 'Landing', + 5 => 'Emergency', + 6 => 'User Takeoff', + 7 => 'Motor Ramping', + 8 => 'Emergency Landing', + }, + }, + 71 => { + Name => 'Animation', + Groups => { 2 => 'Device' }, + Mask => 0x80, + }, + 71.1 => { + Name => 'PilotingMode', + Groups => { 2 => 'Device' }, + Mask => 0x7f, + PrintConv => { + 0 => 'Manual', + 1 => 'Return Home', + 2 => 'Flight Plan', + 3 => 'Follow Me / Tracking', # (same as 'Tracking') + 4 => 'Magic Carpet', + 5 => 'Move To', + }, + }, +); + +# tags found in the Parrot 'mett' E1 timestamp timed metadata (ref 1) +%Image::ExifTool::Parrot::TimeStamp = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + NOTES => 'Parrot streaming metadata timestamp extension.', + GROUPS => { 2 => 'Time' }, + 4 => { + Name => 'TimeStamp', + Format => 'int64u', + ValueConv => '$val / 1e6', + }, +); + +# tags found in the Parrot 'mett' E2 follow-me timed metadata (ref 1) [untested] +%Image::ExifTool::Parrot::FollowMe = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + NOTES => 'Parrot streaming metadata follow-me extension.', + 4 => { + Name => 'GPSTargetLatitude', + Format => 'int32s', + ValueConv => '$val / 0x400000', + }, + 8 => { + Name => 'GPSTargetLongitude', + Format => 'int32s', + ValueConv => '$val / 0x400000', + }, + 12 => { + Name => 'GPSTargetAltitude', + Format => 'int32s', + ValueConv => '$val / 0x10000', + }, + 16 => { + Name => 'Follow-meMode', + Groups => { 2 => 'Device' }, + PrintConv => { BITMASK => { + 0 => 'Follow-me enabled', + 1 => 'Follow-me', # (0=Look-at-me! auggh. see AutomationFlags below) + 2 => 'Angle locked', + }}, + }, + 17 => { + Name => 'Follow-meAnimation', + Groups => { 2 => 'Device' }, + PrintConv => { + 0 => 'None', + 1 => 'Orbit', + 2 => 'Boomerang', + 3 => 'Parabola', + 4 => 'Zenith', + }, + }, +); + +# tags found in the Parrot 'mett' E3 automation timed metadata (ref 1) +%Image::ExifTool::Parrot::Automation = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + NOTES => 'Parrot streaming metadata automation extension.', + 4 => { + Name => 'GPSFramingLatitude', + Format => 'int32s', + ValueConv => '$val / 0x400000', + }, + 8 => { + Name => 'GPSFramingLongitude', + Format => 'int32s', + ValueConv => '$val / 0x400000', + }, + 12 => { + Name => 'GPSFramingAltitude', + Format => 'int32s', + ValueConv => '$val / 0x10000', + }, + 16 => { + Name => 'GPSDestLatitude', + Format => 'int32s', + ValueConv => '$val / 0x400000', + }, + 20 => { + Name => 'GPSDestLongitude', + Format => 'int32s', + ValueConv => '$val / 0x400000', + }, + 24 => { + Name => 'GPSDestAltitude', + Format => 'int32s', + ValueConv => '$val / 0x10000', + }, + 28 => { + Name => 'AutomationAnimation', + Groups => { 2 => 'Device' }, + PrintConv => { + 0 => 'None', + 1 => 'Orbit', + 2 => 'Boomerang', + 3 => 'Parabola', + 4 => 'Dolly Slide', + 5 => 'Dolly Zoom', + 6 => 'Reveal Vertical', + 7 => 'Reveal Horizontal', + 8 => 'Candle', + 9 => 'Flip Front', + 10 => 'Flip Back', + 11 => 'Flip Left', + 12 => 'Flip Right', + 13 => 'Twist Up', + 14 => 'Position Twist Up', + }, + }, + 29 => { + Name => 'AutomationFlags', + Groups => { 2 => 'Device' }, + PrintConv => { BITMASK => { + 0 => 'Follow-me enabled', + 1 => 'Look-at-me enabled', # (really? opposite sense to Follow-meMode above!) + 2 => 'Angle locked', + }}, + }, +); + +# ARCore Accel data (ref PH) +%Image::ExifTool::Parrot::ARCoreAccel = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + NOTES => 'ARCore accelerometer data.', + FIRST_ENTRY => 0, + # 00-04: always 10 34 16 1 29 + 4 => { + Name => 'AccelerometerUnknown', + Format => 'undef[16]', + Unknown => 1, + ValueConv => 'join " ", unpack("Cx4Cx4Cx4C", $val)', + }, + 5 => { # (NC) + Name => 'Accelerometer', + Format => 'undef[14]', + RawConv => 'GetFloat(\$val,0) . " " . GetFloat(\$val,5) . " " . GetFloat(\$val,10)', + }, + # 05-08: float Accelerometer X + # 09: 37 + # 10-13: float Accelerometer Y + # 14: 45 + # 15-18: float Accelerometer Z + # 19: 48 + # 20-24: 128-255 + # 25: 246 then 247 + # 26: 188 + # 27: 2 + # 28: 56 + # 29-32: 128-255 + # 33: increments slowly (about once every 56 samples or so) +); + +# ARCore Gyro data (ref PH) +%Image::ExifTool::Parrot::ARCoreGyro = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + NOTES => 'ARCore accelerometer data.', + FIRST_ENTRY => 0, + # 00-04: always 10 34 16 3 29 + 4 => { + Name => 'GyroscopeUnknown', + Format => 'undef[16]', + Unknown => 1, # always "29 37 45 48" in my sample, just like AccelerometerUnknown + ValueConv => 'join " ", unpack("Cx4Cx4Cx4C", $val)', + }, + 5 => { # (NC) + Name => 'Gyroscope', + Format => 'undef[14]', + RawConv => 'GetFloat(\$val,0) . " " . GetFloat(\$val,5) . " " . GetFloat(\$val,10)', + }, +); + +%Image::ExifTool::Parrot::ARCoreVideo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, +); + +%Image::ExifTool::Parrot::ARCoreCustom = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, +); + +%Image::ExifTool::Parrot::Composite = ( + GPSDateTime => { + Description => 'GPS Date/Time', + Groups => { 2 => 'Time' }, + SubDoc => 1, + Require => { + 0 => 'Parrot:GPSLatitude', # (avoids creating this tag for other videos) + 1 => 'Main:CreateDate', + 2 => 'SampleTime', + }, + ValueConv => q{ + my $time = $val[1]; + my $diff = $val[2]; + # handle time zone and shift to UTC + if ($time =~ s/([-+])(\d{1,2}):?(\d{2})$//) { + my $secs = (($2 * 60) + $3) * 60; + $secs *= -1 if $1 eq '+'; + $diff += $secs; + } elsif ($time !~ s/Z$//) { + # shift from local time + $diff += GetUnixTime($time, 1) - GetUnixTime($time); + } + my $sign = ($diff =~ s/^-// ? '-' : ''); + $time .= '.000'; # add decimal seconds + ShiftTime($time, "${sign}0:0:$diff"); + return $time . 'Z'; + }, + PrintConv => '$self->ConvertDateTime($val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Parrot'); + + +#------------------------------------------------------------------------------ +# Parse Parrot 'mett' timed metadata +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# (this metadata design is really stupid -- you need to know the size of the base structures) +sub Process_mett($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $dirEnd = length $$dataPt; + my $pos = $$dirInfo{DirStart} || 0; + my $metaType = $$et{MetaType} || ''; + + $et->VerboseDir('Parrot mett', undef, $dirEnd); + + if ($$tagTbl{$metaType}) { + $et->HandleTag($tagTbl, $metaType, undef, + DataPt => $dataPt, + DataPos => $dataPos, + Base => $$dirInfo{Base}, + ); + return 1; + } + while ($pos + 4 < $dirEnd) { + my ($id, $nwords) = unpack("x${pos}a2n", $$dataPt); + my $size; + if ($id !~ /^[EP]\d/) { + # no ID so this should be a 60-byte V1 recording record, otherwise give up + last unless $dirEnd == 60; + $id = 'P1'; # generate a fake ID + # ignore the first 4 of the record so the fields will align with + # the other V1 records (unfortunately, this means that we won't + # decode the V1 recording frame timestamp, but oh well) + $pos += 4; + $size = $dirEnd - $pos; + # must override size for P3 and P3 records since it includes the extensions (augh!) + } elsif ($id eq 'P2') { + $size = 56; + } elsif ($id eq 'P3') { + $size = 72; + } else { + $size = $nwords * 4 + 4; + } + last if $pos + $size > $dirEnd; + $et->HandleTag($tagTbl, $id, undef, + DataPt => $dataPt, + DataPos => $dataPos, + Base => $$dirInfo{Base}, + Start => $pos, + Size => $size, + ); + $pos += $size; + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Parrot - Read timed metadata from Parrot drone videos + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +timed metadata from the 'mett' frame found in Parrot drone MP4 videos. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://developer.parrot.com/docs/pdraw/metadata.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Parrot Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Pentax.pm b/ExifTool/lib/Image/ExifTool/Pentax.pm new file mode 100644 index 0000000..b950dea --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Pentax.pm @@ -0,0 +1,6339 @@ +#------------------------------------------------------------------------------ +# File: Pentax.pm +# +# Description: Pentax/Asahi EXIF maker notes tags +# +# Revisions: 11/25/2003 - P. Harvey Created +# 02/10/2004 - P. Harvey Completely re-done +# 02/16/2004 - W. Smith Updated (see ref 3) +# 11/10/2004 - P. Harvey Added support for Asahi cameras +# 01/10/2005 - P. Harvey Added LensType with values from ref 4 +# 03/30/2005 - P. Harvey Added new tags from ref 5 +# 10/04/2005 - P. Harvey Added MOV tags +# 10/22/2007 - P. Harvey Got my new K10D! (more new tags to decode) +# 11/03/2010 - P. Harvey Got my new K-5! (a gold mine of new tags to discover!) +# +# References: 1) Image::MakerNotes::Pentax +# 2) http://johnst.org/sw/exiftags/ (Asahi cameras) +# 3) Wayne Smith private communication (Optio 550) +# 4) http://kobe1995.jp/~kaz/astro/istD.html +# 5) John Francis (http://www.panix.com/~johnf/raw/index.html) (ist-D/ist-DS) +# 6) http://www.cybercom.net/~dcoffin/dcraw/ +# 7) Douglas O'Brien private communication (*istD, K10D) +# 8) Denis Bourez private communication +# 9) Kazumichi Kawabata private communication +# 10) David Buret private communication (*istD) +# 11) http://forums.dpreview.com/forums/read.asp?forum=1036&message=17465929 +# 12) Derby Chang private communication +# 13) http://homepage3.nifty.com/kamisaka/makernote/makernote_pentax.htm (2007/02/28) +# 14) Ger Vermeulen private communication (Optio S6) +# 15) Barney Garrett private communication (Samsung GX-1S) +# 16) Axel Kellner private communication (K10D) +# 17) Cvetan Ivanov private communication (K100D) +# 18) http://gvsoft.homedns.org/exif/makernote-pentax-type3.html +# 19) Dave Nicholson private communication (K10D) +# 20) Bogdan and yeryry (http://www.cpanforum.com/posts/8037) +# 21) Peter (*istD, http://www.cpanforum.com/posts/8078) +# 22) Bozi (K10D, http://www.cpanforum.com/posts/8480) +# 23) Akos Szalkai (https://rt.cpan.org/Ticket/Display.html?id=43743) +# 24) Albert Bogner private communication +# 26) https://exiftool.org/forum/index.php/topic,3444.0.html +# 27) https://exiftool.org/forum/index.php/topic,3833.0.html +# 28) Klaus Homeister https://exiftool.org/forum/index.php/topic,4803.0.html +# 29) Louis Granboulan private communication (K-5II) +# 30) https://exiftool.org/forum/index.php?topic=5433 +# 31) Andras Salamon private communication (K-70) +# IB) Iliah Borg private communication (LibRaw) +# JD) Jens Duttke private communication +# NJ) Niels Kristian Bech Jensen private communication +# +# Notes: See POD documentation at the bottom of this file +#------------------------------------------------------------------------------ + +package Image::ExifTool::Pentax; + +use strict; +use vars qw($VERSION %pentaxLensTypes); +use Image::ExifTool::Exif; +use Image::ExifTool::GPS; +use Image::ExifTool::HP; + +$VERSION = '3.43'; + +sub CryptShutterCount($$); +sub PrintFilter($$$); +sub DecodeAFPoints($$$$;$); + +# pentax lens type codes (ref 4) +# The first number gives the lens series, and the 2nd gives the model number +# Series numbers: K=1; A=2; F=3; FAJ=4; DFA=4,7; FA=3,4,5,6; FA*=5,6; +# DA=3,4,7; DA*=7,8; FA645=11; DFA645=13; Q=21 +%pentaxLensTypes = ( + Notes => q{ + The first number gives the series of the lens, and the second identifies the + lens model. Note that newer series numbers may not always be properly + identified by cameras running older firmware versions. + }, + OTHER => sub { + my ($val, $inv, $conv) = @_; + return undef if $inv; + # *istD may report a series number of 4 for series 7 lenses + $val =~ s/^4 /7 / and $$conv{$val} and return "$$conv{$val} ($_[0])"; + # cameras that don't recognize SDM lenses (eg. older K10 firmware) + # may report series 7 instead of 8 + $val =~ s/^7 /8 / and $$conv{$val} and return "$$conv{$val} ? ($_[0])"; + # there seems to some inconsistency between FA and DFA lenses for the 645D... + ($val =~ s/^11 /13 / or $val =~ s/^13 /11 /) and $$conv{$val} and return "$$conv{$val} ? ($_[0])"; + return undef; + }, + '0 0' => 'M-42 or No Lens', #17 + '1 0' => 'K or M Lens', + '2 0' => 'A Series Lens', #7 (from smc PENTAX-A 400mm F5.6) + '3 0' => 'Sigma', + # (and 'Sigma 18-50mm F2.8 EX Macro') + # (and 'Sigma 30mm F1.4 EX DC', ref PH) + # (and 'Sigma 50-500mm F4-6.3 DG APO') + # (and 'Sigma 70mm F2.8 EX DG Macro') + # (and 'Sigma 105mm F2.8 EX DG Macro', ref 24) + # (and 'Sigma 180mm F4.5 EX DG Macro') + '3 17' => 'smc PENTAX-FA SOFT 85mm F2.8', # (also F version, ref 29) + '3 18' => 'smc PENTAX-F 1.7X AF ADAPTER', + '3 19' => 'smc PENTAX-F 24-50mm F4', + '3 20' => 'smc PENTAX-F 35-80mm F4-5.6', + '3 21' => 'smc PENTAX-F 80-200mm F4.7-5.6', + '3 22' => 'smc PENTAX-F FISH-EYE 17-28mm F3.5-4.5', + '3 23' => 'smc PENTAX-F 100-300mm F4.5-5.6 or Sigma Lens', + '3 23.1' => 'Sigma AF 28-300mm F3.5-5.6 DL IF', #JD + '3 23.2' => 'Sigma AF 28-300mm F3.5-6.3 DG IF Macro', #JD + '3 23.3' => 'Tokina 80-200mm F2.8 ATX-Pro', #Exiv2 + '3 24' => 'smc PENTAX-F 35-135mm F3.5-4.5', + '3 25' => 'smc PENTAX-F 35-105mm F4-5.6 or Sigma or Tokina Lens', + '3 25.1' => 'Sigma 55-200mm F4-5.6 DC', #JD + '3 25.2' => 'Sigma AF 28-300mm F3.5-5.6 DL IF', #11 + '3 25.3' => 'Sigma AF 28-300mm F3.5-6.3 DL IF', #Exiv2 + '3 25.4' => 'Sigma AF 28-300mm F3.5-6.3 DG IF Macro', #JD + '3 25.5' => 'Tokina 80-200mm F2.8 ATX-Pro', #12 + '3 26' => 'smc PENTAX-F* 250-600mm F5.6 ED[IF]', + '3 27' => 'smc PENTAX-F 28-80mm F3.5-4.5 or Tokina Lens', + '3 27.1' => 'Tokina AT-X Pro AF 28-70mm F2.6-2.8', #JD + '3 28' => 'smc PENTAX-F 35-70mm F3.5-4.5 or Tokina Lens', + '3 28.1' => 'Tokina 19-35mm F3.5-4.5 AF', #12 + '3 28.2' => 'Tokina AT-X AF 400mm F5.6', #NJ + '3 29' => 'PENTAX-F 28-80mm F3.5-4.5 or Sigma or Tokina Lens', + '3 29.1' => 'Sigma AF 18-125mm F3.5-5.6 DC', #11 + '3 29.2' => 'Tokina AT-X PRO 28-70mm F2.6-2.8', #22 + '3 30' => 'PENTAX-F 70-200mm F4-5.6', + '3 31' => 'smc PENTAX-F 70-210mm F4-5.6 or Tokina or Takumar Lens', + '3 31.1' => 'Tokina AF 730 75-300mm F4.5-5.6', + '3 31.2' => 'Takumar-F 70-210mm F4-5.6', #JD + '3 32' => 'smc PENTAX-F 50mm F1.4', + '3 33' => 'smc PENTAX-F 50mm F1.7', + '3 34' => 'smc PENTAX-F 135mm F2.8 [IF]', + '3 35' => 'smc PENTAX-F 28mm F2.8', + '3 36' => 'Sigma 20mm F1.8 EX DG Aspherical RF', + '3 38' => 'smc PENTAX-F* 300mm F4.5 ED[IF]', + '3 39' => 'smc PENTAX-F* 600mm F4 ED[IF]', + '3 40' => 'smc PENTAX-F Macro 100mm F2.8', + '3 41' => 'smc PENTAX-F Macro 50mm F2.8 or Sigma Lens', #4 + '3 41.1' => 'Sigma 50mm F2.8 Macro', #16 + '3 42' => 'Sigma 300mm F2.8 EX DG APO IF', #27 + '3 44' => 'Sigma or Tamron Lens (3 44)', + '3 44.1' => 'Sigma AF 10-20mm F4-5.6 EX DC', #JD + '3 44.2' => 'Sigma 12-24mm F4.5-5.6 EX DG', #12 (added "-5.6", ref 29) + '3 44.3' => 'Sigma 17-70mm F2.8-4.5 DC Macro', #(Bart Hickman) + '3 44.4' => 'Sigma 18-50mm F3.5-5.6 DC', #4 + '3 44.5' => 'Sigma 17-35mm F2.8-4 EX DG', #29 + '3 44.6' => 'Tamron 35-90mm F4-5.6 AF', #12 (added "-5.6", ref IB) + '3 44.7' => 'Sigma AF 18-35mm F3.5-4.5 Aspherical', #29 + '3 46' => 'Sigma or Samsung Lens (3 46)', + '3 46.1' => 'Sigma APO 70-200mm F2.8 EX', + '3 46.2' => 'Sigma EX APO 100-300mm F4 IF', #JD + '3 46.3' => 'Samsung/Schneider D-XENON 50-200mm F4-5.6 ED', #29 + '3 50' => 'smc PENTAX-FA 28-70mm F4 AL', + '3 51' => 'Sigma 28mm F1.8 EX DG Aspherical Macro', + '3 52' => 'smc PENTAX-FA 28-200mm F3.8-5.6 AL[IF] or Tamron Lens', + '3 52.1' => 'Tamron AF LD 28-200mm F3.8-5.6 [IF] Aspherical (171D)', #JD + '3 53' => 'smc PENTAX-FA 28-80mm F3.5-5.6 AL', + '3 247' => 'smc PENTAX-DA FISH-EYE 10-17mm F3.5-4.5 ED[IF]', + '3 248' => 'smc PENTAX-DA 12-24mm F4 ED AL[IF]', + '3 250' => 'smc PENTAX-DA 50-200mm F4-5.6 ED', + '3 251' => 'smc PENTAX-DA 40mm F2.8 Limited', + '3 252' => 'smc PENTAX-DA 18-55mm F3.5-5.6 AL', + '3 253' => 'smc PENTAX-DA 14mm F2.8 ED[IF]', + '3 254' => 'smc PENTAX-DA 16-45mm F4 ED AL', + '3 255' => 'Sigma Lens (3 255)', + '3 255.1' => 'Sigma 18-200mm F3.5-6.3 DC', #8 + '3 255.2' => 'Sigma DL-II 35-80mm F4-5.6', #12 + '3 255.3' => 'Sigma DL Zoom 75-300mm F4-5.6', #12 + '3 255.4' => 'Sigma DF EX Aspherical 28-70mm F2.8', #12 + '3 255.5' => 'Sigma AF Tele 400mm F5.6 Multi-coated', #JD + '3 255.6' => 'Sigma 24-60mm F2.8 EX DG', #PH + '3 255.7' => 'Sigma 70-300mm F4-5.6 Macro', #JD (also DG Macro, ref 27) + '3 255.8' => 'Sigma 55-200mm F4-5.6 DC', #JD + '3 255.9' => 'Sigma 18-50mm F2.8 EX DC', #JD (also Macro version - PH) + '4 1' => 'smc PENTAX-FA SOFT 28mm F2.8', + '4 2' => 'smc PENTAX-FA 80-320mm F4.5-5.6', + '4 3' => 'smc PENTAX-FA 43mm F1.9 Limited', + '4 6' => 'smc PENTAX-FA 35-80mm F4-5.6', + '4 7' => 'Irix 45mm F1.4', #27 + '4 8' => 'Irix 150mm F2.8 Macro', #exiv2 issue 1084 + '4 9' => 'Irix 11mm F4 Firefly', #27 + '4 10' => 'Irix 15mm F2.4', #27 + '4 12' => 'smc PENTAX-FA 50mm F1.4', #17 + '4 15' => 'smc PENTAX-FA 28-105mm F4-5.6 [IF]', + '4 16' => 'Tamron AF 80-210mm F4-5.6 (178D)', #13 + '4 19' => 'Tamron SP AF 90mm F2.8 (172E)', + '4 20' => 'smc PENTAX-FA 28-80mm F3.5-5.6', + '4 21' => 'Cosina AF 100-300mm F5.6-6.7', #20 + '4 22' => 'Tokina 28-80mm F3.5-5.6', #13 + '4 23' => 'smc PENTAX-FA 20-35mm F4 AL', + '4 24' => 'smc PENTAX-FA 77mm F1.8 Limited', + '4 25' => 'Tamron SP AF 14mm F2.8', #13 + '4 26' => 'smc PENTAX-FA Macro 100mm F3.5 or Cosina Lens', + '4 26.1' => 'Cosina 100mm F3.5 Macro', #JD + '4 27' => 'Tamron AF 28-300mm F3.5-6.3 LD Aspherical[IF] Macro (185D/285D)', + '4 28' => 'smc PENTAX-FA 35mm F2 AL', + '4 29' => 'Tamron AF 28-200mm F3.8-5.6 LD Super II Macro (371D)', #JD + '4 34' => 'smc PENTAX-FA 24-90mm F3.5-4.5 AL[IF]', + '4 35' => 'smc PENTAX-FA 100-300mm F4.7-5.8', + # '4 36' => 'Tamron AF70-300mm F4-5.6 LD Macro', # both 572D and A17 (Di) - ref JD + '4 36' => 'Tamron AF 70-300mm F4-5.6 LD Macro 1:2', #NJ + '4 37' => 'Tamron SP AF 24-135mm F3.5-5.6 AD AL (190D)', #13 + '4 38' => 'smc PENTAX-FA 28-105mm F3.2-4.5 AL[IF]', + '4 39' => 'smc PENTAX-FA 31mm F1.8 AL Limited', + '4 41' => 'Tamron AF 28-200mm Super Zoom F3.8-5.6 Aspherical XR [IF] Macro (A03)', + '4 43' => 'smc PENTAX-FA 28-90mm F3.5-5.6', + '4 44' => 'smc PENTAX-FA J 75-300mm F4.5-5.8 AL', + '4 45' => 'Tamron Lens (4 45)', + '4 45.1' => 'Tamron 28-300mm F3.5-6.3 Ultra zoom XR', + '4 45.2' => 'Tamron AF 28-300mm F3.5-6.3 XR Di LD Aspherical [IF] Macro', #JD + '4 46' => 'smc PENTAX-FA J 28-80mm F3.5-5.6 AL', + '4 47' => 'smc PENTAX-FA J 18-35mm F4-5.6 AL', + #'4 49' => 'Tamron SP AF 28-75mm F2.8 XR Di (A09)', + '4 49' => 'Tamron SP AF 28-75mm F2.8 XR Di LD Aspherical [IF] Macro', #NJ + '4 51' => 'smc PENTAX-D FA 50mm F2.8 Macro', + '4 52' => 'smc PENTAX-D FA 100mm F2.8 Macro', + '4 55' => 'Samsung/Schneider D-XENOGON 35mm F2', #29 + '4 56' => 'Samsung/Schneider D-XENON 100mm F2.8 Macro', #Alan Robinson + '4 75' => 'Tamron SP AF 70-200mm F2.8 Di LD [IF] Macro (A001)', #JD + '4 214' => 'smc PENTAX-DA 35mm F2.4 AL', #PH + '4 229' => 'smc PENTAX-DA 18-55mm F3.5-5.6 AL II', #JD + '4 230' => 'Tamron SP AF 17-50mm F2.8 XR Di II', #20 + '4 231' => 'smc PENTAX-DA 18-250mm F3.5-6.3 ED AL [IF]', #21 + '4 237' => 'Samsung/Schneider D-XENOGON 10-17mm F3.5-4.5', #JD + '4 239' => 'Samsung/Schneider D-XENON 12-24mm F4 ED AL [IF]', #23 + '4 242' => 'smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM (SDM unused)', #Pietu Pohjalainen + '4 243' => 'smc PENTAX-DA 70mm F2.4 Limited', #JD + '4 244' => 'smc PENTAX-DA 21mm F3.2 AL Limited', #9 + '4 245' => 'Samsung/Schneider D-XENON 50-200mm F4-5.6', #15 + '4 246' => 'Samsung/Schneider D-XENON 18-55mm F3.5-5.6', #15 + '4 247' => 'smc PENTAX-DA FISH-EYE 10-17mm F3.5-4.5 ED[IF]', #10 + '4 248' => 'smc PENTAX-DA 12-24mm F4 ED AL [IF]', #10 + '4 249' => 'Tamron XR DiII 18-200mm F3.5-6.3 (A14)', + '4 250' => 'smc PENTAX-DA 50-200mm F4-5.6 ED', #8 + '4 251' => 'smc PENTAX-DA 40mm F2.8 Limited', #9 + '4 252' => 'smc PENTAX-DA 18-55mm F3.5-5.6 AL', #8 + '4 253' => 'smc PENTAX-DA 14mm F2.8 ED[IF]', + '4 254' => 'smc PENTAX-DA 16-45mm F4 ED AL', + '5 1' => 'smc PENTAX-FA* 24mm F2 AL[IF]', + '5 2' => 'smc PENTAX-FA 28mm F2.8 AL', + '5 3' => 'smc PENTAX-FA 50mm F1.7', + '5 4' => 'smc PENTAX-FA 50mm F1.4', + '5 5' => 'smc PENTAX-FA* 600mm F4 ED[IF]', + '5 6' => 'smc PENTAX-FA* 300mm F4.5 ED[IF]', + '5 7' => 'smc PENTAX-FA 135mm F2.8 [IF]', + '5 8' => 'smc PENTAX-FA Macro 50mm F2.8', + '5 9' => 'smc PENTAX-FA Macro 100mm F2.8', + '5 10' => 'smc PENTAX-FA* 85mm F1.4 [IF]', + '5 11' => 'smc PENTAX-FA* 200mm F2.8 ED[IF]', + '5 12' => 'smc PENTAX-FA 28-80mm F3.5-4.7', + '5 13' => 'smc PENTAX-FA 70-200mm F4-5.6', + '5 14' => 'smc PENTAX-FA* 250-600mm F5.6 ED[IF]', + '5 15' => 'smc PENTAX-FA 28-105mm F4-5.6', + '5 16' => 'smc PENTAX-FA 100-300mm F4.5-5.6', + '5 98' => 'smc PENTAX-FA 100-300mm F4.5-5.6', #JD (pre-production? - PH) + '6 1' => 'smc PENTAX-FA* 85mm F1.4 [IF]', + '6 2' => 'smc PENTAX-FA* 200mm F2.8 ED[IF]', + '6 3' => 'smc PENTAX-FA* 300mm F2.8 ED[IF]', + '6 4' => 'smc PENTAX-FA* 28-70mm F2.8 AL', + '6 5' => 'smc PENTAX-FA* 80-200mm F2.8 ED[IF]', + '6 6' => 'smc PENTAX-FA* 28-70mm F2.8 AL', + '6 7' => 'smc PENTAX-FA* 80-200mm F2.8 ED[IF]', + '6 8' => 'smc PENTAX-FA 28-70mm F4AL', + '6 9' => 'smc PENTAX-FA 20mm F2.8', + '6 10' => 'smc PENTAX-FA* 400mm F5.6 ED[IF]', + '6 13' => 'smc PENTAX-FA* 400mm F5.6 ED[IF]', + '6 14' => 'smc PENTAX-FA* Macro 200mm F4 ED[IF]', + '7 0' => 'smc PENTAX-DA 21mm F3.2 AL Limited', #13 + '7 58' => 'smc PENTAX-D FA Macro 100mm F2.8 WR', #PH - this bit of information cost me $600 ;) + # '7 58' also 'HD PENTAX-D FA MACRO 100mm F2.8 ED AW' (ref 27) + '7 75' => 'Tamron SP AF 70-200mm F2.8 Di LD [IF] Macro (A001)', #(Anton Bondar) + '7 201' => 'smc Pentax-DA L 50-200mm F4-5.6 ED WR', #(Bruce Rusk) + '7 202' => 'smc PENTAX-DA L 18-55mm F3.5-5.6 AL WR', #29 + '7 203' => 'HD PENTAX-DA 55-300mm F4-5.8 ED WR', #29 + '7 204' => 'HD PENTAX-DA 15mm F4 ED AL Limited', #forum5318 + '7 205' => 'HD PENTAX-DA 35mm F2.8 Macro Limited', #29 + '7 206' => 'HD PENTAX-DA 70mm F2.4 Limited', #29 + '7 207' => 'HD PENTAX-DA 21mm F3.2 ED AL Limited', #forum5327 + '7 208' => 'HD PENTAX-DA 40mm F2.8 Limited', #PH + '7 212' => 'smc PENTAX-DA 50mm F1.8', #PH + '7 213' => 'smc PENTAX-DA 40mm F2.8 XS', #PH + '7 214' => 'smc PENTAX-DA 35mm F2.4 AL', #PH + '7 216' => 'smc PENTAX-DA L 55-300mm F4-5.8 ED', #PH + '7 217' => 'smc PENTAX-DA 50-200mm F4-5.6 ED WR', #JD + '7 218' => 'smc PENTAX-DA 18-55mm F3.5-5.6 AL WR', #JD + '7 220' => 'Tamron SP AF 10-24mm F3.5-4.5 Di II LD Aspherical [IF]', #24 + '7 221' => 'smc PENTAX-DA L 50-200mm F4-5.6 ED', #Ar't + '7 222' => 'smc PENTAX-DA L 18-55mm F3.5-5.6', #PH (tag 0x003f -- was '7 229' in LensInfo of one test image) + '7 223' => 'Samsung/Schneider D-XENON 18-55mm F3.5-5.6 II', #PH + '7 224' => 'smc PENTAX-DA 15mm F4 ED AL Limited', #JD + '7 225' => 'Samsung/Schneider D-XENON 18-250mm F3.5-6.3', #8/PH + '7 226' => 'smc PENTAX-DA* 55mm F1.4 SDM (SDM unused)', #PH (NC) + '7 227' => 'smc PENTAX-DA* 60-250mm F4 [IF] SDM (SDM unused)', #PH (NC) + '7 228' => 'Samsung 16-45mm F4 ED', #29 + '7 229' => 'smc PENTAX-DA 18-55mm F3.5-5.6 AL II', #JD + '7 230' => 'Tamron AF 17-50mm F2.8 XR Di-II LD (Model A16)', #JD + '7 231' => 'smc PENTAX-DA 18-250mm F3.5-6.3 ED AL [IF]', #JD + '7 233' => 'smc PENTAX-DA 35mm F2.8 Macro Limited', #JD + '7 234' => 'smc PENTAX-DA* 300mm F4 ED [IF] SDM (SDM unused)', #19 (NC) + '7 235' => 'smc PENTAX-DA* 200mm F2.8 ED [IF] SDM (SDM unused)', #PH (NC) + '7 236' => 'smc PENTAX-DA 55-300mm F4-5.8 ED', #JD + '7 238' => 'Tamron AF 18-250mm F3.5-6.3 Di II LD Aspherical [IF] Macro', #JD + '7 241' => 'smc PENTAX-DA* 50-135mm F2.8 ED [IF] SDM (SDM unused)', #PH + '7 242' => 'smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM (SDM unused)', #19 + '7 243' => 'smc PENTAX-DA 70mm F2.4 Limited', #PH + '7 244' => 'smc PENTAX-DA 21mm F3.2 AL Limited', #16 + '8 0' => 'Sigma 50-150mm F2.8 II APO EX DC HSM', #forum2997 + '8 3' => 'Sigma 18-125mm F3.8-5.6 DC HSM', #forum10167 + '8 4' => 'Sigma 50mm F1.4 EX DG HSM', #Artur private communication + '8 6' => 'Sigma 4.5mm F2.8 EX DC Fisheye', #IB + '8 7' => 'Sigma 24-70mm F2.8 IF EX DG HSM', #Exiv2 + '8 8' => 'Sigma 18-250mm F3.5-6.3 DC OS HSM', #27 + '8 11' => 'Sigma 10-20mm F3.5 EX DC HSM', #27 + '8 12' => 'Sigma 70-300mm F4-5.6 DG OS', #forum3382 + '8 13' => 'Sigma 120-400mm F4.5-5.6 APO DG OS HSM', #26 + '8 14' => 'Sigma 17-70mm F2.8-4.0 DC Macro OS HSM', #(Hubert Meier) + '8 15' => 'Sigma 150-500mm F5-6.3 APO DG OS HSM', #26 + '8 16' => 'Sigma 70-200mm F2.8 EX DG Macro HSM II', #26 + '8 17' => 'Sigma 50-500mm F4.5-6.3 DG OS HSM', #(Heike Herrmann) (also APO, ref 26) + '8 18' => 'Sigma 8-16mm F4.5-5.6 DC HSM', #forum2998 + '8 20' => 'Sigma 18-50mm F2.8-4.5 DC HSM', #IB + '8 21' => 'Sigma 17-50mm F2.8 EX DC OS HSM', #26 + '8 22' => 'Sigma 85mm F1.4 EX DG HSM', #26 + '8 23' => 'Sigma 70-200mm F2.8 APO EX DG OS HSM', #27 + '8 24' => 'Sigma 17-70mm F2.8-4 DC Macro OS HSM', #27 + '8 25' => 'Sigma 17-50mm F2.8 EX DC HSM', #Exiv2 + '8 27' => 'Sigma 18-200mm F3.5-6.3 II DC HSM', #27 + '8 28' => 'Sigma 18-250mm F3.5-6.3 DC Macro HSM', #27 + '8 29' => 'Sigma 35mm F1.4 DG HSM', #27 + '8 30' => 'Sigma 17-70mm F2.8-4 DC Macro HSM | C', #27 + '8 31' => 'Sigma 18-35mm F1.8 DC HSM', #27 + '8 32' => 'Sigma 30mm F1.4 DC HSM | A', #27 + '8 33' => 'Sigma 18-200mm F3.5-6.3 DC Macro HSM', #DieterPearcey (C014) + '8 34' => 'Sigma 18-300mm F3.5-6.3 DC Macro HSM', #NJ + '8 59' => 'HD PENTAX-D FA 150-450mm F4.5-5.6 ED DC AW', #29 + '8 60' => 'HD PENTAX-D FA* 70-200mm F2.8 ED DC AW', #29 + '8 61' => 'HD PENTAX-D FA 28-105mm F3.5-5.6 ED DC WR', #PH + '8 62' => 'HD PENTAX-D FA 24-70mm F2.8 ED SDM WR', #PH + '8 63' => 'HD PENTAX-D FA 15-30mm F2.8 ED SDM WR', #PH + '8 64' => 'HD PENTAX-D FA* 50mm F1.4 SDM AW', #27 + '8 65' => 'HD PENTAX-D FA 70-210mm F4 ED SDM WR', #PH + '8 66' => 'HD PENTAX-D FA 85mm F1.4 ED SDM AW', #James O'Neill + '8 67' => 'HD PENTAX-D FA 21mm F2.4 ED Limited DC WR', #ChristianShulz + '8 195' => 'HD PENTAX DA* 16-50mm F2.8 ED PLM AW', #27 + '8 196' => 'HD PENTAX-DA* 11-18mm F2.8 ED DC AW', #29 + '8 197' => 'HD PENTAX-DA 55-300mm F4.5-6.3 ED PLM WR RE', #29 + '8 198' => 'smc PENTAX-DA L 18-50mm F4-5.6 DC WR RE', #29 + '8 199' => 'HD PENTAX-DA 18-50mm F4-5.6 DC WR RE', #29 + '8 200' => 'HD PENTAX-DA 16-85mm F3.5-5.6 ED DC WR', #29 + '8 209' => 'HD PENTAX-DA 20-40mm F2.8-4 ED Limited DC WR', #29 + '8 210' => 'smc PENTAX-DA 18-270mm F3.5-6.3 ED SDM', #Helmut Schutz + '8 211' => 'HD PENTAX-DA 560mm F5.6 ED AW', #PH + '8 215' => 'smc PENTAX-DA 18-135mm F3.5-5.6 ED AL [IF] DC WR', #PH + '8 226' => 'smc PENTAX-DA* 55mm F1.4 SDM', #JD + '8 227' => 'smc PENTAX-DA* 60-250mm F4 [IF] SDM', #JD + '8 232' => 'smc PENTAX-DA 17-70mm F4 AL [IF] SDM', #JD + '8 234' => 'smc PENTAX-DA* 300mm F4 ED [IF] SDM', #19 + '8 235' => 'smc PENTAX-DA* 200mm F2.8 ED [IF] SDM', #JD + '8 241' => 'smc PENTAX-DA* 50-135mm F2.8 ED [IF] SDM', #JD + '8 242' => 'smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM', #JD + '8 255' => 'Sigma Lens (8 255)', + '8 255.1' => 'Sigma 70-200mm F2.8 EX DG Macro HSM II', #JD + '8 255.2' => 'Sigma 150-500mm F5-6.3 DG APO [OS] HSM', #JD (non-OS version has same type, ref 29) + '8 255.3' => 'Sigma 50-150mm F2.8 II APO EX DC HSM', #forum2997 + '8 255.4' => 'Sigma 4.5mm F2.8 EX DC HSM Circular Fisheye', #PH + '8 255.5' => 'Sigma 50-200mm F4-5.6 DC OS', #26 + '8 255.6' => 'Sigma 24-70mm F2.8 EX DG HSM', #29 + + '9 0' => '645 Manual Lens', #PH (NC) + '9 3' => 'HD PENTAX-FA 43mm F1.9 Limited', #IB + '9 24' => 'HD PENTAX-FA 77mm F1.8 Limited', #IB + '9 39' => 'HD PENTAX-FA 31mm F1.8 AL Limited', #IB + '9 247' => 'HD PENTAX-DA FISH-EYE 10-17mm F3.5-4.5 ED [IF]', #IB +# +# 645 lenses +# + '10 0' => '645 A Series Lens', #PH + '11 1' => 'smc PENTAX-FA 645 75mm F2.8', #PH + '11 2' => 'smc PENTAX-FA 645 45mm F2.8', #PH + '11 3' => 'smc PENTAX-FA* 645 300mm F4 ED [IF]', #PH + '11 4' => 'smc PENTAX-FA 645 45-85mm F4.5', #PH + '11 5' => 'smc PENTAX-FA 645 400mm F5.6 ED [IF]', #PH + '11 7' => 'smc PENTAX-FA 645 Macro 120mm F4', #PH + '11 8' => 'smc PENTAX-FA 645 80-160mm F4.5', #PH + '11 9' => 'smc PENTAX-FA 645 200mm F4 [IF]', #PH + '11 10' => 'smc PENTAX-FA 645 150mm F2.8 [IF]', #PH + '11 11' => 'smc PENTAX-FA 645 35mm F3.5 AL [IF]', #PH + '11 12' => 'smc PENTAX-FA 645 300mm F5.6 ED [IF]', #29 + '11 14' => 'smc PENTAX-FA 645 55-110mm F5.6', #PH + '11 16' => 'smc PENTAX-FA 645 33-55mm F4.5 AL', #PH + '11 17' => 'smc PENTAX-FA 645 150-300mm F5.6 ED [IF]', #PH + '11 21' => 'HD PENTAX-D FA 645 35mm F3.5 AL [IF]', #29 + '13 18' => 'smc PENTAX-D FA 645 55mm F2.8 AL [IF] SDM AW', #PH + '13 19' => 'smc PENTAX-D FA 645 25mm F4 AL [IF] SDM AW', #PH + '13 20' => 'HD PENTAX-D FA 645 90mm F2.8 ED AW SR', #PH + '13 253' => 'HD PENTAX-DA 645 28-45mm F4.5 ED AW SR', #Dominique Schrekling email + '13 254' => 'smc PENTAX-DA 645 25mm F4 AL [IF] SDM AW', #forum8253 +# +# Q-mount lenses (21=auto focus lens, 22=manual focus) +# + '21 0' => 'Pentax Q Manual Lens', #PH + '21 1' => '01 Standard Prime 8.5mm F1.9', #PH + '21 2' => '02 Standard Zoom 5-15mm F2.8-4.5', #PH + '22 3' => '03 Fish-eye 3.2mm F5.6', #PH + '22 4' => '04 Toy Lens Wide 6.3mm F7.1', #PH + '22 5' => '05 Toy Lens Telephoto 18mm F8', #PH + '21 6' => '06 Telephoto Zoom 15-45mm F2.8', #PH + '21 7' => '07 Mount Shield 11.5mm F9', #PH (NC) + '21 8' => '08 Wide Zoom 3.8-5.9mm F3.7-4', #PH (NC) + '21 233' => 'Adapter Q for K-mount Lens', #29 +# +# Ricoh lenses +# + '31 1' => 'GR Lens', #PH (GR III 28mm F2.8) +); + +# Pentax model ID codes - PH +my %pentaxModelID = ( + 0x0000d => 'Optio 330/430', + 0x12926 => 'Optio 230', + 0x12958 => 'Optio 330GS', + 0x12962 => 'Optio 450/550', + 0x1296c => 'Optio S', + 0x12971 => 'Optio S V1.01', + 0x12994 => '*ist D', + 0x129b2 => 'Optio 33L', + 0x129bc => 'Optio 33LF', + 0x129c6 => 'Optio 33WR/43WR/555', + 0x129d5 => 'Optio S4', + 0x12a02 => 'Optio MX', + 0x12a0c => 'Optio S40', + 0x12a16 => 'Optio S4i', + 0x12a34 => 'Optio 30', + 0x12a52 => 'Optio S30', + 0x12a66 => 'Optio 750Z', + 0x12a70 => 'Optio SV', + 0x12a75 => 'Optio SVi', + 0x12a7a => 'Optio X', + 0x12a8e => 'Optio S5i', + 0x12a98 => 'Optio S50', + 0x12aa2 => '*ist DS', + 0x12ab6 => 'Optio MX4', + 0x12ac0 => 'Optio S5n', + 0x12aca => 'Optio WP', + 0x12afc => 'Optio S55', + 0x12b10 => 'Optio S5z', + 0x12b1a => '*ist DL', + 0x12b24 => 'Optio S60', + 0x12b2e => 'Optio S45', + 0x12b38 => 'Optio S6', + 0x12b4c => 'Optio WPi', #13 + 0x12b56 => 'BenQ DC X600', + 0x12b60 => '*ist DS2', + 0x12b62 => 'Samsung GX-1S', + 0x12b6a => 'Optio A10', + 0x12b7e => '*ist DL2', + 0x12b80 => 'Samsung GX-1L', + 0x12b9c => 'K100D', + 0x12b9d => 'K110D', + 0x12ba2 => 'K100D Super', #JD + 0x12bb0 => 'Optio T10/T20', + 0x12be2 => 'Optio W10', + 0x12bf6 => 'Optio M10', + 0x12c1e => 'K10D', + 0x12c20 => 'Samsung GX10', + 0x12c28 => 'Optio S7', + 0x12c2d => 'Optio L20', + 0x12c32 => 'Optio M20', + 0x12c3c => 'Optio W20', + 0x12c46 => 'Optio A20', + 0x12c78 => 'Optio E30', + 0x12c7d => 'Optio E35', + 0x12c82 => 'Optio T30', + 0x12c8c => 'Optio M30', + 0x12c91 => 'Optio L30', + 0x12c96 => 'Optio W30', + 0x12ca0 => 'Optio A30', + 0x12cb4 => 'Optio E40', + 0x12cbe => 'Optio M40', + 0x12cc3 => 'Optio L40', + 0x12cc5 => 'Optio L36', + 0x12cc8 => 'Optio Z10', + 0x12cd2 => 'K20D', + 0x12cd4 => 'Samsung GX20', #8 + 0x12cdc => 'Optio S10', + 0x12ce6 => 'Optio A40', + 0x12cf0 => 'Optio V10', + 0x12cfa => 'K200D', + 0x12d04 => 'Optio S12', + 0x12d0e => 'Optio E50', + 0x12d18 => 'Optio M50', + 0x12d22 => 'Optio L50', + 0x12d2c => 'Optio V20', + 0x12d40 => 'Optio W60', + 0x12d4a => 'Optio M60', + 0x12d68 => 'Optio E60/M90', + 0x12d72 => 'K2000', + 0x12d73 => 'K-m', + 0x12d86 => 'Optio P70', + 0x12d90 => 'Optio L70', + 0x12d9a => 'Optio E70', + 0x12dae => 'X70', + 0x12db8 => 'K-7', + 0x12dcc => 'Optio W80', + 0x12dea => 'Optio P80', + 0x12df4 => 'Optio WS80', + 0x12dfe => 'K-x', + 0x12e08 => '645D', + 0x12e12 => 'Optio E80', + 0x12e30 => 'Optio W90', + 0x12e3a => 'Optio I-10', + 0x12e44 => 'Optio H90', + 0x12e4e => 'Optio E90', + 0x12e58 => 'X90', + 0x12e6c => 'K-r', + 0x12e76 => 'K-5', + 0x12e8a => 'Optio RS1000/RS1500', + 0x12e94 => 'Optio RZ10', + 0x12e9e => 'Optio LS1000', + 0x12ebc => 'Optio WG-1 GPS', + 0x12ed0 => 'Optio S1', + 0x12ee4 => 'Q', + 0x12ef8 => 'K-01', + 0x12f0c => 'Optio RZ18', + 0x12f16 => 'Optio VS20', + 0x12f2a => 'Optio WG-2 GPS', + 0x12f48 => 'Optio LS465', + 0x12f52 => 'K-30', + 0x12f5c => 'X-5', + 0x12f66 => 'Q10', + 0x12f70 => 'K-5 II', + 0x12f71 => 'K-5 II s', #forum4515 + 0x12f7a => 'Q7', + 0x12f84 => 'MX-1', + 0x12f8e => 'WG-3 GPS', + 0x12f98 => 'WG-3', + 0x12fa2 => 'WG-10', + 0x12fb6 => 'K-50', + 0x12fc0 => 'K-3', #29 + 0x12fca => 'K-500', + 0x12fe8 => 'WG-4', # (Ricoh) + 0x12fde => 'WG-4 GPS', # (Ricoh) + 0x13006 => 'WG-20', # (Ricoh) + 0x13010 => '645Z', + 0x1301a => 'K-S1', + 0x13024 => 'K-S2', #29 (Ricoh) + 0x1302e => 'Q-S1', + 0x13056 => 'WG-30', # (Ricoh) + 0x1307e => 'WG-30W', # (Ricoh) + 0x13088 => 'WG-5 GPS', # (Ricoh) + 0x13092 => 'K-1', #IB (Ricoh) + 0x1309c => 'K-3 II', #29 (Ricoh) + 0x131f0 => 'WG-M2', # (Ricoh) + 0x1320e => 'GR III', # (Ricoh) + 0x13222 => 'K-70', #29 (Ricoh) + 0x1322c => 'KP', #29 (Ricoh) + 0x13240 => 'K-1 Mark II', # (Ricoh) + 0x13254 => 'K-3 Mark III', #IB (Ricoh) + 0x13290 => 'WG-70', # (Ricoh) +); + +# Pentax city codes - (PH, Optio WP) +my %pentaxCities = ( + 0 => 'Pago Pago', + 1 => 'Honolulu', + 2 => 'Anchorage', + 3 => 'Vancouver', + 4 => 'San Francisco', + 5 => 'Los Angeles', + 6 => 'Calgary', + 7 => 'Denver', + 8 => 'Mexico City', + 9 => 'Chicago', + 10 => 'Miami', + 11 => 'Toronto', + 12 => 'New York', + 13 => 'Santiago', + 14 => 'Caracus', + 15 => 'Halifax', + 16 => 'Buenos Aires', + 17 => 'Sao Paulo', + 18 => 'Rio de Janeiro', + 19 => 'Madrid', + 20 => 'London', + 21 => 'Paris', + 22 => 'Milan', + 23 => 'Rome', + 24 => 'Berlin', + 25 => 'Johannesburg', + 26 => 'Istanbul', + 27 => 'Cairo', + 28 => 'Jerusalem', + 29 => 'Moscow', + 30 => 'Jeddah', + 31 => 'Tehran', + 32 => 'Dubai', + 33 => 'Karachi', + 34 => 'Kabul', + 35 => 'Male', + 36 => 'Delhi', + 37 => 'Colombo', + 38 => 'Kathmandu', + 39 => 'Dacca', + 40 => 'Yangon', + 41 => 'Bangkok', + 42 => 'Kuala Lumpur', + 43 => 'Vientiane', + 44 => 'Singapore', + 45 => 'Phnom Penh', + 46 => 'Ho Chi Minh', + 47 => 'Jakarta', + 48 => 'Hong Kong', + 49 => 'Perth', + 50 => 'Beijing', + 51 => 'Shanghai', + 52 => 'Manila', + 53 => 'Taipei', + 54 => 'Seoul', + 55 => 'Adelaide', + 56 => 'Tokyo', + 57 => 'Guam', + 58 => 'Sydney', + 59 => 'Noumea', + 60 => 'Wellington', + 61 => 'Auckland', + 62 => 'Lima', + 63 => 'Dakar', + 64 => 'Algiers', + 65 => 'Helsinki', + 66 => 'Athens', + 67 => 'Nairobi', + 68 => 'Amsterdam', + 69 => 'Stockholm', + 70 => 'Lisbon', #14 + 71 => 'Copenhagen', #NJ + 72 => 'Warsaw', + 73 => 'Prague', + 74 => 'Budapest', +); + +# digital filter tag information (ref PH, K-5) +# (also see %filterSettings below for decoding of filter parameters) +my %digitalFilter = ( + Format => 'undef[17]', + RawConv => '($val!~/^\\0/ or $$self{OPTIONS}{Unknown}) ? join(" ",unpack("Cc*",$val)) : undef', + SeparateTable => 'DigitalFilter', + ValueConvInv => q{ + return "\0" x 17 if $val eq "0"; + $val = pack("Cc*", $val=~/[-+]?\d+/g); + length($val)==17 or warn("Expecting 17 values\n"), return undef; + return $val; + }, + PrintConv => { + OTHER => \&PrintFilter, # this routine actually converts all values + 0 => 'Off', + 1 => 'Base Parameter Adjust', + 2 => 'Soft Focus', + 3 => 'High Contrast', + 4 => 'Color Filter', + 5 => 'Extract Color', + 6 => 'Monochrome', + 7 => 'Slim', + 9 => 'Fisheye', + 10 => 'Toy Camera', + 11 => 'Retro', + 12 => 'Pastel', + 13 => 'Water Color', + 14 => 'HDR', + 16 => 'Miniature', + 17 => 'Starburst', + 18 => 'Posterization', + 19 => 'Sketch Filter', + 20 => 'Shading', # (Q) + 21 => 'Invert Color', # (Q) + 23 => 'Tone Expansion', #Forum5247 + 27 => 'Unicolor Bold', #31 + 28 => 'Bold Monochrome', #31 + 29 => 'Replace Color', #31 + 254 => 'Custom Filter', + }, +); + +# digital filter setting names and conversions (ref PH, K-5) +# Note: names must be unique for writing +my %filterSettings = ( + 1 => ['Brightness', '%+d'], # BPA (-8-+8) + 2 => ['Saturation', '%+d'], # BPA (-3-+3) + 3 => ['Hue', '%+d'], # BPA (-3-+3) + 4 => ['Contrast', '%+d'], # BPA (-3-+3) + 5 => ['Sharpness', '%+d'], # BPA (-3-+3) + 6 => ['SoftFocus', '%d'], # Soft Focus/Custom (1-3) + 7 => ['ShadowBlur', { 0=>'Off',1=>'On' }], # Soft Focus + 8 => ['HighContrast', '%d'], # High Contrast/Custom (1-5) + 9 => ['Color', { 1=>'Red',2=>'Magenta',3=>'Blue',4=>'Cyan',5=>'Green',6=>'Yellow' }], # Color Filter + 10 => ['Density', { 1=>'Light',2=>'Standard',3=>'Dark' }], # Color Filter + 11 => ['ExtractedColor',{ 0=>'Off',1=>'Red',2=>'Magenta',3=>'Blue',4=>'Cyan',5=>'Green',6=>'Yellow' }], # Extract Color [x2] + 12 => ['ColorRange', '%+d'], # Extract Color [x2] (-2-+2) + 13 => ['FilterEffect', { 0=>'Off',1=>'Red',2=>'Green',3=>'Blue',4=>'Infrared'}], # Monochrome + 14 => ['ToningBA', '%+d'], # Monochrome (-3-+3) + 15 => ['InvertColor', { 0=>'Off',1=>'On' }], # Custom/Invert Color + 16 => ['Slim', '%+d'], # Slim (-8-+8) + 17 => ['EffectDensity', { 1=>'Sparse',2=>'Normal',3=>'Dense' }], # Starburst + 18 => ['Size', { 1=>'Small',2=>'Medium',3=>'Large' }], # Starburst + 19 => ['Angle', { 0=>'0deg',2=>'30deg',3=>'45deg',4=>'60deg'}], # Starburst (1 is unused) + 20 => ['Fisheye', { 1=>'Weak',2=>'Medium',3=>'Strong' }], # Fisheye + 21 => ['DistortionType', '%d'], # Custom (1-3) + 22 => ['DistortionLevel',{0=>'Off',1=>'Weak',2=>'Medium',3=>'Strong' }], #Custom + 23 => ['ShadingType', '%d'], # Custom/Shading (1-6) + 24 => ['ShadingLevel', '%+d'], # Custom/Shading (-3-+3) + 25 => ['Shading', '%d'], # Toy Camera (1-3) + 26 => ['Blur', '%d'], # Toy Camera (1-3) + 27 => ['ToneBreak', { 0=>'Off',1=>'Red',2=>'Green',3=>'Blue',4=>'Yellow'}], # Toy Camera/Custom + 28 => ['Toning', '%+d'], # Retro (-3-+3) + 29 => ['FrameComposite',{ 0=>'None',1=>'Thin',2=>'Medium',3=>'Thick' }], # Retro + 30 => ['PastelStrength',{ 1=>'Weak',2=>'Medium',3=>'Strong' }], # Pastel + 31 => ['Intensity', '%d'], # Water Color (1-3) + 32 => ['Saturation2', { 0=>'Off',1=>'Low',2=>'Medium',3=>'High' }], # Water Color + 33 => ['HDR', { 1=>'Weak',2=>'Medium',3=>'Strong' }], # HDR + # (34 missing) + 35 => ['FocusPlane', '%+d'], # Miniature (-3-+3) + 36 => ['FocusWidth', { 1=>'Narrow',2=>'Middle',3=>'Wide' }], # Miniature + 37 => ['PlaneAngle', { 0=>'Horizontal',1=>'Vertical',2=>'Positive slope',3=>'Negative slope' }], # Miniature + 38 => ['Blur2', '%d'], # Miniature (1-3) + 39 => ['Shape', { 1=>'Cross',2=>'Star',3=>'Snowflake',4=>'Heart',5=>'Note'}], # Starburst + 40 => ['Posterization', '%d'], # Posterization (1-5) + 41 => ['Contrast2', { 1=>'Low',2=>'Medium',3=>'High'}], # Sketch Filter + 42 => ['ScratchEffect', { 0=>'Off',1=>'On' }], # Sketch Filter + 45 => ['ToneExpansion', { 1=>'Low',2=>'Medium',3=>'High' }], # Tone Expansion (ref Forum5247) + 47 => ['UnicolorBold', { 1=>'Red',2=>'Magenta',3=>'Blue',4=>'Cyan',5=>'Green',6=>'Yellow' }], #31 Unicolor Bold + 48 => ['BoldMonochrome', '%d'], #31 Bold Monochrome (1-3) + 49 => ['OriginalColor', { 1=>'Red',2=>'Magenta',3=>'Blue',4=>'Cyan',5=>'Green',6=>'Yellow' }], #31 Replace Color + 50 => ['NewColor', { 1=>'Red',2=>'Magenta',3=>'Blue',4=>'Cyan',5=>'Green',6=>'Yellow' }], #31 Replace Color + 51 => ['ColorScale', '%d'], #31 Replace Color (1-5) + 52 => ['Toning2', '%+d'], #31 Extract Color (-3-+3) +); + +# decoding for Pentax Firmware ID tags - PH +my %pentaxFirmwareID = ( + # the first 2 numbers are the firmware version, I'm not sure what the second 2 mean + # Note: the byte order may be different for some models + # which give, for example, version 0.01 instead of 1.00 + ValueConv => sub { + my $val = shift; + return $val unless length($val) == 4; + # (value is encrypted by toggling all bits) + my @a = map { $_ ^ 0xff } unpack("C*",$val); + return sprintf('%d %.2d %.2d %.2d', @a); + }, + ValueConvInv => sub { + my $val = shift; + my @a = $val=~/\b\d+\b/g; + return $val unless @a == 4; + @a = map { ($_ & 0xff) ^ 0xff } @a; + return pack("C*", @a); + }, + PrintConv => '$val=~tr/ /./; $val', + PrintConvInv => '$val=~s/^(\d+)\.(\d+)\.(\d+)\.(\d+)/$1 $2 $3 $4/ ? $val : undef', +); + +# convert 16 or 77 metering segment values to approximate LV equivalent - PH +my %convertMeteringSegments = ( + PrintConv => sub { join ' ', map( + { $_==255 ? 'n/a' : $_==0 ? '0' : sprintf '%.1f', $_ / 8 - 6 } split(' ',$_[0]) + ) }, + PrintConvInv => sub { join ' ', map( + { /^n/i ? 255 : $_==0 ? '0' : int(($_ + 6) * 8 + 0.5) } split(' ',$_[0]) + ) }, +); + +# lens code conversions +my %lensCode = ( + Unknown => 1, + PrintConv => 'sprintf("0x%.2x", $val)', + PrintConvInv => 'hex($val)', +); + +# conversions for tags 0x0053-0x005a +my %colorTemp = ( + Writable => 'undef', + Count => 4, + ValueConv => sub { + my $val = shift; + return $val unless length $val == 4; + my @a = unpack 'nCC', $val; + $a[0] = 53190 - $a[0]; + $a[1] = ($a[2] & 0x0f); $a[1] -= 16 if $a[1] >= 8; + $a[2] = ($a[2] >> 4); $a[2] -= 16 if $a[2] >= 8; + return "@a"; + }, + ValueConvInv => sub { + my $val = shift; + my @a = split ' ', $val; + return undef unless @a == 3; + return pack 'nCC', 53190 - $a[0], 0, ($a[1] & 0x0f) + (($a[2] & 0x0f) << 4); + }, + PrintConv => sub { + $_ = shift; + s/ ([1-9])/ +$1/g; + s/ 0/ 0/g; + return $_; + }, + PrintConvInv => '$val', +); + +# conversions for KelvinWB tags +my %kelvinWB = ( + Format => 'int16u[4]', + ValueConv => sub { + my @a = split ' ', shift; + (53190 - $a[0]) . ' ' . $a[1] . ' ' . ($a[2] / 8192) . ' ' . ($a[3] / 8192); + }, + ValueConvInv => sub { + my @a = split ' ', shift; + (53190 - $a[0]) . ' ' . $a[1] . ' ' . int($a[2]*8192+0.5) . ' ' . int($a[3]*8192+0.5); + }, +); + +my %noYes = ( 0 => 'No', 1 => 'Yes' ); + +# common attributes for writable BinaryData directories +my %binaryDataAttrs = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, +); + +# Pentax makernote tags +%Image::ExifTool::Pentax::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + WRITABLE => 1, + 0x0000 => { #5 + Name => 'PentaxVersion', + Writable => 'int8u', + Count => 4, + PrintConv => '$val=~tr/ /./; $val', + PrintConvInv => '$val=~tr/./ /; $val', + # 0.1.0.3 - PENTAX Optio E40 + # 3.0.0.0 - K10D + # 3.1.0.0 - Optio A40/S10/L36/L40/M40/V10 + # 3.1.1.0 - Optio L36/L40/M40/V10 + # 3.1.2.0 - Optio Z10 + # 4.0.2.0 - Optio E50 + # 4.1.0.0 - Optio S12 + # 4.1.1.0 - Optio M50 + # 4.1.2.0 - K20D, K200D + # 4.2.0.0 - Optio L50/V20 + # 4.2.1.0 - Optio E60/M90 + # 4.2.2.0 - Optio W60 + # 4.2.3.0 - Optio M60 + # 4.4.0.1 - K-m, K2000 + # 4.5.0.0 - Optio E70/L70 + # 4.5.0.0 - Optio E70/L70/P70 + # 4.6.0.0 - Optio E80/E90/W80 + # 5.0.0.0 - K-7, Optio P80/WS80 + # 5.1.0.0 - K-x + # 5.2.0.0 - Optio I-10 + # 5.3.0.0 - Optio H90 + # 5.3.2.0 - Optio W90/X90 + # 6.0.0.0 - K-r, 645D + # 6.1.3.0 - Optio LS1000/RS1000/RS1500/RZ10 + # 7.0.0.0 - K-5 + # 7.1.0.0 - Optio WG-1GPS/WG-10/WG-20 + # 7.2.0.0 - Optio S1 + # 8.0.0.0 - Q + # 8.0.1.0 - Optio RZ18 + # 8.0.4.0 - Optio VS20 + # 8.1.0.0 - Optio LS465/WG-2GPS + # 9.0.0.0 - K-01 + # 9.1.2.0 - X-5 + # 10.0.0.0 - K-30, K-50, K-500, K-5 II, K-5 II s + # 10.0.2.0 - Q10 + # 10.2.0.0 - WG-3/WG-4 + # 10.2.1.0 - MX-1 + # 10.4.1.0 - WG-3/4/5 GPS, WG-30/30W + # 10.6.1.0 - Q-S1, Q7 + # 11.0.0.0 - K-3 + # 11.2.1.0 - 645Z + # 11.3.0.0 - K-S1 + # 11.5.0.0 - K-S2 + # 11.6.1.0 - K-3 II + # 11.7.5.0 - WG-M2 + # 12.0.0.0 - K-1 + # 12.1.3.0 - K-70 + # 12.1.5.0 - KP + }, + 0x0001 => { #PH + Name => 'PentaxModelType', + Writable => 'int16u', + # (values of 0-5 seem to group models into 6 categories, ref 13) + }, + 0x0002 => { #PH + Name => 'PreviewImageSize', + Groups => { 2 => 'Image' }, + Writable => 'int16u', + Count => 2, + PrintConv => '$val =~ tr/ /x/; $val', + PrintConvInv => '$val =~ tr/x/ /; $val', + }, + 0x0003 => { #PH + Name => 'PreviewImageLength', + OffsetPair => 0x0004, # point to associated offset + DataTag => 'PreviewImage', + Groups => { 2 => 'Image' }, + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x0004 => { #PH + Name => 'PreviewImageStart', + IsOffset => 2, # code to use original base + OffsetPair => 0x0003, # point to associated byte count + DataTag => 'PreviewImage', + Groups => { 2 => 'Image' }, + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + 0x0005 => { #13 + Name => 'PentaxModelID', + Writable => 'int32u', + PrintHex => 1, + SeparateTable => 1, + DataMember => 'PentaxModelID', + RawConv => '$$self{PentaxModelID} = $val', + PrintConv => \%pentaxModelID, + }, + 0x0006 => { #5 + # Note: Year is int16u in MM byte ordering regardless of EXIF byte order + Name => 'Date', + Groups => { 2 => 'Time' }, + Notes => 'changing either Date or Time will affect ShutterCount decryption', + Writable => 'undef', + Count => 4, + Shift => 'Time', + DataMember => 'PentaxDate', + RawConv => '$$self{PentaxDate} = $val', # save to decrypt ShutterCount + ValueConv => 'length($val)==4 ? sprintf("%.4d:%.2d:%.2d",unpack("nC2",$val)) : "Unknown ($val)"', + ValueConvInv => q{ + $val =~ s/(\d) .*/$1/; # remove Time + my @v = split /:/, $val; + return pack("nC2",$v[0],$v[1],$v[2]); + }, + }, + 0x0007 => { #5 + Name => 'Time', + Groups => { 2 => 'Time' }, + Writable => 'undef', + Count => 3, + Shift => 'Time', + DataMember => 'PentaxTime', + RawConv => '$$self{PentaxTime} = $val', # save to decrypt ShutterCount + ValueConv => 'length($val)>=3 ? sprintf("%.2d:%.2d:%.2d",unpack("C3",$val)) : "Unknown ($val)"', + ValueConvInv => q{ + $val =~ s/^[0-9:]+ (\d)/$1/; # remove Date + return pack("C3",split(/:/,$val)); + }, + }, + 0x0008 => { #2 + Name => 'Quality', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Good', + 1 => 'Better', + 2 => 'Best', + 3 => 'TIFF', #5 + 4 => 'RAW', #5 + 5 => 'Premium', #PH (K20D) + 7 => 'RAW (pixel shift enabled)', #forum6536 (K-3 II) + 8 => 'Dynamic Pixel Shift', #IB + 65535 => 'n/a', #PH (Q MOV video) + }, + }, + 0x0009 => { #3 + Name => 'PentaxImageSize', + Groups => { 2 => 'Image' }, + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => '640x480', + 1 => 'Full', #PH - this can mean 2048x1536 or 2240x1680 or ... ? + 2 => '1024x768', + 3 => '1280x960', #PH (Optio WP) + 4 => '1600x1200', + 5 => '2048x1536', + 8 => '2560x1920 or 2304x1728', #PH (Optio WP) or #14 + 9 => '3072x2304', #PH (Optio M30) + 10 => '3264x2448', #13 + 19 => '320x240', #PH (Optio WP) + 20 => '2288x1712', #13 + 21 => '2592x1944', + 22 => '2304x1728 or 2592x1944', #2 or #14 + 23 => '3056x2296', #13 + 25 => '2816x2212 or 2816x2112', #13 or #14 + 27 => '3648x2736', #PH (Optio A20) + 29 => '4000x3000', #PH (X70) + 30 => '4288x3216', #PH (Optio RS1000) + 31 => '4608x3456', #PH (Optio RZ18) + 129 => '1920x1080', #PH (Optio RZ10) + 135 => '4608x2592', #PH (Q10 stretch filter) + 257 => '3216x3216', #PH (Optio RZ10) + '0 0' => '2304x1728', #13 + '4 0' => '1600x1200', #PH (Optio MX4) + '5 0' => '2048x1536', #13 + '8 0' => '2560x1920', #13 + '32 2' => '960x640', #7 + '33 2' => '1152x768', #7 + '34 2' => '1536x1024', #7 + '35 1' => '2400x1600', #7 + '36 0' => '3008x2008 or 3040x2024', #PH + '37 0' => '3008x2000', #13 + # 65535 - seen for an X-5 panorama (PH) + }, + }, + 0x000b => { #3 + Name => 'PictureMode', + Writable => 'int16u', + Count => -1, + Notes => q{ + 1 or 2 values. Decimal values differentiate Optio 555 modes which are + different from other models + }, + ValueConv => '(IsInt($val) and $val < 4 and $$self{Model} =~ /Optio 555\b/) ? $val + 0.1 : $val', + ValueConvInv => 'int $val', + PrintConvColumns => 2, + PrintConv => [{ + 0 => 'Program', #PH + 0.1 => 'Av', #PH (Optio 555) + 1 => 'Shutter Speed Priority', #JD + 1.1 => 'M', #PH (Optio 555) + 2 => 'Program AE', #13 + 2.1 => 'Tv', #PH (Optio 555) + 3 => 'Manual', #13 + 3.1 => 'USER', #PH (Optio 555) + 5 => 'Portrait', + 6 => 'Landscape', + 8 => 'Sport', #PH + 9 => 'Night Scene', + # 10 "full mode"? #13 + 11 => 'Soft', #PH + 12 => 'Surf & Snow', + 13 => 'Candlelight', #13 + 14 => 'Autumn', + 15 => 'Macro', + 17 => 'Fireworks', + 18 => 'Text', + 19 => 'Panorama', #PH + 20 => '3-D', #PH (Optio 555) + 21 => 'Black & White', #PH (Optio 555) + 22 => 'Sepia', #PH (Optio 555) + 23 => 'Red', #PH (Optio 555) + 24 => 'Pink', #PH (Optio 555) + 25 => 'Purple', #PH (Optio 555) + 26 => 'Blue', #PH (Optio 555) + 27 => 'Green', #PH (Optio 555) + 28 => 'Yellow', #PH (Optio 555) + 30 => 'Self Portrait', #PH + 31 => 'Illustrations', #13 + 33 => 'Digital Filter', #13 + 35 => 'Night Scene Portrait', #NJ + 37 => 'Museum', #PH + 38 => 'Food', #PH + 39 => 'Underwater', #NJ + 40 => 'Green Mode', #PH + 49 => 'Light Pet', #PH + 50 => 'Dark Pet', #PH + 51 => 'Medium Pet', #PH + 53 => 'Underwater', #PH + 54 => 'Candlelight', #PH + 55 => 'Natural Skin Tone', #PH + 56 => 'Synchro Sound Record', #PH + 58 => 'Frame Composite', #14 + 59 => 'Report', #NJ + 60 => 'Kids', #13 + 61 => 'Blur Reduction', #13 + 63 => 'Panorama 2', #PH (X-5) + 65 => 'Half-length Portrait', #JD + 66 => 'Portrait 2', #PH (LS645) + 74 => 'Digital Microscope', #PH (WG-4) + 75 => 'Blue Sky', #PH (LS465) + 80 => 'Miniature', #PH (VS20) + 81 => 'HDR', #PH (LS465) + 83 => 'Fisheye', #PH (VS20) + 85 => 'Digital Filter 4', #PH (WG-5) + 221 => 'P', #PH (Optio 555) + 255=> 'PICT', #PH (Optio 555) + }], + }, + 0x000c => { #PH + Name => 'FlashMode', + Writable => 'int16u', + Count => -1, + PrintHex => 1, + PrintConv => [{ + 0x000 => 'Auto, Did not fire', + 0x001 => 'Off, Did not fire', + 0x002 => 'On, Did not fire', #19 + 0x003 => 'Auto, Did not fire, Red-eye reduction', + 0x005 => 'On, Did not fire, Wireless (Master)', #19 + 0x100 => 'Auto, Fired', + 0x102 => 'On, Fired', + 0x103 => 'Auto, Fired, Red-eye reduction', + 0x104 => 'On, Red-eye reduction', + 0x105 => 'On, Wireless (Master)', #19 + 0x106 => 'On, Wireless (Control)', #19 + 0x108 => 'On, Soft', + 0x109 => 'On, Slow-sync', + 0x10a => 'On, Slow-sync, Red-eye reduction', + 0x10b => 'On, Trailing-curtain Sync', + },{ #19 (AF-540FGZ flash) + 0x000 => 'n/a - Off-Auto-Aperture', #19 + 0x03f => 'Internal', + 0x100 => 'External, Auto', + 0x23f => 'External, Flash Problem', #JD + 0x300 => 'External, Manual', + 0x304 => 'External, P-TTL Auto', + 0x305 => 'External, Contrast-control Sync', #JD + 0x306 => 'External, High-speed Sync', + 0x30c => 'External, Wireless', + 0x30d => 'External, Wireless, High-speed Sync', + }], + }, + 0x000d => [ #2 + { + Name => 'FocusMode', + # (can't test for "PENTAX" because MOV videos don't have Make) + Condition => '$$self{Make} !~ /^Asahi/', + Notes => 'Pentax models', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { #PH + 0 => 'Normal', + 1 => 'Macro', + 2 => 'Infinity', + 3 => 'Manual', + 4 => 'Super Macro', #JD + 5 => 'Pan Focus', + # 8 - seen for Ricoh GR III + # 9 - seen for Ricoh GR III + 16 => 'AF-S (Focus-priority)', #17 + 17 => 'AF-C (Focus-priority)', #17 + 18 => 'AF-A (Focus-priority)', #PH (educated guess) + 32 => 'Contrast-detect (Focus-priority)', #PH (K-5) + 33 => 'Tracking Contrast-detect (Focus-priority)', #PH (K-5) + # bit 8 indicates release priority + 272 => 'AF-S (Release-priority)', #PH (K-5,K-3) + 273 => 'AF-C (Release-priority)', #PH (K-5,K-3) + 274 => 'AF-A (Release-priority)', #PH (K-3) + 288 => 'Contrast-detect (Release-priority)', #PH (K-01) + # 32777 (0x8009) - seen for Ricoh GR III + # 32779 (0x800b) - seen for Ricoh GR III + }, + },{ + Name => 'FocusMode', + Writable => 'int16u', + Notes => 'Asahi models', + PrintConv => { #2 + 0 => 'Normal', + 1 => 'Macro (1)', + 2 => 'Macro (2)', + 3 => 'Infinity', + }, + }, + ], + 0x000e => [{ #29 + Name => 'AFPointSelected', + Condition => '$$self{Model} =~ /K-1\b/', + Writable => 'int16u', + Notes => 'K-1', + PrintConvColumns => 2, + PrintConv => [{ + 0xffff => 'Auto', + 0xfffe => 'Fixed Center', + 0xfffd => 'Automatic Tracking AF', #JD + 0xfffc => 'Face Detect AF', #JD + 0xfffb => 'AF Select', #PH (Q select from 25-areas) + # AF pattern: + # 01 02 03 04 05 + # 06 07 08 09 10 11 12 + # 13 14 15 16 17 18 19 20 21 + # 22 23 24 25 26 27 28 + # 29 30 31 32 33 + 0 => 'None', + 1 => 'Top-left', + 2 => 'Top Near-left', + 3 => 'Top', + 4 => 'Top Near-right', + 5 => 'Top-right', + 6 => 'Upper Far-left', + 7 => 'Upper-left', + 8 => 'Upper Near-left', + 9 => 'Upper-middle', + 10 => 'Upper Near-right', + 11 => 'Upper-right', + 12 => 'Upper Far-right', + 13 => 'Far Far Left', + 14 => 'Far Left', + 15 => 'Left', + 16 => 'Near-left', + 17 => 'Center', + 18 => 'Near-right', + 19 => 'Right', + 20 => 'Far Right', + 21 => 'Far Far Right', + 22 => 'Lower Far-left', + 23 => 'Lower-left', + 24 => 'Lower Near-left', + 25 => 'Lower-middle', + 26 => 'Lower Near-right', + 27 => 'Lower-right', + 28 => 'Lower Far-right', + 29 => 'Bottom-left', + 30 => 'Bottom Near-left', + 31 => 'Bottom', + 32 => 'Bottom Near-right', + 33 => 'Bottom-right', + 263 => 'Zone Select Upper-left', # 01,02;06,07,08;14,15,16 + 264 => 'Zone Select Upper Near-left', # 01,02,03;07,08,09;15,16,17 + 265 => 'Zone Select Upper Middle', # 02,03,04;08,09,10;16,17,18 + 266 => 'Zone Select Upper Near-right', # 03,04,05;09,10,11;17,18,19 + 267 => 'Zone Select Upper-right', # 04,05;10,11,12;18,19,20 + 270 => 'Zone Select Far Left', # 06,07;13,14,15;22,23 + 271 => 'Zone Select Left', # 06,07,08;14,15,16;22,23,24 + 272 => 'Zone Select Near-left', # 07,08,09;15,16,17;23,24,25 + 273 => 'Zone Select Center', # 08,09,10;16,17,18;24,25,26 + 274 => 'Zone Select Near-right', # 09,10,11;17,18,19;25,26,27 + 275 => 'Zone Select Right', # 10,11,12;18,19,20;26,27,28 + 276 => 'Zone Select Far Right', # 11,12;19,20,21;27,28 + 279 => 'Zone Select Lower-left', # 14,15,16;22,23,24;29,30 + 280 => 'Zone Select Lower Near-left', # 15,16,17;23,24,25;29,30,31 + 281 => 'Zone Select Lower-middle', # 16,17,18;24,25,26;30,31,32 + 282 => 'Zone Select Lower Near-right', # 17,18,19;25,26,27;31,32,33 + 283 => 'Zone Select Lower-right', # 18,19,20;26,27,28;32,33 + },{ + 0 => 'Single Point', + 1 => 'Expanded Area 9-point (S)', + 3 => 'Expanded Area 25-point (M)', + 5 => 'Expanded Area 33-point (L)', + }], + },{ + Name => 'AFPointSelected', + Condition => '$$self{Model} =~ /K-3\b/', + Writable => 'int16u', + Notes => 'K-3', + PrintConvColumns => 2, + PrintConv => [{ + # 0 - Contrast-detect AF? - PH (K-5) + 0xffff => 'Auto', + 0xfffe => 'Fixed Center', + 0xfffd => 'Automatic Tracking AF', #JD + 0xfffc => 'Face Detect AF', #JD + 0xfffb => 'AF Select', #PH (Q select from 25-areas) + # AF pattern: (ref forum5422) + # 01 02 03 04 05 + # 06 07 08 09 10 + # 11 12 13 14 15 16 17 + # 18 19 20 21 22 + # 23 24 25 26 27 + 0 => 'None', + 1 => 'Top-left', + 2 => 'Top Near-left', + 3 => 'Top', + 4 => 'Top Near-right', + 5 => 'Top-right', + 6 => 'Upper-left', + 7 => 'Upper Near-left', + 8 => 'Upper-middle', + 9 => 'Upper Near-right', + 10 => 'Upper-right', + 11 => 'Far Left', + 12 => 'Left', + 13 => 'Near-left', + 14 => 'Center', + 15 => 'Near-right', + 16 => 'Right', + 17 => 'Far Right', + 18 => 'Lower-left', + 19 => 'Lower Near-left', + 20 => 'Lower-middle', + 21 => 'Lower Near-right', + 22 => 'Lower-right', + 23 => 'Bottom-left', + 24 => 'Bottom Near-left', + 25 => 'Bottom', + 26 => 'Bottom Near-right', + 27 => 'Bottom-right', + #forum5892 + 257 => 'Zone Select Top-left', + 258 => 'Zone Select Top Near-left', + 259 => 'Zone Select Top', + 260 => 'Zone Select Top Near-right', + 261 => 'Zone Select Top-right', + 262 => 'Zone Select Upper-left', + 263 => 'Zone Select Upper Near-left', + 264 => 'Zone Select Upper-middle', + 265 => 'Zone Select Upper Near-right', + 266 => 'Zone Select Upper-right', + 267 => 'Zone Select Far Left', + 268 => 'Zone Select Left', + 269 => 'Zone Select Near-left', + 270 => 'Zone Select Center', + 271 => 'Zone Select Near-right', + 272 => 'Zone Select Right', + 273 => 'Zone Select Far Right', + 274 => 'Zone Select Lower-left', + 275 => 'Zone Select Lower Near-left', + 276 => 'Zone Select Lower-middle', + 277 => 'Zone Select Lower Near-right', + 278 => 'Zone Select Lower-right', + 279 => 'Zone Select Bottom-left', + 280 => 'Zone Select Bottom Near-left', + 281 => 'Zone Select Bottom', + 282 => 'Zone Select Bottom Near-right', + 283 => 'Zone Select Bottom-right', + },{ #forum5892 + 0 => 'Single Point', + 1 => 'Expanded Area 9-point (S)', + 3 => 'Expanded Area 25-point (M)', + 5 => 'Expanded Area 27-point (L)', + }], + },{ #7 + Name => 'AFPointSelected', + Writable => 'int16u', + Notes => 'other models', + PrintConvColumns => 2, + PrintConv => [{ + # 0 - Contrast-detect AF? - PH (K-5) + 0xffff => 'Auto', + 0xfffe => 'Fixed Center', + 0xfffd => 'Automatic Tracking AF', #JD + 0xfffc => 'Face Detect AF', #JD + 0xfffb => 'AF Select', #PH (Q select from 25-areas) + 0 => 'None', #PH (Q in manual focus mode) + 1 => 'Upper-left', + 2 => 'Top', + 3 => 'Upper-right', + 4 => 'Left', + 5 => 'Mid-left', + 6 => 'Center', + 7 => 'Mid-right', + 8 => 'Right', + 9 => 'Lower-left', + 10 => 'Bottom', + 11 => 'Lower-right', + }, + # (second number exists for K-5II(s) is usually 0, but is 1 for AF.C with + # AFPointMode=='Select' and extended tracking focus points are enabled in the settings) + ], + }], + 0x000f => [{ #PH + Name => 'AFPointsInFocus', + Condition => '$$self{Model} =~ /K-3\b/', + Writable => 'int32u', + Notes => 'K-3 only', + PrintHex => 1, + PrintConv => { + 0 => '(none)', + BITMASK => { + 0 => 'Top-left', + 1 => 'Top Near-left', + 2 => 'Top', + 3 => 'Top Near-right', + 4 => 'Top-right', + 5 => 'Upper-left', + 6 => 'Upper Near-left', + 7 => 'Upper-middle', + 8 => 'Upper Near-right', + 9 => 'Upper-right', + 10 => 'Far Left', + 11 => 'Left', + 12 => 'Near-left', + 13 => 'Center', + 14 => 'Near-right', + 15 => 'Right', + 16 => 'Far Right', + 17 => 'Lower-left', + 18 => 'Lower Near-left', + 19 => 'Lower-middle', + 20 => 'Lower Near-right', + 21 => 'Lower-right', + 22 => 'Bottom-left', + 23 => 'Bottom Near-left', + 24 => 'Bottom', + 25 => 'Bottom Near-right', + 26 => 'Bottom-right', + }, + }, + },{ #PH + Name => 'AFPointsInFocus', + Notes => 'other models', + Writable => 'int16u', + PrintHex => 1, + PrintConv => { + 0xffff => 'None', + 0 => 'Fixed Center or Multiple', #PH/14 + 1 => 'Top-left', + 2 => 'Top-center', + 3 => 'Top-right', + 4 => 'Left', + 5 => 'Center', + 6 => 'Right', + 7 => 'Bottom-left', + 8 => 'Bottom-center', + 9 => 'Bottom-right', + }, + }], + 0x0010 => { #PH + Name => 'FocusPosition', + Writable => 'int16u', + Notes => 'related to focus distance but affected by focal length', + }, + 0x0012 => { #PH + Name => 'ExposureTime', + Writable => 'int32u', + Priority => 0, + ValueConv => '$val * 1e-5', + ValueConvInv => '$val * 1e5', + # value may be 0xffffffff in Bulb mode (ref JD) + PrintConv => '$val > 42949 ? "Unknown (Bulb)" : Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => '$val=~/(unknown|bulb)/i ? $val : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x0013 => { #PH + Name => 'FNumber', + Writable => 'int16u', + Priority => 0, + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + # ISO Tag - Entries confirmed by W. Smith 12 FEB 04 + 0x0014 => { + Name => 'ISO', + Writable => 'int16u', + Notes => 'may be different than EXIF:ISO, which can round to the nearest full stop', + PrintConvColumns => 4, + PrintConv => { + # 1/3 EV step values + 3 => 50, + 4 => 64, + 5 => 80, + 6 => 100, + 7 => 125, #PH + 8 => 160, #PH + 9 => 200, + 10 => 250, + 11 => 320, #PH + 12 => 400, + 13 => 500, + 14 => 640, + 15 => 800, + 16 => 1000, + 17 => 1250, + 18 => 1600, #PH + 19 => 2000, #PH + 20 => 2500, #PH + 21 => 3200, #PH + 22 => 4000, + 23 => 5000, + 24 => 6400, #PH + 25 => 8000, #PH + 26 => 10000, #PH + 27 => 12800, #PH + 28 => 16000, #PH + 29 => 20000, #PH + 30 => 25600, #PH + 31 => 32000, #PH + 32 => 40000, #PH + 33 => 51200, #PH + 34 => 64000, #PH (NC) + 35 => 80000, #PH (NC) + 36 => 102400, #27 + 37 => 128000, #PH (NC) + 38 => 160000, #PH (NC) + 39 => 204800, #27 + 40 => 256000, #PH (NC) + 41 => 320000, #PH (NC) + 42 => 409600, #PH (NC) + 43 => 512000, #PH (NC) + 44 => 640000, #PH (NC) + 45 => 819200, #PH (KP) + # Optio 330/430 (oddball) + 50 => 50, #PH + 100 => 100, #PH + 200 => 200, #PH + 400 => 400, #PH + 800 => 800, #PH + 1600 => 1600, #PH + 3200 => 3200, #PH + # 1/2 EV step values + 258 => 50, #PH (NC) + 259 => 70, #PH (NC) + 260 => 100, #19 + 261 => 140, #19 + 262 => 200, #19 + 263 => 280, #19 + 264 => 400, #19 + 265 => 560, #19 + 266 => 800, #19 + 267 => 1100, #19 + 268 => 1600, #19 + 269 => 2200, #PH + 270 => 3200, #PH + 271 => 4500, #PH + 272 => 6400, #PH + 273 => 9000, #PH + 274 => 12800, #PH + 275 => 18000, #PH + 276 => 25600, #PH + 277 => 36000, #PH + 278 => 51200, #PH + 279 => 72000, #PH (NC) + 280 => 102400, #PH (NC) + 281 => 144000, #PH (NC) + 282 => 204800, #PH (NC) + 283 => 288000, #PH (NC) + 284 => 409600, #PH (NC) + 285 => 576000, #PH (NC) + 286 => 819200, #PH (NC) + 65534 => 'Auto 2', #PH (Q/Q10/Q7 MOV) [how is this different from 65535?] + 65535 => 'Auto', #PH/31 (K-01/K-70 MP4) + }, + }, + 0x0015 => { #PH + Name => 'LightReading', + Format => 'int16s', # (because I may have seen negative numbers) + Writable => 'int16u', + # ranges from 0-12 for my Optio WP - PH + Notes => q{ + calibrated differently for different models. For the Optio WP, add 6 to get + approximate Light Value. May not be valid for some models, eg. Optio S + }, + }, + 0x0016 => [{ #PH + Name => 'ExposureCompensation', + Condition => '$count == 1', + Notes => q{ + some models write two values here. The second value is meaning of the + second value is not yet known + }, + Writable => 'int16u', + ValueConv => '($val - 50) / 10', + ValueConvInv => 'int($val * 10 + 50.5)', + PrintConv => '$val ? sprintf("%+.1f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + },{ + Name => 'ExposureCompensation', + Writable => 'int16u', + # (2 values for K-70, etc -- have only seen "0" for the 2nd value - PH) + Count => 2, + ValueConv => '$val =~ s/ .*//; ($val - 50) / 10', + ValueConvInv => 'int($val * 10 + 50.5) . " 0"', + PrintConv => '$val ? sprintf("%+.1f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }], + 0x0017 => { #3 + Name => 'MeteringMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Multi-segment', + 1 => 'Center-weighted average', + 2 => 'Spot', + # have seen value of 16 for E70 + }, + }, + 0x0018 => { #PH + Name => 'AutoBracketing', + Writable => 'int16u', + Count => -1, + Notes => q{ + 1 or 2 values: exposure bracket step in EV, then extended bracket if + available. Extended bracket values are printed as 'WB-BA', 'WB-GM', + 'Saturation', 'Sharpness', 'Contrast', 'Hue' or 'HighLowKey' followed by + '+1', '+2' or '+3' for step size + }, + # 1=.3ev, 2=.7, 3=1.0 ... 10=.5, 11=1.5, ... 4096=0, 4097=0.5 ... 8192=0, 8193=0.3 + # (models like K-1 and K-5 use 0x1000 and 0x2000 to indicate 1/2 and 1/3 EV step + # size -- convert this as a fraction so we can recognize this format when writing) + ValueConv => [ q{ + return $val / 3 if $val < 10; + return $val - 9.5 if $val < 20; + return ($val - 0x1000) . '/2' if $val & 0x1000; + return ($val - 0x2000) . '/3' if $val & 0x2000; + return $val; # (shouldn't happen) + }], + ValueConvInv => [ q{ + if ($val =~ s{/(\d+)$}{}) { + return $val + 0x1000 if $1 == 2; + return $val + 0x2000 if $1 == 3; + return undef; + } + return abs($val-int($val)-.5)>0.05 ? int($val*3+0.5) : int($val+10); + }], + PrintConv => sub { + my @v = split(' ', shift); + $v[0] = sprintf('%.1f', $v[0]) if $v[0] and $v[0]!~m{/}; + if ($v[1]) { + my %s = (1=>'WB-BA',2=>'WB-GM',3=>'Saturation',4=>'Sharpness', + 5=>'Contrast',6=>'Hue',7=>'HighLowKey'); + my $t = $v[1] >> 8; + $v[1] = sprintf('%s+%d', $s{$t} || "Unknown($t)", $v[1] & 0xff); + } elsif (defined $v[1]) { + $v[1] = 'No Extended Bracket', + } + return join(' EV, ', @v); + }, + PrintConvInv => sub { + my @v = split(/, ?/, shift); + $v[0] =~ s/ ?EV//i; + if ($v[1]) { + my %s = ('WB-BA'=>1,'WB-GM'=>2,'Saturation'=>3,'Sharpness'=>4, + 'Contrast'=>5,'Hue'=>6,'HighLowKey'=>7); + if ($v[1] =~ /^No\b/i) { + $v[1] = 0; + } elsif ($v[1] =~ /Unknown\((\d+)\)\+(\d+)/i) { + $v[1] = ($1 << 8) + $2; + } elsif ($v[1] =~ /([\w-]+)\+(\d+)/ and $s{$1}) { + $v[1] = ($s{$1} << 8) + $2; + } else { + warn "Bad extended bracket\n"; + } + } + return "@v"; + }, + }, + 0x0019 => { #3 + Name => 'WhiteBalance', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Shade', + 3 => 'Fluorescent', #2 + 4 => 'Tungsten', + 5 => 'Manual', + 6 => 'Daylight Fluorescent', #13 + 7 => 'Day White Fluorescent', #13 + 8 => 'White Fluorescent', #13 + 9 => 'Flash', #13 + 10 => 'Cloudy', #13 + 11 => 'Warm White Fluorescent', #PH (K-3) + 14 => 'Multi Auto', #PH (K-3) + 15 => 'Color Temperature Enhancement', #PH + 17 => 'Kelvin', #PH + 0xfffe => 'Unknown', #13 + 0xffff => 'User-Selected', #13 + }, + }, + 0x001a => { #5 + Name => 'WhiteBalanceMode', + Writable => 'int16u', + PrintConv => { + 1 => 'Auto (Daylight)', + 2 => 'Auto (Shade)', + 3 => 'Auto (Flash)', + 4 => 'Auto (Tungsten)', + 6 => 'Auto (Daylight Fluorescent)', #19 (NC) + 7 => 'Auto (Day White Fluorescent)', #17 (K100D guess) + 8 => 'Auto (White Fluorescent)', #17 (K100D guess) + 10 => 'Auto (Cloudy)', #17 (K100D guess) + # 0xfffd observed in K100D (ref 17) + 0xfffe => 'Unknown', #PH (you get this when shooting night sky shots) + 0xffff => 'User-Selected', + }, + }, + 0x001b => { #6 + Name => 'BlueBalance', + Writable => 'int16u', + ValueConv => '$val / 256', + ValueConvInv => 'int($val * 256 + 0.5)', + }, + 0x001c => { #6 + Name => 'RedBalance', + Writable => 'int16u', + ValueConv => '$val / 256', + ValueConvInv => 'int($val * 256 + 0.5)', + }, + 0x001d => [ + # Would be nice if there was a general way to determine units for FocalLength... + { + # Optio 30, 33WR, 43WR, 450, 550, 555, 750Z, X + Name => 'FocalLength', + Condition => '$self->{Model} =~ /^PENTAX Optio (30|33WR|43WR|450|550|555|750Z|X)\b/', + Writable => 'int32u', + Priority => 0, + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm//;$val', + }, + { + # K100D, Optio 230, 330GS, 33L, 33LF, A10, M10, MX, MX4, S, S30, + # S4, S4i, S5i, S5n, S5z, S6, S45, S50, S55, S60, SV, Svi, W10, WP, + # *ist D, DL, DL2, DS, DS2 + # (Note: the Optio S6 seems to report the minimum focal length - PH) + Name => 'FocalLength', + Writable => 'int32u', + Priority => 0, + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm//;$val', + }, + ], + 0x001e => { #3 + Name => 'DigitalZoom', + Writable => 'int16u', + ValueConv => '$val / 100', #14 + ValueConvInv => '$val * 100', #14 + }, + 0x001f => { + Name => 'Saturation', + Writable => 'int16u', + Count => -1, + Notes => '1 or 2 values', + PrintConvColumns => 2, + PrintConv => [{ # the *istD has pairs of values - PH + 0 => '-2 (low)', #PH + 1 => '0 (normal)', #PH + 2 => '+2 (high)', #PH + 3 => '-1 (medium low)', #2 + 4 => '+1 (medium high)', #2 + 5 => '-3 (very low)', #PH + 6 => '+3 (very high)', #PH (NC) + 7 => '-4 (minimum)', #PH (NC) + 8 => '+4 (maximum)', #PH (K-5) + 65535 => 'None', #PH (Monochrome) + }], + }, + 0x0020 => { + Name => 'Contrast', + Writable => 'int16u', + Count => -1, + Notes => '1 or 2 values', + PrintConvColumns => 2, + PrintConv => [{ # the *istD has pairs of values - PH + 0 => '-2 (low)', #PH + 1 => '0 (normal)', #PH + 2 => '+2 (high)', #PH + 3 => '-1 (medium low)', #2 + 4 => '+1 (medium high)', #2 + 5 => '-3 (very low)', #PH + 6 => '+3 (very high)', #PH (NC) + 7 => '-4 (minimum)', #PH (NC) + 8 => '+4 (maximum)', #PH (K-5) + 65535 => 'n/a', # got this for a Backlight Silhouette - PH (Q) + }], + }, + 0x0021 => { + Name => 'Sharpness', + Writable => 'int16u', + Count => -1, + Notes => '1 or 2 values', + PrintConvColumns => 2, + PrintConv => [{ # the *istD has pairs of values - PH + 0 => '-2 (soft)', #PH + 1 => '0 (normal)', #PH + 2 => '+2 (hard)', #PH + 3 => '-1 (medium soft)', #2 + 4 => '+1 (medium hard)', #2 + 5 => '-3 (very soft)', #(NC) + 6 => '+3 (very hard)', #(NC) + 7 => '-4 (minimum)', #PH (NC) + 8 => '+4 (maximum)', #PH (NC) + }], + }, + 0x0022 => { #PH + Name => 'WorldTimeLocation', + Groups => { 2 => 'Time' }, + Writable => 'int16u', + PrintConv => { + 0 => 'Hometown', + 1 => 'Destination', + }, + }, + 0x0023 => { #PH + Name => 'HometownCity', + Groups => { 2 => 'Time' }, + Writable => 'int16u', + SeparateTable => 'City', + PrintConv => \%pentaxCities, + }, + 0x0024 => { #PH + Name => 'DestinationCity', + Groups => { 2 => 'Time' }, + Writable => 'int16u', + SeparateTable => 'City', + PrintConv => \%pentaxCities, + }, + 0x0025 => { #PH + Name => 'HometownDST', + Groups => { 2 => 'Time' }, + Writable => 'int16u', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x0026 => { #PH + Name => 'DestinationDST', + Groups => { 2 => 'Time' }, + Writable => 'int16u', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x0027 => { #PH + Name => 'DSPFirmwareVersion', + Writable => 'undef', + # - for K10D, this comes from 4 bytes at offset 0x1c in the firmware file + %pentaxFirmwareID, + }, + 0x0028 => { #PH + Name => 'CPUFirmwareVersion', + Writable => 'undef', + # - for K10D, this comes from 4 bytes at offset 0x83fbf8 in firmware file + %pentaxFirmwareID, + }, + 0x0029 => { #5 + Name => 'FrameNumber', + # - one report that this has a value of 84 for the first image with a *istDS + # - another report that file number 4 has frameNumber 154 for *istD, and + # that framenumber jumped at about 9700 to around 26000 + # - with *istDS firmware 2.0, this tag was removed and ShutterCount was added + Writable => 'int32u', + }, + # 0x002b - definitely exposure related somehow - PH + 0x002d => [{ #PH + Name => 'EffectiveLV', + Condition => '$format eq "int16u"', + Notes => 'camera-calculated light value, but includes exposure compensation', + Writable => 'int16u', + Format => 'int16s', # (negative values are valid even though Pentax writes int16u) + ValueConv => '$val/1024', + ValueConvInv => '$val * 1024', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + },{ + Name => 'EffectiveLV', + Condition => '$format eq "int32u"', + Writable => 'int32u', + Format => 'int32s', + ValueConv => '$val/1024', + ValueConvInv => '$val * 1024', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }], + 0x0032 => { #13 + Name => 'ImageEditing', + Writable => 'undef', + Format => 'int8u', + Count => 4, + PrintConv => { + '0 0' => 'None', #PH + '0 0 0 0' => 'None', + '0 0 0 4' => 'Digital Filter', + '1 0 0 0' => 'Resized', #PH (K-5) + '2 0 0 0' => 'Cropped', #PH + # note: doesn't apply to digital filters applied when picture is taken + '4 0 0 0' => 'Digital Filter 4', #PH (K10D, Ricoh WG-5) + '6 0 0 0' => 'Digital Filter 6', #PH (K-5) + '8 0 0 0' => 'Red-eye Correction', #PH (WG-10) + '16 0 0 0' => 'Frame Synthesis?', + }, + }, + 0x0033 => { #PH (K110D/K100D/K-m) + Name => 'PictureMode', + Writable => 'int8u', + Count => 3, + Relist => [ [0, 1], 2 ], # join values 0 and 1 for PrintConv + PrintConvColumns => 2, + PrintConv => [{ + # Program dial modes (from K110D) + '0 0' => 'Program', # (also on K10D, custom settings: Program Line 1, e-dial in Program 3, 4 or 5) + '0 1' => 'Hi-speed Program', #19 (K10D, custom settings: Program Line 2, e-dial in Program 3, 4 or 5) + '0 2' => 'DOF Program', #19 (K10D, custom settings: Program Line 3, e-dial in Program 3, 4 or 5) + '0 3' => 'MTF Program', #19 (K10D, custom settings: Program Line 4, e-dial in Program 3, 4 or 5) + '0 4' => 'Standard', #13 + '0 5' => 'Portrait', + '0 6' => 'Landscape', + '0 7' => 'Macro', + '0 8' => 'Sport', + '0 9' => 'Night Scene Portrait', + '0 10' => 'No Flash', + # SCN modes (menu-selected) (from K100D) + '0 11' => 'Night Scene', + '0 12' => 'Surf & Snow', + '0 13' => 'Text', + '0 14' => 'Sunset', + '0 15' => 'Kids', + '0 16' => 'Pet', + '0 17' => 'Candlelight', + '0 18' => 'Museum', + '0 19' => 'Food', + '0 20' => 'Stage Lighting', + '0 21' => 'Night Snap', + '0 23' => 'Blue Sky', # (Q) + '0 24' => 'Sunset', # (Q) + '0 26' => 'Night Scene HDR', # (Q) + '0 27' => 'HDR', # (Q) + '0 28' => 'Quick Macro', # (Q) + '0 29' => 'Forest', # (Q) + '0 30' => 'Backlight Silhouette', # (Q) + # AUTO PICT modes (auto-selected) + '1 4' => 'Auto PICT (Standard)', #13 + '1 5' => 'Auto PICT (Portrait)', #7 (K100D) + '1 6' => 'Auto PICT (Landscape)', # K110D + '1 7' => 'Auto PICT (Macro)', #13 + '1 8' => 'Auto PICT (Sport)', #13 + # *istD modes (ref 7) + '2 0' => 'Program (HyP)', #13 (K-5 Normal program line - PH) + '2 1' => 'Hi-speed Program (HyP)', #19 (K10D, custom settings: Program Line 2, e-dial in Program 1, 2) + '2 2' => 'DOF Program (HyP)', #19 (K10D, custom settings: Program Line 3, e-dial in Program 1, 2) + '2 3' => 'MTF Program (HyP)', #19 (K10D, custom settings: Program Line 4, e-dial in Program 1, 2) + '2 22' => 'Shallow DOF (HyP)', #PH (K-5) + '3 0' => 'Green Mode', #16 + '4 0' => 'Shutter Speed Priority', + '5 0' => 'Aperture Priority', + '6 0' => 'Program Tv Shift', + '7 0' => 'Program Av Shift', #19 + '8 0' => 'Manual', + '9 0' => 'Bulb', + '10 0' => 'Aperture Priority, Off-Auto-Aperture', + '11 0' => 'Manual, Off-Auto-Aperture', + '12 0' => 'Bulb, Off-Auto-Aperture', + '19 0' => 'Astrotracer', #29 + # extra K10D modes (ref 16) + '13 0' => 'Shutter & Aperture Priority AE', + '15 0' => 'Sensitivity Priority AE', + '16 0' => 'Flash X-Sync Speed AE', + '18 0' => 'Auto Program (Normal)', #PH (K-5) + '18 1' => 'Auto Program (Hi-speed)', #PH (NC) + '18 2' => 'Auto Program (DOF)', #PH (K-5) + '18 3' => 'Auto Program (MTF)', #PH (NC) + '18 22' => 'Auto Program (Shallow DOF)', #PH (NC) + '20 22' => 'Blur Control', #PH (Q) + '249 0' => 'Movie (TAv)', #31 + '250 0' => 'Movie (TAv, Auto Aperture)', #31 + '251 0' => 'Movie (Manual)', #31 + '252 0' => 'Movie (Manual, Auto Aperture)', #31 + '253 0' => 'Movie (Av)', #31 + '254 0' => 'Movie (Av, Auto Aperture)', #31 + '255 0' => 'Movie (P, Auto Aperture)', #31 + '255 4' => 'Video (4)', #PH (K-x,K-01) + },{ + # EV step size (ref 19) + 0 => '1/2 EV steps', + 1 => '1/3 EV steps', + }], + }, + 0x0034 => { #7/PH + Name => 'DriveMode', + Writable => 'int8u', + Count => 4, + PrintConv => [{ + 0 => 'Single-frame', # (also Interval Shooting for K-01 - PH) + 1 => 'Continuous', # (K-5 Hi) + 2 => 'Continuous (Lo)', #PH (K-5) + 3 => 'Burst', #PH (K20D) + 4 => 'Continuous (Medium)', #PH (K-3) + 255 => 'Video', #PH (K-x) + },{ + 0 => 'No Timer', + 1 => 'Self-timer (12 s)', + 2 => 'Self-timer (2 s)', + 15 => 'Video', #PH (Q MOV) + 16 => 'Mirror Lock-up', # (K-5) + 255 => 'n/a', #PH (K-x) + },{ + 0 => 'Shutter Button', # (also computer remote control - PH) + 1 => 'Remote Control (3 s delay)', #19 + 2 => 'Remote Control', #19 + 4 => 'Remote Continuous Shooting', # (K-5) + },{ + 0x00 => 'Single Exposure', + 0x01 => 'Multiple Exposure', + 0x02 => 'Composite Average', #31 + 0x03 => 'Composite Additive', #31 + 0x04 => 'Composite Bright', #31 + 0x08 => 'Interval Shooting', #31 + 0x0a => 'Interval Composite Average', #31 + 0x0b => 'Interval Composite Additive', #31 + 0x0c => 'Interval Composite Bright', #31 + 0x0f => 'Interval Movie', #PH (K-01) + 0x10 => 'HDR', #PH (645D) + 0x20 => 'HDR Strong 1', #PH (NC) (K-5) + 0x30 => 'HDR Strong 2', #PH (K-5) + 0x40 => 'HDR Strong 3', #PH (K-5) + 0x50 => 'HDR Manual', #31 (K-70 HDR 1 and HDR 2) + 0xe0 => 'HDR Auto', #PH (K-5, K-70) + 0xff => 'Video', #PH (K-x) + }], + }, + 0x0035 => { #PH + Name => 'SensorSize', + Format => 'int16u', + Count => 2, + Notes => 'includes masked pixels', + # values for various models (not sure why this is in 2um increments): + # 11894 7962 (K10D,K-m) 12012 7987 (*istDS,K100D,K110D) 12012 8019 (*istD), + # 12061 7988 (K-5) 12053 8005 (K-r,K-x) 14352 9535 (K20D,K-7) + # 22315 16711 (645) 12080 8008 (K-01) + ValueConv => 'my @a=split(" ",$val); $_/=500 foreach @a; join(" ",@a)', + ValueConvInv => 'my @a=split(" ",$val); $_*=500 foreach @a; join(" ",@a)', + PrintConv => 'sprintf("%.3f x %.3f mm", split(" ",$val))', + PrintConvInv => '$val=~s/\s*mm$//; $val=~s/\s*x\s*/ /; $val', + }, + 0x0037 => { #13 + Name => 'ColorSpace', + Writable => 'int16u', + PrintConv => { + 0 => 'sRGB', + 1 => 'Adobe RGB', + }, + }, + 0x0038 => { #5 (PEF only) + Name => 'ImageAreaOffset', + Writable => 'int16u', + Count => 2, + }, + 0x0039 => { #PH + Name => 'RawImageSize', + Writable => 'int16u', + Count => 2, + PrintConv => '$_=$val;s/ /x/;$_', + }, + 0x003c => { #7/PH + Name => 'AFPointsInFocus', + # not writable because I'm not decoding these 4 bytes fully: + # Nibble pattern: XSSSYUUU + # X = unknown (AF focused flag?, 0 or 1) + # SSS = selected AF point bitmask (0x000 or 0x7ff if unused) + # Y = unknown (observed 0,6,7,b,e, always 0 if SSS is 0x000 or 0x7ff) + # UUU = af points used + Format => 'int32u', + Notes => '*istD only', + ValueConv => '$val & 0x7ff', # ignore other bits for now + PrintConvColumns => 2, + PrintConv => { + 0 => '(none)', + BITMASK => { + 0 => 'Upper-left', + 1 => 'Top', + 2 => 'Upper-right', + 3 => 'Left', + 4 => 'Mid-left', + 5 => 'Center', + 6 => 'Mid-right', + 7 => 'Right', + 8 => 'Lower-left', + 9 => 'Bottom', + 10 => 'Lower-right', + }, + }, + }, + 0x003d => { #IB + Name => 'DataScaling', + Writable => 'int16u', + # divide by the second value of Pentax_0x0201 (WhitePoint), usually + # 8192, to get the floating point normalization factor. + # One of the examples of how this tag can be used is calculation of + # baseline exposure compensation (Adobe-style) for a PEF: + # log2(Pentax_0x007e)-14-0.5+log2(Pentax_0x003d)-13 + # or + # log2(Pentax_0x007e*(Pentax_0x003d/(2^13))/(2^14))-0.5 + # where + # makernotes:Pentax_0x003d/(2^13) is the normalization factor. (ref IB) + # - 8192 for most images, but occasionally 11571 for K100D/K110D, + # and 8289 or 8456 for the K-x (ref PH) + }, + 0x003e => { #PH + Name => 'PreviewImageBorders', + Writable => 'int8u', + Count => 4, + Notes => 'top, bottom, left, right', + }, + 0x003f => { #PH + Name => 'LensRec', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensRec' }, + }, + 0x0040 => { #PH + Name => 'SensitivityAdjust', + Writable => 'int16u', + ValueConv => '($val - 50) / 10', + ValueConvInv => '$val * 10 + 50', + PrintConv => '$val ? sprintf("%+.1f", $val) : 0', + PrintConvInv => '$val', + }, + 0x0041 => { #19 + Name => 'ImageEditCount', + Writable => 'int16u', + }, + 0x0047 => { #PH + Name => 'CameraTemperature', # (chassis temperature, ref forum6677) + Writable => 'int8s', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?c$//i; $val', + }, + 0x0048 => { #19 + Name => 'AELock', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x0049 => { #13 + Name => 'NoiseReduction', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x004d => [{ #PH + Name => 'FlashExposureComp', + Condition => '$count == 1', + Writable => 'int32s', + ValueConv => '$val / 256', + ValueConvInv => 'int($val * 256 + ($val > 0 ? 0.5 : -0.5))', + PrintConv => '$val ? sprintf("%+.1f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + },{ #PH (K-3) + Name => 'FlashExposureComp', + Writable => 'int8s', + Count => 2, # (don't know what the 2nd number is for) + ValueConv => [ '$val / 6' ], + ValueConvInv => [ '$val / 6' ], + PrintConv => [ '$val ? sprintf("%+.1f", $val) : 0' ], + PrintConvInv => [ 'Image::ExifTool::Exif::ConvertFraction($val)' ], + }], + 0x004f => { #PH + Name => 'ImageTone', # (Called CustomImageMode in K20D manual) + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Natural', + 1 => 'Bright', + 2 => 'Portrait', # (K20D/K200D) + 3 => 'Landscape', # (K20D) + 4 => 'Vibrant', # (K20D) + 5 => 'Monochrome', # (K20D) + 6 => 'Muted', # (645D) + 7 => 'Reversal Film', # (645D) (Ricoh WG-5 "Slide Film") + 8 => 'Bleach Bypass', # (K-5) + 9 => 'Radiant', # (Q) + 10 => 'Cross Processing', #31 (K-70) + 11 => 'Flat', #31 (K-70) + # 256 - seen for GR III + # 262 - seen for GR III + }, + }, + 0x0050 => { #PH + Name => 'ColorTemperature', + Writable => 'int16u', + RawConv => '$val ? $val : undef', + ValueConv => '53190 - $val', + ValueConvInv => '53190 - $val', + }, + # 0x0053-0x005a - not in JPEG images - PH + 0x0053 => { #28 + Name => 'ColorTempDaylight', + %colorTemp, + Notes => '0x0053-0x005a are 3 numbers: Kelvin, shift AB, shift GM', + }, + 0x0054 => { Name => 'ColorTempShade', %colorTemp }, #28 + 0x0055 => { Name => 'ColorTempCloudy', %colorTemp }, #28 + 0x0056 => { Name => 'ColorTempTungsten', %colorTemp }, #28 + 0x0057 => { Name => 'ColorTempFluorescentD', %colorTemp }, #28 + 0x0058 => { Name => 'ColorTempFluorescentN', %colorTemp }, #28 + 0x0059 => { Name => 'ColorTempFluorescentW', %colorTemp }, #28 + 0x005a => { Name => 'ColorTempFlash', %colorTemp }, #28 + 0x005c => [{ #PH + Name => 'ShakeReductionInfo', + Condition => '$count == 4', # (2 bytes for the K-3) + Format => 'undef', # (written as int8u) - do this just to save time converting the value + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::SRInfo' }, + },{ + Name => 'ShakeReductionInfo', + Format => 'undef', # (written as int8u) - do this just to save time converting the value + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::SRInfo2' }, + }], + 0x005d => { #JD/PH + # (used by all Pentax DSLR's except *istD and *istDS until firmware 2.0 - PH) + # Observed values for the first shot of a new K10D are: 81 [PH], 181 [19], + # 246 [7], and 209 [18 (one of the first 20 shots)], so there must be a number + # of test images shot in the factory. (But my new K-5 started at 1 - PH) + # This count includes shutter actuations even if they don't result in a + # recorded image (eg. manual white balance frame or digital preview), but + # does not include actuations due to Live View or video recording - PH + Name => 'ShutterCount', + Writable => 'undef', + Count => 4, + Notes => q{ + Note: May be reset by servicing! Also, does not include shutter actuations + for live view or video recording + }, + # raw value is a big-endian 4-byte integer, encrypted using Date and Time + RawConv => 'length($val) == 4 ? unpack("N",$val) : undef', + RawConvInv => q{ + my $val = Image::ExifTool::Pentax::CryptShutterCount($val,$self); + return pack('N', $val); + }, + ValueConv => \&CryptShutterCount, + ValueConvInv => '$val', + }, + # 0x005e - + 0x0060 => { #PH (K-5) + Name => 'FaceInfo', + Format => 'undef', # (written as int8u) + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::FaceInfo' }, + }, + 0x0062 => { #forum4803 + Name => 'RawDevelopmentProcess', + Condition => '$$self{Make} =~ /^(PENTAX|RICOH)/', # rules out Kodak, which also use this tag + Writable => 'int16u', + PrintConv => { + 1 => '1 (K10D,K200D,K2000,K-m)', + 3 => '3 (K20D)', + 4 => '4 (K-7)', + 5 => '5 (K-x)', + 6 => '6 (645D)', + 7 => '7 (K-r)', + 8 => '8 (K-5,K-5II,K-5IIs)', + 9 => '9 (Q)', + 10 => '10 (K-01,K-30,K-50,K-500)', + 11 => '11 (Q10)', + 12 => '12 (MX-1,Q-S1,Q7)', + 13 => '13 (K-3,K-3II)', + 14 => '14 (645Z)', + 15 => '15 (K-S1,K-S2)', #PH + 16 => '16 (K-1)', #PH + 17 => '17 (K-70)', #29 + 18 => '18 (KP)', #PH + 19 => '19 (GR III)', #PH + 20 => '20 (K-3III)', #PH + }, + }, + 0x0067 => { #PH (K-5) + Name => 'Hue', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 0 => -2, + 1 => 'Normal', + 2 => 2, + 3 => -1, + 4 => 1, + 5 => -3, + 6 => 3, + 7 => -4, + 8 => 4, + 65535 => 'None', # (Monochrome) + }, + }, + # 0x0067 - int16u: 1 [and 65535 in Monochrome] (K20D,K200D) - PH + 0x0068 => { #PH + Name => 'AWBInfo', + Format => 'undef', # (written as int8u) + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::AWBInfo' }, + }, + 0x0069 => { #PH (K20D, K-5, K-01 highlights only) + Name => 'DynamicRangeExpansion', + Notes => q{ + called highlight correction by Pentax for the K20D, K-5, K-01 and maybe + other models + }, + Writable => 'undef', + Format => 'int8u', + Count => 4, + PrintConv => [{ + 0 => 'Off', + 1 => 'On', + },{ + 0 => 0, + 1 => 'Enabled', # (K-01) + 2 => 'Auto', # (K-01) + }], + }, + 0x006b => { #PH (K-5) + Name => 'TimeInfo', + Format => 'undef', # (written as int8u) + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::TimeInfo' }, + }, + 0x006c => { #PH (K-5) + Name => 'HighLowKeyAdj', + Description => 'High/Low Key Adj', + Writable => 'int16s', + Count => 2, + PrintConvColumns => 3, + PrintConv => { + '-4 0' => -4, + '-3 0' => -3, + '-2 0' => -2, + '-1 0' => -1, + '0 0' => 0, + '1 0' => 1, + '2 0' => 2, + '3 0' => 3, + '4 0' => 4, + }, + }, + 0x006d => { #PH (K-5) + Name => 'ContrastHighlight', + Writable => 'int16s', + Count => 2, + PrintConvColumns => 3, + PrintConv => { + '-4 0' => -4, + '-3 0' => -3, + '-2 0' => -2, + '-1 0' => -1, + '0 0' => 0, + '1 0' => 1, + '2 0' => 2, + '3 0' => 3, + '4 0' => 4, + }, + }, + 0x006e => { #PH (K-5) + Name => 'ContrastShadow', + Writable => 'int16s', + Count => 2, + PrintConvColumns => 3, + PrintConv => { + '-4 0' => -4, + '-3 0' => -3, + '-2 0' => -2, + '-1 0' => -1, + '0 0' => 0, + '1 0' => 1, + '2 0' => 2, + '3 0' => 3, + '4 0' => 4, + }, + }, + 0x006f => { #PH (K-5) + Name => 'ContrastHighlightShadowAdj', + Description => 'Contrast Highlight/Shadow Adj', + Writable => 'int8u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x0070 => { #PH (K-5) + Name => 'FineSharpness', + Writable => 'int8u', + Count => -1, # 1 for K20/K200, 2 for K-5 + PrintConv => [{ + 0 => 'Off', + 1 => 'On', + },{ + 0 => 'Normal', + 2 => 'Extra fine', + }], + }, + 0x0071 => { #PH (K20D,K-x) + Name => 'HighISONoiseReduction', + Format => 'int8u', + PrintConv => [{ + 0 => 'Off', + 1 => 'Weakest', + 2 => 'Weak', # (called "Low" by K-x) + 3 => 'Strong', # (called "High" by K-x) + 4 => 'Medium', + 255 => 'Auto', # (K-5) + },{ + 0 => 'Inactive', + 1 => 'Active', + 2 => 'Active (Weak)', # (K-5) + 3 => 'Active (Strong)', # (K-5) + 4 => 'Active (Medium)', # (K-5) + },{ # Start ISO level for NR (K-x) + 48 => 'ISO>400', + 56 => 'ISO>800', + 64 => 'ISO>1600', + 72 => 'ISO>3200', + }], + }, + 0x0072 => { #JD (K20D) + Name => 'AFAdjustment', + Writable => 'int16s', + }, + 0x0073 => { #PH (K-5) + Name => 'MonochromeFilterEffect', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 65535 => 'None', + 1 => 'Green', + 2 => 'Yellow', + 3 => 'Orange', + 4 => 'Red', + 5 => 'Magenta', + 6 => 'Blue', + 7 => 'Cyan', + 8 => 'Infrared', + }, + }, + 0x0074 => { #PH (K-5) + Name => 'MonochromeToning', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 65535 => 'None', + 0 => -4, + 1 => -3, + 2 => -2, + 3 => -1, + 4 => 0, + 5 => 1, + 6 => 2, + 7 => 3, + 8 => 4, + }, + }, + 0x0076 => { #PH (K-5) + Name => 'FaceDetect', + Writable => 'int8u', + Count => 2, + # the Optio S12 writes this but not the FacesDetected tag, so get FacesDetected from here + DataMember => 'FacesDetected', + RawConv => '$val =~ / (\d+)/ and $$self{FacesDetected} = $1; $val', + # (the K-3 reports "On" even in phase-detect focus modes) + PrintConv => [ + '$val ? "On ($val faces max)" : "Off"', + '"$val faces detected"', + ], + PrintConvInv => [ + '$val =~ /(\d+)/ ? $1 : 0', + '$val =~ /(\d+)/ ? $1 : 0', + ], + }, + 0x0077 => { #PH (K-5) + # set by taking a picture with face detect AF, + # but it isn't reset until camera is turned off? - PH + Name => 'FaceDetectFrameSize', + Writable => 'int16u', + Count => 2, + }, + # 0x0078 - int16u[2]: '0 0' (K-5,K-7,K-r,K-x) + 0x0079 => { #PH + Name => 'ShadowCorrection', + Writable => 'int8u', + Count => -1, + PrintConvColumns => 2, + PrintConv => { + # (1 value for K-m/K2000, 2 for 645D) + 0 => 'Off', + 1 => 'On', + 2 => 'Auto 2', # (NC, WG-3) + '0 0' => 'Off', + '1 1' => 'Weak', + '1 2' => 'Normal', + '1 3' => 'Strong', + '2 4' => 'Auto', # (K-01) + }, + }, + 0x007a => { #PH + Name => 'ISOAutoParameters', + Writable => 'int8u', + Count => 2, + PrintConv => { + '1 0' => 'Slow', + '2 0' => 'Standard', + '3 0' => 'Fast', + # '1 108' - seen for GR III + # '3 84' - seen for K-3III + }, + }, + 0x007b => { #PH (K-5) + Name => 'CrossProcess', + Writable => 'int8u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'Random', + 2 => 'Preset 1', + 3 => 'Preset 2', + 4 => 'Preset 3', + 33 => 'Favorite 1', + 34 => 'Favorite 2', + 35 => 'Favorite 3', + }, + }, + 0x007d => { #PH + Name => 'LensCorr', + Format => 'undef', # (written as int8u) + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensCorr' }, + }, + 0x007e => { #IB + Name => 'WhiteLevel', # (with black level already subtracted) + Writable => 'int32u', + # 15859,15860,15864,15865,16315 (K-5 PEF/DNG only) - PH + # 3934, 3935 (Q DNG) - PH + }, + 0x007f => { #PH (K-5) + Name => 'BleachBypassToning', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + 65535 => 'n/a', #31 + 0 => 'Off', #31 + 1 => 'Green', + 2 => 'Yellow', + 3 => 'Orange', + 4 => 'Red', + 5 => 'Magenta', + 6 => 'Purple', + 7 => 'Blue', + 8 => 'Cyan', + }, + }, + 0x0080 => { #PH (Q) + Name => 'AspectRatio', + PrintConv => { + 0 => '4:3', + 1 => '3:2', + 2 => '16:9', + 3 => '1:1', + }, + }, + # 0x0081 - int8u: 0 (Q) + 0x0082 => { + Name => 'BlurControl', + Writable => 'int8u', + Count => 4, + PrintConv => [ + { + 0 => 'Off', + 1 => 'Low', + 2 => 'Medium', + 3 => 'High', + }, + undef, # 0 with BlurControl is Off, seen 0,1,3 when on (related to subject distance?) + undef, # 0 with BlurControl Off, 45 when on + undef, # always 0 + ], + }, + # 0x0083 - int8u: 0 (Q DNG) + # 0x0084 - int8u: 0 (Q) + 0x0085 => { #PH + Name => 'HDR', + Format => 'int8u', + Count => 4, + PrintConv => [{ # (K-01,K-3) + 0 => 'Off', + 1 => 'HDR Auto', + 2 => 'HDR 1', + 3 => 'HDR 2', + 4 => 'HDR 3', + 5 => 'HDR Advanced', #29 (K-1) + },{ # (K-01) + 0 => 'Auto-align Off', + 1 => 'Auto-align On', + },{ + # not sure about this - PH + # - you can set HDR "Exposure Bracket Value" with the K-3 + # - guessed from imaging-resource K-3 samples K3OUTBHDR_A{1,2,3} + 0 => 'n/a', + 4 => '1 EV', + 8 => '2 EV', + 12 => '3 EV', # (get this from K-01, but can't set EV) + }, + # (4th number is always 0) + ], + }, + # 0x0086 - int8u: 0, 111[Sport,Pet] (Q) - related to Tracking FocusMode? + # 0x0087 - int8u: 0 (Q) + 0x0087 => { #PH + Name => 'ShutterType', + Writable => 'int8u', + PrintConv => { + 0 => 'Normal', # ('Mechanical' if the camera has a mechanical shutter) + 1 => 'Electronic', # (KP) + }, + }, + 0x0088 => { #PH + Name => 'NeutralDensityFilter', + Writable => 'int8u', + Count => -1, + PrintConv => { + 0 => 'Off', + 1 => 'On', + '0 2' => 'Off (0 2)', #PH (NC, GR III) + '1 2' => 'On (1 2)', #PH (NC, GR III) + }, + }, + 0x008b => { #PH (LS465) + Name => 'ISO', + Priority => 0, + Writable => 'int32u', + }, + 0x0092 => { #31 + Name => 'IntervalShooting', + Notes => '2 numbers: 1. Shot number 2. Total number of shots', + Writable => 'int16u', + Count => 2, + PrintConv => { + '0 0' => 'Off', + OTHER => sub { + my ($val, $inv) = @_; + if ($inv) { + $val =~ tr/0-9 //dc; + } else { + $val =~ s/(\d+) (\d+)/Shot $1 of $2/; + } + return $val; + }, + }, + }, + 0x0095 => [{ #31 + Name => 'SkinToneCorrection', + Condition => '$count == 2', + Writable => 'int8s', + Count => 2, + PrintConv => { + '0 0' => 'Off', + '1 1' => 'On (type 1)', + '1 2' => 'On (type 2)', + }, + },{ + Name => 'SkinToneCorrection', + Condition => '$count == 3', + Writable => 'int8s', + Count => 3, + PrintConv => { + '0 0 0' => 'Off', + }, + }], + 0x0096 => { #31 + Name => 'ClarityControl', + Writable => 'int8s', + Count => 2, + PrintConv => { + '0 0' => 'Off', + OTHER => sub { + my ($val, $inv) = @_; + if ($inv) { + $val =~ /(\d+ -?\d+)/ and return $1; + return ("1 $val"); + } elsif ($val =~ /^1 (-?\d+)$/) { + return $1 ? sprintf('%+d', $1) : 0; + } else { + return "Unknown ($val)"; + } + }, + }, + }, + 0x0200 => { #5 + Name => 'BlackPoint', + Writable => 'int16u', + Count => 4, + }, + 0x0201 => { #5 + # (this doesn't change for different fixed white balances in JPEG images: Daylight, + # Tungsten, Kelvin, etc -- always "8192 8192 8192 8192", but it varies for these in + # RAW images, all images in Auto, for different Manual WB settings, and for images + # taken via Pentax Remote Assistant) - PH + Name => 'WhitePoint', + Writable => 'int16u', + Count => 4, + }, + # 0x0202: int16u[4]: all 0's in all my samples + 0x0203 => { #JD (not really sure what these mean) + Name => 'ColorMatrixA', # (camera RGB to sRGB matrix, *ist D, ref IB) + Writable => 'int16s', + Count => 9, + ValueConv => 'join(" ",map({ $_/8192 } split(" ",$val)))', + ValueConvInv => 'join(" ",map({ int($_*8192 + ($_<0?-0.5:0.5)) } split(" ",$val)))', + PrintConv => 'join(" ",map({sprintf("%.5f",$_)} split(" ",$val)))', + PrintConvInv => '"$val"', + }, + 0x0204 => { #JD + Name => 'ColorMatrixB', # (camera RGB to Adobe RGB matrix, *ist D, ref IB) + Writable => 'int16s', + Count => 9, + ValueConv => 'join(" ",map({ $_/8192 } split(" ",$val)))', + ValueConvInv => 'join(" ",map({ int($_*8192 + ($_<0?-0.5:0.5)) } split(" ",$val)))', + PrintConv => 'join(" ",map({sprintf("%.5f",$_)} split(" ",$val)))', + PrintConvInv => '"$val"', + }, + 0x0205 => [{ #19 + Name => 'CameraSettings', + # size: *istD/*istDs/K100D/K110D=16, K-m/K2000=14, K-7/K-x=19, + # K200D/K20D/K-5/645D=20, K-r=21, K10D=22, K-01=25 + Condition => '$count < 25', # (not valid for the K-01) + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::CameraSettings', + ByteOrder => 'BigEndian', + }, + },{ + Name => 'CameraSettingsUnknown', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::CameraSettingsUnknown', + ByteOrder => 'BigEndian', + }, + }], + 0x0206 => [{ #PH + Name => 'AEInfo', + # size: *istD/*istDs/K100D/K110D=14, K10D/K200D/K20D=16, K-m/K2000=20, + # K-7/K-x=24, K-5/K-r/645D=25 + Condition => '$count <= 25 and $count != 21 and $$self{AEInfoSize} = $count', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::AEInfo' }, + },{ + Name => 'AEInfo2', + # size: K-01=21 + Condition => '$count == 21', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::AEInfo2' }, + },{ + Name => 'AEInfo3', + # size: K-30=48 + Condition => '$count == 48', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::AEInfo3' }, + },{ + Name => 'AEInfoUnknown', + # size: Q/Q10=34 + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::AEInfoUnknown' }, + }], + 0x0207 => [ #PH + { + Name => 'LensInfo', + # the *ist series (and Samsung GX-1) always use the old format, and all + # other models but the K100D, K110D and K100D Super always use the newer + # format, and for the K110D/K110D we expect ff or 00 00 at byte 20 if + # it is the old format.) + Condition => q{ + $$self{Model}=~/(\*ist|GX-1[LS])/ or + ($$self{Model}=~/(K100D|K110D)/ and $$valPt=~/^.{20}(\xff|\0\0)/s) + }, + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensInfo' }, + },{ + Name => 'LensInfo', + Condition => '$count != 90 and $count != 91 and $count != 80 and $count != 128 and $count != 168', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensInfo2' }, + },{ + Name => 'LensInfo', # 645D + Condition => '$count == 90', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensInfo3' }, + },{ + Name => 'LensInfo', # K-r, K-5, K-5II + Condition => '$count == 91', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensInfo4' }, + },{ + Name => 'LensInfo', # K-01, K-30, K-50, K-500, K-3, K-3II + Condition => '$count == 80 or $count == 128', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensInfo5' }, + } + # ($count == 168 - Ricoh GR III) + ], + 0x0208 => [ #PH + { + Name => 'FlashInfo', + Condition => '$count == 27', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::FlashInfo' }, + }, + { + Name => 'FlashInfoUnknown', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::FlashInfoUnknown' }, + }, + ], + 0x0209 => { #PH + Name => 'AEMeteringSegments', + Format => 'int8u', + Count => -1, + Notes => q{ + measurements from each of the 16 AE metering segments for models such as the + K10D, 77 metering segments for models such as the K-5, and 4050 metering + segments for the K-3, converted to LV + }, + %convertMeteringSegments, + # 16 metering segment 77 metering segment + # locations (ref JD, K10D) locations (ref PH, K-5) + # +-------------------------+ + # | 14 | +----------------------------------+ + # | +---+---+---+---+ | | 0 1 2 3 4 5 6 7 8 9 10 | + # | | 5 | 3/1\ 2| 4 | | | 11 12 13 14 15 16 17 18 19 20 21 | + # | +-+-+-+-+ - +-+-+-+-+ | | 22 23 24 25 26 27 28 29 30 31 32 | + # +--+ 9 | 7 ||0|| 6 | 8 +--+ | 33 34 35 36 37 38 39 40 41 42 43 | + # | +-+-+-+-+ - +-+-+-+-+ | | 44 45 46 47 48 49 50 51 52 53 54 | + # | |13 |11\ /10|12 | | | 55 56 57 58 59 60 61 62 63 64 65 | + # | +---+---+---+---+ | | 66 67 68 69 70 71 72 73 74 75 76 | + # | 15 | +----------------------------------+ + # +-------------------------+ + }, + 0x020a => { #PH/JD/19 + Name => 'FlashMeteringSegments', + Format => 'int8u', + Count => -1, + %convertMeteringSegments, + }, + 0x020b => { #PH/JD/19 + Name => 'SlaveFlashMeteringSegments', + Format => 'int8u', + Count => -1, + Notes => 'used in wireless control mode', + %convertMeteringSegments, + }, + 0x020d => { #PH + Name => 'WB_RGGBLevelsDaylight', + Writable => 'int16u', + Count => 4, + }, + 0x020e => { #PH + Name => 'WB_RGGBLevelsShade', + Writable => 'int16u', + Count => 4, + }, + 0x020f => { #PH + Name => 'WB_RGGBLevelsCloudy', + Writable => 'int16u', + Count => 4, + }, + 0x0210 => { #PH + Name => 'WB_RGGBLevelsTungsten', + Writable => 'int16u', + Count => 4, + }, + 0x0211 => { #PH + Name => 'WB_RGGBLevelsFluorescentD', + Writable => 'int16u', + Count => 4, + }, + 0x0212 => { #PH + Name => 'WB_RGGBLevelsFluorescentN', + Writable => 'int16u', + Count => 4, + }, + 0x0213 => { #PH + Name => 'WB_RGGBLevelsFluorescentW', + Writable => 'int16u', + Count => 4, + }, + 0x0214 => { #PH + Name => 'WB_RGGBLevelsFlash', + Writable => 'int16u', + Count => 4, + }, + 0x0215 => { #PH + Name => 'CameraInfo', + Format => 'undef', # (written as int32u) + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::CameraInfo' }, + }, + 0x0216 => { #PH + Name => 'BatteryInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::BatteryInfo', + ByteOrder => 'BigEndian', # have seen makernotes changed to little-endian in DNG! + }, + }, + # 0x021a - undef[1068] (K-5) - ToneMode/Saturation mapping matrices (ref 28) + 0x021b => { #19 + Name => 'SaturationInfo', + Flags => [ 'Unknown', 'Binary' ], + Writable => 0, + Notes => 'only in PEF and DNG images', + # K10D values with various Saturation settings (ref 19): + # Very Low: 000000022820f9a0fe4000802660f92002e0fee01e402c40f880fb40ffc02b20f52002e0fe401ee0 + # Low: 000000022ae0f700fe20ff402840f88001e0fcc021602f60f560fb40fe602d20f48001c0fbc02280 + # Med Low: 000000022dc0f420fe20fe002a20f7e000c0fa8024c032c0f220fb20fce02f60f3c000a0f9202640 + # Normal: 000000023120f0e0fe00fc802c40f740ffa0f7e028803660ee80fb20fb4031c0f300ff60f6202a80 + # Med High: 0000000234e0ed40fde0fae02ea0f680fe60f5002ca03a80ea80fb00f9603480f220fe00f2e02f20 + # High: 0000000238c0e960fde0f9203140f5a0fce0f1e031403f00e600fb00f7803760f120fc60ef403460 + # Very High:000000023d20e520fdc0f7203420f4c0fb60ee6036404400e120fae0f5403aa0f020fac0eb403a00 + }, + 0x021c => { #IB + Name => 'ColorMatrixA2', + Format => 'int16s', + Writable => 'undef', + Count => 9, + }, + 0x021d => { #IB + Name => 'ColorMatrixB2', + Format => 'int16s', + Writable => 'undef', + Count => 9, + }, + # 0x021e - undef[8] (K-5, Q) + 0x021f => { #JD + Name => 'AFInfo', + SubDirectory => { + # NOTE: Most of these subdirectories are 'undef' format, and as such the + # byte ordering is not changed when changed via the Pentax software (which + # will write a little-endian TIFF on an Intel system). So we must define + # BigEndian byte ordering for any of these which contain multi-byte values. - PH + ByteOrder => 'BigEndian', + TagTable => 'Image::ExifTool::Pentax::AFInfo', + }, + }, + 0x0220 => { #6 + Name => 'HuffmanTable', + Flags => [ 'Unknown', 'Binary' ], + Writable => 0, + Notes => 'found in K10D, K20D and K2000 PEF images', + }, + 0x0221 => { #28 + Name => 'KelvinWB', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::KelvinWB' }, + }, + 0x0222 => { #PH + Name => 'ColorInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::ColorInfo' }, + }, + # 0x0223 - undef[198] (K-5 PEF/DNG only) + 0x0224 => { #19 + Name => 'EVStepInfo', + Drop => 200, # drop if larger than 200 bytes (40 kB in Pentax Q and Q10) + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::EVStepInfo' }, + }, + 0x0226 => { #PH + Name => 'ShotInfo', # (may want to change this later when more is decoded) + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::ShotInfo' }, + }, + 0x0227 => { #PH + Name => 'FacePos', + Condition => '$$self{FacesDetected}', # ignore if no faces to decode + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::FacePos' }, + }, + 0x0228 => { #PH + Name => 'FaceSize', + Condition => '$$self{FacesDetected}', # ignore if no faces to decode + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::FaceSize' }, + }, + 0x0229 => { #PH (verified) (K-m, K-x, K-7) + Name => 'SerialNumber', + Writable => 'string', + Notes => 'left blank by some cameras', + }, + 0x022a => [{ #PH (RICOH models (GR III)) + Name => 'FilterInfo', + Condition => '$$self{Make} =~ /^RICOH/', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::FilterInfo', + ByteOrder => 'LittleEndian', + }, + },{ #PH (K-5) + Name => 'FilterInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::FilterInfo', + ByteOrder => 'BigEndian', + }, + }], + 0x022b => { #PH (K-5) + Name => 'LevelInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LevelInfo' }, + }, + # 0x022c - undef[46] (K-5) + 0x022d => { #28 + Name => 'WBLevels', + Condition => '$count == 100', # (just to be safe, but no other counts observed) + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::WBLevels' }, + }, + 0x022e => { #PH (K-5 AVI videos) + Name => 'Artist', + Groups => { 2 => 'Author' }, + Writable => 'string', + }, + 0x022f => { #PH (K-5 AVI videos) + Name => 'Copyright', + Groups => { 2 => 'Author' }, + Writable => 'string', + }, + 0x0230 => { #PH (K-x AVI videos) (and K-70/Q-S1 MOV videos, ref 31) + Name => 'FirmwareVersion', + Notes => 'only in videos', + # this tag only exists in AVI/MOV videos, and for the K-x the value of + # this tag is "K-x Ver 1.00", which is the same as the EXIF Software + # tag. I used a different tag name for this because Pentax uses the + # AVI Software tag for a different string, "PENTAX K-x". + Writable => 'string', + }, + 0x0231 => { #PH (K-5) + Name => 'ContrastDetectAFArea', + Writable => 'int16u', + Count => 4, + Notes => q{ + AF area of the most recent contrast-detect focus operation. Coordinates + are left, top, width and height in a 720x480 frame, with Y downwards + }, + }, + 0x0235 => { #PH (K-5) + Name => 'CrossProcessParams', + # (it would be interesting to know exactly what these mean) + Writable => 'undef', + Format => 'int8u', + Count => 10, + }, + # 0x0236 - undef[52] (Q) + # 0x0237 - undef[11] possibly related to smart effect setting? (Q) + # 0x0238 - undef[9] (Q) + 0x0239 => { #PH + Name => 'LensInfoQ', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensInfoQ' }, + }, + # 0x023a - undef[10] (Q) + # 0x023b - undef[9] (K-01) + # 01a700500000000000, 91a700500000000000, 41a700500000000000, 002700500000000000 + # c00500400000000000, 400500500000000000, 4004ff420100000000, 4087ff480000000000 + 0x023f => { #31 (K-70 MOV videos) + Name => 'Model', + Description => 'Camera Model Name', + Writable => 'string', + }, + 0x0243 => { #PH + Name => 'PixelShiftInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::PixelShiftInfo' }, + }, + 0x0245 => { #29 + Name => 'AFPointInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::AFPointInfo' }, + }, + 0x03fe => { #PH + Name => 'DataDump', + Writable => 0, + PrintConv => '\$val', + }, + 0x03ff => [ #PH + { + Name => 'TempInfo', + Condition => '$$self{Model} =~ /K-(01|3|30|5|50|500)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::TempInfo' }, + },{ + Name => 'UnknownInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::UnknownInfo' }, + }, + ], + 0x0402 => { #5 + Name => 'ToneCurve', + PrintConv => '\$val', + }, + 0x0403 => { #5 + Name => 'ToneCurves', + PrintConv => '\$val', + }, + # 0x0404 - undef[2086] (K-5) + 0x0405 => { #PH - undef[24200] (K-5 PEF/DNG only), undef[28672] (Q DNG) + Name => 'UnknownBlock', + Writable => 'undef', + Notes => 'large unknown data block in PEF/DNG images but not JPG images', + Flags => [ 'Unknown', 'Binary', 'Drop' ], + }, + # 0x0406 - undef[4116] (K-5) + # 0x0407 - undef[3072] (Q DNG) + # 0x0408 - undef[1024] (Q DNG) + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, +); + +# shake reduction information (ref PH) +%Image::ExifTool::Pentax::SRInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Shake reduction information.', + 0 => { + Name => 'SRResult', + PrintConv => { #PH/JD + 0 => 'Not stabilized', + BITMASK => { + 0 => 'Stabilized', + # have seen 1 and 4 for 0.5 and 0.3 sec exposures with NR on and Bit 0 also set - ref 19 + # have seen bits 1,2,3,4 in K-5 AVI videos - PH + 6 => 'Not ready', + }, + }, + }, + 1 => { + Name => 'ShakeReduction', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 4 => 'Off (4)', # (K20D, K200D, K-7, K-5) + 5 => 'On but Disabled', # (K20D, K-5) + # (can be 5 "On but Disabled" for K-5 with HDR [auto-align off only], + # Composition Adjust, DriveMode = Self-timer or Remote, and movie with SR off!) + 6 => 'On (Video)', # (K-7) + 7 => 'On (7)', #(NC) (K20D, K200D, K-m, K-5) + 15 => 'On (15)', # (K20D with Tamron 10-20mm @ 10mm) + 39 => 'On (mode 2)', # (K-01) (on during capture and live view) + 135 => 'On (135)', # (K-5IIs) + 167 => 'On (mode 1)', # (K-01) (on during capture only) + }, + }, + 2 => { + Name => 'SRHalfPressTime', + # (was SR_SWSToSWRTime: SWS=photometering switch, SWR=shutter release switch) + # (from http://www.patentstorm.us/patents/6597867-description.html) + # (here, SR could more accurately mean Shutter Release, not Shake Reduction) + # (not valid for K-01 - PH) + Notes => q{ + time from when the shutter button was half pressed to when the shutter was + released, including time for focusing. Not valid for some models + }, + # (constant of 60 determined from times: 2sec=127; 3sec=184,197; 4sec=244,249,243,246 - PH) + ValueConv => '$val / 60', + ValueConvInv => 'my $v=$val*60; $v < 255 ? int($v + 0.5) : 255', + PrintConv => 'sprintf("%.2f s",$val) . ($val > 254.5/60 ? " or longer" : "")', + PrintConvInv => '$val=~tr/0-9.//dc; $val', + }, + 3 => { #JD + Name => 'SRFocalLength', + ValueConv => '$val & 0x01 ? $val * 4 : $val / 2', + ValueConvInv => '$val <= 127 ? int($val) * 2 : int($val / 4) | 0x01', + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm//;$val', + }, +); + +# shake reduction information for the K-3 (ref PH) +%Image::ExifTool::Pentax::SRInfo2 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Shake reduction information for the K-3.', + 0 => { + Name => 'SRResult', + Unknown => 1, + PrintConv => { BITMASK => { + # Bit 0 - have seen this set in a few Pentax samples - PH + # Bit 6 - usually set when SR is Off, and occasionally when On - PH + # Bit 7 - set when AA simulation is on - PH + }}, + }, + 1 => { + Name => 'ShakeReduction', + PrintConv => { #forum5425 + 0 => 'Off', # (NC for K-3) + 1 => 'On', # (NC for K-3) + 4 => 'Off (AA simulation off)', + 5 => 'On but Disabled', # (NC for K-3) + 6 => 'On (Video)', # (NC for K-3) + 7 => 'On (AA simulation off)', + 8 => 'Off (AA simulation type 1) (8)', #forum8362 (K-70) + 12 => 'Off (AA simulation type 1)', # (AA linear motion) + 15 => 'On (AA simulation type 1)', # (AA linear motion) + 16 => 'Off (AA simulation type 2) (16)', #forum8362 (K-70) + 20 => 'Off (AA simulation type 2)', # (AA circular motion) + 23 => 'On (AA simulation type 2)', # (AA circular motion) + }, + }, +); + +# face detection information (ref PH, K-5) +%Image::ExifTool::Pentax::FaceInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0 ], + 0 => { + Name => 'FacesDetected', + RawConv => '$$self{FacesDetected} = $val', + }, + 2 => { + Name => 'FacePosition', + Notes => q{ + X/Y coordinates of the center of the main face in percent of frame size, + with positive Y downwards + }, + Format => 'int8u[2]', + }, +); + +# automatic white balance settings (ref PH, K-5) +%Image::ExifTool::Pentax::AWBInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # 0 - always 1? + # (data ends here for the K20D, K200D, K-x and K-7) + 0 => { + Name => 'WhiteBalanceAutoAdjustment', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 1 => { # (exists only for K-5) + Name => 'TungstenAWB', + PrintConv => { + 0 => 'Subtle Correction', + 1 => 'Strong Correction', + }, + }, +); + +# world time settings (ref PH, K-5) +%Image::ExifTool::Pentax::TimeInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Time' }, + 0.1 => { + Name => 'WorldTimeLocation', + Mask => 0x01, + PrintConv => { + 0 => 'Hometown', + 1 => 'Destination', + }, + }, + 0.2 => { + Name => 'HometownDST', + Mask => 0x02, + PrintConv => \%noYes, + }, + 0.3 => { + Name => 'DestinationDST', + Mask => 0x04, + PrintConv => \%noYes, + }, + 2 => { + Name => 'HometownCity', + SeparateTable => 'City', + PrintConv => \%pentaxCities, + }, + 3 => { + Name => 'DestinationCity', + SeparateTable => 'City', + PrintConv => \%pentaxCities, + }, +); + +# lens distortion correction (ref PH, K-5) +%Image::ExifTool::Pentax::LensCorr = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => { + Name => 'DistortionCorrection', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 1 => { + Name => 'ChromaticAberrationCorrection', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 2 => { + Name => 'PeripheralIlluminationCorr', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 3 => { + Name => 'DiffractionCorrection', + PrintConv => { 0 => 'Off', 16 => 'On' }, + }, +); + +# camera settings (ref 19) +%Image::ExifTool::Pentax::CameraSettings = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PRIORITY => 0, + NOTES => 'Camera settings information written by Pentax DSLR cameras.', + 0 => { + Name => 'PictureMode2', + PrintConv => { + 0 => 'Scene Mode', #PH + 1 => 'Auto PICT', #PH (NC) + 2 => 'Program AE', + 3 => 'Green Mode', + 4 => 'Shutter Speed Priority', + 5 => 'Aperture Priority', + 6 => 'Program Tv Shift', #PH + 7 => 'Program Av Shift', + 8 => 'Manual', #PH + 9 => 'Bulb', #PH + 10 => 'Aperture Priority, Off-Auto-Aperture', #PH (NC) + 11 => 'Manual, Off-Auto-Aperture', #PH + 12 => 'Bulb, Off-Auto-Aperture', #PH (NC) + 13 => 'Shutter & Aperture Priority AE', + 15 => 'Sensitivity Priority AE', + 16 => 'Flash X-Sync Speed AE', #PH + }, + }, + 1.1 => { + Name => 'ProgramLine', + # only set to other than Normal when in Program AE mode + Mask => 0x03, + PrintConv => { + 0 => 'Normal', + 1 => 'Hi Speed', + 2 => 'Depth', + 3 => 'MTF', + }, + }, + 1.2 => { # (K10D, K-5) + Name => 'EVSteps', + Mask => 0x20, + PrintConv => { + 0 => '1/2 EV Steps', + 1 => '1/3 EV Steps', + }, + }, + 1.3 => { # (this bit is set for movies with the K-5 - PH) + Name => 'E-DialInProgram', + # always set even when not in Program AE mode + Mask => 0x40, + PrintConv => { + 0 => 'Tv or Av', + 1 => 'P Shift', + }, + }, + 1.4 => { # (K10D, K-5) + Name => 'ApertureRingUse', + # always set even Aperture Ring is in A mode + Mask => 0x80, + PrintConv => { + 0 => 'Prohibited', + 1 => 'Permitted', + }, + }, + 2 => { + Name => 'FlashOptions', + Notes => 'the camera flash options settings, set even if the flash is off', + Mask => 0xf0, + # Note: These tags correlate with the FlashMode and InternalFlashMode values, + # and match what is displayed by the Pentax software + PrintConv => { + 0 => 'Normal', # (this value can occur in Green Mode) - ref 19 + 1 => 'Red-eye reduction', # (this value can occur in Green Mode) - ref 19 + 2 => 'Auto', # (this value can occur in other than Green Mode) - ref 19 + 3 => 'Auto, Red-eye reduction', #PH (this value can occur in other than Green Mode) - ref 19 + 5 => 'Wireless (Master)', + 6 => 'Wireless (Control)', + 8 => 'Slow-sync', + 9 => 'Slow-sync, Red-eye reduction', + 10 => 'Trailing-curtain Sync' + }, + }, + 2.1 => { + Name => 'MeteringMode2', + Mask => 0x0f, + Notes => 'may not be valid for some models, eg. *ist D', + PrintConv => { + 0 => 'Multi-segment', + BITMASK => { + 0 => 'Center-weighted average', + 1 => 'Spot', + }, + }, + }, + 3 => { + Name => 'AFPointMode', + Mask => 0xf0, + PrintConv => { + 0 => 'Auto', + BITMASK => { + 0 => 'Select', + 1 => 'Fixed Center', + # have seen bit 2 set in pre-production images (firmware 0.20) - PH + }, + }, + }, + 3.1 => { + Name => 'FocusMode2', + Mask => 0x0f, + PrintConv => { + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', #PH + }, + }, + 4 => { + Name => 'AFPointSelected2', + Format => 'int16u', + PrintConv => { + 0 => 'Auto', + BITMASK => { + 0 => 'Upper-left', + 1 => 'Top', + 2 => 'Upper-right', + 3 => 'Left', + 4 => 'Mid-left', + 5 => 'Center', + 6 => 'Mid-right', + 7 => 'Right', + 8 => 'Lower-left', + 9 => 'Bottom', + 10 => 'Lower-right', + }, + }, + }, + 6 => { + Name => 'ISOFloor', #PH + # manual ISO or minimum ISO in Auto ISO mode - PH + ValueConv => 'int(100*exp(Image::ExifTool::Pentax::PentaxEv($val-32)*log(2))+0.5)', + ValueConvInv => 'Image::ExifTool::Pentax::PentaxEvInv(log($val/100)/log(2))+32', + }, + 7 => { + Name => 'DriveMode2', + PrintConv => { + 0 => 'Single-frame', + BITMASK => { + 0 => 'Continuous', # (K-5 Hi) + 1 => 'Continuous (Lo)', #PH (K-5) + 2 => 'Self-timer (12 s)', #PH + 3 => 'Self-timer (2 s)', #PH + 4 => 'Remote Control (3 s delay)', + 5 => 'Remote Control', + 6 => 'Exposure Bracket', #PH/19 + 7 => 'Multiple Exposure', + }, + }, + }, + 8 => { + Name => 'ExposureBracketStepSize', + # This is set even when Exposure Bracket is Off (and the K10D + # displays "---" as the step size when you press the EB button) - DaveN + # because the last value is remembered and if you turn Exposure Bracket + # on the step size goes back to what it was before. + PrintConv => { + 3 => '0.3', + 4 => '0.5', + 5 => '0.7', + 8 => '1.0', #PH + 11 => '1.3', + 12 => '1.5', + 13 => '1.7', #(NC) + 16 => '2.0', #PH + }, + }, + 9 => { #PH/19 + Name => 'BracketShotNumber', + PrintHex => 1, + PrintConv => { + 0 => 'n/a', + 0x02 => '1 of 2', #PH (K-5) + 0x12 => '2 of 2', #PH (K-5) + 0x03 => '1 of 3', + 0x13 => '2 of 3', + 0x23 => '3 of 3', + 0x05 => '1 of 5', + 0x15 => '2 of 5', + 0x25 => '3 of 5', + 0x35 => '4 of 5', + 0x45 => '5 of 5', + }, + }, + 10 => { + Name => 'WhiteBalanceSet', + Mask => 0xf0, + # Not necessarily the white balance used; for example if the custom menu is set to + # "WB when using flash" -> "2 Flash", then this tag reports the camera setting while + # tag 0x0019 reports Flash if the Flash was used. + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Shade', + 3 => 'Cloudy', + 4 => 'Daylight Fluorescent', + 5 => 'Day White Fluorescent', + 6 => 'White Fluorescent', + 7 => 'Tungsten', + 8 => 'Flash', + 9 => 'Manual', + # The three Set Color Temperature settings refer to the 3 preset settings which + # can be saved in the menu (see page 123 of the K10D manual) + 12 => 'Set Color Temperature 1', + 13 => 'Set Color Temperature 2', + 14 => 'Set Color Temperature 3', + }, + }, + 10.1 => { + Name => 'MultipleExposureSet', + Mask => 0x0f, + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 13 => { + Name => 'RawAndJpgRecording', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only', + # this is actually a bit field: - PH + # bit 0=JPEG, bit 2=PEF, bit 3=DNG; high nibble: 0x00=best, 0x20=better, 0x40=good + PrintHex => 1, + PrintConv => { + 0x01 => 'JPEG (Best)', #PH + 0x04 => 'RAW (PEF, Best)', + 0x05 => 'RAW+JPEG (PEF, Best)', + 0x08 => 'RAW (DNG, Best)', #PH (NC) + 0x09 => 'RAW+JPEG (DNG, Best)', #PH (NC) + 0x21 => 'JPEG (Better)', #PH + 0x24 => 'RAW (PEF, Better)', + 0x25 => 'RAW+JPEG (PEF, Better)', #PH + 0x28 => 'RAW (DNG, Better)', #PH + 0x29 => 'RAW+JPEG (DNG, Better)', #PH (NC) + 0x41 => 'JPEG (Good)', + 0x44 => 'RAW (PEF, Good)', #PH (NC) + 0x45 => 'RAW+JPEG (PEF, Good)', #PH (NC) + 0x48 => 'RAW (DNG, Good)', #PH (NC) + 0x49 => 'RAW+JPEG (DNG, Good)', + # have seen values of 0,2,34 for other models (not K10D) - PH + }, + }, + 14.1 => { #PH + Name => 'JpgRecordedPixels', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only', + Mask => 0x03, + PrintConv => { + 0 => '10 MP', + 1 => '6 MP', + 2 => '2 MP', + }, + }, + 14.2 => { #PH (K-5) + Name => 'LinkAEToAFPoint', + Condition => '$$self{Model} =~ /K-5\b/', + Notes => 'K-5 only', + Mask => 0x01, + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 14.3 => { #PH (K-5) + Name => 'SensitivitySteps', + Condition => '$$self{Model} =~ /K-5\b/', + Notes => 'K-5 only', + Mask => 0x02, + PrintConv => { + 0 => '1 EV Steps', + 1 => 'As EV Steps', + }, + }, + 14.4 => { #PH (K-5) + Name => 'ISOAuto', + Condition => '$$self{Model} =~ /K-5\b/', + Notes => 'K-5 only', + Mask => 0x04, + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + # 14.5 Mask 0x80 - changes for K-5 + 16 => { + Name => 'FlashOptions2', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only; set even if the flash is off', + Mask => 0xf0, + # Note: the Normal and Auto values (0x00 to 0x30) do not tags always + # correlate with the FlashMode, InternalFlashMode and FlashOptions values + # however, these values seem to better match the K10D's actual functionality + # (always Auto in Green mode always Normal otherwise if one of the other options + # isn't selected) - ref 19 + # (these tags relate closely to InternalFlashMode values - PH) + PrintConv => { + 0 => 'Normal', # (this value never occurs in Green Mode) - ref 19 + 1 => 'Red-eye reduction', # (this value never occurs in Green Mode) - ref 19 + 2 => 'Auto', # (this value only occurs in Green Mode) - ref 19 + 3 => 'Auto, Red-eye reduction', # (this value only occurs in Green Mode) - ref 19 + 5 => 'Wireless (Master)', + 6 => 'Wireless (Control)', + 8 => 'Slow-sync', + 9 => 'Slow-sync, Red-eye reduction', + 10 => 'Trailing-curtain Sync' + }, + }, + 16.1 => { + Name => 'MeteringMode3', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only', + Mask => 0x0f, + PrintConv => { + 0 => 'Multi-segment', + BITMASK => { + 0 => 'Center-weighted average', + 1 => 'Spot', + }, + }, + }, + # 16 Mask 0x0f - changes when changing EV steps? (K-5) + 17.1 => { + Name => 'SRActive', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => q{ + K10D only; SR is active only when ShakeReduction is On, DriveMode is not + Remote or Self-timer, and Internal/ExternalFlashMode is not "On, Wireless" + }, + Mask => 0x80, + PrintConv => \%noYes, + }, + 17.2 => { + Name => 'Rotation', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only', + Mask => 0x60, + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 180', + 2 => 'Rotate 90 CW', + 3 => 'Rotate 270 CW', + }, + }, + # Bit 0x08 is set on 3 of my 3000 shots to (All 3 were Shutter Priority + # but this may not mean anything with such a small sample) - ref 19 + 17.3 => { + Name => 'ISOSetting', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only', + Mask => 0x04, + PrintConv => { + 0 => 'Manual', + 1 => 'Auto', + }, + }, + 17.4 => { + Name => 'SensitivitySteps', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only', + Mask => 0x02, + PrintConv => { + 0 => '1 EV Steps', + 1 => 'As EV Steps', + }, + }, + # 17 Mask 0x08 - changed when changing Auto ISO range (K-5) + 18 => { + Name => 'TvExposureTimeSetting', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only', + ValueConv => 'exp(-Image::ExifTool::Pentax::PentaxEv($val-68)*log(2))', + ValueConvInv => 'Image::ExifTool::Pentax::PentaxEvInv(-log($val)/log(2))+68', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 19 => { + Name => 'AvApertureSetting', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only', + ValueConv => 'exp(Image::ExifTool::Pentax::PentaxEv($val-68)*log(2)/2)', + ValueConvInv => 'Image::ExifTool::Pentax::PentaxEvInv(log($val)*2/log(2))+68', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 20 => { #PH + Name => 'SvISOSetting', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only', + # ISO setting for sensitivity-priority mode + # (conversion may not give actual displayed values:) + # 32 => 100, 35 => 125, 36 => 140, 37 => 160, + # 40 => 200, 43 => 250, 44 => 280, 45 => 320, + # 48 => 400, 51 => 500, 52 => 560, 53 => 640, + # 56 => 800, 59 => 1000,60 => 1100,61 => 1250, 64 => 1600 + ValueConv => 'int(100*exp(Image::ExifTool::Pentax::PentaxEv($val-32)*log(2))+0.5)', + ValueConvInv => 'Image::ExifTool::Pentax::PentaxEvInv(log($val/100)/log(2))+32', + }, + 21 => { #PH + Name => 'BaseExposureCompensation', + Condition => '$$self{Model} =~ /(K10D|GX10)\b/', + Notes => 'K10D only; exposure compensation without auto bracketing', + ValueConv => 'Image::ExifTool::Pentax::PentaxEv(64-$val)', + ValueConvInv => '64-Image::ExifTool::Pentax::PentaxEvInv($val)', + PrintConv => '$val ? sprintf("%+.1f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, +); + +# unknown camera settings (K-01) +%Image::ExifTool::Pentax::CameraSettingsUnknown = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'This information has not yet been decoded for models such as the K-01.', +); + +# auto-exposure information (ref PH) +%Image::ExifTool::Pentax::AEInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 7 ], + NOTES => 'Auto-exposure information for most Pentax models.', + # instead of /8, should these be PentaxEv(), as in CameraSettings? - PH + 0 => { + Name => 'AEExposureTime', + Notes => 'val = 24 * 2**((32-raw)/8)', + ValueConv => '24*exp(-($val-32)*log(2)/8)', + ValueConvInv => '-log($val/24)*8/log(2)+32', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 1 => { + Name => 'AEAperture', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 2 => { + Name => 'AE_ISO', + Notes => 'val = 100 * 2**((raw-32)/8)', + ValueConv => '100*exp(($val-32)*log(2)/8)', + ValueConvInv => 'log($val/100)*8/log(2)+32', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 3 => { + Name => 'AEXv', + Notes => 'val = (raw-64)/8', + ValueConv => '($val-64)/8', + ValueConvInv => '$val * 8 + 64', + }, + 4 => { + Name => 'AEBXv', + Format => 'int8s', + Notes => 'val = raw / 8', + ValueConv => '$val / 8', + ValueConvInv => '$val * 8', + }, + 5 => { + Name => 'AEMinExposureTime', #19 + Notes => 'val = 24 * 2**((32-raw)/8)', + ValueConv => '24*exp(-($val-32)*log(2)/8)', #JD + ValueConvInv => '-log($val/24)*8/log(2)+32', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 6 => { + Name => 'AEProgramMode', + PrintConvColumns => 2, + PrintConv => { + 0 => 'M, P or TAv', + 1 => 'Av, B or X', + 2 => 'Tv', + 3 => 'Sv or Green Mode', + 8 => 'Hi-speed Program', + 11 => 'Hi-speed Program (P-Shift)', #19 + 16 => 'DOF Program', #19 + 19 => 'DOF Program (P-Shift)', #19 + 24 => 'MTF Program', #19 + 27 => 'MTF Program (P-Shift)', #19 + 35 => 'Standard', + 43 => 'Portrait', + 51 => 'Landscape', + 59 => 'Macro', + 67 => 'Sport', + 75 => 'Night Scene Portrait', + 83 => 'No Flash', + 91 => 'Night Scene', + # 96 - seen for Pentax Q10 + 99 => 'Surf & Snow', + 104 => 'Night Snap', # (Q) + 107 => 'Text', + 115 => 'Sunset', + # 116 - seen for Pentax Q (vivid?) + 123 => 'Kids', + 131 => 'Pet', + 139 => 'Candlelight', + 144 => 'SCN', # (Q) + 160 => 'Program', # (Q) + # 142 - seen for Pentax Q in Program mode + 147 => 'Museum', + 184 => 'Shallow DOF Program', # (K-5) + 216 => 'HDR', # (Q) + }, + }, + 7 => { + Name => 'AEFlags', + Writable => 0, + Hook => '$size > 20 and $varSize += 1', + Notes => 'indices after this are incremented by 1 for some models', + # (this tag can't be unknown because the Hook must be evaluated + # to shift the following offsets if necessary. Instead, ignore + # the return value unless Unknown option used) + RawConv => '$$self{OPTIONS}{Unknown} ? $val : undef', + PrintConv => { #19 + # (seems to be the warnings displayed in the viewfinder for several bits) + BITMASK => { + # 0 - seen in extreme low light conditions (e.g. Lens Cap On) + # 1 - seen in 2 cases, Aperture Priority mode, Auto ISO at 100, + # Shutter speed at 1/4000 and aperture opened wider causing under exposure + # 2 - only (but not always) set in Shutter Speed Priority (seems to be when over/under exposed). + # In one case set when auto exposure compensation changed the Tv from 1/250 to 1/80. + # In another case set when external flash was in SB mode so did not fire. + 3 => 'AE lock', + 4 => 'Flash recommended?', # not 100% sure of this one + # 5 - seen lots... + # 6 - seen lots... + 7 => 'Aperture wide open', # mostly true... (Set for all my lenses except for DA* 16-50mm) + }, + }, + }, + # Note: Offsets below shifted by 1 if record size is > 20 bytes + # (implemented by the Hook above) + 8 => { #30 + Name => 'AEApertureSteps', + Notes => q{ + number of steps the aperture has been stopped down from wide open. There + are roughly 8 steps per F-stop for most lenses, or 18 steps for 645D lenses, + but it varies slightly by lens + }, + PrintConv => '$val == 255 ? "n/a" : $val', + PrintConvInv => '$val eq "n/a" ? 255 : $val', + }, + 9 => { #19 + Name => 'AEMaxAperture', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 10 => { #19 + Name => 'AEMaxAperture2', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 11 => { #19 + Name => 'AEMinAperture', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 12 => { #19 + Name => 'AEMeteringMode', + PrintConv => { + 0 => 'Multi-segment', + BITMASK => { + 4 => 'Center-weighted average', + 5 => 'Spot', + }, + }, + }, + 13 => { #30 + Name => 'AEWhiteBalance', + Condition => '$$self{AEInfoSize} == 24', # (not thoroughly tested for other sizes) + Notes => 'K7 and Kx', + Mask => 0xf0, + PrintConv => { + 0 => 'Standard', + 1 => 'Daylight', + 2 => 'Shade', + 3 => 'Cloudy', + 4 => 'Daylight Fluorescent', + 5 => 'Day White Fluorescent', + 6 => 'White Fluorescent', + 7 => 'Tungsten', + 8 => 'Unknown', #31 (or not set due to inadequate lighting) + }, + }, + 13.1 => { #30 + Name => 'AEMeteringMode2', + Condition => '$$self{AEInfoSize} == 24', # (not thoroughly tested for other sizes) + Notes => 'K7 and Kx, override for an incompatible metering mode setting', + Mask => 0x0f, + PrintConv => { + 0 => 'Multi-segment', + BITMASK => { + 0 => 'Center-weighted average', + 1 => 'Spot', + # 2 - seen for K7 AVI movie + }, + }, + }, + 14 => { #19 + Name => 'FlashExposureCompSet', + Description => 'Flash Exposure Comp. Setting', + Format => 'int8s', + Notes => q{ + reports the camera setting, unlike tag 0x004d which reports 0 in Green mode + or if flash was on but did not fire. Both this tag and 0x004d report the + setting even if the flash is off + }, + ValueConv => 'Image::ExifTool::Pentax::PentaxEv($val)', + ValueConvInv => 'Image::ExifTool::Pentax::PentaxEvInv($val)', + PrintConv => '$val ? sprintf("%+.1f", $val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 21 => { #30 + Name => 'LevelIndicator', + PrintConv => '$val == 90 ? "n/a" : $val', + PrintConvInv => '$val eq "n/a" ? 90 : $val', + }, +); + +# auto-exposure information for the K-01 (ref PH) +%Image::ExifTool::Pentax::AEInfo2 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Auto-exposure information for the K-01.', + # instead of /8, should these be PentaxEv(), as in CameraSettings? - PH + 2 => { + Name => 'AEExposureTime', + Notes => 'val = 24 * 2**((32-raw)/8)', + ValueConv => '24*exp(-($val-32)*log(2)/8)', + ValueConvInv => '-log($val/24)*8/log(2)+32', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 3 => { + Name => 'AEAperture', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 4 => { + Name => 'AE_ISO', + Notes => 'val = 100 * 2**((raw-32)/8)', + ValueConv => '100*exp(($val-32)*log(2)/8)', + ValueConvInv => 'log($val/100)*8/log(2)+32', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 5 => { + Name => 'AEXv', + # this is the negative of exposure compensation, not including bracketing + Notes => 'val = (raw-64)/8', + ValueConv => '($val-64)/8', + ValueConvInv => '$val * 8 + 64', + }, + 6 => { + Name => 'AEBXv', + # this is the negative of auto exposure bracketing compensation + Format => 'int8s', + Notes => 'val = raw / 8', + ValueConv => '$val / 8', + ValueConvInv => '$val * 8', + }, + 8 => { + Name => 'AEError', + Format => 'int8s', + # this is usually zero except in M exposure mode, but it can be non-zero + # in other modes (eg. if you hit an aperture limit in Tv mode) + ValueConv => '-($val-64)/8', # (negate to make overexposed positive) + ValueConvInv => '-$val * 8 + 64', + }, + 11 => { + Name => 'AEApertureSteps', + Notes => q{ + number of steps the aperture has been stopped down from wide open. There + are roughly 8 steps per F-stop, but it varies slightly by lens + }, + PrintConv => '$val == 255 ? "n/a" : $val', + PrintConvInv => '$val eq "n/a" ? 255 : $val', + }, + 15 => { + Name => 'SceneMode', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'HDR', + 4 => 'Auto PICT', + 5 => 'Portrait', + 6 => 'Landscape', + 7 => 'Macro', + 8 => 'Sport', + 9 => 'Night Scene Portrait', + 10 => 'No Flash', + 11 => 'Night Scene', + 12 => 'Surf & Snow', + 14 => 'Sunset', + 15 => 'Kids', + 16 => 'Pet', + 17 => 'Candlelight', + 18 => 'Museum', + 20 => 'Food', + 21 => 'Stage Lighting', + 22 => 'Night Snap', + 25 => 'Night Scene HDR', + 26 => 'Blue Sky', + 27 => 'Forest', + 29 => 'Backlight Silhouette', + }, + }, + 16 => { + Name => 'AEMaxAperture', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 17 => { + Name => 'AEMaxAperture2', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 18 => { + Name => 'AEMinAperture', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 19 => { + Name => 'AEMinExposureTime', + Notes => 'val = 24 * 2**((32-raw)/8)', + ValueConv => '24*exp(-($val-32)*log(2)/8)', + ValueConvInv => '-log($val/24)*8/log(2)+32', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, +); + +# auto-exposure information for the K-30 (ref PH) +%Image::ExifTool::Pentax::AEInfo3 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Auto-exposure information for the K-3, K-30, K-50 and K-500.', + # instead of /8, should these be PentaxEv(), as in CameraSettings? - PH + 16 => { + Name => 'AEExposureTime', + Notes => 'val = 24 * 2**((32-raw)/8)', + ValueConv => '24*exp(-($val-32)*log(2)/8)', + ValueConvInv => '-log($val/24)*8/log(2)+32', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 17 => { + Name => 'AEAperture', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 18 => { + Name => 'AE_ISO', + Notes => 'val = 100 * 2**((raw-32)/8)', + ValueConv => '100*exp(($val-32)*log(2)/8)', + ValueConvInv => 'log($val/100)*8/log(2)+32', + PrintConv => 'int($val + 0.5)', + PrintConvInv => '$val', + }, + 28 => { + Name => 'AEMaxAperture', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 29 => { + Name => 'AEMaxAperture2', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 30 => { + Name => 'AEMinAperture', + Notes => 'val = 2**((raw-68)/16)', + ValueConv => 'exp(($val-68)*log(2)/16)', + ValueConvInv => 'log($val)*16/log(2)+68', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 31 => { + Name => 'AEMinExposureTime', + Notes => 'val = 24 * 2**((32-raw)/8)', + ValueConv => '24*exp(-($val-32)*log(2)/8)', + ValueConvInv => '-log($val/24)*8/log(2)+32', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, +); + +# unknown auto-exposure information (Q, Q10) +%Image::ExifTool::Pentax::AEInfoUnknown = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, +); + +# lens type +%Image::ExifTool::Pentax::LensRec = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + This record stores the LensType, plus one or two unknown bytes for some + models. + }, + 0 => { + Name => 'LensType', + Format => 'int8u[2]', + Priority => 0, + ValueConvInv => '$val=~s/\.\d+$//; $val', + PrintConv => \%pentaxLensTypes, + SeparateTable => 1, + PrintInt => 1, + }, + 3 => { #PH + Name => 'ExtenderStatus', + Notes => 'not valid if a non-AF lens is used', + PrintConv => { 0 => 'Not attached', 1 => 'Attached' }, + }, + # this is a binaryData table because some cameras add an extra + # byte or two here (typically zeros)... +); + +# lens information (ref PH) +%Image::ExifTool::Pentax::LensInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 3 ], + NOTES => 'Pentax lens information structure for models such as the *istD.', + 0 => { + Name => 'LensType', + Format => 'int8u[2]', + Priority => 0, + ValueConvInv => '$val=~s/\.\d+$//; $val', + PrintConv => \%pentaxLensTypes, + SeparateTable => 1, + PrintInt => 1, + }, + 3 => { + Name => 'LensData', + Format => 'undef[17]', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensData' }, + }, +); + +# lens information for newer models (ref PH) +%Image::ExifTool::Pentax::LensInfo2 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 4 ], + NOTES => 'Pentax lens information structure for models such as the K10D and K20D.', + 0 => { + Name => 'LensType', + Format => 'int8u[4]', + Priority => 0, + ValueConv => q{ + my @v = split(' ',$val); + $v[0] &= 0x0f; + $v[1] = $v[2] * 256 + $v[3]; # (always high byte first) + return "$v[0] $v[1]"; + }, + # just fill in the missing bits/bytes with zeros... + ValueConvInv => q{ + my @v = split(' ',$val); + return undef unless @v == 2; + $v[2] = ($v[1] >> 8) & 0xff; + $v[3] = $v[1] & 0xff; + $v[1] = 0; + return "@v"; + }, + PrintConv => \%pentaxLensTypes, + SeparateTable => 1, + PrintInt => 1, + }, + 4 => { + Name => 'LensData', + Format => 'undef[17]', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensData' }, + }, +); + +# lens information for 645D (ref PH) +%Image::ExifTool::Pentax::LensInfo3 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 13 ], + NOTES => 'Pentax lens information structure for 645D.', + 1 => { + Name => 'LensType', + Format => 'int8u[4]', + Priority => 0, + ValueConv => q{ + my @v = split(' ',$val); + $v[0] &= 0x0f; + $v[1] = $v[2] * 256 + $v[3]; # (always high byte first) + return "$v[0] $v[1]"; + }, + # just fill in the missing bits/bytes with zeros... + ValueConvInv => q{ + my @v = split(' ',$val); + return undef unless @v == 2; + $v[2] = ($v[1] >> 8) & 0xff; + $v[3] = $v[1] & 0xff; + $v[1] = 0; + return "@v"; + }, + PrintConv => \%pentaxLensTypes, + SeparateTable => 1, + PrintInt => 1, + }, + 13 => { + Name => 'LensData', + Format => 'undef[17]', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensData' }, + }, +); + +# lens information for K-5, K-r, etc (ref PH) +%Image::ExifTool::Pentax::LensInfo4 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 12 ], + NOTES => 'Pentax lens information structure for models such as the K-5 and K-r.', + 1 => { + Name => 'LensType', + Format => 'int8u[4]', + Priority => 0, + ValueConv => q{ + my @v = split(' ',$val); + $v[0] &= 0x0f; + $v[1] = $v[2] * 256 + $v[3]; # (always high byte first) + return "$v[0] $v[1]"; + }, + # just fill in the missing bits/bytes with zeros... + ValueConvInv => q{ + my @v = split(' ',$val); + return undef unless @v == 2; + $v[2] = ($v[1] >> 8) & 0xff; + $v[3] = $v[1] & 0xff; + $v[1] = 0; + return "@v"; + }, + PrintConv => \%pentaxLensTypes, + SeparateTable => 1, + PrintInt => 1, + }, + 12 => { + Name => 'LensData', + Format => 'undef[18]', + Condition => '$$self{NewLensData} = 1', # not really a condition, just used to set flag + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensData' }, + }, +); + +# lens information for K-01, K-30, K-50, K-500, K-3, K-3II, K-S1 (ref PH) +%Image::ExifTool::Pentax::LensInfo5 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 15 ], + NOTES => 'Pentax lens information structure for the K-01 and newer models.', + 1 => { + Name => 'LensType', + Format => 'int8u[5]', + Priority => 0, + ValueConv => q{ + my @v = split(' ',$val); + $v[0] &= 0x0f; + $v[1] = $v[3] * 256 + $v[4]; # (always high byte first) + return "$v[0] $v[1]"; + }, + # just fill in the missing bits/bytes with zeros... + ValueConvInv => q{ + my @v = split(' ',$val); + return undef unless @v == 2; + $v[3] = ($v[1] >> 8) & 0xff; + $v[4] = $v[1] & 0xff; + $v[1] = $v[2] = 0; + return "@v"; + }, + PrintConv => \%pentaxLensTypes, + SeparateTable => 1, + PrintInt => 1, + }, + 15 => { + Name => 'LensData', + Format => 'undef[17]', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::LensData' }, + }, +); + +# lens data information, including lens codes (ref PH) +%Image::ExifTool::Pentax::LensData = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 12.1 ], + NOTES => q{ + Pentax lens data information. Some of these tags require interesting binary + gymnastics to decode them into useful values. + }, + # this byte comes from the lens electrical contacts + # (see http://kmp.bdimitrov.de/technology/K-mount/Ka.html) + 0.1 => { #JD + Name => 'AutoAperture', + Condition => 'not $$self{NewLensData}', + Notes => 'not valid for the K-r, K-5 or K-5II', #29 + Mask => 0x01, + PrintConv => { 0 => 'On', 1 => 'Off' }, + }, + 0.2 => { #JD + Name => 'MinAperture', + Condition => 'not $$self{NewLensData}', + Notes => 'not valid for the K-r, K-5 or K-5II', #29 + Mask => 0x06, + PrintConv => { + 0 => 22, + 1 => 32, + 2 => 45, + 3 => 16, + }, + }, + 0.3 => { #JD + Name => 'LensFStops', + Condition => 'not $$self{NewLensData}', + Notes => 'not valid for the K-r, K-5 or K-5II', #29 + Mask => 0x70, + ValueConv => '5 + ($val ^ 0x07) / 2', + ValueConvInv => '(($val - 5) * 2) ^ 0x07', + }, + # 1-16 look like Lens Codes LC0-LC15, ref patent 5617173 and 5999753 [+notes by PH] + 1 => { # LC0 = lens kind + version data + Name => 'LensKind', + %lensCode, + }, + 2 => { # LC1 = lens data (changes with AF setting) + Name => 'LC1', + %lensCode, + }, + # LC2 = distance data + 3 => { #29 + Name => 'MinFocusDistance', + Notes => 'minimum focus distance for the lens', + Mask => 0xf8, + PrintConv => { + 0 => '0.13-0.19 m', # (plus K or M lenses) + 1 => '0.20-0.24 m', + 2 => '0.25-0.28 m', + 3 => '0.28-0.30 m', + 4 => '0.35-0.38 m', + 5 => '0.40-0.45 m', + 6 => '0.49-0.50 m', # (plus many Sigma lenses) + 7 => '0.6 m', #PH (NC) + 8 => '0.7 m', # (plus Sigma 55-200) + 9 => '0.8-0.9 m', #PH (NC) Tokina 28-70/2.6-2.8 + 10 => '1.0 m', # (plus Sigma 70 macro) + 11 => '1.1-1.2 m', + 12 => '1.4-1.5 m', + 13 => '1.5 m', # Sigma 70-300/4-5.6 macro + 14 => '2.0 m', + 15 => '2.0-2.1 m', #PH (NC) + 16 => '2.1 m', # Sigma 135-400 APO & DG: 2.0-2.2m + 17 => '2.2-2.9 m', #PH (NC) + 18 => '3.0 m', # Sigma 50-500 : 1.0-3.0m depending on the focal length + ## 50mm, 100mm => 1.0m + ## 200mm => 1.1m + ## 300mm => 1.5m + ## 400mm => 2.2m + ## 500mm => 3.0m + 19 => '4-5 m', #PH (NC) + 20 => '5.6 m', # Pentax DA 560 + # To check: Sigma 120-400 OS: MFD 1.5m + # To check: Sigma 150-500 OS: MFD 2.2m + # To check: Sigma 50-500 has MFD 50-180cm + # 0xd0 - seen for the Sigma 4.5mm F2.8 EX DC HSM Circular Fisheye (ref PH) + }, + }, + 3.1 => { #29 + Name => 'FocusRangeIndex', + Mask => 0x07, + PrintConv => { + 7 => '0 (very close)', + 6 => '1 (close)', + 4 => '2', + 5 => '3', + 1 => '4', + 0 => '5', + 2 => '6 (far)', + 3 => '7 (very far)', + }, + }, + 4 => { # LC3 = K-value data (AF pulses to displace image by unit length) + Name => 'LC3', + %lensCode, + }, + 5 => { # LC4 = aberration correction, near distance data + Name => 'LC4', + %lensCode, + }, + 6 => { # LC5 = light color aberration correction data + Name => 'LC5', + %lensCode, + }, + 7 => { # LC6 = open aberration data + Name => 'LC6', + %lensCode, + }, + 8 => { # LC7 = AF minimum actuation condition + Name => 'LC7', + %lensCode, + }, + 9 => [{ # LC8 = focal length data + Name => 'LensFocalLength', + Notes => 'focal length of lens alone, without adapter', #PH + Priority => 0, + Condition => '$$self{Model} !~ /645Z/', #PH (doesn't work for 645Z) + ValueConv => '10*($val>>2) * 4**(($val&0x03)-2)', #JD + ValueConvInv => q{ + my $range = int(log($val/10)/(2*log(2))); + warn("Value out of range") and return undef if $range < 0 or $range > 3; + return $range + (int($val/(10*4**($range-2))+0.5) << 2); + }, + PrintConv => 'sprintf("%.1f mm", $val)', + PrintConvInv => '$val=~s/\s*mm//; $val', + },{ + Name => 'LC8', + %lensCode, + }], + # the following aperture values change with focal length + 10 => { # LC9 = nominal AVmin/AVmax data (open/closed aperture values) + Name => 'NominalMaxAperture', + Mask => 0xf0, + ValueConv => '2**($val/4)', #JD + ValueConvInv => '4*log($val)/log(2)', + PrintConv => 'sprintf("%.1f", $val)', + PrintConvInv => '$val', + }, + 10.1 => { # LC9 = nominal AVmin/AVmax data (open/closed aperture values) + Name => 'NominalMinAperture', + Mask => 0x0f, + ValueConv => '2**(($val+10)/4)', #JD + ValueConvInv => '4*log($val)/log(2) - 10', + PrintConv => 'sprintf("%.0f", $val)', + PrintConvInv => '$val', + }, + 11 => { # LC10 = mv'/nv' data (full-aperture metering error compensation/marginal lumination compensation) + Name => 'LC10', + %lensCode, + }, + 12 => { # LC11 = AVC 1/EXP data + Name => 'LC11', + %lensCode, + }, + 12.1 => { + Name => 'NewLensDataHook', + Hidden => 1, + Hook => '$varSize += 1 if $$self{NewLensData}', + RawConv => 'undef', + }, + 13 => { # LC12 = mv1 AVminsif data + Name => 'LC12', + Notes => "ID's 13-16 are offset by 1 for the K-r, K-5 and K-5II", #29 + %lensCode, + }, + # 14 - related to live view for K-5 (normally 3, but 1 or 5 in LV mode) + 14.1 => { # LC13 = AVmin (open aperture value) [MaxAperture=(2**((AVmin-1)/32))] + Name => 'MaxAperture', + Condition => '$$self{Model} ne "K-5"', + Notes => 'effective wide open aperture for current focal length', + Mask => 0x7f, # (not sure what the high bit indicates) + # (a value of 1 seems to indicate 'n/a') + RawConv => '$val > 1 ? $val : undef', + ValueConv => '2**(($val-1)/32)', + ValueConvInv => '32*log($val)/log(2) + 1', + PrintConv => 'sprintf("%.1f", $val)', + PrintConvInv => '$val', + }, + 15 => { # LC14 = UNT_12 UNT_6 data + Name => 'LC14', + %lensCode, + }, + 16 => { # LC15 = incorporated flash suited END data + Name => 'LC15', + %lensCode, + }, +); + +# flash information (ref PH) +%Image::ExifTool::Pentax::FlashInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Flash information tags for the K10D, K20D and K200D.', + 0 => { + Name => 'FlashStatus', + PrintHex => 1, + PrintConv => { #19 + 0x00 => 'Off', + 0x01 => 'Off (1)', #PH (K-5) + 0x02 => 'External, Did not fire', # 0010 + 0x06 => 'External, Fired', # 0110 + 0x08 => 'Internal, Did not fire (0x08)', + 0x09 => 'Internal, Did not fire', # 1001 + 0x0d => 'Internal, Fired', # 1101 + }, + }, + 1 => { + Name => 'InternalFlashMode', + PrintHex => 1, + PrintConv => { + 0x00 => 'n/a - Off-Auto-Aperture', #19 + 0x86 => 'Fired, Wireless (Control)', #19 + 0x95 => 'Fired, Wireless (Master)', #19 + 0xc0 => 'Fired', # K10D + 0xc1 => 'Fired, Red-eye reduction', # *istDS2, K10D + 0xc2 => 'Fired, Auto', # K100D, K110D + 0xc3 => 'Fired, Auto, Red-eye reduction', #PH + 0xc6 => 'Fired, Wireless (Control), Fired normally not as control', #19 (Remote 3s) + 0xc8 => 'Fired, Slow-sync', # K10D + 0xc9 => 'Fired, Slow-sync, Red-eye reduction', # K10D + 0xca => 'Fired, Trailing-curtain Sync', # K10D + 0xf0 => 'Did not fire, Normal', #19 + 0xf1 => 'Did not fire, Red-eye reduction', #19 + 0xf2 => 'Did not fire, Auto', #19 + 0xf3 => 'Did not fire, Auto, Red-eye reduction', #19 + 0xf4 => 'Did not fire, (Unknown 0xf4)', #19 + 0xf5 => 'Did not fire, Wireless (Master)', #19 + 0xf6 => 'Did not fire, Wireless (Control)', #19 + 0xf8 => 'Did not fire, Slow-sync', #19 + 0xf9 => 'Did not fire, Slow-sync, Red-eye reduction', #19 + 0xfa => 'Did not fire, Trailing-curtain Sync', #19 + }, + }, + 2 => { + Name => 'ExternalFlashMode', + PrintHex => 1, + PrintConv => { #19 + 0x00 => 'n/a - Off-Auto-Aperture', + 0x3f => 'Off', + 0x40 => 'On, Auto', + 0xbf => 'On, Flash Problem', #JD + 0xc0 => 'On, Manual', + 0xc4 => 'On, P-TTL Auto', + 0xc5 => 'On, Contrast-control Sync', #JD + 0xc6 => 'On, High-speed Sync', + 0xcc => 'On, Wireless', + 0xcd => 'On, Wireless, High-speed Sync', + 0xf0 => 'Not Connected', #PH (K-5) + }, + }, + 3 => { + Name => 'InternalFlashStrength', + Notes => 'saved from the most recent flash picture, on a scale of about 0 to 100', + }, + 4 => 'TTL_DA_AUp', + 5 => 'TTL_DA_ADown', + 6 => 'TTL_DA_BUp', + 7 => 'TTL_DA_BDown', + 24.1 => { #19/17 + Name => 'ExternalFlashGuideNumber', + Mask => 0x1f, + Notes => 'val = 2**(raw/16 + 4), with a few exceptions', + ValueConv => q{ + return 0 unless $val; + $val = -3 if $val == 29; # -3 is stored as 0x1d + return 2**($val/16 + 4); + }, + ValueConvInv => q{ + return 0 unless $val; + my $raw = int((log($val)/log(2)-4)*16+0.5); + $raw = 29 if $raw < 0; # guide number of 14 gives -3 which is stored as 0x1d + $raw = 31 if $raw > 31; # maximum value is 0x1f + return $raw; + }, + PrintConv => '$val ? int($val + 0.5) : "n/a"', + PrintConvInv => '$val=~/^n/ ? 0 : $val', + # observed values for various flash focal lengths/guide numbers: + # AF-540FGZ (ref 19) AF-360FGZ (ref 17) + # 6 => 20mm/21 29 => 20mm/14 (wide angle panel used) + # 16 => 24mm/32 6 => 24mm/21 + # 18 => 28mm/35 7 => 28mm/22 + # 21 => 35mm/39 10 => 35mm/25 + # 24 => 50mm/45 14 => 50mm/30 + # 26 => 70mm/50 17 => 70mm/33 + # 28 => 85mm/54 19 => 85mm/36 + # (I have also seen a value of 31 when both flashes are used together + # in a wired configuration, but I don't know exactly what this means - PH) + }, + # 24 - have seen bit 0x80 set when 2 external wired flashes are used - PH + # 24 - have seen bit 0x40 set when wireless high speed sync is used - ref 19 + 25 => { #19 + Name => 'ExternalFlashExposureComp', + PrintConv => { + 0 => 'n/a', # Off or Auto Modes + 144 => 'n/a (Manual Mode)', # Manual Flash Output + 164 => '-3.0', + 167 => '-2.5', + 168 => '-2.0', + 171 => '-1.5', + 172 => '-1.0', + 175 => '-0.5', + 176 => '0.0', + 179 => '0.5', + 180 => '1.0', + }, + }, + 26 => { #17 + Name => 'ExternalFlashBounce', + Notes => 'saved from the most recent external flash picture', #19 + PrintConv => { + 0 => 'n/a', + 16 => 'Direct', + 48 => 'Bounce', + }, + }, + # ? => 'ExternalFlashAOutput', + # ? => 'ExternalFlashBOutput', +); + +%Image::ExifTool::Pentax::FlashInfoUnknown = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # 4 - changes with FEC for K-5 - PH +); + +# camera manufacture information (ref PH) +%Image::ExifTool::Pentax::CameraInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int32u', + 0 => { + Name => 'PentaxModelID', + Priority => 0, # (Optio SVi uses incorrect Optio SV ID here) + SeparateTable => 1, + PrintHex => 1, + PrintConv => \%pentaxModelID, + }, + 1 => { + Name => 'ManufactureDate', + Groups => { 2 => 'Time' }, + Notes => q{ + this value, and the values of the tags below, may change if the camera is + serviced + }, + ValueConv => q{ + $val =~ /^(\d{4})(\d{2})(\d{2})$/ and return "$1:$2:$3"; + # Optio A10 and A20 leave "200" off the year + $val =~ /^(\d)(\d{2})(\d{2})$/ and return "200$1:$2:$3"; + return "Unknown ($val)"; + }, + ValueConvInv => '$val=~tr/0-9//dc; $val', + }, + 2 => { + #(see http://www.pentaxforums.com/forums/pentax-dslr-discussion/25711-k10d-update-model-revision-8-1-yes-no-8.html) + Name => 'ProductionCode', #(previously ModelRevision) + Format => 'int32u[2]', + Note => 'values of 8.x indicate that the camera has been serviced', + ValueConv => '$val=~tr/ /./; $val', + ValueConvInv => '$val=~tr/./ /; $val', + PrintConv => '$val=~/^8\./ ? "$val (camera has been serviced)" : $val', + PrintConvInv => '$val=~s/\s+.*//s; $val', + }, + 4 => 'InternalSerialNumber', +); + +# battery information (ref PH) +%Image::ExifTool::Pentax::BatteryInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, +# size of data: +# 4 (K-m,K2000=4xAA), 6 (*istD,K10D,K100D,K110D=2xCR-V3/4xAA), +# 7 (K20D=D-LI50, K200D=4xAA), 8 (645D=D-LI90), 10 (K-r pre-production?), +# 14 (K-7=D-LI90, K-r=D-LI109/4xAA, K-x=4xAA), 26 (K-5=D-LI90) +# battery grips available for: +# BG1 (*istD), BG2 (K10D/K20D), BG3 (K200D), BG4 (K-7,K-5) +# no grip available: K-x + 0.1 => { #19 + Name => 'PowerSource', + Mask => 0x0f, + # have seen the upper bit set (value of 0x82) for the + # *istDS and K100D, but I'm not sure what this means - PH + # I've also seen: 0x42 (K2000), 0xf2 (K-7,K-r,K-5), 0x12,0x22 (K-x) - PH + PrintConv => { + 1 => 'Camera Battery', #PH (NC, GR III) + 2 => 'Body Battery', + 3 => 'Grip Battery', + 4 => 'External Power Supply', #PH + }, + }, + 1.1 => [ + { + Name => 'BodyBatteryState', + Condition => '$$self{Model} =~ /(\*ist|K100D|K200D|K10D|GX10|K20D|GX20|GX-1[LS]?)\b/', + Notes => '*istD, K100D, K200D, K10D and K20D', + Mask => 0xf0, + PrintConv => { #19 + 1 => 'Empty or Missing', + 2 => 'Almost Empty', + 3 => 'Running Low', + 4 => 'Full', + }, + },{ + Name => 'BodyBatteryState', + Condition => '$$self{Model} !~ /(K110D|K2000|K-m)\b/', + Notes => 'other models except the K110D, K2000 and K-m', + Mask => 0xf0, + PrintConv => { + 1 => 'Empty or Missing', + 2 => 'Almost Empty', + 3 => 'Running Low', + 4 => 'Close to Full', + 5 => 'Full', + }, + },{ + Name => 'BodyBatteryState', + Notes => 'decoding unknown for other models', + Mask => 0xf0, + }, + ], + 1.2 => [ + { + Name => 'GripBatteryState', + Condition => '$$self{Model} =~ /(K10D|GX10|K20D|GX20)\b/', + Notes => 'K10D and K20D', + Mask => 0x0f, + PrintConv => { #19 + 1 => 'Empty or Missing', + 2 => 'Almost Empty', + 3 => 'Running Low', + 4 => 'Full', + }, + },{ + Name => 'GripBatteryState', + Notes => 'decoding unknown for other models', + Unknown => 1, # (doesn't appear to be valid for the K-5) + Mask => 0x0f, + }, + ], + # internal and grip battery voltage Analogue to Digital measurements, + # open circuit and under load + 2 => [ + { + Name => 'BodyBatteryADNoLoad', + Description => 'Body Battery A/D No Load', + Condition => '$$self{Model} =~ /(K10D|GX10|K20D|GX20)\b/', + Notes => 'roughly calibrated for K10D with a new Pentax battery', + # rough linear calibration drops quickly below 30% - PH + # DVM readings: 8.18V=186, 8.42-8.40V=192 (full), 6.86V=155 (empty) + PrintConv => 'sprintf("%d (%.1fV, %d%%)",$val,$val*8.18/186,($val-155)*100/35)', + PrintConvInv => '$val=~s/ .*//; $val', + }, + { + Name => 'BodyBatteryADNoLoad', + Description => 'Body Battery A/D No Load', + Condition => '$$self{Model} =~ /(\*ist|K100D|K200D|GX-1[LS]?)\b/', + }, + { + Name => 'BodyBatteryVoltage1', # (static?) + Condition => '$$self{Model} !~ /(K100D|K110D|K2000|K-m|Q\d*)\b/', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => 'sprintf("%.2f V", $val)', + PrintConvInv => '$val =~ s/\s*V$//', + # For my K-5: Min (0%) Max (100%) At Meas + # BodyBatteryVoltage1 6.24 V 7.75 V 7.66 V + # BodyBatteryVoltage2 5.98 V 7.43 V 7.34 V + # BodyBatteryVoltage3 6.41 V 7.93 V 7.84 V + # BodyBatteryVoltage4 6.10 V 7.55 V 7.45 V + # "Meas" open-circuit voltages with DVM: AB=0V, AC=+8.33V, BC=+8.22V + # (terminal "C" is closest to edge of battery) + }, + ], + 3 => [ + { + Name => 'BodyBatteryADLoad', + Description => 'Body Battery A/D Load', + Condition => '$$self{Model} =~ /(K10D|GX10|K20D|GX20)\b/', + Notes => 'roughly calibrated for K10D with a new Pentax battery', + # [have seen 187] - PH + PrintConv => 'sprintf("%d (%.1fV, %d%%)",$val,$val*8.18/186,($val-152)*100/34)', + PrintConvInv => '$val=~s/ .*//; $val', + }, + { + Name => 'BodyBatteryADLoad', + Description => 'Body Battery A/D Load', + Condition => '$$self{Model} =~ /(\*ist|K100D|K200D)\b/', + }, + ], + 4 => [ + { + Name => 'GripBatteryADNoLoad', + Description => 'Grip Battery A/D No Load', + Condition => '$$self{Model} =~ /(\*ist|K10D|GX10|K20D|GX20|GX-1[LS]?)\b/', + }, + { + Name => 'BodyBatteryVoltage2', # (less than BodyBatteryVoltage1 -- under load?) + Condition => '$$self{Model} !~ /(K100D|K110D|K2000|K-m|Q\d*)\b/', + Format => 'int16u', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => 'sprintf("%.2f V", $val)', + PrintConvInv => '$val =~ s/\s*V$//', + }, + ], + 5 => { + Name => 'GripBatteryADLoad', + Condition => '$$self{Model} =~ /(\*ist|K10D|GX10|K20D|GX20)\b/', + Description => 'Grip Battery A/D Load', + }, + 6 => { + Name => 'BodyBatteryVoltage3', # (greater than BodyBatteryVoltage1) + Condition => '$$self{Model} =~ /(K-5|K-r|645D)\b/', + Format => 'int16u', + Notes => 'K-5, K-r and 645D only', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => 'sprintf("%.2f V", $val)', + PrintConvInv => '$val =~ s/\s*V$//', + }, + 8 => { + Name => 'BodyBatteryVoltage4', # (between BodyBatteryVoltage1 and BodyBatteryVoltage2) + Condition => '$$self{Model} =~ /(K-5|K-r)\b/', + Format => 'int16u', + Notes => 'K-5 and K-r only', + ValueConv => '$val / 100', + ValueConvInv => '$val * 100', + PrintConv => 'sprintf("%.2f V", $val)', + PrintConvInv => '$val =~ s/\s*V$//', + }, +); + +# auto focus information +%Image::ExifTool::Pentax::AFInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # AF Info tag names in K10D debugging output - PH: + # SelectArea, InFocusArea, Predictor, Defocus, IntegTime2msStep, + # CalFlag, ContrastFlag, PrecalFlag, SelectSensor + 0x00 => { #PH + Name => 'AFPointsUnknown1', + Unknown => 1, + Format => 'int16u', + ValueConv => '$self->Options("Unknown") ? $val : $val & 0x7ff', + ValueConvInv => '$val', + PrintConvColumns => 2, + PrintConv => { + 0 => '(none)', + 0x07ff => 'All', + 0x0777 => 'Central 9 points', + BITMASK => { + 0 => 'Upper-left', + 1 => 'Top', + 2 => 'Upper-right', + 3 => 'Left', + 4 => 'Mid-left', + 5 => 'Center', + 6 => 'Mid-right', + 7 => 'Right', + 8 => 'Lower-left', + 9 => 'Bottom', + 10 => 'Lower-right', + # (bits 12-15 are flags of some sort) + }, + }, + }, + 0x02 => { #PH + Name => 'AFPointsUnknown2', + Unknown => 1, + Format => 'int16u', + ValueConv => '$self->Options("Unknown") ? $val : $val & 0x7ff', + ValueConvInv => '$val', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Auto', + BITMASK => { + 0 => 'Upper-left', + 1 => 'Top', + 2 => 'Upper-right', + 3 => 'Left', + 4 => 'Mid-left', + 5 => 'Center', + 6 => 'Mid-right', + 7 => 'Right', + 8 => 'Lower-left', + 9 => 'Bottom', + 10 => 'Lower-right', + # (bits 12-15 are flags of some sort) + # bit 15 is set for center focus point only if it is vertical + }, + }, + }, + 0x04 => { #PH (educated guess - predicted amount to drive lens) + Name => 'AFPredictor', + Format => 'int16s', + }, + 0x06 => 'AFDefocus', #PH (educated guess - calculated distance from focused) + 0x07 => { #PH + # effective exposure time for AF sensors in 2 ms increments + Name => 'AFIntegrationTime', + Notes => 'times less than 2 ms give a value of 0', + ValueConv => '$val * 2', + ValueConvInv => 'int($val / 2)', # (don't round up) + PrintConv => '"$val ms"', + PrintConvInv => '$val=~tr/0-9//dc; $val', + }, + # 0x0a - values: 00,05,0d,15,86,8e,a6,ae + 0x0b => { #JD + Name => 'AFPointsInFocus', + Condition => '$$self{Model} !~ /K-[13]\b/', + Notes => q{ + models other than the K-1 and K-3. May report two points in focus even + though a single AFPoint has been selected, in which case the selected + AFPoint is the first reported + }, + PrintConvColumns => 2, + PrintConv => { + 0 => 'None', + 1 => 'Lower-left, Bottom', + 2 => 'Bottom', + 3 => 'Lower-right, Bottom', + 4 => 'Mid-left, Center', + 5 => 'Center (horizontal)', #PH + 6 => 'Mid-right, Center', + 7 => 'Upper-left, Top', + 8 => 'Top', + 9 => 'Upper-right, Top', + 10 => 'Right', + 11 => 'Lower-left, Mid-left', + 12 => 'Upper-left, Mid-left', + 13 => 'Bottom, Center', + 14 => 'Top, Center', + 15 => 'Lower-right, Mid-right', + 16 => 'Upper-right, Mid-right', + 17 => 'Left', + 18 => 'Mid-left', + 19 => 'Center (vertical)', #PH + 20 => 'Mid-right', + }, + }, + 0x1fd => { + Name => 'AFHold', + Notes => 'decoded only for the K-3 II', + Condition => '$$self{Model} eq "PENTAX K-3 II"', + PrintConv => { 0 => 'Off', 1 => 'Short', 2 => 'Medium', 3 => 'Long' }, + }, +); + +# Kelvin white balance information (ref 28, topic 4834) +%Image::ExifTool::Pentax::KelvinWB = ( + %binaryDataAttrs, + FORMAT => 'int16u', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'White balance Blue/Red gains as a function of color temperature.', + 1 => { Name => 'KelvinWB_Daylight', %kelvinWB }, + 5 => { Name => 'KelvinWB_01', %kelvinWB }, + 9 => { Name => 'KelvinWB_02', %kelvinWB }, + 13 => { Name => 'KelvinWB_03', %kelvinWB }, + 17 => { Name => 'KelvinWB_04', %kelvinWB }, + 21 => { Name => 'KelvinWB_05', %kelvinWB }, + 25 => { Name => 'KelvinWB_06', %kelvinWB }, + 29 => { Name => 'KelvinWB_07', %kelvinWB }, + 33 => { Name => 'KelvinWB_08', %kelvinWB }, + 37 => { Name => 'KelvinWB_09', %kelvinWB }, + 41 => { Name => 'KelvinWB_10', %kelvinWB }, + 45 => { Name => 'KelvinWB_11', %kelvinWB }, + 49 => { Name => 'KelvinWB_12', %kelvinWB }, + 53 => { Name => 'KelvinWB_13', %kelvinWB }, + 57 => { Name => 'KelvinWB_14', %kelvinWB }, + 61 => { Name => 'KelvinWB_15', %kelvinWB }, + 65 => { Name => 'KelvinWB_16', %kelvinWB }, +); + +# color information - PH +%Image::ExifTool::Pentax::ColorInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FORMAT => 'int8s', + 16 => { + Name => 'WBShiftAB', + Notes => 'positive is a shift toward blue', + }, + 17 => { + Name => 'WBShiftGM', + Notes => 'positive is a shift toward green', + }, +); + +# EV step size information - ref 19 +%Image::ExifTool::Pentax::EVStepInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'EVSteps', + PrintConv => { + 0 => '1/2 EV Steps', + 1 => '1/3 EV Steps', + }, + }, + 1 => { + Name => 'SensitivitySteps', + PrintConv => { + 0 => '1 EV Steps', + 1 => 'As EV Steps', + }, + }, +); + +# shot information? - ref PH (K-5) +%Image::ExifTool::Pentax::ShotInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # 0: 0xf2/0xf3 (HDR), 0xf0 (otherwise) + 1 => { # (presumably this is from an orientation sensor) + Name => 'CameraOrientation', + Condition => '$$self{Model} =~ /K-(5|7|r|x)\b/', + Notes => 'K-5, K-7, K-r and K-x', + PrintHex => 1, + PrintConv => { + 0x10 => 'Horizontal (normal)', + 0x20 => 'Rotate 180', + 0x30 => 'Rotate 90 CW', + 0x40 => 'Rotate 270 CW', + 0x50 => 'Upwards', # (to the sky) + 0x60 => 'Downwards', # (to the ground) + }, + }, + # 2: 0xd3 (live view), 0xdb (HDR), 0x7b (otherwise) + # 3: 0xff + # 4: 0x64, 0x6a, 0x6f, 0xa4, 0xaa, 0xab, 0xbf + # 5: 0xfe + # 6: 0x0e + # 7: 0x02 (live view), 0x06 (otherwise) + # 8-10: 0x00 +); + +# face detect positions - ref PH (Optio RZ10) +%Image::ExifTool::Pentax::FacePos = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FORMAT => 'int16u', + 0 => { + Name => 'Face1Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 1 ? undef : $val', + Notes => 'X/Y coordinates of face center in full-sized image', + }, + 2 => { + Name => 'Face2Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 2 ? undef : $val', + }, + 4 => { + Name => 'Face3Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 3 ? undef : $val', + }, + 6 => { + Name => 'Face4Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 4 ? undef : $val', + }, + 8 => { + Name => 'Face5Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 5 ? undef : $val', + }, + 10 => { + Name => 'Face6Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 6 ? undef : $val', + }, + 12 => { + Name => 'Face7Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 7 ? undef : $val', + }, + 14 => { + Name => 'Face8Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 8 ? undef : $val', + }, + 16 => { + Name => 'Face9Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 9 ? undef : $val', + }, + 18 => { + Name => 'Face10Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 10 ? undef : $val', + }, + 20 => { + Name => 'Face11Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 11 ? undef : $val', + }, + 22 => { + Name => 'Face12Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 12 ? undef : $val', + }, + 24 => { + Name => 'Face13Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 13 ? undef : $val', + }, + 26 => { + Name => 'Face14Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 14 ? undef : $val', + }, + 28 => { + Name => 'Face15Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 15 ? undef : $val', + }, + 30 => { + Name => 'Face16Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 16 ? undef : $val', + }, + 32 => { + Name => 'Face17Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 17 ? undef : $val', + }, + 34 => { + Name => 'Face18Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 18 ? undef : $val', + }, + 36 => { + Name => 'Face19Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 19 ? undef : $val', + }, + 38 => { + Name => 'Face20Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 20 ? undef : $val', + }, + 40 => { + Name => 'Face21Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 21 ? undef : $val', + }, + 42 => { + Name => 'Face22Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 22 ? undef : $val', + }, + 44 => { + Name => 'Face23Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 23 ? undef : $val', + }, + 46 => { + Name => 'Face24Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 24 ? undef : $val', + }, + 48 => { + Name => 'Face25Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 25 ? undef : $val', + }, + 50 => { + Name => 'Face26Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 26 ? undef : $val', + }, + 52 => { + Name => 'Face27Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 27 ? undef : $val', + }, + 54 => { + Name => 'Face28Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 28 ? undef : $val', + }, + 56 => { + Name => 'Face29Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 29 ? undef : $val', + }, + 58 => { + Name => 'Face30Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 30 ? undef : $val', + }, + 60 => { + Name => 'Face31Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 31 ? undef : $val', + }, + 62 => { + Name => 'Face32Position', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 32 ? undef : $val', + }, +); + +# face detect sizes - ref PH (Optio RZ10) +%Image::ExifTool::Pentax::FaceSize = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FORMAT => 'int16u', + 0 => { + Name => 'Face1Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 1 ? undef : $val', + }, + 2 => { + Name => 'Face2Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 2 ? undef : $val', + }, + 4 => { + Name => 'Face3Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 3 ? undef : $val', + }, + 6 => { + Name => 'Face4Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 4 ? undef : $val', + }, + 8 => { + Name => 'Face5Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 5 ? undef : $val', + }, + 10 => { + Name => 'Face6Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 6 ? undef : $val', + }, + 12 => { + Name => 'Face7Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 7 ? undef : $val', + }, + 14 => { + Name => 'Face8Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 8 ? undef : $val', + }, + 16 => { + Name => 'Face9Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 9 ? undef : $val', + }, + 18 => { + Name => 'Face10Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 10 ? undef : $val', + }, + 20 => { + Name => 'Face11Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 11 ? undef : $val', + }, + 22 => { + Name => 'Face12Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 12 ? undef : $val', + }, + 24 => { + Name => 'Face13Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 13 ? undef : $val', + }, + 26 => { + Name => 'Face14Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 14 ? undef : $val', + }, + 28 => { + Name => 'Face15Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 15 ? undef : $val', + }, + 30 => { + Name => 'Face16Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 16 ? undef : $val', + }, + 32 => { + Name => 'Face17Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 17 ? undef : $val', + }, + 34 => { + Name => 'Face18Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 18 ? undef : $val', + }, + 36 => { + Name => 'Face19Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 19 ? undef : $val', + }, + 38 => { + Name => 'Face20Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 20 ? undef : $val', + }, + 40 => { + Name => 'Face21Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 21 ? undef : $val', + }, + 42 => { + Name => 'Face22Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 22 ? undef : $val', + }, + 44 => { + Name => 'Face23Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 23 ? undef : $val', + }, + 46 => { + Name => 'Face24Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 24 ? undef : $val', + }, + 48 => { + Name => 'Face25Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 25 ? undef : $val', + }, + 50 => { + Name => 'Face26Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 26 ? undef : $val', + }, + 52 => { + Name => 'Face27Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 27 ? undef : $val', + }, + 54 => { + Name => 'Face28Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 28 ? undef : $val', + }, + 56 => { + Name => 'Face29Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 29 ? undef : $val', + }, + 58 => { + Name => 'Face30Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 30 ? undef : $val', + }, + 60 => { + Name => 'Face31Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 31 ? undef : $val', + }, + 62 => { + Name => 'Face32Size', + Format => 'int16u[2]', + RawConv => '$$self{FacesDetected} < 32 ? undef : $val', + }, +); + +# digital filter information - ref PH (K-5) +%Image::ExifTool::Pentax::FilterInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FORMAT => 'int8u', + NOTES => q{ + The parameters associated with each type of digital filter are unique, and + these settings are also extracted with the DigitalFilter tag. Information + is not extracted for filters that are "Off" unless the L<Unknown|../ExifTool.html#Unknown> option is + used. + }, + 0 => { + Name => 'SourceDirectoryIndex', + Format => 'int16u', + }, + 2 => { + Name => 'SourceFileIndex', + Format => 'int16u', + }, + 0x005 => { Name => 'DigitalFilter01', %digitalFilter }, + 0x016 => { Name => 'DigitalFilter02', %digitalFilter }, + 0x027 => { Name => 'DigitalFilter03', %digitalFilter }, + 0x038 => { Name => 'DigitalFilter04', %digitalFilter }, + 0x049 => { Name => 'DigitalFilter05', %digitalFilter }, + 0x05a => { Name => 'DigitalFilter06', %digitalFilter }, + 0x06b => { Name => 'DigitalFilter07', %digitalFilter }, + 0x07c => { Name => 'DigitalFilter08', %digitalFilter }, + 0x08d => { Name => 'DigitalFilter09', %digitalFilter }, + 0x09e => { Name => 'DigitalFilter10', %digitalFilter }, + 0x0af => { Name => 'DigitalFilter11', %digitalFilter }, + 0x0c0 => { Name => 'DigitalFilter12', %digitalFilter }, + 0x0d1 => { Name => 'DigitalFilter13', %digitalFilter }, + 0x0e2 => { Name => 'DigitalFilter14', %digitalFilter }, + 0x0f3 => { Name => 'DigitalFilter15', %digitalFilter }, + 0x104 => { Name => 'DigitalFilter16', %digitalFilter }, + 0x115 => { Name => 'DigitalFilter17', %digitalFilter }, + 0x126 => { Name => 'DigitalFilter18', %digitalFilter }, + 0x137 => { Name => 'DigitalFilter19', %digitalFilter }, + 0x148 => { Name => 'DigitalFilter20', %digitalFilter }, +); + +# electronic level information - ref PH (K-5) +%Image::ExifTool::Pentax::LevelInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int8s', + NOTES => q{ + Tags decoded from the electronic level information for the K-5. May not be + valid for other models. + }, + 0 => { + Name => 'LevelOrientation', + Mask => 0x0f, + PrintHex => 0, + PrintConv => { + 0 => 'n/a', #PH (NC, GR III) + 1 => 'Horizontal (normal)', + 2 => 'Rotate 180', + 3 => 'Rotate 90 CW', + 4 => 'Rotate 270 CW', + 9 => 'Horizontal; Off Level', + 10 => 'Rotate 180; Off Level', + 11 => 'Rotate 90 CW; Off Level', + 12 => 'Rotate 270 CW; Off Level', + 13 => 'Upwards', + 14 => 'Downwards', + }, + }, + 0.1 => { + Name => 'CompositionAdjust', + Mask => 0xf0, + PrintConv => { + 0 => 'Off', + 2 => 'Composition Adjust', + 10 => 'Composition Adjust + Horizon Correction', + 12 => 'Horizon Correction', + }, + }, + 1 => { + Name => 'RollAngle', + Notes => 'converted to degrees of clockwise camera rotation', + ValueConv => '-$val / 2', + ValueConvInv => '-$val * 2', + }, + 2 => { + Name => 'PitchAngle', + Notes => 'converted to degrees of upward camera tilt', + ValueConv => '-$val / 2', + ValueConvInv => '-$val * 2', + }, + # 3,4 - related somehow to horizon correction and composition adjust + # 5,6,7 - (the notes below refer to how the image moves in the LCD monitor) + 5 => { + Name => 'CompositionAdjustX', + Notes => 'steps to the right, 1/16 mm per step', + ValueConv => '-$val', + ValueConvInv => '-$val', + }, + 6 => { + Name => 'CompositionAdjustY', + Notes => 'steps up, 1/16 mm per step', + ValueConv => '-$val', + ValueConvInv => '-$val', + }, + 7 => { + Name => 'CompositionAdjustRotation', + Notes => 'steps clockwise, 1/8 degree per step', + ValueConv => '-$val / 2', + ValueConvInv => '-$val * 2', + }, +); + +# white balance RGGB levels (ref 28) +%Image::ExifTool::Pentax::WBLevels = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # 0 - 11 (number of entries in this table) + # 1 - 0 + 2 => { + Name => 'WB_RGGBLevelsDaylight', + Format => 'int16u[4]', + }, + # 10 - 1 + 11 => { + Name => 'WB_RGGBLevelsShade', + Format => 'int16u[4]', + }, + # 19 - 2 + 20 => { + Name => 'WB_RGGBLevelsCloudy', + Format => 'int16u[4]', + }, + # 28 - 3 + 29 => { + Name => 'WB_RGGBLevelsTungsten', + Format => 'int16u[4]', + }, + # 37 - 4 + 38 => { + Name => 'WB_RGGBLevelsFluorescentD', + Format => 'int16u[4]', + }, + # 46 - 5 + 47 => { + Name => 'WB_RGGBLevelsFluorescentN', + Format => 'int16u[4]', + }, + # 55 - 6 + 56 => { + Name => 'WB_RGGBLevelsFluorescentW', + Format => 'int16u[4]', + }, + # 64 - 7 + 65 => { + Name => 'WB_RGGBLevelsFlash', + Format => 'int16u[4]', + }, + # 73 - 8 + 74 => { + Name => 'WB_RGGBLevelsFluorescentL', + Format => 'int16u[4]', + }, + # 82 - 0xfe + 83 => { + Name => 'WB_RGGBLevelsUnknown', + Format => 'int16u[4]', + Unknown => 1, + }, + # 91 - 0xff + 92 => { + Name => 'WB_RGGBLevelsUserSelected', + Format => 'int16u[4]', + }, +); + +# lens information for Penax Q (ref PH) +# (306 bytes long, I wonder if this contains vignetting information too?) +%Image::ExifTool::Pentax::LensInfoQ = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'More lens information stored by the Pentax Q.', + 0x0c => { + Name => 'LensModel', + Format => 'string[30]', + }, + 0x2a => { + Name => 'LensInfo', + Format => 'string[20]', + ValueConv => '$val=~s/mm/mm /; $val', + ValueConvInv => '$val=~tr/ //d; $val', + } +); + +# Pixel shift information for the K-3II (ref PH) +%Image::ExifTool::Pentax::PixelShiftInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Pixel shift information stored by the K-3 II.', + 0x00 => { + Name => 'PixelShiftResolution', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, +); + +# AF point information for the K-1 (ref 29) +%Image::ExifTool::Pentax::AFPointInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'AF point information written by the K-1.', + # 0 - int16u: 1 (version?) + 2 => { + Name => 'NumAFPoints', + Format => 'int16u', + }, + 4 => { + Name => 'AFPointsInFocus', + Condition => '$$self{Model} =~ /K-1\b/', + Format => 'int8u[9]', + PrintConv => 'Image::ExifTool::Pentax::DecodeAFPoints($val,33,2,0x02)', + }, + 4.1 => { + Name => 'AFPointsSelected', + Condition => '$$self{Model} =~ /K-1\b/', + Format => 'int8u[9]', + PrintConv => 'Image::ExifTool::Pentax::DecodeAFPoints($val,33,2,0x03)', + }, + 4.2 => { + Name => 'AFPointsSpecial', + Condition => '$$self{Model} =~ /K-1\b/', + Format => 'int8u[9]', + PrintConv => 'Image::ExifTool::Pentax::DecodeAFPoints($val,33,2,0x03,0x03)', + }, +); + +# temperature information for some models - PH +%Image::ExifTool::Pentax::TempInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + A number of additional temperature readings are extracted from this 256-byte + binary-data block in images from models such as the K-01, K-3, K-5, K-50 and + K-500. It is currently not known where the corresponding temperature + sensors are located in the camera. + }, + # (it would be nice to know where these temperature sensors are located, + # but since according to the manual the Slow Shutter Speed NR Auto mode + # is based on "internal temperature", my guess is that there must be + # at least one on the sensor itself. These temperatures seem to rise + # more quickly than CameraTemperature when shooting video.) + 0x0c => { + Name => 'SensorTemperature', #forum6677 (was CameraTemperature2) + Format => 'int16s', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f C", $val)', + PrintConvInv => '$val=~s/ ?c$//i; $val', + }, + 0x0e => { + Name => 'SensorTemperature2', #forum6677 (was CameraTemperature3) + Format => 'int16s', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f C", $val)', + PrintConvInv => '$val=~s/ ?c$//i; $val', + }, + 0x14 => { + Name => 'CameraTemperature4', + Condition => '$$self{Model} =~ /K-5\b/', + Format => 'int16s', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?c$//i; $val', + }, + 0x16 => { # usually the same as CameraTemperature4, but not always + Name => 'CameraTemperature5', + Condition => '$$self{Model} =~ /K-5\b/', + Format => 'int16s', + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?c$//i; $val', + }, + # 0x18,0x1a,0x1c,0x1e = int16u[4] BlackPoint - PH +); + +# currently unknown info +%Image::ExifTool::Pentax::UnknownInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # K10D: first 8 bytes seem to be short integers which change with ISO (value + # is usually close to ISO/100) possibly smoothing or gain parameters? - PH + # byte 0-1 - Higher for high color temperatures (red boost or red noise suppression?) + # byte 6-7 - Higher for low color temperatures (blue boost or blue noise suppression?) + # also changing are bytes 10,11,14,15 +); + +# Pentax type 2 (Casio-like) maker notes (ref 1) +%Image::ExifTool::Pentax::Type2 = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + WRITABLE => 'int16u', + NOTES => q{ + These tags are used by the Pentax Optio 330 and 430, and are similar to the + tags used by Casio. + }, + 0x0001 => { + Name => 'RecordingMode', + PrintConv => { + 0 => 'Auto', + 1 => 'Night Scene', + 2 => 'Manual', + }, + }, + 0x0002 => { + Name => 'Quality', + PrintConv => { + 0 => 'Good', + 1 => 'Better', + 2 => 'Best', + }, + }, + 0x0003 => { + Name => 'FocusMode', + PrintConv => { + 2 => 'Custom', + 3 => 'Auto', + }, + }, + 0x0004 => { + Name => 'FlashMode', + PrintConv => { + 1 => 'Auto', + 2 => 'On', + 4 => 'Off', + 6 => 'Red-eye reduction', + }, + }, + # Casio 0x0005 is FlashIntensity + # Casio 0x0006 is ObjectDistance + 0x0007 => { + Name => 'WhiteBalance', + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Shade', + 3 => 'Tungsten', + 4 => 'Fluorescent', + 5 => 'Manual', + }, + }, + 0x000a => { + Name => 'DigitalZoom', + Writable => 'int32u', + }, + 0x000b => { + Name => 'Sharpness', + PrintConv => { + 0 => 'Normal', + 1 => 'Soft', + 2 => 'Hard', + }, + }, + 0x000c => { + Name => 'Contrast', + PrintConv => { + 0 => 'Normal', + 1 => 'Low', + 2 => 'High', + }, + }, + 0x000d => { + Name => 'Saturation', + PrintConv => { + 0 => 'Normal', + 1 => 'Low', + 2 => 'High', + }, + }, + 0x0014 => { + Name => 'ISO', + Priority => 0, + PrintConv => { + 10 => 100, + 16 => 200, + 50 => 50, #PH + 100 => 100, #PH + 200 => 200, #PH + 400 => 400, #PH + 800 => 800, #PH + 1600 => 1600, #PH + 3200 => 3200, #PH + 65534 => 'Auto 2', #PH (Q-S1 MOV) [how is this different from 65535?] + 65535 => 'Auto', #PH (K-S1 MOV) + }, + }, + 0x0017 => { + Name => 'ColorFilter', + PrintConv => { + 1 => 'Full', + 2 => 'Black & White', + 3 => 'Sepia', + }, + }, + # Casio 0x0018 is AFPoint + # Casio 0x0019 is FlashIntensity + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, + 0x1000 => { + Name => 'HometownCityCode', + Writable => 'undef', + Count => 4, + }, + 0x1001 => { #PH + Name => 'DestinationCityCode', + Writable => 'undef', + Count => 4, + }, +); + +# ASCII-based maker notes of Optio E20 and E25 - PH +%Image::ExifTool::Pentax::Type4 = ( + PROCESS_PROC => \&Image::ExifTool::HP::ProcessHP, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + The following few tags are extracted from the wealth of information + available in maker notes of the Optio E20 and E25. These maker notes are + stored as ASCII text in a format very similar to some HP models. + }, + 'F/W Version' => 'FirmwareVersion', +); + +# tags in Pentax QuickTime videos (PH - tests with Optio WP) +# (similar information in Kodak,Minolta,Nikon,Olympus,Pentax and Sanyo videos) +%Image::ExifTool::Pentax::MOV = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => 'This information is found in MOV videos from cameras such as the Optio WP.', + 0x00 => { + Name => 'Make', + Format => 'string[24]', + }, + # (01 00 at offset 0x20) + 0x26 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val ? 10 / $val : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x2a => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x32 => { + Name => 'ExposureCompensation', + Format => 'rational64s', + PrintConv => '$val ? sprintf("%+.1f", $val) : 0', + }, + 0x44 => { + Name => 'WhiteBalance', + Format => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Shade', + 3 => 'Fluorescent', #2 + 4 => 'Tungsten', + 5 => 'Manual', + }, + }, + 0x48 => { + Name => 'FocalLength', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + }, + 0xaf => { + Name => 'ISO', + Format => 'int16u', + }, +); + +# Pentax metadata in AVI videos (PH) +%Image::ExifTool::Pentax::AVI = ( + NOTES => 'Pentax-specific RIFF tags found in AVI videos.', + GROUPS => { 0 => 'MakerNotes', 2 => 'Video' }, + hymn => { + Name => 'MakerNotes', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + Start => 10, + Base => '$start', + ByteOrder => 'Unknown', # K-70 is little-endian, K-x is big-endian + }, + }, + mknt => { # (Q-S1) + Name => 'MakerNotes', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + Start => 10, + Base => '$start', + ByteOrder => 'Unknown', + }, + }, +); + +# Pentax metadata in S1 AVI maker notes (PH) +%Image::ExifTool::Pentax::S1 = ( + NOTES => 'Tags extracted from the maker notes of AVI videos from the Optio S1.', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0000 => { #5 + Name => 'MakerNoteVersion', + Writable => 'undef', + Count => 4, + }, +); + +# Pentax metadata in AVI videos from the RS1000 (PH) +%Image::ExifTool::Pentax::Junk = ( + NOTES => 'Tags found in the JUNK chunk of AVI videos from the RS1000.', + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0c => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[32]', + }, +); + +# PreviewImage information found in PXTH atom of K-01 MOV videos +%Image::ExifTool::Pentax::PXTH = ( + NOTES => 'Tags found in the PXTH atom of MOV videos from the K-01.', + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x00 => { + Name => 'PreviewImageLength', + Format => 'int32u', + }, + 0x04 => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Format => 'undef[$val{0}]', + Notes => '640-pixel-wide JPEG preview', # (360 pixels high, may depend on aspect ratio) + RawConv => '$self->ValidateImage(\$val,$tag)', + }, +); + +# information in PENT atom of MOV videos from the Optio WG-2 GPS +%Image::ExifTool::Pentax::PENT = ( + NOTES => 'Tags found in the PENT atom of MOV videos from the Optio WG-2 GPS.', + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0 => { + Name => 'Make', + Format => 'string[24]', + }, + 0x1a => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[24]', + }, + 0x38 => { #(NC) + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val ? 10 / $val : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x3c => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x44 => { #(NC) + Name => 'ExposureCompensation', + Format => 'rational64s', + PrintConv => '$val ? sprintf("%+.1f", $val) : 0', + }, + 0x54 => { #(NC) + Name => 'FocalLength', + Format => 'int32u', + PrintConv => '"$val mm"', + }, + 0x71 => { + Name => 'DateTime1', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + }, + 0x8b => { + Name => 'DateTime2', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + }, + 0xa7 => { #(NC) + Name => 'ISO', + Format => 'int32u', + }, + 0xc7 => { + Name => 'GPSVersionID', + Format => 'undef[8]', + Groups => { 1 => 'GPS', 2 => 'Location' }, + DataMember => 'GPSVersionID', + RawConv => '$$self{GPSVersionID} = ($val=~s/GPS_// ? join(" ",unpack("C*",$val)) : undef)', + PrintConv => '$val =~ tr/ /./; $val', + }, + 0xcf => { + Name => 'GPSLatitudeRef', + Condition => '$$self{GPSVersionID}', + Format => 'string[2]', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + N => 'North', + S => 'South', + }, + }, + 0xd1 => { + Name => 'GPSLatitude', + Condition => '$$self{GPSVersionID}', + Format => 'rational64u[3]', + Groups => { 1 => 'GPS', 2 => 'Location' }, + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', + }, + 0xe9 => { + Name => 'GPSLongitudeRef', + Condition => '$$self{GPSVersionID}', + Format => 'string[2]', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + E => 'East', + W => 'West', + }, + }, + 0xeb => { + Name => 'GPSLongitude', + Condition => '$$self{GPSVersionID}', + Format => 'rational64u[3]', + Groups => { 1 => 'GPS', 2 => 'Location' }, + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', + }, + 0x103 => { + Name => 'GPSAltitudeRef', + Condition => '$$self{GPSVersionID}', + Format => 'int8u', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + 0 => 'Above Sea Level', + 1 => 'Below Sea Level', + }, + }, + 0x104 => { + Name => 'GPSAltitude', + Condition => '$$self{GPSVersionID}', + Format => 'rational64u', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"', + }, + 0x11c => { + Name => 'GPSTimeStamp', + Condition => '$$self{GPSVersionID}', + Groups => { 1 => 'GPS', 2 => 'Time' }, + Format => 'rational64u[3]', + ValueConv => 'Image::ExifTool::GPS::ConvertTimeStamp($val)', + PrintConv => 'Image::ExifTool::GPS::PrintTimeStamp($val)', + }, + 0x134 => { + Name => 'GPSSatellites', + Condition => '$$self{GPSVersionID}', + Format => 'string[3]', + Groups => { 1 => 'GPS', 2 => 'Location' }, + }, + 0x137 => { + Name => 'GPSStatus', + Condition => '$$self{GPSVersionID}', + Format => 'string[2]', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + A => 'Measurement Active', + V => 'Measurement Void', + }, + }, + 0x139 => { + Name => 'GPSMeasureMode', + Condition => '$$self{GPSVersionID}', + Format => 'string[2]', + Groups => { 1 => 'GPS', 2 => 'Location' }, + PrintConv => { + 2 => '2-Dimensional Measurement', + 3 => '3-Dimensional Measurement', + }, + }, + 0x13b => { + Name => 'GPSMapDatum', + Condition => '$$self{GPSVersionID}', + Format => 'string[7]', + Groups => { 1 => 'GPS', 2 => 'Location' }, + }, + 0x142 => { + Name => 'GPSDateStamp', + Condition => '$$self{GPSVersionID}', + Groups => { 1 => 'GPS', 2 => 'Time' }, + Format => 'string[11]', + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + }, + 0x173 => { #(NC) + Name => 'AudioCodecID', + Format => 'string[4]', + }, + 0x7d3 => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Format => 'undef[$size-0x7d3]', + Notes => '640x480 JPEG preview image', # (black borders pad to 480 pixels high) + RawConv => '$self->ValidateImage(\$val,$tag)', + }, +); + +# tags in Pentax Optio RZ18 AVI videos (ref PH) +# (very similar to Olympus::AVI tags) +%Image::ExifTool::Pentax::Junk2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FIRST_ENTRY => 0, + NOTES => 'This information is found in AVI videos from the Optio RZ18.', + 0x12 => { + Name => 'Make', + Format => 'string[24]', + }, + 0x2c => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[24]', + }, + 0x5e => { + Name => 'FNumber', + Format => 'rational64u', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x83 => { + Name => 'DateTime1', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + }, + 0x9d => { + Name => 'DateTime2', + Format => 'string[24]', + Groups => { 2 => 'Time' }, + }, + 0x12b => { + Name => 'ThumbnailWidth', + Format => 'int16u', + }, + 0x12d => { + Name => 'ThumbnailHeight', + Format => 'int16u', + }, + 0x12f => { + Name => 'ThumbnailLength', + Format => 'int32u', + }, + 0x133 => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Format => 'undef[$val{0x12f}]', + Notes => '160x120 JPEG thumbnail image', + RawConv => '$self->ValidateImage(\$val,$tag)', + }, +); + +#------------------------------------------------------------------------------ +# Convert filter settings (ref PH, K-5) +# Inputs: 0) value to convert, 1) flag for inverse conversion, 2) lookup table +# Returns: converted value +sub PrintFilter($$$) +{ + my ($val, $inv, $conv) = @_; + my (@vals, @cval, $t, $v); + + if (not $inv) { + # forward conversion (reading): + @vals = split ' ',$val; + $t = shift @vals; + push @cval, $$conv{$t} || "Unknown ($t)"; + while (@vals) { + $t = shift @vals; + $v = shift @vals; + next unless $t; + last unless defined $v; + my $c = $filterSettings{$t}; + if ($c) { + my $c1 = $$c[1]; + if (ref $c1) { + $v = $$c1{$v} || "Unknown($v)"; + } elsif ($v) { + $v = sprintf $c1, $v; + } + push @cval, "$$c[0]=$v"; + } else { + push @cval, "Unknown($t)=$v"; + } + } + return @cval ? \@cval : undef; + } else { + # reverse conversion (writing): + @vals = split /,\s*/, $val; + # convert filter name + delete $$conv{OTHER}; # avoid recursion + $v = Image::ExifTool::ReverseLookup(shift(@vals), $conv); + $$conv{OTHER} = \&PrintFilter; + return undef unless defined $v; + push @cval, $v; + # generate a lookup table for the filter setting name + my %settingNames; + $settingNames{$_} = $filterSettings{$_}[0] foreach keys %filterSettings; + # convert filter settings + foreach $v (@vals) { + $v =~ /^(.*)=(.*)$/ or return undef; + ($t, $v) = ($1, $2); + # look up settings name + $t = Image::ExifTool::ReverseLookup($t, \%settingNames); + return undef unless defined $t; + if (ref $filterSettings{$t}[1]) { + # look up settings value + $v = Image::ExifTool::ReverseLookup($v, $filterSettings{$t}[1]); + return undef unless defined $v; + } else { + return undef unless Image::ExifTool::IsInt($v); + } + push @cval, $t, $v; + } + push @cval, (0) x (17 - @cval) if @cval < 17; # pad with zeros if necessary + return join(' ', @cval); + } +} + +#------------------------------------------------------------------------------ +# Decode AF bit mask (ref 29) +# Inputs: 0) raw binary value, 1) number of AF points, 2) number of bits per AF point, +# 3) bit mask, 4) bit value (undef for any bit set) +sub DecodeAFPoints($$$$;$) +{ + my ($val, $num, $bits, $mask, $bitVal) = @_; + my @bytes = split ' ', $val; + my $i = 1; # (starts at AF point number 1) + my $shift = 8 - $bits; + my $byte = shift @bytes; + my @bitList; + for (;;) { + if ($bitVal) { + push @bitList, $i if (($byte >> $shift) & $mask) == $bitVal; + } else { + push @bitList, $i if ($byte >> $shift) & $mask; + } + last if ++$i > $num; + $shift -= $bits; + if ($shift < 0) { + last unless @bytes; + $byte = shift @bytes; + $shift += 8; + } + } + return join(',', @bitList); +} + +#------------------------------------------------------------------------------ +# Convert Pentax hex-based EV (modulo 8) to real number +# Inputs: 0) value to convert +# eg) 0x00 -> 0 +# 0x03 -> 0.33333 +# 0x04 -> 0.5 +# 0x05 -> 0.66666 +# 0x08 -> 1 ... etc +sub PentaxEv($) +{ + my $val = shift; + if ($val & 0x01) { + my $sign = $val < 0 ? -1 : 1; + my $frac = ($val * $sign) & 0x07; + if ($frac == 0x03) { + $val += $sign * ( 8 / 3 - $frac); + } elsif ($frac == 0x05) { + $val += $sign * (16 / 3 - $frac); + } + } + return $val / 8; +} + +#------------------------------------------------------------------------------ +# Convert number to Pentax hex-based EV (modulo 8) +# Inputs: 0) number +# Returns: Pentax EV code +sub PentaxEvInv($) +{ + my $num = shift; + my $val = $num * 8; + # extra fudging makes sure 0.3 and 0.33333 both round up to 3, etc + my $sign = $num < 0 ? -1 : 1; + my $inum = $num * $sign - int($num * $sign); + if ($inum > 0.29 and $inum < 0.4) { + $val += $sign / 3; + } elsif ($inum > 0.6 and $inum < .71) { + $val -= $sign / 3; + } + return int($val + 0.5 * $sign); +} + +#------------------------------------------------------------------------------ +# Encrypt or decrypt Pentax ShutterCount (symmetrical encryption) - PH +# Inputs: 0) shutter count value, 1) ExifTool object ref +# Returns: Encrypted or decrypted ShutterCount +sub CryptShutterCount($$) +{ + my ($val, $et) = @_; + # Pentax Date and Time values are used in the encryption + return undef unless $$et{PentaxDate} and $$et{PentaxTime} and + length($$et{PentaxDate})==4 and length($$et{PentaxTime})>=3; + # get Date and Time as integers (after padding Time with a null byte) + my $date = unpack('N', $$et{PentaxDate}); + my $time = unpack('N', $$et{PentaxTime} . "\0"); + return $val ^ $date ^ (0xffffffff - $time); +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Pentax - Pentax/Asahi 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 +Pentax and Asahi maker notes in EXIF information. + +=head1 NOTES + +I couldn't find a good source for Pentax maker notes information, but I've +managed to discover a fair bit of information by analyzing sample images +downloaded from the internet, and through tests with my own Optio S, Optio +WP, K10D, K-01 and K-5, and with help provided by other ExifTool users (see +L</ACKNOWLEDGEMENTS>). + +The Pentax maker notes are stored in standard EXIF format, but the offsets +used for some of their cameras are wacky. The Optio 330 gives the offset +relative to the offset of the tag in the directory, the Optio WP uses a base +offset in the middle of nowhere, and the Optio 550 uses different (and +totally illogical) bases for different menu entries. Very weird. (It +wouldn't surprise me if Pentax can't read their own maker notes!) Luckily, +there are only a few entries in the maker notes which are large enough to +require offsets, so this doesn't affect much useful information. ExifTool +attempts to make sense of this fiasco by making an assumption about where +the information should be stored to deduce the correct offsets. + +=head1 REFERENCES + +=over 4 + +=item L<Image::MakerNotes::Pentax|Image::MakerNotes::Pentax> + +=item L<http://johnst.org/sw/exiftags/> (Asahi models) + +=item L<http://kobe1995.jp/~kaz/astro/istD.html> + +=item L<http://www.cybercom.net/~dcoffin/dcraw/> + +=item (...plus lots of testing with my Optio WP, K10D and K-5!) + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Wayne Smith, John Francis, Douglas O'Brien, Cvetan Ivanov, Jens +Duttke, Dave Nicholson, Iliah Borg, Klaus Homeister, Louis Granboulan and +Andras Salamon for help figuring out some Pentax tags, Ger Vermeulen and +Niels Kristian Bech Jensen for contributing print conversion values for some +tags, and everyone who helped contribute to the LensType values. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Pentax Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::Info(3pm)|Image::Info> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/PhaseOne.pm b/ExifTool/lib/Image/ExifTool/PhaseOne.pm new file mode 100644 index 0000000..eba7349 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PhaseOne.pm @@ -0,0 +1,746 @@ +#------------------------------------------------------------------------------ +# File: PhaseOne.pm +# +# Description: Phase One maker notes tags +# +# Revisions: 2013-02-17 - P. Harvey Created +# +# References: 1) http://www.cybercom.net/~dcoffin/dcraw/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::PhaseOne; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.09'; + +sub WritePhaseOne($$$); +sub ProcessPhaseOne($$$); + +# default formats based on PhaseOne format size +my @formatName = ( undef, 'string', 'int16s', undef, 'int32s' ); + +# Phase One maker notes (ref PH) +%Image::ExifTool::PhaseOne::Main = ( + PROCESS_PROC => \&ProcessPhaseOne, + WRITE_PROC => \&WritePhaseOne, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => '1', + FORMAT => 'int32s', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + VARS => { ENTRY_SIZE => 16 }, # (entries contain a format field) + NOTES => 'These tags are extracted from the maker notes of Phase One images.', + 0x0100 => { #1 + Name => 'CameraOrientation', + ValueConv => '$val & 0x03', # ignore other bits for now + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + 3 => 'Rotate 180', + }, + }, + # 0x0101 - int32u: 96,160,192,256,544 (same as 0x0213) + 0x0102 => { Name => 'SerialNumber', Format => 'string' }, + # 0x0103 - int32u: 19,20,59769034 + # 0x0104 - int32u: 50,200 + 0x0105 => 'ISO', + 0x0106 => { + Name => 'ColorMatrix1', + Format => 'float', + Count => 9, + PrintConv => q{ + my @a = map { sprintf('%.3f', $_) } split ' ', $val; + return "@a"; + }, + PrintConvInv => '$val', + }, + 0x0107 => { Name => 'WB_RGBLevels', Format => 'float', Count => 3 }, + 0x0108 => 'SensorWidth', + 0x0109 => 'SensorHeight', + 0x010a => 'SensorLeftMargin', #1 + 0x010b => 'SensorTopMargin', #1 + 0x010c => 'ImageWidth', + 0x010d => 'ImageHeight', + 0x010e => { #1 + Name => 'RawFormat', + # 1 = raw bit mask 0x5555 (>1 mask 0x1354) + # >2 = compressed + # 5 = non-linear + PrintConv => { #PH + 1 => 'RAW 1', #? (encrypted) + 2 => 'RAW 2', #? (encrypted) + 3 => 'IIQ L', # (now "L14", ref IB) + # 4? + 5 => 'IIQ S', + 6 => 'IIQ Sv2', # (now "S14" for "IIQ 14 Smart" and "IIQ 14 Sensor+", ref IB) + 8 => 'IIQ L16', #IB ("IIQ 16 Extended" and "IIQ 16 Large") + }, + }, + 0x010f => { + Name => 'RawData', + Format => 'undef', # (actually 2-byte integers, but don't convert) + Binary => 1, + IsImageData => 1, + PutFirst => 1, + Writable => 0, + Drop => 1, # don't copy to other file types + }, + 0x0110 => { #1 + Name => 'SensorCalibration', + SubDirectory => { TagTable => 'Image::ExifTool::PhaseOne::SensorCalibration' }, + }, + 0x0112 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Format => 'int32u', + Writable => 0, # (don't write because this is an encryption key for RawFormat 1 and 2) + Priority => 0, + Shift => 'Time', + Groups => { 2 => 'Time' }, + Notes => 'may be used as a key to encrypt the raw data', #1 + ValueConv => 'ConvertUnixTime($val)', + ValueConvInv => 'GetUnixTime($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + 0x0113 => 'ImageNumber', # (NC) + 0x0203 => { Name => 'Software', Format => 'string' }, + 0x0204 => { Name => 'System', Format => 'string' }, + # 0x020b - int32u: 0,1 + # 0x020c - int32u: 1,2 + # 0x020e - int32u: 1,3 + 0x0210 => { # (NC) (used in linearization formula - ref 1) + Name => 'SensorTemperature', + Format => 'float', + PrintConv => 'sprintf("%.2f C",$val)', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 0x0211 => { # (NC) + Name => 'SensorTemperature2', + Format => 'float', + PrintConv => 'sprintf("%.2f C",$val)', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 0x0212 => { + Name => 'UnknownDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + # (this time is within about 10 minutes before or after 0x0112) + Unknown => 1, + Shift => 'Time', + ValueConv => 'ConvertUnixTime($val)', + ValueConvInv => 'GetUnixTime($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + # 0x0213 - int32u: 96,160,192,256,544 (same as 0x0101) + # 0x0215 - int32u: 4,5 + # 0x021a - used by dcraw + 0x021c => { Name => 'StripOffsets', Binary => 1, Writable => 0 }, + 0x021d => 'BlackLevel', #1 + # 0x021e - int32u: 1 + # 0x0220 - int32u: 32 + # 0x0221 - float: 0-271 + 0x0222 => 'SplitColumn', #1 + 0x0223 => { Name => 'BlackLevelData', Format => 'int16u', Count => -1, Binary => 1 }, #1 + # 0x0224 - int32u: 1688,2748,3372 + 0x0225 => { + Name => 'PhaseOne_0x0225', + Format => 'int16s', + Count => -1, + Flags => ['Unknown','Hidden'], + PrintConv => 'length($val) > 60 ? substr($val,0,55) . "[...]" : $val', + }, + 0x0226 => { + Name => 'ColorMatrix2', + Format => 'float', + Count => 9, + PrintConv => q{ + my @a = map { sprintf('%.3f', $_) } split ' ', $val; + return "@a"; + }, + PrintConvInv => '$val', + }, + # 0x0227 - int32u: 0,1 + # 0x0228 - int32u: 1,2 + # 0x0229 - int32s: -2,0 + 0x0267 => { #PH + Name => 'AFAdjustment', + Format => 'float', + }, + 0x022b => { #PH + Name => 'PhaseOne_0x022b', + Format => 'float', + Flags => ['Unknown','Hidden'], + }, + # 0x0242 - int32u: 55 + # 0x0244 - int32u: 102 + # 0x0245 - float: 1.2 + 0x0258 => { #PH + Name => 'PhaseOne_0x0258', + Format => 'int16s', + Flags => ['Unknown','Hidden'], + PrintConv => 'length($val) > 60 ? substr($val,0,55) . "[...]" : $val', + }, + 0x025a => { #PH + Name => 'PhaseOne_0x025a', + Format => 'int16s', + Flags => ['Unknown','Hidden'], + PrintConv => 'length($val) > 60 ? substr($val,0,55) . "[...]" : $val', + }, + # 0x0300 - int32u: 100,101,102 + 0x0301 => { Name => 'FirmwareVersions', Format => 'string' }, + # 0x0304 - int32u: 8,3073,3076 + 0x0400 => { + Name => 'ShutterSpeedValue', + Format => 'float', + ValueConv => 'abs($val)<100 ? 2**(-$val) : 0', + ValueConvInv => '$val>0 ? -log($val)/log(2) : -100', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x0401 => { + Name => 'ApertureValue', + Format => 'float', + ValueConv => '2 ** ($val / 2)', + ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x0402 => { + Name => 'ExposureCompensation', + Format => 'float', + PrintConv => 'sprintf("%.3f",$val)', + PrintConvInv => '$val', + }, + 0x0403 => { + Name => 'FocalLength', + Format => 'float', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + # 0x0404 - int32u: 0,3 + # 0x0405 - int32u? (big numbers) + # 0x0406 - int32u: 1 + # 0x0407 - float: -0.333 (exposure compensation again?) + # 0x0408-0x0409 - int32u: 1 + 0x0410 => { Name => 'CameraModel', Format => 'string' }, + # 0x0411 - int32u: 33556736 + 0x0412 => { Name => 'LensModel', Format => 'string' }, + 0x0414 => { + Name => 'MaxApertureValue', + Format => 'float', + ValueConv => '2 ** ($val / 2)', + ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x0415 => { + Name => 'MinApertureValue', + Format => 'float', + ValueConv => '2 ** ($val / 2)', + ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + # 0x0416 - float: (min focal length? ref LR, Credo50) (but looks more like an int32u date for the 645DF - PH) + # 0x0417 - float: 80 (max focal length? ref LR) + 0x0455 => { #PH + Name => 'Viewfinder', + Format => 'string', + }, +); + +# Phase One metadata (ref 1) +%Image::ExifTool::PhaseOne::SensorCalibration = ( + PROCESS_PROC => \&ProcessPhaseOne, + WRITE_PROC => \&WritePhaseOne, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + TAG_PREFIX => 'SensorCalibration', + WRITE_GROUP => 'PhaseOne', + VARS => { ENTRY_SIZE => 12 }, # (entries do not contain a format field) + 0x0400 => { + Name => 'SensorDefects', + # list of defects. each defect is 4 x int16u values: + # 0=column, 1=row, 2=type (129=bad pixel, 131=bad column), 3=? + # (but it isn't really worth the time decoding this -- it can be a few hundred kB) + Format => 'undef', + Binary => 1, + }, + 0x0401 => { + Name => 'AllColorFlatField1', + Format => 'undef', + Flags => ['Unknown','Binary'], + }, + 0x0404 => { #PH + Name => 'SensorCalibration_0x0404', + Format => 'string', + Flags => ['Unknown','Hidden'], + }, + 0x0405 => { #PH + Name => 'SensorCalibration_0x0405', + Format => 'string', + Flags => ['Unknown','Hidden'], + }, + 0x0406 => { #PH + Name => 'SensorCalibration_0x0406', + Format => 'string', + Flags => ['Unknown','Hidden'], + }, + 0x0407 => { #PH + Name => 'SerialNumber', + Format => 'string', + Writable => 1, + }, + 0x0408 => { #PH + Name => 'SensorCalibration_0x0408', + Format => 'float', + Flags => ['Unknown','Hidden'], + }, + 0x040b => { + Name => 'RedBlueFlatField', + Format => 'undef', + Flags => ['Unknown','Binary'], + }, + 0x040f => { #PH + Name => 'SensorCalibration_0x040f', + Format => 'undef', + Flags => ['Unknown','Hidden'], + }, + 0x0410 => { + Name => 'AllColorFlatField2', + Format => 'undef', + Flags => ['Unknown','Binary'], + }, + # 0x0412 - used by dcraw + 0x0413 => { #PH + Name => 'SensorCalibration_0x0413', + Format => 'double', + Flags => ['Unknown','Hidden'], + }, + 0x0414 => { #PH + Name => 'SensorCalibration_0x0414', + Format => 'undef', + Flags => ['Unknown','Hidden'], + ValueConv => q{ + my $order = GetByteOrder(); + if (length $val >= 8 and SetByteOrder(substr($val,0,2))) { + $val = ReadValue(\$val, 2, 'int16u', 1, length($val)-2) . ' ' . + ReadValue(\$val, 4, 'float', undef, length($val)-4); + SetByteOrder($order); + } + return $val; + }, + }, + 0x0416 => { + Name => 'AllColorFlatField3', + Format => 'undef', + Flags => ['Unknown','Binary'], + }, + 0x0418 => { #PH + Name => 'SensorCalibration_0x0418', + Format => 'undef', + Flags => ['Unknown','Hidden'], + }, + 0x0419 => { + Name => 'LinearizationCoefficients1', + Format => 'float', + PrintConv => 'my @a=split " ",$val;join " ", map { sprintf("%.5g",$_) } @a', + }, + 0x041a => { + Name => 'LinearizationCoefficients2', + Format => 'float', + PrintConv => 'my @a=split " ",$val;join " ", map { sprintf("%.5g",$_) } @a', + }, + 0x041c => { #PH + Name => 'SensorCalibration_0x041c', + Format => 'float', + Flags => ['Unknown','Hidden'], + }, + 0x041e => { #PH + Name => 'SensorCalibration_0x041e', + Format => 'undef', + Flags => ['Unknown','Hidden'], + ValueConv => q{ + my $order = GetByteOrder(); + if (length $val >= 8 and SetByteOrder(substr($val,0,2))) { + $val = ReadValue(\$val, 2, 'int16u', 1, length($val)-2) . ' ' . + ReadValue(\$val, 4, 'float', undef, length($val)-4); + SetByteOrder($order); + } + return $val; + }, + }, +); + +#------------------------------------------------------------------------------ +# Do HTML dump of an IFD entry +# Inputs: 0) ExifTool ref, 1) tag table ref, 3) tag ID, 4) tag value, +# 5) IFD entry offset, 6) IFD entry size, 7) parameter hash +sub HtmlDump($$$$$$%) +{ + my ($et, $tagTablePtr, $tagID, $value, $entry, $entryLen, %parms) = @_; + my ($dirName, $index, $formatStr, $dataPos, $base, $size, $valuePtr) = + @parms{qw(DirName Index Format DataPos Base Size Start)}; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID); + my ($tagName, $colName, $subdir); + my $count = $parms{Count} || $size; + $base = 0 unless defined $base; + if ($tagInfo) { + $tagName = $$tagInfo{Name}; + $subdir = $$tagInfo{SubDirectory}; + if ($$tagInfo{Format}) { + $formatStr = $$tagInfo{Format}; + $count = $size / Image::ExifTool::FormatSize($formatStr); + } + } else { + $tagName = sprintf("Tag 0x%.4x", $tagID); + } + my $dname = sprintf("${dirName}-%.2d", $index); + # build our tool tip + my $fstr = "$formatStr\[$count]"; + my $tip = sprintf("Tag ID: 0x%.4x\n", $tagID) . + "Format: $fstr\nSize: $size bytes\n"; + if ($size > 4) { + $tip .= sprintf("Value offset: 0x%.4x\n", $valuePtr - $base); + $tip .= sprintf("Actual offset: 0x%.4x\n", $valuePtr + $dataPos); + $tip .= sprintf("Offset base: 0x%.4x\n", $dataPos + $base); + $colName = "<span class=F>$tagName</span>"; + } else { + $colName = $tagName; + } + unless (ref $value) { + my $tval = length($value) > 32 ? substr($value,0,28) . '[...]' : $value; + $tval =~ tr/\x00-\x1f\x7f-\xff/./; + $tip .= "Value: $tval"; + } + $et->HDump($entry+$dataPos, $entryLen, "$dname $colName", $tip, 1); + if ($size > 4) { + my $dumpPos = $valuePtr + $dataPos; + # add value data block + $et->HDump($dumpPos,$size,"$tagName value",'SAME', $subdir ? 0x04 : 0); + } +} + +#------------------------------------------------------------------------------ +# Write PhaseOne maker notes (both types of PhaseOne IFD) +# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref +# Returns: data block or undef on error +sub WritePhaseOne($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + + # nothing to do if we aren't changing any PhaseOne tags + my $newTags = $et->GetNewTagInfoHash($tagTablePtr); + return undef unless %$newTags or $$et{DropTags} or $$et{EDIT_DIRS}{PhaseOne}; + + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos} || 0; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || $$dirInfo{DataLen} - $dirStart; + my $dirName = $$dirInfo{DirName}; + my $verbose = $et->Options('Verbose'); + + return undef if $dirLen < 12; + unless ($$tagTablePtr{VARS} and $$tagTablePtr{VARS}{ENTRY_SIZE}) { + $et->WarnOnce("No ENTRY_SIZE for $$tagTablePtr{TABLE_NAME}"); + return undef; + } + my $entrySize = $$tagTablePtr{VARS}{ENTRY_SIZE}; + my $ifdType = $$tagTablePtr{TAG_PREFIX} || 'PhaseOne'; + my $hdr = substr($$dataPt, $dirStart, 12); + if ($entrySize == 16) { + return undef unless $hdr =~ /^(IIII.waR|MMMMRaw.)/s; + } elsif ($hdr !~ /^(IIII\x01\0\0\0|MMMM\0\0\0\x01)/s) { + $et->Warn("Unrecognized $ifdType directory version"); + return undef; + } + SetByteOrder(substr($hdr, 0, 2)); + # get offset to start of PhaseOne directory + my $ifdStart = Get32u(\$hdr, 8); + return undef if $ifdStart + 8 > $dirLen; + # initialize output directory buffer with (fixed) number of entries plus 4-byte padding + my $dirBuff = substr($$dataPt, $dirStart + $ifdStart, 8); + # get number of entries in PhaseOne directory + my $numEntries = Get32u(\$dirBuff, 0); + my $ifdEnd = $ifdStart + 8 + $entrySize * $numEntries; + return undef if $numEntries < 2 or $numEntries > 300 or $ifdEnd > $dirLen; + my $hdrBuff = $hdr; + my $valBuff = ''; # buffer for value data + my $fixup = new Image::ExifTool::Fixup; + my $index; + for ($index=0; $index<$numEntries; ++$index) { + my $entry = $dirStart + $ifdStart + 8 + $entrySize * $index; + my $tagID = Get32u($dataPt, $entry); + my $size = Get32u($dataPt, $entry+$entrySize-8); + my ($formatSize, $formatStr); + if ($entrySize == 16) { + $formatSize = Get32u($dataPt, $entry+4); + $formatStr = $formatName[$formatSize]; + unless ($formatStr) { + $et->Warn("Possibly invalid $ifdType IFD entry $index",1); + delete $$newTags{$tagID}; # make sure we don't try to change this one + } + } else { + # (no format code for SensorCalibration IFD entries) + $formatSize = 1; + $formatStr = 'undef'; + } + my $valuePtr = $entry + $entrySize - 4; + if ($size > 4) { + if ($size > 0x7fffffff) { + $et->Error("Invalid size for $ifdType IFD entry $index",1); + return undef; + } + $valuePtr = Get32u($dataPt, $valuePtr); + if ($valuePtr + $size > $dirLen) { + $et->Error(sprintf("Invalid offset 0x%.4x for $ifdType IFD entry $index",$valuePtr),1); + return undef; + } + $valuePtr += $dirStart; + } + my $value = substr($$dataPt, $valuePtr, $size); + my $tagInfo = $$newTags{$tagID} || $$tagTablePtr{$tagID}; + $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID) if $tagInfo and ref($tagInfo) ne 'HASH'; + if ($$newTags{$tagID}) { + $formatStr = $$tagInfo{Format} if $$tagInfo{Format}; + my $count = int($size / Image::ExifTool::FormatSize($formatStr)); + my $val = ReadValue(\$value, 0, $formatStr, $count, $size); + my $nvHash = $et->GetNewValueHash($tagInfo); + if ($et->IsOverwriting($nvHash, $val)) { + my $newVal = $et->GetNewValue($nvHash); + # allow count to change for string and undef types only + undef $count if $formatStr eq 'string' or $formatStr eq 'undef'; + my $newValue = WriteValue($newVal, $formatStr, $count); + if (defined $newValue) { + $value = $newValue; + $size = length $newValue; + $et->VerboseValue("- $dirName:$$tagInfo{Name}", $val); + $et->VerboseValue("+ $dirName:$$tagInfo{Name}", $newVal); + ++$$et{CHANGED}; + } + } + } elsif ($tagInfo and $$tagInfo{SubDirectory}) { + my $subTable = GetTagTable($$tagInfo{SubDirectory}{TagTable}); + my %subdirInfo = ( + DirName => $$tagInfo{Name}, + DataPt => \$value, + DataLen => length $value, + ); + my $newValue = $et->WriteDirectory(\%subdirInfo, $subTable); + if (defined $newValue and length($newValue)) { + $value = $newValue; + $size = length $newValue; + } + } elsif ($$et{DropTags} and (($tagInfo and $$tagInfo{Drop}) or $size > 8192)) { + # decrease the number of entries in the directory + Set32u(Get32u(\$dirBuff, 0) - 1, \$dirBuff, 0); + next; # drop this tag + } + # add the tagID, possibly format size, and size to this directory entry + $dirBuff .= substr($$dataPt, $entry, $entrySize - 8) . Set32u($size); + + # pad value to an even 4-byte boundary just in case + $value .= ("\0" x (4 - ($size & 0x03))) if $size & 0x03 or not $size; + if ($size <= 4) { + # store value in place of the IFD value pointer (already padded to 4 bytes) + $dirBuff .= $value; + } elsif ($tagInfo and $$tagInfo{PutFirst}) { + # store value immediately after header + $dirBuff .= Set32u(length $hdrBuff); + $hdrBuff .= $value; + } else { + # store value at end of value buffer + $fixup->AddFixup(length $dirBuff); + $dirBuff .= Set32u(length $valBuff); + $valBuff .= $value; + } + } + # apply necessary fixup to offsets in PhaseOne directory + $$fixup{Shift} = length $hdrBuff; + $fixup->ApplyFixup(\$dirBuff); + # set pointer to PhaseOneIFD in header + Set32u(length($hdrBuff) + length($valBuff), \$hdrBuff, 8); + return $hdrBuff . $valBuff . $dirBuff; +} + +#------------------------------------------------------------------------------ +# Read Phase One maker notes +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# Notes: This routine processes both the main PhaseOne IFD type (with 16 bytes +# per entry), and the SensorCalibration IFD type (12 bytes per entry) +sub ProcessPhaseOne($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = ($$dirInfo{DataPos} || 0) + ($$dirInfo{Base} || 0); + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || $$dirInfo{DataLen} - $dirStart; + my $binary = $et->Options('Binary'); + my $verbose = $et->Options('Verbose'); + my $hash = $$et{ImageDataHash}; + my $htmlDump = $$et{HTML_DUMP}; + + return 0 if $dirLen < 12; + unless ($$tagTablePtr{VARS} and $$tagTablePtr{VARS}{ENTRY_SIZE}) { + $et->WarnOnce("No ENTRY_SIZE for $$tagTablePtr{TABLE_NAME}"); + return undef; + } + my $entrySize = $$tagTablePtr{VARS}{ENTRY_SIZE}; + my $ifdType = $$tagTablePtr{TAG_PREFIX} || 'PhaseOne'; + + my $hdr = substr($$dataPt, $dirStart, 12); + if ($entrySize == 16) { + return 0 unless $hdr =~ /^(IIII.waR|MMMMRaw.)/s; + } elsif ($hdr !~ /^(IIII\x01\0\0\0|MMMM\0\0\0\x01)/s) { + $et->Warn("Unrecognized $ifdType directory version"); + return 0; + } + SetByteOrder(substr($hdr, 0, 2)); + # get offset to start of PhaseOne directory + my $ifdStart = Get32u(\$hdr, 8); + return 0 if $ifdStart + 8 > $dirLen; + # get number of entries in PhaseOne directory + my $numEntries = Get32u($dataPt, $dirStart + $ifdStart); + my $ifdEnd = $ifdStart + 8 + $entrySize * $numEntries; + return 0 if $numEntries < 2 or $numEntries > 300 or $ifdEnd > $dirLen; + $et->VerboseDir($ifdType, $numEntries); + if ($htmlDump) { + $et->HDump($dirStart + $dataPos, 8, "$ifdType header"); + $et->HDump($dirStart + $dataPos + 8, 4, "$ifdType IFD offset"); + $et->HDump($dirStart + $dataPos + $ifdStart, 4, "$ifdType entries", + "Entry count: $numEntries"); + $et->HDump($dirStart + $dataPos + $ifdStart + 4, 4, '[unused]'); + } + my $index; + for ($index=0; $index<$numEntries; ++$index) { + my $entry = $dirStart + $ifdStart + 8 + $entrySize * $index; + my $tagID = Get32u($dataPt, $entry); + my $size = Get32u($dataPt, $entry+$entrySize-8); + my $valuePtr = $entry + $entrySize - 4; + my ($formatSize, $formatStr, $value); + if ($entrySize == 16) { + # (format code only for the 16-byte IFD entry) + $formatSize = Get32u($dataPt, $entry+4); + $formatStr = $formatName[$formatSize]; + unless ($formatStr) { + $et->WarnOnce("Unrecognized $ifdType format size $formatSize",1); + $formatSize = 1; + $formatStr = 'undef'; + } + } elsif ($size %4) { + $formatSize = 1; + $formatStr = 'undef'; + } else { + $formatSize = 4; + $formatStr = 'int32s'; + } + if ($size > 4) { + if ($size > 0x7fffffff) { + $et->Warn("Invalid size for $ifdType IFD entry $index"); + return 0; + } + $valuePtr = Get32u($dataPt, $valuePtr); + if ($valuePtr + $size > $dirLen) { + $et->Warn(sprintf("Invalid offset 0x%.4x for $ifdType IFD entry $index",$valuePtr)); + return 0; + } + $valuePtr += $dirStart; + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID); + if ($tagInfo) { + $formatStr = $$tagInfo{Format} if $$tagInfo{Format}; + } else { + next unless $verbose or $htmlDump; + } + my $count = int($size / Image::ExifTool::FormatSize($formatStr)); + if ($count > 100000 and not $binary) { + $value = \ "Binary data $size bytes"; + } else { + $value = ReadValue($dataPt,$valuePtr,$formatStr,$count,$size); + # try to distinguish between the various format types + if ($formatStr eq 'int32s') { + my ($val) = split ' ', $value; + if (defined $val) { + # get floating point exponent (has bias of 127) + my $exp = ($val & 0x7f800000) >> 23; + if ($exp > 120 and $exp < 140) { + $formatStr = 'float'; + $value = ReadValue($dataPt,$valuePtr,$formatStr,$count,$size); + } + } + } + } + if ($hash and $tagInfo and $$tagInfo{IsImageData}) { + my ($pos, $len) = ($valuePtr, $size); + while ($len) { + my $n = $len > 65536 ? 65536 : $len; + my $tmp = substr($$dataPt, $pos, $n); + $hash->add($tmp); + $len -= $n; + $pos += $n; + } + $et->VPrint(0, "$$et{INDENT}(ImageDataHash: $size bytes of PhaseOne:$$tagInfo{Name})\n"); + } + my %parms = ( + DirName => $ifdType, + Index => $index, + DataPt => $dataPt, + DataPos => $dataPos, + Size => $size, + Start => $valuePtr, + Format => $formatStr, + Count => $count + ); + $htmlDump and HtmlDump($et, $tagTablePtr, $tagID, $value, $entry, $entrySize, + %parms, Base => $dirStart); + $et->HandleTag($tagTablePtr, $tagID, $value, %parms); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PhaseOne - Phase One 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 decode Phase +One maker notes. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 SEE ALSO + +L<Image::ExifTool::TagNames/PhaseOne Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/PhotoCD.pm b/ExifTool/lib/Image/ExifTool/PhotoCD.pm new file mode 100644 index 0000000..cffb1f2 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PhotoCD.pm @@ -0,0 +1,506 @@ +#------------------------------------------------------------------------------ +# File: PhotoCD.pm +# +# Description: Read Kodak Photo CD Image Pac (PCD) metadata +# +# Revisions: 2012/05/07 - P. Harvey Created +# +# References: 1) http://pcdtojpeg.sourceforge.net/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::PhotoCD; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.03'; + +sub ProcessExtData($$$); + +# PhotoCD info +%Image::ExifTool::PhotoCD::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + NOTES => 'Tags extracted from Kodak Photo CD Image Pac (PCD) files.', + 7 => { + Name => 'SpecificationVersion', + Format => 'int8u[2]', + RawConv => '$val eq "255 255" ? "n/a" : $val', + ValueConv => '$val =~ tr/ /./; $val', + }, + 9 => { + Name => 'AuthoringSoftwareRelease', + Format => 'int8u[2]', + RawConv => '$val eq "255 255" ? "n/a" : $val', + ValueConv => '$val =~ tr/ /./; $val', + }, + 11 => { + Name => 'ImageMagnificationDescriptor', + Format => 'int8u[2]', + ValueConv => '$val =~ tr/ /./; $val', + }, + 13 => { + Name => 'CreateDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + RawConv => '$val == 0xffffffff ? undef : $val', + ValueConv => 'ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 17 => { + Name => 'ModifyDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + RawConv => '$val == 0xffffffff ? undef : $val', + ValueConv => 'ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 21 => { + Name => 'ImageMedium', + PrintConv => { + 0 => 'Color negative', + 1 => 'Color reversal', + 2 => 'Color hard copy', + 3 => 'Thermal hard copy', + 4 => 'Black and white negative', + 5 => 'Black and white reversal', + 6 => 'Black and white hard copy', + 7 => 'Internegative', + 8 => 'Synthetic image', + }, + }, + 22 => { + Name => 'ProductType', + Format => 'string[20]', + ValueConv => '$val =~ s/[ \0]+$//; $val', + }, + 42 => { + Name => 'ScannerVendorID', + Format => 'string[20]', + ValueConv => '$val =~ s/[ \0]+$//; $val', + }, + 62 => { + Name => 'ScannerProductID', + Format => 'string[16]', + ValueConv => '$val =~ s/[ \0]+$//; $val', + }, + 78 => { + Name => 'ScannerFirmwareVersion', + Format => 'string[4]', + ValueConv => '$val =~ s/[ \0]+$//; $val', + }, + 82 => { + Name => 'ScannerFirmwareDate', + Format => 'string[8]', + ValueConv => '$val =~ s/[ \0]+$//; $val', + }, + 90 => { + Name => 'ScannerSerialNumber', + Format => 'string[20]', + ValueConv => '$val =~ s/[ \0]+$//; $val', + }, + 110 => { + Name => 'ScannerPixelSize', + Format => 'undef[2]', + ValueConv => 'join(".",unpack("H2H2",$val))', + PrintConv => '"$val micrometers"', + }, + 112 => { + Name => 'ImageWorkstationMake', + Format => 'string[20]', + ValueConv => '$val =~ s/[ \0]+$//; $val', + }, + 132 => { + Name => 'CharacterSet', + PrintConv => { + 1 => '38 characters ISO 646', + 2 => '65 characters ISO 646', + 3 => '95 characters ISO 646', + 4 => '191 characters ISO 8850-1', + 5 => 'ISO 2022', + 6 => 'Includes characters not ISO 2375 registered', + }, + }, + 133 => { + Name => 'CharacterEscapeSequence', + Format => 'undef[32]', + Binary => 1, + Unknown => 1, + }, + 165 => { + Name => 'PhotoFinisherName', + Format => 'string[60]', + ValueConv => '$val =~ s/[ \0]+$//; $val', + }, + 225 => { + Name => 'HasSBA', + Format => 'undef[3]', + Hidden => 1, + RawConv => '$val eq "SBA" and $$self{HasSBA} = 1; undef', + }, + 228 => { + Name => 'SceneBalanceAlgorithmRevision', + Condition => '$$self{HasSBA}', + Format => 'int8u[2]', + ValueConv => '$val =~ tr/ /./; $val', + }, + 230 => { + Name => 'SceneBalanceAlgorithmCommand', + Condition => '$$self{HasSBA}', + PrintConv => { + 0 => 'Neutral SBA On, Color SBA On', + 1 => 'Neutral SBA Off, Color SBA Off', + 2 => 'Neutral SBA On, Color SBA Off', + 3 => 'Neutral SBA Off, Color SBA On', + }, + }, + 325 => { + Name => 'SceneBalanceAlgorithmFilmID', + Condition => '$$self{HasSBA}', + Format => 'int16u', + PrintConv => { + 1 => '3M ScotchColor AT 100', + 2 => '3M ScotchColor AT 200', + 3 => '3M ScotchColor HR2 400', + 7 => '3M Scotch HR 200 Gen 2', + 9 => '3M Scotch HR 400 Gen 2', + 16 => 'Agfa Agfacolor XRS 400 Gen 1', + 17 => 'Agfa Agfacolor XRG/XRS 400', + 18 => 'Agfa Agfacolor XRG/XRS 200', + 19 => 'Agfa Agfacolor XRS 1000 Gen 2', + 20 => 'Agfa Agfacolor XRS 400 Gen 2', + 21 => 'Agfa Agfacolor XRS/XRC 100', + 26 => 'Fuji Reala 100 (JAPAN)', + 27 => 'Fuji Reala 100 Gen 1', + 28 => 'Fuji Reala 100 Gen 2', + 29 => 'Fuji SHR 400 Gen 2', + 30 => 'Fuji Super HG 100', + 31 => 'Fuji Super HG 1600 Gen 1', + 32 => 'Fuji Super HG 200', + 33 => 'Fuji Super HG 400', + 34 => 'Fuji Super HG 100 Gen 2', + 35 => 'Fuji Super HR 100 Gen 1', + 36 => 'Fuji Super HR 100 Gen 2', + 37 => 'Fuji Super HR 1600 Gen 2', + 38 => 'Fuji Super HR 200 Gen 1', + 39 => 'Fuji Super HR 200 Gen 2', + 40 => 'Fuji Super HR 400 Gen 1', + 43 => 'Fuji NSP 160S (Pro)', + 45 => 'Kodak Kodacolor VR 100 Gen 2', + 47 => 'Kodak Gold 400 Gen 3', + 55 => 'Kodak Ektar 100 Gen 1', + 56 => 'Kodak Ektar 1000 Gen 1', + 57 => 'Kodak Ektar 125 Gen 1', + 58 => 'Kodak Royal Gold 25 RZ', + 60 => 'Kodak Gold 1600 Gen 1', + 61 => 'Kodak Gold 200 Gen 2', + 62 => 'Kodak Gold 400 Gen 2', + 65 => 'Kodak Kodacolor VR 100 Gen 1', + 66 => 'Kodak Kodacolor VR 1000 Gen 2', + 67 => 'Kodak Kodacolor VR 1000 Gen 1', + 68 => 'Kodak Kodacolor VR 200 Gen 1', + 69 => 'Kodak Kodacolor VR 400 Gen 1', + 70 => 'Kodak Kodacolor VR 200 Gen 2', + 71 => 'Kodak Kodacolor VRG 100 Gen 1', + 72 => 'Kodak Gold 100 Gen 2', + 73 => 'Kodak Kodacolor VRG 200 Gen 1', + 74 => 'Kodak Gold 400 Gen 1', + 87 => 'Kodak Ektacolor Gold 160', + 88 => 'Kodak Ektapress 1600 Gen 1 PPC', + 89 => 'Kodak Ektapress Gold 100 Gen 1 PPA', + 90 => 'Kodak Ektapress Gold 400 PPB-3', + 92 => 'Kodak Ektar 25 Professional PHR', + 97 => 'Kodak T-Max 100 Professional', + 98 => 'Kodak T-Max 3200 Professional', + 99 => 'Kodak T-Max 400 Professional', + 101 => 'Kodak Vericolor 400 Prof VPH', + 102 => 'Kodak Vericolor III Pro', + 121 => 'Konika Konica Color SR-G 3200', + 122 => 'Konika Konica Color Super SR100', + 123 => 'Konika Konica Color Super SR 400', + 138 => 'Kodak Gold Unknown', + 139 => 'Kodak Unknown Neg A- Normal SBA', + 143 => 'Kodak Ektar 100 Gen 2', + 147 => 'Kodak Kodacolor CII', + 148 => 'Kodak Kodacolor II', + 149 => 'Kodak Gold Plus 200 Gen 3', + 150 => 'Kodak Internegative +10% Contrast', + 151 => 'Agfa Agfacolor Ultra 50', + 152 => 'Fuji NHG 400', + 153 => 'Agfa Agfacolor XRG 100', + 154 => 'Kodak Gold Plus 100 Gen 3', + 155 => 'Konika Konica Color Super SR200 Gen 1', + 156 => 'Konika Konica Color SR-G 160', + 157 => 'Agfa Agfacolor Optima 125', + 158 => 'Agfa Agfacolor Portrait 160', + 162 => 'Kodak Kodacolor VRG 400 Gen 1', + 163 => 'Kodak Gold 200 Gen 1', + 164 => 'Kodak Kodacolor VRG 100 Gen 2', + 174 => 'Kodak Internegative +20% Contrast', + 175 => 'Kodak Internegative +30% Contrast', + 176 => 'Kodak Internegative +40% Contrast', + 184 => 'Kodak TMax-100 D-76 CI = .40', + 185 => 'Kodak TMax-100 D-76 CI = .50', + 186 => 'Kodak TMax-100 D-76 CI = .55', + 187 => 'Kodak TMax-100 D-76 CI = .70', + 188 => 'Kodak TMax-100 D-76 CI = .80', + 189 => 'Kodak TMax-100 TMax CI = .40', + 190 => 'Kodak TMax-100 TMax CI = .50', + 191 => 'Kodak TMax-100 TMax CI = .55', + 192 => 'Kodak TMax-100 TMax CI = .70', + 193 => 'Kodak TMax-100 TMax CI = .80', + 195 => 'Kodak TMax-400 D-76 CI = .40', + 196 => 'Kodak TMax-400 D-76 CI = .50', + 197 => 'Kodak TMax-400 D-76 CI = .55', + 198 => 'Kodak TMax-400 D-76 CI = .70', + 214 => 'Kodak TMax-400 D-76 CI = .80', + 215 => 'Kodak TMax-400 TMax CI = .40', + 216 => 'Kodak TMax-400 TMax CI = .50', + 217 => 'Kodak TMax-400 TMax CI = .55', + 218 => 'Kodak TMax-400 TMax CI = .70', + 219 => 'Kodak TMax-400 TMax CI = .80', + 224 => '3M ScotchColor ATG 400/EXL 400', + 266 => 'Agfa Agfacolor Optima 200', + 267 => 'Konika Impressa 50', + 268 => 'Polaroid Polaroid CP 200', + 269 => 'Konika Konica Color Super SR200 Gen 2', + 270 => 'ILFORD XP2 400', + 271 => 'Polaroid Polaroid Color HD2 100', + 272 => 'Polaroid Polaroid Color HD2 400', + 273 => 'Polaroid Polaroid Color HD2 200', + 282 => '3M ScotchColor ATG-1 200', + 284 => 'Konika XG 400', + 307 => 'Kodak Universal Reversal B/W', + 308 => 'Kodak RPC Copy Film Gen 1', + 312 => 'Kodak Universal E6', + 324 => 'Kodak Gold Ultra 400 Gen 4', + 328 => 'Fuji Super G 100', + 329 => 'Fuji Super G 200', + 330 => 'Fuji Super G 400 Gen 2', + 333 => 'Kodak Universal K14', + 334 => 'Fuji Super G 400 Gen 1', + 366 => 'Kodak Vericolor HC 6329 VHC', + 367 => 'Kodak Vericolor HC 4329 VHC', + 368 => 'Kodak Vericolor L 6013 VPL', + 369 => 'Kodak Vericolor L 4013 VPL', + 418 => 'Kodak Ektacolor Gold II 400 Prof', + 430 => 'Kodak Royal Gold 1000', + 431 => 'Kodak Kodacolor VR 200 / 5093', + 432 => 'Kodak Gold Plus 100 Gen 4', + 443 => 'Kodak Royal Gold 100', + 444 => 'Kodak Royal Gold 400', + 445 => 'Kodak Universal E6 auto-balance', + 446 => 'Kodak Universal E6 illum. corr.', + 447 => 'Kodak Universal K14 auto-balance', + 448 => 'Kodak Universal K14 illum. corr.', + 449 => 'Kodak Ektar 100 Gen 3 SY', + 456 => 'Kodak Ektar 25', + 457 => 'Kodak Ektar 100 Gen 3 CX', + 458 => 'Kodak Ektapress Plus 100 Prof PJA-1', + 459 => 'Kodak Ektapress Gold II 100 Prof', + 460 => 'Kodak Pro 100 PRN', + 461 => 'Kodak Vericolor HC 100 Prof VHC-2', + 462 => 'Kodak Prof Color Neg 100', + 463 => 'Kodak Ektar 1000 Gen 2', + 464 => 'Kodak Ektapress Plus 1600 Pro PJC-1', + 465 => 'Kodak Ektapress Gold II 1600 Prof', + 466 => 'Kodak Super Gold 1600 GF Gen 2', + 467 => 'Kodak Kodacolor 100 Print Gen 4', + 468 => 'Kodak Super Gold 100 Gen 4', + 469 => 'Kodak Gold 100 Gen 4', + 470 => 'Kodak Gold III 100 Gen 4', + 471 => 'Kodak Funtime 100 FA', + 472 => 'Kodak Funtime 200 FB', + 473 => 'Kodak Kodacolor VR 200 Gen 4', + 474 => 'Kodak Gold Super 200 Gen 4', + 475 => 'Kodak Kodacolor 200 Print Gen 4', + 476 => 'Kodak Super Gold 200 Gen 4', + 477 => 'Kodak Gold 200 Gen 4', + 478 => 'Kodak Gold III 200 Gen 4', + 479 => 'Kodak Gold Ultra 400 Gen 5', + 480 => 'Kodak Super Gold 400 Gen 5', + 481 => 'Kodak Gold 400 Gen 5', + 482 => 'Kodak Gold III 400 Gen 5', + 483 => 'Kodak Kodacolor 400 Print Gen 5', + 484 => 'Kodak Ektapress Plus 400 Prof PJB-2', + 485 => 'Kodak Ektapress Gold II 400 Prof G5', + 486 => 'Kodak Pro 400 PPF-2', + 487 => 'Kodak Ektacolor Gold II 400 EGP-4', + 488 => 'Kodak Ektacolor Gold 400 Prof EGP-4', + 489 => 'Kodak Ektapress Gold II Multspd PJM', + 490 => 'Kodak Pro 400 MC PMC', + 491 => 'Kodak Vericolor 400 Prof VPH-2', + 492 => 'Kodak Vericolor 400 Plus Prof VPH-2', + 493 => 'Kodak Unknown Neg Product Code 83', + 505 => 'Kodak Ektacolor Pro Gold 160 GPX', + 508 => 'Kodak Royal Gold 200', + 517 => 'Kodak 4050000000', + 519 => 'Kodak Gold Plus 100 Gen 5', + 520 => 'Kodak Gold 800 Gen 1', + 521 => 'Kodak Gold Super 200 Gen 5', + 522 => 'Kodak Ektapress Plus 200 Prof', + 523 => 'Kodak 4050 E6 auto-balance', + 524 => 'Kodak 4050 E6 ilum. corr.', + 525 => 'Kodak 4050 K14', + 526 => 'Kodak 4050 K14 auto-balance', + 527 => 'Kodak 4050 K14 ilum. corr.', + 528 => 'Kodak 4050 Reversal B&W', + 532 => 'Kodak Advantix 200', + 533 => 'Kodak Advantix 400', + 534 => 'Kodak Advantix 100', + 535 => 'Kodak Ektapress Multspd Prof PJM-2', + 536 => 'Kodak Kodacolor VR 200 Gen 5', + 537 => 'Kodak Funtime 200 FB Gen 2', + 538 => 'Kodak Commercial 200', + 539 => 'Kodak Royal Gold 25 Copystand', + 540 => 'Kodak Kodacolor DA 100 Gen 5', + 545 => 'Kodak Kodacolor VR 400 Gen 2', + 546 => 'Kodak Gold 100 Gen 6', + 547 => 'Kodak Gold 200 Gen 6', + 548 => 'Kodak Gold 400 Gen 6', + 549 => 'Kodak Royal Gold 100 Gen 2', + 550 => 'Kodak Royal Gold 200 Gen 2', + 551 => 'Kodak Royal Gold 400 Gen 2', + 552 => 'Kodak Gold Max 800 Gen 2', + 554 => 'Kodak 4050 E6 high contrast', + 555 => 'Kodak 4050 E6 low saturation high contrast', + 556 => 'Kodak 4050 E6 low saturation', + 557 => 'Kodak Universal E-6 Low Saturation', + 558 => 'Kodak T-Max T400 CN', + 563 => 'Kodak Ektapress PJ100', + 564 => 'Kodak Ektapress PJ400', + 565 => 'Kodak Ektapress PJ800', + 567 => 'Kodak Portra 160NC', + 568 => 'Kodak Portra 160VC', + 569 => 'Kodak Portra 400NC', + 570 => 'Kodak Portra 400VC', + 575 => 'Kodak Advantix 100-2', + 576 => 'Kodak Advantix 200-2', + 577 => 'Kodak Advantix Black & White + 400', + 578 => 'Kodak Ektapress PJ800-2', + }, + }, + 331 => { + Name => 'CopyrightStatus', + Condition => '$$self{HasSBA}', + RawConv => '$$self{CopyrightStatus} = $val', + PrintConv => { + 1 => 'Restrictions apply', + 0xff => 'Not specified', + }, + }, + 332 => { + Name => 'CopyrightFileName', + Condition => '$$self{CopyrightStatus} and $$self{CopyrightStatus} == 1', + Format => 'string[12]', + ValueConv => '$val =~ s/[ \0]+$//; $val', + }, + 1538 => { + Name => 'Orientation', + Mask => 0x03, + RawConv => '$$self{Orient} = $val', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 270 CW', + 2 => 'Rotate 180', + 3 => 'Rotate 90 CW', + }, + }, + 1538.1 => { + Name => 'ImageWidth', + Mask => 0x0c, + # 0=Base (768x512), 1=4Base (1536x1024), 2=16Base (3072x2048) + ValueConv => '($$self{Orient} & 0x01 ? 512 : 768) * ($val * 2 || 1)', + }, + 1538.2 => { + Name => 'ImageHeight', + Mask => 0x0c, + ValueConv => '($$self{Orient} & 0x01 ? 768 : 512) * ($val * 2 || 1)', + }, + 1538.3 => { + Name => 'CompressionClass', + Mask => 0x60, + PrintConv => { + 0 => 'Class 1 - 35mm film; Pictoral hard copy', + 1 => 'Class 2 - Large format film', + 2 => 'Class 3 - Text and graphics, high resolution', + 3 => 'Class 4 - Text and graphics, high dynamic range', + }, + }, + #1544 => 'InterleaveRatio', + #1545 => 'ADPCMResolution', + #1546 => { + # Name => 'ADPCMMagnificationPanning', + # Format => 'int8u[2]', + #}, + #1548 => 'ADPCMMagnificationFactor', + #1549 => { + # Name => 'ADPCMDisplayOffset', + # Format => 'int8u[2]', + #}, + #1551 => 'ADPCMTransitionDescriptor', +); + +#------------------------------------------------------------------------------ +# Extract information from a PhotoCD image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid PhotoCD file +sub ProcessPCD($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + return 0 unless $raf->Seek(2048, 0) and + $raf->Read($buff, 2048) == 2048 and + $buff =~ /^PCD_IPI/; + SetByteOrder('MM'); + $et->SetFileType(); + my %dirInfo = ( + DirName => 'PhotoCD', + DataPt => \$buff, + DataPos => 4096, + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::PhotoCD::Main'); + return $et->ProcessBinaryData(\%dirInfo, $tagTablePtr); +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PhotoCD - Read Kodak Photo CD Image Pac (PCD) metadata + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +information from Kodak Photo CD Image Pac (PCD) files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://pcdtojpeg.sourceforge.net/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PhotoCD Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/PhotoMechanic.pm b/ExifTool/lib/Image/ExifTool/PhotoMechanic.pm new file mode 100644 index 0000000..9946c4c --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PhotoMechanic.pm @@ -0,0 +1,256 @@ +#------------------------------------------------------------------------------ +# File: PhotoMechanic.pm +# +# Description: Read/write Camera Bits Photo Mechanic information +# +# Revisions: 10/28/2006 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::PhotoMechanic; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::IPTC; +use Image::ExifTool::XMP; + +$VERSION = '1.06'; + +sub ProcessPhotoMechanic($$); + +# color class names +my %colorClasses = ( + 0 => '0 (None)', + 1 => '1 (Winner)', + 2 => '2 (Winner alt)', + 3 => '3 (Superior)', + 4 => '4 (Superior alt)', + 5 => '5 (Typical)', + 6 => '6 (Typical alt)', + 7 => '7 (Extras)', + 8 => '8 (Trash)', +); + +# main tag table IPTC-format records in PhotoMechanic trailer +%Image::ExifTool::PhotoMechanic::Main = ( + GROUPS => { 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::IPTC::ProcessIPTC, + WRITE_PROC => \&Image::ExifTool::IPTC::WriteIPTC, + NOTES => q{ + The Photo Mechanic trailer contains data in an IPTC-format structure, with + soft edit information stored under record number 2. + }, + 2 => { + Name => 'SoftEdit', + SubDirectory => { + TagTable => 'Image::ExifTool::PhotoMechanic::SoftEdit', + }, + }, +); + +# raw/preview crop coordinate conversions +my %rawCropConv = ( + ValueConv => '$val / 655.36', + ValueConvInv => 'int($val * 655.36 + 0.5)', + PrintConv => 'sprintf("%.3f%%",$val)', + PrintConvInv => '$val=~tr/ %//d; $val', +); + +# Record 2 -- PhotoMechanic soft edit information +%Image::ExifTool::PhotoMechanic::SoftEdit = ( + GROUPS => { 2 => 'Image' }, + WRITE_PROC => \&Image::ExifTool::IPTC::WriteIPTC, + CHECK_PROC => \&Image::ExifTool::IPTC::CheckIPTC, + WRITABLE => 1, + FORMAT => 'int32s', + 209 => { Name => 'RawCropLeft', %rawCropConv }, + 210 => { Name => 'RawCropTop', %rawCropConv }, + 211 => { Name => 'RawCropRight', %rawCropConv }, + 212 => { Name => 'RawCropBottom', %rawCropConv }, + 213 => 'ConstrainedCropWidth', + 214 => 'ConstrainedCropHeight', + 215 => 'FrameNum', + 216 => { + Name => 'Rotation', + PrintConv => { + 0 => '0', + 1 => '90', + 2 => '180', + 3 => '270', + }, + }, + 217 => 'CropLeft', + 218 => 'CropTop', + 219 => 'CropRight', + 220 => 'CropBottom', + 221 => { + Name => 'Tagged', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 222 => { + Name => 'ColorClass', + PrintConv => \%colorClasses, + }, + 223 => 'Rating', + 236 => { Name => 'PreviewCropLeft', %rawCropConv }, + 237 => { Name => 'PreviewCropTop', %rawCropConv }, + 238 => { Name => 'PreviewCropRight', %rawCropConv }, + 239 => { Name => 'PreviewCropBottom', %rawCropConv }, +); + +# PhotoMechanic XMP properties +%Image::ExifTool::PhotoMechanic::XMP = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-photomech', 2 => 'Image' }, + NAMESPACE => { photomechanic => 'http://ns.camerabits.com/photomechanic/1.0/' }, + WRITE_PROC => \&Image::ExifTool::XMP::WriteXMP, + WRITABLE => 'string', + NOTES => q{ + Below is a list of the observed PhotoMechanic XMP tags. The actual + namespace prefix is "photomechanic" but ExifTool shortens this in + the family 1 group name. + }, + ColorClass => { + Writable => 'integer', + PrintConv => \%colorClasses, + }, + CountryCode => { Avoid => 1, Groups => { 2 => 'Location' } }, + EditStatus => { }, + PMVersion => { }, + Prefs => { + Notes => 'format is "Tagged:0, ColorClass:1, Rating:2, FrameNum:3"', + PrintConv => q{ + $val =~ s[\s*(\d+):\s*(\d+):\s*(\d+):\s*(\S*)] + [Tagged:$1, ColorClass:$2, Rating:$3, FrameNum:$4]; + return $val; + }, + PrintConvInv => q{ + $val =~ s[Tagged:\s*(\d+).*ColorClass:\s*(\d+).*Rating:\s*(\d+).*FrameNum:\s*(\S*)] + [$1:$2:$3:$4]is; + return $val; + }, + }, + Tagged => { Writable => 'boolean', PrintConv => { False => 'No', True => 'Yes' } }, + TimeCreated => { + Avoid => 1, + Groups => { 2 => 'Time' }, + Shift => 'Time', + ValueConv => 'Image::ExifTool::Exif::ExifTime($val)', + ValueConvInv => 'Image::ExifTool::IPTC::IptcTime($val)', + }, +); + +#------------------------------------------------------------------------------ +# Read/write PhotoMechanic information in a file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this file didn't contain PhotoMechanic information +# - updates DataPos to point to start of PhotoMechanic information +# - updates DirLen to trailer length +sub ProcessPhotoMechanic($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $offset = $$dirInfo{Offset} || 0; + my $outfile = $$dirInfo{OutFile}; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my $rtnVal = 0; + my ($buff, $footer); + + for (;;) { + # read and validate trailer footer (last 12 bytes) + last unless $raf->Seek(-12-$offset, 2) and $raf->Read($footer, 12) == 12; + last unless $footer =~ /cbipcbbl$/; + my $size = unpack('N', $footer); + + if ($size & 0x80000000 or not $raf->Seek(-$size-12, 1)) { + $et->Warn('Bad PhotoMechanic trailer'); + last; + } + unless ($raf->Read($buff, $size) == $size) { + $et->Warn('Error reading PhotoMechanic trailer'); + last; + } + $rtnVal = 1; # we read the trailer successfully + + # set variables returned in dirInfo hash + $$dirInfo{DataPos} = $raf->Tell() - $size; + $$dirInfo{DirLen} = $size + 12; + + my %dirInfo = ( + DataPt => \$buff, + DataPos => $$dirInfo{DataPos}, + DirStart => 0, + DirLen => $size, + Parent => 'PhotoMechanic', + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::PhotoMechanic::Main'); + if (not $outfile) { + # extract trailer information + $et->DumpTrailer($dirInfo) if $verbose or $$et{HTML_DUMP}; + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } elsif ($$et{DEL_GROUP}{PhotoMechanic}) { + # delete the trailer + $verbose and print $out " Deleting PhotoMechanic trailer\n"; + ++$$et{CHANGED}; + } else { + # rewrite the trailer + my $newPt; + my $newBuff = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $newBuff) { + $newPt = \$newBuff; # write out the modified trailer + my $pad = 0x800 - length($newBuff); + if ($pad > 0 and not $$et{OPTIONS}{Compact}{NoPadding}) { + # pad out to 2kB like PhotoMechanic does + $newBuff .= "\0" x $pad; + } + # generate new footer + $footer = pack('N', length($$newPt)) . 'cbipcbbl'; + } else { + $newPt = \$buff; # just copy existing trailer + } + # write out the trailer + Write($outfile, $$newPt, $footer) or $rtnVal = -1; + } + last; + } + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PhotoMechanic - Read/write Photo Mechanic information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read and +write information written by the Camera Bits Photo Mechanic software. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 ACKNOWLEDGEMENTS + +Many thanks to the great support provided by Camera Bits, and in particular +for the valuable exchanges with Kirk Baker. Based on this experience, I can +say that the technical support offered by Camera Bits is second to none. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PhotoMechanic Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Photoshop.pm b/ExifTool/lib/Image/ExifTool/Photoshop.pm new file mode 100644 index 0000000..bb0a18a --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Photoshop.pm @@ -0,0 +1,1231 @@ +#------------------------------------------------------------------------------ +# File: Photoshop.pm +# +# Description: Read/write Photoshop IRB meta information +# +# Revisions: 02/06/2004 - P. Harvey Created +# 02/25/2004 - P. Harvey Added hack for problem with old photoshops +# 10/04/2004 - P. Harvey Added a bunch of tags (ref Image::MetaData::JPEG) +# but left most of them commented out until I have enough +# information to write PrintConv routines for them to +# display something useful +# 07/08/2005 - P. Harvey Added support for reading PSD files +# 01/07/2006 - P. Harvey Added PSD write support +# 11/04/2006 - P. Harvey Added handling of resource name +# +# References: 1) http://www.fine-view.com/jp/lab/doc/ps6ffspecsv2.pdf +# 2) http://www.ozhiker.com/electronics/pjmt/jpeg_info/irb_jpeg_qual.html +# 3) Matt Mueller private communication (tests with PS CS2) +# 4) http://www.fileformat.info/format/psd/egff.htm +# 5) http://www.telegraphics.com.au/svn/psdparse/trunk/resources.c +# 6) http://libpsd.graphest.com/files/Photoshop%20File%20Formats.pdf +# 7) http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::Photoshop; + +use strict; +use vars qw($VERSION $AUTOLOAD $iptcDigestInfo %printFlags); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.69'; + +sub ProcessPhotoshop($$$); +sub WritePhotoshop($$$); +sub ProcessLayers($$$); + +# PrintFlags bit definitions (ref forum13785) +%printFlags = ( + 0 => 'Labels', + 1 => 'Corner crop marks', + 2 => 'Color bars', # (deprecated) + 3 => 'Registration marks', + 4 => 'Negative', + 5 => 'Emulsion down', + 6 => 'Interpolate', # (deprecated) + 7 => 'Description', + 8 => 'Print flags', +); + +# map of where information is stored in PSD image +my %psdMap = ( + IPTC => 'Photoshop', + XMP => 'Photoshop', + EXIFInfo => 'Photoshop', + IFD0 => 'EXIFInfo', + IFD1 => 'IFD0', + ICC_Profile => 'Photoshop', + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', +); + +# tag information for PhotoshopThumbnail and PhotoshopBGRThumbnail +my %thumbnailInfo = ( + Writable => 'undef', + Protected => 1, + RawConv => 'my $img=substr($val,0x1c); $self->ValidateImage(\$img,$tag)', + ValueConvInv => q{ + my $et = new Image::ExifTool; + my @tags = qw{ImageWidth ImageHeight FileType}; + my $info = $et->ImageInfo(\$val, @tags); + my ($w, $h, $type) = @$info{@tags}; + $w and $h and $type and $type eq 'JPEG' or warn("Not a valid JPEG image\n"), return undef; + my $wbytes = int(($w * 24 + 31) / 32) * 4; + return pack('N6n2', 1, $w, $h, $wbytes, $wbytes * $h, length($val), 24, 1) . $val; + }, +); + +# tag info to decode Photoshop Unicode string +my %unicodeString = ( + ValueConv => sub { + my ($val, $et) = @_; + return '<err>' if length($val) < 4; + my $len = unpack('N', $val) * 2; + return '<err>' if length($val) < 4 + $len; + return $et->Decode(substr($val, 4, $len), 'UCS2', 'MM'); + }, + ValueConvInv => sub { + my ($val, $et) = @_; + return pack('N', length $val) . $et->Encode($val, 'UCS2', 'MM'); + }, +); + +# Photoshop APP13 tag table +# (set Unknown flag for information we don't want to display normally) +%Image::ExifTool::Photoshop::Main = ( + GROUPS => { 2 => 'Image' }, + PROCESS_PROC => \&ProcessPhotoshop, + WRITE_PROC => \&WritePhotoshop, + 0x03e8 => { Unknown => 1, Name => 'Photoshop2Info' }, + 0x03e9 => { Unknown => 1, Name => 'MacintoshPrintInfo' }, + 0x03ea => { Unknown => 1, Name => 'XMLData', Binary => 1 }, #PH + 0x03eb => { Unknown => 1, Name => 'Photoshop2ColorTable' }, + 0x03ed => { + Name => 'ResolutionInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Photoshop::Resolution', + }, + }, + 0x03ee => { + Name => 'AlphaChannelsNames', + ValueConv => 'Image::ExifTool::Photoshop::ConvertPascalString($self,$val)', + }, + 0x03ef => { Unknown => 1, Name => 'DisplayInfo' }, + 0x03f0 => { Unknown => 1, Name => 'PStringCaption' }, + 0x03f1 => { Unknown => 1, Name => 'BorderInformation' }, + 0x03f2 => { Unknown => 1, Name => 'BackgroundColor' }, + 0x03f3 => { + Unknown => 1, + Name => 'PrintFlags', + Format => 'int8u', + PrintConv => q{ + my $byte = 0; + my @bits = $val =~ /\d+/g; + $byte = ($byte << 1) | ($_ ? 1 : 0) foreach reverse @bits; + return DecodeBits($byte, \%Image::ExifTool::Photoshop::printFlags); + }, + }, + 0x03f4 => { Unknown => 1, Name => 'BW_HalftoningInfo' }, + 0x03f5 => { Unknown => 1, Name => 'ColorHalftoningInfo' }, + 0x03f6 => { Unknown => 1, Name => 'DuotoneHalftoningInfo' }, + 0x03f7 => { Unknown => 1, Name => 'BW_TransferFunc' }, + 0x03f8 => { Unknown => 1, Name => 'ColorTransferFuncs' }, + 0x03f9 => { Unknown => 1, Name => 'DuotoneTransferFuncs' }, + 0x03fa => { Unknown => 1, Name => 'DuotoneImageInfo' }, + 0x03fb => { Unknown => 1, Name => 'EffectiveBW', Format => 'int8u' }, + 0x03fc => { Unknown => 1, Name => 'ObsoletePhotoshopTag1' }, + 0x03fd => { Unknown => 1, Name => 'EPSOptions' }, + 0x03fe => { Unknown => 1, Name => 'QuickMaskInfo' }, + 0x03ff => { Unknown => 1, Name => 'ObsoletePhotoshopTag2' }, + 0x0400 => { Unknown => 1, Name => 'TargetLayerID', Format => 'int16u' }, # (LayerStateInfo) + 0x0401 => { Unknown => 1, Name => 'WorkingPath' }, + 0x0402 => { Unknown => 1, Name => 'LayersGroupInfo', Format => 'int16u' }, + 0x0403 => { Unknown => 1, Name => 'ObsoletePhotoshopTag3' }, + 0x0404 => { + Name => 'IPTCData', + SubDirectory => { + DirName => 'IPTC', + TagTable => 'Image::ExifTool::IPTC::Main', + }, + }, + 0x0405 => { Unknown => 1, Name => 'RawImageMode' }, + 0x0406 => { #2 + Name => 'JPEG_Quality', + SubDirectory => { + TagTable => 'Image::ExifTool::Photoshop::JPEG_Quality', + }, + }, + 0x0408 => { Unknown => 1, Name => 'GridGuidesInfo' }, + 0x0409 => { + Name => 'PhotoshopBGRThumbnail', + Notes => 'this is a JPEG image, but in BGR format instead of RGB', + %thumbnailInfo, + Groups => { 2 => 'Preview' }, + }, + 0x040a => { + Name => 'CopyrightFlag', + Writable => 'int8u', + Groups => { 2 => 'Author' }, + ValueConv => 'join(" ",unpack("C*", $val))', + ValueConvInv => 'pack("C*",split(" ",$val))', + PrintConv => { #3 + 0 => 'False', + 1 => 'True', + }, + }, + 0x040b => { + Name => 'URL', + Writable => 'string', + Groups => { 2 => 'Author' }, + }, + 0x040c => { + Name => 'PhotoshopThumbnail', + %thumbnailInfo, + Groups => { 2 => 'Preview' }, + }, + 0x040d => { + Name => 'GlobalAngle', + Writable => 'int32u', + ValueConv => 'unpack("N",$val)', + ValueConvInv => 'pack("N",$val)', + }, + 0x040e => { Unknown => 1, Name => 'ColorSamplersResource' }, + 0x040f => { + Name => 'ICC_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + }, + 0x0410 => { Unknown => 1, Name => 'Watermark', Format => 'int8u' }, + 0x0411 => { Unknown => 1, Name => 'ICC_Untagged', Format => 'int8u' }, + 0x0412 => { Unknown => 1, Name => 'EffectsVisible', Format => 'int8u' }, + 0x0413 => { Unknown => 1, Name => 'SpotHalftone' }, + 0x0414 => { Unknown => 1, Name => 'IDsBaseValue', Description => 'IDs Base Value', Format => 'int32u' }, + 0x0415 => { Unknown => 1, Name => 'UnicodeAlphaNames' }, + 0x0416 => { Unknown => 1, Name => 'IndexedColorTableCount', Format => 'int16u' }, + 0x0417 => { Unknown => 1, Name => 'TransparentIndex', Format => 'int16u' }, + 0x0419 => { + Name => 'GlobalAltitude', + Writable => 'int32u', + ValueConv => 'unpack("N",$val)', + ValueConvInv => 'pack("N",$val)', + }, + 0x041a => { + Name => 'SliceInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::SliceInfo' }, + }, + 0x041b => { Name => 'WorkflowURL', %unicodeString }, + 0x041c => { Unknown => 1, Name => 'JumpToXPEP' }, + 0x041d => { Unknown => 1, Name => 'AlphaIdentifiers' }, + 0x041e => { + Name => 'URL_List', + List => 1, + Writable => 1, + ValueConv => sub { + my ($val, $et) = @_; + return '<err>' if length($val) < 4; + my $num = unpack('N', $val); + my ($i, @vals); + my $pos = 4; + for ($i=0; $i<$num; ++$i) { + $pos += 8; # (skip word and ID) + last if length($val) < $pos + 4; + my $len = unpack("x${pos}N", $val) * 2; + last if length($val) < $pos + 4 + $len; + push @vals, $et->Decode(substr($val,$pos+4,$len), 'UCS2', 'MM'); + $pos += 4 + $len; + } + return \@vals; + }, + # (this is tricky to make writable) + }, + 0x0421 => { + Name => 'VersionInfo', + SubDirectory => { + TagTable => 'Image::ExifTool::Photoshop::VersionInfo', + }, + }, + 0x0422 => { + Name => 'EXIFInfo', #PH (Found in EPS and PSD files) + SubDirectory => { + TagTable=> 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + WriteProc => \&Image::ExifTool::WriteTIFF, + }, + }, + 0x0423 => { Unknown => 1, Name => 'ExifInfo2', Binary => 1 }, #5 + 0x0424 => { + Name => 'XMP', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + }, + }, + 0x0425 => { + Name => 'IPTCDigest', + Writable => 'string', + Protected => 1, + Notes => q{ + this tag indicates provides a way for XMP-aware applications to indicate + that the XMP is synchronized with the IPTC. The MWG recommendation is to + ignore the XMP if IPTCDigest exists and doesn't match the CurrentIPTCDigest. + When writing, special values of "new" and "old" represent the digests of the + IPTC from the edited and original files respectively, and are undefined if + the IPTC does not exist in the respective file. Set this to "new" as an + indication that the XMP is synchronized with the IPTC + }, + # also note the 'new' feature requires that the IPTC comes before this tag is written + ValueConv => 'unpack("H*", $val)', + ValueConvInv => q{ + if (lc($val) eq 'new' or lc($val) eq 'old') { + { + local $SIG{'__WARN__'} = sub { }; + return lc($val) if eval { require Digest::MD5 }; + } + warn "Digest::MD5 must be installed\n"; + return undef; + } + return pack('H*', $val) if $val =~ /^[0-9a-f]{32}$/i; + warn "Value must be 'new', 'old' or 32 hexadecimal digits\n"; + return undef; + } + }, + 0x0426 => { + Name => 'PrintScaleInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::PrintScaleInfo' }, + }, + 0x0428 => { + Name => 'PixelInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::PixelInfo' }, + }, + 0x0429 => { Unknown => 1, Name => 'LayerComps' }, #5 + 0x042a => { Unknown => 1, Name => 'AlternateDuotoneColors' }, #5 + 0x042b => { Unknown => 1, Name => 'AlternateSpotColors' }, #5 + 0x042d => { #7 + Name => 'LayerSelectionIDs', + Description => 'Layer Selection IDs', + Unknown => 1, + ValueConv => q{ + my ($n, @a) = unpack("nN*",$val); + $#a = $n - 1 if $n > @a; + return join(' ', @a); + }, + }, + 0x042e => { Unknown => 1, Name => 'HDRToningInfo' }, #7 + 0x042f => { Unknown => 1, Name => 'PrintInfo' }, #7 + 0x0430 => { Unknown => 1, Name => 'LayerGroupsEnabledID', Format => 'int8u' }, #7 + 0x0431 => { Unknown => 1, Name => 'ColorSamplersResource2' }, #7 + 0x0432 => { Unknown => 1, Name => 'MeasurementScale' }, #7 + 0x0433 => { Unknown => 1, Name => 'TimelineInfo' }, #7 + 0x0434 => { Unknown => 1, Name => 'SheetDisclosure' }, #7 + 0x0435 => { Unknown => 1, Name => 'DisplayInfo' }, #7 + 0x0436 => { Unknown => 1, Name => 'OnionSkins' }, #7 + 0x0438 => { Unknown => 1, Name => 'CountInfo' }, #7 + 0x043a => { Unknown => 1, Name => 'PrintInfo2' }, #7 + 0x043b => { Unknown => 1, Name => 'PrintStyle' }, #7 + 0x043c => { Unknown => 1, Name => 'MacintoshNSPrintInfo' }, #7 + 0x043d => { Unknown => 1, Name => 'WindowsDEVMODE' }, #7 + 0x043e => { Unknown => 1, Name => 'AutoSaveFilePath' }, #7 + 0x043f => { Unknown => 1, Name => 'AutoSaveFormat' }, #7 + 0x0440 => { Unknown => 1, Name => 'PathSelectionState' }, #7 + # 0x07d0-0x0bb6 Path information + 0x0bb7 => { + Name => 'ClippingPathName', + # convert from a Pascal string (ignoring 6 bytes of unknown data after string) + ValueConv => q{ + my $len = ord($val); + $val = substr($val, 0, $len+1) if $len < length($val); + return Image::ExifTool::Photoshop::ConvertPascalString($self,$val); + }, + }, + 0x0bb8 => { Unknown => 1, Name => 'OriginPathInfo' }, #7 + # 0x0fa0-0x1387 - plug-in resources (ref 7) + 0x1b58 => { Unknown => 1, Name => 'ImageReadyVariables' }, #7 + 0x1b59 => { Unknown => 1, Name => 'ImageReadyDataSets' }, #7 + 0x1f40 => { Unknown => 1, Name => 'LightroomWorkflow' }, #7 + 0x2710 => { Unknown => 1, Name => 'PrintFlagsInfo' }, +); + +# Photoshop JPEG quality record (ref 2) +%Image::ExifTool::Photoshop::JPEG_Quality = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + DATAMEMBER => [ 1 ], + FORMAT => 'int16s', + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'PhotoshopQuality', + Writable => 1, + PrintConv => '$val + 4', + PrintConvInv => '$val - 4', + }, + 1 => { + Name => 'PhotoshopFormat', + RawConv => '$$self{PhotoshopFormat} = $val', + PrintConv => { + 0x0000 => 'Standard', + 0x0001 => 'Optimized', + 0x0101 => 'Progressive', + }, + }, + 2 => { + Name => 'ProgressiveScans', + Condition => '$$self{PhotoshopFormat} == 0x0101', + PrintConv => { + 1 => '3 Scans', + 2 => '4 Scans', + 3 => '5 Scans', + }, + }, +); + +# Photoshop Slices +%Image::ExifTool::Photoshop::SliceInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + 20 => { Name => 'SlicesGroupName', Format => 'var_ustr32' }, + 24 => { Name => 'NumSlices', Format => 'int32u' }, +); + +# Photoshop resolution information #PH +%Image::ExifTool::Photoshop::Resolution = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int16u', + FIRST_ENTRY => 0, + WRITABLE => 1, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'XResolution', + Format => 'int32u', + Priority => 0, + ValueConv => '$val / 0x10000', + ValueConvInv => 'int($val * 0x10000 + 0.5)', + PrintConv => 'int($val * 100 + 0.5) / 100', + PrintConvInv => '$val', + }, + 2 => { + Name => 'DisplayedUnitsX', + PrintConv => { + 1 => 'inches', + 2 => 'cm', + }, + }, + 4 => { + Name => 'YResolution', + Format => 'int32u', + Priority => 0, + ValueConv => '$val / 0x10000', + ValueConvInv => 'int($val * 0x10000 + 0.5)', + PrintConv => 'int($val * 100 + 0.5) / 100', + PrintConvInv => '$val', + }, + 6 => { + Name => 'DisplayedUnitsY', + PrintConv => { + 1 => 'inches', + 2 => 'cm', + }, + }, +); + +# Photoshop version information +%Image::ExifTool::Photoshop::VersionInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FIRST_ENTRY => 0, + GROUPS => { 2 => 'Image' }, + # (always 1) 0 => { Name => 'PhotoshopVersion', Format => 'int32u' }, + 4 => { Name => 'HasRealMergedData', Format => 'int8u', PrintConv => { 0 => 'No', 1 => 'Yes' } }, + 5 => { Name => 'WriterName', Format => 'var_ustr32' }, + 9 => { Name => 'ReaderName', Format => 'var_ustr32' }, + # (always 1) 13 => { Name => 'FileVersion', Format => 'int32u' }, +); + +# Print Scale +%Image::ExifTool::Photoshop::PrintScaleInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FIRST_ENTRY => 0, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'PrintStyle', + Format => 'int16u', + PrintConv => { + 0 => 'Centered', + 1 => 'Size to Fit', + 2 => 'User Defined', + }, + }, + 2 => { Name => 'PrintPosition', Format => 'float[2]' }, + 10 => { Name => 'PrintScale', Format => 'float' }, +); + +# Pixel Aspect Ratio +%Image::ExifTool::Photoshop::PixelInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FIRST_ENTRY => 0, + GROUPS => { 2 => 'Image' }, + # 0 - version + 4 => { Name => 'PixelAspectRatio', Format => 'double' }, +); + +# Photoshop PSD file header +%Image::ExifTool::Photoshop::Header = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int16u', + GROUPS => { 2 => 'Image' }, + NOTES => 'This information is found in the PSD file header.', + 6 => 'NumChannels', + 7 => { Name => 'ImageHeight', Format => 'int32u' }, + 9 => { Name => 'ImageWidth', Format => 'int32u' }, + 11 => 'BitDepth', + 12 => { + Name => 'ColorMode', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Bitmap', + 1 => 'Grayscale', + 2 => 'Indexed', + 3 => 'RGB', + 4 => 'CMYK', + 7 => 'Multichannel', + 8 => 'Duotone', + 9 => 'Lab', + }, + }, +); + +# Layer information +%Image::ExifTool::Photoshop::Layers = ( + PROCESS_PROC => \&ProcessLayers, + GROUPS => { 2 => 'Image' }, + NOTES => 'Tags extracted from Photoshop layer information.', + # tags extracted from layer information + # (tag ID's are for convenience only) + _xcnt => { Name => 'LayerCount', Format => 'int16u' }, + _xrct => { + Name => 'LayerRectangles', + Format => 'int32u', + Count => 4, + List => 1, + Notes => 'top left bottom right', + }, + _xnam => { Name => 'LayerNames', + Format => 'string', + List => 1, + ValueConv => q{ + my $charset = $self->Options('CharsetPhotoshop') || 'Latin'; + return $self->Decode($val, $charset); + }, + }, + _xbnd => { + Name => 'LayerBlendModes', + Format => 'undef', + List => 1, + RawConv => 'GetByteOrder() eq "II" ? pack "N*", unpack "V*", $val : $val', + PrintConv => { + pass => 'Pass Through', + norm => 'Normal', + diss => 'Dissolve', + dark => 'Darken', + 'mul '=> 'Multiply', + idiv => 'Color Burn', + lbrn => 'Linear Burn', + dkCl => 'Darker Color', + lite => 'Lighten', + scrn => 'Screen', + 'div '=> 'Color Dodge', + lddg => 'Linear Dodge', + lgCl => 'Lighter Color', + over => 'Overlay', + sLit => 'Soft Light', + hLit => 'Hard Light', + vLit => 'Vivid Light', + lLit => 'Linear Light', + pLit => 'Pin Light', + hMix => 'Hard Mix', + diff => 'Difference', + smud => 'Exclusion', + fsub => 'Subtract', + fdiv => 'Divide', + 'hue '=> 'Hue', + 'sat '=> 'Saturation', + colr => 'Color', + 'lum '=> 'Luminosity', + }, + }, + _xopc => { + Name => 'LayerOpacities', + Format => 'int8u', + List => 1, + ValueConv => '100 * $val / 255', + PrintConv => 'sprintf("%d%%",$val)', + }, + _xvis => { + Name => 'LayerVisible', + Format => 'int8u', + List => 1, + ValueConv => '$val & 0x02', + PrintConv => { 0x02 => 'No', 0x00 => 'Yes' }, + }, + # tags extracted from additional layer information (tag ID's are real) + # - must be able to accommodate a blank entry to preserve the list ordering + luni => { + Name => 'LayerUnicodeNames', + List => 1, + RawConv => q{ + return '' if length($val) < 4; + my $len = Get32u(\$val, 0); + return $self->Decode(substr($val, 4, $len * 2), 'UCS2'); + }, + }, + lyid => { + Name => 'LayerIDs', + Description => 'Layer IDs', + Format => 'int32u', + List => 1, + Unknown => 1, + }, + lclr => { + Name => 'LayerColors', + Format => 'int16u', + Count => 1, + List => 1, + PrintConv => { + 0=>'None', 1=>'Red', 2=>'Orange', 3=>'Yellow', + 4=>'Green', 5=>'Blue', 6=>'Violet', 7=>'Gray', + }, + }, + shmd => { # layer metadata (undocumented structure) + # (for now, only extract layerTime. May also contain "layerXMP" -- + # it would be nice to decode this but I need a sample) + Name => 'LayerModifyDates', + Groups => { 2 => 'Time' }, + List => 1, + RawConv => q{ + return '' unless $val =~ /layerTime(doub|buod)(.{8})/s; + my $tmp = $2; + return GetDouble(\$tmp, 0); + }, + ValueConv => 'length $val ? ConvertUnixTime($val,1) : ""', + PrintConv => 'length $val ? $self->ConvertDateTime($val) : ""', + }, + lsct => { + Name => 'LayerSections', + Format => 'int32u', + Count => 1, + List => 1, + PrintConv => { 0 => 'Layer', 1 => 'Folder (open)', 2 => 'Folder (closed)', 3 => 'Divider' }, + }, +); + +# tags extracted from ImageSourceData found in TIFF images (ref PH) +%Image::ExifTool::Photoshop::DocumentData = ( + PROCESS_PROC => \&ProcessDocumentData, + GROUPS => { 2 => 'Image' }, + Layr => { + Name => 'Layers', + SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Layers' }, + }, + Lr16 => { # (NC) + Name => 'Layers', + SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Layers' }, + }, +); + +# image data +%Image::ExifTool::Photoshop::ImageData = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + 0 => { + Name => 'Compression', + Format => 'int16u', + PrintConv => { + 0 => 'Uncompressed', + 1 => 'RLE', + 2 => 'ZIP without prediction', + 3 => 'ZIP with prediction', + }, + }, +); + +# tags for unknown resource types +%Image::ExifTool::Photoshop::Unknown = ( + GROUPS => { 2 => 'Unknown' }, +); + +# define reference to IPTCDigest tagInfo hash for convenience +$iptcDigestInfo = $Image::ExifTool::Photoshop::Main{0x0425}; + + +#------------------------------------------------------------------------------ +# AutoLoad our writer routines when necessary +# +sub AUTOLOAD +{ + return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); +} + +#------------------------------------------------------------------------------ +# Convert pascal string(s) to something we can use +# Inputs: 1) Pascal string data +# Returns: Strings, concatenated with ', ' +sub ConvertPascalString($$) +{ + my ($et, $inStr) = @_; + my $outStr = ''; + my $len = length($inStr); + my $i=0; + while ($i < $len) { + my $n = ord(substr($inStr, $i, 1)); + last if $i + $n >= $len; + $i and $outStr .= ', '; + $outStr .= substr($inStr, $i+1, $n); + $i += $n + 1; + } + my $charset = $et->Options('CharsetPhotoshop') || 'Latin'; + return $et->Decode($outStr, $charset); +} + +#------------------------------------------------------------------------------ +# Process Photoshop layers and mask information section of PSD/PSB file +# Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref +# Returns: 1 on success (and seeks to the end of this section) +sub ProcessLayersAndMask($$$) +{ + local $_; + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $fileType = $$et{FileType}; + my $data; + + return 0 unless $fileType eq 'PSD' or $fileType eq 'PSB'; # (no layer section in CS1 files) + + # (some words are 4 bytes in PSD files and 8 bytes in PSB) + my ($psb, $psiz) = $fileType eq 'PSB' ? (1, 8) : (undef, 4); + + # read the layer information header + my $n = $psiz * 2 + 2; + $raf->Read($data, $n) == $n or return 0; + my $tot = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0); # length of layer and mask info + return 1 if $tot == 0; + my $end = $raf->Tell() - $psiz - 2 + $tot; + $data = substr $data, $psiz; + my $len = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0); # length of layer info section + my $num = Get16s(\$data, $psiz); + # check for Lr16 block if layers length is 0 (ref https://forums.adobe.com/thread/1540914) + if ($len == 0 and $num == 0) { + $raf->Read($data,10) == 10 or return 0; + if ($data =~ /^..8BIMLr16/s) { + $raf->Read($data, $psiz+2) == $psiz+2 or return 0; + $len = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0); + } elsif ($data =~ /^..8BIMMt16/s) { # (have seen Mt16 before Lr16, ref PH) + $raf->Read($data, $psiz) == $psiz or return 0; + $raf->Read($data, 8) == 8 or return 0; + if ($data eq '8BIMLr16') { + $raf->Read($data, $psiz+2) == $psiz+2 or return 0; + $len = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0); + } else { + $raf->Seek(-18-$psiz, 1) or return 0; + } + } else { + $raf->Seek(-10, 1) or return 0; + } + } + $len += 2; # include layer count with layer info section + $raf->Seek(-2, 1) or return 0; + my %dinfo = ( + RAF => $raf, + DirLen => $len, + ); + $$et{IsPSB} = $psb; # set PSB flag + ProcessLayers($et, \%dinfo, $tagTablePtr); + + # seek to the end of this section and return success flag + return $raf->Seek($end, 0) ? 1 : 0; +} + +#------------------------------------------------------------------------------ +# Process Photoshop layers (beginning with layer count) +# Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref +# Returns: 1 on success +# Notes: Uses ExifTool IsPSB member to determine whether file is PSB format +sub ProcessLayers($$$) +{ + local $_; + my ($et, $dirInfo, $tagTablePtr) = @_; + my ($i, $n, %count, $buff, $buf2); + my $raf = $$dirInfo{RAF}; + my $dirLen = $$dirInfo{DirLen}; + my $verbose = $$et{OPTIONS}{Verbose}; + my %dinfo = ( DataPt => \$buff, Base => $raf->Tell() ); + my $pos = 0; + return 0 if $dirLen < 2; + $raf->Read($buff, 2) == 2 or return 0; + my $num = Get16s(\$buff, 0); # number of layers + $num = -$num if $num < 0; # (first channel is transparency data if negative) + $et->VerboseDir('Layers', $num, $dirLen); + $et->HandleTag($tagTablePtr, '_xcnt', $num, Start => $pos, Size => 2, %dinfo); # LayerCount + my $oldIndent = $$et{INDENT}; + $$et{INDENT} .= '| '; + $pos += 2; + my $psb = $$et{IsPSB}; # is PSB format? + my $psiz = $psb ? 8 : 4; + for ($i=0; $i<$num; ++$i) { # process each layer + $et->VPrint(0, $oldIndent.'+ [Layer '.($i+1)." of $num]\n"); + last if $pos + 18 > $dirLen; + $raf->Read($buff, 18) == 18 or last; + $dinfo{DataPos} = $pos; + # save the layer rectangle + $et->HandleTag($tagTablePtr, '_xrct', undef, Start => 0, Size => 16, %dinfo); + my $numChannels = Get16u(\$buff, 16); + $n = (2 + $psiz) * $numChannels; # size of channel information + $raf->Seek($n, 1) or last; + $pos += 18 + $n; + last if $pos + 20 > $dirLen; + $raf->Read($buff, 20) == 20 or last; + $dinfo{DataPos} = $pos; + my $sig = substr($buff, 0, 4); + $sig =~ /^(8BIM|MIB8)$/ or last; # verify signature + $et->HandleTag($tagTablePtr, '_xbnd', undef, Start => 4, Size => 4, %dinfo); + $et->HandleTag($tagTablePtr, '_xopc', undef, Start => 8, Size => 1, %dinfo); + $et->HandleTag($tagTablePtr, '_xvis', undef, Start =>10, Size => 1, %dinfo); + my $nxt = $pos + 16 + Get32u(\$buff, 12); + $n = Get32u(\$buff, 16); # get size of layer mask data + $pos += 20 + $n; # skip layer mask data + last if $pos + 4 > $dirLen; + $raf->Seek($n, 1) and $raf->Read($buff, 4) == 4 or last; + $n = Get32u(\$buff, 0); # get size of layer blending ranges + $pos += 4 + $n; # skip layer blending ranges data + last if $pos + 1 > $dirLen; + $raf->Seek($n, 1) and $raf->Read($buff, 1) == 1 or last; + $n = Get8u(\$buff, 0); # get length of layer name + last if $pos + 1 + $n > $dirLen; + $raf->Read($buff, $n) == $n or last; + $dinfo{DataPos} = $pos + 1; + $et->HandleTag($tagTablePtr, '_xnam', undef, Start => 0, Size => $n, %dinfo); + my $frag = ($n + 1) & 0x3; + $raf->Seek(4 - $frag, 1) or last if $frag; + $n = ($n + 4) & 0xfffffffc; # +1 for length byte then pad to multiple of 4 bytes + $pos += $n; + # process additional layer info + while ($pos + 12 <= $nxt) { + $raf->Read($buff, 12) == 12 or last; + my $dat = substr($buff, 0, 8); + $dat = pack 'N*', unpack 'V*', $dat if GetByteOrder() eq 'II'; + my $sig = substr($dat, 0, 4); + last unless $sig eq '8BIM' or $sig eq '8B64'; # verify signature + my $tag = substr($dat, 4, 4); + # (some structures have an 8-byte size word [augh!] + # --> it would be great if '8B64' indicated a 64-bit version, and this may well + # be the case, but it is not mentioned in the Photoshop file format specification) + if ($psb and $tag =~ /^(LMsk|Lr16|Lr32|Layr|Mt16|Mt32|Mtrn|Alph|FMsk|lnk2|FEid|FXid|PxSD)$/) { + last if $pos + 16 > $nxt; + $raf->Read($buf2, 4) == 4 or last; + $buff .= $buf2; + $n = Get64u(\$buff, 8); + $pos += 4; + } else { + $n = Get32u(\$buff, 8); + } + $pos += 12; + last if $pos + $n > $nxt; + $frag = $n & 0x3; + if ($$tagTablePtr{$tag} or $verbose) { + # pad with empty entries if necessary to keep the same index for each item in the layer + $count{$tag} = 0 unless defined $count{$tag}; + $raf->Read($buff, $n) == $n or last; + $dinfo{DataPos} = $pos; + while ($count{$tag} < $i) { + $et->HandleTag($tagTablePtr, $tag, $tag eq 'lsct' ? 0 : ''); + ++$count{$tag}; + } + $et->HandleTag($tagTablePtr, $tag, undef, Start => 0, Size => $n, %dinfo); + ++$count{$tag}; + if ($frag) { + $raf->Seek(4 - $frag, 1) or last; + $n += 4 - $frag; # pad to multiple of 4 bytes (PH NC) + } + } else { + $n += 4 - $frag if $frag; + $raf->Seek($n, 1) or last; + } + $pos += $n; # step to start of next structure + } + $pos = $nxt; + } + # pad lists if necessary to have an entry for each layer + foreach (sort keys %count) { + while ($count{$_} < $num) { + $et->HandleTag($tagTablePtr, $_, $_ eq 'lsct' ? 0 : ''); + ++$count{$_}; + } + } + $$et{INDENT} = $oldIndent; + return 1; +} + +#------------------------------------------------------------------------------ +# Process Photoshop ImageSourceData +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessDocumentData($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $verbose = $$et{OPTIONS}{Verbose}; + my $raf = $$dirInfo{RAF}; + my $dirLen = $$dirInfo{DirLen}; + my $pos = 36; # length of header + my ($buff, $n, $err); + + $et->VerboseDir('Photoshop Document Data', undef, $dirLen); + unless ($raf) { + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart} || 0; + $raf = new File::RandomAccess($dataPt); + $raf->Seek($start, 0) if $start; + $dirLen = length $$dataPt - $start unless defined $dirLen; + $et->VerboseDump($dataPt, Start => $start, Len => $dirLen, Base => $$dirInfo{Base}); + } + unless ($raf->Read($buff, $pos) == $pos and + $buff =~ /^Adobe Photoshop Document Data (Block|V0002)\0/) + { + $et->Warn('Invalid Photoshop Document Data'); + return 0; + } + my $psb = ($1 eq 'V0002'); + my %dinfo = ( DataPt => \$buff ); + $$et{IsPSB} = $psb; # set PSB flag (needed when handling Layers directory) + while ($pos + 12 <= $dirLen) { + $raf->Read($buff, 8) == 8 or $err = 'Error reading document data', last; + # set byte order according to byte order of first signature + SetByteOrder($buff =~ /^(8BIM|8B64)/ ? 'MM' : 'II') if $pos == 36; + $buff = pack 'N*', unpack 'V*', $buff if GetByteOrder() eq 'II'; + my $sig = substr($buff, 0, 4); + $sig eq '8BIM' or $sig eq '8B64' or $err = 'Bad photoshop resource', last; # verify signature + my $tag = substr($buff, 4, 4); + if ($psb and $tag =~ /^(LMsk|Lr16|Lr32|Layr|Mt16|Mt32|Mtrn|Alph|FMsk|lnk2|FEid|FXid|PxSD)$/) { + $pos + 16 > $dirLen and $err = 'Short PSB resource', last; + $raf->Read($buff, 8) == 8 or $err = 'Error reading PSB resource', last; + $n = Get64u(\$buff, 0); + $pos += 4; + } else { + $raf->Read($buff, 4) == 4 or $err = 'Error reading PSD resource', last; + $n = Get32u(\$buff, 0); + } + $pos += 12; + $pos + $n > $dirLen and $err = 'Truncated photoshop resource', last; + my $pad = (4 - ($n & 3)) & 3; # number of padding bytes + my $tagInfo = $$tagTablePtr{$tag}; + if ($tagInfo or $verbose) { + if ($tagInfo and $$tagInfo{SubDirectory}) { + my $fpos = $raf->Tell() + $n + $pad; + my $subTable = GetTagTable($$tagInfo{SubDirectory}{TagTable}); + $et->ProcessDirectory({ RAF => $raf, DirLen => $n }, $subTable); + $raf->Seek($fpos, 0) or $err = 'Seek error', last; + } else { + $dinfo{DataPos} = $raf->Tell(); + $dinfo{Start} = 0; + $dinfo{Size} = $n; + $raf->Read($buff, $n) == $n or $err = 'Error reading photoshop resource', last; + $et->HandleTag($tagTablePtr, $tag, undef, %dinfo); + $raf->Seek($pad, 1) or $err = 'Seek error', last; + } + } else { + $raf->Seek($n + $pad, 1) or $err = 'Seek error', last; + } + $pos += $n + $pad; # step to start of next structure + } + $err and $et->Warn($err); + return 1; +} + +#------------------------------------------------------------------------------ +# Process Photoshop APP13 record +# Inputs: 0) ExifTool object reference, 1) Reference to directory information +# 2) Tag table reference +# Returns: 1 on success +sub ProcessPhotoshop($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $dirEnd = $pos + $$dirInfo{DirLen}; + my $verbose = $et->Options('Verbose'); + my $success = 0; + + # ignore non-standard XMP while in strict MWG compatibility mode + if (($Image::ExifTool::MWG::strict or $et->Options('Validate')) and + $$et{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/) + { + my $path = $et->MetadataPath(); + unless ($path =~ /^(JPEG-APP13-Photoshop|TIFF-IFD0-Photoshop|PSD)$/) { + if ($Image::ExifTool::MWG::strict) { + $et->Warn("Ignored non-standard Photoshop at $path"); + return 1; + } else { + $et->Warn("Non-standard Photoshop at $path", 1); + } + } + } + if ($$et{FILE_TYPE} eq 'JPEG' and $$dirInfo{Parent} ne 'APP13') { + $$et{LOW_PRIORITY_DIR}{'*'} = 1; # lower priority of all these tags + } + SetByteOrder('MM'); # Photoshop is always big-endian + $verbose and $et->VerboseDir('Photoshop', 0, $$dirInfo{DirLen}); + + # scan through resource blocks: + # Format: 0) Type, 4 bytes - '8BIM' (or the rare 'PHUT', 'DCSR', 'AgHg' or 'MeSa') + # 1) TagID,2 bytes + # 2) Name, pascal string padded to even no. bytes + # 3) Size, 4 bytes - N + # 4) Data, N bytes + while ($pos + 8 < $dirEnd) { + my $type = substr($$dataPt, $pos, 4); + my ($ttPtr, $extra, $val, $name); + if ($type eq '8BIM') { + $ttPtr = $tagTablePtr; + } elsif ($type =~ /^(PHUT|DCSR|AgHg|MeSa)$/) { # (PHUT~ImageReady, MeSa~PhotoDeluxe) + $ttPtr = GetTagTable('Image::ExifTool::Photoshop::Unknown'); + } else { + $type =~ s/([^\w])/sprintf("\\x%.2x",ord($1))/ge; + $et->Warn(qq{Bad Photoshop IRB resource "$type"}); + last; + } + my $tag = Get16u($dataPt, $pos + 4); + $pos += 6; # point to start of name + my $nameLen = Get8u($dataPt, $pos); + my $namePos = ++$pos; + # skip resource block name (pascal string, padded to an even # of bytes) + $pos += $nameLen; + ++$pos unless $nameLen & 0x01; + if ($pos + 4 > $dirEnd) { + $et->Warn("Bad Photoshop resource block"); + last; + } + my $size = Get32u($dataPt, $pos); + $pos += 4; + if ($size + $pos > $dirEnd) { + $et->Warn("Bad Photoshop resource data size $size"); + last; + } + $success = 1; + if ($nameLen) { + $name = substr($$dataPt, $namePos, $nameLen); + $extra = qq{, Name="$name"}; + } else { + $name = ''; + } + my $tagInfo = $et->GetTagInfo($ttPtr, $tag); + # append resource name to value if requested (braced by "/#...#/") + if ($tagInfo and defined $$tagInfo{SetResourceName} and + $$tagInfo{SetResourceName} eq '1' and $name !~ m{/#}) + { + $val = substr($$dataPt, $pos, $size) . '/#' . $name . '#/'; + } + $et->HandleTag($ttPtr, $tag, $val, + TagInfo => $tagInfo, + Extra => $extra, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Size => $size, + Start => $pos, + Base => $$dirInfo{Base}, + Parent => $$dirInfo{DirName}, + ); + $size += 1 if $size & 0x01; # size is padded to an even # bytes + $pos += $size; + } + # warn about incorrect IPTCDigest + if ($$et{VALUE}{IPTCDigest} and $$et{VALUE}{CurrentIPTCDigest} and + $$et{VALUE}{IPTCDigest} ne $$et{VALUE}{CurrentIPTCDigest}) + { + $et->WarnOnce('IPTCDigest is not current. XMP may be out of sync'); + } + delete $$et{LOW_PRIORITY_DIR}{'*'}; + return $success; +} + +#------------------------------------------------------------------------------ +# extract information from Photoshop PSD file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 if this was a valid PSD file, -1 on write error +sub ProcessPSD($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my ($data, $err, $tagTablePtr); + + $raf->Read($data, 30) == 30 or return 0; + $data =~ /^8BPS\0([\x01\x02])/ or return 0; + SetByteOrder('MM'); + $et->SetFileType($1 eq "\x01" ? 'PSD' : 'PSB'); # set the FileType tag + my %dirInfo = ( + DataPt => \$data, + DirStart => 0, + DirName => 'Photoshop', + ); + my $len = Get32u(\$data, 26); + if ($outfile) { + Write($outfile, $data) or $err = 1; + $raf->Read($data, $len) == $len or return -1; + Write($outfile, $data) or $err = 1; # write color mode data + # initialize map of where things are written + $et->InitWriteDirs(\%psdMap); + } else { + # process the header + $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Header'); + $dirInfo{DirLen} = 30; + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + $raf->Seek($len, 1) or $err = 1; # skip over color mode data + } + # read image resource section + $raf->Read($data, 4) == 4 or $err = 1; + $len = Get32u(\$data, 0); + $raf->Read($data, $len) == $len or $err = 1; + $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main'); + $dirInfo{DirLen} = $len; + my $rtnVal = 1; + if ($outfile) { + # rewrite IRB resources + $data = WritePhotoshop($et, \%dirInfo, $tagTablePtr); + if ($data) { + $len = Set32u(length $data); + Write($outfile, $len, $data) or $err = 1; + # look for trailer and edit if necessary + my $trailInfo = Image::ExifTool::IdentifyTrailer($raf); + if ($trailInfo) { + my $tbuf = ''; + $$trailInfo{OutFile} = \$tbuf; # rewrite trailer(s) + # rewrite all trailers to buffer + if ($et->ProcessTrailers($trailInfo)) { + my $copyBytes = $$trailInfo{DataPos} - $raf->Tell(); + if ($copyBytes >= 0) { + # copy remaining PSD file up to start of trailer + while ($copyBytes) { + my $n = ($copyBytes > 65536) ? 65536 : $copyBytes; + $raf->Read($data, $n) == $n or $err = 1; + Write($outfile, $data) or $err = 1; + $copyBytes -= $n; + } + # write the trailer (or not) + $et->WriteTrailerBuffer($trailInfo, $outfile) or $err = 1; + } else { + $et->Warn('Overlapping trailer'); + undef $trailInfo; + } + } else { + undef $trailInfo; + } + } + unless ($trailInfo) { + # copy over the rest of the file + while ($raf->Read($data, 65536)) { + Write($outfile, $data) or $err = 1; + } + } + } else { + $err = 1; + } + $rtnVal = -1 if $err; + } elsif ($err) { + $et->Warn('File format error'); + } else { + # read IRB resources + ProcessPhotoshop($et, \%dirInfo, $tagTablePtr); + # read layer and mask information section + $dirInfo{RAF} = $raf; + $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Layers'); + my $oldIndent = $$et{INDENT}; + $$et{INDENT} .= '| '; + if (ProcessLayersAndMask($et, \%dirInfo, $tagTablePtr) and + # read compression mode from image data section + $raf->Read($data,2) == 2) + { + my %dirInfo = ( + DataPt => \$data, + DataPos => $raf->Tell() - 2, + ); + $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::ImageData'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + $$et{INDENT} = $oldIndent; + # process trailers if they exist + my $trailInfo = Image::ExifTool::IdentifyTrailer($raf); + $et->ProcessTrailers($trailInfo) if $trailInfo; + } + return $rtnVal; +} + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::Photoshop - Read/write Photoshop IRB meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +Photoshop writes its own format of meta information called a Photoshop IRB +resource which is located in the APP13 record of JPEG files. This module +contains the definitions to read this information. + +=head1 NOTES + +Photoshop IRB blocks may have an associated resource name. These names are +usually just an empty string, but if not empty they are displayed in the +verbose level 2 (or greater) output. A special C<SetResourceName> flag may +be set to '1' in the tag information hash to cause the resource name to be +appended to the value when extracted. If this is done, the returned value +has the form "VALUE/#NAME#/". When writing, the writer routine looks for +this syntax (if C<SetResourceName> is defined), and and uses the embedded +name to set the name of the new resource. This allows the resource names to +be preserved when copying Photoshop information via user-defined tags. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.fine-view.com/jp/lab/doc/ps6ffspecsv2.pdf> + +=item L<http://www.ozhiker.com/electronics/pjmt/jpeg_info/irb_jpeg_qual.html> + +=item L<http://www.fileformat.info/format/psd/egff.htm> + +=item L<http://libpsd.graphest.com/files/Photoshop%20File%20Formats.pdf> + +=item L<http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Photoshop Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::MetaData::JPEG(3pm)|Image::MetaData::JPEG> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/PostScript.pm b/ExifTool/lib/Image/ExifTool/PostScript.pm new file mode 100644 index 0000000..361ffea --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PostScript.pm @@ -0,0 +1,802 @@ +#------------------------------------------------------------------------------ +# File: PostScript.pm +# +# Description: Read PostScript meta information +# +# Revisions: 07/08/2005 - P. Harvey Created +# +# References: 1) http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf +# 2) http://partners.adobe.com/public/developer/en/ps/5001.DSC_Spec.pdf +# 3) http://partners.adobe.com/public/developer/en/illustrator/sdk/AI7FileFormat.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::PostScript; + +use strict; +use vars qw($VERSION $AUTOLOAD); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.44'; + +sub WritePS($$); +sub ProcessPS($$;$); + +# PostScript tag table +%Image::ExifTool::PostScript::Main = ( + PROCESS_PROC => \&ProcessPS, + WRITE_PROC => \&WritePS, + PREFERRED => 1, # always add these tags when writing + GROUPS => { 2 => 'Image' }, + # Note: Make all of these tags priority 0 since the first one found at + # the start of the file should take priority (in case multiples exist) + Author => { Priority => 0, Groups => { 2 => 'Author' }, Writable => 'string' }, + BoundingBox => { Priority => 0 }, + Copyright => { Priority => 0, Writable => 'string' }, #2 + CreationDate => { + Name => 'CreateDate', + Priority => 0, + Groups => { 2 => 'Time' }, + Writable => 'string', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + Creator => { Priority => 0, Writable => 'string' }, + ImageData => { Priority => 0 }, + For => { Priority => 0, Writable => 'string', Notes => 'for whom the document was prepared'}, + Keywords => { Priority => 0, Writable => 'string' }, + ModDate => { + Name => 'ModifyDate', + Priority => 0, + Groups => { 2 => 'Time' }, + Writable => 'string', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + Pages => { Priority => 0 }, + Routing => { Priority => 0, Writable => 'string' }, #2 + Subject => { Priority => 0, Writable => 'string' }, + Title => { Priority => 0, Writable => 'string' }, + Version => { Priority => 0, Writable => 'string' }, #2 + # these subdirectories for documentation only + BeginPhotoshop => { + Name => 'PhotoshopData', + SubDirectory => { + TagTable => 'Image::ExifTool::Photoshop::Main', + }, + }, + BeginICCProfile => { + Name => 'ICC_Profile', + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + }, + }, + begin_xml_packet => { + Name => 'XMP', + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + }, + }, + TIFFPreview => { + Groups => { 2 => 'Preview' }, + Binary => 1, + Notes => q{ + not a real tag ID, but used to represent the TIFF preview extracted from DOS + EPS images + }, + }, + BeginDocument => { + Name => 'EmbeddedFile', + SubDirectory => { + TagTable => 'Image::ExifTool::PostScript::Main', + }, + Notes => 'extracted with L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> option', + }, + EmbeddedFileName => { + Notes => q{ + not a real tag ID, but the file name from a BeginDocument statement. + Extracted with document metadata when L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> option is used + }, + }, + # AI metadata (most with a single leading '%') + AI9_ColorModel => { + Name => 'AIColorModel', + PrintConv => { + 1 => 'RGB', + 2 => 'CMYK', + }, + }, + AI3_ColorUsage => { Name => 'AIColorUsage' }, + AI5_RulerUnits => { + Name => 'AIRulerUnits', + PrintConv => { + 0 => 'Inches', + 1 => 'Millimeters', + 2 => 'Points', + 3 => 'Picas', + 4 => 'Centimeters', + 6 => 'Pixels', + }, + }, + AI5_TargetResolution => { Name => 'AITargetResolution' }, + AI5_NumLayers => { Name => 'AINumLayers' }, + AI5_FileFormat => { Name => 'AIFileFormat' }, + AI8_CreatorVersion => { Name => 'AICreatorVersion' }, # (double leading '%') + AI12_BuildNumber => { Name => 'AIBuildNumber' }, +); + +# composite tags +%Image::ExifTool::PostScript::Composite = ( + GROUPS => { 2 => 'Image' }, + # BoundingBox is in points, not pixels, + # but use it anyway if ImageData is not available + ImageWidth => { + Desire => { + 0 => 'Main:PostScript:ImageData', + 1 => 'PostScript:BoundingBox', + }, + ValueConv => 'Image::ExifTool::PostScript::ImageSize(\@val, 0)', + }, + ImageHeight => { + Desire => { + 0 => 'Main:PostScript:ImageData', + 1 => 'PostScript:BoundingBox', + }, + ValueConv => 'Image::ExifTool::PostScript::ImageSize(\@val, 1)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::PostScript'); + +#------------------------------------------------------------------------------ +# AutoLoad our writer routines when necessary +# +sub AUTOLOAD +{ + return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); +} + +#------------------------------------------------------------------------------ +# Is this a PC system +# Returns: true for PC systems +my %isPC = (MSWin32 => 1, os2 => 1, dos => 1, NetWare => 1, symbian => 1, cygwin => 1); +sub IsPC() +{ + return $isPC{$^O}; +} + +#------------------------------------------------------------------------------ +# Get image width or height +# Inputs: 0) value list ref (ImageData, BoundingBox), 1) true to get height +sub ImageSize($$) +{ + my ($vals, $getHeight) = @_; + my ($w, $h); + if ($$vals[0] and $$vals[0] =~ /^(\d+) (\d+)/) { + ($w, $h) = ($1, $2); + } elsif ($$vals[1] and $$vals[1] =~ /^(\d+) (\d+) (\d+) (\d+)/) { + ($w, $h) = ($3 - $1, $4 - $2); + } + return $getHeight ? $h : $w; +} + +#------------------------------------------------------------------------------ +# Set PostScript format error warning +# Inputs: 0) ExifTool object reference, 1) error string +# Returns: 1 +sub PSErr($$) +{ + my ($et, $str) = @_; + # set file type if not done already + my $ext = $$et{FILE_EXT}; + $et->SetFileType(($ext and $ext eq 'AI') ? 'AI' : 'PS'); + $et->Warn("PostScript format error ($str)"); + return 1; +} + +#------------------------------------------------------------------------------ +# Return input record separator to use for the specified file +# Inputs: 0) RAF reference +# Returns: Input record separator or undef on error +sub GetInputRecordSeparator($) +{ + my $raf = shift; + my $pos = $raf->Tell(); # save current position + my ($data, $sep); + $raf->Read($data,256) or return undef; + my ($a, $d) = (999,999); + $a = pos($data), pos($data) = 0 if $data =~ /\x0a/g; + $d = pos($data) if $data =~ /\x0d/g; + my $diff = $a - $d; + if ($diff == 1) { + $sep = "\x0d\x0a"; + } elsif ($diff == -1) { + $sep = "\x0a\x0d"; + } elsif ($diff > 0) { + $sep = "\x0d"; + } elsif ($diff < 0) { + $sep = "\x0a"; + } # else error + $raf->Seek($pos, 0); # restore original position + return $sep; +} + +#------------------------------------------------------------------------------ +# Split into lines ending in any CR, LF or CR+LF combination +# (this is annoying, and could be avoided if EPS files didn't mix linefeeds!) +# Inputs: 0) data pointer, 1) reference to lines array +# Notes: Fills @$lines with lines from splitting $$dataPt +sub SplitLine($$) +{ + my ($dataPt, $lines) = @_; + for (;;) { + my $endl; + # find the position of the first LF (\x0a) + $endl = pos($$dataPt), pos($$dataPt) = 0 if $$dataPt =~ /\x0a/g; + if ($$dataPt =~ /\x0d/g) { # find the first CR (\x0d) + if (defined $endl) { + # (remember, CR+LF is a DOS newline...) + $endl = pos($$dataPt) if pos($$dataPt) < $endl - 1; + } else { + $endl = pos($$dataPt); + } + } elsif (not defined $endl) { + push @$lines, $$dataPt; + last; + } + if (length $$dataPt == $endl) { + push @$lines, $$dataPt; + last; + } else { + # continue to split into separate lines + push @$lines, substr($$dataPt, 0, $endl); + $$dataPt = substr($$dataPt, $endl); + } + } +} + +#------------------------------------------------------------------------------ +# check to be sure we haven't read past end of PS data in DOS-style file +# Inputs: 0) RAF ref (with PSEnd member), 1) data ref +# - modifies data and sets RAF to EOF if end of PS is reached +sub CheckPSEnd($$) +{ + my ($raf, $dataPt) = @_; + my $pos = $raf->Tell(); + if ($pos >= $$raf{PSEnd}) { + $raf->Seek(0, 2); # seek to end of file so we can't read any more + $$dataPt = substr($$dataPt, 0, length($$dataPt) - $pos + $$raf{PSEnd}) if $pos > $$raf{PSEnd}; + } +} + +#------------------------------------------------------------------------------ +# Read next line from EPS file +# Inputs: 0) RAF ref (with PSEnd member if Postscript ends before end of file) +# 1) array of lines from file +# Returns: true on success +sub GetNextLine($$) +{ + my ($raf, $lines) = @_; + my ($data, $changedNL); + my $altnl = ($/ eq "\x0d") ? "\x0a" : "\x0d"; + for (;;) { + $raf->ReadLine($data) or last; + $$raf{PSEnd} and CheckPSEnd($raf, \$data); + # split line if it contains other newline sequences + if ($data =~ /$altnl/) { + if (length($data) > 500000 and IsPC()) { + # patch for Windows memory problem + unless ($changedNL) { + $changedNL = $/; + $/ = $altnl; + $altnl = $changedNL; + $raf->Seek(-length($data), 1); + next; + } + } else { + # split into separate lines + # push @$lines, split /$altnl/, $data, -1; + # if (@$lines == 2 and $$lines[1] eq $/) { + # # handle case of DOS newline data inside file using Unix newlines + # $$lines[0] .= pop @$lines; + # } + # split into separate lines if necessary + SplitLine(\$data, $lines); + } + } else { + push @$lines, $data; + } + $/ = $changedNL if $changedNL; + return 1; + } + return 0; +} + +#------------------------------------------------------------------------------ +# Decode comment from PostScript file +# Inputs: 0) comment string, 1) RAF ref, 2) reference to lines array +# 3) optional data reference for extra lines read from file +# Returns: Decoded comment string (may be an array reference) +# - handles multi-line comments and escape sequences +sub DecodeComment($$$;$) +{ + my ($val, $raf, $lines, $dataPt) = @_; + $val =~ s/\x0d*\x0a*$//; # remove trailing CR, LF or CR/LF + # check for continuation comments + for (;;) { + @$lines or GetNextLine($raf, $lines) or last; + last unless $$lines[0] =~ /^%%\+/; # is the next line a continuation? + $$dataPt .= $$lines[0] if $dataPt; # add to data if necessary + $$lines[0] =~ s/\x0d*\x0a*$//; # remove trailing CR, LF or CR/LF + $val .= substr(shift(@$lines), 3); # add to value (without leading "%%+") + } + my @vals; + # handle bracketed string values + if ($val =~ s/^\((.*)\)$/$1/) { # remove brackets if necessary + # split into an array of strings if necessary + my $nesting = 1; + while ($val =~ /(\(|\))/g) { + my $bra = $1; + my $pos = pos($val) - 2; + my $backslashes = 0; + while ($pos and substr($val, $pos, 1) eq '\\') { + --$pos; + ++$backslashes; + } + next if $backslashes & 0x01; # escaped if odd number + if ($bra eq '(') { + ++$nesting; + } else { + --$nesting; + unless ($nesting) { + push @vals, substr($val, 0, pos($val)-1); + $val = substr($val, pos($val)); + ++$nesting if $val =~ s/\s*\(//; + } + } + } + push @vals, $val; + foreach $val (@vals) { + # decode escape sequences in bracketed strings + # (similar to code in PDF.pm, but without line continuation) + while ($val =~ /\\(.)/sg) { + my $n = pos($val) - 2; + my $c = $1; + my $r; + if ($c =~ /[0-7]/) { + # get up to 2 more octal digits + $c .= $1 if $val =~ /\G([0-7]{1,2})/g; + # convert octal escape code + $r = chr(oct($c) & 0xff); + } else { + # convert escaped characters + ($r = $c) =~ tr/nrtbf/\n\r\t\b\f/; + } + substr($val, $n, length($c)+1) = $r; + # continue search after this character + pos($val) = $n + length($r); + } + } + $val = @vals > 1 ? \@vals : $vals[0]; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Unescape PostScript string +# Inputs: 0) string +# Returns: unescaped string +sub UnescapePostScript($) +{ + my $str = shift; + # decode escape sequences in literal strings + while ($str =~ /\\(.)/sg) { + my $n = pos($str) - 2; + my $c = $1; + my $r; + if ($c =~ /[0-7]/) { + # get up to 2 more octal digits + $c .= $1 if $str =~ /\G([0-7]{1,2})/g; + # convert octal escape code + $r = chr(oct($c) & 0xff); + } elsif ($c eq "\x0d") { + # the string is continued if the line ends with '\' + # (also remove "\x0d\x0a") + $c .= $1 if $str =~ /\G(\x0a)/g; + $r = ''; + } elsif ($c eq "\x0a") { + $r = ''; + } else { + # convert escaped characters + ($r = $c) =~ tr/nrtbf/\n\r\t\b\f/; + } + substr($str, $n, length($c)+1) = $r; + # continue search after this character + pos($str) = $n + length($r); + } + return $str; +} + +#------------------------------------------------------------------------------ +# Extract information from EPS, PS or AI file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) optional tag table ref +# Returns: 1 if this was a valid PostScript file +sub ProcessPS($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $embedded = $et->Options('ExtractEmbedded'); + my ($data, $dos, $endDoc, $fontTable, $comment); + + # allow read from data + unless ($raf) { + $raf = new File::RandomAccess($$dirInfo{DataPt}); + $et->VerboseDir('PostScript'); + } +# +# determine if this is a postscript file +# + $raf->Read($data, 4) == 4 or return 0; + # accept either ASCII or DOS binary postscript file format + return 0 unless $data =~ /^(%!PS|%!Ad|%!Fo|\xc5\xd0\xd3\xc6)/; + if ($data =~ /^%!Ad/) { + # I've seen PS files start with "%!Adobe-PS"... + return 0 unless $raf->Read($data, 6) == 6 and $data eq "obe-PS"; + } elsif ($data =~ /^\xc5\xd0\xd3\xc6/) { + # process DOS binary file header + # - save DOS header then seek ahead and check PS header + $raf->Read($dos, 26) == 26 or return 0; + SetByteOrder('II'); + my $psStart = Get32u(\$dos, 0); + unless ($raf->Seek($psStart, 0) and + $raf->Read($data, 4) == 4 and $data eq '%!PS') + { + return PSErr($et, 'invalid header'); + } + $$raf{PSEnd} = $psStart + Get32u(\$dos, 4); # set end of PostScript data in RAF + } else { + # check for PostScript font file (PFA or PFB) + my $d2; + $data .= $d2 if $raf->Read($d2,12); + if ($data =~ /^%!(PS-(AdobeFont-|Bitstream )|FontType1-)/) { + $et->SetFileType('PFA'); # PostScript ASCII font file + $fontTable = GetTagTable('Image::ExifTool::Font::PSInfo'); + # PostScript font files may contain an unformatted comments which may + # contain useful information, so accumulate these for the Comment tag + $comment = 1; + } + $raf->Seek(-length($data), 1); + } +# +# set the newline type based on the first newline found in the file +# + local $/ = GetInputRecordSeparator($raf); + $/ or return PSErr($et, 'invalid PS data'); + + # set file type (PostScript or EPS) + $raf->ReadLine($data) or $data = ''; + my $type; + if ($data =~ /EPSF/) { + $type = 'EPS'; + } else { + # read next line to see if this is an Illustrator file + my $line2; + my $pos = $raf->Tell(); + if ($raf->ReadLine($line2) and $line2 =~ /^%%Creator: Adobe Illustrator/) { + $type = 'AI'; + } else { + $type = 'PS'; + } + $raf->Seek($pos, 0); + } + $et->SetFileType($type); + return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3; +# +# extract TIFF information from DOS header +# + $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::PostScript::Main'); + if ($dos) { + my $base = Get32u(\$dos, 16); + if ($base) { + my $pos = $raf->Tell(); + # extract the TIFF preview + my $len = Get32u(\$dos, 20); + my $val = $et->ExtractBinary($base, $len, 'TIFFPreview'); + if (defined $val and $val =~ /^(MM\0\x2a|II\x2a\0|Binary)/) { + $et->HandleTag($tagTablePtr, 'TIFFPreview', $val); + } else { + $et->Warn('Bad TIFF preview image'); + } + # extract information from TIFF in DOS header + # (set Parent to '' to avoid setting FileType tag again) + my %dirInfo = ( + Parent => '', + RAF => $raf, + Base => $base, + ); + $et->ProcessTIFF(\%dirInfo) or $et->Warn('Bad embedded TIFF'); + # position file pointer to extract PS information + $raf->Seek($pos, 0); + } + } +# +# parse the postscript +# + my ($buff, $mode, $beginToken, $endToken, $docNum, $subDocNum, $changedNL); + my (@lines, $altnl); + if ($/ eq "\x0d") { + $altnl = "\x0a"; + } else { + $/ = "\x0a"; # end on any LF (even if DOS CR+LF) + $altnl = "\x0d"; + } + for (;;) { + if (@lines) { + $data = shift @lines; + } else { + $raf->ReadLine($data) or last; + # check for alternate newlines as efficiently as possible + if ($data =~ /$altnl/) { + if (length($data) > 500000 and IsPC()) { + # Windows can't split very long lines due to poor memory handling, + # so re-read the file with the other newline character instead + # (slower but uses less memory) + unless ($changedNL) { + $changedNL = 1; + my $t = $/; + $/ = $altnl; + $altnl = $t; + $raf->Seek(-length($data), 1); + next; + } + } else { + # split into separate lines + @lines = split /$altnl/, $data, -1; + $data = shift @lines; + if (@lines == 1 and $lines[0] eq $/) { + # handle case of DOS newline data inside file using Unix newlines + $data .= $lines[0]; + undef @lines; + } + } + } + } + undef $changedNL; + if ($mode) { + if (not $endToken) { + $buff .= $data; + next unless $data =~ m{<\?xpacket end=.(w|r).\?>(\n|\r|$)}; + } elsif ($data !~ /^$endToken/i) { + if ($mode eq 'XMP') { + $buff .= $data; + } elsif ($mode eq 'Document') { + # ignore embedded documents, but keep track of nesting level + $docNum .= '-1' if $data =~ /^$beginToken/; + } else { + # data is ASCII-hex encoded + $data =~ tr/0-9A-Fa-f//dc; # remove all but hex characters + $buff .= pack('H*', $data); # translate from hex + } + next; + } elsif ($mode eq 'Document') { + $docNum =~ s/-?\d+$//; # decrement document nesting level + # done with Document mode if we are back at the top level + undef $mode unless $docNum; + next; + } + } elsif ($endDoc and $data =~ /^$endDoc/i) { + $docNum =~ s/-?(\d+)$//; # decrement nesting level + $subDocNum = $1; # remember our last sub-document number + $$et{DOC_NUM} = $docNum; + undef $endDoc unless $docNum; # done with document if top level + next; + } elsif ($data =~ /^(%{1,2})(Begin)(_xml_packet|Photoshop|ICCProfile|Document|Binary)/i) { + # the beginning of a data block + my %modeLookup = ( + _xml_packet => 'XMP', + photoshop => 'Photoshop', + iccprofile => 'ICC_Profile', + document => 'Document', + binary => undef, # (we will try to skip this) + ); + $mode = $modeLookup{lc $3}; + unless ($mode) { + if (not @lines and $data =~ /^%{1,2}BeginBinary:\s*(\d+)/i) { + $raf->Seek($1, 1) or last; # skip binary data + } + next; + } + $buff = ''; + $beginToken = $1 . $2 . $3; + $endToken = $1 . ($2 eq 'begin' ? 'end' : 'End') . $3; + if ($mode eq 'Document') { + # this is either the 1st sub-document or Nth document + if ($docNum) { + # increase nesting level + $docNum .= '-' . (++$subDocNum); + } else { + # this is the Nth document + $docNum = $$et{DOC_COUNT} + 1; + } + $subDocNum = 0; # new level, so reset subDocNum + next unless $embedded; # skip over this document + # set document number for family 4-7 group names + $$et{DOC_NUM} = $docNum; + $$et{LIST_TAGS} = { }; # don't build lists across different documents + $$et{PROCESSED} = { }; # re-initialize processed directory lookup too + $endDoc = $endToken; # parse to EndDocument token + # reset mode to allow parsing into sub-directories + undef $endToken; + undef $mode; + # save document name if available + if ($data =~ /^$beginToken:\s+([^\n\r]+)/i) { + my $docName = $1; + # remove brackets if necessary + $docName = $1 if $docName =~ /^\((.*)\)$/; + $et->HandleTag($tagTablePtr, 'EmbeddedFileName', $docName); + } + } + next; + } elsif ($data =~ /^<\?xpacket begin=.{7,13}W5M0MpCehiHzreSzNTczkc9d/) { + # pick up any stray XMP data + $mode = 'XMP'; + $buff = $data; + undef $endToken; # no end token (just look for xpacket end) + # XMP could be contained in a single line (if newlines are different) + next unless $data =~ m{<\?xpacket end=.(w|r).\?>(\n|\r|$)}; + } elsif ($data =~ /^%%?(\w+): ?(.*)/s and $$tagTablePtr{$1}) { + my ($tag, $val) = ($1, $2); + # only allow 'ImageData' and AI tags to have single leading '%' + next unless $data =~ /^%(%|AI\d+_)/ or $tag eq 'ImageData'; + # decode comment string (reading continuation lines if necessary) + $val = DecodeComment($val, $raf, \@lines); + $et->HandleTag($tagTablePtr, $tag, $val); + next; + } elsif ($embedded and $data =~ /^%AI12_CompressedData/) { + # the rest of the file is compressed + unless (eval { require Compress::Zlib }) { + $et->Warn('Install Compress::Zlib to extract compressed embedded data'); + last; + } + # seek back to find the start of the compressed data in the file + my $tlen = length($data) + @lines; + $tlen += length $_ foreach @lines; + my $backTo = $raf->Tell() - $tlen - 64; + $backTo = 0 if $backTo < 0; + last unless $raf->Seek($backTo, 0) and $raf->Read($data, 2048); + last unless $data =~ s/.*?%AI12_CompressedData//; + my $inflate = Compress::Zlib::inflateInit(); + $inflate or $et->Warn('Error initializing inflate'), last; + # generate a PS-like file in memory from the compressed data + my $verbose = $et->Options('Verbose'); + if ($verbose > 1) { + $et->VerboseDir('AI12_CompressedData (first 4kB)'); + $et->VerboseDump(\$data); + } + # remove header if it exists (Windows AI files only) + $data =~ s/^.{0,256}EndData[\x0d\x0a]+//s; + my $val; + for (;;) { + my ($v2, $stat) = $inflate->inflate($data); + $stat == Compress::Zlib::Z_STREAM_END() and $val .= $v2, last; + $stat != Compress::Zlib::Z_OK() and undef($val), last; + if (defined $val) { + $val .= $v2; + } elsif ($v2 =~ /^%!PS/) { + $val = $v2; + } else { + # add postscript header (for file recognition) if it doesn't exist + $val = "%!PS-Adobe-3.0$/" . $v2; + } + $raf->Read($data, 65536) or last; + } + defined $val or $et->Warn('Error inflating AI compressed data'), last; + if ($verbose > 1) { + $et->VerboseDir('Uncompressed AI12 Data'); + $et->VerboseDump(\$val); + } + # extract information from embedded images in the uncompressed data + $val = # add PS header in case it needs one + ProcessPS($et, { DataPt => \$val }); + last; + } elsif ($fontTable) { + if (defined $comment) { + # extract initial comments from PostScript Font files + if ($data =~ /^%\s+(.*?)[\x0d\x0a]/) { + $comment .= "\n" if $comment; + $comment .= $1; + next; + } elsif ($data !~ /^%/) { + # stop extracting comments at the first non-comment line + $et->FoundTag('Comment', $comment) if length $comment; + undef $comment; + } + } + if ($data =~ m{^\s*/(\w+)\s*(.*)} and $$fontTable{$1}) { + my ($tag, $val) = ($1, $2); + if ($val =~ /^\((.*)\)/) { + $val = UnescapePostScript($1); + } elsif ($val =~ m{/?(\S+)}) { + $val = $1; + } + $et->HandleTag($fontTable, $tag, $val); + } elsif ($data =~ /^currentdict end/) { + # only extract tags from initial FontInfo dict + undef $fontTable; + } + next; + } else { + next; + } + # extract information from buffered data + my %dirInfo = ( + DataPt => \$buff, + DataLen => length $buff, + DirStart => 0, + DirLen => length $buff, + Parent => 'PostScript', + ); + my $subTablePtr = GetTagTable("Image::ExifTool::${mode}::Main"); + unless ($et->ProcessDirectory(\%dirInfo, $subTablePtr)) { + $et->Warn("Error processing $mode information in PostScript file"); + } + undef $buff; + undef $mode; + } + $mode = 'Document' if $endDoc and not $mode; + $mode and PSErr($et, "unterminated $mode data"); + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from EPS file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 if this was a valid PostScript file +sub ProcessEPS($$) +{ + return ProcessPS($_[0],$_[1]); +} + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::PostScript - Read PostScript meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This code reads meta information from EPS (Encapsulated PostScript), PS +(PostScript) and AI (Adobe Illustrator) files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf> + +=item L<http://partners.adobe.com/public/developer/en/ps/5001.DSC_Spec.pdf> + +=item L<http://partners.adobe.com/public/developer/en/illustrator/sdk/AI7FileFormat.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PostScript Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/PrintIM.pm b/ExifTool/lib/Image/ExifTool/PrintIM.pm new file mode 100644 index 0000000..ff7e448 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/PrintIM.pm @@ -0,0 +1,125 @@ +#------------------------------------------------------------------------------ +# File: PrintIM.pm +# +# Description: Read PrintIM meta information +# +# Revisions: 04/07/2004 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::PrintIM; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess); + +$VERSION = '1.07'; + +sub ProcessPrintIM($$$); + +# PrintIM table (proprietary specification by Epson) +%Image::ExifTool::PrintIM::Main = ( + PROCESS_PROC => \&ProcessPrintIM, + GROUPS => { 0 => 'PrintIM', 1 => 'PrintIM', 2 => 'Printing' }, + PRINT_CONV => 'sprintf("0x%.8x", $val)', + TAG_PREFIX => 'PrintIM', + PrintIMVersion => { # values: 0100, 0250, 0260, 0300 + Description => 'PrintIM Version', + PrintConv => undef, + }, + # the following names are from http://www.kanzaki.com/ns/exif + # but the decoding is unknown: + # 9 => { Name => 'PIMContrast', Unknown => 1 }, #1 + # 10 => { Name => 'PIMBrightness', Unknown => 1 }, #1 + # 11 => { Name => 'PIMColorbalance', Unknown => 1 }, #1 + # 12 => { Name => 'PIMSaturation', Unknown => 1 }, #1 + # 13 => { Name => 'PIMSharpness', Unknown => 1 }, #1 +); + + +#------------------------------------------------------------------------------ +# Process PrintIM IFD +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessPrintIM($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $offset = $$dirInfo{DirStart}; + my $size = $$dirInfo{DirLen}; + my $verbose = $et->Options('Verbose'); + + unless ($size) { + $et->Warn('Empty PrintIM data', 1); + return 0; + } + unless ($size > 15) { + $et->Warn('Bad PrintIM data'); + return 0; + } + unless (substr($$dataPt, $offset, 7) eq 'PrintIM') { + $et->Warn('Invalid PrintIM header'); + return 0; + } + # check size of PrintIM block + my $num = Get16u($dataPt, $offset + 14); + if ($size < 16 + $num * 6) { + # size is too big, maybe byte ordering is wrong + ToggleByteOrder(); + $num = Get16u($dataPt, $offset + 14); + if ($size < 16 + $num * 6) { + $et->Warn('Bad PrintIM size'); + return 0; + } + } + $verbose and $et->VerboseDir('PrintIM', $num); + $et->HandleTag($tagTablePtr, 'PrintIMVersion', substr($$dataPt, $offset + 8, 4), + DataPt => $dataPt, + Start => $offset + 8, + Size => 4, + ); + my $n; + for ($n=0; $n<$num; ++$n) { + my $pos = $offset + 16 + $n * 6; + my $tag = Get16u($dataPt, $pos); + my $val = Get32u($dataPt, $pos + 2); + $et->HandleTag($tagTablePtr, $tag, $val, + Index => $n, + DataPt => $dataPt, + Start => $pos + 2, + Size => 4, + ); + } + return 1; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::PrintIM - Read PrintIM meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to interpret +Print Image Matching meta information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/PrintIM Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Qualcomm.pm b/ExifTool/lib/Image/ExifTool/Qualcomm.pm new file mode 100644 index 0000000..64225ec --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Qualcomm.pm @@ -0,0 +1,1344 @@ +#------------------------------------------------------------------------------ +# File: Qualcomm.pm +# +# Description: Read Qualcomm APP7 meta information +# +# Revisions: 2012/02/14 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::Qualcomm; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.01'; + +sub ProcessQualcomm($$$); +sub MakeNameAndDesc($$); + +# Qualcomm format codes (ref PH (NC)) +my @qualcommFormat = ( + 'int8u', 'int8s', 'int16u', 'int16s', + 'int32u', 'int32s', 'float', 'double', +); + +# information found in JPEG APP7 Qualcomm Camera Attributes segment +%Image::ExifTool::Qualcomm::Main = ( + PROCESS_PROC => \&ProcessQualcomm, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + VARS => { NO_ID => 1, NO_LOOKUP => 1 }, # too long, too many, and too obscure + NOTES => q{ + The tags below have been observed in the JPEG APP7 "Qualcomm Camera + Attributes" segment written by some cameras such as the HP iPAQ Voice + Messenger. ExifTool will extract any information found from this segment, + even if it is not listed in this table. + }, + 'aec_current_sensor_luma' => { }, + 'af_position' => { }, + 'aec_current_exp_index' => { }, + 'awb_sample_decision' => { }, + 'asf5_enable' => { }, + 'asf5_filter_mode' => { }, + 'asf5_exposure_index_1' => { }, + 'asf5_exposure_index_2' => { }, + 'asf5_max_exposure_index' => { }, + 'asf5_luma_filter[0]' => { }, + 'asf5_luma_filter[1]' => { }, + 'asf5_luma_filter[2]' => { }, + 'asf5_luma_filter[3]' => { }, + 'asf5_luma_filter[4]' => { }, + 'asf5_luma_filter[5]' => { }, + 'asf5_luma_filter[6]' => { }, + 'asf5_luma_filter[7]' => { }, + 'asf5_luma_filter[8]' => { }, + 'asf5_filter1_a11' => { }, + 'asf5_filter1_a12' => { }, + 'asf5_filter1_a13' => { }, + 'asf5_filter1_a14' => { }, + 'asf5_filter1_a15' => { }, + 'asf5_filter1_a21' => { }, + 'asf5_filter1_a22' => { }, + 'asf5_filter1_a23' => { }, + 'asf5_filter1_a24' => { }, + 'asf5_filter1_a25' => { }, + 'asf5_filter1_a31' => { }, + 'asf5_filter1_a32' => { }, + 'asf5_filter1_a33' => { }, + 'asf5_filter1_a34' => { }, + 'asf5_filter1_a35' => { }, + 'asf5_filter1_a41' => { }, + 'asf5_filter1_a42' => { }, + 'asf5_filter1_a43' => { }, + 'asf5_filter1_a44' => { }, + 'asf5_filter1_a45' => { }, + 'asf5_filter1_a51' => { }, + 'asf5_filter1_a52' => { }, + 'asf5_filter1_a53' => { }, + 'asf5_filter1_a54' => { }, + 'asf5_filter1_a55' => { }, + 'asf5_filter2_a11' => { }, + 'asf5_filter2_a12' => { }, + 'asf5_filter2_a13' => { }, + 'asf5_filter2_a14' => { }, + 'asf5_filter2_a15' => { }, + 'asf5_filter2_a21' => { }, + 'asf5_filter2_a22' => { }, + 'asf5_filter2_a23' => { }, + 'asf5_filter2_a24' => { }, + 'asf5_filter2_a25' => { }, + 'asf5_filter2_a31' => { }, + 'asf5_filter2_a32' => { }, + 'asf5_filter2_a33' => { }, + 'asf5_filter2_a34' => { }, + 'asf5_filter2_a35' => { }, + 'asf5_filter2_a41' => { }, + 'asf5_filter2_a42' => { }, + 'asf5_filter2_a43' => { }, + 'asf5_filter2_a44' => { }, + 'asf5_filter2_a45' => { }, + 'asf5_filter2_a51' => { }, + 'asf5_filter2_a52' => { }, + 'asf5_filter2_a53' => { }, + 'asf5_filter2_a54' => { }, + 'asf5_filter2_a55' => { }, + 'asf5_nrmize_factor1' => { }, + 'asf5_nrmize_factor2' => { }, + 'asf5_low_lo_thres' => { }, + 'asf5_low_up_thres' => { }, + 'asf5_low_shrp_deg_f1' => { }, + 'asf5_low_shrp_deg_f2' => { }, + 'asf5_low_smth_prcnt' => { }, + 'asf5_nrm_lo_thres' => { }, + 'asf5_nrm_up_thres' => { }, + 'asf5_nrm_shrp_deg_f1' => { }, + 'asf5_nrm_shrp_deg_f2' => { }, + 'asf5_nrm_smth_prcnt' => { }, + 'asf5_brt_lo_thres' => { }, + 'asf5_brt_up_thres' => { }, + 'asf5_brt_shrp_deg_f1' => { }, + 'asf5_brt_shrp_deg_f2' => { }, + 'asf5_brt_smth_percent' => { }, + 'asf3_enable' => { }, + 'asf3_edge_filter_a11' => { }, + 'asf3_edge_filter_a12' => { }, + 'asf3_edge_filter_a13' => { }, + 'asf3_edge_filter_a21' => { }, + 'asf3_edge_filter_a22' => { }, + 'asf3_edge_filter_a23' => { }, + 'asf3_edge_filter_a31' => { }, + 'asf3_edge_filter_a32' => { }, + 'asf3_edge_filter_a33' => { }, + 'asf3_noise_filter_a11' => { }, + 'asf3_noise_filter_a12' => { }, + 'asf3_noise_filter_a13' => { }, + 'asf3_noise_filter_a21' => { }, + 'asf3_noise_filter_a22' => { }, + 'asf3_noise_filter_a23' => { }, + 'asf3_noise_filter_a31' => { }, + 'asf3_noise_filter_a32' => { }, + 'asf3_noise_filter_a33' => { }, + 'asf3_lower_threshold' => { }, + 'asf3_upper_threshold' => { }, + 'asf3_edge_detect' => { }, + 'aec_enable' => { }, + 'aec_mode' => { }, + 'aec_aggressiveness' => { }, + 'aec_luma_target' => { }, + 'aec_luma_tolerance' => { }, + 'aec_indoor_idx' => { }, + 'aec_odoor_idx' => { }, + 'aec_exposure_index_adj_step' => { }, + 'aec_outdoor_gamma_index' => { }, + 'aec_vfe_luma' => { }, + 'aec_high_luma_region_threshold' => { }, + 'aec_snapshot_sensor_gain' => { }, + 'aec_snapshot_digital_gain' => { }, + 'aec_snapshot_line_count' => { }, + 'aec_snapshot_exposure_time_ms' => { }, + 'aec_outdoor_bright_enable' => { }, + 'aec_outdoor_bright_reduction' => { }, + 'aec_outdoor_bright_threshold_LO' => { }, + 'aec_outdoor_bright_threshold_HI' => { }, + 'aec_outdoor_bright_discarded' => { }, + 'aec_high_luma_region_count' => { }, + 'antibanding_enable' => { }, + 'anti_bading_pixel_clk' => { }, + 'anti_bading_pixel_clk_per_line' => { }, + 'afr_enable' => { }, + 'afr_faster_0_trigger' => { }, + 'afr_slower_0_trigger' => { }, + 'afr_faster_0_exp_mod' => { }, + 'afr_slower_0_exp_mod' => { }, + 'afr_faster_1_trigger' => { }, + 'afr_slower_1_trigger' => { }, + 'afr_faster_1_exp_mod' => { }, + 'afr_slower_1_exp_mod' => { }, + 'afr_faster_2_trigger' => { }, + 'afr_slower_2_trigger' => { }, + 'afr_faster_2_exp_mod' => { }, + 'afr_slower_2_exp_mod' => { }, + 'afr_faster_3_trigger' => { }, + 'afr_slower_3_trigger' => { }, + 'afr_faster_3_exp_mod' => { }, + 'afr_slower_3_exp_mod' => { }, + 'afr_faster_4_trigger' => { }, + 'afr_slower_4_trigger' => { }, + 'afr_faster_4_exp_mod' => { }, + 'afr_slower_4_exp_mod' => { }, + 'afr_possible_frame_cnt' => { }, + 'af_enable' => { }, + 'af_steps_near_far' => { }, + 'af_steps_near_infinity' => { }, + 'af_gross_step' => { }, + 'af_fine_step' => { }, + 'af_fine_srch_points' => { }, + 'af_process' => { }, + 'af_mode' => { }, + 'af_near_end' => { }, + 'af_boundary' => { }, + 'af_far_end' => { }, + 'af_collect_end_stat' => { }, + 'af_test_mode' => { }, + 'af_undershoot_protect' => { }, + 'af_reset_lens_after_snap' => { }, + 'clip_to_af_rato' => { }, + 'af_pos_def_macro' => { }, + 'af_pos_def_norm' => { }, + 'af_vfe_vert_offset' => { }, + 'af_vfe_horz_offset' => { }, + 'af_vfe_vert_height' => { }, + 'af_vfe_horz_width' => { }, + 'af_vfe_metric_max' => { }, + 'af_trace_positions[0]' => { }, + 'af_trace_positions[1]' => { }, + 'af_trace_positions[2]' => { }, + 'af_trace_positions[3]' => { }, + 'af_trace_positions[4]' => { }, + 'af_trace_positions[5]' => { }, + 'af_trace_positions[6]' => { }, + 'af_trace_positions[7]' => { }, + 'af_trace_positions[8]' => { }, + 'af_trace_positions[9]' => { }, + 'af_trace_positions[10]' => { }, + 'af_trace_positions[11]' => { }, + 'af_trace_positions[12]' => { }, + 'af_trace_positions[13]' => { }, + 'af_trace_positions[14]' => { }, + 'af_trace_positions[15]' => { }, + 'af_trace_positions[16]' => { }, + 'af_trace_positions[17]' => { }, + 'af_trace_positions[18]' => { }, + 'af_trace_positions[19]' => { }, + 'af_trace_positions[20]' => { }, + 'af_trace_positions[21]' => { }, + 'af_trace_positions[22]' => { }, + 'af_trace_positions[23]' => { }, + 'af_trace_positions[24]' => { }, + 'af_trace_positions[25]' => { }, + 'af_trace_positions[26]' => { }, + 'af_trace_positions[27]' => { }, + 'af_trace_positions[28]' => { }, + 'af_trace_positions[29]' => { }, + 'af_trace_positions[30]' => { }, + 'af_trace_positions[31]' => { }, + 'af_trace_positions[32]' => { }, + 'af_trace_positions[33]' => { }, + 'af_trace_positions[34]' => { }, + 'af_trace_positions[35]' => { }, + 'af_trace_positions[36]' => { }, + 'af_trace_positions[37]' => { }, + 'af_trace_positions[38]' => { }, + 'af_trace_positions[39]' => { }, + 'af_trace_positions[40]' => { }, + 'af_trace_positions[41]' => { }, + 'af_trace_positions[42]' => { }, + 'af_trace_positions[43]' => { }, + 'af_trace_positions[44]' => { }, + 'af_trace_positions[45]' => { }, + 'af_trace_positions[46]' => { }, + 'af_trace_positions[47]' => { }, + 'af_trace_positions[48]' => { }, + 'af_trace_positions[49]' => { }, + 'af_trace_stats[0]' => { }, + 'af_trace_stats[1]' => { }, + 'af_trace_stats[2]' => { }, + 'af_trace_stats[3]' => { }, + 'af_trace_stats[4]' => { }, + 'af_trace_stats[5]' => { }, + 'af_trace_stats[6]' => { }, + 'af_trace_stats[7]' => { }, + 'af_trace_stats[8]' => { }, + 'af_trace_stats[9]' => { }, + 'af_trace_stats[10]' => { }, + 'af_trace_stats[11]' => { }, + 'af_trace_stats[12]' => { }, + 'af_trace_stats[13]' => { }, + 'af_trace_stats[14]' => { }, + 'af_trace_stats[15]' => { }, + 'af_trace_stats[16]' => { }, + 'af_trace_stats[17]' => { }, + 'af_trace_stats[18]' => { }, + 'af_trace_stats[19]' => { }, + 'af_trace_stats[20]' => { }, + 'af_trace_stats[21]' => { }, + 'af_trace_stats[22]' => { }, + 'af_trace_stats[23]' => { }, + 'af_trace_stats[24]' => { }, + 'af_trace_stats[25]' => { }, + 'af_trace_stats[26]' => { }, + 'af_trace_stats[27]' => { }, + 'af_trace_stats[28]' => { }, + 'af_trace_stats[29]' => { }, + 'af_trace_stats[30]' => { }, + 'af_trace_stats[31]' => { }, + 'af_trace_stats[32]' => { }, + 'af_trace_stats[33]' => { }, + 'af_trace_stats[34]' => { }, + 'af_trace_stats[35]' => { }, + 'af_trace_stats[36]' => { }, + 'af_trace_stats[37]' => { }, + 'af_trace_stats[38]' => { }, + 'af_trace_stats[39]' => { }, + 'af_trace_stats[40]' => { }, + 'af_trace_stats[41]' => { }, + 'af_trace_stats[42]' => { }, + 'af_trace_stats[43]' => { }, + 'af_trace_stats[44]' => { }, + 'af_trace_stats[45]' => { }, + 'af_trace_stats[46]' => { }, + 'af_trace_stats[47]' => { }, + 'af_trace_stats[48]' => { }, + 'af_trace_stats[49]' => { }, + 'af_focus_time' => { }, + 'awb_enable' => { }, + 'awb_algorithm' => { }, + 'awb_aggressiveness' => { }, + 'awb_red_gain_ref1' => { }, + 'awb_blue_gain_ref1' => { }, + 'awb_red_gain_adj_ref1' => { }, + 'awb_blue_gain_adj_ref1' => { }, + 'awb_red_gain_ref2' => { }, + 'awb_blue_gain_ref2' => { }, + 'awb_red_gain_adj_ref2' => { }, + 'awb_blue_gain_adj_ref2' => { }, + 'awb_red_gain_ref3' => { }, + 'awb_blue_gain_ref3' => { }, + 'awb_red_gain_adj_ref3' => { }, + 'awb_blue_gain_adj_ref3' => { }, + 'awb_red_gain_ref4' => { }, + 'awb_blue_gain_ref4' => { }, + 'awb_red_gain_adj_ref4' => { }, + 'awb_blue_gain_adj_ref4' => { }, + 'awb_red_gain_ref5' => { }, + 'awb_blue_gain_ref5' => { }, + 'awb_red_gain_adj_ref5' => { }, + 'awb_blue_gain_adj_ref5' => { }, + 'awb_red_gain_ref6' => { }, + 'awb_blue_gain_ref6' => { }, + 'awb_red_gain_adj_ref6' => { }, + 'awb_blue_gain_adj_ref6' => { }, + 'awb_red_gain_ref7' => { }, + 'awb_blue_gain_ref7' => { }, + 'awb_red_gain_adj_ref7' => { }, + 'awb_blue_gain_adj_ref7' => { }, + 'awb_red_gain_ref8' => { }, + 'awb_blue_gain_ref8' => { }, + 'awb_red_gain_adj_ref8' => { }, + 'awb_blue_gain_adj_ref8' => { }, + 'awb_lo_vfe_max_y' => { }, + 'awb_lo_vfe_min_y' => { }, + 'awb_lo_vfe_m1' => { }, + 'awb_lo_vfe_m2' => { }, + 'awb_lo_vfe_m3' => { }, + 'awb_lo_vfe_m4' => { }, + 'awb_lo_vfe_c1' => { }, + 'awb_lo_vfe_c2' => { }, + 'awb_lo_vfe_c3' => { }, + 'awb_lo_vfe_c4' => { }, + 'awb_norm_vfe_max_y' => { }, + 'awb_norm_vfe_min_y' => { }, + 'awb_norm_vfe_m1' => { }, + 'awb_norm_vfe_m2' => { }, + 'awb_norm_vfe_m3' => { }, + 'awb_norm_vfe_m4' => { }, + 'awb_norm_vfe_c1' => { }, + 'awb_norm_vfe_c2' => { }, + 'awb_norm_vfe_c3' => { }, + 'awb_norm_vfe_c4' => { }, + 'awb_oudor_vfe_max_y' => { }, + 'awb_oudor_vfe_min_y' => { }, + 'awb_oudor_vfe_m1' => { }, + 'awb_oudor_vfe_m2' => { }, + 'awb_oudor_vfe_m3' => { }, + 'awb_oudor_vfe_m4' => { }, + 'awb_oudor_vfe_c1' => { }, + 'awb_oudor_vfe_c2' => { }, + 'awb_oudor_vfe_c3' => { }, + 'awb_oudor_vfe_c4' => { }, + 'awb_cc_bias' => { }, + 'awb_min_r_gain' => { }, + 'awb_min_g_gain' => { }, + 'awb_min_b_gain' => { }, + 'awb_max_r_gain' => { }, + 'awb_max_g_gain' => { }, + 'awb_max_b_gain' => { }, + 'awb_outdoor_sample_influence' => { }, + 'awb_indoor_sample_influence' => { }, + 'awb_low_lig_col_cor_ena' => { }, + 'awb_agw_grid_dist_2_thresh' => { }, + 'awb_ave_rg_ratio' => { }, + 'awb_ave_bg_ratio' => { }, + 'awb_compact_cluster_R2' => { }, + 'outlier_distance' => { }, + 'awb_green_offset_rg' => { }, + 'awb_green_offset_bg' => { }, + 'awb_prev_wb_rgain' => { }, + 'awb_prev_wb_ggain' => { }, + 'awb_prev_wb_bgain' => { }, + 'awb_snapshot_r_gain' => { }, + 'awb_snapshot_b_gain' => { }, + 'rolloff_enable' => { }, + 'r2_tl84_cx' => { }, + 'r2_tl84_cy' => { }, + 'r2_tl84_width' => { }, + 'r2_tl84_height' => { }, + 'r2_tl84_intervals' => { }, + 'r2_tl84_tbl[0]' => { }, + 'r2_tl84_tbl[1]' => { }, + 'r2_tl84_tbl[2]' => { }, + 'r2_tl84_tbl[3]' => { }, + 'r2_tl84_tbl[4]' => { }, + 'r2_tl84_tbl[5]' => { }, + 'r2_tl84_tbl[6]' => { }, + 'r2_tl84_tbl[7]' => { }, + 'r2_tl84_tbl[8]' => { }, + 'r2_tl84_tbl[9]' => { }, + 'r2_tl84_tbl[10]' => { }, + 'r2_tl84_tbl[11]' => { }, + 'r2_tl84_tbl[12]' => { }, + 'r2_tl84_tbl[13]' => { }, + 'r2_tl84_tbl[14]' => { }, + 'r2_tl84_tbl[15]' => { }, + 'r2_tl84_tbl[16]' => { }, + 'r2_tl84_tbl[17]' => { }, + 'r2_tl84_tbl[18]' => { }, + 'r2_tl84_tbl[19]' => { }, + 'r2_tl84_tbl[20]' => { }, + 'r2_tl84_tbl[21]' => { }, + 'r2_tl84_tbl[22]' => { }, + 'r2_tl84_tbl[23]' => { }, + 'r2_tl84_tbl[24]' => { }, + 'r2_tl84_tbl[25]' => { }, + 'r2_tl84_tbl[26]' => { }, + 'r2_tl84_tbl[27]' => { }, + 'r2_tl84_tbl[28]' => { }, + 'r2_tl84_tbl[29]' => { }, + 'r2_tl84_tbl[30]' => { }, + 'r2_tl84_tbl[31]' => { }, + 'r2_tl84_red_ctbl[0]' => { }, + 'r2_tl84_red_ctbl[1]' => { }, + 'r2_tl84_red_ctbl[2]' => { }, + 'r2_tl84_red_ctbl[3]' => { }, + 'r2_tl84_red_ctbl[4]' => { }, + 'r2_tl84_red_ctbl[5]' => { }, + 'r2_tl84_red_ctbl[6]' => { }, + 'r2_tl84_red_ctbl[7]' => { }, + 'r2_tl84_red_ctbl[8]' => { }, + 'r2_tl84_red_ctbl[9]' => { }, + 'r2_tl84_red_ctbl[10]' => { }, + 'r2_tl84_red_ctbl[11]' => { }, + 'r2_tl84_red_ctbl[12]' => { }, + 'r2_tl84_red_ctbl[13]' => { }, + 'r2_tl84_red_ctbl[14]' => { }, + 'r2_tl84_red_ctbl[15]' => { }, + 'r2_tl84_red_ctbl[16]' => { }, + 'r2_tl84_red_ctbl[17]' => { }, + 'r2_tl84_red_ctbl[18]' => { }, + 'r2_tl84_red_ctbl[19]' => { }, + 'r2_tl84_red_ctbl[20]' => { }, + 'r2_tl84_red_ctbl[21]' => { }, + 'r2_tl84_red_ctbl[22]' => { }, + 'r2_tl84_red_ctbl[23]' => { }, + 'r2_tl84_red_ctbl[24]' => { }, + 'r2_tl84_red_ctbl[25]' => { }, + 'r2_tl84_red_ctbl[26]' => { }, + 'r2_tl84_red_ctbl[27]' => { }, + 'r2_tl84_red_ctbl[28]' => { }, + 'r2_tl84_red_ctbl[29]' => { }, + 'r2_tl84_red_ctbl[30]' => { }, + 'r2_tl84_red_ctbl[31]' => { }, + 'r2_tl84_green_ctbl[0]' => { }, + 'r2_tl84_green_ctbl[1]' => { }, + 'r2_tl84_green_ctbl[2]' => { }, + 'r2_tl84_green_ctbl[3]' => { }, + 'r2_tl84_green_ctbl[4]' => { }, + 'r2_tl84_green_ctbl[5]' => { }, + 'r2_tl84_green_ctbl[6]' => { }, + 'r2_tl84_green_ctbl[7]' => { }, + 'r2_tl84_green_ctbl[8]' => { }, + 'r2_tl84_green_ctbl[9]' => { }, + 'r2_tl84_green_ctbl[10]' => { }, + 'r2_tl84_green_ctbl[11]' => { }, + 'r2_tl84_green_ctbl[12]' => { }, + 'r2_tl84_green_ctbl[13]' => { }, + 'r2_tl84_green_ctbl[14]' => { }, + 'r2_tl84_green_ctbl[15]' => { }, + 'r2_tl84_green_ctbl[16]' => { }, + 'r2_tl84_green_ctbl[17]' => { }, + 'r2_tl84_green_ctbl[18]' => { }, + 'r2_tl84_green_ctbl[19]' => { }, + 'r2_tl84_green_ctbl[20]' => { }, + 'r2_tl84_green_ctbl[21]' => { }, + 'r2_tl84_green_ctbl[22]' => { }, + 'r2_tl84_green_ctbl[23]' => { }, + 'r2_tl84_green_ctbl[24]' => { }, + 'r2_tl84_green_ctbl[25]' => { }, + 'r2_tl84_green_ctbl[26]' => { }, + 'r2_tl84_green_ctbl[27]' => { }, + 'r2_tl84_green_ctbl[28]' => { }, + 'r2_tl84_green_ctbl[29]' => { }, + 'r2_tl84_green_ctbl[30]' => { }, + 'r2_tl84_green_ctbl[31]' => { }, + 'r2_tl84_blue_ctbl[0]' => { }, + 'r2_tl84_blue_ctbl[1]' => { }, + 'r2_tl84_blue_ctbl[2]' => { }, + 'r2_tl84_blue_ctbl[3]' => { }, + 'r2_tl84_blue_ctbl[4]' => { }, + 'r2_tl84_blue_ctbl[5]' => { }, + 'r2_tl84_blue_ctbl[6]' => { }, + 'r2_tl84_blue_ctbl[7]' => { }, + 'r2_tl84_blue_ctbl[8]' => { }, + 'r2_tl84_blue_ctbl[9]' => { }, + 'r2_tl84_blue_ctbl[10]' => { }, + 'r2_tl84_blue_ctbl[11]' => { }, + 'r2_tl84_blue_ctbl[12]' => { }, + 'r2_tl84_blue_ctbl[13]' => { }, + 'r2_tl84_blue_ctbl[14]' => { }, + 'r2_tl84_blue_ctbl[15]' => { }, + 'r2_tl84_blue_ctbl[16]' => { }, + 'r2_tl84_blue_ctbl[17]' => { }, + 'r2_tl84_blue_ctbl[18]' => { }, + 'r2_tl84_blue_ctbl[19]' => { }, + 'r2_tl84_blue_ctbl[20]' => { }, + 'r2_tl84_blue_ctbl[21]' => { }, + 'r2_tl84_blue_ctbl[22]' => { }, + 'r2_tl84_blue_ctbl[23]' => { }, + 'r2_tl84_blue_ctbl[24]' => { }, + 'r2_tl84_blue_ctbl[25]' => { }, + 'r2_tl84_blue_ctbl[26]' => { }, + 'r2_tl84_blue_ctbl[27]' => { }, + 'r2_tl84_blue_ctbl[28]' => { }, + 'r2_tl84_blue_ctbl[29]' => { }, + 'r2_tl84_blue_ctbl[30]' => { }, + 'r2_tl84_blue_ctbl[31]' => { }, + 'r2_tl84_red_stbl[0]' => { }, + 'r2_tl84_red_stbl[1]' => { }, + 'r2_tl84_red_stbl[2]' => { }, + 'r2_tl84_red_stbl[3]' => { }, + 'r2_tl84_red_stbl[4]' => { }, + 'r2_tl84_red_stbl[5]' => { }, + 'r2_tl84_red_stbl[6]' => { }, + 'r2_tl84_red_stbl[7]' => { }, + 'r2_tl84_red_stbl[8]' => { }, + 'r2_tl84_red_stbl[9]' => { }, + 'r2_tl84_red_stbl[10]' => { }, + 'r2_tl84_red_stbl[11]' => { }, + 'r2_tl84_red_stbl[12]' => { }, + 'r2_tl84_red_stbl[13]' => { }, + 'r2_tl84_red_stbl[14]' => { }, + 'r2_tl84_red_stbl[15]' => { }, + 'r2_tl84_red_stbl[16]' => { }, + 'r2_tl84_red_stbl[17]' => { }, + 'r2_tl84_red_stbl[18]' => { }, + 'r2_tl84_red_stbl[19]' => { }, + 'r2_tl84_red_stbl[20]' => { }, + 'r2_tl84_red_stbl[21]' => { }, + 'r2_tl84_red_stbl[22]' => { }, + 'r2_tl84_red_stbl[23]' => { }, + 'r2_tl84_red_stbl[24]' => { }, + 'r2_tl84_red_stbl[25]' => { }, + 'r2_tl84_red_stbl[26]' => { }, + 'r2_tl84_red_stbl[27]' => { }, + 'r2_tl84_red_stbl[28]' => { }, + 'r2_tl84_red_stbl[29]' => { }, + 'r2_tl84_red_stbl[30]' => { }, + 'r2_tl84_red_stbl[31]' => { }, + 'r2_tl84_blue_stbl[0]' => { }, + 'r2_tl84_blue_stbl[1]' => { }, + 'r2_tl84_blue_stbl[2]' => { }, + 'r2_tl84_blue_stbl[3]' => { }, + 'r2_tl84_blue_stbl[4]' => { }, + 'r2_tl84_blue_stbl[5]' => { }, + 'r2_tl84_blue_stbl[6]' => { }, + 'r2_tl84_blue_stbl[7]' => { }, + 'r2_tl84_blue_stbl[8]' => { }, + 'r2_tl84_blue_stbl[9]' => { }, + 'r2_tl84_blue_stbl[10]' => { }, + 'r2_tl84_blue_stbl[11]' => { }, + 'r2_tl84_blue_stbl[12]' => { }, + 'r2_tl84_blue_stbl[13]' => { }, + 'r2_tl84_blue_stbl[14]' => { }, + 'r2_tl84_blue_stbl[15]' => { }, + 'r2_tl84_blue_stbl[16]' => { }, + 'r2_tl84_blue_stbl[17]' => { }, + 'r2_tl84_blue_stbl[18]' => { }, + 'r2_tl84_blue_stbl[19]' => { }, + 'r2_tl84_blue_stbl[20]' => { }, + 'r2_tl84_blue_stbl[21]' => { }, + 'r2_tl84_blue_stbl[22]' => { }, + 'r2_tl84_blue_stbl[23]' => { }, + 'r2_tl84_blue_stbl[24]' => { }, + 'r2_tl84_blue_stbl[25]' => { }, + 'r2_tl84_blue_stbl[26]' => { }, + 'r2_tl84_blue_stbl[27]' => { }, + 'r2_tl84_blue_stbl[28]' => { }, + 'r2_tl84_blue_stbl[29]' => { }, + 'r2_tl84_blue_stbl[30]' => { }, + 'r2_tl84_blue_stbl[31]' => { }, + 'r2_tl84_green_stbl[0]' => { }, + 'r2_tl84_green_stbl[1]' => { }, + 'r2_tl84_green_stbl[2]' => { }, + 'r2_tl84_green_stbl[3]' => { }, + 'r2_tl84_green_stbl[4]' => { }, + 'r2_tl84_green_stbl[5]' => { }, + 'r2_tl84_green_stbl[6]' => { }, + 'r2_tl84_green_stbl[7]' => { }, + 'r2_tl84_green_stbl[8]' => { }, + 'r2_tl84_green_stbl[9]' => { }, + 'r2_tl84_green_stbl[10]' => { }, + 'r2_tl84_green_stbl[11]' => { }, + 'r2_tl84_green_stbl[12]' => { }, + 'r2_tl84_green_stbl[13]' => { }, + 'r2_tl84_green_stbl[14]' => { }, + 'r2_tl84_green_stbl[15]' => { }, + 'r2_tl84_green_stbl[16]' => { }, + 'r2_tl84_green_stbl[17]' => { }, + 'r2_tl84_green_stbl[18]' => { }, + 'r2_tl84_green_stbl[19]' => { }, + 'r2_tl84_green_stbl[20]' => { }, + 'r2_tl84_green_stbl[21]' => { }, + 'r2_tl84_green_stbl[22]' => { }, + 'r2_tl84_green_stbl[23]' => { }, + 'r2_tl84_green_stbl[24]' => { }, + 'r2_tl84_green_stbl[25]' => { }, + 'r2_tl84_green_stbl[26]' => { }, + 'r2_tl84_green_stbl[27]' => { }, + 'r2_tl84_green_stbl[28]' => { }, + 'r2_tl84_green_stbl[29]' => { }, + 'r2_tl84_green_stbl[30]' => { }, + 'r2_tl84_green_stbl[31]' => { }, + 'r2_d65_cx' => { }, + 'r2_d65_cy' => { }, + 'r2_d65_width' => { }, + 'r2_d65_height' => { }, + 'r2_d65_intervals' => { }, + 'r2_d65_tbl[0]' => { }, + 'r2_d65_tbl[1]' => { }, + 'r2_d65_tbl[2]' => { }, + 'r2_d65_tbl[3]' => { }, + 'r2_d65_tbl[4]' => { }, + 'r2_d65_tbl[5]' => { }, + 'r2_d65_tbl[6]' => { }, + 'r2_d65_tbl[7]' => { }, + 'r2_d65_tbl[8]' => { }, + 'r2_d65_tbl[9]' => { }, + 'r2_d65_tbl[10]' => { }, + 'r2_d65_tbl[11]' => { }, + 'r2_d65_tbl[12]' => { }, + 'r2_d65_tbl[13]' => { }, + 'r2_d65_tbl[14]' => { }, + 'r2_d65_tbl[15]' => { }, + 'r2_d65_tbl[16]' => { }, + 'r2_d65_tbl[17]' => { }, + 'r2_d65_tbl[18]' => { }, + 'r2_d65_tbl[19]' => { }, + 'r2_d65_tbl[20]' => { }, + 'r2_d65_tbl[21]' => { }, + 'r2_d65_tbl[22]' => { }, + 'r2_d65_tbl[23]' => { }, + 'r2_d65_tbl[24]' => { }, + 'r2_d65_tbl[25]' => { }, + 'r2_d65_tbl[26]' => { }, + 'r2_d65_tbl[27]' => { }, + 'r2_d65_tbl[28]' => { }, + 'r2_d65_tbl[29]' => { }, + 'r2_d65_tbl[30]' => { }, + 'r2_d65_tbl[31]' => { }, + 'r2_d65_red_ctbl[0]' => { }, + 'r2_d65_red_ctbl[1]' => { }, + 'r2_d65_red_ctbl[2]' => { }, + 'r2_d65_red_ctbl[3]' => { }, + 'r2_d65_red_ctbl[4]' => { }, + 'r2_d65_red_ctbl[5]' => { }, + 'r2_d65_red_ctbl[6]' => { }, + 'r2_d65_red_ctbl[7]' => { }, + 'r2_d65_red_ctbl[8]' => { }, + 'r2_d65_red_ctbl[9]' => { }, + 'r2_d65_red_ctbl[10]' => { }, + 'r2_d65_red_ctbl[11]' => { }, + 'r2_d65_red_ctbl[12]' => { }, + 'r2_d65_red_ctbl[13]' => { }, + 'r2_d65_red_ctbl[14]' => { }, + 'r2_d65_red_ctbl[15]' => { }, + 'r2_d65_red_ctbl[16]' => { }, + 'r2_d65_red_ctbl[17]' => { }, + 'r2_d65_red_ctbl[18]' => { }, + 'r2_d65_red_ctbl[19]' => { }, + 'r2_d65_red_ctbl[20]' => { }, + 'r2_d65_red_ctbl[21]' => { }, + 'r2_d65_red_ctbl[22]' => { }, + 'r2_d65_red_ctbl[23]' => { }, + 'r2_d65_red_ctbl[24]' => { }, + 'r2_d65_red_ctbl[25]' => { }, + 'r2_d65_red_ctbl[26]' => { }, + 'r2_d65_red_ctbl[27]' => { }, + 'r2_d65_red_ctbl[28]' => { }, + 'r2_d65_red_ctbl[29]' => { }, + 'r2_d65_red_ctbl[30]' => { }, + 'r2_d65_red_ctbl[31]' => { }, + 'r2_d65_green_ctbl[0]' => { }, + 'r2_d65_green_ctbl[1]' => { }, + 'r2_d65_green_ctbl[2]' => { }, + 'r2_d65_green_ctbl[3]' => { }, + 'r2_d65_green_ctbl[4]' => { }, + 'r2_d65_green_ctbl[5]' => { }, + 'r2_d65_green_ctbl[6]' => { }, + 'r2_d65_green_ctbl[7]' => { }, + 'r2_d65_green_ctbl[8]' => { }, + 'r2_d65_green_ctbl[9]' => { }, + 'r2_d65_green_ctbl[10]' => { }, + 'r2_d65_green_ctbl[11]' => { }, + 'r2_d65_green_ctbl[12]' => { }, + 'r2_d65_green_ctbl[13]' => { }, + 'r2_d65_green_ctbl[14]' => { }, + 'r2_d65_green_ctbl[15]' => { }, + 'r2_d65_green_ctbl[16]' => { }, + 'r2_d65_green_ctbl[17]' => { }, + 'r2_d65_green_ctbl[18]' => { }, + 'r2_d65_green_ctbl[19]' => { }, + 'r2_d65_green_ctbl[20]' => { }, + 'r2_d65_green_ctbl[21]' => { }, + 'r2_d65_green_ctbl[22]' => { }, + 'r2_d65_green_ctbl[23]' => { }, + 'r2_d65_green_ctbl[24]' => { }, + 'r2_d65_green_ctbl[25]' => { }, + 'r2_d65_green_ctbl[26]' => { }, + 'r2_d65_green_ctbl[27]' => { }, + 'r2_d65_green_ctbl[28]' => { }, + 'r2_d65_green_ctbl[29]' => { }, + 'r2_d65_green_ctbl[30]' => { }, + 'r2_d65_green_ctbl[31]' => { }, + 'r2_d65_blue_ctbl[0]' => { }, + 'r2_d65_blue_ctbl[1]' => { }, + 'r2_d65_blue_ctbl[2]' => { }, + 'r2_d65_blue_ctbl[3]' => { }, + 'r2_d65_blue_ctbl[4]' => { }, + 'r2_d65_blue_ctbl[5]' => { }, + 'r2_d65_blue_ctbl[6]' => { }, + 'r2_d65_blue_ctbl[7]' => { }, + 'r2_d65_blue_ctbl[8]' => { }, + 'r2_d65_blue_ctbl[9]' => { }, + 'r2_d65_blue_ctbl[10]' => { }, + 'r2_d65_blue_ctbl[11]' => { }, + 'r2_d65_blue_ctbl[12]' => { }, + 'r2_d65_blue_ctbl[13]' => { }, + 'r2_d65_blue_ctbl[14]' => { }, + 'r2_d65_blue_ctbl[15]' => { }, + 'r2_d65_blue_ctbl[16]' => { }, + 'r2_d65_blue_ctbl[17]' => { }, + 'r2_d65_blue_ctbl[18]' => { }, + 'r2_d65_blue_ctbl[19]' => { }, + 'r2_d65_blue_ctbl[20]' => { }, + 'r2_d65_blue_ctbl[21]' => { }, + 'r2_d65_blue_ctbl[22]' => { }, + 'r2_d65_blue_ctbl[23]' => { }, + 'r2_d65_blue_ctbl[24]' => { }, + 'r2_d65_blue_ctbl[25]' => { }, + 'r2_d65_blue_ctbl[26]' => { }, + 'r2_d65_blue_ctbl[27]' => { }, + 'r2_d65_blue_ctbl[28]' => { }, + 'r2_d65_blue_ctbl[29]' => { }, + 'r2_d65_blue_ctbl[30]' => { }, + 'r2_d65_blue_ctbl[31]' => { }, + 'r2_d65_red_stbl[0]' => { }, + 'r2_d65_red_stbl[1]' => { }, + 'r2_d65_red_stbl[2]' => { }, + 'r2_d65_red_stbl[3]' => { }, + 'r2_d65_red_stbl[4]' => { }, + 'r2_d65_red_stbl[5]' => { }, + 'r2_d65_red_stbl[6]' => { }, + 'r2_d65_red_stbl[7]' => { }, + 'r2_d65_red_stbl[8]' => { }, + 'r2_d65_red_stbl[9]' => { }, + 'r2_d65_red_stbl[10]' => { }, + 'r2_d65_red_stbl[11]' => { }, + 'r2_d65_red_stbl[12]' => { }, + 'r2_d65_red_stbl[13]' => { }, + 'r2_d65_red_stbl[14]' => { }, + 'r2_d65_red_stbl[15]' => { }, + 'r2_d65_red_stbl[16]' => { }, + 'r2_d65_red_stbl[17]' => { }, + 'r2_d65_red_stbl[18]' => { }, + 'r2_d65_red_stbl[19]' => { }, + 'r2_d65_red_stbl[20]' => { }, + 'r2_d65_red_stbl[21]' => { }, + 'r2_d65_red_stbl[22]' => { }, + 'r2_d65_red_stbl[23]' => { }, + 'r2_d65_red_stbl[24]' => { }, + 'r2_d65_red_stbl[25]' => { }, + 'r2_d65_red_stbl[26]' => { }, + 'r2_d65_red_stbl[27]' => { }, + 'r2_d65_red_stbl[28]' => { }, + 'r2_d65_red_stbl[29]' => { }, + 'r2_d65_red_stbl[30]' => { }, + 'r2_d65_red_stbl[31]' => { }, + 'r2_d65_blue_stbl[0]' => { }, + 'r2_d65_blue_stbl[1]' => { }, + 'r2_d65_blue_stbl[2]' => { }, + 'r2_d65_blue_stbl[3]' => { }, + 'r2_d65_blue_stbl[4]' => { }, + 'r2_d65_blue_stbl[5]' => { }, + 'r2_d65_blue_stbl[6]' => { }, + 'r2_d65_blue_stbl[7]' => { }, + 'r2_d65_blue_stbl[8]' => { }, + 'r2_d65_blue_stbl[9]' => { }, + 'r2_d65_blue_stbl[10]' => { }, + 'r2_d65_blue_stbl[11]' => { }, + 'r2_d65_blue_stbl[12]' => { }, + 'r2_d65_blue_stbl[13]' => { }, + 'r2_d65_blue_stbl[14]' => { }, + 'r2_d65_blue_stbl[15]' => { }, + 'r2_d65_blue_stbl[16]' => { }, + 'r2_d65_blue_stbl[17]' => { }, + 'r2_d65_blue_stbl[18]' => { }, + 'r2_d65_blue_stbl[19]' => { }, + 'r2_d65_blue_stbl[20]' => { }, + 'r2_d65_blue_stbl[21]' => { }, + 'r2_d65_blue_stbl[22]' => { }, + 'r2_d65_blue_stbl[23]' => { }, + 'r2_d65_blue_stbl[24]' => { }, + 'r2_d65_blue_stbl[25]' => { }, + 'r2_d65_blue_stbl[26]' => { }, + 'r2_d65_blue_stbl[27]' => { }, + 'r2_d65_blue_stbl[28]' => { }, + 'r2_d65_blue_stbl[29]' => { }, + 'r2_d65_blue_stbl[30]' => { }, + 'r2_d65_blue_stbl[31]' => { }, + 'r2_d65_green_stbl[0]' => { }, + 'r2_d65_green_stbl[1]' => { }, + 'r2_d65_green_stbl[2]' => { }, + 'r2_d65_green_stbl[3]' => { }, + 'r2_d65_green_stbl[4]' => { }, + 'r2_d65_green_stbl[5]' => { }, + 'r2_d65_green_stbl[6]' => { }, + 'r2_d65_green_stbl[7]' => { }, + 'r2_d65_green_stbl[8]' => { }, + 'r2_d65_green_stbl[9]' => { }, + 'r2_d65_green_stbl[10]' => { }, + 'r2_d65_green_stbl[11]' => { }, + 'r2_d65_green_stbl[12]' => { }, + 'r2_d65_green_stbl[13]' => { }, + 'r2_d65_green_stbl[14]' => { }, + 'r2_d65_green_stbl[15]' => { }, + 'r2_d65_green_stbl[16]' => { }, + 'r2_d65_green_stbl[17]' => { }, + 'r2_d65_green_stbl[18]' => { }, + 'r2_d65_green_stbl[19]' => { }, + 'r2_d65_green_stbl[20]' => { }, + 'r2_d65_green_stbl[21]' => { }, + 'r2_d65_green_stbl[22]' => { }, + 'r2_d65_green_stbl[23]' => { }, + 'r2_d65_green_stbl[24]' => { }, + 'r2_d65_green_stbl[25]' => { }, + 'r2_d65_green_stbl[26]' => { }, + 'r2_d65_green_stbl[27]' => { }, + 'r2_d65_green_stbl[28]' => { }, + 'r2_d65_green_stbl[29]' => { }, + 'r2_d65_green_stbl[30]' => { }, + 'r2_d65_green_stbl[31]' => { }, + 'r2_a_cx' => { }, + 'r2_a_cy' => { }, + 'r2_a_width' => { }, + 'r2_a_height' => { }, + 'r2_a_intervals' => { }, + 'r2_a_tbl[0]' => { }, + 'r2_a_tbl[1]' => { }, + 'r2_a_tbl[2]' => { }, + 'r2_a_tbl[3]' => { }, + 'r2_a_tbl[4]' => { }, + 'r2_a_tbl[5]' => { }, + 'r2_a_tbl[6]' => { }, + 'r2_a_tbl[7]' => { }, + 'r2_a_tbl[8]' => { }, + 'r2_a_tbl[9]' => { }, + 'r2_a_tbl[10]' => { }, + 'r2_a_tbl[11]' => { }, + 'r2_a_tbl[12]' => { }, + 'r2_a_tbl[13]' => { }, + 'r2_a_tbl[14]' => { }, + 'r2_a_tbl[15]' => { }, + 'r2_a_tbl[16]' => { }, + 'r2_a_tbl[17]' => { }, + 'r2_a_tbl[18]' => { }, + 'r2_a_tbl[19]' => { }, + 'r2_a_tbl[20]' => { }, + 'r2_a_tbl[21]' => { }, + 'r2_a_tbl[22]' => { }, + 'r2_a_tbl[23]' => { }, + 'r2_a_tbl[24]' => { }, + 'r2_a_tbl[25]' => { }, + 'r2_a_tbl[26]' => { }, + 'r2_a_tbl[27]' => { }, + 'r2_a_tbl[28]' => { }, + 'r2_a_tbl[29]' => { }, + 'r2_a_tbl[30]' => { }, + 'r2_a_tbl[31]' => { }, + 'r2_a_red_ctbl[0]' => { }, + 'r2_a_red_ctbl[1]' => { }, + 'r2_a_red_ctbl[2]' => { }, + 'r2_a_red_ctbl[3]' => { }, + 'r2_a_red_ctbl[4]' => { }, + 'r2_a_red_ctbl[5]' => { }, + 'r2_a_red_ctbl[6]' => { }, + 'r2_a_red_ctbl[7]' => { }, + 'r2_a_red_ctbl[8]' => { }, + 'r2_a_red_ctbl[9]' => { }, + 'r2_a_red_ctbl[10]' => { }, + 'r2_a_red_ctbl[11]' => { }, + 'r2_a_red_ctbl[12]' => { }, + 'r2_a_red_ctbl[13]' => { }, + 'r2_a_red_ctbl[14]' => { }, + 'r2_a_red_ctbl[15]' => { }, + 'r2_a_red_ctbl[16]' => { }, + 'r2_a_red_ctbl[17]' => { }, + 'r2_a_red_ctbl[18]' => { }, + 'r2_a_red_ctbl[19]' => { }, + 'r2_a_red_ctbl[20]' => { }, + 'r2_a_red_ctbl[21]' => { }, + 'r2_a_red_ctbl[22]' => { }, + 'r2_a_red_ctbl[23]' => { }, + 'r2_a_red_ctbl[24]' => { }, + 'r2_a_red_ctbl[25]' => { }, + 'r2_a_red_ctbl[26]' => { }, + 'r2_a_red_ctbl[27]' => { }, + 'r2_a_red_ctbl[28]' => { }, + 'r2_a_red_ctbl[29]' => { }, + 'r2_a_red_ctbl[30]' => { }, + 'r2_a_red_ctbl[31]' => { }, + 'r2_a_green_ctbl[0]' => { }, + 'r2_a_green_ctbl[1]' => { }, + 'r2_a_green_ctbl[2]' => { }, + 'r2_a_green_ctbl[3]' => { }, + 'r2_a_green_ctbl[4]' => { }, + 'r2_a_green_ctbl[5]' => { }, + 'r2_a_green_ctbl[6]' => { }, + 'r2_a_green_ctbl[7]' => { }, + 'r2_a_green_ctbl[8]' => { }, + 'r2_a_green_ctbl[9]' => { }, + 'r2_a_green_ctbl[10]' => { }, + 'r2_a_green_ctbl[11]' => { }, + 'r2_a_green_ctbl[12]' => { }, + 'r2_a_green_ctbl[13]' => { }, + 'r2_a_green_ctbl[14]' => { }, + 'r2_a_green_ctbl[15]' => { }, + 'r2_a_green_ctbl[16]' => { }, + 'r2_a_green_ctbl[17]' => { }, + 'r2_a_green_ctbl[18]' => { }, + 'r2_a_green_ctbl[19]' => { }, + 'r2_a_green_ctbl[20]' => { }, + 'r2_a_green_ctbl[21]' => { }, + 'r2_a_green_ctbl[22]' => { }, + 'r2_a_green_ctbl[23]' => { }, + 'r2_a_green_ctbl[24]' => { }, + 'r2_a_green_ctbl[25]' => { }, + 'r2_a_green_ctbl[26]' => { }, + 'r2_a_green_ctbl[27]' => { }, + 'r2_a_green_ctbl[28]' => { }, + 'r2_a_green_ctbl[29]' => { }, + 'r2_a_green_ctbl[30]' => { }, + 'r2_a_green_ctbl[31]' => { }, + 'r2_a_blue_ctbl[0]' => { }, + 'r2_a_blue_ctbl[1]' => { }, + 'r2_a_blue_ctbl[2]' => { }, + 'r2_a_blue_ctbl[3]' => { }, + 'r2_a_blue_ctbl[4]' => { }, + 'r2_a_blue_ctbl[5]' => { }, + 'r2_a_blue_ctbl[6]' => { }, + 'r2_a_blue_ctbl[7]' => { }, + 'r2_a_blue_ctbl[8]' => { }, + 'r2_a_blue_ctbl[9]' => { }, + 'r2_a_blue_ctbl[10]' => { }, + 'r2_a_blue_ctbl[11]' => { }, + 'r2_a_blue_ctbl[12]' => { }, + 'r2_a_blue_ctbl[13]' => { }, + 'r2_a_blue_ctbl[14]' => { }, + 'r2_a_blue_ctbl[15]' => { }, + 'r2_a_blue_ctbl[16]' => { }, + 'r2_a_blue_ctbl[17]' => { }, + 'r2_a_blue_ctbl[18]' => { }, + 'r2_a_blue_ctbl[19]' => { }, + 'r2_a_blue_ctbl[20]' => { }, + 'r2_a_blue_ctbl[21]' => { }, + 'r2_a_blue_ctbl[22]' => { }, + 'r2_a_blue_ctbl[23]' => { }, + 'r2_a_blue_ctbl[24]' => { }, + 'r2_a_blue_ctbl[25]' => { }, + 'r2_a_blue_ctbl[26]' => { }, + 'r2_a_blue_ctbl[27]' => { }, + 'r2_a_blue_ctbl[28]' => { }, + 'r2_a_blue_ctbl[29]' => { }, + 'r2_a_blue_ctbl[30]' => { }, + 'r2_a_blue_ctbl[31]' => { }, + 'r2_a_red_stbl[0]' => { }, + 'r2_a_red_stbl[1]' => { }, + 'r2_a_red_stbl[2]' => { }, + 'r2_a_red_stbl[3]' => { }, + 'r2_a_red_stbl[4]' => { }, + 'r2_a_red_stbl[5]' => { }, + 'r2_a_red_stbl[6]' => { }, + 'r2_a_red_stbl[7]' => { }, + 'r2_a_red_stbl[8]' => { }, + 'r2_a_red_stbl[9]' => { }, + 'r2_a_red_stbl[10]' => { }, + 'r2_a_red_stbl[11]' => { }, + 'r2_a_red_stbl[12]' => { }, + 'r2_a_red_stbl[13]' => { }, + 'r2_a_red_stbl[14]' => { }, + 'r2_a_red_stbl[15]' => { }, + 'r2_a_red_stbl[16]' => { }, + 'r2_a_red_stbl[17]' => { }, + 'r2_a_red_stbl[18]' => { }, + 'r2_a_red_stbl[19]' => { }, + 'r2_a_red_stbl[20]' => { }, + 'r2_a_red_stbl[21]' => { }, + 'r2_a_red_stbl[22]' => { }, + 'r2_a_red_stbl[23]' => { }, + 'r2_a_red_stbl[24]' => { }, + 'r2_a_red_stbl[25]' => { }, + 'r2_a_red_stbl[26]' => { }, + 'r2_a_red_stbl[27]' => { }, + 'r2_a_red_stbl[28]' => { }, + 'r2_a_red_stbl[29]' => { }, + 'r2_a_red_stbl[30]' => { }, + 'r2_a_red_stbl[31]' => { }, + 'r2_a_blue_stbl[0]' => { }, + 'r2_a_blue_stbl[1]' => { }, + 'r2_a_blue_stbl[2]' => { }, + 'r2_a_blue_stbl[3]' => { }, + 'r2_a_blue_stbl[4]' => { }, + 'r2_a_blue_stbl[5]' => { }, + 'r2_a_blue_stbl[6]' => { }, + 'r2_a_blue_stbl[7]' => { }, + 'r2_a_blue_stbl[8]' => { }, + 'r2_a_blue_stbl[9]' => { }, + 'r2_a_blue_stbl[10]' => { }, + 'r2_a_blue_stbl[11]' => { }, + 'r2_a_blue_stbl[12]' => { }, + 'r2_a_blue_stbl[13]' => { }, + 'r2_a_blue_stbl[14]' => { }, + 'r2_a_blue_stbl[15]' => { }, + 'r2_a_blue_stbl[16]' => { }, + 'r2_a_blue_stbl[17]' => { }, + 'r2_a_blue_stbl[18]' => { }, + 'r2_a_blue_stbl[19]' => { }, + 'r2_a_blue_stbl[20]' => { }, + 'r2_a_blue_stbl[21]' => { }, + 'r2_a_blue_stbl[22]' => { }, + 'r2_a_blue_stbl[23]' => { }, + 'r2_a_blue_stbl[24]' => { }, + 'r2_a_blue_stbl[25]' => { }, + 'r2_a_blue_stbl[26]' => { }, + 'r2_a_blue_stbl[27]' => { }, + 'r2_a_blue_stbl[28]' => { }, + 'r2_a_blue_stbl[29]' => { }, + 'r2_a_blue_stbl[30]' => { }, + 'r2_a_blue_stbl[31]' => { }, + 'r2_a_green_stbl[0]' => { }, + 'r2_a_green_stbl[1]' => { }, + 'r2_a_green_stbl[2]' => { }, + 'r2_a_green_stbl[3]' => { }, + 'r2_a_green_stbl[4]' => { }, + 'r2_a_green_stbl[5]' => { }, + 'r2_a_green_stbl[6]' => { }, + 'r2_a_green_stbl[7]' => { }, + 'r2_a_green_stbl[8]' => { }, + 'r2_a_green_stbl[9]' => { }, + 'r2_a_green_stbl[10]' => { }, + 'r2_a_green_stbl[11]' => { }, + 'r2_a_green_stbl[12]' => { }, + 'r2_a_green_stbl[13]' => { }, + 'r2_a_green_stbl[14]' => { }, + 'r2_a_green_stbl[15]' => { }, + 'r2_a_green_stbl[16]' => { }, + 'r2_a_green_stbl[17]' => { }, + 'r2_a_green_stbl[18]' => { }, + 'r2_a_green_stbl[19]' => { }, + 'r2_a_green_stbl[20]' => { }, + 'r2_a_green_stbl[21]' => { }, + 'r2_a_green_stbl[22]' => { }, + 'r2_a_green_stbl[23]' => { }, + 'r2_a_green_stbl[24]' => { }, + 'r2_a_green_stbl[25]' => { }, + 'r2_a_green_stbl[26]' => { }, + 'r2_a_green_stbl[27]' => { }, + 'r2_a_green_stbl[28]' => { }, + 'r2_a_green_stbl[29]' => { }, + 'r2_a_green_stbl[30]' => { }, + 'r2_a_green_stbl[31]' => { }, + 'def_cor_c0' => { }, + 'def_cor_c1' => { }, + 'def_cor_c2' => { }, + 'def_cor_c3' => { }, + 'def_cor_c4' => { }, + 'def_cor_c5' => { }, + 'def_cor_c6' => { }, + 'def_cor_c7' => { }, + 'def_cor_c8' => { }, + 'def_cor_k0' => { }, + 'def_cor_k1' => { }, + 'def_cor_k2' => { }, + 'yhi_ylo_cor_c0' => { }, + 'yhi_ylo_cor_c1' => { }, + 'yhi_ylo_cor_c2' => { }, + 'yhi_ylo_cor_c3' => { }, + 'yhi_ylo_cor_c4' => { }, + 'yhi_ylo_cor_c5' => { }, + 'yhi_ylo_cor_c6' => { }, + 'yhi_ylo_cor_c7' => { }, + 'yhi_ylo_cor_c8' => { }, + 'yhi_ylo_cor_k0' => { }, + 'yhi_ylo_cor_k1' => { }, + 'yhi_ylo_cor_k2' => { }, + 'def_conv_chrm_a_m' => { }, + 'def_conv_chrm_a_p' => { }, + 'def_conv_chrm_b_m' => { }, + 'def_conv_chrm_b_p' => { }, + 'def_conv_chrm_c_m' => { }, + 'def_conv_chrm_c_p' => { }, + 'def_conv_chrm_d_m' => { }, + 'def_conv_chrm_d_p' => { }, + 'def_conv_chrm_k_cb' => { }, + 'def_conv_chrm_k_cr' => { }, + 'def_conv_luma_v0' => { }, + 'def_conv_luma_v1' => { }, + 'def_conv_luma_v2' => { }, + 'def_conv_luma_k' => { }, + 'tl84_conv_chrm_a_m' => { }, + 'tl84_conv_chrm_a_p' => { }, + 'tl84_conv_chrm_b_m' => { }, + 'tl84_conv_chrm_b_p' => { }, + 'tl84_conv_chrm_c_m' => { }, + 'tl84_conv_chrm_c_p' => { }, + 'tl84_conv_chrm_d_m' => { }, + 'tl84_conv_chrm_d_p' => { }, + 'tl84_conv_chrm_k_cb' => { }, + 'tl84_conv_chrm_k_cr' => { }, + 'tl84_conv_luma_v0' => { }, + 'tl84_conv_luma_v1' => { }, + 'tl84_conv_luma_v2' => { }, + 'tl84_conv_luma_k' => { }, + 'incand_conv_chrm_a_m' => { }, + 'incand_conv_chrm_a_p' => { }, + 'incand_conv_chrm_b_m' => { }, + 'incand_conv_chrm_b_p' => { }, + 'incand_conv_chrm_c_m' => { }, + 'incand_conv_chrm_c_p' => { }, + 'incand_conv_chrm_d_m' => { }, + 'incand_conv_chrm_d_p' => { }, + 'incand_conv_chrm_k_cb' => { }, + 'incand_conv_chrm_k_cr' => { }, + 'incand_conv_luma_v0' => { }, + 'incand_conv_luma_v1' => { }, + 'incand_conv_luma_v2' => { }, + 'incand_conv_luma_k' => { }, + 'daylt_conv_chrm_a_m' => { }, + 'daylt_conv_chrm_a_p' => { }, + 'daylt_conv_chrm_b_m' => { }, + 'daylt_conv_chrm_b_p' => { }, + 'daylt_conv_chrm_c_m' => { }, + 'daylt_conv_chrm_c_p' => { }, + 'daylt_conv_chrm_d_m' => { }, + 'daylt_conv_chrm_d_p' => { }, + 'daylt_conv_chrm_k_cb' => { }, + 'daylt_conv_chrm_k_cr' => { }, + 'daylt_conv_luma_v0' => { }, + 'daylt_conv_luma_v1' => { }, + 'daylt_conv_luma_v2' => { }, + 'daylt_conv_luma_k' => { }, + 'yhi_ylo_conv_chrm_a_m' => { }, + 'yhi_ylo_conv_chrm_a_p' => { }, + 'yhi_ylo_conv_chrm_b_m' => { }, + 'yhi_ylo_conv_chrm_b_p' => { }, + 'yhi_ylo_conv_chrm_c_m' => { }, + 'yhi_ylo_conv_chrm_c_p' => { }, + 'yhi_ylo_conv_chrm_d_m' => { }, + 'yhi_ylo_conv_chrm_d_p' => { }, + 'yhi_ylo_conv_chrm_k_cb' => { }, + 'yhi_ylo_conv_chrm_k_cr' => { }, + 'yhi_ylo_conv_luma_v0' => { }, + 'yhi_ylo_conv_luma_v1' => { }, + 'yhi_ylo_conv_luma_v2' => { }, + 'yhi_ylo_conv_luma_k' => { }, + 'gamma_enable' => { }, + 'def_luma_gamma_mode' => { }, + 'def_rgb_gamma_mode' => { }, + 'blck_lvl_even_cols' => { }, + 'blck_lvl_odd_cols' => { }, + 'defect_pix_min_thresh' => { }, + 'defect_pix_max_thresh' => { }, + 'defect_pix_cor_enable' => { }, + 'prview_resol' => { }, + 'snapshot_resol' => { }, + 'curr_resol' => { }, + 'sensor_fmt' => { }, + 'discard_frst_frm' => { }, + 'frm_skip_pttrn' => { }, + 'sensor_type' => { }, + 'max_video_fps' => { }, + 'video_fps' => { }, + 'max_prview_fps' => { }, + 'prview_fps' => { }, + 'nghtsht_fps' => { }, + 'sensr_ful_wdth' => { }, + 'sensr_ful_hght' => { }, + 'sensr_qtr_wdth' => { }, + 'sensr_qtr_hght' => { }, + 'nightshot_mode' => { }, + 'pclk_invert' => { }, + 'cam_mclk_hz' => { }, + 'chrom_supress' => { }, + 'chro_sup_luma_thres_1' => { }, + 'chro_sup_luma_thres_2' => { }, + 'chro_sup_luma_thres_3' => { }, + 'chro_sup_luma_thres_4' => { }, + 'chro_sup_chro_thres_1' => { }, + 'chro_sup_chro_thres_2' => { }, + 'la_detect' => { }, + 'la_enable' => { }, + 'HJR_enable' => { }, + 'HJR_max_num_frames' => { }, + 'HJR_one_to_two_offset' => { }, + 'HJR_n_reduction_flat' => { }, + 'HJR_n_reduction_texture' => { }, + 'HJR_texture_threshold' => { }, +); + +# generate tag names and descriptions +{ + local $_; + my $table = \%Image::ExifTool::Qualcomm::Main; + MakeNameAndDesc($_, $$table{$_}) foreach TagTableKeys($table); +} + +#------------------------------------------------------------------------------ +# Generate tag Name and Description from a Qualcomm tag ID +# Inputs: 0) tag ID, 1) tagInfo ref +# Returns: true on success +sub MakeNameAndDesc($$) +{ + local $_ = shift; + my $tagInfo = shift; + # capitalize various leading acronyms or just first letter... + s/^(asf|awb|aec|afr|af_|la_|r2_tl|tl)/\U$1/ or $_ = ucfirst; + s/_([a-z])/_\u$1/g; # capitalize first letter of each word + s/\[(\d+)\]$/sprintf("_%.2d",$1)/e; # use 2-digit subscripts (and remove brackets) + tr/-_a-zA-Z0-9//dc; # delete invalid characters + my $desc = $_; + # convert underlines to spaces in description + if ($desc =~ tr/_/ /) { + # remove unnecessary underlines from tag name... + s/_([A-Z][a-z])/$1/g; + s/([a-z0-9])_([A-Z])/$1$2/g; + s/([A-Za-z])_(\d)/$1$2/g; + } + return 0 unless length; + $$tagInfo{Name} = $_; + $$tagInfo{Description} = $desc; + return 1; +} + +#------------------------------------------------------------------------------ +# Process Qualcomm APP7 metadata (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessQualcomm($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $pos = $$dirInfo{DirStart}; + my $dirEnd = $pos + $$dirInfo{DirLen}; + + $et->VerboseDir('Qualcomm', undef, $$dirInfo{DirLen}); + SetByteOrder('II'); + + while ($pos + 3 < $dirEnd) { + my $valLen = Get16u($dataPt, $pos); + my $tagLen = Get8u($dataPt, $pos + 2); + last if $pos + 8 + $tagLen + $valLen > $dirEnd; + my $tag = substr($$dataPt, $pos + 3, $tagLen); + $pos += 3 + $tagLen; # point to format byte + my $fmt = Get8u($dataPt, $pos); + # (not sure what these counts are for -- both are always 1 in my samples) + #my $cnt1 = Get16u($dataPt, $pos + 1); + #my $cnt2 = Get16u($dataPt, $pos + 3); + $pos += 5; # point to start of value data + my ($val, $format); + if ($fmt <= 7) { + $format = $qualcommFormat[$fmt]; + $val = ReadValue($dataPt, $pos, $format, undef, $valLen); + } else { + $format = "format $fmt"; + my $value = substr($$dataPt, $pos, $valLen); + $val = \$value; + } + unless (defined $$tagTablePtr{$tag} or $Image::ExifTool::specialTags{$tag}) { + my %tagInfo; + if (MakeNameAndDesc($tag, \%tagInfo)) { + $et->VPrint(0, $$et{INDENT}, "[adding Qualcomm:$tagInfo{Name}]\n"); + AddTagToTable($tagTablePtr, $tag, \%tagInfo); + } + } + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos, + Size => $valLen, + Format => $format, + ); + $pos += $valLen; # point to start of next entry + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Qualcomm - Read Qualcomm APP7 meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +information from the APP7 Qualcomm segment in JPEG images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Qualcomm Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/QuickTime.pm b/ExifTool/lib/Image/ExifTool/QuickTime.pm new file mode 100644 index 0000000..7049d36 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/QuickTime.pm @@ -0,0 +1,9991 @@ +#------------------------------------------------------------------------------ +# File: QuickTime.pm +# +# Description: Read QuickTime and MP4 meta information +# +# Revisions: 10/04/2005 - P. Harvey Created +# 12/19/2005 - P. Harvey Added MP4 support +# 09/22/2006 - P. Harvey Added M4A support +# 07/27/2010 - P. Harvey Updated to 2010-05-03 QuickTime spec +# +# References: +# +# 1) http://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html +# 2) http://search.cpan.org/dist/MP4-Info-1.04/ +# 3) http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt +# 4) http://wiki.multimedia.cx/index.php?title=Apple_QuickTime +# 5) ISO 14496-12 (http://read.pudn.com/downloads64/ebook/226547/ISO_base_media_file_format.pdf) +# 6) ISO 14496-16 (http://www.iec-normen.de/previewpdf/info_isoiec14496-16%7Bed2.0%7Den.pdf) +# 7) http://atomicparsley.sourceforge.net/mpeg-4files.html +# 8) http://wiki.multimedia.cx/index.php?title=QuickTime_container +# 9) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf (Oct 2008) +# 10) http://code.google.com/p/mp4v2/wiki/iTunesMetadata +# 11) http://www.canieti.com.mx/assets/files/1011/IEC_100_1384_DC.pdf +# 12) QuickTime file format specification 2010-05-03 +# 13) http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf +# 14) http://standards.iso.org/ittf/PubliclyAvailableStandards/c051533_ISO_IEC_14496-12_2008.zip +# 15) http://getid3.sourceforge.net/source/module.audio-video.quicktime.phps +# 16) http://qtra.apple.com/atoms.html +# 17) http://www.etsi.org/deliver/etsi_ts/126200_126299/126244/10.01.00_60/ts_126244v100100p.pdf +# 18) https://github.com/appsec-labs/iNalyzer/blob/master/scinfo.m +# 19) http://nah6.com/~itsme/cvs-xdadevtools/iphone/tools/decodesinf.pl +# 20) https://developer.apple.com/legacy/library/documentation/quicktime/reference/QT7-1_Update_Reference/QT7-1_Update_Reference.pdf +# 21) Francois Bonzon private communication +# 22) https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html +# 23) http://atomicparsley.sourceforge.net/mpeg-4files.html +# 24) https://github.com/sergiomb2/libmp4v2/wiki/iTunesMetadata +# 25) https://cconcolato.github.io/mp4ra/atoms.html +# 26) https://github.com/SamsungVR/android_upload_sdk/blob/master/SDKLib/src/main/java/com/samsung/msca/samsungvr/sdk/UserVideo.java +# 27) https://exiftool.org/forum/index.php?topic=11517.0 +# 28) https://docs.mp3tag.de/mapping/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::QuickTime; + +use strict; +use vars qw($VERSION $AUTOLOAD %stringEncoding); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::GPS; + +$VERSION = '2.87'; + +sub ProcessMOV($$;$); +sub ProcessKeys($$$); +sub ProcessMetaKeys($$$); +sub ProcessMetaData($$$); +sub ProcessEncodingParams($$$); +sub ProcessSampleDesc($$$); +sub ProcessHybrid($$$); +sub ProcessRights($$$); +# ++vvvvvvvvvvvv++ (in QuickTimeStream.pl) +sub Process_mebx($$$); +sub Process_3gf($$$); +sub Process_gps0($$$); +sub Process_gsen($$$); +sub ProcessKenwood($$$); +sub ProcessRIFFTrailer($$$); +sub ProcessTTAD($$$); +sub ProcessNMEA($$$); +sub ProcessGPSLog($$$); +sub ProcessGarminGPS($$$); +sub SaveMetaKeys($$$); +# ++^^^^^^^^^^^^++ +sub ParseItemLocation($$); +sub ParseContentDescribes($$); +sub ParseItemInfoEntry($$); +sub ParseItemPropAssoc($$); +sub FixWrongFormat($); +sub GetMatrixStructure($$); +sub ConvertISO6709($); +sub ConvInvISO6709($); +sub ConvertChapterList($); +sub PrintChapter($); +sub PrintGPSCoordinates($); +sub PrintInvGPSCoordinates($); +sub UnpackLang($;$); +sub WriteKeys($$$); +sub WriteQuickTime($$$); +sub WriteMOV($$); +sub GetLangInfo($$); +sub CheckQTValue($$$); + +# MIME types for all entries in the ftypLookup with file extensions +# (defaults to 'video/mp4' if not found in this lookup) +my %mimeLookup = ( + '3G2' => 'video/3gpp2', + '3GP' => 'video/3gpp', + AAX => 'audio/vnd.audible.aax', + DVB => 'video/vnd.dvb.file', + F4A => 'audio/mp4', + F4B => 'audio/mp4', + JP2 => 'image/jp2', + JPM => 'image/jpm', + JPX => 'image/jpx', + M4A => 'audio/mp4', + M4B => 'audio/mp4', + M4P => 'audio/mp4', + M4V => 'video/x-m4v', + MOV => 'video/quicktime', + MQV => 'video/quicktime', + HEIC => 'image/heic', + HEVC => 'image/heic-sequence', + HEICS=> 'image/heic-sequence', + HEIF => 'image/heif', + HEIFS=> 'image/heif-sequence', + AVIF => 'image/avif', #PH (NC) + CRX => 'video/x-canon-crx', # (will get overridden) +); + +# look up file type from ftyp atom type, with MIME type in comment if known +# (ref http://www.ftyps.com/) +my %ftypLookup = ( + '3g2a' => '3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-0 V1.0', # video/3gpp2 + '3g2b' => '3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-A V1.0.0', # video/3gpp2 + '3g2c' => '3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-B v1.0', # video/3gpp2 + '3ge6' => '3GPP (.3GP) Release 6 MBMS Extended Presentations', # video/3gpp + '3ge7' => '3GPP (.3GP) Release 7 MBMS Extended Presentations', # video/3gpp + '3gg6' => '3GPP Release 6 General Profile', # video/3gpp + '3gp1' => '3GPP Media (.3GP) Release 1 (probably non-existent)', # video/3gpp + '3gp2' => '3GPP Media (.3GP) Release 2 (probably non-existent)', # video/3gpp + '3gp3' => '3GPP Media (.3GP) Release 3 (probably non-existent)', # video/3gpp + '3gp4' => '3GPP Media (.3GP) Release 4', # video/3gpp + '3gp5' => '3GPP Media (.3GP) Release 5', # video/3gpp + '3gp6' => '3GPP Media (.3GP) Release 6 Basic Profile', # video/3gpp + '3gp6' => '3GPP Media (.3GP) Release 6 Progressive Download', # video/3gpp + '3gp6' => '3GPP Media (.3GP) Release 6 Streaming Servers', # video/3gpp + '3gs7' => '3GPP Media (.3GP) Release 7 Streaming Servers', # video/3gpp + 'aax ' => 'Audible Enhanced Audiobook (.AAX)', #PH + 'avc1' => 'MP4 Base w/ AVC ext [ISO 14496-12:2005]', # video/mp4 + 'CAEP' => 'Canon Digital Camera', + 'caqv' => 'Casio Digital Camera', + 'CDes' => 'Convergent Design', + 'da0a' => 'DMB MAF w/ MPEG Layer II aud, MOT slides, DLS, JPG/PNG/MNG images', + 'da0b' => 'DMB MAF, extending DA0A, with 3GPP timed text, DID, TVA, REL, IPMP', + 'da1a' => 'DMB MAF audio with ER-BSAC audio, JPG/PNG/MNG images', + 'da1b' => 'DMB MAF, extending da1a, with 3GPP timed text, DID, TVA, REL, IPMP', + 'da2a' => 'DMB MAF aud w/ HE-AAC v2 aud, MOT slides, DLS, JPG/PNG/MNG images', + 'da2b' => 'DMB MAF, extending da2a, with 3GPP timed text, DID, TVA, REL, IPMP', + 'da3a' => 'DMB MAF aud with HE-AAC aud, JPG/PNG/MNG images', + 'da3b' => 'DMB MAF, extending da3a w/ BIFS, 3GPP timed text, DID, TVA, REL, IPMP', + 'dmb1' => 'DMB MAF supporting all the components defined in the specification', + 'dmpf' => 'Digital Media Project', # various + 'drc1' => 'Dirac (wavelet compression), encapsulated in ISO base media (MP4)', + 'dv1a' => 'DMB MAF vid w/ AVC vid, ER-BSAC aud, BIFS, JPG/PNG/MNG images, TS', + 'dv1b' => 'DMB MAF, extending dv1a, with 3GPP timed text, DID, TVA, REL, IPMP', + 'dv2a' => 'DMB MAF vid w/ AVC vid, HE-AAC v2 aud, BIFS, JPG/PNG/MNG images, TS', + 'dv2b' => 'DMB MAF, extending dv2a, with 3GPP timed text, DID, TVA, REL, IPMP', + 'dv3a' => 'DMB MAF vid w/ AVC vid, HE-AAC aud, BIFS, JPG/PNG/MNG images, TS', + 'dv3b' => 'DMB MAF, extending dv3a, with 3GPP timed text, DID, TVA, REL, IPMP', + 'dvr1' => 'DVB (.DVB) over RTP', # video/vnd.dvb.file + 'dvt1' => 'DVB (.DVB) over MPEG-2 Transport Stream', # video/vnd.dvb.file + 'F4A ' => 'Audio for Adobe Flash Player 9+ (.F4A)', # audio/mp4 + 'F4B ' => 'Audio Book for Adobe Flash Player 9+ (.F4B)', # audio/mp4 + 'F4P ' => 'Protected Video for Adobe Flash Player 9+ (.F4P)', # video/mp4 + 'F4V ' => 'Video for Adobe Flash Player 9+ (.F4V)', # video/mp4 + 'isc2' => 'ISMACryp 2.0 Encrypted File', # ?/enc-isoff-generic + 'iso2' => 'MP4 Base Media v2 [ISO 14496-12:2005]', # video/mp4 (or audio) + 'iso3' => 'MP4 Base Media v3', # video/mp4 (or audio) + 'iso4' => 'MP4 Base Media v4', # video/mp4 (or audio) + 'iso5' => 'MP4 Base Media v5', # video/mp4 (or audio) + 'iso6' => 'MP4 Base Media v6', # video/mp4 (or audio) + 'iso7' => 'MP4 Base Media v7', # video/mp4 (or audio) + 'iso8' => 'MP4 Base Media v8', # video/mp4 (or audio) + 'iso9' => 'MP4 Base Media v9', # video/mp4 (or audio) + 'isom' => 'MP4 Base Media v1 [IS0 14496-12:2003]', # video/mp4 (or audio) + 'JP2 ' => 'JPEG 2000 Image (.JP2) [ISO 15444-1 ?]', # image/jp2 + 'JP20' => 'Unknown, from GPAC samples (prob non-existent)', + 'jpm ' => 'JPEG 2000 Compound Image (.JPM) [ISO 15444-6]', # image/jpm + 'jpx ' => 'JPEG 2000 with extensions (.JPX) [ISO 15444-2]', # image/jpx + 'KDDI' => '3GPP2 EZmovie for KDDI 3G cellphones', # video/3gpp2 + #LCAG => (found in CompatibleBrands of Leica MOV videos) + 'M4A ' => 'Apple iTunes AAC-LC (.M4A) Audio', # audio/x-m4a + 'M4B ' => 'Apple iTunes AAC-LC (.M4B) Audio Book', # audio/mp4 + 'M4P ' => 'Apple iTunes AAC-LC (.M4P) AES Protected Audio', # audio/mp4 + 'M4V ' => 'Apple iTunes Video (.M4V) Video', # video/x-m4v + 'M4VH' => 'Apple TV (.M4V)', # video/x-m4v + 'M4VP' => 'Apple iPhone (.M4V)', # video/x-m4v + 'mj2s' => 'Motion JPEG 2000 [ISO 15444-3] Simple Profile', # video/mj2 + 'mjp2' => 'Motion JPEG 2000 [ISO 15444-3] General Profile', # video/mj2 + 'mmp4' => 'MPEG-4/3GPP Mobile Profile (.MP4/3GP) (for NTT)', # video/mp4 + 'mp21' => 'MPEG-21 [ISO/IEC 21000-9]', # various + 'mp41' => 'MP4 v1 [ISO 14496-1:ch13]', # video/mp4 + 'mp42' => 'MP4 v2 [ISO 14496-14]', # video/mp4 + 'mp71' => 'MP4 w/ MPEG-7 Metadata [per ISO 14496-12]', # various + 'MPPI' => 'Photo Player, MAF [ISO/IEC 23000-3]', # various + 'mqt ' => 'Sony / Mobile QuickTime (.MQV) US Patent 7,477,830 (Sony Corp)', # video/quicktime + 'MSNV' => 'MPEG-4 (.MP4) for SonyPSP', # audio/mp4 + 'NDAS' => 'MP4 v2 [ISO 14496-14] Nero Digital AAC Audio', # audio/mp4 + 'NDSC' => 'MPEG-4 (.MP4) Nero Cinema Profile', # video/mp4 + 'NDSH' => 'MPEG-4 (.MP4) Nero HDTV Profile', # video/mp4 + 'NDSM' => 'MPEG-4 (.MP4) Nero Mobile Profile', # video/mp4 + 'NDSP' => 'MPEG-4 (.MP4) Nero Portable Profile', # video/mp4 + 'NDSS' => 'MPEG-4 (.MP4) Nero Standard Profile', # video/mp4 + 'NDXC' => 'H.264/MPEG-4 AVC (.MP4) Nero Cinema Profile', # video/mp4 + 'NDXH' => 'H.264/MPEG-4 AVC (.MP4) Nero HDTV Profile', # video/mp4 + 'NDXM' => 'H.264/MPEG-4 AVC (.MP4) Nero Mobile Profile', # video/mp4 + 'NDXP' => 'H.264/MPEG-4 AVC (.MP4) Nero Portable Profile', # video/mp4 + 'NDXS' => 'H.264/MPEG-4 AVC (.MP4) Nero Standard Profile', # video/mp4 + 'odcf' => 'OMA DCF DRM Format 2.0 (OMA-TS-DRM-DCF-V2_0-20060303-A)', # various + 'opf2' => 'OMA PDCF DRM Format 2.1 (OMA-TS-DRM-DCF-V2_1-20070724-C)', + 'opx2' => 'OMA PDCF DRM + XBS extensions (OMA-TS-DRM_XBS-V1_0-20070529-C)', + 'pana' => 'Panasonic Digital Camera', + 'qt ' => 'Apple QuickTime (.MOV/QT)', # video/quicktime + 'ROSS' => 'Ross Video', + 'sdv ' => 'SD Memory Card Video', # various? + 'ssc1' => 'Samsung stereoscopic, single stream', + 'ssc2' => 'Samsung stereoscopic, dual stream', + 'XAVC' => 'Sony XAVC', #PH + 'heic' => 'High Efficiency Image Format HEVC still image (.HEIC)', # image/heic + 'hevc' => 'High Efficiency Image Format HEVC sequence (.HEICS)', # image/heic-sequence + 'mif1' => 'High Efficiency Image Format still image (.HEIF)', # image/heif + 'msf1' => 'High Efficiency Image Format sequence (.HEIFS)', # image/heif-sequence + 'heix' => 'High Efficiency Image Format still image (.HEIF)', # image/heif (ref PH, Canon 1DXmkIII) + 'avif' => 'AV1 Image File Format (.AVIF)', # image/avif + 'crx ' => 'Canon Raw (.CRX)', #PH (CR3 or CRM; use Canon CompressorVersion to decide) +); + +# use extension to determine file type +my %useExt = ( GLV => 'MP4' ); + +# information for int32u date/time tags (time zero is Jan 1, 1904) +my %timeInfo = ( + Notes => 'converted from UTC to local time if the QuickTimeUTC option is set', + Shift => 'Time', + Writable => 1, + Permanent => 1, + DelValue => 0, + # It is not uncommon for brain-dead software to use the wrong time zero, it should be + # Jan 1, 1904, so assume a time zero of Jan 1, 1970 if the date is before this + # Note: This value will be in UTC if generated by a system that is aware of the time zone + # (also note: this code is duplicated for the CreateDate tag) + RawConv => q{ + my $offset = (66 * 365 + 17) * 24 * 3600; + return $val - $offset if $val >= $offset or $$self{OPTIONS}{QuickTimeUTC}; + if ($val and not $$self{IsWriting}) { + $self->WarnOnce('Patched incorrect time zero for QuickTime date/time tag',1); + } + return $val; + }, + RawConvInv => q{ + if ($$self{FileType} eq 'CR3' and not $self->Options('QuickTimeUTC')) { + # convert to UTC + my $offset = (66 * 365 + 17) * 24 * 3600; + $val = ConvertUnixTime($val - $offset); + $val = GetUnixTime($val, 1) + $offset; + } + return $val; + }, + # (all CR3 files store UTC times - PH) + ValueConv => 'ConvertUnixTime($val, $self->Options("QuickTimeUTC") || $$self{FileType} eq "CR3")', + ValueConvInv => q{ + $val = GetUnixTime($val, $self->Options("QuickTimeUTC")); + return undef unless defined $val; + return $val + (66 * 365 + 17) * 24 * 3600; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + # (can't put Groups here because they aren't constant!) +); +# information for duration tags +my %durationInfo = ( + ValueConv => '$$self{TimeScale} ? $val / $$self{TimeScale} : $val', + PrintConv => '$$self{TimeScale} ? ConvertDuration($val) : $val', +); +# handle unknown tags +my %unknownInfo = ( + Unknown => 1, + ValueConv => '$val =~ /^([\x20-\x7e]*)\0*$/ ? $1 : \$val', +); + +# multi-language text with 6-byte header +my %langText = ( IText => 6 ); + +# parsing for most of the 3gp udta language text boxes +my %langText3gp = ( + Notes => 'used in 3gp videos', + Avoid => 1, + IText => 6, +); + +# 4-character Vendor ID codes (ref PH) +my %vendorID = ( + appl => 'Apple', + fe20 => 'Olympus (fe20)', # (FE200) + FFMP => 'FFmpeg', + 'GIC '=> 'General Imaging Co.', + kdak => 'Kodak', + KMPI => 'Konica-Minolta', + leic => 'Leica', + mino => 'Minolta', + niko => 'Nikon', + NIKO => 'Nikon', + olym => 'Olympus', + pana => 'Panasonic', + pent => 'Pentax', + pr01 => 'Olympus (pr01)', # (FE100,FE110,FE115) + sany => 'Sanyo', + 'SMI '=> 'Sorenson Media Inc.', + ZORA => 'Zoran Corporation', + 'AR.D'=> 'Parrot AR.Drone', + ' KD '=> 'Kodak', # (FZ201) +); + +# QuickTime data atom encodings for string types (ref 12) +%stringEncoding = ( + 1 => 'UTF8', + 2 => 'UTF16', + 3 => 'ShiftJIS', + 4 => 'UTF8', + 5 => 'UTF16', +); + +my %graphicsMode = ( + # (ref http://homepage.mac.com/vanhoek/MovieGuts%20docs/64.html) + 0x00 => 'srcCopy', + 0x01 => 'srcOr', + 0x02 => 'srcXor', + 0x03 => 'srcBic', + 0x04 => 'notSrcCopy', + 0x05 => 'notSrcOr', + 0x06 => 'notSrcXor', + 0x07 => 'notSrcBic', + 0x08 => 'patCopy', + 0x09 => 'patOr', + 0x0a => 'patXor', + 0x0b => 'patBic', + 0x0c => 'notPatCopy', + 0x0d => 'notPatOr', + 0x0e => 'notPatXor', + 0x0f => 'notPatBic', + 0x20 => 'blend', + 0x21 => 'addPin', + 0x22 => 'addOver', + 0x23 => 'subPin', + 0x24 => 'transparent', + 0x25 => 'addMax', + 0x26 => 'subOver', + 0x27 => 'addMin', + 0x31 => 'grayishTextOr', + 0x32 => 'hilite', + 0x40 => 'ditherCopy', + # the following ref ISO/IEC 15444-3 + 0x100 => 'Alpha', + 0x101 => 'White Alpha', + 0x102 => 'Pre-multiplied Black Alpha', + 0x110 => 'Component Alpha', +); + +my %channelLabel = ( + 0xFFFFFFFF => 'Unknown', + 0 => 'Unused', + 100 => 'UseCoordinates', + 1 => 'Left', + 2 => 'Right', + 3 => 'Center', + 4 => 'LFEScreen', + 5 => 'LeftSurround', + 6 => 'RightSurround', + 7 => 'LeftCenter', + 8 => 'RightCenter', + 9 => 'CenterSurround', + 10 => 'LeftSurroundDirect', + 11 => 'RightSurroundDirect', + 12 => 'TopCenterSurround', + 13 => 'VerticalHeightLeft', + 14 => 'VerticalHeightCenter', + 15 => 'VerticalHeightRight', + 16 => 'TopBackLeft', + 17 => 'TopBackCenter', + 18 => 'TopBackRight', + 33 => 'RearSurroundLeft', + 34 => 'RearSurroundRight', + 35 => 'LeftWide', + 36 => 'RightWide', + 37 => 'LFE2', + 38 => 'LeftTotal', + 39 => 'RightTotal', + 40 => 'HearingImpaired', + 41 => 'Narration', + 42 => 'Mono', + 43 => 'DialogCentricMix', + 44 => 'CenterSurroundDirect', + 45 => 'Haptic', + 200 => 'Ambisonic_W', + 201 => 'Ambisonic_X', + 202 => 'Ambisonic_Y', + 203 => 'Ambisonic_Z', + 204 => 'MS_Mid', + 205 => 'MS_Side', + 206 => 'XY_X', + 207 => 'XY_Y', + 301 => 'HeadphonesLeft', + 302 => 'HeadphonesRight', + 304 => 'ClickTrack', + 305 => 'ForeignLanguage', + 400 => 'Discrete', + 0x10000 => 'Discrete_0', + 0x10001 => 'Discrete_1', + 0x10002 => 'Discrete_2', + 0x10003 => 'Discrete_3', + 0x10004 => 'Discrete_4', + 0x10005 => 'Discrete_5', + 0x10006 => 'Discrete_6', + 0x10007 => 'Discrete_7', + 0x10008 => 'Discrete_8', + 0x10009 => 'Discrete_9', + 0x1000a => 'Discrete_10', + 0x1000b => 'Discrete_11', + 0x1000c => 'Discrete_12', + 0x1000d => 'Discrete_13', + 0x1000e => 'Discrete_14', + 0x1000f => 'Discrete_15', + 0x1ffff => 'Discrete_65535', +); + +my %qtFlags = ( #12 + 0 => 'undef', 22 => 'unsigned int', 71 => 'float[2] size', + 1 => 'UTF-8', 23 => 'float', 72 => 'float[4] rect', + 2 => 'UTF-16', 24 => 'double', 74 => 'int64s', + 3 => 'ShiftJIS', 27 => 'BMP', 75 => 'int8u', + 4 => 'UTF-8 sort', 28 => 'QT atom', 76 => 'int16u', + 5 => 'UTF-16 sort', 65 => 'int8s', 77 => 'int32u', + 13 => 'JPEG', 66 => 'int16s', 78 => 'int64u', + 14 => 'PNG', 67 => 'int32s', 79 => 'double[3][3]', + 21 => 'signed int', 70 => 'float[2] point', +); + +# properties which don't get inherited from the parent +my %dontInherit = ( + ispe => 1, # size of parent may be different + hvcC => 1, # (likely redundant) +); + +# tags that may be duplicated and directories that may contain duplicate tags +# (used only to avoid warnings when Validate-ing) +my %dupTagOK = ( mdat => 1, trak => 1, free => 1, infe => 1, sgpd => 1, dimg => 1, CCDT => 1, + sbgp => 1, csgm => 1, uuid => 1, cdsc => 1, maxr => 1, '----' => 1 ); +my %dupDirOK = ( ipco => 1, '----' => 1 ); + +# the usual atoms required to decode timed metadata with the ExtractEmbedded option +my %eeStd = ( stco => 'stbl', co64 => 'stbl', stsz => 'stbl', stz2 => 'stbl', + stsc => 'stbl', stts => 'stbl' ); + +# atoms required for generating ImageDataHash +my %hashBox = ( vide => { %eeStd }, soun => { %eeStd } ); + +# boxes and their containers for the various handler types that we want to save +# when the ExtractEmbedded is enabled (currently only the 'gps ' container name is +# used, but others have been checked against all available sample files and may be +# useful in the future if the names are used for different boxes on other locations) +my %eeBox = ( + # (note: vide is only processed if specific atoms exist in the VideoSampleDesc) + vide => { %eeStd, JPEG => 'stsd' }, + text => { %eeStd }, + meta => { %eeStd }, + sbtl => { %eeStd }, + data => { %eeStd }, + camm => { %eeStd }, # (Insta360) + ctbx => { %eeStd }, # (GM cars) + '' => { 'gps ' => 'moov', 'GPS ' => 'main' }, # (no handler -- in top level 'moov' box, and main) +); +# boxes to save when ExtractEmbedded is set to 2 or higher +my %eeBox2 = ( + vide => { avcC => 'stsd' }, # (parses H264 video stream) +); + +# image types in AVIF and HEIC files +my %isImageData = ( av01 => 1, avc1 => 1, hvc1 => 1, lhv1 => 1, hvt1 => 1 ); + +# QuickTime atoms +%Image::ExifTool::QuickTime::Main = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, # (only needs to be defined for directories to process when writing) + GROUPS => { 2 => 'Video' }, + meta => { # 'meta' is found here in my Sony ILCE-7S MP4 sample - PH + Name => 'Meta', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Meta', + Start => 4, # skip 4-byte version number header + }, + }, + meco => { #ISO14496-12:2015 + Name => 'OtherMeta', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::OtherMeta' }, + }, + free => [ + { + Name => 'KodakFree', + # (found in Kodak M5370 MP4 videos) + Condition => '$$valPt =~ /^\0\0\0.Seri/s', + SubDirectory => { TagTable => 'Image::ExifTool::Kodak::Free' }, + },{ + Name => 'Pittasoft', + # (Pittasoft Blackview dashcam MP4 videos) + Condition => '$$valPt =~ /^\0\0..(cprt|sttm|ptnm|ptrh|thum|gps |3gf )/s', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Pittasoft' }, + },{ + Name => 'ThumbnailImage', + # (DJI Zenmuse XT2 thermal camera) + Groups => { 2 => 'Preview' }, + Condition => '$$valPt =~ /^.{4}mdat\xff\xd8\xff/s', + RawConv => q{ + my $len = unpack('N', $val); + return undef if $len <= 8 or $len > length($val); + return substr($val, 8, $len-8); + }, + Binary => 1, + },{ + Unknown => 1, + Binary => 1, + }, + # (also Samsung WB750 uncompressed thumbnail data starting with "SDIC\0") + ], + # fre1 - 4 bytes: "june" (Kodak PixPro SP360) + frea => { + Name => 'Kodak_frea', + SubDirectory => { TagTable => 'Image::ExifTool::Kodak::frea' }, + }, + skip => [ + { + Name => 'CanonSkip', + Condition => '$$valPt =~ /^\0.{3}(CNDB|CNCV|CNMN|CNFV|CNTH|CNDM)/s', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::Skip' }, + }, + { + Name => 'PreviewImage', # (found in DuDuBell M1 dashcam MOV files) + Groups => { 2 => 'Preview' }, + Condition => '$$valPt =~ /^.{12}\xff\xd8\xff/', + Binary => 1, + RawConv => q{ + my $len = Get32u(\$val, 8); + return undef unless length($val) >= $len + 12; + return substr($val, 12, $len); + }, + }, + { + Name => 'SkipInfo', # (found in 70mai Pro Plus+ MP4 videos) + # (look for something that looks like a QuickTime atom header) + Condition => '$$valPt =~ /^\0[\0-\x04]..[a-zA-Z ]{4}/s', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::SkipInfo' }, + }, + { Name => 'Skip', Unknown => 1, Binary => 1 }, + ], + wide => { Unknown => 1, Binary => 1 }, + ftyp => { #MP4 + Name => 'FileType', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::FileType' }, + }, + pnot => { + Name => 'Preview', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Preview' }, + }, + PICT => { + Name => 'PreviewPICT', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + pict => { #8 + Name => 'PreviewPICT', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + # (note that moov is present for an HEIF sequence) + moov => { + Name => 'Movie', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Movie' }, + }, + moof => { + Name => 'MovieFragment', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MovieFragment' }, + }, + # mfra - movie fragment random access: contains tfra (track fragment random access), and + # mfro (movie fragment random access offset) (ref 5) + mdat => { Name => 'MediaData', Unknown => 1, Binary => 1 }, + 'mdat-size' => { + Name => 'MediaDataSize', + RawConv => '$$self{MediaDataSize} = $val', + Notes => q{ + not a real tag ID, this tag represents the size of the 'mdat' data in bytes + and is used in the AvgBitrate calculation + }, + }, + 'mdat-offset' => { + Name => 'MediaDataOffset', + RawConv => '$$self{MediaDataOffset} = $val', + }, + junk => { Unknown => 1, Binary => 1 }, #8 + uuid => [ + { #9 (MP4 files) + Name => 'XMP', + # *** this is where ExifTool writes XMP in MP4 videos (as per XMP spec) *** + Condition => '$$valPt=~/^\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac/', + WriteGroup => 'XMP', # (write main XMP tags here) + PreservePadding => 1, + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + Start => 16, + }, + }, + { #11 (MP4 files) + Name => 'UUID-PROF', + Condition => '$$valPt=~/^PROF!\xd2\x4f\xce\xbb\x88\x69\x5c\xfa\xc9\xc7\x40/', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Profile', + Start => 24, # uid(16) + version(1) + flags(3) + count(4) + }, + }, + { #PH (Flip MP4 files) + Name => 'UUID-Flip', + Condition => '$$valPt=~/^\x4a\xb0\x3b\x0f\x61\x8d\x40\x75\x82\xb2\xd9\xfa\xce\xd3\x5f\xf5/', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Flip', + Start => 16, + }, + }, + # "\x98\x7f\xa3\xdf\x2a\x85\x43\xc0\x8f\x8f\xd9\x7c\x47\x1e\x8e\xea" - unknown data in Flip videos + { #PH (Canon CR3) + Name => 'UUID-Canon2', + WriteLast => 1, # MUST come after mdat or DPP will drop mdat when writing! + Condition => '$$valPt=~/^\x21\x0f\x16\x87\x91\x49\x11\xe4\x81\x11\x00\x24\x21\x31\xfc\xe4/', + SubDirectory => { + TagTable => 'Image::ExifTool::Canon::uuid2', + Start => 16, + }, + }, + { # (ref https://github.com/JamesHeinrich/getID3/blob/master/getid3/module.audio-video.quicktime.php) + Name => 'SensorData', # sensor data for the 360Fly + Condition => '$$valPt=~/^\xef\xe1\x58\x9a\xbb\x77\x49\xef\x80\x95\x27\x75\x9e\xb1\xdc\x6f/ and $$self{OPTIONS}{ExtractEmbedded}', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Tags360Fly' }, + }, + { + Name => 'SensorData', + Condition => '$$valPt=~/^\xef\xe1\x58\x9a\xbb\x77\x49\xef\x80\x95\x27\x75\x9e\xb1\xdc\x6f/', + Notes => 'raw 360Fly sensor data without ExtractEmbedded option', + RawConv => q{ + $self->WarnOnce('Use the ExtractEmbedded option to decode timed SensorData',3); + return \$val; + }, + }, + { #PH (Canon CR3) + Name => 'PreviewImage', + Condition => '$$valPt=~/^\xea\xf4\x2b\x5e\x1c\x98\x4b\x88\xb9\xfb\xb7\xdc\x40\x6e\x4d\x16/', + Groups => { 2 => 'Preview' }, + PreservePadding => 1, + # 0x00 - undef[16]: UUID + # 0x10 - int32u[2]: "0 1" (version and/or item count?) + # 0x18 - int32u: PRVW atom size + # 0x20 - int32u: 'PRVW' + # 0x30 - int32u: 0 + # 0x34 - int16u: 1 + # 0x36 - int16u: image width + # 0x38 - int16u: image height + # 0x3a - int16u: 1 + # 0x3c - int32u: preview length + RawConv => '$val = substr($val, 0x30); $self->ValidateImage(\$val, $tag)', + }, + { #8 + Name => 'UUID-Unknown', + %unknownInfo, + }, + ], + _htc => { + Name => 'HTCInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::HTCInfo' }, + }, + udta => [{ + Name => 'KenwoodData', + Condition => '$$valPt =~ /^VIDEOUUUUUUUUUUUUUUUUUUUUUU/', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&ProcessKenwood, + }, + },{ + Name => 'FLIRData', + SubDirectory => { TagTable => 'Image::ExifTool::FLIR::UserData' }, + }], + thum => { #PH + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + 'thm ' => { #PH (70mai A800) + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + ardt => { #PH + Name => 'ARDroneFile', + ValueConv => 'length($val) > 4 ? substr($val,4) : $val', # remove length + }, + prrt => { #PH + Name => 'ARDroneTelemetry', + Notes => q{ + telemetry information for each video frame: status1, status2, time, pitch, + roll, yaw, speed, altitude + }, + ValueConv => q{ + my $size = length $val; + return \$val if $size < 12 or not $$self{OPTIONS}{Binary}; + my $len = Get16u(\$val, 2); + my $str = ''; + SetByteOrder('II'); + my $pos = 12; + while ($pos + $len <= $size) { + my $s1 = Get16u(\$val, $pos); + # s2: 7=take-off?, 3=moving, 4=hovering, 9=landing?, 2=landed + my $s2 = Get16u(\$val, $pos + 2); + $str .= "$s1 $s2"; + my $num = int(($len-4)/4); + my ($i, $v); + for ($i=0; $i<$num; ++$i) { + my $pt = $pos + 4 + $i * 4; + if ($i > 0 && $i < 4) { + $v = GetFloat(\$val, $pt); # pitch/roll/yaw + } else { + $v = Get32u(\$val, $pt); + # convert time to sec, and speed(NC)/altitude to metres + $v /= 1000 if $i <= 5; + } + $str .= " $v"; + } + $str .= "\n"; + $pos += $len; + } + SetByteOrder('MM'); + return \$str; + }, + }, + udat => { #PH (GPS NMEA-format log written by Datakam Player software) + Name => 'GPSLog', + Binary => 1, # (actually ASCII, but very lengthy) + Notes => 'parsed to extract GPS separately when ExtractEmbedded is used', + RawConv => q{ + $val =~ s/\0+$//; # remove trailing nulls + if (length $val and $$self{OPTIONS}{ExtractEmbedded}) { + my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + Image::ExifTool::QuickTime::ProcessGPSLog($self, { DataPt => \$val }, $tagTbl); + } + return $val; + }, + }, + # meta - proprietary XML information written by some Flip cameras - PH + # beam - 16 bytes found in an iPhone video + IDIT => { #PH (written by DuDuBell M1, VSYS M6L dashcams) + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Format => 'string', # (removes trailing "\0") + Shift => 'Time', + Writable => 1, + Permanent => 1, + DelValue => '0000-00-00T00:00:00+0000', + ValueConv => '$val=~tr/-/:/; $val', + ValueConvInv => '$val=~s/(\d+):(\d+):/$1-$2-/; $val', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,1)', # (add time zone if it didn't exist) + }, + gps0 => { #PH (DuDuBell M1, VSYS M6L) + Name => 'GPSTrack', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&Process_gps0, + }, + }, + gsen => { #PH (DuDuBell M1, VSYS M6L) + Name => 'GSensor', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&Process_gsen, + }, + }, + # gpsa - seen hex "01 20 00 00" (DuDuBell M1, VSYS M6L) + # gsea - 20 bytes hex "05 00's..." (DuDuBell M1) "05 08 02 01 ..." (VSYS M6L) + 'GPS ' => { # GPS data written by 70mai dashcam (parsed in QuickTimeStream.pl) + Name => 'GPSDataList2', + Unknown => 1, + Binary => 1, + }, + sefd => { + Name => 'SamsungTrailer', + SubDirectory => { TagTable => 'Image::ExifTool::Samsung::Trailer' }, + }, + # 'samn'? - seen in Vantrue N2S sample video + mpvd => { + Name => 'MotionPhotoVideo', + Notes => 'MP4-format video saved in Samsung motion-photo HEIC images.', + Binary => 1, + # note that this may be written and/or deleted, but can't currently be added back again + Writable => 1, + }, +); + +# stuff seen in 'skip' atom (70mai Pro Plus+ MP4 videos) +%Image::ExifTool::QuickTime::SkipInfo = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + 'ver ' => 'Version', + # tima - int32u: seen 0x3c + thma => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, +); + +# MPEG-4 'ftyp' atom +# (ref http://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html) +%Image::ExifTool::QuickTime::FileType = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + 0 => { + Name => 'MajorBrand', + Format => 'undef[4]', + PrintConv => \%ftypLookup, + }, + 1 => { + Name => 'MinorVersion', + Format => 'undef[4]', + ValueConv => 'sprintf("%x.%x.%x", unpack("nCC", $val))', + }, + 2 => { + Name => 'CompatibleBrands', + Format => 'undef[$size-8]', + # ignore any entry with a null, and return others as a list + ValueConv => 'my @a=($val=~/.{4}/sg); @a=grep(!/\0/,@a); \@a', + }, +); + +# proprietary HTC atom (HTC One MP4 video) +%Image::ExifTool::QuickTime::HTCInfo = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + NOTES => 'Tags written by some HTC camera phones.', + slmt => { + Name => 'Unknown_slmt', + Unknown => 1, + Format => 'int32u', # (observed values: 4) + }, +); + +# atoms used in QTIF files +%Image::ExifTool::QuickTime::ImageFile = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Image' }, + NOTES => 'Tags used in QTIF QuickTime Image Files.', + idsc => { + Name => 'ImageDescription', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ImageDesc' }, + }, + idat => { + Name => 'ImageData', + Binary => 1, + }, + iicc => { + Name => 'ICC_Profile', + SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' }, + }, +); + +# image description data block +%Image::ExifTool::QuickTime::ImageDesc = ( + PROCESS_PROC => \&ProcessHybrid, + VARS => { ID_LABEL => 'ID/Index' }, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int16u', + 2 => { + Name => 'CompressorID', + Format => 'string[4]', +# not very useful since this isn't a complete list and name is given below +# # ref http://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html +# PrintConv => { +# cvid => 'Cinepak', +# jpeg => 'JPEG', +# 'smc '=> 'Graphics', +# 'rle '=> 'Animation', +# rpza => 'Apple Video', +# kpcd => 'Kodak Photo CD', +# 'png '=> 'Portable Network Graphics', +# mjpa => 'Motion-JPEG (format A)', +# mjpb => 'Motion-JPEG (format B)', +# SVQ1 => 'Sorenson video, version 1', +# SVQ3 => 'Sorenson video, version 3', +# mp4v => 'MPEG-4 video', +# 'dvc '=> 'NTSC DV-25 video', +# dvcp => 'PAL DV-25 video', +# 'gif '=> 'Compuserve Graphics Interchange Format', +# h263 => 'H.263 video', +# tiff => 'Tagged Image File Format', +# 'raw '=> 'Uncompressed RGB', +# '2vuY'=> "Uncompressed Y'CbCr, 3x8-bit 4:2:2 (2vuY)", +# 'yuv2'=> "Uncompressed Y'CbCr, 3x8-bit 4:2:2 (yuv2)", +# v308 => "Uncompressed Y'CbCr, 8-bit 4:4:4", +# v408 => "Uncompressed Y'CbCr, 8-bit 4:4:4:4", +# v216 => "Uncompressed Y'CbCr, 10, 12, 14, or 16-bit 4:2:2", +# v410 => "Uncompressed Y'CbCr, 10-bit 4:4:4", +# v210 => "Uncompressed Y'CbCr, 10-bit 4:2:2", +# hvc1 => 'HEVC', #PH +# }, + }, + 10 => { + Name => 'VendorID', + Format => 'string[4]', + RawConv => 'length $val ? $val : undef', + PrintConv => \%vendorID, + SeparateTable => 'VendorID', + }, + # 14 - ("Quality" in QuickTime docs) ?? + 16 => 'SourceImageWidth', + 17 => 'SourceImageHeight', + 18 => { Name => 'XResolution', Format => 'fixed32u' }, + 20 => { Name => 'YResolution', Format => 'fixed32u' }, + # 24 => 'FrameCount', # always 1 (what good is this?) + 25 => { + Name => 'CompressorName', + Format => 'string[32]', + # (sometimes this is a Pascal string, and sometimes it is a C string) + RawConv => q{ + $val=substr($val,1,ord($1)) if $val=~/^([\0-\x1f])/ and ord($1)<length($val); + length $val ? $val : undef; + }, + }, + 41 => 'BitDepth', +# +# Observed offsets for child atoms of various CompressorID types: +# +# CompressorID Offset Child atoms +# ----------- ------ ---------------- +# avc1 86 avcC, btrt, colr, pasp, fiel, clap, svcC +# mp4v 86 esds, pasp +# s263 86 d263 +# + btrt => { + Name => 'BitrateInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Bitrate' }, + }, + # Reference for fiel, colr, pasp, clap: + # https://developer.apple.com/library/mac/technotes/tn2162/_index.html#//apple_ref/doc/uid/DTS40013070-CH1-TNTAG9 + fiel => { + Name => 'VideoFieldOrder', + ValueConv => 'join(" ", unpack("C*",$val))', + PrintConv => [{ + 1 => 'Progressive', + 2 => '2:1 Interlaced', + }], + }, + colr => { + Name => 'ColorRepresentation', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ColorRep' }, + }, + pasp => { + Name => 'PixelAspectRatio', + ValueConv => 'join(":", unpack("N*",$val))', + }, + clap => { + Name => 'CleanAperture', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::CleanAperture' }, + }, + avcC => { + # (see http://thompsonng.blogspot.ca/2010/11/mp4-file-format-part-2.html) + Name => 'AVCConfiguration', + Unknown => 1, + Binary => 1, + }, + JPEG => { # (found in CR3 images; used as a flag to identify JpgFromRaw 'vide' stream) + Name => 'JPEGInfo', + # (4 bytes all zero) + Unknown => 1, + Binary => 1, + }, + # hvcC - HEVC configuration + # svcC - 7 bytes: 00 00 00 00 ff e0 00 + # esds - elementary stream descriptor + # d263 + gama => { Name => 'Gamma', Format => 'fixed32u' }, + # mjqt - default quantization table for MJPEG + # mjht - default Huffman table for MJPEG + # csgm ? (seen in hevc video) + CMP1 => { # Canon CR3 + Name => 'CMP1', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CMP1' }, + }, + CDI1 => { # Canon CR3 + Name => 'CDI1', + SubDirectory => { + TagTable => 'Image::ExifTool::Canon::CDI1', + Start => 4, + }, + }, + # JPEG - 4 bytes all 0 (Canon CR3) + # free - (Canon CR3) +# +# spherical video v2 stuff (untested) +# + st3d => { + Name => 'Stereoscopic3D', + Format => 'int8u', + ValueConv => '$val =~ s/.* //; $val', # (remove leading version/flags bytes?) + PrintConv => { + 0 => 'Monoscopic', + 1 => 'Stereoscopic Top-Bottom', + 2 => 'Stereoscopic Left-Right', + 3 => 'Stereoscopic Stereo-Custom', + 4 => 'Stereoscopic Right-Left', + }, + }, + sv3d => { + Name => 'SphericalVideo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::sv3d' }, + }, +); + +# 'sv3d' atom information (ref https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md) +%Image::ExifTool::QuickTime::sv3d = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + NOTES => q{ + Tags defined by the Spherical Video V2 specification. See + L<https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md> + for the specification. + }, + svhd => { + Name => 'MetadataSource', + Format => 'undef', + ValueConv => '$val=~tr/\0//d; $val', # (remove version/flags? and terminator?) + }, + proj => { + Name => 'Projection', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::proj' }, + }, +); + +# 'proj' atom information (ref https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md) +%Image::ExifTool::QuickTime::proj = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + prhd => { + Name => 'ProjectionHeader', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::prhd' }, + }, + cbmp => { + Name => 'CubemapProj', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::cbmp' }, + }, + equi => { + Name => 'EquirectangularProj', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::equi' }, + }, + # mshp - MeshProjection (P.I.T.A. to decode, for not much reward, see ref) +); + +# 'prhd' atom information (ref https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md) +%Image::ExifTool::QuickTime::prhd = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'fixed32s', + # 0 - version (high 8 bits) / flags (low 24 bits) + 1 => 'PoseYawDegrees', + 2 => 'PosePitchDegrees', + 3 => 'PoseRollDegrees', +); + +# 'cbmp' atom information (ref https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md) +%Image::ExifTool::QuickTime::cbmp = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + # 0 - version (high 8 bits) / flags (low 24 bits) + 1 => 'Layout', + 2 => 'Padding', +); + +# 'equi' atom information (ref https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md) +%Image::ExifTool::QuickTime::equi = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', # (actually 0.32 fixed point) + # 0 - version (high 8 bits) / flags (low 24 bits) + 1 => { Name => 'ProjectionBoundsTop', ValueConv => '$val / 4294967296' }, + 2 => { Name => 'ProjectionBoundsBottom',ValueConv => '$val / 4294967296' }, + 3 => { Name => 'ProjectionBoundsLeft', ValueConv => '$val / 4294967296' }, + 4 => { Name => 'ProjectionBoundsRight', ValueConv => '$val / 4294967296' }, +); + +# 'btrt' atom information (ref http://lists.freedesktop.org/archives/gstreamer-commits/2011-October/054459.html) +%Image::ExifTool::QuickTime::Bitrate = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + PRIORITY => 0, # often filled with zeros + 0 => 'BufferSize', + 1 => 'MaxBitrate', + 2 => 'AverageBitrate', +); + +# 'clap' atom information (ref https://developer.apple.com/library/mac/technotes/tn2162/_index.html#//apple_ref/doc/uid/DTS40013070-CH1-TNTAG9) +%Image::ExifTool::QuickTime::CleanAperture = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'rational64s', + 0 => 'CleanApertureWidth', + 1 => 'CleanApertureHeight', + 2 => 'CleanApertureOffsetX', + 3 => 'CleanApertureOffsetY', +); + +# preview data block +%Image::ExifTool::QuickTime::Preview = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 2 => 'Image' }, + FORMAT => 'int16u', + 0 => { + Name => 'PreviewDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + %timeInfo, + }, + 2 => 'PreviewVersion', + 3 => { + Name => 'PreviewAtomType', + Format => 'string[4]', + }, + 5 => 'PreviewAtomIndex', +); + +# movie atoms +%Image::ExifTool::QuickTime::Movie = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 2 => 'Video' }, + mvhd => { + Name => 'MovieHeader', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MovieHeader' }, + }, + trak => { + Name => 'Track', + CanCreate => 0, # don't create this atom + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Track' }, + }, + udta => { + Name => 'UserData', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::UserData' }, + }, + meta => { # 'meta' is found here in my EX-F1 MOV sample - PH + Name => 'Meta', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Meta' }, + }, + iods => { + Name => 'InitialObjectDescriptor', + Flags => ['Binary','Unknown'], + }, + uuid => [ + { #11 (MP4 files) (also found in QuickTime::Track) + Name => 'UUID-USMT', + Condition => '$$valPt=~/^USMT!\xd2\x4f\xce\xbb\x88\x69\x5c\xfa\xc9\xc7\x40/', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::UserMedia', + Start => 16, + }, + }, + { #PH (Canon SX280) + Name => 'UUID-Canon', + Condition => '$$valPt=~/^\x85\xc0\xb6\x87\x82\x0f\x11\xe0\x81\x11\xf4\xce\x46\x2b\x6a\x48/', + SubDirectory => { + TagTable => 'Image::ExifTool::Canon::uuid', + Start => 16, + }, + }, + { + Name => 'GarminGPS', + Condition => q{ + $$valPt=~/^\x9b\x63\x0f\x8d\x63\x74\x40\xec\x82\x04\xbc\x5f\xf5\x09\x17\x28/ and + $$self{OPTIONS}{ExtractEmbedded} + }, + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&ProcessGarminGPS, + }, + }, + { + Name => 'GarminGPS', + Condition => '$$valPt=~/^\x9b\x63\x0f\x8d\x63\x74\x40\xec\x82\x04\xbc\x5f\xf5\x09\x17\x28/', + Notes => 'Garmin GPS sensor data', + RawConv => q{ + $self->WarnOnce('Use the ExtractEmbedded option to decode timed Garmin GPS',3); + return \$val; + }, + }, + { + Name => 'UUID-Unknown', + %unknownInfo, + }, + ], + cmov => { + Name => 'CompressedMovie', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::CMovie' }, + }, + htka => { # (written by HTC One M8 in slow-motion 1280x720 video - PH) + Name => 'HTCTrack', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Track' }, + }, + 'gps ' => { # GPS data written by Novatek cameras (parsed in QuickTimeStream.pl) + Name => 'GPSDataList', + Unknown => 1, + Binary => 1, + }, + meco => { #ISO14496-12:2015 + Name => 'OtherMeta', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::OtherMeta' }, + }, + # prfl - Profile (ref 12) + # clip - clipping --> contains crgn (clip region) (ref 12) + # mvex - movie extends --> contains mehd (movie extends header), trex (track extends) (ref 14) + # ICAT - 4 bytes: "6350" (Nikon CoolPix S6900), "6500" (Panasonic FT7) +); + +# (ref CFFMediaFormat-2_1.pdf) +%Image::ExifTool::QuickTime::MovieFragment = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 2 => 'Video' }, + mfhd => { + Name => 'MovieFragmentHeader', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MovieFragHdr' }, + }, + traf => { + Name => 'TrackFragment', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::TrackFragment' }, + }, + meta => { #ISO14496-12:2015 + Name => 'Meta', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Meta' }, + }, +); + +# (ref CFFMediaFormat-2_1.pdf) +%Image::ExifTool::QuickTime::MovieFragHdr = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + 1 => 'MovieFragmentSequence', +); + +# (ref CFFMediaFormat-2_1.pdf) +%Image::ExifTool::QuickTime::TrackFragment = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 2 => 'Video' }, + meta => { #ISO14496-12:2015 + Name => 'Meta', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Meta' }, + }, + # tfhd - track fragment header + # edts - edits --> contains elst (edit list) (ref PH) + # tfdt - track fragment base media decode time + # trik - trick play box + # trun - track fragment run box + # avcn - AVC NAL unit storage box + # secn - sample encryption box + # saio - sample auxiliary information offsets box + # sbgp - sample to group box + # sgpd - sample group description box + # sdtp - independent and disposable samples (ref 5) + # subs - sub-sample information (ref 5) +); + +# movie header data block +%Image::ExifTool::QuickTime::MovieHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + DATAMEMBER => [ 0, 1, 2, 3, 4 ], + 0 => { + Name => 'MovieHeaderVersion', + Format => 'int8u', + RawConv => '$$self{MovieHeaderVersion} = $val', + }, + 1 => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + %timeInfo, + RawConv => q{ + my $offset = (66 * 365 + 17) * 24 * 3600; + if ($val >= $offset or $$self{OPTIONS}{QuickTimeUTC}) { + $val -= $offset; + } elsif ($val and not $$self{IsWriting}) { + $self->WarnOnce('Patched incorrect time zero for QuickTime date/time tag',1); + } + return $$self{CreateDate} = $val; + }, + # this is int64u if MovieHeaderVersion == 1 (ref 13) + Hook => '$$self{MovieHeaderVersion} and $format = "int64u", $varSize += 4', + }, + 2 => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + %timeInfo, + # this is int64u if MovieHeaderVersion == 1 (ref 13) + Hook => '$$self{MovieHeaderVersion} and $format = "int64u", $varSize += 4', + }, + 3 => { + Name => 'TimeScale', + RawConv => '$$self{TimeScale} = $val', + }, + 4 => { + Name => 'Duration', + %durationInfo, + # this is int64u if MovieHeaderVersion == 1 (ref 13) + Hook => '$$self{MovieHeaderVersion} and $format = "int64u", $varSize += 4', + }, + 5 => { + Name => 'PreferredRate', + ValueConv => '$val / 0x10000', + }, + 6 => { + Name => 'PreferredVolume', + Format => 'int16u', + ValueConv => '$val / 256', + PrintConv => 'sprintf("%.2f%%", $val * 100)', + }, + 9 => { + Name => 'MatrixStructure', + Format => 'fixed32s[9]', + # (the right column is fixed 2.30 instead of 16.16) + ValueConv => q{ + my @a = split ' ',$val; + $_ /= 0x4000 foreach @a[2,5,8]; + return "@a"; + }, + }, + 18 => { Name => 'PreviewTime', %durationInfo }, + 19 => { Name => 'PreviewDuration', %durationInfo }, + 20 => { Name => 'PosterTime', %durationInfo }, + 21 => { Name => 'SelectionTime', %durationInfo }, + 22 => { Name => 'SelectionDuration',%durationInfo }, + 23 => { Name => 'CurrentTime', %durationInfo }, + 24 => 'NextTrackID', +); + +# track atoms +%Image::ExifTool::QuickTime::Track = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 1 => 'Track#', 2 => 'Video' }, + tkhd => { + Name => 'TrackHeader', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::TrackHeader' }, + }, + udta => { + Name => 'UserData', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::UserData' }, + }, + mdia => { #MP4 + Name => 'Media', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Media' }, + }, + meta => { #PH (MOV) + Name => 'Meta', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Meta' }, + }, + tref => { + Name => 'TrackRef', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::TrackRef' }, + }, + tapt => { + Name => 'TrackAperture', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::TrackAperture' }, + }, + uuid => [ + { #11 (MP4 files) (also found in QuickTime::Movie) + Name => 'UUID-USMT', + Condition => '$$valPt=~/^USMT!\xd2\x4f\xce\xbb\x88\x69\x5c\xfa\xc9\xc7\x40/', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::UserMedia', + Start => 16, + }, + }, + { #https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md + Name => 'SphericalVideoXML', + # (this tag is readable/writable as a block through the Extra SphericalVideoXML tags) + Condition => '$$valPt=~/^\xff\xcc\x82\x63\xf8\x55\x4a\x93\x88\x14\x58\x7a\x02\x52\x1f\xdd/', + WriteGroup => 'GSpherical', # write only GSpherical XMP tags here + HandlerType => 'vide', # only write in video tracks + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::Main', + Start => 16, + ProcessProc => 'Image::ExifTool::XMP::ProcessGSpherical', + WriteProc => 'Image::ExifTool::XMP::WriteGSpherical', + }, + }, + { + Name => 'UUID-Unknown', + %unknownInfo, + }, + ], + meco => { #ISO14492-12:2015 pg 83 + Name => 'OtherMeta', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::OtherMeta' }, + }, + # edts - edits --> contains elst (edit list) + # clip - clipping --> contains crgn (clip region) + # matt - track matt --> contains kmat (compressed matt) + # load - track loading settings + # imap - track input map --> contains ' in' --> contains ' ty', obid + # prfl - Profile (ref 12) +); + +# track header data block +%Image::ExifTool::QuickTime::TrackHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 1 => 'Track#', 2 => 'Video' }, + FORMAT => 'int32u', + DATAMEMBER => [ 0, 1, 2, 5, 7 ], + 0 => { + Name => 'TrackHeaderVersion', + Format => 'int8u', + Priority => 0, + RawConv => '$$self{TrackHeaderVersion} = $val', + }, + 1 => { + Name => 'TrackCreateDate', + Priority => 0, + Groups => { 2 => 'Time' }, + %timeInfo, + # this is int64u if TrackHeaderVersion == 1 (ref 13) + Hook => '$$self{TrackHeaderVersion} and $format = "int64u", $varSize += 4', + }, + 2 => { + Name => 'TrackModifyDate', + Priority => 0, + Groups => { 2 => 'Time' }, + %timeInfo, + # this is int64u if TrackHeaderVersion == 1 (ref 13) + Hook => '$$self{TrackHeaderVersion} and $format = "int64u", $varSize += 4', + }, + 3 => { + Name => 'TrackID', + Priority => 0, + }, + 5 => { + Name => 'TrackDuration', + Priority => 0, + %durationInfo, + # this is int64u if TrackHeaderVersion == 1 (ref 13) + Hook => '$$self{TrackHeaderVersion} and $format = "int64u", $varSize += 4', + }, + 7 => { # (used only for writing MatrixStructure) + Name => 'ImageSizeLookahead', + Hidden => 1, + Format => 'int32u[14]', + RawConv => '$$self{ImageSizeLookahead} = $val; undef', + }, + 8 => { + Name => 'TrackLayer', + Format => 'int16u', + Priority => 0, + }, + 9 => { + Name => 'TrackVolume', + Format => 'int16u', + Priority => 0, + ValueConv => '$val / 256', + PrintConv => 'sprintf("%.2f%%", $val * 100)', + }, + 10 => { + Name => 'MatrixStructure', + Format => 'fixed32s[9]', + Notes => 'writable for the video track via the Composite Rotation tag', + Writable => 1, + Protected => 1, + Permanent => 1, + # only set rotation if image size is non-zero + RawConvInv => \&GetMatrixStructure, + # (the right column is fixed 2.30 instead of 16.16) + ValueConv => q{ + my @a = split ' ',$val; + $_ /= 0x4000 foreach @a[2,5,8]; + return "@a"; + }, + ValueConvInv => q{ + my @a = split ' ',$val; + $_ *= 0x4000 foreach @a[2,5,8]; + return "@a"; + }, + }, + 19 => { + Name => 'ImageWidth', + Priority => 0, + RawConv => \&FixWrongFormat, + }, + 20 => { + Name => 'ImageHeight', + Priority => 0, + RawConv => \&FixWrongFormat, + }, +); + +# user data atoms +%Image::ExifTool::QuickTime::UserData = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + CHECK_PROC => \&CheckQTValue, + GROUPS => { 1 => 'UserData', 2 => 'Video' }, + WRITABLE => 1, + PREFERRED => 1, # (preferred over Keys tags when writing) + FORMAT => 'string', + WRITE_GROUP => 'UserData', + LANG_INFO => \&GetLangInfo, + NOTES => q{ + Tag ID's beginning with the copyright symbol (hex 0xa9) are multi-language + text. Alternate language tags are accessed by adding a dash followed by a + 3-character ISO 639-2 language code to the tag name. ExifTool will extract + any multi-language user data tags found, even if they aren't in this table. + Note when creating new tags, + L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> tags are + preferred over these, so to create the tag when a same-named ItemList tag + exists, either "UserData" must be specified (eg. C<-UserData:Artist=Monet> + on the command line), or the PREFERRED level must be changed via + L<the config file|../config.html#PREF>. + }, + "\xa9cpy" => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + "\xa9day" => { + Name => 'ContentCreateDate', + Groups => { 2 => 'Time' }, + Shift => 'Time', + # handle values in the form "2010-02-12T13:27:14-0800" (written by Apple iPhone) + ValueConv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::ConvertXMPDate($val); + $val =~ s/([-+]\d{2})(\d{2})$/$1:$2/; # add colon to timezone if necessary + return $val; + }, + ValueConvInv => q{ + require Image::ExifTool::XMP; + my $tmp = Image::ExifTool::XMP::FormatXMPDate($val); + ($val = $tmp) =~ s/([-+]\d{2}):(\d{2})$/$1$2/ if defined $tmp; # remove time zone colon + return $val; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,1)', # (add time zone if it didn't exist) + }, + "\xa9ART" => 'Artist', #PH (iTunes 8.0.2) + "\xa9alb" => 'Album', #PH (iTunes 8.0.2) + "\xa9arg" => 'Arranger', #12 + "\xa9ark" => 'ArrangerKeywords', #12 + "\xa9cmt" => 'Comment', #PH (iTunes 8.0.2) + "\xa9cok" => 'ComposerKeywords', #12 + "\xa9com" => 'Composer', #12 + "\xa9dir" => 'Director', #12 + "\xa9ed1" => 'Edit1', + "\xa9ed2" => 'Edit2', + "\xa9ed3" => 'Edit3', + "\xa9ed4" => 'Edit4', + "\xa9ed5" => 'Edit5', + "\xa9ed6" => 'Edit6', + "\xa9ed7" => 'Edit7', + "\xa9ed8" => 'Edit8', + "\xa9ed9" => 'Edit9', + "\xa9fmt" => 'Format', + "\xa9gen" => 'Genre', #PH (iTunes 8.0.2) + "\xa9grp" => 'Grouping', #PH (NC) + "\xa9inf" => 'Information', + "\xa9isr" => 'ISRCCode', #12 + "\xa9lab" => 'RecordLabelName', #12 + "\xa9lal" => 'RecordLabelURL', #12 + "\xa9lyr" => 'Lyrics', #PH (NC) + "\xa9mak" => 'Make', #12 + "\xa9mal" => 'MakerURL', #12 + "\xa9mod" => 'Model', #PH + "\xa9nam" => 'Title', #12 + "\xa9pdk" => 'ProducerKeywords', #12 + "\xa9phg" => 'RecordingCopyright', #12 + "\xa9prd" => 'Producer', + "\xa9prf" => 'Performers', + "\xa9prk" => 'PerformerKeywords', #12 + "\xa9prl" => 'PerformerURL', + "\xa9req" => 'Requirements', + "\xa9snk" => 'SubtitleKeywords', #12 + "\xa9snm" => 'Subtitle', #12 + "\xa9src" => 'SourceCredits', #12 + "\xa9swf" => 'SongWriter', #12 + "\xa9swk" => 'SongWriterKeywords', #12 + "\xa9swr" => 'SoftwareVersion', #12 + "\xa9too" => 'Encoder', #PH (NC) + "\xa9trk" => 'Track', #PH (NC) + "\xa9wrt" => { Name => 'Composer', Avoid => 1 }, # ("\xa9com" is preferred in UserData) + "\xa9xyz" => { #PH (iPhone 3GS) + Name => 'GPSCoordinates', + Groups => { 2 => 'Location' }, + ValueConv => \&ConvertISO6709, + ValueConvInv => \&ConvInvISO6709, + PrintConv => \&PrintGPSCoordinates, + PrintConvInv => \&PrintInvGPSCoordinates, + }, + # \xa9 tags written by DJI Phantom 3: (ref PH) + "\xa9xsp" => 'SpeedX', #PH (guess) + "\xa9ysp" => 'SpeedY', #PH (guess) + "\xa9zsp" => 'SpeedZ', #PH (guess) + "\xa9fpt" => 'Pitch', #PH + "\xa9fyw" => 'Yaw', #PH + "\xa9frl" => 'Roll', #PH + "\xa9gpt" => 'CameraPitch', #PH + "\xa9gyw" => 'CameraYaw', #PH + "\xa9grl" => 'CameraRoll', #PH + "\xa9enc" => 'EncoderID', #PH (forum9271) + # and the following entries don't have the proper 4-byte header for \xa9 tags: + "\xa9dji" => { Name => 'UserData_dji', Format => 'undef', Binary => 1, Unknown => 1, Hidden => 1 }, + "\xa9res" => { Name => 'UserData_res', Format => 'undef', Binary => 1, Unknown => 1, Hidden => 1 }, + "\xa9uid" => { Name => 'UserData_uid', Format => 'undef', Binary => 1, Unknown => 1, Hidden => 1 }, + "\xa9mdl" => { + Name => 'Model', + Notes => 'non-standard-format DJI tag', + Format => 'string', + Avoid => 1, + }, + # end DJI tags + name => 'Name', + WLOC => { + Name => 'WindowLocation', + Format => 'int16u', + }, + LOOP => { + Name => 'LoopStyle', + Format => 'int32u', + PrintConv => { + 1 => 'Normal', + 2 => 'Palindromic', + }, + }, + SelO => { + Name => 'PlaySelection', + Format => 'int8u', + }, + AllF => { + Name => 'PlayAllFrames', + Format => 'int8u', + }, + meta => { + Name => 'Meta', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Meta', + Start => 4, # must skip 4-byte version number header + }, + }, + 'ptv '=> { + Name => 'PrintToVideo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Video' }, + }, + hnti => { + Name => 'HintInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::HintInfo' }, + }, + hinf => { + Name => 'HintTrackInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::HintTrackInfo' }, + }, + hinv => 'HintVersion', #PH (guess) + XMP_ => { #PH (Adobe CS3 Bridge) + Name => 'XMP', + WriteGroup => 'XMP', # (write main tags here) + # *** this is where ExifTool writes XMP in MOV videos (as per XMP spec) *** + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, + # the following are 3gp tags, references: + # http://atomicparsley.sourceforge.net + # http://www.3gpp.org/ftp/tsg_sa/WG4_CODEC/TSGS4_25/Docs/ + # (note that all %langText3gp tags are Avoid => 1) + cprt => { Name => 'Copyright', %langText3gp, Groups => { 2 => 'Author' } }, + auth => { Name => 'Author', %langText3gp, Groups => { 2 => 'Author' } }, + titl => { Name => 'Title', %langText3gp }, + dscp => { Name => 'Description',%langText3gp }, + perf => { Name => 'Performer', %langText3gp }, + gnre => { Name => 'Genre', %langText3gp }, + albm => { Name => 'Album', %langText3gp }, + coll => { Name => 'CollectionName', %langText3gp }, #17 + rtng => { + Name => 'Rating', + Writable => 'undef', + Avoid => 1, + # (4-byte flags, 4-char entity, 4-char criteria, 2-byte lang, string) + IText => 14, # (14 bytes before string) + Notes => 'string in the form "Entity=XXXX Criteria=XXXX XXXXX", used in 3gp videos', + ValueConv => '$val=~s/^(.{4})(.{4})/Entity=$1 Criteria=$2 /i; $val', + ValueConvInv => '$val=~s/Entity=(.{4}) Criteria=(.{4}) ?/$1$2/i; $val', + }, + clsf => { + Name => 'Classification', + Writable => 'undef', + Avoid => 1, + # (4-byte flags, 4-char entity, 2-byte index, 2-byte lang, string) + IText => 12, + Notes => 'string in the form "Entity=XXXX Index=### XXXXX", used in 3gp videos', + ValueConv => '$val=~s/^(.{4})(.{2})/"Entity=$1 Index=".unpack("n",$2)." "/ie; $val', + ValueConvInv => '$val=~s/Entity=(.{4}) Index=(\d+) ?/$1.pack("n",$2)/ie; $val', + }, + kywd => { + Name => 'Keywords', + # (4 byte flags, 2-byte lang, 1-byte count, count x pascal strings, ref 17) + # (but I have also seen a simple string written by iPhone, so don't make writable yet) + Notes => "not writable because Apple doesn't follow the 3gp specification", + RawConv => q{ + my $sep = $self->Options('ListSep'); + return join($sep, split /\0+/, $val) unless $val =~ /^\0/; # (iPhone) + return '<err>' unless length $val >= 7; + my $lang = Image::ExifTool::QuickTime::UnpackLang(Get16u(\$val, 4)); + $lang = $lang ? "($lang) " : ''; + my $num = Get8u(\$val, 6); + my ($i, @vals); + my $pos = 7; + for ($i=0; $i<$num; ++$i) { + last if $pos >= length $val; + my $len = Get8u(\$val, $pos++); + last if $pos + $len > length $val; + my $v = substr($val, $pos, $len); + $v = $self->Decode($v, 'UCS2') if $v =~ /^\xfe\xff/; + push @vals, $v; + $pos += $len; + } + return $lang . join($sep, @vals); + }, + }, + loci => { + Name => 'LocationInformation', + Groups => { 2 => 'Location' }, + Writable => 'undef', + IText => 6, + Avoid => 1, + NoDecode => 1, # (we'll decode the data ourself) + Notes => q{ + string in the form "XXXXX Role=XXX Lat=XXX Lon=XXX Alt=XXX Body=XXX + Notes=XXX", used in 3gp videos + }, + # (4-byte flags, 2-byte lang, location string, 1-byte role, 4-byte fixed longitude, + # 4-byte fixed latitude, 4-byte fixed altitude, body string, notes string) + RawConv => q{ + my $str; + if ($val =~ /^\xfe\xff/) { + $val =~ s/^(\xfe\xff(.{2})*?)\0\0//s or return '<err>'; + $str = $self->Decode($1, 'UCS2'); + } else { + $val =~ s/^(.*?)\0//s or return '<err>'; + $str = $self->Decode($1, 'UTF8'); + } + $str = '(none)' unless length $str; + return '<err>' if length $val < 13; + my $role = Get8u(\$val, 0); + my $lon = GetFixed32s(\$val, 1); + my $lat = GetFixed32s(\$val, 5); + my $alt = GetFixed32s(\$val, 9); + my $roleStr = {0=>'shooting',1=>'real',2=>'fictional',3=>'reserved'}->{$role}; + $str .= ' Role=' . ($roleStr || "unknown($role)"); + $str .= sprintf(' Lat=%.5f Lon=%.5f Alt=%.2f', $lat, $lon, $alt); + $val = substr($val, 13); + if ($val =~ s/^(\xfe\xff(.{2})*?)\0\0//s) { + $str .= ' Body=' . $self->Decode($1, 'UCS2'); + } elsif ($val =~ s/^(.*?)\0//s) { + $str .= ' Body=' . $self->Decode($1, 'UTF8'); + } + if ($val =~ s/^(\xfe\xff(.{2})*?)\0\0//s) { + $str .= ' Notes=' . $self->Decode($1, 'UCS2'); + } elsif ($val =~ s/^(.*?)\0//s) { + $str .= ' Notes=' . $self->Decode($1, 'UTF8'); + } + return $str; + }, + RawConvInv => q{ + my ($role, $lat, $lon, $alt, $body, $note); + $lat = $1 if $val =~ s/ Lat=([-+]?[.\d]+)//i; + $lon = $1 if $val =~ s/ Lon=([-+]?[.\d]+)//i; + $alt = $1 if $val =~ s/ Alt=([-+]?[.\d]+)//i; + $note = $val =~ s/ Notes=(.*)//i ? $1 : ''; + $body = $val =~ s/ Body=(.*)//i ? $1 : ''; + $role = $val =~ s/ Role=(.*)//i ? $1 : ''; + $val = '' if $val eq '(none)'; + $role = {shooting=>0,real=>1,fictional=>2}->{lc $role} || 0; + return $self->Encode($val, 'UTF8') . "\0" . Set8u($role) . + SetFixed32s(defined $lon ? $lon : 999) . + SetFixed32s(defined $lat ? $lat : 999) . + SetFixed32s(defined $alt ? $alt : 0) . + $self->Encode($body) . "\0" . + $self->Encode($note) . "\0"; + }, + }, + yrrc => { + Name => 'Year', + Writable => 'undef', + Groups => { 2 => 'Time' }, + Avoid => 1, + Notes => 'used in 3gp videos', + ValueConv => 'length($val) >= 6 ? unpack("x4n",$val) : "<err>"', + ValueConvInv => 'pack("Nn",0,$val)', + }, + urat => { #17 + Name => 'UserRating', + Writable => 'undef', + Notes => 'used in 3gp videos', + Avoid => 1, + ValueConv => q{ + return '<err>' unless length $val >= 8; + unpack('x7C', $val); + }, + ValueConvInv => 'pack("N2",0,$val)', + }, + # tsel - TrackSelection (ref 17) + # Apple tags (ref 16[dead] -- see ref 25 instead) + angl => { Name => 'CameraAngle', Format => 'string' }, # (NC) + clfn => { Name => 'ClipFileName', Format => 'string' }, # (NC) + clid => { Name => 'ClipID', Format => 'string' }, # (NC) + cmid => { Name => 'CameraID', Format => 'string' }, # (NC) + cmnm => { # (NC) + Name => 'Model', + Description => 'Camera Model Name', + Avoid => 1, + Format => 'string', # (necessary to remove the trailing NULL) + }, + date => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Notes => q{ + Apple Photos has been reported to show a crazy date/time for some MP4 files + containing this tag, but perhaps only if it is missing a time zone + }, #forum10690/11125 + Shift => 'Time', + ValueConv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::ConvertXMPDate($val); + $val =~ s/([-+]\d{2})(\d{2})$/$1:$2/; # add colon to timezone if necessary + return $val; + }, + ValueConvInv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::FormatXMPDate($val); + $val =~ s/([-+]\d{2}):(\d{2})$/$1$2/; # remove time zone colon + return $val; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,1)', # (add time zone if it didn't exist) + }, + manu => { # (SX280) + Name => 'Make', + Avoid => 1, + # (with Canon there are 6 unknown bytes before the model: "\0\0\0\0\x15\xc7") + RawConv => '$val=~s/^\0{4}..//s; $val=~s/\0.*//; $val', + }, + modl => { # (Samsung GT-S8530, Canon SX280) + Name => 'Model', + Description => 'Camera Model Name', + Avoid => 1, + # (with Canon there are 6 unknown bytes before the model: "\0\0\0\0\x15\xc7") + RawConv => '$val=~s/^\0{4}..//s; $val=~s/\0.*//; $val', + }, + reel => { Name => 'ReelName', Format => 'string' }, # (NC) + scen => { Name => 'Scene', Format => 'string' }, # (NC) + shot => { Name => 'ShotName', Format => 'string' }, # (NC) + slno => { Name => 'SerialNumber', Format => 'string' }, # (NC) + apmd => { Name => 'ApertureMode', Format => 'undef' }, #20 + kgtt => { #http://lists.ffmpeg.org/pipermail/ffmpeg-devel-irc/2012-June/000707.html + # 'TrackType' will expand to 'Track#Type' when found inside a track + Name => 'TrackType', + # set flag to process this as international text + # even though the tag ID doesn't start with 0xa9 + IText => 4, # IText with 4-byte header + }, + chpl => { # (Nero chapter list) + Name => 'ChapterList', + ValueConv => \&ConvertChapterList, + PrintConv => \&PrintChapter, + }, + # ndrm - 7 bytes (0 0 0 1 0 0 0) Nero Digital Rights Management? (PH) + # other non-Apple tags (ref 16) + # hpix - HipixRichPicture (ref 16, HIPIX) + # strk - sub-track information (ref 16, ISO) +# +# Manufacturer-specific metadata +# + TAGS => [ #PH + # these tags were initially discovered in a Pentax movie, + # but similar information is found in videos from other manufacturers + { + Name => 'FujiFilmTags', + Condition => '$$valPt =~ /^FUJIFILM DIGITAL CAMERA\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::FujiFilm::MOV', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'KodakTags', + Condition => '$$valPt =~ /^EASTMAN KODAK COMPANY/', + SubDirectory => { + TagTable => 'Image::ExifTool::Kodak::MOV', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'KonicaMinoltaTags', + Condition => '$$valPt =~ /^KONICA MINOLTA DIGITAL CAMERA/', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::MOV1', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'MinoltaTags', + Condition => '$$valPt =~ /^MINOLTA DIGITAL CAMERA/', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::MOV2', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'NikonTags', + Condition => '$$valPt =~ /^NIKON DIGITAL CAMERA\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::MOV', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'OlympusTags1', + Condition => '$$valPt =~ /^OLYMPUS DIGITAL CAMERA\0.{9}\x01\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::MOV1', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'OlympusTags2', + Condition => '$$valPt =~ /^OLYMPUS DIGITAL CAMERA(?!\0.{21}\x0a\0{3})/s', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::MOV2', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'OlympusTags3', + Condition => '$$valPt =~ /^OLYMPUS DIGITAL CAMERA\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::MP4', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'OlympusTags4', + Condition => '$$valPt =~ /^.{16}OLYM\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::MOV3', + Start => 12, + }, + }, + { + Name => 'PentaxTags', + Condition => '$$valPt =~ /^PENTAX DIGITAL CAMERA\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::MOV', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'SamsungTags', + Condition => '$$valPt =~ /^SAMSUNG DIGITAL CAMERA\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::Samsung::MP4', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'SanyoMOV', + Condition => q{ + $$valPt =~ /^SANYO DIGITAL CAMERA\0/ and + $$self{FileType} eq "MOV" + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Sanyo::MOV', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'SanyoMP4', + Condition => q{ + $$valPt =~ /^SANYO DIGITAL CAMERA\0/ and + $$self{FileType} eq "MP4" + }, + SubDirectory => { + TagTable => 'Image::ExifTool::Sanyo::MP4', + ByteOrder => 'LittleEndian', + }, + }, + { + Name => 'UnknownTags', + Unknown => 1, + Binary => 1 + }, + ], + # ---- Canon ---- + CNCV => { Name => 'CompressorVersion', Format => 'string' }, #PH (5D Mark II) + CNMN => { + Name => 'Model', #PH (EOS 550D) + Description => 'Camera Model Name', + Avoid => 1, + Format => 'string', # (necessary to remove the trailing NULL) + }, + CNFV => { Name => 'FirmwareVersion', Format => 'string' }, #PH (EOS 550D) + CNTH => { #PH (PowerShot S95) + Name => 'CanonCNTH', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CNTH' }, + }, + CNOP => { #PH (7DmkII) + Name => 'CanonCNOP', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CNOP' }, + }, + # CNDB - 2112 bytes (550D) + # CNDM - 4 bytes - 0xff,0xd8,0xff,0xd9 (S95) + # CNDG - 10232 bytes, mostly zeros (N100) + # ---- Casio ---- + QVMI => { #PH + Name => 'CasioQVMI', + # Casio stores standard EXIF-format information in MOV videos (eg. EX-S880) + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::Exif::ProcessExif, # (because ProcessMOV is default) + DirName => 'IFD0', + Multi => 0, # (no NextIFD pointer) + Start => 10, + ByteOrder => 'BigEndian', + }, + }, + # ---- FujiFilm ---- + FFMV => { #PH (FinePix HS20EXR) + Name => 'FujiFilmFFMV', + SubDirectory => { TagTable => 'Image::ExifTool::FujiFilm::FFMV' }, + }, + MVTG => { #PH (FinePix HS20EXR) + Name => 'FujiFilmMVTG', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::Exif::ProcessExif, # (because ProcessMOV is default) + DirName => 'IFD0', + Start => 16, + Base => '$start', + ByteOrder => 'LittleEndian', + }, + }, + # ---- Garmin ---- (ref PH) + uuid => [{ + Name => 'GarminSoftware', # (NC) + Condition => '$$valPt =~ /^VIRBactioncamera/', + RawConv => 'substr($val, 16)', + RawConvInv => '"VIRBactioncamera$val"', + },{ + # have seen "28 f3 11 e2 b7 91 4f 6f 94 e2 4f 5d ea cb 3c 01" for RicohThetaZ1 accelerometer RADT data (not yet decoded) + Name => 'UUID-Unknown', + Writable => 0, + %unknownInfo, + }], + pmcc => { + Name => 'GarminSettings', + ValueConv => 'substr($val, 4)', + ValueConvInv => '"\0\0\0\x01$val"', + }, + # hmtp - 412 bytes: "\0\0\0\x01" then maybe "\0\0\0\x64" and the rest zeros + # vrin - 12 bytes: "\0\0\0\x01" followed by 8 bytes of zero + # ---- GoPro ---- (ref PH) + GoPr => 'GoProType', # (Hero3+) + FIRM => { Name => 'FirmwareVersion', Avoid => 1 }, # (Hero4) + LENS => 'LensSerialNumber', # (Hero4) + CAME => { # (Hero4) + Name => 'SerialNumberHash', + Description => 'Camera Serial Number Hash', + ValueConv => 'unpack("H*",$val)', + ValueConvInv => 'pack("H*",$val)', + }, + # SETT? 12 bytes (Hero4) + # MUID? 32 bytes (Hero4, starts with serial number hash) + # HMMT? 404 bytes (Hero4, all zero) + # BCID? 26 bytes (Hero5, all zero), 36 bytes GoPro Max + # GUMI? 16 bytes (Hero5) + "FOV\0" => 'FieldOfView', #forum8938 (Hero2) seen: "Wide" + GPMF => { + Name => 'GoProGPMF', + SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' }, + }, + # free (all zero) + "\xa9TSC" => 'StartTimeScale', # (Hero6) + "\xa9TSZ" => 'StartTimeSampleSize', # (Hero6) + "\xa9TIM" => 'StartTimecode', #PH (NC) + # --- HTC ---- + htcb => { + Name => 'HTCBinary', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::HTCBinary' }, + }, + # ---- Kodak ---- + DcMD => { + Name => 'KodakDcMD', + SubDirectory => { TagTable => 'Image::ExifTool::Kodak::DcMD' }, + }, + SNum => { Name => 'SerialNumber', Avoid => 1, Groups => { 2 => 'Camera' } }, + ptch => { Name => 'Pitch', Format => 'rational64s', Avoid => 1 }, # Units?? + _yaw => { Name => 'Yaw', Format => 'rational64s', Avoid => 1 }, # Units?? + roll => { Name => 'Roll', Format => 'rational64s', Avoid => 1 }, # Units?? + _cx_ => { Name => 'CX', Format => 'rational64s', Unknown => 1 }, + _cy_ => { Name => 'CY', Format => 'rational64s', Unknown => 1 }, + rads => { Name => 'Rads', Format => 'rational64s', Unknown => 1 }, + lvlm => { Name => 'LevelMeter', Format => 'rational64s', Unknown => 1 }, # (guess) + Lvlm => { Name => 'LevelMeter', Format => 'rational64s', Unknown => 1 }, # (guess) + pose => { Name => 'pose', SubDirectory => { TagTable => 'Image::ExifTool::Kodak::pose' } }, + # AMBA => Ambarella AVC atom (unknown data written by Kodak Playsport video cam) + # tmlp - 1 byte: 0 (PixPro SP360/4KVR360) + # pivi - 72 bytes (PixPro SP360) + # pive - 12 bytes (PixPro SP360) + # loop - 4 bytes: 0 0 0 0 (PixPro 4KVR360) + # m cm - 2 bytes: 0 0 (PixPro 4KVR360) + # m ev - 2 bytes: 0 0 (PixPro SP360/4KVR360) (exposure comp?) + # m vr - 2 bytes: 0 1 (PixPro 4KVR360) (virtual reality?) + # m wb - 4 bytes: 0 0 0 0 (PixPro SP360/4KVR360) (white balance?) + # mclr - 4 bytes: 0 0 0 0 (PixPro SP360/4KVR360) + # mmtr - 4 bytes: 0,6 0 0 0 (PixPro SP360/4KVR360) + # mflr - 4 bytes: 0 0 0 0 (PixPro SP360) + # lvlm - 24 bytes (PixPro SP360) + # Lvlm - 24 bytes (PixPro 4KVR360) + # ufdm - 4 bytes: 0 0 0 1 (PixPro SP360) + # mtdt - 1 byte: 0 (PixPro SP360/4KVR360) + # gdta - 75240 bytes (PixPro SP360) + # EIS1 - 4 bytes: 03 07 00 00 (PixPro 4KVR360) + # EIS2 - 4 bytes: 04 97 00 00 (PixPro 4KVR360) + # ---- LG ---- + adzc => { Name => 'Unknown_adzc', Unknown => 1, Hidden => 1, %langText }, # "false\0/","true\0/" + adze => { Name => 'Unknown_adze', Unknown => 1, Hidden => 1, %langText }, # "false\0/" + adzm => { Name => 'Unknown_adzm', Unknown => 1, Hidden => 1, %langText }, # "\x0e\x04/","\x10\x06" + # ---- Microsoft ---- + Xtra => { #PH (microsoft) + Name => 'MicrosoftXtra', + WriteGroup => 'Microsoft', + SubDirectory => { + DirName => 'Microsoft', + TagTable => 'Image::ExifTool::Microsoft::Xtra', + }, + }, + # ---- Minolta ---- + MMA0 => { #PH (DiMage 7Hi) + Name => 'MinoltaMMA0', + SubDirectory => { TagTable => 'Image::ExifTool::Minolta::MMA' }, + }, + MMA1 => { #PH (Dimage A2) + Name => 'MinoltaMMA1', + SubDirectory => { TagTable => 'Image::ExifTool::Minolta::MMA' }, + }, + # ---- Nikon ---- + NCDT => { #PH + Name => 'NikonNCDT', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::NCDT' }, + }, + # ---- Olympus ---- + scrn => { #PH (TG-810) + Name => 'OlympusPreview', + Condition => '$$valPt =~ /^.{4}\xff\xd8\xff\xdb/s', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::scrn' }, + }, + # ---- Panasonic/Leica ---- + PANA => { #PH + Name => 'PanasonicPANA', + SubDirectory => { TagTable => 'Image::ExifTool::Panasonic::PANA' }, + }, + LEIC => { #PH + Name => 'LeicaLEIC', + SubDirectory => { TagTable => 'Image::ExifTool::Panasonic::PANA' }, + }, + # ---- Pentax ---- + thmb => [ # (apparently defined by 3gpp, ref 16) + { #PH (Pentax Q) + Name => 'MakerNotePentax5a', + Condition => '$$valPt =~ /^PENTAX \0II/', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + ProcessProc => \&Image::ExifTool::Exif::ProcessExif, # (because ProcessMOV is default) + Start => 10, + Base => '$start - 10', + ByteOrder => 'LittleEndian', + }, + },{ #PH (TG-810) + Name => 'OlympusThumbnail', + Condition => '$$valPt =~ /^.{4}\xff\xd8\xff\xdb/s', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::thmb' }, + },{ #17 (format is in bytes 3-7) + Name => 'ThumbnailImage', + Condition => '$$valPt =~ /^.{8}\xff\xd8\xff[\xdb\xe0]/s', + Groups => { 2 => 'Preview' }, + RawConv => 'substr($val, 8)', + Binary => 1, + },{ #17 (format is in bytes 3-7) + Name => 'ThumbnailPNG', + Condition => '$$valPt =~ /^.{8}\x89PNG\r\n\x1a\n/s', + Groups => { 2 => 'Preview' }, + RawConv => 'substr($val, 8)', + Binary => 1, + },{ + Name => 'UnknownThumbnail', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + ], + PENT => { #PH + Name => 'PentaxPENT', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::PENT', + ByteOrder => 'LittleEndian', + }, + }, + PXTH => { #PH (Pentax K-01) + Name => 'PentaxPreview', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::PXTH' }, + }, + PXMN => [{ #PH (Pentax K-01) + Name => 'MakerNotePentax5b', + Condition => '$$valPt =~ /^PENTAX \0MM/', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + ProcessProc => \&Image::ExifTool::Exif::ProcessExif, # (because ProcessMOV is default) + Start => 10, + Base => '$start - 10', + ByteOrder => 'BigEndian', + }, + },{ #PH (Pentax 645Z) + Name => 'MakerNotePentax5c', + Condition => '$$valPt =~ /^PENTAX \0II/', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::Main', + ProcessProc => \&Image::ExifTool::Exif::ProcessExif, # (because ProcessMOV is default) + Start => 10, + Base => '$start - 10', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'MakerNotePentaxUnknown', + Binary => 1, + }], + # ---- Ricoh ---- + RTHU => { #PH (GR) + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + RawConv => '$self->ValidateImage(\$val, $tag)', + }, + RMKN => { #PH (GR) + Name => 'RicohRMKN', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, # (because ProcessMOV is default) + }, + }, + '@mak' => { Name => 'Make', Avoid => 1 }, + '@mod' => { Name => 'Model', Avoid => 1 }, + '@swr' => { Name => 'SoftwareVersion', Avoid => 1 }, + '@day' => { + Name => 'ContentCreateDate', + Notes => q{ + some stupid Ricoh programmer used the '@' symbol instead of the copyright + symbol in these tag ID's for the Ricoh Theta Z1 and maybe other models + }, + Groups => { 2 => 'Time' }, + Shift => 'Time', + Avoid => 1, + # handle values in the form "2010-02-12T13:27:14-0800" + ValueConv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::ConvertXMPDate($val); + $val =~ s/([-+]\d{2})(\d{2})$/$1:$2/; # add colon to timezone if necessary + return $val; + }, + ValueConvInv => q{ + require Image::ExifTool::XMP; + my $tmp = Image::ExifTool::XMP::FormatXMPDate($val); + ($val = $tmp) =~ s/([-+]\d{2}):(\d{2})$/$1$2/ if defined $tmp; # remove time zone colon + return $val; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,1)', # (add time zone if it didn't exist) + }, + '@xyz' => { #PH (iPhone 3GS) + Name => 'GPSCoordinates', + Groups => { 2 => 'Location' }, + Avoid => 1, + ValueConv => \&ConvertISO6709, + ValueConvInv => \&ConvInvISO6709, + PrintConv => \&PrintGPSCoordinates, + PrintConvInv => \&PrintInvGPSCoordinates, + }, + # RDT1 - pairs of int32u_BE, starting at byte 8: "458275 471846" + # RDT2 - pairs of int32u_BE, starting at byte 8: "472276 468526" + # RDT3 - pairs of int32u_BE, starting at byte 8: "876603 482191" + # RDT4 - pairs of int32u_BE, starting at byte 8: "1955 484612" + # RDT6 - empty + # RDT7 - empty + # RDT8 - empty + # RDT9 - only 16-byte header? + # the boxes below all have a similar header (little-endian): + # 0 int32u - number of records + # 4 ? - "1e 00" + # 6 int16u - record length in bytes + # 8 ? - "23 01 00 00 00 00 00 00" + # 16 - start of records (each record ends in an int64u timestamp "ts" in ns) + # RDTA - float[4],ts: "-0.31289672 -0.2245330 11.303817 0 775.780" + # RDTB - float[4],ts: "-0.04841613 -0.2166595 0.0724792 0 775.780" + # RDTC - float[4],ts: "27.60925 -27.10037 -13.27285 0 775.829" + # RDTD - int16s[3],ts: "353 -914 16354 0 775.829" + # RDTG - ts: "775.825" + # RDTI - float[4],ts: "0.00165951 0.005770059 0.06838259 0.1744695 775.862" + # ---- Samsung ---- + vndr => 'Vendor', #PH (Samsung PL70) + SDLN => 'PlayMode', #PH (NC, Samsung ST80 "SEQ_PLAY") + INFO => { + Name => 'SamsungINFO', + SubDirectory => { TagTable => 'Image::ExifTool::Samsung::INFO' }, + }, + '@sec' => { #PH (Samsung WB30F) + Name => 'SamsungSec', + SubDirectory => { TagTable => 'Image::ExifTool::Samsung::sec' }, + }, + 'smta' => { #PH (Samsung SM-C101) + Name => 'SamsungSmta', + SubDirectory => { + TagTable => 'Image::ExifTool::Samsung::smta', + Start => 4, + }, + }, + cver => 'CodeVersion', #PH (guess, Samsung MV900F) + # ducp - 4 bytes all zero (Samsung ST96,WB750), 52 bytes all zero (Samsung WB30F) + # edli - 52 bytes all zero (Samsung WB30F) + # @etc - 4 bytes all zero (Samsung WB30F) + # saut - 4 bytes all zero (Samsung SM-N900T) + # smrd - string "TRUEBLUE" (Samsung SM-C101) + # ---- TomTom Bandit Action Cam ---- + TTMD => { + Name => 'TomTomMetaData', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::TomTom' }, + }, + # ---- Samsung Gear 360 ---- + vrot => { + Name => 'AccelerometerData', + Notes => q{ + accelerometer readings for each frame of the video, expressed as sets of + yaw, pitch and roll angles in degrees + }, + Format => 'rational64s', + ValueConv => '$val =~ s/^-?\d+ //; \$val', # (ignore leading version/size words) + }, + # m360 - 8 bytes "0 0 0 0 0 0 0 1" + # opax - 164 bytes unknown (center and affine arrays? ref 26) + # opai - 32 bytes (maybe contains a serial number starting at byte 16? - PH) (rgb gains, degamma, gamma? ref 26) + # intv - 16 bytes all zero + # ---- Xaiomi ---- + mcvr => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + # ---- Unknown ---- + # CDET - 128 bytes (unknown origin) + # mtyp - 4 bytes all zero (some drone video) + # kgrf - 8 bytes all zero ? (in udta inside trak atom) + # kgcg - 128 bytes 0's and 1's + # kgsi - 4 bytes "00 00 00 80" + # FIEL - 18 bytes "FIEL\0\x01\0\0\0..." +# +# other 3rd-party tags +# (ref http://code.google.com/p/mp4parser/source/browse/trunk/isoparser/src/main/resources/isoparser-default.properties?r=814) +# + ccid => 'ContentID', + icnu => 'IconURI', + infu => 'InfoURL', + cdis => 'ContentDistributorID', + albr => { Name => 'AlbumArtist', Groups => { 2 => 'Author' } }, + cvru => 'CoverURI', + lrcu => 'LyricsURI', + + tags => { # found in Audible .m4b audio books (ref PH) + Name => 'Audible_tags', + SubDirectory => { TagTable => 'Image::ExifTool::Audible::tags' }, + }, +); + +# Unknown information stored in HTC One (M8) videos - PH +%Image::ExifTool::QuickTime::HTCBinary = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 1 => 'HTC', 2 => 'Video' }, + TAG_PREFIX => 'HTCBinary', + FORMAT => 'int32u', + FIRST_ENTRY => 0, + # 0 - values: 1 + # 1 - values: 0 + # 2 - values: 0 + # 3 - values: FileSize minus 12 (why?) + # 4 - values: 12 +); + +# TomTom Bandit Action Cam metadata (ref PH) +%Image::ExifTool::QuickTime::TomTom = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + NOTES => 'Tags found in TomTom Bandit Action Cam MP4 videos.', + TTAD => { + Name => 'TomTomAD', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&Image::ExifTool::QuickTime::ProcessTTAD, + }, + }, + TTHL => { Name => 'TomTomHL', Binary => 1, Unknown => 1 }, # (mostly zeros) + # (TTID values are different for each video) + TTID => { Name => 'TomTomID', ValueConv => 'unpack("x4H*",$val)' }, + TTVI => { Name => 'TomTomVI', Format => 'int32u', Unknown => 1 }, # seen: "0 1 61 508 508" + # TTVD seen: "normal 720p 60fps 60fps 16/9 wide 1x" + TTVD => { Name => 'TomTomVD', ValueConv => 'my @a = ($val =~ /[\x20-\x7f]+/g); "@a"' }, +); + +# User-specific media data atoms (ref 11) +%Image::ExifTool::QuickTime::UserMedia = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + MTDT => { + Name => 'MetaData', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MetaData' }, + }, +); + +# User-specific media data atoms (ref 11) +%Image::ExifTool::QuickTime::MetaData = ( + PROCESS_PROC => \&ProcessMetaData, + GROUPS => { 2 => 'Video' }, + TAG_PREFIX => 'MetaData', + 0x01 => 'Title', + 0x03 => { + Name => 'ProductionDate', + Groups => { 2 => 'Time' }, + Shift => 'Time', + Writable => 1, + Permanent => 1, + DelValue => '0000/00/00 00:00:00', + # translate from format "YYYY/mm/dd HH:MM:SS" + ValueConv => '$val=~tr{/}{:}; $val', + ValueConvInv => '$val=~s[^(\d{4}):(\d{2}):][$1/$2/]; $val', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + 0x04 => 'Software', + 0x05 => 'Product', + 0x0a => { + Name => 'TrackProperty', + RawConv => 'my @a=unpack("Nnn",$val); "@a"', + PrintConv => [ + { 0 => 'No presentation', BITMASK => { 0 => 'Main track' } }, + { 0 => 'No attributes', BITMASK => { 15 => 'Read only' } }, + '"Priority $val"', + ], + }, + 0x0b => { + Name => 'TimeZone', + Groups => { 2 => 'Time' }, + Writable => 1, + Permanent => 1, + DelValue => 0, + RawConv => 'Get16s(\$val,0)', + RawConvInv => 'Set16s($val)', + PrintConv => 'TimeZoneString($val)', + PrintConvInv => q{ + return undef unless $val =~ /^([-+])(\d{1,2}):?(\d{2})$/' + my $tzmin = $2 * 60 + $3; + $tzmin = -$tzmin if $1 eq '-'; + return $tzmin; + } + }, + 0x0c => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + Shift => 'Time', + Writable => 1, + Permanent => 1, + DelValue => '0000/00/00 00:00:00', + # translate from format "YYYY/mm/dd HH:MM:SS" + ValueConv => '$val=~tr{/}{:}; $val', + ValueConvInv => '$val=~s[^(\d{4}):(\d{2}):][$1/$2/]; $val', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, +); + +# compressed movie atoms (ref http://wiki.multimedia.cx/index.php?title=QuickTime_container#cmov) +%Image::ExifTool::QuickTime::CMovie = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + dcom => 'Compression', + # cmvd - compressed moov atom data +); + +# Profile atoms (ref 11) +%Image::ExifTool::QuickTime::Profile = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + FPRF => { + Name => 'FileGlobalProfile', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::FileProf' }, + }, + APRF => { + Name => 'AudioProfile', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::AudioProf' }, + }, + VPRF => { + Name => 'VideoProfile', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::VideoProf' }, + }, + OLYM => { #PH + Name => 'OlympusOLYM', + SubDirectory => { + TagTable => 'Image::ExifTool::Olympus::OLYM', + ByteOrder => 'BigEndian', + }, + }, +); + +# FPRF atom information (ref 11) +%Image::ExifTool::QuickTime::FileProf = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + 0 => { Name => 'FileProfileVersion', Unknown => 1 }, # unknown = uninteresting + 1 => { + Name => 'FileFunctionFlags', + PrintConv => { BITMASK => { + 28 => 'Fragmented', + 29 => 'Additional tracks', + 30 => 'Edited', # (main AV track is edited) + }}, + }, + # 2 - reserved +); + +# APRF atom information (ref 11) +%Image::ExifTool::QuickTime::AudioProf = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + FORMAT => 'int32u', + 0 => { Name => 'AudioProfileVersion', Unknown => 1 }, + 1 => 'AudioTrackID', + 2 => { + Name => 'AudioCodec', + Format => 'undef[4]', + }, + 3 => { + Name => 'AudioCodecInfo', + Unknown => 1, + PrintConv => 'sprintf("0x%.4x", $val)', + }, + 4 => { + Name => 'AudioAttributes', + PrintConv => { BITMASK => { + 0 => 'Encrypted', + 1 => 'Variable bitrate', + 2 => 'Dual mono', + }}, + }, + 5 => { + Name => 'AudioAvgBitrate', + ValueConv => '$val * 1000', + PrintConv => 'ConvertBitrate($val)', + }, + 6 => { + Name => 'AudioMaxBitrate', + ValueConv => '$val * 1000', + PrintConv => 'ConvertBitrate($val)', + }, + 7 => 'AudioSampleRate', + 8 => 'AudioChannels', +); + +# VPRF atom information (ref 11) +%Image::ExifTool::QuickTime::VideoProf = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + 0 => { Name => 'VideoProfileVersion', Unknown => 1 }, + 1 => 'VideoTrackID', + 2 => { + Name => 'VideoCodec', + Format => 'undef[4]', + }, + 3 => { + Name => 'VideoCodecInfo', + Unknown => 1, + PrintConv => 'sprintf("0x%.4x", $val)', + }, + 4 => { + Name => 'VideoAttributes', + PrintConv => { BITMASK => { + 0 => 'Encrypted', + 1 => 'Variable bitrate', + 2 => 'Variable frame rate', + 3 => 'Interlaced', + }}, + }, + 5 => { + Name => 'VideoAvgBitrate', + ValueConv => '$val * 1000', + PrintConv => 'ConvertBitrate($val)', + }, + 6 => { + Name => 'VideoMaxBitrate', + ValueConv => '$val * 1000', + PrintConv => 'ConvertBitrate($val)', + }, + 7 => { + Name => 'VideoAvgFrameRate', + Format => 'fixed32u', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + 8 => { + Name => 'VideoMaxFrameRate', + Format => 'fixed32u', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + 9 => { + Name => 'VideoSize', + Format => 'int16u[2]', + PrintConv => '$val=~tr/ /x/; $val', + }, + 10 => { + Name => 'PixelAspectRatio', + Format => 'int16u[2]', + PrintConv => '$val=~tr/ /:/; $val', + }, +); + +# meta atoms +%Image::ExifTool::QuickTime::Meta = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 1 => 'Meta', 2 => 'Video' }, + ilst => { + Name => 'ItemList', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::ItemList', + HasData => 1, # process atoms as containers with 'data' elements + }, + }, + # MP4 tags (ref 5) + hdlr => { + Name => 'Handler', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Handler' }, + }, + dinf => { + Name => 'DataInfo', # (don't change this name -- used to recognize directory when writing) + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::DataInfo' }, + }, + ipmc => { + Name => 'IPMPControl', + Flags => ['Binary','Unknown'], + }, + iloc => { + Name => 'ItemLocation', + RawConv => \&ParseItemLocation, + WriteHook => \&ParseItemLocation, + Notes => 'parsed, but not extracted as a tag', + }, + ipro => { + Name => 'ItemProtection', + Flags => ['Binary','Unknown'], + }, + iinf => [{ + Name => 'ItemInformation', + Condition => '$$valPt =~ /^\0/', # (check for version 0) + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::ItemInfo', + Start => 6, # (4-byte version/flags + 2-byte count) + }, + },{ + Name => 'ItemInformation', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::ItemInfo', + Start => 8, # (4-byte version/flags + 4-byte count) + }, + }], + 'xml ' => { + Name => 'XML', + Flags => [ 'Binary', 'Protected' ], + SubDirectory => { + TagTable => 'Image::ExifTool::XMP::XML', + IgnoreProp => { NonRealTimeMeta => 1 }, # ignore container for Sony 'nrtm' + }, + }, + 'keys' => { + Name => 'Keys', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Keys' }, + }, + bxml => { + Name => 'BinaryXML', + Flags => ['Binary','Unknown'], + }, + pitm => [{ + Name => 'PrimaryItemReference', + Condition => '$$valPt =~ /^\0/', # (version 0?) + RawConv => '$$self{PrimaryItem} = unpack("x4n",$val)', + WriteHook => sub { my ($val,$et) = @_; $$et{PrimaryItem} = unpack("x4n",$val); }, + },{ + Name => 'PrimaryItemReference', + RawConv => '$$self{PrimaryItem} = unpack("x4N",$val)', + WriteHook => sub { my ($val,$et) = @_; $$et{PrimaryItem} = unpack("x4N",$val); }, + }], + free => { #PH + Name => 'Free', + Flags => ['Binary','Unknown'], + }, + iprp => { + Name => 'ItemProperties', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ItemProp' }, + }, + iref => { + Name => 'ItemReference', + # the version is needed to parse some of the item references + Condition => '$$self{ItemRefVersion} = ord($$valPt); 1', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::ItemRef', + Start => 4, + }, + }, + idat => { + Name => 'MetaImageSize', #PH (NC) + Format => 'int16u', + # (don't know what the first two numbers are for) + PrintConv => '$val =~ s/^(\d+) (\d+) (\d+) (\d+)/${3}x$4/; $val', + }, + uuid => [ + { #PH (Canon R5/R6 HIF) + Name => 'MetaVersion', # (NC) + Condition => '$$valPt=~/^\x85\xc0\xb6\x87\x82\x0f\x11\xe0\x81\x11\xf4\xce\x46\x2b\x6a\x48/', + RawConv => 'substr($val, 0x14)', + }, + { + Name => 'UUID-Unknown', + %unknownInfo, + }, + ], +); + +# additional metadata container (ref ISO14496-12:2015) +%Image::ExifTool::QuickTime::OtherMeta = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 2 => 'Video' }, + mere => { + Name => 'MetaRelation', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MetaRelation' }, + }, + meta => { + Name => 'Meta', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Meta' }, + }, +); + +# metabox relation (ref ISO14496-12:2015) +%Image::ExifTool::QuickTime::MetaRelation = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + # 0 => 'MetaRelationVersion', + # 1 => 'FirstMetaboxHandlerType', + # 2 => 'FirstMetaboxHandlerType', + # 3 => { Name => 'MetaboxRelation', Format => 'int8u' }, +); + +%Image::ExifTool::QuickTime::ItemProp = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 2 => 'Image' }, + ipco => { + Name => 'ItemPropertyContainer', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ItemPropCont' }, + }, + ipma => { + Name => 'ItemPropertyAssociation', + RawConv => \&ParseItemPropAssoc, + WriteHook => \&ParseItemPropAssoc, + Notes => 'parsed, but not extracted as a tag', + }, +); + +%Image::ExifTool::QuickTime::ItemPropCont = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + PERMANENT => 1, # (can't be deleted) + GROUPS => { 2 => 'Image' }, + VARS => { START_INDEX => 1 }, # show verbose indices starting at 1 + colr => [{ + Name => 'ICC_Profile', + Condition => '$$valPt =~ /^(prof|rICC)/', + Permanent => 0, # (in QuickTime, this writes a zero-length box instead of deleting) + SubDirectory => { + TagTable => 'Image::ExifTool::ICC_Profile::Main', + Start => 4, + }, + },{ + Name => 'ColorRepresentation', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ColorRep' }, + }], + irot => { + Name => 'Rotation', + Format => 'int8u', + Writable => 'int8u', + Protected => 1, + ValueConv => '$val * 90', + ValueConvInv => 'int($val / 90 + 0.5)', + }, + ispe => { + Name => 'ImageSpatialExtent', + Condition => '$$valPt =~ /^\0{4}/', # (version/flags == 0/0) + RawConv => q{ + my @dim = unpack("x4N*", $val); + return undef if @dim < 2; + unless ($$self{DOC_NUM}) { + $self->FoundTag(ImageWidth => $dim[0]); + $self->FoundTag(ImageHeight => $dim[1]); + } + return join ' ', @dim; + }, + PrintConv => '$val =~ tr/ /x/; $val', + }, + pixi => { + Name => 'ImagePixelDepth', + Condition => '$$valPt =~ /^\0{4}./s', # (version/flags == 0/0 and count) + RawConv => 'join " ", unpack("x5C*", $val)', + }, + auxC => { + Name => 'AuxiliaryImageType', + Format => 'undef', + RawConv => '$val = substr($val, 4); $val =~ s/\0.*//s; $val', + }, + pasp => { + Name => 'PixelAspectRatio', + Format => 'int32u', + Writable => 'int32u', + Protected => 1, + }, + rloc => { + Name => 'RelativeLocation', + Format => 'int32u', + RawConv => '$val =~ s/^\S+\s+//; $val', # remove version/flags + }, + clap => { + Name => 'CleanAperture', + Format => 'rational64s', + Notes => '4 numbers: width, height, left and top', + }, + hvcC => { + Name => 'HEVCConfiguration', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::HEVCConfig' }, + }, + av1C => { + Name => 'AV1Configuration', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::AV1Config' }, + }, + clli => { + Name => 'ContentLightLevel', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ContentLightLevel' }, + }, + # ref https://nokiatech.github.io/heif/technical.html + # cclv - Content Color Volume + # mdcv - Mastering Display Color Volume + # rrtp - Required reference types + # crtt - Creation time information + # mdft - Modification time information + # udes - User description + # altt - Accessibility text + # aebr - Auto exposure information + # wbbr - White balance information + # fobr - Focus information + # afbr - Flash exposure information + # dobr - Depth of field information + # pano - Panorama information + # iscl - Image Scaling +); + +# ref https://aomediacodec.github.io/av1-spec/av1-spec.pdf +%Image::ExifTool::QuickTime::ColorRep = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FIRST_ENTRY => 0, + 0 => { Name => 'ColorProfiles', Format => 'undef[4]' }, + 4 => { + Name => 'ColorPrimaries', + Format => 'int16u', + PrintConv => { + 1 => 'BT.709', + 2 => 'Unspecified', + 4 => 'BT.470 System M (historical)', + 5 => 'BT.470 System B, G (historical)', + 6 => 'BT.601', + 7 => 'SMPTE 240', + 8 => 'Generic film (color filters using illuminant C)', + 9 => 'BT.2020, BT.2100', + 10 => 'SMPTE 428 (CIE 1931 XYZ)', #forum14766 + 11 => 'SMPTE RP 431-2', + 12 => 'SMPTE EG 432-1', + 22 => 'EBU Tech. 3213-E', + }, + }, + 6 => { + Name => 'TransferCharacteristics', + Format => 'int16u', + PrintConv => { + 0 => 'For future use (0)', + 1 => 'BT.709', + 2 => 'Unspecified', + 3 => 'For future use (3)', + 4 => 'BT.470 System M (historical)', # Gamma 2.2? (ref forum14960) + 5 => 'BT.470 System B, G (historical)', # Gamma 2.8? (ref forum14960) + 6 => 'BT.601', + 7 => 'SMPTE 240 M', + 8 => 'Linear', + 9 => 'Logarithmic (100 : 1 range)', + 10 => 'Logarithmic (100 * Sqrt(10) : 1 range)', + 11 => 'IEC 61966-2-4', + 12 => 'BT.1361', + 13 => 'sRGB or sYCC', + 14 => 'BT.2020 10-bit systems', + 15 => 'BT.2020 12-bit systems', + 16 => 'SMPTE ST 2084, ITU BT.2100 PQ', + 17 => 'SMPTE ST 428', + 18 => 'BT.2100 HLG, ARIB STD-B67', + }, + }, + 8 => { + Name => 'MatrixCoefficients', + Format => 'int16u', + PrintConv => { + 0 => 'Identity matrix', + 1 => 'BT.709', + 2 => 'Unspecified', + 3 => 'For future use (3)', + 4 => 'US FCC 73.628', + 5 => 'BT.470 System B, G (historical)', + 6 => 'BT.601', + 7 => 'SMPTE 240 M', + 8 => 'YCgCo', + 9 => 'BT.2020 non-constant luminance, BT.2100 YCbCr', + 10 => 'BT.2020 constant luminance', + 11 => 'SMPTE ST 2085 YDzDx', + 12 => 'Chromaticity-derived non-constant luminance', + 13 => 'Chromaticity-derived constant luminance', + 14 => 'BT.2100 ICtCp', + }, + }, +); + +# HEVC configuration (ref https://github.com/MPEGGroup/isobmff/blob/master/IsoLib/libisomediafile/src/HEVCConfigAtom.c) +%Image::ExifTool::QuickTime::HEVCConfig = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FIRST_ENTRY => 0, + 0 => 'HEVCConfigurationVersion', + 1 => { + Name => 'GeneralProfileSpace', + Mask => 0xc0, + PrintConv => { 0 => 'Conforming' }, + }, + 1.1 => { + Name => 'GeneralTierFlag', + Mask => 0x20, + PrintConv => { + 0 => 'Main Tier', + 1 => 'High Tier', + }, + }, + 1.2 => { + Name => 'GeneralProfileIDC', + Mask => 0x1f, + PrintConv => { + 0 => 'No Profile', + 1 => 'Main', + 2 => 'Main 10', + 3 => 'Main Still Picture', + 4 => 'Format Range Extensions', + 5 => 'High Throughput', + 6 => 'Multiview Main', + 7 => 'Scalable Main', + 8 => '3D Main', + 9 => 'Screen Content Coding Extensions', + 10 => 'Scalable Format Range Extensions', + 11 => 'High Throughput Screen Content Coding Extensions', + }, + }, + 2 => { + Name => 'GenProfileCompatibilityFlags', + Format => 'int32u', + PrintConv => { BITMASK => { + 31 => 'No Profile', # (bit 0 in stream) + 30 => 'Main', # (bit 1 in stream) + 29 => 'Main 10', # (bit 2 in stream) + 28 => 'Main Still Picture', # (bit 3 in stream) + 27 => 'Format Range Extensions',# (...) + 26 => 'High Throughput', + 25 => 'Multiview Main', + 24 => 'Scalable Main', + 23 => '3D Main', + 22 => 'Screen Content Coding Extensions', + 21 => 'Scalable Format Range Extensions', + 20 => 'High Throughput Screen Content Coding Extensions', + }}, + }, + 6 => { + Name => 'ConstraintIndicatorFlags', + Format => 'int8u[6]', + }, + 12 => { + Name => 'GeneralLevelIDC', + PrintConv => 'sprintf("%d (level %.1f)", $val, $val/30)', + }, + 13 => { + Name => 'MinSpatialSegmentationIDC', + Format => 'int16u', + Mask => 0x0fff, + }, + 15 => { + Name => 'ParallelismType', + Mask => 0x03, + }, + 16 => { + Name => 'ChromaFormat', + Mask => 0x03, + PrintConv => { + 0 => 'Monochrome', + 1 => '4:2:0', + 2 => '4:2:2', + 3 => '4:4:4', + }, + }, + 17 => { + Name => 'BitDepthLuma', + Mask => 0x07, + ValueConv => '$val + 8', + }, + 18 => { + Name => 'BitDepthChroma', + Mask => 0x07, + ValueConv => '$val + 8', + }, + 19 => { + Name => 'AverageFrameRate', + Format => 'int16u', + ValueConv => '$val / 256', + }, + 21 => { + Name => 'ConstantFrameRate', + Mask => 0xc0, + PrintConv => { + 0 => 'Unknown', + 1 => 'Constant Frame Rate', + 2 => 'Each Temporal Layer is Constant Frame Rate', + }, + }, + 21.1 => { + Name => 'NumTemporalLayers', + Mask => 0x38, + }, + 21.2 => { + Name => 'TemporalIDNested', + Mask => 0x04, + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + #21.3 => { + # Name => 'NALUnitLengthSize', + # Mask => 0x03, + # ValueConv => '$val + 1', + # PrintConv => { 1 => '8-bit', 2 => '16-bit', 4 => '32-bit' }, + #}, + #22 => 'NumberOfNALUnitArrays', + # (don't decode the NAL unit arrays) +); + +# HEVC configuration (ref https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox) +%Image::ExifTool::QuickTime::AV1Config = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FIRST_ENTRY => 0, + 0 => { + Name => 'AV1ConfigurationVersion', + Mask => 0x7f, + }, + 1.0 => { + Name => 'SeqProfile', + Mask => 0xe0, + Unknown => 1, + }, + 1.1 => { + Name => 'SeqLevelIdx0', + Mask => 0x1f, + Unknown => 1, + }, + 2.0 => { + Name => 'SeqTier0', + Mask => 0x80, + Unknown => 1, + }, + 2.1 => { + Name => 'HighBitDepth', + Mask => 0x40, + Unknown => 1, + }, + 2.2 => { + Name => 'TwelveBit', + Mask => 0x20, + Unknown => 1, + }, + 2.3 => { + Name => 'ChromaFormat', # (Monochrome+SubSamplingX+SubSamplingY) + Notes => 'bits: 0x04 = Monochrome, 0x02 = SubSamplingX, 0x01 = SubSamplingY', + Mask => 0x1c, + PrintConv => { + 0x00 => 'YUV 4:4:4', + 0x02 => 'YUV 4:2:2', + 0x03 => 'YUV 4:2:0', + 0x07 => 'Monochrome 4:0:0', + }, + }, + 2.4 => { + Name => 'ChromaSamplePosition', + Mask => 0x03, + PrintConv => { + 0 => 'Unknown', + 1 => 'Vertical', + 2 => 'Colocated', + 3 => '(reserved)', + }, + }, + 3 => { + Name => 'InitialDelaySamples', + RawConv => '$val & 0x10 ? undef : ($val & 0x0f) + 1', + Unknown => 1, + }, +); + +# ref https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/MPEG4Writer.cpp +%Image::ExifTool::QuickTime::ContentLightLevel = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FIRST_ENTRY => 0, + FORMAT => 'int16u', + 0 => 'MaxContentLightLevel', + 1 => 'MaxPicAverageLightLevel', +); + +%Image::ExifTool::QuickTime::ItemRef = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 2 => 'Image' }, + # (Note: ExifTool's ItemRefVersion may be used to test the iref version number) + NOTES => q{ + The Item reference entries listed in the table below contain information about + the associations between items in the file. This information is used by + ExifTool, but these entries are not extracted as tags. + }, + dimg => { Name => 'DerivedImageRef', RawConv => 'undef' }, + thmb => { Name => 'ThumbnailRef', RawConv => 'undef' }, + auxl => { Name => 'AuxiliaryImageRef', RawConv => 'undef' }, + cdsc => { + Name => 'ContentDescribes', + RawConv => \&ParseContentDescribes, + WriteHook => \&ParseContentDescribes, + }, +); + +%Image::ExifTool::QuickTime::ItemInfo = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 2 => 'Image' }, + # avc1 - AVC image + # hvc1 - HEVC image + # lhv1 - L-HEVC image + # infe - ItemInformationEntry + # infe types: avc1,hvc1,lhv1,Exif,xml1,iovl(overlay image),grid,mime,hvt1(tile image) + infe => { + Name => 'ItemInfoEntry', + RawConv => \&ParseItemInfoEntry, + WriteHook => \&ParseItemInfoEntry, + Notes => 'parsed, but not extracted as a tag', + }, +); + +# track reference atoms +%Image::ExifTool::QuickTime::TrackRef = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 1 => 'Track#', 2 => 'Video' }, + chap => { Name => 'ChapterListTrackID', Format => 'int32u' }, + tmcd => { Name => 'TimeCode', Format => 'int32u' }, + mpod => { #PH (FLIR MP4) + Name => 'ElementaryStreamTrack', + Format => 'int32u', + ValueConv => '$val =~ s/^1 //; $val', # (why 2 numbers? -- ignore the first if "1") + }, + # also: sync, scpt, ssrc, iTunesInfo + cdsc => { + Name => 'ContentDescribes', + Format => 'int32u', + PrintConv => '"Track $val"', + }, + # cdep (Structural Dependency QT tag?) +); + +# track aperture mode dimensions atoms +# (ref https://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap2/qtff2.html) +%Image::ExifTool::QuickTime::TrackAperture = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 1 => 'Track#', 2 => 'Video' }, + clef => { + Name => 'CleanApertureDimensions', + Format => 'fixed32u', + Count => 3, + ValueConv => '$val =~ s/^.*? //; $val', # remove flags word + PrintConv => '$val =~ tr/ /x/; $val', + }, + prof => { + Name => 'ProductionApertureDimensions', + Format => 'fixed32u', + Count => 3, + ValueConv => '$val =~ s/^.*? //; $val', + PrintConv => '$val =~ tr/ /x/; $val', + }, + enof => { + Name => 'EncodedPixelsDimensions', + Format => 'fixed32u', + Count => 3, + ValueConv => '$val =~ s/^.*? //; $val', + PrintConv => '$val =~ tr/ /x/; $val', + }, +); + +# item list atoms +# -> these atoms are unique, and contain one or more 'data' atoms +%Image::ExifTool::QuickTime::ItemList = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + CHECK_PROC => \&CheckQTValue, + WRITABLE => 1, + PREFERRED => 2, # (preferred over UserData and Keys tags when writing) + FORMAT => 'string', + GROUPS => { 1 => 'ItemList', 2 => 'Audio' }, + WRITE_GROUP => 'ItemList', + LANG_INFO => \&GetLangInfo, + NOTES => q{ + This is the preferred location for creating new QuickTime tags. Tags in + this table support alternate languages which are accessed by adding a + 3-character ISO 639-2 language code and an optional ISO 3166-1 alpha 2 + country code to the tag name (eg. "ItemList:Title-fra" or + "ItemList::Title-fra-FR"). When creating a new Meta box to contain the + ItemList directory, by default ExifTool adds an 'mdir' (Metadata) Handler + box because Apple software may ignore ItemList tags otherwise, but the API + L<QuickTimeHandler|../ExifTool.html#QuickTimeHandler> option may be set to 0 to avoid this. + }, + # in this table, binary 1 and 2-byte "data"-type tags are interpreted as + # int8u and int16u. Multi-byte binary "data" tags are extracted as binary data. + # (Note that the Preferred property is set to 0 for some tags to prevent them + # from being created when a same-named tag already exists in the table) + "\xa9ART" => 'Artist', + "\xa9alb" => 'Album', + "\xa9aut" => { Name => 'Author', Avoid => 1, Groups => { 2 => 'Author' } }, #forum10091 ('auth' is preferred) + "\xa9cmt" => 'Comment', + "\xa9com" => { Name => 'Composer', Avoid => 1, }, # ("\xa9wrt" is preferred in ItemList) + "\xa9day" => { + Name => 'ContentCreateDate', + Groups => { 2 => 'Time' }, + Shift => 'Time', + # handle values in the form "2010-02-12T13:27:14-0800" + ValueConv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::ConvertXMPDate($val); + $val =~ s/([-+]\d{2})(\d{2})$/$1:$2/; # add colon to timezone if necessary + return $val; + }, + ValueConvInv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::FormatXMPDate($val); + $val =~ s/([-+]\d{2}):(\d{2})$/$1$2/; # remove time zone colon + return $val; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,1)', # (add time zone if it didn't exist) + }, + "\xa9des" => 'Description', #4 + "\xa9enc" => 'EncodedBy', #10 + "\xa9gen" => 'Genre', + "\xa9grp" => 'Grouping', + "\xa9lyr" => 'Lyrics', + "\xa9nam" => 'Title', + "\xa9too" => 'Encoder', + "\xa9trk" => 'Track', + "\xa9wrt" => 'Composer', +# +# the following tags written by AtomicParsley 0.9.6 +# (ref https://exiftool.org/forum/index.php?topic=11455.0) +# + "\xa9st3" => 'Subtitle', + "\xa9con" => 'Conductor', + "\xa9sol" => 'Soloist', + "\xa9arg" => 'Arranger', + "\xa9ope" => 'OriginalArtist', + "\xa9dir" => 'Director', + "\xa9ard" => 'ArtDirector', + "\xa9sne" => 'SoundEngineer', + "\xa9prd" => 'Producer', + "\xa9xpd" => 'ExecutiveProducer', + sdes => 'StoreDescription', +# + '----' => { + Name => 'iTunesInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::iTunesInfo' }, + }, + aART => { Name => 'AlbumArtist', Groups => { 2 => 'Author' } }, + covr => { Name => 'CoverArt', Groups => { 2 => 'Preview' } }, + cpil => { #10 + Name => 'Compilation', + Format => 'int8u', #27 (ref 23 contradicts what AtomicParsley actually writes, which is int8s) + Writable => 'int8s', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + disk => { + Name => 'DiskNumber', + Format => 'undef', # (necessary to prevent decoding as string!) + ValueConv => q{ + return \$val unless length($val) >= 6; + my @a = unpack 'x2nn', $val; + return $a[1] ? join(' of ', @a) : $a[0]; + }, + ValueConvInv => q{ + my @a = $val =~ /\d+/g; + return undef if @a == 0 or @a > 2; + push @a, 0 if @a == 1; + return pack('n3', 0, @a); + }, + }, + pgap => { #10 + Name => 'PlayGap', + Format => 'int8u', #23 + Writable => 'int8s', #27 + PrintConv => { + 0 => 'Insert Gap', + 1 => 'No Gap', + }, + }, + tmpo => { + Name => 'BeatsPerMinute', + # marked as boolean but really int16u in my sample + # (but written as int16s by iTunes and AtomicParsley, ref forum11506) + Format => 'int16u', + Writable => 'int16s', + }, + trkn => { + Name => 'TrackNumber', + Format => 'undef', # (necessary to prevent decoding as string!) + ValueConv => q{ + return \$val unless length($val) >= 6; + my @a = unpack 'x2nn', $val; + return $a[1] ? join(' of ', @a) : $a[0]; + }, + # (see forum11501 for discussion about the format used) + ValueConvInv => q{ + my @a = $val =~ /\d+/g; + return undef if @a == 0 or @a > 2; + push @a, 0 if @a == 1; + return pack('n4', 0, @a, 0); + }, + }, +# +# Note: it is possible that the tags below are not being decoded properly +# because I don't have samples to verify many of these - PH +# + akID => { #10 + Name => 'AppleStoreAccountType', + Format => 'int8u', #24 + Writable => 'int8s', #27 + PrintConv => { + 0 => 'iTunes', + 1 => 'AOL', + }, + }, + albm => { Name => 'Album', Avoid => 1 }, #(ffmpeg source) + apID => 'AppleStoreAccount', + atID => { + # (ref 10 called this AlbumTitleID or TVSeries) + Name => 'ArtistID', #28 (or Track ID ref https://gist.github.com/maf654321/2b44c7b15d798f0c52ee) + Format => 'int32u', + Writable => 'int32s', #27 + }, + auth => { Name => 'Author', Groups => { 2 => 'Author' } }, + catg => 'Category', #7 + cnID => { #10 + Name => 'AppleStoreCatalogID', + Format => 'int32u', + Writable => 'int32s', #27 + }, + cmID => 'ComposerID', #28 (need sample to get format) + cprt => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + dscp => { Name => 'Description', Avoid => 1 }, + desc => { Name => 'Description', Avoid => 1 }, #7 + gnre => { #10 + Name => 'Genre', + Avoid => 1, + # (Note: see https://exiftool.org/forum/index.php?topic=11537.0) + Format => 'undef', + ValueConv => 'unpack("n",$val)', + ValueConvInv => '$val =~ /^\d+$/ ? pack("n",$val) : undef', + PrintConv => q{ + return $val unless $val =~ /^\d+$/; + require Image::ExifTool::ID3; + Image::ExifTool::ID3::PrintGenre($val - 1); # note the "- 1" + }, + PrintConvInv => q{ + return $val if $val =~ /^[0-9]+$/; + require Image::ExifTool::ID3; + my $id = Image::ExifTool::ID3::GetGenreID($val); + return unless defined $id and $id =~ /^\d+$/; + return $id + 1; + }, + }, + egid => 'EpisodeGlobalUniqueID', #7 + geID => { #10 + Name => 'GenreID', + Format => 'int32u', + Writable => 'int32s', #27 + SeparateTable => 1, + # the following lookup is based on http://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/genres + # (see scripts/parse_genre to parse genre JSON file from above) + PrintConv => { #21/PH + 2 => 'Music|Blues', + 3 => 'Music|Comedy', + 4 => "Music|Children's Music", + 5 => 'Music|Classical', + 6 => 'Music|Country', + 7 => 'Music|Electronic', + 8 => 'Music|Holiday', + 9 => 'Music|Classical|Opera', + 10 => 'Music|Singer/Songwriter', + 11 => 'Music|Jazz', + 12 => 'Music|Latino', + 13 => 'Music|New Age', + 14 => 'Music|Pop', + 15 => 'Music|R&B/Soul', + 16 => 'Music|Soundtrack', + 17 => 'Music|Dance', + 18 => 'Music|Hip-Hop/Rap', + 19 => 'Music|World', + 20 => 'Music|Alternative', + 21 => 'Music|Rock', + 22 => 'Music|Christian & Gospel', + 23 => 'Music|Vocal', + 24 => 'Music|Reggae', + 25 => 'Music|Easy Listening', + 26 => 'Podcasts', + 27 => 'Music|J-Pop', + 28 => 'Music|Enka', + 29 => 'Music|Anime', + 30 => 'Music|Kayokyoku', + 31 => 'Music Videos', + 32 => 'TV Shows', + 33 => 'Movies', + 34 => 'Music', + 35 => 'iPod Games', + 36 => 'App Store', + 37 => 'Tones', + 38 => 'Books', + 39 => 'Mac App Store', + 40 => 'Textbooks', + 50 => 'Music|Fitness & Workout', + 51 => 'Music|Pop|K-Pop', + 52 => 'Music|Karaoke', + 53 => 'Music|Instrumental', + 74 => 'Audiobooks|News', + 75 => 'Audiobooks|Programs & Performances', + 500 => 'Fitness Music', + 501 => 'Fitness Music|Pop', + 502 => 'Fitness Music|Dance', + 503 => 'Fitness Music|Hip-Hop', + 504 => 'Fitness Music|Rock', + 505 => 'Fitness Music|Alt/Indie', + 506 => 'Fitness Music|Latino', + 507 => 'Fitness Music|Country', + 508 => 'Fitness Music|World', + 509 => 'Fitness Music|New Age', + 510 => 'Fitness Music|Classical', + 1001 => 'Music|Alternative|College Rock', + 1002 => 'Music|Alternative|Goth Rock', + 1003 => 'Music|Alternative|Grunge', + 1004 => 'Music|Alternative|Indie Rock', + 1005 => 'Music|Alternative|New Wave', + 1006 => 'Music|Alternative|Punk', + 1007 => 'Music|Blues|Chicago Blues', + 1009 => 'Music|Blues|Classic Blues', + 1010 => 'Music|Blues|Contemporary Blues', + 1011 => 'Music|Blues|Country Blues', + 1012 => 'Music|Blues|Delta Blues', + 1013 => 'Music|Blues|Electric Blues', + 1014 => "Music|Children's Music|Lullabies", + 1015 => "Music|Children's Music|Sing-Along", + 1016 => "Music|Children's Music|Stories", + 1017 => 'Music|Classical|Avant-Garde', + 1018 => 'Music|Classical|Baroque Era', + 1019 => 'Music|Classical|Chamber Music', + 1020 => 'Music|Classical|Chant', + 1021 => 'Music|Classical|Choral', + 1022 => 'Music|Classical|Classical Crossover', + 1023 => 'Music|Classical|Early Music', + 1024 => 'Music|Classical|Impressionist', + 1025 => 'Music|Classical|Medieval Era', + 1026 => 'Music|Classical|Minimalism', + 1027 => 'Music|Classical|Modern Era', + 1028 => 'Music|Classical|Opera', + 1029 => 'Music|Classical|Orchestral', + 1030 => 'Music|Classical|Renaissance', + 1031 => 'Music|Classical|Romantic Era', + 1032 => 'Music|Classical|Wedding Music', + 1033 => 'Music|Country|Alternative Country', + 1034 => 'Music|Country|Americana', + 1035 => 'Music|Country|Bluegrass', + 1036 => 'Music|Country|Contemporary Bluegrass', + 1037 => 'Music|Country|Contemporary Country', + 1038 => 'Music|Country|Country Gospel', + 1039 => 'Music|Country|Honky Tonk', + 1040 => 'Music|Country|Outlaw Country', + 1041 => 'Music|Country|Traditional Bluegrass', + 1042 => 'Music|Country|Traditional Country', + 1043 => 'Music|Country|Urban Cowboy', + 1044 => 'Music|Dance|Breakbeat', + 1045 => 'Music|Dance|Exercise', + 1046 => 'Music|Dance|Garage', + 1047 => 'Music|Dance|Hardcore', + 1048 => 'Music|Dance|House', + 1049 => "Music|Dance|Jungle/Drum'n'bass", + 1050 => 'Music|Dance|Techno', + 1051 => 'Music|Dance|Trance', + 1052 => 'Music|Jazz|Big Band', + 1053 => 'Music|Jazz|Bop', + 1054 => 'Music|Easy Listening|Lounge', + 1055 => 'Music|Easy Listening|Swing', + 1056 => 'Music|Electronic|Ambient', + 1057 => 'Music|Electronic|Downtempo', + 1058 => 'Music|Electronic|Electronica', + 1060 => 'Music|Electronic|IDM/Experimental', + 1061 => 'Music|Electronic|Industrial', + 1062 => 'Music|Singer/Songwriter|Alternative Folk', + 1063 => 'Music|Singer/Songwriter|Contemporary Folk', + 1064 => 'Music|Singer/Songwriter|Contemporary Singer/Songwriter', + 1065 => 'Music|Singer/Songwriter|Folk-Rock', + 1066 => 'Music|Singer/Songwriter|New Acoustic', + 1067 => 'Music|Singer/Songwriter|Traditional Folk', + 1068 => 'Music|Hip-Hop/Rap|Alternative Rap', + 1069 => 'Music|Hip-Hop/Rap|Dirty South', + 1070 => 'Music|Hip-Hop/Rap|East Coast Rap', + 1071 => 'Music|Hip-Hop/Rap|Gangsta Rap', + 1072 => 'Music|Hip-Hop/Rap|Hardcore Rap', + 1073 => 'Music|Hip-Hop/Rap|Hip-Hop', + 1074 => 'Music|Hip-Hop/Rap|Latin Rap', + 1075 => 'Music|Hip-Hop/Rap|Old School Rap', + 1076 => 'Music|Hip-Hop/Rap|Rap', + 1077 => 'Music|Hip-Hop/Rap|Underground Rap', + 1078 => 'Music|Hip-Hop/Rap|West Coast Rap', + 1079 => 'Music|Holiday|Chanukah', + 1080 => 'Music|Holiday|Christmas', + 1081 => "Music|Holiday|Christmas: Children's", + 1082 => 'Music|Holiday|Christmas: Classic', + 1083 => 'Music|Holiday|Christmas: Classical', + 1084 => 'Music|Holiday|Christmas: Jazz', + 1085 => 'Music|Holiday|Christmas: Modern', + 1086 => 'Music|Holiday|Christmas: Pop', + 1087 => 'Music|Holiday|Christmas: R&B', + 1088 => 'Music|Holiday|Christmas: Religious', + 1089 => 'Music|Holiday|Christmas: Rock', + 1090 => 'Music|Holiday|Easter', + 1091 => 'Music|Holiday|Halloween', + 1092 => 'Music|Holiday|Holiday: Other', + 1093 => 'Music|Holiday|Thanksgiving', + 1094 => 'Music|Christian & Gospel|CCM', + 1095 => 'Music|Christian & Gospel|Christian Metal', + 1096 => 'Music|Christian & Gospel|Christian Pop', + 1097 => 'Music|Christian & Gospel|Christian Rap', + 1098 => 'Music|Christian & Gospel|Christian Rock', + 1099 => 'Music|Christian & Gospel|Classic Christian', + 1100 => 'Music|Christian & Gospel|Contemporary Gospel', + 1101 => 'Music|Christian & Gospel|Gospel', + 1103 => 'Music|Christian & Gospel|Praise & Worship', + 1104 => 'Music|Christian & Gospel|Southern Gospel', + 1105 => 'Music|Christian & Gospel|Traditional Gospel', + 1106 => 'Music|Jazz|Avant-Garde Jazz', + 1107 => 'Music|Jazz|Contemporary Jazz', + 1108 => 'Music|Jazz|Crossover Jazz', + 1109 => 'Music|Jazz|Dixieland', + 1110 => 'Music|Jazz|Fusion', + 1111 => 'Music|Jazz|Latin Jazz', + 1112 => 'Music|Jazz|Mainstream Jazz', + 1113 => 'Music|Jazz|Ragtime', + 1114 => 'Music|Jazz|Smooth Jazz', + 1115 => 'Music|Latino|Latin Jazz', + 1116 => 'Music|Latino|Contemporary Latin', + 1117 => 'Music|Latino|Pop Latino', + 1118 => 'Music|Latino|Raices', # (Ra&iacute;ces) + 1119 => 'Music|Latino|Urbano latino', + 1120 => 'Music|Latino|Baladas y Boleros', + 1121 => 'Music|Latino|Rock y Alternativo', + 1122 => 'Music|Brazilian', + 1123 => 'Music|Latino|Musica Mexicana', # (M&uacute;sica Mexicana) + 1124 => 'Music|Latino|Musica tropical', # (M&uacute;sica tropical) + 1125 => 'Music|New Age|Environmental', + 1126 => 'Music|New Age|Healing', + 1127 => 'Music|New Age|Meditation', + 1128 => 'Music|New Age|Nature', + 1129 => 'Music|New Age|Relaxation', + 1130 => 'Music|New Age|Travel', + 1131 => 'Music|Pop|Adult Contemporary', + 1132 => 'Music|Pop|Britpop', + 1133 => 'Music|Pop|Pop/Rock', + 1134 => 'Music|Pop|Soft Rock', + 1135 => 'Music|Pop|Teen Pop', + 1136 => 'Music|R&B/Soul|Contemporary R&B', + 1137 => 'Music|R&B/Soul|Disco', + 1138 => 'Music|R&B/Soul|Doo Wop', + 1139 => 'Music|R&B/Soul|Funk', + 1140 => 'Music|R&B/Soul|Motown', + 1141 => 'Music|R&B/Soul|Neo-Soul', + 1142 => 'Music|R&B/Soul|Quiet Storm', + 1143 => 'Music|R&B/Soul|Soul', + 1144 => 'Music|Rock|Adult Alternative', + 1145 => 'Music|Rock|American Trad Rock', + 1146 => 'Music|Rock|Arena Rock', + 1147 => 'Music|Rock|Blues-Rock', + 1148 => 'Music|Rock|British Invasion', + 1149 => 'Music|Rock|Death Metal/Black Metal', + 1150 => 'Music|Rock|Glam Rock', + 1151 => 'Music|Rock|Hair Metal', + 1152 => 'Music|Rock|Hard Rock', + 1153 => 'Music|Rock|Metal', + 1154 => 'Music|Rock|Jam Bands', + 1155 => 'Music|Rock|Prog-Rock/Art Rock', + 1156 => 'Music|Rock|Psychedelic', + 1157 => 'Music|Rock|Rock & Roll', + 1158 => 'Music|Rock|Rockabilly', + 1159 => 'Music|Rock|Roots Rock', + 1160 => 'Music|Rock|Singer/Songwriter', + 1161 => 'Music|Rock|Southern Rock', + 1162 => 'Music|Rock|Surf', + 1163 => 'Music|Rock|Tex-Mex', + 1165 => 'Music|Soundtrack|Foreign Cinema', + 1166 => 'Music|Soundtrack|Musicals', + 1167 => 'Music|Comedy|Novelty', + 1168 => 'Music|Soundtrack|Original Score', + 1169 => 'Music|Soundtrack|Soundtrack', + 1171 => 'Music|Comedy|Standup Comedy', + 1172 => 'Music|Soundtrack|TV Soundtrack', + 1173 => 'Music|Vocal|Standards', + 1174 => 'Music|Vocal|Traditional Pop', + 1175 => 'Music|Jazz|Vocal Jazz', + 1176 => 'Music|Vocal|Vocal Pop', + 1177 => 'Music|African|Afro-Beat', + 1178 => 'Music|African|Afro-Pop', + 1179 => 'Music|World|Cajun', + 1180 => 'Music|World|Celtic', + 1181 => 'Music|World|Celtic Folk', + 1182 => 'Music|World|Contemporary Celtic', + 1183 => 'Music|Reggae|Modern Dancehall', + 1184 => 'Music|World|Drinking Songs', + 1185 => 'Music|Indian|Indian Pop', + 1186 => 'Music|World|Japanese Pop', + 1187 => 'Music|World|Klezmer', + 1188 => 'Music|World|Polka', + 1189 => 'Music|World|Traditional Celtic', + 1190 => 'Music|World|Worldbeat', + 1191 => 'Music|World|Zydeco', + 1192 => 'Music|Reggae|Roots Reggae', + 1193 => 'Music|Reggae|Dub', + 1194 => 'Music|Reggae|Ska', + 1195 => 'Music|World|Caribbean', + 1196 => 'Music|World|South America', + 1197 => 'Music|Arabic', + 1198 => 'Music|World|North America', + 1199 => 'Music|World|Hawaii', + 1200 => 'Music|World|Australia', + 1201 => 'Music|World|Japan', + 1202 => 'Music|World|France', + 1203 => 'Music|African', + 1204 => 'Music|World|Asia', + 1205 => 'Music|World|Europe', + 1206 => 'Music|World|South Africa', + 1207 => 'Music|Jazz|Hard Bop', + 1208 => 'Music|Jazz|Trad Jazz', + 1209 => 'Music|Jazz|Cool Jazz', + 1210 => 'Music|Blues|Acoustic Blues', + 1211 => 'Music|Classical|High Classical', + 1220 => 'Music|Brazilian|Axe', # (Ax&eacute;) + 1221 => 'Music|Brazilian|Bossa Nova', + 1222 => 'Music|Brazilian|Choro', + 1223 => 'Music|Brazilian|Forro', # (Forr&oacute;) + 1224 => 'Music|Brazilian|Frevo', + 1225 => 'Music|Brazilian|MPB', + 1226 => 'Music|Brazilian|Pagode', + 1227 => 'Music|Brazilian|Samba', + 1228 => 'Music|Brazilian|Sertanejo', + 1229 => 'Music|Brazilian|Baile Funk', + 1230 => 'Music|Alternative|Chinese Alt', + 1231 => 'Music|Alternative|Korean Indie', + 1232 => 'Music|Chinese', + 1233 => 'Music|Chinese|Chinese Classical', + 1234 => 'Music|Chinese|Chinese Flute', + 1235 => 'Music|Chinese|Chinese Opera', + 1236 => 'Music|Chinese|Chinese Orchestral', + 1237 => 'Music|Chinese|Chinese Regional Folk', + 1238 => 'Music|Chinese|Chinese Strings', + 1239 => 'Music|Chinese|Taiwanese Folk', + 1240 => 'Music|Chinese|Tibetan Native Music', + 1241 => 'Music|Hip-Hop/Rap|Chinese Hip-Hop', + 1242 => 'Music|Hip-Hop/Rap|Korean Hip-Hop', + 1243 => 'Music|Korean', + 1244 => 'Music|Korean|Korean Classical', + 1245 => 'Music|Korean|Korean Trad Song', + 1246 => 'Music|Korean|Korean Trad Instrumental', + 1247 => 'Music|Korean|Korean Trad Theater', + 1248 => 'Music|Rock|Chinese Rock', + 1249 => 'Music|Rock|Korean Rock', + 1250 => 'Music|Pop|C-Pop', + 1251 => 'Music|Pop|Cantopop/HK-Pop', + 1252 => 'Music|Pop|Korean Folk-Pop', + 1253 => 'Music|Pop|Mandopop', + 1254 => 'Music|Pop|Tai-Pop', + 1255 => 'Music|Pop|Malaysian Pop', + 1256 => 'Music|Pop|Pinoy Pop', + 1257 => 'Music|Pop|Original Pilipino Music', + 1258 => 'Music|Pop|Manilla Sound', + 1259 => 'Music|Pop|Indo Pop', + 1260 => 'Music|Pop|Thai Pop', + 1261 => 'Music|Vocal|Trot', + 1262 => 'Music|Indian', + 1263 => 'Music|Indian|Bollywood', + 1264 => 'Music|Indian|Regional Indian|Tamil', + 1265 => 'Music|Indian|Regional Indian|Telugu', + 1266 => 'Music|Indian|Regional Indian', + 1267 => 'Music|Indian|Devotional & Spiritual', + 1268 => 'Music|Indian|Sufi', + 1269 => 'Music|Indian|Indian Classical', + 1270 => 'Music|Russian|Russian Chanson', + 1271 => 'Music|World|Dini', + 1272 => 'Music|Turkish|Halk', + 1273 => 'Music|Turkish|Sanat', + 1274 => 'Music|World|Dangdut', + 1275 => 'Music|World|Indonesian Religious', + 1276 => 'Music|World|Calypso', + 1277 => 'Music|World|Soca', + 1278 => 'Music|Indian|Ghazals', + 1279 => 'Music|Indian|Indian Folk', + 1280 => 'Music|Turkish|Arabesque', + 1281 => 'Music|African|Afrikaans', + 1282 => 'Music|World|Farsi', + 1283 => 'Music|World|Israeli', + 1284 => 'Music|Arabic|Khaleeji', + 1285 => 'Music|Arabic|North African', + 1286 => 'Music|Arabic|Arabic Pop', + 1287 => 'Music|Arabic|Islamic', + 1288 => 'Music|Soundtrack|Sound Effects', + 1289 => 'Music|Folk', + 1290 => 'Music|Orchestral', + 1291 => 'Music|Marching', + 1293 => 'Music|Pop|Oldies', + 1294 => 'Music|Country|Thai Country', + 1295 => 'Music|World|Flamenco', + 1296 => 'Music|World|Tango', + 1297 => 'Music|World|Fado', + 1298 => 'Music|World|Iberia', + 1299 => 'Music|Russian', + 1300 => 'Music|Turkish', + 1301 => 'Podcasts|Arts', + 1302 => 'Podcasts|Society & Culture|Personal Journals', + 1303 => 'Podcasts|Comedy', + 1304 => 'Podcasts|Education', + 1305 => 'Podcasts|Kids & Family', + 1306 => 'Podcasts|Arts|Food', + 1307 => 'Podcasts|Health', + 1309 => 'Podcasts|TV & Film', + 1310 => 'Podcasts|Music', + 1311 => 'Podcasts|News & Politics', + 1314 => 'Podcasts|Religion & Spirituality', + 1315 => 'Podcasts|Science & Medicine', + 1316 => 'Podcasts|Sports & Recreation', + 1318 => 'Podcasts|Technology', + 1320 => 'Podcasts|Society & Culture|Places & Travel', + 1321 => 'Podcasts|Business', + 1323 => 'Podcasts|Games & Hobbies', + 1324 => 'Podcasts|Society & Culture', + 1325 => 'Podcasts|Government & Organizations', + 1337 => 'Music Videos|Classical|Piano', + 1401 => 'Podcasts|Arts|Literature', + 1402 => 'Podcasts|Arts|Design', + 1404 => 'Podcasts|Games & Hobbies|Video Games', + 1405 => 'Podcasts|Arts|Performing Arts', + 1406 => 'Podcasts|Arts|Visual Arts', + 1410 => 'Podcasts|Business|Careers', + 1412 => 'Podcasts|Business|Investing', + 1413 => 'Podcasts|Business|Management & Marketing', + 1415 => 'Podcasts|Education|K-12', + 1416 => 'Podcasts|Education|Higher Education', + 1417 => 'Podcasts|Health|Fitness & Nutrition', + 1420 => 'Podcasts|Health|Self-Help', + 1421 => 'Podcasts|Health|Sexuality', + 1438 => 'Podcasts|Religion & Spirituality|Buddhism', + 1439 => 'Podcasts|Religion & Spirituality|Christianity', + 1440 => 'Podcasts|Religion & Spirituality|Islam', + 1441 => 'Podcasts|Religion & Spirituality|Judaism', + 1443 => 'Podcasts|Society & Culture|Philosophy', + 1444 => 'Podcasts|Religion & Spirituality|Spirituality', + 1446 => 'Podcasts|Technology|Gadgets', + 1448 => 'Podcasts|Technology|Tech News', + 1450 => 'Podcasts|Technology|Podcasting', + 1454 => 'Podcasts|Games & Hobbies|Automotive', + 1455 => 'Podcasts|Games & Hobbies|Aviation', + 1456 => 'Podcasts|Sports & Recreation|Outdoor', + 1459 => 'Podcasts|Arts|Fashion & Beauty', + 1460 => 'Podcasts|Games & Hobbies|Hobbies', + 1461 => 'Podcasts|Games & Hobbies|Other Games', + 1462 => 'Podcasts|Society & Culture|History', + 1463 => 'Podcasts|Religion & Spirituality|Hinduism', + 1464 => 'Podcasts|Religion & Spirituality|Other', + 1465 => 'Podcasts|Sports & Recreation|Professional', + 1466 => 'Podcasts|Sports & Recreation|College & High School', + 1467 => 'Podcasts|Sports & Recreation|Amateur', + 1468 => 'Podcasts|Education|Educational Technology', + 1469 => 'Podcasts|Education|Language Courses', + 1470 => 'Podcasts|Education|Training', + 1471 => 'Podcasts|Business|Business News', + 1472 => 'Podcasts|Business|Shopping', + 1473 => 'Podcasts|Government & Organizations|National', + 1474 => 'Podcasts|Government & Organizations|Regional', + 1475 => 'Podcasts|Government & Organizations|Local', + 1476 => 'Podcasts|Government & Organizations|Non-Profit', + 1477 => 'Podcasts|Science & Medicine|Natural Sciences', + 1478 => 'Podcasts|Science & Medicine|Medicine', + 1479 => 'Podcasts|Science & Medicine|Social Sciences', + 1480 => 'Podcasts|Technology|Software How-To', + 1481 => 'Podcasts|Health|Alternative Health', + 1482 => 'Podcasts|Arts|Books', + 1483 => 'Podcasts|Fiction', + 1484 => 'Podcasts|Fiction|Drama', + 1485 => 'Podcasts|Fiction|Science Fiction', + 1486 => 'Podcasts|Fiction|Comedy Fiction', + 1487 => 'Podcasts|History', + 1488 => 'Podcasts|True Crime', + 1489 => 'Podcasts|News', + 1490 => 'Podcasts|News|Business News', + 1491 => 'Podcasts|Business|Management', + 1492 => 'Podcasts|Business|Marketing', + 1493 => 'Podcasts|Business|Entrepreneurship', + 1494 => 'Podcasts|Business|Non-Profit', + 1495 => 'Podcasts|Comedy|Improv', + 1496 => 'Podcasts|Comedy|Comedy Interviews', + 1497 => 'Podcasts|Comedy|Stand-Up', + 1498 => 'Podcasts|Education|Language Learning', + 1499 => 'Podcasts|Education|How To', + 1500 => 'Podcasts|Education|Self-Improvement', + 1501 => 'Podcasts|Education|Courses', + 1502 => 'Podcasts|Leisure', + 1503 => 'Podcasts|Leisure|Automotive', + 1504 => 'Podcasts|Leisure|Aviation', + 1505 => 'Podcasts|Leisure|Hobbies', + 1506 => 'Podcasts|Leisure|Crafts', + 1507 => 'Podcasts|Leisure|Games', + 1508 => 'Podcasts|Leisure|Home & Garden', + 1509 => 'Podcasts|Leisure|Video Games', + 1510 => 'Podcasts|Leisure|Animation & Manga', + 1511 => 'Podcasts|Government', + 1512 => 'Podcasts|Health & Fitness', + 1513 => 'Podcasts|Health & Fitness|Alternative Health', + 1514 => 'Podcasts|Health & Fitness|Fitness', + 1515 => 'Podcasts|Health & Fitness|Nutrition', + 1516 => 'Podcasts|Health & Fitness|Sexuality', + 1517 => 'Podcasts|Health & Fitness|Mental Health', + 1518 => 'Podcasts|Health & Fitness|Medicine', + 1519 => 'Podcasts|Kids & Family|Education for Kids', + 1520 => 'Podcasts|Kids & Family|Stories for Kids', + 1521 => 'Podcasts|Kids & Family|Parenting', + 1522 => 'Podcasts|Kids & Family|Pets & Animals', + 1523 => 'Podcasts|Music|Music Commentary', + 1524 => 'Podcasts|Music|Music History', + 1525 => 'Podcasts|Music|Music Interviews', + 1526 => 'Podcasts|News|Daily News', + 1527 => 'Podcasts|News|Politics', + 1528 => 'Podcasts|News|Tech News', + 1529 => 'Podcasts|News|Sports News', + 1530 => 'Podcasts|News|News Commentary', + 1531 => 'Podcasts|News|Entertainment News', + 1532 => 'Podcasts|Religion & Spirituality|Religion', + 1533 => 'Podcasts|Science', + 1534 => 'Podcasts|Science|Natural Sciences', + 1535 => 'Podcasts|Science|Social Sciences', + 1536 => 'Podcasts|Science|Mathematics', + 1537 => 'Podcasts|Science|Nature', + 1538 => 'Podcasts|Science|Astronomy', + 1539 => 'Podcasts|Science|Chemistry', + 1540 => 'Podcasts|Science|Earth Sciences', + 1541 => 'Podcasts|Science|Life Sciences', + 1542 => 'Podcasts|Science|Physics', + 1543 => 'Podcasts|Society & Culture|Documentary', + 1544 => 'Podcasts|Society & Culture|Relationships', + 1545 => 'Podcasts|Sports', + 1546 => 'Podcasts|Sports|Soccer', + 1547 => 'Podcasts|Sports|Football', + 1548 => 'Podcasts|Sports|Basketball', + 1549 => 'Podcasts|Sports|Baseball', + 1550 => 'Podcasts|Sports|Hockey', + 1551 => 'Podcasts|Sports|Running', + 1552 => 'Podcasts|Sports|Rugby', + 1553 => 'Podcasts|Sports|Golf', + 1554 => 'Podcasts|Sports|Cricket', + 1555 => 'Podcasts|Sports|Wrestling', + 1556 => 'Podcasts|Sports|Tennis', + 1557 => 'Podcasts|Sports|Volleyball', + 1558 => 'Podcasts|Sports|Swimming', + 1559 => 'Podcasts|Sports|Wilderness', + 1560 => 'Podcasts|Sports|Fantasy Sports', + 1561 => 'Podcasts|TV & Film|TV Reviews', + 1562 => 'Podcasts|TV & Film|After Shows', + 1563 => 'Podcasts|TV & Film|Film Reviews', + 1564 => 'Podcasts|TV & Film|Film History', + 1565 => 'Podcasts|TV & Film|Film Interviews', + 1602 => 'Music Videos|Blues', + 1603 => 'Music Videos|Comedy', + 1604 => "Music Videos|Children's Music", + 1605 => 'Music Videos|Classical', + 1606 => 'Music Videos|Country', + 1607 => 'Music Videos|Electronic', + 1608 => 'Music Videos|Holiday', + 1609 => 'Music Videos|Classical|Opera', + 1610 => 'Music Videos|Singer/Songwriter', + 1611 => 'Music Videos|Jazz', + 1612 => 'Music Videos|Latin', + 1613 => 'Music Videos|New Age', + 1614 => 'Music Videos|Pop', + 1615 => 'Music Videos|R&B/Soul', + 1616 => 'Music Videos|Soundtrack', + 1617 => 'Music Videos|Dance', + 1618 => 'Music Videos|Hip-Hop/Rap', + 1619 => 'Music Videos|World', + 1620 => 'Music Videos|Alternative', + 1621 => 'Music Videos|Rock', + 1622 => 'Music Videos|Christian & Gospel', + 1623 => 'Music Videos|Vocal', + 1624 => 'Music Videos|Reggae', + 1625 => 'Music Videos|Easy Listening', + 1626 => 'Music Videos|Podcasts', + 1627 => 'Music Videos|J-Pop', + 1628 => 'Music Videos|Enka', + 1629 => 'Music Videos|Anime', + 1630 => 'Music Videos|Kayokyoku', + 1631 => 'Music Videos|Disney', + 1632 => 'Music Videos|French Pop', + 1633 => 'Music Videos|German Pop', + 1634 => 'Music Videos|German Folk', + 1635 => 'Music Videos|Alternative|Chinese Alt', + 1636 => 'Music Videos|Alternative|Korean Indie', + 1637 => 'Music Videos|Chinese', + 1638 => 'Music Videos|Chinese|Chinese Classical', + 1639 => 'Music Videos|Chinese|Chinese Flute', + 1640 => 'Music Videos|Chinese|Chinese Opera', + 1641 => 'Music Videos|Chinese|Chinese Orchestral', + 1642 => 'Music Videos|Chinese|Chinese Regional Folk', + 1643 => 'Music Videos|Chinese|Chinese Strings', + 1644 => 'Music Videos|Chinese|Taiwanese Folk', + 1645 => 'Music Videos|Chinese|Tibetan Native Music', + 1646 => 'Music Videos|Hip-Hop/Rap|Chinese Hip-Hop', + 1647 => 'Music Videos|Hip-Hop/Rap|Korean Hip-Hop', + 1648 => 'Music Videos|Korean', + 1649 => 'Music Videos|Korean|Korean Classical', + 1650 => 'Music Videos|Korean|Korean Trad Song', + 1651 => 'Music Videos|Korean|Korean Trad Instrumental', + 1652 => 'Music Videos|Korean|Korean Trad Theater', + 1653 => 'Music Videos|Rock|Chinese Rock', + 1654 => 'Music Videos|Rock|Korean Rock', + 1655 => 'Music Videos|Pop|C-Pop', + 1656 => 'Music Videos|Pop|Cantopop/HK-Pop', + 1657 => 'Music Videos|Pop|Korean Folk-Pop', + 1658 => 'Music Videos|Pop|Mandopop', + 1659 => 'Music Videos|Pop|Tai-Pop', + 1660 => 'Music Videos|Pop|Malaysian Pop', + 1661 => 'Music Videos|Pop|Pinoy Pop', + 1662 => 'Music Videos|Pop|Original Pilipino Music', + 1663 => 'Music Videos|Pop|Manilla Sound', + 1664 => 'Music Videos|Pop|Indo Pop', + 1665 => 'Music Videos|Pop|Thai Pop', + 1666 => 'Music Videos|Vocal|Trot', + 1671 => 'Music Videos|Brazilian', + 1672 => 'Music Videos|Brazilian|Axe', # (Ax&eacute;) + 1673 => 'Music Videos|Brazilian|Baile Funk', + 1674 => 'Music Videos|Brazilian|Bossa Nova', + 1675 => 'Music Videos|Brazilian|Choro', + 1676 => 'Music Videos|Brazilian|Forro', + 1677 => 'Music Videos|Brazilian|Frevo', + 1678 => 'Music Videos|Brazilian|MPB', + 1679 => 'Music Videos|Brazilian|Pagode', + 1680 => 'Music Videos|Brazilian|Samba', + 1681 => 'Music Videos|Brazilian|Sertanejo', + 1682 => 'Music Videos|Classical|High Classical', + 1683 => 'Music Videos|Fitness & Workout', + 1684 => 'Music Videos|Instrumental', + 1685 => 'Music Videos|Jazz|Big Band', + 1686 => 'Music Videos|Pop|K-Pop', + 1687 => 'Music Videos|Karaoke', + 1688 => 'Music Videos|Rock|Heavy Metal', + 1689 => 'Music Videos|Spoken Word', + 1690 => 'Music Videos|Indian', + 1691 => 'Music Videos|Indian|Bollywood', + 1692 => 'Music Videos|Indian|Regional Indian|Tamil', + 1693 => 'Music Videos|Indian|Regional Indian|Telugu', + 1694 => 'Music Videos|Indian|Regional Indian', + 1695 => 'Music Videos|Indian|Devotional & Spiritual', + 1696 => 'Music Videos|Indian|Sufi', + 1697 => 'Music Videos|Indian|Indian Classical', + 1698 => 'Music Videos|Russian|Russian Chanson', + 1699 => 'Music Videos|World|Dini', + 1700 => 'Music Videos|Turkish|Halk', + 1701 => 'Music Videos|Turkish|Sanat', + 1702 => 'Music Videos|World|Dangdut', + 1703 => 'Music Videos|World|Indonesian Religious', + 1704 => 'Music Videos|Indian|Indian Pop', + 1705 => 'Music Videos|World|Calypso', + 1706 => 'Music Videos|World|Soca', + 1707 => 'Music Videos|Indian|Ghazals', + 1708 => 'Music Videos|Indian|Indian Folk', + 1709 => 'Music Videos|Turkish|Arabesque', + 1710 => 'Music Videos|African|Afrikaans', + 1711 => 'Music Videos|World|Farsi', + 1712 => 'Music Videos|World|Israeli', + 1713 => 'Music Videos|Arabic', + 1714 => 'Music Videos|Arabic|Khaleeji', + 1715 => 'Music Videos|Arabic|North African', + 1716 => 'Music Videos|Arabic|Arabic Pop', + 1717 => 'Music Videos|Arabic|Islamic', + 1718 => 'Music Videos|Soundtrack|Sound Effects', + 1719 => 'Music Videos|Folk', + 1720 => 'Music Videos|Orchestral', + 1721 => 'Music Videos|Marching', + 1723 => 'Music Videos|Pop|Oldies', + 1724 => 'Music Videos|Country|Thai Country', + 1725 => 'Music Videos|World|Flamenco', + 1726 => 'Music Videos|World|Tango', + 1727 => 'Music Videos|World|Fado', + 1728 => 'Music Videos|World|Iberia', + 1729 => 'Music Videos|Russian', + 1730 => 'Music Videos|Turkish', + 1731 => 'Music Videos|Alternative|College Rock', + 1732 => 'Music Videos|Alternative|Goth Rock', + 1733 => 'Music Videos|Alternative|Grunge', + 1734 => 'Music Videos|Alternative|Indie Rock', + 1735 => 'Music Videos|Alternative|New Wave', + 1736 => 'Music Videos|Alternative|Punk', + 1737 => 'Music Videos|Blues|Acoustic Blues', + 1738 => 'Music Videos|Blues|Chicago Blues', + 1739 => 'Music Videos|Blues|Classic Blues', + 1740 => 'Music Videos|Blues|Contemporary Blues', + 1741 => 'Music Videos|Blues|Country Blues', + 1742 => 'Music Videos|Blues|Delta Blues', + 1743 => 'Music Videos|Blues|Electric Blues', + 1744 => "Music Videos|Children's Music|Lullabies", + 1745 => "Music Videos|Children's Music|Sing-Along", + 1746 => "Music Videos|Children's Music|Stories", + 1747 => 'Music Videos|Christian & Gospel|CCM', + 1748 => 'Music Videos|Christian & Gospel|Christian Metal', + 1749 => 'Music Videos|Christian & Gospel|Christian Pop', + 1750 => 'Music Videos|Christian & Gospel|Christian Rap', + 1751 => 'Music Videos|Christian & Gospel|Christian Rock', + 1752 => 'Music Videos|Christian & Gospel|Classic Christian', + 1753 => 'Music Videos|Christian & Gospel|Contemporary Gospel', + 1754 => 'Music Videos|Christian & Gospel|Gospel', + 1755 => 'Music Videos|Christian & Gospel|Praise & Worship', + 1756 => 'Music Videos|Christian & Gospel|Southern Gospel', + 1757 => 'Music Videos|Christian & Gospel|Traditional Gospel', + 1758 => 'Music Videos|Classical|Avant-Garde', + 1759 => 'Music Videos|Classical|Baroque Era', + 1760 => 'Music Videos|Classical|Chamber Music', + 1761 => 'Music Videos|Classical|Chant', + 1762 => 'Music Videos|Classical|Choral', + 1763 => 'Music Videos|Classical|Classical Crossover', + 1764 => 'Music Videos|Classical|Early Music', + 1765 => 'Music Videos|Classical|Impressionist', + 1766 => 'Music Videos|Classical|Medieval Era', + 1767 => 'Music Videos|Classical|Minimalism', + 1768 => 'Music Videos|Classical|Modern Era', + 1769 => 'Music Videos|Classical|Orchestral', + 1770 => 'Music Videos|Classical|Renaissance', + 1771 => 'Music Videos|Classical|Romantic Era', + 1772 => 'Music Videos|Classical|Wedding Music', + 1773 => 'Music Videos|Comedy|Novelty', + 1774 => 'Music Videos|Comedy|Standup Comedy', + 1775 => 'Music Videos|Country|Alternative Country', + 1776 => 'Music Videos|Country|Americana', + 1777 => 'Music Videos|Country|Bluegrass', + 1778 => 'Music Videos|Country|Contemporary Bluegrass', + 1779 => 'Music Videos|Country|Contemporary Country', + 1780 => 'Music Videos|Country|Country Gospel', + 1781 => 'Music Videos|Country|Honky Tonk', + 1782 => 'Music Videos|Country|Outlaw Country', + 1783 => 'Music Videos|Country|Traditional Bluegrass', + 1784 => 'Music Videos|Country|Traditional Country', + 1785 => 'Music Videos|Country|Urban Cowboy', + 1786 => 'Music Videos|Dance|Breakbeat', + 1787 => 'Music Videos|Dance|Exercise', + 1788 => 'Music Videos|Dance|Garage', + 1789 => 'Music Videos|Dance|Hardcore', + 1790 => 'Music Videos|Dance|House', + 1791 => "Music Videos|Dance|Jungle/Drum'n'bass", + 1792 => 'Music Videos|Dance|Techno', + 1793 => 'Music Videos|Dance|Trance', + 1794 => 'Music Videos|Easy Listening|Lounge', + 1795 => 'Music Videos|Easy Listening|Swing', + 1796 => 'Music Videos|Electronic|Ambient', + 1797 => 'Music Videos|Electronic|Downtempo', + 1798 => 'Music Videos|Electronic|Electronica', + 1799 => 'Music Videos|Electronic|IDM/Experimental', + 1800 => 'Music Videos|Electronic|Industrial', + 1801 => 'Music Videos|Hip-Hop/Rap|Alternative Rap', + 1802 => 'Music Videos|Hip-Hop/Rap|Dirty South', + 1803 => 'Music Videos|Hip-Hop/Rap|East Coast Rap', + 1804 => 'Music Videos|Hip-Hop/Rap|Gangsta Rap', + 1805 => 'Music Videos|Hip-Hop/Rap|Hardcore Rap', + 1806 => 'Music Videos|Hip-Hop/Rap|Hip-Hop', + 1807 => 'Music Videos|Hip-Hop/Rap|Latin Rap', + 1808 => 'Music Videos|Hip-Hop/Rap|Old School Rap', + 1809 => 'Music Videos|Hip-Hop/Rap|Rap', + 1810 => 'Music Videos|Hip-Hop/Rap|Underground Rap', + 1811 => 'Music Videos|Hip-Hop/Rap|West Coast Rap', + 1812 => 'Music Videos|Holiday|Chanukah', + 1813 => 'Music Videos|Holiday|Christmas', + 1814 => "Music Videos|Holiday|Christmas: Children's", + 1815 => 'Music Videos|Holiday|Christmas: Classic', + 1816 => 'Music Videos|Holiday|Christmas: Classical', + 1817 => 'Music Videos|Holiday|Christmas: Jazz', + 1818 => 'Music Videos|Holiday|Christmas: Modern', + 1819 => 'Music Videos|Holiday|Christmas: Pop', + 1820 => 'Music Videos|Holiday|Christmas: R&B', + 1821 => 'Music Videos|Holiday|Christmas: Religious', + 1822 => 'Music Videos|Holiday|Christmas: Rock', + 1823 => 'Music Videos|Holiday|Easter', + 1824 => 'Music Videos|Holiday|Halloween', + 1825 => 'Music Videos|Holiday|Thanksgiving', + 1826 => 'Music Videos|Jazz|Avant-Garde Jazz', + 1828 => 'Music Videos|Jazz|Bop', + 1829 => 'Music Videos|Jazz|Contemporary Jazz', + 1830 => 'Music Videos|Jazz|Cool Jazz', + 1831 => 'Music Videos|Jazz|Crossover Jazz', + 1832 => 'Music Videos|Jazz|Dixieland', + 1833 => 'Music Videos|Jazz|Fusion', + 1834 => 'Music Videos|Jazz|Hard Bop', + 1835 => 'Music Videos|Jazz|Latin Jazz', + 1836 => 'Music Videos|Jazz|Mainstream Jazz', + 1837 => 'Music Videos|Jazz|Ragtime', + 1838 => 'Music Videos|Jazz|Smooth Jazz', + 1839 => 'Music Videos|Jazz|Trad Jazz', + 1840 => 'Music Videos|Latin|Alternative & Rock in Spanish', + 1841 => 'Music Videos|Latin|Baladas y Boleros', + 1842 => 'Music Videos|Latin|Contemporary Latin', + 1843 => 'Music Videos|Latin|Latin Jazz', + 1844 => 'Music Videos|Latin|Latin Urban', + 1845 => 'Music Videos|Latin|Pop in Spanish', + 1846 => 'Music Videos|Latin|Raices', + 1847 => 'Music Videos|Latin|Musica Mexicana', # (M&uacute;sica Mexicana) + 1848 => 'Music Videos|Latin|Salsa y Tropical', + 1849 => 'Music Videos|New Age|Healing', + 1850 => 'Music Videos|New Age|Meditation', + 1851 => 'Music Videos|New Age|Nature', + 1852 => 'Music Videos|New Age|Relaxation', + 1853 => 'Music Videos|New Age|Travel', + 1854 => 'Music Videos|Pop|Adult Contemporary', + 1855 => 'Music Videos|Pop|Britpop', + 1856 => 'Music Videos|Pop|Pop/Rock', + 1857 => 'Music Videos|Pop|Soft Rock', + 1858 => 'Music Videos|Pop|Teen Pop', + 1859 => 'Music Videos|R&B/Soul|Contemporary R&B', + 1860 => 'Music Videos|R&B/Soul|Disco', + 1861 => 'Music Videos|R&B/Soul|Doo Wop', + 1862 => 'Music Videos|R&B/Soul|Funk', + 1863 => 'Music Videos|R&B/Soul|Motown', + 1864 => 'Music Videos|R&B/Soul|Neo-Soul', + 1865 => 'Music Videos|R&B/Soul|Soul', + 1866 => 'Music Videos|Reggae|Modern Dancehall', + 1867 => 'Music Videos|Reggae|Dub', + 1868 => 'Music Videos|Reggae|Roots Reggae', + 1869 => 'Music Videos|Reggae|Ska', + 1870 => 'Music Videos|Rock|Adult Alternative', + 1871 => 'Music Videos|Rock|American Trad Rock', + 1872 => 'Music Videos|Rock|Arena Rock', + 1873 => 'Music Videos|Rock|Blues-Rock', + 1874 => 'Music Videos|Rock|British Invasion', + 1875 => 'Music Videos|Rock|Death Metal/Black Metal', + 1876 => 'Music Videos|Rock|Glam Rock', + 1877 => 'Music Videos|Rock|Hair Metal', + 1878 => 'Music Videos|Rock|Hard Rock', + 1879 => 'Music Videos|Rock|Jam Bands', + 1880 => 'Music Videos|Rock|Prog-Rock/Art Rock', + 1881 => 'Music Videos|Rock|Psychedelic', + 1882 => 'Music Videos|Rock|Rock & Roll', + 1883 => 'Music Videos|Rock|Rockabilly', + 1884 => 'Music Videos|Rock|Roots Rock', + 1885 => 'Music Videos|Rock|Singer/Songwriter', + 1886 => 'Music Videos|Rock|Southern Rock', + 1887 => 'Music Videos|Rock|Surf', + 1888 => 'Music Videos|Rock|Tex-Mex', + 1889 => 'Music Videos|Singer/Songwriter|Alternative Folk', + 1890 => 'Music Videos|Singer/Songwriter|Contemporary Folk', + 1891 => 'Music Videos|Singer/Songwriter|Contemporary Singer/Songwriter', + 1892 => 'Music Videos|Singer/Songwriter|Folk-Rock', + 1893 => 'Music Videos|Singer/Songwriter|New Acoustic', + 1894 => 'Music Videos|Singer/Songwriter|Traditional Folk', + 1895 => 'Music Videos|Soundtrack|Foreign Cinema', + 1896 => 'Music Videos|Soundtrack|Musicals', + 1897 => 'Music Videos|Soundtrack|Original Score', + 1898 => 'Music Videos|Soundtrack|Soundtrack', + 1899 => 'Music Videos|Soundtrack|TV Soundtrack', + 1900 => 'Music Videos|Vocal|Standards', + 1901 => 'Music Videos|Vocal|Traditional Pop', + 1902 => 'Music Videos|Jazz|Vocal Jazz', + 1903 => 'Music Videos|Vocal|Vocal Pop', + 1904 => 'Music Videos|African', + 1905 => 'Music Videos|African|Afro-Beat', + 1906 => 'Music Videos|African|Afro-Pop', + 1907 => 'Music Videos|World|Asia', + 1908 => 'Music Videos|World|Australia', + 1909 => 'Music Videos|World|Cajun', + 1910 => 'Music Videos|World|Caribbean', + 1911 => 'Music Videos|World|Celtic', + 1912 => 'Music Videos|World|Celtic Folk', + 1913 => 'Music Videos|World|Contemporary Celtic', + 1914 => 'Music Videos|World|Europe', + 1915 => 'Music Videos|World|France', + 1916 => 'Music Videos|World|Hawaii', + 1917 => 'Music Videos|World|Japan', + 1918 => 'Music Videos|World|Klezmer', + 1919 => 'Music Videos|World|North America', + 1920 => 'Music Videos|World|Polka', + 1921 => 'Music Videos|World|South Africa', + 1922 => 'Music Videos|World|South America', + 1923 => 'Music Videos|World|Traditional Celtic', + 1924 => 'Music Videos|World|Worldbeat', + 1925 => 'Music Videos|World|Zydeco', + 1926 => 'Music Videos|Christian & Gospel', + 1928 => 'Music Videos|Classical|Art Song', + 1929 => 'Music Videos|Classical|Brass & Woodwinds', + 1930 => 'Music Videos|Classical|Solo Instrumental', + 1931 => 'Music Videos|Classical|Contemporary Era', + 1932 => 'Music Videos|Classical|Oratorio', + 1933 => 'Music Videos|Classical|Cantata', + 1934 => 'Music Videos|Classical|Electronic', + 1935 => 'Music Videos|Classical|Sacred', + 1936 => 'Music Videos|Classical|Guitar', + 1938 => 'Music Videos|Classical|Violin', + 1939 => 'Music Videos|Classical|Cello', + 1940 => 'Music Videos|Classical|Percussion', + 1941 => 'Music Videos|Electronic|Dubstep', + 1942 => 'Music Videos|Electronic|Bass', + 1943 => 'Music Videos|Hip-Hop/Rap|UK Hip-Hop', + 1944 => 'Music Videos|Reggae|Lovers Rock', + 1945 => 'Music Videos|Alternative|EMO', + 1946 => 'Music Videos|Alternative|Pop Punk', + 1947 => 'Music Videos|Alternative|Indie Pop', + 1948 => 'Music Videos|New Age|Yoga', + 1949 => 'Music Videos|Pop|Tribute', + 1950 => 'Music Videos|Pop|Shows', + 1951 => 'Music Videos|Cuban', + 1952 => 'Music Videos|Cuban|Mambo', + 1953 => 'Music Videos|Cuban|Chachacha', + 1954 => 'Music Videos|Cuban|Guajira', + 1955 => 'Music Videos|Cuban|Son', + 1956 => 'Music Videos|Cuban|Bolero', + 1957 => 'Music Videos|Cuban|Guaracha', + 1958 => 'Music Videos|Cuban|Timba', + 1959 => 'Music Videos|Soundtrack|Video Game', + 1960 => 'Music Videos|Indian|Regional Indian|Punjabi|Punjabi Pop', + 1961 => 'Music Videos|Indian|Regional Indian|Bengali|Rabindra Sangeet', + 1962 => 'Music Videos|Indian|Regional Indian|Malayalam', + 1963 => 'Music Videos|Indian|Regional Indian|Kannada', + 1964 => 'Music Videos|Indian|Regional Indian|Marathi', + 1965 => 'Music Videos|Indian|Regional Indian|Gujarati', + 1966 => 'Music Videos|Indian|Regional Indian|Assamese', + 1967 => 'Music Videos|Indian|Regional Indian|Bhojpuri', + 1968 => 'Music Videos|Indian|Regional Indian|Haryanvi', + 1969 => 'Music Videos|Indian|Regional Indian|Odia', + 1970 => 'Music Videos|Indian|Regional Indian|Rajasthani', + 1971 => 'Music Videos|Indian|Regional Indian|Urdu', + 1972 => 'Music Videos|Indian|Regional Indian|Punjabi', + 1973 => 'Music Videos|Indian|Regional Indian|Bengali', + 1974 => 'Music Videos|Indian|Indian Classical|Carnatic Classical', + 1975 => 'Music Videos|Indian|Indian Classical|Hindustani Classical', + 1976 => 'Music Videos|African|Afro House', + 1977 => 'Music Videos|African|Afro Soul', + 1978 => 'Music Videos|African|Afrobeats', + 1979 => 'Music Videos|African|Benga', + 1980 => 'Music Videos|African|Bongo-Flava', + 1981 => 'Music Videos|African|Coupe-Decale', + 1982 => 'Music Videos|African|Gqom', + 1983 => 'Music Videos|African|Highlife', + 1984 => 'Music Videos|African|Kuduro', + 1985 => 'Music Videos|African|Kizomba', + 1986 => 'Music Videos|African|Kwaito', + 1987 => 'Music Videos|African|Mbalax', + 1988 => 'Music Videos|African|Ndombolo', + 1989 => 'Music Videos|African|Shangaan Electro', + 1990 => 'Music Videos|African|Soukous', + 1991 => 'Music Videos|African|Taarab', + 1992 => 'Music Videos|African|Zouglou', + 1993 => 'Music Videos|Turkish|Ozgun', + 1994 => 'Music Videos|Turkish|Fantezi', + 1995 => 'Music Videos|Turkish|Religious', + 1996 => 'Music Videos|Pop|Turkish Pop', + 1997 => 'Music Videos|Rock|Turkish Rock', + 1998 => 'Music Videos|Alternative|Turkish Alternative', + 1999 => 'Music Videos|Hip-Hop/Rap|Turkish Hip-Hop/Rap', + 2000 => 'Music Videos|African|Maskandi', + 2001 => 'Music Videos|Russian|Russian Romance', + 2002 => 'Music Videos|Russian|Russian Bard', + 2003 => 'Music Videos|Russian|Russian Pop', + 2004 => 'Music Videos|Russian|Russian Rock', + 2005 => 'Music Videos|Russian|Russian Hip-Hop', + 2006 => 'Music Videos|Arabic|Levant', + 2007 => 'Music Videos|Arabic|Levant|Dabke', + 2008 => 'Music Videos|Arabic|Maghreb Rai', + 2009 => 'Music Videos|Arabic|Khaleeji|Khaleeji Jalsat', + 2010 => 'Music Videos|Arabic|Khaleeji|Khaleeji Shailat', + 2011 => 'Music Videos|Tarab', + 2012 => 'Music Videos|Tarab|Iraqi Tarab', + 2013 => 'Music Videos|Tarab|Egyptian Tarab', + 2014 => 'Music Videos|Tarab|Khaleeji Tarab', + 2015 => 'Music Videos|Pop|Levant Pop', + 2016 => 'Music Videos|Pop|Iraqi Pop', + 2017 => 'Music Videos|Pop|Egyptian Pop', + 2018 => 'Music Videos|Pop|Maghreb Pop', + 2019 => 'Music Videos|Pop|Khaleeji Pop', + 2020 => 'Music Videos|Hip-Hop/Rap|Levant Hip-Hop', + 2021 => 'Music Videos|Hip-Hop/Rap|Egyptian Hip-Hop', + 2022 => 'Music Videos|Hip-Hop/Rap|Maghreb Hip-Hop', + 2023 => 'Music Videos|Hip-Hop/Rap|Khaleeji Hip-Hop', + 2024 => 'Music Videos|Alternative|Indie Levant', + 2025 => 'Music Videos|Alternative|Indie Egyptian', + 2026 => 'Music Videos|Alternative|Indie Maghreb', + 2027 => 'Music Videos|Electronic|Levant Electronic', + 2028 => "Music Videos|Electronic|Electro-Cha'abi", + 2029 => 'Music Videos|Electronic|Maghreb Electronic', + 2030 => 'Music Videos|Folk|Iraqi Folk', + 2031 => 'Music Videos|Folk|Khaleeji Folk', + 2032 => 'Music Videos|Dance|Maghreb Dance', + 4000 => 'TV Shows|Comedy', + 4001 => 'TV Shows|Drama', + 4002 => 'TV Shows|Animation', + 4003 => 'TV Shows|Action & Adventure', + 4004 => 'TV Shows|Classics', + 4005 => 'TV Shows|Kids & Family', + 4006 => 'TV Shows|Nonfiction', + 4007 => 'TV Shows|Reality TV', + 4008 => 'TV Shows|Sci-Fi & Fantasy', + 4009 => 'TV Shows|Sports', + 4010 => 'TV Shows|Teens', + 4011 => 'TV Shows|Latino TV', + 4401 => 'Movies|Action & Adventure', + 4402 => 'Movies|Anime', + 4403 => 'Movies|Classics', + 4404 => 'Movies|Comedy', + 4405 => 'Movies|Documentary', + 4406 => 'Movies|Drama', + 4407 => 'Movies|Foreign', + 4408 => 'Movies|Horror', + 4409 => 'Movies|Independent', + 4410 => 'Movies|Kids & Family', + 4411 => 'Movies|Musicals', + 4412 => 'Movies|Romance', + 4413 => 'Movies|Sci-Fi & Fantasy', + 4414 => 'Movies|Short Films', + 4415 => 'Movies|Special Interest', + 4416 => 'Movies|Thriller', + 4417 => 'Movies|Sports', + 4418 => 'Movies|Western', + 4419 => 'Movies|Urban', + 4420 => 'Movies|Holiday', + 4421 => 'Movies|Made for TV', + 4422 => 'Movies|Concert Films', + 4423 => 'Movies|Music Documentaries', + 4424 => 'Movies|Music Feature Films', + 4425 => 'Movies|Japanese Cinema', + 4426 => 'Movies|Jidaigeki', + 4427 => 'Movies|Tokusatsu', + 4428 => 'Movies|Korean Cinema', + 4429 => 'Movies|Russian', + 4430 => 'Movies|Turkish', + 4431 => 'Movies|Bollywood', + 4432 => 'Movies|Regional Indian', + 4433 => 'Movies|Middle Eastern', + 4434 => 'Movies|African', + 6000 => 'App Store|Business', + 6001 => 'App Store|Weather', + 6002 => 'App Store|Utilities', + 6003 => 'App Store|Travel', + 6004 => 'App Store|Sports', + 6005 => 'App Store|Social Networking', + 6006 => 'App Store|Reference', + 6007 => 'App Store|Productivity', + 6008 => 'App Store|Photo & Video', + 6009 => 'App Store|News', + 6010 => 'App Store|Navigation', + 6011 => 'App Store|Music', + 6012 => 'App Store|Lifestyle', + 6013 => 'App Store|Health & Fitness', + 6014 => 'App Store|Games', + 6015 => 'App Store|Finance', + 6016 => 'App Store|Entertainment', + 6017 => 'App Store|Education', + 6018 => 'App Store|Books', + 6020 => 'App Store|Medical', + 6021 => 'App Store|Magazines & Newspapers', + 6022 => 'App Store|Catalogs', + 6023 => 'App Store|Food & Drink', + 6024 => 'App Store|Shopping', + 6025 => 'App Store|Stickers', + 6026 => 'App Store|Developer Tools', + 6027 => 'App Store|Graphics & Design', + 7001 => 'App Store|Games|Action', + 7002 => 'App Store|Games|Adventure', + 7003 => 'App Store|Games|Casual', + 7004 => 'App Store|Games|Board', + 7005 => 'App Store|Games|Card', + 7006 => 'App Store|Games|Casino', + 7007 => 'App Store|Games|Dice', + 7008 => 'App Store|Games|Educational', + 7009 => 'App Store|Games|Family', + 7011 => 'App Store|Games|Music', + 7012 => 'App Store|Games|Puzzle', + 7013 => 'App Store|Games|Racing', + 7014 => 'App Store|Games|Role Playing', + 7015 => 'App Store|Games|Simulation', + 7016 => 'App Store|Games|Sports', + 7017 => 'App Store|Games|Strategy', + 7018 => 'App Store|Games|Trivia', + 7019 => 'App Store|Games|Word', + 8001 => 'Tones|Ringtones|Alternative', + 8002 => 'Tones|Ringtones|Blues', + 8003 => "Tones|Ringtones|Children's Music", + 8004 => 'Tones|Ringtones|Classical', + 8005 => 'Tones|Ringtones|Comedy', + 8006 => 'Tones|Ringtones|Country', + 8007 => 'Tones|Ringtones|Dance', + 8008 => 'Tones|Ringtones|Electronic', + 8009 => 'Tones|Ringtones|Enka', + 8010 => 'Tones|Ringtones|French Pop', + 8011 => 'Tones|Ringtones|German Folk', + 8012 => 'Tones|Ringtones|German Pop', + 8013 => 'Tones|Ringtones|Hip-Hop/Rap', + 8014 => 'Tones|Ringtones|Holiday', + 8015 => 'Tones|Ringtones|Inspirational', + 8016 => 'Tones|Ringtones|J-Pop', + 8017 => 'Tones|Ringtones|Jazz', + 8018 => 'Tones|Ringtones|Kayokyoku', + 8019 => 'Tones|Ringtones|Latin', + 8020 => 'Tones|Ringtones|New Age', + 8021 => 'Tones|Ringtones|Classical|Opera', + 8022 => 'Tones|Ringtones|Pop', + 8023 => 'Tones|Ringtones|R&B/Soul', + 8024 => 'Tones|Ringtones|Reggae', + 8025 => 'Tones|Ringtones|Rock', + 8026 => 'Tones|Ringtones|Singer/Songwriter', + 8027 => 'Tones|Ringtones|Soundtrack', + 8028 => 'Tones|Ringtones|Spoken Word', + 8029 => 'Tones|Ringtones|Vocal', + 8030 => 'Tones|Ringtones|World', + 8050 => 'Tones|Alert Tones|Sound Effects', + 8051 => 'Tones|Alert Tones|Dialogue', + 8052 => 'Tones|Alert Tones|Music', + 8053 => 'Tones|Ringtones', + 8054 => 'Tones|Alert Tones', + 8055 => 'Tones|Ringtones|Alternative|Chinese Alt', + 8056 => 'Tones|Ringtones|Alternative|College Rock', + 8057 => 'Tones|Ringtones|Alternative|Goth Rock', + 8058 => 'Tones|Ringtones|Alternative|Grunge', + 8059 => 'Tones|Ringtones|Alternative|Indie Rock', + 8060 => 'Tones|Ringtones|Alternative|Korean Indie', + 8061 => 'Tones|Ringtones|Alternative|New Wave', + 8062 => 'Tones|Ringtones|Alternative|Punk', + 8063 => 'Tones|Ringtones|Anime', + 8064 => 'Tones|Ringtones|Arabic', + 8065 => 'Tones|Ringtones|Arabic|Arabic Pop', + 8066 => 'Tones|Ringtones|Arabic|Islamic', + 8067 => 'Tones|Ringtones|Arabic|Khaleeji', + 8068 => 'Tones|Ringtones|Arabic|North African', + 8069 => 'Tones|Ringtones|Blues|Acoustic Blues', + 8070 => 'Tones|Ringtones|Blues|Chicago Blues', + 8071 => 'Tones|Ringtones|Blues|Classic Blues', + 8072 => 'Tones|Ringtones|Blues|Contemporary Blues', + 8073 => 'Tones|Ringtones|Blues|Country Blues', + 8074 => 'Tones|Ringtones|Blues|Delta Blues', + 8075 => 'Tones|Ringtones|Blues|Electric Blues', + 8076 => 'Tones|Ringtones|Brazilian', + 8077 => 'Tones|Ringtones|Brazilian|Axe', # (Ax&eacute;) + 8078 => 'Tones|Ringtones|Brazilian|Baile Funk', + 8079 => 'Tones|Ringtones|Brazilian|Bossa Nova', + 8080 => 'Tones|Ringtones|Brazilian|Choro', + 8081 => 'Tones|Ringtones|Brazilian|Forro', # (Forr&oacute;) + 8082 => 'Tones|Ringtones|Brazilian|Frevo', + 8083 => 'Tones|Ringtones|Brazilian|MPB', + 8084 => 'Tones|Ringtones|Brazilian|Pagode', + 8085 => 'Tones|Ringtones|Brazilian|Samba', + 8086 => 'Tones|Ringtones|Brazilian|Sertanejo', + 8087 => "Tones|Ringtones|Children's Music|Lullabies", + 8088 => "Tones|Ringtones|Children's Music|Sing-Along", + 8089 => "Tones|Ringtones|Children's Music|Stories", + 8090 => 'Tones|Ringtones|Chinese', + 8091 => 'Tones|Ringtones|Chinese|Chinese Classical', + 8092 => 'Tones|Ringtones|Chinese|Chinese Flute', + 8093 => 'Tones|Ringtones|Chinese|Chinese Opera', + 8094 => 'Tones|Ringtones|Chinese|Chinese Orchestral', + 8095 => 'Tones|Ringtones|Chinese|Chinese Regional Folk', + 8096 => 'Tones|Ringtones|Chinese|Chinese Strings', + 8097 => 'Tones|Ringtones|Chinese|Taiwanese Folk', + 8098 => 'Tones|Ringtones|Chinese|Tibetan Native Music', + 8099 => 'Tones|Ringtones|Christian & Gospel', + 8100 => 'Tones|Ringtones|Christian & Gospel|CCM', + 8101 => 'Tones|Ringtones|Christian & Gospel|Christian Metal', + 8102 => 'Tones|Ringtones|Christian & Gospel|Christian Pop', + 8103 => 'Tones|Ringtones|Christian & Gospel|Christian Rap', + 8104 => 'Tones|Ringtones|Christian & Gospel|Christian Rock', + 8105 => 'Tones|Ringtones|Christian & Gospel|Classic Christian', + 8106 => 'Tones|Ringtones|Christian & Gospel|Contemporary Gospel', + 8107 => 'Tones|Ringtones|Christian & Gospel|Gospel', + 8108 => 'Tones|Ringtones|Christian & Gospel|Praise & Worship', + 8109 => 'Tones|Ringtones|Christian & Gospel|Southern Gospel', + 8110 => 'Tones|Ringtones|Christian & Gospel|Traditional Gospel', + 8111 => 'Tones|Ringtones|Classical|Avant-Garde', + 8112 => 'Tones|Ringtones|Classical|Baroque Era', + 8113 => 'Tones|Ringtones|Classical|Chamber Music', + 8114 => 'Tones|Ringtones|Classical|Chant', + 8115 => 'Tones|Ringtones|Classical|Choral', + 8116 => 'Tones|Ringtones|Classical|Classical Crossover', + 8117 => 'Tones|Ringtones|Classical|Early Music', + 8118 => 'Tones|Ringtones|Classical|High Classical', + 8119 => 'Tones|Ringtones|Classical|Impressionist', + 8120 => 'Tones|Ringtones|Classical|Medieval Era', + 8121 => 'Tones|Ringtones|Classical|Minimalism', + 8122 => 'Tones|Ringtones|Classical|Modern Era', + 8123 => 'Tones|Ringtones|Classical|Orchestral', + 8124 => 'Tones|Ringtones|Classical|Renaissance', + 8125 => 'Tones|Ringtones|Classical|Romantic Era', + 8126 => 'Tones|Ringtones|Classical|Wedding Music', + 8127 => 'Tones|Ringtones|Comedy|Novelty', + 8128 => 'Tones|Ringtones|Comedy|Standup Comedy', + 8129 => 'Tones|Ringtones|Country|Alternative Country', + 8130 => 'Tones|Ringtones|Country|Americana', + 8131 => 'Tones|Ringtones|Country|Bluegrass', + 8132 => 'Tones|Ringtones|Country|Contemporary Bluegrass', + 8133 => 'Tones|Ringtones|Country|Contemporary Country', + 8134 => 'Tones|Ringtones|Country|Country Gospel', + 8135 => 'Tones|Ringtones|Country|Honky Tonk', + 8136 => 'Tones|Ringtones|Country|Outlaw Country', + 8137 => 'Tones|Ringtones|Country|Thai Country', + 8138 => 'Tones|Ringtones|Country|Traditional Bluegrass', + 8139 => 'Tones|Ringtones|Country|Traditional Country', + 8140 => 'Tones|Ringtones|Country|Urban Cowboy', + 8141 => 'Tones|Ringtones|Dance|Breakbeat', + 8142 => 'Tones|Ringtones|Dance|Exercise', + 8143 => 'Tones|Ringtones|Dance|Garage', + 8144 => 'Tones|Ringtones|Dance|Hardcore', + 8145 => 'Tones|Ringtones|Dance|House', + 8146 => "Tones|Ringtones|Dance|Jungle/Drum'n'bass", + 8147 => 'Tones|Ringtones|Dance|Techno', + 8148 => 'Tones|Ringtones|Dance|Trance', + 8149 => 'Tones|Ringtones|Disney', + 8150 => 'Tones|Ringtones|Easy Listening', + 8151 => 'Tones|Ringtones|Easy Listening|Lounge', + 8152 => 'Tones|Ringtones|Easy Listening|Swing', + 8153 => 'Tones|Ringtones|Electronic|Ambient', + 8154 => 'Tones|Ringtones|Electronic|Downtempo', + 8155 => 'Tones|Ringtones|Electronic|Electronica', + 8156 => 'Tones|Ringtones|Electronic|IDM/Experimental', + 8157 => 'Tones|Ringtones|Electronic|Industrial', + 8158 => 'Tones|Ringtones|Fitness & Workout', + 8159 => 'Tones|Ringtones|Folk', + 8160 => 'Tones|Ringtones|Hip-Hop/Rap|Alternative Rap', + 8161 => 'Tones|Ringtones|Hip-Hop/Rap|Chinese Hip-Hop', + 8162 => 'Tones|Ringtones|Hip-Hop/Rap|Dirty South', + 8163 => 'Tones|Ringtones|Hip-Hop/Rap|East Coast Rap', + 8164 => 'Tones|Ringtones|Hip-Hop/Rap|Gangsta Rap', + 8165 => 'Tones|Ringtones|Hip-Hop/Rap|Hardcore Rap', + 8166 => 'Tones|Ringtones|Hip-Hop/Rap|Hip-Hop', + 8167 => 'Tones|Ringtones|Hip-Hop/Rap|Korean Hip-Hop', + 8168 => 'Tones|Ringtones|Hip-Hop/Rap|Latin Rap', + 8169 => 'Tones|Ringtones|Hip-Hop/Rap|Old School Rap', + 8170 => 'Tones|Ringtones|Hip-Hop/Rap|Rap', + 8171 => 'Tones|Ringtones|Hip-Hop/Rap|Underground Rap', + 8172 => 'Tones|Ringtones|Hip-Hop/Rap|West Coast Rap', + 8173 => 'Tones|Ringtones|Holiday|Chanukah', + 8174 => 'Tones|Ringtones|Holiday|Christmas', + 8175 => "Tones|Ringtones|Holiday|Christmas: Children's", + 8176 => 'Tones|Ringtones|Holiday|Christmas: Classic', + 8177 => 'Tones|Ringtones|Holiday|Christmas: Classical', + 8178 => 'Tones|Ringtones|Holiday|Christmas: Jazz', + 8179 => 'Tones|Ringtones|Holiday|Christmas: Modern', + 8180 => 'Tones|Ringtones|Holiday|Christmas: Pop', + 8181 => 'Tones|Ringtones|Holiday|Christmas: R&B', + 8182 => 'Tones|Ringtones|Holiday|Christmas: Religious', + 8183 => 'Tones|Ringtones|Holiday|Christmas: Rock', + 8184 => 'Tones|Ringtones|Holiday|Easter', + 8185 => 'Tones|Ringtones|Holiday|Halloween', + 8186 => 'Tones|Ringtones|Holiday|Thanksgiving', + 8187 => 'Tones|Ringtones|Indian', + 8188 => 'Tones|Ringtones|Indian|Bollywood', + 8189 => 'Tones|Ringtones|Indian|Devotional & Spiritual', + 8190 => 'Tones|Ringtones|Indian|Ghazals', + 8191 => 'Tones|Ringtones|Indian|Indian Classical', + 8192 => 'Tones|Ringtones|Indian|Indian Folk', + 8193 => 'Tones|Ringtones|Indian|Indian Pop', + 8194 => 'Tones|Ringtones|Indian|Regional Indian', + 8195 => 'Tones|Ringtones|Indian|Sufi', + 8196 => 'Tones|Ringtones|Indian|Regional Indian|Tamil', + 8197 => 'Tones|Ringtones|Indian|Regional Indian|Telugu', + 8198 => 'Tones|Ringtones|Instrumental', + 8199 => 'Tones|Ringtones|Jazz|Avant-Garde Jazz', + 8201 => 'Tones|Ringtones|Jazz|Big Band', + 8202 => 'Tones|Ringtones|Jazz|Bop', + 8203 => 'Tones|Ringtones|Jazz|Contemporary Jazz', + 8204 => 'Tones|Ringtones|Jazz|Cool Jazz', + 8205 => 'Tones|Ringtones|Jazz|Crossover Jazz', + 8206 => 'Tones|Ringtones|Jazz|Dixieland', + 8207 => 'Tones|Ringtones|Jazz|Fusion', + 8208 => 'Tones|Ringtones|Jazz|Hard Bop', + 8209 => 'Tones|Ringtones|Jazz|Latin Jazz', + 8210 => 'Tones|Ringtones|Jazz|Mainstream Jazz', + 8211 => 'Tones|Ringtones|Jazz|Ragtime', + 8212 => 'Tones|Ringtones|Jazz|Smooth Jazz', + 8213 => 'Tones|Ringtones|Jazz|Trad Jazz', + 8214 => 'Tones|Ringtones|Pop|K-Pop', + 8215 => 'Tones|Ringtones|Karaoke', + 8216 => 'Tones|Ringtones|Korean', + 8217 => 'Tones|Ringtones|Korean|Korean Classical', + 8218 => 'Tones|Ringtones|Korean|Korean Trad Instrumental', + 8219 => 'Tones|Ringtones|Korean|Korean Trad Song', + 8220 => 'Tones|Ringtones|Korean|Korean Trad Theater', + 8221 => 'Tones|Ringtones|Latin|Alternative & Rock in Spanish', + 8222 => 'Tones|Ringtones|Latin|Baladas y Boleros', + 8223 => 'Tones|Ringtones|Latin|Contemporary Latin', + 8224 => 'Tones|Ringtones|Latin|Latin Jazz', + 8225 => 'Tones|Ringtones|Latin|Latin Urban', + 8226 => 'Tones|Ringtones|Latin|Pop in Spanish', + 8227 => 'Tones|Ringtones|Latin|Raices', + 8228 => 'Tones|Ringtones|Latin|Musica Mexicana', # (M&uacute;sica Mexicana) + 8229 => 'Tones|Ringtones|Latin|Salsa y Tropical', + 8230 => 'Tones|Ringtones|Marching Bands', + 8231 => 'Tones|Ringtones|New Age|Healing', + 8232 => 'Tones|Ringtones|New Age|Meditation', + 8233 => 'Tones|Ringtones|New Age|Nature', + 8234 => 'Tones|Ringtones|New Age|Relaxation', + 8235 => 'Tones|Ringtones|New Age|Travel', + 8236 => 'Tones|Ringtones|Orchestral', + 8237 => 'Tones|Ringtones|Pop|Adult Contemporary', + 8238 => 'Tones|Ringtones|Pop|Britpop', + 8239 => 'Tones|Ringtones|Pop|C-Pop', + 8240 => 'Tones|Ringtones|Pop|Cantopop/HK-Pop', + 8241 => 'Tones|Ringtones|Pop|Indo Pop', + 8242 => 'Tones|Ringtones|Pop|Korean Folk-Pop', + 8243 => 'Tones|Ringtones|Pop|Malaysian Pop', + 8244 => 'Tones|Ringtones|Pop|Mandopop', + 8245 => 'Tones|Ringtones|Pop|Manilla Sound', + 8246 => 'Tones|Ringtones|Pop|Oldies', + 8247 => 'Tones|Ringtones|Pop|Original Pilipino Music', + 8248 => 'Tones|Ringtones|Pop|Pinoy Pop', + 8249 => 'Tones|Ringtones|Pop|Pop/Rock', + 8250 => 'Tones|Ringtones|Pop|Soft Rock', + 8251 => 'Tones|Ringtones|Pop|Tai-Pop', + 8252 => 'Tones|Ringtones|Pop|Teen Pop', + 8253 => 'Tones|Ringtones|Pop|Thai Pop', + 8254 => 'Tones|Ringtones|R&B/Soul|Contemporary R&B', + 8255 => 'Tones|Ringtones|R&B/Soul|Disco', + 8256 => 'Tones|Ringtones|R&B/Soul|Doo Wop', + 8257 => 'Tones|Ringtones|R&B/Soul|Funk', + 8258 => 'Tones|Ringtones|R&B/Soul|Motown', + 8259 => 'Tones|Ringtones|R&B/Soul|Neo-Soul', + 8260 => 'Tones|Ringtones|R&B/Soul|Soul', + 8261 => 'Tones|Ringtones|Reggae|Modern Dancehall', + 8262 => 'Tones|Ringtones|Reggae|Dub', + 8263 => 'Tones|Ringtones|Reggae|Roots Reggae', + 8264 => 'Tones|Ringtones|Reggae|Ska', + 8265 => 'Tones|Ringtones|Rock|Adult Alternative', + 8266 => 'Tones|Ringtones|Rock|American Trad Rock', + 8267 => 'Tones|Ringtones|Rock|Arena Rock', + 8268 => 'Tones|Ringtones|Rock|Blues-Rock', + 8269 => 'Tones|Ringtones|Rock|British Invasion', + 8270 => 'Tones|Ringtones|Rock|Chinese Rock', + 8271 => 'Tones|Ringtones|Rock|Death Metal/Black Metal', + 8272 => 'Tones|Ringtones|Rock|Glam Rock', + 8273 => 'Tones|Ringtones|Rock|Hair Metal', + 8274 => 'Tones|Ringtones|Rock|Hard Rock', + 8275 => 'Tones|Ringtones|Rock|Metal', + 8276 => 'Tones|Ringtones|Rock|Jam Bands', + 8277 => 'Tones|Ringtones|Rock|Korean Rock', + 8278 => 'Tones|Ringtones|Rock|Prog-Rock/Art Rock', + 8279 => 'Tones|Ringtones|Rock|Psychedelic', + 8280 => 'Tones|Ringtones|Rock|Rock & Roll', + 8281 => 'Tones|Ringtones|Rock|Rockabilly', + 8282 => 'Tones|Ringtones|Rock|Roots Rock', + 8283 => 'Tones|Ringtones|Rock|Singer/Songwriter', + 8284 => 'Tones|Ringtones|Rock|Southern Rock', + 8285 => 'Tones|Ringtones|Rock|Surf', + 8286 => 'Tones|Ringtones|Rock|Tex-Mex', + 8287 => 'Tones|Ringtones|Singer/Songwriter|Alternative Folk', + 8288 => 'Tones|Ringtones|Singer/Songwriter|Contemporary Folk', + 8289 => 'Tones|Ringtones|Singer/Songwriter|Contemporary Singer/Songwriter', + 8290 => 'Tones|Ringtones|Singer/Songwriter|Folk-Rock', + 8291 => 'Tones|Ringtones|Singer/Songwriter|New Acoustic', + 8292 => 'Tones|Ringtones|Singer/Songwriter|Traditional Folk', + 8293 => 'Tones|Ringtones|Soundtrack|Foreign Cinema', + 8294 => 'Tones|Ringtones|Soundtrack|Musicals', + 8295 => 'Tones|Ringtones|Soundtrack|Original Score', + 8296 => 'Tones|Ringtones|Soundtrack|Sound Effects', + 8297 => 'Tones|Ringtones|Soundtrack|Soundtrack', + 8298 => 'Tones|Ringtones|Soundtrack|TV Soundtrack', + 8299 => 'Tones|Ringtones|Vocal|Standards', + 8300 => 'Tones|Ringtones|Vocal|Traditional Pop', + 8301 => 'Tones|Ringtones|Vocal|Trot', + 8302 => 'Tones|Ringtones|Jazz|Vocal Jazz', + 8303 => 'Tones|Ringtones|Vocal|Vocal Pop', + 8304 => 'Tones|Ringtones|African', + 8305 => 'Tones|Ringtones|African|Afrikaans', + 8306 => 'Tones|Ringtones|African|Afro-Beat', + 8307 => 'Tones|Ringtones|African|Afro-Pop', + 8308 => 'Tones|Ringtones|Turkish|Arabesque', + 8309 => 'Tones|Ringtones|World|Asia', + 8310 => 'Tones|Ringtones|World|Australia', + 8311 => 'Tones|Ringtones|World|Cajun', + 8312 => 'Tones|Ringtones|World|Calypso', + 8313 => 'Tones|Ringtones|World|Caribbean', + 8314 => 'Tones|Ringtones|World|Celtic', + 8315 => 'Tones|Ringtones|World|Celtic Folk', + 8316 => 'Tones|Ringtones|World|Contemporary Celtic', + 8317 => 'Tones|Ringtones|World|Dangdut', + 8318 => 'Tones|Ringtones|World|Dini', + 8319 => 'Tones|Ringtones|World|Europe', + 8320 => 'Tones|Ringtones|World|Fado', + 8321 => 'Tones|Ringtones|World|Farsi', + 8322 => 'Tones|Ringtones|World|Flamenco', + 8323 => 'Tones|Ringtones|World|France', + 8324 => 'Tones|Ringtones|Turkish|Halk', + 8325 => 'Tones|Ringtones|World|Hawaii', + 8326 => 'Tones|Ringtones|World|Iberia', + 8327 => 'Tones|Ringtones|World|Indonesian Religious', + 8328 => 'Tones|Ringtones|World|Israeli', + 8329 => 'Tones|Ringtones|World|Japan', + 8330 => 'Tones|Ringtones|World|Klezmer', + 8331 => 'Tones|Ringtones|World|North America', + 8332 => 'Tones|Ringtones|World|Polka', + 8333 => 'Tones|Ringtones|Russian', + 8334 => 'Tones|Ringtones|Russian|Russian Chanson', + 8335 => 'Tones|Ringtones|Turkish|Sanat', + 8336 => 'Tones|Ringtones|World|Soca', + 8337 => 'Tones|Ringtones|World|South Africa', + 8338 => 'Tones|Ringtones|World|South America', + 8339 => 'Tones|Ringtones|World|Tango', + 8340 => 'Tones|Ringtones|World|Traditional Celtic', + 8341 => 'Tones|Ringtones|Turkish', + 8342 => 'Tones|Ringtones|World|Worldbeat', + 8343 => 'Tones|Ringtones|World|Zydeco', + 8345 => 'Tones|Ringtones|Classical|Art Song', + 8346 => 'Tones|Ringtones|Classical|Brass & Woodwinds', + 8347 => 'Tones|Ringtones|Classical|Solo Instrumental', + 8348 => 'Tones|Ringtones|Classical|Contemporary Era', + 8349 => 'Tones|Ringtones|Classical|Oratorio', + 8350 => 'Tones|Ringtones|Classical|Cantata', + 8351 => 'Tones|Ringtones|Classical|Electronic', + 8352 => 'Tones|Ringtones|Classical|Sacred', + 8353 => 'Tones|Ringtones|Classical|Guitar', + 8354 => 'Tones|Ringtones|Classical|Piano', + 8355 => 'Tones|Ringtones|Classical|Violin', + 8356 => 'Tones|Ringtones|Classical|Cello', + 8357 => 'Tones|Ringtones|Classical|Percussion', + 8358 => 'Tones|Ringtones|Electronic|Dubstep', + 8359 => 'Tones|Ringtones|Electronic|Bass', + 8360 => 'Tones|Ringtones|Hip-Hop/Rap|UK Hip Hop', + 8361 => 'Tones|Ringtones|Reggae|Lovers Rock', + 8362 => 'Tones|Ringtones|Alternative|EMO', + 8363 => 'Tones|Ringtones|Alternative|Pop Punk', + 8364 => 'Tones|Ringtones|Alternative|Indie Pop', + 8365 => 'Tones|Ringtones|New Age|Yoga', + 8366 => 'Tones|Ringtones|Pop|Tribute', + 8367 => 'Tones|Ringtones|Pop|Shows', + 8368 => 'Tones|Ringtones|Cuban', + 8369 => 'Tones|Ringtones|Cuban|Mambo', + 8370 => 'Tones|Ringtones|Cuban|Chachacha', + 8371 => 'Tones|Ringtones|Cuban|Guajira', + 8372 => 'Tones|Ringtones|Cuban|Son', + 8373 => 'Tones|Ringtones|Cuban|Bolero', + 8374 => 'Tones|Ringtones|Cuban|Guaracha', + 8375 => 'Tones|Ringtones|Cuban|Timba', + 8376 => 'Tones|Ringtones|Soundtrack|Video Game', + 8377 => 'Tones|Ringtones|Indian|Regional Indian|Punjabi|Punjabi Pop', + 8378 => 'Tones|Ringtones|Indian|Regional Indian|Bengali|Rabindra Sangeet', + 8379 => 'Tones|Ringtones|Indian|Regional Indian|Malayalam', + 8380 => 'Tones|Ringtones|Indian|Regional Indian|Kannada', + 8381 => 'Tones|Ringtones|Indian|Regional Indian|Marathi', + 8382 => 'Tones|Ringtones|Indian|Regional Indian|Gujarati', + 8383 => 'Tones|Ringtones|Indian|Regional Indian|Assamese', + 8384 => 'Tones|Ringtones|Indian|Regional Indian|Bhojpuri', + 8385 => 'Tones|Ringtones|Indian|Regional Indian|Haryanvi', + 8386 => 'Tones|Ringtones|Indian|Regional Indian|Odia', + 8387 => 'Tones|Ringtones|Indian|Regional Indian|Rajasthani', + 8388 => 'Tones|Ringtones|Indian|Regional Indian|Urdu', + 8389 => 'Tones|Ringtones|Indian|Regional Indian|Punjabi', + 8390 => 'Tones|Ringtones|Indian|Regional Indian|Bengali', + 8391 => 'Tones|Ringtones|Indian|Indian Classical|Carnatic Classical', + 8392 => 'Tones|Ringtones|Indian|Indian Classical|Hindustani Classical', + 8393 => 'Tones|Ringtones|African|Afro House', + 8394 => 'Tones|Ringtones|African|Afro Soul', + 8395 => 'Tones|Ringtones|African|Afrobeats', + 8396 => 'Tones|Ringtones|African|Benga', + 8397 => 'Tones|Ringtones|African|Bongo-Flava', + 8398 => 'Tones|Ringtones|African|Coupe-Decale', + 8399 => 'Tones|Ringtones|African|Gqom', + 8400 => 'Tones|Ringtones|African|Highlife', + 8401 => 'Tones|Ringtones|African|Kuduro', + 8402 => 'Tones|Ringtones|African|Kizomba', + 8403 => 'Tones|Ringtones|African|Kwaito', + 8404 => 'Tones|Ringtones|African|Mbalax', + 8405 => 'Tones|Ringtones|African|Ndombolo', + 8406 => 'Tones|Ringtones|African|Shangaan Electro', + 8407 => 'Tones|Ringtones|African|Soukous', + 8408 => 'Tones|Ringtones|African|Taarab', + 8409 => 'Tones|Ringtones|African|Zouglou', + 8410 => 'Tones|Ringtones|Turkish|Ozgun', + 8411 => 'Tones|Ringtones|Turkish|Fantezi', + 8412 => 'Tones|Ringtones|Turkish|Religious', + 8413 => 'Tones|Ringtones|Pop|Turkish Pop', + 8414 => 'Tones|Ringtones|Rock|Turkish Rock', + 8415 => 'Tones|Ringtones|Alternative|Turkish Alternative', + 8416 => 'Tones|Ringtones|Hip-Hop/Rap|Turkish Hip-Hop/Rap', + 8417 => 'Tones|Ringtones|African|Maskandi', + 8418 => 'Tones|Ringtones|Russian|Russian Romance', + 8419 => 'Tones|Ringtones|Russian|Russian Bard', + 8420 => 'Tones|Ringtones|Russian|Russian Pop', + 8421 => 'Tones|Ringtones|Russian|Russian Rock', + 8422 => 'Tones|Ringtones|Russian|Russian Hip-Hop', + 8423 => 'Tones|Ringtones|Arabic|Levant', + 8424 => 'Tones|Ringtones|Arabic|Levant|Dabke', + 8425 => 'Tones|Ringtones|Arabic|Maghreb Rai', + 8426 => 'Tones|Ringtones|Arabic|Khaleeji|Khaleeji Jalsat', + 8427 => 'Tones|Ringtones|Arabic|Khaleeji|Khaleeji Shailat', + 8428 => 'Tones|Ringtones|Tarab', + 8429 => 'Tones|Ringtones|Tarab|Iraqi Tarab', + 8430 => 'Tones|Ringtones|Tarab|Egyptian Tarab', + 8431 => 'Tones|Ringtones|Tarab|Khaleeji Tarab', + 8432 => 'Tones|Ringtones|Pop|Levant Pop', + 8433 => 'Tones|Ringtones|Pop|Iraqi Pop', + 8434 => 'Tones|Ringtones|Pop|Egyptian Pop', + 8435 => 'Tones|Ringtones|Pop|Maghreb Pop', + 8436 => 'Tones|Ringtones|Pop|Khaleeji Pop', + 8437 => 'Tones|Ringtones|Hip-Hop/Rap|Levant Hip-Hop', + 8438 => 'Tones|Ringtones|Hip-Hop/Rap|Egyptian Hip-Hop', + 8439 => 'Tones|Ringtones|Hip-Hop/Rap|Maghreb Hip-Hop', + 8440 => 'Tones|Ringtones|Hip-Hop/Rap|Khaleeji Hip-Hop', + 8441 => 'Tones|Ringtones|Alternative|Indie Levant', + 8442 => 'Tones|Ringtones|Alternative|Indie Egyptian', + 8443 => 'Tones|Ringtones|Alternative|Indie Maghreb', + 8444 => 'Tones|Ringtones|Electronic|Levant Electronic', + 8445 => "Tones|Ringtones|Electronic|Electro-Cha'abi", + 8446 => 'Tones|Ringtones|Electronic|Maghreb Electronic', + 8447 => 'Tones|Ringtones|Folk|Iraqi Folk', + 8448 => 'Tones|Ringtones|Folk|Khaleeji Folk', + 8449 => 'Tones|Ringtones|Dance|Maghreb Dance', + 9002 => 'Books|Nonfiction', + 9003 => 'Books|Romance', + 9004 => 'Books|Travel & Adventure', + 9007 => 'Books|Arts & Entertainment', + 9008 => 'Books|Biographies & Memoirs', + 9009 => 'Books|Business & Personal Finance', + 9010 => 'Books|Children & Teens', + 9012 => 'Books|Humor', + 9015 => 'Books|History', + 9018 => 'Books|Religion & Spirituality', + 9019 => 'Books|Science & Nature', + 9020 => 'Books|Sci-Fi & Fantasy', + 9024 => 'Books|Lifestyle & Home', + 9025 => 'Books|Self-Development', + 9026 => 'Books|Comics & Graphic Novels', + 9027 => 'Books|Computers & Internet', + 9028 => 'Books|Cookbooks, Food & Wine', + 9029 => 'Books|Professional & Technical', + 9030 => 'Books|Parenting', + 9031 => 'Books|Fiction & Literature', + 9032 => 'Books|Mysteries & Thrillers', + 9033 => 'Books|Reference', + 9034 => 'Books|Politics & Current Events', + 9035 => 'Books|Sports & Outdoors', + 10001 => 'Books|Lifestyle & Home|Antiques & Collectibles', + 10002 => 'Books|Arts & Entertainment|Art & Architecture', + 10003 => 'Books|Religion & Spirituality|Bibles', + 10004 => 'Books|Self-Development|Spirituality', + 10005 => 'Books|Business & Personal Finance|Industries & Professions', + 10006 => 'Books|Business & Personal Finance|Marketing & Sales', + 10007 => 'Books|Business & Personal Finance|Small Business & Entrepreneurship', + 10008 => 'Books|Business & Personal Finance|Personal Finance', + 10009 => 'Books|Business & Personal Finance|Reference', + 10010 => 'Books|Business & Personal Finance|Careers', + 10011 => 'Books|Business & Personal Finance|Economics', + 10012 => 'Books|Business & Personal Finance|Investing', + 10013 => 'Books|Business & Personal Finance|Finance', + 10014 => 'Books|Business & Personal Finance|Management & Leadership', + 10015 => 'Books|Comics & Graphic Novels|Graphic Novels', + 10016 => 'Books|Comics & Graphic Novels|Manga', + 10017 => 'Books|Computers & Internet|Computers', + 10018 => 'Books|Computers & Internet|Databases', + 10019 => 'Books|Computers & Internet|Digital Media', + 10020 => 'Books|Computers & Internet|Internet', + 10021 => 'Books|Computers & Internet|Network', + 10022 => 'Books|Computers & Internet|Operating Systems', + 10023 => 'Books|Computers & Internet|Programming', + 10024 => 'Books|Computers & Internet|Software', + 10025 => 'Books|Computers & Internet|System Administration', + 10026 => 'Books|Cookbooks, Food & Wine|Beverages', + 10027 => 'Books|Cookbooks, Food & Wine|Courses & Dishes', + 10028 => 'Books|Cookbooks, Food & Wine|Special Diet', + 10029 => 'Books|Cookbooks, Food & Wine|Special Occasions', + 10030 => 'Books|Cookbooks, Food & Wine|Methods', + 10031 => 'Books|Cookbooks, Food & Wine|Reference', + 10032 => 'Books|Cookbooks, Food & Wine|Regional & Ethnic', + 10033 => 'Books|Cookbooks, Food & Wine|Specific Ingredients', + 10034 => 'Books|Lifestyle & Home|Crafts & Hobbies', + 10035 => 'Books|Professional & Technical|Design', + 10036 => 'Books|Arts & Entertainment|Theater', + 10037 => 'Books|Professional & Technical|Education', + 10038 => 'Books|Nonfiction|Family & Relationships', + 10039 => 'Books|Fiction & Literature|Action & Adventure', + 10040 => 'Books|Fiction & Literature|African American', + 10041 => 'Books|Fiction & Literature|Religious', + 10042 => 'Books|Fiction & Literature|Classics', + 10043 => 'Books|Fiction & Literature|Erotica', + 10044 => 'Books|Sci-Fi & Fantasy|Fantasy', + 10045 => 'Books|Fiction & Literature|Gay', + 10046 => 'Books|Fiction & Literature|Ghost', + 10047 => 'Books|Fiction & Literature|Historical', + 10048 => 'Books|Fiction & Literature|Horror', + 10049 => 'Books|Fiction & Literature|Literary', + 10050 => 'Books|Mysteries & Thrillers|Hard-Boiled', + 10051 => 'Books|Mysteries & Thrillers|Historical', + 10052 => 'Books|Mysteries & Thrillers|Police Procedural', + 10053 => 'Books|Mysteries & Thrillers|Short Stories', + 10054 => 'Books|Mysteries & Thrillers|British Detectives', + 10055 => 'Books|Mysteries & Thrillers|Women Sleuths', + 10056 => 'Books|Romance|Erotic Romance', + 10057 => 'Books|Romance|Contemporary', + 10058 => 'Books|Romance|Paranormal', + 10059 => 'Books|Romance|Historical', + 10060 => 'Books|Romance|Short Stories', + 10061 => 'Books|Romance|Suspense', + 10062 => 'Books|Romance|Western', + 10063 => 'Books|Sci-Fi & Fantasy|Science Fiction', + 10064 => 'Books|Sci-Fi & Fantasy|Science Fiction & Literature', + 10065 => 'Books|Fiction & Literature|Short Stories', + 10066 => 'Books|Reference|Foreign Languages', + 10067 => 'Books|Arts & Entertainment|Games', + 10068 => 'Books|Lifestyle & Home|Gardening', + 10069 => 'Books|Self-Development|Health & Fitness', + 10070 => 'Books|History|Africa', + 10071 => 'Books|History|Americas', + 10072 => 'Books|History|Ancient', + 10073 => 'Books|History|Asia', + 10074 => 'Books|History|Australia & Oceania', + 10075 => 'Books|History|Europe', + 10076 => 'Books|History|Latin America', + 10077 => 'Books|History|Middle East', + 10078 => 'Books|History|Military', + 10079 => 'Books|History|United States', + 10080 => 'Books|History|World', + 10081 => "Books|Children & Teens|Children's Fiction", + 10082 => "Books|Children & Teens|Children's Nonfiction", + 10083 => 'Books|Professional & Technical|Law', + 10084 => 'Books|Fiction & Literature|Literary Criticism', + 10085 => 'Books|Science & Nature|Mathematics', + 10086 => 'Books|Professional & Technical|Medical', + 10087 => 'Books|Arts & Entertainment|Music', + 10088 => 'Books|Science & Nature|Nature', + 10089 => 'Books|Arts & Entertainment|Performing Arts', + 10090 => 'Books|Lifestyle & Home|Pets', + 10091 => 'Books|Nonfiction|Philosophy', + 10092 => 'Books|Arts & Entertainment|Photography', + 10093 => 'Books|Fiction & Literature|Poetry', + 10094 => 'Books|Self-Development|Psychology', + 10095 => 'Books|Reference|Almanacs & Yearbooks', + 10096 => 'Books|Reference|Atlases & Maps', + 10097 => 'Books|Reference|Catalogs & Directories', + 10098 => 'Books|Reference|Consumer Guides', + 10099 => 'Books|Reference|Dictionaries & Thesauruses', + 10100 => 'Books|Reference|Encyclopedias', + 10101 => 'Books|Reference|Etiquette', + 10102 => 'Books|Reference|Quotations', + 10103 => 'Books|Reference|Words & Language', + 10104 => 'Books|Reference|Writing', + 10105 => 'Books|Religion & Spirituality|Bible Studies', + 10106 => 'Books|Religion & Spirituality|Buddhism', + 10107 => 'Books|Religion & Spirituality|Christianity', + 10108 => 'Books|Religion & Spirituality|Hinduism', + 10109 => 'Books|Religion & Spirituality|Islam', + 10110 => 'Books|Religion & Spirituality|Judaism', + 10111 => 'Books|Science & Nature|Astronomy', + 10112 => 'Books|Science & Nature|Chemistry', + 10113 => 'Books|Science & Nature|Earth Sciences', + 10114 => 'Books|Science & Nature|Essays', + 10115 => 'Books|Science & Nature|History', + 10116 => 'Books|Science & Nature|Life Sciences', + 10117 => 'Books|Science & Nature|Physics', + 10118 => 'Books|Science & Nature|Reference', + 10119 => 'Books|Self-Development|Self-Improvement', + 10120 => 'Books|Nonfiction|Social Science', + 10121 => 'Books|Sports & Outdoors|Baseball', + 10122 => 'Books|Sports & Outdoors|Basketball', + 10123 => 'Books|Sports & Outdoors|Coaching', + 10124 => 'Books|Sports & Outdoors|Extreme Sports', + 10125 => 'Books|Sports & Outdoors|Football', + 10126 => 'Books|Sports & Outdoors|Golf', + 10127 => 'Books|Sports & Outdoors|Hockey', + 10128 => 'Books|Sports & Outdoors|Mountaineering', + 10129 => 'Books|Sports & Outdoors|Outdoors', + 10130 => 'Books|Sports & Outdoors|Racket Sports', + 10131 => 'Books|Sports & Outdoors|Reference', + 10132 => 'Books|Sports & Outdoors|Soccer', + 10133 => 'Books|Sports & Outdoors|Training', + 10134 => 'Books|Sports & Outdoors|Water Sports', + 10135 => 'Books|Sports & Outdoors|Winter Sports', + 10136 => 'Books|Reference|Study Aids', + 10137 => 'Books|Professional & Technical|Engineering', + 10138 => 'Books|Nonfiction|Transportation', + 10139 => 'Books|Travel & Adventure|Africa', + 10140 => 'Books|Travel & Adventure|Asia', + 10141 => 'Books|Travel & Adventure|Specialty Travel', + 10142 => 'Books|Travel & Adventure|Canada', + 10143 => 'Books|Travel & Adventure|Caribbean', + 10144 => 'Books|Travel & Adventure|Latin America', + 10145 => 'Books|Travel & Adventure|Essays & Memoirs', + 10146 => 'Books|Travel & Adventure|Europe', + 10147 => 'Books|Travel & Adventure|Middle East', + 10148 => 'Books|Travel & Adventure|United States', + 10149 => 'Books|Nonfiction|True Crime', + 11001 => 'Books|Sci-Fi & Fantasy|Fantasy|Contemporary', + 11002 => 'Books|Sci-Fi & Fantasy|Fantasy|Epic', + 11003 => 'Books|Sci-Fi & Fantasy|Fantasy|Historical', + 11004 => 'Books|Sci-Fi & Fantasy|Fantasy|Paranormal', + 11005 => 'Books|Sci-Fi & Fantasy|Fantasy|Short Stories', + 11006 => 'Books|Sci-Fi & Fantasy|Science Fiction & Literature|Adventure', + 11007 => 'Books|Sci-Fi & Fantasy|Science Fiction & Literature|High Tech', + 11008 => 'Books|Sci-Fi & Fantasy|Science Fiction & Literature|Short Stories', + 11009 => 'Books|Professional & Technical|Education|Language Arts & Disciplines', + 11010 => 'Books|Communications & Media', + 11011 => 'Books|Communications & Media|Broadcasting', + 11012 => 'Books|Communications & Media|Digital Media', + 11013 => 'Books|Communications & Media|Journalism', + 11014 => 'Books|Communications & Media|Photojournalism', + 11015 => 'Books|Communications & Media|Print', + 11016 => 'Books|Communications & Media|Speech', + 11017 => 'Books|Communications & Media|Writing', + 11018 => 'Books|Arts & Entertainment|Art & Architecture|Urban Planning', + 11019 => 'Books|Arts & Entertainment|Dance', + 11020 => 'Books|Arts & Entertainment|Fashion', + 11021 => 'Books|Arts & Entertainment|Film', + 11022 => 'Books|Arts & Entertainment|Interior Design', + 11023 => 'Books|Arts & Entertainment|Media Arts', + 11024 => 'Books|Arts & Entertainment|Radio', + 11025 => 'Books|Arts & Entertainment|TV', + 11026 => 'Books|Arts & Entertainment|Visual Arts', + 11027 => 'Books|Biographies & Memoirs|Arts & Entertainment', + 11028 => 'Books|Biographies & Memoirs|Business', + 11029 => 'Books|Biographies & Memoirs|Culinary', + 11030 => 'Books|Biographies & Memoirs|Gay & Lesbian', + 11031 => 'Books|Biographies & Memoirs|Historical', + 11032 => 'Books|Biographies & Memoirs|Literary', + 11033 => 'Books|Biographies & Memoirs|Media & Journalism', + 11034 => 'Books|Biographies & Memoirs|Military', + 11035 => 'Books|Biographies & Memoirs|Politics', + 11036 => 'Books|Biographies & Memoirs|Religious', + 11037 => 'Books|Biographies & Memoirs|Science & Technology', + 11038 => 'Books|Biographies & Memoirs|Sports', + 11039 => 'Books|Biographies & Memoirs|Women', + 11040 => 'Books|Romance|New Adult', + 11042 => 'Books|Romance|Romantic Comedy', + 11043 => 'Books|Romance|Gay & Lesbian', + 11044 => 'Books|Fiction & Literature|Essays', + 11045 => 'Books|Fiction & Literature|Anthologies', + 11046 => 'Books|Fiction & Literature|Comparative Literature', + 11047 => 'Books|Fiction & Literature|Drama', + 11049 => 'Books|Fiction & Literature|Fairy Tales, Myths & Fables', + 11050 => 'Books|Fiction & Literature|Family', + 11051 => 'Books|Comics & Graphic Novels|Manga|School Drama', + 11052 => 'Books|Comics & Graphic Novels|Manga|Human Drama', + 11053 => 'Books|Comics & Graphic Novels|Manga|Family Drama', + 11054 => 'Books|Sports & Outdoors|Boxing', + 11055 => 'Books|Sports & Outdoors|Cricket', + 11056 => 'Books|Sports & Outdoors|Cycling', + 11057 => 'Books|Sports & Outdoors|Equestrian', + 11058 => 'Books|Sports & Outdoors|Martial Arts & Self Defense', + 11059 => 'Books|Sports & Outdoors|Motor Sports', + 11060 => 'Books|Sports & Outdoors|Rugby', + 11061 => 'Books|Sports & Outdoors|Running', + 11062 => 'Books|Self-Development|Diet & Nutrition', + 11063 => 'Books|Science & Nature|Agriculture', + 11064 => 'Books|Science & Nature|Atmosphere', + 11065 => 'Books|Science & Nature|Biology', + 11066 => 'Books|Science & Nature|Ecology', + 11067 => 'Books|Science & Nature|Environment', + 11068 => 'Books|Science & Nature|Geography', + 11069 => 'Books|Science & Nature|Geology', + 11070 => 'Books|Nonfiction|Social Science|Anthropology', + 11071 => 'Books|Nonfiction|Social Science|Archaeology', + 11072 => 'Books|Nonfiction|Social Science|Civics', + 11073 => 'Books|Nonfiction|Social Science|Government', + 11074 => 'Books|Nonfiction|Social Science|Social Studies', + 11075 => 'Books|Nonfiction|Social Science|Social Welfare', + 11076 => 'Books|Nonfiction|Social Science|Society', + 11077 => 'Books|Nonfiction|Philosophy|Aesthetics', + 11078 => 'Books|Nonfiction|Philosophy|Epistemology', + 11079 => 'Books|Nonfiction|Philosophy|Ethics', + 11080 => 'Books|Nonfiction|Philosophy|Language', + 11081 => 'Books|Nonfiction|Philosophy|Logic', + 11082 => 'Books|Nonfiction|Philosophy|Metaphysics', + 11083 => 'Books|Nonfiction|Philosophy|Political', + 11084 => 'Books|Nonfiction|Philosophy|Religion', + 11085 => 'Books|Reference|Manuals', + 11086 => 'Books|Kids', + 11087 => 'Books|Kids|Animals', + 11088 => 'Books|Kids|Basic Concepts', + 11089 => 'Books|Kids|Basic Concepts|Alphabet', + 11090 => 'Books|Kids|Basic Concepts|Body', + 11091 => 'Books|Kids|Basic Concepts|Colors', + 11092 => 'Books|Kids|Basic Concepts|Counting & Numbers', + 11093 => 'Books|Kids|Basic Concepts|Date & Time', + 11094 => 'Books|Kids|Basic Concepts|General', + 11095 => 'Books|Kids|Basic Concepts|Money', + 11096 => 'Books|Kids|Basic Concepts|Opposites', + 11097 => 'Books|Kids|Basic Concepts|Seasons', + 11098 => 'Books|Kids|Basic Concepts|Senses & Sensation', + 11099 => 'Books|Kids|Basic Concepts|Size & Shape', + 11100 => 'Books|Kids|Basic Concepts|Sounds', + 11101 => 'Books|Kids|Basic Concepts|Words', + 11102 => 'Books|Kids|Biography', + 11103 => 'Books|Kids|Careers & Occupations', + 11104 => 'Books|Kids|Computers & Technology', + 11105 => 'Books|Kids|Cooking & Food', + 11106 => 'Books|Kids|Arts & Entertainment', + 11107 => 'Books|Kids|Arts & Entertainment|Art', + 11108 => 'Books|Kids|Arts & Entertainment|Crafts', + 11109 => 'Books|Kids|Arts & Entertainment|Music', + 11110 => 'Books|Kids|Arts & Entertainment|Performing Arts', + 11111 => 'Books|Kids|Family', + 11112 => 'Books|Kids|Fiction', + 11113 => 'Books|Kids|Fiction|Action & Adventure', + 11114 => 'Books|Kids|Fiction|Animals', + 11115 => 'Books|Kids|Fiction|Classics', + 11116 => 'Books|Kids|Fiction|Comics & Graphic Novels', + 11117 => 'Books|Kids|Fiction|Culture, Places & People', + 11118 => 'Books|Kids|Fiction|Family & Relationships', + 11119 => 'Books|Kids|Fiction|Fantasy', + 11120 => 'Books|Kids|Fiction|Fairy Tales, Myths & Fables', + 11121 => 'Books|Kids|Fiction|Favorite Characters', + 11122 => 'Books|Kids|Fiction|Historical', + 11123 => 'Books|Kids|Fiction|Holidays & Celebrations', + 11124 => 'Books|Kids|Fiction|Monsters & Ghosts', + 11125 => 'Books|Kids|Fiction|Mysteries', + 11126 => 'Books|Kids|Fiction|Nature', + 11127 => 'Books|Kids|Fiction|Religion', + 11128 => 'Books|Kids|Fiction|Sci-Fi', + 11129 => 'Books|Kids|Fiction|Social Issues', + 11130 => 'Books|Kids|Fiction|Sports & Recreation', + 11131 => 'Books|Kids|Fiction|Transportation', + 11132 => 'Books|Kids|Games & Activities', + 11133 => 'Books|Kids|General Nonfiction', + 11134 => 'Books|Kids|Health', + 11135 => 'Books|Kids|History', + 11136 => 'Books|Kids|Holidays & Celebrations', + 11137 => 'Books|Kids|Holidays & Celebrations|Birthdays', + 11138 => 'Books|Kids|Holidays & Celebrations|Christmas & Advent', + 11139 => 'Books|Kids|Holidays & Celebrations|Easter & Lent', + 11140 => 'Books|Kids|Holidays & Celebrations|General', + 11141 => 'Books|Kids|Holidays & Celebrations|Halloween', + 11142 => 'Books|Kids|Holidays & Celebrations|Hanukkah', + 11143 => 'Books|Kids|Holidays & Celebrations|Other', + 11144 => 'Books|Kids|Holidays & Celebrations|Passover', + 11145 => 'Books|Kids|Holidays & Celebrations|Patriotic Holidays', + 11146 => 'Books|Kids|Holidays & Celebrations|Ramadan', + 11147 => 'Books|Kids|Holidays & Celebrations|Thanksgiving', + 11148 => "Books|Kids|Holidays & Celebrations|Valentine's Day", + 11149 => 'Books|Kids|Humor', + 11150 => 'Books|Kids|Humor|Jokes & Riddles', + 11151 => 'Books|Kids|Poetry', + 11152 => 'Books|Kids|Learning to Read', + 11153 => 'Books|Kids|Learning to Read|Chapter Books', + 11154 => 'Books|Kids|Learning to Read|Early Readers', + 11155 => 'Books|Kids|Learning to Read|Intermediate Readers', + 11156 => 'Books|Kids|Nursery Rhymes', + 11157 => 'Books|Kids|Government', + 11158 => 'Books|Kids|Reference', + 11159 => 'Books|Kids|Religion', + 11160 => 'Books|Kids|Science & Nature', + 11161 => 'Books|Kids|Social Issues', + 11162 => 'Books|Kids|Social Studies', + 11163 => 'Books|Kids|Sports & Recreation', + 11164 => 'Books|Kids|Transportation', + 11165 => 'Books|Young Adult', + 11166 => 'Books|Young Adult|Animals', + 11167 => 'Books|Young Adult|Biography', + 11168 => 'Books|Young Adult|Careers & Occupations', + 11169 => 'Books|Young Adult|Computers & Technology', + 11170 => 'Books|Young Adult|Cooking & Food', + 11171 => 'Books|Young Adult|Arts & Entertainment', + 11172 => 'Books|Young Adult|Arts & Entertainment|Art', + 11173 => 'Books|Young Adult|Arts & Entertainment|Crafts', + 11174 => 'Books|Young Adult|Arts & Entertainment|Music', + 11175 => 'Books|Young Adult|Arts & Entertainment|Performing Arts', + 11176 => 'Books|Young Adult|Family', + 11177 => 'Books|Young Adult|Fiction', + 11178 => 'Books|Young Adult|Fiction|Action & Adventure', + 11179 => 'Books|Young Adult|Fiction|Animals', + 11180 => 'Books|Young Adult|Fiction|Classics', + 11181 => 'Books|Young Adult|Fiction|Comics & Graphic Novels', + 11182 => 'Books|Young Adult|Fiction|Culture, Places & People', + 11183 => 'Books|Young Adult|Fiction|Dystopian', + 11184 => 'Books|Young Adult|Fiction|Family & Relationships', + 11185 => 'Books|Young Adult|Fiction|Fantasy', + 11186 => 'Books|Young Adult|Fiction|Fairy Tales, Myths & Fables', + 11187 => 'Books|Young Adult|Fiction|Favorite Characters', + 11188 => 'Books|Young Adult|Fiction|Historical', + 11189 => 'Books|Young Adult|Fiction|Holidays & Celebrations', + 11190 => 'Books|Young Adult|Fiction|Horror, Monsters & Ghosts', + 11191 => 'Books|Young Adult|Fiction|Crime & Mystery', + 11192 => 'Books|Young Adult|Fiction|Nature', + 11193 => 'Books|Young Adult|Fiction|Religion', + 11194 => 'Books|Young Adult|Fiction|Romance', + 11195 => 'Books|Young Adult|Fiction|Sci-Fi', + 11196 => 'Books|Young Adult|Fiction|Coming of Age', + 11197 => 'Books|Young Adult|Fiction|Sports & Recreation', + 11198 => 'Books|Young Adult|Fiction|Transportation', + 11199 => 'Books|Young Adult|Games & Activities', + 11200 => 'Books|Young Adult|General Nonfiction', + 11201 => 'Books|Young Adult|Health', + 11202 => 'Books|Young Adult|History', + 11203 => 'Books|Young Adult|Holidays & Celebrations', + 11204 => 'Books|Young Adult|Holidays & Celebrations|Birthdays', + 11205 => 'Books|Young Adult|Holidays & Celebrations|Christmas & Advent', + 11206 => 'Books|Young Adult|Holidays & Celebrations|Easter & Lent', + 11207 => 'Books|Young Adult|Holidays & Celebrations|General', + 11208 => 'Books|Young Adult|Holidays & Celebrations|Halloween', + 11209 => 'Books|Young Adult|Holidays & Celebrations|Hanukkah', + 11210 => 'Books|Young Adult|Holidays & Celebrations|Other', + 11211 => 'Books|Young Adult|Holidays & Celebrations|Passover', + 11212 => 'Books|Young Adult|Holidays & Celebrations|Patriotic Holidays', + 11213 => 'Books|Young Adult|Holidays & Celebrations|Ramadan', + 11214 => 'Books|Young Adult|Holidays & Celebrations|Thanksgiving', + 11215 => "Books|Young Adult|Holidays & Celebrations|Valentine's Day", + 11216 => 'Books|Young Adult|Humor', + 11217 => 'Books|Young Adult|Humor|Jokes & Riddles', + 11218 => 'Books|Young Adult|Poetry', + 11219 => 'Books|Young Adult|Politics & Government', + 11220 => 'Books|Young Adult|Reference', + 11221 => 'Books|Young Adult|Religion', + 11222 => 'Books|Young Adult|Science & Nature', + 11223 => 'Books|Young Adult|Coming of Age', + 11224 => 'Books|Young Adult|Social Studies', + 11225 => 'Books|Young Adult|Sports & Recreation', + 11226 => 'Books|Young Adult|Transportation', + 11227 => 'Books|Communications & Media', + 11228 => 'Books|Military & Warfare', + 11229 => 'Books|Romance|Inspirational', + 11231 => 'Books|Romance|Holiday', + 11232 => 'Books|Romance|Wholesome', + 11233 => 'Books|Romance|Military', + 11234 => 'Books|Arts & Entertainment|Art History', + 11236 => 'Books|Arts & Entertainment|Design', + 11243 => 'Books|Business & Personal Finance|Accounting', + 11244 => 'Books|Business & Personal Finance|Hospitality', + 11245 => 'Books|Business & Personal Finance|Real Estate', + 11246 => 'Books|Humor|Jokes & Riddles', + 11247 => 'Books|Religion & Spirituality|Comparative Religion', + 11255 => 'Books|Cookbooks, Food & Wine|Culinary Arts', + 11259 => 'Books|Mysteries & Thrillers|Cozy', + 11260 => 'Books|Politics & Current Events|Current Events', + 11261 => 'Books|Politics & Current Events|Foreign Policy & International Relations', + 11262 => 'Books|Politics & Current Events|Local Government', + 11263 => 'Books|Politics & Current Events|National Government', + 11264 => 'Books|Politics & Current Events|Political Science', + 11265 => 'Books|Politics & Current Events|Public Administration', + 11266 => 'Books|Politics & Current Events|World Affairs', + 11273 => 'Books|Nonfiction|Family & Relationships|Family & Childcare', + 11274 => 'Books|Nonfiction|Family & Relationships|Love & Romance', + 11275 => 'Books|Sci-Fi & Fantasy|Fantasy|Urban', + 11276 => 'Books|Reference|Foreign Languages|Arabic', + 11277 => 'Books|Reference|Foreign Languages|Bilingual Editions', + 11278 => 'Books|Reference|Foreign Languages|African Languages', + 11279 => 'Books|Reference|Foreign Languages|Ancient Languages', + 11280 => 'Books|Reference|Foreign Languages|Chinese', + 11281 => 'Books|Reference|Foreign Languages|English', + 11282 => 'Books|Reference|Foreign Languages|French', + 11283 => 'Books|Reference|Foreign Languages|German', + 11284 => 'Books|Reference|Foreign Languages|Hebrew', + 11285 => 'Books|Reference|Foreign Languages|Hindi', + 11286 => 'Books|Reference|Foreign Languages|Italian', + 11287 => 'Books|Reference|Foreign Languages|Japanese', + 11288 => 'Books|Reference|Foreign Languages|Korean', + 11289 => 'Books|Reference|Foreign Languages|Linguistics', + 11290 => 'Books|Reference|Foreign Languages|Other Languages', + 11291 => 'Books|Reference|Foreign Languages|Portuguese', + 11292 => 'Books|Reference|Foreign Languages|Russian', + 11293 => 'Books|Reference|Foreign Languages|Spanish', + 11294 => 'Books|Reference|Foreign Languages|Speech Pathology', + 11295 => 'Books|Science & Nature|Mathematics|Advanced Mathematics', + 11296 => 'Books|Science & Nature|Mathematics|Algebra', + 11297 => 'Books|Science & Nature|Mathematics|Arithmetic', + 11298 => 'Books|Science & Nature|Mathematics|Calculus', + 11299 => 'Books|Science & Nature|Mathematics|Geometry', + 11300 => 'Books|Science & Nature|Mathematics|Statistics', + 11301 => 'Books|Professional & Technical|Medical|Veterinary', + 11302 => 'Books|Professional & Technical|Medical|Neuroscience', + 11303 => 'Books|Professional & Technical|Medical|Immunology', + 11304 => 'Books|Professional & Technical|Medical|Nursing', + 11305 => 'Books|Professional & Technical|Medical|Pharmacology & Toxicology', + 11306 => 'Books|Professional & Technical|Medical|Anatomy & Physiology', + 11307 => 'Books|Professional & Technical|Medical|Dentistry', + 11308 => 'Books|Professional & Technical|Medical|Emergency Medicine', + 11309 => 'Books|Professional & Technical|Medical|Genetics', + 11310 => 'Books|Professional & Technical|Medical|Psychiatry', + 11311 => 'Books|Professional & Technical|Medical|Radiology', + 11312 => 'Books|Professional & Technical|Medical|Alternative Medicine', + 11317 => 'Books|Nonfiction|Philosophy|Political Philosophy', + 11319 => 'Books|Nonfiction|Philosophy|Philosophy of Language', + 11320 => 'Books|Nonfiction|Philosophy|Philosophy of Religion', + 11327 => 'Books|Nonfiction|Social Science|Sociology', + 11329 => 'Books|Professional & Technical|Engineering|Aeronautics', + 11330 => 'Books|Professional & Technical|Engineering|Chemical & Petroleum Engineering', + 11331 => 'Books|Professional & Technical|Engineering|Civil Engineering', + 11332 => 'Books|Professional & Technical|Engineering|Computer Science', + 11333 => 'Books|Professional & Technical|Engineering|Electrical Engineering', + 11334 => 'Books|Professional & Technical|Engineering|Environmental Engineering', + 11335 => 'Books|Professional & Technical|Engineering|Mechanical Engineering', + 11336 => 'Books|Professional & Technical|Engineering|Power Resources', + 11337 => 'Books|Comics & Graphic Novels|Manga|Boys', + 11338 => 'Books|Comics & Graphic Novels|Manga|Men', + 11339 => 'Books|Comics & Graphic Novels|Manga|Girls', + 11340 => 'Books|Comics & Graphic Novels|Manga|Women', + 11341 => 'Books|Comics & Graphic Novels|Manga|Other', + 11342 => 'Books|Comics & Graphic Novels|Manga|Yaoi', + 11343 => 'Books|Comics & Graphic Novels|Manga|Comic Essays', + 12001 => 'Mac App Store|Business', + 12002 => 'Mac App Store|Developer Tools', + 12003 => 'Mac App Store|Education', + 12004 => 'Mac App Store|Entertainment', + 12005 => 'Mac App Store|Finance', + 12006 => 'Mac App Store|Games', + 12007 => 'Mac App Store|Health & Fitness', + 12008 => 'Mac App Store|Lifestyle', + 12010 => 'Mac App Store|Medical', + 12011 => 'Mac App Store|Music', + 12012 => 'Mac App Store|News', + 12013 => 'Mac App Store|Photography', + 12014 => 'Mac App Store|Productivity', + 12015 => 'Mac App Store|Reference', + 12016 => 'Mac App Store|Social Networking', + 12017 => 'Mac App Store|Sports', + 12018 => 'Mac App Store|Travel', + 12019 => 'Mac App Store|Utilities', + 12020 => 'Mac App Store|Video', + 12021 => 'Mac App Store|Weather', + 12022 => 'Mac App Store|Graphics & Design', + 12201 => 'Mac App Store|Games|Action', + 12202 => 'Mac App Store|Games|Adventure', + 12203 => 'Mac App Store|Games|Casual', + 12204 => 'Mac App Store|Games|Board', + 12205 => 'Mac App Store|Games|Card', + 12206 => 'Mac App Store|Games|Casino', + 12207 => 'Mac App Store|Games|Dice', + 12208 => 'Mac App Store|Games|Educational', + 12209 => 'Mac App Store|Games|Family', + 12210 => 'Mac App Store|Games|Kids', + 12211 => 'Mac App Store|Games|Music', + 12212 => 'Mac App Store|Games|Puzzle', + 12213 => 'Mac App Store|Games|Racing', + 12214 => 'Mac App Store|Games|Role Playing', + 12215 => 'Mac App Store|Games|Simulation', + 12216 => 'Mac App Store|Games|Sports', + 12217 => 'Mac App Store|Games|Strategy', + 12218 => 'Mac App Store|Games|Trivia', + 12219 => 'Mac App Store|Games|Word', + 13001 => 'App Store|Magazines & Newspapers|News & Politics', + 13002 => 'App Store|Magazines & Newspapers|Fashion & Style', + 13003 => 'App Store|Magazines & Newspapers|Home & Garden', + 13004 => 'App Store|Magazines & Newspapers|Outdoors & Nature', + 13005 => 'App Store|Magazines & Newspapers|Sports & Leisure', + 13006 => 'App Store|Magazines & Newspapers|Automotive', + 13007 => 'App Store|Magazines & Newspapers|Arts & Photography', + 13008 => 'App Store|Magazines & Newspapers|Brides & Weddings', + 13009 => 'App Store|Magazines & Newspapers|Business & Investing', + 13010 => "App Store|Magazines & Newspapers|Children's Magazines", + 13011 => 'App Store|Magazines & Newspapers|Computers & Internet', + 13012 => 'App Store|Magazines & Newspapers|Cooking, Food & Drink', + 13013 => 'App Store|Magazines & Newspapers|Crafts & Hobbies', + 13014 => 'App Store|Magazines & Newspapers|Electronics & Audio', + 13015 => 'App Store|Magazines & Newspapers|Entertainment', + 13017 => 'App Store|Magazines & Newspapers|Health, Mind & Body', + 13018 => 'App Store|Magazines & Newspapers|History', + 13019 => 'App Store|Magazines & Newspapers|Literary Magazines & Journals', + 13020 => "App Store|Magazines & Newspapers|Men's Interest", + 13021 => 'App Store|Magazines & Newspapers|Movies & Music', + 13023 => 'App Store|Magazines & Newspapers|Parenting & Family', + 13024 => 'App Store|Magazines & Newspapers|Pets', + 13025 => 'App Store|Magazines & Newspapers|Professional & Trade', + 13026 => 'App Store|Magazines & Newspapers|Regional News', + 13027 => 'App Store|Magazines & Newspapers|Science', + 13028 => 'App Store|Magazines & Newspapers|Teens', + 13029 => 'App Store|Magazines & Newspapers|Travel & Regional', + 13030 => "App Store|Magazines & Newspapers|Women's Interest", + 15000 => 'Textbooks|Arts & Entertainment', + 15001 => 'Textbooks|Arts & Entertainment|Art & Architecture', + 15002 => 'Textbooks|Arts & Entertainment|Art & Architecture|Urban Planning', + 15003 => 'Textbooks|Arts & Entertainment|Art History', + 15004 => 'Textbooks|Arts & Entertainment|Dance', + 15005 => 'Textbooks|Arts & Entertainment|Design', + 15006 => 'Textbooks|Arts & Entertainment|Fashion', + 15007 => 'Textbooks|Arts & Entertainment|Film', + 15008 => 'Textbooks|Arts & Entertainment|Games', + 15009 => 'Textbooks|Arts & Entertainment|Interior Design', + 15010 => 'Textbooks|Arts & Entertainment|Media Arts', + 15011 => 'Textbooks|Arts & Entertainment|Music', + 15012 => 'Textbooks|Arts & Entertainment|Performing Arts', + 15013 => 'Textbooks|Arts & Entertainment|Photography', + 15014 => 'Textbooks|Arts & Entertainment|Theater', + 15015 => 'Textbooks|Arts & Entertainment|TV', + 15016 => 'Textbooks|Arts & Entertainment|Visual Arts', + 15017 => 'Textbooks|Biographies & Memoirs', + 15018 => 'Textbooks|Business & Personal Finance', + 15019 => 'Textbooks|Business & Personal Finance|Accounting', + 15020 => 'Textbooks|Business & Personal Finance|Careers', + 15021 => 'Textbooks|Business & Personal Finance|Economics', + 15022 => 'Textbooks|Business & Personal Finance|Finance', + 15023 => 'Textbooks|Business & Personal Finance|Hospitality', + 15024 => 'Textbooks|Business & Personal Finance|Industries & Professions', + 15025 => 'Textbooks|Business & Personal Finance|Investing', + 15026 => 'Textbooks|Business & Personal Finance|Management & Leadership', + 15027 => 'Textbooks|Business & Personal Finance|Marketing & Sales', + 15028 => 'Textbooks|Business & Personal Finance|Personal Finance', + 15029 => 'Textbooks|Business & Personal Finance|Real Estate', + 15030 => 'Textbooks|Business & Personal Finance|Reference', + 15031 => 'Textbooks|Business & Personal Finance|Small Business & Entrepreneurship', + 15032 => 'Textbooks|Children & Teens', + 15033 => 'Textbooks|Children & Teens|Fiction', + 15034 => 'Textbooks|Children & Teens|Nonfiction', + 15035 => 'Textbooks|Comics & Graphic Novels', + 15036 => 'Textbooks|Comics & Graphic Novels|Graphic Novels', + 15037 => 'Textbooks|Comics & Graphic Novels|Manga', + 15038 => 'Textbooks|Communications & Media', + 15039 => 'Textbooks|Communications & Media|Broadcasting', + 15040 => 'Textbooks|Communications & Media|Digital Media', + 15041 => 'Textbooks|Communications & Media|Journalism', + 15042 => 'Textbooks|Communications & Media|Photojournalism', + 15043 => 'Textbooks|Communications & Media|Print', + 15044 => 'Textbooks|Communications & Media|Speech', + 15045 => 'Textbooks|Communications & Media|Writing', + 15046 => 'Textbooks|Computers & Internet', + 15047 => 'Textbooks|Computers & Internet|Computers', + 15048 => 'Textbooks|Computers & Internet|Databases', + 15049 => 'Textbooks|Computers & Internet|Digital Media', + 15050 => 'Textbooks|Computers & Internet|Internet', + 15051 => 'Textbooks|Computers & Internet|Network', + 15052 => 'Textbooks|Computers & Internet|Operating Systems', + 15053 => 'Textbooks|Computers & Internet|Programming', + 15054 => 'Textbooks|Computers & Internet|Software', + 15055 => 'Textbooks|Computers & Internet|System Administration', + 15056 => 'Textbooks|Cookbooks, Food & Wine', + 15057 => 'Textbooks|Cookbooks, Food & Wine|Beverages', + 15058 => 'Textbooks|Cookbooks, Food & Wine|Courses & Dishes', + 15059 => 'Textbooks|Cookbooks, Food & Wine|Culinary Arts', + 15060 => 'Textbooks|Cookbooks, Food & Wine|Methods', + 15061 => 'Textbooks|Cookbooks, Food & Wine|Reference', + 15062 => 'Textbooks|Cookbooks, Food & Wine|Regional & Ethnic', + 15063 => 'Textbooks|Cookbooks, Food & Wine|Special Diet', + 15064 => 'Textbooks|Cookbooks, Food & Wine|Special Occasions', + 15065 => 'Textbooks|Cookbooks, Food & Wine|Specific Ingredients', + 15066 => 'Textbooks|Engineering', + 15067 => 'Textbooks|Engineering|Aeronautics', + 15068 => 'Textbooks|Engineering|Chemical & Petroleum Engineering', + 15069 => 'Textbooks|Engineering|Civil Engineering', + 15070 => 'Textbooks|Engineering|Computer Science', + 15071 => 'Textbooks|Engineering|Electrical Engineering', + 15072 => 'Textbooks|Engineering|Environmental Engineering', + 15073 => 'Textbooks|Engineering|Mechanical Engineering', + 15074 => 'Textbooks|Engineering|Power Resources', + 15075 => 'Textbooks|Fiction & Literature', + 15076 => 'Textbooks|Fiction & Literature|Latino', + 15077 => 'Textbooks|Fiction & Literature|Action & Adventure', + 15078 => 'Textbooks|Fiction & Literature|African American', + 15079 => 'Textbooks|Fiction & Literature|Anthologies', + 15080 => 'Textbooks|Fiction & Literature|Classics', + 15081 => 'Textbooks|Fiction & Literature|Comparative Literature', + 15082 => 'Textbooks|Fiction & Literature|Erotica', + 15083 => 'Textbooks|Fiction & Literature|Gay', + 15084 => 'Textbooks|Fiction & Literature|Ghost', + 15085 => 'Textbooks|Fiction & Literature|Historical', + 15086 => 'Textbooks|Fiction & Literature|Horror', + 15087 => 'Textbooks|Fiction & Literature|Literary', + 15088 => 'Textbooks|Fiction & Literature|Literary Criticism', + 15089 => 'Textbooks|Fiction & Literature|Poetry', + 15090 => 'Textbooks|Fiction & Literature|Religious', + 15091 => 'Textbooks|Fiction & Literature|Short Stories', + 15092 => 'Textbooks|Health, Mind & Body', + 15093 => 'Textbooks|Health, Mind & Body|Fitness', + 15094 => 'Textbooks|Health, Mind & Body|Self-Improvement', + 15095 => 'Textbooks|History', + 15096 => 'Textbooks|History|Africa', + 15097 => 'Textbooks|History|Americas', + 15098 => 'Textbooks|History|Americas|Canada', + 15099 => 'Textbooks|History|Americas|Latin America', + 15100 => 'Textbooks|History|Americas|United States', + 15101 => 'Textbooks|History|Ancient', + 15102 => 'Textbooks|History|Asia', + 15103 => 'Textbooks|History|Australia & Oceania', + 15104 => 'Textbooks|History|Europe', + 15105 => 'Textbooks|History|Middle East', + 15106 => 'Textbooks|History|Military', + 15107 => 'Textbooks|History|World', + 15108 => 'Textbooks|Humor', + 15109 => 'Textbooks|Language Studies', + 15110 => 'Textbooks|Language Studies|African Languages', + 15111 => 'Textbooks|Language Studies|Ancient Languages', + 15112 => 'Textbooks|Language Studies|Arabic', + 15113 => 'Textbooks|Language Studies|Bilingual Editions', + 15114 => 'Textbooks|Language Studies|Chinese', + 15115 => 'Textbooks|Language Studies|English', + 15116 => 'Textbooks|Language Studies|French', + 15117 => 'Textbooks|Language Studies|German', + 15118 => 'Textbooks|Language Studies|Hebrew', + 15119 => 'Textbooks|Language Studies|Hindi', + 15120 => 'Textbooks|Language Studies|Indigenous Languages', + 15121 => 'Textbooks|Language Studies|Italian', + 15122 => 'Textbooks|Language Studies|Japanese', + 15123 => 'Textbooks|Language Studies|Korean', + 15124 => 'Textbooks|Language Studies|Linguistics', + 15125 => 'Textbooks|Language Studies|Other Language', + 15126 => 'Textbooks|Language Studies|Portuguese', + 15127 => 'Textbooks|Language Studies|Russian', + 15128 => 'Textbooks|Language Studies|Spanish', + 15129 => 'Textbooks|Language Studies|Speech Pathology', + 15130 => 'Textbooks|Lifestyle & Home', + 15131 => 'Textbooks|Lifestyle & Home|Antiques & Collectibles', + 15132 => 'Textbooks|Lifestyle & Home|Crafts & Hobbies', + 15133 => 'Textbooks|Lifestyle & Home|Gardening', + 15134 => 'Textbooks|Lifestyle & Home|Pets', + 15135 => 'Textbooks|Mathematics', + 15136 => 'Textbooks|Mathematics|Advanced Mathematics', + 15137 => 'Textbooks|Mathematics|Algebra', + 15138 => 'Textbooks|Mathematics|Arithmetic', + 15139 => 'Textbooks|Mathematics|Calculus', + 15140 => 'Textbooks|Mathematics|Geometry', + 15141 => 'Textbooks|Mathematics|Statistics', + 15142 => 'Textbooks|Medicine', + 15143 => 'Textbooks|Medicine|Anatomy & Physiology', + 15144 => 'Textbooks|Medicine|Dentistry', + 15145 => 'Textbooks|Medicine|Emergency Medicine', + 15146 => 'Textbooks|Medicine|Genetics', + 15147 => 'Textbooks|Medicine|Immunology', + 15148 => 'Textbooks|Medicine|Neuroscience', + 15149 => 'Textbooks|Medicine|Nursing', + 15150 => 'Textbooks|Medicine|Pharmacology & Toxicology', + 15151 => 'Textbooks|Medicine|Psychiatry', + 15152 => 'Textbooks|Medicine|Psychology', + 15153 => 'Textbooks|Medicine|Radiology', + 15154 => 'Textbooks|Medicine|Veterinary', + 15155 => 'Textbooks|Mysteries & Thrillers', + 15156 => 'Textbooks|Mysteries & Thrillers|British Detectives', + 15157 => 'Textbooks|Mysteries & Thrillers|Hard-Boiled', + 15158 => 'Textbooks|Mysteries & Thrillers|Historical', + 15159 => 'Textbooks|Mysteries & Thrillers|Police Procedural', + 15160 => 'Textbooks|Mysteries & Thrillers|Short Stories', + 15161 => 'Textbooks|Mysteries & Thrillers|Women Sleuths', + 15162 => 'Textbooks|Nonfiction', + 15163 => 'Textbooks|Nonfiction|Family & Relationships', + 15164 => 'Textbooks|Nonfiction|Transportation', + 15165 => 'Textbooks|Nonfiction|True Crime', + 15166 => 'Textbooks|Parenting', + 15167 => 'Textbooks|Philosophy', + 15168 => 'Textbooks|Philosophy|Aesthetics', + 15169 => 'Textbooks|Philosophy|Epistemology', + 15170 => 'Textbooks|Philosophy|Ethics', + 15171 => 'Textbooks|Philosophy|Philosophy of Language', + 15172 => 'Textbooks|Philosophy|Logic', + 15173 => 'Textbooks|Philosophy|Metaphysics', + 15174 => 'Textbooks|Philosophy|Political Philosophy', + 15175 => 'Textbooks|Philosophy|Philosophy of Religion', + 15176 => 'Textbooks|Politics & Current Events', + 15177 => 'Textbooks|Politics & Current Events|Current Events', + 15178 => 'Textbooks|Politics & Current Events|Foreign Policy & International Relations', + 15179 => 'Textbooks|Politics & Current Events|Local Governments', + 15180 => 'Textbooks|Politics & Current Events|National Governments', + 15181 => 'Textbooks|Politics & Current Events|Political Science', + 15182 => 'Textbooks|Politics & Current Events|Public Administration', + 15183 => 'Textbooks|Politics & Current Events|World Affairs', + 15184 => 'Textbooks|Professional & Technical', + 15185 => 'Textbooks|Professional & Technical|Design', + 15186 => 'Textbooks|Professional & Technical|Language Arts & Disciplines', + 15187 => 'Textbooks|Professional & Technical|Engineering', + 15188 => 'Textbooks|Professional & Technical|Law', + 15189 => 'Textbooks|Professional & Technical|Medical', + 15190 => 'Textbooks|Reference', + 15191 => 'Textbooks|Reference|Almanacs & Yearbooks', + 15192 => 'Textbooks|Reference|Atlases & Maps', + 15193 => 'Textbooks|Reference|Catalogs & Directories', + 15194 => 'Textbooks|Reference|Consumer Guides', + 15195 => 'Textbooks|Reference|Dictionaries & Thesauruses', + 15196 => 'Textbooks|Reference|Encyclopedias', + 15197 => 'Textbooks|Reference|Etiquette', + 15198 => 'Textbooks|Reference|Quotations', + 15199 => 'Textbooks|Reference|Study Aids', + 15200 => 'Textbooks|Reference|Words & Language', + 15201 => 'Textbooks|Reference|Writing', + 15202 => 'Textbooks|Religion & Spirituality', + 15203 => 'Textbooks|Religion & Spirituality|Bible Studies', + 15204 => 'Textbooks|Religion & Spirituality|Bibles', + 15205 => 'Textbooks|Religion & Spirituality|Buddhism', + 15206 => 'Textbooks|Religion & Spirituality|Christianity', + 15207 => 'Textbooks|Religion & Spirituality|Comparative Religion', + 15208 => 'Textbooks|Religion & Spirituality|Hinduism', + 15209 => 'Textbooks|Religion & Spirituality|Islam', + 15210 => 'Textbooks|Religion & Spirituality|Judaism', + 15211 => 'Textbooks|Religion & Spirituality|Spirituality', + 15212 => 'Textbooks|Romance', + 15213 => 'Textbooks|Romance|Contemporary', + 15214 => 'Textbooks|Romance|Erotic Romance', + 15215 => 'Textbooks|Romance|Paranormal', + 15216 => 'Textbooks|Romance|Historical', + 15217 => 'Textbooks|Romance|Short Stories', + 15218 => 'Textbooks|Romance|Suspense', + 15219 => 'Textbooks|Romance|Western', + 15220 => 'Textbooks|Sci-Fi & Fantasy', + 15221 => 'Textbooks|Sci-Fi & Fantasy|Fantasy', + 15222 => 'Textbooks|Sci-Fi & Fantasy|Fantasy|Contemporary', + 15223 => 'Textbooks|Sci-Fi & Fantasy|Fantasy|Epic', + 15224 => 'Textbooks|Sci-Fi & Fantasy|Fantasy|Historical', + 15225 => 'Textbooks|Sci-Fi & Fantasy|Fantasy|Paranormal', + 15226 => 'Textbooks|Sci-Fi & Fantasy|Fantasy|Short Stories', + 15227 => 'Textbooks|Sci-Fi & Fantasy|Science Fiction', + 15228 => 'Textbooks|Sci-Fi & Fantasy|Science Fiction & Literature', + 15229 => 'Textbooks|Sci-Fi & Fantasy|Science Fiction & Literature|Adventure', + 15230 => 'Textbooks|Sci-Fi & Fantasy|Science Fiction & Literature|High Tech', + 15231 => 'Textbooks|Sci-Fi & Fantasy|Science Fiction & Literature|Short Stories', + 15232 => 'Textbooks|Science & Nature', + 15233 => 'Textbooks|Science & Nature|Agriculture', + 15234 => 'Textbooks|Science & Nature|Astronomy', + 15235 => 'Textbooks|Science & Nature|Atmosphere', + 15236 => 'Textbooks|Science & Nature|Biology', + 15237 => 'Textbooks|Science & Nature|Chemistry', + 15238 => 'Textbooks|Science & Nature|Earth Sciences', + 15239 => 'Textbooks|Science & Nature|Ecology', + 15240 => 'Textbooks|Science & Nature|Environment', + 15241 => 'Textbooks|Science & Nature|Essays', + 15242 => 'Textbooks|Science & Nature|Geography', + 15243 => 'Textbooks|Science & Nature|Geology', + 15244 => 'Textbooks|Science & Nature|History', + 15245 => 'Textbooks|Science & Nature|Life Sciences', + 15246 => 'Textbooks|Science & Nature|Nature', + 15247 => 'Textbooks|Science & Nature|Physics', + 15248 => 'Textbooks|Science & Nature|Reference', + 15249 => 'Textbooks|Social Science', + 15250 => 'Textbooks|Social Science|Anthropology', + 15251 => 'Textbooks|Social Science|Archaeology', + 15252 => 'Textbooks|Social Science|Civics', + 15253 => 'Textbooks|Social Science|Government', + 15254 => 'Textbooks|Social Science|Social Studies', + 15255 => 'Textbooks|Social Science|Social Welfare', + 15256 => 'Textbooks|Social Science|Society', + 15257 => 'Textbooks|Social Science|Society|African Studies', + 15258 => 'Textbooks|Social Science|Society|American Studies', + 15259 => 'Textbooks|Social Science|Society|Asia Pacific Studies', + 15260 => 'Textbooks|Social Science|Society|Cross-Cultural Studies', + 15261 => 'Textbooks|Social Science|Society|European Studies', + 15262 => 'Textbooks|Social Science|Society|Immigration & Emigration', + 15263 => 'Textbooks|Social Science|Society|Indigenous Studies', + 15264 => 'Textbooks|Social Science|Society|Latin & Caribbean Studies', + 15265 => 'Textbooks|Social Science|Society|Middle Eastern Studies', + 15266 => 'Textbooks|Social Science|Society|Race & Ethnicity Studies', + 15267 => 'Textbooks|Social Science|Society|Sexuality Studies', + 15268 => "Textbooks|Social Science|Society|Women's Studies", + 15269 => 'Textbooks|Social Science|Sociology', + 15270 => 'Textbooks|Sports & Outdoors', + 15271 => 'Textbooks|Sports & Outdoors|Baseball', + 15272 => 'Textbooks|Sports & Outdoors|Basketball', + 15273 => 'Textbooks|Sports & Outdoors|Coaching', + 15274 => 'Textbooks|Sports & Outdoors|Equestrian', + 15275 => 'Textbooks|Sports & Outdoors|Extreme Sports', + 15276 => 'Textbooks|Sports & Outdoors|Football', + 15277 => 'Textbooks|Sports & Outdoors|Golf', + 15278 => 'Textbooks|Sports & Outdoors|Hockey', + 15279 => 'Textbooks|Sports & Outdoors|Motor Sports', + 15280 => 'Textbooks|Sports & Outdoors|Mountaineering', + 15281 => 'Textbooks|Sports & Outdoors|Outdoors', + 15282 => 'Textbooks|Sports & Outdoors|Racket Sports', + 15283 => 'Textbooks|Sports & Outdoors|Reference', + 15284 => 'Textbooks|Sports & Outdoors|Soccer', + 15285 => 'Textbooks|Sports & Outdoors|Training', + 15286 => 'Textbooks|Sports & Outdoors|Water Sports', + 15287 => 'Textbooks|Sports & Outdoors|Winter Sports', + 15288 => 'Textbooks|Teaching & Learning', + 15289 => 'Textbooks|Teaching & Learning|Adult Education', + 15290 => 'Textbooks|Teaching & Learning|Curriculum & Teaching', + 15291 => 'Textbooks|Teaching & Learning|Educational Leadership', + 15292 => 'Textbooks|Teaching & Learning|Educational Technology', + 15293 => 'Textbooks|Teaching & Learning|Family & Childcare', + 15294 => 'Textbooks|Teaching & Learning|Information & Library Science', + 15295 => 'Textbooks|Teaching & Learning|Learning Resources', + 15296 => 'Textbooks|Teaching & Learning|Psychology & Research', + 15297 => 'Textbooks|Teaching & Learning|Special Education', + 15298 => 'Textbooks|Travel & Adventure', + 15299 => 'Textbooks|Travel & Adventure|Africa', + 15300 => 'Textbooks|Travel & Adventure|Americas', + 15301 => 'Textbooks|Travel & Adventure|Americas|Canada', + 15302 => 'Textbooks|Travel & Adventure|Americas|Latin America', + 15303 => 'Textbooks|Travel & Adventure|Americas|United States', + 15304 => 'Textbooks|Travel & Adventure|Asia', + 15305 => 'Textbooks|Travel & Adventure|Caribbean', + 15306 => 'Textbooks|Travel & Adventure|Essays & Memoirs', + 15307 => 'Textbooks|Travel & Adventure|Europe', + 15308 => 'Textbooks|Travel & Adventure|Middle East', + 15309 => 'Textbooks|Travel & Adventure|Oceania', + 15310 => 'Textbooks|Travel & Adventure|Specialty Travel', + 15311 => 'Textbooks|Comics & Graphic Novels|Comics', + 15312 => 'Textbooks|Reference|Manuals', + 16001 => 'App Store|Stickers|Emoji & Expressions', + 16003 => 'App Store|Stickers|Animals & Nature', + 16005 => 'App Store|Stickers|Art', + 16006 => 'App Store|Stickers|Celebrations', + 16007 => 'App Store|Stickers|Celebrities', + 16008 => 'App Store|Stickers|Comics & Cartoons', + 16009 => 'App Store|Stickers|Eating & Drinking', + 16010 => 'App Store|Stickers|Gaming', + 16014 => 'App Store|Stickers|Movies & TV', + 16015 => 'App Store|Stickers|Music', + 16017 => 'App Store|Stickers|People', + 16019 => 'App Store|Stickers|Places & Objects', + 16021 => 'App Store|Stickers|Sports & Activities', + 16025 => 'App Store|Stickers|Kids & Family', + 16026 => 'App Store|Stickers|Fashion', + 100000 => 'Music|Christian & Gospel', + 100001 => 'Music|Classical|Art Song', + 100002 => 'Music|Classical|Brass & Woodwinds', + 100003 => 'Music|Classical|Solo Instrumental', + 100004 => 'Music|Classical|Contemporary Era', + 100005 => 'Music|Classical|Oratorio', + 100006 => 'Music|Classical|Cantata', + 100007 => 'Music|Classical|Electronic', + 100008 => 'Music|Classical|Sacred', + 100009 => 'Music|Classical|Guitar', + 100010 => 'Music|Classical|Piano', + 100011 => 'Music|Classical|Violin', + 100012 => 'Music|Classical|Cello', + 100013 => 'Music|Classical|Percussion', + 100014 => 'Music|Electronic|Dubstep', + 100015 => 'Music|Electronic|Bass', + 100016 => 'Music|Hip-Hop/Rap|UK Hip-Hop', + 100017 => 'Music|Reggae|Lovers Rock', + 100018 => 'Music|Alternative|EMO', + 100019 => 'Music|Alternative|Pop Punk', + 100020 => 'Music|Alternative|Indie Pop', + 100021 => 'Music|New Age|Yoga', + 100022 => 'Music|Pop|Tribute', + 100023 => 'Music|Pop|Shows', + 100024 => 'Music|Cuban', + 100025 => 'Music|Cuban|Mambo', + 100026 => 'Music|Cuban|Chachacha', + 100027 => 'Music|Cuban|Guajira', + 100028 => 'Music|Cuban|Son', + 100029 => 'Music|Cuban|Bolero', + 100030 => 'Music|Cuban|Guaracha', + 100031 => 'Music|Cuban|Timba', + 100032 => 'Music|Soundtrack|Video Game', + 100033 => 'Music|Indian|Regional Indian|Punjabi|Punjabi Pop', + 100034 => 'Music|Indian|Regional Indian|Bengali|Rabindra Sangeet', + 100035 => 'Music|Indian|Regional Indian|Malayalam', + 100036 => 'Music|Indian|Regional Indian|Kannada', + 100037 => 'Music|Indian|Regional Indian|Marathi', + 100038 => 'Music|Indian|Regional Indian|Gujarati', + 100039 => 'Music|Indian|Regional Indian|Assamese', + 100040 => 'Music|Indian|Regional Indian|Bhojpuri', + 100041 => 'Music|Indian|Regional Indian|Haryanvi', + 100042 => 'Music|Indian|Regional Indian|Odia', + 100043 => 'Music|Indian|Regional Indian|Rajasthani', + 100044 => 'Music|Indian|Regional Indian|Urdu', + 100045 => 'Music|Indian|Regional Indian|Punjabi', + 100046 => 'Music|Indian|Regional Indian|Bengali', + 100047 => 'Music|Indian|Indian Classical|Carnatic Classical', + 100048 => 'Music|Indian|Indian Classical|Hindustani Classical', + 100049 => 'Music|African|Afro House', + 100050 => 'Music|African|Afro Soul', + 100051 => 'Music|African|Afrobeats', + 100052 => 'Music|African|Benga', + 100053 => 'Music|African|Bongo-Flava', + 100054 => 'Music|African|Coupe-Decale', + 100055 => 'Music|African|Gqom', + 100056 => 'Music|African|Highlife', + 100057 => 'Music|African|Kuduro', + 100058 => 'Music|African|Kizomba', + 100059 => 'Music|African|Kwaito', + 100060 => 'Music|African|Mbalax', + 100061 => 'Music|African|Ndombolo', + 100062 => 'Music|African|Shangaan Electro', + 100063 => 'Music|African|Soukous', + 100064 => 'Music|African|Taarab', + 100065 => 'Music|African|Zouglou', + 100066 => 'Music|Turkish|Ozgun', + 100067 => 'Music|Turkish|Fantezi', + 100068 => 'Music|Turkish|Religious', + 100069 => 'Music|Pop|Turkish Pop', + 100070 => 'Music|Rock|Turkish Rock', + 100071 => 'Music|Alternative|Turkish Alternative', + 100072 => 'Music|Hip-Hop/Rap|Turkish Hip-Hop/Rap', + 100073 => 'Music|African|Maskandi', + 100074 => 'Music|Russian|Russian Romance', + 100075 => 'Music|Russian|Russian Bard', + 100076 => 'Music|Russian|Russian Pop', + 100077 => 'Music|Russian|Russian Rock', + 100078 => 'Music|Russian|Russian Hip-Hop', + 100079 => 'Music|Arabic|Levant', + 100080 => 'Music|Arabic|Levant|Dabke', + 100081 => 'Music|Arabic|Maghreb Rai', + 100082 => 'Music|Arabic|Khaleeji|Khaleeji Jalsat', + 100083 => 'Music|Arabic|Khaleeji|Khaleeji Shailat', + 100084 => 'Music|Tarab', + 100085 => 'Music|Tarab|Iraqi Tarab', + 100086 => 'Music|Tarab|Egyptian Tarab', + 100087 => 'Music|Tarab|Khaleeji Tarab', + 100088 => 'Music|Pop|Levant Pop', + 100089 => 'Music|Pop|Iraqi Pop', + 100090 => 'Music|Pop|Egyptian Pop', + 100091 => 'Music|Pop|Maghreb Pop', + 100092 => 'Music|Pop|Khaleeji Pop', + 100093 => 'Music|Hip-Hop/Rap|Levant Hip-Hop', + 100094 => 'Music|Hip-Hop/Rap|Egyptian Hip-Hop', + 100095 => 'Music|Hip-Hop/Rap|Maghreb Hip-Hop', + 100096 => 'Music|Hip-Hop/Rap|Khaleeji Hip-Hop', + 100097 => 'Music|Alternative|Indie Levant', + 100098 => 'Music|Alternative|Indie Egyptian', + 100099 => 'Music|Alternative|Indie Maghreb', + 100100 => 'Music|Electronic|Levant Electronic', + 100101 => "Music|Electronic|Electro-Cha'abi", + 100102 => 'Music|Electronic|Maghreb Electronic', + 100103 => 'Music|Folk|Iraqi Folk', + 100104 => 'Music|Folk|Khaleeji Folk', + 100105 => 'Music|Dance|Maghreb Dance', + 40000000 => 'iTunes U', + 40000001 => 'iTunes U|Business & Economics', + 40000002 => 'iTunes U|Business & Economics|Economics', + 40000003 => 'iTunes U|Business & Economics|Finance', + 40000004 => 'iTunes U|Business & Economics|Hospitality', + 40000005 => 'iTunes U|Business & Economics|Management', + 40000006 => 'iTunes U|Business & Economics|Marketing', + 40000007 => 'iTunes U|Business & Economics|Personal Finance', + 40000008 => 'iTunes U|Business & Economics|Real Estate', + 40000009 => 'iTunes U|Engineering', + 40000010 => 'iTunes U|Engineering|Chemical & Petroleum Engineering', + 40000011 => 'iTunes U|Engineering|Civil Engineering', + 40000012 => 'iTunes U|Engineering|Computer Science', + 40000013 => 'iTunes U|Engineering|Electrical Engineering', + 40000014 => 'iTunes U|Engineering|Environmental Engineering', + 40000015 => 'iTunes U|Engineering|Mechanical Engineering', + 40000016 => 'iTunes U|Music, Art, & Design', + 40000017 => 'iTunes U|Music, Art, & Design|Architecture', + 40000019 => 'iTunes U|Music, Art, & Design|Art History', + 40000020 => 'iTunes U|Music, Art, & Design|Dance', + 40000021 => 'iTunes U|Music, Art, & Design|Film', + 40000022 => 'iTunes U|Music, Art, & Design|Design', + 40000023 => 'iTunes U|Music, Art, & Design|Interior Design', + 40000024 => 'iTunes U|Music, Art, & Design|Music', + 40000025 => 'iTunes U|Music, Art, & Design|Theater', + 40000026 => 'iTunes U|Health & Medicine', + 40000027 => 'iTunes U|Health & Medicine|Anatomy & Physiology', + 40000028 => 'iTunes U|Health & Medicine|Behavioral Science', + 40000029 => 'iTunes U|Health & Medicine|Dentistry', + 40000030 => 'iTunes U|Health & Medicine|Diet & Nutrition', + 40000031 => 'iTunes U|Health & Medicine|Emergency Medicine', + 40000032 => 'iTunes U|Health & Medicine|Genetics', + 40000033 => 'iTunes U|Health & Medicine|Gerontology', + 40000034 => 'iTunes U|Health & Medicine|Health & Exercise Science', + 40000035 => 'iTunes U|Health & Medicine|Immunology', + 40000036 => 'iTunes U|Health & Medicine|Neuroscience', + 40000037 => 'iTunes U|Health & Medicine|Pharmacology & Toxicology', + 40000038 => 'iTunes U|Health & Medicine|Psychiatry', + 40000039 => 'iTunes U|Health & Medicine|Global Health', + 40000040 => 'iTunes U|Health & Medicine|Radiology', + 40000041 => 'iTunes U|History', + 40000042 => 'iTunes U|History|Ancient History', + 40000043 => 'iTunes U|History|Medieval History', + 40000044 => 'iTunes U|History|Military History', + 40000045 => 'iTunes U|History|Modern History', + 40000046 => 'iTunes U|History|African History', + 40000047 => 'iTunes U|History|Asia-Pacific History', + 40000048 => 'iTunes U|History|European History', + 40000049 => 'iTunes U|History|Middle Eastern History', + 40000050 => 'iTunes U|History|North American History', + 40000051 => 'iTunes U|History|South American History', + 40000053 => 'iTunes U|Communications & Journalism', + 40000054 => 'iTunes U|Philosophy', + 40000055 => 'iTunes U|Religion & Spirituality', + 40000056 => 'iTunes U|Languages', + 40000057 => 'iTunes U|Languages|African Languages', + 40000058 => 'iTunes U|Languages|Ancient Languages', + 40000061 => 'iTunes U|Languages|English', + 40000063 => 'iTunes U|Languages|French', + 40000064 => 'iTunes U|Languages|German', + 40000065 => 'iTunes U|Languages|Italian', + 40000066 => 'iTunes U|Languages|Linguistics', + 40000068 => 'iTunes U|Languages|Spanish', + 40000069 => 'iTunes U|Languages|Speech Pathology', + 40000070 => 'iTunes U|Writing & Literature', + 40000071 => 'iTunes U|Writing & Literature|Anthologies', + 40000072 => 'iTunes U|Writing & Literature|Biography', + 40000073 => 'iTunes U|Writing & Literature|Classics', + 40000074 => 'iTunes U|Writing & Literature|Literary Criticism', + 40000075 => 'iTunes U|Writing & Literature|Fiction', + 40000076 => 'iTunes U|Writing & Literature|Poetry', + 40000077 => 'iTunes U|Mathematics', + 40000078 => 'iTunes U|Mathematics|Advanced Mathematics', + 40000079 => 'iTunes U|Mathematics|Algebra', + 40000080 => 'iTunes U|Mathematics|Arithmetic', + 40000081 => 'iTunes U|Mathematics|Calculus', + 40000082 => 'iTunes U|Mathematics|Geometry', + 40000083 => 'iTunes U|Mathematics|Statistics', + 40000084 => 'iTunes U|Science', + 40000085 => 'iTunes U|Science|Agricultural', + 40000086 => 'iTunes U|Science|Astronomy', + 40000087 => 'iTunes U|Science|Atmosphere', + 40000088 => 'iTunes U|Science|Biology', + 40000089 => 'iTunes U|Science|Chemistry', + 40000090 => 'iTunes U|Science|Ecology', + 40000091 => 'iTunes U|Science|Geography', + 40000092 => 'iTunes U|Science|Geology', + 40000093 => 'iTunes U|Science|Physics', + 40000094 => 'iTunes U|Social Science', + 40000095 => 'iTunes U|Law & Politics|Law', + 40000096 => 'iTunes U|Law & Politics|Political Science', + 40000097 => 'iTunes U|Law & Politics|Public Administration', + 40000098 => 'iTunes U|Social Science|Psychology', + 40000099 => 'iTunes U|Social Science|Social Welfare', + 40000100 => 'iTunes U|Social Science|Sociology', + 40000101 => 'iTunes U|Society', + 40000103 => 'iTunes U|Society|Asia Pacific Studies', + 40000104 => 'iTunes U|Society|European Studies', + 40000105 => 'iTunes U|Society|Indigenous Studies', + 40000106 => 'iTunes U|Society|Latin & Caribbean Studies', + 40000107 => 'iTunes U|Society|Middle Eastern Studies', + 40000108 => "iTunes U|Society|Women's Studies", + 40000109 => 'iTunes U|Teaching & Learning', + 40000110 => 'iTunes U|Teaching & Learning|Curriculum & Teaching', + 40000111 => 'iTunes U|Teaching & Learning|Educational Leadership', + 40000112 => 'iTunes U|Teaching & Learning|Family & Childcare', + 40000113 => 'iTunes U|Teaching & Learning|Learning Resources', + 40000114 => 'iTunes U|Teaching & Learning|Psychology & Research', + 40000115 => 'iTunes U|Teaching & Learning|Special Education', + 40000116 => 'iTunes U|Music, Art, & Design|Culinary Arts', + 40000117 => 'iTunes U|Music, Art, & Design|Fashion', + 40000118 => 'iTunes U|Music, Art, & Design|Media Arts', + 40000119 => 'iTunes U|Music, Art, & Design|Photography', + 40000120 => 'iTunes U|Music, Art, & Design|Visual Art', + 40000121 => 'iTunes U|Business & Economics|Entrepreneurship', + 40000122 => 'iTunes U|Communications & Journalism|Broadcasting', + 40000123 => 'iTunes U|Communications & Journalism|Digital Media', + 40000124 => 'iTunes U|Communications & Journalism|Journalism', + 40000125 => 'iTunes U|Communications & Journalism|Photojournalism', + 40000126 => 'iTunes U|Communications & Journalism|Print', + 40000127 => 'iTunes U|Communications & Journalism|Speech', + 40000128 => 'iTunes U|Communications & Journalism|Writing', + 40000129 => 'iTunes U|Health & Medicine|Nursing', + 40000130 => 'iTunes U|Languages|Arabic', + 40000131 => 'iTunes U|Languages|Chinese', + 40000132 => 'iTunes U|Languages|Hebrew', + 40000133 => 'iTunes U|Languages|Hindi', + 40000134 => 'iTunes U|Languages|Indigenous Languages', + 40000135 => 'iTunes U|Languages|Japanese', + 40000136 => 'iTunes U|Languages|Korean', + 40000137 => 'iTunes U|Languages|Other Languages', + 40000138 => 'iTunes U|Languages|Portuguese', + 40000139 => 'iTunes U|Languages|Russian', + 40000140 => 'iTunes U|Law & Politics', + 40000141 => 'iTunes U|Law & Politics|Foreign Policy & International Relations', + 40000142 => 'iTunes U|Law & Politics|Local Governments', + 40000143 => 'iTunes U|Law & Politics|National Governments', + 40000144 => 'iTunes U|Law & Politics|World Affairs', + 40000145 => 'iTunes U|Writing & Literature|Comparative Literature', + 40000146 => 'iTunes U|Philosophy|Aesthetics', + 40000147 => 'iTunes U|Philosophy|Epistemology', + 40000148 => 'iTunes U|Philosophy|Ethics', + 40000149 => 'iTunes U|Philosophy|Metaphysics', + 40000150 => 'iTunes U|Philosophy|Political Philosophy', + 40000151 => 'iTunes U|Philosophy|Logic', + 40000152 => 'iTunes U|Philosophy|Philosophy of Language', + 40000153 => 'iTunes U|Philosophy|Philosophy of Religion', + 40000154 => 'iTunes U|Social Science|Archaeology', + 40000155 => 'iTunes U|Social Science|Anthropology', + 40000156 => 'iTunes U|Religion & Spirituality|Buddhism', + 40000157 => 'iTunes U|Religion & Spirituality|Christianity', + 40000158 => 'iTunes U|Religion & Spirituality|Comparative Religion', + 40000159 => 'iTunes U|Religion & Spirituality|Hinduism', + 40000160 => 'iTunes U|Religion & Spirituality|Islam', + 40000161 => 'iTunes U|Religion & Spirituality|Judaism', + 40000162 => 'iTunes U|Religion & Spirituality|Other Religions', + 40000163 => 'iTunes U|Religion & Spirituality|Spirituality', + 40000164 => 'iTunes U|Science|Environment', + 40000165 => 'iTunes U|Society|African Studies', + 40000166 => 'iTunes U|Society|American Studies', + 40000167 => 'iTunes U|Society|Cross-cultural Studies', + 40000168 => 'iTunes U|Society|Immigration & Emigration', + 40000169 => 'iTunes U|Society|Race & Ethnicity Studies', + 40000170 => 'iTunes U|Society|Sexuality Studies', + 40000171 => 'iTunes U|Teaching & Learning|Educational Technology', + 40000172 => 'iTunes U|Teaching & Learning|Information/Library Science', + 40000173 => 'iTunes U|Languages|Dutch', + 40000174 => 'iTunes U|Languages|Luxembourgish', + 40000175 => 'iTunes U|Languages|Swedish', + 40000176 => 'iTunes U|Languages|Norwegian', + 40000177 => 'iTunes U|Languages|Finnish', + 40000178 => 'iTunes U|Languages|Danish', + 40000179 => 'iTunes U|Languages|Polish', + 40000180 => 'iTunes U|Languages|Turkish', + 40000181 => 'iTunes U|Languages|Flemish', + 50000024 => 'Audiobooks', + 50000040 => 'Audiobooks|Fiction', + 50000041 => 'Audiobooks|Arts & Entertainment', + 50000042 => 'Audiobooks|Biographies & Memoirs', + 50000043 => 'Audiobooks|Business & Personal Finance', + 50000044 => 'Audiobooks|Kids & Young Adults', + 50000045 => 'Audiobooks|Classics', + 50000046 => 'Audiobooks|Comedy', + 50000047 => 'Audiobooks|Drama & Poetry', + 50000048 => 'Audiobooks|Speakers & Storytellers', + 50000049 => 'Audiobooks|History', + 50000050 => 'Audiobooks|Languages', + 50000051 => 'Audiobooks|Mysteries & Thrillers', + 50000052 => 'Audiobooks|Nonfiction', + 50000053 => 'Audiobooks|Religion & Spirituality', + 50000054 => 'Audiobooks|Science & Nature', + 50000055 => 'Audiobooks|Sci Fi & Fantasy', + 50000056 => 'Audiobooks|Self-Development', + 50000057 => 'Audiobooks|Sports & Outdoors', + 50000058 => 'Audiobooks|Technology', + 50000059 => 'Audiobooks|Travel & Adventure', + 50000061 => 'Music|Spoken Word', + 50000063 => 'Music|Disney', + 50000064 => 'Music|French Pop', + 50000066 => 'Music|German Pop', + 50000068 => 'Music|German Folk', + 50000069 => 'Audiobooks|Romance', + 50000070 => 'Audiobooks|Audiobooks Latino', + 50000071 => 'Books|Comics & Graphic Novels|Manga|Action', + 50000072 => 'Books|Comics & Graphic Novels|Manga|Comedy', + 50000073 => 'Books|Comics & Graphic Novels|Manga|Erotica', + 50000074 => 'Books|Comics & Graphic Novels|Manga|Fantasy', + 50000075 => 'Books|Comics & Graphic Novels|Manga|Four Cell Manga', + 50000076 => 'Books|Comics & Graphic Novels|Manga|Gay & Lesbian', + 50000077 => 'Books|Comics & Graphic Novels|Manga|Hard-Boiled', + 50000078 => 'Books|Comics & Graphic Novels|Manga|Heroes', + 50000079 => 'Books|Comics & Graphic Novels|Manga|Historical Fiction', + 50000080 => 'Books|Comics & Graphic Novels|Manga|Mecha', + 50000081 => 'Books|Comics & Graphic Novels|Manga|Mystery', + 50000082 => 'Books|Comics & Graphic Novels|Manga|Nonfiction', + 50000083 => 'Books|Comics & Graphic Novels|Manga|Religious', + 50000084 => 'Books|Comics & Graphic Novels|Manga|Romance', + 50000085 => 'Books|Comics & Graphic Novels|Manga|Romantic Comedy', + 50000086 => 'Books|Comics & Graphic Novels|Manga|Science Fiction', + 50000087 => 'Books|Comics & Graphic Novels|Manga|Sports', + 50000088 => 'Books|Fiction & Literature|Light Novels', + 50000089 => 'Books|Comics & Graphic Novels|Manga|Horror', + 50000090 => 'Books|Comics & Graphic Novels|Comics', + 50000091 => 'Books|Romance|Multicultural', + 50000092 => 'Audiobooks|Erotica', + 50000093 => 'Audiobooks|Light Novels', + }, + }, + grup => { Name => 'Grouping', Avoid => 1 }, #10 + hdvd => { #10 + Name => 'HDVideo', + Format => 'int8u', #24 + Writable => 'int8s', #27 + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + keyw => 'Keyword', #7 + ldes => 'LongDescription', #10 + pcst => { #7 + Name => 'Podcast', + Format => 'int8u', #23 + Writable => 'int8s', #27 + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + perf => 'Performer', + plID => { + # (ref 10 called this PlayListID or TVSeason) + Name => 'AlbumID', #28 + Format => 'int64u', + Writable => 'int32s', #27 + }, + purd => 'PurchaseDate', #7 + purl => 'PodcastURL', #7 + rtng => { #10 + Name => 'Rating', + Format => 'int8u', #23 + Writable => 'int8s', #27 + PrintConv => { + 0 => 'none', + 1 => 'Explicit', + 2 => 'Clean', + 4 => 'Explicit (old)', + }, + }, + sfID => { #10 + Name => 'AppleStoreCountry', + Format => 'int32u', + Writable => 'int32s', #27 + SeparateTable => 1, + PrintConv => { #21 + 143441 => 'United States', # US + 143442 => 'France', # FR + 143443 => 'Germany', # DE + 143444 => 'United Kingdom', # GB + 143445 => 'Austria', # AT + 143446 => 'Belgium', # BE + 143447 => 'Finland', # FI + 143448 => 'Greece', # GR + 143449 => 'Ireland', # IE + 143450 => 'Italy', # IT + 143451 => 'Luxembourg', # LU + 143452 => 'Netherlands', # NL + 143453 => 'Portugal', # PT + 143454 => 'Spain', # ES + 143455 => 'Canada', # CA + 143456 => 'Sweden', # SE + 143457 => 'Norway', # NO + 143458 => 'Denmark', # DK + 143459 => 'Switzerland', # CH + 143460 => 'Australia', # AU + 143461 => 'New Zealand', # NZ + 143462 => 'Japan', # JP + 143463 => 'Hong Kong', # HK + 143464 => 'Singapore', # SG + 143465 => 'China', # CN + 143466 => 'Republic of Korea', # KR + 143467 => 'India', # IN + 143468 => 'Mexico', # MX + 143469 => 'Russia', # RU + 143470 => 'Taiwan', # TW + 143471 => 'Vietnam', # VN + 143472 => 'South Africa', # ZA + 143473 => 'Malaysia', # MY + 143474 => 'Philippines', # PH + 143475 => 'Thailand', # TH + 143476 => 'Indonesia', # ID + 143477 => 'Pakistan', # PK + 143478 => 'Poland', # PL + 143479 => 'Saudi Arabia', # SA + 143480 => 'Turkey', # TR + 143481 => 'United Arab Emirates', # AE + 143482 => 'Hungary', # HU + 143483 => 'Chile', # CL + 143484 => 'Nepal', # NP + 143485 => 'Panama', # PA + 143486 => 'Sri Lanka', # LK + 143487 => 'Romania', # RO + 143489 => 'Czech Republic', # CZ + 143491 => 'Israel', # IL + 143492 => 'Ukraine', # UA + 143493 => 'Kuwait', # KW + 143494 => 'Croatia', # HR + 143495 => 'Costa Rica', # CR + 143496 => 'Slovakia', # SK + 143497 => 'Lebanon', # LB + 143498 => 'Qatar', # QA + 143499 => 'Slovenia', # SI + 143501 => 'Colombia', # CO + 143502 => 'Venezuela', # VE + 143503 => 'Brazil', # BR + 143504 => 'Guatemala', # GT + 143505 => 'Argentina', # AR + 143506 => 'El Salvador', # SV + 143507 => 'Peru', # PE + 143508 => 'Dominican Republic', # DO + 143509 => 'Ecuador', # EC + 143510 => 'Honduras', # HN + 143511 => 'Jamaica', # JM + 143512 => 'Nicaragua', # NI + 143513 => 'Paraguay', # PY + 143514 => 'Uruguay', # UY + 143515 => 'Macau', # MO + 143516 => 'Egypt', # EG + 143517 => 'Kazakhstan', # KZ + 143518 => 'Estonia', # EE + 143519 => 'Latvia', # LV + 143520 => 'Lithuania', # LT + 143521 => 'Malta', # MT + 143523 => 'Moldova', # MD + 143524 => 'Armenia', # AM + 143525 => 'Botswana', # BW + 143526 => 'Bulgaria', # BG + 143528 => 'Jordan', # JO + 143529 => 'Kenya', # KE + 143530 => 'Macedonia', # MK + 143531 => 'Madagascar', # MG + 143532 => 'Mali', # ML + 143533 => 'Mauritius', # MU + 143534 => 'Niger', # NE + 143535 => 'Senegal', # SN + 143536 => 'Tunisia', # TN + 143537 => 'Uganda', # UG + 143538 => 'Anguilla', # AI + 143539 => 'Bahamas', # BS + 143540 => 'Antigua and Barbuda', # AG + 143541 => 'Barbados', # BB + 143542 => 'Bermuda', # BM + 143543 => 'British Virgin Islands', # VG + 143544 => 'Cayman Islands', # KY + 143545 => 'Dominica', # DM + 143546 => 'Grenada', # GD + 143547 => 'Montserrat', # MS + 143548 => 'St. Kitts and Nevis', # KN + 143549 => 'St. Lucia', # LC + 143550 => 'St. Vincent and The Grenadines', # VC + 143551 => 'Trinidad and Tobago', # TT + 143552 => 'Turks and Caicos', # TC + 143553 => 'Guyana', # GY + 143554 => 'Suriname', # SR + 143555 => 'Belize', # BZ + 143556 => 'Bolivia', # BO + 143557 => 'Cyprus', # CY + 143558 => 'Iceland', # IS + 143559 => 'Bahrain', # BH + 143560 => 'Brunei Darussalam', # BN + 143561 => 'Nigeria', # NG + 143562 => 'Oman', # OM + 143563 => 'Algeria', # DZ + 143564 => 'Angola', # AO + 143565 => 'Belarus', # BY + 143566 => 'Uzbekistan', # UZ + 143568 => 'Azerbaijan', # AZ + 143571 => 'Yemen', # YE + 143572 => 'Tanzania', # TZ + 143573 => 'Ghana', # GH + 143575 => 'Albania', # AL + 143576 => 'Benin', # BJ + 143577 => 'Bhutan', # BT + 143578 => 'Burkina Faso', # BF + 143579 => 'Cambodia', # KH + 143580 => 'Cape Verde', # CV + 143581 => 'Chad', # TD + 143582 => 'Republic of the Congo', # CG + 143583 => 'Fiji', # FJ + 143584 => 'Gambia', # GM + 143585 => 'Guinea-Bissau', # GW + 143586 => 'Kyrgyzstan', # KG + 143587 => "Lao People's Democratic Republic", # LA + 143588 => 'Liberia', # LR + 143589 => 'Malawi', # MW + 143590 => 'Mauritania', # MR + 143591 => 'Federated States of Micronesia', # FM + 143592 => 'Mongolia', # MN + 143593 => 'Mozambique', # MZ + 143594 => 'Namibia', # NA + 143595 => 'Palau', # PW + 143597 => 'Papua New Guinea', # PG + 143598 => 'Sao Tome and Principe', # ST (S&atilde;o Tom&eacute; and Pr&iacute;ncipe) + 143599 => 'Seychelles', # SC + 143600 => 'Sierra Leone', # SL + 143601 => 'Solomon Islands', # SB + 143602 => 'Swaziland', # SZ + 143603 => 'Tajikistan', # TJ + 143604 => 'Turkmenistan', # TM + 143605 => 'Zimbabwe', # ZW + }, + }, + soaa => 'SortAlbumArtist', #10 + soal => 'SortAlbum', #10 + soar => 'SortArtist', #10 + soco => 'SortComposer', #10 + sonm => 'SortName', #10 + sosn => 'SortShow', #10 + stik => { #10 + Name => 'MediaType', + Format => 'int8u', #23 + Writable => 'int8s', #27 + PrintConvColumns => 2, + PrintConv => { #(http://weblog.xanga.com/gryphondwb/615474010/iphone-ringtones---what-did-itunes-741-really-do.html) + 0 => 'Movie (old)', #forum9059 (was Movie) + 1 => 'Normal (Music)', + 2 => 'Audiobook', + 5 => 'Whacked Bookmark', + 6 => 'Music Video', + 9 => 'Movie', #forum9059 (was Short Film) + 10 => 'TV Show', + 11 => 'Booklet', + 14 => 'Ringtone', + 21 => 'Podcast', #15 + 23 => 'iTunes U', #forum9059 + }, + }, + rate => 'RatingPercent', #PH + titl => { Name => 'Title', Avoid => 1 }, + tven => 'TVEpisodeID', #7 + tves => { #7/10 + Name => 'TVEpisode', + Format => 'int32u', + Writable => 'int32s', #27 + }, + tvnn => 'TVNetworkName', #7 + tvsh => 'TVShow', #10 + tvsn => { #7/10 + Name => 'TVSeason', + Format => 'int32u', + }, + yrrc => 'Year', #(ffmpeg source) + itnu => { #PH (iTunes 10.5) + Name => 'iTunesU', + Format => 'int8u', #27 + Writable => 'int8s', #27 + Description => 'iTunes U', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + #https://github.com/communitymedia/mediautilities/blob/master/src/net/sourceforge/jaad/mp4/boxes/BoxTypes.java + gshh => { Name => 'GoogleHostHeader', Format => 'string' }, + gspm => { Name => 'GooglePingMessage', Format => 'string' }, + gspu => { Name => 'GooglePingURL', Format => 'string' }, + gssd => { Name => 'GoogleSourceData', Format => 'string' }, + gsst => { Name => 'GoogleStartTime', Format => 'string' }, + gstd => { + Name => 'GoogleTrackDuration', + Format => 'string', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => 'ConvertDuration($val)', + PrintConvInv => q{ + $val =~ s/ s$//; + my @a = split /(:| days )/, $val; + my $sign = ($val =~ s/^-//) ? -1 : 1; + $a[0] += shift(@a) * 24 if @a == 4; + $a[0] += shift(@a) * 60 while @a > 1; + return $a[0] * $sign; + }, + }, + + # atoms observed in AAX audiobooks (ref PH) + "\xa9cpy" => { Name => 'Copyright', Avoid => 1, Groups => { 2 => 'Author' } }, + "\xa9pub" => 'Publisher', + "\xa9nrt" => 'Narrator', + '@pti' => 'ParentTitle', # (guess -- same as "\xa9nam") + '@PST' => 'ParentShortTitle', # (guess -- same as "\xa9nam") + '@ppi' => 'ParentProductID', # (guess -- same as 'prID') + '@sti' => 'ShortTitle', # (guess -- same as "\xa9nam") + prID => 'ProductID', + rldt => { Name => 'ReleaseDate', Groups => { 2 => 'Time' }}, + CDEK => { Name => 'Unknown_CDEK', Unknown => 1 }, # eg: "B004ZMTFEG" - used in URL's ("asin=") + CDET => { Name => 'Unknown_CDET', Unknown => 1 }, # eg: "ADBL" + VERS => 'ProductVersion', + GUID => 'GUID', + AACR => { Name => 'Unknown_AACR', Unknown => 1 }, # eg: "CR!1T1H1QH6WX7T714G2BMFX3E9MC4S" + # ausr - 30 bytes (User Alias?) + "\xa9xyz" => { #PH (written by Google Photos) + Name => 'GPSCoordinates', + Groups => { 2 => 'Location' }, + ValueConv => \&ConvertISO6709, + ValueConvInv => \&ConvInvISO6709, + PrintConv => \&PrintGPSCoordinates, + PrintConvInv => \&PrintInvGPSCoordinates, + }, + # the following tags written by iTunes 12.5.1.21 + # (ref https://www.ventismedia.com/mantis/view.php?id=14963 + # https://community.mp3tag.de/t/x-mp4-new-tag-problems/19488) + "\xa9wrk" => 'Work', #PH + "\xa9mvn" => 'MovementName', #PH + "\xa9mvi" => { #PH + Name => 'MovementNumber', + Format => 'int16u', #27 + Writable => 'int16s', #27 + }, + "\xa9mvc" => { #PH + Name => 'MovementCount', + Format => 'int16u', #27 + Writable => 'int16s', #27 + }, + shwm => { #PH + Name => 'ShowMovement', + Format => 'int8u', #27 + Writable => 'int8s', #27 + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, +); + +# tag decoded from timed face records +%Image::ExifTool::QuickTime::FaceInfo = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + crec => { + Name => 'FaceRec', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::FaceRec', + }, + }, +); + +# tag decoded from timed face records +%Image::ExifTool::QuickTime::FaceRec = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + cits => { + Name => 'FaceItem', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Keys', + ProcessProc => \&Process_mebx, + }, + }, +); + +# item list keys (ref PH) +%Image::ExifTool::QuickTime::Keys = ( + PROCESS_PROC => \&ProcessKeys, + WRITE_PROC => \&WriteKeys, + CHECK_PROC => \&CheckQTValue, + VARS => { LONG_TAGS => 7 }, + WRITABLE => 1, + # (not PREFERRED when writing) + GROUPS => { 1 => 'Keys' }, + WRITE_GROUP => 'Keys', + LANG_INFO => \&GetLangInfo, + NOTES => q{ + This directory contains a list of key names which are used to decode tags + written by the "mdta" handler. Also in this table are a few tags found in + timed metadata that are not yet writable by ExifTool. The prefix of + "com.apple.quicktime." has been removed from the TagID's below. These tags + support alternate languages in the same way as the + L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> tags. Note + that by default, + L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> and + L<UserData|Image::ExifTool::TagNames/QuickTime UserData Tags> tags are + preferred when writing, so to create a tag when a same-named tag exists in + either of these tables, either the "Keys" location must be specified (eg. + C<-Keys:Author=Phil> on the command line), or the PREFERRED level must be + changed via L<the config file|../config.html#PREF>. + }, + version => 'Version', + album => 'Album', + artist => { }, + artwork => { }, + author => { Name => 'Author', Groups => { 2 => 'Author' } }, + comment => { }, + copyright => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + creationdate=> { + Name => 'CreationDate', + Groups => { 2 => 'Time' }, + Shift => 'Time', + ValueConv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::ConvertXMPDate($val,1); + $val =~ s/([-+]\d{2})(\d{2})$/$1:$2/; # add colon to timezone if necessary + return $val; + }, + ValueConvInv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::FormatXMPDate($val); + $val =~ s/([-+]\d{2}):(\d{2})$/$1$2/; # remove time zone colon + return $val; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,1)', # (add time zone if it didn't exist) + }, + description => { }, + director => { }, + displayname => { Name => 'DisplayName' }, + title => { }, #22 + genre => { }, + information => { }, + keywords => { }, + producer => { }, #22 + make => { Name => 'Make', Groups => { 2 => 'Camera' } }, + model => { Name => 'Model', Groups => { 2 => 'Camera' } }, + publisher => { }, + software => { }, + year => { Groups => { 2 => 'Time' } }, + 'camera.identifier' => 'CameraIdentifier', # (iPhone 4) + 'camera.framereadouttimeinmicroseconds' => { # (iPhone 4) + Name => 'FrameReadoutTime', + ValueConv => '$val * 1e-6', + ValueConvInv => 'int($val * 1e6 + 0.5)', + PrintConv => '$val * 1e6 . " microseconds"', + PrintConvInv => '$val =~ s/ .*//; $val * 1e-6', + }, + 'location.ISO6709' => { + Name => 'GPSCoordinates', + Groups => { 2 => 'Location' }, + Notes => q{ + Google Photos may ignore this if the coorinates have more than 5 digits + after the decimal + }, + ValueConv => \&ConvertISO6709, + ValueConvInv => \&ConvInvISO6709, + PrintConv => \&PrintGPSCoordinates, + PrintConvInv => \&PrintInvGPSCoordinates, + }, + 'location.name' => { Name => 'LocationName', Groups => { 2 => 'Location' } }, + 'location.body' => { Name => 'LocationBody', Groups => { 2 => 'Location' } }, + 'location.note' => { Name => 'LocationNote', Groups => { 2 => 'Location' } }, + 'location.role' => { + Name => 'LocationRole', + Groups => { 2 => 'Location' }, + PrintConv => { + 0 => 'Shooting Location', + 1 => 'Real Location', + 2 => 'Fictional Location', + }, + }, + 'location.date' => { + Name => 'LocationDate', + Groups => { 2 => 'Time' }, + Shift => 'Time', + ValueConv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::ConvertXMPDate($val); + $val =~ s/([-+]\d{2})(\d{2})$/$1:$2/; # add colon to timezone if necessary + return $val; + }, + ValueConvInv => q{ + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::FormatXMPDate($val); + $val =~ s/([-+]\d{2}):(\d{2})$/$1$2/; # remove time zone colon + return $val; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,1)', # (add time zone if it didn't exist) + }, + 'location.accuracy.horizontal' => { Name => 'LocationAccuracyHorizontal' }, + 'live-photo.auto' => { Name => 'LivePhotoAuto', Writable => 'int8u' }, + 'live-photo.vitality-score' => { Name => 'LivePhotoVitalityScore', Writable => 'float' }, + 'live-photo.vitality-scoring-version' => { Name => 'LivePhotoVitalityScoringVersion', Writable => 'int64s' }, + 'apple.photos.variation-identifier' => { Name => 'ApplePhotosVariationIdentifier', Writable => 'int64s' }, + 'direction.facing' => { Name => 'CameraDirection', Groups => { 2 => 'Location' } }, + 'direction.motion' => { Name => 'CameraMotion', Groups => { 2 => 'Location' } }, + 'location.body' => { Name => 'LocationBody', Groups => { 2 => 'Location' } }, + 'player.version' => 'PlayerVersion', + 'player.movie.visual.brightness'=> 'Brightness', + 'player.movie.visual.color' => 'Color', + 'player.movie.visual.tint' => 'Tint', + 'player.movie.visual.contrast' => 'Contrast', + 'player.movie.audio.gain' => 'AudioGain', + 'player.movie.audio.treble' => 'Treble', + 'player.movie.audio.bass' => 'Bass', + 'player.movie.audio.balance' => 'Balance', + 'player.movie.audio.pitchshift' => 'PitchShift', + 'player.movie.audio.mute' => { + Name => 'Mute', + Format => 'int8u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 'rating.user' => 'UserRating', # (Canon ELPH 510 HS) + 'collection.user' => 'UserCollection', #22 + 'Encoded_With' => 'EncodedWith', + 'content.identifier' => 'ContentIdentifier', #forum14874 +# +# the following tags aren't in the com.apple.quicktime namespace: +# + 'com.apple.photos.captureMode' => 'CaptureMode', + 'com.android.version' => 'AndroidVersion', +# +# also seen +# + # com.divergentmedia.clipwrap.model ('NEX-FS700EK') + # com.divergentmedia.clipwrap.model1 ('49') + # com.divergentmedia.clipwrap.model2 ('0') + # com.divergentmedia.clipwrap.manufacturer ('Sony') + # com.divergentmedia.clipwrap.originalDateTime ('2013/2/6 10:30:40+0200') +# +# seen in timed metadata (mebx), and added dynamically to the table via SaveMetaKeys() +# NOTE: these tags are not writable! (timed metadata cannot yet be written) +# + # (mdta)com.apple.quicktime.video-orientation (dtyp=66, int16s) + 'video-orientation' => { + Name => 'VideoOrientation', + Writable => 0, + PrintConv => \%Image::ExifTool::Exif::orientation, #PH (NC) + }, + # (mdta)com.apple.quicktime.live-photo-info (dtyp=com.apple.quicktime.com.apple.quicktime.live-photo-info) + 'live-photo-info' => { + Name => 'LivePhotoInfo', + Writable => 0, + # not sure what these values mean, but unpack them anyway - PH + # (ignore the fact that the "f" and "l" unpacks won't work on a big-endian machine) + ValueConv => 'join " ",unpack "VfVVf6c4lCCcclf4Vvv", $val', + }, + # (mdta)com.apple.quicktime.still-image-time (dtyp=65, int8s) + 'still-image-time' => { # (found in live photo) + Name => 'StillImageTime', + Writable => 0, + Notes => q{ + this tag always has a value of -1; the time of the still image is obtained + from the associated SampleTime + }, + }, + # (mdta)com.apple.quicktime.detected-face (dtyp='com.apple.quicktime.detected-face') + 'detected-face' => { + Name => 'FaceInfo', + Writable => 0, + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::FaceInfo' }, + }, + # ---- detected-face fields ( ---- + # --> back here after a round trip through FaceInfo -> FaceRec -> FaceItem + # (fiel)com.apple.quicktime.detected-face.bounds (dtyp=80, float[8]) + 'detected-face.bounds' => { + Name => 'DetectedFaceBounds', + Writable => 0, + # round to a reasonable number of decimal places + PrintConv => 'my @a=split " ",$val;$_=int($_*1e6+.5)/1e6 foreach @a;join " ",@a', + PrintConvInv => '$val', + }, + # (fiel)com.apple.quicktime.detected-face.face-id (dtyp=77, int32u) + 'detected-face.face-id' => { Name => 'DetectedFaceID', Writable => 0 }, + # (fiel)com.apple.quicktime.detected-face.roll-angle (dtyp=23, float) + 'detected-face.roll-angle' => { Name => 'DetectedFaceRollAngle', Writable => 0 }, + # (fiel)com.apple.quicktime.detected-face.yaw-angle (dtyp=23, float) + 'detected-face.yaw-angle' => { Name => 'DetectedFaceYawAngle', Writable => 0 }, +# +# seen in Apple ProRes RAW file +# + # (mdta)com.apple.proapps.manufacturer (eg. "Sony") + # (mdta)com.apple.proapps.exif.{Exif}.FNumber (float, eg. 1.0) + # (mdta)org.smpte.rdd18.lens.irisfnumber (eg. "F1.0") + # (mdta)com.apple.proapps.exif.{Exif}.ShutterSpeedValue (float, eg. 1.006) + # (mdta)org.smpte.rdd18.camera.shutterspeed_angle (eg. "179.2deg") + # (mdta)org.smpte.rdd18.camera.neutraldensityfilterwheelsetting (eg. "ND1") + # (mdta)org.smpte.rdd18.camera.whitebalance (eg. "4300K") + # (mdta)com.apple.proapps.exif.{Exif}.ExposureIndex (float, eg. 4000) + # (mdta)org.smpte.rdd18.camera.isosensitivity (eg. "4000") + # (mdta)com.apple.proapps.image.{TIFF}.Make (eg. "Atmos") + # (mdta)com.apple.proapps.image.{TIFF}.Model (eg. "ShogunInferno") + # (mdta)com.apple.proapps.image.{TIFF}.Software (eg. "9.0") +); + +# iTunes info ('----') atoms +%Image::ExifTool::QuickTime::iTunesInfo = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 1 => 'iTunes', 2 => 'Audio' }, + VARS => { LONG_TAGS => 0 }, # (hack for discrepancy in the way long tags are counted in BuildTagLookup) + NOTES => q{ + ExifTool will extract any iTunesInfo tags that exist, even if they are not + defined in this table. These tags belong to the family 1 "iTunes" group, + and are not currently writable. + }, + # 'mean'/'name'/'data' atoms form a triplet, but unfortunately + # I haven't been able to find any documentation on this. + # 'mean' is normally 'com.apple.iTunes' + mean => { + Name => 'Mean', + # the 'Triplet' flag tells ProcessMOV() to generate + # a single tag from the mean/name/data triplet + Triplet => 1, + Hidden => 1, + }, + name => { + Name => 'Name', + Triplet => 1, + Hidden => 1, + }, + data => { + Name => 'Data', + Triplet => 1, + Hidden => 1, + }, + # the tag ID's below are composed from "mean/name", + # but "mean/" is omitted if it is "com.apple.iTunes/": + 'iTunMOVI' => { + Name => 'iTunMOVI', + SubDirectory => { TagTable => 'Image::ExifTool::PLIST::Main' }, + }, + 'tool' => { + Name => 'iTunTool', + Description => 'iTunTool', + Format => 'int32u', + PrintConv => 'sprintf("0x%.8x",$val)', + }, + 'iTunEXTC' => { + Name => 'ContentRating', + Notes => 'standard | rating | score | reasons', + # eg. 'us-tv|TV-14|500|V', 'mpaa|PG-13|300|For violence and sexuality' + # (see http://shadowofged.blogspot.ca/2008/06/itunes-content-ratings.html) + }, + 'iTunNORM' => { + Name => 'VolumeNormalization', + PrintConv => '$val=~s/ 0+(\w)/ $1/g; $val=~s/^\s+//; $val', + }, + 'iTunSMPB' => { + Name => 'iTunSMPB', + Description => 'iTunSMPB', + # hex format, similar to iTunNORM, but 12 words instead of 10, + # and 4th word is 16 hex digits (all others are 8) + # (gives AAC encoder delay, ref http://code.google.com/p/l-smash/issues/detail?id=1) + PrintConv => '$val=~s/ 0+(\w)/ $1/g; $val=~s/^\s+//; $val', + }, + # (CDDB = Compact Disc DataBase) + # iTunes_CDDB_1 = <CDDB1 disk ID>+<# tracks>+<logical block address for each track>... + 'iTunes_CDDB_1' => 'CDDB1Info', + 'iTunes_CDDB_TrackNumber' => 'CDDBTrackNumber', + 'Encoding Params' => { + Name => 'EncodingParams', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::EncodingParams' }, + }, + # also heard about 'iTunPGAP', but I haven't seen a sample + # all tags below were added based on samples I have seen - PH + DISCNUMBER => 'DiscNumber', + TRACKNUMBER => 'TrackNumber', + ARTISTS => 'Artists', + CATALOGNUMBER => 'CatalogNumber', + RATING => 'Rating', + MEDIA => 'Media', + SCRIPT => 'Script', # character set? (seen 'Latn') + BARCODE => 'Barcode', + LABEL => 'Label', + MOOD => 'Mood', + popularimeter => 'Popularimeter', + 'Dynamic Range (DR)'=> 'DynamicRange', + initialkey => 'InitialKey', + originalyear => 'OriginalYear', + originaldate => 'OriginalDate', + '~length' => 'Length', # play length? (ie. duration?) + replaygain_track_gain=>'ReplayTrackGain', + replaygain_track_peak=>'ReplayTrackPeak', + 'Volume Level (ReplayGain)'=> 'ReplayVolumeLevel', + 'Dynamic Range (R128)'=> 'DynamicRangeR128', + 'Volume Level (R128)' => 'VolumeLevelR128', + 'Peak Level (Sample)' => 'PeakLevelSample', + 'Peak Level (R128)' => 'PeakLevelR128', + # also seen (many from forum12777): + # 'MusicBrainz Album Release Country' + # 'MusicBrainz Album Type' + # 'MusicBrainz Album Status' + # 'MusicBrainz Track Id' + # 'MusicBrainz Release Track Id' + # 'MusicBrainz Album Id' + # 'MusicBrainz Album Artist Id' + # 'MusicBrainz Artist Id' + # 'Acoustid Id' (sic) + # 'Tool Version' + # 'Tool Name' + # 'ISRC' + # 'HDCD' + # 'Waveform' +); + +# iTunes audio encoding parameters +# ref https://developer.apple.com/library/mac/#documentation/MusicAudio/Reference/AudioCodecServicesRef/Reference/reference.html +%Image::ExifTool::QuickTime::EncodingParams = ( + PROCESS_PROC => \&ProcessEncodingParams, + GROUPS => { 2 => 'Audio' }, + # (I have commented out the ones that don't have integer values because they + # probably don't appear, and definitely wouldn't work with current decoding - PH) + + # global codec properties + #'lnam' => 'AudioCodecName', + #'lmak' => 'AudioCodecManufacturer', + #'lfor' => 'AudioCodecFormat', + 'vpk?' => 'AudioHasVariablePacketByteSizes', + #'ifm#' => 'AudioSupportedInputFormats', + #'ofm#' => 'AudioSupportedOutputFormats', + #'aisr' => 'AudioAvailableInputSampleRates', + #'aosr' => 'AudioAvailableOutputSampleRates', + 'abrt' => 'AudioAvailableBitRateRange', + 'mnip' => 'AudioMinimumNumberInputPackets', + 'mnop' => 'AudioMinimumNumberOutputPackets', + 'cmnc' => 'AudioAvailableNumberChannels', + 'lmrc' => 'AudioDoesSampleRateConversion', + #'aicl' => 'AudioAvailableInputChannelLayoutTags', + #'aocl' => 'AudioAvailableOutputChannelLayoutTags', + #'if4o' => 'AudioInputFormatsForOutputFormat', + #'of4i' => 'AudioOutputFormatsForInputFormat', + #'acfi' => 'AudioFormatInfo', + + # instance codec properties + 'tbuf' => 'AudioInputBufferSize', + 'pakf' => 'AudioPacketFrameSize', + 'pakb' => 'AudioMaximumPacketByteSize', + #'ifmt' => 'AudioCurrentInputFormat', + #'ofmt' => 'AudioCurrentOutputFormat', + #'kuki' => 'AudioMagicCookie', + 'ubuf' => 'AudioUsedInputBufferSize', + 'init' => 'AudioIsInitialized', + 'brat' => 'AudioCurrentTargetBitRate', + #'cisr' => 'AudioCurrentInputSampleRate', + #'cosr' => 'AudioCurrentOutputSampleRate', + 'srcq' => 'AudioQualitySetting', + #'brta' => 'AudioApplicableBitRateRange', + #'isra' => 'AudioApplicableInputSampleRates', + #'osra' => 'AudioApplicableOutputSampleRates', + 'pad0' => 'AudioZeroFramesPadded', + 'prmm' => 'AudioCodecPrimeMethod', + #'prim' => 'AudioCodecPrimeInfo', + #'icl ' => 'AudioInputChannelLayout', + #'ocl ' => 'AudioOutputChannelLayout', + #'acs ' => 'AudioCodecSettings', + #'acfl' => 'AudioCodecFormatList', + 'acbf' => 'AudioBitRateControlMode', + 'vbrq' => 'AudioVBRQuality', + 'mdel' => 'AudioMinimumDelayMode', + + # deprecated + 'pakd' => 'AudioRequiresPacketDescription', + #'brt#' => 'AudioAvailableBitRates', + 'acef' => 'AudioExtendFrequencies', + 'ursr' => 'AudioUseRecommendedSampleRate', + 'oppr' => 'AudioOutputPrecedence', + #'loud' => 'AudioCurrentLoudnessStatistics', + + # others + 'vers' => 'AudioEncodingParamsVersion', #PH + 'cdcv' => { #PH + Name => 'AudioComponentVersion', + ValueConv => 'join ".", unpack("ncc", pack("N",$val))', + }, +); + +# print to video data block +%Image::ExifTool::QuickTime::Video = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + 0 => { + Name => 'DisplaySize', + PrintConv => { + 0 => 'Normal', + 1 => 'Double Size', + 2 => 'Half Size', + 3 => 'Full Screen', + 4 => 'Current Size', + }, + }, + 6 => { + Name => 'SlideShow', + PrintConv => { + 0 => 'No', + 1 => 'Yes', + }, + }, +); + +# 'hnti' atoms +%Image::ExifTool::QuickTime::HintInfo = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + 'rtp ' => { + Name => 'RealtimeStreamingProtocol', + PrintConv => '$val=~s/^sdp /(SDP) /; $val', + }, + 'sdp ' => 'StreamingDataProtocol', +); + +# 'hinf' atoms +%Image::ExifTool::QuickTime::HintTrackInfo = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Video' }, + trpY => { Name => 'TotalBytes', Format => 'int64u' }, #(documented) + trpy => { Name => 'TotalBytes', Format => 'int64u' }, #(observed) + totl => { Name => 'TotalBytes', Format => 'int32u' }, + nump => { Name => 'NumPackets', Format => 'int64u' }, + npck => { Name => 'NumPackets', Format => 'int32u' }, + tpyl => { Name => 'TotalBytesNoRTPHeaders', Format => 'int64u' }, + tpaY => { Name => 'TotalBytesNoRTPHeaders', Format => 'int32u' }, #(documented) + tpay => { Name => 'TotalBytesNoRTPHeaders', Format => 'int32u' }, #(observed) + maxr => { + Name => 'MaxDataRate', + Format => 'int32u', + Count => 2, + PrintConv => 'my @a=split(" ",$val);sprintf("%d bytes in %.3f s",$a[1],$a[0]/1000)', + }, + dmed => { Name => 'MediaTrackBytes', Format => 'int64u' }, + dimm => { Name => 'ImmediateDataBytes', Format => 'int64u' }, + drep => { Name => 'RepeatedDataBytes', Format => 'int64u' }, + tmin => { + Name => 'MinTransmissionTime', + Format => 'int32u', + PrintConv => 'sprintf("%.3f s",$val/1000)', + }, + tmax => { + Name => 'MaxTransmissionTime', + Format => 'int32u', + PrintConv => 'sprintf("%.3f s",$val/1000)', + }, + pmax => { Name => 'LargestPacketSize', Format => 'int32u' }, + dmax => { + Name => 'LargestPacketDuration', + Format => 'int32u', + PrintConv => 'sprintf("%.3f s",$val/1000)', + }, + payt => { + Name => 'PayloadType', + Format => 'undef', # (necessary to prevent decoding as string!) + ValueConv => 'unpack("N",$val) . " " . substr($val, 5)', + PrintConv => '$val=~s/ /, /;$val', + }, +); + +# MP4 media box (ref 5) +%Image::ExifTool::QuickTime::Media = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 1 => 'Track#', 2 => 'Video' }, + NOTES => 'MP4 media box.', + mdhd => { + Name => 'MediaHeader', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MediaHeader' }, + }, + hdlr => { + Name => 'Handler', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Handler' }, + }, + minf => { + Name => 'MediaInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MediaInfo' }, + }, +); + +# MP4 media header box (ref 5) +%Image::ExifTool::QuickTime::MediaHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 1 => 'Track#', 2 => 'Video' }, + FORMAT => 'int32u', + DATAMEMBER => [ 0, 1, 2, 3, 4 ], + 0 => { + Name => 'MediaHeaderVersion', + RawConv => '$$self{MediaHeaderVersion} = $val', + }, + 1 => { + Name => 'MediaCreateDate', + Groups => { 2 => 'Time' }, + %timeInfo, + # this is int64u if MediaHeaderVersion == 1 (ref 5/13) + Hook => '$$self{MediaHeaderVersion} and $format = "int64u", $varSize += 4', + }, + 2 => { + Name => 'MediaModifyDate', + Groups => { 2 => 'Time' }, + %timeInfo, + # this is int64u if MediaHeaderVersion == 1 (ref 5/13) + Hook => '$$self{MediaHeaderVersion} and $format = "int64u", $varSize += 4', + }, + 3 => { + Name => 'MediaTimeScale', + RawConv => '$$self{MediaTS} = $val', + }, + 4 => { + Name => 'MediaDuration', + RawConv => '$$self{MediaTS} ? $val / $$self{MediaTS} : $val', + PrintConv => '$$self{MediaTS} ? ConvertDuration($val) : $val', + # this is int64u if MediaHeaderVersion == 1 (ref 5/13) + Hook => '$$self{MediaHeaderVersion} and $format = "int64u", $varSize += 4', + }, + 5 => { + Name => 'MediaLanguageCode', + Format => 'int16u', + RawConv => '$val ? $val : undef', + # allow both Macintosh (for MOV files) and ISO (for MP4 files) language codes + ValueConv => '($val < 0x400 or $val == 0x7fff) ? $val : pack "C*", map { (($val>>$_)&0x1f)+0x60 } 10, 5, 0', + PrintConv => q{ + return $val unless $val =~ /^\d+$/; + require Image::ExifTool::Font; + return $Image::ExifTool::Font::ttLang{Macintosh}{$val} || "Unknown ($val)"; + }, + }, +); + +# MP4 media information box (ref 5) +%Image::ExifTool::QuickTime::MediaInfo = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 1 => 'Track#', 2 => 'Video' }, + NOTES => 'MP4 media info box.', + vmhd => { + Name => 'VideoHeader', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::VideoHeader' }, + }, + smhd => { + Name => 'AudioHeader', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::AudioHeader' }, + }, + hmhd => { + Name => 'HintHeader', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::HintHeader' }, + }, + nmhd => { + Name => 'NullMediaHeader', + Flags => ['Binary','Unknown'], + }, + dinf => { + Name => 'DataInfo', # (don't change this name -- used to recognize directory when writing) + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::DataInfo' }, + }, + gmhd => { + Name => 'GenMediaHeader', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::GenMediaHeader' }, + }, + hdlr => { #PH + Name => 'Handler', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Handler' }, + }, + stbl => { + Name => 'SampleTable', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::SampleTable' }, + }, +); + +# MP4 video media header (ref 5) +%Image::ExifTool::QuickTime::VideoHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + NOTES => 'MP4 video media header.', + FORMAT => 'int16u', + 2 => { + Name => 'GraphicsMode', + PrintHex => 1, + SeparateTable => 'GraphicsMode', + PrintConv => \%graphicsMode, + }, + 3 => { Name => 'OpColor', Format => 'int16u[3]' }, +); + +# MP4 audio media header (ref 5) +%Image::ExifTool::QuickTime::AudioHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + NOTES => 'MP4 audio media header.', + FORMAT => 'int16u', + 2 => { Name => 'Balance', Format => 'fixed16s' }, +); + +# MP4 hint media header (ref 5) +%Image::ExifTool::QuickTime::HintHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + NOTES => 'MP4 hint media header.', + FORMAT => 'int16u', + 2 => 'MaxPDUSize', + 3 => 'AvgPDUSize', + 4 => { Name => 'MaxBitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)' }, + 6 => { Name => 'AvgBitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)' }, +); + +# MP4 sample table box (ref 5) +%Image::ExifTool::QuickTime::SampleTable = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, + GROUPS => { 2 => 'Video' }, + NOTES => 'MP4 sample table box.', + stsd => [ + { + Name => 'AudioSampleDesc', + Condition => '$$self{HandlerType} and $$self{HandlerType} eq "soun"', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::AudioSampleDesc', + ProcessProc => \&ProcessSampleDesc, + }, + },{ + Name => 'VideoSampleDesc', + Condition => '$$self{HandlerType} and $$self{HandlerType} eq "vide"', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::ImageDesc', + ProcessProc => \&ProcessSampleDesc, + }, + },{ + Name => 'HintSampleDesc', + Condition => '$$self{HandlerType} and $$self{HandlerType} eq "hint"', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::HintSampleDesc', + ProcessProc => \&ProcessSampleDesc, + }, + },{ + Name => 'MetaSampleDesc', + Condition => '$$self{HandlerType} and $$self{HandlerType} eq "meta"', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::MetaSampleDesc', + ProcessProc => \&ProcessSampleDesc, + }, + },{ + Name => 'OtherSampleDesc', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::OtherSampleDesc', + ProcessProc => \&ProcessSampleDesc, + }, + }, + # (Note: "alis" HandlerType handled by the parent audio or video handler) + ], + stts => [ # decoding time-to-sample table + { + Name => 'VideoFrameRate', + Notes => 'average rate calculated from time-to-sample table for video media', + Condition => '$$self{HandlerType} and $$self{HandlerType} eq "vide"', + Format => 'undef', # (necessary to prevent decoding as string!) + # (must be RawConv so appropriate MediaTS is used in calculation) + RawConv => 'Image::ExifTool::QuickTime::CalcSampleRate($self, \$val)', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + { + Name => 'TimeToSampleTable', + Flags => ['Binary','Unknown'], + }, + ], + ctts => { + Name => 'CompositionTimeToSample', + Flags => ['Binary','Unknown'], + }, + stsc => { + Name => 'SampleToChunk', + Flags => ['Binary','Unknown'], + }, + stsz => { + Name => 'SampleSizes', + Flags => ['Binary','Unknown'], + }, + stz2 => { + Name => 'CompactSampleSizes', + Flags => ['Binary','Unknown'], + }, + stco => { + Name => 'ChunkOffset', + Flags => ['Binary','Unknown'], + }, + co64 => { + Name => 'ChunkOffset64', + Flags => ['Binary','Unknown'], + }, + stss => { + Name => 'SyncSampleTable', + Flags => ['Binary','Unknown'], + }, + stsh => { + Name => 'ShadowSyncSampleTable', + Flags => ['Binary','Unknown'], + }, + padb => { + Name => 'SamplePaddingBits', + Flags => ['Binary','Unknown'], + }, + stdp => { + Name => 'SampleDegradationPriority', + Flags => ['Binary','Unknown'], + }, + sdtp => { + Name => 'IdependentAndDisposableSamples', + Flags => ['Binary','Unknown'], + }, + sbgp => { + Name => 'SampleToGroup', + Flags => ['Binary','Unknown'], + }, + sgpd => { + Name => 'SampleGroupDescription', + Flags => ['Binary','Unknown'], + # bytes 4-7 give grouping type (ref ISO/IEC 14496-15:2014) + # tsas - temporal sublayer sample + # stsa - step-wise temporal layer access + # avss - AVC sample + # tscl - temporal layer scalability + # sync - sync sample + }, + subs => { + Name => 'Sub-sampleInformation', + Flags => ['Binary','Unknown'], + }, + cslg => { + Name => 'CompositionToDecodeTimelineMapping', + Flags => ['Binary','Unknown'], + }, + stps => { + Name => 'PartialSyncSamples', + ValueConv => 'join " ",unpack("x8N*",$val)', + }, + # mark - 8 bytes all zero (GoPro) +); + +# MP4 audio sample description box (ref 5/AtomicParsley 0.9.4 parsley.cpp) +%Image::ExifTool::QuickTime::AudioSampleDesc = ( + PROCESS_PROC => \&ProcessHybrid, + VARS => { ID_LABEL => 'ID/Index' }, + GROUPS => { 2 => 'Audio' }, + NOTES => q{ + MP4 audio sample description. This hybrid atom contains both data and child + atoms. + }, + 4 => { + Name => 'AudioFormat', + Format => 'undef[4]', + RawConv => q{ + $$self{AudioFormat} = $val; + return undef unless $val =~ /^[\w ]{4}$/i; + # check for protected audio format + $self->OverrideFileType('M4P') if $val eq 'drms' and $$self{FileType} eq 'M4A'; + return $val; + }, + # see this link for print conversions (not complete): + # https://github.com/yannickcr/brooser/blob/master/php/librairies/getid3/module.audio-video.quicktime.php + }, + 20 => { #PH + Name => 'AudioVendorID', + Condition => '$$self{AudioFormat} ne "mp4s"', + Format => 'undef[4]', + RawConv => '$val eq "\0\0\0\0" ? undef : $val', + PrintConv => \%vendorID, + SeparateTable => 'VendorID', + }, + 24 => { Name => 'AudioChannels', Format => 'int16u' }, + 26 => { Name => 'AudioBitsPerSample', Format => 'int16u' }, + 32 => { Name => 'AudioSampleRate', Format => 'fixed32u' }, +# +# Observed offsets for child atoms of various AudioFormat types: +# +# AudioFormat Offset Child atoms +# ----------- ------ ---------------- +# mp4a 52 * wave, chan, esds, SA3D(Insta360 spherical video params?,also GoPro Max and Garmin VIRB 360) +# in24 52 wave, chan +# "ms\0\x11" 52 wave +# sowt 52 chan +# mp4a 36 * esds, pinf +# drms 36 esds, sinf +# samr 36 damr +# alac 36 alac +# ac-3 36 dac3 +# +# (* child atoms found at different offsets in mp4a) +# + pinf => { + Name => 'PurchaseInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ProtectionInfo' }, + }, + sinf => { # "protection scheme information" + Name => 'ProtectionInfo', #3 + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ProtectionInfo' }, + }, + # f - 16/36 bytes + # esds - 31/40/42/43 bytes - ES descriptor (ref 3) + damr => { #3 + Name => 'DecodeConfig', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::DecodeConfig' }, + }, + wave => { + Name => 'Wave', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Wave' }, + }, + chan => { + Name => 'AudioChannelLayout', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ChannelLayout' }, + }, + SA3D => { # written by Garmin VIRB360 + Name => 'SpatialAudio', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::SpatialAudio' }, + }, + # alac - 28 bytes + # adrm - AAX DRM atom? 148 bytes + # aabd - AAX unknown 17kB (contains 'aavd' strings) +); + +# AMR decode config box (ref 3) +%Image::ExifTool::QuickTime::DecodeConfig = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + 0 => { + Name => 'EncoderVendor', + Format => 'undef[4]', + }, + 4 => 'EncoderVersion', + # 5 - int16u - packet modes + # 7 - int8u - number of packet mode changes + # 8 - int8u - bytes per packet +); + +%Image::ExifTool::QuickTime::ProtectionInfo = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Audio' }, + NOTES => 'Child atoms found in "sinf" and/or "pinf" atoms.', + frma => 'OriginalFormat', + # imif - IPMP information + schm => { + Name => 'SchemeType', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::SchemeType' }, + }, + schi => { + Name => 'SchemeInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::SchemeInfo' }, + }, + enda => { + Name => 'Endianness', + Format => 'int16u', + PrintConv => { + 0 => 'Big-endian (Motorola, MM)', + 1 => 'Little-endian (Intel, II)', + }, + }, + # skcr +); + +%Image::ExifTool::QuickTime::Wave = ( + PROCESS_PROC => \&ProcessMOV, + frma => 'PurchaseFileFormat', + enda => { + Name => 'Endianness', + Format => 'int16u', + PrintConv => { + 0 => 'Big-endian (Motorola, MM)', + 1 => 'Little-endian (Intel, II)', + }, + }, + # "ms\0\x11" - 20 bytes +); + +# audio channel layout (ref CoreAudioTypes.h) +%Image::ExifTool::QuickTime::ChannelLayout = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + DATAMEMBER => [ 0, 8 ], + NOTES => 'Audio channel layout.', + # 0 - version and flags + 4 => { + Name => 'LayoutFlags', + Format => 'int16u', + RawConv => '$$self{LayoutFlags} = $val', + PrintConvColumns => 2, + PrintConv => { + 0 => 'UseDescriptions', + 1 => 'UseBitmap', + 100 => 'Mono', + 101 => 'Stereo', + 102 => 'StereoHeadphones', + 100 => 'Mono', + 101 => 'Stereo', + 102 => 'StereoHeadphones', + 103 => 'MatrixStereo', + 104 => 'MidSide', + 105 => 'XY', + 106 => 'Binaural', + 107 => 'Ambisonic_B_Format', + 108 => 'Quadraphonic', + 109 => 'Pentagonal', + 110 => 'Hexagonal', + 111 => 'Octagonal', + 112 => 'Cube', + 113 => 'MPEG_3_0_A', + 114 => 'MPEG_3_0_B', + 115 => 'MPEG_4_0_A', + 116 => 'MPEG_4_0_B', + 117 => 'MPEG_5_0_A', + 118 => 'MPEG_5_0_B', + 119 => 'MPEG_5_0_C', + 120 => 'MPEG_5_0_D', + 121 => 'MPEG_5_1_A', + 122 => 'MPEG_5_1_B', + 123 => 'MPEG_5_1_C', + 124 => 'MPEG_5_1_D', + 125 => 'MPEG_6_1_A', + 126 => 'MPEG_7_1_A', + 127 => 'MPEG_7_1_B', + 128 => 'MPEG_7_1_C', + 129 => 'Emagic_Default_7_1', + 130 => 'SMPTE_DTV', + 131 => 'ITU_2_1', + 132 => 'ITU_2_2', + 133 => 'DVD_4', + 134 => 'DVD_5', + 135 => 'DVD_6', + 136 => 'DVD_10', + 137 => 'DVD_11', + 138 => 'DVD_18', + 139 => 'AudioUnit_6_0', + 140 => 'AudioUnit_7_0', + 141 => 'AAC_6_0', + 142 => 'AAC_6_1', + 143 => 'AAC_7_0', + 144 => 'AAC_Octagonal', + 145 => 'TMH_10_2_std', + 146 => 'TMH_10_2_full', + 147 => 'DiscreteInOrder', + 148 => 'AudioUnit_7_0_Front', + 149 => 'AC3_1_0_1', + 150 => 'AC3_3_0', + 151 => 'AC3_3_1', + 152 => 'AC3_3_0_1', + 153 => 'AC3_2_1_1', + 154 => 'AC3_3_1_1', + 155 => 'EAC_6_0_A', + 156 => 'EAC_7_0_A', + 157 => 'EAC3_6_1_A', + 158 => 'EAC3_6_1_B', + 159 => 'EAC3_6_1_C', + 160 => 'EAC3_7_1_A', + 161 => 'EAC3_7_1_B', + 162 => 'EAC3_7_1_C', + 163 => 'EAC3_7_1_D', + 164 => 'EAC3_7_1_E', + 165 => 'EAC3_7_1_F', + 166 => 'EAC3_7_1_G', + 167 => 'EAC3_7_1_H', + 168 => 'DTS_3_1', + 169 => 'DTS_4_1', + 170 => 'DTS_6_0_A', + 171 => 'DTS_6_0_B', + 172 => 'DTS_6_0_C', + 173 => 'DTS_6_1_A', + 174 => 'DTS_6_1_B', + 175 => 'DTS_6_1_C', + 176 => 'DTS_7_0', + 177 => 'DTS_7_1', + 178 => 'DTS_8_0_A', + 179 => 'DTS_8_0_B', + 180 => 'DTS_8_1_A', + 181 => 'DTS_8_1_B', + 182 => 'DTS_6_1_D', + 183 => 'AAC_7_1_B', + 0xffff => 'Unknown', + }, + }, + 6 => { + Name => 'AudioChannels', + Condition => '$$self{LayoutFlags} != 0 and $$self{LayoutFlags} != 1', + Format => 'int16u', + }, + 8 => { + Name => 'AudioChannelTypes', + Condition => '$$self{LayoutFlags} == 1', + Format => 'int32u', + PrintConv => { BITMASK => { + 0 => 'Left', + 1 => 'Right', + 2 => 'Center', + 3 => 'LFEScreen', + 4 => 'LeftSurround', + 5 => 'RightSurround', + 6 => 'LeftCenter', + 7 => 'RightCenter', + 8 => 'CenterSurround', + 9 => 'LeftSurroundDirect', + 10 => 'RightSurroundDirect', + 11 => 'TopCenterSurround', + 12 => 'VerticalHeightLeft', + 13 => 'VerticalHeightCenter', + 14 => 'VerticalHeightRight', + 15 => 'TopBackLeft', + 16 => 'TopBackCenter', + 17 => 'TopBackRight', + }}, + }, + 12 => { + Name => 'NumChannelDescriptions', + Condition => '$$self{LayoutFlags} == 1', + Format => 'int32u', + RawConv => '$$self{NumChannelDescriptions} = $val', + }, + 16 => { + Name => 'Channel1Label', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 0', + Format => 'int32u', + SeparateTable => 'ChannelLabel', + PrintConv => \%channelLabel, + }, + 20 => { + Name => 'Channel1Flags', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 0', + Format => 'int32u', + PrintConv => { BITMASK => { 0 => 'Rectangular', 1 => 'Spherical', 2 => 'Meters' }}, + }, + 24 => { + Name => 'Channel1Coordinates', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 0', + Notes => q{ + 3 numbers: for rectangular coordinates left/right, back/front, down/up; for + spherical coordinates left/right degrees, down/up degrees, distance + }, + Format => 'float[3]', + }, + 36 => { + Name => 'Channel2Label', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 1', + Format => 'int32u', + SeparateTable => 'ChannelLabel', + PrintConv => \%channelLabel, + }, + 40 => { + Name => 'Channel2Flags', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 1', + Format => 'int32u', + PrintConv => { BITMASK => { 0 => 'Rectangular', 1 => 'Spherical', 2 => 'Meters' }}, + }, + 44 => { + Name => 'Channel2Coordinates', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 1', + Format => 'float[3]', + }, + 56 => { + Name => 'Channel3Label', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 2', + Format => 'int32u', + SeparateTable => 'ChannelLabel', + PrintConv => \%channelLabel, + }, + 60 => { + Name => 'Channel3Flags', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 2', + Format => 'int32u', + PrintConv => { BITMASK => { 0 => 'Rectangular', 1 => 'Spherical', 2 => 'Meters' }}, + }, + 64 => { + Name => 'Channel3Coordinates', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 2', + Format => 'float[3]', + }, + 76 => { + Name => 'Channel4Label', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 3', + Format => 'int32u', + SeparateTable => 'ChannelLabel', + PrintConv => \%channelLabel, + }, + 80 => { + Name => 'Channel4Flags', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 3', + Format => 'int32u', + PrintConv => { BITMASK => { 0 => 'Rectangular', 1 => 'Spherical', 2 => 'Meters' }}, + }, + 84 => { + Name => 'Channel4Coordinates', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 3', + Format => 'float[3]', + }, + 96 => { + Name => 'Channel5Label', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 4', + Format => 'int32u', + SeparateTable => 'ChannelLabel', + PrintConv => \%channelLabel, + }, + 100 => { + Name => 'Channel5Flags', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 4', + Format => 'int32u', + PrintConv => { BITMASK => { 0 => 'Rectangular', 1 => 'Spherical', 2 => 'Meters' }}, + }, + 104 => { + Name => 'Channel5Coordinates', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 4', + Format => 'float[3]', + }, + 116 => { + Name => 'Channel6Label', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 5', + Format => 'int32u', + SeparateTable => 'ChannelLabel', + PrintConv => \%channelLabel, + }, + 120 => { + Name => 'Channel6Flags', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 5', + Format => 'int32u', + PrintConv => { BITMASK => { 0 => 'Rectangular', 1 => 'Spherical', 2 => 'Meters' }}, + }, + 124 => { + Name => 'Channel6Coordinates', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 5', + Format => 'float[3]', + }, + 136 => { + Name => 'Channel7Label', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 6', + Format => 'int32u', + SeparateTable => 'ChannelLabel', + PrintConv => \%channelLabel, + }, + 140 => { + Name => 'Channel7Flags', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 6', + Format => 'int32u', + PrintConv => { BITMASK => { 0 => 'Rectangular', 1 => 'Spherical', 2 => 'Meters' }}, + }, + 144 => { + Name => 'Channel7Coordinates', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 6', + Format => 'float[3]', + }, + 156 => { + Name => 'Channel8Label', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 7', + Format => 'int32u', + SeparateTable => 'ChannelLabel', + PrintConv => \%channelLabel, + }, + 160 => { + Name => 'Channel8Flags', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 7', + Format => 'int32u', + PrintConv => { BITMASK => { 0 => 'Rectangular', 1 => 'Spherical', 2 => 'Meters' }}, + }, + 164 => { + Name => 'Channel8Coordinates', + Condition => '$$self{LayoutFlags} == 1 and $$self{NumChannelDescriptions} > 7', + Format => 'float[3]', + }, + # (arbitrarily decode only first 8 channels) +); + +# spatial audio (ref https://github.com/google/spatial-media/blob/master/docs/spatial-audio-rfc.md) +%Image::ExifTool::QuickTime::SpatialAudio = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + NOTES => 'Spatial Audio tags.', + 0 => 'SpatialAudioVersion', + 1 => { Name => 'AmbisonicType', PrintConv => { 0 => 'Periphonic' } }, + 2 => { Name => 'AmbisonicOrder', Format => 'int32u' }, + 6 => { Name => 'AmbisonicChannelOrdering', PrintConv => { 0 => 'ACN' } }, + 7 => { Name => 'AmbisonicNormalization', PrintConv => { 0 => 'SN3D' } }, + 8 => { Name => 'AmbisonicChannels', Format => 'int32u' }, + 12 => { Name => 'AmbisonicChannelMap', Format => 'int32u[$val{8}]' }, +); + +# scheme type atom +# ref http://xhelmboyx.tripod.com/formats/mp4-layout.txt +%Image::ExifTool::QuickTime::SchemeType = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + # 0 - 4 bytes version + 4 => { Name => 'SchemeType', Format => 'undef[4]' }, + 8 => { Name => 'SchemeVersion', Format => 'int16u' }, + 10 => { Name => 'SchemeURL', Format => 'string[$size-10]' }, +); + +%Image::ExifTool::QuickTime::SchemeInfo = ( + PROCESS_PROC => \&ProcessMOV, + GROUPS => { 2 => 'Audio' }, + user => { + Name => 'UserID', + Groups => { 2 => 'Author' }, + ValueConv => '"0x" . unpack("H*",$val)', + }, + cert => { # ref http://www.onvif.org/specs/stream/ONVIF-ExportFileFormat-Spec-v100.pdf + Name => 'Certificate', + ValueConv => '"0x" . unpack("H*",$val)', + }, + 'key ' => { + Name => 'KeyID', + ValueConv => '"0x" . unpack("H*",$val)', + }, + iviv => { + Name => 'InitializationVector', + ValueConv => 'unpack("H*",$val)', + }, + righ => { + Name => 'Rights', + Groups => { 2 => 'Author' }, + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Rights' }, + }, + name => { Name => 'UserName', Groups => { 2 => 'Author' } }, + # chtb + # priv - private data + # sign + # adkm - Adobe DRM key management system (ref http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf) + # iKMS + # iSFM + # iSLT +); + +%Image::ExifTool::QuickTime::Rights = ( + PROCESS_PROC => \&ProcessRights, + GROUPS => { 2 => 'Audio' }, + veID => 'ItemVendorID', #PH ("VendorID" ref 19) + plat => 'Platform', #18? + aver => 'VersionRestrictions', #19 ("appversion?" ref 18) + tran => 'TransactionID', #18 + song => 'ItemID', #19 ("appid" ref 18) + tool => { + Name => 'ItemTool', #PH (guess) ("itunes build?" ref 18) + Format => 'string', + }, + medi => 'MediaFlags', #PH (?) + mode => 'ModeFlags', #PH (?) 0x04 is HD flag (https://compilr.com/heksesang/requiem-mac/UnDrm.java) +); + +# MP4 hint sample description box (ref 5) +# (ref https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-SW1) +%Image::ExifTool::QuickTime::HintSampleDesc = ( + PROCESS_PROC => \&ProcessHybrid, + VARS => { ID_LABEL => 'ID/Index' }, + NOTES => 'MP4 hint sample description.', + 4 => { Name => 'HintFormat', Format => 'undef[4]' }, + # 14 - int16u DataReferenceIndex + 16 => { Name => 'HintTrackVersion', Format => 'int16u' }, + # 18 - int16u LastCompatibleHintTrackVersion + 20 => { Name => 'MaxPacketSize', Format => 'int32u' }, +# +# Observed offsets for child atoms of various HintFormat types: +# +# HintFormat Offset Child atoms +# ----------- ------ ---------------- +# "rtp " 24 tims +# + tims => { Name => 'RTPTimeScale', Format => 'int32u' }, + tsro => { Name => 'TimestampRandomOffset', Format => 'int32u' }, + snro => { Name => 'SequenceNumberRandomOffset', Format => 'int32u' }, +); + +# MP4 metadata sample description box +%Image::ExifTool::QuickTime::MetaSampleDesc = ( + PROCESS_PROC => \&ProcessHybrid, + NOTES => 'MP4 metadata sample description.', + 4 => { + Name => 'MetaFormat', + Format => 'undef[4]', + RawConv => '$$self{MetaFormat} = $val', + }, + 8 => { # starts at 8 for MetaFormat eq 'camm', and 17 for 'mett' + Name => 'MetaType', + Format => 'undef[$size-8]', + # may start at various locations! + RawConv => '$$self{MetaType} = ($val=~/(application[^\0]+)/ ? $1 : undef)', + }, +# +# Observed offsets for child atoms of various MetaFormat types: +# +# MetaFormat Offset Child atoms +# ----------- ------ ---------------- +# mebx 24 keys,btrt,lidp,lidl +# fdsc - - +# gpmd - - +# rtmd - - +# CTMD - - +# + 'keys' => { #PH (iPhone7+ hevc) + Name => 'Keys', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Keys', + ProcessProc => \&ProcessMetaKeys, + }, + }, + btrt => { + Name => 'BitrateInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Bitrate' }, + }, +); + +# MP4 generic sample description box +%Image::ExifTool::QuickTime::OtherSampleDesc = ( + PROCESS_PROC => \&ProcessHybrid, + 4 => { + Name => 'OtherFormat', + Format => 'undef[4]', + RawConv => '$$self{MetaFormat} = $val', # (yes, use MetaFormat for this too) + }, + 24 => { + Condition => '$$self{MetaFormat} eq "tmcd"', + Name => 'PlaybackFrameRate', # (may differ from recorded FrameRate eg. ../pics/FujiFilmX-H1.mov) + Format => 'rational64u', + }, +# +# Observed offsets for child atoms of various OtherFormat types: +# +# OtherFormat Offset Child atoms +# ----------- ------ ---------------- +# avc1 86 avcC +# mp4a 36 esds +# mp4s 16 esds +# tmcd 34 name +# data - - +# + ftab => { Name => 'FontTable', Format => 'undef', ValueConv => 'substr($val, 5)' }, + name => { Name => 'OtherName', Format => 'undef', ValueConv => 'substr($val, 4)' }, + # mrlh = GM header? + # mrlv = GM data + # mrld = GM data (448-byte records): + # 0 - int32u count + # 4 - int32u ? (related to units) 0=none,1=m/km,2=L,3=kph,4=C,7=deg,8=rpm,9=kPa,10=G,11=V,15=Nm,16=% + # 8 - int32u ? (0,1,3,4,5) + # 12 - string[64] units + # 76 - int32u ? (1,3,7,15) + # 80 - int32u 0 + # 84 - undef[4] ? + # 88 - int16u[6] ? + # 100 - undef[32] ? + # 132 - string[64] measurement name + # 196 - string[64] measurement name +); + +# MP4 data information box (ref 5) +%Image::ExifTool::QuickTime::DataInfo = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, # (necessary to parse dref even though we don't change it) + NOTES => 'MP4 data information box.', + dref => { + Name => 'DataRef', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::DataRef', + Start => 8, + }, + }, +); + +# Generic media header +%Image::ExifTool::QuickTime::GenMediaHeader = ( + PROCESS_PROC => \&ProcessMOV, + gmin => { + Name => 'GenMediaInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::GenMediaInfo' }, + }, + text => { + Name => 'Text', + Flags => ['Binary','Unknown'], + }, + tmcd => { + Name => 'TimeCode', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::TimeCode' }, + }, +); + +# TimeCode header +%Image::ExifTool::QuickTime::TimeCode = ( + PROCESS_PROC => \&ProcessMOV, + tcmi => { + Name => 'TCMediaInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::TCMediaInfo' }, + }, +); + +# TimeCode media info (ref 12) +%Image::ExifTool::QuickTime::TCMediaInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + 4 => { + Name => 'TextFont', + Format => 'int16u', + PrintConv => { 0 => 'System' }, + }, + 6 => { + Name => 'TextFace', + Format => 'int16u', + PrintConv => { + 0 => 'Plain', + BITMASK => { + 0 => 'Bold', + 1 => 'Italic', + 2 => 'Underline', + 3 => 'Outline', + 4 => 'Shadow', + 5 => 'Condense', + 6 => 'Extend', + }, + }, + }, + 8 => { + Name => 'TextSize', + Format => 'int16u', + }, + # 10 - reserved + 12 => { + Name => 'TextColor', + Format => 'int16u[3]', + }, + 18 => { + Name => 'BackgroundColor', + Format => 'int16u[3]', + }, + 24 => { + Name => 'FontName', + Format => 'pstring', + ValueConv => '$self->Decode($val, $self->Options("CharsetQuickTime"))', + }, +); + +# Generic media info (ref http://sourceforge.jp/cvs/view/ntvrec/ntvrec/libqtime/gmin.h?view=co) +%Image::ExifTool::QuickTime::GenMediaInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + 0 => 'GenMediaVersion', + 1 => { Name => 'GenFlags', Format => 'int8u[3]' }, + 4 => { Name => 'GenGraphicsMode', + Format => 'int16u', + PrintHex => 1, + SeparateTable => 'GraphicsMode', + PrintConv => \%graphicsMode, + }, + 6 => { Name => 'GenOpColor', Format => 'int16u[3]' }, + 12 => { Name => 'GenBalance', Format => 'fixed16s' }, +); + +# MP4 data reference box (ref 5) +%Image::ExifTool::QuickTime::DataRef = ( + PROCESS_PROC => \&ProcessMOV, + WRITE_PROC => \&WriteQuickTime, # (necessary to parse dref even though we don't change it) + NOTES => 'MP4 data reference box.', + 'url ' => { + Name => 'URL', + Format => 'undef', # (necessary to prevent decoding as string!) + RawConv => q{ + # ignore if self-contained (flags bit 0 set) + return undef if unpack("N",$val) & 0x01; + $_ = substr($val,4); s/\0.*//s; $_; + }, + }, + "url\0" => { # (written by GoPro) + Name => 'URL', + Format => 'undef', # (necessary to prevent decoding as string!) + RawConv => q{ + # ignore if self-contained (flags bit 0 set) + return undef if unpack("N",$val) & 0x01; + $_ = substr($val,4); s/\0.*//s; $_; + }, + }, + 'urn ' => { + Name => 'URN', + Format => 'undef', # (necessary to prevent decoding as string!) + RawConv => q{ + return undef if unpack("N",$val) & 0x01; + $_ = substr($val,4); s/\0+/; /; s/\0.*//s; $_; + }, + }, +); + +# MP4 handler box (ref 5) +%Image::ExifTool::QuickTime::Handler = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + 4 => { #PH + Name => 'HandlerClass', + Format => 'undef[4]', + RawConv => '$val eq "\0\0\0\0" ? undef : $val', + PrintConv => { + mhlr => 'Media Handler', + dhlr => 'Data Handler', + }, + }, + 8 => { + Name => 'HandlerType', + Format => 'undef[4]', + RawConv => q{ + $$self{HandlerType} = $val unless $val eq 'alis' or $val eq 'url '; + $$self{HasHandler}{$val} = 1; # remember all our handlers + return $val; + }, + PrintConvColumns => 2, + PrintConv => { + alis => 'Alias Data', #PH + crsm => 'Clock Reference', #3 + hint => 'Hint Track', + ipsm => 'IPMP', #3 + m7sm => 'MPEG-7 Stream', #3 + meta => 'NRT Metadata', #PH + mdir => 'Metadata', #3 + mdta => 'Metadata Tags', #PH + mjsm => 'MPEG-J', #3 + ocsm => 'Object Content', #3 + odsm => 'Object Descriptor', #3 + priv => 'Private', #PH + sdsm => 'Scene Description', #3 + soun => 'Audio Track', + text => 'Text', #PH (but what type? subtitle?) + tmcd => 'Time Code', #PH + 'url '=> 'URL', #3 + vide => 'Video Track', + subp => 'Subpicture', #http://www.google.nl/patents/US7778526 + nrtm => 'Non-Real Time Metadata', #PH (Sony ILCE-7S) [how is this different from "meta"?] + pict => 'Picture', # (HEIC images) + camm => 'Camera Metadata', # (Insta360 MP4) + psmd => 'Panasonic Static Metadata', #PH (Leica C-Lux CAM-DC25) + data => 'Data', #PH (GPS and G-sensor data from DataKam) + sbtl => 'Subtitle', #PH (TomTom Bandit Action Cam) + }, + }, + 12 => { #PH + Name => 'HandlerVendorID', + Format => 'undef[4]', + RawConv => '$val eq "\0\0\0\0" ? undef : $val', + PrintConv => \%vendorID, + SeparateTable => 'VendorID', + }, + 24 => { + Name => 'HandlerDescription', + Format => 'string', + # (sometimes this is a Pascal string, and sometimes it is a C string) + RawConv => q{ + $val=substr($val,1,ord($1)) if $val=~/^([\0-\x1f])/ and ord($1)<length($val); + length $val ? $val : undef; + }, + }, +); + +# Flip uuid data (ref PH) +%Image::ExifTool::QuickTime::Flip = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + NOTES => 'Found in MP4 files from Flip Video cameras.', + GROUPS => { 1 => 'MakerNotes', 2 => 'Image' }, + 1 => 'PreviewImageWidth', + 2 => 'PreviewImageHeight', + 13 => 'PreviewImageLength', + 14 => { # (confirmed for FlipVideoMinoHD) + Name => 'SerialNumber', + Groups => { 2 => 'Camera' }, + Format => 'string[16]', + }, + 28 => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Format => 'undef[$val{13}]', + RawConv => '$self->ValidateImage(\$val, $tag)', + }, +); + +# atoms in Pittasoft "free" atom +%Image::ExifTool::QuickTime::Pittasoft = ( + PROCESS_PROC => \&ProcessMOV, + NOTES => 'Tags found in Pittasoft Blackvue dashcam "free" data.', + cprt => 'Copyright', + thum => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + RawConv => q{ + return undef unless length $val > 4; + my $len = unpack('N', $val); + return undef unless length $val >= 4 + $len; + return substr($val, 4, $len); + }, + }, + ptnm => { + Name => 'OriginalFileName', + ValueConv => 'substr($val, 4, -1)', + }, + ptrh => { + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Pittasoft' }, + # contains these atoms: + # ptvi - 27 bytes: '..avc1...' + # ptso - 16 bytes: '..mp4a...' + }, + 'gps ' => { + Name => 'GPSLog', + Binary => 1, # (ASCII NMEA track log with leading timestamps) + Notes => 'parsed to extract GPS separately when ExtractEmbedded is used', + RawConv => q{ + $val =~ s/\0+$//; # remove trailing nulls + if (length $val and $$self{OPTIONS}{ExtractEmbedded}) { + my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + Image::ExifTool::QuickTime::ProcessGPSLog($self, { DataPt => \$val }, $tagTbl); + } + return $val; + }, + }, + '3gf ' => { + Name => 'AccelData', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&Process_3gf, + }, + }, + sttm => { + Name => 'StartTime', + Format => 'int64u', + Groups => { 2 => 'Time' }, + RawConv => '$$self{StartTime} = $val', + # (ms since Jan 1, 1970, in local time zone - PH) + ValueConv => q{ + my $secs = int($val / 1000); + return ConvertUnixTime($secs) . sprintf(".%03d",$val - $secs * 1000); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, +); + +# QuickTime composite tags +%Image::ExifTool::QuickTime::Composite = ( + GROUPS => { 2 => 'Video' }, + Rotation => { + Notes => q{ + writing this tag updates QuickTime MatrixStructure for all tracks with a + non-zero image size + }, + Require => { + 0 => 'QuickTime:MatrixStructure', + 1 => 'QuickTime:HandlerType', + }, + Writable => 1, + Protected => 1, + WriteAlso => { + MatrixStructure => 'Image::ExifTool::QuickTime::GetRotationMatrix($val)', + }, + ValueConv => 'Image::ExifTool::QuickTime::CalcRotation($self)', + ValueConvInv => '$val', + }, + AvgBitrate => { + Priority => 0, # let QuickTime::AvgBitrate take priority + Require => { + 0 => 'QuickTime::MediaDataSize', + 1 => 'QuickTime::Duration', + }, + RawConv => q{ + return undef unless $val[1]; + $val[1] /= $$self{TimeScale} if $$self{TimeScale}; + my $key = 'MediaDataSize'; + my $size = $val[0]; + for (;;) { + $key = $self->NextTagKey($key) or last; + $size += $self->GetValue($key, 'ValueConv'); + } + return int($size * 8 / $val[1] + 0.5); + }, + PrintConv => 'ConvertBitrate($val)', + }, + GPSLatitude => { + Require => 'QuickTime:GPSCoordinates', + Groups => { 2 => 'Location' }, + ValueConv => 'my @c = split " ", $val; $c[0]', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + GPSLongitude => { + Require => 'QuickTime:GPSCoordinates', + Groups => { 2 => 'Location' }, + ValueConv => 'my @c = split " ", $val; $c[1]', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + # split altitude into GPSAltitude/GPSAltitudeRef like EXIF and XMP + GPSAltitude => { + Require => 'QuickTime:GPSCoordinates', + Groups => { 2 => 'Location' }, + Priority => 0, # (because it may not exist) + ValueConv => 'my @c = split " ", $val; defined $c[2] ? abs($c[2]) : undef', + PrintConv => '"$val m"', + }, + GPSAltitudeRef => { + Require => 'QuickTime:GPSCoordinates', + Groups => { 2 => 'Location' }, + Priority => 0, # (because altitude information may not exist) + ValueConv => 'my @c = split " ", $val; defined $c[2] ? ($c[2] < 0 ? 1 : 0) : undef', + PrintConv => { + 0 => 'Above Sea Level', + 1 => 'Below Sea Level', + }, + }, + GPSLatitude2 => { + Name => 'GPSLatitude', + Require => 'QuickTime:LocationInformation', + Groups => { 2 => 'Location' }, + ValueConv => '$val =~ /Lat=([-+.\d]+)/; $1', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + GPSLongitude2 => { + Name => 'GPSLongitude', + Require => 'QuickTime:LocationInformation', + Groups => { 2 => 'Location' }, + ValueConv => '$val =~ /Lon=([-+.\d]+)/; $1', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + GPSAltitude2 => { + Name => 'GPSAltitude', + Require => 'QuickTime:LocationInformation', + Groups => { 2 => 'Location' }, + ValueConv => '$val =~ /Alt=([-+.\d]+)/; abs($1)', + PrintConv => '"$val m"', + }, + GPSAltitudeRef2 => { + Name => 'GPSAltitudeRef', + Require => 'QuickTime:LocationInformation', + Groups => { 2 => 'Location' }, + ValueConv => '$val =~ /Alt=([-+.\d]+)/; $1 < 0 ? 1 : 0', + PrintConv => { + 0 => 'Above Sea Level', + 1 => 'Below Sea Level', + }, + }, + CDDBDiscPlayTime => { + Require => 'CDDB1Info', + Groups => { 2 => 'Audio' }, + ValueConv => '$val =~ /^..([a-z0-9]{4})/i ? hex($1) : undef', + PrintConv => 'ConvertDuration($val)', + }, + CDDBDiscTracks => { + Require => 'CDDB1Info', + Groups => { 2 => 'Audio' }, + ValueConv => '$val =~ /^.{6}([a-z0-9]{2})/i ? hex($1) : undef', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::QuickTime'); + + +#------------------------------------------------------------------------------ +# AutoLoad our routines when necessary +# +sub AUTOLOAD +{ + # (Note: no need to autoload routines in QuickTimeStream that use Stream table) + if ($AUTOLOAD eq 'Image::ExifTool::QuickTime::Process_mebx') { + require 'Image/ExifTool/QuickTimeStream.pl'; + no strict 'refs'; + return &$AUTOLOAD(@_); + } else { + return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); + } +} + +#------------------------------------------------------------------------------ +# Get rotation matrix +# Inputs: 0) angle in degrees +# Returns: 9-element rotation matrix as a string (with 0 x/y offsets) +sub GetRotationMatrix($) +{ + my $ang = 3.14159265358979323846264 * shift() / 180; + my $cos = cos $ang; + my $sin = sin $ang; + # round to zero + $cos = 0 if abs($cos) < 1e-12; + $sin = 0 if abs($sin) < 1e-12; + my $msn = -$sin; + return "$cos $sin 0 $msn $cos 0 0 0 1"; +} + +#------------------------------------------------------------------------------ +# Get rotation angle from a matrix +# Inputs: 0) rotation matrix as a string +# Return: positive rotation angle in degrees rounded to 3 decimal points, +# or undef on error +sub GetRotationAngle($) +{ + my $rotMatrix = shift; + my @a = split ' ', $rotMatrix; + return undef if $a[0]==0 and $a[1]==0; + # calculate the rotation angle (assume uniform rotation) + my $angle = atan2($a[1], $a[0]) * 180 / 3.14159; + $angle += 360 if $angle < 0; + return int($angle * 1000 + 0.5) / 1000; +} + +#------------------------------------------------------------------------------ +# Calculate rotation of video track +# Inputs: 0) ExifTool object ref +# Returns: rotation angle or undef +sub CalcRotation($) +{ + my $et = shift; + my $value = $$et{VALUE}; + my ($i, $track); + # get the video track family 1 group (eg. "Track1"); + for ($i=0; ; ++$i) { + my $idx = $i ? " ($i)" : ''; + my $tag = "HandlerType$idx"; + last unless $$value{$tag}; + next unless $$value{$tag} eq 'vide'; + $track = $et->GetGroup($tag, 1); + last; + } + return undef unless $track; + # get the video track matrix + for ($i=0; ; ++$i) { + my $idx = $i ? " ($i)" : ''; + my $tag = "MatrixStructure$idx"; + last unless $$value{$tag}; + next unless $et->GetGroup($tag, 1) eq $track; + return GetRotationAngle($$value{$tag}); + } + return undef; +} + +#------------------------------------------------------------------------------ +# Get MatrixStructure for a given rotation angle +# Inputs: 0) rotation angle (deg), 1) ExifTool ref +# Returns: matrix structure as a string, or undef if it can't be rotated +# - requires ImageSizeLookahead to determine the video image size, and doesn't +# rotate matrix unless image size is valid +sub GetMatrixStructure($$) +{ + my ($val, $et) = @_; + my @a = split ' ', $val; + # pass straight through if it already has an offset + return $val unless $a[6] == 0 and $a[7] == 0; + my @s = split ' ', $$et{ImageSizeLookahead}; + my ($w, $h) = @s[12,13]; + return undef unless $w and $h; # don't rotate 0-sized track + $_ = Image::ExifTool::QuickTime::FixWrongFormat($_) foreach $w,$h; + # apply necessary offsets for the standard rotations + my $angle = GetRotationAngle($val); + return undef unless defined $angle; + if ($angle == 90) { + @a[6,7] = ($h, 0); + } elsif ($angle == 180) { + @a[6,7] = ($w, $h); + } elsif ($angle == 270) { + @a[6,7] = (0, $w); + } + return "@a"; +} + +#------------------------------------------------------------------------------ +# Determine the average sample rate from a time-to-sample table +# Inputs: 0) ExifTool object ref, 1) time-to-sample table data ref +# Returns: average sample rate (in Hz) +sub CalcSampleRate($$) +{ + my ($et, $valPt) = @_; + my @dat = unpack('N*', $$valPt); + my ($num, $dur) = (0, 0); + my $i; + for ($i=2; $i<@dat-1; $i+=2) { + $num += $dat[$i]; # total number of samples + $dur += $dat[$i] * $dat[$i+1]; # total sample duration + } + return undef unless $num and $dur and $$et{MediaTS}; + return $num * $$et{MediaTS} / $dur; +} + +#------------------------------------------------------------------------------ +# Fix incorrect format for ImageWidth/Height as written by Pentax +sub FixWrongFormat($) +{ + my $val = shift; + return undef unless $val; + return $val & 0xfff00000 ? unpack('n',pack('N',$val)) : $val; +} + +#------------------------------------------------------------------------------ +# Convert ISO 6709 string to standard lag/lon format +# Inputs: 0) ISO 6709 string (lat, lon, and optional alt) +# Returns: position in decimal degrees with altitude if available +# Notes: Wikipedia indicates altitude may be in feet -- how is this specified? +sub ConvertISO6709($) +{ + my $val = shift; + if ($val =~ /^([-+]\d{1,2}(?:\.\d*)?)([-+]\d{1,3}(?:\.\d*)?)([-+]\d+(?:\.\d*)?)?/) { + # +DD.DDD+DDD.DDD+AA.AAA + $val = ($1 + 0) . ' ' . ($2 + 0); + $val .= ' ' . ($3 + 0) if $3; + } elsif ($val =~ /^([-+])(\d{2})(\d{2}(?:\.\d*)?)([-+])(\d{3})(\d{2}(?:\.\d*)?)([-+]\d+(?:\.\d*)?)?/) { + # +DDMM.MMM+DDDMM.MMM+AA.AAA + my $lat = $2 + $3 / 60; + $lat = -$lat if $1 eq '-'; + my $lon = $5 + $6 / 60; + $lon = -$lon if $4 eq '-'; + $val = "$lat $lon"; + $val .= ' ' . ($7 + 0) if $7; + } elsif ($val =~ /^([-+])(\d{2})(\d{2})(\d{2}(?:\.\d*)?)([-+])(\d{3})(\d{2})(\d{2}(?:\.\d*)?)([-+]\d+(?:\.\d*)?)?/) { + # +DDMMSS.SSS+DDDMMSS.SSS+AA.AAA + my $lat = $2 + $3 / 60 + $4 / 3600; + $lat = -$lat if $1 eq '-'; + my $lon = $6 + $7 / 60 + $8 / 3600; + $lon = -$lon if $5 eq '-'; + $val = "$lat $lon"; + $val .= ' ' . ($9 + 0) if $9; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Convert Nero chapter list (ref ffmpeg libavformat/movenc.c) +# Inputs: 0) binary chpl data +# Returns: chapter list +sub ConvertChapterList($) +{ + my $val = shift; + my $size = length $val; + return '<invalid>' if $size < 9; + my $num = Get8u(\$val, 8); + my ($i, @chapters); + my $pos = 9; + for ($i=0; $i<$num; ++$i) { + last if $pos + 9 > $size; + my $dur = Get64u(\$val, $pos) / 10000000; + my $len = Get8u(\$val, $pos + 8); + last if $pos + 9 + $len > $size; + my $title = substr($val, $pos + 9, $len); + $pos += 9 + $len; + push @chapters, "$dur $title"; + } + return \@chapters; # return as a list +} + +#------------------------------------------------------------------------------ +# Print conversion for a Nero chapter list item +# Inputs: 0) ValueConv chapter string +# Returns: formatted chapter string +sub PrintChapter($) +{ + my $val = shift; + $val =~ /^(\S+) (.*)/ or return $val; + my ($dur, $title) = ($1, $2); + my $h = int($dur / 3600); + $dur -= $h * 3600; + my $m = int($dur / 60); + my $s = $dur - $m * 60; + my $ss = sprintf('%06.3f', $s); + if ($ss >= 60) { + $ss = '00.000'; + ++$m >= 60 and $m -= 60, ++$h; + } + return sprintf("[%d:%.2d:%s] %s",$h,$m,$ss,$title); +} + +#------------------------------------------------------------------------------ +# Format GPSCoordinates for printing +# Inputs: 0) string with numerical lat, lon and optional alt, separated by spaces +# 1) ExifTool object reference +# Returns: PrintConv value +sub PrintGPSCoordinates($) +{ + my ($val, $et) = @_; + my @v = split ' ', $val; + my $prt = Image::ExifTool::GPS::ToDMS($et, $v[0], 1, "N") . ', ' . + Image::ExifTool::GPS::ToDMS($et, $v[1], 1, "E"); + if (defined $v[2]) { + $prt .= ', ' . ($v[2] < 0 ? -$v[2] . ' m Below' : $v[2] . ' m Above') . ' Sea Level'; + } + return $prt; +} + +#------------------------------------------------------------------------------ +# Unpack packed ISO 639/T language code +# Inputs: 0) packed language code (or undef/0), 1) true to not treat 'und' and 'eng' as default +# Returns: language code, or undef/0 for default language, or 'err' for format error +sub UnpackLang($;$) +{ + my ($lang, $noDef) = @_; + if ($lang) { + # language code is packed in 5-bit characters + $lang = pack 'C*', map { (($lang>>$_)&0x1f)+0x60 } 10, 5, 0; + # validate language code + if ($lang =~ /^[a-z]+$/) { + # treat 'eng' or 'und' as the default language + undef $lang if ($lang eq 'und' or $lang eq 'eng') and not $noDef; + } else { + $lang = 'err'; # invalid language code + } + } + return $lang; +} + +#------------------------------------------------------------------------------ +# Get language code string given QuickTime language and country codes +# Inputs: 0) numerical language code, 1) numerical country code, 2) no defaults +# Returns: language code string (ie. "fra-FR") or undef for default language +sub GetLangCode($;$$) +{ + my ($lang, $ctry, $noDef) = @_; + # ignore country ('ctry') and language lists ('lang') for now + undef $ctry if $ctry and $ctry <= 255; + undef $lang if $lang and $lang <= 255; + $lang = UnpackLang($lang, $noDef); + # add country code if specified + if ($ctry) { + $ctry = unpack('a2',pack('n',$ctry)); # unpack as ISO 3166-1 + # treat 'ZZ' like a default country (see ref 12) + undef $ctry if $ctry eq 'ZZ'; + if ($ctry and $ctry =~ /^[A-Z]{2}$/) { + $lang or $lang = 'und'; + $lang .= "-$ctry"; + } + } + return $lang; +} + +#------------------------------------------------------------------------------ +# Get langInfo hash and save details about alt-lang tags +# Inputs: 0) ExifTool ref, 1) tagInfo hash ref, 2) locale code +# Returns: new tagInfo hash ref, or undef if invalid +sub GetLangInfoQT($$$) +{ + my ($et, $tagInfo, $langCode) = @_; + my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode); + if ($langInfo) { + $$et{QTLang} or $$et{QTLang} = [ ]; + push @{$$et{QTLang}}, $$langInfo{Name}; + } + return $langInfo; +} + +#------------------------------------------------------------------------------ +# Get variable-length integer from data (used by ParseItemLocation) +# Inputs: 0) data ref, 1) start position, 2) integer size in bytes (0, 4 or 8), +# 3) default value +# Returns: integer value, and updates current position +sub GetVarInt($$$;$) +{ + my ($dataPt, $pos, $n, $default) = @_; + my $len = length $$dataPt; + $_[1] = $pos + $n; # update current position + return undef if $pos + $n > $len; + if ($n == 0) { + return $default || 0; + } elsif ($n == 4) { + return Get32u($dataPt, $pos); + } elsif ($n == 8) { + return Get64u($dataPt, $pos); + } + return undef; +} + +#------------------------------------------------------------------------------ +# Get null-terminated string from binary data (used by ParseItemInfoEntry) +# Inputs: 0) data ref, 1) start position +# Returns: string, and updates current position +sub GetString($$) +{ + my ($dataPt, $pos) = @_; + my $len = length $$dataPt; + my $str = ''; + while ($pos < $len) { + my $ch = substr($$dataPt, $pos, 1); + ++$pos; + last if ord($ch) == 0; + $str .= $ch; + } + $_[1] = $pos; # update current position + return $str; +} + +#------------------------------------------------------------------------------ +# Get a printable version of the tag ID +# Inputs: 0) tag ID, 1) Flag: 0x01 - print as 4- or 8-digit hex value if necessary +# 0x02 - put leading backslash before escaped character +# Returns: Printable tag ID +sub PrintableTagID($;$) +{ + my $tag = $_[0]; + my $n = ($tag =~ s/([\x00-\x1f\x7f-\xff])/'x'.unpack('H*',$1)/eg); + if ($n and $_[1]) { + if ($n > 2 and $_[1] & 0x01) { + $tag = '0x' . unpack('H8', $_[0]); + $tag =~ s/^0x0000/0x/; + } elsif ($_[1] & 0x02) { + ($tag = $_[0]) =~ s/([\x00-\x1f\x7f-\xff])/'\\x'.unpack('H*',$1)/eg; + } + } + return $tag; +} + +#============================================================================== +# The following ParseXxx routines parse various boxes to extract this +# information about embedded items in a $$et{ItemInfo} hash, keyed by item ID: +# +# iloc: +# ConstructionMethod - offset type: 0=file, 1=idat, 2=item +# DataReferenceIndex - 0 for "this file", otherwise index in dref box +# BaseOffset - base for file offsets +# Extents - list of details for data in file: +# 0) index (extent_index) +# 1) offset (extent_offset) +# 2) length (extent_length) +# 3) nlen (length_size) +# 4) lenPt (pointer to length word) +# infe: +# ProtectionIndex - index if item is protected (0 for unprotected) +# Name - item name +# ContentType - mime type of item +# ContentEncoding - item encoding +# URI - URI of a 'uri '-type item +# ipma: +# Association - list of associated properties in the ipco container +# Essential - list of "essential" flags for the associated properties +# cdsc: +# RefersTo - hash lookup of flags based on referred item ID +# other: +# DocNum - exiftool document number for this item +# +#------------------------------------------------------------------------------ +# Parse item location (iloc) box (ref ISO 14496-12:2015 pg.79) +# Inputs: 0) iloc data, 1) ExifTool ref +# Returns: undef, and fills in ExifTool ItemInfo hash +# Notes: see also Handle_iloc() in WriteQuickTime.pl +sub ParseItemLocation($$) +{ + my ($val, $et) = @_; + my ($i, $j, $num, $pos, $id); + my ($extent_index, $extent_offset, $extent_length); + + my $verbose = $$et{IsWriting} ? 0 : $et->Options('Verbose'); + my $items = $$et{ItemInfo} || ($$et{ItemInfo} = { }); + my $len = length $val; + return undef if $len < 8; + my $ver = Get8u(\$val, 0); + my $siz = Get16u(\$val, 4); + my $noff = ($siz >> 12); + my $nlen = ($siz >> 8) & 0x0f; + my $nbas = ($siz >> 4) & 0x0f; + my $nind = $siz & 0x0f; + if ($ver < 2) { + $num = Get16u(\$val, 6); + $pos = 8; + } else { + return undef if $len < 10; + $num = Get32u(\$val, 6); + $pos = 10; + } + for ($i=0; $i<$num; ++$i) { + if ($ver < 2) { + return undef if $pos + 2 > $len; + $id = Get16u(\$val, $pos); + $pos += 2; + } else { + return undef if $pos + 4 > $len; + $id = Get32u(\$val, $pos); + $pos += 4; + } + if ($ver == 1 or $ver == 2) { + return undef if $pos + 2 > $len; + $$items{$id}{ConstructionMethod} = Get16u(\$val, $pos) & 0x0f; + $pos += 2; + } + return undef if $pos + 2 > $len; + $$items{$id}{DataReferenceIndex} = Get16u(\$val, $pos); + $pos += 2; + $$items{$id}{BaseOffset} = GetVarInt(\$val, $pos, $nbas); + return undef if $pos + 2 > $len; + my $ext_num = Get16u(\$val, $pos); + $pos += 2; + my @extents; + for ($j=0; $j<$ext_num; ++$j) { + if ($ver == 1 or $ver == 2) { + $extent_index = GetVarInt(\$val, $pos, $nind, 1); + } + $extent_offset = GetVarInt(\$val, $pos, $noff); + $extent_length = GetVarInt(\$val, $pos, $nlen); + return undef unless defined $extent_length; + $et->VPrint(1, "$$et{INDENT} Item $id: const_meth=", + defined $$items{$id}{ConstructionMethod} ? $$items{$id}{ConstructionMethod} : '', + sprintf(" base=0x%x offset=0x%x len=0x%x\n", $$items{$id}{BaseOffset}, + $extent_offset, $extent_length)) if $verbose; + push @extents, [ $extent_index, $extent_offset, $extent_length, $nlen, $pos-$nlen ]; + } + # save item location information keyed on 1-based item ID: + $$items{$id}{Extents} = \@extents; + } + return undef; +} + +#------------------------------------------------------------------------------ +# Parse content describes entry (cdsc) box +# Inputs: 0) cdsc data, 1) ExifTool ref +# Returns: undef, and fills in ExifTool ItemInfo hash +sub ParseContentDescribes($$) +{ + my ($val, $et) = @_; + my ($id, $count, @to); + if ($$et{ItemRefVersion}) { + return undef if length $val < 10; + ($id, $count, @to) = unpack('NnN*', $val); + } else { + return undef if length $val < 6; + ($id, $count, @to) = unpack('nnn*', $val); + } + if ($count > @to) { + my $str = 'Missing values in ContentDescribes box'; + $$et{IsWriting} ? $et->Error($str) : $et->Warn($str); + } elsif ($count < @to) { + $et->Warn('Ignored extra values in ContentDescribes box', 1); + @to = $count; + } + # add all referenced item ID's to a "RefersTo" lookup + $$et{ItemInfo}{$id}{RefersTo}{$_} = 1 foreach @to; + return undef; +} + +#------------------------------------------------------------------------------ +# Parse item information entry (infe) box (ref ISO 14496-12:2015 pg.82) +# Inputs: 0) infe data, 1) ExifTool ref +# Returns: undef, and fills in ExifTool ItemInfo hash +sub ParseItemInfoEntry($$) +{ + my ($val, $et) = @_; + my $id; + + my $verbose = $$et{IsWriting} ? 0 : $et->Options('Verbose'); + my $items = $$et{ItemInfo} || ($$et{ItemInfo} = { }); + my $len = length $val; + return undef if $len < 4; + my $ver = Get8u(\$val, 0); + my $pos = 4; + return undef if $pos + 4 > $len; + if ($ver == 0 or $ver == 1) { + $id = Get16u(\$val, $pos); + $$items{$id}{ProtectionIndex} = Get16u(\$val, $pos + 2); + $pos += 4; + $$items{$id}{Name} = GetString(\$val, $pos); + $$items{$id}{ContentType} = GetString(\$val, $pos); + $$items{$id}{ContentEncoding} = GetString(\$val, $pos); + } else { + if ($ver == 2) { + $id = Get16u(\$val, $pos); + $pos += 2; + } elsif ($ver == 3) { + $id = Get32u(\$val, $pos); + $pos += 4; + } + return undef if $pos + 6 > $len; + $$items{$id}{ProtectionIndex} = Get16u(\$val, $pos); + my $type = substr($val, $pos + 2, 4); + $$items{$id}{Type} = $type; + $pos += 6; + $$items{$id}{Name} = GetString(\$val, $pos); + if ($type eq 'mime') { + $$items{$id}{ContentType} = GetString(\$val, $pos); + $$items{$id}{ContentEncoding} = GetString(\$val, $pos); + } elsif ($type eq 'uri ') { + $$items{$id}{URI} = GetString(\$val, $pos); + } + } + $et->VPrint(1, "$$et{INDENT} Item $id: Type=", $$items{$id}{Type} || '', + ' Name=', $$items{$id}{Name} || '', + ' ContentType=', $$items{$id}{ContentType} || '', + "\n") if $verbose > 1; + return undef; +} + +#------------------------------------------------------------------------------ +# Parse item property association (ipma) box (ref https://github.com/gpac/gpac/blob/master/src/isomedia/iff.c) +# Inputs: 0) ipma data, 1) ExifTool ref +# Returns: undef, and fills in ExifTool ItemInfo hash +sub ParseItemPropAssoc($$) +{ + my ($val, $et) = @_; + my ($i, $j, $id); + + my $verbose = $$et{IsWriting} ? 0 : $et->Options('Verbose'); + my $items = $$et{ItemInfo} || ($$et{ItemInfo} = { }); + my $len = length $val; + return undef if $len < 8; + my $ver = Get8u(\$val, 0); + my $flg = Get32u(\$val, 0); + my $num = Get32u(\$val, 4); + my $pos = 8; + for ($i=0; $i<$num; ++$i) { + if ($ver == 0) { + return undef if $pos + 3 > $len; + $id = Get16u(\$val, $pos); + $pos += 2; + } else { + return undef if $pos + 5 > $len; + $id = Get32u(\$val, $pos); + $pos += 4; + } + my $n = Get8u(\$val, $pos++); + my (@association, @essential); + if ($flg & 0x01) { + return undef if $pos + $n * 2 > $len; + for ($j=0; $j<$n; ++$j) { + my $tmp = Get16u(\$val, $pos + $j * 2); + push @association, $tmp & 0x7fff; + push @essential, ($tmp & 0x8000) ? 1 : 0; + } + $pos += $n * 2; + } else { + return undef if $pos + $n > $len; + for ($j=0; $j<$n; ++$j) { + my $tmp = Get8u(\$val, $pos + $j); + push @association, $tmp & 0x7f; + push @essential, ($tmp & 0x80) ? 1 : 0; + } + $pos += $n; + } + $$items{$id}{Association} = \@association; + $$items{$id}{Essential} = \@essential; + $et->VPrint(1, "$$et{INDENT} Item $id properties: @association\n") if $verbose > 1; + } + return undef; +} + +#------------------------------------------------------------------------------ +# Process item information now +# Inputs: 0) ExifTool ref +sub HandleItemInfo($) +{ + my $et = shift; + my $raf = $$et{RAF}; + my $items = $$et{ItemInfo}; + my $verbose = $et->Options('Verbose'); + my $buff; + + # extract information from EXIF/XMP metadata items + if ($items and $raf) { + push @{$$et{PATH}}, 'ItemInformation'; + my $curPos = $raf->Tell(); + my $primary = $$et{PrimaryItem}; + my $id; + $et->VerboseDir('Processing items from ItemInformation', scalar(keys %$items)); + foreach $id (sort { $a <=> $b } keys %$items) { + my $item = $$items{$id}; + my $type = $$item{ContentType} || $$item{Type} || next; + if ($verbose) { + # add up total length of this item for the verbose output + my $len = 0; + if ($$item{Extents} and @{$$item{Extents}}) { + $len += $$_[2] foreach @{$$item{Extents}}; + } + $et->VPrint(0, "$$et{INDENT}Item $id) '${type}' ($len bytes)\n"); + } + # get ExifTool name for this item + my $name = { Exif => 'EXIF', 'application/rdf+xml' => 'XMP', jpeg => 'PreviewImage' }->{$type} || ''; + my ($warn, $extent); + $warn = "Can't currently decode encoded $type metadata" if $$item{ContentEncoding}; + $warn = "Can't currently decode protected $type metadata" if $$item{ProtectionIndex}; + $warn = "Can't currently extract $type with construction method $$item{ConstructionMethod}" if $$item{ConstructionMethod}; + $et->WarnOnce($warn) if $warn and $name; + $warn = 'Not this file' if $$item{DataReferenceIndex}; # (can only extract from "this file") + unless (($$item{Extents} and @{$$item{Extents}}) or $warn) { + $warn = "No Extents for $type item"; + $et->WarnOnce($warn) if $name; + } + if ($warn) { + $et->VPrint(0, "$$et{INDENT} [not extracted] ($warn)\n") if $verbose > 2; + next; + } + my $base = $$item{BaseOffset} || 0; + if ($verbose > 2) { + # do verbose hex dump + my $len = 0; + undef $buff; + my $val = ''; + my $maxLen = $verbose > 3 ? 2048 : 96; + foreach $extent (@{$$item{Extents}}) { + my $n = $$extent[2]; + my $more = $maxLen - $len; + if ($more > 0 and $n) { + $more = $n if $more > $n; + $val .= $buff if defined $buff; + $raf->Seek($$extent[1] + $base, 0) or last; + $raf->Read($buff, $more) or last; + } + $len += $n; + } + if (defined $buff) { + $buff = $val . $buff if length $val; + $et->VerboseDump(\$buff, DataPos => $$item{Extents}[0][1] + $base); + my $snip = $len - length $buff; + $et->VPrint(0, "$$et{INDENT} [snip $snip bytes]\n") if $snip; + } + } + # do hash of AVIF "av01" and HEIC image data + if ($isImageData{$type} and $$et{ImageDataHash}) { + my $hash = $$et{ImageDataHash}; + my $tot = 0; + foreach $extent (@{$$item{Extents}}) { + $raf->Seek($$extent[1] + $base, 0) or $et->Warn("Seek error in $type image data"), last; + $tot += $et->ImageDataHash($raf, $$extent[2], "$type image", 1); + } + $et->VPrint(0, "$$et{INDENT}(ImageDataHash: $tot bytes of $type data)\n") if $tot; + } + next unless $name; + # assemble the data for this item + undef $buff; + my $val = ''; + foreach $extent (@{$$item{Extents}}) { + $val .= $buff if defined $buff; + $raf->Seek($$extent[1] + $base, 0) or last; + $raf->Read($buff, $$extent[2]) or last; + } + next unless defined $buff; + $buff = $val . $buff if length $val; + next unless length $buff; # ignore empty directories + my ($start, $subTable, $proc); + my $pos = $$item{Extents}[0][1] + $base; + if ($name eq 'EXIF' and length $buff >= 4) { + if ($buff =~ /^(MM\0\x2a|II\x2a\0)/) { + $et->Warn('Missing Exif header'); + $start = 0; + } elsif ($buff =~ /^Exif\0\0/) { + # (haven't seen this yet, but it is just a matter of time + # until someone screws it up like this) + $et->Warn('Missing Exif header size'); + $start = 6; + } else { + my $n = unpack('N', $buff); + $start = 4 + $n; # skip "Exif\0\0" header if it exists + if ($start > length($buff)) { + $et->Warn('Invalid EXIF header'); + next; + } + if ($$et{HTML_DUMP}) { + $et->HDump($pos, 4, 'Exif header length', "Value: $n"); + $et->HDump($pos+4, $start-4, 'Exif header') if $n; + } + } + $subTable = GetTagTable('Image::ExifTool::Exif::Main'); + $proc = \&Image::ExifTool::ProcessTIFF; + } elsif ($name eq 'PreviewImage') { + # take a quick stab at determining the size of the image + # (based on JPEG previews found in Fuji X-H2S HIF images) + my $type = 'PreviewImage'; + if ($buff =~ /^.{556}\xff\xc0\0\x11.(.{4})/s) { + my ($h, $w) = unpack('n2', $1); + # (not sure if $h is ever the long dimension, but test it just in case) + if ($w == 160 or $h == 160) { + $type = 'ThumbnailImage'; + } elsif ($w == 1920 or $h == 1920) { + $type = 'OtherImage'; # (large preview) + } # (PreviewImage is 640x480) + } + $et->FoundTag($type => $buff); + next; + } else { + $start = 0; + $subTable = GetTagTable('Image::ExifTool::XMP::Main'); + } + my %dirInfo = ( + DataPt => \$buff, + DataLen => length $buff, + DirStart => $start, + DirLen => length($buff) - $start, + DataPos => $pos, + Base => $pos + $start, # (needed for HtmlDump and IsOffset tags in binary data) + ); + # handle processing of metadata for sub-documents + if (defined $primary and $$item{RefersTo} and not $$item{RefersTo}{$primary}) { + # set document number if this doesn't refer to the primary document + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + # associate this document number with the lowest item index + my ($lowest) = sort { $a <=> $b } keys %{$$item{RefersTo}}; + $$items{$lowest}{DocNum} = $$et{DOC_NUM}; + } + $et->ProcessDirectory(\%dirInfo, $subTable, $proc); + delete $$et{DOC_NUM}; + } + $raf->Seek($curPos, 0); # seek back to original position + pop @{$$et{PATH}}; + } + # process the item properties now that we should know their associations and document numbers + if ($$et{ItemPropertyContainer}) { + my ($dirInfo, $subTable, $proc) = @{$$et{ItemPropertyContainer}}; + $$et{IsItemProperty} = 1; # set item property flag + $et->ProcessDirectory($dirInfo, $subTable, $proc); + delete $$et{ItemPropertyContainer}; + delete $$et{IsItemProperty}; + delete $$et{DOC_NUM}; + } + delete $$et{ItemInfo}; +} + +#------------------------------------------------------------------------------ +# Warn if ExtractEmbedded option isn't used +# Inputs: 0) ExifTool ref +sub EEWarn($) +{ + my $et = shift; + $et->WarnOnce('The ExtractEmbedded option may find more tags in the media data',3); +} + +#------------------------------------------------------------------------------ +# Get quicktime format from flags word +# Inputs: 0) quicktime atom flags, 1) data length +# Returns: ExifTool format string +sub QuickTimeFormat($$) +{ + my ($flags, $len) = @_; + my $format; + if ($flags == 0x15 or $flags == 0x16) { + $format = { 1=>'int8', 2=>'int16', 4=>'int32', 8=>'int64' }->{$len}; + $format .= $flags == 0x15 ? 's' : 'u' if $format; + } elsif ($flags == 0x17) { + $format = 'float'; + } elsif ($flags == 0x18) { + $format = 'double'; + } elsif ($flags == 0x00) { + $format = { 1=>'int8u', 2=>'int16u' }->{$len}; + } + return $format; +} + +#------------------------------------------------------------------------------ +# Process MPEG-4 MTDT atom (ref 11) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessMetaData($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = length $$dataPt; + my $verbose = $et->Options('Verbose'); + return 0 unless $dirLen >= 2; + my $count = Get16u($dataPt, 0); + $verbose and $et->VerboseDir('MetaData', $count); + my $i; + my $pos = 2; + for ($i=0; $i<$count; ++$i) { + last if $pos + 10 > $dirLen; + my $size = Get16u($dataPt, $pos); + last if $size < 10 or $size + $pos > $dirLen; + my $tag = Get32u($dataPt, $pos + 2); + my $lang = Get16u($dataPt, $pos + 6); + my $enc = Get16u($dataPt, $pos + 8); + my $val = substr($$dataPt, $pos + 10, $size); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo) { + # convert language code to ASCII (ignore read-only bit) + $lang = UnpackLang($lang); + # handle alternate languages + if ($lang) { + my $langInfo = GetLangInfoQT($et, $tagInfo, $lang); + $tagInfo = $langInfo if $langInfo; + } + $verbose and $et->VerboseInfo($tag, $tagInfo, + Value => $val, + DataPt => $dataPt, + Start => $pos + 10, + Size => $size - 10, + ); + # convert from UTF-16 BE if necessary + $val = $et->Decode($val, 'UCS2') if $enc == 1; + if ($enc == 0 and $$tagInfo{Unknown}) { + # binary data + $et->FoundTag($tagInfo, \$val); + } else { + $et->FoundTag($tagInfo, $val); + } + } + $pos += $size; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process sample description table +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# (ref https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25691) +sub ProcessSampleDesc($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $pos); + return 0 if $pos + 8 > $dirLen; + + my $num = Get32u($dataPt, 4); # get number of sample entries in table + $pos += 8; + my ($i, $err); + for ($i=0; $i<$num; ++$i) { # loop through sample entries + $pos + 8 > $dirLen and $err = 1, last; + my $size = Get32u($dataPt, $pos); + $pos + $size > $dirLen and $err = 1, last; + $$dirInfo{DirStart} = $pos; + $$dirInfo{DirLen} = $size; + ProcessHybrid($et, $dirInfo, $tagTablePtr); + $pos += $size; + } + if ($err and $$et{HandlerType}) { + my $grp = $$et{SET_GROUP1} || $$dirInfo{Parent} || 'unknown'; + $et->Warn("Truncated $$et{HandlerType} sample table for $grp"); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process hybrid binary data + QuickTime container (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessHybrid($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + # brute-force search for child atoms after first 8 bytes of binary data + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || length($$dataPt) - $dirStart; + my $end = $dirStart + $dirLen; + my $pos = $dirStart + 8; # skip length/version + my $try = $pos; + my $childPos; + + while ($pos <= $end - 8) { + my $tag = substr($$dataPt, $try+4, 4); + # look only for well-behaved tag ID's + $tag =~ /[^\w ]/ and $try = ++$pos, next; + my $size = Get32u($dataPt, $try); + if ($size + $try == $end) { + # the atom ends exactly at the end of the parent -- this must be it + $childPos = $pos; + $$dirInfo{DirLen} = $pos; # the binary data ends at the first child atom + last; + } + if ($size < 8 or $size + $try > $end - 8) { + $try = ++$pos; # fail. try next position + } else { + $try += $size; # could be another atom following this + } + } + # process binary data + $$dirInfo{MixedTags} = 1; # ignore non-integer tag ID's + $et->ProcessBinaryData($dirInfo, $tagTablePtr); + # process child atoms if found + if ($childPos) { + $$dirInfo{DirStart} = $childPos; + $$dirInfo{DirLen} = $end - $childPos; + ProcessMOV($et, $dirInfo, $tagTablePtr); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process iTunes 'righ' atom (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessRights($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{Base}; + my $dirLen = length $$dataPt; + my $unknown = $$et{OPTIONS}{Unknown} || $$et{OPTIONS}{Verbose}; + my $pos; + $et->VerboseDir('righ', $dirLen / 8); + for ($pos = 0; $pos + 8 <= $dirLen; $pos += 8) { + my $tag = substr($$dataPt, $pos, 4); + last if $tag eq "\0\0\0\0"; + my $val = substr($$dataPt, $pos + 4, 4); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + unless ($tagInfo) { + next unless $unknown; + my $name = PrintableTagID($tag); + $tagInfo = { + Name => "Unknown_$name", + Description => "Unknown $name", + Unknown => 1, + }, + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + $val = '0x' . unpack('H*', $val) unless $$tagInfo{Format}; + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos + 4, + Size => 4, + ); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process iTunes Encoding Params (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessEncodingParams($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = length $$dataPt; + my $pos; + $et->VerboseDir('Encoding Params', $dirLen / 8); + for ($pos = 0; $pos + 8 <= $dirLen; $pos += 8) { + my ($tag, $val) = unpack("x${pos}a4N", $$dataPt); + $et->HandleTag($tagTablePtr, $tag, $val); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Read Meta Keys and add tags to ItemList table ('mdta' handler) (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessKeys($$$) +{ + local $_; + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = length $$dataPt; + my $out; + if ($et->Options('Verbose')) { + $et->VerboseDir('Keys'); + $out = $et->Options('TextOut'); + } + my $pos = 8; + my $index = 1; + ++$$et{KeysCount}; # increment key count for this directory + my $itemList = GetTagTable('Image::ExifTool::QuickTime::ItemList'); + my $userData = GetTagTable('Image::ExifTool::QuickTime::UserData'); + while ($pos < $dirLen - 4) { + my $len = unpack("x${pos}N", $$dataPt); + last if $len < 8 or $pos + $len > $dirLen; + delete $$tagTablePtr{$index}; + my $ns = substr($$dataPt, $pos + 4, 4); + my $tag = substr($$dataPt, $pos + 8, $len - 8); + $tag =~ s/\0.*//s; # truncate at null + my $full = $tag; + $tag =~ s/^com\.(apple\.quicktime\.)?// if $ns eq 'mdta'; # remove apple quicktime domain + $tag = "Tag_$ns" unless $tag; + my $short = $tag; + my $tagInfo; + for (;;) { + $tagInfo = $et->GetTagInfo($tagTablePtr, $tag) and last; + # also try ItemList and UserData tables + $tagInfo = $et->GetTagInfo($itemList, $tag) and last; + $tagInfo = $et->GetTagInfo($userData, $tag) and last; + # (I have some samples where the tag is a reversed ItemList or UserData tag ID) + if ($tag =~ /^\w{3}\xa9$/) { + $tag = pack('N', unpack('V', $tag)); + $tagInfo = $et->GetTagInfo($itemList, $tag) and last; + $tagInfo = $et->GetTagInfo($userData, $tag); + last; + } + if ($tag eq $full) { + $tag = $short; + last; + } + $tag = $full; + } + my ($newInfo, $msg); + if ($tagInfo) { + # copy tag information into new Keys tag + $newInfo = { + Name => $$tagInfo{Name}, + Format => $$tagInfo{Format}, + ValueConv => $$tagInfo{ValueConv}, + ValueConvInv => $$tagInfo{ValueConvInv}, + PrintConv => $$tagInfo{PrintConv}, + PrintConvInv => $$tagInfo{PrintConvInv}, + Writable => defined $$tagInfo{Writable} ? $$tagInfo{Writable} : 1, + SubDirectory => $$tagInfo{SubDirectory}, + }; + my $groups = $$tagInfo{Groups}; + $$newInfo{Groups} = $groups ? { %$groups } : { }; + $$newInfo{Groups}{$_} or $$newInfo{Groups}{$_} = $$tagTablePtr{GROUPS}{$_} foreach 0..2; + $$newInfo{Groups}{1} = 'Keys'; + } elsif ($tag =~ /^[-\w. ]+$/ or $tag =~ /\w{4}/) { + # create info for tags with reasonable id's + my $name = ucfirst $tag; + $name =~ tr/-0-9a-zA-Z_. //dc; + $name =~ s/[. ]+(.?)/\U$1/g; + $name =~ s/_([a-z])/_\U$1/g; + $name =~ s/([a-z])_([A-Z])/$1$2/g; + $name = "Tag_$name" if length $name < 2; + $newInfo = { Name => $name, Groups => { 1 => 'Keys' } }; + $msg = ' (Unknown)'; + } + # substitute this tag in the ItemList table with the given index + my $id = $$et{KeysCount} . '.' . $index; + if (ref $$itemList{$id} eq 'HASH') { + # delete other languages too if they exist + my $oldInfo = $$itemList{$id}; + if ($$oldInfo{OtherLang}) { + delete $$itemList{$_} foreach @{$$oldInfo{OtherLang}}; + } + delete $$itemList{$id}; + } + if ($newInfo) { + $$newInfo{KeysID} = $tag; # save original ID for use in family 7 group name + AddTagToTable($itemList, $id, $newInfo); + $msg or $msg = ''; + $out and print $out "$$et{INDENT}Added ItemList Tag $id = ($ns) $tag$msg\n"; + } + $pos += $len; + ++$index; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process keys in MetaSampleDesc directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessMetaKeys($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + # save this information to decode timed metadata samples when ExtractEmbedded is used + SaveMetaKeys($et, $dirInfo, $tagTablePtr) if $$et{OPTIONS}{ExtractEmbedded}; + return 1; +} + +#------------------------------------------------------------------------------ +# Process a QuickTime atom +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) optional tag table ref +# Returns: 1 on success +sub ProcessMOV($$;$) +{ + local $_; + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $dataPt = $$dirInfo{DataPt}; + my $verbose = $et->Options('Verbose'); + my $validate = $$et{OPTIONS}{Validate}; + my $dataPos = $$dirInfo{Base} || 0; + my $dirID = $$dirInfo{DirID} || ''; + my $charsetQuickTime = $et->Options('CharsetQuickTime'); + my ($buff, $tag, $size, $track, $isUserData, %triplet, $doDefaultLang, $index); + my ($dirEnd, $unkOpt, %saveOptions, $atomCount); + + my $topLevel = not $$et{InQuickTime}; + $$et{InQuickTime} = 1; + $$et{HandlerType} = $$et{MetaFormat} = '' unless defined $$et{HandlerType}; + + unless (defined $$et{KeysCount}) { + $$et{KeysCount} = 0; # initialize ItemList key directory count + $doDefaultLang = 1; # flag to generate default language tags + } + # more convenient to package data as a RandomAccess file + unless ($raf) { + $raf = new File::RandomAccess($dataPt); + $dirEnd = $dataPos + $$dirInfo{DirLen} + ($$dirInfo{DirStart} || 0) if $$dirInfo{DirLen}; + } + # skip leading bytes if necessary + if ($$dirInfo{DirStart}) { + $raf->Seek($$dirInfo{DirStart}, 1) or return 0; + $dataPos += $$dirInfo{DirStart}; + } + # read size/tag name atom header + $raf->Read($buff,8) == 8 or return 0; + $dataPos += 8; + if ($tagTablePtr) { + $isUserData = ($tagTablePtr eq \%Image::ExifTool::QuickTime::UserData); + } else { + $tagTablePtr = GetTagTable('Image::ExifTool::QuickTime::Main'); + } + ($size, $tag) = unpack('Na4', $buff); + if ($dataPt) { + $verbose and $et->VerboseDir($$dirInfo{DirName}); + } else { + # check on file type if called with a RAF + $$tagTablePtr{$tag} or return 0; + my $fileType; + if ($tag eq 'ftyp' and $size >= 12) { + # read ftyp atom to see what type of file this is + if ($raf->Read($buff, $size-8) == $size-8) { + $raf->Seek(-($size-8), 1); + my $type = substr($buff, 0, 4); + $$et{save_ftyp} = $type; + # see if we know the extension for this file type + if ($ftypLookup{$type} and $ftypLookup{$type} =~ /\(\.(\w+)/) { + $fileType = $1; + # check compatible brands + } elsif ($buff =~ /^.{8}(.{4})+(mp41|mp42|avc1)/s) { + $fileType = 'MP4'; + } elsif ($buff =~ /^.{8}(.{4})+(f4v )/s) { + $fileType = 'F4V'; + } elsif ($buff =~ /^.{8}(.{4})+(qt )/s) { + $fileType = 'MOV'; + } + } + $fileType or $fileType = 'MP4'; # default to MP4 + # set file type from extension if appropriate + my $ext = $$et{FILE_EXT}; + $fileType = $ext if $ext and $useExt{$ext} and $fileType eq $useExt{$ext}; + $et->SetFileType($fileType, $mimeLookup{$fileType} || 'video/mp4'); + # temporarily set ExtractEmbedded option for CRX files + $saveOptions{ExtractEmbedded} = $et->Options(ExtractEmbedded => 1) if $fileType eq 'CRX'; + } else { + $et->SetFileType(); # MOV + } + SetByteOrder('MM'); + # have XMP take priority except for HEIC + $$et{PRIORITY_DIR} = 'XMP' unless $fileType and $fileType eq 'HEIC'; + } + my $fast = $$et{OPTIONS}{FastScan} || 0; + $$raf{NoBuffer} = 1 if $fast; # disable buffering in FastScan mode + + my $ee = $$et{OPTIONS}{ExtractEmbedded}; + my $hash = $$et{ImageDataHash}; + if ($ee or $hash) { + $unkOpt = $$et{OPTIONS}{Unknown}; + require 'Image/ExifTool/QuickTimeStream.pl'; + } + if ($$tagTablePtr{VARS}) { + $index = $$tagTablePtr{VARS}{START_INDEX}; + $atomCount = $$tagTablePtr{VARS}{ATOM_COUNT}; + } + for (;;) { + my ($eeTag, $ignore); + last if defined $atomCount and --$atomCount < 0; + if ($size < 8) { + if ($size == 0) { + if ($dataPt) { + # a zero size isn't legal for contained atoms, but Canon uses it to + # terminate the CNTH atom (eg. CanonEOS100D.mov), so tolerate it here + my $pos = $raf->Tell() - 4; + $raf->Seek(0,2); + my $str = $$dirInfo{DirName} . ' with ' . ($raf->Tell() - $pos) . ' bytes'; + $et->VPrint(0,"$$et{INDENT}\[Terminator found in $str remaining]"); + } else { + my $t = PrintableTagID($tag,2); + $et->VPrint(0,"$$et{INDENT}Tag '${t}' extends to end of file"); + if ($$tagTablePtr{"$tag-size"}) { + my $pos = $raf->Tell(); + unless ($fast) { + $raf->Seek(0, 2); + $et->HandleTag($tagTablePtr, "$tag-size", $raf->Tell() - $pos); + } + $et->HandleTag($tagTablePtr, "$tag-offset", $pos) if $$tagTablePtr{"$tag-offset"}; + } + } + last; + } + $size == 1 or $et->Warn('Invalid atom size'), last; + # read extended atom size + $raf->Read($buff, 8) == 8 or $et->Warn('Truncated atom header'), last; + $dataPos += 8; + my ($hi, $lo) = unpack('NN', $buff); + if ($hi or $lo > 0x7fffffff) { + if ($hi > 0x7fffffff) { + $et->Warn('Invalid atom size'); + last; + } elsif (not $et->Options('LargeFileSupport')) { + $et->Warn('End of processing at large atom (LargeFileSupport not enabled)'); + last; + } + } + $size = $hi * 4294967296 + $lo - 16; + $size < 0 and $et->Warn('Invalid extended size'), last; + } else { + $size -= 8; + } + if ($validate) { + $$et{ValidatePath} or $$et{ValidatePath} = { }; + my $path = join('-', @{$$et{PATH}}, $tag); + $path =~ s/-Track-/-$$et{SET_GROUP1}-/ if $$et{SET_GROUP1}; + if ($$et{ValidatePath}{$path} and not $dupTagOK{$tag} and not $dupDirOK{$dirID}) { + my $i = Get32u(\$tag,0); + my $str = $i < 255 ? "index $i" : "tag '" . PrintableTagID($tag,2) . "'"; + $et->WarnOnce("Duplicate $str at " . join('-', @{$$et{PATH}})); + $$et{ValidatePath} = { } if $path eq 'MOV-moov'; # avoid warnings for all contained dups + } + $$et{ValidatePath}{$path} = 1; + } + if ($isUserData and $$et{SET_GROUP1}) { + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + # add track name to UserData tags inside tracks + $tag = $$et{SET_GROUP1} . $tag; + if (not $$tagTablePtr{$tag} and $tagInfo) { + my %newInfo = %$tagInfo; + foreach ('Name', 'Description') { + next unless $$tagInfo{$_}; + $newInfo{$_} = $$et{SET_GROUP1} . $$tagInfo{$_}; + $newInfo{$_} =~ s/^(Track\d+)Track/$1/; # remove duplicate "Track" in name + } + AddTagToTable($tagTablePtr, $tag, \%newInfo); + } + } + # set flag to store additional information for ExtractEmbedded option + my $handlerType = $$et{HandlerType}; + if ($eeBox{$handlerType} and $eeBox{$handlerType}{$tag}) { + if ($ee or $hash) { + # (there is another 'gps ' box with a track log that doesn't contain offsets) + if ($tag ne 'gps ' or $eeBox{$handlerType}{$tag} eq $dirID) { + $eeTag = 1; + $$et{OPTIONS}{Unknown} = 1; # temporarily enable "Unknown" option + } + } elsif ($handlerType ne 'vide' and not $$et{OPTIONS}{Validate}) { + EEWarn($et); + } + } elsif ($ee and $ee > 1 and $eeBox2{$handlerType} and $eeBox2{$handlerType}{$tag}) { + $eeTag = 1; + $$et{OPTIONS}{Unknown} = 1; + } elsif ($hash and $hashBox{$handlerType} and $hashBox{$handlerType}{$tag}) { + $eeTag = 1; + $$et{OPTIONS}{Unknown} = 1; + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + + $$et{OPTIONS}{Unknown} = $unkOpt if $eeTag; # restore Unknown option + + # allow numerical tag ID's + unless ($tagInfo) { + my $id = $$et{KeysCount} . '.' . unpack('N', $tag); + if ($$tagTablePtr{$id}) { + $tagInfo = $et->GetTagInfo($tagTablePtr, $id); + $tag = $id; + } + } + # generate tagInfo if Unknown option set + if (not defined $tagInfo and ($$et{OPTIONS}{Unknown} or + $verbose or $tag =~ /^\xa9/)) + { + my $name = PrintableTagID($tag,1); + if ($name =~ /^xa9(.*)/) { + $tagInfo = { + Name => "UserData_$1", + Description => "User Data $1", + }; + } else { + $tagInfo = { + Name => "Unknown_$name", + Description => "Unknown $name", + %unknownInfo, + }; + } + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + # save required tag sizes + if ($$tagTablePtr{"$tag-size"}) { + $et->HandleTag($tagTablePtr, "$tag-size", $size); + $et->HandleTag($tagTablePtr, "$tag-offset", $raf->Tell()) if $$tagTablePtr{"$tag-offset"}; + } + # stop processing at mdat/idat if -fast2 is used + last if $fast > 1 and ($tag eq 'mdat' or $tag eq 'idat'); + # load values only if associated with a tag (or verbose) and not too big + if ($size > 0x2000000) { # start to get worried above 32 MB + # check for RIFF trailer (written by Auto-Vox dashcam) + if ($buff =~ /^(gpsa|gps0|gsen|gsea)...\0/s) { # (yet seen only gpsa as first record) + $et->VPrint(0, "Found RIFF trailer"); + if ($et->Options('ExtractEmbedded')) { + $raf->Seek(-8, 1) or last; # seek back to start of trailer + my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + ProcessRIFFTrailer($et, { RAF => $raf }, $tbl); + } else { + EEWarn($et); + } + last; + } + $ignore = 1; + if ($tagInfo and not $$tagInfo{Unknown} and not $eeTag) { + my $t = PrintableTagID($tag,2); + if ($size > 0x8000000) { + $et->Warn("Skipping '${t}' atom > 128 MB", 1); + } else { + $et->Warn("Skipping '${t}' atom > 32 MB", 2) or $ignore = 0; + } + } + } + if (defined $tagInfo and not $ignore) { + # set document number for this item property if necessary + if ($$et{IsItemProperty}) { + my $items = $$et{ItemInfo}; + my ($id, $prop, $docNum, $lowest); + my $primary = $$et{PrimaryItem} || 0; +ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) { + next unless $$items{$id}{Association}; + my $item = $$items{$id}; + foreach $prop (@{$$item{Association}}) { + next unless $prop == $index; + if ($id == $primary or (not $dontInherit{$tag} and + (($$item{RefersTo} and $$item{RefersTo}{$primary}) or + # hack: assume Item 1 is from the main image (eg. hvc1 data) + # to hack the case where the primary item (ie. main image) + # doesn't directly reference this property + (not $$item{RefersTo} and $id == 1)))) + { + # this is associated with the primary item or an item describing + # the primary item, so consider this part of the main document + undef $docNum; + undef $lowest; + last ItemID; + } elsif ($$item{DocNum}) { + # this property is already associated with an item that has + # an ExifTool document number, so use the lowest associated DocNum + $docNum = $$item{DocNum} if not defined $docNum or $docNum > $$item{DocNum}; + } else { + # keep track of the lowest associated item ID + $lowest = $id; + } + } + } + if (not defined $docNum and defined $lowest) { + # this is the first time we've seen metadata from this item, + # so use a new document number + $docNum = ++$$et{DOC_COUNT}; + $$items{$lowest}{DocNum} = $docNum; + } + $$et{DOC_NUM} = $docNum; + } + my $val; + my $missing = $size - $raf->Read($val, $size); + if ($missing) { + my $t = PrintableTagID($tag,2); + $et->Warn("Truncated '${t}' data (missing $missing bytes)"); + last; + } + # use value to get tag info if necessary + $tagInfo or $tagInfo = $et->GetTagInfo($tagTablePtr, $tag, \$val); + my $hasData = ($$dirInfo{HasData} and $val =~ /\0...data\0/s); + if ($verbose and not $hasData) { + my $tval; + if ($tagInfo and $$tagInfo{Format}) { + $tval = ReadValue(\$val, 0, $$tagInfo{Format}, $$tagInfo{Count}, length($val)); + } + $et->VerboseInfo($tag, $tagInfo, + Value => $tval, + DataPt => \$val, + DataPos => $dataPos, + Size => $size, + Format => $tagInfo ? $$tagInfo{Format} : undef, + Index => $index, + ); + # print iref item ID numbers + if ($dirID eq 'iref') { + my ($id, $count, @to, $i); + if ($$et{ItemRefVersion}) { + ($id, $count, @to) = unpack('NnN*', $val) if length $val >= 10; + } else { + ($id, $count, @to) = unpack('nnn*', $val) if length $val >= 6; + } + defined $id or $id = '<err>', $count = 0; + $id .= " (wrong count: $count)" if $count != @to; + # convert sequential numbers to a range + for ($i=1; $i<@to; ) { + $to[$i-1] =~ /(\d+)$/ and $to[$i] == $1 + 1 or ++$i, next; + $to[$i-1] =~ s/(-.*)?$/-$to[$i]/; + splice @to, $i, 1; + } + $et->VPrint(1, "$$et{INDENT} Item $id refers to: ",join(',',@to),"\n"); + } + } + # extract metadata from stream if ExtractEmbedded option is enabled + if ($eeTag) { + ParseTag($et, $tag, \$val); + # forget this tag if we generated it only for ExtractEmbedded + undef $tagInfo if $tagInfo and $$tagInfo{Unknown} and not $unkOpt; + } + + # handle iTunesInfo mean/name/data triplets + if ($tagInfo and $$tagInfo{Triplet}) { + if ($tag eq 'data' and $triplet{mean} and $triplet{name}) { + $tag = $triplet{name}; + # add 'mean' to name unless it is 'com.apple.iTunes' + $tag = $triplet{mean} . '/' . $tag unless $triplet{mean} eq 'com.apple.iTunes'; + $tagInfo = $et->GetTagInfo($tagTablePtr, $tag, \$val); + unless ($tagInfo) { + my $name = $triplet{name}; + my $desc = $name; + $name =~ tr/-_a-zA-Z0-9//dc; + $desc =~ tr/_/ /; + $tagInfo = { + Name => $name, + Description => $desc, + }; + $et->VPrint(0, $$et{INDENT}, "[adding QuickTime:$name]\n"); + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + # ignore 8-byte header + $val = substr($val, 8) if length($val) >= 8; + unless ($$tagInfo{Format} or $$tagInfo{SubDirectory}) { + # extract as binary if it contains any non-ASCII or control characters + if ($val =~ /[^\x20-\x7e]/) { + my $buff = $val; + $val = \$buff; + } + } + $$tagInfo{List} = 1; # (allow any of these tags to have multiple data elements) + $et->VerboseInfo($tag, $tagInfo, Value => $val) if $verbose; + } else { + $triplet{$tag} = substr($val,4) if length($val) > 4; + undef $tagInfo; # don't store this tag + } + } + if ($tagInfo) { + my $subdir = $$tagInfo{SubDirectory}; + if ($subdir) { + my $start = $$subdir{Start} || 0; + my ($base, $dPos) = ($dataPos, 0); + if ($$subdir{Base}) { + $dPos -= eval $$subdir{Base}; + $base -= $dPos; + } + my %dirInfo = ( + DataPt => \$val, + DataLen => $size, + DirStart => $start, + DirLen => $size - $start, + DirName => $$subdir{DirName} || $$tagInfo{Name}, + DirID => $tag, + HasData => $$subdir{HasData}, + Multi => $$subdir{Multi}, + IgnoreProp => $$subdir{IgnoreProp}, # (XML hack) + DataPos => $dPos, + Base => $base, # (needed for IsOffset tags in binary data) + ); + $dirInfo{BlockInfo} = $tagInfo if $$tagInfo{BlockExtract}; + if ($$subdir{ByteOrder} and $$subdir{ByteOrder} =~ /^Little/) { + SetByteOrder('II'); + } + my $oldGroup1 = $$et{SET_GROUP1}; + if ($$tagInfo{SubDirectory} and $$tagInfo{SubDirectory}{TagTable} and + $$tagInfo{SubDirectory}{TagTable} eq 'Image::ExifTool::QuickTime::Track') + { + $track or $track = 0; + $$et{SET_GROUP1} = 'Track' . (++$track); + } + my $subTable = GetTagTable($$subdir{TagTable}); + my $proc = $$subdir{ProcessProc}; + # make ProcessMOV() the default processing procedure for subdirectories + $proc = \&ProcessMOV unless $proc or $$subTable{PROCESS_PROC}; + if ($size > $start) { + # delay processing of ipco box until after all other boxes + if ($tag eq 'ipco' and not $$et{IsItemProperty}) { + $$et{ItemPropertyContainer} = [ \%dirInfo, $subTable, $proc ]; + $et->VPrint(0,"$$et{INDENT}\[Process ipco box later]"); + } else { + $et->ProcessDirectory(\%dirInfo, $subTable, $proc); + } + } + if ($tag eq 'stbl') { + # process sample data when exiting SampleTable box if extracting embedded + ProcessSamples($et) if $ee or $hash; + } elsif ($tag eq 'minf') { + $$et{HandlerType} = ''; # reset handler type at end of media info box + } + $$et{SET_GROUP1} = $oldGroup1; + SetByteOrder('MM'); + } elsif ($hasData) { + # handle atoms containing 'data' tags + # (currently ignore contained atoms: 'itif', 'name', etc.) + my $pos = 0; + for (;;) { + last if $pos + 16 > $size; + my ($len, $type, $flags, $ctry, $lang) = unpack("x${pos}Na4Nnn", $val); + last if $pos + $len > $size or not $len; + my ($value, $langInfo, $oldDir); + my $format = $$tagInfo{Format}; + if ($type eq 'data' and $len >= 16) { + $pos += 16; + $len -= 16; + $value = substr($val, $pos, $len); + # format flags (ref 12): + # 0x0=binary, 0x1=UTF-8, 0x2=UTF-16, 0x3=ShiftJIS, + # 0x4=UTF-8 0x5=UTF-16, 0xd=JPEG, 0xe=PNG, + # 0x15=signed int, 0x16=unsigned int, 0x17=float, + # 0x18=double, 0x1b=BMP, 0x1c='meta' atom + if ($stringEncoding{$flags}) { + # handle all string formats + $value = $et->Decode($value, $stringEncoding{$flags}); + # (shouldn't be null terminated, but some software writes it anyway) + $value =~ s/\0$// unless $$tagInfo{Binary}; + } else { + if (not $format) { + $format = QuickTimeFormat($flags, $len); + } elsif ($format =~ /^int\d+([us])$/) { + # adjust integer to available length (but not int64) + my $fmt = { 1=>'int8', 2=>'int16', 4=>'int32' }->{$len}; + $format = $fmt . $1 if defined $fmt; + } + if ($format) { + $value = ReadValue(\$value, 0, $format, $$tagInfo{Count}, $len); + } elsif (not $$tagInfo{ValueConv}) { + # make binary data a scalar reference unless a ValueConv exists + my $buf = $value; + $value = \$buf; + } + } + } + if ($ctry or $lang) { + my $langCode = GetLangCode($lang, $ctry); + if ($langCode) { + # get tagInfo for other language + $langInfo = GetLangInfoQT($et, $tagInfo, $langCode); + # save other language tag ID's so we can delete later if necessary + if ($langInfo) { + $$tagInfo{OtherLang} or $$tagInfo{OtherLang} = [ ]; + push @{$$tagInfo{OtherLang}}, $$langInfo{TagID}; + } + } + } + $langInfo or $langInfo = $tagInfo; + my $str = $qtFlags{$flags} ? " ($qtFlags{$flags})" : ''; + $et->VerboseInfo($tag, $langInfo, + Value => ref $value ? $$value : $value, + DataPt => \$val, + DataPos => $dataPos, + Start => $pos, + Size => $len, + Format => $format, + Index => $index, + Extra => sprintf(", Type='${type}', Flags=0x%x%s, Lang=0x%.4x",$flags,$str,$lang), + ) if $verbose; + # use "Keys" in path instead of ItemList if this was defined by a Keys tag + my $isKey = $$tagInfo{Groups} && $$tagInfo{Groups}{1} && $$tagInfo{Groups}{1} eq 'Keys'; + if ($isKey) { + $oldDir = $$et{PATH}[-1]; + $$et{PATH}[-1] = 'Keys'; + } + $et->FoundTag($langInfo, $value) if defined $value; + $$et{PATH}[-1] = $oldDir if $isKey; + $pos += $len; + } + } elsif ($tag =~ /^\xa9/ or $$tagInfo{IText}) { + # parse international text to extract all languages + my $pos = 0; + if ($$tagInfo{Format}) { + $et->FoundTag($tagInfo, ReadValue(\$val, 0, $$tagInfo{Format}, undef, length($val))); + $pos = $size; + } + for (;;) { + my ($len, $lang); + if ($$tagInfo{IText} and $$tagInfo{IText} >= 6) { + last if $pos + $$tagInfo{IText} > $size; + $pos += $$tagInfo{IText} - 2; + $lang = unpack("x${pos}n", $val); + $pos += 2; + $len = $size - $pos; + } else { + last if $pos + 4 > $size; + ($len, $lang) = unpack("x${pos}nn", $val); + $pos += 4; + # according to the QuickTime spec (ref 12), $len should include + # 4 bytes for length and type words, but nobody (including + # Apple, Pentax and Kodak) seems to add these in, so try + # to allow for either + if ($pos + $len > $size) { + $len -= 4; + last if $pos + $len > $size or $len < 0; + } + } + # ignore any empty entries (or null padding) after the first + next if not $len and $pos; + my $str = substr($val, $pos, $len); + my ($langInfo, $enc); + if (($lang < 0x400 or $lang == 0x7fff) and $str !~ /^\xfe\xff/) { + # this is a Macintosh language code + # a language code of 0 is Macintosh english, so treat as default + if ($lang) { + if ($lang == 0x7fff) { + # technically, ISO 639-2 doesn't have a 2-character + # equivalent for 'und', but use 'un' anyway + $lang = 'un'; + } else { + # use Font.pm to look up language string + require Image::ExifTool::Font; + $lang = $Image::ExifTool::Font::ttLang{Macintosh}{$lang}; + } + } else { + # for the default language code of 0x0000, use UTF-8 instead + # of the CharsetQuickTime setting if obviously UTF8 + $enc = 'UTF8' if Image::ExifTool::IsUTF8(\$str) > 0; + } + # the spec says only "Macintosh text encoding", but + # allow this to be configured by the user + $enc = $charsetQuickTime unless $enc; + } else { + # convert language code to ASCII (ignore read-only bit) + $lang = UnpackLang($lang); + # may be either UTF-8 or UTF-16BE + $enc = $str=~s/^\xfe\xff// ? 'UTF16' : 'UTF8'; + } + unless ($$tagInfo{NoDecode}) { + $str = $et->Decode($str, $enc); + $str =~ s/\0+$//; # remove any trailing nulls (eg. 3gp tags) + } + if ($$tagInfo{IText} and $$tagInfo{IText} > 6) { + my $n = $$tagInfo{IText} - 6; + # add back extra bytes (eg. 'rtng' box) + $str = substr($val, $pos-$n-2, $n) . $str; + } + $langInfo = GetLangInfoQT($et, $tagInfo, $lang) if $lang; + $et->FoundTag($langInfo || $tagInfo, $str); + $pos += $len; + } + } else { + my $format = $$tagInfo{Format}; + if ($format) { + $val = ReadValue(\$val, 0, $format, $$tagInfo{Count}, length($val)); + } + my $oldBase; + if ($$tagInfo{SetBase}) { + $oldBase = $$et{BASE}; + $$et{BASE} = $dataPos; + } + my $key = $et->FoundTag($tagInfo, $val); + $$et{BASE} = $oldBase if defined $oldBase; + # decode if necessary (NOTE: must be done after RawConv) + if (defined $key and (not $format or $format =~ /^string/) and + not $$tagInfo{Unknown} and not $$tagInfo{ValueConv} and + not $$tagInfo{Binary} and defined $$et{VALUE}{$key} and not ref $val) + { + my $vp = \$$et{VALUE}{$key}; + if (not ref $$vp and length($$vp) <= 65536 and $$vp =~ /[\x80-\xff]/) { + # the encoding of this is not specified, so use CharsetQuickTime + # unless the string is valid UTF-8 + my $enc = Image::ExifTool::IsUTF8($vp) > 0 ? 'UTF8' : $charsetQuickTime; + $$vp = $et->Decode($$vp, $enc); + } + } + } + } + } else { + $et->VerboseInfo($tag, $tagInfo, + Size => $size, + Extra => sprintf(' at offset 0x%.4x', $raf->Tell()), + ) if $verbose; + if ($size and (not $raf->Seek($size-1, 1) or $raf->Read($buff, 1) != 1)) { + my $t = PrintableTagID($tag,2); + $et->Warn("Truncated '${t}' data"); + last; + } + } + $dataPos += $size + 8; # point to start of next atom data + last if $dirEnd and $dataPos >= $dirEnd; # (note: ignores last value if 0 bytes) + $raf->Read($buff, 8) == 8 or last; + ($size, $tag) = unpack('Na4', $buff); + ++$index if defined $index; + } + # tweak file type based on track content ("iso*" and "dash" ftyp only) + if ($topLevel and $$et{FileType} and $$et{FileType} eq 'MP4' and + $$et{save_ftyp} and $$et{HasHandler} and $$et{save_ftyp} =~ /^(iso|dash)/ and + $$et{HasHandler}{soun} and not $$et{HasHandler}{vide}) + { + $et->OverrideFileType('M4A', 'audio/mp4'); + } + # fill in missing defaults for alternate language tags + # (the first language is taken as the default) + if ($doDefaultLang and $$et{QTLang}) { +QTLang: foreach $tag (@{$$et{QTLang}}) { + next unless defined $$et{VALUE}{$tag}; + my $langInfo = $$et{TAG_INFO}{$tag} or next; + my $tagInfo = $$langInfo{SrcTagInfo} or next; + my $infoHash = $$et{TAG_INFO}; + my $name = $$tagInfo{Name}; + # loop through all instances of this tag name and generate the default-language + # version only if we don't already have a QuickTime tag with this name + my ($i, $key); + for ($i=0, $key=$name; $$infoHash{$key}; ++$i, $key="$name ($i)") { + next QTLang if $et->GetGroup($key, 0) eq 'QuickTime'; + } + $et->FoundTag($tagInfo, $$et{VALUE}{$tag}); + } + delete $$et{QTLang}; + } + # process item information now that we are done processing its 'meta' container + HandleItemInfo($et) if $topLevel or $dirID eq 'meta'; + + ScanMediaData($et) if $ee and $topLevel; # brute force scan for metadata embedded in media data + + # restore any changed options + $et->Options($_ => $saveOptions{$_}) foreach keys %saveOptions; + return 1; +} + +#------------------------------------------------------------------------------ +# Process a QuickTime Image File +# Inputs: 0) ExifTool object reference, 1) directory information reference +# Returns: 1 on success +sub ProcessQTIF($$) +{ + my ($et, $dirInfo) = @_; + my $table = GetTagTable('Image::ExifTool::QuickTime::ImageFile'); + return ProcessMOV($et, $dirInfo, $table); +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::QuickTime - Read QuickTime and MP4 meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +information from QuickTime and MP4 video, M4A audio, and HEIC image files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html> + +=item L<http://search.cpan.org/dist/MP4-Info-1.04/> + +=item L<http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt> + +=item L<http://wiki.multimedia.cx/index.php?title=Apple_QuickTime> + +=item L<http://atomicparsley.sourceforge.net/mpeg-4files.html> + +=item L<http://wiki.multimedia.cx/index.php?title=QuickTime_container> + +=item L<http://code.google.com/p/mp4v2/wiki/iTunesMetadata> + +=item L<http://www.canieti.com.mx/assets/files/1011/IEC_100_1384_DC.pdf> + +=item L<http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/QuickTime Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/QuickTimeStream.pl b/ExifTool/lib/Image/ExifTool/QuickTimeStream.pl new file mode 100644 index 0000000..90107ae --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/QuickTimeStream.pl @@ -0,0 +1,3206 @@ +#------------------------------------------------------------------------------ +# File: QuickTimeStream.pl +# +# Description: Extract embedded information from QuickTime media data +# +# Revisions: 2018-01-03 - P. Harvey Created +# +# References: 1) https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-SW130 +# 2) http://sergei.nz/files/nvtk_mp42gpx.py +# 3) https://forum.flitsservice.nl/dashcam-info/dod-ls460w-gps-data-uit-mov-bestand-lezen-t87926.html +# 4) https://developers.google.com/streetview/publish/camm-spec +# 5) https://sergei.nz/extracting-gps-data-from-viofo-a119-and-other-novatek-powered-cameras/ +# 6) Thomas Allen https://github.com/exiftool/exiftool/pull/62 +#------------------------------------------------------------------------------ +package Image::ExifTool::QuickTime; + +use strict; + +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::QuickTime; + +sub Process_tx3g($$$); +sub Process_marl($$$); +sub Process_mebx($$$); +sub ProcessFreeGPS($$$); +sub ProcessFreeGPS2($$$); +sub Process360Fly($$$); +sub ProcessFMAS($$$); +sub ProcessCAMM($$$); + +my $debug; # set to 'tEST' (all caps) for extra debugging messages + +# QuickTime data types that have ExifTool equivalents +# (ref https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35) +my %qtFmt = ( + 0 => 'undef', + 1 => 'string', # (UTF-8) + # 2 - UTF-16 + # 3 - shift-JIS + # 4 - UTF-8 sort + # 5 - UTF-16 sort + # 13 - JPEG image + # 14 - PNG image + # 21 - signed integer (1,2,3 or 4 bytes) + # 22 - unsigned integer (1,2,3 or 4 bytes) + 23 => 'float', + 24 => 'double', + # 27 - BMP image + # 28 - QuickTime atom + 65 => 'int8s', + 66 => 'int16s', + 67 => 'int32s', + 70 => 'float', # float[2] x,y + 71 => 'float', # float[2] width,height + 72 => 'float', # float[4] x,y,width,height + 74 => 'int64s', + 75 => 'int8u', + 76 => 'int16u', + 77 => 'int32u', + 78 => 'int64u', + 79 => 'float', # float[9] transform matrix + 80 => 'float', # float[8] face coordinates +); + +# maximums for validating H,M,S,d,m,Y from "freeGPS " metadata +my @dateMax = ( 24, 59, 59, 2200, 12, 31 ); + +# typical (minimum?) size of freeGPS block +my $gpsBlockSize = 0x8000; + +# conversion factors +my $knotsToKph = 1.852; # knots --> km/h +my $mpsToKph = 3.6; # m/s --> km/h +my $mphToKph = 1.60934; # mph --> km/h + +# handler types to process based on MetaFormat/OtherFormat +my %processByMetaFormat = ( + meta => 1, # ('CTMD' in CR3 images, 'priv' unknown in DJI video) + data => 1, # ('RVMI') + sbtl => 1, # (subtitle; 'tx3g' in Yuneec drone videos) + ctbx => 1, # ('marl' in GM videos) +); + +# data lengths for each INSV/INSP record type +my %insvDataLen = ( + 0x200 => 0, # PreivewImage (any size) (a duplicate of PreviewImage in APP2 of INSP files) + 0x300 => 0, # accelerometer (could be either 20 or 56 bytes) + 0x400 => 16, # exposure (ref 6) + 0x600 => 8, # timestamps (ref 6) + 0x700 => 53, # GPS + # 0x900 => 48, # ? (Insta360 X3) + # 0xa00 => 5?, # ? (Insta360 ONE RS) + # 0xb00 => 10, # ? (Insta360 X3) +); + +# limit the default amount of data we read for some record types +# (to avoid running out of memory) +my %insvLimit = ( + 0x300 => [ 'accelerometer', 20000 ], # maximum of 20000 accelerometer records +); + +# tags extracted from various QuickTime data streams +%Image::ExifTool::QuickTime::Stream = ( + GROUPS => { 2 => 'Location' }, + NOTES => q{ + The tags below are extracted from timed metadata in QuickTime and other + formats of video files when the ExtractEmbedded option is used. Although + most of these tags are combined into the single table below, ExifTool + currently reads 66 different formats of timed GPS metadata from video files. + }, + VARS => { NO_ID => 1 }, + GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' }, + GPSLongitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")' }, + GPSAltitude => { PrintConv => '(sprintf("%.4f", $val) + 0) . " m"' }, # round to 4 decimals + GPSSpeed => { PrintConv => 'sprintf("%.4f", $val) + 0', Notes => 'in km/h unless GPSSpeedRef says otherwise' }, + GPSSpeedRef => { PrintConv => { K => 'km/h', M => 'mph', N => 'knots' } }, + GPSTrack => { PrintConv => 'sprintf("%.4f", $val) + 0', Notes => 'relative to true north unless GPSTrackRef says otherwise' }, + GPSTrackRef => { PrintConv => { M => 'Magnetic North', T => 'True North' } }, + GPSDateTime => { + Groups => { 2 => 'Time' }, + Description => 'GPS Date/Time', + RawConv => '$$self{FoundGPSDateTime} = 1; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + GPSTimeStamp => { PrintConv => 'Image::ExifTool::GPS::PrintTimeStamp($val)', Groups => { 2 => 'Time' } }, + GPSSatellites=> { }, + GPSDOP => { Description => 'GPS Dilution Of Precision' }, + Distance => { PrintConv => '"$val m"' }, + VerticalSpeed=> { PrintConv => '"$val m/s"' }, + FNumber => { PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', Groups => { 2 => 'Camera' } }, + ExposureTime => { PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', Groups => { 2 => 'Camera' } }, + ExposureCompensation => { PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', Groups => { 2 => 'Camera' } }, + ISO => { Groups => { 2 => 'Camera' } }, + CameraDateTime=>{ PrintConv => '$self->ConvertDateTime($val)', Groups => { 2 => 'Time' } }, + VideoTimeStamp => { Groups => { 2 => 'Video' } }, + Accelerometer=> { Notes => '3-axis acceleration in units of g' }, + AccelerometerData => { }, + AngularVelocity => { }, + GSensor => { }, + Car => { }, + RawGSensor => { + # (same as GSensor, but offset by some unknown value) + ValueConv => 'my @a=split " ",$val; $_/=1000 foreach @a; "@a"', + }, + Text => { Groups => { 2 => 'Other' } }, + TimeCode => { Groups => { 2 => 'Video' } }, + FrameNumber => { Groups => { 2 => 'Video' } }, + SampleTime => { Groups => { 2 => 'Video' }, PrintConv => 'ConvertDuration($val)', Notes => 'sample decoding time' }, + SampleDuration=>{ Groups => { 2 => 'Video' }, PrintConv => 'ConvertDuration($val)' }, + UserLabel => { Groups => { 2 => 'Other' } }, + KiloCalories => { Groups => { 2 => 'Other' } }, + SampleDateTime => { + Groups => { 2 => 'Time' }, + ValueConv => q{ + my $str = ConvertUnixTime($val); + my $frac = $val - int($val); + if ($frac != 0) { + $frac = sprintf('%.6f', $frac); + $frac =~ s/^0//; + $frac =~ s/0+$//; + $str .= $frac; + } + return $str; + }, + PrintConv => '$self->ConvertDateTime($val)', + }, +# +# timed metadata decoded based on MetaFormat (format of 'meta' or 'data' sample description) +# [or HandlerType, or specific 'vide' type if specified] +# + mebx => { + Name => 'mebx', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Keys', + ProcessProc => \&Process_mebx, + }, + }, + gpmd => [{ + Name => 'gpmd_Kingslim', # Kingslim D4 dashcam + Condition => '$$valPt =~ /^.{21}\0\0\0A[NS][EW]/s', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&ProcessFreeGPS, + }, + },{ + Name => 'gpmd_Rove', # Rove Stealth 4K encrypted text + Condition => '$$valPt =~ /^\0\0\xf2\xe1\xf0\xeeTT/', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&Process_text, + }, + },{ + Name => 'gpmd_FMAS', # Vantrue N2S binary format + Condition => '$$valPt =~ /^FMAS\0\0\0\0/', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&ProcessFMAS, + }, + },{ + Name => 'gpmd_GoPro', + SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' }, + }], + fdsc => { + Name => 'fdsc', + Condition => '$$valPt =~ /^GPRO/', + # (other types of "fdsc" samples aren't yet parsed: /^GP\x00/ and /^GP\x04/) + SubDirectory => { TagTable => 'Image::ExifTool::GoPro::fdsc' }, + }, + rtmd => { + Name => 'rtmd', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::rtmd' }, + }, + marl => { + Name => 'marl', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::marl' }, + }, + CTMD => { # (Canon Timed MetaData) + Name => 'CTMD', + SubDirectory => { TagTable => 'Image::ExifTool::Canon::CTMD' }, + }, + tx3g => { + Name => 'tx3g', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::tx3g' }, + }, + RVMI => [{ # data "OtherFormat" written by unknown software + Name => 'RVMI_gReV', + Condition => '$$valPt =~ /^gReV/', # GPS data + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::RVMI_gReV', + ByteOrder => 'Little-endian', + }, + },{ + Name => 'RVMI_sReV', + Condition => '$$valPt =~ /^sReV/', # sensor data + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::RVMI_sReV', + ByteOrder => 'Little-endian', + }, + # (there is also "tReV" data that hasn't been decoded yet) + }], + camm => [{ + Name => 'camm0', + # (according to the spec. the first 2 bytes are reserved and should be zero, + # but I have samples where these bytes are non-zero, so allow anything here) + Condition => '$$valPt =~ /^..\0\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::camm0', + ByteOrder => 'Little-Endian', + }, + },{ + Name => 'camm1', + Condition => '$$valPt =~ /^..\x01\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::camm1', + ByteOrder => 'Little-Endian', + }, + },{ # (written by Insta360) - [HandlerType, not MetaFormat] + Name => 'camm2', + Condition => '$$valPt =~ /^..\x02\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::camm2', + ByteOrder => 'Little-Endian', + }, + },{ + Name => 'camm3', + Condition => '$$valPt =~ /^..\x03\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::camm3', + ByteOrder => 'Little-Endian', + }, + },{ + Name => 'camm4', + Condition => '$$valPt =~ /^..\x04\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::camm4', + ByteOrder => 'Little-Endian', + }, + },{ + Name => 'camm5', + Condition => '$$valPt =~ /^..\x05\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::camm5', + ByteOrder => 'Little-Endian', + }, + },{ + Name => 'camm6', + Condition => '$$valPt =~ /^..\x06\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::camm6', + ByteOrder => 'Little-Endian', + }, + },{ + Name => 'camm7', + Condition => '$$valPt =~ /^..\x07\0/s', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::camm7', + ByteOrder => 'Little-Endian', + }, + }], + mett => { # Parrot drones + Name => 'mett', + SubDirectory => { TagTable => 'Image::ExifTool::Parrot::mett' }, + }, + JPEG => { # (in CR3 images) - [vide HandlerType with JPEG in SampleDescription, not MetaFormat] + Name => 'JpgFromRaw', + Groups => { 2 => 'Preview' }, + RawConv => '$self->ValidateImage(\$val,$tag)', + }, + text => { # (TomTom Bandit MP4) - [sbtl HandlerType with 'text' in SampleDescription] + Name => 'PreviewInfo', + Condition => 'length $$valPt > 12 and Get32u($valPt,4) == length($$valPt) and $$valPt =~ /^.{8}\xff\xd8\xff/s', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::PreviewInfo' }, + }, + INSV => { + Groups => { 0 => 'Trailer', 1 => 'Insta360' }, # (so these groups will appear in the -listg options) + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::INSV_MakerNotes' }, + }, + Unknown00 => { Unknown => 1 }, + Unknown01 => { Unknown => 1 }, + Unknown02 => { Unknown => 1 }, + Unknown03 => { Unknown => 1 }, +); + +# tags found in 'camm' type 0 timed metadata (ref 4) +%Image::ExifTool::QuickTime::camm0 = ( + PROCESS_PROC => \&ProcessCAMM, + GROUPS => { 2 => 'Location' }, + FIRST_ENTRY => 0, + NOTES => q{ + The camm0 through camm7 tables define tags extracted from the Google Street + View Camera Motion Metadata of MP4 videos. See + L<https://developers.google.com/streetview/publish/camm-spec> for the + specification. + }, + 4 => { + Name => 'AngleAxis', + Notes => 'angle axis orientation in radians in local coordinate system', + Format => 'float[3]', + }, +); + +# tags found in 'camm' type 1 timed metadata (ref 4) +%Image::ExifTool::QuickTime::camm1 = ( + PROCESS_PROC => \&ProcessCAMM, + GROUPS => { 2 => 'Camera' }, + FIRST_ENTRY => 0, + 4 => { + Name => 'PixelExposureTime', + Format => 'int32s', + ValueConv => '$val * 1e-9', + PrintConv => 'sprintf("%.4g ms", $val * 1000)', + }, + 8 => { + Name => 'RollingShutterSkewTime', + Format => 'int32s', + ValueConv => '$val * 1e-9', + PrintConv => 'sprintf("%.4g ms", $val * 1000)', + }, +); + +# tags found in 'camm' type 2 timed metadata (ref PH, Insta360Pro) +%Image::ExifTool::QuickTime::camm2 = ( + PROCESS_PROC => \&ProcessCAMM, + GROUPS => { 2 => 'Location' }, + FIRST_ENTRY => 0, + 4 => { + Name => 'AngularVelocity', + Notes => 'gyro angular velocity about X, Y and Z axes in rad/s', + Format => 'float[3]', + }, +); + +# tags found in 'camm' type 3 timed metadata (ref PH, Insta360Pro) +%Image::ExifTool::QuickTime::camm3 = ( + PROCESS_PROC => \&ProcessCAMM, + GROUPS => { 2 => 'Location' }, + FIRST_ENTRY => 0, + 4 => { + Name => 'Acceleration', + Notes => 'acceleration in the X, Y and Z directions in m/s^2', + Format => 'float[3]', + }, +); + +# tags found in 'camm' type 4 timed metadata (ref 4) +%Image::ExifTool::QuickTime::camm4 = ( + PROCESS_PROC => \&ProcessCAMM, + GROUPS => { 2 => 'Location' }, + FIRST_ENTRY => 0, + 4 => { + Name => 'Position', + Notes => 'X, Y, Z position in local coordinate system', + Format => 'float[3]', + }, +); + +# tags found in 'camm' type 5 timed metadata (ref 4) +%Image::ExifTool::QuickTime::camm5 = ( + PROCESS_PROC => \&ProcessCAMM, + GROUPS => { 2 => 'Location' }, + FIRST_ENTRY => 0, + 4 => { + Name => 'GPSLatitude', + Format => 'double', + RawConv => '$$self{FoundGPSLatitude} = 1; $val', + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 12 => { + Name => 'GPSLongitude', + Format => 'double', + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 20 => { + Name => 'GPSAltitude', + Format => 'double', + PrintConv => '$_ = sprintf("%.6f", $val); s/\.?0+$//; "$_ m"', + }, +); + +# tags found in 'camm' type 6 timed metadata (ref PH/4, Insta360) +%Image::ExifTool::QuickTime::camm6 = ( + PROCESS_PROC => \&ProcessCAMM, + GROUPS => { 2 => 'Location' }, + FIRST_ENTRY => 0, + 0x04 => { + Name => 'GPSDateTime', + Description => 'GPS Date/Time', + Groups => { 2 => 'Time' }, + Format => 'double', + RawConv => '$$self{FoundGPSDateTime} = 1; $val', + # by the specification, this should use the GPS epoch of Jan 6, 1980, + # but I have samples which use the Unix epoch of Jan 1, 1970, so convert + # to the Unix Epoch only if it doesn't match the CreateDate within 5 years + ValueConv => q{ + my $offset = 315964800; + if ($$self{CreateDate} and $$self{CreateDate} - $val > 24 * 3600 * 365 * 5) { + $val += $offset; + } + my $str = ConvertUnixTime($val); + my $frac = $val - int($val); + if ($frac != 0) { + $frac = sprintf('%.6f', $frac); + $frac =~ s/^0//; + $frac =~ s/0+$//; + $str .= $frac; + } + return $str . 'Z'; + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x0c => { + Name => 'GPSMeasureMode', + Format => 'int32u', + PrintConv => { + 0 => 'No Measurement', + 2 => '2-Dimensional Measurement', + 3 => '3-Dimensional Measurement', + }, + }, + 0x10 => { + Name => 'GPSLatitude', + Format => 'double', + RawConv => '$$self{FoundGPSLatitude} = 1; $val', + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 0x18 => { + Name => 'GPSLongitude', + Format => 'double', + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 0x20 => { + Name => 'GPSAltitude', + Format => 'float', + PrintConv => '$_ = sprintf("%.3f", $val); s/\.?0+$//; "$_ m"', + }, + 0x24 => { Name => 'GPSHorizontalAccuracy', Format => 'float', Notes => 'metres' }, + 0x28 => { Name => 'GPSVerticalAccuracy', Format => 'float' }, + 0x2c => { Name => 'GPSVelocityEast', Format => 'float', Notes => 'm/s' }, + 0x30 => { Name => 'GPSVelocityNorth', Format => 'float' }, + 0x34 => { Name => 'GPSVelocityUp', Format => 'float' }, + 0x38 => { Name => 'GPSSpeedAccuracy', Format => 'float' }, +); + +# tags found in 'camm' type 7 timed metadata (ref 4) +%Image::ExifTool::QuickTime::camm7 = ( + PROCESS_PROC => \&ProcessCAMM, + GROUPS => { 2 => 'Location' }, + FIRST_ENTRY => 0, + 4 => { + Name => 'MagneticField', + Format => 'float[3]', + Notes => 'microtesla', + }, +); + +# preview image stored by TomTom Bandit ActionCam +%Image::ExifTool::QuickTime::PreviewInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + NOTES => 'Preview stored by TomTom Bandit ActionCam.', + 8 => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + Format => 'undef[$size-8]', + }, +); + +# tags found in 'RVMI' 'gReV' timed metadata (ref PH) +%Image::ExifTool::QuickTime::RVMI_gReV = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + FIRST_ENTRY => 0, + NOTES => 'GPS information extracted from the RVMI box of MOV videos.', + 4 => { + Name => 'GPSLatitude', + Format => 'int32s', + RawConv => '$$self{FoundGPSLatitude} = 1; $val', + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val/1e6, 1)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 8 => { + Name => 'GPSLongitude', + Format => 'int32s', + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val/1e6, 1)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + # 12 - int32s: space for altitude? (always zero in my sample) + 16 => { + Name => 'GPSSpeed', # km/h + Format => 'int16s', + ValueConv => '$val / 10', + }, + 18 => { + Name => 'GPSTrack', + Format => 'int16u', + ValueConv => '$val * 2', + }, +); + +# tags found in 'RVMI' 'sReV' timed metadata (ref PH) +%Image::ExifTool::QuickTime::RVMI_sReV = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + FIRST_ENTRY => 0, + NOTES => q{ + G-sensor information extracted from the RVMI box of MOV videos. + }, + 4 => { + Name => 'GSensor', + Format => 'int16s[3]', # X Y Z + ValueConv => 'my @a=split " ",$val; $_/=1000 foreach @a; "@a"', + }, +); + +# tags found in 'tx3g' sbtl timed metadata (ref PH) +%Image::ExifTool::QuickTime::tx3g = ( + PROCESS_PROC => \&Process_tx3g, + GROUPS => { 2 => 'Location' }, + FIRST_ENTRY => 0, + NOTES => q{ + Tags extracted from the tx3g sbtl timed metadata of Yuneec drones, and + subtitle text in some other videos. + }, + Lat => { + Name => 'GPSLatitude', + RawConv => '$$self{FoundGPSLatitude} = 1; $val', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + Lon => { + Name => 'GPSLongitude', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + Alt => { + Name => 'GPSAltitude', + ValueConv => '$val =~ s/\s*m$//; $val', # remove " m" + PrintConv => '"$val m"', # add it back again + }, + Yaw => 'Yaw', + Pitch => 'Pitch', + Roll => 'Roll', + GimYaw => 'GimbalYaw', + GimPitch => 'GimbalPitch', + GimRoll => 'GimbalRoll', + DateTime => { # for date/time-format subtitle text + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + Text => { Groups => { 2 => 'Other' } }, +); + +%Image::ExifTool::QuickTime::INSV_MakerNotes = ( + GROUPS => { 1 => 'MakerNotes', 2 => 'Camera' }, + 0x0a => 'SerialNumber', + 0x12 => 'Model', + 0x1a => 'Firmware', + 0x2a => { + Name => 'Parameters', + # (see https://exiftool.org/forum/index.php?msg=78942) + Notes => 'number of lenses, 6-axis orientation of each lens, raw resolution', + ValueConv => '$val =~ tr/_/ /; $val', + }, +); + +%Image::ExifTool::QuickTime::Tags360Fly = ( + PROCESS_PROC => \&Process360Fly, + NOTES => 'Timed metadata found in MP4 videos from the 360Fly.', + 1 => { + Name => 'Accel360Fly', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Accel360Fly' }, + }, + 2 => { + Name => 'Gyro360Fly', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Gyro360Fly' }, + }, + 3 => { + Name => 'Mag360Fly', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Mag360Fly' }, + }, + 5 => { + Name => 'GPS360Fly', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::GPS360Fly' }, + }, + 6 => { + Name => 'Rot360Fly', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Rot360Fly' }, + }, + 250 => { + Name => 'Fusion360Fly', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Fusion360Fly' }, + }, +); + +%Image::ExifTool::QuickTime::Accel360Fly = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + 1 => { Name => 'AccelMode', Unknown => 1 }, # (always 2 in my sample) + 2 => { + Name => 'SampleTime', + Groups => { 2 => 'Video' }, + Format => 'int64u', + ValueConv => '$val / 1e6', + PrintConv => 'ConvertDuration($val)', + }, + 10 => { Name => 'AccelYPR', Format => 'float[3]' }, +); + +%Image::ExifTool::QuickTime::Gyro360Fly = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + 1 => { Name => 'GyroMode', Unknown => 1 }, # (always 1 in my sample) + 2 => { + Name => 'SampleTime', + Groups => { 2 => 'Video' }, + Format => 'int64u', + ValueConv => '$val / 1e6', + PrintConv => 'ConvertDuration($val)', + }, + 10 => { Name => 'GyroYPR', Format => 'float[3]' }, +); + +%Image::ExifTool::QuickTime::Mag360Fly = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + 1 => { Name => 'MagMode', Unknown => 1 }, # (always 1 in my sample) + 2 => { + Name => 'SampleTime', + Groups => { 2 => 'Video' }, + Format => 'int64u', + ValueConv => '$val / 1e6', + PrintConv => 'ConvertDuration($val)', + }, + 10 => { Name => 'MagnetometerXYZ', Format => 'float[3]' }, +); + +%Image::ExifTool::QuickTime::GPS360Fly = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + 1 => { Name => 'GPSMode', Unknown => 1 }, # (always 16 in my sample) + 2 => { + Name => 'SampleTime', + Groups => { 2 => 'Video' }, + Format => 'int64u', + ValueConv => '$val / 1e6', + PrintConv => 'ConvertDuration($val)', + }, + 10 => { Name => 'GPSLatitude', Format => 'float', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")' }, + 14 => { Name => 'GPSLongitude', Format => 'float', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")' }, + 18 => { Name => 'GPSAltitude', Format => 'float', PrintConv => '"$val m"' }, # (questionable accuracy) + 22 => { + Name => 'GPSSpeed', + Notes => 'converted to km/hr', + Format => 'int16u', + ValueConv => '$val * 0.036', + PrintConv => 'sprintf("%.1f",$val)', + }, + 24 => { Name => 'GPSTrack', Format => 'int16u', ValueConv => '$val / 100' }, + 26 => { Name => 'Acceleration', Format => 'int16u', ValueConv => '$val / 1000' }, +); + +%Image::ExifTool::QuickTime::Rot360Fly = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + 1 => { Name => 'RotMode', Unknown => 1 }, # (always 1 in my sample) + 2 => { + Name => 'SampleTime', + Groups => { 2 => 'Video' }, + Format => 'int64u', + ValueConv => '$val / 1e6', + PrintConv => 'ConvertDuration($val)', + }, + 10 => { Name => 'RotationXYZ', Format => 'float[3]' }, +); + +%Image::ExifTool::QuickTime::Fusion360Fly = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + 1 => { Name => 'FusionMode', Unknown => 1 }, # (always 0 in my sample) + 2 => { + Name => 'SampleTime', + Groups => { 2 => 'Video' }, + Format => 'int64u', + ValueConv => '$val / 1e6', + PrintConv => 'ConvertDuration($val)', + }, + 10 => { Name => 'FusionYPR', Format => 'float[3]' }, +); + +# tags found in 'marl' ctbx timed metadata (ref PH) +%Image::ExifTool::QuickTime::marl = ( + PROCESS_PROC => \&Process_marl, + GROUPS => { 2 => 'Other' }, + NOTES => 'Tags extracted from the marl ctbx timed metadata of GM cars.', +); + +#------------------------------------------------------------------------------ +# Save information from keys in OtherSampleDesc directory for processing timed metadata +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# (ref "Timed Metadata Media" here: +# https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html) +sub SaveMetaKeys($$$) +{ + local $_; + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = length $$dataPt; + return 0 unless $dirLen > 8; + my $pos = 0; + my $verbose = $$et{OPTIONS}{Verbose}; + my $oldIndent = $$et{INDENT}; + my $ee = $$et{ee}; + $ee or $ee = $$et{ee} = { }; + + $verbose and $et->VerboseDir($$dirInfo{DirName}, undef, $dirLen); + + # loop through metadata key table + while ($pos + 8 < $dirLen) { + my $size = Get32u($dataPt, $pos); + my $id = substr($$dataPt, $pos+4, 4); + my $end = $pos + $size; + $end = $dirLen if $end > $dirLen; + $pos += 8; + my ($tagID, $format, $pid); + if ($verbose) { + $pid = PrintableTagID($id,1); + $et->VPrint(0, "$oldIndent+ [Metadata Key entry, Local ID=$pid, $size bytes]\n"); + $$et{INDENT} .= '| '; + } + + while ($pos + 4 < $end) { + my $len = unpack("x${pos}N", $$dataPt); + last if $len < 8 or $pos + $len > $end; + my $tag = substr($$dataPt, $pos + 4, 4); + $pos += 8; $len -= 8; + my $val = substr($$dataPt, $pos, $len); + $pos += $len; + my $str; + if ($tag eq 'keyd') { + ($tagID = $val) =~ s/^(mdta|fiel)com\.apple\.quicktime\.//; + $tagID = "Tag_$val" unless $tagID; + ($str = $val) =~ s/(.{4})/$1 / if $verbose; + } elsif ($tag eq 'dtyp') { + next if length $val < 4; + if (length $val >= 4) { + my $ns = unpack('N', $val); + if ($ns == 0) { + length $val >= 8 or $et->Warn('Short dtyp data'), next; + $str = unpack('x4N',$val); + $format = $qtFmt{$str} || 'undef'; + } elsif ($ns == 1) { + $str = substr($val, 4); + $format = 'undef'; + } else { + $format = 'undef'; + } + $str .= " ($format)" if $verbose and defined $str; + } + } + if ($verbose > 1) { + if (defined $str) { + $str =~ tr/\x00-\x1f\x7f-\xff/./; + $str = " = $str"; + } else { + $str = ''; + } + $et->VPrint(1, $$et{INDENT}."- Tag '".PrintableTagID($tag,2)."' ($len bytes)$str\n"); + $et->VerboseDump(\$val); + } + } + if (defined $tagID and defined $format) { + if ($verbose) { + my $t2 = PrintableTagID($tagID); + $et->VPrint(0, "$$et{INDENT}Added Local ID $pid = $t2 ($format)\n"); + } + $$ee{'keys'}{$id} = { TagID => $tagID, Format => $format }; + } + $$et{INDENT} = $oldIndent; + } + return 1; +} + +#------------------------------------------------------------------------------ +# We found some tags for this sample, so set document number and save timing information +# Inputs: 0) ExifTool ref, 1) tag table ref, 2) sample time, 3) sample duration +sub FoundSomething($$;$$) +{ + my ($et, $tagTbl, $time, $dur) = @_; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $et->HandleTag($tagTbl, SampleTime => $time) if defined $time; + $et->HandleTag($tagTbl, SampleDuration => $dur) if defined $dur; +} + +#------------------------------------------------------------------------------ +# Approximate GPSDateTime value from sample time and CreateDate +# Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (s) +# 3) true if CreateDate is at end of video +# Notes: Uses ExifTool CreateDateAtEnd as flag to subtract video duration +sub SetGPSDateTime($$$) +{ + my ($et, $tagTbl, $sampleTime) = @_; + my $value = $$et{VALUE}; + if (defined $sampleTime and $$value{CreateDate}) { + $sampleTime += $$value{CreateDate}; # adjust sample time to seconds since the epoch + if ($$et{CreateDateAtEnd}) { # adjust if CreateDate is at end of video + return unless $$value{TimeScale} and $$value{Duration}; + $sampleTime -= $$value{Duration} / $$value{TimeScale}; + $et->WarnOnce('Approximating GPSDateTime as CreateDate - Duration + SampleTime', 1); + } else { + $et->WarnOnce('Approximating GPSDateTime as CreateDate + SampleTime', 1); + } + unless ($et->Options('QuickTimeUTC')) { + my $tzOff = $$et{tzOff}; # use previously calculated offset + unless (defined $tzOff) { + # adjust to UTC, assuming time is local + my @tm = localtime $$value{CreateDate}; + my @gm = gmtime $$value{CreateDate}; + $tzOff = $$et{tzOff} = Image::ExifTool::GetTimeZone(\@tm, \@gm) * 60; + } + $sampleTime -= $tzOff; # shift from local time to UTC + } + $et->HandleTag($tagTbl, GPSDateTime => Image::ExifTool::ConvertUnixTime($sampleTime,0,3) . 'Z'); + } +} + +#------------------------------------------------------------------------------ +# Handle tags that we found in the subtitle 'text' +# Inputs: 0) ExifTool ref, 1) tag table ref, 2) hash of tag names/values +sub HandleTextTags($$$) +{ + my ($et, $tagTbl, $tags) = @_; + my $tag; + delete $$tags{done}; + delete $$tags{GPSTimeStamp} if $$tags{GPSDateTime}; + foreach $tag (sort keys %$tags) { + $et->HandleTag($tagTbl, $tag => $$tags{$tag}); + } + $$et{UnknownTextCount} = 0; + undef %$tags; # clear the hash +} + +#------------------------------------------------------------------------------ +# Process subtitle 'text' +# Inputs: 0) ExifTool ref, 1) data ref or dirInfo ref, 2) tag table ref +# 3) flag set if text was already stored +sub Process_text($$$;$) +{ + my ($et, $dataPt, $tagTbl, $handled) = @_; + my %tags; + + return if $$et{NoMoreTextDecoding}; + + if (ref $dataPt eq 'HASH') { + my $dirName = $$dataPt{DirName}; + $dataPt = $$dataPt{DataPt}; + $et->VerboseDir($dirName, undef, length($$dataPt)); + } + + while ($$dataPt =~ /\$(\w+)([^\$]*)/g) { + my ($tag, $dat) = ($1, $2); + if ($tag =~ /^[A-Z]{2}RMC$/ and $dat =~ /^,(\d{2})(\d{2})(\d+(?:\.\d*)),A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/) { + my $time = "$1:$2:$3"; + if ($$et{LastTime}) { + if ($$et{LastTime} eq $time) { + # combine with the previous NMEA sentence + $$et{DOC_NUM} = $$et{LastDoc}; + } elsif (%tags) { + # handle existing tags and start a new document + # (see https://exiftool.org/forum/index.php?msg=75422) + HandleTextTags($et, $tagTbl, \%tags); + undef %tags; + # increment document number and update document count if necessary + $$et{DOC_COUNT} < ++$$et{DOC_NUM} and $$et{DOC_COUNT} = $$et{DOC_NUM}; + } + } + $$et{LastTime} = $time; + $$et{LastDoc} = $$et{DOC_NUM}; + my $year = $14 + ($14 >= 70 ? 1900 : 2000); + my $dateTime = sprintf('%.4d:%.2d:%.2d %sZ', $year, $13, $12, $time); + $tags{GPSDateTime} = $dateTime; + $tags{GPSLatitude} = (($4 || 0) + $5/60) * ($6 eq 'N' ? 1 : -1); + $tags{GPSLongitude} = (($7 || 0) + $8/60) * ($9 eq 'E' ? 1 : -1); + $tags{GPSSpeed} = $10 * $knotsToKph if length $10; + $tags{GPSTrack} = $11 if length $11; + } elsif ($tag =~ /^[A-Z]{2}GGA$/ and $dat =~ /^,(\d{2})(\d{2})(\d+(?:\.\d*)?),(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),[1-6]?,(\d+)?,(\.\d+|\d+\.?\d*)?,(-?\d+\.?\d*)?,M?/s) { + my $time = "$1:$2:$3"; + if ($$et{LastTime}) { + if ($$et{LastTime} eq $time) { + $$et{DOC_NUM} = $$et{LastDoc}; + } elsif (%tags) { + HandleTextTags($et, $tagTbl, \%tags); + undef %tags; + $$et{DOC_COUNT} < ++$$et{DOC_NUM} and $$et{DOC_COUNT} = $$et{DOC_NUM}; + } + } + $$et{LastTime} = $time; + $$et{LastDoc} = $$et{DOC_NUM}; + $tags{GPSTimeStamp} = $time; + $tags{GPSLatitude} = (($4 || 0) + $5/60) * ($6 eq 'N' ? 1 : -1); + $tags{GPSLongitude} = (($7 || 0) + $8/60) * ($9 eq 'E' ? 1 : -1); + $tags{GPSSatellites} = $10 if defined $10; + $tags{GPSDOP} = $11 if defined $11; + $tags{GPSAltitude} = $12 if defined $12; + # ($G and $GS are ref https://exiftool.org/forum/index.php?topic=13115.msg71743#msg71743) + } elsif ($tag eq 'G' and $dat =~ /:(\d{4})-(\d{2})-(\d{2}) (\d{2}:\d{2}:\d{2})-([NS])(\d+\.\d+)-([EW])(\d+\.\d+)-S(\d+)/) { + $tags{GPSDateTime} = "$1:$2:$3 $4"; + $tags{GPSLatitude} = $6 * ($5 eq 'S' ? -1 : 1); + $tags{GPSLongitude} = $8 * ($7 eq 'W' ? -1 : 1); + $tags{GPSSpeed} = $9; + } elsif ($tag eq 'GS' and $dat =~ /:([-+]?\d+),([-+]?\d+),([-+]?\d+)/) { + # scale and re-arrange to match gsensor output from Win app (forum11665) + my @acc = ( ($2+2432)/1000, ($3 + 361)/1000, ($1-3708)/1000 ); + $tags{Accelerometer} = "@acc"; + } elsif ($tag eq 'BEGINGSENSOR' and $dat =~ /^:([-+]\d+\.\d+):([-+]\d+\.\d+):([-+]\d+\.\d+)/) { + $tags{Accelerometer} = "$1 $2 $3"; + } elsif ($tag eq 'TIME' and $dat =~ /^:(\d+)/) { + $tags{TimeCode} = $1 / ($$et{MediaTS} || 1); + } elsif ($tag eq 'BEGIN') { + $tags{Text} = $dat if length $dat; + $tags{done} = 1; + } elsif ($tag ne 'END') { + $tags{Text} = "\$$tag$dat" unless $handled; + } + } + %tags and HandleTextTags($et, $tagTbl, \%tags), return; + + # check for enciphered binary GPS data + # BlueSkySea: + # 0000: 00 00 aa aa aa aa 54 54 98 9a 9b 93 9a 92 98 9a [......TT........] + # 0010: 9a 9d 9f 9b 9f 9d aa aa aa aa aa aa aa aa aa aa [................] + # 0020: aa aa aa aa aa a9 e4 9e 92 9f 9b 9f 92 9d 99 ef [................] + # 0030: 9a 9a 98 9b 93 9d 9d 9c 93 aa aa aa aa aa 9a 99 [................] + # 0040: 9b aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa [................] + # [...] + # decrypted: + # 0000: aa aa 00 00 00 00 fe fe 32 30 31 39 30 38 32 30 [........20190820] + # 0010: 30 37 35 31 35 37 00 00 00 00 00 00 00 00 00 00 [075157..........] + # 0020: 00 00 00 00 00 03 4e 34 38 35 31 35 38 37 33 45 [......N48515873E] + # 0030: 30 30 32 31 39 37 37 36 39 00 00 00 00 00 30 33 [002197769.....03] + # 0040: 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [1...............] + # [...] + # Ambarella A12: + # 0000: 00 00 f2 e1 f0 ee 54 54 98 9a 9b 93 9b 9b 9b 9c [......TT........] + # 0010: 9b 9a 9a 93 9a 9b a6 9a 9b 9b 93 9b 9a 9b 9c 9a [................] + # 0020: 9d 9a 92 9f 93 a9 e4 9f 9f 9e 9f 9b 9b 9c 9d ef [................] + # 0030: 9a 99 9d 9e 99 9a 9a 9e 9b 81 9a 9b 9f 9d 9a 9a [................] + # 0040: 9a 87 9a 9a 9a 87 9a 98 99 87 9a 9a 99 87 9a 9a [................] + # [...] + # decrypted: + # 0000: aa aa 58 4b 5a 44 fe fe 32 30 31 39 31 31 31 36 [..XKZD..20191116] + # 0010: 31 30 30 39 30 31 0c 30 31 31 39 31 30 31 36 30 [100901.011910160] + # 0020: 37 30 38 35 39 03 4e 35 35 34 35 31 31 36 37 45 [70859.N55451167E] + # 0030: 30 33 37 34 33 30 30 34 31 2b 30 31 35 37 30 30 [037430041+015700] + # 0040: 30 2d 30 30 30 2d 30 32 33 2d 30 30 33 2d 30 30 [0-000-023-003-00] + # [...] + # 0100: aa 55 57 ed ed 45 58 54 44 00 01 30 30 30 30 31 [.UW..EXTD..00001] + # 0110: 31 30 38 30 30 30 58 00 58 00 58 00 58 00 58 00 [108000X.X.X.X.X.] + # 0120: 58 00 58 00 58 00 58 00 00 00 00 00 00 00 00 00 [X.X.X.X.........] + # 0130: 00 00 00 00 00 00 00 [.......] + if ($$dataPt =~ /^\0\0(..\xaa\xaa|\xf2\xe1\xf0\xee)/s and length $$dataPt >= 282) { + my $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 8, 14))); + if ($val =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/) { + $tags{GPSDateTime} = "$1:$2:$3 $4:$5:$6"; + $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 38, 9))); + if ($val =~ /^([NS])(\d{2})(\d+$)$/) { + $tags{GPSLatitude} = ($2 + $3 / 600000) * ($1 eq 'S' ? -1 : 1); + } + $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 47, 10))); + if ($val =~ /^([EW])(\d{3})(\d+$)$/) { + $tags{GPSLongitude} = ($2 + $3 / 600000) * ($1 eq 'W' ? -1 : 1); + } + $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x39, 5))); + $tags{GPSAltitude} = $val + 0 if $val =~ /^[-+]\d+$/; + $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x3e, 3))); + $tags{GPSSpeed} = $val + 0 if $val =~ /^\d+$/; + if ($$dataPt =~ /^\0\0..\xaa\xaa/s) { # (BlueSkySea) + $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0xad, 12))); + # the first X,Y,Z accelerometer readings from the AccelerometerData + if ($val =~ /^([-+]\d{3})([-+]\d{3})([-+]\d{3})$/) { + $tags{Accelerometer} = "$1 $2 $3"; + $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0xba, 96))); + my $order = GetByteOrder(); + SetByteOrder('II'); + $val = ReadValue(\$val, 0, 'float'); + SetByteOrder($order); + $tags{AccelerometerData} = $val; + } + } else { # (Ambarella) + my @acc; + $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x41, 195))); + push @acc, $1, $2, $3 while $val =~ /\G([-+]\d{3})([-+]\d{3})([-+]\d{3})/g; + $tags{Accelerometer} = "@acc" if @acc; + } + } + %tags and HandleTextTags($et, $tagTbl, \%tags), return; + } + + # check for DJI telemetry data, eg: + # "F/3.5, SS 1000, ISO 100, EV 0, GPS (8.6499, 53.1665, 18), D 24.26m, + # H 6.00m, H.S 2.10m/s, V.S 0.00m/s \n" + if ($$dataPt =~ /GPS \(([-+]?\d*\.\d+),\s*([-+]?\d*\.\d+)/) { + $$et{CreateDateAtEnd} = 1; # set flag indicating the file creation date is at the end + $tags{GPSLatitude} = $2; + $tags{GPSLongitude} = $1; + $tags{GPSAltitude} = $1 if $$dataPt =~ /,\s*H\s+([-+]?\d+\.?\d*)m/; + $tags{GPSSpeed} = $1 * $mpsToKph if $$dataPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/; + $tags{Distance} = $1 * $mpsToKph if $$dataPt =~ /,\s*D\s+(\d+\.?\d*)m/; + $tags{VerticalSpeed} = $1 if $$dataPt =~ /,\s*V.S\s+([-+]?\d+\.?\d*)/; + $tags{FNumber} = $1 if $$dataPt =~ /\bF\/(\d+\.?\d*)/; + $tags{ExposureTime} = 1 / $1 if $$dataPt =~ /\bSS\s+(\d+\.?\d*)/; + $tags{ExposureCompensation} = ($1 / ($2 || 1)) if $$dataPt =~ /\bEV\s+([-+]?\d+\.?\d*)(\/\d+)?/; + $tags{ISO} = $1 if $$dataPt =~ /\bISO\s+(\d+\.?\d*)/; + HandleTextTags($et, $tagTbl, \%tags); + return; + } + + # check for Mini 0806 dashcam GPS, eg: + # "A,270519,201555.000,3356.8925,N,08420.2071,W,000.0,331.0M,+01.84,-09.80,-00.61;\n" + if ($$dataPt =~ /^A,(\d{2})(\d{2})(\d{2}),(\d{2})(\d{2})(\d{2}(\.\d+)?)/) { + $tags{GPSDateTime} = "20$3:$2:$1 $4:$5:$6Z"; + if ($$dataPt =~ /^A,.*?,.*?,(\d{2})(\d+\.\d+),([NS])/) { + $tags{GPSLatitude} = ($1 + $2/60) * ($3 eq 'S' ? -1 : 1); + } + if ($$dataPt =~ /^A,.*?,.*?,.*?,.*?,(\d{3})(\d+\.\d+),([EW])/) { + $tags{GPSLongitude} = ($1 + $2/60) * ($3 eq 'W' ? -1 : 1); + } + my @a = split ',', $$dataPt; + $tags{GPSAltitude} = $a[8] if $a[8] and $a[8] =~ s/M$//; + $tags{GPSSpeed} = $a[7] if $a[7] and $a[7] =~ /^\d+\.\d+$/; # (NC) + $tags{Accelerometer} = "$a[9] $a[10] $a[11]" if $a[11] and $a[11] =~ s/;\s*$//; + HandleTextTags($et, $tagTbl, \%tags); + return; + } + + # check for Roadhawk dashcam text + # ".;;;;D?JL;6+;;;D;R?;4;;;;DBB;;O;;;=D;L;;HO71G>F;-?=J-F:FNJJ;DPP-JF3F;;PL=DBRLBF0F;=?DNF-RD-PF;N;?=JF;;?D=F:*6F~" + # decoded: + # "X0000.2340Y-000.0720Z0000.9900G0001.0400$GPRMC,082138,A,5330.6683,N,00641.9749,W,012.5,87.86,050213,002.1,A" + # (note: "002.1" is magnetic variation and is not decoded; it should have ",E" or ",W" afterward for direction) + if ($$dataPt =~ /\*[0-9A-F]{2}~$/) { + # (ref https://reverseengineering.stackexchange.com/questions/11582/how-to-reverse-engineer-dash-cam-metadata) + my @decode = unpack 'C*', '-I8XQWRVNZOYPUTA0B1C2SJ9K.L,M$D3E4F5G6H7'; + my @chars = unpack 'C*', substr($$dataPt, 0, -4); + foreach (@chars) { + my $n = $_ - 43; + $_ = $decode[$n] if $n >= 0 and defined $decode[$n]; + } + my $buff = pack 'C*', @chars; + if ($buff =~ /X(.*?)Y(.*?)Z(.*?)G(.*?)\$/) { + # yup. the decoding worked out + $tags{Accelerometer} = "$1 $2 $3 $4"; + $$dataPt = $buff; # (process GPRMC below) + } + } + + # check for Thinkware format (and other NMEA RMC), eg: + # "gsensori,4,512,-67,-12,100;GNRMC,161313.00,A,4529.87489,N,07337.01215,W,6.225,35.34,310819,,,A*52..; + # CAR,0,0,0,0.0,0,0,0,0,0,0,0,0" + if ($$dataPt =~ /[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/ and + # do some basic sanity checks on the date + $13 <= 31 and $14 <= 12 and $15 <= 99) + { + my $year = $15 + ($15 >= 70 ? 1900 : 2000); + $tags{GPSDateTime} = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3); + $tags{GPSLatitude} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1); + $tags{GPSLongitude} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1); + $tags{GPSSpeed} = $11 * $knotsToKph if length $11; + $tags{GPSTrack} = $12 if length $12; + } + $tags{GSensor} = $1 if $$dataPt =~ /\bgsensori,(.*?)(;|$)/; + $tags{Car} = $1 if $$dataPt =~ /\bCAR,(.*?)(;|$)/; + + if (%tags) { + HandleTextTags($et, $tagTbl, \%tags); + } else { + $$et{UnknownTextCount} = ($$et{UnknownTextCount} || 0) + 1; + # give up trying to decode useful information if we haven't found anything for a while + $$et{NoMoreTextDecoding} = 1 if $$et{UnknownTextCount} > 100; + } +} + +#------------------------------------------------------------------------------ +# Extract embedded metadata from media samples +# Inputs: 0) ExifTool ref +# Notes: Also accesses ExifTool RAF*, SET_GROUP1, HandlerType, MetaFormat, +# ee*, and avcC elements (* = must exist) +# - may be called either due to ExtractEmbedded option, or ImageDataHash requested +# - hash includes only video and audio data +sub ProcessSamples($) +{ + my $et = shift; + my ($raf, $ee) = @$et{qw(RAF ee)}; + my ($i, $buff, $pos, $hdrLen, $hdrFmt, @time, @dur, $oldIndent, $hash); + my ($mdatOffset, $mdatSize); # (for range-checking samples when hash is done) + + return unless $ee; + delete $$et{ee}; # use only once + + my $eeOpt = $et->Options('ExtractEmbedded'); + my $type = $$et{HandlerType} || ''; + if ($type eq 'vide') { + # only process specific types of video streams + $hash = $$et{ImageDataHash}; + # only process specific video types if ExtractEmbedded was used + # (otherwise we are only here to calculate the audio/video hash) + if ($eeOpt) { + if ($$ee{avcC}) { $type = 'avcC' } + elsif ($$ee{JPEG}) { $type = 'JPEG' } + else { return unless $hash } + } + } elsif ($type eq 'soun') { + $hash = $$et{ImageDataHash}; + return unless $hash; + } else { + return unless $eeOpt; # (don't do hash on other types) + } + + my $hashSize = 0; + my ($start, $size) = @$ee{qw(start size)}; +# +# determine sample start offsets from chunk offsets (stco) and sample-to-chunk table (stsc), +# and sample time/duration from time-to-sample (stts) +# + unless ($start and $size) { + return unless $size; + my ($stco, $stsc, $stts) = @$ee{qw(stco stsc stts)}; + return unless $stco and $stsc and @$stsc; + $start = [ ]; + my ($nextChunk, $iChunk) = (0, 1); + my ($chunkStart, $startChunk, $samplesPerChunk, $descIdx, $timeCount, $timeDelta, $time); + if ($stts and @$stts > 1) { + $time = 0; + $timeCount = shift @$stts; + $timeDelta = shift @$stts; + } + my $ts = $$et{MediaTS} || 1; + my @chunkSize; # total size of each chunk + foreach $chunkStart (@$stco) { + if ($iChunk >= $nextChunk and @$stsc) { + ($startChunk, $samplesPerChunk, $descIdx) = @{shift @$stsc}; + $nextChunk = $$stsc[0][0] if @$stsc; + } + @$size < @$start + $samplesPerChunk and $et->WarnOnce('Sample size error'), last; + last unless defined $chunkStart and length $chunkStart; + my $sampleStart = $chunkStart; + my $chunkSize = 0; +Sample: for ($i=0; ; ) { + push @$start, $sampleStart; + if (defined $time) { + until ($timeCount) { + if (@$stts < 2) { + undef $time; + last Sample; + } + $timeCount = shift @$stts; + $timeDelta = shift @$stts; + } + push @time, $time / $ts; + push @dur, $timeDelta / $ts; + $time += $timeDelta; + --$timeCount; + } + # (eventually should use the description indices: $descIdx) + $chunkSize += $$size[$#$start]; + last if ++$i >= $samplesPerChunk; + $sampleStart += $$size[$#$start]; + } + push @chunkSize, $chunkSize; + ++$iChunk; + } + @$start == @$size or $et->WarnOnce('Incorrect sample start/size count'), return; + # process as chunks if we are only interested in calculating hash + if ($type eq 'soun' or $type eq 'vide') { + $start = $stco; + $size = \@chunkSize; + } + } +# +# extract and parse the sample data +# + my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + my $verbose = $et->Options('Verbose'); + my $metaFormat = $$et{MetaFormat} || ''; + my $tell = $raf->Tell(); + + if ($verbose) { + $et->VPrint(0, "---- Extract Embedded ----\n"); + $oldIndent = $$et{INDENT}; + $$et{INDENT} = ''; + } + if ($hash) { + $mdatSize = $$et{MediaDataSize}; + $mdatOffset = $$et{MediaDataOffset} if defined $mdatSize; + } + # get required information from avcC box if parsing video data + if ($type eq 'avcC') { + $hdrLen = (Get8u(\$$ee{avcC}, 4) & 0x03) + 1; + $hdrFmt = ($hdrLen == 4 ? 'N' : $hdrLen == 2 ? 'n' : 'C'); + require Image::ExifTool::H264; + } + # loop through all samples + for ($i=0; $i<@$start and $i<@$size; ++$i) { + + # initialize our flags for setting GPSDateTime + delete $$et{FoundGPSLatitude}; + delete $$et{FoundGPSDateTime}; + + # range check the sample data for hash if necessary + my $size = $$size[$i]; + if (defined $mdatOffset) { + if ($$start[$i] < $mdatOffset) { + $et->Warn("Sample $i for '${type}' data is before start of mdat"); + } elsif ($$start[$i] + $size > $mdatOffset + $mdatSize) { + $et->Warn("Sample $i for '${type}' data runs off end of mdat"); + $size = $mdatOffset + $mdatSize - $$start[$i]; + $size = 0 if $size < 0; + } + } + # read the sample data + $raf->Seek($$start[$i], 0) or $et->WarnOnce("Seek error in $type data"), next; + my $n = $raf->Read($buff, $size); + unless ($n == $size) { + $et->WarnOnce("Error reading $type data"); + next unless $n; + $size = $n; + } + if ($hash) { + $hash->add($buff); + $hashSize += length $buff; + } + if ($type eq 'avcC') { + next if length($buff) <= $hdrLen; + # scan through all NAL units and send them to ParseH264Video() + for ($pos=0; ; ) { + my $len = unpack("x$pos$hdrFmt", $buff); + last if $pos + $hdrLen + $len > length($buff); + my $tmp = "\0\0\0\x01" . substr($buff, $pos+$hdrLen, $len); + Image::ExifTool::H264::ParseH264Video($et, \$tmp); + $pos += $hdrLen + $len; + last if $pos + $hdrLen >= length($buff); + } + if ($$et{GotNAL06}) { + my $eeOpt = $et->Options('ExtractEmbedded'); + last unless $eeOpt and $eeOpt > 2; + } + next; + } + if ($verbose > 1) { + my $hdr = $$et{SET_GROUP1} ? "$$et{SET_GROUP1} Type='${type}' Format='${metaFormat}'" : "Type='${type}'"; + $et->VPrint(1, "${hdr}, Sample ".($i+1).' of '.scalar(@$start)." ($size bytes)\n"); + $et->VerboseDump(\$buff, Addr => $$start[$i]); + } + if ($type eq 'text' or + # (PNDM is normally 'text', but was sbtl/tx3g in concatenated Garmin sample output_3videos.mp4) + ($type eq 'sbtl' and $metaFormat eq 'tx3g' and $buff =~ /^..PNDM/s)) + { + + my $handled; + FoundSomething($et, $tagTbl, $time[$i], $dur[$i]); + unless ($buff =~ /^\$BEGIN/) { + # remove ending "encd" box if it exists + $buff =~ s/\0\0\0\x0cencd\0\0\x01\0$// and $size -= 12; + # cameras such as the CanonPowerShotN100 store ASCII time codes with a + # leading 2-byte integer giving the length of the string + # (and chapter names start with a 2-byte integer too) + if ($size >= 2 and unpack('n',$buff) == $size - 2) { + next if $size == 2; + $buff = substr($buff,2); + } + my $val; + # check for encrypted GPS text as written by E-PRANCE B47FS camera + if ($buff =~ /^\0/ and $buff =~ /\x0a$/ and length($buff) > 5) { + # decode simple ASCII difference cipher, + # based on known value of 4th-last char = '*' + my $dif = ord('*') - ord(substr($buff, -4, 1)); + my $tmp = pack 'C*',map { $_=($_+$dif)&0xff } unpack 'C*',substr $buff,1,-1; + if ($verbose > 2) { + $et->VPrint(0, "[decrypted text]\n"); + $et->VerboseDump(\$tmp); + } + if ($tmp =~ /^(.*?)(\$[A-Z]{2}RMC.*)/s) { + ($val, $buff) = ($1, $2); + $val =~ tr/\t/ /; + $et->HandleTag($tagTbl, RawGSensor => $val) if length $val; + } + } elsif ($buff =~ /^(\0.{3})?PNDM/s) { + # Garmin Dashcam format (actually binary, not text) + my $n = $1 ? 4 : 0; # skip leading 4-byte size word if it exists + next if length($buff) < 20 + $n; + $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$buff, 12+$n) * 180/0x80000000); + $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$buff, 16+$n) * 180/0x80000000); + $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8+$n) * $mphToKph); + SetGPSDateTime($et, $tagTbl, $time[$i]); + next; # all done (don't store/process as text) + } + unless (defined $val) { + $et->HandleTag($tagTbl, Text => $buff); # just store any other text + $handled = 1; + } + } + Process_text($et, \$buff, $tagTbl, $handled); + + } elsif ($processByMetaFormat{$type}) { + + if ($$tagTbl{$metaFormat}) { + my $tagInfo = $et->GetTagInfo($tagTbl, $metaFormat, \$buff); + if ($tagInfo) { + FoundSomething($et, $tagTbl, $time[$i], $dur[$i]); + $$et{ee} = $ee; # need ee information for 'keys' + $et->HandleTag($tagTbl, $metaFormat, undef, + DataPt => \$buff, + DataPos => 0, + Base => $$start[$i], # (Base must be set for CR3 files) + TagInfo => $tagInfo, + ); + delete $$et{ee}; + } elsif ($metaFormat eq 'camm' and $buff =~ /^X/) { + # seen 'camm' metadata in this format (X/Y/Z acceleration and G force? + GPRMC + ?) + # "X0000.0000Y0000.0000Z0000.0000G0000.0000$GPRMC,000125,V,,,,,000.0,,280908,002.1,N*71~, 794021 \x0a" + FoundSomething($et, $tagTbl, $time[$i], $dur[$i]); + $et->HandleTag($tagTbl, Accelerometer => "$1 $2 $3 $4") if $buff =~ /X(.*?)Y(.*?)Z(.*?)G(.*?)\$/; + Process_text($et, \$buff, $tagTbl); + } + } elsif ($verbose) { + $et->VPrint(0, "Unknown $type format ($metaFormat)"); + } + + } elsif ($type eq 'gps ') { # (ie. GPSDataList tag) + + if ($buff =~ /^....freeGPS /s) { + # decode "freeGPS " data (Novatek) + ProcessFreeGPS($et, { + DataPt => \$buff, + DataPos => $$start[$i], + SampleTime => $time[$i], + SampleDuration => $dur[$i], + }, $tagTbl); + } + + } elsif ($$tagTbl{$type}) { + + my $tagInfo = $et->GetTagInfo($tagTbl, $type, \$buff); + if ($tagInfo) { + FoundSomething($et, $tagTbl, $time[$i], $dur[$i]); + $et->HandleTag($tagTbl, $type, undef, + DataPt => \$buff, + DataPos => 0, + Base => $$start[$i], # (Base must be set for CR3 files) + TagInfo => $tagInfo, + ); + } + } + # generate approximate GPSDateTime if necessary + SetGPSDateTime($et, $tagTbl, $time[$i]) if $$et{FoundGPSLatitude} and not $$et{FoundGPSDateTime}; + } + if ($verbose) { + my $str = $type eq 'soun' ? 'Audio' : 'Video'; + $et->VPrint(0, "$$et{INDENT}(ImageDataHash: $hashSize bytes of $str data)\n") if $hashSize; + $$et{INDENT} = $oldIndent; + $et->VPrint(0, "--------------------------\n"); + } + # clean up + $raf->Seek($tell, 0); # restore original file position + $$et{DOC_NUM} = 0; + $$et{HandlerType} = $$et{HanderDesc} = ''; +} + +#------------------------------------------------------------------------------ +# Convert latitude/longitude from DDDMM.MMMM format to decimal degrees +# Inputs: 0) latitude, 1) longitude +# Returns: lat/lon are changed in place +# (note: this method works fine for negative coordinates) +sub ConvertLatLon($$) +{ + my $deg = int($_[0] / 100); # latitude + $_[0] = $deg + ($_[0] - $deg * 100) / 60; + $deg = int($_[1] / 100); # longitude + $_[1] = $deg + ($_[1] - $deg * 100) / 60; +} + +#------------------------------------------------------------------------------ +# Process "freeGPS " data blocks referenced by a 'gps ' (GPSDataList) atom +# Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,SampleTime,SampleDuration}, 2) tagTable ref +# Returns: 1 on success (or 0 on unrecognized or "measurement-void" GPS data) +# Notes: +# - also see ProcessFreeGPS2() below for processing of other types of freeGPS blocks +sub ProcessFreeGPS($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = length $$dataPt; + my ($yr, $mon, $day, $hr, $min, $sec, $stat, $lbl, $ddd); + my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, @acc, @xtra); + + return 0 if $dirLen < 92; + + if (substr($$dataPt,18,8) eq "\xaa\xaa\xf2\xe1\xf0\xee\x54\x54") { + + $debug and $et->FoundTag(GPSType => '1A'); + # (this is very similar to the encrypted text format) + # decode encrypted ASCII-based GPS (DashCam Azdome GS63H, ref 5) + # header looks like this in my sample: + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 05 01 00 00 [....freeGPS ....] + # 0010: 01 03 aa aa f2 e1 f0 ee 54 54 98 9a 9b 92 9a 93 [........TT......] + # 0020: 98 9e 98 98 9e 93 98 92 a6 9f 9f 9c 9d ed fa 8a [................] + # decrypted (from byte 18): + # 0000: 00 00 58 4b 5a 44 fe fe 32 30 31 38 30 39 32 34 [..XKZD..20180924] + # 0010: 32 32 34 39 32 38 0c 35 35 36 37 47 50 20 20 20 [224928.5567GP ] + # 0020: 00 00 00 00 00 03 4e 34 30 34 36 34 33 35 30 57 [......N40464350W] + # 0030: 30 30 37 30 34 30 33 30 38 30 30 30 30 30 30 30 [0070403080000000] + # 0040: 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [7...............] + # [...] + # 00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 2b 30 39 [.............+09] + # 00b0: 33 2d 30 30 33 2d 30 30 35 00 00 00 00 00 00 00 [3-003-005.......] + # header looks like this for EEEkit gps: + # 0000: 00 00 04 00 66 72 65 65 47 50 53 20 f0 03 00 00 [....freeGPS ....] + # 0010: 01 03 aa aa f2 e1 f0 ee 54 54 98 9a 98 9a 9a 9f [........TT......] + # 0020: 9b 93 9b 9c 98 99 99 9f a6 9a 9a 98 9a 9a 9f 9b [................] + # 0030: 93 9b 9c 98 99 99 9c a9 e4 99 9d 9e 9f 98 9e 9b [................] + # 0040: 9c fd 9b 98 98 98 9f 9f 9a 9a 93 81 9a 9b 9d 9f [................] + # decrypted (from byte 18): + # 0000: 00 00 58 4b 5a 44 fe fe 32 30 32 30 30 35 31 39 [..XKZD..20200519] + # 0010: 31 36 32 33 33 35 0c 30 30 32 30 30 35 31 39 31 [162335.002005191] + # 0020: 36 32 33 33 36 03 4e 33 37 34 35 32 34 31 36 57 [62336.N37452416W] + # 0030: 31 32 32 32 35 35 30 30 39 2b 30 31 37 35 30 31 [122255009+017501] + # 0040: 31 2b 30 31 34 2b 30 30 32 2b 30 32 36 2b 30 31 [1+014+002+026+01] + my $n = $dirLen - 18; + $n = 0x101 if $n > 0x101; + my $buf2 = pack 'C*', map { $_ ^ 0xaa } unpack 'C*', substr($$dataPt,18,$n); + if ($et->Options('Verbose') > 1) { + $et->VPrint(1, '[decrypted freeGPS data]'); + $et->VerboseDump(\$buf2); + } + # (extract longitude as 9 digits, not 8, ref PH) + if ($buf2 =~ /^.{8}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2}).(.{15})([NS])(\d{8})([EW])(\d{9})(\d{8})?/s) { + ($yr,$mon,$day,$hr,$min,$sec,$lbl,$latRef,$lat,$lonRef,$lon,$spd) = ($1,$2,$3,$4,$5,$6,$7,$8,$9/1e4,$10,$11/1e4,$12); + if (defined $spd) { # (Azdome) + $spd += 0; # remove leading 0's + } elsif ($buf2 =~ /^.{57}([-+]\d{4})(\d{3})/s) { # (EEEkit) + # $alt = $1 + 0; (doesn't look right for my sample, but the Ambarella A12 text has this) + $spd = $2 + 0; + } + } + # extract accelerometer data (ref PH) + if ($buf2 =~ /^.{65}(([-+]\d{3})([-+]\d{3})([-+]\d{3})([-+]\d{3})*)/s) { + $_ = $1; + @acc = ($2/100, $3/100, $4/100); + s/([-+])/ $1/g; s/^ //; + push @xtra, AccelerometerData => $_; + } elsif ($buf2 =~ /^.{173}([-+]\d{3})([-+]\d{3})([-+]\d{3})/s) { # (Azdome) + # (Adzome may contain acc and date/time/label even if GPS doesn't exist) + @acc = ($1/100, $2/100, $3/100); + if (not defined $yr and $buf2 =~ /^.{8}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2}).(.{15})/s) { + ($yr,$mon,$day,$hr,$min,$sec,$lbl) = ($1,$2,$3,$4,$5,$6,$7); + } + } + if (defined $lbl) { + $lbl =~ s/\0.*//s; $lbl =~ s/\s+$//; # truncate at null and remove trailing spaces + push @xtra, UserLabel => $lbl if length $lbl; + } + + } elsif ($$dataPt =~ /^.{52}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/s) { + + $debug and $et->FoundTag(GPSType => '1B'); + # decode NMEA-format GPS data (NextBase 512GW dashcam, ref PH) + # header looks like this in my sample: + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 40 01 00 00 [....freeGPS @...] + # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + push @xtra, CameraDateTime => "$1:$2:$3 $4:$5:$6"; + if ($$dataPt =~ /\$[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d+\.\d+),([NS]),(\d+\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/s) { + ($lat,$latRef,$lon,$lonRef) = ($5,$6,$7,$8); + $yr = $13 + ($13 >= 70 ? 1900 : 2000); + ($mon,$day,$hr,$min,$sec) = ($12,$11,$1,$2,$3); + $spd = $9 * $knotsToKph if length $9; + $trk = $10 if length $10; + } + if ($$dataPt =~ /\$[A-Z]{2}GGA,(\d{2})(\d{2})(\d+(\.\d*)?),(\d+\.\d+),([NS]),(\d+\.\d+),([EW]),[1-6]?,(\d+)?,(\.\d+|\d+\.?\d*)?,(-?\d+\.?\d*)?,M?/s) { + ($hr,$min,$sec,$lat,$latRef,$lon,$lonRef) = ($1,$2,$3,$5,$6,$7,$8) unless defined $yr; + $alt = $11; + unshift @xtra, GPSSatellites => $9; + unshift @xtra, GPSDOP => $10; + } + if (defined $lat) { + # extract accelerometer readings if GPS was valid + @acc = unpack('x68V3', $$dataPt); + # change to signed integer and divide by 256 + map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc; + } + + } elsif ($$dataPt =~ /^.{37}\0\0\0A([NS])([EW])/s) { + + $debug and $et->FoundTag(GPSType => '1C'); + # decode freeGPS from ViofoA119v3 dashcam (similar to Novatek GPS format) + # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....] + # 0010: 05 00 00 00 2f 00 00 00 03 00 00 00 13 00 00 00 [..../...........] + # 0020: 09 00 00 00 1b 00 00 00 41 4e 57 00 25 d1 99 45 [........ANW.%..E] + # 0030: f1 47 40 46 66 66 d2 41 85 eb 83 41 00 00 00 00 [.G@Fff.A...A....] + ($latRef, $lonRef) = ($1, $2); + ($hr,$min,$sec,$yr,$mon,$day) = unpack('x16V6', $$dataPt); + if ($yr < 2000) { + $yr += 2000; + } else { + # Kenwood dashcam sometimes stores absolute year and local time + # (but sometimes year since 2000 and UTC time in same video!) + require Time::Local; + my $time = Image::ExifTool::TimeLocal($sec,$min,$hr,$day,$mon-1,$yr); + ($sec,$min,$hr,$day,$mon,$yr) = gmtime($time); + $yr += 1900; + ++$mon; + $et->WarnOnce('Converting GPSDateTime to UTC based on local time zone',1); + } + SetByteOrder('II'); + $lat = GetFloat($dataPt, 0x2c); + $lon = GetFloat($dataPt, 0x30); + $spd = GetFloat($dataPt, 0x34) * $knotsToKph; # (convert knots to km/h) + $trk = GetFloat($dataPt, 0x38); + # (may be all zeros or int16u counting from 1 to 6 if not valid) + my $tmp = substr($$dataPt, 60, 12); + if ($tmp ne "\0\0\0\0\0\0\0\0\0\0\0\0" and $tmp ne "\x01\0\x02\0\x03\0\x04\0\x05\0\x06\0") { + @acc = unpack('V3', $tmp); + map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc; + } + SetByteOrder('MM'); + + } elsif ($$dataPt =~ /^.{21}\0\0\0A([NS])([EW])/s) { + + $debug and $et->FoundTag(GPSType => '1D'); + # also decode 'gpmd' chunk from Kingslim D4 dashcam videos + # 0000: 0a 00 00 00 0b 00 00 00 07 00 00 00 e5 07 00 00 [................] + # 0010: 06 00 00 00 03 00 00 00 41 4e 57 31 91 52 83 45 [........ANW1.R.E] + # 0020: 15 70 fe c5 29 5c c3 41 ae c7 af 42 00 00 d1 be [.p..)\.A...B....] + # 0030: 00 00 80 3b 00 00 2c 3e 00 00 00 00 00 00 00 00 [...;..,>........] + # 0040: 00 00 00 00 00 00 00 00 00 00 00 00 26 26 26 26 [............&&&&] + # 0050: 4c 49 47 4f 47 50 53 49 4e 46 4f 00 00 00 00 05 [LIGOGPSINFO.....] + # 0060: 01 00 00 00 23 23 23 23 75 00 00 00 c0 22 20 20 [....####u...." ] + # 0070: 20 f0 12 10 12 21 e5 0e 10 12 2f 90 10 13 01 f2 [ ....!..../.....] + ($latRef, $lonRef) = ($1, $2); + ($hr,$min,$sec,$yr,$mon,$day) = unpack("V6", $$dataPt); + SetByteOrder('II'); + # lat/lon aren't decoded properly, but spd,trk,acc are + $lat = GetFloat($dataPt, 0x1c); + $lon = GetFloat($dataPt, 0x20); + $et->VPrint(0, sprintf("Raw lat/lon = %.9f %.9f\n", $lat, $lon)); + $et->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong'); + $lat = abs $lat; + $lon = abs $lon; + $spd = GetFloat($dataPt, 0x24) * $knotsToKph; # (convert knots to km/h) + $trk = GetFloat($dataPt, 0x28); + $acc[0] = GetFloat($dataPt, 0x2c); + $acc[1] = GetFloat($dataPt, 0x30); + $acc[2] = GetFloat($dataPt, 0x34); + SetByteOrder('MM'); + + } elsif ($$dataPt =~ /^.{60}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s) { + + $debug and $et->FoundTag(GPSType => '1E'); + # decode freeGPS from Akaso dashcam + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 60 00 00 00 [....freeGPS `...] + # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............] + # 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........] + # 0030: 12 00 00 00 2f 00 00 00 19 00 00 00 41 00 00 00 [..../.......A...] + # 0040: 13 b3 ca 44 4e 00 00 00 29 92 fb 45 45 00 00 00 [...DN...)..EE...] + # 0050: d9 ee b4 41 ec d1 d3 42 e4 07 00 00 01 00 00 00 [...A...B........] + # 0060: 0c 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 [................] + ($latRef, $lonRef) = ($1, $2); + ($hr, $min, $sec, $yr, $mon, $day) = unpack('x48V3x28V3', $$dataPt); + SetByteOrder('II'); + $lat = GetFloat($dataPt, 0x40); + $lon = GetFloat($dataPt, 0x48); + $spd = GetFloat($dataPt, 0x50); + $trk = GetFloat($dataPt, 0x54) + 180; # (why is this off by 180?) + $trk -= 360 if $trk >= 360; + SetByteOrder('MM'); + + } elsif ($$dataPt =~ /^.{60}4W`b]S</s and length($$dataPt) >= 140) { + + $debug and $et->FoundTag(GPSType => '1F'); + # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 01 00 00 [..@.freeGPS ....] + # 0010: 5a 58 53 42 4e 58 59 53 00 00 00 00 00 00 00 00 [ZXSBNXYS........] + # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0030: 00 00 00 00 00 00 00 00 00 00 00 00 34 57 60 62 [............4W`b] + # 0040: 5d 53 3c 41 44 45 41 41 42 3e 40 40 3c 51 3c 45 []S<ADEAAB>@@<Q<E] + # 0050: 41 40 43 3e 41 47 49 48 44 3c 5e 3c 40 41 46 43 [A@C>AGIHD<^<@AFC] + # 0060: 42 3e 49 49 40 42 45 3c 55 3c 45 47 3e 45 43 41 [B>II@BE<U<EG>ECA] + # decipher $GPRMC by subtracting 16 from each character value + $_ = pack 'C*', map { $_>=16 and $_-=16 } unpack('x60C80', $$dataPt); + return 0 unless /[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d*?\d{1,2}\.\d+),([NS]),(\d*?\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/; + ($yr,$mon,$day,$hr,$min,$sec,$lat,$latRef,$lon,$lonRef) = ($13,$12,$11,$1,$2,$3,$5,$6,$7,$8); + $yr += ($yr >= 70 ? 1900 : 2000); + $spd = $9 * $knotsToKph if length $9; + $trk = $10 if length $10; + + } elsif ($$dataPt =~ /^.{64}[\x01-\x0c]\0{3}[\x01-\x1f]\0{3}A[NS][EW]\0{5}/s) { + + $debug and $et->FoundTag(GPSType => '1G'); + # Akaso V1 dascham + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...] + # 0010: 59 6e 64 41 6b 61 73 6f 43 61 72 00 00 00 00 00 [YndAkasoCar.....] + # 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........] + # 0030: 0e 00 00 00 27 00 00 00 2c 00 00 00 e3 07 00 00 [....'...,.......] + # 0040: 05 00 00 00 1d 00 00 00 41 4e 45 00 00 00 00 00 [........ANE.....] + # 0050: f1 4e 3e 3d 90 df ca 40 e3 50 bf 0b 0b 31 a0 40 [.N>=...@.P...1.@] + # 0060: 4b dc c8 41 9a 79 a7 43 34 58 43 31 4f 37 31 35 [K..A.y.C4XC1O715] + # 0070: 35 31 32 36 36 35 37 35 59 4e 44 53 0d e7 cc f9 [51266575YNDS....] + # 0080: 00 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 [................] + # Redtiger F7N dashcam + # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 01 00 00 [..@.freeGPS ....] + # 0010: 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0020: 01 00 00 00 b0 56 50 01 7b 18 68 45 17 02 3f 46 [.....VP.{.hE..?F] + # 0030: 13 00 00 00 01 00 00 00 06 00 00 00 15 00 00 00 [................] + # 0040: 0c 00 00 00 1c 00 00 00 41 4e 57 00 00 00 00 00 [........ANW.....] + # 0050: 80 d4 26 4e 36 11 b5 40 74 b5 15 7b cd 7b f3 40 [..&N6..@t..{.{.@] + # 0060: 0a d7 a3 3d cd 4c 4e 43 38 34 37 41 45 48 31 36 [...=.LNC847AEH16] + # 0070: 33 36 30 38 32 34 35 37 59 53 4b 4a 01 00 00 00 [36082457YSKJ....] + # 0080: ec ff ff ff 00 00 00 00 0e 00 00 00 01 00 00 00 [................] + # 0090: 0a 00 00 00 e5 07 00 00 0c 00 00 00 1c 00 00 00 [................] + ($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef) = + unpack('x48V6a1a1a1x1', $$dataPt); + # ignore invalid fixes + return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and + ($lonRef eq 'E' or $lonRef eq 'W'); + + $et->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong'); + # (see https://exiftool.org/forum/index.php?topic=11320.0) + + SetByteOrder('II'); + + $spd = GetFloat($dataPt, 0x60); + $trk = GetFloat($dataPt, 0x64) + 180; # (why is this off by 180?) + $lat = GetDouble($dataPt, 0x50); # latitude is here, but encrypted somehow + $lon = GetDouble($dataPt, 0x58); # longitude is here, but encrypted somehow + $ddd = 1; # don't convert until we know what the format is + + SetByteOrder('MM'); + #my $serialNum = substr($$dataPt, 0x68, 20); + + } elsif ($$dataPt =~ /^.{12}\xac\0\0\0.{44}(.{72})/s) { + + $debug and $et->FoundTag(GPSType => '1H'); + # EACHPAI dash cam + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 ac 00 00 00 [....freeGPS ....] + # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0030: 00 00 00 00 00 00 00 00 00 00 00 00 34 57 60 62 [............4W`b] + # 0040: 5d 53 3c 41 47 45 45 42 42 3e 40 40 40 3c 51 3c []S<AGEEBB>@@@<Q<] + # 0050: 44 42 44 40 3e 48 46 43 45 3c 5e 3c 40 48 43 41 [DBD@>HFCE<^<@HCA] + # 0060: 42 3e 46 42 47 48 3c 67 3c 40 3e 40 42 3c 43 3e [B>FBGH<g<@>@B<C>] + # 0070: 43 41 3c 40 42 40 46 42 40 3c 3c 3c 51 3a 47 46 [CA<@B@FB@<<<Q:GF] + # 0080: 00 2a 36 35 00 00 00 00 00 00 00 00 00 00 00 00 [.*65............] + + $et->WarnOnce("Can't yet decrypt EACHPAI timed GPS", 1); + # (see https://exiftool.org/forum/index.php?topic=5095.msg61266#msg61266) + return 1; + + my $time = pack 'C*', map { $_ ^= 0 } unpack 'C*', $1; + # bytes 7-12 are the timestamp in ASCII HHMMSS after xor-ing with 0x70 + substr($time,7,6) = pack 'C*', map { $_ ^= 0x70 } unpack 'C*', substr($time,7,6); + # (other values are currently unknown) + + } elsif ($$dataPt =~ /^.{64}A([NS])([EW])\0/s) { + + $debug and $et->FoundTag(GPSType => '1I'); + # Vantrue S1 dashcam + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...] + # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0030: 68 6f 72 73 6f 6e 74 65 63 68 00 00 00 00 00 00 [horsontech......] + # 0040: 41 4e 45 00 15 00 00 00 07 00 00 00 02 00 00 00 [ANE.............] + # 0050: 03 00 00 00 35 00 00 00 05 00 00 00 4f 74 4c 44 [....5.......OtLD] + # 0060: e2 77 a0 45 89 c1 98 42 71 bd ac 42 02 ab 0d 43 [.w.E...Bq..B...C] + # 0070: 05 00 00 00 7f 00 00 00 07 01 00 00 00 00 00 00 [................] + ($latRef, $lonRef) = ($1, $2); + ($yr,$mon,$day,$hr,$min,$sec,@acc) = unpack('x68V6x20V3', $$dataPt); + return 0 unless $mon>=1 and $mon<=12 and $day>=1 and $day<=31; + $yr += 2000 if $yr < 2000; + # (not sure about acc scaling) + map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; + SetByteOrder('II'); + $lon = GetFloat($dataPt, 0x5c); + $lat = GetFloat($dataPt, 0x60); + $spd = GetFloat($dataPt, 0x64) * $knotsToKph; + $trk = GetFloat($dataPt, 0x68); + $alt = GetFloat($dataPt, 0x6c); + SetByteOrder('MM'); + + } else { + + $debug and $et->FoundTag(GPSType => '1J'); + # decode binary GPS format (Viofo A119S, ref 2) + # header looks like this in my sample: + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...] + # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0030: 10 00 00 00 2d 00 00 00 14 00 00 00 11 00 00 00 [....-...........] + # 0040: 0c 00 00 00 1f 00 00 00 41 4e 45 00 5d 9a a9 45 [........ANE.]..E] + # 0050: ab 1e e5 44 ec 51 f0 40 b8 5e a5 43 00 00 00 00 [...D.Q.@.^.C....] + # (records are same structure as Type 3 Novatek GPS in ProcessFreeGPS2() below) + ($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef,$lat,$lon,$spd,$trk) = + unpack('x48V6a1a1a1x1V4', $$dataPt); + # ignore invalid fixes + return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and + ($lonRef eq 'E' or $lonRef eq 'W'); + ($lat,$lon,$spd,$trk) = unpack 'f*', pack 'L*', $lat, $lon, $spd, $trk; + # lat/lon also stored as doubles by Transcend Driver Pro 230 (ref PH) + SetByteOrder('II'); + my ($lat2, $lon2, $alt2) = ( + GetDouble($dataPt, 0x70), + GetDouble($dataPt, 0x80), + # GetDouble($dataPt, 0x98), # (don't know what this is) + GetDouble($dataPt,0xa0), + # GetDouble($dataPt,0xa8)) # (don't know what this is) + ); + if (abs($lat2-$lat) < 0.001 and abs($lon2-$lon) < 0.001) { + $lat = $lat2; + $lon = $lon2; + $alt = $alt2; + } + SetByteOrder('MM'); + $yr += 2000 if $yr < 2000; + $spd *= $knotsToKph; # convert speed to km/h + # ($trk is not confirmed; may be GPSImageDirection, ref PH) + } +# +# save tag values extracted by above code +# + FoundSomething($et, $tagTbl, $$dirInfo{SampleTime}, $$dirInfo{SampleDuration}); + $sec = '0' . $sec unless $sec =~ /^\d{2}/; # pad integer part of seconds to 2 digits + if (defined $yr) { + my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$yr,$mon,$day,$hr,$min,$sec); + $et->HandleTag($tagTbl, GPSDateTime => $time); + } elsif (defined $hr) { + my $time = sprintf('%.2d:%.2d:%sZ',$hr,$min,$sec); + $et->HandleTag($tagTbl, GPSTimeStamp => $time); + } + if (defined $lat) { + # lat/long are in DDDMM.MMMM format unless $ddd is set + ConvertLatLon($lat, $lon) unless $ddd; + $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1)); + } + $et->HandleTag($tagTbl, GPSAltitude => $alt) if defined $alt; + $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd; + $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk; + while (@xtra) { + my $tag = shift @xtra; + $et->HandleTag($tagTbl, $tag => shift @xtra); + } + $et->HandleTag($tagTbl, Accelerometer => \@acc) if @acc; + return 1; +} + +#------------------------------------------------------------------------------ +# Process "freeGPS " data blocks _not_ referenced by a 'gps ' atom +# Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,DataPos,DirLen}, 2) tagTable ref +# Returns: 1 on success +# Notes: +# - also see ProcessFreeGPS() above +sub ProcessFreeGPS2($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + my ($yr, $mon, $day, $hr, $min, $sec, $pos, @acc); + my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, $ddd, $unk); + + return 0 if $dirLen < 82; # minimum size of block with a single GPS record + + if (substr($$dataPt,0x45,3) eq 'ATC') { + + $debug and $et->FoundTag(GPSType => '2A'); + # header looks like this: (sample 1) + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 38 06 00 00 [....freeGPS 8...] + # 0010: 49 51 53 32 30 31 33 30 33 30 36 42 00 00 00 00 [IQS20130306B....] + # 0020: 4d 61 79 20 31 35 20 32 30 31 35 2c 20 31 39 3a [May 15 2015, 19:] + # (sample 2) + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 06 00 00 [....freeGPS L...] + # 0010: 32 30 31 33 30 33 31 38 2e 30 31 00 00 00 00 00 [20130318.01.....] + # 0020: 4d 61 72 20 31 38 20 32 30 31 33 2c 20 31 34 3a [Mar 18 2013, 14:] + + my ($recPos, $lastRecPos, $foundNew); + my $verbose = $et->Options('Verbose'); + my $dataPos = $$dirInfo{DataPos}; + my $then = $$et{FreeGPS2}{Then}; + $then or $then = $$et{FreeGPS2}{Then} = [ (0) x 6 ]; + # Loop through records in the ATC-type GPS block until we find the most recent. + # If we have already found one, then we only need to check the first record + # (in case the buffer wrapped around), and the record after the position of + # the last record we found, because the others will be old. Odd, but this + # is the way it is done... I have only seen one new 52-byte record in the + # entire 32 kB block, but the entire device ring buffer (containing 30 + # entries in my samples) is stored every time. The code below allows for + # the possibility of missing blocks and multiple new records in a single + # block, but I have never seen this. Note that there may be some earlier + # GPS records at the end of the first block that we will miss decoding, but + # these should (I believe) be before the start of the video +ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) { + + my $a = substr($$dataPt, $recPos, 52); # isolate a single record + # decrypt record + my @a = unpack('C*', $a); + my ($key1, $key2) = @a[0x14, 0x1c]; + $a[$_] ^= $key1 foreach 0x00..0x14, 0x18..0x1b; + $a[$_] ^= $key2 foreach 0x1c, 0x20..0x32; + my $b = pack 'C*', @a; + # unpack and validate date/time + my @now = unpack 'x13C3x28vC2', $b; # (H-1,M,S,Y,m,d) + $now[0] = ($now[0] + 1) & 0xff; # increment hour + my $i; + for ($i=0; $i<@dateMax; ++$i) { + next if $now[$i] <= $dateMax[$i]; + $et->WarnOnce('Invalid GPS date/time'); + next ATCRec; # ignore this record + } + # look for next ATC record in temporal sequence + foreach $i (3..5, 0..2) { + if ($now[$i] < $$then[$i]) { + last ATCRec if $foundNew; + last; + } + next if $now[$i] == $$then[$i]; + # we found a more recent record -- extract it and remember its location + if ($verbose) { + $et->VPrint(2, " + [encrypted GPS record]\n"); + $et->VerboseDump(\$a, DataPos => $dataPos + $recPos); + $et->VPrint(2, " + [decrypted GPS record]\n"); + $et->VerboseDump(\$b); + #my @v = unpack 'H8VVC4V!CA3V!CA3VvvV!vCCCCH4', $b; + #$et->VPrint(2, " + [unpacked: @v]\n"); + # values unpacked above (ref PH): + # 0) 0x00 4 bytes - byte 0=1, 1=counts to 255, 2=record index, 3=0 (ref 3) + # 1) 0x04 4 bytes - int32u: bits 0-4=day, 5-8=mon, 9-19=year (ref 3) + # 2) 0x08 4 bytes - int32u: bits 0-5=sec, 6-11=min, 12-16=hour (ref 3) + # 3) 0x0c 1 byte - seen values of 0,1,2 - GPS status maybe? + # 4) 0x0d 1 byte - hour minus 1 + # 5) 0x0e 1 byte - minute + # 6) 0x0f 1 byte - second + # 7) 0x10 4 bytes - int32s latitude * 1e7 + # 8) 0x14 1 byte - always 0 (used for decryption) + # 9) 0x15 3 bytes - always "ATC" + # 10) 0x18 4 bytes - int32s longitude * 1e7 + # 11) 0x1c 1 byte - always 0 (used for decryption) + # 12) 0x1d 3 bytes - always "001" + # 13) 0x20 4 bytes - int32s speed * 100 (m/s) + # 14) 0x24 2 bytes - int16u heading * 100 (-180 to 180 deg) + # 15) 0x26 2 bytes - always zero + # 16) 0x28 4 bytes - int32s altitude * 1000 (ref 3) + # 17) 0x2c 2 bytes - int16u year + # 18) 0x2e 1 byte - month + # 19) 0x2f 1 byte - day + # 20) 0x30 1 byte - unknown + # 21) 0x31 1 byte - always zero + # 22) 0x32 2 bytes - checksum ? + } + @$then = @now; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $trk = Get16s(\$b, 0x24) / 100; + $trk += 360 if $trk < 0; + my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', @now[3..5, 0..2]); + $et->HandleTag($tagTbl, GPSDateTime => $time); + $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$b, 0x10) / 1e7); + $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$b, 0x18) / 1e7); + $et->HandleTag($tagTbl, GPSSpeed => Get32s(\$b, 0x20) / 100 * $mpsToKph); + $et->HandleTag($tagTbl, GPSTrack => $trk); + $et->HandleTag($tagTbl, GPSAltitude => Get32s(\$b, 0x28) / 1000); + $lastRecPos = $recPos; + $foundNew = 1; + # don't skip to location of previous recent record in ring buffer + # since we found a more recent record here + delete $$et{FreeGPS2}{RecentRecPos}; + last; + } + # skip older records + my $recentRecPos = $$et{FreeGPS2}{RecentRecPos}; + $recPos = $recentRecPos if $recentRecPos and $recPos < $recentRecPos; + } + # save position of most recent record (needed when parsing the next freeGPS block) + $$et{FreeGPS2}{RecentRecPos} = $lastRecPos; + return 1; + + } elsif ($$dataPt =~ /^.{60}A\0.{10}([NS])\0.{14}([EW])\0/s) { + + $debug and $et->FoundTag(GPSType => '2B'); + # header looks like this in my sample: + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 08 01 00 00 [....freeGPS ....] + # 0010: 32 30 31 33 30 38 31 35 2e 30 31 00 00 00 00 00 [20130815.01.....] + # 0020: 4a 75 6e 20 31 30 20 32 30 31 37 2c 20 31 34 3a [Jun 10 2017, 14:] + + # Type 2 (ref PH): + # 0x30 - int32u hour + # 0x34 - int32u minute + # 0x38 - int32u second + # 0x3c - int32u GPS status ('A' or 'V') + # 0x40 - double latitude (DDMM.MMMMMM) + # 0x48 - int32u latitude ref ('N' or 'S') + # 0x50 - double longitude (DDMM.MMMMMM) + # 0x58 - int32u longitude ref ('E' or 'W') + # 0x60 - double speed (knots) + # 0x68 - double heading (deg) + # 0x70 - int32u year - 2000 + # 0x74 - int32u month + # 0x78 - int32u day + # 0x7c - int32s[3] accelerometer * 1000 + ($latRef, $lonRef) = ($1, $2); + ($hr,$min,$sec,$yr,$mon,$day,@acc) = unpack('x48V3x52V6', $$dataPt); + map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; + $lat = GetDouble($dataPt, 0x40); + $lon = GetDouble($dataPt, 0x50); + $spd = GetDouble($dataPt, 0x60) * $knotsToKph; + $trk = GetDouble($dataPt, 0x68); + + } elsif ($$dataPt =~ /^.{72}A([NS])([EW])/s) { + + # Type 3 (Novatek GPS, ref 2): (in case it wasn't decoded via 'gps ' atom) + # 0x30 - int32u hour + # 0x34 - int32u minute + # 0x38 - int32u second + # 0x3c - int32u year - 2000 + # 0x40 - int32u month + # 0x44 - int32u day + # 0x48 - int8u GPS status ('A' or 'V') + # 0x49 - int8u latitude ref ('N' or 'S') + # 0x4a - int8u longitude ref ('E' or 'W') + # 0x4b - 0 + # 0x4c - float latitude (DDMM.MMMMMM) + # 0x50 - float longitude (DDMM.MMMMMM) + # 0x54 - float speed (knots) + # 0x58 - float heading (deg) + # Type 3b, same as above for 0x30-0x4a (ref PH) + # 0x4c - int32s latitude (decimal degrees * 1e7) + # 0x50 - int32s longitude (decimal degrees * 1e7) + # 0x54 - int32s speed (m/s * 100) + # 0x58 - float altitude (m * 1000, NC) + ($latRef, $lonRef) = ($1, $2); + ($hr,$min,$sec,$yr,$mon,$day) = unpack('x48V6', $$dataPt); + if (substr($$dataPt, 16, 3) eq 'IQS') { + $debug and $et->FoundTag(GPSType => '2C'); + # Type 3b (ref PH) + # header looks like this in my sample: + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...] + # 0010: 49 51 53 5f 41 37 5f 32 30 31 35 30 34 31 37 00 [IQS_A7_20150417.] + # 0020: 4d 61 72 20 32 39 20 32 30 31 37 2c 20 31 36 3a [Mar 29 2017, 16:] + $ddd = 1; + $lat = abs Get32s($dataPt, 0x4c) / 1e7; + $lon = abs Get32s($dataPt, 0x50) / 1e7; + $spd = Get32s($dataPt, 0x54) / 100 * $mpsToKph; + $alt = GetFloat($dataPt, 0x58) / 1000; # (NC) + + } else { + $debug and $et->FoundTag(GPSType => '2D'); + # Type 3 (ref 2) + # (no sample with this format) + $lat = GetFloat($dataPt, 0x4c); + $lon = GetFloat($dataPt, 0x50); + $spd = GetFloat($dataPt, 0x54) * $knotsToKph; + $trk = GetFloat($dataPt, 0x58); + } + + } elsif ($$dataPt =~ /^.{60}A\0.{6}([NS])\0.{6}([EW])\0/s and $dirLen >= 112) { + + $debug and $et->FoundTag(GPSType => '2E'); + # header looks like this in my sample (unknown dashcam, "Anticlock 2 2020_1125_1455_007.MOV"): + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 68 00 00 00 [....freeGPS h...] + # 0010: 32 30 31 33 30 33 32 35 41 00 00 00 00 00 00 00 [20130325A.......] + # 0020: 41 70 72 20 20 36 20 32 30 31 36 2c 20 31 36 3a [Apr 6 2016, 16:] + # 0030: 0e 00 00 00 38 00 00 00 22 00 00 00 41 00 00 00 [....8..."...A...] + # 0040: 8a 63 24 45 53 00 00 00 9f e6 42 45 45 00 00 00 [.c$ES.....BEE...] + # 0050: 59 c0 04 3f 52 b8 42 41 14 00 00 00 0b 00 00 00 [Y..?R.BA........] + # 0060: 19 00 00 00 06 00 00 00 05 00 00 00 f6 ff ff ff [................] + # 0070: 03 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 [................] + ($latRef, $lonRef) = ($1, $2); + ($hr,$min,$sec,$yr,$mon,$day,@acc) = unpack('x48V3x28V6',$$dataPt); + map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; # (NC) + $lat = GetFloat($dataPt, 0x40); + $lon = GetFloat($dataPt, 0x48); + $spd = GetFloat($dataPt, 0x50); + $trk = GetFloat($dataPt, 0x54); + + } elsif ($$dataPt =~ /^.{16}A([NS])([EW])\0/s) { + + $debug and $et->FoundTag(GPSType => '2F'); + # INNOVV MP4 video (same format as INNOVV TS) + while ($$dataPt =~ /(A[NS][EW]\0.{28})/g) { + my $dat = $1; + $lat = abs(GetFloat(\$dat, 4)); # (abs just to be safe) + $lon = abs(GetFloat(\$dat, 8)); # (abs just to be safe) + $spd = GetFloat(\$dat, 12) * $knotsToKph; + $trk = GetFloat(\$dat, 16); + @acc = unpack('x20V3', $dat); + map { $_ = $_ - 4294967296 if $_ >= 0x80000000 } @acc; + ConvertLatLon($lat, $lon); + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $et->HandleTag($tagTbl, GPSLatitude => $lat * (substr($dat,1,1) eq 'S' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSLongitude => $lon * (substr($dat,2,1) eq 'W' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSSpeed => $spd); + $et->HandleTag($tagTbl, GPSTrack => $trk); + $et->HandleTag($tagTbl, Accelerometer => "@acc"); + } + return 1; + + } elsif ($$dataPt =~ /^.{28}A.{11}([NS]).{15}([EW])/s) { + + $debug and $et->FoundTag(GPSType => '2G'); + # Vantrue N4 dashcam + # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....] + # 0010: 0d 00 00 00 16 00 00 00 1e 00 00 00 41 00 00 00 [............A...] + # 0020: 2c b7 b4 1a 5a 71 b2 40 4e 00 00 00 00 00 00 00 [,...Zq.@N.......] + # 0030: fb ae 08 fe 77 f6 89 40 45 00 00 00 00 00 00 00 [....w..@E.......] + # 0040: be 9f 1a 2f dd 84 36 40 5c 8f c2 f5 28 fc 68 40 [.../..6@\...(.h@] + # 0050: 16 00 00 00 0c 00 00 00 0e 00 00 00 f2 fb ff ff [................] + # 0060: 42 00 00 00 02 00 00 00 20 24 47 4e 52 4d 43 2c [B....... $GNRMC,] + # 0070: 31 33 32 32 33 30 2e 30 30 30 2c 41 2c 34 37 32 [132230.000,A,472] + # 0080: 31 2e 33 35 31 39 37 2c 4e 2c 30 30 38 33 30 2e [1.35197,N,00830.] + # 0090: 38 30 38 35 39 2c 45 2c 32 32 2e 35 31 39 2c 31 [80859,E,22.519,1] + # 00a0: 39 39 2e 38 38 2c 31 34 31 32 32 32 2c 2c 2c 41 [99.88,141222,,,A] + # 00b0: 2a 37 35 0d 0a 00 00 00 00 00 00 00 00 00 00 00 [*75.............] + ($latRef, $lonRef) = ($1, $2); + ($hr,$min,$sec,$yr,$mon,$day,@acc) = unpack('x16V3x52V3V3',$$dataPt); + $lat = abs(GetDouble($dataPt, 32)); # (abs just to be safe) + $lon = abs(GetDouble($dataPt, 48)); # (abs just to be safe) + $spd = GetDouble($dataPt, 64) * $knotsToKph; + $trk = GetDouble($dataPt, 72); + map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; # (NC) + # (not necessary to read RMC sentence because we already have it all) + + } else { + + $debug and $et->FoundTag(GPSType => '2H'); + # (look for binary GPS as stored by NextBase 512G, ref PH) + # header looks like this in my sample: + # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 01 00 00 [....freeGPS x...] + # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............] + # 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........] + + # followed by a number of 32-byte records in this format (big endian!): + # 0x30 - int16u unknown (seen: 0x24 0x53 = "$S") + # 0x32 - int16u speed (m/s * 100) + # 0x34 - int16s heading (deg * 100) (or GPSImgDirection?) + # 0x36 - int16u year + # 0x38 - int8u month + # 0x39 - int8u day + # 0x3a - int8u hour + # 0x3b - int8u min + # 0x3c - int16u sec * 10 + # 0x3e - int8u unknown (seen: 2) + # 0x3f - int32s latitude (decimal degrees * 1e7) + # 0x43 - int32s longitude (decimal degrees * 1e7) + # 0x47 - int8u unknown (seen: 16) + # 0x48-0x4f - all zero + for ($pos=0x32; ; ) { + ($spd,$trk,$yr,$mon,$day,$hr,$min,$sec,$unk,$lat,$lon) = unpack "x${pos}nnnCCCCnCNN", $$dataPt; + # validate record using date/time + last if $yr < 2000 or $yr > 2200 or + $mon < 1 or $mon > 12 or + $day < 1 or $day > 31 or + $hr > 59 or $min > 59 or $sec > 600; + # change lat/lon to signed integer and divide by 1e7 + map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1e7 } $lat, $lon; + $trk -= 0x10000 if $trk >= 0x8000; # make it signed + $trk /= 100; + $trk += 360 if $trk < 0; + my $time = sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%04.1fZ", $yr, $mon, $day, $hr, $min, $sec/10); + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $et->HandleTag($tagTbl, GPSDateTime => $time); + $et->HandleTag($tagTbl, GPSLatitude => $lat); + $et->HandleTag($tagTbl, GPSLongitude => $lon); + $et->HandleTag($tagTbl, GPSSpeed => $spd / 100 * $mpsToKph); + $et->HandleTag($tagTbl, GPSTrack => $trk); + last if $pos += 0x20 > length($$dataPt) - 0x1e; + } + return $$et{DOC_NUM} ? 1 : 0; # return 0 if nothing extracted + } +# +# save tag values extracted by above code +# + return 0 if $mon < 1 or $mon > 12; # quick sanity check + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $yr += 2000 if $yr < 2000; + my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $yr, $mon, $day, $hr, $min, $sec); + # convert from DDMM.MMMMMM to DD.DDDDDD format if necessary + ConvertLatLon($lat, $lon) unless $ddd; + $et->HandleTag($tagTbl, GPSDateTime => $time); + $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd; # (now in km/h) + $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk; + $et->HandleTag($tagTbl, GPSAltitude => $alt) if defined $alt; + $et->HandleTag($tagTbl, Accelerometer => "@acc") if @acc; + return 1; +} + + +#------------------------------------------------------------------------------ +# Extract embedded information referenced from a track +# Inputs: 0) ExifTool ref, 1) tag name, 2) data ref +sub ParseTag($$$) +{ + local $_; + my ($et, $tag, $dataPt) = @_; + my $dataLen = length $$dataPt; + + if ($tag eq 'stsz' or $tag eq 'stz2' and $dataLen > 12) { + # read the sample sizes + my ($sz, $num) = unpack('x4N2', $$dataPt); + my $size = $$et{ee}{size} = [ ]; + if ($tag eq 'stsz') { + if ($sz == 0) { + @$size = ReadValue($dataPt, 12, 'int32u', $num, $dataLen-12); + } else { + @$size = ($sz) x $num; + } + } else { + $sz &= 0xff; + if ($sz == 4) { + my @tmp = ReadValue($dataPt, 12, 'int8u', int(($num+1)/2), $dataLen-12); + foreach (@tmp) { + push @$size, $_ >> 4; + push @$size, $_ & 0xff; + } + } elsif ($sz == 8 || $sz == 16) { + @$size = ReadValue($dataPt, 12, "int${sz}u", $num, $dataLen-12); + } + } + } elsif ($tag eq 'stco' or $tag eq 'co64' and $dataLen > 8) { + # read the chunk offsets + my $num = unpack('x4N', $$dataPt); + my $stco = $$et{ee}{stco} = [ ]; + @$stco = ReadValue($dataPt, 8, $tag eq 'stco' ? 'int32u' : 'int64u', $num, $dataLen-8); + } elsif ($tag eq 'stsc' and $dataLen > 8) { + # read the sample-to-chunk box + my $num = unpack('x4N', $$dataPt); + if ($dataLen >= 8 + $num * 12) { + my ($i, @stsc); + for ($i=0; $i<$num; ++$i) { + # list of (first-chunk, samples-per-chunk, sample-description-index) + push @stsc, [ unpack('x'.(8+$i*12).'N3', $$dataPt) ]; + } + $$et{ee}{stsc} = \@stsc; + } + } elsif ($tag eq 'stts' and $dataLen > 8) { + # read the time-to-sample box + my $num = unpack('x4N', $$dataPt); + if ($dataLen >= 8 + $num * 8) { + $$et{ee}{stts} = [ unpack('x8N'.($num*2), $$dataPt) ]; + } + } elsif ($tag eq 'avcC') { + # read the AVC compressor configuration + $$et{ee}{avcC} = $$dataPt if $dataLen >= 7; # (minimum length is 7) + } elsif ($tag eq 'JPEG') { + $$et{ee}{JPEG} = $$dataPt; + } elsif ($tag eq 'gps ' and $dataLen > 8) { + # decode Novatek 'gps ' box (ref 2) + my $num = Get32u($dataPt, 4); + $num = int(($dataLen - 8) / 8) if $num * 8 + 8 > $dataLen; + my $start = $$et{ee}{start} = [ ]; + my $size = $$et{ee}{size} = [ ]; + my $i; + for ($i=0; $i<$num; ++$i) { + push @$start, Get32u($dataPt, 8 + $i * 8); + push @$size, Get32u($dataPt, 12 + $i * 8); + } + $$et{HandlerType} = $tag; # fake handler type + ProcessSamples($et); # we have all we need to process sample data now + } elsif ($tag eq 'GPS ') { + my $pos = 0; + my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + SetByteOrder('II'); + while ($pos + 36 < $dataLen) { + my $dat = substr($$dataPt, $pos, 36); + last if $dat eq "\x0" x 36; + my @a = unpack 'VVVVaVaV', $dat; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + # 0=1, 1=1, 2=secs, 3=? + SetGPSDateTime($et, $tagTbl, $a[2]); + my $lat = $a[5] / 1e3; + my $lon = $a[7] / 1e3; + ConvertLatLon($lat, $lon); + $lat = -abs($lat) if $a[4] eq 'S'; + $lon = -abs($lon) if $a[6] eq 'W'; + $et->HandleTag($tagTbl, GPSLatitude => $lat); + $et->HandleTag($tagTbl, GPSLongitude => $lon); + $et->HandleTag($tagTbl, GPSSpeed => $a[3] / 1e3); + $pos += 36; + } + SetByteOrder('MM'); + delete $$et{DOC_NUM}; + } +} + +#------------------------------------------------------------------------------ +# Process Yuneec 'tx3g' sbtl metadata (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub Process_tx3g($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + return 0 if length $$dataPt < 2; + pos($$dataPt) = 2; # skip 2-byte length word + $et->VerboseDir('tx3g', undef, length($$dataPt)-2); + $et->HandleTag($tagTablePtr, 'Text', substr($$dataPt, 2)); + if ($$dataPt =~ /^..\w{3} (\d{4})-(\d{2})-(\d{2}) (\d{2}:\d{2}:\d{2}) ?([-+])(\d{2}):?(\d{2})$/s) { + $et->HandleTag($tagTablePtr, 'DateTime', "$1:$2:$3 $4$5$6:$7"); + } else { + $et->HandleTag($tagTablePtr, $1, $2) while $$dataPt =~ /(\w+):([^:]*[^:\s])(\s|$)/sg; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process GM 'marl' ctbx metadata (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub Process_marl($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + return 0 if length $$dataPt < 2; + + # 8-byte records: + # byte 0 seems to be tag ID (0=timestamp in sec * 1e7) + # bytes 1-3 seem to be 24-bit signed integer (unknown meaning) + # bytes 4-7 are an int32u value, usually a multiple of 10000 + + $et->WarnOnce("Can't yet decode timed GM data", 1); + # (see https://exiftool.org/forum/index.php?topic=11335.msg61393#msg61393) + return 1; +} + +#------------------------------------------------------------------------------ +# Process QuickTime 'mebx' timed metadata +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# - uses tag ID keys stored in the ExifTool ee data member by a previous call to SaveMetaKeys +sub Process_mebx($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $ee = $$et{ee} or return 0; + return 0 unless $$ee{'keys'}; + my $dataPt = $$dirInfo{DataPt}; + + # parse using information from 'keys' table (eg. Apple iPhone7+ hevc 'Core Media Data Handler') + $et->VerboseDir('mebx', undef, length $$dataPt); + my ($pos, $len); + for ($pos=0; $pos+8<length($$dataPt); $pos+=$len) { + $len = Get32u($dataPt, $pos); + last if $len < 8 or $pos + $len > length $$dataPt; + my $id = substr($$dataPt, $pos+4, 4); + my $info = $$ee{'keys'}{$id}; + if ($info) { + my $tag = $$info{TagID}; + unless ($$tagTbl{$tag}) { + next unless $tag =~ /^[-\w.]+$/; + # create info for tags with reasonable id's + my $name = $tag; + $name =~ s/[-.](.)/\U$1/g; + AddTagToTable($tagTbl, $tag, { Name => ucfirst($name) }); + } + my $val = ReadValue($dataPt, $pos+8, $$info{Format}, undef, $len-8); + $et->HandleTag($tagTbl, $tag, $val, + DataPt => $dataPt, + Base => $$dirInfo{Base}, + Start => $pos + 8, + Size => $len - 8, + ); + } else { + $et->WarnOnce('No key information for mebx ID ' . PrintableTagID($id,1)); + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process QuickTime '3gf' timed metadata (ref PH, Pittasoft Blackvue dashcam) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub Process_3gf($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + my $recLen = 10; # 10-byte record length + $et->VerboseDir('3gf', undef, $dirLen); + if ($dirLen > $recLen and not $et->Options('ExtractEmbedded')) { + $dirLen = $recLen; + EEWarn($et); + } + my $pos; + for ($pos=0; $pos+$recLen<=$dirLen; $pos+=$recLen) { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + my $tc = Get32u($dataPt, $pos); + last if $tc == 0xffffffff; + my ($x, $y, $z) = (Get16s($dataPt, $pos+4)/10, Get16s($dataPt, $pos+6)/10, Get16s($dataPt, $pos+8)/10); + $et->HandleTag($tagTbl, TimeCode => $tc / 1000); + $et->HandleTag($tagTbl, Accelerometer => "$x $y $z"); + } + delete $$et{DOC_NUM}; + return 1; +} + +#------------------------------------------------------------------------------ +# Process DuDuBell M1 dashcam / VSYS M6L 'gps0' atom (ref PH) +# (Lamax S9 dual dashcam also uses 'gps0' atom, but encrypted text format) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub Process_gps0($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + my ($pos, $recLen); + $et->VerboseDir('gps0', undef, $dirLen); + # check for encrypted format written by Lamax S9 dual dashcam + # (similar to Ambarella A12, but in multiple 311-byte records) + if ($$dataPt =~ /^.{2}\xf2\xe1\xf0\xeeTT\x98/s) { + $recLen = 311; + for ($pos=0; $pos+$recLen<=$dirLen; $pos+=$recLen) { + my $dat = substr($$dataPt, $pos, $recLen); + last unless $dat =~ /^.{2}\xf2\xe1\xf0\xeeTT\x98/s; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + Process_text($et, \$dat, $tagTbl); + $pos += $recLen; + } + delete $$et{DOC_NUM}; + return 1; + } + $recLen = 32; # 32-byte record length + SetByteOrder('II'); + if ($dirLen > $recLen and not $et->Options('ExtractEmbedded')) { + $dirLen = $recLen; + EEWarn($et); + } + for ($pos=0; $pos+$recLen<=$dirLen; $pos+=$recLen) { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + # lat/long are in DDDMM.MMMM format + my $lat = GetDouble($dataPt, $pos); + my $lon = GetDouble($dataPt, $pos+8); + next if abs($lat) > 9000 or abs($lon) > 18000; + ConvertLatLon($lat, $lon); + my @a = unpack('C*', substr($$dataPt, $pos+22, 6)); # unpack date/time + $a[0] += 2000; + $et->HandleTag($tagTbl, GPSDateTime => sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ", @a)); + $et->HandleTag($tagTbl, GPSLatitude => $lat); + $et->HandleTag($tagTbl, GPSLongitude => $lon); + $et->HandleTag($tagTbl, GPSSpeed => Get16u($dataPt, $pos+0x14)); + $et->HandleTag($tagTbl, GPSTrack => Get8u($dataPt, $pos+0x1c) * 2); # (NC) + $et->HandleTag($tagTbl, GPSAltitude => Get32s($dataPt, $pos + 0x10)); + # yet to be decoded: + # 0x1d - int8u[3] seen: "1 1 0" + } + delete $$et{DOC_NUM}; + SetByteOrder('MM'); + return 1; +} + +#------------------------------------------------------------------------------ +# Process DuDuBell M1 dashcam / VSYS M6L 'gsen' atom (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub Process_gsen($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + my $recLen = 3; # 3-byte record length + $et->VerboseDir('gsen', undef, $dirLen); + if ($dirLen > $recLen and not $et->Options('ExtractEmbedded')) { + $dirLen = $recLen; + EEWarn($et); + } + my $pos; + for ($pos=0; $pos+$recLen<=$dirLen; $pos+=$recLen) { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + my @acc = map { $_ /= 16 } unpack "x${pos}c3", $$dataPt; + $et->HandleTag($tagTbl, Accelerometer => "@acc"); + # (there are no associated timestamps, but these are sampled at 5 Hz in my test video) + } + delete $$et{DOC_NUM}; + return 1; +} + +#------------------------------------------------------------------------------ +# Process Kenwood drv-a301w dashcam 'udta' atom (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# Sample data: +# 0000: 56 49 44 45 4f 55 55 55 55 55 55 55 55 55 55 55 [VIDEOUUUUUUUUUUU] +# 0010: 55 55 55 55 55 55 55 55 55 55 55 fe fe 32 30 32 [UUUUUUUUUUU..202] +# 0020: 33 30 31 30 37 31 31 31 39 31 34 2e 32 30 32 33 [30107111914.2023] +# 0030: 30 31 30 37 31 31 31 39 31 35 03 4e 34 37 33 37 [0107111915.N4737] +# 0040: 37 30 35 33 57 31 32 32 30 39 39 30 31 34 2b 30 [7053W122099014+0] +# 0050: 30 35 38 30 30 30 2b 30 30 36 2b 30 30 39 2b 30 [058000+006+009+0] +# 0060: 30 34 2b 30 30 32 2b 30 30 39 2b 30 30 35 2b 30 [04+002+009+005+0] +sub ProcessKenwood($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + while ($$dataPt =~ /\xfe\xfe([^\xfe]+)/g) { + my $dat = $1; + next unless $dat =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})./gs; + my $time = "$1:$2:$3 $4:$5:$6"; # (likely local time zone, but not confirmed) + # ignore second date (what is this for?) + next unless $dat =~ /\G(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})./gs; + next unless $dat =~ /\G([NS])(\d+)([EW])(\d+)/g; + my ($lat, $lon) = ($2/1e4, $4/1e4); + ConvertLatLon($lat, $lon); + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $et->HandleTag($tagTbl, GPSDateTime => $time); + $et->HandleTag($tagTbl, GPSLatitude => $lat * ($1 eq 'S' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSLongitude => $lon * ($3 eq 'W' ? -1 : 1)); + next unless $dat =~ /\G([-+]\d{4})(\d+)/g; + $et->HandleTag($tagTbl, GPSAltitude => $1 + 0); # (NC, educated guess) + $et->HandleTag($tagTbl, GPSSpeed => $2); # (km/h) + my @acc; + while ($dat =~ /\G([-+]\d+)([-+]\d+)([-+]\d+)/g) { + push @acc, $1/1000, $2/1000, $3/1000; + } + $et->HandleTag($tagTbl, Accelerometer => "@acc") if @acc; + } + delete $$et{DOC_NUM}; + return 1; +} + +#------------------------------------------------------------------------------ +# Process RIFF-format trailer written by Auto-Vox dashcam (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# Note: This trailer is basically RIFF chunks added to a QuickTime-format file (augh!), +# but there are differences in the record formats so we can't just call +# ProcessRIFF to process the gps0 and gsen atoms using the routines above +sub ProcessRIFFTrailer($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my ($buff, $pos); + SetByteOrder('II'); + for (;;) { + last unless $raf->Read($buff, 8) == 8; + my ($tag, $len) = unpack('a4V', $buff); + last if $tag eq "\0\0\0\0"; + unless ($tag =~ /^[\w ]{4}/ and $len < 0x2000000) { + $et->Warn('Bad RIFF trailer'); + last; + } + $raf->Read($buff, $len) == $len or $et->Warn("Truncated $tag record in RIFF trailer"), last; + if ($verbose) { + $et->VPrint(0, " - RIFF trailer '${tag}' ($len bytes)\n"); + $et->VerboseDump(\$buff, Addr => $raf->Tell() - $len) if $verbose > 2; + $$et{INDENT} .= '| '; + $et->VerboseDir($tag, undef, $len) if $tag =~ /^(gps0|gsen)$/; + } + if ($tag eq 'gps0') { + # (similar to record decoded in Process_gps0, but with some differences) + # 0000: 41 49 54 47 74 46 94 f6 c6 c5 b4 40 34 a2 b4 37 [AITGtF.....@4..7] + # 0010: f8 7b 8a 40 ff ff 00 00 38 00 77 0a 1a 0c 12 28 [.{.@....8.w....(] + # 0020: 8d 01 02 40 29 07 00 00 [...@)...] + # 0x00 - undef[4] 'AITG' + # 0x04 - double latitude (always positive) + # 0x0c - double longitude (always positive) + # 0x14 - ? seen hex "ff ff 00 00" (altitude in Process_gps0 record below) + # 0x18 - int16u speed in knots (different than km/hr in Process_gps0) + # 0x1a - int8u[6] yr-1900,mon,day,hr,min,sec (different than -2000 in Process_gps0) + # 0x20 - int8u direction in degrees / 2 + # 0x21 - int8u guessing that this is 1=N, 2=S - PH + # 0x22 - int8u guessing that this is 1=E, 2=W - PH + # 0x23 - ? seen hex "40" + # 0x24 - in32u time since start of video (ms) + my $recLen = 0x28; + for ($pos=0; $pos+$recLen<$len; $pos+=$recLen) { + substr($buff, $pos, 4) eq 'AITG' or $et->Warn('Unrecognized gps0 record'), last; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + # lat/long are in DDDMM.MMMM format + my $lat = GetDouble(\$buff, $pos+4); + my $lon = GetDouble(\$buff, $pos+12); + $et->Warn('Bad gps0 record') and last if abs($lat) > 9000 or abs($lon) > 18000; + ConvertLatLon($lat, $lon); + $lat = -$lat if Get8u(\$buff, $pos+0x21) == 2; # wild guess + $lon = -$lon if Get8u(\$buff, $pos+0x22) == 2; # wild guess + my @a = unpack('C*', substr($buff, $pos+26, 6)); # unpack date/time + $a[0] += 1900; # (different than Proces_gps0) + $et->HandleTag($tagTbl, SampleTime => Get32u(\$buff, $pos + 0x24) / 1000); + $et->HandleTag($tagTbl, GPSDateTime => sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ", @a)); + $et->HandleTag($tagTbl, GPSLatitude => $lat); + $et->HandleTag($tagTbl, GPSLongitude => $lon); + $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, $pos+0x18) * $knotsToKph); + $et->HandleTag($tagTbl, GPSTrack => Get8u(\$buff, $pos+0x20) * 2); + } + } elsif ($tag eq 'gsen') { + # (similar to record decoded in Process_gsen) + # 0000: 41 49 54 53 1a 0d 05 ff c8 00 00 00 [AITS........] + # 0x00 - undef[4] 'AITS' + # 0x04 - int8s[3] accelerometer readings + # 0x07 - ? seen hex "ff" + # 0x08 - in32u time since start of video (ms) + my $recLen = 0x0c; + for ($pos=0; $pos+$recLen<$len; $pos+=$recLen) { + substr($buff, $pos, 4) eq 'AITS' or $et->Warn('Unrecognized gsen record'), last; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + my @acc = map { $_ /= 24 } unpack('x'.($pos+4).'c3', $buff); + $et->HandleTag($tagTbl, SampleTime => Get32u(\$buff, $pos + 8) / 1000); + # 0=+Up, 1=+Right, 3=+Forward (calibration of 24 counts/g is a wild guess - PH) + $et->HandleTag($tagTbl, Accelerometer => "@acc"); + } + } + # also seen, but not decoded: + # gpsa (8 bytes): hex "01 20 00 00 08 03 02 08 " + # gsea (20 bytes): all zeros + $$et{INDENT} = substr($$et{INDENT}, 0, -2) if $verbose; + } + delete $$et{DOC_NUM}; + SetByteOrder('MM'); + return 1; +} + +#------------------------------------------------------------------------------ +# Process 'gps ' atom containing NMEA from Pittasoft Blackvue dashcam (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessNMEA($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my ($rtnVal, %fix); + # parse only RMC and GGA sentence [with leading timecode] for now + for (;;) { + my ($tc, $type, $tim); + if ($$dataPt =~ /(?:\[(\d+)\])?\$[A-Z]{2}(RMC|GGA),(\d{2}\d{2}\d+(\.\d*)?),/g) { + ($tc, $type, $tim) = ($1, $2, $3); + } + # write out last fix now if complete + # (use the GPS timestamps because they may be different for the same timecode) + if ($fix{tim} and (not $tim or $fix{tim} != $tim)) { + if ($fix{dat} and defined $fix{lat} and defined $fix{lon}) { + my $sampleTime; + $sampleTime = ($fix{tc} - $$et{StartTime}) / 1000 if $fix{tc} and $$et{StartTime}; + FoundSomething($et, $tagTbl, $sampleTime); + $et->HandleTag($tagTbl, GPSDateTime => $fix{dat}); + $et->HandleTag($tagTbl, GPSLatitude => $fix{lat}); + $et->HandleTag($tagTbl, GPSLongitude => $fix{lon}); + $et->HandleTag($tagTbl, GPSSpeed => $fix{spd} * $knotsToKph) if defined $fix{spd}; + $et->HandleTag($tagTbl, GPSTrack => $fix{trk}) if defined $fix{trk}; + $et->HandleTag($tagTbl, GPSAltitude => $fix{alt}) if defined $fix{alt}; + $et->HandleTag($tagTbl, GPSSatellites=> $fix{nsats}+0) if defined $fix{nsats}; + $et->HandleTag($tagTbl, GPSDOP => $fix{hdop}) if defined $fix{hdop}; + } + undef %fix; + } + $fix{tim} = $tim or last; + my $pos = pos($$dataPt); + pos($$dataPt) = $pos - length($tim) - 1; # rewind to re-parse time + # (parsing of NMEA strings copied from Geotag.pm) + if ($type eq 'RMC' and + $$dataPt =~ /\G(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/g) + { + my $year = $15 + ($15 >= 70 ? 1900 : 2000); + $fix{tc} = $tc; # use timecode of RMC sentence + $fix{dat} = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$year,$14,$13,$1,$2,$3); + $fix{lat} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1); + $fix{lon} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1); + $fix{spd} = $11 if length $11; + $fix{trk} = $12 if length $12; + } elsif ($type eq 'GGA' and + $$dataPt =~ /\G(\d{2})(\d{2})(\d+(\.\d*)?),(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),[1-6]?,(\d+)?,(\.\d+|\d+\.?\d*)?,(-?\d+\.?\d*)?,M?/g) + { + $fix{lat} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1); + $fix{lon} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1); + @fix{qw(nsats hdop alt)} = ($11,$12,$13); + } else { + pos($$dataPt) = $pos; # continue searching from our last match + } + } + delete $$et{DOC_NUM}; + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Process 'gps ' or 'udat' atom possibly containing NMEA (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessGPSLog($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my ($rtnVal, @a); + + # try NMEA format first + return 1 if ProcessNMEA($et,$dirInfo,$tagTbl); + + # DENVER ACG-8050WMK2 format looks like this: + # 210318073213[1][N][52200970][E][006362321][+00152][100][00140][C000000]+000+000+000+000+000+000+000+000+000+000+000+000+000+000+000+000+000+000 + # YYMMDDHHMMSS A? NS lat EW lon alt kph dir kCal accel + while ($$dataPt =~ /\b(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\[1\]\[([NS])\]\[(\d{8})\]\[([EW])\]\[(\d{9})\]\[([-+]?\d*)\]\[(\d*)\]\[(\d*)\]\[C?(\d*)\](([-+]\d{3})+)/g) { + my $lat = substr( $8,0,2) + substr( $8,2) / 600000; + my $lon = substr($10,0,3) + substr($10,3) / 600000; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $et->HandleTag($tagTbl, GPSDateTime => "20$1:$2:$3 $4:$5:$6Z"); + $et->HandleTag($tagTbl, GPSLatitude => $lat * ($7 eq 'S' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSLongitude => $lon * ($9 eq 'W' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSAltitude => $11 / 10) if length $11; + $et->HandleTag($tagTbl, GPSSpeed => $12 + 0) if length $12; + $et->HandleTag($tagTbl, GPSTrack => $13 + 0) if length $13; + $et->HandleTag($tagTbl, KiloCalories => $14 / 10) if length $14; + $et->HandleTag($tagTbl, Accelerometer=> $15) if length $15; + $rtnVal = 1; + } + delete $$et{DOC_NUM}; + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Process TomTom Bandit Action Cam TTAD atom (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +my %ttLen = ( # lengths of known TomTom records + 0 => 12, # angular velocity (NC) + 1 => 4, # ? + 2 => 12, # ? + 3 => 12, # accelerometer (NC) + # (haven't seen a record 4 yet) + 5 => 92, # GPS + 0xff => 4, # timecode +); +sub ProcessTTAD($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + my $pos = 76; + + return 0 if $dirLen < $pos; + + $et->VerboseDir('TTAD', undef, $dirLen); + SetByteOrder('II'); + + my $eeOpt = $et->Options('ExtractEmbedded'); + my $unknown = $et->Options('Unknown'); + my $found = 0; + my $sampleTime = 0; + my $resync = 1; + my $skipped = 0; + my $warned; + + while ($pos < $dirLen) { + # get next record type + my $type = Get8u($dataPt, $pos++); + # resync if necessary by skipping data until next timecode record + if ($resync and $type != 0xff) { + ++$skipped > 0x100 and $et->Warn('Unrecognized or bad TTAD data', 1), last; + next; + } + unless ($ttLen{$type}) { + # skip unknown records + $et->Warn("Unknown TTAD record type $type",1) unless $warned; + $resync = $warned = 1; + ++$skipped; + next; + } + last if $pos + $ttLen{$type} > $dirLen; + if ($type == 0xff) { # timecode? + my $tm = Get32u($dataPt, $pos); + # validate timecode if skipping unknown data + if ($resync) { + if ($tm < $sampleTime or $tm > $sampleTime + 250) { + ++$skipped; + next; + } + undef $resync; + $skipped = 0; + } + $pos += $ttLen{$type}; + $sampleTime = $tm; + next; + } + unless ($eeOpt) { + # only extract one of each type without -ee option + $found & (1 << $type) and $pos += $ttLen{$type}, next; + $found |= (1 << $type); + } + if ($type == 0 or $type == 3) { + # (these are both just educated guesses - PH) + FoundSomething($et, $tagTbl, $sampleTime / 1000); + my @a = map { Get32s($dataPt,$pos+4*$_) / 1000 } 0..2; + $et->HandleTag($tagTbl, ($type ? 'Accelerometer' : 'AngularVelocity') => "@a"); + } elsif ($type == 5) { + # example records unpacked with 'dVddddVddddv*' + # datetime ? spd ele lat lon ? trk ? ? ? ? ? ? ? ? ? + # 2019:03:05 07:52:58.999Z 3 0.02 242 48.0254203 7.8497567 0 45.69 13.34 17.218 17.218 0 0 0 32760 5 0 + # 2019:03:05 07:52:59.999Z 3 0.14 242 48.0254203 7.8497567 0 45.7 12.96 15.662 15.662 0 0 0 32760 5 0 + # 2019:03:05 07:53:00.999Z 3 0.67 243.78 48.0254584 7.8497907 0 50.93 9.16 10.84 10.84 0 0 0 32760 5 0 + # (I think "5" may be the number of satellites. seen: 5,6,7 - PH) + FoundSomething($et, $tagTbl, $sampleTime / 1000); + my $t = GetDouble($dataPt, $pos); + $et->HandleTag($tagTbl, GPSDateTime => Image::ExifTool::ConvertUnixTime($t,undef,3) . 'Z'); + $et->HandleTag($tagTbl, GPSLatitude => GetDouble($dataPt, $pos+0x1c)); + $et->HandleTag($tagTbl, GPSLongitude => GetDouble($dataPt, $pos+0x24)); + $et->HandleTag($tagTbl, GPSAltitude => GetDouble($dataPt, $pos+0x14)); + $et->HandleTag($tagTbl, GPSSpeed => GetDouble($dataPt, $pos+0x0c) * $mpsToKph); + $et->HandleTag($tagTbl, GPSTrack => GetDouble($dataPt, $pos+0x30)); + if ($unknown) { + my @a = map { GetDouble($dataPt, $pos+0x38+8*$_) } 0..2; + $et->HandleTag($tagTbl, Unknown03 => "@a"); + } + } elsif ($type < 3) { + # as yet unknown: + # 1 - int32s[1]? (values around 98k) + # 2 - int32s[3] (values like "806 8124 4323" -- probably something * 1000 again) + if ($unknown) { + FoundSomething($et, $tagTbl, $sampleTime / 1000); + my $n = $type == 1 ? 0 : 2; + my @a = map { Get32s($dataPt,$pos+4*$_) } 0..$n; + $et->HandleTag($tagTbl, "Unknown0$type" => "@a"); + } + } else { + $et->WarnOnce("Unknown TTAD record type $type",1); + } + # without -ee, stop after we find types 0,3,5 (ie. bitmask 0x29) + $eeOpt or ($found & 0x29) != 0x29 or EEWarn($et), last; + $pos += $ttLen{$type}; + } + SetByteOrder('MM'); + delete $$et{DOC_NUM}; + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from Insta360 trailer (INSV and INSP files) (ref PH) +# Inputs: 0) ExifTool ref, 1) Optional dirInfo ref for returning trailer info +# Returns: true on success +sub ProcessInsta360($;$) +{ + local $_; + my ($et, $dirInfo) = @_; + my $raf = $$et{RAF}; + my $offset = $dirInfo ? $$dirInfo{Offset} || 0 : 0; + my $buff; + + return 0 unless $raf->Seek(-78-$offset, 2) and $raf->Read($buff, 78) == 78 and + substr($buff,-32) eq "8db42d694ccc418790edff439fe026bf"; # check magic number + + my $verbose = $et->Options('Verbose'); + my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + my $fileEnd = $raf->Tell(); + my $trailerLen = unpack('x38V', $buff); + $trailerLen > $fileEnd and $et->Warn('Bad Insta360 trailer size'), return 0; + if ($dirInfo) { + $$dirInfo{DirLen} = $trailerLen; + $$dirInfo{DataPos} = $fileEnd - $trailerLen; + if ($$dirInfo{OutFile}) { + if ($$et{DEL_GROUP}{Insta360}) { + ++$$et{CHANGED}; + # just copy the trailer when writing + } elsif ($trailerLen > $fileEnd or not $raf->Seek($$dirInfo{DataPos}, 0) or + $raf->Read(${$$dirInfo{OutFile}}, $trailerLen) != $trailerLen) + { + return 0; + } else { + return 1; + } + } + $et->DumpTrailer($dirInfo) if $verbose or $$et{HTML_DUMP}; + } + unless ($et->Options('ExtractEmbedded')) { + # can arrive here when reading Insta360 trailer on JPEG image (INSP file) + $et->WarnOnce('Use ExtractEmbedded option to extract timed metadata from Insta360 trailer',3); + return 1; + } + + my $unknown = $et->Options('Unknown'); + # position relative to end of trailer (avoids using large offsets for files > 2 GB) + my $epos = -78-$offset; + my ($i, $p); + $$et{SET_GROUP0} = 'Trailer'; + $$et{SET_GROUP1} = 'Insta360'; + SetByteOrder('II'); + # loop through all records in the trailer, from last to first + for (;;) { + my ($id, $len) = unpack('vV', $buff); + ($epos -= $len) + $trailerLen < 0 and last; + $raf->Seek($epos, 2) or last; + if ($verbose) { + $et->VPrint(0, sprintf("Insta360 Record 0x%x (offset 0x%x, %d bytes):\n", $id, $fileEnd + $epos, $len)); + } + # there are 2 types of record 0x300: + # 1. 56 byte records + # 0000: 4a f7 02 00 00 00 00 00 00 00 00 00 00 1e e7 3f [J..............?] + # 0010: 00 00 00 00 00 b2 ef bf 00 00 00 00 00 70 c1 bf [.............p..] + # 0020: 00 00 00 e0 91 5c 8c bf 00 00 00 20 8f ff 87 bf [.....\..... ....] + # 0030: 00 00 00 00 88 7f c9 bf + # 2. 20 byte records + # 0000: c1 d8 d9 0b 00 00 00 00 f5 83 14 80 df 7f fe 7f [................] + # 0010: fe 7f 01 80 + my $dlen = $insvDataLen{$id}; + if (defined $dlen and not $dlen) { + if ($id == 0x300) { + if ($len % 20 and not $len % 56) { + $dlen = 56; + } elsif ($len % 56 and not $len % 20) { + $dlen = 20; + } else { + if ($raf->Read($buff, 20) == 20) { + if (substr($buff, 16, 3) eq "\0\0\0") { + $dlen = 56; + } else { + $dlen = 20; + } + } + $raf->Seek($epos, 2) or last; + } + } elsif ($id == 0x200) { + $dlen = $len; + } + } + # limit the number of records we read if necessary + if ($dlen and $insvLimit{$id} and $len > $insvLimit{$id}[1] * $dlen and + $et->Warn("Insta360 $insvLimit{$id}[0] data is huge. Processing only the first $insvLimit{$id}[1] records",2)) + { + $len = $insvLimit{$id}[1] * $dlen; + } + $raf->Read($buff, $len) == $len or last; + $et->VerboseDump(\$buff) if $verbose > 2; + if ($dlen) { + if ($len % $dlen and $id != 0x700) { # (have seen one 0x700 record which was expected format but not multiple of 53 bytes) + $et->Warn(sprintf('Unexpected Insta360 record 0x%x length',$id)); + } elsif ($id == 0x200) { + $et->FoundTag(PreviewImage => $buff); + } elsif ($id == 0x300) { + for ($p=0; $p<$len; $p+=$dlen) { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + my @a; + if ($dlen == 56) { + @a = map { GetDouble(\$buff, $p + 8 * $_) } 1..6; + } else { + @a = unpack("x${p}x8v6", $buff); + map { $_ = ($_ - 0x8000) / 1000 } @a; + } + $et->HandleTag($tagTbl, TimeCode => sprintf('%.3f', Get64u(\$buff, $p) / 1000)); + $et->HandleTag($tagTbl, Accelerometer => "@a[0..2]"); # (NC) + $et->HandleTag($tagTbl, AngularVelocity => "@a[3..5]"); # (NC) + } + } elsif ($id == 0x400) { + for ($p=0; $p<$len; $p+=$dlen) { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $et->HandleTag($tagTbl, TimeCode => sprintf('%.3f', Get64u(\$buff, $p) / 1000)); + $et->HandleTag($tagTbl, ExposureTime => GetDouble(\$buff, $p + 8)); #6 + } + } elsif ($id == 0x600) { #6 + for ($p=0; $p<$len; $p+=$dlen) { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $et->HandleTag($tagTbl, VideoTimeStamp => sprintf('%.3f', Get64u(\$buff, $p) / 1000)); + } + } elsif ($id == 0x700) { + for ($p=0; $p+$dlen<=$len; $p+=$dlen) { + my $tmp = substr($buff, $p, $dlen); + my @a = unpack('VVvaa8aa8aa8a8a8', $tmp); + unless (($a[5] eq 'N' or $a[5] eq 'S') and # (quick validation) + ($a[7] eq 'E' or $a[7] eq 'W' or + # (odd, but I've seen "O" instead of "W". Perhaps + # when the language is french? ie. "Ouest"?) + $a[7] eq 'O')) + { + next if $a[3] eq 'V'; # void fixes don't have N/S E/W + $et->Warn('Unrecognized INSV GPS format'); + last; + } + next unless $a[3] eq 'A'; # (ignore void fixes) + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $a[$_] = GetDouble(\$a[$_], 0) foreach 4,6,8,9,10; + $a[4] = -abs($a[4]) if $a[5] eq 'S'; # (abs just in case it was already signed) + $a[6] = -abs($a[6]) if $a[7] ne 'E'; + $et->HandleTag($tagTbl, GPSDateTime => Image::ExifTool::ConvertUnixTime($a[0]) . 'Z'); + $et->HandleTag($tagTbl, GPSLatitude => $a[4]); + $et->HandleTag($tagTbl, GPSLongitude => $a[6]); + $et->HandleTag($tagTbl, GPSSpeed => $a[8] * $mpsToKph); + $et->HandleTag($tagTbl, GPSTrack => $a[9]); + $et->HandleTag($tagTbl, GPSAltitude => $a[10]); + $et->HandleTag($tagTbl, Unknown02 => "@a[1,2]") if $unknown; # millisecond counter (https://exiftool.org/forum/index.php?topic=9884.msg65143#msg65143) + } + } + } elsif ($id == 0x101) { + my $tagTablePtr = GetTagTable('Image::ExifTool::QuickTime::INSV_MakerNotes'); + for ($i=0, $p=0; $i<4; ++$i) { + last if $p + 2 > $len; + my ($t, $n) = unpack("x${p}CC", $buff); + last if $p + 2 + $n > $len; + my $val = substr($buff, $p+2, $n); + $et->HandleTag($tagTablePtr, $t, $val); + $p += 2 + $n; + } + } + ($epos -= 6) + $trailerLen < 0 and last; # step back to previous record + $raf->Seek($epos, 2) or last; + $raf->Read($buff, 6) == 6 or last; + } + $$et{DOC_NUM} = 0; + SetByteOrder('MM'); + delete $$et{SET_GROUP0}; + delete $$et{SET_GROUP1}; + return 1; +} + +#------------------------------------------------------------------------------ +# Process CAMM metadata (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessCAMM($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart} || 0; + my $end = $pos + ($$dirInfo{DirLen} || length($$dataPt) - $pos); + # camm record size for each type, including 4-byte header + my %size = ( 1 => 12, 2 => 16, 3 => 16, 4 => 16, 5 => 28, 6 => 60, 7 => 16 ); + my $rtnVal = 0; + while ($pos + 4 < $end) { + my $type = Get16u($dataPt, $pos + 2); + my $size = $size{$type} or $et->WarnOnce("Unknown camm record type $type"), last; + $pos + $size > $end and $et->WarnOnce("Truncated camm record $type"), last; + my $tagTbl = GetTagTable("Image::ExifTool::QuickTime::camm$type"); + $$dirInfo{DirStart} = $pos; + $$dirInfo{DirLen} = $size; + $et->ProcessBinaryData($dirInfo, $tagTbl) and $rtnVal = 1; + # not sure if this is according to specification, but I have seen multiple + # camm records all in a single sample, so step forward to process the next one + $pos += $size; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Process Garmin GPS 'uuid' atom (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +# Note: This format is used by the Garmin DriveAssist 51, but the DriveAssist 50 +# uses a completely different format. :( +sub ProcessGarminGPS($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = length $$dataPt; + my $pos = 33; + my $epoch = (66 * 365 + 17) * 24 * 3600; # time is relative to Jan 1, 1904 + my $scl = 180 / (32768 * 65536); # scaling factor for lat/lon + $et->VerboseDir('GarminGPS'); + $$et{SET_GROUP1} = 'Garmin'; + while ($pos + 20 <= $dataLen) { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + my $time = Image::ExifTool::ConvertUnixTime(Get32u($dataPt, $pos) - $epoch) . 'Z'; + my $lat = Get32s($dataPt, $pos + 12) * $scl; + my $lon = Get32s($dataPt, $pos + 16) * $scl; + my $spd = Get16u($dataPt, $pos + 4); # (in mph) + $et->HandleTag($tagTbl, 'GPSDateTime', $time); + $et->HandleTag($tagTbl, 'GPSLatitude', $lat); + $et->HandleTag($tagTbl, 'GPSLongitude', $lon); + $et->HandleTag($tagTbl, 'GPSSpeed', $spd); + $et->HandleTag($tagTbl, 'GPSSpeedRef', 'M'); + $pos += 20; + } + delete $$et{DOC_NUM}; + delete $$et{SET_GROUP1}; + return 1; +} + +#------------------------------------------------------------------------------ +# Process 360Fly 'uuid' atom containing sensor data +# (ref https://github.com/JamesHeinrich/getID3/blob/master/getid3/module.audio-video.quicktime.php) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub Process360Fly($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = length $$dataPt; + my $pos = 16; + my $lastTime = -1; + my $streamTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + while ($pos + 32 <= $dataLen) { + my $type = ord substr $$dataPt, $pos, 1; + my $time = Get64u($dataPt, $pos + 2); # (only valid for some types) + if ($$tagTbl{$type}) { + if ($time != $lastTime) { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + $lastTime = $time; + } + } + $et->HandleTag($tagTbl, $type, undef, DataPt => $dataPt, Start => $pos, Size => 32); + # synthesize GPSDateTime from the timestamp for GPS records + SetGPSDateTime($et, $streamTbl, $time / 1e6) if $type == 5; + $pos += 32; + } + delete $$et{DOC_NUM}; + return 1; +} + +#------------------------------------------------------------------------------ +# Process GPS from Vantrue N2S dashcam +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessFMAS($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + return 0 unless $$dataPt =~ /^FMAS\0\0\0\0.{72}SAMM.{36}A/s and length($$dataPt) >= 160; + $et->VerboseDir('FMAS', undef, length($$dataPt)); + # 0000: 46 4d 41 53 00 00 00 00 00 00 00 00 00 00 00 00 [FMAS............] + # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + # 0030: 02 08 01 08 06 08 02 04 07 02 06 00 00 00 00 00 [................] + # 0040: 00 00 00 00 00 00 00 00 4f 46 4e 49 4d 4d 41 53 [........OFNIMMAS] + # 0050: 53 41 4d 4d 01 00 00 00 00 00 00 00 00 00 00 00 [SAMM............] + # 0060: e5 07 09 18 08 00 22 00 02 00 00 00 a1 82 8a bf [......".........] + # 0070: 89 23 8e bd 0b 2c 30 bc 41 57 4e 51 16 00 a1 01 [.#...,0.AWNQ....] + # 0080: 29 26 27 0c 4b 00 49 00 00 00 00 00 00 00 00 00 [)&'.K.I.........] + # 0090: 00 00 00 00 00 00 00 00 00 52 00 00 00 00 00 00 [.........R......] + my @a = unpack('x96vCCCCCCx16AAACCCvCCvvv',$$dataPt); + SetByteOrder('II'); + my $acc = ReadValue($dataPt, 0x6c, 'float', 3); # (looks like Z comes first in my sample) + my $lon = $a[10] + ($a[11] + $a[13]/6000) / 60; # (why zero byte at $a[12]?) + my $lat = $a[14] + ($a[15] + $a[16]/6000) / 60; + $et->HandleTag($tagTbl, GPSDateTime => sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @a[0..5])); + $et->HandleTag($tagTbl, GPSLatitude => $lat * ($a[9] eq 'S' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSLongitude => $lon * ($a[8] eq 'W' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSSpeed => $a[17] * $mphToKph); # convert mph -> kph + $et->HandleTag($tagTbl, GPSTrack => $a[18]); + $et->HandleTag($tagTbl, Accelerometer=> $acc); + SetByteOrder('MM'); + return 1; +} + +#------------------------------------------------------------------------------ +# Scan media data for "freeGPS" metadata if not found already (ref PH) +# Inputs: 0) ExifTool ref +sub ScanMediaData($) +{ + my $et = shift; + my $raf = $$et{RAF} or return; + my ($tagTbl, $oldByteOrder, $verbose, $buff, $dataLen); + my ($pos, $buf2) = (0, ''); + + # don't rescan for freeGPS if we already found embedded metadata + my $dataPos = $$et{MediaDataOffset}; + if ($dataPos and not $$et{DOC_COUNT}) { + $dataLen = $$et{MediaDataSize}; + if ($dataLen) { + if ($raf->Seek($dataPos, 0)) { + $$et{FreeGPS2} = { }; # initialize variable space for FreeGPS2() + } else { + undef $dataLen; + } + } + } + + # loop through 'mdat' media data looking for GPS information + while ($dataLen) { + last if $pos + $gpsBlockSize > $dataLen; + last unless $raf->Read($buff, $gpsBlockSize); + $buff = $buf2 . $buff if length $buf2; + last if length $buff < $gpsBlockSize; + # look for "freeGPS " block + # (found on an absolute 0x8000-byte boundary in all of my samples, + # but allow for any alignment when searching) + if ($buff !~ /\0..\0freeGPS /sg) { # (seen ".." = "\0\x80","\x01\0") + $buf2 = substr($buff,-12); + $pos += length($buff)-12; + # in all of my samples the first freeGPS block is within 2 MB of the start + # of the mdat, so limit the scan to the first 20 MB to be fast and safe + next if $tagTbl or $pos < 20e6; + last; + } elsif (not $tagTbl) { + # initialize variables for extracting metadata from this block + $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream'); + $verbose = $$et{OPTIONS}{Verbose}; + $oldByteOrder = GetByteOrder(); + SetByteOrder('II'); + $et->VPrint(0, "---- Extract Embedded ----\n"); + $$et{INDENT} .= '| '; + } + if (pos($buff) > 12) { + $pos += pos($buff) - 12; + $buff = substr($buff, pos($buff) - 12); + } + # make sure we have the full freeGPS record + my $len = unpack('N', $buff); + if ($len < 12) { + $len = 12; + } else { + my $more = $len - length($buff); + if ($more > 0) { + last unless $raf->Read($buf2, $more) == $more; + $buff .= $buf2; + } + if ($verbose) { + $et->VerboseDir('GPS', undef, $len); + $et->VerboseDump(\$buff, DataPos => $pos + $dataPos); + } + my $dirInfo = { DataPt => \$buff, DataPos => $pos + $dataPos, DirLen => $len }; + ProcessFreeGPS2($et, $dirInfo, $tagTbl); + } + $pos += $len; + $buf2 = substr($buff, $len); + } + if ($tagTbl) { + $$et{DOC_NUM} = 0; # reset DOC_NUM after extracting embedded metadata + $et->VPrint(0, "--------------------------\n"); + SetByteOrder($oldByteOrder); + $$et{INDENT} = substr $$et{INDENT}, 0, -2; + } + # process Insta360 trailer if it exists + ProcessInsta360($et); +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::QuickTime - Extract embedded information from media data + +=head1 SYNOPSIS + +These routines are autoloaded by Image::ExifTool::QuickTime. + +=head1 DESCRIPTION + +This file contains routines used by Image::ExifTool to extract embedded +information like GPS tracks from MOV, MP4 and INSV media data. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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 Lhttps://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-SW130> + +=item L<http://sergei.nz/files/nvtk_mp42gpx.py> + +=item L<https://forum.flitsservice.nl/dashcam-info/dod-ls460w-gps-data-uit-mov-bestand-lezen-t87926.html> + +=item L<https://developers.google.com/streetview/publish/camm-spec> + +=item L<https://sergei.nz/extracting-gps-data-from-viofo-a119-and-other-novatek-powered-cameras/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::QuickTime(3pm)|Image::ExifTool::QuickTime>, +L<Image::ExifTool::TagNames/QuickTime Stream Tags>, +L<Image::ExifTool::TagNames/GoPro GPMF Tags>, +L<Image::ExifTool::TagNames/Sony rtmd Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/README b/ExifTool/lib/Image/ExifTool/README new file mode 100644 index 0000000..b019cca --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/README @@ -0,0 +1,1144 @@ +-------------------------------------------------------------------------------- +File: Image/ExifTool/README + +Description: ExifTool support modules documentation + +Note: This documentation is a reference to be used by developers when + adding new tags to ExifTool. + +The ExifTool support modules are loaded by ExifTool to allow processing of +various meta information formats. + +The tables in these files are used as lookups based on the tag ID values. The +hash keys are the tag IDs (in decimal or hexadecimal if the ID is numerical as +with EXIF tables, or the tag name if the ID is ASCII as with XMP tables). In +the case of a BinaryData table, the IDs are numerical and specify offsets into +the binary data block (floating point IDs allow multiple tags for the same +offset, with the integer part being used for the offset). The corresponding +hash value provides information about the tag (explained later). + +Twenty-eight special keys (TABLE_NAME, SHORT_NAME, PROCESS_PROC, WRITE_PROC, +CHECK_PROC, INIT_TABLE, GROUPS, FORMAT, FIRST_ENTRY, TAG_PREFIX, PRINT_CONV, +WRITABLE, TABLE_DESC, NOTES, IS_OFFSET, IS_SUBDIR, EXTRACT_UNKNOWN, NAMESPACE, +PREFERRED, PERMANENT, SRC_TABLE, PRIORITY, AVOID, WRITE_GROUP, LANG_INFO, VARS, +DATAMEMBER and SET_GROUP1) are used to provide additional information about a +table. The special keys have names that are all capitalized to avoid possible +conflicts with tag keys. Below is an explanation of the meaning of each special +key: + + TABLE_NAME : Name of this table (set automatically by GetTagTable()). + + SHORT_NAME : Table name with leading "Image::ExifTool::" removed. + + PROCESS_PROC : Reference to a function used to process the directory for this + table. If PROCESS_PROC is not given, \&Image::ExifTool::Exif::ProcessExif is + assumed (except for QuickTime atoms for which + \&Image::ExifTool::QuickTime::ProcessMOV is the default). If PROCESS_PROC is + set to 0, the tags are not added to the lookup. The process proc returns 1 on + success or 0 on failure, and takes 3 arguments: 0) reference to the ExifTool + object, 1) reference to a directory information hash (with the following + entries:), 2) reference to the tag table hash. + + Name - Tag name for this SubDirectory entry (for verbose messages) + Base - Base offset for pointers from start of file + DataPt - Reference to data block containing directory (may be undef) + DataPos - Position of data block within file (relative to Base) + DataLen - Length of data block in bytes + DirStart - Offset to start of directory from start of data block + DirLen - Length of directory data within block + DirName - Name of this directory + OutFile - Output file or scalar reference + Parent - Name of parent directory + RAF - Reference to File::RandomAccess object if available + NewDataPos - File position of new data (write proc only) + Fixup - Reference to hash of offset fixups (used in EXIF writing only) + FixBase - Flag set to attempt to fix base offsets + FixOffsets - Evaluated for each value pointer to patch maker note offsets + LastIFD - Used by WriteExif() to return offset of last IFD written + ImageData - Used by WriteExif() to avoid buffering large image data blocks + NoRefTest - Flag to bypass "referenced by previous directory" test + + WRITE_PROC : Function reference or name for writing this directory. The + function returns the new directory data or undefined on error. It takes the + same arguments as the process proc above except that the second argument + (reference to directory information hash) is optional, and if specified gives + information about the source directory for tags to be copied to the output. + + CHECK_PROC : Reference to a function which validates Raw values for writing. + The function takes 3-4 arguments: 0) ExifTool object reference, 1) tagInfo + reference, 2) value reference, and 3) [optional] conversion type. It returns + undefined (and possibly modifies the input value) if successful, or an error + message if there was a format problem. May set ExifTool CHECK_WARN datamember + for success with a warning. + + INIT_TABLE : Routine called to do any necessary initialization before the + first time the table is loaded. Argument is a reference to the table. + + GROUPS : A hash lookup for the default group names for all entries in this + table. If not specified, the default Group 0 and 1 names will be set + automatically according to the name of the module, and the default Group 2 + name will be set to 'Other'. + + FORMAT : Specifies the default tag Format, and corresponding pointer increment + for entries in a BinaryData table. Defaults to 'int8u' for BinaryData tables + if not specified. The possible values of FORMAT are: + + int8s - Signed 8-bit integer (EXIF 'SBYTE') + int8u - Unsigned 8-bit integer (EXIF 'BYTE') + int16s - Signed 16-bit integer (EXIF 'SSHORT') + int16u - Unsigned 16-bit integer (EXIF 'SHORT') + int16uRev - Unsigned 16-bit integer, reversed byte order + int32s - Signed 32-bit integer (EXIF 'SLONG') + int32u - Unsigned 32-bit integer (EXIF 'LONG') + int64s - Signed 64-bit integer (BigTIFF 'SLONG8') + int64u - Unsigned 64-bit integer (BigTIFF 'LONG8') + rational32s - Rational consisting of 2 int16s values + rational32u - Rational consisting of 2 int16u values + rational64s - Rational consisting of 2 int32s values (EXIF 'SRATIONAL') + rational64u - Rational consisting of 2 int32u values (EXIF 'RATIONAL') + fixed16s - Signed 16-bit fixed point value + fixed16u - Unsigned 16-bit fixed point value + fixed32s - Signed 32-bit fixed point value + fixed32u - Unsigned 32-bit fixed point value + float - 32-bit IEEE floating point value (EXIF 'FLOAT') + double - 64-bit IEEE floating point value (EXIF 'DOUBLE') + extended - 80-bit extended floating float + ifd - Unsigned 32-bit integer sub-IFD pointer (EXIF 'IFD') + ifd64 - Unsigned 64-bit integer sub-IFD pointer (BigTIFF 'IFD8') + string - Series of 8-bit characters (EXIF 'ASCII') + undef - Undefined-format binary data (EXIF 'UNDEFINED') + binary - Binary data (same as 'undef') + + Additionally, the following variable-length Format types may be used in + individual tag information hashes of a BinaryData table. The 'var_' formats + cause subsequent tag indices to be incremented according to the size of the + data, not including the terminator. See "Format" below for more details. + + pstring - Pascal string (1-byte length followed by ASCII string) + var_string - variable-length null-terminated ASCII string + var_ustring - variable-length null-terminated UCS-2 string + var_pstring - variable-length Pascal string + var_pstr32 - variable-length Pascal string /w 32-bit len + var_ustr32 - variable-length UCS-2 string /w 32-bit len + var_int16u - variable-length undef data with int6u count + var_ue7 - variable-length 7-bit-at-a-time integer (BPG) + + And the following format codes are used for string values in XMP metadata. + (See the XMP specification for more details.) + + boolean - "True" or "False" + date - date/time string + integer - integer as a string + lang-alt - list of alternate-language strings + rational - "N/D" where N=numerator, D=Denominator + real - floating point value as a string + string - unformatted string + struct - structure + + FIRST_ENTRY : Specifies the index for the first tag entry in a binary table. + This value is only used if the Unknown option is set to 2 or higher, and + allows the binary data to be scanned for unknown tag entries. + + TAG_PREFIX : Prefix for names of unknown tags. + + PRINT_CONV : Default print conversion for tags where PrintConv isn't + specified. PrintConv may be set to undef for individual tags to disable print + conversion when PRINT_CONV is defined for a table. + + WRITABLE : Indicates that all tags in this table are writable. This is the + same as setting the Writable flag for each individual tag in the table, except + for SubDirectory tags which are not made Writable. + + TABLE_DESC : Short description for this table. Plain text only. Used only + for XML tag database output. + + NOTES : Notes to introduce the table in the TagNames documentation. Pod + formatting codes B<>, C<> and L<> may be used in this text. + + IS_OFFSET : Writable BinaryData tables only. Reference to list of sorted + TagID's representing offsets. + + IS_SUBDIR : Writable BinaryData tables only. A reference to a list of sorted + tag ID's representing subdirectories. + + EXTRACT_UNKNOWN : Used in PDF tables to specify a directory where all unknown + tags should be extracted. Set to 0 to extract only unknown numbered tags for + which the unnumbered tag is known. + + NAMESPACE : Namespace prefix for tags in the XMP table. If this isn't a + standard namespace defined in %Image::ExifTool::XMP::nsURI, then the URI must + be defined as well (however, this is not recommended for pre-defined + namespaces because then non-standard namespace prefixes won't be recognized). + To define the URI, the NAMESPACE value is a reference to a hash where the key + is the namespace prefix and and the value is the URI (alternatively, a + reference to a 2-element array containing the prefix and URI is also allowed + for backward compatibility). The NAMESPACE value may be undef for XMP tables + where tags have variable namespaces (in this case each tag must have a + Namespace entry). + + PREFERRED : Set to true if the tags in this table should always be added when + writing information. Overrides the order specified by SetNewGroups(). When + this feature is used, it may also be desirable to specify a preferred group + when calling InitWriteDirs() to write these tags; this avoids creating other + directories for tags which are already being creating in the preferred group. + + PERMANENT : Tags in this table are Permanent, and can't be deleted. Maker + notes tags are Permanent by default, without the need to set this property. + + SRC_TABLE : Used internally to store the source table name of a user-defined + tag table so the appropriate module can be loaded as required. + + PRIORITY : Default Priority for all tags in this table. + + AVOID : Flag set to Avoid writing all tags in this table. + + WRITE_GROUP : Default WriteGroup for all tags in the table. + + LANG_INFO : Code reference to a routine which returns a reference to a + language-specific tag information hash. The routine takes two arguments: a + reference to the non-specific tagInfo hash, and the language code. Used only + in tables with writable tags which support tag name language extensions (eg. + MIE and XMP). + + VARS : Hash used to store additional parameters. Individual modules may use + this to store any parameters they want. The following additional parameters + have been defined, and may be used by any module: + + ID_LABEL Label to use instead of "Tag ID" for column heading in tag + name documentation. When this is set, numerical TagID's are + not converted to hexadecimal notation. Unless otherwise set, + an ID_LABEL of "Index" is assumed for tables which use + ProcessBinaryData. + + NO_ID Avoid printing "Tag ID" column in tag name documentation. + + HEX_ID Print tag ID in hexadecimal (with 4 hex digits or more). Set + to 0 for string ID's that look like hex numbers to prevent + them from being converted when importing an XML file for + language translations in BuildLangModules(). + + NO_LOOKUP Do not add tags to TagLookup.pm lookup tables. + + CAPTURE Used by PDF module to name dictionaries to capture when + writing. + + MINOR_ERRORS [EXIF tables only] Flag to make errors in this IFD minor, or + to downgrade already minor errors to warnings while writing. + (Errors in MakerNote IFD's are already classified as minor.) + Note that for certain types errors, the response is to delete + the entire IFD from the image. + + ALPHA_FIRST Sort alphabetical tags before numerical in documentation. + + LONG_TAGS Suppress "Long tags" warning when generating documentation. + Value is the number of long tag names to expect. + + SORT_PROC Reference to sort procedure for sorting tag ID's in the + documentation. Takes two arguments and returns -1, 0 or 1 + if the first argument is less than, equal to, or greater than + the second argument. + + ENTRY_SIZE Used by ProcessPhaseOne to specify size of IFD entry + + MAP_FORMAT Lookup to map unknown format numbers (eg. Panasonic CameraIFD) + + START_INDEX [QuickTime tables only] Initial index for indices shown in + Verbose output. Indices are not show otherwise. + + ATOM_COUNT [QuickTime tables only] Maximum number of atoms contained in + within this atom. Currently used only for Canon CNTH atom, + which contains garbage after the first contained atom. + + NIKON_OFFSETS [Nikon Encrypted tables only] Pointer to int32u NumberOffsets + for offset-type encrypted Nikon directories. When set, + directory and decryption lengths are calculated automatically. + + ALLOW_REPROCESS Flag to allow reprocessing of another directory at this + same location in the file, bypassing recursion avoidance test. + + DATAMEMBER : BinaryData tables only. A reference to a list of sorted tag ID's + which must be extracted as data members when writing. Must also list "var_" + format tags and tags with Hook so offsets are properly calculated if the table + is writable. + + SET_GROUP1 : [EXIF tables only] Flag to set group1 name to the directory name + for all tags in the table. + +The remaining entries in a tag table are the tag IDs with their associated +information. The information may exist in one of three forms: 1) A simple +scalar which is the name of the tag, 2) A reference to a hash of information +describing this tag, or 3) a reference to a list of hashes which contain +Condition expressions allowing the appropriate hash to be selected to suit the +conditions. The following is a description of possible hash entries. All +entries are optional, except for the tag Name which is required if the tag ID is +numerical, and generated automatically otherwise. + + Name : The tag name. Tag names need not be unique. If they aren't + unique, then duplicate tags will hide the values of previous + tags when extracting information unless the Duplicates option + is set or the new tag has lower Priority. With Duplicates + set, to allow multiple tags with the same name to exist in the + tag information hash, the key of the previous tag is changed + to the form "TagName (N)", where N starts at 1 and increments + for subsequent duplicate tags. A tag name should start with + an uppercase letter, and contain only the characters in the + set [A-Za-z0-9_-]. If not given, the Name is taken from the + tag ID with the first character changed to upper case. + + Description : A more readable description of tag name. If a tag doesn't + specify a Description, then the tag Name is used instead, with + spaces inserted between the words. + + Notes : Notes for this tag in the HTML TagNames documentation. + + Groups : Hash lookup for group names for this tag. If not specified, + defaults to the GROUPS specified in the table definition. + + Format : Only valid for BinaryData, EXIF and IPTC tables. For a Binary + or EXIF table, this gives the format that is used to convert + the binary data, and is one of the FORMAT types specified + above. If not specified, the Format of an EXIF entry is taken + from the EXIF information, and the Format of a BinaryData + entry is taken from the FORMAT specified for the table (or + int8u if FORMAT is not specified). Must be specified for + Writable tags where the value must be converted for a + Condition. For BinaryData tables, the format may have a size + in trailing brackets which is a Perl expression to be + evaluated. The expression may access any of the previous + table entries through a %val hash (read-only tags), the data + size via $size, or the ExifTool object via $self. For + example, 'string[$val{3}]' defines a string with length given + by the table entry with tag index '3'. An initial "var_" may + be added to the Format name of any BinaryData tag with a size + in brackets. In this case, subsequent offsets are adjusted by + the value length minus the size of the default table FORMAT + (eg. "var_int16u[10]" causes subsequent offsets to be + incremented by sizeof(int16u) * 10 - sizeof(int8u) if the + default table FORMAT is "int8u"). Note that all "var_" Format + tags (as well as tags with values used within "var_" Format + expressions) must have corresponding DATAMEMBER entries. + + Count : Used when writing EXIF information to specify the number + values to write, or the number of characters in a fixed-length + string. A value of -1 indicates that the count is variable + and should be determined by the number of values provided. + Defaults to 1 if not specified for non-string formats. Note + that this count corresponds to the specified Format, so if a + different-sized Writable format is defined, the actual count + written to the file will be different. + + FixCount : Flag set to determine correct count from offsets in IFD. This + is necessary for some Kodak tags which write an incorrect + Count value. + + Flags : Flags to specify characteristics for this tag. May be a + simple scalar flag name, a reference to a list of flag names, + or a reference to a hash of flag/value pairs. If not a hash + reference, the flag value is set to 1. Flags are expanded for + faster access at run time into members of the tagInfo hash, + and may be written directly as members if desired. The + available flag names are: + + 'AutoSplit' - [List tags only] Similar to ListSplit option, + but applied automatically to individual tags. Value specifies + pattern for split, or 1 for default pattern ',?\\s+'. + + 'Avoid' - avoid creating this tag if possible. This is only + effective if another tag exists with the same name. Setting + this flag also sets the default Priority to 0 for this tag. + + 'Binary' - set to 1 for binary data. This has the same effect + as setting ValueConv to '\$val', but it it a bit cleaner and + avoids dummy ValueConvInv entries for writable tags. Has no + effect if ValueConv is defined for the tag. Some values may + be treated as binary data even if this flag is not set. + + 'BlockExtract' - set for writable directories in EXIF + information which are extracted by default. Otherwise + writable directories are only extracted as a block if + specified explicitly. Also used for Jpeg2000 and QuickTime + XMP-like tags to extract as a block with a name other than + 'XMP'. + + 'ConvertBinary' - true to apply ValueConv and/or PrintConv to + Binary values (ie. SCALAR references). (By default, these + conversions are not done for Binary values.) + + 'DataMember' - name of exiftool data member associated with + this tag if it should be stored as a special data member when + writing information. Necessary only if the value of the tag + affects other written information. Currently only used for + tags in EXIF tables where it triggers the execution of the + RawConv to convert and store the value as an ExifTool data + member when writing. + + 'DataTag' - associated tag name containing data for offset or + byte count tags. + + 'DelGroup' - set if deleting this tag is the same as deleting + the group of the same name. + + 'Drop' - [IFD-format and PhaseOne makernote tags only] set to + 1 for tags that should be excluded when rebuilding maker notes + when copying all tags. Set to a number larger than 1 to drop + only if data is larger than this size. Works for SubDirectory + tags too. + + 'EntryBased' - set to 1 if the offset for this value is based + on the IFD entry position. This allows individual values to + be entry-based even though some others aren't (as with the + Casio PrintIM information). + + 'Flat' - [flattened XMP structure tags only] must be set for + all pre-defined flattened tags (including user-defined + flattened tags). This flag is reset to 0 internally after all + associated flattened tags in the structure have been + generated. + + 'Flattened' - [reserved] used internally to mark Struct tags + which have been processed to generate flattened equivalents. + + 'NotFlat' - [XMP tags only] Flag indicates that this tag ID + does not represent a flattened tag. Used to avoid a conflict + if the tag ID would be the same as a generated ID for a + flattened tag. The result is that the flattened tag will not + be accessible. + + 'Hidden' - set to hide tag from the TagName documentation. + Also suppresses verbose output of a BinaryData tag. The + RawConv of a Hidden tag should return undef so the tag value + is not seen by the user. + + 'IsComposite' - flag set for Composite tags + + 'IsImageData' - flag set if this is an image data offset to + be included in ImageDataHash calculation. Must have an + OffsetPair entry which is the ID of the corresponding size. + + 'IsOffset' - flag set if the tag represents an offset to some + data, and causes value will be adjusted to an absolute file + offset. If set to 2, the offset base of the parent directory + is used even if the base changes for the current directory + (only some maker notes are this messed up). Set to 3 if + absolute file offsets are used. May be set to an expression + to be evaluated. Expression may access $val and $et, + and is evaluated only when reading. + + 'iTXt' - [PNG TextualData tags only] flag to write tag as PNG + iTXt chunk even if it contains no special characters. + + 'List' - flag indicating that duplicate entries of this tag + are allowed, and will be accumulated in a list. Note that for + XMP information, 3 different types of lists are supported and + the List value specifies the type: 'Bag', 'Seq' or 'Alt'. As + well, a value of '1' is used internally in XMP to allow list + behaviour for a flattened tag which is itself not a list + element (eg. a member of list of structures). Note that in + ExifTool an XMP lang-alt tag (Writable="lang-alt") is NOT a + list-type tag (unless it is a list of lang-alt lists, which is + uncommon). + + 'MakerNotes' - set if this tag is maker note data. + + 'MakerPreview' - set in the PreviewImageStart tag information + if the preview must be stored inside the maker notes. + + 'Mandatory' - set for mandatory tags. Used only by TagInfoXML + and for documentation purposes. Mandatory tags may be added + automatically by ExifTool. + + 'NestedHtmlDump' - flag set if value for this tag is also + dumped when processing the SubDirectory. This flag is implied + if the MakerNotes flag is set. Set to 2 if the dump should + only be underlined if nested inside other maker notes. + + 'NotIFD' - set for 'MakerNotes' SubDirectory tags only if the + SubDirectory is not EXIF IFD format. (Note: All SubDirectory + tags in the MakerNotes table are 'MakerNotes' type by + default.) + + 'OffsetPair' - set if the tag represents half of an offset/ + byte count pair. Data for these tags must be handled + separately. Value is the tagID for the paired tag. + + 'Permanent' - flag indicates that a tag is permanent, and + can't be added or deleted from the file, although a new value + may be written if the tag already exists. By default, all + MakerNotes tags are permanent unless otherwise specified. + + 'PreservePadding' - [QuickTime only] flag to preserve the + original size of the QuickTime atom by padding with nulls when + writing with the QuickTimePad option. + + 'PrintHex' - specifies that unknown PrintConv values should + be printed in hex (eg. 'Unknown (0x1)'). Also causes + numerical tag values to be printed in hex in the HTML tag name + documentation, padded to the number of digits given by the + PrintHex value. + + 'PrintInt' - remove decimal part of tag ID in HTML tag name + documentation. (To avoid confusing ExifTool users because + the LensType decimal numbers are for internal use only.) + + 'PrintSort' - causes PrintConv values to be sorted by value + rather than key in the HTML tag name documentation. + + 'PrintString' - flag set to force PrintConv values to be + printed as strings in the documentation. + + 'Priority' - gives the priority of this tag while reading. If + set to zero, this tag will not override the value of previous + tags with the same name. If the priority is greater than + zero, this tag won't be overridden by subsequent tags unless + their priority is equal to or greater than this priority. A + special feature is that Priority 0 tags are automatically + incremented to Priority 1 if they exist in the IFD of the full + resolution image (as determined by SubfileType). If not + defined, the priority defaults to 1 for all tags except except + tags in IFD1 of JPEG images which default to priority 0. + + 'Protected' - bit mask to protect tags from writing: + Bit 0x01 indicates an 'Unsafe' tag, which is not set via + SetNewValuesFromFile() unless specified explicitly. + Bit 0x02 indicates a 'Protected' tag, which should not be set + directly by the user. + + 'PutFirst' - [EXIF only] flag to place this value before IFD0 + when writing (ie. immediately after TIFF header). Only used + for values in main IFD's (IFD0, IFD1, etc) and IFD's where + SubIFD flag is set to 2 (currently only ExifIFD). + + 'RawJoin' - [List tags only] Joins raw List-type tag values + into a single string with a space separator, allowing + ValueConv and PrintConv to act on the concatenated string so + the tag does not exhibit list-type behaviour. When writing, + the inverse is performed and the value is split at whitespace. + + 'Resource' - [XMP only] flag to write value as an rdf:resource + in an empty element instead of as a normal string. + + 'SeparateTable' - set to list PrintConv values in a separate + table in the HTML documentation. Value is 1 for a table name + of 'Module TagName', or 'TAG' for 'Module TAG', or 'MODULE + TAG' to fully specify the table name. The table may have a + 'Notes' entry for a description of the table. + + 'SetResourceName' - [Photoshop tags only] set to 1 to append + resource name to the extracted value (eg. 'VALUE/#NAME#/'). + Also allows resource name to be appended when writing new + values. May be set to any value other than 1 for a default + resource name to use when writing if an appended name is not + provided. + + 'StructType' - [reserved] used internally by XMP writer for + flattened structure tags as a flag to indicate that one or + more enclosing structures has a TYPE field. + + 'SubDoc' - [Composite tags only] set to cause this Composite + tag to also be generated for each sub-document. To achieve + this, 'Main:' and 'Doc#:' prefixes are added to all Require'd + and Desire'd tag names which don't already start with 'Main:' + or 'Doc#:', and the Composite tag is built once for each of + the main document and all sub-documents. May be a reference + to a list of indices for Desire'd tags, one of which must + exist to generate this tag for sub-documents. + + 'SubIFD' - used in writing to determine that the tag specifies + an offset to a sub-IFD. When this flag is set, the Group1 + name gives the name of the IFD. Must be set if and only if + the tag has a SubDirectory Start that references '$val' (this + is validated by BuildTagLookup). Set to 2 for standard EXIF + SubIFD's where the PutFirst flag is valid. + + 'Unknown' - this is an unknown tag (only extracted when the + Unknown option is set). This is set to 2 for Unknown tags in + binary tables (extracted when Unknown is 2). + + 'WriteNothing' - flag indicating that nothing is actually + written when this tag is set. It is a fake writable tag that + isn't actually written (eg. Geotag). Used to avoid processing + a file unnecessarily if nothing will actually be written. + + 'WritePseudo' - flag indicating that this is a writable pseudo + tag. If set, this tag may be written without actually writing + to the file (eg. FileName, FileModifyDate). + + 'WrongBase' - ['IsOffset' tags only] An expression using $self + that is evaluated to generate a base shift for 'IsOffset' tags + which use a different base than the rest of the tags. + + RawConv : Used to convert the Raw value at extraction time (while the + image file is still open, unlike ValueConv and PrintConv below + which are done later only if the value is requested). May be + a scalar expression using $val (the Raw tag value), $self (the + current ExifTool object), $tag (the tag key), $tagInfo + (reference to the tag information hash), $priority (to + dynamically set the priority of a tag), and @grps (to + dynamically set the family and/or groups), or a code reference + with $val and $self as arguments (in which case $$self{grps} + may be used to set the groups). For Composite tags, $val is a + reference to a hash of source ("derived from") tag names, and + @val may be used to access the Raw values of these tags. The + returned value may be a scalar which is used as the new Raw + value, a scalar reference to a binary data value, a hash + reference for Composite tags, an ARRAY reference for a list of + values, or undefined to indicate that the tag should be + ignored. If RawConv is specified for a Composite tag, then + ValueConv and PrintConv evals will no longer have access to + the source @val and @prt values unless the input $val is + returned. RawConv may generate Warning or Error tags, while + ValueConv and PrintConv should not (see ValueConv note below). + Note: RawConv should only be used if necessary (in general, + only if the conversion may return undef to ignore the tag, or + if a Warning may be issued) because ValueConv is more + efficient since it is only executed if the tag value is + requested, while RawConv is executed for all extracted tags. + Note that a tag is still writable if it has a RawConv without + a RawConvInv (with the exception of QuickTime ItemList and + UserData tags, which specifically require this), but this is + not true for ValueConv/PrintConv. + + ValueConv : Used to convert the Raw value to a useable form. May be a hash + reference to act as a lookup table, a scalar which is + evaluated as a Perl expression, a code reference to a + subroutine, or an ARRAY reference (in which case the value is + split at whitespace into a list of items and each item is + converted by the associated entry in the ValueConv list. A + special value of 'REPEAT' may be used to repeat the previous + conversion for all remaining elements). If a hash reference + is used and the Raw value doesn't appear as one of the keys, + then the converted value is set to "Unknown (X)", where X is + the Raw value (unless either of the special keys exist: + 'BITMASK', a reference to a hash used to decode individual + value bits; or 'OTHER', a reference to a subroutine used to + convert unknown values. The OTHER subroutine takes 3 + arguments: the value, a flag which is set for the inverse + conversion, and a reference to the PrintConv hash, and returns + the converted value or undef on error -- it may call warn() to + return an error message. The lookup hash may also contain a + 'Notes' entry which is used for documentation if the + SeparateTable flag is set). In an expression, $self is a + reference to the current ExifTool object, $val is the Raw + value, and $tag is the tag key. The subroutine takes 2 + arguments: the Raw value and a reference to the current + ExifTool object. The advanced formatting expression (if any) + may be accessed via the ExifTool "FMT_EXPR" member variable. + The expression or subroutine is evaluated if and when the tag + value is requested (ie. only after all extraction is + complete), so if necessary at this time the values of all + other tags are available via calls to + $self->GetValue("Tag","Raw"). (Note: In theory, types other + than "Raw" may be accessed, but they are slower and may lead + to cyclical dependencies so they should be avoided). When + evaluated, the expression or subroutine returns a scalar for + the converted value, a SCALAR reference to a binary data value + (see the 'Binary' flag), or an ARRAY reference for a list of + values. The return value should always be defined -- use + RawConv instead to return undef if it is necessary to test the + value for validity, otherwise an undef tag may hide a + previously defined value when the Duplicates option is not + enabled. If this isn't possible (as with Composite tags where + the converted values of the source tags are needed), set the + Priority to 0 to avoid taking priority over a valid tag. If + ValueConv is not specified, the Raw value is not converted. + Composite tags which Require or Desire other tags may access + the ValueConv, PrintConv and Raw values of these tags through + the elements of the @val, @prt and @raw lists respectively + (only if there was no RawConv or it returned a hash + reference). For these tags, $val may be used in an expression + to represent $val[0], and the first argument passed for a code + reference is a reference to @val. Note: Warnings issued by + these conversions are intercepted by ExifTool and saved as + Warning tags, but since ValueConv and PrintConv conversions + are done on demand, the warnings may be generated after the + list of extracted tags is returned, so may easily be missed. + + PrintConv : This entry is similar to ValueConv above, except that it is + used to further convert the tag value to a human readable + form. It can be either a hash lookup, a scalar Perl + expression, a code reference or a list reference. In this + expression, $self, $val and $tag may be used as with + ValueConv, but if ValueConv was defined then $val is the + ValueConv value instead of the Raw value. Composite tags + may also use the @val, @prt and @raw lists. The returned value + should always be defined. Note that the print conversion is + only done if the PrintConv option is enabled (which it is by + default), and if the result of the ValueConv is not a scalar + reference. If it is a list reference, then the converted + values are joined by '; ' in the output string. + + RawConvInv : The inverse of RawConv. This should only be used in very rare + situations when the raw value can not be predetermined. + Unlike the other inverse conversions which are applied in + SetNewValue(), this conversion is applied in WriteInfo() as + the file is being written. This is important because it means + that FILE_TYPE and any DataMember tags ocurring before this + tag in the file are available. Beware that if the return + value is not defined, the tag will be deleted unless there is + specific logic to avoid this (currently, only EXIF and Binary + data directories handle this case). + + ValueConvInv : The inverse of ValueConv. Only necessary for Writable tags + when ValueConv is specified (except WriteAlso tags). Note + that DataMember tags may NOT be used in the inverse + conversions because these conversions are done before the + input file is parsed. Instead, a Condition or RawConvInv must + be used. May return undef on conversion error and call warn() + to issue a warning. If warn() is not called, a generic + warning is generated when undef is returned. An empty warning + ("\n") may be issued to suppress warning messages when undef + is returned. If a scalar, the expression may use the + variables $val, $self and $wantGroup. If a code ref, the + routine is passed 2 arguments: $val and $self. Note that this + conversion is not applied for deleted tags (ie. $val is + undef). + + PrintConvInv : The inverse of PrintConv. Necessary for Writable tags + when PrintConv is specified (unless WriteAlso is used), but + may be used without PrintConv. See ValueConvInv above for + more details. + + PrintConvColumns : Number of columns for PrintConv lookup in HTML docs. If not + set, the number of columns is determined automatically + according to the maximum width of the entries. + + DelValue : Raw value to be used when deleting a permanent tag. (Note all + MakerNotes tags are permanent.) If not specified, an attempt + is made to convert an empty string for the raw value. + + Validate : Scalar expression used to validate the raw value of a tag when + the Validate option is set. The expression may access $val + (the Raw tag value), $tag (the tag key) and $self (the current + ExifTool object), and should return a warning if any problem + is found, or false if OK. The evaluation is performed in the + Image::ExifTool::Validate package namespace, so functions of + this package need not be prefixed by the package name. + + Relist : [Only if ValueConv or PrintConv is a list ref] Reference to a + list of original value indices used to reorganize values. Each + entry in the list may be a scalar index, or a reference to a + list of indices to join values. (Currently values may be + joined, but the order of writable values must not be changed + until this ability is added to the writer.) + + Mask : [BinaryData tags only] Bitmask for this value. (Multiple tags + may have the same index by using floating point indices. An + unknown tag will only be generated for a certain TagID if + there is no integral TagID for that tag.) The Mask and + BitShift are applied before evaluating RawConv. + + BitShift : [Mask tags only] Bit shift for Mask-ed values. If not + specified, set to the number of trailing zero bits in Mask. + When reading, the value is shifted right by this number of + bits after the Mask is applied. + + Condition : If given, specifies scalar which is evaluated as a Perl + expression at extraction time to decide whether the tag is + valid. If used in a list of alternate tag definitions, the + first list entry with a true condition is taken. If no + condition exists, then a 'true' condition is assumed. The + expression may use $self to access the ExifTool object. The + first 128 bytes of the raw data value are accessible through + the reference $valPt for EXIF, Jpeg2000, QuickTime, FLAC and + BinaryData tags only (note that for BinaryData tags, the raw + data of $$valPt is always 'undef' type, and may not be used + when writing except for SubDirectory tags). EXIF tags (and + maybe some other types) may also reference the format string + and value count through $format and $count. Note that if the + value is writable and $valPt is used, the tag must have a + Format (unless 'undef' or 'string'), and a Count (unless 1 or + length of the 'undef' or 'string'), so the raw data may be + generated for evaluating the Condition. When writing, $valPt, + $format and $count refer to the new value, except for + MakerNotes tags where $format and $count refer to the old tag + if it existed. + + Require : [Composite tags only] A hash reference specifying the tags + required to calculate the Composite tag value. The hash + values are the names of the required tags, and the keys + specify the indices where the tag values are stored in the + @val list used in the ValueConv and/or PrintConv expression. + The Composite value is only calculated if the values for all + Require'd tags are defined. Require, Desire and Inhibit tag + names may be prefixed by an optional group family 0 or 1 name + followed by a colon. Case IS significant. The keys used by + the Require, Desire and Inhibit hashes must not overlap (since + they are used as indices into the common @val, @prt and @raw + lists), and together the keys must be sequential starting from + 0. A special feature allows a scalar tag name to be used + instead of the hash reference, in which case the next + available index will be used (avoiding collisions with Desire + and Inhibit hash entries). For example, the following two + definitions are equivalent: + + Require => { 0 => 'XMP:Title' }, + Require => 'XMP:Title', + + Desire : [Composite tags only] This is the same as Require except that + the Composite value is calculated even if the specified tags + don't exist. Beware that the elements of @val, @prt and @raw + may be undefined for Desire'd tags. If no tags are Require'd, + at least one of the Desire'd tags must exist for the Composite + tag to be generated. If there are no Require'd or Desire'd + tags, then the Composite tag is always generated. A scalar + Desire tag name is given the next available index (after + scalar Require tags are allocated). + + Inhibit : [Composite tags only] Similar to the Require and Desire + hashes, except that the Composite tag is NOT built if any of + the Inhibit tags exist. A scalar tag name is given the next + available index (after scalar Require and Desire tags are + allocated). + + Shift : [Writable tags only] Specifies type of shift to apply if this + value may be shifted. Set to 'Time' for shifting date/time + tags, or '0' to prevent tag from being shifted or + conditionally deleted. + + Writable : Indicates this tag can be written (or not written if Writable + is set to zero), and for EXIF and QuickTime tables gives + format for writing. Writable may be set to 1 for MakerNotes + information because the existing format is always used, + however providing a format is desirable because it is used in + validating the value. Set to 2 for tag to show "yes" in the + Writable column of the tag name documentation even when there + is no WRITE_PROC defined (eg. if it is written via an Extra + tag). For EXIF and QuickTime tables, the Writable flag may be + different than the Format flag, in which case Format is used + for converting the binary value and Writable specifies the + format code written to the EXIF IFD. For SubDirectories in + EXIF information, this flag is only defined if the + SubDirectory is writable as a block, or if the SubDirectory + can not be edited (in which case Writable is set to 0). If + non-zero, the SubDirectory is also extracted as a block, so + the Binary and Protected flags should usually set as well. + There is currently no way to specify a write format for a + SubDirectory that is not writable as a block (the default is + 'int32u' for IFD-type SubDirectories, and 'undef' for all + others). + + WriteAlso : Used for writable tag to specify other tags to write when this + tag is written. The value is a hash reference. The hash keys + are the names of the tags to write, and the values are + evaluated to obtain the ValueConv values of each tag (or undef + to delete the tag). In the eval, $val is the new Raw value of + the parent tag (which may be undef if the tag is being + deleted, and should not be modified because it will affect + subsequent WriteAlso tags), and the %opts hash may be accessed + to modify SetNewValue options for each tag. By default, Type + is set to "ValueConv" and the Protected option has bit 0x02 + set to allow writing of Protected tags that aren't directly + writable. The AddValue, DelValue, Shift and Replace options + from the parent tag are also defined, but no other options are + set by default. Previous new values of WriteAlso tags have + already been removed prior to the eval if the Replace option + was used for the parent tag. If an empty warning is issued + ("\n"), the target tag is not written and no error is + reported. + + WriteCheck : If given, specifies a scalar which is evaluated as a Perl + expression for a one-time validatation the Raw value being + written. The expression has access to 3 variables: $val is + the value to be written, $self is the ExifTool object, and + $tagInfo is the tag information hash reference. It returns an + error string, or undef if the value is good. If the error + string is empty, the tag is not written and no warnings are + issued, but WriteAlso is still evaluated if it exists. + + WriteHook : [QuickTime only] Routine to call with these arguments when tag + is encountered during writing: 0) tag value, 1) ExifTool ref. + + WriteOnly : Flag set if tag is write-only. Used for documentation only. + + DelCheck : Similar to WriteCheck, but called when the tag is deleted. The + expression may access $self, $tagInfo and $wantGroup. Returns + error string, or undef on success, and may set $val to + something other than undef. May return empty string ('') to + suppress warning messages but not delete tag (eg. when + deleting only associated tags). + + WriteCondition: [Writable EXIF tags only] Specifies a condition to be + evaluated before the tag can be written to a specific file. + The condition may use $self to reference the ExifTool object, + and returns true if it is OK for writing. Unlike WriteCheck + which is done only once when the new value is set, this + condition is evaluated just before the tag is written to the + file. + + WriteGroup : [Writable EXIF and Composite tags only] Specifies the IFD + where the information gets written by default. Must be + defined for writable EXIF tags if WRITE_GROUP is not specified + in table. A WriteGroup of 'All' is special: For writable + Composite tags this causes any group specified for the + Composite tag to be propagated to the WriteAlso tags when + writing. For other tags this allows writing to all EXIF IFD's + simultaneously (in which case the tag should have a Condition + or WriteCondition to restrict the IFD's to which the tag is + actually written). + + IsOverwriting : [Writable EXIF tags only] Specifies reference to subroutine + which determines if tag should be overwritten. Arguments are: + 0) ExifTool object ref, 1) new value hash ref, 2) old value, + 3) new value reference. + + Preferred : This tag is always preferred when writing. + + Override : [User-defined Composite tags only] Flag to specify that the + new tag definition should override the definition of existing + Composite tags with the same name. Default is 1 unless + otherwise specified. + + AllowGroup : [Writable tags only] Regular expression string (case + insensitive) to match group names which are allowed when + writing this tag. Only needed if tag can be written to + groups other than the normal groups for this tag (very rare). + + OffsetPair : Used in EXIF table to specify the tagID for the corresponding + offset or length tag. + + DataTag : Used in EXIF table to specify the tag name of the data + associated with offset/length tags. + + FixFormat : [Writable EXIF SubIFD SubDirectory's only] Used to specify a + format for writing an IFD pointer other than 'int32u'. + + ChangeBase : [EXIF tags in JPEG images only] Eval used to return new base + for the offset for this tag only. Eval may use $dirStart and + $dataPos. Note this is geared to the quirky Leica preview + image offset, and is only used if the preview is outside the + APP1 EXIF segment. + + BitsPerWord : [BITMASK tags only] Number of bits per decoded word. Defaults + to 32 if not specified. + + BitsTotal : [Writable BITMASK tags only] Total number of bits in bit mask. + Defaults to BitsPerWord if not specified. + + FixedSize : [EXIF only] Hack to ignore value size and use this instead. + Only valid if Format is also defined. + + Struct : [XMP tags only] Reference to structure hash for structured XMP + tags. See "STRUCTURES" section below for more details. (For + backward compatibility, this may be a name to an entry in + %Image::ExifTool::UserDefined::xmpStruct, but this use is + deprecated.) Flattened tags are automatically generated for + each field in the structure. Note that Struct tags can NOT + have ValueConv/PrintConv entries, because values that are HASH + references are are also used by Composite tags, which use the + the Conv entries to generate the Composite value. + + NoSubStruct : [XMP tags only] Flag set in flattened tag to break cyclical + recursion in nested structures. + + Namespace : [XMP tags only] Gives standard XMP namespace prefix to use for + this tag. If not defined, the tag table NAMESPACE is used. + + FlatName : [Struct tags and structure fields only] Name used for + automatically-generated flattened tag names, defaults to Name + if not specified. In general, this is used to remove the + redundant names found in some specifications. May be empty + ('') for a Struct tag but not for a structure field. + + Units : [MIE tags only] Reference to a list of valid units strings. + The default units are the first item in the list. + + Hook : [BinaryData tags only] Expression to be evaluated when + extracting tag to allow dynamic Format, etc for BinaryData + tags. May access $self, $size (the full size of the binary + data record), $dataPt (reference to the data block), and $pos + (position of this tag in the data block), and assign a new + value to $format to dynamically set the tag format, and/or + increment $varSize to add a byte offset to subsequent tags. + $varSize may be set to a large number to effectively abort + processing of the directory after this tag. Must have + corresponding DATAMEMBER entry in writable tables. Must not + have Unknown set. + + LargeTag : [BinaryData tags only] Flag to indicate that the data for this + tag is large, and that it shouldn't be stored in the %val + hash, even if the tag exists as a DATAMEMBER. + + SetBase : [QuickTime and BinaryData tags only] Sets ExifTool BASE offset + for use when ExtractInfo is called with the ReEntry flag from + inside the RawConv of a tag. + + TagID : [reserved] Used internally to save the table key for this tag. + Note: For XMP tables this corresponds to the XMP property + name, but the table key may have a full XMP namespace prefix + added. + + NewTagID : [reserved] Used internally to save new ID of tag in Composite + table if it is different that the original TagID (happens if + there was a conflict with an existing entry in the table) + + Index : [reserved] Used internally to save the index of tagInfo items + which are in a conditional list. + + Table : [reserved] Reference to parent tag table. + + PropertyPath : [reserved] Used internally by XMP writer to save property path + name. Also used in structure field information hashes, but + for these the full property path is not stored. + + SrcTagInfo : [reserved] Used internally to store reference to default + language tagInfo hash for alternate-language tags. + + OtherLang : [reserved] Used internally by QuickTime module to store a list + of tag ID's for alternate-language tags based on this one. + + ParentTagInfo : [reserved] Used internally to store a reference to the tag + information hash of the parent structure for flattened + structure tags. + + RootTagInfo : [reserved] Used internally to store a reference to the tag + information hash of the top-level structure for flattened + structure tags. + + Module : [reserved] Used internally to store module name for writable + Composite tags. + + LangCode : [reserved] Used internally to indicate language code for + alternate language tags (eg. 'fr'). Only used with formats + which support alternate languages (eg. XMP, MIE, etc). + + AddedUnknown : [reserved] Used internally to mark Unknown tags that were + added to the table at run time. + + SubDirectory { If it exists, this specifies the start of a new subdirectory. + It contains a collection of variables which specify the type + and location of the subdirectory. Note that ValueConv and + PrintConv do not apply to SubDirectory tags, but RawConv is + evaluated before the SubDirectory is processed. The following + is a list of SubDirectory variables: + + TagTable : Specifies the name of the tag table lookup for the new + subdirectory. If not specified, the parent tag table is used. + + Start : The offset to the start of the subdirectory relative to the + current Base. This is a Perl expression which may use + $valuePtr to represent the location of the tag value in the + file, or $val for the value itself. If not specified, a Start + of '$valuePtr' is assumed. Subdirectories in BinaryData may + also use $dirStart to represent the offset of the current + directory start relative to the start of the data block. + + OffsetPt : [EXIF directories only] If specified, this is a Perl + expression that gives the position of a 32-bit word in the + current directory that is added to the Start position to get + the position of the new subdirectory. The expression + should use the position of the current tag ($valuePtr). + + Base : This expression specifies the base offset for all pointers in + the subdirectory. This need not be specified if the offset is + the same as the current directory, which is normally the case. + May use $start to represent the subdirectory start location + relative to the current base, and $base for the value of the + current base. If this is defined, the automatic validation of + base offsets is disabled for maker notes directories. The IFD + is flagged as using relative offsets when writing if '$start' + is used in this expression. + + EntryBased : [EXIF directories only] Flag indicating that the offsets are + based on the individual directory entry position, so offsets + are incremented by 12 times the corresponding entry index. + + MaxSubdirs : Maximum number of subdirectories specified by the current tag + (if the tag specifies multiple values). If not specified, the + tag value ($val) is used as-is. If MaxSubdirs is specified, + then one subdirectory is parsed for each value found up to the + maximum number specified. Ignored when writing. + + ByteOrder : Specifies byte ordering if different than than the rest of the + file. Must be either BigEndian, LittleEndian or Unknown. If + Unknown is specified, the byte order will be determined from + the directory count (however, this can not be done if OffsetPt + is specified). + + Validate : If given, specifies Perl expression which is used to validate + the subdirectory data (regardless of Validate option setting). + The following variables may be used in the expression: $val + (value of the tag), $dirData (reference to directory data), + $subdirStart (offset to subdirectory start) and $size (size of + subdirectory). Returns true if subdirectory is valid. + + ProcessProc: If given, specifies processing procedure used to decode this + subdirectory data. This overrides the default procedure + specified by PROCESS_PROC in the tag table. + + WriteProc : If given, specifies processing procedure used to write this + subdirectory data. This overrides the default procedure + specified by WRITE_PROC in the tag table. + + DirName : Name of this subdirectory. If not specified, the name is + taken from the tag name. DirName is important because it is + used when writing to compare against names in the directory + map to determine which directories need to be edited. + + FixBase : Flag set if base offsets should be fixed. Used to add a + constant to maker notes offsets to fix damage done by some + image editing utilities. (maker notes only) Set to 2 for + unknown maker notes to be a bit more flexible in adjusting + the base offset. + + AutoFix : Flag set to patch GE makernote offset quirks and apply FixBase + without warnings when writing. + + FixOffsets : Expression to evaluate for each value pointer to patch + problems with some EXIF maker note offsets. May access the + following variables: $valuePtr, $valEnd, $size, $tagID and + $wFlag. May return undef when writing to ignore the entry. + + RelativeBase:[EXIF directories only] Flag to adjust all offsets relative + to the start of the IFD when writing. + + Multi : [EXIF directories only] Flag to allow multiple linked IFD's. + 1 is assumed if DirName is IFD0 or SubIFD unless otherwise + defined. + + Magic : [TiffIFD directories only] Magic number used in TIFF-like + header. + } + +STRUCTURES + +Structure hashes are very similar to tag tables in that their keys are structure +field ID's and their values are structure field information hashes. The special +keys in structure hashes are: + + NAMESPACE : Same as in tag tables, but may be set to undef for a + variable-namespace structure which holds any top-level tag. This + is mandatory for built-in tags, but optional for user-defined + tags (where it defaults to the NAMESPACE of the tag table). + + STRUCT_NAME : Name of structure (used in warning messages and documentation). + May contain leading module name separated by a space to avoid + name conflicts with same-named structures in other modules. + Default module is "XMP" unless otherwise specified. + + TYPE : rdf:type resource for structure. + + NOTES : Notes for documentation. + + GROUPS : Same as in tag table, but only the family 2 group name is used, + as the default for the flattened tags. + +The contained structure field information hashes are similar to tag information +hashes, except that only the following elements are used: + + Raw/Value/PrintConv (and their inverses), TagID (optional), Groups, List, + Writable, Struct, Namespace, FlatName, LangCode, PropertyPath, Notes. + +But note that for PropertyPath, only the element of the path corresponding to +the specific field is stored (including any necessary list properties). The +full property path is obtained by walking the structure tree. + +Flattened tags corresponding to each field in a structure are automatically +generated (using an on-demand approach to reduce startup overhead). Pre-defined +flattened tags are allowed, and are used when it is necessary to change the Name +or Description of a flattened tag. The flattened tag information hash entries +are copied from the corresponding structure field definitions, even for +pre-defined flattened tags. The exception is that the List property is +generated automatically unless explicitly set to 0 in a pre-defined flattened +tag. + +-------------------------------------------------------------------------------- diff --git a/ExifTool/lib/Image/ExifTool/RIFF.pm b/ExifTool/lib/Image/ExifTool/RIFF.pm new file mode 100644 index 0000000..cb841df --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/RIFF.pm @@ -0,0 +1,2185 @@ +#------------------------------------------------------------------------------ +# File: RIFF.pm +# +# Description: Read RIFF/AVI/WAV meta information +# +# Revisions: 09/14/2005 - P. Harvey Created +# 06/28/2017 - PH Added MBWF/RF64 support +# +# References: 1) http://www.exif.org/Exif2-2.PDF +# 2) http://www.vlsi.fi/datasheets/vs1011.pdf +# 3) http://www.music-center.com.br/spec_rif.htm +# 4) http://www.codeproject.com/audio/wavefiles.asp +# 5) http://msdn.microsoft.com/archive/en-us/directx9_c/directx/htm/avirifffilereference.asp +# 6) http://research.microsoft.com/invisible/tests/riff.h.htm +# 7) http://www.onicos.com/staff/iz/formats/wav.html +# 8) http://graphics.cs.uni-sb.de/NMM/dist-0.9.1/Docs/Doxygen/html/mmreg_8h-source.html +# 9) http://developers.videolan.org/vlc/vlc/doc/doxygen/html/codecs_8h-source.html +# 10) http://wiki.multimedia.cx/index.php?title=TwoCC +# 11) Andreas Winter (SCLive) private communication +# 12) http://abcavi.kibi.ru/infotags.htm +# 13) http://tech.ebu.ch/docs/tech/tech3285.pdf +# 14) https://developers.google.com/speed/webp/docs/riff_container +# 15) https://tech.ebu.ch/docs/tech/tech3306-2009.pdf +# 16) https://sites.google.com/site/musicgapi/technical-documents/wav-file-format +#------------------------------------------------------------------------------ + +package Image::ExifTool::RIFF; + +use strict; +use vars qw($VERSION $AUTOLOAD); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.65'; + +sub ConvertTimecode($); +sub ProcessSGLT($$$); +sub ProcessSLLT($$$); +sub ProcessLucas($$$); +sub WriteRIFF($$); + +# RIFF chunks containing image data (to include in ImageDataHash digest) +my %isImageData = ( + LIST_movi => 1, # (AVI: contains ##db, ##dc, ##wb) + data => 1, # (WAV) + 'VP8 '=>1, VP8L=>1, ANIM=>1, ANMF=>1, ALPH=>1, # (WebP) +); + +# recognized RIFF variants +my %riffType = ( + 'WAVE' => 'WAV', 'AVI ' => 'AVI', 'WEBP' => 'WEBP', + 'LA02' => 'LA', 'LA03' => 'LA', 'LA04' => 'LA', + 'OFR ' => 'OFR', 'LPAC' => 'PAC', 'wvpk' => 'WV', +); + +# MIME types of recognized RIFF-format files +my %riffMimeType = ( + WAV => 'audio/x-wav', + AVI => 'video/x-msvideo', + WEBP => 'image/webp', + LA => 'audio/x-nspaudio', + OFR => 'audio/x-ofr', + PAC => 'audio/x-lpac', + WV => 'audio/x-wavpack', +); + +# character sets for recognized Windows code pages +my %code2charset = ( + 0 => 'Latin', + 65001 => 'UTF8', + 1252 => 'Latin', + 1250 => 'Latin2', + 1251 => 'Cyrillic', + 1253 => 'Greek', + 1254 => 'Turkish', + 1255 => 'Hebrew', + 1256 => 'Arabic', + 1257 => 'Baltic', + 1258 => 'Vietnam', + 874 => 'Thai', + 10000 => 'MacRoman', + 10029 => 'MacLatin2', + 10007 => 'MacCyrillic', + 10006 => 'MacGreek', + 10081 => 'MacTurkish', + 10010 => 'MacRomanian', + 10079 => 'MacIceland', + 10082 => 'MacCroatian', +); + +%Image::ExifTool::RIFF::audioEncoding = ( #2 + Notes => 'These "TwoCC" audio encoding codes are used in RIFF and ASF files.', + 0x01 => 'Microsoft PCM', + 0x02 => 'Microsoft ADPCM', + 0x03 => 'Microsoft IEEE float', + 0x04 => 'Compaq VSELP', #4 + 0x05 => 'IBM CVSD', #4 + 0x06 => 'Microsoft a-Law', + 0x07 => 'Microsoft u-Law', + 0x08 => 'Microsoft DTS', #4 + 0x09 => 'DRM', #4 + 0x0a => 'WMA 9 Speech', #9 + 0x0b => 'Microsoft Windows Media RT Voice', #10 + 0x10 => 'OKI-ADPCM', + 0x11 => 'Intel IMA/DVI-ADPCM', + 0x12 => 'Videologic Mediaspace ADPCM', #4 + 0x13 => 'Sierra ADPCM', #4 + 0x14 => 'Antex G.723 ADPCM', #4 + 0x15 => 'DSP Solutions DIGISTD', + 0x16 => 'DSP Solutions DIGIFIX', + 0x17 => 'Dialoic OKI ADPCM', #6 + 0x18 => 'Media Vision ADPCM', #6 + 0x19 => 'HP CU', #7 + 0x1a => 'HP Dynamic Voice', #10 + 0x20 => 'Yamaha ADPCM', #6 + 0x21 => 'SONARC Speech Compression', #6 + 0x22 => 'DSP Group True Speech', #6 + 0x23 => 'Echo Speech Corp.', #6 + 0x24 => 'Virtual Music Audiofile AF36', #6 + 0x25 => 'Audio Processing Tech.', #6 + 0x26 => 'Virtual Music Audiofile AF10', #6 + 0x27 => 'Aculab Prosody 1612', #7 + 0x28 => 'Merging Tech. LRC', #7 + 0x30 => 'Dolby AC2', + 0x31 => 'Microsoft GSM610', + 0x32 => 'MSN Audio', #6 + 0x33 => 'Antex ADPCME', #6 + 0x34 => 'Control Resources VQLPC', #6 + 0x35 => 'DSP Solutions DIGIREAL', #6 + 0x36 => 'DSP Solutions DIGIADPCM', #6 + 0x37 => 'Control Resources CR10', #6 + 0x38 => 'Natural MicroSystems VBX ADPCM', #6 + 0x39 => 'Crystal Semiconductor IMA ADPCM', #6 + 0x3a => 'Echo Speech ECHOSC3', #6 + 0x3b => 'Rockwell ADPCM', + 0x3c => 'Rockwell DIGITALK', + 0x3d => 'Xebec Multimedia', #6 + 0x40 => 'Antex G.721 ADPCM', + 0x41 => 'Antex G.728 CELP', + 0x42 => 'Microsoft MSG723', #7 + 0x43 => 'IBM AVC ADPCM', #10 + 0x45 => 'ITU-T G.726', #9 + 0x50 => 'Microsoft MPEG', + 0x51 => 'RT23 or PAC', #7 + 0x52 => 'InSoft RT24', #4 + 0x53 => 'InSoft PAC', #4 + 0x55 => 'MP3', + 0x59 => 'Cirrus', #7 + 0x60 => 'Cirrus Logic', #6 + 0x61 => 'ESS Tech. PCM', #6 + 0x62 => 'Voxware Inc.', #6 + 0x63 => 'Canopus ATRAC', #6 + 0x64 => 'APICOM G.726 ADPCM', + 0x65 => 'APICOM G.722 ADPCM', + 0x66 => 'Microsoft DSAT', #6 + 0x67 => 'Microsoft DSAT DISPLAY', #6 + 0x69 => 'Voxware Byte Aligned', #7 + 0x70 => 'Voxware AC8', #7 + 0x71 => 'Voxware AC10', #7 + 0x72 => 'Voxware AC16', #7 + 0x73 => 'Voxware AC20', #7 + 0x74 => 'Voxware MetaVoice', #7 + 0x75 => 'Voxware MetaSound', #7 + 0x76 => 'Voxware RT29HW', #7 + 0x77 => 'Voxware VR12', #7 + 0x78 => 'Voxware VR18', #7 + 0x79 => 'Voxware TQ40', #7 + 0x7a => 'Voxware SC3', #10 + 0x7b => 'Voxware SC3', #10 + 0x80 => 'Soundsoft', #6 + 0x81 => 'Voxware TQ60', #7 + 0x82 => 'Microsoft MSRT24', #7 + 0x83 => 'AT&T G.729A', #7 + 0x84 => 'Motion Pixels MVI MV12', #7 + 0x85 => 'DataFusion G.726', #7 + 0x86 => 'DataFusion GSM610', #7 + 0x88 => 'Iterated Systems Audio', #7 + 0x89 => 'Onlive', #7 + 0x8a => 'Multitude, Inc. FT SX20', #10 + 0x8b => 'Infocom ITS A/S G.721 ADPCM', #10 + 0x8c => 'Convedia G729', #10 + 0x8d => 'Not specified congruency, Inc.', #10 + 0x91 => 'Siemens SBC24', #7 + 0x92 => 'Sonic Foundry Dolby AC3 APDIF', #7 + 0x93 => 'MediaSonic G.723', #8 + 0x94 => 'Aculab Prosody 8kbps', #8 + 0x97 => 'ZyXEL ADPCM', #7, + 0x98 => 'Philips LPCBB', #7 + 0x99 => 'Studer Professional Audio Packed', #7 + 0xa0 => 'Malden PhonyTalk', #8 + 0xa1 => 'Racal Recorder GSM', #10 + 0xa2 => 'Racal Recorder G720.a', #10 + 0xa3 => 'Racal G723.1', #10 + 0xa4 => 'Racal Tetra ACELP', #10 + 0xb0 => 'NEC AAC NEC Corporation', #10 + 0xff => 'AAC', #10 + 0x100 => 'Rhetorex ADPCM', #6 + 0x101 => 'IBM u-Law', #3 + 0x102 => 'IBM a-Law', #3 + 0x103 => 'IBM ADPCM', #3 + 0x111 => 'Vivo G.723', #7 + 0x112 => 'Vivo Siren', #7 + 0x120 => 'Philips Speech Processing CELP', #10 + 0x121 => 'Philips Speech Processing GRUNDIG', #10 + 0x123 => 'Digital G.723', #7 + 0x125 => 'Sanyo LD ADPCM', #8 + 0x130 => 'Sipro Lab ACEPLNET', #8 + 0x131 => 'Sipro Lab ACELP4800', #8 + 0x132 => 'Sipro Lab ACELP8V3', #8 + 0x133 => 'Sipro Lab G.729', #8 + 0x134 => 'Sipro Lab G.729A', #8 + 0x135 => 'Sipro Lab Kelvin', #8 + 0x136 => 'VoiceAge AMR', #10 + 0x140 => 'Dictaphone G.726 ADPCM', #8 + 0x150 => 'Qualcomm PureVoice', #8 + 0x151 => 'Qualcomm HalfRate', #8 + 0x155 => 'Ring Zero Systems TUBGSM', #8 + 0x160 => 'Microsoft Audio1', #8 + 0x161 => 'Windows Media Audio V2 V7 V8 V9 / DivX audio (WMA) / Alex AC3 Audio', #10 + 0x162 => 'Windows Media Audio Professional V9', #10 + 0x163 => 'Windows Media Audio Lossless V9', #10 + 0x164 => 'WMA Pro over S/PDIF', #10 + 0x170 => 'UNISYS NAP ADPCM', #10 + 0x171 => 'UNISYS NAP ULAW', #10 + 0x172 => 'UNISYS NAP ALAW', #10 + 0x173 => 'UNISYS NAP 16K', #10 + 0x174 => 'MM SYCOM ACM SYC008 SyCom Technologies', #10 + 0x175 => 'MM SYCOM ACM SYC701 G726L SyCom Technologies', #10 + 0x176 => 'MM SYCOM ACM SYC701 CELP54 SyCom Technologies', #10 + 0x177 => 'MM SYCOM ACM SYC701 CELP68 SyCom Technologies', #10 + 0x178 => 'Knowledge Adventure ADPCM', #10 + 0x180 => 'Fraunhofer IIS MPEG2AAC', #10 + 0x190 => 'Digital Theater Systems DTS DS', #10 + 0x200 => 'Creative Labs ADPCM', #6 + 0x202 => 'Creative Labs FASTSPEECH8', #6 + 0x203 => 'Creative Labs FASTSPEECH10', #6 + 0x210 => 'UHER ADPCM', #8 + 0x215 => 'Ulead DV ACM', #10 + 0x216 => 'Ulead DV ACM', #10 + 0x220 => 'Quarterdeck Corp.', #6 + 0x230 => 'I-Link VC', #8 + 0x240 => 'Aureal Semiconductor Raw Sport', #8 + 0x241 => 'ESST AC3', #10 + 0x250 => 'Interactive Products HSX', #8 + 0x251 => 'Interactive Products RPELP', #8 + 0x260 => 'Consistent CS2', #8 + 0x270 => 'Sony SCX', #8 + 0x271 => 'Sony SCY', #10 + 0x272 => 'Sony ATRAC3', #10 + 0x273 => 'Sony SPC', #10 + 0x280 => 'TELUM Telum Inc.', #10 + 0x281 => 'TELUMIA Telum Inc.', #10 + 0x285 => 'Norcom Voice Systems ADPCM', #10 + 0x300 => 'Fujitsu FM TOWNS SND', #6 + 0x301 => 'Fujitsu (not specified)', #10 + 0x302 => 'Fujitsu (not specified)', #10 + 0x303 => 'Fujitsu (not specified)', #10 + 0x304 => 'Fujitsu (not specified)', #10 + 0x305 => 'Fujitsu (not specified)', #10 + 0x306 => 'Fujitsu (not specified)', #10 + 0x307 => 'Fujitsu (not specified)', #10 + 0x308 => 'Fujitsu (not specified)', #10 + 0x350 => 'Micronas Semiconductors, Inc. Development', #10 + 0x351 => 'Micronas Semiconductors, Inc. CELP833', #10 + 0x400 => 'Brooktree Digital', #6 + 0x401 => 'Intel Music Coder (IMC)', #10 + 0x402 => 'Ligos Indeo Audio', #10 + 0x450 => 'QDesign Music', #8 + 0x500 => 'On2 VP7 On2 Technologies', #10 + 0x501 => 'On2 VP6 On2 Technologies', #10 + 0x680 => 'AT&T VME VMPCM', #7 + 0x681 => 'AT&T TCP', #8 + 0x700 => 'YMPEG Alpha (dummy for MPEG-2 compressor)', #10 + 0x8ae => 'ClearJump LiteWave (lossless)', #10 + 0x1000 => 'Olivetti GSM', #6 + 0x1001 => 'Olivetti ADPCM', #6 + 0x1002 => 'Olivetti CELP', #6 + 0x1003 => 'Olivetti SBC', #6 + 0x1004 => 'Olivetti OPR', #6 + 0x1100 => 'Lernout & Hauspie', #6 + 0x1101 => 'Lernout & Hauspie CELP codec', #10 + 0x1102 => 'Lernout & Hauspie SBC codec', #10 + 0x1103 => 'Lernout & Hauspie SBC codec', #10 + 0x1104 => 'Lernout & Hauspie SBC codec', #10 + 0x1400 => 'Norris Comm. Inc.', #6 + 0x1401 => 'ISIAudio', #7 + 0x1500 => 'AT&T Soundspace Music Compression', #7 + 0x181c => 'VoxWare RT24 speech codec', #10 + 0x181e => 'Lucent elemedia AX24000P Music codec', #10 + 0x1971 => 'Sonic Foundry LOSSLESS', #10 + 0x1979 => 'Innings Telecom Inc. ADPCM', #10 + 0x1c07 => 'Lucent SX8300P speech codec', #10 + 0x1c0c => 'Lucent SX5363S G.723 compliant codec', #10 + 0x1f03 => 'CUseeMe DigiTalk (ex-Rocwell)', #10 + 0x1fc4 => 'NCT Soft ALF2CD ACM', #10 + 0x2000 => 'FAST Multimedia DVM', #7 + 0x2001 => 'Dolby DTS (Digital Theater System)', #10 + 0x2002 => 'RealAudio 1 / 2 14.4', #10 + 0x2003 => 'RealAudio 1 / 2 28.8', #10 + 0x2004 => 'RealAudio G2 / 8 Cook (low bitrate)', #10 + 0x2005 => 'RealAudio 3 / 4 / 5 Music (DNET)', #10 + 0x2006 => 'RealAudio 10 AAC (RAAC)', #10 + 0x2007 => 'RealAudio 10 AAC+ (RACP)', #10 + 0x2500 => 'Reserved range to 0x2600 Microsoft', #10 + 0x3313 => 'makeAVIS (ffvfw fake AVI sound from AviSynth scripts)', #10 + 0x4143 => 'Divio MPEG-4 AAC audio', #10 + 0x4201 => 'Nokia adaptive multirate', #10 + 0x4243 => 'Divio G726 Divio, Inc.', #10 + 0x434c => 'LEAD Speech', #10 + 0x564c => 'LEAD Vorbis', #10 + 0x5756 => 'WavPack Audio', #10 + 0x674f => 'Ogg Vorbis (mode 1)', #10 + 0x6750 => 'Ogg Vorbis (mode 2)', #10 + 0x6751 => 'Ogg Vorbis (mode 3)', #10 + 0x676f => 'Ogg Vorbis (mode 1+)', #10 + 0x6770 => 'Ogg Vorbis (mode 2+)', #10 + 0x6771 => 'Ogg Vorbis (mode 3+)', #10 + 0x7000 => '3COM NBX 3Com Corporation', #10 + 0x706d => 'FAAD AAC', #10 + 0x7a21 => 'GSM-AMR (CBR, no SID)', #10 + 0x7a22 => 'GSM-AMR (VBR, including SID)', #10 + 0xa100 => 'Comverse Infosys Ltd. G723 1', #10 + 0xa101 => 'Comverse Infosys Ltd. AVQSBC', #10 + 0xa102 => 'Comverse Infosys Ltd. OLDSBC', #10 + 0xa103 => 'Symbol Technologies G729A', #10 + 0xa104 => 'VoiceAge AMR WB VoiceAge Corporation', #10 + 0xa105 => 'Ingenient Technologies Inc. G726', #10 + 0xa106 => 'ISO/MPEG-4 advanced audio Coding', #10 + 0xa107 => 'Encore Software Ltd G726', #10 + 0xa109 => 'Speex ACM Codec xiph.org', #10 + 0xdfac => 'DebugMode SonicFoundry Vegas FrameServer ACM Codec', #10 + 0xe708 => 'Unknown -', #10 + 0xf1ac => 'Free Lossless Audio Codec FLAC', #10 + 0xfffe => 'Extensible', #7 + 0xffff => 'Development', #4 +); + +# RIFF info +%Image::ExifTool::RIFF::Main = ( + PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks, + NOTES => q{ + The RIFF container format is used various types of fines including AVI, WAV, + WEBP, LA, OFR, PAC and WV. According to the EXIF specification, Meta + information is embedded in two types of RIFF C<LIST> chunks: C<INFO> and + C<exif>, and information about the audio content is stored in the C<fmt > + chunk. As well as this information, some video information and proprietary + manufacturer-specific information is also extracted. + + Large AVI videos may be a concatenation of two or more RIFF chunks. For + these files, information is extracted from subsequent RIFF chunks as + sub-documents, but the Duration is calculated for the full video. + + ExifTool currently has the ability to write EXIF, XMP and ICC_Profile + metadata to WEBP images, but can't yet write to other RIFF-based formats. + }, + # (not 100% sure that the concatenation technique mentioned above is valid - PH) + 'fmt ' => { + Name => 'AudioFormat', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::AudioFormat' }, + }, + 'bext' => { + Name => 'BroadcastExtension', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::BroadcastExt' }, + }, + ds64 => { #15 + Name => 'DataSize64', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::DS64' }, + }, + list => 'ListType', #15 + labl => { #16 (in 'adtl' chunk) + Name => 'CuePointLabel', + Priority => 0, # (so they are stored in sequence) + ValueConv => 'my $str=substr($val,4); $str=~s/\0+$//; unpack("V",$val) . " " . $str', + }, + note => { #16 (in 'adtl' chunk) + Name => 'CuePointNote', + Priority => 0, # (so they are stored in sequence) + ValueConv => 'my $str=substr($val,4); $str=~s/\0+$//; unpack("V",$val) . " " . $str', + }, + ltxt => { #16 (in 'adtl' chunk) + Name => 'LabeledText', + Notes => 'CuePointID Length Purpose Country Language Dialect Codepage Text', + Priority => 0, # (so they are stored in sequence) + ValueConv => q{ + my @a = unpack('VVa4vvvv', $val); + $a[2] = "'$a[2]'"; + my $txt = substr($val, 18); + $txt =~ s/\0+$//; # remove null terminator + return join(' ', @a, $txt); + }, + }, + smpl => { #16 + Name => 'Sampler', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Sampler' }, + }, + inst => { #16 + Name => 'Instrument', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Instrument' }, + }, + LIST_INFO => { + Name => 'Info', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Info' }, + }, + LIST_exif => { + Name => 'Exif', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Exif' }, + }, + LIST_hdrl => { # AVI header LIST chunk + Name => 'Hdrl', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Hdrl' }, + }, + LIST_Tdat => { #PH (Adobe CS3 Bridge) + Name => 'Tdat', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Tdat' }, + }, + LIST_ncdt => { #PH (Nikon metadata) + Name => 'NikonData', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::AVI', + # define ProcessProc here so we don't need to load RIFF.pm from Nikon.pm + ProcessProc => \&Image::ExifTool::RIFF::ProcessChunks, + }, + }, + LIST_hydt => { #PH (Pentax metadata) + Name => 'PentaxData', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::AVI', + ProcessProc => \&Image::ExifTool::RIFF::ProcessChunks, + }, + }, + LIST_pntx => { #Andras Salamon (Q-S1 AVI) + Name => 'PentaxData2', + SubDirectory => { + TagTable => 'Image::ExifTool::Pentax::AVI', + ProcessProc => \&Image::ExifTool::RIFF::ProcessChunks, + }, + }, + LIST_adtl => { #PH (ref 16, forum12387) + Name => 'AssociatedDataList', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Main' }, + }, + # seen LIST_JUNK + JUNK => [ + { + Name => 'OlympusJunk', + Condition => '$$valPt =~ /^OLYMDigital Camera/', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::AVI' }, + }, + { + Name => 'CasioJunk', + Condition => '$$valPt =~ /^QVMI/', + # Casio stores standard EXIF-format information in AVI videos (EX-S600) + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + DirName => 'IFD0', + Multi => 0, # (IFD1 is not written) + Start => 10, + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'RicohJunk', + # the Ricoh Caplio GX stores sub-chunks in here + Condition => '$$valPt =~ /^ucmt/', + SubDirectory => { + TagTable => 'Image::ExifTool::Ricoh::AVI', + ProcessProc => \&Image::ExifTool::RIFF::ProcessChunks, + }, + }, + { + Name => 'PentaxJunk', # (Optio RS1000) + Condition => '$$valPt =~ /^IIII\x01\0/', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::Junk' }, + }, + { + Name => 'PentaxJunk2', # (Optio RZ18) + Condition => '$$valPt =~ /^PENTDigital Camera/', + SubDirectory => { TagTable => 'Image::ExifTool::Pentax::Junk2' }, + }, + { + Name => 'LucasJunk', # (Lucas LK-7900 Ace) + Condition => '$$valPt =~ /^0G(DA|PS)/', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&ProcessLucas, + }, + }, + { + Name => 'TextJunk', + # try to interpret unknown junk as an ASCII string + RawConv => '$val =~ /^([^\0-\x1f\x7f-\xff]+)\0*$/ ? $1 : undef', + } + ], + _PMX => { #PH (Adobe CS3 Bridge) + Name => 'XMP', + Notes => 'AVI and WAV files', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, + JUNQ => { #PH (Adobe CS3 Bridge) + # old XMP is preserved when metadata is replaced in Bridge + Name => 'OldXMP', + Binary => 1, + }, + olym => { + Name => 'Olym', + SubDirectory => { TagTable => 'Image::ExifTool::Olympus::WAV' }, + }, + fact => { + Name => 'NumberOfSamples', + RawConv => 'Get32u(\$val, 0)', + }, + 'cue '=> { + Name => 'CuePoints', + Binary => 1, + Notes => q{ + config_files/cutepointlist.config from full distribution will decode this + and generate a list of cue points with labels + }, + }, + plst => { Name => 'Playlist', Binary => 1 }, #16 + afsp => { }, + IDIT => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + ValueConv => 'Image::ExifTool::RIFF::ConvertRIFFDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + CSET => { + Name => 'CharacterSet', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::CSET' }, + }, + # tx_ tags are generated based on the Codec used for the txts stream + tx_USER => { + Name => 'UserText', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::UserText' }, + }, + tx_Unknown => { # (untested) + Name => 'Text', + Notes => 'streamed text, extracted when the ExtractEmbedded option is used', + }, + 'id3 ' => { + Name => 'ID3', + SubDirectory => { TagTable => 'Image::ExifTool::ID3::Main' }, + }, +# +# WebP-specific tags +# + EXIF => [{ # (WebP) + Name => 'EXIF', + Condition => '$$valPt =~ /^(II\x2a\0|MM\0\x2a)/', + Notes => 'WebP files', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + }, + },{ # (WebP) - have also seen with "Exif\0\0" header - PH + Name => 'EXIF', + Condition => '$$valPt =~ /^Exif\0\0(II\x2a\0|MM\0\x2a)/ and $self->Warn("Improper EXIF header",1)', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + ProcessProc => \&Image::ExifTool::ProcessTIFF, + Start => 6, + }, + },{ + Name => 'UnknownEXIF', + Binary => 1, + }], + 'XMP ' => { #14 (WebP) + Name => 'XMP', + Notes => 'WebP files', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }, + }, + ICCP => { #14 (WebP) + Name => 'ICC_Profile', + Notes => 'WebP files', + SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' }, + }, + 'VP8 ' => { # (WebP lossy) + Name => 'VP8Bitstream', + Condition => '$$valPt =~ /^...\x9d\x01\x2a/s', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::VP8' }, + }, + VP8L => { #14 (WebP lossless) + Name => 'VP8L', + Condition => '$$valPt =~ /^\x2f/', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::VP8L' }, + }, + VP8X => { #14 (WebP extended) + Name => 'VP8X', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::VP8X' }, + }, + ANIM => { #14 (WebP animation) + Name => 'ANIM', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ANIM' }, + }, + ANMF => { #14 (WebP animation frame) + Name => 'ANMF', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ANMF' }, + }, + ALPH => { #14 (WebP alpha) + Name => 'ALPH', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ALPH' }, + }, + SGLT => { #PH (BikeBro) + Name => 'BikeBroAccel', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&ProcessSGLT, + }, + }, + SLLT => { #PH (BikeBro) + Name => 'BikeBroGPS', + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => \&ProcessSLLT, + }, + }, + iXML => { #PH + SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' }, + }, + aXML => { #PH + SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' }, + }, +# +# tags found in an AlphaImagingTech AVI video - PH +# + LIST_INF0 => { # ('0' instead of 'O' -- odd) + Name => 'Info', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Info' }, + }, + gps0 => { + Name => 'GPSTrack', + SetGroups => 'RIFF', # (moves "QuickTime" tags to the "RIFF" group) + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + # (don't use code ref here or get "Prototype mismatch" warning with some Perl versions) + ProcessProc => 'Image::ExifTool::QuickTime::Process_gps0', + }, + }, + gsen => { + Name => 'GSensor', + SetGroups => 'RIFF', # (moves "QuickTime" tags to the "RIFF" group) + SubDirectory => { + TagTable => 'Image::ExifTool::QuickTime::Stream', + ProcessProc => 'Image::ExifTool::QuickTime::Process_gsen', + }, + }, + # gpsa - seen hex "01 20 00 00", same as QuickTime + # gsea - 16 bytes hex "04 08 02 00 20 02 00 00 1f 03 00 00 01 00 00 00" + + acid => { # writen by Acidizer + Name => 'Acidizer', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Acidizer' }, + }, + guan => 'Guano', #forum14831 +); + +# the maker notes used by some digital cameras +%Image::ExifTool::RIFF::Junk = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, +); + +# Format and Audio Stream Format chunk data +%Image::ExifTool::RIFF::AudioFormat = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + FORMAT => 'int16u', + 0 => { + Name => 'Encoding', + PrintHex => 1, + PrintConv => \%Image::ExifTool::RIFF::audioEncoding, + SeparateTable => 'AudioEncoding', + }, + 1 => 'NumChannels', + 2 => { + Name => 'SampleRate', + Format => 'int32u', + }, + 4 => { + Name => 'AvgBytesPerSec', + Format => 'int32u', + }, + # uninteresting + # 6 => 'BlockAlignment', + 7 => 'BitsPerSample', +); + +# Broadcast Audio Extension 'bext' information (ref 13) +%Image::ExifTool::RIFF::BroadcastExt = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + NOTES => q{ + Information found in the Broadcast Audio Extension chunk (see + L<http://tech.ebu.ch/docs/tech/tech3285.pdf>). + }, + 0 => { + Name => 'Description', + Format => 'string[256]', + }, + 256 => { + Name => 'Originator', + Format => 'string[32]', + }, + 288 => { + Name => 'OriginatorReference', + Format => 'string[32]', + }, + 320 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + Format => 'string[18]', + ValueConv => '$_=$val; tr/-/:/; s/^(\d{4}:\d{2}:\d{2})/$1 /; $_', + PrintConv => '$self->ConvertDateTime($val)', + }, + 338 => { + Name => 'TimeReference', + Notes => 'first sample count since midnight', + Format => 'int32u[2]', + ValueConv => 'my @v=split(" ",$val); $v[0] + $v[1] * 4294967296', + }, + 346 => { + Name => 'BWFVersion', + Format => 'int16u', + }, + 348 => { + Name => 'BWF_UMID', + Format => 'undef[64]', + ValueConv => '$_=unpack("H*",$val); s/0{64}$//; uc $_', + }, + # 412 - int8u[190] - reserved + 602 => { + Name => 'CodingHistory', + Format => 'string[$size-602]', + }, +); + +# 64-bit chunk sizes (ref 15) +%Image::ExifTool::RIFF::DS64 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + FORMAT => 'int64u', + NOTES => q{ + 64-bit data sizes for MBWF/RF64 files. See + L<https://tech.ebu.ch/docs/tech/tech3306-2009.pdf> for the specification. + }, + 0 => { + Name => 'RIFFSize64', + PrintConv => \&Image::ExifTool::ConvertFileSize, + }, + 1 => { + Name => 'DataSize64', + DataMember => 'DataSize64', + RawConv => '$$self{DataSize64} = $val', + PrintConv => \&Image::ExifTool::ConvertFileSize, + }, + 2 => 'NumberOfSamples64', + # (after this comes a table of size overrides for chunk + # types other than 'data', but since these are currently + # very unlikely, support for these is not yet implemented) +); + +# Sampler chunk (ref 16) +%Image::ExifTool::RIFF::Sampler = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + FORMAT => 'int32u', + 0 => 'Manufacturer', + 1 => 'Product', + 2 => 'SamplePeriod', + 3 => 'MIDIUnityNote', + 4 => 'MIDIPitchFraction', + 5 => { + Name => 'SMPTEFormat', + PrintConv => { + 0 => 'none', + 24 => '24 fps', + 25 => '25 fps', + 29 => '29 fps', + 30 => '30 fps', + }, + }, + 6 => { + Name => 'SMPTEOffset', + Notes => 'HH:MM:SS:FF', + ValueConv => q{ + my $str = sprintf('%.8x', $val); + $str =~ s/(..)(..)(..)(..)/$1:$2:$3:$4/; + return $str; + }, + }, + 7 => 'NumSampleLoops', + 8 => 'SamplerDataLen', + 9 => { Name => 'SamplerData', Format => 'undef[$size-40]', Binary => 1 }, +); + +# Instrument chunk (ref 16) +%Image::ExifTool::RIFF::Instrument = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + FORMAT => 'int8s', + 0 => 'UnshiftedNote', + 1 => 'FineTune', + 2 => 'Gain', + 3 => 'LowNote', + 4 => 'HighNote', + 5 => 'LowVelocity', + 6 => 'HighVelocity', +); + +# Sub chunks of INFO LIST chunk +%Image::ExifTool::RIFF::Info = ( + PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks, + GROUPS => { 2 => 'Audio' }, + FORMAT => 'string', + NOTES => q{ + RIFF INFO tags found in AVI video and WAV audio files. Tags which are part + of the EXIF 2.3 specification have an underlined Tag Name in the HTML + version of this documentation. Other tags are found in AVI files generated + by some software. + }, + IARL => 'ArchivalLocation', + IART => { Name => 'Artist', Groups => { 2 => 'Author' } }, + ICMS => 'Commissioned', + ICMT => 'Comment', + ICOP => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + ICRD => { + Name => 'DateCreated', + Groups => { 2 => 'Time' }, + ValueConv => '$_=$val; s/-/:/g; $_', + }, + ICRP => 'Cropped', + IDIM => 'Dimensions', + IDPI => 'DotsPerInch', + IENG => 'Engineer', + IGNR => 'Genre', + IKEY => 'Keywords', + ILGT => 'Lightness', + IMED => 'Medium', + INAM => 'Title', + ITRK => 'TrackNumber', + IPLT => 'NumColors', + IPRD => 'Product', + ISBJ => 'Subject', + ISFT => { + Name => 'Software', + # remove trailing nulls/spaces and split at first null + # (Casio writes "CASIO" in unicode after the first null) + ValueConv => '$_=$val; s/(\s*\0)+$//; s/(\s*\0)/, /; s/\0+//g; $_', + }, + ISHP => 'Sharpness', + ISRC => 'Source', + ISRF => 'SourceForm', + ITCH => 'Technician', +# +# 3rd party tags +# + # internet movie database (ref 12) + ISGN => 'SecondaryGenre', + IWRI => 'WrittenBy', + IPRO => 'ProducedBy', + ICNM => 'Cinematographer', + IPDS => 'ProductionDesigner', + IEDT => 'EditedBy', + ICDS => 'CostumeDesigner', + IMUS => 'MusicBy', + ISTD => 'ProductionStudio', + IDST => 'DistributedBy', + ICNT => 'Country', + ILNG => 'Language', + IRTD => 'Rating', + ISTR => 'Starring', + # MovieID (ref12) + TITL => 'Title', + DIRC => 'Directory', + YEAR => 'Year', + GENR => 'Genre', + COMM => 'Comments', + LANG => 'Language', + AGES => 'Rated', + STAR => 'Starring', + CODE => 'EncodedBy', + PRT1 => 'Part', + PRT2 => 'NumberOfParts', + # Morgan Multimedia INFO tags (ref 12) + IAS1 => 'FirstLanguage', + IAS2 => 'SecondLanguage', + IAS3 => 'ThirdLanguage', + IAS4 => 'FourthLanguage', + IAS5 => 'FifthLanguage', + IAS6 => 'SixthLanguage', + IAS7 => 'SeventhLanguage', + IAS8 => 'EighthLanguage', + IAS9 => 'NinthLanguage', + ICAS => 'DefaultAudioStream', + IBSU => 'BaseURL', + ILGU => 'LogoURL', + ILIU => 'LogoIconURL', + IWMU => 'WatermarkURL', + IMIU => 'MoreInfoURL', + IMBI => 'MoreInfoBannerImage', + IMBU => 'MoreInfoBannerURL', + IMIT => 'MoreInfoText', + # GSpot INFO tags (ref 12) + IENC => 'EncodedBy', + IRIP => 'RippedBy', + # Sound Forge Pro tags + DISP => 'SoundSchemeTitle', + TLEN => { Name => 'Length', ValueConv => '$val/1000', PrintConv => '"$val s"' }, + TRCK => 'TrackNumber', + TURL => 'URL', + TVER => 'Version', + LOCA => 'Location', + TORG => 'Organization', + # Sony Vegas AVI tags, also used by SCLive and Adobe Premier (ref 11) + TAPE => { + Name => 'TapeName', + Groups => { 2 => 'Video' }, + }, + TCOD => { + Name => 'StartTimecode', + # this is the tape time code for the start of the video + Groups => { 2 => 'Video' }, + ValueConv => '$val * 1e-7', + PrintConv => \&ConvertTimecode, + }, + TCDO => { + Name => 'EndTimecode', + Groups => { 2 => 'Video' }, + ValueConv => '$val * 1e-7', + PrintConv => \&ConvertTimecode, + }, + VMAJ => { + Name => 'VegasVersionMajor', + Groups => { 2 => 'Video' }, + }, + VMIN => { + Name => 'VegasVersionMinor', + Groups => { 2 => 'Video' }, + }, + CMNT => { + Name => 'Comment', + Groups => { 2 => 'Video' }, + }, + RATE => { + Name => 'Rate', #? (video? units?) + Groups => { 2 => 'Video' }, + }, + STAT => { + Name => 'Statistics', + Groups => { 2 => 'Video' }, + # ("7318 0 3.430307 1", "0 0 3500.000000 1", "7 0 3.433228 1") + PrintConv => [ + '"$val frames captured"', + '"$val dropped"', + '"Data rate $val"', + { 0 => 'Bad', 1 => 'OK' }, # capture OK? + ], + }, + DTIM => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + ValueConv => q{ + my @v = split ' ', $val; + return undef unless @v == 2; + # the Kodak EASYSHARE Sport stores this incorrectly as a string: + return $val if $val =~ /^\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}$/; + # get time in seconds + $val = 1e-7 * ($v[0] * 4294967296 + $v[1]); + # shift from Jan 1, 1601 to Jan 1, 1970 + $val -= 134774 * 24 * 3600 if $val != 0; + return Image::ExifTool::ConvertUnixTime($val); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + # not observed, but apparently part of the standard: + IDIT => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + ValueConv => 'Image::ExifTool::RIFF::ConvertRIFFDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + ISMP => 'TimeCode', +); + +# Sub chunks of EXIF LIST chunk +%Image::ExifTool::RIFF::Exif = ( + PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks, + GROUPS => { 2 => 'Audio' }, + NOTES => 'These tags are part of the EXIF 2.3 specification for WAV audio files.', + ever => 'ExifVersion', + erel => 'RelatedImageFile', + etim => { Name => 'TimeCreated', Groups => { 2 => 'Time' } }, + ecor => { Name => 'Make', Groups => { 2 => 'Camera' } }, + emdl => { Name => 'Model', Groups => { 2 => 'Camera' }, Description => 'Camera Model Name' }, + emnt => { Name => 'MakerNotes', Binary => 1 }, + eucm => { + Name => 'UserComment', + PrintConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,"RIFF:UserComment")', + }, +); + +# Sub chunks of hdrl LIST chunk +%Image::ExifTool::RIFF::Hdrl = ( + PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks, + GROUPS => { 2 => 'Image' }, + avih => { + Name => 'AVIHeader', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::AVIHeader' }, + }, + IDIT => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + ValueConv => 'Image::ExifTool::RIFF::ConvertRIFFDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + ISMP => 'TimeCode', + LIST_strl => { + Name => 'Stream', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Stream' }, + }, + LIST_odml => { + Name => 'OpenDML', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::OpenDML' }, + }, +); + +# Sub chunks of Tdat LIST chunk (ref PH) +%Image::ExifTool::RIFF::Tdat = ( + PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks, + GROUPS => { 2 => 'Video' }, + # (have seen tc_O, tc_A, rn_O and rn_A) +); + +# RIFF character set chunk +%Image::ExifTool::RIFF::CSET = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + FORMAT => 'int16u', + 0 => { + Name => 'CodePage', + RawConv => '$$self{CodePage} = $val', + }, + 1 => 'CountryCode', + 2 => 'LanguageCode', + 3 => 'Dialect', +); + +%Image::ExifTool::RIFF::AVIHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + 0 => { + Name => 'FrameRate', + # (must use RawConv because raw value used in Composite tag) + RawConv => '$val ? 1e6 / $val : undef', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + 1 => { + Name => 'MaxDataRate', + PrintConv => 'sprintf("%.4g kB/s",$val / 1024)', + }, + # 2 => 'PaddingGranularity', + # 3 => 'Flags', + 4 => 'FrameCount', + # 5 => 'InitialFrames', + 6 => 'StreamCount', + # 7 => 'SuggestedBufferSize', + 8 => 'ImageWidth', + 9 => 'ImageHeight', +); + +%Image::ExifTool::RIFF::Stream = ( + PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks, + GROUPS => { 2 => 'Image' }, + strh => { + Name => 'StreamHeader', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::StreamHeader' }, + }, + strn => 'StreamName', + strd => { #PH + Name => 'StreamData', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::StreamData' }, + }, + strf => [ + { + Name => 'AudioFormat', + Condition => '$$self{RIFFStreamType} eq "auds"', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::AudioFormat' }, + }, + { + Name => 'VideoFormat', + Condition => '$$self{RIFFStreamType} eq "vids"', + SubDirectory => { TagTable => 'Image::ExifTool::BMP::Main' }, + }, + { + Name => 'TextFormat', + Condition => '$$self{RIFFStreamType} eq "txts"', + Hidden => 1, + RawConv => '$self->Options("ExtractEmbedded") or $self->WarnOnce("Use ExtractEmbedded option to extract timed text",3); undef', + }, + ], +); + +# Open DML tags (ref http://www.morgan-multimedia.com/download/odmlff2.pdf) +%Image::ExifTool::RIFF::OpenDML = ( + PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks, + GROUPS => { 2 => 'Video' }, + dmlh => { + Name => 'ExtendedAVIHeader', + SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ExtAVIHdr' }, + }, +); + +# Extended AVI Header tags (ref http://www.morgan-multimedia.com/download/odmlff2.pdf) +%Image::ExifTool::RIFF::ExtAVIHdr = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + 0 => 'TotalFrameCount', +); + +%Image::ExifTool::RIFF::StreamHeader = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + PRIORITY => 0, # so we get values from the first stream + 0 => { + Name => 'StreamType', + Format => 'string[4]', + RawConv => '$$self{RIFFStreamNum} = ($$self{RIFFStreamNum} || 0) + 1; $$self{RIFFStreamType} = $val', + PrintConv => { + auds => 'Audio', + mids => 'MIDI', + txts => 'Text', + vids => 'Video', + iavs => 'Interleaved Audio+Video', + }, + }, + 1 => [ + { + Name => 'AudioCodec', + Condition => '$$self{RIFFStreamType} eq "auds"', + RawConv => '$$self{RIFFStreamCodec}[$$self{RIFFStreamNum}-1] = $val', + Format => 'string[4]', + }, + { + Name => 'VideoCodec', + Condition => '$$self{RIFFStreamType} eq "vids"', + RawConv => '$$self{RIFFStreamCodec}[$$self{RIFFStreamNum}-1] = $val', + Format => 'string[4]', + }, + { + Name => 'Codec', + Format => 'string[4]', + RawConv => '$$self{RIFFStreamCodec}[$$self{RIFFStreamNum}-1] = $val', + }, + ], + # 2 => 'StreamFlags', + # 3 => 'StreamPriority', + # 3.5 => 'Language', + # 4 => 'InitialFrames', + 5 => [ + { + Name => 'AudioSampleRate', + Condition => '$$self{RIFFStreamType} eq "auds"', + Format => 'rational64u', + ValueConv => '$val ? 1/$val : 0', + PrintConv => 'int($val * 100 + 0.5) / 100', + }, + { + Name => 'VideoFrameRate', + Condition => '$$self{RIFFStreamType} eq "vids"', + Format => 'rational64u', + # (must use RawConv because raw value used in Composite tag) + RawConv => '$val ? 1/$val : undef', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + { + Name => 'StreamSampleRate', + Format => 'rational64u', + ValueConv => '$val ? 1/$val : 0', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + ], + # 7 => 'Start', + 8 => [ + { + Name => 'AudioSampleCount', + Condition => '$$self{RIFFStreamType} eq "auds"', + }, + { + Name => 'VideoFrameCount', + Condition => '$$self{RIFFStreamType} eq "vids"', + }, + { + Name => 'StreamSampleCount', + }, + ], + # 9 => 'SuggestedBufferSize', + 10 => { + Name => 'Quality', + PrintConv => '$val eq 0xffffffff ? "Default" : $val', + }, + 11 => { + Name => 'SampleSize', + PrintConv => '$val ? "$val byte" . ($val==1 ? "" : "s") : "Variable"', + }, + # 12 => { Name => 'Frame', Format => 'int16u[4]' }, +); + +%Image::ExifTool::RIFF::StreamData = ( #PH + PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessStreamData, + GROUPS => { 2 => 'Video' }, + NOTES => q{ + This chunk is used to store proprietary information in AVI videos from some + cameras. The first 4 characters of the data are used as the Tag ID below. + }, + AVIF => { + Name => 'AVIF', + SubDirectory => { + TagTable => 'Image::ExifTool::Exif::Main', + DirName => 'IFD0', + Start => 8, + ByteOrder => 'LittleEndian', + }, + }, + CASI => { # (used by Casio GV-10) + Name => 'CasioData', + SubDirectory => { TagTable => 'Image::ExifTool::Casio::AVI' }, + }, + Zora => 'VendorName', # (Samsung PL90 AVI files) + unknown => { + Name => 'UnknownData', + # try to interpret unknown stream data as a string + RawConv => '$_=$val; /^[^\0-\x1f\x7f-\xff]+$/ ? $_ : undef', + }, +); + +# VP8 bitstream (ref http://www.rfc-editor.org/rfc/pdfrfc/rfc6386.txt.pdf) +%Image::ExifTool::RIFF::VP8 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + NOTES => q{ + This chunk is found in simple-format (lossy) WebP files. See + L<https://developers.google.com/speed/webp/docs/riff_container> for the WebP + container specification. + }, + 0 => { + Name => 'VP8Version', + Mask => 0x0e, + PrintConv => { + 0 => '0 (bicubic reconstruction, normal loop)', + 1 => '1 (bilinear reconstruction, simple loop)', + 2 => '2 (bilinear reconstruction, no loop)', + 3 => '3 (no reconstruction, no loop)', + }, + }, + 6 => { + Name => 'ImageWidth', + Format => 'int16u', + Mask => 0x3fff, + Priority => 0, + }, + 6.1 => { + Name => 'HorizontalScale', + Format => 'int16u', + Mask => 0xc000, + }, + 8 => { + Name => 'ImageHeight', + Format => 'int16u', + Mask => 0x3fff, + Priority => 0, + }, + 8.1 => { + Name => 'VerticalScale', + Format => 'int16u', + Mask => 0xc000, + }, +); + +# WebP lossless info (ref 14) +%Image::ExifTool::RIFF::VP8L = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + NOTES => 'This chunk is found in lossless WebP files.', + GROUPS => { 2 => 'Image' }, + 1 => { + Name => 'ImageWidth', + Format => 'int16u', + Priority => 0, + ValueConv => '($val & 0x3fff) + 1', + }, + 2 => { + Name => 'ImageHeight', + Format => 'int32u', + Priority => 0, + ValueConv => '(($val >> 6) & 0x3fff) + 1', + }, +); + +# WebP extended info (ref 14) +%Image::ExifTool::RIFF::VP8X = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + NOTES => 'This chunk is found in extended WebP files.', + # 0 - bitmask: 2=ICC, 3=alpha, 4=EXIF, 5=XMP, 6=animation + 0 => { + Name => 'WebP_Flags', + Description => 'WebP Flags', + Notes => 'flags used in Extended WebP images', + Format => 'int32u', + PrintConv => { BITMASK => { + 1 => 'Animation', + 2 => 'XMP', + 3 => 'EXIF', + 4 => 'Alpha', + 5 => 'ICC Profile', + }}, + }, + 4 => { + Name => 'ImageWidth', + Format => 'int32u', + ValueConv => '($val & 0xffffff) + 1', + }, + 6 => { + Name => 'ImageHeight', + Format => 'int32u', + ValueConv => '($val >> 8) + 1', + }, +); + +# WebP animation info (ref 14) +%Image::ExifTool::RIFF::ANIM = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + NOTES => 'WebP animation chunk.', + 0 => { + Name => 'BackgroundColor', + Format => 'int8u[4]', + }, + 4 => { + Name => 'AnimationLoopCount', + PrintConv => '$val || "inf"', + }, +); + +# WebP animation frame info (ref 14) +%Image::ExifTool::RIFF::ANMF = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + NOTES => 'WebP animation frame chunk.', + 12 => { + Name => 'Duration', + Format => 'int32u', + Notes => 'extracted as the sum of durations of all animation frames', + RawConv => q{ + if (defined $$self{VALUE}{Duration}) { + $$self{VALUE}{Duration} += $val & 0x0fff; + return undef; + } + return $val & 0x0fff; + }, + ValueConv => '$val / 1000', + PrintConv => 'ConvertDuration($val)', + }, +); + +# streamed USER txts written by Momento M6 dashcam (ref PH) +%Image::ExifTool::RIFF::UserText = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Location' }, + NOTES => q{ + Tags decoded from the USER-format txts stream written by Momento M6 dashcam. + Extracted only if the ExtractEmbedded option is used. + }, + # (little-endian) + # 0 - int32u: 32 + # 4 - int32u: sample number (starting from unknown offset) + # 8 - int8u[4]: "w x y z" ? (w 0=front cam, 1=rear cam, z mostly 5-8) + # 12 - int8u[4]: "0 x 1 0" ? (x incrementing once per second) + # 16 - int8u[4]: "0 32 0 x" ? + # 20 - int32u: 100-150(mostly), 250-300(once per second) + # 24 - int8u[4]: "0 x y 0" ? + 28 => { Name => 'GPSAltitude', Format => 'int32u', ValueConv => '$val / 10' }, # (NC) + # 32 - int32u: 0(mostly), 23(once per second) + # 36 - int32u: 0 + 40 => { Name => 'Accelerometer', Format => 'float[3]' }, + # 52 - int32u: 1 + 56 => { Name => 'GPSSpeed', Format => 'float' }, # km/h + 60 => { + Name => 'GPSLatitude', + Format => 'float', + # Note: these values are unsigned and I don't know where the hemisphere is stored, + # but my only sample is from the U.S., so assume a positive latitude (for now) + ValueConv => 'my $deg = int($val / 100); $deg + ($val - $deg * 100) / 60', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 64 => { + Name => 'GPSLongitude', + Format => 'float', + # Note: these values are unsigned and I don't know where the hemisphere is stored, + # but my only sample is from the U.S., so assume a negative longitude (for now) + ValueConv => 'my $deg = int($val / 100); -($deg + ($val - $deg * 100) / 60)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 68 => { + Name => 'GPSDateTime', + Description => 'GPS Date/Time', + Groups => { 2 => 'Time' }, + Format => 'int32u', + ValueConv => 'ConvertUnixTime($val)', + # (likely local time, but clock seemed off by 3 hours in my sample) + PrintConv => '$self->ConvertDateTime($val)', + }, +); + +# WebP alpha info (ref 14) +%Image::ExifTool::RIFF::ALPH = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Image' }, + NOTES => 'WebP alpha chunk.', + 0 => { + Name => 'AlphaPreprocessing', + Mask => 0x03, + PrintConv => { + 0 => 'none', + 1 => 'Level Reduction', + }, + }, + 0.1 => { + Name => 'AlphaFiltering', + Mask => 0x03, + PrintConv => { + 0 => 'none', + 1 => 'Horizontal', + 2 => 'Vertical', + 3 => 'Gradient', + }, + }, + 0.2 => { + Name => 'AlphaCompression', + Mask => 0x03, + PrintConv => { + 0 => 'none', + 1 => 'Lossless', + }, + }, +); + +# Acidizer information (ref https://forums.cockos.com/showthread.php?t=227118) +%Image::ExifTool::RIFF::Acidizer = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + 0 => { + Name => 'AcidizerFlags', + Format => 'int32u', + PrintConv => { BITMASK => { + 0 => 'One shot', + 1 => 'Root note set', + 2 => 'Stretch', + 3 => 'Disk-based', + 4 => 'High octave', + }}, + }, + 4 => { + Name => 'RootNote', + Format => 'int16u', + PrintConv => { + 0x30 => 'C', 0x3c => 'High C', + 0x31 => 'C#', 0x3d => 'High C#', + 0x32 => 'D', 0x3e => 'High D', + 0x33 => 'D#', 0x3f => 'High D#', + 0x34 => 'E', 0x40 => 'High E', + 0x35 => 'F', 0x41 => 'High F', + 0x36 => 'F#', 0x42 => 'High F#', + 0x37 => 'G', 0x43 => 'High G', + 0x38 => 'G#', 0x44 => 'High G#', + 0x39 => 'A', 0x45 => 'High A', + 0x3a => 'A#', 0x46 => 'High A#', + 0x3b => 'B', 0x47 => 'High B', + }, + }, + 12 => { + Name => 'Beats', + Format => 'int32u', + }, + 16 => { + Name => 'Meter', + Format => 'int16u[2]', + PrintConv => '$val =~ s/(\d+) (\d+)/$2\/$1/; $val', # denominator comes first, so swap them + }, + 20 => { + Name => 'Tempo', + Format => 'float', + }, +); + +# RIFF composite tags +%Image::ExifTool::RIFF::Composite = ( + Duration => { + Require => { + 0 => 'RIFF:FrameRate', + 1 => 'RIFF:FrameCount', + }, + Desire => { + 2 => 'VideoFrameRate', + 3 => 'VideoFrameCount', + }, + RawConv => 'Image::ExifTool::RIFF::CalcDuration($self, @val)', + PrintConv => 'ConvertDuration($val)', + }, + Duration2 => { + Name => 'Duration', + Require => { + 0 => 'RIFF:AvgBytesPerSec', + 1 => 'FileSize', + }, + Desire => { + # check FrameCount because this calculation only applies + # to audio-only files (eg. WAV) + 2 => 'FrameCount', + 3 => 'VideoFrameCount', + }, + # (can't calculate duration like this for compressed audio types) + RawConv => q{ + return undef if $$self{FileType} =~ /^(LA|OFR|PAC|WV)$/; + return(($val[0] and not ($val[2] or $val[3])) ? $val[1] / $val[0] : undef); + }, + PrintConv => 'ConvertDuration($val)', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::RIFF'); + + +#------------------------------------------------------------------------------ +# AutoLoad our writer routines when necessary +# +sub AUTOLOAD +{ + return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); +} + +#------------------------------------------------------------------------------ +# Convert RIFF date to EXIF format +my %monthNum = ( + Jan=>1, Feb=>2, Mar=>3, Apr=>4, May=>5, Jun=>6, + Jul=>7, Aug=>8, Sep=>9, Oct=>10,Nov=>11,Dec=>12 +); +sub ConvertRIFFDate($) +{ + my $val = shift; + my @part = split ' ', $val; + my $mon; + if (@part >= 5 and $mon = $monthNum{ucfirst(lc($part[1]))}) { + # the standard AVI date format (eg. "Mon Mar 10 15:04:43 2003") + $val = sprintf("%.4d:%.2d:%.2d %s", $part[4], + $mon, $part[2], $part[3]); + } elsif ($val =~ m{(\d{4})/\s*(\d+)/\s*(\d+)/?\s+(\d+):\s*(\d+)\s*(P?)}) { + # but the Casio QV-3EX writes dates like "2001/ 1/27 1:42PM", + # and the Casio EX-Z30 writes "2005/11/28/ 09:19"... doh! + $val = sprintf("%.4d:%.2d:%.2d %.2d:%.2d:00",$1,$2,$3,$4+($6?12:0),$5); + } elsif ($val =~ m{(\d{4})[-/](\d+)[-/](\d+)\s+(\d+:\d+:\d+)}) { + # the Konica KD500Z writes "2002-12-16 15:35:01\0\0" + $val = "$1:$2:$3 $4"; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Print time +# Inputs: 0) time in seconds +# Returns: time string +sub ConvertTimecode($) +{ + my $val = shift; + my $hr = int($val / 3600); + $val -= $hr * 3600; + my $min = int($val / 60); + $val -= $min * 60; + my $ss = sprintf('%05.2f', $val); + if ($ss >= 60) { # handle round-off problems + $ss = '00.00'; + ++$min >= 60 and $min -= 60, ++$hr; + } + return sprintf('%d:%.2d:%s', $hr, $min, $ss); +} + +#------------------------------------------------------------------------------ +# Calculate duration of RIFF +# Inputs: 0) ExifTool ref, 1/2) RIFF:FrameRate/Count, 2/3) VideoFrameRate/Count +# Returns: Duration in seconds or undef +# Notes: Sums duration of all sub-documents (concatenated AVI files) +sub CalcDuration($@) +{ + my ($et, @val) = @_; + my $totalDuration = 0; + my $subDoc = 0; + my @keyList; + for (;;) { + # this is annoying. Apparently (although I couldn't verify this), FrameCount + # in the RIFF header includes multiple video tracks if they exist (eg. with the + # FujiFilm REAL 3D AVI's), but the video stream information isn't reliable for + # some cameras (eg. Olympus FE models), so use the video stream information + # only if the RIFF header duration is 2 to 3 times longer + my $dur1; + $dur1 = $val[1] / $val[0] if $val[0]; + if ($val[2] and $val[3]) { + my $dur2 = $val[3] / $val[2]; + my $rat = $dur1 / $dur2; + $dur1 = $dur2 if $rat > 1.9 and $rat < 3.1; + } + $totalDuration += $dur1 if defined $dur1; + last unless $subDoc++ < $$et{DOC_COUNT}; + # get tag values for next sub-document + my @tags = qw(FrameRate FrameCount VideoFrameRate VideoFrameCount); + my $rawValue = $$et{VALUE}; + my ($i, $j, $key, $keys); + for ($i=0; $i<@tags; ++$i) { + if ($subDoc == 1) { + # generate list of available keys for each tag + $keys = $keyList[$i] = [ ]; + for ($j=0; ; ++$j) { + $key = $tags[$i]; + $key .= " ($j)" if $j; + last unless defined $$rawValue{$key}; + push @$keys, $key; + } + } else { + $keys = $keyList[$i]; + } + # find key for tag in this sub-document + my $grp = "Doc$subDoc"; + $grp .= ":RIFF" if $i < 2; # (tags 0 and 1 also in RIFF group) + $key = $et->GroupMatches($grp, $keys); + $val[$i] = $key ? $$rawValue{$key} : undef; + } + last unless defined $val[0] and defined $val[1]; # (Require'd tags) + } + return $totalDuration; +} + +#------------------------------------------------------------------------------ +# Process stream data +# Inputs: 0) ExifTool object ref, 1) dirInfo reference, 2) tag table ref +# Returns: 1 on success +sub ProcessStreamData($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart}; + my $size = $$dirInfo{DirLen}; + return 0 if $size < 4; + if ($et->Options('Verbose')) { + $et->VerboseDir($$dirInfo{DirName}, 0, $size); + } + my $tag = substr($$dataPt, $start, 4); + my $tagInfo = $et->GetTagInfo($tagTbl, $tag); + unless ($tagInfo) { + $tagInfo = $et->GetTagInfo($tagTbl, 'unknown'); + return 1 unless $tagInfo; + } + my $subdir = $$tagInfo{SubDirectory}; + if ($$tagInfo{SubDirectory}) { + my $offset = $$subdir{Start} || 0; + my $baseShift = $$dirInfo{DataPos} + $$dirInfo{DirStart} + $offset; + my %subdirInfo = ( + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos} - $baseShift, + Base => ($$dirInfo{Base} || 0) + $baseShift, + DataLen => $$dirInfo{DataLen}, + DirStart=> $$dirInfo{DirStart} + $offset, + DirLen => $$dirInfo{DirLen} - $offset, + DirName => $$subdir{DirName}, + Parent => $$dirInfo{DirName}, + ); + unless ($offset) { + # allow processing of 2nd directory at the same address + my $addr = $subdirInfo{DirStart} + $subdirInfo{DataPos} + $subdirInfo{Base}; + delete $$et{PROCESSED}{$addr} + } + # (we could set FIRST_EXIF_POS to $subdirInfo{Base} here to make + # htmlDump offsets relative to EXIF base if we wanted...) + my $subTable = GetTagTable($$subdir{TagTable}); + $et->ProcessDirectory(\%subdirInfo, $subTable); + } else { + $et->HandleTag($tagTbl, $tag, undef, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Start => $start, + Size => $size, + TagInfo => $tagInfo, + ); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Make tag information hash for unknown tag +# Inputs: 0) Tag table ref, 1) tag ID +sub MakeTagInfo($$) +{ + my ($tagTbl, $tag) = @_; + my $name = $tag; + my $n = ($name =~ s/([\x00-\x1f\x7f-\xff])/'x'.unpack('H*',$1)/eg); + # print in hex if tag is numerical + $name = sprintf('0x%.4x',unpack('N',$tag)) if $n > 2; + AddTagToTable($tagTbl, $tag, { + Name => "Unknown_$name", + Description => "Unknown $name", + Unknown => 1, + Binary => 1, + }); +} + +#------------------------------------------------------------------------------ +# Process RIFF chunks +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessChunks($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart}; + my $size = $$dirInfo{DirLen}; + my $end = $start + $size; + my $base = $$dirInfo{Base} || 0; + my $verbose = $et->Options('Verbose'); + my $unknown = $et->Options('Unknown'); + my $charset = $et->Options('CharsetRIFF'); + + unless ($charset) { + if ($$et{CodePage}) { + $charset = $$et{CodePage}; + } elsif (defined $charset and $charset eq '0') { + $charset = 'Latin'; + } + } + + $et->VerboseDir($$dirInfo{DirName}, 0, $size) if $verbose; + + while ($start + 8 < $end) { + my $tag = substr($$dataPt, $start, 4); + my $len = Get32u($dataPt, $start + 4); + $start += 8; + if ($start + $len > $end) { + $et->Warn("Bad $tag chunk"); + return 0; + } + if ($tag eq 'LIST' and $len >= 4) { + $tag .= '_' . substr($$dataPt, $start, 4); + $len -= 4; + $start += 4; + } + my $tagInfo = $et->GetTagInfo($tagTbl, $tag); + my $baseShift = 0; + my $val; + if ($tagInfo) { + if ($$tagInfo{SubDirectory}) { + # adjust base if necessary (needed for Ricoh maker notes) + my $newBase = $tagInfo->{SubDirectory}{Base}; + if (defined $newBase) { + # different than your average Base eval... + # here we use an absolute $start address + $start += $base; + #### eval Base ($start) + $newBase = eval $newBase; + $baseShift = $newBase - $base; + $start -= $base; + } + } elsif (not $$tagInfo{Binary}) { + my $format = $$tagInfo{Format} || $$tagTbl{FORMAT}; + if ($format and $format eq 'string') { + $val = substr($$dataPt, $start, $len); + $val =~ s/\0+$//; # remove trailing nulls from strings + # decode if necessary + $val = $et->Decode($val, $charset) if $charset; + } + } + } elsif ($verbose or $unknown) { + MakeTagInfo($tagTbl, $tag); + } + $et->HandleTag($tagTbl, $tag, $val, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos} - $baseShift, + Start => $start, + Size => $len, + Base => $base + $baseShift, + Addr => $base + $baseShift + $start, + ); + ++$len if $len & 0x01; # must account for padding if odd number of bytes + $start += $len; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process BikeBro SGLT chunk (accelerometer data) (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessSGLT($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = length $$dataPt; + my $ee = $et->Options('ExtractEmbedded'); + my $pos; + # example accelerometer record: + # 0 1 2 3 4 5 6 7 + # 00 00 00 24 02 00 00 01 17 04 00 00 00 00 00 00 00 00 9b 02 + # frame------ ?? Xs X---------- Ys Y---------- Zs Z---------- + $$et{SET_GROUP0} = $$et{SET_GROUP1} = 'RIFF'; + for ($pos=0; $pos<=$dataLen-20; $pos+=20) { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + my $buff = substr($$dataPt, $pos); + my @a = unpack('NCCNCNCN', $buff); + my @acc = ($a[3]*($a[2]?-1:1)/1e5, $a[5]*($a[4]?-1:1)/1e5, $a[7]*($a[6]?-1:1)/1e5); + $et->HandleTag($tagTbl, FrameNumber => $a[0]); + $et->HandleTag($tagTbl, Accelerometer => "@acc"); + unless ($ee) { + $et->Warn('Use ExtractEmbedded option to extract all accelerometer data', 3); + last; + } + } + delete $$et{SET_GROUP0}; + delete $$et{SET_GROUP1}; + $$et{DOC_NUM} = 0; + return 0; +} + +#------------------------------------------------------------------------------ +# Process BikeBro SLLT chunk (GPS information) (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessSLLT($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = length $$dataPt; + my $ee = $et->Options('ExtractEmbedded'); + my $pos; + # example GPS record: + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + # 00 00 00 17 01 00 00 03 fa 21 ec 00 35 01 6e c0 06 00 08 00 62 10 0b 1b 07 e2 03 0e 57 4e + # frame------ ?? lonDD lonDDDDDDDD latDD latDDDDDDDD alt-- spd-- hr mn sc yr--- mn dy EW NS + $$et{SET_GROUP0} = $$et{SET_GROUP1} = 'RIFF'; + for ($pos=0; $pos<=$dataLen-30; $pos+=30) { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + my $buff = substr($$dataPt, $pos); + my @a = unpack('NCnNnNnnCCCnCCaa', $buff); + # - is $a[1] perhaps GPSStatus? (only seen 1, or perhaps record type 1=GPS, 2=acc?) + my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', @a[11..13, 8..10]); + $et->HandleTag($tagTbl, FrameNumber => $a[0]); + $et->HandleTag($tagTbl, GPSDateTime => $time); + $et->HandleTag($tagTbl, GPSLatitude => ($a[4] + $a[5]/1e8) * ($a[15] eq 'S' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSLongitude => ($a[2] + $a[3]/1e8) * ($a[14] eq 'W' ? -1 : 1)); + $et->HandleTag($tagTbl, GPSAltitude => $a[6]); + $et->HandleTag($tagTbl, GPSSpeed => $a[7]); + $et->HandleTag($tagTbl, GPSSpeedRef => 'K'); + unless ($ee) { + $et->Warn('Use ExtractEmbedded option to extract timed GPS', 3); + last; + } + } + delete $$et{SET_GROUP0}; + delete $$et{SET_GROUP1}; + $$et{DOC_NUM} = 0; + return 1; +} + +#------------------------------------------------------------------------------ +# Process Lucas streaming GPS information (Lucas LK-7900 Ace) (ref PH) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessLucas($$$) +{ + my ($et, $dirInfo, $tagTbl) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = length $$dataPt; + + unless ($et->Options('ExtractEmbedded')) { + $et->Warn('Use ExtractEmbedded option to extract timed GPS', 3); + return 1; + } + my %recLen = ( # record lengths (not including 4-byte ID) + '0GDA' => 24, + '0GPS' => 48, + ); + my ($date,$time,$lat,$lon,$alt,$spd,$sat,$dop,$ew,$ns); + $$et{SET_GROUP0} = $$et{SET_GROUP1} = 'RIFF'; + while ($$dataPt =~ /(0GDA|0GPS)/g) { + my ($rec, $pos) = ($1, pos $$dataPt); + $pos + $recLen{$rec} > $dataLen and $et->Warn("Truncated $1 record"), last; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + # records start with int64u sample date/time in ms since 1970 + $et->HandleTag($tagTbl, SampleDateTime => Get64u($dataPt, $pos) / 1000); + if ($rec eq '0GPS') { + my $len = Get32u($dataPt, $pos+8); + my $endPos = $pos + $recLen{$rec} + $len; + $endPos > $dataLen and $et->Warn('Truncated 0GPS record'), last; + my $buff = substr($$dataPt, $pos+$recLen{$rec}, $len); + while ($buff =~ /\$(GC|GA),(\d+),/g) { + my $p = pos $buff; + $time = $2; + if ($1 eq 'GC') { + # time date dist ? sat dop alt A + # $GC,052350,180914,0000955,1,08,1.1,0017,,A*45\x0d\x0a\0 + if ($buff =~ /\G(\d+),\d*,\d*,(\d+),([-\d.]+),(\d+),\d*,A/g) { + ($date,$sat,$dop,$alt) = ($1,$2,$3,$4); + } + } else { + # time A lat lon spd N W + # $GA,052351,A,0949.6626,07635.4439,049,N,E,*4C\x0d\x0a\0 + if ($buff =~ /\GA,([\d.]+),([\d.]+),(\d+),([NS]),([EW])/g) { + ($lat,$lon,$spd,$ns,$ew) = ($1,$2,$3,$4,$5,$6); + # lat/long are in DDDMM.MMMM format + my $deg = int($lat / 100); + $lat = $deg + ($lat - $deg * 100) / 60; + $deg = int($lon / 100); + $lon = $deg + ($lon - $deg * 100) / 60; + $lat *= -1 if $ns eq 'S'; + $lon *= -1 if $ew eq 'W'; + } + } + # look ahead to next NMEA-like sentence, and store the fix + # now only if the next sentence is not at the same time + if ($buff !~ /\$(GC|GA),$time,/g) { + pos($$dataPt) = $endPos; + if ($$dataPt !~ /\$(GC|GA),(\d+)/ or $1 ne $time) { + $time =~ s/(\d{2})(\d{2})(\d{2})/$1:$2:$3Z/; + if ($date) { + $date =~ s/(\d{2})(\d{2})(\d{2})/20$3:$2:$1/; + $et->HandleTag($tagTbl, GPSDateTime => "$date $time"); + } else { + $et->HandleTag($tagTbl, GPSTimeStamp => $time); + } + if (defined $lat) { + $et->HandleTag($tagTbl, GPSLatitude => $lat); + $et->HandleTag($tagTbl, GPSLongitude => $lon); + $et->HandleTag($tagTbl, GPSSpeed => $spd); + } + if (defined $alt) { + $et->HandleTag($tagTbl, GPSAltitude => $alt); + $et->HandleTag($tagTbl, GPSSatellites => $sat); + $et->HandleTag($tagTbl, GPSDOP => $dop); + } + undef $lat; + undef $alt; + } + } + pos($buff) = $p; + } + $pos += $len; + } else { # this is an accelerometer (0GDA) record + # record has 4 more int32s values (the last is always 57 or 58 -- + # maybe related to sample time in ms? -- not extracted) + my @acc = unpack('x'.($pos+8).'V3', $$dataPt); + # change to signed integer and divide by 256 + map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc; + $et->HandleTag($tagTbl, Accelerometer => "@acc"); + } + pos($$dataPt) = $pos + $recLen{$rec}; + } + delete $$et{SET_GROUP0}; + delete $$et{SET_GROUP1}; + $$et{DOC_NUM} = 0; + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from a RIFF file +# Inputs: 0) ExifTool object reference, 1) DirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid RIFF file +sub ProcessRIFF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $buf2, $type, $mime, $err, $rf64); + my $verbose = $et->Options('Verbose'); + my $unknown = $et->Options('Unknown'); + my $validate = $et->Options('Validate'); + my $ee = $et->Options('ExtractEmbedded'); + my $hash = $$et{ImageDataHash}; + + # verify this is a valid RIFF file + return 0 unless $raf->Read($buff, 12) == 12; + if ($buff =~ /^(RIFF|RF64)....(.{4})/s) { + $type = $riffType{$2}; + $rf64 = 1 if $1 eq 'RF64'; + } else { + # minimal support for a few obscure lossless audio formats... + return 0 unless $buff =~ /^(LA0[234]|OFR |LPAC|wvpk)/ and $raf->Read($buf2, 1024); + $type = $riffType{$1}; + $buff .= $buf2; + return 0 unless $buff =~ /WAVE(.{4})?fmt /sg and $raf->Seek(pos($buff) - 4, 0); + } + $$raf{NoBuffer} = 1 if $et->Options('FastScan'); # disable buffering in FastScan mode + $mime = $riffMimeType{$type} if $type; + $et->SetFileType($type, $mime); + $$et{VALUE}{FileType} .= ' (RF64)' if $rf64 and $$et{VALUE}{FileType}; + $$et{RIFFStreamType} = ''; # initialize stream type + $$et{RIFFStreamCodec} = []; # initialize codec array + SetByteOrder('II'); + my $riffEnd = Get32u(\$buff, 4) + 8; + $riffEnd += $riffEnd & 0x01; # (account for padding) + my $tagTbl = GetTagTable('Image::ExifTool::RIFF::Main'); + my $pos = 12; +# +# Read chunks in RIFF image +# + for (;;) { + my $num = $raf->Read($buff, 8); + if ($num < 8) { + $err = 1 if $num; + $et->Warn('Incorrect RIFF chunk size' . " $pos vs. $riffEnd") if $validate and $pos != $riffEnd; + last; + } + $pos += 8; + my ($tag, $len) = unpack('a4V', $buff); + # tweak WEBP type if this is an extended WebP + $et->OverrideFileType('Extended WEBP',undef,'webp') if $tag eq 'VP8X' and $type eq 'WEBP'; + # special case: construct new tag name from specific LIST type + if ($tag eq 'LIST') { + $raf->Read($buff, 4) == 4 or $err=1, last; + $pos += 4; + $tag .= "_$buff"; + $len -= 4; # already read 4 bytes (the LIST type) + } elsif ($tag eq 'data' and $len == 0xffffffff and $$et{DataSize64}) { + $len = $$et{DataSize64}; + } + $et->VPrint(0, "RIFF '${tag}' chunk ($len bytes of data):\n"); + if ($len <= 0) { + if ($len < 0) { + $et->Warn('Invalid chunk length'); + } elsif ($tag eq "\0\0\0\0") { + # avoid reading through corupted files filled with nulls because it takes forever + $et->Warn('Encountered empty null chunk. Processing aborted'); + } else { + next; + } + last; + } + # stop when we hit the audio data or AVI index or AVI movie data + # --> no more because Adobe Bridge stores XMP after this!! + # (so now we only do this on the FastScan option) + if ($et->Options('FastScan') and ($tag eq 'data' or $tag eq 'idx1' or + ($tag eq 'LIST_movi' and not $ee))) + { + $et->VPrint(0, "(end of parsing)\n"); + last; + } + # RIFF chunks are padded to an even number of bytes + my $len2 = $len + ($len & 0x01); + # change name of stream txts data depending on the Codec + if ($ee and $tag =~ /^(\d{2})tx$/) { + $tag = 'tx_' . ($$et{RIFFStreamCodec}[$1] || 'Unknown'); + $tag = "tx_Unknown" unless defined $$tagTbl{$tag}; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + } + my $tagInfo = $$tagTbl{$tag}; + # (in LIST_movi chunk: ##db = uncompressed DIB, ##dc = compressed DIB, ##wb = audio data) + if ($tagInfo or (($verbose or $unknown) and $tag !~ /^(data|idx1|LIST_movi|RIFF|\d{2}(db|dc|wb))$/)) { + $raf->Read($buff, $len2) == $len2 or $err=1, last; + if ($hash and $isImageData{$tag}) { + $hash->add($buff); + $et->VPrint(0, "$$et{INDENT}(ImageDataHash: '${tag}' chunk, $len2 bytes)\n"); + } + my $setGroups; + if ($tagInfo and ref $tagInfo eq 'HASH' and $$tagInfo{SetGroups}) { + $setGroups = $$et{SET_GROUP0} = $$et{SET_GROUP1} = $$tagInfo{SetGroups}; + } + MakeTagInfo($tagTbl, $tag) if not $tagInfo and ($verbose or $unknown); + $et->HandleTag($tagTbl, $tag, $buff, + DataPt => \$buff, + DataPos => 0, # (relative to Base) + Start => 0, + Size => $len, + Base => $pos, + ); + if ($setGroups) { + delete $$et{SET_GROUP0}; + delete $$et{SET_GROUP1}; + } + delete $$et{DOC_NUM} if $ee; + } elsif ($tag eq 'RIFF') { + $et->Warn('Incorrect RIFF chunk size') if $validate and $pos - 8 != $riffEnd; + $riffEnd += $len2 + 8; + # don't read into RIFF chunk (eg. concatenated video file) + $raf->Read($buff, 4) == 4 or $err=1, last; # (skip RIFF type word) + $pos += 4; + # extract information from remaining file as an embedded file + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + next; # (must not increment $pos) + } else { + my $rewind; + # do hash if required + if ($hash and $isImageData{$tag}) { + $rewind = $raf->Tell(); + $et->ImageDataHash($raf, $len2, "'${tag}' chunk"); + } + if ($tag eq 'LIST_movi' and $ee) { + $raf->Seek($rewind, 0) or $err = 1, last if $rewind; + next; # parse into movi chunk + } elsif (not $rewind) { + if ($len > 0x7fffffff and not $et->Options('LargeFileSupport')) { + $et->Warn("Stopped parsing at large $tag chunk (LargeFileSupport not set)"); + last; + } + if ($validate and $len2) { + # (must actually try to read something after seeking to detect error) + $raf->Seek($len2-1, 1) and $raf->Read($buff, 1) == 1 or $err = 1, last; + } else { + $raf->Seek($len2, 1) or $err=1, last; + } + } + } + $pos += $len2; + } + delete $$et{DOC_NUM}; + $err and $et->Warn('Error reading RIFF file (corrupted?)'); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::RIFF - Read RIFF/AVI/WAV meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to extract +information from RIFF-based (Resource Interchange File Format) files, +including AVI videos, WAV audio files and WEBP images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.exif.org/Exif2-2.PDF> + +=item L<http://www.vlsi.fi/datasheets/vs1011.pdf> + +=item L<http://www.music-center.com.br/spec_rif.htm> + +=item L<http://www.codeproject.com/audio/wavefiles.asp> + +=item L<http://msdn.microsoft.com/archive/en-us/directx9_c/directx/htm/avirifffilereference.asp> + +=item L<http://wiki.multimedia.cx/index.php?title=TwoCC> + +=item L<https://developers.google.com/speed/webp/docs/riff_container> + +=item L<https://tech.ebu.ch/docs/tech/tech3306-2009.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/RIFF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/RSRC.pm b/ExifTool/lib/Image/ExifTool/RSRC.pm new file mode 100644 index 0000000..40932d7 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/RSRC.pm @@ -0,0 +1,247 @@ +#------------------------------------------------------------------------------ +# File: RSRC.pm +# +# Description: Read Mac OS Resource information +# +# Revisions: 2010/03/17 - P. Harvey Created +# +# References: 1) http://developer.apple.com/legacy/mac/library/documentation/mac/MoreToolbox/MoreToolbox-99.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::RSRC; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.09'; + +sub ProcessRSRC($$); + +# Information decoded from Mac OS resources +%Image::ExifTool::RSRC::Main = ( + GROUPS => { 2 => 'Document' }, + PROCESS_PROC => \&ProcessRSRC, + NOTES => q{ + Tags extracted from Mac OS resource files, DFONT files and "._" sidecar + files. These tags may also be extracted from the resource fork of any file + in OS X, either by adding "/..namedfork/rsrc" to the filename to process the + resource fork alone, or by using the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> (-ee) option to process + the resource fork as a sub-document of the main file. When writing, + ExifTool preserves the Mac OS resource fork by default, but it may deleted + with C<-rsrc:all=> on the command line. + }, + '8BIM' => { + Name => 'PhotoshopInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Main' }, + }, + 'sfnt' => { + Name => 'Font', + SubDirectory => { TagTable => 'Image::ExifTool::Font::Name' }, + }, + # my samples of postscript-type DFONT files have a POST resource + # with ID 0x1f5 and the same format as a PostScript file + 'POST_0x01f5' => { + Name => 'PostscriptFont', + SubDirectory => { TagTable => 'Image::ExifTool::PostScript::Main' }, + }, + 'usro_0x0000' => 'OpenWithApplication', + 'vers_0x0001' => 'ApplicationVersion', + 'STR _0xbff3' => 'ApplicationMissingMsg', + 'STR _0xbff4' => 'CreatorApplication', + # the following written by Photoshop + # (ref http://www.adobe.ca/devnet/photoshop/psir/ps_image_resources.pdf) + 'STR#_0x0080' => 'Keywords', + 'TEXT_0x0080' => 'Description', + # don't extract PICT's because the clip region isn't set properly + # in the PICT resource for some reason. Also, a dummy 512-byte + # header would have to be added to create a valid PICT file. + # 'PICT' => { Name => 'PreviewPICT', Binary => 1 }, +); + +#------------------------------------------------------------------------------ +# Read information from a Mac resource file (ref 1) +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid resource file +sub ProcessRSRC($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($hdr, $map, $buff, $i, $j); + + # allow access with data reference + $raf or $raf = new File::RandomAccess($$dirInfo{DataPt}); + + # attempt to validate the format as thoroughly as practical + return 0 unless $raf->Read($hdr, 30) == 30; + my ($datOff, $mapOff, $datLen, $mapLen) = unpack('N*', $hdr); + return 0 unless $raf->Seek(0, 2); + my $fLen = $raf->Tell(); + return 0 if $datOff < 0x10 or $datOff + $datLen > $fLen; + return 0 if $mapOff < 0x10 or $mapOff + $mapLen > $fLen or $mapLen < 30; + return 0 if $datOff < $mapOff and $datOff + $datLen > $mapOff; + return 0 if $mapOff < $datOff and $mapOff + $mapLen > $datOff; + + # read the resource map + $raf->Seek($mapOff, 0) and $raf->Read($map, $mapLen) == $mapLen or return 0; + SetByteOrder('MM'); + my $typeOff = Get16u(\$map, 24); + my $nameOff = Get16u(\$map, 26); + my $numTypes = (Get16u(\$map, 28) + 1) & 0xffff; + + # validate offsets in the resource map + return 0 if $typeOff < 28 or $nameOff < 30; + + $et->SetFileType('RSRC') unless $$et{IN_RESOURCE}; + my $verbose = $et->Options('Verbose'); + my $tagTablePtr = GetTagTable('Image::ExifTool::RSRC::Main'); + $et->VerboseDir('RSRC', $numTypes); + + # parse resource type list + for ($i=0; $i<$numTypes; ++$i) { + my $off = $typeOff + 2 + 8 * $i; # offset of entry in type list + last if $off + 8 > $mapLen; + my $resType = substr($map,$off,4); # resource type + my $resNum = Get16u(\$map,$off+4); # number of resources - 1 + my $refOff = Get16u(\$map,$off+6) + $typeOff; # offset to first resource reference + # loop through all resources + for ($j=0; $j<=$resNum; ++$j) { + my $roff = $refOff + 12 * $j; + last if $roff + 12 > $mapLen; + # read only the 24-bit resource data offset + my $id = Get16u(\$map,$roff); + my $resOff = (Get32u(\$map,$roff+4) & 0x00ffffff) + $datOff; + my $resNameOff = Get16u(\$map,$roff+2) + $nameOff + $mapOff; + my ($tag, $val, $valLen); + my $tagInfo = $$tagTablePtr{$resType}; + if ($tagInfo) { + $tag = $resType; + } else { + $tag = sprintf('%s_0x%.4x', $resType, $id); + $tagInfo = $$tagTablePtr{$tag}; + } + # read the resource data if necessary + if ($tagInfo or $verbose) { + unless ($raf->Seek($resOff, 0) and $raf->Read($buff, 4) == 4 and + ($valLen = unpack('N', $buff)) < 100000000 and # arbitrary size limit (100MB) + $raf->Read($val, $valLen) == $valLen) + { + $et->Warn("Error reading $resType resource"); + next; + } + } + if ($verbose) { + my ($resName, $nameLen); + $resName = '' unless $raf->Seek($resNameOff, 0) and $raf->Read($buff, 1) and + ($nameLen = ord $buff) != 0 and $raf->Read($resName, $nameLen) == $nameLen; + $et->VPrint(0,sprintf("%s resource ID 0x%.4x (offset 0x%.4x, $valLen bytes, name='%s'):\n", + $resType, $id, $resOff, $resName)); + $et->VerboseDump(\$val); + } + next unless $tagInfo; + if ($resType eq 'vers') { + # parse the 'vers' resource to get the long version string + next unless $valLen > 8; + # long version string is after short version + my $p = 7 + Get8u(\$val, 6); + next if $p >= $valLen; + my $vlen = Get8u(\$val, $p++); + next if $p + $vlen > $valLen; + my $tagTablePtr = GetTagTable('Image::ExifTool::RSRC::Main'); + $val = $et->Decode(substr($val, $p, $vlen), 'MacRoman'); + } elsif ($resType eq 'sfnt') { + # parse the OTF font block + $raf->Seek($resOff + 4, 0) or next; + $$dirInfo{Base} = $resOff + 4; + require Image::ExifTool::Font; + unless (Image::ExifTool::Font::ProcessOTF($et, $dirInfo)) { + $et->Warn('Unrecognized sfnt resource format'); + } + # assume this is a DFONT file unless processing the rsrc fork + $et->OverrideFileType('DFONT') unless $$et{DOC_NUM}; + next; + } elsif ($resType eq '8BIM') { + my $ttPtr = GetTagTable('Image::ExifTool::Photoshop::Main'); + $et->HandleTag($ttPtr, $id, $val, + DataPt => \$val, + DataPos => $resOff + 4, + Size => $valLen, + Start => 0, + Parent => 'RSRC', + ); + next; + } elsif ($resType eq 'STR ' and $valLen > 1) { + # extract Pascal string + my $len = ord $val; + next unless $valLen >= $len + 1; + $val = substr($val, 1, $len); + } elsif ($resType eq 'usro' and $valLen > 4) { + my $len = unpack('N', $val); + next unless $valLen >= $len + 4; + ($val = substr($val, 4, $len)) =~ s/\0.*//g; # truncate at null + } elsif ($resType eq 'STR#' and $valLen > 2) { + # extract list of strings (ref http://simtech.sourceforge.net/tech/strings.html) + my $num = unpack('n', $val); + next if $num & 0xf000; # (ignore special-format STR# resources) + my ($i, @vals); + my $pos = 2; + for ($i=0; $i<$num; ++$i) { + last if $pos >= $valLen; + my $len = ord substr($val, $pos++, 1); + last if $pos + $len > $valLen; + push @vals, substr($val, $pos, $len); + $pos += $len; + } + $val = \@vals; + } elsif ($resType eq 'POST') { + # assume this is a DFONT file unless processing the rsrc fork + $et->OverrideFileType('DFONT') unless $$et{DOC_NUM}; + $val = substr $val, 2; + } elsif ($resType ne 'TEXT') { + next; + } + $et->HandleTag($tagTablePtr, $tag, $val); + } + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::RSRC - Read Mac OS Resource information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read Mac OS +resource files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://developer.apple.com/legacy/mac/library/documentation/mac/MoreToolbox/MoreToolbox-99.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/RSRC Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/RTF.pm b/ExifTool/lib/Image/ExifTool/RTF.pm new file mode 100644 index 0000000..0e2b82c --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/RTF.pm @@ -0,0 +1,390 @@ +#------------------------------------------------------------------------------ +# File: RTF.pm +# +# Description: Read Rich Text Format meta information +# +# Revisions: 2010/06/17 - P. Harvey Created +# +# References: 1) http://download.microsoft.com/download/2/f/5/2f599e18-07ee-4ec5-a1e7-f4e6a9423592/Word2007RTFSpec9.doc +# 2) http://search.cpan.org/dist/RTF-Writer/lib/RTF/Cookbook.pod +#------------------------------------------------------------------------------ + +package Image::ExifTool::RTF; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.04'; + +sub ProcessUserProps($$$); + +# supported RTF character entities +my %rtfEntity = ( + par => 0x0a, + tab => 0x09, + endash => 0x2013, + emdash => 0x2014, + lquote => 0x2018, + rquote => 0x2019, + ldblquote => 0x201c, + rdblquote => 0x201d, + bullet => 0x2022, +); + +# RTF tags (ref 1) +%Image::ExifTool::RTF::Main = ( + GROUPS => { 2 => 'Document' }, + NOTES => q{ + This table lists standard tags of the RTF information group, but ExifTool + will also extract any non-standard tags found in this group. As well, + ExifTool will extract any custom properties that are found. See + L<http://www.microsoft.com/en-ca/download/details.aspx?id=10725> for the + specification. + }, + title => { }, + subject => { }, + author => { Groups => { 2 => 'Author' } }, + manager => { }, + company => { }, + copyright=> { Groups => { 2 => 'Author' } }, # (written by Apple TextEdit) + operator => { Name => 'LastModifiedBy' }, + category => { }, + keywords => { }, + comment => { }, + doccomm => { Name => 'Comments' }, + hlinkbase=> { Name => 'HyperlinkBase' }, + creatim => { + Name => 'CreateDate', + Format => 'date', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + revtim => { + Name => 'ModifyDate', + Format => 'date', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + printim => { + Name => 'LastPrinted', + Format => 'date', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + buptim => { + Name => 'BackupTime', + Format => 'date', + Groups => { 2 => 'Time' }, + PrintConv => '$self->ConvertDateTime($val)', + }, + edmins => { + Name => 'TotalEditTime', # in minutes + PrintConv => 'ConvertTimeSpan($val, 60)', + }, + nofpages => { Name => 'Pages' }, + nofwords => { Name => 'Words' }, + nofchars => { Name => 'Characters' }, + nofcharsws=>{ + Name => 'CharactersWithSpaces', + Notes => q{ + according to the 2007 Microsoft RTF specification this is clearly the number + of characters NOT including spaces, but Microsoft Word writes this as the + number WITH spaces, so ExifTool names this tag according to the de facto + standard + }, + }, + id => { Name => 'InternalIDNumber' }, + version => { Name => 'RevisionNumber' }, + vern => { Name => 'InternalVersionNumber' }, +); + +# lookup for user-defined properties +# (none are pre-defined and this table doesn't appear in the docs) +%Image::ExifTool::RTF::UserProps = ( + GROUPS => { 2 => 'Document' }, +); + +#------------------------------------------------------------------------------ +# Read to nested closing curly bracket "}" +# Inputs: 0) data ref, 1) optional RAF ref to read more data if available +# Returns: text inside brackets, or undef on error +# Notes: On entry the current position in the data must be set to immediately +# after the command that opens the bracket. On return the current +# position is immediately following the closing brace if the return +# value is defined. +sub ReadToNested($;$) +{ + my ($dataPt, $raf) = @_; + my $pos = pos $$dataPt; + my $level = 1; + for (;;) { + # look for the next bracket + unless ($$dataPt =~ /(\\*)([{}])/g) { + # must read some more data + my $p = length $$dataPt; + my $buff; + last unless $raf and $raf->Read($buff, 65536); + $$dataPt .= $buff; + # rewind position to include any leading backslashes + --$p while $p and substr($$dataPt, $p - 1, 1) eq '\\'; + pos($$dataPt) = $p; # set position to continue search + next; + } + # bracket is escaped if preceded by an odd number of backslashes + next if $1 and length($1) & 0x01; + $2 eq '{' and ++$level, next; + next unless --$level <= 0; + return substr($$dataPt, $pos, pos($$dataPt) - $pos - 1); + } + return undef; +} + +#------------------------------------------------------------------------------ +# Unescape RTF escape sequences +# Inputs: 0) ExifTool ref, 1) RTF text, 2) RTF character set (for hex characters) +# Returns: Unescaped text (in current ExifTool Charset) +sub UnescapeRTF($$$) +{ + my ($et, $val, $charset) = @_; + + # return now unless we have a control sequence + unless ($val =~ /\\/) { + $val =~ tr/\n\r//d; # ignore CR's and LF's + return $val; + } + # CR/LF is significant if it terminates a control sequence (so change these to a space) + # (was $val =~ s/(^|[^\\])((?:\\\\)*)(\\[a-zA-Z]+(?:-?\d+)?)[\n\r]/$1$2$3 /g;) + $val =~ s/\\(?:([a-zA-Z]+(?:-?\d+)?)[\n\r]|(.))/'\\'.($1 ? "$1 " : $2)/sge; + # protect the newline control sequence by converting to a \par command + # (was $val =~ s/(^|[^\\])((?:\\\\)*)(\\[\n\r])/$1$2\\par /g;) + $val =~ s/(\\[\n\r])|(\\.)/$2 || '\\par '/sge; + # all other CR/LF's are ignored (so delete them) + $val =~ tr/\n\r//d; + + my $rtnVal = ''; + my $len = length $val; + my $skip = 1; # default Unicode skip count + my $p0 = 0; + + for (;;) { + # find next backslash + my $p1 = ($val =~ /\\/g) ? pos($val) : $len + 1; + # add text up to start of this control sequence (or up to end) + my $n = $p1 - $p0 - 1; + $rtnVal .= substr($val, $p0, $n) if $n > 0; + # all done if at the end or if control sequence is empty + last if $p1 >= $len; + # look for an ASCII-letter control word or Unicode control + if ($val =~ /\G([a-zA-Z]+)(-?\d+)? ?/g) { + # interpret command if recognized + if ($1 eq 'uc') { # \ucN + $skip = $2; + } elsif ($1 eq 'u') { # \uN + if ($2 < 0) { + $et->WarnOnce('Invalid Unicode character(s) in text'); + $rtnVal .= '?'; + } else { + require Image::ExifTool::Charset; + $rtnVal .= Image::ExifTool::Charset::Recompose($et, [$2]); + if ($skip) { + # must skip the specified number of characters + # (not simple because RTF control words count as a single character) + last unless $val =~ /\G([^\\]|\\([a-zA-Z]+)(-?\d+)? ?|\\'.{2}|\\.){$skip}/g; + } + } + } elsif ($rtfEntity{$1}) { + require Image::ExifTool::Charset; + $rtnVal .= Image::ExifTool::Charset::Recompose($et, [$rtfEntity{$1}]); + } # (else ignore the command) + } else { + my $ch = substr($val, $p1, 1); + if ($ch eq "'") { + # hex character code + last if $p1 + 3 > $len; + my $hex = substr($val, $p1 + 1, 2); + if ($hex =~ /^[0-9a-fA-F]{2}$/) { + require Image::ExifTool::Charset; + $rtnVal .= $et->Decode(chr(hex($hex)), $charset); + } + pos($val) = $p1 + 3; # skip to after the hex code + } else { + # assume a standard control symbol (\, {, }, etc) + # (note, this may not be valid for some uncommon + # control symbols like \~ for non-breaking space) + $rtnVal .= $ch; + pos($val) = $p1 + 1; # skip to after this character + } + } + $p0 = pos($val); + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Read information in a RTF document +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid RTF file +sub ProcessRTF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $buf2, $cs); + + return 0 unless $raf->Read($buff, 64) and $raf->Seek(0,0); + return 0 unless $buff =~ /^[\n\r]*\{[\n\r]*\\rtf[^a-zA-Z]/; + $et->SetFileType(); +# +# determine the RTF character set +# + if ($buff=~ /\\ansicpg(\d*)/) { + $cs = "cp$1"; + } elsif ($buff=~ /\\(ansi|mac|pc|pca)[^a-zA-Z]/) { + my %trans = ( + ansi => 'Latin', + mac => 'MacRoman', + pc => 'cp437', + pca => 'cp850', + ); + $cs = $trans{$1}; + } else { + $et->Warn('Unspecified RTF encoding. Will assume Latin'); + $cs = 'Latin'; + } + my $charset = $Image::ExifTool::charsetName{lc $cs}; + unless ($charset) { + $et->Warn("Unsupported RTF encoding $cs. Will assume Latin."); + $charset = 'Latin'; + } + my $tagTablePtr = GetTagTable('Image::ExifTool::RTF::Main'); + undef $buff; +# +# scan for \info group +# + for (;;) { + $raf->Read($buf2, 65536) or last; + if (defined $buff) { + # read more but leave some overlap for the match + $buff = substr($buff, -16) . $buf2; + } else { + $buff = $buf2; + } + next unless $buff =~ /[^\\]\{[\n\r]*\\info([^a-zA-Z])/g; + # anything but a space is included in the contents + pos($buff) = pos($buff) - 1 if $1 ne ' '; + my $info = ReadToNested(\$buff, $raf); + unless (defined $info) { + $et->Warn('Unterminated information group'); + last; + } + # process info commands (eg. "\author", "\*\copyright"); + while ($info =~ /\{[\n\r]*(\\\*[\n\r]*)?\\([a-zA-Z]+)([^a-zA-Z])/g) { + pos($info) = pos($info) - 1 if $3 ne ' '; + my $tag = $2; + my $val = ReadToNested(\$info); + last unless defined $val; + my $tagInfo = $$tagTablePtr{$tag}; + if ($tagInfo and $$tagInfo{Format} and $$tagInfo{Format} eq 'date') { + # parse RTF date commands + my %idx = (yr=>0,mo=>1,dy=>2,hr=>3,min=>4,sec=>5); + my @t = (0) x 6; + while ($val =~ /\\([a-z]+)(\d+)/g) { + next unless defined $idx{$1}; + $t[$idx{$1}] = $2; + } + $val = sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d", @t); + } else { + # unescape RTF string value + $val = UnescapeRTF($et, $val, $charset); + } + # create tagInfo for unknown tags + if (not $tagInfo) { + AddTagToTable($tagTablePtr, $tag, { Name => ucfirst($tag) }); + } + $et->HandleTag($tagTablePtr, $tag, $val); + } + } + return 1 unless defined $buff; +# +# scan for \userprops (but don't read more from file to find the start of this command) +# + pos($buff) = 0; + while ($buff =~ /[^\\]\{[\n\r]*\\\*[\n\r]*\\userprops([^a-zA-Z])/g) { + # Note: The RTF spec places brackets around each propinfo structure, + # but Microsoft Word doesn't write it this way, so tolerate either. + pos($buff) = pos($buff) - 1 if $1 ne ' '; + my $props = ReadToNested(\$buff, $raf); + $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::RTF::UserProps'); + unless (defined $props) { + $et->Warn('Unterminated user properties'); + last; + } + # process user properties + my $tag; + while ($props =~ /\{[\n\r]*(\\\*[\n\r]*)?\\([a-zA-Z]+)([^a-zA-Z])/g) { + pos($props) = pos($props) - 1 if $3 ne ' '; + my $t = $2; + my $val = ReadToNested(\$props); + last unless defined $val; + $val = UnescapeRTF($et, $val, $charset); + if ($t eq 'propname') { + $tag = $val; + next; + } elsif ($t ne 'staticval' or not defined $tag) { + next; # ignore \linkval and \proptype for now + } + $tag =~ s/\s(.)/\U$1/g; # capitalize all words in tag name + $tag =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters + next unless $tag; + # create tagInfo for unknown tags + unless ($$tagTablePtr{$tag}) { + AddTagToTable($tagTablePtr, $tag, { Name => $tag }); + } + $et->HandleTag($tagTablePtr, $tag, $val); + } + last; # (didn't really want to loop) + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::RTF - Read Rich Text Format meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read meta +information from RTF (Rich Text Format) documents. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://download.microsoft.com/download/2/f/5/2f599e18-07ee-4ec5-a1e7-f4e6a9423592/Word2007RTFSpec9.doc> + +=item L<http://search.cpan.org/dist/RTF-Writer/lib/RTF/Cookbook.pod> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/RTF Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Radiance.pm b/ExifTool/lib/Image/ExifTool/Radiance.pm new file mode 100644 index 0000000..9976cf7 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Radiance.pm @@ -0,0 +1,149 @@ +#------------------------------------------------------------------------------ +# File: Radiance.pm +# +# Description: Read Radiance RGBE HDR meta information +# +# Revisions: 2011/12/10 - P. Harvey Created +# +# References: 1) http://www.graphics.cornell.edu/online/formats/rgbe/ +# 2) http://radsite.lbl.gov/radiance/refer/filefmts.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::Radiance; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.02'; + +# Radiance tags +%Image::ExifTool::Radiance::Main = ( + GROUPS => { 2 => 'Image' }, + NOTES => q{ + Information extracted from Radiance RGBE HDR images. Tag ID's are all + uppercase as stored in the file, but converted to lowercase by when + extracting to avoid conflicts with internal ExifTool variables. See + L<http://radsite.lbl.gov/radiance/refer/filefmts.pdf> and + L<http://www.graphics.cornell.edu/online/formats/rgbe/> for the + specification. + }, + _orient => { + Name => 'Orientation', + PrintConv => { + '-Y +X' => 'Horizontal (normal)', + '-Y -X' => 'Mirror horizontal', + '+Y -X' => 'Rotate 180', + '+Y +X' => 'Mirror vertical', + '+X -Y' => 'Mirror horizontal and rotate 270 CW', + '+X +Y' => 'Rotate 90 CW', + '-X +Y' => 'Mirror horizontal and rotate 90 CW', + '-X -Y' => 'Rotate 270 CW', + }, + }, + _command => 'Command', + _comment => 'Comment', + software => 'Software', + view => 'View', + 'format' => 'Format', # <-- this is the one that caused the conflict when uppercase + exposure => { + Name => 'Exposure', + Notes => 'divide pixel values by this to get watts/steradian/meter^2', + }, + gamma => 'Gamma', + colorcorr => 'ColorCorrection', + pixaspect => 'PixelAspectRatio', + primaries => 'ColorPrimaries', +); + +#------------------------------------------------------------------------------ +# Extract information from a Radiance HDR file +# Inputs: 0) ExifTool object reference, 1) DirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid RGBE image +sub ProcessHDR($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + local $/ = "\x0a"; # set newline character for reading + + # verify this is a valid RIFF file + return 0 unless $raf->ReadLine($buff) and $buff =~ /^#\?(RADIANCE|RGBE)\x0a/s; + $et->SetFileType(); + my $tagTablePtr = GetTagTable('Image::ExifTool::Radiance::Main'); + + while ($raf->ReadLine($buff)) { + chomp $buff; + last unless length($buff) > 0 and length($buff) < 4096; + if ($buff =~ s/^#\s*//) { + $et->HandleTag($tagTablePtr, '_comment', $buff) if length $buff; + next; + } + unless ($buff =~ /^(.*)?\s*=\s*(.*)/) { + $et->HandleTag($tagTablePtr, '_command', $buff) if length $buff; + next; + } + # use lower-case tag names to avoid conflicts with reserved tag table entries + my ($tag, $val) = (lc $1, $2); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + unless ($tagInfo) { + my $name = $tag; + $name =~ tr/-_a-zA-Z0-9//dc; + next unless length($name) > 1; + $name = ucfirst $name; + $tagInfo = { Name => $name }; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + $et->FoundTag($tagInfo, $val); + } + # get image dimensions + if ($raf->ReadLine($buff) and $buff =~ /([-+][XY])\s*(\d+)\s*([-+][XY])\s*(\d+)/) { + $et->HandleTag($tagTablePtr, '_orient', "$1 $3"); + $et->FoundTag('ImageHeight', $2); + $et->FoundTag('ImageWidth', $4); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Radiance - Read Radiance RGBE HDR meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Radiance RGBE images. RGBE (Red Green Blue Exponent) +images are a type of high dynamic-range image. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://radsite.lbl.gov/radiance/refer/filefmts.pdf> + +=item L<http://www.graphics.cornell.edu/online/formats/rgbe/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Radiance Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Rawzor.pm b/ExifTool/lib/Image/ExifTool/Rawzor.pm new file mode 100644 index 0000000..be51abb --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Rawzor.pm @@ -0,0 +1,190 @@ +#------------------------------------------------------------------------------ +# File: Rawzor.pm +# +# Description: Read meta information from Rawzor compressed images +# +# Revisions: 09/09/2008 - P. Harvey Created +# +# References: 1) http://www.rawzor.com/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::Rawzor; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.06'; + +# currently support this version Rawzor images +my $implementedRawzorVersion = 199; # (up to version 1.99) + +# Rawzor-specific tags +%Image::ExifTool::Rawzor::Main = ( + GROUPS => { 2 => 'Other' }, + VARS => { NO_ID => 1 }, + NOTES => q{ + Rawzor files store compressed images of other formats. As well as the + information listed below, exiftool uncompresses and extracts the meta + information from the original image. + }, + OriginalFileType => { }, + OriginalFileSize => { + PrintConv => $Image::ExifTool::Extra{FileSize}->{PrintConv}, + }, + RawzorRequiredVersion => { + ValueConv => '$val / 100', + PrintConv => 'sprintf("%.2f", $val)', + }, + RawzorCreatorVersion => { + ValueConv => '$val / 100', + PrintConv => 'sprintf("%.2f", $val)', + }, + # compression factor is originalSize/compressedSize (and compression + # ratio is the inverse - ref "Data Compression" by David Salomon) + CompressionFactor => { PrintConv => 'sprintf("%.2f", $val)' }, +); + +#------------------------------------------------------------------------------ +# Extract information from a Rawzor file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid Rawzor file +sub ProcessRWZ($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $buf2); + + # read the Rawzor file header: + # 0 string - "rawzor" signature + # 6 int16u - Required SDK version + # 8 int16u - Creator SDK version + # 10 int64u - RWZ file size + # 18 int64u - original raw file size + # 26 undef[12] - reserved + # 38 int64u - metadata offset + $raf->Read($buff, 46) == 46 and $buff =~ /^rawzor/ or return 0; + + SetByteOrder('II'); + my $reqVers = Get16u(\$buff, 6); + my $creatorVers = Get16u(\$buff, 8); + my $rwzSize = Get64u(\$buff, 10); + my $origSize = Get64u(\$buff, 18); + my $tagTablePtr = GetTagTable('Image::ExifTool::Rawzor::Main'); + $et->HandleTag($tagTablePtr, RawzorRequiredVersion => $reqVers); + $et->HandleTag($tagTablePtr, RawzorCreatorVersion => $creatorVers); + $et->HandleTag($tagTablePtr, OriginalFileSize => $origSize); + $et->HandleTag($tagTablePtr, CompressionFactor => $origSize/$rwzSize) if $rwzSize; + # check version numbers + if ($reqVers > $implementedRawzorVersion) { + $et->Warn("Version $reqVers Rawzor images not yet supported"); + return 1; + } + my $metaOffset = Get64u(\$buff, 38); + if ($metaOffset > 0x7fffffff) { + $et->Warn('Bad metadata offset'); + return 1; + } + # check for the ability to uncompress the information + unless (eval { require IO::Uncompress::Bunzip2 }) { + $et->Warn('Install IO::Compress::Bzip2 to decode Rawzor bzip2 compression'); + return 1; + } + # read the metadata header: + # 0 int64u - metadata section 0 end (offset in original file) + # 8 int64u - metadata section 1 start + # 16 int64u - metadata section 1 end + # 24 int64u - metadata section 2 start + # 32 undef[4] - reserved + # 36 int32u - original metadata size + # 40 int32u - compressed metadata size + unless ($raf->Seek($metaOffset, 0) and $raf->Read($buff, 44) == 44) { + $et->Warn('Error reading metadata header'); + return 1; + } + my $metaSize = Get32u(\$buff, 36); + if ($metaSize) { + $$et{DontValidateImageData} = 1; + # validate the metadata header and read the compressed metadata + my $end0 = Get64u(\$buff, 0); + my $pos1 = Get64u(\$buff, 8); + my $end1 = Get64u(\$buff, 16); + my $pos2 = Get64u(\$buff, 24); + my $len = Get32u(\$buff, 40); + unless ($raf->Read($buff, $len) == $len and + $end0 + ($end1 - $pos1) + ($origSize - $pos2) == $metaSize and + $end0 <= $pos1 and $pos1 <= $end1 and $end1 <= $pos2) + { + $et->Warn('Error reading image metadata'); + return 1; + } + # uncompress the metadata + unless (IO::Uncompress::Bunzip2::bunzip2(\$buff, \$buf2) and + length($buf2) eq $metaSize) + { + $et->Warn('Error uncompressing image metadata'); + return 1; + } + # re-assemble the original file (sans image data) + undef $buff; # (can't hurt to free memory as soon as possible) + $buff = substr($buf2, 0, $end0) . ("\0" x ($pos1 - $end0)) . + substr($buf2, $end0, $end1 - $pos1) . ("\0" x ($pos2 - $end1)) . + substr($buf2, $end0 + $end1 - $pos1, $origSize - $pos2); + undef $buf2; + + # extract original information by calling ExtractInfo recursively + $et->ExtractInfo(\$buff, { ReEntry => 1 }); + undef $buff; + } + # set OriginalFileType from FileType of original file + # then change FileType and MIMEType to indicate a Rawzor image + my $origFileType = $$et{FileType}; + if ($origFileType) { + $et->HandleTag($tagTablePtr, OriginalFileType => $origFileType); + $et->OverrideFileType('RWZ'); + } else { + $et->HandleTag($tagTablePtr, OriginalFileType => 'Unknown'); + $et->SetFileType(); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Rawzor - Read meta information from Rawzor compressed images + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Rawzor compressed images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.rawzor.com/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Rawzor Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Real.pm b/ExifTool/lib/Image/ExifTool/Real.pm new file mode 100644 index 0000000..b22eb5f --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Real.pm @@ -0,0 +1,739 @@ +#------------------------------------------------------------------------------ +# File: Real.pm +# +# Description: Read Real audio/video meta information +# +# Revisions: 05/16/2006 - P. Harvey Created +# +# References: 1) http://www.getid3.org/ +# 2) https://common.helixcommunity.org/nonav/2003/HCS_SDK_r5/htmfiles/rmff.htm +#------------------------------------------------------------------------------ + +package Image::ExifTool::Real; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Canon; + +$VERSION = '1.07'; + +sub ProcessRealMeta($$$); +sub ProcessRealProperties($$$); + +# Real property types (ref PH) +my %propertyType = ( + 0 => 'int32u', + 2 => 'string', +); + +# Real Metadata property types +my %metadataFormat = ( + 1 => 'string', # text + 2 => 'string', # text list + 3 => 'flag', # 1 or 4 byte integer + 4 => 'int32u', # 4-byte integer + 5 => 'undef', # binary data + 6 => 'string', # URL + 7 => 'string', # date + 8 => 'string', # file name + 9 => undef, # grouping + 10 => undef, # reference +); + +# Real Metadata property flag bit descriptions +my %metadataFlag = ( + 0 => 'Read Only', + 1 => 'Private', + 2 => 'Type Descriptor', +); + + +# tags used in RealMedia (RM, RV and RMVB) files +%Image::ExifTool::Real::Media = ( + GROUPS => { 2 => 'Video' }, + NOTES => q{ + These B<Tag ID>'s are Chunk ID's used in RealMedia and RealVideo (RM, RV and + RMVB) files. + }, + CONT => { SubDirectory => { TagTable => 'Image::ExifTool::Real::ContentDescr' } }, + MDPR => { SubDirectory => { TagTable => 'Image::ExifTool::Real::MediaProps' } }, + PROP => { SubDirectory => { TagTable => 'Image::ExifTool::Real::Properties' } }, + RJMD => { SubDirectory => { TagTable => 'Image::ExifTool::Real::Metadata' } }, +); + +# pseudo-tags used in RealAudio (RA) files +%Image::ExifTool::Real::Audio = ( + GROUPS => { 2 => 'Audio' }, + NOTES => q{ + Tags in the following table reference information extracted from various + versions of RealAudio (RA) files. + }, + '.ra3' => { Name => 'RA3', SubDirectory => { TagTable => 'Image::ExifTool::Real::AudioV3' } }, + '.ra4' => { Name => 'RA4', SubDirectory => { TagTable => 'Image::ExifTool::Real::AudioV4' } }, + '.ra5' => { Name => 'RA5', SubDirectory => { TagTable => 'Image::ExifTool::Real::AudioV5' } }, +); + +# pseudo-tags used in RealMedia Metafiles and RealMedia Plug-in Metafiles (RAM and RPM) +%Image::ExifTool::Real::Metafile = ( + GROUPS => { 2 => 'Video' }, + NOTES => q{ + Tags representing information extracted from Real Audio Metafile and + RealMedia Plug-in Metafile (RAM and RPM) files. + }, + txt => 'Text', + url => 'URL', +); + +%Image::ExifTool::Real::Properties = ( + GROUPS => { 1 => 'Real-PROP', 2 => 'Video' }, + PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData, + VARS => { ID_LABEL => 'Sequence' }, + FORMAT => 'int32u', + 0 => { Name => 'MaxBitrate', PrintConv => 'ConvertBitrate($val)' }, + 1 => { Name => 'AvgBitrate', PrintConv => 'ConvertBitrate($val)' }, + 2 => 'MaxPacketSize', + 3 => 'AvgPacketSize', + 4 => 'NumPackets', + 5 => { Name => 'Duration', ValueConv => '$val / 1000', PrintConv => 'ConvertDuration($val)' }, + 6 => { Name => 'Preroll', ValueConv => '$val / 1000', PrintConv => 'ConvertDuration($val)' }, + 7 => { Name => 'IndexOffset', Unknown => 1 }, + 8 => { Name => 'DataOffset', Unknown => 1 }, + 9 => { Name => 'NumStreams', Format => 'int16u' }, + 10 => { + Name => 'Flags', + Format => 'int16u', + PrintConv => { BITMASK => { + 0 => 'Allow Recording', + 1 => 'Perfect Play', + 2 => 'Live', + 3 => 'Allow Download', #PH (from rmeditor dump) + } }, + }, +); + +%Image::ExifTool::Real::MediaProps = ( + GROUPS => { 1 => 'Real-MDPR', 2 => 'Video' }, + PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData, + VARS => { ID_LABEL => 'Sequence' }, + FORMAT => 'int32u', + PRIORITY => 0, # first stream takes priority + 0 => { Name => 'StreamNumber', Format => 'int16u' }, + 1 => { Name => 'StreamMaxBitrate', PrintConv => 'ConvertBitrate($val)' }, + 2 => { Name => 'StreamAvgBitrate', PrintConv => 'ConvertBitrate($val)' }, + 3 => { Name => 'StreamMaxPacketSize' }, + 4 => { Name => 'StreamAvgPacketSize' }, + 5 => { Name => 'StreamStartTime' }, + 6 => { Name => 'StreamPreroll', ValueConv => '$val / 1000', PrintConv => 'ConvertDuration($val)' }, + 7 => { Name => 'StreamDuration',ValueConv => '$val / 1000', PrintConv => 'ConvertDuration($val)' }, + 8 => { Name => 'StreamNameLen', Format => 'int8u', Unknown => 1 }, + 9 => { Name => 'StreamName', Format => 'string[$val{8}]' }, + 10 => { Name => 'StreamMimeLen', Format => 'int8u', Unknown => 1 }, + 11 => { + Name => 'StreamMimeType', + Format => 'string[$val{10}]', + RawConv => '$self->{RealStreamMime} = $val', + }, + 12 => { Name => 'FileInfoLen', Unknown => 1 }, + 13 => { + Name => 'FileInfoLen2', + # if this condition fails, subsequent tags will not be processed + Condition => '$self->{RealStreamMime} eq "logical-fileinfo"', + Unknown => 1, + }, + 14 => { + Name => 'FileInfoVersion', + Format => 'int16u', + }, + 15 => { + Name => 'PhysicalStreams', + Format => 'int16u', + Unknown => 1, + }, + 16 => { + Name => 'PhysicalStreamNumbers', + Format => 'int16u[$val{15}]', + Unknown => 1, + }, + 17 => { + Name => 'DataOffsets', + Format => 'int32u[$val{15}]', + Unknown => 1, + }, + 18 => { + Name => 'NumRules', + Format => 'int16u', + Unknown => 1, + }, + 19 => { + Name => 'PhysicalStreamNumberMap', + Format => 'int16u[$val{18}]', + Unknown => 1, + }, + 20 => { + Name => 'NumProperties', + Format => 'int16u', + Unknown => 1, + }, + 21 => { + Name => 'FileInfoProperties', + Format => 'undef[$val{13}-$val{15}*6-$val{18}*2-12]', + SubDirectory => { TagTable => 'Image::ExifTool::Real::FileInfo' }, + }, +); + +# Observed FileInfo properties (ref PH) +%Image::ExifTool::Real::FileInfo = ( + GROUPS => { 1 => 'Real-MDPR', 2 => 'Video' }, + PROCESS_PROC => \&ProcessRealProperties, + NOTES => q{ + The following tags have been observed in the FileInfo properties, but any + other existing information will also be extracted. + }, + Indexable => { PrintConv => { 0 => 'False', 1 => 'True' } }, + Keywords => { }, + Description => { }, + 'File ID' => { Name => 'FileID' }, + 'Content Rating' => { + Name => 'ContentRating', + PrintConv => { + 0 => 'No Rating', + 1 => 'All Ages', + 2 => 'Older Children', + 3 => 'Younger Teens', + 4 => 'Older Teens', + 5 => 'Adult Supervision Recommended', + 6 => 'Adults Only', + }, + }, + Audiences => { }, + audioMode => { Name => 'AudioMode' }, + 'Creation Date' => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + ValueConv => q{ + $val =~ m{(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)} ? + sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d",$3,$2,$1,$4,$5,$6) : $val + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 'Generated By' => { Name => 'Software' }, + 'Modification Date' => { + Name => 'ModifyDate', + Groups => { 2 => 'Time' }, + ValueConv => q{ + $val =~ m{(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)} ? + sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d",$3,$2,$1,$4,$5,$6) : $val + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 'Target Audiences' => { Name => 'TargetAudiences' }, + 'Audio Format' => { Name => 'AudioFormat' }, + 'Video Quality' => { Name => 'VideoQuality' }, + videoMode => { Name => 'VideoMode' }, +); + +%Image::ExifTool::Real::ContentDescr = ( + GROUPS => { 1 => 'Real-CONT', 2 => 'Video' }, + PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData, + VARS => { ID_LABEL => 'Sequence' }, + FORMAT => 'int16u', + 0 => { Name => 'TitleLen', Unknown => 1 }, + 1 => { Name => 'Title', Format => 'string[$val{0}]' }, + 2 => { Name => 'AuthorLen', Unknown => 1 }, + 3 => { Name => 'Author', Format => 'string[$val{2}]', Groups => { 2 => 'Author' } }, + 4 => { Name => 'CopyrightLen', Unknown => 1 }, + 5 => { Name => 'Copyright', Format => 'string[$val{4}]', Groups => { 2 => 'Author' } }, + 6 => { Name => 'CommentLen', Unknown => 1 }, + 7 => { Name => 'Comment', Format => 'string[$val{6}]' }, +); + +# Real RJMD meta information (ref PH) +%Image::ExifTool::Real::Metadata = ( + GROUPS => { 1 => 'Real-RJMD', 2 => 'Video' }, + PROCESS_PROC => \&ProcessRealMeta, + NOTES => q{ + The tags below represent information which has been observed in the Real + Metadata format, but ExifTool will extract any information it finds in this + format. (As far as I can tell from the referenced documentation, string + values should be plain text, but this is not the case for the only sample + file I have been able to obtain containing this information. These tags + could also be split into separate sub-directories, but this will wait until + I have better documentation or a more complete set of samples.) + }, + 'Album/Name' => 'AlbumName', + 'Track/Category' => 'TrackCategory', + 'Track/Comments' => 'TrackComments', + 'Track/Lyrics' => 'TrackLyrics', +); + +%Image::ExifTool::Real::AudioV3 = ( + GROUPS => { 1 => 'Real-RA3', 2 => 'Audio' }, + PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData, + VARS => { ID_LABEL => 'Sequence' }, + FORMAT => 'int8u', + 0 => { Name => 'Channels', Format => 'int16u' }, + 1 => { Name => 'Unknown', Format => 'int16u[3]', Unknown => 1 }, + 2 => { Name => 'BytesPerMinute', Format => 'int16u' }, + 3 => { Name => 'AudioBytes', Format => 'int32u' }, + 4 => { Name => 'TitleLen', Unknown => 1 }, + 5 => { Name => 'Title', Format => 'string[$val{4}]' }, + 6 => { Name => 'ArtistLen', Unknown => 1 }, + 7 => { Name => 'Artist', Format => 'string[$val{6}]', Groups => { 2 => 'Author' } }, + 8 => { Name => 'CopyrightLen', Unknown => 1 }, + 9 => { Name => 'Copyright', Format => 'string[$val{8}]', Groups => { 2 => 'Author' } }, + 10 => { Name => 'CommentLen', Unknown => 1 }, + 11 => { Name => 'Comment', Format => 'string[$val{10}]' }, +); + +%Image::ExifTool::Real::AudioV4 = ( + GROUPS => { 1 => 'Real-RA4', 2 => 'Audio' }, + PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData, + VARS => { ID_LABEL => 'Sequence' }, + FORMAT => 'int16u', + 0 => { Name => 'FourCC1', Format => 'undef[4]', Unknown => 1 }, + 1 => { Name => 'AudioFileSize', Format => 'int32u', Unknown => 1 }, + 2 => { Name => 'Version2', Unknown => 1 }, + 3 => { Name => 'HeaderSize', Format => 'int32u', Unknown => 1 }, + 4 => { Name => 'CodecFlavorID', Unknown => 1 }, + 5 => { Name => 'CodedFrameSize', Format => 'int32u', Unknown => 1 }, + 6 => { Name => 'AudioBytes', Format => 'int32u' }, + 7 => { Name => 'BytesPerMinute', Format => 'int32u' }, + 8 => { Name => 'Unknown', Format => 'int32u', Unknown => 1 }, + 9 => { Name => 'SubPacketH', Unknown => 1 }, + 10 => 'AudioFrameSize', + 11 => { Name => 'SubPacketSize', Unknown => 1 }, + 12 => { Name => 'Unknown', Unknown => 1 }, + 13 => 'SampleRate', + 14 => { Name => 'Unknown', Unknown => 1 }, + 15 => 'BitsPerSample', + 16 => 'Channels', + 17 => { Name => 'FourCC2Len', Format => 'int8u', Unknown => 1 }, + 18 => { Name => 'FourCC2', Format => 'undef[4]', Unknown => 1 }, + 19 => { Name => 'FourCC3Len', Format => 'int8u', Unknown => 1 }, + 20 => { Name => 'FourCC3', Format => 'undef[4]', Unknown => 1 }, + 21 => { Name => 'Unknown', Format => 'int8u', Unknown => 1 }, + 22 => { Name => 'Unknown', Unknown => 1 }, + 23 => { Name => 'TitleLen', Format => 'int8u', Unknown => 1 }, + 24 => { Name => 'Title', Format => 'string[$val{23}]' }, + 25 => { Name => 'ArtistLen', Format => 'int8u', Unknown => 1 }, + 26 => { Name => 'Artist', Format => 'string[$val{25}]', Groups => { 2 => 'Author' } }, + 27 => { Name => 'CopyrightLen', Format => 'int8u', Unknown => 1 }, + 28 => { Name => 'Copyright', Format => 'string[$val{27}]', Groups => { 2 => 'Author' } }, + 29 => { Name => 'CommentLen', Format => 'int8u', Unknown => 1 }, + 30 => { Name => 'Comment', Format => 'string[$val{29}]' }, +); + +%Image::ExifTool::Real::AudioV5 = ( + GROUPS => { 1 => 'Real-RA5', 2 => 'Audio' }, + PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData, + VARS => { ID_LABEL => 'Sequence' }, + FORMAT => 'int16u', + 0 => { Name => 'FourCC1', Format => 'undef[4]', Unknown => 1 }, + 1 => { Name => 'AudioFileSize', Format => 'int32u', Unknown => 1 }, + 2 => { Name => 'Version2', Unknown => 1 }, + 3 => { Name => 'HeaderSize', Format => 'int32u', Unknown => 1 }, + 4 => { Name => 'CodecFlavorID', Unknown => 1 }, + 5 => { Name => 'CodedFrameSize', Format => 'int32u', Unknown => 1 }, + 6 => { Name => 'AudioBytes', Format => 'int32u' }, + 7 => { Name => 'BytesPerMinute', Format => 'int32u' }, + 8 => { Name => 'Unknown', Format => 'int32u', Unknown => 1 }, + 9 => { Name => 'SubPacketH', Unknown => 1 }, + 10 => { Name => 'FrameSize', Unknown => 1 }, + 11 => { Name => 'SubPacketSize', Unknown => 1 }, + 12 => 'SampleRate', + 13 => { Name => 'SampleRate2', Unknown => 1 }, + 14 => { Name => 'BitsPerSample', Format => 'int32u' }, + 15 => 'Channels', + 16 => { Name => 'Genr', Format => 'int32u', Unknown => 1 }, + 17 => { Name => 'FourCC3', Format => 'undef[4]', Unknown => 1 }, +); + +#------------------------------------------------------------------------------ +# Process Real NameValueProperties +# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessRealProperties($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = $$dirInfo{DirLen}; + my $pos = $$dirInfo{DirStart}; + my $verbose = $et->Options('Verbose'); + + $verbose and $et->VerboseDir('RealProperties', undef, $dirLen); + + while ($pos + 6 <= $dirLen) { + + # get property size and version + my ($size, $vers) = unpack("x${pos}Nn", $$dataPt); + last if $size < 6; + unless ($vers == 0) { + $pos += $size; + next; + } + $pos += 6; + + my $tagLen = unpack("x${pos}C", $$dataPt); + ++$pos; + + last if $pos + $tagLen > $dirLen; + my $tag = substr($$dataPt, $pos, $tagLen); + $pos += $tagLen; + + last if $pos + 6 > $dirLen; + my ($type, $valLen) = unpack("x${pos}Nn", $$dataPt); + $pos += 6; + + last if $pos + $valLen > $dirLen; + my $format = $propertyType{$type} || 'undef'; + my $count = int($valLen / Image::ExifTool::FormatSize($format)); + my $val = ReadValue($dataPt, $pos, $format, $count, $dirLen-$pos); + + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + unless ($tagInfo) { + my $tagName; + ($tagName = $tag) =~ s/\s+//g; + next unless $tagName =~ /^\w+$/; # ignore crazy names + $tagInfo = { Name => ucfirst($tagName) }; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + if ($verbose) { + $et->VerboseInfo($tag, $tagInfo, + Table => $tagTablePtr, + Value => $val, + DataPt => $dataPt, + Size => $valLen, + Start => $pos, + Addr => $pos + $$dirInfo{DataPos}, + Format => $format, + Count => $count, + ); + } + $et->FoundTag($tagInfo, $val); + $pos += $valLen; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process Real metadata properties +# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessRealMeta($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $pos = $$dirInfo{DirStart}; + my $dirEnd = $pos + $$dirInfo{DirLen}; + my $verbose = $et->Options('Verbose'); + my $prefix = $$dirInfo{Prefix} || ''; + $prefix and $prefix .= '/'; + + $verbose and $et->VerboseDir('RealMetadata', undef, $$dirInfo{DirLen}); + + for (;;) { + last if $pos + 28 > $dirEnd; + # extract fixed-position metadata structure members + my ($size, $type, $flags, $valuePos, $subPropPos, $numSubProps, $nameLen) + = unpack("x${pos}N7", $$dataPt); + # make pointers relative to data start + $valuePos += $pos; + $subPropPos += $pos; + # validate what we have read so far + last if $pos + $size > $dirEnd; + last if $pos + 28 + $nameLen > $dirEnd; + last if $valuePos < $pos + 28 + $nameLen; + last if $valuePos + 4 > $dirEnd; + my $tag = substr($$dataPt, $pos + 28, $nameLen); + $tag =~ s/\0.*//s; # truncate at null + $tag = $prefix . $tag; + my $valueLen = unpack("x${valuePos}N", $$dataPt); + $valuePos += 4; # point at value itself + last if $valuePos + $valueLen > $dirEnd; + + my $format = $metadataFormat{$type}; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + unless ($tagInfo) { + my $tagName = $tag; + $tagName =~ tr/A-Za-z0-9//dc; + $tagInfo = { Name => ucfirst($tagName) }; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + if ($verbose) { + $format = 'undef' unless defined $format; + $flags = Image::ExifTool::DecodeBits($flags, \%metadataFlag); + } + if ($valueLen and $format) { + # (a flag can be 1 or 4 bytes) + if ($format eq 'flag') { + $format = ($valueLen == 4) ? 'int32u' : 'int8u'; + } elsif ($type == 7 and $tagInfo) { + # add PrintConv and ValueConv for "date" type + $$tagInfo{ValueConv} or $$tagInfo{ValueConv} = q{ + $val =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/ ? + sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d",$1,$2,$3,$4,$5,$6) : + $val; + }; + $$tagInfo{PrintConv} or $$tagInfo{PrintConv} = '$self->ConvertDateTime($val)'; + } + my $count = int($valueLen / Image::ExifTool::FormatSize($format)); + my $val = ReadValue($dataPt, $valuePos, $format, $count, $dirEnd-$valuePos); + $et->HandleTag($tagTablePtr, $tag, $val, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $valuePos, + Size => $valueLen, + Format => "type=$type, flags=$flags", + ); + } + # extract sub-properties + if ($numSubProps) { + my $dirStart = $valuePos + $valueLen + $numSubProps * 8; + my %dirInfo = ( + DataPt => $dataPt, + DataPos => $dataPos, + DirStart => $dirStart, + DirLen => $pos + $size - $dirStart, + Prefix => $tag, + ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + $pos += $size; # step to next Metadata structure + } + unless ($pos == $dirEnd) { + $et->Warn('Format error in Real Metadata'); + return 0; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Read information frame a Real file +# Inputs: 0) ExifTool object reference, 1) Directory information reference +# Returns: 1 on success, 0 if this wasn't a valid Real file +sub ProcessReal($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $tag, $vers, $extra, @mimeTypes, %dirCount); + + $raf->Read($buff, 8) == 8 or return 0; + $buff =~ m{^(\.RMF|\.ra\xfd|pnm://|rtsp://|http://)} or return 0; + + my $fast3 = $$et{OPTIONS}{FastScan} && $$et{OPTIONS}{FastScan} == 3; + my ($type, $tagTablePtr); + if ($1 eq '.RMF') { + $tagTablePtr = GetTagTable('Image::ExifTool::Real::Media'); + $type = 'RM'; + } elsif ($1 eq ".ra\xfd") { + $tagTablePtr = GetTagTable('Image::ExifTool::Real::Audio'); + $type = 'RA'; + } else { + $tagTablePtr = GetTagTable('Image::ExifTool::Real::Metafile'); + my $ext = $$et{FILE_EXT}; + $type = ($ext and $ext eq 'RPM') ? 'RPM' : 'RAM'; + require Image::ExifTool::PostScript; + local $/ = Image::ExifTool::PostScript::GetInputRecordSeparator($raf) || "\n"; + $raf->Seek(0,0); + while ($raf->ReadLine($buff)) { + last if length $buff > 256; + next unless $buff ; + chomp $buff; + if ($type) { + # must be a Real file type if protocol is http + return 0 if $buff =~ /^http/ and $buff !~ /\.(ra|rm|rv|rmvb|smil)$/i; + $et->SetFileType($type); + return 1 if $fast3; + undef $type; + } + # save URL or Text from RAM file + my $tag = $buff =~ m{^[a-z]{3,4}://} ? 'url' : 'txt'; + $et->HandleTag($tagTablePtr, $tag, $buff); + } + return 1; + } + + $et->SetFileType($type); + return 1 if $fast3; + SetByteOrder('MM'); + my $verbose = $et->Options('Verbose'); +# +# Process RealAudio file +# + if ($type eq 'RA') { + ($vers, $extra) = unpack('x4nn', $buff); + $tag = ".ra$vers"; + my $fpos = $raf->Tell(); + unless ($raf->Read($buff, 512)) { + $et->Warn('Error reading audio header'); + return 1; + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($verbose > 2) { + $et->VerboseInfo($tag, $tagInfo, DataPt => \$buff, DataPos => $fpos); + } + if ($tagInfo) { + my $subTablePtr = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + my %dirInfo = ( + DataPt => \$buff, + DataPos => $fpos, + DirLen => length $buff, + DirStart => 0, + ); + $et->ProcessDirectory(\%dirInfo, $subTablePtr); + } else { + $et->Warn('Unsupported RealAudio version'); + } + return 1; + } +# +# Process RealMedia file +# + # skip the rest of the RM header + my $size = unpack('x4N', $buff); + unless ($raf->Seek($size - 8, 1)) { + $et->Warn('Error seeking in file'); + return 0; + } + + # Process RealMedia chunks + for (;;) { + $raf->Read($buff, 10) == 10 or last; + ($tag, $size, $vers) = unpack('a4Nn', $buff); + last if $tag eq "\0\0\0\0"; + if ($verbose) { + $et->VPrint(0, "$tag chunk ($size bytes):\n"); + } else { + last if $tag eq 'DATA'; # stop normal parsing at DATA tag + } + if ($size & 0x80000000 or $size < 10) { + $et->Warn('Bad chunk header'); + last; + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo and $$tagInfo{SubDirectory}) { + my $fpos = $raf->Tell(); + unless ($raf->Read($buff, $size-10) == $size-10) { + $et->Warn("Error reading $tag chunk"); + last; + } + if ($verbose > 2) { + $et->VerboseInfo($tag, $tagInfo, DataPt => \$buff, DataPos => $fpos); + } + my $subTablePtr = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + my %dirInfo = ( + DataPt => \$buff, + DataPos => $fpos, + DirLen => length $buff, + DirStart => 0, + ); + if ($dirCount{$tag}) { + $$et{SET_GROUP1} = '+' . ++$dirCount{$tag}; + } else { + $dirCount{$tag} = 1; + } + $et->ProcessDirectory(\%dirInfo, $subTablePtr); + delete $$et{SET_GROUP1}; + # keep track of stream MIME types + my $mime = $$et{RealStreamMime}; + if ($mime) { + delete $$et{RealStreamMime}; + $mime =~ s/\0.*//s; + push @mimeTypes, $mime unless $mime =~ /^logical-/; + } + } else { + unless ($raf->Seek($size-10, 1)) { + $et->Warn('Error seeking in file'); + last; + } + } + } + # override MIMEType with stream MIME type if we only have one stream + if (@mimeTypes == 1 and length $mimeTypes[0]) { + $$et{VALUE}{MIMEType} = $mimeTypes[0]; + $et->VPrint(0, " MIMEType = $mimeTypes[0]\n"); + } +# +# Process footer containing Real metadata and ID3 information +# + if ($raf->Seek(-140, 2) and $raf->Read($buff, 12) == 12 and $buff =~ /^RMJE/) { + my $metaSize = unpack('x8N', $buff); + if ($raf->Seek(-$metaSize-12, 1) and + $raf->Read($buff, $metaSize) == $metaSize and + $buff =~ /^RJMD/) + { + my %dirInfo = ( + DataPt => \$buff, + DataPos => $raf->Tell() - $metaSize, + DirStart => 8, + DirLen => length($buff) - 8, + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::Real::Metadata'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } else { + $et->Warn('Bad metadata footer'); + } + if ($raf->Seek(-128, 2) and $raf->Read($buff, 128) == 128 and $buff =~ /^TAG/) { + $et->VPrint(0, "ID3v1:\n"); + my %dirInfo = ( + DataPt => \$buff, + DirStart => 0, + DirLen => length($buff), + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v1'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Real - Read Real audio/video meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains the routines required by Image::ExifTool to read meta +information in RealAudio (RA), RealMedia (RM, RV and RMVB) and RealMedia +Metafile (RAM and RPM) files. + +=head1 NOTES + +There must be a bug in the software that wrote the Metadata used in the test +file t/images/Real.rm because the TrackLyricsDataSize word is written +little-endian, but the Real format is big-endian. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.getid3.org/> + +=item L<https://common.helixcommunity.org/nonav/2003/HCS_SDK_r5/htmfiles/rmff.htm> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Real Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Reconyx.pm b/ExifTool/lib/Image/ExifTool/Reconyx.pm new file mode 100644 index 0000000..0302db4 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Reconyx.pm @@ -0,0 +1,444 @@ +#------------------------------------------------------------------------------ +# File: Reconyx.pm +# +# Description: Reconyx maker notes tags +# +# Revisions: 2011-01-11 - P. Harvey Created +# +# References: 1) RCNX_MN10.pdf (courtesy of Reconyx Inc.) +# 2) ultrafire_makernote.pdf (courtesy of Reconyx Inc.) +# 3) Reconyx private communication +#------------------------------------------------------------------------------ + +package Image::ExifTool::Reconyx; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.06'; + +# info for Type2 version tags +my %versionInfo = ( + Format => 'undef[7]', + ValueConv => 'sprintf("V%.2x.%.2x %.4x:%.2x:%.2x Rev.%s", unpack("CCvCCa", $val))', + ValueConvInv => q{ + my @v = $val =~ /^V([0-9a-f]+)\.([0-9a-f]+) (\d{4}):(\d{2}):(\d{2})\s*Rev\.(\w)/i or return undef; + pack('CCvCCa', map(hex, @v[0..4]), $v[5]); + }, +); + +# maker notes for Reconyx Hyperfire cameras (ref PH) +%Image::ExifTool::Reconyx::Main = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + TAG_PREFIX => 'Reconyx', + FORMAT => 'int16u', + WRITABLE => 1, + FIRST_ENTRY => 0, + NOTES => q{ + The following tags are extracted from the maker notes of Reconyx Hyperfire + cameras such as the HC500, HC600 and PC900. + }, + 0x00 => { #1 + Name => 'MakerNoteVersion', + PrintConv => 'sprintf("0x%.4x", $val)', + Writable => 0, # (we use this for identification, 0xf101 --> rev 1.0) + PrintConvInv => 'hex $val', + }, + 0x01 => { #1 + Name => 'FirmwareVersion', + Format => 'int16u[3]', + PrintConv => '$val=~tr/ /./; $val', + Writable => 0, # (we use this for identification, 0x0003 --> ver 2 or 3) + }, + 0x04 => { #1 + Name => 'FirmwareDate', + Format => 'int16u[2]', + ValueConv => q{ + my @v = split(' ',$val); + sprintf('%.4x:%.2x:%.2x', $v[0], $v[1]>>8, $v[1]&0xff); + }, + ValueConvInv => q{ + my @v = split(':', $val); + hex($v[0]) . ' ' . hex($v[1] . $v[2]); + }, + }, + 0x06 => { + Name => 'TriggerMode', + Format => 'string[2]', + PrintConv => { + C => 'CodeLoc Not Entered', #1 + E => 'External Sensor', #1 + M => 'Motion Detection', + T => 'Time Lapse', + }, + }, + 0x07 => { + Name => 'Sequence', + Format => 'int16u[2]', + PrintConv => '$val =~ s/ / of /; $val', + PrintConvInv => 'join(" ", $val=~/\d+/g)', + }, + 0x09 => { #1 + Name => 'EventNumber', + Format => 'int16u[2]', + ValueConv => 'my @v=split(" ",$val); ($v[0]<<16) + $v[1]', + ValueConvInv => '($val>>16) . " " . ($val&0xffff)', + }, + 0x0b => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Format => 'int16u[6]', + Groups => { 2 => 'Time' }, + Priority => 0, # (not as reliable as EXIF) + Shift => 'Time', + ValueConv => q{ + my @a = split ' ', $val; + # have seen these values written big-endian when everything else is little-endian + if ($a[0] & 0xff00 and not $a[0] & 0xff) { + $_ = ($_ >> 8) | (($_ & 0xff) << 8) foreach @a; + } + sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @a[5,3,4,2,1,0]); + }, + ValueConvInv => q{ + my @a = ($val =~ /\d+/g); + return undef unless @a >= 6; + join ' ', @a[5,4,3,1,2,0]; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + 0x12 => { + Name => 'MoonPhase', + Groups => { 2 => 'Time' }, + PrintConv => { + 0 => 'New', + 1 => 'New Crescent', + 2 => 'First Quarter', + 3 => 'Waxing Gibbous', + 4 => 'Full', + 5 => 'Waning Gibbous', + 6 => 'Last Quarter', + 7 => 'Old Crescent', + }, + }, + 0x13 => { + Name => 'AmbientTemperatureFahrenheit', + Format => 'int16s', + PrintConv => '"$val F"', + PrintConvInv => '$val=~/(-?\d+)/ ? $1 : $val', + }, + 0x14 => { + Name => 'AmbientTemperature', + Format => 'int16s', + PrintConv => '"$val C"', + PrintConvInv => '$val=~/(-?\d+)/ ? $1 : $val', + }, + 0x15 => { + Name => 'SerialNumber', + Format => 'undef[30]', + RawConv => '$_ = $self->Decode($val, "UCS2"); s/\0.*//; $_', + RawConvInv => q{ + $_ = $self->Encode($val, "UCS2"); + $_ = substr($_, 0, 30) if length($_) > 30; + return $_; + }, + }, + 0x24 => 'Contrast', #1 + 0x25 => 'Brightness', #1 + 0x26 => 'Sharpness', #1 + 0x27 => 'Saturation', #1 + 0x28 => { + Name => 'InfraredIlluminator', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x29 => 'MotionSensitivity', #1 + 0x2a => { #1 + Name => 'BatteryVoltage', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => '"$val V"', + PrintConvInv => '$val=~s/ ?V$//; $val', + }, + 0x2b => { + Name => 'UserLabel', + Format => 'string[22]', #1 (but manual says 16-char limit) + }, +); + +# maker notes for Reconyx UltraFire cameras (ref PH) +%Image::ExifTool::Reconyx::Type2 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + TAG_PREFIX => 'Reconyx', + WRITABLE => 1, + FIRST_ENTRY => 0, + NOTES => 'Tags extracted from models such as the UltraFire.', + # 0x0a - int32u makernote ID 0x00020000 #2 + # 0x0e - int16u makernote size #2 + # 0x12 - int32u public structure ID 0x07f100001 #2 + # 0x16 - int16u public structure size #2 (0x5d = start of public ID to end of UserLabel) + 0x18 => { Name => 'FirmwareVersion', %versionInfo }, + 0x1f => { Name => 'Micro1Version', %versionInfo }, #2 + 0x26 => { Name => 'BootLoaderVersion', %versionInfo }, #2 + 0x2d => { Name => 'Micro2Version', %versionInfo }, #2 + 0x34 => { + Name => 'TriggerMode', + Format => 'undef[1]', + PrintConv => { + M => 'Motion Detection', + T => 'Time Lapse', + P => 'Point and Shoot', #2 + }, + }, + 0x35 => { + Name => 'Sequence', + Format => 'int8u[2]', + PrintConv => '$val =~ s/ / of /; $val', + PrintConvInv => 'join(" ", $val=~/\d+/g)', + }, + 0x37 => { #2 + Name => 'EventNumber', + Format => 'int32u', + }, + 0x3b => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Format => 'int8u[7]', + Groups => { 2 => 'Time' }, + Priority => 0, # (not as reliable as EXIF) + Shift => 'Time', + ValueConv => q{ + my @a = split ' ', $val; + $a[5] += pop(@a) * 256; + sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', reverse @a); + }, + ValueConvInv => q{ + my @a = ($val =~ /\d+/g); + return undef unless @a >= 6; + unshift @a, ($a[0] >> 8); + $a[1] -= $a[0] * 256; + join ' ', @a[6,5,4,3,2,1,0]; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + 0x42 => { #2 + Name => 'DayOfWeek', + Groups => { 2 => 'Time' }, + PrintConv => { + 0 => 'Sunday', + 1 => 'Monday', + 2 => 'Tuesday', + 3 => 'Wednesday', + 4 => 'Thursday', + 5 => 'Friday', + 6 => 'Saturday', + }, + }, + 0x43 => { + Name => 'MoonPhase', + Groups => { 2 => 'Time' }, + PrintConv => { + 0 => 'New', + 1 => 'New Crescent', + 2 => 'First Quarter', + 3 => 'Waxing Gibbous', + 4 => 'Full', + 5 => 'Waning Gibbous', + 6 => 'Last Quarter', + 7 => 'Old Crescent', + }, + }, + 0x44 => { + Name => 'AmbientTemperatureFahrenheit', + Format => 'int16s', + PrintConv => '"$val F"', + PrintConvInv => '$val=~/(-?\d+)/ ? $1 : $val', + }, + 0x46 => { + Name => 'AmbientTemperature', + Format => 'int16s', + PrintConv => '"$val C"', + PrintConvInv => '$val=~/(-?\d+)/ ? $1 : $val', + }, + 0x48 => { + Name => 'Illumination', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x49 => { + Name => 'BatteryVoltage', + Format => 'int16u', + ValueConv => '$val / 1000', + ValueConvInv => '$val * 1000', + PrintConv => '"$val V"', + PrintConvInv => '$val=~s/ ?V$//; $val', + }, + 0x4b => { + Name => 'SerialNumber', + Format => 'string[15]', + }, + 0x5a => { + Name => 'UserLabel', + Format => 'string[21]', + }, +); + +# maker notes for Reconyx HF2 PRO cameras (ref 3) +%Image::ExifTool::Reconyx::Type3 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + TAG_PREFIX => 'Reconyx', + WRITABLE => 1, + FIRST_ENTRY => 0, + NOTES => 'Tags extracted from models such as the HF2 PRO.', + # 0x0a => { Name => 'StructureVersion', Format => 'int16u' }, + # 0x0c => { Name => 'ParentFileSize', Format => 'int32u' }, + 0x10 => { Name => 'FileNumber', Format => 'int16u' }, + 0x12 => { Name => 'DirectoryNumber', Format => 'int16u' }, + # 0x14 => { Name => 'DirCreateDate', Format => 'int16u' }, + # 0x16 => { Name => 'DirCreateTime', Format => 'int16u' }, + # 0x18 - int16[8] SDCardLabel + # 0x28 => { Name => 'MakerNoteVersion', Format => 'int16u' }, + 0x2a => { + Name => 'FirmwareVersion', + Format => 'int16u[3]', + ValueConv => 'my @a = split " ",$val; sprintf("%d.%d%c",@a)', + ValueConvInv => '$val=~/(\d+)\.(\d+)([a-zA-Z])/ ? "$1 $2 ".ord($3) : undef', + }, + 0x30 => { + Name => 'FirmwareDate', + Format => 'int16u[2]', + ValueConv => 'my ($y,$d) = split " ", $val; sprintf("%.4x:%.2x:%.2x",$y,$d>>8,$d&0xff)', + ValueConvInv => 'my @a=split ":", $val; hex($a[0])." ".hex($a[1].$a[2])', + }, + 0x34 => { + Name => 'TriggerMode', #PH (NC) (called EventType in the Reconyx code) + Format => 'string[2]', + PrintConv => { + M => 'Motion Detection', # (seen this one only) + T => 'Time Lapse', # (NC) + P => 'Point and Shoot', # (NC) + }, + }, + 0x36 => { + Name => 'Sequence', + Format => 'int16u[2]', + PrintConv => '$val =~ s/ / of /; $val', + PrintConvInv => 'join(" ", $val=~/\d+/g)', + }, + 0x3a => { + Name => 'EventNumber', + Format => 'int16u[2]', + ValueConv => 'my @a=split " ",$val;($a[0]<<16)+$a[1]', + ValueConvInv => '($val >> 16) . " " . ($val & 0xffff)', + }, + 0x3e => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Format => 'int16u[6]', + Groups => { 2 => 'Time' }, + Priority => 0, # (not as reliable as EXIF) + Shift => 'Time', + ValueConv => q{ + my @a = split ' ', $val; + sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', reverse @a); + }, + ValueConvInv => q{ + my @a = ($val =~ /\d+/g); + return undef unless @a >= 6; + join ' ', @a[6,5,4,3,2,1,0]; + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + 0x4a => { #2 + Name => 'DayOfWeek', + Groups => { 2 => 'Time' }, + Format => 'int16u', + PrintConv => { + 0 => 'Sunday', + 1 => 'Monday', + 2 => 'Tuesday', + 3 => 'Wednesday', + 4 => 'Thursday', + 5 => 'Friday', + 6 => 'Saturday', + }, + }, + 0x4c => { + Name => 'MoonPhase', + Groups => { 2 => 'Time' }, + Format => 'int16u', + PrintConv => { + 0 => 'New', + 1 => 'New Crescent', + 2 => 'First Quarter', + 3 => 'Waxing Gibbous', + 4 => 'Full', + 5 => 'Waning Gibbous', + 6 => 'Last Quarter', + 7 => 'Old Crescent', + }, + }, + 0x4e => { + Name => 'AmbientTemperatureFahrenheit', + Format => 'int16s', + PrintConv => '"$val F"', + PrintConvInv => '$val=~/(-?\d+)/ ? $1 : $val', + }, + 0x50 => { + Name => 'AmbientTemperature', + Format => 'int16s', + PrintConv => '"$val C"', + PrintConvInv => '$val=~/(-?\d+)/ ? $1 : $val', + }, + 0x52 => { Name => 'Contrast', Format => 'int16u' }, + 0x54 => { Name => 'Brightness', Format => 'int16u' }, + 0x56 => { Name => 'Sharpness', Format => 'int16u' }, + 0x58 => { Name => 'Saturation', Format => 'int16u' }, + 0x5a => { Name => 'Flash', Format => 'int16u', PrintConv => { 0 => 'Off', 1 => 'On' } }, + 0x5c => { Name => 'AmbientInfrared', Format => 'int16u' }, + 0x5e => { Name => 'AmbientLight', Format => 'int16u' }, + 0x60 => { Name => 'MotionSensitivity', Format => 'int16u' }, + 0x62 => { Name => 'BatteryVoltage', Format => 'int16u' }, + 0x64 => { Name => 'BatteryVoltageAvg', Format => 'int16u' }, + 0x66 => { Name => 'BatteryType', Format => 'int16u' }, + 0x68 => { Name => 'UserLabel', Format => 'string[22]' }, + 0x7e => { Name => 'SerialNumber', Format => 'unicode[15]' }, +); + +__END__ + +=head1 NAME + +Image::ExifTool::Reconyx - Reconyx 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 +maker notes in images from Reconyx cameras. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Reconyx Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Red.pm b/ExifTool/lib/Image/ExifTool/Red.pm new file mode 100644 index 0000000..3799257 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Red.pm @@ -0,0 +1,325 @@ +#------------------------------------------------------------------------------ +# File: Red.pm +# +# Description: Read Redcode R3D video files +# +# Revisions: 2018-01-25 - P. Harvey Created +# +# References: 1) http://www.wikiwand.com/en/REDCODE +#------------------------------------------------------------------------------ + +package Image::ExifTool::Red; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.01'; + +sub ProcessR3D($$); + +# RED format codes (ref PH) +my %redFormat = ( + 0 => 'int8u', + 1 => 'string', + 2 => 'float', + 3 => 'int8u', # (how is this different than 0?) + 4 => 'int16u', + 5 => 'int8s', # (not sure about this) + 6 => 'int32s', + 7 => 'undef', # (mixed-format structure?) + 8 => 'int32u', # (NC) + 9 => 'undef', # ? (seen 256 bytes, all zero) +); + +# error strings +my $errTrunc = 'Truncated R3D file'; + +# RED directory tags (ref PH) +%Image::ExifTool::Red::Main = ( + GROUPS => { 2 => 'Camera' }, + NOTES => 'Tags extracted from Redcode R3D video files.', + VARS => { ALPHA_FIRST => 1 }, + + RED1 => { Name => 'Red1Header', SubDirectory => { TagTable => 'Image::ExifTool::Red::RED1' } }, + RED2 => { Name => 'Red2Header', SubDirectory => { TagTable => 'Image::ExifTool::Red::RED2' } }, + + # (upper 4 bits of tag ID are the format code) + # ---- format 1 ---- + 0x1000 => 'StartEdgeCode', #1 + 0x1001 => { Name => 'StartTimecode', Groups => { 2 => 'Time' } }, #1 + 0x1002 => { #1 + Name => 'OtherDate1', + Groups => { 2 => 'Time' }, + # format is "YYYY_MM_DD[_TZ?]" + ValueConv => '$val =~ s/(\d{4})_(\d{2})_/$1:$2:/; $val =~ tr/_/ /; $val', + }, + 0x1003 => { #1 + Name => 'OtherDate2', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{4})_(\d{2})_/$1:$2:/; $val =~ tr/_/ /; $val', + }, + 0x1004 => { #1 + Name => 'OtherDate3', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{4})_(\d{2})_/$1:$2:/; $val =~ tr/_/ /; $val', + }, + 0x1005 => { #1 + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})/$1:$2:$3 $4:$5:/; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + 0x1006 => 'SerialNumber', #1 + 0x1019 => 'CameraType', #1 + 0x101a => { Name => 'ReelNumber', Groups => { 2 => 'Video' } }, #1 + 0x101b => { Name => 'Take', Groups => { 2 => 'Video' } }, + 0x1023 => { #1 + Name => 'DateCreated', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{4})(\d{2})/$1:$2:/; $val', + }, + 0x1024 => { #1 + Name => 'TimeCreated', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{2})(\d{2})/$1:$2:/; $val', + }, + 0x1025 => 'FirmwareVersion', #1 + 0x1029 => { Name => 'ReelTimecode', Groups => { 2 => 'Time' } }, #1 + 0x102a => 'StorageType', #1 + 0x1030 => { #1 + Name => 'StorageFormatDate', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{4})(\d{2})/$1:$2:/; $val', + }, + 0x1031 => { #1 + Name => 'StorageFormatTime', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ s/(\d{2})(\d{2})/$1:$2:/; $val', + }, + 0x1032 => 'StorageSerialNumber', #1 + 0x1033 => 'StorageModel', #1 + 0x1036 => 'AspectRatio', #1 + # 0x1041 - seen 'NA' + 0x1042 => 'Revision', # ? (seen "TODO, rev EPIC-1.0" and "MYSTERIUM X, rev EPIC-1.0") + # 0x1051 - seen 'C', 'L' + 0x1056 => 'OriginalFileName', + 0x106e => 'LensMake', + 0x106f => 'LensNumber', # (last 2 hex digits are LensType) + 0x1070 => 'LensModel', + 0x1071 => { + Name => 'Model', + Description => 'Camera Model Name', + }, + 0x107c => { Name => 'CameraOperator', Groups => { 2 => 'Author' } }, + 0x1086 => { + Name => 'VideoFormat', + Groups => { 2 => 'Video' }, + }, + 0x1096 => 'Filter', # optical low-pass filter + 0x10a0 => 'Brain', + 0x10a1 => 'Sensor', + # ---- format 2 ---- + 0x200d => 'ColorTemperature', + # 0x200e - (sometimes this is frame rate) + # 0x2015 - seen '1 1 1' (RGBGain or RGBGamma?) + 0x204b => 'RGBCurves', # (blackx/y,toex/y,midx/y,kneex/y,whitex/y) + 0x2066 => { + Name => 'OriginalFrameRate', + Groups => { 2 => 'Video' }, + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + # ---- format 4 ---- + 0x4037 => { Name => 'CropArea' }, # (NC) + 0x403b => 'ISO', + # 0x404e - related to CropArea (or "0 0 0 0") + 0x406a => { Name => 'FNumber', ValueConv => '$val / 10' }, + 0x406b => 'FocalLength', + # 0x4084 - related to ISO? + # 0x4087 - related to ISO? + # ---- format 6 ---- + 0x606c => { Name => 'FocusDistance', ValueConv => '$val/1000', PrintConv => '"$val m"' }, +); + +# RED1 file header (ref PH) +%Image::ExifTool::Red::RED1 = ( + GROUPS => { 2 => 'Video' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + NOTES => 'Redcode version 1 header.', + # 0x00 - int32u: length of header + # 0x04 - string: "RED1" + # 0x0a - string: "R1" + 0x07 => { Name => 'RedcodeVersion', Format => 'string[1]' }, #1 + # 0x0e - looks funny; my sample has a value of 43392 here + # 0x0e => { Name => 'AudioSampleRate', Format => 'int16u' }, #1 + 0x36 => { Name => 'ImageWidth', Format => 'int16u' }, #1 + 0x3a => { Name => 'ImageHeight', Format => 'int16u' }, #PH (ref 1 gave 0x3c) + 0x3e => { #PH (ref 1 gave 0x42 for denom) + Name => 'FrameRate', + Format => 'rational32u', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + 0x43 => { Name => 'OriginalFileName', Format => 'string[32]' }, #1 +); + +# RED2 file header (ref PH) +%Image::ExifTool::Red::RED2 = ( + GROUPS => { 2 => 'Video' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + NOTES => 'Redcode version 2 header.', + # 0x00 - int32u: length of header + # 0x04 - string: "RED2" + 0x07 => { Name => 'RedcodeVersion', Format => 'string[1]' }, + # 0x08 - seen 0x05 + # 0x09 - seen 0x0d,0x0f,0x10 + # 0x0a - string: "R2" + # 0x0c - seen 0x04,0x05,0x07,0x08,0x0b,0x0c + # 0x0d - seen 0x01,0x08 (and 0x09 in block 1) + # 0x0e - int16u: seen 3072 + # 0x10 - looks like some sort of 32-byte hash or something (same in other blocks) + # 0x30-0x3f - mostly 0x00's with a couple of 0x01's + # 0x40 - int8u: count of 0x18-byte "rdi" records + # 0x41-0x43 - seen "\0\0\x01" + # ---- rdi record: (0x18 bytes long) ---- + # 0x44 - string: "rdi#" (where number is index of "rdi" record, starting at \x01) + 0x4c => { Name => 'ImageWidth', Format => 'int32u' }, + 0x50 => { Name => 'ImageHeight', Format => 'int32u' }, + # 0x54 - seen 0x11,0x13,0x15 (and 0x03 in "rdi\x02" record) + # 0x55 - seen 0x02 + 0x56 => { + Name => 'FrameRate', + Format => 'int16u[3]', + ValueConv => 'my @a = split " ",$val; ($a[1] * 0x10000 + $a[2]) / $a[0]', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + # (immediately following last "rdi" record is a + # Red directory beginning with int16u size) +); + +#------------------------------------------------------------------------------ +# Process metadata from a Redcode R3D video (ref PH) +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid R3D file +sub ProcessR3D($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $buf2, $pos, $dirLen, $dirEnd); + my $verbose = $et->Options('Verbose'); + + # R3D file structure: + # - each block starts with int32u block size followed by 4-byte block type + # - first block type is either "RED1" (version 1) or "RED2" (version 2) + # - blocks begin on even 0x1000 byte boundaries for version 2 files + + # validate the file header + return 0 unless $raf->Read($buff, 8) == 8 and $buff =~ /^\0\0..RED(1|2)/s; + my $ver = $1; + my $size = unpack('N', $buff); + return 0 if $size < 8; + + $et->SetFileType(); + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::Red::Main'); + my $dataPos = 0; + + # read the first block of the file + $raf->Read($buf2, $size - 8) == $size - 8 or return $et->Warn($errTrunc); + $buff .= $buf2; + + # extract tags from the header + $et->HandleTag($tagTablePtr, "RED$ver", undef, DataPt => \$buff); + + # read the second block from a version 1 file because + # the first block doesn't contain a Red directory + if ($ver eq '1') { + # (read more than we need) + $raf->Read($buff, 0x10000) or return $et->Warn($errTrunc); + $dataPos += $size; + $pos = 0x22; # directory starts at offset 0x22 + } else { + # calculate position of Red directory start + length($buff) < 0x41 and return $et->Warn($errTrunc); + my $n = Get8u(\$buff, 0x40); # number of "rdi" records + $pos = 0x44 + $n * 0x18; + } + if ($pos + 8 > length $buff) { + $dirLen = 0; # find directory the hard way + } else { + $dirLen = Get16u(\$buff, $pos); # get length of Red directory + $pos += 2; # skip length word + } + # do sanity check on the directory size (in case our assumptions were wrong) + if ($dirLen < 300 or $dirLen >= 2048 or $pos + $dirLen > length $buff) { + # tag 0x1000 with length 0x000f should be near the directory start + $buff =~ /\0\x0f\x10\0/g or return $et->Warn("Can't find Red directory"); + $pos = pos($buff) - 4; + $dirEnd = length $buff; + undef $dirLen; + $et->Warn('This R3D file is different. Please submit a sample for testing'); + } else { + $dirEnd = $pos + $dirLen; + } + $$et{INDENT} .= '| ', $et->VerboseDir('Red', undef, $dirLen) if $verbose; + + # process the first Red directory + while ($pos + 4 <= $dirEnd) { + my $len = Get16u(\$buff, $pos); + last if $len < 4 or $pos + $len > $dirEnd; + my $tag = Get16u(\$buff, $pos + 2); + my $fmt = $redFormat{$tag >> 12}; # format is top 4 bits of tag ID (ref PH) + $fmt or $dirLen && $et->Warn('Unknown format code'), last; + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => \$buff, + DataPos => $dataPos, + Start => $pos + 4, + Size => $len - 4, + Format => $fmt, + ); + $pos += $len; + } + $$et{INDENT} = substr($$et{INDENT}, 0, -2) if $verbose; + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Red - Read Redcode R3D video files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains routines required by Image::ExifTool to read metadata +from Redcode R3D version 1 and 2 video files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.wikiwand.com/en/REDCODE> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Red Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Ricoh.pm b/ExifTool/lib/Image/ExifTool/Ricoh.pm new file mode 100644 index 0000000..1cae2c3 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Ricoh.pm @@ -0,0 +1,1169 @@ +#------------------------------------------------------------------------------ +# File: Ricoh.pm +# +# Description: Ricoh EXIF maker notes tags +# +# Revisions: 03/28/2005 - P. Harvey Created +# +# References: 1) http://www.ozhiker.com/electronics/pjmt/jpeg_info/ricoh_mn.html +# 2) http://homepage3.nifty.com/kamisaka/makernote/makernote_ricoh.htm +# 3) Tim Gray private communication (GR) +# 4) https://github.com/atotto/ricoh-theta-tools/ +# IB) Iliah Borg private communication (LibRaw) +#------------------------------------------------------------------------------ + +package Image::ExifTool::Ricoh; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.36'; + +sub ProcessRicohText($$$); +sub ProcessRicohRMETA($$$); + +# lens types for Ricoh GXR +my %ricohLensIDs = ( + Notes => q{ + Lens units available for the GXR, used by the Ricoh Composite LensID tag. Note + that unlike lenses for all other makes of cameras, the focal lengths in these + model names have already been scaled to include the 35mm crop factor. + }, + # (the exact lens model names used by Ricoh, except for a change in case) + 'RL1' => 'GR Lens A12 50mm F2.5 Macro', + 'RL2' => 'Ricoh Lens S10 24-70mm F2.5-4.4 VC', + 'RL3' => 'Ricoh Lens P10 28-300mm F3.5-5.6 VC', + 'RL5' => 'GR Lens A12 28mm F2.5', + 'RL8' => 'Mount A12', + 'RL6' => 'Ricoh Lens A16 24-85mm F3.5-5.5', +); + +%Image::ExifTool::Ricoh::Main = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + 0x0001 => { Name => 'MakerNoteType', Writable => 'string' }, + 0x0002 => { #PH + Name => 'FirmwareVersion', + Writable => 'string', + # eg. "Rev0113" is firmware version 1.13 + PrintConv => '$val=~/^Rev(\d+)$/ ? sprintf("%.2f",$1/100) : $val', + PrintConvInv => '$val=~/^(\d+)\.(\d+)$/ ? sprintf("Rev%.2d%.2d",$1,$2) : $val', + }, + 0x0005 => [ #PH + { + Condition => '$$valPt =~ /^[-\w ]+$/', + Name => 'SerialNumber', # (verified for GXR) + Writable => 'undef', + Count => 16, + Notes => q{ + the serial number stamped on the camera begins with 2 model-specific letters + followed by the last 8 digits of this value. For the GXR, this is the + serial number of the lens unit + }, + PrintConv => '$val=~s/^(.*)(.{8})$/($1)$2/; $val', + PrintConvInv => '$val=~tr/()//d; $val', + },{ + Name => 'InternalSerialNumber', + Writable => 'undef', + Count => 16, + ValueConv => 'unpack("H*", $val)', + ValueConvInv => 'pack("H*", $val)', + }, + ], + 0x0e00 => { + Name => 'PrintIM', + Writable => 0, + Description => 'Print Image Matching', + SubDirectory => { TagTable => 'Image::ExifTool::PrintIM::Main' }, + }, + 0x1000 => { #3 + Name => 'RecordingFormat', + Writable => 'int16u', + PrintConv => { + 2 => 'JPEG', + 3 => 'DNG', + }, + }, + 0x1001 => [{ + Name => 'ImageInfo', + Condition => '$format ne "int16u"', + SubDirectory => { TagTable => 'Image::ExifTool::Ricoh::ImageInfo' }, + },{ #3 + Name => 'ExposureProgram', + Writable => 'int16u', + Notes => 'GR', + PrintConv => { + 1 => 'Auto', + 2 => 'Program AE', + 3 => 'Aperture-priority AE', + 4 => 'Shutter speed priority AE', + 5 => 'Shutter/aperture priority AE', # TAv + 6 => 'Manual', + 7 => 'Movie', #PH + }, + }], + 0x1002 => { #3 + Name => 'DriveMode', + Condition => '$format eq "int16u"', + Notes => 'valid only for some models', + Writable => 'int16u', + PrintConv => { + 0 => 'Single-frame', + 1 => 'Continuous', + 8 => 'AF-priority Continuous', + }, + }, + 0x1003 => [{ + Name => 'Sharpness', + Condition => '$format ne "int16u"', + Writable => 'int32u', + PrintConv => { + 0 => 'Sharp', + 1 => 'Normal', + 2 => 'Soft', + }, + },{ #3 + Name => 'WhiteBalance', + Writable => 'int16u', + Notes => 'GR', + PrintConv => { + 0 => 'Auto', + 1 => 'Multi-P Auto', + 2 => 'Daylight', + 3 => 'Cloudy', + 4 => 'Incandescent 1', + 5 => 'Incandescent 2', + 6 => 'Daylight Fluorescent', + 7 => 'Neutral White Fluorescent', + 8 => 'Cool White Fluorescent', + 9 => 'Warm White Fluorescent', + 10 => 'Manual', + 11 => 'Kelvin', + 12 => 'Shade', #IB + }, + }], + 0x1004 => { #3 + Name => 'WhiteBalanceFineTune', + Condition => '$format eq "int16u"', + Format => 'int16s', + Writable => 'int16u', + Notes => q{ + 2 numbers: amount of adjustment towards Amber and Green. Not valid for all + models + }, + }, + # 0x1005 int16u - 5 + 0x1006 => { #3 + Name => 'FocusMode', + Writable => 'int16u', + PrintConv => { + 1 => 'Manual', + 2 => 'Multi AF', + 3 => 'Spot AF', + 4 => 'Snap', + 5 => 'Infinity', + 7 => 'Face Detect', #PH + 8 => 'Subject Tracking', + 9 => 'Pinpoint AF', + 10 => 'Movie', #PH + }, + }, + 0x1007 => { #3 + Name => 'AutoBracketing', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 9 => 'AE', + 11 => 'WB', + 16 => 'DR', # (dynamic range) + 17 => 'Contrast', + 18 => 'WB2', # (selects two different WB presets besides normal) + 19 => 'Effect', + }, + }, + 0x1009 => { #3 + Name => 'MacroMode', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x100a => { #3 + Name => 'FlashMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Auto, Fired', + 2 => 'On', + 3 => 'Auto, Fired, Red-eye reduction', + 4 => 'Slow Sync', + 5 => 'Manual', + 6 => 'On, Red-eye reduction', + 7 => 'Synchro, Red-eye reduction', + 8 => 'Auto, Did not fire', + }, + }, + 0x100b => { #3 + Name => 'FlashExposureComp', + Writable => 'rational64s', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 0x100c => { #3 + Name => 'ManualFlashOutput', + Writable => 'rational64s', + PrintConv => { + 0 => 'Full', + -24 => '1/1.4', + -48 => '1/2', + -72 => '1/2.8', + -96 => '1/4', + -120 => '1/5.6', + -144 => '1/8', + -168 => '1/11', + -192 => '1/16', + -216 => '1/22', + -240 => '1/32', + -288 => '1/64', + }, + }, + 0x100d => { #3 + Name => 'FullPressSnap', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x100e => { #3 + Name => 'DynamicRangeExpansion', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 3 => 'Weak', + 4 => 'Medium', + 5 => 'Strong', + }, + }, + 0x100f => { #3 + Name => 'NoiseReduction', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Weak', + 2 => 'Medium', + 3 => 'Strong', + }, + }, + 0x1010 => { #3 + Name => 'ImageEffects', + Writable => 'int16u', + PrintConv => { + 0 => 'Standard', + 1 => 'Vivid', + 3 => 'Black & White', + 5 => 'B&W Toning Effect', + 6 => 'Setting 1', + 7 => 'Setting 2', + 9 => 'High-contrast B&W', + 10 => 'Cross Process', + 11 => 'Positive Film', + 12 => 'Bleach Bypass', + 13 => 'Retro', + 15 => 'Miniature', + 17 => 'High Key', + }, + }, + 0x1011 => { #3 + Name => 'Vignetting', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Medium', + 3 => 'High', + }, + }, + 0x1012 => { #PH + Name => 'Contrast', + Writable => 'int32u', + Format => 'int32s', #3 (high-contrast B&W also has -1 and -2 settings) + PrintConv => { + OTHER => sub { shift }, + 2147483647 => 'MAX', #3 (high-contrast B&W effect MAX setting) + }, + }, + 0x1013 => { Name => 'Saturation', Writable => 'int32u' }, #PH + 0x1014 => { Name => 'Sharpness', Writable => 'int32u' }, #3 + 0x1015 => { #3 + Name => 'ToningEffect', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Sepia', + 2 => 'Red', + 3 => 'Green', + 4 => 'Blue', + 5 => 'Purple', + 6 => 'B&W', + 7 => 'Color', + }, + }, + 0x1016 => { #3 + Name => 'HueAdjust', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Basic', + 2 => 'Magenta', + 3 => 'Yellow', + 4 => 'Normal', + 5 => 'Warm', + 6 => 'Cool', + }, + }, + 0x1017 => { #3 + Name => 'WideAdapter', + Writable => 'int16u', + PrintConv => { + 0 => 'Not Attached', + 2 => 'Attached', # (21mm) + }, + }, + 0x1018 => { #3 + Name => 'CropMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On (35mm)', + 2 => 'On (47mm)', #IB + }, + }, + 0x1019 => { #3 + Name => 'NDFilter', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x101a => { Name => 'WBBracketShotNumber', Writable => 'int16u' }, #3 + # 0x1100 - related to DR correction (ref 3) + 0x1307 => { Name => 'ColorTempKelvin', Writable => 'int32u' }, #3 + 0x1308 => { Name => 'ColorTemperature', Writable => 'int32u' }, #3 + 0x1500 => { #3 + Name => 'FocalLength', + Writable => 'rational64u', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + 0x1200 => { #3 + Name => 'AFStatus', + Writable => 'int16u', + PrintConv => { + 0 => 'Out of Focus', + 1 => 'In Focus', + }, + }, + # 0x1201-0x1204 - related to focus points (ref 3) + 0x1201 => { #PH (NC) + Name => 'AFAreaXPosition1', + Writable => 'int32u', + Notes => 'manual AF area position in a 1280x864 image', + }, + 0x1202 => { Name => 'AFAreaYPosition1', Writable => 'int32u' }, #PH (NC) + 0x1203 => { #PH (NC) + Name => 'AFAreaXPosition', + Writable => 'int32u', + Notes => 'manual AF area position in the full image', + # (coordinates change to correspond with smaller image + # when recording reduced-size JPEG) + }, + 0x1204 => { Name => 'AFAreaYPosition', Writable => 'int32u' }, #PH (NC) + 0x1205 => { #3 + Name => 'AFAreaMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Auto', + 2 => 'Manual', + }, + }, + 0x1601 => { Name => 'SensorWidth', Writable => 'int32u' }, #3 + 0x1602 => { Name => 'SensorHeight', Writable => 'int32u' }, #3 + 0x1603 => { Name => 'CroppedImageWidth', Writable => 'int32u' }, #3 + 0x1604 => { Name => 'CroppedImageHeight', Writable => 'int32u' }, #3 + # 0x1700 - Composite? (0=normal image, 1=interval composite, 2=multi-exposure composite) (ref 3) + # 0x1703 - 0=normal, 1=final composite (ref 3) + # 0x1704 - 0=normal, 2=final composite (ref 3) + 0x2001 => [ + { + Name => 'RicohSubdir', + Condition => q{ + $self->{Model} !~ /^Caplio RR1\b/ and + ($format ne 'int32u' or $count != 1) + }, + SubDirectory => { + Validate => '$val =~ /^\[Ricoh Camera Info\]/', + TagTable => 'Image::ExifTool::Ricoh::Subdir', + Start => '$valuePtr + 20', + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'RicohSubdirIFD', + # the CX6 and GR Digital 4 write an int32u pointer in AVI videos -- doh! + Condition => '$self->{Model} !~ /^Caplio RR1\b/', + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Ricoh::Subdir', + Start => '$val + 20', # (skip over "[Ricoh Camera Info]\0" header) + ByteOrder => 'BigEndian', + }, + }, + { + Name => 'RicohRR1Subdir', + SubDirectory => { + Validate => '$val =~ /^\[Ricoh Camera Info\]/', + TagTable => 'Image::ExifTool::Ricoh::Subdir', + Start => '$valuePtr + 20', + ByteOrder => 'BigEndian', + # the Caplio RR1 uses a different base address -- doh! + Base => '$start-20', + }, + }, + ], + 0x4001 => { + Name => 'ThetaSubdir', + Groups => { 1 => 'MakerNotes' }, # SubIFD needs group 1 set + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Ricoh::ThetaSubdir', + Start => '$val', + }, + }, +); + +# Ricoh type 2 maker notes (ref PH) +# (similar to Kodak::Type11 and GE::Main) +%Image::ExifTool::Ricoh::Type2 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + Tags written by models such as the Ricoh HZ15 and the Pentax XG-1. These + are not writable due to numerous formatting errors as written by these + cameras. + }, + # 0x104 - int32u: 1 + # 0x200 - int32u[3]: 0 0 0 + # 0x202 - int16u: 0 (GE Macro?) + # 0x203 - int16u: 0,3 (Kodak PictureEffect?) + # 0x204 - rational64u: 0/10 + # 0x205 - rational64u: 150/1 + # 0x206 - float[6]: (not really float because size should be 2 bytes) + 0x207 => { + Name => 'RicohModel', + Writable => 'string', + }, + 0x300 => { + # brutal. There are lots of errors in the XG-1 maker notes. For the XG-1, + # 0x300 has a value of "XG-1Pentax". The "XG-1" part is likely an improperly + # stored 0x207 RicohModel, resulting in an erroneous 4-byte offset for this tag + Name => 'RicohMake', + Writable => 'undef', + ValueConv => '$val =~ s/ *$//; $val', + }, + # 0x306 - int16u: 1 + # 0x500 - int16u: 0,1 + # 0x501 - int16u: 0 + # 0x502 - int16u: 0 + # 0x9c9c - int8u[6]: ? + # 0xadad - int8u[20480]: ? +); + +# Ricoh image info (ref 2) +%Image::ExifTool::Ricoh::ImageInfo = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + PRIORITY => 0, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + IS_OFFSET => [ 28 ], # tag 28 is 'IsOffset' + 0 => { + Name => 'RicohImageWidth', + Format => 'int16u', + }, + 2 => { + Name => 'RicohImageHeight', + Format => 'int16u', + }, + 6 => { + Name => 'RicohDate', + Groups => { 2 => 'Time' }, + Format => 'int8u[7]', + # (what an insane way to encode the date) + ValueConv => q{ + sprintf("%.2x%.2x:%.2x:%.2x %.2x:%.2x:%.2x", + split(' ', $val)); + }, + ValueConvInv => q{ + my @vals = ($val =~ /(\d{1,2})/g); + push @vals, 0 if @vals < 7; + join(' ', map(hex, @vals)); + }, + }, + 28 => { + Name => 'PreviewImageStart', + Format => 'int16u', # ha! (only the lower 16 bits, even if > 0xffff) + Flags => 'IsOffset', + OffsetPair => 30, # associated byte count tagID + DataTag => 'PreviewImage', + Protected => 2, + WriteGroup => 'MakerNotes', + # prevent preview from being written to MakerNotes of DNG images + RawConvInv => q{ + return $val if $$self{FILE_TYPE} eq "JPEG"; + warn "\n"; # suppress warning + return undef; + }, + }, + 30 => { + Name => 'PreviewImageLength', + Format => 'int16u', + OffsetPair => 28, # point to associated offset + DataTag => 'PreviewImage', + Protected => 2, + WriteGroup => 'MakerNotes', + RawConvInv => q{ + return $val if $$self{FILE_TYPE} eq "JPEG"; + warn "\n"; # suppress warning + return undef; + }, + }, + 32 => { + Name => 'FlashMode', + PrintConv => { + 0 => 'Off', + 1 => 'Auto', #PH + 2 => 'On', + }, + }, + 33 => { + Name => 'Macro', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 34 => { + Name => 'Sharpness', + PrintConv => { + 0 => 'Sharp', + 1 => 'Normal', + 2 => 'Soft', + }, + }, + 38 => { + Name => 'WhiteBalance', + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Cloudy', + 3 => 'Tungsten', + 4 => 'Fluorescent', + 5 => 'Manual', #PH (GXR) + 7 => 'Detail', + 9 => 'Multi-pattern Auto', #PH (GXR) + }, + }, + 39 => { + Name => 'ISOSetting', + PrintConv => { + 0 => 'Auto', + 1 => 64, + 2 => 100, + 4 => 200, + 6 => 400, + 7 => 800, + 8 => 1600, + 9 => 'Auto', #PH (? CX3) + 10 => 3200, #PH (A16) + 11 => '100 (Low)', #PH (A16) + }, + }, + 40 => { + Name => 'Saturation', + PrintConv => { + 0 => 'High', + 1 => 'Normal', + 2 => 'Low', + 3 => 'B&W', + 6 => 'Toning Effect', #PH (GXR Sepia,Red,Green,Blue,Purple) + 9 => 'Vivid', #PH (GXR) + 10 => 'Natural', #PH (GXR) + }, + }, +); + +# Ricoh subdirectory tags (ref PH) +# NOTE: this subdir is currently not writable because the offsets would require +# special code to handle the funny start location and base offset +%Image::ExifTool::Ricoh::Subdir = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + # the significance of the following 2 dates is not known. They are usually + # within a month of each other, but I have seen differences of nearly a year. + # Sometimes the first is more recent, and sometimes the second. + # 0x0003 - int32u[1] + 0x0004 => { # (NC) + Name => 'ManufactureDate1', + Groups => { 2 => 'Time' }, + Writable => 'string', + Count => 20, + }, + 0x0005 => { # (NC) + Name => 'ManufactureDate2', + Groups => { 2 => 'Time' }, + Writable => 'string', + Count => 20, + }, + # 0x0006 - undef[16] ? + # 0x0007 - int32u[1] ? + # 0x000c - int32u[2] 1st number is a counter (file number? shutter count?) - PH + # 0x0014 - int8u[338] could contain some data related to face detection? - PH + # 0x0015 - int8u[2]: related to noise reduction? + 0x001a => { #PH + Name => 'FaceInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Ricoh::FaceInfo' }, + }, + 0x0029 => { + Name => 'FirmwareInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Ricoh::FirmwareInfo' }, + }, + 0x002a => { + Name => 'NoiseReduction', + # this is the applied value if NR is set to "Auto" + Writable => 'int32u', + PrintConv => { + 0 => 'Off', + 1 => 'Weak', + 2 => 'Strong', + 3 => 'Max', + }, + }, + 0x002c => { # (GXR) + Name => 'SerialInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Ricoh::SerialInfo' }, + } + # 0x000E ProductionNumber? (ref 2) [no. zero for most models - PH] +); + +# Ricoh Theta subdirectory tags - Contains orientation information (ref 4) +%Image::ExifTool::Ricoh::ThetaSubdir = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + # 0x0001 - int16u[1] ? + # 0x0002 - int16u[1] ? + 0x0003 => { + Name => 'Accelerometer', + Writable => 'rational64s', + Count => 2, + }, + 0x0004 => { + Name => 'Compass', + Writable => 'rational64u', + }, + # 0x0005 - int16u[1] ? + # 0x0006 - int16u[1] ? + # 0x0007 - int16u[1] ? + # 0x0008 - int16u[1] ? + # 0x0009 - int16u[1] ? + 0x000a => { + Name => 'TimeZone', + Writable => 'string', + }, + # 0x0101 - int16u[4] ISO (why 4 values?) + # 0x0102 - rational64s[2] FNumber (why 2 values?) + # 0x0103 - rational64u[2] ExposureTime (why 2 values?) + # 0x0104 - string[9] SerialNumber? + # 0x0105 - string[9] SerialNumber? +); + +# face detection information (ref PH, CX4) +%Image::ExifTool::Ricoh::FaceInfo = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, + DATAMEMBER => [ 181 ], + 0xb5 => { # (should be int16u at 0xb4?) + Name => 'FacesDetected', + DataMember => 'FacesDetected', + RawConv => '$$self{FacesDetected} = $val', + }, + 0xb6 => { + Name => 'FaceDetectFrameSize', + Format => 'int16u[2]', + }, + 0xbc => { + Name => 'Face1Position', + Condition => '$$self{FacesDetected} >= 1', + Format => 'int16u[4]', + Notes => q{ + left, top, width and height of detected face in coordinates of + FaceDetectFrameSize with increasing Y downwards + }, + }, + 0xc8 => { + Name => 'Face2Position', + Condition => '$$self{FacesDetected} >= 2', + Format => 'int16u[4]', + }, + 0xd4 => { + Name => 'Face3Position', + Condition => '$$self{FacesDetected} >= 3', + Format => 'int16u[4]', + }, + 0xe0 => { + Name => 'Face4Position', + Condition => '$$self{FacesDetected} >= 4', + Format => 'int16u[4]', + }, + 0xec => { + Name => 'Face5Position', + Condition => '$$self{FacesDetected} >= 5', + Format => 'int16u[4]', + }, + 0xf8 => { + Name => 'Face6Position', + Condition => '$$self{FacesDetected} >= 6', + Format => 'int16u[4]', + }, + 0x104 => { + Name => 'Face7Position', + Condition => '$$self{FacesDetected} >= 7', + Format => 'int16u[4]', + }, + 0x110 => { + Name => 'Face8Position', + Condition => '$$self{FacesDetected} >= 8', + Format => 'int16u[4]', + }, +); + +# firmware version information (ref PH) +%Image::ExifTool::Ricoh::FirmwareInfo = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + 0x00 => { + Name => 'FirmwareRevision', + Format => 'string[12]', + }, + 0x0c => { + Name => 'FirmwareRevision2', + Format => 'string[12]', + }, +); + +# serial/version number information written by GXR (ref PH) +%Image::ExifTool::Ricoh::SerialInfo = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + NOTES => 'This information is found in images from the GXR.', + 0 => { + Name => 'BodyFirmware', #(NC) + Format => 'string[16]', + # observed: "RS1 :V00560000" --> FirmwareVersion "Rev0056" + # "RS1 :V01020200" --> FirmwareVersion "Rev0102" + }, + 16 => { + Name => 'BodySerialNumber', + Format => 'string[16]', + # observed: "SID:00100056" --> "WD00100056" on plate + }, + 32 => { + Name => 'LensFirmware', #(NC) + Format => 'string[16]', + # observed: "RL1 :V00560000", "RL1 :V01020200" - A12 50mm F2.5 Macro + # "RL2 :V00560000", "RL2 :V01020300" - S10 24-70mm F2.5-4.4 VC + # --> used in a Composite tag to determine LensType + }, + 48 => { + Name => 'LensSerialNumber', + Format => 'string[16]', + # observed: (S10) "LID:00010024" --> "WF00010024" on plate + # (A12) "LID:00010054" --> "WE00010029" on plate?? + }, +); + +# Ricoh text-type maker notes (PH) +%Image::ExifTool::Ricoh::Text = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PROCESS_PROC => \&ProcessRicohText, + NOTES => q{ + Some Ricoh DC and RDC models use a text-based format for their maker notes + instead of the IFD format used by the Caplio models. Below is a list of known + tags in this information. + }, + Rev => { + Name => 'FirmwareVersion', + PrintConv => '$val=~/^\d+$/ ? sprintf("%.2f",$val/100) : $val', + PrintConvInv => '$val=~/^(\d+)\.(\d+)$/ ? sprintf("%.2d%.2d",$1,$2) : $val', + }, + Rv => { + Name => 'FirmwareVersion', + PrintConv => '$val=~/^\d+$/ ? sprintf("%.2f",$val/100) : $val', + PrintConvInv => '$val=~/^(\d+)\.(\d+)$/ ? sprintf("%.2d%.2d",$1,$2) : $val', + }, + Rg => 'RedGain', + Gg => 'GreenGain', + Bg => 'BlueGain', +); + +%Image::ExifTool::Ricoh::RMETA = ( + GROUPS => { 0 => 'APP5', 1 => 'RMETA', 2 => 'Image' }, + PROCESS_PROC => \&Image::ExifTool::Ricoh::ProcessRicohRMETA, + NOTES => q{ + The Ricoh Caplio Pro G3 has the ability to add custom fields to the APP5 + "RMETA" segment of JPEG images. While only a few observed tags have been + defined below, ExifTool will extract any information found here. + }, + 'Sign type' => { Name => 'SignType', PrintConv => { + 1 => 'Directional', + 2 => 'Warning', + 3 => 'Information', + } }, + Location => { PrintConv => { + 1 => 'Verge', + 2 => 'Gantry', + 3 => 'Central reservation', + 4 => 'Roundabout', + } }, + Lit => { PrintConv => { + 1 => 'Yes', + 2 => 'No', + } }, + Condition => { PrintConv => { + 1 => 'Good', + 2 => 'Fair', + 3 => 'Poor', + 4 => 'Damaged', + } }, + Azimuth => { PrintConv => { + 1 => 'N', + 2 => 'NNE', + 3 => 'NE', + 4 => 'ENE', + 5 => 'E', + 6 => 'ESE', + 7 => 'SE', + 8 => 'SSE', + 9 => 'S', + 10 => 'SSW', + 11 => 'SW', + 12 => 'WSW', + 13 => 'W', + 14 => 'WNW', + 15 => 'NW', + 16 => 'NNW', + } }, + _audio => { + Name => 'SoundFile', + Notes => 'audio data recorded in JPEG images by the G700SE', + }, + _barcode => { Name => 'Barcodes', List => 1 }, +); + +# information stored in Ricoh AVI images (ref PH) +%Image::ExifTool::Ricoh::AVI = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Video' }, + ucmt => { + Name => 'Comment', + # Ricoh writes a "Unicode" header even when text is ASCII (spaces anyway) + ValueConv => '$_=$val; s/^(Unicode\0|ASCII\0\0\0)//; tr/\0//d; s/\s+$//; $_', + }, + mnrt => { + Name => 'MakerNoteRicoh', + SubDirectory => { + TagTable => 'Image::ExifTool::Ricoh::Main', + Start => '$valuePtr + 8', + ByteOrder => 'BigEndian', + Base => '8', + }, + }, + rdc2 => { + Name => 'RicohRDC2', + Unknown => 1, + ValueConv => 'unpack("H*",$val)', + # have seen values like 0a000444 and 00000000 - PH + }, + thum => { + Name => 'ThumbnailImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, +); + +# Ricoh composite tags +%Image::ExifTool::Ricoh::Composite = ( + GROUPS => { 2 => 'Camera' }, + LensID => { + SeparateTable => 'Ricoh LensID', + Require => 'Ricoh:LensFirmware', + RawConv => '$val[0] ? $val[0] : undef', + ValueConv => '$val=~s/\s*:.*//; $val', + PrintConv => \%ricohLensIDs, + }, + RicohPitch => { + Require => 'Ricoh:Accelerometer', + ValueConv => 'my @v = split(" ",$val); $v[1]', + }, + RicohRoll => { + Require => 'Ricoh:Accelerometer', + ValueConv => 'my @v = split(" ",$val); $v[0] <= 180 ? $v[0] : $v[0] - 360', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Ricoh'); + + +#------------------------------------------------------------------------------ +# Process Ricoh text-based maker notes +# Inputs: 0) ExifTool object reference +# 1) Reference to directory information hash +# 2) Pointer to tag table for this directory +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessRicohText($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataLen = $$dirInfo{DataLen}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || $dataLen - $dirStart; + my $verbose = $et->Options('Verbose'); + + my $data = substr($$dataPt, $dirStart, $dirLen); + return 1 if $data =~ /^\0/; # blank Ricoh maker notes + $et->VerboseDir('RicohText', undef, $dirLen); + # validate text maker notes + unless ($data =~ /^(Rev|Rv)/) { + $et->Warn('Bad Ricoh maker notes'); + return 0; + } + while ($data =~ m/([A-Z][a-z]{1,2})([0-9A-F]+);/sg) { + my $tag = $1; + my $val = $2; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($verbose) { + $et->VerboseInfo($tag, $tagInfo, + Table => $tagTablePtr, + Value => $val, + ); + } + unless ($tagInfo) { + next unless $$et{OPTIONS}{Unknown}; + $tagInfo = { + Name => "Ricoh_Text_$tag", + Unknown => 1, + PrintConv => 'length($val) > 60 ? substr($val,0,55) . "[...]" : $val', + }; + # add tag information to table + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + $et->FoundTag($tagInfo, $val); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process Ricoh APP5 RMETA information +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessRicohRMETA($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart}; + my $dataLen = length($$dataPt); + my $dirLen = $dataLen - $dirStart; + my $verbose = $et->Options('Verbose'); + + $et->VerboseDir('Ricoh RMETA') if $verbose; + $dirLen < 20 and $et->Warn('Truncated Ricoh RMETA data', 1), return 0; + my $byteOrder = substr($$dataPt, $dirStart, 2); + $byteOrder = GetByteOrder() if $byteOrder eq "\0\0"; # (same order as container) + SetByteOrder($byteOrder) or $et->Warn('Bad Ricoh RMETA data', 1), return 0; + # get the RMETA segment number + my $rmetaNum = Get16u($dataPt, $dirStart+4); + if ($rmetaNum != 0) { + # not sure how to recognize audio, so do it by checking for "RIFF" header + # and assume all subsequent RMETA segments are part of the audio data + # (but it looks like the int16u at $dirStart+6 is the next block number + # if the data is continued, or 0 for the last block) + $dirLen < 14 and $et->Warn('Short Ricoh RMETA block', 1), return 0; + if ($$dataPt =~ /^.{20}BARCODE/s) { + my $val = substr($$dataPt, 20); + $val =~ s/\0.*//s; + $val =~ s/^BARCODE\w+,\d{2},//; + my @codes; + for (;;) { + $val =~ s/(\d+),// and length $val >= $1 or last; + push @codes, substr($val, 0, $1); + last unless length $val > $1; + $val = substr($val, $1+1); + } + $et->HandleTag($tagTablePtr, '_barcode', \@codes) if @codes; + return 1; + } elsif ($$dataPt =~ /^.{18}ASCII/s) { + # (ignore barcode tag names for now) + return 1; + } + my $audioLen = Get16u($dataPt, $dirStart+12); + $audioLen + 14 > $dirLen and $et->Warn('Truncated Ricoh RMETA audio data', 1), return 0; + my $buff = substr($$dataPt, $dirStart + 14, $audioLen); + if ($audioLen >= 4 and substr($buff, 0, 4) eq 'RIFF') { + $et->HandleTag($tagTablePtr, '_audio', \$buff); + } elsif ($$et{VALUE}{SoundFile}) { + ${$$et{VALUE}{SoundFile}} .= $buff; + } else { + $et->Warn('Unknown Ricoh RMETA type', 1); + return 0; + } + return 1; + } + # decode standard RMETA tag directory + my (@tags, @vals, @nums, $valPos, $numPos); + my $pos = $dirStart + Get16u($dataPt, $dirStart+8); + my $numEntries = Get16u($dataPt, $pos); + $numEntries > 100 and $et->Warn('Bad RMETA entry count'), return 0; + $pos += 10; # start of first RMETA section + # loop through RMETA sections + while ($pos <= $dataLen - 4) { + my $type = Get16u($dataPt, $pos); + my $size = Get16u($dataPt, $pos + 2); + last unless $size; + $pos += 4; + $size -= 2; + if ($size < 0 or $pos + $size > $dataLen) { + $et->Warn('Corrupted Ricoh RMETA data', 1); + last; + } + my $dat = substr($$dataPt, $pos, $size); + if ($verbose) { + $et->VPrint(2, "$$et{INDENT}RMETA section type=$type size=$size\n"); + $et->VerboseDump(\$dat, Addr => $$dirInfo{DataPos} + $pos); + } + if ($type == 1) { # section 1: tag names + # save the tag names + @tags = split /\0/, $dat, $numEntries+1; + } elsif ($type == 2 || $type == 18) { # section 2/18: string values (G800 uses type 18) + # save the tag values (assume "ASCII\0" encoding since others never seen) + @vals = split /\0/, $dat, $numEntries+1; + $valPos = $pos; # save position of first string value + } elsif ($type == 3) { # section 3: numerical values + if ($size < $numEntries * 2) { + $et->Warn('Truncated RMETA section 3'); + } else { + # save the numerical tag values + # (0=empty, 0xffff=text input, otherwise menu item number) + @nums = unpack(($byteOrder eq 'MM' ? 'n' : 'v').$numEntries, $dat); + $numPos = $pos; # save position of numerical values + } + } elsif ($type != 16) { + $et->Warn("Unrecognized RMETA section (type $type, len $size)"); + } + $pos += $size; + } + return 1 unless @tags or @vals; + $valPos or $valPos = 0; # (just in case there was no value section) + # find next tag in null-delimited list + # unpack numerical values from block of int16u values + my ($i, $name); + for ($i=0; $i<$numEntries; ++$i) { + my $tag = $tags[$i]; + my $val = $vals[$i]; + $val = '' unless defined $val; + unless (defined $tag and length $tag) { + length $val or ++$valPos, next; # (skip empty entries) + $tag = ''; + } + ($name = $tag) =~ s/\b([a-z])/\U$1/gs; # capitalize all words + $name =~ s/ (\w)/\U$1/g; # remove special characters + $name = 'RMETA_Unknown' unless length($name); + my $num = $nums[$i]; + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo) { + # make sure print conversion is defined + $$tagInfo{PrintConv} = { } unless ref $$tagInfo{PrintConv} eq 'HASH'; + } else { + # create tagInfo hash + $tagInfo = { Name => $name, PrintConv => { } }; + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + # use string value directly if no numerical value + $num = $val unless defined $num; + # add conversion for this value (replacing any existing entry) + $tagInfo->{PrintConv}->{$num} = length $val ? $val : $num; + if ($verbose) { + my %datParms; + if (length $val) { + %datParms = ( Start => $valPos, Size => length($val), Format => 'string' ); + } elsif ($numPos) { + %datParms = ( Start => $numPos + $i * 2, Size => 2, Format => 'int16u' ); + } + %datParms and $datParms{DataPt} = $dataPt, $datParms{DataPos} = $$dirInfo{DataPos}; + $et->VerboseInfo($tag, $tagInfo, Table=>$tagTablePtr, Value=>$num, %datParms); + } + $et->FoundTag($tagInfo, $num); + $valPos += length($val) + 1; + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Ricoh - Ricoh 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 Ricoh maker notes EXIF meta information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.ozhiker.com/electronics/pjmt/jpeg_info/ricoh_mn.html> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Tim Gray for his help decoding a number of tags for the Ricoh GR. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Ricoh Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Samsung.pm b/ExifTool/lib/Image/ExifTool/Samsung.pm new file mode 100644 index 0000000..546c91e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Samsung.pm @@ -0,0 +1,1749 @@ +#------------------------------------------------------------------------------ +# 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) Nick Livchits private communication +# 6) Sreerag Raghavan private communication (SM-C200) +# IB) Iliah Borg private communication (LibRaw) +# NJ) Niels Kristian Bech Jensen 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.54'; + +sub WriteSTMN($$$); +sub ProcessINFO($$$); +sub ProcessSamsungMeta($$$); +sub ProcessSamsungIFD($$$); +sub ProcessSamsung($$$); + +# 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 => 'Samsung NX 16-50mm F2-2.8 S ED OIS', #forum3833 + 14 => 'Samsung NX 10mm F3.5 Fisheye', #NJ + 15 => 'Samsung NX 16-50mm F3.5-5.6 Power Zoom ED OIS', #5 + 20 => 'Samsung NX 50-150mm F2.8 S ED OIS', #PH + 21 => 'Samsung NX 300mm F2.8 ED OIS', #IB +); + +# 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, + WriteGroup => 'MakerNotes', + }, + 3 => { + Name => 'PreviewImageLength', + OffsetPair => 2, # point to associated offset + DataTag => 'PreviewImage', + Protected => 2, + WriteGroup => 'MakerNotes', + }, + 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 L<Unknown|../ExifTool.html#Unknown> (-u) or L<Verbose|../ExifTool.html#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, + }, + 0x0002 => { + Name => 'DeviceType', + Groups => { 2 => 'Camera' }, + Writable => 'int32u', + PrintHex => 1, + PrintConv => { + 0x1000 => 'Compact Digital Camera', + 0x2000 => 'High-end NX Camera', + 0x3000 => 'HXM Video Camera', + 0x12000 => 'Cell Phone', + 0x300000 => 'SMX Video Camera', + }, + }, + 0x0003 => { + Name => 'SamsungModelID', + Groups => { 2 => 'Camera' }, + Writable => 'int32u', + PrintHex => 1, + PrintConv => { + 0x100101c => 'NX10', + 0x1001226 => 'HMX-S10BP', + 0x1001226 => 'HMX-S15BP', + 0x1001233 => 'HMX-Q10', + 0x1001234 => 'HMX-H300', + 0x1001234 => 'HMX-H304', + 0x100130c => 'NX100', + 0x1001327 => 'NX11', + 0x170104b => 'ES65, ES67 / VLUU ES65, ES67 / SL50', + 0x170104e => 'ES70, ES71 / VLUU ES70, ES71 / SL600', + 0x1701052 => 'ES73 / VLUU ES73 / SL605', + 0x1701055 => 'ES25, ES27 / VLUU ES25, ES27 / SL45', + 0x1701300 => 'ES28 / VLUU ES28', + 0x1701303 => 'ES74,ES75,ES78 / VLUU ES75,ES78', + 0x2001046 => 'PL150 / VLUU PL150 / TL210 / PL151', + 0x2001048 => 'PL100 / TL205 / VLUU PL100 / PL101', + 0x2001311 => 'PL120,PL121 / VLUU PL120,PL121', + 0x2001315 => 'PL170,PL171 / VLUUPL170,PL171', + 0x200131e => 'PL210, PL211 / VLUU PL210, PL211', + 0x2701317 => 'PL20,PL21 / VLUU PL20,PL21', + 0x2a0001b => 'WP10 / VLUU WP10 / AQ100', + 0x3000000 => 'Various Models (0x3000000)', + #0x3000000 => 'DV150F / DV151F / DV155F', + #0x3000000 => 'NX mini', + #0x3000000 => 'NX3000', + #0x3000000 => 'NX3300', + #0x3000000 => 'ST150F / ST151F / ST152F', + #0x3000000 => 'WB200F / WB201F / WB202F', + #0x3000000 => 'WB250F / WB251F / WB252F', + #0x3000000 => 'WB30F / WB31F / WB32F', + #0x3000000 => 'WB350F / WB351F / WB352F', + #0x3000000 => 'WB800F', + 0x3a00018 => 'Various Models (0x3a00018)', + #0x3a00018 => 'ES30 / VLUU ES30', + #0x3a00018 => 'ES80 / ES81', + #0x3a00018 => 'ES9 / ES8', + #0x3a00018 => 'PL200 / VLUU PL200', + #0x3a00018 => 'PL80 / VLUU PL80 / SL630 / PL81', + #0x3a00018 => 'PL90 / VLUU PL90', + #0x3a00018 => 'WB1100F / WB1101F / WB1102F', + #0x3a00018 => 'WB2200F', + 0x400101f => 'ST1000 / ST1100 / VLUU ST1000 / CL65', + 0x4001022 => 'ST550 / VLUU ST550 / TL225', + 0x4001025 => 'Various Models (0x4001025)', + #0x4001025 => 'DV300 / DV300F / DV305F', + #0x4001025 => 'ST500 / VLUU ST500 / TL220', + #0x4001025 => 'ST200 / ST200F / ST201 / ST201F / ST205F', + 0x400103e => 'VLUU ST5500, ST5500, CL80', + 0x4001041 => 'VLUU ST5000, ST5000, TL240', + 0x4001043 => 'ST70 / VLUU ST70 / ST71', + 0x400130a => 'Various Models (0x400130a)', + #0x400130a => 'VLUU ST100, ST100', + #0x400130a => 'VLUU ST600, ST600', + #0x400130a => 'VLUU ST80, ST80', + 0x400130e => 'ST90,ST91 / VLUU ST90,ST91', + 0x4001313 => 'VLUU ST95, ST95', + 0x4a00015 => 'VLUU ST60', + 0x4a0135b => 'ST30, ST65 / VLUU ST65 / ST67', + 0x5000000 => 'Various Models (0x5000000)', + #0x5000000 => 'EX2F', + #0x5000000 => 'NX1000', + #0x5000000 => 'NX20', + #0x5000000 => 'NX200', + #0x5000000 => 'NX210', + #0x5000000 => 'ST96', + #0x5000000 => 'WB750', + #0x5000000 => 'ST700', + 0x5001038 => 'Various Models (0x5001038)', + #0x5001038 => 'EK-GN120', + #0x5001038 => 'HMX-E10', + #0x5001038 => 'NX1', + #0x5001038 => 'NX2000', + #0x5001038 => 'NX30', + #0x5001038 => 'NX300', + #0x5001038 => 'NX500', + #0x5001038 => 'SM-C200', + #0x5001038 => 'WB2000', + 0x500103a => 'WB650 / VLUU WB650 / WB660', + 0x500103c => 'WB600 / VLUU WB600 / WB610', + 0x500133e => 'WB150 / WB150F / WB152 / WB152F / WB151', + 0x5a0000f => 'WB5000 / HZ25W', + 0x5a0001e => 'WB5500 / VLUU WB5500 / HZ50W', + 0x6001036 => 'EX1', + 0x700131c => 'VLUU SH100, SH100', + 0x27127002 => 'SMX-C20N', + }, + }, + # 0x0004 - undef[x] (SamsungContentsID?) + # 0x000a - int32u (ContinuousShotMode?) + # 0x000b - int16u (BestPhotoMode?) + # 0x000c - int32u ? values: 0,1 + # 0x000e - int32u[2] (SoundMultiPicture?) + # 0x0010 - rational64u ? values: undef,inf + 0x0011 => { #6 + Name => 'OrientationInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Samsung::OrientationInfo' }, + }, + 0x0020 => [{ #forum7685 + Name => 'SmartAlbumColor', + Condition => '$$valPt =~ /^\0{4}/', + Writable => 'int16u', + Count => 2, + PrintConv => { + '0 0' => 'n/a', + }, + },{ + Name => 'SmartAlbumColor', + Writable => 'int16u', + Count => 2, + PrintConv => [{ + 0 => 'Red', + 1 => 'Yellow', + 2 => 'Green', + 3 => 'Blue', + 4 => 'Magenta', + 5 => 'Black', + 6 => 'White', + 7 => 'Various', + }], + }], + 0x0021 => { #1 + Name => 'PictureWizard', + Writable => 'int16u', + SubDirectory => { TagTable => 'Image::ExifTool::Samsung::PictureWizard' }, + }, + # 0x0022 - int32u (CaptureMode?) (Gamma? eg. 65538 = 1.2, ref forum7720) + # 0x0023 - string: "0123456789" (PH) (placeholder for SerialNumber?) + # 0x0025 - int32u (ImageCount?) + # 0x002a - undef[4] (SNSDirectShare?) + # 0x002f - string (GPSInfo01?) + 0x0030 => { #1 (NX100 with GPS) + Name => 'LocalLocationName', + Groups => { 2 => 'Location' }, + 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', + Groups => { 2 => 'Location' }, + Writable => 'string', + }, + # 0x0032 - string (GPSInfo03) + # 0x0033 - string (GPSInfo04) + # 0x0034 - string (GPSInfo05) + 0x0035 => [{ + Name => 'PreviewIFD', + Condition => '$$self{TIFF_TYPE} eq "SRW" and $$self{Model} ne "EK-GN120"', # (not an IFD in JPEG images) + Groups => { 1 => 'PreviewIFD' }, + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Nikon::PreviewIFD', + ByteOrder => 'Unknown', + Start => '$val', + }, + },{ + 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 - 36', + }, + }], + # 0x003a - int16u[2] (SmartLensInfo?) + # 0x003b - int16u[2] (PhotoStyleSelectInfo?) + # 0x003c - int16u (SmartRange?) + # 0x003d - int16u[5] (SmartCropInfo?) + # 0x003e - int32u (DualCapture?) + # 0x003f - int16u[2] (SGIFInfo?) + 0x0040 => { #forum7432 + Name => 'RawDataByteOrder', + PrintConv => { + 0 => 'Little-endian (Intel, II)', + 1 => 'Big-endian (Motorola, MM)', #(NC) + }, + }, + 0x0041 => { #forum7684 + Name => 'WhiteBalanceSetup', + Writable => 'int32u', + PrintConv => { + 0 => 'Auto', + 1 => 'Manual', + }, + }, + 0x0043 => { #1 (NC) + Name => 'CameraTemperature', + Groups => { 2 => 'Camera' }, + Writable => 'rational64s', + # (DPreview samples all 0.2 C --> pre-production model) + PrintConv => '$val =~ /\d/ ? "$val C" : $val', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + # 0x0045 => { Name => 'RawCompressionMode', Writable => 'int32u' }, # (related to ExposureMode, not raw compresison? ref forum7432) + # 0x004a - int32u[7] (ImageVerification?) + # 0x004b - int32u[2] (RewindInfo?) + # 0x0050 - int32u (ColorSpace? - inconsistent) values: 1 (related to compression mode, ref forum7432) + 0x0050 => { #forum7432 + Name => 'RawDataCFAPattern', + PrintConv => { + 0 => 'Unchanged', + 1 => 'Swap', + 65535 => 'Roll', + }, + }, + # 0x0054 - int16u[2] (WeatherInfo?) + # 0x0060 - undef (AEInfo?) + # 0x0080 - undef (AFInfo?) + # 0x00a0 - undef[8192] (AWBInfo1): 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) + # 0x00a1 - undef (AWBInfo2?) + # 0x00c0 - undef (IPCInfo?) + # 0x00c7 - undef (SmartFunctionInfo?) + # 0x00e0 - int16u (SceneResult?) + # 0x00e1 - int16u[8] (SADebugInfo01?) + # 0x00e1 - int16u[x] (SADebugInfo02?) + 0x0100 => { + Name => 'FaceDetect', + Writable => 'int16u', + PrintConv => { 0 => 'Off', 1 => 'On' }, #(NC) + }, + # 0x0101 - int16u[6] (FaceDetectInfo?) + # 0x0102 - int16u[x] (FaceDetectInfo?) + 0x0120 => { + Name => 'FaceRecognition', + Writable => 'int32u', + PrintConv => { 0 => 'Off', 1 => 'On' }, #(NC) + }, + 0x0123 => { Name => 'FaceName', Writable => 'string' }, + # 0x140 - undef (LensInfo?) +# +# the following tags found only in SRW images +# + # 0xa000 - rational64u: 1 or 1.1 (ref PH) (MakerNoteVersion?) + 0xa001 => { #1 + Name => 'FirmwareName', + Groups => { 2 => 'Camera' }, + Writable => 'string', + }, + 0xa002 => { #PH/IB + Name => 'SerialNumber', + Condition => '$$valPt =~ /^\w{5}/', # should be at least 5 characters long + Groups => { 2 => 'Camera' }, + Writable => 'string', + }, + 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 => { + Name => 'InternalLensSerialNumber', # Not the printed serial number (ref 1) + Groups => { 2 => 'Camera' }, + Writable => 'string', + }, + 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', + ValueConv => '$val=~s/ .*//; $val', # some models write 2 values here + ValueConvInv => '$val', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => '$val', + }, + 0xa019 => { #1 + Name => 'FNumber', + Priority => 0, + Writable => 'rational64u', + ValueConv => '$val=~s/ .*//; $val', # some models write 2 values here + ValueConvInv => '$val', + 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', + }, + # 0xa01b - int32u (ImageCount?) + # 0xa01b - int16u (LDCLens?) + 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)', + }, + 0xa025 => { # (PostAEGain?) + Name => 'DigitalGain', #IB + Writable => 'int32u', + RawConv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,6)', + RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,-6)', + }, + 0xa025 => { #IB + Name => 'HighlightLinearityLimit', + Writable => 'int32u', + }, + 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 => 'CbCrMatrixDefault', + 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 => 'CbCrMatrix', + 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 => 'CbCrGainDefault', + 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 => 'CbCrGain', + 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 => 'ToneCurveSRGBDefault', + 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 => 'ToneCurveAdobeRGBDefault', + 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 => 'ToneCurveSRGB', + 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 => 'ToneCurveAdobeRGB', + 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 => 'RawData', + Unknown => 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 + Name => 'Distortion', + Unknown => 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 => 'ChromaticAberration', + Unknown => 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 + Name => 'Vignetting', + Unknown => 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 => 'VignettingCorrection', + Unknown => 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 => 'VignettingSetting', + Unknown => 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', # (DistortionCamera1st?) + 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', # (DistortionCamera2nd?) + 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', # (DistortionCameraSetting?) + 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)', + }, + # 0xa060 - rational64u (CISTemperature?) + # 0xa061 - int16u (Compression?) +); + +# orientation information (ref 6) +%Image::ExifTool::Samsung::OrientationInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'rational64s', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Camera orientation information written by the Gear 360 (SM-C200).', + 0 => { + Name => 'YawAngle', #(NC) + Unknown => 1, + Notes => 'always zero', + }, + 1 => { + Name => 'PitchAngle', + Notes => 'upward tilt of rear camera in degrees', + }, + 2 => { + Name => 'RollAngle', + Notes => 'clockwise rotation of rear camera in degrees', + }, +); + +# 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', + Groups => { 2 => 'Preview' }, + 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 "ssuniqueid\0" APP5 (ref PH) +%Image::ExifTool::Samsung::APP5 = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + ssuniqueid => { + Name => 'UniqueID', + # 32 bytes - some sort of serial number? + ValueConv => 'unpack("H*",$val)', + }, +); + +# information extracted from Samsung trailer (ie. Samsung SM-T805 "Sound & Shot" JPEG) (ref PH) +%Image::ExifTool::Samsung::Trailer = ( + GROUPS => { 0 => 'MakerNotes', 2 => 'Other' }, + VARS => { NO_ID => 1, HEX_ID => 0 }, + PROCESS_PROC => \&ProcessSamsung, + PRIORITY => 0, # (first one takes priority so DepthMapWidth/Height match first DepthMapData) + 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, and from the 'sefd' atom in HEIC images from the + Samsung S10+. + }, + '0x0001-name' => { + Name => 'EmbeddedImageName', # ("DualShot_1","DualShot_2") + RawConv => '$$self{EmbeddedImageName} = $val', + }, + '0x0001' => [ + { + Name => 'EmbeddedImage', + Condition => '$$self{EmbeddedImageName} eq "DualShot_1"', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + { + Name => 'EmbeddedImage2', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + ], + '0x0100-name' => 'EmbeddedAudioFileName', # ("SoundShot_000") + '0x0100' => { Name => 'EmbeddedAudioFile', Groups => { 2 => 'Audio' }, Binary => 1 }, + '0x0201-name' => 'SurroundShotVideoName', # ("Interactive_Panorama_000") + '0x0201' => { Name => 'SurroundShotVideo', Groups => { 2 => 'Video' }, Binary => 1 }, + # 0x0800-name - seen 'SoundShot_Meta_Info' + # 0x0800 - unknown (29 bytes) (contains already-extracted EmbeddedAudioFileName) + # 0x0830-name - seen '1165724808.pre' + # 0x0830 - unknown (164004 bytes) + # 0x08d0-name - seen 'Interactive_Panorama_Info' + # 0x08d0 - unknown (7984 bytes) + # 0x08e0-name - seen 'Panorama_Shot_Info' + # 0x08e0 - string, seen 'PanoramaShot' + # 0x08e1-name - seen 'Motion_Panorama_Info' + # 0x0910-name - seen 'Front_Cam_Selfie_Info' + # 0x0910 - string, seen 'Front_Cam_Selfie_Info' + # 0x09e0-name - seen 'Burst_Shot_Info' + # 0x09e0 - string, seen '489489125' + # 0x0a01-name - seen 'Image_UTC_Data' + '0x0a01' => { #forum7161 + Name => 'TimeStamp', + Groups => { 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val / 1e3, 1, 3)', + PrintConv => '$self->ConvertDateTime($val)', + }, + '0x0a20-name' => 'DualCameraImageName', # ("FlipPhoto_002") + '0x0a20' => { Name => 'DualCameraImage', Groups => { 2 => 'Preview' }, Binary => 1 }, + '0x0a30-name' => 'EmbeddedVideoType', # ("MotionPhoto_Data") + '0x0a30' => { Name => 'EmbeddedVideoFile', Groups => { 2 => 'Video' }, Binary => 1 }, #forum7161 + # 0x0aa1-name - seen 'MCC_Data' + # 0x0aa1 - seen '204','222','234','302','429' + '0x0aa1' => { + Name => 'MCCData', + Groups => { 2 => 'Location' }, + PrintConv => { + 202 => 'Greece (202)', + 204 => 'Netherlands (204)', + 206 => 'Belgium (206)', + 208 => 'France (208)', + 212 => 'Monaco (212)', + 213 => 'Andorra (213)', + 214 => 'Spain (214)', + 216 => 'Hungary (216)', + 218 => 'Bosnia & Herzegov. (218)', + 219 => 'Croatia (219)', + 220 => 'Serbia (220)', + 221 => 'Kosovo (221)', + 222 => 'Italy (222)', + 226 => 'Romania (226)', + 228 => 'Switzerland (228)', + 230 => 'Czech Rep. (230)', + 231 => 'Slovakia (231)', + 232 => 'Austria (232)', + 234 => 'United Kingdom (234)', + 235 => 'United Kingdom (235)', + 238 => 'Denmark (238)', + 240 => 'Sweden (240)', + 242 => 'Norway (242)', + 244 => 'Finland (244)', + 246 => 'Lithuania (246)', + 247 => 'Latvia (247)', + 248 => 'Estonia (248)', + 250 => 'Russian Federation (250)', + 255 => 'Ukraine (255)', + 257 => 'Belarus (257)', + 259 => 'Moldova (259)', + 260 => 'Poland (260)', + 262 => 'Germany (262)', + 266 => 'Gibraltar (266)', + 268 => 'Portugal (268)', + 270 => 'Luxembourg (270)', + 272 => 'Ireland (272)', + 274 => 'Iceland (274)', + 276 => 'Albania (276)', + 278 => 'Malta (278)', + 280 => 'Cyprus (280)', + 282 => 'Georgia (282)', + 283 => 'Armenia (283)', + 284 => 'Bulgaria (284)', + 286 => 'Turkey (286)', + 288 => 'Faroe Islands (288)', + 289 => 'Abkhazia (289)', + 290 => 'Greenland (290)', + 292 => 'San Marino (292)', + 293 => 'Slovenia (293)', + 294 => 'Macedonia (294)', + 295 => 'Liechtenstein (295)', + 297 => 'Montenegro (297)', + 302 => 'Canada (302)', + 308 => 'St. Pierre & Miquelon (308)', + 310 => 'United States / Guam (310)', + 311 => 'United States / Guam (311)', + 312 => 'United States (312)', + 316 => 'United States (316)', + 330 => 'Puerto Rico (330)', + 334 => 'Mexico (334)', + 338 => 'Jamaica (338)', + 340 => 'French Guiana / Guadeloupe / Martinique (340)', + 342 => 'Barbados (342)', + 344 => 'Antigua and Barbuda (344)', + 346 => 'Cayman Islands (346)', + 348 => 'British Virgin Islands (348)', + 350 => 'Bermuda (350)', + 352 => 'Grenada (352)', + 354 => 'Montserrat (354)', + 356 => 'Saint Kitts and Nevis (356)', + 358 => 'Saint Lucia (358)', + 360 => 'St. Vincent & Gren. (360)', + 362 => 'Bonaire, Sint Eustatius and Saba / Curacao / Netherlands Antilles (362)', + 363 => 'Aruba (363)', + 364 => 'Bahamas (364)', + 365 => 'Anguilla (365)', + 366 => 'Dominica (366)', + 368 => 'Cuba (368)', + 370 => 'Dominican Republic (370)', + 372 => 'Haiti (372)', + 374 => 'Trinidad and Tobago (374)', + 376 => 'Turks and Caicos Islands / US Virgin Islands (376)', + 400 => 'Azerbaijan (400)', + 401 => 'Kazakhstan (401)', + 402 => 'Bhutan (402)', + 404 => 'India (404)', + 405 => 'India (405)', + 410 => 'Pakistan (410)', + 412 => 'Afghanistan (412)', + 413 => 'Sri Lanka (413)', + 414 => 'Myanmar (Burma) (414)', + 415 => 'Lebanon (415)', + 416 => 'Jordan (416)', + 417 => 'Syrian Arab Republic (417)', + 418 => 'Iraq (418)', + 419 => 'Kuwait (419)', + 420 => 'Saudi Arabia (420)', + 421 => 'Yemen (421)', + 422 => 'Oman (422)', + 424 => 'United Arab Emirates (424)', + 425 => 'Israel / Palestinian Territory (425)', + 426 => 'Bahrain (426)', + 427 => 'Qatar (427)', + 428 => 'Mongolia (428)', + 429 => 'Nepal (429)', + 430 => 'United Arab Emirates (430)', + 431 => 'United Arab Emirates (431)', + 432 => 'Iran (432)', + 434 => 'Uzbekistan (434)', + 436 => 'Tajikistan (436)', + 437 => 'Kyrgyzstan (437)', + 438 => 'Turkmenistan (438)', + 440 => 'Japan (440)', + 441 => 'Japan (441)', + 450 => 'South Korea (450)', + 452 => 'Viet Nam (452)', + 454 => 'Hongkong, China (454)', + 455 => 'Macao, China (455)', + 456 => 'Cambodia (456)', + 457 => 'Laos P.D.R. (457)', + 460 => 'China (460)', + 466 => 'Taiwan (466)', + 467 => 'North Korea (467)', + 470 => 'Bangladesh (470)', + 472 => 'Maldives (472)', + 502 => 'Malaysia (502)', + 505 => 'Australia (505)', + 510 => 'Indonesia (510)', + 514 => 'Timor-Leste (514)', + 515 => 'Philippines (515)', + 520 => 'Thailand (520)', + 525 => 'Singapore (525)', + 528 => 'Brunei Darussalam (528)', + 530 => 'New Zealand (530)', + 537 => 'Papua New Guinea (537)', + 539 => 'Tonga (539)', + 540 => 'Solomon Islands (540)', + 541 => 'Vanuatu (541)', + 542 => 'Fiji (542)', + 544 => 'American Samoa (544)', + 545 => 'Kiribati (545)', + 546 => 'New Caledonia (546)', + 547 => 'French Polynesia (547)', + 548 => 'Cook Islands (548)', + 549 => 'Samoa (549)', + 550 => 'Micronesia (550)', + 552 => 'Palau (552)', + 553 => 'Tuvalu (553)', + 555 => 'Niue (555)', + 602 => 'Egypt (602)', + 603 => 'Algeria (603)', + 604 => 'Morocco (604)', + 605 => 'Tunisia (605)', + 606 => 'Libya (606)', + 607 => 'Gambia (607)', + 608 => 'Senegal (608)', + 609 => 'Mauritania (609)', + 610 => 'Mali (610)', + 611 => 'Guinea (611)', + 612 => 'Ivory Coast (612)', + 613 => 'Burkina Faso (613)', + 614 => 'Niger (614)', + 615 => 'Togo (615)', + 616 => 'Benin (616)', + 617 => 'Mauritius (617)', + 618 => 'Liberia (618)', + 619 => 'Sierra Leone (619)', + 620 => 'Ghana (620)', + 621 => 'Nigeria (621)', + 622 => 'Chad (622)', + 623 => 'Central African Rep. (623)', + 624 => 'Cameroon (624)', + 625 => 'Cape Verde (625)', + 626 => 'Sao Tome & Principe (626)', + 627 => 'Equatorial Guinea (627)', + 628 => 'Gabon (628)', + 629 => 'Congo, Republic (629)', + 630 => 'Congo, Dem. Rep. (630)', + 631 => 'Angola (631)', + 632 => 'Guinea-Bissau (632)', + 633 => 'Seychelles (633)', + 634 => 'Sudan (634)', + 635 => 'Rwanda (635)', + 636 => 'Ethiopia (636)', + 637 => 'Somalia (637)', + 638 => 'Djibouti (638)', + 639 => 'Kenya (639)', + 640 => 'Tanzania (640)', + 641 => 'Uganda (641)', + 642 => 'Burundi (642)', + 643 => 'Mozambique (643)', + 645 => 'Zambia (645)', + 646 => 'Madagascar (646)', + 647 => 'Reunion (647)', + 648 => 'Zimbabwe (648)', + 649 => 'Namibia (649)', + 650 => 'Malawi (650)', + 651 => 'Lesotho (651)', + 652 => 'Botswana (652)', + 653 => 'Swaziland (653)', + 654 => 'Comoros (654)', + 655 => 'South Africa (655)', + 657 => 'Eritrea (657)', + 659 => 'South Sudan (659)', + 702 => 'Belize (702)', + 704 => 'Guatemala (704)', + 706 => 'El Salvador (706)', + 708 => 'Honduras (708)', + 710 => 'Nicaragua (710)', + 712 => 'Costa Rica (712)', + 714 => 'Panama (714)', + 716 => 'Peru (716)', + 722 => 'Argentina Republic (722)', + 724 => 'Brazil (724)', + 730 => 'Chile (730)', + 732 => 'Colombia (732)', + 734 => 'Venezuela (734)', + 736 => 'Bolivia (736)', + 738 => 'Guyana (738)', + 740 => 'Ecuador (740)', + 744 => 'Paraguay (744)', + 746 => 'Suriname (746)', + 748 => 'Uruguay (748)', + 750 => 'Falkland Islands (Malvinas) (750)', + 901 => 'International Networks / Satellite Networks (901)', + }, + }, + # 0x0ab0-name - seen 'DualShot_Meta_Info' + '0x0ab1-name' => { + Name => 'DepthMapName', + # seen 'DualShot_DepthMap_1' (SM-N950U), DualShot_DepthMap_5 (SM-G998W) + RawConv => '$$self{DepthMapName} = $val', + }, + '0x0ab1' => [ + { + Name => 'DepthMapData', + Condition => '$$self{DepthMapName} eq "DualShot_DepthMap_1"', + Binary => 1, + },{ + Name => 'DepthMapData2', + Binary => 1, + }, + ], + # 0x0ab3-name - seen 'DualShot_Extra_Info' (SM-N950U) + '0x0ab3' => { # (SM-N950U) + Name => 'DualShotExtra', + SubDirectory => { TagTable => 'Image::ExifTool::Samsung::DualShotExtra' }, + }, + # 0x0ac0-name - seen 'ZoomInOut_Info' (SM-N950U) + # 0x0ac0 - 2048 bytes of interesting stuff including firmware version? (SM-N950U) + '0x0b40' => { # (SM-N975X front camera) + Name => 'SingleShotMeta', + SubDirectory => { TagTable => 'Image::ExifTool::Samsung::SingleShotMeta' }, + }, + # 0x0b41-name - seen 'SingeShot_DepthMap_1' (Yes, "Singe") (SM-N975X front camera) + '0x0b41' => { Name => 'SingleShotDepthMap', Binary => 1 }, + # 0x0ba1-name - seen 'Original_Path_Hash_Key', 'PhotoEditor_Re_Edit_Data' + # 0xa050-name - seen 'Jpeg360_2D_Info' (Samsung Gear 360) + # 0xa050 - seen 'Jpeg3602D' (Samsung Gear 360) + # 0x0c81-name - seen 'Watermark_Info' +); + +# DualShot Extra Info (ref PH) +%Image::ExifTool::Samsung::DualShotExtra = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FIRST_ENTRY => 0, + FORMAT => 'int32u', + # This is a pain, but the DepthMapWidth/Height move around in this record. + # In all of my samples so far, the bytes "01 00 ff ff" precede these tags. + # I have seen this byte sequence at offsets 32, 60, 64 and 68, so look for + # it in bytes 32-95, and use its location to adjust the tag positions + 8 => { + Name => 'DualShotDummy', + Format => 'undef[64]', + Hidden => 1, + Hook => q{ + if ($size >= 96) { + my $tmp = substr($$dataPt, $pos, 64); + # (have seen 0x01,0x03 and 0x07) + if ($tmp =~ /[\x01-\x09]\0\xff\xff/g and not pos($tmp) % 4) { + $$self{DepthMapTagPos} = pos($tmp); + $varSize += $$self{DepthMapTagPos} - 32; + } + } + }, + RawConv => 'undef', # not a real tag + }, + 16 => { + Name => 'DepthMapWidth', + Condition => '$$self{DepthMapTagPos}', + Notes => 'index varies depending on model', + }, + 17 => { + Name => 'DepthMapHeight', + Condition => '$$self{DepthMapTagPos}', + Notes => 'index varies depending on model', + }, +); + +# SingleShot Meta Info (ref PH) (SM-N975X front camera) +%Image::ExifTool::Samsung::SingleShotMeta = ( + PROCESS_PROC => \&ProcessSamsungMeta, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + inputWidth => { }, + inputHeight => { }, + outputWidth => { }, + outputHeight => { }, + segWidth => { }, + segHeight => { }, + depthSWWidth => { }, + depthSWHeight => { }, + depthHWWidth => { }, + depthHWHeight => { }, + flipStatus => { }, + lensFacing => { }, + deviceOrientation => { }, + objectOrientation => { }, + isArtBokeh => { }, + beautyRetouchLevel => { }, + beautyColorLevel => { }, + effectType => { }, + effectStrength => { }, + blurStrength => { }, + spinStrength => { }, + zoomStrength => { }, + colorpopStrength => { }, + monoStrength => { }, + sidelightStrength => { }, + vintageStrength => { }, + bokehShape => { }, + perfMode => { }, +); + +# 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"; + }, + }, + DepthMapTiff => { + Require => { + 0 => 'DepthMapData', + 1 => 'DepthMapWidth', + 2 => 'DepthMapHeight', + }, + ValueConv => q{ + return undef unless length ${$val[0]} == $val[1] * $val[2]; + my $tiff = MakeTiffHeader($val[1],$val[2],1,8) . ${$val[0]}; + return \$tiff; + }, + }, + SingleShotDepthMapTiff => { + Require => { + 0 => 'SingleShotDepthMap', + 1 => 'SegWidth', + 2 => 'SegHeight', + }, + ValueConv => q{ + return undef unless length ${$val[0]} == $val[1] * $val[2]; + my $tiff = MakeTiffHeader($val[1],$val[2],1,8) . ${$val[0]}; + return \$tiff; + }, + }, +); + +# 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; +} + +#------------------------------------------------------------------------------ +# Read Samsung Meta Info from trailer +# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref +# Returns: true on success +sub ProcessSamsungMeta($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dirName = $$dirInfo{DirName}; + my $dataPt = $$dirInfo{DataPt}; + my $pos = $$dirInfo{DirStart}; + my $end = $$dirInfo{DirLen} + $pos; + unless ($pos + 8 <= $end and substr($$dataPt, $pos, 4) eq 'DOFS') { + $et->Warn("Unrecognized $dirName data"); + return 0; + } + my $ver = Get32u($dataPt, $pos + 4); + if ($ver == 3) { + unless ($pos + 18 <= $end and Get32u($dataPt, $pos + 12) == $$dirInfo{DirLen}) { + $et->Warn("Unrecognized $dirName version $ver data"); + return 0; + } + my $num = Get16u($dataPt, $pos + 16); + $et->VerboseDir("$dirName version $ver", $num); + $pos += 18; + my ($i, $val); + for ($i=0; $i<$num; ++$i) { + last if $pos + 2 > $end; + my ($x, $n) = unpack("x${pos}CC", $$dataPt); + $pos += 2; + last if $pos + $n + 2 > $end; + my $tag = substr($$dataPt, $pos, $n); + my $len = Get16u($dataPt, $pos + $n); + $pos += $n + 2; + last if $pos + $len > $end; + if ($len == 4) { + $val = Get32u($dataPt, $pos); + } else { + my $tmp = substr($$dataPt, $pos, $len); + $val = \$pos; + } + $et->HandleTag($tagTablePtr, $tag, $val); + $pos += $len; + } + $et->Warn("Unexpected end of $dirName version $ver $i $num data") if $i < $num; + } + 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); + + unless ($raf) { + $raf = new File::RandomAccess($$dirInfo{DataPt}); + $et->VerboseDir('SamsungTrailer'); + } + 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 and $$dirInfo{RAF}) { + $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); + } + unless ($$tagTablePtr{"$tag-name"}) { + 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-2023, Phil Harvey (philharvey66 at gmail.com) + +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, Pascal de Bruijn for +the PictureWizard values, and everyone else who helped by discovering new +Samsung information. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Samsung Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Sanyo.pm b/ExifTool/lib/Image/ExifTool/Sanyo.pm new file mode 100644 index 0000000..c839dc2 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Sanyo.pm @@ -0,0 +1,440 @@ +#------------------------------------------------------------------------------ +# File: Sanyo.pm +# +# Description: Sanyo EXIF maker notes tags +# +# Revisions: 04/06/2004 - P. Harvey Created +# +# Reference: http://www.exif.org/makernotes/SanyoMakerNote.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::Sanyo; + +use strict; +use vars qw($VERSION); +use Image::ExifTool::Exif; + +$VERSION = '1.16'; + +my %offOn = ( + 0 => 'Off', + 1 => 'On', +); + +%Image::ExifTool::Sanyo::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 1, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x00ff => { + # this is an absolute offset in the JPG file... odd - PH + Name => 'MakerNoteOffset', + Writable => 'int32u', + }, + 0x0100 => { + Name => 'SanyoThumbnail', + Groups => { 2 => 'Preview' }, + Writable => 'undef', + WriteCheck => '$self->CheckImage(\$val)', + RawConv => '$self->ValidateImage(\$val,$tag)', + }, + 0x0200 => { + Name => 'SpecialMode', + Writable => 'int32u', + Count => 3, + }, + 0x0201 => { + Name => 'SanyoQuality', + Flags => 'PrintHex', + Writable => 'int16u', + PrintConv => { + 0x0000 => 'Normal/Very Low', + 0x0001 => 'Normal/Low', + 0x0002 => 'Normal/Medium Low', + 0x0003 => 'Normal/Medium', + 0x0004 => 'Normal/Medium High', + 0x0005 => 'Normal/High', + 0x0006 => 'Normal/Very High', + 0x0007 => 'Normal/Super High', + # have seen 0x11 with HD2000 in '8M-H JPEG' mode - PH + 0x0100 => 'Fine/Very Low', + 0x0101 => 'Fine/Low', + 0x0102 => 'Fine/Medium Low', + 0x0103 => 'Fine/Medium', + 0x0104 => 'Fine/Medium High', + 0x0105 => 'Fine/High', + 0x0106 => 'Fine/Very High', + 0x0107 => 'Fine/Super High', + 0x0200 => 'Super Fine/Very Low', + 0x0201 => 'Super Fine/Low', + 0x0202 => 'Super Fine/Medium Low', + 0x0203 => 'Super Fine/Medium', + 0x0204 => 'Super Fine/Medium High', + 0x0205 => 'Super Fine/High', + 0x0206 => 'Super Fine/Very High', + 0x0207 => 'Super Fine/Super High', + }, + }, + 0x0202 => { + Name => 'Macro', + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'Macro', + 2 => 'View', + 3 => 'Manual', + }, + }, + 0x0204 => { + Name => 'DigitalZoom', + Writable => 'rational64u', + }, + 0x0207 => 'SoftwareVersion', + 0x0208 => 'PictInfo', + 0x0209 => 'CameraID', + 0x020e => { + Name => 'SequentialShot', + Writable => 'int16u', + PrintConv => { + 0 => 'None', + 1 => 'Standard', + 2 => 'Best', + 3 => 'Adjust Exposure', + }, + }, + 0x020f => { + Name => 'WideRange', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x0210 => { + Name => 'ColorAdjustmentMode', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x0213 => { + Name => 'QuickShot', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x0214 => { + Name => 'SelfTimer', + Writable => 'int16u', + PrintConv => \%offOn, + }, + # 0x0215 - Flash? + 0x0216 => { + Name => 'VoiceMemo', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x0217 => { + Name => 'RecordShutterRelease', + Writable => 'int16u', + PrintConv => { + 0 => 'Record while down', + 1 => 'Press start, press stop', + }, + }, + 0x0218 => { + Name => 'FlickerReduce', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x0219 => { + Name => 'OpticalZoomOn', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x021b => { + Name => 'DigitalZoomOn', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x021d => { + Name => 'LightSourceSpecial', + Writable => 'int16u', + PrintConv => \%offOn, + }, + 0x021e => { + Name => 'Resaved', + Writable => 'int16u', + PrintConv => { + 0 => 'No', + 1 => 'Yes', + }, + }, + 0x021f => { + Name => 'SceneSelect', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Sport', + 2 => 'TV', + 3 => 'Night', + 4 => 'User 1', + 5 => 'User 2', + 6 => 'Lamp', #PH + }, + }, + 0x0223 => [ + { + Name => 'ManualFocusDistance', + Condition => '$format eq "rational64u"', + Writable => 'rational64u', + }, { #PH + Name => 'FaceInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Sanyo::FaceInfo' }, + }, + ], + 0x0224 => { + Name => 'SequenceShotInterval', + Writable => 'int16u', + PrintConv => { + 0 => '5 frames/s', + 1 => '10 frames/s', + 2 => '15 frames/s', + 3 => '20 frames/s', + }, + }, + 0x0225 => { + Name => 'FlashMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Force', + 2 => 'Disabled', + 3 => 'Red eye', + }, + }, + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + Writable => 0, + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, + 0x0f00 => { + Name => 'DataDump', + Writable => 0, + Binary => 1, + }, +); + +# face detection information (ref PH) +%Image::ExifTool::Sanyo::FaceInfo = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + WRITABLE => 1, + FORMAT => 'int32u', + FIRST_ENTRY => 0, + 0 => 'FacesDetected', + 4 => { + Name => 'FacePosition', + Format => 'int32u[4]', + Notes => q{ + left, top, right and bottom coordinates of detected face in an unrotated + 640-pixel-wide image, with increasing Y downwards + }, + }, +); + +# tags in Sanyo MOV videos (PH - observations from an E6 sample) +# (similar information in Kodak,Minolta,Nikon,Olympus,Pentax and Sanyo videos) +%Image::ExifTool::Sanyo::MOV = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'This information is found in Sanyo MOV videos.', + 0x00 => { + Name => 'Make', + Format => 'string[24]', + }, + 0x18 => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[8]', + }, + # (01 00 at offset 0x20) + 0x26 => { + Name => 'ExposureTime', + Format => 'int32u', + ValueConv => '$val ? 10 / $val : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x2a => { + Name => 'FNumber', + Format => 'int32u', + ValueConv => '$val / 10', + PrintConv => 'sprintf("%.1f",$val)', + }, + 0x32 => { + Name => 'ExposureCompensation', + Format => 'int32s', + ValueConv => '$val / 10', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + 0x44 => { + Name => 'WhiteBalance', + Format => 'int16u', + PrintConv => { + 0 => 'Auto', + 1 => 'Daylight', + 2 => 'Shade', + 3 => 'Fluorescent', #2 + 4 => 'Tungsten', + 5 => 'Manual', + }, + }, + 0x48 => { + Name => 'FocalLength', + Format => 'int32u', + ValueConv => '$val / 10', + PrintConv => 'sprintf("%.1f mm",$val)', + }, +); + +# tags in Sanyo MP4 videos (PH - from C4, C5 and HD1A samples) +# --> very similar to Samsung MP4 information +# (there is still a lot more information here that could be decoded!) +%Image::ExifTool::Sanyo::MP4 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'This information is found in Sanyo MP4 videos.', + 0x00 => { + Name => 'Make', + Format => 'string[5]', + PrintConv => 'ucfirst(lc($val))', + }, + 0x18 => { + Name => 'Model', + Description => 'Camera Model Name', + Format => 'string[8]', + }, + # (01 00 at offset 0x28) + # (0x2e has values 0x31, 0x33 and 0x3c in my samples, but + # some of the shutter speeds should be around 1/500 or so) + 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', + }, + 0xd1 => { + Name => 'Software', + Notes => 'these tags are shifted up by 1 byte for some models like the HD1A', + Format => 'undef[32]', + RawConv => q{ + $val =~ /^SANYO/ or return undef; + $val =~ tr/\0//d; + $$self{SanyoSledder0xd1} = 1; + return $val; + }, + }, + 0xd2 => { + Name => 'Software', + Format => 'undef[32]', + RawConv => q{ + $val =~ /^SANYO/ or return undef; + $val =~ tr/\0//d; + $$self{SanyoSledder0xd2} = 1; + return $val; + }, + }, + 0xf1 => { + Name => 'Thumbnail', + Condition => '$$self{SanyoSledder0xd1}', + SubDirectory => { + TagTable => 'Image::ExifTool::Sanyo::Thumbnail', + Base => '$start', + }, + }, + 0xf2 => { + Name => 'Thumbnail', + Condition => '$$self{SanyoSledder0xd2}', + SubDirectory => { + TagTable => 'Image::ExifTool::Sanyo::Thumbnail', + Base => '$start', + }, + }, +); + +# thumbnail image information found in MP4 videos (similar in Olympus,Samsung,Sanyo) +%Image::ExifTool::Sanyo::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 }, +); + + +#------------------------------------------------------------------------------ +# Patch incorrect offsets in J1, J2, J4, S1, S3 and S4 maker notes +# Inputs: 0) valuePtr, 1) end of previous value, 2) value size, 3) tag ID, 4) write flag +sub FixOffsets($$$$;$) +{ + my ($valuePtr, $valEnd, $size, $tagID, $wFlag) = @_; + # ignore existing offsets and calculate reasonable values instead + if ($tagID == 0x100) { + # just ignore the SanyoThumbnail when writing (pointer is garbage) + $_[0] = undef if $wFlag; + } else { + $_[0] = $valEnd; # set value pointer to next logical location + ++$size if $size & 0x01; + $_[1] += $size; # update end-of-value pointer + } +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Sanyo - Sanyo 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 +Sanyo maker notes in EXIF information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.exif.org/makernotes/SanyoMakerNote.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Sanyo Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Scalado.pm b/ExifTool/lib/Image/ExifTool/Scalado.pm new file mode 100644 index 0000000..96230f3 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Scalado.pm @@ -0,0 +1,141 @@ +#------------------------------------------------------------------------------ +# File: Scalado.pm +# +# Description: Read APP4 SCALADO metadata +# +# Revisions: 2013-09-13 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::Scalado; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::PLIST; + +$VERSION = '1.01'; + +sub ProcessScalado($$$); + +# JPEG APP4 SCALADO tags +%Image::ExifTool::Scalado::Main = ( + GROUPS => { 0 => 'APP4', 1 => 'Scalado', 2 => 'Image' }, + PROCESS_PROC => \&ProcessScalado, + TAG_PREFIX => 'Scalado', + FORMAT => 'int32s', + NOTES => q{ + Tags extracted from the JPEG APP4 "SCALADO" segment found in images from + HTC, LG and Samsung phones. (Presumably written by Scalado mobile software, + L<http://www.scalado.com/>.) + }, + SPMO => { + Name => 'DataLength', + Unknown => 1, + }, + WDTH => { + Name => 'PreviewImageWidth', + ValueConv => '$val ? abs($val) : undef', + }, + HGHT => { + Name => 'PreviewImageHeight', + ValueConv => '$val ? abs($val) : undef', + }, + QUAL => { + Name => 'PreviewQuality', + ValueConv => '$val ? abs($val) : undef', + }, + # tags not yet decoded with observed values: + # CHKH: 0, various negative values + # CHKL: various negative values + # CLEN: -1024 + # CSPC: -2232593 + # DATA: (+ve data length) + # HDEC: 0 + # MAIN: 0, 60 + # META: 24 + # SCI0: (+ve data length) often 36 + # SCI1: (+ve data length) 36 + # SCX0: (+ve data length) + # SCX1: (+ve data length) often 84 + # WDEC: 0 + # VERS: -131328 +); + +#------------------------------------------------------------------------------ +# Extract information from the JPEG APP4 SCALADO segment +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessScalado($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = 0; + my $end = length $$dataPt; + my $unknown = $et->Options('Unknown'); + + $et->VerboseDir('APP4 SCALADO', undef, $end); + SetByteOrder('MM'); + + for (;;) { + last if $pos + 12 > $end; + my $tag = substr($$dataPt, $pos, 4); + my $ver = Get32u($dataPt, $pos + 4); # (looks like a version for some tags) + if (not $$tagTablePtr{$tag} and $unknown) { + my $name = $tag; + $name =~ tr/-A-Za-z0-9_//dc; + last unless length $name; # stop if tag is garbage + AddTagToTable($tagTablePtr, $tag, { + Name => "Scalado_$name", + Description => "Scalado $name", + Unknown => 1, + }); + } + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => $dataPt, + Start => $pos + 8, + Size => 4, + Extra => ", ver $ver", + ); + if ($tag eq 'SPMO') { + my $val = Get32u($dataPt, $pos + 8) ; + if ($ver < 5) { # (I don't have samples for version 3 or 4, so I'm not sure about these) + $end -= $val; # SPMO gives trailer data length + } else { + $end = $val + 12; # SPMO gives length of Scalado directory (excepting this entry) + } + } + $pos += 12; + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Scalado - Read APP4 SCALADO metadata + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +metadata from the JPEG APP4 SCALADO segment. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Scalado Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Shift.pl b/ExifTool/lib/Image/ExifTool/Shift.pl new file mode 100644 index 0000000..badb99f --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Shift.pl @@ -0,0 +1,644 @@ +#------------------------------------------------------------------------------ +# File: Shift.pl +# +# Description: ExifTool time shifting routines +# +# Revisions: 10/28/2005 - P. Harvey Created +# 03/13/2019 - PH Added single-argument form of ShiftTime() +#------------------------------------------------------------------------------ + +package Image::ExifTool; + +use strict; + +sub ShiftTime($;$$$); + +#------------------------------------------------------------------------------ +# apply shift to value in new value hash +# Inputs: 0) ExifTool ref, 1) shift type, 2) shift string, 3) raw date/time value, +# 4) new value hash ref +# Returns: error string or undef on success and updates value in new value hash +sub ApplyShift($$$$;$) +{ + my ($self, $func, $shift, $val, $nvHash) = @_; + + # get shift direction from first character in shift string + my $pre = ($shift =~ s/^(\+|-)//) ? $1 : '+'; + my $dir = ($pre eq '+') ? 1 : -1; + my $tagInfo = $$nvHash{TagInfo}; + my $tag = $$tagInfo{Name}; + my $shiftOffset; + if ($$nvHash{ShiftOffset}) { + $shiftOffset = $$nvHash{ShiftOffset}; + } else { + $shiftOffset = $$nvHash{ShiftOffset} = { }; + } + + # initialize handler for eval warnings + local $SIG{'__WARN__'} = \&SetWarning; + SetWarning(undef); + + # shift is applied to ValueConv value, so we must ValueConv-Shift-ValueConvInv + my ($type, $err); + foreach $type ('ValueConv','Shift','ValueConvInv') { + if ($type eq 'Shift') { + #### eval ShiftXxx function + $err = eval "Shift$func(\$val, \$shift, \$dir, \$shiftOffset)"; + } elsif ($$tagInfo{$type}) { + my $conv = $$tagInfo{$type}; + if (ref $conv eq 'CODE') { + $val = &$conv($val, $self); + } else { + return "Can't handle $type for $tag in ApplyShift()" if ref $$tagInfo{$type}; + #### eval ValueConv/ValueConvInv ($val, $self) + $val = eval $$tagInfo{$type}; + } + } else { + next; + } + # handle errors + $err and return $err; + $@ and SetWarning($@); + GetWarning() and return CleanWarning(); + } + # update value in new value hash + $nvHash->{Value} = [ $val ]; + return undef; # success +} + +#------------------------------------------------------------------------------ +# Check date/time shift +# Inputs: 0) shift type, 1) shift string (without sign) +# Returns: updated shift string, or undef on error (and may update shift) +sub CheckShift($$) +{ + my ($type, $shift) = @_; + my $err; + if ($type eq 'Time') { + return "No shift direction" unless $shift =~ s/^(\+|-)//; + # do a test shift to validate the shift string + my $testTime = '2005:11:02 09:00:13.25-04:00'; + $err = ShiftTime($testTime, $shift, $1 eq '+' ? 1 : -1); + } else { + $err = "Unknown shift type ($type)"; + } + return $err; +} + +#------------------------------------------------------------------------------ +# return the number of days in a month +# Inputs: 0) month number (Jan=1, may be outside range), 1) year +# Returns: number of days in month +sub DaysInMonth($$) +{ + my ($mon, $year) = @_; + my @days = (31,28,31,30,31,30,31,31,30,31,30,31); + # adjust to the range [0,11] + while ($mon < 1) { $mon += 12; --$year; } + while ($mon > 12) { $mon -= 12; ++$year; } + # return standard number of days unless february on a leap year + return $days[$mon-1] unless $mon == 2 and not $year % 4; + # leap years don't occur on even centuries except every 400 years + return 29 if $year % 100 or not $year % 400; + return 28; +} + +#------------------------------------------------------------------------------ +# split times into corresponding components: YYYY mm dd HH MM SS tzh tzm +# Inputs: 0) date/time or shift string 1) reference to list for returned components +# 2) optional reference to list of time components (if shift string) +# Returns: true on success +# Returned components are 0-Y, 1-M, 2-D, 3-hr, 4-min, 5-sec, 6-tzhr, 7-tzmin +sub SplitTime($$;$) +{ + my ($val, $vals, $time) = @_; + # insert zeros if missing in shift string + if ($time) { + $val =~ s/(^|[-+:\s]):/${1}0:/g; + $val =~ s/:([:\s]|$)/:0$1/g; + } + # change dashes to colons in date (for XMP dates) + if ($val =~ s/^(\d{4})-(\d{2})-(\d{2})/$1:$2:$3/) { + $val =~ tr/T/ /; # change 'T' separator to ' ' + } + # add space before timezone to split it into a separate word + $val =~ s/(\+|-)/ $1/; + my @words = split ' ', $val; + my $err = 1; + my @v; + for (;;) { + my $word = shift @words; + last unless defined $word; + # split word into separate numbers (allow decimal points but no signs) + my @vals = $word =~ /(?=\d|\.\d)\d*(?:\.\d*)?/g or last; + if ($word =~ /^(\+|-)/) { + # this is the timezone + (defined $v[6] or @vals > 2) and $err = 1, last; + my $sign = ($1 ne '-') ? 1 : -1; + # apply sign to both minutes and seconds + $v[6] = $sign * shift(@vals); + $v[7] = $sign * (shift(@vals) || 0); + } elsif ((@words and $words[0] =~ /^\d+/) or # there is a time word to follow + (not $time and $vals[0] =~ /^\d{3}/) or # first value is year (3 or more digits) + ($time and not defined $$time[3] and not defined $v[0])) # we don't have a time + { + # this is a date (must come first) + (@v or @vals > 3) and $err = 1, last; + not $time and @vals != 3 and $err = 1, last; + $v[2] = pop(@vals); # take day first if only one specified + $v[1] = pop(@vals) || 0; + $v[0] = pop(@vals) || 0; + } else { + # this is a time (can't come after timezone) + (defined $v[3] or defined $v[6] or @vals > 3) and $err = 1, last; + not $time and @vals != 3 and @vals != 2 and $err = 1, last; + $v[3] = shift(@vals); # take hour first if only one specified + $v[4] = shift(@vals) || 0; + $v[5] = shift(@vals) || 0; + } + $err = 0; + } + return 0 if $err or not @v; + if ($time) { + # zero any required shift entries which aren't yet defined + $v[0] = $v[1] = $v[2] = 0 if defined $$time[0] and not defined $v[0]; + $v[3] = $v[4] = $v[5] = 0 if defined $$time[3] and not defined $v[3]; + $v[6] = $v[7] = 0 if defined $$time[6] and not defined $v[6]; + } + @$vals = @v; # return split time components + return 1; +} + +#------------------------------------------------------------------------------ +# shift date/time by components +# Inputs: 0) split date/time list ref, 1) split shift list ref, +# 2) shift direction, 3) reference to output list of shifted components +# 4) number of decimal points in seconds +# 5) reference to return time difference due to rounding +# Returns: error string or undef on success +sub ShiftComponents($$$$$;$) +{ + my ($time, $shift, $dir, $toTime, $dec, $rndPt) = @_; + # min/max for Y, M, D, h, m, s + my @min = ( 0, 1, 1, 0, 0, 0); + my @max = (10000,12,28,24,60,60); + my $i; +# +# apply the shift +# + my $c = 0; + for ($i=0; $i<@$time; ++$i) { + my $v = ($$time[$i] || 0) + $dir * ($$shift[$i] || 0) + $c; + # handle fractional values by propagating remainders downwards + if ($v != int($v) and $i < 5) { + my $iv = int($v); + $c = ($v - $iv) * $max[$i+1]; + $v = $iv; + } else { + $c = 0; + } + $$toTime[$i] = $v; + } + # round off seconds to the required number of decimal points + my $sec = $$toTime[5]; + if (defined $sec and $sec != int($sec)) { + my $mult = 10 ** $dec; + my $rndSec = int($sec * $mult + 0.5 * ($sec <=> 0)) / $mult; + $rndPt and $$rndPt = $sec - $rndSec; + $$toTime[5] = $rndSec; + } +# +# handle overflows, starting with least significant number first (seconds) +# + $c = 0; + for ($i=5; $i>=0; $i--) { + defined $$time[$i] or $c = 0, next; + # apply shift and adjust for previous overflow + my $v = $$toTime[$i] + $c; + $c = 0; # set carry to zero + # adjust for over/underflow + my ($min, $max) = ($min[$i], $max[$i]); + if ($v < $min) { + if ($i == 2) { # 2 = day of month + do { + # add number of days in previous month + --$c; + my $mon = $$toTime[$i-1] + $c; + $v += DaysInMonth($mon, $$toTime[$i-2]); + } while ($v < 1); + } else { + my $fc = ($v - $min) / $max; + # carry ($c) must be largest integer equal to or less than $fc + $c = int($fc); + --$c if $c > $fc; + $v -= $c * $max; + } + } elsif ($v >= $max + $min) { + if ($i == 2) { + for (;;) { + # test against number of days in current month + my $mon = $$toTime[$i-1] + $c; + my $days = DaysInMonth($mon, $$toTime[$i-2]); + last if $v <= $days; + $v -= $days; + ++$c; + last if $v <= 28; + } + } else { + my $fc = ($v - $max - $min) / $max; + # carry ($c) must be smallest integer greater than $fc + $c = int($fc); + ++$c if $c <= $fc; + $v -= $c * $max; + } + } + $$toTime[$i] = $v; # save the new value + } + # handle overflows in timezone + if (defined $$toTime[6]) { + my $m = $$toTime[6] * 60 + $$toTime[7]; + $m += 0.5 * ($m <=> 0); # avoid round-off errors + $$toTime[6] = int($m / 60); + $$toTime[7] = int($m - $$toTime[6] * 60); + } + return undef; # success +} + +#------------------------------------------------------------------------------ +# Shift an integer or floating-point number +# Inputs: 0) date/time string, 1) shift string, 2) shift direction (+1 or -1) +# 3) (unused) +# Returns: undef and updates input value +sub ShiftNumber($$$;$) +{ + my ($val, $shift, $dir) = @_; + $_[0] = $val + $shift * $dir; # return shifted value + return undef; # success! +} + +#------------------------------------------------------------------------------ +# Shift date/time string +# Inputs: 0) date/time string, 1) shift string, 2) shift direction (+1 or -1), +# or 0 or undef to take shift direction from sign of shift, +# 3) reference to ShiftOffset hash (with Date, DateTime, Time, Timezone keys) +# or 0) shift string (and operates on $_) +# Returns: error string or undef on success and date/time string is updated +sub ShiftTime($;$$$) +{ + my ($val, $shift, $dir, $shiftOffset); + my (@time, @shift, @toTime, $mode, $needShiftOffset, $dec); + + if (@_ == 1) { # single argument form of ShiftTime()? + $val = $_; + $shift = $_[0]; + } else { + ($val, $shift, $dir, $shiftOffset) = @_; + } + $dir or $dir = ($shift =~ s/^(\+|-)// and $1 eq '-') ? -1 : 1; +# +# figure out what we are dealing with (time, date or date/time) +# + SplitTime($val, \@time) or return "Invalid time string ($val)"; + if (defined $time[0]) { + return "Can't shift from year 0000" if $time[0] eq '0000'; + $mode = defined $time[3] ? 'DateTime' : 'Date'; + } elsif (defined $time[3]) { + $mode = 'Time'; + } else { + $mode = ''; + } + # get number of digits after the seconds decimal point + if (defined $time[5] and $time[5] =~ /\.(\d+)/) { + $dec = length($1); + } else { + $dec = 0; + } + if ($shiftOffset) { + $needShiftOffset = 1 unless defined $$shiftOffset{$mode}; + $needShiftOffset = 1 if defined $time[6] and not defined $$shiftOffset{Timezone}; + } else { + $needShiftOffset = 1; + } + if ($needShiftOffset) { +# +# apply date/time shift the hard way +# + SplitTime($shift, \@shift, \@time) or return "Invalid shift string ($shift)"; + + # change 'Z' timezone to '+00:00' only if necessary + if (@shift > 6 and @time <= 6) { + $time[6] = $time[7] = 0 if $val =~ s/Z$/\+00:00/; + } + my $rndDiff; + my $err = ShiftComponents(\@time, \@shift, $dir, \@toTime, $dec, \$rndDiff); + $err and return $err; +# +# calculate and save the shift offsets for next time +# + if ($shiftOffset) { + if (defined $time[0] or defined $time[3]) { + my @tm1 = (0, 0, 0, 1, 0, 2000); + my @tm2 = (0, 0, 0, 1, 0, 2000); + if (defined $time[0]) { + @tm1[3..5] = reverse @time[0..2]; + @tm2[3..5] = reverse @toTime[0..2]; + --$tm1[4]; # month should start from 0 + --$tm2[4]; + } + my $diff = 0; + if (defined $time[3]) { + @tm1[0..2] = reverse @time[3..5]; + @tm2[0..2] = reverse @toTime[3..5]; + # handle fractional seconds separately + $diff = $tm2[0] - int($tm2[0]) - ($tm1[0] - int($tm1[0])); + $diff += $rndDiff if defined $rndDiff; # un-do rounding + $tm1[0] = int($tm1[0]); + $tm2[0] = int($tm2[0]); + } + eval q{ + require Time::Local; + $diff += Time::Local::timegm(@tm2) - Time::Local::timegm(@tm1); + }; + # not a problem if we failed here since we'll just try again next time, + # so don't return error message + unless (@$) { + my $mode; + if (defined $time[0]) { + $mode = defined $time[3] ? 'DateTime' : 'Date'; + } else { + $mode = 'Time'; + } + $$shiftOffset{$mode} = $diff; + } + } + if (defined $time[6]) { + $$shiftOffset{Timezone} = ($toTime[6] - $time[6]) * 60 + + $toTime[7] - $time[7]; + } + } + + } else { +# +# apply shift from previously calculated offsets +# + if ($$shiftOffset{Timezone} and @time <= 6) { + # change 'Z' timezone to '+00:00' only if necessary + $time[6] = $time[7] = 0 if $val =~ s/Z$/\+00:00/; + } + # apply the previous date/time shift if necessary + if ($mode) { + my @tm = (0, 0, 0, 1, 0, 2000); + if (defined $time[0]) { + @tm[3..5] = reverse @time[0..2]; + --$tm[4]; # month should start from 0 + } + @tm[0..2] = reverse @time[3..5] if defined $time[3]; + # save fractional seconds + my $frac = $tm[0] - int($tm[0]); + $tm[0] = int($tm[0]); + my $tm; + eval q{ + require Time::Local; + $tm = Time::Local::timegm(@tm) + $frac; + }; + $@ and return CleanWarning($@); + $tm += $$shiftOffset{$mode}; # apply the shift + $tm < 0 and return 'Shift results in negative time'; + # save fractional seconds in shifted time + $frac = $tm - int($tm); + if ($frac) { + $tm = int($tm); + # must account for any rounding that could occur + $frac + 0.5 * 10 ** (-$dec) >= 1 and ++$tm, $frac = 0; + } + @tm = gmtime($tm); + @toTime = reverse @tm[0..5]; + $toTime[0] += 1900; + ++$toTime[1]; + $toTime[5] += $frac; # add the fractional seconds back in + } + # apply the previous timezone shift if necessary + if (defined $time[6]) { + my $m = $time[6] * 60 + $time[7]; + $m += $$shiftOffset{Timezone}; + $m += 0.5 * ($m <=> 0); # avoid round-off errors + $toTime[6] = int($m / 60); + $toTime[7] = int($m - $toTime[6] * 60); + } + } +# +# insert shifted time components back into original string +# + my $i; + for ($i=0; $i<@toTime; ++$i) { + next unless defined $time[$i] and defined $toTime[$i]; + my ($v, $d, $s); + if ($i != 6) { # not timezone hours + last unless $val =~ /((?=\d|\.\d)\d*(\.\d*)?)/g; + next if $toTime[$i] == $time[$i]; + $v = $1; # value + $d = $2; # decimal part of value + $s = ''; # no sign + } else { + last if $time[$i] == $toTime[$i] and $time[$i+1] == $toTime[$i+1]; + last unless $val =~ /((?:\+|-)(?=\d|\.\d)\d*(\.\d*)?)/g; + $v = $1; + $d = $2; + if ($toTime[6] >= 0 and $toTime[7] >= 0) { + $s = '+'; + } else { + $s = '-'; + $toTime[6] = -$toTime[6]; + $toTime[7] = -$toTime[7]; + } + } + my $nv = $toTime[$i]; + my $pos = pos $val; + my $len = length $v; + my $sig = $len - length $s; + my $dec = $d ? length($d) - 1 : 0; + my $newNum = sprintf($dec ? "$s%0$sig.${dec}f" : "$s%0${sig}d", $nv); + substr($val, $pos - $len, $len) = $newNum; + pos($val) = $pos + length($newNum) - $len; + } + if (@_ == 1) { + $_ = $val; # set $_ to the returned value + } else { + $_[0] = $val; # return shifted value + } + return undef; # success! +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Shift.pl - ExifTool time shifting routines + +=head1 DESCRIPTION + +This module contains routines used by ExifTool to shift date and time +values. + +=head1 METHODS + +=head2 ShiftTime + +Shift date/time value + + use Image::ExifTool; + $err = Image::ExifTool::ShiftTime($dateTime, $shift); + +=over 4 + +=item Inputs: + +0) Date/time string in EXIF format (eg. C<2016:01:30 11:45:00>). + +1) Shift string (see below) with optional leading sign for shift direction. + +2) [optional] Direction of shift (-1 or +1), or 0 or undef to use the sign +from the shift string. + +3) [optional] Reference to time-shift hash -- filled in by first call to +B<ShiftTime>, and used in subsequent calls to shift date/time values by the +same relative amount (see L</TRICKY> section below). + +or + +0) Shift string (and $_ contains the input date/time string). + +=item Return value: + +Error string, or undef on success and the input date/time string is shifted +by the specified amount. + +=back + +=head1 SHIFT STRING + +Time shifts are applied to standard EXIF-formatted date/time values (eg. +C<2005:03:14 18:55:00>). Date-only and time-only values may also be +shifted, and an optional timezone (eg. C<-05:00>) is also supported. Here +are some general rules and examples to explain how shift strings are +interpreted: + +Date-only values are shifted using the following formats: + + 'Y:M:D' - shift date by 'Y' years, 'M' months and 'D' days + 'M:D' - shift months and days only + 'D' - shift specified number of days + +Time-only values are shifted using the following formats: + + 'h:m:s' - shift time by 'h' hours, 'm' minutes and 's' seconds + 'h:m' - shift hours and minutes only + 'h' - shift specified number of hours + +Timezone shifts are specified in the following formats: + + '+h:m' - shift timezone by 'h' hours and 'm' minutes + '-h:m' - negative shift of timezone hours and minutes + '+h' - shift timezone hours only + '-h' - negative shift of timezone hours only + +A valid shift value consists of one or two arguments, separated by a space. +If only one is provided, it is assumed to be a time shift when applied to a +time-only or a date/time value, or a date shift when applied to a date-only +value. For example: + + '1' - shift by 1 hour if applied to a time or date/time + value, or by one day if applied to a date value + '2:0' - shift 2 hours (time, date/time), or 2 months (date) + '5:0:0' - shift 5 hours (time, date/time), or 5 years (date) + '0:0:1' - shift 1 s (time, date/time), or 1 day (date) + +If two arguments are given, the date shift is first, followed by the time +shift: + + '3:0:0 0' - shift date by 3 years + '0 15:30' - shift time by 15 hours and 30 minutes + '1:0:0 0:0:0+5:0' - shift date by 1 year and timezone by 5 hours + +A date shift is simply ignored if applied to a time value or visa versa. + +Numbers specified in shift fields may contain a decimal point: + + '1.5' - 1 hour 30 minutes (time, date/time), or 1 day (date) + '2.5 0' - 2 days 12 hours (date/time), 12 hours (time) or + 2 days (date) + +And to save typing, a zero is assumed for any missing numbers: + + '1::' - shift by 1 hour (time, date/time) or 1 year (date) + '26:: 0' - shift date by 26 years + '+:30' - shift timezone by 30 minutes + +Below are some specific examples applied to real date and/or time values +('Dir' is the applied shift direction: '+' is positive, '-' is negative): + + Original Value Shift Dir Shifted Value + --------------------- ------- --- --------------------- + '20:30:00' '5' + '01:30:00' + '2005:01:27' '5' + '2005:02:01' + '2005:01:27 20:30:00' '5' + '2005:01:28 01:30:00' + '11:54:00' '2.5 0' - '23:54:00' + '2005:11:02' '2.5 0' - '2005:10:31' + '2005:11:02 11:54:00' '2.5 0' - '2005:10:30 23:54:00' + '2004:02:28 08:00:00' '1 1.3' + '2004:02:29 09:18:00' + '07:00:00' '-5' + '07:00:00' + '07:00:00+01:00' '-5' + '07:00:00-04:00' + '07:00:00Z' '+2:30' - '07:00:00-02:30' + '1970:01:01' '35::' + '2005:01:01' + '2005:01:01' '400' + '2006:02:05' + '10:00:00.00' '::1.33' - '09:59:58.67' + +=head1 NOTES + +The format of the original date/time value is not changed when the time +shift is applied. This means that the length of the date/time string will +not change, and only the numbers in the string will be modified. The only +exception to this rule is that a 'Z' timezone is changed to '+00:00' +notation if a timezone shift is applied. A timezone will not be added to +the date/time string. + +=head1 TRICKY + +This module is perhaps more complicated than it needs to be because it is +designed to be very flexible in the way time shifts are specified and +applied... + +The ability to shift dates by Y years, M months, etc, conflicts with the +design goal of maintaining a constant shift for all time values when +applying a batch shift. This is because shifting by 1 month can be +equivalent to anything from 28 to 31 days, and 1 year can be 365 or 366 +days, depending on the starting date. + +The inconsistency is handled by shifting the first tag found with the actual +specified shift, then calculating the equivalent time difference in seconds +for this shift and applying this difference to subsequent tags in a batch +conversion. So if it works as designed, the behaviour should be both +intuitive and mathematically correct, and the user shouldn't have to worry +about details such as this (in keeping with Perl's "do the right thing" +philosophy). + +=head1 BUGS + +Due to the use of the standard time library functions, dates are typically +limited to the range 1970 to 2038 on 32-bit systems. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Shortcuts.pm b/ExifTool/lib/Image/ExifTool/Shortcuts.pm new file mode 100644 index 0000000..34e09ac --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Shortcuts.pm @@ -0,0 +1,356 @@ +#------------------------------------------------------------------------------ +# File: Shortcuts.pm +# +# Description: ExifTool shortcut tags +# +# Revisions: 02/07/2004 - PH Moved out of Exif.pm +# 09/15/2004 - PH Added D70Boring from Greg Troxel +# 01/11/2005 - PH Added Canon20D from Christian Koller +# 03/03/2005 - PH Added user defined shortcuts +# 03/26/2005 - PH Added Nikon from Tom Christiansen +# 02/28/2007 - PH Removed model-dependent shortcuts +# --> this is what UserDefined::Shortcuts is for +# 02/25/2009 - PH Added Unsafe +# 07/03/2010 - PH Added CommonIFD0 +#------------------------------------------------------------------------------ + +package Image::ExifTool::Shortcuts; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.68'; + +# this is a special table used to define command-line shortcuts +# (documentation Notes may be added for these via %shortcutNotes in BuildTagLookup.pm) +%Image::ExifTool::Shortcuts::Main = ( + # this shortcut allows the three common date/time tags to be shifted at once + AllDates => [ + 'DateTimeOriginal', + 'CreateDate', + 'ModifyDate', + ], + # This is a shortcut to some common information which is useful in most images + Common => [ + 'FileName', + 'FileSize', + 'Model', + 'DateTimeOriginal', + 'ImageSize', + 'Quality', + 'FocalLength', + 'ShutterSpeed', + 'Aperture', + 'ISO', + 'WhiteBalance', + 'Flash', + ], + # This shortcut provides the same information as the Canon utilities + Canon => [ + 'FileName', + 'Model', + 'DateTimeOriginal', + 'ShootingMode', + 'ShutterSpeed', + 'Aperture', + 'MeteringMode', + 'ExposureCompensation', + 'ISO', + 'Lens', + 'FocalLength', + 'ImageSize', + 'Quality', + 'Flash', + 'FlashType', + 'ConditionalFEC', + 'RedEyeReduction', + 'ShutterCurtainHack', + 'WhiteBalance', + 'FocusMode', + 'Contrast', + 'Sharpness', + 'Saturation', + 'ColorTone', + 'ColorSpace', + 'LongExposureNoiseReduction', + 'FileSize', + 'FileNumber', + 'DriveMode', + 'OwnerName', + 'SerialNumber', + ], + Nikon => [ + 'Model', + 'SubSecDateTimeOriginal', + 'ShutterCount', + 'LensSpec', + 'FocalLength', + 'ImageSize', + 'ShutterSpeed', + 'Aperture', + 'ISO', + 'NoiseReduction', + 'ExposureProgram', + 'ExposureCompensation', + 'WhiteBalance', + 'WhiteBalanceFineTune', + 'ShootingMode', + 'Quality', + 'MeteringMode', + 'FocusMode', + 'ImageOptimization', + 'ToneComp', + 'ColorHue', + 'ColorSpace', + 'HueAdjustment', + 'Saturation', + 'Sharpness', + 'Flash', + 'FlashMode', + 'FlashExposureComp', + ], + # This shortcut may be useful when copying tags between files to either + # copy the maker notes as a block or prevent it from being copied + MakerNotes => [ + 'MakerNotes', # (for RIFF MakerNotes) + 'MakerNoteApple', + 'MakerNoteCanon', + 'MakerNoteCasio', + 'MakerNoteCasio2', + 'MakerNoteDJI', + 'MakerNoteDJIInfo', + 'MakerNoteFLIR', + 'MakerNoteFujiFilm', + 'MakerNoteGE', + 'MakerNoteGE2', + 'MakerNoteHasselblad', + 'MakerNoteHP', + 'MakerNoteHP2', + 'MakerNoteHP4', + 'MakerNoteHP6', + 'MakerNoteISL', + 'MakerNoteJVC', + 'MakerNoteJVCText', + 'MakerNoteKodak1a', + 'MakerNoteKodak1b', + 'MakerNoteKodak2', + 'MakerNoteKodak3', + 'MakerNoteKodak4', + 'MakerNoteKodak5', + 'MakerNoteKodak6a', + 'MakerNoteKodak6b', + 'MakerNoteKodak7', + 'MakerNoteKodak8a', + 'MakerNoteKodak8b', + 'MakerNoteKodak8c', + 'MakerNoteKodak9', + 'MakerNoteKodak10', + 'MakerNoteKodak11', + 'MakerNoteKodak12', + 'MakerNoteKodakUnknown', + 'MakerNoteKyocera', + 'MakerNoteMinolta', + 'MakerNoteMinolta2', + 'MakerNoteMinolta3', + 'MakerNoteMotorola', + 'MakerNoteNikon', + 'MakerNoteNikon2', + 'MakerNoteNikon3', + 'MakerNoteNintendo', + 'MakerNoteOlympus', + 'MakerNoteOlympus2', + 'MakerNoteOlympus3', + 'MakerNoteLeica', + 'MakerNoteLeica2', + 'MakerNoteLeica3', + 'MakerNoteLeica4', + 'MakerNoteLeica5', + 'MakerNoteLeica6', + 'MakerNoteLeica7', + 'MakerNoteLeica8', + 'MakerNoteLeica9', + 'MakerNoteLeica10', + 'MakerNotePanasonic', + 'MakerNotePanasonic2', + 'MakerNotePanasonic3', + 'MakerNotePentax', + 'MakerNotePentax2', + 'MakerNotePentax3', + 'MakerNotePentax4', + 'MakerNotePentax5', + 'MakerNotePentax6', + 'MakerNotePhaseOne', + 'MakerNoteReconyx', + 'MakerNoteReconyx2', + 'MakerNoteReconyx3', + 'MakerNoteRicoh', + 'MakerNoteRicoh2', + 'MakerNoteRicohPentax', + 'MakerNoteRicohText', + 'MakerNoteSamsung1a', + 'MakerNoteSamsung1b', + 'MakerNoteSamsung2', + 'MakerNoteSanyo', + 'MakerNoteSanyoC4', + 'MakerNoteSanyoPatch', + 'MakerNoteSigma', + 'MakerNoteSony', + 'MakerNoteSony2', + 'MakerNoteSony3', + 'MakerNoteSony4', + 'MakerNoteSony5', + 'MakerNoteSonyEricsson', + 'MakerNoteSonySRF', + 'MakerNoteUnknownText', + 'MakerNoteUnknownBinary', + 'MakerNoteUnknown', + ], + # "unsafe" tags we normally don't copy in JPEG images, defined + # as a shortcut to use when rebuilding JPEG EXIF from scratch + Unsafe => [ + 'IFD0:YCbCrPositioning', + 'IFD0:YCbCrCoefficients', + 'IFD0:TransferFunction', + 'ExifIFD:ComponentsConfiguration', + 'ExifIFD:CompressedBitsPerPixel', + 'InteropIFD:InteropIndex', + 'InteropIFD:InteropVersion', + 'InteropIFD:RelatedImageWidth', + 'InteropIFD:RelatedImageHeight', + ], + # standard tags used to define the color space of an image + # (useful to preserve color space when deleting all meta information) + ColorSpaceTags => [ + 'ExifIFD:ColorSpace', + 'ExifIFD:Gamma', + 'InteropIFD:InteropIndex', + 'ICC_Profile', + ], + # common metadata tags found in IFD0 of TIFF images + CommonIFD0 => [ + # standard EXIF + 'IFD0:ImageDescription', + 'IFD0:Make', + 'IFD0:Model', + 'IFD0:Software', + 'IFD0:ModifyDate', + 'IFD0:Artist', + 'IFD0:Copyright', + # other TIFF tags + 'IFD0:Rating', + 'IFD0:RatingPercent', + 'IFD0:DNGLensInfo', + 'IFD0:PanasonicTitle', + 'IFD0:PanasonicTitle2', + 'IFD0:XPTitle', + 'IFD0:XPComment', + 'IFD0:XPAuthor', + 'IFD0:XPKeywords', + 'IFD0:XPSubject', + ], + # large binary data tags which won't be loaded if excluded when extracting + LargeTags => [ + 'CanonVRD', + 'DLOData', + 'EXIF', + 'ICC_Profile', + 'IDCPreviewImage', + 'ImageData', + 'IPTC', + 'JpgFromRaw', + 'OriginalRawImage', + 'OtherImage', + 'PreviewImage', + 'ThumbnailImage', + 'TIFFPreview', + 'XML', + 'XMP', + 'ZoomedPreviewImage', + ], + 'ls-l' => [ + 'FilePermissions', + 'FileHardLinks', + 'FileUserID', + 'FileGroupID', + 'FileSize#', + 'FileModifyDate', + 'FileName', + ], + ImageDataMD5 => [ 'ImageDataHash' ], # (for backward compatibilty) +); + +#------------------------------------------------------------------------------ +# load user-defined shortcuts if available +# Inputs: reference to user-defined shortcut hash +sub LoadShortcuts($) +{ + my $shortcuts = shift; + my $shortcut; + foreach $shortcut (keys %$shortcuts) { + my $val = $$shortcuts{$shortcut}; + # also allow simple aliases + $val = [ $val ] unless ref $val eq 'ARRAY'; + # save the user-defined shortcut or alias + $Image::ExifTool::Shortcuts::Main{$shortcut} = $val; + } +} +# (for backward compatibility, renamed in ExifTool 7.75) +if (%Image::ExifTool::Shortcuts::UserDefined) { + LoadShortcuts(\%Image::ExifTool::Shortcuts::UserDefined); +} +if (%Image::ExifTool::UserDefined::Shortcuts) { + LoadShortcuts(\%Image::ExifTool::UserDefined::Shortcuts); +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Shortcuts - ExifTool shortcut tags + +=head1 SYNOPSIS + +This module is required by Image::ExifTool. + +=head1 DESCRIPTION + +This module contains definitions for tag name shortcuts used by +Image::ExifTool. You can customize this file to add your own shortcuts. + +Individual users may also add their own shortcuts to the .ExifTool_config +file in their home directory (or the directory specified by the +EXIFTOOL_HOME environment variable). The shortcuts are defined in a hash +called %Image::ExifTool::UserDefined::Shortcuts. The keys of the hash are +the shortcut names, and the elements are either tag names or references to +lists of tag names. + +An example shortcut definition in .ExifTool_config: + + %Image::ExifTool::UserDefined::Shortcuts = ( + MyShortcut => ['createdate','exif:exposuretime','aperture'], + MyAlias => 'FocalLengthIn35mmFormat', + ); + +In this example, MyShortcut is a shortcut for the CreateDate, +EXIF:ExposureTime and Aperture tags, and MyAlias is a shortcut for +FocalLengthIn35mmFormat. + +The target tag names may contain an optional group name prefix. A group +name applied to the shortcut will be ignored for any target tag with a group +name prefix. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Sigma.pm b/ExifTool/lib/Image/ExifTool/Sigma.pm new file mode 100644 index 0000000..6ca2434 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Sigma.pm @@ -0,0 +1,885 @@ +#------------------------------------------------------------------------------ +# File: Sigma.pm +# +# Description: Sigma/Foveon EXIF maker notes tags +# +# Revisions: 04/06/2004 - P. Harvey Created +# 02/20/2007 - PH added SD14 tags +# 24/06/2010 - PH decode some SD15 tags +# +# References: 1) http://www.x3f.info/technotes/FileDocs/MakerNoteDoc.html +# IB) Iliah Borg private communication (LibRaw) +# NJ) Niels Kristian Bech Jensen +# JR) Jos Roost +#------------------------------------------------------------------------------ + +package Image::ExifTool::Sigma; + +use strict; +use vars qw($VERSION %sigmaLensTypes); +use Image::ExifTool::Exif; + +$VERSION = '1.34'; + +# sigma LensType lookup (ref IB) +%sigmaLensTypes = ( + Notes => q{ + Sigma LensType values are hexadecimal numbers stored as a string (without + the leading "0x"). + }, + # 0x0 => 'Sigma 50mm F2.8 EX Macro', (0x0 used for other lenses too) + # 0x8 - 18-125mm LENSARANGE@18mm=22-4 + 0x10, 'Sigma 50mm F2.8 EX DG MACRO', + # (0x10 = 16) + 16.1 => 'Sigma 70mm F2.8 EX DG Macro', + 16.2 => 'Sigma 105mm F2.8 EX DG Macro', + 0x16 => 'Sigma 18-50mm F3.5-5.6 DC', #PH + 0x103 => 'Sigma 180mm F3.5 EX IF HSM APO Macro', + 0x104 => 'Sigma 150mm F2.8 EX DG HSM APO Macro', + 0x105 => 'Sigma 180mm F3.5 EX DG HSM APO Macro', + 0x106 => 'Sigma 150mm F2.8 EX DG OS HSM APO Macro', + 0x107 => 'Sigma 180mm F2.8 EX DG OS HSM APO Macro', + # (0x129 = 297) + 0x129 => 'Sigma Lens (0x129)', #PH + 297.1 => 'Sigma 14mm F2.8 EX Aspherical', #PH + 297.2 => 'Sigma 30mm F1.4', + # (0x131 = 305) + 0x131 => 'Sigma Lens (0x131)', + 305.1 => 'Sigma 17-70mm F2.8-4.5 DC Macro', #PH + 305.2 => 'Sigma 70-200mm F2.8 APO EX HSM', + 305.3 => 'Sigma 120-300mm F2.8 APO EX IF HSM', + 0x134 => 'Sigma 100-300mm F4 EX DG HSM APO', + 0x135 => 'Sigma 120-300mm F2.8 EX DG HSM APO', + 0x136 => 'Sigma 120-300mm F2.8 EX DG OS HSM APO', + 0x137 => 'Sigma 120-300mm F2.8 DG OS HSM | S', + 0x143 => 'Sigma 600mm F8 Mirror', + # (0x145 = 325) + 0x145 => 'Sigma Lens (0x145)', #PH + 325.1 => 'Sigma 15-30mm F3.5-4.5 EX DG Aspherical', #PH + 325.2 => 'Sigma 18-50mm F2.8 EX DG', #PH (NC) + 325.3 => 'Sigma 20-40mm F2.8 EX DG', #PH + 0x150 => 'Sigma 30mm F1.4 DC HSM', + # (0x152 = 338) + 0x152 => 'Sigma Lens (0x152)', + 338.1 => 'Sigma APO 800mm F5.6 EX DG HSM', + 338.2 => 'Sigma 12-24mm F4.5-5.6 EX DG ASP HSM', + 338.3 => 'Sigma 10-20mm F4-5.6 EX DC HSM', + 0x165 => 'Sigma 70-200mm F2.8 EX', # ...but what specific model?: + # 70-200mm F2.8 EX APO - Original version, minimum focus distance 1.8m (1999) + # 70-200mm F2.8 EX DG - Adds 'digitally optimized' lens coatings to reduce flare (2005) + # 70-200mm F2.8 EX DG Macro (HSM) - Minimum focus distance reduced to 1m (2006) + # 70-200mm F2.8 EX DG Macro HSM II - Improved optical performance (2007) + 0x169 => 'Sigma 18-50mm F2.8 EX DC', #PH (NC) + 0x183 => 'Sigma 500mm F4.5 EX HSM APO', + 0x184 => 'Sigma 500mm F4.5 EX DG HSM APO', + 0x185 => 'Sigma 500mm F4 DG OS HSM | S', #JR (NC; based on product number) (016) + 0x194 => 'Sigma 300mm F2.8 EX HSM APO', + 0x195 => 'Sigma 300mm F2.8 EX DG HSM APO', + 0x200 => 'Sigma 12-24mm F4.5-5.6 EX DG ASP HSM', + 0x201 => 'Sigma 10-20mm F4-5.6 EX DC HSM', + 0x202 => 'Sigma 10-20mm F3.5 EX DC HSM', + 0x203 => 'Sigma 8-16mm F4.5-5.6 DC HSM', + 0x204 => 'Sigma 12-24mm F4.5-5.6 DG HSM II', + 0x205 => 'Sigma 12-24mm F4 DG HSM | A', #JR (NC; based on product number) (016) + 0x210 => 'Sigma 18-35mm F1.8 DC HSM | A', + 0x240 => 'Sigma 135mm F1.8 DG HSM | A', #JR (NC; based on product number) (017) + 0x256 => 'Sigma 105mm F2.8 EX Macro', + 0x257 => 'Sigma 105mm F2.8 EX DG Macro', + 0x258 => 'Sigma 105mm F2.8 EX DG OS HSM Macro', + 0x259 => 'Sigma 105mm F1.4 DG HSM | A', #IB (A018) + 0x270 => 'Sigma 70mm F2.8 EX DG Macro', #NJ (SD1) + 0x271 => 'Sigma 70mm F2.8 DG Macro | A', #IB (A018) + 0x300 => 'Sigma 30mm F1.4 EX DC HSM', + 0x301 => 'Sigma 30mm F1.4 DC HSM | A', + 0x302 => 'Sigma 30mm F1.4 DC DN | C', #JR (DN lenses are only for Sony E or MFT mount) + 0x310 => 'Sigma 50mm F1.4 EX DG HSM', + 0x311 => 'Sigma 50mm F1.4 DG HSM | A', + 0x320 => 'Sigma 85mm F1.4 EX DG HSM', + 0x321 => 'Sigma 85mm F1.4 DG HSM | A', #JR (NC; based on product number) (016) + 0x330 => 'Sigma 30mm F2.8 EX DN', + 0x340 => 'Sigma 35mm F1.4 DG HSM', + 0x345 => 'Sigma 50mm F2.8 EX Macro', + 0x346 => 'Sigma 50mm F2.8 EX DG Macro', + 0x350 => 'Sigma 60mm F2.8 DN | A', + 0x400 => 'Sigma 19mm F2.8 EX DN', + 0x401 => 'Sigma 24mm F1.4 DG HSM | A', + 0x411 => 'Sigma 20mm F1.8 EX DG ASP RF', + 0x412 => 'Sigma 20mm F1.4 DG HSM | A', + 0x432 => 'Sigma 24mm F1.8 EX DG ASP Macro', + 0x440 => 'Sigma 28mm F1.8 EX DG ASP Macro', + 0x450 => 'Sigma 14mm F1.8 DH HSM | A', #JR (NC; based on product number) (017) + 0x461 => 'Sigma 14mm F2.8 EX ASP HSM', + 0x475 => 'Sigma 15mm F2.8 EX Diagonal FishEye', + 0x476 => 'Sigma 15mm F2.8 EX DG Diagonal Fisheye', + 0x477 => 'Sigma 10mm F2.8 EX DC HSM Fisheye', + 0x483 => 'Sigma 8mm F4 EX Circular Fisheye', + 0x484 => 'Sigma 8mm F4 EX DG Circular Fisheye', + 0x485 => 'Sigma 8mm F3.5 EX DG Circular Fisheye', + 0x486 => 'Sigma 4.5mm F2.8 EX DC HSM Circular Fisheye', + 0x504 => 'Sigma 70-300mm F4-5.6 Macro Super', #IB + 0x505 => 'Sigma APO 70-300mm F4-5.6 Macro Super', #IB + 0x506 => 'Sigma 70-300mm F4-5.6 APO Macro Super II', + 0x507 => 'Sigma 70-300mm F4-5.6 DL Macro Super II', + 0x508 => 'Sigma 70-300mm F4-5.6 DG APO Macro', + 0x509 => 'Sigma 70-300mm F4-5.6 DG Macro', + 0x510 => 'Sigma 17-35 F2.8-4 EX DG ASP', + 0x512 => 'Sigma 15-30mm F3.5-4.5 EX DG ASP DF', + 0x513 => 'Sigma 20-40mm F2.8 EX DG', + 0x519 => 'Sigma 17-35 F2.8-4 EX ASP HSM', + 0x520 => 'Sigma 100-300mm F4.5-6.7 DL', + 0x521 => 'Sigma 18-50mm F3.5-5.6 DC Macro', + 0x527 => 'Sigma 100-300mm F4 EX IF HSM', + 0x529 => 'Sigma 120-300mm F2.8 EX HSM IF APO', + 0x545 => 'Sigma 28-70mm F2.8 EX ASP DF', #IB + 0x547 => 'Sigma 24-60mm F2.8 EX DG', + 0x548 => 'Sigma 24-70mm F2.8 EX DG Macro', + 0x549 => 'Sigma 28-70mm F2.8 EX DG', + 0x566 => 'Sigma 70-200mm F2.8 EX IF APO', + 0x567 => 'Sigma 70-200mm F2.8 EX IF HSM APO', + 0x568 => 'Sigma 70-200mm F2.8 EX DG IF HSM APO', + 0x569 => 'Sigma 70-200 F2.8 EX DG HSM APO Macro', + 0x571 => 'Sigma 24-70mm F2.8 IF EX DG HSM', + 0x572 => 'Sigma 70-300mm F4-5.6 DG OS', + 0x576 => 'Sigma 24-70mm F2.8 DG OS HSM | A', #JR (NC; based on product number) (017) + 0x579 => 'Sigma 70-200mm F2.8 EX DG HSM APO Macro', # (also II version) + 0x580 => 'Sigma 18-50mm F2.8 EX DC', + 0x581 => 'Sigma 18-50mm F2.8 EX DC Macro', #PH (SD1) + 0x582 => 'Sigma 18-50mm F2.8 EX DC HSM Macro', + 0x583 => 'Sigma 17-50mm F2.8 EX DC OS HSM', #PH (also SD1 Kit, is this HSM? - PH) + 0x588 => 'Sigma 24-35mm F2 DG HSM | A', + 0x589 => 'Sigma APO 70-200mm F2.8 EX DG OS HSM', + 0x594 => 'Sigma 300-800mm F5.6 EX HSM IF APO', + 0x595 => 'Sigma 300-800mm F5.6 EX DG APO HSM', + 0x597 => 'Sigma 200-500mm F2.8 APO EX DG', + 0x5A8 => 'Sigma 70-300mm F4-5.6 APO DG Macro (Motorized)', + 0x5A9 => 'Sigma 70-300mm F4-5.6 DG Macro (Motorized)', + 0x605 => 'Sigma 24-70mm F3.5-5.6 ASP HF', #IB + 0x633 => 'Sigma 28-70mm F2.8-4 HS', + 0x634 => 'Sigma 28-70mm F2.8-4 DG', + 0x635 => 'Sigma 24-105mm F4 DG OS HSM | A', + 0x644 => 'Sigma 28-80mm F3.5-5.6 ASP HF Macro', + 0x659 => 'Sigma 28-80mm F3.5-5.6 Mini Zoom Macro II ASP', + 0x661 => 'Sigma 28-105mm F2.8-4 IF ASP', + 0x663 => 'Sigma 28-105mm F3.8-5.6 IF UC-III ASP', + 0x664 => 'Sigma 28-105mm F2.8-4 IF DG ASP', + 0x667 => 'Sigma 24-135mm F2.8-4.5 IF ASP', + 0x668 => 'Sigma 17-70mm F2.8-4 DC Macro OS HSM', + 0x669 => 'Sigma 17-70mm F2.8-4.5 DC HSM Macro', + 0x684 => 'Sigma 55-200mm F4-5.6 DC', + 0x686 => 'Sigma 50-200mm F4-5.6 DC OS HSM', + 0x689 => 'Sigma 17-70mm F2.8-4.5 DC Macro', + 0x690 => 'Sigma 50-150mm F2.8 EX DC HSM APO', + 0x691 => 'Sigma 50-150mm F2.8 EX DC APO HSM II', + 0x692 => 'Sigma APO 50-150mm F2.8 EX DC OS HSM', + 0x693 => 'Sigma 50-100mm F1.8 DC HSM | A', #JR (NC; based on product number) (016) + 0x709 => 'Sigma 28-135mm F3.8-5.6 IF ASP Macro', + 0x723 => 'Sigma 135-400mm F4.5-5.6 ASP APO', + 0x725 => 'Sigma 80-400mm F4.5-5.6 EX OS', + 0x726 => 'Sigma 80-400mm F4.5-5.6 EX DG OS APO', + 0x727 => 'Sigma 135-400mm F4.5-5.6 DG ASP APO', + 0x728 => 'Sigma 120-400mm F4.5-5.6 DG APO OS HSM', + 0x729 => 'Sigma 100-400mm F5-6.3 DG OS HSM | C', #JR (017) + 0x730 => 'Sigma 60-600mm F4.5-6.3 DG OS HSM | S', #IB (S018) + 0x733 => 'Sigma 170-500mm F5-6.3 ASP APO', + 0x734 => 'Sigma 170-500mm F5-6.3 DG ASP APO', + 0x735 => 'Sigma 50-500mm F4-6.3 EX RF HSM APO', + 0x736 => 'Sigma 50-500mm F4-6.3 EX DG HSM APO', + 0x737 => 'Sigma 150-500mm F5-6.3 APO DG OS HSM', + 0x738 => 'Sigma 50-500mm F4.5-6.3 APO DG OS HSM', + 0x740 => 'Sigma 150-600mm F5-6.3 DG OS HSM | S', + 0x745 => 'Sigma 150-600mm F5-6.3 DG OS HSM | C', + 0x777 => 'Sigma 18-200mm F3.5-6.3 DC', + 0x77D => 'Sigma 18-200mm F3.5-6.3 DC (Motorized)', + 0x785 => 'Sigma 28-200mm F3.5-5.6 DL ASP IF HZM Macro', #IB + 0x787 => 'Sigma 28-200mm F3.5-5.6 Compact ASP HZ Macro', + 0x789 => 'Sigma 18-125mm F3.5-5.6 DC', + 0x790 => 'Sigma 28-300mm F3.5-6.3 DL ASP IF HZM', #IB + 0x793 => 'Sigma 28-300mm F3.5-6.3 Macro', + 0x794 => 'Sigma 28-200mm F3.5-5.6 DG Compact ASP HZ Macro', + 0x795 => 'Sigma 28-300mm F3.5-6.3 DG Macro', + 0x823 => 'Sigma 1.4X TC EX APO', + 0x824 => 'Sigma 1.4X Teleconverter EX APO DG', + 0x853 => 'Sigma 18-125mm F3.8-5.6 DC OS HSM', + 0x861 => 'Sigma 18-50mm F2.8-4.5 DC OS HSM', #NJ (SD1) + 0x870 => 'Sigma 2.0X Teleconverter TC-2001', #JR + 0x875 => 'Sigma 2.0X TC EX APO', + 0x876 => 'Sigma 2.0X Teleconverter EX APO DG', + 0x879 => 'Sigma 1.4X Teleconverter TC-1401', #JR + 0x880 => 'Sigma 18-250mm F3.5-6.3 DC OS HSM', + 0x882 => 'Sigma 18-200mm F3.5-6.3 II DC OS HSM', + 0x883 => 'Sigma 18-250mm F3.5-6.3 DC Macro OS HSM', + 0x884 => 'Sigma 17-70mm F2.8-4 DC OS HSM Macro | C', + 0x885 => 'Sigma 18-200mm F3.5-6.3 DC OS HSM Macro | C', + 0x886 => 'Sigma 18-300mm F3.5-6.3 DC OS HSM Macro | C', + 0x888 => 'Sigma 18-200mm F3.5-6.3 DC OS', + 0x890 => 'Sigma Mount Converter MC-11', #JR + 0x929 => 'Sigma 19mm F2.8 DN | A', + 0x929 => 'Sigma 30mm F2.8 DN | A', + 0x929 => 'Sigma 60mm F2.8 DN | A', + 0x1003 => 'Sigma 19mm F2.8', #PH (DP1 Merrill kit) + 0x1004 => 'Sigma 30mm F2.8', #PH (DP2 Merrill kit) + 0x1005 => 'Sigma 50mm F2.8 Macro', #PH (DP3 Merrill kit) + 0x1006 => 'Sigma 19mm F2.8', #NJ (DP1 Quattro kit) + 0x1007 => 'Sigma 30mm F2.8', #PH (DP2 Quattro kit) + 0x1008 => 'Sigma 50mm F2.8 Macro', #NJ (DP3 Quattro kit) + 0x1009 => 'Sigma 14mm F4', #NJ (DP0 Quattro kit) + # L-mount lenses?: + 0x4001 => 'Lumix S 24-105mm F4 Macro OIS (S-R24105)', #IB + 0x4002 => 'Lumix S 70-200mm F4 OIS (S-R70200)', #IB + 0x4003 => 'Lumix S 50mm F1.4 (S-X50)', #IB + 0x4006 => 'Lumix S 24-70mm F2.8 (S-E2470)', #IB + 0x4007 => 'Lumix S 16-35mm F4 (S-R1635)', #IB + 0x4008 => 'Lumix S 70-200mm F2.8 OIS (S-E70200)', #IB + 0x4010 => 'Lumix S 35mm F1.8 (S-S35)', #IB + 0x4011 => 'LUMIX S 18mm F1.8 (S-S18)', #IB + 0x400b => 'Lumix S 20-60mm F3.5-5.6 (S-R2060)', #IB + 0x400c => 'Lumix S 85mm F1.8 (S-S85)', #IB + 0x400d => 'Lumix S 70-300 F4.5-5.6 Macro OIS (S-R70300)', #IB + 0x400f => 'Lumix S 24mm F1.8 (S-S24)', #IB + 0x6001 => 'Sigma 150-600mm F5-6.3 DG OS HSM | S', #PH (NC, fp) + 0x6003 => 'Sigma 45mm F2.8 DG DN | C', #PH (NC, fp) + 0x6005 => 'Sigma 14-24mm F2.8 DG DN | A', #IB + 0x6006 => 'Sigma 50mm F1.4 DG HSM | A', #IB (014) + 0x6011 => 'Sigma 24-70mm F2.8 DG DN | A', #IB + 0x6012 => 'Sigma 100-400mm F5-6.3 DG DN OS | C', #IB + 0x6013 => 'Sigma 100-400mm F5-6.3 DG DN OS | C + TC-1411', #IB + 0x6015 => 'Sigma 85mm F1.4 DG DN | A', #IB + 0x6017 => 'Sigma 65mm F2 DG DN | C', #IB + 0x6018 => 'Sigma 35mm F2 DG DN | C', #IB + 0x601a => 'Sigma 28-70mm F2.8 DG DN | C', #IB + 0x601b => 'Sigma 150-600mm F5-6.3 DG DN OS | S', #IB + 0x6020 => 'Sigma 35mm F1.4 DG DN | A', #IB + 0x6021 => 'Sigma 90mm F2.8 DG DN | C', #IB + 0x6023 => 'Sigma 20mm F2 DG DN | C', #IB + 0x6025 => 'Sigma 20mm F1.4 DG DN | A', #IB + 0x6026 => 'Sigma 24mm F1.4 DG DN | A', #IB + 0x602c => "Sigma 50mm F1.4 DG DN | A (2023)", #IB + 0x8005 => 'Sigma 35mm F1.4 DG HSM | A', #PH (012) + 0x8009 => 'Sigma 18-35mm F1.8 DC HSM | A', #PH + 0x8900 => 'Sigma 70-300mm F4-5.6 DG OS', #PH (SD15) + 0xA100 => 'Sigma 24-70mm F2.8 DG Macro', #PH (SD15) + # 'FFFF' - seen this for a 28-70mm F2.8 lens - PH +); + +%Image::ExifTool::Sigma::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + WRITABLE => 'string', + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + These tags are written by Sigma/Foveon cameras. In the early days Sigma was + a class leader by releasing their maker note specification to the public, + but since then they have deviated from this standard and newer camera models + are less than consistent about their metadata formats. + }, + 0x0002 => 'SerialNumber', + 0x0003 => 'DriveMode', + 0x0004 => 'ResolutionMode', + 0x0005 => 'AFMode', + 0x0006 => 'FocusSetting', + 0x0007 => 'WhiteBalance', + 0x0008 => { + Name => 'ExposureMode', + PrintConv => { #PH + A => 'Aperture-priority AE', + M => 'Manual', + P => 'Program AE', + S => 'Shutter speed priority AE', + }, + }, + 0x0009 => { + Name => 'MeteringMode', + PrintConv => { #PH + A => 'Average', + C => 'Center-weighted average', + 8 => 'Multi-segment', + }, + }, + 0x000a => 'LensFocalRange', + 0x000b => 'ColorSpace', + # SIGMA PhotoPro writes these tags as strings, but some cameras (at least) write them as rational + 0x000c => [ + { + Name => 'ExposureCompensation', + Condition => '$format eq "string"', + ValueConv => '$val =~ s/Expo:\s*//, $val', + ValueConvInv => 'IsFloat($val) ? sprintf("Expo:%+.1f",$val) : undef', + }, + { #PH + Name => 'ExposureAdjust', + Writable => 'rational64s', + Unknown => 1, + }, + ], + 0x000d => [ + { + Name => 'Contrast', + Condition => '$format eq "string"', + ValueConv => '$val =~ s/Cont:\s*//, $val', + ValueConvInv => 'IsFloat($val) ? sprintf("Cont:%+.1f",$val) : undef', + }, + { #PH + Name => 'Contrast', + Writable => 'rational64s', + Priority => 0, + }, + ], + 0x000e => [ + { + Name => 'Shadow', + Condition => '$format eq "string"', + ValueConv => '$val =~ s/Shad:\s*//, $val', + ValueConvInv => 'IsFloat($val) ? sprintf("Shad:%+.1f",$val) : undef', + }, + { #PH (may be incorrect for the SD1) + Name => 'Shadow', + Writable => 'rational64s', + Priority => 0, + }, + ], + 0x000f => [ + { + Name => 'Highlight', + Condition => '$format eq "string"', + ValueConv => '$val =~ s/High:\s*//, $val', + ValueConvInv => 'IsFloat($val) ? sprintf("High:%+.1f",$val) : undef', + }, + { #PH (may be incorrect for the SD1) + Name => 'Highlight', + Writable => 'rational64s', + Priority => 0, + }, + ], + 0x0010 => [ + { + Name => 'Saturation', + Condition => '$format eq "string"', + ValueConv => '$val =~ s/Satu:\s*//, $val', + ValueConvInv => 'IsFloat($val) ? sprintf("Satu:%+.1f",$val) : undef', + }, + { #PH (may be incorrect for the SD1) + Name => 'Saturation', + Writable => 'rational64s', + Priority => 0, + }, + ], + 0x0011 => [ + { + Name => 'Sharpness', + Condition => '$format eq "string"', + ValueConv => '$val =~ s/Shar:\s*//, $val', + ValueConvInv => 'IsFloat($val) ? sprintf("Shar:%+.1f",$val) : undef', + }, + { #PH (may be incorrect for the SD1) + Name => 'Sharpness', + Writable => 'rational64s', + Priority => 0, + }, + ], + 0x0012 => [ + { + Name => 'X3FillLight', + Condition => '$format eq "string"', + ValueConv => '$val =~ s/Fill:\s*//, $val', + ValueConvInv => 'IsFloat($val) ? sprintf("Fill:%+.1f",$val) : undef', + }, + { #PH + Name => 'X3FillLight', + Writable => 'rational64s', + }, + ], + 0x0014 => [ + { + Name => 'ColorAdjustment', + Condition => '$format eq "string"', + ValueConv => '$val =~ s/CC:\s*//, $val', + ValueConvInv => 'IsInt($val) ? "CC:$val" : undef', + }, + { #PH + Name => 'ColorAdjustment', + Writable => 'rational64s', + Count => 3, + }, + ], + 0x0015 => 'AdjustmentMode', + 0x0016 => { + Name => 'Quality', + ValueConv => '$val =~ s/Qual:\s*//, $val', + ValueConvInv => 'IsInt($val) ? "Qual:$val" : undef', + }, + 0x0017 => 'Firmware', + 0x0018 => { + Name => 'Software', + Priority => 0, + }, + 0x0019 => 'AutoBracket', + 0x001a => [ #PH + { + Name => 'PreviewImageStart', + Condition => '$format eq "int32u"', + Notes => q{ + Sigma Photo Pro writes ChrominanceNoiseReduction here, but various + models use this for PreviewImageStart + }, + IsOffset => 1, + OffsetPair => 0x001b, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + },{ # (written by Sigma Photo Pro) + Name => 'ChrominanceNoiseReduction', + Condition => '$format eq "string"', + ValueConv => '$val =~ s/Chro:\s*//, $val', + ValueConvInv => 'IsFloat($val) ? sprintf("Chro:%+.1f",$val) : undef', + }, + # the SD1 writes something else here (rational64s, value 0/10) + # (but we can't test by model because Sigma Photo Pro writes this too) + ], + 0x001b => [ #PH + { + Name => 'PreviewImageLength', + Condition => '$format eq "int32u"', + Notes => q{ + Sigma Photo Pro writes LuminanceNoiseReduction here, but various models use + this for PreviewImageLength + }, + OffsetPair => 0x001a, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + },{ # (written by Sigma Photo Pro) + Name => 'LuminanceNoiseReduction', + Condition => '$format eq "string"', + ValueConv => '$val =~ s/Luma:\s*//, $val', + ValueConvInv => 'IsFloat($val) ? sprintf("Luma:%+.1f",$val) : undef', + }, + # the SD1 writes something else here (rational64s, value 0/10) + ], + 0x001c => [ #PH + { + Name => 'PreviewImageSize', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => q{ + PreviewImageStart for the SD1 and Merrill/Quattro models, and + PreviewImageSize for others + }, + Writable => 'int16u', + Count => 2, + PrintConv => '$val =~ tr/ /x/; $val', + PrintConvInv => '$val =~ tr/x/ /; $val', + },{ + Name => 'PreviewImageStart', + Condition => '$format eq "int32u"', + IsOffset => 1, + OffsetPair => 0x001d, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + ], + 0x001d => [ #PH + { + Name => 'MakerNoteVersion', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => q{ + PreviewImageLength for the SD1 and Merrill/Quattro models, and + MakerNoteVersion for others + }, + Writable => 'undef', + },{ + Name => 'PreviewImageLength', + Condition => '$format eq "int32u"', + OffsetPair => 0x001c, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, + ], + # 0x001e - int16u: 0, 4, 13 - flash mode for other models? + 0x001e => { #PH + Name => 'PreviewImageSize', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'only valid for some models', + Writable => 'int16u', + Count => 2, + PrintConv => '$val =~ tr/ /x/; $val', + PrintConvInv => '$val =~ tr/x/ /; $val', + }, + 0x001f => [ #PH + { + Name => 'AFPoint', # (NC -- invalid for SD9,SD14?) + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => q{ + MakerNoteVersion for the SD1 and Merrill/Quattro models, and AFPoint for + others + }, + # values: "", "Center", "Center,Center", "Right,Right" + },{ + Name => 'MakerNoteVersion', + Writable => 'undef', + }, + ], + # 0x0020 - string: " " for most models, or int16u: 4 for the DP3 Merrill + # 0x0021 - string: " " for most models, or int8u[2]: '3 3' for the DP3 Merrill + 0x0022 => { #PH (NC) + Name => 'FileFormat', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => 'models other than the SD1 and Merrill/Quattro models', + # values: "JPG", "JPG-S", "JPG-P", "X3F", "X3F-S" + }, + # 0x0023 - string: "", 10, 83, 131, 145, 150, 152, 169 + 0x0024 => { # (invalid for SD9,SD14?) + Name => 'Calibration', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => 'models other than the SD1 and Merrill/Quattro models', + }, + # 0x0025 - string: "", "0.70", "0.90" + # 0x0026-2b - int32u: 0 + 0x0026 => { #PH (NC) + Name => 'FileFormat', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + # (also Sigma fp) + }, + 0x0027 => [{ #PH + Name => 'LensType', + Condition => '$$self{MakerNoteSigmaVer} >= 3 and $format eq "string"', + Notes => 'some newer models only', + ValueConv => '$val =~ /^[0-9a-f]+$/i ? hex($val) : $val', + # (truncate decimal part and convert hex) + ValueConvInv => '$val=~s/\.\d+$//;$val=~/^0x/ and $val=hex($val);IsInt($val) ? sprintf("%x",$val) : $val', + SeparateTable => 'LensType', + PrintHex => 1, + PrintConv => \%sigmaLensTypes, + PrintInt => 1, + },{ #PH + Name => 'LensType', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some other models like the fp', + Writable => 'int16u', + SeparateTable => 'LensType', + PrintHex => 1, + PrintConv => \%sigmaLensTypes, + PrintInt => 1, + }], + 0x002a => { #PH + Name => 'LensFocalRange', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + Writable => 'rational64u', + Count => 2, + PrintConv => '$val=~s/ / to /; $val', + PrintConvInv => '$val=~s/to //; $val', + }, + 0x002b => { #PH + Name => 'LensMaxApertureRange', + # for most models this gives the max aperture at the long/short focal lengths, + # but for some models this gives the min/max aperture + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + Writable => 'rational64u', + Count => 2, + PrintConv => '$val=~s/ / to /; $val', + PrintConvInv => '$val=~s/to /; $val', + }, + # 0x002c is rational64u for some models, with a value that may be related to FNumber - PH + 0x002c => { #PH + Name => 'ColorMode', + Condition => '$format eq "int32u"', + Notes => 'not valid for some models', + Writable => 'int32u', + # this tag written by Sigma Photo Pro even for cameras that write 'n/a' here + PrintConv => { + 0 => 'n/a', + 1 => 'Sepia', + 2 => 'B&W', + 3 => 'Standard', + 4 => 'Vivid', + 5 => 'Neutral', + 6 => 'Portrait', + 7 => 'Landscape', + 8 => 'FOV Classic Blue', + }, + }, + # 0x002d - int32u: 0 + # 0x002e - rational64s: (the negative of FlashExposureComp, but why?) + # 0x002f - int32u: 0, 1 + 0x0030 => [ #PH + { + Name => 'LensApertureRange', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => q{ + Calibration for the SD1 and Merrill/Quattro models, and LensApertureRange + for others. Note that LensApertureRange changes with focal length, and some + models report the maximum aperture here + }, + },{ + Name => 'Calibration', + }, + ], + 0x0031 => { #PH + Name => 'FNumber', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => 'models other than the SD1 and Merrill/Quattro models', + Writable => 'rational64u', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + Priority => 0, + }, + 0x0032 => { #PH + Name => 'ExposureTime', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => 'models other than the SD1 and Merrill/Quattro models', + Writable => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => '$val', + Priority => 0, + }, + 0x0033 => { #PH + Name => 'ExposureTime2', + Condition => '$$self{Model} !~ / (SD1|SD9|SD15|Merrill|Quattro|fp)$/', + Notes => 'models other than the SD1, SD9, SD15 and Merrill/Quattro models', + ValueConv => '$val * 1e-6', + ValueConvInv => 'int($val * 1e6 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x0034 => { #PH + Name => 'BurstShot', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => 'models other than the SD1 and Merrill/Quattro models', + Writable => 'int32u', + }, + # 0x0034 - int32u: 0,1,2,3 or 4 + 0x0035 => { #PH + Name => 'ExposureCompensation', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => 'models other than the SD1 and Merrill/Quattro models', + Writable => 'rational64s', + # add a '+' sign to positive values + PrintConv => '$val and $val =~ s/^(\d)/\+$1/; $val', + PrintConvInv => '$val', + }, + # 0x0036 - string: " " + # 0x0037-38 - string: "" + 0x0039 => { #PH (invalid for SD9, SD14?) + Name => 'SensorTemperature', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => 'models other than the SD1 and Merrill/Quattro models', + # (string format) + PrintConv => 'IsInt($val) ? "$val C" : $val', + PrintConvInv => '$val=~s/ ?C$//; $val', + }, + 0x003a => { #PH + Name => 'FlashExposureComp', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => 'models other than the SD1 and Merrill/Quattro models', + Writable => 'rational64s', + }, + 0x003b => { #PH (how is this different from other Firmware?) + Name => 'Firmware', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => 'models other than the SD1 and Merrill/Quattro models', + Priority => 0, + }, + 0x003c => { #PH + Name => 'WhiteBalance', + Condition => '$$self{MakerNoteSigmaVer} < 3', + Notes => 'models other than the SD1 and Merrill/Quattro models', + Priority => 0, + }, + 0x003d => { #PH (new for SD15 and SD1) + Name => 'PictureMode', + Notes => 'same as ColorMode, but "Standard" when ColorMode is Sepia or B&W', + }, + 0x0048 => { #PH + Name => 'LensApertureRange', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + }, + 0x0049 => { #PH + Name => 'FNumber', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + Writable => 'rational64u', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + Priority => 0, + }, + 0x004a => { #PH + Name => 'ExposureTime', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + Writable => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => '$val', + Priority => 0, + }, + 0x004b => [{ #PH + Name => 'ExposureTime2', + Condition => '$$self{Model} =~ /^SIGMA (SD1( Merrill)?|DP\d Merrill)$/', + Notes => 'SD1 and DP Merrill models only', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + },{ #PH + Name => 'ExposureTime2', + Condition => '$$self{Model} =~ /^SIGMA dp\d Quattro$/i', + Notes => 'DP Quattro models only', + ValueConv => '$val / 1000000', + ValueConvInv => '$val * 1000000', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }], + 0x004d => { #PH + Name => 'ExposureCompensation', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + Writable => 'rational64s', + # add a '+' sign to positive values + PrintConv => '$val and $val =~ s/^(\d)/\+$1/; $val', + PrintConvInv => '$val', + }, + # 0x0054 - string: "F20","F23" + 0x0055 => { #PH + Name => 'SensorTemperature', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + # (string format) + PrintConv => 'IsInt($val) ? "$val C" : $val', + PrintConvInv => '$val=~s/ ?C$//; $val', + }, + 0x0056 => { #PH (NC) + Name => 'FlashExposureComp', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + Writable => 'rational64s', + }, + 0x0057 => { #PH (how is this different from other Firmware?) + Name => 'Firmware2', + Condition => '$format eq "string"', + Notes => 'some newer models only', + Priority => 0, + }, + 0x0058 => { #PH + Name => 'WhiteBalance', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + Priority => 0, + }, + 0x0059 => { #PH + Name => 'DigitalFilter', + Condition => '$$self{MakerNoteSigmaVer} >= 3', + Notes => 'some newer models only', + # seen: Standard,Landscape,Monochrome,Neutral,Portrait,Sepia,Vivid + }, + # 0x005a/b/c - rational64s: 0/10 for the SD1 + 0x0084 => { #PH (Quattro models and fp) + Name => 'Model', + Description => 'Camera Model Name', + }, + # 0x0085 + 0x0086 => { #PH (Quattro models) + Name => 'ISO', + Writable => 'int16u', + }, + 0x0087 => 'ResolutionMode', #PH (Quattro models) + 0x0088 => 'WhiteBalance', #PH (Quattro models) + 0x008c => 'Firmware', #PH (Quattro models) + 0x011f => { #IB (FP DNG images) + Name => 'CameraCalibration', + Writable => 'float', + Count => 9, + }, + 0x0120 => { #IB (FP DNG images) + Name => 'WBSettings', + SubDirectory => { TagTable => 'Image::ExifTool::Sigma::WBSettings' }, + }, + 0x0121 => { #IB (FP DNG images) + Name => 'WBSettings2', + SubDirectory => { TagTable => 'Image::ExifTool::Sigma::WBSettings2' }, + }, +); + +# WB settings (ref IB) +%Image::ExifTool::Sigma::WBSettings = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'float', + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => { Name => 'WB_RGBLevelsAuto', Format => 'float[3]' }, + 3 => { Name => 'WB_RGBLevelsDaylight', Format => 'float[3]' }, + 6 => { Name => 'WB_RGBLevelsShade', Format => 'float[3]' }, + 9 => { Name => 'WB_RGBLevelsOvercast', Format => 'float[3]' }, + 12 => { Name => 'WB_RGBLevelsIncandescent', Format => 'float[3]' }, + 15 => { Name => 'WB_RGBLevelsFluorescent', Format => 'float[3]' }, + 18 => { Name => 'WB_RGBLevelsFlash', Format => 'float[3]' }, + 21 => { Name => 'WB_RGBLevelsCustom1', Format => 'float[3]' }, + 24 => { Name => 'WB_RGBLevelsCustom2', Format => 'float[3]' }, + 27 => { Name => 'WB_RGBLevelsCustom3', Format => 'float[3]' }, +); + +# WB settings (ref IB) +%Image::ExifTool::Sigma::WBSettings2 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FORMAT => 'float', + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0 => { Name => 'WB_RGBLevelsUnknown0', Unknown => 1, Format => 'float[3]' }, + 3 => { Name => 'WB_RGBLevelsUnknown1', Unknown => 1, Format => 'float[3]' }, + 6 => { Name => 'WB_RGBLevelsUnknown2', Unknown => 1, Format => 'float[3]' }, + 9 => { Name => 'WB_RGBLevelsUnknown3', Unknown => 1, Format => 'float[3]' }, + 12 => { Name => 'WB_RGBLevelsUnknown4', Unknown => 1, Format => 'float[3]' }, + 15 => { Name => 'WB_RGBLevelsUnknown5', Unknown => 1, Format => 'float[3]' }, + 18 => { Name => 'WB_RGBLevelsUnknown6', Unknown => 1, Format => 'float[3]' }, + 21 => { Name => 'WB_RGBLevelsUnknown7', Unknown => 1, Format => 'float[3]' }, + 24 => { Name => 'WB_RGBLevelsUnknown8', Unknown => 1, Format => 'float[3]' }, + 27 => { Name => 'WB_RGBLevelsUnknown9', Unknown => 1, Format => 'float[3]' }, +); + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Sigma - Sigma/Foveon 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 +Sigma and Foveon maker notes in EXIF information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.x3f.info/technotes/FileDocs/MakerNoteDoc.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Sigma Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/SigmaRaw.pm b/ExifTool/lib/Image/ExifTool/SigmaRaw.pm new file mode 100644 index 0000000..1c84094 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/SigmaRaw.pm @@ -0,0 +1,702 @@ +#------------------------------------------------------------------------------ +# File: SigmaRaw.pm +# +# Description: Read Sigma/Foveon RAW (X3F) meta information +# +# Revisions: 2005/10/16 - P. Harvey Created +# 2009/11/30 - P. Harvey Support X3F v2.3 written by Sigma DP2 +# +# References: 1) http://www.x3f.info/technotes/FileDocs/X3F_Format.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::SigmaRaw; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Sigma; + +$VERSION = '1.31'; + +sub ProcessX3FHeader($$$); +sub ProcessX3FDirectory($$$); +sub ProcessX3FProperties($$$); + +# main X3F sections (plus header stuff) +%Image::ExifTool::SigmaRaw::Main = ( + PROCESS_PROC => \&ProcessX3FDirectory, + NOTES => q{ + These tags are used in Sigma and Foveon RAW (.X3F) images. Metadata is also + extracted from the JpgFromRaw image if it exists (all models but the SD9 and + SD10). Currently, metadata may only be written to the embedded JpgFromRaw. + }, + Header => { + SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::Header' }, + }, + Header4 => { + SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::Header4' }, + }, + HeaderExt => { + SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::HeaderExt' }, + }, + PROP => { + Name => 'Properties', + SubDirectory => { TagTable => 'Image::ExifTool::SigmaRaw::Properties' }, + }, + IMAG => { + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + IMA2 => [ + { + Name => 'PreviewImage', + Condition => 'not $$self{IsJpgFromRaw}', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + { + Name => 'JpgFromRaw', + Groups => { 2 => 'Preview' }, + Binary => 1, + }, + ] +); + +# common X3F header structure +%Image::ExifTool::SigmaRaw::Header = ( + PROCESS_PROC => \&ProcessX3FHeader, + FORMAT => 'int32u', + NOTES => 'Information extracted from the header of an X3F file.', + 1 => { + Name => 'FileVersion', + ValueConv => '($val >> 16) . "." . ($val & 0xffff)', + }, + 2 => { + Name => 'ImageUniqueID', + # the serial number (with an extra leading "0") makes up + # the first 8 digits of this UID, + Format => 'undef[16]', + ValueConv => 'unpack("H*", $val)', + }, + 6 => { + Name => 'MarkBits', + PrintConv => { BITMASK => { } }, + }, + 7 => 'ImageWidth', + 8 => 'ImageHeight', + 9 => 'Rotation', + 10 => { + Name => 'WhiteBalance', + Format => 'string[32]', + }, + 18 => { #PH (DP2, FileVersion 2.3) + Name => 'SceneCaptureType', + Format => 'string[32]', + }, +); + +# X3F version 4 header structure (ref PH) +%Image::ExifTool::SigmaRaw::Header4 = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int32u', + NOTES => 'Header information for version 4.0 or greater X3F.', + 1 => { + Name => 'FileVersion', + ValueConv => '($val >> 16) . "." . ($val & 0xffff)', + }, + # 8 - undef[4]: 4 random ASCII characters + 10 => 'ImageWidth', + 11 => 'ImageHeight', + 12 => 'Rotation', + # don't know what the rest of the header contains, but none of + # these values change in any of my samples... +); + +# extended header tags +%Image::ExifTool::SigmaRaw::HeaderExt = ( + GROUPS => { 2 => 'Camera' }, + FORMAT => 'float', + NOTES => 'Extended header data found in version 2.1 and 2.2 files', + 0 => 'Unused', + 1 => { Name => 'ExposureAdjust',PrintConv => 'sprintf("%.1f",$val)' }, + 2 => { Name => 'Contrast', PrintConv => 'sprintf("%.1f",$val)' }, + 3 => { Name => 'Shadow', PrintConv => 'sprintf("%.1f",$val)' }, + 4 => { Name => 'Highlight', PrintConv => 'sprintf("%.1f",$val)' }, + 5 => { Name => 'Saturation', PrintConv => 'sprintf("%.1f",$val)' }, + 6 => { Name => 'Sharpness', PrintConv => 'sprintf("%.1f",$val)' }, + 7 => { Name => 'RedAdjust', PrintConv => 'sprintf("%.1f",$val)' }, + 8 => { Name => 'GreenAdjust', PrintConv => 'sprintf("%.1f",$val)' }, + 9 => { Name => 'BlueAdjust', PrintConv => 'sprintf("%.1f",$val)' }, + 10 => { Name => 'X3FillLight', PrintConv => 'sprintf("%.1f",$val)' }, +); + +# PROP tags +%Image::ExifTool::SigmaRaw::Properties = ( + PROCESS_PROC => \&ProcessX3FProperties, + GROUPS => { 2 => 'Camera' }, + PRIORITY => 0, # (because these aren't writable like the EXIF ones) + AEMODE => { + Name => 'MeteringMode', + PrintConv => { + 8 => '8-segment', + C => 'Center-weighted average', + A => 'Average', + }, + }, + AFAREA => 'AFArea', # observed: CENTER_V + AFINFOCUS => 'AFInFocus', # observed: H + AFMODE => 'FocusMode', + AP_DESC => 'ApertureDisplayed', + APERTURE => { + Name => 'FNumber', + Groups => { 2 => 'Image' }, + PrintConv => 'sprintf("%.1f",$val)', + }, + BRACKET => 'BracketShot', + BURST => 'BurstShot', + CAMMANUF => 'Make', + CAMMODEL => 'Model', + CAMNAME => 'CameraName', + CAMSERIAL => 'SerialNumber', + CM_DESC => 'SceneCaptureType', #PH (DP2) + COLORSPACE => 'ColorSpace', # observed: sRGB + DRIVE => { + Name => 'DriveMode', + PrintConv => { + SINGLE => 'Single Shot', + MULTI => 'Multi Shot', + '2S' => '2 s Timer', + '10S' => '10 s Timer', + UP => 'Mirror Up', + AB => 'Auto Bracket', + OFF => 'Off', + }, + }, + EVAL_STATE => 'EvalState', # observed: POST-EXPOSURE + EXPCOMP => { + Name => 'ExposureCompensation', + Groups => { 2 => 'Image' }, + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + EXPNET => { + Name => 'NetExposureCompensation', + Groups => { 2 => 'Image' }, + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + }, + EXPTIME => { + Name => 'IntegrationTime', + Groups => { 2 => 'Image' }, + ValueConv => '$val * 1e-6', # convert from usec + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + FIRMVERS => 'FirmwareVersion', + FLASH => { + Name => 'FlashMode', + PrintConv => 'ucfirst(lc($val))', + }, + FLASHEXPCOMP=> 'FlashExpComp', + FLASHPOWER => 'FlashPower', + FLASHTTLMODE=> 'FlashTTLMode', # observed: ON + FLASHTYPE => 'FlashType', # observed: NONE + FLENGTH => { + Name => 'FocalLength', + PrintConv => 'sprintf("%.1f mm",$val)', + }, + FLEQ35MM => { + Name => 'FocalLengthIn35mmFormat', + PrintConv => 'sprintf("%.1f mm",$val)', + }, + FOCUS => { + Name => 'Focus', + PrintConv => { + AF => 'Auto-focus Locked', + 'NO LOCK' => "Auto-focus Didn't Lock", + M => 'Manual', + }, + }, + IMAGERBOARDID => 'ImagerBoardID', + IMAGERTEMP => { + Name => 'SensorTemperature', + PrintConv => '"$val C"', + }, + IMAGEBOARDID=> 'ImageBoardID', #PH (DP2) + ISO => 'ISO', + LENSARANGE => 'LensApertureRange', + LENSFRANGE => 'LensFocalRange', + LENSMODEL => { + Name => 'LensType', + ValueConv => '$val =~ /^[0-9a-f]+$/i ? hex($val) : $val', + ValueConvInv => '$val=~s/\.\d+$//; IsInt($val) ? sprintf("%x",$val) : $val', # (truncate decimal part) + SeparateTable => 'Sigma LensType', + PrintHex => 1, + PrintConv => \%Image::ExifTool::Sigma::sigmaLensTypes, + }, + PMODE => { + Name => 'ExposureProgram', + PrintConv => { + P => 'Program', + A => 'Aperture Priority', + S => 'Shutter Priority', + M => 'Manual', + }, + }, + RESOLUTION => { + Name => 'Quality', + PrintConv => { + LOW => 'Low', + MED => 'Medium', + HI => 'High', + }, + }, + SENSORID => 'SensorID', + SH_DESC => 'ShutterSpeedDisplayed', + SHUTTER => { + Name => 'ExposureTime', + Groups => { 2 => 'Image' }, + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + TIME => { + Name => 'DateTimeOriginal', + Groups => { 2 => 'Time' }, + Description => 'Date/Time Original', + ValueConv => 'ConvertUnixTime($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, + WB_DESC => 'WhiteBalance', + VERSION_BF => 'VersionBF', +); + +#------------------------------------------------------------------------------ +# Extract null-terminated unicode string from list of characters +# Inputs: 0) ExifTool ref, 1) list ref, 2) position in list +# Returns: Converted string +sub ExtractUnicodeString($$$) +{ + my ($et, $chars, $pos) = @_; + my $i; + for ($i=$pos; $i<@$chars; ++$i) { + last unless $$chars[$i]; + } + my $buff = pack('v*', @$chars[$pos..$i-1]); + return $et->Decode($buff, 'UCS2', 'II'); +} + +#------------------------------------------------------------------------------ +# Process an X3F header +# Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessX3FHeader($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $hdrLen = $$dirInfo{DirLen}; + + # process the static header structure first + $et->ProcessBinaryData($dirInfo, $tagTablePtr); + + # process extended data if available + if (length($$dataPt) - $hdrLen >= 160) { + my $verbose = $et->Options('Verbose'); + if ($verbose) { + $et->VerboseDir('X3F HeaderExt', 32); + $et->VerboseDump($dataPt, Start => $hdrLen); + } + $tagTablePtr = GetTagTable('Image::ExifTool::SigmaRaw::HeaderExt'); + my @tags = unpack("x${hdrLen}C32", $$dataPt); + my $i; + my $unused = 0; + for ($i=0; $i<32; ++$i) { + $tags[$i] or ++$unused, next; + $et->HandleTag($tagTablePtr, $tags[$i], undef, + Index => $i, + DataPt => $dataPt, + Start => $hdrLen + 32 + $i * 4, + Size => 4, + ); + } + $et->VPrint(0, "$$et{INDENT}($unused entries unused)\n"); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Process an X3F properties +# Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessX3FProperties($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $size = length($$dataPt); + my $verbose = $et->Options('Verbose'); + my $unknown = $et->Options('Unknown'); + + unless ($size >= 24 and $$dataPt =~ /^SECp/) { + $et->Warn('Bad properties header'); + return 0; + } + my ($entries, $fmt, $len) = unpack('x8V2x4V', $$dataPt); + unless ($size >= 24 + 8 * $entries + $len) { + $et->Warn('Truncated Property directory'); + return 0; + } + $verbose and $et->VerboseDir('Properties', $entries); + $fmt == 0 or $et->Warn("Unsupported character format $fmt"), return 0; + my $charPos = 24 + 8 * $entries; + my @chars = unpack('v*',substr($$dataPt, $charPos, $len * 2)); + my $index; + for ($index=0; $index<$entries; ++$index) { + my ($namePos, $valPos) = unpack('V2',substr($$dataPt, $index*8 + 24, 8)); + if ($namePos >= @chars or $valPos >= @chars) { + $et->Warn('Bad Property pointer'); + return 0; + } + my $tag = ExtractUnicodeString($et, \@chars, $namePos); + my $val = ExtractUnicodeString($et, \@chars, $valPos); + if (not $$tagTablePtr{$tag} and $unknown and $tag =~ /^\w+$/) { + my $tagInfo = { + Name => "SigmaRaw_$tag", + Description => Image::ExifTool::MakeDescription('SigmaRaw', $tag), + Unknown => 1, + Writable => 0, # can't write unknown tags + }; + # add tag information to table + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + + $et->HandleTag($tagTablePtr, $tag, $val, + Index => $index, + DataPt => $dataPt, + Start => $charPos + 2 * $valPos, + Size => 2 * (length($val) + 1), + ); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Write an X3F file +# Inputs: 0) ExifTool ref, 1) DirInfo ref (DirStart = directory offset) +# Returns: error string, undef on success, or -1 on write error +# Notes: Writes metadata to embedded JpgFromRaw image +sub WriteX3F($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my ($hdr, $buff, $ver, $entries, $dir, $outPos, $index, $didContain, %order, @order); + + $raf->Seek($$dirInfo{DirStart}, 0) or return 'Error seeking to directory start'; + + # read the X3F directory header (will be copied directly to output) + $raf->Read($hdr, 12) == 12 or return 'Truncated X3F image'; + $hdr =~ /^SECd/ or return 'Bad section header'; + ($ver, $entries) = unpack('x4V2', $hdr); + + # do sanity check on number of entries in directory + return 'Invalid X3F directory count' unless $entries > 2 and $entries < 20; + # read the directory entries + unless ($raf->Read($dir, $entries * 12) == $entries * 12) { + return 'Truncated X3F directory'; + } + # do a quick scan to determine the offset of the first data subsection, + # and the order in which the actual data is stored in the file + for ($index=0; $index<$entries; ++$index) { + my $pos = $index * 12; + my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir); + # remember position of first data subsection + $outPos = $offset if not defined $outPos or $outPos > $offset; + # save the order of the data + $order{BAD} = 1 if defined $order{$offset}; + $order{$offset} = $index; + } + # copy the file header up to the start of the first data subsection + unless ($raf->Seek(0,0) and $raf->Read($buff, $outPos) == $outPos) { + return 'Error reading X3F header'; + } + Write($outfile, $buff) or return -1; + + # this is a bit tricky/unfortunate: the current version of Sigma Photo Pro + # (2022-10-18) is sensitive to the order of the data sections, and these may + # differ from the order of their respective entries in the footer. To patch + # this, instead of looping through the footer sections in order, we process + # them in the order of the offsets they contain, writing their referenced data + # sequentially as we go. This preserves both the order of the data sections + # and the order of the footer entries. (Note that the upcoming release of + # Sigma Photo Pro will fix this issue at their end, but this patch will remain + # to maintain backward compatibilty with older SPP versions.) + if ($order{BAD}) { + # (this could perhaps happen if any of the sections is ever zero-length) + $et->Error('Double-referenced data in footer directory!', 1); + @order = ( 0 .. $entries-1 ); + } else { + @order = map $order{$_}, sort { $a <=> $b } keys %order; + } + + # loop through footer directory, rewriting each section + foreach $index (@order) { + + my $pos = $index * 12; + my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir); + $raf->Seek($offset, 0) or return 'Bad data offset'; + + if ($tag eq 'IMA2' and $len > 28) { + # check subsection header (28 bytes) to see if this is a JPEG preview image + $raf->Read($buff, 28) == 28 or return 'Error reading PreviewImage header'; + Write($outfile, $buff) or return -1; + $len -= 28; + + # only rewrite full-sized JpgFromRaw (version 2.0, type 2, format 18) + if ($buff =~ /^SECi\0\0\x02\0\x02\0\0\0\x12\0\0\0/) { + $raf->Read($buff, $len) == $len or return 'Error reading JpgFromRaw'; + if ($buff =~ /^\xff\xd8\xff\xe1/) { # does this preview contain EXIF? + # use same write directories as JPEG + $et->InitWriteDirs('JPEG'); + # make sure we don't add APP0 JFIF because it would mess up our preview identification + delete $$et{ADD_DIRS}{APP0}; + delete $$et{ADD_DIRS}{JFIF}; + # rewrite the embedded JPEG in memory + my $newData; + my %jpegInfo = ( + Parent => 'X3F', + RAF => new File::RandomAccess(\$buff), + OutFile => \$newData, + ); + $$et{FILE_TYPE} = 'JPEG'; + my $success = $et->WriteJPEG(\%jpegInfo); + $$et{FILE_TYPE} = 'X3F'; + SetByteOrder('II'); + return 'Error writing X3F JpgFromRaw' unless $success and $newData; + return -1 if $success < 0; + # (this shouldn't happen unless someone tries to delete the EXIF...) + return 'EXIF segment must come first in X3F JpgFromRaw' unless $newData =~ /^\xff\xd8\xff\xe1/; + # trim off any extra null bytes (since section length includes padding -- silly Sigma) + $newData =~ s/\0+$//; + # write new data if anything changed, otherwise copy old image + my $outPt = $$et{CHANGED} ? \$newData : \$buff; + Write($outfile, $$outPt) or return -1; + # set $len to the total subsection data length + $len = length($$outPt); + $didContain = 1; + } else { + Write($outfile, $buff) or return -1; + } + } else { + # copy original image data + Image::ExifTool::CopyBlock($raf, $outfile, $len) or return 'Corrupted X3F image'; + } + $len += 28; # add back header length + } else { + # copy data for this subsection + Image::ExifTool::CopyBlock($raf, $outfile, $len) or return 'Corrupted X3F directory'; + } + # pad data to an even 4-byte boundary + # (stored length includes padding! ref Sigma engineer Yuki Miyahara) + if ($len & 0x03) { + my $pad = 4 - ($len & 0x03); + Write($outfile, "\0" x $pad) or return -1; + $len += $pad; + } + # update footer entry with new offset/size + substr($dir, $pos, 8) = pack('V2', $outPos, $len); + $outPos += $len; + } + # warn if we couldn't add metadata to this image (should only be SD9 or SD10) + $didContain or $et->Warn("Can't yet write SD9 or SD10 X3F images"); + # write out the directory and the directory pointer, and we are done + Write($outfile, $hdr, $dir, pack('V', $outPos)) or return -1; + return undef; +} + +#------------------------------------------------------------------------------ +# Process an X3F directory +# Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref +# Returns: error string or undef on success +sub ProcessX3FDirectory($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + + $raf->Seek($$dirInfo{DirStart}, 0) or return 'Error seeking to directory start'; + + # parse the X3F directory structure + my ($buff, $ver, $entries, $index, $dir); + $raf->Read($buff, 12) == 12 or return 'Truncated X3F image'; + $buff =~ /^SECd/ or return 'Bad section header'; + ($ver, $entries) = unpack('x4V2', $buff); + $verbose and $et->VerboseDir('X3F Subsection', $entries); + $raf->Read($dir, $entries * 12) == $entries * 12 or return 'Truncated X3F directory'; + for ($index=0; $index<$entries; ++$index) { + my $pos = $index * 12; + my ($offset, $len, $tag) = unpack("x${pos}V2a4", $dir); + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($verbose) { + $et->VPrint(0, "$$et{INDENT}$index) $tag Subsection ($len bytes):\n"); + if ($verbose > 2) { + $raf->Seek($offset, 0) or return 'Error seeking'; + $raf->Read($buff, $len) == $len or return 'Truncated image'; + $et->VerboseDump(\$buff); + } + } + next unless $tagInfo; + $raf->Seek($offset, 0) or return "Error seeking for $$tagInfo{Name}"; + if ($$tagInfo{Name} eq 'PreviewImage') { + # check image header to see if this is a JPEG preview image + $raf->Read($buff, 28) == 28 or return 'Error reading PreviewImage header'; + $offset += 28; + $len -= 28; + # ignore all image data but JPEG compressed (version 2.0, type 2, format 18) + unless ($buff =~ /^SECi\0\0\x02\0\x02\0\0\0\x12\0\0\0/) { + # do hash on non-preview data if requested + if ($$et{ImageDataHash} and substr($buff,8,1) ne "\x02") { + $et->ImageDataHash($raf, $len, 'SigmaRaw IMAG'); + } + next; + } + $raf->Read($buff, $len) == $len or return "Error reading PreviewImage data"; + # check fore EXIF segment, and extract this image as the JpgFromRaw + if ($buff =~ /^\xff\xd8\xff\xe1/) { + $$et{IsJpgFromRaw} = 1; + $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + delete $$et{IsJpgFromRaw}; + } + } else { + $raf->Read($buff, $len) == $len or return "Error reading $$tagInfo{Name} data"; + } + my $subdir = $$tagInfo{SubDirectory}; + if ($subdir) { + my %dirInfo = ( DataPt => \$buff ); + my $subTable = GetTagTable($$subdir{TagTable}); + $et->ProcessDirectory(\%dirInfo, $subTable); + } else { + # extract metadata from JpgFromRaw + if ($$tagInfo{Name} eq 'JpgFromRaw') { + my %dirInfo = ( + Parent => 'X3F', + RAF => new File::RandomAccess(\$buff), + ); + $$et{BASE} += $offset; + $et->ProcessJPEG(\%dirInfo); + $$et{BASE} -= $offset; + SetByteOrder('II'); + } + $et->FoundTag($tagInfo, $buff); + } + } + return undef; +} + +#------------------------------------------------------------------------------ +# Read/write information from a Sigma raw (X3F) image +# Inputs: 0) ExifTool ref, 1) DirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid X3F image, or -1 on write error +sub ProcessX3F($$) +{ + my ($et, $dirInfo) = @_; + my $outfile = $$dirInfo{OutFile}; + my $raf = $$dirInfo{RAF}; + my $warn = $outfile ? \&Image::ExifTool::Error : \&Image::ExifTool::Warn; + my ($buff, $err, $hdrLen); + + return 0 unless $raf->Read($buff, 40) == 40; + return 0 unless $buff =~ /^FOVb/; + + SetByteOrder('II'); + $et->SetFileType(); + + # check version number + my $ver = unpack('x4V',$buff); + $ver = ($ver >> 16) . '.' . ($ver & 0xffff); + if ($ver > 5) { + &$warn($et, "Untested X3F version ($ver). Please submit sample for testing", 1); + } + # read version 2.1/2.2/2.3 extended header + if ($ver > 2) { + my ($extra, $buf2); + if ($ver >= 4) { + $hdrLen = 0x300; + $extra = 0; + } else { + $hdrLen = $ver > 2.2 ? 104 : 72; # SceneCaptureType string added in 2.3 + $extra = 160; # (extended header is 160 bytes) + } + my $more = $hdrLen - length($buff) + $extra; + unless ($raf->Read($buf2, $more) == $more) { + &$warn($et, 'Error reading X3F header'); + return 1; + } + $buff .= $buf2; + } + my ($widPos, $hdrType) = $ver < 4 ? (28, 'Header') : (40, 'Header4'); + # process header information + my $tagTablePtr = GetTagTable('Image::ExifTool::SigmaRaw::Main'); + unless ($outfile) { + $et->HandleTag($tagTablePtr, $hdrType, $buff, + DataPt => \$buff, + Size => $hdrLen, + ); + } + # read the directory pointer + $raf->Seek(-4, 2) or &$warn($et, 'Seek error'), return 1; + unless ($raf->Read($buff, 4) == 4) { + &$warn($et, 'Error reading X3F dir pointer'); + return 1; + } + my $offset = unpack('V', $buff); + my %dirInfo = ( + RAF => $raf, + DirStart => $offset, + ); + if ($outfile) { + $dirInfo{OutFile} = $outfile; + $err = WriteX3F($et, \%dirInfo); + return -1 if $err and $err eq '-1'; + } else { + # process the X3F subsections + $err = $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + } + $err and &$warn($et, $err); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::SigmaRaw - Read Sigma/Foveon RAW (X3F) meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +Sigma and Foveon X3F images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.x3f.info/technotes/FileDocs/X3F_Format.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/SigmaRaw Tags>, +L<Image::ExifTool::Sigma(3pm)|Image::ExifTool::Sigma>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Sony.pm b/ExifTool/lib/Image/ExifTool/Sony.pm new file mode 100644 index 0000000..c4e25f0 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Sony.pm @@ -0,0 +1,11583 @@ +#------------------------------------------------------------------------------ +# File: Sony.pm +# +# Description: Sony EXIF Maker Notes tags +# +# Revisions: 04/06/2004 - P. Harvey Created +# +# References: 1) http://www.cybercom.net/~dcoffin/dcraw/ +# 2) http://homepage3.nifty.com/kamisaka/makernote/makernote_sony.htm (2006/08/06) +# 3) Thomas Bodenmann private communication +# 4) Philippe Devaux private communication (A700) +# 5) Marcus Holland-Moritz private communication (A700) +# 6) Andrey Tverdokhleb private communication +# 7) Rudiger Lange private communication (A700) +# 8) Igal Milchtaich private communication +# 9) Michael Reitinger private communication (DSC-TX7,RX100) +# 10) http://www.klingebiel.com/tempest/hd/pmp.html +# 11) Mike Battilana private communication +# 13) http://www.mi-fo.de/forum/index.php?showtopic=33239 +# http://www.dyxum.com/dforum/the-alpha-shutter-count-tool_topic97489_page4.html +# 14) Albert Shan private communication (A7M3) +# IB) Iliah Borg private communication (LibRaw) +# JD) Jens Duttke private communication +# JR) Jos Roost private communication +# +# NC = Not Confirmed +#------------------------------------------------------------------------------ + +package Image::ExifTool::Sony; + +use strict; +use vars qw($VERSION %sonyLensTypes %sonyLensTypes2); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::Minolta; + +$VERSION = '3.62'; + +sub ProcessSRF($$$); +sub ProcessSR2($$$); +sub ProcessSonyPIC($$$); +sub ProcessMoreInfo($$$); +sub Process_rtmd($$$); +sub Decipher($;$); +sub ProcessEnciphered($$$); +sub WriteEnciphered($$$); +sub WriteSR2($$$); +sub ConvLensSpec($); +sub ConvInvLensSpec($); +sub PrintLensSpec($); +sub PrintInvLensSpec($;$$); + +# (%sonyLensTypes is filled in based on Minolta LensType's) + +# Sony E-mount lenses +# (NOTE: these should be kept in sync with the 65535 entries in %minoltaLensTypes) +%sonyLensTypes2 = ( + Notes => 'Lens type numbers for Sony E-mount lenses used by NEX/ILCE models.', + 0 => 'Unknown E-mount lens or other lens', + 0.1 => 'Sigma 19mm F2.8 [EX] DN', + 0.2 => 'Sigma 30mm F2.8 [EX] DN', + 0.3 => 'Sigma 60mm F2.8 DN', + 0.4 => 'Sony E 18-200mm F3.5-6.3 OSS LE', # (firmware Ver.01) + 0.5 => 'Tamron 18-200mm F3.5-6.3 Di III VC', # (Model B011) + 0.6 => 'Tokina FiRIN 20mm F2 FE AF', # (firmware Ver.00) samples from Tokina, May 2018 + 0.7 => 'Tokina FiRIN 20mm F2 FE MF', # samples from Tokina, 16-12-2016, DC-watch 01-02-2017 + 0.8 => 'Zeiss Touit 12mm F2.8', # (firmware Ver.00) + 0.9 => 'Zeiss Touit 32mm F1.8', # (firmware Ver.00) + '0.10' => 'Zeiss Touit 50mm F2.8 Macro', # (firmware Ver.00) + '0.11' => 'Zeiss Loxia 50mm F2', # (firmware Ver.01) + '0.12' => 'Zeiss Loxia 35mm F2', # (firmware Ver.01) + 1 => 'Sony LA-EA1 or Sigma MC-11 Adapter', # MC-11 with not-supported lenses + 2 => 'Sony LA-EA2 Adapter', + 3 => 'Sony LA-EA3 Adapter', + 6 => 'Sony LA-EA4 Adapter', + 7 => 'Sony LA-EA5 Adapter', #JR + # 27 => Venus Optics Laowa 12mm f2.8 Zero-D or 105mm f2 (T3.2) Smooth Trans Focus (ref IB) + 44 => 'Metabones Canon EF Smart Adapter', #JR + 78 => 'Metabones Canon EF Smart Adapter Mark III or Other Adapter', #PH/JR (also Mark IV, Fotodiox and Viltrox) + 184 => 'Metabones Canon EF Speed Booster Ultra', #JR ('Green' mode, LensMount reported as A-mount) + 234 => 'Metabones Canon EF Smart Adapter Mark IV', #JR (LensMount reported as A-mount) + 239 => 'Metabones Canon EF Speed Booster', #JR + 24593 => 'LA-EA4r MonsterAdapter', + # Sony VX product code: (http://www.mi-fo.de/forum/index.php?s=7df1c8d3b1cd675f2abf4f4442e19cf2&showtopic=35035&view=findpost&p=303746) + 32784 => 'Sony E 16mm F2.8', # VX9100 + 32785 => 'Sony E 18-55mm F3.5-5.6 OSS', # VX9101 + 32786 => 'Sony E 55-210mm F4.5-6.3 OSS', # VX9102 + 32787 => 'Sony E 18-200mm F3.5-6.3 OSS', # VX9103 + 32788 => 'Sony E 30mm F3.5 Macro', # VX9104 + 32789 => 'Sony E 24mm F1.8 ZA or Samyang AF 50mm F1.4', # VX9105 + 32789.1 => 'Samyang AF 50mm F1.4', + 32790 => 'Sony E 50mm F1.8 OSS or Samyang AF 14mm F2.8', # VX9106 + 32790.1 => 'Samyang AF 14mm F2.8', + 32791 => 'Sony E 16-70mm F4 ZA OSS', # VX9107 + 32792 => 'Sony E 10-18mm F4 OSS', # VX9108 + 32793 => 'Sony E PZ 16-50mm F3.5-5.6 OSS', # VX9109 + 32794 => 'Sony FE 35mm F2.8 ZA or Samyang Lens', # VX9110 + 32794.1 => 'Samyang AF 24mm F2.8', #JR + 32794.2 => 'Samyang AF 35mm F2.8', #IB (also 51505) + 32795 => 'Sony FE 24-70mm F4 ZA OSS', # VX9111 + 32796 => 'Sony FE 85mm F1.8 or Viltrox PFU RBMH 85mm F1.8', #JR + 32796.1 => 'Viltrox PFU RBMH 85mm F1.8', #JR (MF) + 32797 => 'Sony E 18-200mm F3.5-6.3 OSS LE', # VX9113 (firmware Ver.02) + 32798 => 'Sony E 20mm F2.8', # VX9114 + 32799 => 'Sony E 35mm F1.8 OSS', # VX9115 + 32800 => 'Sony E PZ 18-105mm F4 G OSS', #JR # VX9116 + 32801 => 'Sony FE 12-24mm F4 G', #JR + 32802 => 'Sony FE 90mm F2.8 Macro G OSS', # VX? + 32803 => 'Sony E 18-50mm F4-5.6', + 32804 => 'Sony FE 24mm F1.4 GM', #IB + 32805 => 'Sony FE 24-105mm F4 G OSS', #IB # VX9121 + + 32807 => 'Sony E PZ 18-200mm F3.5-6.3 OSS', # VX9123 + 32808 => 'Sony FE 55mm F1.8 ZA', # VX9124 + + 32810 => 'Sony FE 70-200mm F4 G OSS', #JR # VX9126 + 32811 => 'Sony FE 16-35mm F4 ZA OSS', #JR # VX9127 + 32812 => 'Sony FE 50mm F2.8 Macro', #JR + 32813 => 'Sony FE 28-70mm F3.5-5.6 OSS', # VX9129 + 32814 => 'Sony FE 35mm F1.4 ZA', # VX? + 32815 => 'Sony FE 24-240mm F3.5-6.3 OSS', # VX? + 32816 => 'Sony FE 28mm F2', #JR # VX? + 32817 => 'Sony FE PZ 28-135mm F4 G OSS',#JR # VX? + + 32819 => 'Sony FE 100mm F2.8 STF GM OSS', #JR (appears to use 33076 when switched to macro mode) + 32820 => 'Sony E PZ 18-110mm F4 G OSS', #JR + 32821 => 'Sony FE 24-70mm F2.8 GM', #JR/IB + 32822 => 'Sony FE 50mm F1.4 ZA', #JR + 32823 => 'Sony FE 85mm F1.4 GM or Samyang AF 85mm F1.4', #JR/IB + 32823.1 => 'Samyang AF 85mm F1.4', #IB + 32824 => 'Sony FE 50mm F1.8', #JR (Sony code 'SEL50F18F' with trailing "F" as compared to 'SEL50F18' for 32790) + + 32826 => 'Sony FE 21mm F2.8 (SEL28F20 + SEL075UWC)', #JR # (+ Ultra-wide converter) + 32827 => 'Sony FE 16mm F3.5 Fisheye (SEL28F20 + SEL057FEC)', #JR # (+ Fisheye converter) + 32828 => 'Sony FE 70-300mm F4.5-5.6 G OSS', #JR + 32829 => 'Sony FE 100-400mm F4.5-5.6 GM OSS', #JR + 32830 => 'Sony FE 70-200mm F2.8 GM OSS', #JR + 32831 => 'Sony FE 16-35mm F2.8 GM', #JR + 32848 => 'Sony FE 400mm F2.8 GM OSS', #IB + 32849 => 'Sony E 18-135mm F3.5-5.6 OSS', #JR + 32850 => 'Sony FE 135mm F1.8 GM', #IB + 32851 => 'Sony FE 200-600mm F5.6-6.3 G OSS', #IB + 32852 => 'Sony FE 600mm F4 GM OSS', #IB + 32853 => 'Sony E 16-55mm F2.8 G', #IB/JR + 32854 => 'Sony E 70-350mm F4.5-6.3 G OSS', #IB/JR + 32855 => 'Sony FE C 16-35mm T3.1 G', #JR (SELC1635G) (max aperture is 2.8) + 32858 => 'Sony FE 35mm F1.8', #JR/IB + 32859 => 'Sony FE 20mm F1.8 G', #IB/JR + 32860 => 'Sony FE 12-24mm F2.8 GM', #JR/IB + 32862 => 'Sony FE 50mm F1.2 GM', #IB/JR + 32863 => 'Sony FE 14mm F1.8 GM', #IB + 32864 => 'Sony FE 28-60mm F4-5.6', #JR + 32865 => 'Sony FE 35mm F1.4 GM', #IB/JR + 32866 => 'Sony FE 24mm F2.8 G', #IB + 32867 => 'Sony FE 40mm F2.5 G', #IB + 32868 => 'Sony FE 50mm F2.5 G', #IB + 32871 => 'Sony FE PZ 16-35mm F4 G', #JR + 32873 => 'Sony E PZ 10-20mm F4 G', #JR + 32874 => 'Sony FE 70-200mm F2.8 GM OSS II', #IB + 32875 => 'Sony FE 24-70mm F2.8 GM II', #JR + 32876 => 'Sony E 11mm F1.8', #JR + 32877 => 'Sony E 15mm F1.4 G', #JR + 32878 => 'Sony FE 20-70mm F4 G', #JR + 32879 => 'Sony FE 50mm F1.4 GM', #JR + 32884 => 'Sony FE 70-200mm F4 Macro G OSS II', #JR + + # (comment this out so LensID will report the LensModel, which is more useful) + # 32952 => 'Metabones Canon EF Speed Booster Ultra', #JR (corresponds to 184, but 'Advanced' mode, LensMount reported as E-mount) + # 33002 => 'Metabones Canon EF Smart Adapter with Ver.5x', #PH/JR (corresponds to 234, but LensMount reported as E-mount) + + 33072 => 'Sony FE 70-200mm F2.8 GM OSS + 1.4X Teleconverter', #JR + 33073 => 'Sony FE 70-200mm F2.8 GM OSS + 2X Teleconverter', #JR + 33076 => 'Sony FE 100mm F2.8 STF GM OSS (macro mode)', #JR (with macro switching ring set to "0.57m - 1.0m") + 33077 => 'Sony FE 100-400mm F4.5-5.6 GM OSS + 1.4X Teleconverter', #JR + 33078 => 'Sony FE 100-400mm F4.5-5.6 GM OSS + 2X Teleconverter', #JR + 33079 => 'Sony FE 400mm F2.8 GM OSS + 1.4X Teleconverter', #IB + 33080 => 'Sony FE 400mm F2.8 GM OSS + 2X Teleconverter', #JR + 33081 => 'Sony FE 200-600mm F5.6-6.3 G OSS + 1.4X Teleconverter', #JR + 33082 => 'Sony FE 200-600mm F5.6-6.3 G OSS + 2X Teleconverter', #JR + 33083 => 'Sony FE 600mm F4 GM OSS + 1.4X Teleconverter', #JR (NC) + 33084 => 'Sony FE 600mm F4 GM OSS + 2X Teleconverter', #JR + 33085 => 'Sony FE 70-200mm F2.8 GM OSS II + 1.4X Teleconverter', #JR + 33086 => 'Sony FE 70-200mm F2.8 GM OSS II + 2X Teleconverter', #JR + 33087 => 'Sony FE 70-200mm F4 Macro G OSS II + 1.4X Teleconverter', #JR + 33088 => 'Sony FE 70-200mm F4 Macro G OSS II + 2X Teleconverter', #JR + + 49201 => 'Zeiss Touit 12mm F2.8', #JR (lens firmware Ver.02) + 49202 => 'Zeiss Touit 32mm F1.8', #JR (lens firmware Ver.02) + 49203 => 'Zeiss Touit 50mm F2.8 Macro', #JR (lens firmware Ver.02) + 49216 => 'Zeiss Batis 25mm F2', #JR + 49217 => 'Zeiss Batis 85mm F1.8', #JR + 49218 => 'Zeiss Batis 18mm F2.8', #IB + 49219 => 'Zeiss Batis 135mm F2.8', #IB + 49220 => 'Zeiss Batis 40mm F2 CF', #IB + 49232 => 'Zeiss Loxia 50mm F2', #JR (lens firmware Ver.02) + 49233 => 'Zeiss Loxia 35mm F2', #JR (lens firmware Ver.02) + 49234 => 'Zeiss Loxia 21mm F2.8', #PH + 49235 => 'Zeiss Loxia 85mm F2.4', #JR + 49236 => 'Zeiss Loxia 25mm F2.4', #JR + + 49456 => 'Tamron E 18-200mm F3.5-6.3 Di III VC', #FrancoisPiette + 49457 => 'Tamron 28-75mm F2.8 Di III RXD', #JR (Model A036) + 49458 => 'Tamron 17-28mm F2.8 Di III RXD', #JR (Model A046) + 49459 => 'Tamron 35mm F2.8 Di III OSD M1:2', #IB (Model F053) + 49460 => 'Tamron 24mm F2.8 Di III OSD M1:2', #JR (Model F051) + 49461 => 'Tamron 20mm F2.8 Di III OSD M1:2', #JR (Model F050) + 49462 => 'Tamron 70-180mm F2.8 Di III VXD', #JR (Model A056) + 49463 => 'Tamron 28-200mm F2.8-5.6 Di III RXD', #JR (Model A071) + 49464 => 'Tamron 70-300mm F4.5-6.3 Di III RXD', #JR (Model A047) + 49465 => 'Tamron 17-70mm F2.8 Di III-A VC RXD', #JR (Model B070) + 49466 => 'Tamron 150-500mm F5-6.7 Di III VC VXD', #JR (Model A057) + 49467 => 'Tamron 11-20mm F2.8 Di III-A RXD', #JR (Model B060) + 49468 => 'Tamron 18-300mm F3.5-6.3 Di III-A VC VXD', #JR (Model B061) + 49469 => 'Tamron 35-150mm F2-F2.8 Di III VXD', #JR (Model A058) + 49470 => 'Tamron 28-75mm F2.8 Di III VXD G2', #JR (Model A063) + 49471 => 'Tamron 50-400mm F4.5-6.3 Di III VC VXD', #JR (Model A067) + 49472 => 'Tamron 20-40mm F2.8 Di III VXD', #JR (Model A062) + + 49473 => 'Tokina atx-m 85mm F1.8 FE or Viltrox lens', #JR + 49473.1 => 'Viltrox 23mm F1.4 E', #JR + 49473.2 => 'Viltrox 56mm F1.4 E', #JR + 49712 => 'Tokina FiRIN 20mm F2 FE AF', # (firmware Ver.01) + 49713 => 'Tokina FiRIN 100mm F2.8 FE MACRO', # (firmware Ver.01) + 49714 => 'Tokina atx-m 11-18mm F2.8 E', #JR + + 50480 => 'Sigma 30mm F1.4 DC DN | C', #IB/JR (016) + 50481 => 'Sigma 50mm F1.4 DG HSM | A', #JR (014 + MC-11 or 018) + 50482 => 'Sigma 18-300mm F3.5-6.3 DC MACRO OS HSM | C + MC-11', #JR (014) + 50483 => 'Sigma 18-35mm F1.8 DC HSM | A + MC-11', #JR (013) + 50484 => 'Sigma 24-35mm F2 DG HSM | A + MC-11', #JR (015) + 50485 => 'Sigma 24mm F1.4 DG HSM | A + MC-11', #JR (015) + 50486 => 'Sigma 150-600mm F5-6.3 DG OS HSM | C + MC-11', #JR (015) + 50487 => 'Sigma 20mm F1.4 DG HSM | A + MC-11', #JR (015) + 50488 => 'Sigma 35mm F1.4 DG HSM | A', #JR (012 + MC-11 or 018) + 50489 => 'Sigma 150-600mm F5-6.3 DG OS HSM | S + MC-11', #JR (014) + 50490 => 'Sigma 120-300mm F2.8 DG OS HSM | S + MC-11', #JR (013) + 50492 => 'Sigma 24-105mm F4 DG OS HSM | A + MC-11', #JR (013) + 50493 => 'Sigma 17-70mm F2.8-4 DC MACRO OS HSM | C + MC-11', #JR (013) + 50495 => 'Sigma 50-100mm F1.8 DC HSM | A + MC-11', #JR (016) + 50499 => 'Sigma 85mm F1.4 DG HSM | A', #JR (018) + 50501 => 'Sigma 100-400mm F5-6.3 DG OS HSM | C + MC-11', #JR (017) + 50503 => 'Sigma 16mm F1.4 DC DN | C', #JR (017) + 50507 => 'Sigma 105mm F1.4 DG HSM | A', #IB (018) + 50508 => 'Sigma 56mm F1.4 DC DN | C', #JR (018) + 50512 => 'Sigma 70-200mm F2.8 DG OS HSM | S + MC-11', #IB (018) (JR added "+ MC-11") + 50513 => 'Sigma 70mm F2.8 DG MACRO | A', #JR (018) + 50514 => 'Sigma 45mm F2.8 DG DN | C', #IB/JR (019) + 50515 => 'Sigma 35mm F1.2 DG DN | A', #IB/JR (019) + 50516 => 'Sigma 14-24mm F2.8 DG DN | A', #IB/JR (019) + 50517 => 'Sigma 24-70mm F2.8 DG DN | A', #JR (019) + 50518 => 'Sigma 100-400mm F5-6.3 DG DN OS | C', #JR (020) + 50521 => 'Sigma 85mm F1.4 DG DN | A', #JR (020) + 50522 => 'Sigma 105mm F2.8 DG DN MACRO | A', #JR (020) + 50523 => 'Sigma 65mm F2 DG DN | C', #IB (020) + 50524 => 'Sigma 35mm F2 DG DN | C', #IB (020) + 50525 => 'Sigma 24mm F3.5 DG DN | C', #JR (021) + 50526 => 'Sigma 28-70mm F2.8 DG DN | C', #JR (021) + 50527 => 'Sigma 150-600mm F5-6.3 DG DN OS | S', #JR (021) + 50528 => 'Sigma 35mm F1.4 DG DN | A', #IB/JR (021) + 50529 => 'Sigma 90mm F2.8 DG DN | C', #JR (021) + 50530 => 'Sigma 24mm F2 DG DN | C', #JR (021) + 50531 => 'Sigma 18-50mm F2.8 DC DN | C', #IB/JR (021) + 50532 => 'Sigma 20mm F2 DG DN | C', #JR (022) + 50533 => 'Sigma 16-28mm F2.8 DG DN | C', #JR (022) + 50534 => 'Sigma 20mm F1.4 DG DN | A', #JR (022) + 50535 => 'Sigma 24mm F1.4 DG DN | A', #JR (022) + 50536 => 'Sigma 60-600mm F4.5-6.3 DG DN OS | S', #JR (023) + 50537 => 'Sigma 50mm F2 DG DN | C', #JR (023) + 50538 => 'Sigma 17mm F4 DG DN | C', #JR (023) + 50539 => 'Sigma 50mm F1.4 DG DN | A', #JR (023) + 50540 => 'Sigma 14mm F1.4 DG DN | A', #JR (023) + 50544 => 'Sigma 23mm F1.4 DC DN | C', #JR (023) + + 50992 => 'Voigtlander SUPER WIDE-HELIAR 15mm F4.5 III', #JR + 50993 => 'Voigtlander HELIAR-HYPER WIDE 10mm F5.6', #IB + 50994 => 'Voigtlander ULTRA WIDE-HELIAR 12mm F5.6 III', #IB + 50995 => 'Voigtlander MACRO APO-LANTHAR 65mm F2 Aspherical', #JR + 50996 => 'Voigtlander NOKTON 40mm F1.2 Aspherical', #JR + 50997 => 'Voigtlander NOKTON classic 35mm F1.4', #JR + 50998 => 'Voigtlander MACRO APO-LANTHAR 110mm F2.5', #JR + 50999 => 'Voigtlander COLOR-SKOPAR 21mm F3.5 Aspherical', #IB + 51000 => 'Voigtlander NOKTON 50mm F1.2 Aspherical', #JR + 51001 => 'Voigtlander NOKTON 21mm F1.4 Aspherical', #JR + 51002 => 'Voigtlander APO-LANTHAR 50mm F2 Aspherical', #JR + 51003 => 'Voigtlander NOKTON 35mm F1.2 Aspherical SE', #JR + 51006 => 'Voigtlander APO-LANTHAR 35mm F2 Aspherical', #JR + + # lenses listed in the Sigma MC-11 list, but not yet seen: + # 504xx => 'Sigma 18-200mm F3.5-6.3 DC MACRO OS HSM | C + MC-11', # (014) + # 504xx => 'Sigma 30mm F1.4 DC HSM | A + MC-11', # (013) + + # Note: For Samyang lenses, the "FE" designation isn't written to + # EXIF:LensModel, so it isn't included in these strings either - JR/PH + 51504 => 'Samyang AF 50mm F1.4', #IB + 51505 => 'Samyang AF 14mm F2.8 or Samyang AF 35mm F2.8', #forum3833 + 51505.1 => 'Samyang AF 35mm F2.8', #PH (also 32794) + 51507 => 'Samyang AF 35mm F1.4', #IB + 51508 => 'Samyang AF 45mm F1.8', + 51510 => 'Samyang AF 18mm F2.8 or Samyang AF 35mm F1.8', #JR + 51510.1 => 'Samyang AF 35mm F1.8', #JR + 51512 => 'Samyang AF 75mm F1.8', #IB/JR + 51513 => 'Samyang AF 35mm F1.8', #JR + 51514 => 'Samyang AF 24mm F1.8', #IB + 51515 => 'Samyang AF 12mm F2.0', #JR + 51516 => 'Samyang AF 24-70mm F2.8', #JR + 51517 => 'Samyang AF 50mm F1.4 II', #JR + 51518 => 'Samyang AF 135mm F1.8', #JR +); + +# ExposureProgram values (ref PH, mainly decoded from A200) +my %sonyExposureProgram = ( + 0 => 'Auto', # (same as 'Program AE'?) + 1 => 'Manual', + 2 => 'Program AE', + 3 => 'Aperture-priority AE', + 4 => 'Shutter speed priority AE', + 8 => 'Program Shift A', #7 + 9 => 'Program Shift S', #7 + 16 => 'Portrait', # (A330) + 17 => 'Sports', # (A330) + 18 => 'Sunset', # (A330) + 19 => 'Night Portrait', # (A330) + 20 => 'Landscape', # (A330) + 21 => 'Macro', # (A330) + 35 => 'Auto No Flash', # (A330) +); + +# ExposureProgram values in CameraSettings3 (ref JR) +my %sonyExposureProgram2 = ( # A580 Mode Dial setting: + 1 => 'Program AE', # P + 2 => 'Aperture-priority AE', # A + 3 => 'Shutter speed priority AE', # S + 4 => 'Manual', # M + 5 => 'Cont. Priority AE', # (A35) + 16 => 'Auto', # AUTO + 17 => 'Auto (no flash)', # "flash strike-out" symbol + 18 => 'Auto+', #PH (A33) + 49 => 'Portrait', # SCN + 50 => 'Landscape', # SCN + 51 => 'Macro', # SCN + 52 => 'Sports', # SCN + 53 => 'Sunset', # SCN + 54 => 'Night view', # SCN + 55 => 'Night view/portrait', # SCN + 56 => 'Handheld Night Shot', # SCN (also called "Hand-held Twilight") + 57 => '3D Sweep Panorama', # "Panorama" symbol + 64 => 'Auto 2', #PH (A33 AUTO) + 65 => 'Auto 2 (no flash)', #JR (NC, A35) + 80 => 'Sweep Panorama', # "Panorama" symbol + 96 => 'Anti Motion Blur', #PH (NEX-5) + # 128-138 are A35 picture effects (combined SCN/Picture effect mode dial position) + 128 => 'Toy Camera', + 129 => 'Pop Color', + 130 => 'Posterization', + 131 => 'Posterization B/W', + 132 => 'Retro Photo', + 133 => 'High-key', + 134 => 'Partial Color Red', + 135 => 'Partial Color Green', + 136 => 'Partial Color Blue', + 137 => 'Partial Color Yellow', + 138 => 'High Contrast Monochrome', +); + +# ExposureProgram values in Tags 2010 and 94xx (ref JR) +my %sonyExposureProgram3 = ( + 0 => 'Program AE', + 1 => 'Aperture-priority AE', + 2 => 'Shutter speed priority AE', + 3 => 'Manual', + 4 => 'Auto', + 5 => 'iAuto', + 6 => 'Superior Auto', + 7 => 'iAuto+', + 8 => 'Portrait', + 9 => 'Landscape', + 10 => 'Twilight', + 11 => 'Twilight Portrait', + 12 => 'Sunset', + 14 => 'Action (High speed)', #PH (RX100) + 16 => 'Sports', + 17 => 'Handheld Night Shot', + 18 => 'Anti Motion Blur', + 19 => 'High Sensitivity', + 21 => 'Beach', + 22 => 'Snow', + 23 => 'Fireworks', + 26 => 'Underwater', + 27 => 'Gourmet', + 28 => 'Pet', + 29 => 'Macro', + 30 => 'Backlight Correction HDR', + # 32 => 'Night ... ???', # seen for HDR-CX360E + 33 => 'Sweep Panorama', + 36 => 'Background Defocus', + 37 => 'Soft Skin', + 42 => '3D Image', + 43 => 'Cont. Priority AE', + 45 => 'Document', + 46 => 'Party', +); + +# WhiteBalanceSetting values (ref JR) +my %whiteBalanceSetting = ( + 0x10 => 'Auto (-3)', #(NC) + 0x11 => 'Auto (-2)', #(NC) + 0x12 => 'Auto (-1)', #(NC) + 0x13 => 'Auto (0)', + 0x14 => 'Auto (+1)', #(NC) + 0x15 => 'Auto (+2)', #(NC) + 0x16 => 'Auto (+3)', #(NC) + 0x20 => 'Daylight (-3)', + 0x21 => 'Daylight (-2)', #(NC) + 0x22 => 'Daylight (-1)', #(NC) + 0x23 => 'Daylight (0)', + 0x24 => 'Daylight (+1)', + 0x25 => 'Daylight (+2)', + 0x26 => 'Daylight (+3)', + 0x30 => 'Shade (-3)', #(NC) + 0x31 => 'Shade (-2)', #(NC) + 0x32 => 'Shade (-1)', #(NC) + 0x33 => 'Shade (0)', + 0x34 => 'Shade (+1)', #(NC) + 0x35 => 'Shade (+2)', #(NC) + 0x36 => 'Shade (+3)', + 0x40 => 'Cloudy (-3)', #(NC) + 0x41 => 'Cloudy (-2)', #(NC) + 0x42 => 'Cloudy (-1)', #(NC) + 0x43 => 'Cloudy (0)', + 0x44 => 'Cloudy (+1)', #(NC) + 0x45 => 'Cloudy (+2)', #(NC) + 0x46 => 'Cloudy (+3)', #(NC) + 0x50 => 'Tungsten (-3)', #(NC) + 0x51 => 'Tungsten (-2)', #(NC) + 0x52 => 'Tungsten (-1)', #(NC) + 0x53 => 'Tungsten (0)', + 0x54 => 'Tungsten (+1)', #(NC) + 0x55 => 'Tungsten (+2)', #(NC) + 0x56 => 'Tungsten (+3)', #(NC) + 0x60 => 'Fluorescent (-3)', #(NC) + 0x61 => 'Fluorescent (-2)', #(NC) + 0x62 => 'Fluorescent (-1)', #(NC) + 0x63 => 'Fluorescent (0)', + 0x64 => 'Fluorescent (+1)', #(NC) + 0x65 => 'Fluorescent (+2)', #(NC) + 0x66 => 'Fluorescent (+3)', #(NC) + 0x70 => 'Flash (-3)', #(NC) + 0x71 => 'Flash (-2)', #(NC) + 0x72 => 'Flash (-1)', #(NC) + 0x73 => 'Flash (0)', + 0x74 => 'Flash (+1)', #(NC) + 0x75 => 'Flash (+2)', #(NC) + 0x76 => 'Flash (+3)', #(NC) + 0xa3 => 'Custom', + 0xf3 => 'Color Temperature/Color Filter', +); + +# AF points for cameras with 15-point AF (ref JR) +my %afPoint15 = ( + 0 => 'Upper-left', + 1 => 'Left', + 2 => 'Lower-left', + 3 => 'Far Left', + 4 => 'Top (horizontal)', + 5 => 'Near Right', + 6 => 'Center (horizontal)', + 7 => 'Near Left', + 8 => 'Bottom (horizontal)', + 9 => 'Top (vertical)', + 10 => 'Center (vertical)', + 11 => 'Bottom (vertical)', + 12 => 'Far Right', + 13 => 'Upper-right', + 14 => 'Right', + 15 => 'Lower-right', + 16 => 'Upper-middle', + 17 => 'Lower-middle', +); + +# AF points for cameras with 19-point AF (ref PH) +# (verified for A77 firmware 1.07) +my %afPoint19 = ( + 0 => 'Upper Far Left', + 1 => 'Upper-left (horizontal)', + 2 => 'Far Left (horizontal)', + 3 => 'Left (horizontal)', + 4 => 'Lower Far Left', + 5 => 'Lower-left (horizontal)', + 6 => 'Upper-left (vertical)', + 7 => 'Left (vertical)', + 8 => 'Lower-left (vertical)', + 9 => 'Far Left (vertical)', + 10 => 'Top (horizontal)', + 11 => 'Near Right', + 12 => 'Center (horizontal)', + 13 => 'Near Left', + 14 => 'Bottom (horizontal)', + 15 => 'Top (vertical)', + 16 => 'Upper-middle', + 17 => 'Center (vertical)', + 18 => 'Lower-middle', + 19 => 'Bottom (vertical)', + 20 => 'Upper Far Right', + 21 => 'Upper-right (horizontal)', + 22 => 'Far Right (horizontal)', + 23 => 'Right (horizontal)', + 24 => 'Lower Far Right', + 25 => 'Lower-right (horizontal)', + 26 => 'Far Right (vertical)', + 27 => 'Upper-right (vertical)', + 28 => 'Right (vertical)', + 29 => 'Lower-right (vertical)', +); + +# 79 AF point layout and indices for ILCA-68/77M2, numbered 0-78 for direct look-up from BITMASK in 0x2020, +# E6 = Center (ref JR) +my %afPoints79 = ( + 0=>'A5', 1=>'A6', 2=>'A7', + 3=>'B2', 4=>'B3', 5=>'B4', 6=>'B5', 7=>'B6', 8=>'B7', 9=>'B8', 10=>'B9', 11=>'B10', + 12=>'C1', 13=>'C2', 14=>'C3', 15=>'C4', 16=>'C5', 17=>'C6', 18=>'C7', 19=>'C8', 20=>'C9', 21=>'C10', 22=>'C11', + 23=>'D1', 24=>'D2', 25=>'D3', 26=>'D4', 27=>'D5', 28=>'D6', 29=>'D7', 30=>'D8', 31=>'D9', 32=>'D10', 33=>'D11', + 34=>'E1', 35=>'E2', 36=>'E3', 37=>'E4', 38=>'E5', 39=>'E6', 40=>'E7', 41=>'E8', 42=>'E9', 43=>'E10', 44=>'E11', + 45=>'F1', 46=>'F2', 47=>'F3', 48=>'F4', 49=>'F5', 50=>'F6', 51=>'F7', 52=>'F8', 53=>'F9', 54=>'F10', 55=>'F11', + 56=>'G1', 57=>'G2', 58=>'G3', 59=>'G4', 60=>'G5', 61=>'G6', 62=>'G7', 63=>'G8', 64=>'G9', 65=>'G10', 66=>'G11', + 67=>'H2', 68=>'H3', 69=>'H4', 70=>'H5', 71=>'H6', 72=>'H7', 73=>'H8', 74=>'H9', 75=>'H10', + 76=>'I5', 77=>'I6', 78=>'I7', +); + +# AFPoint and AFStatus tags in AFInfo(Tag940e) use numbers 0 to 94 for the 79 positions + 15 cross + 1 F2.8 +my %afPoints79_940e = ( + 59=>'A5', 50=>'A6', 41=>'A7', + 14=>'B2', 7=>'B3', 0=>'B4', 60=>'B5', 51=>'B6', 42=>'B7', 87=>'B8', 80=>'B9', 73=>'B10', + 21=>'C1', 15=>'C2', 8=>'C3', 1=>'C4', 61=>'C5', 52=>'C6', 43=>'C7', 88=>'C8', 81=>'C9', 74=>'C10', 68=>'C11', + 22=>'D1', 16=>'D2', 9=>'D3', 2=>'D4', 62=>'D5', 53=>'D6', 44=>'D7', 89=>'D8', 82=>'D9', 75=>'D10', 69=>'D11', + 23=>'E1', 17=>'E2', 10=>'E3', 3=>'E4', 63=>'E5', 54=>'E6 Center', 45=>'E7', 90=>'E8', 83=>'E9', 76=>'E10', 70=>'E11', + 24=>'F1', 18=>'F2', 11=>'F3', 4=>'F4', 64=>'F5', 55=>'F6', 46=>'F7', 91=>'F8', 84=>'F9', 77=>'F10', 71=>'F11', + 25=>'G1', 19=>'G2', 12=>'G3', 5=>'G4', 65=>'G5', 56=>'G6', 47=>'G7', 92=>'G8', 85=>'G9', 78=>'G10', 72=>'G11', + 20=>'H2', 13=>'H3', 6=>'H4', 66=>'H5', 57=>'H6', 48=>'H7', 93=>'H8', 86=>'H9', 79=>'H10', + 67=>'I5', 58=>'I6', 49=>'I7', + + 28=>'A5 Vertical', 27=>'A6 Vertical', 26=>'A7 Vertical', + 31=>'C5 Vertical', 30=>'C6 Vertical', 29=>'C7 Vertical', + 34=>'E5 Vertical', 33=>'E6 Center Vertical', 32=>'E7 Vertical', + 37=>'G5 Vertical', 36=>'G6 Vertical', 35=>'G7 Vertical', + 40=>'I5 Vertical', 39=>'I6 Vertical', 38=>'I7 Vertical', + + 94=>'E6 Center F2.8', +); + +# ILCA-99M2 has dedicated 79 point Phase Detection AF sensor with similar layout as ILCA-68 and ILCA-77M2. +# ILCA-99M2 has also 399 Focal Plane Phase-Detection AF Points in 19 rows of 21 points, same as ILCE-7RM2. +# Of these 399 points, 323 points are selectable: 19 rows of 17 points (2 left-most and right-most columns not selectable). +# The 79 Focal Plane points that overlap with the 79 points of the dedicated sensor provide for Hybrid Phase AF Points. +# Tag 0x201e gives value 162 for the Center AF point, and 162 is exactly the center of 323 points... +# The below table lists the selectable AF points that correspond to the 79 points of the dedicated sensor. (ref JR) +my %afPoints99M2 = ( + 93=>'A5 (93)', 94=>'A6 (94)', 95=>'A7 (95)', + 106=>'B2 (106)', 107=>'B3 (107)', 108=>'B4 (108)', 110=>'B5 (110)', 111=>'B6 (111)', 112=>'B7 (112)', 114=>'B8 (114)', 115=>'B9 (115)', 116=>'B10 (116)', + 122=>'C1 (122)', 123=>'C2 (123)', 124=>'C3 (124)', 125=>'C4 (125)', 127=>'C5 (127)', 128=>'C6 (128)', 129=>'C7 (129)', 131=>'C8 (131)', 132=>'C9 (132)', 133=>'C10 (133)', 134=>'C11 (134)', + 139=>'D1 (139)', 140=>'D2 (140)', 141=>'D3 (141)', 142=>'D4 (142)', 144=>'D5 (144)', 145=>'D6 (145)', 146=>'D7 (146)', 148=>'D8 (148)', 149=>'D9 (149)', 150=>'D10 (150)', 151=>'D11 (151)', + 156=>'E1 (156)', 157=>'E2 (157)', 158=>'E3 (158)', 159=>'E4 (159)', 161=>'E5 (161)', 162=>'E6 (162)', 163=>'E7 (163)', 165=>'E8 (165)', 166=>'E9 (166)', 167=>'E10 (167)', 168=>'E11 (168)', + 173=>'F1 (173)', 174=>'F2 (174)', 175=>'F3 (175)', 176=>'F4 (176)', 178=>'F5 (178)', 179=>'F6 (179)', 180=>'F7 (180)', 182=>'F8 (182)', 183=>'F9 (183)', 184=>'F10 (184)', 185=>'F11 (185)', + 190=>'G1 (190)', 191=>'G2 (191)', 192=>'G3 (192)', 193=>'G4 (193)', 195=>'G5 (195)', 196=>'G6 (196)', 197=>'G7 (197)', 199=>'G8 (199)', 200=>'G9 (200)', 201=>'G10 (201)', 202=>'G11 (202)', + 208=>'H2 (208)', 209=>'H3 (209)', 210=>'H4 (210)', 212=>'H5 (212)', 213=>'H6 (213)', 214=>'H7 (214)', 216=>'H8 (216)', 217=>'H9 (217)', 218=>'H10 (218)', + 229=>'I5 (229)', 230=>'I6 (230)', 231=>'I7 (231)', +); + +my %binaryDataAttrs = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + WRITE_PROC => \&Image::ExifTool::WriteBinaryData, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + WRITABLE => 1, + FIRST_ENTRY => 0, +); + +# tagInfo attributes for unknown cipher block tags +my %unknownCipherData = ( + Unknown => 1, # require Unknown option + Hidden => 1, # doesn't appear in Tag Name documentation + RawConv => sub { Decipher(\$_[0]); return $_[0] }, + ValueConv => 'PrintHex($val)', # print as hex + PrintConv => 'length($val) > 65 ? substr($val,0,60) . "[...]" : $val', # limit length +); + +my %meterInfo1 = ( + Format => 'int32u[27]', + PrintConv => 'sprintf("%19d %4d %6d" . " %3d %4d %6d" x 8, split(" ",$val))', + PrintConvInv => '$val', +); +my %meterInfo2 = ( + Format => 'int32u[33]', + PrintConv => 'sprintf("%3d %4d %6d" . " %3d %4d %6d" x 10, split(" ",$val))', + PrintConvInv => '$val', +); +my %meterInfo1b = ( + Format => 'undef[90]', + ValueConv => \&ConvMeter1, + PrintConv => 'sprintf("%19d %4d %6d" . " %3d %4d %6d" x 8, split(" ",$val))', +); +my %meterInfo2b = ( + Format => 'undef[110]', + ValueConv => \&ConvMeter2, + PrintConv => 'sprintf("%3d %4d %6d" . " %3d %4d %6d" x 10, split(" ",$val))', +); + +my %hidUnk = ( Hidden => 1, Unknown => 1 ); + +# Sony maker notes tags (some elements in common with %Image::ExifTool::Minolta::Main) +%Image::ExifTool::Sony::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + The following information has been decoded from the MakerNotes of Sony + cameras. Some of these tags have been inherited from the Minolta + MakerNotes. + }, + 0x0010 => [ #PH + # appears to contain mostly AF related information; + # for SLT-A77V and newer, similar info is found in 0x940e AFInfo" (ref JR) + { + Name => 'CameraInfo', + # count: A700=368, A850/A900=5478 + Condition => '$count == 368 or $count == 5478', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::CameraInfo', + ByteOrder => 'BigEndian', + }, + },{ + Name => 'CameraInfo2', + # count: A200/A300/A350=5506, A230/A290/A330/A380/A390=6118 + Condition => '$count == 5506 or $count == 6118', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::CameraInfo2', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'CameraInfo3', + # count: A33/A35/A55V/A450/A500/A550/A560/A580/NEX3/5/5C/C3/VG10E=15360 + Condition => '$count == 15360', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::CameraInfo3', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'CameraInfoUnknown', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::CameraInfoUnknown' }, + }, + ], + # 0x0018 - starts with "GYRO" for sweep panorama images (ref JR) + # - contains ImageStabilization information for Minolta + 0x0020 => [ + # similar to WBInfoA100 in Minolta.pm. + # appears to contain various types of information, as in MoreInfo. (ref JR) + { + Name => 'FocusInfo', #PH + # count: A200/A230/A290/A300/A330/A350/A380/A390==19154, A700/A850/A900=19148 + Condition => '$count == 19154 or $count == 19148', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::FocusInfo', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'MoreInfo', #JR + # count: A450/A500/A550/A560/A580/A33/A35/A55/NEX-3/5/C3/VG10E==20480 + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::MoreInfo', + ByteOrder => 'LittleEndian', + }, + }, + ], + 0x0102 => { #5/JD + Name => 'Quality', + Writable => 'int32u', + PrintConv => { + 0 => 'RAW', + 1 => 'Super Fine', + 2 => 'Fine', + 3 => 'Standard', + 4 => 'Economy', + 5 => 'Extra Fine', + 6 => 'RAW + JPEG/HEIF', + 7 => 'Compressed RAW', + 8 => 'Compressed RAW + JPEG', + 9 => 'Light', #JR + 0xffffffff => 'n/a', #PH (SLT-A57 panorama) + }, + }, + 0x0104 => { #5/JD + Name => 'FlashExposureComp', + Description => 'Flash Exposure Compensation', + Writable => 'rational64s', + }, + 0x0105 => { #5/JD (models since mid-2014, ILCA-77M2, ILCE-7M2/7RM2/7SM2, do not report this tag anymore, ref JR) + Name => 'Teleconverter', + Writable => 'int32u', + PrintHex => 1, + PrintConv => \%Image::ExifTool::Minolta::minoltaTeleconverters, + }, + 0x0112 => { #JD + Name => 'WhiteBalanceFineTune', + Format => 'int32s', + Writable => 'int32u', + }, + 0x0114 => [ #PH + { + Name => 'CameraSettings', + # count: A200/A300/A350/A700=280, A850/A900=364 + Condition => '$count == 280 or $count == 364', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::CameraSettings', + ByteOrder => 'BigEndian', + }, + },{ + Name => 'CameraSettings2', + # count: A230/A290/A330/A380/A390=332 + Condition => '$count == 332', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::CameraSettings2', + ByteOrder => 'BigEndian', + }, + },{ + Name => 'CameraSettings3', + # count: A560/A580/A33/A35/A55/NEX3/5/5C/C3/VG10E=1536, A450/A500/A550=2048 + Condition => '$count == 1536 || $count == 2048', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::CameraSettings3', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'CameraSettingsUnknown', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::CameraSettingsUnknown', + ByteOrder => 'BigEndian', + }, + }, + ], + 0x0115 => { #JD + Name => 'WhiteBalance', + Writable => 'int32u', + Priority => 2, # (more reliable for the RX100) + PrintHex => 1, + PrintConv => { + 0x00 => 'Auto', + 0x01 => 'Color Temperature/Color Filter', + 0x10 => 'Daylight', + 0x20 => 'Cloudy', + 0x30 => 'Shade', + 0x40 => 'Tungsten', + 0x50 => 'Flash', + 0x60 => 'Fluorescent', + 0x70 => 'Custom', + 0x80 => 'Underwater', + }, + }, + # Tag 0x0116: extra hardware info (ref JR) + # (tag not present for A100, A200, A300, A350, A700, nor for A37, A57, A65, A77) + 0x0116 => [ #JR + { + Name => 'ExtraInfo', + Condition => '$$self{Model} =~ /^DSLR-A(850|900)\b/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::ExtraInfo', + ByteOrder => 'BigEndian', + }, + },{ + Name => 'ExtraInfo2', + Condition => '$$self{Model} =~ /^DSLR-A(230|290|330|380|390)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::ExtraInfo2' }, + },{ + Name => 'ExtraInfo3', + # for DSLR-A450/500/550/560/580, SLT-A33/35/55 and NEX-3/5/5C. + SubDirectory => { TagTable => 'Image::ExifTool::Sony::ExtraInfo3' }, + } + ], + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + SubDirectory => { TagTable => 'Image::ExifTool::PrintIM::Main' }, + }, + # the next 3 tags have a different meaning for some models (with format int32u) + 0x1000 => { #9 (F88, multi burst mode only) + Name => 'MultiBurstMode', + Condition => '$format eq "undef"', + Notes => 'MultiBurst tags valid only for models with this feature, like the F88', + Writable => 'undef', + Format => 'int8u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x1001 => { #9 (F88, multi burst mode only) + Name => 'MultiBurstImageWidth', + Condition => '$format eq "int16u"', + Writable => 'int16u', + }, + 0x1002 => { #9 (F88, multi burst mode only) + Name => 'MultiBurstImageHeight', + Condition => '$format eq "int16u"', + Writable => 'int16u', + }, + 0x1003 => { #9 (64 bytes, contains Panorama info for various DSC, NEX, SLT and DSLR models) + Name => 'Panorama', + # panorama: first 4 bytes '1 1 0 0' (little-endian) or '0 0 1 1' (big-endian) + # non-panorama: all bytes are '0' (ref JR) + Condition => '$$self{Panorama} = ($$valPt =~ /^(\0\0)?\x01\x01/)', # (little- or big-endian int32u = 257) + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Panorama' }, + }, + # 0x2000 - undef[1] + 0x2001 => { #PH (JPEG images from all DSLR's except the A100) + Name => 'PreviewImage', + Groups => { 2 => 'Preview' }, + Writable => 'undef', + DataTag => 'PreviewImage', + Notes => 'HD-size preview in JPEG images from almost all DSLR/SLT/ILCA/NEX/ILCE.', + # Note: the preview data starts with a 32-byte proprietary Sony header + # first 8 bytes after 32-byte header: + # \x00\xd8\xff\xe1\x00\x27\xff\xff for JPEG files from A33/35/55V/450/500/550/560/580, NEX-3/5/5C/C3/VG10 + # \x00\xd8\xff\xdb\x00\x84\x00\x01 for JPEG files from all other models + # ( \xff\xd8\xff\xdb\x00\x84\x00\x01 corresponding bytes for all ARW files ) + # + # DSLR-A700/A850/A900 and DSLR-A200/A300/A350: + # - no MPImage2 + # DSLR-A230/A290/A330/A380/A390: + # - PreviewImage start-offset is at 110 bytes inside MPImage2 + # DSLR-A450/A500/A550/A560/A580, SLT-A33/A35/A55V, NEX-3/5/5C/C3/VG10/VG10E: + # - PreviewImage start-offset is at 106 bytes inside MPImage2 + # - different first bytes after 32-byte header + # SLT-A37/A57/A58/A65V/A77V/A99V, ILCA-77M2, NEX-3N/5N/5R/5T/6/7/F3, ILCE-3000/3500/5000/6000/7/7R/7S: + # - PreviewImage start-offset is at 130 bytes inside MPImage2 + # NEX-VG20E/VG30E/VG900, ILCE-QX1: 0x2001 not present + # ILCE-5100/ILCE-7M2/7RM2/7SM2 : 0x2001 present but Size 0 and Offset 0 + # + WriteCheck => 'return $val=~/^(none|.{32}\xff\xd8\xff)/s ? undef : "Not a valid image"', + RawConv => q{ + return \$val if $val =~ /^Binary/; + $val = substr($val,0x20) if length($val) > 0x20; +# return \$val if $val =~ s/^.(\xd8\xff\xdb)/\xff$1/s; + return \$val if $val =~ s/^.(\xd8\xff[\xdb\xe1])/\xff$1/s; + $$self{PreviewError} = 1 unless $val eq 'none' or $val eq ''; + return undef; + }, + # must construct 0x20-byte header which contains length, width and height + ValueConvInv => q{ + return 'none' unless $val; + my $e = new Image::ExifTool; + my $info = $e->ImageInfo(\$val,'ImageWidth','ImageHeight'); + return undef unless $$info{ImageWidth} and $$info{ImageHeight}; + my $size = Set32u($$info{ImageWidth}) . Set32u($$info{ImageHeight}); + return Set32u(length $val) . $size . ("\0" x 8) . $size . ("\0" x 4) . $val; + }, + }, + 0x2002 => { #JR (written by Sony IDC) + Name => 'Rating', + Writable => 'int32u', # (0-5 stars) (4294967295 for an HX9V iSweep Panorama, ref JR) + }, + # 0x2003 - string[256]: all 0 for DSLR, SLT, NEX; data for DSC-HX9V + 0x2004 => { #PH (NEX-5) + Name => 'Contrast', + Writable => 'int32s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x2005 => { #PH (NEX-5) + Name => 'Saturation', + Writable => 'int32s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x2006 => { #PH + Name => 'Sharpness', + Writable => 'int32s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x2007 => { #PH + Name => 'Brightness', + Writable => 'int32s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x2008 => { #PH + Name => 'LongExposureNoiseReduction', + Writable => 'int32u', + PrintHex => 1, + PrintConv => { + 0 => 'Off', + 1 => 'On (unused)', + 0x10001 => 'On (dark subtracted)', # (NEX-C3) + 0xffff0000 => 'Off (65535)', + 0xffff0001 => 'On (65535)', + 0xffffffff => 'n/a', + }, + }, + 0x2009 => { #PH + Name => 'HighISONoiseReduction', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Normal', + 3 => 'High', + 256 => 'Auto', + # it seems that all DSC models except DSC-RX models give n/a here (ref JR) + 65535 => 'n/a', + }, + }, + 0x200a => { #PH (A550) + Name => 'HDR', + Writable => 'int32u', + Format => 'int16u', + Count => 2, + Notes => 'stored as a 32-bit integer, but read as two 16-bit integers', + PrintHex => 1, + PrintConv => [{ + 0x0 => 'Off', + 0x01 => 'Auto', + 0x10 => '1.0 EV', + 0x11 => '1.5 EV', + 0x12 => '2.0 EV', + 0x13 => '2.5 EV', + 0x14 => '3.0 EV', + 0x15 => '3.5 EV', + 0x16 => '4.0 EV', + 0x17 => '4.5 EV', + 0x18 => '5.0 EV', + 0x19 => '5.5 EV', + 0x1a => '6.0 EV', + },{ #JR (A580) + 0 => 'Uncorrected image', # A580 stores 2 images: uncorrected and HDR + 1 => 'HDR image (good)', + 2 => 'HDR image (fail 1)', # alignment problem? + 3 => 'HDR image (fail 2)', # contrast problem? + }], + }, + 0x200b => { #PH + Name => 'MultiFrameNoiseReduction', + Writable => 'int32u', + Notes => 'may not be valid for RS100', # (RS100 sample was 0 when this feature was turned on) + PrintConv => { + 0 => 'Off', + 1 => 'On', + 255 => 'n/a', + }, + }, + # 0x200c - int32u[3]: '0 0 0'; seen '2 1 0' for HX9V 3D-Image (in both JPG and MPO image) + # 0x200d - rational64u: 10/10, seen 2.5 for DSC-TX300V, 8 for DSC-HX100V/RX10 + 0x200e => { #PH (HX20V) + Name => 'PictureEffect', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Toy Camera', #JR (A35) + 2 => 'Pop Color', # (also A35/NEX-C3, ref JR) + 3 => 'Posterization', #JR (A35) + 4 => 'Posterization B/W', #JR (A35) + 5 => 'Retro Photo', #JR (A35, NEX-5) + 6 => 'Soft High Key', # (also A65V, A35/NEX-C3 call this "High-key", ref JR) + 7 => 'Partial Color (red)', #JR (A35) + 8 => 'Partial Color (green)', #JR (A35, NEX-5) + 9 => 'Partial Color (blue)', #JR (A35) + 10 => 'Partial Color (yellow)', #JR (A35, NEX-5) + 13 => 'High Contrast Monochrome', #JR (A35) + 16 => 'Toy Camera (normal)', # (also A65, ref JR) + 17 => 'Toy Camera (cool)', # (RX100) + 18 => 'Toy Camera (warm)', # (RX100) + 19 => 'Toy Camera (green)', # (RX100) + 20 => 'Toy Camera (magenta)', # (RX100) + 32 => 'Soft Focus (low)', #JR (RX100) + 33 => 'Soft Focus', #JR (A65V) + 34 => 'Soft Focus (high)', # (RX100) + 48 => 'Miniature (auto)', #JR (A65V/NEX-7, horizontal) + 49 => 'Miniature (top)', # (RX100) + 50 => 'Miniature (middle horizontal)', # (WX100/HX20V, horizontal) + 51 => 'Miniature (bottom)', # (WX100, rotate 90 CW) + 52 => 'Miniature (left)', # (RX100) + 53 => 'Miniature (middle vertical)', # (RX100) + 54 => 'Miniature (right)', # (RX100) + 64 => 'HDR Painting (low)', # (RX100) + 65 => 'HDR Painting', # (also A65V, ref JR) + 66 => 'HDR Painting (high)', # (RX100) + 80 => 'Rich-tone Monochrome', # (also A65V, ref JR) + 97 => 'Water Color', # (HX200V) + 98 => 'Water Color 2', + 112 => 'Illustration (low)', # (RX100) + 113 => 'Illustration', # (RX100) + 114 => 'Illustration (high)', # (RX100) + }, + }, + 0x200f => { #PH (RX100) + Name => 'SoftSkinEffect', + Writable => 'int32u', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Mid', + 3 => 'High', + # 0x10001 - seen (ref JR) + # 0x10002 - seen for landscape and portrait flash (ref JR) + # 0x10003 - seen (ref JR) + 0xffffffff => 'n/a', # (A35) + }, + }, + 0x2010 => [ #JR + # different camera models have similar content but at different offsets, appears to correlate with: + # 0x1206 - 0x1207 deciphered (0x1205 changes with firmware version): + # ad c3 - NEX-5N + # 0x0192 - 0x0193 deciphered (0x0191 changes with firmware version): + # 91 c3 - NEX-VG20E + # 93 c3 - NEX-7, SLT-A65V/A77V + # 94 c3 - Hasselblad Lunar + # 0x0012 - 0x0013 deciphered (0x0011 changes with firmware version): + # 94 c3 - SLT-A37/A57, NEX-F3 + # 95 d3 - DSC-WX50, WX70 + # 98 c3 - DSC-HX200V, HX20V, HX30V, TX200V, TX300V + # 98 d3 - DSC-HX10V, TX66, WX100, WX150 + # 9a c3 - DSC-RX1, RX1R + # 9b c3 - SLT-A99V, Hasselblad HV + # 9c c3 - NEX-VG30E + # 9d c3 - DSC-RX100, Hasselblad Stellar + # 9e c3 - NEX-VG900, SLT-A58 + # a1 d3 - DSC-TX30 + # a2 d3 - DSC-WX60, WX80, WX200, WX300 + # a3 c3 - NEX-6, DSC-HX300, HX50V + # a4 c3 - NEX-3N/5R/5T, ILCE-3000/3500 + # unknown offsets or values for DSC-TX20/TX55/WX30 + # unknown offsets or values for DSC-HX60V/HX350/HX400V/QX10/QX30/QX100/RX10/RX100M2/RX100M3/WX220/WX350, + # ILCA-68/77M2, ILCE-5000/5100/6000/7/7M2/7R/7S/QX1, Stellar2, Lusso + # unknown offsets or values for DSC-HX80/HX90V/RX0/RX1RM2/RX10M2/RX10M3/RX100M4/RX100M5/WX500, ILCE-6300/6500/7RM2/7SM2, ILCA-99M2 + # unknown offsets or values for ILCE-6100/6400/6600/7C/7M3/7RM3/7RM4/9/9M2, DSC-RX0M2/RX10M4/RX100M6/RX100M5A/RX100M7/HX99 + # July 2020: ILCE-7SM3 doesn't write this tag anymore + { + Name => 'Tag2010a', # ad + Condition => '$$self{Model} =~ /^NEX-5N$/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag2010a' }, + },{ + Name => 'Tag2010b', # 91, 93 + Condition => '$$self{Model} =~ /^(SLT-A(65|77)V?|NEX-(7|VG20E)|Lunar)$/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag2010b' }, + },{ + Name => 'Tag2010c', # 94 + Condition => '$$self{Model} =~ /^(SLT-A(37|57)|NEX-F3)$/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag2010c' }, + },{ + Name => 'Tag2010d', # 95, 98 + Condition => q{ + $$self{Model} =~ /^(DSC-(HX10V|HX20V|HX30V|HX200V|TX66|TX200V|TX300V|WX50|WX70|WX100|WX150))$/ and + not $$self{Panorama} + }, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag2010d' }, + },{ + Name => 'Tag2010e', # 9a, 9b, 9c, 9d, 9e, a1, a2, a3, a4 + Condition => q{ + $$self{Model} =~ /^(SLT-A99V?|HV|SLT-A58|ILCE-(3000|3500)|NEX-(3N|5R|5T|6|VG900|VG30E)|DSC-(RX100|RX1|RX1R)|Stellar)$/ or + ($$self{Model} =~ /^(DSC-(HX300|HX50|HX50V|TX30|WX60|WX80|WX200|WX300))$/ and not $$self{Panorama}) + }, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag2010e' }, + },{ + Name => 'Tag2010f', # ? + Condition => '$$self{Model} =~ /^(DSC-(RX100M2|QX10|QX100))$/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag2010f' }, + },{ + Name => 'Tag2010g', # ? + Condition => '$$self{Model} =~ /^(DSC-(QX30|RX10|RX100M3|HX60V|HX350|HX400V|WX220|WX350)|ILCE-(7(R|S|M2)?|[56]000|5100|QX1)|ILCA-(68|77M2))\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag2010g' }, + },{ + Name => 'Tag2010h', # ? + Condition => '$$self{Model} =~ /^(DSC-(RX0|RX1RM2|RX10M2|RX10M3|RX100M4|RX100M5|HX80|HX90V?|WX500)|ILCE-(6300|6500|7RM2|7SM2)|ILCA-99M2)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag2010h' }, + },{ + Name => 'Tag2010i', # ? + Condition => '$$self{Model} =~ /^(ILCE-(6100|6400|6600|7C|7M3|7RM3A?|7RM4A?|9|9M2)|DSC-(RX10M4|RX100M6|RX100M5A|RX100M7|HX95|HX99|RX0M2)|ZV-(1F?|1M2|E10))\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag2010i' }, + },{ + Name => 'Tag_0x2010', + %unknownCipherData, + }], + 0x2011 => { #PH (A77, NEX-5N) + Name => 'VignettingCorrection', + Writable => 'int32u', + PrintConv => { + 0 => 'Off', + 2 => 'Auto', + 0xffffffff => 'n/a', # (RX100) + }, + }, + 0x2012 => { #PH (A77, NEX-5N) + Name => 'LateralChromaticAberration', + Writable => 'int32u', + PrintConv => { + 0 => 'Off', + 2 => 'Auto', + 0xffffffff => 'n/a', # (RX100) + }, + }, + 0x2013 => { #PH (A77, NEX-5N) ("Setting"; application of such correction is indicated in Tag9405 - ref JR) + Name => 'DistortionCorrectionSetting', + Writable => 'int32u', + PrintConv => { + 0 => 'Off', + 2 => 'Auto', + 0xffffffff => 'n/a', # (RX100) + }, + }, + 0x2014 => { #JR/9 + Name => 'WBShiftAB_GM', + Writable => 'int32s', + Count => 2, + Notes => q{ + 2 numbers: 1. positive is a shift toward amber, 2. positive is a shift + toward magenta + }, + }, + # 0x2015 - int16u: 65535, also for 'normal' HDR images; 0 for HDR-paint and Rich-tone Monochrome effect images + 0x2016 => { #PH (RX100) + Name => 'AutoPortraitFramed', + Writable => 'int16u', + Notes => '"Yes" if this image was created by the Auto Portrait Framing feature', + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + # 0x2017 - int32u: flash mode. 0=off, 1=fired, 2=red-eye (PH, NEX-6) (also in A99, RX1, NEX-5R) + 0x2017 => { #JR + Name => 'FlashAction', + Writable => 'int32u', + PrintConv => { + 0 => 'Did not fire', + 1 => 'Flash Fired', + 2 => 'External Flash Fired', + 3 => 'Wireless Controlled Flash Fired', # (NC) seen for ILCE-9 and ILCE-7M3 images + # 5 => 'External Flash ???', # seen for ILCE-7RM4 + }, + }, + # 0x2018 - something with external flash: seen 1 only when 0x2017 = 2 + # 0x2019 - 0 or 1 (seen 1 for ILCA-77M2, ILCE-7M2/7RM2) + # 0x201a - 0 or 1 + 0x201a => { #JR + Name => 'ElectronicFrontCurtainShutter', + Writable => 'int32u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x201b => { #PH + # FocusMode for SLT/HV/ILCA and NEX/ILCE; doesn't seem to apply to DSC models (always 0) + # from 2018: at least DSC-RX10M4 and RX100M6 also use this tag + Name => 'FocusMode', + Condition => '($$self{Model} !~ /^DSC-/) or ($$self{Model} =~ /^DSC-(RX10M4|RX100M6|RX100M7|RX100M5A|HX95|HX99|RX0M2)/)', + Writable => 'int8u', + Priority => 0, + PrintConv => { + 0 => 'Manual', + 2 => 'AF-S', + 3 => 'AF-C', + 4 => 'AF-A', + 6 => 'DMF', # "Direct Manual Focus" + 7 => 'AF-D', # "Depth Map Assist Continuous AF" + }, + }, + 0x201c => [ #JR + # AFAreaModeSetting for SLT/HV/ILCA and NEX/ILCE; doesn't seem to apply to DSC models (always 0) + # from 2018: at least DSC-RX10M4 and RX100M6 also use this tag + # all DSLR/SLT/HV Wide Zone Spot Local + # all NEX and ILCE-3000 Multi Center FlexibleSpot + # all ILCE and ILCA Wide Zone Center FlexibleSpot ExpandedFlexibleSpot + # (actual AFAreaMode used may be different as camera can override this under certain conditions) + { + Name => 'AFAreaModeSetting', + Condition => '$$self{Model} =~ /^(SLT-|HV)/', + Notes => 'SLT models', + Writable => 'int8u', + PrintConv => { + 0 => 'Wide', + 4 => 'Local', + 8 => 'Zone', #PH + 9 => 'Spot', + }, + },{ + Name => 'AFAreaModeSetting', + Condition => '$$self{Model} =~ /^(NEX-|ILCE-|ILME-|ZV-|DSC-(RX10M4|RX100M6|RX100M7|RX100M5A|HX95|HX99|RX0M2))/', + Notes => 'NEX, ILCE and some DSC models', + RawConv => '$$self{AFAreaILCE} = $val', + DataMember => 'AFAreaILCE', + Writable => 'int8u', + PrintConv => { + 0 => 'Wide', # all NEX and ILCE-3000/3500 use the name 'Multi' + 1 => 'Center', + 3 => 'Flexible Spot', + 4 => 'Flexible Spot (LA-EA4)', # seen for ILCE-7RM2 with LA-EA4 + 9 => 'Center (LA-EA4)', # seen for ILCE-7RM2 with LA-EA4 + 11 => 'Zone', + 12 => 'Expanded Flexible Spot', + }, + },{ + Name => 'AFAreaModeSetting', + Condition => '$$self{Model} =~ /^ILCA-/', + Notes => 'ILCA models', + RawConv => '$$self{AFAreaILCA} = $val', + DataMember => 'AFAreaILCA', + Writable => 'int8u', + PrintConv => { + 0 => 'Wide', + 4 => 'Flexible Spot', + 8 => 'Zone', + 9 => 'Center', + 12 => 'Expanded Flexible Spot', + }, + }, + ], + 0x201d => { #JR + # Flexible Spot position for NEX/ILCE, non-zero only when AFAreaMode='Flexible Spot' + # from 2018: at least DSC-RX10M4 and RX100M6 also use this tag + # observed values in range (0 0) to (640 480), with center (320 240) often seen + # for NEX-5R/6, positions appear to be in an 11x9 grid + Name => 'FlexibleSpotPosition', + Condition => '$$self{Model} =~ /^(NEX-|ILCE-|ILME-|ZV-|DSC-(RX10M4|RX100M6|RX100M7|RX100M5A|HX95|HX99|RX0M2))/', + Writable => 'int16u', + Count => 2, + Notes => q{ + X and Y coordinates of the AF point, valid only when AFAreaMode is Flexible + Spot + }, + }, + 0x201e => [{ #PH (A99) + # Selected AF Point for SLT/HV/ILCA or ILCE with LA-EA2/EA4 + # Selected AF Zone for NEX/ILCE/ILCA when AFAreaMode = 'Zone', + # but also with Expanded Flexible Spot for ILCE-7RM2/7SM2/9 ... + # doesn't seem to apply to DSC models (always 0) + Name => 'AFPointSelected', + Condition => q{ + ($$self{Model} =~ /^(SLT-|HV)/) or ($$self{Model} =~ /^(ILCE-|ILME-)/ and + defined $$self{AFAreaILCE} and $$self{AFAreaILCE} == 4) + }, + Notes => 'SLT models or ILCE with LA-EA2/EA4', + Writable => 'int8u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Auto', #(NC) + 1 => 'Center', + 2 => 'Top', + 3 => 'Upper-right', + 4 => 'Right', + 5 => 'Lower-right', + 6 => 'Bottom', + 7 => 'Lower-left', + 8 => 'Left', + 9 => 'Upper-left', + 10 => 'Far Right', + 11 => 'Far Left', + 12 => 'Upper-middle', + 13 => 'Near Right', + 14 => 'Lower-middle', + 15 => 'Near Left', + 16 => 'Upper Far Right', + 17 => 'Lower Far Right', + 18 => 'Lower Far Left', + 19 => 'Upper Far Left', + }, + },{ + Name => 'AFPointSelected', + Condition => '$$self{Model} =~ /^ILCA-(68|77M2)/ and defined $$self{AFAreaILCA} and $$self{AFAreaILCA} != 8', + Notes => 'ILCA-68 and ILCA-77M2', + Writable => 'int8u', + ValueConv => '$val - 1', # to get the same numbers as from the BITMASK in 0x2020 + ValueConvInv => '$val + 1', + PrintConvColumns => 5, + PrintConv => { + -1 => 'Auto', + %afPoints79, + 39 => 'E6 (Center)', # (add " (Center)" to central point) + }, + },{ + Name => 'AFPointSelected', + Condition => '($$self{Model} =~ /^ILCA-99M2/ and defined $$self{AFAreaILCA} and $$self{AFAreaILCA} != 8)', + Notes => 'ILCA-99M2 when AFAreaModeSetting is not Zone', + # (appears to indicate the number of the selectable Focal Plane AF Point) + Writable => 'int8u', + PrintConvColumns => 4, + PrintConv => { + 0 => 'Auto', # seen for AFAreaModeSetting = Center, Wide + %afPoints99M2, # for selected AFPoints corresponding to the 79 dedicated AF points + 162 => 'E6 (162, Center)', # add " (Center)" to central point + OTHER => sub { shift }, # pass other values straight through + }, + },{ + Name => 'AFPointSelected', + Condition => '($$self{Model} =~ /^ILCA-/ and defined $$self{AFAreaILCA} and $$self{AFAreaILCA} == 8)', + Notes => 'ILCA models when AFAreaModeSetting is set to Zone', + # ILCA-68 and 77M2 have 9 Zones: numbers/locations verified for ILCA-77M2 + # ILCA-99M2 has 15 Zones in Hybrid-AF and 9 Zones in dedicated Phase AF Area, so may not be valid for ILCA-99M2... + Writable => 'int8u', + PrintConv => { + 0 => 'n/a', + 1 => 'Top Left Zone', + 2 => 'Top Zone', + 3 => 'Top Right Zone', + 4 => 'Left Zone', + 5 => 'Center Zone', + 6 => 'Right Zone', + 7 => 'Bottom Left Zone', + 8 => 'Bottom Zone', + 9 => 'Bottom Right Zone', + #14 => ' ??? ', # seen for ILCA-99M2 + }, + },{ + Name => 'AFPointSelected', + # non-zero only when AFAreaMode is 'Zone', and 'Expanded-Flexible-Spot' for ILCE-6300/7RM2/7SM2 + # each Zone has 3x3 AF Areas --> 9 positions within 5x5 total Contrast AF Areas + Condition => '$$self{Model} =~ /^(NEX-|ILCE-|ILME-)/', + Notes => 'NEX and ILCE models', + Writable => 'int8u', + PrintConv => { + 0 => 'n/a', + 1 => 'Center Zone', + 2 => 'Top Zone', + 3 => 'Right Zone', + 4 => 'Left Zone', + 5 => 'Bottom Zone', + 6 => 'Bottom Right Zone', + 7 => 'Bottom Left Zone', + 8 => 'Top Left Zone', + 9 => 'Top Right Zone', + }, + }], + # 0x201f - 0 0 0 0 for SLT and DSC; 4 values for NEX/ILCE with 4th value always 0: + # possibly bits relating to the 25 AF-Contrast-areas ??? + # 0x2020 - 10 values; for SLT/ILCA and NEX/ILCE with A-mount lens: relates to (phase-detect) AFPoints + # but not used by ILCA-99M2 anymore ... ? + 0x2020 => [{ + Name => 'AFPointsUsed', + Condition => '$$self{Model} !~ /^(ILCA-|DSC-)/', # (doesn't seem to apply to DSC-models) + Notes => 'SLT models, or NEX/ILCE with A-mount lenses', + BitsPerWord => 8, + BitsTotal => 80, + PrintConvColumns => 2, + PrintConv => { + 0 => '(none)', + BITMASK => { + 0 => 'Center', + 1 => 'Top', + 2 => 'Upper-right', + 3 => 'Right', + 4 => 'Lower-right', + 5 => 'Bottom', + 6 => 'Lower-left', + 7 => 'Left', + 8 => 'Upper-left', + 9 => 'Far Right', + 10 => 'Far Left', + 11 => 'Upper-middle', + 12 => 'Near Right', + 13 => 'Lower-middle', + 14 => 'Near Left', + 15 => 'Upper Far Right', + 16 => 'Lower Far Right', + 17 => 'Lower Far Left', + 18 => 'Upper Far Left', + }, + }, + },{ + Name => 'AFPointsUsed', + Condition => '$$self{Model} =~ /^ILCA-(68|77M2)/', + Notes => 'ILCA models', + BitsPerWord => 8, + BitsTotal => 80, + PrintConvColumns => 4, + PrintConv => { + 0 => '(none)', + BITMASK => { %afPoints79 }, + }, + }], + # 0x2021 - 0 for DSC; 0, 1 or 2 for SLT/ILCA and NEX/ILCE: 1=Face, 2=object-tracking ? + # from 2018: at least DSC-RX10M4 and RX100M6 also use this tag + 0x2021 => { #JR + Name => 'AFTracking', + Condition => '($$self{Model} !~ /^DSC-/) or ($$self{Model} =~ /^DSC-(RX10M4|RX100M6|RX100M7|RX100M5A|HX95|HX99|RX0M2)/)', + Writable => 'int8u', + PrintConv => { + 0 => 'Off', + 1 => 'Face tracking', + 2 => 'Lock On AF', + }, + }, + # 0x2022 - 13 bytes (104 bits) for SLT-A58/A99V, NEX-3N/5R/5T/6/VG30E/VG900, ILCE-3000/3500/5000/7/7R + # 26 bytes (208 bits) for ILCA-77M2, ILCE-5100/6000/7M2/7S/QX1 (7M2 has 117, 5100/6000 have 179 PhaseAFPoints) + # 52 bytes (416 bits) for ILCE-7RM2 (which has 399 PhaseAFPoints) and ILCE-7SM2 + # Only seen non-zero values for ILCE-5100/6000/7M2/7RM2 in AF-C mode: maybe FocalPlaneAFPointsUsed ??? + # (Similar number of bytes for contemporary DSC models, but mostly all non-zero values.) + # ILCE-6300 does not write this tag anymore, but writes 0x202a ... + 0x2022 => [{ + Name => 'FocalPlaneAFPointsUsed', + Condition => '$$self{Model} =~ /^(ILCE-(5100|6000|7M2))/', + Notes => 'On-sensor/focal-plane phase AF points for ILCE with hybrid AF', + BitsPerWord => 8, + BitsTotal => 208, # 26 words + PrintConv => { + 0 => '(none)', + BITMASK => { }, + }, + },{ + Name => 'FocalPlaneAFPointsUsed', + Condition => '$$self{Model} =~ /^ILCE-7RM2/', + # ILCE-7RM2 has 399 points in 19 rows of 21 points, numbered [0] to [398], [199] is Center + BitsPerWord => 8, + BitsTotal => 416, # 52 words + PrintConv => { + 0 => '(none)', + BITMASK => { }, + }, + }], + 0x2023 => { #JR + Name => 'MultiFrameNREffect', + Writable => 'int32u', + PrintConv => { + 0 => 'Normal', + 1 => 'High', # seen this only for ILCA-77M2 MFNR-12 images + }, + }, + # 0x2024 - 96 byte data block, very similar to 0x3000 ShotInfo, seen in Xperia Z5 + # 0x2025 - n1 n2 0 0 DSC-RX100M3/RX100M4/RX10M2/HX90V/WX500, ILCA-77M2, ILCE-5100/7M2/7RM2/7S/QX1 + # seen n1=0,2,4,5,7 and n2=0,1,3, very often: 7 3 0 0 + + # 0x2026 - 2 values: more precise WB Shift: AB in steps of 0.50, GM in steps of 0.25 (ILCE-7RM2 onwards) + 0x2026 => { #JR + Name => 'WBShiftAB_GM_Precise', + Writable => 'int32s', + Count => 2, + Notes => q{ + 2 numbers: 1. positive is a shift toward amber, 2. positive is a shift + toward magenta + }, + PrintConv => 'my @v=split(" ",$val); $_/=1000 foreach @v; sprintf("%.2f %.2f",$v[0],$v[1])', + }, + # 0x2027 - W H W/2 H/2 or W H val1 val2 (0 0 0 0 for Panorama images) + # Probably location of focus for Playback Zoom. + # Origin appears to be top-left, i.e. 1st coord to the right, 2nd coord. pointing down. + 0x2027 => { #JR + Name => 'FocusLocation', #(NC) + Writable => 'int16u', + Count => 4, + NOTES => q{ + Location in the image where the camera focused, used for Playback Zoom. + If the focus location information cannot be obtained, the centre of the + image will be used. + }, + }, + # 0x2028 - 0 0 for DSC-RX100M4/RX10M2, ILCE-7RM2/7SM2; seen non-zero values only for DSC-RX1RM2 + 0x2028 => { #JR + Name => 'VariableLowPassFilter', + Writable => 'int16u', + Count => 2, + PrintConv => { + '0 0' => 'n/a', + '1 0' => 'Off', + '1 1' => 'Standard', + '1 2' => 'High', + '65535 65535' => 'n/a', # ILCE-7SM3 + }, + }, + 0x2029 => { # uncompressed 14-bit RAW file type setting introduced 2015 + Name => 'RAWFileType', + Writable => 'int16u', + PrintConv => { + 0 => 'Compressed RAW', + 1 => 'Uncompressed RAW', + 2 => 'Lossless Compressed RAW', #JR (NC) seen for ILCE-1 + 65535 => 'n/a', # seen for ILCE-7SM3 JPEG-only + }, + }, + # 0x202a - first seen for ILCE-6300: 66 bytes + # possibly a 'replacement' for Tag2022 FocalPlaneAFPointsUsed, + # but now indicating locations in a 640x428 grid (3:2 part of LCD ?) + # first byte value 1 for ILCE-6300/6500/9, ILCA-99M2 + # values 110,137, ... for DSC-RX10M3, therefore limit to first byte = 1 for now + 0x202a => { + Name => 'Tag202a', + Condition => '$$valPt =~ /^\x01/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag202a' }, + }, + 0x202b => { #JR (ILCA-99M2, ILCE-6500/7M3/7RM3/9, DSC-RX10M4/RX100M5 and newer) + Name => 'PrioritySetInAWB', + Writable => 'int8u', + PrintConv => { + 0 => 'Standard', + 1 => 'Ambience', + 2 => 'White', + }, + }, + 0x202c => { #JR + Name => 'MeteringMode2', + Writable => 'int16u', + PrintHex => 1, + PrintConv => { + 0x100 => 'Multi-segment', + 0x200 => 'Center-weighted average', + 0x301 => 'Spot (Standard)', + 0x302 => 'Spot (Large)', + 0x400 => 'Average', + 0x500 => 'Highlight', + }, + }, + 0x202d => { #JR first seen for ILCA-99M2, ILCE-6500, DSC-RX100M5 + Name => 'ExposureStandardAdjustment', + Writable => 'rational64s', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 0x202e => { #JR (ILCE-7M3/7RM3 and newer) + Name => 'Quality', + Writable => 'int16u', + Count => 2, + PrintConv => { + '0 0' => 'n/a', + '0 1' => 'Standard', + '0 2' => 'Fine', + '0 3' => 'Extra Fine', + '0 4' => 'Light', #JR + '1 0' => 'RAW', + '1 1' => 'RAW + Standard', + '1 2' => 'RAW + Fine', + '1 3' => 'RAW + Extra Fine', + '1 4' => 'RAW + Light', #JR + '2 0' => 'S-size RAW', + '3 0' => 'M-size RAW', # ILCE-1/7RM5, APS-C mode + '3 2' => 'M-size RAW + Fine', + '3 3' => 'M-size RAW + Extra Fine', + }, + }, + 0x202f => { #JR (ILCE-7RM3) + Name => 'PixelShiftInfo', + Writable => 'undef', + # 6 bytes, all 0 for non-PixelShift images + # first 4 bytes: GroupID, read as int32u + # the ID displayed by Sony ImageDataConverter appears to be based on the lower 22 bits: + # 5 bits, 5 bits, 6 bits, 6 bits + # last 2 bytes: ShotNumber: (1 4) to (4 4) and (1 16) to (16 16) are the 4 or 16 source images, + # (0 4) is the combined image for 4-shot PixelShift mode + # (0 16) is the combined image for 16-shot PixelShift mode (ILCE-7RM4) + RawConv => q{ + my ($a,$b,$c) = (Get32u(\$val,0), Get8u(\$val,4), Get8u(\$val,5)); + sprintf("%.2d%.2d%.2d%.2d %d %d 0x%x",($a>>17)&0x1f,($a>>12)&0x1f,($a>>6)&0x3f,$a&0x3f,$b,$c,$a>>22); + }, + RawConvInv => q{ + my ($a,$b,$c,$d) = split ' ', $val; + my @a = $a =~ /../g; + return undef unless @a == 4; + return Set32u((hex($d)<<22) | ($a[0]<<17) | ($a[1]<<12) | ($a[2]<<6) | $a[3]) . chr($b & 0xff) . chr($c & 0xff); + }, + PrintConv => { + '00000000 0 0 0x0' => 'n/a', + OTHER => sub { + my ($val, $inv) = @_; + if ($inv) { + $val =~ s{Composed (\d+)-shot}{Shot 0/$1}i; + $val =~ s{^(?:Group)?\s*(\d+)[, ]+(?:Shot\s*)?(\d+)[/ ](\d+)\s*\(?(\w+)\)?}{$1 $2 $3 $4}i or return undef; + } else { + $val =~ s{(\d+) (\d+) (\d+) (\w+)}{Group $1, Shot $2/$3 ($4)} or return undef; + $val =~ s{Shot 0+/0*(\d+)\b}{Composed $1-shot}i; + } + return $val; + }, + }, + }, + 0x2031 => { #JR (only for ILCE-9 v2.00; ILCE-9 v2.10 and higher have "option" to write into EXIF tag 0xa431 ) + Name => 'SerialNumber', + Writable => 'string', + ValueConv => '$val=~s/(\d{2})(\d{2})(\d{2})(\d{2})/$4$3$2$1/; $val=~s/^0//; $val', + ValueConvInv => '$val="0$val" if length($val)==7; $val=~s/(\d{2})(\d{2})(\d{2})(\d{2})/$4$3$2$1/; $val', + PrintConv => 'sprintf("%.8d",$val)', + PrintConvInv => '$val', + }, +# 0x2032 - 0x2039: from July 2020 for ILCE-7SM3, ILCE-1, ILME-FX3 and newer + 0x2032 => { + Name => 'Shadows', + Writable => 'int32s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x2033 => { + Name => 'Highlights', + Writable => 'int32s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x2034 => { + Name => 'Fade', + Writable => 'int32s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x2035 => { + Name => 'SharpnessRange', + Writable => 'int32s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x2036 => { + Name => 'Clarity', + Writable => 'int32s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x2037 => { + Name => 'FocusFrameSize', + Format => 'int16u', + Count => '3', + Notes => 'width and height of FocusFrame, centered on FocusLocation', + PrintConv => q{ + my @a = split ' ', $val; + return $a[2] ? sprintf('%3dx%3d', $a[0], $a[1]) : 'n/a'; + }, + PrintConvInv => '$val =~ /(\d+)x(\d+)/ ? "$1 $2 257" : "0 0 0"', + }, + 0x2039 => { #JR + Name => 'JPEG-HEIFSwitch', # (name used in camera menus) + Writable => 'int16u', + PrintConv => { + 0 => 'JPEG', + 1 => 'HEIF', + 65535 => 'n/a', + }, + }, +# 0x203a - 0x2041: first seen October 2021 for ILCE-7M4 + 0x3000 => { + Name => 'ShotInfo', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::ShotInfo' }, + }, + # 0x3000: data block that includes DateTimeOriginal string + # 0x5001 - 0 + # 0x5002 - 128 +# +# at least some data for tags 0x2010, 0x9050 and 0x94xx is encrypted - PH +# (This is certainly true for at least parts of 0x2010, 0x9050, 0x9400, 0x9402 and 0x9403, +# but hasn't been verified for other tags -- just to be thorough, decipher all of them) +# Note: "(e)" in a comment indicates an enciphered value, all other values are deciphered +# + # 0x900b - 1st byte 0xae: face detection info for A450/500/550/560/580, A33/35/55, NEX-3/5/5C/C3/VG10 + # - other 1st byte values for some DSC-models + # - seen many 1,8,27,64... values: assume encrypted like other 9xxx tags + 0x900b => { + Name => 'Tag900b', + Condition => '$$valPt =~ /^\xae/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag900b' }, + }, + 0x9050 => [ + # 944 bytes for A37, A57, A99, NEX-F3, NEX-5R, NEX-6, DSC-RX1, DSC-RX100 + # 3072 bytes for A65, A77, NEX-5N, NEX-7, NEX-VG20 (ref JR) + # not valid for DSC/Stellar models + # from mid-2015: ILCE-7RM2/7SM2/6300 and newer models use different offsets + { + Name => 'Tag9050a', + Condition => '$$self{Model} !~ /^(DSC-|Stellar|ILCE-(1|6100|6300|6400|6500|6600|6700|7C|7M3|7M4|7RM2|7RM3A?|7RM4A?|7RM5|7SM2|7SM3|9|9M2)|ILCA-99M2|ILME-FX3|ZV-)/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::Tag9050a', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'Tag9050b', + Condition => '$$self{Model} =~ /^(ILCE-(6100|6300|6400|6500|6600|7C|7M3|7RM2|7RM3A?|7RM4A?|7SM2|9|9M2)|ILCA-99M2|ZV-E10)/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::Tag9050b', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'Tag9050c', + Condition => '$$self{Model} =~ /^(ILCE-(1|7M4|7RM5|7SM3)|ILME-FX3)/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::Tag9050c', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'Tag9050d', + Condition => '$$self{Model} =~ /^(ILCE-6700|ZV-E1)/', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::Tag9050d', + ByteOrder => 'LittleEndian', + }, + },{ + Name => 'Sony_0x9050', + %unknownCipherData, + }], + 0x9400 => [ + # first byte: + # 0x07 (e) for DSC-HX7V/HX9V/HX100V/TX10/TX100/TX100V/WX7/WX9/WX10, HDR-CX../PJ.. + # 0x09 (e) for DSC-TX20/TX55/WX30 + # 0x0a (e) for SLT-A37/A57/A65V/A77V/A99V, NEX-F3/5N/5R/5T/6/7/VG20E, DSC-RX100/RX1/RX1R/HX10V/HX20V/HX30V/HX200V/TX200V/TX300V/TX66/WX50/WX100/WX150, Lunar/Stellar/HV + # 0x0c (e) for ILCE-3000/3500, NEX-3N, SLT-A58, DSC-HX50V/HX300/RX100M2/TX30/WX60/WX80/WX200/WX300, DSC-QX10/QX100 + # 0xd0 (e) H90, W650, W690: tag9400 decoding appears not valid/different + # 0x23 (e) for DSC-RX10/HX60V/HX350/HX400V/WX220/WX350, ILCE-7/7R/5000/6000, ILCA-68/77M2 + # 0x24 (e) for ILCA-99M2,ILCE-5100/6300/6500/7M2/7RM2/7S/7SM2/QX1, DSC-HX80/HX90V/QX30/RX0/RX100M3/RX100M4/RX100M5/RX10M2/RX10M3/RX1RM2/WX500 + # 0x26 (e) for ILCE-6100/6400/6600/7M3/7RM3/9, DSC-RX0M2/RX10M4/RX100M5A/RX100M6/HX95/HX99 + # 0x28 (e) for ILCE-7RM4/9M2, DSC-RX100M7, ZV-1/1F/1M2/E10 + # 0x31 (e) for ILCE-1/7M4/7SM3, ILME-FX3 + # 0x32 (e) for ILCE-7RM5, ILME-FX30 + # 0x33 (e) for ILCE-6700, ZV-E1 + # first byte decoded: 40, 204, 202, 27, 58, 62, 48, 215, 28, 106, 89, 63 respectively + { + Name => 'Tag9400a', + Condition => q{ + $$valPt =~ /^[\x07\x09\x0a]/ or + ($$valPt =~ /^[\x5e\xe7\x04]/ and $$self{DoubleCipher} = 1) + }, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9400a' }, + },{ + Name => 'Tag9400b', + Condition => '$$valPt =~ /^\x0c/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9400b' }, + },{ + Name => 'Tag9400c', + Condition => '$$valPt =~ /^[\x23\x24\x26\x28\x31\x32\x33]/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9400c' }, + },{ + Name => 'Sony_0x9400', + %unknownCipherData, + }], + 0x9401 => { + Name => 'Tag9401', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9401' }, + # notes for data in this block (ref PH/JR): + # 0x02-0x03 appear to have some relation to start-offset of data... + # 0x00 - 0x03 Metering + # Mode + # f4 00 00 03 - - DSC-H90/W650/W690 + # cf 0b 9f 0f 0x09bc (a) DSC-WX9 + # 1c 00 ac 0f 0x09c9 (b) HDR-CX130E/CX160E/CX360E/CX560E/CX700E/PJ10E/PJ30E + # b7 0f f7 0f 0x09dd (c) DSC-HX7V/TX10/WX7/WX10 + # b7 0f fa 0f 0x09e0 (d) DSC-HX9V/HX100V/TX100/TX100V + # 27 00 fd 0f 0x09e7 (e) DSC-TX20/TX55/WX30 + # 69 1f ff 0f 0x09e9 (f) NEX-5N + # 21 2b cf 0f 0x09e9 (f) NEX-7/VG20E, SLT-A65V/A77V, Lunar + # 2d 00 d5 0d 0x09a2 (g) DSC-HX10V/HX20V/HX30V/HX200V/TX66/TX200V/TX300V/WX50/WX70/WX100/WX150 + # 2f 00 d6 0d 0x09a3 (h) NEX-F3, SLT-A37/A57 + # 30 00 d8 0d 0x09a5 (i) HDR-AS15 + # 32 00 e2 0d 0x09ac (j) DSC-RX100, Stellar + # 33 00 e2 0d 0x09ac (j) NEX-5R/5T/6, NEX-VG900/VG30E + # 33 50 e2 0d 0x09ac (j) SLT-A99V, HV + # 33 40 0d 0e 0x09d7 (k) DSC-RX1 v0.01 + # 33 41 0d 0e 0x09d7 (k) DSC-RX1, DSC-RX1R + # 38 00 32 0e 0x09fc (l) SLT-A58, ILCE-3000/3500, NEX-3N, DSC-HX300/HX50V/WX200/WX300/WX60/WX80/TX30 + # 3a 10 3a 0e 0x0a01 (m) DSC-QX10/QX100 + # 3a 20 47 0e 0x0a01 (m) DSC-RX100M2 + # 43 00 66 0e 0x0a1b (n) ILCE-7/7R v0.xx/v1.00/v1.01, ILCE-5000, DSC-RX10 + # 43 10 66 0e 0x0a1b (n) ILCE-7/7R v1.02/v1.10 + # 43 30 6c 0e 0x0a1b (n) ILCE-7/7R v1.20-v3.20 + # 44 00 9c 0e 0x0a39 (o) ILCE-6000 v1.00/v1.10, DSC-HX60V/HX350/HX400V/WX220/WX350 (also DSC-QX30 samples from sony.net) + # 44 10 a2 0e 0x0a39 (o) ILCE-6000 v1.20/v1.21 + # 44 20 a2 0e 0x0a39 (o) ILCE-6000 v2.00-v3.20 + # 49 00 b0 0e 0x0a3b (p) ILCA-68 v1.00, ILCA-77M2 V1.00/v1.01/v2.00 (also DSC-RX100M3 samples from sony.net) + # 4a 00 b3 0e 0x0a3d (q) ILCE-7S v1.00, ILCE-5100 v1.00/v1.10, ILCE-QX1, DSC-QX30/RX100M3 + # 4a 20 b9 0e 0x0a3d (q) ILCE-7S v1.20-v3.20 + # 4e 10 d0 0e 0x0a5a (r) ILCE-7M2 v1.00/v1.10 + # 4e 30 d6 0e 0x0a5a (r) ILCE-7M2 v1.20-v4.00 + # 5a 00 14 0f 0x0a85 (s) DSC-HX80/HX90V/WX500 + # 5d 00 56 0f 0x0ac7 (t) DSC-RX10M2/RX100M4, ILCE-7RM2/7SM2 v1.00-v2.20 (also DSC-RX1RM2 samples from Sony) + # 5d 1d 58 0f 0x0ac7 (t) ILCE-7RM2 v3.00-v4.00 + # 5d 1e 57 0f 0x0ac7 (t) DSC-RX1RM2 v1.00 + # 5d 10 56 0f 0x0ac7 (t) ILCE-6300 v1.00 (samples from Sony) + # 5d 20 58 0f 0x0ac7 (t) ILCE-6300 v1.00/v1.10 + # 5e 00 56 0f 0x0ac7 (t) DSC-RX10M3 v1.00 + # 64 00 a8 0f 0x0b15 (u) DSC-RX100M5 v1.00 + # 67 00 f9 0f 0x0b66 (v) ILCA-99M2 v1.00, ILCE-6500 v1.00-v1.05, DSC-RX0 v1.00 + # 7c 00 fe 0f 0x0adb (w) ILCE-9 v0.01-v2.00 + # 7d 00 fe 0f 0x0adb (w) ILCE-9 v2.10-v4.10 + # 7f 00 fa 0f 0x0add (x) DSC-RX10M4 v1.00 + # 80 00 fa 0f 0x0add (x) ILCE-7M3/7RM3 v1.00-v3.01 + # 82 00 fc 0f 0x0ad9 (y) DSC-RX100M5A v1.00, DSC-RX100M6 v1.00 + # 90 00 fe 0f 0x098f? (z) DSC-HX99 v1.00 + # 92 10 ff 0f 0x0990 (za) ILCE-6100/6400/6600 v1.00 + # 94 00 ce 0b 0x0879 (zb) ILCE-9 v5.00-v6.00, DSC-RX0M2 + # 98 00 db 0c 0x088a (zc) ILCE-7RM4 v1.00 + # 9a 00 e3 0c 0x088a (zc) DSC-RX100M7 v1.00, ILCE-9M2 v1.00 + # + # 0x0004 - (RX100: 0 or 1. subsequent data valid only if 1 - PH) + # 0x0007 => { + # Name => 'DynamicRangeOptimizer_9401', + # PrintConv => { + # 0 => 'Disabled', # seen for Panorama images + # 1 => 'Auto', + # 3 => 'Lv1', #(NC) + # 4 => 'Lv2', #(NC) + # 5 => 'Lv3', + # 6 => 'Lv4', + # 7 => 'Lv5', + # # 8 - seen for VG20E and some other models - PH + # 255 => 'Off', + # }, + # }, + }, + 0x9402 => [{ + Name => 'Tag9402', + # first 2 bytes deciphered: + # 0x00 0x00 SLT-A37/A57/A65/A77 + # 0x0e 0x00 DSC-H90/HX7V/HX9V/HX100V/TX10/TX100/TX100V/TX20/TX55/W650/W690/W730/WX10/WX30/WX7/WX9, but also seen: + # 0x0e 0x01 for a few DSC-W650/W690 samples ... + # 0x0f 0x01 NEX-5N/7/VG20, Lunar + # 0x10 0x01 DSC-HX10V/HX200V/HX20V/HX300/HX30V/HX50V/TX200V/TX30/TX300V/TX66/RX100/RX1/RX1R/WX100/WX150/WX200/WX300/WX50/WX60/WX70/WX80, Stellar, + # ILCE-3000/3500, NEX-F3/3N/5R/5T/6/VG30/VG900 + # 0x11 0x01 DSC-RX100M2/QX10/QX100 + # 0x13 0x01 ILCE-5000/7/7R, DSC-RX10, but also seen: + # 0x12 0x01 for ILCE-7/7R and DSC-RX10 samples from Sony.net ... + # 0x15 0x01 for a few ILCE-7/7R ... + # 0x14 0x01 ILCE-6000, DSC-HX60V/HX350/HX400V/WX220/WX350 + # 0x17 0x01 ILCE-7S/7M2/5100/QX1, DSC-QX30/RX100M3 + # 0x19 0x01 DSC-HX80/HX90V/RX1RM2/RX10M2/RX100M4/WX500, ILCE-6300/7RM2/7SM2 + # 0x1a 0x01 DSC-RX0/RX10M3/RX100M5, ILCE-6500 + # 0x1c 0x01 ILCE-9 + # 0x1d 0x01 DSC-RX10M4 + # 0x1e 0x01 ILCE-7M3/7RM3, DSC-RX100M5A/RX100M6 + # 0x1f 0x01 DSC-HX95/HX99 + # 0x20 0x01 ILCE-6100/6400/6600/7RM4/9M2, ILCE-9 v5.00-v6.00, DSC-RX0M2/RX100M7 + # 0x21 0x01 ZV-1/1F/1M2/E10 + # 0x27 0x01 ZV-E1 + # var var SLT-A58/A99V, HV, ILCA-68/77M2/99M2 + # only valid when first byte 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x17, 0x19, 0x1a, 0x1c (enciphered 0x8a, 0x70, 0xb6, 0x69, 0x88, 0x20, 0x30, 0xd7, 0xbb, 0x92, 0x28) +# Condition => '$$self{DoubleCipher} ? $$valPt =~ /^[\x7e\x46\x1d\x18\x3a\x95\x24\x26\xd6]\x01/ : $$valPt =~ /^[\x8a\x70\xb6\x69\x88\x20\x30\xd7\xbb\x92\x28]\x01/', +# alternative simpler Condition: +# not valid for SLT/HV/ILCA models, and not valid for first byte 0x0e or 0xff (enciphered 0x05 or 0xff) + Condition => '$$self{Model} !~ /^(SLT-|HV|ILCA-)/ and $$valPt !~ /^[\x05\xff]/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9402' }, + },{ + Name => 'Sony_0x9402', + %unknownCipherData, + }], + 0x9403 => { + Name => 'Tag9403', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9403' }, + }, + # 0x9404 first 5 bytes (deciphered): + # 4 0 163 1 2 SLT-A65V/A77V, NEX-5N/7, Lunar, DSC-HX7V/HX9V/HX100V/TX10/TX20/TX55/TX100/TX100V/WX9/WX10/WX30... + # 5 0 172 1 2 DSC-HX10V/HX200V/HX20V/HX30V/TX66/TX200V/TX300V/WX50/WX70/WX100/WX150... + # 9 0 38 2 2 SLT-A37/A57/A99V, NEX-5R/5T/6/F3/VG30E/VG900, DSC-RX1/RX1R/RX100, Stellar + # 12 0 8 2 2 SLT-A58, NEX-3N, ILCE-3000/3500, DSC-HX300/HX50V/WX60/WX80/WX300/TX30... + # 13 0 9 2 2 DSC-QX10/QX100/RX100M2 + # 15 0 35 2 2 ILCA-68/77M2, ILCE-5000/5100/6000/7/7R/7S/7M2/QX1, DSC-HX60V/HX350/HX400V/QX30/RX10/RX100M3/WX220/WX350 + # 16 0 85 2 2 DSC-HX80/HX90V/WX500 + # 17 0 232 1 2 DSC-RX0/RX0M2/RX1RM2/RX10M2/RX10M3/RX10M4/RX100M4/RX100M5/RX100M5A/RX100M6/RX100M7/HX95/HX99, ILCE-6100/6300/6400/6500/6600/7C/7M3/7RM2/7RM3/7RM4/7SM2/9/9M2, ILCA-99M2, ZV-1/1F/1M2/E10 + # 18 0 20 0 164 ILCE-7SM3, ZV-E1 + # other values for Panorama images and several other models + 0x9404 => [{ + Name => 'Tag9404a', + # first byte must be 4 or 5 and 4th byte must be 1 (deciphered) + Condition => '$$valPt =~ /^[\x40\x7d]..\x01/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9404a' }, + },{ + Name => 'Tag9404b', + # first byte must be 9 or 12 or 13 or 15 or 16 and 4th byte must be 2 (deciphered) + Condition => '$$valPt =~ /^[\xe7\xea\xcd\x8a\x70]..\x08/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9404b' }, + },{ + Name => 'Tag9404c', + # first byte must be 17 and 4th byte must be 1 (deciphered) + Condition => '$$valPt =~ /^\xb6..\x01/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9404c' }, + },{ + Name => 'Sony_0x9404', + %unknownCipherData, + }], + # 0x9405 first 2 bytes: + # 0 0 (0x00 = 0 0 enc.) DSC-H90 + # 2 0 (0x08 = 8 0 enc.) DSC and HDR of HX9V generation + # 3 0 (0x1b = 27 0 enc.) SLT, NEX, ILCE-3000/3500, DSC-RX100/RX1 + other DSC of same generation, also QX10 and QX100 + # 4 0 (0x40 = 64 0 enc.) DSC-RX1R + # 5 0 (0x7d = 125 0 enc.) DSC-RX100M2 + # 136 var (0x3a = 58 var enc.) ILCE-7/7R/5000/6000, DSC-HX60V/HX350/HX400V/RX10/WX220/WX350 + # 137 var (0xb3 = 179 var enc.) ILCA-68/77M2, DSC-RX100M3 - appears to go with 136 + # 138 var (0x7e = 126 var enc.) ILCE-7S/5100/QX1, DSC-QX30 - appears to go with 136 + # 139 var (0x9a = 154 var enc.) ILCE-7M2 + # 142 var (0x25 = 37 var enc.) DSC-HX80/HX90V/RX1RM2/RX10M2/RX10M3/RX100M4/WX500, ILCE-6300/7RM2/7SM2 + # 144 var (0xe1 = 225 var enc.) DSC-RX100M5 + # 145 var (0x76 = 118 var enc.) ILCA-99M2, ILCE-6500, DSC-RX0 + # 163 var (0x8b = 139 var enc.) ILCE-6100/6400/6600/7C/7M3/7RM3/7RM4/9/9M2, DSC-RX0M2/RX10M4/RX100M5A/RX100M6/RX100M7/HX95/HX99, ZV-1/1F/1M2/E10 + # July 2020: ILCE-7SM3 doesn't write this tag anymore, but writes 0x9416 + 0x9405 => [{ + Name => 'Tag9405a', + # first byte must be 0x1b or 0x40 or 0x7d + Condition => '$$valPt =~ /^[\x1b\x40\x7d]/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9405a' }, + },{ + Name => 'Tag9405b', + # first byte must be 0x3a, 0xb3, 0x7e, 0x9a, 0x25, 0xe1, 0x76 or 0x8b + Condition => '$$valPt =~ /^[\x3a\xb3\x7e\x9a\x25\xe1\x76\x8b]/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9405b' }, + },{ + Name => 'Sony_0x9405', + %unknownCipherData, + }], + 0x9406 => [{ + Name => 'Tag9406', + # - first byte must be 0x01 or 0x02 (enciphered 0x01 or 0x08), + # or 0x03 (enc. 0x1b) for ILCE-6100/6300/6400/6500/6600/7M3/7RM2/7RM3/7RM4/7SM2/9/9M2, and ILCA-99M2 + # or 0x04 (enc. 0x40) for ILCE-6700, ZV-E1 + # third byte must be 0x02 or 0x03 (enciphered 0x08 or 0x1b) - ref JR + # (applies to most SLT/ILCA and NEX/ILCE models, but no DSC models) + Condition => '$$valPt =~ /^[\x01\x08\x1b].[\x08\x1b]/s or $$valPt =~ /^[\x40]/s', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9406' }, + },{ + Name => 'Sony_0x9406', + %unknownCipherData, + }], + 0x9407 => { + Name => 'Sony_0x9407', + %unknownCipherData, + }, + 0x9408 => { + Name => 'Sony_0x9408', + %unknownCipherData, + }, + 0x9409 => { + Name => 'Sony_0x9409', + %unknownCipherData, + }, + 0x940a => [{ + Name => 'Tag940a', + Condition => '$$self{Model} =~ /^(SLT-|HV)/', # but appears not valid for ILCA models ... + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag940a' }, + },{ + Name => 'Sony_0x940a', + %unknownCipherData, + }], + 0x940b => { + Name => 'Sony_0x940b', + %unknownCipherData, + }, + 0x940c => [{ + Name => 'Tag940c', + Condition => '$$self{Model} =~ /^(NEX-|ILCE-|ILME-|Lunar|ZV-E10|ZV-E1)\b/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag940c' }, + },{ + Name => 'Sony_0x940c', + %unknownCipherData, + }], + 0x940d => { + Name => 'Sony_0x940d', + %unknownCipherData, + }, +# 0x940e: 2nd byte = 0: no AFInfo, default for NEX/ILCE +# 2nd byte = 1: AFInfo for SLT/ILCA models (but also seen 1 for DSC-HX20W/HX300/WX70 ...) +# 2nd byte = 2: AFInfo for NEX/ILCE with LA-EA2/EA4 Phase-detect AF Adapter + 0x940e => [{ + Name => 'AFInfo', + Condition => '$$self{Model} =~ /^(SLT-|HV|ILCA-)/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::AFInfo' }, + },{ + Name => 'Tag940e', + Condition => '$$self{Model} =~ /^(NEX-|ILCE-|Lunar)/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag940e' }, + },{ + Name => 'Sony_0x940e', + %unknownCipherData, + }], + 0x940f => { + Name => 'Sony_0x940f', + %unknownCipherData, + }, + 0x9411 => { + Name => 'Sony_0x9411', + %unknownCipherData, + # 0x02 - int32u?: 1,3,5,7,9 (A77) + }, + 0x9416 => { # replaces 0x9405 for the Sony ILCE-7SM3, from July 2020 + Name => 'Sony_0x9416', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Tag9416' }, + }, + 0xb000 => { #8 + Name => 'FileFormat', + Writable => 'int8u', + Count => 4, + # dynamically set the file type to SR2 because we could have assumed ARW up till now + RawConv => q{ + $self->OverrideFileType($$self{TIFF_TYPE} = 'SR2') if $val eq '1 0 0 0'; + return $val; + }, + PrintConvColumns => 2, + PrintConv => { + '0 0 0 2' => 'JPEG', + '1 0 0 0' => 'SR2', + '2 0 0 0' => 'ARW 1.0', + '3 0 0 0' => 'ARW 2.0', + '3 1 0 0' => 'ARW 2.1', + '3 2 0 0' => 'ARW 2.2', #PH (NEX-5) + '3 3 0 0' => 'ARW 2.3', #PH (SLT-A65,SLT-A77) + '3 3 1 0' => 'ARW 2.3.1', #PH/JR (DSC-RX1R/RX100M2/Stellar2) + '3 3 2 0' => 'ARW 2.3.2', #JR (DSC-RX1RM2,ILCE-7SM2 - support for uncompressed 14-bit RAW) + '3 3 3 0' => 'ARW 2.3.3', #JR (ILCE-9) + '3 3 5 0' => 'ARW 2.3.5', #JR (DSC-HX99) + '4 0 0 0' => 'ARW 4.0', # (ILCE-7SM3) + '4 0 1 0' => 'ARW 4.0.1', #github#195 (ZV-E1) + # what about cRAW images? + }, + }, + 0xb001 => { # ref http://forums.dpreview.com/forums/read.asp?forum=1037&message=33609644 + # (ARW and SR2 images only until the SLT-A65V started writing them to JPEG too) + Name => 'SonyModelID', + Writable => 'int16u', + PrintConvColumns => 2, + PrintConv => { + #0 => 'DSC-HX80', #PH (and several other DSC models) + 2 => 'DSC-R1', + 256 => 'DSLR-A100', + 257 => 'DSLR-A900', + 258 => 'DSLR-A700', + 259 => 'DSLR-A200', + 260 => 'DSLR-A350', + 261 => 'DSLR-A300', + 262 => 'DSLR-A900 (APS-C mode)', #https://exiftool.org/forum/index.php/topic,3994.0.html + 263 => 'DSLR-A380/A390', #PH (A390) + 264 => 'DSLR-A330', + 265 => 'DSLR-A230', + 266 => 'DSLR-A290', #PH + 269 => 'DSLR-A850', + 270 => 'DSLR-A850 (APS-C mode)', #https://exiftool.org/forum/index.php/topic,3994.0.html + 273 => 'DSLR-A550', + 274 => 'DSLR-A500', #PH + 275 => 'DSLR-A450', #http://dev.exiv2.org/issues/show/0000611 + 278 => 'NEX-5', #PH + 279 => 'NEX-3', #PH + 280 => 'SLT-A33', #PH + 281 => 'SLT-A55 / SLT-A55V', #PH (A55 NC) + 282 => 'DSLR-A560', #PH + 283 => 'DSLR-A580', #https://exiftool.org/forum/index.php/topic,2881.0.html + 284 => 'NEX-C3', #PH + 285 => 'SLT-A35', #JR + 286 => 'SLT-A65 / SLT-A65V', #PH + 287 => 'SLT-A77 / SLT-A77V', #PH + 288 => 'NEX-5N', #PH + 289 => 'NEX-7', #PH (also Hasselblad Lunar, ref JR) + 290 => 'NEX-VG20E', #JR + 291 => 'SLT-A37', #JR + 292 => 'SLT-A57', #JR + 293 => 'NEX-F3', #PH + 294 => 'SLT-A99 / SLT-A99V', #JR (also Hasselblad HV) + 295 => 'NEX-6', #JR + 296 => 'NEX-5R', #JR + 297 => 'DSC-RX100', #PH (also Hasselblad Stellar, ref JR) + 298 => 'DSC-RX1', #JR + 299 => 'NEX-VG900', #JR + 300 => 'NEX-VG30E', #JR + 302 => 'ILCE-3000 / ILCE-3500', #JR + 303 => 'SLT-A58', #JR + 305 => 'NEX-3N', #PH + 306 => 'ILCE-7', #JR + 307 => 'NEX-5T', #JR + 308 => 'DSC-RX100M2', #JR + 309 => 'DSC-RX10', #JR + 310 => 'DSC-RX1R', #JR + 311 => 'ILCE-7R', #JR + 312 => 'ILCE-6000', #JR + 313 => 'ILCE-5000', #JR + 317 => 'DSC-RX100M3', #JR + 318 => 'ILCE-7S', #JR + 319 => 'ILCA-77M2', #IB + 339 => 'ILCE-5100', #JR + 340 => 'ILCE-7M2', #JR + 341 => 'DSC-RX100M4', #PH + 342 => 'DSC-RX10M2', #JR + 344 => 'DSC-RX1RM2', #JR + 346 => 'ILCE-QX1', #IB + 347 => 'ILCE-7RM2', #JR + 350 => 'ILCE-7SM2', #JR + 353 => 'ILCA-68', #IB + 354 => 'ILCA-99M2', #JR + 355 => 'DSC-RX10M3', #PH + 356 => 'DSC-RX100M5', #IB/JR + 357 => 'ILCE-6300', #IB + 358 => 'ILCE-9', #JR + 360 => 'ILCE-6500', #JR + 362 => 'ILCE-7RM3', #IB + 363 => 'ILCE-7M3', #JR/IB + 364 => 'DSC-RX0', #PH + 365 => 'DSC-RX10M4', #JR + 366 => 'DSC-RX100M6', #IB + 367 => 'DSC-HX99', #IB + 369 => 'DSC-RX100M5A', #JR + 371 => 'ILCE-6400', #IB + 372 => 'DSC-RX0M2', #JR + 373 => 'DSC-HX95', #github191 + 374 => 'DSC-RX100M7', #IB + 375 => 'ILCE-7RM4', #IB + 376 => 'ILCE-9M2', #JR + 378 => 'ILCE-6600', #IB/JR + 379 => 'ILCE-6100', #IB/JR + 380 => 'ZV-1', #JR + 381 => 'ILCE-7C', #JR + 382 => 'ZV-E10', #JR + 383 => 'ILCE-7SM3', + 384 => 'ILCE-1', #PH + 385 => 'ILME-FX3', #JR + 386 => 'ILCE-7RM3A', #JR + 387 => 'ILCE-7RM4A', #forum12542 + 388 => 'ILCE-7M4', #IB/JR + 389 => 'ZV-1F', #IB + 390 => 'ILCE-7RM5', #IB + 391 => 'ILME-FX30', #JR + 393 => 'ZV-E1', #JR + 394 => 'ILCE-6700', #JR + 395 => 'ZV-1M2', #JR + }, + }, + 0xb020 => { #2 + Name => 'CreativeStyle', # (called CreativeLook by the 7SM3, ref JR) + Writable => 'string', + # (all of these values have been observed, ref JR and PH) + # - this PrintConv is included to make these strings consistent with + # other CreativeStyle tags, and to facilitate the language translations + # - these values are always English, regardless of the camera language settings + PrintConv => { + OTHER => sub { shift }, # pass other values straight through + None => 'None', + AdobeRGB => 'Adobe RGB', + Real => 'Real', + Standard => 'Standard', + Vivid => 'Vivid', + Portrait => 'Portrait', + Landscape => 'Landscape', + Sunset => 'Sunset', + Nightview => 'Night View/Portrait', + BW => 'B&W', + Neutral => 'Neutral', + Clear => 'Clear', + Deep => 'Deep', + Light => 'Light', + Autumnleaves=> 'Autumn Leaves', + Sepia => 'Sepia', + # new for the ILCE-7SM3 (ref JR) + VV2 => 'Vivid 2', # (NC) + FL => 'FL', # "moody finish with sharp contrast and calm coloring as well as the impressive sky and colors of the greens" + IN => 'IN', # "matte textures by suppressing the contrast and saturation" + SH => 'SH', # "bright, transparent, soft, and vivid mood" + # (...also Custom Look 1-6, but don't know the values) + }, + }, + 0xb021 => { #2 + Name => 'ColorTemperature', + Writable => 'int32u', + PrintConv => '$val ? ($val==0xffffffff ? "n/a" : $val) : "Auto"', + PrintConvInv => '$val=~/Auto/i ? 0 : ($val eq "n/a" ? 0xffffffff : $val)', + }, + 0xb022 => { #7 + Name => 'ColorCompensationFilter', + Format => 'int32s', + Writable => 'int32u', # (written incorrectly as unsigned by Sony) + Notes => 'negative is green, positive is magenta', + }, + 0xb023 => { #PH (A100) - (set by mode dial) + Name => 'SceneMode', + Writable => 'int32u', + PrintConvColumns => 2, + PrintConv => \%Image::ExifTool::Minolta::minoltaSceneMode, + }, + 0xb024 => { #PH (A100) + Name => 'ZoneMatching', + Writable => 'int32u', + PrintConv => { + 0 => 'ISO Setting Used', + 1 => 'High Key', + 2 => 'Low Key', + }, + }, + 0xb025 => { #PH (A100) + Name => 'DynamicRangeOptimizer', + Writable => 'int32u', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Off', + 1 => 'Standard', + 2 => 'Advanced Auto', + 3 => 'Auto', # (A550) + 8 => 'Advanced Lv1', #JD + 9 => 'Advanced Lv2', #JD + 10 => 'Advanced Lv3', #JD + 11 => 'Advanced Lv4', #JD + 12 => 'Advanced Lv5', #JD + 16 => 'Lv1', # (NEX-5) + 17 => 'Lv2', + 18 => 'Lv3', + 19 => 'Lv4', + 20 => 'Lv5', + }, + }, + 0xb026 => { #PH (A100) + Name => 'ImageStabilization', + Writable => 'int32u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 0xffffffff => 'n/a', # (HX9V sweep panorama, ref JR) + }, + }, + 0xb027 => { #2 + Name => 'LensType', + Writable => 'int32u', + SeparateTable => 1, + # set to 65535 for E-mount lenses (values 0x80xx) + ValueConvInv => '($val & 0xff00) == 0x8000 ? 65535 : int($val)', + PrintConv => \%sonyLensTypes, + PrintInt => 1, + }, + 0xb028 => { #2 + # (used by the DSLR-A100) + Name => 'MinoltaMakerNote', + # must check for zero since apparently a value of zero indicates the IFD doesn't exist + # (dumb Sony -- they shouldn't write this tag if the IFD is missing!) + Condition => '$$valPt ne "\0\0\0\0"', + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Minolta::Main', + Start => '$val', + }, + }, + 0xb029 => { #2 (set by creative style menu) + Name => 'ColorMode', + Writable => 'int32u', + PrintConvColumns => 2, + PrintConv => \%Image::ExifTool::Minolta::sonyColorMode, + }, + 0xb02a => { + Name => 'LensSpec', + Format => 'undef', + Writable => 'int8u', + Count => 8, + Notes => q{ + like LensInfo, but also specifies lens features: DT, E, ZA, G, SSM, SAM, + OSS, STF, Reflex, Macro and Fisheye + }, + ValueConv => \&ConvLensSpec, + ValueConvInv => \&ConvInvLensSpec, + PrintConv => \&PrintLensSpec, + PrintConvInv => \&PrintInvLensSpec, + }, + 0xb02b => { #PH (A550 JPEG and A200, A230, A300, A350, A380, A700 and A900 ARW) + Name => 'FullImageSize', + Writable => 'int32u', + Count => 2, + # values stored height first, so swap to get "width height" + ValueConv => 'join(" ", reverse split(" ", $val))', + ValueConvInv => 'join(" ", reverse split(" ", $val))', + PrintConv => '$val =~ tr/ /x/; $val', + PrintConvInv => '$val =~ tr/x/ /; $val', + }, + 0xb02c => { #PH (A550 JPEG and A200, A230, A300, A350, A380, A700 and A900 ARW) + Name => 'PreviewImageSize', + Writable => 'int32u', + Count => 2, + ValueConv => 'join(" ", reverse split(" ", $val))', + ValueConvInv => 'join(" ", reverse split(" ", $val))', + PrintConv => '$val =~ tr/ /x/; $val', + PrintConvInv => '$val =~ tr/x/ /; $val', + }, + 0xb040 => { #2 + Name => 'Macro', + Writable => 'int16u', + RawConv => '$val == 65535 ? undef : $val', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'Close Focus', #9 + 65535 => 'n/a', #PH (A100) + }, + }, + 0xb041 => { #2 + Name => 'ExposureMode', + Writable => 'int16u', + RawConv => '$val == 65535 ? undef : $val', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Program AE', # (RX100 'Program','Sunset' - PH) + 1 => 'Portrait', #PH (HX1) + 2 => 'Beach', #9 + 3 => 'Sports', #9 + 4 => 'Snow', #9 + 5 => 'Landscape', + 6 => 'Auto', # (RX100 'Intelligent Auto' - PH) + 7 => 'Aperture-priority AE', + 8 => 'Shutter speed priority AE', + 9 => 'Night Scene / Twilight',#2/9 + 10 => 'Hi-Speed Shutter', #9 + 11 => 'Twilight Portrait', #9 (RX100 'Night Portrait' - PH) + 12 => 'Soft Snap/Portrait', #9 (TX7 'Soft Snap'; RX100/A37 'Portrait' but manuals say "reproduces soft skin tone" - PH) + 13 => 'Fireworks', #9 + 14 => 'Smile Shutter', #9 (T200) + 15 => 'Manual', + 18 => 'High Sensitivity', #9 + 19 => 'Macro', #JR + 20 => 'Advanced Sports Shooting', #9 + 29 => 'Underwater', #9 + # 30 seen for DSC-W110 and W390, maybe something with Face or Portrait ?? + 33 => 'Food', #9 + 34 => 'Sweep Panorama', #PH (HX1) + 35 => 'Handheld Night Shot', #PH (HX1/TX1, also called "Hand-held Twilight") + 36 => 'Anti Motion Blur', #PH (TX1) + 37 => 'Pet', #9 + 38 => 'Backlight Correction HDR', #9 + 39 => 'Superior Auto', #9 + 40 => 'Background Defocus', #PH (HX20V) + 41 => 'Soft Skin', #JR (HX9V) (HX200V Portrait - PH) + 42 => '3D Image', #JR (HX9V) + # 50 seen for DSC-W530 + 65535 => 'n/a', #PH (A100) + }, + }, + 0xb042 => { #9 + Name => 'FocusMode', + # Only FocusMode for older DSC models; + # Newest DSC models give only 0, many models of 'HX9V generation' give only 4 - + # these models give FocusMode in tag 0xb04e, and are excluded here. + Condition => q{ + ($$self{TagB042} = Get16u($valPt, 0)) and + (not $$self{MetaVersion} or $$self{MetaVersion} ne 'DC7303320222000') + }, + Notes => 'not valid for all models', + Writable => 'int16u', + RawConv => '$val == 65535 ? undef : $val', + PrintConv => { + # 0 - seen this for panorama shot + 1 => 'AF-S', # (called Single-AF by Sony) + 2 => 'AF-C', # (called Monitor-AF by Sony) + 4 => 'Permanent-AF', # (TX7,HX9V?) + 65535 => 'n/a', #PH (A100), also for DSC-W690 panorama shots + }, + }, + 0xb043 => [{ #9 + Name => 'AFAreaMode', + # AFAreaMode only for older models; + # exclude newest DSC models, which give AFAreaMode in Tag9402 0x0017 (eg. RX100 - PH) + Writable => 'int16u', + Condition => 'not $$self{MetaVersion} or $$self{MetaVersion} ne "DC7303320222000"', #JR + RawConv => '$val == 65535 ? undef : $val', + Notes => 'older models', + PrintConv => { + # 0 - (takes this value after camera reset, but can't be set back once changed) + 0 => 'Default', + 1 => 'Multi', + 2 => 'Center', + 3 => 'Spot', + 4 => 'Flexible Spot', # (T200) + 6 => 'Touch', + 14 => 'Tracking', #JR (HX9V) ("Manual" for the T200?, ref 9) + 15 => 'Face Tracking', # (not set when in face detect mode and no faces detected) + 65535 => 'n/a', #PH (A100) + }, + },{ #JR + Name => 'AFAreaMode', + # AFAreaMode for DSC-HX9V generation, having values that appear to be different from older models. + Writable => 'int16u', + Condition => '$$self{TagB042} and $$self{TagB042} != 0', + Notes => 'DSC-HX9V generation cameras', + PrintConv => { + 0 => 'Multi', + 1 => 'Center', + 2 => 'Spot', #(NC) seen for DSC-WX9 + 3 => 'Flexible Spot', + 10 => 'Selective (for Miniature effect)', # seen for Miniature effect of DSC-WX30 + 14 => 'Tracking', + 15 => 'Face Tracking', + 255 => 'Manual', + }, + }], + 0xb044 => { #9 + Name => 'AFIlluminator', + Writable => 'int16u', + RawConv => '$val == 65535 ? undef : $val', + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 65535 => 'n/a', #PH (A100) + }, + }, + # 0xb045 - int16u: 0 + # 0xb046 - int16u: 0 + 0xb047 => { #2 + Name => 'JPEGQuality', + Writable => 'int16u', + RawConv => '$val == 65535 ? undef : $val', + PrintConv => { + 0 => 'Standard', + 1 => 'Fine', + 2 => 'Extra Fine', #JR + 65535 => 'n/a', #PH (A100) + }, + }, + 0xb048 => { #9 + Name => 'FlashLevel', #JR other name, but values -9 to 9 match FlashExposureCompensation + Writable => 'int16s', + RawConv => '($val == -1 and $$self{Model} =~ /DSLR-A100\b/) ? undef : $val', + PrintConv => { + -32768 => 'Low', + -9 => '-9/3', #JR + -8 => '-8/3', #JR + -7 => '-7/3', #JR + -6 => '-6/3', #JR + -5 => '-5/3', #JR + -4 => '-4/3', #JR + -3 => '-3/3', + -2 => '-2/3', + -1 => '-1/3', # (for the A100, -1 is effectively 'n/a' - PH) + 0 => 'Normal', + 1 => '+1/3', + 2 => '+2/3', + 3 => '+3/3', + 4 => '+4/3', #JR (NC) + 5 => '+5/3', #JR (NC) + 6 => '+6/3', #JR + 9 => '+9/3', #JR + 128 => 'n/a', #JR (HX9V) + 32767 => 'High', + }, + }, + 0xb049 => { #9 + Name => 'ReleaseMode', + Writable => 'int16u', + RawConv => '$val == 65535 ? undef : $val', + PrintConv => { + 0 => 'Normal', # (ie. shutter button) + 2 => 'Continuous', + 5 => 'Exposure Bracketing', + 6 => 'White Balance Bracketing', # (HX5) + 8 => 'DRO Bracketing', #JR (ILCE-7RM2) + 65535 => 'n/a', #PH (A100) + }, + }, + 0xb04a => { #9 + Name => 'SequenceNumber', + Notes => 'shot number in continuous burst', + Writable => 'int16u', + RawConv => '$val == 65535 ? undef : $val', + PrintConv => { + 0 => 'Single', + 65535 => 'n/a', #PH (A100) + OTHER => sub { shift }, # pass all other numbers straight through + }, + }, + 0xb04b => { #2/PH + Name => 'Anti-Blur', + Writable => 'int16u', + RawConv => '$val == 65535 ? undef : $val', + PrintConv => { + 0 => 'Off', + 1 => 'On (Continuous)', #PH (NC) + 2 => 'On (Shooting)', #PH (NC) + 65535 => 'n/a', + }, + }, + # 0xb04c - rational64u: 10/10 (seen 5 for HX9V Manual-exposure images, ref JR) + # 0xb04d - int16u: 0 + # (the Kamisaka decoding of 0xb04e seems wrong - ref JR) + # 0xb04e => { #2 + # Name => 'LongExposureNoiseReduction', + # Notes => 'LongExposureNoiseReduction for other models', + # Writable => 'int16u', + # RawConv => '$val == 65535 ? undef : $val', + # PrintConv => { + # 0 => 'Off', + # 1 => 'On', + # 2 => 'On 2', #PH (TX10, TX100, WX9, WX10, etc) + # # 4 - seen this (CX360E, CX700E) + # 65535 => 'n/a', #PH (A100) + # }, + # }, + 0xb04e => { #PH (RX100) - but not in RX100M3 anymore (ref JR) + Name => 'FocusMode', + Condition => '$$self{MetaVersion} and $$self{MetaVersion} eq "DC7303320222000"', #JR + Notes => 'valid for DSC-HX9V generation and newer', + Writable => 'int16u', + PrintConv => { + 0 => 'Manual', + # 1 - seen for DSC-WX7 burst, HDR-CX130E/CX560E + 2 => 'AF-S', + 3 => 'AF-C', + # 4 - seen for HDR-CX360E/CX700E + 5 => 'Semi-manual', #JR (HX9V) + 6 => 'DMF', # "Direct Manual Focus" + }, + }, + 0xb04f => { #PH (TX1) + Name => 'DynamicRangeOptimizer', + Writable => 'int16u', + Priority => 0, # (unreliable for the A77) + PrintConv => { + 0 => 'Off', + 1 => 'Standard', + 2 => 'Plus', + # 8 for HDR models - what does this mean? + }, + }, + 0xb050 => { #PH (RX100) + Name => 'HighISONoiseReduction2', + Condition => '$$self{Model} =~ /^(DSC-|Stellar)/', + Notes => 'DSC models only', + Writable => 'int16u', + PrintConv => { + 0 => 'Normal', + 1 => 'High', + 2 => 'Low', + 3 => 'Off', #JR + # it seems that all SLT and NEX models give n/a here (ref JR) + 65535 => 'n/a', + }, + }, + # 0xb051 - int16u: 0 + 0xb052 => { #PH (TX1) + Name => 'IntelligentAuto', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + 2 => 'Advanced', #9 + }, + }, + # 0xb053 - int16u: normally 0, but got 1 for a superior auto backlight picture (RX100) + 0xb054 => { #PH/9/JR (TX1,TX7,RX100,HX9V) + Name => 'WhiteBalance', + Writable => 'int16u', + Notes => q{ + decoding of the Fluorescent settings matches the EXIF standard, which is + different than the names used by Sony for some models + }, + PrintConv => { + 0 => 'Auto', + 4 => 'Custom', # (manual) + 5 => 'Daylight', + 6 => 'Cloudy', + # PrintConv names matching Exif Fluorescent LightSource names (ref JR) + # (Sony uses conflicting names for some models) + 7 => 'Cool White Fluorescent', # (RX100) (TX7/HX9V "Fluorescent 1 (White)", ref 9/JR) + 8 => 'Day White Fluorescent', # (RX100) (TX7/HX9V "Fluorescent 2 (Natural White)", ref 9/JR) + 9 => 'Daylight Fluorescent', # (RX100) (TX7/HX9V "Fluorescent 3 (Day White)", ref 9/JR) + 10 => 'Incandescent2', #JR (HX9V) + 11 => 'Warm White Fluorescent', + 14 => 'Incandescent', + 15 => 'Flash', + 17 => 'Underwater 1 (Blue Water)', #9 + 18 => 'Underwater 2 (Green Water)', #9 + 19 => 'Underwater Auto', #JR + }, + }, +); + +# "SEMC MS" maker notes +%Image::ExifTool::Sony::Ericsson = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => 'Maker notes found in images from some Sony Ericsson phones.', + 0x2000 => { + Name => 'MakerNoteVersion', + Writable => 'undef', + Count => 4, + }, + 0x201 => { + Name => 'PreviewImageStart', + IsOffset => 1, + MakerPreview => 1, # force preview inside maker notes + OffsetPair => 0x202, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + Notes => 'a small 320x200 preview image', + }, + 0x202 => { + Name => 'PreviewImageLength', + OffsetPair => 0x201, + DataTag => 'PreviewImage', + Writable => 'int32u', + WriteGroup => 'MakerNotes', + Protected => 2, + }, +); + +# camera information for the A700/A850/A900 (ref JR) +%Image::ExifTool::Sony::CameraInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Camera information for the A700, A850 and A900.', + 0x00 => { + Name => 'LensSpec', + # the A700/A850/A900 use a different int16 byte ordering! - PH + Format => 'undef[8]', + ValueConv => sub { + my $val = shift;; + return ConvLensSpec(pack('v*', unpack('n*', $val))); + }, + ValueConvInv => sub { + my $val = shift; + return pack('v*', unpack('n*', ConvInvLensSpec($val))); + }, + PrintConv => \&PrintLensSpec, + PrintConvInv => \&PrintInvLensSpec, + }, + 0x0014 => { + Name => 'FocusModeSetting', + Notes => 'FocusModeSetting for the A700, A850 and A900', + PrintConv => { + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', + 4 => 'DMF', + }, + }, + 0x0015 => { # the AF Point selected in AFAreaMode=Local or Spot; always '0' for AFAreaMode=Wide + Name => 'AFPointSelected', + PrintConvColumns => 2, + PrintConv => { + 0 => 'Auto', + 1 => 'Center', + 2 => 'Top', + 3 => 'Upper-right', + 4 => 'Right', + 5 => 'Lower-right', + 6 => 'Bottom', + 7 => 'Lower-left', + 8 => 'Left', + 9 => 'Upper-left', + 10 => 'Far Right', # only given by A700 + 11 => 'Far Left', # only given by A700 + }, + }, + # 0x0019 - AF sensor used for focusing for A700/A850/A900: + # + # A700 AF sensor layout: A850/A900 AF sensor layout: + # + # - *-* - = AF sensor + # | | | | * = assist sensor + # - *-* o = F2.8 sensor + # | | |o| | | | * |o| * | A700 center: double-cross + F2.8 + # - *-* A850 center: double-cross + F2.8 + 4 assist + # | | | | + # - *-* + # + # Following values seen for A700/A850 in AFAreaMode=Local or Spot: (other values only seen in "Wide") + # + # 16 + # 0 19 + # 13 + # 3 1 22 20 18 + # 8 + # 2 21 + # 5 + # + # Note 1: A850/A900 AFPoint Selected 'Left'/'Right' (in 0x0015) corresponds in position (see diagram) + # to A700 Local AFPoint 'Far Left'/'Far Right', and gives 'Far Left'/'Far Right' in 0x0019. + # Note 2: A700 in "Wide" also gives all 23 values in 0x0019, although it doesn't have assist-points ... + 0x0019 => { # the AF sensor used for focusing + Name => 'AFPoint', + PrintConv => { + 0 => 'Upper-left', + 1 => 'Left', + 2 => 'Lower-left', + 3 => 'Far Left', + 4 => 'Bottom Assist-left', #(NC) + 5 => 'Bottom', + 6 => 'Bottom Assist-right', #(NC) + # values 7-14: 8 center points: 4 from double-cross + 4 assist; 7-10 appear horizontal, 11-14 vertical + 7 => 'Center (7)', #(NC) + 8 => 'Center (horizontal)', + 9 => 'Center (9)', #(NC) + 10 => 'Center (10)', #(NC) + 11 => 'Center (11)', #(NC) + 12 => 'Center (12)', #(NC) + 13 => 'Center (vertical)', + 14 => 'Center (14)', #(NC) + 15 => 'Top Assist-left', #(NC) + 16 => 'Top', + 17 => 'Top Assist-right', #(NC) + 18 => 'Far Right', + 19 => 'Upper-right', + 20 => 'Right', + 21 => 'Lower-right', + 22 => 'Center F2.8', + }, + }, + # AF Status for A700/A850/A900, which have different sensor layout + # and different int16 byte ordering + 0x001e => { Name => 'AFStatusActiveSensor', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0020 => { Name => 'AFStatusUpper-left', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0022 => { Name => 'AFStatusLeft', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0024 => { Name => 'AFStatusLower-left', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0026 => { Name => 'AFStatusFarLeft', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0028 => { Name => 'AFStatusBottomAssist-left', %Image::ExifTool::Minolta::afStatusInfo }, + 0x002a => { Name => 'AFStatusBottom', %Image::ExifTool::Minolta::afStatusInfo }, + 0x002c => { Name => 'AFStatusBottomAssist-right', %Image::ExifTool::Minolta::afStatusInfo }, + 0x002e => { Name => 'AFStatusCenter-7', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0030 => { Name => 'AFStatusCenter-horizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0032 => { Name => 'AFStatusCenter-9', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0034 => { Name => 'AFStatusCenter-10', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0036 => { Name => 'AFStatusCenter-11', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0038 => { Name => 'AFStatusCenter-12', %Image::ExifTool::Minolta::afStatusInfo }, + 0x003a => { Name => 'AFStatusCenter-vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x003c => { Name => 'AFStatusCenter-14', %Image::ExifTool::Minolta::afStatusInfo }, + 0x003e => { Name => 'AFStatusTopAssist-left', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0040 => { Name => 'AFStatusTop', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0042 => { Name => 'AFStatusTopAssist-right', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0044 => { Name => 'AFStatusFarRight', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0046 => { Name => 'AFStatusUpper-right', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0048 => { Name => 'AFStatusRight', %Image::ExifTool::Minolta::afStatusInfo }, + 0x004a => { Name => 'AFStatusLower-right', %Image::ExifTool::Minolta::afStatusInfo }, + 0x004c => { Name => 'AFStatusCenterF2-8', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0130 => { + Name => 'AFMicroAdjValue', + Condition => '$$self{Model} =~ /^DSLR-A(850|900)\b/', + ValueConv => '$val - 20', + ValueConvInv => '$val + 20', + }, + 0x0131 => { + Name => 'AFMicroAdjMode', + Condition => '$$self{Model} =~ /^DSLR-A(850|900)\b/', + Mask => 0x80, + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 305.1 => { # (0x131) + Name => 'AFMicroAdjRegisteredLenses', + Notes => 'number of registered lenses with a non-zero AFMicroAdjValue', + Condition => '$$self{Model} =~ /^DSLR-A(850|900)\b/', + Mask => 0x7f, + }, + # 0x0166 - 40 x 128 int8u values: AF Info Blocks for A850 and A900, not for A700 +); + +# camera information for other DSLR models (ref JR) +%Image::ExifTool::Sony::CameraInfo2 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + Camera information for the DSLR-A200, A230, A290, A300, A330, A350, A380 and + A390. + }, + 0x00 => { + Name => 'LensSpec', + Format => 'undef[8]', + ValueConv => \&ConvLensSpec, + ValueConvInv => \&ConvInvLensSpec, + PrintConv => \&PrintLensSpec, + PrintConvInv => \&PrintInvLensSpec, + }, + # 0x0010 - maybe to do with AFStatus: 0 na./Manual, 4 Failed, 16 Tracking, 64 Focused + 0x0014 => { + Name => 'AFPointSelected', + PrintConvColumns => 2, + PrintConv => { #JR (NC) same list as A100, A700/A900, as all have 9 point AF + 0 => 'Auto', + 1 => 'Center', + 2 => 'Top', + 3 => 'Upper-right', + 4 => 'Right', + 5 => 'Lower-right', + 6 => 'Bottom', + 7 => 'Lower-left', + 8 => 'Left', + 9 => 'Upper-left', + }, + }, + 0x0015 => { + Name => 'FocusModeSetting', + Notes => 'FocusModeSetting for other models', + PrintConv => { + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', + 4 => 'DMF', + }, + }, + # 0x0018 - AF sensor used for focusing for A200/A230/A290/A300/A330/A350/A380/A390; AF sensor layout: + # + # - - = AF sensor + # | | + = cross sensor + # + # - + - + # + # | | + # - + # + 0x0018 => { # used A100 list which appears to match + Name => 'AFPoint', + PrintConv => { + 0 => 'Top-right', + 1 => 'Bottom-right', + 2 => 'Bottom', + 3 => 'Middle Horizontal', + 4 => 'Center Vertical', + 5 => 'Top', + 6 => 'Top-left', + 7 => 'Bottom-left', + }, + }, + # AF Status for A200/A230/A290/A300/A330/A350/A380/A390: analogous to A100 in Minolta.pm + 0x001b => { Name => 'AFStatusActiveSensor', %Image::ExifTool::Minolta::afStatusInfo }, + 0x001d => { Name => 'AFStatusTop-right', %Image::ExifTool::Minolta::afStatusInfo }, + 0x001f => { Name => 'AFStatusBottom-right', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0021 => { Name => 'AFStatusBottom', %Image::ExifTool::Minolta::afStatusInfo }, + # MiddleHorizontal is any of the 3 central horizontal sensors + 0x0023 => { Name => 'AFStatusMiddleHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0025 => { Name => 'AFStatusCenterVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0027 => { Name => 'AFStatusTop', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0029 => { Name => 'AFStatusTop-left', %Image::ExifTool::Minolta::afStatusInfo }, + 0x002b => { Name => 'AFStatusBottom-left', %Image::ExifTool::Minolta::afStatusInfo }, + # the 3 MiddleHorizontal sensors + 0x002d => { Name => 'AFStatusLeft', %Image::ExifTool::Minolta::afStatusInfo }, + 0x002f => { Name => 'AFStatusCenterHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0031 => { Name => 'AFStatusRight', %Image::ExifTool::Minolta::afStatusInfo }, + # 0x0166 - 59 x 96 int8u values: AF Info Blocks for A230/A290/A330/A380/A390 + # 0x0182 - 58 x 88 int8u values: AF Info Blocks for A200/A300/A350 +); + +# Camera information for the A55 (ref PH) +# (also valid for A33, A35, A560, A580 - ref JR) +%Image::ExifTool::Sony::CameraInfo3 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + IS_SUBDIR => [ 0x23 ], + NOTES => q{ + Camera information stored by the A33, A35, A55, A450, A500, A550, A560, + A580, NEX-3/5/5C/C3 and VG10E. Some tags are valid only for some of these + models. + }, + 0x00 => { #JR + Name => 'LensSpec', + Condition => '$$self{Model} !~ /^NEX-5C/', + Format => 'undef[8]', + ValueConv => \&ConvLensSpec, + ValueConvInv => \&ConvInvLensSpec, + PrintConv => \&PrintLensSpec, + PrintConvInv => \&PrintInvLensSpec, + }, + 0x0e => { #JR + Name => 'FocalLength', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + Format => 'int16u', + Priority => 0, + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ mm//; $val', + }, + 0x10 => { #JR + Name => 'FocalLengthTeleZoom', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + Format => 'int16u', + ValueConv => '$val * 2 / 3', + ValueConvInv => 'int($val * 3 / 2 + 0.5)', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ mm//; $val', + }, +# +# Note: +# The below AF decoding covers cameras with 2 different AF systems, with overlapping offsets ! +# 1) DSLR-A450/A500/A550 with 9 point AF system: decoding/offsets identical to A200 - A390 in CameraInfo +# 2) SLT-A33/A35/A55 and DSLR-A560/A580 with 15 point AF system: similar/more info but at different offsets +# + 0x14 => { #JR + Name => 'AFPointSelected', + Condition => '$$self{Model} =~ /^(DSLR-A(450|500|550))\b/', + # (these cameras have a 9-point AF system, ref JR) + PrintConvColumns => 2, + PrintConv => { + 0 => 'Auto', # (seen in Wide mode and for Manual Focus) + 1 => 'Center', # seen for AFArea=Spot + 2 => 'Top', + 3 => 'Upper-right', + 4 => 'Right', + 5 => 'Lower-right', + 6 => 'Bottom', + 7 => 'Lower-left', + 8 => 'Left', + 9 => 'Upper-left', + }, + }, + 0x15 => { #JR + Name => 'FocusMode', + Condition => '$$self{Model} =~ /^(DSLR-A(450|500|550))\b/', + PrintConv => { + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', + }, + }, + 0x18 => { #JR + Name => 'AFPoint', + Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', + PrintConv => { + 0 => 'Top-right', + 1 => 'Bottom-right', + 2 => 'Bottom', + 3 => 'Middle Horizontal', + 4 => 'Center Vertical', + 5 => 'Top', + 6 => 'Top-left', + 7 => 'Bottom-left', + }, + }, + 0x19 => { #JR + Name => 'FocusStatus', + Condition => '$$self{Model} =~ /^(SLT-|DSLR-A(560|580))\b/', + Notes => 'not valid with Contrast AF or for NEX models', + # seen the following values: + # 0 with MF (A35, A55V-HDR, A560, A580), non-AF lens (A35), and A580 Contrast-AF + # 4 with MF (A33, A55V), and A580 Contrast-AF + # 16 with AF-C (or AF-A) and focus OK + # 24 with AF-C (or AF-A) and unsharp or fast moving subject e.g. bird in flight + # 64 with AF-S (or AF-A) and focus OK + PrintConv => { + 0 => 'Manual - Not confirmed (0)', + 4 => 'Manual - Not confirmed (4)', + 16 => 'AF-C - Confirmed', + 24 => 'AF-C - Not Confirmed', + 64 => 'AF-S - Confirmed', + }, + }, + 0x1b => { #JR + Name => 'AFStatusActiveSensor', + Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', + %Image::ExifTool::Minolta::afStatusInfo, + }, + 0x1c => { + Name => 'AFPointSelected', # (v8.88: renamed from LocalAFAreaPointSelected) + Condition => '$$self{Model} =~ /^(SLT-|DSLR-A(560|580))\b/', + Notes => 'not valid for Contrast AF', #JR + # (all of these cameras have an 15-point three-cross AF system, ref JR) + PrintConvColumns => 2, + PrintConv => { + 0 => 'Auto', # (seen in Wide mode) + 1 => 'Center', + 2 => 'Top', + 3 => 'Upper-right', + 4 => 'Right', + 5 => 'Lower-right', + 6 => 'Bottom', + 7 => 'Lower-left', + 8 => 'Left', + 9 => 'Upper-left', + 10 => 'Far Right', + 11 => 'Far Left', + 12 => 'Upper-middle', + 13 => 'Near Right', + 14 => 'Lower-middle', + 15 => 'Near Left', + }, + }, + 0x1d => [ + { + Name => 'FocusMode', + Condition => '$$self{Model} =~ /^(SLT-|DSLR-A(560|580))\b/', + PrintConv => { + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', + }, + },{ #JR + Name => 'AFStatusTop-right', + Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', + %Image::ExifTool::Minolta::afStatusInfo, + }, + ], + 0x1f => { #JR + Name => 'AFStatusBottom-right', + Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', + %Image::ExifTool::Minolta::afStatusInfo, + }, + 0x20 => { #JR + Name => 'AFPoint', # (v8.88: renamed from LocalAFAreaPointUsed) + Condition => '$$self{Model} =~ /^(SLT-|DSLR-A(560|580))\b/', + Notes => 'the AF sensor used for focusing. Not valid for Contrast AF', + PrintConvColumns => 2, + PrintConv => { + %afPoint15, + 255 => '(none)', #PH (A55, guess; also A35 with non-AF lens, ref JR) + }, + }, + 0x21 => [ #JR + { + Name => 'AFStatusActiveSensor', + Condition => '$$self{Model} =~ /^(SLT-|DSLR-A(560|580))\b/', + %Image::ExifTool::Minolta::afStatusInfo, + },{ + Name => 'AFStatusBottom', + Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', + %Image::ExifTool::Minolta::afStatusInfo, + }, + ], + 0x23 => [ #JR + { + Name => 'AFStatus15', + Condition => '$$self{Model} =~ /^(SLT-|DSLR-A(560|580))\b/', + Format => 'int16s[18]', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::AFStatus15' }, + },{ + Name => 'AFStatusMiddleHorizontal', # MiddleHorizontal is any of the 3 central horizontal sensors + Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', + %Image::ExifTool::Minolta::afStatusInfo, + }, + ], + 0x25 => { Name => 'AFStatusCenterVertical', Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', %Image::ExifTool::Minolta::afStatusInfo }, + 0x27 => { Name => 'AFStatusTop', Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', %Image::ExifTool::Minolta::afStatusInfo }, + 0x29 => { Name => 'AFStatusTop-left', Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', %Image::ExifTool::Minolta::afStatusInfo }, + 0x2b => { Name => 'AFStatusBottom-left', Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', %Image::ExifTool::Minolta::afStatusInfo }, + # the 3 MiddleHorizontal sensors: + 0x2d => { Name => 'AFStatusLeft', Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', %Image::ExifTool::Minolta::afStatusInfo }, + 0x2f => { Name => 'AFStatusCenterHorizontal', Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', %Image::ExifTool::Minolta::afStatusInfo }, + 0x31 => { Name => 'AFStatusRight', Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)\b/', %Image::ExifTool::Minolta::afStatusInfo }, + # 0x0166 - starting here there are 96 AF Info blocks of 155 bytes each for the SLT-A33/A35/A55 and DSLR-A560/A580, + # starting here there are 86 AF Info blocks of 174 bytes each for the DSLR-A450/A500/A550, + # but NOT for NEX, and not for the A580 in Contrast-AF mode (ref JR) + # The 43rd byte of each block for A580 appears to be the AFPoint as in offset 0x20, + # possibly also 73rd and 74th byte +); + +# Camera information for other models (ref PH) +%Image::ExifTool::Sony::CameraInfoUnknown = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, +); + +# white balance and other camera information (ref PH) +%Image::ExifTool::Sony::FocusInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PRIORITY => 0, + NOTES => q{ + More camera settings and focus information decoded for models such as the + A200, A230, A290, A300, A330, A350, A380, A390, A700, A850 and A900. + }, + 0x0e => [{ #7/JR + Name => 'DriveMode2', + Condition => '$$self{Model} =~ /^DSLR-A(230|290|330|380|390)$/', + Notes => 'A230, A290, A330, A380 and A390', + ValueConvInv => '$val', + PrintHex => 1, + PrintConv => { # (values confirmed for specified models - PH) + 0x01 => 'Single Frame', # (A230,A330,A380) + 0x02 => 'Continuous High', #PH (A230,A330) + 0x04 => 'Self-timer 10 sec', # (A230) + 0x05 => 'Self-timer 2 sec, Mirror Lock-up', # (A230,A290,A330,A380,390) + 0x07 => 'Continuous Bracketing', # (A230,A330) + 0x0a => 'Remote Commander', # (A230) + 0x0b => 'Continuous Self-timer', # (A230,A330) + }, + },{ + Name => 'DriveMode2', + Notes => 'A200, A300, A350, A700, A850 and A900', + ValueConvInv => '$val', + PrintHex => 1, + PrintConv => { + 0x01 => 'Single Frame', + 0x02 => 'Continuous High', # A700/A900; not on A850 + 0x12 => 'Continuous Low', #JR + 0x04 => 'Self-timer 10 sec', + 0x05 => 'Self-timer 2 sec, Mirror Lock-up', + 0x06 => 'Single-frame Bracketing', + 0x07 => 'Continuous Bracketing', + 0x18 => 'White Balance Bracketing Low', #JR + 0x28 => 'White Balance Bracketing High', #JR + 0x19 => 'D-Range Optimizer Bracketing Low', #JR + 0x29 => 'D-Range Optimizer Bracketing High', #JR + 0x0a => 'Remote Commander', #JR + 0x0b => 'Mirror Lock-up', #JR (A850/A900; not on A700) + }, + }], + 0x10 => { #JR (1 and 2 inverted!) + Name => 'Rotation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 270 CW', + 2 => 'Rotate 90 CW', + }, + }, + 0x14 => { + Name => 'ImageStabilizationSetting', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x15 => { #7 + Name => 'DynamicRangeOptimizerMode', + PrintConv => { + 0 => 'Off', + 1 => 'Standard', + 2 => 'Advanced Auto', + 3 => 'Advanced Level', + }, + }, + 0x2b => { #JR seen 2,1,3 for both WB and DRO bracketing + Name => 'BracketShotNumber', + Notes => 'WB and DRO bracketing', + }, + 0x2c => { #JR + Name => 'WhiteBalanceBracketing', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'High', + }, + }, + 0x2d => { #JR seen 2,1,3 for both WB and DRO bracketing + Name => 'BracketShotNumber2', + }, + 0x2e => { #JR + Name => 'DynamicRangeOptimizerBracket', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'High', + }, + }, + 0x2f => { #JR seen 0,1,2 and 0,1,2,3,4 for 3 and 5 image bracketing sequences + Name => 'ExposureBracketShotNumber', + }, + 0x3f => { #JR + Name => 'ExposureProgram', + SeparateTable => 'ExposureProgram', + PrintConv => \%sonyExposureProgram, + }, + 0x41 => { #JR style actually used (combination of mode dial + creative style menu) + Name => 'CreativeStyle', + PrintConvColumns => 2, + PrintConv => { + 1 => 'Standard', + 2 => 'Vivid', + 3 => 'Portrait', + 4 => 'Landscape', + 5 => 'Sunset', + 6 => 'Night View/Portrait', + 8 => 'B&W', + 9 => 'Adobe RGB', # A700 + 11 => 'Neutral', + 12 => 'Clear', #7 + 13 => 'Deep', #7 + 14 => 'Light', #7 + 15 => 'Autumn Leaves', #7 + 16 => 'Sepia', #7 + }, + }, + 0x6d => { + Name => 'ISOSetting', + ValueConv => '$val ? exp(($val/8-6)*log(2))*100 : $val', + ValueConvInv => '$val ? 8*(log($val/100)/log(2)+6) : $val', + PrintConv => '$val ? sprintf("%.0f",$val) : "Auto"', + PrintConvInv => '$val =~ /auto/i ? 0 : $val', + }, + 0x6f => { + Name => 'ISO', + ValueConv => '$val ? exp(($val/8-6)*log(2))*100 : $val', + ValueConvInv => '$val ? 8*(log($val/100)/log(2)+6) : $val', + PrintConv => '$val ? sprintf("%.0f",$val) : "Auto"', + PrintConvInv => '$val =~ /auto/i ? 0 : $val', + }, + 0x77 => { #JR + Name => 'DynamicRangeOptimizerMode', + PrintConv => { + 0 => 'Off', + 1 => 'Standard', + 2 => 'Advanced Auto', + 3 => 'Advanced Level', + }, + }, + 0x79 => 'DynamicRangeOptimizerLevel', +# 0x06f1 - int16u LensType, Condition => '$$self{Model} =~ /^DSLR-A(700|850|900)$/', +# 0x4a81 - int16u LensType, Condition => '$$self{Model} !~ /^DSLR-A(700|850|900)$/', +# 0x4a84 - int16uRev LensType, Condition => '$$self{Model} =~ /^DSLR-A(700|850|900)$/', + 0x0846 => { #13 + Name => 'ShutterCount', # (=ImageCount for these models) + Condition => '$$self{Model} =~ /^DSLR-A(230|290|330|380|390|850|900)$/', + Format => 'int32u', + Notes => 'only valid for some DSLR models', + RawConv => '$val & 0x00ffffff', #PH + }, + 0x09bb => { #PH (validated only for DSLR-A850) + Name => 'FocusPosition', + Condition => '$$self{Model} =~ /^DSLR-A(200|230|290|300|330|350|380|390|700|850|900)$/', + Notes => 'only valid for some DSLR models', + # 128 = infinity -- see Composite:FocusDistance below + }, + 0x1110 => { # (9600 bytes: 4 sets of 40x30 int16u values in the range 0-8191) + Name => 'TiffMeteringImage', + Format => 'undef[9600]', + Notes => q{ + 13-bit RBGG (?) 40x30 pixels, presumably metering info, extracted as a + 16-bit TIFF image; + }, + ValueConv => sub { + my ($val, $et) = @_; + return undef unless length $val >= 9600; + return \ "Binary data 7404 bytes" unless $et->Options('Binary'); + my @dat = unpack('n*', $val); + # TIFF header for a 16-bit RGB 10dpi 40x30 image + $val = Image::ExifTool::MakeTiffHeader(40,30,3,16,10); + # re-order data to RGB pixels + my ($i, @val); + for ($i=0; $i<40*30; ++$i) { + # data is 13-bit (max 8191), shift left to fill 16 bits + # (typically, this gives a very dark image since the data should + # really be anti-logged to convert from EV to perceived brightness) +# push @val, $dat[$i]<<3, $dat[$i+2400]<<3, $dat[$i+1200]<<3; + push @val, int(5041.1*log($dat[$i]+1)/log(2)), int(5041.1*log($dat[$i+2400]+1)/log(2)), int(5041.1*log($dat[$i+1200]+1)/log(2)); + } + $val .= pack('v*', @val); # add TIFF strip data + return \$val; + }, + }, +); + +# more camera setting information (ref JR) +# - many of these tags are the same as in CameraSettings3 +%Image::ExifTool::Sony::MoreInfo = ( + PROCESS_PROC => \&ProcessMoreInfo, + WRITE_PROC => \&ProcessMoreInfo, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + More camera settings information decoded for the A450, A500, A550, A560, + A580, A33, A35, A55, NEX-3/5/C3 and VG10E. + }, + 0x0001 => { # (256 bytes) + Name => 'MoreSettings', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MoreSettings' }, + }, + # (byte sizes for a single A580 image -- not checked for other images) + 0x0002 => [ # (256 bytes) + { + Name => 'FaceInfo', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::FaceInfo' }, + },{ + Name => 'FaceInfoA', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::FaceInfoA' }, + }, + ], + # 0x0101: 512 bytes + # 0x0102: 1804 bytes + # 0x0103: 176 bytes + # 0x0104: 1088 bytes + # 0x0105: 160 bytes (all zero unless flash is used, ref JR) + # 0x0106: 256 bytes (faces detected if first byte is non-zero? ref JR) + 0x0107 => { # (7200 bytes: 3 sets of 40x30 int16u values in the range 0-1023) + Name => 'TiffMeteringImage', + Notes => q{ + 10-bit RGB data from the 1200 AE metering segments, extracted as a 16-bit + TIFF image + }, + ValueConv => sub { + my ($val, $et) = @_; + return undef unless length $val >= 7200; + return \ "Binary data 7404 bytes" unless $et->Options('Binary'); + my @dat = unpack('v*', $val); + # TIFF header for a 16-bit RGB 10dpi 40x30 image + $val = Image::ExifTool::MakeTiffHeader(40,30,3,16,10); + # re-order data to RGB pixels + my ($i, @val); + for ($i=0; $i<40*30; ++$i) { + # data is 10-bit (max 1023), shift left to fill 16 bits + # (typically, this gives a very dark image since the data should + # really be anti-logged to convert from EV to perceived brightness) + push @val, $dat[$i]<<6, $dat[$i+1200]<<6, $dat[$i+2400]<<6; + } + $val .= pack('v*', @val); # add TIFF strip data + return \$val; + }, + }, + # 0x0108: 140 bytes + # 0x0109: 256 bytes + # 0x010a: 256 bytes + # 0x0306: 276 bytes + # 0x0307: 256 bytes + # 0x0308: 96 bytes + # 0x0309: 112 bytes + # 0xffff: 788 bytes + 0x0201 => { # (368 bytes) + Name => 'MoreInfo0201', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MoreInfo0201' }, + }, + # 0x0202: 144 bytes + # 0x0401: 4608 bytes + 0x0401 => { + Name => 'MoreInfo0401', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MoreInfo0401' }, + }, +); + +%Image::ExifTool::Sony::MoreInfo0201 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PRIORITY => 0, +# 0x005d - also from 0 - 255, in sync with but lower than 0x005e, depending on FocalLength +# 0x005e => { +# # FocusPosition for A560/A580/A33/A35/A55V +# # seen values from 80 - 255 (= infinity) -- see Composite:FocusDistance2 below +# Name => 'FocusPosition2_0201', +# Condition => '$$self{Model} !~ /^(NEX-|DSLR-(A450|A500|A550)$)/', +# }, +# 0x0093 - also from 0 - 255, in sync with but lower than 0x0094, depending on FocalLength +# 0x0094 => { +# # FocusPosition for A450/A500/A550 +# # seen values from 80 - 255 (= infinity) -- see Composite:FocusDistance2 below +# Name => 'FocusPosition2_0201', +# Condition => '$$self{Model} =~ /^(DSLR-(A450|A500|A550)$)/', +# }, + 0x011b => { #13 + Name => 'ImageCount', + Condition => '$$self{Model} !~ /^DSLR-A(450|500|550)$/', #JR + Format => 'int32u', + Notes => 'not valid for the A450, A500 or A550', + RawConv => '$val & 0x00ffffff', + }, + 0x0125 => { #13 + Name => 'ShutterCount', + Condition => '$$self{Model} !~ /^DSLR-A(450|500|550)$/', #JR + Format => 'int32u', + Notes => 'not valid for the A450, A500 or A550', + RawConv => '$val & 0x00ffffff', + }, + 0x014a => { #13 + Name => 'ShutterCount', # (=ImageCount for these models) + Condition => '$$self{Model} =~ /^DSLR-A(450|500|550)$/', #JR + Format => 'int32u', + Notes => 'A450, A500 and A550 only', + RawConv => '$val & 0x00ffffff', + }, +); + +%Image::ExifTool::Sony::MoreInfo0401 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PRIORITY => 0, + 0x044e => { #JR + Name => 'ShotNumberSincePowerUp', + Condition => '$$self{Model} !~ /^NEX-(3|5)$/', + Format => 'int32u', + Notes => 'Not valid for the NEX-3 or NEX-5', + RawConv => '$val & 0x00ffffff', + }, +# 0x101e - int16u LensType Condition => '$$self{Model} =~ /^SLT-A(33|55V)/', +# 0x1022 - int16u LensType Condition => '$$self{Model} =~ /^DSLR-A(560|580)/', +# 0x102a - int16u LensType Condition => '$$self{Model} =~ /^(SLT-A35|NEX-C3)/', + +# 0x10a8 - int16u LensType Condition => '$$self{Model} =~ /^SLT-A(33|55V)/', +# 0x10ac - int16u LensType Condition => '$$self{Model} =~ /^DSLR-A(560|580)/', +# 0x10b4 - int16u LensType Condition => '$$self{Model} =~ /^(SLT-A35|NEX-C3)/', +# +# 0x10f7 - int16u LensType Condition => '$$self{Model} =~ /^SLT-A(33|55V)/', +# 0x10fb - int16u LensType Condition => '$$self{Model} =~ /^DSLR-A(560|580)/', +# 0x1103 - int16u LensType Condition => '$$self{Model} =~ /^(SLT-A35|NEX-C3)/', +# +# 0x1181 - int16u LensType Condition => '$$self{Model} =~ /^SLT-A(33|55V)/', +# 0x1185 - int16u LensType Condition => '$$self{Model} =~ /^DSLR-A(560|580)/', +# 0x118d - int16u LensType Condition => '$$self{Model} =~ /^(SLT-A35|NEX-C3)/', +); + +# more camera setting information (ref JR) +# - many of these tags are the same as in CameraSettings3 +%Image::ExifTool::Sony::MoreSettings = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + PRIORITY => 0, + 0x01 => { # interesting: somewhere between CameraSettings3 0x04 and 0x34 + Name => 'DriveMode2', + PrintHex => 1, + PrintConv => { + 0x10 => 'Single Frame', + 0x21 => 'Continuous High', # also automatically selected for Scene mode Sports-action (0x05=52) + 0x22 => 'Continuous Low', + 0x30 => 'Speed Priority Continuous', + 0x51 => 'Self-timer 10 sec', + 0x52 => 'Self-timer 2 sec, Mirror Lock-up', + 0x71 => 'Continuous Bracketing 0.3 EV', + 0x75 => 'Continuous Bracketing 0.7 EV', + 0x91 => 'White Balance Bracketing Low', + 0x92 => 'White Balance Bracketing High', + 0xc0 => 'Remote Commander', + }, + }, + 0x02 => { + Name => 'ExposureProgram', + SeparateTable => 'ExposureProgram2', + PrintConv => \%sonyExposureProgram2, + }, + 0x03 => { + Name => 'MeteringMode', + PrintConv => { + 1 => 'Multi-segment', + 2 => 'Center-weighted average', + 3 => 'Spot', + }, + }, + 0x04 => { + Name => 'DynamicRangeOptimizerSetting', + PrintConv => { + 1 => 'Off', + 16 => 'On (Auto)', + 17 => 'On (Manual)', + }, + }, + 0x05 => 'DynamicRangeOptimizerLevel', + 0x06 => { + Name => 'ColorSpace', + PrintConv => { + 1 => 'sRGB', + 2 => 'Adobe RGB', + }, + }, + 0x07 => { + Name => 'CreativeStyleSetting', + PrintConvColumns => 2, + PrintConv => { + 16 => 'Standard', + 32 => 'Vivid', + 64 => 'Portrait', + 80 => 'Landscape', + 96 => 'B&W', + 160 => 'Sunset', + }, + }, + 0x08 => { #JR + Name => 'ContrastSetting', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x09 => { + Name => 'SaturationSetting', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x0a => { + Name => 'SharpnessSetting', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x0d => { + Name => 'WhiteBalanceSetting', + # many guessed, based on "logical system" as observed for Daylight and Shade and steps of 16 between the modes + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => \%whiteBalanceSetting, + SeparateTable => 1, + }, + 0x0e => { + Name => 'ColorTemperatureSetting', + # matches "0xb021 ColorTemperature" when WB set to "Custom" or "Color Temperature/Color Filter" + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + PrintConv => '"$val K"', + PrintConvInv => '$val =~ s/ ?K$//i; $val', + }, + 0x0f => { + Name => 'ColorCompensationFilterSet', + # seen 0, 1-9 and 245-255, corresponding to 0, M1-M9 and G9-G1 on camera display + # matches "0xb022 ColorCompensationFilter" when WB set to "Custom" or "Color Temperature/Color Filter" + Format => 'int8s', + Notes => 'negative is green, positive is magenta', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x10 => { + Name => 'FlashMode', + PrintConvColumns => 2, + PrintConv => { + 1 => 'Flash Off', + 16 => 'Autoflash', + 17 => 'Fill-flash', + 18 => 'Slow Sync', + 19 => 'Rear Sync', + 20 => 'Wireless', + }, + }, + 0x11 => { + Name => 'LongExposureNoiseReduction', + PrintConv => { + 1 => 'Off', + 16 => 'On', # (unused or dark subject) + }, + }, + 0x12 => { + Name => 'HighISONoiseReduction', + PrintConv => { + 16 => 'Low', + 17 => 'High', + 19 => 'Auto', + }, + }, + 0x13 => { # why is this not valid for A450/A500/A550 ? + Name => 'FocusMode', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 17 => 'AF-S', + 18 => 'AF-C', + 19 => 'AF-A', + 32 => 'Manual', + 48 => 'DMF', #(NC) (seen for NEX-5) + }, + }, + 0x15 => { + Name => 'MultiFrameNoiseReduction', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 0 => 'n/a', # seen for A450/A500/A550 + 1 => 'Off', + 16 => 'On', + 255 => 'None', # seen for NEX-3/5/C3 + }, + }, + 0x16 => { + Name => 'HDRSetting', + PrintConv => { + 1 => 'Off', + 16 => 'On (Auto)', + 17 => 'On (Manual)', + }, + }, + 0x17 => { + Name => 'HDRLevel', + PrintConvColumns => 3, + PrintConv => { + 33 => '1 EV', + 34 => '1.5 EV', #JR (NC) + 35 => '2 EV', + 36 => '2.5 EV', #JR (NC) + 37 => '3 EV', + 38 => '3.5 EV', #PH (NC) + 39 => '4 EV', + 40 => '5 EV', + 41 => '6 EV', + }, + }, + 0x18 => { + Name => 'ViewingMode', + PrintConv => { + 16 => 'ViewFinder', + 33 => 'Focus Check Live View', + 34 => 'Quick AF Live View', + }, + }, + 0x19 => { + Name => 'FaceDetection', + PrintConv => { + 1 => 'Off', + 16 => 'On', + }, + }, + 0x1a => { + Name => 'CustomWB_RBLevels', + # matches "0x7313 WB_RGGBLevels" when WB set to "Custom", except factor of 4 + Format => 'int16uRev[2]', + }, + # From here different and overlapping offsets for 3 groups of cameras: + # 1) DSLR-A450/A500/A550 + # 2) NEX-3/5/5C + # 3) DSLR-A560/A580, NEX-C3/VG10/VG10E, SLT-A33/A35/A55V + 0x1e => [{ + Name => 'BrightnessValue', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + ValueConv => '($val-106)/8', + ValueConvInv => '$val * 8 + 106', + },{ + Name => 'ExposureCompensationSet', + Notes => 'other models', + ValueConv => '($val - 128) / 24', #PH + ValueConvInv => 'int($val * 24 + 128.5)', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }], + 0x1f => [{ + Name => 'ISO', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + ValueConv => '$val ? exp(($val/8-6)*log(2))*100 : $val', + ValueConvInv => '$val ? 8*(log($val/100)/log(2)+6) : $val', + PrintConv => '$val ? sprintf("%.0f",$val) : "Auto"', + PrintConvInv => '$val =~ /auto/i ? 0 : $val', + },{ + Name => 'FlashExposureCompSet', + Notes => 'other models', + Description => 'Flash Exposure Comp. Setting', + ValueConv => '($val - 128) / 24', #PH + ValueConvInv => 'int($val * 24 + 128.5)', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }], + 0x20 => [{ + Name => 'FNumber', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + ValueConv => '2 ** (($val/8 - 1) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + },{ + Name => 'LiveViewAFMethod', + Condition => '$$self{Model} !~ /^NEX-(3|5|5C)/', + Notes => 'other models except the NEX-3/5/5C', + PrintConv => { + 0 => 'n/a', + 1 => 'Phase-detect AF', + 2 => 'Contrast AF', + # Contrast AF is only available with SSM/SAM lenses and in Focus Check LV, + # NOT in Quick AF LV, and is automatically set when mounting SSM/SAM lens + # - changes into Phase-AF when switching to Quick AF LV. + }, + }], + 0x21 => [{ + Name => 'ExposureTime', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + },{ + Name => 'ISO', + Condition => '$$self{Model} =~ /^NEX-(3|5|5C)/', + Notes => 'NEX-3/5/5C', + ValueConv => '$val ? exp(($val/8-6)*log(2))*100 : $val', + ValueConvInv => '$val ? 8*(log($val/100)/log(2)+6) : $val', + PrintConv => '$val ? sprintf("%.0f",$val) : "Auto"', + PrintConvInv => '$val =~ /auto/i ? 0 : $val', + }], + 0x22 => { + Name => 'FNumber', + Condition => '$$self{Model} =~ /^NEX-(3|5|5C)/', + Notes => 'NEX-3/5/5C only', + ValueConv => '2 ** (($val/8 - 1) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }, + 0x23 => [{ + Name => 'FocalLength2', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + ValueConv => '10 * 2 ** (($val-28)/16)', + ValueConvInv => '$val>0 ? log($val/10)/log(2) * 16 + 28 : 0', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//; $val', + },{ + Name => 'ExposureTime', + Condition => '$$self{Model} =~ /^NEX-(3|5|5C)/', + Notes => 'NEX-3/5/5C', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }], + 0x24 => { + Name => 'ExposureCompensation2', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + Format => 'int16s', + ValueConv => '$val / 8', + ValueConvInv => '$val * 8', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }, + 0x25 => [{ + Name => 'FocalLength2', + Condition => '$$self{Model} =~ /^NEX-(3|5|5C)/', + Notes => 'NEX-3/5/5C', + ValueConv => '10 * 2 ** (($val-28)/16)', + ValueConvInv => '$val>0 ? log($val/10)/log(2) * 16 + 28 : 0', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//; $val', + },{ + Name => 'ISO', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)/', + Notes => 'other models except the A450, A500 and A550', + ValueConv => '$val ? exp(($val/8-6)*log(2))*100 : $val', + ValueConvInv => '$val ? 8*(log($val/100)/log(2)+6) : $val', + PrintConv => '$val ? sprintf("%.0f",$val) : "Auto"', + PrintConvInv => '$val =~ /auto/i ? 0 : $val', + }], + 0x26 => [{ + Name => 'FlashExposureCompSet2', + Description => 'Flash Exposure Comp. Setting 2', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + Format => 'int16s', + ValueConv => '$val / 8', + ValueConvInv => '$val * 8', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + },{ + Name => 'ExposureCompensation2', + Condition => '$$self{Model} =~ /^NEX-(3|5|5C)/', + Notes => 'NEX-3/5/5C', + Format => 'int16s', + ValueConv => '$val / 8', + ValueConvInv => '$val * 8', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + },{ + Name => 'FNumber', + Notes => 'other models', + ValueConv => '2 ** (($val/8 - 1) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }], + 0x27 => { + Name => 'ExposureTime', + Condition => '$$self{Model} !~ /^NEX-(3|5|5C)|DSLR-(A450|A500|A550)/', + Notes => 'models other than the A450, A500, A550 and NEX-3/5/5C', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x28 => { + Name => 'Orientation2', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + PrintConv => { + 1 => 'Horizontal (normal)', + 2 => 'Rotate 180', + 6 => 'Rotate 90 CW', + 8 => 'Rotate 270 CW', + }, + }, + 0x29 => [{ + # FocusPosition for A450/A500/A550 + # seen values from 80 - 255 (= infinity) -- see Composite:FocusDistance2 below + Name => 'FocusPosition2', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + },{ + # value increase of 16 corresponds to doubling of FocalLength + Name => 'FocalLength2', + Condition => '$$self{Model} !~ /^NEX-(3|5|5C)/', + Notes => 'other models except the NEX-3/5/5C', + ValueConv => '10 * 2 ** (($val-28)/16)', + ValueConvInv => '$val>0 ? log($val/10)/log(2) * 16 + 28 : 0', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//; $val', + }], + 0x2a => [{ + Name => 'FlashAction', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + PrintConv => { + 0 => 'Did not fire', + 1 => 'Fired', + }, + },{ + Name => 'ExposureCompensation2', + Condition => '$$self{Model} !~ /^NEX-(3|5|5C)/', + Notes => 'other models except the NEX-3/5/5C', + Format => 'int16s', + ValueConv => '$val / 8', + ValueConvInv => '$val * 8', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }], + 0x2b => { + # FocusPosition for NEX-3/5/5C + # seen values from 80 - 255 (= infinity) -- see Composite:FocusDistance2 below + Name => 'FocusPosition2', + Condition => '$$self{Model} =~ /^NEX-(3|5|5C)/', + Notes => 'NEX-3/5/5C only', + }, + 0x2c => [{ + Name => 'FocusMode2', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + Notes => 'A450, A500 and A550', + PrintConv => { + 0 => 'AF', + 1 => 'MF', + }, + },{ + Name => 'FlashAction', + Condition => '$$self{Model} =~ /^NEX-(3|5|5C)/', + Notes => 'NEX-3/5/5C FlashAction2', + PrintConv => { + 0 => 'Did not fire', + 1 => 'Fired', + }, + },{ + Name => 'FlashExposureCompSet2', + Description => 'Flash Exposure Comp. Setting 2', + Notes => 'other models FlashExposureCompSet2', + Format => 'int16s', + ValueConv => '$val / 8', + ValueConvInv => '$val * 8', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', + }], + 0x2e => [{ + Name => 'FocusMode2', + Condition => '$$self{Model} =~ /^NEX-(3|5|5C)/', + Notes => 'NEX-3/5/5C', + PrintConv => { + 0 => 'AF', + 1 => 'MF', + }, + },{ + Name => 'Orientation2', # seen some A55 images where this does not match the other Orientation tags + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)/', + Notes => 'other models except the A450, A500 and A550', + PrintConv => { + 1 => 'Horizontal (normal)', + 2 => 'Rotate 180', + 6 => 'Rotate 90 CW', + 8 => 'Rotate 270 CW', + }, + }], + 0x2f => { + # FocusPosition for A560/A580/A33/A35/A55V and NEX-C3/VG10/VG10E + # seen values from 80 - 255 (= infinity) -- see Composite:FocusDistance2 below + Name => 'FocusPosition2', + Condition => '$$self{Model} !~ /^NEX-(3|5|5C)|DSLR-(A450|A500|A550)/', + Notes => 'models other than the A450, A500, A550 and NEX-3/5/5C', + }, + 0x30 => { + Name => 'FlashAction', + Condition => '$$self{Model} !~ /^NEX-(3|5|5C)|DSLR-(A450|A500|A550)/', + Notes => 'models other than the A450, A500, A550 and NEX-3/5/5C', + PrintConv => { + 0 => 'Did not fire', + 1 => 'Fired', + }, + }, + 0x32 => { + Name => 'FocusMode2', + Condition => '$$self{Model} !~ /^NEX-(3|5|5C)|DSLR-(A450|A500|A550)/', + Notes => 'models other than the A450, A500, A550 and NEX-3/5/5C', + PrintConv => { + 0 => 'AF', + 1 => 'MF', + }, + }, + 0x0077 => { + Name => 'FlashAction2', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)/', + PrintConv => { + 0 => 'Did not fire', + 2 => 'External Flash fired (2)', + 3 => 'Built-in Flash fired', + 4 => 'External Flash fired (4)', # what is difference with 2 ? + }, + }, + 0x0078 => { + Name => 'FlashActionExternal', + Condition => '$$self{Model} =~ /^NEX-(3|5|5C)/', + PrintConv => { + 136 => 'Did not fire', + 121 => 'Fired', # what is difference with 122 ? + 122 => 'Fired', + }, + }, + 0x007c => { + Name => 'FlashActionExternal', + Condition => '$$self{Model} !~ /^NEX-(3|5|5C)|DSLR-(A450|A500|A550)/', + PrintConv => { + 136 => 'Did not fire', + 167 => 'Fired', + 182 => 'Fired, HSS', + }, + }, + 0x0082 => { + Name => 'FlashStatus', + Condition => '$$self{Model} =~ /^NEX-(3|5|5C)/', + PrintConv => { + 0 => 'None', + 2 => 'External', + }, + }, + 0x0086 => { + Name => 'FlashStatus', + Condition => '$$self{Model} !~ /^NEX-(3|5|5C)|DSLR-(A450|A500|A550)/', + PrintConv => { + 0 => 'None', + 1 => 'Built-in', + 2 => 'External', + }, + }, +); + +# Face detection information (ref JR) +my %faceInfo = ( + Format => 'int16u[4]', + # re-order to top,left,height,width and scale to full-sized image like other Sony models + ValueConv => 'my @v=split(" ",$val); $_*=15 foreach @v; "$v[1] $v[0] $v[3] $v[2]"', + ValueConvInv => 'my @v=split(" ",$val); $_=int($_/15+0.5) foreach @v; "$v[1] $v[0] $v[3] $v[2]"', +); +%Image::ExifTool::Sony::FaceInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int16u', + DATAMEMBER => [ 0x00 ], + 0x00 => { + Name => 'FacesDetected', + DataMember => 'FacesDetected', + Format => 'int16s', + RawConv => '$$self{FacesDetected} = ($val == -1 ? 0 : $val); $val', + PrintConv => { + OTHER => sub { shift }, # pass other values straight through + -1 => 'n/a', + }, + }, + 0x01 => { + Name => 'Face1Position', + Condition => '$$self{FacesDetected} >= 1', + %faceInfo, + Notes => q{ + re-ordered and scaled to return the top, left, height and width of detected + face, with coordinates relative to the full-sized unrotated image and + increasing Y downwards + }, + }, + 0x06 => { + Name => 'Face2Position', + Condition => '$$self{FacesDetected} >= 2', + %faceInfo, + }, + 0x0b => { + Name => 'Face3Position', + Condition => '$$self{FacesDetected} >= 3', + %faceInfo, + }, + 0x10 => { + Name => 'Face4Position', + Condition => '$$self{FacesDetected} >= 4', + %faceInfo, + }, + 0x15 => { + Name => 'Face5Position', + Condition => '$$self{FacesDetected} >= 5', + %faceInfo, + }, + 0x1a => { + Name => 'Face6Position', + Condition => '$$self{FacesDetected} >= 6', + %faceInfo, + }, + 0x1f => { + Name => 'Face7Position', + Condition => '$$self{FacesDetected} >= 7', + %faceInfo, + }, + 0x24 => { + Name => 'Face8Position', + Condition => '$$self{FacesDetected} >= 8', + %faceInfo, + }, +); + +%Image::ExifTool::Sony::FaceInfoA = ( # different offsets for A450/A500/A550 + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int16u', + DATAMEMBER => [ 0x02, 0x03, 0x08 ], +# +# The FacesDetected number at 0x03 below is often 1 lower than the one at Tag900b 0x02. +# The number of Face Positions starting at 0x5b (max. 4) corresponds to the number at 0x03. +# The number of Face Positions starting at 0x0b usually corresponds to the FacesDetected number of Tag900b... +# Therefore created the extra condition at 0x0b (11.1) to output an available FacePosition, even when 0x03=0... +# + 0x02 => { + Name => 'FaceTest2', + DataMember => 'FaceTest2', + Hidden => 1, + RawConv => '$$self{FaceTest2} = $val; $$self{OPTIONS}{Unknown}<2 ? undef : $val', + }, + 0x03 => { + Name => 'FacesDetected', + DataMember => 'FacesDetected', + RawConv => '$$self{FacesDetected} = ($val > 8 ? 0 : $val); $val', + ValueConv => '$val > 8 ? 0 : $val', + }, + 0x08 => { + Name => 'FaceTest8', + DataMember => 'FaceTest8', + Hidden => 1, + RawConv => '$$self{FaceTest8} = $val; $$self{OPTIONS}{Unknown}<2 ? undef : $val', + }, +# 0x0b - start of 8 Face Positions of 10 int16u values each + 0x0b => { + Name => 'PotentialFace1Position', + Condition => q{ + $$self{FacesDetected} >= 1 or + ($$self{FaceTest8} > 0 and ($$self{FaceTest2} == 1 or $$self{FaceTest2} == 257)) + }, + %faceInfo, + }, + 0x15 => { + Name => 'PotentialFace2Position', + Condition => '$$self{FacesDetected} >= 2 or ($$self{FacesDetected} == 1 and $$self{FaceTest8} > 0)', + %faceInfo, + }, + 0x1f => { + Name => 'PotentialFace3Position', + Condition => '$$self{FacesDetected} >= 3 or ($$self{FacesDetected} == 2 and $$self{FaceTest8} > 0)', + %faceInfo, + }, + 0x29 => { + Name => 'PotentialFace4Position', + Condition => '$$self{FacesDetected} >= 4 or ($$self{FacesDetected} == 3 and $$self{FaceTest8} > 0)', + %faceInfo, + }, + 0x33 => { + Name => 'PotentialFace5Position', + Condition => '$$self{FacesDetected} >= 5 or ($$self{FacesDetected} == 4 and $$self{FaceTest8} > 0)', + %faceInfo, + }, + 0x3d => { + Name => 'PotentialFace6Position', + Condition => '$$self{FacesDetected} >= 6 or ($$self{FacesDetected} == 5 and $$self{FaceTest8} > 0)', + %faceInfo, + }, + 0x47 => { + Name => 'PotentialFace7Position', + Condition => '$$self{FacesDetected} >= 7 or ($$self{FacesDetected} == 6 and $$self{FaceTest8} > 0)', + %faceInfo, + }, + 0x51 => { + Name => 'PotentialFace8Position', + Condition => '$$self{FacesDetected} >= 8 or ($$self{FacesDetected} == 7 and $$self{FaceTest8} > 0)', + %faceInfo, + }, +# 0x5b - start of max. 4 further Face Positions here + 0x5b => { + Name => 'Face1Position', + Condition => '$$self{FacesDetected} >= 1', + %faceInfo, + }, + 0x65 => { + Name => 'Face2Position', + Condition => '$$self{FacesDetected} >= 2', + %faceInfo, + }, + 0x6f => { + Name => 'Face3Position', + Condition => '$$self{FacesDetected} >= 3', + %faceInfo, + }, + 0x79 => { + Name => 'Face4Position', + Condition => '$$self{FacesDetected} >= 4', + %faceInfo, + }, +); + +# Camera settings (ref PH) (decoded mainly from A200) +%Image::ExifTool::Sony::CameraSettings = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int16u', + PRIORITY => 0, + NOTES => 'Camera settings for the A200, A300, A350, A700, A850 and A900.', + 0x00 => { #JR + Name => 'ExposureTime', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x01 => { #JR + Name => 'FNumber', + ValueConv => '2 ** (($val/8 - 1) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }, + 0x02 => { #JR (requires external flash) + Name => 'HighSpeedSync', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x03 => { #JR + Name => 'ExposureCompensationSet', + ValueConv => '($val - 128) / 24', + ValueConvInv => 'int($val * 24 + 128.5)', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x04 => { #7/JR + Name => 'DriveMode', + Mask => 0xff, # (not sure what upper byte is for) + PrintHex => 1, + PrintConv => { + 0x01 => 'Single Frame', + 0x02 => 'Continuous High', # A700/A900; not on A850 + 0x12 => 'Continuous Low', #JR + 0x04 => 'Self-timer 10 sec', + 0x05 => 'Self-timer 2 sec, Mirror Lock-up', + 0x06 => 'Single-frame Bracketing', + 0x07 => 'Continuous Bracketing', # (A200 val=0x1107) + 0x18 => 'White Balance Bracketing Low', #JR + 0x28 => 'White Balance Bracketing High', #JR + 0x19 => 'D-Range Optimizer Bracketing Low', #JR + 0x29 => 'D-Range Optimizer Bracketing High', #JR + 0x0a => 'Remote Commander', #JR + 0x0b => 'Mirror Lock-up', #JR (A850/A900; not on A700) + }, + }, + 0x05 => { #JR + Name => 'WhiteBalanceSetting', + PrintConv => { + 2 => 'Auto', + 4 => 'Daylight', + 5 => 'Fluorescent', + 6 => 'Tungsten', + 7 => 'Flash', + 16 => 'Cloudy', + 17 => 'Shade', + 18 => 'Color Temperature/Color Filter', + 32 => 'Custom 1', + 33 => 'Custom 2', + 34 => 'Custom 3', + }, + }, + 0x06 => { #7 (A700) (ref JR: at least also valid for A200, ValueConv as for ColorCompensationFilterSet) + Name => 'WhiteBalanceFineTune', + ValueConv => '$val > 128 ? $val - 256 : $val', + ValueConvInv => '$val < 0 ? $val + 256 : $val', + }, + 0x07 => { #JR as set in WB "Color Temperature/Color Filter" and in White Balance Bracketing + Name => 'ColorTemperatureSet', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + PrintConv => '"$val K"', + PrintConvInv => '$val =~ s/ ?K$//i; $val', + }, + 0x08 => { #JR as set in WB "Color Temperature/Color Filter" + Name => 'ColorCompensationFilterSet', + Notes => 'negative is green, positive is magenta', + ValueConv => '$val > 128 ? $val - 256 : $val', + ValueConvInv => '$val < 0 ? $val + 256 : $val', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x0c => { #JR as set in WB "Custom" and in White Balance Bracketing + Name => 'ColorTemperatureCustom', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + PrintConv => '"$val K"', + PrintConvInv => '$val =~ s/ ?K$//i; $val', + }, + 0x0d => { #JR as set in WB "Custom" + Name => 'ColorCompensationFilterCustom', + Notes => 'negative is green, positive is magenta', + ValueConv => '$val > 128 ? $val - 256 : $val', + ValueConvInv => '$val < 0 ? $val + 256 : $val', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x0f => { #JR + Name => 'WhiteBalance', + PrintConv => { + 2 => 'Auto', + 4 => 'Daylight', + 5 => 'Fluorescent', + 6 => 'Tungsten', + 7 => 'Flash', + 12 => 'Color Temperature', + 13 => 'Color Filter', + 14 => 'Custom', + 16 => 'Cloudy', + 17 => 'Shade', + }, + }, + 0x10 => { #7 (A700) + Name => 'FocusModeSetting', + PrintConv => { + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', + 4 => 'DMF', #JR + }, + }, + 0x11 => { #JD (A700) + Name => 'AFAreaMode', + PrintConv => { + 0 => 'Wide', + 1 => 'Local', + 2 => 'Spot', + }, + }, + 0x12 => { #7 (A700) + Name => 'AFPointSetting', + Format => 'int16u', + # The AF point as selected by the user in AFAreaMode=Local or Spot; + # Reported value remains at the last-set position in AFAreaModes=Wide. + # A200, A300, A350: 9-point centre-cross (ref JR) + # A700: 11-point centre-dual-cross (ref JR) + # A850, A900: 9-point centre-dual-cross with 10 assist-points (ref JR) + PrintConvColumns => 2, + PrintConv => { + 1 => 'Center', + 2 => 'Top', + 3 => 'Upper-right', + 4 => 'Right', + 5 => 'Lower-right', + 6 => 'Bottom', + 7 => 'Lower-left', + 8 => 'Left', + 9 => 'Upper-left', + 10 => 'Far Right', # (presumably A700 only) + 11 => 'Far Left', # (presumably A700 only) + }, + }, + 0x13 => { #JR + Name => 'FlashMode', + PrintConv => { + 0 => 'Autoflash', + 2 => 'Rear Sync', + 3 => 'Wireless', + 4 => 'Fill-flash', + 5 => 'Flash Off', + 6 => 'Slow Sync', + }, + }, + 0x14 => { #JR + Name => 'FlashExposureCompSet', + Description => 'Flash Exposure Comp. Setting', + # (as pre-selected by the user, not zero if flash didn't fire) + ValueConv => '($val - 128) / 24', #PH + ValueConvInv => 'int($val * 24 + 128.5)', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x15 => { #7 + Name => 'MeteringMode', + PrintConv => { + 1 => 'Multi-segment', + 2 => 'Center-weighted average', + 4 => 'Spot', + }, + }, + 0x16 => { + Name => 'ISOSetting', + # 0 indicates 'Auto' (I think) + ValueConv => '$val ? exp(($val/8-6)*log(2))*100 : $val', + ValueConvInv => '$val ? 8*(log($val/100)/log(2)+6) : $val', + PrintConv => '$val ? sprintf("%.0f",$val) : "Auto"', + PrintConvInv => '$val =~ /auto/i ? 0 : $val', + }, + 0x18 => { #7 + Name => 'DynamicRangeOptimizerMode', + PrintConv => { + 0 => 'Off', + 1 => 'Standard', + 2 => 'Advanced Auto', + 3 => 'Advanced Level', + }, + }, + 0x19 => { #7 + Name => 'DynamicRangeOptimizerLevel', + }, + 0x1a => { # style actually used (combination of mode dial + creative style menu) + Name => 'CreativeStyle', + PrintConvColumns => 2, + PrintConv => { + 1 => 'Standard', + 2 => 'Vivid', + 3 => 'Portrait', + 4 => 'Landscape', + 5 => 'Sunset', + 6 => 'Night View/Portrait', + 8 => 'B&W', + 9 => 'Adobe RGB', # A700 + 11 => 'Neutral', + 12 => 'Clear', #7 + 13 => 'Deep', #7 + 14 => 'Light', #7 + 15 => 'Autumn Leaves', #7 + 16 => 'Sepia', #7 + }, + }, + 0x1b => { #JR + Name => 'ColorSpace', + PrintConv => { + 0 => 'sRGB', + 1 => 'Adobe RGB', # (A850, selected via Colorspace menu item) + 5 => 'Adobe RGB (A700)', # (A700, selected via CreativeStyle menu) + }, + }, + 0x1c => { + Name => 'Sharpness', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x1d => { + Name => 'Contrast', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x1e => { + Name => 'Saturation', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x1f => { #7 + Name => 'ZoneMatchingValue', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x22 => { #7 + Name => 'Brightness', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x23 => { + Name => 'FlashControl', + PrintConv => { + 0 => 'ADI', + 1 => 'Pre-flash TTL', + 2 => 'Manual', + }, + }, + 0x28 => { #7 + Name => 'PrioritySetupShutterRelease', + PrintConv => { + 0 => 'AF', + 1 => 'Release', + }, + }, + 0x29 => { #7 + Name => 'AFIlluminator', + PrintConv => { + 0 => 'Auto', + 1 => 'Off', + }, + }, + 0x2a => { #7 + Name => 'AFWithShutter', + PrintConv => { 0 => 'On', 1 => 'Off' }, + }, + 0x2b => { #7 + Name => 'LongExposureNoiseReduction', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x2c => { #7 + Name => 'HighISONoiseReduction', + PrintConv => { + 0 => 'Normal', + 1 => 'Low', + 2 => 'High', + 3 => 'Off', + }, + }, + 0x2d => { #7 + Name => 'ImageStyle', + PrintConvColumns => 2, + PrintConv => { + 1 => 'Standard', + 2 => 'Vivid', + 3 => 'Portrait', #PH + 4 => 'Landscape', #PH + 5 => 'Sunset', #PH + 7 => 'Night View/Portrait', #PH (A200/A350 when CreativeStyle was 6!) + 8 => 'B&W', #PH (guess) + 9 => 'Adobe RGB', + 11 => 'Neutral', + 129 => 'StyleBox1', + 130 => 'StyleBox2', + 131 => 'StyleBox3', + 132 => 'StyleBox4', #JR (A850) + 133 => 'StyleBox5', #JR (A850) + 134 => 'StyleBox6', #JR (A850) + }, + }, + 0x2e => { #JR (may not apply to A200/A300/A350 -- they don't have the AF/MF button) + Name => 'FocusModeSwitch', + PrintConv => { + 0 => 'AF', + 1 => 'Manual', + }, + }, + 0x2f => { #JR + Name => 'ShutterSpeedSetting', + Notes => 'used in M, S and Program Shift S modes', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x30 => { #JR + Name => 'ApertureSetting', + Notes => 'used in M, A and Program Shift A modes', + ValueConv => '2 ** (($val/8 - 1) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }, + 0x3c => { + Name => 'ExposureProgram', + SeparateTable => 'ExposureProgram', + PrintConv => \%sonyExposureProgram, + }, + 0x3d => { + Name => 'ImageStabilizationSetting', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x3e => { #JR + Name => 'FlashAction', + PrintConv => { + 0 => 'Did not fire', + 1 => 'Fired', + 2 => 'External Flash, Did not fire', + 3 => 'External Flash, Fired', + }, + }, + 0x3f => { # (verified for A330/A380) + Name => 'Rotation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', #(NC) + 2 => 'Rotate 270 CW', + }, + }, + 0x40 => { #JR + Name => 'AELock', + PrintConv => { + 1 => 'Off', + 2 => 'On', + }, + }, + 0x4c => { #JR + Name => 'FlashAction2', + PrintConv => { + 1 => 'Fired, Autoflash', + 2 => 'Fired, Fill-flash', + 3 => 'Fired, Rear Sync', + 4 => 'Fired, Wireless', + 5 => 'Did not fire', + 6 => 'Fired, Slow Sync', + 17 => 'Fired, Autoflash, Red-eye reduction', + 18 => 'Fired, Fill-flash, Red-eye reduction', + 34 => 'Fired, Fill-flash, HSS', + }, + }, + 0x4d => { #JR + Name => 'FocusMode', # (focus mode actually used) + PrintConv => { + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', + 4 => 'DMF', #JR + }, + }, + 0x50 => { #JR + Name => 'BatteryState', + PrintConv => { + 2 => 'Empty', # 0% + 3 => 'Very Low', # 1 - 20% + 4 => 'Low', # 21 - 50% + 5 => 'Sufficient', # 51 - 80% + 6 => 'Full', # > 80% + }, + }, + 0x51 => { #JR + Name => 'BatteryLevel', + PrintConv => '"$val%"', + PrintConvInv => '$val=~s/\s*\%//; $val', + }, + 0x53 => { #JR + Name => 'FocusStatus', + PrintConv => { + 0 => 'Not confirmed', + 4 => 'Not confirmed, Tracking', + BITMASK => { + 0 => 'Confirmed', + 1 => 'Failed', + 2 => 'Tracking', + }, + }, + }, + 0x54 => { + Name => 'SonyImageSize', + PrintConv => { + 1 => 'Large', + 2 => 'Medium', + 3 => 'Small', + }, + }, + 0x55 => { #7 + Name => 'AspectRatio', + PrintConv => { + 1 => '3:2', + 2 => '16:9', + }, + }, + 0x56 => { #PH/7 + Name => 'Quality', + PrintConv => { + 0 => 'RAW', + 2 => 'CRAW', + 34 => 'RAW + JPEG', + 35 => 'CRAW + JPEG', + 16 => 'Extra Fine', + 32 => 'Fine', + 48 => 'Standard', + }, + }, + 0x58 => { #7 + Name => 'ExposureLevelIncrements', + PrintConv => { + 33 => '1/3 EV', + 50 => '1/2 EV', + }, + }, + 0x6a => { #JR + Name => 'RedEyeReduction', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x9a => { #JR + Name => 'FolderNumber', + Mask => 0x03ff, # (not sure what the upper 6 bits are for) + PrintConv => 'sprintf("%.3d",$val)', + PrintConvInv => '$val', + }, + 0x9b => { #JR + Name => 'ImageNumber', + Mask => 0x3fff, # (not sure what the upper 2 bits are for) + PrintConv => 'sprintf("%.4d",$val)', + PrintConvInv => '$val', + }, +); + +# Camera settings (ref PH) (A230, A290, A330, A380 and A390) +%Image::ExifTool::Sony::CameraSettings2 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int16u', + PRIORITY => 0, + NOTES => 'Camera settings for the A230, A290, A330, A380 and A390.', +### 0x00-0x03: same TagID as CameraSettings + 0x00 => { #JR + Name => 'ExposureTime', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x01 => { #JR + Name => 'FNumber', + ValueConv => '2 ** (($val/8 - 1) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }, + 0x02 => { #JR (requires external flash) + Name => 'HighSpeedSync', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x03 => { #JR + Name => 'ExposureCompensationSet', + ValueConv => '($val - 128) / 24', + ValueConvInv => 'int($val * 24 + 128.5)', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, +### 0x04-0x11: subtract 1 from CameraSettings TagID + 0x04 => { #JR + Name => 'WhiteBalanceSetting', + PrintConv => { + 2 => 'Auto', + 4 => 'Daylight', + 5 => 'Fluorescent', + 6 => 'Tungsten', + 7 => 'Flash', + 16 => 'Cloudy', + 17 => 'Shade', + 18 => 'Color Temperature/Color Filter', + 32 => 'Custom 1', + 33 => 'Custom 2', + 34 => 'Custom 3', + }, + }, + 0x05 => { #JR + Name => 'WhiteBalanceFineTune', + ValueConv => '$val > 128 ? $val - 256 : $val', + ValueConvInv => '$val < 0 ? $val + 256 : $val', + }, + 0x06 => { #JR as set in WB "Color Temperature/Color Filter" and in White Balance Bracketing + Name => 'ColorTemperatureSet', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + PrintConv => '"$val K"', + PrintConvInv => '$val =~ s/ ?K$//i; $val', + }, + 0x07 => { #JR as set in WB "Color Temperature/Color Filter" + Name => 'ColorCompensationFilterSet', + Notes => 'negative is green, positive is magenta', + ValueConv => '$val > 128 ? $val - 256 : $val', + ValueConvInv => '$val < 0 ? $val + 256 : $val', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x08 => { #JR + Name => 'CustomWB_RGBLevels', + Format => 'int16u[3]', + }, + 0x0b => { #JR as set in WB "Custom" and in White Balance Bracketing + Name => 'ColorTemperatureCustom', + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + PrintConv => '"$val K"', + PrintConvInv => '$val =~ s/ ?K$//i; $val', + }, + 0x0c => { #JR as set in WB "Custom" + Name => 'ColorCompensationFilterCustom', + Notes => 'negative is green, positive is magenta', + ValueConv => '$val > 128 ? $val - 256 : $val', + ValueConvInv => '$val < 0 ? $val + 256 : $val', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x0e => { #JR + Name => 'WhiteBalance', + PrintConv => { + 2 => 'Auto', + 4 => 'Daylight', + 5 => 'Fluorescent', + 6 => 'Tungsten', + 7 => 'Flash', + 12 => 'Color Temperature', + 13 => 'Color Filter', + 14 => 'Custom', + 16 => 'Cloudy', + 17 => 'Shade', + }, + }, + 0x0f => { #JR/PH (educated guess) + Name => 'FocusModeSetting', + PrintConv => { + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', + # seen 5 for A380 (FocusMode was Manual and FocusStatus was Confirmed) + }, + }, + 0x10 => { #JR/PH (educated guess) + Name => 'AFAreaMode', + PrintConv => { + 0 => 'Wide', + 1 => 'Local', + 2 => 'Spot', + }, + }, + 0x11 => { #JR/PH (educated guess) + Name => 'AFPointSetting', + Format => 'int16u', + # The AF point as selected by the user in AFAreaMode=Local or Spot; + # Reported value remains at the last-set position in AFAreaModes=Wide. + # (all of these cameras have a 9-point centre-cross AF system, ref JR) + PrintConvColumns => 2, + PrintConv => { + 1 => 'Center', + 2 => 'Top', + 3 => 'Upper-right', + 4 => 'Right', + 5 => 'Lower-right', + 6 => 'Bottom', + 7 => 'Lower-left', + 8 => 'Left', + 9 => 'Upper-left', + }, + }, +### 0x12-0x18: subtract 2 from CameraSettings TagID + 0x12 => { #JR + Name => 'FlashExposureCompSet', + Description => 'Flash Exposure Comp. Setting', + # (as pre-selected by the user, not zero if flash didn't fire) + ValueConv => '($val - 128) / 24', #PH + ValueConvInv => 'int($val * 24 + 128.5)', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x13 => { + Name => 'MeteringMode', + PrintConv => { + 1 => 'Multi-segment', + 2 => 'Center-weighted average', + 4 => 'Spot', + }, + }, + 0x14 => { # A330/A380 + Name => 'ISOSetting', + # 0 indicates 'Auto' (?) + ValueConv => '$val ? exp(($val/8-6)*log(2))*100 : $val', + ValueConvInv => '$val ? 8*(log($val/100)/log(2)+6) : $val', + PrintConv => '$val ? sprintf("%.0f",$val) : "Auto"', + PrintConvInv => '$val =~ /auto/i ? 0 : $val', + }, + 0x16 => { + Name => 'DynamicRangeOptimizerMode', + PrintConv => { + 0 => 'Off', + 1 => 'Standard', + 2 => 'Advanced Auto', + 3 => 'Advanced Level', + }, + }, + 0x17 => 'DynamicRangeOptimizerLevel', + 0x18 => { # A380 + Name => 'CreativeStyle', + PrintConvColumns => 2, + PrintConv => { + 1 => 'Standard', + 2 => 'Vivid', + 3 => 'Portrait', + 4 => 'Landscape', + 5 => 'Sunset', + 6 => 'Night View/Portrait', + 8 => 'B&W', + # (these models don't have Neutral - PH) + }, + }, +### 0x19-0x1b: subtract 3 from CameraSettings TagID + 0x19 => { + Name => 'Sharpness', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x1a => { + Name => 'Contrast', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x1b => { + Name => 'Saturation', + ValueConv => '$val - 10', + ValueConvInv => '$val + 10', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, +### 0x1c-0x24: subtract 4 from CameraSettings TagID (not sure about 0x1c) + 0x1f => { #PH (educated guess) + Name => 'FlashControl', + PrintConv => { + 0 => 'ADI', + 1 => 'Pre-flash TTL', + 2 => 'Manual', + }, + }, +### 0x25-0x27: subtract 6 from CameraSettings TagID + 0x25 => { #PH + Name => 'LongExposureNoiseReduction', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x26 => { #PH + Name => 'HighISONoiseReduction', + # (Note: the order is different from that in CameraSettings) + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Normal', + 3 => 'High', + }, + }, + 0x27 => { #PH + Name => 'ImageStyle', + PrintConvColumns => 2, + PrintConv => { + 1 => 'Standard', + 2 => 'Vivid', + 3 => 'Portrait', #PH + 4 => 'Landscape', #PH + 5 => 'Sunset', #PH + 7 => 'Night View/Portrait', #PH (A200 when CreativeStyle was 6!) + 8 => 'B&W', #PH (A380) + # (these models don't have Neutral - PH) + }, + }, +### 0x28-0x3b: subtract 7 from CameraSettings TagID + 0x28 => { #PH + Name => 'ShutterSpeedSetting', + Notes => 'used in M, S and Program Shift S modes', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x29 => { #PH + Name => 'ApertureSetting', + Notes => 'used in M, A and Program Shift A modes', + ValueConv => '2 ** (($val/8 - 1) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }, +### 0x3c-0x59: same TagID as CameraSettings + 0x3c => { + Name => 'ExposureProgram', + SeparateTable => 'ExposureProgram', + PrintConv => \%sonyExposureProgram, + }, + 0x3d => { # (copied from CameraSettings, ref JR) + Name => 'ImageStabilizationSetting', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x3e => { #JR + Name => 'FlashAction', + PrintConv => { + 0 => 'Did not fire', + 1 => 'Fired', + 2 => 'External Flash, Did not fire', + 3 => 'External Flash, Fired', + }, + }, + 0x3f => { # (verified for A330/A380) + Name => 'Rotation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', #(NC) + 2 => 'Rotate 270 CW', + }, + }, + 0x40 => { #JR + Name => 'AELock', + PrintConv => { + 1 => 'Off', + 2 => 'On', + }, + }, + 0x4c => { #JR + Name => 'FlashAction2', + PrintConv => { + 1 => 'Fired, Autoflash', + 2 => 'Fired, Fill-flash', + 3 => 'Fired, Rear Sync', + 4 => 'Fired, Wireless', + 5 => 'Did not fire', + 6 => 'Fired, Slow Sync', + 17 => 'Fired, Autoflash, Red-eye reduction', + 18 => 'Fired, Fill-flash, Red-eye reduction', + 34 => 'Fired, Fill-flash, HSS', + }, + }, + 0x4d => { #JR + Name => 'FocusMode', # (focus mode actually used) + PrintConv => { + 0 => 'Manual', + 1 => 'AF-S', + 2 => 'AF-C', + 3 => 'AF-A', + }, + }, + 0x53 => { #JR (copied from CameraSettings, but all bits may not be applicable for these models) + Name => 'FocusStatus', + PrintConv => { + 0 => 'Not confirmed', + 4 => 'Not confirmed, Tracking', + BITMASK => { + 0 => 'Confirmed', + 1 => 'Failed', + 2 => 'Tracking', + }, + }, + }, + 0x54 => { + Name => 'SonyImageSize', + PrintConv => { + 1 => 'Large', + 2 => 'Medium', + 3 => 'Small', + }, + }, + 0x55 => { # (copied from CameraSettings, ref JR) + Name => 'AspectRatio', + PrintConv => { + 1 => '3:2', + 2 => '16:9', + }, + }, + 0x56 => { # (copied from CameraSettings, ref JR) + Name => 'Quality', + PrintConv => { + 0 => 'RAW', + 2 => 'CRAW', + 34 => 'RAW + JPEG', + 35 => 'CRAW + JPEG', + 16 => 'Extra Fine', + 32 => 'Fine', + 48 => 'Standard', + }, + }, + 0x58 => { # (copied from CameraSettings, ref JR) + Name => 'ExposureLevelIncrements', + PrintConv => { + 33 => '1/3 EV', + 50 => '1/2 EV', + }, + }, +### 0x5a onwards: subtract 1 from CameraSettings TagID + # (0x69 not confirmed) + #0x69 => { #JR + # Name => 'RedEyeReduction', + # PrintConv => { + # 0 => 'Off', + # 1 => 'On', + # }, + #}, + 0x7e => { #JR + Name => 'DriveMode', + Mask => 0xff, # (not sure what upper byte is for) + PrintConv => { # (values confirmed for specified models - PH) + 1 => 'Single Frame', # (A230,A330,A380) + 2 => 'Continuous High', #PH (A230,A330) + 4 => 'Self-timer 10 sec', # (A230) + 5 => 'Self-timer 2 sec, Mirror Lock-up', # (A230,A290,A330,A380,390) + 7 => 'Continuous Bracketing', # (A230 val=0x1107, A330 val=0x1307 [0.7 EV]) + 10 => 'Remote Commander', # (A230) + 11 => 'Continuous Self-timer', # (A230 val=0x800b [5 shots], A330 val=0x400b [3 shots]) + }, + }, + 0x7f => { #JR + Name => 'FlashMode', + PrintConv => { + 0 => 'Autoflash', + 2 => 'Rear Sync', + 3 => 'Wireless', + 4 => 'Fill-flash', + 5 => 'Flash Off', + 6 => 'Slow Sync', + }, + }, + 0x83 => { #PH + Name => 'ColorSpace', + PrintConv => { + 5 => 'Adobe RGB', + 6 => 'sRGB', + }, + }, +); + +# more Camera settings (ref PH) +# This was decoded for the A55, but it seems to apply to the following models: +# A33, A35, A55, A450, A500, A550, A560, A580, NEX-3, NEX-5, NEX-C3 and NEX-VG10E +%Image::ExifTool::Sony::CameraSettings3 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int8u', + PRIORITY => 0, + DATAMEMBER => [ 0x99 ], + NOTES => q{ + Camera settings for models such as the A33, A35, A55, A450, A500, A550, + A560, A580, NEX-3, NEX-5, NEX-C3 and NEX-VG10E. + }, + 0x00 => { #JR + Name => 'ShutterSpeedSetting', + Notes => 'used only in M and S exposure modes', + ValueConv => '$val ? 2 ** (6 - $val/8) : 0', + ValueConvInv => '$val ? int((6 - log($val) / log(2)) * 8 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x01 => { #JR + Name => 'ApertureSetting', + Notes => 'used only in M and A exposure modes', + ValueConv => '2 ** (($val/8 - 1) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }, + 0x02 => { + Name => 'ISOSetting', + ValueConv => '($val and $val < 254) ? exp(($val/8-6)*log(2))*100 : $val', + ValueConvInv => '($val and $val != 254) ? 8*(log($val/100)/log(2)+6) : $val', + PrintConv => { + OTHER => sub { + my ($val, $inv) = @_; + return int($val + 0.5) unless $inv; + return Image::ExifTool::IsFloat($val) ? $val : undef; + }, + 0 => 'Auto', + 254 => 'n/a', # get this for multi-shot noise reduction + }, + }, + 0x03 => { #JR + Name => 'ExposureCompensationSet', + ValueConv => '($val - 128) / 24', #PH + ValueConvInv => 'int($val * 24 + 128.5)', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x04 => { #JR + Name => 'DriveModeSetting', + # Same drivemode info is repeated in 0x0034, but with at least the following exceptions: + # - 0x0034 not for A550 ? - seen "0" + # - hand-held night (0x05=56): 0x0004=0x10 and 0x0034=0xd3 + # - 3D sweep panorama (0x05=57): 0x0004=0x10 and 0x0034=0xd6 + # - sweep panorama (0x05=80): 0x0004=0x10 and 0x0034=0xd5 + # preliminary conclusion: 0x0004 is Drivemode as pre-set, but may be overruled by Scene/Panorama mode selections + # 0x0034 is Divemode as actually used + PrintHex => 1, + PrintConv => { + 0x10 => 'Single Frame', + 0x21 => 'Continuous High', # also automatically selected for Scene mode Sports-action (0x05=52) + 0x22 => 'Continuous Low', + 0x30 => 'Speed Priority Continuous', + 0x51 => 'Self-timer 10 sec', + 0x52 => 'Self-timer 2 sec, Mirror Lock-up', + 0x71 => 'Continuous Bracketing 0.3 EV', + 0x75 => 'Continuous Bracketing 0.7 EV', + 0x91 => 'White Balance Bracketing Low', + 0x92 => 'White Balance Bracketing High', + 0xc0 => 'Remote Commander', + }, + }, + 0x05 => { #JR + Name => 'ExposureProgram', + # Camera exposure program/mode as selected with the Mode dial. + # For SCN a further selection is done via the menu + # Matches OK with 0xb023 + SeparateTable => 'ExposureProgram2', + PrintConv => \%sonyExposureProgram2, + }, + 0x06 => { #JR + Name => 'FocusModeSetting', + PrintConv => { + 17 => 'AF-S', + 18 => 'AF-C', + 19 => 'AF-A', + 32 => 'Manual', + 48 => 'DMF', #(NC) (seen for NEX-5) + }, + }, + 0x07 => { #JR + Name => 'MeteringMode', + PrintConv => { + 1 => 'Multi-segment', + 2 => 'Center-weighted average', + 3 => 'Spot', + }, + }, + 0x09 => { #JR + Name => 'SonyImageSize', + PrintConv => { # values confirmed as noted for the A580 and A33 + 21 => 'Large (3:2)', # A580: 16M (4912x3264), A33: 14M (4592x3056) + 22 => 'Medium (3:2)', # A580: 8.4M (3568x2368), A33: 7.4M (3344x2224) + 23 => 'Small (3:2)', # A580: 4.0M (2448x1624), A33: 3.5M (2288x1520) + 25 => 'Large (16:9)', # A580: 14M (4912x2760) + 26 => 'Medium (16:9)', # A580: 7.1M (3568x2000) + 27 => 'Small (16:9)', # A580: 3.4M (2448x1376) + }, + }, + 0x0a => { #JR + Name => 'AspectRatio', + # normally 4 for A580 3:2 ratio images + # seen 8 when selecting 16:9 via menu, and when selecting Panorama mode + PrintConv => { + 4 => '3:2', + 8 => '16:9', + }, + }, + 0x0b => { #JR + Name => 'Quality', + PrintConv => { + 2 => 'RAW', + 4 => 'RAW + JPEG', + 6 => 'Fine', + 7 => 'Standard', + }, + }, + 0x0c => { + Name => 'DynamicRangeOptimizerSetting', + PrintConv => { + 1 => 'Off', + 16 => 'On (Auto)', + 17 => 'On (Manual)', + }, + }, + 0x0d => 'DynamicRangeOptimizerLevel', + 0x0e => { #JR + Name => 'ColorSpace', + PrintConv => { + 1 => 'sRGB', + 2 => 'Adobe RGB', + }, + }, + 0x0f => { #JR + Name => 'CreativeStyleSetting', + PrintConvColumns => 2, + PrintConv => { + 16 => 'Standard', + 32 => 'Vivid', + 64 => 'Portrait', + 80 => 'Landscape', + 96 => 'B&W', + 160 => 'Sunset', + }, + }, + 0x10 => { #JR (seen values 253, 254, 255, 0, 1, 2, 3) + Name => 'ContrastSetting', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x11 => { #JR + Name => 'SaturationSetting', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x12 => { #JR + Name => 'SharpnessSetting', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x16 => { #JR + Name => 'WhiteBalanceSetting', + # many guessed, based on "logical system" as observed for Daylight and Shade and steps of 16 between the modes + PrintHex => 1, + PrintConvColumns => 2, + PrintConv => \%whiteBalanceSetting, + SeparateTable => 1, + }, + 0x17 => { #JR + Name => 'ColorTemperatureSetting', + # matches "0xb021 ColorTemperature" when WB set to "Custom" or "Color Temperature/Color Filter" + ValueConv => '$val * 100', + ValueConvInv => '$val / 100', + PrintConv => '"$val K"', + PrintConvInv => '$val =~ s/ ?K$//i; $val', + }, + 0x18 => { #JR + Name => 'ColorCompensationFilterSet', + # seen 0, 1-9 and 245-255, corresponding to 0, M1-M9 and G9-G1 on camera display + # matches "0xb022 ColorCompensationFilter" when WB set to "Custom" or "Color Temperature/Color Filter" + Format => 'int8s', + Notes => 'negative is green, positive is magenta', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x19 => { #JR + Name => 'CustomWB_RGBLevels', + Format => 'int16uRev[3]', + # 0x19 - 0x1e are related to Custom WB measurements performed by the camera. + # The values change only each time when measuring and setting a new Custom WB. + # (0x19,0x1a) and (0x1d,0x1e) are same as MoreSettings (0x1a,0x1b) and (0x1c,0x1d) + }, + # 0x1f - always 2 (ref JR) + 0x20 => { #JR + Name => 'FlashMode', + PrintConvColumns => 2, + PrintConv => { + 1 => 'Flash Off', + 16 => 'Autoflash', + 17 => 'Fill-flash', + 18 => 'Slow Sync', + 19 => 'Rear Sync', + 20 => 'Wireless', + }, + }, + 0x21 => { #JR + Name => 'FlashControl', + PrintConv => { + 1 => 'ADI Flash', + 2 => 'Pre-flash TTL', + }, + }, + 0x23 => { #JR + Name => 'FlashExposureCompSet', + Description => 'Flash Exposure Comp. Setting', + # (as pre-selected by the user, not zero if flash didn't fire) + ValueConv => '($val - 128) / 24', #PH + ValueConvInv => 'int($val * 24 + 128.5)', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x24 => { + Name => 'AFAreaMode', + PrintConv => { + 1 => 'Wide', + 2 => 'Spot', + 3 => 'Local', + 4 => 'Flexible', #JR + # (Flexible Spot is a grid of 17x11 points for the NEX-5) + }, + }, + 0x25 => { #JR + Name => 'LongExposureNoiseReduction', + PrintConv => { + 1 => 'Off', + 16 => 'On', # (unused or dark subject) + }, + }, + 0x26 => { #JR + Name => 'HighISONoiseReduction', + PrintConv => { + 16 => 'Low', + 17 => 'High', + 19 => 'Auto', + }, + }, + 0x27 => { #JR + Name => 'SmileShutterMode', + PrintConv => { + 17 => 'Slight Smile', + 18 => 'Normal Smile', + 19 => 'Big Smile', + }, + }, + 0x28 => { #JR + Name => 'RedEyeReduction', + PrintConv => { + 1 => 'Off', + 16 => 'On', + }, + }, + 0x2d => { + Name => 'HDRSetting', + PrintConv => { + 1 => 'Off', + 16 => 'On (Auto)', + 17 => 'On (Manual)', + }, + }, + 0x2e => { + Name => 'HDRLevel', + PrintConvColumns => 3, + PrintConv => { + 33 => '1 EV', + 34 => '1.5 EV', #JR (NC) + 35 => '2 EV', + 36 => '2.5 EV', #JR (NC) + 37 => '3 EV', + 38 => '3.5 EV', #PH (NC) + 39 => '4 EV', + 40 => '5 EV', + 41 => '6 EV', + }, + }, + 0x2f => { #JR (not sure what is difference with 0x85) + Name => 'ViewingMode', + PrintConv => { + 16 => 'ViewFinder', + 33 => 'Focus Check Live View', + 34 => 'Quick AF Live View', + }, + }, + 0x30 => { #JR + Name => 'FaceDetection', + PrintConv => { + 1 => 'Off', + 16 => 'On', + }, + }, + 0x31 => { #JR + Name => 'SmileShutter', + PrintConv => { + 1 => 'Off', + 16 => 'On', + }, + }, + 0x32 => { #JR + Name => 'SweepPanoramaSize', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 1 => 'Standard', + 2 => 'Wide', + }, + }, + 0x33 => { #JR + Name => 'SweepPanoramaDirection', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 1 => 'Right', + 2 => 'Left', + 3 => 'Up', + 4 => 'Down', + }, + }, + 0x34 => { #JR + Name => 'DriveMode', # (drive mode actually used) + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + PrintHex => 1, + PrintConv => { + 0x10 => 'Single Frame', + 0x21 => 'Continuous High', # also automatically selected for Scene mode Sports-action (0x05=52) + 0x22 => 'Continuous Low', + 0x30 => 'Speed Priority Continuous', + 0x51 => 'Self-timer 10 sec', + 0x52 => 'Self-timer 2 sec, Mirror Lock-up', + 0x71 => 'Continuous Bracketing 0.3 EV', + 0x75 => 'Continuous Bracketing 0.7 EV', + 0x91 => 'White Balance Bracketing Low', + 0x92 => 'White Balance Bracketing High', + 0xc0 => 'Remote Commander', + 0xd1 => 'Continuous - HDR', + 0xd2 => 'Continuous - Multi Frame NR', + 0xd3 => 'Continuous - Handheld Night Shot', # (also called "Hand-held Twilight") + 0xd4 => 'Continuous - Anti Motion Blur', #PH (NEX-5) + 0xd5 => 'Continuous - Sweep Panorama', + 0xd6 => 'Continuous - 3D Sweep Panorama', + }, + }, + 0x35 => { + Name => 'MultiFrameNoiseReduction', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 0 => 'n/a', # seen for A450/A500/A550 + 1 => 'Off', + 16 => 'On', + 255 => 'None', # seen for NEX-3/5/C3 + }, + }, + 0x36 => { #JR (not 100% sure about this one) + Name => 'LiveViewAFSetting', + Condition => '$$self{Model} !~ /^(NEX-|DSLR-(A450|A500|A550)$)/', + PrintConv => { + 0 => 'n/a', + 1 => 'Phase-detect AF', + 2 => 'Contrast AF', + # Contrast AF is only available with SSM/SAM lenses and in Focus Check LV, + # NOT in Quick AF LV, and is automatically set when mounting SSM/SAM lens + # - changes into Phase-AF when switching to Quick AF LV. + }, + }, + 0x38 => { #JR + Name => 'PanoramaSize3D', + Description => '3D Panorama Size', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 0 => 'n/a', + 1 => 'Standard', + 2 => 'Wide', + 3 => '16:9', + }, + }, + 0x83 => { #JR + Name => 'AFButtonPressed', + # only indicates pressing and holding the "AF" button (centre-controller), + # not pressing the shutter release button halfway down + Condition => '$$self{Model} !~ /^(NEX-|DSLR-(A450|A500|A550)$)/', + PrintConv => { + 1 => 'No', + 16 => 'Yes', + }, + }, + 0x84 => { #JR (not 100% sure about this one) + Name => 'LiveViewMetering', + Condition => '$$self{Model} !~ /^(NEX-|DSLR-(A450|A500|A550)$)/', + PrintConv => { + 0 => 'n/a', + 16 => '40 Segment', # DSLR with LiveView/OVF switch in OVF position + 32 => '1200-zone Evaluative', # SLT, or DSLR with LiveView/OVF switch in LiveView position + }, + }, + 0x85 => { #JR (not sure what is difference with 0x2f) + Name => 'ViewingMode2', + Condition => '$$self{Model} !~ /^(NEX-|DSLR-(A450|A500|A550)$)/', + PrintConv => { + 0 => 'n/a', + 16 => 'Viewfinder', + 33 => 'Focus Check Live View', + 34 => 'Quick AF Live View', + }, + }, + 0x86 => { #JR + Name => 'AELock', + Condition => '$$self{Model} !~ /^(NEX-|DSLR-(A450|A500|A550)$)/', + PrintConv => { + 1 => 'On', + 2 => 'Off', + }, + }, + 0x87 => { #JR + Name => 'FlashStatusBuilt-in', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)/', + PrintConv => { + 1 => 'Off', + 2 => 'On', + }, + }, + 0x88 => { #JR + Name => 'FlashStatusExternal', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)/', + PrintConv => { + 1 => 'None', + 2 => 'Off', + 3 => 'On', + }, + }, +# 0x8a => { #JR +# Name => 'LensAF', +# Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', +# PrintConv => { +# 1 => 'No', +# 16 => 'AF Lens', +# }, +# }, + 0x8b => { #JR + Name => 'LiveViewFocusMode', + Condition => '$$self{Model} !~ /^(NEX-|DSLR-(A450|A500|A550)$)/', + PrintConv => { + 0 => 'n/a', + 1 => 'AF', + 16 => 'Manual', + }, + }, +# 0x8e => { #JR +# Name => 'LensSAM', +# Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', +# PrintConv => { +# 1 => 'No', +# 16 => 'SAM Lens', +# }, +# }, + 0x99 => { #JR + Name => 'LensMount', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + DataMember => 'LensMount', + RawConv => '$$self{LensMount} = $val', + PrintConv => { + 1 => 'Unknown', + 16 => 'A-mount', + 17 => 'E-mount', + }, + }, +# 0x9b => { #JR +# Name => 'LensOSS', +# Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', +# PrintConv => { +# 1 => 'No', +# 16 => 'OSS Lens', +# 17 => 'OSS Lens (NEX-VG)', # active ? +# }, +# }, + # 0x9c - 1; 2 for multi-shot modes + 0x10c => { #JR + Name => 'SequenceNumber', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', # seen 18 for A550, so better exclude ? + # normally 0; seen 1,2,3 for bracketing, 6 for Handheld Night Shot, 3 for HDR, 6 for MFNR + PrintConv => { + 0 => 'Single', + 255 => 'n/a', + OTHER => sub { shift }, # pass all other numbers straight through + }, + }, + # when reading 0x0114 - 0x0117 as int32u: + # - upper 8 bits (0x0117): always value 4, meaning unknown + # - next 10 bits: FolderNumber (max. 999 according to manual) + # - last 14 bits: ImageNumber (max 9999) + 0x0114 => { #JR + Name => 'FolderNumber', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + Format => 'int32u', + Mask => 0x00ffc000, + PrintConv => 'sprintf("%.3d",$val)', + PrintConvInv => '$val', + }, + 276.1 => { #JR (0x0114.1) + Name => 'ImageNumber', + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + Format => 'int32u', + Mask => 0x00003fff, + PrintConv => 'sprintf("%.4d",$val)', + PrintConvInv => '$val', + }, + 0x200 => { #JR + Name => 'ShotNumberSincePowerUp2', + Notes => q{ + same as ShotNumberSincePowerUp for single-shot images, but includes all + shots of the current image in multi-shot modes like HDR, panorama, and + multi-frame noise reduction + }, + # (includes all shutter actuations of the current shot) + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', + Format => 'int32u', + }, + 0x283 => { #JR + Name => 'AFButtonPressed', + # only indicates pressing and holding the "AF" button (centre-controller), + # not pressing the shutter release button halfway down + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 1 => 'No', + 16 => 'Yes', + }, + }, + 0x284 => { #JR (not 100% sure about this one) + Name => 'LiveViewMetering', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 0 => 'n/a', + 16 => '40 Segment', # DSLR with LiveView/OVF switch in OVF position + 32 => '1200-zone Evaluative', # DSLR with LiveView/OVF switch in LiveView position + }, + }, + 0x285 => { #JR (not sure what is difference with 0x2f) + Name => 'ViewingMode2', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 0 => 'n/a', + 16 => 'Viewfinder', + 33 => 'Focus Check Live View', + 34 => 'Quick AF Live View', + }, + }, + 0x286 => { #JR + Name => 'AELock', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 1 => 'On', + 2 => 'Off', + }, + }, + 0x287 => { #JR + Name => 'FlashStatusBuilt-in', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + Notes => 'A450, A500 and A550', + PrintConv => { + 1 => 'Off', + 2 => 'On', + }, + }, + 0x288 => { #JR + Name => 'FlashStatusExternal', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + Notes => 'A450, A500 and A550', + PrintConv => { + 1 => 'None', + 2 => 'Off', + 3 => 'On', + }, + }, + 0x28b => { #JR + Name => 'LiveViewFocusMode', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + PrintConv => { + 0 => 'n/a', + 1 => 'AF', + 16 => 'Manual', + }, + }, + 0x30c => { #JR + Name => 'SequenceNumber', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + Notes => 'A450, A500 and A550', + # normally 0; seen 2 for HDR + PrintConv => { + 0 => 'Single', + 255 => 'n/a', + OTHER => sub { shift }, # pass all other numbers straight through + }, + }, + 0x314 => { #JR + Name => 'ImageNumber', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + Format => 'int16u', + Notes => 'A450, A500 and A550', + Mask => 0x3fff, #PH (not sure what the upper 2 bits are for) + PrintConv => 'sprintf("%.4d",$val)', + PrintConvInv => '$val', + }, + 0x316 => { #JR + Name => 'FolderNumber', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + Notes => 'A450, A500 and A550', + Format => 'int16u', + Mask => 0x03ff, #(NC) + PrintConv => 'sprintf("%.3d",$val)', + PrintConvInv => '$val', + }, + 0x03f0 => { + Name => 'LensE-mountVersion', + Format => 'int16u', + Condition => '($$self{Model} =~ /^NEX-/)', + PrintConv => 'sprintf("%x.%.2x",$val>>8,$val&0xff)', + PrintConvInv => 'my @a=split(/\./,$val);(hex($a[0])<<8)|hex($a[1])', + }, + # 0x03f3 - this is probably LensFirmwareVersion and not CameraE-MountVersion (ref JR, Sept.2015) + # 0x03f3 and 0x03f4 change together and behave similarly to Tag940c 0x0014 and 0x0015 - see comments there. + 0x03f3 => { + Name => 'LensFirmwareVersion', + Format => 'int16u', + Condition => '($$self{Model} =~ /^NEX-/)', + PrintConv => 'sprintf("Ver.%.2x.%.3d",$val>>8,$val&0xff)', + }, + 0x3f7 => { #JR + Name => 'LensType2', + Condition => '($$self{Model} =~ /^NEX-/) and ($$self{LensMount} != 1)', + Format => 'int16u', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x400 => { #JR + Name => 'ImageNumber', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + Format => 'int16u', + Notes => 'A450, A500 and A550', + Mask => 0x3fff, #PH (not sure what the upper 2 bits are for) + PrintConv => 'sprintf("%.4d",$val)', + PrintConvInv => '$val', + }, + 0x402 => { #JR + Name => 'FolderNumber', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)$/', + Format => 'int16u', + Mask => 0x03ff, #(NC) + Notes => 'A450, A500 and A550', + PrintConv => 'sprintf("%.3d",$val)', + PrintConvInv => '$val', + }, +); + +# Camera settings for other models +%Image::ExifTool::Sony::CameraSettingsUnknown = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int16u', +); + +# extra hardware information (ref JR) +%Image::ExifTool::Sony::ExtraInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Extra hardware information for the A850 and A900.', + # 0x0000: seen values 5 or 6 + 0x0001 => { + Name => 'BatteryTemperature', + # seen values of appr. 55 - 115: looks like temperature Fahrenheit + # changing battery in cold weather: new battery starts with value 53 + ValueConv => '($val - 32) / 1.8', # convert to Celsius + ValueConvInv => '$val * 1.8 + 32', + PrintConv => 'sprintf("%.1f C",$val)', + PrintConvInv => '$val=~ s/\s*C//; $val', + # (may be invalid for non-OEM batteries) + }, + 0x0002 => { + Name => 'BatteryUnknown', + # appears to be an int16u value together with 0x0005 (values similar to ExtraInfo3 0x0000) + # seen values of appr. 800 at 23 deg C to 630 at 40 deg C for A850 with NP-FM500H battery (7.2 V nominal) + # i.e. inversely proportional to BatteryTemperature: can not be BatteryVoltage, must be something else ? + Unknown => 1, + Format => 'undef[4]', + ValueConv => sub { + my $val = shift;; + my @a = unpack("CvC",pack('v*', unpack('n*', $val))); + return $a[1]; + }, + }, + # 0x0003: seen 0 or 16 + # 0x0004: always 255 + # 0x0006: int16u value together with 0x0009: same behaviour and almost same values as 0x0002 + # 0x0007: always 3 + 0x0008 => { + Name => 'BatteryVoltage', + # 0x0008: int16u value together with 0x000b: + # values follow BatteryLevel: from appr.900 when battery full, to appr. 775 when empty. + # with factor 118 they range from appr. 7.6 to 6.6 - looks like battery voltage (nominal 7.2 V) + Unknown => 1, + Format => 'undef[4]', + ValueConv => sub { + my $val = shift;; + my @a = unpack("CvC",pack('v*', unpack('n*', $val))); + return $a[1]/118; + }, + PrintConv => 'sprintf("%.2f V",$val)', + }, + 0x000a => { + # seen mostly 213 and 246, corresponding with other ImageStabilization On/Off tags. + Name => 'ImageStabilization2', + Unknown => 1, # (because the decoding is funny and possibly incomplete - PH) + PrintConv => { + 191 => 'On (191)', # seen a few times with moving subject, continuous drive, bracketing + 207 => 'On (207)', # seen once with RemoteCommander + 210 => 'On (210)', # seen a few times with continuous drive + 213 => 'On', + 246 => 'Off', + }, + }, + # 0x000c: seen always decreasing values, from max. 107 to min. 0, + # then jump back to high value: correlates with battery change/recharging + # Seen once 255 immediately after inserting new battery, next frame OK at 106. + # Validation: matches exactly with batterylevel display on camera (all 100+ values displayed as 100%) + 0x000c => { + Name => 'BatteryLevel', + PrintConv => '"$val%"', + PrintConvInv => '$val=~s/\s*\%//; $val', + }, + # 0x000d: always 2 + # 0x000e: always 204 + # 0x000f: always 0 + # 0x0010-0x0019: always 204 + 0x001a => { + Name => 'ExtraInfoVersion', + Format => 'int8u[4]', + PrintConv => '$val=~tr/ /./; $val', + PrintConvInv => '$val=~tr/./ /; $val', + # always 0 1 0 1 for 0x0131 Software = DSLR-A850 v1.00 + # always 0 2 0 4 for 0x0131 Software = DSLR-A850 v2.00 + # seen 0 2 0 0 for 0x0131 Software = DSLR-A900 v1.00 + # seen 0 4 0 0 for 0x0131 Software = DSLR-A900 v1.00 + # seen 0 5 0 4 for 0x0131 Software = DSLR-A900 v2.00 + # A850: correlates exactly with Firmware versions. + # A900: have there been different FW 1.0 versions ? + }, +); + +# extra hardware information (ref JR) +%Image::ExifTool::Sony::ExtraInfo2 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'Extra hardware information for the A230/290/330/380/390.', + 0x0004 => { + Name => 'BatteryLevel', + PrintConv => '"$val%"', + PrintConvInv => '$val=~s/\s*\%//; $val', + }, + 0x0012 => { + Name => 'ImageStabilization', + PrintConv => { + 0 => 'Off', + 64 => 'On', + }, + }, +); + +# extra hardware information (ref JR) +%Image::ExifTool::Sony::ExtraInfo3 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + Extra hardware information for the A33, A35, A55, A450, A500, A550, A560, + A580 and NEX-3/5/C3/VG10. + }, + 0x0000 => { + Name => 'BatteryUnknown', + # seen values of appr. 870 at 10 deg C to 650 at 39 deg C for A580 with NP-FM500H battery (7.2 V nominal) + # i.e. inversely proportional to BatteryTemperature: can not be BatteryVoltage, must be something else ? + Unknown => 1, + Format => 'int16u', + }, + 0x0002 => { + Name => 'BatteryTemperature', + ValueConv => '($val - 32) / 1.8', # convert to Celsius + ValueConvInv => '$val * 1.8 + 32', + PrintConv => 'sprintf("%.1f C",$val)', + PrintConvInv => '$val=~ s/\s*C//; $val', + }, + 0x0004 => { + Name => 'BatteryLevel', + PrintConv => '"$val%"', + PrintConvInv => '$val=~s/\s*\%//; $val', + }, + # 0x0005: always 255 + # from here on the differences between DSLR, SLT and NEX + # 0x0006 and 0x0008: + # values follow BatteryLevel: start high (970, 940) when battery full, + # and decrease with decreasing battery level to (850, 815) + # with factor 128 they range from (7.6, 7.3) to (6.6, 6.4) - looks like battery voltage (nominal 7.2 V) + 0x0006 => { + Name => 'BatteryVoltage1', + Format => 'int16u', + Condition => '$$self{Model} !~ /^(NEX-(3|5|5C|C3|VG10|VG10E))\b/', + ValueConv => '$val / 128', + ValueConvInv => '$val * 128', + PrintConv => 'sprintf("%.2f V",$val)', + PrintConvInv => '$val=~s/\s*V//; $val', + }, + 0x0008 => { + Name => 'BatteryVoltage2', + Format => 'int16u', + Condition => '$$self{Model} !~ /^(NEX-(3|5|5C|C3|VG10|VG10E))\b/', + ValueConv => '$val / 128', + ValueConvInv => '$val * 128', + PrintConv => 'sprintf("%.2f V",$val)', + PrintConvInv => '$val=~s/\s*V//; $val', + }, + # 0x000a - 0x000f: 3 int16u values: probably some mode or status info: + # seen various 3-number-sequences for SLT and DSLR, but meaning unknown + # 0x000a => { + # Name => 'ExtraInfo_000a', + # Format => 'int16u[3]', + # Condition => '$$self{Model} !~ /^(NEX-(3|5|5C|C3|VG10|VG10E))\b/', + # }, + # 0x0010 seen: + # 176 for SLT + # 204 for NEX + # 240 for DSLR + 0x0011 => { + Name => 'ImageStabilization', + Condition => '$$self{Model} !~ /^(NEX-(3|5|5C|C3|VG10|VG10E))\b/', + # usually matches 0xb026 ImageStabilization, except some images with SelfTimer and on tripod + PrintConv => { + 0 => 'Off', + 64 => 'On', + }, + }, + 0x0014 => [ + { + Name => 'BatteryState', + Condition => '$$self{Model} =~ /^SLT-/', + # possibly relates to "simple" batterylevel indication with battery-icon, but not completely sure + Notes => 'BatteryState for SLT models', + PrintConv => { + 1 => 'Empty', + 2 => 'Low', + 3 => 'Half full', + 4 => 'Almost full', + 5 => 'Full', + }, + },{ + Name => 'ExposureProgram', + Condition => '$$self{Model} =~ /^DSLR-(A450|A500|A550)\b/', + Notes => 'ExposureProgram for the A450, A500 and A550', + Priority => 0, # (some unknown values) + PrintConv => { + 241 => 'Landscape', + 243 => 'Aperture-priority AE', + 245 => 'Portrait', + 246 => 'Auto', + 247 => 'Program AE', + 249 => 'Macro', + 252 => 'Sunset', + 253 => 'Sports', #PH (A550) + 255 => 'Manual', + # missing: Shutter speed priority AE, No Flash, Night View + }, + },{ + Name => 'ModeDialPosition', + Condition => '$$self{Model} =~ /^DSLR-/', + Notes => 'ModeDialPosition for other DSLR models', + # (decoded from A560/A580) + PrintConv => { + 248 => 'No Flash', + 249 => 'Aperture-priority AE', + 250 => 'SCN', # <-- the reason we don't call it ExposureProgram for these models + 251 => 'Shutter speed priority AE', + 252 => 'Auto', + 253 => 'Program AE', + 254 => 'Panorama', + 255 => 'Manual', + }, + }, + ], + # 0x0015: DSLR: appears to be a bitmask relating to "switch" positions: + # bit 0 ( 1) only seen OFF for A580 + # bit 1 ( 2) ON = Flash down, OFF = Flash raised + # bit 2 ( 4) only seen ON for A580 + # bit 3 ( 8) only seen ON for A580 + # bit 4 ( 16) ON = AF, OFF = MF + # bit 5 ( 32) ON = OVF, OFF = LiveView + # bit 6 ( 64) seen ON and OFF, meaning unknown + # bit 7 (128) seen ON and OFF, meaning unknown + # 0x0016: DSLR: seen 244,245,252,254, decoded for A580 with 32GB SD and 16GB MS cards + # 0x0016: NEX: seen 61,62, 125,126, 190: bits '64' and '128' appear to relate to CameraOrientation + # 0x0016: SLT: seen 64 - 78, meaning unknown + 0x0016 => [{ + Name => 'MemoryCardConfiguration', + Condition => '$$self{Model} =~ /^DSLR-/', + PrintConv => { + 244 => 'MemoryStick in use, SD card present', + 245 => 'MemoryStick in use, SD slot empty', + 252 => 'SD card in use, MemoryStick present', + 254 => 'SD card in use, MemoryStick slot empty', + }, + },{ + Name => 'CameraOrientation', + Condition => '$$self{Model} =~ /^(NEX-(3|5|5C|C3|VG10|VG10E))\b/', + Mask => 0xc0, # (don't know what other bits mean) + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + 3 => 'Rotate 180', #(NC) + }, + + }], + # 0x0017: seen 0 for SLT, 255 for DSLR, variable for NEX + 0x0018 => { + Name => 'CameraOrientation', + Condition => '$$self{Model} !~ /^(NEX-(3|5|5C|C3|VG10|VG10E))\b/', + Mask => 0x30, # (don't know what other bits mean) + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 90 CW', + 2 => 'Rotate 270 CW', + 3 => 'Rotate 180', + }, + }, + # 0x0019: + # A450/500/550: 0 - 12 and 233 - 255 + # A560/580: 1 or 64, seen a few 0 and 8 + # A33/35/55: seen 0, 1, 64 + # NEX: 204 + # 0x001a, 0x001c appear to be 2 int16u values, meaning unknown +); + +# shot information (ref PH) +%Image::ExifTool::Sony::ShotInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x02, 0x30, 0x32, 0x34 ], + IS_SUBDIR => [ 0x48, 0x5e ], + # 0x00 - byte order 'II' + 0x02 => { + Name => 'FaceInfoOffset', + Format => 'int16u', + DataMember => 'FaceInfoOffset', + Writable => 0, + RawConv => '$$self{FaceInfoOffset} = $val', + }, + 0x06 => { + Name => 'SonyDateTime', + Format => 'string[20]', + Groups => { 2 => 'Time' }, + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,0)', + }, + 0x1a => { Name => 'SonyImageHeight', Format => 'int16u' }, #JR + 0x1c => { Name => 'SonyImageWidth', Format => 'int16u' }, #JR + 0x30 => { #Jeffrey Friedl + Name => 'FacesDetected', + DataMember => 'FacesDetected', + Format => 'int16u', + RawConv => '$$self{FacesDetected} = $val', + }, + 0x32 => { + Name => 'FaceInfoLength', # length of a single FaceInfo entry + DataMember => 'FaceInfoLength', + Format => 'int16u', + Writable => 0, + RawConv => '$$self{FaceInfoLength} = $val', + }, + 0x34 => { + # oldest/other DSC/other - + # older DSC models "DC5303320222000" or "DC6303320222000" + # DSC-W650/W690/W730 "THm101000000000" or "THm211000000000" + # DSC-HX9V generation and newer "DC7303320222000" + Name => 'MetaVersion', # (tentative) + Format => 'string[16]', + DataMember => 'MetaVersion', + RawConv => '$$self{MetaVersion} = $val', + }, + 0x48 => { # (most models: DC5303320222000 and DC6303320222000) + Name => 'FaceInfo1', + Condition => q{ + $$self{FacesDetected} and + $$self{FaceInfoOffset} == 0x48 and + $$self{FaceInfoLength} == 0x20 + }, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::FaceInfo1' }, + }, + 0x5e => { # (HX7V: DC7303320222000) + Name => 'FaceInfo2', + Condition => q{ + $$self{FacesDetected} and + $$self{FaceInfoOffset} == 0x5e and + $$self{FaceInfoLength} == 0x25 + }, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::FaceInfo2' }, + }, +); + +# tags used in Tag2010 and Tag9400 tables +my %sequenceImageNumber = ( #PH + Name => 'SequenceImageNumber', + Notes => 'number of images captured in burst sequence', + # (not shutter count because it increments for auto portrait framing) + Format => 'int32u', + ValueConv => '$val + 1', + ValueConvInv => '$val - 1', +); +my %sequenceFileNumber = ( #PH + Name => 'SequenceFileNumber', + Notes => 'file number in burst sequence', + Format => 'int32u', + ValueConv => '$val + 1', #JR + ValueConvInv => '$val - 1', +); +my %releaseMode2 = ( #JR + Name => 'ReleaseMode2', + SeparateTable => 'ReleaseMode2', + PrintConv => { + 0 => 'Normal', + 1 => 'Continuous', # (RX100 "Continuous - Self-timer") + 2 => 'Continuous - Exposure Bracketing', # (RX100) +# 3 - also DRO Bracketing (ILCE-7RM2), not "Continuous" because only single exposure from which camera makes 3 versions + 3 => 'DRO or White Balance Bracketing', # (HX9V) (RX100) (ILCE-7RM2) + 5 => 'Continuous - Burst', # (HX9V) + 6 => 'Single Frame - Capture During Movie', #PH (RX100) + 7 => 'Continuous - Sweep Panorama', + 8 => 'Continuous - Anti-Motion Blur, Hand-held Twilight', # (HX9V) + 9 => 'Continuous - HDR', + 10 => 'Continuous - Background defocus', # (HX9V) + 13 => 'Continuous - 3D Sweep Panorama', #PH/JR + 15 => 'Continuous - High Resolution Sweep Panorama', #JR (HX50V) + 16 => 'Continuous - 3D Image', # (HX9V) + 17 => 'Continuous - Burst 2', # (WX7 - PH) (#JR 9400-SequenceLength=10 shots) + 18 => 'Normal - iAuto+', # seen for several ILCE-3500/6000/6500/7S iAuto+ single-shot images ... + 19 => 'Continuous - Speed/Advance Priority', #PH/JR (RX100) + 20 => 'Continuous - Multi Frame NR', + 23 => 'Single-frame - Exposure Bracketing', # (seen for ILCE-7 series) + 26 => 'Continuous Low', #PH (A77) + 27 => 'Continuous - High Sensitivity', # seen for DSC-WX60 and WX300 + 28 => 'Smile Shutter', #PH (RX100) + 29 => 'Continuous - Tele-zoom Advance Priority', + # 30 - seen quite often for single-shot images ... SLT-A58, ILCE-5100/6000 + # 33 - Continuous - seen for DSC-RX100M5, RX10M4, maybe 24 fps mode ??? + 146 => 'Single Frame - Movie Capture', #PH (seen in Tag2010 ReleaseMode2 values) + }, +); + +# tag definitions for Tag2010 tables (ref JR) +my %sonyDateTime2010 = ( + Name => 'SonyDateTime', + Format => 'undef[7]', + Shift => 'Time', + ValueConv => q{ + my @v = unpack('vC*', $val); + return sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d", @v) + }, + ValueConvInv => q{ + my @v = ($val =~ /\d+/g); + return undef unless @v == 6; + return pack('vC*', @v); + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,0)', +); +my %releaseMode2010 = ( + Name => 'ReleaseMode3', + PrintConv => { + 0 => 'Normal', + 1 => 'Continuous', + 2 => 'Bracketing', # (all types: Continuous and Single-Frame Exposure, White Balance and DRO bracketing - PH/JR) + # 3 => 'Remote Commander', (NC) (seen this when other ReleaseMode and ReleaseMode2 are 'Normal' - PH, A77) + 4 => 'Continuous - Burst', # seen for DSC-WX500 with burst of 10 shots + 5 => 'Continuous - Speed/Advance Priority', + 6 => 'Normal - Self-timer', # seen for ILCE-6300/6500/9, ILCA-99M2 + 9 => 'Single Burst Shooting', # first seen for DSC-RX100M7 + }, +); +my %selfTimer2010 = ( + Name => 'SelfTimer', + PrintConv => { + 0 => 'Off', + 1 => 'Self-timer 10 s', + 2 => 'Self-timer 2 s', + }, +); +my %selfTimerB2010 = ( # also value 1 for new 5 s mode of DSC-HX90V/RX10M2/RX100M4/WX500, ILCE-7RM2/7SM2 + Name => 'SelfTimer', + PrintConv => { + 0 => 'Off', + 1 => 'Self-timer 5 or 10 s', + 2 => 'Self-timer 2 s', + }, +); +my %gain2010 = ( + Name => 'StopsAboveBaseISO', + # BaseISO is 100 for SLT, ILCE-3000, NEX-5N/5R/5T/6/7/VG20/VG30/VG900, DSC-RX1/RX1R + # BaseISO is 200 for NEX-F3/3N + # BaseISO is 160 for DSC-RX100M2 + # BaseISO is 125 for DSC-RX100 + # Also several other DSC models have BaseISO different from 100. + Format => 'int16u', + ValueConv => '16 - $val/256', + ValueConvInv => '(16 - $val) * 256', + PrintConv => '$val ? sprintf("%.1f",$val) : $val', + PrintConvInv => '$val', +); +my %brightnessValue2010 = ( + Name => 'BrightnessValue', + Format => 'int16u', + ValueConv => '$val/256 - 56.6', + ValueConvInv => '($val + 56.6) * 256', +); +my %dynamicRangeOptimizer2010 = ( + Name => 'DynamicRangeOptimizer', + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 3 => 'Lv1', + 4 => 'Lv2', + 5 => 'Lv3', + 6 => 'Lv4', + 7 => 'Lv5', + 8 => 'n/a', + }, +); +my %hdr2010 = ( + Name => 'HDRSetting', # (Off when HDR tag is On for RX100 superior auto backlight - PH) + PrintConv => { + 0 => 'Off', + 1 => 'HDR Auto', + 3 => 'HDR 1 EV', + 5 => 'HDR 2 EV', + 7 => 'HDR 3 EV', + 9 => 'HDR 4 EV', + 11 => 'HDR 5 EV', + 13 => 'HDR 6 EV', + }, +); +my %exposureComp2010 = ( # only as set manually, remains 0 in exposure-bracketing modes + Name => 'ExposureCompensation', + Format=>'int16s', + ValueConv => '-$val/256', + ValueConvInv => '-$val*256', + PrintConv => '$val ? sprintf("%+.1f",$val) : 0', + PrintConvInv => '$val', +); +my %pictureEffect2010 = ( + Name => 'PictureEffect2', + SeparateTable => 'PictureEffect2', + PrintConv => { + 0 => 'Off', + 1 => 'Toy Camera', + 2 => 'Pop Color', + 3 => 'Posterization', + 4 => 'Retro Photo', + 5 => 'Soft High Key', + 6 => 'Partial Color', + 7 => 'High Contrast Monochrome', + 8 => 'Soft Focus', + 9 => 'HDR Painting', + 10 => 'Rich-tone Monochrome', + 11 => 'Miniature', + 12 => 'Water Color', + 13 => 'Illustration', + }, +); +my %quality2010 = ( + Name => 'Quality2', + PrintConv => { + 0 => 'JPEG', + 1 => 'RAW', + 2 => 'RAW + JPEG', + }, +); +my %meteringMode2010 = ( + Name => 'MeteringMode', + PrintConv => { + 0 => 'Multi-segment', + 2 => 'Center-weighted average', + 3 => 'Spot', + 4 => 'Average', + 5 => 'Highlight', + }, +); +my %flashMode2010 = ( + Name => 'FlashMode', + PrintConv => { + 0 => 'Autoflash', + 1 => 'Fill-flash', + 2 => 'Flash Off', + 3 => 'Slow Sync', + 4 => 'Rear Sync', + 6 => 'Wireless', + # 129 => 'unknown', # seen for ILCE-7M3 images + }, +); +my %exposureProgram2010 = ( + Name => 'ExposureProgram', + SeparateTable => 'ExposureProgram3', + PrintConv => \%sonyExposureProgram3, +); +my %pictureProfile2010 = ( + Name => 'PictureProfile', + # values 0-9: Seen for all cameras writing this tag: matches CreativeStyle and/or SceneMode settings. + # 10 and higher: Seen for ILCE-7S/7M2 and newer, having a PictureProfile setting, also some DSC/HDR models. + # Although intended for video, when set these profiles are also applied to (JPG) still images. + PrintConv => { + 0 => 'Gamma Still - Standard/Neutral (PP2)', # CreativeStyle = Standard or Neutral + 1 => 'Gamma Still - Portrait', + #2 - seen for DSC-HX90 + 3 => 'Gamma Still - Night View/Portrait', + 4 => 'Gamma Still - B&W/Sepia', + 5 => 'Gamma Still - Clear', + 6 => 'Gamma Still - Deep', + 7 => 'Gamma Still - Light', + 8 => 'Gamma Still - Vivid', # SceneMode or CreativeStyle = Vivid, Autumn, Sunset or Landscape + 9 => 'Gamma Still - Real', + 10 => 'Gamma Movie (PP1)', + 22 => 'Gamma ITU709 (PP3 or PP4)', # (also PP4 for A7M3, ref 14) + 24 => 'Gamma Cine1 (PP5)', + 25 => 'Gamma Cine2 (PP6)', + 26 => 'Gamma Cine3', + 27 => 'Gamma Cine4', + 28 => 'Gamma S-Log2 (PP7)', + 29 => 'Gamma ITU709 (800%)', + 31 => 'Gamma S-Log3 (PP8 or PP9)', #14 + 33 => 'Gamma HLG2 (PP10)', #14 + 34 => 'Gamma HLG3', #IB + 36 => 'Off', + 37 => 'FL', + 38 => 'VV2', + 39 => 'IN', + 40 => 'SH', + }, +); +my %isoSetting2010 = ( + 0 => 'Auto', + 5 => 25, + 7 => 40, + 8 => 50, + 9 => 64, + 10 => 80, + 11 => 100, + 12 => 125, + 13 => 160, + 14 => 200, + 15 => 250, + 16 => 320, + 17 => 400, + 18 => 500, + 19 => 640, + 20 => 800, + 21 => 1000, + 22 => 1250, + 23 => 1600, + 24 => 2000, + 25 => 2500, + 26 => 3200, + 27 => 4000, + 28 => 5000, + 29 => 6400, + 30 => 8000, + 31 => 10000, + 32 => 12800, + 33 => 16000, + 34 => 20000, + 35 => 25600, + 36 => 32000, + 37 => 40000, + 38 => 51200, + 39 => 64000, + 40 => 80000, + 41 => 102400, + 42 => 128000, + 43 => 160000, + 44 => 204800, + 45 => 256000, + 46 => 320000, + 47 => 409600, +); + +%Image::ExifTool::Sony::Tag2010a = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => 'Valid for NEX-5N.', + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + IS_SUBDIR => [ 0x04b0 ], + 0x04b0 => { + Name => 'MeterInfo', + Format => 'int32u[486]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MeterInfo' }, + }, + 0x1128 => { %releaseMode2010 }, + 0x112c => { %releaseMode2 }, + 0x1134 => { %selfTimer2010 }, + 0x1138 => { %flashMode2010 }, + 0x113e => { %gain2010 }, + 0x1140 => { %brightnessValue2010 }, + 0x1144 => { %dynamicRangeOptimizer2010 }, + 0x1148 => { %hdr2010 }, + 0x114c => { %exposureComp2010 }, + 0x115e => { %pictureProfile2010 }, + 0x115f => { %pictureProfile2010 }, + 0x1163 => { %pictureEffect2010 }, + 0x1170 => { %quality2010 }, + 0x1174 => { %meteringMode2010 }, + 0x1175 => { %exposureProgram2010 }, + 0x117c => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + #0x1a08 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x1a0c => { Name => 'SonyImageHeight', Format => 'int16u' }, +); + +%Image::ExifTool::Sony::Tag2010b = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => 'Valid for SLT-A65/A77, NEX-7/VG20E.', + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + IS_SUBDIR => [ 0x04b4 ], + 0x0000 => { %sequenceImageNumber }, #PH + 0x0004 => { %sequenceFileNumber }, #PH + 0x0008 => { %releaseMode2, Format => 'int32u' }, + #0x0044 => { Name => 'SonyImageWidth3', Format => 'int16u' }, + #0x0048 => { Name => 'SonyImageHeight3', Format => 'int16u' }, + #0x0054 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x0058 => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x0064 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x0068 => { Name => 'SonyImageHeight', Format => 'int16u' }, + #0x00a8 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x00ac => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x00b8 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x00bc => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x00c8 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x00cc => { Name => 'SonyImageHeight', Format => 'int16u' }, + 0x01b6 => { %sonyDateTime2010, Groups => { 2 => 'Time' } }, + #0x0204 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x0206 => { Name => 'SonyImageHeight', Format => 'int16u' }, + 0x0324 => { %dynamicRangeOptimizer2010 }, + 0x04b4 => { + Name => 'MeterInfo', + Format => 'int32u[486]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MeterInfo' }, + }, + 0x1128 => { %releaseMode2010 }, + 0x112c => { %releaseMode2 }, + 0x1134 => { %selfTimer2010 }, + 0x1138 => { %flashMode2010 }, + 0x113e => { %gain2010 }, + 0x1140 => { %brightnessValue2010 }, + 0x1144 => { %dynamicRangeOptimizer2010 }, + 0x1148 => { %hdr2010 }, + 0x114c => { %exposureComp2010 }, + 0x1162 => { %pictureProfile2010 }, + 0x1163 => { %pictureProfile2010 }, + 0x1167 => { %pictureEffect2010 }, + 0x1174 => { %quality2010 }, + 0x1178 => { %meteringMode2010 }, + 0x1179 => { %exposureProgram2010 }, + 0x1180 => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + 0x1218 => { + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + #0x1a08 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x1a0c => { Name => 'SonyImageHeight', Format => 'int16u' }, + 0x1a23 => { # only for NEX-7 with Firmware v1.02 and higher, but slightly different from Tag9405 ... + Name => 'DistortionCorrParams', + Format => 'int16s[16]', + }, +); + +%Image::ExifTool::Sony::Tag2010c = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => 'Valid for SLT-A37/A57 and NEX-F3.', + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + IS_SUBDIR => [ 0x0490 ], + 0x0000 => { %sequenceImageNumber }, #PH + 0x0004 => { %sequenceFileNumber }, #PH + 0x0008 => { %releaseMode2, Format => 'int32u' }, + #0x0048 => { Name => 'SonyImageWidth3', Format => 'int16u' }, + #0x004c => { Name => 'SonyImageHeight3', Format => 'int16u' }, + #0x0058 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x005c => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x0068 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x006c => { Name => 'SonyImageHeight', Format => 'int16u' }, + #0x00c0 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x00c4 => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x00d0 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x00d4 => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x00e0 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x00e4 => { Name => 'SonyImageHeight', Format => 'int16u' }, + #0x0134 => { Name => 'SonyImageHeight', Format => 'int16u' }, + #0x0144 => { Name => 'SonyImageHeight', Format => 'int16u' }, + #0x0154 => { Name => 'SonyImageHeight', Format => 'int16u' }, + 0x0200 => { Name => 'DigitalZoomRatio', ValueConv => '$val/16', ValueConvInv => '$val*16', Priority => 0 }, + 0x0210 => { %sonyDateTime2010, Groups => { 2 => 'Time' } }, + 0x0300 => { %dynamicRangeOptimizer2010 }, + 0x0490 => { + Name => 'MeterInfo', + Format => 'int32u[486]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MeterInfo' }, + }, + 0x1104 => { %releaseMode2010 }, + 0x1108 => { %releaseMode2 }, + 0x1110 => { %selfTimer2010 }, + 0x1114 => { %flashMode2010 }, + 0x111a => { %gain2010 }, + 0x111c => { %brightnessValue2010 }, + 0x1120 => { %dynamicRangeOptimizer2010 }, + 0x1124 => { %hdr2010 }, + 0x1128 => { %exposureComp2010 }, + 0x113e => { %pictureProfile2010 }, + 0x113f => { %pictureProfile2010 }, + 0x1143 => { %pictureEffect2010 }, + 0x1150 => { %quality2010 }, + 0x1154 => { %meteringMode2010 }, + 0x1155 => { %exposureProgram2010 }, + 0x115c => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + 0x11f4 => { + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + #0x1a08 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x1a0c => { Name => 'SonyImageHeight', Format => 'int16u' }, +); + +%Image::ExifTool::Sony::Tag2010d = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => q{ + Valid for DSC-HX10V/HX20V/HX200V/TX66/TX200V/TX300V/WX50/WX100/WX150, but + not valid for panorama images. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + IS_SUBDIR => [ 0x050c ], + 0x0000 => { %sequenceImageNumber }, #PH + 0x0004 => { %sequenceFileNumber }, #PH + 0x0008 => { %releaseMode2, Format => 'int32u' }, + #0x0048 => { Name => 'SonyImageWidth3', Format => 'int16u' }, + #0x004c => { Name => 'SonyImageHeight3', Format => 'int16u' }, + #0x0058 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x005c => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x0068 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x006c => { Name => 'SonyImageHeight', Format => 'int16u' }, + #0x00c0 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x00c4 => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x00d0 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x00d4 => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x00e0 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x00e4 => { Name => 'SonyImageHeight', Format => 'int16u' }, + 0x01fe => { %sonyDateTime2010, Groups => { 2 => 'Time' } }, + 0x037c => { %dynamicRangeOptimizer2010 }, + 0x050c => { + Name => 'MeterInfo', + Format => 'int32u[486]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MeterInfo' }, + }, + 0x1180 => { %releaseMode2010 }, + 0x1184 => { %releaseMode2 }, + 0x118c => { %selfTimer2010 }, + 0x1190 => { %flashMode2010 }, + 0x1196 => { %gain2010 }, + 0x1198 => { %brightnessValue2010 }, + 0x119c => { %dynamicRangeOptimizer2010 }, + 0x11a0 => { %hdr2010 }, + 0x11ba => { %pictureProfile2010 }, + 0x11bb => { %pictureProfile2010 }, + 0x11bf => { %pictureEffect2010 }, + 0x11d0 => { %meteringMode2010 }, + # 0x11d1 - not valid for HX20V panorama images - PH + 0x11d1 => { %exposureProgram2010 }, + 0x11d8 => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + 0x1270 => { + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, +); + +%Image::ExifTool::Sony::Tag2010e = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => q{ + Valid for SLT-A58/A99, ILCE-3000/3500, NEX-3N/5R/5T/6/VG30E/VG900, + DSC-RX100, DSC-RX1/RX1R. Also valid for DSC-HX300/HX50V/TX30/WX60/WX200/ + WX300, but not for panorama images. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x1892 ], + IS_SUBDIR => [ 0x04b8 ], + 0x0000 => { %sequenceImageNumber }, #PH + 0x0004 => { %sequenceFileNumber }, #PH + 0x0008 => { %releaseMode2, Format => 'int32u' }, + #0x0048 => { Name => 'SonyImageWidth3', Format => 'int16u' }, + #0x004c => { Name => 'SonyImageHeight3', Format => 'int16u' }, + #0x0058 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x005c => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x0068 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x006c => { Name => 'SonyImageHeight', Format => 'int16u' }, + #0x00c0 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x00c4 => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x00d0 => { Name => 'SonyImageWidth2', Format => 'int16u' }, + #0x00d4 => { Name => 'SonyImageHeight2', Format => 'int16u' }, + #0x00e0 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x00e4 => { Name => 'SonyImageHeight', Format => 'int16u' }, + #0x01fa => { Name => 'SonyImageHeight', Format => 'int16u' }, + #0x0200 => { Name => 'SonyImageWidth', Format => 'int16u' }, + 0x021c => { Name => 'DigitalZoomRatio', ValueConv => '$val/16', ValueConvInv => '$val*16', Priority => 0 }, + 0x022c => { %sonyDateTime2010, Groups => { 2 => 'Time' } }, + 0x0328 => { %dynamicRangeOptimizer2010 }, + 0x04b8 => { + Name => 'MeterInfo', + Format => 'int32u[486]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MeterInfo' }, + }, + 0x115c => { %releaseMode2010 }, + 0x1160 => { %releaseMode2 }, + 0x1168 => { %selfTimer2010 }, + 0x116c => { %flashMode2010 }, + 0x1172 => { %gain2010 }, + 0x1174 => { %brightnessValue2010 }, + 0x1178 => { %dynamicRangeOptimizer2010 }, + 0x117c => { %hdr2010 }, + 0x1180 => { %exposureComp2010 }, + 0x1196 => { %pictureProfile2010 }, + 0x1197 => { %pictureProfile2010 }, + 0x119b => { %pictureEffect2010 }, + 0x11a8 => { %quality2010 }, + 0x11ac => { %meteringMode2010 }, + 0x11ad => { %exposureProgram2010 }, + 0x11b4 => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + 0x1254 => { + Condition => '$$self{Model} =~ /^(SLT-(A99|A99V)|NEX-(5R|5T|6|VG900|VG30E)|DSC-RX100|Stellar|HV)\b/', + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x1258 => { + Condition => '$$self{Model} =~ /^(DSC-(RX1|RX1R))\b/', + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x1278 => { + Condition => '$$self{Model} =~ /^(SLT-A58|ILCE-(3000|3500)|NEX-3N|DSC-(HX300|HX50V|WX60|WX80|WX200|WX300|TX30))\b/', + Name => 'FocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x127a => { + Condition => '$$self{Model} =~ /^(SLT-A58|ILCE-(3000|3500)|NEX-3N|DSC-(HX300|HX50V|WX60|WX80|WX200|WX300|TX30))\b/', + Name => 'MinFocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x127c => { # may give 0 for fixed focal length lenses + Condition => '$$self{Model} =~ /^(SLT-A58|ILCE-(3000|3500)|NEX-3N|DSC-(HX300|HX50V|WX60|WX80|WX200|WX300|TX30))\b/', + Name => 'MaxFocalLength', + Format => 'int16u', + RawConv => '$val || undef', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x1280 => { + Condition => '$$self{Model} =~ /^(SLT-A58|ILCE-(3000|3500)|NEX-3N|DSC-(HX300|HX50V|WX60|WX80|WX200|WX300|TX30))\b/', + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x1870 => { + Name => 'DistortionCorrParams', + Condition => '$$self{Model} !~ /^(DSC-|Stellar)/', + Format => 'int16s[16]', + }, + # 0x1890 - same as 0x1892, but has value 3 for SAL18135, SAL50F14Z, SAL55300, SAL70200G2, SAL70300G2, SAL70400G2 + # Presumably, these are the A-mount lenses "compatible with software updates" as referred to in the ILCA-99M2 manual. + # Indeed, SAL70400G2 on ILCA-77M2 gives Version: "Lens: Ver.01" + 0x1891 => { + Name => 'LensFormat', + Condition => '$$self{Model} !~ /^(DSC-|Stellar)/', + PrintConv => { + 0 => 'Unknown', + 1 => 'APS-C', + 2 => 'Full-frame', + }, + }, + 0x1892 => { + Name => 'LensMount', + DataMember => 'LensMount', + RawConv => '$$self{LensMount} = $val; $$self{Model} =~ /^(DSC-|Stellar)/ ? undef : $val', + PrintConv => { + 0 => 'Unknown', + 1 => 'A-mount', + 2 => 'E-mount', + }, + }, + 0x1893 => { #JR + Name => 'LensType2', + Condition => '$$self{LensMount} == 2', + Format => 'int16u', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x1896 => { + Name => 'LensType', + Condition => '$$self{LensMount} == 1', + Priority => 0, #PH (just to be safe) + Format => 'int16u', #PH + SeparateTable => 1, + ValueConvInv => '($val & 0xff00) == 0x8000 ? 0 : int($val)', + PrintConv => \%sonyLensTypes, + PrintInt => 1, + }, + 0x1898 => { + Name => 'DistortionCorrParamsPresent', + Condition => '$$self{Model} !~ /^(DSC-|Stellar)/', + PrintConv => { 0 => 'No', 1 => 'Yes'}, + }, + 0x1899 => { + Name => 'DistortionCorrParamsNumber', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { 11 => '11 (APS-C)', 16 => '16 (Full-frame)'}, + }, + #0x1914 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x1918 => { Name => 'SonyImageHeight', Format => 'int16u' }, + 0x192c => { + Name => 'AspectRatio', + Condition => '$$self{Model} !~ /^(DSC-RX100|Stellar)\b/', + PrintConv => { + 0 => '16:9', + 1 => '4:3', + 2 => '3:2', + 3 => '1:1', + 5 => 'Panorama', + }, + }, + #0x192e => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x1930 => { Name => 'SonyImageHeight', Format => 'int16u' }, + 0x1a88 => { + Name => 'AspectRatio', + Condition => '$$self{Model} =~ /^(DSC-RX100|Stellar)\b/', + PrintConv => { + 0 => '16:9', + 1 => '4:3', + 2 => '3:2', + 3 => '1:1', + 5 => 'Panorama', + }, + }, +); + +%Image::ExifTool::Sony::Tag2010f = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => 'Valid for DSC-RX100M2, DSC-QX10/QX100.', + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + IS_SUBDIR => [ 0x01e0 ], + 0x0004 => { %releaseMode2, Format => 'int32u' }, # NOT at offset 0x08 ! + #0x002e => { Name => 'SonyImageWidth3', Format => 'int16u' }, + #0x0042 => { Name => 'SonyImageWidth3', Format => 'int16u' }, + 0x0050 => { %dynamicRangeOptimizer2010 }, + 0x01e0 => { + Name => 'MeterInfo', + Format => 'int32u[486]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MeterInfo' }, + }, + 0x1014 => { %releaseMode2010 }, + 0x1018 => { %releaseMode2 }, + 0x1020 => { %selfTimer2010 }, + 0x1024 => { %flashMode2010 }, + 0x102a => { %gain2010 }, + 0x102c => { %brightnessValue2010 }, + 0x1030 => { %dynamicRangeOptimizer2010 }, + 0x1034 => { %hdr2010 }, + 0x1038 => { %exposureComp2010 }, + 0x104e => { %pictureProfile2010 }, + 0x104f => { %pictureProfile2010 }, + 0x1053 => { %pictureEffect2010 }, + 0x1060 => { %quality2010 }, + 0x1064 => { %meteringMode2010 }, + 0x1065 => { %exposureProgram2010 }, + 0x106c => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + #0x1096 => { Name => 'SonyImageWidth3', Format => 'int16u' }, + #0x10aa => { Name => 'SonyImageWidth3', Format => 'int16u' }, + 0x1134 => { + Name => 'FocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x1136 => { + Name => 'MinFocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x1138 => { + Name => 'MaxFocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x113c => { + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + #0x1914 => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x1918 => { Name => 'SonyImageHeight', Format => 'int16u' }, + 0x192c => { + Name => 'AspectRatio', + PrintConv => { + 0 => '16:9', + 1 => '4:3', + 2 => '3:2', + 3 => '1:1', + 5 => 'Panorama', + }, + }, + #0x192e => { Name => 'SonyImageWidth', Format => 'int16u' }, + #0x1930 => { Name => 'SonyImageHeight', Format => 'int16u' }, +); + +%Image::ExifTool::Sony::Tag2010g = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => q{ + Valid for DSC-HX60V/HX350/HX400V/QX30/RX10/RX100M3/WX220/WX350, + ILCE-7/7R/7S/7M2/5000/5100/6000/QX1, ILCA-68/77M2. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x18be ], + IS_SUBDIR => [ 0x0388 ], + 0x0004 => { %releaseMode2, Format => 'int32u' }, # NOT at offset 0x08 ! + 0x0050 => { %dynamicRangeOptimizer2010 }, + 0x020c => { %releaseMode2010 }, + 0x0210 => { %releaseMode2 }, + 0x0218 => { %selfTimer2010 }, + 0x021c => { %flashMode2010 }, + 0x0222 => { %gain2010 }, + 0x0224 => { %brightnessValue2010 }, + 0x0228 => { %dynamicRangeOptimizer2010 }, + 0x022c => { %hdr2010 }, + 0x0230 => { %exposureComp2010 }, + 0x0246 => { %pictureProfile2010 }, + 0x0247 => { %pictureProfile2010 }, + 0x024b => { %pictureEffect2010 }, + 0x0258 => { %quality2010 }, + 0x025c => { %meteringMode2010 }, + 0x025d => { %exposureProgram2010 }, + 0x0264 => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + 0x032c => { + Name => 'FocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x032e => { + Name => 'MinFocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x0330 => { # may give 0 for fixed focal length lenses + Name => 'MaxFocalLength', + Format => 'int16u', + RawConv => '$val || undef', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x0344 => { + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x0388 => { + Name => 'MeterInfo', + Format => 'int32u[486]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MeterInfo' }, + }, + 0x189c => { + Name => 'DistortionCorrParams', + Condition => '$$self{Model} !~ /^DSC-/', + Format => 'int16s[16]', + }, + # 0x18bc - same as 0x18be, but has value 3 for SAL18135, SAL50F14Z, SAL55300, SAL70200G2, SAL70300G2, SAL70400G2 + 0x18bd => { + Name => 'LensFormat', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { + 0 => 'Unknown', + 1 => 'APS-C', + 2 => 'Full-frame', + }, + }, + 0x18be => { + Name => 'LensMount', + DataMember => 'LensMount', + RawConv => '$$self{LensMount} = $val; $$self{Model} =~ /^DSC-/ ? undef : $val', + PrintConv => { + 0 => 'Unknown', + 1 => 'A-mount', + 2 => 'E-mount', + }, + }, + 0x18bf => { #JR + Name => 'LensType2', + Condition => '$$self{LensMount} == 2', + Format => 'int16u', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x18c2 => { + Name => 'LensType', + Condition => '$$self{LensMount} == 1', + Priority => 0, #PH (just to be safe) + Format => 'int16u', #PH + SeparateTable => 1, + ValueConvInv => '($val & 0xff00) == 0x8000 ? 0 : int($val)', + PrintConv => \%sonyLensTypes, + PrintInt => 1, + }, + 0x18c4 => { + Name => 'DistortionCorrParamsPresent', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { 0 => 'No', 1 => 'Yes'}, + }, + 0x18c5 => { + Name => 'DistortionCorrParamsNumber', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { 11 => '11 (APS-C)', 16 => '16 (Full-frame)'}, + }, + # 0x1940 => { Name => 'SonyImageWidth', Format => 'int16u' }, + # 0x1944 => { Name => 'SonyImageHeight', Format => 'int16u' }, + 0x1958 => { + Name => 'AspectRatio', + PrintConv => { + 0 => '16:9', + 1 => '4:3', + 2 => '3:2', + 3 => '1:1', + 5 => 'Panorama', + }, + }, + # 0x195a => { Name => 'SonyImageWidth', Format => 'int16u' }, + # 0x195c => { Name => 'SonyImageHeight', Format => 'int16u' }, +); + +%Image::ExifTool::Sony::Tag2010h = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => q{ + Valid for DSC-HX80/HX90V/RX0/RX1RM2/RX10M2/RX10M3/RX100M4/RX100M5/WX500, + ILCE-6300/6500/7RM2/7SM2, ILCA-99M2. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x18ee ], + IS_SUBDIR => [ 0x0388, 0x0398 ], + 0x0004 => { %releaseMode2, Format => 'int32u' }, + 0x0050 => { %dynamicRangeOptimizer2010 }, + 0x020c => { %releaseMode2010 }, + 0x0210 => { %releaseMode2 }, + 0x0218 => { %selfTimerB2010 }, + 0x021c => { %flashMode2010 }, + 0x0222 => { %gain2010 }, + 0x0224 => { %brightnessValue2010 }, + 0x0228 => { %dynamicRangeOptimizer2010 }, + 0x022c => { %hdr2010 }, + 0x0230 => { %exposureComp2010 }, + 0x0246 => { %pictureProfile2010 }, + 0x0247 => { %pictureProfile2010 }, + 0x024b => { %pictureEffect2010 }, + 0x0258 => { %quality2010 }, + 0x025c => { %meteringMode2010 }, + 0x025d => { %exposureProgram2010 }, + 0x0264 => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + 0x032c => { + Name => 'FocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x032e => { + Name => 'MinFocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x0330 => { # may give 0 for fixed focal length lenses + Name => 'MaxFocalLength', + Format => 'int16u', + RawConv => '$val || undef', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x0346 => { + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x0388 => { + Name => 'MeterInfo', + Format => 'int32u[486]', + Condition => '$$self{Model} !~ /^(ILCA-99M2|ILCE-6500|DSC-(RX0|RX100M5))/', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MeterInfo' }, + }, + 0x0398 => { + Name => 'MeterInfo', + Format => 'int32u[486]', + Condition => '$$self{Model} =~ /^(ILCA-99M2|ILCE-6500|DSC-(RX0|RX100M5))/', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MeterInfo' }, + }, + 0x18cc => { + Name => 'DistortionCorrParams', + Condition => '$$self{Model} !~ /^DSC-/', + Format => 'int16s[16]', + }, + # 0x18ec - same as 0x18ee, but has value 3 for SAL18135, SAL50F14Z, SAL55300, SAL70200G2, SAL70300G2, SAL70400G2 + 0x18ed => { + Name => 'LensFormat', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { + 0 => 'Unknown', + 1 => 'APS-C', + 2 => 'Full-frame', + }, + }, + 0x18ee => { + Name => 'LensMount', + DataMember => 'LensMount', + RawConv => '$$self{LensMount} = $val; $$self{Model} =~ /^DSC-/ ? undef : $val', + PrintConv => { + 0 => 'Unknown', + 1 => 'A-mount', + 2 => 'E-mount', + }, + }, + 0x18ef => { #JR + Name => 'LensType2', + Condition => '$$self{LensMount} == 2', + Format => 'int16u', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x18f2 => { + Name => 'LensType', + Condition => '$$self{LensMount} == 1', + Priority => 0, #PH (just to be safe) + Format => 'int16u', #PH + SeparateTable => 1, + ValueConvInv => '($val & 0xff00) == 0x8000 ? 0 : int($val)', + PrintConv => \%sonyLensTypes, + PrintInt => 1, + }, + 0x18f4 => { + Name => 'DistortionCorrParamsPresent', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { 0 => 'No', 1 => 'Yes'}, + }, + 0x18f5 => { + Name => 'DistortionCorrParamsNumber', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { 11 => '11 (APS-C)', 16 => '16 (Full-frame)'}, + }, + 0x192c => { + Name => 'AspectRatio', + PrintConv => { + 0 => '16:9', + 1 => '4:3', + 2 => '3:2', + 3 => '1:1', + 5 => 'Panorama', + }, + }, + # 0x1970 => { Name => 'SonyImageWidth', Format => 'int16u' }, + # 0x1974 => { Name => 'SonyImageHeight', Format => 'int16u' }, + # 0x198a => { Name => 'SonyImageWidth', Format => 'int16u' }, + # 0x198c => { Name => 'SonyImageHeight', Format => 'int16u' }, +); + +%Image::ExifTool::Sony::Tag2010i = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => q{ + Valid for ILCE-6100/6400/6600/7C/7M3/7RM3/7RM4/9/9M2, DSC-RX0M2/RX10M4/RX100M6/ + RX100M5A/RX100M7/HX99. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x17f2 ], + IS_SUBDIR => [ 0x036d ], + 0x0004 => { %releaseMode2, Format => 'int32u' }, + 0x004e => { %dynamicRangeOptimizer2010 }, + 0x0204 => { %releaseMode2010 }, + 0x0208 => { %releaseMode2 }, + 0x0210 => { %selfTimerB2010 }, + 0x0211 => { %flashMode2010 }, + 0x0217 => { %gain2010 }, + 0x0219 => { %brightnessValue2010 }, + 0x021b => { %dynamicRangeOptimizer2010 }, + 0x021f => { %hdr2010 }, + 0x0223 => { %exposureComp2010 }, + 0x0237 => { %pictureProfile2010 }, + 0x0238 => { %pictureProfile2010 }, + 0x023c => { %pictureEffect2010 }, + 0x0247 => { %quality2010 }, + 0x024b => { %meteringMode2010 }, + 0x024c => { %exposureProgram2010 }, + 0x0252 => { Name => 'WB_RGBLevels', Format => 'int16u[3]' }, + 0x030a => { + Name => 'FocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x030c => { + Name => 'MinFocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x030e => { # may give 0 for fixed focal length lenses + Name => 'MaxFocalLength', + Format => 'int16u', + RawConv => '$val || undef', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x0320 => { + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x036d => { # different format from all previous cameras: now each triple is int16u-int32u-int32u + Name => 'MeterInfo', + Format => 'undef[1620]', + Unknown => 1, + SubDirectory => { TagTable => 'Image::ExifTool::Sony::MeterInfo9' }, + }, + 0x17d0 => { + Name => 'DistortionCorrParams', + Condition => '$$self{Model} !~ /^DSC-/', + Format => 'int16s[16]', + }, + # 0x17f0 - same as 0x17f2, but has value 3 for SAL18135, SAL50F14Z, SAL55300, SAL70200G2, SAL70300G2, SAL70400G2 + 0x17f1 => { + Name => 'LensFormat', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { + 0 => 'Unknown', + 1 => 'APS-C', + 2 => 'Full-frame', + }, + }, + 0x17f2 => { + Name => 'LensMount', + DataMember => 'LensMount', + RawConv => '$$self{LensMount} = $val; $$self{Model} =~ /^DSC-/ ? undef : $val', + PrintConv => { + 0 => 'Unknown', + 1 => 'A-mount', + 2 => 'E-mount', + }, + }, + 0x17f3 => { #JR + Name => 'LensType2', + Condition => '$$self{LensMount} == 2', + Format => 'int16u', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x17f6 => { + Name => 'LensType', + Condition => '$$self{LensMount} == 1', + Priority => 0, #PH (just to be safe) + Format => 'int16u', #PH + SeparateTable => 1, + ValueConvInv => '($val & 0xff00) == 0x8000 ? 0 : int($val)', + PrintConv => \%sonyLensTypes, + PrintInt => 1, + }, + 0x17f8 => { + Name => 'DistortionCorrParamsPresent', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { 0 => 'No', 1 => 'Yes'}, + }, + 0x17f9 => { + Name => 'DistortionCorrParamsNumber', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { 11 => '11 (APS-C)', 16 => '16 (Full-frame)'}, + }, + 0x188c => { + Name => 'AspectRatio', + PrintConv => { + 0 => '16:9', + 1 => '4:3', + 2 => '3:2', + 3 => '1:1', + 5 => 'Panorama', + }, + }, +); + +%Image::ExifTool::Sony::Tag202a = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + FORMAT => 'int8u', + DATAMEMBER => [ 0x01 ], +# +# first seen for ILCE-6300 +# Possibly the Locations of the FocalPlaneAFPointsUsed are indicated here. +# 66 bytes, structure appears to be as follows: +# 0x00 - 1 for ILCE-6300/6500, ILCA-99M2 +# 0x01 - int8u: 0 to 15 - nr of locations to follow +# 0x02 and 0x03 - (128 2) or 640 - (max.) width (X) of (LCD screen) area of following locations +# 0x04 and 0x05 - (172 1) or 428 - (max.) height (Y) of (LCD screen) area of following locations +# origin of X-Y coordinates appears to be top-left, i.e. X to the right, Y pointing down. +# 0x06 0x07 0x08 0x09 - 2x int16u : X and Y coordinates of Location 1 +# etc. +# + 0x01 => { + Name => 'FocalPlaneAFPointsUsed', + DataMember => 'Locations', + Format => 'int8u', + RawConv => '$$self{Locations} = $val', + }, + 0x02 => { + Name => 'FocalPlaneAFPointArea', + Condition => '$$self{Locations} >= 1', # only output this if at least 1 Location follows ? + Format => 'int16u[2]', + }, + 0x06 => { Name => 'FocalPlaneAFPointLocation1', Condition => '$$self{Locations} >= 1', Format => 'int16u[2]' }, + 0x0a => { Name => 'FocalPlaneAFPointLocation2', Condition => '$$self{Locations} >= 2', Format => 'int16u[2]' }, + 0x0e => { Name => 'FocalPlaneAFPointLocation3', Condition => '$$self{Locations} >= 3', Format => 'int16u[2]' }, + 0x12 => { Name => 'FocalPlaneAFPointLocation4', Condition => '$$self{Locations} >= 4', Format => 'int16u[2]' }, + 0x16 => { Name => 'FocalPlaneAFPointLocation5', Condition => '$$self{Locations} >= 5', Format => 'int16u[2]' }, + 0x1a => { Name => 'FocalPlaneAFPointLocation6', Condition => '$$self{Locations} >= 6', Format => 'int16u[2]' }, + 0x1e => { Name => 'FocalPlaneAFPointLocation7', Condition => '$$self{Locations} >= 7', Format => 'int16u[2]' }, + 0x22 => { Name => 'FocalPlaneAFPointLocation8', Condition => '$$self{Locations} >= 8', Format => 'int16u[2]' }, + 0x26 => { Name => 'FocalPlaneAFPointLocation9', Condition => '$$self{Locations} >= 9', Format => 'int16u[2]' }, + 0x2a => { Name => 'FocalPlaneAFPointLocation10', Condition => '$$self{Locations} >= 10', Format => 'int16u[2]' }, + 0x2e => { Name => 'FocalPlaneAFPointLocation11', Condition => '$$self{Locations} >= 11', Format => 'int16u[2]' }, + 0x32 => { Name => 'FocalPlaneAFPointLocation12', Condition => '$$self{Locations} >= 12', Format => 'int16u[2]' }, + 0x36 => { Name => 'FocalPlaneAFPointLocation13', Condition => '$$self{Locations} >= 13', Format => 'int16u[2]' }, + 0x3a => { Name => 'FocalPlaneAFPointLocation14', Condition => '$$self{Locations} >= 14', Format => 'int16u[2]' }, + 0x3e => { Name => 'FocalPlaneAFPointLocation15', Condition => '$$self{Locations} >= 15', Format => 'int16u[2]' }, +); + +# possible metering information (ref JR) +%Image::ExifTool::Sony::MeterInfo = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => q{ + Information possibly related to metering. Extracted only if the Unknown + option is used. + }, +# +# 162 'triplets' of 3 int32u numbers: (k,n1,n2) +# These appear to relate to two sets of brightness distribution over the image area: +# Set 1: 7 rows of 9 +# Set 2: 9 rows of 11 +# +# Exact meaning presently unknown, however: +# n1 ranges from 0 (black) - appr. 1300 (bright white) +# n2 ranges from 0 (black) - appr. 142000 (bright white), i.e. roughly factor 100 higher +# Many panorama images: all 0 +# Flash images: n2 = n1 +# k maybe some kind of 'gain' or multiplication factor ? +# k distribution over the image as function of Aspect-Ratio is as follows: +# +# 3:2 image 16:9 image 4:3 image +# set 1: +# +# 12 12 12 12 12 12 12 12 12 12 12 12 15 18 15 12 12 12 9 12 12 12 12 12 12 12 9 +# 12 12 12 12 12 12 12 12 12 12 12 12 15 18 15 12 12 12 9 12 12 12 12 12 12 12 9 +# 16 16 16 16 16 16 16 16 16 12 12 12 15 18 15 12 12 12 12 16 16 16 16 16 16 16 12 +# 16 16 16 16 16 16 16 16 16 12 12 12 15 18 15 12 12 12 12 16 16 16 16 16 16 16 12 +# 16 16 16 16 16 16 16 16 16 12 12 12 15 18 15 12 12 12 12 16 16 16 16 16 16 16 12 +# 12 12 12 12 12 12 12 12 12 12 12 12 15 18 15 12 12 12 9 12 12 12 12 12 12 12 9 +# 12 12 12 12 12 12 12 12 12 12 12 12 15 18 15 12 12 12 9 12 12 12 12 12 12 12 9 +# +# set 2: +# +# 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 15 18 15 12 12 12 12 9 9 12 12 12 12 12 12 12 9 9 +# 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 15 18 15 12 12 12 12 9 9 12 12 12 12 12 12 12 9 9 +# 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 15 18 15 12 12 12 12 9 9 12 12 12 12 12 12 12 9 9 +# 16 16 16 16 16 16 16 16 16 16 16 12 12 12 12 15 18 15 12 12 12 12 12 12 16 16 16 16 16 16 16 12 12 +# 16 16 16 16 16 16 16 16 16 16 16 12 12 12 12 15 18 15 12 12 12 12 12 12 16 16 16 16 16 16 16 12 12 +# 16 16 16 16 16 16 16 16 16 16 16 12 12 12 12 15 18 15 12 12 12 12 12 12 16 16 16 16 16 16 16 12 12 +# 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 15 18 15 12 12 12 12 9 9 12 12 12 12 12 12 12 9 9 +# 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 15 18 15 12 12 12 12 9 9 12 12 12 12 12 12 12 9 9 +# 12 12 12 12 12 12 12 12 12 12 12 8 8 8 8 10 12 10 8 8 8 8 9 9 12 12 12 12 12 12 12 9 9 +# +# Usually, in the center, the numbers of set 1 row 2-6 match with set 2 row 3-7, except for first and last 2 columns. +# + 0x0000 => { Name => 'MeterInfo1Row1', %meterInfo1 }, + 0x006c => { Name => 'MeterInfo1Row2', %meterInfo1 }, + 0x00d8 => { Name => 'MeterInfo1Row3', %meterInfo1 }, + 0x0144 => { Name => 'MeterInfo1Row4', %meterInfo1 }, + 0x01b0 => { Name => 'MeterInfo1Row5', %meterInfo1 }, + 0x021c => { Name => 'MeterInfo1Row6', %meterInfo1 }, + 0x0288 => { Name => 'MeterInfo1Row7', %meterInfo1 }, + + 0x02f4 => { Name => 'MeterInfo2Row1', %meterInfo2 }, + 0x0378 => { Name => 'MeterInfo2Row2', %meterInfo2 }, + 0x03fc => { Name => 'MeterInfo2Row3', %meterInfo2 }, + 0x0480 => { Name => 'MeterInfo2Row4', %meterInfo2 }, + 0x0504 => { Name => 'MeterInfo2Row5', %meterInfo2 }, + 0x0588 => { Name => 'MeterInfo2Row6', %meterInfo2 }, + 0x060c => { Name => 'MeterInfo2Row7', %meterInfo2 }, + 0x0690 => { Name => 'MeterInfo2Row8', %meterInfo2 }, + 0x0714 => { Name => 'MeterInfo2Row9', %meterInfo2 }, +); + +# possible metering information (ref JR) +%Image::ExifTool::Sony::MeterInfo9 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => q{ + Information possibly related to metering. Extracted only if the Unknown + option is used. + }, +# new format for ILCE-9: +# 162 'triplets' of (int16u int32u int32u) numbers: (k,n1,n2) +# Set 1: 7 rows of 9 +# Set 2: 9 rows of 11 + 0x0000 => { Name => 'MeterInfo1Row1', %meterInfo1b }, + 0x005a => { Name => 'MeterInfo1Row2', %meterInfo1b }, + 0x00b4 => { Name => 'MeterInfo1Row3', %meterInfo1b }, + 0x010e => { Name => 'MeterInfo1Row4', %meterInfo1b }, + 0x0168 => { Name => 'MeterInfo1Row5', %meterInfo1b }, + 0x01c2 => { Name => 'MeterInfo1Row6', %meterInfo1b }, + 0x021c => { Name => 'MeterInfo1Row7', %meterInfo1b }, + + 0x0276 => { Name => 'MeterInfo2Row1', %meterInfo2b }, + 0x02e4 => { Name => 'MeterInfo2Row2', %meterInfo2b }, + 0x0352 => { Name => 'MeterInfo2Row3', %meterInfo2b }, + 0x03c0 => { Name => 'MeterInfo2Row4', %meterInfo2b }, + 0x042e => { Name => 'MeterInfo2Row5', %meterInfo2b }, + 0x049c => { Name => 'MeterInfo2Row6', %meterInfo2b }, + 0x050a => { Name => 'MeterInfo2Row7', %meterInfo2b }, + 0x0578 => { Name => 'MeterInfo2Row8', %meterInfo2b }, + 0x05e6 => { Name => 'MeterInfo2Row9', %meterInfo2b }, +); + +%Image::ExifTool::Sony::Tag900b = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + # 0x0000 - always 174 (e) + 0x0002 => { + Name => 'FacesDetected', + PrintConv => { + 0 => '0', + 98 => '1', + 57 => '2', + 93 => '3', + 77 => '4', + 33 => '5', + 168 => '6', + 241 => '7', + 115 => '8', + }, + }, + # 0x00bc - always 98 (221 (e)) + 0x00bd => { + Condition => '$$self{Model} !~ /^DSLR-(A450|A500|A550)$/', # always 98 for A450/A500/A550: exclude + Name => 'FaceDetection', + PrintConv => { + 0 => 'Off', + 98 => 'On', + }, + }, +); + +%Image::ExifTool::Sony::Tag9050a = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => q{ + Valid for SLT, ILCA, NEX and ILCE models, except ILCE-6300/6500/7RM2/7SM2, + ILCA-99M2. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x0031, 0x0105 ], + NOTES => q{ + Data for tags 0x9050, 0x94xx and 0x2010 is encrypted by a simple + substitution cipher, but the deciphered values are listed below. + }, + 0x0000 => { + Condition => '$$self{Model} !~ /^(NEX-|Lunar|ILCE-)/', + Name => 'SonyMaxAperture', # (at current focal length) + # seen values from 17 - 48 + ValueConv => '2 ** (($val/8 - 1.06) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x0001 => { + Condition => '$$self{Model} !~ /^(NEX-|Lunar|ILCE-)/', + Name => 'SonyMinAperture', # (at current focal length) + # seen values from 80 - 95 + ValueConv => '2 ** (($val/8 - 1.06) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, +# 0x0002 and 0x0004 (int16u) for SLT: +# appears to be difference between used FNumber and MaxAperture, 256 being +1 APEX or stop +# however, not always valid e.g. bracketing, Shutter-prio e.a. +# difference between 0x0002 and 0x0004 mostly 0.0, 0.1 or 0.2 stops. + 0x0020 => { + Name => 'Shutter', + Format => 'int16u[3]', + PrintConv => { + '0 0 0' => 'Silent / Electronic (0 0 0)', + OTHER => sub { + my ($val, $inv) = @_; + return $inv ? ($val=~/\((.*?)\)/ ? $1 : undef) : "Mechanical ($val)"; + }, + }, + }, + 0x0031 => { #JR + Name => 'FlashStatus', + RawConv => '$$self{FlashFired} = $val', + PrintConv => { + 0 => 'No Flash present', + 2 => 'Flash Inhibited', # seen for ILCE-7/7R continuous, panorama, HDR mode + 64 => 'Built-in Flash present', + 65 => 'Built-in Flash Fired', + 66 => 'Built-in Flash Inhibited', # seen for panorama, HDR, burst mode + 128 => 'External Flash present', # seen for NEX-5N/5T + 129 => 'External Flash Fired', # seen for SLT-A99V, ILCE-7R, NEX-5N/5R + }, + }, + 0x0032 => { #13 + Name => 'ShutterCount', + # this seems to be valid for the A7R,A37,A57,A65,A77,A99,A99V and possibly the + # NEX-5N/7. For the A99V it is definitely more than 16 bits, but it wraps at + # 65536 for the A7R. + Format => 'int32u', + Notes => q{ + total number of image exposures made by the camera, modulo 65536 for some + models + }, + RawConv => '$val & 0x00ffffff', + }, + 0x003a => { # appr. same value as Exif ExposureTime, but longer in HDR-modes + Name => 'SonyExposureTime', + Format => 'int16u', + ValueConv => '$val ? 2 ** (16 - $val/256) : 0', + ValueConvInv => '$val ? int((16 - log($val) / log(2)) * 256 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x003c => { + Name => 'SonyFNumber', + Format => 'int16u', + ValueConv => '2 ** (($val/256 - 16) / 2)', + ValueConvInv => '(log($val)*2/log(2)+16)*256', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x003f => { + Name => 'ReleaseMode2', + %releaseMode2, + }, +# 0x004c - 0x005b: A-Mount: 16 values, only non-zero data for lenses giving "A-mount (3)" in 0x0104 below. +# e.g for SAL70400G2: 9 5 5 64 69 74 79 84 89 94 98 104 255 105 89 80 +# where 9 means 9 focal lengths: 64 ... 104, +# corresponding to 70-400mm via FocalLength = 4.375*2**($val/16) +# 0x004c, 0x0051: E-Mount: ShutterCount and dateTime + 0x004c => { # only ILCE-7/7R/7S/7M2/5000/5100/6000/QX1 - but appears not valid when flash is used ... + Name => 'ShutterCount2', + Condition => '($$self{Model} =~ /^(ILCE-(7(R|S|M2)?|[56]000|5100|QX1))\b/) and (($$self{FlashFired} & 0x01) != 1)', + Format => 'int32u', + RawConv => '$val & 0x00ffffff', + }, + 0x0051 => { # only ILCE-7/7R/7S/7M2/5000/5100/6000/QX1, but hours usually different from SonyDateTime - UTC? + # appears not valid (all '0') when flash is used, panorama, hdr modes ... + Name => 'SonyDateTime2', + Condition => '$$self{Model} =~ /^(ILCE-(7(R|S|M2)?|[56]000|5100|QX1))\b/', + Groups => { 2 => 'Time' }, + Shift => 'Time', + Format => 'undef[6]', + ValueConv => q{ + my @v = unpack('C*', $val); + return undef unless $v[0] > 0; + return sprintf("20%.2d:%.2d:%.2d %.2d:%.2d:%.2d", @v) + }, + ValueConvInv => q{ + my @v = ($val =~ /\d+/g); + return undef unless @v == 6 and ($v[0]-=2000) >= 0; + return pack('C*', @v); + }, + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,0)', + }, + 0x0067 => { + Name => 'ReleaseMode2', + Condition => '$$self{Model} !~ /^(SLT-A(65|77)V?|Lunar|NEX-(5N|7|VG20E))/', + %releaseMode2, + }, + 0x007c => { #JR valid for ILCE and most NEX + Name => 'InternalSerialNumber', #(NC) + Condition => '$$self{Model} !~ /^(Lunar|NEX-(5N|7|VG20E)|SLT-|HV|ILCA-)/', + Format => 'int8u[4]', + PrintConv => 'unpack "H*", pack "C*", split " ", $val', + }, + 0x00f0 => { #JR valid for SLT/ILCA models + Name => 'InternalSerialNumber', #(NC) + Condition => '$$self{Model} =~ /^(SLT-|HV|ILCA-)/', + Format => 'int8u[5]', + PrintConv => 'unpack "H*", pack "C*", split " ", $val', + PrintConvInv => 'join " ", unpack "C*", pack "H*", $val', + }, + # 0x0104 - same as 0x0105, but has value 3 for SAL18135, SAL50F14Z, SAL55300, SAL70200G2, SAL70300G2, SAL70400G2 + 0x0105 => { + Name => 'LensMount', + DataMember => 'LensMount', + RawConv => '$$self{LensMount} = $val', + PrintConv => { + 0 => 'Unknown', + 1 => 'A-mount', + 2 => 'E-mount', + }, + }, + 0x0106 => { + Name => 'LensFormat', + PrintConv => { + 0 => 'Unknown', + 1 => 'APS-C', + 2 => 'Full-frame', + }, + }, + 0x0107 => { + Name => 'LensType2', + Condition => '$$self{LensMount} == 2', + Format => 'int16u', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x0109 => { + Name => 'LensType', + Condition => '$$self{LensMount} == 1', + Priority => 0, #PH (just to be safe) + Format => 'int16u', #PH + Notes => 'SLT models, and NEX with A-mount lenses', + SeparateTable => 1, + # has a value of 0 for E-mount lenses (values 0x80xx) + ValueConvInv => '($val & 0xff00) == 0x8000 ? 0 : int($val)', + PrintConv => \%sonyLensTypes, + PrintInt => 1, + }, + 0x010b => { + Name => 'DistortionCorrParamsPresent', + PrintConv => { 0 => 'No', 1 => 'Yes'}, + }, + 0x0114 => { + Name => 'APS-CSizeCapture', + Condition => '$$self{Model} =~ /^(SLT-A99|HV|ILCE-7)/', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + # 0x0115 and 0x0116, or 0x0116 and 0x0117: + # give the same info as the first and last bytes of LensSpec, + # but also for older Sony and Minolta lenses where all LensSpec bytes are 0. + 0x0115 => { + Name => 'LensSpecFeatures', + Condition => '$$self{Model} =~ /^(SLT-A(37|57|65|77)V?|Lunar|NEX-(F3|5N|7|VG20E))/', + Format => 'undef[2]', + ValueConv => 'join " ", unpack "H2H2", $val', + ValueConvInv => sub { + my @a = split(" ", shift); + return @a == 2 ? pack 'CC', hex($a[0]), hex($a[1]) : undef; + }, + PrintConv => \&PrintLensSpec, + PrintConvInv => 'Image::ExifTool::Sony::PrintInvLensSpec($val, $self, 1)', + }, + 0x0116 => { + Name => 'LensSpecFeatures', + Condition => '$$self{Model} !~ /^(SLT-A(37|57|65|77)V?|Lunar|NEX-(F3|5N|7|VG20E))/', + Format => 'undef[2]', + ValueConv => 'join " ", unpack "H2H2", $val', + ValueConvInv => sub { + my @a = split(" ", shift); + return @a == 2 ? pack 'CC', hex($a[0]), hex($a[1]) : undef; + }, + PrintConv => \&PrintLensSpec, + PrintConvInv => 'Image::ExifTool::Sony::PrintInvLensSpec($val, $self, 1)', + }, + +# 0x0122 => {Name=>'LensType',Format=>'int16u',Condition =>'$$self{Model}=~/^(SLT-A(37|57|65|77)V?|Lunar|NEX-(F3|5N|7|VG20E))/'}, +# 0x0123 => {Name=>'LensType',Format=>'int16u',Condition =>'$$self{Model}=~/^(SLT-A(58|99V?)|HV|ILCA-(68|77M2)|NEX-(3N|5R|5T|6|VG30E|VG900)|ILCE-(3000|3500|5000|5100|6000|7|7R|7S|7M2|QX1))/'}, +# 0x012d => {Name=>'LensType',Format=>'int16u',Condition =>'$$self{Model}=~/^(SLT-A(37|57|65|77)V?|Lunar|NEX-(F3|5N|7|VG20E))/'}, +# 0x012e => {Name=>'LensType',Format=>'int16u',Condition =>'$$self{Model}=~/^(SLT-A(58|99V?)|HV|ILCA-(68|77M2)|NEX-(3N|5R|5T|6|VG30E|VG900)|ILCE-(3000|3500|5000|5100|6000|7|7R|7S|7M2|QX1))/'}, + +# ShutterCount3 = ShutterCount for SLT-A58, ILCE, ILCA, NEX-3N +# ShutterCount-1 for SLT-A37,A57,A65,A77,A99, NEX-F3,5N,5R,5T,6,7, sometimes 0 +# ShutterCount-2 for NEX-VG, and often 0; "ShutterCount-2" also seen on a few A99V images +# The offset for ShutterCount3 changes with firmware version for the ILCE-7/7R/7S/7M2, so don't decode it for now: +# ILCE-7M2/7S: 0x01a0 (firmware 1.0x, 1.1x), 0x01b6 (firmware 1.20, 1.21, 2.00) +# ILCE-7/7R: 0x01aa (firmware 1.0x, 1.1x), 0x01c0 (firmware 1.20, 1.21, 2.00) +# Similarly for ILCE-6000 v2.00: 0x01aa --> 0x01c0: removed from 0x01aa + 0x01a0 => { + Name => 'ShutterCount3', + Format => 'int32u', + RawConv => '$val == 0 ? undef : $val', + Condition => '$$self{Model} =~ /^(ILCE-(5100|QX1)|ILCA-(68|77M2))/', + }, + 0x01aa => { + Name => 'ShutterCount3', + Format => 'int32u', + RawConv => '$val == 0 ? undef : $val', + Condition => '$$self{Model} =~ /^(SLT-A(58|99V?)|HV|NEX-(3N|5R|5T|6|VG900|VG30E)|ILCE-([35]000|3500))\b/', + }, + 0x01bd => { + Name => 'ShutterCount3', + Format => 'int32u', + RawConv => '$val == 0 ? undef : $val', + Condition => '$$self{Model} =~ /^(SLT-A(37|57|65|77)V?|Lunar|NEX-(F3|5N|7|VG20E))/', + }, + +# 0x0222 => {Name=>'LensType2',Format=>'int16u',Condition =>'$$self{Model}=~/^(ILCE-(5100|7S|7M2|QX1))/'}, +# 0x0224 => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(ILCE-(5100|7S|7M2|QX1)|ILCA-(68|77M2))/'}, +# 0x0229 => {Name=>'LensType2',Format=>'int16u',Condition =>'$$self{Model}=~/^(NEX-(5R|5T|6|VG30E|VG900))/'}, +# 0x022b => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(NEX-(5R|5T|6|VG30E|VG900))/'}, +# 0x022c => {Name=>'LensType2',Format=>'int16u',Condition =>'$$self{Model}=~/^(ILCE-(3000|3500|5000|6000|7|7R)|NEX-3N)\b/'}, +# 0x022e => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(ILCE-(3000|3500|5000|6000|7|7R)|NEX-3N|SLT-A(58|99V?)|HV)\b/'}, + +# 0x0231 => {Name=>'LensSpecFeatures',Format=>'undef[2]',Condition=>'$$self{Model}=~/^(ILCE-(7S|7M2|5100|QX1)|ILCA-(68|77M2))/'}, +# 0x0238 => {Name=>'LensSpecFeatures',Format=>'undef[2]',Condition=>'$$self{Model}=~/^(NEX-(5R|5T|6|VG30E|VG900))/'}, +# 0x023b => {Name=>'LensSpecFeatures',Format=>'undef[2]',Condition=>'$$self{Model}=~/^(SLT-A(58|99V?)|HV|ILCE-(3000|3500|5000|6000|7|7R)|NEX-3N)\b/'}, + +# 0x023c => {Name=>'LensType2',Format=>'int16u',Condition =>'$$self{Model}=~/^(Lunar|NEX-(F3|5N|7|VG20E))/'}, +# 0x023e => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(SLT-A(37|57|65|77)V?|Lunar|NEX-(F3|5N|7|VG20E)|ILCE-(5100|7S|7M2|QX1)|ILCA-(68|77M2))/'}, +# 0x0245 => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(NEX-(5R|5T|6|VG30E|VG900))/'}, +# 0x0248 => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(SLT-A(58|99V?)|HV|ILCE-(3000|3500|5000|6000|7|7R)|NEX-3N)\b/'}, +# 0x0249 => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(ILCE-(5100|7S|7M2|QX1)|ILCA-(68|77M2))/'}, + +# 0x024a => {Name=>'LensSpecFeatures',Format=>'undef[2]',Condition=>'$$self{Model}=~/^(SLT-A(37|57|65|77)V?|Lunar|NEX-(F3|5N|7|VG20E))/'}, + +# 0x0250 => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(NEX-(5R|5T|6|VG30E|VG900))/'}, +# 0x0253 => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(SLT-A(58|99V?)|HV|ILCE-(3000|3500|5000|6000|7|7R|7S|7M2)|NEX-3N)\b/'}, +# 0x0257 => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(SLT-A(37|57|65|77)V?|Lunar|NEX-(F3|5N|7|VG20E))/'}, +# 0x0262 => {Name=>'LensType', Format=>'int16u',Condition =>'$$self{Model}=~/^(SLT-A(37|57|65|77)V?|Lunar|NEX-(F3|5N|7|VG20E))/'}, + +# 0x031b => {%gain2010,Condition=>'$$self{Model}=~/^(DSC-RX100M3|ILCA-(68|77M2)|ILCE-(5100|7S|7M2|QX1))/'}, +# 0x032c => {%gain2010,Condition=>'$$self{Model}=~/^(NEX-(5R|5T|6|VG30E|VG900))/'}, +# 0x032f => {%gain2010,Condition=>'$$self{Model}=~/^(DSC-RX10|SLT-A(58|99V?)|HV|ILCE-(3000|3500|5000|6000|7|7R)|NEX-3N)\b/'}, +# 0x0350 => {%gain2010,Condition=>'$$self{Model}=~/^(SLT-A(37|57)|NEX-F3)/'}, +# 0x037b => {%gain2010,Condition=>'$$self{Model}=~/^(SLT-A(65|77)V?|Lunar|NEX-(7|VG20E))/'}, +); + +%Image::ExifTool::Sony::Tag9050b = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => q{ + Valid from July 2015 for ILCE-6100/6300/6400/6500/6600/7C/7M3/7RM2/7RM3/7RM4/ + 7SM2/9/9M2, ILCA-99M2. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x0039, 0x0105 ], + 0x0000 => { + Condition => '$$self{Model} =~ /^(ILCA-)/', + Name => 'SonyMaxAperture', # (at current focal length) + # seen values from 17 - 48 + ValueConv => '2 ** (($val/8 - 1.06) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x0001 => { + Condition => '$$self{Model} =~ /^(ILCA-)/', + Name => 'SonyMinAperture', # (at current focal length) + # seen values from 80 - 95 + ValueConv => '2 ** (($val/8 - 1.06) / 2)', + ValueConvInv => 'int((log($val) * 2 / log(2) + 1) * 8 + 0.5)', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x0026 => { + Name => 'Shutter', + Format => 'int16u[3]', + PrintConv => { + '0 0 0' => 'Silent / Electronic (0 0 0)', + OTHER => sub { + my ($val, $inv) = @_; + return $inv ? ($val=~/\((.*?)\)/ ? $1 : undef) : "Mechanical ($val)"; + }, + }, + }, + #0x002c => { + # Name => 'Shutter', + # Condition => '$$self{Model} !~ /^(ILCE-6400)/ and $$self{Software} !~ /^ILCE-9 v5.0/', + # PrintConv => { + # 0 => 'Silent / Electronic', + # 56 => 'Mechanical', + # }, + #}, + 0x0039 => { + Name => 'FlashStatus', + RawConv => '$$self{FlashFired} = $val', + PrintConv => { + 0 => 'No Flash present', + 2 => 'Flash Inhibited', # seen for ILCE-7/7R continuous, panorama, HDR mode + 64 => 'Built-in Flash present', + 65 => 'Built-in Flash Fired', + 66 => 'Built-in Flash Inhibited', # seen for panorama, HDR, burst mode + 128 => 'External Flash present', # seen for NEX-5N/5T + 129 => 'External Flash Fired', # seen for SLT-A99V, ILCE-7R, NEX-5N/5R +# 131 => 'External Flash ???', # seen for ILCE-7RM4 + }, + }, + 0x003a => { + Name => 'ShutterCount', + # or "ShutterCount"? : number of shutter actuations, does not increase during Silent Shooting, + # at least for ILCE-7RM2 + Format => 'int32u', + Notes => 'total number of image exposures made by the camera', + RawConv => '$val & 0x00ffffff', + }, + 0x0046 => { # appr. same value as Exif ExposureTime, but longer in HDR-modes + Name => 'SonyExposureTime', + Format => 'int16u', + ValueConv => '$val ? 2 ** (16 - $val/256) : 0', + ValueConvInv => '$val ? int((16 - log($val) / log(2)) * 256 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x0048 => { + Name => 'SonyFNumber', + Format => 'int16u', + ValueConv => '2 ** (($val/256 - 16) / 2)', + ValueConvInv => '(log($val)*2/log(2)+16)*256', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x004b => { + Name => 'ReleaseMode2', + %releaseMode2, + }, +# March 2019: ILCE-9 with v5.0x firmware follows ILCE-6400 in many tags ... + 0x0050 => { + Name => 'ShutterCount2', + Condition => '(($$self{FlashFired} & 0x01) != 1) and ($$self{Model} =~ /^(ILCE-(6100|6400|6600|7C|7RM4A?|9M2)|ZV-E10)/ or $$self{Software} =~ /^ILCE-9 (v5.0|v6.0)/)', + Format => 'int32u', + RawConv => '$val & 0x00ffffff', + }, + 0x0052 => { + Name => 'ShutterCount2', + Condition => '(($$self{FlashFired} & 0x01) != 1) and ($$self{Model} =~ /^(ILCE-(7M3|7RM3A?))/)', + Format => 'int32u', + RawConv => '$val & 0x00ffffff', + }, +# 0x0058 - 0x0067: A-Mount: 16 values, only non-zero data for lenses giving "A-mount (3)" in 0x0104 below. +# e.g for SAL70400G2: 9 5 5 64 69 74 79 84 89 94 98 104 255 105 89 80 +# where 9 means 9 focal lengths: 64 ... 104, +# corresponding to 70-400mm via FocalLength = 4.375*2**($val/16) +# 0x0058, 0x0061: E-Mount: ShutterCount and dateTime + 0x0058 => { # appears not valid when flash is used ... not for ILCA-99M2 + Name => 'ShutterCount2', + Condition => '(($$self{FlashFired} & 0x01) != 1) and ($$self{Model} !~ /^(ILCA-99M2|ILCE-(6100|6400|6600|7C|7M3|7RM3A?|7RM4A?|9M2)|ZV-E10)/) and $$self{Software} !~ /^ILCE-9 (v5.0|v6.0)/', + Format => 'int32u', + RawConv => '$val & 0x00ffffff', + }, + 0x0061 => { # only minutes-seconds, not for ILCA-99M2, ILCE-9 + Name => 'SonyTimeMinSec', + Condition => '$$self{Model} !~ /^(ILCA-99M2|ILCE-(6100|6400|6600|7C|7M3|7RM3A?|7RM4A?|9|9M2)|ZV-E10)/', + Format => 'undef[2]', + ValueConv => q{ + my @v = unpack('C*', $val); + return sprintf("%.2d:%.2d", @v) + }, + }, + 0x006b => { + Name => 'ReleaseMode2', + Condition => '$$self{Model} =~ /^(ILCE-(6100|6400|6600|7C|7RM4A?|9M2)|ZV-E10)/ or $$self{Software} =~ /^ILCE-9 (v5.0|v6.0)/', + %releaseMode2, + }, + 0x006d => { + Name => 'ReleaseMode2', + Condition => '$$self{Model} =~ /^(ILCE-(7M3|7RM3A?))/', + %releaseMode2, + }, + 0x0073 => { + Name => 'ReleaseMode2', + Condition => '$$self{Model} !~ /^(ILCE-(6100|6400|6600|7C|7M3|7RM3A?|7RM4A?|9M2)|ZV-E10)/ and $$self{Software} !~ /^ILCE-9 (v5.0|v6.0)/', + %releaseMode2, + }, + 0x0088 => { + Name => 'InternalSerialNumber', #(NC) + Format => 'int8u[6]', + PrintConv => 'unpack "H*", pack "C*", split " ", $val', + }, + +##### same offsets for lens info tags + + # 0x0104 - same as 0x0105, but has value 3 for SAL18135, SAL50F14Z, SAL55300, SAL70200G2, SAL70300G2, SAL70400G2 + 0x0105 => { + Name => 'LensMount', + DataMember => 'LensMount', + RawConv => '$$self{LensMount} = $val', + PrintConv => { + 0 => 'Unknown', + 1 => 'A-mount', + 2 => 'E-mount', + }, + }, + 0x0106 => { + Name => 'LensFormat', + PrintConv => { + 0 => 'Unknown', + 1 => 'APS-C', + 2 => 'Full-frame', + }, + }, + 0x0107 => { + Name => 'LensType2', + Condition => '$$self{LensMount} == 2', + Format => 'int16u', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x0109 => { + Name => 'LensType', + Condition => '$$self{LensMount} == 1', + Priority => 0, #PH (just to be safe) + Format => 'int16u', #PH + Notes => 'SLT models, and NEX with A-mount lenses', + SeparateTable => 1, + # has a value of 0 for E-mount lenses (values 0x80xx) + ValueConvInv => '($val & 0xff00) == 0x8000 ? 0 : int($val)', + PrintConv => \%sonyLensTypes, + PrintInt => 1, + }, + 0x010b => { + Name => 'DistortionCorrParamsPresent', + PrintConv => { 0 => 'No', 1 => 'Yes'}, + }, + 0x0114 => { + Name => 'APS-CSizeCapture', + Condition => '$$self{Model} =~ /^(ILCE-7|ILCE-9|ILCA-99)/', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + # 0x0116 and 0x0117: + # give the same info as the first and last bytes of LensSpec, + # but also for older Sony and Minolta lenses where all LensSpec bytes are 0. + 0x0116 => { + Name => 'LensSpecFeatures', + Format => 'undef[2]', + ValueConv => 'join " ", unpack "H2H2", $val', + ValueConvInv => sub { + my @a = split(" ", shift); + return @a == 2 ? pack 'CC', hex($a[0]), hex($a[1]) : undef; + }, + PrintConv => \&PrintLensSpec, + PrintConvInv => 'Image::ExifTool::Sony::PrintInvLensSpec($val, $self, 1)', + }, +# +# tags becoming model- and/or firmware-dependent from here. +# + 0x019f => { + Name => 'ShutterCount3', + Condition => '$$self{Model} =~ /^(ILCE-(6100|6400|6600|7C|7M3|7RM3A?|7RM4A?|9|9M2)|ZV-E10)\b/', + Format => 'int32u', + RawConv => '$val == 0 ? undef : $val', + }, + 0x01cb => { + Name => 'ShutterCount3', + Condition => '$$self{Model} =~ /^(ILCE-(7RM2|7SM2))/', + Format => 'int32u', + RawConv => '$val == 0 ? undef : $val', + }, + 0x01cd => { + Name => 'ShutterCount3', + Condition => '$$self{Model} =~ /^(ILCE-(6300|6500)|ILCA-99M2)/', + Format => 'int32u', + RawConv => '$val == 0 ? undef : $val', + }, + 0x01eb => { + Name => 'APS-CSizeCapture', + Condition => '$$self{Model} =~ /^ILCE-(7RM4A?|7C|9M2)|ZV-E10/ or $$self{Software} =~ /^ILCE-9 (v5.0|v6.0)/', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x01ed => { + Name => 'LensSpecFeatures', + Condition => '$$self{Model} =~ /^ILCE-(7RM4A?|7C|9M2)|ZV-E10/ or $$self{Software} =~ /^ILCE-9 (v5.0|v6.0)/', + Priority => 0, + Format => 'undef[2]', + ValueConv => 'join " ", unpack "H2H2", $val', + ValueConvInv => sub { + my @a = split(" ", shift); + return @a == 2 ? pack 'CC', hex($a[0]), hex($a[1]) : undef; + }, + PrintConv => \&PrintLensSpec, + PrintConvInv => 'Image::ExifTool::Sony::PrintInvLensSpec($val, $self, 1)', + }, + 0x01ee => { + Name => 'APS-CSizeCapture', + Condition => '$$self{Model} =~ /^(ILCE-(7M3|7RM3A?|9))\b/ and $$self{Software} !~ /^ILCE-9 (v5.0|v6.0)/', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x01f0 => { + Name => 'LensSpecFeatures', + Condition => '$$self{Model} =~ /^(ILCE-(6100|6400|6600|7M3|7RM3A?|9))\b/ and $$self{Software} !~ /^ILCE-9 (v5.0|v6.0)/', + Priority => 0, + Format => 'undef[2]', + ValueConv => 'join " ", unpack "H2H2", $val', + ValueConvInv => sub { + my @a = split(" ", shift); + return @a == 2 ? pack 'CC', hex($a[0]), hex($a[1]) : undef; + }, + PrintConv => \&PrintLensSpec, + PrintConvInv => 'Image::ExifTool::Sony::PrintInvLensSpec($val, $self, 1)', + }, +# the following tags are commented out because they can cause problems for the +# Composite LensID of non-electronic lenses (even if Priority is set to 0) +# 0x020d => { +# Name=>'LensType2', +# Condition => '$$self{Model} =~ /^(ILCE-(7RM2|7SM2))/', +# Format=>'int16u', +# }, +# 0x020f => [{ +# Name=>'LensType', +# Condition => '$$self{Model} =~ /^(ILCE-(7RM2|7SM2))/', +# Format=>'int16u', +# },{ +# Name=>'LensType2', +# Condition => '$$self{Model} =~ /^(ILCE-(6300|6500)|ILCA-99M2)/', +# Format=>'int16u', +# }], +# 0x0211 => { +# Name=>'LensType', +# Condition => '$$self{Model} =~ /^(ILCE-(6300|6500)|ILCA-99M2)/', +# Format=>'int16u', +# }, + 0x021a => { + Name => 'APS-CSizeCapture', + Condition => '$$self{Model} =~ /^(ILCE-(7RM2|7SM2))/', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x021c => [{ + Name => 'LensSpecFeatures', + Condition => '$$self{Model} =~ /^(ILCE-(7RM2|7SM2))/', + Priority => 0, + Format => 'undef[2]', + ValueConv => 'join " ", unpack "H2H2", $val', + ValueConvInv => sub { + my @a = split(" ", shift); + return @a == 2 ? pack 'CC', hex($a[0]), hex($a[1]) : undef; + }, + PrintConv => \&PrintLensSpec, + PrintConvInv => 'Image::ExifTool::Sony::PrintInvLensSpec($val, $self, 1)', + },{ + Name => 'APS-CSizeCapture', + Condition => '$$self{Model} =~ /^(ILCA-99M2)/', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }], + 0x021e => { + Name => 'LensSpecFeatures', + Condition => '$$self{Model} =~ /^(ILCE-(6300|6500)|ILCA-99M2)/', + Priority => 0, + Format => 'undef[2]', + ValueConv => 'join " ", unpack "H2H2", $val', + ValueConvInv => sub { + my @a = split(" ", shift); + return @a == 2 ? pack 'CC', hex($a[0]), hex($a[1]) : undef; + }, + PrintConv => \&PrintLensSpec, + PrintConvInv => 'Image::ExifTool::Sony::PrintInvLensSpec($val, $self, 1)', + }, +); + +%Image::ExifTool::Sony::Tag9050c = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => q{ + Valid from July 2020 for ILCE-1/7SM3, ILME-FX3. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x0039 ], + 0x0026 => { + Name => 'Shutter', + Format => 'int16u[3]', + PrintConv => { + '0 0 0' => 'Silent / Electronic (0 0 0)', + OTHER => sub { + my ($val, $inv) = @_; + return $inv ? ($val=~/\((.*?)\)/ ? $1 : undef) : "Mechanical ($val)"; + }, + }, + }, + 0x0039 => { + Name => 'FlashStatus', + RawConv => '$$self{FlashFired} = $val', + PrintConv => { + 0 => 'No Flash present', + 2 => 'Flash Inhibited', # seen for ILCE-7/7R continuous, panorama, HDR mode + 64 => 'Built-in Flash present', + 65 => 'Built-in Flash Fired', + 66 => 'Built-in Flash Inhibited', # seen for panorama, HDR, burst mode + 128 => 'External Flash present', # seen for NEX-5N/5T + 129 => 'External Flash Fired', # seen for SLT-A99V, ILCE-7R, NEX-5N/5R + }, + }, + 0x003a => { + Name => 'ShutterCount', + # or "ShutterCount"? : number of shutter actuations, does not increase during Silent Shooting, + # at least for ILCE-7RM2 + Format => 'int32u', + Notes => 'total number of image exposures made by the camera', + RawConv => '$val & 0x00ffffff', + PrintConv => 'sprintf("%6d",$val)', + }, + 0x0046 => { # appr. same value as Exif ExposureTime, but longer in HDR-modes + Name => 'SonyExposureTime', + Format => 'int16u', + ValueConv => '$val ? 2 ** (16 - $val/256) : 0', + ValueConvInv => '$val ? int((16 - log($val) / log(2)) * 256 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x0048 => { + Name => 'SonyFNumber', + Format => 'int16u', + ValueConv => '2 ** (($val/256 - 16) / 2)', + ValueConvInv => '(log($val)*2/log(2)+16)*256', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x004b => { + Name => 'ReleaseMode2', + %releaseMode2, + }, + 0x0050 => { + Name => 'ShutterCount2', + Condition => '($$self{FlashFired} & 0x01) != 1', + Format => 'int32u', + RawConv => '$val & 0x00ffffff', + }, + 0x0066 => { # appr. same value as Exif ExposureTime, but not valid in HDR-modes + Name => 'SonyExposureTime', + Format => 'int16u', + ValueConv => '$val ? 2 ** (16 - $val/256) : 0', + ValueConvInv => '$val ? int((16 - log($val) / log(2)) * 256 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x0068 => { # appr. same value as Exif ExposureTime, but not valid in HDR-modes + Name => 'SonyFNumber', + Format => 'int16u', + ValueConv => '2 ** (($val/256 - 16) / 2)', + ValueConvInv => '(log($val)*2/log(2)+16)*256', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x006b => { + Name => 'ReleaseMode2', + %releaseMode2, + }, + 0x0088 => { + Name => 'InternalSerialNumber', #(NC) + Condition => '$$self{Model} =~ /^(ILCE-(7M4|7RM5|7SM3)|ILME-FX3)/', + Format => 'int8u[6]', + PrintConv => 'unpack "H*", pack "C*", split " ", $val', + }, + 0x008a => { + Name => 'InternalSerialNumber', #(NC) + Condition => '$$self{Model} =~ /^(ILCE-1)/', + Format => 'int8u[6]', + PrintConv => 'unpack "H*", pack "C*", split " ", $val', + }, +); + +%Image::ExifTool::Sony::Tag9050d = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => 'Valid for ILCE-6700/ZV-E1.', + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x000a => { + Name => 'ShutterCount', + # number of mechanical shutter actuations, does not increase during electronic shutter / Silent Shooting + Condition => '$$self{Model} =~ /^(ILCE-6700)/', + Format => 'int32u', + Notes => 'total number of image exposures made by the camera', + RawConv => '$val & 0x00ffffff', + PrintConv => 'sprintf("%6d",$val)', + PrintConvInv => '$val', + }, + 0x001a => { # appr. same value as Exif ExposureTime, but not valid in HDR-modes + Name => 'SonyExposureTime', + Format => 'int16u', + ValueConv => '$val ? 2 ** (16 - $val/256) : 0', + ValueConvInv => '$val ? int((16 - log($val) / log(2)) * 256 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x001c => { # appr. same value as Exif ExposureTime, but not valid in HDR-modes + Name => 'SonyFNumber', + Format => 'int16u', + ValueConv => '2 ** (($val/256 - 16) / 2)', + ValueConvInv => '(log($val)*2/log(2)+16)*256', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x001f => { + Name => 'ReleaseMode2', + %releaseMode2, + }, + 0x0038 => { + Name => 'InternalSerialNumber', #(NC) + Format => 'int8u[6]', + PrintConv => 'unpack "H*", pack "C*", split " ", $val', + }, +); + +%Image::ExifTool::Sony::Tag9400a = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => 'Valid for many DSC, NEX and SLT models', + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x0008 => { %sequenceImageNumber }, #PH + 0x000c => { %sequenceFileNumber }, #PH + 0x0010 => { %releaseMode2 }, + 0x0012 => { + Name => 'DigitalZoom', + Condition => '$$self{Model} !~ /^(SLT-(A65|A77)V?|NEX-(5N|7|VG20E)|Lunar|DSC-(HX10V|HX20V|HX200V|TX20|TX55|TX300V|WX30|WX70))\b/', + PrintConv => { + 0 => 'No', + 1 => 'Yes', + }, + }, +# 0x0013 - Flash fired 0=no, 1=yes +# 0x0014 - related to flash / RedEyeReduction ? +# 0x0015 - CameraType: 1=HDR, 2=DSC, 3=SLT/NEX + 0x001a => { #PH + Name => 'ShotNumberSincePowerUp', + Format => 'int32u', + }, +# 0x001e - increments by 4 or 6 or 8 each shutter release press since power up +# 0x001f - 0=most pictures, 1='Self-timer/Self-portrait', 2='Self-portrait (2 people)', +# 3='Continuous Self-timer', 26='Sweep Panorama' (PH, RX100) +# 0x0021 - maybe related to Image Stabilization or Smile Shutter ? + 0x0022 => { + Name => 'SequenceLength', + PrintConv => { + 0 => 'Continuous', # (RX100 too) + 1 => '1 shot', + 2 => '2 shots', # (Background defocus, 3D Image) + 3 => '3 shots', # (HDR, WB Bracketing) (RX100, also continuous bracket) + 4 => '4 shots', # seen for DSC-WX300 in Superior-Auto Anti-Motion-Blur + 5 => '5 shots', # (PH, RX100) + 6 => '6 shots', # (Multi Frame NR, Anti Motion blur, Hand-held Twilight) + 10 => '10 shots', # (HX9V Burst) + 100 => 'Continuous - iSweep Panorama', # (HX9V) + 200 => 'Continuous - Sweep Panorama', + }, + }, +# 0x0027 - 1=single exposure, 2=multi-exposure (eg. pano,some superior auto) (PH, RX100) + 0x0028 => { + Name => 'CameraOrientation', # (also RX100 - PH) + PrintConv => { + 1 => 'Horizontal (normal)', + 3 => 'Rotate 180', + 6 => 'Rotate 90 CW', + 8 => 'Rotate 270 CW', + }, + }, + 0x0029 => { + Name => 'Quality2', # (also RX100 - PH) + PrintConv => { + 0 => 'JPEG', + 1 => 'RAW', + 2 => 'RAW + JPEG', + 3 => 'JPEG + MPO', # 3D images + }, + }, +# 0x002b - FacesDetected_OK 0=no, 1=yes appears valid for SLT, but not for NEX and DSC-HX9V +# 0x0030 - long exposure noise reduction used 0=no, 1=yes (PH, RX100) +# 0x0031 - smile shutter used 0=no, 1=yes (PH, RX100) +# 0x0033 - 0 for DSC-HX9V, 8 for SLT, NEX +# 0x0034 and 0x0038 - different offset for HX9V and SLT/NEX, but similar numbers, non-zero when flash fired + 0x0044 => { + Condition => '$$self{Model} =~ /^(SLT-|HV|NEX-|Lunar|DSC-RX|Stellar)/', # not valid for most other DSC and HDR models + Name => 'SonyImageHeight', + Format => 'int16u', + PrintConv => '$val > 0 ? 8*$val : "n.a."', + }, + 0x0052 => { + Name => 'ModelReleaseYear', + Condition => '$$self{Model} =~ /^(SLT-|HV|NEX-|Lunar|DSC-RX|Stellar)/', # not valid for most other DSC and HDR models + Format => 'int8u', + PrintConv => 'sprintf("20%.2d", $val)', + }, +); + +%Image::ExifTool::Sony::Tag9400b = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => q{ + Valid for NEX-3N, ILCE-3000/3500, SLT-A58, DSC-WX60, DSC-WX300, DSC-RX100M2, + DSC-HX50V, DSC-QX10/QX100. + }, + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x0008 => { %sequenceImageNumber }, #PH + 0x000c => { %sequenceFileNumber }, #PH + 0x0010 => { %releaseMode2 }, + 0x0012 => { + Name => 'DigitalZoom', + PrintConv => { + 0 => 'No', + 1 => 'Yes', + }, + }, +# 0x0013 - Flash fired 0=no, 1=yes +# 0x0014 - related to flash / RedEyeReduction ? +# 0x0015 - CameraType: 1=HDR, 2=DSC, 3=SLT/NEX + 0x0016 => { #PH + Name => 'ShotNumberSincePowerUp', + Format => 'int32u', + }, + 0x001e => { + Name => 'SequenceLength', + PrintConv => { + 0 => 'Continuous', + 1 => '1 shot', + 2 => '2 shots', + 3 => '3 shots', + 4 => '4 shots', + 5 => '5 shots', + 6 => '6 shots', + 10 => '10 shots', + 100 => 'Continuous - iSweep Panorama', + 200 => 'Continuous - Sweep Panorama', + }, + }, + 0x0024 => { + Name => 'CameraOrientation', + PrintConv => { + 1 => 'Horizontal (normal)', + 3 => 'Rotate 180', + 6 => 'Rotate 90 CW', + 8 => 'Rotate 270 CW', + }, + }, + 0x0025 => { + Name => 'Quality2', + PrintConv => { + 0 => 'JPEG', + 1 => 'RAW', + 2 => 'RAW + JPEG', + 3 => 'JPEG + MPO', # 3D images + }, + }, +# 0x0027 - FacesDetected_OK 0=no, 1=yes +# 0x002c - long exposure noise reduction used 0=no, 1=yes (PH, RX100) + 0x003f => { + Name => 'SonyImageHeight', + Format => 'int16u', + PrintConv => '$val > 0 ? 8*$val : "n.a."', + }, + 0x0046 => { # but Panorama images give incorrect result + Name => 'ModelReleaseYear', + Format => 'int8u', + PrintConv => 'sprintf("20%.2d", $val)', + }, +); + +%Image::ExifTool::Sony::Tag9400c = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + NOTES => q{ + Valid for DSC-HX60V/HX80/HX90V/HX99/HX350/HX400V/QX30/RX0/RX1RM2/RX10/ + RX10M2/RX10M3/RX10M4/RX100M3/RX100M4/RX100M5/RX100M5A/RX100M6/RX100M7/WX220/ + WX350/WX500, ILCE-1/7/7C/7R/7S/7M2/7M3/7RM2/7RM3/7RM4/7SM2/7SM3/9/9M2/5000/ + 5100/6000/6100/6300/6400/6500/6600/QX1, ILCA-68/77M2/99M2. + }, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x0009 => { %releaseMode2 }, + 0x000a => { + Name => 'ShotNumberSincePowerUp', + Condition => '$$self{Model} =~ /^(ILCA-(68|77M2|99M2)|ILCE-(5000|5100|6000|6300|6500|7|7M2|7R|7RM2|7S|7SM2|QX1)|DSC-(HX350|HX400V|HX60V|HX80|HX90|HX90V|QX30|RX0|RX1RM2|RX10|RX10M2|RX10M3|RX100M3|RX100M4|RX100M5|WX220|WX350|WX500))\b/', + Notes => 'valid only for some models', + Format => 'int32u', + }, + 0x0012 => { %sequenceImageNumber }, + 0x0016 => { + Name => 'SequenceLength', + PrintConv => { + 0 => 'Continuous', + 1 => '1 shot', + 2 => '2 shots', + 3 => '3 shots', + 4 => '4 shots', + 5 => '5 shots', + 6 => '6 shots', + 7 => '7 shots', # DSC-RX100M7 Single Burst Shooting + 9 => '9 shots', # ILCE-7RM2 9-shot bracketing + 10 => '10 shots', + 12 => '12 shots', # ILCA-77M2 12-shot MFNR-mode + 16 => '16 shots', # ILCE-7RM4 16-shot PixelShift + 100 => 'Continuous - iSweep Panorama', + 200 => 'Continuous - Sweep Panorama', + }, + }, + 0x001a => { %sequenceFileNumber }, + 0x001e => { + Name => 'SequenceLength', + PrintConv => { + 0 => 'Continuous', + 1 => '1 file', + 2 => '2 files', + 3 => '3 files', + 5 => '5 files', + 7 => '7 files', # DSC-RX100M7 Single Burst Shooting + 9 => '9 files', # ILCE-7RM2 9-shot bracketing + 10 => '10 files', # seen for DSC-WX500 with burst of 10 shots + }, + }, + 0x0029 => { + Name => 'CameraOrientation', + PrintConv => { + 1 => 'Horizontal (normal)', + 3 => 'Rotate 180', + 6 => 'Rotate 90 CW', + 8 => 'Rotate 270 CW', + }, + }, + 0x002a => [{ + Name => 'Quality2', + Condition => '$$self{Model} !~ /^(ILCE-(1|6700|7M4|7RM5|7SM3)|ILME-(FX3|FX30)|ZV-E1)\b/', + PrintConv => { + 0 => 'JPEG', + 1 => 'RAW', + 2 => 'RAW + JPEG', + 3 => 'JPEG + MPO', # 3D images + }, + },{ + Name => 'Quality2', + PrintConv => { + 1 => 'JPEG', + 2 => 'RAW', + 3 => 'RAW + JPEG', + 4 => 'HEIF', + 6 => 'RAW + HEIF', + }, + }], + 0x0047 => { + Name => 'SonyImageHeight', + Condition => '$$self{Model} !~ /^(ILCE-(1|6700|7M4|7RM5|7SM3)|ILME-(FX3|FX30)|ZV-E1)\b/', + Format => 'int16u', + PrintConv => '$val > 0 ? 8*$val : "n.a."', + }, + 0x0053 => { + Name => 'ModelReleaseYear', + Condition => '$$self{Model} !~ /^(ILCE-(1|6700|7M4|7RM5|7SM3)|ILME-(FX3|FX30)|ZV-E1)\b/', + Format => 'int8u', + PrintConv => 'sprintf("20%.2d", $val)', + }, + 0x0133 => { + Name => 'ShutterType', + Condition => '$$self{Model} =~ /^(DSC-(HX350|HX400V|HX60V|HX80|HX90|HX90V|QX30|RX10|RX10M2|RX10M3|RX100M3|RX100M4))\b/', + PrintConv => { + 7 => 'Electronic', + 23 => 'Mechanical', + }, + }, + 0x0139 => { + Name => 'ShutterType', + Condition => '$$self{Model} =~ /^(DSC-(HX95|HX99|RX0|RX0M2|RX10M4|RX100M5|RX100M5A|RX100M6))\b/', + PrintConv => { + 7 => 'Electronic', + 23 => 'Mechanical', + }, + }, + 0x013f => { + Name => 'ShutterType', + Condition => '$$self{Model} =~ /^(DSC-RX100M7|ZV-(1|1F|1M2))\b/', + PrintConv => { + 7 => 'Electronic', + 23 => 'Mechanical', + }, + }, +); + +%Image::ExifTool::Sony::Tag9401 = ( # JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0 ], + IS_SUBDIR => [ 0x03e2, 0x044e, 0x0498, 0x049d, 0x04a1, 0x04a2, 0x04ba, 0x059d, 0x0634, 0x0636, 0x064c, 0x0653, 0x0678, 0x06b8, 0x06de, 0x06e7 ], + 0x0000 => { Name => 'Ver9401', Hidden => 1, RawConv => '$$self{Ver9401} = $val; $$self{OPTIONS}{Unknown}<2 ? undef : $val' }, + + 0x03e2 => { Name => 'ISOInfo', Condition => '$$self{Ver9401} == 181', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x044e => { Name => 'ISOInfo', Condition => '$$self{Ver9401} == 178', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x0498 => { Name => 'ISOInfo', Condition => '$$self{Ver9401} == 148', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x049d => { Name => 'ISOInfo', Condition => '$$self{Ver9401} == 167', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x04a1 => { Name => 'ISOInfo', Condition => '$$self{Ver9401} =~ /^(160|164)/', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x04a2 => { Name => 'ISOInfo', Condition => '$$self{Ver9401} =~ /^(152|154|155)/ and $$self{Model} !~ /^ZV-1M2/', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x04ba => { Name => 'ISOInfo', Condition => '$$self{Ver9401} == 155 and $$self{Model} =~ /^ZV-1M2/', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x059d => { Name => 'ISOInfo', Condition => '$$self{Ver9401} =~ /^(144|146)/', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x0634 => { Name => 'ISOInfo', Condition => '$$self{Ver9401} == 68', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x0636 => { Name => 'ISOInfo', Condition => '$$self{Ver9401} =~ /^(73|74)/', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x064c => { Name => 'ISOInfo', Condition => '$$self{Ver9401} == 78', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x0653 => { Name => 'ISOInfo', Condition => '$$self{Ver9401} == 90', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x0678 => { Name => 'ISOInfo', Condition => '$$self{Ver9401} =~ /^(93|94)/', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x06b8 => { Name => 'ISOInfo', Condition => '$$self{Ver9401} =~ /^(100|103)/', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x06de => { Name => 'ISOInfo', Condition => '$$self{Ver9401} =~ /^(124|125)/', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, + 0x06e7 => { Name => 'ISOInfo', Condition => '$$self{Ver9401} =~ /^(127|128|130)/', Format => 'int8u[5]', SubDirectory => { TagTable => 'Image::ExifTool::Sony::ISOInfo' } }, +); + +%Image::ExifTool::Sony::ISOInfo = ( # JR + FORMAT => 'int8u', + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + 0x0000 => { Name => 'ISOSetting', ValueConv => \%isoSetting2010 }, + 0x0002 => { Name => 'ISOAutoMin', ValueConv => \%isoSetting2010 }, + 0x0004 => { Name => 'ISOAutoMax', ValueConv => \%isoSetting2010 }, +); + +# PH (RX100) +%Image::ExifTool::Sony::Tag9402 = ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x02 ], + PRIORITY => 0, + 0x02 => { + Name => 'TempTest1', + DataMember => 'TempTest1', + Hidden => 1, + RawConv => '$$self{TempTest1}=$val; $$self{OPTIONS}{Unknown}<2 ? undef : $val', + }, + 0x04 => { + Name => 'AmbientTemperature', + # this (and many other values) are only valid if 0x02 is 255 (why?) + Condition => '$$self{TempTest1} == 255', + Format => 'int8s', # (verified for negative temperature) + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + 0x16 => { #JR + Name => 'FocusMode', + Mask => 0x7f, # (often +128, not sure what upper bit is for) + PrintConv => { + 0 => 'Manual', + 2 => 'AF-S', + 3 => 'AF-C', + 4 => 'AF-A', # seen in ILCE-6000 images + 6 => 'DMF', + # 7 => 'AF-D', # not for DSC, NEX, ILCE ... + }, + }, + 0x17 => { + Name => 'AFAreaMode', + PrintConv => { + 0 => 'Multi', # newer DSC/ILC use name 'Wide' + 1 => 'Center', + 2 => 'Spot', #(NC) seen for DSC-WX300 + 3 => 'Flexible Spot', + 10 => 'Selective (for Miniature effect)', # seen for DSC-HX30V,TX30,WX60,WX100 + 11 => 'Zone', #JR (ILCE-7 series) + 12 => 'Expanded Flexible Spot', #JR (HX90V, ILCE-7 series) + 14 => 'Tracking', + 15 => 'Face Tracking', + 20 => 'Animal Eye Tracking', +# 21 => '???', # (ILCE-1/6700/7SM3, ILME-FX3, ZV-E1: tracking ... mostly human face ...) +# 22 => '???', # (ILCE-6700, ZV-E1: tracking ...) + 255 => 'Manual', + }, + }, + # 0x24, 0x26: factor 10 for NEX and ILCE, factor 100 for DSC + # 0x24, 0x26, 0x28, 0x2a: inconsistent for A-mount lenses on NEX/ILCE: some correct, some 0, some incorrect ... + # 0x28 - not valid for DSC-[HTW]X models, or DSC-RX100 +# 0x24 => { # same values as Exif FocalLength (but have seen FocalLength for previous shot, ref IB, RX100M5) +# Name => 'FocalLength', +# Format => 'int16u', +# RawConv => '$val || undef', +# ValueConv => '$val / ($$self{Model}=~/DSC/ ? 100 : 10)', +# ValueConvInv => '$val * ($$self{Model}=~/DSC/ ? 100 : 10)', +# PrintConv => 'sprintf("%.1f mm",$val)', +# PrintConvInv => '$val =~ s/ ?mm//; $val', +# }, +# 0x26 => { # usually identical to 0x24 or 0 +# Name => 'FocalLength', +# Format => 'int16u', +# ValueConv => '$val / ($$self{Model}=~/DSC/ ? 100 : 10)', +# ValueConvInv => '$val * ($$self{Model}=~/DSC/ ? 100 : 10)', +# PrintConv => 'sprintf("%.1f mm",$val)', +# PrintConvInv => '$val =~ s/ ?mm//; $val', +# }, +# 0x28 => { # values slightly different from Exif FocalLength +# Name => 'FocalLength2', +# Format => 'int16u', +# RawConv => '$val || undef', +# ValueConv => '$val / 10', +# ValueConvInv => '$val * 10', +# PrintConv => 'sprintf("%.1f mm",$val)', +# PrintConvInv => '$val =~ s/ ?mm//; $val', +# }, +# 0x2a => { # usually identical to 0x28 or 0 +# Name => 'FocalLength2', +# Format => 'int16u', +# ValueConv => '$val / 10', +# ValueConvInv => '$val * 10', +# PrintConv => 'sprintf("%.1f mm",$val)', +# PrintConvInv => '$val =~ s/ ?mm//; $val', +# }, +# 0x002c => { +# # seen values from 80 - 255 (= infinity) -- see Composite:FocusDistance2 below +# Name => 'FocusPosition2', +# Condition => '$$self{Model} !~ /^(DSC-|Stellar)/', +# }, + 0x002d => { # usually same as 0x002c, but some differences + # seen values from 80 - 255 (= infinity) -- see Composite:FocusDistance2 below + Name => 'FocusPosition2', + Condition => '$$self{Model} !~ /^(DSC-|Stellar)/', + }, + # 0x8a - int32u: some sort of accumulated time or something since power up + # (doesn't increment during continuous shooting and at some other times) +); + +# PH (RX100) +%Image::ExifTool::Sony::Tag9403 = ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x04 ], + 0x04 => { + # seen values 0,2,3,18,19,32,49,50,83,130,132,148,213,229,255 + # CameraTemperature is valid for all values above except ==0 and >=130 + Name => 'TempTest2', + DataMember => 'TempTest2', + Hidden => 1, + RawConv => '$$self{TempTest2}=$val; $$self{OPTIONS}{Unknown}<2 ? undef : $val', + }, + 0x05 => { + Name => 'CameraTemperature', # (maybe SensorTemperature? - heats up when taking movies) + Condition => '$$self{TempTest2} and $$self{TempTest2} < 100', + Format => 'int8s', # have seen as low as -1 for AmbientTemperature of -18 + PrintConv => '"$val C"', + PrintConvInv => '$val=~s/ ?C//; $val', + }, + # 0x0f - same as 0x05 + # 0x18 - maybe another temperature? +); + +# Tag9404 (ref JR) +%Image::ExifTool::Sony::Tag9404a = ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x000b => { %exposureProgram2010 }, + 0x000d => { Name => 'IntelligentAuto', PrintConv => { 0 => 'Off', 1 => 'On'} }, + 0x0019 => { + Name => 'LensZoomPosition', + Format => 'int16u', + Condition => '$$self{Model} !~ /^SLT-/', + PrintConv => 'sprintf("%.0f%%",$val/10.24)', + PrintConvInv => '$val=~s/ ?%$//; $val * 10.24', + }, +); + +# Tag9404 (ref JR) +%Image::ExifTool::Sony::Tag9404b= ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x000c => { %exposureProgram2010 }, + 0x000e => { Name => 'IntelligentAuto', PrintConv => { 0 => 'Off', 1 => 'On'} }, + 0x001e => { + Name => 'LensZoomPosition', + Format => 'int16u', + Condition => '$$self{Model} !~ /^(SLT-|HV|ILCA-)/', + PrintConv => 'sprintf("%.0f%%",$val/10.24)', + PrintConvInv => '$val=~s/ ?%$//; $val * 10.24', + }, + 0x0020 => { + # seen values from 80 - 255 (= infinity) -- see Composite:FocusDistance2 below + Name => 'FocusPosition2', + Condition => '$$self{Model} =~ /^(SLT-|HV|ILCA-)/', + }, +); + +# Tag9404 (ref JR) +%Image::ExifTool::Sony::Tag9404c= ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x000b => { %exposureProgram2010 }, + 0x000d => { Name => 'IntelligentAuto', PrintConv => { 0 => 'Off', 1 => 'On'} }, +); + +# Tag9405 (ref JR) +%Image::ExifTool::Sony::Tag9405a = ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x0604 ], + NOTES => 'Valid for SLT, NEX, ILCE-3000/3500 and several DSC models.', + 0x0600 => { + Name => 'DistortionCorrParamsPresent', + Condition => '$$self{Model} !~ /^(DSC-|Stellar)/', + PrintConv => { 0 => 'No', 1 => 'Yes'}, + }, + 0x0601 => { + Name => 'DistortionCorrection', + PrintConv => { + 0 => 'None', + 1 => 'Applied', + }, + }, + # 0x0602 - same as 0x0604, but has value 3 for SAL18135, SAL50F14Z, SAL55300, SAL70200G2, SAL70300G2, SAL70400G2 + 0x0603 => { + Name => 'LensFormat', + Condition => '$$self{Model} !~ /^(DSC-|Stellar)/', + PrintConv => { + 0 => 'Unknown', + 1 => 'APS-C', + 2 => 'Full-frame', + }, + }, + 0x0604 => { + Name => 'LensMount', + DataMember => 'LensMount', + RawConv => '$$self{LensMount} = $val; $$self{Model} =~ /^(DSC-|Stellar)/ ? undef : $val', + PrintConv => { + 0 => 'Unknown', + 1 => 'A-mount', + 2 => 'E-mount', + # 6 - seen for A58 panorama image + }, + }, + 0x0605 => { + Name => 'LensType2', + Condition => '$$self{LensMount} == 2', + Format => 'int16u', + Notes => 'E-mount lenses', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x0608 => { + Name => 'LensType', + Condition => '$$self{LensMount} == 1', + Format => 'int16u', + Notes => 'A-mount lenses on SLT and NEX', + SeparateTable => 1, + PrintConv => \%sonyLensTypes, + PrintInt => 1, + }, + 0x064a => { + Name => 'VignettingCorrParams', + Condition => '$$self{Model} !~ /^(DSC-|Stellar)/', + Format => 'int16s[16]', + }, + 0x066a => { + Name => 'ChromaticAberrationCorrParams', + Condition => '$$self{Model} !~ /^(DSC-|Stellar)/', + Format => 'int16s[32]', + }, + 0x06ca => { + Name => 'DistortionCorrParams', + Condition => '$$self{Model} !~ /^(DSC-|Stellar)/', + Format => 'int16s[16]', + }, +); + +# Tag9405b (ref JR) +%Image::ExifTool::Sony::Tag9405b = ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x005e ], + NOTES => q{ + Valid for DSC-HX60V/HX80/HX90V/HX99/HX350/HX400V/QX30/RX0/RX10/RX10M2/ + RX10M3/RX10M4/RX100M3/RX100M4/RX100M5/RX100M5A/RX100M6/RX100M7/WX220/WX350, + ILCE-7/7M2/7M3/7R/7RM2/7RM3/7RM4/7S/7SM2/9/9M2/5000/5100/6000/6100/6300/ + 6400/6500/6600/QX1, ILCA-68/77M2/99M2. + }, + 0x0004 => { + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x0006 => { + Name => 'BaseISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x000a => { %gain2010 }, + 0x000e => { # appr. same value as Exif ExposureTime, but shorter in HDR-modes + Name => 'SonyExposureTime2', + Format => 'int16u', + ValueConv => '$val ? 2 ** (16 - $val/256) : 0', + ValueConvInv => '$val ? int((16 - log($val) / log(2)) * 256 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x0010 => { + Name => 'ExposureTime', + Format => 'rational32u', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', # (Bulb NC) + PrintConvInv => 'lc($val) eq "bulb" ? 0 : $val', + }, + 0x0014 => { # but often odd results for DSC models: exclude + # also often deviating results for Sony FE 90mm F2.8 Macro G OSS ... + Name => 'SonyFNumber', + Format => 'int16u', + Condition => '$$self{Model} !~ /^(DSC-|ZV-)/', + ValueConv => '2 ** (($val/256 - 16) / 2)', + ValueConvInv => '(log($val)*2/log(2)+16)*256', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x0016 => { + Name => 'SonyMaxApertureValue', # (at current focal length) + Format => 'int16u', + ValueConv => '2 ** (($val/256 - 16) / 2)', + ValueConvInv => '(log($val)*2/log(2)+16)*256', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x0024 => { %sequenceImageNumber }, + 0x0034 => { %releaseMode2 }, + # 0x003e and 0x0040: maximum image size; ShotInfo gives actual image size + 0x003e => { Name => 'SonyImageWidthMax', Format => 'int16u' }, + 0x0040 => { Name => 'SonyImageHeightMax', Format => 'int16u' }, + 0x0042 => { + Name => 'HighISONoiseReduction', + PrintConv => { + 0 => 'Off', + 1 => 'Low', + 2 => 'Normal', + 3 => 'High', + }, + }, + 0x0044 => { + Name => 'LongExposureNoiseReduction', + PrintConv => { + 0 => 'Off', + 1 => 'On', # (unused or dark subject) + }, + }, + 0x0046 => { %pictureEffect2010 }, + 0x0048 => { %exposureProgram2010 }, + 0x004a => { + Name => 'CreativeStyle', + PrintConv => { + 0 => 'Standard', + 1 => 'Vivid', + 2 => 'Neutral', + 3 => 'Portrait', + 4 => 'Landscape', + 5 => 'B&W', + 6 => 'Clear', + 7 => 'Deep', + 8 => 'Light', + 9 => 'Sunset', + 10 => 'Night View/Portrait', + 11 => 'Autumn Leaves', + 13 => 'Sepia', + 15 => 'FL', + 16 => 'VV2', + 17 => 'IN', + 18 => 'SH', + 255 => 'Off', + }, + }, + 0x0052 => { + Name => 'Sharpness', + Format => 'int8s', + PrintConv => '$val > 0 ? "+$val" : $val', + PrintConvInv => '$val', + }, + 0x005a => { + Name => 'DistortionCorrParamsPresent', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { 0 => 'No', 1 => 'Yes'}, + }, + 0x005b => { + Name => 'DistortionCorrection', + PrintConv => { + 0 => 'None', + 1 => 'Applied', + }, + }, + # 0x005c - same as 0x005e, but has value 3 for SAL18135, SAL50F14Z, SAL55300, SAL70200G2, SAL70300G2, SAL70400G2 + 0x005d => { + Name => 'LensFormat', + Condition => '$$self{Model} !~ /^DSC-/', + PrintConv => { + 0 => 'Unknown', + 1 => 'APS-C', + 2 => 'Full-frame', + }, + }, + 0x005e => { + Name => 'LensMount', + DataMember => 'LensMount', + RawConv => '$$self{LensMount} = $val; $$self{Model} =~ /^DSC-/ ? undef : $val', + PrintConv => { + 0 => 'Unknown', + 1 => 'A-mount', + 2 => 'E-mount', + }, + }, + 0x0060 => { + Name => 'LensType2', + Condition => '$$self{LensMount} == 2', + Format => 'int16u', + Notes => 'E-mount lenses', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x0062 => { + Name => 'LensType', + Condition => '$$self{LensMount} == 1', + Format => 'int16u', + Notes => 'A-mount lenses on SLT and NEX', + SeparateTable => 1, + PrintConv => \%sonyLensTypes, + PrintInt => 1, + }, + 0x0064 => { + Name => 'DistortionCorrParams', + Condition => '$$self{Model} !~ /^DSC-/', + Format => 'int16s[16]', + }, + 0x0342 => { + Name => 'LensZoomPosition', + Condition => '$$self{Model} !~ /^(ILCA-|ILCE-(7RM2|7M3|7RM3A?|7RM4A?|7SM2|6100|6300|6400|6500|6600|7C|9|9M2)|DSC-(HX80|HX90V|HX99|RX0|RX10M2|RX10M3|RX10M4|RX100M4|RX100M5|RX100M5A|RX100M6|RX100M7|WX500)|ZV-)/', + Format => 'int16u', + PrintConv => 'sprintf("%.0f%%",$val/10.24)', + PrintConvInv => '$val=~s/ ?%$//; $val * 10.24', + }, + 0x034a => { + Name => 'VignettingCorrParams', + Condition => '$$self{Model} =~ /^(ILCA-(68|77M2)|ILCE-(5000|5100|6000|7|7R|7S|QX1)|Lusso)\b/', + Format => 'int16s[16]', + }, + 0x034e => { + Name => 'LensZoomPosition', + Condition => '$$self{Model} =~ /^(DSC-(RX100M5|RX100M5A|RX100M6|RX100M7|RX10M4|HX99)|ILCE-(6100|6400|6600|7C|7M3|7RM3A?|7RM4A?|9M2)|ZV-E10)/', + Format => 'int16u', + PrintConv => 'sprintf("%.0f%%",$val/10.24)', + PrintConvInv => '$val=~s/ ?%$//; $val * 10.24', + }, + 0x0350 => { + Name => 'VignettingCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-7M2)/', + Format => 'int16s[16]', + }, + 0x035c => { + Name => 'VignettingCorrParams', + Condition => '$$self{Model} =~ /^(ILCA-99M2|ILCE-(6100|6400|6500|6600|7C|7M3|7RM3A?|7RM4A?|9|9M2)|ZV-E10)/', + Format => 'int16s[16]', + }, + 0x035a => { + Name => 'LensZoomPosition', + Condition => '$$self{Model} =~ /^(ILCE-(7RM2|7SM2)|DSC-(HX80|HX90V|RX10M2|RX10M3|RX100M4|WX500))/', + Format => 'int16u', + PrintConv => 'sprintf("%.0f%%",$val/10.24)', + PrintConvInv => '$val=~s/ ?%$//; $val * 10.24', + }, + 0x0368 => { + Name => 'VignettingCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-(6300|7RM2|7SM2))/', + Format => 'int16s[16]', + }, + 0x037c => { + Name => 'ChromaticAberrationCorrParams', + Condition => '$$self{Model} =~ /^(ILCA-(68|77M2)|ILCE-(5000|5100|6000|7|7R|7S|QX1)|Lusso)\b/', + Format => 'int16s[32]', + }, + 0x0384 => { + Name => 'ChromaticAberrationCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-7M2)/', + Format => 'int16s[32]', + }, + 0x039c => { + Name => 'ChromaticAberrationCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-(6300|7RM2|7SM2))/', + Format => 'int16s[32]', + }, + 0x03b0 => { + Name => 'ChromaticAberrationCorrParams', + Condition => '$$self{Model} =~ /^(ILCA-99M2|ILCE-6500)/', + Format => 'int16s[32]', + }, + 0x03b8 => { + Name => 'ChromaticAberrationCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-(6100|6400|6600|7C|7M3|7RM3A?|7RM4A?|9|9M2)|ZV-E10)/', + Format => 'int16s[32]', + }, +); + +# Tag9406 (ref JR) +%Image::ExifTool::Sony::Tag9406 = ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + DATAMEMBER => [ 0 ], + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, +# 0x0000: 1 for SLT-A37/A57/A65/A77, NEX-5N/7/F3/VG20 +# 2 for SLT-A58/99V, NEX-3N/5R/5T/6/VG30/VG900, ILCA-68/77M2, ILCE-3000/3500/7/7M2/7R/7S/5000/6000 +# 3 for ILCA-99M2, ILCE-6100/6300/6400/6500/6600/7M3/7RM2/7RM3/7RM4/7SM2/9/9M2 +# 4 for ILCE-6700, ZV-E1 + 0x0000 => { Name => 'Ver9406', Hidden => 1, RawConv => '$$self{Ver9406} = $val; $$self{OPTIONS}{Unknown}<2 ? undef : $val' }, +# 0x0001+0x0002: Int16u, seen 580 - 770: similar to "BatteryUnknown" ?? +# 0x0005: int8u, seen 73 - 117: maybe Fahrenheit? Higher than "AmbientTemperature", but same trend. + 0x0005 => [{ + Name => 'BatteryTemperature', + Condition => '$$self{Ver9406} != 4', + ValueConv => '($val - 32) / 1.8', # convert to Celsius + ValueConvInv => '$val * 1.8 + 32', + PrintConv => 'sprintf("%.1f C",$val)', + PrintConvInv => '$val=~s/\s*C//; $val', + },{ + Name => 'BatteryLevel', + Condition => '$$self{Ver9406} == 4', + RawConv => '$val ? $val : undef', # only valid when not 0 + PrintConv => '"$val%"', + PrintConvInv => '$val=~s/\s*\%//; $val', + }], + # 0x0006: usually 0, seen non-zero values only for SLT-A99V, ILCA-77M2/99M2 and ILCE-7/7R/7RM2/9: BatteryLevel Grip ? + 0x0006 => { + Name => 'BatteryLevelGrip1', + RawConv => '$val ? $val : undef', # only valid when not 0 + PrintConv => '"$val%"', + PrintConvInv => '$val=~s/\s*\%//; $val', + }, + # 0x0007: seen values from 8 - 105, decreasing in sequences of images: BatteryLevel + 0x0007 => { + Name => 'BatteryLevel', + Condition => '$$self{Ver9406} != 4', + PrintConv => '"$val%"', + PrintConvInv => '$val=~s/\s*\%//; $val', + }, + # 0x0008: usually 255 or 0 (ILCE-7/7R), seen other values only for A99V and ILCE-7/7R when 0x0006 not 0. + # A99V with grip can have 3 batteries: => Grip 2; + # but ILCE-7/7R with grip can have max 2, and as all ILCE-7/7R samples give >100 values, exclude... + 0x0008 => { + Name => 'BatteryLevelGrip2', + Condition => '$$self{Model} !~ /^(ILCE-(7|7R)|Lusso)$/', # not valid for ILCE-7/7R + RawConv => '($val and $val != 255) ? $val : undef', # not valid if 0 or 255 + PrintConv => '"$val%"', + PrintConvInv => '$val=~s/\s*\%//; $val', + }, +# 0x0009-0x001a: looks like 9 Int16u values +# 0x0022: 0 or 1 for A99, NEX-5R, 6 +# 0x0025: 0 or 1 for other SLT and NEX (0x0022, 0x0023, 0x0024 = 255) +); + +# Tag940a (ref PH, decoded mainly from A77) +%Image::ExifTool::Sony::Tag940a = ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'These tags are currently extracted for SLT models only.', + # 0x00 - 10(A65,A77,NEX-5N,7,VG20E), 11(A37,A57,A99,NEX-5R,6,F3,RX1,RX100), + # 9(HX9V), 4,68,86,110(panoramas) - ref JR + 0x04 => { + Name => 'AFPointsSelected', + Format => 'int32u', + PrintConvColumns => 2, + PrintConv => { + # verified for A77 firmware 1.03 and 1.07 and A99 firmware 1.00, + # but there were inconsistencies with my A77 firmware 1.04 samples - PH + 0 => '(none)', # ILCA-68/77M2/99M2 always give this + 0x00007801 => 'Center Zone', + 0x0001821c => 'Right Zone', + 0x000605c0 => 'Left Zone', + 0x0003ffff => '(all LA-EA4)', # for LA-EA4: 18 bits + 0x7fffffff => '(all)', # also for LA-EA2 + 0xffffffff => 'n/a', # DSC and ILCE/NEX models always give this, except when using LA-EA2 or LA-EA4 + # (on Wide AFAreaMode, outer focus points are dropped + # at progressively higher digital zoom ratios, ref JR) + BITMASK => { + 0 => 'Center', # (1.04 gave this for Upper-middle and Near Left) + 1 => 'Top', # (1.04 didn't give this value) + 2 => 'Upper-right', # (1.04 OK) + 3 => 'Right', # (1.04 didn't give this value) + 4 => 'Lower-right', # (1.04 gave this for Bottom) + 5 => 'Bottom', # (1.04 gave this for Lower-middle) + 6 => 'Lower-left', # (1.04 gave this for Left and Lower Far Left) + 7 => 'Left', # (1.04 gave this for Far Left) + 8 => 'Upper-left', # (1.04 OK) + 9 => 'Far Right', # (1.04 gave this for Upper Far Right and Right) + 10 => 'Far Left', # (1.04 didn't give this value) + 11 => 'Upper-middle', # (1.04 gave this for Top) + 12 => 'Near Right', # (1.04 gave this for Center) + 13 => 'Lower-middle', # (1.04 gave this for Lower-left and Near Right) + 14 => 'Near Left', # (1.04 didn't give this value) + 15 => 'Upper Far Right',# (1.04 didn't give this value) + 16 => 'Lower Far Right',# (1.04 OK, but gave this for Far Right and Lower-right too) + 17 => 'Lower Far Left', # (1.04 didn't give this value) + 18 => 'Upper Far Left', # (1.04 OK) + # higher bits may be used in panorama images - ref JR + }, + }, + }, + # 0x0a - int16u: 0,1,2,3 + # 0xa6 - 8 bytes face detection info ?; starts with 1, otherwise all 0 +); + +# Tag940c (ref JR) +%Image::ExifTool::Sony::Tag940c = ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + DATAMEMBER => [ 0x0008 ], + NOTES => 'E-mount cameras only.', + + # 0x0001 - 0 for all NEX and ILCE-3000/3500, 20 for all other ILCE (17 for ILCE samples from Sony.net) + # 0x0008 - LensMount, but different values from Tag9405-0x0105 and Tag9050-0x0604. + # don't know what difference is between values '1' and '5' ... + 0x0008 => { + Name => 'LensMount2', # ? maybe some other meaning ? (A-mount adapter-only images give 0) + RawConv => '$$self{LensMount} = $val', + PrintConv => { + 0 => 'Unknown', # LA-EA3 with non-SSM/SAM lens will often give this, not 1 or 5 + 1 => 'A-mount (1)', + 4 => 'E-mount', + 5 => 'A-mount (5)', + }, + }, + # 0x0009 - LensType3: + # This tag appears to also indicate adapter info, similar to CameraSettings3-0x03f7 for the original NEX-3/5. + # (Tag9405-0x0605 and Tag9050-0x0107 LensType2 always give '0' for adapters/A-mount lenses.) + # - seen a few instances of 0x0009 indicating an E-mount lens, but 0xb027 LensType indicating an A-mount lens: + # possibly due to adapter info not being read/reset correctly ? + # August 2015: renamed from LensType2 into Lenstype3 because too often info here relates to previously-mounted-lens + 0x0009 => { + Name => 'LensType3', + RawConv => '(($$self{LensMount} != 0) or ($val > 0 and $val < 32784)) ? $val : undef', + Format => 'int16u', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x000b => { + Name => 'CameraE-mountVersion', + Format => 'int16u', + PrintConv => 'sprintf("%x.%.2x",$val>>8,$val&0xff)', + PrintConvInv => 'my @a=split(/\./,$val);(hex($a[0])<<8)|hex($a[1])', + # E-mount versions seen for various cameras / camera-firmware versions: + # - : info not present in CameraSettings3 for NEX-3/5/5C/C3/VG10E + # 1.14: NEX-5N/5R/6/7/F3/VG20E/VG30E/VG900 v1.00, NEX-5N v1.01, NEX-3N v0.90 + # 1.20: NEX-3N v1.00, NEX-6 v1.01, NEX-7 v1.02, ILCE-3000 v1.00, ILCE-3500 v1.01 + # 1.30: NEX-5T v1.00, NEX-6 v1.02/v1.03, NEX-7 v1.03 + # 1.31: ILCE-7/7R v0.95/v1.00/v1.01, ILCE-5000 + # 1.40: ILCE-7/7R v1.02/v1.10, ILCE-7S v1.00, ILCE-6000 v1.00/v1.10, ILCE-5100/QX1 + # 1.50: ILCE-7/7R/7S v1.20-v3.20, ILCE-7M2, ILCE-7RM2 v1.00-v3.00, ILCE-7SM2 v1.00-v2.20, + # ILCE-6000 v1.20-v3.20 + # 1.60: ILCE-6300/6500, ILCE-7RM2 v3.05-v4.00 + # 1.70: ILCE-7M3/7RM3, ILCE-9 v1.00-v4.10 + # 1.80: ILCE-6100/6400/6600/7RM4/9M2, ILCE-9 v5.00-v6.00 + }, + 0x000d => { + Name => 'LensE-mountVersion', + Condition => '$$self{LensMount} != 0', + Format => 'int16u', + PrintConv => 'sprintf("%x.%.2x",$val>>8,$val&0xff)', + PrintConvInv => 'my @a=split(/\./,$val);(hex($a[0])<<8)|hex($a[1])', + # E-mount versions seen for various lens models: + # 0.00: Unknown lenses/adapters + # 1.00: SEL18200LE, Sigma DN, Tamron Di III, Zeiss Touit + # 1.07: (Ver.01) original E-lenses (SEL16F28, SEL18200, SEL1855, SEL24F18Z, SEL30M35, SEL50F18, SEL55210) and LA-EA1 + # 1.08: LA-EA1 (Ver.02), Metabones Smart + # 1.14: LA-EA2 + # 1.20: (Ver.02) firmware-updated E-lenses (SEL1855, SEL24F18Z, SEL30M35, SEL50F18, SEL55210), + # newer E-lenses (SEL1018, SEL1670Z, SEL20F28, SEL35F18, SELP1650, SELP18105G, SELP18200) or LA-EA3 Ver.01 + # 1.30: LA-EA4 + # 1.31: original FE-lenses (SEL2470Z, SEL2870, SEL35F28Z, SEL55F18Z), SEL1850 + # 1.35: SEL70200G, SEL55210 (Black?, seen with ILCE-3500) + # 1.40: SEL1635Z, SEL24240, SEL35F14Z, SELP28135G, Zeiss Loxia 35mm/50mm Ver.01, Zeiss Touit Ver.02 + # 1.41: SELP18105G Ver.02 + # 1.50: SEL28F20, SEL90M28G, Zeiss Batis 18mm/25mm/85mm/135mm, Zeiss Loxia 21mm, Zeiss Loxia 35mm/50mm Ver.02, + # Tokina FiRIN 20mm + # 1.60: SEL1224G, SEL1635GM, SEL1655G, SELP18110G, SEL18135, SEL2470GM, SEL24105G, SEL35F18F, SEL50F14Z, SEL50F18F, + # SEL50M28, SEL70200GM, SEL70300G, SEL70350G, SEL85F14GM, SEL85F18, SEL100F28GM, SEL100400GM, SEL135F18GM, + # SEL200600G, SEL600F40GM, Sigma 16F14DCDN/30F14DCDN/35F12DGDN/45F28DGDN, Sigma MC-11, Samyang AF 14mm/50mm, + # Voigtlander 15mm, Viltrox 85mm MF + # 1.70: LA-EA3 Ver.02, Samyang AF 24mm/35mm/85mm, Tamron 17-28mm, 28-75mm, Tokina FiRIN 20mm AF Ver.01, Tokina FiRIN 100mm Macro, + # Voigtlander 10mm/12mm/40mm/65mm, Zeiss Loxia 25mm/85mm, Sigma 14-24mm + # 1.80: Voigtlander 21mm + }, + # 0x0014 and 0x0015: change together: LensFirmwareVersion + # 0x0015 as 2-digit hex matches known firmware versions of Sony lenses and Metabones adapters, + # 0x0014 as 3-digit decimal: not confirmed sub-versions + # Some versions as seen with this decoding: + # 00.nnn for several pre-production Sony E-mount lenses + # 01.000 - 01.009 for various Sony E-mount lenses + # 02.nnn, 03.nnn for various Sony E-mount lenses for which a Ver.02 or Ver.03 update is available + # 16.001 for Metabones Speed Booster + # 19/22/24/30/32/41.001 etc. for Metabones Canon EF Smart adapters + 0x0014 => { + Name => 'LensFirmwareVersion', + Condition => '$$self{LensMount} != 0', + Format => 'int16u', + PrintConv => 'sprintf("Ver.%.2x.%.3d",$val>>8,$val&0xff)', + }, + # 0x0016 - 0x003f: non-0 data present when: 0x0001>0 AND 0x0008=4(E-mount) AND 0x000f<255 +); + + +# AFInfo 0x940e (SLT models only) (ref PH, decoded mainly from A77) +%Image::ExifTool::Sony::AFInfo = ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + WRITABLE => 1, + FIRST_ENTRY => 0, + PRIORITY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + DATAMEMBER => [ 0x02 ], + IS_SUBDIR => [ 0x11, 0x7d ], + NOTES => 'These tags are currently extracted for SLT models only.', + # first 4 bytes (deciphered) (ref JR): + # 0 1 1 0 for A37, A57, A58 + # 2 1 1 0 for A65V + # 2 1 2 0 for A77V + # 0 1 2 0 for A99V + # 1 1 3 0 for ILCA-68/77M2/99M2 + # 0 0 0 0 for NEX and ILCE-3000/3500, also seen for SLT/ILCA with non-AF lens + # 1 0 0 0 for ILCE-5000/5100/6000/7/7M2/7R/7S/QX1 + # 6 0 0 0 for ILCE-6100/6300/6400/6500/6600/7C/7M3/7RM2/7RM3/7RM4/7SM2/9/9M2 + # 9 . . . for ILCE-7SM3 + # 11 . . . for ILCE-1 + # 0 2 0 0 for NEX/ILCE with LA-EA2/EA4 Phase-AF adapter + # 2 0 0 0 seen for a few NEX-5N images + # 2 2 0 0 seen for a few NEX-5N/7 images with LA-EA2 adapter + 0x02 => { + Name => 'AFType', + RawConv => '$$self{AFType} = $val', + PrintConv => { + # 0 => '?? n.a.', # seen on some A99V images with non-AF (Samyang) lens + 1 => '15-point', + 2 => '19-point', + 3 => '79-point', # ILCA-68/77M2/99M2 + }, + }, + +### decoding for SLT; ILCA-68/77M2/99M2 (AFType == 3) uses different offsets: see below + + 0x04 => { + Name => 'AFStatusActiveSensor', + Condition => '$$self{Model} !~ /^ILCA-/', + %Image::ExifTool::Minolta::afStatusInfo, + }, + 0x07 => [ # the active AF sensor + { + Name => 'AFPoint', + Condition => '$$self{AFType} == 1', + Notes => 'models with 15-point AF', + PrintConvColumns => 2, + PrintConv => \%afPoint15, + },{ + Name => 'AFPoint', + Condition => '$$self{AFType} == 2', + Notes => 'models with 19-point AF', + PrintConvColumns => 2, + PrintConv => \%afPoint19, + }, + ], + 0x08 => [ # the AF sensor in focus at focus time (shutter release half press) + { + Name => 'AFPointInFocus', + Condition => '$$self{AFType} == 1', + Notes => 'models with 15-point AF', + PrintConvColumns => 2, + PrintConv => { + %afPoint15, + 255 => '(none)', + }, + },{ + Name => 'AFPointInFocus', + Condition => '$$self{AFType} == 2', + Notes => 'models with 19-point AF', + PrintConvColumns => 2, + PrintConv => { + %afPoint19, + 255 => '(none)', + }, + }, + ], + 0x09 => [ # the AF sensor in focus at shutter release (shutter release full press) + { + Name => 'AFPointAtShutterRelease', + Condition => '$$self{AFType} == 1', + Notes => 'models with 15-point AF', + PrintConvColumns => 2, + PrintConv => { + %afPoint15, + 30 => '(out of focus)', + }, + },{ + Name => 'AFPointAtShutterRelease', + Condition => '$$self{AFType} == 2', + Notes => 'models with 19-point AF', + PrintConvColumns => 2, + PrintConv => { + %afPoint19, + 30 => '(out of focus)', + }, + }, + ], + 0x0a => { + Name => 'AFAreaMode', + Condition => '$$self{Model} !~ /^ILCA-/', + PrintConv => { + 0 => 'Wide', + 1 => 'Spot', + 2 => 'Local', + 3 => 'Zone', + }, + }, + 0x0b => { + Name => 'FocusMode', + Condition => '$$self{Model} !~ /^ILCA-/', + PrintConvColumns => 2, + # validated for A77 firmware 1.03, 1.04 and 1.07 and A99 + # - not confirmed for A37,A57 and A65 which also write this tag + PrintConv => { + 0 => 'Manual', + 2 => 'AF-S', + 3 => 'AF-C', + 4 => 'AF-A', + 6 => 'DMF', + 7 => 'AF-D', # (unique to A99) + }, + }, + 0x11 => [ #JR + { + Name => 'AFStatus15', + Condition => '$$self{AFType} == 1', + Format => 'int16s[18]', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::AFStatus15' }, + },{ + Name => 'AFStatus19', + Condition => '$$self{AFType} == 2', + Format => 'int16s[30]', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::AFStatus19' }, + }, + ], + # 0x004d - 18 or 30 int16 values + # 0x0089 - 18 or 30 int16 values + # 0x00b1 - 18 or 30 int16 values + # 0x0121 - 18 or 30 int16s values, similar to 0x11 AFStatus + # 0x016e - SLT: 4 bytes indicating 'AFPointsUsed', identical to first 4 bytes of 0x2020 for A58/A99V + 0x016e => { + Name => 'AFPointsUsed', + Condition => '$$self{Model} !~ /^ILCA-/', + Notes => 'SLT models only', + Format => 'int32u', + PrintConvColumns => 2, + PrintConv => { + 0 => '(none)', + BITMASK => { + 0 => 'Center', + 1 => 'Top', + 2 => 'Upper-right', + 3 => 'Right', + 4 => 'Lower-right', + 5 => 'Bottom', + 6 => 'Lower-left', + 7 => 'Left', + 8 => 'Upper-left', + 9 => 'Far Right', + 10 => 'Far Left', + 11 => 'Upper-middle', + 12 => 'Near Right', + 13 => 'Lower-middle', + 14 => 'Near Left', + 15 => 'Upper Far Right', + 16 => 'Lower Far Right', + 17 => 'Lower Far Left', + 18 => 'Upper Far Left', + }, + }, + }, + # 0x017b: int16s: also has to do with AFMicroAdj: value equals -AFMA * 4 * MaxApertureValue (ref JR) + 0x017d => { #PH (verified for the SLT-A77/A99; other SLT models don't have this setting - ref JR) + # (different from AFMicroAdjValue because it is 0 when the adjustment is off) + Name => 'AFMicroAdj', + Condition => '$$self{Model} !~ /^ILCA-/', + Format => 'int8s', + }, + 0x017e => { #JR + Name => 'ExposureProgram', + Condition => '$$self{Model} !~ /^ILCA-/', + Priority => 0, + SeparateTable => 'ExposureProgram3', + PrintConv => \%sonyExposureProgram3, + }, + # 0x01b8 - 65 AF Info blocks of 180 bytes each for SLT (ref JR) + # In each block, the 9th, 10th and 11th byte appear to relate to AFPoint as at offsets 0x07, 0x08, 0x09 above.. + # Possibly, these blocks relate to sequential focusing attempts and/or object tracking, + # the first byte being an Index or Counter. + # The last block before the block with index 0, appears to relate to the AF data at ShutterRelease. + + # 0xf38,0x1208,0x14d8,0x158c,0x1640,(and more) - 0 if AFMicroAdj is On, 1 if Off + # 0x1ab6 - 0x80 if AFMicroAdj is On, 0 if Off + # tags also related to AFPoint (PH, A77): + # 0x11ec, 0x122a, 0x1408, 0x1446, 0x14bc, 0x1f86, + # 0x14fa, 0x1570, 0x1572, 0x15ae, 0x1f48 + +### decoding for ILCA-68/77M2/99M2, AFType == 3 + + 0x0005 => { #JR + Name => 'FocusMode', + Condition => '$$self{Model} =~ /^ILCA-/', + Notes => 'ILCA models only', + Writable => 'int8u', + Priority => 0, + PrintConv => { + 0 => 'Manual', + 2 => 'AF-S', + 3 => 'AF-C', + 4 => 'AF-A', + 6 => 'DMF', + # 7 => 'AF-D', # not yet seen + }, + }, + # 0x0010 - for ILCA-68/77M2/99M2: 10 bytes identical to 0x2020, and probably indicating 'AFPointsUsed' (ref JR) + 0x0010 => { + Name => 'AFPointsUsed', + Condition => '$$self{Model} =~ /^ILCA-/', + Format => 'int8u[10]', + BitsPerWord => 8, + BitsTotal => 80, + PrintConv => { + 0 => '(none)', + BITMASK => { %afPoints79 }, + }, + }, + # 0x0037, 0x0038, 0x0039 similar to 0x07, 0x08, 0x09, but using numbers from 0-94 for ILCA-68/77M2/99M2 + 0x0037 => { # the active AF sensor + Name => 'AFPoint', + Condition => '$$self{AFType} == 3', + PrintConv => { + %afPoints79_940e, + 255 => '(none)', + }, + }, + 0x0038 => { # the AF sensor in focus at focus time (shutter release half press) + Name => 'AFPointInFocus', + Condition => '$$self{AFType} == 3', + PrintConv => { + %afPoints79_940e, + 255 => '(none)', + }, + }, + 0x0039 => { # the AF sensor in focus at shutter release (shutter release full press) + Name => 'AFPointAtShutterRelease', + Condition => '$$self{AFType} == 3', + PrintConv => { + %afPoints79_940e, + 95 => '(none)', + }, + }, + 0x003a => { #JR + Name => 'AFAreaMode', + Condition => '$$self{Model} =~ /^ILCA-/', + PrintConv => { + 0 => 'Wide', + 1 => 'Center', + 2 => 'Flexible Spot', + 3 => 'Zone', + 4 => 'Expanded Flexible Spot', #(NC) + }, + }, + 0x003b => { + Name => 'AFStatusActiveSensor', + Condition => '$$self{Model} =~ /^ILCA-/', + %Image::ExifTool::Minolta::afStatusInfo, + }, + 0x0043 => { + Name => 'ExposureProgram', + Condition => '$$self{Model} =~ /^ILCA-/', + Priority => 0, + SeparateTable => 'ExposureProgram3', + PrintConv => \%sonyExposureProgram3, + }, + # 0x004e: int16s: also has to do with AFMicroAdj: value equals -AFMA * 4 * MaxApertureValue (ref JR) + 0x0050 => { #PH (ILCA-A77M2, to be confirmed for other ILCA models) + Name => 'AFMicroAdj', + Condition => '$$self{Model} =~ /^ILCA-/', + Format => 'int8s', + }, + # 0x007d - AFStatus79 - 95 int16s values for the ILCA-68/77M2/99M2: 79 AF points + 15 cross + 1 F2.8 + 0x007d => { + Name => 'AFStatus79', + Condition => '$$self{AFType} == 3', + Format => 'int16s[95]', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::AFStatus79' }, + }, + # 0x013b - 95 int8u values + # 0x01ab - 95 int8u values + # 0x021b - 95 int16s values, similar to 0x007d AFStatus79, but not sure if this is valid for ILCA-99M2 + # 0x04c0 - 45 AF Info blocks of 244 bytes each for ILCA-68/77M2 + # 0x12a0 - 30 AF Info blocks of 244 bytes each for ILCA-99M2 +); + +%Image::ExifTool::Sony::Tag940e = ( #JR + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => 'E-mount models.', + + # (see comment in AFInfo for deciphered values of first 4 bytes for various models) + + # 0x0004 - if 0x0001 == 2: LA-EA2/EA4 15-point SLT Phase-detect AF adapter used: + # start of 164-byte AF Info Blocks, possibly the 11th byte might be the AFPoint. + # ILCE-7M2/7RM2/7SM2: 40 Blocks of 164 bytes + # other NEX/ILCE: 74 blocks of 164 bytes + + # 0x1a06 onwards - first seen for ILCE-7RM2: appears to be some kind of metering image + # but not valid anymore for ILCE-6400, ILCE-9 v5.0x + 0x1a06 => { Name => 'TiffMeteringImageWidth', Condition => '$$self{Model} =~ /^(ILCE-(6300|6500|7M3|7RM2|7RM3A?|7SM2|9))\b/ and $$self{Software} !~ /^ILCE-9 (v5.0|v6.0)/' }, + 0x1a07 => { Name => 'TiffMeteringImageHeight', Condition => '$$self{Model} =~ /^(ILCE-(6300|6500|7M3|7RM2|7RM3A?|7SM2|9))\b/ and $$self{Software} !~ /^ILCE-9 (v5.0|v6.0)/' }, + 0x1a08 => { # (2640 bytes: 1 set of 44x30 int16u values) + Name => 'TiffMeteringImage', + Condition => '$$self{Model} =~ /^(ILCE-(6300|6500|7M3|7RM2|7RM3A?|7SM2|9))\b/ and $$self{Software} !~ /^ILCE-9 (v5.0|v6.0)/', + Format => 'undef[2640]', + Notes => q{ + 13(?)-bit intensity data from 1320 (1200) metering segments, extracted as a + 16-bit TIFF image + }, + ValueConv => sub { + my ($val, $et) = @_; + return undef unless length $val >= 2640; + return \ "Binary data 2640 bytes" unless $et->Options('Binary'); + my @dat = unpack('v*', $val); + # TIFF header for a 16-bit RGB 10dpi 44x30 image + $val = Image::ExifTool::MakeTiffHeader(44,30,3,16,10); + # re-order data to RGB pixels - use same value for R, G and B + my ($i, @val); + for ($i=0; $i<44*30; ++$i) { + # data is 13-bit (max 8191), shift left to fill 16 bits + push @val, int(5041.1*log($dat[$i]+1)/log(2)), int(5041.1*log($dat[$i]+1)/log(2)), int(5041.1*log($dat[$i]+1)/log(2)); + } + $val .= pack('v*', @val); # add TIFF strip data + return \$val; + }, + }, +); + +# AF Point Status (ref JR) +%Image::ExifTool::Sony::AFStatus15 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'AF Status information for models with 15-point AF.', + 0x00 => { Name => 'AFStatusUpper-left', %Image::ExifTool::Minolta::afStatusInfo }, + 0x02 => { Name => 'AFStatusLeft', %Image::ExifTool::Minolta::afStatusInfo }, + 0x04 => { Name => 'AFStatusLower-left', %Image::ExifTool::Minolta::afStatusInfo }, + 0x06 => { Name => 'AFStatusFarLeft', %Image::ExifTool::Minolta::afStatusInfo }, + 0x08 => { Name => 'AFStatusTopHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0a => { Name => 'AFStatusNearRight', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0c => { Name => 'AFStatusCenterHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0e => { Name => 'AFStatusNearLeft', %Image::ExifTool::Minolta::afStatusInfo }, + 0x10 => { Name => 'AFStatusBottomHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x12 => { Name => 'AFStatusTopVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x14 => { Name => 'AFStatusCenterVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x16 => { Name => 'AFStatusBottomVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x18 => { Name => 'AFStatusFarRight', %Image::ExifTool::Minolta::afStatusInfo }, + 0x1a => { Name => 'AFStatusUpper-right', %Image::ExifTool::Minolta::afStatusInfo }, + 0x1c => { Name => 'AFStatusRight', %Image::ExifTool::Minolta::afStatusInfo }, + 0x1e => { Name => 'AFStatusLower-right', %Image::ExifTool::Minolta::afStatusInfo }, + 0x20 => { Name => 'AFStatusUpper-middle', %Image::ExifTool::Minolta::afStatusInfo }, + 0x22 => { Name => 'AFStatusLower-middle', %Image::ExifTool::Minolta::afStatusInfo }, +); + +# AF Point Status (ref JR) +%Image::ExifTool::Sony::AFStatus19 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'AF Status information for models with 19-point AF.', + 0x00 => { Name => 'AFStatusUpperFarLeft', %Image::ExifTool::Minolta::afStatusInfo }, + 0x02 => { Name => 'AFStatusUpper-leftHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x04 => { Name => 'AFStatusFarLeftHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x06 => { Name => 'AFStatusLeftHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x08 => { Name => 'AFStatusLowerFarLeft', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0a => { Name => 'AFStatusLower-leftHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0c => { Name => 'AFStatusUpper-leftVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0e => { Name => 'AFStatusLeftVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x10 => { Name => 'AFStatusLower-leftVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x12 => { Name => 'AFStatusFarLeftVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x14 => { Name => 'AFStatusTopHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x16 => { Name => 'AFStatusNearRight', %Image::ExifTool::Minolta::afStatusInfo }, + 0x18 => { Name => 'AFStatusCenterHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x1a => { Name => 'AFStatusNearLeft', %Image::ExifTool::Minolta::afStatusInfo }, + 0x1c => { Name => 'AFStatusBottomHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x1e => { Name => 'AFStatusTopVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x20 => { Name => 'AFStatusUpper-middle', %Image::ExifTool::Minolta::afStatusInfo }, + 0x22 => { Name => 'AFStatusCenterVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x24 => { Name => 'AFStatusLower-middle', %Image::ExifTool::Minolta::afStatusInfo }, + 0x26 => { Name => 'AFStatusBottomVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x28 => { Name => 'AFStatusUpperFarRight', %Image::ExifTool::Minolta::afStatusInfo }, + 0x2a => { Name => 'AFStatusUpper-rightHorizontal',%Image::ExifTool::Minolta::afStatusInfo }, + 0x2c => { Name => 'AFStatusFarRightHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x2e => { Name => 'AFStatusRightHorizontal', %Image::ExifTool::Minolta::afStatusInfo }, + 0x30 => { Name => 'AFStatusLowerFarRight', %Image::ExifTool::Minolta::afStatusInfo }, + 0x32 => { Name => 'AFStatusLower-rightHorizontal',%Image::ExifTool::Minolta::afStatusInfo }, + 0x34 => { Name => 'AFStatusFarRightVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x36 => { Name => 'AFStatusUpper-rightVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x38 => { Name => 'AFStatusRightVertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x3a => { Name => 'AFStatusLower-rightVertical', %Image::ExifTool::Minolta::afStatusInfo }, +); + +# AF Point Status (ref JR) +%Image::ExifTool::Sony::AFStatus79 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => 'AF Status information for models with 79-point AF.', +# +# ILCA-68/77M2 AF sensor layout: +# A5* A6* A7* +# B2 B3 B4 B5 B6 B7 B8 B9 B10 +# C1 C2 C3 C4 C5* C6* C7* C8 C9 C10 C11 +# D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 +# E1 E2 E3 E4 E5* E6* E7* E8 E9 E10 E11 +# F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 +# G1 G2 G3 G4 G5* G6* G7* G8 G9 G10 G11 +# H2 H3 H4 H5 H6 H7 H8 H9 H10 +# I5* I6* I7* +# left section, from top to bottom, from right to left + 0x00 => { Name => 'AFStatus_00_B4', %Image::ExifTool::Minolta::afStatusInfo }, + 0x02 => { Name => 'AFStatus_01_C4', %Image::ExifTool::Minolta::afStatusInfo }, + 0x04 => { Name => 'AFStatus_02_D4', %Image::ExifTool::Minolta::afStatusInfo }, + 0x06 => { Name => 'AFStatus_03_E4', %Image::ExifTool::Minolta::afStatusInfo }, + 0x08 => { Name => 'AFStatus_04_F4', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0a => { Name => 'AFStatus_05_G4', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0c => { Name => 'AFStatus_06_H4', %Image::ExifTool::Minolta::afStatusInfo }, + 0x0e => { Name => 'AFStatus_07_B3', %Image::ExifTool::Minolta::afStatusInfo }, + 0x10 => { Name => 'AFStatus_08_C3', %Image::ExifTool::Minolta::afStatusInfo }, + 0x12 => { Name => 'AFStatus_09_D3', %Image::ExifTool::Minolta::afStatusInfo }, + 0x14 => { Name => 'AFStatus_10_E3', %Image::ExifTool::Minolta::afStatusInfo }, + 0x16 => { Name => 'AFStatus_11_F3', %Image::ExifTool::Minolta::afStatusInfo }, + 0x18 => { Name => 'AFStatus_12_G3', %Image::ExifTool::Minolta::afStatusInfo }, + 0x1a => { Name => 'AFStatus_13_H3', %Image::ExifTool::Minolta::afStatusInfo }, + 0x1c => { Name => 'AFStatus_14_B2', %Image::ExifTool::Minolta::afStatusInfo }, + 0x1e => { Name => 'AFStatus_15_C2', %Image::ExifTool::Minolta::afStatusInfo }, + 0x20 => { Name => 'AFStatus_16_D2', %Image::ExifTool::Minolta::afStatusInfo }, + 0x22 => { Name => 'AFStatus_17_E2', %Image::ExifTool::Minolta::afStatusInfo }, + 0x24 => { Name => 'AFStatus_18_F2', %Image::ExifTool::Minolta::afStatusInfo }, + 0x26 => { Name => 'AFStatus_19_G2', %Image::ExifTool::Minolta::afStatusInfo }, + 0x28 => { Name => 'AFStatus_20_H2', %Image::ExifTool::Minolta::afStatusInfo }, + 0x2a => { Name => 'AFStatus_21_C1', %Image::ExifTool::Minolta::afStatusInfo }, + 0x2c => { Name => 'AFStatus_22_D1', %Image::ExifTool::Minolta::afStatusInfo }, + 0x2e => { Name => 'AFStatus_23_E1', %Image::ExifTool::Minolta::afStatusInfo }, + 0x30 => { Name => 'AFStatus_24_F1', %Image::ExifTool::Minolta::afStatusInfo }, + 0x32 => { Name => 'AFStatus_25_G1', %Image::ExifTool::Minolta::afStatusInfo }, +# center section, cross-sensors *, from right to left, from top to bottom +# These are presumably Vertical, as all others are default Horizontal (ref Sony ILCA-77M2 brochure). + 0x34 => { Name => 'AFStatus_26_A7_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x36 => { Name => 'AFStatus_27_A6_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x38 => { Name => 'AFStatus_28_A5_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x3a => { Name => 'AFStatus_29_C7_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x3c => { Name => 'AFStatus_30_C6_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x3e => { Name => 'AFStatus_31_C5_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x40 => { Name => 'AFStatus_32_E7_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x42 => { Name => 'AFStatus_33_E6_Center_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x44 => { Name => 'AFStatus_34_E5_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x46 => { Name => 'AFStatus_35_G7_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x48 => { Name => 'AFStatus_36_G6_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x4a => { Name => 'AFStatus_37_G5_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x4c => { Name => 'AFStatus_38_I7_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x4e => { Name => 'AFStatus_39_I6_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, + 0x50 => { Name => 'AFStatus_40_I5_Vertical', %Image::ExifTool::Minolta::afStatusInfo }, +# center section, all sensors, from top to bottom, from right to left + 0x52 => { Name => 'AFStatus_41_A7', %Image::ExifTool::Minolta::afStatusInfo }, + 0x54 => { Name => 'AFStatus_42_B7', %Image::ExifTool::Minolta::afStatusInfo }, + 0x56 => { Name => 'AFStatus_43_C7', %Image::ExifTool::Minolta::afStatusInfo }, + 0x58 => { Name => 'AFStatus_44_D7', %Image::ExifTool::Minolta::afStatusInfo }, + 0x5a => { Name => 'AFStatus_45_E7', %Image::ExifTool::Minolta::afStatusInfo }, + 0x5c => { Name => 'AFStatus_46_F7', %Image::ExifTool::Minolta::afStatusInfo }, + 0x5e => { Name => 'AFStatus_47_G7', %Image::ExifTool::Minolta::afStatusInfo }, + 0x60 => { Name => 'AFStatus_48_H7', %Image::ExifTool::Minolta::afStatusInfo }, + 0x62 => { Name => 'AFStatus_49_I7', %Image::ExifTool::Minolta::afStatusInfo }, + 0x64 => { Name => 'AFStatus_50_A6', %Image::ExifTool::Minolta::afStatusInfo }, + 0x66 => { Name => 'AFStatus_51_B6', %Image::ExifTool::Minolta::afStatusInfo }, + 0x68 => { Name => 'AFStatus_52_C6', %Image::ExifTool::Minolta::afStatusInfo }, + 0x6a => { Name => 'AFStatus_53_D6', %Image::ExifTool::Minolta::afStatusInfo }, + 0x6c => { Name => 'AFStatus_54_E6_Center', %Image::ExifTool::Minolta::afStatusInfo }, + 0x6e => { Name => 'AFStatus_55_F6', %Image::ExifTool::Minolta::afStatusInfo }, + 0x70 => { Name => 'AFStatus_56_G6', %Image::ExifTool::Minolta::afStatusInfo }, + 0x72 => { Name => 'AFStatus_57_H6', %Image::ExifTool::Minolta::afStatusInfo }, + 0x74 => { Name => 'AFStatus_58_I6', %Image::ExifTool::Minolta::afStatusInfo }, + 0x76 => { Name => 'AFStatus_59_A5', %Image::ExifTool::Minolta::afStatusInfo }, + 0x78 => { Name => 'AFStatus_60_B5', %Image::ExifTool::Minolta::afStatusInfo }, + 0x7a => { Name => 'AFStatus_61_C5', %Image::ExifTool::Minolta::afStatusInfo }, + 0x7c => { Name => 'AFStatus_62_D5', %Image::ExifTool::Minolta::afStatusInfo }, + 0x7e => { Name => 'AFStatus_63_E5', %Image::ExifTool::Minolta::afStatusInfo }, + 0x80 => { Name => 'AFStatus_64_F5', %Image::ExifTool::Minolta::afStatusInfo }, + 0x82 => { Name => 'AFStatus_65_G5', %Image::ExifTool::Minolta::afStatusInfo }, + 0x84 => { Name => 'AFStatus_66_H5', %Image::ExifTool::Minolta::afStatusInfo }, + 0x86 => { Name => 'AFStatus_67_I5', %Image::ExifTool::Minolta::afStatusInfo }, +# right section, from top to bottom, from right to left + 0x88 => { Name => 'AFStatus_68_C11', %Image::ExifTool::Minolta::afStatusInfo }, + 0x8a => { Name => 'AFStatus_69_D11', %Image::ExifTool::Minolta::afStatusInfo }, + 0x8c => { Name => 'AFStatus_70_E11', %Image::ExifTool::Minolta::afStatusInfo }, + 0x8e => { Name => 'AFStatus_71_F11', %Image::ExifTool::Minolta::afStatusInfo }, + 0x90 => { Name => 'AFStatus_72_G11', %Image::ExifTool::Minolta::afStatusInfo }, + 0x92 => { Name => 'AFStatus_73_B10', %Image::ExifTool::Minolta::afStatusInfo }, + 0x94 => { Name => 'AFStatus_74_C10', %Image::ExifTool::Minolta::afStatusInfo }, + 0x96 => { Name => 'AFStatus_75_D10', %Image::ExifTool::Minolta::afStatusInfo }, + 0x98 => { Name => 'AFStatus_76_E10', %Image::ExifTool::Minolta::afStatusInfo }, + 0x9a => { Name => 'AFStatus_77_F10', %Image::ExifTool::Minolta::afStatusInfo }, + 0x9c => { Name => 'AFStatus_78_G10', %Image::ExifTool::Minolta::afStatusInfo }, + 0x9e => { Name => 'AFStatus_79_H10', %Image::ExifTool::Minolta::afStatusInfo }, + 0xa0 => { Name => 'AFStatus_80_B9', %Image::ExifTool::Minolta::afStatusInfo }, + 0xa2 => { Name => 'AFStatus_81_C9', %Image::ExifTool::Minolta::afStatusInfo }, + 0xa4 => { Name => 'AFStatus_82_D9', %Image::ExifTool::Minolta::afStatusInfo }, + 0xa6 => { Name => 'AFStatus_83_E9', %Image::ExifTool::Minolta::afStatusInfo }, + 0xa8 => { Name => 'AFStatus_84_F9', %Image::ExifTool::Minolta::afStatusInfo }, + 0xaa => { Name => 'AFStatus_85_G9', %Image::ExifTool::Minolta::afStatusInfo }, + 0xac => { Name => 'AFStatus_86_H9', %Image::ExifTool::Minolta::afStatusInfo }, + 0xae => { Name => 'AFStatus_87_B8', %Image::ExifTool::Minolta::afStatusInfo }, + 0xb0 => { Name => 'AFStatus_88_C8', %Image::ExifTool::Minolta::afStatusInfo }, + 0xb2 => { Name => 'AFStatus_89_D8', %Image::ExifTool::Minolta::afStatusInfo }, + 0xb4 => { Name => 'AFStatus_90_E8', %Image::ExifTool::Minolta::afStatusInfo }, + 0xb6 => { Name => 'AFStatus_91_F8', %Image::ExifTool::Minolta::afStatusInfo }, + 0xb8 => { Name => 'AFStatus_92_G8', %Image::ExifTool::Minolta::afStatusInfo }, + 0xba => { Name => 'AFStatus_93_H8', %Image::ExifTool::Minolta::afStatusInfo }, +# central F2.8 sensor + 0xbc => { Name => 'AFStatus_94_E6_Center_F2-8', %Image::ExifTool::Minolta::afStatusInfo }, +); + +# tag 0x9416 decoding (ref JR) +%Image::ExifTool::Sony::Tag9416 = ( + PROCESS_PROC => \&ProcessEnciphered, + WRITE_PROC => \&WriteEnciphered, + CHECK_PROC => \&Image::ExifTool::CheckBinaryData, + FORMAT => 'int8u', + NOTES => 'Valid for the ILCE-1/6700/7M4/7RM5/7SM3, ILME-FX3/FX30, ZV-E1.', + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x0000 => { Name => 'Tag9416_0000', PrintConv => 'sprintf("%3d",$val)', RawConv => '$$self{TagVersion} = $val' }, + 0x0004 => { + Name => 'SonyISO', + Format => 'int16u', + ValueConv => '100 * 2**(16 - $val/256)', + ValueConvInv => '256 * (16 - log($val/100)/log(2))', + PrintConv => 'sprintf("%.0f",$val)', + PrintConvInv => '$val', + }, + 0x0006 => { %gain2010 }, + 0x000a => { # appr. same value as Exif ExposureTime, but shorter in HDR-modes + Name => 'SonyExposureTime2', + Format => 'int16u', + ValueConv => '$val ? 2 ** (16 - $val/256) : 0', + ValueConvInv => '$val ? int((16 - log($val) / log(2)) * 256 + 0.5) : 0', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', + PrintConvInv => 'lc($val) eq "bulb" ? 0 : Image::ExifTool::Exif::ConvertFraction($val)', + }, + 0x000c => { + Name => 'ExposureTime', + Format => 'rational32u', + PrintConv => '$val ? Image::ExifTool::Exif::PrintExposureTime($val) : "Bulb"', # (Bulb NC) + PrintConvInv => 'lc($val) eq "bulb" ? 0 : $val', + }, + 0x0010 => { # but sometimes deviating results + Name => 'SonyFNumber2', + Format => 'int16u', + ValueConv => '2 ** (($val/256 - 16) / 2)', + ValueConvInv => '(log($val)*2/log(2)+16)*256', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x0012 => { + Name => 'SonyMaxApertureValue', # (at current focal length) + Format => 'int16u', + ValueConv => '2 ** (($val/256 - 16) / 2)', + ValueConvInv => '(log($val)*2/log(2)+16)*256', + PrintConv => 'sprintf("%.1f",$val)', + PrintConvInv => '$val', + }, + 0x001d => { %sequenceImageNumber }, + 0x0035 => { + Name => 'ExposureProgram', + Priority => 0, + SeparateTable => 'ExposureProgram3', + PrintConv => \%sonyExposureProgram3, + }, + 0x0048 => { + Name => 'LensMount', + Condition => '$$self{Model} !~ /^(DSC-)/', + PrintConv => { + 0 => 'Unknown', + 1 => 'A-mount', + 2 => 'E-mount', + 3 => 'A-mount (3)', + }, + }, + 0x0049 => { + Name => 'LensFormat', + Condition => '$$self{Model} !~ /^(DSC-)/', + PrintConv => { + 0 => 'Unknown', + 1 => 'APS-C', + 2 => 'Full-frame', + }, + }, + 0x004a => { + Name => 'LensMount', + DataMember => 'LensMount', + RawConv => '$$self{LensMount} = $val; $$self{Model} =~ /^(DSC-)/ ? undef : $val', + PrintConv => { + 0 => 'Unknown', + 1 => 'A-mount', + 2 => 'E-mount', + }, + }, + 0x004b => { + Name => 'LensType2', + Condition => '$$self{LensMount} == 2', + Format => 'int16u', + SeparateTable => 'LensType2', + PrintConv => \%sonyLensTypes2, + PrintInt => 1, + }, + 0x004d => { + Name => 'LensType', + Condition => '$$self{LensMount} == 1', + Priority => 0, #PH (just to be safe) + Format => 'int16u', #PH + SeparateTable => 1, + ValueConvInv => '($val & 0xff00) == 0x8000 ? 0 : int($val)', + PrintConv => \%sonyLensTypes, + PrintInt => 1, + }, + 0x004f => { + Name => 'DistortionCorrParams', + Format => 'int16s[16]', + }, + 0x0070 => { %pictureProfile2010 }, #IB + 0x0071 => { + Name => 'FocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x0073 => { + Name => 'MinFocalLength', + Format => 'int16u', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x0075 => { # may give 0 for fixed focal length lenses + Name => 'MaxFocalLength', + Format => 'int16u', + RawConv => '$val || undef', + ValueConv => '$val / 10', + ValueConvInv => '$val * 10', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val =~ s/ ?mm//; $val', + }, + 0x088f => { + Name => 'VignettingCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-(1|7SM3)|ILME-FX3)\b/', + Format => 'int16s[16]', + }, + 0x0891 => { + Name => 'VignettingCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-7M4)/', + Format => 'int16s[16]', + }, + 0x089d => { # Note: 32 values for these newer models, and 32 non-zero values present for new lenses like SEL2470GM2 and SEL2470G + Name => 'VignettingCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-(6700|7RM5)|ILME-FX30|ZV-E1)\b/', + Format => 'int16s[32]', + }, + 0x08b5 => { + Name => 'APS-CSizeCapture', + Condition => '$$self{Model} =~ /^(ILCE-(1|7SM3)|ILME-FX3)\b/', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x08b7 => { + Name => 'APS-CSizeCapture', + Condition => '$$self{Model} =~ /^(ILCE-7M4)/', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x08e5 => { + Name => 'APS-CSizeCapture', + Condition => '$$self{Model} =~ /^(ILCE-7RM5|ZV-E1)\b/', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x0914 => { + Name => 'ChromaticAberrationCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-(1|7SM3)|ILME-FX3)\b/', + Format => 'int16s[32]', + }, + 0x0916 => { + Name => 'ChromaticAberrationCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-7M4)/', + Format => 'int16s[32]', + }, + 0x0945 => { + Name => 'ChromaticAberrationCorrParams', + Condition => '$$self{Model} =~ /^(ILCE-(6700|7RM5)|ILME-FX30|ZV-E1)\b/', + Format => 'int16s[32]', + }, +); + +%Image::ExifTool::Sony::FaceInfo1 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x00 => { + Name => 'Face1Position', + Format => 'int16u[4]', + Notes => q{ + top, left, height and width of detected face. Coordinates are relative to + the full-sized unrotated image, with increasing Y downwards + }, + RawConv => '$$self{FacesDetected} < 1 ? undef : $val', + }, + 0x20 => { + Name => 'Face2Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 2 ? undef : $val', + }, + 0x40 => { + Name => 'Face3Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 3 ? undef : $val', + }, + 0x60 => { + Name => 'Face4Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 4 ? undef : $val', + }, + 0x80 => { + Name => 'Face5Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 5 ? undef : $val', + }, + 0xa0 => { + Name => 'Face6Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 6 ? undef : $val', + }, + 0xc0 => { + Name => 'Face7Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 7 ? undef : $val', + }, + 0xe0 => { + Name => 'Face8Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 8 ? undef : $val', + }, +); + +%Image::ExifTool::Sony::FaceInfo2 = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + 0x00 => { + Name => 'Face1Position', + Format => 'int16u[4]', + Notes => q{ + top, left, height and width of detected face. Coordinates are relative to + the full-sized unrotated image, with increasing Y downwards + }, + RawConv => '$$self{FacesDetected} < 1 ? undef : $val', + }, + 0x25 => { + Name => 'Face2Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 2 ? undef : $val', + }, + 0x4a => { + Name => 'Face3Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 3 ? undef : $val', + }, + 0x6f => { + Name => 'Face4Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 4 ? undef : $val', + }, + 0x94 => { + Name => 'Face5Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 5 ? undef : $val', + }, + 0xb9 => { + Name => 'Face6Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 6 ? undef : $val', + }, + 0xde => { + Name => 'Face7Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 7 ? undef : $val', + }, + 0x103 => { + Name => 'Face8Position', + Format => 'int16u[4]', + RawConv => '$$self{FacesDetected} < 8 ? undef : $val', + }, +); + +# panorama info for cameras such as the HX1, HX5, TX7 (ref 9/PH) +%Image::ExifTool::Sony::Panorama = ( + %binaryDataAttrs, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FORMAT => 'int32u', + NOTES => q{ + Tags found in panorama images from various Sony DSC, NEX, SLT and DSLR + cameras. The width/height values of these tags are not affected by camera + rotation -- the width is always the longer dimension. + }, + # 0: 257 for panorama images, 0 for all other images (ref JR) + 1 => 'PanoramaFullWidth', # (including black/gray borders) + 2 => 'PanoramaFullHeight', + 3 => { + Name => 'PanoramaDirection', + PrintConv => { + 0 => 'Left or Up', + 1 => 'Right or Down', + }, + }, + # crop area to remove black/gray borders from full image + 4 => 'PanoramaCropLeft', + 5 => 'PanoramaCropTop', #PH guess (NC) + 6 => 'PanoramaCropRight', + 7 => 'PanoramaCropBottom', + # 8: 1728 (HX1), 1824 (HX5/TX7) (value8/value9 = 16/9) + 8 => 'PanoramaFrameWidth', #PH guess (NC) + # 9: 972 (HX1), 1026 (HX5/TX7) + 9 => 'PanoramaFrameHeight', #PH guess (NC) + # 10: 3200-3800 (HX1), 4000-4900 (HX5/TX7) + 10 => 'PanoramaSourceWidth', #PH guess (NC) + # 11: 800-1800 (larger for taller panoramas) + 11 => 'PanoramaSourceHeight', #PH guess (NC) + # 12-15: 0 +); + +# tag table for SRF0 IFD (ref 1) +%Image::ExifTool::Sony::SRF = ( + PROCESS_PROC => \&ProcessSRF, + GROUPS => { 0 => 'MakerNotes', 1 => 'SRF#', 2 => 'Camera' }, + NOTES => q{ + The maker notes in SRF (Sony Raw Format) images contain 7 IFD's with family + 1 group names SRF0 through SRF6. SRF0 and SRF1 use the tags in this table, + while SRF2 through SRF5 use the tags in the next table, and SRF6 uses + standard EXIF tags. All information other than SRF0 is encrypted, but + thanks to Dave Coffin the decryption algorithm is known. SRF images are + written by the Sony DSC-F828 and DSC-V3. + }, + # tags 0-1 are used in SRF1 + 0 => { + Name => 'SRF2Key', + Notes => 'key to decrypt maker notes from the start of SRF2', + RawConv => '$$self{SRF2Key} = $val', + }, + 1 => { + Name => 'DataKey', + Notes => 'key to decrypt the rest of the file from the end of the maker notes', + RawConv => '$$self{SRFDataKey} = $val', + }, + # SRF0 contains a single unknown tag with TagID 0x0003 +); + +# tag table for Sony RAW Format (ref 1) +%Image::ExifTool::Sony::SRF2 = ( + PROCESS_PROC => \&ProcessSRF, + GROUPS => { 0 => 'MakerNotes', 1 => 'SRF#', 2 => 'Camera' }, + NOTES => "These tags are found in the SRF2 through SRF5 IFD's.", + # the following tags are used in SRF2-5 + 2 => 'SRF6Offset', #PH + # SRFDataOffset references 2220 bytes of unknown data for the DSC-F828 - PH + 3 => { Name => 'SRFDataOffset', Unknown => 1 }, #PH + 4 => { Name => 'RawDataOffset' }, #PH + 5 => { Name => 'RawDataLength' }, #PH + 0x0043 => 'MaxApertureAtMaxFocal', #IB + 0x0044 => 'MaxApertureAtMinFocal', #IB + 0x0045 => { #IB + Name => 'MinFocalLength', + PrintConv => '"$val mm"', + }, + 0x0046 => { #IB + Name => 'MaxFocalLength', + PrintConv => '"$val mm"', + }, + 0x00c0 => 'WBRedDaylight', #IB + 0x00c1 => 'WBGreenDaylight', #IB + 0x00c2 => 'WBBlueDaylight', #IB + 0x00c3 => 'WBRedCloudy', #IB + 0x00c4 => 'WBGreenCloudy', #IB + 0x00c5 => 'WBBlueCloudy', #IB + 0x00c6 => 'WBRedFluorescent', #IB + 0x00c7 => 'WBGreenFluorescent', #IB + 0x00c8 => 'WBBlueFluorescent', #IB + 0x00c9 => 'WBRedTungsten', #IB + 0x00ca => 'WBGreenTungsten', #IB + 0x00cb => 'WBBlueTungsten', #IB + 0x00cc => 'WBRedFlash', #IB + 0x00cd => 'WBGreenFlash', #IB + 0x00ce => 'WBBlueFlash', #IB + 0x00d0 => 'WBRedAsShot', #IB + 0x00d1 => 'WBGreenAsShot', #IB + 0x00d2 => 'WBBlueAsShot', #IB +); + +# tag table for Sony RAW 2 Format Private IFD (ref 1) +%Image::ExifTool::Sony::SR2Private = ( + PROCESS_PROC => \&ProcessSR2, + WRITE_PROC => \&WriteSR2, + GROUPS => { 0 => 'MakerNotes', 1 => 'SR2', 2 => 'Camera' }, + NOTES => q{ + The SR2 format uses the DNGPrivateData tag to reference a private IFD + containing these tags. SR2 images are written by the Sony DSC-R1, but + this information is also written to ARW images by other models. + }, + 0x7200 => { + Name => 'SR2SubIFDOffset', + # (adjusting offset messes up calculations for AdobeSR2 in DNG images) + # Flags => 'IsOffset', + # (can't set OffsetPair or else DataMember won't be set when writing) + # OffsetPair => 0x7201, + DataMember => 'SR2SubIFDOffset', + RawConv => '$$self{SR2SubIFDOffset} = $val', + }, + 0x7201 => { + Name => 'SR2SubIFDLength', + # (can't set OffsetPair or else DataMember won't be set when writing) + # OffsetPair => 0x7200, + DataMember => 'SR2SubIFDLength', + RawConv => '$$self{SR2SubIFDLength} = $val', + }, + 0x7221 => { + Name => 'SR2SubIFDKey', + Format => 'int32u', + Notes => 'key to decrypt SR2SubIFD', + DataMember => 'SR2SubIFDKey', + RawConv => '$$self{SR2SubIFDKey} = $val', + PrintConv => 'sprintf("0x%.8x", $val)', + }, + 0x7240 => { #PH + Name => 'IDC_IFD', + Groups => { 1 => 'SonyIDC' }, + Condition => '$$valPt !~ /^\0\0\0\0/', # (just in case this could be zero) + Flags => 'SubIFD', + SubDirectory => { + DirName => 'SonyIDC', + TagTable => 'Image::ExifTool::SonyIDC::Main', + Start => '$val', + }, + }, + 0x7241 => { #PH + Name => 'IDC2_IFD', + Groups => { 1 => 'SonyIDC' }, + Condition => '$$valPt !~ /^\0\0\0\0/', # may be zero if dir doesn't exist + Flags => 'SubIFD', + SubDirectory => { + DirName => 'SonyIDC2', + TagTable => 'Image::ExifTool::SonyIDC::Main', + Start => '$val', + Base => '$start', + MaxSubdirs => 20, # (A900 has 10 null entries, but IDC writes only 1) + RelativeBase => 1, # needed to write SubIFD with relative offsets + }, + }, + 0x7250 => { #1 + Name => 'MRWInfo', + Condition => '$$valPt !~ /^\0\0\0\0/', # (just in case this could be zero) + SubDirectory => { + TagTable => 'Image::ExifTool::MinoltaRaw::Main', + }, + }, +); + +%Image::ExifTool::Sony::SR2SubIFD = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'SR2SubIFD', 2 => 'Camera' }, + WRITE_GROUP => 'SR2SubIFD', + PERMANENT => 1, + SET_GROUP1 => 1, # set group1 name to directory name for all tags in table + NOTES => 'Tags in the encrypted SR2SubIFD', + 0x7300 => { Name => 'BlackLevel', Writable => 'int16u', Count => 4, Protected => 1 }, + 0x7302 => { Name => 'WB_GRBGLevelsAuto', Writable => 'int16s', Count => 4, Protected => 1 }, #IB (R1) + 0x7303 => { Name => 'WB_GRBGLevels', Writable => 'int16s', Count => 4, Protected => 1 }, #1 (R1 "as shot", ref IB) + 0x7310 => { Name => 'BlackLevel', Writable => 'int16u', Count => 4, Protected => 1 }, #IB (divide by 4) + 0x7312 => { Name => 'WB_RGGBLevelsAuto', Writable => 'int16s', Count => 4, Protected => 1 }, #IB + 0x7313 => { Name => 'WB_RGGBLevels', Writable => 'int16s', Count => 4, Protected => 1 }, #6 + 0x7480 => { Name => 'WB_RGBLevelsDaylight', Writable => 'int16s', Count => 4, Protected => 1 }, #IB (R1) + 0x7481 => { Name => 'WB_RGBLevelsCloudy', Writable => 'int16s', Count => 4, Protected => 1 }, #IB (R1) + 0x7482 => { Name => 'WB_RGBLevelsTungsten', Writable => 'int16s', Count => 4, Protected => 1 }, #IB (R1) + 0x7483 => { Name => 'WB_RGBLevelsFlash', Writable => 'int16s', Count => 4, Protected => 1 }, #IB (R1) + 0x7484 => { Name => 'WB_RGBLevels4500K', Writable => 'int16s', Count => 4, Protected => 1 }, #IB (R1) + 0x7486 => { Name => 'WB_RGBLevelsFluorescent', Writable => 'int16s', Count => 4, Protected => 1 }, #IB (R1) + 0x74a0 => 'MaxApertureAtMaxFocal', #PH + 0x74a1 => 'MaxApertureAtMinFocal', #PH + 0x74a2 => { #IB (R1) + Name => 'MaxFocalLength', + PrintConv => '"$val mm"', + }, + 0x74a3 => { #IB (R1) + Name => 'MinFocalLength', + PrintConv => '"$val mm"', + }, + 0x74c0 => { #PH + Name => 'SR2DataIFD', + Groups => { 1 => 'SR2DataIFD' }, # (needed to set SubIFD DirName) + Flags => 'SubIFD', + SubDirectory => { + TagTable => 'Image::ExifTool::Sony::SR2DataIFD', + Start => '$val', + MaxSubdirs => 20, # an A700 ARW has 14 of these! - PH + }, + }, + 0x7800 => 'ColorMatrix', #IB (divide by 1024) + 0x7820 => { Name => 'WB_RGBLevelsDaylight', Writable => 'int16s', Count => 3, Protected => 1 }, #6 (or 5300K, ref IB) + 0x7821 => { Name => 'WB_RGBLevelsCloudy', Writable => 'int16s', Count => 3, Protected => 1 }, #6 (or 6100K, ref IB) + 0x7822 => { Name => 'WB_RGBLevelsTungsten', Writable => 'int16s', Count => 3, Protected => 1 }, #6 + 0x7823 => { Name => 'WB_RGBLevelsFlash', Writable => 'int16s', Count => 3, Protected => 1 }, #IB + 0x7824 => { Name => 'WB_RGBLevels4500K', Writable => 'int16s', Count => 3, Protected => 1 }, #IB + 0x7825 => { Name => 'WB_RGBLevelsShade', Writable => 'int16s', Count => 3, Protected => 1 }, #6 (or 7500K, ref IB) + 0x7826 => { Name => 'WB_RGBLevelsFluorescent', Writable => 'int16s', Count => 3, Protected => 1 }, #6 (~4000K) + 0x7827 => { Name => 'WB_RGBLevelsFluorescentP1', Writable => 'int16s', Count => 3, Protected => 1 }, #IB (~5000K) + 0x7828 => { Name => 'WB_RGBLevelsFluorescentP2', Writable => 'int16s', Count => 3, Protected => 1 }, #IB (~6500K) (was Flash, ref 6) + 0x7829 => { Name => 'WB_RGBLevelsFluorescentM1', Writable => 'int16s', Count => 3, Protected => 1 }, #IB (~3500K) + 0x782a => { Name => 'WB_RGBLevels8500K', Writable => 'int16s', Count => 3, Protected => 1 }, #IB + 0x782b => { Name => 'WB_RGBLevels6000K', Writable => 'int16s', Count => 3, Protected => 1 }, #IB + 0x782c => { Name => 'WB_RGBLevels3200K', Writable => 'int16s', Count => 3, Protected => 1 }, #IB + 0x782d => { Name => 'WB_RGBLevels2500K', Writable => 'int16s', Count => 3, Protected => 1 }, #IB + 0x787f => { Name => 'WhiteLevel', Writable => 'int16u', Count => 3, Protected => 1 }, #IB (divide by 4) + 0x797d => 'VignettingCorrParams', #forum7640 + 0x7980 => 'ChromaticAberrationCorrParams', #forum6509 (Sony A7 ARW) + 0x7982 => 'DistortionCorrParams', #forum6509 (Sony A7 ARW) +); + +%Image::ExifTool::Sony::SR2DataIFD = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'SR2DataIFD', 2 => 'Camera' }, + SET_GROUP1 => 1, # set group1 name to directory name for all tags in table + # 0x7313 => 'WB_RGGBLevels', (duplicated in all SR2DataIFD's) + 0x7770 => { #PH + Name => 'ColorMode', + Priority => 0, + }, +); + +# extract information from "SONY PIC\0" maker notes (ref PH) +%Image::ExifTool::Sony::PIC = ( + PROCESS_PROC => \&ProcessSonyPIC, + GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' }, + NOTES => q{ + The TextInfo data is extracted as a block to preserve the formatting, and + some of the more interesting information is extracted as separate tags. + }, + TextInfo1 => { Binary => 1 }, + TextInfo2 => { Binary => 1 }, + # tags extracted from TextInfo blocks (ID's must end with ':') + 'Temp:' => { + Name => 'CameraTemperature', + RawConv => '$val =~ /^-?\d+/ ? $val : undef', + PrintConv => '"$val C"', + }, + 'Temp:Clbt:' => { Name => 'BoardTemperature', PrintConv => '"$val C"' }, #(NC) + 'Capt:' => { Name => 'SensorTemperature', PrintConv => '"$val C"' }, #(NC) + 'VR Enable C:' => { + Name => 'VibrationReduction', + PrintConv => { 0 => 'Off', 1 => 'On' }, #(NC) + }, + 'FWVer:' => 'FirmwareVersion', + 'BC:' => { + Name => 'Barcode', + Condition => 'not $$self{VALUE}{Barcode}', + ValueConv => '$val=~s/IP1.*//; $val', + }, + 'barcode:' => 'Barcode', + 'BarCode:' => { + Name => 'Barcode', + ValueConv => 'length($val) > 12 ? substr($val,0,12) : $val', + }, + # 'EvA:' - exposure compensation * 10 (ref JR) + # IFD: for documentation only -- this IFD is handled manually + IFD => { + Name => 'PIC_IFD', + SubDirectory => { TagTable => 'Image::ExifTool::Sony::Main' }, + }, +); + +# tags found in DSC-F1 PMP header (ref 10) +%Image::ExifTool::Sony::PMP = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + FIRST_ENTRY => 0, + NOTES => q{ + These tags are written in the proprietary-format header of PMP images from + the DSC-F1. + }, + 8 => { #PH + Name => 'JpgFromRawStart', + Format => 'int32u', + Notes => q{ + OK, not really a RAW file, but this mechanism is used to allow extraction of + the JPEG image from a PMP file + }, + }, + 12 => { Name => 'JpgFromRawLength', Format => 'int32u' }, + 22 => { Name => 'SonyImageWidth', Format => 'int16u' }, + 24 => { Name => 'SonyImageHeight', Format => 'int16u' }, + 27 => { + Name => 'Orientation', + PrintConv => { + 0 => 'Horizontal (normal)', + 1 => 'Rotate 270 CW',#11 + 2 => 'Rotate 180', + 3 => 'Rotate 90 CW',#11 + }, + }, + 29 => { + Name => 'ImageQuality', + PrintConv => { + 8 => 'Snap Shot', + 23 => 'Standard', + 51 => 'Fine', + }, + }, + # 40 => ImageWidth again (int16u) + # 42 => ImageHeight again (int16u) + 52 => { Name => 'Comment', Format => 'string[19]' }, + 76 => { + Name => 'DateTimeOriginal', + Description => 'Date/Time Original', + Format => 'int8u[6]', + Groups => { 2 => 'Time' }, + ValueConv => q{ + my @a = split ' ', $val; + $a[0] += $a[0] < 70 ? 2000 : 1900; + sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @a); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 84 => { + Name => 'ModifyDate', + Format => 'int8u[6]', + Groups => { 2 => 'Time' }, + ValueConv => q{ + my @a = split ' ', $val; + $a[0] += $a[0] < 70 ? 2000 : 1900; + sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @a); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 102 => { + Name => 'ExposureTime', + Format => 'int16s', + RawConv => '$val <= 0 ? undef : $val', + ValueConv => '2 ** (-$val / 100)', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 106 => { # (NC -- not written by DSC-F1) + Name => 'FNumber', + Format => 'int16s', + RawConv => '$val <= 0 ? undef : $val', + ValueConv => '$val / 100', # (likely wrong) + }, + 108 => { # (NC -- not written by DSC-F1) + Name => 'ExposureCompensation', + Format => 'int16s', + RawConv => '($val == -1 or $val == -32768) ? undef : $val', + ValueConv => '$val / 100', # (probably wrong too) + }, + 112 => { # (NC -- not written by DSC-F1) + Name => 'FocalLength', + Format => 'int16s', + Groups => { 2 => 'Camera' }, + RawConv => '$val <= 0 ? undef : $val', + ValueConv => '$val / 100', + PrintConv => 'sprintf("%.1f mm",$val)', + }, + 118 => { + Name => 'Flash', + Groups => { 2 => 'Camera' }, + PrintConv => { 0 => 'No Flash', 1 => 'Fired' }, + }, +); + +# tags found in 'rtmd' timed metadata in ILCE-7S/DSC-RX100M6 MP4 videos (ref PH) +%Image::ExifTool::Sony::rtmd = ( + PROCESS_PROC => \&Process_rtmd, + GROUPS => { 2 => 'Video' }, + NOTES => q{ + These tags are extracted from the 'rtmd' timed metadata of MP4 videos from + some models when the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> option is used. + }, + # 0x060e - 16 bytes starting with 0x060e2b340253 (fake tag ID - comes before 0x8300 container) + 0x060e => { Name => 'Sony_rtmd_0x060e', Format => 'int8u', %hidUnk }, + # 0x32xx - 16 bytes starting with 0x060e2b340401 + 0x3210 => { Name => 'Sony_rtmd_0x3210', Format => 'int8u', %hidUnk }, + 0x3219 => { Name => 'Sony_rtmd_0x3219', Format => 'int8u', %hidUnk }, + 0x321a => { Name => 'Sony_rtmd_0x321a', Format => 'int8u', %hidUnk }, + 0x8000 => { #forum12218 + Name => 'FNumber', + Format => 'int16u', + ValueConv => '2 ** (8-$val/8192)', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + }, + 0x8001 => { Name => 'Sony_rtmd_0x8001', Format => 'int16u', %hidUnk }, + 0x8004 => { Name => 'Sony_rtmd_0x8004', Format => 'int16u', %hidUnk }, # (FocalLength35efl?, forum14315) + 0x8005 => { Name => 'Sony_rtmd_0x8005', Format => 'int16u', %hidUnk }, # (FocalLength?, forum14315) + 0x800a => { Name => 'Sony_rtmd_0x800a', Format => 'int16u', %hidUnk }, # (FocusRingPosition?, forum14315) + 0x800b => { Name => 'Sony_rtmd_0x800b', Format => 'int16u', %hidUnk }, # (ZoomRingPosition?, forum14315) + # 0x8100 - 16 bytes starting with 0x060e2b340401 + 0x8100 => { Name => 'Sony_rtmd_0x8100', Format => 'int8u', %hidUnk }, + 0x8101 => { Name => 'Sony_rtmd_0x8101', Format => 'int8u', %hidUnk }, # seen: 0,1 + 0x8104 => { Name => 'Sony_rtmd_0x8104', Format => 'int16u', %hidUnk }, # seen: 35616 + 0x8105 => { Name => 'Sony_rtmd_0x8105', Format => 'int16u', %hidUnk }, # seen: 20092 + 0x8106 => { Name => 'Sony_rtmd_0x8106', Format => 'int32u', %hidUnk }, # seen: "25 1","24000 1001" frame rate? + 0x8109 => { #forum12218 + Name => 'ExposureTime', + Format => 'rational64u', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + }, + 0x810a => { #PH (NC, based on samples from forum12218) + Name => 'MasterGainAdjustment', + Format => 'int16u', + ValueConv => '$val / 100', + PrintConv => 'sprintf("%.2f dB", $val)', + }, + 0x810b => { Name => 'ISO', Format => 'int16u' }, #forum12218 + 0x810c => { #PH (NC, based on samples from forum12218) + Name => 'ElectricalExtenderMagnification', + Format => 'int16u', + }, + 0x810d => { Name => 'Sony_rtmd_0x810d', Format => 'int8u', %hidUnk }, # seen: 0,1 + 0x8115 => { Name => 'Sony_rtmd_0x8115', Format => 'int16u', %hidUnk }, # seen: 100 + # 0x8300 - container for other tags in this format + 0x8500 => { + Name => 'GPSVersionID', + Groups => { 2 => 'Location' }, + Format => 'int8u', + PrintConv => '$val =~ tr/ /./; $val', + }, + 0x8501 => { + Name => 'GPSLatitudeRef', + Groups => { 2 => 'Location' }, + Format => 'string', + PrintConv => { + N => 'North', + S => 'South', + }, + }, + 0x8502 => { + Name => 'GPSLatitude', + Groups => { 2 => 'Location' }, + Format => 'rational64u', + ValueConv => 'require Image::ExifTool::GPS;Image::ExifTool::GPS::ToDegrees($val)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', + }, + 0x8503 => { + Name => 'GPSLongitudeRef', + Groups => { 2 => 'Location' }, + Format => 'string', + PrintConv => { + E => 'East', + W => 'West', + }, + }, + 0x8504 => { + Name => 'GPSLongitude', + Groups => { 2 => 'Location' }, + Format => 'rational64u', + ValueConv => 'require Image::ExifTool::GPS;Image::ExifTool::GPS::ToDegrees($val)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', + }, + 0x8507 => { + Name => 'GPSTimeStamp', + Groups => { 2 => 'Time' }, + Format => 'rational64u', + ValueConv => 'require Image::ExifTool::GPS;Image::ExifTool::GPS::ConvertTimeStamp($val)', + PrintConv => 'Image::ExifTool::GPS::PrintTimeStamp($val)', + }, + 0x8509 => { + Name => 'GPSStatus', + Groups => { 2 => 'Location' }, + Format => 'string', + PrintConv => { + A => 'Measurement Active', + V => 'Measurement Void', + }, + }, + 0x850a => { + Name => 'GPSMeasureMode', + Groups => { 2 => 'Location' }, + Format => 'string', + PrintConv => { + 2 => '2-Dimensional Measurement', + 3 => '3-Dimensional Measurement', + }, + }, + 0x8512 => { + Name => 'GPSMapDatum', + Groups => { 2 => 'Location' }, + Format => 'string', + }, + 0x851d => { + Name => 'GPSDateStamp', + Groups => { 2 => 'Time' }, + Format => 'string', + ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', + }, + 0xe000 => { Name => 'Sony_rtmd_0xe000', Format => 'int8u', %hidUnk }, # (16 bytes) + 0xe300 => { Name => 'Sony_rtmd_0xe300', Format => 'int8u', %hidUnk }, # seen: 0,1 + 0xe301 => { Name => 'Sony_rtmd_0xe301', Format => 'int32u', %hidUnk }, # seen: 100,1600,12800 + 0xe302 => { Name => 'Sony_rtmd_0xe302', Format => 'int8u', %hidUnk }, # seen: 1 + 0xe303 => { #forum12218 + Name => 'WhiteBalance', + Format => 'int8u', + PrintConv => { + 1 => 'Incandescent', + 2 => 'Fluorescent', + 4 => 'Daylight', + 5 => 'Cloudy', + 6 => 'Custom', # ("Shade" uses this value too) + 255 => 'Preset', + }, + }, + 0xe304 => { + Name => 'DateTime', + Groups => { 2 => 'Time' }, + Format => 'undef', + ValueConv => 'my @a=unpack("x1H4H2H2H2H2H2",$val); "$a[0]:$a[1]:$a[2] $a[3]:$a[4]:$a[5]"', + PrintConv => '$self->ConvertDateTime($val)', + }, + 0xe435 => { Name => 'Sony_rtmd_0xe435', Format => 'int32u', %hidUnk }, # seen: 2000 + 0xe437 => { Name => 'Sony_rtmd_0xe437', Format => 'int32s', %hidUnk }, # seen: -3800 to -3400 + 0xe43b => { + Name => 'PitchRollYaw', + Format => 'int16s', + RawConv => 'substr($val, 8)', + }, + 0xe445 => { Name => 'Sony_rtmd_0xe445', Format => 'int32u', %hidUnk }, # seen: 2000 + 0xe44b => { + Name => 'Accelerometer', # (NC) + Format => 'int16s', + RawConv => 'substr($val, 8)', + }, + # f010 - 2048 bytes + # f020 - 543 bytes +); + +# Composite Sony tags +%Image::ExifTool::Sony::Composite = ( + GROUPS => { 2 => 'Camera' }, + FocusDistance => { + Require => { + 0 => 'Sony:FocusPosition', + 1 => 'FocalLength', + }, + Notes => 'distance in metres = FocusPosition * FocalLength / 1000', + ValueConv => '$val >= 128 ? "inf" : $val * $val[1] / 1000', + PrintConv => '$val eq "inf" ? $val : "$val m"', + }, + FocusDistance2 => { + # For DSLR-A550 and newer, NEX/ILCE/SLT/ILCA (only A65V/A77V are missing ...): + # seen FocusPosition2 with values from 80 - 255 (and 21 for Touit 12mm...) + # Formula from minolta.pm (WBInfoA100 - 0x49bb) gives mostly correct/acceptable distance indications. + # (https://exiftool.org/forum/index.php/topic,3688.0.html) + # if this value is the 35mm equivalent magnification, then the formula could + # be (1.5 * 2**($val/16-5)+1) * FocalLength, but this tends to underestimate + # distance by about 18% (ref 20) (255=inf) + # modified 16-10-2014 based on A99V measurements: use FocalLengthIn35mmFormat and leave out the "1.5*" factor. + Require => { + 0 => 'Sony:FocusPosition2', + 1 => 'FocalLengthIn35mmFormat', + }, + ValueConv => q{ + return undef unless $val; + return 'inf' if $val >= 255; + return (2**($val/16-5) + 1) * $val[1] / 1000; + }, + PrintConv => '$val eq "inf" ? $val : sprintf("%.4g m", $val)', + }, + GPSDateTime => { + Description => 'GPS Date/Time', + Groups => { 2 => 'Time' }, + SubDoc => 1, # generate for all sub-documents + Require => { + 0 => 'Sony:GPSDateStamp', + 1 => 'Sony:GPSTimeStamp', + }, + ValueConv => '"$val[0] $val[1]Z"', + PrintConv => '$self->ConvertDateTime($val)', + }, + GPSLatitude => { + SubDoc => 1, # generate for all sub-documents + Groups => { 2 => 'Location' }, + Require => { + 0 => 'Sony:GPSLatitude', + 1 => 'Sony:GPSLatitudeRef', + }, + ValueConv => '$val[1] =~ /^S/i ? -$val[0] : $val[0]', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + GPSLongitude => { + SubDoc => 1, # generate for all sub-documents + Groups => { 2 => 'Location' }, + Require => { + 0 => 'Sony:GPSLongitude', + 1 => 'Sony:GPSLongitudeRef', + }, + ValueConv => '$val[1] =~ /^W/i ? -$val[0] : $val[0]', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Sony'); + +sub SortLensTypes +{ + return $a <=> $b unless $a =~ /\./ and $b =~ /\./; + my @a = split /\./, $a; + my @b = split /\./, $b; + # must compare the decimal part separately to sort in proper order + return $a[0] <=> $b[0] || $a[1] <=> $b[1]; +} + +# fill in Sony LensType lookup based on Minolta values +{ + my $minoltaTypes = \%Image::ExifTool::Minolta::minoltaLensTypes; + %sonyLensTypes = %$minoltaTypes; + my $other = $$minoltaTypes{OTHER}; + delete $$minoltaTypes{Notes}; # (temporarily) + delete $$minoltaTypes{OTHER}; # (temporarily) + my $id; + # 5-digit lens ID's are missing the last digit (usually "1") in the metadata for + # some Sony models, so generate corresponding 4-digit entries for these cameras + foreach $id (sort SortLensTypes keys %$minoltaTypes) { + next if $id < 10000; + my $sid = int($id/10); + my $i; + my $lens = $$minoltaTypes{$id}; + if ($sonyLensTypes{$sid}) { + # put lens name with "or" first in list + if ($lens =~ / or /) { + my $tmp = $sonyLensTypes{$sid}; + $sonyLensTypes{$sid} = $lens; + $lens = $tmp; + } + for (;;) { + $i = ($i || 0) + 1; + $sid = int($id/10) . ".$i"; + last unless $sonyLensTypes{$sid}; + } + } + $sonyLensTypes{$sid} = $lens; + } + $$minoltaTypes{Notes} = $sonyLensTypes{Notes}; # (restore original Notes) + $$minoltaTypes{OTHER} = $other; +} + +#------------------------------------------------------------------------------ +# Process "SONY PIC\0" maker notes (DSC-H200/J10/W370/W510, MHS-TS20, ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 +sub ProcessSonyPIC($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart} || 0; + my $len = $$dirInfo{DirLen} || (length($$dataPt) - $start); + my $data = substr($$dataPt, $start, $len); + + # H200 panorama images have an IFD at offset 12 (non-panoramas have 0's here) + # - assume other images could too, but do a bit of validation to check + # - MHS-TS20 images have some other data here + if ($len >= 26) { + my $count = Get16u($dataPt, $start + 12); + if ($count > 256) { + ToggleByteOrder(); + $count = Get16u($dataPt, $start + 12); + } + if ($count and $count < 256) { + my $format = Get16u($dataPt, $start + 16); + if ($format >= 1 and $format <= 10) { + $$dirInfo{DirStart} = $start + 12; + $$dirInfo{DirLen} = $len - 12; + my $sonyTable = GetTagTable('Image::ExifTool::Sony::Main'); + Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $sonyTable); + } + } + } + # Do a brute force search for text data: + # For the DSC-J10/W370/W510 the first text block is at offset 0x1ec and + # starts with "BarCode:". For the H200 it is at 0x1f0 and starts with "BC:". + # For the TS20 it is at 0x5b and starts with "V400 AELOG\nbarcode:". + # The second text block starts with "AFLOG" (Auto-Focus log) and is at + # 0x600 for all models, except for the TS20 it is at 0x45b. + my $i = 0; + while ($data =~ /(\w[\x09\x0a\x0d\x20-\x7e]+)/sg) { + next unless length $1 > 32; + my ($tag, $val) = ('TextInfo' . (++$i), $1); + $$tagTablePtr{$tag} or AddTagToTable($tagTablePtr, $tag, { Name => $tag, Binary => 1 }); + $et->HandleTag($tagTablePtr, $tag, $val); + # extract interesting tags separately (might want to speed this up) + foreach $tag (sort { lc $a cmp lc $b } keys %$tagTablePtr) { + next unless $tag =~ /:$/ and $val =~ /\b$tag\s*([^\s;,:]+)/; + $et->HandleTag($tagTablePtr, $tag, $1); + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# MeterInfo value conversions +# Inputs: 0) value +# Returns: converted value +sub ConvMeter1($) +{ + my $val = shift; + return \$val unless length($val) == 90; + my @a = unpack("SLLSLLSLLSLLSLLSLLSLLSLLSLL",$val); + return join ' ', @a; +} +sub ConvMeter2($) +{ + my $val = shift; + return \$val unless length($val) == 110; + my @a = unpack("SLLSLLSLLSLLSLLSLLSLLSLLSLLSLLSLL",$val); + return join ' ', @a; +} + +#------------------------------------------------------------------------------ +# LensSpec value conversions +# Inputs: 0) value +# Returns: converted value +# Notes: unpacks in format compatible with LensInfo, with extra flags bytes at start and end +sub ConvLensSpec($) +{ + my $val = shift; + return \$val unless length($val) == 8; + my @a = unpack("H2H4H4H2H2H2",$val); + $a[1] += 0; $a[2] += 0; # remove leading zeros from focal lengths + s/([a-f])/hex($1)/e foreach @a[3,4]; # convert hex digits (ie. "b0" = f11) + $a[3] /= 10; $a[4] /= 10; # divide f-numbers by 10 + return join ' ', @a; +} +sub ConvInvLensSpec($) +{ + my $val = shift; + my @a=split(" ", $val); + return $val unless @a == 6; + $a[3] *= 10; $a[4] *= 10; # f-numbers are multiplied by 10 + s/^(\d{2})0$/sprintf('%x0',$1)/e foreach @a[3,4]; + $_ = hex foreach @a; # convert from hex + return pack 'CnnCCC', @a; +} + +#------------------------------------------------------------------------------ +# Print Sony LensSpec value +# Inputs: 0) LensSpec numerical value +# Returns: converted LensSpec string (eg. "DT 18-55mm F3.5-5.6 SAM") +# Refs: http://equational.org/importphotos/alphalensinfo.html +# http://www.dyxum.com/dforum/the-lens-information-different-from-lensid_topic37682.html +my @lensFeatures = ( + # lens features in the order they are added to the LensSpec string + # (high byte of Mask/Bits represents byte 0 of LensSpec, low byte is byte 7) + # Mask { Bits Name Bits Name } Prefix flag + # ------ ------ ----- ------ ----- ----------- + [ 0x4000, { 0x4000 => 'PZ' }, 1 ], + [ 0x0300, { 0x0100 => 'DT', 0x0200 => 'FE', 0x0300 => 'E' }, 1 ], # (will come before preceding prefix), FE added (ref JR) + [ 0x00e0, { 0x0020 => 'STF', 0x0040 => 'Reflex', 0x0060 => 'Macro', 0x0080 => 'Fisheye' } ], + [ 0x000c, { 0x0004 => 'ZA', 0x0008 => 'G' } ], + [ 0x0003, { 0x0001 => 'SSM', 0x0002 => 'SAM' } ], + [ 0x8000, { 0x8000 => 'OSS' } ], + [ 0x2000, { 0x2000 => 'LE' } ], #JR + [ 0x0800, { 0x0800 => 'II' } ], #JR +); +sub PrintLensSpec($) +{ + my $val = shift; + my ($rtnVal, $feature, $f1, $sf, $lf, $sa, $la, $f2); + # 0=flags1, 1=short focal, 2=long focal, 3=max aperture at short focal, + # 4=max aperture at long focal, 5=flags2 + my @a = split ' ', $val; + if (@a == 2) { # LensSpecFeatures patch + ($f1, $f2) = @a; + $rtnVal = ''; + } elsif (@a >= 6) { + ($f1, $sf, $lf, $sa, $la, $f2) = @a; + # crude validation of focal length and aperture values + if ($sf != 0 and $sa != 0 and ($lf == 0 or $lf >= $sf) and ($la == 0 or $la >= $sa)) { + # use focal and aperture range if this is a zoom lens + $sf .= '-' . $lf if $lf != $sf and $lf != 0; + $sa .= '-' . $la if $sa != $la and $la != 0; + $rtnVal = "${sf}mm F$sa"; # heart of LensSpec is a LensInfo string + } + } + if (defined $rtnVal) { + # loop through available lens features + my $flags = hex($f1 . $f2); + foreach $feature (@lensFeatures) { + my $bits = $$feature[0] & $flags; + next unless $bits or $$feature[1]{$bits}; + # add feature name as a prefix or suffix to the LensSpec + my $str = $$feature[1]{$bits} || sprintf('Unknown(%.4x)',$bits); + $rtnVal = $rtnVal ? ($$feature[2] ? "$str $rtnVal" : "$rtnVal $str") : $str; + } + } else { + $rtnVal = "Unknown ($val)"; + } + return $rtnVal; +} +# inverse conversion +sub PrintInvLensSpec($;$$) +{ + my ($val, $self, $features) = @_; + return $1 if $val =~ /Unknown \((.*)\)/i; + my ($sf, $lf, $sa, $la) = Image::ExifTool::Exif::GetLensInfo($val); + my $str; + if ($features) { + $str = ''; + } elsif ($sf) { + # fixed focal length and aperture have zero for 2nd number + $lf = 0 if $lf == $sf; + $la = 0 if $la == $sa; + $str = " $sf $lf $sa $la"; + } else { + return undef; + } + my $flags = 0; + my ($feature, $bits); + foreach $feature (@lensFeatures) { + foreach $bits (keys %{$$feature[1]}) { + # set corresponding flag bits for each feature name found + my $name = $$feature[1]{$bits}; + $val =~ /\b$name\b/i and $flags |= $bits; + } + } + return sprintf "%.2x$str %.2x", $flags>>8, $flags&0xff; +} + +#------------------------------------------------------------------------------ +# Read/Write MoreInfo information (tag 0x0020, count 20480) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success when reading, or new directory when writing (IsWriting set) +sub ProcessMoreInfo($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to write routine + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || length($$dataPt); + my $isWriting = $$dirInfo{IsWriting}; + my $rtnVal = $isWriting ? undef : 0; + return $rtnVal if $dirLen < 4; + + my $num = Get16u($dataPt, $start); # number of entries + my $len = Get16u($dataPt, $start + 2); # total data length + + if ($dirLen < 4 + $num * 4) { + $et->Warn('Truncated MoreInfo data', 1); + return $rtnVal; + } + if ($num > 50) { + $et->Warn('Possibly corrupted MoreInfo data', 1); + return $rtnVal; + } + + $et->VerboseDir('MoreInfo', $num, $len) unless $isWriting; + + if ($len > $dirLen) { + $et->Warn('MoreInfo data length too large', 1); + $len = $dirLen; + } + # loop through the MoreInfo index section to get the block offsets and tag ID's + # (in case they are out of order, even though this may never happen) + my ($i, @offset, @tagID, %blockSize); + for ($i=0; $i<$num; ++$i) { + my $entry = $start + 4 + $i * 4; + push @tagID, Get16u($dataPt, $entry); + push @offset, Get16u($dataPt, $entry + 2); + if ($offset[-1] > $len and $offset[-1] <= $dirLen) { + $et->Warn('MoreInfo data length too small', 1); + $len = $dirLen; + } + } + # generate a lookup table of block sizes + my @sorted = sort { $a <=> $b } @offset; + push @sorted, 0xffff; # (simplifies logic in loop below) + for ($i=0; $i<$num; ++$i) { + my $offset = $sorted[$i]; + my $size = $sorted[$i+1] - $offset; + # note that block size will be negative for blocks with starting + # offsets greater than $dirLen, but we will ignore these below + $size = $len - $offset if $size > $len - $offset; + # (if blockSize is already defined for this offset, then there + # are 2 blocks with the same starting offset and the existing + # size must be zero. Since we can't know which block is + # actually non-zero size, the reasonable thing to do is + # assume that both have a size of zero) + $blockSize{$offset} = $size unless defined $blockSize{$offset}; + } + # initialize successful return value + $rtnVal = $isWriting ? substr($$dataPt, $start, $dirLen) : 1; + # now process each block + my $unknown = $$et{OPTIONS}{Unknown}; + for ($i=0; $i<$num; ++$i) { + next if $offset[$i] > $dirLen; # ignore bad offsets + my $tag = $tagID[$i]; + if ($isWriting) { + # write new tags + my $tagInfo = $$tagTablePtr{$tag}; + next unless ref $tagInfo eq 'HASH' and $$tagInfo{SubDirectory}; + my $offset = $offset[$i]; + my $size = $blockSize{$offset}; + next unless $size; # ignore zero-length blocks + my %dirInfo = ( + DirName => $$tagInfo{Name}, + Parent => $$dirInfo{DirName}, + DataPt => \$rtnVal, + DirStart => $offset, + DirLen => $size, + ); + my $subTable = GetTagTable($$tagInfo{SubDirectory}{TagTable}); + my $val = $et->WriteDirectory(\%dirInfo, $subTable); + # update this block in the returned MoreInfo data + substr($rtnVal, $offset, $size) = $val if defined $val; + next; + } + # generate binary tables for unknown tags if -U option used + if (not defined $$tagTablePtr{$tag} and $unknown > 1) { + my $name = sprintf('MoreInfo%.4x', $tag); + my $table = "Image::ExifTool::Sony::$name"; + no strict 'refs'; + %$table = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FIRST_ENTRY => 0, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + ); + use strict 'refs'; + my %tagInfo = ( + Name => $name, + SubDirectory => { TagTable => $table }, + ); + AddTagToTable($tagTablePtr, $tag, \%tagInfo); + } + $et->HandleTag($tagTablePtr, $tag, undef, + Index => $i, + DataPt => $dataPt, + DataPos => $$dirInfo{DataPos}, + Start => $start + $offset[$i], + Size => $blockSize{$offset[$i]}, + ); + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Read Sony DSC-F1 PMP file +# Inputs: 0) ExifTool object ref, 1) dirInfo ref +# Returns: 1 on success when reading, 0 if this isn't a valid PMP file +sub ProcessPMP($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $buff; + $raf->Read($buff, 128) == 128 or return 0; + # validate header length (124 bytes) + $buff =~ /^.{8}\0{3}\x7c.{112}\xff\xd8\xff\xdb$/s or return 0; + $et->SetFileType(); + SetByteOrder('MM'); + $et->FoundTag(Make => 'Sony'); + $et->FoundTag(Model => 'DSC-F1'); + # extract information from 124-byte header + my $tagTablePtr = GetTagTable('Image::ExifTool::Sony::PMP'); + my %dirInfo = ( DataPt => \$buff, DirName => 'PMP' ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + # process JPEG image + $raf->Seek(124, 0); + $$dirInfo{Base} = 124; + $et->ProcessJPEG($dirInfo); + return 1; +} + +#------------------------------------------------------------------------------ +# Set the ARW file type and decide between SubIFD and A100DataOffset +# Inputs: 0) ExifTool object ref, 1) reference to tag 0x14a raw data +# Returns: true if tag 0x14a is a SubIFD, false otherwise +sub SetARW($$) +{ + my ($et, $valPt) = @_; + + # assume ARW for now -- SR2's get identified when FileFormat is parsed + $et->OverrideFileType($$et{TIFF_TYPE} = 'ARW'); + + # this should always be a SubIFD for models other than the A100 + return 1 unless $$et{Model} eq 'DSLR-A100' and length $$valPt == 4; + + # for the A100, IFD0 tag 0x14a is either a pointer to the raw data if this is + # an original image, or a SubIFD offset if the image was edited by Sony IDC, + # so assume it points to the raw data if it isn't a valid IFD (this assumption + # will be checked later when we try to parse the SR2Private directory) + my %subdir = ( + DirStart => Get32u($valPt, 0), + Base => 0, + RAF => $$et{RAF}, + AllowOutOfOrderTags => 1, # doh! + ); + return Image::ExifTool::Exif::ValidateIFD(\%subdir); +} + +#------------------------------------------------------------------------------ +# Finish writing ARW image, patching necessary Sony quirks, etc +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) EXIF data ref, 3) image data reference +# Returns: undef on success, error string otherwise +# Notes: (it turns that all of this is for the A100 only) +sub FinishARW($$$$) +{ + my ($et, $dirInfo, $dataPt, $imageData) = @_; + + # pre-scan IFD0 to get IFD entry offsets for each tag + my $dataLen = length $$dataPt; + return 'Truncated IFD0' if $dataLen < 2; + my $n = Get16u($dataPt, 0); + return 'Truncated IFD0' if $dataLen < 2 + 12 * $n; + my ($i, %entry, $dataBlock, $pad, $dataOffset); + for ($i=0; $i<$n; ++$i) { + my $entry = 2 + $i * 12; + $entry{Get16u($dataPt, $entry)} = $entry; + } + # fix up SR2Private offset and A100DataOffset (A100 only) + if ($entry{0xc634} and $$et{MRWDirData}) { + return 'Unexpected MRW block' unless $$et{Model} eq 'DSLR-A100'; + return 'Missing A100DataOffset' unless $entry{0x14a} and $$et{A100DataOffset}; + # account for total length of image data + my $totalLen = 8 + $dataLen; + if (ref $imageData) { + foreach $dataBlock (@$imageData) { + my ($pos, $size, $pad) = @$dataBlock; + $totalLen += $size + $pad; + } + } + # align MRW block on an even 4-byte boundary + my $remain = $totalLen & 0x03; + $pad = 4 - $remain and $totalLen += $pad if $remain; + # set offset for the MRW directory data + Set32u($totalLen, $dataPt, $entry{0xc634} + 8); + # also pad MRWDirData data to an even 4 bytes (just to be safe) + $remain = length($$et{MRWDirData}) & 0x03; + $$et{MRWDirData} .= "\0" x (4 - $remain) if $remain; + $totalLen += length $$et{MRWDirData}; + # fix up A100DataOffset + $dataOffset = $$et{A100DataOffset}; + Set32u($totalLen, $dataPt, $entry{0x14a} + 8); + } + # patch double-referenced and incorrectly-sized A100 PreviewImage + if ($entry{0x201} and $$et{A100PreviewStart} and + $entry{0x202} and $$et{A100PreviewLength}) + { + Set32u($$et{A100PreviewStart}, $dataPt, $entry{0x201} + 8); + Set32u($$et{A100PreviewLength}, $dataPt, $entry{0x202} + 8); + } + # write TIFF IFD structure + my $outfile = $$dirInfo{OutFile}; + my $header = GetByteOrder() . Set16u(0x2a) . Set32u(8); + Write($outfile, $header, $$dataPt) or return 'Error writing'; + # copy over image data + if (ref $imageData) { + $et->CopyImageData($imageData, $outfile) or return 'Error copying image data'; + } + # write MRW data if necessary + if ($$et{MRWDirData}) { + Write($outfile, "\0" x $pad) if $pad; # write padding if necessary + Write($outfile, $$et{MRWDirData}); + delete $$et{MRWDirData}; + # set TIFF_END to copy over the MRW image data + $$et{TIFF_END} = $dataOffset if $dataOffset; + } + return undef; +} + +#------------------------------------------------------------------------------ +# Decrypt/Encrypt Sony data (ref 1) (reversible encryption) +# Inputs: 0) data reference, 1) start offset, 2) data length, 3) decryption key +# Returns: nothing (original data buffer is updated with decrypted data) +# Notes: data length should be a multiple of 4 +sub Decrypt($$$$) +{ + my ($dataPt, $start, $len, $key) = @_; + my ($i, $j, @pad); + my $words = int ($len / 4); + + for ($i=0; $i<4; ++$i) { + my $lo = ($key & 0xffff) * 0x0edd + 1; + my $hi = ($key >> 16) * 0x0edd + ($key & 0xffff) * 0x02e9 + ($lo >> 16); + $pad[$i] = $key = (($hi & 0xffff) << 16) + ($lo & 0xffff); + } + $pad[3] = ($pad[3] << 1 | ($pad[0]^$pad[2]) >> 31) & 0xffffffff; + for ($i=4; $i<0x7f; ++$i) { + $pad[$i] = (($pad[$i-4]^$pad[$i-2]) << 1 | + ($pad[$i-3]^$pad[$i-1]) >> 31) & 0xffffffff; + } + my @data = unpack("x$start N$words", $$dataPt); + for ($i=0x7f,$j=0; $j<$words; ++$i,++$j) { + $data[$j] ^= $pad[$i & 0x7f] = $pad[($i+1) & 0x7f] ^ $pad[($i+65) & 0x7f]; + } + substr($$dataPt, $start, $words*4) = pack('N*', @data); +} + +#------------------------------------------------------------------------------ +# Decipher/encipher Sony tag 0x2010, 0x900b, 0x9050 and 0x940x data (ref PH) +# Inputs: 0) data reference, 1) true to encipher the data +sub Decipher($;$) +{ + my ($dataPt, $encipher) = @_; + # This is a simple substitution cipher, so use a hardcoded translation table for speed. + # The formula is: $c = ($b*$b*$b) % 249, where $c is the enciphered data byte + # (note that bytes with values 249-255 are not translated, and 0-1, 82-84, + # 165-167 and 248 have the same enciphered value) + if ($encipher) { # encipher + $$dataPt =~ tr/\x02-\xf7/\x08\x1b\x40\x7d\xd8\x5e\x0e\xe7\x04V\xea\xcd\x05\x8ap\xb6i\x88\x200\xbe\xd7\x81\xbb\x92\x0c\x28\xecl\xa0\x95Q\xd3\x2f\x5dj\x5c9\x07\xc5\x87L\x1a\xf0\xe2\xef\x24y\x02\xb7\xac\xe0\x60\x2bG\xba\x91\xcbu\x8e\x233\xc4\xe3\x96\xdc\xc2N\x7fb\xf6OeE\xeet\xcf\x138KRST\x5bn\x93\xd02\xb1aAW\xa9D\x27X\xdd\xc3\x10\xbc\xdbs\x83\x181\xd4\x15\xe5_\x7bF\xbf\xf3\xe8\xa4\x2d\x82\xb0\xbd\xaf\x8cZ\x1f\xda\x9fmJ\x3cIw\xccU\x11\x06\x3a\xb3\x7e\x9a\x14\xe4\x25\xc8\xe1v\x86\x1e\x3d\xe96\x1c\xa1\xd2\xb5P\xa2\xb8\x98H\xc7\x29f\x8b\x9e\xa5\xa6\xa7\xae\xc1\xe6\x2a\x85\x0b\xb4\x94\xaa\x03\x97z\xab7\x1dc\x165\xc6\xd6k\x84\x2eh\x3f\xb2\xce\x99\x19MB\xf7\x80\xd5\x0a\x17\x09\xdf\xadr4\xf2\xc0\x9d\x8f\x9c\xca\x26\xa8dY\x8d\x0d\xd1\xedg\x3ex\x22\x3b\xc9\xd9q\x90C\x89o\xf4\x2c\x0f\xa3\xf5\x12\xeb\x9b\x21\x7c\xb9\xde\xf1/; + } else { # decipher + $$dataPt =~ tr/\x08\x1b\x40\x7d\xd8\x5e\x0e\xe7\x04V\xea\xcd\x05\x8ap\xb6i\x88\x200\xbe\xd7\x81\xbb\x92\x0c\x28\xecl\xa0\x95Q\xd3\x2f\x5dj\x5c9\x07\xc5\x87L\x1a\xf0\xe2\xef\x24y\x02\xb7\xac\xe0\x60\x2bG\xba\x91\xcbu\x8e\x233\xc4\xe3\x96\xdc\xc2N\x7fb\xf6OeE\xeet\xcf\x138KRST\x5bn\x93\xd02\xb1aAW\xa9D\x27X\xdd\xc3\x10\xbc\xdbs\x83\x181\xd4\x15\xe5_\x7bF\xbf\xf3\xe8\xa4\x2d\x82\xb0\xbd\xaf\x8cZ\x1f\xda\x9fmJ\x3cIw\xccU\x11\x06\x3a\xb3\x7e\x9a\x14\xe4\x25\xc8\xe1v\x86\x1e\x3d\xe96\x1c\xa1\xd2\xb5P\xa2\xb8\x98H\xc7\x29f\x8b\x9e\xa5\xa6\xa7\xae\xc1\xe6\x2a\x85\x0b\xb4\x94\xaa\x03\x97z\xab7\x1dc\x165\xc6\xd6k\x84\x2eh\x3f\xb2\xce\x99\x19MB\xf7\x80\xd5\x0a\x17\x09\xdf\xadr4\xf2\xc0\x9d\x8f\x9c\xca\x26\xa8dY\x8d\x0d\xd1\xedg\x3ex\x22\x3b\xc9\xd9q\x90C\x89o\xf4\x2c\x0f\xa3\xf5\x12\xeb\x9b\x21\x7c\xb9\xde\xf1/\x02-\xf7/; + } +} + +#------------------------------------------------------------------------------ +# Process Sony 0x94xx cipherdata directory +# Inputs: 0) ExifTool object ref, 1) directory information ref, 2) tag table ref +# Returns: 1 on success +# Notes: +# 1) dirInfo may contain VarFormatData (reference to empty list) to return +# details about any variable-length-format tags in the table (used when writing) +# 2) A bug in ExifTool 9.04-9.10 could have double-enciphered these blocks +sub ProcessEnciphered($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart); + my $data = substr($$dataPt, $dirStart, $dirLen); + my %dirInfo = ( + %$dirInfo, + DataPt => \$data, + DataPos => $$dirInfo{DataPos} + $dirStart, + DirStart => 0, + ); + Decipher(\$data); + if ($$et{DoubleCipher}) { + Decipher(\$data); + $et->WarnOnce('Some Sony metadata is double-enciphered. Write any tag to fix',1); + } + if ($et->Options('Verbose') > 2) { + my $tagInfo = $$dirInfo{TagInfo} || { Name => 'data' }; + my $str = $$et{DoubleCipher} ? 'ouble-d' : ''; + $et->VerboseDir("D${str}eciphered $$tagInfo{Name}"); + $et->VerboseDump(\$data, + Prefix => $$et{INDENT} . ' ', + DataPos => $$dirInfo{DirStart} + $$dirInfo{DataPos} + ($$dirInfo{Base} || 0), + ); + } + return $et->ProcessBinaryData(\%dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Write Sony 0x94xx cipherdata directory +# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref +# Returns: cipherdata block or undefined on error +sub WriteEnciphered($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; + my $dataPt = $$dirInfo{DataPt}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart); + my $data = substr($$dataPt, $dirStart, $dirLen); + my $changed = $$et{CHANGED}; + Decipher(\$data); + # fix double-enciphered data (due to bug in ExifTool 9.04-9.10) + if ($$et{DoubleCipher}) { + Decipher(\$data); + ++$$et{CHANGED}; + $et->WarnOnce('Fixed double-enciphered Sony metadata',1); + } + my %dirInfo = ( + %$dirInfo, + DataPt => \$data, + DataPos => $$dirInfo{DataPos} + $dirStart, + DirStart => 0, + ); + $data = $et->WriteBinaryData(\%dirInfo, $tagTablePtr); + if ($changed == $$et{CHANGED}) { + # nothing changed, so recover original data + $data = substr($$dataPt, $dirStart, $dirLen); + } elsif (defined $data) { + Decipher(\$data,1); # re-encipher + } + return $data; +} + +#------------------------------------------------------------------------------ +# Process "rtmd" timed metadata embedded in Sony MP4 videos (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub Process_rtmd($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = ($$dirInfo{DataPos} || 0) + ($$dirInfo{Base} || 0); + my $end = length $$dataPt; + return 0 if $end < 2; + $et->VerboseDir('Sony rtmd', undef, $end); + # Note: The 0x1c-byte header contains some as-yet unextracted information: + # offset 0x0e: int8u some minutes + # 0x0f: int8u some seconds + # 0x11: int8u frame number? (0-24 or 0-25) + my $pos = Get16u($dataPt, 0); # get header length (= 0x1c) + while ($pos + 4 < $end) { + my $tag = Get16u($dataPt, $pos); + last if $tag == 0; + my $len = Get16u($dataPt, $pos+2); + if ($tag == 0x060e) { + $len = 0x10; # (unknown 16 bytes starting with 0x060e2b340253) + } else { + $pos += 4; # skip tag id/size + next if $tag == 0x8300; # descend into contents of 0x8300 (container) + } + last if $pos + $len > $end; + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos, + Size => $len, + ); + $pos += $len; # step to next tag + } + return 1; +}; + +#------------------------------------------------------------------------------ +# Process SRF maker notes +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success +sub ProcessSRF($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $start = $$dirInfo{DirStart}; + my $verbose = $et->Options('Verbose'); + + # process IFD chain + my ($ifd, $success); + for ($ifd=0; ; ) { + # switch tag table for SRF2-5 and SRF6 + if ($ifd == 2) { + $tagTablePtr = GetTagTable('Image::ExifTool::Sony::SRF2'); + } elsif ($ifd == 6) { + # SRF6 uses standard EXIF tags + $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); + } + my $srf = $$dirInfo{DirName} = "SRF$ifd"; + $$et{SET_GROUP1} = $srf; + $success = Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $tagTablePtr); + delete $$et{SET_GROUP1}; + last unless $success; +# +# get pointer to next IFD +# + my $count = Get16u($dataPt, $$dirInfo{DirStart}); + my $dirEnd = $$dirInfo{DirStart} + 2 + $count * 12; + last if $dirEnd + 4 > length($$dataPt); + my $nextIFD = Get32u($dataPt, $dirEnd); + last unless $nextIFD; + $nextIFD -= $$dirInfo{DataPos}; # adjust for position of makernotes data + $$dirInfo{DirStart} = $nextIFD; +# +# decrypt next IFD data if necessary +# + ++$ifd; + my ($key, $len); + if ($ifd == 1) { + # get the key to decrypt IFD1 + my $cp = $start + 0x8ddc; # why? + last if $cp + 1 > length($$dataPt); + my $ip = $cp + 4 * unpack("x$cp C", $$dataPt); + last if $ip + 4 > length($$dataPt); + $key = unpack("x$ip N", $$dataPt); + $len = $cp + $nextIFD; # decrypt up to $cp + } elsif ($ifd == 2) { + # get the key to decrypt IFD2 + $key = $$et{SRF2Key}; + $len = length($$dataPt) - $nextIFD; # decrypt rest of maker notes + } else { + next; # no decryption needed + } + # decrypt data + Decrypt($dataPt, $nextIFD, $len, $key) if defined $key; + next unless $verbose > 2; + # display decrypted data in verbose mode + $et->VerboseDir("Decrypted SRF$ifd", 0, $nextIFD + $len); + $et->VerboseDump($dataPt, + Prefix => "$$et{INDENT} ", + Start => $nextIFD, + DataPos => $$dirInfo{DataPos}, + ); + } +} + +#------------------------------------------------------------------------------ +# Write SR2 data +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success when reading, or SR2 directory or undef when writing +sub WriteSR2($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + my $buff = ''; + $$dirInfo{OutFile} = \$buff; + return ProcessSR2($et, $dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Read/Write SR2 IFD and its encrypted subdirectories +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success when reading, or SR2 directory or undef when writing +sub ProcessSR2($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $raf = $$dirInfo{RAF}; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $dataLen = $$dirInfo{DataLen} || length $$dataPt; + my $base = $$dirInfo{Base} || 0; + my $outfile = $$dirInfo{OutFile}; + + # clear SR2 member variables to be safe + delete $$et{SR2SubIFDOffset}; + delete $$et{SR2SubIFDLength}; + delete $$et{SR2SubIFDKey}; + + # make sure we have the first 4 bytes available to test directory type + my $buff; + if ($dataLen < 4 and $raf) { + my $pos = $dataPos + ($$dirInfo{DirStart}||0) + $base; + if ($raf->Seek($pos, 0) and $raf->Read($buff, 4) == 4) { + $dataPt = \$buff; + undef $$dirInfo{DataPt}; # must load data from file + $raf->Seek($pos, 0); + } + } + # this may either be a normal IFD, or a MRW data block + # (only original ARW images from the A100 use the MRW block) + my $dataOffset; + if ($dataPt and $$dataPt =~ /^\0MR[IM]/) { + my ($err, $srfPos, $srfLen, $dataOffset); + $dataOffset = $$et{A100DataOffset}; + if ($dataOffset) { + # save information about the RAW data trailer so it will be preserved + $$et{KnownTrailer} = { Name => 'A100 RAW Data', Start => $dataOffset }; + } else { + $err = 'A100DataOffset tag is missing from A100 ARW image'; + } + $raf or $err = 'Unrecognized SR2 structure'; + unless ($err) { + $srfPos = $raf->Tell(); + $srfLen = $dataOffset - $srfPos; + unless ($srfLen > 0 and $raf->Read($buff, $srfLen) == $srfLen) { + $err = 'Error reading MRW directory'; + } + } + if ($err) { + $outfile and $et->Error($err), return undef; + $et->Warn($err); + return 0; + } + my %dirInfo = ( DataPt => \$buff ); + require Image::ExifTool::MinoltaRaw; + if ($outfile) { + # save MRW data to be written last + $$et{MRWDirData} = Image::ExifTool::MinoltaRaw::WriteMRW($et, \%dirInfo); + return $$et{MRWDirData} ? "\0\0\0\0\0\0" : undef; + } else { + if (not $outfile and $$et{HTML_DUMP}) { + $et->HDump($srfPos, $srfLen, '[A100 SRF Data]'); + } + return Image::ExifTool::MinoltaRaw::ProcessMRW($et, \%dirInfo); + } + } elsif ($$et{A100DataOffset}) { + my $err = 'Unexpected A100DataOffset tag'; + $outfile and $et->Error($err), return undef; + $et->Warn($err); + return 0; + } + my $verbose = $et->Options('Verbose'); + my $result; + if ($outfile) { + $result = Image::ExifTool::Exif::WriteExif($et, $dirInfo, $tagTablePtr); + return undef unless $result; + $$outfile .= $result; + + } else { + $result = Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $tagTablePtr); + } + return $result unless $result and $$et{SR2SubIFDOffset}; + # only take first offset value if more than one! + my @offsets = split ' ', $$et{SR2SubIFDOffset}; + my $offset = shift @offsets; + my $length = $$et{SR2SubIFDLength}; + my $key = $$et{SR2SubIFDKey}; + my @subifdPos; + if ($offset and $length and defined $key) { + my $buff; + # read encrypted SR2SubIFD from file + if (($raf and $raf->Seek($offset+$base, 0) and + $raf->Read($buff, $length) == $length) or + # or read from data (when processing Adobe DNGPrivateData) + ($offset - $dataPos >= 0 and $offset - $dataPos + $length < $dataLen and + ($buff = substr($$dataPt, $offset - $dataPos, $length)))) + { + Decrypt(\$buff, 0, $length, $key); + # display decrypted data in verbose mode + if ($verbose > 2 and not $outfile) { + $et->VerboseDir("Decrypted SR2SubIFD", 0, $length); + $et->VerboseDump(\$buff, Addr => $offset + $base); + } + my $num = ''; + my $dPos = $offset; + for (;;) { + my %dirInfo = ( + Base => $base, + DataPt => \$buff, + DataLen => length $buff, + DirStart => $offset - $dPos, + DirName => "SR2SubIFD$num", + DataPos => $dPos, + ); + my $subTable = GetTagTable('Image::ExifTool::Sony::SR2SubIFD'); + if ($outfile) { + my $fixup = new Image::ExifTool::Fixup; + $dirInfo{Fixup} = $fixup; + $result = $et->WriteDirectory(\%dirInfo, $subTable); + return undef unless $result; + # save position of this SubIFD + push @subifdPos, length($$outfile); + # add this directory to the returned data + $$fixup{Start} += length($$outfile); + $$outfile .= $result; + $$dirInfo{Fixup}->AddFixup($fixup); + } else { + $result = $et->ProcessDirectory(\%dirInfo, $subTable); + } + last unless @offsets; + $offset = shift @offsets; + $num = ($num || 1) + 1; + } + + } else { + $et->Warn('Error reading SR2 data'); + } + } + if ($outfile and @subifdPos) { + # the SR2SubIFD must be padded to a multiple of 4 bytes for the encryption + my $sr2Len = length($$outfile) - $subifdPos[0]; + if ($sr2Len & 0x03) { + my $pad = 4 - ($sr2Len & 0x03); + $sr2Len += $pad; + $$outfile .= ' ' x $pad; + } + # save the new SR2SubIFD Length and Key to be used later for encryption + $$et{SR2SubIFDLength} = $sr2Len; + my $newKey = $$et{VALUE}{SR2SubIFDKey}; + $$et{SR2SubIFDKey} = $newKey if defined $newKey; + # update SubIFD pointers manually and add to fixup, and set SR2SubIFDLength + my $n = Get16u($outfile, 0); + my ($i, %found); + for ($i=0; $i<$n; ++$i) { + my $entry = 2 + 12 * $i; + my $tagID = Get16u($outfile, $entry); + # only interested in SR2SubIFDOffset (0x7200) and SR2SubIFDLength (0x7201) + next unless $tagID == 0x7200 or $tagID == 0x7201; + $found{$tagID} = 1; + my $fmt = Get16u($outfile, $entry + 2); + if ($fmt != 0x04) { # must be int32u + $et->Error("Unexpected format ($fmt) for SR2SubIFD tag"); + return undef; + } + if ($tagID == 0x7201) { # SR2SubIFDLength + Set32u($sr2Len, $outfile, $entry + 8); + next; + } + my $tag = 'SR2SubIFDOffset'; + my $valuePtr = @subifdPos < 2 ? $entry+8 : Get32u($outfile, $entry+8); + my $pos; + foreach $pos (@subifdPos) { + Set32u($pos, $outfile, $valuePtr); + $$dirInfo{Fixup}->AddFixup($valuePtr, $tag); + undef $tag; + $valuePtr += 4; + } + } + unless ($found{0x7200} and $found{0x7201}) { + $et->Error('Missing SR2SubIFD tag'); + return undef; + } + } + return $outfile ? $$outfile : $result; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Sony - Sony 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 +Sony maker notes EXIF meta information. + +=head1 NOTES + +Also see Minolta.pm since Sony DSLR models use structures originating from +Minolta. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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/> + +=item L<http://homepage3.nifty.com/kamisaka/makernote/makernote_sony.htm> + +=item L<http://www.klingebiel.com/tempest/hd/pmp.html> + +=item (...plus lots of testing with my RX100!) + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Thomas Bodenmann, Philippe Devaux, Jens Duttke, Marcus +Holland-Moritz, Andrey Tverdokhleb, Rudiger Lange, Igal Milchtaich, Michael +Reitinger and Jos Roost for help decoding some tags. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Sony Tags>, +L<Image::ExifTool::TagNames/Minolta Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/SonyIDC.pm b/ExifTool/lib/Image/ExifTool/SonyIDC.pm new file mode 100644 index 0000000..e8ec637 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/SonyIDC.pm @@ -0,0 +1,393 @@ +#------------------------------------------------------------------------------ +# File: SonyIDC.pm +# +# Description: Read/write Sony IDC information +# +# Revisions: 2010/01/05 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::SonyIDC; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::Exif; + +$VERSION = '1.08'; + +# Sony IDC tags (ref PH) +%Image::ExifTool::SonyIDC::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 2 => 'Image' }, + NOTES => 'Tags written by the Sony Image Data Converter utility in ARW images.', + SET_GROUP1 => 1, + 0x201 => { + Name => 'IDCPreviewStart', + IsOffset => 1, + OffsetPair => 0x202, + DataTag => 'IDCPreview', + Writable => 'int32u', + Protected => 2, + }, + 0x202 => { + Name => 'IDCPreviewLength', + OffsetPair => 0x201, + DataTag => 'IDCPreview', + Writable => 'int32u', + Protected => 2, + }, + 0x8000 => { + Name => 'IDCCreativeStyle', + Writable => 'int32u', + PrintConvColumns => 2, + PrintConv => { + 1 => 'Camera Setting', + 2 => 'Standard', + 3 => 'Real', + 4 => 'Vivid', + 5 => 'Adobe RGB', + 6 => 'A100 Standard', # shows up as '-' in IDC menu + 7 => 'Neutral', + 8 => 'Portrait', + 9 => 'Landscape', + 10 => 'Clear', + 11 => 'Deep', + 12 => 'Light', + 13 => 'Sunset', + 14 => 'Night View', + 15 => 'Autumn Leaves', + 16 => 'B&W', + 17 => 'Sepia', + }, + }, + 0x8001 => { + Name => 'CreativeStyleWasChanged', + Writable => 'int32u', + Notes => 'set if the creative style was ever changed', + # (even if it was changed back again later) + PrintConv => { 0 => 'No', 1 => 'Yes' }, + }, + 0x8002 => { + Name => 'PresetWhiteBalance', + Writable => 'int32u', + PrintConv => { + 1 => 'Camera Setting', + 2 => 'Color Temperature', + 3 => 'Specify Gray Point', + 4 => 'Daylight', + 5 => 'Cloudy', + 6 => 'Shade', + 7 => 'Cool White Fluorescent', + 8 => 'Day Light Fluorescent', + 9 => 'Day White Fluorescent', + 10 => 'Warm White Fluorescent', + 11 => 'Tungsten', + 12 => 'Flash', + 13 => 'Auto', + }, + }, + 0x8013 => { Name => 'ColorTemperatureAdj', Writable => 'int16u' }, + 0x8014 => { Name => 'PresetWhiteBalanceAdj',Writable => 'int32s' }, + 0x8015 => { Name => 'ColorCorrection', Writable => 'int32s' }, + 0x8016 => { Name => 'SaturationAdj', Writable => 'int32s' }, + 0x8017 => { Name => 'ContrastAdj', Writable => 'int32s' }, + 0x8018 => { + Name => 'BrightnessAdj', + Writable => 'int32s', + PrintConv => 'sprintf("%.2f", $val/300)', #JR + PrintConvInv => '$val * 300', + }, + 0x8019 => { Name => 'HueAdj', Writable => 'int32s' }, + 0x801a => { Name => 'SharpnessAdj', Writable => 'int32s' }, + 0x801b => { Name => 'SharpnessOvershoot', Writable => 'int32s' }, + 0x801c => { Name => 'SharpnessUndershoot', Writable => 'int32s' }, + 0x801d => { Name => 'SharpnessThreshold', Writable => 'int32s' }, + 0x801e => { + Name => 'NoiseReductionMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 0x8021 => { + Name => 'GrayPoint', + Writable => 'int16u', + Count => 4, + }, + 0x8022 => { + Name => 'D-RangeOptimizerMode', + Writable => 'int16u', + PrintConv => { + 0 => 'Off', + 1 => 'Auto', + 2 => 'Manual', + }, + }, + 0x8023 => { Name => 'D-RangeOptimizerValue', Writable => 'int32s' }, + 0x8024 => { Name => 'D-RangeOptimizerHighlight',Writable => 'int32s' }, + 0x8026 => { + Name => 'HighlightColorDistortReduct', + Writable => 'int16u', + PrintConv => { + 0 => 'Standard', + 1 => 'Advanced', + }, + }, + 0x8027 => { + Name => 'NoiseReductionValue', + Writable => 'int32s', + ValueConv => '($val + 100) / 2', + ValueConvInv => '$val * 2 - 100', + }, + 0x8028 => { + Name => 'EdgeNoiseReduction', + Writable => 'int32s', + ValueConv => '($val + 100) / 2', + ValueConvInv => '$val * 2 - 100', + }, + 0x8029 => { + Name => 'ColorNoiseReduction', + Writable => 'int32s', + ValueConv => '($val + 100) / 2', + ValueConvInv => '$val * 2 - 100', + }, + 0x802d => { Name => 'D-RangeOptimizerShadow', Writable => 'int32s' }, + 0x8030 => { Name => 'PeripheralIllumCentralRadius', Writable => 'int32s' }, + 0x8031 => { Name => 'PeripheralIllumCentralValue', Writable => 'int32s' }, + 0x8032 => { Name => 'PeripheralIllumPeriphValue', Writable => 'int32s' }, + 0x8040 => { #JR + Name => 'DistortionCompensation', + Writable => 'int32s', + PrintConv => { + -1 => 'n/a', # (fixed by lens) + 1 => 'On', + 2 => 'Off', + }, + }, + 0x9000 => { + Name => 'ToneCurveBrightnessX', + Writable => 'int16u', + Count => -1, + }, + 0x9001 => { + Name => 'ToneCurveRedX', + Writable => 'int16u', + Count => -1, + }, + 0x9002 => { + Name => 'ToneCurveGreenX', + Writable => 'int16u', + Count => -1, + }, + 0x9003 => { + Name => 'ToneCurveBlueX', + Writable => 'int16u', + Count => -1, + }, + 0x9004 => { + Name => 'ToneCurveBrightnessY', + Writable => 'int16u', + Count => -1, + }, + 0x9005 => { + Name => 'ToneCurveRedY', + Writable => 'int16u', + Count => -1, + }, + 0x9006 => { + Name => 'ToneCurveGreenY', + Writable => 'int16u', + Count => -1, + }, + 0x9007 => { + Name => 'ToneCurveBlueY', + Writable => 'int16u', + Count => -1, + }, + 0x900d => { #JR + Name => 'ChromaticAberrationCorrection', # "Magnification Chromatic Aberration" + Writable => 'int32s', + PrintConv => { 1 => 'On', 2 => 'Off' }, + }, + 0x900e => { #JR + Name => 'InclinationCorrection', + Writable => 'int32u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x900f => { #JR + Name => 'InclinationAngle', + Writable => 'int32s', + PrintConv => 'sprintf("%.1f deg", $val/1000)', + PrintConvInv => 'ToFloat($val) * 1000', + }, + 0x9010 => { #JR + Name => 'Cropping', + Writable => 'int32u', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x9011 => { #JR + Name => 'CropArea', + Writable => 'int32u', + Count => 4, + }, + 0x9012 => { #JR + Name => 'PreviewImageSize', + Writable => 'int32u', + Count => 2, + }, + 0x9013 => { #JR (ARQ images) + Name => 'PxShiftPeriphEdgeNR', + Writable => 'int32s', + PrintConv => { 0 => 'Off', 1 => 'On' }, + }, + 0x9014 => { #JR (ARQ images) + Name => 'PxShiftPeriphEdgeNRValue', + Writable => 'int32s', + PrintConv => 'sprintf("%.1f", $val/10)', + PrintConvInv => '$val * 10', + }, + 0x9017 => { Name => 'WhitesAdj', Writable => 'int32s' }, #JR + 0x9018 => { Name => 'BlacksAdj', Writable => 'int32s' }, #JR + 0x9019 => { Name => 'HighlightsAdj', Writable => 'int32s' }, #JR + 0x901a => { Name => 'ShadowsAdj', Writable => 'int32s' }, #JR + 0xd000 => { Name => 'CurrentVersion', Writable => 'int32u' }, + 0xd001 => { + Name => 'VersionIFD', + Groups => { 1 => 'Version0' }, + Flags => 'SubIFD', + Notes => 'there is one VersionIFD for each entry in the "Version Stack"', + SubDirectory => { + DirName => 'Version0', + TagTable => 'Image::ExifTool::SonyIDC::Main', + Start => '$val', + Base => '$start', + MaxSubdirs => 20, # (IDC v3.0 writes max. 10) + RelativeBase => 1, # needed to write SubIFD with relative offsets + }, + }, + 0xd100 => { + Name => 'VersionCreateDate', + Writable => 'string', + Groups => { 2 => 'Time' }, + Notes => 'date/time when this entry was created in the "Version Stack"', + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,0)', + }, + 0xd101 => { + Name => 'VersionModifyDate', + Writable => 'string', + Groups => { 2 => 'Time' }, + Shift => 'Time', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,0)', + }, +); + +# extract IDC preview images as composite tags +%Image::ExifTool::SonyIDC::Composite = ( + GROUPS => { 2 => 'Image' }, + IDCPreviewImage => { + Groups => { 2 => 'Preview' }, + Require => { + 0 => 'IDCPreviewStart', + 1 => 'IDCPreviewLength', + }, + # extract all preview images (not just one) + RawConv => q{ + @grps = $self->GetGroup($$val{0}); + require Image::ExifTool::SonyIDC; + Image::ExifTool::SonyIDC::ExtractPreviews($self); + }, + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::SonyIDC'); + +# set "Permanent" flag for all tags +{ + my $key; + foreach $key (TagTableKeys(\%Image::ExifTool::SonyIDC::Main)) { + $Image::ExifTool::SonyIDC::Main{$key}{Permanent} = 1; + } +} + +#------------------------------------------------------------------------------ +# Extract all IDC preview images +# Inputs: 0) ExifTool object ref +# Returns: data for "IDCPreviewImage" tag (which I have never seen), +# or undef if there was no preview in the SonyIDC IFD +sub ExtractPreviews($) +{ + my $et = shift; + my $i = 1; + my $xtra = ' (1)'; + my $preview; + # loop through all available IDC preview images in the order they were found + for (;;) { + my $key = "IDCPreviewStart$xtra"; + unless (defined $$et{VALUE}{$key}) { + last unless $xtra; + $xtra = ''; # do the last tag extracted last + next; + } + # run through IDC preview images in the same order they were extracted + my $off = $et->GetValue($key, 'ValueConv') or last; + my $len = $et->GetValue("IDCPreviewLength$xtra", 'ValueConv') or last; + # get stack version from number in group 1 name + my $grp1 = $et->GetGroup($key, 1); + if ($grp1 =~ /(\d+)$/) { + my $tag = "IDCPreviewImage$1"; + unless ($Image::ExifTool::Extra{$tag}) { + AddTagToTable(\%Image::ExifTool::Extra, $tag, { + Name => $tag, + Groups => { 0 => 'Composite', 1 => 'Composite', 2 => 'Preview'}, + }); + } + my $val = Image::ExifTool::Exif::ExtractImage($et, $off, $len, $tag); + $et->FoundTag($tag, $val, $et->GetGroup($key)); + } else { + $preview = Image::ExifTool::Exif::ExtractImage($et, $off, $len, 'IDCPreviewImage'); + } + # step to next set of tags unless we are done + last unless $xtra; + ++$i; + $xtra = " ($i)"; + } + return $preview; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::SonyIDC - Read/write Sony IDC information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read and +write Sony Image Data Converter version 3.0 metadata in ARW images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/SonyIDC Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Stim.pm b/ExifTool/lib/Image/ExifTool/Stim.pm new file mode 100644 index 0000000..71fab8f --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Stim.pm @@ -0,0 +1,196 @@ +#------------------------------------------------------------------------------ +# File: Stim.pm +# +# Description: Definitions for Stereo Still Image tags +# +# Revisions: 06/12/2009 - P. Harvey Created +# +# References: 1) http://www.cipa.jp/std/documents/e/DC-006_E.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::Stim; + +use strict; +use vars qw($VERSION); + +$VERSION = '1.01'; + +# Tags found in APP3 Stim segment in JPEG images +%Image::ExifTool::Stim::Main = ( + GROUPS => { 0 => 'Stim', 1 => 'Stim', 2 => 'Image'}, + NOTES => q{ + These tags are part of the CIPA Stereo Still Image specification, and are + found in the APP3 "Stim" segment of JPEG images. See + L<https://web.archive.org/web/20190718152459/http://www.cipa.jp/std/documents/e/DC-006_E.pdf> + for the official specification. + }, + 0 => 'StimVersion', + 1 => { + Name => 'ApplicationData', + Binary => 1, + }, + 2 => { + Name => 'ImageArrangement', + PrintConv => { + 0 => 'Parallel View Alignment', + 1 => 'Cross View Alignment', + }, + }, + 3 => { + Name => 'ImageRotation', + PrintConv => { + 1 => 'None', + }, + }, + 4 => 'ScalingFactor', + 5 => 'CropXSize', + 6 => 'CropYSize', + 7 => { + Name => 'CropX', + SubDirectory => { + TagTable => 'Image::ExifTool::Stim::CropX', + }, + }, + 8 => { + Name => 'CropY', + SubDirectory => { + TagTable => 'Image::ExifTool::Stim::CropY', + }, + }, + 9 => { + Name => 'ViewType', + PrintConv => { + 0 => 'No Pop-up Effect', + 1 => 'Pop-up Effect', + }, + }, + 10 => { + Name => 'RepresentativeImage', + PrintConv => { + 0 => 'Left Viewpoint', + 1 => 'Right Viewpoint', + }, + }, + 11 => { + Name => 'ConvergenceBaseImage', + PrintConv => { + 0 => 'Left Viewpoint', + 1 => 'Right Viewpoint', + 255 => 'Equivalent for Both Viewpoints', + }, + }, + 12 => { + Name => 'AssumedDisplaySize', + PrintConv => '"$val mm"', + }, + 13 => { + Name => 'AssumedDistanceView', + PrintConv => '"$val mm"', + }, + 14 => 'RepresentativeDisparityNear', + 15 => 'RepresentativeDisparityFar', + 16 => { + Name => 'InitialDisplayEffect', + PrintConv => { + 0 => 'Off', + 1 => 'On', + }, + }, + 17 => { + Name => 'ConvergenceDistance', + PrintConv => '$val ? "$val mm" : "inf"', + }, + 18 => { + Name => 'CameraArrangementInterval', + PrintConv => '"$val mm"', + }, + 19 => 'ShootingCount', +); + +# crop offset X tags +%Image::ExifTool::Stim::CropX = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'Stim', 1 => 'Stim', 2 => 'Image'}, + 0 => { + Name => 'CropXCommonOffset', + Format => 'int16u', + PrintConv => { + 0 => 'Common Offset Setting', + 1 => 'Individual Offset Setting', + }, + }, + 2 => 'CropXViewpointNumber', + 3 => { + Name => 'CropXOffset', + Format => 'int32s', + }, + 7 => 'CropXViewpointNumber2', + 8 => { + Name => 'CropXOffset2', + Format => 'int32s', + }, +); + +# crop offset Y tags +%Image::ExifTool::Stim::CropY = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'Stim', 1 => 'Stim', 2 => 'Image'}, + 0 => { + Name => 'CropYCommonOffset', + Format => 'int16u', + PrintConv => { + 0 => 'Common Offset Setting', + 1 => 'Individual Offset Setting', + }, + }, + 2 => 'CropYViewpointNumber', + 3 => { + Name => 'CropYOffset', + Format => 'int32s', + }, + 7 => 'CropYViewpointNumber2', + 8 => { + Name => 'CropYOffset2', + Format => 'int32s', + }, +); + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Stim - Definitions for Stereo Still Image tags + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains tag definitions for Stereo Still Image format (Stim) +information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.cipa.jp/std/documents/e/DC-006_E.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Stim Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/TagInfoXML.pm b/ExifTool/lib/Image/ExifTool/TagInfoXML.pm new file mode 100644 index 0000000..5e3ff0d --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/TagInfoXML.pm @@ -0,0 +1,847 @@ +#------------------------------------------------------------------------------ +# File: TagInfoXML.pm +# +# Description: Read/write tag information XML database +# +# Revisions: 2009/01/28 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::TagInfoXML; + +use strict; +require Exporter; + +use vars qw($VERSION @ISA $makeMissing); +use Image::ExifTool qw(:Utils :Vars); +use Image::ExifTool::XMP; + +$VERSION = '1.35'; +@ISA = qw(Exporter); + +# set this to a language code to generate Lang module with 'MISSING' entries +$makeMissing = ''; + +sub LoadLangModules($;$); +sub WriteLangModule($$;$); +sub NumbersFirst; + +# names for acknowledgements in the POD documentation +my %credits = ( + cs => 'Jens Duttke and Petr MichE<aacute>lek', + de => 'Jens Duttke, Herbert Kauer and Jobi', + es => 'Jens Duttke, Santiago del BrE<iacute>o GonzE<aacute>lez and Emilio Sancha', + fi => 'Jens Duttke and Jarkko ME<auml>kineva', + fr => 'Jens Duttke, Bernard Guillotin, Jean Glasser, Jean Piquemal, Harry Nizard, Alphonse Philippe and Philippe Bonnaure (GraphicConverter)', + it => 'Jens Duttke, Ferdinando Agovino, Emilio Dati and Michele Locati', + ja => 'Jens Duttke and Kazunari Nishina', + ko => 'Jens Duttke and Jeong Beom Kim', + nl => 'Jens Duttke, Peter Moonen, Herman Beld and Peter van der Laan', + pl => 'Jens Duttke, Przemyslaw Sulek and Kacper Perschke', + ru => 'Jens Duttke, Sergey Shemetov, Dmitry Yerokhin, Anton Sukhinov and Alexander', + sk => 'Peter Bagin', + sv => 'Jens Duttke and BjE<ouml>rn SE<ouml>derstrE<ouml>m', + 'tr' => 'Jens Duttke, Hasan Yildirim and Cihan Ulusoy', + zh_cn => 'Jens Duttke and Haibing Zhong', + zh_tw => 'Jens Duttke and MikeF', +); + +# translate country codes to language codes +my %translateLang = ( + ch_s => 'zh_cn', + ch_cn => 'zh_cn', + ch_tw => 'zh_tw', + cz => 'cs', + jp => 'ja', + kr => 'ko', + se => 'sv', +); + +my $numbersFirst = 1; # set to -1 to sort numbers last, or 2 to put negative numbers last +my $caseInsensitive; # used internally by sort routine + +# write groups that don't represent real family 1 group names +my %fakeWriteGroup = ( + Comment => 1, # (JPEG Comment) + colr => 1, # (Jpeg2000 'colr' box) +); + +#------------------------------------------------------------------------------ +# Utility to print tag information database as an XML list +# Inputs: 0) output file name (undef to send to console), +# 1) group name (may be undef), 2) options hash ('Flags','NoDesc','Lang') +# Returns: true on success +sub Write(;$$%) +{ + local ($_, *PTIFILE); + my ($file, $group, %opts) = @_; + my $et = new Image::ExifTool; + my ($fp, $tableName, %langInfo, @langs, $defaultLang, @groups); + @groups = split ':', $group if $group; + + Image::ExifTool::LoadAllTables(); # first load all our tables + unless ($opts{NoDesc}) { + $defaultLang = $Image::ExifTool::defaultLang; + LoadLangModules(\%langInfo, $opts{Lang}); # load necessary Lang modules + if ($opts{Lang}) { + @langs = grep /^$opts{Lang}$/i, keys %langInfo; + } else { + @langs = sort keys %langInfo; + } + } + if (defined $file) { + open PTIFILE, ">$file" or return 0; + $fp = \*PTIFILE; + } else { + $fp = \*STDOUT; + } + print $fp "<?xml version='1.0' encoding='UTF-8'?>\n"; + print $fp "<!-- Generated by Image::ExifTool $Image::ExifTool::VERSION -->\n"; + print $fp "<taginfo>\n\n"; + + # loop through all tables and save tag names to %allTags hash + foreach $tableName (sort keys %allTables) { + my $table = GetTagTable($tableName); + my $grps = $$table{GROUPS}; + my ($tagID, $didTag); + # sort in same order as tag name documentation + $caseInsensitive = ($tableName =~ /::XMP::/); + # get list of languages defining elements in this table + my $isBinary = ($$table{PROCESS_PROC} and + $$table{PROCESS_PROC} eq \&Image::ExifTool::ProcessBinaryData); + # generate flattened tag names for structure fields if this is an XMP table + if ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP') { + Image::ExifTool::XMP::AddFlattenedTags($table); + } + $numbersFirst = 2; + $numbersFirst = -1 if $$table{VARS} and $$table{VARS}{ALPHA_FIRST}; + my @keys = sort NumbersFirst TagTableKeys($table); + $numbersFirst = 1; + # loop through all tag ID's in this table + foreach $tagID (@keys) { + my @infoArray = GetTagInfoList($table, $tagID); + my $xmlID = Image::ExifTool::XMP::FullEscapeXML($tagID); + # get a list of languages defining elements for this ID + my ($index, $fam); +PTILoop: for ($index=0; $index<@infoArray; ++$index) { + my $tagInfo = $infoArray[$index]; + # don't list subdirectories unless they are writable + next unless $$tagInfo{Writable} or not $$tagInfo{SubDirectory}; + if (@groups) { + my @tg = $et->GetGroup($tagInfo); + foreach $group (@groups) { + next PTILoop unless grep /^$group$/i, @tg; + } + } + unless ($didTag) { + my $tname = $$table{SHORT_NAME}; + print $fp "<table name='${tname}' g0='$$grps{0}' g1='$$grps{1}' g2='$$grps{2}'>\n"; + unless ($opts{NoDesc}) { + # print table description + my $desc = $$table{TABLE_DESC}; + unless ($desc) { + ($desc = $tname) =~ s/::Main$//; + $desc =~ s/::/ /g; + } + # print alternate language descriptions + print $fp " <desc lang='en'>$desc</desc>\n"; + foreach (@langs) { + $desc = $langInfo{$_}{$tableName} or next; + $desc = Image::ExifTool::XMP::EscapeXML($desc); + print $fp " <desc lang='${_}'>$desc</desc>\n"; + } + } + $didTag = 1; + } + my $name = $$tagInfo{Name}; + my $ind = @infoArray > 1 ? " index='${index}'" : ''; + my $format = $$tagInfo{Writable} || $$table{WRITABLE}; + my $writable = $format ? 'true' : 'false'; + # check our conversions to make sure we can really write this tag + if ($writable eq 'true') { + foreach ('PrintConv','ValueConv') { + next unless $$tagInfo{$_}; + next if $$tagInfo{$_ . 'Inv'}; + next if ref($$tagInfo{$_}) =~ /^(HASH|ARRAY)$/; + next if $$tagInfo{WriteAlso}; + $writable = 'false'; + last; + } + } + $format = $$tagInfo{Format} || $$table{FORMAT} if not defined $format or $format eq '1'; + $format = 'struct' if $$tagInfo{Struct}; + if (defined $format) { + $format =~ s/\[.*\$.*\]//; # remove expressions from format + $format = 'undef' if $format eq '2'; # (special case) + } elsif ($isBinary) { + $format = 'int8u'; + } else { + $format = '?'; + } + my $count = ''; + if ($format =~ s/\[.*?(\d*)\]$//) { + $count = " count='${1}'" if length $1; + } elsif ($$tagInfo{Count} and $$tagInfo{Count} > 1) { + $count = " count='$$tagInfo{Count}'"; + } + my @groups = $et->GetGroup($tagInfo); + my $writeGroup = $$tagInfo{WriteGroup} || $$table{WRITE_GROUP}; + # use common write group for group 1 (unless fake) + $groups[1] = $writeGroup if $writeGroup and not $fakeWriteGroup{$writeGroup}; + # add group names if different from table defaults + my $grp = ''; + for ($fam=0; $fam<3; ++$fam) { + $grp .= " g$fam='$groups[$fam]'" if $groups[$fam] ne $$grps{$fam}; + } + # add flags if necessary + if ($opts{Flags}) { + my @flags; + foreach (qw(Avoid Binary List Mandatory Unknown)) { + push @flags, $_ if $$tagInfo{$_}; + } + push @flags, $$tagInfo{List} if $$tagInfo{List} and $$tagInfo{List} =~ /^(Alt|Bag|Seq)$/; + push @flags, 'Flattened' if defined $$tagInfo{Flat}; + push @flags, 'Unsafe' if $$tagInfo{Protected} and $$tagInfo{Protected} & 0x01; + push @flags, 'Protected' if $$tagInfo{Protected} and $$tagInfo{Protected} & 0x02; + push @flags, 'Permanent' if $$tagInfo{Permanent} or + ($groups[0] eq 'MakerNotes' and not defined $$tagInfo{Permanent}); + $grp = " flags='" . join(',', sort @flags) . "'$grp" if @flags; + # add parent structure tag ID + $grp .= " struct='$$tagInfo{ParentTagInfo}{TagID}'" if $$tagInfo{ParentTagInfo}; + } + print $fp " <tag id='${xmlID}' name='${name}'$ind type='${format}'$count writable='${writable}'$grp"; + if ($opts{NoDesc}) { + # short output format + print $fp "/>\n"; # empty tag element + next; # no descriptions or values + } else { + print $fp ">"; + } + my $desc = $$tagInfo{Description}; + $desc = Image::ExifTool::MakeDescription($name) unless defined $desc; + # add alternate language descriptions and get references + # to alternate language PrintConv hashes + my $altDescr = ''; + my %langConv; + foreach (@langs) { + my $ld = $langInfo{$_}{$name} or next; + if (ref $ld) { + $langConv{$_} = $$ld{PrintConv}; + $ld = $$ld{Description} or next; + } + # ignore descriptions that are the same as the default language + next if $ld eq $desc; + $ld = Image::ExifTool::XMP::EscapeXML($ld); + $altDescr .= "\n <desc lang='${_}'>$ld</desc>"; + } + # print tag descriptions + $desc = Image::ExifTool::XMP::EscapeXML($desc); + unless ($opts{Lang} and $altDescr) { + print $fp "\n <desc lang='${defaultLang}'>$desc</desc>"; + } + print $fp "$altDescr\n"; + for (my $i=0; ; ++$i) { + my $conv = $$tagInfo{PrintConv}; + my $idx = ''; + if (ref $conv eq 'ARRAY') { + last unless $i < @$conv; + $conv = $$conv[$i]; + $idx = " index='${i}'"; + } else { + last if $i; + } + next unless ref $conv eq 'HASH'; + # make a list of available alternate languages + my @langConv = sort keys %langConv; + print $fp " <values$idx>\n"; + my $key; + $caseInsensitive = 0; + # add bitmask values to main lookup + if ($$conv{BITMASK}) { + foreach $key (keys %{$$conv{BITMASK}}) { + my $mask = 0x01 << $key; + next if not $mask or $$conv{$mask}; + $$conv{$mask} = $$conv{BITMASK}{$key}; + } + } + foreach $key (sort NumbersFirst keys %$conv) { + next if $key eq 'BITMASK' or $key eq 'OTHER' or $key eq 'Notes'; + my $val = $$conv{$key}; + my $xmlVal = Image::ExifTool::XMP::EscapeXML($val); + my $xmlKey = Image::ExifTool::XMP::FullEscapeXML($key); + print $fp " <key id='${xmlKey}'>\n"; + # add alternate language values + my $altConv = ''; + foreach (@langConv) { + my $lv = $langConv{$_}; + # handle indexed PrintConv entries + $lv = $$lv[$i] or next if ref $lv eq 'ARRAY'; + $lv = $$lv{$val}; + # ignore values that are missing or same as default + next unless defined $lv and $lv ne $val; + $lv = Image::ExifTool::XMP::EscapeXML($lv); + $altConv .= " <val lang='${_}'>$lv</val>\n"; + } + unless ($opts{Lang} and $altConv) { + print $fp " <val lang='${defaultLang}'>$xmlVal</val>\n" + } + print $fp "$altConv </key>\n"; + } + print $fp " </values>\n"; + } + print $fp " </tag>\n"; + } + } + print $fp "</table>\n\n" if $didTag; + } + my $success = 1; + print $fp "</taginfo>\n" or $success = 0; + close $fp or $success = 0 if defined $file; + return $success; +} + +#------------------------------------------------------------------------------ +# Escape backslash and quote in string +# Inputs: string +# Returns: escaped string +sub EscapePerl +{ + my $str = shift; + $str =~ s/\\/\\\\/g; + $str =~ s/'/\\'/g; + return $str; +} + +#------------------------------------------------------------------------------ +# Generate Lang modules from input tag info XML database +# Inputs: 0) XML filename, 1) update flags: +# 0x01 = preserve version numbers +# 0x02 = update all modules, even if they didn't change +# 0x04 = update from scratch, ignoring existing definitions +# 0x08 = override existing different descriptions and values +# Returns: Count of updated Lang modules, or -1 on error +# Notes: Must be run from the directory containing 'lib' +sub BuildLangModules($;$) +{ + local ($_, *XFILE); + my ($file, $updateFlag) = @_; + my ($table, $tableName, $id, $index, $valIndex, $name, $key, $lang, $defDesc); + my (%langInfo, %different, %changed, $overrideDifferent); + + Image::ExifTool::LoadAllTables(); # first load all our tables + # generate our flattened tags + foreach $tableName (sort keys %allTables) { + my $table = GetTagTable($tableName); + next unless $$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP'; + Image::ExifTool::XMP::AddFlattenedTags($table); + } + LoadLangModules(\%langInfo); # load all existing Lang modules + $updateFlag = 0 unless $updateFlag; + %langInfo = () if $updateFlag & 0x04; + $overrideDifferent = 1 if $updateFlag & 0x08; + + if (defined $file) { + open XFILE, $file or return -1; + while (<XFILE>) { + next unless /^\s*<(\/?)(\w+)/; + my $tok = $2; + if ($1) { + # close appropriate entities + if ($tok eq 'tag') { + undef $id; + undef $index; + undef $name; + undef $defDesc; + } elsif ($tok eq 'values') { + undef $key; + undef $valIndex; + } elsif ($tok eq 'table') { + undef $table; + undef $id; + } + next; + } + if ($tok eq 'table') { + /^\s*<table name='([^']+)'[ >]/ or warn('Bad table'), next; + $tableName = "Image::ExifTool::$1"; + # ignore userdefined tables + next if $tableName =~ /^Image::ExifTool::UserDefined/; + $table = Image::ExifTool::GetTagTable($tableName); + $table or warn("Unknown tag table $tableName\n"); + next; + } + next unless defined $table; + if ($tok eq 'tag') { + /^\s*<tag id='([^']*)' name='([^']+)'( index='(\d+)')?[ >]/ or warn('Bad tag'), next; + $id = Image::ExifTool::XMP::FullUnescapeXML($1); + $name = $2; + $index = $4; + # convert hex ID's unless HEX_ID is 0 (for string ID's that look like hex) + if ($id =~ /^0x[\da-fA-F]+$/ and (not defined $$table{VARS} or + not defined $$table{VARS}{HEX_ID} or $$table{VARS}{HEX_ID})) + { + $id = hex($id); + } + next; + } + if ($tok eq 'values') { + /^\s*<values index='([^']*)'>/ or next; + $valIndex = $1; + } elsif ($tok eq 'key') { + defined $id or warn('No ID'), next; + /^\s*<key id='([^']*)'>/ or warn('Bad key'), next; + $key = Image::ExifTool::XMP::FullUnescapeXML($1); + $key = hex($key) if $key =~ /^0x[\da-fA-F]+$/; # convert hex keys + } elsif ($tok eq 'val' or $tok eq 'desc') { + /^\s*<$tok( lang='([-\w]+?)')?>(.*)<\/$tok>/ or warn("Bad $tok"), next; + $tok eq 'desc' and defined $key and warn('Out of order "desc"'), next; + my $lang = $2 or next; # looking only for alternate languages + $lang =~ tr/-A-Z/_a-z/; + # use standard ISO 639-1 language codes + $lang = $translateLang{$lang} if $translateLang{$lang}; + my $tval = Image::ExifTool::XMP::UnescapeXML($3); + my $val = ucfirst $tval; + $val = $tval if $tval =~ /^(cRAW|iTun)/; # special-case non-capitalized values + my $cap = ($tval ne $val); + if ($makeMissing) { + if ($lang eq 'en') { + $lang = $makeMissing; + $val = 'MISSING'; + undef $cap; + } + } elsif ($val eq 'MISSING') { + next; # ignore "MISSING" entries + } + my $isDefault = ($lang eq $Image::ExifTool::defaultLang); + unless ($langInfo{$lang} or $isDefault) { + print "Creating new language $lang\n"; + $langInfo{$lang} = { }; + } + defined $name or $name = '<unknown>'; + unless (defined $id) { + next if $isDefault; + # this is a table description + next if $langInfo{$lang}{$tableName} and + $langInfo{$lang}{$tableName} eq $val; + $langInfo{$lang}{$tableName} = $val; + $changed{$lang} = 1; + warn("Capitalized '${lang}' val for $name: $val\n") if $cap; + next; + } + my @infoArray = GetTagInfoList($table, $id); + + # this will fail for UserDefined tags and tags without ID's + @infoArray or warn("Error loading tag for $tableName ID='${id}'\n"), next; + my ($tagInfo, $langInfo); + if (defined $index) { + $tagInfo = $infoArray[$index]; + $tagInfo or warn('Invalid index'), next; + } else { + @infoArray > 1 and warn('Missing index'), next; + $tagInfo = $infoArray[0]; + } + my $tagName = $$tagInfo{Name}; + if ($isDefault) { + unless ($$tagInfo{Description}) { + $$tagInfo{Description} = Image::ExifTool::MakeDescription($tagName); + } + $defDesc = $$tagInfo{Description}; + $langInfo = $tagInfo; + } else { + $langInfo = $langInfo{$lang}{$tagName}; + if (not defined $langInfo) { + $langInfo = $langInfo{$lang}{$tagName} = { }; + } elsif (not ref $langInfo) { + $langInfo = $langInfo{$lang}{$tagName} = { Description => $langInfo }; + } + } + # save new value in langInfo record + if ($tok eq 'desc') { + my $oldVal = $$langInfo{Description}; + next if defined $oldVal and $oldVal eq $val; + if ($makeMissing) { + next if defined $oldVal and $val eq 'MISSING'; + } elsif (defined $oldVal) { + my $t = "$lang $tagName"; + unless (defined $different{$t} and $different{$t} eq $val) { + my $a = defined $different{$t} ? 'ANOTHER ' : ''; + warn "${a}Different '${lang}' desc for $tagName: $val (was $$langInfo{Description})\n"; + next if defined $different{$t}; # don't change back again + $different{$t} = $val; + } + next unless $overrideDifferent; + } + next if $isDefault; + if (defined $defDesc and $defDesc eq $val) { + delete $$langInfo{Description}; # delete if same as default language + } else { + $$langInfo{Description} = $val; + } + } else { + defined $key or warn("No key for $$tagInfo{Name}"), next; + my $printConv = $$tagInfo{PrintConv}; + if (ref $printConv eq 'ARRAY') { + defined $valIndex or warn('No value index'), next; + $printConv = $$printConv[$valIndex]; + } + ref $printConv eq 'HASH' or warn('No PrintConv'), next; + my $convVal = $$printConv{$key}; + unless (defined $convVal) { + if ($$printConv{BITMASK} and $key =~ /^\d+$/) { + my $i; + for ($i=0; $i<64; ++$i) { + my $mask = (0x01 << $i) or last; + next unless $key == $mask; + $convVal = $$printConv{BITMASK}{$i}; + } + } + warn("Missing PrintConv entry for $tableName $$tagInfo{Name} $key\n") and next unless defined $convVal; + } + if ($cap and $convVal =~ /^[a-z]/) { + $val = lcfirst $val; # change back to lower case + undef $cap; + } + my $lc = $$langInfo{PrintConv}; + $lc or $lc = $$langInfo{PrintConv} = { }; + $lc = $printConv if ref $lc eq 'ARRAY'; #(default lang only) + my $oldVal = $$lc{$convVal}; + next if defined $oldVal and $oldVal eq $val; + if ($makeMissing) { + next if defined $oldVal and $val eq 'MISSING'; + } elsif (defined $oldVal and (not $isDefault or not $val=~/^\d+$/)) { + my $t = "$lang $tagName $convVal"; + unless (defined $different{$t} and $different{$t} eq $val) { + my $a = defined $different{$t} ? 'ANOTHER ' : ''; + warn "${a}Different '${lang}' val for $tagName '${convVal}': $val (was $oldVal)\n"; + next if defined $different{$t}; # don't change back again + $different{$t} = $val; + } + next unless $overrideDifferent; + } + next if $isDefault; + warn("Capitalized '${lang}' val for $tagName: $tval\n") if $cap; + $$lc{$convVal} = $val; + } + $changed{$lang} = 1; + } + } + close XFILE; + } + # rewrite all changed Lang modules + my $rtnVal = 0; + foreach $lang ($updateFlag & 0x02 ? @Image::ExifTool::langs : sort keys %changed) { + next if $lang eq $Image::ExifTool::defaultLang; + ++$rtnVal; + # write this module (only increment version number if not forced) + WriteLangModule($lang, $langInfo{$lang}, not $updateFlag & 0x01) or $rtnVal = -1, last; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Write Lang module +# Inputs: 0) language string, 1) langInfo lookup reference, 2) flag to increment version +# Returns: true on success +sub WriteLangModule($$;$) +{ + local ($_, *XOUT); + my ($lang, $langTags, $newVersion) = @_; + my $err; + -e "lib/Image/ExifTool" or die "Must run from directory containing 'lib'\n"; + my $out = "lib/Image/ExifTool/Lang/$lang.pm"; + my $tmp = "$out.tmp"; + open XOUT, ">$tmp" or die "Error creating $tmp\n"; + my $ver = "Image::ExifTool::Lang::${lang}::VERSION"; + no strict 'refs'; + if ($$ver) { + $ver = $$ver; + $ver = int($ver * 100 + 1.5) / 100 if $newVersion; + } else { + $ver = 1.0; + } + $ver = sprintf('%.2f', $ver); + use strict 'refs'; + my $langName = $Image::ExifTool::langName{$lang} || $lang; + $langName =~ s/\s*\(.*//; + print XOUT <<HEADER; +#------------------------------------------------------------------------------ +# File: $lang.pm +# +# Description: ExifTool $langName language translations +# +# Notes: This file generated automatically by Image::ExifTool::TagInfoXML +#------------------------------------------------------------------------------ + +package Image::ExifTool::Lang::$lang; + +use strict; +use vars qw(\$VERSION); + +\$VERSION = '${ver}'; + +HEADER + print XOUT "\%Image::ExifTool::Lang::${lang}::Translate = (\n"; + # loop through all tag and table names + my $tag; + foreach $tag (sort keys %$langTags) { + my $desc = $$langTags{$tag}; + my $conv; + if (ref $desc) { + $conv = $$desc{PrintConv}; + $desc = $$desc{Description}; + # remove description if not necessary + # (not strictly correct -- should test against tag description, not name) + undef $desc if $desc and $desc eq $tag; + # remove unnecessary value translations + if ($conv) { + my @keys = keys %$conv; + foreach (@keys) { + delete $$conv{$_} if $_ eq $$conv{$_}; + } + undef $conv unless %$conv; + } + } + if (defined $desc) { + $desc = EscapePerl($desc); + } else { + next unless $conv; + } + print XOUT " '${tag}' => "; + unless ($conv) { + print XOUT "'${desc}',\n"; + next; + } + print XOUT "{\n"; + print XOUT " Description => '${desc}',\n" if defined $desc; + if ($conv) { + print XOUT " PrintConv => {\n"; + foreach (sort keys %$conv) { + my $str = EscapePerl($_); + my $val = EscapePerl($$conv{$_}); + print XOUT " '${str}' => '${val}',\n"; + } + print XOUT " },\n"; + } + print XOUT " },\n"; + } + # generate acknowledgements for this language + my $ack; + if ($credits{$lang}) { + $ack = "Thanks to $credits{$lang} for providing this translation."; + $ack =~ s/(.{1,76})( +|$)/$1\n/sg; # wrap text to 76 columns + $ack = "~head1 ACKNOWLEDGEMENTS\n\n$ack\n"; + } else { + $ack = ''; + } + my $footer = <<FOOTER; +); + +1; # end + +__END__ + +~head1 NAME + +Image::ExifTool::Lang::$lang.pm - ExifTool $langName language translations + +~head1 DESCRIPTION + +This file is used by Image::ExifTool to generate localized tag descriptions +and values. + +~head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +$ack~head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagInfoXML(3pm)|Image::ExifTool::TagInfoXML> + +~cut +FOOTER + $footer =~ s/^~/=/mg; # un-do pod obfuscation + print XOUT $footer or $err = 1; + close XOUT or $err = 1; + if ($err or not rename($tmp, $out)) { + warn "Error writing $out\n"; + unlink $tmp; + $err = 1; + } + return $err ? 0 : 1; +} + +#------------------------------------------------------------------------------ +# load all lang modules into hash +# Inputs: 0) Hash reference, 1) specific language to load (undef for all) +sub LoadLangModules($;$) +{ + my ($langHash, $lang) = @_; + require Image::ExifTool; + my @langs = $lang ? ($lang) : @Image::ExifTool::langs; + foreach $lang (@langs) { + next if $lang eq $Image::ExifTool::defaultLang; + eval "require Image::ExifTool::Lang::$lang" or warn("Can't load Lang::$lang\n"), next; + my $xlat = "Image::ExifTool::Lang::${lang}::Translate"; + no strict 'refs'; + %$xlat or warn("Missing Info for $lang\n"), next; + $$langHash{$lang} = \%$xlat; + use strict 'refs'; + } +} + +#------------------------------------------------------------------------------ +# sort numbers first numerically, then strings alphabetically (case insensitive) +sub NumbersFirst +{ + my $rtnVal; + my ($bNum, $bDec); + ($bNum, $bDec) = ($1, $3) if $b =~ /^(-?[0-9]+)(\.(\d*))?$/; + if ($a =~ /^(-?[0-9]+)(\.(\d*))?$/) { + if (defined $bNum) { + $bNum += 1e9 if $numbersFirst == 2 and $bNum < 0; + my $aInt = $1; + $aInt += 1e9 if $numbersFirst == 2 and $aInt < 0; + # compare integer part as a number + $rtnVal = $aInt <=> $bNum; + unless ($rtnVal) { + my $aDec = $3 || 0; + $bDec or $bDec = 0; + # compare decimal part as an integer too + # (so that "1.10" comes after "1.9") + $rtnVal = $aDec <=> $bDec; + } + } else { + $rtnVal = -$numbersFirst; + } + } elsif (defined $bNum) { + $rtnVal = $numbersFirst; + } else { + my ($a2, $b2) = ($a, $b); + # expand numbers to 3 digits (with restrictions to avoid messing up ascii-hex tags) + $a2 =~ s/(\d+)/sprintf("%.3d",$1)/eg if $a2 =~ /^(APP|DMC-\w+ )?[.0-9 ]*$/ and length($a2)<16; + $b2 =~ s/(\d+)/sprintf("%.3d",$1)/eg if $b2 =~ /^(APP|DMC-\w+ )?[.0-9 ]*$/ and length($b2)<16; + $caseInsensitive and $rtnVal = (lc($a2) cmp lc($b2)); + $rtnVal or $rtnVal = ($a2 cmp $b2); + } + return $rtnVal; +} + +1; # end + + +__END__ + +=head1 NAME + +Image::ExifTool::TagInfoXML - Read/write tag information XML database + +=head1 DESCRIPTION + +This module is used to generate an XML database from all ExifTool tag +information. The XML database may then be edited and used to re-generate +the language modules (Image::ExifTool::Lang::*). + +=head1 METHODS + +=head2 Write + +Print complete tag information database in XML format. + + # save list of all tags + $success = Image::ExifTool::TagInfoXML::Write('dst.xml'); + + # list all IPTC tags to console, including Flags + Image::ExifTool::TagInfoXML::Write(undef, 'IPTC', Flags => 1); + + # write all EXIF Camera tags to file + Image::ExifTool::TagInfoXML::Write($outfile, 'exif:camera'); + +=over 4 + +=item Inputs: + +0) [optional] Output file name, or undef for console output. Output file +will be overwritten if it already exists. + +1) [optional] String of group names separated by colons to specify the group +to print. A specific IFD may not be given as a group, since EXIF tags may +be written to any IFD. Saves all groups if not specified. + +2) [optional] Hash of options values: + + Flags - Set to output 'flags' attribute + NoDesc - Set to suppress output of descriptions + Lang - Select a single language for output + +=item Return Value: + +True on success. + +=item Sample XML Output: + +=back + + <?xml version='1.0' encoding='UTF-8'?> + <taginfo> + + <table name='XMP::dc' g0='XMP' g1='XMP-dc' g2='Other'> + <desc lang='en'>XMP Dublin Core</desc> + <tag id='title' name='Title' type='lang-alt' writable='true' g2='Image'> + <desc lang='en'>Title</desc> + <desc lang='de'>Titel</desc> + <desc lang='fr'>Titre</desc> + </tag> + ... + </table> + + </taginfo> + +Flags (if selected and available) are formatted as a comma-separated list of +the following possible values: Avoid, Binary, List, Mandatory, Permanent, +Protected, Unknown and Unsafe. See the +L<tag name documentation|Image::ExifTool::TagNames> and +lib/Image/ExifTool/README for a description of these flags. For XMP List +tags, the list type (Alt, Bag or Seq) is also output as a flag if +applicable. + +=head2 BuildLangModules + +Build all Image::ExifTool::Lang modules from an XML database file. + + Image::ExifTool::TagInfoXML::BuildLangModules('src.xml'); + +=over 4 + +=item Inputs: + +0) XML file name + +1) Update flags: + + 0x01 = preserve version numbers + 0x02 = update all modules, even if they didn't change + 0x04 = update from scratch, ignoring existing definitions + 0x08 = override existing different descriptions and values + +=item Return Value: + +Number of modules updated, or negative on error. + +=back + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagNames(3pm)|Image::ExifTool::TagNames> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/TagLookup.pm b/ExifTool/lib/Image/ExifTool/TagLookup.pm new file mode 100644 index 0000000..25a68be --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/TagLookup.pm @@ -0,0 +1,12974 @@ +#------------------------------------------------------------------------------ +# File: TagLookup.pm +# +# Description: Fast lookup for ExifTool tags +# +# Notes: This lookup is used to look up tag names for writing +# +# Revisions: 2004-12-31 - P. Harvey Created +# 2013-01-06 - PH Allow wildcards in FindTagInfo() +#------------------------------------------------------------------------------ + +package Image::ExifTool::TagLookup; + +use strict; +require Exporter; + +use vars qw($VERSION @ISA @EXPORT_OK); +use Image::ExifTool qw(:Utils); + +$VERSION = '1.19'; +@ISA = qw(Exporter); +@EXPORT_OK = qw(FindTagInfo TagExists); + +sub AddTags($$); +sub AddFields($$$$$$;$$); + +# +# Note: the lists below were generated by Image::ExifTool::BuildTagLookup +# + +#++++ Begin automatically generated code ++++ + +# list of tables containing writable tags +my @tableList = ( + 'Image::ExifTool::APP12::Ducky', + 'Image::ExifTool::Apple::Main', + 'Image::ExifTool::Canon::AFConfig', + 'Image::ExifTool::Canon::AFMicroAdj', + 'Image::ExifTool::Canon::Ambience', + 'Image::ExifTool::Canon::AspectInfo', + 'Image::ExifTool::Canon::CNTH', + 'Image::ExifTool::Canon::CameraInfo1000D', + 'Image::ExifTool::Canon::CameraInfo1D', + 'Image::ExifTool::Canon::CameraInfo1DX', + 'Image::ExifTool::Canon::CameraInfo1DmkII', + 'Image::ExifTool::Canon::CameraInfo1DmkIII', + 'Image::ExifTool::Canon::CameraInfo1DmkIIN', + 'Image::ExifTool::Canon::CameraInfo1DmkIV', + 'Image::ExifTool::Canon::CameraInfo40D', + 'Image::ExifTool::Canon::CameraInfo450D', + 'Image::ExifTool::Canon::CameraInfo500D', + 'Image::ExifTool::Canon::CameraInfo50D', + 'Image::ExifTool::Canon::CameraInfo550D', + 'Image::ExifTool::Canon::CameraInfo5D', + 'Image::ExifTool::Canon::CameraInfo5DmkII', + 'Image::ExifTool::Canon::CameraInfo5DmkIII', + 'Image::ExifTool::Canon::CameraInfo600D', + 'Image::ExifTool::Canon::CameraInfo60D', + 'Image::ExifTool::Canon::CameraInfo650D', + 'Image::ExifTool::Canon::CameraInfo6D', + 'Image::ExifTool::Canon::CameraInfo70D', + 'Image::ExifTool::Canon::CameraInfo750D', + 'Image::ExifTool::Canon::CameraInfo7D', + 'Image::ExifTool::Canon::CameraInfo80D', + 'Image::ExifTool::Canon::CameraInfoPowerShot', + 'Image::ExifTool::Canon::CameraInfoPowerShot2', + 'Image::ExifTool::Canon::CameraInfoUnknown', + 'Image::ExifTool::Canon::CameraInfoUnknown32', + 'Image::ExifTool::Canon::CameraSettings', + 'Image::ExifTool::Canon::ColorBalance', + 'Image::ExifTool::Canon::ColorCalib', + 'Image::ExifTool::Canon::ColorCalib2', + 'Image::ExifTool::Canon::ColorCoefs', + 'Image::ExifTool::Canon::ColorCoefs2', + 'Image::ExifTool::Canon::ColorData1', + 'Image::ExifTool::Canon::ColorData10', + 'Image::ExifTool::Canon::ColorData11', + 'Image::ExifTool::Canon::ColorData2', + 'Image::ExifTool::Canon::ColorData3', + 'Image::ExifTool::Canon::ColorData4', + 'Image::ExifTool::Canon::ColorData5', + 'Image::ExifTool::Canon::ColorData6', + 'Image::ExifTool::Canon::ColorData7', + 'Image::ExifTool::Canon::ColorData8', + 'Image::ExifTool::Canon::ColorData9', + 'Image::ExifTool::Canon::ColorInfo', + 'Image::ExifTool::Canon::ContrastInfo', + 'Image::ExifTool::Canon::CropInfo', + 'Image::ExifTool::Canon::FaceDetect1', + 'Image::ExifTool::Canon::FaceDetect2', + 'Image::ExifTool::Canon::FaceDetect3', + 'Image::ExifTool::Canon::FileInfo', + 'Image::ExifTool::Canon::Flags', + 'Image::ExifTool::Canon::FocalLength', + 'Image::ExifTool::Canon::HDRInfo', + 'Image::ExifTool::Canon::LensInfo', + 'Image::ExifTool::Canon::LightingOpt', + 'Image::ExifTool::Canon::LogInfo', + 'Image::ExifTool::Canon::Main', + 'Image::ExifTool::Canon::MeasuredColor', + 'Image::ExifTool::Canon::ModifiedInfo', + 'Image::ExifTool::Canon::MovieInfo', + 'Image::ExifTool::Canon::MultiExp', + 'Image::ExifTool::Canon::MyColors', + 'Image::ExifTool::Canon::PSInfo', + 'Image::ExifTool::Canon::PSInfo2', + 'Image::ExifTool::Canon::Panorama', + 'Image::ExifTool::Canon::PreviewImageInfo', + 'Image::ExifTool::Canon::Processing', + 'Image::ExifTool::Canon::RawBurstInfo', + 'Image::ExifTool::Canon::SerialInfo', + 'Image::ExifTool::Canon::ShotInfo', + 'Image::ExifTool::Canon::TimeInfo', + 'Image::ExifTool::Canon::VignettingCorr', + 'Image::ExifTool::Canon::VignettingCorr2', + 'Image::ExifTool::Canon::WBInfo', + 'Image::ExifTool::CanonCustom::Functions10D', + 'Image::ExifTool::CanonCustom::Functions1D', + 'Image::ExifTool::CanonCustom::Functions2', + 'Image::ExifTool::CanonCustom::Functions20D', + 'Image::ExifTool::CanonCustom::Functions30D', + 'Image::ExifTool::CanonCustom::Functions350D', + 'Image::ExifTool::CanonCustom::Functions400D', + 'Image::ExifTool::CanonCustom::Functions5D', + 'Image::ExifTool::CanonCustom::FunctionsD30', + 'Image::ExifTool::CanonCustom::PersonalFuncValues', + 'Image::ExifTool::CanonCustom::PersonalFuncs', + 'Image::ExifTool::CanonRaw::ExposureInfo', + 'Image::ExifTool::CanonRaw::FlashInfo', + 'Image::ExifTool::CanonRaw::ImageFormat', + 'Image::ExifTool::CanonRaw::ImageInfo', + 'Image::ExifTool::CanonRaw::Main', + 'Image::ExifTool::CanonRaw::MakeModel', + 'Image::ExifTool::CanonRaw::RawJpgInfo', + 'Image::ExifTool::CanonRaw::TimeStamp', + 'Image::ExifTool::CanonVRD::CropInfo', + 'Image::ExifTool::CanonVRD::DLOInfo', + 'Image::ExifTool::CanonVRD::DR4', + 'Image::ExifTool::CanonVRD::DR4Header', + 'Image::ExifTool::CanonVRD::GammaInfo', + 'Image::ExifTool::CanonVRD::Main', + 'Image::ExifTool::CanonVRD::ToneCurve', + 'Image::ExifTool::CanonVRD::Ver1', + 'Image::ExifTool::CanonVRD::Ver2', + 'Image::ExifTool::Casio::FaceInfo1', + 'Image::ExifTool::Casio::FaceInfo2', + 'Image::ExifTool::Casio::Main', + 'Image::ExifTool::Casio::Type2', + 'Image::ExifTool::Composite', + 'Image::ExifTool::DJI::Main', + 'Image::ExifTool::DJI::XMP', + 'Image::ExifTool::DNG::AdobeData', + 'Image::ExifTool::DarwinCore::Main', + 'Image::ExifTool::Exif::Main', + 'Image::ExifTool::Extra', + 'Image::ExifTool::FLIR::Main', + 'Image::ExifTool::FotoStation::Main', + 'Image::ExifTool::FotoStation::SoftEdit', + 'Image::ExifTool::FujiFilm::AFCSettings', + 'Image::ExifTool::FujiFilm::DriveSettings', + 'Image::ExifTool::FujiFilm::FocusSettings', + 'Image::ExifTool::FujiFilm::Main', + 'Image::ExifTool::FujiFilm::PrioritySettings', + 'Image::ExifTool::GE::Main', + 'Image::ExifTool::GPS::Main', + 'Image::ExifTool::IPTC::ApplicationRecord', + 'Image::ExifTool::IPTC::EnvelopeRecord', + 'Image::ExifTool::IPTC::NewsPhoto', + 'Image::ExifTool::JFIF::Main', + 'Image::ExifTool::Jpeg2000::ColorSpec', + 'Image::ExifTool::Jpeg2000::Main', + 'Image::ExifTool::Kodak::CameraInfo', + 'Image::ExifTool::Kodak::IFD', + 'Image::ExifTool::Kodak::KDC_IFD', + 'Image::ExifTool::Kodak::Main', + 'Image::ExifTool::Kodak::Meta', + 'Image::ExifTool::Kodak::SubIFD0', + 'Image::ExifTool::Kodak::SubIFD1', + 'Image::ExifTool::Kodak::SubIFD2', + 'Image::ExifTool::Kodak::SubIFD3', + 'Image::ExifTool::Kodak::SubIFD5', + 'Image::ExifTool::Kodak::Type10', + 'Image::ExifTool::Kodak::Type2', + 'Image::ExifTool::Kodak::Type3', + 'Image::ExifTool::Kodak::Type4', + 'Image::ExifTool::Kodak::Type5', + 'Image::ExifTool::Kodak::Type6', + 'Image::ExifTool::Kodak::Type7', + 'Image::ExifTool::Kodak::Type9', + 'Image::ExifTool::MIE::Audio', + 'Image::ExifTool::MIE::Camera', + 'Image::ExifTool::MIE::Doc', + 'Image::ExifTool::MIE::Extender', + 'Image::ExifTool::MIE::Flash', + 'Image::ExifTool::MIE::GPS', + 'Image::ExifTool::MIE::Geo', + 'Image::ExifTool::MIE::Image', + 'Image::ExifTool::MIE::Lens', + 'Image::ExifTool::MIE::Main', + 'Image::ExifTool::MIE::Orient', + 'Image::ExifTool::MIE::Preview', + 'Image::ExifTool::MIE::Thumbnail', + 'Image::ExifTool::MIE::UTM', + 'Image::ExifTool::MIE::Video', + 'Image::ExifTool::MWG::Collections', + 'Image::ExifTool::MWG::Keywords', + 'Image::ExifTool::MWG::Regions', + 'Image::ExifTool::MacOS::MDItem', + 'Image::ExifTool::MacOS::XAttr', + 'Image::ExifTool::Microsoft::MP', + 'Image::ExifTool::Microsoft::MP1', + 'Image::ExifTool::Microsoft::Stitch', + 'Image::ExifTool::Microsoft::XMP', + 'Image::ExifTool::Microsoft::Xtra', + 'Image::ExifTool::Minolta::CameraInfoA100', + 'Image::ExifTool::Minolta::CameraSettings', + 'Image::ExifTool::Minolta::CameraSettings5D', + 'Image::ExifTool::Minolta::CameraSettings7D', + 'Image::ExifTool::Minolta::CameraSettingsA100', + 'Image::ExifTool::Minolta::ISInfoA100', + 'Image::ExifTool::Minolta::Main', + 'Image::ExifTool::Minolta::WBInfoA100', + 'Image::ExifTool::MinoltaRaw::PRD', + 'Image::ExifTool::MinoltaRaw::RIF', + 'Image::ExifTool::MinoltaRaw::WBG', + 'Image::ExifTool::Motorola::Main', + 'Image::ExifTool::Nikon::AFInfo', + 'Image::ExifTool::Nikon::AFInfo2', + 'Image::ExifTool::Nikon::AFInfo2V0400', + 'Image::ExifTool::Nikon::AFTune', + 'Image::ExifTool::Nikon::AutoCaptureInfo', + 'Image::ExifTool::Nikon::BarometerInfo', + 'Image::ExifTool::Nikon::BracketingInfoD500', + 'Image::ExifTool::Nikon::BracketingInfoD810', + 'Image::ExifTool::Nikon::CaptureOutput', + 'Image::ExifTool::Nikon::ColorBalance1', + 'Image::ExifTool::Nikon::ColorBalance2', + 'Image::ExifTool::Nikon::ColorBalance3', + 'Image::ExifTool::Nikon::ColorBalance4', + 'Image::ExifTool::Nikon::ColorBalanceA', + 'Image::ExifTool::Nikon::ColorBalanceB', + 'Image::ExifTool::Nikon::ColorBalanceC', + 'Image::ExifTool::Nikon::ColorBalanceUnknown', + 'Image::ExifTool::Nikon::DistortInfo', + 'Image::ExifTool::Nikon::FaceDetect', + 'Image::ExifTool::Nikon::FileInfo', + 'Image::ExifTool::Nikon::FlashInfo0100', + 'Image::ExifTool::Nikon::FlashInfo0102', + 'Image::ExifTool::Nikon::FlashInfo0103', + 'Image::ExifTool::Nikon::FlashInfo0106', + 'Image::ExifTool::Nikon::FlashInfo0107', + 'Image::ExifTool::Nikon::FlashInfo0300', + 'Image::ExifTool::Nikon::GEM', + 'Image::ExifTool::Nikon::HDRInfo', + 'Image::ExifTool::Nikon::HDRInfo2', + 'Image::ExifTool::Nikon::ISOAutoInfoD810', + 'Image::ExifTool::Nikon::ISOInfo', + 'Image::ExifTool::Nikon::IntervalInfoD6', + 'Image::ExifTool::Nikon::IntervalInfoZ7II', + 'Image::ExifTool::Nikon::JPGInfoD500', + 'Image::ExifTool::Nikon::LensData00', + 'Image::ExifTool::Nikon::LensData01', + 'Image::ExifTool::Nikon::LensData0204', + 'Image::ExifTool::Nikon::LensData0400', + 'Image::ExifTool::Nikon::LensData0402', + 'Image::ExifTool::Nikon::LensData0403', + 'Image::ExifTool::Nikon::LensData0800', + 'Image::ExifTool::Nikon::LocationInfo', + 'Image::ExifTool::Nikon::Main', + 'Image::ExifTool::Nikon::MakerNotes0x51', + 'Image::ExifTool::Nikon::MakerNotes0x56', + 'Image::ExifTool::Nikon::MenuSettingsD850', + 'Image::ExifTool::Nikon::MenuSettingsZ7II', + 'Image::ExifTool::Nikon::MenuSettingsZ8', + 'Image::ExifTool::Nikon::MenuSettingsZ9', + 'Image::ExifTool::Nikon::MenuSettingsZ9v3', + 'Image::ExifTool::Nikon::MenuSettingsZ9v4', + 'Image::ExifTool::Nikon::MoreSettingsD850', + 'Image::ExifTool::Nikon::MultiExposure', + 'Image::ExifTool::Nikon::MultiExposure2', + 'Image::ExifTool::Nikon::Offset13InfoZ9', + 'Image::ExifTool::Nikon::OrientationInfo', + 'Image::ExifTool::Nikon::OtherInfoD500', + 'Image::ExifTool::Nikon::PictureControl', + 'Image::ExifTool::Nikon::PictureControl2', + 'Image::ExifTool::Nikon::PictureControl3', + 'Image::ExifTool::Nikon::PortraitInfoZ7II', + 'Image::ExifTool::Nikon::PreviewIFD', + 'Image::ExifTool::Nikon::ROC', + 'Image::ExifTool::Nikon::RetouchInfo', + 'Image::ExifTool::Nikon::RotationInfoD500', + 'Image::ExifTool::Nikon::Scan', + 'Image::ExifTool::Nikon::SeqInfoD6', + 'Image::ExifTool::Nikon::SeqInfoZ9', + 'Image::ExifTool::Nikon::SettingsInfoD810', + 'Image::ExifTool::Nikon::ShootingMenuD500', + 'Image::ExifTool::Nikon::ShotInfo', + 'Image::ExifTool::Nikon::ShotInfoD300S', + 'Image::ExifTool::Nikon::ShotInfoD300a', + 'Image::ExifTool::Nikon::ShotInfoD300b', + 'Image::ExifTool::Nikon::ShotInfoD3S', + 'Image::ExifTool::Nikon::ShotInfoD3X', + 'Image::ExifTool::Nikon::ShotInfoD3a', + 'Image::ExifTool::Nikon::ShotInfoD3b', + 'Image::ExifTool::Nikon::ShotInfoD40', + 'Image::ExifTool::Nikon::ShotInfoD4S', + 'Image::ExifTool::Nikon::ShotInfoD5000', + 'Image::ExifTool::Nikon::ShotInfoD5100', + 'Image::ExifTool::Nikon::ShotInfoD5200', + 'Image::ExifTool::Nikon::ShotInfoD700', + 'Image::ExifTool::Nikon::ShotInfoD7000', + 'Image::ExifTool::Nikon::ShotInfoD80', + 'Image::ExifTool::Nikon::ShotInfoD800', + 'Image::ExifTool::Nikon::ShotInfoD90', + 'Image::ExifTool::Nikon::Type2', + 'Image::ExifTool::Nikon::VRInfo', + 'Image::ExifTool::Nikon::WorldTime', + 'Image::ExifTool::NikonCapture::Brightness', + 'Image::ExifTool::NikonCapture::ColorBoost', + 'Image::ExifTool::NikonCapture::CropData', + 'Image::ExifTool::NikonCapture::DLightingHQ', + 'Image::ExifTool::NikonCapture::DLightingHS', + 'Image::ExifTool::NikonCapture::Exposure', + 'Image::ExifTool::NikonCapture::HighlightData', + 'Image::ExifTool::NikonCapture::Main', + 'Image::ExifTool::NikonCapture::NoiseReduction', + 'Image::ExifTool::NikonCapture::PhotoEffects', + 'Image::ExifTool::NikonCapture::PictureCtrl', + 'Image::ExifTool::NikonCapture::RedEyeData', + 'Image::ExifTool::NikonCapture::UnsharpData', + 'Image::ExifTool::NikonCapture::WBAdjData', + 'Image::ExifTool::NikonCustom::SettingsD3', + 'Image::ExifTool::NikonCustom::SettingsD4', + 'Image::ExifTool::NikonCustom::SettingsD40', + 'Image::ExifTool::NikonCustom::SettingsD5', + 'Image::ExifTool::NikonCustom::SettingsD500', + 'Image::ExifTool::NikonCustom::SettingsD5000', + 'Image::ExifTool::NikonCustom::SettingsD5100', + 'Image::ExifTool::NikonCustom::SettingsD5200', + 'Image::ExifTool::NikonCustom::SettingsD610', + 'Image::ExifTool::NikonCustom::SettingsD700', + 'Image::ExifTool::NikonCustom::SettingsD7000', + 'Image::ExifTool::NikonCustom::SettingsD80', + 'Image::ExifTool::NikonCustom::SettingsD800', + 'Image::ExifTool::NikonCustom::SettingsD810', + 'Image::ExifTool::NikonCustom::SettingsD850', + 'Image::ExifTool::NikonCustom::SettingsD90', + 'Image::ExifTool::NikonCustom::SettingsZ8', + 'Image::ExifTool::NikonCustom::SettingsZ9', + 'Image::ExifTool::NikonCustom::SettingsZ9v4', + 'Image::ExifTool::Nintendo::CameraInfo', + 'Image::ExifTool::Olympus::CameraSettings', + 'Image::ExifTool::Olympus::Equipment', + 'Image::ExifTool::Olympus::FETags', + 'Image::ExifTool::Olympus::FocusInfo', + 'Image::ExifTool::Olympus::ImageProcessing', + 'Image::ExifTool::Olympus::Main', + 'Image::ExifTool::Olympus::RawDevelopment', + 'Image::ExifTool::Olympus::RawDevelopment2', + 'Image::ExifTool::Olympus::RawInfo', + 'Image::ExifTool::PDF::Info', + 'Image::ExifTool::PLUS::XMP', + 'Image::ExifTool::PNG::Main', + 'Image::ExifTool::PNG::PhysicalPixel', + 'Image::ExifTool::PNG::TextualData', + 'Image::ExifTool::Panasonic::Data1', + 'Image::ExifTool::Panasonic::FaceDetInfo', + 'Image::ExifTool::Panasonic::FaceRecInfo', + 'Image::ExifTool::Panasonic::FocusInfo', + 'Image::ExifTool::Panasonic::Leica2', + 'Image::ExifTool::Panasonic::Leica3', + 'Image::ExifTool::Panasonic::Leica5', + 'Image::ExifTool::Panasonic::Leica6', + 'Image::ExifTool::Panasonic::Leica9', + 'Image::ExifTool::Panasonic::Main', + 'Image::ExifTool::Panasonic::ShotInfo', + 'Image::ExifTool::Panasonic::Subdir', + 'Image::ExifTool::Panasonic::TimeInfo', + 'Image::ExifTool::PanasonicRaw::DistortionInfo', + 'Image::ExifTool::PanasonicRaw::Main', + 'Image::ExifTool::PanasonicRaw::WBInfo', + 'Image::ExifTool::PanasonicRaw::WBInfo2', + 'Image::ExifTool::Pentax::AEInfo', + 'Image::ExifTool::Pentax::AEInfo2', + 'Image::ExifTool::Pentax::AEInfo3', + 'Image::ExifTool::Pentax::AFInfo', + 'Image::ExifTool::Pentax::AFPointInfo', + 'Image::ExifTool::Pentax::AWBInfo', + 'Image::ExifTool::Pentax::BatteryInfo', + 'Image::ExifTool::Pentax::CameraInfo', + 'Image::ExifTool::Pentax::CameraSettings', + 'Image::ExifTool::Pentax::ColorInfo', + 'Image::ExifTool::Pentax::EVStepInfo', + 'Image::ExifTool::Pentax::FaceInfo', + 'Image::ExifTool::Pentax::FacePos', + 'Image::ExifTool::Pentax::FaceSize', + 'Image::ExifTool::Pentax::FilterInfo', + 'Image::ExifTool::Pentax::FlashInfo', + 'Image::ExifTool::Pentax::KelvinWB', + 'Image::ExifTool::Pentax::LensCorr', + 'Image::ExifTool::Pentax::LensData', + 'Image::ExifTool::Pentax::LensInfo', + 'Image::ExifTool::Pentax::LensInfo2', + 'Image::ExifTool::Pentax::LensInfo3', + 'Image::ExifTool::Pentax::LensInfo4', + 'Image::ExifTool::Pentax::LensInfo5', + 'Image::ExifTool::Pentax::LensInfoQ', + 'Image::ExifTool::Pentax::LensRec', + 'Image::ExifTool::Pentax::LevelInfo', + 'Image::ExifTool::Pentax::Main', + 'Image::ExifTool::Pentax::PixelShiftInfo', + 'Image::ExifTool::Pentax::SRInfo', + 'Image::ExifTool::Pentax::SRInfo2', + 'Image::ExifTool::Pentax::ShotInfo', + 'Image::ExifTool::Pentax::TempInfo', + 'Image::ExifTool::Pentax::TimeInfo', + 'Image::ExifTool::Pentax::Type2', + 'Image::ExifTool::Pentax::WBLevels', + 'Image::ExifTool::PhaseOne::Main', + 'Image::ExifTool::PhaseOne::SensorCalibration', + 'Image::ExifTool::PhotoMechanic::SoftEdit', + 'Image::ExifTool::PhotoMechanic::XMP', + 'Image::ExifTool::Photoshop::JPEG_Quality', + 'Image::ExifTool::Photoshop::Main', + 'Image::ExifTool::Photoshop::Resolution', + 'Image::ExifTool::PostScript::Main', + 'Image::ExifTool::QuickTime::ItemList', + 'Image::ExifTool::QuickTime::ItemPropCont', + 'Image::ExifTool::QuickTime::Keys', + 'Image::ExifTool::QuickTime::Main', + 'Image::ExifTool::QuickTime::MediaHeader', + 'Image::ExifTool::QuickTime::MovieHeader', + 'Image::ExifTool::QuickTime::Preview', + 'Image::ExifTool::QuickTime::TrackHeader', + 'Image::ExifTool::QuickTime::UserData', + 'Image::ExifTool::Reconyx::Main', + 'Image::ExifTool::Reconyx::Type2', + 'Image::ExifTool::Reconyx::Type3', + 'Image::ExifTool::Ricoh::FaceInfo', + 'Image::ExifTool::Ricoh::FirmwareInfo', + 'Image::ExifTool::Ricoh::ImageInfo', + 'Image::ExifTool::Ricoh::Main', + 'Image::ExifTool::Ricoh::SerialInfo', + 'Image::ExifTool::Ricoh::Subdir', + 'Image::ExifTool::Ricoh::ThetaSubdir', + 'Image::ExifTool::Samsung::Main', + 'Image::ExifTool::Samsung::OrientationInfo', + 'Image::ExifTool::Samsung::PictureWizard', + 'Image::ExifTool::Samsung::Type2', + 'Image::ExifTool::Sanyo::FaceInfo', + 'Image::ExifTool::Sanyo::Main', + 'Image::ExifTool::Sigma::Main', + 'Image::ExifTool::Sigma::WBSettings', + 'Image::ExifTool::Sigma::WBSettings2', + 'Image::ExifTool::Sony::AFInfo', + 'Image::ExifTool::Sony::AFStatus15', + 'Image::ExifTool::Sony::AFStatus19', + 'Image::ExifTool::Sony::AFStatus79', + 'Image::ExifTool::Sony::CameraInfo', + 'Image::ExifTool::Sony::CameraInfo2', + 'Image::ExifTool::Sony::CameraInfo3', + 'Image::ExifTool::Sony::CameraSettings', + 'Image::ExifTool::Sony::CameraSettings2', + 'Image::ExifTool::Sony::CameraSettings3', + 'Image::ExifTool::Sony::Ericsson', + 'Image::ExifTool::Sony::ExtraInfo', + 'Image::ExifTool::Sony::ExtraInfo2', + 'Image::ExifTool::Sony::ExtraInfo3', + 'Image::ExifTool::Sony::FaceInfo', + 'Image::ExifTool::Sony::FaceInfo1', + 'Image::ExifTool::Sony::FaceInfo2', + 'Image::ExifTool::Sony::FaceInfoA', + 'Image::ExifTool::Sony::FocusInfo', + 'Image::ExifTool::Sony::ISOInfo', + 'Image::ExifTool::Sony::Main', + 'Image::ExifTool::Sony::MeterInfo', + 'Image::ExifTool::Sony::MeterInfo9', + 'Image::ExifTool::Sony::MoreInfo0201', + 'Image::ExifTool::Sony::MoreInfo0401', + 'Image::ExifTool::Sony::MoreSettings', + 'Image::ExifTool::Sony::Panorama', + 'Image::ExifTool::Sony::SR2SubIFD', + 'Image::ExifTool::Sony::ShotInfo', + 'Image::ExifTool::Sony::Tag2010a', + 'Image::ExifTool::Sony::Tag2010b', + 'Image::ExifTool::Sony::Tag2010c', + 'Image::ExifTool::Sony::Tag2010d', + 'Image::ExifTool::Sony::Tag2010e', + 'Image::ExifTool::Sony::Tag2010f', + 'Image::ExifTool::Sony::Tag2010g', + 'Image::ExifTool::Sony::Tag2010h', + 'Image::ExifTool::Sony::Tag2010i', + 'Image::ExifTool::Sony::Tag202a', + 'Image::ExifTool::Sony::Tag9050a', + 'Image::ExifTool::Sony::Tag9050b', + 'Image::ExifTool::Sony::Tag9050c', + 'Image::ExifTool::Sony::Tag9050d', + 'Image::ExifTool::Sony::Tag9400a', + 'Image::ExifTool::Sony::Tag9400b', + 'Image::ExifTool::Sony::Tag9400c', + 'Image::ExifTool::Sony::Tag9402', + 'Image::ExifTool::Sony::Tag9403', + 'Image::ExifTool::Sony::Tag9404a', + 'Image::ExifTool::Sony::Tag9404b', + 'Image::ExifTool::Sony::Tag9404c', + 'Image::ExifTool::Sony::Tag9405a', + 'Image::ExifTool::Sony::Tag9405b', + 'Image::ExifTool::Sony::Tag9406', + 'Image::ExifTool::Sony::Tag940a', + 'Image::ExifTool::Sony::Tag940c', + 'Image::ExifTool::SonyIDC::Main', + 'Image::ExifTool::XMP::Album', + 'Image::ExifTool::XMP::DICOM', + 'Image::ExifTool::XMP::Device', + 'Image::ExifTool::XMP::ExifTool', + 'Image::ExifTool::XMP::ExpressionMedia', + 'Image::ExifTool::XMP::GAudio', + 'Image::ExifTool::XMP::GCamera', + 'Image::ExifTool::XMP::GCreations', + 'Image::ExifTool::XMP::GDepth', + 'Image::ExifTool::XMP::GFocus', + 'Image::ExifTool::XMP::GImage', + 'Image::ExifTool::XMP::GPano', + 'Image::ExifTool::XMP::GSpherical', + 'Image::ExifTool::XMP::GettyImages', + 'Image::ExifTool::XMP::LImage', + 'Image::ExifTool::XMP::Lightroom', + 'Image::ExifTool::XMP::MediaPro', + 'Image::ExifTool::XMP::aas', + 'Image::ExifTool::XMP::acdsee', + 'Image::ExifTool::XMP::apple_fi', + 'Image::ExifTool::XMP::aux', + 'Image::ExifTool::XMP::cc', + 'Image::ExifTool::XMP::cell', + 'Image::ExifTool::XMP::crd', + 'Image::ExifTool::XMP::creatorAtom', + 'Image::ExifTool::XMP::crs', + 'Image::ExifTool::XMP::dc', + 'Image::ExifTool::XMP::dex', + 'Image::ExifTool::XMP::digiKam', + 'Image::ExifTool::XMP::exif', + 'Image::ExifTool::XMP::exifEX', + 'Image::ExifTool::XMP::extensis', + 'Image::ExifTool::XMP::fpv', + 'Image::ExifTool::XMP::hdr', + 'Image::ExifTool::XMP::hdrgm', + 'Image::ExifTool::XMP::ics', + 'Image::ExifTool::XMP::iptcCore', + 'Image::ExifTool::XMP::iptcExt', + 'Image::ExifTool::XMP::panorama', + 'Image::ExifTool::XMP::pdf', + 'Image::ExifTool::XMP::photoshop', + 'Image::ExifTool::XMP::pmi', + 'Image::ExifTool::XMP::prism', + 'Image::ExifTool::XMP::prl', + 'Image::ExifTool::XMP::prm', + 'Image::ExifTool::XMP::pur', + 'Image::ExifTool::XMP::rdf', + 'Image::ExifTool::XMP::swf', + 'Image::ExifTool::XMP::tiff', + 'Image::ExifTool::XMP::x', + 'Image::ExifTool::XMP::xmp', + 'Image::ExifTool::XMP::xmpBJ', + 'Image::ExifTool::XMP::xmpDM', + 'Image::ExifTool::XMP::xmpMM', + 'Image::ExifTool::XMP::xmpNote', + 'Image::ExifTool::XMP::xmpPLUS', + 'Image::ExifTool::XMP::xmpRights', + 'Image::ExifTool::XMP::xmpTPg', +); + +# lookup for all writable tags +my %tagLookup = ( + 'aberrationcorrectiondistance' => { 109 => 0x69 }, + 'about' => { 523 => 'about' }, + 'aboutcvterm' => { 514 => 'AboutCvTerm' }, + 'aboutcvtermcvid' => { 514 => [\'AboutCvTerm','AboutCvTermCvId'] }, + 'aboutcvtermid' => { 514 => [\'AboutCvTerm','AboutCvTermCvTermId'] }, + 'aboutcvtermname' => { 514 => [\'AboutCvTerm','AboutCvTermCvTermName'] }, + 'aboutcvtermrefinedabout' => { 514 => [\'AboutCvTerm','AboutCvTermCvTermRefinedAbout'] }, + 'absolutealtitude' => { 116 => 'AbsoluteAltitude' }, + 'abspeakaudiofilepath' => { 529 => 'absPeakAudioFilePath' }, + 'academicfield' => { 519 => 'academicField' }, + 'acceleration' => { 119 => 0x9404, 507 => 'Acceleration' }, + 'accelerationtracking' => { 84 => 0x518 }, + 'accelerationvector' => { 1 => 0x8 }, + 'accelerometer' => { 410 => 0x3 }, + 'accelerometerdata' => { 400 => 'vrot' }, + 'accelerometerx' => { 340 => 0x8d }, + 'accelerometery' => { 340 => 0x8e }, + 'accelerometerz' => { 340 => 0x8c }, + 'accessoryserialnumber' => { 340 => 0x54 }, + 'accessorytype' => { 340 => 0x53 }, + 'actionadvised' => { 131 => 0x2a }, + 'activearea' => { 119 => 0xc68d }, + 'actived-lighting' => { 234 => 0x22, 288 => 0x24 }, + 'actived-lightingmode' => { 288 => 0x25 }, + 'adaptervoltage' => { 138 => 0x407 }, + 'addaspectratioinfo' => { 84 => 0x80e }, + 'addiptcinformation' => { 84 => 0x815 }, + 'additionalmodelinformation' => { 514 => 'AddlModelInfo' }, + 'addoriginaldecisiondata' => { 84 => 0x80f, 85 => 0x11, 86 => 0x13, 89 => 0x14 }, + 'address' => { 161 => 'Address' }, + 'adjustmentmode' => { 417 => 0x15 }, + 'adlbracketingstep' => { 198 => 0x17 }, + 'adlbracketingtype' => { 198 => 0x18 }, + 'adobe' => { 120 => 'Adobe' }, + 'adultcontentwarning' => { 327 => 'AdultContentWarning', 522 => 'adultContentWarning' }, + 'advancedfilter' => { 127 => 0x1201 }, + 'advancedraw' => { 290 => 0x76a43203 }, + 'advancedscenetype' => { 340 => 0x3d }, + 'advisory' => { 527 => 'Advisory' }, + 'ae_iso' => { 348 => 0x2, 349 => 0x4, 350 => 0x12 }, + 'aeaperture' => { 348 => 0x1, 349 => 0x3, 350 => 0x11 }, + 'aeaperturesteps' => { 348 => 0x8, 349 => 0xb }, + 'aeaverage' => { 1 => 0x6 }, + 'aebautocancel' => { 84 => 0x104 }, + 'aebbracketvalue' => { 77 => 0x11 }, + 'aebracketingsteps' => { 198 => 0xf, 199 => 0xf, 271 => 0x174c }, + 'aebsequence' => { 84 => 0x105 }, + 'aebsequenceautocancel' => { 82 => 0x9, 83 => 0x9, 85 => 0x8, 86 => 0x9, 89 => 0x9, 90 => 0x7 }, + 'aebshotcount' => { 84 => 0x106 }, + 'aebxv' => { 348 => 0x4, 349 => 0x6 }, + 'aeerror' => { 349 => 0x8 }, + 'aeexposuretime' => { 348 => 0x0, 349 => 0x2, 350 => 0x10 }, + 'aelbutton' => { 184 => 0x45 }, + 'aelexposureindicator' => { 184 => 0x51 }, + 'aelock' => { 184 => 0x5b, 299 => '4.2', 308 => '4.2', 317 => 0x201, 375 => 0x48, 427 => 0x40, 428 => 0x40, 429 => [0x86,0x286] }, + 'aelockbutton' => { 297 => '16.1', 299 => '4.1', 302 => '15.1', 303 => '16.1', 304 => '16.1', 306 => '30.1', 307 => '16.1', 308 => '4.1', 310 => '17.1', 312 => '17.1' }, + 'aelockbuttonplusdials' => { 297 => '16.2', 306 => '32.1', 310 => '44.1' }, + 'aelockformb-d80' => { 312 => '3.1' }, + 'aelockmetermodeafterfocus' => { 84 => 0x114 }, + 'aematrix' => { 1 => 0x2 }, + 'aemaxaperture' => { 348 => 0x9, 349 => 0x10, 350 => 0x1c }, + 'aemaxaperture2' => { 348 => 0xa, 349 => 0x11, 350 => 0x1d }, + 'aemeteringmode' => { 348 => 0xc }, + 'aemeteringmode2' => { 348 => '13.1' }, + 'aemeteringsegments' => { 187 => 0x628, 375 => 0x209 }, + 'aemicroadjustment' => { 84 => 0x110 }, + 'aeminaperture' => { 348 => 0xb, 349 => 0x12, 350 => 0x1e }, + 'aeminexposuretime' => { 348 => 0x5, 349 => 0x13, 350 => 0x1f }, + 'aeprogrammode' => { 348 => 0x6 }, + 'aeprojectlink' => { 501 => 'aeProjectLink' }, + 'aeprojectlinkcompositionid' => { 501 => [\'aeProjectLink','aeProjectLinkCompositionID'] }, + 'aeprojectlinkfullpath' => { 501 => [\'aeProjectLink','aeProjectLinkFullPath'] }, + 'aeprojectlinkrenderoutputmoduleindex' => { 501 => [\'aeProjectLink','aeProjectLinkRenderOutputModuleIndex'] }, + 'aeprojectlinkrenderqueueitemid' => { 501 => [\'aeProjectLink','aeProjectLinkRenderQueueItemID'] }, + 'aeprojectlinkrendertimestamp' => { 501 => [\'aeProjectLink','aeProjectLinkRenderTimeStamp'] }, + 'aesetting' => { 34 => 0x21 }, + 'aestable' => { 1 => 0x4 }, + 'aetarget' => { 1 => 0x5 }, + 'aewhitebalance' => { 348 => 0xd }, + 'aexv' => { 348 => 0x3, 349 => 0x5 }, + 'af-assistilluminator' => { 313 => 0x19, 314 => 0x19, 315 => 0x19 }, + 'af-cfocusdisplay' => { 313 => 0x234, 314 => 0x234, 315 => 0x24c }, + 'af-cpriority' => { 128 => '0.2' }, + 'af-cpriorityselection' => { 297 => '1.1', 298 => '1.1', 300 => '1.1', 301 => '1.1', 303 => '0.1', 304 => '0.1', 305 => '0.1', 306 => '1.1', 307 => '0.1', 310 => '1.1', 311 => '1.1', 313 => 0x3, 314 => 0x3, 315 => 0x3 }, + 'af-csetting' => { 124 => 0x0 }, + 'af-cspeedtrackingsensitivity' => { 124 => '0.2' }, + 'af-ctrackingsensitivity' => { 124 => '0.1' }, + 'af-czoneareaswitching' => { 124 => '0.3' }, + 'af-onbutton' => { 300 => '70.1', 301 => '70.1', 311 => '70.1' }, + 'af-onformb-d10' => { 297 => '3.3', 306 => '3.2' }, + 'af-onformb-d11' => { 307 => '2.2' }, + 'af-onformb-d12' => { 310 => '50.1' }, + 'af-onoutoffocusrelease' => { 313 => 0x10, 314 => 0x10, 315 => 0x10 }, + 'af-spriority' => { 128 => '0.1' }, + 'af-spriorityselection' => { 297 => '1.2', 298 => '1.2', 300 => '1.2', 301 => '1.2', 305 => '0.2', 306 => '1.2', 307 => '0.2', 310 => '1.2', 311 => '1.2', 313 => 0x5, 314 => 0x5, 315 => 0x5 }, + 'afacceldeceltracking' => { 2 => 0x3 }, + 'afactivation' => { 297 => '2.1', 298 => '2.1', 300 => '78.3', 301 => '78.3', 306 => '2.1', 310 => '2.1', 311 => '78.3', 313 => 0xf, 314 => 0xf, 315 => 0xf }, + 'afadjustment' => { 375 => 0x72, 384 => 0x267 }, + 'afandmeteringbuttons' => { 84 => 0x701 }, + 'afaperture' => { 227 => 0x5, 228 => 0x5, 232 => 0x5 }, + 'afareaheight' => { 193 => [0x1a,0x34,0x50], 194 => 0x48 }, + 'afareaillumination' => { 184 => 0x4b, 308 => '15.3' }, + 'afareainitialheight' => { 246 => 0xbeb }, + 'afareainitialwidth' => { 246 => 0xbea }, + 'afareainitialxposition' => { 246 => 0xbe8 }, + 'afareainitialyposition' => { 246 => 0xbe9 }, + 'afareamode' => { 126 => '0.3', 180 => 0x33, 184 => 0xe, 192 => 0x0, 193 => 0x5, 194 => 0x5, 239 => 0x224, 240 => 0x210, 241 => 0x224, 242 => 0x224, 340 => 0xf, 407 => 0x1205, 420 => [0xa,0x3a], 427 => 0x11, 428 => 0x10, 429 => 0x24, 440 => 0xb043, 466 => 0x17 }, + 'afareamodesetting' => { 299 => '11.1', 302 => '0.1', 308 => '16.1', 312 => '2.1', 440 => 0x201c }, + 'afareapointsize' => { 126 => '0.4' }, + 'afareas' => { 317 => 0x304 }, + 'afareaselectionmethod' => { 2 => 0xd }, + 'afareaselectmethod' => { 84 => 0x51b }, + 'afareawidth' => { 193 => [0x18,0x32,0x4e], 194 => 0x46 }, + 'afareaxposition' => { 193 => [0x14,0x2e,0x4a], 194 => 0x42, 407 => 0x1203 }, + 'afareaxposition1' => { 407 => 0x1201 }, + 'afareayposition' => { 193 => [0x16,0x30,0x4c], 194 => 0x44, 407 => 0x1204 }, + 'afareayposition1' => { 407 => 0x1202 }, + 'afareazonesize' => { 126 => '0.5' }, + 'afassist' => { 82 => 0x5, 90 => 0x5, 184 => 0x48, 297 => '2.5', 299 => '0.2', 302 => '0.2', 303 => '1.1', 304 => '1.1', 305 => '1.3', 306 => '2.4', 307 => '1.3', 308 => '0.2', 310 => '2.4', 312 => '2.3' }, + 'afassistbeam' => { 2 => 0x8, 84 => 0x50e, 85 => 0x4, 86 => 0x5, 87 => 0x4, 88 => 0x4, 89 => 0x5 }, + 'afassistlamp' => { 340 => 0x31 }, + 'afbuttonpressed' => { 429 => [0x83,0x283] }, + 'afconfidence' => { 1 => 0x3d }, + 'afconfigtool' => { 2 => 0x1 }, + 'afdefocus' => { 351 => 0x6 }, + 'afduringliveview' => { 84 => 0x511 }, + 'affinea' => { 494 => 'AffineA' }, + 'affineb' => { 494 => 'AffineB' }, + 'affinec' => { 494 => 'AffineC' }, + 'affined' => { 494 => 'AffineD' }, + 'affinetune' => { 195 => 0x0, 239 => 0x6b0, 240 => 0x5b0, 241 => 0x5e0, 242 => 0x5f8, 317 => 0x306 }, + 'affinetuneadj' => { 195 => 0x2, 264 => 0x2d1, 265 => 0x2dc, 317 => 0x307 }, + 'affinetuneadjtele' => { 195 => 0x3 }, + 'affinetuneindex' => { 195 => 0x1 }, + 'affinex' => { 494 => 'AffineX' }, + 'affiney' => { 494 => 'AffineY' }, + 'afhold' => { 351 => 0x1fd }, + 'afilluminator' => { 427 => 0x29, 440 => 0xb044 }, + 'afimageheight' => { 193 => [0x12,0x2c,0x48], 194 => 0x40 }, + 'afimagewidth' => { 193 => [0x10,0x2a,0x46], 194 => 0x3e }, + 'afintegrationtime' => { 351 => 0x7 }, + 'afmeasureddepth' => { 1 => 0x38 }, + 'afmicroadj' => { 420 => [0x17d,0x50] }, + 'afmicroadjmode' => { 3 => 0x1, 424 => 0x131 }, + 'afmicroadjregisteredlenses' => { 424 => '305.1' }, + 'afmicroadjustment' => { 84 => 0x507 }, + 'afmicroadjvalue' => { 3 => 0x2, 424 => 0x130 }, + 'afmode' => { 113 => 0x3009, 127 => 0x1022, 180 => 0x16, 417 => 0x5 }, + 'afmoderestrictions' => { 298 => '50.3', 300 => '48.3', 301 => '48.3', 310 => '48.3', 311 => '48.3' }, + 'afonaelockbuttonswitch' => { 84 => 0x702 }, + 'afonbutton' => { 297 => '3.1', 298 => '47.1', 313 => 0x83, 314 => 0x83, 315 => 0x83 }, + 'afpoint' => { 34 => 0x13, 112 => 0x18, 180 => 0x15, 192 => 0x1, 320 => 0x308, 417 => 0x1f, 420 => [0x37,0x7], 424 => 0x19, 425 => 0x18, 426 => [0x18,0x20] }, + 'afpointactivationarea' => { 83 => 0x11, 89 => 0x11 }, + 'afpointareaexpansion' => { 84 => 0x508 }, + 'afpointatshutterrelease' => { 420 => [0x39,0x9] }, + 'afpointautoselection' => { 84 => 0x50b }, + 'afpointbrightness' => { 84 => 0x50d, 297 => '2.4', 298 => '46.5', 300 => '2.2', 301 => '2.2', 310 => '2.3', 311 => '2.2' }, + 'afpointdetails' => { 320 => 0x31b }, + 'afpointdisplayduringfocus' => { 2 => 0x10, 84 => 0x50c }, + 'afpointillumination' => { 83 => 0xa, 297 => '2.3', 298 => '46.2', 300 => '47.2', 301 => '47.2', 305 => '1.2', 306 => '2.3', 307 => '1.2', 310 => '47.2', 311 => '47.2', 312 => '2.4' }, + 'afpointinfocus' => { 420 => [0x38,0x8] }, + 'afpointmode' => { 356 => 0x3 }, + 'afpointposition' => { 113 => 0x2021, 340 => 0x4d }, + 'afpointregistration' => { 82 => 0x7 }, + 'afpoints' => { 183 => 0x10 }, + 'afpointsel' => { 313 => 0xb, 314 => 0xb, 315 => 0xb }, + 'afpointselected' => { 184 => 0xd, 317 => 0x305, 375 => 0xe, 424 => 0x15, 425 => 0x14, 426 => [0x14,0x1c], 440 => 0x201e }, + 'afpointselected2' => { 356 => 0x4 }, + 'afpointselection' => { 83 => 0xb, 297 => '1.3', 298 => '1.3', 306 => '1.3', 310 => '1.3', 311 => '1.3' }, + 'afpointselectionmethod' => { 84 => 0x50f, 85 => 0xc, 86 => 0xd, 89 => 0xd }, + 'afpointsetting' => { 427 => 0x12, 428 => 0x11 }, + 'afpointsinfocus' => { 77 => 0xe, 192 => 0x2, 193 => 0x30, 351 => 0xb, 352 => 0x4, 375 => [0xf,0x3c] }, + 'afpointsinfocus5d' => { 19 => 0x38 }, + 'afpointspotmetering' => { 83 => 0xd }, + 'afpointsselected' => { 193 => 0x1c, 352 => '4.1', 474 => 0x4 }, + 'afpointsspecial' => { 352 => '4.2' }, + 'afpointsunknown1' => { 351 => 0x0 }, + 'afpointsunknown2' => { 351 => 0x2 }, + 'afpointsused' => { 193 => [0xa,0x8], 194 => 0xa, 420 => [0x10,0x16e] }, + 'afpointswitching' => { 2 => 0x4 }, + 'afpredictor' => { 351 => 0x4 }, + 'afresponse' => { 234 => 0xad }, + 'afresult' => { 322 => 0x1038 }, + 'afsearch' => { 317 => 0x303 }, + 'afsensoractive' => { 180 => 0x1 }, + 'afstable' => { 1 => 0x7 }, + 'afstatus' => { 407 => 0x1200 }, + 'afstatus_00_b4' => { 423 => 0x0 }, + 'afstatus_01_c4' => { 423 => 0x2 }, + 'afstatus_02_d4' => { 423 => 0x4 }, + 'afstatus_03_e4' => { 423 => 0x6 }, + 'afstatus_04_f4' => { 423 => 0x8 }, + 'afstatus_05_g4' => { 423 => 0xa }, + 'afstatus_06_h4' => { 423 => 0xc }, + 'afstatus_07_b3' => { 423 => 0xe }, + 'afstatus_08_c3' => { 423 => 0x10 }, + 'afstatus_09_d3' => { 423 => 0x12 }, + 'afstatus_10_e3' => { 423 => 0x14 }, + 'afstatus_11_f3' => { 423 => 0x16 }, + 'afstatus_12_g3' => { 423 => 0x18 }, + 'afstatus_13_h3' => { 423 => 0x1a }, + 'afstatus_14_b2' => { 423 => 0x1c }, + 'afstatus_15_c2' => { 423 => 0x1e }, + 'afstatus_16_d2' => { 423 => 0x20 }, + 'afstatus_17_e2' => { 423 => 0x22 }, + 'afstatus_18_f2' => { 423 => 0x24 }, + 'afstatus_19_g2' => { 423 => 0x26 }, + 'afstatus_20_h2' => { 423 => 0x28 }, + 'afstatus_21_c1' => { 423 => 0x2a }, + 'afstatus_22_d1' => { 423 => 0x2c }, + 'afstatus_23_e1' => { 423 => 0x2e }, + 'afstatus_24_f1' => { 423 => 0x30 }, + 'afstatus_25_g1' => { 423 => 0x32 }, + 'afstatus_26_a7_vertical' => { 423 => 0x34 }, + 'afstatus_27_a6_vertical' => { 423 => 0x36 }, + 'afstatus_28_a5_vertical' => { 423 => 0x38 }, + 'afstatus_29_c7_vertical' => { 423 => 0x3a }, + 'afstatus_30_c6_vertical' => { 423 => 0x3c }, + 'afstatus_31_c5_vertical' => { 423 => 0x3e }, + 'afstatus_32_e7_vertical' => { 423 => 0x40 }, + 'afstatus_33_e6_center_vertical' => { 423 => 0x42 }, + 'afstatus_34_e5_vertical' => { 423 => 0x44 }, + 'afstatus_35_g7_vertical' => { 423 => 0x46 }, + 'afstatus_36_g6_vertical' => { 423 => 0x48 }, + 'afstatus_37_g5_vertical' => { 423 => 0x4a }, + 'afstatus_38_i7_vertical' => { 423 => 0x4c }, + 'afstatus_39_i6_vertical' => { 423 => 0x4e }, + 'afstatus_40_i5_vertical' => { 423 => 0x50 }, + 'afstatus_41_a7' => { 423 => 0x52 }, + 'afstatus_42_b7' => { 423 => 0x54 }, + 'afstatus_43_c7' => { 423 => 0x56 }, + 'afstatus_44_d7' => { 423 => 0x58 }, + 'afstatus_45_e7' => { 423 => 0x5a }, + 'afstatus_46_f7' => { 423 => 0x5c }, + 'afstatus_47_g7' => { 423 => 0x5e }, + 'afstatus_48_h7' => { 423 => 0x60 }, + 'afstatus_49_i7' => { 423 => 0x62 }, + 'afstatus_50_a6' => { 423 => 0x64 }, + 'afstatus_51_b6' => { 423 => 0x66 }, + 'afstatus_52_c6' => { 423 => 0x68 }, + 'afstatus_53_d6' => { 423 => 0x6a }, + 'afstatus_54_e6_center' => { 423 => 0x6c }, + 'afstatus_55_f6' => { 423 => 0x6e }, + 'afstatus_56_g6' => { 423 => 0x70 }, + 'afstatus_57_h6' => { 423 => 0x72 }, + 'afstatus_58_i6' => { 423 => 0x74 }, + 'afstatus_59_a5' => { 423 => 0x76 }, + 'afstatus_60_b5' => { 423 => 0x78 }, + 'afstatus_61_c5' => { 423 => 0x7a }, + 'afstatus_62_d5' => { 423 => 0x7c }, + 'afstatus_63_e5' => { 423 => 0x7e }, + 'afstatus_64_f5' => { 423 => 0x80 }, + 'afstatus_65_g5' => { 423 => 0x82 }, + 'afstatus_66_h5' => { 423 => 0x84 }, + 'afstatus_67_i5' => { 423 => 0x86 }, + 'afstatus_68_c11' => { 423 => 0x88 }, + 'afstatus_69_d11' => { 423 => 0x8a }, + 'afstatus_70_e11' => { 423 => 0x8c }, + 'afstatus_71_f11' => { 423 => 0x8e }, + 'afstatus_72_g11' => { 423 => 0x90 }, + 'afstatus_73_b10' => { 423 => 0x92 }, + 'afstatus_74_c10' => { 423 => 0x94 }, + 'afstatus_75_d10' => { 423 => 0x96 }, + 'afstatus_76_e10' => { 423 => 0x98 }, + 'afstatus_77_f10' => { 423 => 0x9a }, + 'afstatus_78_g10' => { 423 => 0x9c }, + 'afstatus_79_h10' => { 423 => 0x9e }, + 'afstatus_80_b9' => { 423 => 0xa0 }, + 'afstatus_81_c9' => { 423 => 0xa2 }, + 'afstatus_82_d9' => { 423 => 0xa4 }, + 'afstatus_83_e9' => { 423 => 0xa6 }, + 'afstatus_84_f9' => { 423 => 0xa8 }, + 'afstatus_85_g9' => { 423 => 0xaa }, + 'afstatus_86_h9' => { 423 => 0xac }, + 'afstatus_87_b8' => { 423 => 0xae }, + 'afstatus_88_c8' => { 423 => 0xb0 }, + 'afstatus_89_d8' => { 423 => 0xb2 }, + 'afstatus_90_e8' => { 423 => 0xb4 }, + 'afstatus_91_f8' => { 423 => 0xb6 }, + 'afstatus_92_g8' => { 423 => 0xb8 }, + 'afstatus_93_h8' => { 423 => 0xba }, + 'afstatus_94_e6_center_f2-8' => { 423 => 0xbc }, + 'afstatusactivesensor' => { 180 => 0x2, 420 => [0x4,0x3b], 424 => 0x1e, 425 => 0x1b, 426 => [0x1b,0x21] }, + 'afstatusbottom' => { 180 => 0x8, 424 => 0x2a, 425 => 0x21, 426 => 0x21 }, + 'afstatusbottom-left' => { 180 => 0x12, 425 => 0x2b, 426 => 0x2b }, + 'afstatusbottom-right' => { 180 => 0x6, 425 => 0x1f, 426 => 0x1f }, + 'afstatusbottomassist-left' => { 424 => 0x28 }, + 'afstatusbottomassist-right' => { 424 => 0x2c }, + 'afstatusbottomhorizontal' => { 421 => 0x10, 422 => 0x1c }, + 'afstatusbottomvertical' => { 421 => 0x16, 422 => 0x26 }, + 'afstatuscenter-10' => { 424 => 0x34 }, + 'afstatuscenter-11' => { 424 => 0x36 }, + 'afstatuscenter-12' => { 424 => 0x38 }, + 'afstatuscenter-14' => { 424 => 0x3c }, + 'afstatuscenter-7' => { 424 => 0x2e }, + 'afstatuscenter-9' => { 424 => 0x32 }, + 'afstatuscenter-horizontal' => { 424 => 0x30 }, + 'afstatuscenter-vertical' => { 424 => 0x3a }, + 'afstatuscenterf2-8' => { 424 => 0x4c }, + 'afstatuscenterhorizontal' => { 180 => 0x2f, 421 => 0xc, 422 => 0x18, 425 => 0x2f, 426 => 0x2f }, + 'afstatuscentervertical' => { 180 => 0xc, 421 => 0x14, 422 => 0x22, 425 => 0x25, 426 => 0x25 }, + 'afstatusfarleft' => { 421 => 0x6, 424 => 0x26 }, + 'afstatusfarlefthorizontal' => { 422 => 0x4 }, + 'afstatusfarleftvertical' => { 422 => 0x12 }, + 'afstatusfarright' => { 421 => 0x18, 424 => 0x44 }, + 'afstatusfarrighthorizontal' => { 422 => 0x2c }, + 'afstatusfarrightvertical' => { 422 => 0x34 }, + 'afstatusleft' => { 180 => 0x2d, 421 => 0x2, 424 => 0x22, 425 => 0x2d, 426 => 0x2d }, + 'afstatuslefthorizontal' => { 422 => 0x6 }, + 'afstatusleftvertical' => { 422 => 0xe }, + 'afstatuslower-left' => { 421 => 0x4, 424 => 0x24 }, + 'afstatuslower-lefthorizontal' => { 422 => 0xa }, + 'afstatuslower-leftvertical' => { 422 => 0x10 }, + 'afstatuslower-middle' => { 421 => 0x22, 422 => 0x24 }, + 'afstatuslower-right' => { 421 => 0x1e, 424 => 0x4a }, + 'afstatuslower-righthorizontal' => { 422 => 0x32 }, + 'afstatuslower-rightvertical' => { 422 => 0x3a }, + 'afstatuslowerfarleft' => { 422 => 0x8 }, + 'afstatuslowerfarright' => { 422 => 0x30 }, + 'afstatusmiddlehorizontal' => { 180 => 0xa, 425 => 0x23, 426 => 0x23 }, + 'afstatusnearleft' => { 421 => 0xe, 422 => 0x1a }, + 'afstatusnearright' => { 421 => 0xa, 422 => 0x16 }, + 'afstatusright' => { 180 => 0x31, 421 => 0x1c, 424 => 0x48, 425 => 0x31, 426 => 0x31 }, + 'afstatusrighthorizontal' => { 422 => 0x2e }, + 'afstatusrightvertical' => { 422 => 0x38 }, + 'afstatustop' => { 180 => 0xe, 424 => 0x40, 425 => 0x27, 426 => 0x27 }, + 'afstatustop-left' => { 180 => 0x10, 425 => 0x29, 426 => 0x29 }, + 'afstatustop-right' => { 180 => 0x4, 425 => 0x1d, 426 => 0x1d }, + 'afstatustopassist-left' => { 424 => 0x3e }, + 'afstatustopassist-right' => { 424 => 0x42 }, + 'afstatustophorizontal' => { 421 => 0x8, 422 => 0x14 }, + 'afstatustopvertical' => { 421 => 0x12, 422 => 0x1e }, + 'afstatusupper-left' => { 421 => 0x0, 424 => 0x20 }, + 'afstatusupper-lefthorizontal' => { 422 => 0x2 }, + 'afstatusupper-leftvertical' => { 422 => 0xc }, + 'afstatusupper-middle' => { 421 => 0x20, 422 => 0x20 }, + 'afstatusupper-right' => { 421 => 0x1a, 424 => 0x46 }, + 'afstatusupper-righthorizontal' => { 422 => 0x2a }, + 'afstatusupper-rightvertical' => { 422 => 0x36 }, + 'afstatusupperfarleft' => { 422 => 0x0 }, + 'afstatusupperfarright' => { 422 => 0x28 }, + 'afstatusviewfinder' => { 2 => 0x12 }, + 'aftracking' => { 440 => 0x2021 }, + 'aftrackingsensitivity' => { 2 => 0x2 }, + 'aftype' => { 420 => 0x2 }, + 'afwithshutter' => { 427 => 0x2a }, + 'aggregateissuenumber' => { 519 => 'aggregateIssueNumber' }, + 'aggregationtype' => { 519 => 'aggregationType' }, + 'agreement' => { 522 => 'agreement' }, + 'ah2greeninterpolationthreshold' => { 138 => 0xe4e }, + 'airplanemode' => { 239 => 0x722, 240 => 0x624, 241 => 0x654, 242 => 0x6bc }, + 'aiservocontinuousshooting' => { 83 => 0x15 }, + 'aiservofirstimage' => { 2 => 0x5 }, + 'aiservofirstimagepriority' => { 84 => 0x519 }, + 'aiservoimagepriority' => { 84 => 0x503 }, + 'aiservosecondimage' => { 2 => 0x6 }, + 'aiservosecondimagepriority' => { 84 => 0x51a }, + 'aiservotrackingmethod' => { 84 => 0x504 }, + 'aiservotrackingsensitivity' => { 83 => 0x14, 84 => 0x502 }, + 'aisubjecttrackingmode' => { 317 => 0x309 }, + 'album' => { 392 => ['albm',"\xa9alb"], 394 => 'album', 400 => ['albm',"\xa9alb"], 529 => 'album' }, + 'albumartist' => { 179 => 'WM/AlbumArtist', 392 => 'aART', 400 => 'albr' }, + 'albumcoverurl' => { 179 => 'WM/AlbumCoverURL' }, + 'albumid' => { 392 => 'plID' }, + 'albumtitle' => { 179 => 'WM/AlbumTitle' }, + 'alreadyapplied' => { 500 => 'AlreadyApplied', 502 => 'AlreadyApplied' }, + 'alternatetitle' => { 519 => 'alternateTitle' }, + 'alternatetitlea-lang' => { 519 => [\'alternateTitle','alternateTitleA-lang'] }, + 'alternatetitlea-platform' => { 519 => [\'alternateTitle','alternateTitleA-platform'] }, + 'alternatetitletext' => { 519 => [\'alternateTitle','alternateTitleText'] }, + 'altitude' => { 197 => 0x6 }, + 'alttapename' => { 529 => 'altTapeName' }, + 'alttextaccessibility' => { 513 => 'AltTextAccessibility' }, + 'alttimecode' => { 529 => 'altTimecode' }, + 'alttimecodetimeformat' => { 529 => [\'altTimecode','altTimecodeTimeFormat'] }, + 'alttimecodetimevalue' => { 529 => [\'altTimecode','altTimecodeTimeValue'] }, + 'alttimecodevalue' => { 529 => [\'altTimecode','altTimecodeValue'] }, + 'ambienceselection' => { 4 => 0x1 }, + 'ambientinfrared' => { 403 => 0x5c }, + 'ambientlight' => { 403 => 0x5e }, + 'ambienttemperature' => { 119 => 0x9400, 401 => 0x14, 402 => 0x46, 403 => 0x50, 466 => 0x4, 507 => 'Temperature' }, + 'ambienttemperaturefahrenheit' => { 401 => 0x13, 402 => 0x44, 403 => 0x4e }, + 'analogbalance' => { 119 => 0xc627 }, + 'analogcaptureiso' => { 138 => 0x89e }, + 'analogisotable' => { 138 => 0x89d }, + 'androidversion' => { 394 => 'com.android.version' }, + 'angleadj' => { 103 => 0x10003, 109 => 0x8b }, + 'angleinforoll' => { 496 => 'AngleInfoRoll' }, + 'angleinfoyaw' => { 496 => 'AngleInfoYaw' }, + 'anti-blur' => { 440 => 0xb04b }, + 'antialiasstrength' => { 119 => 0xc632 }, + 'aperturelock' => { 298 => '38.2', 300 => '38.2', 301 => '38.2', 310 => '38.2', 311 => '38.2', 313 => 0xb8, 314 => 0xb8, 315 => 0xb8 }, + 'aperturemode' => { 400 => 'apmd' }, + 'aperturerange' => { 84 => 0x10d }, + 'apertureringuse' => { 356 => '1.4' }, + 'aperturesetting' => { 184 => 0x7, 427 => 0x30, 428 => 0x29, 429 => 0x1 }, + 'aperturevalue' => { 93 => 0x2, 119 => 0x9202, 322 => 0x1002, 384 => 0x401, 506 => 'ApertureValue' }, + 'appinfo' => { 479 => 'AppInfo' }, + 'appinfoapplication' => { 479 => [\'AppInfo','AppInfoApplication'] }, + 'appinfoitemuri' => { 479 => [\'AppInfo','AppInfoItemURI'] }, + 'appinfoversion' => { 479 => [\'AppInfo','AppInfoVersion'] }, + 'applekeywords' => { 326 => 'AAPL:Keywords' }, + 'applephotosvariationidentifier' => { 394 => 'apple.photos.variation-identifier' }, + 'applestoreaccount' => { 392 => 'apID' }, + 'applestoreaccounttype' => { 392 => 'akID' }, + 'applestorecatalogid' => { 392 => 'cnID' }, + 'applestorecountry' => { 392 => 'sfID' }, + 'applicationkeystring' => { 138 => 0x400 }, + 'applicationnotes' => { 119 => 0x2bc, 345 => 0x2bc }, + 'applicationrecordversion' => { 131 => 0x0 }, + 'applyshootingmeteringmode' => { 84 => 0x10e }, + 'approved' => { 508 => 'Approved' }, + 'approvedby' => { 508 => 'ApprovedBy' }, + 'approximatefnumber' => { 335 => 0x313, 342 => 0x3406 }, + 'approximatefocusdistance' => { 497 => 'ApproximateFocusDistance' }, + 'appversion' => { 512 => 'AppVersion' }, + 'aps-csizecapture' => { 459 => 0x114, 460 => [0x114,0x1eb,0x1ee,0x21a,0x21c] }, + 'aquahsl' => { 103 => 0x20914 }, + 'armidentifier' => { 132 => 0x78 }, + 'armversion' => { 132 => 0x7a }, + 'arranger' => { 392 => "\xa9arg", 400 => "\xa9arg" }, + 'arrangerkeywords' => { 400 => "\xa9ark" }, + 'artdirector' => { 392 => "\xa9ard" }, + 'artfilter' => { 317 => 0x529 }, + 'artfiltereffect' => { 317 => 0x52f }, + 'artist' => { 119 => 0x13b, 330 => 'Artist', 345 => 0x13b, 375 => 0x22e, 392 => "\xa9ART", 394 => 'artist', 400 => "\xa9ART", 525 => 'Artist', 529 => 'artist' }, + 'artistid' => { 392 => 'atID' }, + 'artmode' => { 113 => 0x301b }, + 'artmodeparameters' => { 113 => 0x310b }, + 'artwork' => { 394 => 'artwork' }, + 'artworkcircadatecreated' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOCircaDateCreated'] }, + 'artworkcontentdescription' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOContentDescription'] }, + 'artworkcontributiondescription' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOContributionDescription'] }, + 'artworkcopyrightnotice' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOCopyrightNotice'] }, + 'artworkcopyrightownerid' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOCurrentCopyrightOwnerId'] }, + 'artworkcopyrightownername' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOCurrentCopyrightOwnerName'] }, + 'artworkcreator' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOCreator'] }, + 'artworkcreatorid' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOCreatorId'] }, + 'artworkdatecreated' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAODateCreated'] }, + 'artworklicensorid' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOCurrentLicensorId'] }, + 'artworklicensorname' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOCurrentLicensorName'] }, + 'artworkorobject' => { 514 => 'ArtworkOrObject' }, + 'artworkphysicaldescription' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOPhysicalDescription'] }, + 'artworksource' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOSource'] }, + 'artworksourceinventoryno' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOSourceInvNo'] }, + 'artworksourceinvurl' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOSourceInvURL'] }, + 'artworkstyleperiod' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOStylePeriod'] }, + 'artworktitle' => { 514 => [\'ArtworkOrObject','ArtworkOrObjectAOTitle'] }, + 'aspectframe' => { 321 => 0x1113 }, + 'aspectratio' => { 5 => 0x0, 321 => 0x1112, 375 => 0x80, 427 => 0x55, 428 => 0x55, 429 => 0xa, 453 => [0x192c,0x1a88], 454 => 0x192c, 455 => 0x1958, 456 => 0x192c, 457 => 0x188c }, + 'assetid' => { 490 => 'AssetID' }, + 'asshoticcprofile' => { 119 => 0xc68f }, + 'asshotneutral' => { 119 => 0xc628 }, + 'asshotpreprofilematrix' => { 119 => 0xc690 }, + 'asshotprofilename' => { 119 => 0xc6f6 }, + 'asshotwhitexy' => { 119 => 0xc629 }, + 'assignbktbutton' => { 297 => '4.2', 298 => '16.1', 300 => '16.1', 301 => '16.1', 310 => '16.1', 311 => '16.1' }, + 'assignfuncbutton' => { 84 => 0x70b }, + 'assignmb-d17af-onbutton' => { 301 => '79.1' }, + 'assignmb-d17funcbutton' => { 301 => '67.1' }, + 'assignmb-d17funcbuttonplusdials' => { 301 => '68.1' }, + 'assignmb-d18af-onbutton' => { 311 => '79.1' }, + 'assignmb-d18funcbutton' => { 311 => '67.1' }, + 'assignmb-d18funcbuttonplusdials' => { 311 => '68.1' }, + 'assignmoviefunc1buttonplusdials' => { 300 => '75.1', 301 => '75.1', 311 => '75.1' }, + 'assignmoviefunc2button' => { 300 => '82.1', 301 => '82.1', 311 => '82.1' }, + 'assignmoviepreviewbuttonplusdials' => { 300 => '75.2', 301 => '75.2', 311 => '75.2' }, + 'assignmovierecordbutton' => { 298 => '43.2', 310 => '45.1', 313 => 0x9b, 314 => 0x9b, 315 => 0x9b }, + 'assignmovierecordbuttonplusdials' => { 300 => '45.1', 301 => '45.1', 311 => '45.1' }, + 'assignmoviesubselector' => { 300 => '74.1', 301 => '74.1', 311 => '74.1' }, + 'assignmoviesubselectorplusdials' => { 300 => '76.1', 301 => '76.1', 311 => '76.1' }, + 'assignremotefnbutton' => { 298 => '54.1', 310 => '51.1' }, + 'assistbuttonfunction' => { 82 => 0xd }, + 'atcaptureusercrop' => { 138 => 0x943 }, + 'attributionname' => { 498 => 'attributionName' }, + 'attributionurl' => { 498 => 'attributionURL' }, + 'audio' => { 340 => 0x20 }, + 'audiobitrate' => { 67 => 0x6c, 514 => 'audioBitRate' }, + 'audiobitratemode' => { 514 => 'audioBitRateMode' }, + 'audiobitspersample' => { 514 => 'audioBitsPerSample' }, + 'audiobutton' => { 314 => 0x17b, 315 => 0x193 }, + 'audiobuttonplaybackmode' => { 314 => 0x1b9, 315 => 0x1d1 }, + 'audiochannelcount' => { 514 => 'audioChannelCount' }, + 'audiochannels' => { 67 => 0x70 }, + 'audiochanneltype' => { 529 => 'audioChannelType' }, + 'audiocompression' => { 84 => 0x816, 155 => 'Compression' }, + 'audiocompressor' => { 529 => 'audioCompressor' }, + 'audiodata' => { 482 => 'Data' }, + 'audioduration' => { 131 => 0x99 }, + 'audiogain' => { 394 => 'player.movie.audio.gain' }, + 'audiomimetype' => { 482 => 'Mime' }, + 'audiomoddate' => { 529 => 'audioModDate' }, + 'audiooutcue' => { 131 => 0x9a }, + 'audiosamplerate' => { 67 => 0x6e, 529 => 'audioSampleRate' }, + 'audiosampletype' => { 529 => 'audioSampleType' }, + 'audiosamplingrate' => { 131 => 0x97 }, + 'audiosamplingresolution' => { 131 => 0x98 }, + 'audiotype' => { 131 => 0x96 }, + 'author' => { 157 => 'Author', 326 => 'Author', 330 => 'Author', 391 => 'Author', 392 => ['auth',"\xa9aut"], 394 => 'author', 400 => 'auth', 495 => 'author', 516 => 'Author', 527 => 'Author' }, + 'authorsposition' => { 517 => 'AuthorsPosition' }, + 'authorurl' => { 179 => 'WM/AuthorURL' }, + 'autoafpointcolortracking' => { 84 => 0x51c }, + 'autoafpointseleositraf' => { 2 => 0xa }, + 'autoaperture' => { 366 => '0.1' }, + 'autobracket' => { 417 => 0x19 }, + 'autobracketing' => { 127 => 0x1100, 375 => 0x18, 407 => 0x1007 }, + 'autobracketingmode' => { 309 => '12.3' }, + 'autobracketingset' => { 309 => '12.1' }, + 'autobracketmodem' => { 297 => '21.2', 298 => '13.3', 300 => '13.2', 301 => '13.2', 306 => '26.2', 310 => '13.3', 311 => '13.2', 313 => 0x5f, 314 => 0x5f, 315 => 0x5f }, + 'autobracketorder' => { 184 => 0x43, 297 => '21.3', 298 => '13.2', 300 => '13.1', 301 => '13.1', 306 => '26.3', 307 => '12.2', 308 => '2.2', 309 => '12.2', 310 => '13.2', 311 => '13.1', 312 => '13.2', 313 => 0x61, 314 => 0x61, 315 => 0x61 }, + 'autobracketset' => { 297 => '21.1', 298 => '13.1', 302 => '11.1', 303 => '12.1', 304 => '12.1', 306 => '26.1', 307 => '12.1', 308 => '2.1', 310 => '13.1', 312 => '13.1' }, + 'autobrightness' => { 500 => 'AutoBrightness', 502 => 'AutoBrightness' }, + 'autocapturecriteria' => { 196 => 0x1 }, + 'autocapturecriteriamotiondirection' => { 196 => 0x5f }, + 'autocapturecriteriamotionsize' => { 196 => 0x64 }, + 'autocapturecriteriamotionspeed' => { 196 => 0x63 }, + 'autocapturecriteriasubjectsize' => { 196 => 0x69 }, + 'autocapturecriteriasubjecttype' => { 196 => 0x6a }, + 'autocapturedframe' => { 196 => 0x0 }, + 'autocapturedistancefar' => { 196 => 0x4a }, + 'autocapturedistancenear' => { 196 => 0x4e }, + 'autocapturepreset' => { 242 => 0x746 }, + 'autocapturerecordingtime' => { 196 => 0x37 }, + 'autocapturewaittime' => { 196 => 0x38 }, + 'autocontrast' => { 500 => 'AutoContrast', 502 => 'AutoContrast' }, + 'autodistortioncontrol' => { 209 => 0x4, 238 => 0x143 }, + 'autodynamicrange' => { 127 => 0x140b }, + 'autoexposure' => { 500 => 'AutoExposure', 502 => 'AutoExposure' }, + 'autoexposurebracketing' => { 77 => 0x10 }, + 'autoflashisosensitivity' => { 300 => '38.5', 301 => '38.5', 311 => '38.5', 313 => 0x5b, 314 => 0x5b, 315 => 0x5b }, + 'autofocus' => { 320 => 0x209 }, + 'autofocusmoderestrictions' => { 313 => 0x107, 314 => 0x107, 315 => 0x11d }, + 'autofp' => { 308 => '7.3', 312 => '31.4' }, + 'autoiso' => { 77 => 0x1, 113 => 0x3008, 239 => 0x152, 240 => 0x142, 241 => 0x156, 242 => 0x156, 299 => '1.1', 308 => '1.1' }, + 'autoisomax' => { 299 => '1.2', 308 => '1.2' }, + 'autoisominshutterspeed' => { 299 => '1.3', 308 => '1.3' }, + 'autolateralca' => { 500 => 'AutoLateralCA', 502 => 'AutoLateralCA' }, + 'autolightingoptimizer' => { 16 => 0xbe, 17 => 0xbf, 20 => 0xbf, 62 => 0x2, 84 => 0x204, 103 => 0x20500, 109 => 0x6f }, + 'autolightingoptimizeron' => { 103 => '0x20500.0', 109 => 0x6e }, + 'autoportraitframed' => { 440 => 0x2016 }, + 'autoredeye' => { 290 => 0xfe28a44f }, + 'autorotate' => { 77 => 0x1b }, + 'autoshadows' => { 500 => 'AutoShadows', 502 => 'AutoShadows' }, + 'autotone' => { 500 => 'AutoTone', 502 => 'AutoTone' }, + 'autotonedigest' => { 500 => 'AutoToneDigest', 502 => 'AutoToneDigest' }, + 'autotonedigestnosat' => { 500 => 'AutoToneDigestNoSat', 502 => 'AutoToneDigestNoSat' }, + 'autowhiteversion' => { 500 => 'AutoWhiteVersion', 502 => 'AutoWhiteVersion' }, + 'auxiliarylens' => { 234 => 0x82 }, + 'avaperturesetting' => { 356 => 0x13 }, + 'averageblacklevel' => { 45 => 0xe7, 47 => 0xfb, 48 => 0x114, 49 => 0x146 }, + 'averagelv' => { 187 => 0x38 }, + 'avsettingwithoutlens' => { 84 => 0x707 }, + 'azimuth' => { 165 => 'Azimuth' }, + 'babyage' => { 340 => [0x8010,0x33] }, + 'babyname' => { 340 => 0x66 }, + 'backgroundalpha' => { 524 => 'bgalpha' }, + 'balance' => { 394 => 'player.movie.audio.balance' }, + 'baseexposurecompensation' => { 356 => 0x15 }, + 'baseiso' => { 77 => 0x2, 97 => 0x101c, 138 => 0x903, 342 => 0x312a, 472 => 0x6 }, + 'baseisodaylight' => { 138 => 0x910 }, + 'baseisoflash' => { 138 => 0x913 }, + 'baseisofluorescent' => { 138 => 0x912 }, + 'baseisotungsten' => { 138 => 0x911 }, + 'baselineexposure' => { 119 => 0xc62a }, + 'baselineexposureoffset' => { 119 => 0xc7a5 }, + 'baselinenoise' => { 119 => 0xc62b }, + 'baselinesharpness' => { 119 => 0xc62c }, + 'baserenditionishdr' => { 511 => 'BaseRenditionIsHDR' }, + 'baseurl' => { 527 => 'BaseURL' }, + 'bass' => { 394 => 'player.movie.audio.bass' }, + 'batterylevel' => { 340 => 0x38, 427 => 0x51, 431 => 0xc, 432 => 0x4, 433 => 0x4, 473 => [0x5,0x7] }, + 'batterylevelgrip1' => { 473 => 0x6 }, + 'batterylevelgrip2' => { 473 => 0x8 }, + 'batteryorder' => { 297 => '12.5', 306 => '13.2', 307 => '2.1', 310 => '3.1' }, + 'batterystate' => { 184 => 0x60, 427 => 0x50, 433 => 0x14 }, + 'batterytemperature' => { 431 => 0x1, 433 => 0x2, 473 => 0x5 }, + 'batterytype' => { 64 => 0x38, 403 => 0x66 }, + 'batteryunknown' => { 431 => 0x2, 433 => 0x0 }, + 'batteryvoltage' => { 138 => 0x408, 401 => 0x2a, 402 => 0x49, 403 => 0x62, 431 => 0x8 }, + 'batteryvoltage1' => { 433 => 0x6 }, + 'batteryvoltage2' => { 433 => 0x8 }, + 'batteryvoltageavg' => { 403 => 0x64 }, + 'bayergreensplit' => { 119 => 0xc62d }, + 'bayerpattern' => { 137 => 0xf902, 188 => 0x17 }, + 'beatsperminute' => { 392 => 'tmpo' }, + 'beatspliceparams' => { 529 => 'beatSpliceParams' }, + 'beatspliceparamsriseindecibel' => { 529 => [\'beatSpliceParams','beatSpliceParamsRiseInDecibel'] }, + 'beatspliceparamsriseintimeduration' => { 529 => [\'beatSpliceParams','beatSpliceParamsRiseInTimeDuration'] }, + 'beatspliceparamsriseintimedurationscale' => { 529 => [\'beatSpliceParams','beatSpliceParamsRiseInTimeDurationScale'] }, + 'beatspliceparamsriseintimedurationvalue' => { 529 => [\'beatSpliceParams','beatSpliceParamsRiseInTimeDurationValue'] }, + 'beatspliceparamsusefilebeatsmarker' => { 529 => [\'beatSpliceParams','beatSpliceParamsUseFileBeatsMarker'] }, + 'beep' => { 297 => '13.1', 298 => '5.4', 299 => '0.1', 302 => '2.1', 303 => '3.1', 304 => '3.1', 306 => '10.1', 308 => '0.1', 310 => '5.5', 312 => '4.1' }, + 'beeppitch' => { 307 => '3.1' }, + 'beepvolume' => { 307 => '4.5' }, + 'bestqualityscale' => { 119 => 0xc65c }, + 'bestshotmode' => { 113 => 0x3007 }, + 'bitdepth' => { 162 => 'BitDepth', 188 => 0x11, 257 => 0x41 }, + 'bitspercomponent' => { 133 => 0x87 }, + 'bitspersample' => { 119 => 0x102, 345 => 0xa, 525 => 'BitsPerSample' }, + 'blackacquirerows' => { 138 => 0x18ba }, + 'blacklevel' => { 119 => [0x7310,0xc61a], 207 => 0x20, 234 => 0x3d, 322 => [0x401,0x1012], 384 => 0x21d, 447 => [0x7300,0x7310] }, + 'blacklevel2' => { 321 => 0x600, 325 => 0x600 }, + 'blacklevelblue' => { 345 => 0x1e }, + 'blacklevelbottom' => { 138 => 0x3f0 }, + 'blackleveldata' => { 384 => 0x223 }, + 'blackleveldeltah' => { 119 => 0xc61b }, + 'blackleveldeltav' => { 119 => 0xc61c }, + 'blacklevelgreen' => { 345 => 0x1d }, + 'blacklevelred' => { 345 => 0x1c }, + 'blacklevelrepeatdim' => { 119 => 0xc619 }, + 'blacklevelrough' => { 138 => 0x40e }, + 'blacklevelroughafter' => { 138 => 0x416 }, + 'blacklevels' => { 35 => 0x1d }, + 'blackleveltop' => { 138 => 0x3ef }, + 'blackpoint' => { 375 => 0x200 }, + 'blacks2012' => { 500 => 'Blacks2012', 502 => 'Blacks2012' }, + 'blacksadj' => { 476 => 0x9018 }, + 'bleachbypasstoning' => { 375 => 0x7f }, + 'blockshotafresponse' => { 300 => '1.5', 301 => '1.5', 311 => '1.5', 313 => 0x7, 314 => 0x7, 315 => 0x7 }, + 'blogtitle' => { 519 => 'blogTitle' }, + 'blogurl' => { 519 => 'blogURL' }, + 'bluebalance' => { 322 => 0x1018, 345 => 0x12, 375 => 0x1b }, + 'bluecurvelimits' => { 108 => 0x1fe }, + 'bluecurvepoints' => { 107 => 0x79, 108 => 0x1d4 }, + 'bluehsl' => { 103 => 0x20915 }, + 'bluehue' => { 500 => 'BlueHue', 502 => 'BlueHue' }, + 'bluesaturation' => { 500 => 'BlueSaturation', 502 => 'BlueSaturation' }, + 'bluratinfinity' => { 486 => 'BlurAtInfinity' }, + 'blurcontrol' => { 375 => 0x82 }, + 'blurwarning' => { 127 => 0x1300 }, + 'bodybatteryadload' => { 354 => 0x3 }, + 'bodybatteryadnoload' => { 354 => 0x2 }, + 'bodybatterystate' => { 354 => '1.1' }, + 'bodybatteryvoltage1' => { 354 => 0x2 }, + 'bodybatteryvoltage2' => { 354 => 0x4 }, + 'bodybatteryvoltage3' => { 354 => 0x6 }, + 'bodybatteryvoltage4' => { 354 => 0x8 }, + 'bodyfirmware' => { 408 => 0x0 }, + 'bodyfirmwareversion' => { 318 => 0x104, 319 => 0x100, 322 => 0x104 }, + 'bodyserialnumber' => { 408 => 0x10 }, + 'bookedition' => { 519 => 'bookEdition' }, + 'bootloaderversion' => { 402 => 0x26 }, + 'bracketbutton' => { 242 => 0x80c }, + 'bracketbuttonplaybackmode' => { 242 => 0x816 }, + 'bracketincrement' => { 239 => 0x22e, 240 => 0x21a, 241 => 0x22e, 242 => 0x22e }, + 'bracketmode' => { 57 => 0x3 }, + 'bracketprogram' => { 239 => 0x22c, 240 => 0x218, 241 => 0x22c, 242 => 0x22c }, + 'bracketsequence' => { 113 => 0x301d }, + 'bracketset' => { 239 => 0x22a, 240 => 0x216, 241 => 0x22a, 242 => 0x22a }, + 'bracketsettings' => { 340 => 0x45 }, + 'bracketshotnumber' => { 57 => 0x5, 356 => 0x9, 438 => 0x2b }, + 'bracketshotnumber2' => { 438 => 0x2d }, + 'bracketstep' => { 181 => 0xe }, + 'bracketvalue' => { 57 => 0x4 }, + 'brightness' => { 119 => 0xfe53, 156 => 'Brightness', 176 => 'Brightness', 181 => 0x2c, 249 => 0x34, 250 => 0x39, 251 => 0x41, 394 => 'player.movie.visual.brightness', 401 => 0x25, 403 => 0x54, 427 => 0x22, 440 => 0x2007, 500 => 'Brightness', 502 => 'Brightness' }, + 'brightnessadj' => { 108 => 0x114, 283 => 0x0, 293 => 0x2d, 476 => 0x8018 }, + 'brightnessvalue' => { 119 => 0x9203, 187 => [0x691,0x49c3], 322 => 0x1003, 445 => 0x1e, 449 => 0x1140, 450 => 0x1140, 451 => 0x111c, 452 => 0x1198, 453 => 0x1174, 454 => 0x102c, 455 => 0x224, 456 => 0x224, 457 => 0x219, 506 => 'BrightnessValue' }, + 'buildnumber' => { 191 => 0x5500 }, + 'bulbduration' => { 77 => 0x18 }, + 'burstcount' => { 138 => 0x40d }, + 'burstgroupid' => { 236 => 0x4 }, + 'burstid' => { 483 => 'BurstID' }, + 'burstmode' => { 140 => 0xa, 340 => 0x2a }, + 'burstmode2' => { 140 => 0x18 }, + 'burstprimary' => { 483 => 'BurstPrimary' }, + 'burstshot' => { 417 => 0x34 }, + 'burstspeed' => { 340 => 0x77 }, + 'burstuuid' => { 1 => 0xb }, + 'buttonfunctioncontroloff' => { 84 => 0x70a }, + 'bwadjustment' => { 127 => 0x1049 }, + 'bwfilter' => { 181 => 0x2a, 189 => 0x39 }, + 'bwmagentagreen' => { 127 => 0x104b }, + 'bwmode' => { 322 => 0x203 }, + 'by-line' => { 131 => 0x50 }, + 'by-linetitle' => { 131 => 0x55 }, + 'bytecount' => { 519 => 'byteCount' }, + 'c14configuration' => { 138 => 0x1964 }, + 'cacheversion' => { 119 => 0xc7aa }, + 'calibratedfocallength' => { 116 => 'CalibratedFocalLength' }, + 'calibratedopticalcenterx' => { 116 => 'CalibratedOpticalCenterX' }, + 'calibratedopticalcentery' => { 116 => 'CalibratedOpticalCenterY' }, + 'calibration' => { 417 => [0x24,0x30] }, + 'calibrationhistory' => { 138 => 0x9c9 }, + 'calibrationilluminant1' => { 119 => 0xc65a }, + 'calibrationilluminant2' => { 119 => 0xc65b }, + 'calibrationilluminant3' => { 119 => 0xcd31 }, + 'calibrationversion' => { 138 => 0x9c6 }, + 'callforimage' => { 490 => 'CallForImage' }, + 'camera' => { 479 => [\'Cameras','CamerasCamera'] }, + 'cameraangle' => { 400 => 'angl', 529 => 'cameraAngle' }, + 'cameraappinfo' => { 479 => [\'Cameras','CamerasCameraAppInfo'] }, + 'cameraappinfoapplication' => { 479 => [\'Cameras','CamerasCameraAppInfoApplication'] }, + 'cameraappinfoitemuri' => { 479 => [\'Cameras','CamerasCameraAppInfoItemURI'] }, + 'cameraappinfoversion' => { 479 => [\'Cameras','CamerasCameraAppInfoVersion'] }, + 'cameraburstid' => { 484 => 'CameraBurstID' }, + 'cameracalibration' => { 417 => 0x11f }, + 'cameracalibration1' => { 119 => 0xc623 }, + 'cameracalibration2' => { 119 => 0xc624 }, + 'cameracalibration3' => { 119 => 0xcd32 }, + 'cameracalibrationsig' => { 119 => 0xc6f3 }, + 'cameracolorcalibration01' => { 36 => 0x0, 37 => 0x0 }, + 'cameracolorcalibration02' => { 36 => 0x4, 37 => 0x5 }, + 'cameracolorcalibration03' => { 36 => 0x8, 37 => 0xa }, + 'cameracolorcalibration04' => { 36 => 0xc, 37 => 0xf }, + 'cameracolorcalibration05' => { 36 => 0x10, 37 => 0x14 }, + 'cameracolorcalibration06' => { 36 => 0x14, 37 => 0x19 }, + 'cameracolorcalibration07' => { 36 => 0x18, 37 => 0x1e }, + 'cameracolorcalibration08' => { 36 => 0x1c, 37 => 0x23 }, + 'cameracolorcalibration09' => { 36 => 0x20, 37 => 0x28 }, + 'cameracolorcalibration10' => { 36 => 0x24, 37 => 0x2d }, + 'cameracolorcalibration11' => { 36 => 0x28, 37 => 0x32 }, + 'cameracolorcalibration12' => { 36 => 0x2c, 37 => 0x37 }, + 'cameracolorcalibration13' => { 36 => 0x30, 37 => 0x3c }, + 'cameracolorcalibration14' => { 36 => 0x34, 37 => 0x41 }, + 'cameracolorcalibration15' => { 36 => 0x38, 37 => 0x46 }, + 'cameradepthmap' => { 479 => [\'Cameras','CamerasCameraDepthMap'] }, + 'cameradepthmapconfidenceuri' => { 479 => [\'Cameras','CamerasCameraDepthMapConfidenceURI'] }, + 'cameradepthmapdepthuri' => { 479 => [\'Cameras','CamerasCameraDepthMapDepthURI'] }, + 'cameradepthmapfar' => { 479 => [\'Cameras','CamerasCameraDepthMapFar'] }, + 'cameradepthmapfocaltable' => { 479 => [\'Cameras','CamerasCameraDepthMapFocalTable'] }, + 'cameradepthmapfocaltableentrycount' => { 479 => [\'Cameras','CamerasCameraDepthMapFocalTableEntryCount'] }, + 'cameradepthmapformat' => { 479 => [\'Cameras','CamerasCameraDepthMapFormat'] }, + 'cameradepthmapitemsemantic' => { 479 => [\'Cameras','CamerasCameraDepthMapItemSemantic'] }, + 'cameradepthmapmeasuretype' => { 479 => [\'Cameras','CamerasCameraDepthMapMeasureType'] }, + 'cameradepthmapnear' => { 479 => [\'Cameras','CamerasCameraDepthMapNear'] }, + 'cameradepthmapsoftware' => { 479 => [\'Cameras','CamerasCameraDepthMapSoftware'] }, + 'cameradepthmapunits' => { 479 => [\'Cameras','CamerasCameraDepthMapUnits'] }, + 'cameradirection' => { 394 => 'direction.facing' }, + 'camerae-mountversion' => { 475 => 0xb }, + 'cameraelevationangle' => { 119 => 0x9405, 507 => 'CameraElevationAngle' }, + 'camerafilename' => { 490 => 'CameraFilename' }, + 'camerafirmware' => { 119 => 0xa439 }, + 'cameraid' => { 322 => 0x209, 400 => 'cmid', 416 => 0x209 }, + 'cameraidentifier' => { 394 => 'camera.identifier' }, + 'cameraimage' => { 479 => [\'Cameras','CamerasCameraImage'] }, + 'cameraimageitemsemantic' => { 479 => [\'Cameras','CamerasCameraImageItemSemantic'] }, + 'cameraimageitemuri' => { 479 => [\'Cameras','CamerasCameraImageItemURI'] }, + 'cameraimagingmodel' => { 479 => [\'Cameras','CamerasCameraImagingModel'] }, + 'cameraimagingmodeldistortion' => { 479 => [\'Cameras','CamerasCameraImagingModelDistortion'] }, + 'cameraimagingmodeldistortioncount' => { 479 => [\'Cameras','CamerasCameraImagingModelDistortionCount'] }, + 'cameraimagingmodelfocallengthx' => { 479 => [\'Cameras','CamerasCameraImagingModelFocalLengthX'] }, + 'cameraimagingmodelfocallengthy' => { 479 => [\'Cameras','CamerasCameraImagingModelFocalLengthY'] }, + 'cameraimagingmodelimageheight' => { 479 => [\'Cameras','CamerasCameraImagingModelImageHeight'] }, + 'cameraimagingmodelimagewidth' => { 479 => [\'Cameras','CamerasCameraImagingModelImageWidth'] }, + 'cameraimagingmodelpixelaspectratio' => { 479 => [\'Cameras','CamerasCameraImagingModelPixelAspectRatio'] }, + 'cameraimagingmodelprincipalpointx' => { 479 => [\'Cameras','CamerasCameraImagingModelPrincipalPointX'] }, + 'cameraimagingmodelprincipalpointy' => { 479 => [\'Cameras','CamerasCameraImagingModelPrincipalPointY'] }, + 'cameraimagingmodelskew' => { 479 => [\'Cameras','CamerasCameraImagingModelSkew'] }, + 'cameraiso' => { 34 => 0x10 }, + 'cameralabel' => { 119 => 0xc7a1, 529 => 'cameraLabel' }, + 'cameralightestimate' => { 479 => [\'Cameras','CamerasCameraLightEstimate'] }, + 'cameralightestimatecolorcorrectionb' => { 479 => [\'Cameras','CamerasCameraLightEstimateColorCorrectionB'] }, + 'cameralightestimatecolorcorrectiong' => { 479 => [\'Cameras','CamerasCameraLightEstimateColorCorrectionG'] }, + 'cameralightestimatecolorcorrectionr' => { 479 => [\'Cameras','CamerasCameraLightEstimateColorCorrectionR'] }, + 'cameralightestimatepixelintensity' => { 479 => [\'Cameras','CamerasCameraLightEstimatePixelIntensity'] }, + 'cameramakemodel' => { 490 => 'CameraMakeModel' }, + 'cameramodel' => { 384 => 0x410, 529 => 'cameraModel' }, + 'cameramodelid' => { 176 => 'CameraModelID' }, + 'cameramodelrestriction' => { 500 => 'CameraModelRestriction', 502 => 'CameraModelRestriction' }, + 'cameramotion' => { 394 => 'direction.motion' }, + 'cameramove' => { 529 => 'cameraMove' }, + 'cameraorientation' => { 7 => 0x30, 9 => 0x7d, 11 => 0x30, 13 => 0x35, 14 => 0x30, 15 => 0x30, 16 => 0x31, 17 => 0x31, 18 => 0x35, 19 => 0x27, 20 => 0x31, 21 => 0x7d, 22 => 0x38, 23 => [0x36,0x3a], 24 => 0x7d, 25 => 0x83, 26 => 0x84, 27 => 0x96, 28 => 0x35, 29 => 0x96, 340 => 0x8f, 379 => 0x1, 384 => 0x100, 433 => [0x16,0x18], 463 => 0x28, 464 => 0x24, 465 => 0x29 }, + 'cameraowner' => { 141 => 0xc353 }, + 'cameraparameters' => { 322 => 0x2050 }, + 'camerapicturestyle' => { 28 => 0xaf }, + 'camerapitch' => { 115 => 0x9, 400 => "\xa9gpt" }, + 'camerapointcloud' => { 479 => [\'Cameras','CamerasCameraPointCloud'] }, + 'camerapointcloudmetric' => { 479 => [\'Cameras','CamerasCameraPointCloudMetric'] }, + 'camerapointcloudpointcloud' => { 479 => [\'Cameras','CamerasCameraPointCloudPointCloud'] }, + 'camerapointcloudpoints' => { 479 => [\'Cameras','CamerasCameraPointCloudPoints'] }, + 'camerapose' => { 479 => [\'Cameras','CamerasCameraPose'] }, + 'cameraposepositionx' => { 479 => [\'Cameras','CamerasCameraPosePositionX'] }, + 'cameraposepositiony' => { 479 => [\'Cameras','CamerasCameraPosePositionY'] }, + 'cameraposepositionz' => { 479 => [\'Cameras','CamerasCameraPosePositionZ'] }, + 'cameraposerotationw' => { 479 => [\'Cameras','CamerasCameraPoseRotationW'] }, + 'cameraposerotationx' => { 479 => [\'Cameras','CamerasCameraPoseRotationX'] }, + 'cameraposerotationy' => { 479 => [\'Cameras','CamerasCameraPoseRotationY'] }, + 'cameraposerotationz' => { 479 => [\'Cameras','CamerasCameraPoseRotationZ'] }, + 'cameraposetimestamp' => { 479 => [\'Cameras','CamerasCameraPoseTimestamp'] }, + 'cameraprofile' => { 500 => 'CameraProfile', 502 => 'CameraProfile' }, + 'cameraprofiledigest' => { 500 => 'CameraProfileDigest', 502 => 'CameraProfileDigest' }, + 'cameraprofiles' => { 517 => 'CameraProfiles' }, + 'cameraprofilesaperturevalue' => { 517 => [\'CameraProfiles','CameraProfilesApertureValue'] }, + 'cameraprofilesauthor' => { 517 => [\'CameraProfiles','CameraProfilesAuthor'] }, + 'cameraprofilesautoscale' => { 517 => [\'CameraProfiles','CameraProfilesAutoScale'] }, + 'cameraprofilescameraprettyname' => { 517 => [\'CameraProfiles','CameraProfilesCameraPrettyName'] }, + 'cameraprofilescamerarawprofile' => { 517 => [\'CameraProfiles','CameraProfilesCameraRawProfile'] }, + 'cameraprofilesfocallength' => { 517 => [\'CameraProfiles','CameraProfilesFocalLength'] }, + 'cameraprofilesfocusdistance' => { 517 => [\'CameraProfiles','CameraProfilesFocusDistance'] }, + 'cameraprofileslens' => { 517 => [\'CameraProfiles','CameraProfilesLens'] }, + 'cameraprofileslensprettyname' => { 517 => [\'CameraProfiles','CameraProfilesLensPrettyName'] }, + 'cameraprofilesmake' => { 517 => [\'CameraProfiles','CameraProfilesMake'] }, + 'cameraprofilesmodel' => { 517 => [\'CameraProfiles','CameraProfilesModel'] }, + 'cameraprofilesperspectivemodel' => { 517 => [\'CameraProfiles','CameraProfilesPerspectiveModel'] }, + 'cameraprofilesperspectivemodelimagexcenter' => { 517 => [\'CameraProfiles','CameraProfilesPerspectiveModelImageXCenter'] }, + 'cameraprofilesperspectivemodelimageycenter' => { 517 => [\'CameraProfiles','CameraProfilesPerspectiveModelImageYCenter'] }, + 'cameraprofilesperspectivemodelradialdistortparam1' => { 517 => [\'CameraProfiles','CameraProfilesPerspectiveModelRadialDistortParam1'] }, + 'cameraprofilesperspectivemodelradialdistortparam2' => { 517 => [\'CameraProfiles','CameraProfilesPerspectiveModelRadialDistortParam2'] }, + 'cameraprofilesperspectivemodelradialdistortparam3' => { 517 => [\'CameraProfiles','CameraProfilesPerspectiveModelRadialDistortParam3'] }, + 'cameraprofilesperspectivemodelscalefactor' => { 517 => [\'CameraProfiles','CameraProfilesPerspectiveModelScaleFactor'] }, + 'cameraprofilesperspectivemodelversion' => { 517 => [\'CameraProfiles','CameraProfilesPerspectiveModelVersion'] }, + 'cameraprofilesprofilename' => { 517 => [\'CameraProfiles','CameraProfilesProfileName'] }, + 'cameraprofilessensorformatfactor' => { 517 => [\'CameraProfiles','CameraProfilesSensorFormatFactor'] }, + 'cameraprofilesuniquecameramodel' => { 517 => [\'CameraProfiles','CameraProfilesUniqueCameraModel'] }, + 'camerarawcolortone' => { 109 => 0xe1 }, + 'camerarawcontrast' => { 109 => 0xe3 }, + 'camerarawhighlightpoint' => { 109 => 0xe6 }, + 'camerarawlinear' => { 109 => 0xe4 }, + 'camerarawoutputhighlightpoint' => { 109 => 0xe8 }, + 'camerarawoutputshadowpoint' => { 109 => 0xe9 }, + 'camerarawsaturation' => { 109 => 0xe2 }, + 'camerarawshadowpoint' => { 109 => 0xe7 }, + 'camerarawsharpness' => { 109 => 0xe5 }, + 'cameraroll' => { 115 => 0xb, 400 => "\xa9grl" }, + 'cameras' => { 479 => 'Cameras' }, + 'cameraserialnumber' => { 119 => 0xc62f, 178 => 'CameraSerialNumber', 490 => 'CameraSerialNumber' }, + 'camerasettingsversion' => { 317 => 0x0 }, + 'cameratemperature' => { 7 => 0x18, 9 => 0x1b, 11 => 0x18, 13 => 0x19, 14 => 0x18, 15 => 0x18, 16 => 0x19, 17 => 0x19, 18 => 0x19, 19 => 0x17, 20 => 0x19, 21 => 0x1b, 22 => 0x19, 23 => 0x19, 24 => 0x1b, 25 => 0x1b, 26 => 0x1b, 27 => 0x1b, 28 => 0x19, 29 => 0x1b, 30 => [0x87,0x91], 31 => [0x99,0x9f,0xa4,0xa8,0x105], 33 => ['-3',0x64,0x47,0x53,0x5b,0x5c], 77 => 0xc, 138 => 0x406, 321 => 0x1306, 335 => 0x320, 342 => 0x3402, 375 => 0x47, 414 => 0x43, 467 => 0x5 }, + 'cameratemperature4' => { 380 => 0x14 }, + 'cameratemperature5' => { 380 => 0x16 }, + 'cameratemperaturerangemax' => { 121 => 0x5 }, + 'cameratemperaturerangemin' => { 121 => 0x6 }, + 'cameratrait' => { 479 => [\'Cameras','CamerasCameraTrait'] }, + 'cameratype' => { 77 => 0x1a, 322 => 0x207 }, + 'cameratype2' => { 318 => 0x100 }, + 'cameravendorinfo' => { 479 => [\'Cameras','CamerasCameraVendorInfo'] }, + 'cameravendorinfomanufacturer' => { 479 => [\'Cameras','CamerasCameraVendorInfoManufacturer'] }, + 'cameravendorinfomodel' => { 479 => [\'Cameras','CamerasCameraVendorInfoModel'] }, + 'cameravendorinfonotes' => { 479 => [\'Cameras','CamerasCameraVendorInfoNotes'] }, + 'camerayaw' => { 115 => 0xa, 400 => "\xa9gyw" }, + 'camreverse' => { 116 => 'CamReverse' }, + 'canondr4' => { 120 => 'CanonDR4' }, + 'canonexposuremode' => { 34 => 0x14 }, + 'canonfiledescription' => { 97 => 0x805 }, + 'canonfilelength' => { 64 => 0xe }, + 'canonfirmwareversion' => { 64 => 0x7, 97 => 0x80b }, + 'canonflashinfo' => { 97 => 0x1028 }, + 'canonflashmode' => { 34 => 0x4 }, + 'canonimagesize' => { 10 => 0x39, 34 => 0xa }, + 'canonimagetype' => { 64 => 0x6, 97 => 0x815 }, + 'canonlogversion' => { 63 => 0xb }, + 'canonmodelid' => { 64 => 0x10, 97 => 0x1834 }, + 'canonvrd' => { 120 => 'CanonVRD' }, + 'caption' => { 495 => 'caption' }, + 'caption-abstract' => { 131 => 0x78 }, + 'captionsauthornames' => { 505 => 'CaptionsAuthorNames' }, + 'captionsdatetimestamps' => { 505 => 'CaptionsDateTimeStamps' }, + 'captionwriter' => { 517 => 'CaptionWriter' }, + 'captureframerate' => { 113 => 0x4001 }, + 'captureheightnormal' => { 138 => 0x1839 }, + 'capturelook' => { 138 => 0xc48 }, + 'capturemode' => { 394 => 'com.apple.photos.captureMode' }, + 'capturesoftware' => { 488 => 'CaptureSoftware' }, + 'capturewidthnormal' => { 138 => 0x1838 }, + 'capturewidthtest' => { 138 => 0x1842 }, + 'cardshutterlock' => { 184 => 0x49 }, + 'casioimagesize' => { 113 => 0x9 }, + 'catalogsets' => { 131 => 0xff, 481 => 'CatalogSets', 493 => 'CatalogSets' }, + 'categories' => { 64 => 0x23, 495 => 'categories' }, + 'category' => { 131 => 0xf, 179 => 'WM/Category', 316 => 0x30, 392 => 'catg', 517 => 'Category' }, + 'cbcrgain' => { 414 => 0xa036 }, + 'cbcrgaindefault' => { 414 => 0xa035 }, + 'cbcrmatrix' => { 414 => 0xa034 }, + 'cbcrmatrixdefault' => { 414 => 0xa033 }, + 'ccdboardversion' => { 335 => 0x331 }, + 'ccdscanmode' => { 322 => 0x1039 }, + 'ccdsensitivity' => { 280 => 0x6 }, + 'ccdversion' => { 335 => 0x330 }, + 'ccvavgluminancenits' => { 510 => 'ccv_avg_luminance_nits' }, + 'ccvmaxluminancenits' => { 510 => 'ccv_max_luminance_nits' }, + 'ccvminluminancenits' => { 510 => 'ccv_min_luminance_nits' }, + 'ccvprimariesxy' => { 510 => 'ccv_primaries_xy' }, + 'ccvwhitexy' => { 510 => 'ccv_white_xy' }, + 'cellglobalid' => { 499 => 'cgi' }, + 'celllength' => { 119 => 0x109 }, + 'cellr' => { 499 => 'r' }, + 'celltowerid' => { 499 => 'cellid' }, + 'cellwidth' => { 119 => 0x108 }, + 'centerafarea' => { 308 => '15.1' }, + 'centerfocuspoint' => { 312 => '2.2' }, + 'centerpixel' => { 138 => 0x40c }, + 'centerweightedareasize' => { 297 => '7.1', 298 => '8.1', 300 => '8.1', 301 => '8.1', 305 => '7.1', 306 => '5.1', 307 => '7.1', 308 => '6.3', 310 => '8.1', 311 => '8.1', 312 => '8.1', 313 => 0x1f, 314 => 0x1f, 315 => 0x1f }, + 'certificate' => { 533 => 'Certificate' }, + 'cfainterpolationalgorithm' => { 138 => 0xe60 }, + 'cfainterpolationmetric' => { 138 => 0xe61 }, + 'cfaoffsetcols' => { 138 => 0xc71 }, + 'cfaoffsetrows' => { 138 => 0xc6f }, + 'cfapattern' => { 119 => 0xa302, 345 => 0x9, 506 => 'CFAPattern' }, + 'cfapattern2' => { 119 => 0x828e }, + 'cfapatterncolumns' => { 506 => [\'CFAPattern','CFAPatternColumns'] }, + 'cfapatternrows' => { 506 => [\'CFAPattern','CFAPatternRows'] }, + 'cfapatternvalues' => { 506 => [\'CFAPattern','CFAPatternValues'] }, + 'cfarepeatpatterndim' => { 119 => 0x828d }, + 'cfazipperfixthreshold' => { 138 => 0xe62 }, + 'channel' => { 519 => 'channel' }, + 'channela-lang' => { 519 => [\'channel','channelA-lang'] }, + 'channelchannel' => { 519 => [\'channel','channelChannel'] }, + 'channels' => { 155 => 'Channels' }, + 'channelsubchannel1' => { 519 => [\'channel','channelSubchannel1'] }, + 'channelsubchannel2' => { 519 => [\'channel','channelSubchannel2'] }, + 'channelsubchannel3' => { 519 => [\'channel','channelSubchannel3'] }, + 'channelsubchannel4' => { 519 => [\'channel','channelSubchannel4'] }, + 'chapterlist' => { 400 => 'chpl' }, + 'checkmark' => { 103 => 0x10101, 108 => 0x26a }, + 'checkmark2' => { 109 => 0x8e }, + 'childfontfiles' => { 534 => [\'Fonts','FontsChildFontFiles'] }, + 'chmodeshootingspeed' => { 297 => '10.3', 298 => '11.2', 313 => 0x10b, 314 => 0x10b, 315 => 0x121 }, + 'chromablurradius' => { 119 => 0xc631 }, + 'chromanoisecolorspace' => { 138 => 0xe6d }, + 'chromanoiseedgemapthresh' => { 138 => 0xe6c }, + 'chromanoisehighfthresh' => { 138 => 0xe6a }, + 'chromanoiselowfthresh' => { 138 => 0xe6b }, + 'chromaticaberration' => { 103 => 0x20703, 109 => 0x66, 414 => 0xa051 }, + 'chromaticaberrationb' => { 500 => 'ChromaticAberrationB', 502 => 'ChromaticAberrationB' }, + 'chromaticaberrationblue' => { 103 => 0x20708, 109 => 0x6b }, + 'chromaticaberrationcorr' => { 79 => [0x4,0x5] }, + 'chromaticaberrationcorrection' => { 119 => 0x7034, 365 => 0x1, 476 => 0x900d }, + 'chromaticaberrationcorrparams' => { 119 => 0x7035, 471 => 0x66a, 472 => [0x37c,0x384,0x39c,0x3b0,0x3b8] }, + 'chromaticaberrationon' => { 103 => '0x20703.0', 109 => 0x62 }, + 'chromaticaberrationr' => { 500 => 'ChromaticAberrationR', 502 => 'ChromaticAberrationR' }, + 'chromaticaberrationred' => { 103 => 0x20707, 109 => 0x6a }, + 'chromaticaberrationsetting' => { 80 => 0x6 }, + 'chrominancenoisereduction' => { 103 => 0x20601, 109 => 0x5e, 417 => 0x1a }, + 'chrominancenr_tiff_jpeg' => { 109 => 0x60 }, + 'circadatecreated' => { 514 => 'CircaDateCreated' }, + 'circgradbasedcorractive' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionActive'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionActive'] }, + 'circgradbasedcorramount' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionAmount'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionAmount'] }, + 'circgradbasedcorrblacks2012' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalBlacks2012'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalBlacks2012'] }, + 'circgradbasedcorrbrightness' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalBrightness'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalBrightness'] }, + 'circgradbasedcorrclarity' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalClarity'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalClarity'] }, + 'circgradbasedcorrclarity2012' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalClarity2012'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalClarity2012'] }, + 'circgradbasedcorrcontrast' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalContrast'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalContrast'] }, + 'circgradbasedcorrcontrast2012' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalContrast2012'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalContrast2012'] }, + 'circgradbasedcorrcorrectionname' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionName'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionName'] }, + 'circgradbasedcorrcorrectionsyncid' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionSyncID'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionSyncID'] }, + 'circgradbasedcorrdefringe' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalDefringe'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalDefringe'] }, + 'circgradbasedcorrdehaze' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalDehaze'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalDehaze'] }, + 'circgradbasedcorrexposure' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalExposure'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalExposure'] }, + 'circgradbasedcorrexposure2012' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalExposure2012'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalExposure2012'] }, + 'circgradbasedcorrhighlights2012' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalHighlights2012'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalHighlights2012'] }, + 'circgradbasedcorrhue' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalHue'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalHue'] }, + 'circgradbasedcorrluminancenoise' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalLuminanceNoise'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalLuminanceNoise'] }, + 'circgradbasedcorrmaskalpha' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksAlpha'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksAlpha'] }, + 'circgradbasedcorrmaskangle' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksAngle'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksAngle'] }, + 'circgradbasedcorrmaskbottom' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksBottom'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksBottom'] }, + 'circgradbasedcorrmaskcentervalue' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCenterValue'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCenterValue'] }, + 'circgradbasedcorrmaskcenterweight' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCenterWeight'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCenterWeight'] }, + 'circgradbasedcorrmaskdabs' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksDabs'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksDabs'] }, + 'circgradbasedcorrmaskfeather' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksFeather'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksFeather'] }, + 'circgradbasedcorrmaskflipped' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksFlipped'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksFlipped'] }, + 'circgradbasedcorrmaskflow' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksFlow'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksFlow'] }, + 'circgradbasedcorrmaskfullx' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksFullX'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksFullX'] }, + 'circgradbasedcorrmaskfully' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksFullY'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksFullY'] }, + 'circgradbasedcorrmaskinputdigest' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksInputDigest'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksInputDigest'] }, + 'circgradbasedcorrmaskleft' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksLeft'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksLeft'] }, + 'circgradbasedcorrmaskmaskactive' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskActive'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskActive'] }, + 'circgradbasedcorrmaskmaskblendmode' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskBlendMode'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskBlendMode'] }, + 'circgradbasedcorrmaskmaskdigest' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskDigest'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskDigest'] }, + 'circgradbasedcorrmaskmaskinverted' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskInverted'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskInverted'] }, + 'circgradbasedcorrmaskmaskname' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskName'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskName'] }, + 'circgradbasedcorrmaskmasks' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasks'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasks'] }, + 'circgradbasedcorrmaskmasksalpha' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksAlpha'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksAlpha'] }, + 'circgradbasedcorrmaskmasksangle' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksAngle'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksAngle'] }, + 'circgradbasedcorrmaskmasksbottom' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksBottom'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksBottom'] }, + 'circgradbasedcorrmaskmaskscentervalue' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksCenterValue'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksCenterValue'] }, + 'circgradbasedcorrmaskmaskscenterweight' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksCenterWeight'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksCenterWeight'] }, + 'circgradbasedcorrmaskmasksdabs' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksDabs'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksDabs'] }, + 'circgradbasedcorrmaskmasksfeather' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksFeather'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksFeather'] }, + 'circgradbasedcorrmaskmasksflipped' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksFlipped'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksFlipped'] }, + 'circgradbasedcorrmaskmasksflow' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksFlow'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksFlow'] }, + 'circgradbasedcorrmaskmasksfullx' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksFullX'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksFullX'] }, + 'circgradbasedcorrmaskmasksfully' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksFullY'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksFullY'] }, + 'circgradbasedcorrmaskmasksinputdigest' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksInputDigest'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksInputDigest'] }, + 'circgradbasedcorrmaskmasksleft' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksLeft'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksLeft'] }, + 'circgradbasedcorrmaskmasksmaskactive' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskActive'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskActive'] }, + 'circgradbasedcorrmaskmasksmaskblendmode' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskBlendMode'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskBlendMode'] }, + 'circgradbasedcorrmaskmasksmaskdigest' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskDigest'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskDigest'] }, + 'circgradbasedcorrmaskmasksmaskinverted' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskInverted'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskInverted'] }, + 'circgradbasedcorrmaskmasksmaskname' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskName'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskName'] }, + 'circgradbasedcorrmaskmasksmasksubtype' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskSubType'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskSubType'] }, + 'circgradbasedcorrmaskmasksmasksyncid' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskSyncID'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskSyncID'] }, + 'circgradbasedcorrmaskmasksmaskversion' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskVersion'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskVersion'] }, + 'circgradbasedcorrmaskmasksmidpoint' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMidpoint'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMidpoint'] }, + 'circgradbasedcorrmaskmasksorigin' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksOrigin'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksOrigin'] }, + 'circgradbasedcorrmaskmasksperimetervalue' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksPerimeterValue'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksPerimeterValue'] }, + 'circgradbasedcorrmaskmasksradius' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksRadius'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksRadius'] }, + 'circgradbasedcorrmaskmasksreferencepoint' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksReferencePoint'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksReferencePoint'] }, + 'circgradbasedcorrmaskmasksright' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksRight'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksRight'] }, + 'circgradbasedcorrmaskmasksroundness' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksRoundness'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksRoundness'] }, + 'circgradbasedcorrmaskmaskssizex' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksSizeX'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksSizeX'] }, + 'circgradbasedcorrmaskmaskssizey' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksSizeY'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksSizeY'] }, + 'circgradbasedcorrmaskmaskstop' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksTop'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksTop'] }, + 'circgradbasedcorrmaskmasksubtype' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskSubType'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskSubType'] }, + 'circgradbasedcorrmaskmasksvalue' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskValue'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksMaskValue'] }, + 'circgradbasedcorrmaskmasksversion' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksVersion'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksVersion'] }, + 'circgradbasedcorrmaskmaskswhat' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksWhat'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksWhat'] }, + 'circgradbasedcorrmaskmaskswholeimagearea' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksWholeImageArea'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksWholeImageArea'] }, + 'circgradbasedcorrmaskmasksx' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksX'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksX'] }, + 'circgradbasedcorrmaskmasksy' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksY'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksY'] }, + 'circgradbasedcorrmaskmasksyncid' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskSyncID'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskSyncID'] }, + 'circgradbasedcorrmaskmaskszerox' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksZeroX'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksZeroX'] }, + 'circgradbasedcorrmaskmaskszeroy' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksZeroY'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMasksZeroY'] }, + 'circgradbasedcorrmaskmaskversion' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskVersion'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskVersion'] }, + 'circgradbasedcorrmaskmidpoint' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMidpoint'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMidpoint'] }, + 'circgradbasedcorrmaskorigin' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksOrigin'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksOrigin'] }, + 'circgradbasedcorrmaskperimetervalue' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksPerimeterValue'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksPerimeterValue'] }, + 'circgradbasedcorrmaskradius' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksRadius'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksRadius'] }, + 'circgradbasedcorrmaskrange' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMask'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMask'] }, + 'circgradbasedcorrmaskrangeareamodels' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels'] }, + 'circgradbasedcorrmaskrangeareamodelscolorsampleinfo' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'] }, + 'circgradbasedcorrmaskrangeareamodelscomponents' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents'] }, + 'circgradbasedcorrmaskrangecoloramount' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount'] }, + 'circgradbasedcorrmaskrangedepthfeather' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather'] }, + 'circgradbasedcorrmaskrangedepthmax' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax'] }, + 'circgradbasedcorrmaskrangedepthmin' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin'] }, + 'circgradbasedcorrmaskrangeinvert' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert'] }, + 'circgradbasedcorrmaskrangelumfeather' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather'] }, + 'circgradbasedcorrmaskrangeluminancedepthsampleinfo' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo'] }, + 'circgradbasedcorrmaskrangelummax' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax'] }, + 'circgradbasedcorrmaskrangelummin' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin'] }, + 'circgradbasedcorrmaskrangelumrange' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange'] }, + 'circgradbasedcorrmaskrangesampletype' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType'] }, + 'circgradbasedcorrmaskrangetype' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskType'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskType'] }, + 'circgradbasedcorrmaskrangeversion' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion'] }, + 'circgradbasedcorrmaskreferencepoint' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksReferencePoint'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksReferencePoint'] }, + 'circgradbasedcorrmaskright' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksRight'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksRight'] }, + 'circgradbasedcorrmaskroundness' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksRoundness'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksRoundness'] }, + 'circgradbasedcorrmasks' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasks'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasks'] }, + 'circgradbasedcorrmasksizex' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksSizeX'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksSizeX'] }, + 'circgradbasedcorrmasksizey' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksSizeY'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksSizeY'] }, + 'circgradbasedcorrmasktop' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksTop'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksTop'] }, + 'circgradbasedcorrmaskvalue' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskValue'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksMaskValue'] }, + 'circgradbasedcorrmaskversion' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksVersion'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksVersion'] }, + 'circgradbasedcorrmaskwhat' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksWhat'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksWhat'] }, + 'circgradbasedcorrmaskwholeimagearea' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksWholeImageArea'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksWholeImageArea'] }, + 'circgradbasedcorrmaskx' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksX'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksX'] }, + 'circgradbasedcorrmasky' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksY'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksY'] }, + 'circgradbasedcorrmaskzerox' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksZeroX'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksZeroX'] }, + 'circgradbasedcorrmaskzeroy' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksZeroY'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionMasksZeroY'] }, + 'circgradbasedcorrmoire' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalMoire'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalMoire'] }, + 'circgradbasedcorrrangemask' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMask'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMask'] }, + 'circgradbasedcorrrangemaskareamodels' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModels'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModels'] }, + 'circgradbasedcorrrangemaskareamodelscolorsampleinfo' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'] }, + 'circgradbasedcorrrangemaskareamodelscomponents' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents'] }, + 'circgradbasedcorrrangemaskcoloramount' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskColorAmount'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskColorAmount'] }, + 'circgradbasedcorrrangemaskdepthfeather' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskDepthFeather'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskDepthFeather'] }, + 'circgradbasedcorrrangemaskdepthmax' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskDepthMax'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskDepthMax'] }, + 'circgradbasedcorrrangemaskdepthmin' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskDepthMin'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskDepthMin'] }, + 'circgradbasedcorrrangemaskinvert' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskInvert'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskInvert'] }, + 'circgradbasedcorrrangemasklumfeather' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskLumFeather'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskLumFeather'] }, + 'circgradbasedcorrrangemaskluminancedepthsampleinfo' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo'] }, + 'circgradbasedcorrrangemasklummax' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskLumMax'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskLumMax'] }, + 'circgradbasedcorrrangemasklummin' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskLumMin'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskLumMin'] }, + 'circgradbasedcorrrangemasklumrange' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskLumRange'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskLumRange'] }, + 'circgradbasedcorrrangemasksampletype' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskSampleType'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskSampleType'] }, + 'circgradbasedcorrrangemasktype' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskType'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskType'] }, + 'circgradbasedcorrrangemaskversion' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskVersion'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsCorrectionRangeMaskVersion'] }, + 'circgradbasedcorrsaturation' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalSaturation'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalSaturation'] }, + 'circgradbasedcorrshadows2012' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalShadows2012'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalShadows2012'] }, + 'circgradbasedcorrsharpness' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalSharpness'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalSharpness'] }, + 'circgradbasedcorrtemperature' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalTemperature'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalTemperature'] }, + 'circgradbasedcorrtexture' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalTexture'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalTexture'] }, + 'circgradbasedcorrtint' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalTint'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalTint'] }, + 'circgradbasedcorrtoninghue' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalToningHue'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalToningHue'] }, + 'circgradbasedcorrtoningsaturation' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalToningSaturation'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalToningSaturation'] }, + 'circgradbasedcorrwhat' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsWhat'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsWhat'] }, + 'circgradbasedcorrwhites2012' => { 500 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalWhites2012'], 502 => [\'CircularGradientBasedCorrections','CircularGradientBasedCorrectionsLocalWhites2012'] }, + 'circulargradientbasedcorrections' => { 500 => 'CircularGradientBasedCorrections', 502 => 'CircularGradientBasedCorrections' }, + 'city' => { 131 => 0x5a, 161 => 'City', 340 => 0x6d, 517 => 'City' }, + 'city2' => { 340 => 0x80 }, + 'clarity' => { 127 => 0x100f, 250 => 0x35, 251 => 0x3d, 440 => 0x2036, 500 => 'Clarity', 502 => 'Clarity' }, + 'clarity2012' => { 500 => 'Clarity2012', 502 => 'Clarity2012' }, + 'claritycontrol' => { 375 => 0x96 }, + 'classification' => { 400 => 'clsf' }, + 'classifystate' => { 131 => 0xe1 }, + 'clearretouch' => { 340 => 0x7c }, + 'clearretouchvalue' => { 340 => 0xa3 }, + 'client' => { 529 => 'client' }, + 'clientname' => { 508 => 'ClientName' }, + 'clipboardaspectratio' => { 500 => 'ClipboardAspectRatio', 502 => 'ClipboardAspectRatio' }, + 'clipboardorientation' => { 500 => 'ClipboardOrientation', 502 => 'ClipboardOrientation' }, + 'clipfilename' => { 400 => 'clfn' }, + 'clipid' => { 400 => 'clid' }, + 'clmodeshootingspeed' => { 297 => '10.2', 298 => '11.3', 300 => '11.2', 301 => '11.2', 306 => '11.2', 307 => '10.2', 310 => '11.2', 311 => '11.2', 312 => '11.1', 313 => 0x3b, 314 => 0x3b, 315 => 0x3b }, + 'cluster' => { 500 => 'Cluster', 502 => 'Cluster' }, + 'cmcontrast' => { 325 => 0x2022 }, + 'cmddialsreverserotation' => { 313 => 0xba, 314 => 0xba, 315 => 0xba }, + 'cmexposurecompensation' => { 325 => 0x2000 }, + 'cmhue' => { 325 => 0x2021 }, + 'cmsaturation' => { 325 => 0x2020 }, + 'cmsharpness' => { 325 => 0x2023 }, + 'cmwhitebalance' => { 325 => 0x2001 }, + 'cmwhitebalancecomp' => { 325 => 0x2002 }, + 'cmwhitebalancegraypoint' => { 325 => 0x2010 }, + 'codec' => { 169 => 'Codec' }, + 'codedcharacterset' => { 132 => 0x5a }, + 'codeversion' => { 400 => 'cver' }, + 'collection' => { 330 => 'Collection' }, + 'collectionname' => { 170 => [\'Collections','CollectionsCollectionName'], 400 => 'coll' }, + 'collections' => { 170 => 'Collections', 495 => 'collections' }, + 'collectionuri' => { 170 => [\'Collections','CollectionsCollectionURI'] }, + 'color' => { 394 => 'player.movie.visual.color', 518 => 'color' }, + 'coloraberrationcontrol' => { 290 => 0xc89224b }, + 'coloradjustment' => { 417 => 0x14 }, + 'coloradjustmentmode' => { 416 => 0x210 }, + 'coloranta' => { 534 => [\'Colorants','ColorantsA'] }, + 'colorantb' => { 534 => [\'Colorants','ColorantsB'] }, + 'colorantblack' => { 534 => [\'Colorants','ColorantsBlack'] }, + 'colorantblue' => { 534 => [\'Colorants','ColorantsBlue'] }, + 'colorantcyan' => { 534 => [\'Colorants','ColorantsCyan'] }, + 'colorantgray' => { 534 => [\'Colorants','ColorantsGray'] }, + 'colorantgreen' => { 534 => [\'Colorants','ColorantsGreen'] }, + 'colorantl' => { 534 => [\'Colorants','ColorantsL'] }, + 'colorantmagenta' => { 534 => [\'Colorants','ColorantsMagenta'] }, + 'colorantmode' => { 534 => [\'Colorants','ColorantsMode'] }, + 'colorantred' => { 534 => [\'Colorants','ColorantsRed'] }, + 'colorants' => { 534 => 'Colorants' }, + 'colorantswatchname' => { 534 => [\'Colorants','ColorantsSwatchName'] }, + 'coloranttint' => { 534 => [\'Colorants','ColorantsTint'] }, + 'coloranttype' => { 534 => [\'Colorants','ColorantsType'] }, + 'colorantyellow' => { 534 => [\'Colorants','ColorantsYellow'] }, + 'colorbalance' => { 156 => 'ColorBalance' }, + 'colorbalanceadj' => { 290 => 0x76a43202 }, + 'colorbalanceblue' => { 181 => 0x1e }, + 'colorbalancegreen' => { 181 => 0x1d }, + 'colorbalancered' => { 181 => 0x1c }, + 'colorbalanceversion' => { 206 => 0x4, 207 => 0x4, 208 => 0x0 }, + 'colorblur' => { 109 => 0x65 }, + 'colorbluron' => { 103 => 0x20704 }, + 'colorbooster' => { 290 => 0x5f0e7d23 }, + 'colorboostlevel' => { 284 => 0x1 }, + 'colorboosttype' => { 284 => 0x0 }, + 'colorchromeeffect' => { 127 => 0x1048 }, + 'colorchromefxblue' => { 127 => 0x104e }, + 'colorclass' => { 386 => 0xde, 387 => 'ColorClass' }, + 'colorcompensationfilter' => { 184 => [0x3a,0x5f], 186 => 0x111, 440 => 0xb022 }, + 'colorcompensationfiltercustom' => { 427 => 0xd, 428 => 0xc }, + 'colorcompensationfilterset' => { 427 => 0x8, 428 => 0x7, 429 => 0x18, 445 => 0xf }, + 'colorcontrol' => { 322 => 0x102b }, + 'colorcorrection' => { 476 => 0x8015 }, + 'colorcorrectionmatrix' => { 1 => 0x3e }, + 'colorcreatoreffect' => { 317 => 0x532 }, + 'colordataversion' => { 41 => 0x0, 42 => 0x0, 44 => 0x0, 45 => 0x0, 46 => 0x0, 47 => 0x0, 48 => 0x0, 49 => 0x0, 50 => 0x0 }, + 'coloreffect' => { 340 => 0x28 }, + 'colorfilter' => { 112 => 0x17, 113 => 0x3017, 181 => 0x29, 189 => [0x38,0x4d,0x4f], 382 => 0x17 }, + 'colorgain' => { 257 => 0x51 }, + 'colorgradeblending' => { 500 => 'ColorGradeBlending', 502 => 'ColorGradeBlending' }, + 'colorgradeglobalhue' => { 500 => 'ColorGradeGlobalHue', 502 => 'ColorGradeGlobalHue' }, + 'colorgradegloballum' => { 500 => 'ColorGradeGlobalLum', 502 => 'ColorGradeGlobalLum' }, + 'colorgradeglobalsat' => { 500 => 'ColorGradeGlobalSat', 502 => 'ColorGradeGlobalSat' }, + 'colorgradehighlightlum' => { 500 => 'ColorGradeHighlightLum', 502 => 'ColorGradeHighlightLum' }, + 'colorgrademidtonehue' => { 500 => 'ColorGradeMidtoneHue', 502 => 'ColorGradeMidtoneHue' }, + 'colorgrademidtonelum' => { 500 => 'ColorGradeMidtoneLum', 502 => 'ColorGradeMidtoneLum' }, + 'colorgrademidtonesat' => { 500 => 'ColorGradeMidtoneSat', 502 => 'ColorGradeMidtoneSat' }, + 'colorgradeshadowlum' => { 500 => 'ColorGradeShadowLum', 502 => 'ColorGradeShadowLum' }, + 'colorhue' => { 103 => 0x20900, 234 => 0x8d }, + 'colorimetricreference' => { 119 => 0xc6bf }, + 'colorlabel' => { 505 => 'ColorLabel' }, + 'colormatrix' => { 63 => 0xa, 321 => 0x200, 322 => 0x1011, 414 => 0xa030 }, + 'colormatrix1' => { 119 => 0xc621, 384 => 0x106 }, + 'colormatrix2' => { 119 => 0xc622, 325 => 0x200, 384 => 0x226 }, + 'colormatrix3' => { 119 => 0xcd33 }, + 'colormatrixa' => { 375 => 0x203 }, + 'colormatrixa2' => { 375 => 0x21c }, + 'colormatrixadobergb' => { 414 => 0xa032 }, + 'colormatrixb' => { 375 => 0x204 }, + 'colormatrixb2' => { 375 => 0x21d }, + 'colormatrixnumber' => { 322 => 0x1019 }, + 'colormatrixsrgb' => { 414 => 0xa031 }, + 'colormode' => { 113 => 0x3015, 127 => 0x1210, 140 => 0x66, 181 => 0x28, 184 => 0x16, 186 => 0x101, 187 => 0x36, 189 => 0x7, 234 => 0x3, 280 => 0x4, 340 => 0x32, 417 => 0x2c, 440 => 0xb029, 517 => 'ColorMode' }, + 'colormoirereduction' => { 103 => 0x20670, 291 => 0x15 }, + 'colormoirereductionmode' => { 291 => 0x5 }, + 'colormoirereductionon' => { 103 => '0x20670.0' }, + 'colornoisereduction' => { 476 => 0x8029, 500 => 'ColorNoiseReduction', 502 => 'ColorNoiseReduction' }, + 'colornoisereductiondetail' => { 500 => 'ColorNoiseReductionDetail', 502 => 'ColorNoiseReductionDetail' }, + 'colornoisereductionintensity' => { 291 => 0x18 }, + 'colornoisereductionsharpness' => { 291 => 0x1c }, + 'colornoisereductionsmoothness' => { 500 => 'ColorNoiseReductionSmoothness', 502 => 'ColorNoiseReductionSmoothness' }, + 'colorplanes' => { 123 => 0x2 }, + 'colorprofile' => { 181 => 0x33 }, + 'colorprofilesettings' => { 317 => 0x539 }, + 'colorrepresentation' => { 133 => 0x3c }, + 'colorsaturationadj' => { 103 => 0x20305 }, + 'colorsequence' => { 133 => 0x41 }, + 'colorspace' => { 51 => 0x3, 64 => 0xb4, 97 => 0x10b4, 119 => 0xa001, 135 => 0x3, 162 => 'ColorSpace', 182 => 0x2f, 183 => 0x25, 184 => 0x17, 234 => 0x1e, 317 => 0x507, 375 => 0x37, 414 => 0xa011, 417 => 0xb, 427 => 0x1b, 428 => 0x83, 429 => 0xe, 445 => 0x6, 506 => 'ColorSpace' }, + 'colorspace2' => { 63 => 0x9 }, + 'colorspecapproximation' => { 135 => 0x2 }, + 'colorspecdata' => { 135 => 0x3 }, + 'colorspecmethod' => { 135 => 0x0 }, + 'colorspecprecedence' => { 135 => 0x1 }, + 'colortempasshot' => { 38 => 0x4, 39 => 0x7, 40 => 0x1d, 41 => 0x59, 42 => 0x6d, 43 => 0x26, 44 => 0x43, 47 => 0x43, 48 => 0x43, 49 => 0x43, 50 => 0x4b }, + 'colortempauto' => { 38 => 0x9, 39 => 0xf, 40 => 0x22, 41 => 0x5e, 42 => 0x72, 43 => 0x1c, 44 => 0x48, 47 => 0x48, 48 => 0x48, 49 => 0x48, 50 => 0x50 }, + 'colortempcloudy' => { 38 => 0x22, 39 => 0x37, 40 => 0x31, 41 => 0xa4, 42 => 0xdb, 43 => 0x35, 44 => 0x5c, 47 => 0x75, 48 => 0x8e, 49 => 0x93, 50 => 0x96, 375 => 0x55 }, + 'colortempcustom' => { 44 => 0x84 }, + 'colortempcustom1' => { 40 => 0x45 }, + 'colortempcustom2' => { 40 => 0x4a }, + 'colortempdaylight' => { 38 => 0x18, 39 => 0x27, 40 => 0x27, 41 => 0x9a, 42 => 0xd1, 43 => 0x2b, 44 => 0x52, 47 => 0x6b, 48 => 0x84, 49 => 0x89, 50 => 0x8c, 375 => 0x53 }, + 'colortemperature' => { 7 => 0x73, 8 => [0x48,0x4e], 9 => 0xc0, 10 => 0x37, 11 => 0x62, 12 => 0x37, 13 => 0x7c, 14 => 0x73, 15 => 0x73, 16 => 0x77, 17 => 0x73, 18 => 0x7c, 19 => 0x58, 20 => 0x73, 21 => 0xc0, 22 => 0x7f, 23 => 0x7d, 24 => 0xc0, 25 => 0xc6, 26 => 0xc7, 27 => 0x135, 28 => 0x7b, 29 => 0x13a, 64 => 0xae, 74 => 0x9, 97 => 0x10ae, 127 => 0x1005, 138 => 0x846, 156 => 'ColorTemperature', 182 => [0x6e,0x49], 183 => 0x3f, 184 => [0x39,0x5e], 186 => 0x10b, 189 => [0x3c,0x4c,0x4e], 335 => 0x321, 375 => 0x50, 407 => 0x1308, 440 => 0xb021, 500 => 'Temperature', 502 => 'Temperature' }, + 'colortemperatureadj' => { 476 => 0x8013 }, + 'colortemperatureauto' => { 234 => 0x4f }, + 'colortemperaturebg' => { 322 => 0x1013 }, + 'colortemperaturecustom' => { 427 => 0xc, 428 => 0xb }, + 'colortemperaturerg' => { 322 => 0x1014 }, + 'colortemperatureset' => { 427 => 0x7, 428 => 0x6 }, + 'colortemperaturesetting' => { 184 => 0x25, 429 => 0x17, 445 => 0xe }, + 'colortempflash' => { 38 => 0x36, 39 => 0x57, 40 => 0x40, 41 => 0xb8, 42 => 0xef, 43 => 0x49, 44 => 0x70, 47 => 0x89, 48 => 0xa2, 49 => 0xa7, 50 => 0xaa, 375 => 0x5a }, + 'colortempflashdata' => { 44 => 0x24a }, + 'colortempfluorescent' => { 38 => 0x2c, 39 => 0x47, 40 => 0x3b, 41 => 0xae, 42 => 0xe5, 43 => 0x3f, 44 => 0x66, 47 => 0x7f, 48 => 0x98, 49 => 0x9d, 50 => 0xa0 }, + 'colortempfluorescentd' => { 375 => 0x57 }, + 'colortempfluorescentn' => { 375 => 0x58 }, + 'colortempfluorescentw' => { 375 => 0x59 }, + 'colortempkelvin' => { 38 => 0x31, 39 => 0x4f, 41 => 0xb3, 42 => 0xea, 43 => 0x44, 44 => 0x6b, 47 => 0x84, 48 => 0x9d, 49 => 0xa2, 50 => 0xa5, 340 => 0x44, 407 => 0x1307 }, + 'colortempmeasured' => { 38 => 0xe, 39 => 0x17, 41 => 0x63, 42 => 0x77, 44 => 0x4d, 47 => 0x4d, 48 => 0x4d, 49 => 0x4d, 50 => 0x55 }, + 'colortemppc1' => { 43 => 0x94, 44 => 0x75 }, + 'colortemppc2' => { 43 => 0x99, 44 => 0x7a }, + 'colortemppc3' => { 43 => 0x9e, 44 => 0x7f }, + 'colortempshade' => { 38 => 0x1d, 39 => 0x2f, 40 => 0x2c, 41 => 0x9f, 42 => 0xd6, 43 => 0x30, 44 => 0x57, 47 => 0x70, 48 => 0x89, 49 => 0x8e, 50 => 0x91, 375 => 0x54 }, + 'colortemptungsten' => { 38 => 0x27, 39 => 0x3f, 40 => 0x36, 41 => 0xa9, 42 => 0xe0, 43 => 0x3a, 44 => 0x61, 47 => 0x7a, 48 => 0x93, 49 => 0x98, 50 => 0x9b, 375 => 0x56 }, + 'colortempunknown' => { 38 => 0x13, 39 => 0x1f, 41 => 0x68, 42 => 0x7c, 43 => 0x21, 47 => 0x52, 48 => 0x52, 49 => 0x52, 50 => 0x5a }, + 'colortempunknown10' => { 38 => 0x63, 39 => 0x9f, 41 => 0x95, 42 => 0xa9, 43 => 0x76, 47 => 0xa2, 48 => 0x7f, 49 => 0x7f, 50 => 0x87 }, + 'colortempunknown11' => { 38 => 0x68, 39 => 0xa7, 41 => 0xbd, 42 => [0xae,0xb3], 43 => 0x7b, 47 => 0xa7, 48 => 0xa7, 49 => 0x84, 50 => 0xaf }, + 'colortempunknown12' => { 38 => 0x6d, 39 => 0xaf, 41 => 0xc2, 42 => 0xb8, 43 => 0x80, 47 => 0xac, 48 => 0xac, 49 => 0xac, 50 => 0xb4 }, + 'colortempunknown13' => { 38 => 0x72, 39 => 0xb7, 41 => 0xc7, 42 => 0xbd, 43 => 0x85, 47 => 0xb1, 48 => 0xb1, 49 => 0xb1, 50 => 0xb9 }, + 'colortempunknown14' => { 41 => 0xcc, 42 => 0xc2, 43 => 0x8a, 47 => 0xb6, 48 => 0xb6, 49 => 0xb6, 50 => 0xbe }, + 'colortempunknown15' => { 41 => 0xd1, 42 => 0xc7, 43 => 0x8f, 47 => 0xbb, 48 => 0xbb, 49 => 0xbb, 50 => 0xc3 }, + 'colortempunknown16' => { 41 => 0xd6, 42 => 0xcc, 43 => 0xa3, 48 => 0xc0, 49 => 0xc0, 50 => 0xc8 }, + 'colortempunknown17' => { 41 => 0xdb, 42 => 0xf4, 48 => 0xc5, 49 => 0xc5, 50 => 0xcd }, + 'colortempunknown18' => { 41 => 0xe0, 42 => 0xf9, 48 => 0xca, 49 => 0xca, 50 => 0xd2 }, + 'colortempunknown19' => { 41 => 0xe5, 42 => 0xfe, 48 => 0xcf, 49 => 0xcf, 50 => 0xd7 }, + 'colortempunknown2' => { 38 => 0x3b, 39 => 0x5f, 41 => 0x6d, 42 => 0x81, 43 => 0x4e, 47 => 0x57, 48 => 0x57, 49 => 0x57, 50 => 0x5f }, + 'colortempunknown20' => { 41 => 0xea, 42 => 0x103, 48 => 0xd4, 49 => 0xd4, 50 => 0xdc }, + 'colortempunknown21' => { 41 => 0xef, 42 => 0x108, 49 => 0xd9, 50 => 0xe1 }, + 'colortempunknown22' => { 41 => 0xf4, 42 => 0x10d, 49 => 0xde, 50 => 0xe6 }, + 'colortempunknown23' => { 41 => 0xf9, 42 => 0x112, 49 => 0xe3, 50 => 0xeb }, + 'colortempunknown24' => { 41 => 0xfe, 42 => 0x117, 49 => 0xe8, 50 => 0xf0 }, + 'colortempunknown25' => { 41 => 0x103, 42 => 0x11c, 49 => 0xed, 50 => 0xf5 }, + 'colortempunknown26' => { 41 => 0x108, 42 => 0x121, 49 => 0xf2, 50 => 0xfa }, + 'colortempunknown27' => { 41 => 0x10d, 42 => 0x126, 49 => 0xf7, 50 => 0xff }, + 'colortempunknown28' => { 41 => 0x112, 49 => 0xfc, 50 => 0x104 }, + 'colortempunknown29' => { 41 => 0x117, 49 => 0x101, 50 => 0x109 }, + 'colortempunknown3' => { 38 => 0x40, 39 => 0x67, 41 => 0x72, 42 => 0x86, 43 => 0x53, 47 => 0x5c, 48 => 0x5c, 49 => 0x5c, 50 => 0x64 }, + 'colortempunknown30' => { 49 => 0x106 }, + 'colortempunknown4' => { 38 => 0x45, 39 => 0x6f, 41 => 0x77, 42 => 0x8b, 43 => 0x58, 47 => 0x61, 48 => 0x61, 49 => 0x61, 50 => 0x69 }, + 'colortempunknown5' => { 38 => 0x4a, 39 => 0x77, 41 => 0x7c, 42 => 0x90, 43 => 0x5d, 47 => 0x66, 48 => 0x66, 49 => 0x66, 50 => 0x6e }, + 'colortempunknown6' => { 38 => 0x4f, 39 => 0x7f, 41 => 0x81, 42 => 0x95, 43 => 0x62, 47 => 0x8e, 48 => 0x6b, 49 => 0x6b, 50 => 0x73 }, + 'colortempunknown7' => { 38 => 0x54, 39 => 0x87, 41 => 0x86, 42 => 0x9a, 43 => 0x67, 47 => 0x93, 48 => 0x70, 49 => 0x70, 50 => 0x78 }, + 'colortempunknown8' => { 38 => 0x59, 39 => 0x8f, 41 => 0x8b, 42 => 0x9f, 43 => 0x6c, 47 => 0x98, 48 => 0x75, 49 => 0x75, 50 => 0x7d }, + 'colortempunknown9' => { 38 => 0x5e, 39 => 0x97, 41 => 0x90, 42 => 0xa4, 43 => 0x71, 47 => 0x9d, 48 => 0x7a, 49 => 0x7a, 50 => 0x82 }, + 'colortint' => { 339 => 0x35c }, + 'colortone' => { 10 => 0x6f, 12 => 0x77, 34 => 0x2a, 51 => 0x2, 63 => 0x8 }, + 'colortoneadj' => { 103 => 0x20304, 108 => 0x11e }, + 'colortoneauto' => { 71 => 0x9c }, + 'colortonefaithful' => { 19 => 0x107, 70 => 0x6c, 71 => 0x6c }, + 'colortonelandscape' => { 19 => 0x105, 70 => 0x3c, 71 => 0x3c }, + 'colortonemonochrome' => { 70 => 0x84, 71 => 0x84 }, + 'colortoneneutral' => { 19 => 0x106, 70 => 0x54, 71 => 0x54 }, + 'colortoneportrait' => { 19 => 0x104, 70 => 0x24, 71 => 0x24 }, + 'colortonestandard' => { 19 => 0x103, 70 => 0xc, 71 => 0xc }, + 'colortoneuserdef1' => { 19 => 0x109, 70 => 0x9c, 71 => 0xb4 }, + 'colortoneuserdef2' => { 19 => 0x10a, 70 => 0xb4, 71 => 0xcc }, + 'colortoneuserdef3' => { 19 => 0x10b, 70 => 0xcc, 71 => 0xe4 }, + 'comlenblksize' => { 138 => 0xfde8 }, + 'commanddialplaybackmode' => { 313 => 0x1cb, 314 => 0x1cb, 315 => 0x1e3 }, + 'commanddials' => { 308 => '5.1' }, + 'commanddialsaperturesetting' => { 297 => '17.3', 298 => '18.3', 300 => '18.3', 301 => '18.3', 306 => '33.3', 307 => '17.3', 310 => '18.3', 311 => '18.3' }, + 'commanddialschangemainsub' => { 297 => '17.2', 298 => '18.1', 300 => '18.1', 301 => '18.1', 306 => '33.2', 307 => '17.2', 310 => '18.1', 311 => '18.1' }, + 'commanddialsmenuandplayback' => { 297 => '17.4', 298 => '18.2', 300 => '18.2', 301 => '18.2', 306 => '33.4', 307 => '17.4', 310 => '18.2', 311 => '18.2' }, + 'commanddialsreverserotation' => { 297 => '17.1', 298 => '6.4', 300 => '6.2', 301 => '6.2', 302 => '16.2', 303 => '17.2', 306 => '33.1', 307 => '17.1', 310 => '6.2', 311 => '6.2', 312 => '18.1' }, + 'commanderchannel' => { 308 => '10.2', 309 => '25.2' }, + 'commandergroupa_ttl-aacomp' => { 308 => '13.1', 309 => '31.1' }, + 'commandergroupa_ttlcomp' => { 312 => '32.1' }, + 'commandergroupamanualoutput' => { 308 => '13.2', 309 => '28.2' }, + 'commandergroupamode' => { 308 => '11.2', 309 => '28.1' }, + 'commandergroupb_ttl-aacomp' => { 308 => '14.1', 309 => '32.1' }, + 'commandergroupb_ttlcomp' => { 312 => '33.1' }, + 'commandergroupbmanualoutput' => { 308 => '14.2', 309 => '29.2' }, + 'commandergroupbmode' => { 308 => '11.3', 309 => '29.1' }, + 'commanderinternalflash' => { 308 => '11.1', 309 => '27.1' }, + 'commanderinternalmanualoutput' => { 308 => '12.2', 309 => '27.2' }, + 'commanderinternalttlchannel' => { 306 => '18.2' }, + 'commanderinternalttlcomp' => { 308 => '12.1', 309 => '30.2', 312 => '31.2' }, + 'commanderinternalttlcompbuiltin' => { 306 => '20.1', 307 => '26.1' }, + 'commanderinternalttlcompgroupa' => { 306 => '21.1', 307 => '27.1' }, + 'commanderinternalttlcompgroupb' => { 306 => '22.1', 307 => '28.1' }, + 'comment' => { 0 => 0x2, 120 => 'Comment', 157 => 'Comment', 330 => 'Comment', 392 => "\xa9cmt", 394 => 'comment', 400 => "\xa9cmt" }, + 'compass' => { 410 => 0x4 }, + 'compatibleversion' => { 500 => 'CompatibleVersion', 502 => 'CompatibleVersion' }, + 'compilation' => { 392 => 'cpil' }, + 'compimageimagespersequence' => { 507 => [\'CompositeImageExposureTimes','CompositeImageExposureTimesNumberOfImagesInSequences'] }, + 'compimagemaxexposureall' => { 507 => [\'CompositeImageExposureTimes','CompositeImageExposureTimesMaxExposureTimesOfAll'] }, + 'compimagemaxexposureused' => { 507 => [\'CompositeImageExposureTimes','CompositeImageExposureTimesMaxExposureTimesOfUsed'] }, + 'compimageminexposureall' => { 507 => [\'CompositeImageExposureTimes','CompositeImageExposureTimesMinExposureTimesOfAll'] }, + 'compimageminexposureused' => { 507 => [\'CompositeImageExposureTimes','CompositeImageExposureTimesMinExposureTimesOfUsed'] }, + 'compimagenumsequences' => { 507 => [\'CompositeImageExposureTimes','CompositeImageExposureTimesNumberOfSequences'] }, + 'compimagesumexposureall' => { 507 => [\'CompositeImageExposureTimes','CompositeImageExposureTimesSumOfExposureTimesOfAll'] }, + 'compimagesumexposureused' => { 507 => [\'CompositeImageExposureTimes','CompositeImageExposureTimesSumOfExposureTimesOfUsed'] }, + 'compimagetotalexposureperiod' => { 507 => [\'CompositeImageExposureTimes','CompositeImageExposureTimesTotalExposurePeriod'] }, + 'compimagevalues' => { 507 => [\'CompositeImageExposureTimes','CompositeImageExposureTimesValues'] }, + 'complianceprofile' => { 519 => 'complianceProfile' }, + 'componentsconfiguration' => { 119 => 0x9101, 162 => 'Components', 506 => 'ComponentsConfiguration' }, + 'componentversion' => { 97 => 0x80c }, + 'composer' => { 179 => 'WM/Composer', 392 => ["\xa9com","\xa9wrt"], 400 => ["\xa9com","\xa9wrt"], 529 => 'composer' }, + 'composerid' => { 392 => 'cmID' }, + 'composerkeywords' => { 400 => "\xa9cok" }, + 'compositeimage' => { 119 => 0xa460, 507 => 'CompositeImage' }, + 'compositeimagecount' => { 119 => 0xa461, 507 => 'CompositeImageCount' }, + 'compositeimageexposuretimes' => { 119 => 0xa462, 507 => 'CompositeImageExposureTimes' }, + 'composition' => { 490 => 'Composition' }, + 'compositionadjust' => { 374 => '0.1' }, + 'compositionadjustrotation' => { 374 => 0x7 }, + 'compositionadjustx' => { 374 => 0x5 }, + 'compositionadjusty' => { 374 => 0x6 }, + 'compressedbitsperpixel' => { 119 => 0x9102, 506 => 'CompressedBitsPerPixel' }, + 'compressedimagesize' => { 186 => 0x40, 322 => 0x40 }, + 'compression' => { 119 => 0x103, 345 => 0xb, 525 => 'Compression' }, + 'compressionfactor' => { 317 => 0x50d }, + 'compressionformat' => { 63 => 0x4 }, + 'compressionratio' => { 162 => 'Compression', 322 => 0x1034 }, + 'compressorversion' => { 400 => 'CNCV' }, + 'condadobvfactor' => { 138 => 0xf3e }, + 'condadodaybvthresh' => { 138 => 0xf3c }, + 'condadodayoffsets' => { 138 => 0xf42 }, + 'condadofluoffsets' => { 138 => 0xf44 }, + 'condadofluthresh' => { 138 => 0xf41 }, + 'condadoillfactor' => { 138 => 0xf3f }, + 'condadoneurange' => { 138 => 0xf3d }, + 'condadotunoffsets' => { 138 => 0xf43 }, + 'condadotunthresh' => { 138 => 0xf40 }, + 'conductor' => { 179 => 'WM/Conductor', 392 => "\xa9con" }, + 'confidence' => { 485 => 'Confidence' }, + 'confidencelevel' => { 496 => 'ConfidenceLevel' }, + 'confidencemime' => { 485 => 'ConfidenceMime' }, + 'constrainedcropheight' => { 108 => 0x266, 386 => 0xd6 }, + 'constrainedcropwidth' => { 108 => 0x262, 386 => 0xd5 }, + 'contact' => { 131 => 0x76 }, + 'contactinfo' => { 500 => 'ContactInfo', 502 => 'ContactInfo', 518 => 'contactInfo' }, + 'container' => { 479 => 'Container' }, + 'containerdirectory' => { 479 => [\'Container','ContainerDirectory'] }, + 'containerdirectoryitem' => { 479 => [\'Container','ContainerDirectoryItem'] }, + 'containerdirectoryitemdatauri' => { 479 => [\'Container','ContainerDirectoryItemDataURI'] }, + 'containerdirectoryitemlength' => { 479 => [\'Container','ContainerDirectoryItemLength'] }, + 'containerdirectoryitemmime' => { 479 => [\'Container','ContainerDirectoryItemMime'] }, + 'containerdirectoryitempadding' => { 479 => [\'Container','ContainerDirectoryItemPadding'] }, + 'containerformat' => { 514 => 'ContainerFormat' }, + 'containerformatidentifier' => { 514 => [\'ContainerFormat','ContainerFormatIdentifier'] }, + 'containerformatname' => { 514 => [\'ContainerFormat','ContainerFormatName'] }, + 'contentcreatedate' => { 392 => "\xa9day", 400 => ['@day',"\xa9day"] }, + 'contentdistributor' => { 179 => 'WM/ContentDistributor' }, + 'contentdistributorid' => { 400 => 'cdis' }, + 'contentid' => { 400 => 'ccid' }, + 'contentidentifier' => { 1 => 0x11, 394 => 'content.identifier' }, + 'contentlocationcode' => { 131 => 0x1a }, + 'contentlocationname' => { 131 => 0x1b }, + 'contenttype' => { 519 => 'contentType' }, + 'continuousbracketing' => { 184 => 0x20 }, + 'continuousdrive' => { 34 => 0x5 }, + 'continuousmodedisplay' => { 313 => 0x51, 314 => 0x51, 315 => 0x51 }, + 'continuousmodeliveview' => { 311 => '77.2' }, + 'continuousshootingspeed' => { 84 => 0x610 }, + 'continuousshotlimit' => { 84 => 0x611 }, + 'contrast' => { 10 => 0x73, 12 => 0x75, 34 => 0xd, 112 => 0xc, 113 => [0x3012,0x20], 119 => [0xa408,0xfe54], 127 => [0x1004,0x1006], 156 => 'Contrast', 176 => 'Contrast', 181 => 0x20, 182 => 0x31, 183 => 0x27, 184 => 0x19, 189 => 0x2, 249 => 0x33, 250 => 0x37, 251 => 0x3f, 322 => 0x1029, 340 => 0x39, 342 => 0x300a, 375 => 0x20, 382 => 0xc, 394 => 'player.movie.visual.contrast', 401 => 0x24, 403 => 0x52, 407 => 0x1012, 417 => 0xd, 427 => 0x1d, 428 => 0x1a, 440 => 0x2004, 500 => 'Contrast', 502 => 'Contrast', 506 => 'Contrast' }, + 'contrast2012' => { 500 => 'Contrast2012', 502 => 'Contrast2012' }, + 'contrastadj' => { 103 => 0x20303, 108 => 0x115, 293 => 0x2c, 476 => 0x8017 }, + 'contrastauto' => { 71 => 0x90 }, + 'contrastcurve' => { 234 => 0x8c }, + 'contrastdetectaf' => { 193 => 0x4 }, + 'contrastdetectafarea' => { 375 => 0x231 }, + 'contrastdetectafinfocus' => { 193 => [0x1c,0x52] }, + 'contrastfaithful' => { 19 => 0xec, 70 => 0x60, 71 => 0x60 }, + 'contrasthighlight' => { 375 => 0x6d }, + 'contrasthighlightshadowadj' => { 375 => 0x6f }, + 'contrastlandscape' => { 19 => 0xea, 70 => 0x30, 71 => 0x30 }, + 'contrastmode' => { 340 => 0x2c }, + 'contrastmonochrome' => { 19 => 0xed, 70 => 0x78, 71 => 0x78 }, + 'contrastneutral' => { 19 => 0xeb, 70 => 0x48, 71 => 0x48 }, + 'contrastportrait' => { 19 => 0xe9, 70 => 0x18, 71 => 0x18 }, + 'contrastsetting' => { 317 => 0x505, 325 => 0x1012, 429 => 0x10, 445 => 0x8 }, + 'contrastshadow' => { 375 => 0x6e }, + 'contraststandard' => { 19 => 0xe8, 70 => 0x0, 71 => 0x0 }, + 'contrastuserdef1' => { 19 => 0xee, 70 => 0x90, 71 => 0xa8 }, + 'contrastuserdef2' => { 19 => 0xef, 70 => 0xa8, 71 => 0xc0 }, + 'contrastuserdef3' => { 19 => 0xf0, 70 => 0xc0, 71 => 0xd8 }, + 'contributedmedia' => { 529 => 'contributedMedia' }, + 'contributedmediaduration' => { 529 => [\'contributedMedia','contributedMediaDuration'] }, + 'contributedmediadurationscale' => { 529 => [\'contributedMedia','contributedMediaDurationScale'] }, + 'contributedmediadurationvalue' => { 529 => [\'contributedMedia','contributedMediaDurationValue'] }, + 'contributedmediamanaged' => { 529 => [\'contributedMedia','contributedMediaManaged'] }, + 'contributedmediapath' => { 529 => [\'contributedMedia','contributedMediaPath'] }, + 'contributedmediastarttime' => { 529 => [\'contributedMedia','contributedMediaStartTime'] }, + 'contributedmediastarttimescale' => { 529 => [\'contributedMedia','contributedMediaStartTimeScale'] }, + 'contributedmediastarttimevalue' => { 529 => [\'contributedMedia','contributedMediaStartTimeValue'] }, + 'contributedmediatrack' => { 529 => [\'contributedMedia','contributedMediaTrack'] }, + 'contributedmediawebstatement' => { 529 => [\'contributedMedia','contributedMediaWebStatement'] }, + 'contributor' => { 503 => 'contributor', 514 => 'Contributor' }, + 'contributoridentifier' => { 514 => [\'Contributor','ContributorIdentifier'] }, + 'contributorname' => { 514 => [\'Contributor','ContributorName'] }, + 'contributorrole' => { 514 => [\'Contributor','ContributorRole'] }, + 'contributors' => { 157 => 'Contributors' }, + 'controldialset' => { 184 => 0x46 }, + 'controlledvocabularyterm' => { 514 => 'CVterm' }, + 'controllerboardversion' => { 335 => 0x332 }, + 'controlmode' => { 77 => 0x12 }, + 'controlringresponse' => { 313 => 0x1d5, 314 => 0x1d5, 315 => 0x1ed }, + 'controlringrotation' => { 84 => 0x712 }, + 'conversionlens' => { 318 => 0x403, 340 => 0x35 }, + 'converter' => { 119 => 0xfe4d, 280 => 0xb, 500 => 'Converter', 502 => 'Converter' }, + 'converttograyscale' => { 500 => 'ConvertToGrayscale', 502 => 'ConvertToGrayscale' }, + 'cookingequipment' => { 521 => 'cookingEquipment' }, + 'cookingmethod' => { 521 => 'cookingMethod' }, + 'copyright' => { 0 => 0x3, 119 => 0x8298, 157 => 'Copyright', 330 => 'Copyright', 345 => 0x8298, 375 => 0x22f, 391 => 'Copyright', 392 => ['cprt',"\xa9cpy"], 394 => 'copyright', 400 => ['cprt',"\xa9cpy"], 500 => 'Copyright', 502 => 'Copyright', 516 => 'Copyright', 522 => 'copyright', 525 => 'Copyright', 529 => 'copyright' }, + 'copyrightflag' => { 389 => 0x40a }, + 'copyrightnotice' => { 131 => 0x74 }, + 'copyrightowner' => { 327 => 'CopyrightOwner' }, + 'copyrightownerid' => { 327 => [\'CopyrightOwner','CopyrightOwnerCopyrightOwnerID'] }, + 'copyrightownerimageid' => { 327 => 'CopyrightOwnerImageID' }, + 'copyrightownername' => { 327 => [\'CopyrightOwner','CopyrightOwnerCopyrightOwnerName'] }, + 'copyrightregistrationnumber' => { 327 => 'CopyrightRegistrationNumber' }, + 'copyrightstatus' => { 327 => 'CopyrightStatus' }, + 'copyrightyear' => { 514 => 'CopyrightYear', 519 => 'copyrightYear' }, + 'coringfilter' => { 321 => 0x310, 322 => 0x102d, 325 => 0x310 }, + 'coringvalues' => { 321 => 0x311, 325 => 0x311 }, + 'corporateentity' => { 519 => 'corporateEntity' }, + 'correlatedcolortemp' => { 339 => 0x35b }, + 'country' => { 161 => 'Country', 340 => 0x69, 517 => 'Country' }, + 'country-primarylocationcode' => { 131 => 0x64 }, + 'country-primarylocationname' => { 131 => 0x65 }, + 'countrycode' => { 233 => 0x5, 387 => 'CountryCode', 513 => 'CountryCode' }, + 'course' => { 521 => 'course' }, + 'coverage' => { 503 => 'coverage' }, + 'coverart' => { 392 => 'covr' }, + 'coverdate' => { 519 => 'coverDate' }, + 'coverdisplaydate' => { 519 => 'coverDisplayDate' }, + 'coveruri' => { 400 => 'cvru' }, + 'cpufirmwareversion' => { 375 => 0x28 }, + 'crc32' => { 504 => 'crc32' }, + 'createdate' => { 119 => 0x9004, 157 => 'CreateDate', 326 => 'CreationDate', 330 => 'create-date', 391 => 'CreationDate', 397 => 0x1, 527 => 'CreateDate' }, + 'creationdate' => { 394 => 'creationdate', 516 => 'CreationDate', 519 => 'creationDate' }, + 'creationtime' => { 330 => 'Creation Time' }, + 'creativestyle' => { 427 => 0x1a, 428 => 0x18, 438 => 0x41, 440 => 0xb020, 472 => 0x4a }, + 'creativestylesetting' => { 429 => 0xf, 445 => 0x7 }, + 'creativestylewaschanged' => { 476 => 0x8001 }, + 'creator' => { 326 => 'Creator', 391 => 'Creator', 503 => 'creator', 514 => 'Creator', 516 => 'Creator' }, + 'creatoraddress' => { 513 => [\'CreatorContactInfo','CreatorContactInfoCiAdrExtadr'] }, + 'creatorappid' => { 178 => 'CreatorAppId' }, + 'creatorcity' => { 513 => [\'CreatorContactInfo','CreatorContactInfoCiAdrCity'] }, + 'creatorcontactinfo' => { 513 => 'CreatorContactInfo' }, + 'creatorcountry' => { 513 => [\'CreatorContactInfo','CreatorContactInfoCiAdrCtry'] }, + 'creatoridentifier' => { 514 => [\'Creator','CreatorIdentifier'] }, + 'creatorname' => { 514 => [\'Creator','CreatorName'] }, + 'creatoropenwithuioptions' => { 178 => 'CreatorOpenWithUIOptions' }, + 'creatorpostalcode' => { 513 => [\'CreatorContactInfo','CreatorContactInfoCiAdrPcode'] }, + 'creatorregion' => { 513 => [\'CreatorContactInfo','CreatorContactInfoCiAdrRegion'] }, + 'creatorrole' => { 514 => [\'Creator','CreatorRole'] }, + 'creatortool' => { 527 => 'CreatorTool' }, + 'creatorworkemail' => { 513 => [\'CreatorContactInfo','CreatorContactInfoCiEmailWork'] }, + 'creatorworktelephone' => { 513 => [\'CreatorContactInfo','CreatorContactInfoCiTelWork'] }, + 'creatorworkurl' => { 513 => [\'CreatorContactInfo','CreatorContactInfoCiUrlWork'] }, + 'credit' => { 131 => 0x6e, 517 => 'Credit' }, + 'creditline' => { 522 => 'creditLine' }, + 'creditlinereq' => { 532 => 'CreditLineReq' }, + 'creditlinerequired' => { 327 => 'CreditLineRequired' }, + 'cropactive' => { 101 => 0x0, 108 => 0x244 }, + 'cropangle' => { 500 => 'CropAngle', 502 => 'CropAngle' }, + 'croparea' => { 234 => 0x45, 476 => 0x9011 }, + 'cropaspectratio' => { 103 => 0x30101, 108 => 0x260 }, + 'cropaspectratiocustom' => { 103 => 0x30102 }, + 'cropbottom' => { 123 => 0x9, 285 => 0x36, 345 => 0x31, 386 => 0xdc, 500 => 'CropBottom', 502 => 'CropBottom' }, + 'cropbottommargin' => { 53 => 0x3 }, + 'cropcircleactive' => { 109 => 0xd6 }, + 'cropcircleradius' => { 109 => 0xd9 }, + 'cropcirclex' => { 109 => 0xd7 }, + 'cropcircley' => { 109 => 0xd8 }, + 'cropconstraintowarp' => { 500 => 'CropConstrainToWarp', 502 => 'CropConstrainToWarp' }, + 'croph' => { 494 => 'CropH' }, + 'cropheight' => { 101 => 0x6, 108 => 0x24c, 321 => 0x615, 325 => 0x615, 500 => 'CropHeight', 502 => 'CropHeight' }, + 'crophispeed' => { 234 => 0x1b }, + 'cropleft' => { 108 => 0x246, 123 => 0x6, 285 => 0x1e, 321 => 0x612, 325 => 0x612, 345 => 0x30, 386 => 0xd9, 500 => 'CropLeft', 502 => 'CropLeft' }, + 'cropleftmargin' => { 53 => 0x0 }, + 'cropmode' => { 127 => 0x104d, 407 => 0x1018 }, + 'croporiginalheight' => { 101 => 0xb }, + 'croporiginalwidth' => { 101 => 0xa }, + 'cropoutputheight' => { 285 => 0xce }, + 'cropoutputheightinches' => { 285 => 0x96 }, + 'cropoutputpixels' => { 285 => 0xd6 }, + 'cropoutputresolution' => { 285 => 0xb6 }, + 'cropoutputscale' => { 285 => 0xbe }, + 'cropoutputwidth' => { 285 => 0xc6 }, + 'cropoutputwidthinches' => { 285 => 0x8e }, + 'croppedareaimageheightpixels' => { 488 => 'CroppedAreaImageHeightPixels', 489 => 'CroppedAreaImageHeightPixels' }, + 'croppedareaimagewidthpixels' => { 488 => 'CroppedAreaImageWidthPixels', 489 => 'CroppedAreaImageWidthPixels' }, + 'croppedarealeftpixels' => { 488 => 'CroppedAreaLeftPixels', 489 => 'CroppedAreaLeftPixels' }, + 'croppedareatoppixels' => { 488 => 'CroppedAreaTopPixels', 489 => 'CroppedAreaTopPixels' }, + 'croppedimageheight' => { 5 => 0x2, 407 => 0x1604 }, + 'croppedimageleft' => { 5 => 0x3 }, + 'croppedimagetop' => { 5 => 0x4 }, + 'croppedimagewidth' => { 5 => 0x1, 407 => 0x1603 }, + 'cropping' => { 476 => 0x9010 }, + 'cropright' => { 123 => 0x8, 285 => 0x2e, 345 => 0x32, 386 => 0xdb, 500 => 'CropRight', 502 => 'CropRight' }, + 'croprightmargin' => { 53 => 0x1 }, + 'croprotatedoriginalheight' => { 101 => 0x2 }, + 'croprotatedoriginalwidth' => { 101 => 0x1 }, + 'croprotation' => { 101 => 0x8, 123 => 0xb }, + 'cropscaledresolution' => { 285 => 0x9e }, + 'cropsourceresolution' => { 285 => 0xae }, + 'croptop' => { 108 => 0x248, 123 => 0x7, 285 => 0x26, 321 => 0x613, 325 => 0x613, 345 => 0x2f, 386 => 0xda, 500 => 'CropTop', 502 => 'CropTop' }, + 'croptopmargin' => { 53 => 0x2 }, + 'cropunit' => { 500 => 'CropUnit', 502 => 'CropUnit' }, + 'cropunits' => { 500 => 'CropUnits', 502 => 'CropUnits' }, + 'cropw' => { 494 => 'CropW' }, + 'cropwidth' => { 101 => 0x5, 108 => 0x24a, 321 => 0x614, 325 => 0x614, 500 => 'CropWidth', 502 => 'CropWidth' }, + 'cropx' => { 101 => 0x3, 494 => 'CropX' }, + 'cropy' => { 101 => 0x4, 494 => 'CropY' }, + 'crossprocess' => { 375 => 0x7b }, + 'crossprocessparams' => { 375 => 0x235 }, + 'cuisine' => { 521 => 'cuisine' }, + 'currenticcprofile' => { 119 => 0xc691 }, + 'currentpreprofilematrix' => { 119 => 0xc692 }, + 'currentversion' => { 476 => 0xd000 }, + 'curve0x' => { 494 => 'Curve0x' }, + 'curve0y' => { 494 => 'Curve0y' }, + 'curve1x' => { 494 => 'Curve1x' }, + 'curve1y' => { 494 => 'Curve1y' }, + 'curve2x' => { 494 => 'Curve2x' }, + 'curve2y' => { 494 => 'Curve2y' }, + 'curve3x' => { 494 => 'Curve3x' }, + 'curve3y' => { 494 => 'Curve3y' }, + 'curve4x' => { 494 => 'Curve4x' }, + 'curve4y' => { 494 => 'Curve4y' }, + 'curves' => { 290 => 0x76a43201 }, + 'custom1' => { 327 => 'Custom1' }, + 'custom10' => { 327 => 'Custom10' }, + 'custom2' => { 327 => 'Custom2' }, + 'custom3' => { 327 => 'Custom3' }, + 'custom4' => { 327 => 'Custom4' }, + 'custom5' => { 327 => 'Custom5' }, + 'custom6' => { 327 => 'Custom6' }, + 'custom7' => { 327 => 'Custom7' }, + 'custom8' => { 327 => 'Custom8' }, + 'custom9' => { 327 => 'Custom9' }, + 'customcolortone' => { 109 => 0x4c }, + 'customcontrast' => { 109 => 0x4e }, + 'customcontrols' => { 84 => 0x70c }, + 'customdefaultunsharpfineness' => { 109 => 0xbe }, + 'customdefaultunsharpstrength' => { 109 => 0xbc }, + 'customdefaultunsharpthreshold' => { 109 => 0xc0 }, + 'customilluminant' => { 138 => 0x405 }, + 'customizedials' => { 84 => 0x715 }, + 'customlinear' => { 109 => 0x4f }, + 'customoutputhighlightpoint' => { 109 => 0x53 }, + 'customoutputshadowpoint' => { 109 => 0x54 }, + 'custompicturestyle' => { 103 => 0x10021 }, + 'custompicturestyledata' => { 103 => 0xf0500 }, + 'custompicturestylefilename' => { 64 => 0x4010 }, + 'customrawhighlight' => { 109 => 0x7c }, + 'customrawhighlightpoint' => { 109 => 0x51 }, + 'customrawshadow' => { 109 => 0x85 }, + 'customrawshadowpoint' => { 109 => 0x52 }, + 'customrendered' => { 119 => 0xa401, 191 => 0x6420, 506 => 'CustomRendered' }, + 'customsaturation' => { 109 => 0x4d, 317 => 0x503 }, + 'customsettingsalldefault' => { 297 => '0.2', 306 => '0.2' }, + 'customsettingsbank' => { 297 => '0.1', 298 => '0.1', 300 => '0.1', 301 => '0.1', 306 => '0.1', 310 => '0.2', 311 => '0.2', 313 => 0x1, 314 => 0x1, 315 => 0x1 }, + 'customsharpness' => { 109 => 0x50 }, + 'customunsharpmaskfineness' => { 109 => 0xb8 }, + 'customunsharpmaskstrength' => { 109 => 0xb6 }, + 'customunsharpmaskthreshold' => { 109 => 0xba }, + 'customwb_rblevels' => { 445 => 0x1a }, + 'customwb_rgblevels' => { 428 => 0x8, 429 => 0x19 }, + 'customwbbluelevel' => { 184 => 0x36 }, + 'customwberror' => { 184 => 0x37 }, + 'customwbgreenlevel' => { 184 => 0x35 }, + 'customwbredlevel' => { 184 => 0x34 }, + 'customwbsetting' => { 184 => 0x26 }, + 'cx' => { 400 => '_cx_' }, + 'cy' => { 400 => '_cy_' }, + 'd-lightinghq' => { 290 => 0x2175eb78 }, + 'd-lightinghqcolorboost' => { 286 => 0x2 }, + 'd-lightinghqhighlight' => { 286 => 0x1 }, + 'd-lightinghqselected' => { 290 => 0x6a6e36b6 }, + 'd-lightinghqshadow' => { 286 => 0x0 }, + 'd-lightinghs' => { 290 => 0xce5554aa }, + 'd-lightinghsadjustment' => { 287 => 0x0 }, + 'd-lightinghscolorboost' => { 287 => 0x1 }, + 'd-rangeoptimizerhighlight' => { 476 => 0x8024 }, + 'd-rangeoptimizermode' => { 476 => 0x8022 }, + 'd-rangeoptimizershadow' => { 476 => 0x802d }, + 'd-rangeoptimizervalue' => { 476 => 0x8023 }, + 'darkblacksegrows' => { 138 => 0x18d8 }, + 'darkfocusenvironment' => { 340 => 0x8003 }, + 'darkframecountfactor' => { 138 => 0xc85 }, + 'darkframelongexposure' => { 138 => 0xc84 }, + 'darkframeshortexposure' => { 138 => 0xc83 }, + 'darkpedestal' => { 138 => 0xc7f }, + 'datacompressionmethod' => { 133 => 0x6e }, + 'dataimprint' => { 181 => 0x34 }, + 'datamining' => { 327 => 'DataMining' }, + 'dataonscreen' => { 514 => 'DataOnScreen' }, + 'dataonscreenregion' => { 514 => [\'DataOnScreen','DataOnScreenRegion'] }, + 'dataonscreenregiond' => { 514 => [\'DataOnScreen','DataOnScreenRegionD'] }, + 'dataonscreenregionh' => { 514 => [\'DataOnScreen','DataOnScreenRegionH'] }, + 'dataonscreenregiontext' => { 514 => [\'DataOnScreen','DataOnScreenRegionText'] }, + 'dataonscreenregionunit' => { 514 => [\'DataOnScreen','DataOnScreenRegionUnit'] }, + 'dataonscreenregionw' => { 514 => [\'DataOnScreen','DataOnScreenRegionW'] }, + 'dataonscreenregionx' => { 514 => [\'DataOnScreen','DataOnScreenRegionX'] }, + 'dataonscreenregiony' => { 514 => [\'DataOnScreen','DataOnScreenRegionY'] }, + 'datascaling' => { 375 => 0x3d }, + 'date' => { 375 => 0x6, 503 => 'date' }, + 'dateacquired' => { 178 => 'DateAcquired', 179 => '{2CBAA8F5-D81F-47CA-B17A-F8D822300131} 100' }, + 'datecreated' => { 131 => 0x37, 517 => 'DateCreated' }, + 'datedisplayformat' => { 282 => 0x3 }, + 'dateidentified' => { 118 => [\'Identification','IdentificationDateIdentified'] }, + 'dateimprint' => { 302 => '4.2' }, + 'daterecieved' => { 519 => 'dateRecieved' }, + 'datesent' => { 132 => 0x46 }, + 'datestampmode' => { 64 => 0x1c, 234 => 0x9d }, + 'datetime' => { 495 => 'datetime', 525 => 'DateTime' }, + 'datetimedigitized' => { 506 => 'DateTimeDigitized' }, + 'datetimeoriginal' => { 100 => 0x0, 119 => 0x9003, 154 => 0x14, 157 => 'OriginalDate', 395 => 'IDIT', 400 => 'date', 401 => 0xb, 402 => 0x3b, 403 => 0x3e, 506 => 'DateTimeOriginal' }, + 'datetimestamp' => { 140 => 0x64 }, + 'datetimeutc' => { 317 => 0x908 }, + 'daylightsavings' => { 78 => 0x3, 282 => 0x2 }, + 'dayofweek' => { 402 => 0x42, 403 => 0x4a }, + 'dccontinent' => { 118 => [\'dctermsLocation','dctermsLocationContinent'] }, + 'dccoordinateprecision' => { 118 => [\'dctermsLocation','dctermsLocationCoordinatePrecision'] }, + 'dccoordinateuncertaintyinmeters' => { 118 => [\'dctermsLocation','dctermsLocationCoordinateUncertaintyInMeters'] }, + 'dccountry' => { 118 => [\'dctermsLocation','dctermsLocationCountry'] }, + 'dccountrycode' => { 118 => [\'dctermsLocation','dctermsLocationCountryCode'] }, + 'dccounty' => { 118 => [\'dctermsLocation','dctermsLocationCounty'] }, + 'dcdecimallatitude' => { 118 => [\'dctermsLocation','dctermsLocationDecimalLatitude'] }, + 'dcdecimallongitude' => { 118 => [\'dctermsLocation','dctermsLocationDecimalLongitude'] }, + 'dcevent' => { 118 => 'Event' }, + 'dcfootprintspatialfit' => { 118 => [\'dctermsLocation','dctermsLocationFootprintSpatialFit'] }, + 'dcfootprintsrs' => { 118 => [\'dctermsLocation','dctermsLocationFootprintSRS'] }, + 'dcfootprintwkt' => { 118 => [\'dctermsLocation','dctermsLocationFootprintWKT'] }, + 'dcgeodeticdatum' => { 118 => [\'dctermsLocation','dctermsLocationGeodeticDatum'] }, + 'dcgeoreferencedby' => { 118 => [\'dctermsLocation','dctermsLocationGeoreferencedBy'] }, + 'dcgeoreferenceddate' => { 118 => [\'dctermsLocation','dctermsLocationGeoreferencedDate'] }, + 'dcgeoreferenceprotocol' => { 118 => [\'dctermsLocation','dctermsLocationGeoreferenceProtocol'] }, + 'dcgeoreferenceremarks' => { 118 => [\'dctermsLocation','dctermsLocationGeoreferenceRemarks'] }, + 'dcgeoreferencesources' => { 118 => [\'dctermsLocation','dctermsLocationGeoreferenceSources'] }, + 'dcgeoreferenceverificationstatus' => { 118 => [\'dctermsLocation','dctermsLocationGeoreferenceVerificationStatus'] }, + 'dchighergeography' => { 118 => [\'dctermsLocation','dctermsLocationHigherGeography'] }, + 'dchighergeographyid' => { 118 => [\'dctermsLocation','dctermsLocationHigherGeographyID'] }, + 'dcisland' => { 118 => [\'dctermsLocation','dctermsLocationIsland'] }, + 'dcislandgroup' => { 118 => [\'dctermsLocation','dctermsLocationIslandGroup'] }, + 'dclocality' => { 118 => [\'dctermsLocation','dctermsLocationLocality'] }, + 'dclocationaccordingto' => { 118 => [\'dctermsLocation','dctermsLocationLocationAccordingTo'] }, + 'dclocationid' => { 118 => [\'dctermsLocation','dctermsLocationLocationID'] }, + 'dclocationremarks' => { 118 => [\'dctermsLocation','dctermsLocationLocationRemarks'] }, + 'dcmaximumdepthinmeters' => { 118 => [\'dctermsLocation','dctermsLocationMaximumDepthInMeters'] }, + 'dcmaximumdistanceabovesurfaceinmeters' => { 118 => [\'dctermsLocation','dctermsLocationMaximumDistanceAboveSurfaceInMeters'] }, + 'dcmaximumelevationinmeters' => { 118 => [\'dctermsLocation','dctermsLocationMaximumElevationInMeters'] }, + 'dcminimumdepthinmeters' => { 118 => [\'dctermsLocation','dctermsLocationMinimumDepthInMeters'] }, + 'dcminimumdistanceabovesurfaceinmeters' => { 118 => [\'dctermsLocation','dctermsLocationMinimumDistanceAboveSurfaceInMeters'] }, + 'dcminimumelevationinmeters' => { 118 => [\'dctermsLocation','dctermsLocationMinimumElevationInMeters'] }, + 'dcmunicipality' => { 118 => [\'dctermsLocation','dctermsLocationMunicipality'] }, + 'dcpointradiusspatialfit' => { 118 => [\'dctermsLocation','dctermsLocationPointRadiusSpatialFit'] }, + 'dcstateprovince' => { 118 => [\'dctermsLocation','dctermsLocationStateProvince'] }, + 'dctermslocation' => { 118 => 'dctermsLocation' }, + 'dcverbatimcoordinates' => { 118 => [\'dctermsLocation','dctermsLocationVerbatimCoordinates'] }, + 'dcverbatimcoordinatesystem' => { 118 => [\'dctermsLocation','dctermsLocationVerbatimCoordinateSystem'] }, + 'dcverbatimdepth' => { 118 => [\'dctermsLocation','dctermsLocationVerbatimDepth'] }, + 'dcverbatimelevation' => { 118 => [\'dctermsLocation','dctermsLocationVerbatimElevation'] }, + 'dcverbatimlatitude' => { 118 => [\'dctermsLocation','dctermsLocationVerbatimLatitude'] }, + 'dcverbatimlocality' => { 118 => [\'dctermsLocation','dctermsLocationVerbatimLocality'] }, + 'dcverbatimlongitude' => { 118 => [\'dctermsLocation','dctermsLocationVerbatimLongitude'] }, + 'dcverbatimsrs' => { 118 => [\'dctermsLocation','dctermsLocationVerbatimSRS'] }, + 'dcverticaldatum' => { 118 => [\'dctermsLocation','dctermsLocationVerticalDatum'] }, + 'dcwaterbody' => { 118 => [\'dctermsLocation','dctermsLocationWaterBody'] }, + 'declination' => { 165 => 'Declination' }, + 'decposition' => { 181 => 0x32 }, + 'defaultautogray' => { 500 => 'DefaultAutoGray', 502 => 'DefaultAutoGray' }, + 'defaultautotone' => { 500 => 'DefaultAutoTone', 502 => 'DefaultAutoTone' }, + 'defaultblackrender' => { 119 => 0xc7a6 }, + 'defaultcroporigin' => { 119 => 0xc61f }, + 'defaultcropsize' => { 119 => 0xc620 }, + 'defaulteraseoption' => { 84 => 0x813 }, + 'defaultscale' => { 119 => 0xc61e }, + 'defaultsspecifictoiso' => { 500 => 'DefaultsSpecificToISO', 502 => 'DefaultsSpecificToISO' }, + 'defaultsspecifictoserial' => { 500 => 'DefaultsSpecificToSerial', 502 => 'DefaultsSpecificToSerial' }, + 'defaultusercrop' => { 119 => 0xc7b5 }, + 'defectisocode' => { 138 => 0x90f }, + 'defringe' => { 500 => 'Defringe', 502 => 'Defringe' }, + 'defringegreenamount' => { 500 => 'DefringeGreenAmount', 502 => 'DefringeGreenAmount' }, + 'defringegreenhuehi' => { 500 => 'DefringeGreenHueHi', 502 => 'DefringeGreenHueHi' }, + 'defringegreenhuelo' => { 500 => 'DefringeGreenHueLo', 502 => 'DefringeGreenHueLo' }, + 'defringepurpleamount' => { 500 => 'DefringePurpleAmount', 502 => 'DefringePurpleAmount' }, + 'defringepurplehuehi' => { 500 => 'DefringePurpleHueHi', 502 => 'DefringePurpleHueHi' }, + 'defringepurplehuelo' => { 500 => 'DefringePurpleHueLo', 502 => 'DefringePurpleHueLo' }, + 'dehaze' => { 500 => 'Dehaze', 502 => 'Dehaze' }, + 'deletedimagecount' => { 234 => 0xa6, 262 => 0x6e }, + 'deprecatedon' => { 498 => 'deprecatedOn' }, + 'depthfar' => { 119 => 0xc7eb }, + 'depthformat' => { 119 => 0xc7e9 }, + 'depthimage' => { 485 => 'Data' }, + 'depthmeasuretype' => { 119 => 0xc7ed }, + 'depthnear' => { 119 => 0xc7ea }, + 'depthunits' => { 119 => 0xc7ec }, + 'derivedfrom' => { 530 => 'DerivedFrom' }, + 'derivedfromalternatepaths' => { 530 => [\'DerivedFrom','DerivedFromAlternatePaths'] }, + 'derivedfromdocumentid' => { 530 => [\'DerivedFrom','DerivedFromDocumentID'] }, + 'derivedfromfilepath' => { 530 => [\'DerivedFrom','DerivedFromFilePath'] }, + 'derivedfromfrompart' => { 530 => [\'DerivedFrom','DerivedFromFromPart'] }, + 'derivedfrominstanceid' => { 530 => [\'DerivedFrom','DerivedFromInstanceID'] }, + 'derivedfromlastmodifydate' => { 530 => [\'DerivedFrom','DerivedFromLastModifyDate'] }, + 'derivedfromlasturl' => { 530 => [\'DerivedFrom','DerivedFromLastURL'] }, + 'derivedfromlinkcategory' => { 530 => [\'DerivedFrom','DerivedFromLinkCategory'] }, + 'derivedfromlinkform' => { 530 => [\'DerivedFrom','DerivedFromLinkForm'] }, + 'derivedfrommanager' => { 530 => [\'DerivedFrom','DerivedFromManager'] }, + 'derivedfrommanagervariant' => { 530 => [\'DerivedFrom','DerivedFromManagerVariant'] }, + 'derivedfrommanageto' => { 530 => [\'DerivedFrom','DerivedFromManageTo'] }, + 'derivedfrommanageui' => { 530 => [\'DerivedFrom','DerivedFromManageUI'] }, + 'derivedfrommaskmarkers' => { 530 => [\'DerivedFrom','DerivedFromMaskMarkers'] }, + 'derivedfromoriginaldocumentid' => { 530 => [\'DerivedFrom','DerivedFromOriginalDocumentID'] }, + 'derivedfrompartmapping' => { 530 => [\'DerivedFrom','DerivedFromPartMapping'] }, + 'derivedfromplacedresolutionunit' => { 530 => [\'DerivedFrom','DerivedFromPlacedResolutionUnit'] }, + 'derivedfromplacedxresolution' => { 530 => [\'DerivedFrom','DerivedFromPlacedXResolution'] }, + 'derivedfromplacedyresolution' => { 530 => [\'DerivedFrom','DerivedFromPlacedYResolution'] }, + 'derivedfromrenditionclass' => { 530 => [\'DerivedFrom','DerivedFromRenditionClass'] }, + 'derivedfromrenditionparams' => { 530 => [\'DerivedFrom','DerivedFromRenditionParams'] }, + 'derivedfromtopart' => { 530 => [\'DerivedFrom','DerivedFromToPart'] }, + 'derivedfromversionid' => { 530 => [\'DerivedFrom','DerivedFromVersionID'] }, + 'description' => { 330 => 'Description', 392 => ['desc','dscp',"\xa9des"], 394 => 'description', 400 => 'dscp', 500 => 'Description', 502 => 'Description', 503 => 'description', 527 => 'Description' }, + 'destination' => { 132 => 0x5 }, + 'destinationcity' => { 375 => 0x24, 381 => 0x3 }, + 'destinationcitycode' => { 382 => 0x1001 }, + 'destinationdst' => { 375 => 0x26, 381 => '0.3' }, + 'developmentdynamicrange' => { 127 => 0x1403 }, + 'deviantmatrixcustom' => { 138 => 0x7de }, + 'deviantmatrixdaylight' => { 138 => 0x7da }, + 'deviantmatrixflash' => { 138 => 0x7dd }, + 'deviantmatrixfluorescent' => { 138 => 0x7dc }, + 'deviantmatrixtungsten' => { 138 => 0x7db }, + 'deviantwhitecustom' => { 138 => 0x842 }, + 'deviantwhitedaylight' => { 138 => 0x83e }, + 'deviantwhiteflash' => { 138 => 0x841 }, + 'deviantwhitefluorescent' => { 138 => 0x840 }, + 'deviantwhitetungsten' => { 138 => 0x83f }, + 'device' => { 519 => 'device' }, + 'devicesettingdescription' => { 506 => 'DeviceSettingDescription' }, + 'devicesettingdescriptioncolumns' => { 506 => [\'DeviceSettingDescription','DeviceSettingDescriptionColumns'] }, + 'devicesettingdescriptionrows' => { 506 => [\'DeviceSettingDescription','DeviceSettingDescriptionRows'] }, + 'devicesettingdescriptionsettings' => { 506 => [\'DeviceSettingDescription','DeviceSettingDescriptionSettings'] }, + 'devicetype' => { 414 => 0x2 }, + 'dewarpdata' => { 116 => 'DewarpData' }, + 'dewarpflag' => { 116 => 'DewarpFlag' }, + 'dialdirectiontvav' => { 84 => 0x706 }, + 'dietaryneeds' => { 521 => 'dietaryNeeds' }, + 'diffractioncompensation' => { 223 => 0x20e, 238 => 0x142, 239 => 0x1b2, 240 => 0x1a2, 241 => 0x1b6, 242 => 0x1b6 }, + 'diffractioncorrection' => { 340 => 0xbc, 365 => 0x3 }, + 'diffractioncorrectionon' => { 103 => 0x2070b }, + 'digitalcreationdate' => { 131 => 0x3e }, + 'digitalcreationtime' => { 131 => 0x3f }, + 'digitaldeehighlightadj' => { 257 => 0x202 }, + 'digitaldeeshadowadj' => { 257 => 0x200 }, + 'digitaldeethreshold' => { 257 => 0x201 }, + 'digitalfilter' => { 417 => 0x59 }, + 'digitalfilter01' => { 362 => 0x5 }, + 'digitalfilter02' => { 362 => 0x16 }, + 'digitalfilter03' => { 362 => 0x27 }, + 'digitalfilter04' => { 362 => 0x38 }, + 'digitalfilter05' => { 362 => 0x49 }, + 'digitalfilter06' => { 362 => 0x5a }, + 'digitalfilter07' => { 362 => 0x6b }, + 'digitalfilter08' => { 362 => 0x7c }, + 'digitalfilter09' => { 362 => 0x8d }, + 'digitalfilter10' => { 362 => 0x9e }, + 'digitalfilter11' => { 362 => 0xaf }, + 'digitalfilter12' => { 362 => 0xc0 }, + 'digitalfilter13' => { 362 => 0xd1 }, + 'digitalfilter14' => { 362 => 0xe2 }, + 'digitalfilter15' => { 362 => 0xf3 }, + 'digitalfilter16' => { 362 => 0x104 }, + 'digitalfilter17' => { 362 => 0x115 }, + 'digitalfilter18' => { 362 => 0x126 }, + 'digitalfilter19' => { 362 => 0x137 }, + 'digitalfilter20' => { 362 => 0x148 }, + 'digitalgain' => { 74 => 0xb }, + 'digitalgem' => { 218 => 0x0 }, + 'digitalice' => { 257 => 0x100 }, + 'digitalimageguid' => { 514 => 'DigImageGUID' }, + 'digitallensoptimizer' => { 62 => 0xa }, + 'digitallensoptimizersetting' => { 80 => 0x9 }, + 'digitalroc' => { 254 => 0x0 }, + 'digitalsourcefiletype' => { 514 => 'DigitalSourcefileType' }, + 'digitalsourcetype' => { 514 => 'DigitalSourceType' }, + 'digitalzoom' => { 34 => 0xc, 112 => 0xa, 127 => 0x1044, 140 => 0x68, 151 => 0x22, 152 => 0x1e, 156 => 'DigitalZoom', 181 => 0xc, 234 => 0x86, 280 => 0xa, 322 => 0x204, 375 => 0x1e, 382 => 0xa, 416 => 0x204, 463 => 0x12, 464 => 0x12 }, + 'digitalzoomon' => { 416 => 0x21b }, + 'digitalzoomratio' => { 119 => 0xa404, 451 => 0x200, 453 => 0x21c, 506 => 'DigitalZoomRatio' }, + 'director' => { 179 => 'WM/Director', 392 => "\xa9dir", 394 => 'director', 400 => "\xa9dir", 529 => 'director' }, + 'directorphotography' => { 529 => 'directorPhotography' }, + 'directory' => { 120 => 'Directory' }, + 'directoryindex' => { 7 => 0x137, 9 => 0x2dc, 11 => 0x17e, 13 => 0x238, 14 => 0x13f, 15 => 0x133, 16 => 0x1df, 17 => 0x1a7, 18 => 0x1f0, 19 => 0xcc, 20 => 0x1c7, 21 => 0x298, 22 => 0x1e7, 23 => 0x1e5, 24 => [0x27c,0x280], 25 => 0x2b6, 26 => 0x2bf, 28 => 0x1f7, 29 => 0x4ba }, + 'directoryindex2' => { 21 => 0x29c }, + 'directorynumber' => { 211 => 0x3, 403 => 0x12 }, + 'disableautocreation' => { 483 => 'DisableAutoCreation' }, + 'disclaimer' => { 330 => 'Disclaimer' }, + 'discnumber' => { 529 => 'discNumber' }, + 'dishtype' => { 521 => 'dishType' }, + 'disknumber' => { 392 => 'disk' }, + 'dispbutton' => { 242 => 0x7d4, 313 => 0x2ab }, + 'displayallafpoints' => { 84 => 0x514 }, + 'displayaperture' => { 34 => 0x23 }, + 'displayedunitsx' => { 390 => 0x2 }, + 'displayedunitsy' => { 390 => 0x6 }, + 'displayname' => { 394 => 'displayname', 518 => 'displayName' }, + 'distance1' => { 140 => 0x28 }, + 'distance2' => { 140 => 0x2c }, + 'distance3' => { 140 => 0x30 }, + 'distance4' => { 140 => 0x34 }, + 'distortion' => { 414 => 0xa050 }, + 'distortioncompensation' => { 476 => 0x8040 }, + 'distortioncontrol' => { 262 => 0x10 }, + 'distortioncorrection' => { 79 => 0x3, 103 => 0x20705, 109 => 0x67, 119 => 0x7036, 317 => 0x50b, 344 => '7.1', 365 => 0x0, 471 => 0x601, 472 => 0x5b }, + 'distortioncorrection2' => { 321 => 0x1011 }, + 'distortioncorrectionalreadyapplied' => { 497 => 'DistortionCorrectionAlreadyApplied' }, + 'distortioncorrectionon' => { 103 => '0x20705.0', 109 => 0x63 }, + 'distortioncorrectionsetting' => { 80 => 0x7, 440 => 0x2013 }, + 'distortioncorrectionvalue' => { 79 => 0x9 }, + 'distortioncorrparams' => { 119 => 0x7037, 450 => 0x1a23, 453 => 0x1870, 455 => 0x189c, 456 => 0x18cc, 457 => 0x17d0, 471 => 0x6ca, 472 => 0x64 }, + 'distortioncorrparamsnumber' => { 453 => 0x1899, 455 => 0x18c5, 456 => 0x18f5, 457 => 0x17f9 }, + 'distortioncorrparamspresent' => { 453 => 0x1898, 455 => 0x18c4, 456 => 0x18f4, 457 => 0x17f8, 459 => 0x10b, 460 => 0x10b, 471 => 0x600, 472 => 0x5a }, + 'distortioneffect' => { 103 => 0x20709 }, + 'distortionn' => { 344 => 0xc }, + 'distortionparam02' => { 344 => 0x2 }, + 'distortionparam04' => { 344 => 0x4 }, + 'distortionparam08' => { 344 => 0x8 }, + 'distortionparam09' => { 344 => 0x9 }, + 'distortionparam11' => { 344 => 0xb }, + 'distortionscale' => { 344 => 0x5 }, + 'distributor' => { 519 => 'distributor' }, + 'distributorproductid' => { 518 => 'distributorProductID' }, + 'dloon' => { 103 => '0x20706.0', 109 => 0xdc }, + 'dlosetting' => { 103 => 0x20706, 109 => 0xdd }, + 'dlosettingapplied' => { 102 => 0x4 }, + 'dloshootingdistance' => { 109 => 0xde }, + 'dloversion' => { 102 => 0x5 }, + 'dmcomment' => { 529 => 'comment' }, + 'dmdithermatrix' => { 138 => 0xc7a }, + 'dmdithermatrixheight' => { 138 => 0xc7c }, + 'dmdithermatrixwidth' => { 138 => 0xc7b }, + 'dngadobedata' => { 119 => 0xc634 }, + 'dngbackwardversion' => { 119 => 0xc613 }, + 'dngignoresidecars' => { 500 => 'DNGIgnoreSidecars', 502 => 'DNGIgnoreSidecars' }, + 'dnglensinfo' => { 119 => 0xc630 }, + 'dngprivatedata' => { 119 => 0xc634 }, + 'dngversion' => { 119 => 0xc612 }, + 'document' => { 330 => 'Document' }, + 'documentancestors' => { 517 => 'DocumentAncestors' }, + 'documenthistory' => { 131 => 0xe7 }, + 'documentid' => { 530 => 'DocumentID' }, + 'documentname' => { 119 => 0x10d }, + 'documentnotes' => { 131 => 0xe6 }, + 'doi' => { 519 => 'doi' }, + 'dopesheet' => { 514 => 'Dopesheet' }, + 'dopesheetlink' => { 514 => 'DopesheetLink' }, + 'dopesheetlinklink' => { 514 => [\'DopesheetLink','DopesheetLinkLink'] }, + 'dopesheetlinklinkqualifier' => { 514 => [\'DopesheetLink','DopesheetLinkLinkQualifier'] }, + 'doublingmicrovolts' => { 138 => 0xc82 }, + 'dpp' => { 495 => 'dpp' }, + 'dr4cameramodel' => { 104 => 0x3 }, + 'drangepriority' => { 127 => 0x1443 }, + 'drangepriorityauto' => { 127 => 0x1444 }, + 'drangepriorityfixed' => { 127 => 0x1445 }, + 'drivemode' => { 113 => 0x3103, 125 => '0.1', 181 => 0x6, 184 => 0x1e, 187 => 0xe, 191 => 0x64d0, 317 => 0x600, 375 => 0x34, 407 => 0x1002, 417 => 0x3, 427 => 0x4, 428 => 0x7e, 429 => 0x34 }, + 'drivemode2' => { 184 => 0xa, 356 => 0x7, 438 => 0xe, 445 => 0x1 }, + 'drivemodesetting' => { 429 => 0x4 }, + 'drivespeed' => { 125 => '0.2' }, + 'dspfirmwareversion' => { 375 => 0x27 }, + 'duration' => { 67 => 0x6a, 155 => 'Duration', 169 => 'Duration', 521 => 'duration', 529 => 'duration' }, + 'durationscale' => { 529 => [\'duration','durationScale'] }, + 'durationvalue' => { 529 => [\'duration','durationValue'] }, + 'dustremovaldata' => { 64 => 0x97 }, + 'dxcropalert' => { 239 => 0x250, 240 => 0x23c, 241 => 0x250, 242 => 0x250 }, + 'dynamicafarea' => { 297 => '1.4', 306 => '1.4' }, + 'dynamicafareasize' => { 239 => 0x254, 240 => 0x240, 241 => 0x254, 242 => 0x254 }, + 'dynamicareaafassist' => { 313 => 0x18, 314 => 0x18, 315 => 0x18 }, + 'dynamicareaafdisplay' => { 298 => '46.1', 300 => '47.1', 301 => '47.1', 310 => '47.1', 311 => '47.1' }, + 'dynamicrange' => { 127 => 0x1400 }, + 'dynamicrangeboost' => { 340 => 0xee }, + 'dynamicrangeexpansion' => { 375 => 0x69, 407 => 0x100e }, + 'dynamicrangemax' => { 108 => 0x7c }, + 'dynamicrangemin' => { 108 => 0x7a }, + 'dynamicrangeoptimizer' => { 184 => 0x15, 440 => [0xb025,0xb04f], 449 => 0x1144, 450 => [0x1144,0x324], 451 => [0x1120,0x300], 452 => [0x119c,0x37c], 453 => [0x1178,0x328], 454 => [0x1030,0x50], 455 => [0x228,0x50], 456 => [0x228,0x50], 457 => [0x21b,0x4e] }, + 'dynamicrangeoptimizerbracket' => { 438 => 0x2e }, + 'dynamicrangeoptimizerlevel' => { 427 => 0x19, 428 => 0x17, 429 => 0xd, 438 => 0x79, 445 => 0x5 }, + 'dynamicrangeoptimizermode' => { 187 => 0x15, 427 => 0x18, 428 => 0x16, 438 => [0x77,0x15] }, + 'dynamicrangeoptimizersetting' => { 184 => 0x27, 429 => 0xc, 445 => 0x4 }, + 'dynamicrangesetting' => { 127 => 0x1402 }, + 'e-dialinprogram' => { 356 => '1.3' }, + 'earliestageorloweststage' => { 118 => [\'GeologicalContext','GeologicalContextEarliestAgeOrLowestStage'] }, + 'earliesteonorlowesteonothem' => { 118 => [\'GeologicalContext','GeologicalContextEarliestEonOrLowestEonothem'] }, + 'earliestepochorlowestseries' => { 118 => [\'GeologicalContext','GeologicalContextEarliestEpochOrLowestSeries'] }, + 'earliesteraorlowesterathem' => { 118 => [\'GeologicalContext','GeologicalContextEarliestEraOrLowestErathem'] }, + 'earliestperiodorlowestsystem' => { 118 => [\'GeologicalContext','GeologicalContextEarliestPeriodOrLowestSystem'] }, + 'earthpos' => { 479 => 'EarthPos' }, + 'earthposaltitude' => { 479 => [\'EarthPos','EarthPosAltitude'] }, + 'earthposlatitude' => { 479 => [\'EarthPos','EarthPosLatitude'] }, + 'earthposlongitude' => { 479 => [\'EarthPos','EarthPosLongitude'] }, + 'earthposrotationw' => { 479 => [\'EarthPos','EarthPosRotationW'] }, + 'earthposrotationx' => { 479 => [\'EarthPos','EarthPosRotationX'] }, + 'earthposrotationy' => { 479 => [\'EarthPos','EarthPosRotationY'] }, + 'earthposrotationz' => { 479 => [\'EarthPos','EarthPosRotationZ'] }, + 'earthpostimestamp' => { 479 => [\'EarthPos','EarthPosTimestamp'] }, + 'easyexposurecomp' => { 312 => '6.1' }, + 'easyexposurecompensation' => { 297 => '6.4', 298 => '6.5', 300 => '6.3', 301 => '6.3', 305 => '5.1', 306 => '4.4', 307 => '5.2', 310 => '6.3', 311 => '6.3', 313 => 0x1d, 314 => 0x1d, 315 => 0x1d }, + 'easymode' => { 34 => 0xb }, + 'edgemapslope' => { 138 => 0x930 }, + 'edgemapx1' => { 138 => 0x931 }, + 'edgemapx2' => { 138 => 0x932 }, + 'edgemapx3' => { 138 => 0x934 }, + 'edgemapx4' => { 138 => 0x935 }, + 'edgenoisereduction' => { 291 => 0x4, 476 => 0x8028 }, + 'edit1' => { 400 => "\xa9ed1" }, + 'edit2' => { 400 => "\xa9ed2" }, + 'edit3' => { 400 => "\xa9ed3" }, + 'edit4' => { 400 => "\xa9ed4" }, + 'edit5' => { 400 => "\xa9ed5" }, + 'edit6' => { 400 => "\xa9ed6" }, + 'edit7' => { 400 => "\xa9ed7" }, + 'edit8' => { 400 => "\xa9ed8" }, + 'edit9' => { 400 => "\xa9ed9" }, + 'edition' => { 519 => 'edition' }, + 'editorialupdate' => { 131 => 0x8 }, + 'editstatus' => { 131 => 0x7, 387 => 'EditStatus', 495 => 'EditStatus' }, + 'editversionname' => { 290 => 0x3d136244 }, + 'effectivelv' => { 375 => 0x2d }, + 'effectivemaxaperture' => { 227 => 0x12, 228 => 0x13, 232 => 0x14 }, + 'eissn' => { 519 => 'eIssn' }, + 'electricalblackcolumns' => { 138 => 0x1810 }, + 'electronicfront-curtainshutter' => { 300 => '5.2', 301 => '5.2', 310 => '5.3', 311 => '5.2' }, + 'electronicfrontcurtainshutter' => { 440 => 0x201a }, + 'elevation' => { 165 => 'Elevation' }, + 'email' => { 157 => 'EMail' }, + 'embargodate' => { 522 => 'embargoDate' }, + 'embdencrightsexpr' => { 514 => 'EmbdEncRightsExpr' }, + 'embeddedencodedrightsexpr' => { 514 => [\'EmbdEncRightsExpr','EmbdEncRightsExprEncRightsExpr'] }, + 'embeddedencodedrightsexprlangid' => { 514 => [\'EmbdEncRightsExpr','EmbdEncRightsExprRightsExprLangId'] }, + 'embeddedencodedrightsexprtype' => { 514 => [\'EmbdEncRightsExpr','EmbdEncRightsExprRightsExprEncType'] }, + 'embeddedxmpdigest' => { 517 => 'EmbeddedXMPDigest' }, + 'emissivity' => { 121 => 0x3 }, + 'emptyslotrelease' => { 239 => 0x723, 240 => 0x625, 241 => 0x655, 242 => 0x6bd }, + 'enablechromanoisereduction' => { 138 => 0xe6e }, + 'enablesharpening' => { 138 => 0x92e }, + 'encodedby' => { 179 => 'WM/EncodedBy', 392 => "\xa9enc" }, + 'encodedwith' => { 394 => 'Encoded_With' }, + 'encoder' => { 392 => "\xa9too", 400 => "\xa9too" }, + 'encoderid' => { 400 => "\xa9enc" }, + 'encodingtime' => { 179 => 'WM/EncodingTime' }, + 'encryptionkey' => { 414 => 0xa020 }, + 'endingpage' => { 519 => 'endingPage' }, + 'enduser' => { 327 => 'EndUser' }, + 'enduserid' => { 327 => [\'EndUser','EndUserEndUserID'] }, + 'endusername' => { 327 => [\'EndUser','EndUserEndUserName'] }, + 'energysavingmode' => { 239 => 0x746, 240 => 0x648, 241 => 0x678, 242 => 0x6e0 }, + 'engineer' => { 529 => 'engineer' }, + 'enhancedarktones' => { 283 => 0x8 }, + 'enhancedenoisealreadyapplied' => { 497 => 'EnhanceDenoiseAlreadyApplied' }, + 'enhancedenoiselumaamount' => { 497 => 'EnhanceDenoiseLumaAmount' }, + 'enhancedenoiseversion' => { 497 => 'EnhanceDenoiseVersion' }, + 'enhancedetailsalreadyapplied' => { 497 => 'EnhanceDetailsAlreadyApplied' }, + 'enhancedetailsversion' => { 497 => 'EnhanceDetailsVersion' }, + 'enhancement' => { 112 => 0x16, 113 => 0x3016 }, + 'enhanceparams' => { 119 => 0xc7ee }, + 'enhancer' => { 321 => 0x300 }, + 'enhancervalues' => { 321 => 0x301 }, + 'enhancesuperresolutionalreadyapplied' => { 497 => 'EnhanceSuperResolutionAlreadyApplied' }, + 'enhancesuperresolutionscale' => { 497 => 'EnhanceSuperResolutionScale' }, + 'enhancesuperresolutionversion' => { 497 => 'EnhanceSuperResolutionVersion' }, + 'envelopenumber' => { 132 => 0x28 }, + 'envelopepriority' => { 132 => 0x3c }, + 'enveloperecordversion' => { 132 => 0x0 }, + 'episode' => { 514 => 'Episode' }, + 'episodeglobaluniqueid' => { 392 => 'egid' }, + 'episodeidentifier' => { 514 => [\'Episode','EpisodeIdentifier'] }, + 'episodename' => { 514 => [\'Episode','EpisodeName'] }, + 'episodenumber' => { 514 => [\'Episode','EpisodeNumber'] }, + 'epsonimageheight' => { 322 => 0x20c }, + 'epsonimagewidth' => { 322 => 0x20b }, + 'epsonsoftware' => { 322 => 0x20d }, + 'equipmentinstitution' => { 478 => 'EquipmentInstitution' }, + 'equipmentmanufacturer' => { 478 => 'EquipmentManufacturer' }, + 'equipmentversion' => { 318 => 0x0 }, + 'ettlii' => { 84 => 0x304, 85 => 0xd, 86 => 0xe, 87 => 0x7, 88 => 0x7, 89 => 0xe }, + 'event' => { 481 => 'Event', 493 => 'Event', 514 => 'Event', 519 => 'event' }, + 'eventalias' => { 518 => 'eventAlias' }, + 'eventdate' => { 118 => [\'Event','EventEventDate'] }, + 'eventday' => { 118 => [\'Event','EventDay'] }, + 'eventearliestdate' => { 118 => [\'Event','EventEarliestDate'] }, + 'eventend' => { 518 => 'eventEnd' }, + 'eventenddayofyear' => { 118 => [\'Event','EventEndDayOfYear'] }, + 'eventfieldnotes' => { 118 => [\'Event','EventFieldNotes'] }, + 'eventfieldnumber' => { 118 => [\'Event','EventFieldNumber'] }, + 'eventhabitat' => { 118 => [\'Event','EventHabitat'] }, + 'eventid' => { 118 => [\'Event','EventEventID'], 514 => 'EventId' }, + 'eventlatestdate' => { 118 => [\'Event','EventLatestDate'] }, + 'eventmonth' => { 118 => [\'Event','EventMonth'] }, + 'eventnumber' => { 401 => 0x9, 402 => 0x37, 403 => 0x3a }, + 'eventparenteventid' => { 118 => [\'Event','EventParentEventID'] }, + 'eventremarks' => { 118 => [\'Event','EventEventRemarks'] }, + 'eventsamplesizeunit' => { 118 => [\'Event','EventSampleSizeUnit'] }, + 'eventsamplesizevalue' => { 118 => [\'Event','EventSampleSizeValue'] }, + 'eventsamplingeffort' => { 118 => [\'Event','EventSamplingEffort'] }, + 'eventsamplingprotocol' => { 118 => [\'Event','EventSamplingProtocol'] }, + 'eventstart' => { 518 => 'eventStart' }, + 'eventstartdayofyear' => { 118 => [\'Event','EventStartDayOfYear'] }, + 'eventsubtype' => { 518 => 'eventSubtype' }, + 'eventtime' => { 118 => [\'Event','EventEventTime'] }, + 'eventtype' => { 518 => 'eventType' }, + 'eventverbatimeventdate' => { 118 => [\'Event','EventVerbatimEventDate'] }, + 'eventyear' => { 118 => [\'Event','EventYear'] }, + 'evfgrid' => { 313 => 0x165, 314 => 0x165, 315 => 0x17d }, + 'evfimageframe' => { 313 => 0x164, 314 => 0x164, 315 => 0x17c }, + 'evfreleaseindicator' => { 313 => 0x24f, 314 => 0x24f, 315 => 0x267 }, + 'evfwarmdisplaybrightness' => { 313 => 0x24d, 314 => 0x24d, 315 => 0x265 }, + 'evfwarmdisplaymode' => { 313 => 0x24b, 314 => 0x24b, 315 => 0x263 }, + 'evsteps' => { 356 => '1.2', 358 => 0x0 }, + 'evstepsize' => { 302 => '5.1', 303 => '6.1', 304 => '6.1', 308 => '0.7' }, + 'exclusivecoverage' => { 490 => 'ExclusiveCoverage' }, + 'exclusivityenddate' => { 522 => 'exclusivityEndDate' }, + 'excursiontolerance' => { 133 => 0x82 }, + 'executiveproducer' => { 392 => "\xa9xpd" }, + 'exif' => { 120 => 'EXIF' }, + 'exifbyteorder' => { 120 => 'ExifByteOrder' }, + 'exifcamerainfo' => { 131 => 0xe8 }, + 'exifimageheight' => { 119 => 0xa003, 506 => 'PixelYDimension' }, + 'exifimagewidth' => { 119 => 0xa002, 506 => 'PixelXDimension' }, + 'exifunicodebyteorder' => { 120 => 'ExifUnicodeByteOrder' }, + 'exifversion' => { 119 => 0x9000, 506 => 'ExifVersion' }, + 'exitpupilposition' => { 227 => 0x4, 228 => 0x4, 232 => 0x4 }, + 'expirationdate' => { 131 => 0x25, 522 => 'expirationDate' }, + 'expirationtime' => { 131 => 0x26 }, + 'exposure' => { 119 => 0xfe51, 500 => 'Exposure', 502 => 'Exposure' }, + 'exposure2012' => { 500 => 'Exposure2012', 502 => 'Exposure2012' }, + 'exposureadj' => { 288 => 0x0 }, + 'exposureadj2' => { 288 => 0x12 }, + 'exposureadjust' => { 417 => 0xc }, + 'exposurebracketingindicatorlast' => { 184 => 0x52 }, + 'exposurebracketshotnumber' => { 187 => 0x2d, 438 => 0x2f }, + 'exposurebracketstepsize' => { 356 => 0x8 }, + 'exposurebracketvalue' => { 234 => 0x19 }, + 'exposurecompautocancel' => { 84 => 0x113 }, + 'exposurecompensation' => { 77 => 0x6, 93 => 0x0, 119 => 0x9204, 140 => 0x24, 156 => 'ExposureComp', 176 => 'ExposureCompensation', 181 => 0xd, 182 => 0x53, 183 => 0x1e, 187 => 0x49c0, 322 => 0x1006, 375 => 0x16, 384 => 0x402, 414 => 0xa013, 417 => [0xc,0x35,0x4d], 449 => 0x114c, 450 => 0x114c, 451 => 0x1128, 453 => 0x1180, 454 => 0x1038, 455 => 0x230, 456 => 0x230, 457 => 0x223, 506 => 'ExposureBiasValue' }, + 'exposurecompensation2' => { 445 => [0x24,0x26,0x2a] }, + 'exposurecompensationbutton' => { 242 => 0x794 }, + 'exposurecompensationmode' => { 184 => 0x47, 187 => 0x2a }, + 'exposurecompensationset' => { 427 => 0x3, 428 => 0x3, 429 => 0x3, 445 => 0x1e }, + 'exposurecompensationsetting' => { 184 => 0x1 }, + 'exposurecompstepsize' => { 297 => '6.3', 298 => '7.3', 300 => '7.3', 301 => '7.3', 306 => '4.3', 310 => '7.3', 311 => '7.3' }, + 'exposurecontrolstep' => { 305 => '6.1', 307 => '6.1' }, + 'exposurecontrolstepsize' => { 297 => '6.2', 298 => '7.1', 300 => '7.1', 301 => '7.1', 306 => '4.2', 310 => '7.1', 311 => '7.1', 312 => '7.1', 313 => 0x1b, 314 => 0x1b, 315 => 0x1b }, + 'exposurecount' => { 127 => 0x1032 }, + 'exposuredelay' => { 242 => 0x800 }, + 'exposuredelaymode' => { 297 => '10.1', 298 => '11.1', 300 => '11.1', 301 => '11.1', 302 => '9.1', 303 => '10.1', 304 => '10.1', 306 => '10.4', 307 => '10.1', 308 => '6.4', 310 => '11.1', 311 => '11.1', 312 => '11.2' }, + 'exposuredifference' => { 234 => 0xe }, + 'exposuregaincustom' => { 138 => 0x89c }, + 'exposuregaindaylight' => { 138 => 0x898 }, + 'exposuregainflash' => { 138 => 0x89b }, + 'exposuregainfluorescent' => { 138 => 0x89a }, + 'exposuregaintungsten' => { 138 => 0x899 }, + 'exposureindex' => { 119 => 0xa215, 506 => 'ExposureIndex' }, + 'exposureindicator' => { 184 => 0x50 }, + 'exposurelevelincrements' => { 82 => 0x6, 83 => 0x6, 84 => 0x101, 85 => 0x5, 86 => 0x6, 87 => 0x5, 88 => 0x5, 89 => 0x6, 90 => 0x4, 427 => 0x58, 428 => 0x58 }, + 'exposurelockused' => { 488 => 'ExposureLockUsed' }, + 'exposuremode' => { 119 => 0xa402, 156 => 'ExposureMode', 181 => 0x1, 182 => 0xa, 183 => 0x0, 184 => 0x0, 187 => 0x34, 317 => 0x200, 337 => 0x40d, 417 => 0x8, 440 => 0xb041, 506 => 'ExposureMode' }, + 'exposuremodeinmanual' => { 84 => 0x10b }, + 'exposureprogram' => { 119 => 0x8822, 407 => 0x1001, 420 => [0x17e,0x43], 427 => 0x3c, 428 => 0x3c, 429 => 0x5, 433 => 0x14, 438 => 0x3f, 445 => 0x2, 449 => 0x1175, 450 => 0x1179, 451 => 0x1155, 452 => 0x11d1, 453 => 0x11ad, 454 => 0x1065, 455 => 0x25d, 456 => 0x25d, 457 => 0x24c, 468 => 0xb, 469 => 0xc, 470 => 0xb, 472 => 0x48, 506 => 'ExposureProgram' }, + 'exposureshift' => { 317 => 0x203 }, + 'exposurestandardadjustment' => { 440 => 0x202d }, + 'exposuretime' => { 7 => 0x4, 8 => 0x4, 9 => 0x4, 10 => 0x4, 11 => 0x4, 12 => 0x4, 13 => 0x4, 14 => 0x4, 15 => 0x4, 16 => 0x4, 17 => 0x4, 18 => 0x4, 19 => 0x4, 20 => 0x4, 21 => 0x4, 22 => 0x4, 23 => 0x4, 24 => 0x4, 25 => 0x4, 26 => 0x4, 27 => 0x4, 28 => 0x4, 29 => 0x4, 30 => 0x6, 31 => 0x7, 77 => 0x16, 119 => 0x829a, 137 => 0xfd05, 140 => 0x20, 142 => 0xfa24, 144 => 0xf104, 147 => 0x12, 149 => 0x38, 151 => 0x14, 152 => 0x10, 154 => 0x10, 156 => 'ExposureTime', 181 => 0x9, 182 => 0x35, 183 => 0x48, 184 => 0x8, 187 => 0x49b8, 375 => 0x12, 414 => 0xa018, 417 => [0x32,0x4a], 427 => 0x0, 428 => 0x0, 445 => [0x21,0x23,0x27], 472 => 0x10, 506 => 'ExposureTime' }, + 'exposuretime2' => { 417 => [0x33,0x4b] }, + 'exposuretuning' => { 234 => 0x1c }, + 'exposurevalue' => { 138 => 0x3 }, + 'exposurewarning' => { 127 => 0x1302 }, + 'exrauto' => { 127 => 0x1033 }, + 'exrmode' => { 127 => 0x1034 }, + 'extdescraccessibility' => { 513 => 'ExtDescrAccessibility' }, + 'extendedmenubanks' => { 239 => 0x120, 240 => 0x114, 241 => 0x124, 242 => 0x124 }, + 'extendedshutterspeeds' => { 313 => 0x102, 314 => 0x102, 315 => 0x118 }, + 'extendedwbdetect' => { 317 => 0x902 }, + 'extender' => { 318 => 0x301 }, + 'extenderfirmwareversion' => { 318 => 0x304 }, + 'extendermagnification' => { 158 => 'Magnification' }, + 'extendermake' => { 158 => 'Make' }, + 'extendermodel' => { 158 => 'Model', 318 => 0x303 }, + 'extenderserialnumber' => { 158 => 'SerialNumber', 318 => 0x302 }, + 'extenderstatus' => { 373 => 0x3 }, + 'externalflash' => { 320 => 0x1201 }, + 'externalflashae1' => { 322 => 0x101f }, + 'externalflashae1_0' => { 322 => 0x101b }, + 'externalflashae2' => { 322 => 0x1020 }, + 'externalflashae2_0' => { 322 => 0x101c }, + 'externalflashbounce' => { 320 => 0x1204, 322 => 0x1026, 363 => 0x1a }, + 'externalflashcompensation' => { 214 => 0x1b }, + 'externalflashexposurecomp' => { 234 => 0x17, 363 => 0x19 }, + 'externalflashfirmware' => { 212 => 0x6, 213 => 0x6, 214 => 0x6, 215 => 0x6, 216 => 0x6, 217 => 0x6 }, + 'externalflashflags' => { 212 => 0x8, 213 => 0x8, 214 => 0x8, 215 => 0x8, 217 => 0x8 }, + 'externalflashguidenumber' => { 320 => 0x1203, 363 => '24.1' }, + 'externalflashgvalue' => { 322 => 0x1025 }, + 'externalflashmode' => { 322 => 0x1028, 363 => 0x2 }, + 'externalflashreadystate' => { 216 => '9.1' }, + 'externalflashstatus' => { 216 => '8.2' }, + 'externalflashzoom' => { 320 => 0x1205, 322 => 0x1027 }, + 'externalflashzoomoverride' => { 216 => '8.1' }, + 'externalmetadatalink' => { 514 => 'ExternalMetadataLink' }, + 'externalsensorbrightnessvalue' => { 335 => 0x311, 338 => 0x311, 339 => 0x311, 342 => 0x3408 }, + 'extrainfoversion' => { 431 => 0x1a }, + 'eyestartaf' => { 184 => 0x40 }, + 'fac100per' => { 138 => 0xe94 }, + 'fac170per' => { 138 => 0xe93 }, + 'fac18per' => { 138 => 0xe92 }, + 'face10position' => { 110 => 0x3f4, 111 => 0x1ec, 210 => 0x28, 360 => 0x12 }, + 'face10size' => { 361 => 0x12 }, + 'face11position' => { 210 => 0x2c, 360 => 0x14 }, + 'face11size' => { 361 => 0x14 }, + 'face12position' => { 210 => 0x30, 360 => 0x16 }, + 'face12size' => { 361 => 0x16 }, + 'face13position' => { 360 => 0x18 }, + 'face13size' => { 361 => 0x18 }, + 'face14position' => { 360 => 0x1a }, + 'face14size' => { 361 => 0x1a }, + 'face15position' => { 360 => 0x1c }, + 'face15size' => { 361 => 0x1c }, + 'face16position' => { 360 => 0x1e }, + 'face16size' => { 361 => 0x1e }, + 'face17position' => { 360 => 0x20 }, + 'face17size' => { 361 => 0x20 }, + 'face18position' => { 360 => 0x22 }, + 'face18size' => { 361 => 0x22 }, + 'face19position' => { 360 => 0x24 }, + 'face19size' => { 361 => 0x24 }, + 'face1position' => { 54 => 0x8, 110 => 0xd, 111 => 0x18, 210 => 0x4, 332 => 0x1, 360 => 0x0, 404 => 0xbc, 434 => 0x1, 435 => 0x0, 436 => 0x0, 437 => 0x5b }, + 'face1size' => { 361 => 0x0 }, + 'face20position' => { 360 => 0x26 }, + 'face20size' => { 361 => 0x26 }, + 'face21position' => { 360 => 0x28 }, + 'face21size' => { 361 => 0x28 }, + 'face22position' => { 360 => 0x2a }, + 'face22size' => { 361 => 0x2a }, + 'face23position' => { 360 => 0x2c }, + 'face23size' => { 361 => 0x2c }, + 'face24position' => { 360 => 0x2e }, + 'face24size' => { 361 => 0x2e }, + 'face25position' => { 360 => 0x30 }, + 'face25size' => { 361 => 0x30 }, + 'face26position' => { 360 => 0x32 }, + 'face26size' => { 361 => 0x32 }, + 'face27position' => { 360 => 0x34 }, + 'face27size' => { 361 => 0x34 }, + 'face28position' => { 360 => 0x36 }, + 'face28size' => { 361 => 0x36 }, + 'face29position' => { 360 => 0x38 }, + 'face29size' => { 361 => 0x38 }, + 'face2position' => { 54 => 0xa, 110 => 0x7c, 111 => 0x4c, 210 => 0x8, 332 => 0x5, 360 => 0x2, 404 => 0xc8, 434 => 0x6, 435 => 0x20, 436 => 0x25, 437 => 0x65 }, + 'face2size' => { 361 => 0x2 }, + 'face30position' => { 360 => 0x3a }, + 'face30size' => { 361 => 0x3a }, + 'face31position' => { 360 => 0x3c }, + 'face31size' => { 361 => 0x3c }, + 'face32position' => { 360 => 0x3e }, + 'face32size' => { 361 => 0x3e }, + 'face3position' => { 54 => 0xc, 110 => 0xeb, 111 => 0x80, 210 => 0xc, 332 => 0x9, 360 => 0x4, 404 => 0xd4, 434 => 0xb, 435 => 0x40, 436 => 0x4a, 437 => 0x6f }, + 'face3size' => { 361 => 0x4 }, + 'face4position' => { 54 => 0xe, 110 => 0x15a, 111 => 0xb4, 210 => 0x10, 332 => 0xd, 360 => 0x6, 404 => 0xe0, 434 => 0x10, 435 => 0x60, 436 => 0x6f, 437 => 0x79 }, + 'face4size' => { 361 => 0x6 }, + 'face5position' => { 54 => 0x10, 110 => 0x1c9, 111 => 0xe8, 210 => 0x14, 332 => 0x11, 360 => 0x8, 404 => 0xec, 434 => 0x15, 435 => 0x80, 436 => 0x94 }, + 'face5size' => { 361 => 0x8 }, + 'face6position' => { 54 => 0x12, 110 => 0x238, 111 => 0x11c, 210 => 0x18, 360 => 0xa, 404 => 0xf8, 434 => 0x1a, 435 => 0xa0, 436 => 0xb9 }, + 'face6size' => { 361 => 0xa }, + 'face7position' => { 54 => 0x14, 110 => 0x2a7, 111 => 0x150, 210 => 0x1c, 360 => 0xc, 404 => 0x104, 434 => 0x1f, 435 => 0xc0, 436 => 0xde }, + 'face7size' => { 361 => 0xc }, + 'face8position' => { 54 => 0x16, 110 => 0x316, 111 => 0x184, 210 => 0x20, 360 => 0xe, 404 => 0x110, 434 => 0x24, 435 => 0xe0, 436 => 0x103 }, + 'face8size' => { 361 => 0xe }, + 'face9position' => { 54 => 0x18, 110 => 0x385, 111 => 0x1b8, 210 => 0x24, 360 => 0x10 }, + 'face9size' => { 361 => 0x10 }, + 'facebalanceorigi' => { 494 => 'FaceBalanceOrigI' }, + 'facebalanceorigq' => { 494 => 'FaceBalanceOrigQ' }, + 'facebalancestrength' => { 494 => 'FaceBalanceStrength' }, + 'facebalancewarmth' => { 494 => 'FaceBalanceWarmth' }, + 'facedetect' => { 375 => 0x76, 414 => 0x100 }, + 'facedetectarea' => { 321 => 0x1201 }, + 'facedetectframecrop' => { 321 => 0x1207 }, + 'facedetectframesize' => { 54 => 0x3, 110 => 0x1, 111 => 0x4, 210 => 0x1, 321 => 0x1203, 375 => 0x77, 404 => 0xb6 }, + 'facedetection' => { 429 => 0x30, 445 => 0x19 }, + 'faceelementpositions' => { 127 => 0x4203 }, + 'faceelementselected' => { 127 => 0x4005 }, + 'faceelementtypes' => { 127 => 0x4201 }, + 'faceid' => { 496 => 'FaceID' }, + 'faceinfounknown' => { 113 => 0x2089 }, + 'facename' => { 414 => 0x123 }, + 'faceorientation' => { 111 => 0x8 }, + 'faceposition' => { 359 => 0x2, 415 => 0x4 }, + 'facepositions' => { 127 => 0x4103 }, + 'facerecognition' => { 414 => 0x120 }, + 'facesdetected' => { 54 => 0x2, 55 => 0x2, 56 => 0x3, 110 => 0x0, 111 => 0x2, 113 => 0x211c, 127 => 0x4100, 210 => 0x3, 321 => 0x1200, 340 => 0x3f, 359 => 0x0, 404 => 0xb5, 415 => 0x0, 434 => 0x0, 437 => 0x3, 448 => 0x30 }, + 'facesrecognized' => { 333 => 0x0 }, + 'facewidth' => { 55 => 0x1 }, + 'fade' => { 440 => 0x2034 }, + 'faithfuloutputhighlightpoint' => { 109 => 0x38 }, + 'faithfuloutputshadowpoint' => { 109 => 0x39 }, + 'faithfulrawcolortone' => { 109 => 0x31 }, + 'faithfulrawcontrast' => { 109 => 0x33 }, + 'faithfulrawhighlight' => { 109 => 0x79 }, + 'faithfulrawhighlightpoint' => { 109 => 0x36 }, + 'faithfulrawlinear' => { 109 => 0x34 }, + 'faithfulrawsaturation' => { 109 => 0x32 }, + 'faithfulrawshadow' => { 109 => 0x82 }, + 'faithfulrawshadowpoint' => { 109 => 0x37 }, + 'faithfulrawsharpness' => { 109 => 0x35 }, + 'faithfulunsharpmaskfineness' => { 109 => 0xac }, + 'faithfulunsharpmaskstrength' => { 109 => 0xaa }, + 'faithfulunsharpmaskthreshold' => { 109 => 0xae }, + 'far' => { 485 => 'Far' }, + 'fastresetlinetime' => { 138 => 0x1860 }, + 'feedidentifier' => { 514 => 'FeedIdentifier' }, + 'femicroadjustment' => { 84 => 0x111 }, + 'ffid' => { 504 => 'ffid' }, + 'field' => { 518 => 'field' }, + 'fieldcount' => { 322 => 0x103f }, + 'fieldofview' => { 400 => "FOV\x00" }, + 'fifoenonepixeldelay' => { 138 => 0x1901 }, + 'filecreatedate' => { 120 => 'FileCreateDate' }, + 'filedatarate' => { 529 => 'fileDataRate' }, + 'fileformat' => { 95 => 0x0, 132 => 0x14, 417 => [0x22,0x26], 440 => 0xb000 }, + 'filegroupid' => { 120 => 'FileGroupID' }, + 'fileindex' => { 7 => 0x143, 9 => 0x2d0, 11 => 0x172, 13 => 0x22c, 14 => 0x133, 15 => 0x13f, 16 => 0x1d3, 17 => 0x19b, 18 => 0x1e4, 19 => 0xd0, 20 => 0x1bb, 21 => 0x28c, 22 => 0x1db, 23 => 0x1d9, 24 => [0x270,0x274], 25 => 0x2aa, 26 => 0x2b3, 28 => 0x1eb, 29 => 0x4ae, 341 => 0x0 }, + 'fileindex2' => { 21 => 0x290 }, + 'filemodifydate' => { 120 => 'FileModifyDate' }, + 'filename' => { 120 => 'FileName' }, + 'filenameasdelivered' => { 327 => 'FileNameAsDelivered' }, + 'filenumber' => { 57 => 0x1, 64 => 0x8, 97 => 0x1817, 114 => 'Canon-FileNumber', 211 => 0x4, 403 => 0x10 }, + 'filenumbermemory' => { 181 => 0x1a }, + 'filenumbersequence' => { 297 => '12.2', 302 => '3.1', 303 => '4.1', 304 => '4.1', 306 => '11.1', 307 => '4.3', 312 => '5.2', 313 => 0x48, 314 => 0x48, 315 => 0x48 }, + 'filepermissions' => { 120 => 'FilePermissions' }, + 'filesource' => { 119 => 0xa300, 127 => 0x8000, 506 => 'FileSource' }, + 'fileuserid' => { 120 => 'FileUserID' }, + 'fileversion' => { 132 => 0x16 }, + 'fillflashautoreduction' => { 82 => 0xe, 83 => 0xe, 90 => 0xa }, + 'filllight' => { 500 => 'FillLight', 502 => 'FillLight' }, + 'fillorder' => { 119 => 0x10a }, + 'filmgraineffect' => { 317 => 0x538 }, + 'filmmode' => { 127 => 0x1401, 337 => 0x412, 340 => 0x42 }, + 'filmtype' => { 257 => 0x2 }, + 'filtereffect' => { 57 => 0xe, 249 => 0x37, 250 => 0x3f, 251 => 0x47, 340 => 0xa1 }, + 'filtereffectauto' => { 71 => 0xa0 }, + 'filtereffectfaithful' => { 70 => 0x70, 71 => 0x70 }, + 'filtereffectlandscape' => { 70 => 0x40, 71 => 0x40 }, + 'filtereffectmonochrome' => { 19 => 0xff, 70 => 0x88, 71 => 0x88 }, + 'filtereffectneutral' => { 70 => 0x58, 71 => 0x58 }, + 'filtereffectportrait' => { 70 => 0x28, 71 => 0x28 }, + 'filtereffectstandard' => { 70 => 0x10, 71 => 0x10 }, + 'filtereffectuserdef1' => { 70 => 0xa0, 71 => 0xb8 }, + 'filtereffectuserdef2' => { 70 => 0xb8, 71 => 0xd0 }, + 'filtereffectuserdef3' => { 70 => 0xd0, 71 => 0xe8 }, + 'finderdisplayduringexposure' => { 83 => 0x1 }, + 'finesharpness' => { 375 => 0x70 }, + 'finetuneoptcenterweighted' => { 297 => '7.2', 298 => '9.1', 300 => '9.1', 301 => '9.1', 305 => '8.1', 310 => '9.1', 311 => '9.1', 312 => '9.1', 313 => 0x23, 314 => 0x23, 315 => 0x23 }, + 'finetuneopthighlightweighted' => { 300 => '46.1', 301 => '46.1', 310 => '46.1', 311 => '46.1', 313 => 0x27, 314 => 0x27, 315 => 0x27 }, + 'finetuneoptmatrixmetering' => { 297 => '8.1', 298 => '8.2', 300 => '8.2', 301 => '8.2', 305 => '7.2', 306 => '6.1', 310 => '8.2', 311 => '8.2', 312 => '8.2', 313 => 0x21, 314 => 0x21, 315 => 0x21 }, + 'finetuneoptspotmetering' => { 297 => '8.2', 298 => '9.2', 300 => '9.2', 301 => '9.2', 305 => '8.2', 306 => '6.2', 310 => '9.2', 311 => '9.2', 312 => '9.2', 313 => 0x25, 314 => 0x25, 315 => 0x25 }, + 'finishexposure' => { 138 => 0xdbd }, + 'finishfiletype' => { 138 => 0xdb8 }, + 'finishlook' => { 138 => 0xdbc }, + 'finishnoise' => { 138 => 0xdba }, + 'finishresolution' => { 138 => 0xdb9 }, + 'finishsharpening' => { 138 => 0xdbb }, + 'firmware' => { 322 => 0x405, 417 => [0x8c,0x17,0x3b], 497 => 'Firmware' }, + 'firmware2' => { 417 => 0x57 }, + 'firmwaredate' => { 112 => 0x15, 113 => 0x2001, 401 => 0x4, 403 => 0x30 }, + 'firmwareid' => { 188 => 0x0 }, + 'firmwarename' => { 414 => 0xa001 }, + 'firmwarerevision' => { 19 => 0xa4, 64 => 0x1e, 405 => 0x0 }, + 'firmwarerevision2' => { 405 => 0xc }, + 'firmwareversion' => { 7 => 0x10b, 11 => 0x136, 14 => 0xff, 15 => 0x107, 138 => [0x415,0xce5], 154 => 0x57, 156 => 'FirmwareVersion', 338 => 0x320, 340 => 0x2, 342 => 0x3109, 375 => 0x230, 400 => ['CNFV','FIRM'], 402 => 0x18, 403 => 0x2a, 407 => 0x2 }, + 'firmwareversions' => { 384 => 0x301 }, + 'firstphotodate' => { 488 => 'FirstPhotoDate' }, + 'firstpublicationdate' => { 327 => 'FirstPublicationDate' }, + 'fixtureidentifier' => { 131 => 0x16, 495 => 'FixtureIdentifier' }, + 'flash' => { 114 => 'XMP-Flash', 119 => 0x9209, 152 => 0x22, 182 => 0x1f, 183 => 0x15, 403 => 0x5a, 506 => 'Flash' }, + 'flashaction' => { 427 => 0x3e, 428 => 0x3e, 440 => 0x2017, 445 => [0x2a,0x2c,0x30] }, + 'flashaction2' => { 427 => 0x4c, 428 => 0x4c, 445 => 0x77 }, + 'flashactionexternal' => { 445 => [0x78,0x7c] }, + 'flashactivity' => { 34 => 0x1c }, + 'flashbatterylevel' => { 44 => 0x249 }, + 'flashbias' => { 340 => 0x24 }, + 'flashbits' => { 34 => 0x1d }, + 'flashburstpriority' => { 313 => 0x111, 314 => 0x111, 315 => 0x129 }, + 'flashbuttonfunction' => { 84 => 0x70e }, + 'flashchargelevel' => { 322 => 0x1010 }, + 'flashcolorfilter' => { 214 => 0x10, 215 => 0x10, 217 => 0x10 }, + 'flashcommandermode' => { 212 => '9.1', 213 => '9.1', 214 => '9.1', 215 => '9.1', 217 => '9.1' }, + 'flashcompensation' => { 138 => 0x3f3, 212 => 0xa, 213 => 0xa, 214 => 0xa, 215 => 0x27, 216 => 0xa, 217 => 0xa, 497 => 'FlashCompensation' }, + 'flashcontrol' => { 427 => 0x23, 428 => 0x1f, 429 => 0x21 }, + 'flashcontrolbuilt-in' => { 306 => '16.1', 307 => '23.1', 309 => '23.1', 310 => '24.1' }, + 'flashcontrolmode' => { 212 => '9.2', 213 => '9.2', 214 => '9.2', 215 => '9.2', 217 => '9.2', 223 => 0x214, 238 => 0x148, 239 => 0x1b8, 240 => 0x1a8, 241 => 0x1bc, 242 => 0x1bc, 317 => 0x404 }, + 'flashcurtain' => { 340 => 0x48 }, + 'flashdefault' => { 184 => 0x42 }, + 'flashdevice' => { 322 => 0x1005 }, + 'flashdistance' => { 113 => 0x2034 }, + 'flashenergy' => { 119 => 0xa20b, 506 => 'FlashEnergy' }, + 'flashexposurebracketvalue' => { 234 => 0x18 }, + 'flashexposurecomp' => { 77 => 0xf, 127 => 0x1011, 159 => 'ExposureComp', 181 => 0x23, 186 => 0x104, 187 => 0x49c1, 234 => 0x12, 317 => 0x401, 322 => 0x1023, 375 => 0x4d, 407 => 0x100b, 417 => [0x3a,0x56], 440 => 0x104 }, + 'flashexposurecomp2' => { 278 => 0x4d2 }, + 'flashexposurecomp3' => { 214 => 0x1d }, + 'flashexposurecomp4' => { 214 => 0x27 }, + 'flashexposurecomparea' => { 298 => '38.4', 300 => '38.4', 301 => '38.4', 310 => '38.4', 311 => '38.4', 313 => 0x59, 314 => 0x59, 315 => 0x59 }, + 'flashexposurecompset' => { 184 => 0x10, 348 => 0xe, 427 => 0x14, 428 => 0x12, 429 => 0x23, 445 => 0x1f }, + 'flashexposurecompset2' => { 445 => [0x26,0x2c] }, + 'flashexposureindicator' => { 184 => 0x54 }, + 'flashexposureindicatorlast' => { 184 => 0x56 }, + 'flashexposureindicatornext' => { 184 => 0x55 }, + 'flashexposurelock' => { 57 => 0x19 }, + 'flashfired' => { 140 => 0x5d, 159 => 'Fired', 181 => 0x14, 277 => '590.3', 506 => [\'Flash','FlashFired'] }, + 'flashfiring' => { 84 => 0x306, 85 => 0x6, 86 => 0x7, 89 => 0x7 }, + 'flashfirmwareversion' => { 318 => 0x1002 }, + 'flashfocallength' => { 212 => 0xb, 213 => 0xc, 214 => 0xc, 215 => 0xc, 216 => 0xc, 217 => 0x26 }, + 'flashfunction' => { 187 => 0x31, 506 => [\'Flash','FlashFunction'] }, + 'flashgndistance' => { 212 => 0xe, 213 => 0xf, 214 => 0xf, 215 => 0xf, 216 => 0xf, 217 => 0xf, 223 => 0x21a, 238 => 0x14e, 240 => 0x1ae, 241 => 0x1c2, 242 => 0x1c2 }, + 'flashgroupacompensation' => { 212 => 0x11, 213 => 0x12, 214 => 0x13, 215 => 0x28, 216 => 0x28, 217 => 0x28 }, + 'flashgroupacontrolmode' => { 212 => 0xf, 213 => '16.1', 214 => '17.1', 215 => '17.1', 216 => '17.1', 217 => '17.1' }, + 'flashgroupaoutput' => { 212 => 0x11, 213 => 0x12, 214 => 0x13, 215 => 0x28, 216 => 0x28, 217 => 0x28 }, + 'flashgroupbcompensation' => { 212 => 0x12, 213 => 0x13, 214 => 0x14, 215 => 0x29, 216 => 0x29, 217 => 0x29 }, + 'flashgroupbcontrolmode' => { 212 => 0x10, 213 => '17.1', 214 => '18.1', 215 => '18.1', 216 => '18.1', 217 => '18.1' }, + 'flashgroupboutput' => { 212 => 0x12, 213 => 0x13, 214 => 0x14, 215 => 0x29, 216 => 0x29, 217 => 0x29 }, + 'flashgroupccompensation' => { 213 => 0x14, 214 => 0x15, 215 => 0x2a, 216 => 0x2a, 217 => 0x2a }, + 'flashgroupccontrolmode' => { 213 => '17.2', 214 => '18.2', 215 => '18.2', 216 => '18.2', 217 => '18.2' }, + 'flashgroupcoutput' => { 213 => 0x14, 214 => 0x15, 215 => 0x2a, 216 => 0x2a, 217 => 0x2a }, + 'flashguidenumber' => { 77 => 0xd, 94 => 0x0, 159 => 'GuideNumber' }, + 'flashilluminationpattern' => { 217 => 0x25 }, + 'flashintensity' => { 112 => [0x19,0x5], 317 => 0x405 }, + 'flashlevel' => { 299 => 0x9, 440 => 0xb048 }, + 'flashmake' => { 159 => 'Make' }, + 'flashmanufacturer' => { 178 => 'FlashManufacturer' }, + 'flashmastercompensation' => { 223 => 0x22e, 238 => 0x162, 240 => 0x1aa, 241 => 0x1be, 242 => 0x1be }, + 'flashmastercontrolmode' => { 223 => 0x22c, 238 => 0x160 }, + 'flashmasteroutput' => { 223 => 0x232, 238 => 0x166 }, + 'flashmetering' => { 181 => 0x3f, 184 => 0x1c }, + 'flashmeteringmode' => { 7 => 0x15, 13 => 0x15, 14 => 0x15, 15 => 0x15, 16 => 0x15, 17 => 0x15, 18 => 0x15, 20 => 0x15, 22 => 0x15, 28 => 0x15 }, + 'flashmeteringsegments' => { 375 => 0x20a }, + 'flashmode' => { 112 => 0x4, 138 => 0x3f2, 140 => 0x5c, 151 => 0x27, 159 => 'Mode', 181 => 0x2, 182 => 0x20, 183 => 0x16, 184 => 0xf, 234 => 0x87, 317 => 0x400, 322 => 0x1004, 375 => 0xc, 382 => 0x4, 406 => 0x20, 407 => 0x100a, 416 => 0x225, 427 => 0x13, 428 => 0x7f, 429 => 0x20, 445 => 0x10, 449 => 0x1138, 450 => 0x1138, 451 => 0x1114, 452 => 0x1190, 453 => 0x116c, 454 => 0x1024, 455 => 0x21c, 456 => 0x21c, 457 => 0x211, 506 => [\'Flash','FlashMode'] }, + 'flashmodebutton' => { 242 => 0x80e }, + 'flashmodebuttonplaybackmode' => { 242 => 0x818 }, + 'flashmodel' => { 159 => 'Model', 178 => 'FlashModel', 318 => 0x1001 }, + 'flashoptions' => { 356 => 0x2 }, + 'flashoptions2' => { 356 => 0x10 }, + 'flashoutput' => { 44 => 0x248, 77 => 0x21, 212 => 0xa, 213 => 0xa, 214 => 0xa, 215 => 0x27, 217 => 0x21, 223 => 0x21e, 238 => 0x152, 240 => 0x1b2, 241 => 0x1c6, 242 => 0x1c6 }, + 'flashpixversion' => { 119 => 0xa000, 506 => 'FlashpixVersion' }, + 'flashredeyemode' => { 506 => [\'Flash','FlashRedEyeMode'] }, + 'flashremotecontrol' => { 223 => 0x228, 238 => 0x15c, 240 => 0x1bc, 317 => 0x403 }, + 'flashreturn' => { 506 => [\'Flash','FlashReturn'] }, + 'flashserialnumber' => { 159 => 'SerialNumber', 318 => 0x1003 }, + 'flashsetting' => { 234 => 0x8 }, + 'flashshutterspeed' => { 297 => '20.2', 298 => '23.2', 300 => '23.2', 301 => '23.2', 306 => '15.2', 307 => '22.2', 308 => '7.2', 309 => '22.2', 310 => '23.2', 311 => '23.2', 312 => '23.1', 313 => 0x57, 314 => 0x57, 315 => 0x57 }, + 'flashsource' => { 212 => 0x4, 213 => 0x4, 214 => 0x4, 215 => 0x4, 216 => 0x4, 217 => 0x4 }, + 'flashstatus' => { 363 => 0x0, 445 => [0x82,0x86], 459 => 0x31, 460 => 0x39, 461 => 0x39 }, + 'flashstatusbuilt-in' => { 429 => [0x87,0x287] }, + 'flashstatusexternal' => { 429 => [0x88,0x288] }, + 'flashsyncspeed' => { 297 => '20.1', 298 => '23.1', 300 => '23.1', 301 => '23.1', 306 => '15.1', 307 => '22.1', 309 => '22.1', 310 => '23.1', 311 => '23.1', 313 => 0x53, 314 => 0x53, 315 => 0x53 }, + 'flashsyncspeedav' => { 82 => 0x3, 84 => 0x10f, 85 => 0x2, 86 => 0x3, 87 => 0x2, 88 => 0x2, 89 => 0x3, 90 => 0x6 }, + 'flashthreshold' => { 94 => 0x1 }, + 'flashtype' => { 159 => 'Type', 184 => 0x59, 234 => 0x9, 318 => 0x1000 }, + 'flashwarning' => { 307 => '30.1', 308 => '7.1', 312 => '31.1', 340 => 0x62 }, + 'flashwirelessoption' => { 223 => 0x234, 238 => 0x15a, 240 => 0x1c8 }, + 'flexiblespotposition' => { 440 => 0x201d }, + 'flickadvancedirection' => { 313 => 0x25f, 314 => 0x25f, 315 => 0x277 }, + 'flickerreduce' => { 416 => 0x218 }, + 'flickerreduction' => { 127 => 0x1446, 261 => 0x7 }, + 'flickerreductionindicator' => { 256 => 0x532 }, + 'flickerreductionshooting' => { 239 => 0x1b4, 240 => 0x1a4, 241 => 0x1b8, 242 => 0x1b8 }, + 'flightpitchdegree' => { 116 => 'FlightPitchDegree' }, + 'flightrolldegree' => { 116 => 'FlightRollDegree' }, + 'flightxspeed' => { 116 => 'FlightXSpeed' }, + 'flightyawdegree' => { 116 => 'FlightYawDegree' }, + 'flightyspeed' => { 116 => 'FlightYSpeed' }, + 'flightzspeed' => { 116 => 'FlightZSpeed' }, + 'fliphorizontal' => { 290 => 0x76a43206 }, + 'fnumber' => { 7 => 0x3, 9 => 0x3, 11 => 0x3, 13 => 0x3, 14 => 0x3, 15 => 0x3, 16 => 0x3, 17 => 0x3, 18 => 0x3, 19 => 0x3, 20 => 0x3, 21 => 0x3, 22 => 0x3, 23 => 0x3, 24 => 0x3, 25 => 0x3, 26 => 0x3, 27 => 0x3, 28 => 0x3, 29 => 0x3, 30 => 0x5, 31 => 0x6, 77 => 0x15, 119 => 0x829d, 137 => 0xfd04, 140 => 0x1e, 142 => 0xfa23, 144 => 0xf103, 147 => 0x13, 149 => 0x3c, 151 => 0x1c, 152 => 0x18, 154 => 0xc, 163 => 'FNumber', 181 => 0xa, 182 => 0x36, 183 => 0x47, 184 => 0x9, 187 => 0x49c7, 232 => 0x38, 339 => 0x35a, 375 => 0x13, 414 => 0xa019, 417 => [0x31,0x49], 427 => 0x1, 428 => 0x1, 445 => [0x20,0x22,0x26], 506 => 'FNumber' }, + 'focaldistance' => { 486 => 'FocalDistance' }, + 'focallength' => { 7 => 0x1d, 8 => 0xa, 9 => 0x23, 10 => 0x9, 11 => 0x1d, 12 => 0x9, 13 => 0x1e, 14 => 0x1d, 15 => 0x1d, 16 => 0x1e, 17 => 0x1e, 18 => 0x1e, 19 => 0x28, 20 => 0x1e, 21 => 0x23, 22 => 0x1e, 23 => 0x1e, 24 => 0x23, 25 => 0x23, 26 => 0x23, 27 => 0x23, 28 => 0x1e, 29 => 0x23, 59 => 0x1, 113 => 0x1d, 119 => 0x920a, 147 => 0x1d, 163 => 'FocalLength', 181 => 0x12, 227 => 0xa, 228 => 0xb, 232 => [0xc,0x3c], 334 => 0x1, 375 => 0x1d, 384 => 0x403, 407 => 0x1500, 426 => 0xe, 453 => 0x1278, 454 => 0x1134, 455 => 0x32c, 456 => 0x32c, 457 => 0x30a, 506 => 'FocalLength' }, + 'focallength2' => { 445 => [0x23,0x25,0x29] }, + 'focallengthin35mmformat' => { 119 => 0xa405, 414 => 0xa01a, 506 => 'FocalLengthIn35mmFilm' }, + 'focallengthtelezoom' => { 426 => 0x10 }, + 'focalplaneafpointarea' => { 458 => 0x2 }, + 'focalplaneafpointlocation1' => { 458 => 0x6 }, + 'focalplaneafpointlocation10' => { 458 => 0x2a }, + 'focalplaneafpointlocation11' => { 458 => 0x2e }, + 'focalplaneafpointlocation12' => { 458 => 0x32 }, + 'focalplaneafpointlocation13' => { 458 => 0x36 }, + 'focalplaneafpointlocation14' => { 458 => 0x3a }, + 'focalplaneafpointlocation15' => { 458 => 0x3e }, + 'focalplaneafpointlocation2' => { 458 => 0xa }, + 'focalplaneafpointlocation3' => { 458 => 0xe }, + 'focalplaneafpointlocation4' => { 458 => 0x12 }, + 'focalplaneafpointlocation5' => { 458 => 0x16 }, + 'focalplaneafpointlocation6' => { 458 => 0x1a }, + 'focalplaneafpointlocation7' => { 458 => 0x1e }, + 'focalplaneafpointlocation8' => { 458 => 0x22 }, + 'focalplaneafpointlocation9' => { 458 => 0x26 }, + 'focalplaneafpointsused' => { 458 => 0x1 }, + 'focalplanediagonal' => { 318 => 0x103, 322 => 0x205 }, + 'focalplaneresolutionunit' => { 119 => 0xa210, 506 => 'FocalPlaneResolutionUnit' }, + 'focalplanexresolution' => { 119 => 0xa20e, 506 => 'FocalPlaneXResolution' }, + 'focalplanexsize' => { 59 => 0x2 }, + 'focalplanexunknown' => { 59 => 0x2 }, + 'focalplaneyresolution' => { 119 => 0xa20f, 506 => 'FocalPlaneYResolution' }, + 'focalplaneysize' => { 59 => 0x3 }, + 'focalplaneyunknown' => { 59 => 0x3 }, + 'focalpointx' => { 486 => 'FocalPointX' }, + 'focalpointy' => { 486 => 'FocalPointY' }, + 'focaltype' => { 10 => 0x2d, 59 => 0x0 }, + 'focalunits' => { 34 => 0x19 }, + 'focus' => { 280 => 0x8 }, + 'focusarea' => { 181 => 0x31 }, + 'focusareaselection' => { 308 => '15.2' }, + 'focusbracket' => { 340 => 0xbd }, + 'focusbracketstepsize' => { 317 => 0x308 }, + 'focuscontinuous' => { 34 => 0x20 }, + 'focusdisplayaiservoandmf' => { 84 => 0x515 }, + 'focusdistance' => { 163 => 'FocusDistance', 181 => 0x13, 187 => 0x49bb, 227 => 0x9, 228 => 0xa, 232 => [0xb,0x4e], 320 => 0x305, 334 => 0x0, 338 => 0x304, 339 => 0x304 }, + 'focusdistancelower' => { 7 => 0x45, 9 => 0x8e, 11 => 0x45, 13 => 0x56, 14 => 0x45, 15 => 0x45, 16 => 0x52, 17 => 0x52, 18 => 0x56, 20 => 0x52, 21 => 0x8e, 22 => 0x59, 23 => 0x57, 24 => 0x8e, 25 => 0x94, 26 => 0x95, 27 => 0xa7, 28 => 0x56, 29 => 0xa7, 57 => 0x15, 77 => 0x14 }, + 'focusdistancerange' => { 1 => 0xc }, + 'focusdistancerangewidth' => { 232 => 0x4c }, + 'focusdistanceupper' => { 7 => 0x43, 9 => 0x8c, 11 => 0x43, 13 => 0x54, 14 => 0x43, 15 => 0x43, 16 => 0x50, 17 => 0x50, 18 => 0x54, 20 => 0x50, 21 => 0x8c, 22 => 0x57, 23 => 0x55, 24 => 0x8c, 25 => 0x92, 26 => 0x93, 27 => 0xa5, 28 => 0x54, 29 => 0xa5, 57 => 0x14, 77 => 0x13 }, + 'focusholdbutton' => { 184 => 0x44 }, + 'focusinfoversion' => { 320 => 0x0 }, + 'focusingscreen' => { 83 => 0x0, 84 => 0x80b, 89 => 0x0 }, + 'focuslocation' => { 440 => 0x2027 }, + 'focuslocked' => { 180 => 0x14 }, + 'focusmode' => { 34 => 0x7, 112 => 0x3, 113 => [0x3003,0xd], 127 => 0x1021, 138 => 0x3f5, 140 => 0x38, 156 => 'FocusMode', 181 => 0x30, 183 => 0xe, 184 => 0xc, 234 => 0x7, 317 => 0x301, 322 => 0x100b, 340 => 0x7, 375 => 0xd, 382 => 0x3, 407 => 0x1006, 420 => [0xb,0x5], 426 => [0x15,0x1d], 427 => 0x4d, 428 => 0x4d, 440 => [0xb042,0xb04e,0x201b], 445 => 0x13, 466 => 0x16 }, + 'focusmode2' => { 126 => '0.1', 356 => '3.1', 445 => [0x2c,0x2e,0x32] }, + 'focusmodesetting' => { 299 => '10.1', 424 => 0x14, 425 => 0x15, 427 => 0x10, 428 => 0xf, 429 => 0x6 }, + 'focusmodeswitch' => { 184 => 0x58, 427 => 0x2e }, + 'focuspeakingdisplay' => { 313 => 0x235, 314 => 0x235, 315 => 0x24d }, + 'focuspeakinghighlightcolor' => { 313 => 0x4b, 314 => 0x4b, 315 => 0x4b }, + 'focuspeakinglevel' => { 313 => 0x49, 314 => 0x49, 315 => 0x49 }, + 'focuspixel' => { 127 => 0x1023 }, + 'focuspointlock' => { 313 => 0x1d3, 314 => 0x1d3, 315 => 0x1eb }, + 'focuspointpersistence' => { 313 => 0x105, 314 => 0x105, 315 => 0x11b }, + 'focuspointwrap' => { 297 => '2.2', 298 => '2.2', 300 => '2.1', 301 => '2.1', 305 => '1.1', 306 => '2.2', 307 => '1.1', 310 => '2.2', 311 => '2.1', 312 => '2.5', 313 => 0x16, 314 => 0x16, 315 => 0x16 }, + 'focusposition' => { 1 => 0x2f, 227 => 0x8, 228 => 0x8, 375 => 0x10, 438 => 0x9bb }, + 'focusposition2' => { 445 => [0x29,0x2b,0x2f], 466 => 0x2d, 469 => 0x20 }, + 'focuspositionhorizontal' => { 193 => 0x2f, 194 => 0x43 }, + 'focuspositionvertical' => { 193 => 0x31, 194 => 0x45 }, + 'focusprocess' => { 317 => 0x302 }, + 'focusrange' => { 34 => 0x12, 322 => 0x100a }, + 'focusrangeindex' => { 366 => '3.1' }, + 'focusresult' => { 194 => 0x4a }, + 'focusringrotation' => { 84 => 0x713 }, + 'focussetting' => { 417 => 0x6 }, + 'focusshiftautoreset' => { 241 => 0x6da, 242 => 0x748 }, + 'focusshiftexposurelock' => { 223 => 0x1b4, 238 => 0xe8, 239 => 0x100, 240 => 0xf4, 241 => 0x104, 242 => 0x104 }, + 'focusshiftinterval' => { 223 => 0x1b0, 238 => 0xe4, 239 => 0xfc, 240 => 0xf0, 241 => 0x100, 242 => 0x100 }, + 'focusshiftnumbershots' => { 223 => 0x1a8, 238 => 0xdc, 239 => 0xf4, 240 => 0xe8, 241 => 0xf8, 242 => 0xf8 }, + 'focusshiftshooting' => { 259 => 0x20 }, + 'focusshiftstepwidth' => { 223 => 0x1ac, 238 => 0xe0, 239 => 0xf8, 240 => 0xec, 241 => 0xfc, 242 => 0xfc }, + 'focusstatus' => { 426 => 0x19, 427 => 0x53, 428 => 0x53 }, + 'focusstepcount' => { 320 => 0x301, 322 => 0x100e }, + 'focusstepinfinity' => { 320 => 0x303, 322 => 0x103b }, + 'focusstepnear' => { 320 => 0x304, 322 => 0x103c }, + 'focusstepsfrominfinity' => { 232 => 0x58 }, + 'focustrackinglockon' => { 297 => ['1.5','4.1'], 298 => '1.4', 305 => '0.4', 306 => '3.1', 307 => '0.4', 310 => '1.4' }, + 'focuswarning' => { 127 => 0x1301 }, + 'foldername' => { 181 => 0x27 }, + 'foldernumber' => { 427 => 0x9a, 429 => [0x402,0x114,0x316] }, + 'fontcomposite' => { 534 => [\'Fonts','FontsComposite'] }, + 'fontface' => { 534 => [\'Fonts','FontsFontFace'] }, + 'fontfamily' => { 534 => [\'Fonts','FontsFontFamily'] }, + 'fontfilename' => { 534 => [\'Fonts','FontsFontFileName'] }, + 'fontname' => { 534 => [\'Fonts','FontsFontName'] }, + 'fonts' => { 534 => 'Fonts' }, + 'fonttype' => { 534 => [\'Fonts','FontsFontType'] }, + 'fontversion' => { 534 => [\'Fonts','FontsVersionString'] }, + 'for' => { 391 => 'For' }, + 'forcewrite' => { 120 => 'ForceWrite' }, + 'format' => { 400 => "\xa9fmt", 485 => 'Format', 503 => 'format', 527 => 'Format' }, + 'forwardlock' => { 524 => 'forwardlock' }, + 'forwardmatrix1' => { 119 => 0xc714 }, + 'forwardmatrix2' => { 119 => 0xc715 }, + 'forwardmatrix3' => { 119 => 0xcd34 }, + 'fossilspecimen' => { 118 => 'FossilSpecimen' }, + 'fossilspecimenmaterialsampleid' => { 118 => [\'FossilSpecimen','FossilSpecimenMaterialSampleID'] }, + 'framecount' => { 67 => [0x2,0x4] }, + 'frameheight' => { 127 => 0x3822 }, + 'framenum' => { 386 => 0xd7 }, + 'framenumber' => { 127 => 0x8003, 187 => 0x3c, 375 => 0x29 }, + 'framerate' => { 67 => [0x1,0x6], 119 => 0xc764, 127 => 0x3820 }, + 'framereadouttime' => { 394 => 'camera.framereadouttimeinmicroseconds' }, + 'framewidth' => { 127 => 0x3821 }, + 'framing' => { 518 => 'framing' }, + 'freebytes' => { 97 => 0x1 }, + 'freememorycardimages' => { 182 => [0x37,0x54], 183 => [0x2d,0x4a], 184 => 0x32 }, + 'frontfacingcamera' => { 1 => 0x45 }, + 'fujiflashmode' => { 127 => 0x1010 }, + 'fujimodel' => { 127 => 0x1447 }, + 'fujimodel2' => { 127 => 0x1448 }, + 'fullhdhighspeedrec' => { 127 => 0x3824 }, + 'fullimagesize' => { 440 => 0xb02b }, + 'fullpanoheightpixels' => { 488 => 'FullPanoHeightPixels', 489 => 'FullPanoHeightPixels' }, + 'fullpanowidthpixels' => { 488 => 'FullPanoWidthPixels', 489 => 'FullPanoWidthPixels' }, + 'fullpresssnap' => { 407 => 0x100d }, + 'fullsizeimage' => { 162 => 'data' }, + 'fullsizeimagename' => { 162 => '1Name' }, + 'fullsizeimagetype' => { 162 => '0Type' }, + 'func1button' => { 300 => '14.1', 301 => '14.1', 311 => '14.1', 313 => 0x63, 314 => 0x63, 315 => 0x63 }, + 'func1buttonplaybackmode' => { 313 => 0x1a5, 314 => 0x1a5, 315 => 0x1bd }, + 'func1buttonplusdials' => { 300 => '42.1', 301 => '42.1', 311 => '42.1' }, + 'func2button' => { 300 => '80.1', 301 => '80.1', 311 => '80.1', 313 => 0x73, 314 => 0x73, 315 => 0x73 }, + 'func2buttonplaybackmode' => { 313 => 0x1a7, 314 => 0x1a7, 315 => 0x1bf }, + 'func2buttonplusdials' => { 300 => '81.1' }, + 'func3button' => { 300 => '83.1', 314 => 0x119, 315 => 0x131 }, + 'func3buttonplaybackmode' => { 314 => 0x1a9, 315 => 0x1c1 }, + 'func4button' => { 314 => 0x175, 315 => 0x18d }, + 'func4buttonplaybackmode' => { 314 => 0x1af, 315 => 0x1c7 }, + 'funcbutton' => { 297 => ['14.1','15.1'], 298 => '14.1', 306 => '28.1', 307 => '13.1', 310 => '14.1', 312 => '14.1' }, + 'funcbuttonplusdials' => { 297 => ['14.2','15.2'], 298 => '14.2', 306 => '31.1', 310 => '42.1' }, + 'functionbutton' => { 304 => '13.1', 308 => '5.2' }, + 'gainbase' => { 321 => 0x610 }, + 'gaincontrol' => { 119 => 0xa407, 506 => 'GainControl' }, + 'gainmapmax' => { 511 => 'GainMapMax' }, + 'gainmapmin' => { 511 => 'GainMapMin' }, + 'gamma' => { 119 => 0xa500, 138 => 0x8fe, 328 => 'gAMA', 345 => 0x11c, 507 => 'Gamma', 511 => 'Gamma' }, + 'gammablackpoint' => { 105 => 0xc }, + 'gammacolortone' => { 105 => 0x3 }, + 'gammacompensatedvalue' => { 133 => 0x91 }, + 'gammacontrast' => { 105 => 0x2 }, + 'gammacurveoutputrange' => { 105 => 0xf }, + 'gammahighlight' => { 105 => 0xa }, + 'gammalinear' => { 103 => 0x20200 }, + 'gammamidpoint' => { 105 => 0xe }, + 'gammasaturation' => { 105 => 0x4 }, + 'gammashadow' => { 105 => 0x9 }, + 'gammasharpnessstrength' => { 105 => 0x8 }, + 'gammaunsharpmaskfineness' => { 105 => 0x6 }, + 'gammaunsharpmaskstrength' => { 105 => 0x5 }, + 'gammaunsharpmaskthreshold' => { 105 => 0x7 }, + 'gammawhitepoint' => { 105 => 0xd }, + 'garminsettings' => { 400 => 'pmcc' }, + 'garminsoftware' => { 400 => 'uuid' }, + 'gdalmetadata' => { 119 => 0xa480 }, + 'gdalnodata' => { 119 => 0xa481 }, + 'geimagesize' => { 127 => 0x1304 }, + 'gemake' => { 129 => 0x300 }, + 'gemodel' => { 129 => 0x207 }, + 'genre' => { 392 => ['gnre',"\xa9gen"], 394 => 'genre', 400 => ['gnre',"\xa9gen"], 514 => 'Genre', 519 => 'genre', 529 => 'genre' }, + 'genrecvid' => { 514 => [\'Genre','GenreCvId'] }, + 'genrecvtermid' => { 514 => [\'Genre','GenreCvTermId'] }, + 'genrecvtermname' => { 514 => [\'Genre','GenreCvTermName'] }, + 'genrecvtermrefinedabout' => { 514 => [\'Genre','GenreCvTermRefinedAbout'] }, + 'genreid' => { 392 => 'geID' }, + 'geography' => { 520 => 'geography' }, + 'geologicalcontext' => { 118 => 'GeologicalContext' }, + 'geologicalcontextbed' => { 118 => [\'GeologicalContext','GeologicalContextBed'] }, + 'geologicalcontextformation' => { 118 => [\'GeologicalContext','GeologicalContextFormation'] }, + 'geologicalcontextgroup' => { 118 => [\'GeologicalContext','GeologicalContextGroup'] }, + 'geologicalcontextid' => { 118 => [\'GeologicalContext','GeologicalContextGeologicalContextID'] }, + 'geologicalcontextmember' => { 118 => [\'GeologicalContext','GeologicalContextMember'] }, + 'geosync' => { 120 => 'Geosync' }, + 'geotag' => { 120 => 'Geotag' }, + 'geotiffasciiparams' => { 119 => 0x87b1 }, + 'geotiffdirectory' => { 119 => 0x87af }, + 'geotiffdoubleparams' => { 119 => 0x87b0 }, + 'geotime' => { 120 => 'Geotime' }, + 'giftftppriority' => { 490 => 'GIFTFtpPriority' }, + 'gimbalpitchdegree' => { 116 => 'GimbalPitchDegree' }, + 'gimbalreverse' => { 116 => 'GimbalReverse' }, + 'gimbalrolldegree' => { 116 => 'GimbalRollDegree' }, + 'gimbalyawdegree' => { 116 => 'GimbalYawDegree' }, + 'globalaltitude' => { 389 => 0x419 }, + 'globalangle' => { 389 => 0x40d }, + 'good' => { 529 => 'good' }, + 'googlehostheader' => { 392 => 'gshh' }, + 'googlepingmessage' => { 392 => 'gspm' }, + 'googlepingurl' => { 392 => 'gspu' }, + 'googleplusuploadcode' => { 119 => 0x9009 }, + 'googlesourcedata' => { 392 => 'gssd' }, + 'googlestarttime' => { 392 => 'gsst' }, + 'googletrackduration' => { 392 => 'gstd' }, + 'goprotype' => { 400 => 'GoPr' }, + 'gpsaltitude' => { 130 => 0x6, 160 => 'Altitude', 506 => 'GPSAltitude' }, + 'gpsaltituderef' => { 130 => 0x5, 506 => 'GPSAltitudeRef' }, + 'gpsareainformation' => { 130 => 0x1c, 506 => 'GPSAreaInformation' }, + 'gpscoordinates' => { 392 => "\xa9xyz", 394 => 'location.ISO6709', 400 => ['@xyz',"\xa9xyz"] }, + 'gpsdatestamp' => { 130 => 0x1d }, + 'gpsdatetime' => { 160 => 'DateTime', 506 => 'GPSTimeStamp' }, + 'gpsdestbearing' => { 130 => 0x18, 160 => 'Bearing', 506 => 'GPSDestBearing' }, + 'gpsdestbearingref' => { 130 => 0x17, 506 => 'GPSDestBearingRef' }, + 'gpsdestdistance' => { 130 => 0x1a, 160 => 'Distance', 506 => 'GPSDestDistance' }, + 'gpsdestdistanceref' => { 130 => 0x19, 506 => 'GPSDestDistanceRef' }, + 'gpsdestlatitude' => { 130 => 0x14, 506 => 'GPSDestLatitude' }, + 'gpsdestlatituderef' => { 130 => 0x13 }, + 'gpsdestlongitude' => { 130 => 0x16, 506 => 'GPSDestLongitude' }, + 'gpsdestlongituderef' => { 130 => 0x15 }, + 'gpsdifferential' => { 130 => 0x1e, 160 => 'Differential', 506 => 'GPSDifferential' }, + 'gpsdop' => { 130 => 0xb, 506 => 'GPSDOP' }, + 'gpshpositioningerror' => { 130 => 0x1f, 506 => 'GPSHPositioningError' }, + 'gpsimgdirection' => { 130 => 0x11, 506 => 'GPSImgDirection' }, + 'gpsimgdirectionref' => { 130 => 0x10, 506 => 'GPSImgDirectionRef' }, + 'gpslatitude' => { 114 => 'GPS-GPSLatitude', 116 => 'GpsLatitude', 130 => 0x2, 160 => 'Latitude', 506 => 'GPSLatitude' }, + 'gpslatituderef' => { 130 => 0x1 }, + 'gpslongitude' => { 114 => 'GPS-GPSLongitude', 116 => 'GpsLongitude', 130 => 0x4, 160 => 'Longitude', 506 => 'GPSLongitude' }, + 'gpslongituderef' => { 130 => 0x3 }, + 'gpslongtitude' => { 116 => 'GpsLongtitude' }, + 'gpsmapdatum' => { 130 => 0x12, 160 => 'Datum', 506 => 'GPSMapDatum' }, + 'gpsmeasuremode' => { 130 => 0xa, 160 => 'MeasureMode', 506 => 'GPSMeasureMode' }, + 'gpsposition' => { 114 => 'Exif-GPSPosition' }, + 'gpsprocessingmethod' => { 130 => 0x1b, 506 => 'GPSProcessingMethod' }, + 'gpssatellites' => { 130 => 0x8, 160 => 'Satellites', 506 => 'GPSSatellites' }, + 'gpsspeed' => { 130 => 0xd, 160 => 'Speed', 506 => 'GPSSpeed' }, + 'gpsspeedref' => { 130 => 0xc, 506 => 'GPSSpeedRef' }, + 'gpsstatus' => { 130 => 0x9, 506 => 'GPSStatus' }, + 'gpsstring' => { 138 => 0x402 }, + 'gpstimestamp' => { 130 => 0x7 }, + 'gpstrack' => { 130 => 0xf, 160 => 'Heading', 506 => 'GPSTrack' }, + 'gpstrackref' => { 130 => 0xe, 506 => 'GPSTrackRef' }, + 'gpsversionid' => { 130 => 0x0, 506 => 'GPSVersionID' }, + 'gradation' => { 317 => 0x50f }, + 'gradientbasedcorractive' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionActive'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionActive'] }, + 'gradientbasedcorramount' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionAmount'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionAmount'] }, + 'gradientbasedcorrblacks2012' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalBlacks2012'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalBlacks2012'] }, + 'gradientbasedcorrbrightness' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalBrightness'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalBrightness'] }, + 'gradientbasedcorrclarity' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalClarity'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalClarity'] }, + 'gradientbasedcorrclarity2012' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalClarity2012'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalClarity2012'] }, + 'gradientbasedcorrcontrast' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalContrast'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalContrast'] }, + 'gradientbasedcorrcontrast2012' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalContrast2012'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalContrast2012'] }, + 'gradientbasedcorrcorrectionname' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionName'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionName'] }, + 'gradientbasedcorrcorrectionsyncid' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionSyncID'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionSyncID'] }, + 'gradientbasedcorrdefringe' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalDefringe'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalDefringe'] }, + 'gradientbasedcorrdehaze' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalDehaze'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalDehaze'] }, + 'gradientbasedcorrections' => { 500 => 'GradientBasedCorrections', 502 => 'GradientBasedCorrections' }, + 'gradientbasedcorrexposure' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalExposure'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalExposure'] }, + 'gradientbasedcorrexposure2012' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalExposure2012'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalExposure2012'] }, + 'gradientbasedcorrhighlights2012' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalHighlights2012'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalHighlights2012'] }, + 'gradientbasedcorrhue' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalHue'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalHue'] }, + 'gradientbasedcorrluminancenoise' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalLuminanceNoise'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalLuminanceNoise'] }, + 'gradientbasedcorrmaskalpha' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksAlpha'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksAlpha'] }, + 'gradientbasedcorrmaskangle' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksAngle'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksAngle'] }, + 'gradientbasedcorrmaskbottom' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksBottom'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksBottom'] }, + 'gradientbasedcorrmaskcentervalue' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCenterValue'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCenterValue'] }, + 'gradientbasedcorrmaskcenterweight' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCenterWeight'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCenterWeight'] }, + 'gradientbasedcorrmaskdabs' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksDabs'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksDabs'] }, + 'gradientbasedcorrmaskfeather' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksFeather'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksFeather'] }, + 'gradientbasedcorrmaskflipped' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksFlipped'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksFlipped'] }, + 'gradientbasedcorrmaskflow' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksFlow'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksFlow'] }, + 'gradientbasedcorrmaskfullx' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksFullX'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksFullX'] }, + 'gradientbasedcorrmaskfully' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksFullY'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksFullY'] }, + 'gradientbasedcorrmaskinputdigest' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksInputDigest'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksInputDigest'] }, + 'gradientbasedcorrmaskleft' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksLeft'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksLeft'] }, + 'gradientbasedcorrmaskmaskactive' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskActive'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskActive'] }, + 'gradientbasedcorrmaskmaskblendmode' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskBlendMode'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskBlendMode'] }, + 'gradientbasedcorrmaskmaskdigest' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskDigest'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskDigest'] }, + 'gradientbasedcorrmaskmaskinverted' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskInverted'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskInverted'] }, + 'gradientbasedcorrmaskmaskname' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskName'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskName'] }, + 'gradientbasedcorrmaskmasks' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasks'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasks'] }, + 'gradientbasedcorrmaskmasksalpha' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksAlpha'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksAlpha'] }, + 'gradientbasedcorrmaskmasksangle' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksAngle'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksAngle'] }, + 'gradientbasedcorrmaskmasksbottom' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksBottom'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksBottom'] }, + 'gradientbasedcorrmaskmaskscentervalue' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksCenterValue'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksCenterValue'] }, + 'gradientbasedcorrmaskmaskscenterweight' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksCenterWeight'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksCenterWeight'] }, + 'gradientbasedcorrmaskmasksdabs' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksDabs'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksDabs'] }, + 'gradientbasedcorrmaskmasksfeather' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksFeather'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksFeather'] }, + 'gradientbasedcorrmaskmasksflipped' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksFlipped'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksFlipped'] }, + 'gradientbasedcorrmaskmasksflow' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksFlow'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksFlow'] }, + 'gradientbasedcorrmaskmasksfullx' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksFullX'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksFullX'] }, + 'gradientbasedcorrmaskmasksfully' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksFullY'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksFullY'] }, + 'gradientbasedcorrmaskmasksinputdigest' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksInputDigest'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksInputDigest'] }, + 'gradientbasedcorrmaskmasksleft' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksLeft'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksLeft'] }, + 'gradientbasedcorrmaskmasksmaskactive' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskActive'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskActive'] }, + 'gradientbasedcorrmaskmasksmaskblendmode' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskBlendMode'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskBlendMode'] }, + 'gradientbasedcorrmaskmasksmaskdigest' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskDigest'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskDigest'] }, + 'gradientbasedcorrmaskmasksmaskinverted' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskInverted'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskInverted'] }, + 'gradientbasedcorrmaskmasksmaskname' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskName'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskName'] }, + 'gradientbasedcorrmaskmasksmasksubtype' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskSubType'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskSubType'] }, + 'gradientbasedcorrmaskmasksmasksyncid' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskSyncID'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskSyncID'] }, + 'gradientbasedcorrmaskmasksmaskversion' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskVersion'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskVersion'] }, + 'gradientbasedcorrmaskmasksmidpoint' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMidpoint'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMidpoint'] }, + 'gradientbasedcorrmaskmasksorigin' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksOrigin'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksOrigin'] }, + 'gradientbasedcorrmaskmasksperimetervalue' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksPerimeterValue'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksPerimeterValue'] }, + 'gradientbasedcorrmaskmasksradius' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksRadius'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksRadius'] }, + 'gradientbasedcorrmaskmasksreferencepoint' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksReferencePoint'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksReferencePoint'] }, + 'gradientbasedcorrmaskmasksright' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksRight'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksRight'] }, + 'gradientbasedcorrmaskmasksroundness' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksRoundness'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksRoundness'] }, + 'gradientbasedcorrmaskmaskssizex' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksSizeX'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksSizeX'] }, + 'gradientbasedcorrmaskmaskssizey' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksSizeY'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksSizeY'] }, + 'gradientbasedcorrmaskmaskstop' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksTop'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksTop'] }, + 'gradientbasedcorrmaskmasksubtype' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskSubType'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskSubType'] }, + 'gradientbasedcorrmaskmasksvalue' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskValue'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksMaskValue'] }, + 'gradientbasedcorrmaskmasksversion' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksVersion'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksVersion'] }, + 'gradientbasedcorrmaskmaskswhat' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksWhat'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksWhat'] }, + 'gradientbasedcorrmaskmaskswholeimagearea' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksWholeImageArea'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksWholeImageArea'] }, + 'gradientbasedcorrmaskmasksx' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksX'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksX'] }, + 'gradientbasedcorrmaskmasksy' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksY'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksY'] }, + 'gradientbasedcorrmaskmasksyncid' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskSyncID'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskSyncID'] }, + 'gradientbasedcorrmaskmaskszerox' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksZeroX'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksZeroX'] }, + 'gradientbasedcorrmaskmaskszeroy' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksZeroY'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMasksZeroY'] }, + 'gradientbasedcorrmaskmaskversion' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskVersion'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskVersion'] }, + 'gradientbasedcorrmaskmidpoint' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMidpoint'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMidpoint'] }, + 'gradientbasedcorrmaskorigin' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksOrigin'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksOrigin'] }, + 'gradientbasedcorrmaskperimetervalue' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksPerimeterValue'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksPerimeterValue'] }, + 'gradientbasedcorrmaskradius' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksRadius'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksRadius'] }, + 'gradientbasedcorrmaskrange' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMask'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMask'] }, + 'gradientbasedcorrmaskrangeareamodels' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels'] }, + 'gradientbasedcorrmaskrangeareamodelscolorsampleinfo' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'] }, + 'gradientbasedcorrmaskrangeareamodelscomponents' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents'] }, + 'gradientbasedcorrmaskrangecoloramount' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount'] }, + 'gradientbasedcorrmaskrangedepthfeather' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather'] }, + 'gradientbasedcorrmaskrangedepthmax' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax'] }, + 'gradientbasedcorrmaskrangedepthmin' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin'] }, + 'gradientbasedcorrmaskrangeinvert' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert'] }, + 'gradientbasedcorrmaskrangelumfeather' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather'] }, + 'gradientbasedcorrmaskrangeluminancedepthsampleinfo' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo'] }, + 'gradientbasedcorrmaskrangelummax' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax'] }, + 'gradientbasedcorrmaskrangelummin' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin'] }, + 'gradientbasedcorrmaskrangelumrange' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange'] }, + 'gradientbasedcorrmaskrangesampletype' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType'] }, + 'gradientbasedcorrmaskrangetype' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskType'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskType'] }, + 'gradientbasedcorrmaskrangeversion' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion'] }, + 'gradientbasedcorrmaskreferencepoint' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksReferencePoint'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksReferencePoint'] }, + 'gradientbasedcorrmaskright' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksRight'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksRight'] }, + 'gradientbasedcorrmaskroundness' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksRoundness'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksRoundness'] }, + 'gradientbasedcorrmasks' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasks'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasks'] }, + 'gradientbasedcorrmasksizex' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksSizeX'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksSizeX'] }, + 'gradientbasedcorrmasksizey' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksSizeY'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksSizeY'] }, + 'gradientbasedcorrmasktop' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksTop'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksTop'] }, + 'gradientbasedcorrmaskvalue' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskValue'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksMaskValue'] }, + 'gradientbasedcorrmaskversion' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksVersion'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksVersion'] }, + 'gradientbasedcorrmaskwhat' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksWhat'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksWhat'] }, + 'gradientbasedcorrmaskwholeimagearea' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksWholeImageArea'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksWholeImageArea'] }, + 'gradientbasedcorrmaskx' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksX'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksX'] }, + 'gradientbasedcorrmasky' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksY'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksY'] }, + 'gradientbasedcorrmaskzerox' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksZeroX'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksZeroX'] }, + 'gradientbasedcorrmaskzeroy' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksZeroY'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionMasksZeroY'] }, + 'gradientbasedcorrmoire' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalMoire'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalMoire'] }, + 'gradientbasedcorrrangemask' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMask'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMask'] }, + 'gradientbasedcorrrangemaskareamodels' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskAreaModels'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskAreaModels'] }, + 'gradientbasedcorrrangemaskareamodelscolorsampleinfo' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'] }, + 'gradientbasedcorrrangemaskareamodelscomponents' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents'] }, + 'gradientbasedcorrrangemaskcoloramount' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskColorAmount'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskColorAmount'] }, + 'gradientbasedcorrrangemaskdepthfeather' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskDepthFeather'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskDepthFeather'] }, + 'gradientbasedcorrrangemaskdepthmax' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskDepthMax'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskDepthMax'] }, + 'gradientbasedcorrrangemaskdepthmin' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskDepthMin'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskDepthMin'] }, + 'gradientbasedcorrrangemaskinvert' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskInvert'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskInvert'] }, + 'gradientbasedcorrrangemasklumfeather' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskLumFeather'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskLumFeather'] }, + 'gradientbasedcorrrangemaskluminancedepthsampleinfo' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo'] }, + 'gradientbasedcorrrangemasklummax' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskLumMax'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskLumMax'] }, + 'gradientbasedcorrrangemasklummin' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskLumMin'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskLumMin'] }, + 'gradientbasedcorrrangemasklumrange' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskLumRange'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskLumRange'] }, + 'gradientbasedcorrrangemasksampletype' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskSampleType'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskSampleType'] }, + 'gradientbasedcorrrangemasktype' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskType'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskType'] }, + 'gradientbasedcorrrangemaskversion' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskVersion'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsCorrectionRangeMaskVersion'] }, + 'gradientbasedcorrsaturation' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalSaturation'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalSaturation'] }, + 'gradientbasedcorrshadows2012' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalShadows2012'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalShadows2012'] }, + 'gradientbasedcorrsharpness' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalSharpness'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalSharpness'] }, + 'gradientbasedcorrtemperature' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalTemperature'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalTemperature'] }, + 'gradientbasedcorrtexture' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalTexture'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalTexture'] }, + 'gradientbasedcorrtint' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalTint'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalTint'] }, + 'gradientbasedcorrtoninghue' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalToningHue'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalToningHue'] }, + 'gradientbasedcorrtoningsaturation' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalToningSaturation'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalToningSaturation'] }, + 'gradientbasedcorrwhat' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsWhat'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsWhat'] }, + 'gradientbasedcorrwhites2012' => { 500 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalWhites2012'], 502 => [\'GradientBasedCorrections','GradientBasedCorrectionsLocalWhites2012'] }, + 'grainamount' => { 500 => 'GrainAmount', 502 => 'GrainAmount' }, + 'graineffectroughness' => { 127 => 0x1047 }, + 'graineffectsize' => { 127 => 0x104c }, + 'grainfrequency' => { 500 => 'GrainFrequency', 502 => 'GrainFrequency' }, + 'grainseed' => { 500 => 'GrainSeed', 502 => 'GrainSeed' }, + 'grainsize' => { 500 => 'GrainSize', 502 => 'GrainSize' }, + 'graymixeraqua' => { 500 => 'GrayMixerAqua', 502 => 'GrayMixerAqua' }, + 'graymixerblue' => { 500 => 'GrayMixerBlue', 502 => 'GrayMixerBlue' }, + 'graymixergreen' => { 500 => 'GrayMixerGreen', 502 => 'GrayMixerGreen' }, + 'graymixermagenta' => { 500 => 'GrayMixerMagenta', 502 => 'GrayMixerMagenta' }, + 'graymixerorange' => { 500 => 'GrayMixerOrange', 502 => 'GrayMixerOrange' }, + 'graymixerpurple' => { 500 => 'GrayMixerPurple', 502 => 'GrayMixerPurple' }, + 'graymixerred' => { 500 => 'GrayMixerRed', 502 => 'GrayMixerRed' }, + 'graymixeryellow' => { 500 => 'GrayMixerYellow', 502 => 'GrayMixerYellow' }, + 'graypoint' => { 476 => 0x8021 }, + 'grayresponseunit' => { 119 => 0x122 }, + 'greencurvelimits' => { 108 => 0x1c4 }, + 'greencurvepoints' => { 107 => 0x53, 108 => 0x19a }, + 'greenghostmitigationstatus' => { 1 => 0x3f }, + 'greenhsl' => { 103 => 0x20913 }, + 'greenhue' => { 500 => 'GreenHue', 502 => 'GreenHue' }, + 'greensaturation' => { 500 => 'GreenSaturation', 502 => 'GreenSaturation' }, + 'griddisplay' => { 297 => '13.3', 298 => '4.3', 300 => '4.2', 301 => '4.2', 302 => '2.2', 306 => '10.5', 307 => '3.4', 308 => '6.1', 310 => '4.4', 311 => '4.2', 312 => '4.2' }, + 'gripbatteryadload' => { 354 => 0x5 }, + 'gripbatteryadnoload' => { 354 => 0x4 }, + 'gripbatterystate' => { 354 => '1.2' }, + 'group' => { 500 => 'Group', 502 => 'Group' }, + 'groupareaafillumination' => { 298 => '46.4', 301 => '47.4', 310 => '47.4' }, + 'grouping' => { 392 => ['grup',"\xa9grp"], 400 => "\xa9grp" }, + 'guid' => { 392 => 'GUID' }, + 'h2resetblackpixels' => { 138 => 0x18a6 }, + 'h3resetblackcolumns' => { 138 => 0x18ce }, + 'h3resetblackpixels' => { 138 => 0x18b0 }, + 'halftonehints' => { 119 => 0x141 }, + 'hardlink' => { 120 => 'HardLink' }, + 'hasalternative' => { 519 => 'hasAlternative' }, + 'hascorrection' => { 519 => 'hasCorrection' }, + 'hascorrectiona-lang' => { 519 => [\'hasCorrection','hasCorrectionA-lang'] }, + 'hascorrectiona-platform' => { 519 => [\'hasCorrection','hasCorrectionA-platform'] }, + 'hascorrectiontext' => { 519 => [\'hasCorrection','hasCorrectionText'] }, + 'hascrop' => { 500 => 'HasCrop', 502 => 'HasCrop' }, + 'hasextendedxmp' => { 531 => 'HasExtendedXMP' }, + 'hassettings' => { 500 => 'HasSettings', 502 => 'HasSettings' }, + 'hastranslation' => { 519 => 'hasTranslation' }, + 'hasvisibleoverprint' => { 534 => 'HasVisibleOverprint' }, + 'hasvisibletransparency' => { 534 => 'HasVisibleTransparency' }, + 'hdmioutputn-log' => { 238 => 0x35a }, + 'hdmioutputresolution' => { 239 => 0x710, 240 => 0x610, 241 => 0x640, 242 => 0x6a8 }, + 'hdmiviewassist' => { 314 => 0x20f, 315 => 0x227 }, + 'hdr' => { 60 => 0x1, 219 => 0x4, 220 => 0x4, 239 => 0x23a, 242 => 0x23a, 340 => 0x9e, 375 => 0x85, 440 => 0x200a }, + 'hdrcapacitymax' => { 511 => 'HDRCapacityMax' }, + 'hdrcapacitymin' => { 511 => 'HDRCapacityMin' }, + 'hdreditmode' => { 500 => 'HDREditMode', 502 => 'HDREditMode' }, + 'hdreffect' => { 60 => 0x2 }, + 'hdrgain' => { 1 => 0x30 }, + 'hdrheadroom' => { 1 => 0x21 }, + 'hdrimagetype' => { 1 => 0xa }, + 'hdrlevel' => { 219 => 0x5, 220 => 0x5, 239 => 0x246, 242 => 0x246, 429 => 0x2e, 445 => 0x17 }, + 'hdrlevel2' => { 219 => 0x7 }, + 'hdrpmakernote' => { 483 => 'hdrp_makernote' }, + 'hdrsetting' => { 429 => 0x2d, 445 => 0x16, 449 => 0x1148, 450 => 0x1148, 451 => 0x1124, 452 => 0x11a0, 453 => 0x117c, 454 => 0x1034, 455 => 0x22c, 456 => 0x22c, 457 => 0x21f }, + 'hdrshot' => { 340 => 0x76 }, + 'hdrsmoothing' => { 219 => 0x6 }, + 'hdvideo' => { 392 => 'hdvd' }, + 'headline' => { 131 => 0x69, 514 => 'Headline', 517 => 'Headline' }, + 'hierarchicalkeywords' => { 171 => [\'Keywords','KeywordsHierarchy'] }, + 'hierarchicalkeywords1' => { 171 => [\'Keywords','KeywordsHierarchyKeyword'] }, + 'hierarchicalkeywords1applied' => { 171 => [\'Keywords','KeywordsHierarchyApplied'] }, + 'hierarchicalkeywords1children' => { 171 => [\'Keywords','KeywordsHierarchyChildren'] }, + 'hierarchicalkeywords2' => { 171 => [\'Keywords','KeywordsHierarchyChildrenKeyword'] }, + 'hierarchicalkeywords2applied' => { 171 => [\'Keywords','KeywordsHierarchyChildrenApplied'] }, + 'hierarchicalkeywords2children' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildren'] }, + 'hierarchicalkeywords3' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenKeyword'] }, + 'hierarchicalkeywords3applied' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenApplied'] }, + 'hierarchicalkeywords3children' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenChildren'] }, + 'hierarchicalkeywords4' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenChildrenKeyword'] }, + 'hierarchicalkeywords4applied' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenChildrenApplied'] }, + 'hierarchicalkeywords4children' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenChildrenChildren'] }, + 'hierarchicalkeywords5' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenChildrenChildrenKeyword'] }, + 'hierarchicalkeywords5applied' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenChildrenChildrenApplied'] }, + 'hierarchicalkeywords5children' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenChildrenChildrenChildren'] }, + 'hierarchicalkeywords6' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenChildrenChildrenChildrenKeyword'] }, + 'hierarchicalkeywords6applied' => { 171 => [\'Keywords','KeywordsHierarchyChildrenChildrenChildrenChildrenChildrenApplied'] }, + 'hierarchicalsubject' => { 492 => 'hierarchicalSubject' }, + 'highestbiostratigraphiczone' => { 118 => [\'GeologicalContext','GeologicalContextHighestBiostratigraphicZone'] }, + 'highframerate' => { 239 => 0x48, 241 => 0x48, 242 => 0x48 }, + 'highfrequencyflickerreductionshooting' => { 239 => 0x27c, 241 => 0x27c, 242 => 0x27c }, + 'highisomultiplierblue' => { 345 => 0x1a }, + 'highisomultipliergreen' => { 345 => 0x19 }, + 'highisomultiplierred' => { 345 => 0x18 }, + 'highisonoisereduction' => { 16 => 0xbc, 17 => 0xbd, 20 => 0xbd, 28 => 0xc9, 62 => 0x5, 84 => 0x202, 234 => 0xb1, 375 => 0x71, 427 => 0x2c, 428 => 0x26, 429 => 0x26, 440 => 0x2009, 445 => 0x12, 472 => 0x42 }, + 'highisonoisereduction2' => { 440 => 0xb050 }, + 'highlight' => { 417 => 0xf }, + 'highlight2012' => { 500 => 'Highlight2012', 502 => 'Highlight2012' }, + 'highlightadj' => { 103 => 0x2030c }, + 'highlightcolordistortreduct' => { 476 => 0x8026 }, + 'highlightlinearitylimit' => { 414 => 0xa025 }, + 'highlightprotection' => { 289 => 0x6 }, + 'highlightrecovery' => { 500 => 'HighlightRecovery', 502 => 'HighlightRecovery' }, + 'highlights' => { 440 => 0x2033, 494 => 'Highlights' }, + 'highlights2012' => { 500 => 'Highlights2012', 502 => 'Highlights2012' }, + 'highlightsadj' => { 476 => 0x9019 }, + 'highlightshadow' => { 340 => 0xad }, + 'highlighttone' => { 127 => 0x1041 }, + 'highlighttonepriority' => { 13 => 0x7, 16 => 0x7, 17 => 0x7, 18 => 0x7, 20 => 0x7, 22 => 0x7, 28 => 0x7, 62 => 0x3, 84 => 0x203 }, + 'highlightwarning' => { 340 => 0x8002 }, + 'highlowkeyadj' => { 375 => 0x6c }, + 'highspeedsync' => { 184 => 0x5, 313 => 0x55, 314 => 0x55, 315 => 0x55, 427 => 0x2, 428 => 0x2 }, + 'hintversion' => { 400 => 'hinv' }, + 'histogramxml' => { 290 => 0x83a1a25 }, + 'history' => { 517 => 'History', 530 => 'History' }, + 'historyaction' => { 530 => [\'History','HistoryAction'] }, + 'historychanged' => { 530 => [\'History','HistoryChanged'] }, + 'historyinstanceid' => { 530 => [\'History','HistoryInstanceID'] }, + 'historyparameters' => { 530 => [\'History','HistoryParameters'] }, + 'historysoftwareagent' => { 530 => [\'History','HistorySoftwareAgent'] }, + 'historywhen' => { 530 => [\'History','HistoryWhen'] }, + 'holefilldarkdeltathreshold' => { 138 => 0xc88 }, + 'holefilldeltathreshold' => { 138 => 0xc7e }, + 'hometowncity' => { 113 => 0x3006, 375 => 0x23, 381 => 0x2 }, + 'hometowncitycode' => { 382 => 0x1000 }, + 'hometowndst' => { 375 => 0x25, 381 => '0.2' }, + 'hostcomputer' => { 119 => 0x13c }, + 'hostsoftwarerendering' => { 138 => 0xce7 }, + 'hue' => { 189 => 0x3b, 250 => 0x3d, 251 => 0x45, 375 => 0x67 }, + 'hueadj' => { 293 => 0x2f, 476 => 0x8019 }, + 'hueadjust' => { 407 => 0x1016 }, + 'hueadjustment' => { 182 => 0x4a, 183 => 0x40, 234 => 0x92, 249 => 0x36 }, + 'hueadjustmentaqua' => { 500 => 'HueAdjustmentAqua', 502 => 'HueAdjustmentAqua' }, + 'hueadjustmentblue' => { 500 => 'HueAdjustmentBlue', 502 => 'HueAdjustmentBlue' }, + 'hueadjustmentgreen' => { 500 => 'HueAdjustmentGreen', 502 => 'HueAdjustmentGreen' }, + 'hueadjustmentmagenta' => { 500 => 'HueAdjustmentMagenta', 502 => 'HueAdjustmentMagenta' }, + 'hueadjustmentorange' => { 500 => 'HueAdjustmentOrange', 502 => 'HueAdjustmentOrange' }, + 'hueadjustmentpurple' => { 500 => 'HueAdjustmentPurple', 502 => 'HueAdjustmentPurple' }, + 'hueadjustmentred' => { 500 => 'HueAdjustmentRed', 502 => 'HueAdjustmentRed' }, + 'hueadjustmentyellow' => { 500 => 'HueAdjustmentYellow', 502 => 'HueAdjustmentYellow' }, + 'huesetting' => { 325 => 0x1011 }, + 'humanobservation' => { 118 => 'HumanObservation' }, + 'humanobservationday' => { 118 => [\'HumanObservation','HumanObservationDay'] }, + 'humanobservationearliestdate' => { 118 => [\'HumanObservation','HumanObservationEarliestDate'] }, + 'humanobservationenddayofyear' => { 118 => [\'HumanObservation','HumanObservationEndDayOfYear'] }, + 'humanobservationeventdate' => { 118 => [\'HumanObservation','HumanObservationEventDate'] }, + 'humanobservationeventid' => { 118 => [\'HumanObservation','HumanObservationEventID'] }, + 'humanobservationeventremarks' => { 118 => [\'HumanObservation','HumanObservationEventRemarks'] }, + 'humanobservationeventtime' => { 118 => [\'HumanObservation','HumanObservationEventTime'] }, + 'humanobservationfieldnotes' => { 118 => [\'HumanObservation','HumanObservationFieldNotes'] }, + 'humanobservationfieldnumber' => { 118 => [\'HumanObservation','HumanObservationFieldNumber'] }, + 'humanobservationhabitat' => { 118 => [\'HumanObservation','HumanObservationHabitat'] }, + 'humanobservationlatestdate' => { 118 => [\'HumanObservation','HumanObservationLatestDate'] }, + 'humanobservationmonth' => { 118 => [\'HumanObservation','HumanObservationMonth'] }, + 'humanobservationparenteventid' => { 118 => [\'HumanObservation','HumanObservationParentEventID'] }, + 'humanobservationsamplesizeunit' => { 118 => [\'HumanObservation','HumanObservationSampleSizeUnit'] }, + 'humanobservationsamplesizevalue' => { 118 => [\'HumanObservation','HumanObservationSampleSizeValue'] }, + 'humanobservationsamplingeffort' => { 118 => [\'HumanObservation','HumanObservationSamplingEffort'] }, + 'humanobservationsamplingprotocol' => { 118 => [\'HumanObservation','HumanObservationSamplingProtocol'] }, + 'humanobservationstartdayofyear' => { 118 => [\'HumanObservation','HumanObservationStartDayOfYear'] }, + 'humanobservationverbatimeventdate' => { 118 => [\'HumanObservation','HumanObservationVerbatimEventDate'] }, + 'humanobservationyear' => { 118 => [\'HumanObservation','HumanObservationYear'] }, + 'humidity' => { 119 => 0x9401, 507 => 'Humidity' }, + 'icc_profile' => { 120 => 'ICC_Profile' }, + 'iccprofilename' => { 517 => 'ICCProfile' }, + 'iconuri' => { 400 => 'icnu' }, + 'idccreativestyle' => { 476 => 0x8000 }, + 'idcpreviewlength' => { 476 => 0x202 }, + 'idcpreviewstart' => { 476 => 0x201 }, + 'identification' => { 118 => 'Identification' }, + 'identificationid' => { 118 => [\'Identification','IdentificationIdentificationID'] }, + 'identificationqualifier' => { 118 => [\'Identification','IdentificationIdentificationQualifier'] }, + 'identificationreferences' => { 118 => [\'Identification','IdentificationIdentificationReferences'] }, + 'identificationremarks' => { 118 => [\'Identification','IdentificationIdentificationRemarks'] }, + 'identificationverificationstatus' => { 118 => [\'Identification','IdentificationIdentificationVerificationStatus'] }, + 'identifiedby' => { 118 => [\'Identification','IdentificationIdentifiedBy'] }, + 'identifiedbyid' => { 118 => [\'Identification','IdentificationIdentifiedByID'] }, + 'identifier' => { 503 => 'identifier', 527 => 'Identifier' }, + 'ifcameramodel' => { 138 => 0x9c8 }, + 'illuminantdata1' => { 119 => 0xcd35 }, + 'illuminantdata2' => { 119 => 0xcd36 }, + 'illuminantdata3' => { 119 => 0xcd37 }, + 'illumination' => { 308 => '0.5', 402 => 0x48 }, + 'imageabsolutex' => { 138 => 0x3fe }, + 'imageabsolutey' => { 138 => 0x3ff }, + 'imageadjustment' => { 234 => 0x80, 280 => 0x5 }, + 'imagealterationconstraints' => { 327 => 'ImageAlterationConstraints' }, + 'imagearea' => { 266 => 0x10, 269 => 0x10 }, + 'imageareaoffset' => { 375 => 0x38 }, + 'imageauthentication' => { 234 => 0x20 }, + 'imageboundary' => { 234 => 0x16 }, + 'imagecapturerequestid' => { 1 => 0x20 }, + 'imagecapturetype' => { 1 => 0x14 }, + 'imagecount' => { 127 => 0x1438, 234 => 0xa5, 443 => 0x11b }, + 'imagecreator' => { 327 => 'ImageCreator' }, + 'imagecreatorid' => { 327 => [\'ImageCreator','ImageCreatorImageCreatorID'] }, + 'imagecreatorimageid' => { 327 => 'ImageCreatorImageID' }, + 'imagecreatorname' => { 327 => [\'ImageCreator','ImageCreatorImageCreatorName'] }, + 'imagecropx' => { 138 => 0x41f }, + 'imagecropy' => { 138 => 0x420 }, + 'imagedata' => { 487 => 'Data' }, + 'imagedatasize' => { 234 => 0xa2 }, + 'imagedescription' => { 119 => 0x10e, 525 => 'ImageDescription' }, + 'imageduplicationconstraints' => { 327 => 'ImageDuplicationConstraints' }, + 'imagedustoff' => { 290 => 0xfe443a45 }, + 'imageeditcount' => { 375 => 0x41 }, + 'imageediting' => { 375 => 0x32 }, + 'imageeditingsoftware' => { 119 => 0xa43b }, + 'imageeditor' => { 119 => 0xa438 }, + 'imageeffects' => { 407 => 0x1010 }, + 'imagefileconstraints' => { 327 => 'ImageFileConstraints' }, + 'imagefileformatasdelivered' => { 327 => 'ImageFileFormatAsDelivered' }, + 'imagefilesizeasdelivered' => { 327 => 'ImageFileSizeAsDelivered' }, + 'imagegeneration' => { 127 => 0x1436 }, + 'imageheight' => { 119 => 0x101, 188 => 0xc, 384 => 0x10d, 485 => 'ImageHeight', 525 => 'ImageLength' }, + 'imagehistory' => { 119 => 0x9213, 505 => 'ImageHistory' }, + 'imageidnumber' => { 335 => 0x340 }, + 'imagemimetype' => { 487 => 'Mime' }, + 'imagenumber' => { 119 => 0x9211, 156 => 'ImageNumber', 182 => 0xae, 183 => 0x5e, 384 => 0x113, 427 => 0x9b, 429 => [0x400,'276.1',0x314], 497 => 'ImageNumber' }, + 'imagenumber2' => { 183 => 0x62 }, + 'imageoptimization' => { 234 => 0xa9 }, + 'imageorientation' => { 131 => 0x83 }, + 'imageprocessing' => { 234 => 0x1a }, + 'imageprocessingfiledatecreated' => { 138 => 0xc81 }, + 'imageprocessingfiletagsversionnumber' => { 138 => 0xc80 }, + 'imageprocessingflags' => { 1 => 0x19 }, + 'imageprocessingversion' => { 321 => 0x0 }, + 'imagequality' => { 156 => 'ImageQuality', 268 => '723.2', 269 => '732.2', 277 => '708.1', 340 => 0x1 }, + 'imagequality2' => { 317 => 0x603 }, + 'imagerank' => { 490 => 'ImageRank' }, + 'imagerbiassettlingdelaymsec' => { 138 => 0x600 }, + 'imagerboardversion' => { 138 => 0x439 }, + 'imagercols' => { 138 => 0x17d4 }, + 'imageref' => { 512 => 'ImageRef' }, + 'imageregion' => { 514 => 'ImageRegion' }, + 'imageregionboundary' => { 514 => [\'ImageRegion','ImageRegionRegionBoundary'] }, + 'imageregionboundaryh' => { 514 => [\'ImageRegion','ImageRegionRegionBoundaryRbH'] }, + 'imageregionboundaryrx' => { 514 => [\'ImageRegion','ImageRegionRegionBoundaryRbRx'] }, + 'imageregionboundaryshape' => { 514 => [\'ImageRegion','ImageRegionRegionBoundaryRbShape'] }, + 'imageregionboundaryunit' => { 514 => [\'ImageRegion','ImageRegionRegionBoundaryRbUnit'] }, + 'imageregionboundaryvertices' => { 514 => [\'ImageRegion','ImageRegionRegionBoundaryRbVertices'] }, + 'imageregionboundaryverticesx' => { 514 => [\'ImageRegion','ImageRegionRegionBoundaryRbVerticesRbX'] }, + 'imageregionboundaryverticesy' => { 514 => [\'ImageRegion','ImageRegionRegionBoundaryRbVerticesRbY'] }, + 'imageregionboundaryw' => { 514 => [\'ImageRegion','ImageRegionRegionBoundaryRbW'] }, + 'imageregionboundaryx' => { 514 => [\'ImageRegion','ImageRegionRegionBoundaryRbX'] }, + 'imageregionboundaryy' => { 514 => [\'ImageRegion','ImageRegionRegionBoundaryRbY'] }, + 'imageregionctype' => { 514 => [\'ImageRegion','ImageRegionRCtype'] }, + 'imageregionctypeidentifier' => { 514 => [\'ImageRegion','ImageRegionRCtypeIdentifier'] }, + 'imageregionctypename' => { 514 => [\'ImageRegion','ImageRegionRCtypeName'] }, + 'imageregionid' => { 514 => [\'ImageRegion','ImageRegionRId'] }, + 'imageregionname' => { 514 => [\'ImageRegion','ImageRegionName'] }, + 'imageregionrole' => { 514 => [\'ImageRegion','ImageRegionRRole'] }, + 'imageregionroleidentifier' => { 514 => [\'ImageRegion','ImageRegionRRoleIdentifier'] }, + 'imageregionrolename' => { 514 => [\'ImageRegion','ImageRegionRRoleName'] }, + 'imageresolution' => { 138 => 0x944 }, + 'imageresolutionjpg' => { 138 => 0x945 }, + 'imagereview' => { 299 => '0.4', 308 => '0.4' }, + 'imagereviewmonitorofftime' => { 298 => '21.1', 300 => '21.1', 301 => '21.1', 305 => '20.1', 310 => '21.1', 311 => '21.1', 313 => 0x39, 314 => 0x39, 315 => 0x39 }, + 'imagereviewtime' => { 297 => '25.1', 299 => '2.1', 302 => '19.1', 303 => '20.1', 304 => '20.1', 306 => '9.2', 307 => '20.1', 312 => '21.2' }, + 'imagerfiledatecreated' => { 138 => 0x9c5 }, + 'imagerfileproductionlevel' => { 138 => 0x9c4 }, + 'imagerfiletagsversionstandard' => { 138 => 0x9c7 }, + 'imagerotated' => { 151 => 0x2a }, + 'imagerpowerondelaymsec' => { 138 => 0x5fd }, + 'imagerrows' => { 138 => 0x17de }, + 'imagesegmentlines' => { 138 => 0x184d }, + 'imagesegmentstartline' => { 138 => 0x184c }, + 'imagesize' => { 162 => 'ImageSize' }, + 'imagesizeraw' => { 234 => 0x3e }, + 'imagesizerestriction' => { 522 => 'imageSizeRestriction' }, + 'imagesourcedata' => { 119 => 0x935c }, + 'imagespace' => { 138 => 0x909 }, + 'imagestabilization' => { 34 => 0x22, 113 => 0x3020, 127 => 0x1422, 156 => 'ImageStabilization', 182 => 0xbd, 183 => 0x71, 184 => 0x57, 185 => 0x0, 186 => [0x18,0x107,0x113], 187 => 0x49c2, 234 => 0xac, 317 => 0x604, 320 => 0x1600, 340 => 0x1a, 432 => 0x12, 433 => 0x11, 440 => 0xb026 }, + 'imagestabilization2' => { 431 => 0xa }, + 'imagestabilizationsetting' => { 187 => 0x14, 427 => 0x3d, 428 => 0x3d, 438 => 0x14 }, + 'imagestyle' => { 427 => 0x2d, 428 => 0x27 }, + 'imagesupplier' => { 327 => 'ImageSupplier' }, + 'imagesupplierid' => { 327 => [\'ImageSupplier','ImageSupplierImageSupplierID'] }, + 'imagesupplierimageid' => { 327 => 'ImageSupplierImageID' }, + 'imagesuppliername' => { 327 => [\'ImageSupplier','ImageSupplierImageSupplierName'] }, + 'imagetemperaturemax' => { 121 => 0x1 }, + 'imagetemperaturemin' => { 121 => 0x2 }, + 'imagetone' => { 375 => 0x4f }, + 'imagetype' => { 131 => 0x82, 327 => 'ImageType' }, + 'imageuniqueid' => { 1 => 0x15, 64 => 0x28, 119 => 0xa420, 505 => 'ImageUniqueID', 506 => 'ImageUniqueID' }, + 'imagewidth' => { 119 => 0x100, 188 => 0xe, 384 => 0x10c, 485 => 'ImageWidth', 525 => 'ImageWidth' }, + 'inclinationangle' => { 476 => 0x900f }, + 'inclinationcorrection' => { 476 => 0x900e }, + 'incrementaltemperature' => { 500 => 'IncrementalTemperature', 502 => 'IncrementalTemperature' }, + 'incrementaltint' => { 500 => 'IncrementalTint', 502 => 'IncrementalTint' }, + 'industry' => { 519 => 'industry', 520 => 'industry' }, + 'infobuttonwhenshooting' => { 84 => 0x409 }, + 'information' => { 394 => 'information', 400 => "\xa9inf" }, + 'infourl' => { 400 => 'infu' }, + 'infraredilluminator' => { 401 => 0x28 }, + 'ingredientexclusion' => { 521 => 'ingredientExclusion' }, + 'ingredients' => { 530 => 'Ingredients' }, + 'ingredientsalternatepaths' => { 530 => [\'Ingredients','IngredientsAlternatePaths'] }, + 'ingredientsdocumentid' => { 530 => [\'Ingredients','IngredientsDocumentID'] }, + 'ingredientsfilepath' => { 530 => [\'Ingredients','IngredientsFilePath'] }, + 'ingredientsfrompart' => { 530 => [\'Ingredients','IngredientsFromPart'] }, + 'ingredientsinstanceid' => { 530 => [\'Ingredients','IngredientsInstanceID'] }, + 'ingredientslastmodifydate' => { 530 => [\'Ingredients','IngredientsLastModifyDate'] }, + 'ingredientslasturl' => { 530 => [\'Ingredients','IngredientsLastURL'] }, + 'ingredientslinkcategory' => { 530 => [\'Ingredients','IngredientsLinkCategory'] }, + 'ingredientslinkform' => { 530 => [\'Ingredients','IngredientsLinkForm'] }, + 'ingredientsmanager' => { 530 => [\'Ingredients','IngredientsManager'] }, + 'ingredientsmanagervariant' => { 530 => [\'Ingredients','IngredientsManagerVariant'] }, + 'ingredientsmanageto' => { 530 => [\'Ingredients','IngredientsManageTo'] }, + 'ingredientsmanageui' => { 530 => [\'Ingredients','IngredientsManageUI'] }, + 'ingredientsmaskmarkers' => { 530 => [\'Ingredients','IngredientsMaskMarkers'] }, + 'ingredientsoriginaldocumentid' => { 530 => [\'Ingredients','IngredientsOriginalDocumentID'] }, + 'ingredientspartmapping' => { 530 => [\'Ingredients','IngredientsPartMapping'] }, + 'ingredientsplacedresolutionunit' => { 530 => [\'Ingredients','IngredientsPlacedResolutionUnit'] }, + 'ingredientsplacedxresolution' => { 530 => [\'Ingredients','IngredientsPlacedXResolution'] }, + 'ingredientsplacedyresolution' => { 530 => [\'Ingredients','IngredientsPlacedYResolution'] }, + 'ingredientsrenditionclass' => { 530 => [\'Ingredients','IngredientsRenditionClass'] }, + 'ingredientsrenditionparams' => { 530 => [\'Ingredients','IngredientsRenditionParams'] }, + 'ingredientstopart' => { 530 => [\'Ingredients','IngredientsToPart'] }, + 'ingredientsversionid' => { 530 => [\'Ingredients','IngredientsVersionID'] }, + 'initialafpointaiservoaf' => { 84 => 0x51e }, + 'initialafpointinservo' => { 2 => 0x13 }, + 'initialcameradolly' => { 488 => 'InitialCameraDolly' }, + 'initialhorizontalfovdegrees' => { 488 => 'InitialHorizontalFOVDegrees' }, + 'initialkey' => { 179 => 'WM/InitialKey' }, + 'initialverticalfovdegrees' => { 488 => 'InitialVerticalFOVDegrees' }, + 'initialviewheadingdegrees' => { 488 => 'InitialViewHeadingDegrees', 489 => 'InitialViewHeadingDegrees' }, + 'initialviewpitchdegrees' => { 488 => 'InitialViewPitchDegrees', 489 => 'InitialViewPitchDegrees' }, + 'initialviewrolldegrees' => { 488 => 'InitialViewRollDegrees', 489 => 'InitialViewRollDegrees' }, + 'initialzoomliveview' => { 297 => '4.4' }, + 'initialzoomsetting' => { 297 => '9.3', 306 => '27.3' }, + 'inkset' => { 119 => 0x14c }, + 'inputprofile' => { 138 => 0x1389 }, + 'instanceid' => { 530 => 'InstanceID' }, + 'instantplaybacksetup' => { 184 => 0x3e }, + 'instantplaybacktime' => { 184 => 0x3d }, + 'instructions' => { 517 => 'Instructions' }, + 'instrument' => { 529 => 'instrument' }, + 'integrationtime' => { 138 => 0x423 }, + 'intellectualgenre' => { 513 => 'IntellectualGenre' }, + 'intelligentauto' => { 440 => 0xb052, 468 => 0xd, 469 => 0xe, 470 => 0xd }, + 'intelligentcontrast' => { 52 => 0x4 }, + 'intelligentd-range' => { 340 => 0x79 }, + 'intelligentexposure' => { 340 => 0x5d }, + 'intelligentresolution' => { 340 => 0x70 }, + 'interchangecolorspace' => { 133 => 0x40 }, + 'intergraphmatrix' => { 119 => 0x8480 }, + 'internalflash' => { 181 => 0x2b, 297 => '23.1', 299 => '8.1', 302 => '22.1', 304 => '23.1', 308 => '8.1', 312 => '24.1', 320 => 0x1208 }, + 'internalflashae1' => { 322 => 0x1021 }, + 'internalflashae1_0' => { 322 => 0x101d }, + 'internalflashae2' => { 322 => 0x1022 }, + 'internalflashae2_0' => { 322 => 0x101e }, + 'internalflashmode' => { 363 => 0x1 }, + 'internalflashstrength' => { 363 => 0x3 }, + 'internalflashtable' => { 322 => 0x1024 }, + 'internallensserialnumber' => { 414 => 0xa005 }, + 'internalndfilter' => { 340 => 0x9d }, + 'internalserialnumber' => { 64 => 0x96, 76 => 0x9, 127 => 0x10, 187 => 0x49dc, 316 => 0x18, 318 => 0x102, 337 => 0x500, 340 => 0x25, 355 => 0x4, 407 => 0x5, 459 => [0x7c,0xf0], 460 => 0x88, 461 => [0x88,0x8a], 462 => 0x38 }, + 'interopindex' => { 119 => 0x1, 507 => 'InteroperabilityIndex' }, + 'interopversion' => { 119 => 0x2 }, + 'interval' => { 256 => 0x20 }, + 'intervaldurationhours' => { 238 => 0xa0, 239 => 0xb8 }, + 'intervaldurationminutes' => { 238 => 0xa4, 239 => 0xbc }, + 'intervaldurationseconds' => { 238 => 0xa8, 239 => 0xc0 }, + 'intervalexposuresmoothing' => { 223 => 0x184, 238 => 0xb8, 239 => 0xd0 }, + 'intervalframe' => { 256 => 0x24 }, + 'intervallength' => { 181 => 0x10 }, + 'intervalmode' => { 181 => 0x26 }, + 'intervalnumber' => { 181 => 0x11 }, + 'intervalpriority' => { 223 => 0x186, 238 => 0xba, 239 => 0xd2 }, + 'intervals' => { 223 => 0x17c, 238 => 0xb0, 239 => 0xc8, 240 => 0xbc, 241 => 0xcc, 242 => 0xcc }, + 'intervalshooting' => { 224 => 0x24, 258 => 0x24, 259 => 0x28, 375 => 0x92 }, + 'introtime' => { 529 => 'introTime' }, + 'introtimescale' => { 529 => [\'introTime','introTimeScale'] }, + 'introtimevalue' => { 529 => [\'introTime','introTimeValue'] }, + 'ipaversion' => { 138 => 0xdae }, + 'ipfcameramodel' => { 138 => 0xe4d }, + 'iptc' => { 120 => 'IPTC' }, + 'iptc-naa' => { 119 => 0x83bb, 345 => 0x83bb }, + 'iptcbitspersample' => { 133 => 0x56 }, + 'iptcdigest' => { 389 => 0x425 }, + 'iptcimageheight' => { 133 => 0x1e }, + 'iptcimagerotation' => { 133 => 0x66 }, + 'iptcimagewidth' => { 133 => 0x14 }, + 'iptclastedited' => { 514 => 'IptcLastEdited' }, + 'iptcpicturenumber' => { 133 => 0xa }, + 'iptcpixelheight' => { 133 => 0x32 }, + 'iptcpixelwidth' => { 133 => 0x28 }, + 'isalternativeof' => { 519 => 'isAlternativeOf' }, + 'isbn' => { 519 => 'isbn' }, + 'iscorrectionof' => { 519 => 'isCorrectionOf' }, + 'iscustompicturestyle' => { 109 => 0x3 }, + 'ismergedhdr' => { 497 => 'IsMergedHDR' }, + 'ismergedpanorama' => { 497 => 'IsMergedPanorama' }, + 'iso' => { 7 => 0x6, 9 => 0x6, 10 => 0x75, 11 => 0x6, 12 => 0x79, 13 => 0x6, 14 => 0x6, 15 => 0x6, 16 => 0x6, 17 => 0x6, 18 => 0x6, 19 => 0x6, 20 => 0x6, 21 => 0x6, 22 => 0x6, 23 => 0x6, 24 => 0x6, 25 => 0x6, 26 => 0x6, 27 => 0x6, 28 => 0x6, 29 => 0x6, 30 => 0x0, 31 => 0x1, 112 => 0x14, 113 => [0x3014,0x14], 119 => 0x8827, 137 => 0xfd06, 138 => 0x1784, 140 => 0x60, 142 => [0xfa2e,0xfa46], 143 => [0x27,0x28], 144 => 0xf105, 147 => 0x14, 149 => 0x4e, 151 => 0x1e, 152 => 0x1a, 154 => 0x34, 156 => 'ISO', 181 => 0x8, 187 => 0x49ba, 222 => 0x0, 234 => 0x2, 340 => 0xd1, 345 => 0x17, 375 => [0x8b,0x14], 382 => 0x14, 384 => 0x105, 414 => 0xa014, 417 => 0x86, 438 => 0x6f, 445 => [0x1f,0x21,0x25], 506 => 'ISOSpeedRatings' }, + 'iso2' => { 222 => 0x6, 263 => 0x265, 264 => 0x25c, 265 => 0x265, 266 => 0x221, 267 => 0x25d, 268 => 0x256, 269 => 0x25d, 272 => 0x2b5, 275 => 0x265, 279 => 0x2b5 }, + 'isoauto' => { 356 => '14.4' }, + 'isoautoflashlimit' => { 239 => 0x156, 240 => 0x146, 241 => 0x15a, 242 => 0x15a }, + 'isoautohilimit' => { 221 => 0x5, 239 => 0x154, 240 => 0x144, 241 => 0x158, 242 => 0x158, 261 => 0x5, 271 => 0x18eb }, + 'isoautomax' => { 439 => 0x4 }, + 'isoautomin' => { 439 => 0x2 }, + 'isoautoparameters' => { 375 => 0x7a }, + 'isoautoshuttertime' => { 221 => 0x4, 239 => 0x15e, 240 => 0x14e, 241 => 0x162, 242 => 0x162, 261 => 0x4, 271 => 0x18ea }, + 'isobutton' => { 242 => 0x796 }, + 'isocalibrationgain' => { 138 => 0x89f }, + 'isodisplay' => { 300 => '4.1', 301 => '4.1', 302 => '2.3', 303 => '3.3', 304 => '3.3', 307 => '3.3', 310 => '4.3', 311 => '4.1', 312 => '4.3' }, + 'isoexpansion' => { 84 => 0x103, 85 => 0x7, 86 => 0x8, 89 => 0x8, 222 => 0x4 }, + 'isoexpansion2' => { 222 => 0xa }, + 'isofloor' => { 356 => 0x6 }, + 'isoselected' => { 339 => 0x359 }, + 'isoselection' => { 234 => 0xf }, + 'isosensitivitystep' => { 305 => '6.2', 307 => '6.2' }, + 'isosetting' => { 140 => 0x5e, 152 => 0x14, 156 => 'ISOSetting', 181 => 0x24, 182 => 0x26, 183 => 0x1c, 184 => 0x13, 189 => 0x6, 234 => 0x13, 356 => '17.3', 406 => 0x27, 427 => 0x16, 428 => 0x14, 429 => 0x2, 438 => 0x6d, 439 => 0x0 }, + 'isospeed' => { 119 => 0x8833, 507 => 'ISOSpeed' }, + 'isospeedexpansion' => { 83 => 0x3 }, + 'isospeedincrements' => { 84 => 0x102 }, + 'isospeedlatitudeyyy' => { 119 => 0x8834, 507 => 'ISOSpeedLatitudeyyy' }, + 'isospeedlatitudezzz' => { 119 => 0x8835, 507 => 'ISOSpeedLatitudezzz' }, + 'isospeedrange' => { 84 => 0x103 }, + 'isostepsize' => { 297 => '6.1', 298 => '7.2', 300 => '7.2', 301 => '7.2', 306 => '4.1', 310 => '7.2', 311 => '7.2', 313 => 0x15d, 314 => 0x15d, 315 => 0x175 }, + 'isovalue' => { 322 => 0x1001 }, + 'isrccode' => { 400 => "\xa9isr" }, + 'issn' => { 519 => 'issn' }, + 'issueidentifier' => { 519 => 'issueIdentifier' }, + 'issuename' => { 519 => 'issueName' }, + 'issueteaser' => { 519 => 'issueTeaser' }, + 'issuetype' => { 519 => 'issueType' }, + 'istranslationof' => { 519 => 'isTranslationOf' }, + 'itemsubtype' => { 178 => 'ItemSubType' }, + 'itunesu' => { 392 => 'itnu' }, + 'jobid' => { 131 => 0xb8 }, + 'jobname' => { 508 => 'JobName' }, + 'jobref' => { 528 => 'JobRef' }, + 'jobrefid' => { 528 => [\'JobRef','JobRefId'] }, + 'jobrefname' => { 528 => [\'JobRef','JobRefName'] }, + 'jobrefurl' => { 528 => [\'JobRef','JobRefUrl'] }, + 'jobstatus' => { 508 => 'JobStatus' }, + 'jpeg-heifswitch' => { 440 => 0x2039 }, + 'jpeghandling' => { 500 => 'JPEGHandling', 502 => 'JPEGHandling' }, + 'jpegquality' => { 10 => 0x66, 340 => 0x43, 342 => 0x3034, 440 => 0xb047 }, + 'jpegsize' => { 342 => 0x303a }, + 'jpgcompression' => { 225 => 0x24, 234 => 0x44 }, + 'jpgfromraw' => { 97 => 0x2007, 114 => 'Exif-JpgFromRaw', 345 => 0x2e }, + 'jpgfromrawlength' => { 119 => [0x117,0x202] }, + 'jpgfromrawstart' => { 119 => [0x111,0x201] }, + 'jpgrecordedpixels' => { 356 => '14.1' }, + 'jurisdiction' => { 498 => 'jurisdiction' }, + 'keepexposure' => { 313 => 0x237, 314 => 0x237, 315 => 0x24f }, + 'kelvinwb_01' => { 364 => 0x5 }, + 'kelvinwb_02' => { 364 => 0x9 }, + 'kelvinwb_03' => { 364 => 0xd }, + 'kelvinwb_04' => { 364 => 0x11 }, + 'kelvinwb_05' => { 364 => 0x15 }, + 'kelvinwb_06' => { 364 => 0x19 }, + 'kelvinwb_07' => { 364 => 0x1d }, + 'kelvinwb_08' => { 364 => 0x21 }, + 'kelvinwb_09' => { 364 => 0x25 }, + 'kelvinwb_10' => { 364 => 0x29 }, + 'kelvinwb_11' => { 364 => 0x2d }, + 'kelvinwb_12' => { 364 => 0x31 }, + 'kelvinwb_13' => { 364 => 0x35 }, + 'kelvinwb_14' => { 364 => 0x39 }, + 'kelvinwb_15' => { 364 => 0x3d }, + 'kelvinwb_16' => { 364 => 0x41 }, + 'kelvinwb_daylight' => { 364 => 0x1 }, + 'kerneldenominators' => { 138 => 0x933 }, + 'key' => { 529 => 'key' }, + 'keystonecompensation' => { 321 => 0x1900 }, + 'keystonedirection' => { 321 => 0x1901 }, + 'keystonevalue' => { 321 => 0x1906 }, + 'keyword' => { 392 => 'keyw', 519 => 'keyword' }, + 'keywordinfo' => { 171 => 'Keywords' }, + 'keywords' => { 131 => 0x19, 157 => 'Keywords', 326 => 'Keywords', 391 => 'Keywords', 394 => 'keywords', 495 => 'keywords', 516 => 'Keywords', 527 => 'Keywords' }, + 'killdate' => { 519 => 'killDate' }, + 'killdatea-platform' => { 519 => [\'killDate','killDateA-platform'] }, + 'killdatedate' => { 519 => [\'killDate','killDateDate'] }, + 'kodakimageheight' => { 137 => 0xf908, 140 => 0xe, 142 => [0xfa1e,0xfa52], 148 => 0x70 }, + 'kodakimagewidth' => { 137 => 0xf907, 140 => 0xc, 142 => [0xfa1d,0xfa51], 148 => 0x6c }, + 'kodakinfotype' => { 137 => 0xfa00 }, + 'kodaklook' => { 138 => 0xe4c }, + 'kodaklookprofile' => { 138 => 0x138a }, + 'kodakmaker' => { 148 => 0x8 }, + 'kodakmodel' => { 140 => 0x0, 148 => 0x28 }, + 'kodaktag' => { 138 => 0x3ea }, + 'kodakversion' => { 138 => 0x0 }, + 'label' => { 330 => 'Label', 527 => 'Label' }, + 'labelname1' => { 512 => [\'TagStructure','TagStructureLabelName'] }, + 'labelname2' => { 512 => [\'TagStructure','TagStructureSubLabelsLabelName'] }, + 'labelname3' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsLabelName'] }, + 'labelname4' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsLabelName'] }, + 'labelname5' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsSubLabelsLabelName'] }, + 'labelname6' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabelsLabelName'] }, + 'landmark' => { 340 => 0x6f }, + 'landscapeoutputhighlightpoint' => { 109 => 0x26 }, + 'landscapeoutputshadowpoint' => { 109 => 0x27 }, + 'landscaperawcolortone' => { 109 => 0x1f }, + 'landscaperawcontrast' => { 109 => 0x21 }, + 'landscaperawhighlight' => { 109 => 0x77 }, + 'landscaperawhighlightpoint' => { 109 => 0x24 }, + 'landscaperawlinear' => { 109 => 0x22 }, + 'landscaperawsaturation' => { 109 => 0x20 }, + 'landscaperawshadow' => { 109 => 0x80 }, + 'landscaperawshadowpoint' => { 109 => 0x25 }, + 'landscaperawsharpness' => { 109 => 0x23 }, + 'landscapeunsharpmaskfineness' => { 109 => 0xa0 }, + 'landscapeunsharpmaskstrength' => { 109 => 0x9e }, + 'landscapeunsharpmaskthreshold' => { 109 => 0xa2 }, + 'language' => { 239 => 0x692, 240 => 0x592, 241 => 0x5c2, 242 => 0x5da, 503 => 'language' }, + 'languageidentifier' => { 131 => 0x87 }, + 'largestvalidinteriorrectheight' => { 488 => 'LargestValidInteriorRectHeight' }, + 'largestvalidinteriorrectleft' => { 488 => 'LargestValidInteriorRectLeft' }, + 'largestvalidinteriorrecttop' => { 488 => 'LargestValidInteriorRectTop' }, + 'largestvalidinteriorrectwidth' => { 488 => 'LargestValidInteriorRectWidth' }, + 'lastfilenumber' => { 181 => 0x1b }, + 'lastkeywordiptc' => { 178 => 'LastKeywordIPTC' }, + 'lastkeywordxmp' => { 178 => 'LastKeywordXMP' }, + 'lastphotodate' => { 488 => 'LastPhotoDate' }, + 'lasturl' => { 530 => 'LastURL' }, + 'lateralchromaticaberration' => { 440 => 0x2012 }, + 'lateralchromaticaberrationcorrectionalreadyapplied' => { 497 => 'LateralChromaticAberrationCorrectionAlreadyApplied' }, + 'latestageorhigheststage' => { 118 => [\'GeologicalContext','GeologicalContextLatestAgeOrHighestStage'] }, + 'latesteonorhighesteonothem' => { 118 => [\'GeologicalContext','GeologicalContextLatestEonOrHighestEonothem'] }, + 'latestepochorhighestseries' => { 118 => [\'GeologicalContext','GeologicalContextLatestEpochOrHighestSeries'] }, + 'latesteraorhighesterathem' => { 118 => [\'GeologicalContext','GeologicalContextLatestEraOrHighestErathem'] }, + 'latestperiodorhighestsystem' => { 118 => [\'GeologicalContext','GeologicalContextLatestPeriodOrHighestSystem'] }, + 'latitude' => { 116 => 'Latitude' }, + 'lc1' => { 366 => 0x2 }, + 'lc10' => { 366 => 0xb }, + 'lc11' => { 366 => 0xc }, + 'lc12' => { 366 => 0xd }, + 'lc14' => { 366 => 0xf }, + 'lc15' => { 366 => 0x10 }, + 'lc3' => { 366 => 0x4 }, + 'lc4' => { 366 => 0x5 }, + 'lc5' => { 366 => 0x6 }, + 'lc6' => { 366 => 0x7 }, + 'lc7' => { 366 => 0x8 }, + 'lc8' => { 366 => 0x9 }, + 'lcddisplayatpoweron' => { 84 => 0x811, 88 => 0xa }, + 'lcddisplayreturntoshoot' => { 89 => 0x12 }, + 'lcdillumination' => { 297 => '17.5', 298 => '5.2', 300 => '5.1', 301 => '5.1', 306 => '10.3', 307 => '4.2', 310 => '5.2', 311 => '5.1', 312 => '5.4', 313 => 0x101, 314 => 0x101, 315 => 0x117 }, + 'lcdilluminationduringbulb' => { 84 => 0x408 }, + 'lcdmatrix' => { 138 => 0xe74 }, + 'lcdmatrixchickfix' => { 138 => 0xe75 }, + 'lcdmatrixmarvin' => { 138 => 0xe76 }, + 'lcdpanels' => { 83 => 0x8 }, + 'lcheditor' => { 290 => 0x8ae85e }, + 'legacyiptcdigest' => { 517 => 'LegacyIPTCDigest' }, + 'legalcode' => { 498 => 'legalcode' }, + 'lens' => { 119 => 0xfdea, 234 => 0x84, 497 => 'Lens' }, + 'lensafstopbutton' => { 82 => 0x11, 83 => 0x13, 84 => 0x506, 85 => 0x10, 86 => 0x12, 89 => 0x13, 90 => 0x9 }, + 'lensaperturerange' => { 417 => [0x30,0x48] }, + 'lenscontrolring' => { 313 => 0xad, 314 => 0xad, 315 => 0xad }, + 'lenscorrectionsettings' => { 505 => 'LensCorrectionSettings' }, + 'lensdistortinfo' => { 497 => 'LensDistortInfo' }, + 'lensdistortionparams' => { 322 => 0x206 }, + 'lensdriveend' => { 232 => 0x56 }, + 'lensdrivenoaf' => { 84 => 0x505 }, + 'lensdrivewhenafimpossible' => { 2 => 0xb }, + 'lense-mountversion' => { 429 => 0x3f0, 475 => 0xd }, + 'lensfirmware' => { 408 => 0x20, 414 => 0xa004 }, + 'lensfirmwareversion' => { 318 => 0x204, 340 => 0x60, 429 => 0x3f3, 475 => 0x14 }, + 'lensfocallength' => { 103 => 0xf0512, 366 => 0x9 }, + 'lensfocalrange' => { 417 => [0xa,0x2a] }, + 'lensfocusfunctionbuttons' => { 298 => '55.1', 300 => '52.1', 301 => '52.1', 310 => '52.1', 311 => '52.1' }, + 'lensformat' => { 453 => 0x1891, 455 => 0x18bd, 456 => 0x18ed, 457 => 0x17f1, 459 => 0x106, 460 => 0x106, 471 => 0x603, 472 => 0x5d }, + 'lensfstops' => { 226 => 0x7, 227 => 0xc, 228 => 0xd, 232 => 0xe, 234 => 0x8b, 366 => '0.3' }, + 'lensfunc1button' => { 313 => 0x9f, 314 => 0x9f, 315 => 0x9f }, + 'lensfunc1buttonplaybackmode' => { 242 => 0x810 }, + 'lensfunc2button' => { 313 => 0xa7, 314 => 0xa7, 315 => 0xa7 }, + 'lensfunc2buttonplaybackmode' => { 242 => 0x812 }, + 'lensid' => { 232 => 0x30, 497 => 'LensID' }, + 'lensidnumber' => { 226 => 0x6, 227 => 0xb, 228 => 0xc, 232 => 0xd }, + 'lensinfo' => { 119 => 0xa432, 372 => 0x2a, 497 => 'LensInfo', 507 => 'LensSpecification' }, + 'lenskind' => { 366 => 0x1 }, + 'lensmake' => { 119 => 0xa433, 163 => 'Make', 507 => 'LensMake' }, + 'lensmanualdistortionamount' => { 500 => 'LensManualDistortionAmount', 502 => 'LensManualDistortionAmount' }, + 'lensmanufacturer' => { 178 => 'LensManufacturer' }, + 'lensmaxaperturerange' => { 417 => 0x2b }, + 'lensmodel' => { 7 => 0x937, 14 => 0x92b, 15 => 0x933, 64 => 0x95, 119 => 0xa434, 163 => 'Model', 178 => 'LensModel', 229 => 0x18a, 230 => 0x18b, 231 => 0x2ac, 318 => 0x203, 372 => 0xc, 384 => 0x412, 507 => 'LensModel' }, + 'lensmodulationoptimizer' => { 127 => 0x1045 }, + 'lensmount' => { 429 => 0x99, 453 => 0x1892, 455 => 0x18be, 456 => 0x18ee, 457 => 0x17f2, 459 => 0x105, 460 => 0x105, 471 => 0x604, 472 => 0x5e }, + 'lensmount2' => { 475 => 0x8 }, + 'lensmounttype' => { 232 => 0x35 }, + 'lenspositionabsolute' => { 232 => 0x5a }, + 'lensprofilechromaticaberrationscale' => { 500 => 'LensProfileChromaticAberrationScale', 502 => 'LensProfileChromaticAberrationScale' }, + 'lensprofiledigest' => { 500 => 'LensProfileDigest', 502 => 'LensProfileDigest' }, + 'lensprofiledistortionscale' => { 500 => 'LensProfileDistortionScale', 502 => 'LensProfileDistortionScale' }, + 'lensprofileenable' => { 500 => 'LensProfileEnable', 502 => 'LensProfileEnable' }, + 'lensprofilefilename' => { 500 => 'LensProfileFilename', 502 => 'LensProfileFilename' }, + 'lensprofileisembedded' => { 500 => 'LensProfileIsEmbedded', 502 => 'LensProfileIsEmbedded' }, + 'lensprofilematchkeycameramodelname' => { 500 => 'LensProfileMatchKeyCameraModelName', 502 => 'LensProfileMatchKeyCameraModelName' }, + 'lensprofilematchkeyexifmake' => { 500 => 'LensProfileMatchKeyExifMake', 502 => 'LensProfileMatchKeyExifMake' }, + 'lensprofilematchkeyexifmodel' => { 500 => 'LensProfileMatchKeyExifModel', 502 => 'LensProfileMatchKeyExifModel' }, + 'lensprofilematchkeyisraw' => { 500 => 'LensProfileMatchKeyIsRaw', 502 => 'LensProfileMatchKeyIsRaw' }, + 'lensprofilematchkeylensid' => { 500 => 'LensProfileMatchKeyLensID', 502 => 'LensProfileMatchKeyLensID' }, + 'lensprofilematchkeylensinfo' => { 500 => 'LensProfileMatchKeyLensInfo', 502 => 'LensProfileMatchKeyLensInfo' }, + 'lensprofilematchkeylensname' => { 500 => 'LensProfileMatchKeyLensName', 502 => 'LensProfileMatchKeyLensName' }, + 'lensprofilematchkeysensorformatfactor' => { 500 => 'LensProfileMatchKeySensorFormatFactor', 502 => 'LensProfileMatchKeySensorFormatFactor' }, + 'lensprofilename' => { 500 => 'LensProfileName', 502 => 'LensProfileName' }, + 'lensprofilesetup' => { 500 => 'LensProfileSetup', 502 => 'LensProfileSetup' }, + 'lensprofilevignettingscale' => { 500 => 'LensProfileVignettingScale', 502 => 'LensProfileVignettingScale' }, + 'lensproperties' => { 318 => 0x20b }, + 'lensserialnumber' => { 21 => 0x164, 32 => 0x16b, 61 => 0x0, 119 => 0xa435, 163 => 'SerialNumber', 318 => 0x202, 338 => 0x321, 340 => 0x52, 400 => 'LENS', 408 => 0x30, 497 => 'LensSerialNumber', 507 => 'LensSerialNumber' }, + 'lensshutterlock' => { 184 => 0x4a }, + 'lensspec' => { 424 => 0x0, 425 => 0x0, 426 => 0x0, 440 => 0xb02a }, + 'lensspecfeatures' => { 459 => [0x115,0x116], 460 => [0x116,0x1ed,0x1f0,0x21c,0x21e] }, + 'lenstemperature' => { 322 => 0x1008 }, + 'lenstype' => { 7 => 0xe2, 8 => 0xd, 9 => 0x1a7, 10 => 0xc, 11 => 0x111, 12 => 0xc, 13 => 0x14f, 14 => 0xd6, 15 => 0xde, 16 => 0xf6, 17 => 0xea, 18 => 0xff, 19 => [0xc,0x97], 20 => 0xe6, 21 => 0x153, 22 => 0xea, 23 => 0xe8, 24 => 0x127, 25 => 0x161, 26 => 0x166, 27 => 0x184, 28 => 0x112, 29 => 0x189, 34 => 0x16, 186 => 0x10c, 187 => 0x49bd, 234 => 0x83, 318 => 0x201, 331 => 0x16, 335 => 0x310, 337 => 0x303, 338 => 0x303, 340 => 0x51, 342 => 0x3405, 367 => 0x0, 368 => 0x0, 369 => 0x1, 370 => 0x1, 371 => 0x1, 373 => 0x0, 414 => 0xa003, 417 => 0x27, 440 => 0xb027, 453 => 0x1896, 455 => 0x18c2, 456 => 0x18f2, 457 => 0x17f6, 459 => 0x109, 460 => 0x109, 471 => 0x608, 472 => 0x62 }, + 'lenstype2' => { 429 => 0x3f7, 453 => 0x1893, 455 => 0x18bf, 456 => 0x18ef, 457 => 0x17f3, 459 => 0x107, 460 => 0x107, 471 => 0x605, 472 => 0x60 }, + 'lenstype3' => { 475 => 0x9 }, + 'lenstypemake' => { 340 => 0xc4 }, + 'lenstypemodel' => { 340 => [0xc5,0xe4] }, + 'lenszoomposition' => { 468 => 0x19, 469 => 0x1e, 472 => [0x342,0x34e,0x35a] }, + 'levelindicator' => { 348 => 0x15 }, + 'levelmeter' => { 400 => ['Lvlm','lvlm'] }, + 'levelorientation' => { 374 => 0x0 }, + 'license' => { 498 => 'license' }, + 'licensee' => { 327 => 'Licensee' }, + 'licenseeid' => { 327 => [\'Licensee','LicenseeLicenseeID'] }, + 'licenseeimageid' => { 327 => 'LicenseeImageID' }, + 'licenseeimagenotes' => { 327 => 'LicenseeImageNotes' }, + 'licenseename' => { 327 => [\'Licensee','LicenseeLicenseeName'] }, + 'licenseenddate' => { 327 => 'LicenseEndDate' }, + 'licenseeprojectreference' => { 327 => 'LicenseeProjectReference' }, + 'licenseetransactionid' => { 327 => 'LicenseeTransactionID' }, + 'licenseid' => { 327 => 'LicenseID' }, + 'licensestartdate' => { 327 => 'LicenseStartDate' }, + 'licensetransactiondate' => { 327 => 'LicenseTransactionDate' }, + 'licensetype' => { 504 => 'licensetype' }, + 'licensor' => { 327 => 'Licensor' }, + 'licensorcity' => { 327 => [\'Licensor','LicensorLicensorCity'] }, + 'licensorcountry' => { 327 => [\'Licensor','LicensorLicensorCountry'] }, + 'licensoremail' => { 327 => [\'Licensor','LicensorLicensorEmail'] }, + 'licensorextendedaddress' => { 327 => [\'Licensor','LicensorLicensorExtendedAddress'] }, + 'licensorid' => { 327 => [\'Licensor','LicensorLicensorID'] }, + 'licensorimageid' => { 327 => 'LicensorImageID' }, + 'licensorname' => { 327 => [\'Licensor','LicensorLicensorName'] }, + 'licensornotes' => { 327 => 'LicensorNotes' }, + 'licensorpostalcode' => { 327 => [\'Licensor','LicensorLicensorPostalCode'] }, + 'licensorregion' => { 327 => [\'Licensor','LicensorLicensorRegion'] }, + 'licensorstreetaddress' => { 327 => [\'Licensor','LicensorLicensorStreetAddress'] }, + 'licensortelephone1' => { 327 => [\'Licensor','LicensorLicensorTelephone1'] }, + 'licensortelephone2' => { 327 => [\'Licensor','LicensorLicensorTelephone2'] }, + 'licensortelephonetype1' => { 327 => [\'Licensor','LicensorLicensorTelephoneType1'] }, + 'licensortelephonetype2' => { 327 => [\'Licensor','LicensorLicensorTelephoneType2'] }, + 'licensortransactionid' => { 327 => 'LicensorTransactionID' }, + 'licensorurl' => { 327 => [\'Licensor','LicensorLicensorURL'] }, + 'lightcondition' => { 322 => 0x1009 }, + 'lightingmode' => { 113 => 0x302a }, + 'lightreading' => { 375 => 0x15 }, + 'lightsource' => { 119 => 0x9208, 234 => 0x90, 325 => 0x1000, 506 => 'LightSource' }, + 'lightsourcespecial' => { 416 => 0x21d }, + 'lightswitch' => { 310 => '0.1', 312 => '0.1' }, + 'lightvaluecenter' => { 322 => 0x103d }, + 'lightvalueperiphery' => { 322 => 0x103e }, + 'limitaf-areamodesel3dtracking' => { 313 => 0x153, 314 => 0x153, 315 => 0x16b }, + 'limitaf-areamodeseldynamic_l' => { 313 => 0x151, 314 => 0x151, 315 => 0x169 }, + 'limitaf-areamodeseldynamic_m' => { 313 => 0x150, 314 => 0x150, 315 => 0x168 }, + 'limitaf-areamodeseldynamic_s' => { 313 => 0x14f, 314 => 0x14f, 315 => 0x167 }, + 'limitaf-areamodeselpinpoint' => { 313 => 0x11, 314 => 0x11, 315 => 0x11 }, + 'limitaf-areamodeselwideaf_l' => { 313 => 0x14, 314 => 0x14, 315 => 0x14 }, + 'limitaf-areamodeselwideaf_s' => { 313 => 0x13, 314 => 0x13, 315 => 0x13 }, + 'limitafareamodeselauto' => { 313 => 0x15, 314 => 0x15, 315 => 0x15 }, + 'limitafareamodeselection' => { 298 => '51.1', 300 => '49.1', 301 => '49.1', 310 => '49.1', 311 => '49.1' }, + 'limitreleasemodeselc120' => { 314 => '269.4', 315 => '293.4' }, + 'limitreleasemodeselc30' => { 314 => '269.3', 315 => '293.3' }, + 'limitreleasemodeselch' => { 314 => '269.2', 315 => '293.2' }, + 'limitreleasemodeselcl' => { 314 => '269.1', 315 => '293.1' }, + 'limitreleasemodeselself' => { 314 => '269.5', 315 => '293.5' }, + 'limitselectableimagearea16to9' => { 313 => 0x47, 314 => 0x47, 315 => 0x47 }, + 'limitselectableimagearea1to1' => { 313 => 0x46, 314 => 0x46, 315 => 0x46 }, + 'limitselectableimageareadx' => { 313 => 0x45, 314 => 0x45, 315 => 0x45 }, + 'linearitylimitblue' => { 345 => 0x10 }, + 'linearitylimitgreen' => { 345 => 0xf }, + 'linearitylimitred' => { 345 => 0xe }, + 'linearityuppermargin' => { 41 => 0x32c, 42 => 0x282, 45 => [0x2ba,0x2d1,0x2d5], 47 => 0x1e5, 48 => [0x1fe,0x2de], 49 => [0x232,0x310], 50 => 0x31e }, + 'linearizationtable' => { 119 => 0xc618 }, + 'linearresponselimit' => { 119 => 0xc62e }, + 'link' => { 519 => 'link' }, + 'linkaetoafpoint' => { 356 => '14.2' }, + 'linkedencodedrightsexpr' => { 514 => [\'LinkedEncRightsExpr','LinkedEncRightsExprLinkedRightsExpr'] }, + 'linkedencodedrightsexprlangid' => { 514 => [\'LinkedEncRightsExpr','LinkedEncRightsExprRightsExprLangId'] }, + 'linkedencodedrightsexprtype' => { 514 => [\'LinkedEncRightsExpr','LinkedEncRightsExprRightsExprEncType'] }, + 'linkedencrightsexpr' => { 514 => 'LinkedEncRightsExpr' }, + 'linlogcoring' => { 138 => 0x904 }, + 'lithostratigraphicterms' => { 118 => [\'GeologicalContext','GeologicalContextLithostratigraphicTerms'] }, + 'livephotoauto' => { 394 => 'live-photo.auto' }, + 'livephotovideoindex' => { 1 => 0x17 }, + 'livephotovitalityscore' => { 394 => 'live-photo.vitality-score' }, + 'livephotovitalityscoringversion' => { 394 => 'live-photo.vitality-scoring-version' }, + 'liveviewaf' => { 302 => '32.1', 312 => '34.1' }, + 'liveviewafareamode' => { 307 => '34.1' }, + 'liveviewafmethod' => { 445 => 0x20 }, + 'liveviewafmode' => { 307 => '34.2' }, + 'liveviewafsetting' => { 429 => 0x36 }, + 'liveviewbuttonoptions' => { 298 => '50.2', 300 => '48.2', 301 => '48.2', 310 => '48.2', 311 => '48.2' }, + 'liveviewexposuresimulation' => { 84 => 0x810 }, + 'liveviewfocusmode' => { 429 => [0x8b,0x28b] }, + 'liveviewmetering' => { 429 => [0x84,0x284] }, + 'liveviewmonitorofftime' => { 298 => '21.2', 300 => '21.2', 301 => '21.2', 303 => '20.2', 304 => '20.2', 305 => '20.2', 307 => '20.2', 310 => '21.2', 311 => '21.2' }, + 'liveviewshooting' => { 57 => 0x13 }, + 'livingspecimen' => { 118 => 'LivingSpecimen' }, + 'livingspecimenmaterialsampleid' => { 118 => [\'LivingSpecimen','LivingSpecimenMaterialSampleID'] }, + 'localcaption' => { 131 => 0x79 }, + 'localizedcameramodel' => { 119 => 0xc615 }, + 'locallocationname' => { 414 => 0x30 }, + 'location' => { 233 => 0x9, 340 => 0x67, 493 => 'Location', 513 => 'Location', 518 => 'location', 519 => 'location' }, + 'locationaccuracyhorizontal' => { 394 => 'location.accuracy.horizontal' }, + 'locationareacode' => { 499 => 'lac' }, + 'locationbody' => { 394 => 'location.body' }, + 'locationcreated' => { 514 => 'LocationCreated' }, + 'locationcreatedcity' => { 514 => [\'LocationCreated','LocationCreatedCity'] }, + 'locationcreatedcountrycode' => { 514 => [\'LocationCreated','LocationCreatedCountryCode'] }, + 'locationcreatedcountryname' => { 514 => [\'LocationCreated','LocationCreatedCountryName'] }, + 'locationcreatedgpsaltitude' => { 514 => [\'LocationCreated','LocationCreatedGPSAltitude'] }, + 'locationcreatedgpslatitude' => { 514 => [\'LocationCreated','LocationCreatedGPSLatitude'] }, + 'locationcreatedgpslongitude' => { 514 => [\'LocationCreated','LocationCreatedGPSLongitude'] }, + 'locationcreatedidentifier' => { 514 => [\'LocationCreated','LocationCreatedIdentifier'] }, + 'locationcreatedlocationid' => { 514 => [\'LocationCreated','LocationCreatedLocationId'] }, + 'locationcreatedlocationname' => { 514 => [\'LocationCreated','LocationCreatedLocationName'] }, + 'locationcreatedprovincestate' => { 514 => [\'LocationCreated','LocationCreatedProvinceState'] }, + 'locationcreatedsublocation' => { 514 => [\'LocationCreated','LocationCreatedSublocation'] }, + 'locationcreatedworldregion' => { 514 => [\'LocationCreated','LocationCreatedWorldRegion'] }, + 'locationdate' => { 394 => 'location.date' }, + 'locationinformation' => { 400 => 'loci' }, + 'locationinfoversion' => { 233 => 0x0 }, + 'locationname' => { 394 => 'location.name', 414 => 0x31 }, + 'locationnote' => { 394 => 'location.note' }, + 'locationrole' => { 394 => 'location.role' }, + 'locationshown' => { 514 => 'LocationShown' }, + 'locationshowncity' => { 514 => [\'LocationShown','LocationShownCity'] }, + 'locationshowncountrycode' => { 514 => [\'LocationShown','LocationShownCountryCode'] }, + 'locationshowncountryname' => { 514 => [\'LocationShown','LocationShownCountryName'] }, + 'locationshowngpsaltitude' => { 514 => [\'LocationShown','LocationShownGPSAltitude'] }, + 'locationshowngpslatitude' => { 514 => [\'LocationShown','LocationShownGPSLatitude'] }, + 'locationshowngpslongitude' => { 514 => [\'LocationShown','LocationShownGPSLongitude'] }, + 'locationshownidentifier' => { 514 => [\'LocationShown','LocationShownIdentifier'] }, + 'locationshownlocationid' => { 514 => [\'LocationShown','LocationShownLocationId'] }, + 'locationshownlocationname' => { 514 => [\'LocationShown','LocationShownLocationName'] }, + 'locationshownprovincestate' => { 514 => [\'LocationShown','LocationShownProvinceState'] }, + 'locationshownsublocation' => { 514 => [\'LocationShown','LocationShownSublocation'] }, + 'locationshownworldregion' => { 514 => [\'LocationShown','LocationShownWorldRegion'] }, + 'lockmicrophonebutton' => { 84 => 0x709 }, + 'logcomment' => { 529 => 'logComment' }, + 'logscale' => { 138 => 0x902 }, + 'longdescription' => { 392 => 'ldes' }, + 'longexposurenoisereduction' => { 62 => 0x4, 84 => 0x201, 85 => 0x1, 86 => 0x2, 87 => 0x1, 88 => 0x1, 89 => 0x2, 90 => 0x1, 340 => 0x49, 427 => 0x2b, 428 => 0x25, 429 => 0x25, 440 => 0x2008, 445 => 0x11, 472 => 0x44 }, + 'longexposurenoisereduction2' => { 57 => 0x8 }, + 'longexposurenrused' => { 340 => 0xbe }, + 'longitude' => { 116 => 'Longitude' }, + 'look' => { 500 => 'Look', 502 => 'Look' }, + 'lookamount' => { 500 => [\'Look','LookAmount'], 502 => [\'Look','LookAmount'] }, + 'lookcluster' => { 500 => [\'Look','LookCluster'], 502 => [\'Look','LookCluster'] }, + 'lookcopyright' => { 500 => [\'Look','LookCopyright'], 502 => [\'Look','LookCopyright'] }, + 'lookgroup' => { 500 => [\'Look','LookGroup'], 502 => [\'Look','LookGroup'] }, + 'lookname' => { 500 => 'LookName', 502 => 'LookName' }, + 'lookparameters' => { 500 => [\'Look','LookParameters'], 502 => [\'Look','LookParameters'] }, + 'lookparameterscameraprofile' => { 500 => [\'Look','LookParametersCameraProfile'], 502 => [\'Look','LookParametersCameraProfile'] }, + 'lookparametersclarity2012' => { 500 => [\'Look','LookParametersClarity2012'], 502 => [\'Look','LookParametersClarity2012'] }, + 'lookparametersconverttograyscale' => { 500 => [\'Look','LookParametersConvertToGrayscale'], 502 => [\'Look','LookParametersConvertToGrayscale'] }, + 'lookparameterslooktable' => { 500 => [\'Look','LookParametersLookTable'], 502 => [\'Look','LookParametersLookTable'] }, + 'lookparametersprocessversion' => { 500 => [\'Look','LookParametersProcessVersion'], 502 => [\'Look','LookParametersProcessVersion'] }, + 'lookparameterstonecurvepv2012' => { 500 => [\'Look','LookParametersToneCurvePV2012'], 502 => [\'Look','LookParametersToneCurvePV2012'] }, + 'lookparameterstonecurvepv2012blue' => { 500 => [\'Look','LookParametersToneCurvePV2012Blue'], 502 => [\'Look','LookParametersToneCurvePV2012Blue'] }, + 'lookparameterstonecurvepv2012green' => { 500 => [\'Look','LookParametersToneCurvePV2012Green'], 502 => [\'Look','LookParametersToneCurvePV2012Green'] }, + 'lookparameterstonecurvepv2012red' => { 500 => [\'Look','LookParametersToneCurvePV2012Red'], 502 => [\'Look','LookParametersToneCurvePV2012Red'] }, + 'lookparametersversion' => { 500 => [\'Look','LookParametersVersion'], 502 => [\'Look','LookParametersVersion'] }, + 'looksupportsamount' => { 500 => [\'Look','LookSupportsAmount'], 502 => [\'Look','LookSupportsAmount'] }, + 'looksupportsmonochrome' => { 500 => [\'Look','LookSupportsMonochrome'], 502 => [\'Look','LookSupportsMonochrome'] }, + 'looksupportsoutputreferred' => { 500 => [\'Look','LookSupportsOutputReferred'], 502 => [\'Look','LookSupportsOutputReferred'] }, + 'lookuuid' => { 500 => [\'Look','LookUUID'], 502 => [\'Look','LookUUID'] }, + 'loop' => { 529 => 'loop' }, + 'loopstyle' => { 400 => 'LOOP' }, + 'lowestbiostratigraphiczone' => { 118 => [\'GeologicalContext','GeologicalContextLowestBiostratigraphicZone'] }, + 'luminanceadjustmentaqua' => { 500 => 'LuminanceAdjustmentAqua', 502 => 'LuminanceAdjustmentAqua' }, + 'luminanceadjustmentblue' => { 500 => 'LuminanceAdjustmentBlue', 502 => 'LuminanceAdjustmentBlue' }, + 'luminanceadjustmentgreen' => { 500 => 'LuminanceAdjustmentGreen', 502 => 'LuminanceAdjustmentGreen' }, + 'luminanceadjustmentmagenta' => { 500 => 'LuminanceAdjustmentMagenta', 502 => 'LuminanceAdjustmentMagenta' }, + 'luminanceadjustmentorange' => { 500 => 'LuminanceAdjustmentOrange', 502 => 'LuminanceAdjustmentOrange' }, + 'luminanceadjustmentpurple' => { 500 => 'LuminanceAdjustmentPurple', 502 => 'LuminanceAdjustmentPurple' }, + 'luminanceadjustmentred' => { 500 => 'LuminanceAdjustmentRed', 502 => 'LuminanceAdjustmentRed' }, + 'luminanceadjustmentyellow' => { 500 => 'LuminanceAdjustmentYellow', 502 => 'LuminanceAdjustmentYellow' }, + 'luminancecurvelimits' => { 108 => 0x150 }, + 'luminancecurvepoints' => { 108 => 0x126 }, + 'luminancenoiseamplitude' => { 1 => 0x1d }, + 'luminancenoisereduction' => { 103 => 0x20600, 109 => 0x5f, 417 => 0x1b }, + 'luminancenoisereductioncontrast' => { 500 => 'LuminanceNoiseReductionContrast', 502 => 'LuminanceNoiseReductionContrast' }, + 'luminancenoisereductiondetail' => { 500 => 'LuminanceNoiseReductionDetail', 502 => 'LuminanceNoiseReductionDetail' }, + 'luminancenr_tiff_jpeg' => { 109 => 0x6d }, + 'luminancesmoothing' => { 500 => 'LuminanceSmoothing', 502 => 'LuminanceSmoothing' }, + 'lvshootingareadisplay' => { 84 => [0x40b,0x40c] }, + 'lyrics' => { 392 => "\xa9lyr", 400 => "\xa9lyr", 529 => 'lyrics' }, + 'lyricsuri' => { 400 => 'lrcu' }, + 'm16cversion' => { 335 => 0x333 }, + 'macatom' => { 501 => 'macAtom' }, + 'macatomapplicationcode' => { 501 => [\'macAtom','macAtomApplicationCode'] }, + 'macatominvocationappleevent' => { 501 => [\'macAtom','macAtomInvocationAppleEvent'] }, + 'macatomposixprojectpath' => { 501 => [\'macAtom','macAtomPosixProjectPath'] }, + 'machineobservation' => { 118 => 'MachineObservation' }, + 'machineobservationday' => { 118 => [\'MachineObservation','MachineObservationDay'] }, + 'machineobservationearliestdate' => { 118 => [\'MachineObservation','MachineObservationEarliestDate'] }, + 'machineobservationenddayofyear' => { 118 => [\'MachineObservation','MachineObservationEndDayOfYear'] }, + 'machineobservationeventdate' => { 118 => [\'MachineObservation','MachineObservationEventDate'] }, + 'machineobservationeventid' => { 118 => [\'MachineObservation','MachineObservationEventID'] }, + 'machineobservationeventremarks' => { 118 => [\'MachineObservation','MachineObservationEventRemarks'] }, + 'machineobservationeventtime' => { 118 => [\'MachineObservation','MachineObservationEventTime'] }, + 'machineobservationfieldnotes' => { 118 => [\'MachineObservation','MachineObservationFieldNotes'] }, + 'machineobservationfieldnumber' => { 118 => [\'MachineObservation','MachineObservationFieldNumber'] }, + 'machineobservationhabitat' => { 118 => [\'MachineObservation','MachineObservationHabitat'] }, + 'machineobservationlatestdate' => { 118 => [\'MachineObservation','MachineObservationLatestDate'] }, + 'machineobservationmonth' => { 118 => [\'MachineObservation','MachineObservationMonth'] }, + 'machineobservationparenteventid' => { 118 => [\'MachineObservation','MachineObservationParentEventID'] }, + 'machineobservationsamplesizeunit' => { 118 => [\'MachineObservation','MachineObservationSampleSizeUnit'] }, + 'machineobservationsamplesizevalue' => { 118 => [\'MachineObservation','MachineObservationSampleSizeValue'] }, + 'machineobservationsamplingeffort' => { 118 => [\'MachineObservation','MachineObservationSamplingEffort'] }, + 'machineobservationsamplingprotocol' => { 118 => [\'MachineObservation','MachineObservationSamplingProtocol'] }, + 'machineobservationstartdayofyear' => { 118 => [\'MachineObservation','MachineObservationStartDayOfYear'] }, + 'machineobservationverbatimeventdate' => { 118 => [\'MachineObservation','MachineObservationVerbatimEventDate'] }, + 'machineobservationyear' => { 118 => [\'MachineObservation','MachineObservationYear'] }, + 'macro' => { 127 => 0x1020, 129 => 0x202, 151 => 0x2b, 322 => 0x202, 406 => 0x21, 416 => 0x202, 440 => 0xb040 }, + 'macroled' => { 320 => 0x120a }, + 'macromagnification' => { 7 => 0x1b, 11 => 0x1b, 14 => 0x1b, 15 => 0x1b, 19 => 0x1b, 20 => 0x1b, 57 => 0x10 }, + 'macromode' => { 34 => 0x1, 181 => 0xb, 317 => 0x300, 340 => 0x1c, 407 => 0x1009 }, + 'magentahsl' => { 103 => 0x20917 }, + 'magicfilter' => { 317 => 0x52c }, + 'magnifiedview' => { 86 => 0x11, 88 => 0x9 }, + 'mainboardversion' => { 138 => 0x438 }, + 'maindialexposurecomp' => { 308 => '0.6' }, + 'mainingredient' => { 521 => 'mainIngredient' }, + 'majorversion' => { 491 => 'MajorVersion' }, + 'make' => { 98 => 0x0, 115 => 0x1, 119 => 0x10f, 156 => 'Make', 330 => 'Make', 345 => 0x10f, 394 => 'make', 400 => ['@mak',"\xa9mak"], 518 => 'make', 525 => 'Make' }, + 'makernote' => { 506 => 'MakerNote' }, + 'makernoteapple' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotecanon' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotecasio' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotecasio2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotedji' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotedjiinfo' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteflir' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotefujifilm' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotege' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotege2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotehasselblad' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotehp' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotehp2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotehp4' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotehp6' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteisl' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotejvc' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotejvctext' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak10' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak11' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak12' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak1a' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak1b' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak3' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak4' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak5' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak6a' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak6b' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak7' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak8a' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak8b' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak8c' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodak9' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekodakunknown' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotekyocera' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteleica' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteleica10' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteleica2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteleica3' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteleica4' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteleica5' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteleica6' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteleica7' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteleica8' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteleica9' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteminolta' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteminolta2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteminolta3' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotemotorola' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotenikon' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotenikon2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotenikon3' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotenintendo' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteoffset' => { 416 => 0xff }, + 'makernoteolympus' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteolympus2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteolympus3' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotepanasonic' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotepanasonic2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotepanasonic3' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotepentax' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotepentax2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotepentax3' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotepentax4' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotepentax5' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotepentax6' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotepentaxunknown' => { 400 => 'PXMN' }, + 'makernotephaseone' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotereconyx' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotereconyx2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotereconyx3' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotericoh' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotericoh2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotericohpentax' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotericohtext' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesafety' => { 119 => 0xc635 }, + 'makernotesamsung1a' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesamsung1b' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesamsung2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesanyo' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesanyoc4' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesanyopatch' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesigma' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesony' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesony2' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesony3' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesony4' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesony5' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesonyericsson' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotesonysrf' => { 117 => 'MakN', 119 => 0x927c }, + 'makernotetype' => { 407 => 0x1 }, + 'makernoteunknown' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteunknownbinary' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteunknowntext' => { 117 => 'MakN', 119 => 0x927c }, + 'makernoteversion' => { 1 => 0x1, 186 => 0x0, 234 => 0x1, 322 => 0x0, 340 => 0x8000, 411 => 0x0, 414 => 0x1, 417 => [0x1d,0x1f], 430 => 0x2000 }, + 'makerurl' => { 400 => "\xa9mal" }, + 'managedfrom' => { 530 => 'ManagedFrom' }, + 'managedfromalternatepaths' => { 530 => [\'ManagedFrom','ManagedFromAlternatePaths'] }, + 'managedfromdocumentid' => { 530 => [\'ManagedFrom','ManagedFromDocumentID'] }, + 'managedfromfilepath' => { 530 => [\'ManagedFrom','ManagedFromFilePath'] }, + 'managedfromfrompart' => { 530 => [\'ManagedFrom','ManagedFromFromPart'] }, + 'managedfrominstanceid' => { 530 => [\'ManagedFrom','ManagedFromInstanceID'] }, + 'managedfromlastmodifydate' => { 530 => [\'ManagedFrom','ManagedFromLastModifyDate'] }, + 'managedfromlasturl' => { 530 => [\'ManagedFrom','ManagedFromLastURL'] }, + 'managedfromlinkcategory' => { 530 => [\'ManagedFrom','ManagedFromLinkCategory'] }, + 'managedfromlinkform' => { 530 => [\'ManagedFrom','ManagedFromLinkForm'] }, + 'managedfrommanager' => { 530 => [\'ManagedFrom','ManagedFromManager'] }, + 'managedfrommanagervariant' => { 530 => [\'ManagedFrom','ManagedFromManagerVariant'] }, + 'managedfrommanageto' => { 530 => [\'ManagedFrom','ManagedFromManageTo'] }, + 'managedfrommanageui' => { 530 => [\'ManagedFrom','ManagedFromManageUI'] }, + 'managedfrommaskmarkers' => { 530 => [\'ManagedFrom','ManagedFromMaskMarkers'] }, + 'managedfromoriginaldocumentid' => { 530 => [\'ManagedFrom','ManagedFromOriginalDocumentID'] }, + 'managedfrompartmapping' => { 530 => [\'ManagedFrom','ManagedFromPartMapping'] }, + 'managedfromplacedresolutionunit' => { 530 => [\'ManagedFrom','ManagedFromPlacedResolutionUnit'] }, + 'managedfromplacedxresolution' => { 530 => [\'ManagedFrom','ManagedFromPlacedXResolution'] }, + 'managedfromplacedyresolution' => { 530 => [\'ManagedFrom','ManagedFromPlacedYResolution'] }, + 'managedfromrenditionclass' => { 530 => [\'ManagedFrom','ManagedFromRenditionClass'] }, + 'managedfromrenditionparams' => { 530 => [\'ManagedFrom','ManagedFromRenditionParams'] }, + 'managedfromtopart' => { 530 => [\'ManagedFrom','ManagedFromToPart'] }, + 'managedfromversionid' => { 530 => [\'ManagedFrom','ManagedFromVersionID'] }, + 'manager' => { 530 => 'Manager' }, + 'managervariant' => { 530 => 'ManagerVariant' }, + 'manageto' => { 530 => 'ManageTo' }, + 'manageui' => { 530 => 'ManageUI' }, + 'manifest' => { 530 => 'Manifest' }, + 'manifestlinkform' => { 530 => [\'Manifest','ManifestLinkForm'] }, + 'manifestplacedresolutionunit' => { 530 => [\'Manifest','ManifestPlacedResolutionUnit'] }, + 'manifestplacedxresolution' => { 530 => [\'Manifest','ManifestPlacedXResolution'] }, + 'manifestplacedyresolution' => { 530 => [\'Manifest','ManifestPlacedYResolution'] }, + 'manifestreference' => { 530 => [\'Manifest','ManifestReference'] }, + 'manifestreferencealternatepaths' => { 530 => [\'Manifest','ManifestReferenceAlternatePaths'] }, + 'manifestreferencedocumentid' => { 530 => [\'Manifest','ManifestReferenceDocumentID'] }, + 'manifestreferencefilepath' => { 530 => [\'Manifest','ManifestReferenceFilePath'] }, + 'manifestreferencefrompart' => { 530 => [\'Manifest','ManifestReferenceFromPart'] }, + 'manifestreferenceinstanceid' => { 530 => [\'Manifest','ManifestReferenceInstanceID'] }, + 'manifestreferencelastmodifydate' => { 530 => [\'Manifest','ManifestReferenceLastModifyDate'] }, + 'manifestreferencelasturl' => { 530 => [\'Manifest','ManifestReferenceLastURL'] }, + 'manifestreferencelinkcategory' => { 530 => [\'Manifest','ManifestReferenceLinkCategory'] }, + 'manifestreferencelinkform' => { 530 => [\'Manifest','ManifestReferenceLinkForm'] }, + 'manifestreferencemanager' => { 530 => [\'Manifest','ManifestReferenceManager'] }, + 'manifestreferencemanagervariant' => { 530 => [\'Manifest','ManifestReferenceManagerVariant'] }, + 'manifestreferencemanageto' => { 530 => [\'Manifest','ManifestReferenceManageTo'] }, + 'manifestreferencemanageui' => { 530 => [\'Manifest','ManifestReferenceManageUI'] }, + 'manifestreferencemaskmarkers' => { 530 => [\'Manifest','ManifestReferenceMaskMarkers'] }, + 'manifestreferenceoriginaldocumentid' => { 530 => [\'Manifest','ManifestReferenceOriginalDocumentID'] }, + 'manifestreferencepartmapping' => { 530 => [\'Manifest','ManifestReferencePartMapping'] }, + 'manifestreferenceplacedresolutionunit' => { 530 => [\'Manifest','ManifestReferencePlacedResolutionUnit'] }, + 'manifestreferenceplacedxresolution' => { 530 => [\'Manifest','ManifestReferencePlacedXResolution'] }, + 'manifestreferenceplacedyresolution' => { 530 => [\'Manifest','ManifestReferencePlacedYResolution'] }, + 'manifestreferencerenditionclass' => { 530 => [\'Manifest','ManifestReferenceRenditionClass'] }, + 'manifestreferencerenditionparams' => { 530 => [\'Manifest','ManifestReferenceRenditionParams'] }, + 'manifestreferencetopart' => { 530 => [\'Manifest','ManifestReferenceToPart'] }, + 'manifestreferenceversionid' => { 530 => [\'Manifest','ManifestReferenceVersionID'] }, + 'manometerpressure' => { 317 => 0x900, 340 => 0x86 }, + 'manometerreading' => { 317 => 0x901 }, + 'manualafpointselectpattern' => { 84 => 0x513 }, + 'manualafpointselpattern' => { 2 => 0xf }, + 'manualflash' => { 320 => 0x1209 }, + 'manualflashoutput' => { 34 => 0x29, 299 => '8.2', 302 => '22.2', 303 => '23.1', 304 => '23.2', 306 => '16.2', 307 => '23.2', 308 => '8.2', 309 => '23.2', 312 => '24.2', 407 => 0x100c }, + 'manualflashstrength' => { 317 => 0x406 }, + 'manualfocusdistance' => { 234 => 0x85, 322 => 0x100c, 416 => 0x223 }, + 'manualfocuspointillumination' => { 313 => 0x17, 314 => 0x17, 315 => 0x17 }, + 'manualfocusringinafmode' => { 313 => 0x1a, 314 => 0x1a, 315 => 0x1a }, + 'manualtv' => { 83 => 0x5, 84 => 0x705 }, + 'manufacturedate' => { 191 => 0x6705, 355 => 0x1 }, + 'manufacturedate1' => { 409 => 0x4 }, + 'manufacturedate2' => { 409 => 0x5 }, + 'manufacturer' => { 485 => 'Manufacturer', 518 => 'manufacturer' }, + 'mariahchromablursize' => { 138 => 0xf0d }, + 'mariahmaphithreshold' => { 138 => 0xf0c }, + 'mariahmaplothreshold' => { 138 => 0xf0b }, + 'mariahsigmathreshold' => { 138 => 0xf0e }, + 'mariahtexturethreshold' => { 138 => 0xf0a }, + 'marked' => { 516 => 'Marked', 533 => 'Marked' }, + 'markers' => { 529 => 'markers' }, + 'markerscomment' => { 529 => [\'markers','markersComment'] }, + 'markerscuepointparams' => { 529 => [\'markers','markersCuePointParams'] }, + 'markerscuepointparamskey' => { 529 => [\'markers','markersCuePointParamsKey'] }, + 'markerscuepointparamsvalue' => { 529 => [\'markers','markersCuePointParamsValue'] }, + 'markerscuepointtype' => { 529 => [\'markers','markersCuePointType'] }, + 'markersduration' => { 529 => [\'markers','markersDuration'] }, + 'markerslocation' => { 529 => [\'markers','markersLocation'] }, + 'markersname' => { 529 => [\'markers','markersName'] }, + 'markersprobability' => { 529 => [\'markers','markersProbability'] }, + 'markersspeaker' => { 529 => [\'markers','markersSpeaker'] }, + 'markersstarttime' => { 529 => [\'markers','markersStartTime'] }, + 'markerstarget' => { 529 => [\'markers','markersTarget'] }, + 'markerstype' => { 529 => [\'markers','markersType'] }, + 'maskedareas' => { 119 => 0xc68e }, + 'maskgroupbasedcorractive' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionActive'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionActive'] }, + 'maskgroupbasedcorramount' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionAmount'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionAmount'] }, + 'maskgroupbasedcorrblacks2012' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalBlacks2012'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalBlacks2012'] }, + 'maskgroupbasedcorrbrightness' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalBrightness'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalBrightness'] }, + 'maskgroupbasedcorrclarity' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalClarity'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalClarity'] }, + 'maskgroupbasedcorrclarity2012' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalClarity2012'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalClarity2012'] }, + 'maskgroupbasedcorrcontrast' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalContrast'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalContrast'] }, + 'maskgroupbasedcorrcontrast2012' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalContrast2012'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalContrast2012'] }, + 'maskgroupbasedcorrcorrectionname' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionName'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionName'] }, + 'maskgroupbasedcorrcorrectionsyncid' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionSyncID'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionSyncID'] }, + 'maskgroupbasedcorrdefringe' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalDefringe'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalDefringe'] }, + 'maskgroupbasedcorrdehaze' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalDehaze'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalDehaze'] }, + 'maskgroupbasedcorrections' => { 500 => 'MaskGroupBasedCorrections', 502 => 'MaskGroupBasedCorrections' }, + 'maskgroupbasedcorrexposure' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalExposure'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalExposure'] }, + 'maskgroupbasedcorrexposure2012' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalExposure2012'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalExposure2012'] }, + 'maskgroupbasedcorrhighlights2012' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalHighlights2012'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalHighlights2012'] }, + 'maskgroupbasedcorrhue' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalHue'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalHue'] }, + 'maskgroupbasedcorrluminancenoise' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalLuminanceNoise'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalLuminanceNoise'] }, + 'maskgroupbasedcorrmask' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasks'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasks'] }, + 'maskgroupbasedcorrmaskalpha' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksAlpha'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksAlpha'] }, + 'maskgroupbasedcorrmaskangle' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksAngle'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksAngle'] }, + 'maskgroupbasedcorrmaskbottom' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksBottom'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksBottom'] }, + 'maskgroupbasedcorrmaskcentervalue' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCenterValue'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCenterValue'] }, + 'maskgroupbasedcorrmaskcenterweight' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCenterWeight'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCenterWeight'] }, + 'maskgroupbasedcorrmaskdabs' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksDabs'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksDabs'] }, + 'maskgroupbasedcorrmaskfeather' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksFeather'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksFeather'] }, + 'maskgroupbasedcorrmaskflipped' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksFlipped'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksFlipped'] }, + 'maskgroupbasedcorrmaskflow' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksFlow'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksFlow'] }, + 'maskgroupbasedcorrmaskfullx' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksFullX'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksFullX'] }, + 'maskgroupbasedcorrmaskfully' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksFullY'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksFullY'] }, + 'maskgroupbasedcorrmaskinputdigest' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksInputDigest'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksInputDigest'] }, + 'maskgroupbasedcorrmaskleft' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksLeft'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksLeft'] }, + 'maskgroupbasedcorrmaskmaskactive' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskActive'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskActive'] }, + 'maskgroupbasedcorrmaskmaskblendmode' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskBlendMode'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskBlendMode'] }, + 'maskgroupbasedcorrmaskmaskdigest' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskDigest'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskDigest'] }, + 'maskgroupbasedcorrmaskmaskinverted' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskInverted'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskInverted'] }, + 'maskgroupbasedcorrmaskmaskname' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskName'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskName'] }, + 'maskgroupbasedcorrmaskmasks' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasks'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasks'] }, + 'maskgroupbasedcorrmaskmasksalpha' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksAlpha'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksAlpha'] }, + 'maskgroupbasedcorrmaskmasksangle' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksAngle'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksAngle'] }, + 'maskgroupbasedcorrmaskmasksbottom' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksBottom'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksBottom'] }, + 'maskgroupbasedcorrmaskmaskscentervalue' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksCenterValue'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksCenterValue'] }, + 'maskgroupbasedcorrmaskmaskscenterweight' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksCenterWeight'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksCenterWeight'] }, + 'maskgroupbasedcorrmaskmasksdabs' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksDabs'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksDabs'] }, + 'maskgroupbasedcorrmaskmasksfeather' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksFeather'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksFeather'] }, + 'maskgroupbasedcorrmaskmasksflipped' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksFlipped'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksFlipped'] }, + 'maskgroupbasedcorrmaskmasksflow' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksFlow'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksFlow'] }, + 'maskgroupbasedcorrmaskmasksfullx' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksFullX'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksFullX'] }, + 'maskgroupbasedcorrmaskmasksfully' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksFullY'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksFullY'] }, + 'maskgroupbasedcorrmaskmasksinputdigest' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksInputDigest'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksInputDigest'] }, + 'maskgroupbasedcorrmaskmasksleft' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksLeft'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksLeft'] }, + 'maskgroupbasedcorrmaskmasksmaskactive' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskActive'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskActive'] }, + 'maskgroupbasedcorrmaskmasksmaskblendmode' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskBlendMode'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskBlendMode'] }, + 'maskgroupbasedcorrmaskmasksmaskdigest' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskDigest'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskDigest'] }, + 'maskgroupbasedcorrmaskmasksmaskinverted' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskInverted'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskInverted'] }, + 'maskgroupbasedcorrmaskmasksmaskname' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskName'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskName'] }, + 'maskgroupbasedcorrmaskmasksmasksubtype' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskSubType'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskSubType'] }, + 'maskgroupbasedcorrmaskmasksmasksyncid' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskSyncID'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskSyncID'] }, + 'maskgroupbasedcorrmaskmasksmaskversion' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskVersion'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskVersion'] }, + 'maskgroupbasedcorrmaskmasksmidpoint' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMidpoint'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMidpoint'] }, + 'maskgroupbasedcorrmaskmasksorigin' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksOrigin'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksOrigin'] }, + 'maskgroupbasedcorrmaskmasksperimetervalue' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksPerimeterValue'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksPerimeterValue'] }, + 'maskgroupbasedcorrmaskmasksradius' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksRadius'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksRadius'] }, + 'maskgroupbasedcorrmaskmasksreferencepoint' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksReferencePoint'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksReferencePoint'] }, + 'maskgroupbasedcorrmaskmasksright' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksRight'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksRight'] }, + 'maskgroupbasedcorrmaskmasksroundness' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksRoundness'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksRoundness'] }, + 'maskgroupbasedcorrmaskmaskssizex' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksSizeX'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksSizeX'] }, + 'maskgroupbasedcorrmaskmaskssizey' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksSizeY'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksSizeY'] }, + 'maskgroupbasedcorrmaskmaskstop' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksTop'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksTop'] }, + 'maskgroupbasedcorrmaskmasksubtype' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskSubType'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskSubType'] }, + 'maskgroupbasedcorrmaskmasksvalue' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskValue'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksMaskValue'] }, + 'maskgroupbasedcorrmaskmasksversion' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksVersion'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksVersion'] }, + 'maskgroupbasedcorrmaskmaskswhat' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksWhat'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksWhat'] }, + 'maskgroupbasedcorrmaskmaskswholeimagearea' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksWholeImageArea'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksWholeImageArea'] }, + 'maskgroupbasedcorrmaskmasksx' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksX'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksX'] }, + 'maskgroupbasedcorrmaskmasksy' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksY'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksY'] }, + 'maskgroupbasedcorrmaskmasksyncid' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskSyncID'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskSyncID'] }, + 'maskgroupbasedcorrmaskmaskszerox' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksZeroX'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksZeroX'] }, + 'maskgroupbasedcorrmaskmaskszeroy' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksZeroY'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMasksZeroY'] }, + 'maskgroupbasedcorrmaskmaskversion' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskVersion'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskVersion'] }, + 'maskgroupbasedcorrmaskmidpoint' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMidpoint'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMidpoint'] }, + 'maskgroupbasedcorrmaskorigin' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksOrigin'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksOrigin'] }, + 'maskgroupbasedcorrmaskperimetervalue' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksPerimeterValue'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksPerimeterValue'] }, + 'maskgroupbasedcorrmaskradius' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksRadius'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksRadius'] }, + 'maskgroupbasedcorrmaskrange' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMask'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMask'] }, + 'maskgroupbasedcorrmaskrangeareamodels' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels'] }, + 'maskgroupbasedcorrmaskrangeareamodelscolorsampleinfo' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'] }, + 'maskgroupbasedcorrmaskrangeareamodelscomponents' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents'] }, + 'maskgroupbasedcorrmaskrangecoloramount' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount'] }, + 'maskgroupbasedcorrmaskrangedepthfeather' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather'] }, + 'maskgroupbasedcorrmaskrangedepthmax' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax'] }, + 'maskgroupbasedcorrmaskrangedepthmin' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin'] }, + 'maskgroupbasedcorrmaskrangeinvert' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert'] }, + 'maskgroupbasedcorrmaskrangelumfeather' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather'] }, + 'maskgroupbasedcorrmaskrangeluminancedepthsampleinfo' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo'] }, + 'maskgroupbasedcorrmaskrangelummax' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax'] }, + 'maskgroupbasedcorrmaskrangelummin' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin'] }, + 'maskgroupbasedcorrmaskrangelumrange' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange'] }, + 'maskgroupbasedcorrmaskrangesampletype' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType'] }, + 'maskgroupbasedcorrmaskrangetype' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskType'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskType'] }, + 'maskgroupbasedcorrmaskrangeversion' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion'] }, + 'maskgroupbasedcorrmaskreferencepoint' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksReferencePoint'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksReferencePoint'] }, + 'maskgroupbasedcorrmaskright' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksRight'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksRight'] }, + 'maskgroupbasedcorrmaskroundness' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksRoundness'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksRoundness'] }, + 'maskgroupbasedcorrmasksizex' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksSizeX'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksSizeX'] }, + 'maskgroupbasedcorrmasksizey' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksSizeY'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksSizeY'] }, + 'maskgroupbasedcorrmasktop' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksTop'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksTop'] }, + 'maskgroupbasedcorrmaskvalue' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskValue'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksMaskValue'] }, + 'maskgroupbasedcorrmaskversion' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksVersion'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksVersion'] }, + 'maskgroupbasedcorrmaskwhat' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksWhat'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksWhat'] }, + 'maskgroupbasedcorrmaskwholeimagearea' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksWholeImageArea'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksWholeImageArea'] }, + 'maskgroupbasedcorrmaskx' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksX'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksX'] }, + 'maskgroupbasedcorrmasky' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksY'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksY'] }, + 'maskgroupbasedcorrmaskzerox' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksZeroX'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksZeroX'] }, + 'maskgroupbasedcorrmaskzeroy' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksZeroY'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionMasksZeroY'] }, + 'maskgroupbasedcorrmoire' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalMoire'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalMoire'] }, + 'maskgroupbasedcorrrangemask' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMask'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMask'] }, + 'maskgroupbasedcorrrangemaskareamodels' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModels'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModels'] }, + 'maskgroupbasedcorrrangemaskareamodelscolorsampleinfo' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'] }, + 'maskgroupbasedcorrrangemaskareamodelscomponents' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents'] }, + 'maskgroupbasedcorrrangemaskcoloramount' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskColorAmount'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskColorAmount'] }, + 'maskgroupbasedcorrrangemaskdepthfeather' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskDepthFeather'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskDepthFeather'] }, + 'maskgroupbasedcorrrangemaskdepthmax' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskDepthMax'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskDepthMax'] }, + 'maskgroupbasedcorrrangemaskdepthmin' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskDepthMin'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskDepthMin'] }, + 'maskgroupbasedcorrrangemaskinvert' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskInvert'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskInvert'] }, + 'maskgroupbasedcorrrangemasklumfeather' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskLumFeather'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskLumFeather'] }, + 'maskgroupbasedcorrrangemaskluminancedepthsampleinfo' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo'] }, + 'maskgroupbasedcorrrangemasklummax' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskLumMax'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskLumMax'] }, + 'maskgroupbasedcorrrangemasklummin' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskLumMin'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskLumMin'] }, + 'maskgroupbasedcorrrangemasklumrange' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskLumRange'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskLumRange'] }, + 'maskgroupbasedcorrrangemasksampletype' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskSampleType'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskSampleType'] }, + 'maskgroupbasedcorrrangemasktype' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskType'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskType'] }, + 'maskgroupbasedcorrrangemaskversion' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskVersion'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsCorrectionRangeMaskVersion'] }, + 'maskgroupbasedcorrsaturation' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalSaturation'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalSaturation'] }, + 'maskgroupbasedcorrshadows2012' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalShadows2012'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalShadows2012'] }, + 'maskgroupbasedcorrsharpness' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalSharpness'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalSharpness'] }, + 'maskgroupbasedcorrtemperature' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalTemperature'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalTemperature'] }, + 'maskgroupbasedcorrtexture' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalTexture'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalTexture'] }, + 'maskgroupbasedcorrtint' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalTint'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalTint'] }, + 'maskgroupbasedcorrtoninghue' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalToningHue'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalToningHue'] }, + 'maskgroupbasedcorrtoningsaturation' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalToningSaturation'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalToningSaturation'] }, + 'maskgroupbasedcorrwhat' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsWhat'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsWhat'] }, + 'maskgroupbasedcorrwhites2012' => { 500 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalWhites2012'], 502 => [\'MaskGroupBasedCorrections','MaskGroupBasedCorrectionsLocalWhites2012'] }, + 'masterdocumentid' => { 131 => 0xb9 }, + 'mastergain' => { 257 => 0x50 }, + 'materialsample' => { 118 => 'MaterialSample' }, + 'materialsampleid' => { 118 => [\'MaterialSample','MaterialSampleMaterialSampleID'] }, + 'matrixmetering' => { 298 => '50.1', 300 => '48.1', 301 => '48.1', 310 => '48.1', 311 => '48.1', 313 => 0x233, 314 => 0x233, 315 => 0x24b }, + 'matrixselectk' => { 138 => 0x91b }, + 'matrixselectthreshold' => { 138 => 0x91a }, + 'matrixselectthreshold1' => { 138 => 0x91e }, + 'matrixselectthreshold2' => { 138 => 0x91f }, + 'matrixstructure' => { 399 => 0xa }, + 'maxaperture' => { 34 => 0x1a, 138 => 0x3f9, 144 => 0x6103, 163 => 'MaxAperture', 181 => 0x17, 187 => 0x49c5, 232 => 0x36, 318 => 0x20a, 366 => '14.1' }, + 'maxapertureatmaxfocal' => { 127 => 0x1407, 163 => 'MaxApertureAtMaxFocal', 226 => 0xb, 227 => 0x10, 228 => 0x11, 232 => 0x12, 318 => 0x206 }, + 'maxapertureatminfocal' => { 127 => 0x1406, 226 => 0xa, 227 => 0xf, 228 => 0x10, 232 => 0x11, 318 => 0x205 }, + 'maxaperturevalue' => { 119 => 0x9205, 384 => 0x414, 506 => 'MaxApertureValue' }, + 'maxavailheight' => { 514 => 'MaxAvailHeight' }, + 'maxavailwidth' => { 514 => 'MaxAvailWidth' }, + 'maxcontinuousrelease' => { 297 => 0xb, 298 => 0xc, 300 => '12.1', 301 => '12.1', 306 => 0xc, 307 => 0xb, 310 => '12.1', 311 => '12.1', 313 => 0x3d, 314 => 0x3d, 315 => 0x3d }, + 'maxfaces' => { 321 => 0x1202 }, + 'maxfocallength' => { 7 => 0xe6, 8 => 0x10, 9 => 0x1ab, 10 => 0x13, 11 => 0x115, 12 => 0x13, 13 => 0x153, 14 => 0xda, 16 => 0xfa, 17 => 0xee, 18 => 0x103, 19 => 0x95, 20 => 0xea, 21 => 0x157, 22 => 0xee, 23 => 0xec, 24 => 0x12b, 25 => 0x165, 26 => 0x16a, 27 => 0x188, 28 => 0x116, 29 => 0x18d, 34 => 0x17, 127 => 0x1405, 163 => 'MaxFocalLength', 226 => 0x9, 227 => 0xe, 228 => 0xf, 232 => 0x10, 318 => 0x208, 453 => 0x127c, 454 => 0x1138, 455 => 0x330, 456 => 0x330, 457 => 0x30e }, + 'maximumdensityrange' => { 133 => 0x8c }, + 'maxpagesize' => { 534 => 'MaxPageSize' }, + 'maxpagesizeh' => { 534 => [\'MaxPageSize','MaxPageSizeH'] }, + 'maxpagesizeunit' => { 534 => [\'MaxPageSize','MaxPageSizeUnit'] }, + 'maxpagesizew' => { 534 => [\'MaxPageSize','MaxPageSizeW'] }, + 'maxpixelvaluethreshold' => { 138 => 0xc7d }, + 'maxsamplevalue' => { 119 => 0x119 }, + 'maxstorage' => { 524 => 'maxstorage' }, + 'mb-d10batteries' => { 297 => '12.6' }, + 'mb-d10batterytype' => { 306 => '13.3' }, + 'mb-d11batterytype' => { 307 => '2.3' }, + 'mb-d12batterytype' => { 310 => '3.2' }, + 'mb-d80batteries' => { 308 => '6.5' }, + 'mb-d80batterytype' => { 312 => '3.2' }, + 'mcuversion' => { 226 => 0xc, 227 => 0x11, 228 => 0x12, 232 => 0x13 }, + 'md5digest' => { 164 => 'zmd5' }, + 'mditemfindercomment' => { 173 => 'MDItemFinderComment' }, + 'mditemfscreationdate' => { 173 => 'MDItemFSCreationDate' }, + 'mditemfslabel' => { 173 => 'MDItemFSLabel' }, + 'mditemusertags' => { 173 => 'MDItemUserTags' }, + 'meal' => { 521 => 'meal' }, + 'measuredev' => { 28 => 0x9, 77 => 0x3, 97 => 0x1814, 156 => 'MeasuredEV' }, + 'measuredev2' => { 13 => 0x8, 28 => 0x8, 77 => 0x17 }, + 'measuredev3' => { 13 => 0x9 }, + 'measuredlv' => { 187 => 0x690, 335 => 0x312, 338 => 0x312, 339 => 0x312, 342 => 0x3407 }, + 'measuredrggb' => { 65 => 0x1 }, + 'measuredrggbdata' => { 44 => 0x287 }, + 'measurementaccuracy' => { 118 => [\'MeasurementOrFact','MeasurementOrFactMeasurementAccuracy'] }, + 'measurementdeterminedby' => { 118 => [\'MeasurementOrFact','MeasurementOrFactMeasurementDeterminedBy'] }, + 'measurementdetermineddate' => { 118 => [\'MeasurementOrFact','MeasurementOrFactMeasurementDeterminedDate'] }, + 'measurementid' => { 118 => [\'MeasurementOrFact','MeasurementOrFactMeasurementID'] }, + 'measurementmethod' => { 118 => [\'MeasurementOrFact','MeasurementOrFactMeasurementMethod'] }, + 'measurementorfact' => { 118 => 'MeasurementOrFact' }, + 'measurementremarks' => { 118 => [\'MeasurementOrFact','MeasurementOrFactMeasurementRemarks'] }, + 'measurementtype' => { 118 => [\'MeasurementOrFact','MeasurementOrFactMeasurementType'] }, + 'measurementunit' => { 118 => [\'MeasurementOrFact','MeasurementOrFactMeasurementUnit'] }, + 'measurementvalue' => { 118 => [\'MeasurementOrFact','MeasurementOrFactMeasurementValue'] }, + 'measuretype' => { 485 => 'MeasureType' }, + 'mechanicalshuttercount' => { 234 => 0x37 }, + 'mediaclassprimaryid' => { 179 => 'WM/MediaClassPrimaryID' }, + 'mediaclasssecondaryid' => { 179 => 'WM/MediaClassSecondaryID' }, + 'mediaconstraints' => { 327 => 'MediaConstraints' }, + 'mediacreatedate' => { 396 => 0x1 }, + 'mediaeventiddate' => { 490 => 'MediaEventIdDate' }, + 'mediamodifydate' => { 396 => 0x2 }, + 'mediasummarycode' => { 327 => 'MediaSummaryCode' }, + 'mediatype' => { 392 => 'stik' }, + 'memoaudioquality' => { 84 => 0x812 }, + 'memorycardconfiguration' => { 433 => 0x16 }, + 'memorycardnumber' => { 211 => 0x2 }, + 'menubuttondisplayposition' => { 82 => 0xb, 85 => 0xa, 86 => 0xb, 89 => 0xb }, + 'menubuttonreturn' => { 90 => 0xb }, + 'menumonitorofftime' => { 297 => '26.1', 298 => '22.1', 300 => '22.1', 301 => '22.1', 305 => '21.1', 306 => '8.2', 307 => '21.1', 310 => '22.1', 311 => '22.1', 312 => '22.1', 313 => 0x35, 314 => 0x35, 315 => 0x35 }, + 'metadataauthority' => { 514 => 'metadataAuthority' }, + 'metadataauthorityidentifier' => { 514 => [\'metadataAuthority','metadataAuthorityIdentifier'] }, + 'metadataauthorityname' => { 514 => [\'metadataAuthority','metadataAuthorityName'] }, + 'metadatadate' => { 527 => 'MetadataDate' }, + 'metadataeditingsoftware' => { 119 => 0xa43c }, + 'metadatalastedited' => { 514 => 'metadataLastEdited' }, + 'metadatalasteditor' => { 514 => 'metadataLastEditor' }, + 'metadatalasteditoridentifier' => { 514 => [\'metadataLastEditor','metadataLastEditorIdentifier'] }, + 'metadatalasteditorname' => { 514 => [\'metadataLastEditor','metadataLastEditorName'] }, + 'metadatamoddate' => { 529 => 'metadataModDate' }, + 'metaversion' => { 448 => 0x34 }, + 'meterinfo1row1' => { 441 => 0x0, 442 => 0x0 }, + 'meterinfo1row2' => { 441 => 0x6c, 442 => 0x5a }, + 'meterinfo1row3' => { 441 => 0xd8, 442 => 0xb4 }, + 'meterinfo1row4' => { 441 => 0x144, 442 => 0x10e }, + 'meterinfo1row5' => { 441 => 0x1b0, 442 => 0x168 }, + 'meterinfo1row6' => { 441 => 0x21c, 442 => 0x1c2 }, + 'meterinfo1row7' => { 441 => 0x288, 442 => 0x21c }, + 'meterinfo2row1' => { 441 => 0x2f4, 442 => 0x276 }, + 'meterinfo2row2' => { 441 => 0x378, 442 => 0x2e4 }, + 'meterinfo2row3' => { 441 => 0x3fc, 442 => 0x352 }, + 'meterinfo2row4' => { 441 => 0x480, 442 => 0x3c0 }, + 'meterinfo2row5' => { 441 => 0x504, 442 => 0x42e }, + 'meterinfo2row6' => { 441 => 0x588, 442 => 0x49c }, + 'meterinfo2row7' => { 441 => 0x60c, 442 => 0x50a }, + 'meterinfo2row8' => { 441 => 0x690, 442 => 0x578 }, + 'meterinfo2row9' => { 441 => 0x714, 442 => 0x5e6 }, + 'metering' => { 299 => '6.1' }, + 'meteringmode' => { 34 => 0x11, 119 => 0x9207, 140 => 0x1c, 181 => 0x7, 182 => 0x25, 184 => 0x12, 317 => 0x202, 375 => 0x17, 417 => 0x9, 427 => 0x15, 428 => 0x13, 429 => 0x7, 445 => 0x3, 449 => 0x1174, 450 => 0x1178, 451 => 0x1154, 452 => 0x11d0, 453 => 0x11ac, 454 => 0x1064, 455 => 0x25c, 456 => 0x25c, 457 => 0x24b, 506 => 'MeteringMode' }, + 'meteringmode2' => { 356 => '2.1', 440 => 0x202c }, + 'meteringmode3' => { 356 => '16.1' }, + 'meteringoffscaleindicator' => { 184 => 0x53 }, + 'meteringtime' => { 297 => '22.2', 299 => '3.2', 302 => '17.1', 303 => '18.1', 306 => '7.3', 307 => '18.1', 308 => '3.2', 312 => '19.1' }, + 'micro1version' => { 402 => 0x1f }, + 'micro2version' => { 402 => 0x2d }, + 'microphoneattenuator' => { 239 => 0x34e, 240 => 0x2d2, 241 => 0x2fa, 242 => 0x2fa }, + 'microphonefrequencyresponse' => { 239 => 0x350, 240 => 0x2d4, 241 => 0x2fc, 242 => 0x2fc }, + 'microphonejackpower' => { 239 => 0x376, 240 => 0x2fa, 241 => 0x322, 242 => 0x322 }, + 'microphonesensitivity' => { 239 => 0x34c, 240 => 0x2d0, 241 => 0x2f8, 242 => 0x2f8 }, + 'microvideo' => { 483 => 'MicroVideo' }, + 'microvideooffset' => { 483 => 'MicroVideoOffset' }, + 'microvideopresentationtimestampus' => { 483 => 'MicroVideoPresentationTimestampUs' }, + 'microvideoversion' => { 483 => 'MicroVideoVersion' }, + 'midrangesharpness' => { 251 => 0x3b }, + 'mieversion' => { 164 => '0Vers' }, + 'mime' => { 485 => 'Mime' }, + 'minaperture' => { 34 => 0x1b, 138 => 0x3f8, 163 => 'MinAperture', 366 => '0.2' }, + 'minaperturevalue' => { 384 => 0x415 }, + 'minfocallength' => { 7 => 0xe4, 8 => 0xe, 9 => 0x1a9, 10 => 0x11, 11 => 0x113, 12 => 0x11, 13 => 0x151, 14 => 0xd8, 16 => 0xf8, 17 => 0xec, 18 => 0x101, 19 => 0x93, 20 => 0xe8, 21 => 0x155, 22 => 0xec, 23 => 0xea, 24 => 0x129, 25 => 0x163, 26 => 0x168, 27 => 0x186, 28 => 0x114, 29 => 0x18b, 34 => 0x18, 127 => 0x1404, 163 => 'MinFocalLength', 226 => 0x8, 227 => 0xd, 228 => 0xe, 232 => 0xf, 318 => 0x207, 453 => 0x127a, 454 => 0x1136, 455 => 0x32e, 456 => 0x32e, 457 => 0x30c }, + 'minfocusdistance' => { 366 => 0x3 }, + 'minimumiso' => { 340 => 0xe8 }, + 'minintegrationrows' => { 138 => 0x1874 }, + 'minoltadate' => { 181 => 0x15 }, + 'minoltaimagesize' => { 181 => 0x4, 182 => 0xc, 183 => 0x2, 186 => 0x103 }, + 'minoltamodelid' => { 181 => 0x25 }, + 'minoltaquality' => { 181 => 0x5, 182 => 0xd, 183 => 0x3, 186 => [0x102,0x103] }, + 'minoltatime' => { 181 => 0x16 }, + 'minormodelagedisclosure' => { 327 => 'MinorModelAgeDisclosure' }, + 'minorversion' => { 491 => 'MinorVersion' }, + 'minsamplevalue' => { 119 => 0x118 }, + 'mirrorlockup' => { 82 => 0xc, 83 => 0xc, 84 => 0x60f, 85 => 0xb, 86 => 0xc, 87 => 0x6, 88 => 0x6, 89 => 0xc, 90 => 0x3 }, + 'mobilecountrycode' => { 499 => 'mcc' }, + 'mobilenetworkcode' => { 499 => 'mnc' }, + 'moddate' => { 330 => 'modify-date', 516 => 'ModDate' }, + 'modedialposition' => { 433 => 0x14 }, + 'model' => { 98 => 0x6, 119 => 0x110, 156 => 'Model', 330 => 'Model', 345 => 0x110, 375 => 0x23f, 394 => 'model', 400 => ['@mod','CNMN','cmnm',"\xa9mdl","\xa9mod"], 417 => 0x84, 485 => 'Model', 518 => 'model', 525 => 'Model' }, + 'modelage' => { 514 => 'ModelAge' }, + 'modelid' => { 316 => 0x0 }, + 'modelingflash' => { 297 => '21.4', 298 => '31.1', 300 => '31.1', 301 => '31.1', 306 => '26.4', 307 => '30.2', 308 => '7.4', 309 => '30.1', 310 => '31.1', 311 => '31.1', 312 => '31.3', 313 => 0x5d, 314 => 0x5d, 315 => 0x5d }, + 'modelreleaseid' => { 327 => 'ModelReleaseID' }, + 'modelreleasestatus' => { 327 => 'ModelReleaseStatus' }, + 'modelreleaseyear' => { 463 => 0x52, 464 => 0x46, 465 => 0x53 }, + 'modeltiepoint' => { 119 => 0x8482 }, + 'modeltransform' => { 119 => 0x85d8 }, + 'modelyear' => { 518 => 'modelYear' }, + 'modificationdate' => { 519 => 'modificationDate' }, + 'modifiedcolortemp' => { 66 => 0x9 }, + 'modifieddigitalgain' => { 66 => 0xb }, + 'modifiedparamflag' => { 58 => 0x1 }, + 'modifiedpicturestyle' => { 66 => 0xa }, + 'modifiedsaturation' => { 317 => 0x504 }, + 'modifiedsensorbluelevel' => { 66 => 0x5 }, + 'modifiedsensorredlevel' => { 66 => 0x4 }, + 'modifiedsharpness' => { 66 => 0x2 }, + 'modifiedsharpnessfreq' => { 66 => 0x3 }, + 'modifiedtonecurve' => { 66 => 0x1 }, + 'modifiedwhitebalance' => { 66 => 0x8 }, + 'modifiedwhitebalanceblue' => { 66 => 0x7 }, + 'modifiedwhitebalancered' => { 66 => 0x6 }, + 'modifydate' => { 119 => 0x132, 157 => 'ModifyDate', 326 => 'ModDate', 328 => 'tIME', 391 => 'ModDate', 397 => 0x2, 527 => 'ModifyDate' }, + 'moirefilter' => { 119 => 0xfe58, 500 => 'MoireFilter', 502 => 'MoireFilter' }, + 'monitorbrightness' => { 239 => 0x69a, 240 => 0x59a, 241 => 0x5ca, 242 => 0x5e2 }, + 'monitordisplayoff' => { 184 => 0x4c }, + 'monitormatrix' => { 138 => 0x8fc }, + 'monitorofftime' => { 297 => '18.2', 299 => '3.1', 308 => '3.1' }, + 'monochromecolor' => { 317 => 0x53b }, + 'monochromecontrast' => { 109 => 0x3c }, + 'monochromefiltereffect' => { 103 => 0x20307, 109 => 0x3a, 340 => 0xac, 375 => 0x73 }, + 'monochromegraineffect' => { 340 => 0xd2 }, + 'monochromelinear' => { 109 => 0x3d }, + 'monochromeoutputhighlightpoint' => { 109 => 0x41 }, + 'monochromeoutputshadowpoint' => { 109 => 0x42 }, + 'monochromeprofilesettings' => { 317 => 0x537 }, + 'monochromerawhighlight' => { 109 => 0x7a }, + 'monochromerawhighlightpoint' => { 109 => 0x3f }, + 'monochromerawshadow' => { 109 => 0x83 }, + 'monochromerawshadowpoint' => { 109 => 0x40 }, + 'monochromesharpness' => { 109 => 0x3e }, + 'monochrometoning' => { 375 => 0x74 }, + 'monochrometoningeffect' => { 103 => 0x20306, 109 => 0x3b }, + 'monochromeunsharpmaskfineness' => { 109 => 0xb2 }, + 'monochromeunsharpmaskstrength' => { 109 => 0xb0 }, + 'monochromeunsharpmaskthreshold' => { 109 => 0xb4 }, + 'monochromevignetting' => { 317 => 0x53a }, + 'monthdaycreated' => { 140 => 0x12, 149 => 0xe }, + 'mood' => { 179 => 'WM/Mood' }, + 'moonphase' => { 401 => 0x12, 402 => 0x43, 403 => 0x4c }, + 'morepermissions' => { 498 => 'morePermissions' }, + 'motionphotovideo' => { 395 => 'mpvd' }, + 'motionsensitivity' => { 401 => 0x29, 403 => 0x60 }, + 'movementcount' => { 392 => "\xa9mvc" }, + 'movementname' => { 392 => "\xa9mvn" }, + 'movementnumber' => { 392 => "\xa9mvi" }, + 'movieactived-lighting' => { 238 => 0x238, 239 => 0x334, 240 => 0x2b8, 241 => 0x2e0, 242 => 0x2e0 }, + 'movieaelockbuttonassignment' => { 310 => '40.1' }, + 'movieaf-onbutton' => { 313 => 0xcb, 314 => 0xcb, 315 => 0xcb }, + 'movieafareamode' => { 239 => 0x342, 240 => 0x2c6, 241 => 0x2ee, 242 => 0x2ee, 313 => 0x203, 314 => 0x203, 315 => 0x21b }, + 'movieafspeed' => { 313 => 0xdd, 314 => 0xdd, 315 => 0xdd }, + 'movieafspeedapply' => { 313 => 0xdf, 314 => 0xdf, 315 => 0xdf }, + 'movieaftrackingsensitivity' => { 313 => 0xe1, 314 => 0xe1, 315 => 0xe1 }, + 'movieaperturelock' => { 313 => 0x259, 314 => 0x259, 315 => 0x271 }, + 'movieautodistortioncontrol' => { 238 => 0x242 }, + 'moviediffractioncompensation' => { 238 => 0x241 }, + 'moviedxcropalert' => { 239 => 0x377, 240 => 0x2fb, 241 => 0x323, 242 => 0x323 }, + 'movieelectronicvr' => { 239 => 0x348, 240 => 0x2cc, 241 => 0x2f4, 242 => 0x2f4 }, + 'movieevfgrid' => { 313 => 0x21d, 314 => 0x21d, 315 => 0x235 }, + 'movieflickerreduction' => { 239 => 0x33c, 240 => 0x2c0, 241 => 0x2e8, 242 => 0x2e8 }, + 'moviefocusmode' => { 238 => 0x248, 239 => 0x340, 240 => 0x2c4, 241 => 0x2ec, 242 => 0x2ec }, + 'moviefocuspointlock' => { 313 => 0x226, 314 => 0x226, 315 => 0x23e }, + 'movieframerate' => { 238 => 0x1f8, 239 => 0x374, 240 => 0x2f4, 241 => 0x31c, 242 => 0x31c }, + 'movieframesize' => { 238 => 0x1f6, 239 => 0x372, 240 => 0x2f2, 241 => 0x31a, 242 => 0x31a }, + 'moviefunc1button' => { 300 => '41.1', 301 => '41.1', 311 => '41.1', 313 => 0xc3, 314 => 0xc3, 315 => 0xc3 }, + 'moviefunc2button' => { 313 => 0xc7, 314 => 0xc7, 315 => 0xc7 }, + 'moviefunc3button' => { 314 => 0x127, 315 => 0x13f }, + 'moviefunctionbutton' => { 298 => '41.1', 310 => '41.1' }, + 'moviefunctionbuttonplusdials' => { 298 => '52.1' }, + 'moviehighisonoisereduction' => { 238 => 0x23c, 239 => 0x336, 240 => 0x2ba, 241 => 0x2e2, 242 => 0x2e2 }, + 'moviehighlightdisplaythreshold' => { 313 => 0x215, 314 => 0x215, 315 => 0x22d }, + 'moviehighreszoom' => { 239 => 0x380, 241 => 0x32c, 242 => 0x32c }, + 'movieimagearea' => { 239 => 0x2da, 240 => 0x25c, 241 => 0x286, 242 => 0x286 }, + 'movieisoautocontrolmanualmode' => { 239 => 0x2e8, 240 => 0x26a, 241 => 0x294, 242 => 0x294 }, + 'movieisoautohilimit' => { 239 => 0x2e6, 240 => 0x268, 241 => 0x292, 242 => 0x292 }, + 'movieisoautomanualmode' => { 238 => 0x204, 239 => 0x2ea, 240 => 0x26c, 241 => 0x296, 242 => 0x296 }, + 'movielenscontrolring' => { 313 => 0xd7, 314 => 0xd7, 315 => 0xd7 }, + 'moviemeteringmode' => { 239 => 0x33e, 240 => 0x2c2, 241 => 0x2ea, 242 => 0x2ea }, + 'moviemidtonedisplayrange' => { 313 => 0x219, 314 => 0x219, 315 => 0x231 }, + 'moviemidtonedisplayvalue' => { 313 => 0x217, 314 => 0x217, 315 => 0x22f }, + 'moviemultiselector' => { 313 => 0xd9, 314 => 0xcf, 315 => 0xcf }, + 'moviepreviewbutton' => { 298 => '41.2', 300 => '41.2', 301 => '41.2', 310 => '41.2', 311 => '41.2' }, + 'moviepreviewbuttonplusdials' => { 298 => '52.2' }, + 'movierecordbuttonplaybackmode' => { 313 => 0x1b5, 314 => 0x1b5, 315 => 0x1cd }, + 'movieshutterbutton' => { 298 => '38.3', 300 => '38.3', 301 => '38.3', 310 => '38.3', 311 => '38.3' }, + 'movieshutterspeedlock' => { 313 => 0x225, 314 => 0x225, 315 => 0x23d }, + 'movieslowmotion' => { 238 => 0x1fa }, + 'moviesoundrecording' => { 239 => 0x34a, 240 => 0x2ce, 241 => 0x2f6, 242 => 0x2f6 }, + 'moviesubjectdetection' => { 239 => 0x378, 240 => 0x2fc, 241 => 0x324, 242 => 0x324 }, + 'moviesubselectorassignment' => { 298 => '48.2' }, + 'moviesubselectorassignmentplusdials' => { 298 => '53.1' }, + 'movietonemap' => { 240 => 0x2ec, 241 => 0x314, 242 => 0x314 }, + 'movietype' => { 223 => 0x2ca, 238 => 0x1fe, 239 => 0x2e4, 240 => 0x266, 241 => 0x290, 242 => 0x290 }, + 'movievibrationreduction' => { 238 => 0x24e }, + 'movievibrationreductionsameasphoto' => { 238 => 0x24f }, + 'movievignettecontrol' => { 238 => 0x23e, 239 => 0x1b0, 240 => 0x1a0, 241 => 0x1b4, 242 => 0x1b4 }, + 'movievignettecontrolsameasphoto' => { 238 => 0x240 }, + 'movievrmode' => { 239 => 0x344, 240 => 0x2c8, 241 => 0x2f0, 242 => 0x2f0 }, + 'moviezebrapattern' => { 313 => 0x213, 314 => 0x213, 315 => 0x22b }, + 'multiburstimageheight' => { 440 => 0x1002 }, + 'multiburstimagewidth' => { 440 => 0x1001 }, + 'multiburstmode' => { 440 => 0x1000 }, + 'multicontrollerwhilemetering' => { 84 => 0x517 }, + 'multiexposure' => { 68 => 0x1, 340 => 0xb4 }, + 'multiexposureautogain' => { 244 => 0x3 }, + 'multiexposurecontrol' => { 68 => 0x2 }, + 'multiexposuremode' => { 244 => 0x1, 245 => 0x1 }, + 'multiexposureoverlaymode' => { 245 => 0x3 }, + 'multiexposureshots' => { 68 => 0x3, 239 => 0x9a, 240 => 0x8e, 241 => 0x9c, 242 => 0x9c, 244 => 0x2, 245 => 0x2 }, + 'multiframenoisereduction' => { 429 => 0x35, 440 => 0x200b, 445 => 0x15 }, + 'multiframenreffect' => { 440 => 0x2023 }, + 'multifunctionlock' => { 84 => 0x70f }, + 'multipleexposuremode' => { 239 => 0x98, 240 => 0x8c, 241 => 0x9a, 242 => 0x9a, 321 => 0x101c }, + 'multipleexposureset' => { 356 => '10.1' }, + 'multisample' => { 257 => 0x40 }, + 'multiselector' => { 297 => '9.4', 298 => '10.3', 300 => '10.3', 301 => '10.3', 306 => '27.4', 310 => '10.3', 311 => '10.3' }, + 'multiselectorliveview' => { 297 => '4.3', 300 => '37.1', 301 => '37.1', 310 => '37.1', 311 => '37.1' }, + 'multiselectorliveviewmode' => { 271 => 0x18c2 }, + 'multiselectorplaybackmode' => { 297 => ['13.5','9.2'], 298 => '10.2', 301 => '10.2', 306 => '27.2', 310 => '10.2', 311 => '10.2', 313 => 0xb3, 314 => 0xb3, 315 => 0xb3 }, + 'multiselectorshootmode' => { 297 => '9.1', 298 => '10.1', 300 => '10.1', 301 => '10.1', 306 => '27.1', 310 => '10.1', 311 => '10.1', 313 => 0xaf, 314 => 0xaf, 315 => 0xaf }, + 'multishot' => { 345 => 0x121 }, + 'mute' => { 394 => 'player.movie.audio.mute' }, + 'mycolormode' => { 69 => 0x2 }, + 'name' => { 400 => 'name', 500 => 'Name', 502 => 'Name' }, + 'narrator' => { 392 => "\xa9nrt" }, + 'nationalcatalognumber' => { 519 => 'nationalCatalogNumber' }, + 'nativedigest' => { 506 => 'NativeDigest', 525 => 'NativeDigest' }, + 'ndfilter' => { 77 => 0x1c, 317 => 0x204, 407 => 0x1019 }, + 'near' => { 485 => 'Near' }, + 'nefbitdepth' => { 234 => 0xe22 }, + 'nefcompression' => { 234 => 0x93, 235 => 0xa }, + 'neflinearizationtable' => { 234 => 0x96 }, + 'negativecachelargepreviewsize' => { 500 => 'NegativeCacheLargePreviewSize', 502 => 'NegativeCacheLargePreviewSize' }, + 'negativecachemaximumsize' => { 500 => 'NegativeCacheMaximumSize', 502 => 'NegativeCacheMaximumSize' }, + 'negativecachepath' => { 500 => 'NegativeCachePath', 502 => 'NegativeCachePath' }, + 'neutraldensityfactor' => { 497 => 'NeutralDensityFactor' }, + 'neutraldensityfilter' => { 375 => 0x88 }, + 'neutraloutputhighlightpoint' => { 109 => 0x2f }, + 'neutraloutputshadowpoint' => { 109 => 0x30 }, + 'neutralrawcolortone' => { 109 => 0x28 }, + 'neutralrawcontrast' => { 109 => 0x2a }, + 'neutralrawhighlight' => { 109 => 0x78 }, + 'neutralrawhighlightpoint' => { 109 => 0x2d }, + 'neutralrawlinear' => { 109 => 0x2b }, + 'neutralrawsaturation' => { 109 => 0x29 }, + 'neutralrawshadow' => { 109 => 0x81 }, + 'neutralrawshadowpoint' => { 109 => 0x2e }, + 'neutralrawsharpness' => { 109 => 0x2c }, + 'neutralunsharpmaskfineness' => { 109 => 0xa6 }, + 'neutralunsharpmaskthreshold' => { 109 => 0xa8 }, + 'neutraunsharpmaskstrength' => { 109 => 0xa4 }, + 'newlensdata' => { 232 => 0x2f }, + 'newrawimagedigest' => { 119 => 0xc7a7 }, + 'newsphotoversion' => { 133 => 0x0 }, + 'nickname' => { 527 => 'Nickname' }, + 'nikoncapturedata' => { 234 => 0xe01 }, + 'nikoncaptureeditversions' => { 234 => 0xe13 }, + 'nikoncaptureoffsets' => { 234 => 0xe0e }, + 'nikoncaptureoutput' => { 234 => 0xe1e }, + 'nikoncaptureversion' => { 234 => 0xe09 }, + 'nikoniccprofile' => { 234 => 0xe1d }, + 'nikonimagesize' => { 268 => '723.1', 269 => '732.1', 277 => 0x2c4 }, + 'nikonmeteringmode' => { 199 => 0x17, 238 => 0x146, 248 => 0x214 }, + 'nikonsettings' => { 234 => 0x4e }, + 'noisefilter' => { 317 => 0x527 }, + 'noiseprofile' => { 119 => 0xc761 }, + 'noisereduction' => { 127 => [0x100b,0x100e], 182 => 0xb0, 183 => 0x60, 184 => 0x3f, 234 => 0x95, 290 => 0x753dcbc0, 291 => 0x17, 317 => 0x50a, 322 => 0x103a, 340 => 0x2d, 375 => 0x49, 407 => 0x100f, 409 => 0x2a }, + 'noisereduction2' => { 321 => 0x1010 }, + 'noisereductionapplied' => { 119 => 0xc6f7 }, + 'noisereductionintensity' => { 291 => 0x9 }, + 'noisereductionmethod' => { 291 => 0x11 }, + 'noisereductionmode' => { 476 => 0x801e }, + 'noisereductionparametersatcapture' => { 138 => 0xe73 }, + 'noisereductionparameterscamera' => { 138 => 0xe72 }, + 'noisereductionparametershost3mp' => { 138 => 0xe71 }, + 'noisereductionparametershost6mp' => { 138 => 0xe70 }, + 'noisereductionparametershostrgb' => { 138 => 0xe6f }, + 'noisereductionparameterskhufu3mp' => { 138 => 0xe65 }, + 'noisereductionparameterskhufu6mp' => { 138 => 0xe64 }, + 'noisereductionparameterskhufurgb' => { 138 => 0xe63 }, + 'noisereductionparams' => { 345 => 0x1b }, + 'noisereductionsharpness' => { 291 => 0xd }, + 'noisereductionstrength' => { 340 => 0xd6 }, + 'noisereductionvalue' => { 476 => 0x8027 }, + 'nomemorycard' => { 297 => '22.1', 298 => '4.2', 299 => '0.3', 302 => '2.4', 303 => '3.2', 304 => '3.2', 306 => '33.7', 307 => '3.2', 308 => '0.3', 310 => '4.2', 312 => '4.5' }, + 'nominalmaxaperture' => { 366 => 0xa }, + 'nominalminaperture' => { 366 => '10.1' }, + 'noncpulens10focallength' => { 239 => 0x6c6, 242 => 0x620 }, + 'noncpulens10maxaperture' => { 239 => 0x6ee, 242 => 0x670 }, + 'noncpulens11focallength' => { 239 => 0x6c8, 242 => 0x624 }, + 'noncpulens11maxaperture' => { 239 => 0x6f0, 242 => 0x674 }, + 'noncpulens12focallength' => { 239 => 0x6ca, 242 => 0x628 }, + 'noncpulens12maxaperture' => { 239 => 0x6f2, 242 => 0x678 }, + 'noncpulens13focallength' => { 239 => 0x6cc, 242 => 0x62c }, + 'noncpulens13maxaperture' => { 239 => 0x6f4, 242 => 0x67c }, + 'noncpulens14focallength' => { 239 => 0x6ce, 242 => 0x630 }, + 'noncpulens14maxaperture' => { 239 => 0x6f6, 242 => 0x680 }, + 'noncpulens15focallength' => { 239 => 0x6d0, 242 => 0x634 }, + 'noncpulens15maxaperture' => { 239 => 0x6f8, 242 => 0x684 }, + 'noncpulens16focallength' => { 239 => 0x6d2, 242 => 0x638 }, + 'noncpulens16maxaperture' => { 239 => 0x6fa, 242 => 0x688 }, + 'noncpulens17focallength' => { 239 => 0x6d4, 242 => 0x63c }, + 'noncpulens17maxaperture' => { 239 => 0x6fc, 242 => 0x68c }, + 'noncpulens18focallength' => { 239 => 0x6d6, 242 => 0x640 }, + 'noncpulens18maxaperture' => { 239 => 0x6fe, 242 => 0x690 }, + 'noncpulens19focallength' => { 239 => 0x6d8, 242 => 0x644 }, + 'noncpulens19maxaperture' => { 239 => 0x700, 242 => 0x694 }, + 'noncpulens1focallength' => { 239 => 0x6b4, 242 => 0x5fc }, + 'noncpulens1maxaperture' => { 239 => 0x6dc, 242 => 0x64c }, + 'noncpulens20focallength' => { 239 => 0x6da, 242 => 0x648 }, + 'noncpulens20maxaperture' => { 239 => 0x702, 242 => 0x698 }, + 'noncpulens2focallength' => { 239 => 0x6b6, 242 => 0x600 }, + 'noncpulens2maxaperture' => { 239 => 0x6de, 242 => 0x650 }, + 'noncpulens3focallength' => { 239 => 0x6b8, 242 => 0x604 }, + 'noncpulens3maxaperture' => { 239 => 0x6e0, 242 => 0x654 }, + 'noncpulens4focallength' => { 239 => 0x6ba, 242 => 0x608 }, + 'noncpulens4maxaperture' => { 239 => 0x6e2, 242 => 0x658 }, + 'noncpulens5focallength' => { 239 => 0x6bc, 242 => 0x60c }, + 'noncpulens5maxaperture' => { 239 => 0x6e4, 242 => 0x65c }, + 'noncpulens6focallength' => { 239 => 0x6be, 242 => 0x610 }, + 'noncpulens6maxaperture' => { 239 => 0x6e6, 242 => 0x660 }, + 'noncpulens7focallength' => { 239 => 0x6c0, 242 => 0x614 }, + 'noncpulens7maxaperture' => { 239 => 0x6e8, 242 => 0x664 }, + 'noncpulens8focallength' => { 239 => 0x6c2, 242 => 0x618 }, + 'noncpulens8maxaperture' => { 239 => 0x6ea, 242 => 0x668 }, + 'noncpulens9focallength' => { 239 => 0x6c4, 242 => 0x61c }, + 'noncpulens9maxaperture' => { 239 => 0x6ec, 242 => 0x66c }, + 'normallinetime' => { 138 => 0x186a }, + 'normalwhitelevel' => { 41 => 0x32a, 42 => 0x280, 45 => [0x2b8,0x2cf,0x2d3], 46 => 0x569, 47 => 0x1e3, 48 => [0x1fc,0x2dc], 49 => [0x230,0x30e], 50 => 0x31c }, + 'notes' => { 477 => 'Notes', 495 => 'notes' }, + 'npages' => { 534 => 'NPages' }, + 'nullrecord' => { 97 => 0x0 }, + 'numafpoints' => { 352 => 0x2 }, + 'number' => { 519 => 'number' }, + 'numberofbeats' => { 529 => 'numberOfBeats' }, + 'numberoffocuspoints' => { 300 => '1.3', 301 => '1.3', 304 => '0.2', 305 => '0.3', 307 => '0.3' }, + 'numfaceelements' => { 127 => 0x4200 }, + 'numfacepositions' => { 332 => 0x0 }, + 'numindexentries' => { 133 => 0x54 }, + 'numwbentries' => { 346 => 0x0, 347 => 0x0 }, + 'object' => { 519 => 'object' }, + 'objectattributereference' => { 131 => 0x4 }, + 'objectcycle' => { 131 => 0x4b, 495 => 'ObjectCycle' }, + 'objectdescription' => { 518 => 'objectDescription' }, + 'objectdistance' => { 112 => 0x6, 113 => 0x2022 }, + 'objectname' => { 131 => 0x5 }, + 'objectpreviewdata' => { 131 => 0xca }, + 'objectpreviewfileformat' => { 131 => 0xc8 }, + 'objectpreviewfileversion' => { 131 => 0xc9 }, + 'objectsubtype' => { 518 => 'objectSubtype' }, + 'objecttype' => { 518 => 'objectType' }, + 'objecttypereference' => { 131 => 0x3 }, + 'occurrence' => { 118 => 'Occurrence' }, + 'occurrenceassociatedmedia' => { 118 => [\'Occurrence','OccurrenceAssociatedMedia'] }, + 'occurrenceassociatedoccurrences' => { 118 => [\'Occurrence','OccurrenceAssociatedOccurrences'] }, + 'occurrenceassociatedreferences' => { 118 => [\'Occurrence','OccurrenceAssociatedReferences'] }, + 'occurrenceassociatedsequences' => { 118 => [\'Occurrence','OccurrenceAssociatedSequences'] }, + 'occurrenceassociatedtaxa' => { 118 => [\'Occurrence','OccurrenceAssociatedTaxa'] }, + 'occurrencebehavior' => { 118 => [\'Occurrence','OccurrenceBehavior'] }, + 'occurrencecatalognumber' => { 118 => [\'Occurrence','OccurrenceCatalogNumber'] }, + 'occurrencedegreeofestablishment' => { 118 => [\'Occurrence','OccurrenceDegreeOfEstablishment'] }, + 'occurrencedetails' => { 118 => [\'Occurrence','OccurrenceOccurrenceDetails'] }, + 'occurrencedisposition' => { 118 => [\'Occurrence','OccurrenceDisposition'] }, + 'occurrenceestablishmentmeans' => { 118 => [\'Occurrence','OccurrenceEstablishmentMeans'] }, + 'occurrencegeoreferenceverificationstatus' => { 118 => [\'Occurrence','OccurrenceGeoreferenceVerificationStatus'] }, + 'occurrenceid' => { 118 => [\'Occurrence','OccurrenceOccurrenceID'] }, + 'occurrenceindividualcount' => { 118 => [\'Occurrence','OccurrenceIndividualCount'] }, + 'occurrenceindividualid' => { 118 => [\'Occurrence','OccurrenceIndividualID'] }, + 'occurrencelifestage' => { 118 => [\'Occurrence','OccurrenceLifeStage'] }, + 'occurrenceorganismquantity' => { 118 => [\'Occurrence','OccurrenceOrganismQuantity'] }, + 'occurrenceorganismquantitytype' => { 118 => [\'Occurrence','OccurrenceOrganismQuantityType'] }, + 'occurrenceothercatalognumbers' => { 118 => [\'Occurrence','OccurrenceOtherCatalogNumbers'] }, + 'occurrencepathway' => { 118 => [\'Occurrence','OccurrencePathway'] }, + 'occurrencepreparations' => { 118 => [\'Occurrence','OccurrencePreparations'] }, + 'occurrencepreviousidentifications' => { 118 => [\'Occurrence','OccurrencePreviousIdentifications'] }, + 'occurrencerecordedby' => { 118 => [\'Occurrence','OccurrenceRecordedBy'] }, + 'occurrencerecordedbyid' => { 118 => [\'Occurrence','OccurrenceRecordedByID'] }, + 'occurrencerecordnumber' => { 118 => [\'Occurrence','OccurrenceRecordNumber'] }, + 'occurrenceremarks' => { 118 => [\'Occurrence','OccurrenceOccurrenceRemarks'] }, + 'occurrencereproductivecondition' => { 118 => [\'Occurrence','OccurrenceReproductiveCondition'] }, + 'occurrencesex' => { 118 => [\'Occurrence','OccurrenceSex'] }, + 'occurrencestatus' => { 118 => [\'Occurrence','OccurrenceOccurrenceStatus'] }, + 'oecfcolumns' => { 506 => [\'OECF','OECFColumns'] }, + 'oecfnames' => { 506 => [\'OECF','OECFNames'] }, + 'oecfrows' => { 506 => [\'OECF','OECFRows'] }, + 'oecfvalues' => { 506 => [\'OECF','OECFValues'] }, + 'offsaledate' => { 519 => 'offSaleDate' }, + 'offsaledatea-platform' => { 519 => [\'offSaleDate','offSaleDateA-platform'] }, + 'offsaledatedate' => { 519 => [\'offSaleDate','offSaleDateDate'] }, + 'offsetdacvalue' => { 138 => 0x190a }, + 'offsethdr' => { 511 => 'OffsetHDR' }, + 'offsetschema' => { 119 => 0xea1d }, + 'offsetsdr' => { 511 => 'OffsetSDR' }, + 'offsettime' => { 119 => 0x9010 }, + 'offsettimedigitized' => { 119 => 0x9012 }, + 'offsettimeoriginal' => { 119 => 0x9011 }, + 'oismode' => { 1 => 0xf }, + 'okbutton' => { 307 => '15.1', 312 => '16.1' }, + 'oldsubfiletype' => { 119 => 0xff }, + 'olympusimageheight' => { 322 => 0x102f }, + 'olympusimagewidth' => { 322 => 0x102e }, + 'omenatcapturestrength' => { 138 => 0xa60 }, + 'omenautostrength' => { 138 => 0xa5f }, + 'omenearlystrength' => { 138 => 0xa5e }, + 'omenfocallengthlimit' => { 138 => 0xa62 }, + 'omeninitialipfstrength' => { 138 => 0xa5d }, + 'omensurfaceindex' => { 138 => 0xa64 }, + 'oneshotafrelease' => { 2 => 0x9 }, + 'onetouchwb' => { 322 => 0x302 }, + 'onsaledate' => { 519 => 'onSaleDate' }, + 'onsaledatea-platform' => { 519 => [\'onSaleDate','onSaleDateA-platform'] }, + 'onsaledatedate' => { 519 => [\'onSaleDate','onSaleDateDate'] }, + 'onsaleday' => { 519 => 'onSaleDay' }, + 'onsaledaya-platform' => { 519 => [\'onSaleDay','onSaleDayA-platform'] }, + 'onsaledayday' => { 519 => [\'onSaleDay','onSaleDayDay'] }, + 'opcodelist1' => { 119 => 0xc740 }, + 'opcodelist2' => { 119 => 0xc741 }, + 'opcodelist3' => { 119 => 0xc74e }, + 'opticalzoom' => { 142 => 0xfa3d, 144 => [0x6006,0xf006], 145 => 0x1000, 146 => 0xf, 149 => 0x1e, 151 => 0x20, 152 => 0x1c, 163 => 'OpticalZoom' }, + 'opticalzoomcode' => { 77 => 0xa }, + 'opticalzoommode' => { 340 => 0x34 }, + 'opticalzoomon' => { 416 => 0x219 }, + 'optionenddate' => { 522 => 'optionEndDate' }, + 'opto-electricconvfactor' => { 506 => 'OECF' }, + 'orangehsl' => { 103 => 0x20911 }, + 'ordernumber' => { 127 => 0x8002 }, + 'organisationinimagecode' => { 514 => 'OrganisationInImageCode' }, + 'organisationinimagename' => { 514 => 'OrganisationInImageName' }, + 'organism' => { 118 => 'Organism' }, + 'organismassociatedoccurrences' => { 118 => [\'Organism','OrganismAssociatedOccurrences'] }, + 'organismassociatedorganisms' => { 118 => [\'Organism','OrganismAssociatedOrganisms'] }, + 'organismid' => { 118 => [\'Organism','OrganismOrganismID'] }, + 'organismname' => { 118 => [\'Organism','OrganismOrganismName'] }, + 'organismpreviousidentifications' => { 118 => [\'Organism','OrganismPreviousIdentifications'] }, + 'organismremarks' => { 118 => [\'Organism','OrganismOrganismRemarks'] }, + 'organismscope' => { 118 => [\'Organism','OrganismOrganismScope'] }, + 'organization' => { 519 => 'organization' }, + 'orientation' => { 119 => 0x112, 345 => 0x112, 518 => 'orientation', 525 => 'Orientation' }, + 'orientation2' => { 445 => [0x28,0x2e] }, + 'orientationlinkedaf' => { 2 => 0xe }, + 'orientationlinkedafpoint' => { 84 => 0x516 }, + 'originalalbumtitle' => { 179 => 'WM/OriginalAlbumTitle' }, + 'originalartist' => { 179 => 'WM/OriginalArtist', 392 => "\xa9ope" }, + 'originalbestqualitysize' => { 119 => 0xc792 }, + 'originalcreatedatetime' => { 490 => 'OriginalCreateDateTime' }, + 'originaldecisiondata' => { 114 => 'Canon-OriginalDecisionData' }, + 'originaldecisiondataoffset' => { 64 => 0x83 }, + 'originaldefaultcropsize' => { 119 => 0xc793 }, + 'originaldefaultfinalsize' => { 119 => 0xc791 }, + 'originaldirectory' => { 337 => 0x408 }, + 'originaldocumentid' => { 530 => 'OriginalDocumentID' }, + 'originalfilename' => { 97 => 0x816, 138 => 0x3e9, 150 => 0x20, 337 => 0x407, 490 => 'OriginalFilename' }, + 'originalimagehash' => { 480 => 'OriginalImageHash' }, + 'originalimagehashtype' => { 480 => 'OriginalImageHashType' }, + 'originalimageheight' => { 79 => 0xc, 123 => 0x1 }, + 'originalimagemd5' => { 480 => 'OriginalImageMD5' }, + 'originalimagesize' => { 162 => 'OriginalImageSize' }, + 'originalimagewidth' => { 79 => 0xb, 123 => 0x0 }, + 'originallyricist' => { 179 => 'WM/OriginalLyricist' }, + 'originalrawfiledata' => { 119 => 0xc68c }, + 'originalrawfiledigest' => { 119 => 0xc71d }, + 'originalrawfilename' => { 119 => 0xc68b }, + 'originaltransmissionreference' => { 131 => 0x67 }, + 'originatingprogram' => { 131 => 0x41, 495 => 'OriginatingProgram' }, + 'originplatform' => { 519 => 'originPlatform' }, + 'os' => { 504 => 'os' }, + 'otherconditions' => { 327 => 'OtherConditions' }, + 'otherconstraints' => { 327 => 'OtherConstraints' }, + 'otherimage' => { 114 => 'Exif-OtherImage' }, + 'otherimageinfo' => { 327 => 'OtherImageInfo' }, + 'otherimagelength' => { 119 => 0x202 }, + 'otherimagestart' => { 119 => 0x201 }, + 'otherlicensedocuments' => { 327 => 'OtherLicenseDocuments' }, + 'otherlicenseinfo' => { 327 => 'OtherLicenseInfo' }, + 'otherlicenserequirements' => { 327 => 'OtherLicenseRequirements' }, + 'outcue' => { 529 => 'outCue' }, + 'outcuescale' => { 529 => [\'outCue','outCueScale'] }, + 'outcuevalue' => { 529 => [\'outCue','outCueValue'] }, + 'outputimageheight' => { 200 => 0x3 }, + 'outputimagewidth' => { 200 => 0x2 }, + 'outputlut' => { 340 => 0xa7 }, + 'outputprofile' => { 138 => 0x138b }, + 'outputresolution' => { 200 => 0x4 }, + 'overclockcols' => { 138 => 0x189c }, + 'overclockrows' => { 138 => 0x18c4 }, + 'overridelookvignette' => { 500 => 'OverrideLookVignette', 502 => 'OverrideLookVignette' }, + 'owner' => { 533 => 'Owner' }, + 'ownerid' => { 131 => 0xbc }, + 'ownername' => { 15 => 0x10f, 64 => 0x9, 97 => 0x810, 119 => [0xa430,0xfde8], 156 => 'OwnerName', 497 => 'OwnerName', 507 => 'CameraOwnerName' }, + 'padding' => { 119 => 0xea1c }, + 'pagecount' => { 519 => 'pageCount' }, + 'pageimage' => { 527 => [\'PageInfo','PageInfoImage'] }, + 'pageimageformat' => { 527 => [\'PageInfo','PageInfoFormat'] }, + 'pageimageheight' => { 527 => [\'PageInfo','PageInfoHeight'] }, + 'pageimagepagenumber' => { 527 => [\'PageInfo','PageInfoPageNumber'] }, + 'pageimagewidth' => { 527 => [\'PageInfo','PageInfoWidth'] }, + 'pageinfo' => { 527 => 'PageInfo' }, + 'pagename' => { 119 => 0x11d }, + 'pagenumber' => { 119 => 0x129 }, + 'pageprogressiondirection' => { 519 => 'pageProgressionDirection' }, + 'pagerange' => { 519 => 'pageRange' }, + 'paintbasedcorrectionmasks' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasks'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasks'] }, + 'paintbasedcorrections' => { 500 => 'PaintBasedCorrections', 502 => 'PaintBasedCorrections' }, + 'paintcorrectionactive' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionActive'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionActive'] }, + 'paintcorrectionamount' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionAmount'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionAmount'] }, + 'paintcorrectionblacks2012' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalBlacks2012'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalBlacks2012'] }, + 'paintcorrectionbrightness' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalBrightness'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalBrightness'] }, + 'paintcorrectionclarity' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalClarity'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalClarity'] }, + 'paintcorrectionclarity2012' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalClarity2012'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalClarity2012'] }, + 'paintcorrectioncontrast' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalContrast'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalContrast'] }, + 'paintcorrectioncontrast2012' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalContrast2012'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalContrast2012'] }, + 'paintcorrectioncorrectionname' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionName'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionName'] }, + 'paintcorrectioncorrectionsyncid' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionSyncID'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionSyncID'] }, + 'paintcorrectiondefringe' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalDefringe'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalDefringe'] }, + 'paintcorrectiondehaze' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalDehaze'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalDehaze'] }, + 'paintcorrectionexposure' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalExposure'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalExposure'] }, + 'paintcorrectionexposure2012' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalExposure2012'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalExposure2012'] }, + 'paintcorrectionhighlights2012' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalHighlights2012'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalHighlights2012'] }, + 'paintcorrectionhue' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalHue'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalHue'] }, + 'paintcorrectionluminancenoise' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalLuminanceNoise'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalLuminanceNoise'] }, + 'paintcorrectionmaskalpha' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksAlpha'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksAlpha'] }, + 'paintcorrectionmaskangle' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksAngle'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksAngle'] }, + 'paintcorrectionmaskbottom' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksBottom'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksBottom'] }, + 'paintcorrectionmaskcentervalue' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCenterValue'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCenterValue'] }, + 'paintcorrectionmaskcenterweight' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCenterWeight'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCenterWeight'] }, + 'paintcorrectionmaskdabs' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksDabs'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksDabs'] }, + 'paintcorrectionmaskfeather' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksFeather'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksFeather'] }, + 'paintcorrectionmaskflipped' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksFlipped'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksFlipped'] }, + 'paintcorrectionmaskflow' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksFlow'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksFlow'] }, + 'paintcorrectionmaskfullx' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksFullX'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksFullX'] }, + 'paintcorrectionmaskfully' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksFullY'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksFullY'] }, + 'paintcorrectionmaskinputdigest' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksInputDigest'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksInputDigest'] }, + 'paintcorrectionmaskleft' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksLeft'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksLeft'] }, + 'paintcorrectionmaskmaskactive' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskActive'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskActive'] }, + 'paintcorrectionmaskmaskblendmode' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskBlendMode'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskBlendMode'] }, + 'paintcorrectionmaskmaskdigest' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskDigest'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskDigest'] }, + 'paintcorrectionmaskmaskinverted' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskInverted'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskInverted'] }, + 'paintcorrectionmaskmaskname' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskName'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskName'] }, + 'paintcorrectionmaskmasks' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasks'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasks'] }, + 'paintcorrectionmaskmasksalpha' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksAlpha'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksAlpha'] }, + 'paintcorrectionmaskmasksangle' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksAngle'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksAngle'] }, + 'paintcorrectionmaskmasksbottom' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksBottom'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksBottom'] }, + 'paintcorrectionmaskmaskscentervalue' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksCenterValue'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksCenterValue'] }, + 'paintcorrectionmaskmaskscenterweight' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksCenterWeight'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksCenterWeight'] }, + 'paintcorrectionmaskmasksdabs' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksDabs'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksDabs'] }, + 'paintcorrectionmaskmasksfeather' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksFeather'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksFeather'] }, + 'paintcorrectionmaskmasksflipped' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksFlipped'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksFlipped'] }, + 'paintcorrectionmaskmasksflow' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksFlow'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksFlow'] }, + 'paintcorrectionmaskmasksfullx' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksFullX'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksFullX'] }, + 'paintcorrectionmaskmasksfully' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksFullY'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksFullY'] }, + 'paintcorrectionmaskmasksinputdigest' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksInputDigest'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksInputDigest'] }, + 'paintcorrectionmaskmasksleft' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksLeft'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksLeft'] }, + 'paintcorrectionmaskmasksmaskactive' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskActive'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskActive'] }, + 'paintcorrectionmaskmasksmaskblendmode' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskBlendMode'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskBlendMode'] }, + 'paintcorrectionmaskmasksmaskdigest' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskDigest'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskDigest'] }, + 'paintcorrectionmaskmasksmaskinverted' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskInverted'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskInverted'] }, + 'paintcorrectionmaskmasksmaskname' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskName'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskName'] }, + 'paintcorrectionmaskmasksmasksubtype' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskSubType'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskSubType'] }, + 'paintcorrectionmaskmasksmasksyncid' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskSyncID'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskSyncID'] }, + 'paintcorrectionmaskmasksmaskversion' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskVersion'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskVersion'] }, + 'paintcorrectionmaskmasksmidpoint' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMidpoint'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMidpoint'] }, + 'paintcorrectionmaskmasksorigin' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksOrigin'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksOrigin'] }, + 'paintcorrectionmaskmasksperimetervalue' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksPerimeterValue'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksPerimeterValue'] }, + 'paintcorrectionmaskmasksradius' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksRadius'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksRadius'] }, + 'paintcorrectionmaskmasksreferencepoint' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksReferencePoint'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksReferencePoint'] }, + 'paintcorrectionmaskmasksright' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksRight'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksRight'] }, + 'paintcorrectionmaskmasksroundness' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksRoundness'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksRoundness'] }, + 'paintcorrectionmaskmaskssizex' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksSizeX'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksSizeX'] }, + 'paintcorrectionmaskmaskssizey' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksSizeY'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksSizeY'] }, + 'paintcorrectionmaskmaskstop' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksTop'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksTop'] }, + 'paintcorrectionmaskmasksubtype' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskSubType'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskSubType'] }, + 'paintcorrectionmaskmasksvalue' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskValue'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksMaskValue'] }, + 'paintcorrectionmaskmasksversion' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksVersion'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksVersion'] }, + 'paintcorrectionmaskmaskswhat' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksWhat'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksWhat'] }, + 'paintcorrectionmaskmaskswholeimagearea' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksWholeImageArea'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksWholeImageArea'] }, + 'paintcorrectionmaskmasksx' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksX'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksX'] }, + 'paintcorrectionmaskmasksy' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksY'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksY'] }, + 'paintcorrectionmaskmasksyncid' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskSyncID'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskSyncID'] }, + 'paintcorrectionmaskmaskszerox' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksZeroX'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksZeroX'] }, + 'paintcorrectionmaskmaskszeroy' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksZeroY'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMasksZeroY'] }, + 'paintcorrectionmaskmaskversion' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskVersion'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskVersion'] }, + 'paintcorrectionmaskmidpoint' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMidpoint'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMidpoint'] }, + 'paintcorrectionmaskorigin' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksOrigin'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksOrigin'] }, + 'paintcorrectionmaskperimetervalue' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksPerimeterValue'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksPerimeterValue'] }, + 'paintcorrectionmaskradius' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksRadius'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksRadius'] }, + 'paintcorrectionmaskrange' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMask'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMask'] }, + 'paintcorrectionmaskrangeareamodels' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModels'] }, + 'paintcorrectionmaskrangeareamodelscolorsampleinfo' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'] }, + 'paintcorrectionmaskrangeareamodelscomponents' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskAreaModelsAreaComponents'] }, + 'paintcorrectionmaskrangecoloramount' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskColorAmount'] }, + 'paintcorrectionmaskrangedepthfeather' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthFeather'] }, + 'paintcorrectionmaskrangedepthmax' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMax'] }, + 'paintcorrectionmaskrangedepthmin' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskDepthMin'] }, + 'paintcorrectionmaskrangeinvert' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskInvert'] }, + 'paintcorrectionmaskrangelumfeather' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumFeather'] }, + 'paintcorrectionmaskrangeluminancedepthsampleinfo' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLuminanceDepthSampleInfo'] }, + 'paintcorrectionmaskrangelummax' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMax'] }, + 'paintcorrectionmaskrangelummin' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumMin'] }, + 'paintcorrectionmaskrangelumrange' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskLumRange'] }, + 'paintcorrectionmaskrangesampletype' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskSampleType'] }, + 'paintcorrectionmaskrangetype' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskType'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskType'] }, + 'paintcorrectionmaskrangeversion' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksCorrectionRangeMaskVersion'] }, + 'paintcorrectionmaskreferencepoint' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksReferencePoint'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksReferencePoint'] }, + 'paintcorrectionmaskright' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksRight'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksRight'] }, + 'paintcorrectionmaskroundness' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksRoundness'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksRoundness'] }, + 'paintcorrectionmasksizex' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksSizeX'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksSizeX'] }, + 'paintcorrectionmasksizey' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksSizeY'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksSizeY'] }, + 'paintcorrectionmasktop' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksTop'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksTop'] }, + 'paintcorrectionmaskvalue' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskValue'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksMaskValue'] }, + 'paintcorrectionmaskversion' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksVersion'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksVersion'] }, + 'paintcorrectionmaskwhat' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksWhat'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksWhat'] }, + 'paintcorrectionmaskwholeimagearea' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksWholeImageArea'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksWholeImageArea'] }, + 'paintcorrectionmaskx' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksX'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksX'] }, + 'paintcorrectionmasky' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksY'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksY'] }, + 'paintcorrectionmaskzerox' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksZeroX'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksZeroX'] }, + 'paintcorrectionmaskzeroy' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksZeroY'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionMasksZeroY'] }, + 'paintcorrectionmoire' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalMoire'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalMoire'] }, + 'paintcorrectionrangemask' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMask'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMask'] }, + 'paintcorrectionrangemaskareamodels' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskAreaModels'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskAreaModels'] }, + 'paintcorrectionrangemaskareamodelscolorsampleinfo' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'] }, + 'paintcorrectionrangemaskareamodelscomponents' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskAreaModelsAreaComponents'] }, + 'paintcorrectionrangemaskcoloramount' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskColorAmount'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskColorAmount'] }, + 'paintcorrectionrangemaskdepthfeather' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskDepthFeather'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskDepthFeather'] }, + 'paintcorrectionrangemaskdepthmax' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskDepthMax'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskDepthMax'] }, + 'paintcorrectionrangemaskdepthmin' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskDepthMin'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskDepthMin'] }, + 'paintcorrectionrangemaskinvert' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskInvert'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskInvert'] }, + 'paintcorrectionrangemasklumfeather' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskLumFeather'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskLumFeather'] }, + 'paintcorrectionrangemaskluminancedepthsampleinfo' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskLuminanceDepthSampleInfo'] }, + 'paintcorrectionrangemasklummax' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskLumMax'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskLumMax'] }, + 'paintcorrectionrangemasklummin' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskLumMin'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskLumMin'] }, + 'paintcorrectionrangemasklumrange' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskLumRange'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskLumRange'] }, + 'paintcorrectionrangemasksampletype' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskSampleType'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskSampleType'] }, + 'paintcorrectionrangemasktype' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskType'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskType'] }, + 'paintcorrectionrangemaskversion' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskVersion'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsCorrectionRangeMaskVersion'] }, + 'paintcorrectionsaturation' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalSaturation'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalSaturation'] }, + 'paintcorrectionshadows2012' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalShadows2012'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalShadows2012'] }, + 'paintcorrectionsharpness' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalSharpness'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalSharpness'] }, + 'paintcorrectiontemperature' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalTemperature'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalTemperature'] }, + 'paintcorrectiontexture' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalTexture'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalTexture'] }, + 'paintcorrectiontint' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalTint'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalTint'] }, + 'paintcorrectiontoninghue' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalToningHue'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalToningHue'] }, + 'paintcorrectiontoningsaturation' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalToningSaturation'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalToningSaturation'] }, + 'paintcorrectionwhat' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsWhat'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsWhat'] }, + 'paintcorrectionwhites2012' => { 500 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalWhites2012'], 502 => [\'PaintBasedCorrections','PaintBasedCorrectionsLocalWhites2012'] }, + 'panasonicdatetime' => { 343 => 0x0 }, + 'panasonicexifversion' => { 340 => 0x26 }, + 'panasonicimageheight' => { 340 => 0x4c }, + 'panasonicimagewidth' => { 340 => 0x4b }, + 'panasonicrawversion' => { 345 => 0x1 }, + 'panasonictitle' => { 119 => 0xc6d2 }, + 'panasonictitle2' => { 119 => 0xc6d3 }, + 'panoramaangle' => { 127 => 0x1153 }, + 'panoramacropbottom' => { 446 => 0x7 }, + 'panoramacropleft' => { 446 => 0x4 }, + 'panoramacropright' => { 446 => 0x6 }, + 'panoramacroptop' => { 446 => 0x5 }, + 'panoramadirection' => { 72 => 0x5, 127 => 0x1154, 446 => 0x3 }, + 'panoramaframeheight' => { 446 => 0x9 }, + 'panoramaframenumber' => { 72 => 0x2 }, + 'panoramaframewidth' => { 446 => 0x8 }, + 'panoramafullheight' => { 446 => 0x2 }, + 'panoramafullwidth' => { 446 => 0x1 }, + 'panoramamode' => { 140 => 0x3c, 317 => 0x601 }, + 'panoramasize3d' => { 429 => 0x38 }, + 'panoramasourceheight' => { 446 => 0xb }, + 'panoramasourcewidth' => { 446 => 0xa }, + 'panoramicstitchcameramotion' => { 176 => 'PanoramicStitchCameraMotion', 177 => 0x1 }, + 'panoramicstitchmaptype' => { 176 => 'PanoramicStitchMapType', 177 => 0x2 }, + 'panoramicstitchphi0' => { 176 => 'PanoramicStitchPhi0', 177 => 0x5 }, + 'panoramicstitchphi1' => { 176 => 'PanoramicStitchPhi1', 177 => 0x6 }, + 'panoramicstitchtheta0' => { 176 => 'PanoramicStitchTheta0', 177 => 0x3 }, + 'panoramicstitchtheta1' => { 176 => 'PanoramicStitchTheta1', 177 => 0x4 }, + 'panoramicstitchversion' => { 177 => 0x0 }, + 'pantry' => { 530 => 'Pantry' }, + 'pantryinstanceid' => { 530 => [\'Pantry','PantryInstanceID'] }, + 'parallax' => { 127 => 0xb211, 316 => 0x28 }, + 'parametricdarks' => { 500 => 'ParametricDarks', 502 => 'ParametricDarks' }, + 'parametrichighlights' => { 500 => 'ParametricHighlights', 502 => 'ParametricHighlights' }, + 'parametrichighlightsplit' => { 500 => 'ParametricHighlightSplit', 502 => 'ParametricHighlightSplit' }, + 'parametriclights' => { 500 => 'ParametricLights', 502 => 'ParametricLights' }, + 'parametricmidtonesplit' => { 500 => 'ParametricMidtoneSplit', 502 => 'ParametricMidtoneSplit' }, + 'parametricshadows' => { 500 => 'ParametricShadows', 502 => 'ParametricShadows' }, + 'parametricshadowsplit' => { 500 => 'ParametricShadowSplit', 502 => 'ParametricShadowSplit' }, + 'parentalrating' => { 179 => 'WM/ParentalRating' }, + 'parentid' => { 514 => 'parentId' }, + 'parentmediaeventid' => { 490 => 'ParentMediaEventID' }, + 'parentmeid' => { 490 => 'ParentMEID' }, + 'parentproductid' => { 392 => '@ppi' }, + 'parentreference1' => { 512 => [\'TagStructure','TagStructureParentReference'] }, + 'parentreference2' => { 512 => [\'TagStructure','TagStructureSubLabelsParentReference'] }, + 'parentreference3' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsParentReference'] }, + 'parentreference4' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsParentReference'] }, + 'parentreference5' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsSubLabelsParentReference'] }, + 'parentreference6' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabelsParentReference'] }, + 'parentshorttitle' => { 392 => '@PST' }, + 'parenttitle' => { 392 => '@pti' }, + 'partialactivecols1' => { 138 => 0x17e8 }, + 'partialactivecols2' => { 138 => 0x17f2 }, + 'partialactiverows1' => { 138 => 0x17fc }, + 'partialactiverows2' => { 138 => 0x1806 }, + 'partofcompilation' => { 529 => 'partOfCompilation' }, + 'patientbirthdate' => { 478 => 'PatientDOB' }, + 'patientid' => { 478 => 'PatientID' }, + 'patientname' => { 478 => 'PatientName' }, + 'patientsex' => { 478 => 'PatientSex' }, + 'patternareaheight' => { 138 => 0x963 }, + 'patternareawidth' => { 138 => 0x962 }, + 'patterncorrectionfactorscale' => { 138 => 0x969 }, + 'patterncropcols1' => { 138 => 0x96c }, + 'patterncropcols2' => { 138 => 0x96d }, + 'patterncroprows1' => { 138 => 0x96a }, + 'patterncroprows2' => { 138 => 0x96b }, + 'patternimagerheight' => { 138 => 0x961 }, + 'patternimagerwidth' => { 138 => 0x960 }, + 'patternx' => { 138 => 0x966 }, + 'patterny' => { 138 => 0x967 }, + 'pdfversion' => { 516 => 'PDFVersion' }, + 'pentaximagesize' => { 375 => 0x9 }, + 'pentaxmodelid' => { 355 => 0x0, 375 => 0x5 }, + 'pentaxmodeltype' => { 375 => 0x1 }, + 'pentaxversion' => { 375 => 0x0 }, + 'people' => { 481 => 'People', 493 => 'People' }, + 'perchannelblacklevel' => { 41 => 0x157, 42 => 0x16b, 44 => 0xc4, 45 => [0x2b4,0x2cb,0x2cf], 46 => [0x108,0x14d], 47 => 0x1df, 48 => [0x1f8,0x2d8], 49 => [0x22c,0x30a], 50 => 0x149 }, + 'performer' => { 392 => 'perf', 400 => 'perf' }, + 'performerkeywords' => { 400 => "\xa9prk" }, + 'performers' => { 400 => "\xa9prf" }, + 'performerurl' => { 400 => "\xa9prl" }, + 'period' => { 179 => 'WM/Period' }, + 'peripheralillumcentralradius' => { 476 => 0x8030 }, + 'peripheralillumcentralvalue' => { 476 => 0x8031 }, + 'peripheralillumination' => { 103 => 0x20702, 109 => 0x68 }, + 'peripheralilluminationcorr' => { 62 => 0x1, 365 => 0x2 }, + 'peripheralilluminationon' => { 103 => '0x20702.0', 109 => 0x64 }, + 'peripheralillumperiphvalue' => { 476 => 0x8032 }, + 'peripherallighting' => { 79 => 0x2, 127 => 0x3804 }, + 'peripherallightingsetting' => { 80 => 0x5 }, + 'peripherallightingvalue' => { 79 => 0x6 }, + 'permissions' => { 522 => 'permissions' }, + 'permits' => { 498 => 'permits' }, + 'person' => { 519 => 'person' }, + 'personality' => { 490 => 'Personality' }, + 'personheard' => { 514 => 'PersonHeard' }, + 'personheardidentifier' => { 514 => [\'PersonHeard','PersonHeardIdentifier'] }, + 'personheardname' => { 514 => [\'PersonHeard','PersonHeardName'] }, + 'personinimage' => { 514 => 'PersonInImage' }, + 'personinimagecharacteristic' => { 514 => [\'PersonInImageWDetails','PersonInImageWDetailsPersonCharacteristic'] }, + 'personinimagecvtermcvid' => { 514 => [\'PersonInImageWDetails','PersonInImageWDetailsPersonCharacteristicCvId'] }, + 'personinimagecvtermid' => { 514 => [\'PersonInImageWDetails','PersonInImageWDetailsPersonCharacteristicCvTermId'] }, + 'personinimagecvtermname' => { 514 => [\'PersonInImageWDetails','PersonInImageWDetailsPersonCharacteristicCvTermName'] }, + 'personinimagecvtermrefinedabout' => { 514 => [\'PersonInImageWDetails','PersonInImageWDetailsPersonCharacteristicCvTermRefinedAbout'] }, + 'personinimagedescription' => { 514 => [\'PersonInImageWDetails','PersonInImageWDetailsPersonDescription'] }, + 'personinimageid' => { 514 => [\'PersonInImageWDetails','PersonInImageWDetailsPersonId'] }, + 'personinimagename' => { 514 => [\'PersonInImageWDetails','PersonInImageWDetailsPersonName'] }, + 'personinimagewdetails' => { 514 => 'PersonInImageWDetails' }, + 'perspectiveaspect' => { 500 => 'PerspectiveAspect', 502 => 'PerspectiveAspect' }, + 'perspectivehorizontal' => { 500 => 'PerspectiveHorizontal', 502 => 'PerspectiveHorizontal' }, + 'perspectiverotate' => { 500 => 'PerspectiveRotate', 502 => 'PerspectiveRotate' }, + 'perspectivescale' => { 500 => 'PerspectiveScale', 502 => 'PerspectiveScale' }, + 'perspectiveupright' => { 500 => 'PerspectiveUpright', 502 => 'PerspectiveUpright' }, + 'perspectivevertical' => { 500 => 'PerspectiveVertical', 502 => 'PerspectiveVertical' }, + 'perspectivex' => { 500 => 'PerspectiveX', 502 => 'PerspectiveX' }, + 'perspectivey' => { 500 => 'PerspectiveY', 502 => 'PerspectiveY' }, + 'pf0customfuncregistration' => { 92 => 0x1 }, + 'pf10retainprogramshift' => { 92 => 0xb }, + 'pf13drivepriority' => { 92 => 0xe }, + 'pf14disablefocussearch' => { 92 => 0xf }, + 'pf15disableafassistbeam' => { 92 => 0x10 }, + 'pf16autofocuspointshoot' => { 92 => 0x11 }, + 'pf17disableafpointsel' => { 92 => 0x12 }, + 'pf18enableautoafpointsel' => { 92 => 0x13 }, + 'pf19continuousshootspeed' => { 92 => 0x14 }, + 'pf19shootingspeedhigh' => { 91 => 0xa }, + 'pf19shootingspeedlow' => { 91 => 0x9 }, + 'pf1disableshootingmodes' => { 92 => 0x2 }, + 'pf1value' => { 91 => 0x1 }, + 'pf20limitcontinousshots' => { 92 => 0x15 }, + 'pf20maxcontinousshots' => { 91 => 0xb }, + 'pf21enablequietoperation' => { 92 => 0x16 }, + 'pf23felocktime' => { 91 => 0xd }, + 'pf23postreleasetime' => { 91 => 0xe }, + 'pf23settimerlengths' => { 92 => 0x18 }, + 'pf23shutterbuttontime' => { 91 => 0xc }, + 'pf24lightlcdduringbulb' => { 92 => 0x19 }, + 'pf25aemode' => { 91 => 0xf }, + 'pf25afmode' => { 91 => 0x12 }, + 'pf25afpointsel' => { 91 => 0x13 }, + 'pf25colormatrix' => { 91 => 0x17 }, + 'pf25defaultclearsettings' => { 92 => 0x1a }, + 'pf25drivemode' => { 91 => 0x11 }, + 'pf25imagesize' => { 91 => 0x14 }, + 'pf25meteringmode' => { 91 => 0x10 }, + 'pf25parameters' => { 91 => 0x16 }, + 'pf25wbmode' => { 91 => 0x15 }, + 'pf26shortenreleaselag' => { 92 => 0x1b }, + 'pf27reversedialrotation' => { 92 => 0x1c }, + 'pf27value' => { 91 => 0x18 }, + 'pf28noquickdialexpcomp' => { 92 => 0x1d }, + 'pf29quickdialswitchoff' => { 92 => 0x1e }, + 'pf2disablemeteringmodes' => { 92 => 0x3 }, + 'pf2value' => { 91 => 0x2 }, + 'pf30enlargementmode' => { 92 => 0x1f }, + 'pf31originaldecisiondata' => { 92 => 0x20 }, + 'pf3manualexposuremetering' => { 92 => 0x4 }, + 'pf3value' => { 91 => 0x3 }, + 'pf4exposuretimelimits' => { 92 => 0x5 }, + 'pf4exposuretimemax' => { 91 => 0x5 }, + 'pf4exposuretimemin' => { 91 => 0x4 }, + 'pf5aperturelimits' => { 92 => 0x6 }, + 'pf5aperturemax' => { 91 => 0x7 }, + 'pf5aperturemin' => { 91 => 0x6 }, + 'pf6presetshootingmodes' => { 92 => 0x7 }, + 'pf7bracketcontinuousshoot' => { 92 => 0x8 }, + 'pf8bracketshots' => { 91 => 0x8 }, + 'pf8setbracketshots' => { 92 => 0x9 }, + 'pf9changebracketsequence' => { 92 => 0xa }, + 'phasedetectaf' => { 193 => 0x6 }, + 'phonenumber' => { 157 => 'Phone' }, + 'photoeffect' => { 34 => 0x28 }, + 'photoeffecthistoryxml' => { 290 => 0xe9651831 }, + 'photoeffects' => { 290 => 0xab5eca5e }, + 'photoeffectsblue' => { 292 => 0x8 }, + 'photoeffectsgreen' => { 292 => 0x6 }, + 'photoeffectsred' => { 292 => 0x4 }, + 'photoeffectstype' => { 292 => 0x0 }, + 'photographer' => { 119 => 0xa437 }, + 'photographicsensitivity' => { 507 => 'PhotographicSensitivity' }, + 'photoidentifier' => { 1 => 0x2b }, + 'photoinfoplayback' => { 297 => '17.6', 306 => '33.6' }, + 'photometricinterpretation' => { 119 => 0x106, 525 => 'PhotometricInterpretation' }, + 'photoshootingmenubank' => { 239 => 0x11e, 240 => 0x112, 241 => 0x122, 242 => 0x122, 243 => 0x24, 261 => 0x0 }, + 'photoshootingmenubankimagearea' => { 237 => 0x6dd, 239 => 0x144, 240 => 0x134, 241 => 0x148, 242 => 0x148, 261 => '7.1' }, + 'photoshopbgrthumbnail' => { 389 => 0x409 }, + 'photoshopquality' => { 388 => 0x0 }, + 'photoshopthumbnail' => { 389 => 0x40c }, + 'photostyle' => { 340 => 0x89 }, + 'picasawebgphotoid' => { 505 => 'picasawebGPhotoId' }, + 'picklabel' => { 505 => 'PickLabel' }, + 'pictinfo' => { 416 => 0x208 }, + 'picturecontrol' => { 290 => 0xe2173c47 }, + 'picturecontrolactive' => { 293 => 0x0 }, + 'picturecontroladjust' => { 249 => 0x30, 250 => 0x30, 251 => 0x36 }, + 'picturecontrolbase' => { 249 => 0x18, 250 => 0x18, 251 => 0x1c }, + 'picturecontroldata' => { 234 => [0xbd,0x23] }, + 'picturecontrolmode' => { 293 => 0x13 }, + 'picturecontrolname' => { 249 => 0x4, 250 => 0x4, 251 => 0x8 }, + 'picturecontrolquickadjust' => { 249 => 0x31, 250 => 0x31, 251 => 0x37 }, + 'pictureeffect' => { 440 => 0x200e }, + 'pictureeffect2' => { 449 => 0x1163, 450 => 0x1167, 451 => 0x1143, 452 => 0x11bf, 453 => 0x119b, 454 => 0x1053, 455 => 0x24b, 456 => 0x24b, 457 => 0x23c, 472 => 0x46 }, + 'picturefinish' => { 182 => 0x71 }, + 'picturemode' => { 127 => 0x1031, 317 => 0x520, 375 => [0xb,0x33], 417 => 0x3d }, + 'picturemode2' => { 356 => 0x0 }, + 'picturemodebwfilter' => { 317 => 0x525 }, + 'picturemodecontrast' => { 317 => 0x523 }, + 'picturemodeeffect' => { 317 => 0x52d }, + 'picturemodehue' => { 317 => 0x522 }, + 'picturemodesaturation' => { 317 => 0x521 }, + 'picturemodesharpness' => { 317 => 0x524 }, + 'picturemodetone' => { 317 => 0x526 }, + 'pictureprofile' => { 449 => [0x115e,0x115f], 450 => [0x1162,0x1163], 451 => [0x113e,0x113f], 452 => [0x11ba,0x11bb], 453 => [0x1196,0x1197], 454 => [0x104e,0x104f], 455 => [0x246,0x247], 456 => [0x246,0x247], 457 => [0x237,0x238] }, + 'picturestyle' => { 8 => [0x4b,0x51], 9 => 0xf4, 10 => 0x6c, 11 => 0x86, 12 => 0x73, 16 => 0xab, 17 => 0xa7, 18 => 0xb0, 19 => 0x6c, 20 => 0xa7, 21 => 0xf4, 22 => 0xb3, 24 => 0xf4, 25 => 0xfa, 27 => 0x169, 74 => 0xa, 103 => 0x20301, 109 => 0x2 }, + 'picturestylepc' => { 64 => 0x4009 }, + 'picturestyleuserdef' => { 64 => 0x4008 }, + 'picturewizard' => { 414 => 0x21 }, + 'picturewizardcolor' => { 413 => 0x1 }, + 'picturewizardcontrast' => { 413 => 0x4 }, + 'picturewizardmode' => { 413 => 0x0 }, + 'picturewizardsaturation' => { 413 => 0x2 }, + 'picturewizardsharpness' => { 413 => 0x3 }, + 'pipelineversion' => { 176 => 'PipelineVersion' }, + 'pitch' => { 115 => 0x6, 298 => '4.1', 310 => '4.1', 400 => ['ptch',"\xa9fpt"] }, + 'pitchangle' => { 247 => 0x4, 317 => 0x904, 340 => 0x91, 374 => 0x2, 412 => 0x1 }, + 'pitchshift' => { 394 => 'player.movie.audio.pitchshift' }, + 'pixelaspectratio' => { 393 => 'pasp' }, + 'pixelclockfrequency' => { 138 => 0x40b }, + 'pixelcorrectionoffset' => { 138 => 0x972 }, + 'pixelcorrectionscale' => { 138 => 0x971 }, + 'pixelscale' => { 119 => 0x830e }, + 'pixelshiftinfo' => { 440 => 0x202f }, + 'pixelshiftoffset' => { 127 => 0x1106 }, + 'pixelshiftresolution' => { 376 => 0x0 }, + 'pixelshiftshots' => { 127 => 0x1105 }, + 'pixelsperunitx' => { 329 => 0x0 }, + 'pixelsperunity' => { 329 => 0x4 }, + 'pixelunits' => { 329 => 0x8 }, + 'planarconfiguration' => { 119 => 0x11c, 525 => 'PlanarConfiguration' }, + 'plane' => { 479 => [\'Planes','PlanesPlane'] }, + 'planeboundary' => { 479 => [\'Planes','PlanesPlaneBoundary'] }, + 'planeboundaryvertexcount' => { 479 => [\'Planes','PlanesPlaneBoundaryVertexCount'] }, + 'planeextentx' => { 479 => [\'Planes','PlanesPlaneExtentX'] }, + 'planeextentz' => { 479 => [\'Planes','PlanesPlaneExtentZ'] }, + 'planepose' => { 479 => [\'Planes','PlanesPlanePose'] }, + 'planeposepositionx' => { 479 => [\'Planes','PlanesPlanePosePositionX'] }, + 'planeposepositiony' => { 479 => [\'Planes','PlanesPlanePosePositionY'] }, + 'planeposepositionz' => { 479 => [\'Planes','PlanesPlanePosePositionZ'] }, + 'planeposerotationw' => { 479 => [\'Planes','PlanesPlanePoseRotationW'] }, + 'planeposerotationx' => { 479 => [\'Planes','PlanesPlanePoseRotationX'] }, + 'planeposerotationy' => { 479 => [\'Planes','PlanesPlanePoseRotationY'] }, + 'planeposerotationz' => { 479 => [\'Planes','PlanesPlanePoseRotationZ'] }, + 'planeposetimestamp' => { 479 => [\'Planes','PlanesPlanePoseTimestamp'] }, + 'planes' => { 479 => 'Planes' }, + 'planningref' => { 514 => 'PlanningRef' }, + 'planningrefidentifier' => { 514 => [\'PlanningRef','PlanningRefIdentifier'] }, + 'planningrefname' => { 514 => [\'PlanningRef','PlanningRefName'] }, + 'planningrefrole' => { 514 => [\'PlanningRef','PlanningRefRole'] }, + 'platenames' => { 534 => 'PlateNames' }, + 'platform' => { 519 => 'platform' }, + 'playallframes' => { 400 => 'AllF' }, + 'playbackbutton' => { 242 => 0x808 }, + 'playbackbuttonplaybackmode' => { 242 => 0x814 }, + 'playbackflickdown' => { 313 => 0x159, 314 => 0x159, 315 => 0x171 }, + 'playbackflickup' => { 313 => 0x155, 314 => 0x155, 315 => 0x16d }, + 'playbackmenustime' => { 302 => '20.1', 303 => '21.1', 304 => '21.1' }, + 'playbackmonitorofftime' => { 297 => '25.2', 298 => '36.1', 300 => '36.1', 301 => '36.1', 305 => '35.1', 306 => '8.1', 307 => '35.1', 310 => '36.1', 311 => '36.1', 312 => '21.1', 313 => 0x33, 314 => 0x33, 315 => 0x33 }, + 'playbackzoom' => { 298 => '37.1' }, + 'playdisplay' => { 184 => 0x4e }, + 'playerversion' => { 394 => 'player.version' }, + 'playgap' => { 392 => 'pgap' }, + 'playmode' => { 400 => 'SDLN' }, + 'playselection' => { 400 => 'SelO' }, + 'plusversion' => { 327 => 'Version' }, + 'pmversion' => { 387 => 'PMVersion' }, + 'pngwarning' => { 330 => 'Warning' }, + 'podcast' => { 392 => 'pcst' }, + 'podcasturl' => { 392 => 'purl' }, + 'poilevel' => { 233 => 0x8 }, + 'portraitimpressionbalance' => { 239 => 0x26e, 252 => 0xa0 }, + 'portraitnote' => { 483 => 'PortraitNote' }, + 'portraitoutputhighlightpoint' => { 109 => 0x1d }, + 'portraitoutputshadowpoint' => { 109 => 0x1e }, + 'portraitrawcolortone' => { 109 => 0x16 }, + 'portraitrawcontrast' => { 109 => 0x18 }, + 'portraitrawhighlight' => { 109 => 0x76 }, + 'portraitrawhighlightpoint' => { 109 => 0x1b }, + 'portraitrawlinear' => { 109 => 0x19 }, + 'portraitrawsaturation' => { 109 => 0x17 }, + 'portraitrawshadow' => { 109 => 0x7f }, + 'portraitrawshadowpoint' => { 109 => 0x1c }, + 'portraitrawsharpness' => { 109 => 0x1a }, + 'portraitrefiner' => { 113 => 0x302b }, + 'portraitrequest' => { 483 => 'PortraitRequest' }, + 'portraitunsharpmaskfineness' => { 109 => 0x9a }, + 'portraitunsharpmaskstrength' => { 109 => 0x98 }, + 'portraitunsharpmaskthreshold' => { 109 => 0x9c }, + 'portraitversion' => { 483 => 'PortraitVersion' }, + 'pose' => { 479 => 'Pose' }, + 'poseheadingdegrees' => { 488 => 'PoseHeadingDegrees' }, + 'posepitchdegrees' => { 488 => 'PosePitchDegrees' }, + 'posepositionx' => { 479 => [\'Pose','PosePositionX'] }, + 'posepositiony' => { 479 => [\'Pose','PosePositionY'] }, + 'posepositionz' => { 479 => [\'Pose','PosePositionZ'] }, + 'poserolldegrees' => { 488 => 'PoseRollDegrees' }, + 'poserotationw' => { 479 => [\'Pose','PoseRotationW'] }, + 'poserotationx' => { 479 => [\'Pose','PoseRotationX'] }, + 'poserotationy' => { 479 => [\'Pose','PoseRotationY'] }, + 'poserotationz' => { 479 => [\'Pose','PoseRotationZ'] }, + 'posetimestamp' => { 479 => [\'Pose','PoseTimestamp'] }, + 'positiondescriptor' => { 518 => 'positionDescriptor' }, + 'postalcode' => { 161 => 'PostalCode' }, + 'postcropvignetteamount' => { 500 => 'PostCropVignetteAmount', 502 => 'PostCropVignetteAmount' }, + 'postcropvignettefeather' => { 500 => 'PostCropVignetteFeather', 502 => 'PostCropVignetteFeather' }, + 'postcropvignettehighlightcontrast' => { 500 => 'PostCropVignetteHighlightContrast', 502 => 'PostCropVignetteHighlightContrast' }, + 'postcropvignettemidpoint' => { 500 => 'PostCropVignetteMidpoint', 502 => 'PostCropVignetteMidpoint' }, + 'postcropvignetteroundness' => { 500 => 'PostCropVignetteRoundness', 502 => 'PostCropVignetteRoundness' }, + 'postcropvignettestyle' => { 500 => 'PostCropVignetteStyle', 502 => 'PostCropVignetteStyle' }, + 'postfocusmerging' => { 340 => 0xbf }, + 'postreleaseburstlength' => { 241 => 0x714, 242 => 0x784, 313 => 0x289 }, + 'potentialface1position' => { 437 => 0xb }, + 'potentialface2position' => { 437 => 0x15 }, + 'potentialface3position' => { 437 => 0x1f }, + 'potentialface4position' => { 437 => 0x29 }, + 'potentialface5position' => { 437 => 0x33 }, + 'potentialface6position' => { 437 => 0x3d }, + 'potentialface7position' => { 437 => 0x47 }, + 'potentialface8position' => { 437 => 0x51 }, + 'powersource' => { 354 => '0.1' }, + 'poweruptime' => { 234 => 0xb6 }, + 'preaf' => { 126 => '0.2' }, + 'precaptureframes' => { 322 => 0x300 }, + 'predictor' => { 119 => 0x13d }, + 'preflashreturnstrength' => { 269 => 0x28a }, + 'prefs' => { 131 => 0xdd, 387 => 'Prefs' }, + 'prereadfastresetcount' => { 138 => 0x187e }, + 'prereleaseburstlength' => { 241 => 0x712, 242 => 0x782, 313 => 0x287 }, + 'preservedfilename' => { 530 => 'PreservedFileName' }, + 'preservedspecimen' => { 118 => 'PreservedSpecimen' }, + 'preservedspecimenmaterialsampleid' => { 118 => [\'PreservedSpecimen','PreservedSpecimenMaterialSampleID'] }, + 'presettype' => { 500 => 'PresetType', 502 => 'PresetType' }, + 'presetwhitebalance' => { 184 => 0x24, 476 => 0x8002 }, + 'presetwhitebalanceadj' => { 476 => 0x8014 }, + 'pressure' => { 119 => 0x9402, 507 => 'Pressure' }, + 'previewapplicationname' => { 119 => 0xc716 }, + 'previewapplicationversion' => { 119 => 0xc717 }, + 'previewbutton' => { 297 => ['14.1','15.1'], 298 => '15.1', 300 => '15.1', 301 => '15.1', 306 => '29.1', 307 => '14.1', 310 => '15.1', 311 => '15.1' }, + 'previewbuttonplusdials' => { 297 => ['14.2','15.2'], 298 => '15.2', 300 => '43.1', 301 => '43.1', 306 => '31.2', 310 => '43.1', 311 => '43.1' }, + 'previewcolorspace' => { 119 => 0xc71a }, + 'previewcropbottom' => { 386 => 0xef }, + 'previewcropleft' => { 386 => 0xec }, + 'previewcropright' => { 386 => 0xee }, + 'previewcroptop' => { 386 => 0xed }, + 'previewdate' => { 398 => 0x0 }, + 'previewdatetime' => { 119 => 0xc71b }, + 'previewimage' => { 113 => 0x2000, 114 => 'Exif-PreviewImage', 120 => 'PreviewImage', 122 => 0x4, 166 => 'data', 186 => 0x81, 322 => 0x280, 338 => 0x300, 400 => 'mcvr', 440 => 0x2001 }, + 'previewimageborders' => { 375 => 0x3e }, + 'previewimageheight' => { 73 => 0x4, 142 => 0xfa58 }, + 'previewimagelength' => { 73 => 0x2, 113 => 0x3, 119 => [0x117,0x202], 186 => 0x89, 253 => 0x202, 317 => 0x102, 322 => 0x1037, 375 => 0x3, 406 => 0x1e, 411 => 0x3, 417 => [0x1b,0x1d], 430 => 0x202 }, + 'previewimagename' => { 166 => '1Name' }, + 'previewimagesize' => { 113 => 0x2, 147 => 0x2, 166 => 'ImageSize', 375 => 0x2, 417 => [0x1c,0x1e], 440 => 0xb02c, 476 => 0x9012 }, + 'previewimagestart' => { 73 => 0x5, 113 => 0x4, 119 => [0x111,0x201], 186 => 0x88, 253 => 0x201, 317 => 0x101, 322 => 0x1036, 375 => 0x4, 406 => 0x1c, 411 => 0x2, 417 => [0x1a,0x1c], 430 => 0x201 }, + 'previewimagetype' => { 166 => '0Type' }, + 'previewimagevalid' => { 317 => 0x100, 322 => 0x1035 }, + 'previewimagewidth' => { 73 => 0x3, 142 => 0xfa57 }, + 'previewquality' => { 73 => 0x1 }, + 'previewsettingsdigest' => { 119 => 0xc719 }, + 'previewsettingsname' => { 119 => 0xc718 }, + 'primaryafpoint' => { 193 => [0x38,0x44,0x7,0x8] }, + 'primarychromaticities' => { 119 => 0x13f, 525 => 'PrimaryChromaticities' }, + 'primaryftp' => { 490 => 'PrimaryFTP' }, + 'primaryslot' => { 243 => 0x25, 261 => 0x2 }, + 'printim' => { 119 => 0xc4a5 }, + 'prioritysetinawb' => { 440 => 0x202b }, + 'prioritysetupshutterrelease' => { 184 => 0x1d, 427 => 0x28 }, + 'privatertkinfo' => { 492 => 'privateRTKInfo' }, + 'processbordercolsleft' => { 138 => 0xc61 }, + 'processbordercolsright' => { 138 => 0xc62 }, + 'processborderrowsbottom' => { 138 => 0xc64 }, + 'processborderrowstop' => { 138 => 0xc63 }, + 'processingsoftware' => { 119 => 0xb }, + 'processversion' => { 500 => 'ProcessVersion', 502 => 'ProcessVersion' }, + 'producer' => { 179 => 'WM/Producer', 326 => 'Producer', 392 => "\xa9prd", 394 => 'producer', 400 => "\xa9prd", 516 => 'Producer' }, + 'producerkeywords' => { 400 => "\xa9pdk" }, + 'productcode' => { 519 => 'productCode' }, + 'productid' => { 132 => 0x32, 392 => 'prID', 518 => 'productID' }, + 'productidtype' => { 518 => 'productIDType' }, + 'productinimage' => { 514 => 'ProductInImage' }, + 'productinimagedescription' => { 514 => [\'ProductInImage','ProductInImageProductDescription'] }, + 'productinimagegtin' => { 514 => [\'ProductInImage','ProductInImageProductGTIN'] }, + 'productinimagename' => { 514 => [\'ProductInImage','ProductInImageProductName'] }, + 'productinimageproductid' => { 514 => [\'ProductInImage','ProductInImageProductId'] }, + 'productioncode' => { 355 => 0x2 }, + 'productorserviceconstraints' => { 327 => 'ProductOrServiceConstraints' }, + 'productversion' => { 392 => 'VERS' }, + 'profession' => { 519 => 'profession' }, + 'profile' => { 479 => [\'Profiles','ProfilesProfile'] }, + 'profilecalibrationsig' => { 119 => 0xc6f4 }, + 'profilecameraindices' => { 479 => [\'Profiles','ProfilesProfileCameraIndices'] }, + 'profilecopyright' => { 119 => 0xc6fe }, + 'profileembedpolicy' => { 119 => 0xc6fd }, + 'profilegaintablemap' => { 119 => 0xcd2d }, + 'profilehuesatmapdata1' => { 119 => 0xc6fa }, + 'profilehuesatmapdata2' => { 119 => 0xc6fb }, + 'profilehuesatmapdata3' => { 119 => 0xcd39 }, + 'profilehuesatmapdims' => { 119 => 0xc6f9 }, + 'profilehuesatmapencoding' => { 119 => 0xc7a3 }, + 'profilelooktabledata' => { 119 => 0xc726 }, + 'profilelooktabledims' => { 119 => 0xc725 }, + 'profilelooktableencoding' => { 119 => 0xc7a4 }, + 'profilename' => { 119 => 0xc6f8, 328 => 'iCCP-name' }, + 'profiles' => { 479 => 'Profiles' }, + 'profiletonecurve' => { 119 => 0xc6fc }, + 'profiletype' => { 479 => [\'Profiles','ProfilesProfileType'] }, + 'programiso' => { 340 => 0x3c }, + 'programline' => { 356 => '1.1' }, + 'programmode' => { 189 => 0x5 }, + 'programshift' => { 234 => 0xd }, + 'programversion' => { 131 => 0x46 }, + 'prohibits' => { 498 => 'prohibits' }, + 'projectiontype' => { 488 => 'ProjectionType', 489 => 'ProjectionType' }, + 'projectname' => { 529 => 'projectName' }, + 'projectref' => { 529 => 'projectRef' }, + 'projectrefpath' => { 529 => [\'projectRef','projectRefPath'] }, + 'projectreftype' => { 529 => [\'projectRef','projectRefType'] }, + 'promotionurl' => { 179 => 'WM/PromotionURL' }, + 'propertyreleaseid' => { 327 => 'PropertyReleaseID' }, + 'propertyreleasestatus' => { 327 => 'PropertyReleaseStatus' }, + 'provider' => { 179 => 'WM/Provider' }, + 'province-state' => { 131 => 0x5f }, + 'publicationdate' => { 519 => 'publicationDate' }, + 'publicationdatea-platform' => { 519 => [\'publicationDate','publicationDateA-platform'] }, + 'publicationdatedate' => { 519 => [\'publicationDate','publicationDateDate'] }, + 'publicationdisplaydate' => { 519 => 'publicationDisplayDate' }, + 'publicationdisplaydatea-platform' => { 519 => [\'publicationDisplayDate','publicationDisplayDateA-platform'] }, + 'publicationdisplaydatedate' => { 519 => [\'publicationDisplayDate','publicationDisplayDateDate'] }, + 'publicationevent' => { 514 => 'PublicationEvent' }, + 'publicationeventdate' => { 514 => [\'PublicationEvent','PublicationEventDate'] }, + 'publicationeventidentifier' => { 514 => [\'PublicationEvent','PublicationEventIdentifier'] }, + 'publicationeventname' => { 514 => [\'PublicationEvent','PublicationEventName'] }, + 'publicationname' => { 519 => 'publicationName' }, + 'publisher' => { 179 => 'WM/Publisher', 392 => "\xa9pub", 394 => 'publisher', 503 => 'publisher' }, + 'publishingfrequency' => { 519 => 'publishingFrequency' }, + 'pulldown' => { 529 => 'pullDown' }, + 'purchasedate' => { 392 => 'purd' }, + 'purplehsl' => { 103 => 0x20916 }, + 'pxshiftperiphedgenr' => { 476 => 0x9013 }, + 'pxshiftperiphedgenrvalue' => { 476 => 0x9014 }, + 'quality' => { 0 => 0x1, 34 => 0x3, 112 => 0x2, 113 => 0x3002, 127 => 0x1000, 140 => 0x9, 234 => 0x4, 280 => 0x3, 322 => 0x201, 335 => 0x300, 375 => 0x8, 382 => 0x2, 417 => 0x16, 427 => 0x56, 428 => 0x56, 429 => 0xb, 440 => [0x102,0x202e] }, + 'quality2' => { 449 => 0x1170, 450 => 0x1174, 451 => 0x1150, 453 => 0x11a8, 454 => 0x1060, 455 => 0x258, 456 => 0x258, 457 => 0x247, 463 => 0x29, 464 => 0x25, 465 => 0x2a }, + 'qualitybutton' => { 314 => 0x17d, 315 => 0x195 }, + 'qualitybuttonplaybackmode' => { 314 => 0x1bf, 315 => 0x1d7 }, + 'qualityhint' => { 1 => 0x1a }, + 'qualitymode' => { 113 => 0x8 }, + 'quantizationmethod' => { 133 => 0x78 }, + 'quickadjust' => { 293 => 0x2a }, + 'quickcontroldialinmeter' => { 84 => 0x703 }, + 'quickfix' => { 290 => 0x416391c6 }, + 'quickshot' => { 416 => 0x213 }, + 'quiettime' => { 138 => 0x188a }, + 'rads' => { 400 => 'rads' }, + 'rangefinder' => { 302 => '4.1', 303 => '5.1', 304 => '5.1' }, + 'rangemask' => { 500 => 'RangeMaskMapInfo', 502 => 'RangeMaskMapInfo' }, + 'rangemaskmapinfo' => { 500 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfo'], 502 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfo'] }, + 'rangemaskmapinfolabmax' => { 500 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfoLabMax'], 502 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfoLabMax'] }, + 'rangemaskmapinfolabmin' => { 500 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfoLabMin'], 502 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfoLabMin'] }, + 'rangemaskmapinfolumeq' => { 500 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfoLumEq'], 502 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfoLumEq'] }, + 'rangemaskmapinforgbmax' => { 500 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfoRGBMax'], 502 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfoRGBMax'] }, + 'rangemaskmapinforgbmin' => { 500 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfoRGBMin'], 502 => [\'RangeMaskMapInfo','RangeMaskMapInfoRangeMaskMapInfoRGBMin'] }, + 'rasterizedcaption' => { 131 => 0x7d }, + 'rating' => { 119 => 0x4746, 127 => 0x1431, 386 => 0xdf, 392 => 'rtng', 400 => 'rtng', 440 => 0x2002, 495 => 'rating', 504 => 'rating', 514 => 'Rating', 519 => 'rating', 527 => 'Rating' }, + 'ratingpercent' => { 119 => 0x4749, 178 => 'Rating', 392 => 'rate', 527 => 'RatingPercent' }, + 'ratingregion' => { 514 => [\'Rating','RatingRatingRegion'] }, + 'ratingregioncity' => { 514 => [\'Rating','RatingRatingRegionCity'] }, + 'ratingregioncountrycode' => { 514 => [\'Rating','RatingRatingRegionCountryCode'] }, + 'ratingregioncountryname' => { 514 => [\'Rating','RatingRatingRegionCountryName'] }, + 'ratingregiongpsaltitude' => { 514 => [\'Rating','RatingRatingRegionGPSAltitude'] }, + 'ratingregiongpslatitude' => { 514 => [\'Rating','RatingRatingRegionGPSLatitude'] }, + 'ratingregiongpslongitude' => { 514 => [\'Rating','RatingRatingRegionGPSLongitude'] }, + 'ratingregionidentifier' => { 514 => [\'Rating','RatingRatingRegionIdentifier'] }, + 'ratingregionlocationid' => { 514 => [\'Rating','RatingRatingRegionLocationId'] }, + 'ratingregionlocationname' => { 514 => [\'Rating','RatingRatingRegionLocationName'] }, + 'ratingregionprovincestate' => { 514 => [\'Rating','RatingRatingRegionProvinceState'] }, + 'ratingregionsublocation' => { 514 => [\'Rating','RatingRatingRegionSublocation'] }, + 'ratingregionworldregion' => { 514 => [\'Rating','RatingRatingRegionWorldRegion'] }, + 'ratingscalemaxvalue' => { 514 => [\'Rating','RatingRatingScaleMaxValue'] }, + 'ratingscaleminvalue' => { 514 => [\'Rating','RatingRatingScaleMinValue'] }, + 'ratingsourcelink' => { 514 => [\'Rating','RatingRatingSourceLink'] }, + 'ratingvalue' => { 514 => [\'Rating','RatingRatingValue'] }, + 'ratingvaluelogolink' => { 514 => [\'Rating','RatingRatingValueLogoLink'] }, + 'rawandjpgrecording' => { 82 => 0x8, 186 => 0x109, 356 => 0xd }, + 'rawbrightnessadj' => { 103 => 0x20001, 108 => 0x38 }, + 'rawburstimagecount' => { 75 => 0x2 }, + 'rawburstimagenum' => { 75 => 0x1 }, + 'rawcoloradj' => { 108 => 0x2e }, + 'rawcropbottom' => { 386 => 0xd4 }, + 'rawcropleft' => { 386 => 0xd1 }, + 'rawcropright' => { 386 => 0xd3 }, + 'rawcroptop' => { 386 => 0xd2 }, + 'rawcustomsaturation' => { 108 => 0x30 }, + 'rawcustomtone' => { 108 => 0x34 }, + 'rawdata' => { 414 => 0xa048 }, + 'rawdatabyteorder' => { 414 => 0x40 }, + 'rawdatacfapattern' => { 414 => 0x50 }, + 'rawdatauniqueid' => { 119 => 0xc65d }, + 'rawdepth' => { 188 => 0x10 }, + 'rawdevartfilter' => { 324 => 0x121 }, + 'rawdevautogradation' => { 324 => 0x119 }, + 'rawdevcolorspace' => { 323 => 0x108, 324 => 0x109 }, + 'rawdevcontrastvalue' => { 323 => 0x106, 324 => 0x105 }, + 'rawdeveditstatus' => { 323 => 0x10b }, + 'rawdevelopingsoftware' => { 119 => 0xa43a }, + 'rawdevelopmentprocess' => { 375 => 0x62 }, + 'rawdevengine' => { 323 => 0x109, 324 => 0x10b }, + 'rawdevexposurebiasvalue' => { 323 => 0x100, 324 => 0x100 }, + 'rawdevgradation' => { 324 => 0x112 }, + 'rawdevgraypoint' => { 323 => 0x103, 324 => 0x104 }, + 'rawdevmemorycoloremphasis' => { 323 => 0x105, 324 => 0x108 }, + 'rawdevnoisereduction' => { 323 => 0x10a, 324 => 0x10a }, + 'rawdevpicturemode' => { 324 => 0x10c }, + 'rawdevpm_bwfilter' => { 324 => 0x110 }, + 'rawdevpmcontrast' => { 324 => 0x10e }, + 'rawdevpmnoisefilter' => { 324 => 0x120 }, + 'rawdevpmpicturetone' => { 324 => 0x111 }, + 'rawdevpmsaturation' => { 324 => 0x10d }, + 'rawdevpmsharpness' => { 324 => 0x10f }, + 'rawdevsaturation3' => { 324 => 0x113 }, + 'rawdevsaturationemphasis' => { 323 => 0x104, 324 => 0x107 }, + 'rawdevsettings' => { 323 => 0x10c }, + 'rawdevsharpnessvalue' => { 323 => 0x107, 324 => 0x106 }, + 'rawdevversion' => { 323 => 0x0, 324 => 0x0 }, + 'rawdevwbfineadjustment' => { 323 => 0x102, 324 => 0x103 }, + 'rawdevwhitebalance' => { 324 => 0x101 }, + 'rawdevwhitebalancevalue' => { 323 => 0x101, 324 => 0x102 }, + 'rawfile' => { 119 => 0xfe4c }, + 'rawfilename' => { 500 => 'RawFileName', 502 => 'RawFileName' }, + 'rawfiletype' => { 440 => 0x2029 }, + 'rawformat' => { 345 => 0x2d, 384 => 0x10e }, + 'rawimagecenter' => { 234 => 0x99 }, + 'rawimagedigest' => { 119 => 0xc71c }, + 'rawimagesize' => { 375 => 0x39 }, + 'rawinfoversion' => { 325 => 0x0 }, + 'rawjpgheight' => { 99 => 0x4 }, + 'rawjpgquality' => { 57 => 0x6, 99 => 0x1 }, + 'rawjpgsize' => { 57 => 0x7, 99 => 0x2 }, + 'rawjpgwidth' => { 99 => 0x3 }, + 'rawmeasuredrggb' => { 43 => 0x26a, 45 => 0x280, 47 => 0x194, 48 => [0x1ad,0x26b] }, + 'rawrppused' => { 495 => 'rawrppused' }, + 'rawtopreviewgain' => { 119 => 0xc7a8 }, + 'readouttypeactual' => { 138 => 0x1903 }, + 'readouttyperequested' => { 138 => 0x1902 }, + 'reardisplay' => { 297 => '12.3', 298 => '6.2' }, + 'recdevice' => { 514 => 'RecDevice' }, + 'recdeviceattlensdescription' => { 514 => [\'RecDevice','RecDeviceAttLensDescription'] }, + 'recdevicemanufacturer' => { 514 => [\'RecDevice','RecDeviceManufacturer'] }, + 'recdevicemodelname' => { 514 => [\'RecDevice','RecDeviceModelName'] }, + 'recdeviceownersdeviceid' => { 514 => [\'RecDevice','RecDeviceOwnersDeviceId'] }, + 'recdeviceserialnumber' => { 514 => [\'RecDevice','RecDeviceSerialNumber'] }, + 'recipeendingpage' => { 521 => 'recipeEndingPage' }, + 'recipepagerange' => { 521 => 'recipePageRange' }, + 'recipesource' => { 521 => 'recipeSource' }, + 'recipestartingpage' => { 521 => 'recipeStartingPage' }, + 'recipetitle' => { 521 => 'recipeTitle' }, + 'recognizedface1age' => { 333 => 0x20 }, + 'recognizedface1name' => { 333 => 0x4 }, + 'recognizedface1position' => { 333 => 0x18 }, + 'recognizedface2age' => { 333 => 0x50 }, + 'recognizedface2name' => { 333 => 0x34 }, + 'recognizedface2position' => { 333 => 0x48 }, + 'recognizedface3age' => { 333 => 0x80 }, + 'recognizedface3name' => { 333 => 0x64 }, + 'recognizedface3position' => { 333 => 0x78 }, + 'recognizedfaceflags' => { 340 => 0x63 }, + 'recommendedexposureindex' => { 119 => 0x8832, 507 => 'RecommendedExposureIndex' }, + 'record' => { 118 => 'Record' }, + 'recordbasisofrecord' => { 118 => [\'Record','RecordBasisOfRecord'] }, + 'recordcollectioncode' => { 118 => [\'Record','RecordCollectionCode'] }, + 'recordcollectionid' => { 118 => [\'Record','RecordCollectionID'] }, + 'recorddatageneralizations' => { 118 => [\'Record','RecordDataGeneralizations'] }, + 'recorddatasetid' => { 118 => [\'Record','RecordDatasetID'] }, + 'recorddatasetname' => { 118 => [\'Record','RecordDatasetName'] }, + 'recorddisplay' => { 184 => 0x4d }, + 'recorddynamicproperties' => { 118 => [\'Record','RecordDynamicProperties'] }, + 'recordid' => { 97 => 0x1804 }, + 'recordinformationwithheld' => { 118 => [\'Record','RecordInformationWithheld'] }, + 'recordingcopyright' => { 400 => "\xa9phg" }, + 'recordingformat' => { 407 => 0x1000 }, + 'recordingmode' => { 112 => 0x1, 382 => 0x1 }, + 'recordinstitutioncode' => { 118 => [\'Record','RecordInstitutionCode'] }, + 'recordinstitutionid' => { 118 => [\'Record','RecordInstitutionID'] }, + 'recordlabelname' => { 400 => "\xa9lab" }, + 'recordlabelurl' => { 400 => "\xa9lal" }, + 'recordlocationdata' => { 240 => 0x660, 241 => 0x690, 242 => 0x6f8 }, + 'recordmode' => { 34 => 0x9, 113 => 0x3000 }, + 'recordownerinstitutioncode' => { 118 => [\'Record','RecordOwnerInstitutionCode'] }, + 'recordshutterrelease' => { 416 => 0x217 }, + 'redbalance' => { 322 => 0x1017, 345 => 0x11, 375 => 0x1c }, + 'redcurvelimits' => { 108 => 0x18a }, + 'redcurvepoints' => { 107 => 0x2d, 108 => 0x160 }, + 'redeyecorrection' => { 294 => 0x0 }, + 'redeyeinfo' => { 500 => 'RedEyeInfo', 502 => 'RedEyeInfo' }, + 'redeyereduction' => { 184 => 0x41, 427 => 0x6a, 429 => 0x28 }, + 'redeyeremoval' => { 340 => 0xb9 }, + 'redhsl' => { 103 => 0x20910 }, + 'redhue' => { 500 => 'RedHue', 502 => 'RedHue' }, + 'redsaturation' => { 500 => 'RedSaturation', 502 => 'RedSaturation' }, + 'reductionmatrix1' => { 119 => 0xc625 }, + 'reductionmatrix2' => { 119 => 0xc626 }, + 'reductionmatrix3' => { 119 => 0xcd3a }, + 'reelname' => { 119 => 0xc789, 400 => 'reel' }, + 'reference1' => { 512 => [\'TagStructure','TagStructureReference'] }, + 'reference2' => { 512 => [\'TagStructure','TagStructureSubLabelsReference'] }, + 'reference3' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsReference'] }, + 'reference4' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsReference'] }, + 'reference5' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsSubLabelsReference'] }, + 'reference6' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabelsReference'] }, + 'referenceblackwhite' => { 119 => 0x214, 525 => 'ReferenceBlackWhite' }, + 'referencedate' => { 131 => 0x2f }, + 'referencenumber' => { 131 => 0x32 }, + 'references' => { 157 => 'References' }, + 'referenceservice' => { 131 => 0x2d }, + 'regionappliedtodimensions' => { 172 => [\'Regions','RegionsAppliedToDimensions'] }, + 'regionappliedtodimensionsh' => { 172 => [\'Regions','RegionsAppliedToDimensionsH'] }, + 'regionappliedtodimensionsunit' => { 172 => [\'Regions','RegionsAppliedToDimensionsUnit'] }, + 'regionappliedtodimensionsw' => { 172 => [\'Regions','RegionsAppliedToDimensionsW'] }, + 'regionarea' => { 172 => [\'Regions','RegionsRegionListArea'] }, + 'regionaread' => { 172 => [\'Regions','RegionsRegionListAreaD'] }, + 'regionareah' => { 172 => [\'Regions','RegionsRegionListAreaH'] }, + 'regionareaunit' => { 172 => [\'Regions','RegionsRegionListAreaUnit'] }, + 'regionareaw' => { 172 => [\'Regions','RegionsRegionListAreaW'] }, + 'regionareax' => { 172 => [\'Regions','RegionsRegionListAreaX'] }, + 'regionareay' => { 172 => [\'Regions','RegionsRegionListAreaY'] }, + 'regionbarcodevalue' => { 172 => [\'Regions','RegionsRegionListBarCodeValue'] }, + 'regionconstraints' => { 327 => 'RegionConstraints' }, + 'regiondescription' => { 172 => [\'Regions','RegionsRegionListDescription'] }, + 'regionextensions' => { 172 => [\'Regions','RegionsRegionListExtensions'] }, + 'regionfocususage' => { 172 => [\'Regions','RegionsRegionListFocusUsage'] }, + 'regioninfo' => { 172 => 'Regions' }, + 'regioninfodateregionsvalid' => { 175 => [\'RegionInfo','RegionInfoDateRegionsValid'] }, + 'regioninfomp' => { 175 => 'RegionInfo' }, + 'regioninforegions' => { 175 => [\'RegionInfo','RegionInfoRegions'] }, + 'regionlist' => { 172 => [\'Regions','RegionsRegionList'] }, + 'regionname' => { 172 => [\'Regions','RegionsRegionListName'] }, + 'regionpersondisplayname' => { 175 => [\'RegionInfo','RegionInfoRegionsPersonDisplayName'] }, + 'regionpersonemaildigest' => { 175 => [\'RegionInfo','RegionInfoRegionsPersonEmailDigest'] }, + 'regionpersonliveidcid' => { 175 => [\'RegionInfo','RegionInfoRegionsPersonLiveIdCID'] }, + 'regionpersonsourceid' => { 175 => [\'RegionInfo','RegionInfoRegionsPersonSourceID'] }, + 'regionrectangle' => { 175 => [\'RegionInfo','RegionInfoRegionsRectangle'] }, + 'regionrotation' => { 172 => [\'Regions','RegionsRegionListRotation'] }, + 'regionseealso' => { 172 => [\'Regions','RegionsRegionListSeeAlso'] }, + 'regiontype' => { 172 => [\'Regions','RegionsRegionListType'] }, + 'registryentryrole' => { 514 => [\'RegistryId','RegistryIdRegEntryRole'] }, + 'registryid' => { 514 => 'RegistryId' }, + 'registryitemid' => { 514 => [\'RegistryId','RegistryIdRegItemId'] }, + 'registryorganisationid' => { 514 => [\'RegistryId','RegistryIdRegOrgId'] }, + 'relatedaudiofile' => { 155 => 'data' }, + 'relatedaudiofilename' => { 155 => '1Name' }, + 'relatedaudiofiletype' => { 155 => '0Type' }, + 'relatedimagefileformat' => { 119 => 0x1000 }, + 'relatedimageheight' => { 119 => 0x1002 }, + 'relatedimagewidth' => { 119 => 0x1001 }, + 'relatedresourceid' => { 118 => [\'ResourceRelationship','ResourceRelationshipRelatedResourceID'] }, + 'relatedsoundfile' => { 119 => 0xa004, 506 => 'RelatedSoundFile' }, + 'relatedvideofile' => { 169 => 'data' }, + 'relatedvideofilename' => { 169 => '1Name' }, + 'relatedvideofiletype' => { 169 => '0Type' }, + 'relation' => { 503 => 'relation' }, + 'relationshipaccordingto' => { 118 => [\'ResourceRelationship','ResourceRelationshipRelationshipAccordingTo'] }, + 'relationshipestablisheddate' => { 118 => [\'ResourceRelationship','ResourceRelationshipRelationshipEstablishedDate'] }, + 'relationshipofresource' => { 118 => [\'ResourceRelationship','ResourceRelationshipRelationshipOfResource'] }, + 'relationshipofresourceid' => { 118 => [\'ResourceRelationship','ResourceRelationshipRelationshipOfResourceID'] }, + 'relationshipremarks' => { 118 => [\'ResourceRelationship','ResourceRelationshipRelationshipRemarks'] }, + 'relativealtitude' => { 116 => 'RelativeAltitude' }, + 'relativepeakaudiofilepath' => { 529 => 'relativePeakAudioFilePath' }, + 'relativetimestamp' => { 529 => 'relativeTimestamp' }, + 'relativetimestampscale' => { 529 => [\'relativeTimestamp','relativeTimestampScale'] }, + 'relativetimestampvalue' => { 529 => [\'relativeTimestamp','relativeTimestampValue'] }, + 'releasebuttontousedial' => { 297 => '17.8', 298 => '18.5', 300 => '18.4', 301 => '18.4', 306 => '33.8', 307 => '17.6', 310 => '18.5', 311 => '18.4' }, + 'releasedate' => { 131 => 0x1e, 392 => 'rldt', 495 => 'ReleaseDate', 529 => 'releaseDate' }, + 'releasemode' => { 113 => 0x3001, 238 => 0x5c, 271 => 0x184d, 440 => 0xb049 }, + 'releasemode2' => { 449 => 0x112c, 450 => [0x112c,0x8], 451 => [0x1108,0x8], 452 => [0x1184,0x8], 453 => [0x1160,0x8], 454 => [0x4,0x1018], 455 => [0x4,0x210], 456 => [0x4,0x210], 457 => [0x4,0x208], 459 => [0x67,0x3f], 460 => [0x6b,0x6d,0x73,0x4b], 461 => [0x6b,0x4b], 462 => 0x1f, 463 => 0x10, 464 => 0x10, 465 => 0x9, 472 => 0x34 }, + 'releasemode3' => { 449 => 0x1128, 450 => 0x1128, 451 => 0x1104, 452 => 0x1180, 453 => 0x115c, 454 => 0x1014, 455 => 0x20c, 456 => 0x20c, 457 => 0x204 }, + 'releaseready' => { 514 => 'ReleaseReady' }, + 'releasesetting' => { 97 => 0x1016 }, + 'releasetime' => { 131 => 0x23, 495 => 'ReleaseTime' }, + 'remoteonduration' => { 299 => '3.4', 302 => '17.2', 303 => '18.2', 304 => '18.2', 305 => '18.2', 307 => '18.2', 308 => '4.3', 312 => '19.2' }, + 'renditionclass' => { 530 => 'RenditionClass' }, + 'renditionof' => { 530 => 'RenditionOf' }, + 'renditionofalternatepaths' => { 530 => [\'RenditionOf','RenditionOfAlternatePaths'] }, + 'renditionofdocumentid' => { 530 => [\'RenditionOf','RenditionOfDocumentID'] }, + 'renditionoffilepath' => { 530 => [\'RenditionOf','RenditionOfFilePath'] }, + 'renditionoffrompart' => { 530 => [\'RenditionOf','RenditionOfFromPart'] }, + 'renditionofinstanceid' => { 530 => [\'RenditionOf','RenditionOfInstanceID'] }, + 'renditionoflastmodifydate' => { 530 => [\'RenditionOf','RenditionOfLastModifyDate'] }, + 'renditionoflasturl' => { 530 => [\'RenditionOf','RenditionOfLastURL'] }, + 'renditionoflinkcategory' => { 530 => [\'RenditionOf','RenditionOfLinkCategory'] }, + 'renditionoflinkform' => { 530 => [\'RenditionOf','RenditionOfLinkForm'] }, + 'renditionofmanager' => { 530 => [\'RenditionOf','RenditionOfManager'] }, + 'renditionofmanagervariant' => { 530 => [\'RenditionOf','RenditionOfManagerVariant'] }, + 'renditionofmanageto' => { 530 => [\'RenditionOf','RenditionOfManageTo'] }, + 'renditionofmanageui' => { 530 => [\'RenditionOf','RenditionOfManageUI'] }, + 'renditionofmaskmarkers' => { 530 => [\'RenditionOf','RenditionOfMaskMarkers'] }, + 'renditionoforiginaldocumentid' => { 530 => [\'RenditionOf','RenditionOfOriginalDocumentID'] }, + 'renditionofpartmapping' => { 530 => [\'RenditionOf','RenditionOfPartMapping'] }, + 'renditionofplacedresolutionunit' => { 530 => [\'RenditionOf','RenditionOfPlacedResolutionUnit'] }, + 'renditionofplacedxresolution' => { 530 => [\'RenditionOf','RenditionOfPlacedXResolution'] }, + 'renditionofplacedyresolution' => { 530 => [\'RenditionOf','RenditionOfPlacedYResolution'] }, + 'renditionofrenditionclass' => { 530 => [\'RenditionOf','RenditionOfRenditionClass'] }, + 'renditionofrenditionparams' => { 530 => [\'RenditionOf','RenditionOfRenditionParams'] }, + 'renditionoftopart' => { 530 => [\'RenditionOf','RenditionOfToPart'] }, + 'renditionofversionid' => { 530 => [\'RenditionOf','RenditionOfVersionID'] }, + 'renditionparams' => { 530 => 'RenditionParams' }, + 'repeatingflashcount' => { 212 => 0xd, 213 => 0xe, 214 => 0xe, 215 => 0xe, 216 => 0xe, 217 => 0xe, 306 => '17.2', 307 => '24.2', 308 => '9.2', 309 => '24.2', 312 => '25.2' }, + 'repeatingflashcountbuilt-in' => { 278 => 0x4db }, + 'repeatingflashcountexternal' => { 278 => 0x4c3 }, + 'repeatingflashoutput' => { 306 => '17.1', 307 => '24.1', 308 => '9.1', 309 => '24.1', 312 => '25.1' }, + 'repeatingflashoutputexternal' => { 278 => 0x4c0 }, + 'repeatingflashrate' => { 212 => 0xc, 213 => 0xd, 214 => 0xd, 215 => 0xd, 216 => 0xd, 217 => 0xd, 306 => '18.1', 307 => '25.1', 308 => '10.1', 309 => '25.1', 312 => '26.1' }, + 'repeatingflashratebuilt-in' => { 278 => 0x4da }, + 'repeatingflashrateexternal' => { 278 => 0x4c2 }, + 'requirements' => { 400 => "\xa9req" }, + 'requires' => { 498 => 'requires' }, + 'resampleparams' => { 529 => 'resampleParams' }, + 'resampleparamsquality' => { 529 => [\'resampleParams','resampleParamsQuality'] }, + 'resamplingkerneldenominators050' => { 138 => 0xe50 }, + 'resamplingkerneldenominators067' => { 138 => 0xe4f }, + 'resamplingkerneldenominators100' => { 138 => 0xe51 }, + 'resaved' => { 416 => 0x21e }, + 'resetblacksegrows' => { 138 => 0x181a }, + 'resolution' => { 162 => 'Resolution' }, + 'resolutionmode' => { 417 => [0x87,0x4] }, + 'resolutionunit' => { 119 => 0x128, 134 => 0x2, 525 => 'ResolutionUnit' }, + 'resourceid' => { 118 => [\'ResourceRelationship','ResourceRelationshipResourceID'] }, + 'resourcerelationship' => { 118 => 'ResourceRelationship' }, + 'resourcerelationshipid' => { 118 => [\'ResourceRelationship','ResourceRelationshipResourceRelationshipID'] }, + 'restrictdrivemodes' => { 84 => 0x612 }, + 'restrictions' => { 522 => 'restrictions' }, + 'retouchareafeather' => { 500 => [\'RetouchAreas','RetouchAreasFeather'], 502 => [\'RetouchAreas','RetouchAreasFeather'] }, + 'retouchareamaskalpha' => { 500 => [\'RetouchAreas','RetouchAreasMasksAlpha'], 502 => [\'RetouchAreas','RetouchAreasMasksAlpha'] }, + 'retouchareamaskangle' => { 500 => [\'RetouchAreas','RetouchAreasMasksAngle'], 502 => [\'RetouchAreas','RetouchAreasMasksAngle'] }, + 'retouchareamaskbottom' => { 500 => [\'RetouchAreas','RetouchAreasMasksBottom'], 502 => [\'RetouchAreas','RetouchAreasMasksBottom'] }, + 'retouchareamaskcentervalue' => { 500 => [\'RetouchAreas','RetouchAreasMasksCenterValue'], 502 => [\'RetouchAreas','RetouchAreasMasksCenterValue'] }, + 'retouchareamaskcenterweight' => { 500 => [\'RetouchAreas','RetouchAreasMasksCenterWeight'], 502 => [\'RetouchAreas','RetouchAreasMasksCenterWeight'] }, + 'retouchareamaskdabs' => { 500 => [\'RetouchAreas','RetouchAreasMasksDabs'], 502 => [\'RetouchAreas','RetouchAreasMasksDabs'] }, + 'retouchareamaskfeather' => { 500 => [\'RetouchAreas','RetouchAreasMasksFeather'], 502 => [\'RetouchAreas','RetouchAreasMasksFeather'] }, + 'retouchareamaskflipped' => { 500 => [\'RetouchAreas','RetouchAreasMasksFlipped'], 502 => [\'RetouchAreas','RetouchAreasMasksFlipped'] }, + 'retouchareamaskflow' => { 500 => [\'RetouchAreas','RetouchAreasMasksFlow'], 502 => [\'RetouchAreas','RetouchAreasMasksFlow'] }, + 'retouchareamaskfullx' => { 500 => [\'RetouchAreas','RetouchAreasMasksFullX'], 502 => [\'RetouchAreas','RetouchAreasMasksFullX'] }, + 'retouchareamaskfully' => { 500 => [\'RetouchAreas','RetouchAreasMasksFullY'], 502 => [\'RetouchAreas','RetouchAreasMasksFullY'] }, + 'retouchareamaskinputdigest' => { 500 => [\'RetouchAreas','RetouchAreasMasksInputDigest'], 502 => [\'RetouchAreas','RetouchAreasMasksInputDigest'] }, + 'retouchareamaskleft' => { 500 => [\'RetouchAreas','RetouchAreasMasksLeft'], 502 => [\'RetouchAreas','RetouchAreasMasksLeft'] }, + 'retouchareamaskmaskactive' => { 500 => [\'RetouchAreas','RetouchAreasMasksMaskActive'], 502 => [\'RetouchAreas','RetouchAreasMasksMaskActive'] }, + 'retouchareamaskmaskblendmode' => { 500 => [\'RetouchAreas','RetouchAreasMasksMaskBlendMode'], 502 => [\'RetouchAreas','RetouchAreasMasksMaskBlendMode'] }, + 'retouchareamaskmaskdigest' => { 500 => [\'RetouchAreas','RetouchAreasMasksMaskDigest'], 502 => [\'RetouchAreas','RetouchAreasMasksMaskDigest'] }, + 'retouchareamaskmaskinverted' => { 500 => [\'RetouchAreas','RetouchAreasMasksMaskInverted'], 502 => [\'RetouchAreas','RetouchAreasMasksMaskInverted'] }, + 'retouchareamaskmaskname' => { 500 => [\'RetouchAreas','RetouchAreasMasksMaskName'], 502 => [\'RetouchAreas','RetouchAreasMasksMaskName'] }, + 'retouchareamaskmasks' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasks'], 502 => [\'RetouchAreas','RetouchAreasMasksMasks'] }, + 'retouchareamaskmasksalpha' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksAlpha'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksAlpha'] }, + 'retouchareamaskmasksangle' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksAngle'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksAngle'] }, + 'retouchareamaskmasksbottom' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksBottom'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksBottom'] }, + 'retouchareamaskmaskscentervalue' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksCenterValue'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksCenterValue'] }, + 'retouchareamaskmaskscenterweight' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksCenterWeight'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksCenterWeight'] }, + 'retouchareamaskmasksdabs' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksDabs'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksDabs'] }, + 'retouchareamaskmasksfeather' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksFeather'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksFeather'] }, + 'retouchareamaskmasksflipped' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksFlipped'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksFlipped'] }, + 'retouchareamaskmasksflow' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksFlow'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksFlow'] }, + 'retouchareamaskmasksfullx' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksFullX'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksFullX'] }, + 'retouchareamaskmasksfully' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksFullY'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksFullY'] }, + 'retouchareamaskmasksinputdigest' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksInputDigest'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksInputDigest'] }, + 'retouchareamaskmasksleft' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksLeft'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksLeft'] }, + 'retouchareamaskmasksmaskactive' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksMaskActive'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksMaskActive'] }, + 'retouchareamaskmasksmaskblendmode' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksMaskBlendMode'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksMaskBlendMode'] }, + 'retouchareamaskmasksmaskdigest' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksMaskDigest'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksMaskDigest'] }, + 'retouchareamaskmasksmaskinverted' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksMaskInverted'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksMaskInverted'] }, + 'retouchareamaskmasksmaskname' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksMaskName'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksMaskName'] }, + 'retouchareamaskmasksmasksubtype' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksMaskSubType'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksMaskSubType'] }, + 'retouchareamaskmasksmasksyncid' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksMaskSyncID'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksMaskSyncID'] }, + 'retouchareamaskmasksmaskversion' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksMaskVersion'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksMaskVersion'] }, + 'retouchareamaskmasksmidpoint' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksMidpoint'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksMidpoint'] }, + 'retouchareamaskmasksorigin' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksOrigin'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksOrigin'] }, + 'retouchareamaskmasksperimetervalue' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksPerimeterValue'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksPerimeterValue'] }, + 'retouchareamaskmasksradius' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksRadius'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksRadius'] }, + 'retouchareamaskmasksreferencepoint' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksReferencePoint'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksReferencePoint'] }, + 'retouchareamaskmasksright' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksRight'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksRight'] }, + 'retouchareamaskmasksroundness' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksRoundness'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksRoundness'] }, + 'retouchareamaskmaskssizex' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksSizeX'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksSizeX'] }, + 'retouchareamaskmaskssizey' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksSizeY'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksSizeY'] }, + 'retouchareamaskmaskstop' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksTop'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksTop'] }, + 'retouchareamaskmasksubtype' => { 500 => [\'RetouchAreas','RetouchAreasMasksMaskSubType'], 502 => [\'RetouchAreas','RetouchAreasMasksMaskSubType'] }, + 'retouchareamaskmasksvalue' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksMaskValue'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksMaskValue'] }, + 'retouchareamaskmasksversion' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksVersion'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksVersion'] }, + 'retouchareamaskmaskswhat' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksWhat'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksWhat'] }, + 'retouchareamaskmaskswholeimagearea' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksWholeImageArea'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksWholeImageArea'] }, + 'retouchareamaskmasksx' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksX'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksX'] }, + 'retouchareamaskmasksy' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksY'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksY'] }, + 'retouchareamaskmasksyncid' => { 500 => [\'RetouchAreas','RetouchAreasMasksMaskSyncID'], 502 => [\'RetouchAreas','RetouchAreasMasksMaskSyncID'] }, + 'retouchareamaskmaskszerox' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksZeroX'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksZeroX'] }, + 'retouchareamaskmaskszeroy' => { 500 => [\'RetouchAreas','RetouchAreasMasksMasksZeroY'], 502 => [\'RetouchAreas','RetouchAreasMasksMasksZeroY'] }, + 'retouchareamaskmaskversion' => { 500 => [\'RetouchAreas','RetouchAreasMasksMaskVersion'], 502 => [\'RetouchAreas','RetouchAreasMasksMaskVersion'] }, + 'retouchareamaskmidpoint' => { 500 => [\'RetouchAreas','RetouchAreasMasksMidpoint'], 502 => [\'RetouchAreas','RetouchAreasMasksMidpoint'] }, + 'retouchareamaskorigin' => { 500 => [\'RetouchAreas','RetouchAreasMasksOrigin'], 502 => [\'RetouchAreas','RetouchAreasMasksOrigin'] }, + 'retouchareamaskperimetervalue' => { 500 => [\'RetouchAreas','RetouchAreasMasksPerimeterValue'], 502 => [\'RetouchAreas','RetouchAreasMasksPerimeterValue'] }, + 'retouchareamaskradius' => { 500 => [\'RetouchAreas','RetouchAreasMasksRadius'], 502 => [\'RetouchAreas','RetouchAreasMasksRadius'] }, + 'retouchareamaskrange' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMask'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMask'] }, + 'retouchareamaskrangeareamodels' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskAreaModels'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskAreaModels'] }, + 'retouchareamaskrangeareamodelscolorsampleinfo' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskAreaModelsColorRangeMaskAreaSampleInfo'] }, + 'retouchareamaskrangeareamodelscomponents' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskAreaModelsAreaComponents'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskAreaModelsAreaComponents'] }, + 'retouchareamaskrangecoloramount' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskColorAmount'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskColorAmount'] }, + 'retouchareamaskrangedepthfeather' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskDepthFeather'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskDepthFeather'] }, + 'retouchareamaskrangedepthmax' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskDepthMax'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskDepthMax'] }, + 'retouchareamaskrangedepthmin' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskDepthMin'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskDepthMin'] }, + 'retouchareamaskrangeinvert' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskInvert'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskInvert'] }, + 'retouchareamaskrangelumfeather' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskLumFeather'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskLumFeather'] }, + 'retouchareamaskrangeluminancedepthsampleinfo' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskLuminanceDepthSampleInfo'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskLuminanceDepthSampleInfo'] }, + 'retouchareamaskrangelummax' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskLumMax'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskLumMax'] }, + 'retouchareamaskrangelummin' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskLumMin'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskLumMin'] }, + 'retouchareamaskrangelumrange' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskLumRange'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskLumRange'] }, + 'retouchareamaskrangesampletype' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskSampleType'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskSampleType'] }, + 'retouchareamaskrangetype' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskType'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskType'] }, + 'retouchareamaskrangeversion' => { 500 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskVersion'], 502 => [\'RetouchAreas','RetouchAreasMasksCorrectionRangeMaskVersion'] }, + 'retouchareamaskreferencepoint' => { 500 => [\'RetouchAreas','RetouchAreasMasksReferencePoint'], 502 => [\'RetouchAreas','RetouchAreasMasksReferencePoint'] }, + 'retouchareamaskright' => { 500 => [\'RetouchAreas','RetouchAreasMasksRight'], 502 => [\'RetouchAreas','RetouchAreasMasksRight'] }, + 'retouchareamaskroundness' => { 500 => [\'RetouchAreas','RetouchAreasMasksRoundness'], 502 => [\'RetouchAreas','RetouchAreasMasksRoundness'] }, + 'retouchareamasks' => { 500 => [\'RetouchAreas','RetouchAreasMasks'], 502 => [\'RetouchAreas','RetouchAreasMasks'] }, + 'retouchareamasksizex' => { 500 => [\'RetouchAreas','RetouchAreasMasksSizeX'], 502 => [\'RetouchAreas','RetouchAreasMasksSizeX'] }, + 'retouchareamasksizey' => { 500 => [\'RetouchAreas','RetouchAreasMasksSizeY'], 502 => [\'RetouchAreas','RetouchAreasMasksSizeY'] }, + 'retouchareamasktop' => { 500 => [\'RetouchAreas','RetouchAreasMasksTop'], 502 => [\'RetouchAreas','RetouchAreasMasksTop'] }, + 'retouchareamaskvalue' => { 500 => [\'RetouchAreas','RetouchAreasMasksMaskValue'], 502 => [\'RetouchAreas','RetouchAreasMasksMaskValue'] }, + 'retouchareamaskversion' => { 500 => [\'RetouchAreas','RetouchAreasMasksVersion'], 502 => [\'RetouchAreas','RetouchAreasMasksVersion'] }, + 'retouchareamaskwhat' => { 500 => [\'RetouchAreas','RetouchAreasMasksWhat'], 502 => [\'RetouchAreas','RetouchAreasMasksWhat'] }, + 'retouchareamaskwholeimagearea' => { 500 => [\'RetouchAreas','RetouchAreasMasksWholeImageArea'], 502 => [\'RetouchAreas','RetouchAreasMasksWholeImageArea'] }, + 'retouchareamaskx' => { 500 => [\'RetouchAreas','RetouchAreasMasksX'], 502 => [\'RetouchAreas','RetouchAreasMasksX'] }, + 'retouchareamasky' => { 500 => [\'RetouchAreas','RetouchAreasMasksY'], 502 => [\'RetouchAreas','RetouchAreasMasksY'] }, + 'retouchareamaskzerox' => { 500 => [\'RetouchAreas','RetouchAreasMasksZeroX'], 502 => [\'RetouchAreas','RetouchAreasMasksZeroX'] }, + 'retouchareamaskzeroy' => { 500 => [\'RetouchAreas','RetouchAreasMasksZeroY'], 502 => [\'RetouchAreas','RetouchAreasMasksZeroY'] }, + 'retouchareamethod' => { 500 => [\'RetouchAreas','RetouchAreasMethod'], 502 => [\'RetouchAreas','RetouchAreasMethod'] }, + 'retouchareaoffsety' => { 500 => [\'RetouchAreas','RetouchAreasOffsetY'], 502 => [\'RetouchAreas','RetouchAreasOffsetY'] }, + 'retouchareaopacity' => { 500 => [\'RetouchAreas','RetouchAreasOpacity'], 502 => [\'RetouchAreas','RetouchAreasOpacity'] }, + 'retouchareas' => { 500 => 'RetouchAreas', 502 => 'RetouchAreas' }, + 'retouchareaseed' => { 500 => [\'RetouchAreas','RetouchAreasSeed'], 502 => [\'RetouchAreas','RetouchAreasSeed'] }, + 'retouchareasourcestate' => { 500 => [\'RetouchAreas','RetouchAreasSourceState'], 502 => [\'RetouchAreas','RetouchAreasSourceState'] }, + 'retouchareasourcex' => { 500 => [\'RetouchAreas','RetouchAreasSourceX'], 502 => [\'RetouchAreas','RetouchAreasSourceX'] }, + 'retouchareaspottype' => { 500 => [\'RetouchAreas','RetouchAreasSpotType'], 502 => [\'RetouchAreas','RetouchAreasSpotType'] }, + 'retouchhistory' => { 234 => 0x9e }, + 'retouchinfo' => { 500 => 'RetouchInfo', 502 => 'RetouchInfo' }, + 'retouchnefprocessing' => { 255 => 0x5 }, + 'retractlensonpoweroff' => { 84 => 0x814 }, + 'reuse' => { 327 => 'Reuse' }, + 'reuseallowed' => { 532 => 'ReuseAllowed' }, + 'reuseprohibited' => { 522 => 'reuseProhibited' }, + 'reverseexposurecompdial' => { 304 => '5.2' }, + 'reversefocusring' => { 313 => 0x163, 314 => 0x163, 315 => 0x17b }, + 'reverseindicators' => { 297 => '12.1', 298 => '6.1', 300 => '6.1', 301 => '6.1', 302 => '4.3', 303 => '5.2', 304 => '5.4', 306 => '33.5', 307 => '5.1', 310 => '6.1', 311 => '6.1', 312 => '6.2', 313 => 0xc1, 314 => 0xc1, 315 => 0xc1 }, + 'reverseshutterspeedaperture' => { 304 => '5.3' }, + 'revision' => { 504 => 'revision' }, + 'rflensmffocusringsensitivity' => { 84 => 0x714 }, + 'rflenstype' => { 57 => 0x3d }, + 'rgbcurvelimits' => { 108 => 0x238 }, + 'rgbcurvepoints' => { 107 => 0x7, 108 => 0x20e }, + 'rgbtables' => { 119 => 0xcd3b }, + 'richtextcomment' => { 509 => 'RichTextComment' }, + 'ricohdate' => { 406 => 0x6 }, + 'ricohimageheight' => { 406 => 0x2 }, + 'ricohimagewidth' => { 406 => 0x0 }, + 'rightalbedo' => { 491 => 'RightAlbedo' }, + 'rightascension' => { 165 => 'RightAscension' }, + 'rights' => { 503 => 'rights' }, + 'rightsagent' => { 522 => 'rightsAgent' }, + 'rightsowner' => { 522 => 'rightsOwner' }, + 'roll' => { 115 => 0x8, 400 => ['roll',"\xa9frl"] }, + 'rollangle' => { 127 => 0x144d, 247 => 0x0, 317 => 0x903, 340 => 0x90, 374 => 0x1, 412 => 0x2 }, + 'romoperationmode' => { 97 => 0x80d }, + 'rotation' => { 30 => 0x17, 31 => 0x18, 96 => 0x3, 103 => 0x10002, 108 => 0x26e, 114 => 'QuickTime-Rotation', 123 => 0x4, 165 => 'Rotation', 182 => [0x65,0x50], 183 => 0x46, 184 => 0x5a, 187 => 0x10, 256 => 0x1a, 271 => 0x3693, 277 => '590.1', 290 => 0x76a43207, 340 => 0x30, 356 => '17.2', 386 => 0xd8, 393 => 'irot', 427 => 0x3f, 428 => 0x3f, 438 => 0x10 }, + 'routedto' => { 508 => 'RoutedTo' }, + 'routing' => { 391 => 'Routing' }, + 'routingdestinations' => { 490 => 'RoutingDestinations' }, + 'routingexclusions' => { 490 => 'RoutingExclusions' }, + 'routingnotes' => { 508 => 'RoutingNotes' }, + 'rowsperstrip' => { 119 => 0x116 }, + 'rpp' => { 495 => 'rpp' }, + 'rtkflag' => { 116 => 'RtkFlag' }, + 'rtkstdhgt' => { 116 => 'RtkStdHgt' }, + 'rtkstdlat' => { 116 => 'RtkStdLat' }, + 'rtkstdlon' => { 116 => 'RtkStdLon' }, + 'safetyshift' => { 84 => 0x108 }, + 'safetyshiftinavortv' => { 82 => 0x10, 83 => 0x10, 85 => 0xf, 86 => 0x10, 89 => 0x10 }, + 'sameexposurefornewaperture' => { 84 => 0x112 }, + 'samplebits' => { 155 => 'SampleBits' }, + 'samplepagerange' => { 519 => 'samplePageRange' }, + 'samplerate' => { 155 => 'SampleRate' }, + 'samplesperpixel' => { 119 => 0x115, 345 => 0x8, 525 => 'SamplesPerPixel' }, + 'samplestructure' => { 133 => 0x5a }, + 'samsungmodelid' => { 414 => 0x3 }, + 'sanyoquality' => { 416 => 0x201 }, + 'sanyothumbnail' => { 416 => 0x100 }, + 'saturation' => { 10 => 0x6e, 12 => 0x76, 34 => 0xe, 51 => 0x1, 63 => 0x7, 112 => 0xd, 113 => [0x3013,0x1f], 119 => [0xa409,0xfe55], 127 => 0x1003, 156 => 'Saturation', 181 => 0x1f, 182 => 0x32, 183 => 0x28, 184 => 0x1a, 189 => 0x1, 234 => 0xaa, 249 => 0x35, 250 => 0x3b, 251 => 0x43, 340 => 0x40, 342 => 0x300d, 375 => 0x1f, 382 => 0xd, 401 => 0x27, 403 => 0x58, 406 => 0x28, 407 => 0x1013, 417 => 0x10, 427 => 0x1e, 428 => 0x1b, 440 => 0x2005, 500 => 'Saturation', 502 => 'Saturation', 506 => 'Saturation' }, + 'saturationadj' => { 103 => 0x20901, 108 => 0x116, 234 => 0x94, 289 => 0x1, 293 => 0x2e, 476 => 0x8016 }, + 'saturationadjustmentaqua' => { 500 => 'SaturationAdjustmentAqua', 502 => 'SaturationAdjustmentAqua' }, + 'saturationadjustmentblue' => { 500 => 'SaturationAdjustmentBlue', 502 => 'SaturationAdjustmentBlue' }, + 'saturationadjustmentgreen' => { 500 => 'SaturationAdjustmentGreen', 502 => 'SaturationAdjustmentGreen' }, + 'saturationadjustmentmagenta' => { 500 => 'SaturationAdjustmentMagenta', 502 => 'SaturationAdjustmentMagenta' }, + 'saturationadjustmentorange' => { 500 => 'SaturationAdjustmentOrange', 502 => 'SaturationAdjustmentOrange' }, + 'saturationadjustmentpurple' => { 500 => 'SaturationAdjustmentPurple', 502 => 'SaturationAdjustmentPurple' }, + 'saturationadjustmentred' => { 500 => 'SaturationAdjustmentRed', 502 => 'SaturationAdjustmentRed' }, + 'saturationadjustmentyellow' => { 500 => 'SaturationAdjustmentYellow', 502 => 'SaturationAdjustmentYellow' }, + 'saturationauto' => { 71 => 0x98 }, + 'saturationfaithful' => { 19 => 0xfe, 70 => 0x68, 71 => 0x68 }, + 'saturationlandscape' => { 19 => 0xfc, 70 => 0x38, 71 => 0x38 }, + 'saturationmonochrome' => { 70 => 0x80, 71 => 0x80 }, + 'saturationneutral' => { 19 => 0xfd, 70 => 0x50, 71 => 0x50 }, + 'saturationportrait' => { 19 => 0xfb, 70 => 0x20, 71 => 0x20 }, + 'saturationsetting' => { 325 => 0x1010, 429 => 0x11, 445 => 0x9 }, + 'saturationstandard' => { 19 => 0xfa, 70 => 0x8, 71 => 0x8 }, + 'saturationuserdef1' => { 19 => 0x100, 70 => 0x98, 71 => 0xb0 }, + 'saturationuserdef2' => { 19 => 0x101, 70 => 0xb0, 71 => 0xc8 }, + 'saturationuserdef3' => { 19 => 0x102, 70 => 0xc8, 71 => 0xe0 }, + 'saveid' => { 530 => 'SaveID' }, + 'sbaanalysiscomplete' => { 138 => 0xc35 }, + 'sbablack' => { 138 => 0xc25 }, + 'sbagmoffset' => { 138 => 0xc4a }, + 'sbagray' => { 138 => 0xc26 }, + 'sbagreenmagentabal' => { 138 => 0xc33 }, + 'sbahighgray' => { 138 => 0xc46 }, + 'sbailloffset' => { 138 => 0xc49 }, + 'sbailluminantbal' => { 138 => 0xc34 }, + 'sbalowgray' => { 138 => 0xc47 }, + 'sbaneutralbal' => { 138 => 0xc32 }, + 'sbawhite' => { 138 => 0xc27 }, + 'scaletype' => { 529 => 'scaleType' }, + 'scanimageenhancer' => { 257 => 0x60 }, + 'scanningdirection' => { 133 => 0x64 }, + 'scene' => { 400 => 'scen', 513 => 'Scene', 529 => 'scene' }, + 'scenearea' => { 320 => 0x211, 322 => 0x1031 }, + 'sceneassist' => { 234 => 0x9c }, + 'scenecapturetype' => { 119 => 0xa406, 506 => 'SceneCaptureType' }, + 'scenedetect' => { 320 => 0x210, 322 => 0x1030 }, + 'scenedetectdata' => { 320 => 0x212, 322 => 0x1033 }, + 'sceneflags' => { 1 => 0x25 }, + 'scenemode' => { 142 => 0xfa02, 186 => 0x100, 234 => 0x8f, 317 => 0x509, 322 => 0x403, 340 => 0x8001, 349 => 0xf, 440 => 0xb023 }, + 'scenemodeused' => { 144 => [0x6002,0xf002] }, + 'scenerecognition' => { 127 => 0x1425 }, + 'scenereferred' => { 510 => 'scene_referred' }, + 'sceneselect' => { 416 => 0x21f }, + 'scenetype' => { 119 => 0xa301, 506 => 'SceneType' }, + 'screentips' => { 297 => '12.7', 298 => '5.3', 306 => '13.1', 307 => '4.4', 310 => '5.4', 312 => '5.1' }, + 'scriptversion' => { 138 => 0x1770 }, + 'sdrblend' => { 500 => 'SDRBlend', 502 => 'SDRBlend' }, + 'sdrbrightness' => { 500 => 'SDRBrightness', 502 => 'SDRBrightness' }, + 'sdrcontrast' => { 500 => 'SDRContrast', 502 => 'SDRContrast' }, + 'sdrhighlights' => { 500 => 'SDRHighlights', 502 => 'SDRHighlights' }, + 'sdrshadows' => { 500 => 'SDRShadows', 502 => 'SDRShadows' }, + 'sdrwhites' => { 500 => 'SDRWhites', 502 => 'SDRWhites' }, + 'season' => { 514 => 'Season', 518 => 'season' }, + 'seasonidentifier' => { 514 => [\'Season','SeasonIdentifier'] }, + 'seasonname' => { 514 => [\'Season','SeasonName'] }, + 'seasonnumber' => { 514 => [\'Season','SeasonNumber'] }, + 'secondaryftp' => { 490 => 'SecondaryFTP' }, + 'secondaryslotfunction' => { 239 => 0x240, 240 => 0x22c, 241 => 0x240, 242 => 0x240, 260 => 0x13c, 271 => 0x1d0 }, + 'section' => { 519 => 'section' }, + 'securityclassification' => { 119 => 0x9212 }, + 'selectableafpoint' => { 84 => 0x509 }, + 'selectafareaselectionmode' => { 2 => 0xc }, + 'selectafareaselectmode' => { 84 => 0x512 }, + 'selfdata' => { 116 => 'SelfData' }, + 'selftimer' => { 34 => 0x2, 340 => 0x2e, 416 => 0x214, 449 => 0x1134, 450 => 0x1134, 451 => 0x1110, 452 => 0x118c, 453 => 0x1168, 454 => 0x1020, 455 => 0x218, 456 => 0x218, 457 => 0x210 }, + 'selftimer2' => { 77 => 0x1d }, + 'selftimerinterval' => { 307 => '19.2' }, + 'selftimermode' => { 119 => 0x882b }, + 'selftimershotcount' => { 298 => '20.2', 300 => '20.3', 301 => '20.3', 302 => '18.2', 303 => '19.2', 304 => '19.2', 305 => '19.3', 307 => '19.3', 310 => '20.3', 311 => '20.3', 312 => '20.2', 313 => 0x2d, 314 => 0x2d, 315 => 0x2d }, + 'selftimershotinterval' => { 298 => '20.3', 300 => '20.2', 301 => '20.2', 305 => '19.2', 310 => '20.2', 311 => '20.2', 313 => 0x31, 314 => 0x31, 315 => 0x31 }, + 'selftimertime' => { 97 => 0x1806, 184 => 0x1f, 297 => '18.1', 298 => '20.1', 299 => '3.3', 300 => '20.1', 301 => '20.1', 302 => '18.1', 303 => '19.1', 304 => '19.1', 305 => '19.1', 306 => '7.2', 307 => '19.1', 308 => '3.3', 310 => '20.1', 311 => '20.1', 312 => '20.1', 313 => 0x2b, 314 => 0x2b, 315 => 0x2b }, + 'sellingagency' => { 519 => 'sellingAgency' }, + 'semanticstyle' => { 1 => 0x40 }, + 'semanticstylepreset' => { 1 => 0x42 }, + 'semanticstylerenderingver' => { 1 => 0x41 }, + 'seminfo' => { 119 => 0x8546 }, + 'sensingmethod' => { 119 => 0xa217, 506 => 'SensingMethod' }, + 'sensitivityadjust' => { 375 => 0x40 }, + 'sensitivitysteps' => { 356 => ['14.3','17.4'], 358 => 0x1 }, + 'sensitivitytype' => { 119 => 0x8830, 507 => 'SensitivityType' }, + 'sensor' => { 191 => 0x665e }, + 'sensorarea' => { 322 => 0x400 }, + 'sensorareas' => { 414 => 0xa010 }, + 'sensorbitdepth' => { 342 => 0x312d }, + 'sensorbluelevel' => { 74 => 0x5 }, + 'sensorcalibration' => { 321 => 0x805 }, + 'sensorcleaning' => { 90 => 0xd }, + 'sensorfullheight' => { 137 => 0xf904 }, + 'sensorfullwidth' => { 137 => 0xf903 }, + 'sensorheight' => { 137 => 0xf901, 142 => 0xfa21, 188 => 0x8, 342 => 0x312c, 384 => 0x109, 407 => 0x1602 }, + 'sensorimageheight' => { 138 => 0x3ee }, + 'sensorimagewidth' => { 138 => 0x3ed }, + 'sensorleftborder' => { 138 => 0x3eb }, + 'sensorleftmargin' => { 384 => 0x10a }, + 'sensorpixelsize' => { 234 => 0x9a }, + 'sensorredlevel' => { 74 => 0x4 }, + 'sensorserialnumber' => { 138 => 0x9ce }, + 'sensorshield' => { 239 => 0x76b, 240 => 0x66d, 241 => 0x69d, 242 => 0x705 }, + 'sensorsize' => { 156 => 'SensorSize', 375 => 0x35 }, + 'sensortemperature' => { 320 => 0x1500, 322 => 0x1007, 380 => 0xc, 384 => 0x210, 417 => [0x39,0x55] }, + 'sensortemperature2' => { 380 => 0xe, 384 => 0x211 }, + 'sensortopborder' => { 138 => 0x3ec }, + 'sensortopmargin' => { 384 => 0x10b }, + 'sensortype' => { 340 => 0xca }, + 'sensorwidth' => { 137 => 0xf900, 142 => 0xfa20, 188 => 0xa, 342 => 0x312b, 384 => 0x108, 407 => 0x1601 }, + 'sequence' => { 401 => 0x7, 402 => 0x35, 403 => 0x36 }, + 'sequencefilenumber' => { 450 => 0x4, 451 => 0x4, 452 => 0x4, 453 => 0x4, 463 => 0xc, 464 => 0xc, 465 => 0x1a }, + 'sequenceimagenumber' => { 450 => 0x0, 451 => 0x0, 452 => 0x0, 453 => 0x0, 463 => 0x8, 464 => 0x8, 465 => 0x12, 472 => 0x24 }, + 'sequencelength' => { 463 => 0x22, 464 => 0x1e, 465 => [0x16,0x1e] }, + 'sequencename' => { 518 => 'sequenceName' }, + 'sequencenumber' => { 77 => 0x9, 113 => 0x301c, 127 => 0x1101, 140 => 0x1d, 278 => 0x51c, 340 => 0x2b, 429 => [0x10c,0x30c], 440 => 0xb04a, 518 => 'sequenceNumber' }, + 'sequenceshotinterval' => { 416 => 0x224 }, + 'sequencetotalnumber' => { 518 => 'sequenceTotalNumber' }, + 'sequentialshot' => { 416 => 0x20e }, + 'serialnumber' => { 64 => 0xc, 97 => 0x180b, 119 => [0xa431,0xfde9], 137 => 0xfa04, 139 => 0xfa00, 141 => 0xc354, 142 => 0xfa19, 153 => 0x0, 156 => 'SerialNumber', 191 => 0x5501, 234 => [0xa0,0x1d], 318 => 0x101, 322 => [0x404,0x101a], 335 => 0x303, 337 => 0x305, 342 => 0x3103, 375 => 0x229, 384 => 0x102, 385 => 0x407, 400 => ['SNum','slno'], 401 => 0x15, 402 => 0x4b, 403 => 0x7e, 407 => 0x5, 414 => 0xa002, 417 => 0x2, 440 => 0x2031, 497 => 'SerialNumber', 507 => 'BodySerialNumber' }, + 'serialnumberformat' => { 64 => 0x15, 97 => 0x183b }, + 'serialnumberhash' => { 400 => 'CAME' }, + 'series' => { 514 => 'Series' }, + 'seriesdatetime' => { 478 => 'SeriesDateTime' }, + 'seriesdescription' => { 478 => 'SeriesDescription' }, + 'seriesidentifier' => { 514 => [\'Series','SeriesIdentifier'] }, + 'seriesmodality' => { 478 => 'SeriesModality' }, + 'seriesname' => { 514 => [\'Series','SeriesName'] }, + 'seriesnumber' => { 478 => 'SeriesNumber', 519 => 'seriesNumber' }, + 'seriestitle' => { 519 => 'seriesTitle' }, + 'serviceidentifier' => { 132 => 0x1e }, + 'servingsize' => { 521 => 'servingSize' }, + 'setbuttoncrosskeysfunc' => { 87 => 0x0, 88 => 0x0 }, + 'setbuttonwhenshooting' => { 82 => 0x1, 84 => 0x704, 90 => 0xc }, + 'setclockfromlocationdata' => { 240 => 0x61d, 241 => 0x64d, 242 => 0x6b5 }, + 'setfunctionwhenshooting' => { 85 => 0x0, 86 => 0x1, 89 => 0x1 }, + 'setting' => { 518 => 'setting' }, + 'shadingcompensation' => { 317 => 0x50c, 340 => 0x8a }, + 'shadingcompensation2' => { 321 => 0x1012 }, + 'shadow' => { 417 => 0xe }, + 'shadowadj' => { 103 => 0x2030b }, + 'shadowcorrection' => { 375 => 0x79 }, + 'shadowprotection' => { 289 => 0x0 }, + 'shadows' => { 119 => 0xfe52, 440 => 0x2032, 494 => 'Shadows', 500 => 'Shadows', 502 => 'Shadows' }, + 'shadows2012' => { 500 => 'Shadows2012', 502 => 'Shadows2012' }, + 'shadowsadj' => { 476 => 0x901a }, + 'shadowscale' => { 119 => 0xc633 }, + 'shadowtint' => { 500 => 'ShadowTint', 502 => 'ShadowTint' }, + 'shadowtone' => { 127 => 0x1040 }, + 'shakereduction' => { 377 => 0x1, 378 => 0x1 }, + 'shareduserrating' => { 179 => 'WM/SharedUserRating' }, + 'sharpendetail' => { 500 => 'SharpenDetail', 502 => 'SharpenDetail' }, + 'sharpenedgemasking' => { 500 => 'SharpenEdgeMasking', 502 => 'SharpenEdgeMasking' }, + 'sharpening' => { 342 => 0x300b }, + 'sharpeningadj' => { 293 => 0x2b }, + 'sharpeningkernel' => { 138 => 0x92f }, + 'sharpenradius' => { 500 => 'SharpenRadius', 502 => 'SharpenRadius' }, + 'sharpness' => { 8 => [0x42,0x48], 10 => 0x72, 12 => 0x74, 34 => 0xf, 63 => 0x6, 74 => 0x2, 112 => 0xb, 113 => [0x3011,0x21], 119 => [0xa40a,0xfe56], 127 => 0x1001, 140 => 0x6b, 149 => 0x37, 156 => 'Sharpness', 181 => 0x21, 182 => 0x30, 183 => 0x26, 184 => 0x18, 189 => 0x3, 234 => 0x6, 249 => 0x32, 250 => 0x33, 251 => 0x39, 322 => 0x100f, 340 => 0x41, 375 => 0x21, 382 => 0xb, 401 => 0x26, 403 => 0x56, 406 => 0x22, 407 => [0x1003,0x1014], 417 => 0x11, 427 => 0x1c, 428 => 0x19, 440 => 0x2006, 472 => 0x52, 500 => 'Sharpness', 502 => 'Sharpness', 506 => 'Sharpness' }, + 'sharpnessadj' => { 103 => 0x20310, 108 => 0x25a, 476 => 0x801a }, + 'sharpnessadjon' => { 103 => '0x20310.0' }, + 'sharpnessauto' => { 71 => 0x94 }, + 'sharpnessfactor' => { 322 => 0x102a }, + 'sharpnessfaithful' => { 19 => 0xf5, 70 => 0x64, 71 => 0x64 }, + 'sharpnessfrequency' => { 8 => [0x41,0x47], 74 => 0x3 }, + 'sharpnesslandscape' => { 19 => 0xf3, 70 => 0x34, 71 => 0x34 }, + 'sharpnessmonochrome' => { 19 => 0xf6, 70 => 0x7c, 71 => 0x7c }, + 'sharpnessneutral' => { 19 => 0xf4, 70 => 0x4c, 71 => 0x4c }, + 'sharpnessovershoot' => { 476 => 0x801b }, + 'sharpnessportrait' => { 19 => 0xf2, 70 => 0x1c, 71 => 0x1c }, + 'sharpnessrange' => { 440 => 0x2035 }, + 'sharpnesssetting' => { 317 => 0x506, 325 => 0x1013, 429 => 0x12, 445 => 0xa }, + 'sharpnessstandard' => { 19 => 0xf1, 70 => 0x4, 71 => 0x4 }, + 'sharpnessstrength' => { 103 => 0x20311 }, + 'sharpnessthreshold' => { 476 => 0x801d }, + 'sharpnessundershoot' => { 476 => 0x801c }, + 'sharpnessuserdef1' => { 19 => 0xf7, 70 => 0x94, 71 => 0xac }, + 'sharpnessuserdef2' => { 19 => 0xf8, 70 => 0xac, 71 => 0xc4 }, + 'sharpnessuserdef3' => { 19 => 0xf9, 70 => 0xc4, 71 => 0xdc }, + 'shiftcols' => { 138 => 0xc70 }, + 'shootid' => { 518 => 'shootID' }, + 'shootingdistance' => { 103 => 0x20701 }, + 'shootinginfodisplay' => { 297 => '13.2', 298 => '5.1', 306 => '10.2', 307 => '4.1', 310 => '5.1', 312 => '5.3' }, + 'shootinginfomonitorofftime' => { 297 => '26.2', 298 => '22.2', 300 => '22.2', 301 => '22.2', 305 => '21.2', 306 => '9.1', 307 => '21.2', 310 => '22.2', 311 => '22.2', 312 => '22.2', 313 => 0x37, 314 => 0x37, 315 => 0x37 }, + 'shootingmode' => { 156 => 'ShootingMode', 234 => 0x89, 340 => 0x1f }, + 'shootingmodesetting' => { 299 => '5.1' }, + 'shortdescription' => { 504 => 'shortdescription' }, + 'shortdocumentid' => { 131 => 0xba }, + 'shortname' => { 500 => 'ShortName', 502 => 'ShortName' }, + 'shortownername' => { 19 => 0xac }, + 'shortreleasetimelag' => { 84 => 0x80d }, + 'shorttitle' => { 392 => '@sti' }, + 'shotdate' => { 529 => 'shotDate' }, + 'shotday' => { 529 => 'shotDay' }, + 'shotlocation' => { 529 => 'shotLocation' }, + 'shotlogdata' => { 483 => 'shot_log_data' }, + 'shotname' => { 400 => 'shot', 529 => 'shotName' }, + 'shotnumber' => { 529 => 'shotNumber' }, + 'shotnumbersincepowerup' => { 444 => 0x44e, 463 => 0x1a, 464 => 0x16, 465 => 0xa }, + 'shotnumbersincepowerup2' => { 429 => 0x200 }, + 'shotsize' => { 529 => 'shotSize' }, + 'shotsperinterval' => { 223 => 0x180, 238 => 0xb4, 239 => 0xcc, 240 => 0xc0, 241 => 0xd0, 242 => 0xd0 }, + 'showmovement' => { 392 => 'shwm' }, + 'shownevent' => { 514 => 'EventExt' }, + 'showneventidentifier' => { 514 => [\'EventExt','EventExtIdentifier'] }, + 'showneventname' => { 514 => [\'EventExt','EventExtName'] }, + 'shutter' => { 459 => 0x20, 460 => 0x26, 461 => 0x26 }, + 'shutter-aelock' => { 82 => 0x4, 84 => 0x701, 85 => 0x3, 86 => 0x4, 87 => 0x3, 88 => 0x3, 89 => 0x4, 90 => 0x2 }, + 'shutteraelbutton' => { 83 => 0x4 }, + 'shutterbuttonafonbutton' => { 84 => 0x701 }, + 'shuttercount' => { 11 => 0x176, 57 => 0x1, 234 => 0xa7, 262 => [0x6a,0x157,0x24d], 263 => 0x286, 264 => 0x279, 265 => 0x284, 266 => 0x242, 267 => 0x280, 268 => 0x276, 269 => [0x27d,0x27f], 270 => 0x246, 272 => 0x2d6, 273 => 0x321, 274 => 0xbd8, 275 => 0x287, 276 => 0x320, 277 => 0x24a, 278 => 0x5fb, 279 => 0x2d5, 375 => 0x5d, 438 => 0x846, 443 => [0x125,0x14a], 459 => 0x32, 460 => 0x3a, 461 => 0x3a, 462 => 0xa }, + 'shuttercount2' => { 459 => 0x4c, 460 => [0x50,0x52,0x58], 461 => 0x50 }, + 'shuttercount3' => { 459 => [0x1a0,0x1aa,0x1bd], 460 => [0x19f,0x1cb,0x1cd] }, + 'shuttercurtainsync' => { 82 => 0xf, 83 => 0xf, 84 => 0x305, 85 => 0xe, 86 => 0xf, 87 => 0x8, 88 => 0x8, 89 => 0xf, 90 => 0x8 }, + 'shuttermode' => { 57 => 0x17, 140 => 0x1b, 234 => 0x34 }, + 'shutterreleasebuttonae-l' => { 297 => '17.7', 298 => '18.4', 300 => '78.4', 301 => '78.4', 302 => '16.1', 303 => '17.1', 304 => '17.1', 305 => '17.1', 306 => '7.1', 307 => '17.5', 310 => '18.4', 311 => '78.4', 312 => '18.2', 313 => 0x29, 314 => 0x29, 315 => 0x29 }, + 'shutterreleasemethod' => { 97 => 0x1010 }, + 'shutterreleasenocfcard' => { 82 => 0x2, 83 => 0x2, 90 => 0xf }, + 'shutterreleasetiming' => { 97 => 0x1011 }, + 'shutterreleasewithoutlens' => { 84 => 0x711 }, + 'shutterspeedlock' => { 298 => '38.1', 300 => '38.1', 301 => '38.1', 310 => '38.1', 311 => '38.1', 313 => 0xb7, 314 => 0xb7, 315 => 0xb7 }, + 'shutterspeedrange' => { 84 => 0x10c }, + 'shutterspeedsetting' => { 184 => 0x6, 427 => 0x2f, 428 => 0x28, 429 => 0x0 }, + 'shutterspeedvalue' => { 93 => 0x1, 119 => 0x9201, 322 => 0x1000, 384 => 0x400, 506 => 'ShutterSpeedValue' }, + 'shuttertype' => { 127 => 0x1050, 340 => 0x9f, 375 => 0x87, 465 => [0x133,0x139,0x13f] }, + 'sidecarforextension' => { 517 => 'SidecarForExtension' }, + 'sigmaimpulseparameters' => { 138 => 0xe0d }, + 'sigmascalingfactorcamera' => { 138 => 0xe0c }, + 'sigmascalingfactorlowres' => { 138 => 0xe0b }, + 'sigmasizetable' => { 138 => 0xe0f }, + 'signaltonoiseratio' => { 1 => 0x27 }, + 'signaltonoiseratiotype' => { 1 => 0x26 }, + 'silentphotography' => { 234 => 0xbf }, + 'similarityindex' => { 131 => 0xe4 }, + 'singleframebracketing' => { 184 => 0x21 }, + 'skilllevel' => { 521 => 'skillLevel' }, + 'skintonecorrection' => { 375 => 0x95 }, + 'skiplinetime' => { 138 => 0x184e }, + 'slaveflashmeteringsegments' => { 375 => 0x20b }, + 'slideshowname' => { 518 => 'slideshowName' }, + 'slideshownumber' => { 518 => 'slideshowNumber' }, + 'slideshowtotalnumber' => { 518 => 'slideshowTotalNumber' }, + 'slot2jpgsize' => { 239 => 0x24a, 242 => 0x24a }, + 'slowshutter' => { 77 => 0x8 }, + 'slowsync' => { 127 => 0x1030 }, + 'smartalbumcolor' => { 414 => 0x20 }, + 'smartrange' => { 414 => 0xa012 }, + 'smileshutter' => { 429 => 0x31 }, + 'smileshuttermode' => { 429 => 0x27 }, + 'smoothness' => { 119 => 0xfe57, 500 => 'Smoothness', 502 => 'Smoothness' }, + 'snapshot' => { 514 => 'SnapshotLink' }, + 'snapshotformat' => { 514 => [\'SnapshotLink','SnapshotLinkFormat'] }, + 'snapshotheightpixels' => { 514 => [\'SnapshotLink','SnapshotLinkHeightPixels'] }, + 'snapshotimagerole' => { 514 => [\'SnapshotLink','SnapshotLinkImageRole'] }, + 'snapshotlink' => { 514 => [\'SnapshotLink','SnapshotLinkLink'] }, + 'snapshotlinkqualifier' => { 514 => [\'SnapshotLink','SnapshotLinkLinkQualifier'] }, + 'snapshots' => { 495 => 'Snapshots' }, + 'snapshotusedvideoframe' => { 514 => [\'SnapshotLink','SnapshotLinkUsedVideoFrame'] }, + 'snapshotusedvideoframetimeformat' => { 514 => [\'SnapshotLink','SnapshotLinkUsedVideoFrameTimeFormat'] }, + 'snapshotusedvideoframetimevalue' => { 514 => [\'SnapshotLink','SnapshotLinkUsedVideoFrameTimeValue'] }, + 'snapshotusedvideoframevalue' => { 514 => [\'SnapshotLink','SnapshotLinkUsedVideoFrameValue'] }, + 'snapshotwidthpixels' => { 514 => [\'SnapshotLink','SnapshotLinkWidthPixels'] }, + 'softskineffect' => { 440 => 0x200f }, + 'software' => { 119 => 0x131, 157 => 'Software', 330 => 'Software', 384 => 0x203, 394 => 'software', 417 => 0x18, 485 => 'Software', 525 => 'Software' }, + 'softwareversion' => { 400 => ['@swr',"\xa9swr"], 416 => 0x207 }, + 'soloist' => { 392 => "\xa9sol" }, + 'songwriter' => { 400 => "\xa9swf" }, + 'songwriterkeywords' => { 400 => "\xa9swk" }, + 'sonycropsize' => { 119 => 0x74c8 }, + 'sonycroptopleft' => { 119 => 0x74c7 }, + 'sonydatetime' => { 448 => 0x6, 450 => 0x1b6, 451 => 0x210, 452 => 0x1fe, 453 => 0x22c }, + 'sonydatetime2' => { 459 => 0x51 }, + 'sonyexposuretime' => { 459 => 0x3a, 460 => 0x46, 461 => [0x66,0x46], 462 => 0x1a }, + 'sonyexposuretime2' => { 472 => 0xe }, + 'sonyfnumber' => { 459 => 0x3c, 460 => 0x48, 461 => [0x68,0x48], 462 => 0x1c, 472 => 0x14 }, + 'sonyimageheight' => { 448 => 0x1a, 463 => 0x44, 464 => 0x3f, 465 => 0x47 }, + 'sonyimageheightmax' => { 472 => 0x40 }, + 'sonyimagesize' => { 184 => 0x3b, 427 => 0x54, 428 => 0x54, 429 => 0x9 }, + 'sonyimagewidth' => { 448 => 0x1c }, + 'sonyimagewidthmax' => { 472 => 0x3e }, + 'sonyiso' => { 450 => 0x1218, 451 => 0x11f4, 452 => 0x1270, 453 => [0x1254,0x1258,0x1280], 454 => 0x113c, 455 => 0x344, 456 => 0x346, 457 => 0x320, 472 => 0x4 }, + 'sonymaxaperture' => { 459 => 0x0, 460 => 0x0 }, + 'sonymaxaperturevalue' => { 472 => 0x16 }, + 'sonyminaperture' => { 459 => 0x1, 460 => 0x1 }, + 'sonymodelid' => { 440 => 0xb001 }, + 'sonyquality' => { 184 => 0x3c }, + 'sonyrawimagesize' => { 119 => 0x7038 }, + 'sonytimeminsec' => { 460 => 0x61 }, + 'sortalbum' => { 392 => 'soal' }, + 'sortalbumartist' => { 392 => 'soaa' }, + 'sortartist' => { 392 => 'soar' }, + 'sortcomposer' => { 392 => 'soco' }, + 'sortname' => { 392 => 'sonm', 500 => 'SortName', 502 => 'SortName' }, + 'sortshow' => { 392 => 'sosn' }, + 'soundengineer' => { 392 => "\xa9sne" }, + 'source' => { 131 => 0x73, 330 => 'Source', 503 => 'source', 504 => 'source', 517 => 'Source' }, + 'sourcecount' => { 489 => 'SourceCount' }, + 'sourcecredits' => { 400 => "\xa9src" }, + 'sourcedirectoryindex' => { 362 => 0x0 }, + 'sourcefileindex' => { 362 => 0x2 }, + 'sourcephotoscount' => { 488 => 'SourcePhotosCount' }, + 'sourceprofileprefix' => { 138 => 0x1390 }, + 'spatialfrequencyresponse' => { 506 => 'SpatialFrequencyResponse' }, + 'spatialfrequencyresponsecolumns' => { 506 => [\'SpatialFrequencyResponse','SpatialFrequencyResponseColumns'] }, + 'spatialfrequencyresponsenames' => { 506 => [\'SpatialFrequencyResponse','SpatialFrequencyResponseNames'] }, + 'spatialfrequencyresponserows' => { 506 => [\'SpatialFrequencyResponse','SpatialFrequencyResponseRows'] }, + 'spatialfrequencyresponsevalues' => { 506 => [\'SpatialFrequencyResponse','SpatialFrequencyResponseValues'] }, + 'speakerplacement' => { 529 => 'speakerPlacement' }, + 'specialeffectlevel' => { 113 => 0x3030 }, + 'specialeffectmode' => { 113 => 0x2076 }, + 'specialeffectsetting' => { 113 => 0x3031 }, + 'specialinstructions' => { 131 => 0x28 }, + 'specialmode' => { 322 => 0x200, 416 => 0x200 }, + 'specialoccasion' => { 521 => 'specialOccasion' }, + 'specialtypeid' => { 483 => 'SpecialTypeID' }, + 'spectralsensitivity' => { 119 => 0x8824, 506 => 'SpectralSensitivity' }, + 'specularwhitelevel' => { 41 => 0x32b, 42 => 0x281, 45 => [0x2b9,0x2d0,0x2d4], 46 => 0x56a, 47 => 0x1e4, 48 => [0x1fd,0x2dd], 49 => [0x231,0x30f], 50 => 0x31d }, + 'speedx' => { 115 => 0x3, 400 => "\xa9xsp" }, + 'speedy' => { 115 => 0x4, 400 => "\xa9ysp" }, + 'speedz' => { 115 => 0x5, 400 => "\xa9zsp" }, + 'spherical' => { 489 => 'Spherical' }, + 'sphericalvideoxml' => { 120 => 'SphericalVideoXML' }, + 'splitcolumn' => { 384 => 0x222 }, + 'splittoningbalance' => { 500 => 'SplitToningBalance', 502 => 'SplitToningBalance' }, + 'splittoninghighlighthue' => { 500 => 'SplitToningHighlightHue', 502 => 'SplitToningHighlightHue' }, + 'splittoninghighlightsaturation' => { 500 => 'SplitToningHighlightSaturation', 502 => 'SplitToningHighlightSaturation' }, + 'splittoningshadowhue' => { 500 => 'SplitToningShadowHue', 502 => 'SplitToningShadowHue' }, + 'splittoningshadowsaturation' => { 500 => 'SplitToningShadowSaturation', 502 => 'SplitToningShadowSaturation' }, + 'sport' => { 519 => 'sport' }, + 'spotfocuspointx' => { 181 => 0x2d }, + 'spotfocuspointy' => { 181 => 0x2e }, + 'spotmeteringmode' => { 34 => 0x27 }, + 'spotmeterlinktoafpoint' => { 84 => 0x107 }, + 'sractive' => { 356 => '17.1' }, + 'srawquality' => { 34 => 0x2e }, + 'srfocallength' => { 377 => 0x3 }, + 'srgbrendering' => { 328 => 'sRGB' }, + 'srhalfpresstime' => { 377 => 0x2 }, + 'srresult' => { 377 => 0x0, 378 => 0x0 }, + 'stackedimage' => { 317 => 0x804 }, + 'standardmatrixcustom' => { 138 => 0x7d4 }, + 'standardmatrixdaylight' => { 138 => 0x7d0 }, + 'standardmatrixflash' => { 138 => 0x7d3 }, + 'standardmatrixfluorescent' => { 138 => 0x7d2 }, + 'standardmatrixtungsten' => { 138 => 0x7d1 }, + 'standardoutputhighlightpoint' => { 109 => 0x14 }, + 'standardoutputsensitivity' => { 119 => 0x8831, 507 => 'StandardOutputSensitivity' }, + 'standardoutputshadowpoint' => { 109 => 0x15 }, + 'standardrawcolortone' => { 109 => 0xd }, + 'standardrawcontrast' => { 109 => 0xf }, + 'standardrawhighlight' => { 109 => 0x75 }, + 'standardrawhighlightpoint' => { 109 => 0x12 }, + 'standardrawlinear' => { 109 => 0x10 }, + 'standardrawsaturation' => { 109 => 0xe }, + 'standardrawshadow' => { 109 => 0x7e }, + 'standardrawshadowpoint' => { 109 => 0x13 }, + 'standardrawsharpness' => { 109 => 0x11 }, + 'standardunsharpmaskfineness' => { 109 => 0x94 }, + 'standardunsharpmaskstrength' => { 109 => 0x92 }, + 'standardunsharpmaskthreshold' => { 109 => 0x96 }, + 'standardwhitecustom' => { 138 => 0x838 }, + 'standardwhitedaylight' => { 138 => 0x834 }, + 'standardwhiteflash' => { 138 => 0x837 }, + 'standardwhitefluorescent' => { 138 => 0x836 }, + 'standardwhitetungsten' => { 138 => 0x835 }, + 'standbytimer' => { 298 => '19.1', 300 => '19.1', 301 => '19.1', 304 => '18.1', 305 => '18.1', 310 => '19.1', 311 => '19.1' }, + 'starlightview' => { 313 => 0x249, 314 => 0x249, 315 => 0x261 }, + 'startingpage' => { 519 => 'startingPage' }, + 'startmovieshooting' => { 84 => 0x70d }, + 'starttimecode' => { 400 => "\xa9TIM", 529 => 'startTimecode' }, + 'starttimecodetimeformat' => { 529 => [\'startTimecode','startTimecodeTimeFormat'] }, + 'starttimecodetimevalue' => { 529 => [\'startTimecode','startTimecodeTimeValue'] }, + 'starttimecodevalue' => { 529 => [\'startTimecode','startTimecodeValue'] }, + 'starttimesamplesize' => { 400 => "\xa9TSZ", 529 => 'startTimeSampleSize' }, + 'starttimescale' => { 400 => "\xa9TSC", 529 => 'startTimeScale' }, + 'state' => { 161 => 'State', 340 => 0x6b, 517 => 'State' }, + 'status' => { 481 => 'Status', 493 => 'Status' }, + 'stereomode' => { 489 => 'StereoMode' }, + 'stitched' => { 489 => 'Stitched' }, + 'stitchingsoftware' => { 488 => 'StitchingSoftware', 489 => 'StitchingSoftware' }, + 'stopsabovebaseiso' => { 449 => 0x113e, 450 => 0x113e, 451 => 0x111a, 452 => 0x1196, 453 => 0x1172, 454 => 0x102a, 455 => 0x222, 456 => 0x222, 457 => 0x217, 472 => 0xa }, + 'storagemethod' => { 188 => 0x12 }, + 'storebyorientation' => { 298 => '46.3', 300 => '47.3', 301 => '47.3', 310 => '47.3', 311 => '47.3', 313 => 0xd, 314 => 0xd, 315 => 0xd }, + 'storedescription' => { 392 => 'sdes' }, + 'storylineidentifier' => { 514 => 'StorylineIdentifier' }, + 'straightenangle' => { 290 => 0x2fc08431 }, + 'streamready' => { 514 => 'StreamReady' }, + 'streamtype' => { 176 => 'StreamType' }, + 'stretchmode' => { 529 => 'stretchMode' }, + 'studydatetime' => { 478 => 'StudyDateTime' }, + 'studydescription' => { 478 => 'StudyDescription' }, + 'studyid' => { 478 => 'StudyID' }, + 'studyphysician' => { 478 => 'StudyPhysician' }, + 'styleperiod' => { 514 => 'StylePeriod' }, + 'sub-location' => { 131 => 0x5c }, + 'subcommanddialplaybackmode' => { 313 => 0x1cf, 314 => 0x1cf, 315 => 0x1e7 }, + 'subfiledata' => { 164 => 'data' }, + 'subfiledirectory' => { 164 => '1Directory' }, + 'subfilemimetype' => { 164 => '2MIME' }, + 'subfilename' => { 164 => '1Name' }, + 'subfileresource' => { 164 => 'rsrc' }, + 'subfiletype' => { 119 => 0xfe, 164 => '0Type' }, + 'subject' => { 326 => 'Subject', 391 => 'Subject', 503 => 'subject', 516 => 'Subject', 530 => 'subject' }, + 'subjectarea' => { 119 => 0x9214, 506 => 'SubjectArea' }, + 'subjectcode' => { 513 => 'SubjectCode' }, + 'subjectdetection' => { 239 => 0x252, 240 => 0x23e, 241 => 0x252, 242 => 0x252 }, + 'subjectdistance' => { 119 => 0x9206, 140 => 0x3e, 506 => 'SubjectDistance' }, + 'subjectdistancerange' => { 119 => 0xa40c, 506 => 'SubjectDistanceRange' }, + 'subjectlocation' => { 119 => 0xa214, 506 => 'SubjectLocation' }, + 'subjectmotion' => { 300 => '78.2', 301 => '78.2', 311 => '78.2', 313 => 0x103, 314 => 0x103, 315 => 0x119 }, + 'subjectprogram' => { 181 => 0x22 }, + 'subjectreference' => { 131 => 0xc }, + 'sublabels1' => { 512 => [\'TagStructure','TagStructureSubLabels'] }, + 'sublabels2' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabels'] }, + 'sublabels3' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabels'] }, + 'sublabels4' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsSubLabels'] }, + 'sublabels5' => { 512 => [\'TagStructure','TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabels'] }, + 'subseccreatedate' => { 114 => 'Exif-SubSecCreateDate' }, + 'subsecdatetimeoriginal' => { 114 => 'Exif-SubSecDateTimeOriginal' }, + 'subsecmodifydate' => { 114 => 'Exif-SubSecModifyDate' }, + 'subsectime' => { 119 => 0x9290 }, + 'subsectimedigitized' => { 119 => 0x9292 }, + 'subsectimeoriginal' => { 119 => 0x9291 }, + 'subsection1' => { 519 => 'subsection1' }, + 'subsection2' => { 519 => 'subsection2' }, + 'subsection3' => { 519 => 'subsection3' }, + 'subsection4' => { 519 => 'subsection4' }, + 'subselector' => { 298 => '49.1', 300 => '71.1', 301 => '71.1', 311 => '71.1', 313 => 0x8f, 314 => 0x8f, 315 => 0x8f }, + 'subselectorassignment' => { 298 => '48.1' }, + 'subselectorcenter' => { 300 => '72.1', 301 => '72.1', 311 => '72.1' }, + 'subselectorplusdials' => { 298 => '49.2', 300 => '73.1', 301 => '73.1', 311 => '73.1' }, + 'subtitle' => { 179 => 'WM/SubTitle', 392 => "\xa9st3", 400 => "\xa9snm", 519 => 'subtitle' }, + 'subtitlekeywords' => { 400 => "\xa9snk" }, + 'subversionfilename' => { 512 => [\'SubVersions','SubVersionsFileName'] }, + 'subversionreference' => { 512 => [\'SubVersions','SubVersionsVersRef'] }, + 'subversions' => { 512 => 'SubVersions' }, + 'superimposeddisplay' => { 82 => 0xa, 84 => 0x510, 85 => 0x9, 86 => 0xa, 89 => 0xa, 90 => 0xe }, + 'supermacro' => { 64 => 0x1a }, + 'supplementalcategories' => { 131 => 0x14, 517 => 'SupplementalCategories' }, + 'supplementaltype' => { 133 => 0x37 }, + 'supplementdisplayid' => { 519 => 'supplementDisplayID' }, + 'supplementstartingpage' => { 519 => 'supplementStartingPage' }, + 'supplementtitle' => { 519 => 'supplementTitle' }, + 'supplychainsource' => { 514 => 'SupplyChainSource' }, + 'supplychainsourceidentifier' => { 514 => [\'SupplyChainSource','SupplyChainSourceIdentifier'] }, + 'supplychainsourcename' => { 514 => [\'SupplyChainSource','SupplyChainSourceName'] }, + 'supportsamount' => { 500 => 'SupportsAmount', 502 => 'SupportsAmount' }, + 'supportscolor' => { 500 => 'SupportsColor', 502 => 'SupportsColor' }, + 'supportshighdynamicrange' => { 500 => 'SupportsHighDynamicRange', 502 => 'SupportsHighDynamicRange' }, + 'supportsmonochrome' => { 500 => 'SupportsMonochrome', 502 => 'SupportsMonochrome' }, + 'supportsnormaldynamicrange' => { 500 => 'SupportsNormalDynamicRange', 502 => 'SupportsNormalDynamicRange' }, + 'supportsoutputreferred' => { 500 => 'SupportsOutputReferred', 502 => 'SupportsOutputReferred' }, + 'supportsscenereferred' => { 500 => 'SupportsSceneReferred', 502 => 'SupportsSceneReferred' }, + 'svisosetting' => { 356 => 0x14 }, + 'swatchcoloranta' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsA'] }, + 'swatchcolorantb' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsB'] }, + 'swatchcolorantblack' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsBlack'] }, + 'swatchcolorantblue' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsBlue'] }, + 'swatchcolorantcyan' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsCyan'] }, + 'swatchcolorantgray' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsGray'] }, + 'swatchcolorantgreen' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsGreen'] }, + 'swatchcolorantl' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsL'] }, + 'swatchcolorantmagenta' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsMagenta'] }, + 'swatchcolorantmode' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsMode'] }, + 'swatchcolorantred' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsRed'] }, + 'swatchcolorantswatchname' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsSwatchName'] }, + 'swatchcoloranttint' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsTint'] }, + 'swatchcoloranttype' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsType'] }, + 'swatchcolorantyellow' => { 534 => [\'SwatchGroups','SwatchGroupsColorantsYellow'] }, + 'swatchgroupname' => { 534 => [\'SwatchGroups','SwatchGroupsGroupName'] }, + 'swatchgroups' => { 534 => 'SwatchGroups' }, + 'swatchgroupscolorants' => { 534 => [\'SwatchGroups','SwatchGroupsColorants'] }, + 'swatchgrouptype' => { 534 => [\'SwatchGroups','SwatchGroupsGroupType'] }, + 'sweeppanoramadirection' => { 340 => 0x93, 429 => 0x33 }, + 'sweeppanoramafieldofview' => { 340 => 0x94 }, + 'sweeppanoramasize' => { 429 => 0x32 }, + 'switchtoregisteredafpoint' => { 83 => 0x12, 84 => 0x50a }, + 'symlink' => { 120 => 'SymLink' }, + 'syncreleasemode' => { 300 => '77.1', 301 => '77.1', 311 => '77.1', 313 => 0x41, 314 => 0x41, 315 => 0x41 }, + 'system' => { 384 => 0x204 }, + 'tagged' => { 386 => 0xdd, 387 => 'Tagged', 495 => 'tagged' }, + 'tagslist' => { 505 => 'TagsList' }, + 'tagstructure' => { 512 => 'TagStructure' }, + 'takenumber' => { 529 => 'takeNumber' }, + 'tapename' => { 529 => 'tapeName' }, + 'targetaperture' => { 77 => 0x4 }, + 'targetcompressionratio' => { 95 => 0x1 }, + 'targetdistancesetting' => { 97 => 0x1807 }, + 'targetexposuretime' => { 77 => 0x5 }, + 'targetimagetype' => { 97 => 0x100a }, + 'targetprinter' => { 119 => 0x151 }, + 'taxon' => { 118 => 'Taxon' }, + 'taxonacceptednameusage' => { 118 => [\'Taxon','TaxonAcceptedNameUsage'] }, + 'taxonacceptednameusageid' => { 118 => [\'Taxon','TaxonAcceptedNameUsageID'] }, + 'taxonclass' => { 118 => [\'Taxon','TaxonClass'] }, + 'taxonconceptid' => { 118 => [\'Taxon','TaxonTaxonConceptID'] }, + 'taxoncultivarepithet' => { 118 => [\'Taxon','TaxonCultivarEpithet'] }, + 'taxonfamily' => { 118 => [\'Taxon','TaxonFamily'] }, + 'taxongenus' => { 118 => [\'Taxon','TaxonGenus'] }, + 'taxonhigherclassification' => { 118 => [\'Taxon','TaxonHigherClassification'] }, + 'taxonid' => { 118 => [\'Taxon','TaxonTaxonID'] }, + 'taxoninfraspecificepithet' => { 118 => [\'Taxon','TaxonInfraspecificEpithet'] }, + 'taxonkingdom' => { 118 => [\'Taxon','TaxonKingdom'] }, + 'taxonnameaccordingto' => { 118 => [\'Taxon','TaxonNameAccordingTo'] }, + 'taxonnameaccordingtoid' => { 118 => [\'Taxon','TaxonNameAccordingToID'] }, + 'taxonnamepublishedin' => { 118 => [\'Taxon','TaxonNamePublishedIn'] }, + 'taxonnamepublishedinid' => { 118 => [\'Taxon','TaxonNamePublishedInID'] }, + 'taxonnamepublishedinyear' => { 118 => [\'Taxon','TaxonNamePublishedInYear'] }, + 'taxonnomenclaturalcode' => { 118 => [\'Taxon','TaxonNomenclaturalCode'] }, + 'taxonnomenclaturalstatus' => { 118 => [\'Taxon','TaxonNomenclaturalStatus'] }, + 'taxonorder' => { 118 => [\'Taxon','TaxonOrder'] }, + 'taxonoriginalnameusage' => { 118 => [\'Taxon','TaxonOriginalNameUsage'] }, + 'taxonoriginalnameusageid' => { 118 => [\'Taxon','TaxonOriginalNameUsageID'] }, + 'taxonparentnameusage' => { 118 => [\'Taxon','TaxonParentNameUsage'] }, + 'taxonparentnameusageid' => { 118 => [\'Taxon','TaxonParentNameUsageID'] }, + 'taxonphylum' => { 118 => [\'Taxon','TaxonPhylum'] }, + 'taxonrank' => { 118 => [\'Taxon','TaxonTaxonRank'] }, + 'taxonremarks' => { 118 => [\'Taxon','TaxonTaxonRemarks'] }, + 'taxonscientificname' => { 118 => [\'Taxon','TaxonScientificName'] }, + 'taxonscientificnameauthorship' => { 118 => [\'Taxon','TaxonScientificNameAuthorship'] }, + 'taxonscientificnameid' => { 118 => [\'Taxon','TaxonScientificNameID'] }, + 'taxonspecificepithet' => { 118 => [\'Taxon','TaxonSpecificEpithet'] }, + 'taxonsubgenus' => { 118 => [\'Taxon','TaxonSubgenus'] }, + 'taxontaxonomicstatus' => { 118 => [\'Taxon','TaxonTaxonomicStatus'] }, + 'taxonverbatimtaxonrank' => { 118 => [\'Taxon','TaxonVerbatimTaxonRank'] }, + 'taxonvernacularname' => { 118 => [\'Taxon','TaxonVernacularName'] }, + 'tda1bandwidth' => { 138 => 0x196f }, + 'tda1edgepolarity' => { 138 => 0x1971 }, + 'tda1gain' => { 138 => 0x1970 }, + 'tda1offset' => { 138 => 0x196e }, + 'tda2bandwidth' => { 138 => 0x1979 }, + 'tda2edgepolarity' => { 138 => 0x197b }, + 'tda2gain' => { 138 => 0x197a }, + 'tda2offset' => { 138 => 0x1978 }, + 'tda3bandwidth' => { 138 => 0x1983 }, + 'tda3edgepolarity' => { 138 => 0x1985 }, + 'tda3gain' => { 138 => 0x1984 }, + 'tda3offset' => { 138 => 0x1982 }, + 'tda4bandwidth' => { 138 => 0x198d }, + 'tda4edgepolarity' => { 138 => 0x198f }, + 'tda4gain' => { 138 => 0x198e }, + 'tda4offset' => { 138 => 0x198c }, + 'teaser' => { 519 => 'teaser' }, + 'teleconverter' => { 186 => 0x105, 440 => 0x105 }, + 'tempampgainx100' => { 138 => 0x1914 }, + 'tempo' => { 529 => 'tempo' }, + 'temporalcoverage' => { 514 => 'TemporalCoverage' }, + 'temporalcoveragefrom' => { 514 => [\'TemporalCoverage','TemporalCoverageTempCoverageFrom'] }, + 'temporalcoverageto' => { 514 => [\'TemporalCoverage','TemporalCoverageTempCoverageTo'] }, + 'termsandconditionstext' => { 327 => 'TermsAndConditionsText' }, + 'termsandconditionsurl' => { 327 => 'TermsAndConditionsURL' }, + 'testname' => { 120 => 'TestName' }, + 'textencoding' => { 233 => 0x4 }, + 'textlayername' => { 517 => [\'TextLayers','TextLayersLayerName'] }, + 'textlayers' => { 517 => 'TextLayers' }, + 'textlayertext' => { 517 => [\'TextLayers','TextLayersLayerText'] }, + 'textstamp' => { 340 => [0x8008,0x8009,0x3b,0x3e] }, + 'texture' => { 500 => 'Texture', 502 => 'Texture' }, + 'three-dtrackingfacedetection' => { 300 => '1.4', 301 => '1.4', 311 => '1.4' }, + 'three-dtrackingwatcharea' => { 300 => '78.1', 301 => '78.1', 311 => '78.1' }, + 'thresholding' => { 119 => 0x107 }, + 'thumbnailfilename' => { 97 => 0x817 }, + 'thumbnailformat' => { 527 => [\'Thumbnails','ThumbnailsFormat'] }, + 'thumbnailheight' => { 142 => 0xfa55, 527 => [\'Thumbnails','ThumbnailsHeight'] }, + 'thumbnailimage' => { 6 => 'CNDA', 97 => 0x2008, 114 => 'Exif-ThumbnailImage', 122 => 0x3, 167 => 'data', 322 => 0x100, 400 => 'thmb', 527 => [\'Thumbnails','ThumbnailsImage'] }, + 'thumbnailimagename' => { 167 => '1Name' }, + 'thumbnailimagesize' => { 167 => 'ImageSize' }, + 'thumbnailimagetype' => { 167 => '0Type' }, + 'thumbnailimagevalidarea' => { 64 => 0x13 }, + 'thumbnaillength' => { 119 => 0x202 }, + 'thumbnailoffset' => { 119 => 0x201 }, + 'thumbnailpng' => { 400 => 'thmb' }, + 'thumbnails' => { 527 => 'Thumbnails' }, + 'thumbnailwidth' => { 142 => 0xfa54, 527 => [\'Thumbnails','ThumbnailsWidth'] }, + 'ticker' => { 519 => 'ticker' }, + 'tiffhandling' => { 500 => 'TIFFHandling', 502 => 'TIFFHandling' }, + 'tiffmeteringimage' => { 187 => 0x104c, 438 => 0x1110 }, + 'tilelength' => { 119 => 0x143 }, + 'tilewidth' => { 119 => 0x142 }, + 'time' => { 138 => 0x401, 375 => 0x7 }, + 'timecodes' => { 119 => 0xc763 }, + 'timecreated' => { 131 => 0x3c, 140 => 0x14, 149 => 0x10, 387 => 'TimeCreated' }, + 'timelapseshotnumber' => { 343 => 0x10 }, + 'timeperiod' => { 519 => 'timePeriod' }, + 'timerfunctionbutton' => { 299 => '5.2', 302 => '12.1', 303 => '13.1' }, + 'timerlength' => { 84 => 0x80c }, + 'timerrecording' => { 340 => 0x96 }, + 'timescaleparams' => { 529 => 'timeScaleParams' }, + 'timescaleparamsframeoverlappingpercentage' => { 529 => [\'timeScaleParams','timeScaleParamsFrameOverlappingPercentage'] }, + 'timescaleparamsframesize' => { 529 => [\'timeScaleParams','timeScaleParamsFrameSize'] }, + 'timescaleparamsquality' => { 529 => [\'timeScaleParams','timeScaleParamsQuality'] }, + 'timesent' => { 132 => 0x50 }, + 'timeshot' => { 490 => 'TimeShot' }, + 'timesignature' => { 529 => 'timeSignature' }, + 'timesincepoweron' => { 340 => 0x29 }, + 'timestamp' => { 11 => 0x45e, 19 => 0x11c, 316 => 0x8, 330 => 'TimeStamp', 340 => 0xaf, 489 => 'Timestamp', 496 => 'Timestamp', 512 => 'TimeStamp' }, + 'timestamp1' => { 11 => 0x45a }, + 'timezone' => { 78 => 0x1, 239 => 0x694, 240 => 0x594, 241 => 0x5c4, 242 => 0x5dc, 282 => 0x0, 410 => 0xa }, + 'timezonecity' => { 78 => 0x2 }, + 'timezonecode' => { 100 => 0x1 }, + 'timezoneinfo' => { 100 => 0x2 }, + 'timezoneoffset' => { 119 => 0x882a }, + 'tint' => { 394 => 'player.movie.visual.tint', 500 => 'Tint', 502 => 'Tint' }, + 'title' => { 119 => 0xa436, 157 => 'Title', 326 => 'Title', 330 => 'Title', 340 => 0x65, 391 => 'Title', 392 => ['titl',"\xa9nam"], 394 => 'title', 400 => ['titl',"\xa9nam"], 503 => 'title', 516 => 'Title', 527 => 'Title' }, + 'togglestyleamount' => { 500 => 'ToggleStyleAmount', 502 => 'ToggleStyleAmount' }, + 'togglestyledigest' => { 500 => 'ToggleStyleDigest', 502 => 'ToggleStyleDigest' }, + 'tonecomp' => { 234 => 0x81 }, + 'tonecurve' => { 74 => 0x1, 375 => 0x402, 500 => 'ToneCurve', 502 => 'ToneCurve' }, + 'tonecurveactive' => { 108 => 0x110 }, + 'tonecurveadobergb' => { 414 => 0xa043 }, + 'tonecurveadobergbdefault' => { 414 => 0xa041 }, + 'tonecurveblue' => { 500 => 'ToneCurveBlue', 502 => 'ToneCurveBlue' }, + 'tonecurvebluex' => { 476 => 0x9003 }, + 'tonecurvebluey' => { 476 => 0x9007 }, + 'tonecurvebrightness' => { 103 => 0x20410 }, + 'tonecurvebrightnessx' => { 476 => 0x9000 }, + 'tonecurvebrightnessy' => { 476 => 0x9004 }, + 'tonecurvecolorspace' => { 107 => 0x0 }, + 'tonecurvecontrast' => { 103 => 0x20411 }, + 'tonecurvegreen' => { 500 => 'ToneCurveGreen', 502 => 'ToneCurveGreen' }, + 'tonecurvegreenx' => { 476 => 0x9002 }, + 'tonecurvegreeny' => { 476 => 0x9006 }, + 'tonecurveinputrange' => { 107 => 0x3 }, + 'tonecurveinterpolation' => { 108 => 0x159 }, + 'tonecurvemode' => { 108 => 0x113 }, + 'tonecurvename' => { 500 => 'ToneCurveName', 502 => 'ToneCurveName' }, + 'tonecurvename2012' => { 500 => 'ToneCurveName2012', 502 => 'ToneCurveName2012' }, + 'tonecurveoriginal' => { 103 => '0x20400.1' }, + 'tonecurveoutputrange' => { 107 => 0x5 }, + 'tonecurveprofilename' => { 138 => 0x1391 }, + 'tonecurveproperty' => { 108 => 0x3c }, + 'tonecurvepv2012' => { 500 => 'ToneCurvePV2012', 502 => 'ToneCurvePV2012' }, + 'tonecurvepv2012blue' => { 500 => 'ToneCurvePV2012Blue', 502 => 'ToneCurvePV2012Blue' }, + 'tonecurvepv2012green' => { 500 => 'ToneCurvePV2012Green', 502 => 'ToneCurvePV2012Green' }, + 'tonecurvepv2012red' => { 500 => 'ToneCurvePV2012Red', 502 => 'ToneCurvePV2012Red' }, + 'tonecurvered' => { 500 => 'ToneCurveRed', 502 => 'ToneCurveRed' }, + 'tonecurveredx' => { 476 => 0x9001 }, + 'tonecurveredy' => { 476 => 0x9005 }, + 'tonecurves' => { 375 => 0x403 }, + 'tonecurveshape' => { 107 => 0x1 }, + 'tonecurvesrgb' => { 414 => 0xa042 }, + 'tonecurvesrgbdefault' => { 414 => 0xa040 }, + 'tonecurvex' => { 107 => 0xa }, + 'tonecurvey' => { 107 => 0xb }, + 'tonelevel' => { 317 => 0x52e }, + 'tonemap' => { 239 => 0x26a }, + 'tonemapstrength' => { 500 => 'ToneMapStrength', 502 => 'ToneMapStrength' }, + 'toningeffect' => { 57 => 0xf, 234 => 0xb3, 249 => 0x38, 250 => 0x40, 251 => 0x48, 407 => 0x1015 }, + 'toningeffectauto' => { 71 => 0xa4 }, + 'toningeffectfaithful' => { 70 => 0x74, 71 => 0x74 }, + 'toningeffectlandscape' => { 70 => 0x44, 71 => 0x44 }, + 'toningeffectmonochrome' => { 19 => 0x108, 70 => 0x8c, 71 => 0x8c }, + 'toningeffectneutral' => { 70 => 0x5c, 71 => 0x5c }, + 'toningeffectportrait' => { 70 => 0x2c, 71 => 0x2c }, + 'toningeffectstandard' => { 70 => 0x14, 71 => 0x14 }, + 'toningeffectuserdef1' => { 70 => 0xa4, 71 => 0xbc }, + 'toningeffectuserdef2' => { 70 => 0xbc, 71 => 0xd4 }, + 'toningeffectuserdef3' => { 70 => 0xd4, 71 => 0xec }, + 'toningsaturation' => { 249 => 0x39, 250 => 0x41, 251 => 0x49 }, + 'totalzoom' => { 140 => 0x62 }, + 'touchae' => { 340 => 0xab }, + 'track' => { 392 => "\xa9trk", 400 => "\xa9trk" }, + 'trackcreatedate' => { 399 => 0x1 }, + 'trackmodifydate' => { 399 => 0x2 }, + 'tracknumber' => { 392 => 'trkn', 529 => 'trackNumber' }, + 'tracks' => { 529 => 'Tracks' }, + 'tracksframerate' => { 529 => [\'Tracks','TracksFrameRate'] }, + 'tracksmarkers' => { 529 => [\'Tracks','TracksMarkers'] }, + 'tracksmarkerscomment' => { 529 => [\'Tracks','TracksMarkersComment'] }, + 'tracksmarkerscuepointparams' => { 529 => [\'Tracks','TracksMarkersCuePointParams'] }, + 'tracksmarkerscuepointparamskey' => { 529 => [\'Tracks','TracksMarkersCuePointParamsKey'] }, + 'tracksmarkerscuepointparamsvalue' => { 529 => [\'Tracks','TracksMarkersCuePointParamsValue'] }, + 'tracksmarkerscuepointtype' => { 529 => [\'Tracks','TracksMarkersCuePointType'] }, + 'tracksmarkersduration' => { 529 => [\'Tracks','TracksMarkersDuration'] }, + 'tracksmarkerslocation' => { 529 => [\'Tracks','TracksMarkersLocation'] }, + 'tracksmarkersname' => { 529 => [\'Tracks','TracksMarkersName'] }, + 'tracksmarkersprobability' => { 529 => [\'Tracks','TracksMarkersProbability'] }, + 'tracksmarkersspeaker' => { 529 => [\'Tracks','TracksMarkersSpeaker'] }, + 'tracksmarkersstarttime' => { 529 => [\'Tracks','TracksMarkersStartTime'] }, + 'tracksmarkerstarget' => { 529 => [\'Tracks','TracksMarkersTarget'] }, + 'tracksmarkerstype' => { 529 => [\'Tracks','TracksMarkersType'] }, + 'trackstrackname' => { 529 => [\'Tracks','TracksTrackName'] }, + 'trackstracktype' => { 529 => [\'Tracks','TracksTrackType'] }, + 'tracktype' => { 400 => 'kgtt' }, + 'trailer' => { 120 => 'Trailer' }, + 'trailersignature' => { 164 => 'zmie' }, + 'transcript' => { 514 => 'Transcript' }, + 'transcriptlink' => { 514 => 'TranscriptLink' }, + 'transcriptlinklink' => { 514 => [\'TranscriptLink','TranscriptLinkLink'] }, + 'transcriptlinklinkqualifier' => { 514 => [\'TranscriptLink','TranscriptLinkLinkQualifier'] }, + 'transferfunction' => { 119 => 0x12d, 525 => 'TransferFunction' }, + 'transfertimenormal' => { 138 => 0x1888 }, + 'transfertimetest' => { 138 => 0x1889 }, + 'transform' => { 340 => [0x8012,0x59] }, + 'transformation' => { 515 => 'Transformation' }, + 'transmissionreference' => { 517 => 'TransmissionReference' }, + 'trapped' => { 326 => 'Trapped', 516 => 'Trapped' }, + 'trashbuttonfunction' => { 84 => 0x710 }, + 'travelday' => { 340 => 0x36 }, + 'treble' => { 394 => 'player.movie.audio.treble' }, + 'triggermode' => { 401 => 0x6, 402 => 0x34, 403 => 0x34 }, + 'tstop' => { 119 => 0xc772 }, + 'ttl_da_adown' => { 363 => 0x5 }, + 'ttl_da_aup' => { 363 => 0x4 }, + 'ttl_da_bdown' => { 363 => 0x7 }, + 'ttl_da_bup' => { 363 => 0x6 }, + 'tungstenawb' => { 353 => 0x1 }, + 'tvepisode' => { 392 => 'tves' }, + 'tvepisodeid' => { 392 => 'tven' }, + 'tvexposuretimesetting' => { 356 => 0x12 }, + 'tvnetworkname' => { 392 => 'tvnn' }, + 'tvseason' => { 392 => 'tvsn' }, + 'tvshow' => { 392 => 'tvsh' }, + 'type' => { 484 => 'Type', 503 => 'type', 524 => 'type' }, + 'typestatus' => { 118 => [\'Identification','IdentificationTypeStatus'] }, + 'uniquecameramodel' => { 119 => 0xc614 }, + 'uniquedocumentid' => { 131 => 0xbb }, + 'uniquematrixauto' => { 138 => 0x7e9 }, + 'uniquematrixcustom' => { 138 => 0x7e8 }, + 'uniquematrixdaylight' => { 138 => 0x7e4 }, + 'uniquematrixflash' => { 138 => 0x7e7 }, + 'uniquematrixfluorescent' => { 138 => 0x7e6 }, + 'uniquematrixtungsten' => { 138 => 0x7e5 }, + 'uniqueobjectname' => { 132 => 0x64 }, + 'units' => { 485 => 'Units' }, + 'unknown_aacr' => { 392 => 'AACR' }, + 'unknown_cdek' => { 392 => 'CDEK' }, + 'unknown_cdet' => { 392 => 'CDET' }, + 'unknownblock' => { 375 => 0x405 }, + 'unknownblock1' => { 321 => 0x635 }, + 'unknownblock2' => { 321 => 0x636 }, + 'unknownblock3' => { 321 => 0x1103 }, + 'unknownblock4' => { 321 => 0x1104 }, + 'unknowncontrast' => { 109 => 0x45 }, + 'unknowndate' => { 384 => 0x212 }, + 'unknownev' => { 138 => 0x1 }, + 'unknownlinear' => { 109 => 0x46 }, + 'unknownnumber' => { 97 => 0x180b }, + 'unknownoutputhighlightpoint' => { 109 => 0x4a }, + 'unknownoutputshadowpoint' => { 109 => 0x4b }, + 'unknownrawhighlight' => { 109 => 0x7b }, + 'unknownrawhighlightpoint' => { 109 => 0x48 }, + 'unknownrawshadow' => { 109 => 0x84 }, + 'unknownrawshadowpoint' => { 109 => 0x49 }, + 'unknownsharpness' => { 109 => 0x47 }, + 'unknowntags' => { 400 => 'TAGS' }, + 'unknowntemperature' => { 121 => 0x4 }, + 'unknownthumbnail' => { 400 => 'thmb' }, + 'unsharp1color' => { 295 => 0x13 }, + 'unsharp1halowidth' => { 295 => 0x19 }, + 'unsharp1intensity' => { 295 => 0x17 }, + 'unsharp1threshold' => { 295 => 0x1b }, + 'unsharp2color' => { 295 => 0x2e }, + 'unsharp2halowidth' => { 295 => 0x34 }, + 'unsharp2intensity' => { 295 => 0x32 }, + 'unsharp2threshold' => { 295 => 0x36 }, + 'unsharp3color' => { 295 => 0x49 }, + 'unsharp3halowidth' => { 295 => 0x4f }, + 'unsharp3intensity' => { 295 => 0x4d }, + 'unsharp3threshold' => { 295 => 0x51 }, + 'unsharp4color' => { 295 => 0x64 }, + 'unsharp4halowidth' => { 295 => 0x6a }, + 'unsharp4intensity' => { 295 => 0x68 }, + 'unsharp4threshold' => { 295 => 0x6c }, + 'unsharpcount' => { 295 => 0x0 }, + 'unsharpmask' => { 109 => 0x90, 290 => 0x76a43200 }, + 'unsharpmaskfineness' => { 103 => 0x20309 }, + 'unsharpmaskstrength' => { 103 => 0x20308 }, + 'unsharpmaskthreshold' => { 103 => 0x2030a }, + 'uprightcentermode' => { 500 => 'UprightCenterMode', 502 => 'UprightCenterMode' }, + 'uprightcenternormx' => { 500 => 'UprightCenterNormX', 502 => 'UprightCenterNormX' }, + 'uprightcenternormy' => { 500 => 'UprightCenterNormY', 502 => 'UprightCenterNormY' }, + 'uprightdependentdigest' => { 500 => 'UprightDependentDigest', 502 => 'UprightDependentDigest' }, + 'uprightfocallength35mm' => { 500 => 'UprightFocalLength35mm', 502 => 'UprightFocalLength35mm' }, + 'uprightfocalmode' => { 500 => 'UprightFocalMode', 502 => 'UprightFocalMode' }, + 'uprightfoursegments_0' => { 500 => 'UprightFourSegments_0', 502 => 'UprightFourSegments_0' }, + 'uprightfoursegments_1' => { 500 => 'UprightFourSegments_1', 502 => 'UprightFourSegments_1' }, + 'uprightfoursegments_2' => { 500 => 'UprightFourSegments_2', 502 => 'UprightFourSegments_2' }, + 'uprightfoursegments_3' => { 500 => 'UprightFourSegments_3', 502 => 'UprightFourSegments_3' }, + 'uprightfoursegmentscount' => { 500 => 'UprightFourSegmentsCount', 502 => 'UprightFourSegmentsCount' }, + 'uprightguideddependentdigest' => { 500 => 'UprightGuidedDependentDigest', 502 => 'UprightGuidedDependentDigest' }, + 'uprightpreview' => { 500 => 'UprightPreview', 502 => 'UprightPreview' }, + 'uprighttransform_0' => { 500 => 'UprightTransform_0', 502 => 'UprightTransform_0' }, + 'uprighttransform_1' => { 500 => 'UprightTransform_1', 502 => 'UprightTransform_1' }, + 'uprighttransform_2' => { 500 => 'UprightTransform_2', 502 => 'UprightTransform_2' }, + 'uprighttransform_3' => { 500 => 'UprightTransform_3', 502 => 'UprightTransform_3' }, + 'uprighttransform_4' => { 500 => 'UprightTransform_4', 502 => 'UprightTransform_4' }, + 'uprighttransform_5' => { 500 => 'UprightTransform_5', 502 => 'UprightTransform_5' }, + 'uprighttransformcount' => { 500 => 'UprightTransformCount', 502 => 'UprightTransformCount' }, + 'uprightversion' => { 500 => 'UprightVersion', 502 => 'UprightVersion' }, + 'urgency' => { 131 => 0xa, 517 => 'Urgency' }, + 'url' => { 157 => 'URL', 330 => 'URL', 389 => 0x40b, 519 => 'url' }, + 'url_list' => { 389 => 0x41e }, + 'urla-platform' => { 519 => [\'url','urlA-platform'] }, + 'urlurl' => { 519 => [\'url','urlUrl'] }, + 'usablemeteringmodes' => { 84 => 0x10a }, + 'usableshootingmodes' => { 84 => 0x109 }, + 'usage' => { 520 => 'usage' }, + 'usageterms' => { 533 => 'UsageTerms' }, + 'usbpowerdelivery' => { 239 => 0x762, 240 => 0x664, 241 => 0x694, 242 => 0x6fc }, + 'usedialwithouthold' => { 313 => 0xbf, 314 => 0xbf, 315 => 0xbf }, + 'useguidelines' => { 498 => 'useGuidelines' }, + 'usepanoramaviewer' => { 488 => 'UsePanoramaViewer' }, + 'usercollection' => { 394 => 'collection.user' }, + 'usercomment' => { 97 => 0x805, 119 => 0x9286, 506 => 'UserComment' }, + 'userdef1picturestyle' => { 19 => 0x10c, 70 => 0xd8, 71 => 0xf0 }, + 'userdef2picturestyle' => { 19 => 0x10e, 70 => 0xda, 71 => 0xf2 }, + 'userdef3picturestyle' => { 19 => 0x110, 70 => 0xdc, 71 => 0xf4 }, + 'userfields' => { 493 => 'UserFields' }, + 'userlabel' => { 401 => 0x2b, 402 => 0x5a, 403 => 0x68 }, + 'userprofile' => { 335 => 0x302, 339 => 0x34c, 342 => 0x3038 }, + 'userrating' => { 394 => 'rating.user', 400 => 'urat' }, + 'usmlenselectronicmf' => { 2 => 0x7, 83 => 0x7, 84 => 0x501 }, + 'uspsnumber' => { 519 => 'uspsNumber' }, + 'utmeasting' => { 168 => 'Easting' }, + 'utmmapdatum' => { 168 => 'Datum' }, + 'utmnorthing' => { 168 => 'Northing' }, + 'utmzone' => { 168 => 'Zone' }, + 'uuid' => { 500 => 'UUID', 502 => 'UUID' }, + 'uv-irfiltercorrection' => { 335 => 0x325 }, + 'validbits' => { 321 => 0x611, 322 => 0x102c }, + 'validpixeldepth' => { 325 => 0x611 }, + 'variablelowpassfilter' => { 440 => 0x2028 }, + 'variousmodes' => { 140 => 0x26 }, + 'variousmodes2' => { 140 => 0x3a }, + 'variprogram' => { 234 => 0xab }, + 'varraydacnominalvalues' => { 138 => 0x191e }, + 'vendor' => { 400 => 'vndr' }, + 'vendorinfo' => { 479 => 'VendorInfo' }, + 'vendorinfomanufacturer' => { 479 => [\'VendorInfo','VendorInfoManufacturer'] }, + 'vendorinfomodel' => { 479 => [\'VendorInfo','VendorInfoModel'] }, + 'vendorinfonotes' => { 479 => [\'VendorInfo','VendorInfoNotes'] }, + 'verbatimidentification' => { 118 => [\'Identification','IdentificationVerbatimIdentification'] }, + 'version' => { 127 => 0x0, 391 => 'Version', 394 => 'version', 500 => 'Version', 502 => 'Version', 511 => 'Version' }, + 'versioncreatedate' => { 476 => 0xd100 }, + 'versionid' => { 530 => 'VersionID' }, + 'versionidentifier' => { 519 => 'versionIdentifier' }, + 'versionmodifydate' => { 476 => 0xd101 }, + 'versions' => { 530 => 'Versions' }, + 'versionscomments' => { 530 => [\'Versions','VersionsComments'] }, + 'versionsevent' => { 530 => [\'Versions','VersionsEvent'] }, + 'versionseventaction' => { 530 => [\'Versions','VersionsEventAction'] }, + 'versionseventchanged' => { 530 => [\'Versions','VersionsEventChanged'] }, + 'versionseventinstanceid' => { 530 => [\'Versions','VersionsEventInstanceID'] }, + 'versionseventparameters' => { 530 => [\'Versions','VersionsEventParameters'] }, + 'versionseventsoftwareagent' => { 530 => [\'Versions','VersionsEventSoftwareAgent'] }, + 'versionseventwhen' => { 530 => [\'Versions','VersionsEventWhen'] }, + 'versionsmodifier' => { 530 => [\'Versions','VersionsModifier'] }, + 'versionsmodifydate' => { 530 => [\'Versions','VersionsModifyDate'] }, + 'versionsversion' => { 530 => [\'Versions','VersionsVersion'] }, + 'verticalafonbutton' => { 297 => '3.2', 298 => '47.2', 300 => '79.1', 314 => 0x11d, 315 => 0x135 }, + 'verticalclockoverlaps' => { 138 => 0x412 }, + 'verticalfuncbutton' => { 298 => '42.2', 300 => '67.1', 314 => 0x115, 315 => 0x12d }, + 'verticalfuncbuttonplaybackmode' => { 314 => 0x1b7, 315 => 0x1cf }, + 'verticalfuncbuttonplusdials' => { 298 => '43.1' }, + 'verticalfuncplusdials' => { 300 => '68.1' }, + 'verticalisobutton' => { 242 => 0x792 }, + 'verticalmovieafonbutton' => { 314 => 0x1f9, 315 => 0x211 }, + 'verticalmoviefuncbutton' => { 314 => 0x1e1, 315 => 0x1f9 }, + 'verticalmultiselector' => { 298 => '42.1', 300 => '66.1', 301 => '66.1', 311 => '66.1', 314 => 0x18f, 315 => 0x1a7 }, + 'verticalmultiselectorplaybackmode' => { 314 => 0x125, 315 => 0x13d }, + 'vfdisplayillumination' => { 2 => 0x11, 84 => [0x510,0x51d] }, + 'vibrance' => { 494 => 'Vibrance', 500 => 'Vibrance', 502 => 'Vibrance' }, + 'vibrationreduction' => { 262 => [0x75,0x82,0x1ae], 270 => '586.1', 277 => '590.2', 281 => 0x4 }, + 'videoalphamode' => { 529 => 'videoAlphaMode' }, + 'videoalphapremultiplecolor' => { 529 => 'videoAlphaPremultipleColor' }, + 'videoalphapremultiplecolora' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorA'] }, + 'videoalphapremultiplecolorb' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorB'] }, + 'videoalphapremultiplecolorblack' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorBlack'] }, + 'videoalphapremultiplecolorblue' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorBlue'] }, + 'videoalphapremultiplecolorcyan' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorCyan'] }, + 'videoalphapremultiplecolorgray' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorGray'] }, + 'videoalphapremultiplecolorgreen' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorGreen'] }, + 'videoalphapremultiplecolorl' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorL'] }, + 'videoalphapremultiplecolormagenta' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorMagenta'] }, + 'videoalphapremultiplecolormode' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorMode'] }, + 'videoalphapremultiplecolorred' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorRed'] }, + 'videoalphapremultiplecolorswatchname' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorSwatchName'] }, + 'videoalphapremultiplecolortint' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorTint'] }, + 'videoalphapremultiplecolortype' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorType'] }, + 'videoalphapremultiplecoloryellow' => { 529 => [\'videoAlphaPremultipleColor','videoAlphaPremultipleColorYellow'] }, + 'videoalphaunityistransparent' => { 529 => 'videoAlphaUnityIsTransparent' }, + 'videobitrate' => { 514 => 'videoBitRate' }, + 'videobitratemode' => { 514 => 'videoBitRateMode' }, + 'videoburstmode' => { 340 => 0xbb }, + 'videoburstresolution' => { 340 => 0xb3 }, + 'videocodec' => { 67 => 0x74 }, + 'videocolorspace' => { 529 => 'videoColorSpace' }, + 'videocompression' => { 127 => 0x3806 }, + 'videocompressor' => { 529 => 'videoCompressor' }, + 'videodisplayaspectratio' => { 514 => 'videoDisplayAspectRatio' }, + 'videoencodingprofile' => { 514 => 'videoEncodingProfile' }, + 'videofieldorder' => { 529 => 'videoFieldOrder' }, + 'videoframerate' => { 340 => 0x27, 529 => 'videoFrameRate' }, + 'videoframesize' => { 529 => 'videoFrameSize' }, + 'videoframesizeh' => { 529 => [\'videoFrameSize','videoFrameSizeH'] }, + 'videoframesizeunit' => { 529 => [\'videoFrameSize','videoFrameSizeUnit'] }, + 'videoframesizew' => { 529 => [\'videoFrameSize','videoFrameSizeW'] }, + 'videomoddate' => { 529 => 'videoModDate' }, + 'videopixelaspectratio' => { 529 => 'videoPixelAspectRatio' }, + 'videopixeldepth' => { 529 => 'videoPixelDepth' }, + 'videopreburst' => { 340 => 0xc1 }, + 'videoquality' => { 113 => 0x4003 }, + 'videorecordingmode' => { 127 => 0x3803 }, + 'videoshottype' => { 514 => 'VideoShotType' }, + 'videoshottypeidentifier' => { 514 => [\'VideoShotType','VideoShotTypeIdentifier'] }, + 'videoshottypename' => { 514 => [\'VideoShotType','VideoShotTypeName'] }, + 'videostreamscount' => { 514 => 'videoStreamsCount' }, + 'viewfinder' => { 384 => 0x455 }, + 'viewfinderdisplay' => { 297 => '12.4', 298 => '6.3' }, + 'viewfinderwarning' => { 297 => '13.4', 307 => '3.5', 308 => '6.2', 312 => '4.4' }, + 'viewfinderwarnings' => { 84 => 0x40a }, + 'viewinfoduringexposure' => { 84 => 0x407 }, + 'viewingmode' => { 429 => 0x2f, 445 => 0x18 }, + 'viewingmode2' => { 429 => [0x85,0x285] }, + 'viewmodeshoweffectsofsettings' => { 242 => 0x7d2, 313 => 0x2a9 }, + 'viewpoint' => { 518 => 'viewpoint' }, + 'vignetteamount' => { 500 => 'VignetteAmount', 502 => 'VignetteAmount' }, + 'vignettecontrol' => { 234 => 0x2a, 290 => 0x76a43205 }, + 'vignettecontrolintensity' => { 290 => 0xac6bd5c0 }, + 'vignettecorrectionalreadyapplied' => { 497 => 'VignetteCorrectionAlreadyApplied' }, + 'vignettemidpoint' => { 500 => 'VignetteMidpoint', 502 => 'VignetteMidpoint' }, + 'vignetting' => { 407 => 0x1011, 414 => 0xa052 }, + 'vignettingcorrection' => { 119 => 0x7031, 414 => 0xa053, 440 => 0x2011 }, + 'vignettingcorrparams' => { 119 => 0x7032, 471 => 0x64a, 472 => [0x34a,0x350,0x35c,0x368] }, + 'vignettingsetting' => { 414 => 0xa054 }, + 'virtualfocallength' => { 515 => 'VirtualFocalLength' }, + 'virtualhorizonstyle' => { 313 => 0x167, 314 => 0x167, 315 => 0x17f }, + 'virtualimagexcenter' => { 515 => 'VirtualImageXCenter' }, + 'virtualimageycenter' => { 515 => 'VirtualImageYCenter' }, + 'visualcolor' => { 514 => 'VisualColour' }, + 'visualtechnique' => { 518 => 'visualTechnique' }, + 'voicememo' => { 416 => 0x216 }, + 'volume' => { 519 => 'volume' }, + 'vr_0x66' => { 262 => 0x66 }, + 'vrdoffset' => { 64 => 0xd0 }, + 'vrmode' => { 239 => 0x226, 240 => 0x212, 241 => 0x226, 242 => 0x226, 281 => 0x6 }, + 'vrtype' => { 281 => 0x8 }, + 'waterdepth' => { 119 => 0x9403, 507 => 'WaterDepth' }, + 'wb_bluelevel3500k' => { 187 => 0x19a }, + 'wb_bluelevel6500k' => { 187 => 0x18a }, + 'wb_bluelevelcustom' => { 187 => 0x18e }, + 'wb_bluelevelscloudy' => { 187 => 0x106 }, + 'wb_bluelevelsdaylight' => { 187 => 0xea }, + 'wb_bluelevelsflash' => { 187 => 0x122 }, + 'wb_bluelevelsfluorescent' => { 187 => [0x40a,0x15a] }, + 'wb_bluelevelskelvin' => { 187 => 0x254 }, + 'wb_bluelevelsshade' => { 187 => 0x176 }, + 'wb_bluelevelstungsten' => { 187 => 0xce }, + 'wb_gbrglevels' => { 187 => 0xae, 190 => 0x4 }, + 'wb_glevel' => { 321 => 0x11f }, + 'wb_glevel3000k' => { 321 => 0x113 }, + 'wb_glevel3300k' => { 321 => 0x114 }, + 'wb_glevel3600k' => { 321 => 0x115 }, + 'wb_glevel3900k' => { 321 => 0x116 }, + 'wb_glevel4000k' => { 321 => 0x117 }, + 'wb_glevel4300k' => { 321 => 0x118 }, + 'wb_glevel4500k' => { 321 => 0x119 }, + 'wb_glevel4800k' => { 321 => 0x11a }, + 'wb_glevel5300k' => { 321 => 0x11b }, + 'wb_glevel6000k' => { 321 => 0x11c }, + 'wb_glevel6600k' => { 321 => 0x11d }, + 'wb_glevel7500k' => { 321 => 0x11e }, + 'wb_grbglevels' => { 204 => 0x0, 447 => 0x7303 }, + 'wb_grbglevelsauto' => { 81 => 0x2, 447 => 0x7302 }, + 'wb_grbglevelscloudy' => { 81 => 0x12 }, + 'wb_grbglevelscustom1' => { 81 => 0x42 }, + 'wb_grbglevelscustom2' => { 81 => 0x4a }, + 'wb_grbglevelsdaylight' => { 81 => 0xa }, + 'wb_grbglevelsflash' => { 81 => 0x32 }, + 'wb_grbglevelsfluorescent' => { 81 => 0x22 }, + 'wb_grbglevelsfluorhigh' => { 81 => 0x2a }, + 'wb_grbglevelstungsten' => { 81 => 0x1a }, + 'wb_grbglevelsunderwater' => { 81 => 0x3a }, + 'wb_rbgglevels' => { 201 => 0x0 }, + 'wb_rblevels' => { 205 => 0x270, 234 => 0xc, 321 => 0x100 }, + 'wb_rblevels1' => { 346 => 0x2 }, + 'wb_rblevels2' => { 346 => 0x5 }, + 'wb_rblevels3' => { 346 => 0x8 }, + 'wb_rblevels3000k' => { 321 => 0x102 }, + 'wb_rblevels3300k' => { 321 => 0x103 }, + 'wb_rblevels3500k' => { 187 => 0x430 }, + 'wb_rblevels3600k' => { 321 => 0x104 }, + 'wb_rblevels3900k' => { 321 => 0x105 }, + 'wb_rblevels4' => { 346 => 0xb }, + 'wb_rblevels4000k' => { 321 => 0x106 }, + 'wb_rblevels4300k' => { 321 => 0x107 }, + 'wb_rblevels4500k' => { 321 => 0x108 }, + 'wb_rblevels4800k' => { 321 => 0x109 }, + 'wb_rblevels5' => { 346 => 0xe }, + 'wb_rblevels5300k' => { 321 => 0x10a }, + 'wb_rblevels6' => { 346 => 0x11 }, + 'wb_rblevels6000k' => { 321 => 0x10b }, + 'wb_rblevels6500k' => { 187 => 0x420 }, + 'wb_rblevels6600k' => { 321 => 0x10c }, + 'wb_rblevels7' => { 346 => 0x14 }, + 'wb_rblevels7500k' => { 321 => 0x10d }, + 'wb_rblevelsauto' => { 205 => 0x272, 325 => 0x110 }, + 'wb_rblevelscloudy' => { 187 => 0x3f0, 189 => 0x10, 205 => 0x296, 325 => 0x121 }, + 'wb_rblevelscoolwhitef' => { 187 => 0x308, 189 => 0x14 }, + 'wb_rblevelscoolwhitefluor' => { 325 => 0x132 }, + 'wb_rblevelscustom' => { 187 => 0x424, 189 => 0x1c }, + 'wb_rblevelscwb1' => { 321 => 0x10e }, + 'wb_rblevelscwb2' => { 321 => 0x10f }, + 'wb_rblevelscwb3' => { 321 => 0x110 }, + 'wb_rblevelscwb4' => { 321 => 0x111 }, + 'wb_rblevelsdaylight' => { 187 => [0x3ec,0x528], 189 => 0xc, 205 => 0x274 }, + 'wb_rblevelsdaylightf' => { 189 => 0x24 }, + 'wb_rblevelsdaylightfluor' => { 325 => 0x130 }, + 'wb_rblevelsdaywhitef' => { 189 => 0x28 }, + 'wb_rblevelsdaywhitefluor' => { 325 => 0x131 }, + 'wb_rblevelseveningsunlight' => { 325 => 0x124 }, + 'wb_rblevelsfineweather' => { 325 => 0x122 }, + 'wb_rblevelsflash' => { 187 => [0x3f4,0x304], 189 => 0x18, 205 => 0x2a4 }, + 'wb_rblevelsfluorescent' => { 205 => 0x290 }, + 'wb_rblevelsincandescent' => { 205 => 0x282 }, + 'wb_rblevelsshade' => { 187 => 0x418, 189 => 0x20, 205 => 0x2b2, 325 => 0x120 }, + 'wb_rblevelstungsten' => { 187 => 0x3e8, 189 => 0x8, 325 => 0x123 }, + 'wb_rblevelsused' => { 325 => 0x100 }, + 'wb_rblevelswhitef' => { 189 => 0x2c }, + 'wb_rblevelswhitefluorescent' => { 325 => 0x133 }, + 'wb_redlevel3500k' => { 187 => 0x198 }, + 'wb_redlevel6500k' => { 187 => 0x188 }, + 'wb_redlevelcustom' => { 187 => 0x18c }, + 'wb_redlevelscloudy' => { 187 => 0xf8 }, + 'wb_redlevelsdaylight' => { 187 => 0xdc }, + 'wb_redlevelsflash' => { 187 => 0x114 }, + 'wb_redlevelsfluorescent' => { 187 => [0x3fc,0x14c] }, + 'wb_redlevelskelvin' => { 187 => 0x1be }, + 'wb_redlevelsshade' => { 187 => 0x168 }, + 'wb_redlevelstungsten' => { 187 => 0xc0 }, + 'wb_rgbglevels' => { 203 => 0x0 }, + 'wb_rgblevels' => { 187 => [0x546,0x96], 336 => 0xd, 337 => 0x413, 342 => 0x3036, 384 => 0x107, 449 => 0x117c, 450 => 0x1180, 451 => 0x115c, 452 => 0x11d8, 453 => 0x11b4, 454 => 0x106c, 455 => 0x264, 456 => 0x264, 457 => 0x252 }, + 'wb_rgblevels1' => { 347 => 0x2 }, + 'wb_rgblevels2' => { 347 => 0x6 }, + 'wb_rgblevels2500k' => { 447 => 0x782d }, + 'wb_rgblevels3' => { 347 => 0xa }, + 'wb_rgblevels3200k' => { 447 => 0x782c }, + 'wb_rgblevels4' => { 347 => 0xe }, + 'wb_rgblevels4500k' => { 447 => [0x7484,0x7824] }, + 'wb_rgblevels5' => { 347 => 0x12 }, + 'wb_rgblevels6' => { 347 => 0x16 }, + 'wb_rgblevels6000k' => { 447 => 0x782b }, + 'wb_rgblevels7' => { 347 => 0x1a }, + 'wb_rgblevels8500k' => { 447 => 0x782a }, + 'wb_rgblevelsauto' => { 418 => 0x0 }, + 'wb_rgblevelscloudy' => { 447 => [0x7481,0x7821] }, + 'wb_rgblevelscustom1' => { 418 => 0x15 }, + 'wb_rgblevelscustom2' => { 418 => 0x18 }, + 'wb_rgblevelscustom3' => { 418 => 0x1b }, + 'wb_rgblevelsdaylight' => { 418 => 0x3, 447 => [0x7480,0x7820] }, + 'wb_rgblevelsflash' => { 418 => 0x12, 447 => [0x7483,0x7823] }, + 'wb_rgblevelsfluorescent' => { 418 => 0xf, 447 => [0x7486,0x7826] }, + 'wb_rgblevelsfluorescentm1' => { 447 => 0x7829 }, + 'wb_rgblevelsfluorescentp1' => { 447 => 0x7827 }, + 'wb_rgblevelsfluorescentp2' => { 447 => 0x7828 }, + 'wb_rgblevelsincandescent' => { 418 => 0xc }, + 'wb_rgblevelsovercast' => { 418 => 0x9 }, + 'wb_rgblevelsshade' => { 418 => 0x6, 447 => 0x7825 }, + 'wb_rgblevelstungsten' => { 447 => [0x7482,0x7822] }, + 'wb_rgblevelsunknown0' => { 419 => 0x0 }, + 'wb_rgblevelsunknown1' => { 419 => 0x3 }, + 'wb_rgblevelsunknown2' => { 419 => 0x6 }, + 'wb_rgblevelsunknown3' => { 419 => 0x9 }, + 'wb_rgblevelsunknown4' => { 419 => 0xc }, + 'wb_rgblevelsunknown5' => { 419 => 0xf }, + 'wb_rgblevelsunknown6' => { 419 => 0x12 }, + 'wb_rgblevelsunknown7' => { 419 => 0x15 }, + 'wb_rgblevelsunknown8' => { 419 => 0x18 }, + 'wb_rgblevelsunknown9' => { 419 => 0x1b }, + 'wb_rgbmuldaylight' => { 138 => 0x852 }, + 'wb_rgbmulflash' => { 138 => 0x855 }, + 'wb_rgbmulfluorescent' => { 138 => 0x854 }, + 'wb_rgbmultungsten' => { 138 => 0x853 }, + 'wb_rggbblacklevels' => { 35 => 0x25 }, + 'wb_rggblevels' => { 119 => 0x7313, 190 => 0x4, 202 => 0x0, 206 => 0x13e8, 207 => 0x38, 447 => 0x7313 }, + 'wb_rggblevelsasshot' => { 38 => 0x0, 39 => 0x0, 40 => 0x19, 41 => 0x55, 42 => 0x69, 43 => 0x22, 44 => 0x3f, 47 => 0x3f, 48 => 0x3f, 49 => 0x3f, 50 => 0x47 }, + 'wb_rggblevelsauto' => { 35 => 0x1, 38 => 0x5, 39 => 0x8, 40 => 0x1e, 41 => 0x5a, 42 => 0x6e, 43 => 0x18, 44 => 0x44, 47 => 0x44, 48 => 0x44, 49 => 0x44, 50 => 0x4c, 206 => 0x1478, 207 => 0x114, 414 => 0xa022, 447 => 0x7312 }, + 'wb_rggblevelsblack' => { 414 => 0xa028 }, + 'wb_rggblevelscloudy' => { 35 => 0xd, 38 => 0x1e, 39 => 0x30, 40 => 0x2d, 41 => 0xa0, 42 => 0xd7, 43 => 0x31, 44 => 0x58, 47 => 0x71, 48 => 0x8a, 49 => 0x8f, 50 => 0x92, 206 => 0x1408, 207 => 0x60, 375 => 0x20f, 383 => 0x14 }, + 'wb_rggblevelscustom' => { 35 => 0x1d, 44 => 0x80, 206 => 0x1468, 207 => 0x100 }, + 'wb_rggblevelscustom1' => { 40 => 0x41 }, + 'wb_rggblevelscustom2' => { 40 => 0x46 }, + 'wb_rggblevelsdaylight' => { 35 => 0x5, 38 => 0x14, 39 => 0x20, 40 => 0x23, 41 => 0x96, 42 => 0xcd, 43 => 0x27, 44 => 0x4e, 47 => 0x67, 48 => 0x80, 49 => 0x85, 50 => 0x88, 206 => 0x13f8, 207 => 0x4c, 375 => 0x20d, 383 => 0x2 }, + 'wb_rggblevelsflash' => { 35 => 0x19, 38 => 0x32, 39 => 0x50, 40 => 0x3c, 41 => 0xb4, 42 => 0xeb, 43 => 0x45, 44 => 0x6c, 47 => 0x85, 48 => 0x9e, 49 => 0xa3, 50 => 0xa6, 206 => 0x1448, 375 => 0x214, 383 => 0x41 }, + 'wb_rggblevelsfluorescent' => { 35 => 0x15, 38 => 0x28, 39 => 0x40, 40 => 0x37, 41 => 0xaa, 42 => 0xe1, 43 => 0x3b, 44 => 0x62, 47 => 0x7b, 48 => 0x94, 49 => 0x99, 50 => 0x9c }, + 'wb_rggblevelsfluorescentd' => { 207 => 0xc4, 375 => 0x211, 383 => 0x26 }, + 'wb_rggblevelsfluorescentl' => { 383 => 0x4a }, + 'wb_rggblevelsfluorescentn' => { 207 => 0xb0, 375 => 0x212, 383 => 0x2f }, + 'wb_rggblevelsfluorescentw' => { 206 => 0x1438, 207 => 0x9c, 375 => 0x213, 383 => 0x38 }, + 'wb_rggblevelshtmercury' => { 207 => 0xd8 }, + 'wb_rggblevelsilluminator1' => { 414 => 0xa023 }, + 'wb_rggblevelsilluminator2' => { 414 => 0xa024 }, + 'wb_rggblevelskelvin' => { 35 => 0x21, 38 => 0x2d, 39 => 0x48, 41 => 0xaf, 42 => 0xe6, 43 => 0x40, 44 => 0x67, 47 => 0x80, 48 => 0x99, 49 => 0x9e, 50 => 0xa1 }, + 'wb_rggblevelsmeasured' => { 38 => 0xa, 39 => 0x10, 41 => 0x5f, 42 => 0x73, 44 => 0x49, 47 => 0x49, 48 => 0x49, 49 => 0x49, 50 => 0x51 }, + 'wb_rggblevelspc1' => { 43 => 0x90, 44 => 0x71 }, + 'wb_rggblevelspc2' => { 43 => 0x95, 44 => 0x76 }, + 'wb_rggblevelspc3' => { 43 => 0x9a, 44 => 0x7b }, + 'wb_rggblevelsshade' => { 35 => 0x9, 38 => 0x19, 39 => 0x28, 40 => 0x28, 41 => 0x9b, 42 => 0xd2, 43 => 0x2c, 44 => 0x53, 47 => 0x6c, 48 => 0x85, 49 => 0x8a, 50 => 0x8d, 207 => 0x74, 375 => 0x20e, 383 => 0xb }, + 'wb_rggblevelstungsten' => { 35 => 0x11, 38 => 0x23, 39 => 0x38, 40 => 0x32, 41 => 0xa5, 42 => 0xdc, 43 => 0x36, 44 => 0x5d, 47 => 0x76, 48 => 0x8f, 49 => 0x94, 50 => 0x97, 206 => 0x1428, 207 => 0x88, 375 => 0x210, 383 => 0x1d }, + 'wb_rggblevelsuncorrected' => { 414 => 0xa021 }, + 'wb_rggblevelsunknown' => { 38 => 0xf, 39 => 0x18, 41 => 0x64, 42 => 0x78, 43 => 0x1d, 47 => 0x4e, 48 => 0x4e, 49 => 0x4e, 50 => 0x56, 383 => 0x53 }, + 'wb_rggblevelsunknown10' => { 38 => 0x5f, 39 => 0x98, 41 => 0x91, 42 => 0xa5, 43 => 0x72, 47 => 0x9e, 48 => 0x7b, 49 => 0x7b, 50 => 0x83 }, + 'wb_rggblevelsunknown11' => { 38 => 0x64, 39 => 0xa0, 41 => 0xb9, 42 => [0xaa,0xaf], 43 => 0x77, 47 => 0xa3, 48 => 0xa3, 49 => 0x80, 50 => 0xab }, + 'wb_rggblevelsunknown12' => { 38 => 0x69, 39 => 0xa8, 41 => 0xbe, 42 => 0xb4, 43 => 0x7c, 47 => 0xa8, 48 => 0xa8, 49 => 0xa8, 50 => 0xb0 }, + 'wb_rggblevelsunknown13' => { 38 => 0x6e, 39 => 0xb0, 41 => 0xc3, 42 => 0xb9, 43 => 0x81, 47 => 0xad, 48 => 0xad, 49 => 0xad, 50 => 0xb5 }, + 'wb_rggblevelsunknown14' => { 41 => 0xc8, 42 => 0xbe, 43 => 0x86, 47 => 0xb2, 48 => 0xb2, 49 => 0xb2, 50 => 0xba }, + 'wb_rggblevelsunknown15' => { 41 => 0xcd, 42 => 0xc3, 43 => 0x8b, 47 => 0xb7, 48 => 0xb7, 49 => 0xb7, 50 => 0xbf }, + 'wb_rggblevelsunknown16' => { 41 => 0xd2, 42 => 0xc8, 43 => 0x9f, 48 => 0xbc, 49 => 0xbc, 50 => 0xc4 }, + 'wb_rggblevelsunknown17' => { 41 => 0xd7, 42 => 0xf0, 48 => 0xc1, 49 => 0xc1, 50 => 0xc9 }, + 'wb_rggblevelsunknown18' => { 41 => 0xdc, 42 => 0xf5, 48 => 0xc6, 49 => 0xc6, 50 => 0xce }, + 'wb_rggblevelsunknown19' => { 41 => 0xe1, 42 => 0xfa, 48 => 0xcb, 49 => 0xcb, 50 => 0xd3 }, + 'wb_rggblevelsunknown2' => { 38 => 0x37, 39 => 0x58, 41 => 0x69, 42 => 0x7d, 43 => 0x4a, 47 => 0x53, 48 => 0x53, 49 => 0x53, 50 => 0x5b }, + 'wb_rggblevelsunknown20' => { 41 => 0xe6, 42 => 0xff, 48 => 0xd0, 49 => 0xd0, 50 => 0xd8 }, + 'wb_rggblevelsunknown21' => { 41 => 0xeb, 42 => 0x104, 49 => 0xd5, 50 => 0xdd }, + 'wb_rggblevelsunknown22' => { 41 => 0xf0, 42 => 0x109, 49 => 0xda, 50 => 0xe2 }, + 'wb_rggblevelsunknown23' => { 41 => 0xf5, 42 => 0x10e, 49 => 0xdf, 50 => 0xe7 }, + 'wb_rggblevelsunknown24' => { 41 => 0xfa, 42 => 0x113, 49 => 0xe4, 50 => 0xec }, + 'wb_rggblevelsunknown25' => { 41 => 0xff, 42 => 0x118, 49 => 0xe9, 50 => 0xf1 }, + 'wb_rggblevelsunknown26' => { 41 => 0x104, 42 => 0x11d, 49 => 0xee, 50 => 0xf6 }, + 'wb_rggblevelsunknown27' => { 41 => 0x109, 42 => 0x122, 49 => 0xf3, 50 => 0xfb }, + 'wb_rggblevelsunknown28' => { 41 => 0x10e, 49 => 0xf8, 50 => 0x100 }, + 'wb_rggblevelsunknown29' => { 41 => 0x113, 49 => 0xfd, 50 => 0x105 }, + 'wb_rggblevelsunknown3' => { 38 => 0x3c, 39 => 0x60, 41 => 0x6e, 42 => 0x82, 43 => 0x4f, 47 => 0x58, 48 => 0x58, 49 => 0x58, 50 => 0x60 }, + 'wb_rggblevelsunknown30' => { 49 => 0x102 }, + 'wb_rggblevelsunknown4' => { 38 => 0x41, 39 => 0x68, 41 => 0x73, 42 => 0x87, 43 => 0x54, 47 => 0x5d, 48 => 0x5d, 49 => 0x5d, 50 => 0x65 }, + 'wb_rggblevelsunknown5' => { 38 => 0x46, 39 => 0x70, 41 => 0x78, 42 => 0x8c, 43 => 0x59, 47 => 0x62, 48 => 0x62, 49 => 0x62, 50 => 0x6a }, + 'wb_rggblevelsunknown6' => { 38 => 0x4b, 39 => 0x78, 41 => 0x7d, 42 => 0x91, 43 => 0x5e, 47 => 0x8a, 48 => 0x67, 49 => 0x67, 50 => 0x6f }, + 'wb_rggblevelsunknown7' => { 38 => 0x50, 39 => 0x80, 41 => 0x82, 42 => 0x96, 43 => 0x63, 47 => 0x8f, 48 => 0x6c, 49 => 0x6c, 50 => 0x74 }, + 'wb_rggblevelsunknown8' => { 38 => 0x55, 39 => 0x88, 41 => 0x87, 42 => 0x9b, 43 => 0x68, 47 => 0x94, 48 => 0x71, 49 => 0x71, 50 => 0x79 }, + 'wb_rggblevelsunknown9' => { 38 => 0x5a, 39 => 0x90, 41 => 0x8c, 42 => 0xa0, 43 => 0x6d, 47 => 0x99, 48 => 0x76, 49 => 0x76, 50 => 0x7e }, + 'wb_rggblevelsuserselected' => { 383 => 0x5c }, + 'wbadjblueamber' => { 103 => 0x20106 }, + 'wbadjbluebalance' => { 296 => 0x8 }, + 'wbadjcolortemp' => { 103 => 0x20102, 108 => 0x1a }, + 'wbadjlighting' => { 296 => 0x14 }, + 'wbadjmagentagreen' => { 103 => 0x20105 }, + 'wbadjmode' => { 296 => 0x10 }, + 'wbadjredbalance' => { 296 => 0x0 }, + 'wbadjrggblevels' => { 103 => 0x20125, 108 => 0x6 }, + 'wbadjtemperature' => { 296 => 0x18 }, + 'wbadjtint' => { 296 => 0x25 }, + 'wbbluelevel' => { 335 => 0x324, 340 => 0x8006, 345 => 0x26 }, + 'wbbracketingsteps' => { 198 => 0x10, 199 => 0x10, 271 => 0x174d }, + 'wbbracketmode' => { 57 => 0x9 }, + 'wbbracketshotnumber' => { 187 => 0x2b, 407 => 0x101a }, + 'wbbracketvalueab' => { 57 => 0xc }, + 'wbbracketvaluegm' => { 57 => 0xd }, + 'wbbutton' => { 242 => 0x80a }, + 'wbfinetuneactive' => { 108 => 0x24 }, + 'wbfinetunesaturation' => { 108 => 0x28 }, + 'wbfinetunetone' => { 108 => 0x2c }, + 'wbgreenlevel' => { 335 => 0x323, 340 => 0x8005, 345 => 0x25 }, + 'wbmediaimagesizesetting' => { 84 => 0x708 }, + 'wbmode' => { 189 => 0x4, 322 => 0x1015 }, + 'wbredlevel' => { 335 => 0x322, 340 => 0x8004, 345 => 0x24 }, + 'wbscale' => { 190 => 0x0 }, + 'wbshiftab' => { 74 => 0xc, 340 => 0x46, 357 => 0x10 }, + 'wbshiftab_gm' => { 440 => 0x2014 }, + 'wbshiftab_gm_precise' => { 440 => 0x2026 }, + 'wbshiftcreativecontrol' => { 340 => 0x92 }, + 'wbshiftgm' => { 74 => 0xd, 340 => 0x47, 357 => 0x11 }, + 'wbshiftintelligentauto' => { 340 => 0x8b }, + 'wbtype1' => { 346 => 0x1, 347 => 0x1 }, + 'wbtype2' => { 346 => 0x4, 347 => 0x5 }, + 'wbtype3' => { 346 => 0x7, 347 => 0x9 }, + 'wbtype4' => { 346 => 0xa, 347 => 0xd }, + 'wbtype5' => { 346 => 0xd, 347 => 0x11 }, + 'wbtype6' => { 346 => 0x10, 347 => 0x15 }, + 'wbtype7' => { 346 => 0x13, 347 => 0x19 }, + 'webstatement' => { 533 => 'WebStatement' }, + 'weightedflatsubject' => { 492 => 'weightedFlatSubject' }, + 'what' => { 500 => 'What', 502 => 'What' }, + 'whitebalance' => { 7 => 0x6f, 8 => [0x44,0x4a], 9 => 0xbc, 10 => 0x36, 11 => 0x5e, 12 => 0x36, 13 => 0x78, 14 => 0x6f, 15 => 0x6f, 16 => 0x73, 17 => 0x6f, 18 => 0x78, 19 => 0x54, 20 => 0x6f, 21 => 0xbc, 22 => 0x7b, 24 => 0xbc, 25 => 0xc2, 27 => 0x131, 28 => 0x77, 74 => 0x8, 77 => 0x7, 112 => 0x7, 113 => [0x19,0x2012], 119 => [0xa403,0xfe4e], 127 => 0x1002, 138 => 0x3fc, 139 => 0xfa0d, 140 => 0x40, 151 => 0x1a, 181 => 0x3, 182 => 0xe, 183 => 0x4, 184 => 0xb, 186 => 0x115, 234 => 0x5, 280 => 0x7, 335 => 0x304, 340 => 0x3, 342 => 0x3033, 375 => 0x19, 382 => 0x7, 406 => 0x26, 407 => 0x1003, 417 => [0x88,0x3c,0x7,0x58], 427 => 0xf, 428 => 0xe, 440 => [0x115,0xb054], 500 => 'WhiteBalance', 502 => 'WhiteBalance', 506 => 'WhiteBalance' }, + 'whitebalance0' => { 176 => 'WhiteBalance0' }, + 'whitebalance1' => { 176 => 'WhiteBalance1' }, + 'whitebalance2' => { 176 => 'WhiteBalance2', 317 => 0x500 }, + 'whitebalanceadj' => { 103 => 0x20101, 108 => 0x18, 290 => 0x76a43204 }, + 'whitebalanceautoadjustment' => { 353 => 0x0 }, + 'whitebalancebias' => { 113 => 0x2011, 322 => 0x304, 340 => 0x23 }, + 'whitebalanceblue' => { 74 => 0x7 }, + 'whitebalancebracket' => { 317 => 0x502, 322 => 0x303 }, + 'whitebalancebracketing' => { 184 => 0x22, 187 => 0x2c, 438 => 0x2c }, + 'whitebalancebuttonplaybackmode' => { 314 => 0x1c5, 315 => 0x1dd }, + 'whitebalancecomp' => { 325 => 0x1001 }, + 'whitebalancedetected' => { 138 => 0x3fb }, + 'whitebalancefinetune' => { 127 => 0x100a, 184 => 0x38, 186 => 0x112, 234 => [0xb,0x3f], 407 => 0x1004, 427 => 0x6, 428 => 0x5, 440 => 0x112 }, + 'whitebalancemode' => { 138 => 0x3fa, 375 => 0x1a }, + 'whitebalancered' => { 74 => 0x6 }, + 'whitebalanceset' => { 356 => 0xa }, + 'whitebalancesetting' => { 184 => 0x23, 427 => 0x5, 428 => 0x4, 429 => 0x16, 445 => 0xd }, + 'whitebalancesetup' => { 414 => 0x41 }, + 'whitebalancetemperature' => { 317 => 0x501 }, + 'whiteboard' => { 322 => 0x301 }, + 'whitelevel' => { 119 => 0xc61d, 375 => 0x7e, 447 => 0x787f }, + 'whitepoint' => { 119 => 0x13e, 339 => 0x35d, 375 => 0x201, 525 => 'WhitePoint' }, + 'whites2012' => { 500 => 'Whites2012', 502 => 'Whites2012' }, + 'whitesadj' => { 476 => 0x9017 }, + 'wideadapter' => { 407 => 0x1017 }, + 'widefocuszone' => { 181 => 0x2f }, + 'widerange' => { 416 => 0x20f }, + 'windmode' => { 138 => 0x3f4 }, + 'windnoisereduction' => { 239 => 0x352, 240 => 0x2d6, 241 => 0x2fe, 242 => 0x2fe }, + 'windowlocation' => { 400 => 'WLOC' }, + 'windowsatom' => { 501 => 'windowsAtom' }, + 'windowsatomextension' => { 501 => [\'windowsAtom','windowsAtomExtension'] }, + 'windowsatominvocationflags' => { 501 => [\'windowsAtom','windowsAtomInvocationFlags'] }, + 'windowsatomuncprojectpath' => { 501 => [\'windowsAtom','windowsAtomUncProjectPath'] }, + 'wordcount' => { 519 => 'wordCount' }, + 'work' => { 392 => "\xa9wrk" }, + 'workcolorspace' => { 103 => 0x10200, 108 => 0x270 }, + 'workflowtag' => { 514 => 'WorkflowTag' }, + 'workflowtagcvid' => { 514 => [\'WorkflowTag','WorkflowTagCvId'] }, + 'workflowtagcvtermid' => { 514 => [\'WorkflowTag','WorkflowTagCvTermId'] }, + 'workflowtagcvtermname' => { 514 => [\'WorkflowTag','WorkflowTagCvTermName'] }, + 'workflowtagcvtermrefinedabout' => { 514 => [\'WorkflowTag','WorkflowTagCvTermRefinedAbout'] }, + 'worktodo' => { 508 => 'WorkToDo' }, + 'worldtimelocation' => { 340 => 0x3a, 375 => 0x22, 381 => '0.1' }, + 'writer' => { 179 => 'WM/Writer' }, + 'writer-editor' => { 131 => 0x7a }, + 'x3filllight' => { 417 => 0x12 }, + 'xattrquarantine' => { 174 => 'com.apple.quarantine' }, + 'xilinxversion' => { 138 => 0x414 }, + 'xml' => { 136 => 'xml ' }, + 'xmp' => { 106 => 0xffff00f6, 120 => 'XMP' }, + 'xmptoolkit' => { 526 => 'xmptk' }, + 'xpauthor' => { 119 => 0x9c9d }, + 'xpcomment' => { 119 => 0x9c9c }, + 'xpkeywords' => { 119 => 0x9c9e }, + 'xposition' => { 119 => 0x11e }, + 'xpsubject' => { 119 => 0x9c9f }, + 'xptitle' => { 119 => 0x9c9b }, + 'xresolution' => { 119 => 0x11a, 134 => 0x3, 390 => 0x0, 525 => 'XResolution' }, + 'xyresolution' => { 123 => 0x3 }, + 'yaw' => { 115 => 0x7, 400 => ['_yaw',"\xa9fyw"] }, + 'yawangle' => { 247 => 0x8, 412 => 0x0 }, + 'ycbcrcoefficients' => { 119 => 0x211, 525 => 'YCbCrCoefficients' }, + 'ycbcrpositioning' => { 119 => 0x213, 525 => 'YCbCrPositioning' }, + 'ycbcrsubsampling' => { 119 => 0x212, 525 => 'YCbCrSubSampling' }, + 'year' => { 392 => 'yrrc', 394 => 'year', 400 => 'yrrc' }, + 'yearcreated' => { 140 => 0x10, 149 => 0xc }, + 'yellowhsl' => { 103 => 0x20912 }, + 'yield' => { 521 => 'yield' }, + 'yposition' => { 119 => 0x11f }, + 'yresolution' => { 119 => 0x11b, 134 => 0x5, 390 => 0x4, 525 => 'YResolution' }, + 'zebrapatterntonerange' => { 313 => 0x211, 314 => 0x211, 315 => 0x229 }, + 'zoneidentifier' => { 120 => 'ZoneIdentifier' }, + 'zonematching' => { 186 => 0x10a, 189 => [0x3a,0x4a], 440 => 0xb024 }, + 'zonematchingmode' => { 184 => 0x14 }, + 'zonematchingon' => { 183 => 0x75 }, + 'zonematchingvalue' => { 427 => 0x1f }, + 'zoomedpreviewlength' => { 322 => 0xf05 }, + 'zoomedpreviewsize' => { 322 => 0xf06 }, + 'zoomedpreviewstart' => { 322 => 0xf04 }, + 'zoomsourcewidth' => { 34 => 0x24 }, + 'zoomstepcount' => { 320 => 0x300, 322 => 0x100d }, + 'zoomtargetwidth' => { 34 => 0x25 }, +); + +# lookup for non-writable tags to check if the name exists +my %tagExists = ( + '_ac3' => 1, + '_h264' => 1, + '_misb' => 1, + '_stream' => 1, + 'a100dataoffset' => 1, + 'aas' => 1, + 'abovecolor' => 1, + 'abstract' => 1, + 'abstractfilename' => 1, + 'accel360fly' => 1, + 'acceldata' => 1, + 'accelerometermatrix' => 1, + 'accelerometertime' => 1, + 'accelerometerunknown' => 1, + 'accelmode' => 1, + 'accelypr' => 1, + 'accessdate' => 1, + 'accompaniment' => 1, + 'accountname' => 1, + 'acdsee' => 1, + 'acidizer' => 1, + 'acidizerflags' => 1, + 'acquisitiontime' => 1, + 'acquisitiontimeday' => 1, + 'acquisitiontimemonth' => 1, + 'acquisitiontimeyear' => 1, + 'acquisitiontimeyearmonth' => 1, + 'acquisitiontimeyearmonthday' => 1, + 'acroform' => 1, + 'activebuf1cols' => 1, + 'activebuf1rows' => 1, + 'activebuf2cols' => 1, + 'activebuf2rows' => 1, + 'activecols' => 1, + 'activectemonitor1cols' => 1, + 'activectemonitor2cols' => 1, + 'activectemonitorrows' => 1, + 'activerows' => 1, + 'actor' => 1, + 'actualcompensation' => 1, + 'actualscalemax' => 1, + 'actualscalemin' => 1, + 'actualsoftpostpadding' => 1, + 'actualsoftprepadding' => 1, + 'additionalinfo' => 1, + 'adjdebuginfo' => 1, + 'adjustedtbnimageheight' => 1, + 'adjustedtbnimagewidth' => 1, + 'adobe_cm' => 1, + 'adobecmtype' => 1, + 'adobecrw' => 1, + 'adobekoda' => 1, + 'adobeleaf' => 1, + 'adobemrw' => 1, + 'adobepano' => 1, + 'adobephotoshop' => 1, + 'adoberaf' => 1, + 'adobesr2' => 1, + 'adoptedneutral' => 1, + 'adult' => 1, + 'advancedcontentencryption' => 1, + 'advancedmutualexcl' => 1, + 'advancedscenemode' => 1, + 'adventrevision' => 1, + 'adventscale' => 1, + 'aedebuginfo' => 1, + 'aeflags' => 1, + 'aehistograminfo' => 1, + 'aeinfo' => 1, + 'aeinfo2' => 1, + 'aeinfo3' => 1, + 'aeinfounknown' => 1, + 'aeliveviewhistograminfo' => 1, + 'aeliveviewlocalhistogram' => 1, + 'aelocalhistogram' => 1, + 'af-cprioritysel' => 1, + 'af-sprioritysel' => 1, + 'afarea' => 1, + 'afareaheights' => 1, + 'afareawidths' => 1, + 'afareaxpositions' => 1, + 'afareaypositions' => 1, + 'afconfig' => 1, + 'afcp' => 1, + 'afcp_iptc' => 1, + 'afcsettings' => 1, + 'afdebuginfo' => 1, + 'aff1' => 1, + 'aff5' => 1, + 'affheader' => 1, + 'affinetransformmat' => 1, + 'afinfo' => 1, + 'afinfo2' => 1, + 'afinfo2version' => 1, + 'afinfo3' => 1, + 'afinfocus' => 1, + 'afinfosize' => 1, + 'afm' => 1, + 'afpointinfo' => 1, + 'afpointsinfocus1d' => 1, + 'afsp' => 1, + 'afstatus15' => 1, + 'afstatus19' => 1, + 'afstatus79' => 1, + 'aftune' => 1, + 'aibuildnumber' => 1, + 'aicolormodel' => 1, + 'aicolorusage' => 1, + 'aicreatorversion' => 1, + 'aifileformat' => 1, + 'aimetadata' => 1, + 'ainumlayers' => 1, + 'aipdfprivatedata' => 1, + 'aiprivatedata' => 1, + 'airfieldbarometricpressure' => 1, + 'airfieldelevation' => 1, + 'airspeed' => 1, + 'airtemperature' => 1, + 'airulerunits' => 1, + 'aitargetresolution' => 1, + 'album2' => 1, + 'albumartistsortorder' => 1, + 'albumidalbumartist' => 1, + 'albumname' => 1, + 'albumsortorder' => 1, + 'albumtitlesortorder' => 1, + 'aliaslayermetadata' => 1, + 'alignment' => 1, + 'allcolorflatfield1' => 1, + 'allcolorflatfield2' => 1, + 'allcolorflatfield3' => 1, + 'alph' => 1, + 'alpha' => 1, + 'alphabitdepth' => 1, + 'alphabytecount' => 1, + 'alphachannelsnames' => 1, + 'alphacompression' => 1, + 'alphadatadiscard' => 1, + 'alphafilter' => 1, + 'alphafiltering' => 1, + 'alphaidentifiers' => 1, + 'alphainterlace' => 1, + 'alphamask' => 1, + 'alphaoffset' => 1, + 'alphapreprocessing' => 1, + 'alphasample' => 1, + 'alternatealtitude' => 1, + 'alternateduotonecolors' => 1, + 'alternateellipsoidheight' => 1, + 'alternateellipsoidheightextended' => 1, + 'alternateheading' => 1, + 'alternatelatitude' => 1, + 'alternatelongitude' => 1, + 'alternatename' => 1, + 'alternatesourceurl' => 1, + 'alternatespotcolors' => 1, + 'altitudefromtakeoff' => 1, + 'ambienceinfo' => 1, + 'ambisonicchannelmap' => 1, + 'ambisonicchannelordering' => 1, + 'ambisonicchannels' => 1, + 'ambisonicnormalization' => 1, + 'ambisonicorder' => 1, + 'ambisonictype' => 1, + 'amendlocalset' => 1, + 'angleaxis' => 1, + 'angleofattack' => 1, + 'angularvelocity' => 1, + 'anim' => 1, + 'animation' => 1, + 'animationcontrol' => 1, + 'animationframes' => 1, + 'animationiterations' => 1, + 'animationloopcount' => 1, + 'animationplays' => 1, + 'anmf' => 1, + 'anniversary' => 1, + 'annotation' => 1, + 'annotations' => 1, + 'annotationusagerights' => 1, + 'announce' => 1, + 'announcelist1' => 1, + 'anoiselines' => 1, + 'anta' => 1, + 'aperture' => 1, + 'aperturedisplayed' => 1, + 'apeversion' => 1, + 'app14flags0' => 1, + 'app14flags1' => 1, + 'app1_profile' => 1, + 'apple-fi' => 1, + 'appledataoffsets' => 1, + 'applemaildatereceived' => 1, + 'applemaildatesent' => 1, + 'applemailflagged' => 1, + 'applemailisremoteattachment' => 1, + 'applemailmessageid' => 1, + 'applemailpriority' => 1, + 'applemailread' => 1, + 'applemailrepliedto' => 1, + 'application' => 1, + 'application_riff' => 1, + 'applicationdata' => 1, + 'applicationmissingmsg' => 1, + 'applicationunknown' => 1, + 'applicationversion' => 1, + 'applysettingstoliveview' => 1, + 'appname' => 1, + 'archivallocation' => 1, + 'archivedfilename' => 1, + 'arcoreaccel' => 1, + 'arcorecustom' => 1, + 'arcoregyro' => 1, + 'arcorevideo' => 1, + 'ardronefile' => 1, + 'ardronetelemetry' => 1, + 'artist2' => 1, + 'artistlen' => 1, + 'artists' => 1, + 'artisturl' => 1, + 'ascender' => 1, + 'ascent' => 1, + 'asfleakybucketpairs' => 1, + 'asfpacketcount' => 1, + 'asfsecurityobjectssize' => 1, + 'asin' => 1, + 'asin2' => 1, + 'aspectinfo' => 1, + 'aspectratiox' => 1, + 'aspectratioy' => 1, + 'assistantdirector' => 1, + 'assistantsname' => 1, + 'assistantsphone' => 1, + 'associateddatalist' => 1, + 'associatedimagefile' => 1, + 'association' => 1, + 'assumeddisplaysize' => 1, + 'assumeddistanceview' => 1, + 'ast' => 1, + 'atmospherictemperature' => 1, + 'atmospherictransalpha1' => 1, + 'atmospherictransalpha2' => 1, + 'atmospherictransbeta1' => 1, + 'atmospherictransbeta2' => 1, + 'atmospherictransx' => 1, + 'atob0' => 1, + 'atob1' => 1, + 'atob2' => 1, + 'atob3' => 1, + 'atom0' => 1, + 'atsccontent' => 1, + 'attachments' => 1, + 'attitude' => 1, + 'attitudetarget' => 1, + 'attr' => 1, + 'attributes' => 1, + 'audible_cvrx' => 1, + 'audible_meta' => 1, + 'audible_meta2' => 1, + 'audible_tags' => 1, + 'audible_tseg' => 1, + 'audiences' => 1, + 'audioattributes' => 1, + 'audioavailablebitraterange' => 1, + 'audioavailablenumberchannels' => 1, + 'audioavgbitrate' => 1, + 'audiobitratecontrolmode' => 1, + 'audiobytes' => 1, + 'audiochannellayout' => 1, + 'audiochanneltypes' => 1, + 'audiocodec' => 1, + 'audiocodecdescription' => 1, + 'audiocodecid' => 1, + 'audiocodecinfo' => 1, + 'audiocodecname' => 1, + 'audiocodecprimemethod' => 1, + 'audiocomponentversion' => 1, + 'audiocurrenttargetbitrate' => 1, + 'audiodelay' => 1, + 'audiodoessamplerateconversion' => 1, + 'audioencoding' => 1, + 'audioencodingparamsversion' => 1, + 'audioextendfrequencies' => 1, + 'audiofilesize' => 1, + 'audiofileurl' => 1, + 'audioformat' => 1, + 'audioframesize' => 1, + 'audiohasvariablepacketbytesizes' => 1, + 'audioheader' => 1, + 'audioinfo' => 1, + 'audioinputbuffersize' => 1, + 'audioisinitialized' => 1, + 'audiolayer' => 1, + 'audiomaxbitrate' => 1, + 'audiomaximumpacketbytesize' => 1, + 'audiominimumdelaymode' => 1, + 'audiominimumnumberinputpackets' => 1, + 'audiominimumnumberoutputpackets' => 1, + 'audiomode' => 1, + 'audiooutputprecedence' => 1, + 'audiopacketframesize' => 1, + 'audioprofile' => 1, + 'audioprofileversion' => 1, + 'audioqualitysetting' => 1, + 'audiorequirespacketdescription' => 1, + 'audiosamplecount' => 1, + 'audiosampledesc' => 1, + 'audiosamplesize' => 1, + 'audiosetting' => 1, + 'audiosize' => 1, + 'audiosourceurl' => 1, + 'audiostream' => 1, + 'audiostreamtype' => 1, + 'audiotrackid' => 1, + 'audiousedinputbuffersize' => 1, + 'audiouserecommendedsamplerate' => 1, + 'audiovbrquality' => 1, + 'audiovendorid' => 1, + 'audiozeroframespadded' => 1, + 'authenticationtime' => 1, + 'authenticationtype' => 1, + 'authoridentifier' => 1, + 'authoringsoftwarerelease' => 1, + 'authorlen' => 1, + 'authorname' => 1, + 'authorship' => 1, + 'authorsortorder' => 1, + 'autoareaafstartingpoint' => 1, + 'autocaptureoffset' => 1, + 'autoisomin' => 1, + 'autolowlightduration' => 1, + 'automationanimation' => 1, + 'automationflags' => 1, + 'autorotation' => 1, + 'autosavefilepath' => 1, + 'autosaveformat' => 1, + 'autosummary' => 1, + 'aux' => 1, + 'auxiliaryimageref' => 1, + 'auxiliaryimagetype' => 1, + 'av1configuration' => 1, + 'av1configurationversion' => 1, + 'avcconfiguration' => 1, + 'averagebitrate' => 1, + 'averageframerate' => 1, + 'averagelevel' => 1, + 'avgbitrate' => 1, + 'avgbytespersec' => 1, + 'avgpacketsize' => 1, + 'avgpdusize' => 1, + 'avgwidth' => 1, + 'avi1' => 1, + 'avif' => 1, + 'aviheader' => 1, + 'awbdebuginfo' => 1, + 'awbinfo' => 1, + 'axisdistancex' => 1, + 'axisdistancey' => 1, + 'axisdistancez' => 1, + 'axml' => 1, + 'b1' => 1, + 'b2' => 1, + 'background' => 1, + 'backgroundcolor' => 1, + 'backgroundcolorindicator' => 1, + 'backgroundcolorvalue' => 1, + 'backgroundimageid' => 1, + 'backgroundtiling' => 1, + 'backlight' => 1, + 'backserial' => 1, + 'backuptime' => 1, + 'badfaxlines' => 1, + 'bahpver' => 1, + 'balpver' => 1, + 'band' => 1, + 'bandwidthsharing' => 1, + 'bannerimage' => 1, + 'bannerimagedata' => 1, + 'bannerimagetype' => 1, + 'bannerimageurl' => 1, + 'barcode' => 1, + 'barcodes' => 1, + 'barometerinfo' => 1, + 'barometerinfoversion' => 1, + 'baselinelength' => 1, + 'basename' => 1, + 'baseviewpointnum' => 1, + 'basicfileinfo' => 1, + 'basisobject' => 1, + 'battery' => 1, + 'batterycapacity' => 1, + 'batterycurrent' => 1, + 'batteryinfo' => 1, + 'batterystatus' => 1, + 'batterytime' => 1, + 'batteryvoltage3' => 1, + 'batteryvoltage4' => 1, + 'bavpver' => 1, + 'bccaddresses' => 1, + 'bccnames' => 1, + 'beats' => 1, + 'beats-per-minute' => 1, + 'beautycolorlevel' => 1, + 'beautyretouchlevel' => 1, + 'belowcolor' => 1, + 'beta' => 1, + 'bibligraphicfilename' => 1, + 'bikebroaccel' => 1, + 'bikebrogps' => 1, + 'billinginformation' => 1, + 'binarydata' => 1, + 'binarydatatype' => 1, + 'binaryfilter' => 1, + 'binaryxml' => 1, + 'binning' => 1, + 'birthday' => 1, + 'bitdepthchroma' => 1, + 'bitdepthluma' => 1, + 'bitrate' => 1, + 'bitrateinfo' => 1, + 'bitratemutualexclusion' => 1, + 'bitsperextendedrunlength' => 1, + 'bitsperpixel' => 1, + 'bitsperrunlength' => 1, + 'blackadcountsperdacvolt' => 1, + 'blackclamp' => 1, + 'blackclampoffset' => 1, + 'blackcolsleft' => 1, + 'blackcolsright' => 1, + 'blackdacchannel' => 1, + 'blackdacsettlingmsec' => 1, + 'blackmaskbottomborder' => 1, + 'blackmaskleftborder' => 1, + 'blackmaskrightborder' => 1, + 'blackmasktopborder' => 1, + 'blackrowsbottom' => 1, + 'blackrowstop' => 1, + 'blacktarget' => 1, + 'blocklocation' => 1, + 'blocksize' => 1, + 'blocksizemax' => 1, + 'blocksizemin' => 1, + 'blockspercolumn' => 1, + 'blocksperframe' => 1, + 'blocksperrow' => 1, + 'blueadjust' => 1, + 'blueendpoint' => 1, + 'bluegain' => 1, + 'bluegrassscale1' => 1, + 'bluegrassscale2' => 1, + 'bluegrasstable' => 1, + 'bluemask' => 1, + 'bluematrixcolumn' => 1, + 'blueprimary' => 1, + 'bluesample' => 1, + 'bluetrc' => 1, + 'bluex' => 1, + 'bluey' => 1, + 'blurstrength' => 1, + 'bmpversion' => 1, + 'boardtemperature' => 1, + 'body' => 1, + 'bokehshape' => 1, + 'bookname' => 1, + 'booktitle' => 1, + 'booktype' => 1, + 'bookversion' => 1, + 'bootidentifier' => 1, + 'bootrecord' => 1, + 'bootsystem' => 1, + 'bordercolor' => 1, + 'borderid' => 1, + 'borderinformation' => 1, + 'borderlocation' => 1, + 'bordername' => 1, + 'bordersversion' => 1, + 'bordertype' => 1, + 'bottomdarkrow1' => 1, + 'bottomdarkrow2' => 1, + 'bottommag' => 1, + 'boundaryorigin' => 1, + 'boundingbox' => 1, + 'bpm' => 1, + 'bps' => 1, + 'bracketingburstoptions' => 1, + 'bracketingcounter' => 1, + 'bracketingmode' => 1, + 'bracketingoffset' => 1, + 'bracketingstep' => 1, + 'bracketshot' => 1, + 'brain' => 1, + 'brandingimageid' => 1, + 'brandingname' => 1, + 'brdfatob0' => 1, + 'brdfatob1' => 1, + 'brdfatob2' => 1, + 'brdfatob3' => 1, + 'brdfbtoa0' => 1, + 'brdfbtoa1' => 1, + 'brdfbtoa2' => 1, + 'brdfbtoa3' => 1, + 'brdfbtod0' => 1, + 'brdfbtod1' => 1, + 'brdfbtod2' => 1, + 'brdfbtod3' => 1, + 'brdfcolorimetricparam0' => 1, + 'brdfcolorimetricparam1' => 1, + 'brdfcolorimetricparam2' => 1, + 'brdfcolorimetricparam3' => 1, + 'brdfdtob0' => 1, + 'brdfdtob1' => 1, + 'brdfdtob2' => 1, + 'brdfdtob3' => 1, + 'brdfmtob0' => 1, + 'brdfmtob1' => 1, + 'brdfmtob2' => 1, + 'brdfmtob3' => 1, + 'brdfmtos0' => 1, + 'brdfmtos1' => 1, + 'brdfmtos2' => 1, + 'brdfmtos3' => 1, + 'brdfspectralparam0' => 1, + 'brdfspectralparam1' => 1, + 'brdfspectralparam2' => 1, + 'brdfspectralparam3' => 1, + 'breakchar' => 1, + 'brightdefectintegrationms' => 1, + 'brightdefectisocode' => 1, + 'brightdefectthreshold' => 1, + 'brightnessdata' => 1, + 'brightrowstop' => 1, + 'broadcast' => 1, + 'broadcastdate' => 1, + 'broadcastextension' => 1, + 'brotliexif' => 1, + 'brotlijumb' => 1, + 'brotlixmp' => 1, + 'btoa0' => 1, + 'btoa1' => 1, + 'btoa2' => 1, + 'btoa3' => 1, + 'btod0' => 1, + 'btod1' => 1, + 'btod2' => 1, + 'btod3' => 1, + 'bufferaverage' => 1, + 'buffersize' => 1, + 'builddate' => 1, + 'buildversion' => 1, + 'businessaddress' => 1, + 'businesscity' => 1, + 'businesscountry-region' => 1, + 'businessfax' => 1, + 'businesshomepage' => 1, + 'businessphone' => 1, + 'businesspobox' => 1, + 'businesspostalcode' => 1, + 'businessstateorprovince' => 1, + 'businessstreet' => 1, + 'buynow' => 1, + 'buytickets' => 1, + 'bw_halftoninginfo' => 1, + 'bw_transferfunc' => 1, + 'bwf_umid' => 1, + 'bwfversion' => 1, + 'bytelength' => 1, + 'byteorder' => 1, + 'byteordermark' => 1, + 'bytes' => 1, + 'bytesperline' => 1, + 'bytesperminute' => 1, + 'c2pasalthash' => 1, + 'cachecontrol' => 1, + 'cachedimageheight' => 1, + 'cachedimagewidth' => 1, + 'calculatedscalemax' => 1, + 'calculatedscalemin' => 1, + 'calibrationdatetime' => 1, + 'calibrationlog' => 1, + 'callbacknumber' => 1, + 'callletters' => 1, + 'callsign' => 1, + 'camcore' => 1, + 'camera1' => 1, + 'camera2' => 1, + 'cameraarrangementinterval' => 1, + 'camerabacktype' => 1, + 'camerabody' => 1, + 'camerabyteorder' => 1, + 'cameradatetime' => 1, + 'cameraifd' => 1, + 'camerainfo' => 1, + 'camerainfo2' => 1, + 'camerainfo3' => 1, + 'camerainfoa100' => 1, + 'camerainfounknown' => 1, + 'cameramaker' => 1, + 'cameramanufacturer' => 1, + 'cameraname' => 1, + 'cameraobjbacktype' => 1, + 'cameraobject' => 1, + 'cameraobjname' => 1, + 'cameraobjtype' => 1, + 'cameraobjversion' => 1, + 'cameraoperator' => 1, + 'camerapan' => 1, + 'camerapartnumber' => 1, + 'cameraprofileversion' => 1, + 'camerascalemax' => 1, + 'camerascalemin' => 1, + 'camerasettings' => 1, + 'camerasettings2' => 1, + 'camerasettings3' => 1, + 'camerasettingsa100' => 1, + 'camerasettingsifd' => 1, + 'camerasettingsunknown' => 1, + 'camerasetup' => 1, + 'camerasoftware' => 1, + 'cameraspecification' => 1, + 'cameratemperaturemaxclip' => 1, + 'cameratemperaturemaxsaturated' => 1, + 'cameratemperaturemaxwarn' => 1, + 'cameratemperatureminclip' => 1, + 'cameratemperatureminsaturated' => 1, + 'cameratemperatureminwarn' => 1, + 'cameratilt' => 1, + 'camm0' => 1, + 'camm1' => 1, + 'camm2' => 1, + 'camm3' => 1, + 'camm4' => 1, + 'camm5' => 1, + 'camm6' => 1, + 'camm7' => 1, + 'can_skip_backward' => 1, + 'can_skip_forward' => 1, + 'canon' => 1, + 'canon_afinfo2_0x000d' => 1, + 'canon_afinfo_0x000b' => 1, + 'canonafinfo' => 1, + 'canonafinfo2' => 1, + 'canoncamerainfo1000d' => 1, + 'canoncamerainfo1100d' => 1, + 'canoncamerainfo1200d' => 1, + 'canoncamerainfo1d' => 1, + 'canoncamerainfo1dmkii' => 1, + 'canoncamerainfo1dmkiii' => 1, + 'canoncamerainfo1dmkiin' => 1, + 'canoncamerainfo1dmkiv' => 1, + 'canoncamerainfo1dx' => 1, + 'canoncamerainfo40d' => 1, + 'canoncamerainfo450d' => 1, + 'canoncamerainfo500d' => 1, + 'canoncamerainfo50d' => 1, + 'canoncamerainfo550d' => 1, + 'canoncamerainfo5d' => 1, + 'canoncamerainfo5dmkii' => 1, + 'canoncamerainfo5dmkiii' => 1, + 'canoncamerainfo600d' => 1, + 'canoncamerainfo60d' => 1, + 'canoncamerainfo650d' => 1, + 'canoncamerainfo6d' => 1, + 'canoncamerainfo700d' => 1, + 'canoncamerainfo70d' => 1, + 'canoncamerainfo750d' => 1, + 'canoncamerainfo760d' => 1, + 'canoncamerainfo7d' => 1, + 'canoncamerainfo80d' => 1, + 'canoncamerainfopowershot' => 1, + 'canoncamerainfopowershot2' => 1, + 'canoncamerainfounknown' => 1, + 'canoncamerainfounknown16' => 1, + 'canoncamerainfounknown32' => 1, + 'canoncamerasettings' => 1, + 'canoncctp' => 1, + 'canoncnop' => 1, + 'canoncnth' => 1, + 'canoncolorinfo1' => 1, + 'canoncolorinfo2' => 1, + 'canonfileinfo' => 1, + 'canonflags' => 1, + 'canonfocallength' => 1, + 'canonimageheight' => 1, + 'canonimagewidth' => 1, + 'canonpanorama' => 1, + 'canonrawmakemodel' => 1, + 'canonshotinfo' => 1, + 'canonskip' => 1, + 'canseekontime' => 1, + 'canseektoend' => 1, + 'capheight' => 1, + 'captiontext' => 1, + 'captprofbacktype' => 1, + 'captprofname' => 1, + 'captproftype' => 1, + 'captprofversion' => 1, + 'captureconditionspar' => 1, + 'capturedevicefid' => 1, + 'captureheightresetblacksegnormal' => 1, + 'captureheightresetblacksegtest' => 1, + 'captureheighttest' => 1, + 'captureobjbacktype' => 1, + 'captureobjname' => 1, + 'captureobjtype' => 1, + 'captureobjversion' => 1, + 'captureprofile' => 1, + 'captureresolution' => 1, + 'captureserial' => 1, + 'capturesetup' => 1, + 'capturewidthresetblacksegnormal' => 1, + 'capturewidthresetblacksegtest' => 1, + 'capturexresolution' => 1, + 'capturexresolutionunit' => 1, + 'captureyresolution' => 1, + 'captureyresolutionunit' => 1, + 'car' => 1, + 'carphone' => 1, + 'casio' => 1, + 'casiodata' => 1, + 'casiojunk' => 1, + 'casioquality' => 1, + 'casioqvmi' => 1, + 'cast' => 1, + 'catalognumber' => 1, + 'caveats' => 1, + 'cbordata' => 1, + 'cc' => 1, + 'ccaddresses' => 1, + 'ccdrect' => 1, + 'ccdvalidrect' => 1, + 'ccdvideorect' => 1, + 'ccnames' => 1, + 'cddb1info' => 1, + 'cddbdiscplaytime' => 1, + 'cddbdisctracks' => 1, + 'cddbtracknumber' => 1, + 'cdetype' => 1, + 'cdi1' => 1, + 'cdtrackenabled' => 1, + 'cell' => 1, + 'cellphone' => 1, + 'centerdarkrect' => 1, + 'cfagreenthreshold1' => 1, + 'cfagreenthreshold2' => 1, + 'cfalayout' => 1, + 'cfaplanecolor' => 1, + 'channel0lagkernel' => 1, + 'channel1coordinates' => 1, + 'channel1flags' => 1, + 'channel1label' => 1, + 'channel1lagkernel' => 1, + 'channel2coordinates' => 1, + 'channel2flags' => 1, + 'channel2label' => 1, + 'channel2lagkernel' => 1, + 'channel3coordinates' => 1, + 'channel3flags' => 1, + 'channel3label' => 1, + 'channel3lagkernel' => 1, + 'channel4coordinates' => 1, + 'channel4flags' => 1, + 'channel4label' => 1, + 'channel5coordinates' => 1, + 'channel5flags' => 1, + 'channel5label' => 1, + 'channel6coordinates' => 1, + 'channel6flags' => 1, + 'channel6label' => 1, + 'channel7coordinates' => 1, + 'channel7flags' => 1, + 'channel7label' => 1, + 'channel8coordinates' => 1, + 'channel8flags' => 1, + 'channel8label' => 1, + 'channelcoarsegainadjust' => 1, + 'channelmode' => 1, + 'channelnumber' => 1, + 'channelusage' => 1, + 'chapter' => 1, + 'chaptercount' => 1, + 'chapterlisttrackid' => 1, + 'chaptername' => 1, + 'chapternum' => 1, + 'chapternumber' => 1, + 'character' => 1, + 'characterescapesequence' => 1, + 'characters' => 1, + 'characterset' => 1, + 'characterswithspaces' => 1, + 'charcountwithspaces' => 1, + 'chartarget' => 1, + 'chartcontourinterval' => 1, + 'chartcorrdate' => 1, + 'chartcountryorigin' => 1, + 'chartdepthunits' => 1, + 'chartformat' => 1, + 'chartmagvar' => 1, + 'chartmagvarannchange' => 1, + 'chartmagvaryear' => 1, + 'chartrasteredition' => 1, + 'chartsoundingdatum' => 1, + 'chartsource' => 1, + 'chartsourcedate' => 1, + 'chartsourceedition' => 1, + 'chartwgsnsshift' => 1, + 'checkedby' => 1, + 'checksum' => 1, + 'children' => 1, + 'chmversion' => 1, + 'choregrapher' => 1, + 'chromaformat' => 1, + 'chromasampleposition' => 1, + 'chromaticaberrationparams' => 1, + 'chromaticadaptation' => 1, + 'chromaticities' => 1, + 'chromaticity' => 1, + 'chromaticitychannel1' => 1, + 'chromaticitychannel2' => 1, + 'chromaticitychannel3' => 1, + 'chromaticitychannel4' => 1, + 'chromaticitychannels' => 1, + 'chromaticitycolorant' => 1, + 'chunkcount' => 1, + 'chunkoffset' => 1, + 'chunkoffset64' => 1, + 'churchillnav' => 1, + 'cicodepoints' => 1, + 'ciff' => 1, + 'cinematographer' => 1, + 'cip3datafile' => 1, + 'cip3sheet' => 1, + 'cip3side' => 1, + 'circleofconfusion' => 1, + 'class' => 1, + 'classificationandmarkingsystem' => 1, + 'classificationcomments' => 1, + 'classificationreason' => 1, + 'classifiedby' => 1, + 'classifyingcountry' => 1, + 'classifyingcountrycodemethod' => 1, + 'classifyingcountrycodingmethoddate' => 1, + 'cleanaperture' => 1, + 'cleanaperturedimensions' => 1, + 'cleanapertureheight' => 1, + 'cleanapertureoffsetx' => 1, + 'cleanapertureoffsety' => 1, + 'cleanaperturewidth' => 1, + 'cleanfaxdata' => 1, + 'clientid' => 1, + 'clipboundary' => 1, + 'clipobjects' => 1, + 'clippath' => 1, + 'clippingboundary' => 1, + 'clippinglimit' => 1, + 'clippingpathname' => 1, + 'cloneid' => 1, + 'cloneobject' => 1, + 'clonetype' => 1, + 'closedcaptioning' => 1, + 'cmddialsaperturesetting' => 1, + 'cmddialschangemainsub' => 1, + 'cmddialschangemainsubexposure' => 1, + 'cmddialsmenuandplayback' => 1, + 'cmddialsreverserotexposurecomp' => 1, + 'cmmflags' => 1, + 'cmp1' => 1, + 'cmykequivalent' => 1, + 'coarsedata' => 1, + 'coarsemapimage' => 1, + 'coarsemapimageheight' => 1, + 'coarsemapimagetype' => 1, + 'coarsemapimagewidth' => 1, + 'codecflavorid' => 1, + 'codeclist' => 1, + 'codedframesize' => 1, + 'codepage' => 1, + 'codesize' => 1, + 'codestreamheader' => 1, + 'codestreamregistration' => 1, + 'codinghistory' => 1, + 'codingmethods' => 1, + 'codirectors' => 1, + 'colorant1coordinates' => 1, + 'colorant1name' => 1, + 'colorant2coordinates' => 1, + 'colorant2name' => 1, + 'colorant3coordinates' => 1, + 'colorant3name' => 1, + 'colorantcount' => 1, + 'colorantinfoout' => 1, + 'colorantorder' => 1, + 'colorantorderout' => 1, + 'coloranttable' => 1, + 'coloranttableout' => 1, + 'coloraverages' => 1, + 'colorbalance0100' => 1, + 'colorbalance0102' => 1, + 'colorbalance0103' => 1, + 'colorbalance02' => 1, + 'colorbalance0205' => 1, + 'colorbalance0209' => 1, + 'colorbalance0211' => 1, + 'colorbalance0213' => 1, + 'colorbalance0215' => 1, + 'colorbalancea' => 1, + 'colorbalanceunknown' => 1, + 'colorbalanceunknown02' => 1, + 'colorbalanceunknown04' => 1, + 'colorbitdepth' => 1, + 'colorboostdata' => 1, + 'colorbw' => 1, + 'colorcalib' => 1, + 'colorcalib2' => 1, + 'colorcalibrationmatrix' => 1, + 'colorcasts' => 1, + 'colorcharacterization' => 1, + 'colorcoefs' => 1, + 'colorcoefs2' => 1, + 'colorcomponents' => 1, + 'colordata1' => 1, + 'colordata10' => 1, + 'colordata11' => 1, + 'colordata2' => 1, + 'colordata3' => 1, + 'colordata4' => 1, + 'colordata5' => 1, + 'colordata6' => 1, + 'colordata7' => 1, + 'colordata8' => 1, + 'colordata9' => 1, + 'colordataunknown' => 1, + 'colorencodingparams' => 1, + 'colorgroup' => 1, + 'colorhalftoninginfo' => 1, + 'colorimetricintentimagestate' => 1, + 'colorimetricspecification' => 1, + 'colorimetry' => 1, + 'colorinfo' => 1, + 'colormap' => 1, + 'colorobjbacktype' => 1, + 'colorobjname' => 1, + 'colorobjtype' => 1, + 'colorobjversion' => 1, + 'colorpalette' => 1, + 'colorpopstrength' => 1, + 'colorprimaries' => 1, + 'colorprofiles' => 1, + 'colorresolutiondepth' => 1, + 'colorresponseunit' => 1, + 'colors' => 1, + 'colorsamplersresource' => 1, + 'colorsamplersresource2' => 1, + 'colorsetup' => 1, + 'colorspacedata' => 1, + 'colorspacename' => 1, + 'colorspecification' => 1, + 'colortable' => 1, + 'colortemperatures' => 1, + 'colortransferfuncs' => 1, + 'colortransform' => 1, + 'colortwistmatrix' => 1, + 'colortype' => 1, + 'columncount' => 1, + 'columngainfactors' => 1, + 'columnresetoffsets' => 1, + 'command' => 1, + 'commandlinearguments' => 1, + 'commentby' => 1, + 'commentlen' => 1, + 'comments' => 1, + 'commenttime' => 1, + 'commercialurl' => 1, + 'commissioned' => 1, + 'common' => 1, + 'commonnetworkrellink' => 1, + 'commonpathsuffix' => 1, + 'compactsamplesizes' => 1, + 'company' => 1, + 'companymainphone' => 1, + 'companyname' => 1, + 'compatibility' => 1, + 'compatiblebrands' => 1, + 'compatiblefontname' => 1, + 'complete' => 1, + 'compobj' => 1, + 'compobjusertype' => 1, + 'compobjusertypelen' => 1, + 'componentbitdepth' => 1, + 'componentdefinition' => 1, + 'componentmapping' => 1, + 'componenttable' => 1, + 'composernationality' => 1, + 'composers' => 1, + 'composersortorder' => 1, + 'compositeimaginglocalset' => 1, + 'compositinglayerheader' => 1, + 'compositionlocation' => 1, + 'compositionmode' => 1, + 'compositionoptions' => 1, + 'compositiontimetosample' => 1, + 'compositiontodecodetimelinemapping' => 1, + 'compressed' => 1, + 'compressedannotation' => 1, + 'compresseddatalength' => 1, + 'compresseddataoffset' => 1, + 'compressedmovie' => 1, + 'compressedsize' => 1, + 'compressedtext' => 1, + 'compressionclass' => 1, + 'compressionlevel' => 1, + 'compressiontype' => 1, + 'compressorid' => 1, + 'compressorname' => 1, + 'computedatmospherictrans' => 1, + 'computer' => 1, + 'concreteflag' => 1, + 'condition' => 1, + 'conditionalfec' => 1, + 'conductors' => 1, + 'confirmedobjectsize' => 1, + 'connectionspaceilluminant' => 1, + 'consecutivebadfaxlines' => 1, + 'consoledata' => 1, + 'consolefedata' => 1, + 'constantframerate' => 1, + 'constraintindicatorflags' => 1, + 'cont' => 1, + 'contactnames' => 1, + 'containerversion' => 1, + 'contake' => 1, + 'contentbranding' => 1, + 'contentdescribes' => 1, + 'contentdescription' => 1, + 'contentdescriptionnotes' => 1, + 'contentdisposition' => 1, + 'contentdistributorduration' => 1, + 'contentdistributortype' => 1, + 'contentencryption' => 1, + 'contentgroupdescription' => 1, + 'contentlanguage' => 1, + 'contentlightlevel' => 1, + 'contentprotected' => 1, + 'contentprotectedpercent' => 1, + 'contentrating' => 1, + 'contents' => 1, + 'contentscripttype' => 1, + 'contentstatus' => 1, + 'contentstyletype' => 1, + 'contiguouscodestream' => 1, + 'contrastadjustment' => 1, + 'contrastinfo' => 1, + 'controller' => 1, + 'convergenceangle' => 1, + 'convergencebaseimage' => 1, + 'convergencedistance' => 1, + 'conversationid' => 1, + 'coproducer' => 1, + 'copyrightfilename' => 1, + 'copyrightlen' => 1, + 'copyrighturl' => 1, + 'coringtablebest' => 1, + 'coringtablebetter' => 1, + 'coringtablegood' => 1, + 'cornerlatitude1' => 1, + 'cornerlatitude2' => 1, + 'cornerlatitude3' => 1, + 'cornerlatitude4' => 1, + 'cornerlongitude1' => 1, + 'cornerlongitude2' => 1, + 'cornerlongitude3' => 1, + 'cornerlongitude4' => 1, + 'correctionmethod' => 1, + 'costumedesigner' => 1, + 'count' => 1, + 'countinfo' => 1, + 'country-region' => 1, + 'coverartmimetype' => 1, + 'coverarttype' => 1, + 'coyotesense' => 1, + 'coyotestatus' => 1, + 'cpuarchitecture' => 1, + 'cpubyteorder' => 1, + 'cpucount' => 1, + 'cpusubtype' => 1, + 'cputype' => 1, + 'cpuversions' => 1, + 'cr2cfapattern' => 1, + 'crc' => 1, + 'crcdevelparams' => 1, + 'crd' => 1, + 'crdinfo' => 1, + 'createdby' => 1, + 'creatingapplication' => 1, + 'creatingtransform' => 1, + 'creationpathvector' => 1, + 'creatorapp' => 1, + 'creatorapplication' => 1, + 'creatorappversion' => 1, + 'creatoratom' => 1, + 'creatorbuildnumber' => 1, + 'creatorbuildnumber2' => 1, + 'creatorinfo' => 1, + 'creatormajorversion' => 1, + 'creatorminorversion' => 1, + 'creatorsoftware' => 1, + 'creatorversion' => 1, + 'crgbtoerimm0spline' => 1, + 'crgbtoerimm1spline' => 1, + 'crgbtoerimm2spline' => 1, + 'crgbtoerimm3spline' => 1, + 'crgbtoerimm4spline' => 1, + 'crgbtoerimm5spline' => 1, + 'crgbtoerimm6spline' => 1, + 'crgbtoerimm7spline' => 1, + 'crgbtoerimm8spline' => 1, + 'crgbtoerimm9spline' => 1, + 'cropdata' => 1, + 'cropinfo' => 1, + 'cropped' => 1, + 'cropxcommonoffset' => 1, + 'cropxoffset' => 1, + 'cropxoffset2' => 1, + 'cropxsize' => 1, + 'cropxviewpointnumber' => 1, + 'cropxviewpointnumber2' => 1, + 'cropycommonoffset' => 1, + 'cropyoffset' => 1, + 'cropyoffset2' => 1, + 'cropysize' => 1, + 'cropyviewpointnumber' => 1, + 'cropyviewpointnumber2' => 1, + 'cross-reference' => 1, + 'crossbarenable' => 1, + 'crossref' => 1, + 'crs' => 1, + 'crwparam' => 1, + 'cs0' => 1, + 'cs1' => 1, + 'ctmd' => 1, + 'cubemapproj' => 1, + 'cuepoint' => 1, + 'cuepointlabel' => 1, + 'cuepointnote' => 1, + 'cuepoints' => 1, + 'cuesheet' => 1, + 'currentbitrate' => 1, + 'currentiptcdigest' => 1, + 'currenttime' => 1, + 'currentuser' => 1, + 'cursorsize' => 1, + 'customfunctions10d' => 1, + 'customfunctions1d' => 1, + 'customfunctions2' => 1, + 'customfunctions20d' => 1, + 'customfunctions30d' => 1, + 'customfunctions350d' => 1, + 'customfunctions400d' => 1, + 'customfunctions5d' => 1, + 'customfunctionsd30' => 1, + 'customfunctionsd60' => 1, + 'customfunctionsunknown' => 1, + 'customsettingsd3' => 1, + 'customsettingsd300' => 1, + 'customsettingsd300s' => 1, + 'customsettingsd3s' => 1, + 'customsettingsd3x' => 1, + 'customsettingsd4' => 1, + 'customsettingsd40' => 1, + 'customsettingsd4s' => 1, + 'customsettingsd5' => 1, + 'customsettingsd500' => 1, + 'customsettingsd5000' => 1, + 'customsettingsd5100' => 1, + 'customsettingsd5200' => 1, + 'customsettingsd610' => 1, + 'customsettingsd700' => 1, + 'customsettingsd7000' => 1, + 'customsettingsd80' => 1, + 'customsettingsd800' => 1, + 'customsettingsd90' => 1, + 'customsettingsoffset' => 1, + 'customsettingsz8' => 1, + 'customsettingsz9' => 1, + 'customsettingsz9v4' => 1, + 'customtostandardpcc' => 1, + 'cxf' => 1, + 'd-lightinghqdata' => 1, + 'd-lightinghsdata' => 1, + 'd2' => 1, + 'daccountspervolt' => 1, + 'dacgainscoarse' => 1, + 'dacgainscoarseadjpreif41' => 1, + 'dacgainsfine' => 1, + 'dacvoltages' => 1, + 'dacvoltagesflush' => 1, + 'darkcorrectiontype' => 1, + 'darkmapscale' => 1, + 'darkrefoffsetnormal' => 1, + 'darkrefoffsettest' => 1, + 'darkthreshold' => 1, + 'darwindata' => 1, + 'data' => 1, + 'data1' => 1, + 'data2' => 1, + 'databasename' => 1, + 'datacreatedate' => 1, + 'datadump' => 1, + 'datadump2' => 1, + 'datainfo' => 1, + 'datakey' => 1, + 'datalen' => 1, + 'datalength' => 1, + 'datalocation' => 1, + 'datamodifydate' => 1, + 'dataobject' => 1, + 'dataobjectid' => 1, + 'dataobjectstatus' => 1, + 'dataobjecttitle' => 1, + 'dataoffset' => 1, + 'dataoffsets' => 1, + 'datapackets' => 1, + 'datapreparer' => 1, + 'datarate' => 1, + 'dataref' => 1, + 'datareference' => 1, + 'datasign' => 1, + 'datasize' => 1, + 'datasize64' => 1, + 'datatype' => 1, + 'datawindow' => 1, + 'dateaccessed' => 1, + 'datearchived' => 1, + 'datecompleted' => 1, + 'dateencoded' => 1, + 'dateimported' => 1, + 'datelastsaved' => 1, + 'datemodified' => 1, + 'datepicturetaken' => 1, + 'datepurchased' => 1, + 'datereceived' => 1, + 'datereleased' => 1, + 'datetagged' => 1, + 'datetime1' => 1, + 'datetime2' => 1, + 'datetimecreated' => 1, + 'datetimeend' => 1, + 'datetimegenerated' => 1, + 'datevisited' => 1, + 'datewritten' => 1, + 'daymat0' => 1, + 'daymat1' => 1, + 'daymat2' => 1, + 'daymat3' => 1, + 'daymat4' => 1, + 'daymat5' => 1, + 'daymat6' => 1, + 'daymat7' => 1, + 'daymat8' => 1, + 'daymat9' => 1, + 'db' => 1, + 'dblcoldcacthres1' => 1, + 'dblcoldcacthres2' => 1, + 'dc' => 1, + 'dcem' => 1, + 'dcme' => 1, + 'dcs3xxprocessinginfo' => 1, + 'dcs3xxprocessinginfoifd' => 1, + 'dctencodeversion' => 1, + 'dealeridnumber' => 1, + 'decimationmethod' => 1, + 'decimationprefilterwidth' => 1, + 'declassificationdate' => 1, + 'decode' => 1, + 'decodeconfig' => 1, + 'decodertable' => 1, + 'decodertablenumber' => 1, + 'decoderversion' => 1, + 'defaultaudiostream' => 1, + 'defaultchar' => 1, + 'defaultcmyk' => 1, + 'defaultdisplayheight' => 1, + 'defaultdisplaywidth' => 1, + 'defaultimagecolor' => 1, + 'defaultrgb' => 1, + 'defaultstyle' => 1, + 'defectconcealartcorrectthres' => 1, + 'defectconcealthrestable' => 1, + 'defectcount' => 1, + 'defectlist' => 1, + 'defectlistpacked' => 1, + 'defineobject' => 1, + 'definequantizationtable' => 1, + 'delay' => 1, + 'delaycols' => 1, + 'delaytime' => 1, + 'delimiter' => 1, + 'delta12to8spline' => 1, + 'delta8to12spline' => 1, + 'deltapngheader' => 1, + 'deltatype' => 1, + 'deltaxy' => 1, + 'densityaltitude' => 1, + 'densityaltitudeextended' => 1, + 'department' => 1, + 'dependentimage1entrynumber' => 1, + 'dependentimage2entrynumber' => 1, + 'depth' => 1, + 'depthhwheight' => 1, + 'depthhwwidth' => 1, + 'depthmapdata' => 1, + 'depthmapdata2' => 1, + 'depthmapheight' => 1, + 'depthmapname' => 1, + 'depthmaptiff' => 1, + 'depthmapwidth' => 1, + 'depthswheight' => 1, + 'depthswwidth' => 1, + 'derivedimageref' => 1, + 'descender' => 1, + 'designer' => 1, + 'designerurl' => 1, + 'desiredreproductions' => 1, + 'destinationid' => 1, + 'detectedfacebounds' => 1, + 'detectedfaceid' => 1, + 'detectedfacerollangle' => 1, + 'detectedfaceyawangle' => 1, + 'detector' => 1, + 'detectorboard' => 1, + 'deviceattributes' => 1, + 'devicecontainer' => 1, + 'deviceid' => 1, + 'devicemanufacturer' => 1, + 'devicemfgdesc' => 1, + 'devicemodel' => 1, + 'devicemodeldesc' => 1, + 'devicename' => 1, + 'deviceorientation' => 1, + 'devicesettings' => 1, + 'dex' => 1, + 'dialect' => 1, + 'dicom' => 1, + 'dictionary' => 1, + 'dictionaryshortname' => 1, + 'differentialpressure' => 1, + 'difftilegains24t852822' => 1, + 'difftilegains602832' => 1, + 'digikam' => 1, + 'digitalcreationdatetime' => 1, + 'digitaleffectsname' => 1, + 'digitaleffectstype' => 1, + 'digitaleffectsversion' => 1, + 'digitalexposurebiases' => 1, + 'digitalexposuregains' => 1, + 'digitalimagebroker' => 1, + 'digitalsignature' => 1, + 'dimensions' => 1, + 'directionalatob0' => 1, + 'directionalatob1' => 1, + 'directionalatob2' => 1, + 'directionalatob3' => 1, + 'directionalbtoa0' => 1, + 'directionalbtoa1' => 1, + 'directionalbtoa2' => 1, + 'directionalbtoa3' => 1, + 'directionalbtod0' => 1, + 'directionalbtod1' => 1, + 'directionalbtod2' => 1, + 'directionalbtod3' => 1, + 'directionaldtob0' => 1, + 'directionaldtob1' => 1, + 'directionaldtob2' => 1, + 'directionaldtob3' => 1, + 'directorofphotography' => 1, + 'directors' => 1, + 'disableflagspresent' => 1, + 'discardobjects' => 1, + 'displayartist' => 1, + 'displayinfo' => 1, + 'displayresolution' => 1, + 'displaysize' => 1, + 'displayunits' => 1, + 'displaywindow' => 1, + 'displayxresolution' => 1, + 'displayxresolutionunit' => 1, + 'displayyresolution' => 1, + 'displayyresolutionunit' => 1, + 'dispose' => 1, + 'disposition' => 1, + 'distance' => 1, + 'distancefromhome' => 1, + 'distortinfo' => 1, + 'distortioninfo' => 1, + 'distortionversion' => 1, + 'distributedby' => 1, + 'distribution' => 1, + 'dittokey' => 1, + 'division' => 1, + 'dji-dbg' => 1, + 'dji_dtat' => 1, + 'djvuversion' => 1, + 'dlnaserverudn' => 1, + 'dlnasourceuri' => 1, + 'dlodata' => 1, + 'dlodatalength' => 1, + 'dloinfo' => 1, + 'dmeroderadius' => 1, + 'dmfillrejthresh' => 1, + 'dmnoisescale' => 1, + 'dmnumpatches' => 1, + 'dmpixelthresholdfactor' => 1, + 'dmsmoothrejthresh' => 1, + 'dmtrimfraction' => 1, + 'dmwindowthresholdfactor' => 1, + 'docclass' => 1, + 'docflags' => 1, + 'docmdp' => 1, + 'docrights' => 1, + 'docsecurity' => 1, + 'doctype' => 1, + 'documentinfo' => 1, + 'documentnumber' => 1, + 'documentusagerights' => 1, + 'docversion' => 1, + 'dof' => 1, + 'donotshow' => 1, + 'dop' => 1, + 'dotrange' => 1, + 'dotsperinch' => 1, + 'downsampleby2hor' => 1, + 'downsampleby2hor3mpdcr' => 1, + 'downsampleby2ver' => 1, + 'downsampleby2ver3mpdcr' => 1, + 'downsampleby3hor' => 1, + 'downsampleby3ver' => 1, + 'downsampleby4hor' => 1, + 'downsampleby4ver' => 1, + 'downsampleby6hor' => 1, + 'downsampleby6ver' => 1, + 'dpxfilesize' => 1, + 'dr4' => 1, + 'dr4header' => 1, + 'driveserialnumber' => 1, + 'drivesettings' => 1, + 'drivetype' => 1, + 'drm' => 1, + 'drm_contentid' => 1, + 'drm_drmheader' => 1, + 'drm_drmheader_contentdistributor' => 1, + 'drm_drmheader_contentid' => 1, + 'drm_drmheader_individualizedversion' => 1, + 'drm_drmheader_keyid' => 1, + 'drm_drmheader_licenseacqurl' => 1, + 'drm_drmheader_subscriptioncontentid' => 1, + 'drm_e-bookbaseid' => 1, + 'drm_individualizedversion' => 1, + 'drm_keyid' => 1, + 'drm_lasignaturecert' => 1, + 'drm_lasignaturelicsrvcert' => 1, + 'drm_lasignatureprivkey' => 1, + 'drm_lasignaturerootcert' => 1, + 'drm_licenseacqurl' => 1, + 'drm_v1licenseacqurl' => 1, + 'drmcommerceid' => 1, + 'drmindividualizedversion' => 1, + 'drmkeyid' => 1, + 'drmserverid' => 1, + 'drone-dji' => 1, + 'dronepitch' => 1, + 'dronequaternion' => 1, + 'droneroll' => 1, + 'droneyaw' => 1, + 'dropbykeyword' => 1, + 'dropchunks' => 1, + 'dtcpiphost' => 1, + 'dtcpipport' => 1, + 'dtob0' => 1, + 'dtob1' => 1, + 'dtob2' => 1, + 'dtob3' => 1, + 'dtvcontent' => 1, + 'dualcameraimage' => 1, + 'dualcameraimagename' => 1, + 'dualshotextra' => 1, + 'ducky' => 1, + 'duedate' => 1, + 'dummycolsleft' => 1, + 'dummycolsright' => 1, + 'duotonehalftoninginfo' => 1, + 'duotoneimageinfo' => 1, + 'duotonetransferfuncs' => 1, + 'dustdeleteapplied' => 1, + 'dustinfo' => 1, + 'dvdid' => 1, + 'dwc' => 1, + 'dynamicranger128' => 1, + 'e-mail2' => 1, + 'e-mail3' => 1, + 'e-mailaddress' => 1, + 'e-maildisplayname' => 1, + 'e-maillist' => 1, + 'edgespline' => 1, + 'edgesplinehigh' => 1, + 'edgesplinelow' => 1, + 'edgesplinemed' => 1, + 'edit4data' => 1, + 'editdata' => 1, + 'editedby' => 1, + 'editor' => 1, + 'edittagarray' => 1, + 'effect' => 1, + 'effectivebw' => 1, + 'effectstrength' => 1, + 'effectsvisible' => 1, + 'effecttype' => 1, + 'eighteenpercentpoint' => 1, + 'eighthlanguage' => 1, + 'electricalextendermagnification' => 1, + 'electronicimagestabilization' => 1, + 'electronicvr' => 1, + 'elementarystreamtrack' => 1, + 'embeddedaudiofile' => 1, + 'embeddedaudiofilename' => 1, + 'embeddedfile' => 1, + 'embeddedfilename' => 1, + 'embeddedfileusagerights' => 1, + 'embeddedimage' => 1, + 'embeddedimage2' => 1, + 'embeddedimageclass' => 1, + 'embeddedimagecolorspace' => 1, + 'embeddedimagefilter' => 1, + 'embeddedimageheight' => 1, + 'embeddedimagename' => 1, + 'embeddedimagerectangle' => 1, + 'embeddedimagetype' => 1, + 'embeddedimagewidth' => 1, + 'embeddedjpg' => 1, + 'embeddedpng' => 1, + 'embeddedvideo' => 1, + 'embeddedvideofile' => 1, + 'embeddedvideotype' => 1, + 'emphasis' => 1, + 'empty' => 1, + 'encodedpixelsdimensions' => 1, + 'encodedusing' => 1, + 'encoderoptions' => 1, + 'encodersettings' => 1, + 'encodervendor' => 1, + 'encoderversion' => 1, + 'encodetime' => 1, + 'encoding' => 1, + 'encodingparams' => 1, + 'encodingprocess' => 1, + 'encodingscheme' => 1, + 'encodingsettings' => 1, + 'encrypt' => 1, + 'encryption' => 1, + 'enddate' => 1, + 'endianness' => 1, + 'endpoints' => 1, + 'endtime' => 1, + 'endtimecode' => 1, + 'entrypoint' => 1, + 'entrytype' => 1, + 'environmentmap' => 1, + 'envvardata' => 1, + 'eppim' => 1, + 'eprint' => 1, + 'epsoptions' => 1, + 'equipment' => 1, + 'equipmentifd' => 1, + 'equirectangularproj' => 1, + 'erimmnonlinearityspline' => 1, + 'erimmtocrgb0spline' => 1, + 'erimmtocrgb1spline' => 1, + 'erimmtocrgb2spline' => 1, + 'erimmtocrgb3spline' => 1, + 'erimmtocrgb4spline' => 1, + 'erimmtocrgb5spline' => 1, + 'erimmtocrgb6spline' => 1, + 'erimmtocrgb7spline' => 1, + 'erimmtocrgb8spline' => 1, + 'erimmtocrgb9spline' => 1, + 'erimmtonescale0spline' => 1, + 'erimmtonescale1spline' => 1, + 'erimmtonescale2spline' => 1, + 'erimmtonescale3spline' => 1, + 'erimmtonescale4spline' => 1, + 'erimmtonescale5spline' => 1, + 'erimmtonescale6spline' => 1, + 'erimmtonescale7spline' => 1, + 'erimmtonescale8spline' => 1, + 'erimmtonescale9spline' => 1, + 'error' => 1, + 'errorcorrection' => 1, + 'errorcorrectiontype' => 1, + 'escapestatus' => 1, + 'escchar' => 1, + 'estimatedatmospherictrans' => 1, + 'et' => 1, + 'evalstate' => 1, + 'eventlogcapture' => 1, + 'eventlogprocess' => 1, + 'events' => 1, + 'eventstarttime' => 1, + 'evstepinfo' => 1, + 'exif_profile' => 1, + 'exifdata' => 1, + 'exifex' => 1, + 'exififd' => 1, + 'exifinfo' => 1, + 'exifinfo2' => 1, + 'exifinfo7' => 1, + 'exifinfo8' => 1, + 'exifinfo9' => 1, + 'exifinformation' => 1, + 'exifoffset' => 1, + 'exiftoolversion' => 1, + 'expandfilm' => 1, + 'expandfilterlens' => 1, + 'expandflashlamp' => 1, + 'expandlens' => 1, + 'expandscanner' => 1, + 'expandsoftware' => 1, + 'expirationspan' => 1, + 'expires' => 1, + 'exportimage' => 1, + 'exposurebias' => 1, + 'exposureheadroomfactor' => 1, + 'exposureinfo' => 1, + 'exposurereferencegain' => 1, + 'exposurereferenceoffset' => 1, + 'exposuretimes' => 1, + 'exposureunknown' => 1, + 'expressionmedia' => 1, + 'exrversion' => 1, + 'extcache' => 1, + 'extendedalbumname' => 1, + 'extendedartistname' => 1, + 'extendedaviheader' => 1, + 'extendedcontentdescr' => 1, + 'extendedcontentencryption' => 1, + 'extendedinfo' => 1, + 'extendedstreamprops' => 1, + 'extendedtracktitle' => 1, + 'extendedxmp' => 1, + 'extensionclassid' => 1, + 'extensioncreatedate' => 1, + 'extensiondescription' => 1, + 'extensionmodifydate' => 1, + 'extensionname' => 1, + 'extensionpersistence' => 1, + 'extensions' => 1, + 'extensis' => 1, + 'externalleading' => 1, + 'externaltriggercount' => 1, + 'extraflags' => 1, + 'extrainfo' => 1, + 'extrainfo2' => 1, + 'extrainfo3' => 1, + 'extrasamples' => 1, + 'extraticklocations' => 1, + 'f-stop' => 1, + 'face1birthday' => 1, + 'face1category' => 1, + 'face1name' => 1, + 'face2birthday' => 1, + 'face2category' => 1, + 'face2name' => 1, + 'face3birthday' => 1, + 'face3category' => 1, + 'face3name' => 1, + 'face4birthday' => 1, + 'face4category' => 1, + 'face4name' => 1, + 'face5birthday' => 1, + 'face5category' => 1, + 'face5name' => 1, + 'face6birthday' => 1, + 'face6category' => 1, + 'face6name' => 1, + 'face7birthday' => 1, + 'face7category' => 1, + 'face7name' => 1, + 'face8birthday' => 1, + 'face8category' => 1, + 'face8name' => 1, + 'facedetect1' => 1, + 'facedetect2' => 1, + 'facedetect3' => 1, + 'facedetected' => 1, + 'facedetinfo' => 1, + 'faceinfo' => 1, + 'faceinfo1' => 1, + 'faceinfo2' => 1, + 'faceinfoa' => 1, + 'faceinfolength' => 1, + 'faceinfooffset' => 1, + 'faceitem' => 1, + 'facenumbers' => 1, + 'facepos' => 1, + 'facerec' => 1, + 'facerecinfo' => 1, + 'facesize' => 1, + 'factorywhitegainsdaylight' => 1, + 'factorywhiteoffsetsdaylight' => 1, + 'farklewhitethreshold' => 1, + 'fastseek' => 1, + 'fax' => 1, + 'faxprofile' => 1, + 'faxrecvparams' => 1, + 'faxrecvtime' => 1, + 'faxsubaddress' => 1, + 'fdsc' => 1, + 'fedexedr' => 1, + 'fffheader' => 1, + 'fieldmdp' => 1, + 'fieldpermissions' => 1, + 'fifthlanguage' => 1, + 'file1duration' => 1, + 'file1length' => 1, + 'file1md5sum' => 1, + 'file1media' => 1, + 'file1path' => 1, + 'file1pathutf-8' => 1, + 'fileaccessdate' => 1, + 'fileas' => 1, + 'fileattributes' => 1, + 'fileblockcount' => 1, + 'fileblocksize' => 1, + 'filecount' => 1, + 'filedescription' => 1, + 'filedeviceid' => 1, + 'filedevicenumber' => 1, + 'fileflags' => 1, + 'fileflagsmask' => 1, + 'filefunctionflags' => 1, + 'fileglobalprofile' => 1, + 'fileguid' => 1, + 'filehardlinks' => 1, + 'fileid' => 1, + 'fileinfo' => 1, + 'fileinfolen' => 1, + 'fileinfolen2' => 1, + 'fileinfoproperties' => 1, + 'fileinfoversion' => 1, + 'fileinodechangedate' => 1, + 'fileinodenumber' => 1, + 'filelength' => 1, + 'fileos' => 1, + 'fileowner' => 1, + 'filepath' => 1, + 'fileprofileversion' => 1, + 'fileproperties' => 1, + 'files' => 1, + 'filesequence' => 1, + 'filesize' => 1, + 'filesizebytes' => 1, + 'filesubtype' => 1, + 'filetype' => 1, + 'filetypeextension' => 1, + 'fileurl' => 1, + 'fileversionnumber' => 1, + 'fillattributes' => 1, + 'fillmethod' => 1, + 'filmbrand' => 1, + 'filmcategory' => 1, + 'filmframenumber' => 1, + 'filmgencode' => 1, + 'filmproductcode' => 1, + 'filmrollnumber' => 1, + 'filmsize' => 1, + 'filter' => 1, + 'filterinfo' => 1, + 'filtering' => 1, + 'filtermodel' => 1, + 'filterparametersbinary' => 1, + 'filterparameterscustomcustomdata' => 1, + 'filterparametersexportexportdata' => 1, + 'filterpartnumber' => 1, + 'filterserialnumber' => 1, + 'finalflushsequence' => 1, + 'finalframeblocks' => 1, + 'finetune' => 1, + 'finishedfileprocessingrequest' => 1, + 'finishipaversion' => 1, + 'finishipfversion' => 1, + 'firmwareinfo' => 1, + 'firmwareversion51' => 1, + 'firstchar' => 1, + 'firstflushsequence' => 1, + 'firstlanguage' => 1, + 'firstlines' => 1, + 'firstlinetransfertiming' => 1, + 'firstname' => 1, + 'firstobject' => 1, + 'firstobjectid' => 1, + 'fisheyefilter' => 1, + 'flac' => 1, + 'flagcolor' => 1, + 'flags' => 1, + 'flagstatus' => 1, + 'flashattributes' => 1, + 'flashexpcomp' => 1, + 'flashinfo' => 1, + 'flashinfo0100' => 1, + 'flashinfo0102' => 1, + 'flashinfo0103' => 1, + 'flashinfo0106' => 1, + 'flashinfo0107' => 1, + 'flashinfo0300' => 1, + 'flashinfounknown' => 1, + 'flashinfoversion' => 1, + 'flashmat0' => 1, + 'flashmat1' => 1, + 'flashmat2' => 1, + 'flashmat3' => 1, + 'flashmat4' => 1, + 'flashmat5' => 1, + 'flashmat6' => 1, + 'flashmat7' => 1, + 'flashmat8' => 1, + 'flashmat9' => 1, + 'flashpixstreamfieldoffset' => 1, + 'flashpixstreampathname' => 1, + 'flashpower' => 1, + 'flashsyncmode' => 1, + 'flashttlmode' => 1, + 'flashused' => 1, + 'flashversion' => 1, + 'flavor' => 1, + 'flightdegree' => 1, + 'flightspeed' => 1, + 'flipstatus' => 1, + 'flir' => 1, + 'flir_gps' => 1, + 'flir_moreinfo' => 1, + 'flir_params' => 1, + 'flir_parts' => 1, + 'flir_serial' => 1, + 'flir_unknown' => 1, + 'flir_unknownuuid' => 1, + 'flirdata' => 1, + 'fluormat0' => 1, + 'fluormat1' => 1, + 'fluormat2' => 1, + 'fluormat3' => 1, + 'fluormat4' => 1, + 'fluormat5' => 1, + 'fluormat6' => 1, + 'fluormat7' => 1, + 'fluormat8' => 1, + 'fluormat9' => 1, + 'flushtiming' => 1, + 'flyingstate' => 1, + 'focalinfo' => 1, + 'focallength35efl' => 1, + 'focallength35mm' => 1, + 'focalplanecolorimetryestimates' => 1, + 'focalrange' => 1, + 'focusdistance2' => 1, + 'focusedgemap' => 1, + 'focusframesize' => 1, + 'focusinfo' => 1, + 'focusinfoifd' => 1, + 'focuspointbrightness' => 1, + 'focuspointselectionspeed' => 1, + 'focuspos' => 1, + 'focussettings' => 1, + 'folder' => 1, + 'folderpath' => 1, + 'follow-meanimation' => 1, + 'follow-memode' => 1, + 'font' => 1, + 'fontsize' => 1, + 'fontsubfamily' => 1, + 'fontsubfamilyid' => 1, + 'fonttable' => 1, + 'fontweight' => 1, + 'footerposition' => 1, + 'footnotes' => 1, + 'form' => 1, + 'formattag' => 1, + 'formatter' => 1, + 'formatversion' => 1, + 'formatversiontime' => 1, + 'formextrausagerights' => 1, + 'formfields' => 1, + 'formusagerights' => 1, + 'forwardto' => 1, + 'fotostation' => 1, + 'fourcc' => 1, + 'fourcc1' => 1, + 'fourcc2' => 1, + 'fourcc2len' => 1, + 'fourcc3' => 1, + 'fourcc3len' => 1, + 'fourthlanguage' => 1, + 'fov' => 1, + 'fovcot' => 1, + 'fpfversion' => 1, + 'fps' => 1, + 'fpv' => 1, + 'fpxr' => 1, + 'fractalparameters' => 1, + 'fragmentlist' => 1, + 'fragmenttable' => 1, + 'frame' => 1, + 'framebaseview' => 1, + 'framecenterelevation' => 1, + 'framecenterheightaboveellipsoid' => 1, + 'framecenterlatitude' => 1, + 'framecenterlongitude' => 1, + 'frameexposuretime' => 1, + 'frameid' => 1, + 'frameinfo' => 1, + 'framepriority' => 1, + 'framesize' => 1, + 'framesizemax' => 1, + 'framesizemin' => 1, + 'framespersecond' => 1, + 'frameview' => 1, + 'framinggriddisplay' => 1, + 'free' => 1, + 'free-busystatus' => 1, + 'freebytecounts' => 1, + 'freeoffsets' => 1, + 'frequency' => 1, + 'fromaddresses' => 1, + 'fromnames' => 1, + 'fstype' => 1, + 'fuelremaining' => 1, + 'fujifilm' => 1, + 'fujifilmffmv' => 1, + 'fujifilmmvtg' => 1, + 'fujifilmtags' => 1, + 'fujiifd' => 1, + 'fujilayout' => 1, + 'fullangleofattack' => 1, + 'fullname' => 1, + 'fullpitchangle' => 1, + 'fullrollangle' => 1, + 'fullscreen' => 1, + 'fullsideslipangle' => 1, + 'fusion360fly' => 1, + 'fusionmode' => 1, + 'fusionypr' => 1, + 'gain' => 1, + 'gaindeaddata' => 1, + 'gaindeadmapimage' => 1, + 'gaindeadmapimageheight' => 1, + 'gaindeadmapimagetype' => 1, + 'gaindeadmapimagewidth' => 1, + 'gainmapimage' => 1, + 'gammablue' => 1, + 'gammagreen' => 1, + 'gammainfo' => 1, + 'gammared' => 1, + 'gammatable' => 1, + 'gamut' => 1, + 'gamutboundarydescription0' => 1, + 'gamutboundarydescription1' => 1, + 'gamutboundarydescription2' => 1, + 'gamutboundarydescription3' => 1, + 'gapless' => 1, + 'garmingps' => 1, + 'gaudio' => 1, + 'gaussianweights' => 1, + 'gcamera' => 1, + 'gcreations' => 1, + 'gdepth' => 1, + 'geminfo' => 1, + 'genbalance' => 1, + 'gender' => 1, + 'generallevelidc' => 1, + 'generalprofileidc' => 1, + 'generalprofilespace' => 1, + 'generaltierflag' => 1, + 'generator' => 1, + 'generatorversion' => 1, + 'genericflagdata01' => 1, + 'genflags' => 1, + 'gengraphicsmode' => 1, + 'genmediaheader' => 1, + 'genmediainfo' => 1, + 'genmediaversion' => 1, + 'genopcolor' => 1, + 'genprofilecompatibilityflags' => 1, + 'genr' => 1, + 'geo' => 1, + 'geogangularunits' => 1, + 'geogangularunitsize' => 1, + 'geogazimuthunits' => 1, + 'geogcitation' => 1, + 'geogellipsoid' => 1, + 'geoggeodeticdatum' => 1, + 'geoginvflattening' => 1, + 'geoglinearunits' => 1, + 'geoglinearunitsize' => 1, + 'geogprimemeridian' => 1, + 'geogprimemeridianlong' => 1, + 'geographictype' => 1, + 'geogsemimajoraxis' => 1, + 'geogsemiminoraxis' => 1, + 'geogtowgs84' => 1, + 'geometricdistortionparams' => 1, + 'georegistrationlocalset' => 1, + 'geotiffversion' => 1, + 'getty' => 1, + 'gfocus' => 1, + 'gidcver' => 1, + 'gidpver' => 1, + 'gifapplicationextension' => 1, + 'gifgraphiccontrolextension' => 1, + 'gifplaintextextension' => 1, + 'gifversion' => 1, + 'gimage' => 1, + 'gimbaldegree' => 1, + 'gimbalpitch' => 1, + 'gimbalroll' => 1, + 'gimbalyaw' => 1, + 'gipc_cpld' => 1, + 'gipcver' => 1, + 'givenname' => 1, + 'gixiver' => 1, + 'globalinfo' => 1, + 'globalparametersifd' => 1, + 'globalpixelsize' => 1, + 'googlebot' => 1, + 'gopro' => 1, + 'goprogpmf' => 1, + 'gpano' => 1, + 'gpmd_fmas' => 1, + 'gpmd_gopro' => 1, + 'gpmd_kingslim' => 1, + 'gpmd_rove' => 1, + 'gps' => 1, + 'gps360fly' => 1, + 'gpsaltituderaw' => 1, + 'gpsdatalist' => 1, + 'gpsdatalist2' => 1, + 'gpsdatetimeraw' => 1, + 'gpsdestaltitude' => 1, + 'gpsframingaltitude' => 1, + 'gpsframinglatitude' => 1, + 'gpsframinglongitude' => 1, + 'gpshorizontalaccuracy' => 1, + 'gpsinfo' => 1, + 'gpslatituderaw' => 1, + 'gpslog' => 1, + 'gpslongituderaw' => 1, + 'gpsmode' => 1, + 'gpspos' => 1, + 'gpsraw' => 1, + 'gpsspeed3d' => 1, + 'gpsspeedaccuracy' => 1, + 'gpsspeedraw' => 1, + 'gpsspeedx' => 1, + 'gpsspeedy' => 1, + 'gpsspeedz' => 1, + 'gpstargetaltitude' => 1, + 'gpstargetlatitude' => 1, + 'gpstargetlongitude' => 1, + 'gpstrackraw' => 1, + 'gpsvalid' => 1, + 'gpsvelocitydown' => 1, + 'gpsvelocityeast' => 1, + 'gpsvelocitynorth' => 1, + 'gpsvelocityup' => 1, + 'gpsverticalaccuracy' => 1, + 'grainybwfilter' => 1, + 'graphicconverter' => 1, + 'graphicsmode' => 1, + 'graphicstechnologystandardoutput' => 1, + 'grayresponsecurve' => 1, + 'graytrc' => 1, + 'greenadjust' => 1, + 'greenendpoint' => 1, + 'greengain' => 1, + 'greenmask' => 1, + 'greenmatrixcolumn' => 1, + 'greenprimary' => 1, + 'greensample' => 1, + 'greentrc' => 1, + 'greenx' => 1, + 'greeny' => 1, + 'gridguidesinfo' => 1, + 'groundrange' => 1, + 'groupareac1' => 1, + 'groupcaption' => 1, + 'groupmutualexclusion' => 1, + 'groups' => 1, + 'gsensor' => 1, + 'gspherical' => 1, + 'gtcitation' => 1, + 'gtmodeltype' => 1, + 'gtrastertype' => 1, + 'guano' => 1, + 'gyro360fly' => 1, + 'gyromode' => 1, + 'gyroscope' => 1, + 'gyroscopeunknown' => 1, + 'gyroypr' => 1, + 'handler' => 1, + 'handlerclass' => 1, + 'handlerdescription' => 1, + 'handlertype' => 1, + 'handlervendorid' => 1, + 'hardpostpadding' => 1, + 'hardprepadding' => 1, + 'hasarbitrarydatastream' => 1, + 'hasattachedimages' => 1, + 'hasattachments' => 1, + 'hasaudio' => 1, + 'hascolormap' => 1, + 'hascuepoints' => 1, + 'hasfiletransferstream' => 1, + 'hasflag' => 1, + 'hasicc' => 1, + 'hasimage' => 1, + 'haskeyframes' => 1, + 'hasmetadata' => 1, + 'hasrealmergeddata' => 1, + 'hasscript' => 1, + 'hasselbladexif' => 1, + 'hasselbladrawimage' => 1, + 'hasvideo' => 1, + 'hasxfa' => 1, + 'hcusage' => 1, + 'hdcontent' => 1, + 'hdmibitdepth' => 1, + 'hdmiexternalrecorder' => 1, + 'hdmioutputhdr' => 1, + 'hdmioutputrange' => 1, + 'hdrgm' => 1, + 'hdrinfo' => 1, + 'hdrinfo2' => 1, + 'hdrinfoversion' => 1, + 'hdrl' => 1, + 'hdrtoninginfo' => 1, + 'header' => 1, + 'header4' => 1, + 'headerext' => 1, + 'headerextension' => 1, + 'headersize' => 1, + 'headerversion' => 1, + 'headingpairs' => 1, + 'heightresolution' => 1, + 'hevcconfiguration' => 1, + 'hevcconfigurationversion' => 1, + 'hiddenslides' => 1, + 'highbitdepth' => 1, + 'highisomode' => 1, + 'highlightdata' => 1, + 'highlightendpoints' => 1, + 'highnote' => 1, + 'highvelocity' => 1, + 'hintformat' => 1, + 'hintheader' => 1, + 'hintinfo' => 1, + 'hintsampledesc' => 1, + 'hinttrackinfo' => 1, + 'hinttrackversion' => 1, + 'histogram' => 1, + 'historybuffersize' => 1, + 'hmaphandling' => 1, + 'hmcolrejthresh' => 1, + 'hmcolthresh' => 1, + 'hmpixthresh' => 1, + 'hmwsize' => 1, + 'hobbies' => 1, + 'homeaddress' => 1, + 'homecity' => 1, + 'homecountry-region' => 1, + 'homefax' => 1, + 'homephone' => 1, + 'homepobox' => 1, + 'homepostalcode' => 1, + 'homestateorprovince' => 1, + 'homestreet' => 1, + 'horizontalfieldofview' => 1, + 'horizontalresolution' => 1, + 'horizontalscale' => 1, + 'hostsoftwareexportversion' => 1, + 'hotkey' => 1, + 'hotspotx' => 1, + 'hotspoty' => 1, + 'howpublished' => 1, + 'hp_tdhd' => 1, + 'hrnoiselines' => 1, + 'htcbinary' => 1, + 'htcinfo' => 1, + 'htctrack' => 1, + 'http-equiv' => 1, + 'httphostheader' => 1, + 'huawei' => 1, + 'huffmantable' => 1, + 'huffmantablelength' => 1, + 'huffmantablevalue' => 1, + 'hyperfocaldistance' => 1, + 'hyperlapsdebuginfo' => 1, + 'hyperlinkbase' => 1, + 'hyperlinks' => 1, + 'hyperlinkschanged' => 1, + 'iad1' => 1, + 'icc_untagged' => 1, + 'iccbased' => 1, + 'icingdetected' => 1, + 'icondir' => 1, + 'iconenvdata' => 1, + 'iconfilename' => 1, + 'iconindex' => 1, + 'ics' => 1, + 'id' => 1, + 'id3' => 1, + 'id3size' => 1, + 'id3v1' => 1, + 'id3v1_enh' => 1, + 'id3v2_2' => 1, + 'id3v2_3' => 1, + 'id3v2_4' => 1, + 'idc2_ifd' => 1, + 'idc_ifd' => 1, + 'idcpreviewimage' => 1, + 'idependentanddisposablesamples' => 1, + 'idlesequence' => 1, + 'idletiming' => 1, + 'idsbasevalue' => 1, + 'idstring' => 1, + 'ifd0' => 1, + 'ifd0_offset' => 1, + 'ihl_exif' => 1, + 'ihldata' => 1, + 'illuminantdetectordata' => 1, + 'illuminantdetecttable' => 1, + 'illustrator' => 1, + 'im' => 1, + 'imaddresses' => 1, + 'image' => 1, + 'image2description' => 1, + 'image3description' => 1, + 'image4description' => 1, + 'image5description' => 1, + 'image6description' => 1, + 'image7description' => 1, + 'image8description' => 1, + 'imagearrangement' => 1, + 'imageboardid' => 1, + 'imagebounds' => 1, + 'imagebytecount' => 1, + 'imageclass' => 1, + 'imagecolor' => 1, + 'imagecolorindicator' => 1, + 'imagecolorvalue' => 1, + 'imagecompressiontable' => 1, + 'imagecoordinatesystem' => 1, + 'imagedatadiscard' => 1, + 'imagedatahash' => 1, + 'imagedataoffset' => 1, + 'imagedepth' => 1, + 'imageelements' => 1, + 'imageexpansiontable' => 1, + 'imagefields' => 1, + 'imagefilecharacteristics' => 1, + 'imagefilename' => 1, + 'imageformat' => 1, + 'imagefullheight' => 1, + 'imagefullwidth' => 1, + 'imageheader' => 1, + 'imageheightinches' => 1, + 'imagehorizonpixelpack' => 1, + 'imageid' => 1, + 'imageinfo' => 1, + 'imagelayer' => 1, + 'imagelength' => 1, + 'imagelimitexposurebias' => 1, + 'imagemagnificationdescriptor' => 1, + 'imagemedium' => 1, + 'imagemodulationexposurebias' => 1, + 'imageoffset' => 1, + 'imagepixeldepth' => 1, + 'imagepixelformat' => 1, + 'imageprintstatus' => 1, + 'imageprocessingifd' => 1, + 'imageprofile' => 1, + 'imageprops' => 1, + 'imagerboardid' => 1, + 'imagereadydatasets' => 1, + 'imagereadyvariables' => 1, + 'imagereferencepoints' => 1, + 'imageresources' => 1, + 'imagerinitialtimingcode' => 1, + 'imagerlogicprogram' => 1, + 'imagerotation' => 1, + 'imagerotationstatus' => 1, + 'imagertimingdata' => 1, + 'imagesensorgain' => 1, + 'imagesourceek' => 1, + 'imagespatialextent' => 1, + 'imagestatus' => 1, + 'imagetoolbar' => 1, + 'imageuidlist' => 1, + 'imageversion' => 1, + 'imagewidthinches' => 1, + 'imageworkstationmake' => 1, + 'imagingdata' => 1, + 'imdb' => 1, + 'imgprofbacktype' => 1, + 'imgprofname' => 1, + 'imgproftype' => 1, + 'imgprofversion' => 1, + 'immediatedatabytes' => 1, + 'importance' => 1, + 'imprint' => 1, + 'inbandratingattributes' => 1, + 'inbandratinglevel' => 1, + 'inbandratingsystem' => 1, + 'includedfileid' => 1, + 'incomplete' => 1, + 'index' => 1, + 'index01' => 1, + 'index02' => 1, + 'index03' => 1, + 'index04' => 1, + 'index05' => 1, + 'index06' => 1, + 'index07' => 1, + 'index08' => 1, + 'index09' => 1, + 'index10' => 1, + 'index11' => 1, + 'index12' => 1, + 'index13' => 1, + 'index14' => 1, + 'index15' => 1, + 'index16' => 1, + 'indexable' => 1, + 'indexed' => 1, + 'indexedcolortablecount' => 1, + 'indexoffset' => 1, + 'indexparameters' => 1, + 'indicatedairspeed' => 1, + 'indications' => 1, + 'infirayfactory' => 1, + 'infirayisothermal' => 1, + 'infiraymixmode' => 1, + 'infirayopmode' => 1, + 'infiraypicture' => 1, + 'infiraysensor' => 1, + 'infirayversion' => 1, + 'info' => 1, + 'ingrreserved' => 1, + 'initialdelaysamples' => 1, + 'initialdisplayeffect' => 1, + 'initializationvector' => 1, + 'initializeddatasize' => 1, + 'initialobjectdescriptor' => 1, + 'initials' => 1, + 'inknames' => 1, + 'inputdataobjectlist' => 1, + 'inputdevicename' => 1, + 'inputdeviceserialnumber' => 1, + 'inputheight' => 1, + 'inputorientation' => 1, + 'inputuniformity' => 1, + 'inputwidth' => 1, + 'insertmode' => 1, + 'insetnwpixelx' => 1, + 'insetnwpixely' => 1, + 'insta360' => 1, + 'institution' => 1, + 'instructionset' => 1, + 'instruments' => 1, + 'insv' => 1, + 'integratetiming' => 1, + 'intellectualproperty' => 1, + 'intellectualpropertynotes' => 1, + 'intensitystereo' => 1, + 'intergraphflagregisters' => 1, + 'intergraphpacketdata' => 1, + 'interlace' => 1, + 'interleavedfield' => 1, + 'internalidnumber' => 1, + 'internalleading' => 1, + 'internalname' => 1, + 'internalversionnumber' => 1, + 'internationaltext' => 1, + 'internetradiostationname' => 1, + 'internetradiostationowner' => 1, + 'internetradiostationurl' => 1, + 'interopoffset' => 1, + 'interpolationcoefficients' => 1, + 'interpolationcoefficients3mp' => 1, + 'interpolationcoefficients6mp' => 1, + 'interpretedby' => 1, + 'intervaloffset' => 1, + 'inversemonitormatrix' => 1, + 'inverserimnonlinearity' => 1, + 'inversesbalog12transform' => 1, + 'inversesbalogtransform' => 1, + 'invnifnonlinearity' => 1, + 'involvedpeople' => 1, + 'ipmpcontrol' => 1, + 'iptc_profile' => 1, + 'iptcapplication' => 1, + 'iptccore' => 1, + 'iptcdata' => 1, + 'iptcenvelope' => 1, + 'iptcext' => 1, + 'iptcfotostation' => 1, + 'iptcnewsphoto' => 1, + 'iptcobjectdata' => 1, + 'iptcpostobjectdata' => 1, + 'iptcpreobjectdata' => 1, + 'irwindowtemperature' => 1, + 'irwindowtransmission' => 1, + 'is_protected' => 1, + 'is_trusted' => 1, + 'isartbokeh' => 1, + 'isattachment' => 1, + 'isbasefont' => 1, + 'iscompleted' => 1, + 'isdeleted' => 1, + 'isfixedpitch' => 1, + 'isfixedv' => 1, + 'isinfoa100' => 1, + 'isnetworkfeed' => 1, + 'isoautooffset' => 1, + 'isocalibrationgaintable' => 1, + 'isoinfo' => 1, + 'isonline' => 1, + 'isospeeds' => 1, + 'isotherm1color' => 1, + 'isotherm2color' => 1, + 'isprotected' => 1, + 'isrc' => 1, + 'isrcnumber' => 1, + 'isrecurring' => 1, + 'isvbr' => 1, + 'it8header' => 1, + 'italic' => 1, + 'italicangle' => 1, + 'itch' => 1, + 'item' => 1, + 'item0032' => 1, + 'itemdesignatorid' => 1, + 'itemid' => 1, + 'iteminfoentry' => 1, + 'iteminformation' => 1, + 'itemlist' => 1, + 'itemlocation' => 1, + 'itemproperties' => 1, + 'itempropertyassociation' => 1, + 'itempropertycontainer' => 1, + 'itemprotection' => 1, + 'itemreference' => 1, + 'itemtool' => 1, + 'itemvendorid' => 1, + 'iterationcount' => 1, + 'iterationendaction' => 1, + 'iterationmax' => 1, + 'iterationminmax' => 1, + 'iterations' => 1, + 'itunesinfo' => 1, + 'itunesmediatype' => 1, + 'itunmovi' => 1, + 'itunsmpb' => 1, + 'ituntool' => 1, + 'ixml' => 1, + 'jbigoptions' => 1, + 'jfif' => 1, + 'jfifversion' => 1, + 'jfxx' => 1, + 'jngheader' => 1, + 'jobtitle' => 1, + 'journal' => 1, + 'jp2header' => 1, + 'jp2signature' => 1, + 'jpeg-hdr' => 1, + 'jpeg-hdrversion' => 1, + 'jpeg-likedata' => 1, + 'jpeg_quality' => 1, + 'jpegactables' => 1, + 'jpegdctables' => 1, + 'jpegdigest' => 1, + 'jpegexifdata' => 1, + 'jpegimagelength' => 1, + 'jpeginfo' => 1, + 'jpeglosslesspredictors' => 1, + 'jpegpointtransforms' => 1, + 'jpegproc' => 1, + 'jpegprocess' => 1, + 'jpegqtablebest' => 1, + 'jpegqtablebetter' => 1, + 'jpegqtablegood' => 1, + 'jpegqtables' => 1, + 'jpegqualityestimate' => 1, + 'jpegrestartinterval' => 1, + 'jpegtables' => 1, + 'jpgfromraw2' => 1, + 'jpginfooffset' => 1, + 'jplcartoifd' => 1, + 'jps' => 1, + 'jpscomment' => 1, + 'jpsflags' => 1, + 'jpslayout' => 1, + 'jpsseparation' => 1, + 'jpstype' => 1, + 'jsondata' => 1, + 'jsonmetadata' => 1, + 'jumbf' => 1, + 'jumbfbox' => 1, + 'jumbfdescr' => 1, + 'jumdid' => 1, + 'jumdlabel' => 1, + 'jumdsignature' => 1, + 'jumdtoggles' => 1, + 'jumdtype' => 1, + 'jumptoxpep' => 1, + 'junk' => 1, + 'jxlcodestream' => 1, + 'k1' => 1, + 'k2' => 1, + 'k3' => 1, + 'k4' => 1, + 'kbytesize' => 1, + 'kdc_ifd' => 1, + 'keepexposurewithteleconverter' => 1, + 'keepuntil' => 1, + 'kelvinwb' => 1, + 'kenwooddata' => 1, + 'keycode' => 1, + 'keyframepositions' => 1, + 'keyframestimes' => 1, + 'keyid' => 1, + 'keys' => 1, + 'kf' => 1, + 'kf8coveruri' => 1, + 'khufui0thresholds' => 1, + 'khufui1thresholds' => 1, + 'khufui2thresholds' => 1, + 'khufui3thresholds' => 1, + 'khufui4thresholds' => 1, + 'khufui5thresholds' => 1, + 'khufulinearbluemixingcoefficient' => 1, + 'khufulineargreenmixingcoefficient' => 1, + 'khufulinearredmixingcoefficient' => 1, + 'khufusigmagaussianweights' => 1, + 'khufusigmascalingfactors14mp' => 1, + 'khufusigmascalingfactors3mp' => 1, + 'khufusigmascalingfactors6mp' => 1, + 'khufuuspacec2mixingcoefficient' => 1, + 'kids' => 1, + 'kilocalories' => 1, + 'kinds' => 1, + 'kj' => 1, + 'kk' => 1, + 'klut' => 1, + 'klut12tolin12' => 1, + 'knownfolderdata' => 1, + 'kodak' => 1, + 'kodak_frea' => 1, + 'kodakbordersifd' => 1, + 'kodakdcmd' => 1, + 'kodakeffectsifd' => 1, + 'kodakfree' => 1, + 'kodakifd' => 1, + 'kodakmake' => 1, + 'kodaktags' => 1, + 'konicaminolta' => 1, + 'konicaminoltatags' => 1, + 'label0' => 1, + 'label1' => 1, + 'label2' => 1, + 'label3' => 1, + 'labelcode' => 1, + 'labeledtext' => 1, + 'lamebitrate' => 1, + 'lameheader' => 1, + 'lamelowpassfilter' => 1, + 'lamemethod' => 1, + 'lamequality' => 1, + 'lamestereomode' => 1, + 'lamevbrquality' => 1, + 'languagecode' => 1, + 'languagelist' => 1, + 'largestpacketduration' => 1, + 'largestpacketsize' => 1, + 'laserprfcode' => 1, + 'lastauthor' => 1, + 'lastbackupdate' => 1, + 'lastchar' => 1, + 'lastkeyframetime' => 1, + 'lastmodifiedby' => 1, + 'lastmodifier' => 1, + 'lastname' => 1, + 'lastobject' => 1, + 'lastobjectid' => 1, + 'lastprinted' => 1, + 'lastsavedby' => 1, + 'lasttimestamp' => 1, + 'lastupdate' => 1, + 'lastupdatetime' => 1, + 'lawrating' => 1, + 'layerblendmodes' => 1, + 'layercolors' => 1, + 'layercomps' => 1, + 'layercount' => 1, + 'layergroupsenabledid' => 1, + 'layerids' => 1, + 'layermodifydates' => 1, + 'layernames' => 1, + 'layeropacities' => 1, + 'layerrectangles' => 1, + 'layers' => 1, + 'layersections' => 1, + 'layerselectionids' => 1, + 'layersgroupinfo' => 1, + 'layerunicodenames' => 1, + 'layervisible' => 1, + 'layout' => 1, + 'layoutflags' => 1, + 'lccn' => 1, + 'lcdedgemapslope' => 1, + 'lcdedgemapx1' => 1, + 'lcdedgemapx2' => 1, + 'lcdedgemapx3' => 1, + 'lcdedgemapx4' => 1, + 'lcdedgespline' => 1, + 'lcdgammatable' => 1, + 'lcdgammatablechickfix' => 1, + 'lcdgammatablemarvin' => 1, + 'lcdhistlut0' => 1, + 'lcdhistlut1' => 1, + 'lcdhistlut2' => 1, + 'lcdhistlut3' => 1, + 'lcdhistlut4' => 1, + 'lcdhistlut5' => 1, + 'lcdhistlut6' => 1, + 'lcdhistlut7' => 1, + 'lcdhistlut8' => 1, + 'lcdhistlut9' => 1, + 'lcdlinearclipvalue' => 1, + 'lcdsharpeningf1' => 1, + 'lcdsharpeningf2' => 1, + 'lcdsharpeningf3' => 1, + 'lcdsharpeningf4' => 1, + 'lcdstepyvalues' => 1, + 'lcdstepyvalueschickfix' => 1, + 'lcdstepyvaluesmarvin' => 1, + 'leadperformer' => 1, + 'leafautoactive' => 1, + 'leafautobasename' => 1, + 'leafdata' => 1, + 'leafhotfolder' => 1, + 'leafopenprochdr' => 1, + 'leafoutputfiletype' => 1, + 'leafsaveselection' => 1, + 'leafsubifd' => 1, + 'leftdarkcol1' => 1, + 'leftdarkcol2' => 1, + 'leftdarkrect' => 1, + 'leftmag' => 1, + 'leftmargin' => 1, + 'legalcopyright' => 1, + 'legaltrademarks' => 1, + 'leicaleic' => 1, + 'length' => 1, + 'lens35efl' => 1, + 'lensattached' => 1, + 'lenscorr' => 1, + 'lensdata' => 1, + 'lensdata0100' => 1, + 'lensdata0101' => 1, + 'lensdata0201' => 1, + 'lensdata0204' => 1, + 'lensdata0400' => 1, + 'lensdata0402' => 1, + 'lensdata0403' => 1, + 'lensdata0800' => 1, + 'lensdataunknown' => 1, + 'lensdataversion' => 1, + 'lensfacing' => 1, + 'lensinfoq' => 1, + 'lensmaker' => 1, + 'lensnumber' => 1, + 'lenspartnumber' => 1, + 'lensrec' => 1, + 'lensshading' => 1, + 'lenstableindex' => 1, + 'levelinfo' => 1, + 'libraryid' => 1, + 'libraryname' => 1, + 'licenseinfourl' => 1, + 'lightingopt' => 1, + 'lightness' => 1, + 'lightroomworkflow' => 1, + 'lights' => 1, + 'lightvalue' => 1, + 'limage' => 1, + 'limitaf-areamodeselautoanimals' => 1, + 'limitaf-areamodeselautopeople' => 1, + 'limitaf-areamodeseldynamic' => 1, + 'limitaf-areamodeselwidelanimals' => 1, + 'limitaf-areamodeselwidelpeople' => 1, + 'limitafareamodesel3d' => 1, + 'limitafareamodeseld105' => 1, + 'limitafareamodeseld25' => 1, + 'limitafareamodeseld49' => 1, + 'limitafareamodeseld9' => 1, + 'limitafareamodeselgroup' => 1, + 'limitafareamodeselgroupc1' => 1, + 'limitafareamodeselgroupc2' => 1, + 'limitreleasemodeselmirror-up' => 1, + 'limitreleasemodeselq' => 1, + 'limitreleasemodeseltimer' => 1, + 'limitselectableimagearea5to4' => 1, + 'lin12toklut12' => 1, + 'lin12toklut8' => 1, + 'linearitysplinetags' => 1, + 'linearizationcoefficients1' => 1, + 'linearizationcoefficients2' => 1, + 'linearized' => 1, + 'linecount' => 1, + 'lineorder' => 1, + 'lines' => 1, + 'linkedprofilename' => 1, + 'linkerversion' => 1, + 'linkgoodput' => 1, + 'linkinfo' => 1, + 'linkquality' => 1, + 'linkstatus' => 1, + 'linksuptodate' => 1, + 'linktarget' => 1, + 'linlogtable' => 1, + 'listtype' => 1, + 'lit' => 1, + 'livephotoinfo' => 1, + 'ln0' => 1, + 'ln1' => 1, + 'localbasepath' => 1, + 'localdeltatype' => 1, + 'localeindicator' => 1, + 'localpositionned' => 1, + 'locationinfo' => 1, + 'lockedpropertylist' => 1, + 'locks' => 1, + 'loginfo' => 1, + 'loglintable' => 1, + 'logoiconurl' => 1, + 'logourl' => 1, + 'lookheadbacktype' => 1, + 'lookheader' => 1, + 'lookheadname' => 1, + 'lookheadtype' => 1, + 'lookheadversion' => 1, + 'lookmat0' => 1, + 'lookmat1' => 1, + 'lookmat2' => 1, + 'lookmat3' => 1, + 'lookmat4' => 1, + 'lookmat5' => 1, + 'lookmat6' => 1, + 'lookmat7' => 1, + 'lookmat8' => 1, + 'lookmat9' => 1, + 'lookmodtransform' => 1, + 'lookuptable' => 1, + 'lotus' => 1, + 'lowlightaf' => 1, + 'lownote' => 1, + 'lowvelocity' => 1, + 'lr' => 1, + 'lslv' => 1, + 'lucasjunk' => 1, + 'luminance' => 1, + 'luminanceconsts' => 1, + 'lyricist' => 1, + 'lyrics_synchronised' => 1, + 'machineid' => 1, + 'machinetype' => 1, + 'macintoshnsprintinfo' => 1, + 'macintoshprintinfo' => 1, + 'mag360fly' => 1, + 'magmode' => 1, + 'magneticfield' => 1, + 'magneticheading' => 1, + 'magnetometer' => 1, + 'magnetometerxyz' => 1, + 'magnification' => 1, + 'magnifyobject' => 1, + 'mailingaddress' => 1, + 'mailstop' => 1, + 'mainboard' => 1, + 'maininfo' => 1, + 'maininfoifd' => 1, + 'majorbrand' => 1, + 'makeandmodel' => 1, + 'makemodel' => 1, + 'makernotepentax5a' => 1, + 'makernotepentax5b' => 1, + 'makernotepentax5c' => 1, + 'makernotes' => 1, + 'mandatorybackground' => 1, + 'manufacturecode' => 1, + 'manufactureindex' => 1, + 'manufacturername' => 1, + 'mappingscheme' => 1, + 'mapscale' => 1, + 'mariahthresholds' => 1, + 'mariahthresholdslow' => 1, + 'mariahthresholdsnormal' => 1, + 'mariahthresholdsstrong' => 1, + 'markbits' => 1, + 'marker' => 1, + 'markerid' => 1, + 'markinfo' => 1, + 'marl' => 1, + 'masksubarea' => 1, + 'masteredby' => 1, + 'mastergainadjustment' => 1, + 'matrixcoefficients' => 1, + 'matrixworldtocamera' => 1, + 'matrixworldtoscreen' => 1, + 'mattcolor' => 1, + 'matte' => 1, + 'matteing' => 1, + 'matter' => 1, + 'maxband' => 1, + 'maxbitrate' => 1, + 'maxcontentlightlevel' => 1, + 'maxdatarate' => 1, + 'maximumbitrate' => 1, + 'maximumimageindex' => 1, + 'maximumobjectsize' => 1, + 'maximumoperationindex' => 1, + 'maximumshutterangle' => 1, + 'maximumtransformindex' => 1, + 'maxjpegtableindex' => 1, + 'maxpacketsize' => 1, + 'maxpagenormal' => 1, + 'maxpdusize' => 1, + 'maxpicaveragelightlevel' => 1, + 'maxsubfilesize' => 1, + 'maxtransmissiontime' => 1, + 'maxval' => 1, + 'maxwidth' => 1, + 'mc' => 1, + 'mccdata' => 1, + 'mcdi' => 1, + 'md5signature' => 1, + 'md5sum' => 1, + 'mdcolortable' => 1, + 'mdfiletag' => 1, + 'mdfileunits' => 1, + 'mditemaccounthandles' => 1, + 'mditemaccountidentifier' => 1, + 'mditemacquisitionmake' => 1, + 'mditemacquisitionmodel' => 1, + 'mditemaltitude' => 1, + 'mditemaperture' => 1, + 'mditemaudiobitrate' => 1, + 'mditemaudiochannelcount' => 1, + 'mditemauthoremailaddresses' => 1, + 'mditemauthors' => 1, + 'mditembitspersample' => 1, + 'mditembundleidentifier' => 1, + 'mditemcity' => 1, + 'mditemcodecs' => 1, + 'mditemcolorspace' => 1, + 'mditemcomment' => 1, + 'mditemcontentcreationdate' => 1, + 'mditemcontentcreationdate_ranking' => 1, + 'mditemcontentcreationdateranking' => 1, + 'mditemcontentmodificationdate' => 1, + 'mditemcontenttype' => 1, + 'mditemcontenttypetree' => 1, + 'mditemcontributors' => 1, + 'mditemcopyright' => 1, + 'mditemcountry' => 1, + 'mditemcreator' => 1, + 'mditemdateadded' => 1, + 'mditemdateadded_ranking' => 1, + 'mditemdescription' => 1, + 'mditemdisplayname' => 1, + 'mditemdownloadeddate' => 1, + 'mditemdurationseconds' => 1, + 'mditememailconversationid' => 1, + 'mditemencodingapplications' => 1, + 'mditemexifgpsversion' => 1, + 'mditemexifversion' => 1, + 'mditemexposuremode' => 1, + 'mditemexposureprogram' => 1, + 'mditemexposuretimeseconds' => 1, + 'mditemflashonoff' => 1, + 'mditemfnumber' => 1, + 'mditemfocallength' => 1, + 'mditemfscontentchangedate' => 1, + 'mditemfscreatorcode' => 1, + 'mditemfsfinderflags' => 1, + 'mditemfshascustomicon' => 1, + 'mditemfsinvisible' => 1, + 'mditemfsisextensionhidden' => 1, + 'mditemfsisstationery' => 1, + 'mditemfsname' => 1, + 'mditemfsnodecount' => 1, + 'mditemfsownergroupid' => 1, + 'mditemfsowneruserid' => 1, + 'mditemfssize' => 1, + 'mditemfstypecode' => 1, + 'mditemgpsdatestamp' => 1, + 'mditemgpsstatus' => 1, + 'mditemgpstrack' => 1, + 'mditemhasalphachannel' => 1, + 'mditemidentifier' => 1, + 'mditemimagedirection' => 1, + 'mditeminterestingdate_ranking' => 1, + 'mditeminterestingdateranking' => 1, + 'mditemisapplicationmanaged' => 1, + 'mditemisexistingthread' => 1, + 'mditemislikelyjunk' => 1, + 'mditemisospeed' => 1, + 'mditemkeywords' => 1, + 'mditemkind' => 1, + 'mditemlastuseddate' => 1, + 'mditemlastuseddate_ranking' => 1, + 'mditemlatitude' => 1, + 'mditemlensmodel' => 1, + 'mditemlogicalsize' => 1, + 'mditemlongitude' => 1, + 'mditemmailboxes' => 1, + 'mditemmaildatereceived_ranking' => 1, + 'mditemmediatypes' => 1, + 'mditemnumberofpages' => 1, + 'mditemorientation' => 1, + 'mditemoriginapplicationidentifier' => 1, + 'mditemoriginmessageid' => 1, + 'mditemoriginsenderdisplayname' => 1, + 'mditemoriginsenderhandle' => 1, + 'mditemoriginsubject' => 1, + 'mditempageheight' => 1, + 'mditempagewidth' => 1, + 'mditemphysicalsize' => 1, + 'mditempixelcount' => 1, + 'mditempixelheight' => 1, + 'mditempixelwidth' => 1, + 'mditemprimaryrecipientemailaddresses' => 1, + 'mditemprofilename' => 1, + 'mditemrecipients' => 1, + 'mditemredeyeonoff' => 1, + 'mditemresolutionheightdpi' => 1, + 'mditemresolutionwidthdpi' => 1, + 'mditemsecuritymethod' => 1, + 'mditemspeed' => 1, + 'mditemstateorprovince' => 1, + 'mditemstreamable' => 1, + 'mditemsubject' => 1, + 'mditemtimestamp' => 1, + 'mditemtitle' => 1, + 'mditemtotalbitrate' => 1, + 'mditemusecount' => 1, + 'mditemuseddates' => 1, + 'mditemuserdownloadeddate' => 1, + 'mditemuserdownloadeduserhandle' => 1, + 'mditemusersharedreceiveddate' => 1, + 'mditemusersharedreceivedrecipient' => 1, + 'mditemusersharedreceivedrecipienthandle' => 1, + 'mditemusersharedreceivedsender' => 1, + 'mditemusersharedreceivedsenderhandle' => 1, + 'mditemusersharedreceivedtransport' => 1, + 'mditemversion' => 1, + 'mditemvideobitrate' => 1, + 'mditemwherefroms' => 1, + 'mditemwhitebalance' => 1, + 'mdlabname' => 1, + 'mdpm' => 1, + 'mdpr' => 1, + 'mdprepdate' => 1, + 'mdpreptime' => 1, + 'mdsampleinfo' => 1, + 'mdscalepixel' => 1, + 'meas1label' => 1, + 'meas1params' => 1, + 'meas1type' => 1, + 'measure' => 1, + 'measuredcolor' => 1, + 'measuredinfo' => 1, + 'measurement' => 1, + 'measurementbacking' => 1, + 'measurementflare' => 1, + 'measurementgeometry' => 1, + 'measurementilluminant' => 1, + 'measurementinfo' => 1, + 'measurementinputinfo' => 1, + 'measurementobserver' => 1, + 'measurementscale' => 1, + 'mebx' => 1, + 'media' => 1, + 'mediablackpoint' => 1, + 'mediacolor' => 1, + 'mediacontenttypes' => 1, + 'mediacreated' => 1, + 'mediacredits' => 1, + 'mediadata' => 1, + 'mediadataoffset' => 1, + 'mediadatasize' => 1, + 'mediaduration' => 1, + 'mediaflags' => 1, + 'mediaheader' => 1, + 'mediaheaderversion' => 1, + 'mediaindex' => 1, + 'mediainfo' => 1, + 'mediaisdelay' => 1, + 'mediaisfinale' => 1, + 'mediaislive' => 1, + 'mediaismovie' => 1, + 'mediaispremiere' => 1, + 'mediaisrepeat' => 1, + 'mediaissap' => 1, + 'mediaissport' => 1, + 'mediaisstereo' => 1, + 'mediaissubtitled' => 1, + 'mediaistape' => 1, + 'mediajukebox' => 1, + 'medialanguagecode' => 1, + 'medianetworkaffiliation' => 1, + 'mediaoriginalbroadcastdatetime' => 1, + 'mediaoriginalchannel' => 1, + 'mediaoriginalchannelsubnumber' => 1, + 'mediaoriginalruntime' => 1, + 'mediapro' => 1, + 'mediastationcallsign' => 1, + 'mediastationname' => 1, + 'mediathumbaspectratiox' => 1, + 'mediathumbaspectratioy' => 1, + 'mediathumbheight' => 1, + 'mediathumbratingattributes' => 1, + 'mediathumbratinglevel' => 1, + 'mediathumbratingsystem' => 1, + 'mediathumbret' => 1, + 'mediathumbstride' => 1, + 'mediathumbtimestamp' => 1, + 'mediathumbwidth' => 1, + 'mediatimescale' => 1, + 'mediatrackbytes' => 1, + 'mediauniqueid' => 1, + 'mediaweight' => 1, + 'mediawhitepoint' => 1, + 'medium' => 1, + 'megapixels' => 1, + 'melodicpolyphony' => 1, + 'menuoffset' => 1, + 'menusettingsoffset' => 1, + 'menusettingsoffsetz7ii' => 1, + 'menusettingsoffsetz8' => 1, + 'menusettingsoffsetz9' => 1, + 'menusettingsoffsetz9v3' => 1, + 'menusettingsoffsetz9v4' => 1, + 'message' => 1, + 'meta' => 1, + 'metadata' => 1, + 'metadatacreator' => 1, + 'metadataid' => 1, + 'metadatalibrary' => 1, + 'metadatanumber' => 1, + 'metadatasource' => 1, + 'metadataversion' => 1, + 'metaformat' => 1, + 'metaimagesize' => 1, + 'metarelation' => 1, + 'metasampledesc' => 1, + 'metatype' => 1, + 'metdata' => 1, + 'meter' => 1, + 'meterinfo' => 1, + 'meteringbutton' => 1, + 'meterlink' => 1, + 'metermode' => 1, + 'mett' => 1, + 'microsoft' => 1, + 'microsoftxtra' => 1, + 'middlename' => 1, + 'midicontrol' => 1, + 'midicontrolversion' => 1, + 'midipitchfraction' => 1, + 'midisong' => 1, + 'midiunitynote' => 1, + 'mie' => 1, + 'miiscoreidentifier' => 1, + 'mileage' => 1, + 'mimeencoding' => 1, + 'mimetype' => 1, + 'miniaturefilter' => 1, + 'miniaturefilterorientation' => 1, + 'miniaturefilterparameter' => 1, + 'miniaturefilterposition' => 1, + 'minimumbitrate' => 1, + 'minimumflushrows' => 1, + 'minimumversion' => 1, + 'minoltacamerasettings' => 1, + 'minoltacamerasettings2' => 1, + 'minoltacamerasettings5d' => 1, + 'minoltacamerasettings7d' => 1, + 'minoltacamerasettingsold' => 1, + 'minoltamakernote' => 1, + 'minoltamma0' => 1, + 'minoltamma1' => 1, + 'minoltaprd' => 1, + 'minoltarif' => 1, + 'minoltatags' => 1, + 'minoltattw' => 1, + 'minoltawbg' => 1, + 'minpacketsize' => 1, + 'minspatialsegmentationidc' => 1, + 'mintransmissiontime' => 1, + 'missionid' => 1, + 'mixedby' => 1, + 'mmclips' => 1, + 'mngheader' => 1, + 'mobitype' => 1, + 'mobiversion' => 1, + 'modeextension' => 1, + 'modeflags' => 1, + 'model2' => 1, + 'modelandversion' => 1, + 'modeltype' => 1, + 'modenumber' => 1, + 'modificationnumber' => 1, + 'modificationpermissions' => 1, + 'modifiedby' => 1, + 'modifiedinfo' => 1, + 'moiversion' => 1, + 'monomonitormatrix' => 1, + 'monostrength' => 1, + 'monotonescaletable' => 1, + 'monouniquematrix' => 1, + 'montage' => 1, + 'month' => 1, + 'moreinfo' => 1, + 'moreinfo0201' => 1, + 'moreinfo0401' => 1, + 'moreinfobannerimage' => 1, + 'moreinfobannerurl' => 1, + 'moreinfotext' => 1, + 'moreinfourl' => 1, + 'moresettings' => 1, + 'moresettingsoffset' => 1, + 'mosaicpattern' => 1, + 'movableinfo' => 1, + 'moveobjects' => 1, + 'movie' => 1, + 'moviefragment' => 1, + 'moviefragmentheader' => 1, + 'moviefragmentsequence' => 1, + 'movieheader' => 1, + 'movieheaderversion' => 1, + 'moviehighlightdisplaypattern' => 1, + 'movieinfo' => 1, + 'moviestreamname' => 1, + 'moviewhitebalancesameasphoto' => 1, + 'mp' => 1, + 'mp1' => 1, + 'mpeg7binary' => 1, + 'mpegaudioversion' => 1, + 'mpf' => 1, + 'mpfversion' => 1, + 'mpimage' => 1, + 'mpimageflags' => 1, + 'mpimageformat' => 1, + 'mpimagelength' => 1, + 'mpimagelist' => 1, + 'mpimagestart' => 1, + 'mpimagetype' => 1, + 'mpindividualnum' => 1, + 'mrwinfo' => 1, + 'msdocumenttext' => 1, + 'msdocumenttextposition' => 1, + 'mspropertysetstorage' => 1, + 'msstereo' => 1, + 'mtoa0' => 1, + 'mtob0' => 1, + 'mtob1' => 1, + 'mtob2' => 1, + 'mtob3' => 1, + 'mtos0' => 1, + 'mtos1' => 1, + 'mtos2' => 1, + 'mtos3' => 1, + 'multiexp' => 1, + 'multiexposure2' => 1, + 'multiexposureversion' => 1, + 'multimediatype' => 1, + 'multiplexdefaultvalues' => 1, + 'multiplextypearray' => 1, + 'multiprofiles' => 1, + 'multiquality' => 1, + 'multishoton' => 1, + 'multiview' => 1, + 'musicby' => 1, + 'musiccdidentifier' => 1, + 'musiciancredits' => 1, + 'mwg-coll' => 1, + 'mwg-kw' => 1, + 'mwg-rs' => 1, + 'mxfversion' => 1, + 'mycolors' => 1, + 'namedcolor' => 1, + 'namedcolor2' => 1, + 'nametableversion' => 1, + 'nameutf-8' => 1, + 'nativedisplayinfo' => 1, + 'nativeresolutionunit' => 1, + 'nativexresolution' => 1, + 'nativeyresolution' => 1, + 'ncc' => 1, + 'nemoblurkernel' => 1, + 'nemodarklimit' => 1, + 'nemogainfactors' => 1, + 'nemohighlight12limit' => 1, + 'nemotilesize' => 1, + 'nestedsignalstream' => 1, + 'nestlevel' => 1, + 'netexposurecompensation' => 1, + 'netname' => 1, + 'netprovidertype' => 1, + 'neutals' => 1, + 'neutobjbacktype' => 1, + 'neutobjname' => 1, + 'neutobjtype' => 1, + 'neutobjversion' => 1, + 'neutrals' => 1, + 'newbitdepth' => 1, + 'newcolortype' => 1, + 'newguid' => 1, + 'newlines' => 1, + 'nexttrackid' => 1, + 'nifnonlinearity' => 1, + 'nifnonlinearity12bit' => 1, + 'nifnonlinearity12to16' => 1, + 'nifnonlinearity16bit' => 1, + 'nifnonlinearityext' => 1, + 'nikon' => 1, + 'nikonapp' => 1, + 'nikondata' => 1, + 'nikondatetime' => 1, + 'nikonncdb' => 1, + 'nikonncdt' => 1, + 'nikonnefinfo' => 1, + 'nikonscanifd' => 1, + 'nikontags' => 1, + 'nikonvers' => 1, + 'nine' => 1, + 'nineedits' => 1, + 'ninthlanguage' => 1, + 'nitf' => 1, + 'nitfversion' => 1, + 'noise' => 1, + 'noisereductiondata' => 1, + 'noisereductionkernel' => 1, + 'noisereductionparametershostlow' => 1, + 'noisereductionparametershostnormal' => 1, + 'noisereductionparametershoststrong' => 1, + 'nominalbitrate' => 1, + 'nominalframecount' => 1, + 'nominallayercount' => 1, + 'nominalplaytime' => 1, + 'nominalvideobitrate' => 1, + 'nomssmarttags' => 1, + 'normallinetransfertiming' => 1, + 'note' => 1, + 'notice' => 1, + 'now' => 1, + 'npts' => 1, + 'nrwdata' => 1, + 'nsc_address' => 1, + 'nsc_description' => 1, + 'nsc_email' => 1, + 'nsc_name' => 1, + 'nsc_phone' => 1, + 'nullmediaheader' => 1, + 'numberlist' => 1, + 'numberofcomponents' => 1, + 'numberofframes' => 1, + 'numberofimages' => 1, + 'numberofinks' => 1, + 'numberofparts' => 1, + 'numberofplanes' => 1, + 'numberofresolutions' => 1, + 'numberofsamples' => 1, + 'numberofsamples64' => 1, + 'numchanneldescriptions' => 1, + 'numchannels' => 1, + 'numcolors' => 1, + 'numfonts' => 1, + 'numhistorybuffers' => 1, + 'numimportantcolors' => 1, + 'numpackets' => 1, + 'numproperties' => 1, + 'numrules' => 1, + 'numsampleframes' => 1, + 'numsampleloops' => 1, + 'numslices' => 1, + 'numstreams' => 1, + 'numtemporallayers' => 1, + 'objectcountrycodes' => 1, + 'objectcountrycodingmethod' => 1, + 'objectcountrycodingmethoddate' => 1, + 'objectfiletype' => 1, + 'objectflags' => 1, + 'objectid' => 1, + 'objectorientation' => 1, + 'objectsizeannounced' => 1, + 'observationdate' => 1, + 'observationdateend' => 1, + 'observationtime' => 1, + 'observationtimeend' => 1, + 'observer' => 1, + 'obsoletephotoshoptag1' => 1, + 'obsoletephotoshoptag2' => 1, + 'obsoletephotoshoptag3' => 1, + 'ocad' => 1, + 'ocadrevision' => 1, + 'oceapplicationselector' => 1, + 'oceidnumber' => 1, + 'oceimagelogic' => 1, + 'ocescanjobdesc' => 1, + 'office' => 1, + 'officelocation' => 1, + 'offlineavailability' => 1, + 'offlinestatus' => 1, + 'offset13' => 1, + 'offsetcornerlatitude1' => 1, + 'offsetcornerlatitude2' => 1, + 'offsetcornerlatitude3' => 1, + 'offsetcornerlatitude4' => 1, + 'offsetcornerlongitude1' => 1, + 'offsetcornerlongitude2' => 1, + 'offsetcornerlongitude3' => 1, + 'offsetcornerlongitude4' => 1, + 'offsetmaphorizontal' => 1, + 'offsetmapvertical' => 1, + 'offsetorigin' => 1, + 'offsetx' => 1, + 'offsetxy' => 1, + 'offsety' => 1, + 'oldxmp' => 1, + 'olym' => 1, + 'olympus' => 1, + 'olympus2100' => 1, + 'olympus2100ifd' => 1, + 'olympus2200' => 1, + 'olympus2200ifd' => 1, + 'olympus2300' => 1, + 'olympus2300ifd' => 1, + 'olympus2400' => 1, + 'olympus2400ifd' => 1, + 'olympus2500' => 1, + 'olympus2500ifd' => 1, + 'olympus2600' => 1, + 'olympus2600ifd' => 1, + 'olympus2700' => 1, + 'olympus2700ifd' => 1, + 'olympus2800' => 1, + 'olympus2800ifd' => 1, + 'olympus2900' => 1, + 'olympus2900ifd' => 1, + 'olympusatom' => 1, + 'olympusdss' => 1, + 'olympusjunk' => 1, + 'olympusolym' => 1, + 'olympuspreview' => 1, + 'olympustags1' => 1, + 'olympustags2' => 1, + 'olympustags3' => 1, + 'olympustags4' => 1, + 'olympusthumbnail' => 1, + 'omenatcapturemode' => 1, + 'omenearlygobcolumns' => 1, + 'omenearlygobrows' => 1, + 'omenearlygobsurface' => 1, + 'omengradientexclusionlimits' => 1, + 'omengradientkernel' => 1, + 'omengradientkerneltaps' => 1, + 'omengradientoffset' => 1, + 'omeninitialcolumns' => 1, + 'omeninitialrows' => 1, + 'omeninitialscaling' => 1, + 'omenmeantostrength' => 1, + 'omenpercenttorationallimitsblue' => 1, + 'omenpercenttorationallimitsgob' => 1, + 'omenpercenttorationallimitsgor' => 1, + 'omenpercenttorationallimitsred' => 1, + 'omenrangeweighting' => 1, + 'omenratioclipfactors' => 1, + 'omenratioexclusionfactors' => 1, + 'omenroicoefficients' => 1, + 'omenroicoordinates' => 1, + 'omensmoothingkernel' => 1, + 'on1_settingsdata' => 1, + 'on1_settingsmetadatacreated' => 1, + 'on1_settingsmetadatamodified' => 1, + 'on1_settingsmetadataname' => 1, + 'on1_settingsmetadatapluginid' => 1, + 'on1_settingsmetadatatimestamp' => 1, + 'on1_settingsmetadatausage' => 1, + 'on1_settingsmetadatavisibletouser' => 1, + 'onionskins' => 1, + 'opacity' => 1, + 'opcolor' => 1, + 'opendml' => 1, + 'openwithapplication' => 1, + 'operatingsystem' => 1, + 'operation' => 1, + 'operationalmode' => 1, + 'operationclassid' => 1, + 'operationid' => 1, + 'operationnumber' => 1, + 'opiproxy' => 1, + 'opticalvr' => 1, + 'optics' => 1, + 'optimalbitrate' => 1, + 'optionalattendeeaddresses' => 1, + 'optionalattendees' => 1, + 'opus' => 1, + 'opusversion' => 1, + 'orderingrestrictions' => 1, + 'organizeraddress' => 1, + 'organizername' => 1, + 'orientationinfo' => 1, + 'orientationoffset' => 1, + 'orientoffset' => 1, + 'original' => 1, + 'originalalbum' => 1, + 'originaldate' => 1, + 'originaldocumentsize' => 1, + 'originalfilesize' => 1, + 'originalfiletype' => 1, + 'originalformat' => 1, + 'originalframerate' => 1, + 'originalimagebroker' => 1, + 'originalmedia' => 1, + 'originalmediatype' => 1, + 'originalmedium' => 1, + 'originalrawcreator' => 1, + 'originalrawfiletype' => 1, + 'originalrawimage' => 1, + 'originalrawresource' => 1, + 'originalreleasetime' => 1, + 'originalreleaseyear' => 1, + 'originalscannedimagesize' => 1, + 'originalsoftpostpadding' => 1, + 'originalsoftprepadding' => 1, + 'originalthmcreator' => 1, + 'originalthmfiletype' => 1, + 'originalthmimage' => 1, + 'originalthmresource' => 1, + 'originalyear' => 1, + 'originator' => 1, + 'originatorreference' => 1, + 'originpathinfo' => 1, + 'osversion' => 1, + 'otheraddress' => 1, + 'othercity' => 1, + 'othercodecdescription' => 1, + 'othercodecname' => 1, + 'othercountry-region' => 1, + 'otherdate1' => 1, + 'otherdate2' => 1, + 'otherdate3' => 1, + 'otherfirmware' => 1, + 'otherformat' => 1, + 'otherinfo' => 1, + 'othermeta' => 1, + 'othername' => 1, + 'otheroffset' => 1, + 'otherpobox' => 1, + 'otherpostalcode' => 1, + 'othersampledesc' => 1, + 'otherserialnumber' => 1, + 'otherstateorprovince' => 1, + 'otherstreet' => 1, + 'outputdataobjectlist' => 1, + 'outputgain' => 1, + 'outputheight' => 1, + 'outputorientation' => 1, + 'outputresponse' => 1, + 'outputwidth' => 1, + 'overclockcolsright' => 1, + 'overclockrowsbottom' => 1, + 'overflowcolor' => 1, + 'ownership' => 1, + 'packets' => 1, + 'packingmethod' => 1, + 'page' => 1, + 'pageenter' => 1, + 'pageexit' => 1, + 'pagefront' => 1, + 'pagelayout' => 1, + 'pagemode' => 1, + 'pagenormal' => 1, + 'pager' => 1, + 'pages' => 1, + 'pagespecial' => 1, + 'paintdata' => 1, + 'paintimage' => 1, + 'paintimageheight' => 1, + 'paintimagetype' => 1, + 'paintimagewidth' => 1, + 'paintingfilter' => 1, + 'palette' => 1, + 'palettecolors' => 1, + 'palettefilename' => 1, + 'palettehistogram' => 1, + 'paletteinfo' => 1, + 'palettemethod' => 1, + 'palettename' => 1, + 'palettestretch' => 1, + 'palmfiletype' => 1, + 'panasonic' => 1, + 'panasonicpana' => 1, + 'panorama' => 1, + 'panorientation' => 1, + 'panoverlaph' => 1, + 'panoverlapv' => 1, + 'paragraphs' => 1, + 'parallelismtype' => 1, + 'param0' => 1, + 'param1' => 1, + 'param2' => 1, + 'param3' => 1, + 'parameter' => 1, + 'parameterinfo' => 1, + 'parameters' => 1, + 'parasites' => 1, + 'parentalratingreason' => 1, + 'parrotautomation' => 1, + 'parrotfollowme' => 1, + 'parrottimestamp' => 1, + 'parrotv1' => 1, + 'parrotv2' => 1, + 'parrotv3' => 1, + 'part' => 1, + 'partialactivecolsleft' => 1, + 'partialactivecolsright' => 1, + 'partialactiverowsbottom' => 1, + 'partialactiverowstop' => 1, + 'partialjxlcodestream' => 1, + 'partialpalette' => 1, + 'partialsyncsamples' => 1, + 'participants' => 1, + 'partnumber' => 1, + 'partoffset' => 1, + 'partofset' => 1, + 'pastclippingboundary' => 1, + 'pasteimage' => 1, + 'path' => 1, + 'pathselectionstate' => 1, + 'pathtablelocation' => 1, + 'pathtablesize' => 1, + 'patternangle' => 1, + 'patterncorrectionfactors' => 1, + 'patterncorrectiongains' => 1, + 'patterncorrectionoffsets' => 1, + 'patterngainconversiontable' => 1, + 'payloadtype' => 1, + 'paymenturl' => 1, + 'pcscitation' => 1, + 'pdahistogram' => 1, + 'pdf' => 1, + 'pdfx' => 1, + 'peakbitrate' => 1, + 'peaklevelr128' => 1, + 'peaklevelsample' => 1, + 'peakspectralsensitivity' => 1, + 'peakvalue' => 1, + 'pefversion' => 1, + 'pentax' => 1, + 'pentaxdata' => 1, + 'pentaxdata2' => 1, + 'pentaxjunk' => 1, + 'pentaxjunk2' => 1, + 'pentaxpent' => 1, + 'pentaxpreview' => 1, + 'pentaxtags' => 1, + 'perceivedtype' => 1, + 'perceptualrenderingintentgamut' => 1, + 'percussivepolyphony' => 1, + 'perfmode' => 1, + 'performersortorder' => 1, + 'perms' => 1, + 'perpicturenotes' => 1, + 'personalfunctions' => 1, + 'personalfunctionvalues' => 1, + 'personaltitle' => 1, + 'petype' => 1, + 'pfmheader' => 1, + 'pfmversion' => 1, + 'pgfversion' => 1, + 'phone' => 1, + 'photoeffectsdata' => 1, + 'photofinishername' => 1, + 'photomech' => 1, + 'photomechanic' => 1, + 'photoresolution' => 1, + 'photoshop' => 1, + 'photoshop2colortable' => 1, + 'photoshop2info' => 1, + 'photoshop_profile' => 1, + 'photoshopdata' => 1, + 'photoshopformat' => 1, + 'photoshopinfo' => 1, + 'photoshopsettings' => 1, + 'physicalpixel' => 1, + 'physicalstreamnumbermap' => 1, + 'physicalstreamnumbers' => 1, + 'physicalstreams' => 1, + 'pic_ifd' => 1, + 'picslabel' => 1, + 'picture' => 1, + 'picturebitsperpixel' => 1, + 'picturecontrolversion' => 1, + 'picturectrl' => 1, + 'picturedescription' => 1, + 'pictureformat' => 1, + 'pictureheight' => 1, + 'pictureindexedcolors' => 1, + 'pictureinfo' => 1, + 'picturelength' => 1, + 'picturemimetype' => 1, + 'picturestyleinfo' => 1, + 'picturetype' => 1, + 'picturewidth' => 1, + 'pieceinfo' => 1, + 'piecelength' => 1, + 'pieces' => 1, + 'pilotingmode' => 1, + 'pip' => 1, + 'pipx1' => 1, + 'pipx2' => 1, + 'pipy1' => 1, + 'pipy2' => 1, + 'pitchandfamily' => 1, + 'pitchrollyaw' => 1, + 'pittasoft' => 1, + 'pixelaspectratiox' => 1, + 'pixelaspectratioy' => 1, + 'pixelcalibration' => 1, + 'pixelcorrectiongains' => 1, + 'pixelexposuretime' => 1, + 'pixelformat' => 1, + 'pixelinfo' => 1, + 'pixelintensityrange' => 1, + 'pixellive' => 1, + 'pixelmagicjbigoptions' => 1, + 'pixelspermeterx' => 1, + 'pixelspermetery' => 1, + 'pixheight' => 1, + 'pixwidth' => 1, + 'places' => 1, + 'planckb' => 1, + 'planckf' => 1, + 'plancko' => 1, + 'planckr1' => 1, + 'planckr2' => 1, + 'playbackflickdownrating' => 1, + 'playbackflickuprating' => 1, + 'playbackframerate' => 1, + 'playcounter' => 1, + 'playlist' => 1, + 'playlistdelay' => 1, + 'playlistindex' => 1, + 'plus' => 1, + 'pmi' => 1, + 'pobox' => 1, + 'podcastcategory' => 1, + 'podcastdescription' => 1, + 'podcastid' => 1, + 'podcastkeywords' => 1, + 'pointsize' => 1, + 'popularimeter' => 1, + 'popupfillattributes' => 1, + 'popupflash' => 1, + 'portraitmatrix' => 1, + 'portraitoffset' => 1, + 'portraittonescaletable' => 1, + 'poseyawdegrees' => 1, + 'position' => 1, + 'postertime' => 1, + 'postscript2crd0' => 1, + 'postscript2crd1' => 1, + 'postscript2crd2' => 1, + 'postscript2crd3' => 1, + 'postscript2csa' => 1, + 'postscriptfont' => 1, + 'postscriptfontname' => 1, + 'pragma' => 1, + 'preferredfamily' => 1, + 'preferredrate' => 1, + 'preferredsubfamily' => 1, + 'preferredvolume' => 1, + 'prefersubselectorcenter' => 1, + 'preroll' => 1, + 'presentationformat' => 1, + 'presentationtarget' => 1, + 'preview' => 1, + 'preview0' => 1, + 'preview1' => 1, + 'preview2' => 1, + 'previewatomindex' => 1, + 'previewatomtype' => 1, + 'previewduration' => 1, + 'previewifd' => 1, + 'previewifd_offset' => 1, + 'previewimage1' => 1, + 'previewimage2' => 1, + 'previewimagedata' => 1, + 'previewimageinfo' => 1, + 'previewinfo' => 1, + 'previewpdf' => 1, + 'previewpict' => 1, + 'previewpng' => 1, + 'previewtiff' => 1, + 'previewtime' => 1, + 'previewversion' => 1, + 'previewwmf' => 1, + 'primarye-mail' => 1, + 'primaryfileguid' => 1, + 'primaryitemreference' => 1, + 'primaryphone' => 1, + 'primaryplatform' => 1, + 'primaryvolume' => 1, + 'printflags' => 1, + 'printflagsinfo' => 1, + 'printimversion' => 1, + 'printinfo' => 1, + 'printinfo2' => 1, + 'printposition' => 1, + 'printquality' => 1, + 'printscale' => 1, + 'printscaleinfo' => 1, + 'printstyle' => 1, + 'printtovideo' => 1, + 'priority' => 1, + 'prioritysettings' => 1, + 'prism' => 1, + 'private' => 1, + 'privatebuild' => 1, + 'prl' => 1, + 'prm' => 1, + 'processing' => 1, + 'processinginfo' => 1, + 'processingparameters' => 1, + 'processingtime' => 1, + 'processparametersv2' => 1, + 'prod' => 1, + 'prodnotes' => 1, + 'producedby' => 1, + 'produceddate' => 1, + 'producednotice' => 1, + 'producers' => 1, + 'product' => 1, + 'productionaperturedimensions' => 1, + 'productioncopyright' => 1, + 'productiondate' => 1, + 'productiondesigner' => 1, + 'productionstudio' => 1, + 'productname' => 1, + 'producttype' => 1, + 'productversionnumber' => 1, + 'profile1audiocodec' => 1, + 'profile1height' => 1, + 'profile1videocodec' => 1, + 'profile1width' => 1, + 'profileclass' => 1, + 'profilecmmtype' => 1, + 'profileconnectionspace' => 1, + 'profilecreator' => 1, + 'profiledataoffset' => 1, + 'profiledatetime' => 1, + 'profiledescription' => 1, + 'profiledescriptionml' => 1, + 'profilefilesignature' => 1, + 'profileheader' => 1, + 'profileid' => 1, + 'profileifd' => 1, + 'profilesequencedesc' => 1, + 'profilesequenceidentifier' => 1, + 'profilesequenceinfo' => 1, + 'profilesize' => 1, + 'profileversion' => 1, + 'progid' => 1, + 'programdescription' => 1, + 'programid' => 1, + 'programname' => 1, + 'progressivescans' => 1, + 'projazimuthangle' => 1, + 'projcentereasting' => 1, + 'projcenterlat' => 1, + 'projcenterlong' => 1, + 'projcenternorthing' => 1, + 'projcoordtrans' => 1, + 'project' => 1, + 'projectedcstype' => 1, + 'projectidcode' => 1, + 'projection' => 1, + 'projectionboundsbottom' => 1, + 'projectionboundsleft' => 1, + 'projectionboundsright' => 1, + 'projectionboundstop' => 1, + 'projectionheader' => 1, + 'projects' => 1, + 'projfalseeasting' => 1, + 'projfalsenorthing' => 1, + 'projfalseorigineasting' => 1, + 'projfalseoriginlat' => 1, + 'projfalseoriginlong' => 1, + 'projfalseoriginnorthing' => 1, + 'projlinearunits' => 1, + 'projlinearunitsize' => 1, + 'projnatoriginlat' => 1, + 'projnatoriginlong' => 1, + 'projrectifiedgridangle' => 1, + 'projscaleatcenter' => 1, + 'projscaleatnatorigin' => 1, + 'projstdparallel1' => 1, + 'projstdparallel2' => 1, + 'projstraightvertpolelong' => 1, + 'promoteparent' => 1, + 'prop' => 1, + 'properties' => 1, + 'propertysetidcodes' => 1, + 'propertysetpathname' => 1, + 'propertystoredata' => 1, + 'propertyvectorelements' => 1, + 'protect' => 1, + 'protected' => 1, + 'protectioninfo' => 1, + 'protectiontype' => 1, + 'protune' => 1, + 'providercopyright' => 1, + 'providerlogourl' => 1, + 'providerrating' => 1, + 'providerstyle' => 1, + 'providerurl' => 1, + 'ps2crdvmsize' => 1, + 'ps2renderingintent' => 1, + 'psfontinfo' => 1, + 'pstringcaption' => 1, + 'ptrh' => 1, + 'publishdate' => 1, + 'publishdatestart' => 1, + 'publisherlimit' => 1, + 'publisherurl' => 1, + 'pur' => 1, + 'purchasecurrency' => 1, + 'purchasefileformat' => 1, + 'purchaseinfo' => 1, + 'purchaseitem' => 1, + 'purchaseowner' => 1, + 'purchaseprice' => 1, + 'purpose' => 1, + 'pyramidlevels' => 1, + 'qtablelarge100pct' => 1, + 'qtablelarge25pct' => 1, + 'qtablelarge50pct' => 1, + 'qtablelarge67pct' => 1, + 'qtablemedium100pct' => 1, + 'qtablemedium25pct' => 1, + 'qtablemedium50pct' => 1, + 'qtablemedium67pct' => 1, + 'qtablesmall100pct' => 1, + 'qtablesmall25pct' => 1, + 'qtablesmall50pct' => 1, + 'qtablesmall67pct' => 1, + 'qualcomm' => 1, + 'quickedit' => 1, + 'quickmaskinfo' => 1, + 'quietshuttershootingspeed' => 1, + 'quoting' => 1, + 'qvci' => 1, + 'ra3' => 1, + 'ra4' => 1, + 'ra5' => 1, + 'radioband' => 1, + 'radioformat' => 1, + 'radiostationname' => 1, + 'radiostationowner' => 1, + 'rafdata' => 1, + 'rafversion' => 1, + 'rangeimagelocalset' => 1, + 'rasterpadding' => 1, + 'rate' => 1, + 'rated' => 1, + 'ratingorg' => 1, + 'ratioimage' => 1, + 'rawburstmoderoll' => 1, + 'rawcfacomponentaverages' => 1, + 'rawcodecversion' => 1, + 'rawdatalength' => 1, + 'rawdataoffset' => 1, + 'rawdatarotation' => 1, + 'rawdev2' => 1, + 'rawdev2ifd' => 1, + 'rawdevelopment' => 1, + 'rawdevelopmentifd' => 1, + 'rawexposurebias' => 1, + 'rawgsensor' => 1, + 'rawhistogram' => 1, + 'rawimageaspectratio' => 1, + 'rawimagecroppedsize' => 1, + 'rawimagecroptopleft' => 1, + 'rawimagefullheight' => 1, + 'rawimagefullsize' => 1, + 'rawimagefullwidth' => 1, + 'rawimageheight' => 1, + 'rawimagemode' => 1, + 'rawimagesegmentation' => 1, + 'rawimagewidth' => 1, + 'rawinfo' => 1, + 'rawinfoifd' => 1, + 'rawjpginfo' => 1, + 'rawthermalimage' => 1, + 'rawthermalimageheight' => 1, + 'rawthermalimagetype' => 1, + 'rawthermalimagewidth' => 1, + 'rawvaluemedian' => 1, + 'rawvaluerange' => 1, + 'rawvaluerangemax' => 1, + 'rawvaluerangemin' => 1, + 'rawzorcreatorversion' => 1, + 'rawzorrequiredversion' => 1, + 'rdf' => 1, + 'readername' => 1, + 'readerrequirements' => 1, + 'reading1description' => 1, + 'reading1device' => 1, + 'reading1units' => 1, + 'reading1value' => 1, + 'reading2description' => 1, + 'reading2device' => 1, + 'reading2units' => 1, + 'reading2value' => 1, + 'reading3description' => 1, + 'reading3device' => 1, + 'reading3units' => 1, + 'reading3value' => 1, + 'reading4description' => 1, + 'reading4device' => 1, + 'reading4units' => 1, + 'reading4value' => 1, + 'readstatus' => 1, + 'real2ir' => 1, + 'realtimestreamingprotocol' => 1, + 'rearcontropaneldisplay' => 1, + 'recallshootfuncaf-on' => 1, + 'recallshootfuncafareamode' => 1, + 'recallshootfuncaperture' => 1, + 'recallshootfuncexposurecomp' => 1, + 'recallshootfuncexposuremode' => 1, + 'recallshootfuncfocustracking' => 1, + 'recallshootfunciso' => 1, + 'recallshootfuncmeteringmode' => 1, + 'recallshootfuncshutterspeed' => 1, + 'recallshootfuncwhitebalance' => 1, + 'receivedfrom' => 1, + 'recengineer' => 1, + 'recinfo' => 1, + 'reclocation' => 1, + 'reconstructiontype' => 1, + 'recordedby' => 1, + 'recordeddate' => 1, + 'recordingdates' => 1, + 'recordinglocation' => 1, + 'recordingtime' => 1, + 'recordingtimeday' => 1, + 'recordingtimemonth' => 1, + 'recordingtimeyear' => 1, + 'recordingtimeyearmonth' => 1, + 'recordingtimeyearmonthday' => 1, + 'records' => 1, + 'recordsv2' => 1, + 'rect' => 1, + 'rectangleofinterest' => 1, + 'red1header' => 1, + 'red2header' => 1, + 'redadjust' => 1, + 'redblueflatfield' => 1, + 'redcodeversion' => 1, + 'redendpoint' => 1, + 'redeyedata' => 1, + 'redgain' => 1, + 'redmask' => 1, + 'redmatrixcolumn' => 1, + 'redprimary' => 1, + 'redsample' => 1, + 'redtrc' => 1, + 'redx' => 1, + 'redy' => 1, + 'reelnumber' => 1, + 'reeltimecode' => 1, + 'reference' => 1, + 'referencename' => 1, + 'referencetemperature' => 1, + 'reflectedapparenttemperature' => 1, + 'reflectedtemperature' => 1, + 'reflection' => 1, + 'reflectionhardcopyorigcolorimetry' => 1, + 'reflectionprintoutputcolorimetry' => 1, + 'refresh' => 1, + 'regionxformtackpoint' => 1, + 'registerreadtiming' => 1, + 'relatedimagefile' => 1, + 'relationship' => 1, + 'relativeexposure' => 1, + 'relativehumidity' => 1, + 'relativelocation' => 1, + 'relativepath' => 1, + 'relativevolumeadjustment' => 1, + 'releasedateday' => 1, + 'releasedatemonth' => 1, + 'releasedateyear' => 1, + 'releasedateyearmonth' => 1, + 'releasedateyearmonthday' => 1, + 'releasinginstructions' => 1, + 'rembrandtconsumertonescale' => 1, + 'rembrandtportraittonescale' => 1, + 'rembrandttonescale' => 1, + 'remindertime' => 1, + 'remixedby' => 1, + 'remotefuncbutton' => 1, + 'removehistoryduplicates' => 1, + 'renderingintent' => 1, + 'renderingtransform' => 1, + 'rentalexpirationdate' => 1, + 'rentalflag' => 1, + 'repeateddatabytes' => 1, + 'replaygainalbumgain' => 1, + 'replaygainalbumpeak' => 1, + 'replaygaingain' => 1, + 'replaygainpeak' => 1, + 'replaygaintrackgain' => 1, + 'replaygaintrackpeak' => 1, + 'replaytrackgain' => 1, + 'replaytrackpeak' => 1, + 'replayvolumelevel' => 1, + 'replyto' => 1, + 'representativedisparityfar' => 1, + 'representativedisparitynear' => 1, + 'representativeimage' => 1, + 'requestid' => 1, + 'requeststate' => 1, + 'requiredattendeeaddresses' => 1, + 'requiredattendees' => 1, + 'rerun' => 1, + 'reserved1' => 1, + 'reserved5' => 1, + 'reservedblob2' => 1, + 'reservedblob3' => 1, + 'reservedblob4' => 1, + 'reservedblob5' => 1, + 'reservedblob6' => 1, + 'reservedblob7' => 1, + 'reservedblob8' => 1, + 'reservedblob9' => 1, + 'resolutioninfo' => 1, + 'resourcecount' => 1, + 'resourceforksize' => 1, + 'resources' => 1, + 'resourcesneeded' => 1, + 'resourcetype' => 1, + 'resultaspectratio' => 1, + 'retailprice' => 1, + 'retailpricecurrency' => 1, + 'retouchinfoversion' => 1, + 'review' => 1, + 'revisiondate' => 1, + 'revisionnumber' => 1, + 'revisitafter' => 1, + 'rgb_profile' => 1, + 'rgbcurves' => 1, + 'rgbtoev0' => 1, + 'rgbtoev1' => 1, + 'rgbtoev2' => 1, + 'rgbtoev3' => 1, + 'rgbtoev4' => 1, + 'rgbtoev5' => 1, + 'rgbtoev6' => 1, + 'rgbtoev7' => 1, + 'rgbtoev8' => 1, + 'rgbtoev9' => 1, + 'rgtable' => 1, + 'ricoh' => 1, + 'ricohjunk' => 1, + 'ricohmake' => 1, + 'ricohmodel' => 1, + 'ricohpitch' => 1, + 'ricohrdc2' => 1, + 'ricohrmkn' => 1, + 'ricohroll' => 1, + 'ricohrr1subdir' => 1, + 'ricohsubdir' => 1, + 'ricohsubdirifd' => 1, + 'riffsize64' => 1, + 'rightdarkcol1' => 1, + 'rightdarkcol2' => 1, + 'rightdarkrect' => 1, + 'rightmag' => 1, + 'rimm13toerimm12spline' => 1, + 'rimmtocrgb0spline' => 1, + 'rimmtocrgb1spline' => 1, + 'rimmtocrgb2spline' => 1, + 'rimmtocrgb3spline' => 1, + 'rimmtocrgb4spline' => 1, + 'rimmtocrgb5spline' => 1, + 'rimmtocrgb6spline' => 1, + 'rimmtocrgb7spline' => 1, + 'rimmtocrgb8spline' => 1, + 'rimmtocrgb9spline' => 1, + 'rimnonlinearity' => 1, + 'rimtonifcolortransform' => 1, + 'rimtonifscalefactor' => 1, + 'rippedby' => 1, + 'rjmd' => 1, + 'rmeta' => 1, + 'rnoiselines' => 1, + 'robots' => 1, + 'rocinfo' => 1, + 'roidescription' => 1, + 'rollguidelements' => 1, + 'rollingshutterskewtime' => 1, + 'root' => 1, + 'rootdirectorycreatedate' => 1, + 'rootnote' => 1, + 'rot360fly' => 1, + 'rotationangle' => 1, + 'rotationinfooffset' => 1, + 'rotationxyz' => 1, + 'rotmode' => 1, + 'roundtripversion' => 1, + 'rowcount' => 1, + 'rowinterleavefactor' => 1, + 'rsrc' => 1, + 'rtmd' => 1, + 'rtptimescale' => 1, + 'runtime' => 1, + 'runtimeepoch' => 1, + 'runtimeflags' => 1, + 'runtimescale' => 1, + 'runtimesincepowerup' => 1, + 'runtimevalue' => 1, + 'runwindow' => 1, + 'rvmi_grev' => 1, + 'rvmi_srev' => 1, + 'rvtlocalset' => 1, + 's2n' => 1, + 'sample' => 1, + 'sampleblacksequence' => 1, + 'sampledatetime' => 1, + 'sampledegradationpriority' => 1, + 'sampleduration' => 1, + 'sampleflag' => 1, + 'sampleformat' => 1, + 'samplegroupdescription' => 1, + 'samplepaddingbits' => 1, + 'sampleperiod' => 1, + 'sampler' => 1, + 'samplerate2' => 1, + 'samplerdata' => 1, + 'samplerdatalen' => 1, + 'samplesize' => 1, + 'samplesizes' => 1, + 'sampletable' => 1, + 'sampletext' => 1, + 'sampletime' => 1, + 'sampletochunk' => 1, + 'sampletogroup' => 1, + 'samsung' => 1, + 'samsungifd' => 1, + 'samsunginfo' => 1, + 'samsungrawbyteorder' => 1, + 'samsungrawpointerslength' => 1, + 'samsungrawpointersoffset' => 1, + 'samsungrawunknown' => 1, + 'samsungsec' => 1, + 'samsungsmta' => 1, + 'samsungsvss' => 1, + 'samsungtags' => 1, + 'samsungtrailer' => 1, + 'samsunguniqueid' => 1, + 'sanyomov' => 1, + 'sanyomp4' => 1, + 'sap' => 1, + 'sarmotionimagerydata' => 1, + 'saturationinfo' => 1, + 'saturationrenderingintentgamut' => 1, + 'savefocus' => 1, + 'saveobjbacktype' => 1, + 'saveobjects' => 1, + 'saveobjname' => 1, + 'saveobjtype' => 1, + 'saveobjversion' => 1, + 'savesetup' => 1, + 'sba_rgbshifts' => 1, + 'sbabluebalancelut' => 1, + 'sbaexposurerecord' => 1, + 'sbagreenbalancelut' => 1, + 'sbainputimagebitdepth' => 1, + 'sbainputimagecolorspace' => 1, + 'sbalog12transform' => 1, + 'sbalogtransform' => 1, + 'sbaredbalancelut' => 1, + 'scalado' => 1, + 'scale' => 1, + 'scalecrop' => 1, + 'scaledimu' => 1, + 'scaledpressure' => 1, + 'scalefactor' => 1, + 'scalefactor35efl' => 1, + 'scalingfactor' => 1, + 'scandate' => 1, + 'scannerfirmwaredate' => 1, + 'scannerfirmwareversion' => 1, + 'scannermake' => 1, + 'scannermodel' => 1, + 'scannerpixelsize' => 1, + 'scannerproductid' => 1, + 'scannerserialnumber' => 1, + 'scannervendorid' => 1, + 'scanoperatorid' => 1, + 'scansoftware' => 1, + 'scansoftwarerevisiondate' => 1, + 'scav11cols' => 1, + 'scav12cols' => 1, + 'scav21cols' => 1, + 'scav22cols' => 1, + 'sceneappearanceestimates' => 1, + 'scenebalancealgorithmcommand' => 1, + 'scenebalancealgorithmfilmid' => 1, + 'scenebalancealgorithmrevision' => 1, + 'scenecolorimetryestimates' => 1, + 'scheduleitemid' => 1, + 'schemeinfo' => 1, + 'schemetype' => 1, + 'schemeurl' => 1, + 'schemeversion' => 1, + 'school' => 1, + 'screenbuffersize' => 1, + 'screendescriptor' => 1, + 'screenheight' => 1, + 'screening' => 1, + 'screeningdesc' => 1, + 'screennail' => 1, + 'screenplayby' => 1, + 'screenwidth' => 1, + 'screenwindowcenter' => 1, + 'screenwindowwidth' => 1, + 'screenwriters' => 1, + 'script' => 1, + 'scriptcommand' => 1, + 'sdc' => 1, + 'sdcc-flp' => 1, + 'searchranking' => 1, + 'secondarygenre' => 1, + 'secondlanguage' => 1, + 'security' => 1, + 'securitylocalmetadataset' => 1, + 'securitysci-shiinformation' => 1, + 'securityversion' => 1, + 'seekable' => 1, + 'seekpoint' => 1, + 'seektable' => 1, + 'segheight' => 1, + 'segmentlocalset' => 1, + 'segwidth' => 1, + 'selection' => 1, + 'selectionduration' => 1, + 'selectiontime' => 1, + 'selobjbacktype' => 1, + 'selobjname' => 1, + 'selobjtype' => 1, + 'selobjversion' => 1, + 'semanticinstanceid' => 1, + 'semanticname' => 1, + 'sendduration' => 1, + 'senderaddress' => 1, + 'sendername' => 1, + 'sensitivity' => 1, + 'sensitivityvalue' => 1, + 'sensorbottomborder' => 1, + 'sensordata' => 1, + 'sensordefects' => 1, + 'sensorellipsoidheight' => 1, + 'sensorellipsoidheightextended' => 1, + 'sensorfieldofviewname' => 1, + 'sensorid' => 1, + 'sensorinfo' => 1, + 'sensorname' => 1, + 'sensorreadouttime' => 1, + 'sensorrelativeazimuthangle' => 1, + 'sensorrelativeelevationangle' => 1, + 'sensorrelativerollangle' => 1, + 'sensorrightborder' => 1, + 'sensorvelocityeast' => 1, + 'sensorvelocitynorth' => 1, + 'seqlevelidx0' => 1, + 'seqprofile' => 1, + 'seqtier0' => 1, + 'sequenceframenumber' => 1, + 'sequencenumberrandomoffset' => 1, + 'sequenceoffset' => 1, + 'serialinfo' => 1, + 'seriesuid' => 1, + 'serviceid' => 1, + 'serviceorganizationname' => 1, + 'setcookie' => 1, + 'setinfo' => 1, + 'setsubtitle' => 1, + 'settingsoffset' => 1, + 'seventhlanguage' => 1, + 'sfsboundary' => 1, + 'sglcoldcacthres1' => 1, + 'sglcoldcacthres2' => 1, + 'sglcoldcacthres3' => 1, + 'shadowendpoints' => 1, + 'shadowfilepath' => 1, + 'shadowsyncsampletable' => 1, + 'shakereductioninfo' => 1, + 'shared' => 1, + 'shareddata' => 1, + 'shareddoc' => 1, + 'sharedwith' => 1, + 'sharpenforthumbnail' => 1, + 'sharpeningon' => 1, + 'sharpinfo' => 1, + 'sharpmethod' => 1, + 'sharpnessapproximation' => 1, + 'sharpnessfreqtable' => 1, + 'sharpnesstable' => 1, + 'sharpobjbacktype' => 1, + 'sharpobjname' => 1, + 'sharpobjtype' => 1, + 'sharpobjversion' => 1, + 'sheetdisclosure' => 1, + 'shifttiming' => 1, + 'shimdata' => 1, + 'shootingcount' => 1, + 'shootingmenuoffset' => 1, + 'shootingrecord' => 1, + 'shootobjbacktype' => 1, + 'shootobjname' => 1, + 'shootobjtype' => 1, + 'shootobjversion' => 1, + 'shootsetup' => 1, + 'shotinfo' => 1, + 'shotinfo02xx' => 1, + 'shotinfod300a' => 1, + 'shotinfod300b' => 1, + 'shotinfod300s' => 1, + 'shotinfod3a' => 1, + 'shotinfod3b' => 1, + 'shotinfod3s' => 1, + 'shotinfod3x' => 1, + 'shotinfod4' => 1, + 'shotinfod40' => 1, + 'shotinfod4s' => 1, + 'shotinfod500' => 1, + 'shotinfod5000' => 1, + 'shotinfod5100' => 1, + 'shotinfod5200' => 1, + 'shotinfod6' => 1, + 'shotinfod610' => 1, + 'shotinfod700' => 1, + 'shotinfod7000' => 1, + 'shotinfod7500' => 1, + 'shotinfod780' => 1, + 'shotinfod80' => 1, + 'shotinfod800' => 1, + 'shotinfod810' => 1, + 'shotinfod850' => 1, + 'shotinfod90' => 1, + 'shotinfounknown' => 1, + 'shotinfoversion' => 1, + 'shotinfoz7ii' => 1, + 'shotinfoz8' => 1, + 'shotinfoz9' => 1, + 'showmode' => 1, + 'showobjects' => 1, + 'shutterangle' => 1, + 'shuttercurtainhack' => 1, + 'shutterspeed' => 1, + 'shutterspeeddisplayed' => 1, + 'sidebars' => 1, + 'sidelightstrength' => 1, + 'sideslipangle' => 1, + 'sigma' => 1, + 'sigmanoisefiltercaltablev1' => 1, + 'sigmanoisefiltertablev1' => 1, + 'sigmanoisefiltertablev1version' => 1, + 'sigmanoisethreshtablev2' => 1, + 'signalnumber' => 1, + 'signature' => 1, + 'signature_name' => 1, + 'signatureusagerights' => 1, + 'signercontactinfo' => 1, + 'significantbits' => 1, + 'signingauthority' => 1, + 'signingdate' => 1, + 'signinglocation' => 1, + 'signingreason' => 1, + 'signtype' => 1, + 'simpleindex' => 1, + 'simplicityprofile' => 1, + 'singlequality' => 1, + 'singleshotdepthmap' => 1, + 'singleshotdepthmaptiff' => 1, + 'singleshotmeta' => 1, + 'site' => 1, + 'siteenter' => 1, + 'siteexit' => 1, + 'siunits' => 1, + 'sixthlanguage' => 1, + 'size' => 1, + 'sizemode' => 1, + 'skip' => 1, + 'skipinfo' => 1, + 'slantrange' => 1, + 'slateinformation' => 1, + 'sliceinfo' => 1, + 'slicesgroupname' => 1, + 'slides' => 1, + 'slideshow' => 1, + 'smaxsamplevalue' => 1, + 'sminsamplevalue' => 1, + 'smpteformat' => 1, + 'smpteoffset' => 1, + 'snapshotid' => 1, + 'snapshotname' => 1, + 'soctemperature' => 1, + 'softedit' => 1, + 'softfocusfilter' => 1, + 'softwarecomponents' => 1, + 'softwarerelease' => 1, + 'sony' => 1, + 'sony_0x9416' => 1, + 'sonyfnumber2' => 1, + 'sonyrawfiletype' => 1, + 'sonytonecurve' => 1, + 'sortwith' => 1, + 'soundfile' => 1, + 'soundschemetitle' => 1, + 'sourcecreatedate' => 1, + 'sourcedata' => 1, + 'sourcedate' => 1, + 'sourceedition' => 1, + 'sourcefilename' => 1, + 'sourceform' => 1, + 'sourceid' => 1, + 'sourceimagedirectory' => 1, + 'sourceimagefilename' => 1, + 'sourceimageheight' => 1, + 'sourceimagevolumename' => 1, + 'sourceimagewidth' => 1, + 'sourcepublisher' => 1, + 'sourcerights' => 1, + 'sourcetitle' => 1, + 'sourceurl' => 1, + 'spacefree' => 1, + 'spaceused' => 1, + 'spatialaudio' => 1, + 'spatialaudioversion' => 1, + 'spatialorientation' => 1, + 'spatialresolution' => 1, + 'specialbuild' => 1, + 'specialeffectsopticalfilter' => 1, + 'specialfolderdata' => 1, + 'specificationversion' => 1, + 'spectralviewingconditions' => 1, + 'spectralwhitepoint' => 1, + 'speed' => 1, + 'sphericalvideo' => 1, + 'spiff' => 1, + 'spiffversion' => 1, + 'spinstrength' => 1, + 'spothalftone' => 1, + 'spouse' => 1, + 'sr2dataifd' => 1, + 'sr2private' => 1, + 'sr2subifdkey' => 1, + 'sr2subifdlength' => 1, + 'sr2subifdoffset' => 1, + 'srawtype' => 1, + 'srf2key' => 1, + 'srf6offset' => 1, + 'srfdataoffset' => 1, + 'stampinfo' => 1, + 'stamptoolcount' => 1, + 'standardtocustompcc' => 1, + 'standbymonitorofftime' => 1, + 'starring' => 1, + 'startdate' => 1, + 'startedgecode' => 1, + 'startofframe' => 1, + 'startreading' => 1, + 'starttime' => 1, + 'stateorprovince' => 1, + 'staticpressure' => 1, + 'stationcallsign' => 1, + 'stationname' => 1, + 'statistics' => 1, + 'stdautoactive' => 1, + 'stdbasename' => 1, + 'stdhotfolder' => 1, + 'stdopeninphotoshop' => 1, + 'stdoutputbitdepth' => 1, + 'stdoutputcolormode' => 1, + 'stdoutputfiletype' => 1, + 'stdoxygen' => 1, + 'stdsaveselection' => 1, + 'stdscaledoutput' => 1, + 'stdsharpenoutput' => 1, + 'stereo' => 1, + 'stereoimage' => 1, + 'stereoscopic3d' => 1, + 'stillimagetime' => 1, + 'stim' => 1, + 'stimversion' => 1, + 'stitchcolumns' => 1, + 'stitchinfo' => 1, + 'stitchrows' => 1, + 'stonits' => 1, + 'storage-streampathname' => 1, + 'storageformatdate' => 1, + 'storageformattime' => 1, + 'storagemodel' => 1, + 'storageserialnumber' => 1, + 'storagetype' => 1, + 'store' => 1, + 'stream' => 1, + 'streamavgbitrate' => 1, + 'streamavgpacketsize' => 1, + 'streambitdepth' => 1, + 'streambitrateprops' => 1, + 'streamcolor' => 1, + 'streamcount' => 1, + 'streamdata' => 1, + 'streamduration' => 1, + 'streamheader' => 1, + 'streamid' => 1, + 'streaminfo' => 1, + 'streamingdataprotocol' => 1, + 'streammaxbitrate' => 1, + 'streammaxpacketsize' => 1, + 'streammimelen' => 1, + 'streammimetype' => 1, + 'streamname' => 1, + 'streamnamelen' => 1, + 'streamnumber' => 1, + 'streampreroll' => 1, + 'streamprioritization' => 1, + 'streamproperties' => 1, + 'streamsamplecount' => 1, + 'streamsamplerate' => 1, + 'streamstarttime' => 1, + 'streamtypeinfo' => 1, + 'street' => 1, + 'stridable' => 1, + 'strikeout' => 1, + 'stripbytecounts' => 1, + 'stripoffsets' => 1, + 'striprowcounts' => 1, + 'strobe' => 1, + 'strobetime' => 1, + 'structuretype' => 1, + 'studio' => 1, + 'sub-sampleinformation' => 1, + 'subdialframeadvance' => 1, + 'subdialframeadvancerating0' => 1, + 'subdialframeadvancerating1' => 1, + 'subdialframeadvancerating2' => 1, + 'subdialframeadvancerating3' => 1, + 'subdialframeadvancerating4' => 1, + 'subdialframeadvancerating5' => 1, + 'subdir3000' => 1, + 'subdir3100' => 1, + 'subdir3400' => 1, + 'subdir3900' => 1, + 'subfile' => 1, + 'subifd' => 1, + 'subifd0' => 1, + 'subifd1' => 1, + 'subifd2' => 1, + 'subifd255' => 1, + 'subifd3' => 1, + 'subifd4' => 1, + 'subifd5' => 1, + 'subifd6' => 1, + 'subifd_offset' => 1, + 'subimagecolor' => 1, + 'subimagehdr' => 1, + 'subimageheight' => 1, + 'subimageicc_profile' => 1, + 'subimagenumericalformat' => 1, + 'subimageresolutions' => 1, + 'subimagetilecount' => 1, + 'subimagetileheight' => 1, + 'subimagetilewidth' => 1, + 'subimagewidth' => 1, + 'subjectpixelheight' => 1, + 'subjectpixelwidth' => 1, + 'subjectscale' => 1, + 'subjectunits' => 1, + 'subpacketh' => 1, + 'subpacketsize' => 1, + 'subscriptioncontentid' => 1, + 'subsystem' => 1, + 'subsystemversion' => 1, + 'subtileblocksize' => 1, + 'subtitledescription' => 1, + 'suffix' => 1, + 'suggestedpalette' => 1, + 'summary' => 1, + 'summaryinfo' => 1, + 'surfacemap' => 1, + 'surroundmode' => 1, + 'surroundshotvideo' => 1, + 'surroundshotvideoname' => 1, + 'svgversion' => 1, + 'swf' => 1, + 'sync01' => 1, + 'sync02' => 1, + 'sync03' => 1, + 'sync04' => 1, + 'sync05' => 1, + 'sync06' => 1, + 'sync07' => 1, + 'sync08' => 1, + 'sync09' => 1, + 'sync10' => 1, + 'sync11' => 1, + 'sync12' => 1, + 'sync13' => 1, + 'sync14' => 1, + 'sync15' => 1, + 'sync16' => 1, + 'synchronizedlyricsdescription' => 1, + 'synchronizedlyricstext' => 1, + 'synchronizedlyricstype' => 1, + 'synconly' => 1, + 'syncsampletable' => 1, + 'syncstate' => 1, + 'synlyrics' => 1, + 'synopsis' => 1, + 'systemtime' => 1, + 't4options' => 1, + 't6options' => 1, + 't82options' => 1, + 't88options' => 1, + 'tag2010a' => 1, + 'tag2010b' => 1, + 'tag2010c' => 1, + 'tag2010d' => 1, + 'tag2010e' => 1, + 'tag2010f' => 1, + 'tag2010g' => 1, + 'tag2010h' => 1, + 'tag2010i' => 1, + 'tag202a' => 1, + 'tag900b' => 1, + 'tag9050a' => 1, + 'tag9050b' => 1, + 'tag9050c' => 1, + 'tag9050d' => 1, + 'tag9400a' => 1, + 'tag9400b' => 1, + 'tag9400c' => 1, + 'tag9401' => 1, + 'tag9402' => 1, + 'tag9403' => 1, + 'tag9404a' => 1, + 'tag9404b' => 1, + 'tag9404c' => 1, + 'tag9405a' => 1, + 'tag9405b' => 1, + 'tag9406' => 1, + 'tag940a' => 1, + 'tag940c' => 1, + 'tag940e' => 1, + 'tag9416_0000' => 1, + 'taggedpdf' => 1, + 'taggingtime' => 1, + 'tailnumber' => 1, + 'take' => 1, + 'tamper-proofkeys' => 1, + 'targetaudiences' => 1, + 'targetdeltatype' => 1, + 'targeterrorestimatece90' => 1, + 'targeterrorestimatele90' => 1, + 'targetfiledosname' => 1, + 'targetfilesize' => 1, + 'targetlayerid' => 1, + 'targetlocationcovariancematrix' => 1, + 'targetlocationelevation' => 1, + 'targetlocationlatitude' => 1, + 'targetlocationlongitude' => 1, + 'targettrackgateheight' => 1, + 'targettrackgatewidth' => 1, + 'targetwidth' => 1, + 'targetwidthextended' => 1, + 'targetxy' => 1, + 'taskowner' => 1, + 'tattoo' => 1, + 'tcmediainfo' => 1, + 'tdat' => 1, + 'tdhd' => 1, + 'technician' => 1, + 'technology' => 1, + 'telephonenumber' => 1, + 'telescope' => 1, + 'telex' => 1, + 'temperature' => 1, + 'tempinfo' => 1, + 'template' => 1, + 'temporalidnested' => 1, + 'temporary' => 1, + 'terminationaction' => 1, + 'terminationcondition' => 1, + 'termsofuse' => 1, + 'testtarget' => 1, + 'testtransfertiming' => 1, + 'text' => 1, + 'textcolor' => 1, + 'textface' => 1, + 'textfont' => 1, + 'textinfo' => 1, + 'textinfo1' => 1, + 'textinfo2' => 1, + 'textjunk' => 1, + 'textsize' => 1, + 'texttospeech' => 1, + 'textualdata' => 1, + 'textualinfo' => 1, + 'textureformat' => 1, + 'thanksto' => 1, + 'theora' => 1, + 'theoraversion' => 1, + 'thermalcalibration' => 1, + 'thermaldata' => 1, + 'thermalparams' => 1, + 'thermalparams2' => 1, + 'thermalparams3' => 1, + 'thetasubdir' => 1, + 'things' => 1, + 'thirdlanguage' => 1, + 'thumbinfo' => 1, + 'thumbnail' => 1, + 'thumbnailbpg' => 1, + 'thumbnailclip' => 1, + 'thumbnailcompressiontable' => 1, + 'thumbnailexpansiontable' => 1, + 'thumbnailhash' => 1, + 'thumbnailref' => 1, + 'thumbnailresizeratio' => 1, + 'thumbnailtiff' => 1, + 'thumbnailurl' => 1, + 'tickspersecond' => 1, + 'tiff' => 1, + 'tiff-epstandardid' => 1, + 'tiff_fxextensions' => 1, + 'tiffmeteringimageheight' => 1, + 'tiffmeteringimagewidth' => 1, + 'tiffpreview' => 1, + 'tilebytecounts' => 1, + 'tiledepth' => 1, + 'tilegaindeterminationtable' => 1, + 'tileoffsets' => 1, + 'tiles' => 1, + 'timecode' => 1, + 'timecodeindex' => 1, + 'timecodeindexparms' => 1, + 'timeinfo' => 1, + 'timelineinfo' => 1, + 'timeoffset' => 1, + 'timereference' => 1, + 'timescale' => 1, + 'timestamplist' => 1, + 'timestamprandomoffset' => 1, + 'timetosampletable' => 1, + 'title2' => 1, + 'titlelen' => 1, + 'titlenum' => 1, + 'titleofparts' => 1, + 'titlesofparts' => 1, + 'titlesortorder' => 1, + 'tmdb' => 1, + 'toaddresses' => 1, + 'tocitems' => 1, + 'todotitle' => 1, + 'tomtomad' => 1, + 'tomtomhl' => 1, + 'tomtomid' => 1, + 'tomtommetadata' => 1, + 'tomtomvd' => 1, + 'tomtomvi' => 1, + 'tonames' => 1, + 'tonecurvematching' => 1, + 'tonecurvetable' => 1, + 'tonematrix' => 1, + 'toneobjbacktype' => 1, + 'toneobjname' => 1, + 'toneobjtype' => 1, + 'toneobjversion' => 1, + 'tones' => 1, + 'tonescale0' => 1, + 'tonescale0spline' => 1, + 'tonescale1' => 1, + 'tonescale1spline' => 1, + 'tonescale2' => 1, + 'tonescale2spline' => 1, + 'tonescale3' => 1, + 'tonescale3spline' => 1, + 'tonescale4' => 1, + 'tonescale4spline' => 1, + 'tonescale5' => 1, + 'tonescale5spline' => 1, + 'tonescale6' => 1, + 'tonescale6spline' => 1, + 'tonescale7' => 1, + 'tonescale7spline' => 1, + 'tonescale8' => 1, + 'tonescale8spline' => 1, + 'tonescale9' => 1, + 'tonescale9spline' => 1, + 'tonespaceflow' => 1, + 'tonscaletable' => 1, + 'tool_name' => 1, + 'tool_version' => 1, + 'toolname' => 1, + 'toolversion' => 1, + 'topdarkrow1' => 1, + 'topdarkrow2' => 1, + 'topmag' => 1, + 'topmargin' => 1, + 'totalbitrate' => 1, + 'totalbytes' => 1, + 'totalbytesnortpheaders' => 1, + 'totaldatarate' => 1, + 'totalduration' => 1, + 'totaleditingtime' => 1, + 'totaledittime' => 1, + 'totalfilesize' => 1, + 'totalframecount' => 1, + 'totalframes' => 1, + 'totalparts' => 1, + 'totalsamples' => 1, + 'totalsize' => 1, + 'toycamerafilter' => 1, + 'trackaperture' => 1, + 'trackcategory' => 1, + 'trackcomments' => 1, + 'trackduration' => 1, + 'trackerdata' => 1, + 'trackfragment' => 1, + 'trackheader' => 1, + 'trackheaderversion' => 1, + 'trackid' => 1, + 'trackingid' => 1, + 'tracklayer' => 1, + 'tracklyrics' => 1, + 'trackproperty' => 1, + 'trackref' => 1, + 'trackvolume' => 1, + 'trademark' => 1, + 'transactionid' => 1, + 'transfercharacteristic' => 1, + 'transfercharacteristics' => 1, + 'transferrange' => 1, + 'transfersequence' => 1, + 'transformcreatedate' => 1, + 'transformedimagetitle' => 1, + 'transformmodifydate' => 1, + 'transformnodeid' => 1, + 'transformparams' => 1, + 'transformtitle' => 1, + 'transparency' => 1, + 'transparencyindicator' => 1, + 'transparentcolor' => 1, + 'transparentindex' => 1, + 'transportstreamid' => 1, + 'trapindicator' => 1, + 'trashcolsright' => 1, + 'trashrowsbottom' => 1, + 'trim' => 1, + 'trueairspeed' => 1, + 'tty-ttdphone' => 1, + 'tungmat0' => 1, + 'tungmat1' => 1, + 'tungmat2' => 1, + 'tungmat3' => 1, + 'tungmat4' => 1, + 'tungmat5' => 1, + 'tungmat6' => 1, + 'tungmat7' => 1, + 'tungmat8' => 1, + 'tungmat9' => 1, + 'tuning' => 1, + 'tvdb' => 1, + 'twelvebit' => 1, + 'tx3g' => 1, + 'typeoforiginal' => 1, + 'typist' => 1, + 'uas_lsversionnumber' => 1, + 'uasdatalink' => 1, + 'ucrbg' => 1, + 'uic1tag' => 1, + 'uic2tag' => 1, + 'uic3tag' => 1, + 'uic4tag' => 1, + 'umid' => 1, + 'uncompressed' => 1, + 'uncompressedsize' => 1, + 'uncompressedtextlength' => 1, + 'underflowcolor' => 1, + 'underline' => 1, + 'underlineposition' => 1, + 'underlinethickness' => 1, + 'unicodealphanames' => 1, + 'uninitializeddatasize' => 1, + 'uniquefileidentifier' => 1, + 'uniqueid' => 1, + 'unknown' => 1, + 'unknown00' => 1, + 'unknown01' => 1, + 'unknown02' => 1, + 'unknown03' => 1, + 'unknown_cndb' => 1, + 'unknown_slmt' => 1, + 'unknownd30' => 1, + 'unknowndata' => 1, + 'unknownexif' => 1, + 'unknowninfo' => 1, + 'unknowninfo2' => 1, + 'unknowninfo2version' => 1, + 'unknowninfoifd' => 1, + 'unknowninfoversion' => 1, + 'unknownserial1' => 1, + 'unknownserial2' => 1, + 'unknownserial3' => 1, + 'unknownserial4' => 1, + 'unknowntemperature1' => 1, + 'unknowntemperature2' => 1, + 'unsharpdata' => 1, + 'unshiftednote' => 1, + 'untitled0' => 1, + 'untitled1' => 1, + 'untitled2' => 1, + 'unused' => 1, + 'unusedblackcolsleftin' => 1, + 'unusedblackcolsleftout' => 1, + 'unusedblackcolsrightin' => 1, + 'unusedblackcolsrightout' => 1, + 'unusedblackrowsbottomin' => 1, + 'unusedblackrowsbottomout' => 1, + 'unusedblackrowstopin' => 1, + 'unusedblackrowstopout' => 1, + 'updatedtitle' => 1, + 'ur3' => 1, + 'urllist1' => 1, + 'urn' => 1, + 'usagerightsmessage' => 1, + 'usedextensionnumbers' => 1, + 'useraccess' => 1, + 'useradjsba_rgbshifts' => 1, + 'usercustom1' => 1, + 'usercustom2' => 1, + 'userdata' => 1, + 'userdefinedid' => 1, + 'userdefinedtext' => 1, + 'userdefinedurl' => 1, + 'usereffectiverating' => 1, + 'userid' => 1, + 'userlastplayedtime' => 1, + 'usermetadata' => 1, + 'username' => 1, + 'userplaycount' => 1, + 'userplaycountafternoon' => 1, + 'userplaycountevening' => 1, + 'userplaycountmorning' => 1, + 'userplaycountnight' => 1, + 'userplaycountweekday' => 1, + 'userplaycountweekend' => 1, + 'userselectgrouptitle' => 1, + 'userservicerating' => 1, + 'usertext' => 1, + 'userweburl' => 1, + 'usingtransforms' => 1, + 'usmparametershigh' => 1, + 'usmparametershost' => 1, + 'usmparametershost3mp' => 1, + 'usmparametershost6mp' => 1, + 'usmparameterslow' => 1, + 'usmparametersmed' => 1, + 'usptomiscellaneous' => 1, + 'usptooriginalcontenttype' => 1, + 'utm' => 1, + 'uuid-c2paclaimsignature' => 1, + 'uuid-canon' => 1, + 'uuid-canon2' => 1, + 'uuid-exif' => 1, + 'uuid-exif2' => 1, + 'uuid-exif_bad' => 1, + 'uuid-flip' => 1, + 'uuid-geojp2' => 1, + 'uuid-iptc' => 1, + 'uuid-iptc2' => 1, + 'uuid-photoshop' => 1, + 'uuid-prof' => 1, + 'uuid-signature' => 1, + 'uuid-unknown' => 1, + 'uuid-usmt' => 1, + 'uuid-xmp' => 1, + 'uuidinfo' => 1, + 'uuidlist' => 1, + 'validafpoints' => 1, + 'validate' => 1, + 'value0' => 1, + 'value1' => 1, + 'value2' => 1, + 'value3' => 1, + 'vary' => 1, + 'vbrbytes' => 1, + 'vbrframes' => 1, + 'vbrpeak' => 1, + 'vbrscale' => 1, + 'vddimdacnominalvalues' => 1, + 'vegasversionmajor' => 1, + 'vegasversionminor' => 1, + 'vendorid' => 1, + 'vendorname' => 1, + 'vendorurl' => 1, + 'version1' => 1, + 'version2' => 1, + 'versionbf' => 1, + 'versionifd' => 1, + 'versioninfo' => 1, + 'versionrestrictions' => 1, + 'versionyear' => 1, + 'verticalaf-onbutton' => 1, + 'verticalcitation' => 1, + 'verticalcstype' => 1, + 'verticaldatum' => 1, + 'verticaldivergence' => 1, + 'verticalfieldofview' => 1, + 'verticalresolution' => 1, + 'verticalscale' => 1, + 'verticalspeed' => 1, + 'verticalunits' => 1, + 'video' => 1, + 'videoattributes' => 1, + 'videoavgbitrate' => 1, + 'videoavgframerate' => 1, + 'videocardgamma' => 1, + 'videoclosedcaptioning' => 1, + 'videocodecdescription' => 1, + 'videocodecid' => 1, + 'videocodecinfo' => 1, + 'videocodecname' => 1, + 'videoencoding' => 1, + 'videoformat' => 1, + 'videoframecount' => 1, + 'videofullrangeflag' => 1, + 'videoheader' => 1, + 'videoheight' => 1, + 'videomaxbitrate' => 1, + 'videomaxframerate' => 1, + 'videomode' => 1, + 'videoorientation' => 1, + 'videoprofile' => 1, + 'videoprofileversion' => 1, + 'videosampledesc' => 1, + 'videoscantype' => 1, + 'videosize' => 1, + 'videostreamtype' => 1, + 'videotimestamp' => 1, + 'videotrackid' => 1, + 'videowidth' => 1, + 'view' => 1, + 'viewable' => 1, + 'viewingconddesc' => 1, + 'viewingcondilluminant' => 1, + 'viewingcondilluminanttype' => 1, + 'viewingconditions' => 1, + 'viewingcondsurround' => 1, + 'viewtype' => 1, + 'vignettingcorr' => 1, + 'vignettingcorr2' => 1, + 'vignettingcorrunknown1' => 1, + 'vignettingcorrunknown2' => 1, + 'vignettingcorrversion' => 1, + 'vignettingparams' => 1, + 'vintagestrength' => 1, + 'virtualimageheight' => 1, + 'virtualimagewidth' => 1, + 'virtualpage' => 1, + 'virtualpageunits' => 1, + 'visibleoutputs' => 1, + 'vistaidlistdata' => 1, + 'visualflightruleshud' => 1, + 'vmaphandling' => 1, + 'vmcoldropthresh' => 1, + 'vmcolthresh' => 1, + 'vmnbands' => 1, + 'vmpatchreslimit' => 1, + 'vmpixthresh' => 1, + 'vmtidataset' => 1, + 'vmwsize' => 1, + 'volumeblockcount' => 1, + 'volumeblocksize' => 1, + 'volumecreatedate' => 1, + 'volumeeffectivedate' => 1, + 'volumeexpirationdate' => 1, + 'volumeid' => 1, + 'volumelabel' => 1, + 'volumelevelr128' => 1, + 'volumemodifydate' => 1, + 'volumename' => 1, + 'volumenormalization' => 1, + 'volumesetdiskcount' => 1, + 'volumesetdisknumber' => 1, + 'volumesetname' => 1, + 'volumesize' => 1, + 'vorbis' => 1, + 'vorbiscomment' => 1, + 'vorbisversion' => 1, + 'vp8bitstream' => 1, + 'vp8l' => 1, + 'vp8version' => 1, + 'vp8x' => 1, + 'vrd1' => 1, + 'vrd2' => 1, + 'vrdstamptool' => 1, + 'vrdversion' => 1, + 'vrinfo' => 1, + 'vrinfoversion' => 1, + 'vw96' => 1, + 'wangannotation' => 1, + 'wangtag1' => 1, + 'wangtag3' => 1, + 'wangtag4' => 1, + 'warning' => 1, + 'warpquadrilateral' => 1, + 'watched' => 1, + 'watercolorfilter' => 1, + 'watermark' => 1, + 'watermarktype' => 1, + 'watermarkurl' => 1, + 'wave' => 1, + 'wb_bluelevelauto' => 1, + 'wb_cfa0_leveldaylight' => 1, + 'wb_cfa1_leveldaylight' => 1, + 'wb_cfa2_leveldaylight' => 1, + 'wb_cfa3_leveldaylight' => 1, + 'wb_grblevels' => 1, + 'wb_grblevelsauto' => 1, + 'wb_grblevelsstandard' => 1, + 'wb_grgblevels' => 1, + 'wb_grgblevelsauto' => 1, + 'wb_grgblevelscloudy' => 1, + 'wb_grgblevelsdaylight' => 1, + 'wb_grgblevelsdaylightfluor' => 1, + 'wb_grgblevelsdaywhitefluor' => 1, + 'wb_grgblevelslivingroomwarmwhitefluor' => 1, + 'wb_grgblevelstungsten' => 1, + 'wb_grgblevelswarmwhitefluor' => 1, + 'wb_grgblevelswhitefluorescent' => 1, + 'wb_redlevelauto' => 1, + 'wb_rgbcoeffsdaylight' => 1, + 'wb_rgbcoeffsflash' => 1, + 'wb_rgbcoeffsfluorescent' => 1, + 'wb_rgbcoeffstungsten' => 1, + 'wb_rgblevelsasshot' => 1, + 'wb_rgblevelscustom' => 1, + 'wbadjdata' => 1, + 'wbblueasshot' => 1, + 'wbbluecloudy' => 1, + 'wbbluedaylight' => 1, + 'wbblueflash' => 1, + 'wbbluefluorescent' => 1, + 'wbbluetungsten' => 1, + 'wbgreenasshot' => 1, + 'wbgreencloudy' => 1, + 'wbgreendaylight' => 1, + 'wbgreenflash' => 1, + 'wbgreenfluorescent' => 1, + 'wbgreentungsten' => 1, + 'wbinfo' => 1, + 'wbinfo2' => 1, + 'wbinfoa100' => 1, + 'wblevels' => 1, + 'wbredasshot' => 1, + 'wbredcloudy' => 1, + 'wbreddaylight' => 1, + 'wbredflash' => 1, + 'wbredfluorescent' => 1, + 'wbredtungsten' => 1, + 'wbsettings' => 1, + 'wbsettings2' => 1, + 'wcsprofiles' => 1, + 'weaponfired' => 1, + 'weaponload' => 1, + 'webp_flags' => 1, + 'webpage' => 1, + 'weight' => 1, + 'whitebalancematching' => 1, + 'whitebalancergb' => 1, + 'whitebalancetable' => 1, + 'whiteluminance' => 1, + 'whitepointx' => 1, + 'whitepointy' => 1, + 'whitesample' => 1, + 'whitesamplebits' => 1, + 'whitesampleheight' => 1, + 'whitesampleleftborder' => 1, + 'whitesampletopborder' => 1, + 'whitesamplewidth' => 1, + 'wide' => 1, + 'widthbytes' => 1, + 'widthresolution' => 1, + 'wifirssi' => 1, + 'winddirection' => 1, + 'windoworigin' => 1, + 'windoworiginauto' => 1, + 'windowsdevmode' => 1, + 'windowsfilename' => 1, + 'windowsize' => 1, + 'windowtarget' => 1, + 'windspeed' => 1, + 'wm_collectiongroupid' => 1, + 'wm_collectionid' => 1, + 'wm_contentid' => 1, + 'wm_mediaclassprimaryid' => 1, + 'wm_mediaclasssecondaryid' => 1, + 'wm_provider' => 1, + 'wmadrcaveragereference' => 1, + 'wmadrcaveragetarget' => 1, + 'wmadrcpeakreference' => 1, + 'wmadrcpeaktarget' => 1, + 'wmcollectiongroupid' => 1, + 'wmcollectionid' => 1, + 'wmcontentid' => 1, + 'wmshadowfilesourcedrmtype' => 1, + 'wmshadowfilesourcefiletype' => 1, + 'word97' => 1, + 'worddocument' => 1, + 'words' => 1, + 'workflowurl' => 1, + 'workingdirectory' => 1, + 'workingpath' => 1, + 'worldtime' => 1, + 'worldtocamera' => 1, + 'worldtondc' => 1, + 'wpgversion' => 1, + 'wrapmodes' => 1, + 'writername' => 1, + 'writers' => 1, + 'writtenby' => 1, + 'wwsfamilyname' => 1, + 'wwssubfamilyname' => 1, + 'x' => 1, + 'xattrapplemaildatereceived' => 1, + 'xattrapplemaildatesent' => 1, + 'xattrapplemailisremoteattachment' => 1, + 'xattrfinderinfo' => 1, + 'xattrlastuseddate' => 1, + 'xattrmditemdownloadeddate' => 1, + 'xattrmditemfindercomment' => 1, + 'xattrmditemwherefroms' => 1, + 'xattrmdlabel' => 1, + 'xattrresourcefork' => 1, + 'xcfversion' => 1, + 'xclippathunits' => 1, + 'xheight' => 1, + 'xidiri' => 1, + 'xmag' => 1, + 'xmethod' => 1, + 'xmldata' => 1, + 'xmlfiletype' => 1, + 'xmlpackets' => 1, + 'xmp_profile' => 1, + 'xmpbj' => 1, + 'xmpdm' => 1, + 'xmpmm' => 1, + 'xmpnote' => 1, + 'xmpplus' => 1, + 'xmprights' => 1, + 'xmptpg' => 1, + 'xobject' => 1, + 'xoffset' => 1, + 'xp_dip_xml' => 1, + 'xtranslayout' => 1, + 'xylocation' => 1, + 'xyoffsetinfo' => 1, + 'yclippathunits' => 1, + 'ycrcbmatrix' => 1, + 'ylevel' => 1, + 'ymag' => 1, + 'ymethod' => 1, + 'yoffset' => 1, + 'ytarget' => 1, + 'zipbitflag' => 1, + 'zipcompressedsize' => 1, + 'zipcompression' => 1, + 'zipcrc' => 1, + 'zipfilecomment' => 1, + 'zipfilename' => 1, + 'zipmodifydate' => 1, + 'ziprequiredversion' => 1, + 'zipuncompressedsize' => 1, + 'zisrawversion' => 1, + 'zoom' => 1, + 'zoomedpreviewimage' => 1, + 'zoompos' => 1, + 'zoomposition' => 1, + 'zoomstrength' => 1, + 'zxif' => 1, +); + +# module names for writable Composite tags +my %compositeModules = ( + 'filenumber' => 'Image::ExifTool::Canon', + 'flash' => 'Image::ExifTool::XMP', + 'gpslatitude' => 'Image::ExifTool::GPS', + 'gpslongitude' => 'Image::ExifTool::GPS', + 'gpsposition' => 'Image::ExifTool::Exif', + 'jpgfromraw' => 'Image::ExifTool::Exif', + 'originaldecisiondata' => 'Image::ExifTool::Canon', + 'otherimage' => 'Image::ExifTool::Exif', + 'previewimage' => 'Image::ExifTool::Exif', + 'rotation' => 'Image::ExifTool::QuickTime', + 'subseccreatedate' => 'Image::ExifTool::Exif', + 'subsecdatetimeoriginal' => 'Image::ExifTool::Exif', + 'subsecmodifydate' => 'Image::ExifTool::Exif', + 'thumbnailimage' => 'Image::ExifTool::Exif', +); + +#++++ End automatically generated code ++++ + +my %specialStruct = ( + NAMESPACE => 1, + STRUCT_NAME => 1, + TYPE => 1, + NOTES => 1, + GROUPS => 1, +); + +# insert any user-defined tags into our tag lookup +if (%Image::ExifTool::UserDefined) { + my @userTables = sort keys %Image::ExifTool::UserDefined; + while (@userTables) { + my $table = shift @userTables; + AddTags($Image::ExifTool::UserDefined{$table}, $table); + } +} + +# insert any other queued tags from plug-in modules +if (@Image::ExifTool::pluginTags) { + my $args; + foreach $args (@Image::ExifTool::pluginTags) { + AddTags($$args[0], $$args[1]); + } + undef @Image::ExifTool::pluginTags; +} + + +#------------------------------------------------------------------------------ +# Add tag names to lookup corresponding to flattened XMP tags for all structure fields +# Inputs: 0) tag table ref for flattened tags, 1) tagID, 2) lowercase tag name, +# 3) tag ID list ref, 4) reference to list of lowercase tag names +# 5) table number in lookup, 6) tagInfo hash for parent structure +# 7) tag ID of top-level structure +# Notes: Used only for user-defined structures +sub AddFields($$$$$$;$$) +{ + my ($tagTablePtr, $tagID, $lcTag, $tagIDs, $lcTags, $tnum, $tagInfo, $baseID) = @_; + return if length($tagID) > 500; # avoid deep recursion + unless ($tagInfo) { + $tagInfo = $$tagTablePtr{$tagID}; + $baseID = $tagID; + } + my $strTable = $$tagInfo{Struct}; + unless (ref $strTable) { + my $strName = $strTable; + $strTable = $Image::ExifTool::UserDefined::xmpStruct{$strTable}; + $strTable or warn("No definition for structure '${strName}'\n"), return; + $$strTable{STRUCT_NAME} or $$strTable{STRUCT_NAME} = "XMP $strName"; + $$tagInfo{Struct} = $strTable; # replace name with hash ref + delete $$tagInfo{SubDirectory}; # deprecated use of SubDirectory in Struct tags + } + # inherit NAMESPACE from parent table if it doesn't exist + $$strTable{NAMESPACE} = $$tagTablePtr{NAMESPACE} unless exists $$strTable{NAMESPACE}; + my $field; + foreach $field (keys %$strTable) { + next if $specialStruct{$field}; + my $id = $tagID . ucfirst($field); + # use name of existing flattened tag if already defined + my $flatInfo = $$tagTablePtr{$id}; + my $fieldInfo = $$strTable{$field}; + my $flatName; + $flatName = $$fieldInfo{FlatName} if ref $fieldInfo eq 'HASH'; + $flatName or ($flatName = $field) =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters + my $lc = ($flatInfo and $$flatInfo{Name}) ? lc $$flatInfo{Name} : $lcTag . lc($flatName); + my $copyID = $baseID; + push @$tagIDs, \$copyID, $id; + push @$lcTags, $lc, $lc; + next unless ref $fieldInfo eq 'HASH' and $$fieldInfo{Struct}; + # recursively add flattened tags for all sub-fields + AddFields($tagTablePtr, $id, $lc, $tagIDs, $lcTags, $tnum, $fieldInfo, $baseID); + } +} + +#------------------------------------------------------------------------------ +# Add tags to the lookup (for user-defined tags, etc) +# Inputs: 0) tag table ref, 1) table name +my %tableNumHash; +sub AddTags($$) +{ + my ($tagTablePtr, $table) = @_; + + # generate lookup for table numbers if not done already + unless (%tableNumHash) { + my $tnum = 0; + my $tbl; + foreach $tbl (@tableList) { + $tableNumHash{$tbl} = $tnum++; + } + } + my (@moreTables, %moreTableRefs); + for (;;) { + my $tnum = $tableNumHash{$table}; + unless (defined $tnum) { + # add new table to list + $tnum = scalar @tableList; + push @tableList, $table; + } + my $tagID; + foreach $tagID (Image::ExifTool::TagTableKeys($tagTablePtr)) { + my $newInfo = $$tagTablePtr{$tagID}; + if (ref $newInfo eq 'HASH') { + $$newInfo{Name} or $$newInfo{Name} = Image::ExifTool::MakeTagName($tagID); + # use new ID of tag in Composite table in case it changed + $tagID = $$newInfo{NewTagID} if defined $$newInfo{NewTagID}; + } else { + $newInfo = $$tagTablePtr{$tagID} = { Name => $newInfo }; + } + my $lcTag = lc $$newInfo{Name}; + my (@lcTags, @tagIDs, $existed, $isFlat); + # if this is a structure, add all flattened tag names to lookup + if ($$newInfo{Struct}) { + my $lcFlat = $$newInfo{FlatName} ? lc($$newInfo{FlatName}) : $lcTag; + AddFields($tagTablePtr, $tagID, $lcFlat, \@tagIDs, \@lcTags, $tnum); + } + # add tags to lookup even though we don't know if they are writable + # (to save speed by not having to load the module) +Tags: for (;;) { + my $lookup = $tagLookup{$lcTag}; + $lookup or $lookup = $tagLookup{$lcTag} = { }; + if ($$lookup{$tnum}) { + my $le = $$lookup{$tnum}; + my $ids = (ref $le eq 'ARRAY') ? $le : [ $le ]; + if (ref $tagID) { + # a reference points to the tag ID of the base structure for + # a flattened tag. There must be only one of these, + # and it must come first. + unshift @$ids, $tagID unless ref $$ids[0]; + # only add new tag ID if it didn't already exist + } elsif (grep /^$tagID$/, @$ids) { + $existed = 1 unless $isFlat; + } else { + push @$ids, $tagID; + } + # only update lookup if there is now more than one entry + $$lookup{$tnum} = $ids if @$ids > 1; + } else { + $$lookup{$tnum} = $tagID; + } + last unless @tagIDs; + for (;;) { + $tagID = shift @tagIDs; + $lcTag = shift @lcTags; + last unless $$tagTablePtr{$tagID}; # don't waste time if it exists + last Tags unless @tagIDs; + } + $isFlat = 1; + } + # add any new subdirectory tables (unless done already) + next if $existed or not $$newInfo{SubDirectory}; + my $subTable = $$newInfo{SubDirectory}{TagTable}; + next unless $subTable and not defined $tableNumHash{$subTable}; + next if $moreTableRefs{$subTable}; + no strict 'refs'; + next unless %$subTable; + # save new table to process after this one + push @moreTables, $subTable; + $moreTableRefs{$subTable} = \%$subTable; + # save source table name so we can load it when necessary + $$subTable{SRC_TABLE} = $table; + } + $table = shift @moreTables or last; + $tagTablePtr = $moreTableRefs{$table}; + } +} + +#------------------------------------------------------------------------------ +# Return true if the tag exists +# Inputs: 0) tag name (case insensitive) +# Returns: true if tag exists +sub TagExists($) +{ + my $tag = lc($_[0]); + return 1 if $tagExists{$tag} or $tagLookup{$tag}; + return 0; +} + +#------------------------------------------------------------------------------ +# Find information for writable tags (case insensitive) +# Inputs: 0) tag name (case insensitive) +# Returns: Scalar context: tag info or false if none found +# List context: list of all matching tagInfo hashes +sub FindTagInfo($) +{ + local $_; + my $tag = shift; + my $lcTag = lc($tag); + my ($tableNum, @tagInfoList, @lookups); + my $lookup = $tagLookup{$lcTag}; + if (not $lookup and $lcTag =~ /[?*]/) { + # allow wildcards in tag name + my $pat = $lcTag; + $pat =~ s/\*/[-\\w]*/g; + $pat =~ s/\?/[-\\w]/g; + my @tags = grep(/^$pat$/, keys %tagLookup); + if (@tags) { + push @lookups, $tagLookup{$_} foreach sort @tags; + $lookup = shift @lookups; + } + } + while ($lookup) { + foreach $tableNum (sort { $a <=> $b } keys %$lookup) { + my $table = GetTagTable($tableList[$tableNum]); + my $le = $$lookup{$tableNum}; + my ($tagID, $tagIDs); + if (ref $le eq 'ARRAY') { + $tagIDs = $le; + # if first entry is a reference, it indicates that this is a + # flattened tag and refers to the tag ID of the containing top-level + # structure, so now is the time to generate the flattened tags + if (ref $$tagIDs[0]) { + # (remove from the lookup since we only need to do this once) + my $rootIDPtr = shift @$tagIDs; + require Image::ExifTool::XMP; + Image::ExifTool::XMP::AddFlattenedTags($table, $$rootIDPtr); + } + } else { + $tagIDs = [ $le ]; + } + foreach $tagID (@$tagIDs) { + my @infoList = GetTagInfoList($table,$tagID); + unless (@infoList) { + my $reload; + # call write proc if it exists in case it adds tags to the table + my $writeProc = $table->{WRITE_PROC}; + if ($writeProc) { + no strict 'refs'; + $reload = 1 if &$writeProc(); + } + # load module with composite tag if necessary + my $compMod = $compositeModules{$lcTag}; + $reload = 1 if $compMod and eval "require $compMod"; + @infoList = GetTagInfoList($table,$tagID) if $reload; + } + if (@infoList == 1) { + push @tagInfoList, @infoList; + } elsif (@infoList > 1) { + my $tagInfo; + # must check tag list in case tags have different names + foreach $tagInfo (@infoList) { + next unless $lcTag eq lc($$tagInfo{Name}); + push @tagInfoList, $tagInfo; + } + } + } + } + $lookup = shift @lookups; + } + if (wantarray) { + return @tagInfoList; + } else { + return $tagInfoList[0]; + } +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::TagLookup - Fast lookup for ExifTool tags + +=head1 SYNOPSIS + +This module is required by Image::ExifTool for writing tags. + +=head1 DESCRIPTION + +Provides a fast, case insensitive lookup for tag names. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::BuildTagLookup(3pm)|Image::ExifTool::BuildTagLookup>, +L<Image::ExifTool::TagNames(3pm)|Image::ExifTool::TagNames> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/TagNames.pod b/ExifTool/lib/Image/ExifTool/TagNames.pod new file mode 100644 index 0000000..25b2093 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/TagNames.pod @@ -0,0 +1,39741 @@ + +=head1 NAME + +Image::ExifTool::TagNames - ExifTool tag name documentation + +=head1 DESCRIPTION + +This document contains a complete list of ExifTool tag names, organized into +tables based on information type. Tag names are used to reference specific +meta information extracted from or written to a file. + +=head1 TAG TABLES + +The tables listed below give the names of all tags recognized by ExifTool. +They contain a total of 26993 tags, with 16973 unique tag names. + +B<Tag ID>, B<Index#> or B<Sequence> is given in the first column of each +table. A B<Tag ID> is the computer-readable equivalent of a tag name, and +is the identifier that is actually stored in the file. B<Index#> refers to +the offset of a value when found at a fixed position within a data block +(B<#> is the multiplier for calculating a byte offset: B<1>, B<2>, B<4> or +B<8>). These offsets may have a decimal part which is used only to +differentiate tags with values stored at the same position. (Note that +writable tags within binary data blocks are not individually deletable, +and the usual alternative is to set them to a value of zero.) B<Sequence> +gives the order of values for a serial data stream. + +A B<Tag Name> is the handle by which the information is accessed in +ExifTool. In some instances, more than one name may correspond to a single +tag ID. In these cases, the actual name used depends on the context in +which the information is found. Valid characters in a tag name are A-Z, +a-z, 0-9, hyphen (-) and underline (_). Case is not significant. A +question mark (C<?>) after a tag name indicates that the information is +either not understood, not verified, or not very useful -- these tags are +not extracted by ExifTool unless the Unknown (-u) option is enabled. Be +aware that some tag names are different than the descriptions printed out by +default when extracting information with exiftool. To see the tag names +instead of the descriptions, use C<exiftool -s>. + +The B<Writable> column indicates whether the tag is writable by ExifTool. +Anything but a C<no> in this column means the tag is writable. A C<yes> +indicates writable information that is either unformatted or written using +the existing format. Other expressions give details about the format of the +stored value, and vary depending on the general type of information. The +format name may be followed by a number in square brackets to indicate the +number of values written, or the number of characters in a fixed-length +string (including a null terminator which is added if required). + +A plus sign (C<+>) after an entry in the B<Writable> column indicates a +I<List> tag which supports multiple values and allows individual values to +be added and deleted. A slash (C</>) indicates a tag that ExifTool will +I<Avoid> when writing. These will be edited but not created if another +same-named tag may be created instead. To create these tags, the group +should be specified. A tilde (C<~>) indicates a tag this is writable only +when the print conversion is disabled (by setting PrintConv to 0, using the +-n option, or suffixing the tag name with a C<#> character). An exclamation +point (C<!>) indicates a tag that is considered I<Unsafe> to write under +normal circumstances. These tags are not written unless specified +explicitly (ie. not when wildcards or "all" are used), and care should be +taken when editing them manually since they may affect the way an image is +rendered. An asterisk (C<*>) indicates a I<Protected> tag which is not +writable directly, but is written automatically by ExifTool (often when a +corresponding L<Composite|Image::ExifTool::TagNames/Composite Tags> or +L<Extra|Image::ExifTool::TagNames/Extra Tags> tag is written). A colon +(C<:>) indicates a I<Mandatory> tag which may be added automatically when +writing. Normally MakerNotes tags may not be deleted individually, but a +caret (C<^>) indicates a I<Deletable> MakerNotes tag. + +The HTML version of these tables also lists possible B<Values> for +discrete-valued tags, as well as B<Notes> for some tags. The B<Values> are +listed with the computer-readable values on the left of the equals sign +(C<=>), and the human-readable values on the right. The human-readable +values are used by default when reading and writing, but the +computer-readable values may be accessed by disabling the value conversion +with the -n option on the command line, by setting the PrintConv option to 0 +in the API, or or on a per-tag basis by adding a hash (C<#>) after the tag +name. + +B<Note>: If you are familiar with common meta-information tag names, you may +find that some ExifTool tag names are different than expected. The usual +reason for this is to make the tag names more consistent across different +types of meta information. To determine a tag name, either consult this +documentation or run C<exiftool -s> on a file containing the information in +question. + +I<(This documentation is the result of decades of research, testing and +reverse engineering, and is the most complete metadata tag list available +anywhere on the internet. It is provided not only for ExifTool users, but +more importantly as a public service to help augment the collective +knowledge, and is often used as a primary source of information in the +development of other metadata software. Please help keep this documentation +as accurate and complete as possible, and feed any new discoveries back to +ExifTool. A big thanks to everyone who has helped with this so far!)> + +=head2 JPEG Tags + +This table lists information extracted by ExifTool from JPEG images. See +L<https://www.w3.org/Graphics/JPEG/jfif3.pdf> for the JPEG specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'APP0' JFIF JFIF + JFXX JFIF Extension + CIFF CanonRaw + AVI1 JPEG AVI1 + Ocad JPEG Ocad + 'APP1' EXIF EXIF + ExtendedXMP XMP + XMP XMP + QVCI Casio QVCI + FLIR FLIR FFF + RawThermalImage no + 'APP2' ICC_Profile ICC_Profile + FPXR FlashPix + MPF MPF + InfiRayVersion InfiRay Version + PreviewImage no + 'APP3' Meta Kodak Meta + Stim Stim + JPS JPEG JPS + ThermalData no + ImagingData no + PreviewImage no + 'APP4' Scalado Scalado + FPXR FlashPix + InfiRayFactory InfiRay Factory + ThermalParams DJI ThermalParams + ThermalParams2 DJI ThermalParams2 + ThermalParams3 DJI ThermalParams3 + PreviewImage no + 'APP5' RMETA Ricoh RMETA + SamsungUniqueID Samsung APP5 + InfiRayPicture InfiRay Picture + ThermalCalibration no + PreviewImage no + 'APP6' EPPIM JPEG EPPIM + NITF JPEG NITF + HP_TDHD HP TDHD + GoPro GoPro GPMF + InfiRayMixMode InfiRay MixMode + DJI_DTAT no + 'APP7' Pentax Pentax + Huawei Unknown + Qualcomm Qualcomm + InfiRayOpMode InfiRay OpMode + DJI-DBG DJI Info + 'APP8' SPIFF JPEG SPIFF + InfiRayIsothermal InfiRay Isothermal + 'APP9' MediaJukebox JPEG MediaJukebox + InfiRaySensor InfiRay Sensor + 'APP10' Comment no + 'APP11' JPEG-HDR JPEG HDR + JUMBF Jpeg2000 + 'APP12' PictureInfo APP12 PictureInfo + Ducky APP12 Ducky + 'APP13' Photoshop Photoshop + Adobe_CM JPEG AdobeCM + 'APP14' Adobe JPEG Adobe + 'APP15' GraphicConverter JPEG GraphConv + 'COM' Comment yes + 'DQT' DefineQuantizationTable no + 'SOF' StartOfFrame JPEG SOF + 'Trailer' AFCP AFCP + CanonVRD CanonVRD + FotoStation FotoStation + PhotoMechanic PhotoMechanic + MIE MIE + Samsung Samsung Trailer + EmbeddedVideo no + Insta360 no + NikonApp no + PreviewImage yes + +=head3 JPEG AVI1 Tags + +This information may be found in APP0 of JPEG image data from AVI videos. + + Index1 Tag Name Writable + ------ -------- -------- + 0 InterleavedField no + +=head3 JPEG Ocad Tags + +Tags extracted from the JPEG APP0 "Ocad" segment (found in Photobucket +images). + + Tag ID Tag Name Writable + ------ -------- -------- + 'Rev' OcadRevision no + +=head3 JPEG JPS Tags + +Tags found in JPEG Stereo (JPS) images. + + Index1 Tag Name Writable + ------ -------- -------- + 10 JPSSeparation no + 11 JPSFlags no + 12 JPSLayout no + 13 JPSType no + 16 JPSComment no + +=head3 JPEG EPPIM Tags + +APP6 is used in by the Toshiba PDR-M700 to store a TIFF structure containing +PrintIM information. + + Tag ID Tag Name Writable + ------ -------- -------- + 0xc4a5 PrintIM PrintIM + +=head3 JPEG NITF Tags + +Information in APP6 used by the National Imagery Transmission Format. See +L<http://www.gwg.nga.mil/ntb/baseline/docs/n010697/bwcguide25aug98.pdf> for +the official specification. + + Index1 Tag Name Writable + ------ -------- -------- + 0 NITFVersion no + 2 ImageFormat no + 3 BlocksPerRow no + 5 BlocksPerColumn no + 7 ImageColor no + 8 BitDepth no + 9 ImageClass no + 10 JPEGProcess no + 11 Quality no + 12 StreamColor no + 13 StreamBitDepth no + 14 Flags no + +=head3 JPEG SPIFF Tags + +This information is found in APP8 of SPIFF-style JPEG images (the "official" +yet rarely used JPEG file format standard: Still Picture Interchange File +Format). See L<http://www.jpeg.org/public/spiff.pdf> for the official +specification. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SPIFFVersion no + 2 ProfileID no + 3 ColorComponents no + 6 ImageHeight no + 10 ImageWidth no + 14 ColorSpace no + 15 BitsPerSample no + 16 Compression no + 17 ResolutionUnit no + 18 YResolution no + 22 XResolution no + +=head3 JPEG MediaJukebox Tags + +Tags found in the XML metadata of the APP9 "Media Jukebox" segment. + + Tag Name Writable + -------- -------- + Album no + Caption no + Date no + Keywords no + Name no + People no + Places no + Tool_Name no + Tool_Version no + +=head3 JPEG HDR Tags + +Information extracted from APP11 of a JPEG-HDR image. + + Tag ID Tag Name Writable + ------ -------- -------- + 'RatioImage' RatioImage no + 'alp' Alpha no + 'bet' Beta no + 'cor' CorrectionMethod no + 'ln0' Ln0 no + 'ln1' Ln1 no + 's2n' S2n no + 'ver' JPEG-HDRVersion no + +=head3 JPEG AdobeCM Tags + +The APP13 "Adobe_CM" segment presumably contains color management +information, but the meaning of the data is currently unknown. If anyone +has an idea about what this means, please let me know. + + Index2 Tag Name Writable + ------ -------- -------- + 0 AdobeCMType no + +=head3 JPEG Adobe Tags + +The APP14 "Adobe" segment stores image encoding information for DCT filters. +This segment may be copied or deleted as a block using the Extra "Adobe" +tag, but note that it is not deleted by default when deleting all metadata +because it may affect the appearance of the image. + + Index2 Tag Name Writable + ------ -------- -------- + 0 DCTEncodeVersion no + 1 APP14Flags0 no + 2 APP14Flags1 no + 3 ColorTransform no + +=head3 JPEG GraphConv Tags + +APP15 is used by GraphicConverter to store JPEG quality. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Q' Quality no + +=head3 JPEG SOF Tags + +This information is extracted from the JPEG Start Of Frame segment. + + Tag Name Writable + -------- -------- + BitsPerSample no + ColorComponents no + EncodingProcess no + ImageHeight no + ImageWidth no + YCbCrSubSampling no + +=head2 EXIF Tags + +EXIF stands for "Exchangeable Image File Format". This type of information +is formatted according to the TIFF specification, and may be found in JPG, +TIFF, PNG, JP2, PGF, MIFF, HDP, PSP and XCF images, as well as many +TIFF-based RAW images, and even some AVI and MOV videos. + +The EXIF meta information is organized into different Image File Directories +(IFD's) within an image. The names of these IFD's correspond to the +ExifTool family 1 group names. When writing EXIF information, the default +B<Group> listed below is used unless another group is specified. + +Mandatory tags (indicated by a colon after the B<Writable> type) may be +added automatically with default values when creating a new IFD, and the IFD +is removed automatically when deleting tags if only default-valued mandatory +tags remain. + +The table below lists all EXIF tags. Also listed are TIFF, DNG, HDP and +other tags which are not part of the EXIF specification, but may co-exist +with EXIF tags in some images. Tags which are part of the EXIF 2.32 +specification have an underlined B<Tag Name> in the HTML version of this +documentation. See +L<https://web.archive.org/web/20190624045241if_/http://www.cipa.jp:80/std/documents/e/DC-008-Translation-2019-E.pdf> +for the official EXIF 2.32 specification. + + Tag ID Tag Name Group Writable + ------ -------- ----- -------- + 0x0001 InteropIndex InteropIFD string! + 0x0002 InteropVersion InteropIFD undef!: + 0x000b ProcessingSoftware IFD0 string + 0x00fe SubfileType IFD0 int32u! + 0x00ff OldSubfileType IFD0 int16u! + 0x0100 ImageWidth IFD0 int32u! + 0x0101 ImageHeight IFD0 int32u! + 0x0102 BitsPerSample IFD0 int16u[n]! + 0x0103 Compression IFD0 int16u!: + 0x0106 PhotometricInterpretation IFD0 int16u! + 0x0107 Thresholding IFD0 int16u! + 0x0108 CellWidth IFD0 int16u! + 0x0109 CellLength IFD0 int16u! + 0x010a FillOrder IFD0 int16u! + 0x010d DocumentName IFD0 string + 0x010e ImageDescription IFD0 string + 0x010f Make IFD0 string + 0x0110 Model IFD0 string + 0x0111 StripOffsets - no + OtherImageStart - no + StripOffsets - no + PreviewImageStart IFD0 int32u* + PreviewImageStart All int32u* + JpgFromRawStart SubIFD2 int32u* + 0x0112 Orientation IFD0 int16u + 0x0115 SamplesPerPixel IFD0 int16u! + 0x0116 RowsPerStrip IFD0 int32u! + 0x0117 StripByteCounts - no + OtherImageLength - no + StripByteCounts - no + PreviewImageLength IFD0 int32u* + PreviewImageLength All int32u* + JpgFromRawLength SubIFD2 int32u* + 0x0118 MinSampleValue IFD0 int16u + 0x0119 MaxSampleValue IFD0 int16u + 0x011a XResolution IFD0 rational64u: + 0x011b YResolution IFD0 rational64u: + 0x011c PlanarConfiguration IFD0 int16u! + 0x011d PageName IFD0 string + 0x011e XPosition IFD0 rational64u + 0x011f YPosition IFD0 rational64u + 0x0120 FreeOffsets - no + 0x0121 FreeByteCounts - no + 0x0122 GrayResponseUnit IFD0 int16u + 0x0123 GrayResponseCurve - no + 0x0124 T4Options - no + 0x0125 T6Options - no + 0x0128 ResolutionUnit IFD0 int16u: + 0x0129 PageNumber IFD0 int16u[2] + 0x012c ColorResponseUnit - no + 0x012d TransferFunction IFD0 int16u[768]! + 0x0131 Software IFD0 string + 0x0132 ModifyDate IFD0 string + 0x013b Artist IFD0 string + 0x013c HostComputer IFD0 string + 0x013d Predictor IFD0 int16u! + 0x013e WhitePoint IFD0 rational64u[2] + 0x013f PrimaryChromaticities IFD0 rational64u[6] + 0x0140 ColorMap - no + 0x0141 HalftoneHints IFD0 int16u[2] + 0x0142 TileWidth IFD0 int32u! + 0x0143 TileLength IFD0 int32u! + 0x0144 TileOffsets - no + 0x0145 TileByteCounts - no + 0x0146 BadFaxLines - no + 0x0147 CleanFaxData - no + 0x0148 ConsecutiveBadFaxLines - no + 0x014a SubIFD - EXIF + A100DataOffset IFD0 no + 0x014c InkSet IFD0 int16u + 0x014d InkNames - no + 0x014e NumberofInks - no + 0x0150 DotRange - no + 0x0151 TargetPrinter IFD0 string + 0x0152 ExtraSamples - no + 0x0153 SampleFormat SubIFD no + 0x0154 SMinSampleValue - no + 0x0155 SMaxSampleValue - no + 0x0156 TransferRange - no + 0x0157 ClipPath - no + 0x0158 XClipPathUnits - no + 0x0159 YClipPathUnits - no + 0x015a Indexed - no + 0x015b JPEGTables - no + 0x015f OPIProxy - no + 0x0190 GlobalParametersIFD - EXIF + 0x0191 ProfileType - no + 0x0192 FaxProfile - no + 0x0193 CodingMethods - no + 0x0194 VersionYear - no + 0x0195 ModeNumber - no + 0x01b1 Decode - no + 0x01b2 DefaultImageColor - no + 0x01b3 T82Options - no + 0x01b5 JPEGTables - no + 0x0200 JPEGProc - no + 0x0201 ThumbnailOffset IFD1 int32u* + ThumbnailOffset IFD0 int32u* + ThumbnailOffset SubIFD int32u* + PreviewImageStart MakerNotes int32u* + PreviewImageStart IFD0 int32u* + JpgFromRawStart SubIFD int32u* + JpgFromRawStart IFD2 int32u* + OtherImageStart SubIFD1 int32u* + OtherImageStart SubIFD2 int32u* + OtherImageStart - no + 0x0202 ThumbnailLength IFD1 int32u* + ThumbnailLength IFD0 int32u* + ThumbnailLength SubIFD int32u* + PreviewImageLength MakerNotes int32u* + PreviewImageLength IFD0 int32u* + JpgFromRawLength SubIFD int32u* + JpgFromRawLength IFD2 int32u* + OtherImageLength SubIFD1 int32u* + OtherImageLength SubIFD2 int32u* + OtherImageLength - no + 0x0203 JPEGRestartInterval - no + 0x0205 JPEGLosslessPredictors - no + 0x0206 JPEGPointTransforms - no + 0x0207 JPEGQTables - no + 0x0208 JPEGDCTables - no + 0x0209 JPEGACTables - no + 0x0211 YCbCrCoefficients IFD0 rational64u[3]! + 0x0212 YCbCrSubSampling IFD0 int16u[2]! + 0x0213 YCbCrPositioning IFD0 int16u!: + 0x0214 ReferenceBlackWhite IFD0 rational64u[6] + 0x022f StripRowCounts - no + 0x02bc ApplicationNotes IFD0 XMP + 0x03e7 USPTOMiscellaneous - no + 0x1000 RelatedImageFileFormat InteropIFD string! + 0x1001 RelatedImageWidth InteropIFD int16u! + 0x1002 RelatedImageHeight InteropIFD int16u! + 0x4746 Rating IFD0 int16u/ + 0x4747 XP_DIP_XML - no + 0x4748 StitchInfo - Microsoft Stitch + 0x4749 RatingPercent IFD0 int16u/ + 0x7000 SonyRawFileType - no + 0x7010 SonyToneCurve - no + 0x7031 VignettingCorrection SubIFD int16s! + 0x7032 VignettingCorrParams SubIFD int16s[17]! + 0x7034 ChromaticAberrationCorrection SubIFD int16s! + 0x7035 ChromaticAberrationCorrParams SubIFD int16s[33]! + 0x7036 DistortionCorrection SubIFD int16s! + 0x7037 DistortionCorrParams SubIFD int16s[17]! + 0x7038 SonyRawImageSize SubIFD int32u[2]! + 0x7310 BlackLevel SubIFD int16u[4]! + 0x7313 WB_RGGBLevels SubIFD int16s[4]! + 0x74c7 SonyCropTopLeft SubIFD int32u[2]! + 0x74c8 SonyCropSize SubIFD int32u[2]! + 0x800d ImageID - no + 0x80a3 WangTag1 - no + 0x80a4 WangAnnotation - no + 0x80a5 WangTag3 - no + 0x80a6 WangTag4 - no + 0x80b9 ImageReferencePoints - no + 0x80ba RegionXformTackPoint - no + 0x80bb WarpQuadrilateral - no + 0x80bc AffineTransformMat - no + 0x80e3 Matteing - no + 0x80e4 DataType - no + 0x80e5 ImageDepth - no + 0x80e6 TileDepth - no + 0x8214 ImageFullWidth - no + 0x8215 ImageFullHeight - no + 0x8216 TextureFormat - no + 0x8217 WrapModes - no + 0x8218 FovCot - no + 0x8219 MatrixWorldToScreen - no + 0x821a MatrixWorldToCamera - no + 0x827d Model2 - no + 0x828d CFARepeatPatternDim SubIFD int16u[2]! + 0x828e CFAPattern2 SubIFD int8u[n]! + 0x828f BatteryLevel - no + 0x8290 KodakIFD - Kodak IFD + 0x8298 Copyright IFD0 string + 0x829a ExposureTime ExifIFD rational64u + 0x829d FNumber ExifIFD rational64u + 0x82a5 MDFileTag - no + 0x82a6 MDScalePixel - no + 0x82a7 MDColorTable - no + 0x82a8 MDLabName - no + 0x82a9 MDSampleInfo - no + 0x82aa MDPrepDate - no + 0x82ab MDPrepTime - no + 0x82ac MDFileUnits - no + 0x830e PixelScale IFD0 double[3] + 0x8335 AdventScale - no + 0x8336 AdventRevision - no + 0x835c UIC1Tag - no + 0x835d UIC2Tag - no + 0x835e UIC3Tag - no + 0x835f UIC4Tag - no + 0x83bb IPTC-NAA IFD0 IPTC + 0x847e IntergraphPacketData - no + 0x847f IntergraphFlagRegisters - no + 0x8480 IntergraphMatrix IFD0 double[n] + 0x8481 INGRReserved - no + 0x8482 ModelTiePoint IFD0 double[n] + 0x84e0 Site - no + 0x84e1 ColorSequence - no + 0x84e2 IT8Header - no + 0x84e3 RasterPadding - no + 0x84e4 BitsPerRunLength - no + 0x84e5 BitsPerExtendedRunLength - no + 0x84e6 ColorTable - no + 0x84e7 ImageColorIndicator - no + 0x84e8 BackgroundColorIndicator - no + 0x84e9 ImageColorValue - no + 0x84ea BackgroundColorValue - no + 0x84eb PixelIntensityRange - no + 0x84ec TransparencyIndicator - no + 0x84ed ColorCharacterization - no + 0x84ee HCUsage - no + 0x84ef TrapIndicator - no + 0x84f0 CMYKEquivalent - no + 0x8546 SEMInfo IFD0 string + 0x8568 AFCP_IPTC - IPTC + 0x85b8 PixelMagicJBIGOptions - no + 0x85d7 JPLCartoIFD - no + 0x85d8 ModelTransform IFD0 double[16] + 0x8602 WB_GRGBLevels - no + 0x8606 LeafData - Leaf + 0x8649 PhotoshopSettings IFD0 Photoshop + 0x8769 ExifOffset IFD0 EXIF + 0x8773 ICC_Profile IFD0 ICC_Profile + 0x877f TIFF_FXExtensions - no + 0x8780 MultiProfiles - no + 0x8781 SharedData - no + 0x8782 T88Options - no + 0x87ac ImageLayer - no + 0x87af GeoTiffDirectory IFD0 int16u[0.5] + 0x87b0 GeoTiffDoubleParams IFD0 double[0.125] + 0x87b1 GeoTiffAsciiParams IFD0 string + 0x87be JBIGOptions - no + 0x8822 ExposureProgram ExifIFD int16u + 0x8824 SpectralSensitivity ExifIFD string + 0x8825 GPSInfo IFD0 GPS + 0x8827 ISO ExifIFD int16u[n] + 0x8828 Opto-ElectricConvFactor - no + 0x8829 Interlace - no + 0x882a TimeZoneOffset ExifIFD int16s[n] + 0x882b SelfTimerMode ExifIFD int16u + 0x8830 SensitivityType ExifIFD int16u + 0x8831 StandardOutputSensitivity ExifIFD int32u + 0x8832 RecommendedExposureIndex ExifIFD int32u + 0x8833 ISOSpeed ExifIFD int32u + 0x8834 ISOSpeedLatitudeyyy ExifIFD int32u + 0x8835 ISOSpeedLatitudezzz ExifIFD int32u + 0x885c FaxRecvParams - no + 0x885d FaxSubAddress - no + 0x885e FaxRecvTime - no + 0x8871 FedexEDR - no + 0x888a LeafSubIFD - Leaf SubIFD + 0x9000 ExifVersion ExifIFD undef: + 0x9003 DateTimeOriginal ExifIFD string + 0x9004 CreateDate ExifIFD string + 0x9009 GooglePlusUploadCode ExifIFD undef[n] + 0x9010 OffsetTime ExifIFD string + 0x9011 OffsetTimeOriginal ExifIFD string + 0x9012 OffsetTimeDigitized ExifIFD string + 0x9101 ComponentsConfiguration ExifIFD undef[4]!: + 0x9102 CompressedBitsPerPixel ExifIFD rational64u! + 0x9201 ShutterSpeedValue ExifIFD rational64s + 0x9202 ApertureValue ExifIFD rational64u + 0x9203 BrightnessValue ExifIFD rational64s + 0x9204 ExposureCompensation ExifIFD rational64s + 0x9205 MaxApertureValue ExifIFD rational64u + 0x9206 SubjectDistance ExifIFD rational64u + 0x9207 MeteringMode ExifIFD int16u + 0x9208 LightSource ExifIFD int16u + 0x9209 Flash ExifIFD int16u + 0x920a FocalLength ExifIFD rational64u + 0x920b FlashEnergy - no + 0x920c SpatialFrequencyResponse - no + 0x920d Noise - no + 0x920e FocalPlaneXResolution - no + 0x920f FocalPlaneYResolution - no + 0x9210 FocalPlaneResolutionUnit - no + 0x9211 ImageNumber ExifIFD int32u + 0x9212 SecurityClassification ExifIFD string + 0x9213 ImageHistory ExifIFD string + 0x9214 SubjectArea ExifIFD int16u[n] + 0x9215 ExposureIndex - no + 0x9216 TIFF-EPStandardID - no + 0x9217 SensingMethod - no + 0x923a CIP3DataFile - no + 0x923b CIP3Sheet - no + 0x923c CIP3Side - no + 0x923f StoNits - no + 0x927c MakerNoteApple ExifIFD Apple + MakerNoteNikon ExifIFD Nikon + MakerNoteCanon ExifIFD Canon + MakerNoteCasio ExifIFD Casio + MakerNoteCasio2 ExifIFD Casio Type2 + MakerNoteDJIInfo ExifIFD DJI Info + MakerNoteDJI ExifIFD DJI + MakerNoteFLIR ExifIFD FLIR + MakerNoteFujiFilm ExifIFD FujiFilm + MakerNoteGE ExifIFD GE + MakerNoteGE2 ExifIFD FujiFilm + MakerNoteHasselblad ExifIFD Unknown + MakerNoteHP ExifIFD HP + MakerNoteHP2 ExifIFD HP Type2 + MakerNoteHP4 ExifIFD HP Type4 + MakerNoteHP6 ExifIFD HP Type6 + MakerNoteISL ExifIFD Unknown + MakerNoteJVC ExifIFD JVC + MakerNoteJVCText ExifIFD JVC Text + MakerNoteKodak1a ExifIFD Kodak + MakerNoteKodak1b ExifIFD Kodak + MakerNoteKodak2 ExifIFD Kodak Type2 + MakerNoteKodak3 ExifIFD Kodak Type3 + MakerNoteKodak4 ExifIFD Kodak Type4 + MakerNoteKodak5 ExifIFD Kodak Type5 + MakerNoteKodak6a ExifIFD Kodak Type6 + MakerNoteKodak6b ExifIFD Kodak Type6 + MakerNoteKodak7 ExifIFD Kodak Type7 + MakerNoteKodak8a ExifIFD Kodak Type8 + MakerNoteKodak8b ExifIFD Kodak Type8 + MakerNoteKodak8c ExifIFD Kodak Type8 + MakerNoteKodak9 ExifIFD Kodak Type9 + MakerNoteKodak10 ExifIFD Kodak Type10 + MakerNoteKodak11 ExifIFD Kodak Type11 + MakerNoteKodak12 ExifIFD Kodak Type11 + MakerNoteKodakUnknown ExifIFD Kodak Unknown + MakerNoteKyocera ExifIFD Unknown + MakerNoteMinolta ExifIFD Minolta + MakerNoteMinolta2 ExifIFD Olympus + MakerNoteMinolta3 ExifIFD undef + MakerNoteMotorola ExifIFD Motorola + MakerNoteNikon2 ExifIFD Nikon Type2 + MakerNoteNikon3 ExifIFD Nikon + MakerNoteNintendo ExifIFD Nintendo + MakerNoteOlympus ExifIFD Olympus + MakerNoteOlympus2 ExifIFD Olympus + MakerNoteOlympus3 ExifIFD Olympus + MakerNoteLeica ExifIFD Panasonic + MakerNoteLeica2 ExifIFD Panasonic Leica2 + MakerNoteLeica3 ExifIFD Panasonic Leica3 + MakerNoteLeica4 ExifIFD Panasonic Leica4 + MakerNoteLeica5 ExifIFD Panasonic Leica5 + MakerNoteLeica6 ExifIFD Panasonic Leica6 + MakerNoteLeica7 ExifIFD Panasonic Leica6 + MakerNoteLeica8 ExifIFD Panasonic Leica5 + MakerNoteLeica9 ExifIFD Panasonic Leica9 + MakerNoteLeica10 ExifIFD Panasonic + MakerNotePanasonic ExifIFD Panasonic + MakerNotePanasonic2 ExifIFD Panasonic Type2 + MakerNotePanasonic3 ExifIFD Panasonic + MakerNotePentax ExifIFD Pentax + MakerNotePentax2 ExifIFD Pentax Type2 + MakerNotePentax3 ExifIFD Casio Type2 + MakerNotePentax4 ExifIFD Pentax Type4 + MakerNotePentax5 ExifIFD Pentax + MakerNotePentax6 ExifIFD Pentax S1 + MakerNotePhaseOne ExifIFD PhaseOne + MakerNoteReconyx ExifIFD Reconyx + MakerNoteReconyx2 ExifIFD Reconyx Type2 + MakerNoteReconyx3 ExifIFD Reconyx Type3 + MakerNoteRicohPentax ExifIFD Pentax + MakerNoteRicoh ExifIFD Ricoh + MakerNoteRicoh2 ExifIFD Ricoh Type2 + MakerNoteRicohText ExifIFD Ricoh Text + MakerNoteSamsung1a ExifIFD undef + MakerNoteSamsung1b ExifIFD Samsung + MakerNoteSamsung2 ExifIFD Samsung Type2 + MakerNoteSanyo ExifIFD Sanyo + MakerNoteSanyoC4 ExifIFD Sanyo + MakerNoteSanyoPatch ExifIFD Sanyo + MakerNoteSigma ExifIFD Sigma + MakerNoteSony ExifIFD Sony + MakerNoteSony2 ExifIFD Olympus + MakerNoteSony3 ExifIFD Olympus + MakerNoteSony4 ExifIFD Sony PIC + MakerNoteSony5 ExifIFD Sony + MakerNoteSonyEricsson ExifIFD Sony Ericsson + MakerNoteSonySRF ExifIFD Sony SRF + MakerNoteUnknownText ExifIFD undef + MakerNoteUnknownBinary ExifIFD undef + MakerNoteUnknown ExifIFD Unknown + 0x9286 UserComment ExifIFD undef + 0x9290 SubSecTime ExifIFD string + 0x9291 SubSecTimeOriginal ExifIFD string + 0x9292 SubSecTimeDigitized ExifIFD string + 0x932f MSDocumentText - no + 0x9330 MSPropertySetStorage - no + 0x9331 MSDocumentTextPosition - no + 0x935c ImageSourceData IFD0 Photoshop DocumentData + 0x9400 AmbientTemperature ExifIFD rational64s + 0x9401 Humidity ExifIFD rational64u + 0x9402 Pressure ExifIFD rational64u + 0x9403 WaterDepth ExifIFD rational64s + 0x9404 Acceleration ExifIFD rational64u + 0x9405 CameraElevationAngle ExifIFD rational64s + 0x9c9b XPTitle IFD0 int8u + 0x9c9c XPComment IFD0 int8u + 0x9c9d XPAuthor IFD0 int8u + 0x9c9e XPKeywords IFD0 int8u + 0x9c9f XPSubject IFD0 int8u + 0xa000 FlashpixVersion ExifIFD undef: + 0xa001 ColorSpace ExifIFD int16u: + 0xa002 ExifImageWidth ExifIFD int16u: + 0xa003 ExifImageHeight ExifIFD int16u: + 0xa004 RelatedSoundFile ExifIFD string + 0xa005 InteropOffset - EXIF + 0xa010 SamsungRawPointersOffset - no + 0xa011 SamsungRawPointersLength - no + 0xa101 SamsungRawByteOrder - no + 0xa102 SamsungRawUnknown? - no + 0xa20b FlashEnergy ExifIFD rational64u + 0xa20c SpatialFrequencyResponse - no + 0xa20d Noise - no + 0xa20e FocalPlaneXResolution ExifIFD rational64u + 0xa20f FocalPlaneYResolution ExifIFD rational64u + 0xa210 FocalPlaneResolutionUnit ExifIFD int16u + 0xa211 ImageNumber - no + 0xa212 SecurityClassification - no + 0xa213 ImageHistory - no + 0xa214 SubjectLocation ExifIFD int16u[2] + 0xa215 ExposureIndex ExifIFD rational64u + 0xa216 TIFF-EPStandardID - no + 0xa217 SensingMethod ExifIFD int16u + 0xa300 FileSource ExifIFD undef + 0xa301 SceneType ExifIFD undef + 0xa302 CFAPattern ExifIFD undef + 0xa401 CustomRendered ExifIFD int16u + 0xa402 ExposureMode ExifIFD int16u + 0xa403 WhiteBalance ExifIFD int16u + 0xa404 DigitalZoomRatio ExifIFD rational64u + 0xa405 FocalLengthIn35mmFormat ExifIFD int16u + 0xa406 SceneCaptureType ExifIFD int16u + 0xa407 GainControl ExifIFD int16u + 0xa408 Contrast ExifIFD int16u + 0xa409 Saturation ExifIFD int16u + 0xa40a Sharpness ExifIFD int16u + 0xa40b DeviceSettingDescription - no + 0xa40c SubjectDistanceRange ExifIFD int16u + 0xa420 ImageUniqueID ExifIFD string + 0xa430 OwnerName ExifIFD string + 0xa431 SerialNumber ExifIFD string + 0xa432 LensInfo ExifIFD rational64u[4] + 0xa433 LensMake ExifIFD string + 0xa434 LensModel ExifIFD string + 0xa435 LensSerialNumber ExifIFD string + 0xa436 Title ExifIFD string/ + 0xa437 Photographer ExifIFD string + 0xa438 ImageEditor ExifIFD string + 0xa439 CameraFirmware ExifIFD string + 0xa43a RAWDevelopingSoftware ExifIFD string + 0xa43b ImageEditingSoftware ExifIFD string + 0xa43c MetadataEditingSoftware ExifIFD string + 0xa460 CompositeImage ExifIFD int16u + 0xa461 CompositeImageCount ExifIFD int16u[2] + 0xa462 CompositeImageExposureTimes ExifIFD undef + 0xa480 GDALMetadata IFD0 string + 0xa481 GDALNoData IFD0 string + 0xa500 Gamma ExifIFD rational64u + 0xafc0 ExpandSoftware - no + 0xafc1 ExpandLens - no + 0xafc2 ExpandFilm - no + 0xafc3 ExpandFilterLens - no + 0xafc4 ExpandScanner - no + 0xafc5 ExpandFlashLamp - no + 0xb4c3 HasselbladRawImage - no + 0xbc01 PixelFormat - no + 0xbc02 Transformation - no + 0xbc03 Uncompressed - no + 0xbc04 ImageType - no + 0xbc80 ImageWidth - no + 0xbc81 ImageHeight - no + 0xbc82 WidthResolution - no + 0xbc83 HeightResolution - no + 0xbcc0 ImageOffset - no + 0xbcc1 ImageByteCount - no + 0xbcc2 AlphaOffset - no + 0xbcc3 AlphaByteCount - no + 0xbcc4 ImageDataDiscard - no + 0xbcc5 AlphaDataDiscard - no + 0xc427 OceScanjobDesc - no + 0xc428 OceApplicationSelector - no + 0xc429 OceIDNumber - no + 0xc42a OceImageLogic - no + 0xc44f Annotations - no + 0xc4a5 PrintIM IFD0 PrintIM + 0xc51b HasselbladExif - no + 0xc573 OriginalFileName - no + 0xc580 USPTOOriginalContentType - no + 0xc5e0 CR2CFAPattern - no + 0xc612 DNGVersion IFD0 int8u[4]! + 0xc613 DNGBackwardVersion IFD0 int8u[4]! + 0xc614 UniqueCameraModel IFD0 string + 0xc615 LocalizedCameraModel IFD0 string + 0xc616 CFAPlaneColor SubIFD no + 0xc617 CFALayout SubIFD no + 0xc618 LinearizationTable SubIFD int16u[n]! + 0xc619 BlackLevelRepeatDim SubIFD int16u[2]! + 0xc61a BlackLevel SubIFD rational64u[n]! + 0xc61b BlackLevelDeltaH SubIFD rational64s[n]! + 0xc61c BlackLevelDeltaV SubIFD rational64s[n]! + 0xc61d WhiteLevel SubIFD int32u[n]! + 0xc61e DefaultScale SubIFD rational64u[2]! + 0xc61f DefaultCropOrigin SubIFD int32u[2]! + 0xc620 DefaultCropSize SubIFD int32u[2]! + 0xc621 ColorMatrix1 IFD0 rational64s[n]! + 0xc622 ColorMatrix2 IFD0 rational64s[n]! + 0xc623 CameraCalibration1 IFD0 rational64s[n]! + 0xc624 CameraCalibration2 IFD0 rational64s[n]! + 0xc625 ReductionMatrix1 IFD0 rational64s[n]! + 0xc626 ReductionMatrix2 IFD0 rational64s[n]! + 0xc627 AnalogBalance IFD0 rational64u[n]! + 0xc628 AsShotNeutral IFD0 rational64u[n]! + 0xc629 AsShotWhiteXY IFD0 rational64u[2]! + 0xc62a BaselineExposure IFD0 rational64s! + 0xc62b BaselineNoise IFD0 rational64u! + 0xc62c BaselineSharpness IFD0 rational64u! + 0xc62d BayerGreenSplit SubIFD int32u! + 0xc62e LinearResponseLimit IFD0 rational64u! + 0xc62f CameraSerialNumber IFD0 string + 0xc630 DNGLensInfo IFD0 rational64u[4] + 0xc631 ChromaBlurRadius SubIFD rational64u! + 0xc632 AntiAliasStrength SubIFD rational64u! + 0xc633 ShadowScale IFD0 rational64u! + 0xc634 SR2Private IFD0 Sony SR2Private + DNGAdobeData IFD0 DNG AdobeData + MakerNotePentax IFD0 Pentax + MakerNotePentax5 IFD0 Pentax + MakerNoteRicohPentax IFD0 Pentax + MakerNoteDJIInfo IFD0 DJI Info + DNGPrivateData IFD0 int8u! + 0xc635 MakerNoteSafety IFD0 int16u + 0xc640 RawImageSegmentation - no + 0xc65a CalibrationIlluminant1 IFD0 int16u! + 0xc65b CalibrationIlluminant2 IFD0 int16u! + 0xc65c BestQualityScale SubIFD rational64u! + 0xc65d RawDataUniqueID IFD0 int8u[16]! + 0xc660 AliasLayerMetadata - no + 0xc68b OriginalRawFileName IFD0 string! + 0xc68c OriginalRawFileData IFD0 DNG OriginalRaw + 0xc68d ActiveArea SubIFD int32u[4]! + 0xc68e MaskedAreas SubIFD int32u[n]! + 0xc68f AsShotICCProfile IFD0 ICC_Profile + 0xc690 AsShotPreProfileMatrix IFD0 rational64s[n]! + 0xc691 CurrentICCProfile IFD0 ICC_Profile + 0xc692 CurrentPreProfileMatrix IFD0 rational64s[n]! + 0xc6bf ColorimetricReference IFD0 int16u! + 0xc6c5 SRawType IFD0 no + 0xc6d2 PanasonicTitle IFD0 undef + 0xc6d3 PanasonicTitle2 IFD0 undef + 0xc6f3 CameraCalibrationSig IFD0 string! + 0xc6f4 ProfileCalibrationSig IFD0 string! + 0xc6f5 ProfileIFD IFD0 EXIF + 0xc6f6 AsShotProfileName IFD0 string! + 0xc6f7 NoiseReductionApplied SubIFD rational64u! + 0xc6f8 ProfileName IFD0 string! + 0xc6f9 ProfileHueSatMapDims IFD0 int32u[3]! + 0xc6fa ProfileHueSatMapData1 IFD0 float[n]! + 0xc6fb ProfileHueSatMapData2 IFD0 float[n]! + 0xc6fc ProfileToneCurve IFD0 float[n]! + 0xc6fd ProfileEmbedPolicy IFD0 int32u! + 0xc6fe ProfileCopyright IFD0 string! + 0xc714 ForwardMatrix1 IFD0 rational64s[n]! + 0xc715 ForwardMatrix2 IFD0 rational64s[n]! + 0xc716 PreviewApplicationName IFD0 string! + 0xc717 PreviewApplicationVersion IFD0 string! + 0xc718 PreviewSettingsName IFD0 string! + 0xc719 PreviewSettingsDigest IFD0 int8u! + 0xc71a PreviewColorSpace IFD0 int32u! + 0xc71b PreviewDateTime IFD0 string! + 0xc71c RawImageDigest IFD0 int8u[16]! + 0xc71d OriginalRawFileDigest IFD0 int8u[16]! + 0xc71e SubTileBlockSize - no + 0xc71f RowInterleaveFactor - no + 0xc725 ProfileLookTableDims IFD0 int32u[3]! + 0xc726 ProfileLookTableData IFD0 float[n]! + 0xc740 OpcodeList1 SubIFD undef~! + 0xc741 OpcodeList2 SubIFD undef~! + 0xc74e OpcodeList3 SubIFD undef~! + 0xc761 NoiseProfile SubIFD double[n]! + 0xc763 TimeCodes IFD0 int8u[n] + 0xc764 FrameRate IFD0 rational64s + 0xc772 TStop IFD0 rational64u[n] + 0xc789 ReelName IFD0 string + 0xc791 OriginalDefaultFinalSize IFD0 int32u[2]! + 0xc792 OriginalBestQualitySize IFD0 int32u[2]! + 0xc793 OriginalDefaultCropSize IFD0 rational64u[2]! + 0xc7a1 CameraLabel IFD0 string + 0xc7a3 ProfileHueSatMapEncoding IFD0 int32u! + 0xc7a4 ProfileLookTableEncoding IFD0 int32u! + 0xc7a5 BaselineExposureOffset IFD0 rational64s! + 0xc7a6 DefaultBlackRender IFD0 int32u! + 0xc7a7 NewRawImageDigest IFD0 int8u[16]! + 0xc7a8 RawToPreviewGain IFD0 double! + 0xc7aa CacheVersion SubIFD2 int32u! + 0xc7b5 DefaultUserCrop SubIFD rational64u[4]! + 0xc7d5 NikonNEFInfo - Nikon NEFInfo + 0xc7e9 DepthFormat IFD0 int16u! + 0xc7ea DepthNear IFD0 rational64u! + 0xc7eb DepthFar IFD0 rational64u! + 0xc7ec DepthUnits IFD0 int16u! + 0xc7ed DepthMeasureType IFD0 int16u! + 0xc7ee EnhanceParams IFD0 string! + 0xcd2d ProfileGainTableMap SubIFD undef! + 0xcd2e SemanticName SubIFD no + 0xcd30 SemanticInstanceID SubIFD no + 0xcd31 CalibrationIlluminant3 IFD0 int16u! + 0xcd32 CameraCalibration3 IFD0 rational64s[n]! + 0xcd33 ColorMatrix3 IFD0 rational64s[n]! + 0xcd34 ForwardMatrix3 IFD0 rational64s[n]! + 0xcd35 IlluminantData1 IFD0 undef! + 0xcd36 IlluminantData2 IFD0 undef! + 0xcd37 IlluminantData3 IFD0 undef! + 0xcd38 MaskSubArea SubIFD no + 0xcd39 ProfileHueSatMapData3 IFD0 float[n]! + 0xcd3a ReductionMatrix3 IFD0 rational64s[n]! + 0xcd3b RGBTables IFD0 undef! + 0xea1c Padding ExifIFD undef! + 0xea1d OffsetSchema ExifIFD int32s! + 0xfde8 OwnerName ExifIFD string/ + 0xfde9 SerialNumber ExifIFD string/ + 0xfdea Lens ExifIFD string/ + 0xfe00 KDC_IFD - Kodak KDC_IFD + 0xfe4c RawFile ExifIFD string/ + 0xfe4d Converter ExifIFD string/ + 0xfe4e WhiteBalance ExifIFD string/ + 0xfe51 Exposure ExifIFD string/ + 0xfe52 Shadows ExifIFD string/ + 0xfe53 Brightness ExifIFD string/ + 0xfe54 Contrast ExifIFD string/ + 0xfe55 Saturation ExifIFD string/ + 0xfe56 Sharpness ExifIFD string/ + 0xfe57 Smoothness ExifIFD string/ + 0xfe58 MoireFilter ExifIFD string/ + +=head2 IPTC Tags + +The tags listed below are part of the International Press Telecommunications +Council (IPTC) and the Newspaper Association of America (NAA) Information +Interchange Model (IIM). This is an older meta information format, slowly +being phased out in favor of XMP -- the newer IPTCCore specification uses +XMP format. IPTC information may be found in JPG, TIFF, PNG, MIFF, PS, PDF, +PSD, XCF and DNG images. + +IPTC information is separated into different records, each of which has its +own set of tags. See +L<http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf> for the +official IPTC IIM specification. + +This specification dictates a length for ASCII (C<string> or C<digits>) and +binary (C<undef>) values. These lengths are given in square brackets after +the B<Writable> format name. For tags where a range of lengths is allowed, +the minimum and maximum lengths are separated by a comma within the +brackets. When writing, ExifTool issues a minor warning and truncates the +value if it is longer than allowed by the IPTC specification. Minor errors +may be ignored with the IgnoreMinorErrors (-m) option, allowing longer +values to be written, but beware that values like this may cause problems +for some other IPTC readers. ExifTool will happily read IPTC values of any +length. + +Separate IPTC date and time tags may be written with a combined date/time +value and ExifTool automagically takes the appropriate part of the date/time +string depending on whether a date or time tag is being written. This is +very useful when copying date/time values to IPTC from other metadata +formats. + +IPTC time values include a timezone offset. If written with a value which +doesn't include a timezone then the current local timezone offset is used +(unless written with a combined date/time, in which case the local timezone +offset at the specified date/time is used, which may be different due to +changes in daylight savings time). + +Note that it is not uncommon for IPTC to be found in non-standard locations +in JPEG and TIFF-based images. When reading, the family 1 group name has a +number added for non-standard IPTC ("IPTC2", "IPTC3", etc), but when writing +only "IPTC" may be specified as the group. To keep the IPTC consistent, +ExifTool updates tags in all existing IPTC locations, but will create a new +IPTC group only in the standard location. + + Record Tag Name Writable + ------ -------- -------- + 1 IPTCEnvelope IPTC EnvelopeRecord + 2 IPTCApplication IPTC ApplicationRecord + 3 IPTCNewsPhoto IPTC NewsPhoto + 7 IPTCPreObjectData IPTC PreObjectData + 8 IPTCObjectData IPTC ObjectData + 9 IPTCPostObjectData IPTC PostObjectData + 240 IPTCFotoStation IPTC FotoStation + +=head3 IPTC EnvelopeRecord Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0 EnvelopeRecordVersion int16u: + 5 Destination string[0,1024]+ + 20 FileFormat int16u + 22 FileVersion int16u + 30 ServiceIdentifier string[0,10] + 40 EnvelopeNumber digits[8] + 50 ProductID string[0,32]+ + 60 EnvelopePriority digits[1] + 70 DateSent digits[8] + 80 TimeSent string[11] + 90 CodedCharacterSet string[0,32]! + 100 UniqueObjectName string[14,80] + 120 ARMIdentifier int16u + 122 ARMVersion int16u + +=head3 IPTC ApplicationRecord Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0 ApplicationRecordVersion int16u: + 3 ObjectTypeReference string[3,67] + 4 ObjectAttributeReference string[4,68]+ + 5 ObjectName string[0,64] + 7 EditStatus string[0,64] + 8 EditorialUpdate digits[2] + 10 Urgency digits[1] + 12 SubjectReference string[13,236]+ + 15 Category string[0,3] + 20 SupplementalCategories string[0,32]+ + 22 FixtureIdentifier string[0,32] + 25 Keywords string[0,64]+ + 26 ContentLocationCode string[3]+ + 27 ContentLocationName string[0,64]+ + 30 ReleaseDate digits[8] + 35 ReleaseTime string[11] + 37 ExpirationDate digits[8] + 38 ExpirationTime string[11] + 40 SpecialInstructions string[0,256] + 42 ActionAdvised digits[2] + 45 ReferenceService string[0,10]+ + 47 ReferenceDate digits[8]+ + 50 ReferenceNumber digits[8]+ + 55 DateCreated digits[8] + 60 TimeCreated string[11] + 62 DigitalCreationDate digits[8] + 63 DigitalCreationTime string[11] + 65 OriginatingProgram string[0,32] + 70 ProgramVersion string[0,10] + 75 ObjectCycle string[1] + 80 By-line string[0,32]+ + 85 By-lineTitle string[0,32]+ + 90 City string[0,32] + 92 Sub-location string[0,32] + 95 Province-State string[0,32] + 100 Country-PrimaryLocationCode string[3] + 101 Country-PrimaryLocationName string[0,64] + 103 OriginalTransmissionReference string[0,32] + 105 Headline string[0,256] + 110 Credit string[0,32] + 115 Source string[0,32] + 116 CopyrightNotice string[0,128] + 118 Contact string[0,128]+ + 120 Caption-Abstract string[0,2000] + 121 LocalCaption string[0,256] + 122 Writer-Editor string[0,32]+ + 125 RasterizedCaption undef[7360] + 130 ImageType string[2] + 131 ImageOrientation string[1] + 135 LanguageIdentifier string[2,3] + 150 AudioType string[2] + 151 AudioSamplingRate digits[6] + 152 AudioSamplingResolution digits[2] + 153 AudioDuration digits[6] + 154 AudioOutcue string[0,64] + 184 JobID string[0,64] + 185 MasterDocumentID string[0,256] + 186 ShortDocumentID string[0,64] + 187 UniqueDocumentID string[0,128] + 188 OwnerID string[0,128] + 200 ObjectPreviewFileFormat int16u + 201 ObjectPreviewFileVersion int16u + 202 ObjectPreviewData undef[0,256000] + 221 Prefs string[0,64] + 225 ClassifyState string[0,64] + 228 SimilarityIndex string[0,32] + 230 DocumentNotes string[0,1024] + 231 DocumentHistory string[0,256] + 232 ExifCameraInfo string[0,4096] + 255 CatalogSets string[0,256]+ + +=head3 IPTC NewsPhoto Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0 NewsPhotoVersion int16u: + 10 IPTCPictureNumber string[16] + 20 IPTCImageWidth int16u + 30 IPTCImageHeight int16u + 40 IPTCPixelWidth int16u + 50 IPTCPixelHeight int16u + 55 SupplementalType int8u + 60 ColorRepresentation int16u + 64 InterchangeColorSpace int8u + 65 ColorSequence int8u + 66 ICC_Profile no + 70 ColorCalibrationMatrix no + 80 LookupTable no + 84 NumIndexEntries int16u + 85 ColorPalette no + 86 IPTCBitsPerSample int8u + 90 SampleStructure int8u + 100 ScanningDirection int8u + 102 IPTCImageRotation int8u + 110 DataCompressionMethod int32u + 120 QuantizationMethod int8u + 125 EndPoints no + 130 ExcursionTolerance int8u + 135 BitsPerComponent int8u + 140 MaximumDensityRange int16u + 145 GammaCompensatedValue int16u + +=head3 IPTC PreObjectData Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 10 SizeMode no + 20 MaxSubfileSize no + 90 ObjectSizeAnnounced no + 95 MaximumObjectSize no + +=head3 IPTC ObjectData Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 10 SubFile no+ + +=head3 IPTC PostObjectData Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 10 ConfirmedObjectSize no + +=head3 IPTC FotoStation Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head2 XMP Tags + +XMP stands for "Extensible Metadata Platform", an XML/RDF-based metadata +format which is being pushed by Adobe. Information in this format can be +embedded in many different image file types including JPG, JP2, TIFF, GIF, +EPS, PDF, PSD, IND, INX, PNG, DJVU, SVG, PGF, MIFF, XCF, CRW, DNG and a +variety of proprietary TIFF-based RAW images, as well as MOV, AVI, ASF, WMV, +FLV, SWF and MP4 videos, and WMA and audio formats supporting ID3v2 +information. + +The XMP B<Tag ID>'s aren't listed because in most cases they are identical +to the B<Tag Name> (aside from differences in case). Tags with different +ID's are mentioned in the B<Notes> column of the HTML version of this +document. + +All XMP information is stored as character strings. The B<Writable> column +specifies the information format: C<string> is an unformatted string, +C<integer> is a string of digits (possibly beginning with a '+' or '-'), +C<real> is a floating point number, C<rational> is entered as a floating +point number but stored as two C<integer> strings separated by a '/' +character, C<date> is a date/time string entered in the format "YYYY:mm:dd +HH:MM:SS[.ss][+/-HH:MM]" but some partial date/time formats are also +accepted (see L<https://exiftool.org/faq.html#Q5>), C<boolean> is either +"True" or "False" (but "true" and "false" may be written as a ValueConv +value for compatibility with non-conforming applications), C<struct> +indicates a structured tag, and C<lang-alt> is a tag that supports alternate +languages. + +When reading, C<struct> tags are extracted only if the Struct (-struct) +option is used. Otherwise the corresponding I<Flattened> tags, indicated by +an underline (C<_>) after the B<Writable> type, are extracted. When +copying, by default both structured and flattened tags are available, but +the flattened tags are considered "unsafe" so they aren't copied unless +specified explicitly. The Struct option may be disabled by setting Struct +to 0 via the API or with --struct on the command line to copy only flattened +tags, or enabled by setting Struct to 1 via the API or with -struct on the +command line to copy only as structures. When writing, the Struct option +has no effect, and both structured and flattened tags may be written. See +L<https://exiftool.org/struct.html> for more details. + +Individual languages for C<lang-alt> tags are accessed by suffixing the tag +name with a '-', followed by an RFC 3066 language code (eg. "XMP:Title-fr", +or "Rights-en-US"). (See L<http://www.ietf.org/rfc/rfc3066.txt> for the RFC +3066 specification.) A C<lang-alt> tag with no language code accesses the +"x-default" language, but causes other languages for this tag to be deleted +when writing. The "x-default" language code may be specified when writing +to preserve other existing languages (eg. "XMP-dc:Description-x-default"). +When reading, "x-default" is not specified. + +The XMP tags are organized according to schema B<Namespace> in the following +tables. The ExifTool family 1 group names are derived from the namespace +prefixes by adding a leading "XMP-" (eg. "XMP-dc"). A few of the longer +prefixes have been shortened (as mentioned in the documentation below) to +avoid excessively long ExifTool group names. The tags of any namespace may +be deleted as a group by specifying the family 1 group name (eg. +"-XMP-dc:all=" on the command line). This includes namespaces which are not +pre-defined by ExifTool. + +In cases where a tag name exists in more than one namespace, less common +namespaces are avoided when writing. However, a specific namespace may be +written by providing a family 1 group name for the tag (eg. XMP-crs:Contrast +or XMP-exif:Contrast). When deciding on which tags to add to an image, +using standard schemas such as L<dc|/XMP dc Tags>, L<xmp|/XMP xmp Tags>, +L<iptcCore|/XMP iptcCore Tags> and L<iptcExt|/XMP iptcExt Tags> is +recommended if possible. + +For structures, the heading of the first column is B<Field Name>. Field +names are very similar to tag names, except they are used to identify fields +inside structures instead of stand-alone tags. See +L<https://exiftool.org/struct.html#Fields> for more +details. + +ExifTool will extract XMP information even if it is not listed in these +tables, but other tags are not writable unless added as user-defined tags in +the ExifTool config file. For example, the C<pdfx> namespace doesn't have a +predefined set of tag names because it is used to store application-defined +PDF information, so although this information will be extracted, it is only +writable if the corresponding user-defined tags have been created. + +The tables below list tags from the official XMP specification (with an +underlined B<Namespace> in the HTML version of this documentation), as well +as extensions from various other sources. See +L<http://www.adobe.com/devnet/xmp/> for the official XMP specification. + + Namespace Writable + --------- -------- + aas XMP aas + acdsee XMP acdsee + album XMP Album + apple-fi XMP apple_fi + ast Nikon ast + aux XMP aux + cc XMP cc + cell XMP cell + crd XMP crd + creatorAtom XMP creatorAtom + crs XMP crs + dc XMP dc + Device XMP Device + dex XMP dex + DICOM XMP DICOM + digiKam XMP digiKam + drone-dji DJI XMP + dwc DarwinCore + et XMP ExifTool + exif XMP exif + exifEX XMP exifEX + expressionmedia XMP ExpressionMedia + extensis XMP extensis + fpv XMP fpv + GAudio XMP GAudio + GCamera XMP GCamera + GCreations XMP GCreations + GDepth XMP GDepth + getty XMP GettyImages + GFocus XMP GFocus + GImage XMP GImage + GPano XMP GPano + GSpherical XMP GSpherical + hdr XMP hdr + hdrgm XMP hdrgm + ics XMP ics + iptcCore XMP iptcCore + iptcExt XMP iptcExt + LImage XMP LImage + lr XMP Lightroom + mediapro XMP MediaPro + microsoft Microsoft XMP + MP Microsoft MP + MP1 Microsoft MP1 + mwg-coll MWG Collections + mwg-kw MWG Keywords + mwg-rs MWG Regions + nine Nikon nine + panorama XMP panorama + pdf XMP pdf + pdfx XMP pdfx + photomech PhotoMechanic XMP + photoshop XMP photoshop + PixelLive XMP PixelLive + plus PLUS XMP + pmi XMP pmi + prism XMP prism + prl XMP prl + prm XMP prm + pur XMP pur + rdf XMP rdf + sdc Nikon sdc + swf XMP swf + tiff XMP tiff + x XMP x + xmp XMP xmp + xmpBJ XMP xmpBJ + xmpDM XMP xmpDM + xmpMM XMP xmpMM + xmpNote XMP xmpNote + xmpPLUS XMP xmpPLUS + xmpRights XMP xmpRights + xmpTPg XMP xmpTPg + +=head3 XMP aas Tags + +Apple Adjustment Settings used by iPhone/iPad. + +These tags belong to the ExifTool XMP-aas family 1 group. + + Tag Name Writable + -------- -------- + AffineA real + AffineB real + AffineC real + AffineD real + AffineX real + AffineY real + CropH integer/ + CropW integer/ + CropX integer/ + CropY integer/ + Curve0x real + Curve0y real + Curve1x real + Curve1y real + Curve2x real + Curve2y real + Curve3x real + Curve3y real + Curve4x real + Curve4y real + FaceBalanceOrigI real + FaceBalanceOrigQ real + FaceBalanceStrength real + FaceBalanceWarmth real + Highlights real/ + Shadows real/ + Vibrance real/ + +=head3 XMP acdsee Tags + +ACD Systems ACDSee namespace tags. + +(A note to software developers: Re-inventing your own private tags instead +of using the equivalent tags in standard XMP namespaces defeats one of the +most valuable features of metadata: interoperability. Your applications +mumble to themselves instead of speaking out for the rest of the world to +hear.) + +These tags belong to the ExifTool XMP-acdsee family 1 group. + + Tag Name Writable + -------- -------- + Author string/ + Caption string/ + Categories string/ + Collections string/ + DateTime date/ + DPP lang-alt/ + EditStatus string/ + FixtureIdentifier string/ + Keywords string/+ + Notes string/ + ObjectCycle string/ + OriginatingProgram string/ + Rating real/ + Rawrppused boolean/ + ReleaseDate string/ + ReleaseTime string/ + RPP lang-alt/ + Snapshots string/+ + Tagged boolean/ + +=head3 XMP Album Tags + +Adobe Album namespace tags. + +These tags belong to the ExifTool XMP-album family 1 group. + + Tag Name Writable + -------- -------- + Notes string + +=head3 XMP apple_fi Tags + +Face information tags written by the Apple iPhone 5 inside the mwg-rs +RegionExtensions. + +These tags belong to the ExifTool XMP-apple-fi family 1 group. + + Tag Name Writable + -------- -------- + AngleInfoRoll integer + AngleInfoYaw integer + ConfidenceLevel integer + FaceID integer + TimeStamp integer + +=head3 XMP aux Tags + +Adobe-defined auxiliary EXIF tags. This namespace existed in the XMP +specification until it was dropped in 2012, presumably due to the +introduction of the EXIF 2.3 for XMP specification and the exifEX namespace +at this time. For this reason, tags below with equivalents in the +L<exifEX namespace|/XMP exifEX Tags> are avoided when writing. + +These tags belong to the ExifTool XMP-aux family 1 group. + + Tag Name Writable + -------- -------- + ApproximateFocusDistance rational + DistortionCorrectionAlreadyApplied boolean + EnhanceDenoiseAlreadyApplied boolean + EnhanceDenoiseLumaAmount string + EnhanceDenoiseVersion string + EnhanceDetailsAlreadyApplied boolean + EnhanceDetailsVersion string + EnhanceSuperResolutionAlreadyApplied boolean + EnhanceSuperResolutionScale rational + EnhanceSuperResolutionVersion string + Firmware string + FlashCompensation rational + ImageNumber string + IsMergedHDR boolean + IsMergedPanorama boolean + LateralChromaticAberrationCorrectionAlreadyApplied boolean + Lens string + LensDistortInfo string + LensID string + LensInfo string/ + LensSerialNumber string/ + NeutralDensityFactor string + OwnerName string/ + SerialNumber string/ + VignetteCorrectionAlreadyApplied boolean + +=head3 XMP cc Tags + +Creative Commons namespace tags. Note that the CC specification for XMP is +non-existent, so ExifTool must make some assumptions about the format of the +specific properties in XMP (see L<http://creativecommons.org/ns>). + +These tags belong to the ExifTool XMP-cc family 1 group. + + Tag Name Writable + -------- -------- + AttributionName string + AttributionURL string + DeprecatedOn date + Jurisdiction string + LegalCode string + License string + MorePermissions string + Permits string+ + Prohibits string+ + Requires string+ + UseGuidelines string + +=head3 XMP cell Tags + +Location tags written by some Sony Ericsson phones. + +These tags belong to the ExifTool XMP-cell family 1 group. + + Tag Name Writable + -------- -------- + CellTowerID string + CellGlobalID string + LocationAreaCode string + MobileCountryCode string + MobileNetworkCode string + CellR string + +=head3 XMP crd Tags + +Adobe Camera Raw Defaults tags. + +These tags belong to the ExifTool XMP-crd family 1 group. + + Tag Name Writable + -------- -------- + AlreadyApplied boolean/ + AutoBrightness boolean/ + AutoContrast boolean/ + AutoExposure boolean/ + AutoLateralCA integer/ + AutoShadows boolean/ + AutoTone boolean/ + AutoToneDigest string/ + AutoToneDigestNoSat string/ + AutoWhiteVersion integer/ + Blacks2012 integer/ + BlueHue integer/ + BlueSaturation integer/ + Brightness integer/ + CameraModelRestriction string/ + CameraProfile string/ + CameraProfileDigest string/ + ChromaticAberrationB integer/ + ChromaticAberrationR integer/ + CircularGradientBasedCorrections Correction Struct+ + CircGradBasedCorrActive boolean/_ + CircGradBasedCorrAmount real/_ + CircGradBasedCorrMasks CorrectionMask Struct_+ + CircGradBasedCorrMaskAlpha real/_ + CircGradBasedCorrMaskAngle real/_ + CircGradBasedCorrMaskBottom real/_ + CircGradBasedCorrMaskCenterValue real/_ + CircGradBasedCorrMaskCenterWeight real/_ + CircGradBasedCorrMaskRange CorrRangeMask Struct_+ + CircGradBasedCorrMaskRangeAreaModels AreaModels Struct_+ + CircGradBasedCorrMaskRangeAreaModelsComponents string/_+ + CircGradBasedCorrMaskRangeAreaModelsColorSampleInfo string/_+ + CircGradBasedCorrMaskRangeColorAmount real/_+ + CircGradBasedCorrMaskRangeDepthFeather real/_+ + CircGradBasedCorrMaskRangeDepthMax real/_+ + CircGradBasedCorrMaskRangeDepthMin real/_+ + CircGradBasedCorrMaskRangeInvert boolean/_+ + CircGradBasedCorrMaskRangeLumFeather real/_+ + CircGradBasedCorrMaskRangeLuminanceDepthSampleInfo string/_+ + CircGradBasedCorrMaskRangeLumMax real/_+ + CircGradBasedCorrMaskRangeLumMin real/_+ + CircGradBasedCorrMaskRangeLumRange string/_+ + CircGradBasedCorrMaskRangeSampleType integer/_+ + CircGradBasedCorrMaskRangeType string/_+ + CircGradBasedCorrMaskRangeVersion string/_+ + CircGradBasedCorrMaskDabs string/_ + CircGradBasedCorrMaskFeather real/_ + CircGradBasedCorrMaskFlipped boolean/_ + CircGradBasedCorrMaskFlow real/_ + CircGradBasedCorrMaskFullX real/_ + CircGradBasedCorrMaskFullY real/_ + CircGradBasedCorrMaskInputDigest string/_ + CircGradBasedCorrMaskLeft real/_ + CircGradBasedCorrMaskMaskActive boolean/_ + CircGradBasedCorrMaskMaskBlendMode integer/_ + CircGradBasedCorrMaskMaskDigest string/_ + CircGradBasedCorrMaskMaskInverted boolean/_ + CircGradBasedCorrMaskMaskName string/_ + CircGradBasedCorrMaskMasks CorrectionMask Struct_+ + CircGradBasedCorrMaskMasksAlpha real/_ + CircGradBasedCorrMaskMasksAngle real/_ + CircGradBasedCorrMaskMasksBottom real/_ + CircGradBasedCorrMaskMasksCenterValue real/_ + CircGradBasedCorrMaskMasksCenterWeight real/_ + CircGradBasedCorrMaskMasksDabs string/_+ + CircGradBasedCorrMaskMasksFeather real/_ + CircGradBasedCorrMaskMasksFlipped boolean/_ + CircGradBasedCorrMaskMasksFlow real/_ + CircGradBasedCorrMaskMasksFullX real/_ + CircGradBasedCorrMaskMasksFullY real/_ + CircGradBasedCorrMaskMasksInputDigest string/_ + CircGradBasedCorrMaskMasksLeft real/_ + CircGradBasedCorrMaskMasksMaskActive boolean/_ + CircGradBasedCorrMaskMasksMaskBlendMode integer/_ + CircGradBasedCorrMaskMasksMaskDigest string/_ + CircGradBasedCorrMaskMasksMaskInverted boolean/_ + CircGradBasedCorrMaskMasksMaskName string/_ + CircGradBasedCorrMaskMasksMaskSubType string/_ + CircGradBasedCorrMaskMasksMaskSyncID string/_ + CircGradBasedCorrMaskMasksValue real/_ + CircGradBasedCorrMaskMasksMaskVersion string/_ + CircGradBasedCorrMaskMasksMidpoint real/_ + CircGradBasedCorrMaskMasksOrigin string/_ + CircGradBasedCorrMaskMasksPerimeterValue real/_ + CircGradBasedCorrMaskMasksRadius real/_ + CircGradBasedCorrMaskMasksReferencePoint string/_ + CircGradBasedCorrMaskMasksRight real/_ + CircGradBasedCorrMaskMasksRoundness real/_ + CircGradBasedCorrMaskMasksSizeX real/_ + CircGradBasedCorrMaskMasksSizeY real/_ + CircGradBasedCorrMaskMasksTop real/_ + CircGradBasedCorrMaskMaskSubType string/_ + CircGradBasedCorrMaskMasksVersion integer/_ + CircGradBasedCorrMaskMasksWhat string/_ + CircGradBasedCorrMaskMasksWholeImageArea string/_ + CircGradBasedCorrMaskMasksX real/_ + CircGradBasedCorrMaskMasksY real/_ + CircGradBasedCorrMaskMaskSyncID string/_ + CircGradBasedCorrMaskMasksZeroX real/_ + CircGradBasedCorrMaskMasksZeroY real/_ + CircGradBasedCorrMaskValue real/_ + CircGradBasedCorrMaskMaskVersion string/_ + CircGradBasedCorrMaskMidpoint real/_ + CircGradBasedCorrMaskOrigin string/_ + CircGradBasedCorrMaskPerimeterValue real/_ + CircGradBasedCorrMaskRadius real/_ + CircGradBasedCorrMaskReferencePoint string/_ + CircGradBasedCorrMaskRight real/_ + CircGradBasedCorrMaskRoundness real/_ + CircGradBasedCorrMaskSizeX real/_ + CircGradBasedCorrMaskSizeY real/_ + CircGradBasedCorrMaskTop real/_ + CircGradBasedCorrMaskVersion integer/_ + CircGradBasedCorrMaskWhat string/_ + CircGradBasedCorrMaskWholeImageArea string/_ + CircGradBasedCorrMaskX real/_ + CircGradBasedCorrMaskY real/_ + CircGradBasedCorrMaskZeroX real/_ + CircGradBasedCorrMaskZeroY real/_ + CircGradBasedCorrCorrectionName string/_+ + CircGradBasedCorrRangeMask CorrRangeMask Struct_+ + CircGradBasedCorrRangeMaskAreaModels AreaModels Struct_+ + CircGradBasedCorrRangeMaskAreaModelsComponents string/_+ + CircGradBasedCorrRangeMaskAreaModelsColorSampleInfo string/_+ + CircGradBasedCorrRangeMaskColorAmount real/_+ + CircGradBasedCorrRangeMaskDepthFeather real/_+ + CircGradBasedCorrRangeMaskDepthMax real/_+ + CircGradBasedCorrRangeMaskDepthMin real/_+ + CircGradBasedCorrRangeMaskInvert boolean/_+ + CircGradBasedCorrRangeMaskLumFeather real/_+ + CircGradBasedCorrRangeMaskLuminanceDepthSampleInfo string/_+ + CircGradBasedCorrRangeMaskLumMax real/_+ + CircGradBasedCorrRangeMaskLumMin real/_+ + CircGradBasedCorrRangeMaskLumRange string/_+ + CircGradBasedCorrRangeMaskSampleType integer/_+ + CircGradBasedCorrRangeMaskType string/_+ + CircGradBasedCorrRangeMaskVersion string/_+ + CircGradBasedCorrCorrectionSyncID string/_+ + CircGradBasedCorrBlacks2012 real/_ + CircGradBasedCorrBrightness real/_ + CircGradBasedCorrClarity real/_ + CircGradBasedCorrClarity2012 real/_ + CircGradBasedCorrContrast real/_ + CircGradBasedCorrContrast2012 real/_ + CircGradBasedCorrDefringe real/_ + CircGradBasedCorrDehaze real/_ + CircGradBasedCorrExposure real/_ + CircGradBasedCorrExposure2012 real/_ + CircGradBasedCorrHighlights2012 real/_ + CircGradBasedCorrHue real/_ + CircGradBasedCorrLuminanceNoise real/_ + CircGradBasedCorrMoire real/_ + CircGradBasedCorrSaturation real/_ + CircGradBasedCorrShadows2012 real/_ + CircGradBasedCorrSharpness real/_ + CircGradBasedCorrTemperature real/_ + CircGradBasedCorrTexture real/_ + CircGradBasedCorrTint real/_ + CircGradBasedCorrToningHue real/_ + CircGradBasedCorrToningSaturation real/_ + CircGradBasedCorrWhites2012 real/_ + CircGradBasedCorrWhat string/_ + Clarity integer/ + Clarity2012 integer/ + ClipboardAspectRatio integer/ + ClipboardOrientation integer/ + Cluster string/ + ColorGradeBlending integer/ + ColorGradeGlobalHue integer/ + ColorGradeGlobalLum integer/ + ColorGradeGlobalSat integer/ + ColorGradeHighlightLum integer/ + ColorGradeMidtoneHue integer/ + ColorGradeMidtoneLum integer/ + ColorGradeMidtoneSat integer/ + ColorGradeShadowLum integer/ + ColorNoiseReduction integer/ + ColorNoiseReductionDetail integer/ + ColorNoiseReductionSmoothness integer/ + CompatibleVersion string/ + ContactInfo string/ + Contrast integer/ + Contrast2012 integer/ + Converter string/ + ConvertToGrayscale boolean/ + Copyright string/ + CropAngle real/ + CropBottom real/ + CropConstrainToWarp integer/ + CropHeight real/ + CropLeft real/ + CropRight real/ + CropTop real/ + CropUnit integer/ + CropUnits integer/ + CropWidth real/ + DefaultAutoGray boolean/ + DefaultAutoTone boolean/ + DefaultsSpecificToISO boolean/ + DefaultsSpecificToSerial boolean/ + Defringe integer/ + DefringeGreenAmount integer/ + DefringeGreenHueHi integer/ + DefringeGreenHueLo integer/ + DefringePurpleAmount integer/ + DefringePurpleHueHi integer/ + DefringePurpleHueLo integer/ + Dehaze real/ + Description lang-alt/ + DNGIgnoreSidecars boolean/ + Exposure real/ + Exposure2012 real/ + FillLight integer/ + GradientBasedCorrections Correction Struct+ + GradientBasedCorrActive boolean/_ + GradientBasedCorrAmount real/_ + GradientBasedCorrMasks CorrectionMask Struct_+ + GradientBasedCorrMaskAlpha real/_ + GradientBasedCorrMaskAngle real/_ + GradientBasedCorrMaskBottom real/_ + GradientBasedCorrMaskCenterValue real/_ + GradientBasedCorrMaskCenterWeight real/_ + GradientBasedCorrMaskRange CorrRangeMask Struct_+ + GradientBasedCorrMaskRangeAreaModels AreaModels Struct_+ + GradientBasedCorrMaskRangeAreaModelsComponents string/_+ + GradientBasedCorrMaskRangeAreaModelsColorSampleInfo string/_+ + GradientBasedCorrMaskRangeColorAmount real/_+ + GradientBasedCorrMaskRangeDepthFeather real/_+ + GradientBasedCorrMaskRangeDepthMax real/_+ + GradientBasedCorrMaskRangeDepthMin real/_+ + GradientBasedCorrMaskRangeInvert boolean/_+ + GradientBasedCorrMaskRangeLumFeather real/_+ + GradientBasedCorrMaskRangeLuminanceDepthSampleInfo string/_+ + GradientBasedCorrMaskRangeLumMax real/_+ + GradientBasedCorrMaskRangeLumMin real/_+ + GradientBasedCorrMaskRangeLumRange string/_+ + GradientBasedCorrMaskRangeSampleType integer/_+ + GradientBasedCorrMaskRangeType string/_+ + GradientBasedCorrMaskRangeVersion string/_+ + GradientBasedCorrMaskDabs string/_ + GradientBasedCorrMaskFeather real/_ + GradientBasedCorrMaskFlipped boolean/_ + GradientBasedCorrMaskFlow real/_ + GradientBasedCorrMaskFullX real/_ + GradientBasedCorrMaskFullY real/_ + GradientBasedCorrMaskInputDigest string/_ + GradientBasedCorrMaskLeft real/_ + GradientBasedCorrMaskMaskActive boolean/_ + GradientBasedCorrMaskMaskBlendMode integer/_ + GradientBasedCorrMaskMaskDigest string/_ + GradientBasedCorrMaskMaskInverted boolean/_ + GradientBasedCorrMaskMaskName string/_ + GradientBasedCorrMaskMasks CorrectionMask Struct_+ + GradientBasedCorrMaskMasksAlpha real/_ + GradientBasedCorrMaskMasksAngle real/_ + GradientBasedCorrMaskMasksBottom real/_ + GradientBasedCorrMaskMasksCenterValue real/_ + GradientBasedCorrMaskMasksCenterWeight real/_ + GradientBasedCorrMaskMasksDabs string/_+ + GradientBasedCorrMaskMasksFeather real/_ + GradientBasedCorrMaskMasksFlipped boolean/_ + GradientBasedCorrMaskMasksFlow real/_ + GradientBasedCorrMaskMasksFullX real/_ + GradientBasedCorrMaskMasksFullY real/_ + GradientBasedCorrMaskMasksInputDigest string/_ + GradientBasedCorrMaskMasksLeft real/_ + GradientBasedCorrMaskMasksMaskActive boolean/_ + GradientBasedCorrMaskMasksMaskBlendMode integer/_ + GradientBasedCorrMaskMasksMaskDigest string/_ + GradientBasedCorrMaskMasksMaskInverted boolean/_ + GradientBasedCorrMaskMasksMaskName string/_ + GradientBasedCorrMaskMasksMaskSubType string/_ + GradientBasedCorrMaskMasksMaskSyncID string/_ + GradientBasedCorrMaskMasksValue real/_ + GradientBasedCorrMaskMasksMaskVersion string/_ + GradientBasedCorrMaskMasksMidpoint real/_ + GradientBasedCorrMaskMasksOrigin string/_ + GradientBasedCorrMaskMasksPerimeterValue real/_ + GradientBasedCorrMaskMasksRadius real/_ + GradientBasedCorrMaskMasksReferencePoint string/_ + GradientBasedCorrMaskMasksRight real/_ + GradientBasedCorrMaskMasksRoundness real/_ + GradientBasedCorrMaskMasksSizeX real/_ + GradientBasedCorrMaskMasksSizeY real/_ + GradientBasedCorrMaskMasksTop real/_ + GradientBasedCorrMaskMaskSubType string/_ + GradientBasedCorrMaskMasksVersion integer/_ + GradientBasedCorrMaskMasksWhat string/_ + GradientBasedCorrMaskMasksWholeImageArea string/_ + GradientBasedCorrMaskMasksX real/_ + GradientBasedCorrMaskMasksY real/_ + GradientBasedCorrMaskMaskSyncID string/_ + GradientBasedCorrMaskMasksZeroX real/_ + GradientBasedCorrMaskMasksZeroY real/_ + GradientBasedCorrMaskValue real/_ + GradientBasedCorrMaskMaskVersion string/_ + GradientBasedCorrMaskMidpoint real/_ + GradientBasedCorrMaskOrigin string/_ + GradientBasedCorrMaskPerimeterValue real/_ + GradientBasedCorrMaskRadius real/_ + GradientBasedCorrMaskReferencePoint string/_ + GradientBasedCorrMaskRight real/_ + GradientBasedCorrMaskRoundness real/_ + GradientBasedCorrMaskSizeX real/_ + GradientBasedCorrMaskSizeY real/_ + GradientBasedCorrMaskTop real/_ + GradientBasedCorrMaskVersion integer/_ + GradientBasedCorrMaskWhat string/_ + GradientBasedCorrMaskWholeImageArea string/_ + GradientBasedCorrMaskX real/_ + GradientBasedCorrMaskY real/_ + GradientBasedCorrMaskZeroX real/_ + GradientBasedCorrMaskZeroY real/_ + GradientBasedCorrCorrectionName string/_+ + GradientBasedCorrRangeMask CorrRangeMask Struct_+ + GradientBasedCorrRangeMaskAreaModels AreaModels Struct_+ + GradientBasedCorrRangeMaskAreaModelsComponents string/_+ + GradientBasedCorrRangeMaskAreaModelsColorSampleInfo string/_+ + GradientBasedCorrRangeMaskColorAmount real/_+ + GradientBasedCorrRangeMaskDepthFeather real/_+ + GradientBasedCorrRangeMaskDepthMax real/_+ + GradientBasedCorrRangeMaskDepthMin real/_+ + GradientBasedCorrRangeMaskInvert boolean/_+ + GradientBasedCorrRangeMaskLumFeather real/_+ + GradientBasedCorrRangeMaskLuminanceDepthSampleInfo string/_+ + GradientBasedCorrRangeMaskLumMax real/_+ + GradientBasedCorrRangeMaskLumMin real/_+ + GradientBasedCorrRangeMaskLumRange string/_+ + GradientBasedCorrRangeMaskSampleType integer/_+ + GradientBasedCorrRangeMaskType string/_+ + GradientBasedCorrRangeMaskVersion string/_+ + GradientBasedCorrCorrectionSyncID string/_+ + GradientBasedCorrBlacks2012 real/_ + GradientBasedCorrBrightness real/_ + GradientBasedCorrClarity real/_ + GradientBasedCorrClarity2012 real/_ + GradientBasedCorrContrast real/_ + GradientBasedCorrContrast2012 real/_ + GradientBasedCorrDefringe real/_ + GradientBasedCorrDehaze real/_ + GradientBasedCorrExposure real/_ + GradientBasedCorrExposure2012 real/_ + GradientBasedCorrHighlights2012 real/_ + GradientBasedCorrHue real/_ + GradientBasedCorrLuminanceNoise real/_ + GradientBasedCorrMoire real/_ + GradientBasedCorrSaturation real/_ + GradientBasedCorrShadows2012 real/_ + GradientBasedCorrSharpness real/_ + GradientBasedCorrTemperature real/_ + GradientBasedCorrTexture real/_ + GradientBasedCorrTint real/_ + GradientBasedCorrToningHue real/_ + GradientBasedCorrToningSaturation real/_ + GradientBasedCorrWhites2012 real/_ + GradientBasedCorrWhat string/_ + GrainAmount integer/ + GrainFrequency integer/ + GrainSeed integer/ + GrainSize integer/ + GrayMixerAqua integer/ + GrayMixerBlue integer/ + GrayMixerGreen integer/ + GrayMixerMagenta integer/ + GrayMixerOrange integer/ + GrayMixerPurple integer/ + GrayMixerRed integer/ + GrayMixerYellow integer/ + GreenHue integer/ + GreenSaturation integer/ + Group lang-alt/ + HasCrop boolean/ + HasSettings boolean/ + HDREditMode integer/ + Highlight2012 integer/ + HighlightRecovery integer/ + Highlights2012 integer/ + HueAdjustmentAqua integer/ + HueAdjustmentBlue integer/ + HueAdjustmentGreen integer/ + HueAdjustmentMagenta integer/ + HueAdjustmentOrange integer/ + HueAdjustmentPurple integer/ + HueAdjustmentRed integer/ + HueAdjustmentYellow integer/ + IncrementalTemperature integer/ + IncrementalTint integer/ + JPEGHandling string/ + LensManualDistortionAmount integer/ + LensProfileChromaticAberrationScale integer/ + LensProfileDigest string/ + LensProfileDistortionScale integer/ + LensProfileEnable integer/ + LensProfileFilename string/ + LensProfileIsEmbedded boolean/ + LensProfileMatchKeyCameraModelName string/ + LensProfileMatchKeyExifMake string/ + LensProfileMatchKeyExifModel string/ + LensProfileMatchKeyIsRaw boolean/ + LensProfileMatchKeyLensID string/ + LensProfileMatchKeyLensInfo string/ + LensProfileMatchKeyLensName string/ + LensProfileMatchKeySensorFormatFactor real/ + LensProfileName string/ + LensProfileSetup string/ + LensProfileVignettingScale integer/ + Look Look Struct + LookAmount string/_ + LookCluster string/_ + LookCopyright string/_ + LookGroup lang-alt/_ + LookName string/ + LookParameters LookParms Struct_ + LookParametersCameraProfile string/_ + LookParametersClarity2012 string/_ + LookParametersConvertToGrayscale string/_ + LookParametersLookTable string/_ + LookParametersProcessVersion string/_ + LookParametersToneCurvePV2012 string/_+ + LookParametersToneCurvePV2012Blue string/_+ + LookParametersToneCurvePV2012Green string/_+ + LookParametersToneCurvePV2012Red string/_+ + LookParametersVersion string/_ + LookSupportsAmount string/_ + LookSupportsMonochrome string/_ + LookSupportsOutputReferred string/_ + LookUUID string/_ + LuminanceAdjustmentAqua integer/ + LuminanceAdjustmentBlue integer/ + LuminanceAdjustmentGreen integer/ + LuminanceAdjustmentMagenta integer/ + LuminanceAdjustmentOrange integer/ + LuminanceAdjustmentPurple integer/ + LuminanceAdjustmentRed integer/ + LuminanceAdjustmentYellow integer/ + LuminanceNoiseReductionContrast integer/ + LuminanceNoiseReductionDetail integer/ + LuminanceSmoothing integer/ + MaskGroupBasedCorrections Correction Struct+ + MaskGroupBasedCorrActive boolean/_ + MaskGroupBasedCorrAmount real/_ + MaskGroupBasedCorrMask CorrectionMask Struct_+ + MaskGroupBasedCorrMaskAlpha real/_ + MaskGroupBasedCorrMaskAngle real/_ + MaskGroupBasedCorrMaskBottom real/_ + MaskGroupBasedCorrMaskCenterValue real/_ + MaskGroupBasedCorrMaskCenterWeight real/_ + MaskGroupBasedCorrMaskRange CorrRangeMask Struct_+ + MaskGroupBasedCorrMaskRangeAreaModels AreaModels Struct_+ + MaskGroupBasedCorrMaskRangeAreaModelsComponents string/_+ + MaskGroupBasedCorrMaskRangeAreaModelsColorSampleInfo string/_+ + MaskGroupBasedCorrMaskRangeColorAmount real/_+ + MaskGroupBasedCorrMaskRangeDepthFeather real/_+ + MaskGroupBasedCorrMaskRangeDepthMax real/_+ + MaskGroupBasedCorrMaskRangeDepthMin real/_+ + MaskGroupBasedCorrMaskRangeInvert boolean/_+ + MaskGroupBasedCorrMaskRangeLumFeather real/_+ + MaskGroupBasedCorrMaskRangeLuminanceDepthSampleInfo string/_+ + MaskGroupBasedCorrMaskRangeLumMax real/_+ + MaskGroupBasedCorrMaskRangeLumMin real/_+ + MaskGroupBasedCorrMaskRangeLumRange string/_+ + MaskGroupBasedCorrMaskRangeSampleType integer/_+ + MaskGroupBasedCorrMaskRangeType string/_+ + MaskGroupBasedCorrMaskRangeVersion string/_+ + MaskGroupBasedCorrMaskDabs string/_+ + MaskGroupBasedCorrMaskFeather real/_ + MaskGroupBasedCorrMaskFlipped boolean/_ + MaskGroupBasedCorrMaskFlow real/_ + MaskGroupBasedCorrMaskFullX real/_ + MaskGroupBasedCorrMaskFullY real/_ + MaskGroupBasedCorrMaskInputDigest string/_ + MaskGroupBasedCorrMaskLeft real/_ + MaskGroupBasedCorrMaskMaskActive boolean/_ + MaskGroupBasedCorrMaskMaskBlendMode integer/_ + MaskGroupBasedCorrMaskMaskDigest string/_ + MaskGroupBasedCorrMaskMaskInverted boolean/_ + MaskGroupBasedCorrMaskMaskName string/_ + MaskGroupBasedCorrMaskMasks CorrectionMask Struct_+ + MaskGroupBasedCorrMaskMasksAlpha real/_ + MaskGroupBasedCorrMaskMasksAngle real/_ + MaskGroupBasedCorrMaskMasksBottom real/_ + MaskGroupBasedCorrMaskMasksCenterValue real/_ + MaskGroupBasedCorrMaskMasksCenterWeight real/_ + MaskGroupBasedCorrMaskMasksDabs string/_+ + MaskGroupBasedCorrMaskMasksFeather real/_ + MaskGroupBasedCorrMaskMasksFlipped boolean/_ + MaskGroupBasedCorrMaskMasksFlow real/_ + MaskGroupBasedCorrMaskMasksFullX real/_ + MaskGroupBasedCorrMaskMasksFullY real/_ + MaskGroupBasedCorrMaskMasksInputDigest string/_ + MaskGroupBasedCorrMaskMasksLeft real/_ + MaskGroupBasedCorrMaskMasksMaskActive boolean/_ + MaskGroupBasedCorrMaskMasksMaskBlendMode integer/_ + MaskGroupBasedCorrMaskMasksMaskDigest string/_ + MaskGroupBasedCorrMaskMasksMaskInverted boolean/_ + MaskGroupBasedCorrMaskMasksMaskName string/_ + MaskGroupBasedCorrMaskMasksMaskSubType string/_ + MaskGroupBasedCorrMaskMasksMaskSyncID string/_ + MaskGroupBasedCorrMaskMasksValue real/_ + MaskGroupBasedCorrMaskMasksMaskVersion string/_ + MaskGroupBasedCorrMaskMasksMidpoint real/_ + MaskGroupBasedCorrMaskMasksOrigin string/_ + MaskGroupBasedCorrMaskMasksPerimeterValue real/_ + MaskGroupBasedCorrMaskMasksRadius real/_ + MaskGroupBasedCorrMaskMasksReferencePoint string/_ + MaskGroupBasedCorrMaskMasksRight real/_ + MaskGroupBasedCorrMaskMasksRoundness real/_ + MaskGroupBasedCorrMaskMasksSizeX real/_ + MaskGroupBasedCorrMaskMasksSizeY real/_ + MaskGroupBasedCorrMaskMasksTop real/_ + MaskGroupBasedCorrMaskMaskSubType string/_ + MaskGroupBasedCorrMaskMasksVersion integer/_ + MaskGroupBasedCorrMaskMasksWhat string/_ + MaskGroupBasedCorrMaskMasksWholeImageArea string/_ + MaskGroupBasedCorrMaskMasksX real/_ + MaskGroupBasedCorrMaskMasksY real/_ + MaskGroupBasedCorrMaskMaskSyncID string/_ + MaskGroupBasedCorrMaskMasksZeroX real/_ + MaskGroupBasedCorrMaskMasksZeroY real/_ + MaskGroupBasedCorrMaskValue real/_ + MaskGroupBasedCorrMaskMaskVersion string/_ + MaskGroupBasedCorrMaskMidpoint real/_ + MaskGroupBasedCorrMaskOrigin string/_ + MaskGroupBasedCorrMaskPerimeterValue real/_ + MaskGroupBasedCorrMaskRadius real/_ + MaskGroupBasedCorrMaskReferencePoint string/_ + MaskGroupBasedCorrMaskRight real/_ + MaskGroupBasedCorrMaskRoundness real/_ + MaskGroupBasedCorrMaskSizeX real/_ + MaskGroupBasedCorrMaskSizeY real/_ + MaskGroupBasedCorrMaskTop real/_ + MaskGroupBasedCorrMaskVersion integer/_ + MaskGroupBasedCorrMaskWhat string/_ + MaskGroupBasedCorrMaskWholeImageArea string/_ + MaskGroupBasedCorrMaskX real/_ + MaskGroupBasedCorrMaskY real/_ + MaskGroupBasedCorrMaskZeroX real/_ + MaskGroupBasedCorrMaskZeroY real/_ + MaskGroupBasedCorrCorrectionName string/_+ + MaskGroupBasedCorrRangeMask CorrRangeMask Struct_+ + MaskGroupBasedCorrRangeMaskAreaModels AreaModels Struct_+ + MaskGroupBasedCorrRangeMaskAreaModelsComponents string/_+ + MaskGroupBasedCorrRangeMaskAreaModelsColorSampleInfo string/_+ + MaskGroupBasedCorrRangeMaskColorAmount real/_+ + MaskGroupBasedCorrRangeMaskDepthFeather real/_+ + MaskGroupBasedCorrRangeMaskDepthMax real/_+ + MaskGroupBasedCorrRangeMaskDepthMin real/_+ + MaskGroupBasedCorrRangeMaskInvert boolean/_+ + MaskGroupBasedCorrRangeMaskLumFeather real/_+ + MaskGroupBasedCorrRangeMaskLuminanceDepthSampleInfo string/_+ + MaskGroupBasedCorrRangeMaskLumMax real/_+ + MaskGroupBasedCorrRangeMaskLumMin real/_+ + MaskGroupBasedCorrRangeMaskLumRange string/_+ + MaskGroupBasedCorrRangeMaskSampleType integer/_+ + MaskGroupBasedCorrRangeMaskType string/_+ + MaskGroupBasedCorrRangeMaskVersion string/_+ + MaskGroupBasedCorrCorrectionSyncID string/_+ + MaskGroupBasedCorrBlacks2012 real/_ + MaskGroupBasedCorrBrightness real/_ + MaskGroupBasedCorrClarity real/_ + MaskGroupBasedCorrClarity2012 real/_ + MaskGroupBasedCorrContrast real/_ + MaskGroupBasedCorrContrast2012 real/_ + MaskGroupBasedCorrDefringe real/_ + MaskGroupBasedCorrDehaze real/_ + MaskGroupBasedCorrExposure real/_ + MaskGroupBasedCorrExposure2012 real/_ + MaskGroupBasedCorrHighlights2012 real/_ + MaskGroupBasedCorrHue real/_ + MaskGroupBasedCorrLuminanceNoise real/_ + MaskGroupBasedCorrMoire real/_ + MaskGroupBasedCorrSaturation real/_ + MaskGroupBasedCorrShadows2012 real/_ + MaskGroupBasedCorrSharpness real/_ + MaskGroupBasedCorrTemperature real/_ + MaskGroupBasedCorrTexture real/_ + MaskGroupBasedCorrTint real/_ + MaskGroupBasedCorrToningHue real/_ + MaskGroupBasedCorrToningSaturation real/_ + MaskGroupBasedCorrWhites2012 real/_ + MaskGroupBasedCorrWhat string/_ + MoireFilter string/ + Name lang-alt/ + NegativeCacheLargePreviewSize integer/ + NegativeCacheMaximumSize real/ + NegativeCachePath string/ + OverrideLookVignette boolean/ + PaintBasedCorrections Correction Struct+ + PaintCorrectionActive boolean/_ + PaintCorrectionAmount real/_ + PaintBasedCorrectionMasks CorrectionMask Struct_+ + PaintCorrectionMaskAlpha real/_ + PaintCorrectionMaskAngle real/_ + PaintCorrectionMaskBottom real/_ + PaintCorrectionMaskCenterValue real/_ + PaintCorrectionMaskCenterWeight real/_ + PaintCorrectionMaskRange CorrRangeMask Struct_+ + PaintCorrectionMaskRangeAreaModels AreaModels Struct_+ + PaintCorrectionMaskRangeAreaModelsComponents string/_+ + PaintCorrectionMaskRangeAreaModelsColorSampleInfo string/_+ + PaintCorrectionMaskRangeColorAmount real/_+ + PaintCorrectionMaskRangeDepthFeather real/_+ + PaintCorrectionMaskRangeDepthMax real/_+ + PaintCorrectionMaskRangeDepthMin real/_+ + PaintCorrectionMaskRangeInvert boolean/_+ + PaintCorrectionMaskRangeLumFeather real/_+ + PaintCorrectionMaskRangeLuminanceDepthSampleInfo string/_+ + PaintCorrectionMaskRangeLumMax real/_+ + PaintCorrectionMaskRangeLumMin real/_+ + PaintCorrectionMaskRangeLumRange string/_+ + PaintCorrectionMaskRangeSampleType integer/_+ + PaintCorrectionMaskRangeType string/_+ + PaintCorrectionMaskRangeVersion string/_+ + PaintCorrectionMaskDabs string/_ + PaintCorrectionMaskFeather real/_ + PaintCorrectionMaskFlipped boolean/_ + PaintCorrectionMaskFlow real/_ + PaintCorrectionMaskFullX real/_ + PaintCorrectionMaskFullY real/_ + PaintCorrectionMaskInputDigest string/_ + PaintCorrectionMaskLeft real/_ + PaintCorrectionMaskMaskActive boolean/_ + PaintCorrectionMaskMaskBlendMode integer/_ + PaintCorrectionMaskMaskDigest string/_ + PaintCorrectionMaskMaskInverted boolean/_ + PaintCorrectionMaskMaskName string/_ + PaintCorrectionMaskMasks CorrectionMask Struct_+ + PaintCorrectionMaskMasksAlpha real/_ + PaintCorrectionMaskMasksAngle real/_ + PaintCorrectionMaskMasksBottom real/_ + PaintCorrectionMaskMasksCenterValue real/_ + PaintCorrectionMaskMasksCenterWeight real/_ + PaintCorrectionMaskMasksDabs string/_+ + PaintCorrectionMaskMasksFeather real/_ + PaintCorrectionMaskMasksFlipped boolean/_ + PaintCorrectionMaskMasksFlow real/_ + PaintCorrectionMaskMasksFullX real/_ + PaintCorrectionMaskMasksFullY real/_ + PaintCorrectionMaskMasksInputDigest string/_ + PaintCorrectionMaskMasksLeft real/_ + PaintCorrectionMaskMasksMaskActive boolean/_ + PaintCorrectionMaskMasksMaskBlendMode integer/_ + PaintCorrectionMaskMasksMaskDigest string/_ + PaintCorrectionMaskMasksMaskInverted boolean/_ + PaintCorrectionMaskMasksMaskName string/_ + PaintCorrectionMaskMasksMaskSubType string/_ + PaintCorrectionMaskMasksMaskSyncID string/_ + PaintCorrectionMaskMasksValue real/_ + PaintCorrectionMaskMasksMaskVersion string/_ + PaintCorrectionMaskMasksMidpoint real/_ + PaintCorrectionMaskMasksOrigin string/_ + PaintCorrectionMaskMasksPerimeterValue real/_ + PaintCorrectionMaskMasksRadius real/_ + PaintCorrectionMaskMasksReferencePoint string/_ + PaintCorrectionMaskMasksRight real/_ + PaintCorrectionMaskMasksRoundness real/_ + PaintCorrectionMaskMasksSizeX real/_ + PaintCorrectionMaskMasksSizeY real/_ + PaintCorrectionMaskMasksTop real/_ + PaintCorrectionMaskMaskSubType string/_ + PaintCorrectionMaskMasksVersion integer/_ + PaintCorrectionMaskMasksWhat string/_ + PaintCorrectionMaskMasksWholeImageArea string/_ + PaintCorrectionMaskMasksX real/_ + PaintCorrectionMaskMasksY real/_ + PaintCorrectionMaskMaskSyncID string/_ + PaintCorrectionMaskMasksZeroX real/_ + PaintCorrectionMaskMasksZeroY real/_ + PaintCorrectionMaskValue real/_ + PaintCorrectionMaskMaskVersion string/_ + PaintCorrectionMaskMidpoint real/_ + PaintCorrectionMaskOrigin string/_ + PaintCorrectionMaskPerimeterValue real/_ + PaintCorrectionMaskRadius real/_ + PaintCorrectionMaskReferencePoint string/_ + PaintCorrectionMaskRight real/_ + PaintCorrectionMaskRoundness real/_ + PaintCorrectionMaskSizeX real/_ + PaintCorrectionMaskSizeY real/_ + PaintCorrectionMaskTop real/_ + PaintCorrectionMaskVersion integer/_ + PaintCorrectionMaskWhat string/_ + PaintCorrectionMaskWholeImageArea string/_ + PaintCorrectionMaskX real/_ + PaintCorrectionMaskY real/_ + PaintCorrectionMaskZeroX real/_ + PaintCorrectionMaskZeroY real/_ + PaintCorrectionCorrectionName string/_+ + PaintCorrectionRangeMask CorrRangeMask Struct_+ + PaintCorrectionRangeMaskAreaModels AreaModels Struct_+ + PaintCorrectionRangeMaskAreaModelsComponents string/_+ + PaintCorrectionRangeMaskAreaModelsColorSampleInfo string/_+ + PaintCorrectionRangeMaskColorAmount real/_+ + PaintCorrectionRangeMaskDepthFeather real/_+ + PaintCorrectionRangeMaskDepthMax real/_+ + PaintCorrectionRangeMaskDepthMin real/_+ + PaintCorrectionRangeMaskInvert boolean/_+ + PaintCorrectionRangeMaskLumFeather real/_+ + PaintCorrectionRangeMaskLuminanceDepthSampleInfo string/_+ + PaintCorrectionRangeMaskLumMax real/_+ + PaintCorrectionRangeMaskLumMin real/_+ + PaintCorrectionRangeMaskLumRange string/_+ + PaintCorrectionRangeMaskSampleType integer/_+ + PaintCorrectionRangeMaskType string/_+ + PaintCorrectionRangeMaskVersion string/_+ + PaintCorrectionCorrectionSyncID string/_+ + PaintCorrectionBlacks2012 real/_ + PaintCorrectionBrightness real/_ + PaintCorrectionClarity real/_ + PaintCorrectionClarity2012 real/_ + PaintCorrectionContrast real/_ + PaintCorrectionContrast2012 real/_ + PaintCorrectionDefringe real/_ + PaintCorrectionDehaze real/_ + PaintCorrectionExposure real/_ + PaintCorrectionExposure2012 real/_ + PaintCorrectionHighlights2012 real/_ + PaintCorrectionHue real/_ + PaintCorrectionLuminanceNoise real/_ + PaintCorrectionMoire real/_ + PaintCorrectionSaturation real/_ + PaintCorrectionShadows2012 real/_ + PaintCorrectionSharpness real/_ + PaintCorrectionTemperature real/_ + PaintCorrectionTexture real/_ + PaintCorrectionTint real/_ + PaintCorrectionToningHue real/_ + PaintCorrectionToningSaturation real/_ + PaintCorrectionWhites2012 real/_ + PaintCorrectionWhat string/_ + ParametricDarks integer/ + ParametricHighlights integer/ + ParametricHighlightSplit integer/ + ParametricLights integer/ + ParametricMidtoneSplit integer/ + ParametricShadows integer/ + ParametricShadowSplit integer/ + PerspectiveAspect integer/ + PerspectiveHorizontal integer/ + PerspectiveRotate real/ + PerspectiveScale integer/ + PerspectiveUpright integer/ + PerspectiveVertical integer/ + PerspectiveX real/ + PerspectiveY real/ + PostCropVignetteAmount integer/ + PostCropVignetteFeather integer/ + PostCropVignetteHighlightContrast integer/ + PostCropVignetteMidpoint integer/ + PostCropVignetteRoundness integer/ + PostCropVignetteStyle integer/ + PresetType string/ + ProcessVersion string/ + RangeMask RangeMask Struct + RangeMaskMapInfo MapInfo Struct_ + RangeMaskMapInfoLabMax string/_ + RangeMaskMapInfoLabMin string/_ + RangeMaskMapInfoLumEq string/_+ + RangeMaskMapInfoRGBMax string/_ + RangeMaskMapInfoRGBMin string/_ + RawFileName string/ + RedEyeInfo string/+ + RedHue integer/ + RedSaturation integer/ + RetouchAreas RetouchArea Struct+ + RetouchAreaFeather real/_ + RetouchAreaMasks CorrectionMask Struct_+ + RetouchAreaMaskAlpha real/_ + RetouchAreaMaskAngle real/_ + RetouchAreaMaskBottom real/_ + RetouchAreaMaskCenterValue real/_ + RetouchAreaMaskCenterWeight real/_ + RetouchAreaMaskRange CorrRangeMask Struct_+ + RetouchAreaMaskRangeAreaModels AreaModels Struct_+ + RetouchAreaMaskRangeAreaModelsComponents string/_+ + RetouchAreaMaskRangeAreaModelsColorSampleInfo string/_+ + RetouchAreaMaskRangeColorAmount real/_+ + RetouchAreaMaskRangeDepthFeather real/_+ + RetouchAreaMaskRangeDepthMax real/_+ + RetouchAreaMaskRangeDepthMin real/_+ + RetouchAreaMaskRangeInvert boolean/_+ + RetouchAreaMaskRangeLumFeather real/_+ + RetouchAreaMaskRangeLuminanceDepthSampleInfo string/_+ + RetouchAreaMaskRangeLumMax real/_+ + RetouchAreaMaskRangeLumMin real/_+ + RetouchAreaMaskRangeLumRange string/_+ + RetouchAreaMaskRangeSampleType integer/_+ + RetouchAreaMaskRangeType string/_+ + RetouchAreaMaskRangeVersion string/_+ + RetouchAreaMaskDabs string/_ + RetouchAreaMaskFeather real/_ + RetouchAreaMaskFlipped boolean/_ + RetouchAreaMaskFlow real/_ + RetouchAreaMaskFullX real/_ + RetouchAreaMaskFullY real/_ + RetouchAreaMaskInputDigest string/_ + RetouchAreaMaskLeft real/_ + RetouchAreaMaskMaskActive boolean/_ + RetouchAreaMaskMaskBlendMode integer/_ + RetouchAreaMaskMaskDigest string/_ + RetouchAreaMaskMaskInverted boolean/_ + RetouchAreaMaskMaskName string/_ + RetouchAreaMaskMasks CorrectionMask Struct_+ + RetouchAreaMaskMasksAlpha real/_ + RetouchAreaMaskMasksAngle real/_ + RetouchAreaMaskMasksBottom real/_ + RetouchAreaMaskMasksCenterValue real/_ + RetouchAreaMaskMasksCenterWeight real/_ + RetouchAreaMaskMasksDabs string/_+ + RetouchAreaMaskMasksFeather real/_ + RetouchAreaMaskMasksFlipped boolean/_ + RetouchAreaMaskMasksFlow real/_ + RetouchAreaMaskMasksFullX real/_ + RetouchAreaMaskMasksFullY real/_ + RetouchAreaMaskMasksInputDigest string/_ + RetouchAreaMaskMasksLeft real/_ + RetouchAreaMaskMasksMaskActive boolean/_ + RetouchAreaMaskMasksMaskBlendMode integer/_ + RetouchAreaMaskMasksMaskDigest string/_ + RetouchAreaMaskMasksMaskInverted boolean/_ + RetouchAreaMaskMasksMaskName string/_ + RetouchAreaMaskMasksMaskSubType string/_ + RetouchAreaMaskMasksMaskSyncID string/_ + RetouchAreaMaskMasksValue real/_ + RetouchAreaMaskMasksMaskVersion string/_ + RetouchAreaMaskMasksMidpoint real/_ + RetouchAreaMaskMasksOrigin string/_ + RetouchAreaMaskMasksPerimeterValue real/_ + RetouchAreaMaskMasksRadius real/_ + RetouchAreaMaskMasksReferencePoint string/_ + RetouchAreaMaskMasksRight real/_ + RetouchAreaMaskMasksRoundness real/_ + RetouchAreaMaskMasksSizeX real/_ + RetouchAreaMaskMasksSizeY real/_ + RetouchAreaMaskMasksTop real/_ + RetouchAreaMaskMaskSubType string/_ + RetouchAreaMaskMasksVersion integer/_ + RetouchAreaMaskMasksWhat string/_ + RetouchAreaMaskMasksWholeImageArea string/_ + RetouchAreaMaskMasksX real/_ + RetouchAreaMaskMasksY real/_ + RetouchAreaMaskMaskSyncID string/_ + RetouchAreaMaskMasksZeroX real/_ + RetouchAreaMaskMasksZeroY real/_ + RetouchAreaMaskValue real/_ + RetouchAreaMaskMaskVersion string/_ + RetouchAreaMaskMidpoint real/_ + RetouchAreaMaskOrigin string/_ + RetouchAreaMaskPerimeterValue real/_ + RetouchAreaMaskRadius real/_ + RetouchAreaMaskReferencePoint string/_ + RetouchAreaMaskRight real/_ + RetouchAreaMaskRoundness real/_ + RetouchAreaMaskSizeX real/_ + RetouchAreaMaskSizeY real/_ + RetouchAreaMaskTop real/_ + RetouchAreaMaskVersion integer/_ + RetouchAreaMaskWhat string/_ + RetouchAreaMaskWholeImageArea string/_ + RetouchAreaMaskX real/_ + RetouchAreaMaskY real/_ + RetouchAreaMaskZeroX real/_ + RetouchAreaMaskZeroY real/_ + RetouchAreaMethod string/_ + RetouchAreaOffsetY real/_ + RetouchAreaOpacity real/_ + RetouchAreaSeed integer/_ + RetouchAreaSourceState string/_ + RetouchAreaSourceX real/_ + RetouchAreaSpotType string/_ + RetouchInfo string/+ + Saturation integer/ + SaturationAdjustmentAqua integer/ + SaturationAdjustmentBlue integer/ + SaturationAdjustmentGreen integer/ + SaturationAdjustmentMagenta integer/ + SaturationAdjustmentOrange integer/ + SaturationAdjustmentPurple integer/ + SaturationAdjustmentRed integer/ + SaturationAdjustmentYellow integer/ + SDRBlend real/ + SDRBrightness real/ + SDRContrast real/ + SDRHighlights real/ + SDRShadows real/ + SDRWhites real/ + Shadows integer/ + Shadows2012 integer/ + ShadowTint integer/ + SharpenDetail integer/ + SharpenEdgeMasking integer/ + SharpenRadius real/ + Sharpness integer/ + ShortName lang-alt/ + Smoothness integer/ + SortName lang-alt/ + SplitToningBalance integer/ + SplitToningHighlightHue integer/ + SplitToningHighlightSaturation integer/ + SplitToningShadowHue integer/ + SplitToningShadowSaturation integer/ + SupportsAmount boolean/ + SupportsColor boolean/ + SupportsHighDynamicRange boolean/ + SupportsMonochrome boolean/ + SupportsNormalDynamicRange boolean/ + SupportsOutputReferred boolean/ + SupportsSceneReferred boolean/ + ColorTemperature integer/ + Texture integer/ + TIFFHandling string/ + Tint integer/ + ToggleStyleAmount integer/ + ToggleStyleDigest string/ + ToneCurve string/+ + ToneCurveBlue string/+ + ToneCurveGreen string/+ + ToneCurveName string/ + ToneCurveName2012 string/ + ToneCurvePV2012 string/+ + ToneCurvePV2012Blue string/+ + ToneCurvePV2012Green string/+ + ToneCurvePV2012Red string/+ + ToneCurveRed string/+ + ToneMapStrength real/ + UprightCenterMode integer/ + UprightCenterNormX real/ + UprightCenterNormY real/ + UprightDependentDigest string/ + UprightFocalLength35mm real/ + UprightFocalMode integer/ + UprightFourSegments_0 string/ + UprightFourSegments_1 string/ + UprightFourSegments_2 string/ + UprightFourSegments_3 string/ + UprightFourSegmentsCount integer/ + UprightGuidedDependentDigest string/ + UprightPreview boolean/ + UprightTransform_0 string/ + UprightTransform_1 string/ + UprightTransform_2 string/ + UprightTransform_3 string/ + UprightTransform_4 string/ + UprightTransform_5 string/ + UprightTransformCount integer/ + UprightVersion integer/ + UUID string/ + Version string/ + Vibrance integer/ + VignetteAmount integer/ + VignetteMidpoint integer/ + What string/ + WhiteBalance string/ + Whites2012 integer/ + +=head3 XMP Correction Struct + + Field Name Writable + ---------- -------- + CorrectionActive boolean + CorrectionAmount real + CorrectionMasks CorrectionMask Struct+ + CorrectionName string + CorrRangeMask CorrRangeMask Struct + - + CorrectionSyncID string + LocalBlacks2012 real + LocalBrightness real + LocalClarity real + LocalClarity2012 real + LocalContrast real + LocalContrast2012 real + LocalDefringe real + LocalDehaze real + LocalExposure real + LocalExposure2012 real + LocalHighlights2012 real + LocalHue real + LocalLuminanceNoise real + LocalMoire real + LocalSaturation real + LocalShadows2012 real + LocalSharpness real + LocalTemperature real + LocalTexture real + LocalTint real + LocalToningHue real + LocalToningSaturation real + LocalWhites2012 real + What string + +=head3 XMP CorrectionMask Struct + + Field Name Writable + ---------- -------- + Alpha real + Angle real + Bottom real + CenterValue real + CenterWeight real + CorrRangeMask CorrRangeMask Struct + - + Dabs string+ + Feather real + Flipped boolean + Flow real + FullX real + FullY real + InputDigest string + Left real + MaskActive boolean + MaskBlendMode integer + MaskDigest string + MaskInverted boolean + MaskName string + MaskSubType string + MaskSyncID string + MaskValue real + MaskVersion string + Masks CorrectionMask Struct + Midpoint real + Origin string + PerimeterValue real + Radius real + ReferencePoint string + Right real + Roundness real + SizeX real + SizeY real + Top real + Version integer + What string + WholeImageArea string + X real + Y real + ZeroX real + ZeroY real + +=head3 XMP CorrRangeMask Struct + +Called CorrectionRangeMask by the spec. + + Field Name Writable + ---------- -------- + AreaModels AreaModels Struct+ + ColorAmount real + DepthFeather real + DepthMax real + DepthMin real + Invert boolean + LumFeather real + LumMax real + LumMin real + LumRange string + LuminanceDepthSampleInfo string + SampleType integer + Type string + Version string + +=head3 XMP AreaModels Struct + + Field Name Writable + ---------- -------- + AreaComponents string+ + ColorRangeMaskAreaSampleInfo string + +=head3 XMP Look Struct + + Field Name Writable + ---------- -------- + Amount string + Cluster string + Copyright string + Group lang-alt + Name string + Parameters LookParms Struct + SupportsAmount string + SupportsMonochrome string + SupportsOutputReferred string + UUID string + +=head3 XMP LookParms Struct + + Field Name Writable + ---------- -------- + CameraProfile string + Clarity2012 string + ConvertToGrayscale string + LookTable string + ProcessVersion string + ToneCurvePV2012 string+ + ToneCurvePV2012Blue string+ + ToneCurvePV2012Green string+ + ToneCurvePV2012Red string+ + Version string + +=head3 XMP RangeMask Struct + +This structure is actually called RangeMaskMapInfo, but it only contains one +element which is a RangeMaskMapInfo structure (Yes, really!). So these are +renamed to RangeMask and MapInfo respectively to avoid confusion and +redundancy in the tag names. + + Field Name Writable + ---------- -------- + RangeMaskMapInfo MapInfo Struct + +=head3 XMP MapInfo Struct + +Called RangeMaskMapInfo by the specification, the same as the containing +structure. + + Field Name Writable + ---------- -------- + LabMax string + LabMin string + LumEq string+ + RGBMax string + RGBMin string + +=head3 XMP RetouchArea Struct + + Field Name Writable + ---------- -------- + Feather real + Masks CorrectionMask Struct+ + Method string + OffsetY real + Opacity real + Seed integer + SourceState string + SourceX real + SpotType string + +=head3 XMP creatorAtom Tags + +Adobe creatorAtom tags, written by After Effects. + +These tags belong to the ExifTool XMP-creatorAtom family 1 group. + + Tag Name Writable + -------- -------- + AeProjectLink AEProjectLink Struct + AeProjectLinkCompositionID string_ + AeProjectLinkFullPath string_ + AeProjectLinkRenderOutputModuleIndex string_ + AeProjectLinkRenderQueueItemID string_ + AeProjectLinkRenderTimeStamp integer_ + MacAtom MacAtom Struct + MacAtomApplicationCode string_ + MacAtomInvocationAppleEvent string_ + MacAtomPosixProjectPath string_ + WindowsAtom WindowsAtom Struct + WindowsAtomExtension string_ + WindowsAtomInvocationFlags string_ + WindowsAtomUncProjectPath string_ + +=head3 XMP AEProjectLink Struct + + Field Name Writable + ---------- -------- + CompositionID string + FullPath string + RenderOutputModuleIndex string + RenderQueueItemID string + RenderTimeStamp integer + +=head3 XMP MacAtom Struct + + Field Name Writable + ---------- -------- + ApplicationCode string + InvocationAppleEvent string + PosixProjectPath string + +=head3 XMP WindowsAtom Struct + + Field Name Writable + ---------- -------- + Extension string + InvocationFlags string + UncProjectPath string + +=head3 XMP crs Tags + +Photoshop Camera Raw namespace tags. It is a shame that Adobe pollutes the +metadata space with these incredibly bulky image editing parameters. + +These tags belong to the ExifTool XMP-crs family 1 group. + + Tag Name Writable + -------- -------- + AlreadyApplied boolean + AutoBrightness boolean + AutoContrast boolean + AutoExposure boolean + AutoLateralCA integer + AutoShadows boolean + AutoTone boolean + AutoToneDigest string + AutoToneDigestNoSat string + AutoWhiteVersion integer + Blacks2012 integer + BlueHue integer + BlueSaturation integer + Brightness integer + CameraModelRestriction string + CameraProfile string + CameraProfileDigest string + ChromaticAberrationB integer + ChromaticAberrationR integer + CircularGradientBasedCorrections Correction Struct+ + CircGradBasedCorrActive boolean_ + CircGradBasedCorrAmount real_ + CircGradBasedCorrMasks CorrectionMask Struct_+ + CircGradBasedCorrMaskAlpha real_ + CircGradBasedCorrMaskAngle real_ + CircGradBasedCorrMaskBottom real_ + CircGradBasedCorrMaskCenterValue real_ + CircGradBasedCorrMaskCenterWeight real_ + CircGradBasedCorrMaskRange CorrRangeMask Struct_+ + CircGradBasedCorrMaskRangeAreaModels AreaModels Struct_+ + CircGradBasedCorrMaskRangeAreaModelsComponents string_+ + CircGradBasedCorrMaskRangeAreaModelsColorSampleInfo string_+ + CircGradBasedCorrMaskRangeColorAmount real_+ + CircGradBasedCorrMaskRangeDepthFeather real_+ + CircGradBasedCorrMaskRangeDepthMax real_+ + CircGradBasedCorrMaskRangeDepthMin real_+ + CircGradBasedCorrMaskRangeInvert boolean_+ + CircGradBasedCorrMaskRangeLumFeather real_+ + CircGradBasedCorrMaskRangeLuminanceDepthSampleInfo string_+ + CircGradBasedCorrMaskRangeLumMax real_+ + CircGradBasedCorrMaskRangeLumMin real_+ + CircGradBasedCorrMaskRangeLumRange string_+ + CircGradBasedCorrMaskRangeSampleType integer_+ + CircGradBasedCorrMaskRangeType string_+ + CircGradBasedCorrMaskRangeVersion string_+ + CircGradBasedCorrMaskDabs string_ + CircGradBasedCorrMaskFeather real_ + CircGradBasedCorrMaskFlipped boolean_ + CircGradBasedCorrMaskFlow real_ + CircGradBasedCorrMaskFullX real_ + CircGradBasedCorrMaskFullY real_ + CircGradBasedCorrMaskInputDigest string_ + CircGradBasedCorrMaskLeft real_ + CircGradBasedCorrMaskMaskActive boolean_ + CircGradBasedCorrMaskMaskBlendMode integer_ + CircGradBasedCorrMaskMaskDigest string_ + CircGradBasedCorrMaskMaskInverted boolean_ + CircGradBasedCorrMaskMaskName string_ + CircGradBasedCorrMaskMasks CorrectionMask Struct_+ + CircGradBasedCorrMaskMasksAlpha real_ + CircGradBasedCorrMaskMasksAngle real_ + CircGradBasedCorrMaskMasksBottom real_ + CircGradBasedCorrMaskMasksCenterValue real_ + CircGradBasedCorrMaskMasksCenterWeight real_ + CircGradBasedCorrMaskMasksDabs string_+ + CircGradBasedCorrMaskMasksFeather real_ + CircGradBasedCorrMaskMasksFlipped boolean_ + CircGradBasedCorrMaskMasksFlow real_ + CircGradBasedCorrMaskMasksFullX real_ + CircGradBasedCorrMaskMasksFullY real_ + CircGradBasedCorrMaskMasksInputDigest string_ + CircGradBasedCorrMaskMasksLeft real_ + CircGradBasedCorrMaskMasksMaskActive boolean_ + CircGradBasedCorrMaskMasksMaskBlendMode integer_ + CircGradBasedCorrMaskMasksMaskDigest string_ + CircGradBasedCorrMaskMasksMaskInverted boolean_ + CircGradBasedCorrMaskMasksMaskName string_ + CircGradBasedCorrMaskMasksMaskSubType string_ + CircGradBasedCorrMaskMasksMaskSyncID string_ + CircGradBasedCorrMaskMasksValue real_ + CircGradBasedCorrMaskMasksMaskVersion string_ + CircGradBasedCorrMaskMasksMidpoint real_ + CircGradBasedCorrMaskMasksOrigin string_ + CircGradBasedCorrMaskMasksPerimeterValue real_ + CircGradBasedCorrMaskMasksRadius real_ + CircGradBasedCorrMaskMasksReferencePoint string_ + CircGradBasedCorrMaskMasksRight real_ + CircGradBasedCorrMaskMasksRoundness real_ + CircGradBasedCorrMaskMasksSizeX real_ + CircGradBasedCorrMaskMasksSizeY real_ + CircGradBasedCorrMaskMasksTop real_ + CircGradBasedCorrMaskMaskSubType string_ + CircGradBasedCorrMaskMasksVersion integer_ + CircGradBasedCorrMaskMasksWhat string_ + CircGradBasedCorrMaskMasksWholeImageArea string_ + CircGradBasedCorrMaskMasksX real_ + CircGradBasedCorrMaskMasksY real_ + CircGradBasedCorrMaskMaskSyncID string_ + CircGradBasedCorrMaskMasksZeroX real_ + CircGradBasedCorrMaskMasksZeroY real_ + CircGradBasedCorrMaskValue real_ + CircGradBasedCorrMaskMaskVersion string_ + CircGradBasedCorrMaskMidpoint real_ + CircGradBasedCorrMaskOrigin string_ + CircGradBasedCorrMaskPerimeterValue real_ + CircGradBasedCorrMaskRadius real_ + CircGradBasedCorrMaskReferencePoint string_ + CircGradBasedCorrMaskRight real_ + CircGradBasedCorrMaskRoundness real_ + CircGradBasedCorrMaskSizeX real_ + CircGradBasedCorrMaskSizeY real_ + CircGradBasedCorrMaskTop real_ + CircGradBasedCorrMaskVersion integer_ + CircGradBasedCorrMaskWhat string_ + CircGradBasedCorrMaskWholeImageArea string_ + CircGradBasedCorrMaskX real_ + CircGradBasedCorrMaskY real_ + CircGradBasedCorrMaskZeroX real_ + CircGradBasedCorrMaskZeroY real_ + CircGradBasedCorrCorrectionName string_+ + CircGradBasedCorrRangeMask CorrRangeMask Struct_+ + CircGradBasedCorrRangeMaskAreaModels AreaModels Struct_+ + CircGradBasedCorrRangeMaskAreaModelsComponents string_+ + CircGradBasedCorrRangeMaskAreaModelsColorSampleInfo string_+ + CircGradBasedCorrRangeMaskColorAmount real_+ + CircGradBasedCorrRangeMaskDepthFeather real_+ + CircGradBasedCorrRangeMaskDepthMax real_+ + CircGradBasedCorrRangeMaskDepthMin real_+ + CircGradBasedCorrRangeMaskInvert boolean_+ + CircGradBasedCorrRangeMaskLumFeather real_+ + CircGradBasedCorrRangeMaskLuminanceDepthSampleInfo string_+ + CircGradBasedCorrRangeMaskLumMax real_+ + CircGradBasedCorrRangeMaskLumMin real_+ + CircGradBasedCorrRangeMaskLumRange string_+ + CircGradBasedCorrRangeMaskSampleType integer_+ + CircGradBasedCorrRangeMaskType string_+ + CircGradBasedCorrRangeMaskVersion string_+ + CircGradBasedCorrCorrectionSyncID string_+ + CircGradBasedCorrBlacks2012 real_ + CircGradBasedCorrBrightness real_ + CircGradBasedCorrClarity real_ + CircGradBasedCorrClarity2012 real_ + CircGradBasedCorrContrast real_ + CircGradBasedCorrContrast2012 real_ + CircGradBasedCorrDefringe real_ + CircGradBasedCorrDehaze real_ + CircGradBasedCorrExposure real_ + CircGradBasedCorrExposure2012 real_ + CircGradBasedCorrHighlights2012 real_ + CircGradBasedCorrHue real_ + CircGradBasedCorrLuminanceNoise real_ + CircGradBasedCorrMoire real_ + CircGradBasedCorrSaturation real_ + CircGradBasedCorrShadows2012 real_ + CircGradBasedCorrSharpness real_ + CircGradBasedCorrTemperature real_ + CircGradBasedCorrTexture real_ + CircGradBasedCorrTint real_ + CircGradBasedCorrToningHue real_ + CircGradBasedCorrToningSaturation real_ + CircGradBasedCorrWhites2012 real_ + CircGradBasedCorrWhat string_ + Clarity integer + Clarity2012 integer + ClipboardAspectRatio integer + ClipboardOrientation integer + Cluster string + ColorGradeBlending integer + ColorGradeGlobalHue integer + ColorGradeGlobalLum integer + ColorGradeGlobalSat integer + ColorGradeHighlightLum integer + ColorGradeMidtoneHue integer + ColorGradeMidtoneLum integer + ColorGradeMidtoneSat integer + ColorGradeShadowLum integer + ColorNoiseReduction integer + ColorNoiseReductionDetail integer + ColorNoiseReductionSmoothness integer + CompatibleVersion string + ContactInfo string + Contrast integer/ + Contrast2012 integer + Converter string + ConvertToGrayscale boolean + Copyright string/ + CropAngle real + CropBottom real + CropConstrainToWarp integer + CropHeight real + CropLeft real + CropRight real + CropTop real + CropUnit integer + CropUnits integer + CropWidth real + DefaultAutoGray boolean + DefaultAutoTone boolean + DefaultsSpecificToISO boolean + DefaultsSpecificToSerial boolean + Defringe integer + DefringeGreenAmount integer + DefringeGreenHueHi integer + DefringeGreenHueLo integer + DefringePurpleAmount integer + DefringePurpleHueHi integer + DefringePurpleHueLo integer + Dehaze real + Description lang-alt/ + DNGIgnoreSidecars boolean + Exposure real + Exposure2012 real + FillLight integer + GradientBasedCorrections Correction Struct+ + GradientBasedCorrActive boolean_ + GradientBasedCorrAmount real_ + GradientBasedCorrMasks CorrectionMask Struct_+ + GradientBasedCorrMaskAlpha real_ + GradientBasedCorrMaskAngle real_ + GradientBasedCorrMaskBottom real_ + GradientBasedCorrMaskCenterValue real_ + GradientBasedCorrMaskCenterWeight real_ + GradientBasedCorrMaskRange CorrRangeMask Struct_+ + GradientBasedCorrMaskRangeAreaModels AreaModels Struct_+ + GradientBasedCorrMaskRangeAreaModelsComponents string_+ + GradientBasedCorrMaskRangeAreaModelsColorSampleInfo string_+ + GradientBasedCorrMaskRangeColorAmount real_+ + GradientBasedCorrMaskRangeDepthFeather real_+ + GradientBasedCorrMaskRangeDepthMax real_+ + GradientBasedCorrMaskRangeDepthMin real_+ + GradientBasedCorrMaskRangeInvert boolean_+ + GradientBasedCorrMaskRangeLumFeather real_+ + GradientBasedCorrMaskRangeLuminanceDepthSampleInfo string_+ + GradientBasedCorrMaskRangeLumMax real_+ + GradientBasedCorrMaskRangeLumMin real_+ + GradientBasedCorrMaskRangeLumRange string_+ + GradientBasedCorrMaskRangeSampleType integer_+ + GradientBasedCorrMaskRangeType string_+ + GradientBasedCorrMaskRangeVersion string_+ + GradientBasedCorrMaskDabs string_ + GradientBasedCorrMaskFeather real_ + GradientBasedCorrMaskFlipped boolean_ + GradientBasedCorrMaskFlow real_ + GradientBasedCorrMaskFullX real_ + GradientBasedCorrMaskFullY real_ + GradientBasedCorrMaskInputDigest string_ + GradientBasedCorrMaskLeft real_ + GradientBasedCorrMaskMaskActive boolean_ + GradientBasedCorrMaskMaskBlendMode integer_ + GradientBasedCorrMaskMaskDigest string_ + GradientBasedCorrMaskMaskInverted boolean_ + GradientBasedCorrMaskMaskName string_ + GradientBasedCorrMaskMasks CorrectionMask Struct_+ + GradientBasedCorrMaskMasksAlpha real_ + GradientBasedCorrMaskMasksAngle real_ + GradientBasedCorrMaskMasksBottom real_ + GradientBasedCorrMaskMasksCenterValue real_ + GradientBasedCorrMaskMasksCenterWeight real_ + GradientBasedCorrMaskMasksDabs string_+ + GradientBasedCorrMaskMasksFeather real_ + GradientBasedCorrMaskMasksFlipped boolean_ + GradientBasedCorrMaskMasksFlow real_ + GradientBasedCorrMaskMasksFullX real_ + GradientBasedCorrMaskMasksFullY real_ + GradientBasedCorrMaskMasksInputDigest string_ + GradientBasedCorrMaskMasksLeft real_ + GradientBasedCorrMaskMasksMaskActive boolean_ + GradientBasedCorrMaskMasksMaskBlendMode integer_ + GradientBasedCorrMaskMasksMaskDigest string_ + GradientBasedCorrMaskMasksMaskInverted boolean_ + GradientBasedCorrMaskMasksMaskName string_ + GradientBasedCorrMaskMasksMaskSubType string_ + GradientBasedCorrMaskMasksMaskSyncID string_ + GradientBasedCorrMaskMasksValue real_ + GradientBasedCorrMaskMasksMaskVersion string_ + GradientBasedCorrMaskMasksMidpoint real_ + GradientBasedCorrMaskMasksOrigin string_ + GradientBasedCorrMaskMasksPerimeterValue real_ + GradientBasedCorrMaskMasksRadius real_ + GradientBasedCorrMaskMasksReferencePoint string_ + GradientBasedCorrMaskMasksRight real_ + GradientBasedCorrMaskMasksRoundness real_ + GradientBasedCorrMaskMasksSizeX real_ + GradientBasedCorrMaskMasksSizeY real_ + GradientBasedCorrMaskMasksTop real_ + GradientBasedCorrMaskMaskSubType string_ + GradientBasedCorrMaskMasksVersion integer_ + GradientBasedCorrMaskMasksWhat string_ + GradientBasedCorrMaskMasksWholeImageArea string_ + GradientBasedCorrMaskMasksX real_ + GradientBasedCorrMaskMasksY real_ + GradientBasedCorrMaskMaskSyncID string_ + GradientBasedCorrMaskMasksZeroX real_ + GradientBasedCorrMaskMasksZeroY real_ + GradientBasedCorrMaskValue real_ + GradientBasedCorrMaskMaskVersion string_ + GradientBasedCorrMaskMidpoint real_ + GradientBasedCorrMaskOrigin string_ + GradientBasedCorrMaskPerimeterValue real_ + GradientBasedCorrMaskRadius real_ + GradientBasedCorrMaskReferencePoint string_ + GradientBasedCorrMaskRight real_ + GradientBasedCorrMaskRoundness real_ + GradientBasedCorrMaskSizeX real_ + GradientBasedCorrMaskSizeY real_ + GradientBasedCorrMaskTop real_ + GradientBasedCorrMaskVersion integer_ + GradientBasedCorrMaskWhat string_ + GradientBasedCorrMaskWholeImageArea string_ + GradientBasedCorrMaskX real_ + GradientBasedCorrMaskY real_ + GradientBasedCorrMaskZeroX real_ + GradientBasedCorrMaskZeroY real_ + GradientBasedCorrCorrectionName string_+ + GradientBasedCorrRangeMask CorrRangeMask Struct_+ + GradientBasedCorrRangeMaskAreaModels AreaModels Struct_+ + GradientBasedCorrRangeMaskAreaModelsComponents string_+ + GradientBasedCorrRangeMaskAreaModelsColorSampleInfo string_+ + GradientBasedCorrRangeMaskColorAmount real_+ + GradientBasedCorrRangeMaskDepthFeather real_+ + GradientBasedCorrRangeMaskDepthMax real_+ + GradientBasedCorrRangeMaskDepthMin real_+ + GradientBasedCorrRangeMaskInvert boolean_+ + GradientBasedCorrRangeMaskLumFeather real_+ + GradientBasedCorrRangeMaskLuminanceDepthSampleInfo string_+ + GradientBasedCorrRangeMaskLumMax real_+ + GradientBasedCorrRangeMaskLumMin real_+ + GradientBasedCorrRangeMaskLumRange string_+ + GradientBasedCorrRangeMaskSampleType integer_+ + GradientBasedCorrRangeMaskType string_+ + GradientBasedCorrRangeMaskVersion string_+ + GradientBasedCorrCorrectionSyncID string_+ + GradientBasedCorrBlacks2012 real_ + GradientBasedCorrBrightness real_ + GradientBasedCorrClarity real_ + GradientBasedCorrClarity2012 real_ + GradientBasedCorrContrast real_ + GradientBasedCorrContrast2012 real_ + GradientBasedCorrDefringe real_ + GradientBasedCorrDehaze real_ + GradientBasedCorrExposure real_ + GradientBasedCorrExposure2012 real_ + GradientBasedCorrHighlights2012 real_ + GradientBasedCorrHue real_ + GradientBasedCorrLuminanceNoise real_ + GradientBasedCorrMoire real_ + GradientBasedCorrSaturation real_ + GradientBasedCorrShadows2012 real_ + GradientBasedCorrSharpness real_ + GradientBasedCorrTemperature real_ + GradientBasedCorrTexture real_ + GradientBasedCorrTint real_ + GradientBasedCorrToningHue real_ + GradientBasedCorrToningSaturation real_ + GradientBasedCorrWhites2012 real_ + GradientBasedCorrWhat string_ + GrainAmount integer + GrainFrequency integer + GrainSeed integer + GrainSize integer + GrayMixerAqua integer + GrayMixerBlue integer + GrayMixerGreen integer + GrayMixerMagenta integer + GrayMixerOrange integer + GrayMixerPurple integer + GrayMixerRed integer + GrayMixerYellow integer + GreenHue integer + GreenSaturation integer + Group lang-alt/ + HasCrop boolean + HasSettings boolean + HDREditMode integer + Highlight2012 integer + HighlightRecovery integer + Highlights2012 integer + HueAdjustmentAqua integer + HueAdjustmentBlue integer + HueAdjustmentGreen integer + HueAdjustmentMagenta integer + HueAdjustmentOrange integer + HueAdjustmentPurple integer + HueAdjustmentRed integer + HueAdjustmentYellow integer + IncrementalTemperature integer + IncrementalTint integer + JPEGHandling string + LensManualDistortionAmount integer + LensProfileChromaticAberrationScale integer + LensProfileDigest string + LensProfileDistortionScale integer + LensProfileEnable integer + LensProfileFilename string + LensProfileIsEmbedded boolean + LensProfileMatchKeyCameraModelName string + LensProfileMatchKeyExifMake string + LensProfileMatchKeyExifModel string + LensProfileMatchKeyIsRaw boolean + LensProfileMatchKeyLensID string + LensProfileMatchKeyLensInfo string + LensProfileMatchKeyLensName string + LensProfileMatchKeySensorFormatFactor real + LensProfileName string + LensProfileSetup string + LensProfileVignettingScale integer + Look Look Struct + LookAmount string_ + LookCluster string_ + LookCopyright string_ + LookGroup lang-alt_ + LookName string + LookParameters LookParms Struct_ + LookParametersCameraProfile string_ + LookParametersClarity2012 string_ + LookParametersConvertToGrayscale string_ + LookParametersLookTable string_ + LookParametersProcessVersion string_ + LookParametersToneCurvePV2012 string_+ + LookParametersToneCurvePV2012Blue string_+ + LookParametersToneCurvePV2012Green string_+ + LookParametersToneCurvePV2012Red string_+ + LookParametersVersion string_ + LookSupportsAmount string_ + LookSupportsMonochrome string_ + LookSupportsOutputReferred string_ + LookUUID string_ + LuminanceAdjustmentAqua integer + LuminanceAdjustmentBlue integer + LuminanceAdjustmentGreen integer + LuminanceAdjustmentMagenta integer + LuminanceAdjustmentOrange integer + LuminanceAdjustmentPurple integer + LuminanceAdjustmentRed integer + LuminanceAdjustmentYellow integer + LuminanceNoiseReductionContrast integer + LuminanceNoiseReductionDetail integer + LuminanceSmoothing integer + MaskGroupBasedCorrections Correction Struct+ + MaskGroupBasedCorrActive boolean_ + MaskGroupBasedCorrAmount real_ + MaskGroupBasedCorrMask CorrectionMask Struct_+ + MaskGroupBasedCorrMaskAlpha real_ + MaskGroupBasedCorrMaskAngle real_ + MaskGroupBasedCorrMaskBottom real_ + MaskGroupBasedCorrMaskCenterValue real_ + MaskGroupBasedCorrMaskCenterWeight real_ + MaskGroupBasedCorrMaskRange CorrRangeMask Struct_+ + MaskGroupBasedCorrMaskRangeAreaModels AreaModels Struct_+ + MaskGroupBasedCorrMaskRangeAreaModelsComponents string_+ + MaskGroupBasedCorrMaskRangeAreaModelsColorSampleInfo string_+ + MaskGroupBasedCorrMaskRangeColorAmount real_+ + MaskGroupBasedCorrMaskRangeDepthFeather real_+ + MaskGroupBasedCorrMaskRangeDepthMax real_+ + MaskGroupBasedCorrMaskRangeDepthMin real_+ + MaskGroupBasedCorrMaskRangeInvert boolean_+ + MaskGroupBasedCorrMaskRangeLumFeather real_+ + MaskGroupBasedCorrMaskRangeLuminanceDepthSampleInfo string_+ + MaskGroupBasedCorrMaskRangeLumMax real_+ + MaskGroupBasedCorrMaskRangeLumMin real_+ + MaskGroupBasedCorrMaskRangeLumRange string_+ + MaskGroupBasedCorrMaskRangeSampleType integer_+ + MaskGroupBasedCorrMaskRangeType string_+ + MaskGroupBasedCorrMaskRangeVersion string_+ + MaskGroupBasedCorrMaskDabs string_+ + MaskGroupBasedCorrMaskFeather real_ + MaskGroupBasedCorrMaskFlipped boolean_ + MaskGroupBasedCorrMaskFlow real_ + MaskGroupBasedCorrMaskFullX real_ + MaskGroupBasedCorrMaskFullY real_ + MaskGroupBasedCorrMaskInputDigest string_ + MaskGroupBasedCorrMaskLeft real_ + MaskGroupBasedCorrMaskMaskActive boolean_ + MaskGroupBasedCorrMaskMaskBlendMode integer_ + MaskGroupBasedCorrMaskMaskDigest string_ + MaskGroupBasedCorrMaskMaskInverted boolean_ + MaskGroupBasedCorrMaskMaskName string_ + MaskGroupBasedCorrMaskMasks CorrectionMask Struct_+ + MaskGroupBasedCorrMaskMasksAlpha real_ + MaskGroupBasedCorrMaskMasksAngle real_ + MaskGroupBasedCorrMaskMasksBottom real_ + MaskGroupBasedCorrMaskMasksCenterValue real_ + MaskGroupBasedCorrMaskMasksCenterWeight real_ + MaskGroupBasedCorrMaskMasksDabs string_+ + MaskGroupBasedCorrMaskMasksFeather real_ + MaskGroupBasedCorrMaskMasksFlipped boolean_ + MaskGroupBasedCorrMaskMasksFlow real_ + MaskGroupBasedCorrMaskMasksFullX real_ + MaskGroupBasedCorrMaskMasksFullY real_ + MaskGroupBasedCorrMaskMasksInputDigest string_ + MaskGroupBasedCorrMaskMasksLeft real_ + MaskGroupBasedCorrMaskMasksMaskActive boolean_ + MaskGroupBasedCorrMaskMasksMaskBlendMode integer_ + MaskGroupBasedCorrMaskMasksMaskDigest string_ + MaskGroupBasedCorrMaskMasksMaskInverted boolean_ + MaskGroupBasedCorrMaskMasksMaskName string_ + MaskGroupBasedCorrMaskMasksMaskSubType string_ + MaskGroupBasedCorrMaskMasksMaskSyncID string_ + MaskGroupBasedCorrMaskMasksValue real_ + MaskGroupBasedCorrMaskMasksMaskVersion string_ + MaskGroupBasedCorrMaskMasksMidpoint real_ + MaskGroupBasedCorrMaskMasksOrigin string_ + MaskGroupBasedCorrMaskMasksPerimeterValue real_ + MaskGroupBasedCorrMaskMasksRadius real_ + MaskGroupBasedCorrMaskMasksReferencePoint string_ + MaskGroupBasedCorrMaskMasksRight real_ + MaskGroupBasedCorrMaskMasksRoundness real_ + MaskGroupBasedCorrMaskMasksSizeX real_ + MaskGroupBasedCorrMaskMasksSizeY real_ + MaskGroupBasedCorrMaskMasksTop real_ + MaskGroupBasedCorrMaskMaskSubType string_ + MaskGroupBasedCorrMaskMasksVersion integer_ + MaskGroupBasedCorrMaskMasksWhat string_ + MaskGroupBasedCorrMaskMasksWholeImageArea string_ + MaskGroupBasedCorrMaskMasksX real_ + MaskGroupBasedCorrMaskMasksY real_ + MaskGroupBasedCorrMaskMaskSyncID string_ + MaskGroupBasedCorrMaskMasksZeroX real_ + MaskGroupBasedCorrMaskMasksZeroY real_ + MaskGroupBasedCorrMaskValue real_ + MaskGroupBasedCorrMaskMaskVersion string_ + MaskGroupBasedCorrMaskMidpoint real_ + MaskGroupBasedCorrMaskOrigin string_ + MaskGroupBasedCorrMaskPerimeterValue real_ + MaskGroupBasedCorrMaskRadius real_ + MaskGroupBasedCorrMaskReferencePoint string_ + MaskGroupBasedCorrMaskRight real_ + MaskGroupBasedCorrMaskRoundness real_ + MaskGroupBasedCorrMaskSizeX real_ + MaskGroupBasedCorrMaskSizeY real_ + MaskGroupBasedCorrMaskTop real_ + MaskGroupBasedCorrMaskVersion integer_ + MaskGroupBasedCorrMaskWhat string_ + MaskGroupBasedCorrMaskWholeImageArea string_ + MaskGroupBasedCorrMaskX real_ + MaskGroupBasedCorrMaskY real_ + MaskGroupBasedCorrMaskZeroX real_ + MaskGroupBasedCorrMaskZeroY real_ + MaskGroupBasedCorrCorrectionName string_+ + MaskGroupBasedCorrRangeMask CorrRangeMask Struct_+ + MaskGroupBasedCorrRangeMaskAreaModels AreaModels Struct_+ + MaskGroupBasedCorrRangeMaskAreaModelsComponents string_+ + MaskGroupBasedCorrRangeMaskAreaModelsColorSampleInfo string_+ + MaskGroupBasedCorrRangeMaskColorAmount real_+ + MaskGroupBasedCorrRangeMaskDepthFeather real_+ + MaskGroupBasedCorrRangeMaskDepthMax real_+ + MaskGroupBasedCorrRangeMaskDepthMin real_+ + MaskGroupBasedCorrRangeMaskInvert boolean_+ + MaskGroupBasedCorrRangeMaskLumFeather real_+ + MaskGroupBasedCorrRangeMaskLuminanceDepthSampleInfo string_+ + MaskGroupBasedCorrRangeMaskLumMax real_+ + MaskGroupBasedCorrRangeMaskLumMin real_+ + MaskGroupBasedCorrRangeMaskLumRange string_+ + MaskGroupBasedCorrRangeMaskSampleType integer_+ + MaskGroupBasedCorrRangeMaskType string_+ + MaskGroupBasedCorrRangeMaskVersion string_+ + MaskGroupBasedCorrCorrectionSyncID string_+ + MaskGroupBasedCorrBlacks2012 real_ + MaskGroupBasedCorrBrightness real_ + MaskGroupBasedCorrClarity real_ + MaskGroupBasedCorrClarity2012 real_ + MaskGroupBasedCorrContrast real_ + MaskGroupBasedCorrContrast2012 real_ + MaskGroupBasedCorrDefringe real_ + MaskGroupBasedCorrDehaze real_ + MaskGroupBasedCorrExposure real_ + MaskGroupBasedCorrExposure2012 real_ + MaskGroupBasedCorrHighlights2012 real_ + MaskGroupBasedCorrHue real_ + MaskGroupBasedCorrLuminanceNoise real_ + MaskGroupBasedCorrMoire real_ + MaskGroupBasedCorrSaturation real_ + MaskGroupBasedCorrShadows2012 real_ + MaskGroupBasedCorrSharpness real_ + MaskGroupBasedCorrTemperature real_ + MaskGroupBasedCorrTexture real_ + MaskGroupBasedCorrTint real_ + MaskGroupBasedCorrToningHue real_ + MaskGroupBasedCorrToningSaturation real_ + MaskGroupBasedCorrWhites2012 real_ + MaskGroupBasedCorrWhat string_ + MoireFilter string + Name lang-alt/ + NegativeCacheLargePreviewSize integer + NegativeCacheMaximumSize real + NegativeCachePath string + OverrideLookVignette boolean + PaintBasedCorrections Correction Struct+ + PaintCorrectionActive boolean_ + PaintCorrectionAmount real_ + PaintBasedCorrectionMasks CorrectionMask Struct_+ + PaintCorrectionMaskAlpha real_ + PaintCorrectionMaskAngle real_ + PaintCorrectionMaskBottom real_ + PaintCorrectionMaskCenterValue real_ + PaintCorrectionMaskCenterWeight real_ + PaintCorrectionMaskRange CorrRangeMask Struct_+ + PaintCorrectionMaskRangeAreaModels AreaModels Struct_+ + PaintCorrectionMaskRangeAreaModelsComponents string_+ + PaintCorrectionMaskRangeAreaModelsColorSampleInfo string_+ + PaintCorrectionMaskRangeColorAmount real_+ + PaintCorrectionMaskRangeDepthFeather real_+ + PaintCorrectionMaskRangeDepthMax real_+ + PaintCorrectionMaskRangeDepthMin real_+ + PaintCorrectionMaskRangeInvert boolean_+ + PaintCorrectionMaskRangeLumFeather real_+ + PaintCorrectionMaskRangeLuminanceDepthSampleInfo string_+ + PaintCorrectionMaskRangeLumMax real_+ + PaintCorrectionMaskRangeLumMin real_+ + PaintCorrectionMaskRangeLumRange string_+ + PaintCorrectionMaskRangeSampleType integer_+ + PaintCorrectionMaskRangeType string_+ + PaintCorrectionMaskRangeVersion string_+ + PaintCorrectionMaskDabs string_ + PaintCorrectionMaskFeather real_ + PaintCorrectionMaskFlipped boolean_ + PaintCorrectionMaskFlow real_ + PaintCorrectionMaskFullX real_ + PaintCorrectionMaskFullY real_ + PaintCorrectionMaskInputDigest string_ + PaintCorrectionMaskLeft real_ + PaintCorrectionMaskMaskActive boolean_ + PaintCorrectionMaskMaskBlendMode integer_ + PaintCorrectionMaskMaskDigest string_ + PaintCorrectionMaskMaskInverted boolean_ + PaintCorrectionMaskMaskName string_ + PaintCorrectionMaskMasks CorrectionMask Struct_+ + PaintCorrectionMaskMasksAlpha real_ + PaintCorrectionMaskMasksAngle real_ + PaintCorrectionMaskMasksBottom real_ + PaintCorrectionMaskMasksCenterValue real_ + PaintCorrectionMaskMasksCenterWeight real_ + PaintCorrectionMaskMasksDabs string_+ + PaintCorrectionMaskMasksFeather real_ + PaintCorrectionMaskMasksFlipped boolean_ + PaintCorrectionMaskMasksFlow real_ + PaintCorrectionMaskMasksFullX real_ + PaintCorrectionMaskMasksFullY real_ + PaintCorrectionMaskMasksInputDigest string_ + PaintCorrectionMaskMasksLeft real_ + PaintCorrectionMaskMasksMaskActive boolean_ + PaintCorrectionMaskMasksMaskBlendMode integer_ + PaintCorrectionMaskMasksMaskDigest string_ + PaintCorrectionMaskMasksMaskInverted boolean_ + PaintCorrectionMaskMasksMaskName string_ + PaintCorrectionMaskMasksMaskSubType string_ + PaintCorrectionMaskMasksMaskSyncID string_ + PaintCorrectionMaskMasksValue real_ + PaintCorrectionMaskMasksMaskVersion string_ + PaintCorrectionMaskMasksMidpoint real_ + PaintCorrectionMaskMasksOrigin string_ + PaintCorrectionMaskMasksPerimeterValue real_ + PaintCorrectionMaskMasksRadius real_ + PaintCorrectionMaskMasksReferencePoint string_ + PaintCorrectionMaskMasksRight real_ + PaintCorrectionMaskMasksRoundness real_ + PaintCorrectionMaskMasksSizeX real_ + PaintCorrectionMaskMasksSizeY real_ + PaintCorrectionMaskMasksTop real_ + PaintCorrectionMaskMaskSubType string_ + PaintCorrectionMaskMasksVersion integer_ + PaintCorrectionMaskMasksWhat string_ + PaintCorrectionMaskMasksWholeImageArea string_ + PaintCorrectionMaskMasksX real_ + PaintCorrectionMaskMasksY real_ + PaintCorrectionMaskMaskSyncID string_ + PaintCorrectionMaskMasksZeroX real_ + PaintCorrectionMaskMasksZeroY real_ + PaintCorrectionMaskValue real_ + PaintCorrectionMaskMaskVersion string_ + PaintCorrectionMaskMidpoint real_ + PaintCorrectionMaskOrigin string_ + PaintCorrectionMaskPerimeterValue real_ + PaintCorrectionMaskRadius real_ + PaintCorrectionMaskReferencePoint string_ + PaintCorrectionMaskRight real_ + PaintCorrectionMaskRoundness real_ + PaintCorrectionMaskSizeX real_ + PaintCorrectionMaskSizeY real_ + PaintCorrectionMaskTop real_ + PaintCorrectionMaskVersion integer_ + PaintCorrectionMaskWhat string_ + PaintCorrectionMaskWholeImageArea string_ + PaintCorrectionMaskX real_ + PaintCorrectionMaskY real_ + PaintCorrectionMaskZeroX real_ + PaintCorrectionMaskZeroY real_ + PaintCorrectionCorrectionName string_+ + PaintCorrectionRangeMask CorrRangeMask Struct_+ + PaintCorrectionRangeMaskAreaModels AreaModels Struct_+ + PaintCorrectionRangeMaskAreaModelsComponents string_+ + PaintCorrectionRangeMaskAreaModelsColorSampleInfo string_+ + PaintCorrectionRangeMaskColorAmount real_+ + PaintCorrectionRangeMaskDepthFeather real_+ + PaintCorrectionRangeMaskDepthMax real_+ + PaintCorrectionRangeMaskDepthMin real_+ + PaintCorrectionRangeMaskInvert boolean_+ + PaintCorrectionRangeMaskLumFeather real_+ + PaintCorrectionRangeMaskLuminanceDepthSampleInfo string_+ + PaintCorrectionRangeMaskLumMax real_+ + PaintCorrectionRangeMaskLumMin real_+ + PaintCorrectionRangeMaskLumRange string_+ + PaintCorrectionRangeMaskSampleType integer_+ + PaintCorrectionRangeMaskType string_+ + PaintCorrectionRangeMaskVersion string_+ + PaintCorrectionCorrectionSyncID string_+ + PaintCorrectionBlacks2012 real_ + PaintCorrectionBrightness real_ + PaintCorrectionClarity real_ + PaintCorrectionClarity2012 real_ + PaintCorrectionContrast real_ + PaintCorrectionContrast2012 real_ + PaintCorrectionDefringe real_ + PaintCorrectionDehaze real_ + PaintCorrectionExposure real_ + PaintCorrectionExposure2012 real_ + PaintCorrectionHighlights2012 real_ + PaintCorrectionHue real_ + PaintCorrectionLuminanceNoise real_ + PaintCorrectionMoire real_ + PaintCorrectionSaturation real_ + PaintCorrectionShadows2012 real_ + PaintCorrectionSharpness real_ + PaintCorrectionTemperature real_ + PaintCorrectionTexture real_ + PaintCorrectionTint real_ + PaintCorrectionToningHue real_ + PaintCorrectionToningSaturation real_ + PaintCorrectionWhites2012 real_ + PaintCorrectionWhat string_ + ParametricDarks integer + ParametricHighlights integer + ParametricHighlightSplit integer + ParametricLights integer + ParametricMidtoneSplit integer + ParametricShadows integer + ParametricShadowSplit integer + PerspectiveAspect integer + PerspectiveHorizontal integer + PerspectiveRotate real + PerspectiveScale integer + PerspectiveUpright integer + PerspectiveVertical integer + PerspectiveX real + PerspectiveY real + PostCropVignetteAmount integer + PostCropVignetteFeather integer + PostCropVignetteHighlightContrast integer + PostCropVignetteMidpoint integer + PostCropVignetteRoundness integer + PostCropVignetteStyle integer + PresetType string + ProcessVersion string + RangeMask RangeMask Struct + RangeMaskMapInfo MapInfo Struct_ + RangeMaskMapInfoLabMax string_ + RangeMaskMapInfoLabMin string_ + RangeMaskMapInfoLumEq string_+ + RangeMaskMapInfoRGBMax string_ + RangeMaskMapInfoRGBMin string_ + RawFileName string + RedEyeInfo string+ + RedHue integer + RedSaturation integer + RetouchAreas RetouchArea Struct+ + RetouchAreaFeather real_ + RetouchAreaMasks CorrectionMask Struct_+ + RetouchAreaMaskAlpha real_ + RetouchAreaMaskAngle real_ + RetouchAreaMaskBottom real_ + RetouchAreaMaskCenterValue real_ + RetouchAreaMaskCenterWeight real_ + RetouchAreaMaskRange CorrRangeMask Struct_+ + RetouchAreaMaskRangeAreaModels AreaModels Struct_+ + RetouchAreaMaskRangeAreaModelsComponents string_+ + RetouchAreaMaskRangeAreaModelsColorSampleInfo string_+ + RetouchAreaMaskRangeColorAmount real_+ + RetouchAreaMaskRangeDepthFeather real_+ + RetouchAreaMaskRangeDepthMax real_+ + RetouchAreaMaskRangeDepthMin real_+ + RetouchAreaMaskRangeInvert boolean_+ + RetouchAreaMaskRangeLumFeather real_+ + RetouchAreaMaskRangeLuminanceDepthSampleInfo string_+ + RetouchAreaMaskRangeLumMax real_+ + RetouchAreaMaskRangeLumMin real_+ + RetouchAreaMaskRangeLumRange string_+ + RetouchAreaMaskRangeSampleType integer_+ + RetouchAreaMaskRangeType string_+ + RetouchAreaMaskRangeVersion string_+ + RetouchAreaMaskDabs string_ + RetouchAreaMaskFeather real_ + RetouchAreaMaskFlipped boolean_ + RetouchAreaMaskFlow real_ + RetouchAreaMaskFullX real_ + RetouchAreaMaskFullY real_ + RetouchAreaMaskInputDigest string_ + RetouchAreaMaskLeft real_ + RetouchAreaMaskMaskActive boolean_ + RetouchAreaMaskMaskBlendMode integer_ + RetouchAreaMaskMaskDigest string_ + RetouchAreaMaskMaskInverted boolean_ + RetouchAreaMaskMaskName string_ + RetouchAreaMaskMasks CorrectionMask Struct_+ + RetouchAreaMaskMasksAlpha real_ + RetouchAreaMaskMasksAngle real_ + RetouchAreaMaskMasksBottom real_ + RetouchAreaMaskMasksCenterValue real_ + RetouchAreaMaskMasksCenterWeight real_ + RetouchAreaMaskMasksDabs string_+ + RetouchAreaMaskMasksFeather real_ + RetouchAreaMaskMasksFlipped boolean_ + RetouchAreaMaskMasksFlow real_ + RetouchAreaMaskMasksFullX real_ + RetouchAreaMaskMasksFullY real_ + RetouchAreaMaskMasksInputDigest string_ + RetouchAreaMaskMasksLeft real_ + RetouchAreaMaskMasksMaskActive boolean_ + RetouchAreaMaskMasksMaskBlendMode integer_ + RetouchAreaMaskMasksMaskDigest string_ + RetouchAreaMaskMasksMaskInverted boolean_ + RetouchAreaMaskMasksMaskName string_ + RetouchAreaMaskMasksMaskSubType string_ + RetouchAreaMaskMasksMaskSyncID string_ + RetouchAreaMaskMasksValue real_ + RetouchAreaMaskMasksMaskVersion string_ + RetouchAreaMaskMasksMidpoint real_ + RetouchAreaMaskMasksOrigin string_ + RetouchAreaMaskMasksPerimeterValue real_ + RetouchAreaMaskMasksRadius real_ + RetouchAreaMaskMasksReferencePoint string_ + RetouchAreaMaskMasksRight real_ + RetouchAreaMaskMasksRoundness real_ + RetouchAreaMaskMasksSizeX real_ + RetouchAreaMaskMasksSizeY real_ + RetouchAreaMaskMasksTop real_ + RetouchAreaMaskMaskSubType string_ + RetouchAreaMaskMasksVersion integer_ + RetouchAreaMaskMasksWhat string_ + RetouchAreaMaskMasksWholeImageArea string_ + RetouchAreaMaskMasksX real_ + RetouchAreaMaskMasksY real_ + RetouchAreaMaskMaskSyncID string_ + RetouchAreaMaskMasksZeroX real_ + RetouchAreaMaskMasksZeroY real_ + RetouchAreaMaskValue real_ + RetouchAreaMaskMaskVersion string_ + RetouchAreaMaskMidpoint real_ + RetouchAreaMaskOrigin string_ + RetouchAreaMaskPerimeterValue real_ + RetouchAreaMaskRadius real_ + RetouchAreaMaskReferencePoint string_ + RetouchAreaMaskRight real_ + RetouchAreaMaskRoundness real_ + RetouchAreaMaskSizeX real_ + RetouchAreaMaskSizeY real_ + RetouchAreaMaskTop real_ + RetouchAreaMaskVersion integer_ + RetouchAreaMaskWhat string_ + RetouchAreaMaskWholeImageArea string_ + RetouchAreaMaskX real_ + RetouchAreaMaskY real_ + RetouchAreaMaskZeroX real_ + RetouchAreaMaskZeroY real_ + RetouchAreaMethod string_ + RetouchAreaOffsetY real_ + RetouchAreaOpacity real_ + RetouchAreaSeed integer_ + RetouchAreaSourceState string_ + RetouchAreaSourceX real_ + RetouchAreaSpotType string_ + RetouchInfo string+ + Saturation integer/ + SaturationAdjustmentAqua integer + SaturationAdjustmentBlue integer + SaturationAdjustmentGreen integer + SaturationAdjustmentMagenta integer + SaturationAdjustmentOrange integer + SaturationAdjustmentPurple integer + SaturationAdjustmentRed integer + SaturationAdjustmentYellow integer + SDRBlend real + SDRBrightness real + SDRContrast real + SDRHighlights real + SDRShadows real + SDRWhites real + Shadows integer + Shadows2012 integer + ShadowTint integer + SharpenDetail integer + SharpenEdgeMasking integer + SharpenRadius real + Sharpness integer/ + ShortName lang-alt + Smoothness integer + SortName lang-alt + SplitToningBalance integer + SplitToningHighlightHue integer + SplitToningHighlightSaturation integer + SplitToningShadowHue integer + SplitToningShadowSaturation integer + SupportsAmount boolean + SupportsColor boolean + SupportsHighDynamicRange boolean + SupportsMonochrome boolean + SupportsNormalDynamicRange boolean + SupportsOutputReferred boolean + SupportsSceneReferred boolean + ColorTemperature integer + Texture integer + TIFFHandling string + Tint integer + ToggleStyleAmount integer + ToggleStyleDigest string + ToneCurve string+ + ToneCurveBlue string+ + ToneCurveGreen string+ + ToneCurveName string + ToneCurveName2012 string + ToneCurvePV2012 string+ + ToneCurvePV2012Blue string+ + ToneCurvePV2012Green string+ + ToneCurvePV2012Red string+ + ToneCurveRed string+ + ToneMapStrength real + UprightCenterMode integer + UprightCenterNormX real + UprightCenterNormY real + UprightDependentDigest string + UprightFocalLength35mm real + UprightFocalMode integer + UprightFourSegments_0 string + UprightFourSegments_1 string + UprightFourSegments_2 string + UprightFourSegments_3 string + UprightFourSegmentsCount integer + UprightGuidedDependentDigest string + UprightPreview boolean + UprightTransform_0 string + UprightTransform_1 string + UprightTransform_2 string + UprightTransform_3 string + UprightTransform_4 string + UprightTransform_5 string + UprightTransformCount integer + UprightVersion integer + UUID string/ + Version string + Vibrance integer + VignetteAmount integer + VignetteMidpoint integer + What string + WhiteBalance string/ + Whites2012 integer + +=head3 XMP dc Tags + +Dublin Core namespace tags. + +These tags belong to the ExifTool XMP-dc family 1 group. + + Tag Name Writable + -------- -------- + Contributor string+ + Coverage string + Creator string+ + Date date+ + Description lang-alt + Format string + Identifier string + Language string+ + Publisher string+ + Relation string+ + Rights lang-alt + Source string/ + Subject string+ + Title lang-alt + Type string+ + +=head3 XMP Device Tags + +Google depth-map Device tags. See +L<https://developer.android.com/training/camera2/Dynamic-depth-v1.0.pdf> for +the specification. + +These tags belong to the ExifTool XMP-Device family 1 group. + + Tag Name Writable + -------- -------- + AppInfo AppInfo Struct + AppInfoApplication string_ + AppInfoItemURI string_ + AppInfoVersion string_ + Cameras DeviceCameras Struct+ + Camera DeviceCamera Struct_+ + CameraAppInfo AppInfo Struct_+ + CameraAppInfoApplication string_+ + CameraAppInfoItemURI string_+ + CameraAppInfoVersion string_+ + CameraDepthMap DeviceDepthMap Struct_+ + CameraDepthMapConfidenceURI string_+ + CameraDepthMapDepthURI string_+ + CameraDepthMapFar real_+ + CameraDepthMapFocalTable string_+ + CameraDepthMapFocalTableEntryCount integer_+ + CameraDepthMapFormat string_+ + CameraDepthMapItemSemantic string_+ + CameraDepthMapMeasureType string_+ + CameraDepthMapNear real_+ + CameraDepthMapSoftware string_+ + CameraDepthMapUnits string_+ + CameraImage DeviceImage Struct_+ + CameraImageItemSemantic string_+ + CameraImageItemURI string_+ + CameraImagingModel DeviceImagingModel Struct_+ + CameraImagingModelDistortion string_+ + CameraImagingModelDistortionCount integer_+ + CameraImagingModelFocalLengthX real_+ + CameraImagingModelFocalLengthY real_+ + CameraImagingModelImageHeight integer_+ + CameraImagingModelImageWidth integer_+ + CameraImagingModelPixelAspectRatio real_+ + CameraImagingModelPrincipalPointX real_+ + CameraImagingModelPrincipalPointY real_+ + CameraImagingModelSkew real_+ + CameraLightEstimate DeviceLightEstimate Struct_+ + CameraLightEstimateColorCorrectionB real_+ + CameraLightEstimateColorCorrectionG real_+ + CameraLightEstimateColorCorrectionR real_+ + CameraLightEstimatePixelIntensity real_+ + CameraPointCloud DevicePointCloud Struct_+ + CameraPointCloudMetric boolean_+ + CameraPointCloudPointCloud integer_+ + CameraPointCloudPoints string_+ + CameraPose Pose Struct_+ + CameraPosePositionX real_+ + CameraPosePositionY real_+ + CameraPosePositionZ real_+ + CameraPoseRotationW real_+ + CameraPoseRotationX real_+ + CameraPoseRotationY real_+ + CameraPoseRotationZ real_+ + CameraPoseTimestamp integer_+ + CameraTrait string_+ + CameraVendorInfo VendorInfo Struct_+ + CameraVendorInfoManufacturer string_+ + CameraVendorInfoModel string_+ + CameraVendorInfoNotes string_+ + Container DeviceContainer Struct + ContainerDirectory DeviceDirectory Struct_+ + ContainerDirectoryItem DeviceItem Struct_+ + ContainerDirectoryItemDataURI string_+ + ContainerDirectoryItemLength integer_+ + ContainerDirectoryItemMime string_+ + ContainerDirectoryItemPadding integer_+ + EarthPos EarthPose Struct + EarthPosAltitude real_ + EarthPosLatitude real_ + EarthPosLongitude real_ + EarthPosRotationW real_ + EarthPosRotationX real_ + EarthPosRotationY real_ + EarthPosRotationZ real_ + EarthPosTimestamp integer_ + Planes DevicePlanes Struct+ + Plane DevicePlane Struct_+ + PlaneBoundary string_+ + PlaneBoundaryVertexCount integer_+ + PlaneExtentX real_+ + PlaneExtentZ real_+ + PlanePose Pose Struct_+ + PlanePosePositionX real_+ + PlanePosePositionY real_+ + PlanePosePositionZ real_+ + PlanePoseRotationW real_+ + PlanePoseRotationX real_+ + PlanePoseRotationY real_+ + PlanePoseRotationZ real_+ + PlanePoseTimestamp integer_+ + Pose Pose Struct + PosePositionX real_ + PosePositionY real_ + PosePositionZ real_ + PoseRotationW real_ + PoseRotationX real_ + PoseRotationY real_ + PoseRotationZ real_ + PoseTimestamp integer_ + Profiles DeviceProfiles Struct+ + Profile DeviceProfile Struct_+ + ProfileCameraIndices integer_+ + ProfileType string_+ + VendorInfo VendorInfo Struct + VendorInfoManufacturer string_ + VendorInfoModel string_ + VendorInfoNotes string_ + +=head3 XMP AppInfo Struct + + Field Name Writable + ---------- -------- + Application string + ItemURI string + Version string + +=head3 XMP DeviceCameras Struct + + Field Name Writable + ---------- -------- + Camera DeviceCamera Struct + +=head3 XMP DeviceCamera Struct + + Field Name Writable + ---------- -------- + AppInfo AppInfo Struct + DepthMap DeviceDepthMap Struct + Image DeviceImage Struct + ImagingModel DeviceImagingModel Struct + LightEstimate DeviceLightEstimate Struct + PointCloud DevicePointCloud Struct + Pose Pose Struct + Trait string + VendorInfo VendorInfo Struct + +=head3 XMP DeviceDepthMap Struct + + Field Name Writable + ---------- -------- + ConfidenceURI string + DepthURI string + Far real + FocalTable string + FocalTableEntryCount integer + Format string + ItemSemantic string + MeasureType string + Near real + Software string + Units string + +=head3 XMP DeviceImage Struct + + Field Name Writable + ---------- -------- + ItemSemantic string + ItemURI string + +=head3 XMP DeviceImagingModel Struct + + Field Name Writable + ---------- -------- + Distortion string + DistortionCount integer + FocalLengthX real + FocalLengthY real + ImageHeight integer + ImageWidth integer + PixelAspectRatio real + PrincipalPointX real + PrincipalPointY real + Skew real + +=head3 XMP DeviceLightEstimate Struct + + Field Name Writable + ---------- -------- + ColorCorrectionB real + ColorCorrectionG real + ColorCorrectionR real + PixelIntensity real + +=head3 XMP DevicePointCloud Struct + + Field Name Writable + ---------- -------- + Metric boolean + PointCloud integer + Points string + +=head3 XMP Pose Struct + + Field Name Writable + ---------- -------- + PositionX real + PositionY real + PositionZ real + RotationW real + RotationX real + RotationY real + RotationZ real + Timestamp integer + +=head3 XMP VendorInfo Struct + + Field Name Writable + ---------- -------- + Manufacturer string + Model string + Notes string + +=head3 XMP DeviceContainer Struct + + Field Name Writable + ---------- -------- + Directory DeviceDirectory Struct+ + +=head3 XMP DeviceDirectory Struct + + Field Name Writable + ---------- -------- + Item DeviceItem Struct + +=head3 XMP DeviceItem Struct + + Field Name Writable + ---------- -------- + DataURI string + Length integer + Mime string + Padding integer + +=head3 XMP EarthPose Struct + + Field Name Writable + ---------- -------- + Altitude real + Latitude real + Longitude real + RotationW real + RotationX real + RotationY real + RotationZ real + Timestamp integer + +=head3 XMP DevicePlanes Struct + + Field Name Writable + ---------- -------- + Plane DevicePlane Struct + +=head3 XMP DevicePlane Struct + + Field Name Writable + ---------- -------- + Boundary string + BoundaryVertexCount integer + ExtentX real + ExtentZ real + Pose Pose Struct + +=head3 XMP DeviceProfiles Struct + + Field Name Writable + ---------- -------- + Profile DeviceProfile Struct + +=head3 XMP DeviceProfile Struct + + Field Name Writable + ---------- -------- + CameraIndices integer+ + Type string + +=head3 XMP dex Tags + +Description Explorer namespace tags. These tags are not very common. The +Source and Rating tags are avoided when writing due to name conflicts with +other XMP tags. (see L<http://www.optimasc.com/products/fileid/>) + +These tags belong to the ExifTool XMP-dex family 1 group. + + Tag Name Writable + -------- -------- + CRC32 integer + FFID string + LicenseType string + OS integer + Rating string/ + Revision string + ShortDescription lang-alt + Source string/ + +=head3 XMP DICOM Tags + +DICOM namespace tags. These XMP tags allow some DICOM information to be +stored in files of other than DICOM format. See the +L<DICOM Tags documentation|Image::ExifTool::TagNames/DICOM Tags> for a list +of tags available in DICOM-format files. + +These tags belong to the ExifTool XMP-DICOM family 1 group. + + Tag Name Writable + -------- -------- + EquipmentInstitution string + EquipmentManufacturer string + PatientBirthDate date + PatientID string + PatientName string + PatientSex string + SeriesDateTime date + SeriesDescription string + SeriesModality string + SeriesNumber string + StudyDateTime date + StudyDescription string + StudyID string + StudyPhysician string + +=head3 XMP digiKam Tags + +DigiKam namespace tags. + +These tags belong to the ExifTool XMP-digiKam family 1 group. + + Tag Name Writable + -------- -------- + CaptionsAuthorNames lang-alt + CaptionsDateTimeStamps lang-alt + ColorLabel string + ImageHistory string/ + ImageUniqueID string/ + LensCorrectionSettings string + PicasawebGPhotoId string + PickLabel string + TagsList string+ + +=head3 XMP ExifTool Tags + +These tags belong to the ExifTool XMP-et family 1 group. + + Tag Name Writable + -------- -------- + OriginalImageHash string + OriginalImageHashType string + OriginalImageMD5 string + +=head3 XMP exif Tags + +EXIF namespace for EXIF tags. See +L<https://web.archive.org/web/20180921145139if_/http://www.cipa.jp:80/std/documents/e/DC-010-2017_E.pdf> +for the specification. + +These tags belong to the ExifTool XMP-exif family 1 group. + + Tag Name Writable + -------- -------- + ApertureValue rational + BrightnessValue rational + CFAPattern CFAPattern Struct + CFAPatternColumns integer_ + CFAPatternRows integer_ + CFAPatternValues integer_+ + ColorSpace integer + ComponentsConfiguration integer+ + CompressedBitsPerPixel rational + Contrast integer + CustomRendered integer + DateTimeDigitized date + DateTimeOriginal date + DeviceSettingDescription DeviceSettings Struct + DeviceSettingDescriptionColumns integer_ + DeviceSettingDescriptionRows integer_ + DeviceSettingDescriptionSettings string_+ + DigitalZoomRatio rational + ExifVersion string + ExposureCompensation rational + ExposureIndex rational + ExposureMode integer + ExposureProgram integer + ExposureTime rational + FileSource integer + Flash Flash Struct + FlashEnergy rational + FlashFired boolean_ + FlashFunction boolean_ + FlashMode integer_ + FlashpixVersion string + FlashRedEyeMode boolean_ + FlashReturn integer_ + FNumber rational + FocalLength rational + FocalLengthIn35mmFormat integer + FocalPlaneResolutionUnit integer + FocalPlaneXResolution rational + FocalPlaneYResolution rational + GainControl integer + GPSAltitude rational + GPSAltitudeRef integer + GPSAreaInformation string + GPSDestBearing rational + GPSDestBearingRef string + GPSDestDistance rational + GPSDestDistanceRef string + GPSDestLatitude string + GPSDestLongitude string + GPSDifferential integer + GPSDOP rational + GPSHPositioningError rational + GPSImgDirection rational + GPSImgDirectionRef string + GPSLatitude string + GPSLongitude string + GPSMapDatum string + GPSMeasureMode integer + GPSProcessingMethod string + GPSSatellites string + GPSSpeed rational + GPSSpeedRef string + GPSStatus string + GPSDateTime date + GPSTrack rational + GPSTrackRef string + GPSVersionID string + ImageUniqueID string + ISO integer+ + LightSource string + MakerNote string + MaxApertureValue rational + MeteringMode integer + NativeDigest string + Opto-ElectricConvFactor OECF Struct + OECFColumns integer_ + OECFNames string_+ + OECFRows integer_ + OECFValues rational_+ + ExifImageWidth integer + ExifImageHeight integer + RelatedSoundFile string + Saturation integer + SceneCaptureType integer + SceneType integer + SensingMethod integer + Sharpness integer + ShutterSpeedValue rational + SpatialFrequencyResponse OECF Struct + SpatialFrequencyResponseColumns integer_ + SpatialFrequencyResponseNames string_+ + SpatialFrequencyResponseRows integer_ + SpatialFrequencyResponseValues rational_+ + SpectralSensitivity string + SubjectArea integer+ + SubjectDistance rational + SubjectDistanceRange integer + SubjectLocation integer+ + UserComment lang-alt + WhiteBalance integer + +=head3 XMP CFAPattern Struct + + Field Name Writable + ---------- -------- + Columns integer + Rows integer + Values integer+ + +=head3 XMP DeviceSettings Struct + + Field Name Writable + ---------- -------- + Columns integer + Rows integer + Settings string+ + +=head3 XMP Flash Struct + + Field Name Writable + ---------- -------- + Fired boolean + Function boolean + Mode integer + RedEyeMode boolean + Return integer + +=head3 XMP OECF Struct + + Field Name Writable + ---------- -------- + Columns integer + Names string+ + Rows integer + Values rational+ + +=head3 XMP exifEX Tags + +EXIF tags added by the EXIF 2.32 for XMP specification (see +L<https://cipa.jp/std/documents/download_e.html?DC-010-2020_E>). + +These tags belong to the ExifTool XMP-exifEX family 1 group. + + Tag Name Writable + -------- -------- + Acceleration rational + SerialNumber string + CameraElevationAngle rational + OwnerName string + CompositeImage integer + CompositeImageCount integer+ + CompositeImageExposureTimes CompImageExp Struct + CompImageMaxExposureAll rational_ + CompImageMaxExposureUsed rational_ + CompImageMinExposureAll rational_ + CompImageMinExposureUsed rational_ + CompImageImagesPerSequence integer_ + CompImageNumSequences integer_ + CompImageSumExposureAll rational_ + CompImageSumExposureUsed rational_ + CompImageTotalExposurePeriod rational_ + CompImageValues rational_+ + Gamma rational + Humidity rational + InteropIndex string + ISOSpeed integer + ISOSpeedLatitudeyyy integer + ISOSpeedLatitudezzz integer + LensMake string + LensModel string + LensSerialNumber string + LensInfo rational+ + PhotographicSensitivity integer + Pressure rational + RecommendedExposureIndex integer + SensitivityType integer + StandardOutputSensitivity integer + AmbientTemperature rational + WaterDepth rational + +=head3 XMP CompImageExp Struct + + Field Name Writable + ---------- -------- + MaxExposureTimesOfAll rational + MaxExposureTimesOfUsed rational + MinExposureTimesOfAll rational + MinExposureTimesOfUsed rational + NumberOfImagesInSequences integer + NumberOfSequences integer + SumOfExposureTimesOfAll rational + SumOfExposureTimesOfUsed rational + TotalExposurePeriod rational + Values rational+ + +=head3 XMP ExpressionMedia Tags + +Microsoft Expression Media namespace tags. These tags are avoided when +writing due to name conflicts with tags in other schemas. + +These tags belong to the ExifTool XMP-expressionmedia family 1 group. + + Tag Name Writable + -------- -------- + CatalogSets string/+ + Event string/ + People string/+ + Status string/ + +=head3 XMP extensis Tags + +Tags used by Extensis Portfolio. + +These tags belong to the ExifTool XMP-extensis family 1 group. + + Tag Name Writable + -------- -------- + Approved boolean + ApprovedBy string + ClientName string + JobName string + JobStatus string + RoutedTo string + RoutingNotes string + WorkToDo string + +=head3 XMP fpv Tags + +Fast Picture Viewer tags (see +L<http://www.fastpictureviewer.com/help/#rtfcomments>). + +These tags belong to the ExifTool XMP-fpv family 1 group. + + Tag Name Writable + -------- -------- + RichTextComment string + +=head3 XMP GAudio Tags + +These tags belong to the ExifTool XMP-GAudio family 1 group. + + Tag Name Writable + -------- -------- + AudioData string + AudioMimeType string + +=head3 XMP GCamera Tags + +Camera information found in Google panorama images. + +These tags belong to the ExifTool XMP-GCamera family 1 group. + + Tag Name Writable + -------- -------- + BurstID string + BurstPrimary string + DisableAutoCreation string+ + HDRPMakerNote string + MicroVideo integer + MicroVideoOffset integer + MicroVideoPresentationTimestampUs integer + MicroVideoVersion integer + PortraitNote string + PortraitRequest string + PortraitVersion string + ShotLogData string + SpecialTypeID string+ + +=head3 XMP GCreations Tags + +Google creations tags. + +These tags belong to the ExifTool XMP-GCreations family 1 group. + + Tag Name Writable + -------- -------- + CameraBurstID string + Type string/ + +=head3 XMP GDepth Tags + +Google depthmap information. See +L<https://developers.google.com/depthmap-metadata/> for the specification. + +These tags belong to the ExifTool XMP-GDepth family 1 group. + + Tag Name Writable + -------- -------- + Confidence string/ + ConfidenceMime string/ + DepthImage string/ + Far real/ + Format string/ + ImageHeight real/ + ImageWidth real/ + Manufacturer string/ + MeasureType string/ + Mime string/ + Model string/ + Near real/ + Software string/ + Units string/ + +=head3 XMP GettyImages Tags + +The actual Getty Images namespace prefix is "GettyImagesGIFT", which is the +prefix recorded in the file, but ExifTool shortens this for the family 1 +group name. + +These tags belong to the ExifTool XMP-getty family 1 group. + + Tag Name Writable + -------- -------- + AssetID string + CallForImage string + CameraFilename string + CameraMakeModel string/ + CameraSerialNumber string/ + Composition string + ExclusiveCoverage string + GIFTFtpPriority string + ImageRank string + MediaEventIdDate string + OriginalCreateDateTime date/ + OriginalFileName string + ParentMediaEventID string + ParentMEID string + Personality string+ + PrimaryFTP string+ + RoutingDestinations string+ + RoutingExclusions string+ + SecondaryFTP string+ + TimeShot string + +=head3 XMP GFocus Tags + +Focus information found in Google depthmap images. + +These tags belong to the ExifTool XMP-GFocus family 1 group. + + Tag Name Writable + -------- -------- + BlurAtInfinity real + FocalDistance real + FocalPointX real + FocalPointY real + +=head3 XMP GImage Tags + +These tags belong to the ExifTool XMP-GImage family 1 group. + + Tag Name Writable + -------- -------- + ImageData string + ImageMimeType string + +=head3 XMP GPano Tags + +Panorama tags written by Google Photosphere. See +L<https://developers.google.com/panorama/metadata/> for the specification. + +These tags belong to the ExifTool XMP-GPano family 1 group. + + Tag Name Writable + -------- -------- + CaptureSoftware string + CroppedAreaImageHeightPixels real + CroppedAreaImageWidthPixels real + CroppedAreaLeftPixels real + CroppedAreaTopPixels real + ExposureLockUsed boolean + FirstPhotoDate date + FullPanoHeightPixels real + FullPanoWidthPixels real + InitialCameraDolly real + InitialHorizontalFOVDegrees real + InitialVerticalFOVDegrees real + InitialViewHeadingDegrees real + InitialViewPitchDegrees real + InitialViewRollDegrees real + LargestValidInteriorRectHeight real + LargestValidInteriorRectLeft real + LargestValidInteriorRectTop real + LargestValidInteriorRectWidth real + LastPhotoDate date + PoseHeadingDegrees real + PosePitchDegrees real + PoseRollDegrees real + ProjectionType string + SourcePhotosCount integer + StitchingSoftware string + UsePanoramaViewer boolean + +=head3 XMP GSpherical Tags + +Not actually XMP. These RDF/XML tags are used in Google spherical MP4 +videos. These tags are written into the video track of MOV/MP4 files, and +not at the top level like other XMP tags. See +L<https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md> +for the specification. + +These tags belong to the ExifTool XMP-GSpherical family 1 group. + + Tag Name Writable + -------- -------- + CroppedAreaImageHeightPixels integer/ + CroppedAreaImageWidthPixels integer/ + CroppedAreaLeftPixels integer/ + CroppedAreaTopPixels integer/ + FullPanoHeightPixels integer/ + FullPanoWidthPixels integer/ + InitialViewHeadingDegrees real/ + InitialViewPitchDegrees real/ + InitialViewRollDegrees real/ + ProjectionType string/ + SourceCount integer/ + Spherical boolean/ + StereoMode string/ + Stitched boolean/ + StitchingSoftware string/ + TimeStamp integer/ + +=head3 XMP hdr Tags + +HDR metadata namespace tags written by ACR 15.1. The actual namespace +prefix is "hdr_metadata", which is the prefix recorded in the file, but +ExifTool shortens this for the family 1 group name. + +These tags belong to the ExifTool XMP-hdr family 1 group. + + Tag Name Writable + -------- -------- + CCVAvgLuminanceNits real + CCVMaxLuminanceNits real + CCVMinLuminanceNits real + CCVPrimariesXY string + CCVWhiteXY string + SceneReferred boolean + +=head3 XMP hdrgm Tags + +Tags used in Adobe gain map images. + +These tags belong to the ExifTool XMP-hdrgm family 1 group. + + Tag Name Writable + -------- -------- + BaseRenditionIsHDR boolean + GainMapMax real+ + GainMapMin real+ + Gamma real/+ + HDRCapacityMax real + HDRCapacityMin real + OffsetHDR real+ + OffsetSDR real+ + Version string/ + +=head3 XMP ics Tags + +Tags used by IDimager. Nested TagStructure structures are unrolled to an +arbitrary depth of 6 to avoid infinite recursion. + +These tags belong to the ExifTool XMP-ics family 1 group. + + Tag Name Writable + -------- -------- + AppVersion string/ + ImageRef string + SubVersions SubVersion Struct+ + SubVersionFileName string_+ + SubVersionReference string_+ + TagStructure TagStructure Struct+ + LabelName1 string_+ + ParentReference1 string_+ + Reference1 string_+ + SubLabels1 TagStructure Struct_+ + LabelName2 string_+ + ParentReference2 string_+ + Reference2 string_+ + SubLabels2 TagStructure Struct_+ + LabelName3 string_+ + ParentReference3 string_+ + Reference3 string_+ + SubLabels3 TagStructure Struct_+ + LabelName4 string_+ + ParentReference4 string_+ + Reference4 string_+ + SubLabels4 TagStructure Struct_+ + LabelName5 string_+ + ParentReference5 string_+ + Reference5 string_+ + SubLabels5 TagStructure Struct_+ + LabelName6 string_+ + ParentReference6 string_+ + Reference6 string_+ + TimeStamp date/ + +=head3 XMP SubVersion Struct + + Field Name Writable + ---------- -------- + FileName string + VersRef string + +=head3 XMP TagStructure Struct + + Field Name Writable + ---------- -------- + LabelName string + ParentReference string + Reference string + SubLabels TagStructure Struct+ + +=head3 XMP iptcCore Tags + +IPTC Core namespace tags. The actual IPTC Core namespace prefix is +"Iptc4xmpCore", which is the prefix recorded in the file, but ExifTool +shortens this for the family 1 group name. (see +L<http://www.iptc.org/IPTC4XMP/>) + +These tags belong to the ExifTool XMP-iptcCore family 1 group. + + Tag Name Writable + -------- -------- + AltTextAccessibility lang-alt + CountryCode string + CreatorContactInfo ContactInfo Struct + CreatorCity string_ + CreatorCountry string_ + CreatorAddress string_ + CreatorPostalCode string_ + CreatorRegion string_ + CreatorWorkEmail string_ + CreatorWorkTelephone string_ + CreatorWorkURL string_ + ExtDescrAccessibility lang-alt + IntellectualGenre string + Location string + Scene string+ + SubjectCode string+ + +=head3 XMP ContactInfo Struct + + Field Name Writable + ---------- -------- + CiAdrCity string + CiAdrCtry string + CiAdrExtadr string + CiAdrPcode string + CiAdrRegion string + CiEmailWork string + CiTelWork string + CiUrlWork string + +=head3 XMP iptcExt Tags + +This table contains tags defined by the IPTC Extension schema version 1.7 +and IPTC Video Metadata version 1.3. The actual namespace prefix is +"Iptc4xmpExt", but ExifTool shortens this for the family 1 group name. (See +L<http://www.iptc.org/standards/photo-metadata/iptc-standard/> and +L<https://iptc.org/standards/video-metadata-hub/>.) + +These tags belong to the ExifTool XMP-iptcExt family 1 group. + + Tag Name Writable + -------- -------- + AboutCvTerm CVTermDetails Struct+ + AboutCvTermCvId string_+ + AboutCvTermId string_+ + AboutCvTermName lang-alt_+ + AboutCvTermRefinedAbout string_+ + AdditionalModelInformation string + ArtworkOrObject ArtworkOrObjectDetails Struct+ + ArtworkCircaDateCreated string!_+ + ArtworkContentDescription lang-alt_+ + ArtworkContributionDescription lang-alt_+ + ArtworkCopyrightNotice string_+ + ArtworkCreator string_+ + ArtworkCreatorID string_+ + ArtworkCopyrightOwnerID string_+ + ArtworkCopyrightOwnerName string_+ + ArtworkLicensorID string_+ + ArtworkLicensorName string_+ + ArtworkDateCreated date_+ + ArtworkPhysicalDescription lang-alt_+ + ArtworkSource string_+ + ArtworkSourceInventoryNo string_+ + ArtworkSourceInvURL string_+ + ArtworkStylePeriod string_+ + ArtworkTitle lang-alt_+ + AudioBitrate integer + AudioBitrateMode string + AudioBitsPerSample integer + AudioChannelCount integer + CircaDateCreated string + ContainerFormat Entity Struct + ContainerFormatIdentifier string_+ + ContainerFormatName lang-alt_ + Contributor EntityWithRole Struct+ + ContributorIdentifier string_+ + ContributorName lang-alt_+ + ContributorRole string_+ + CopyrightYear integer + Creator EntityWithRole Struct+ + CreatorIdentifier string_+ + CreatorName lang-alt_+ + CreatorRole string_+ + ControlledVocabularyTerm string+ + DataOnScreen TextRegion Struct+ + DataOnScreenRegion Area Struct_+ + DataOnScreenRegionD real_+ + DataOnScreenRegionH real_+ + DataOnScreenRegionText string_+ + DataOnScreenRegionUnit string_+ + DataOnScreenRegionW real_+ + DataOnScreenRegionX real_+ + DataOnScreenRegionY real_+ + DigitalImageGUID string + DigitalSourceFileType string + DigitalSourceType string + Dopesheet lang-alt + DopesheetLink QualifiedLink Struct+ + DopesheetLinkLink string_+ + DopesheetLinkLinkQualifier string_+ + EmbdEncRightsExpr EEREDetails Struct+ + EmbeddedEncodedRightsExpr string_+ + EmbeddedEncodedRightsExprType string_+ + EmbeddedEncodedRightsExprLangID string_+ + Episode EpisodeOrSeason Struct + EpisodeIdentifier string_ + EpisodeName string_ + EpisodeNumber string_ + Event lang-alt + ShownEvent Entity Struct+ + ShownEventIdentifier string_+ + ShownEventName lang-alt_+ + EventID string+ + ExternalMetadataLink string+ + FeedIdentifier string + Genre CVTermDetails Struct+ + GenreCvId string_+ + GenreCvTermId string_+ + GenreCvTermName lang-alt_+ + GenreCvTermRefinedAbout string_+ + Headline lang-alt/ + ImageRegion ImageRegion Struct+ + ImageRegionName lang-alt_+ + ImageRegionCtype Entity Struct_+ + ImageRegionCtypeIdentifier string_+ + ImageRegionCtypeName lang-alt_+ + ImageRegionBoundary RegionBoundary Struct_+ + ImageRegionBoundaryH real_+ + ImageRegionBoundaryRx real_+ + ImageRegionBoundaryShape string_+ + ImageRegionBoundaryUnit string_+ + ImageRegionBoundaryVertices BoundaryPoint Struct_+ + ImageRegionBoundaryVerticesX real_+ + ImageRegionBoundaryVerticesY real_+ + ImageRegionBoundaryW real_+ + ImageRegionBoundaryX real_+ + ImageRegionBoundaryY real_+ + ImageRegionID string_+ + ImageRegionRole Entity Struct_+ + ImageRegionRoleIdentifier string_+ + ImageRegionRoleName lang-alt_+ + IPTCLastEdited date + LinkedEncRightsExpr LEREDetails Struct+ + LinkedEncodedRightsExpr string_+ + LinkedEncodedRightsExprType string_+ + LinkedEncodedRightsExprLangID string_+ + LocationCreated LocationDetails Struct+ + LocationCreatedCity string_+ + LocationCreatedCountryCode string_+ + LocationCreatedCountryName string_+ + LocationCreatedGPSAltitude rational_+ + LocationCreatedGPSLatitude string_+ + LocationCreatedGPSLongitude string_+ + LocationCreatedIdentifier string_+ + LocationCreatedLocationId string_+ + LocationCreatedLocationName lang-alt_+ + LocationCreatedProvinceState string_+ + LocationCreatedSublocation string_+ + LocationCreatedWorldRegion string_+ + LocationShown LocationDetails Struct+ + LocationShownCity string_+ + LocationShownCountryCode string_+ + LocationShownCountryName string_+ + LocationShownGPSAltitude rational_+ + LocationShownGPSLatitude string_+ + LocationShownGPSLongitude string_+ + LocationShownIdentifier string_+ + LocationShownLocationId string_+ + LocationShownLocationName lang-alt_+ + LocationShownProvinceState string_+ + LocationShownSublocation string_+ + LocationShownWorldRegion string_+ + MaxAvailHeight integer + MaxAvailWidth integer + MetadataAuthority Entity Struct + MetadataAuthorityIdentifier string_+ + MetadataAuthorityName lang-alt_ + MetadataLastEdited date + MetadataLastEditor Entity Struct + MetadataLastEditorIdentifier string_+ + MetadataLastEditorName lang-alt_ + ModelAge integer+ + OrganisationInImageCode string+ + OrganisationInImageName string+ + ParentID string + PersonHeard Entity Struct+ + PersonHeardIdentifier string_+ + PersonHeardName lang-alt_+ + PersonInImage string+ + PersonInImageWDetails PersonDetails Struct+ + PersonInImageCharacteristic CVTermDetails Struct_+ + PersonInImageCvTermCvId string_+ + PersonInImageCvTermId string_+ + PersonInImageCvTermName lang-alt_+ + PersonInImageCvTermRefinedAbout string_+ + PersonInImageDescription lang-alt_+ + PersonInImageId string_+ + PersonInImageName lang-alt_+ + PlanningRef EntityWithRole Struct+ + PlanningRefIdentifier string_+ + PlanningRefName lang-alt_+ + PlanningRefRole string_+ + ProductInImage ProductDetails Struct+ + ProductInImageDescription lang-alt_+ + ProductInImageGTIN string_+ + ProductInImageProductId string_+ + ProductInImageName lang-alt_+ + PublicationEvent PublicationEvent Struct+ + PublicationEventDate date_+ + PublicationEventIdentifier string_+ + PublicationEventName string_+ + Rating Rating Struct+ + RatingRegion LocationDetails Struct_+ + RatingRegionCity string_+ + RatingRegionCountryCode string_+ + RatingRegionCountryName string_+ + RatingRegionGPSAltitude rational_+ + RatingRegionGPSLatitude string_+ + RatingRegionGPSLongitude string_+ + RatingRegionIdentifier string_+ + RatingRegionLocationId string_+ + RatingRegionLocationName lang-alt_+ + RatingRegionProvinceState string_+ + RatingRegionSublocation string_+ + RatingRegionWorldRegion string_+ + RatingScaleMaxValue string_+ + RatingScaleMinValue string_+ + RatingSourceLink string_+ + RatingValue string_+ + RatingValueLogoLink string_+ + RecDevice Device Struct + RecDeviceAttLensDescription string_ + RecDeviceManufacturer string_ + RecDeviceModelName string_ + RecDeviceOwnersDeviceId string_ + RecDeviceSerialNumber string_ + RegistryID RegistryEntryDetails Struct+ + RegistryEntryRole string_+ + RegistryItemID string_+ + RegistryOrganisationID string_+ + ReleaseReady boolean + Season EpisodeOrSeason Struct + SeasonIdentifier string_ + SeasonName string_ + SeasonNumber string_ + Series Series Struct + SeriesIdentifier string_ + SeriesName string_ + Snapshot LinkedImage Struct+ + SnapshotFormat string_+ + SnapshotHeightPixels integer_+ + SnapshotImageRole string_+ + SnapshotLink string_+ + SnapshotLinkQualifier string_+ + SnapshotUsedVideoFrame Timecode Struct_+ + SnapshotUsedVideoFrameTimeFormat string_+ + SnapshotUsedVideoFrameTimeValue string_+ + SnapshotUsedVideoFrameValue integer_+ + SnapshotWidthPixels integer_+ + StorylineIdentifier string+ + StreamReady string + StylePeriod string + SupplyChainSource Entity Struct+ + SupplyChainSourceIdentifier string_+ + SupplyChainSourceName lang-alt_+ + TemporalCoverage TemporalCoverage Struct + TemporalCoverageFrom date_ + TemporalCoverageTo date_ + Transcript lang-alt + TranscriptLink QualifiedLink Struct+ + TranscriptLinkLink string_+ + TranscriptLinkLinkQualifier string_+ + VideoBitrate integer + VideoBitrateMode string + VideoDisplayAspectRatio rational + VideoEncodingProfile string + VideoShotType Entity Struct+ + VideoShotTypeIdentifier string_+ + VideoShotTypeName lang-alt_+ + VideoStreamsCount integer + VisualColor string + WorkflowTag CVTermDetails Struct + WorkflowTagCvId string_ + WorkflowTagCvTermId string_ + WorkflowTagCvTermName lang-alt_ + WorkflowTagCvTermRefinedAbout string_ + +=head3 XMP CVTermDetails Struct + + Field Name Writable + ---------- -------- + CvId string + CvTermId string + CvTermName lang-alt + CvTermRefinedAbout string + +=head3 XMP ArtworkOrObjectDetails Struct + + Field Name Writable + ---------- -------- + AOCircaDateCreated string + AOContentDescription lang-alt + AOContributionDescription lang-alt + AOCopyrightNotice string + AOCreator string+ + AOCreatorId string+ + AOCurrentCopyrightOwnerId string + AOCurrentCopyrightOwnerName string + AOCurrentLicensorId string + AOCurrentLicensorName string + AODateCreated date + AOPhysicalDescription lang-alt + AOSource string + AOSourceInvNo string + AOSourceInvURL string + AOStylePeriod string+ + AOTitle lang-alt + +=head3 XMP Entity Struct + + Field Name Writable + ---------- -------- + Identifier string+ + Name lang-alt + +=head3 XMP EntityWithRole Struct + + Field Name Writable + ---------- -------- + Identifier string+ + Name lang-alt + Role string+ + +=head3 XMP TextRegion Struct + + Field Name Writable + ---------- -------- + Region Area Struct + RegionText string + +=head3 XMP Area Struct + + Field Name Writable + ---------- -------- + D real + H real + Unit string + W real + X real + Y real + +=head3 XMP QualifiedLink Struct + + Field Name Writable + ---------- -------- + Link string + LinkQualifier string + +=head3 XMP EEREDetails Struct + + Field Name Writable + ---------- -------- + EncRightsExpr string + RightsExprEncType string + RightsExprLangId string + +=head3 XMP EpisodeOrSeason Struct + + Field Name Writable + ---------- -------- + Identifier string + Name string + Number string + +=head3 XMP ImageRegion Struct + +This structure is new in the IPTC Extension version 1.5 specification. As +well as the fields defined below, this structure may contain any top-level +XMP tags, but since they aren't pre-defined the only way to add these tags +is to write ImageRegion as a structure with these tags as new fields. + + Field Name Writable + ---------- -------- + Name lang-alt + RegionBoundary RegionBoundary Struct + RCtype Entity Struct+ + RId string + RRole Entity Struct+ + +=head3 XMP RegionBoundary Struct + + Field Name Writable + ---------- -------- + RbH real + RbRx real + RbShape string + RbUnit string + RbVertices BoundaryPoint Struct+ + RbW real + RbX real + RbY real + +=head3 XMP BoundaryPoint Struct + + Field Name Writable + ---------- -------- + RbX real + RbY real + +=head3 XMP LEREDetails Struct + + Field Name Writable + ---------- -------- + LinkedRightsExpr string + RightsExprEncType string + RightsExprLangId string + +=head3 XMP LocationDetails Struct + + Field Name Writable + ---------- -------- + City string + CountryCode string + CountryName string + GPSAltitude rational + GPSLatitude string + GPSLongitude string + Identifier string+ + LocationId string+ + LocationName lang-alt + ProvinceState string + Sublocation string + WorldRegion string + +=head3 XMP PersonDetails Struct + + Field Name Writable + ---------- -------- + PersonCharacteristic CVTermDetails Struct+ + PersonDescription lang-alt + PersonId string+ + PersonName lang-alt + +=head3 XMP ProductDetails Struct + + Field Name Writable + ---------- -------- + ProductDescription lang-alt + ProductGTIN string + ProductId string + ProductName lang-alt + +=head3 XMP PublicationEvent Struct + + Field Name Writable + ---------- -------- + Date date + Identifier string + Name string + +=head3 XMP Rating Struct + + Field Name Writable + ---------- -------- + RatingRegion LocationDetails Struct+ + RatingScaleMaxValue string + RatingScaleMinValue string + RatingSourceLink string + RatingValue string + RatingValueLogoLink string + +=head3 XMP Device Struct + + Field Name Writable + ---------- -------- + AttLensDescription string + Manufacturer string + ModelName string + OwnersDeviceId string + SerialNumber string + +=head3 XMP RegistryEntryDetails Struct + + Field Name Writable + ---------- -------- + RegEntryRole string + RegItemId string + RegOrgId string + +=head3 XMP Series Struct + + Field Name Writable + ---------- -------- + Identifier string + Name string + +=head3 XMP LinkedImage Struct + + Field Name Writable + ---------- -------- + HeightPixels integer + ImageRole string + Link string + LinkQualifier string+ + UsedVideoFrame Timecode Struct + WidthPixels integer + Format string + +=head3 XMP Timecode Struct + + Field Name Writable + ---------- -------- + TimeFormat string + TimeValue string + Value integer + +=head3 XMP TemporalCoverage Struct + + Field Name Writable + ---------- -------- + TempCoverageFrom date + TempCoverageTo date + +=head3 XMP LImage Tags + +Tags written by RED smartphones. + +These tags belong to the ExifTool XMP-LImage family 1 group. + + Tag Name Writable + -------- -------- + MajorVersion string + MinorVersion string + RightAlbedo string + +=head3 XMP Lightroom Tags + +Adobe Lightroom "lr" namespace tags. + +These tags belong to the ExifTool XMP-lr family 1 group. + + Tag Name Writable + -------- -------- + HierarchicalSubject string+ + PrivateRTKInfo string + WeightedFlatSubject string+ + +=head3 XMP MediaPro Tags + +iView MediaPro namespace tags. + +These tags belong to the ExifTool XMP-mediapro family 1 group. + + Tag Name Writable + -------- -------- + CatalogSets string+ + Event string/ + Location string/ + People string+ + Status string + UserFields string+ + +=head3 XMP panorama Tags + +Adobe Photoshop Panorama-profile tags. + +These tags belong to the ExifTool XMP-panorama family 1 group. + + Tag Name Writable + -------- -------- + Transformation string + VirtualFocalLength real + VirtualImageXCenter real + VirtualImageYCenter real + +=head3 XMP pdf Tags + +Adobe PDF namespace tags. The official XMP specification defines only +Keywords, PDFVersion, Producer and Trapped. The other tags are included +because they have been observed in PDF files, but some are avoided when +writing due to name conflicts with other XMP namespaces. + +These tags belong to the ExifTool XMP-pdf family 1 group. + + Tag Name Writable + -------- -------- + Author string + Copyright string/ + CreationDate date + Creator string/ + Keywords string + Marked boolean/ + ModDate date + PDFVersion string + Producer string + Subject string/ + Title string/ + Trapped string + +=head3 XMP pdfx Tags + +PDF extension tags. This namespace is used to store application-defined PDF +information, so there are no pre-defined tags. User-defined tags must be +created to enable writing of XMP-pdfx information. + +These tags belong to the ExifTool XMP-pdfx family 1 group. + + Tag Name Writable + -------- -------- + [no tags known] + +=head3 XMP photoshop Tags + +Adobe Photoshop namespace tags. + +These tags belong to the ExifTool XMP-photoshop family 1 group. + + Tag Name Writable + -------- -------- + AuthorsPosition string + CameraProfiles Camera Struct+ + CameraProfilesApertureValue real_+ + CameraProfilesAuthor string_+ + CameraProfilesAutoScale boolean_+ + CameraProfilesCameraPrettyName string_+ + CameraProfilesCameraRawProfile boolean_+ + CameraProfilesFocalLength real_+ + CameraProfilesFocusDistance real_+ + CameraProfilesLens string_+ + CameraProfilesLensPrettyName string_+ + CameraProfilesMake string_+ + CameraProfilesModel string_+ + CameraProfilesPerspectiveModel PerspectiveModel Struct_+ + CameraProfilesPerspectiveModelImageXCenter real_+ + CameraProfilesPerspectiveModelImageYCenter real_+ + CameraProfilesPerspectiveModelRadialDistortParam1 real_+ + CameraProfilesPerspectiveModelRadialDistortParam2 real_+ + CameraProfilesPerspectiveModelRadialDistortParam3 real_+ + CameraProfilesPerspectiveModelScaleFactor real_+ + CameraProfilesPerspectiveModelVersion string_+ + CameraProfilesProfileName string_+ + CameraProfilesSensorFormatFactor real_+ + CameraProfilesUniqueCameraModel string_+ + CaptionWriter string + Category string + City string + ColorMode integer + Country string + Credit string + DateCreated date + DocumentAncestors string+ + EmbeddedXMPDigest string + Headline string + History string + ICCProfileName string + Instructions string + LegacyIPTCDigest string + SidecarForExtension string + Source string + State string + SupplementalCategories string+ + TextLayers Layer Struct+ + TextLayerName string_+ + TextLayerText string_+ + TransmissionReference string + Urgency integer + +=head3 XMP Camera Struct + + Field Name Writable + ---------- -------- + ApertureValue real + Author string + AutoScale boolean + CameraPrettyName string + CameraRawProfile boolean + FocalLength real + FocusDistance real + Lens string + LensPrettyName string + Make string + Model string + PerspectiveModel PerspectiveModel Struct + ProfileName string + SensorFormatFactor real + UniqueCameraModel string + +=head3 XMP PerspectiveModel Struct + + Field Name Writable + ---------- -------- + ImageXCenter real + ImageYCenter real + RadialDistortParam1 real + RadialDistortParam2 real + RadialDistortParam3 real + ScaleFactor real + Version string + +=head3 XMP Layer Struct + + Field Name Writable + ---------- -------- + LayerName string + LayerText string + +=head3 XMP PixelLive Tags + +PixelLive namespace tags. These tags are not writable because they are very +uncommon and I haven't been able to locate a reference which gives the +namespace URI. + +These tags belong to the ExifTool XMP-PixelLive family 1 group. + + Tag Name Writable + -------- -------- + Author no + Comments no + Copyright no + Date no + Genre no + Title no + +=head3 XMP pmi Tags + +PRISM Metadata for Images 3.0 namespace tags. (see +L<http://www.prismstandard.org/>) + +These tags belong to the ExifTool XMP-pmi family 1 group. + + Tag Name Writable + -------- -------- + Color string/ + ContactInfo string/ + DisplayName string/ + DistributorProductID string/ + EventAlias string/ + EventEnd string/ + EventStart string/ + EventSubtype string/ + EventType string/ + Field string/ + Framing string/ + Location string/ + Make string/ + Manufacturer string/ + Model string/ + ModelYear string/ + ObjectDescription string/ + ObjectSubtype string/ + ObjectType string/ + Orientation string/ + PositionDescriptor string/ + ProductID string/ + ProductIDType string/ + Season string/ + SequenceName string/ + SequenceNumber string/ + SequenceTotalNumber string/ + Setting string/ + ShootID string/ + SlideshowName string/ + SlideshowNumber integer/ + SlideshowTotalNumber integer/ + Viewpoint string/ + VisualTechnique string/ + +=head3 XMP prism Tags + +Publishing Requirements for Industry Standard Metadata 3.0 namespace +tags. (see +L<https://www.w3.org/Submission/2020/SUBM-prism-20200910/prism-basic.html/>) + +These tags belong to the ExifTool XMP-prism family 1 group. + + Tag Name Writable + -------- -------- + AcademicField string/ + AggregateIssueNumber integer/ + AggregationType string/+ + AlternateTitle prismAlternateTitle Struct+ + AlternateTitleA-lang string/_+ + AlternateTitleA-platform string/_+ + AlternateTitleText string/_+ + BlogTitle string/ + BlogURL string/ + BookEdition string/ + ByteCount integer/ + Channel prismChannel Struct+ + ChannelA-lang string/_+ + ChannelChannel string/_+ + ChannelSubchannel1 string/_+ + ChannelSubchannel2 string/_+ + ChannelSubchannel3 string/_+ + ChannelSubchannel4 string/_+ + ComplianceProfile string/ + ContentType string/ + CopyrightYear string/ + CorporateEntity string/+ + CoverDate date/ + CoverDisplayDate string/ + CreationDate date/ + DateRecieved date/ + Device string/ + Distributor string/ + DOI string/ + Edition string/ + EIssn string/ + EndingPage string/ + Event string/+ + Genre string/+ + HasAlternative string/+ + HasCorrection prismHasCorrection Struct + HasCorrectionA-lang string/_ + HasCorrectionA-platform string/_ + HasCorrectionText string/_ + HasTranslation string/+ + Industry string/+ + IsAlternativeOf string/+ + ISBN string/+ + IsCorrectionOf string/+ + ISSN string/ + IssueIdentifier string/ + IssueName string/ + IssueTeaser string/ + IssueType string/ + IsTranslationOf string/ + Keyword string/+ + KillDate prismKillDate Struct + KillDateA-platform string/_ + KillDateDate date/_ + Link string/+ + Location string/+ + ModificationDate date/ + NationalCatalogNumber string/ + Number string/ + Object string/+ + OffSaleDate prismOffSaleDate Struct+ + OffSaleDateA-platform string/_+ + OffSaleDateDate date/_+ + OnSaleDate prismOnSaleDate Struct+ + OnSaleDateA-platform string/_+ + OnSaleDateDate date/_+ + OnSaleDay prismOnSaleDay Struct+ + OnSaleDayA-platform string/_+ + OnSaleDayDay string/_+ + Organization string/+ + OriginPlatform string/+ + PageCount integer/ + PageProgressionDirection string/ + PageRange string/+ + Person string/ + Platform string/ + ProductCode string/ + Profession string/ + PublicationDate prismPublicationDate Struct+ + PublicationDateA-platform string/_+ + PublicationDateDate date/_+ + PublicationDisplayDate prismPublicationDate Struct+ + PublicationDisplayDateA-platform string/_+ + PublicationDisplayDateDate date/_+ + PublicationName string/ + PublishingFrequency string/ + Rating string/ + SamplePageRange string/ + Section string/ + SellingAgency string/ + SeriesNumber integer/ + SeriesTitle string/ + Sport string/ + StartingPage string/ + Subsection1 string/ + Subsection2 string/ + Subsection3 string/ + Subsection4 string/ + Subtitle string/ + SupplementDisplayID string/ + SupplementStartingPage string/ + SupplementTitle string/ + Teaser string/+ + Ticker string/+ + TimePeriod string/ + URL prismUrl Struct+ + URLA-platform string/_+ + URLUrl string/_+ + UspsNumber string/ + VersionIdentifier string/ + Volume string/ + WordCount integer/ + +=head3 XMP prismAlternateTitle Struct + + Field Name Writable + ---------- -------- + A-lang string + A-platform string + Text string + +=head3 XMP prismChannel Struct + + Field Name Writable + ---------- -------- + A-lang string + Channel string + Subchannel1 string + Subchannel2 string + Subchannel3 string + Subchannel4 string + +=head3 XMP prismHasCorrection Struct + + Field Name Writable + ---------- -------- + A-lang string + A-platform string + Text string + +=head3 XMP prismKillDate Struct + + Field Name Writable + ---------- -------- + A-platform string + Date date + +=head3 XMP prismOffSaleDate Struct + + Field Name Writable + ---------- -------- + A-platform string + Date date + +=head3 XMP prismOnSaleDate Struct + + Field Name Writable + ---------- -------- + A-platform string + Date date + +=head3 XMP prismOnSaleDay Struct + + Field Name Writable + ---------- -------- + A-platform string + Day string + +=head3 XMP prismPublicationDate Struct + + Field Name Writable + ---------- -------- + A-platform string + Date date + +=head3 XMP prismUrl Struct + + Field Name Writable + ---------- -------- + A-platform string + Url string + +=head3 XMP prl Tags + +PRISM Rights Language 2.1 namespace tags. These tags have been deprecated +since the release of the PRISM Usage Rights 3.0. (see +L<https://www.w3.org/submissions/2020/SUBM-prism-20200910/prism-image.html>) + +These tags belong to the ExifTool XMP-prl family 1 group. + + Tag Name Writable + -------- -------- + Geography string/+ + Industry string/+ + Usage string/+ + +=head3 XMP prm Tags + +PRISM Recipe Metadata 3.0 namespace tags. (see +L<http://www.prismstandard.org/>) + +These tags belong to the ExifTool XMP-prm family 1 group. + + Tag Name Writable + -------- -------- + CookingEquipment string/ + CookingMethod string/ + Course string/ + Cuisine string/ + DietaryNeeds string/ + DishType string/ + Duration string/ + IngredientExclusion string/ + MainIngredient string/ + Meal string/ + RecipeEndingPage string/ + RecipePageRange string/ + RecipeSource string/ + RecipeStartingPage string/ + RecipeTitle string/ + ServingSize string/ + SkillLevel string/ + SpecialOccasion string/ + Yield string/ + +=head3 XMP pur Tags + +PRISM Usage Rights 3.0 namespace tags. (see +L<http://www.prismstandard.org/>) + +These tags belong to the ExifTool XMP-pur family 1 group. + + Tag Name Writable + -------- -------- + AdultContentWarning string/+ + Agreement string/+ + Copyright lang-alt/ + CreditLine string/+ + EmbargoDate date/+ + ExclusivityEndDate date/+ + ExpirationDate date/+ + ImageSizeRestriction string/ + OptionEndDate date/+ + Permissions string/+ + Restrictions string/+ + ReuseProhibited boolean/ + RightsAgent string/ + RightsOwner string/ + +=head3 XMP rdf Tags + +Most RDF attributes are handled internally, but the "about" attribute is +treated specially to allow it to be set to a specific value if required. + +These tags belong to the ExifTool XMP-rdf family 1 group. + + Tag Name Writable + -------- -------- + About string! + +=head3 XMP swf Tags + +Adobe SWF namespace tags. + +These tags belong to the ExifTool XMP-swf family 1 group. + + Tag Name Writable + -------- -------- + BackgroundAlpha integer + ForwardLock boolean + MaxStorage integer + Type string/ + +=head3 XMP tiff Tags + +EXIF namespace for TIFF tags. See +L<https://web.archive.org/web/20180921145139if_/http://www.cipa.jp:80/std/documents/e/DC-010-2017_E.pdf> +for the specification. + +These tags belong to the ExifTool XMP-tiff family 1 group. + + Tag Name Writable + -------- -------- + Artist string + BitsPerSample integer+ + Compression integer + Copyright lang-alt + DateTime date + ImageDescription lang-alt + ImageHeight integer + ImageWidth integer + Make string + Model string + NativeDigest string/ + Orientation integer + PhotometricInterpretation integer + PlanarConfiguration integer + PrimaryChromaticities rational+ + ReferenceBlackWhite rational+ + ResolutionUnit integer + SamplesPerPixel integer + Software string + TransferFunction integer+ + WhitePoint rational+ + XResolution rational + YCbCrCoefficients rational+ + YCbCrPositioning integer + YCbCrSubSampling integer+ + YResolution rational + +=head3 XMP x Tags + +The "x" namespace is used for the "xmpmeta" wrapper, and may contain an +"xmptk" attribute that is extracted as the XMPToolkit tag. When writing, +the XMPToolkit tag is generated automatically by ExifTool unless +specifically set to another value. + +These tags belong to the ExifTool XMP-x family 1 group. + + Tag Name Writable + -------- -------- + XMPToolkit string! + +=head3 XMP xmp Tags + +XMP namespace tags. If the older "xap", "xapBJ", "xapMM" or "xapRights" +namespace prefixes are found, they are translated to the newer "xmp", +"xmpBJ", "xmpMM" and "xmpRights" prefixes for use in family 1 group names. + +These tags belong to the ExifTool XMP-xmp family 1 group. + + Tag Name Writable + -------- -------- + Advisory string+ + Author string/ + BaseURL string + CreateDate date + CreatorTool string + Description lang-alt/ + Format string/ + Identifier string/+ + Keywords string/ + Label string + MetadataDate date + ModifyDate date + Nickname string + PageInfo PageInfo Struct+ + PageImageFormat string_+ + PageImageHeight integer_+ + PageImage string_+ + PageImagePageNumber integer_+ + PageImageWidth integer_+ + Rating real + RatingPercent real/ + Thumbnails Thumbnail Struct+ + ThumbnailFormat string_+ + ThumbnailHeight integer_+ + ThumbnailImage string/_+ + ThumbnailWidth integer_+ + Title lang-alt/ + +=head3 XMP PageInfo Struct + + Field Name Writable + ---------- -------- + PageNumber integer + Format string + Height integer + Image string + Width integer + +=head3 XMP Thumbnail Struct + + Field Name Writable + ---------- -------- + Format string + Height integer + Image string + Width integer + +=head3 XMP xmpBJ Tags + +XMP Basic Job Ticket namespace tags. + +These tags belong to the ExifTool XMP-xmpBJ family 1 group. + + Tag Name Writable + -------- -------- + JobRef JobRef Struct+ + JobRefId string_+ + JobRefName string_+ + JobRefUrl string_+ + +=head3 XMP JobRef Struct + + Field Name Writable + ---------- -------- + Id string + Name string + Url string + +=head3 XMP xmpDM Tags + +XMP Dynamic Media namespace tags. + +These tags belong to the ExifTool XMP-xmpDM family 1 group. + + Tag Name Writable + -------- -------- + AbsPeakAudioFilePath string + Album string + AltTapeName string + AltTimecode Timecode Struct + AltTimecodeTimeFormat string_ + AltTimecodeTimeValue string_ + AltTimecodeValue integer_ + Artist string/ + AudioChannelType string + AudioCompressor string + AudioModDate date + AudioSampleRate integer + AudioSampleType string + BeatSpliceParams BeatSpliceStretch Struct + BeatSpliceParamsRiseInDecibel real_ + BeatSpliceParamsRiseInTimeDuration Time Struct_ + BeatSpliceParamsRiseInTimeDurationScale rational_ + BeatSpliceParamsRiseInTimeDurationValue integer_ + BeatSpliceParamsUseFileBeatsMarker boolean_ + CameraAngle string + CameraLabel string + CameraModel string + CameraMove string + Client string + DMComment string + Composer string + ContributedMedia Media Struct+ + ContributedMediaDuration Time Struct_+ + ContributedMediaDurationScale rational_+ + ContributedMediaDurationValue integer_+ + ContributedMediaManaged boolean_+ + ContributedMediaPath string_+ + ContributedMediaStartTime Time Struct_+ + ContributedMediaStartTimeScale rational_+ + ContributedMediaStartTimeValue integer_+ + ContributedMediaTrack string_+ + ContributedMediaWebStatement string_+ + Copyright string/ + Director string + DirectorPhotography string + DiscNumber string + Duration Time Struct + DurationScale rational_ + DurationValue integer_ + Engineer string + FileDataRate rational + Genre string + Good boolean + Instrument string + IntroTime Time Struct + IntroTimeScale rational_ + IntroTimeValue integer_ + Key string + LogComment string + Loop boolean + Lyrics string + Markers Marker Struct+ + MarkersComment string_+ + MarkersCuePointParams CuePointParam Struct_+ + MarkersCuePointParamsKey string_+ + MarkersCuePointParamsValue string_+ + MarkersCuePointType string_+ + MarkersDuration string_+ + MarkersLocation string_+ + MarkersName string_+ + MarkersProbability real_+ + MarkersSpeaker string_+ + MarkersStartTime string_+ + MarkersTarget string_+ + MarkersType string_+ + MetadataModDate date + NumberOfBeats real + OutCue Time Struct + OutCueScale rational_ + OutCueValue integer_ + PartOfCompilation boolean + ProjectName string + ProjectRef ProjectLink Struct + ProjectRefPath string_ + ProjectRefType string_ + PullDown string + RelativePeakAudioFilePath string + RelativeTimestamp Time Struct + RelativeTimestampScale rational_ + RelativeTimestampValue integer_ + ReleaseDate date + ResampleParams ResampleStretch Struct + ResampleParamsQuality string_ + ScaleType string + Scene string/ + ShotDate date + ShotDay string + ShotLocation string + ShotName string + ShotNumber string + ShotSize string + SpeakerPlacement string + StartTimecode Timecode Struct + StartTimecodeTimeFormat string_ + StartTimecodeTimeValue string_ + StartTimecodeValue integer_ + StartTimeSampleSize integer + StartTimeScale string + StretchMode string + TakeNumber integer + TapeName string + Tempo real + TimeScaleParams TimeScaleStretch Struct + TimeScaleParamsFrameOverlappingPercentage real_ + TimeScaleParamsFrameSize real_ + TimeScaleParamsQuality string_ + TimeSignature string + TrackNumber integer + Tracks Track Struct+ + TracksFrameRate string_+ + TracksMarkers Marker Struct_+ + TracksMarkersComment string_+ + TracksMarkersCuePointParams CuePointParam Struct_+ + TracksMarkersCuePointParamsKey string_+ + TracksMarkersCuePointParamsValue string_+ + TracksMarkersCuePointType string_+ + TracksMarkersDuration string_+ + TracksMarkersLocation string_+ + TracksMarkersName string_+ + TracksMarkersProbability real_+ + TracksMarkersSpeaker string_+ + TracksMarkersStartTime string_+ + TracksMarkersTarget string_+ + TracksMarkersType string_+ + TracksTrackName string_+ + TracksTrackType string_+ + VideoAlphaMode string + VideoAlphaPremultipleColor Colorant Struct + VideoAlphaPremultipleColorA integer_ + VideoAlphaPremultipleColorB integer_ + VideoAlphaPremultipleColorBlack real_ + VideoAlphaPremultipleColorBlue integer_ + VideoAlphaPremultipleColorCyan real_ + VideoAlphaPremultipleColorGray integer_ + VideoAlphaPremultipleColorGreen integer_ + VideoAlphaPremultipleColorL real_ + VideoAlphaPremultipleColorMagenta real_ + VideoAlphaPremultipleColorMode string_ + VideoAlphaPremultipleColorRed integer_ + VideoAlphaPremultipleColorSwatchName string_ + VideoAlphaPremultipleColorTint integer_ + VideoAlphaPremultipleColorType string_ + VideoAlphaPremultipleColorYellow real_ + VideoAlphaUnityIsTransparent boolean + VideoColorSpace string + VideoCompressor string + VideoFieldOrder string + VideoFrameRate real + VideoFrameSize Dimensions Struct + VideoFrameSizeH real_ + VideoFrameSizeUnit string_ + VideoFrameSizeW real_ + VideoModDate date + VideoPixelAspectRatio rational + VideoPixelDepth string + +=head3 XMP BeatSpliceStretch Struct + + Field Name Writable + ---------- -------- + RiseInDecibel real + RiseInTimeDuration Time Struct + UseFileBeatsMarker boolean + +=head3 XMP Time Struct + + Field Name Writable + ---------- -------- + Scale rational + Value integer + +=head3 XMP Media Struct + + Field Name Writable + ---------- -------- + Duration Time Struct + Managed boolean + Path string + StartTime Time Struct + Track string + WebStatement string + +=head3 XMP Marker Struct + + Field Name Writable + ---------- -------- + Comment string + CuePointParams CuePointParam Struct+ + CuePointType string + Duration string + Location string + Name string + Probability real + Speaker string + StartTime string + Target string + Type string + +=head3 XMP CuePointParam Struct + + Field Name Writable + ---------- -------- + Key string + Value string + +=head3 XMP ProjectLink Struct + + Field Name Writable + ---------- -------- + Path string + Type string + +=head3 XMP ResampleStretch Struct + + Field Name Writable + ---------- -------- + Quality string + +=head3 XMP TimeScaleStretch Struct + + Field Name Writable + ---------- -------- + FrameOverlappingPercentage real + FrameSize real + Quality string + +=head3 XMP Track Struct + + Field Name Writable + ---------- -------- + FrameRate string + Markers Marker Struct+ + TrackName string + TrackType string + +=head3 XMP Colorant Struct + + Field Name Writable + ---------- -------- + A integer + B integer + L real + Black real + Blue integer + Cyan real + Gray integer + Green integer + Magenta real + Mode string + Red integer + SwatchName string + Tint integer + Type string + Yellow real + +=head3 XMP Dimensions Struct + + Field Name Writable + ---------- -------- + H real + Unit string + W real + +=head3 XMP xmpMM Tags + +XMP Media Management namespace tags. + +These tags belong to the ExifTool XMP-xmpMM family 1 group. + + Tag Name Writable + -------- -------- + DerivedFrom ResourceRef Struct + DerivedFromAlternatePaths string_+ + DerivedFromDocumentID string_ + DerivedFromFilePath string_ + DerivedFromFromPart string_ + DerivedFromInstanceID string_ + DerivedFromLastModifyDate date_ + DerivedFromLastURL string_ + DerivedFromLinkCategory string_ + DerivedFromLinkForm string_ + DerivedFromManager string_ + DerivedFromManagerVariant string_ + DerivedFromManageTo string_ + DerivedFromManageUI string_ + DerivedFromMaskMarkers string_ + DerivedFromOriginalDocumentID string_ + DerivedFromPartMapping string_ + DerivedFromPlacedResolutionUnit string_ + DerivedFromPlacedXResolution string_ + DerivedFromPlacedYResolution string_ + DerivedFromRenditionClass string_ + DerivedFromRenditionParams string_ + DerivedFromToPart string_ + DerivedFromVersionID string_ + DocumentID string + History ResourceEvent Struct+ + HistoryAction string_+ + HistoryChanged string_+ + HistoryInstanceID string_+ + HistoryParameters string_+ + HistorySoftwareAgent string_+ + HistoryWhen date_+ + Ingredients ResourceRef Struct+ + IngredientsAlternatePaths string_+ + IngredientsDocumentID string_+ + IngredientsFilePath string_+ + IngredientsFromPart string_+ + IngredientsInstanceID string_+ + IngredientsLastModifyDate date_+ + IngredientsLastURL string_+ + IngredientsLinkCategory string_+ + IngredientsLinkForm string_+ + IngredientsManager string_+ + IngredientsManagerVariant string_+ + IngredientsManageTo string_+ + IngredientsManageUI string_+ + IngredientsMaskMarkers string_+ + IngredientsOriginalDocumentID string_+ + IngredientsPartMapping string_+ + IngredientsPlacedResolutionUnit string_+ + IngredientsPlacedXResolution string_+ + IngredientsPlacedYResolution string_+ + IngredientsRenditionClass string_+ + IngredientsRenditionParams string_+ + IngredientsToPart string_+ + IngredientsVersionID string_+ + InstanceID string + LastURL string + ManagedFrom ResourceRef Struct + ManagedFromAlternatePaths string_+ + ManagedFromDocumentID string_ + ManagedFromFilePath string_ + ManagedFromFromPart string_ + ManagedFromInstanceID string_ + ManagedFromLastModifyDate date_ + ManagedFromLastURL string_ + ManagedFromLinkCategory string_ + ManagedFromLinkForm string_ + ManagedFromManager string_ + ManagedFromManagerVariant string_ + ManagedFromManageTo string_ + ManagedFromManageUI string_ + ManagedFromMaskMarkers string_ + ManagedFromOriginalDocumentID string_ + ManagedFromPartMapping string_ + ManagedFromPlacedResolutionUnit string_ + ManagedFromPlacedXResolution string_ + ManagedFromPlacedYResolution string_ + ManagedFromRenditionClass string_ + ManagedFromRenditionParams string_ + ManagedFromToPart string_ + ManagedFromVersionID string_ + Manager string + ManagerVariant string + ManageTo string + ManageUI string + Manifest ManifestItem Struct+ + ManifestLinkForm string_+ + ManifestPlacedResolutionUnit string_+ + ManifestPlacedXResolution real_+ + ManifestPlacedYResolution real_+ + ManifestReference ResourceRef Struct_+ + ManifestReferenceAlternatePaths string_+ + ManifestReferenceDocumentID string_+ + ManifestReferenceFilePath string_+ + ManifestReferenceFromPart string_+ + ManifestReferenceInstanceID string_+ + ManifestReferenceLastModifyDate date_+ + ManifestReferenceLastURL string_+ + ManifestReferenceLinkCategory string_+ + ManifestReferenceLinkForm string_+ + ManifestReferenceManager string_+ + ManifestReferenceManagerVariant string_+ + ManifestReferenceManageTo string_+ + ManifestReferenceManageUI string_+ + ManifestReferenceMaskMarkers string_+ + ManifestReferenceOriginalDocumentID string_+ + ManifestReferencePartMapping string_+ + ManifestReferencePlacedResolutionUnit string_+ + ManifestReferencePlacedXResolution string_+ + ManifestReferencePlacedYResolution string_+ + ManifestReferenceRenditionClass string_+ + ManifestReferenceRenditionParams string_+ + ManifestReferenceToPart string_+ + ManifestReferenceVersionID string_+ + OriginalDocumentID string + Pantry PantryItem Struct+ + PantryInstanceID string_ + PreservedFileName string + RenditionClass string + RenditionOf ResourceRef Struct + RenditionOfAlternatePaths string_+ + RenditionOfDocumentID string_ + RenditionOfFilePath string_ + RenditionOfFromPart string_ + RenditionOfInstanceID string_ + RenditionOfLastModifyDate date_ + RenditionOfLastURL string_ + RenditionOfLinkCategory string_ + RenditionOfLinkForm string_ + RenditionOfManager string_ + RenditionOfManagerVariant string_ + RenditionOfManageTo string_ + RenditionOfManageUI string_ + RenditionOfMaskMarkers string_ + RenditionOfOriginalDocumentID string_ + RenditionOfPartMapping string_ + RenditionOfPlacedResolutionUnit string_ + RenditionOfPlacedXResolution string_ + RenditionOfPlacedYResolution string_ + RenditionOfRenditionClass string_ + RenditionOfRenditionParams string_ + RenditionOfToPart string_ + RenditionOfVersionID string_ + RenditionParams string + SaveID integer + Subject string/+ + VersionID string + Versions Version Struct+ + VersionsComments string_+ + VersionsEvent ResourceEvent Struct_+ + VersionsEventAction string_+ + VersionsEventChanged string_+ + VersionsEventInstanceID string_+ + VersionsEventParameters string_+ + VersionsEventSoftwareAgent string_+ + VersionsEventWhen date_+ + VersionsModifier string_+ + VersionsModifyDate date_+ + VersionsVersion string_+ + +=head3 XMP ResourceRef Struct + + Field Name Writable + ---------- -------- + AlternatePaths string+ + DocumentID string + FilePath string + FromPart string + InstanceID string + LastModifyDate date + LastURL string + LinkCategory string + LinkForm string + ManageTo string + ManageUI string + Manager string + ManagerVariant string + MaskMarkers string + OriginalDocumentID string + PartMapping string + PlacedResolutionUnit string + PlacedXResolution string + PlacedYResolution string + RenditionClass string + RenditionParams string + ToPart string + VersionID string + +=head3 XMP ResourceEvent Struct + + Field Name Writable + ---------- -------- + Action string + Changed string + InstanceID string + Parameters string + SoftwareAgent string + When date + +=head3 XMP ManifestItem Struct + + Field Name Writable + ---------- -------- + LinkForm string + PlacedResolutionUnit string + PlacedXResolution real + PlacedYResolution real + Reference ResourceRef Struct + +=head3 XMP PantryItem Struct + +This structure must have an InstanceID field, but may also contain any other +XMP properties. + + Field Name Writable + ---------- -------- + InstanceID string + +=head3 XMP Version Struct + + Field Name Writable + ---------- -------- + Comments string + Event ResourceEvent Struct + Modifier string + ModifyDate date + Version string + +=head3 XMP xmpNote Tags + +XMP Note namespace tags. + +These tags belong to the ExifTool XMP-xmpNote family 1 group. + + Tag Name Writable + -------- -------- + HasExtendedXMP string* + +=head3 XMP xmpPLUS Tags + +XMP Picture Licensing Universal System (PLUS) tags as written by some older +Adobe applications. See L<PLUS XMP Tags|Image::ExifTool::TagNames/PLUS XMP Tags> +for the current PLUS tags. + +These tags belong to the ExifTool XMP-xmpPLUS family 1 group. + + Tag Name Writable + -------- -------- + CreditLineReq boolean/ + ReuseAllowed boolean/ + +=head3 XMP xmpRights Tags + +XMP Rights Management namespace tags. + +These tags belong to the ExifTool XMP-xmpRights family 1 group. + + Tag Name Writable + -------- -------- + Certificate string + Marked boolean + Owner string+ + UsageTerms lang-alt + WebStatement string + +=head3 XMP xmpTPg Tags + +XMP Paged-Text namespace tags. + +These tags belong to the ExifTool XMP-xmpTPg family 1 group. + + Tag Name Writable + -------- -------- + Colorants Colorant Struct+ + ColorantA integer_+ + ColorantB integer_+ + ColorantBlack real_+ + ColorantBlue integer_+ + ColorantCyan real_+ + ColorantGray integer_+ + ColorantGreen integer_+ + ColorantL real_+ + ColorantMagenta real_+ + ColorantMode string_+ + ColorantRed integer_+ + ColorantSwatchName string_+ + ColorantTint integer_+ + ColorantType string_+ + ColorantYellow real_+ + Fonts Font Struct+ + ChildFontFiles string_+ + FontComposite boolean_+ + FontFace string_+ + FontFamily string_+ + FontFileName string_+ + FontName string_+ + FontType string_+ + FontVersion string_+ + HasVisibleOverprint boolean + HasVisibleTransparency boolean + MaxPageSize Dimensions Struct + MaxPageSizeH real_ + MaxPageSizeUnit string_ + MaxPageSizeW real_ + NPages integer + PlateNames string+ + SwatchGroups SwatchGroup Struct+ + SwatchGroupsColorants Colorant Struct_+ + SwatchColorantA integer_+ + SwatchColorantB integer_+ + SwatchColorantBlack real_+ + SwatchColorantBlue integer_+ + SwatchColorantCyan real_+ + SwatchColorantGray integer_+ + SwatchColorantGreen integer_+ + SwatchColorantL real_+ + SwatchColorantMagenta real_+ + SwatchColorantMode string_+ + SwatchColorantRed integer_+ + SwatchColorantSwatchName string_+ + SwatchColorantTint integer_+ + SwatchColorantType string_+ + SwatchColorantYellow real_+ + SwatchGroupName string_+ + SwatchGroupType integer_+ + +=head3 XMP Font Struct + + Field Name Writable + ---------- -------- + ChildFontFiles string+ + Composite boolean + FontFace string + FontFamily string + FontFileName string + FontName string + FontType string + VersionString string + +=head3 XMP SwatchGroup Struct + + Field Name Writable + ---------- -------- + Colorants Colorant Struct+ + GroupName string + GroupType integer + +=head3 XMP XML Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'dc' dc XMP dc + 'lastUpdate' LastUpdate no + +=head3 XMP SVG Tags + +SVG (Scalable Vector Graphics) image tags. By default, only the top-level +SVG and Metadata tags are extracted from these images, but all graphics tags +may be extracted by setting the Unknown option to 2 (-U on the command +line). The SVG tags are not part of XMP as such, but are included with the +XMP module for convenience. (see L<http://www.w3.org/TR/SVG11/>) + +These tags belong to the ExifTool XMP-svg family 1 group. + + Tag ID Tag Name Writable + ------ -------- -------- + 'height' ImageHeight no + 'id' ID no + 'metadataId' MetadataID no + 'version' SVGVersion no + 'width' ImageWidth no + +=head2 GPS Tags + +These GPS tags are part of the EXIF standard, and are stored in a separate +IFD within the EXIF information. + +ExifTool is very flexible about the input format when writing lat/long +coordinates, and will accept from 1 to 3 floating point numbers (for decimal +degrees, degrees and minutes, or degrees, minutes and seconds) separated by +just about anything, and will format them properly according to the EXIF +specification. + +Some GPS tags have values which are fixed-length strings. For these, the +indicated string lengths include a null terminator which is added +automatically by ExifTool. Remember that the descriptive values are used +when writing (eg. 'Above Sea Level', not '0') unless the print conversion is +disabled (with '-n' on the command line or the PrintConv option in the API, +or by suffixing the tag name with a C<#> character). + +When adding GPS information to an image, it is important to set all of the +following tags: GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef, +and GPSAltitude and GPSAltitudeRef if the altitude is known. ExifTool will +write the required GPSVersionID tag automatically if new a GPS IFD is added +to an image. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 GPSVersionID int8u[4]: + 0x0001 GPSLatitudeRef string[2] + 0x0002 GPSLatitude rational64u[3] + 0x0003 GPSLongitudeRef string[2] + 0x0004 GPSLongitude rational64u[3] + 0x0005 GPSAltitudeRef int8u + 0x0006 GPSAltitude rational64u + 0x0007 GPSTimeStamp rational64u[3] + 0x0008 GPSSatellites string + 0x0009 GPSStatus string[2] + 0x000a GPSMeasureMode string[2] + 0x000b GPSDOP rational64u + 0x000c GPSSpeedRef string[2] + 0x000d GPSSpeed rational64u + 0x000e GPSTrackRef string[2] + 0x000f GPSTrack rational64u + 0x0010 GPSImgDirectionRef string[2] + 0x0011 GPSImgDirection rational64u + 0x0012 GPSMapDatum string + 0x0013 GPSDestLatitudeRef string[2] + 0x0014 GPSDestLatitude rational64u[3] + 0x0015 GPSDestLongitudeRef string[2] + 0x0016 GPSDestLongitude rational64u[3] + 0x0017 GPSDestBearingRef string[2] + 0x0018 GPSDestBearing rational64u + 0x0019 GPSDestDistanceRef string[2] + 0x001a GPSDestDistance rational64u + 0x001b GPSProcessingMethod undef + 0x001c GPSAreaInformation undef + 0x001d GPSDateStamp string[11] + 0x001e GPSDifferential int16u + 0x001f GPSHPositioningError rational64u + +=head2 GeoTiff Tags + +ExifTool extracts the following tags from GeoTIFF images. See +L<https://web.archive.org/web/20070820121549/http://www.remotesensing.org/geotiff/spec/geotiffhome.html> +for the complete GeoTIFF specification. Also included in the table below +are ChartTIFF tags (see +L<https://web.archive.org/web/20020828193928/http://www.charttiff.com/whitepapers.shtml>). +GeoTIFF tags are not writable individually, but they may be copied en mass +via the block tags GeoTiffDirectory, GeoTiffDoubleParams and +GeoTiffAsciiParams. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 GeoTiffVersion no + 0x0400 GTModelType no + 0x0401 GTRasterType no + 0x0402 GTCitation no + 0x0800 GeographicType no + 0x0801 GeogCitation no + 0x0802 GeogGeodeticDatum no + 0x0803 GeogPrimeMeridian no + 0x0804 GeogLinearUnits no + 0x0805 GeogLinearUnitSize no + 0x0806 GeogAngularUnits no + 0x0807 GeogAngularUnitSize no + 0x0808 GeogEllipsoid no + 0x0809 GeogSemiMajorAxis no + 0x080a GeogSemiMinorAxis no + 0x080b GeogInvFlattening no + 0x080c GeogAzimuthUnits no + 0x080d GeogPrimeMeridianLong no + 0x080e GeogToWGS84 no + 0x0c00 ProjectedCSType no + 0x0c01 PCSCitation no + 0x0c02 Projection no + 0x0c03 ProjCoordTrans no + 0x0c04 ProjLinearUnits no + 0x0c05 ProjLinearUnitSize no + 0x0c06 ProjStdParallel1 no + 0x0c07 ProjStdParallel2 no + 0x0c08 ProjNatOriginLong no + 0x0c09 ProjNatOriginLat no + 0x0c0a ProjFalseEasting no + 0x0c0b ProjFalseNorthing no + 0x0c0c ProjFalseOriginLong no + 0x0c0d ProjFalseOriginLat no + 0x0c0e ProjFalseOriginEasting no + 0x0c0f ProjFalseOriginNorthing no + 0x0c10 ProjCenterLong no + 0x0c11 ProjCenterLat no + 0x0c12 ProjCenterEasting no + 0x0c13 ProjCenterNorthing no + 0x0c14 ProjScaleAtNatOrigin no + 0x0c15 ProjScaleAtCenter no + 0x0c16 ProjAzimuthAngle no + 0x0c17 ProjStraightVertPoleLong no + 0x0c18 ProjRectifiedGridAngle no + 0x1000 VerticalCSType no + 0x1001 VerticalCitation no + 0x1002 VerticalDatum no + 0x1003 VerticalUnits no + 0xb799 ChartFormat no + 0xb79a ChartSource no + 0xb79b ChartSourceEdition no + 0xb79c ChartSourceDate no + 0xb79d ChartCorrDate no + 0xb79e ChartCountryOrigin no + 0xb79f ChartRasterEdition no + 0xb7a0 ChartSoundingDatum no + 0xb7a1 ChartDepthUnits no + 0xb7a2 ChartMagVar no + 0xb7a3 ChartMagVarYear no + 0xb7a4 ChartMagVarAnnChange no + 0xb7a5 ChartWGSNSShift no + 0xb7a7 InsetNWPixelX no + 0xb7a8 InsetNWPixelY no + 0xb7a9 ChartContourInterval no + +=head2 PLUS Tags + +=head3 PLUS XMP Tags + +PLUS (Picture Licensing Universal System) License Data Format 2.0.1 XMP +tags. Note that all controlled-vocabulary tags in this table (ie. tags with +a fixed set of values) have raw values which begin with +"http://ns.useplus.org/ldf/vocab/", but to reduce clutter this prefix has +been removed from the values shown below, and from the values read and +written with the -n option. See L<http://ns.useplus.org/> for the complete +specification. + +These tags belong to the ExifTool XMP-plus family 1 group. + + Tag Name Writable + -------- -------- + AdultContentWarning string + CopyrightOwner CopyrightOwner Struct+ + CopyrightOwnerID string_+ + CopyrightOwnerName string_+ + CopyrightOwnerImageID string + CopyrightRegistrationNumber string + CopyrightStatus string + CreditLineRequired string + Custom1 lang-alt+ + Custom10 lang-alt+ + Custom2 lang-alt+ + Custom3 lang-alt+ + Custom4 lang-alt+ + Custom5 lang-alt+ + Custom6 lang-alt+ + Custom7 lang-alt+ + Custom8 lang-alt+ + Custom9 lang-alt+ + DataMining string + EndUser EndUser Struct+ + EndUserID string_+ + EndUserName string_+ + FileNameAsDelivered string + FirstPublicationDate date + ImageAlterationConstraints string+ + ImageCreator ImageCreator Struct+ + ImageCreatorID string_+ + ImageCreatorName string_+ + ImageCreatorImageID string + ImageDuplicationConstraints string + ImageFileConstraints string+ + ImageFileFormatAsDelivered string + ImageFileSizeAsDelivered string + ImageSupplier ImageSupplier Struct+ + ImageSupplierImageID string + ImageSupplierID string_+ + ImageSupplierName string_+ + ImageType string + Licensee Licensee Struct+ + LicenseeImageID string + LicenseeImageNotes lang-alt + LicenseeID string_+ + LicenseeName string_+ + LicenseEndDate date + LicenseeProjectReference string+ + LicenseeTransactionID string+ + LicenseID string + LicenseStartDate date + LicenseTransactionDate date + Licensor Licensor Struct+ + LicensorImageID string + LicensorCity string_+ + LicensorCountry string_+ + LicensorEmail string_+ + LicensorExtendedAddress string_+ + LicensorID string_+ + LicensorName string_+ + LicensorPostalCode string_+ + LicensorRegion string_+ + LicensorStreetAddress string_+ + LicensorTelephone1 string_+ + LicensorTelephone2 string_+ + LicensorTelephoneType1 string_+ + LicensorTelephoneType2 string_+ + LicensorURL string_+ + LicensorNotes lang-alt + LicensorTransactionID string+ + MediaConstraints lang-alt + MediaSummaryCode string + MinorModelAgeDisclosure string + ModelReleaseID string+ + ModelReleaseStatus string + OtherConditions lang-alt + OtherConstraints lang-alt + OtherImageInfo lang-alt + OtherLicenseDocuments string+ + OtherLicenseInfo lang-alt + OtherLicenseRequirements lang-alt + ProductOrServiceConstraints lang-alt + PropertyReleaseID string+ + PropertyReleaseStatus string + RegionConstraints lang-alt + Reuse string + TermsAndConditionsText lang-alt + TermsAndConditionsURL string + PLUSVersion string + +=head3 XMP CopyrightOwner Struct + + Field Name Writable + ---------- -------- + CopyrightOwnerID string + CopyrightOwnerName string + +=head3 XMP EndUser Struct + + Field Name Writable + ---------- -------- + EndUserID string + EndUserName string + +=head3 XMP ImageCreator Struct + + Field Name Writable + ---------- -------- + ImageCreatorID string + ImageCreatorName string + +=head3 XMP ImageSupplier Struct + + Field Name Writable + ---------- -------- + ImageSupplierID string + ImageSupplierName string + +=head3 XMP Licensee Struct + + Field Name Writable + ---------- -------- + LicenseeID string + LicenseeName string + +=head3 XMP Licensor Struct + + Field Name Writable + ---------- -------- + LicensorCity string + LicensorCountry string + LicensorEmail string + LicensorExtendedAddress string + LicensorID string + LicensorName string + LicensorPostalCode string + LicensorRegion string + LicensorStreetAddress string + LicensorTelephone1 string + LicensorTelephone2 string + LicensorTelephoneType1 string + LicensorTelephoneType2 string + LicensorURL string + +=head2 ICC_Profile Tags + +ICC profile information is used in many different file types including JPEG, +TIFF, PDF, PostScript, Photoshop, PNG, MIFF, PICT, QuickTime, XCF and some +RAW formats. While the tags listed below are not individually writable, the +entire profile itself can be accessed via the extra 'ICC_Profile' tag, but +this tag is neither extracted nor written unless specified explicitly. See +L<http://www.color.org/icc_specs2.xalter> for the official ICC +specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'A2B0' AToB0 no + 'A2B1' AToB1 no + 'A2B2' AToB2 no + 'A2B3' AToB3 no + 'A2M0' AToM0 no + 'B2A0' BToA0 no + 'B2A1' BToA1 no + 'B2A2' BToA2 no + 'B2A3' BToA3 no + 'B2D0' BToD0 no + 'B2D1' BToD1 no + 'B2D2' BToD2 no + 'B2D3' BToD3 no + 'CxF ' CXF no + 'D2B0' DToB0 no + 'D2B1' DToB1 no + 'D2B2' DToB2 no + 'D2B3' DToB3 no + 'Header' ProfileHeader ICC_Profile Header + 'M2A0' MToA0 no + 'M2B0' MToB0 no + 'M2B1' MToB1 no + 'M2B2' MToB2 no + 'M2B3' MToB3 no + 'M2S0' MToS0 no + 'M2S1' MToS1 no + 'M2S2' MToS2 no + 'M2S3' MToS3 no + 'MS00' WCSProfiles no + 'bAB0' BRDFAToB0 no + 'bAB1' BRDFAToB1 no + 'bAB2' BRDFAToB2 no + 'bAB3' BRDFAToB3 no + 'bBA0' BRDFBToA0 no + 'bBA1' BRDFBToA1 no + 'bBA2' BRDFBToA2 no + 'bBA3' BRDFBToA3 no + 'bBD0' BRDFBToD0 no + 'bBD1' BRDFBToD1 no + 'bBD2' BRDFBToD2 no + 'bBD3' BRDFBToD3 no + 'bDB0' BRDFDToB0 no + 'bDB1' BRDFDToB1 no + 'bDB2' BRDFDToB2 no + 'bDB3' BRDFDToB3 no + 'bMB0' BRDFMToB0 no + 'bMB1' BRDFMToB1 no + 'bMB2' BRDFMToB2 no + 'bMB3' BRDFMToB3 no + 'bMS0' BRDFMToS0 no + 'bMS1' BRDFMToS1 no + 'bMS2' BRDFMToS2 no + 'bMS3' BRDFMToS3 no + 'bTRC' BlueTRC no + 'bXYZ' BlueMatrixColumn no + 'bcp0' BRDFColorimetricParam0 no + 'bcp1' BRDFColorimetricParam1 no + 'bcp2' BRDFColorimetricParam2 no + 'bcp3' BRDFColorimetricParam3 no + 'bfd ' UCRBG no + 'bkpt' MediaBlackPoint no + 'bsp0' BRDFSpectralParam0 no + 'bsp1' BRDFSpectralParam1 no + 'bsp2' BRDFSpectralParam2 no + 'bsp3' BRDFSpectralParam3 no + 'c2sp' CustomToStandardPcc no + 'calt' CalibrationDateTime no + 'cept' ColorEncodingParams no + 'chad' ChromaticAdaptation no + 'chrm' Chromaticity ICC_Profile Chromaticity + 'ciis' ColorimetricIntentImageState no + 'clio' ColorantInfoOut no + 'cloo' ColorantOrderOut no + 'clot' ColorantTableOut no + 'clro' ColorantOrder no + 'clrt' ColorantTable ICC_Profile ColorantTable + 'cprt' ProfileCopyright no + 'crdi' CRDInfo no + 'csnm' ColorSpaceName no + 'dAB0' DirectionalAToB0 no + 'dAB1' DirectionalAToB1 no + 'dAB2' DirectionalAToB2 no + 'dAB3' DirectionalAToB3 no + 'dBA0' DirectionalBToA0 no + 'dBA1' DirectionalBToA1 no + 'dBA2' DirectionalBToA2 no + 'dBA3' DirectionalBToA3 no + 'dBD0' DirectionalBToD0 no + 'dBD1' DirectionalBToD1 no + 'dBD2' DirectionalBToD2 no + 'dBD3' DirectionalBToD3 no + 'dDB0' DirectionalDToB0 no + 'dDB1' DirectionalDToB1 no + 'dDB2' DirectionalDToB2 no + 'dDB3' DirectionalDToB3 no + 'desc' ProfileDescription no + 'devs' DeviceSettings no + 'dmdd' DeviceModelDesc no + 'dmnd' DeviceMfgDesc no + 'dscm' ProfileDescriptionML no + 'fpce' FocalPlaneColorimetryEstimates no + 'gTRC' GreenTRC no + 'gXYZ' GreenMatrixColumn no + 'gamt' Gamut no + 'gdb0' GamutBoundaryDescription0 no + 'gdb1' GamutBoundaryDescription1 no + 'gdb2' GamutBoundaryDescription2 no + 'gdb3' GamutBoundaryDescription3 no + 'kTRC' GrayTRC no + 'lumi' Luminance no + 'mcta' MultiplexTypeArray no + 'mdv ' MultiplexDefaultValues no + 'meas' Measurement ICC_Profile Measurement + 'meta' Metadata ICC_Profile Metadata + 'miin' MeasurementInputInfo no + 'minf' MeasurementInfo no + 'mmod' MakeAndModel no + 'ncl2' NamedColor2 no + 'ncol' NamedColor no + 'ndin' NativeDisplayInfo no + 'nmcl' NamedColor no + 'pre0' Preview0 no + 'pre1' Preview1 no + 'pre2' Preview2 no + 'ps2i' PS2RenderingIntent no + 'ps2s' PostScript2CSA no + 'psd0' PostScript2CRD0 no + 'psd1' PostScript2CRD1 no + 'psd2' PostScript2CRD2 no + 'psd3' PostScript2CRD3 no + 'pseq' ProfileSequenceDesc no + 'psid' ProfileSequenceIdentifier no + 'psin' ProfileSequenceInfo no + 'psvm' PS2CRDVMSize no + 'rTRC' RedTRC no + 'rXYZ' RedMatrixColumn no + 'resp' OutputResponse no + 'rfnm' ReferenceName no + 'rhoc' ReflectionHardcopyOrigColorimetry no + 'rig0' PerceptualRenderingIntentGamut no + 'rig2' SaturationRenderingIntentGamut no + 'rpoc' ReflectionPrintOutputColorimetry no + 's2cp' StandardToCustomPcc no + 'sape' SceneAppearanceEstimates no + 'scoe' SceneColorimetryEstimates no + 'scrd' ScreeningDesc no + 'scrn' Screening no + 'smap' SurfaceMap no + 'svcn' SpectralViewingConditions no + 'swpt' SpectralWhitePoint no + 'targ' CharTarget no + 'tech' Technology no + 'vcgt' VideoCardGamma no + 'view' ViewingConditions ICC_Profile ViewingConditions + 'vued' ViewingCondDesc no + 'wtpt' MediaWhitePoint no + +=head3 ICC_Profile Header Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 ProfileCMMType no + 8 ProfileVersion no + 12 ProfileClass no + 16 ColorSpaceData no + 20 ProfileConnectionSpace no + 24 ProfileDateTime no + 36 ProfileFileSignature no + 40 PrimaryPlatform no + 44 CMMFlags no + 48 DeviceManufacturer no + 52 DeviceModel no + 56 DeviceAttributes no + 64 RenderingIntent no + 68 ConnectionSpaceIlluminant no + 80 ProfileCreator no + 84 ProfileID no + +=head3 ICC_Profile Chromaticity Tags + + Index1 Tag Name Writable + ------ -------- -------- + 8 ChromaticityChannels no + 10 ChromaticityColorant no + 12 ChromaticityChannel1 no + 20 ChromaticityChannel2 no + 28 ChromaticityChannel3 no + 36 ChromaticityChannel4 no + +=head3 ICC_Profile ColorantTable Tags + + Index1 Tag Name Writable + ------ -------- -------- + 8 ColorantCount no + 12 Colorant1Name no + 44 Colorant1Coordinates no + 50 Colorant2Name no + 82 Colorant2Coordinates no + 88 Colorant3Name no + 120 Colorant3Coordinates no + +=head3 ICC_Profile Measurement Tags + + Index1 Tag Name Writable + ------ -------- -------- + 8 MeasurementObserver no + 12 MeasurementBacking no + 24 MeasurementGeometry no + 28 MeasurementFlare no + 32 MeasurementIlluminant no + +=head3 ICC_Profile Metadata Tags + +Only these few tags have been pre-defined, but ExifTool will extract any +Metadata tags that exist. + + Tag Name Writable + -------- -------- + CreatorApp no + ManufacturerName no + MediaColor no + MediaWeight no + +=head3 ICC_Profile ViewingConditions Tags + + Index1 Tag Name Writable + ------ -------- -------- + 8 ViewingCondIlluminant no + 20 ViewingCondSurround no + 32 ViewingCondIlluminantType no + +=head2 PrintIM Tags + +The format of the PrintIM information is known, however no PrintIM tags have +been decoded. Use the Unknown (-u) option to extract PrintIM information. + + Tag ID Tag Name Writable + ------ -------- -------- + 'PrintIMVersion' PrintIMVersion no + +=head2 Photoshop Tags + +Photoshop tags are found in PSD and PSB files, as well as inside embedded +Photoshop information in many other file types (JPEG, TIFF, PDF, PNG to name +a few). + +Many Photoshop tags are marked as Unknown (indicated by a question mark +after the tag name) because the information they provide is not very useful +under normal circumstances. These unknown tags are not extracted unless the +Unknown (-u) option is used. See +L<http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/> for the +official specification + +Photoshop path tags (Tag ID's 0x7d0 to 0xbb5) are not defined by default, +but a config file included in the full ExifTool distribution +(config_files/photoshop_paths.config) contains the tag definitions to allow +access to this information. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x03e8 Photoshop2Info? no + 0x03e9 MacintoshPrintInfo? no + 0x03ea XMLData? no + 0x03eb Photoshop2ColorTable? no + 0x03ed ResolutionInfo Photoshop Resolution + 0x03ee AlphaChannelsNames no + 0x03ef DisplayInfo? no + 0x03f0 PStringCaption? no + 0x03f1 BorderInformation? no + 0x03f2 BackgroundColor? no + 0x03f3 PrintFlags? no + 0x03f4 BW_HalftoningInfo? no + 0x03f5 ColorHalftoningInfo? no + 0x03f6 DuotoneHalftoningInfo? no + 0x03f7 BW_TransferFunc? no + 0x03f8 ColorTransferFuncs? no + 0x03f9 DuotoneTransferFuncs? no + 0x03fa DuotoneImageInfo? no + 0x03fb EffectiveBW? no + 0x03fc ObsoletePhotoshopTag1? no + 0x03fd EPSOptions? no + 0x03fe QuickMaskInfo? no + 0x03ff ObsoletePhotoshopTag2? no + 0x0400 TargetLayerID? no + 0x0401 WorkingPath? no + 0x0402 LayersGroupInfo? no + 0x0403 ObsoletePhotoshopTag3? no + 0x0404 IPTCData IPTC + 0x0405 RawImageMode? no + 0x0406 JPEG_Quality Photoshop JPEG_Quality + 0x0408 GridGuidesInfo? no + 0x0409 PhotoshopBGRThumbnail undef! + 0x040a CopyrightFlag int8u + 0x040b URL string + 0x040c PhotoshopThumbnail undef! + 0x040d GlobalAngle int32u + 0x040e ColorSamplersResource? no + 0x040f ICC_Profile ICC_Profile + 0x0410 Watermark? no + 0x0411 ICC_Untagged? no + 0x0412 EffectsVisible? no + 0x0413 SpotHalftone? no + 0x0414 IDsBaseValue? no + 0x0415 UnicodeAlphaNames? no + 0x0416 IndexedColorTableCount? no + 0x0417 TransparentIndex? no + 0x0419 GlobalAltitude int32u + 0x041a SliceInfo Photoshop SliceInfo + 0x041b WorkflowURL no + 0x041c JumpToXPEP? no + 0x041d AlphaIdentifiers? no + 0x041e URL_List no+ + 0x0421 VersionInfo Photoshop VersionInfo + 0x0422 EXIFInfo EXIF + 0x0423 ExifInfo2? no + 0x0424 XMP XMP + 0x0425 IPTCDigest string! + 0x0426 PrintScaleInfo Photoshop PrintScaleInfo + 0x0428 PixelInfo Photoshop PixelInfo + 0x0429 LayerComps? no + 0x042a AlternateDuotoneColors? no + 0x042b AlternateSpotColors? no + 0x042d LayerSelectionIDs? no + 0x042e HDRToningInfo? no + 0x042f PrintInfo? no + 0x0430 LayerGroupsEnabledID? no + 0x0431 ColorSamplersResource2? no + 0x0432 MeasurementScale? no + 0x0433 TimelineInfo? no + 0x0434 SheetDisclosure? no + 0x0435 DisplayInfo? no + 0x0436 OnionSkins? no + 0x0438 CountInfo? no + 0x043a PrintInfo2? no + 0x043b PrintStyle? no + 0x043c MacintoshNSPrintInfo? no + 0x043d WindowsDEVMODE? no + 0x043e AutoSaveFilePath? no + 0x043f AutoSaveFormat? no + 0x0440 PathSelectionState? no + 0x0bb7 ClippingPathName no + 0x0bb8 OriginPathInfo? no + 0x1b58 ImageReadyVariables? no + 0x1b59 ImageReadyDataSets? no + 0x1f40 LightroomWorkflow? no + 0x2710 PrintFlagsInfo? no + +=head3 Photoshop Resolution Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 XResolution int32u + 2 DisplayedUnitsX int16u + 4 YResolution int32u + 6 DisplayedUnitsY int16u + +=head3 Photoshop JPEG_Quality Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 PhotoshopQuality int16s + 1 PhotoshopFormat no + 2 ProgressiveScans no + +=head3 Photoshop SliceInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 20 SlicesGroupName no + 24 NumSlices no + +=head3 Photoshop VersionInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 HasRealMergedData no + 5 WriterName no + 9 ReaderName no + +=head3 Photoshop PrintScaleInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 PrintStyle no + 2 PrintPosition no + 10 PrintScale no + +=head3 Photoshop PixelInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 PixelAspectRatio no + +=head3 Photoshop DocumentData Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Layr' Layers Photoshop Layers + 'Lr16' Layers Photoshop Layers + +=head3 Photoshop Layers Tags + +Tags extracted from Photoshop layer information. + + Tag ID Tag Name Writable + ------ -------- -------- + '_xbnd' LayerBlendModes no+ + '_xcnt' LayerCount no + '_xnam' LayerNames no+ + '_xopc' LayerOpacities no+ + '_xrct' LayerRectangles no+ + '_xvis' LayerVisible no+ + 'lclr' LayerColors no+ + 'lsct' LayerSections no+ + 'luni' LayerUnicodeNames no+ + 'lyid' LayerIDs? no+ + 'shmd' LayerModifyDates no+ + +=head3 Photoshop Header Tags + +This information is found in the PSD file header. + + Index2 Tag Name Writable + ------ -------- -------- + 6 NumChannels no + 7 ImageHeight no + 9 ImageWidth no + 11 BitDepth no + 12 ColorMode no + +=head3 Photoshop ImageData Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 Compression no + +=head2 Apple Tags + +Tags extracted from the maker notes of iPhone images. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 MakerNoteVersion int32s + 0x0002 AEMatrix? no + 0x0003 RunTime Apple RunTime + 0x0004 AEStable int32s + 0x0005 AETarget int32s + 0x0006 AEAverage int32s + 0x0007 AFStable int32s + 0x0008 AccelerationVector rational64s[3] + 0x000a HDRImageType int32s + 0x000b BurstUUID string + 0x000c FocusDistanceRange rational64s[2] + 0x000f OISMode int32s + 0x0011 ContentIdentifier string + 0x0014 ImageCaptureType int32s + 0x0015 ImageUniqueID string + 0x0017 LivePhotoVideoIndex yes + 0x0019 ImageProcessingFlags? int32s + 0x001a QualityHint? string + 0x001d LuminanceNoiseAmplitude rational64s + 0x0020 ImageCaptureRequestID? string + 0x0021 HDRHeadroom rational64s + 0x0025 SceneFlags? int32s + 0x0026 SignalToNoiseRatioType? int32s + 0x0027 SignalToNoiseRatio rational64s + 0x002b PhotoIdentifier string + 0x002f FocusPosition int32s + 0x0030 HDRGain rational64s + 0x0038 AFMeasuredDepth int32s + 0x003d AFConfidence int32s + 0x003e ColorCorrectionMatrix? no + 0x003f GreenGhostMitigationStatus? int32s + 0x0040 SemanticStyle no + 0x0041 SemanticStyleRenderingVer no + 0x0042 SemanticStylePreset no + 0x0045 FrontFacingCamera int32s + +=head3 Apple RunTime Tags + +This PLIST-format information contains the elements of a CMTime structure +representing the amount of time the phone has been running since the last +boot, not including standby time. + + Tag ID Tag Name Writable + ------ -------- -------- + 'epoch' RunTimeEpoch no + 'flags' RunTimeFlags no + 'timescale' RunTimeScale no + 'value' RunTimeValue no + +=head2 NikonSettings Tags + +User settings for newer Nikon models. A number of the tags are marked as +Unknown only to reduce the volume of the normal output. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 ISOAutoHiLimit no + 0x0002 ISOAutoFlashLimit no + 0x0003 ISOAutoShutterTime no + 0x000b FlickerReductionShooting no + 0x000c FlickerReductionIndicator no + 0x000d MovieISOAutoHiLimit no + 0x000e MovieISOAutoControlManualMode no + 0x000f MovieWhiteBalanceSameAsPhoto no + 0x001d AF-CPrioritySel no + 0x001e AF-SPrioritySel no + 0x0020 AFPointSel no + 0x0022 AFActivation no + 0x0023 FocusPointWrap no + 0x0025 ManualFocusPointIllumination no + 0x0026 AF-AssistIlluminator no + 0x0027 ManualFocusRingInAFMode no + 0x0029 ISOStepSize no + 0x002a ExposureControlStepSize no + 0x002b EasyExposureCompensation no + 0x002c MatrixMetering no + 0x002d CenterWeightedAreaSize no + 0x002f FineTuneOptMatrixMetering no + 0x0030 FineTuneOptCenterWeighted no + 0x0031 FineTuneOptSpotMetering no + 0x0032 FineTuneOptHighlightWeighted no + 0x0033 ShutterReleaseButtonAE-L no + 0x0034 StandbyMonitorOffTime no + 0x0035 SelfTimerTime no + 0x0036 SelfTimerShotCount no + 0x0037 SelfTimerShotInterval no + 0x0038 PlaybackMonitorOffTime no + 0x0039 MenuMonitorOffTime no + 0x003a ShootingInfoMonitorOffTime no + 0x003b ImageReviewMonitorOffTime no + 0x003c LiveViewMonitorOffTime no + 0x003e CLModeShootingSpeed no + 0x003f MaxContinuousRelease no + 0x0040 ExposureDelayMode no + 0x0041 ElectronicFront-CurtainShutter no + 0x0042 FileNumberSequence no + 0x0043 FramingGridDisplay no + 0x0045 LCDIllumination no + 0x0046 OpticalVR no + 0x0047 FlashSyncSpeed no + 0x0048 FlashShutterSpeed no + 0x0049 FlashExposureCompArea no + 0x004a AutoFlashISOSensitivity no + 0x0051 AssignBktButton no + 0x0052 AssignMovieRecordButton no + 0x0053 MultiSelectorShootMode no + 0x0054 MultiSelectorPlaybackMode no + 0x0056 MultiSelectorLiveView no + 0x0058 CmdDialsReverseRotExposureComp? no + 0x0059 CmdDialsChangeMainSubExposure? no + 0x005a CmdDialsChangeMainSub no + 0x005b CmdDialsMenuAndPlayback no + 0x005c SubDialFrameAdvance no + 0x005d ReleaseButtonToUseDial no + 0x005e ReverseIndicators no + 0x0062 MovieShutterButton no + 0x0063 Language no + 0x006c ShootingInfoDisplay no + 0x0074 FlickAdvanceDirection no + 0x0075 HDMIOutputResolution no + 0x0077 HDMIOutputRange no + 0x0080 RemoteFuncButton no + 0x008b CmdDialsReverseRotation no + 0x008d FocusPeakingHighlightColor no + 0x008e ContinuousModeDisplay no + 0x008f ShutterSpeedLock no + 0x0090 ApertureLock no + 0x0091 MovieHighlightDisplayThreshold no + 0x0092 HDMIExternalRecorder no + 0x0093 BlockShotAFResponse no + 0x0094 SubjectMotion no + 0x0095 Three-DTrackingFaceDetection no + 0x0097 StoreByOrientation no + 0x0099 DynamicAreaAFAssist no + 0x009a ExposureCompStepSize no + 0x009b SyncReleaseMode no + 0x009c ModelingFlash no + 0x009d AutoBracketModeM no + 0x009e PreviewButton no + 0x00a0 Func1Button no + 0x00a2 Func2Button no + 0x00a3 AF-OnButton no + 0x00a4 SubSelector no + 0x00a5 SubSelectorCenter no + 0x00a7 LensFunc1Button no + 0x00a8 CmdDialsApertureSetting no + 0x00a9 MultiSelector no + 0x00aa LiveViewButtonOptions no + 0x00ab LightSwitch no + 0x00b1 MoviePreviewButton no + MovieFunc1Button no + 0x00b3 MovieFunc1Button no + MovieFunc2Button no + 0x00b5 MovieFunc2Button no + 0x00b6 AssignMovieSubselector no + 0x00b8 LimitAFAreaModeSelD9? no + 0x00b9 LimitAFAreaModeSelD25? no + 0x00bc LimitAFAreaModeSel3D? no + 0x00bd LimitAFAreaModeSelGroup? no + 0x00be LimitAFAreaModeSelAuto? no + 0x00c1 LimitSelectableImageArea5To4? no + 0x00c2 LimitSelectableImageArea1To1? no + 0x00d4 PhotoShootingMenuBank no + 0x00d5 CustomSettingsBank no + 0x00d6 LimitAF-AreaModeSelPinpoint? no + 0x00d7 LimitAF-AreaModeSelDynamic? no + 0x00d8 LimitAF-AreaModeSelWideAF_S? no + 0x00d9 LimitAF-AreaModeSelWideAF_L? no + 0x00da LowLightAF no + 0x00db LimitSelectableImageAreaDX? no + 0x00dc LimitSelectableImageArea5To4? no + 0x00dd LimitSelectableImageArea1To1? no + 0x00de LimitSelectableImageArea16To9? no + 0x00df ApplySettingsToLiveView no + 0x00e0 FocusPeakingLevel no + 0x00ea LensControlRing no + 0x00ed MovieMultiSelector no + 0x00ee MovieAFSpeed no + 0x00ef MovieAFSpeedApply no + 0x00f0 MovieAFTrackingSensitivity no + 0x00f1 MovieHighlightDisplayPattern no + 0x00f2 SubDialFrameAdvanceRating5? no + 0x00f3 SubDialFrameAdvanceRating4? no + 0x00f4 SubDialFrameAdvanceRating3? no + 0x00f5 SubDialFrameAdvanceRating2? no + 0x00f6 SubDialFrameAdvanceRating1? no + 0x00f7 SubDialFrameAdvanceRating0? no + 0x00f9 MovieAF-OnButton no + 0x00fb SecondarySlotFunction no + 0x00fc SilentPhotography no + 0x00fd ExtendedShutterSpeeds no + 0x0102 HDMIBitDepth no + 0x0103 HDMIOutputHDR no + 0x0104 HDMIViewAssist no + 0x0109 BracketSet no + 0x010a BracketProgram no + 0x010b BracketIncrement no + 0x010c BracketIncrement no + 0x010e MonitorBrightness no + 0x0116 GroupAreaC1 no + 0x0117 AutoAreaAFStartingPoint no + 0x0118 FocusPointPersistence no + 0x0119 LimitAFAreaModeSelD49? no + 0x011a LimitAFAreaModeSelD105? no + 0x011b LimitAFAreaModeSelGroupC1? no + 0x011c LimitAFAreaModeSelGroupC2? no + 0x011d AutoFocusModeRestrictions no + 0x011e FocusPointBrightness no + 0x011f CHModeShootingSpeed no + 0x0120 CLModeShootingSpeed no + 0x0121 QuietShutterShootingSpeed no + 0x0122 LimitReleaseModeSelCL? no + 0x0123 LimitReleaseModeSelCH? no + 0x0124 LimitReleaseModeSelQ? no + 0x0125 LimitReleaseModeSelTimer? no + 0x0126 LimitReleaseModeSelMirror-Up? no + 0x0127 LimitSelectableImageArea16To9? no + 0x0128 RearControPanelDisplay no + 0x0129 FlashBurstPriority no + 0x012a RecallShootFuncExposureMode no + 0x012b RecallShootFuncShutterSpeed no + 0x012c RecallShootFuncAperture no + 0x012d RecallShootFuncExposureComp no + 0x012e RecallShootFuncISO no + 0x012f RecallShootFuncMeteringMode no + 0x0130 RecallShootFuncWhiteBalance no + 0x0131 RecallShootFuncAFAreaMode no + 0x0132 RecallShootFuncFocusTracking no + 0x0133 RecallShootFuncAF-On no + 0x0134 VerticalFuncButton no + 0x0135 Func3Button no + 0x0136 VerticalAF-OnButton no + 0x0137 VerticalMultiSelector no + 0x0138 MeteringButton no + 0x0139 PlaybackFlickUp no + 0x013a PlaybackFlickUpRating no + 0x013b PlaybackFlickDown no + 0x013c PlaybackFlickDownRating no + 0x013d MovieFunc3Button no + 0x0150 ShutterType no + 0x0151 LensFunc2Button no + 0x0158 USBPowerDelivery no + 0x0159 EnergySavingMode no + 0x015c BracketingBurstOptions no + 0x015e PrimarySlot no + 0x015f ReverseFocusRing no + 0x0160 VerticalFuncButton no + 0x0161 VerticalAFOnButton no + 0x0162 VerticalMultiSelector no + 0x0164 VerticalMovieFuncButton no + 0x0165 VerticalMovieAFOnButton no + 0x0169 LimitAF-AreaModeSelAutoPeople? no + 0x016a LimitAF-AreaModeSelAutoAnimals? no + 0x016b LimitAF-AreaModeSelWideLPeople? no + 0x016c LimitAF-AreaModeSelWideLAnimals? no + 0x016d SaveFocus no + 0x016e AFAreaMode no + 0x016f MovieAFAreaMode no + 0x0170 PreferSubSelectorCenter no + 0x0171 KeepExposureWithTeleconverter no + 0x0174 FocusPointSelectionSpeed no + +=head2 Canon Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 CanonCameraSettings Canon CameraSettings + 0x0002 CanonFocalLength Canon FocalLength + 0x0003 CanonFlashInfo? no + 0x0004 CanonShotInfo Canon ShotInfo + 0x0005 CanonPanorama Canon Panorama + 0x0006 CanonImageType string + 0x0007 CanonFirmwareVersion string + 0x0008 FileNumber int32u + 0x0009 OwnerName string + 0x000a UnknownD30 Canon UnknownD30 + 0x000c SerialNumber int32u + 0x000d CanonCameraInfo1D Canon CameraInfo1D + CanonCameraInfo1DmkII Canon CameraInfo1DmkII + CanonCameraInfo1DmkIIN Canon CameraInfo1DmkIIN + CanonCameraInfo1DmkIII Canon CameraInfo1DmkIII + CanonCameraInfo1DmkIV Canon CameraInfo1DmkIV + CanonCameraInfo1DX Canon CameraInfo1DX + CanonCameraInfo5D Canon CameraInfo5D + CanonCameraInfo5DmkII Canon CameraInfo5DmkII + CanonCameraInfo5DmkIII Canon CameraInfo5DmkIII + CanonCameraInfo6D Canon CameraInfo6D + CanonCameraInfo7D Canon CameraInfo7D + CanonCameraInfo40D Canon CameraInfo40D + CanonCameraInfo50D Canon CameraInfo50D + CanonCameraInfo60D Canon CameraInfo60D + CanonCameraInfo70D Canon CameraInfo70D + CanonCameraInfo80D Canon CameraInfo80D + CanonCameraInfo450D Canon CameraInfo450D + CanonCameraInfo500D Canon CameraInfo500D + CanonCameraInfo550D Canon CameraInfo550D + CanonCameraInfo600D Canon CameraInfo600D + CanonCameraInfo650D Canon CameraInfo650D + CanonCameraInfo700D Canon CameraInfo650D + CanonCameraInfo750D Canon CameraInfo750D + CanonCameraInfo760D Canon CameraInfo750D + CanonCameraInfo1000D Canon CameraInfo1000D + CanonCameraInfo1100D Canon CameraInfo600D + CanonCameraInfo1200D Canon CameraInfo60D + CanonCameraInfoPowerShot Canon CameraInfoPowerShot + CanonCameraInfoPowerShot2 Canon CameraInfoPowerShot2 + CanonCameraInfoUnknown32 Canon CameraInfoUnknown32 + CanonCameraInfoUnknown16 Canon CameraInfoUnknown16 + CanonCameraInfoUnknown Canon CameraInfoUnknown + 0x000e CanonFileLength int32u + 0x000f CustomFunctions1D CanonCustom Functions1D + CustomFunctions5D CanonCustom Functions5D + CustomFunctions10D CanonCustom Functions10D + CustomFunctions20D CanonCustom Functions20D + CustomFunctions30D CanonCustom Functions30D + CustomFunctions350D CanonCustom Functions350D + CustomFunctions400D CanonCustom Functions400D + CustomFunctionsD30 CanonCustom FunctionsD30 + CustomFunctionsD60 CanonCustom FunctionsD30 + CustomFunctionsUnknown CanonCustom FuncsUnknown + 0x0010 CanonModelID int32u + 0x0011 MovieInfo Canon MovieInfo + 0x0012 CanonAFInfo Canon AFInfo + 0x0013 ThumbnailImageValidArea int16u[4] + 0x0015 SerialNumberFormat int32u + 0x001a SuperMacro int16u + 0x001c DateStampMode int16u + 0x001d MyColors Canon MyColors + 0x001e FirmwareRevision int32u + 0x0023 Categories int32u[2] + 0x0024 FaceDetect1 Canon FaceDetect1 + 0x0025 FaceDetect2 Canon FaceDetect2 + 0x0026 CanonAFInfo2 Canon AFInfo2 + 0x0027 ContrastInfo Canon ContrastInfo + 0x0028 ImageUniqueID int8u + 0x0029 WBInfo Canon WBInfo + 0x002f FaceDetect3 Canon FaceDetect3 + 0x0035 TimeInfo Canon TimeInfo + 0x0038 BatteryType undef + 0x003c AFInfo3 Canon AFInfo2 + 0x0081 RawDataOffset no + 0x0082 RawDataLength no + 0x0083 OriginalDecisionDataOffset int32u* + 0x0090 CustomFunctions1D CanonCustom Functions1D + 0x0091 PersonalFunctions CanonCustom PersonalFuncs + 0x0092 PersonalFunctionValues CanonCustom PersonalFuncValues + 0x0093 CanonFileInfo Canon FileInfo + 0x0094 AFPointsInFocus1D no + 0x0095 LensModel string + 0x0096 SerialInfo Canon SerialInfo + InternalSerialNumber string + 0x0097 DustRemovalData undef! + 0x0098 CropInfo Canon CropInfo + 0x0099 CustomFunctions2 CanonCustom Functions2 + 0x009a AspectInfo Canon AspectInfo + 0x00a0 ProcessingInfo Canon Processing + 0x00a1 ToneCurveTable no + 0x00a2 SharpnessTable no + 0x00a3 SharpnessFreqTable no + 0x00a4 WhiteBalanceTable no + 0x00a9 ColorBalance Canon ColorBalance + 0x00aa MeasuredColor Canon MeasuredColor + 0x00ae ColorTemperature int16u + 0x00b0 CanonFlags Canon Flags + 0x00b1 ModifiedInfo Canon ModifiedInfo + 0x00b2 ToneCurveMatching no + 0x00b3 WhiteBalanceMatching no + 0x00b4 ColorSpace int16u + 0x00b6 PreviewImageInfo Canon PreviewImageInfo + 0x00d0 VRDOffset int32u* + 0x00e0 SensorInfo Canon SensorInfo + 0x4001 ColorData1 Canon ColorData1 + ColorData2 Canon ColorData2 + ColorData3 Canon ColorData3 + ColorData4 Canon ColorData4 + ColorData5 Canon ColorData5 + ColorData6 Canon ColorData6 + ColorData7 Canon ColorData7 + ColorData8 Canon ColorData8 + ColorData9 Canon ColorData9 + ColorData10 Canon ColorData10 + ColorData11 Canon ColorData11 + ColorDataUnknown Canon ColorDataUnknown + 0x4002 CRWParam? no + 0x4003 ColorInfo Canon ColorInfo + 0x4005 Flavor? no + 0x4008 PictureStyleUserDef [Values 0-2] + Canon PictureStyle + 0x4009 PictureStylePC [Values 0-2] + Canon PictureStyle + 0x4010 CustomPictureStyleFileName string + 0x4013 AFMicroAdj Canon AFMicroAdj + 0x4015 VignettingCorr Canon VignettingCorr + VignettingCorrUnknown1 Canon VignettingCorrUnknown + VignettingCorrUnknown2 Canon VignettingCorrUnknown + 0x4016 VignettingCorr2 Canon VignettingCorr2 + 0x4018 LightingOpt Canon LightingOpt + 0x4019 LensInfo Canon LensInfo + 0x4020 AmbienceInfo Canon Ambience + 0x4021 MultiExp Canon MultiExp + 0x4024 FilterInfo Canon FilterInfo + 0x4025 HDRInfo Canon HDRInfo + 0x4026 LogInfo Canon LogInfo + 0x4028 AFConfig Canon AFConfig + 0x403f RawBurstModeRoll Canon RawBurstInfo + +=head3 Canon CameraSettings Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 MacroMode int16s + 2 SelfTimer int16s + 3 Quality int16s + 4 CanonFlashMode int16s + 5 ContinuousDrive int16s + 7 FocusMode int16s + 9 RecordMode int16s + 10 CanonImageSize int16s + 11 EasyMode int16s + 12 DigitalZoom int16s + 13 Contrast int16s + 14 Saturation int16s + 15 Sharpness int16s + 16 CameraISO int16s + 17 MeteringMode int16s + 18 FocusRange int16s + 19 AFPoint int16s + 20 CanonExposureMode int16s + 22 LensType int16u + 23 MaxFocalLength int16u + 24 MinFocalLength int16u + 25 FocalUnits int16s + 26 MaxAperture int16s + 27 MinAperture int16s + 28 FlashActivity int16s + 29 FlashBits int16s + 32 FocusContinuous int16s + 33 AESetting int16s + 34 ImageStabilization int16s + 35 DisplayAperture int16s + 36 ZoomSourceWidth int16s + 37 ZoomTargetWidth int16s + 39 SpotMeteringMode int16s + 40 PhotoEffect int16s + 41 ManualFlashOutput int16s + 42 ColorTone int16s + 46 SRAWQuality int16s + +=head3 Canon FocalLength Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 FocalType int16u + 1 FocalLength int16u + 2 FocalPlaneXSize int16u + FocalPlaneXUnknown? int16u + 3 FocalPlaneYSize int16u + FocalPlaneYUnknown? int16u + +=head3 Canon ShotInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 AutoISO int16s + 2 BaseISO int16s + 3 MeasuredEV int16s + 4 TargetAperture int16s + 5 TargetExposureTime int16s + 6 ExposureCompensation int16s + 7 WhiteBalance int16s + 8 SlowShutter int16s + 9 SequenceNumber int16s + 10 OpticalZoomCode int16s + 12 CameraTemperature int16s + 13 FlashGuideNumber int16s + 14 AFPointsInFocus int16s + 15 FlashExposureComp int16s + 16 AutoExposureBracketing int16s + 17 AEBBracketValue int16s + 18 ControlMode int16s + 19 FocusDistanceUpper int16u + 20 FocusDistanceLower int16u + 21 FNumber int16s + 22 ExposureTime int16s + 23 MeasuredEV2 int16s + 24 BulbDuration int16s + 26 CameraType int16s + 27 AutoRotate int16s + 28 NDFilter int16s + 29 SelfTimer2 int16s + 33 FlashOutput int16s + +=head3 Canon Panorama Tags + + Index2 Tag Name Writable + ------ -------- -------- + 2 PanoramaFrameNumber int16s + 5 PanoramaDirection int16s + +=head3 Canon UnknownD30 Tags + + Index2 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Canon CameraInfo1D Tags + +Information in the "CameraInfo" records is tricky to decode because the +encodings are very different than in other Canon records (even sometimes +switching endianness between values within a single camera), plus there is +considerable variation in format from model to model. The first table below +lists CameraInfo tags for the 1D and 1DS. + + Index1 Tag Name Writable + ------ -------- -------- + 4 ExposureTime int8u + 10 FocalLength int16u + 13 LensType int16uRev + 14 MinFocalLength int16u + 16 MaxFocalLength int16u + 65 SharpnessFrequency int8u + 66 Sharpness int8s + 68 WhiteBalance int8u + 71 SharpnessFrequency int8u + 72 ColorTemperature int16u + Sharpness int8s + 74 WhiteBalance int8u + 75 PictureStyle int8u + 78 ColorTemperature int16u + 81 PictureStyle int8u + +=head3 Canon CameraInfo1DmkII Tags + +CameraInfo tags for the 1DmkII and 1DSmkII. + + Index1 Tag Name Writable + ------ -------- -------- + 4 ExposureTime int8u + 9 FocalLength int16uRev + 12 LensType int16uRev + 17 MinFocalLength int16uRev + 19 MaxFocalLength int16uRev + 45 FocalType int8u + 54 WhiteBalance int8u + 55 ColorTemperature int16uRev + 57 CanonImageSize int16u + 102 JPEGQuality int8u + 108 PictureStyle int8u + 110 Saturation int8s + 111 ColorTone int8s + 114 Sharpness int8s + 115 Contrast int8s + 117 ISO string[5] + +=head3 Canon CameraInfo1DmkIIN Tags + +CameraInfo tags for the 1DmkIIN. + + Index1 Tag Name Writable + ------ -------- -------- + 4 ExposureTime int8u + 9 FocalLength int16uRev + 12 LensType int16uRev + 17 MinFocalLength int16uRev + 19 MaxFocalLength int16uRev + 54 WhiteBalance int8u + 55 ColorTemperature int16uRev + 115 PictureStyle int8u + 116 Sharpness int8s + 117 Contrast int8s + 118 Saturation int8s + 119 ColorTone int8s + 121 ISO string[5] + +=head3 Canon CameraInfo1DmkIII Tags + +CameraInfo tags for the 1DmkIII and 1DSmkIII. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 24 CameraTemperature int8u + 27 MacroMagnification int8u + 29 FocalLength int16uRev + 48 CameraOrientation int8u + 67 FocusDistanceUpper int16uRev + 69 FocusDistanceLower int16uRev + 94 WhiteBalance int16u + 98 ColorTemperature int16u + 134 PictureStyle int8u + 273 LensType int16uRev + 275 MinFocalLength int16uRev + 277 MaxFocalLength int16uRev + 310 FirmwareVersion string[6] + 370 FileIndex int32u + 374 ShutterCount int32u + 382 DirectoryIndex int32u + 682 PictureStyleInfo Canon PSInfo + 1114 TimeStamp1 int32u + 1118 TimeStamp int32u + +=head3 Canon PSInfo Tags + +Custom picture style information for various models. + + Index1 Tag Name Writable + ------ -------- -------- + 0 ContrastStandard int32s + 4 SharpnessStandard int32s + 8 SaturationStandard int32s + 12 ColorToneStandard int32s + 16 FilterEffectStandard? int32s + 20 ToningEffectStandard? int32s + 24 ContrastPortrait int32s + 28 SharpnessPortrait int32s + 32 SaturationPortrait int32s + 36 ColorTonePortrait int32s + 40 FilterEffectPortrait? int32s + 44 ToningEffectPortrait? int32s + 48 ContrastLandscape int32s + 52 SharpnessLandscape int32s + 56 SaturationLandscape int32s + 60 ColorToneLandscape int32s + 64 FilterEffectLandscape? int32s + 68 ToningEffectLandscape? int32s + 72 ContrastNeutral int32s + 76 SharpnessNeutral int32s + 80 SaturationNeutral int32s + 84 ColorToneNeutral int32s + 88 FilterEffectNeutral? int32s + 92 ToningEffectNeutral? int32s + 96 ContrastFaithful int32s + 100 SharpnessFaithful int32s + 104 SaturationFaithful int32s + 108 ColorToneFaithful int32s + 112 FilterEffectFaithful? int32s + 116 ToningEffectFaithful? int32s + 120 ContrastMonochrome int32s + 124 SharpnessMonochrome int32s + 128 SaturationMonochrome? int32s + 132 ColorToneMonochrome? int32s + 136 FilterEffectMonochrome int32s + 140 ToningEffectMonochrome int32s + 144 ContrastUserDef1 int32s + 148 SharpnessUserDef1 int32s + 152 SaturationUserDef1 int32s + 156 ColorToneUserDef1 int32s + 160 FilterEffectUserDef1 int32s + 164 ToningEffectUserDef1 int32s + 168 ContrastUserDef2 int32s + 172 SharpnessUserDef2 int32s + 176 SaturationUserDef2 int32s + 180 ColorToneUserDef2 int32s + 184 FilterEffectUserDef2 int32s + 188 ToningEffectUserDef2 int32s + 192 ContrastUserDef3 int32s + 196 SharpnessUserDef3 int32s + 200 SaturationUserDef3 int32s + 204 ColorToneUserDef3 int32s + 208 FilterEffectUserDef3 int32s + 212 ToningEffectUserDef3 int32s + 216 UserDef1PictureStyle int16u + 218 UserDef2PictureStyle int16u + 220 UserDef3PictureStyle int16u + +=head3 Canon CameraInfo1DmkIV Tags + +CameraInfo tags for the EOS 1D Mark IV. Indices shown are for firmware +versions 1.0.x, but they may be different for other firmware versions. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 7 HighlightTonePriority int8u + 8 MeasuredEV2 int8u + 9 MeasuredEV3 int8u + 21 FlashMeteringMode int8u + 25 CameraTemperature int8u + 30 FocalLength int16uRev + 53 CameraOrientation int8u + 84 FocusDistanceUpper int16uRev + 86 FocusDistanceLower int16uRev + 120 WhiteBalance int16u + 124 ColorTemperature int16u + 335 LensType int16uRev + 337 MinFocalLength int16uRev + 339 MaxFocalLength int16uRev + 493 FirmwareVersion no + 556 FileIndex int32u + 568 DirectoryIndex int32u + 872 PictureStyleInfo Canon PSInfo + +=head3 Canon CameraInfo1DX Tags + +CameraInfo tags for the EOS 1D X. Indices shown are for firmware version +1.0.2, but they may be different for other firmware versions. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 27 CameraTemperature int8u + 35 FocalLength int16uRev + 125 CameraOrientation int8u + 140 FocusDistanceUpper int16uRev + 142 FocusDistanceLower int16uRev + 188 WhiteBalance int16u + 192 ColorTemperature int16u + 244 PictureStyle int8u + 423 LensType int16uRev + 425 MinFocalLength int16uRev + 427 MaxFocalLength int16uRev + 640 FirmwareVersion no + 720 FileIndex int32u + 732 DirectoryIndex int32u + 1012 PictureStyleInfo Canon PSInfo2 + +=head3 Canon PSInfo2 Tags + +Custom picture style information for the EOS 5DmkIII, 60D, 600D and 1100D. + + Index1 Tag Name Writable + ------ -------- -------- + 0 ContrastStandard int32s + 4 SharpnessStandard int32s + 8 SaturationStandard int32s + 12 ColorToneStandard int32s + 16 FilterEffectStandard? int32s + 20 ToningEffectStandard? int32s + 24 ContrastPortrait int32s + 28 SharpnessPortrait int32s + 32 SaturationPortrait int32s + 36 ColorTonePortrait int32s + 40 FilterEffectPortrait? int32s + 44 ToningEffectPortrait? int32s + 48 ContrastLandscape int32s + 52 SharpnessLandscape int32s + 56 SaturationLandscape int32s + 60 ColorToneLandscape int32s + 64 FilterEffectLandscape? int32s + 68 ToningEffectLandscape? int32s + 72 ContrastNeutral int32s + 76 SharpnessNeutral int32s + 80 SaturationNeutral int32s + 84 ColorToneNeutral int32s + 88 FilterEffectNeutral? int32s + 92 ToningEffectNeutral? int32s + 96 ContrastFaithful int32s + 100 SharpnessFaithful int32s + 104 SaturationFaithful int32s + 108 ColorToneFaithful int32s + 112 FilterEffectFaithful? int32s + 116 ToningEffectFaithful? int32s + 120 ContrastMonochrome int32s + 124 SharpnessMonochrome int32s + 128 SaturationMonochrome? int32s + 132 ColorToneMonochrome? int32s + 136 FilterEffectMonochrome int32s + 140 ToningEffectMonochrome int32s + 144 ContrastAuto int32s + 148 SharpnessAuto int32s + 152 SaturationAuto int32s + 156 ColorToneAuto int32s + 160 FilterEffectAuto int32s + 164 ToningEffectAuto int32s + 168 ContrastUserDef1 int32s + 172 SharpnessUserDef1 int32s + 176 SaturationUserDef1 int32s + 180 ColorToneUserDef1 int32s + 184 FilterEffectUserDef1 int32s + 188 ToningEffectUserDef1 int32s + 192 ContrastUserDef2 int32s + 196 SharpnessUserDef2 int32s + 200 SaturationUserDef2 int32s + 204 ColorToneUserDef2 int32s + 208 FilterEffectUserDef2 int32s + 212 ToningEffectUserDef2 int32s + 216 ContrastUserDef3 int32s + 220 SharpnessUserDef3 int32s + 224 SaturationUserDef3 int32s + 228 ColorToneUserDef3 int32s + 232 FilterEffectUserDef3 int32s + 236 ToningEffectUserDef3 int32s + 240 UserDef1PictureStyle int16u + 242 UserDef2PictureStyle int16u + 244 UserDef3PictureStyle int16u + +=head3 Canon CameraInfo5D Tags + +CameraInfo tags for the EOS 5D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 12 LensType int16uRev + 23 CameraTemperature int8u + 27 MacroMagnification int8s + 39 CameraOrientation int8s + 40 FocalLength int16uRev + 56 AFPointsInFocus5D int16uRev + 84 WhiteBalance int16u + 88 ColorTemperature int16u + 108 PictureStyle int8u + 147 MinFocalLength int16uRev + 149 MaxFocalLength int16uRev + 151 LensType int16uRev + 164 FirmwareRevision string[8] + 172 ShortOwnerName string[16] + 204 DirectoryIndex int32u + 208 FileIndex int16u + 232 ContrastStandard int8s + 233 ContrastPortrait int8s + 234 ContrastLandscape int8s + 235 ContrastNeutral int8s + 236 ContrastFaithful int8s + 237 ContrastMonochrome int8s + 238 ContrastUserDef1 int8s + 239 ContrastUserDef2 int8s + 240 ContrastUserDef3 int8s + 241 SharpnessStandard int8s + 242 SharpnessPortrait int8s + 243 SharpnessLandscape int8s + 244 SharpnessNeutral int8s + 245 SharpnessFaithful int8s + 246 SharpnessMonochrome int8s + 247 SharpnessUserDef1 int8s + 248 SharpnessUserDef2 int8s + 249 SharpnessUserDef3 int8s + 250 SaturationStandard int8s + 251 SaturationPortrait int8s + 252 SaturationLandscape int8s + 253 SaturationNeutral int8s + 254 SaturationFaithful int8s + 255 FilterEffectMonochrome int8s + 256 SaturationUserDef1 int8s + 257 SaturationUserDef2 int8s + 258 SaturationUserDef3 int8s + 259 ColorToneStandard int8s + 260 ColorTonePortrait int8s + 261 ColorToneLandscape int8s + 262 ColorToneNeutral int8s + 263 ColorToneFaithful int8s + 264 ToningEffectMonochrome int8s + 265 ColorToneUserDef1 int8s + 266 ColorToneUserDef2 int8s + 267 ColorToneUserDef3 int8s + 268 UserDef1PictureStyle int16u + 270 UserDef2PictureStyle int16u + 272 UserDef3PictureStyle int16u + 284 TimeStamp int32u + +=head3 Canon CameraInfo5DmkII Tags + +CameraInfo tags for the EOS 5D Mark II. Indices shown are for firmware +version 1.0.6, but they may be different for other firmware versions. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 7 HighlightTonePriority int8u + 21 FlashMeteringMode int8u + 25 CameraTemperature int8u + 27 MacroMagnification int8u + 30 FocalLength int16uRev + 49 CameraOrientation int8u + 80 FocusDistanceUpper int16uRev + 82 FocusDistanceLower int16uRev + 111 WhiteBalance int16u + 115 ColorTemperature int16u + 167 PictureStyle int8u + 189 HighISONoiseReduction int8u + 191 AutoLightingOptimizer int8u + 230 LensType int16uRev + 232 MinFocalLength int16uRev + 234 MaxFocalLength int16uRev + 382 FirmwareVersion no + 443 FileIndex int32u + 455 DirectoryIndex int32u + 759 PictureStyleInfo Canon PSInfo + +=head3 Canon CameraInfo5DmkIII Tags + +CameraInfo tags for the EOS 5D Mark III. Indices shown are for firmware +versions 1.0.x, but they may be different for other firmware versions. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 27 CameraTemperature int8u + 35 FocalLength int16uRev + 125 CameraOrientation int8u + 140 FocusDistanceUpper int16uRev + 142 FocusDistanceLower int16uRev + 188 WhiteBalance int16u + 192 ColorTemperature int16u + 244 PictureStyle int8u + 339 LensType int16uRev + 341 MinFocalLength int16uRev + 343 MaxFocalLength int16uRev + 356 LensSerialNumber undef[5] + 572 FirmwareVersion no + 652 FileIndex int32u + 656 FileIndex2 int32u + 664 DirectoryIndex int32u + 668 DirectoryIndex2 int32u + 944 PictureStyleInfo Canon PSInfo2 + +=head3 Canon CameraInfo6D Tags + +CameraInfo tags for the EOS 6D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 27 CameraTemperature int8u + 35 FocalLength int16uRev + 131 CameraOrientation int8u + 146 FocusDistanceUpper int16uRev + 148 FocusDistanceLower int16uRev + 194 WhiteBalance int16u + 198 ColorTemperature int16u + 250 PictureStyle int8u + 353 LensType int16uRev + 355 MinFocalLength int16uRev + 357 MaxFocalLength int16uRev + 598 FirmwareVersion no + 682 FileIndex int32u + 694 DirectoryIndex int32u + 966 PictureStyleInfo Canon PSInfo2 + +=head3 Canon CameraInfo7D Tags + +CameraInfo tags for the EOS 7D. Indices shown are for firmware versions +1.0.x, but they may be different for other firmware versions. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 7 HighlightTonePriority int8u + 8 MeasuredEV2 int8u + 9 MeasuredEV int8u + 21 FlashMeteringMode int8u + 25 CameraTemperature int8u + 30 FocalLength int16uRev + 53 CameraOrientation int8u + 84 FocusDistanceUpper int16uRev + 86 FocusDistanceLower int16uRev + 119 WhiteBalance int16u + 123 ColorTemperature int16u + 175 CameraPictureStyle int8u + 201 HighISONoiseReduction int8u + 274 LensType int16uRev + 276 MinFocalLength int16uRev + 278 MaxFocalLength int16uRev + 428 FirmwareVersion no + 491 FileIndex int32u + 503 DirectoryIndex int32u + 807 PictureStyleInfo Canon PSInfo + +=head3 Canon CameraInfo40D Tags + +CameraInfo tags for the EOS 40D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 21 FlashMeteringMode int8u + 24 CameraTemperature int8u + 27 MacroMagnification int8u + 29 FocalLength int16uRev + 48 CameraOrientation int8u + 67 FocusDistanceUpper int16uRev + 69 FocusDistanceLower int16uRev + 111 WhiteBalance int16u + 115 ColorTemperature int16u + 214 LensType int16uRev + 216 MinFocalLength int16uRev + 218 MaxFocalLength int16uRev + 255 FirmwareVersion string[6] + 307 FileIndex int32u + 319 DirectoryIndex int32u + 603 PictureStyleInfo Canon PSInfo + 2347 LensModel string[64] + +=head3 Canon CameraInfo50D Tags + +CameraInfo tags for the EOS 50D. Indices shown are for firmware versions +1.0.x, but they may be different for other firmware versions. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 7 HighlightTonePriority int8u + 21 FlashMeteringMode int8u + 25 CameraTemperature int8u + 30 FocalLength int16uRev + 49 CameraOrientation int8u + 80 FocusDistanceUpper int16uRev + 82 FocusDistanceLower int16uRev + 111 WhiteBalance int16u + 115 ColorTemperature int16u + 167 PictureStyle int8u + 189 HighISONoiseReduction int8u + 191 AutoLightingOptimizer int8u + 234 LensType int16uRev + 236 MinFocalLength int16uRev + 238 MaxFocalLength int16uRev + 350 FirmwareVersion no + 411 FileIndex int32u + 423 DirectoryIndex int32u + 727 PictureStyleInfo Canon PSInfo + +=head3 Canon CameraInfo60D Tags + +CameraInfo tags for the EOS 60D and 1200D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 25 CameraTemperature int8u + 30 FocalLength int16uRev + 54 CameraOrientation int8u + 58 CameraOrientation int8u + 85 FocusDistanceUpper int16uRev + 87 FocusDistanceLower int16uRev + 125 ColorTemperature int16u + 232 LensType int16uRev + 234 MinFocalLength int16uRev + 236 MaxFocalLength int16uRev + 409 FirmwareVersion no + 473 FileIndex int32u + 485 DirectoryIndex int32u + 761 PictureStyleInfo Canon PSInfo2 + 801 PictureStyleInfo Canon PSInfo2 + +=head3 Canon CameraInfo70D Tags + +CameraInfo tags for the EOS 70D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 27 CameraTemperature int8u + 35 FocalLength int16uRev + 132 CameraOrientation int8u + 147 FocusDistanceUpper int16uRev + 149 FocusDistanceLower int16uRev + 199 ColorTemperature int16u + 358 LensType int16uRev + 360 MinFocalLength int16uRev + 362 MaxFocalLength int16uRev + 606 FirmwareVersion no + 691 FileIndex int32u + 703 DirectoryIndex int32u + 975 PictureStyleInfo Canon PSInfo2 + +=head3 Canon CameraInfo80D Tags + +CameraInfo tags for the EOS 80D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 27 CameraTemperature int8u + 35 FocalLength int16uRev + 150 CameraOrientation int8u + 165 FocusDistanceUpper int16uRev + 167 FocusDistanceLower int16uRev + 314 ColorTemperature int16u + 393 LensType int16uRev + 395 MinFocalLength int16uRev + 397 MaxFocalLength int16uRev + 1114 FirmwareVersion no + 1198 FileIndex int32u + 1210 DirectoryIndex int32u + +=head3 Canon CameraInfo450D Tags + +CameraInfo tags for the EOS 450D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 21 FlashMeteringMode int8u + 24 CameraTemperature int8u + 27 MacroMagnification int8u + 29 FocalLength int16uRev + 48 CameraOrientation int8u + 67 FocusDistanceUpper int16uRev + 69 FocusDistanceLower int16uRev + 111 WhiteBalance int16u + 115 ColorTemperature int16u + 222 LensType int16uRev + 263 FirmwareVersion string[6] + 271 OwnerName string[32] + 307 DirectoryIndex int32u + 319 FileIndex int32u + 611 PictureStyleInfo Canon PSInfo + 2355 LensModel string[64] + +=head3 Canon CameraInfo500D Tags + +CameraInfo tags for the EOS 500D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 7 HighlightTonePriority int8u + 21 FlashMeteringMode int8u + 25 CameraTemperature int8u + 30 FocalLength int16uRev + 49 CameraOrientation int8u + 80 FocusDistanceUpper int16uRev + 82 FocusDistanceLower int16uRev + 115 WhiteBalance int16u + 119 ColorTemperature int16u + 171 PictureStyle int8u + 188 HighISONoiseReduction int8u + 190 AutoLightingOptimizer int8u + 246 LensType int16uRev + 248 MinFocalLength int16uRev + 250 MaxFocalLength int16uRev + 400 FirmwareVersion no + 467 FileIndex int32u + 479 DirectoryIndex int32u + 779 PictureStyleInfo Canon PSInfo + +=head3 Canon CameraInfo550D Tags + +CameraInfo tags for the EOS 550D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 7 HighlightTonePriority int8u + 21 FlashMeteringMode int8u + 25 CameraTemperature int8u + 30 FocalLength int16uRev + 53 CameraOrientation int8u + 84 FocusDistanceUpper int16uRev + 86 FocusDistanceLower int16uRev + 120 WhiteBalance int16u + 124 ColorTemperature int16u + 176 PictureStyle int8u + 255 LensType int16uRev + 257 MinFocalLength int16uRev + 259 MaxFocalLength int16uRev + 420 FirmwareVersion no + 484 FileIndex int32u + 496 DirectoryIndex int32u + 796 PictureStyleInfo Canon PSInfo + +=head3 Canon CameraInfo600D Tags + +CameraInfo tags for the EOS 600D and 1100D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 7 HighlightTonePriority int8u + 21 FlashMeteringMode int8u + 25 CameraTemperature int8u + 30 FocalLength int16uRev + 56 CameraOrientation int8u + 87 FocusDistanceUpper int16uRev + 89 FocusDistanceLower int16uRev + 123 WhiteBalance int16u + 127 ColorTemperature int16u + 179 PictureStyle int8u + 234 LensType int16uRev + 236 MinFocalLength int16uRev + 238 MaxFocalLength int16uRev + 411 FirmwareVersion no + 475 FileIndex int32u + 487 DirectoryIndex int32u + 763 PictureStyleInfo Canon PSInfo2 + +=head3 Canon CameraInfo650D Tags + +CameraInfo tags for the EOS 650D and 700D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 27 CameraTemperature int8u + 35 FocalLength int16uRev + 125 CameraOrientation int8u + 140 FocusDistanceUpper int16uRev + 142 FocusDistanceLower int16uRev + 188 WhiteBalance int16u + 192 ColorTemperature int16u + 244 PictureStyle int8u + 295 LensType int16uRev + 297 MinFocalLength int16uRev + 299 MaxFocalLength int16uRev + 539 FirmwareVersion no + 544 FirmwareVersion no + 624 FileIndex int32u + 628 FileIndex int32u + 636 DirectoryIndex int32u + 640 DirectoryIndex int32u + 912 PictureStyleInfo Canon PSInfo2 + +=head3 Canon CameraInfo750D Tags + +CameraInfo tags for the EOS 750D and 760D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 27 CameraTemperature int8u + 35 FocalLength int16uRev + 150 CameraOrientation int8u + 165 FocusDistanceUpper int16uRev + 167 FocusDistanceLower int16uRev + 305 WhiteBalance int16u + 309 ColorTemperature int16u + 361 PictureStyle int8u + 388 LensType int16uRev + 390 MinFocalLength int16uRev + 392 MaxFocalLength int16uRev + 1085 FirmwareVersion no + 1097 FirmwareVersion no + +=head3 Canon CameraInfo1000D Tags + +CameraInfo tags for the EOS 1000D. + + Index1 Tag Name Writable + ------ -------- -------- + 3 FNumber int8u + 4 ExposureTime int8u + 6 ISO int8u + 21 FlashMeteringMode int8u + 24 CameraTemperature int8u + 27 MacroMagnification int8u + 29 FocalLength int16uRev + 48 CameraOrientation int8u + 67 FocusDistanceUpper int16uRev + 69 FocusDistanceLower int16uRev + 111 WhiteBalance int16u + 115 ColorTemperature int16u + 226 LensType int16uRev + 228 MinFocalLength int16uRev + 230 MaxFocalLength int16uRev + 267 FirmwareVersion string[6] + 311 DirectoryIndex int32u + 323 FileIndex int32u + 615 PictureStyleInfo Canon PSInfo + 2359 LensModel string[64] + +=head3 Canon CameraInfoPowerShot Tags + +CameraInfo tags for PowerShot models such as the A450, A460, A550, A560, +A570, A630, A640, A650, A710, A720, G7, G9, S5, SD40, SD750, SD800, SD850, +SD870, SD900, SD950, SD1000, SX100 and TX1. + + Index4 Tag Name Writable + ------ -------- -------- + 0 ISO int32s + 5 FNumber int32s + 6 ExposureTime int32s + 23 Rotation int32s + 135 CameraTemperature int32s + 145 CameraTemperature int32s + +=head3 Canon CameraInfoPowerShot2 Tags + +CameraInfo tags for PowerShot models such as the A470, A480, A490, A495, +A580, A590, A1000, A1100, A2000, A2100, A3000, A3100, D10, E1, G10, G11, +S90, S95, SD770, SD780, SD790, SD880, SD890, SD940, SD960, SD970, SD980, +SD990, SD1100, SD1200, SD1300, SD1400, SD3500, SD4000, SD4500, SX1, SX10, +SX20, SX110, SX120, SX130, SX200 and SX210. + + Index4 Tag Name Writable + ------ -------- -------- + 1 ISO int32s + 6 FNumber int32s + 7 ExposureTime int32s + 24 Rotation int32s + 153 CameraTemperature int32s + 159 CameraTemperature int32s + 164 CameraTemperature int32s + 168 CameraTemperature int32s + 261 CameraTemperature int32s + +=head3 Canon CameraInfoUnknown32 Tags + +Unknown CameraInfo tags are divided into 3 tables based on format size. + + Index4 Tag Name Writable + ------ -------- -------- + 71 CameraTemperature int32s + 83 CameraTemperature int32s + 91 CameraTemperature int32s + 92 CameraTemperature int32s + 100 CameraTemperature int32s + -3 CameraTemperature int32s + +=head3 Canon CameraInfoUnknown16 Tags + + Index2 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Canon CameraInfoUnknown Tags + + Index1 Tag Name Writable + ------ -------- -------- + 363 LensSerialNumber undef[5] + 1473 FirmwareVersion no + +=head3 Canon MovieInfo Tags + +Tags written by some Canon cameras when recording video. + + Index2 Tag Name Writable + ------ -------- -------- + 1 FrameRate int16u + 2 FrameCount int16u + 4 FrameCount int32u + 6 FrameRate rational32u + 106 Duration int32u + 108 AudioBitrate int32u + 110 AudioSampleRate int32u + 112 AudioChannels int32u + 116 VideoCodec undef[4] + +=head3 Canon AFInfo Tags + +Auto-focus information used by many older Canon models. The values in this +record are sequential, and some have variable sizes based on the value of +NumAFPoints (which may be 1,5,7,9,15,45 or 53). The AFArea coordinates are +given in a system where the image has dimensions given by AFImageWidth and +AFImageHeight, and 0,0 is the image center. The direction of the Y axis +depends on the camera model, with positive Y upwards for EOS models, but +apparently downwards for PowerShot models. + + Sequence Tag Name Writable + -------- -------- -------- + 0 NumAFPoints no + 1 ValidAFPoints no + 2 CanonImageWidth no + 3 CanonImageHeight no + 4 AFImageWidth no + 5 AFImageHeight no + 6 AFAreaWidth no + 7 AFAreaHeight no + 8 AFAreaXPositions no + 9 AFAreaYPositions no + 10 AFPointsInFocus no + 11 PrimaryAFPoint no + Canon_AFInfo_0x000b? no + 12 PrimaryAFPoint no + +=head3 Canon MyColors Tags + + Index2 Tag Name Writable + ------ -------- -------- + 2 MyColorMode int16u + +=head3 Canon FaceDetect1 Tags + + Index2 Tag Name Writable + ------ -------- -------- + 2 FacesDetected int16u + 3 FaceDetectFrameSize int16u[2] + 8 Face1Position int16s[2] + 10 Face2Position int16s[2] + 12 Face3Position int16s[2] + 14 Face4Position int16s[2] + 16 Face5Position int16s[2] + 18 Face6Position int16s[2] + 20 Face7Position int16s[2] + 22 Face8Position int16s[2] + 24 Face9Position int16s[2] + +=head3 Canon FaceDetect2 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 FaceWidth int8u + 2 FacesDetected int8u + +=head3 Canon AFInfo2 Tags + +Newer version of the AFInfo record containing much of the same information +(and coordinate confusion) as the older version. In this record, NumAFPoints +may be 7, 9, 11, 19, 31, 45 or 61, depending on the camera model. + + Sequence Tag Name Writable + -------- -------- -------- + 0 AFInfoSize? no + 1 AFAreaMode no + 2 NumAFPoints no + 3 ValidAFPoints no + 4 CanonImageWidth no + 5 CanonImageHeight no + 6 AFImageWidth no + 7 AFImageHeight no + 8 AFAreaWidths no + 9 AFAreaHeights no + 10 AFAreaXPositions no + 11 AFAreaYPositions no + 12 AFPointsInFocus no + 13 AFPointsSelected no + Canon_AFInfo2_0x000d? no + 14 PrimaryAFPoint no + +=head3 Canon ContrastInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 4 IntelligentContrast int16u + +=head3 Canon WBInfo Tags + +WB tags for the Canon G9. + + Index4 Tag Name Writable + ------ -------- -------- + 2 WB_GRBGLevelsAuto int32s[4] + 10 WB_GRBGLevelsDaylight int32s[4] + 18 WB_GRBGLevelsCloudy int32s[4] + 26 WB_GRBGLevelsTungsten int32s[4] + 34 WB_GRBGLevelsFluorescent int32s[4] + 42 WB_GRBGLevelsFluorHigh int32s[4] + 50 WB_GRBGLevelsFlash int32s[4] + 58 WB_GRBGLevelsUnderwater int32s[4] + 66 WB_GRBGLevelsCustom1 int32s[4] + 74 WB_GRBGLevelsCustom2 int32s[4] + +=head3 Canon FaceDetect3 Tags + + Index2 Tag Name Writable + ------ -------- -------- + 3 FacesDetected int16u + +=head3 Canon TimeInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 TimeZone int32s + 2 TimeZoneCity int32s + 3 DaylightSavings int32s + +=head3 Canon FileInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 FileNumber int32u + ShutterCount int32u + 3 BracketMode int16s + 4 BracketValue int16s + 5 BracketShotNumber int16s + 6 RawJpgQuality int16s + 7 RawJpgSize int16s + 8 LongExposureNoiseReduction2 int16s + 9 WBBracketMode int16s + 12 WBBracketValueAB int16s + 13 WBBracketValueGM int16s + 14 FilterEffect int16s + 15 ToningEffect int16s + 16 MacroMagnification int16s + 19 LiveViewShooting int16s + 20 FocusDistanceUpper int16u + 21 FocusDistanceLower int16u + 23 ShutterMode int16s + 25 FlashExposureLock int16s + 61 RFLensType int16u + +=head3 Canon SerialInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 9 InternalSerialNumber string + +=head3 Canon CropInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 CropLeftMargin int16u + 1 CropRightMargin int16u + 2 CropTopMargin int16u + 3 CropBottomMargin int16u + +=head3 Canon AspectInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 AspectRatio int32u + 1 CroppedImageWidth int32u + 2 CroppedImageHeight int32u + 3 CroppedImageLeft int32u + 4 CroppedImageTop int32u + +=head3 Canon Processing Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 ToneCurve int16s + 2 Sharpness int16s + 3 SharpnessFrequency int16s + 4 SensorRedLevel int16s + 5 SensorBlueLevel int16s + 6 WhiteBalanceRed int16s + 7 WhiteBalanceBlue int16s + 8 WhiteBalance int16s + 9 ColorTemperature int16s + 10 PictureStyle int16s + 11 DigitalGain int16s + 12 WBShiftAB int16s + 13 WBShiftGM int16s + +=head3 Canon ColorBalance Tags + +These tags are used by the 10D and 300D. + + Index2 Tag Name Writable + ------ -------- -------- + 1 WB_RGGBLevelsAuto int16s[4] + 5 WB_RGGBLevelsDaylight int16s[4] + 9 WB_RGGBLevelsShade int16s[4] + 13 WB_RGGBLevelsCloudy int16s[4] + 17 WB_RGGBLevelsTungsten int16s[4] + 21 WB_RGGBLevelsFluorescent int16s[4] + 25 WB_RGGBLevelsFlash int16s[4] + 29 WB_RGGBLevelsCustom int16s[4] + BlackLevels int16s[4] + 33 WB_RGGBLevelsKelvin int16s[4] + 37 WB_RGGBBlackLevels int16s[4] + +=head3 Canon MeasuredColor Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 MeasuredRGGB int16u[4] + +=head3 Canon Flags Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 ModifiedParamFlag int16s + +=head3 Canon ModifiedInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 ModifiedToneCurve int16s + 2 ModifiedSharpness int16s + 3 ModifiedSharpnessFreq int16s + 4 ModifiedSensorRedLevel int16s + 5 ModifiedSensorBlueLevel int16s + 6 ModifiedWhiteBalanceRed int16s + 7 ModifiedWhiteBalanceBlue int16s + 8 ModifiedWhiteBalance int16s + 9 ModifiedColorTemp int16s + 10 ModifiedPictureStyle int16s + 11 ModifiedDigitalGain int16s + +=head3 Canon PreviewImageInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 PreviewQuality int32u + 2 PreviewImageLength int32u* + 3 PreviewImageWidth int32u + 4 PreviewImageHeight int32u + 5 PreviewImageStart int32u* + +=head3 Canon SensorInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 SensorWidth no + 2 SensorHeight no + 5 SensorLeftBorder no + 6 SensorTopBorder no + 7 SensorRightBorder no + 8 SensorBottomBorder no + 9 BlackMaskLeftBorder no + 10 BlackMaskTopBorder no + 11 BlackMaskRightBorder no + 12 BlackMaskBottomBorder no + +=head3 Canon ColorData1 Tags + +These tags are used by the 20D and 350D. + + Index2 Tag Name Writable + ------ -------- -------- + 25 WB_RGGBLevelsAsShot int16s[4] + 29 ColorTempAsShot int16s + 30 WB_RGGBLevelsAuto int16s[4] + 34 ColorTempAuto int16s + 35 WB_RGGBLevelsDaylight int16s[4] + 39 ColorTempDaylight int16s + 40 WB_RGGBLevelsShade int16s[4] + 44 ColorTempShade int16s + 45 WB_RGGBLevelsCloudy int16s[4] + 49 ColorTempCloudy int16s + 50 WB_RGGBLevelsTungsten int16s[4] + 54 ColorTempTungsten int16s + 55 WB_RGGBLevelsFluorescent int16s[4] + 59 ColorTempFluorescent int16s + 60 WB_RGGBLevelsFlash int16s[4] + 64 ColorTempFlash int16s + 65 WB_RGGBLevelsCustom1 int16s[4] + 69 ColorTempCustom1 int16s + 70 WB_RGGBLevelsCustom2 int16s[4] + 74 ColorTempCustom2 int16s + 75 ColorCalib? Canon ColorCalib + +=head3 Canon ColorCalib Tags + +Camera color calibration data. For the 20D, 350D, 1DmkII and 1DSmkII the +order of the coefficients is A, B, C, Temperature, but for newer models it +is B, C, A, Temperature. These tags are extracted only when the Unknown +option is used. + + Index2 Tag Name Writable + ------ -------- -------- + 0 CameraColorCalibration01? int16s[4] + 4 CameraColorCalibration02? int16s[4] + 8 CameraColorCalibration03? int16s[4] + 12 CameraColorCalibration04? int16s[4] + 16 CameraColorCalibration05? int16s[4] + 20 CameraColorCalibration06? int16s[4] + 24 CameraColorCalibration07? int16s[4] + 28 CameraColorCalibration08? int16s[4] + 32 CameraColorCalibration09? int16s[4] + 36 CameraColorCalibration10? int16s[4] + 40 CameraColorCalibration11? int16s[4] + 44 CameraColorCalibration12? int16s[4] + 48 CameraColorCalibration13? int16s[4] + 52 CameraColorCalibration14? int16s[4] + 56 CameraColorCalibration15? int16s[4] + +=head3 Canon ColorData2 Tags + +These tags are used by the 1DmkII and 1DSmkII. + + Index2 Tag Name Writable + ------ -------- -------- + 24 WB_RGGBLevelsAuto int16s[4] + 28 ColorTempAuto int16s + 29 WB_RGGBLevelsUnknown? int16s[4] + 33 ColorTempUnknown? int16s + 34 WB_RGGBLevelsAsShot int16s[4] + 38 ColorTempAsShot int16s + 39 WB_RGGBLevelsDaylight int16s[4] + 43 ColorTempDaylight int16s + 44 WB_RGGBLevelsShade int16s[4] + 48 ColorTempShade int16s + 49 WB_RGGBLevelsCloudy int16s[4] + 53 ColorTempCloudy int16s + 54 WB_RGGBLevelsTungsten int16s[4] + 58 ColorTempTungsten int16s + 59 WB_RGGBLevelsFluorescent int16s[4] + 63 ColorTempFluorescent int16s + 64 WB_RGGBLevelsKelvin int16s[4] + 68 ColorTempKelvin int16s + 69 WB_RGGBLevelsFlash int16s[4] + 73 ColorTempFlash int16s + 74 WB_RGGBLevelsUnknown2? int16s[4] + 78 ColorTempUnknown2? int16s + 79 WB_RGGBLevelsUnknown3? int16s[4] + 83 ColorTempUnknown3? int16s + 84 WB_RGGBLevelsUnknown4? int16s[4] + 88 ColorTempUnknown4? int16s + 89 WB_RGGBLevelsUnknown5? int16s[4] + 93 ColorTempUnknown5? int16s + 94 WB_RGGBLevelsUnknown6? int16s[4] + 98 ColorTempUnknown6? int16s + 99 WB_RGGBLevelsUnknown7? int16s[4] + 103 ColorTempUnknown7? int16s + 104 WB_RGGBLevelsUnknown8? int16s[4] + 108 ColorTempUnknown8? int16s + 109 WB_RGGBLevelsUnknown9? int16s[4] + 113 ColorTempUnknown9? int16s + 114 WB_RGGBLevelsUnknown10? int16s[4] + 118 ColorTempUnknown10? int16s + 119 WB_RGGBLevelsUnknown11? int16s[4] + 123 ColorTempUnknown11? int16s + 124 WB_RGGBLevelsUnknown12? int16s[4] + 128 ColorTempUnknown12? int16s + 129 WB_RGGBLevelsUnknown13? int16s[4] + 133 ColorTempUnknown13? int16s + 134 WB_RGGBLevelsUnknown14? int16s[4] + 138 ColorTempUnknown14? int16s + 139 WB_RGGBLevelsUnknown15? int16s[4] + 143 ColorTempUnknown15? int16s + 144 WB_RGGBLevelsPC1 int16s[4] + 148 ColorTempPC1 int16s + 149 WB_RGGBLevelsPC2 int16s[4] + 153 ColorTempPC2 int16s + 154 WB_RGGBLevelsPC3 int16s[4] + 158 ColorTempPC3 int16s + 159 WB_RGGBLevelsUnknown16? int16s[4] + 163 ColorTempUnknown16? int16s + 164 ColorCalib? Canon ColorCalib + 618 RawMeasuredRGGB int32u[4] + +=head3 Canon ColorData3 Tags + +These tags are used by the 1DmkIIN, 5D, 30D and 400D. + + Index2 Tag Name Writable + ------ -------- -------- + 0 ColorDataVersion int16s + 63 WB_RGGBLevelsAsShot int16s[4] + 67 ColorTempAsShot int16s + 68 WB_RGGBLevelsAuto int16s[4] + 72 ColorTempAuto int16s + 73 WB_RGGBLevelsMeasured int16s[4] + 77 ColorTempMeasured int16s + 78 WB_RGGBLevelsDaylight int16s[4] + 82 ColorTempDaylight int16s + 83 WB_RGGBLevelsShade int16s[4] + 87 ColorTempShade int16s + 88 WB_RGGBLevelsCloudy int16s[4] + 92 ColorTempCloudy int16s + 93 WB_RGGBLevelsTungsten int16s[4] + 97 ColorTempTungsten int16s + 98 WB_RGGBLevelsFluorescent int16s[4] + 102 ColorTempFluorescent int16s + 103 WB_RGGBLevelsKelvin int16s[4] + 107 ColorTempKelvin int16s + 108 WB_RGGBLevelsFlash int16s[4] + 112 ColorTempFlash int16s + 113 WB_RGGBLevelsPC1 int16s[4] + 117 ColorTempPC1 int16s + 118 WB_RGGBLevelsPC2 int16s[4] + 122 ColorTempPC2 int16s + 123 WB_RGGBLevelsPC3 int16s[4] + 127 ColorTempPC3 int16s + 128 WB_RGGBLevelsCustom int16s[4] + 132 ColorTempCustom int16s + 133 ColorCalib? Canon ColorCalib + 196 PerChannelBlackLevel int16u[4] + 584 FlashOutput int16s + 585 FlashBatteryLevel int16s + 586 ColorTempFlashData int16s + 647 MeasuredRGGBData int32u[4] + +=head3 Canon ColorData4 Tags + +These tags are used by the 1DmkIII, 1DSmkIII, 1DmkIV, 5DmkII, 7D, 40D, 50D, +60D, 450D, 500D, 550D, 1000D and 1100D. + + Index2 Tag Name Writable + ------ -------- -------- + 0 ColorDataVersion int16s + 63 ColorCoefs Canon ColorCoefs + 168 ColorCalib? Canon ColorCalib + 231 AverageBlackLevel int16u[4] + 640 RawMeasuredRGGB int32u[4] + 692 PerChannelBlackLevel int16u[4] + 696 NormalWhiteLevel int16u + 697 SpecularWhiteLevel int16u + 698 LinearityUpperMargin int16u + 715 PerChannelBlackLevel int16u[4] + 719 NormalWhiteLevel int16u + PerChannelBlackLevel int16u[4] + 720 SpecularWhiteLevel int16u + 721 LinearityUpperMargin int16u + 723 NormalWhiteLevel int16u + 724 SpecularWhiteLevel int16u + 725 LinearityUpperMargin int16u + +=head3 Canon ColorCoefs Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 WB_RGGBLevelsAsShot int16s[4] + 4 ColorTempAsShot int16s + 5 WB_RGGBLevelsAuto int16s[4] + 9 ColorTempAuto int16s + 10 WB_RGGBLevelsMeasured int16s[4] + 14 ColorTempMeasured int16s + 15 WB_RGGBLevelsUnknown? int16s[4] + 19 ColorTempUnknown? int16s + 20 WB_RGGBLevelsDaylight int16s[4] + 24 ColorTempDaylight int16s + 25 WB_RGGBLevelsShade int16s[4] + 29 ColorTempShade int16s + 30 WB_RGGBLevelsCloudy int16s[4] + 34 ColorTempCloudy int16s + 35 WB_RGGBLevelsTungsten int16s[4] + 39 ColorTempTungsten int16s + 40 WB_RGGBLevelsFluorescent int16s[4] + 44 ColorTempFluorescent int16s + 45 WB_RGGBLevelsKelvin int16s[4] + 49 ColorTempKelvin int16s + 50 WB_RGGBLevelsFlash int16s[4] + 54 ColorTempFlash int16s + 55 WB_RGGBLevelsUnknown2? int16s[4] + 59 ColorTempUnknown2? int16s + 60 WB_RGGBLevelsUnknown3? int16s[4] + 64 ColorTempUnknown3? int16s + 65 WB_RGGBLevelsUnknown4? int16s[4] + 69 ColorTempUnknown4? int16s + 70 WB_RGGBLevelsUnknown5? int16s[4] + 74 ColorTempUnknown5? int16s + 75 WB_RGGBLevelsUnknown6? int16s[4] + 79 ColorTempUnknown6? int16s + 80 WB_RGGBLevelsUnknown7? int16s[4] + 84 ColorTempUnknown7? int16s + 85 WB_RGGBLevelsUnknown8? int16s[4] + 89 ColorTempUnknown8? int16s + 90 WB_RGGBLevelsUnknown9? int16s[4] + 94 ColorTempUnknown9? int16s + 95 WB_RGGBLevelsUnknown10? int16s[4] + 99 ColorTempUnknown10? int16s + 100 WB_RGGBLevelsUnknown11? int16s[4] + 104 ColorTempUnknown11? int16s + 105 WB_RGGBLevelsUnknown12? int16s[4] + 109 ColorTempUnknown12? int16s + 110 WB_RGGBLevelsUnknown13? int16s[4] + 114 ColorTempUnknown13? int16s + +=head3 Canon ColorData5 Tags + +These tags are used by many EOS M and PowerShot models. + + Index2 Tag Name Writable + ------ -------- -------- + 0 ColorDataVersion int16s + 71 ColorCoefs Canon ColorCoefs + ColorCoefs2 Canon ColorCoefs2 + 186 ColorCalib2? Canon ColorCalib2 + 255 ColorCalib2? Canon ColorCalib2 + 264 PerChannelBlackLevel int16s[4] + 333 PerChannelBlackLevel int16s[4] + 1385 NormalWhiteLevel int16u + 1386 SpecularWhiteLevel int16u + +=head3 Canon ColorCoefs2 Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 WB_RGGBLevelsAsShot int16s[4] + 7 ColorTempAsShot int16s + 8 WB_RGGBLevelsAuto int16s[4] + 15 ColorTempAuto int16s + 16 WB_RGGBLevelsMeasured int16s[4] + 23 ColorTempMeasured int16s + 24 WB_RGGBLevelsUnknown? int16s[4] + 31 ColorTempUnknown? int16s + 32 WB_RGGBLevelsDaylight int16s[4] + 39 ColorTempDaylight int16s + 40 WB_RGGBLevelsShade int16s[4] + 47 ColorTempShade int16s + 48 WB_RGGBLevelsCloudy int16s[4] + 55 ColorTempCloudy int16s + 56 WB_RGGBLevelsTungsten int16s[4] + 63 ColorTempTungsten int16s + 64 WB_RGGBLevelsFluorescent int16s[4] + 71 ColorTempFluorescent int16s + 72 WB_RGGBLevelsKelvin int16s[4] + 79 ColorTempKelvin int16s + 80 WB_RGGBLevelsFlash int16s[4] + 87 ColorTempFlash int16s + 88 WB_RGGBLevelsUnknown2? int16s[4] + 95 ColorTempUnknown2? int16s + 96 WB_RGGBLevelsUnknown3? int16s[4] + 103 ColorTempUnknown3? int16s + 104 WB_RGGBLevelsUnknown4? int16s[4] + 111 ColorTempUnknown4? int16s + 112 WB_RGGBLevelsUnknown5? int16s[4] + 119 ColorTempUnknown5? int16s + 120 WB_RGGBLevelsUnknown6? int16s[4] + 127 ColorTempUnknown6? int16s + 128 WB_RGGBLevelsUnknown7? int16s[4] + 135 ColorTempUnknown7? int16s + 136 WB_RGGBLevelsUnknown8? int16s[4] + 143 ColorTempUnknown8? int16s + 144 WB_RGGBLevelsUnknown9? int16s[4] + 151 ColorTempUnknown9? int16s + 152 WB_RGGBLevelsUnknown10? int16s[4] + 159 ColorTempUnknown10? int16s + 160 WB_RGGBLevelsUnknown11? int16s[4] + 167 ColorTempUnknown11? int16s + 168 WB_RGGBLevelsUnknown12? int16s[4] + 175 ColorTempUnknown12? int16s + 176 WB_RGGBLevelsUnknown13? int16s[4] + 183 ColorTempUnknown13? int16s + +=head3 Canon ColorCalib2 Tags + +B, C, A, D, Temperature. + + Index2 Tag Name Writable + ------ -------- -------- + 0 CameraColorCalibration01? int16s[5] + 5 CameraColorCalibration02? int16s[5] + 10 CameraColorCalibration03? int16s[5] + 15 CameraColorCalibration04? int16s[5] + 20 CameraColorCalibration05? int16s[5] + 25 CameraColorCalibration06? int16s[5] + 30 CameraColorCalibration07? int16s[5] + 35 CameraColorCalibration08? int16s[5] + 40 CameraColorCalibration09? int16s[5] + 45 CameraColorCalibration10? int16s[5] + 50 CameraColorCalibration11? int16s[5] + 55 CameraColorCalibration12? int16s[5] + 60 CameraColorCalibration13? int16s[5] + 65 CameraColorCalibration14? int16s[5] + 70 CameraColorCalibration15? int16s[5] + +=head3 Canon ColorData6 Tags + +These tags are used by the EOS 600D and 1200D. + + Index2 Tag Name Writable + ------ -------- -------- + 0 ColorDataVersion int16s + 63 WB_RGGBLevelsAsShot int16s[4] + 67 ColorTempAsShot int16s + 68 WB_RGGBLevelsAuto int16s[4] + 72 ColorTempAuto int16s + 73 WB_RGGBLevelsMeasured int16s[4] + 77 ColorTempMeasured int16s + 78 WB_RGGBLevelsUnknown? int16s[4] + 82 ColorTempUnknown? int16s + 83 WB_RGGBLevelsUnknown2? int16s[4] + 87 ColorTempUnknown2? int16s + 88 WB_RGGBLevelsUnknown3? int16s[4] + 92 ColorTempUnknown3? int16s + 93 WB_RGGBLevelsUnknown4? int16s[4] + 97 ColorTempUnknown4? int16s + 98 WB_RGGBLevelsUnknown5? int16s[4] + 102 ColorTempUnknown5? int16s + 103 WB_RGGBLevelsDaylight int16s[4] + 107 ColorTempDaylight int16s + 108 WB_RGGBLevelsShade int16s[4] + 112 ColorTempShade int16s + 113 WB_RGGBLevelsCloudy int16s[4] + 117 ColorTempCloudy int16s + 118 WB_RGGBLevelsTungsten int16s[4] + 122 ColorTempTungsten int16s + 123 WB_RGGBLevelsFluorescent int16s[4] + 127 ColorTempFluorescent int16s + 128 WB_RGGBLevelsKelvin int16s[4] + 132 ColorTempKelvin int16s + 133 WB_RGGBLevelsFlash int16s[4] + 137 ColorTempFlash int16s + 138 WB_RGGBLevelsUnknown6? int16s[4] + 142 ColorTempUnknown6? int16s + 143 WB_RGGBLevelsUnknown7? int16s[4] + 147 ColorTempUnknown7? int16s + 148 WB_RGGBLevelsUnknown8? int16s[4] + 152 ColorTempUnknown8? int16s + 153 WB_RGGBLevelsUnknown9? int16s[4] + 157 ColorTempUnknown9? int16s + 158 WB_RGGBLevelsUnknown10? int16s[4] + 162 ColorTempUnknown10? int16s + 163 WB_RGGBLevelsUnknown11? int16s[4] + 167 ColorTempUnknown11? int16s + 168 WB_RGGBLevelsUnknown12? int16s[4] + 172 ColorTempUnknown12? int16s + 173 WB_RGGBLevelsUnknown13? int16s[4] + 177 ColorTempUnknown13? int16s + 178 WB_RGGBLevelsUnknown14? int16s[4] + 182 ColorTempUnknown14? int16s + 183 WB_RGGBLevelsUnknown15? int16s[4] + 187 ColorTempUnknown15? int16s + 188 ColorCalib? Canon ColorCalib + 251 AverageBlackLevel int16u[4] + 404 RawMeasuredRGGB int32u[4] + 479 PerChannelBlackLevel int16u[4] + 483 NormalWhiteLevel int16u + 484 SpecularWhiteLevel int16u + 485 LinearityUpperMargin int16u + +=head3 Canon ColorData7 Tags + +These tags are used by the EOS 1DX, 5DmkIII, 6D, 7DmkII, 100D, 650D, 700D, +8000D, M and M2. + + Index2 Tag Name Writable + ------ -------- -------- + 0 ColorDataVersion int16s + 63 WB_RGGBLevelsAsShot int16s[4] + 67 ColorTempAsShot int16s + 68 WB_RGGBLevelsAuto int16s[4] + 72 ColorTempAuto int16s + 73 WB_RGGBLevelsMeasured int16s[4] + 77 ColorTempMeasured int16s + 78 WB_RGGBLevelsUnknown? int16s[4] + 82 ColorTempUnknown? int16s + 83 WB_RGGBLevelsUnknown2? int16s[4] + 87 ColorTempUnknown2? int16s + 88 WB_RGGBLevelsUnknown3? int16s[4] + 92 ColorTempUnknown3? int16s + 93 WB_RGGBLevelsUnknown4? int16s[4] + 97 ColorTempUnknown4? int16s + 98 WB_RGGBLevelsUnknown5? int16s[4] + 102 ColorTempUnknown5? int16s + 103 WB_RGGBLevelsUnknown6? int16s[4] + 107 ColorTempUnknown6? int16s + 108 WB_RGGBLevelsUnknown7? int16s[4] + 112 ColorTempUnknown7? int16s + 113 WB_RGGBLevelsUnknown8? int16s[4] + 117 ColorTempUnknown8? int16s + 118 WB_RGGBLevelsUnknown9? int16s[4] + 122 ColorTempUnknown9? int16s + 123 WB_RGGBLevelsUnknown10? int16s[4] + 127 ColorTempUnknown10? int16s + 128 WB_RGGBLevelsDaylight int16s[4] + 132 ColorTempDaylight int16s + 133 WB_RGGBLevelsShade int16s[4] + 137 ColorTempShade int16s + 138 WB_RGGBLevelsCloudy int16s[4] + 142 ColorTempCloudy int16s + 143 WB_RGGBLevelsTungsten int16s[4] + 147 ColorTempTungsten int16s + 148 WB_RGGBLevelsFluorescent int16s[4] + 152 ColorTempFluorescent int16s + 153 WB_RGGBLevelsKelvin int16s[4] + 157 ColorTempKelvin int16s + 158 WB_RGGBLevelsFlash int16s[4] + 162 ColorTempFlash int16s + 163 WB_RGGBLevelsUnknown11? int16s[4] + 167 ColorTempUnknown11? int16s + 168 WB_RGGBLevelsUnknown12? int16s[4] + 172 ColorTempUnknown12? int16s + 173 WB_RGGBLevelsUnknown13? int16s[4] + 177 ColorTempUnknown13? int16s + 178 WB_RGGBLevelsUnknown14? int16s[4] + 182 ColorTempUnknown14? int16s + 183 WB_RGGBLevelsUnknown15? int16s[4] + 187 ColorTempUnknown15? int16s + 188 WB_RGGBLevelsUnknown16? int16s[4] + 192 ColorTempUnknown16? int16s + 193 WB_RGGBLevelsUnknown17? int16s[4] + 197 ColorTempUnknown17? int16s + 198 WB_RGGBLevelsUnknown18? int16s[4] + 202 ColorTempUnknown18? int16s + 203 WB_RGGBLevelsUnknown19? int16s[4] + 207 ColorTempUnknown19? int16s + 208 WB_RGGBLevelsUnknown20? int16s[4] + 212 ColorTempUnknown20? int16s + 213 ColorCalib? Canon ColorCalib + 276 AverageBlackLevel int16u[4] + 429 RawMeasuredRGGB int32u[4] + 504 PerChannelBlackLevel int16u[4] + 508 NormalWhiteLevel int16u + 509 SpecularWhiteLevel int16u + 510 LinearityUpperMargin int16u + 619 RawMeasuredRGGB int32u[4] + 728 PerChannelBlackLevel int16u[4] + 732 NormalWhiteLevel int16u + 733 SpecularWhiteLevel int16u + 734 LinearityUpperMargin int16u + +=head3 Canon ColorData8 Tags + +These tags are used by the EOS 1DXmkII, 5DS, 5DSR, 5DmkIV, 6DmkII, 77D, 80D, +200D, 800D, 1300D, 2000D, 4000D and 9000D. + + Index2 Tag Name Writable + ------ -------- -------- + 0 ColorDataVersion int16s + 63 WB_RGGBLevelsAsShot int16s[4] + 67 ColorTempAsShot int16s + 68 WB_RGGBLevelsAuto int16s[4] + 72 ColorTempAuto int16s + 73 WB_RGGBLevelsMeasured int16s[4] + 77 ColorTempMeasured int16s + 78 WB_RGGBLevelsUnknown? int16s[4] + 82 ColorTempUnknown? int16s + 83 WB_RGGBLevelsUnknown2? int16s[4] + 87 ColorTempUnknown2? int16s + 88 WB_RGGBLevelsUnknown3? int16s[4] + 92 ColorTempUnknown3? int16s + 93 WB_RGGBLevelsUnknown4? int16s[4] + 97 ColorTempUnknown4? int16s + 98 WB_RGGBLevelsUnknown5? int16s[4] + 102 ColorTempUnknown5? int16s + 103 WB_RGGBLevelsUnknown6? int16s[4] + 107 ColorTempUnknown6? int16s + 108 WB_RGGBLevelsUnknown7? int16s[4] + 112 ColorTempUnknown7? int16s + 113 WB_RGGBLevelsUnknown8? int16s[4] + 117 ColorTempUnknown8? int16s + 118 WB_RGGBLevelsUnknown9? int16s[4] + 122 ColorTempUnknown9? int16s + 123 WB_RGGBLevelsUnknown10? int16s[4] + 127 ColorTempUnknown10? int16s + 128 WB_RGGBLevelsUnknown11? int16s[4] + 132 ColorTempUnknown11? int16s + 133 WB_RGGBLevelsDaylight int16s[4] + 137 ColorTempDaylight int16s + 138 WB_RGGBLevelsShade int16s[4] + 142 ColorTempShade int16s + 143 WB_RGGBLevelsCloudy int16s[4] + 147 ColorTempCloudy int16s + 148 WB_RGGBLevelsTungsten int16s[4] + 152 ColorTempTungsten int16s + 153 WB_RGGBLevelsFluorescent int16s[4] + 157 ColorTempFluorescent int16s + 158 WB_RGGBLevelsKelvin int16s[4] + 162 ColorTempKelvin int16s + 163 WB_RGGBLevelsFlash int16s[4] + 167 ColorTempFlash int16s + 168 WB_RGGBLevelsUnknown12? int16s[4] + 172 ColorTempUnknown12? int16s + 173 WB_RGGBLevelsUnknown13? int16s[4] + 177 ColorTempUnknown13? int16s + 178 WB_RGGBLevelsUnknown14? int16s[4] + 182 ColorTempUnknown14? int16s + 183 WB_RGGBLevelsUnknown15? int16s[4] + 187 ColorTempUnknown15? int16s + 188 WB_RGGBLevelsUnknown16? int16s[4] + 192 ColorTempUnknown16? int16s + 193 WB_RGGBLevelsUnknown17? int16s[4] + 197 ColorTempUnknown17? int16s + 198 WB_RGGBLevelsUnknown18? int16s[4] + 202 ColorTempUnknown18? int16s + 203 WB_RGGBLevelsUnknown19? int16s[4] + 207 ColorTempUnknown19? int16s + 208 WB_RGGBLevelsUnknown20? int16s[4] + 212 ColorTempUnknown20? int16s + 213 WB_RGGBLevelsUnknown21? int16s[4] + 217 ColorTempUnknown21? int16s + 218 WB_RGGBLevelsUnknown22? int16s[4] + 222 ColorTempUnknown22? int16s + 223 WB_RGGBLevelsUnknown23? int16s[4] + 227 ColorTempUnknown23? int16s + 228 WB_RGGBLevelsUnknown24? int16s[4] + 232 ColorTempUnknown24? int16s + 233 WB_RGGBLevelsUnknown25? int16s[4] + 237 ColorTempUnknown25? int16s + 238 WB_RGGBLevelsUnknown26? int16s[4] + 242 ColorTempUnknown26? int16s + 243 WB_RGGBLevelsUnknown27? int16s[4] + 247 ColorTempUnknown27? int16s + 248 WB_RGGBLevelsUnknown28? int16s[4] + 252 ColorTempUnknown28? int16s + 253 WB_RGGBLevelsUnknown29? int16s[4] + 257 ColorTempUnknown29? int16s + 258 WB_RGGBLevelsUnknown30? int16s[4] + 262 ColorTempUnknown30? int16s + 263 ColorCalib? Canon ColorCalib + 326 AverageBlackLevel int16u[4] + 556 PerChannelBlackLevel int16u[4] + 560 NormalWhiteLevel int16u + 561 SpecularWhiteLevel int16u + 562 LinearityUpperMargin int16u + 778 PerChannelBlackLevel int16u[4] + 782 NormalWhiteLevel int16u + 783 SpecularWhiteLevel int16u + 784 LinearityUpperMargin int16u + +=head3 Canon ColorData9 Tags + +These tags are used by the M6mkII, M50, M200, EOS R, RP, 90D, 250D and 850D + + Index2 Tag Name Writable + ------ -------- -------- + 0 ColorDataVersion int16s + 71 WB_RGGBLevelsAsShot int16s[4] + 75 ColorTempAsShot int16s + 76 WB_RGGBLevelsAuto int16s[4] + 80 ColorTempAuto int16s + 81 WB_RGGBLevelsMeasured int16s[4] + 85 ColorTempMeasured int16s + 86 WB_RGGBLevelsUnknown? int16s[4] + 90 ColorTempUnknown? int16s + 91 WB_RGGBLevelsUnknown2? int16s[4] + 95 ColorTempUnknown2? int16s + 96 WB_RGGBLevelsUnknown3? int16s[4] + 100 ColorTempUnknown3? int16s + 101 WB_RGGBLevelsUnknown4? int16s[4] + 105 ColorTempUnknown4? int16s + 106 WB_RGGBLevelsUnknown5? int16s[4] + 110 ColorTempUnknown5? int16s + 111 WB_RGGBLevelsUnknown6? int16s[4] + 115 ColorTempUnknown6? int16s + 116 WB_RGGBLevelsUnknown7? int16s[4] + 120 ColorTempUnknown7? int16s + 121 WB_RGGBLevelsUnknown8? int16s[4] + 125 ColorTempUnknown8? int16s + 126 WB_RGGBLevelsUnknown9? int16s[4] + 130 ColorTempUnknown9? int16s + 131 WB_RGGBLevelsUnknown10? int16s[4] + 135 ColorTempUnknown10? int16s + 136 WB_RGGBLevelsDaylight int16s[4] + 140 ColorTempDaylight int16s + 141 WB_RGGBLevelsShade int16s[4] + 145 ColorTempShade int16s + 146 WB_RGGBLevelsCloudy int16s[4] + 150 ColorTempCloudy int16s + 151 WB_RGGBLevelsTungsten int16s[4] + 155 ColorTempTungsten int16s + 156 WB_RGGBLevelsFluorescent int16s[4] + 160 ColorTempFluorescent int16s + 161 WB_RGGBLevelsKelvin int16s[4] + 165 ColorTempKelvin int16s + 166 WB_RGGBLevelsFlash int16s[4] + 170 ColorTempFlash int16s + 171 WB_RGGBLevelsUnknown11? int16s[4] + 175 ColorTempUnknown11? int16s + 176 WB_RGGBLevelsUnknown12? int16s[4] + 180 ColorTempUnknown12? int16s + 181 WB_RGGBLevelsUnknown13? int16s[4] + 185 ColorTempUnknown13? int16s + 186 WB_RGGBLevelsUnknown14? int16s[4] + 190 ColorTempUnknown14? int16s + 191 WB_RGGBLevelsUnknown15? int16s[4] + 195 ColorTempUnknown15? int16s + 196 WB_RGGBLevelsUnknown16? int16s[4] + 200 ColorTempUnknown16? int16s + 201 WB_RGGBLevelsUnknown17? int16s[4] + 205 ColorTempUnknown17? int16s + 206 WB_RGGBLevelsUnknown18? int16s[4] + 210 ColorTempUnknown18? int16s + 211 WB_RGGBLevelsUnknown19? int16s[4] + 215 ColorTempUnknown19? int16s + 216 WB_RGGBLevelsUnknown20? int16s[4] + 220 ColorTempUnknown20? int16s + 221 WB_RGGBLevelsUnknown21? int16s[4] + 225 ColorTempUnknown21? int16s + 226 WB_RGGBLevelsUnknown22? int16s[4] + 230 ColorTempUnknown22? int16s + 231 WB_RGGBLevelsUnknown23? int16s[4] + 235 ColorTempUnknown23? int16s + 236 WB_RGGBLevelsUnknown24? int16s[4] + 240 ColorTempUnknown24? int16s + 241 WB_RGGBLevelsUnknown25? int16s[4] + 245 ColorTempUnknown25? int16s + 246 WB_RGGBLevelsUnknown26? int16s[4] + 250 ColorTempUnknown26? int16s + 251 WB_RGGBLevelsUnknown27? int16s[4] + 255 ColorTempUnknown27? int16s + 256 WB_RGGBLevelsUnknown28? int16s[4] + 260 ColorTempUnknown28? int16s + 261 WB_RGGBLevelsUnknown29? int16s[4] + 265 ColorTempUnknown29? int16s + 266 ColorCalib? Canon ColorCalib + 329 PerChannelBlackLevel int16u[4] + 796 NormalWhiteLevel int16u + 797 SpecularWhiteLevel int16u + 798 LinearityUpperMargin int16u + +=head3 Canon ColorData10 Tags + +These tags are used by the R5, R5 and EOS 1DXmkIII. + + Index2 Tag Name Writable + ------ -------- -------- + 0 ColorDataVersion int16s + 85 WB_RGGBLevelsAsShot int16s[4] + 89 ColorTempAsShot int16s + 90 WB_RGGBLevelsAuto int16s[4] + 94 ColorTempAuto int16s + 95 WB_RGGBLevelsMeasured int16s[4] + 99 ColorTempMeasured int16s + 100 WB_RGGBLevelsUnknown? int16s[4] + 104 ColorTempUnknown? int16s + 105 WB_RGGBLevelsUnknown2? int16s[4] + 109 ColorTempUnknown2? int16s + 110 WB_RGGBLevelsUnknown3? int16s[4] + 114 ColorTempUnknown3? int16s + 115 WB_RGGBLevelsUnknown4? int16s[4] + 119 ColorTempUnknown4? int16s + 120 WB_RGGBLevelsUnknown5? int16s[4] + 124 ColorTempUnknown5? int16s + 125 WB_RGGBLevelsUnknown6? int16s[4] + 129 ColorTempUnknown6? int16s + 130 WB_RGGBLevelsUnknown7? int16s[4] + 134 ColorTempUnknown7? int16s + 135 WB_RGGBLevelsUnknown8? int16s[4] + 139 ColorTempUnknown8? int16s + 140 WB_RGGBLevelsUnknown9? int16s[4] + 144 ColorTempUnknown9? int16s + 145 WB_RGGBLevelsUnknown10? int16s[4] + 149 ColorTempUnknown10? int16s + 150 WB_RGGBLevelsDaylight int16s[4] + 154 ColorTempDaylight int16s + 155 WB_RGGBLevelsShade int16s[4] + 159 ColorTempShade int16s + 160 WB_RGGBLevelsCloudy int16s[4] + 164 ColorTempCloudy int16s + 165 WB_RGGBLevelsTungsten int16s[4] + 169 ColorTempTungsten int16s + 170 WB_RGGBLevelsFluorescent int16s[4] + 174 ColorTempFluorescent int16s + 175 WB_RGGBLevelsKelvin int16s[4] + 179 ColorTempKelvin int16s + 180 WB_RGGBLevelsFlash int16s[4] + 184 ColorTempFlash int16s + 185 WB_RGGBLevelsUnknown11? int16s[4] + 189 ColorTempUnknown11? int16s + 190 WB_RGGBLevelsUnknown12? int16s[4] + 194 ColorTempUnknown12? int16s + 195 WB_RGGBLevelsUnknown13? int16s[4] + 199 ColorTempUnknown13? int16s + 200 WB_RGGBLevelsUnknown14? int16s[4] + 204 ColorTempUnknown14? int16s + 205 WB_RGGBLevelsUnknown15? int16s[4] + 209 ColorTempUnknown15? int16s + 210 WB_RGGBLevelsUnknown16? int16s[4] + 214 ColorTempUnknown16? int16s + 215 WB_RGGBLevelsUnknown17? int16s[4] + 219 ColorTempUnknown17? int16s + 220 WB_RGGBLevelsUnknown18? int16s[4] + 224 ColorTempUnknown18? int16s + 225 WB_RGGBLevelsUnknown19? int16s[4] + 229 ColorTempUnknown19? int16s + 230 WB_RGGBLevelsUnknown20? int16s[4] + 234 ColorTempUnknown20? int16s + 235 WB_RGGBLevelsUnknown21? int16s[4] + 239 ColorTempUnknown21? int16s + 240 WB_RGGBLevelsUnknown22? int16s[4] + 244 ColorTempUnknown22? int16s + 245 WB_RGGBLevelsUnknown23? int16s[4] + 249 ColorTempUnknown23? int16s + 250 WB_RGGBLevelsUnknown24? int16s[4] + 254 ColorTempUnknown24? int16s + 255 WB_RGGBLevelsUnknown25? int16s[4] + 259 ColorTempUnknown25? int16s + 260 WB_RGGBLevelsUnknown26? int16s[4] + 264 ColorTempUnknown26? int16s + 265 WB_RGGBLevelsUnknown27? int16s[4] + 269 ColorTempUnknown27? int16s + 270 WB_RGGBLevelsUnknown28? int16s[4] + 274 ColorTempUnknown28? int16s + 275 WB_RGGBLevelsUnknown29? int16s[4] + 279 ColorTempUnknown29? int16s + 280 ColorCalib? Canon ColorCalib + 343 PerChannelBlackLevel int16u[4] + 810 NormalWhiteLevel int16u + 811 SpecularWhiteLevel int16u + 812 LinearityUpperMargin int16u + +=head3 Canon ColorData11 Tags + +These tags are used by the EOS R3, R7 and R6mkII + + Index2 Tag Name Writable + ------ -------- -------- + 0 ColorDataVersion int16s + 105 WB_RGGBLevelsAsShot int16s[4] + 109 ColorTempAsShot int16s + 110 WB_RGGBLevelsAuto int16s[4] + 114 ColorTempAuto int16s + 115 WB_RGGBLevelsMeasured int16s[4] + 119 ColorTempMeasured int16s + 120 WB_RGGBLevelsUnknown? int16s[4] + 124 ColorTempUnknown? int16s + 125 WB_RGGBLevelsUnknown2? int16s[4] + 129 ColorTempUnknown2? int16s + 130 WB_RGGBLevelsUnknown3? int16s[4] + 134 ColorTempUnknown3? int16s + 135 WB_RGGBLevelsUnknown4? int16s[4] + 139 ColorTempUnknown4? int16s + 140 WB_RGGBLevelsUnknown5? int16s[4] + 144 ColorTempUnknown5? int16s + 145 WB_RGGBLevelsUnknown6? int16s[4] + 149 ColorTempUnknown6? int16s + 150 WB_RGGBLevelsUnknown7? int16s[4] + 154 ColorTempUnknown7? int16s + 155 WB_RGGBLevelsUnknown8? int16s[4] + 159 ColorTempUnknown8? int16s + 160 WB_RGGBLevelsUnknown9? int16s[4] + 164 ColorTempUnknown9? int16s + 165 WB_RGGBLevelsUnknown10? int16s[4] + 169 ColorTempUnknown10? int16s + 170 WB_RGGBLevelsUnknown11? int16s[4] + 174 ColorTempUnknown11? int16s + 175 WB_RGGBLevelsUnknown11? int16s[4] + 179 ColorTempUnknown11? int16s + 180 WB_RGGBLevelsUnknown12? int16s[4] + 184 ColorTempUnknown12? int16s + 185 WB_RGGBLevelsUnknown13? int16s[4] + 189 ColorTempUnknown13? int16s + 190 WB_RGGBLevelsUnknown14? int16s[4] + 194 ColorTempUnknown14? int16s + 195 WB_RGGBLevelsUnknown15? int16s[4] + 199 ColorTempUnknown15? int16s + 200 WB_RGGBLevelsUnknown16? int16s[4] + 204 ColorTempUnknown16? int16s + 205 WB_RGGBLevelsDaylight int16s[4] + 209 ColorTempDaylight int16s + 210 WB_RGGBLevelsShade int16s[4] + 214 ColorTempShade int16s + 215 WB_RGGBLevelsCloudy int16s[4] + 219 ColorTempCloudy int16s + 220 WB_RGGBLevelsTungsten int16s[4] + 224 ColorTempTungsten int16s + 225 WB_RGGBLevelsFluorescent int16s[4] + 229 ColorTempFluorescent int16s + 230 WB_RGGBLevelsKelvin int16s[4] + 234 ColorTempKelvin int16s + 235 WB_RGGBLevelsFlash int16s[4] + 239 ColorTempFlash int16s + 240 WB_RGGBLevelsUnknown17? int16s[4] + 244 ColorTempUnknown17? int16s + 245 WB_RGGBLevelsUnknown18? int16s[4] + 249 ColorTempUnknown18? int16s + 250 WB_RGGBLevelsUnknown19? int16s[4] + 254 ColorTempUnknown19? int16s + 255 WB_RGGBLevelsUnknown20? int16s[4] + 259 ColorTempUnknown20? int16s + 260 WB_RGGBLevelsUnknown21? int16s[4] + 264 ColorTempUnknown21? int16s + 265 WB_RGGBLevelsUnknown22? int16s[4] + 269 ColorTempUnknown22? int16s + 270 WB_RGGBLevelsUnknown23? int16s[4] + 274 ColorTempUnknown23? int16s + 275 WB_RGGBLevelsUnknown24? int16s[4] + 279 ColorTempUnknown24? int16s + 280 WB_RGGBLevelsUnknown25? int16s[4] + 284 ColorTempUnknown25? int16s + 285 WB_RGGBLevelsUnknown26? int16s[4] + 289 ColorTempUnknown26? int16s + 290 WB_RGGBLevelsUnknown27? int16s[4] + 294 ColorTempUnknown27? int16s + 300 ColorCalib? Canon ColorCalib + 363 PerChannelBlackLevel int16u[4] + 640 NormalWhiteLevel int16u + 641 SpecularWhiteLevel int16u + 642 LinearityUpperMargin int16u + +=head3 Canon ColorDataUnknown Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 ColorDataVersion no + +=head3 Canon ColorInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 Saturation int16s + 2 ColorTone int16s + 3 ColorSpace int16s + +=head3 Canon AFMicroAdj Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 AFMicroAdjMode int32s + 2 AFMicroAdjValue rational64s + +=head3 Canon VignettingCorr Tags + +This information is found in images from newer EOS models. + + Index2 Tag Name Writable + ------ -------- -------- + 0 VignettingCorrVersion no + 2 PeripheralLighting int16s + 3 DistortionCorrection int16s + 4 ChromaticAberrationCorr int16s + 5 ChromaticAberrationCorr int16s + 6 PeripheralLightingValue int16s + 9 DistortionCorrectionValue int16s + 11 OriginalImageWidth int16s + 12 OriginalImageHeight int16s + +=head3 Canon VignettingCorrUnknown Tags + +Vignetting correction from PowerShot models. + + Index2 Tag Name Writable + ------ -------- -------- + 0 VignettingCorrVersion no + +=head3 Canon VignettingCorr2 Tags + + Index4 Tag Name Writable + ------ -------- -------- + 5 PeripheralLightingSetting int32s + 6 ChromaticAberrationSetting int32s + 7 DistortionCorrectionSetting int32s + 9 DigitalLensOptimizerSetting int32s + +=head3 Canon LightingOpt Tags + +This information is new in images from the EOS 7D. + + Index4 Tag Name Writable + ------ -------- -------- + 1 PeripheralIlluminationCorr int32s + 2 AutoLightingOptimizer int32s + 3 HighlightTonePriority int32s + 4 LongExposureNoiseReduction int32s + 5 HighISONoiseReduction int32s + 10 DigitalLensOptimizer int32s + +=head3 Canon LensInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensSerialNumber undef[5] + +=head3 Canon Ambience Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 AmbienceSelection int32s + +=head3 Canon MultiExp Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 MultiExposure int32s + 2 MultiExposureControl int32s + 3 MultiExposureShots int32s + +=head3 Canon FilterInfo Tags + +Information about creative filter settings. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0101 GrainyBWFilter no + 0x0201 SoftFocusFilter no + 0x0301 ToyCameraFilter no + 0x0401 MiniatureFilter no + 0x0402 MiniatureFilterOrientation no + 0x0403 MiniatureFilterPosition no + 0x0404 MiniatureFilterParameter no + 0x0501 FisheyeFilter no + 0x0601 PaintingFilter no + 0x0701 WatercolorFilter no + +=head3 Canon HDRInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 HDR int32s + 2 HDREffect int32s + +=head3 Canon LogInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 4 CompressionFormat int32s + 6 Sharpness int32s + 7 Saturation int32s + 8 ColorTone int32s + 9 ColorSpace2 int32s + 10 ColorMatrix int32s + 11 CanonLogVersion int32s + +=head3 Canon AFConfig Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 AFConfigTool int32s + 2 AFTrackingSensitivity int32s + 3 AFAccelDecelTracking int32s + 4 AFPointSwitching int32s + 5 AIServoFirstImage int32s + 6 AIServoSecondImage int32s + 7 USMLensElectronicMF int32s + 8 AFAssistBeam int32s + 9 OneShotAFRelease int32s + 10 AutoAFPointSelEOSiTRAF int32s + 11 LensDriveWhenAFImpossible int32s + 12 SelectAFAreaSelectionMode int32s + 13 AFAreaSelectionMethod int32s + 14 OrientationLinkedAF int32s + 15 ManualAFPointSelPattern int32s + 16 AFPointDisplayDuringFocus int32s + 17 VFDisplayIllumination int32s + 18 AFStatusViewfinder int32s + 19 InitialAFPointInServo int32s + +=head3 Canon RawBurstInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 RawBurstImageNum int32u + 2 RawBurstImageCount int32u + +=head3 Canon CTMD Tags + +Canon Timed MetaData tags found in CR3 images. The ExtractEmbedded option +is automatically applied when reading CR3 files to be able to extract this +information. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 TimeStamp no + 0x0004 FocalInfo Canon FocalInfo + 0x0005 ExposureInfo Canon ExposureInfo + 0x0007 ExifInfo7 Canon ExifInfo + 0x0008 ExifInfo8 Canon ExifInfo + 0x0009 ExifInfo9 Canon ExifInfo + +=head3 Canon FocalInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 FocalLength no + +=head3 Canon ExposureInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 FNumber no + 1 ExposureTime no + 2 ISO no + +=head3 Canon ExifInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x8769 ExifIFD EXIF + 0x927c MakerNoteCanon Canon + +=head3 Canon CDI1 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'IAD1' IAD1 Canon IAD1 + +=head3 Canon IAD1 Tags + + Index2 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Canon CMP1 Tags + + Index2 Tag Name Writable + ------ -------- -------- + 8 ImageWidth no + 10 ImageHeight no + +=head3 Canon CNOP Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Canon CNTH Tags + +Canon-specific QuickTime tags found in the CNTH atom of MOV/MP4 videos from +some cameras. + + Tag ID Tag Name Writable + ------ -------- -------- + 'CNDA' ThumbnailImage undef + +=head3 Canon uuid Tags + +Tags extracted from the uuid atom of MP4 videos from cameras such as the +SX280, and CR3 images from cameras such as the EOS M50. + + Tag ID Tag Name Writable + ------ -------- -------- + 'CCTP' CanonCCTP Canon CCTP + 'CMT1' IFD0 EXIF + 'CMT2' ExifIFD EXIF + 'CMT3' MakerNoteCanon Canon + 'CMT4' GPSInfo GPS + 'CNCV' CompressorVersion no + 'CNOP' CanonCNOP Canon CNOP + 'CNTH' CanonCNTH Canon CNTH + 'THMB' ThumbnailImage no + +=head3 Canon CCTP Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Canon Skip Tags + +Information found in the "skip" atom of Canon MOV videos. + + Tag ID Tag Name Writable + ------ -------- -------- + 'CNDB' Unknown_CNDB? no + +=head3 Canon uuid2 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'CNOP' CanonVRD CanonVRD + +=head2 CanonCustom Tags + +=head3 CanonCustom Functions1D Tags + +These custom functions are used by all 1D models up to but not including the +Mark III. + + Tag ID Tag Name Writable + ------ -------- -------- + 0 FocusingScreen int8u + 1 FinderDisplayDuringExposure int8u + 2 ShutterReleaseNoCFCard int8u + 3 ISOSpeedExpansion int8u + 4 ShutterAELButton int8u + 5 ManualTv int8u + 6 ExposureLevelIncrements int8u + 7 USMLensElectronicMF int8u + 8 LCDPanels int8u + 9 AEBSequenceAutoCancel int8u + 10 AFPointIllumination int8u + 11 AFPointSelection int8u + 12 MirrorLockup int8u + 13 AFPointSpotMetering int8u + 14 FillFlashAutoReduction int8u + 15 ShutterCurtainSync int8u + 16 SafetyShiftInAvOrTv int8u + 17 AFPointActivationArea int8u + 18 SwitchToRegisteredAFPoint int8u + 19 LensAFStopButton int8u + 20 AIServoTrackingSensitivity int8u + 21 AIServoContinuousShooting int8u + +=head3 CanonCustom Functions5D Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0 FocusingScreen int8u + 1 SetFunctionWhenShooting int8u + 2 LongExposureNoiseReduction int8u + 3 FlashSyncSpeedAv int8u + 4 Shutter-AELock int8u + 5 AFAssistBeam int8u + 6 ExposureLevelIncrements int8u + 7 FlashFiring int8u + 8 ISOExpansion int8u + 9 AEBSequenceAutoCancel int8u + 10 SuperimposedDisplay int8u + 11 MenuButtonDisplayPosition int8u + 12 MirrorLockup int8u + 13 AFPointSelectionMethod int8u + 14 ETTLII int8u + 15 ShutterCurtainSync int8u + 16 SafetyShiftInAvOrTv int8u + 17 AFPointActivationArea int8u + 18 LCDDisplayReturnToShoot int8u + 19 LensAFStopButton int8u + 20 AddOriginalDecisionData int8u + +=head3 CanonCustom Functions10D Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 1 SetButtonWhenShooting int8u + 2 ShutterReleaseNoCFCard int8u + 3 FlashSyncSpeedAv int8u + 4 Shutter-AELock int8u + 5 AFAssist int8u + 6 ExposureLevelIncrements int8u + 7 AFPointRegistration int8u + 8 RawAndJpgRecording int8u + 9 AEBSequenceAutoCancel int8u + 10 SuperimposedDisplay int8u + 11 MenuButtonDisplayPosition int8u + 12 MirrorLockup int8u + 13 AssistButtonFunction int8u + 14 FillFlashAutoReduction int8u + 15 ShutterCurtainSync int8u + 16 SafetyShiftInAvOrTv int8u + 17 LensAFStopButton int8u + +=head3 CanonCustom Functions20D Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0 SetFunctionWhenShooting int8u + 1 LongExposureNoiseReduction int8u + 2 FlashSyncSpeedAv int8u + 3 Shutter-AELock int8u + 4 AFAssistBeam int8u + 5 ExposureLevelIncrements int8u + 6 FlashFiring int8u + 7 ISOExpansion int8u + 8 AEBSequenceAutoCancel int8u + 9 SuperimposedDisplay int8u + 10 MenuButtonDisplayPosition int8u + 11 MirrorLockup int8u + 12 AFPointSelectionMethod int8u + 13 ETTLII int8u + 14 ShutterCurtainSync int8u + 15 SafetyShiftInAvOrTv int8u + 16 LensAFStopButton int8u + 17 AddOriginalDecisionData int8u + +=head3 CanonCustom Functions30D Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 1 SetFunctionWhenShooting int8u + 2 LongExposureNoiseReduction int8u + 3 FlashSyncSpeedAv int8u + 4 Shutter-AELock int8u + 5 AFAssistBeam int8u + 6 ExposureLevelIncrements int8u + 7 FlashFiring int8u + 8 ISOExpansion int8u + 9 AEBSequenceAutoCancel int8u + 10 SuperimposedDisplay int8u + 11 MenuButtonDisplayPosition int8u + 12 MirrorLockup int8u + 13 AFPointSelectionMethod int8u + 14 ETTLII int8u + 15 ShutterCurtainSync int8u + 16 SafetyShiftInAvOrTv int8u + 17 MagnifiedView int8u + 18 LensAFStopButton int8u + 19 AddOriginalDecisionData int8u + +=head3 CanonCustom Functions350D Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0 SetButtonCrossKeysFunc int8u + 1 LongExposureNoiseReduction int8u + 2 FlashSyncSpeedAv int8u + 3 Shutter-AELock int8u + 4 AFAssistBeam int8u + 5 ExposureLevelIncrements int8u + 6 MirrorLockup int8u + 7 ETTLII int8u + 8 ShutterCurtainSync int8u + +=head3 CanonCustom Functions400D Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0 SetButtonCrossKeysFunc int8u + 1 LongExposureNoiseReduction int8u + 2 FlashSyncSpeedAv int8u + 3 Shutter-AELock int8u + 4 AFAssistBeam int8u + 5 ExposureLevelIncrements int8u + 6 MirrorLockup int8u + 7 ETTLII int8u + 8 ShutterCurtainSync int8u + 9 MagnifiedView int8u + 10 LCDDisplayAtPowerOn int8u + +=head3 CanonCustom FunctionsD30 Tags + +Custom functions for the EOS D30 and D60. + + Tag ID Tag Name Writable + ------ -------- -------- + 1 LongExposureNoiseReduction int8u + 2 Shutter-AELock int8u + 3 MirrorLockup int8u + 4 ExposureLevelIncrements int8u + 5 AFAssist int8u + 6 FlashSyncSpeedAv int8u + 7 AEBSequenceAutoCancel int8u + 8 ShutterCurtainSync int8u + 9 LensAFStopButton int8u + 10 FillFlashAutoReduction int8u + 11 MenuButtonReturn int8u + 12 SetButtonWhenShooting int8u + 13 SensorCleaning int8u + 14 SuperimposedDisplay int8u + 15 ShutterReleaseNoCFCard int8u + +=head3 CanonCustom FuncsUnknown Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 CanonCustom PersonalFuncs Tags + +Personal function settings for the EOS-1D. + + Index2 Tag Name Writable + ------ -------- -------- + 1 PF0CustomFuncRegistration int16u + 2 PF1DisableShootingModes int16u + 3 PF2DisableMeteringModes int16u + 4 PF3ManualExposureMetering int16u + 5 PF4ExposureTimeLimits int16u + 6 PF5ApertureLimits int16u + 7 PF6PresetShootingModes int16u + 8 PF7BracketContinuousShoot int16u + 9 PF8SetBracketShots int16u + 10 PF9ChangeBracketSequence int16u + 11 PF10RetainProgramShift int16u + 14 PF13DrivePriority int16u + 15 PF14DisableFocusSearch int16u + 16 PF15DisableAFAssistBeam int16u + 17 PF16AutoFocusPointShoot int16u + 18 PF17DisableAFPointSel int16u + 19 PF18EnableAutoAFPointSel int16u + 20 PF19ContinuousShootSpeed int16u + 21 PF20LimitContinousShots int16u + 22 PF21EnableQuietOperation int16u + 24 PF23SetTimerLengths int16u + 25 PF24LightLCDDuringBulb int16u + 26 PF25DefaultClearSettings int16u + 27 PF26ShortenReleaseLag int16u + 28 PF27ReverseDialRotation int16u + 29 PF28NoQuickDialExpComp int16u + 30 PF29QuickDialSwitchOff int16u + 31 PF30EnlargementMode int16u + 32 PF31OriginalDecisionData int16u + +=head3 CanonCustom PersonalFuncValues Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 PF1Value int16u + 2 PF2Value int16u + 3 PF3Value int16u + 4 PF4ExposureTimeMin int16u + 5 PF4ExposureTimeMax int16u + 6 PF5ApertureMin int16u + 7 PF5ApertureMax int16u + 8 PF8BracketShots int16u + 9 PF19ShootingSpeedLow int16u + 10 PF19ShootingSpeedHigh int16u + 11 PF20MaxContinousShots int16u + 12 PF23ShutterButtonTime int16u + 13 PF23FELockTime int16u + 14 PF23PostReleaseTime int16u + 15 PF25AEMode int16u + 16 PF25MeteringMode int16u + 17 PF25DriveMode int16u + 18 PF25AFMode int16u + 19 PF25AFPointSel int16u + 20 PF25ImageSize int16u + 21 PF25WBMode int16u + 22 PF25Parameters int16u + 23 PF25ColorMatrix int16u + 24 PF27Value int16u + +=head3 CanonCustom Functions2 Tags + +Beginning with the EOS 1D Mark III, Canon finally created a set of custom +function tags which are (reasonably) consistent across models. The EOS 1D +Mark III has 57 custom function tags divided into four main groups: 1. +Exposure (0x0101-0x010f), 2. Image (0x0201-0x0203), Flash Exposure +(0x0304-0x0306) and Display (0x0407-0x0409), 3. Auto Focus (0x0501-0x050e) +and Drive (0x060f-0x0611), and 4. Operation (0x0701-0x070a) and Others +(0x080b-0x0810). The table below lists tags used by the EOS 1D Mark III, as +well as newer tags and values added by later models. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0101 ExposureLevelIncrements int32s + 0x0102 ISOSpeedIncrements int32s + 0x0103 ISOSpeedRange int32s[3] + ISOExpansion int32s + 0x0104 AEBAutoCancel int32s + 0x0105 AEBSequence int32s + 0x0106 AEBShotCount int32s + AEBShotCount int32s[2] + 0x0107 SpotMeterLinkToAFPoint int32s + 0x0108 SafetyShift int32s + 0x0109 UsableShootingModes int32s + UsableShootingModes int32s[2] + 0x010a UsableMeteringModes int32s + UsableMeteringModes int32s[2] + 0x010b ExposureModeInManual int32s + 0x010c ShutterSpeedRange int32s[3] + ShutterSpeedRange int32s[4] + 0x010d ApertureRange int32s[3] + ApertureRange int32s[4] + 0x010e ApplyShootingMeteringMode int32s[8] + 0x010f FlashSyncSpeedAv int32s + 0x0110 AEMicroadjustment int32s[3] + 0x0111 FEMicroadjustment int32s[3] + 0x0112 SameExposureForNewAperture int32s + 0x0113 ExposureCompAutoCancel int32s + 0x0114 AELockMeterModeAfterFocus int32s + 0x0201 LongExposureNoiseReduction int32s + 0x0202 HighISONoiseReduction int32s + 0x0203 HighlightTonePriority int32s + 0x0204 AutoLightingOptimizer int32s + 0x0304 ETTLII int32s + 0x0305 ShutterCurtainSync int32s + 0x0306 FlashFiring int32s + 0x0407 ViewInfoDuringExposure int32s + 0x0408 LCDIlluminationDuringBulb int32s + 0x0409 InfoButtonWhenShooting int32s + 0x040a ViewfinderWarnings int32s + 0x040b LVShootingAreaDisplay int32s + 0x040c LVShootingAreaDisplay int32s + 0x0501 USMLensElectronicMF int32s + 0x0502 AIServoTrackingSensitivity int32s + 0x0503 AIServoImagePriority int32s + 0x0504 AIServoTrackingMethod int32s + 0x0505 LensDriveNoAF int32s + 0x0506 LensAFStopButton int32s + 0x0507 AFMicroadjustment int32s[5] + 0x0508 AFPointAreaExpansion int32s + 0x0509 SelectableAFPoint int32s + 0x050a SwitchToRegisteredAFPoint int32s + 0x050b AFPointAutoSelection int32s + 0x050c AFPointDisplayDuringFocus int32s + 0x050d AFPointBrightness int32s + 0x050e AFAssistBeam int32s + 0x050f AFPointSelectionMethod int32s + 0x0510 VFDisplayIllumination int32s + SuperimposedDisplay int32s + 0x0511 AFDuringLiveView int32s + 0x0512 SelectAFAreaSelectMode int32s + 0x0513 ManualAFPointSelectPattern int32s + 0x0514 DisplayAllAFPoints int32s + 0x0515 FocusDisplayAIServoAndMF int32s + 0x0516 OrientationLinkedAFPoint int32s + 0x0517 MultiControllerWhileMetering int32s + 0x0518 AccelerationTracking int32s + 0x0519 AIServoFirstImagePriority int32s + 0x051a AIServoSecondImagePriority int32s + 0x051b AFAreaSelectMethod int32s + 0x051c AutoAFPointColorTracking int32s + 0x051d VFDisplayIllumination int32s + 0x051e InitialAFPointAIServoAF int32s + 0x060f MirrorLockup int32s + 0x0610 ContinuousShootingSpeed int32s[6] + ContinuousShootingSpeed int32s[5] + ContinuousShootingSpeed int32s[3] + 0x0611 ContinuousShotLimit int32s[2] + 0x0612 RestrictDriveModes int32s + RestrictDriveModes int32s[2] + 0x0701 Shutter-AELock int32s + AFAndMeteringButtons int32s + ShutterButtonAFOnButton int32s + 0x0702 AFOnAELockButtonSwitch int32s + 0x0703 QuickControlDialInMeter int32s + 0x0704 SetButtonWhenShooting int32s + 0x0705 ManualTv int32s + 0x0706 DialDirectionTvAv int32s + 0x0707 AvSettingWithoutLens int32s + 0x0708 WBMediaImageSizeSetting int32s + 0x0709 LockMicrophoneButton int32s + 0x070a ButtonFunctionControlOff int32s + 0x070b AssignFuncButton int32s + 0x070c CustomControls int32s + 0x070d StartMovieShooting int32s + 0x070e FlashButtonFunction int32s + 0x070f MultiFunctionLock int32s + 0x0710 TrashButtonFunction int32s + 0x0711 ShutterReleaseWithoutLens int32s + 0x0712 ControlRingRotation int32s + 0x0713 FocusRingRotation int32s + 0x0714 RFLensMFFocusRingSensitivity int32s + 0x0715 CustomizeDials int32s + 0x080b FocusingScreen int32s + 0x080c TimerLength int32s[3] + TimerLength int32s[4] + 0x080d ShortReleaseTimeLag int32s + 0x080e AddAspectRatioInfo int32s + 0x080f AddOriginalDecisionData int32s + 0x0810 LiveViewExposureSimulation int32s + 0x0811 LCDDisplayAtPowerOn int32s + 0x0812 MemoAudioQuality int32s + 0x0813 DefaultEraseOption int32s + 0x0814 RetractLensOnPowerOff int32s + 0x0815 AddIPTCInformation int32s + 0x0816 AudioCompression int32s + +=head2 CanonVRD Tags + +Canon Digital Photo Professional writes VRD (Recipe Data) information as a +trailer record to JPEG, TIFF, CRW and CR2 images, or as stand-alone VRD or +DR4 files. The tags listed below represent information found in these +records. The complete VRD/DR4 data record may be accessed as a block using +the Extra 'CanonVRD' or 'CanonDR4' tag, but this tag is not extracted or +copied unless specified explicitly. + + Tag ID Tag Name Writable + ------ -------- -------- + 0xffff00f4 EditData CanonVRD Edit + 0xffff00f5 IHLData CanonVRD IHL + 0xffff00f6 XMP XMP + 0xffff00f7 Edit4Data CanonVRD Edit4 + +=head3 CanonVRD Edit Tags + +Canon VRD edit information. + + Index Tag Name Writable + ----- -------- -------- + 0 VRD1 CanonVRD Ver1 + 1 VRDStampTool CanonVRD StampTool + 2 VRD2 CanonVRD Ver2 + +=head3 CanonVRD Ver1 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 2 VRDVersion no + 6 WBAdjRGGBLevels int16u[4] + 24 WhiteBalanceAdj int16u + 26 WBAdjColorTemp int16u + 36 WBFineTuneActive int16u + 40 WBFineTuneSaturation int16u + 44 WBFineTuneTone int16u + 46 RawColorAdj int16u + 48 RawCustomSaturation int32s + 52 RawCustomTone int32s + 56 RawBrightnessAdj int32s + 60 ToneCurveProperty int16u + 122 DynamicRangeMin int16u + 124 DynamicRangeMax int16u + 272 ToneCurveActive int16u + 275 ToneCurveMode int8u + 276 BrightnessAdj int8s + 277 ContrastAdj int8s + 278 SaturationAdj int16s + 286 ColorToneAdj int32s + 294 LuminanceCurvePoints int16u[21] + 336 LuminanceCurveLimits int16u[4] + 345 ToneCurveInterpolation int8u + 352 RedCurvePoints int16u[21] + 394 RedCurveLimits int16u[4] + 410 GreenCurvePoints int16u[21] + 452 GreenCurveLimits int16u[4] + 468 BlueCurvePoints int16u[21] + 510 BlueCurveLimits int16u[4] + 526 RGBCurvePoints int16u[21] + 568 RGBCurveLimits int16u[4] + 580 CropActive int16u + 582 CropLeft int16u + 584 CropTop int16u + 586 CropWidth int16u + 588 CropHeight int16u + 602 SharpnessAdj int16u + 608 CropAspectRatio int16u + 610 ConstrainedCropWidth float + 614 ConstrainedCropHeight float + 618 CheckMark int16u + 622 Rotation int16u + 624 WorkColorSpace int16u + +=head3 CanonVRD StampTool Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 StampToolCount no + +=head3 CanonVRD Ver2 Tags + +Tags added in DPP version 2.0 and later. + + Index2 Tag Name Writable + ------ -------- -------- + 2 PictureStyle int16s + 3 IsCustomPictureStyle int16s + 13 StandardRawColorTone int16s + 14 StandardRawSaturation int16s + 15 StandardRawContrast int16s + 16 StandardRawLinear int16s + 17 StandardRawSharpness int16s + 18 StandardRawHighlightPoint int16s + 19 StandardRawShadowPoint int16s + 20 StandardOutputHighlightPoint int16s + 21 StandardOutputShadowPoint int16s + 22 PortraitRawColorTone int16s + 23 PortraitRawSaturation int16s + 24 PortraitRawContrast int16s + 25 PortraitRawLinear int16s + 26 PortraitRawSharpness int16s + 27 PortraitRawHighlightPoint int16s + 28 PortraitRawShadowPoint int16s + 29 PortraitOutputHighlightPoint int16s + 30 PortraitOutputShadowPoint int16s + 31 LandscapeRawColorTone int16s + 32 LandscapeRawSaturation int16s + 33 LandscapeRawContrast int16s + 34 LandscapeRawLinear int16s + 35 LandscapeRawSharpness int16s + 36 LandscapeRawHighlightPoint int16s + 37 LandscapeRawShadowPoint int16s + 38 LandscapeOutputHighlightPoint int16s + 39 LandscapeOutputShadowPoint int16s + 40 NeutralRawColorTone int16s + 41 NeutralRawSaturation int16s + 42 NeutralRawContrast int16s + 43 NeutralRawLinear int16s + 44 NeutralRawSharpness int16s + 45 NeutralRawHighlightPoint int16s + 46 NeutralRawShadowPoint int16s + 47 NeutralOutputHighlightPoint int16s + 48 NeutralOutputShadowPoint int16s + 49 FaithfulRawColorTone int16s + 50 FaithfulRawSaturation int16s + 51 FaithfulRawContrast int16s + 52 FaithfulRawLinear int16s + 53 FaithfulRawSharpness int16s + 54 FaithfulRawHighlightPoint int16s + 55 FaithfulRawShadowPoint int16s + 56 FaithfulOutputHighlightPoint int16s + 57 FaithfulOutputShadowPoint int16s + 58 MonochromeFilterEffect int16s + 59 MonochromeToningEffect int16s + 60 MonochromeContrast int16s + 61 MonochromeLinear int16s + 62 MonochromeSharpness int16s + 63 MonochromeRawHighlightPoint int16s + 64 MonochromeRawShadowPoint int16s + 65 MonochromeOutputHighlightPoint int16s + 66 MonochromeOutputShadowPoint int16s + 69 UnknownContrast? int16s + 70 UnknownLinear? int16s + 71 UnknownSharpness? int16s + 72 UnknownRawHighlightPoint? int16s + 73 UnknownRawShadowPoint? int16s + 74 UnknownOutputHighlightPoint? int16s + 75 UnknownOutputShadowPoint? int16s + 76 CustomColorTone int16s + 77 CustomSaturation int16s + 78 CustomContrast int16s + 79 CustomLinear int16s + 80 CustomSharpness int16s + 81 CustomRawHighlightPoint int16s + 82 CustomRawShadowPoint int16s + 83 CustomOutputHighlightPoint int16s + 84 CustomOutputShadowPoint int16s + 88 CustomPictureStyleData no + 94 ChrominanceNoiseReduction int16s + 95 LuminanceNoiseReduction int16s + 96 ChrominanceNR_TIFF_JPEG int16s + 98 ChromaticAberrationOn int16s + 99 DistortionCorrectionOn int16s + 100 PeripheralIlluminationOn int16s + 101 ColorBlur int16s + 102 ChromaticAberration int16s + 103 DistortionCorrection int16s + 104 PeripheralIllumination int16s + 105 AberrationCorrectionDistance int16s + 106 ChromaticAberrationRed int16s + 107 ChromaticAberrationBlue int16s + 109 LuminanceNR_TIFF_JPEG int16s + 110 AutoLightingOptimizerOn int16s + 111 AutoLightingOptimizer int16s + 117 StandardRawHighlight int16s + 118 PortraitRawHighlight int16s + 119 LandscapeRawHighlight int16s + 120 NeutralRawHighlight int16s + 121 FaithfulRawHighlight int16s + 122 MonochromeRawHighlight int16s + 123 UnknownRawHighlight? int16s + 124 CustomRawHighlight int16s + 126 StandardRawShadow int16s + 127 PortraitRawShadow int16s + 128 LandscapeRawShadow int16s + 129 NeutralRawShadow int16s + 130 FaithfulRawShadow int16s + 131 MonochromeRawShadow int16s + 132 UnknownRawShadow? int16s + 133 CustomRawShadow int16s + 139 AngleAdj int32s + 142 CheckMark2 int16u + 144 UnsharpMask int16s + 146 StandardUnsharpMaskStrength int16s + 148 StandardUnsharpMaskFineness int16s + 150 StandardUnsharpMaskThreshold int16s + 152 PortraitUnsharpMaskStrength int16s + 154 PortraitUnsharpMaskFineness int16s + 156 PortraitUnsharpMaskThreshold int16s + 158 LandscapeUnsharpMaskStrength int16s + 160 LandscapeUnsharpMaskFineness int16s + 162 LandscapeUnsharpMaskThreshold int16s + 164 NeutraUnsharpMaskStrength int16s + 166 NeutralUnsharpMaskFineness int16s + 168 NeutralUnsharpMaskThreshold int16s + 170 FaithfulUnsharpMaskStrength int16s + 172 FaithfulUnsharpMaskFineness int16s + 174 FaithfulUnsharpMaskThreshold int16s + 176 MonochromeUnsharpMaskStrength int16s + 178 MonochromeUnsharpMaskFineness int16s + 180 MonochromeUnsharpMaskThreshold int16s + 182 CustomUnsharpMaskStrength int16s + 184 CustomUnsharpMaskFineness int16s + 186 CustomUnsharpMaskThreshold int16s + 188 CustomDefaultUnsharpStrength int16s + 190 CustomDefaultUnsharpFineness int16s + 192 CustomDefaultUnsharpThreshold int16s + 214 CropCircleActive int16s + 215 CropCircleX int16s + 216 CropCircleY int16s + 217 CropCircleRadius int16s + 220 DLOOn int16s + 221 DLOSetting int16s + 222 DLOShootingDistance int16s + 223 DLODataLength no + 224 DLOInfo CanonVRD DLOInfo + 225 CameraRawColorTone int16s + 226 CameraRawSaturation int16s + 227 CameraRawContrast int16s + 228 CameraRawLinear int16s + 229 CameraRawSharpness int16s + 230 CameraRawHighlightPoint int16s + 231 CameraRawShadowPoint int16s + 232 CameraRawOutputHighlightPoint int16s + 233 CameraRawOutputShadowPoint int16s + +=head3 CanonVRD DLOInfo Tags + +Tags added when DLO (Digital Lens Optimizer) is on. + + Index2 Tag Name Writable + ------ -------- -------- + 4 DLOSettingApplied int16s + 5 DLOVersion string[10] + 10 DLOData no + +=head3 CanonVRD IHL Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 IHL_EXIF EXIF + IHL_EXIF? no + 0x0003 ThumbnailImage no + 0x0004 PreviewImage no + 0x0005 RawCodecVersion no + 0x0006 CRCDevelParams? no + +=head3 CanonVRD Edit4 Tags + +Canon DPP version 4 edit information. + + Index Tag Name Writable + ----- -------- -------- + 0 DR4 CanonVRD DR4 + +=head3 CanonVRD DR4 Tags + +Tags written by Canon DPP version 4 in CanonVRD trailers and DR4 files. Each +tag has three associated flag words which are stored with the directory +entry, some of which are extracted as a separate tag, indicated in the table +below by a decimal appended to the tag ID (.0, .1 or .2). + + Tag ID Tag Name Writable + ------ -------- -------- + 'header' DR4Header CanonVRD DR4Header + 0x10002 Rotation yes + 0x10003 AngleAdj yes + 0x10021 CustomPictureStyle yes + 0x10101 CheckMark yes + 0x10200 WorkColorSpace yes + 0x20001 RawBrightnessAdj yes + 0x20101 WhiteBalanceAdj yes + 0x20102 WBAdjColorTemp yes + 0x20105 WBAdjMagentaGreen yes + 0x20106 WBAdjBlueAmber yes + 0x20125 WBAdjRGGBLevels yes + 0x20200 GammaLinear yes + 0x20301 PictureStyle yes + 0x20303 ContrastAdj yes + 0x20304 ColorToneAdj yes + 0x20305 ColorSaturationAdj yes + 0x20306 MonochromeToningEffect yes + 0x20307 MonochromeFilterEffect yes + 0x20308 UnsharpMaskStrength yes + 0x20309 UnsharpMaskFineness yes + 0x2030a UnsharpMaskThreshold yes + 0x2030b ShadowAdj yes + 0x2030c HighlightAdj yes + 0x20310 SharpnessAdj yes + 0x20310.0 SharpnessAdjOn yes + 0x20311 SharpnessStrength yes + 0x20400 ToneCurve CanonVRD ToneCurve + 0x20400.1 ToneCurveOriginal yes + 0x20410 ToneCurveBrightness yes + 0x20411 ToneCurveContrast yes + 0x20500 AutoLightingOptimizer yes + 0x20500.0 AutoLightingOptimizerOn yes + 0x20600 LuminanceNoiseReduction yes + 0x20601 ChrominanceNoiseReduction yes + 0x20670 ColorMoireReduction yes + 0x20670.0 ColorMoireReductionOn yes + 0x20701 ShootingDistance yes + 0x20702 PeripheralIllumination yes + 0x20702.0 PeripheralIlluminationOn yes + 0x20703 ChromaticAberration yes + 0x20703.0 ChromaticAberrationOn yes + 0x20704 ColorBlurOn yes + 0x20705 DistortionCorrection yes + 0x20705.0 DistortionCorrectionOn yes + 0x20706 DLOSetting yes + 0x20706.0 DLOOn yes + 0x20707 ChromaticAberrationRed yes + 0x20708 ChromaticAberrationBlue yes + 0x20709 DistortionEffect yes + 0x2070b DiffractionCorrectionOn yes + 0x20900 ColorHue yes + 0x20901 SaturationAdj yes + 0x20910 RedHSL yes + 0x20911 OrangeHSL yes + 0x20912 YellowHSL yes + 0x20913 GreenHSL yes + 0x20914 AquaHSL yes + 0x20915 BlueHSL yes + 0x20916 PurpleHSL yes + 0x20917 MagentaHSL yes + 0x20a00 GammaInfo CanonVRD GammaInfo + 0x30101 CropAspectRatio yes + 0x30102 CropAspectRatioCustom yes + 0xf0100 CropInfo CanonVRD CropInfo + 0xf0500 CustomPictureStyleData yes + 0xf0510 StampInfo CanonVRD StampInfo + 0xf0511 DustInfo CanonVRD DustInfo + 0xf0512 LensFocalLength yes + +=head3 CanonVRD DR4Header Tags + + Index4 Tag Name Writable + ------ -------- -------- + 3 DR4CameraModel int32u + +=head3 CanonVRD ToneCurve Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 ToneCurveColorSpace int32u + 1 ToneCurveShape int32u + 3 ToneCurveInputRange int32u[2] + 5 ToneCurveOutputRange int32u[2] + 7 RGBCurvePoints int32u[21] + 10 ToneCurveX int32u + 11 ToneCurveY int32u + 45 RedCurvePoints int32u[21] + 83 GreenCurvePoints int32u[21] + 121 BlueCurvePoints int32u[21] + +=head3 CanonVRD GammaInfo Tags + + Index8 Tag Name Writable + ------ -------- -------- + 2 GammaContrast double + 3 GammaColorTone double + 4 GammaSaturation double + 5 GammaUnsharpMaskStrength double + 6 GammaUnsharpMaskFineness double + 7 GammaUnsharpMaskThreshold double + 8 GammaSharpnessStrength double + 9 GammaShadow double + 10 GammaHighlight double + 12 GammaBlackPoint double + 13 GammaWhitePoint double + 14 GammaMidPoint double + 15 GammaCurveOutputRange double[2] + +=head3 CanonVRD CropInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 CropActive int32s + 1 CropRotatedOriginalWidth int32s + 2 CropRotatedOriginalHeight int32s + 3 CropX int32s + 4 CropY int32s + 5 CropWidth int32s + 6 CropHeight int32s + 8 CropRotation double + 10 CropOriginalWidth int32s + 11 CropOriginalHeight int32s + +=head3 CanonVRD StampInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 2 StampToolCount no + +=head3 CanonVRD DustInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 2 DustDeleteApplied no + +=head2 Casio Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 RecordingMode int16u + 0x0002 Quality int16u + 0x0003 FocusMode int16u + 0x0004 FlashMode int16u + 0x0005 FlashIntensity int16u + 0x0006 ObjectDistance int32u + 0x0007 WhiteBalance int16u + 0x000a DigitalZoom int32u + 0x000b Sharpness int16u + 0x000c Contrast int16u + 0x000d Saturation int16u + 0x0014 ISO int16u + 0x0015 FirmwareDate string[18] + 0x0016 Enhancement int16u + 0x0017 ColorFilter int16u + 0x0018 AFPoint int16u + 0x0019 FlashIntensity int16u + 0x0e00 PrintIM PrintIM + +=head3 Casio Type2 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0002 PreviewImageSize int16u[2] + 0x0003 PreviewImageLength int32u* + 0x0004 PreviewImageStart int32u* + 0x0008 QualityMode int16u + 0x0009 CasioImageSize int16u + 0x000d FocusMode int16u + 0x0014 ISO int16u + 0x0019 WhiteBalance int16u + 0x001d FocalLength rational64u + 0x001f Saturation int16u + 0x0020 Contrast int16u + 0x0021 Sharpness int16u + 0x0e00 PrintIM PrintIM + 0x2000 PreviewImage undef + 0x2001 FirmwareDate string[18] + 0x2011 WhiteBalanceBias int16u[2] + 0x2012 WhiteBalance int16u + 0x2021 AFPointPosition int16u[4]~ + 0x2022 ObjectDistance int32u + 0x2034 FlashDistance int16u + 0x2076 SpecialEffectMode int8u[3] + 0x2089 FaceInfo1 Casio FaceInfo1 + FaceInfo2 Casio FaceInfo2 + FaceInfoUnknown? yes + 0x211c FacesDetected int8u + 0x3000 RecordMode int16u + 0x3001 ReleaseMode int16u + 0x3002 Quality int16u + 0x3003 FocusMode int16u + 0x3006 HometownCity string + 0x3007 BestShotMode int16u + 0x3008 AutoISO int16u + 0x3009 AFMode int16u + 0x3011 Sharpness undef[2] + 0x3012 Contrast undef[2] + 0x3013 Saturation undef[2] + 0x3014 ISO int16u + 0x3015 ColorMode int16u + 0x3016 Enhancement int16u + 0x3017 ColorFilter int16u + 0x301b ArtMode int16u + 0x301c SequenceNumber int16u + 0x301d BracketSequence int16u[2] + 0x3020 ImageStabilization int16u + 0x302a LightingMode int16u + 0x302b PortraitRefiner int16u + 0x3030 SpecialEffectLevel int16u + 0x3031 SpecialEffectSetting int16u + 0x3103 DriveMode int16u + 0x310b ArtModeParameters int8u[3] + 0x4001 CaptureFrameRate int16u[n] + 0x4003 VideoQuality int16u + +=head3 Casio FaceInfo1 Tags + +Face-detect tags extracted from models such as the EX-H5. + + Index1 Tag Name Writable + ------ -------- -------- + 0 FacesDetected int8u + 1 FaceDetectFrameSize int16u[2] + 13 Face1Position int16u[4] + 124 Face2Position int16u[4] + 235 Face3Position int16u[4] + 346 Face4Position int16u[4] + 457 Face5Position int16u[4] + 568 Face6Position int16u[4] + 679 Face7Position int16u[4] + 790 Face8Position int16u[4] + 901 Face9Position int16u[4] + 1012 Face10Position int16u[4] + +=head3 Casio FaceInfo2 Tags + +Face-detect tags extracted from models such as the EX-H20G and EX-ZR100. + + Index1 Tag Name Writable + ------ -------- -------- + 2 FacesDetected int8u + 4 FaceDetectFrameSize int16u[2] + 8 FaceOrientation int8u + 24 Face1Position int16u[4] + 76 Face2Position int16u[4] + 128 Face3Position int16u[4] + 180 Face4Position int16u[4] + 232 Face5Position int16u[4] + 284 Face6Position int16u[4] + 336 Face7Position int16u[4] + 388 Face8Position int16u[4] + 440 Face9Position int16u[4] + 492 Face10Position int16u[4] + +=head3 Casio QVCI Tags + +This information is found in the APP1 QVCI segment of JPEG images from the +Casio QV-7000SX. + + Index1 Tag Name Writable + ------ -------- -------- + 44 CasioQuality no + 55 FocalRange? no + 77 DateTimeOriginal no + 98 ModelType no + 114 ManufactureIndex no + 124 ManufactureCode no + +=head3 Casio AVI Tags + +This information is found in Casio GV-10 AVI videos. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Software no + +=head2 DJI Tags + +This table lists tags found in the maker notes of images from some DJI +Phantom drones. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 Make string + 0x0003 SpeedX float + 0x0004 SpeedY float + 0x0005 SpeedZ float + 0x0006 Pitch float + 0x0007 Yaw float + 0x0008 Roll float + 0x0009 CameraPitch float + 0x000a CameraYaw float + 0x000b CameraRoll float + +=head3 DJI XMP Tags + +XMP tags used by DJI for images from drones. + +These tags belong to the ExifTool XMP-drone-dji family 1 group. + + Tag Name Writable + -------- -------- + AbsoluteAltitude real + CalibratedFocalLength real + CalibratedOpticalCenterX real + CalibratedOpticalCenterY real + CamReverse string + DewarpData string + DewarpFlag string + FlightPitchDegree real + FlightRollDegree real + FlightXSpeed real + FlightYSpeed real + FlightYawDegree real + FlightZSpeed real + GPSLatitude real/ + GPSLongitude real/ + GPSLongtitude real + GimbalPitchDegree real + GimbalReverse string + GimbalRollDegree real + GimbalYawDegree real + Latitude real + Longitude real + RelativeAltitude real + RtkFlag string + RtkStdHgt real + RtkStdLat real + RtkStdLon real + SelfData string + +=head3 DJI Info Tags + +Tags written by some DJI drones. + + Tag ID Tag Name Writable + ------ -------- -------- + 'FlightDegree(Y,P,R)' FlightDegree no + 'FlightSpeed(X,Y,Z)' FlightSpeed no + 'GimbalDegree(Y,P,R)' GimbalDegree no + 'adj_dbg_info' ADJDebugInfo no + 'ae_dbg_info' AEDebugInfo no + 'ae_histogram_info' AEHistogramInfo no + 'ae_liveview_histogram_info' + AELiveViewHistogramInfo no + 'ae_liveview_local_histogram' + AELiveViewLocalHistogram no + 'ae_local_histogram' AELocalHistogram no + 'af_dbg_info' AFDebugInfo no + 'awb_dbg_info' AWBDebugInfo no + 'hiso' Histogram no + 'hyperlapse_dbg_info' HyperlapsDebugInfo no + 'sensor_id' SensorID no + 'xidiri' Xidiri no + +=head3 DJI ThermalParams Tags + +Thermal parameters extracted from APP4 of DJI RJPEG files from the ZH20T. + + Index1 Tag Name Writable + ------ -------- -------- + 36 K1 no + 40 K2 no + 44 K3 no + 48 K4 no + 52 KF no + 56 B1 no + 60 B2 no + 68 ObjectDistance no + 70 RelativeHumidity no + 72 Emissivity no + 74 Reflection no + 76 AmbientTemperature no + 80 D2 no + 84 KJ no + 86 DB no + 88 KK no + +=head3 DJI ThermalParams2 Tags + +Thermal parameters extracted from APP4 of DJI M3T RJPEG files. + + Index1 Tag Name Writable + ------ -------- -------- + 0 AmbientTemperature no + 4 ObjectDistance no + 8 Emissivity no + 12 RelativeHumidity no + 16 ReflectedTemperature no + 101 IDString no + +=head3 DJI ThermalParams3 Tags + +Thermal parameters extracted from APP4 of some DJI RJPEG files. + + Index1 Tag Name Writable + ------ -------- -------- + 4 RelativeHumidity no + 6 ObjectDistance no + 8 Emissivity no + 10 ReflectedTemperature no + +=head2 FLIR Tags + +Information extracted from the maker notes of JPEG images from thermal +imaging cameras by FLIR Systems Inc. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 ImageTemperatureMax rational64u + 0x0002 ImageTemperatureMin rational64u + 0x0003 Emissivity rational64u + 0x0004 UnknownTemperature? rational64u + 0x0005 CameraTemperatureRangeMax? rational64u + 0x0006 CameraTemperatureRangeMin? rational64u + +=head3 FLIR FFF Tags + +Information extracted from FLIR FFF images and the APP1 FLIR segment of JPEG +images. These tags may also be extracted from the first frame of an FLIR +SEQ file, or all frames if the ExtractEmbedded option is used. Setting +ExtractEmbedded to 2 also the raw thermal data from all frames. + + Tag ID Tag Name Writable + ------ -------- -------- + '_header' FFFHeader FLIR Header + 0x0001 RawData FLIR RawData + 0x0005 GainDeadData FLIR GainDeadData + 0x0006 CoarseData FLIR CoarseData + 0x000e EmbeddedImage FLIR EmbeddedImage + 0x0020 CameraInfo FLIR CameraInfo + 0x0021 MeasurementInfo FLIR MeasInfo + 0x0022 PaletteInfo FLIR PaletteInfo + 0x0023 TextInfo FLIR TextInfo + 0x0024 EmbeddedAudioFile no + 0x0028 PaintData FLIR PaintData + 0x002a PiP FLIR PiP + 0x002b GPSInfo FLIR GPSInfo + 0x002c MeterLink FLIR MeterLink + 0x002e ParameterInfo FLIR ParamInfo + +=head3 FLIR Header Tags + +Tags extracted from the FLIR FFF/AFF header. + + Index1 Tag Name Writable + ------ -------- -------- + 4 CreatorSoftware no + +=head3 FLIR RawData Tags + +The thermal image data may be stored either as raw data, or in PNG format. +If stored as raw data, ExifTool adds a TIFF header to allow the data to be +viewed as a TIFF image. If stored in PNG format, the PNG image is extracted +as-is. Note that most FLIR cameras using the PNG format seem to write the +16-bit raw image data in the wrong byte order. + + Index2 Tag Name Writable + ------ -------- -------- + 1 RawThermalImageWidth no + 2 RawThermalImageHeight no + 16 RawThermalImageType no + 16.1 RawThermalImage no + +=head3 FLIR GainDeadData Tags + +Information found in FFF-format .GAN calibration image files. + + Index2 Tag Name Writable + ------ -------- -------- + 1 GainDeadMapImageWidth no + 2 GainDeadMapImageHeight no + 16 GainDeadMapImageType no + 16.1 GainDeadMapImage no + +=head3 FLIR CoarseData Tags + +Information found in FFF-format .CRS correction image files. + + Index2 Tag Name Writable + ------ -------- -------- + 1 CoarseMapImageWidth no + 2 CoarseMapImageHeight no + 16 CoarseMapImageType no + 16.1 CoarseMapImage no + +=head3 FLIR EmbeddedImage Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 EmbeddedImageWidth no + 2 EmbeddedImageHeight no + 16 EmbeddedImageType no + 16.1 EmbeddedImage no + +=head3 FLIR CameraInfo Tags + +FLIR camera information. The Planck tags are variables used in the +temperature calculation. See +L<https://exiftool.org/forum/index.php?topic=4898.msg23972#msg23972> +for details. + + Index1 Tag Name Writable + ------ -------- -------- + 32 Emissivity no + 36 ObjectDistance no + 40 ReflectedApparentTemperature no + 44 AtmosphericTemperature no + 48 IRWindowTemperature no + 52 IRWindowTransmission no + 60 RelativeHumidity no + 88 PlanckR1 no + 92 PlanckB no + 96 PlanckF no + 112 AtmosphericTransAlpha1 no + 116 AtmosphericTransAlpha2 no + 120 AtmosphericTransBeta1 no + 124 AtmosphericTransBeta2 no + 128 AtmosphericTransX no + 144 CameraTemperatureRangeMax no + 148 CameraTemperatureRangeMin no + 152 CameraTemperatureMaxClip no + 156 CameraTemperatureMinClip no + 160 CameraTemperatureMaxWarn no + 164 CameraTemperatureMinWarn no + 168 CameraTemperatureMaxSaturated no + 172 CameraTemperatureMinSaturated no + 212 CameraModel no + 244 CameraPartNumber no + 260 CameraSerialNumber no + 276 CameraSoftware no + 368 LensModel no + 400 LensPartNumber no + 416 LensSerialNumber no + 436 FieldOfView no + 492 FilterModel no + 508 FilterPartNumber no + 540 FilterSerialNumber no + 776 PlanckO no + 780 PlanckR2 no + 784 RawValueRangeMin no + 786 RawValueRangeMax no + 824 RawValueMedian no + 828 RawValueRange no + 900 DateTimeOriginal no + 912 FocusStepCount no + 1116 FocusDistance no + 1124 FrameRate no + +=head3 FLIR MeasInfo Tags + +Tags listed below are only for the first measurement tool, however multiple +measurements may be added, and information is extracted for all of them. +Tags for subsequent measurements are generated as required with the prefixes +"Meas2", "Meas3", etc. + + Tag Name Writable + -------- -------- + Meas1Label no + Meas1Params no + Meas1Type no + +=head3 FLIR PaletteInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 PaletteColors no + 6 AboveColor no + 9 BelowColor no + 12 OverflowColor no + 15 UnderflowColor no + 18 Isotherm1Color no + 21 Isotherm2Color no + 26 PaletteMethod no + 27 PaletteStretch no + 48 PaletteFileName no + 80 PaletteName no + 112 Palette no + +=head3 FLIR TextInfo Tags + + Tag Name Writable + -------- -------- + Label0 no + Label1 no + Label2 no + Label3 no + Value0 no + Value1 no + Value2 no + Value3 no + +=head3 FLIR PaintData Tags + +Information generated by FLIR Tools "Paint colors" tool. + + Index2 Tag Name Writable + ------ -------- -------- + 5 PaintImageWidth no + 6 PaintImageHeight no + 20 PaintImageType no + 20.1 PaintImage no + +=head3 FLIR PiP Tags + +FLIR Picture in Picture tags. + + Index2 Tag Name Writable + ------ -------- -------- + 0 Real2IR no + 2 OffsetX no + 3 OffsetY no + 4 PiPX1 no + 5 PiPX2 no + 6 PiPY1 no + 7 PiPY2 no + +=head3 FLIR GPSInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 GPSValid no + 4 GPSVersionID no + 8 GPSLatitudeRef no + 10 GPSLongitudeRef no + 16 GPSLatitude no + 24 GPSLongitude no + 32 GPSAltitude no + 64 GPSDOP no + 68 GPSSpeedRef no + 70 GPSTrackRef no + 72 GPSImgDirectionRef no + 76 GPSSpeed no + 80 GPSTrack no + 84 GPSImgDirection no + 88 GPSMapDatum no + +=head3 FLIR MeterLink Tags + +Tags containing Meterlink humidity meter information. + + Index1 Tag Name Writable + ------ -------- -------- + 26 Reading1Units no + 28 Reading1Description no + 32 Reading1Device no + 96 Reading1Value no + 126 Reading2Units no + 128 Reading2Description no + 132 Reading2Device no + 196 Reading2Value no + 226 Reading3Units no + 228 Reading3Description no + 232 Reading3Device no + 296 Reading3Value no + 326 Reading4Units no + 328 Reading4Description no + 332 Reading4Device no + 396 Reading4Value no + +=head3 FLIR ParamInfo Tags + + Tag Name Writable + -------- -------- + DateTimeGenerated no + Param0 no + Param1 no + Param2 no + Param3 no + +=head3 FLIR UserData Tags + +Tags written by some FLIR cameras in a top-level (!) "udta" atom of MP4 +videos. + + Tag ID Tag Name Writable + ------ -------- -------- + 'uuid' FLIR_Parts FLIR Parts + FLIR_Serial FLIR SerialNums + FLIR_Params FLIR Params + FLIR_UnknownUUID FLIR UnknownUUID + FLIR_GPS FLIR GPS_UUID + FLIR_MoreInfo FLIR MoreInfo + SoftwareComponents? no + FLIR_Unknown? no + Units no+ + ThumbnailImage no + +=head3 FLIR Parts Tags + +Tags extracted from the "uuid" box with ID 43c3993b0f94424b82056b66513f485d +in FLIR MP4 videos. + + Index1 Tag Name Writable + ------ -------- -------- + 4 BAHPVer no + BALPVer no + Battery no + BAVPVer no + CamCore no + DetectorBoard no + Detector no + GIDCVer no + GIDPVer no + GIPC_CPLD no + GIPCVer no + GIXIVer no + MainBoard no + Optics no + PartNumber no + +=head3 FLIR SerialNums Tags + +Tags extracted from the "uuid" box with ID 57f5b93e51e448afa0d9c3ef1b37f712 +in FLIR MP4 videos. + + Index1 Tag Name Writable + ------ -------- -------- + 12 UnknownSerial1? no + 45 UnknownSerial2? no + 78 UnknownSerial3? no + 111 UnknownSerial4? no + 123 UnknownNumber? no + 126 CameraSerialNumber no + +=head3 FLIR Params Tags + +Tags extracted from the "uuid" box with ID 41e5dcf9e80a41ceadfe7f0c58082c19 +in FLIR MP4 videos. + + Index4 Tag Name Writable + ------ -------- -------- + 1 ReflectedApparentTemperature no + 2 AtmosphericTemperature no + 3 Emissivity no + 4 ObjectDistance no + 5 RelativeHumidity no + 6 EstimatedAtmosphericTrans no + 7 IRWindowTemperature no + 8 IRWindowTransmission no + +=head3 FLIR UnknownUUID Tags + +Tags extracted from the "uuid" box with ID 574520502cbb44adae5415e9b839d903 +in FLIR MP4 videos. + + Index4 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 FLIR GPS_UUID Tags + +Tags extracted from the "uuid" box with ID 7f2e21008b464918afb1de709a74f6f5 +in FLIR MP4 videos. + + Index4 Tag Name Writable + ------ -------- -------- + 1 GPSLatitude no + 2 GPSLongitude no + 3 GPSAltitude no + +=head3 FLIR MoreInfo Tags + +Tags extracted from the "uuid" box with ID 2b452fdc74354094baee22a6b23a7cf8 +in FLIR MP4 videos. + + Index1 Tag Name Writable + ------ -------- -------- + 5 LensModel no + 11 UnknownTemperature1? no + 15 UnknownTemperature2? no + +=head3 FLIR AFF Tags + +Tags extracted from FLIR "AFF" SEQ images. + + Tag ID Tag Name Writable + ------ -------- -------- + '_header' AFFHeader FLIR Header + 0x0001 AFF1 FLIR AFF1 + 0x0005 AFF5 FLIR AFF5 + +=head3 FLIR AFF1 Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 SensorWidth no + 2 SensorHeight no + +=head3 FLIR AFF5 Tags + + Index2 Tag Name Writable + ------ -------- -------- + 19 SensorWidth no + 20 SensorHeight no + +=head3 FLIR FPF Tags + +Tags extracted from FLIR Public image Format (FPF) files. + + Index1 Tag Name Writable + ------ -------- -------- + 32 FPFVersion no + 36 ImageDataOffset no + 40 ImageType no + 42 ImagePixelFormat no + 44 ImageWidth no + 46 ImageHeight no + 48 ExternalTriggerCount no + 52 SequenceFrameNumber no + 120 CameraModel no + 152 CameraPartNumber no + 184 CameraSerialNumber no + 216 CameraTemperatureRangeMin no + 220 CameraTemperatureRangeMax no + 224 LensModel no + 256 LensPartNumber no + 288 LensSerialNumber no + 320 FilterModel no + 336 FilterPartNumber no + 384 FilterSerialNumber no + 480 Emissivity no + 484 ObjectDistance no + 488 ReflectedApparentTemperature no + 492 AtmosphericTemperature no + 496 RelativeHumidity no + 500 ComputedAtmosphericTrans no + 504 EstimatedAtmosphericTrans no + 508 ReferenceTemperature no + 512 IRWindowTemperature no + 516 IRWindowTransmission no + 584 DateTimeOriginal no + 676 CameraScaleMin no + 680 CameraScaleMax no + 684 CalculatedScaleMin no + 688 CalculatedScaleMax no + 692 ActualScaleMin no + 696 ActualScaleMax no + +=head2 FujiFilm Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 Version undef + 0x0010 InternalSerialNumber string + 0x1000 Quality string + 0x1001 Sharpness int16u + 0x1002 WhiteBalance int16u + 0x1003 Saturation int16u + 0x1004 Contrast int16u + 0x1005 ColorTemperature int16u + 0x1006 Contrast int16u + 0x100a WhiteBalanceFineTune int32s[2] + 0x100b NoiseReduction int16u + 0x100e NoiseReduction int16u + 0x100f Clarity int32s + 0x1010 FujiFlashMode int16u + 0x1011 FlashExposureComp rational64s + 0x1020 Macro int16u + 0x1021 FocusMode int16u + 0x1022 AFMode int16u + 0x1023 FocusPixel int16u[2] + 0x102b PrioritySettings FujiFilm PrioritySettings + 0x102d FocusSettings FujiFilm FocusSettings + 0x102e AFCSettings FujiFilm AFCSettings + 0x1030 SlowSync int16u + 0x1031 PictureMode int16u + 0x1032 ExposureCount int16u + 0x1033 EXRAuto int16u + 0x1034 EXRMode int16u + 0x1040 ShadowTone int32s + 0x1041 HighlightTone int32s + 0x1044 DigitalZoom int32u + 0x1045 LensModulationOptimizer int32u + 0x1047 GrainEffectRoughness int32s + 0x1048 ColorChromeEffect int32s + 0x1049 BWAdjustment int8s + 0x104b BWMagentaGreen int8s + 0x104c GrainEffectSize int16u + 0x104d CropMode int16u + 0x104e ColorChromeFXBlue int32s + 0x1050 ShutterType int16u + 0x1100 AutoBracketing int16u + 0x1101 SequenceNumber int16u + 0x1103 DriveSettings FujiFilm DriveSettings + 0x1105 PixelShiftShots int16u + 0x1106 PixelShiftOffset rational64s[2] + 0x1153 PanoramaAngle int16u + 0x1154 PanoramaDirection int16u + 0x1201 AdvancedFilter int32u + 0x1210 ColorMode int16u + 0x1300 BlurWarning int16u + 0x1301 FocusWarning int16u + 0x1302 ExposureWarning int16u + 0x1304 GEImageSize string + 0x1400 DynamicRange int16u + 0x1401 FilmMode int16u + 0x1402 DynamicRangeSetting int16u + 0x1403 DevelopmentDynamicRange int16u + 0x1404 MinFocalLength rational64s + 0x1405 MaxFocalLength rational64s + 0x1406 MaxApertureAtMinFocal rational64s + 0x1407 MaxApertureAtMaxFocal rational64s + 0x140b AutoDynamicRange int16u + 0x1422 ImageStabilization int16u[3] + 0x1425 SceneRecognition int16u + 0x1431 Rating int32u + 0x1436 ImageGeneration int16u + 0x1438 ImageCount int16u + 0x1443 DRangePriority int16u + 0x1444 DRangePriorityAuto int16u + 0x1445 DRangePriorityFixed int16u + 0x1446 FlickerReduction int32u + 0x1447 FujiModel string + 0x1448 FujiModel2 string + 0x144d RollAngle rational64s + 0x3803 VideoRecordingMode int32u + 0x3804 PeripheralLighting int16u + 0x3806 VideoCompression int16u + 0x3820 FrameRate int16u + 0x3821 FrameWidth int16u + 0x3822 FrameHeight int16u + 0x3824 FullHDHighSpeedRec int32u + 0x4005 FaceElementSelected int16u[4] + 0x4100 FacesDetected int16u + 0x4103 FacePositions int16u[n] + 0x4200 NumFaceElements int16u + 0x4201 FaceElementTypes int8u[n] + 0x4203 FaceElementPositions int16u[n] + 0x4282 FaceRecInfo FujiFilm FaceRecInfo + 0x8000 FileSource string + 0x8002 OrderNumber int32u + 0x8003 FrameNumber int16u + 0xb211 Parallax rational64s + +=head3 FujiFilm PrioritySettings Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0.1 AF-SPriority int16u & 0x0f + 0.2 AF-CPriority int16u & 0xf0 + +=head3 FujiFilm FocusSettings Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0.1 FocusMode2 int32u & 0x0f + 0.2 PreAF int32u & 0xf0 + 0.3 AFAreaMode int32u & 0xf00 + 0.4 AFAreaPointSize int32u & 0xf000 + 0.5 AFAreaZoneSize int32u & 0xf0000 + +=head3 FujiFilm AFCSettings Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 AF-CSetting int32u + 0.1 AF-CTrackingSensitivity int32u & 0x0f + 0.2 AF-CSpeedTrackingSensitivity int32u & 0xf0 + 0.3 AF-CZoneAreaSwitching int32u & 0xf00 + +=head3 FujiFilm DriveSettings Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0.1 DriveMode int32u & 0xff + 0.2 DriveSpeed int32u & 0xff000000 + +=head3 FujiFilm FaceRecInfo Tags + +Face recognition information. + + Tag Name Writable + -------- -------- + Face1Birthday no + Face1Category no + Face1Name no + Face2Birthday no + Face2Category no + Face2Name no + Face3Birthday no + Face3Category no + Face3Name no + Face4Birthday no + Face4Category no + Face4Name no + Face5Birthday no + Face5Category no + Face5Name no + Face6Birthday no + Face6Category no + Face6Name no + Face7Birthday no + Face7Category no + Face7Name no + Face8Birthday no + Face8Category no + Face8Name no + +=head3 FujiFilm RAF Tags + +FujiFilm RAF images contain meta information stored in a proprietary +FujiFilm RAF format, as well as EXIF information stored inside an embedded +JPEG preview image. The table below lists tags currently decoded from the +RAF-format information. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0100 RawImageFullSize no + 0x0110 RawImageCropTopLeft no + 0x0111 RawImageCroppedSize no + 0x0115 RawImageAspectRatio no + 0x0121 RawImageSize no + 0x0130 FujiLayout no + 0x0131 XTransLayout no + 0x2000 WB_GRGBLevelsAuto no + 0x2100 WB_GRGBLevelsDaylight no + 0x2200 WB_GRGBLevelsCloudy no + 0x2300 WB_GRGBLevelsDaylightFluor no + 0x2301 WB_GRGBLevelsDayWhiteFluor no + 0x2302 WB_GRGBLevelsWhiteFluorescent no + 0x2310 WB_GRGBLevelsWarmWhiteFluor no + 0x2311 WB_GRGBLevelsLivingRoomWarmWhiteFluor no + 0x2400 WB_GRGBLevelsTungsten no + 0x2ff0 WB_GRGBLevels no + 0x9200 RelativeExposure no + 0x9650 RawExposureBias no + 0xc000 RAFData FujiFilm RAFData + +=head3 FujiFilm RAFData Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 RawImageWidth no + 4 RawImageWidth no + RawImageHeight no + 8 RawImageWidth no + RawImageHeight no + 12 RawImageHeight no + +=head3 FujiFilm IFD Tags + +Tags found in the FujiIFD information of RAF images from some models. + + Tag ID Tag Name Writable + ------ -------- -------- + 0xf000 FujiIFD FujiFilm IFD + 0xf001 RawImageFullWidth no + 0xf002 RawImageFullHeight no + 0xf003 BitsPerSample no + 0xf007 StripOffsets no + 0xf008 StripByteCounts no + 0xf00a BlackLevel no + 0xf00b GeometricDistortionParams no + 0xf00c WB_GRBLevelsStandard no + 0xf00d WB_GRBLevelsAuto no + 0xf00e WB_GRBLevels no + 0xf00f ChromaticAberrationParams no + 0xf010 VignettingParams no + +=head3 FujiFilm FFMV Tags + +Information found in the FFMV atom of MOV videos. + + Index1 Tag Name Writable + ------ -------- -------- + 0 MovieStreamName no + +=head3 FujiFilm MOV Tags + +This information is found in MOV videos from some FujiFilm cameras. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 24 Model no + 46 ExposureTime no + 50 FNumber no + 58 ExposureCompensation no + +=head2 GE Tags + +This table lists tags found in the maker notes of some General Imaging +camera models. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0202 Macro int16u + 0x0207 GEModel string + 0x0300 GEMake string + +=head2 HP Tags + +These tables list tags found in the maker notes of some Hewlett-Packard +camera models. + +The first table lists tags found in the EXIF-format maker notes of the +PhotoSmart 720 (also used by the Vivitar ViviCam 3705, 3705B and 3715). + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0e00 PrintIM PrintIM + +=head3 HP Type2 Tags + +These tags are used by the PhotoSmart E427. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Lens Shading' LensShading no + 'PreviewImage' PreviewImage no + 'Serial Number' SerialNumber no + +=head3 HP Type4 Tags + +These tags are used by the PhotoSmart M627. + + Index1 Tag Name Writable + ------ -------- -------- + 12 MaxAperture no + 16 ExposureTime no + 20 CameraDateTime no + 52 ISO no + 92 SerialNumber no + +=head3 HP Type6 Tags + +These tags are used by the PhotoSmart M425, M525 and M527. + + Index1 Tag Name Writable + ------ -------- -------- + 12 FNumber no + 16 ExposureTime no + 20 CameraDateTime no + 52 ISO no + 88 SerialNumber no + +=head3 HP TDHD Tags + +These tags are extracted from the APP6 "TDHD" segment of Photosmart R837 +JPEG images. Many other unknown tags exist in is data, and can be seen with +the Unknown (-u) option. + + Tag ID Tag Name Writable + ------ -------- -------- + 'CMSN' SerialNumber no + 'FWRV' FirmwareVersion no + 'LSLV' LSLV HP TDHD + 'TDHD' TDHD HP TDHD + +=head2 JVC Tags + +JVC EXIF maker note tags. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0002 CPUVersions no + 0x0003 Quality no + +=head3 JVC Text Tags + +JVC/Victor text-based maker note tags. + + Tag ID Tag Name Writable + ------ -------- -------- + 'QTY' Quality no + 'VER' MakerNoteVersion no + +=head2 Kodak Tags + +Many Kodak models don't store the maker notes in standard IFD format, and +these formats vary with different models. Some information has been +decoded, but much of the Kodak information remains unknown. + +The table below contains the most common set of Kodak tags. The following +Kodak camera models have been tested and found to use these tags: C360, +C663, C875, CX6330, CX6445, CX7330, CX7430, CX7525, CX7530, DC4800, DC4900, +DX3500, DX3600, DX3900, DX4330, DX4530, DX4900, DX6340, DX6440, DX6490, +DX7440, DX7590, DX7630, EasyShare-One, LS420, LS443, LS633, LS743, LS753, +V530, V550, V570, V603, V610, V705, Z650, Z700, Z710, Z730, Z740, Z760 and +Z7590. + + Index1 Tag Name Writable + ------ -------- -------- + 0 KodakModel string[8] + 9 Quality int8u + 10 BurstMode int8u + 12 KodakImageWidth int16u + 14 KodakImageHeight int16u + 16 YearCreated int16u + 18 MonthDayCreated int8u[2] + 20 TimeCreated int8u[4] + 24 BurstMode2? int16u + 27 ShutterMode int8u + 28 MeteringMode int8u + 29 SequenceNumber int8u + 30 FNumber int16u + 32 ExposureTime int32u + 36 ExposureCompensation int16s + 38 VariousModes? int16u + 40 Distance1? int32u + 44 Distance2? int32u + 48 Distance3? int32u + 52 Distance4? int32u + 56 FocusMode int8u + 58 VariousModes2? int16u + 60 PanoramaMode? int16u + 62 SubjectDistance? int16u + 64 WhiteBalance int8u + 92 FlashMode int8u + 93 FlashFired int8u + 94 ISOSetting int16u + 96 ISO int16u + 98 TotalZoom int16u + 100 DateTimeStamp int16u + 102 ColorMode int16u + 104 DigitalZoom int16u + 107 Sharpness int8s + +=head3 Kodak IFD Tags + +These tags are found in a separate IFD of JPEG, TIFF, DCR and KDC images +from some older Kodak models such as the DC50, DC120, DCS760C, DCS Pro 14N, +14nx, SLR/n, Pro Back and Canon EOS D2000. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 KodakVersion int8u[4] + 0x0001 UnknownEV? rational64u + 0x0003 ExposureValue rational64u + 0x03e9 OriginalFileName string + 0x03ea KodakTag int32u + 0x03eb SensorLeftBorder int16u + 0x03ec SensorTopBorder int16u + 0x03ed SensorImageWidth int16u + 0x03ee SensorImageHeight int16u + 0x03ef BlackLevelTop int16u + 0x03f0 BlackLevelBottom int16u + 0x03f1 TextualInfo Kodak TextualInfo + 0x03f2 FlashMode? int16u + 0x03f3 FlashCompensation rational64s + 0x03f4 WindMode? int16u + 0x03f5 FocusMode? int16u + 0x03f8 MinAperture rational64u + 0x03f9 MaxAperture rational64u + 0x03fa WhiteBalanceMode? int16u + 0x03fb WhiteBalanceDetected? int16u + 0x03fc WhiteBalance int16u + 0x03fd Processing Kodak Processing + ProcessingParameters no + 0x03fe ImageAbsoluteX int16s + 0x03ff ImageAbsoluteY int16s + 0x0400 ApplicationKeyString string + 0x0401 Time string + 0x0402 GPSString string + 0x0403 EventLogCapture? no + 0x0404 ComponentTable? no + 0x0405 CustomIlluminant? int16u + 0x0406 CameraTemperature rational64s + CameraTemperature no + 0x0407 AdapterVoltage rational64u + 0x0408 BatteryVoltage rational64u + 0x0409 DacVoltages no + 0x040a IlluminantDetectorData? no + 0x040b PixelClockFrequency int32u + 0x040c CenterPixel int16u[3] + 0x040d BurstCount int16u + 0x040e BlackLevelRough int16u + 0x040f OffsetMapHorizontal? no + 0x0410 OffsetMapVertical? no + 0x0411 Histogram? no + 0x0412 VerticalClockOverlaps int16u[2] + 0x0413 SensorTemperature no + 0x0414 XilinxVersion string + 0x0415 FirmwareVersion int32u + 0x0416 BlackLevelRoughAfter int16u + 0x0417 BrightRowsTop no + 0x0418 EventLogProcess no + 0x0419 DacVoltagesFlush no + 0x041a FlashUsed no + 0x041b FlashType no + 0x041c SelfTimer no + 0x041d AFMode no + 0x041e LensType no + 0x041f ImageCropX int16s + 0x0420 ImageCropY int16s + 0x0421 AdjustedTbnImageWidth no + 0x0422 AdjustedTbnImageHeight no + 0x0423 IntegrationTime int32u + 0x0424 BracketingMode no + 0x0425 BracketingStep no + 0x0426 BracketingCounter no + 0x042e HuffmanTableLength no + 0x042f HuffmanTableValue no + 0x0438 MainBoardVersion int32u + 0x0439 ImagerBoardVersion int32u + 0x044c FocusEdgeMap no + 0x05e6 IdleTiming no + 0x05e7 FlushTiming no + 0x05e8 IntegrateTiming no + 0x05e9 RegisterReadTiming no + 0x05ea FirstLineTransferTiming no + 0x05eb ShiftTiming no + 0x05ec NormalLineTransferTiming no + 0x05ed TestTransferTiming no + 0x05fa MinimumFlushRows no + 0x05fd ImagerPowerOnDelayMsec int32u + 0x05fe ImagerInitialTimingCode no + 0x05ff ImagerLogicProgram no + 0x0600 ImagerBiasSettlingDelayMsec int32u + 0x0604 IdleSequence no + 0x0605 FirstFlushSequence no + 0x0606 FinalFlushSequence no + 0x0607 SampleBlackSequence no + 0x0608 TransferSequence no + 0x060e DacCountsPerVolt no + 0x060f BlackDacChannel no + 0x0610 BlackAdCountsPerDacVolt no + 0x0611 BlackTarget no + 0x0612 BlackDacSettlingMsec no + 0x07d0 StandardMatrixDaylight rational64s[9] + 0x07d1 StandardMatrixTungsten rational64s[9] + 0x07d2 StandardMatrixFluorescent rational64s[9] + 0x07d3 StandardMatrixFlash rational64s[9] + 0x07d4 StandardMatrixCustom rational64s[9] + 0x07da DeviantMatrixDaylight rational64s[9] + 0x07db DeviantMatrixTungsten rational64s[9] + 0x07dc DeviantMatrixFluorescent rational64s[9] + 0x07dd DeviantMatrixFlash rational64s[9] + 0x07de DeviantMatrixCustom rational64s[9] + 0x07e4 UniqueMatrixDaylight rational64s[9] + 0x07e5 UniqueMatrixTungsten rational64s[9] + 0x07e6 UniqueMatrixFluorescent rational64s[9] + 0x07e7 UniqueMatrixFlash rational64s[9] + 0x07e8 UniqueMatrixCustom rational64s[9] + 0x07e9 UniqueMatrixAuto rational64s[9] + 0x0834 StandardWhiteDaylight rational64s[3] + 0x0835 StandardWhiteTungsten rational64s[3] + 0x0836 StandardWhiteFluorescent rational64s[3] + 0x0837 StandardWhiteFlash rational64s[3] + 0x0838 StandardWhiteCustom rational64s[3] + 0x083e DeviantWhiteDaylight rational64s[3] + 0x083f DeviantWhiteTungsten rational64s[3] + 0x0840 DeviantWhiteFluorescent rational64s[3] + 0x0841 DeviantWhiteFlash rational64s[3] + 0x0842 DeviantWhiteCustom rational64s[3] + 0x0846 ColorTemperature int16u + 0x0847 WB_RGBLevelsAsShot no + 0x0848 WB_RGBLevelsDaylight no + 0x0849 WB_RGBLevelsTungsten no + 0x084a WB_RGBLevelsFluorescent no + 0x084b WB_RGBLevelsFlash no + 0x084c WB_RGBLevelsCustom no + 0x084d WB_RGBLevelsAuto no + 0x0852 WB_RGBMulDaylight rational64u[3] + 0x0853 WB_RGBMulTungsten rational64u[3] + 0x0854 WB_RGBMulFluorescent rational64u[3] + 0x0855 WB_RGBMulFlash rational64u[3] + 0x085c WB_RGBCoeffsDaylight no + 0x085d WB_RGBCoeffsTungsten no + 0x085e WB_RGBCoeffsFluorescent no + 0x085f WB_RGBCoeffsFlash no + 0x0898 ExposureGainDaylight rational64s + 0x0899 ExposureGainTungsten rational64s + 0x089a ExposureGainFluorescent rational64s + 0x089b ExposureGainFlash rational64s + 0x089c ExposureGainCustom rational64s + 0x089d AnalogISOTable rational64u[3] + 0x089e AnalogCaptureISO int32u + 0x089f ISOCalibrationGain rational64u + 0x08a0 ISOCalibrationGainTable no + 0x08a1 ExposureHeadroomFactor no + 0x08ab LinearitySplineTags no + 0x08fc MonitorMatrix rational64s[9] + 0x08fd TonScaleTable no + 0x08fe Gamma rational64u + 0x08ff LogLinTable no + 0x0900 LinLogTable no + 0x0901 GammaTable no + 0x0902 LogScale rational64u + 0x0903 BaseISO rational64u + 0x0904 LinLogCoring int16u + 0x0905 PatternGainConversionTable no + 0x0906 DefectCount no + 0x0907 DefectList no + 0x0908 DefectListPacked no + 0x0909 ImageSpace int16u + 0x090a ThumbnailCompressionTable no + 0x090b ThumbnailExpansionTable no + 0x090c ImageCompressionTable no + 0x090d ImageExpansionTable no + 0x090e EighteenPercentPoint no + 0x090f DefectIsoCode int16u + 0x0910 BaseISODaylight rational64u + 0x0911 BaseISOTungsten rational64u + 0x0912 BaseISOFluorescent rational64u + 0x0913 BaseISOFlash rational64u + 0x091a MatrixSelectThreshold int16s + 0x091b MatrixSelectK rational64u + 0x091c IlluminantDetectTable no + 0x091d RGTable no + 0x091e MatrixSelectThreshold1 int16s + 0x091f MatrixSelectThreshold2 int16s + 0x0924 PortraitMatrix no + 0x0925 PortraitToneScaleTable no + 0x092e EnableSharpening int16u + 0x092f SharpeningKernel int16s[25] + 0x0930 EdgeMapSlope int16u + 0x0931 EdgeMapX1 int16u + 0x0932 EdgeMapX2 int16u + 0x0933 KernelDenominators int16u[3] + 0x0934 EdgeMapX3 int16u + 0x0935 EdgeMapX4 int16u + 0x0936 SharpenForThumbnail no + 0x0937 EdgeSpline no + 0x0938 DownSampleBy2Hor no + 0x0939 DownSampleBy2Ver no + 0x093a DownSampleBy4Hor no + 0x093b DownSampleBy4Ver no + 0x093c DownSampleBy3Hor no + 0x093d DownSampleBy3Ver no + 0x093e DownSampleBy6Hor no + 0x093f DownSampleBy6Ver no + 0x0940 DownSampleBy2Hor3MPdcr no + 0x0941 DownSampleBy2Ver3MPdcr no + 0x0942 ThumbnailResizeRatio no + 0x0943 AtCaptureUserCrop int32u[4] + 0x0944 ImageResolution int32u + 0x0945 ImageResolutionJpg int32u + 0x094c USMParametersLow no + 0x094d USMParametersMed no + 0x094e USMParametersHigh no + 0x094f USMParametersHost no + 0x0950 EdgeSplineLow no + 0x0951 EdgeSplineMed no + 0x0952 EdgeSplineHigh no + 0x0953 USMParametersHost6MP no + 0x0954 USMParametersHost3MP no + 0x0960 PatternImagerWidth int16u + 0x0961 PatternImagerHeight int16u + 0x0962 PatternAreaWidth int16u + 0x0963 PatternAreaHeight int16u + 0x0964 PatternCorrectionGains no + 0x0965 PatternCorrectionOffsets no + 0x0966 PatternX int16u + 0x0967 PatternY int16u + 0x0968 PatternCorrectionFactors no + 0x0969 PatternCorrectionFactorScale int16u + 0x096a PatternCropRows1 int16u + 0x096b PatternCropRows2 int16u + 0x096c PatternCropCols1 int16u + 0x096d PatternCropCols2 int16u + 0x096e PixelCorrectionGains no + 0x096f StitchRows no + 0x0970 StitchColumns no + 0x0971 PixelCorrectionScale int16u + 0x0972 PixelCorrectionOffset int16u + 0x0988 LensTableIndex no + 0x0992 DiffTileGains602832 no + 0x0993 DiffTileGains24t852822 no + 0x099c TileGainDeterminationTable no + 0x099d NemoBlurKernel no + 0x099e NemoTileSize no + 0x099f NemoGainFactors no + 0x09a0 NemoDarkLimit no + 0x09a1 NemoHighlight12Limit no + 0x09c4 ImagerFileProductionLevel int16u + 0x09c5 ImagerFileDateCreated int32u + 0x09c6 CalibrationVersion string + 0x09c7 ImagerFileTagsVersionStandard int16u + 0x09c8 IFCameraModel string + 0x09c9 CalibrationHistory string + 0x09ca CalibrationLog no + 0x09ce SensorSerialNumber string + 0x09f6 DefectConcealArtCorrectThres no + 0x09f7 SglColDCACThres1 no + 0x09f8 SglColDCACThres2 no + 0x09f9 SglColDCACTHres3 no + 0x0a01 DblColDCACThres1 no + 0x0a02 DblColDCACThres2 no + 0x0a0a DefectConcealThresTable no + 0x0a28 MonoUniqueMatrix no + 0x0a29 MonoMonitorMatrix no + 0x0a2a MonoToneScaleTable no + 0x0a5a OmenInitialScaling no + 0x0a5b OmenInitialRows no + 0x0a5c OmenInitialColumns no + 0x0a5d OmenInitialIPFStrength int32s[4] + 0x0a5e OmenEarlyStrength int32s[4] + 0x0a5f OmenAutoStrength int32s[4] + 0x0a60 OmenAtCaptureStrength int32s[4] + 0x0a61 OmenAtCaptureMode no + 0x0a62 OmenFocalLengthLimit int16s + 0x0a64 OmenSurfaceIndex int16s + 0x0a65 OmenPercentToRationalLimitsRed no + 0x0a66 OmenPercentToRationalLimitsGoR no + 0x0a67 OmenPercentToRationalLimitsBlue no + 0x0a68 OmenPercentToRationalLimitsGoB no + 0x0a6e OmenEarlyGoBSurface no + 0x0a6f OmenEarlyGoBRows no + 0x0a70 OmenEarlyGoBColumns no + 0x0a73 OmenSmoothingKernel no + 0x0a74 OmenGradientOffset no + 0x0a75 OmenGradientKernel no + 0x0a76 OmenGradientKernelTaps no + 0x0a77 OmenRatioClipFactors no + 0x0a78 OmenRatioExclusionFactors no + 0x0a79 OmenGradientExclusionLimits no + 0x0a7a OmenROICoordinates no + 0x0a7b OmenROICoefficients no + 0x0a7c OmenRangeWeighting no + 0x0a7d OmenMeanToStrength no + 0x0bb8 FactoryWhiteGainsDaylight no + 0x0bb9 FactoryWhiteOffsetsDaylight no + 0x0bba DacGainsCoarse no + 0x0bbb DacGainsFine no + 0x0bbc DigitalExposureGains no + 0x0bbd DigitalExposureBiases no + 0x0bbe BlackClamp no + 0x0bbf ChannelCoarseGainAdjust no + 0x0bc0 BlackClampOffset no + 0x0bf4 DMPixelThresholdFactor no + 0x0bf5 DMWindowThresholdFactor no + 0x0bf6 DMTrimFraction no + 0x0bf7 DMSmoothRejThresh no + 0x0bf8 DMFillRejThresh no + 0x0bf9 VMWsize no + 0x0bfa DMErodeRadius no + 0x0bfb DMNumPatches no + 0x0bfc DMNoiseScale no + 0x0bfe BrightDefectThreshold no + 0x0bff BrightDefectIntegrationMS no + 0x0c00 BrightDefectIsoCode no + 0x0c03 TopDarkRow1 no + 0x0c04 TopDarkRow2 no + 0x0c05 BottomDarkRow1 no + 0x0c06 BottomDarkRow2 no + 0x0c07 LeftDarkCol1 no + 0x0c08 LeftDarkCol2 no + 0x0c09 RightDarkCol1 no + 0x0c0a RightDarkCol2 no + 0x0c0b HMPixThresh no + 0x0c0c HMColThresh no + 0x0c0d HMWsize no + 0x0c0e HMColRejThresh no + 0x0c0f VMPixThresh no + 0x0c10 VMColThresh no + 0x0c11 VMNbands no + 0x0c12 VMColDropThresh no + 0x0c13 VMPatchResLimit no + 0x0c14 MapScale no + 0x0c1c Klut no + 0x0c1d RimNonlinearity no + 0x0c1e InverseRimNonlinearity no + 0x0c1f RembrandtToneScale no + 0x0c20 RimToNifColorTransform no + 0x0c21 RimToNifScaleFactor no + 0x0c22 NifNonlinearity no + 0x0c23 SBALogTransform no + 0x0c24 InverseSBALogTransform no + 0x0c25 SBABlack int16u + 0x0c26 SBAGray int16u + 0x0c27 SBAWhite int16u + 0x0c28 GaussianWeights no + 0x0c29 SfsBoundary no + 0x0c2a CoringTableBest no + 0x0c2b CoringTableBetter no + 0x0c2c CoringTableGood no + 0x0c2d ExposureReferenceGain no + 0x0c2e ExposureReferenceOffset no + 0x0c2f SBARedBalanceLut no + 0x0c30 SBAGreenBalanceLut no + 0x0c31 SBABlueBalanceLut no + 0x0c32 SBANeutralBAL int32s + 0x0c33 SBAGreenMagentaBAL int32s + 0x0c34 SBAIlluminantBAL int32s + 0x0c35 SBAAnalysisComplete int8u + 0x0c36 JPEGQTableBest no + 0x0c37 JPEGQTableBetter no + 0x0c38 JPEGQTableGood no + 0x0c39 RembrandtPortraitToneScale no + 0x0c3a RembrandtConsumerToneScale no + 0x0c3b CFAGreenThreshold1 no + 0x0c3c CFAGreenThreshold2 no + 0x0c3d QTableLarge50Pct no + 0x0c3e QTableLarge67Pct no + 0x0c3f QTableLarge100Pct no + 0x0c40 QTableMedium50Pct no + 0x0c41 QTableMedium67Pct no + 0x0c42 QTableMedium100Pct no + 0x0c43 QTableSmall50Pct no + 0x0c44 QTableSmall67Pct no + 0x0c45 QTableSmall100Pct no + 0x0c46 SBAHighGray int16u + 0x0c47 SBALowGray int16u + 0x0c48 CaptureLook int16u + 0x0c49 SBAIllOffset int16s + 0x0c4a SBAGmOffset int16s + 0x0c4b NifNonlinearity12Bit no + 0x0c4c SharpeningOn no + 0x0c4d NifNonlinearity16Bit no + 0x0c4e RawHistogram no + 0x0c4f RawCFAComponentAverages no + 0x0c50 DisableFlagsPresent no + 0x0c51 DelayCols no + 0x0c52 DummyColsLeft no + 0x0c53 TrashColsRight no + 0x0c54 BlackColsRight no + 0x0c55 DummyColsRight no + 0x0c56 OverClockColsRight no + 0x0c57 UnusedBlackRowsTopOut no + 0x0c58 TrashRowsBottom no + 0x0c59 BlackRowsBottom no + 0x0c5a OverClockRowsBottom no + 0x0c5b BlackColsLeft no + 0x0c5c BlackRowsTop no + 0x0c5d PartialActiveColsLeft no + 0x0c5e PartialActiveColsRight no + 0x0c5f PartialActiveRowsTop no + 0x0c60 PartialActiveRowsBottom no + 0x0c61 ProcessBorderColsLeft int16u + 0x0c62 ProcessBorderColsRight int16u + 0x0c63 ProcessBorderRowsTop int16u + 0x0c64 ProcessBorderRowsBottom int16u + 0x0c65 ActiveCols no + 0x0c66 ActiveRows no + 0x0c67 FirstLines no + 0x0c68 UnusedBlackRowsTopIn no + 0x0c69 UnusedBlackRowsBottomIn no + 0x0c6a UnusedBlackRowsBottomOut no + 0x0c6b UnusedBlackColsLeftOut no + 0x0c6c UnusedBlackColsLeftIn no + 0x0c6d UnusedBlackColsRightIn no + 0x0c6e UnusedBlackColsRightOut no + 0x0c6f CFAOffsetRows int32u + 0x0c70 ShiftCols int16s + 0x0c71 CFAOffsetCols int32u + 0x0c76 DarkMapScale no + 0x0c77 HMapHandling no + 0x0c78 VMapHandling no + 0x0c79 DarkThreshold no + 0x0c7a DMDitherMatrix int16u + 0x0c7b DMDitherMatrixWidth int16u + 0x0c7c DMDitherMatrixHeight int16u + 0x0c7d MaxPixelValueThreshold int16u + 0x0c7e HoleFillDeltaThreshold int16u + 0x0c7f DarkPedestal int16u + 0x0c80 ImageProcessingFileTagsVersionNumber int16u + 0x0c81 ImageProcessingFileDateCreated string + 0x0c82 DoublingMicrovolts int32s + 0x0c83 DarkFrameShortExposure int32u + 0x0c84 DarkFrameLongExposure int32u + 0x0c85 DarkFrameCountFactor rational64u + 0x0c88 HoleFillDarkDeltaThreshold int16u + 0x0c89 FarkleWhiteThreshold no + 0x0c8a ColumnResetOffsets no + 0x0c8b ColumnGainFactors no + 0x0c8c Channel0LagKernel no + 0x0c8d Channel1LagKernel no + 0x0c8e Channel2LagKernel no + 0x0c8f Channel3LagKernel no + 0x0c90 BluegrassTable no + 0x0c91 BluegrassScale1 no + 0x0c92 BluegrassScale2 no + 0x0ce4 FinishedFileProcessingRequest no + 0x0ce5 FirmwareVersion string + 0x0ce6 HostSoftwareExportVersion no + 0x0ce7 HostSoftwareRendering int32u + 0x0dac DCS3XXProcessingInfoIFD no + 0x0dad DCS3XXProcessingInfo no + 0x0dae IPAVersion int32u + 0x0db6 FinishIPAVersion no + 0x0db7 FinishIPFVersion no + 0x0db8 FinishFileType int32u + 0x0db9 FinishResolution int32u + 0x0dba FinishNoise int32u + 0x0dbb FinishSharpening int32u + 0x0dbc FinishLook int32u + 0x0dbd FinishExposure int32u + 0x0e0b SigmaScalingFactorLowRes rational64u + 0x0e0c SigmaScalingFactorCamera rational64u + 0x0e0d SigmaImpulseParameters int16u[n] + 0x0e0e SigmaNoiseThreshTableV2 no + 0x0e0f SigmaSizeTable int16u[n] + 0x0e10 DacGainsCoarseAdjPreIF41 no + 0x0e11 SigmaNoiseFilterCalTableV1 no + 0x0e12 SigmaNoiseFilterTableV1 no + 0x0e13 Lin12ToKlut8 no + 0x0e14 SigmaNoiseFilterTableV1Version no + 0x0e15 Lin12ToKlut12 no + 0x0e16 Klut12ToLin12 no + 0x0e17 NifNonlinearity12To16 no + 0x0e18 SBALog12Transform no + 0x0e19 InverseSBALog12Transform no + 0x0e1a ToneScale0 no + 0x0e1b ToneScale1 no + 0x0e1c ToneScale2 no + 0x0e1d ToneScale3 no + 0x0e1e ToneScale4 no + 0x0e1f ToneScale5 no + 0x0e20 ToneScale6 no + 0x0e21 ToneScale7 no + 0x0e22 ToneScale8 no + 0x0e23 ToneScale9 no + 0x0e24 DayMat0 no + 0x0e25 DayMat1 no + 0x0e26 DayMat2 no + 0x0e27 DayMat3 no + 0x0e28 DayMat4 no + 0x0e29 DayMat5 no + 0x0e2a DayMat6 no + 0x0e2b DayMat7 no + 0x0e2c DayMat8 no + 0x0e2d DayMat9 no + 0x0e2e TungMat0 no + 0x0e2f TungMat1 no + 0x0e30 TungMat2 no + 0x0e31 TungMat3 no + 0x0e32 TungMat4 no + 0x0e33 TungMat5 no + 0x0e34 TungMat6 no + 0x0e35 TungMat7 no + 0x0e36 TungMat8 no + 0x0e37 TungMat9 no + 0x0e38 FluorMat0 no + 0x0e39 FluorMat1 no + 0x0e3a FluorMat2 no + 0x0e3b FluorMat3 no + 0x0e3c FluorMat4 no + 0x0e3d FluorMat5 no + 0x0e3e FluorMat6 no + 0x0e3f FluorMat7 no + 0x0e40 FluorMat8 no + 0x0e41 FluorMat9 no + 0x0e42 FlashMat0 no + 0x0e43 FlashMat1 no + 0x0e44 FlashMat2 no + 0x0e45 FlashMat3 no + 0x0e46 FlashMat4 no + 0x0e47 FlashMat5 no + 0x0e48 FlashMat6 no + 0x0e49 FlashMat7 no + 0x0e4a FlashMat8 no + 0x0e4b FlashMat9 no + 0x0e4c KodakLook string + 0x0e4d IPFCameraModel string + 0x0e4e AH2GreenInterpolationThreshold int16u + 0x0e4f ResamplingKernelDenominators067 int16u[3] + 0x0e50 ResamplingKernelDenominators050 int16u[3] + 0x0e51 ResamplingKernelDenominators100 int16u[3] + 0x0e56 LookMat0 no + 0x0e57 LookMat1 no + 0x0e58 LookMat2 no + 0x0e59 LookMat3 no + 0x0e5a LookMat4 no + 0x0e5b LookMat5 no + 0x0e5c LookMat6 no + 0x0e5d LookMat7 no + 0x0e5e LookMat8 no + 0x0e5f LookMat9 no + 0x0e60 CFAInterpolationAlgorithm int16u + 0x0e61 CFAInterpolationMetric int16u + 0x0e62 CFAZipperFixThreshold int16u + 0x0e63 NoiseReductionParametersKhufuRGB int16u[9] + 0x0e64 NoiseReductionParametersKhufu6MP int16u[9] + 0x0e65 NoiseReductionParametersKhufu3MP int16u[9] + 0x0e6a ChromaNoiseHighFThresh int32u[2] + 0x0e6b ChromaNoiseLowFThresh int32u[2] + 0x0e6c ChromaNoiseEdgeMapThresh int32u + 0x0e6d ChromaNoiseColorSpace int32u + 0x0e6e EnableChromaNoiseReduction int16u + 0x0e6f NoiseReductionParametersHostRGB int16u[9] + 0x0e70 NoiseReductionParametersHost6MP int16u[9] + 0x0e71 NoiseReductionParametersHost3MP int16u[9] + 0x0e72 NoiseReductionParametersCamera int16u[6] + 0x0e73 NoiseReductionParametersAtCapture int16u[6] + 0x0e74 LCDMatrix rational64s[9] + 0x0e75 LCDMatrixChickFix rational64s[9] + 0x0e76 LCDMatrixMarvin rational64s[9] + 0x0e7c LCDGammaTableChickFix no + 0x0e7d LCDGammaTableMarvin no + 0x0e7e LCDGammaTable no + 0x0e7f LCDSharpeningF1 no + 0x0e80 LCDSharpeningF2 no + 0x0e81 LCDSharpeningF3 no + 0x0e82 LCDSharpeningF4 no + 0x0e83 LCDEdgeMapX1 no + 0x0e84 LCDEdgeMapX2 no + 0x0e85 LCDEdgeMapX3 no + 0x0e86 LCDEdgeMapX4 no + 0x0e87 LCDEdgeMapSlope no + 0x0e88 YCrCbMatrix no + 0x0e89 LCDEdgeSpline no + 0x0e92 Fac18Per int16u + 0x0e93 Fac170Per int16u + 0x0e94 Fac100Per int16u + 0x0e9b ExtraTickLocations no + 0x0e9c RGBtoeV0 no + 0x0e9d RGBtoeV1 no + 0x0e9e RGBtoeV2 no + 0x0e9f RGBtoeV3 no + 0x0ea0 RGBtoeV4 no + 0x0ea1 RGBtoeV5 no + 0x0ea2 RGBtoeV6 no + 0x0ea3 RGBtoeV7 no + 0x0ea4 RGBtoeV8 no + 0x0ea5 RGBtoeV9 no + 0x0ea6 LCDHistLUT0 no + 0x0ea7 LCDHistLUT1 no + 0x0ea8 LCDHistLUT2 no + 0x0ea9 LCDHistLUT3 no + 0x0eaa LCDHistLUT4 no + 0x0eab LCDHistLUT5 no + 0x0eac LCDHistLUT6 no + 0x0ead LCDHistLUT7 no + 0x0eae LCDHistLUT8 no + 0x0eaf LCDHistLUT9 no + 0x0eb0 LCDLinearClipValue no + 0x0ece LCDStepYvalues no + 0x0ecf LCDStepYvaluesChickFix no + 0x0ed0 LCDStepYvaluesMarvin no + 0x0ed8 InterpolationCoefficients no + 0x0ed9 InterpolationCoefficients6MP no + 0x0eda InterpolationCoefficients3MP no + 0x0f00 NoiseReductionParametersHostNormal no + 0x0f01 NoiseReductionParametersHostStrong no + 0x0f02 NoiseReductionParametersHostLow no + 0x0f0a MariahTextureThreshold int16u + 0x0f0b MariahMapLoThreshold int16u + 0x0f0c MariahMapHiThreshold int16u + 0x0f0d MariahChromaBlurSize int16u + 0x0f0e MariahSigmaThreshold int16u + 0x0f0f MariahThresholds no + 0x0f10 MariahThresholdsNormal no + 0x0f11 MariahThresholdsStrong no + 0x0f12 MariahThresholdsLow no + 0x0f14 KhufuLinearRedMixingCoefficient no + 0x0f15 KhufuLinearGreenMixingCoefficient no + 0x0f16 KhufuLinearBlueMixingCoefficient no + 0x0f17 KhufuUSpaceC2MixingCoefficient no + 0x0f18 KhufuSigmaGaussianWeights no + 0x0f19 KhufuSigmaScalingFactors6MP no + 0x0f1a KhufuSigmaScalingFactors3MP no + 0x0f1b KhufuSigmaScalingFactors14MP no + 0x0f32 KhufuI0Thresholds no + 0x0f33 KhufuI1Thresholds no + 0x0f34 KhufuI2Thresholds no + 0x0f35 KhufuI3Thresholds no + 0x0f36 KhufuI4Thresholds no + 0x0f37 KhufuI5Thresholds no + 0x0f3c CondadoDayBVThresh int16u + 0x0f3d CondadoNeuRange int16u + 0x0f3e CondadoBVFactor int16s + 0x0f3f CondadoIllFactor int16s + 0x0f40 CondadoTunThresh int16s + 0x0f41 CondadoFluThresh int16s + 0x0f42 CondadoDayOffsets int16s[2] + 0x0f43 CondadoTunOffsets int16s[2] + 0x0f44 CondadoFluOffsets int16s[2] + 0x0f5a ERIMMToCRGB0Spline no + 0x0f5b ERIMMToCRGB1Spline no + 0x0f5c ERIMMToCRGB2Spline no + 0x0f5d ERIMMToCRGB3Spline no + 0x0f5e ERIMMToCRGB4Spline no + 0x0f5f ERIMMToCRGB5Spline no + 0x0f60 ERIMMToCRGB6Spline no + 0x0f61 ERIMMToCRGB7Spline no + 0x0f62 ERIMMToCRGB8Spline no + 0x0f63 ERIMMToCRGB9Spline no + 0x0f64 CRGBToERIMM0Spline no + 0x0f65 CRGBToERIMM1Spline no + 0x0f66 CRGBToERIMM2Spline no + 0x0f67 CRGBToERIMM3Spline no + 0x0f68 CRGBToERIMM4Spline no + 0x0f69 CRGBToERIMM5Spline no + 0x0f6a CRGBToERIMM6Spline no + 0x0f6b CRGBToERIMM7Spline no + 0x0f6c CRGBToERIMM8Spline no + 0x0f6d CRGBToERIMM9Spline no + 0x0f6e ERIMMNonLinearitySpline no + 0x0f6f Delta12To8Spline no + 0x0f70 Delta8To12Spline no + 0x0f71 InverseMonitorMatrix no + 0x0f72 NifNonlinearityExt no + 0x0f73 InvNifNonLinearity no + 0x0f74 RIMM13ToERIMM12Spline no + 0x0f78 ToneScale0Spline no + 0x0f79 ToneScale1Spline no + 0x0f7a ToneScale2Spline no + 0x0f7b ToneScale3Spline no + 0x0f7c ToneScale4Spline no + 0x0f7d ToneScale5Spline no + 0x0f7e ToneScale6Spline no + 0x0f7f ToneScale7Spline no + 0x0f80 ToneScale8Spline no + 0x0f81 ToneScale9Spline no + 0x0f82 ERIMMToneScale0Spline no + 0x0f83 ERIMMToneScale1Spline no + 0x0f84 ERIMMToneScale2Spline no + 0x0f85 ERIMMToneScale3Spline no + 0x0f86 ERIMMToneScale4Spline no + 0x0f87 ERIMMToneScale5Spline no + 0x0f88 ERIMMToneScale6Spline no + 0x0f89 ERIMMToneScale7Spline no + 0x0f8a ERIMMToneScale8Spline no + 0x0f8b ERIMMToneScale9Spline no + 0x0f8c RIMMToCRGB0Spline no + 0x0f8d RIMMToCRGB1Spline no + 0x0f8e RIMMToCRGB2Spline no + 0x0f8f RIMMToCRGB3Spline no + 0x0f90 RIMMToCRGB4Spline no + 0x0f91 RIMMToCRGB5Spline no + 0x0f92 RIMMToCRGB6Spline no + 0x0f93 RIMMToCRGB7Spline no + 0x0f94 RIMMToCRGB8Spline no + 0x0f95 RIMMToCRGB9Spline no + 0x0fa0 QTableLarge25Pct no + 0x0fa1 QTableMedium25Pct no + 0x0fa2 QTableSmall25Pct no + 0x1130 NoiseReductionKernel no + 0x1388 UserMetaData no + 0x1389 InputProfile undef + 0x138a KodakLookProfile undef + 0x138b OutputProfile undef + 0x1390 SourceProfilePrefix string + 0x1391 ToneCurveProfileName string + 0x1392 InputProfile ICC_Profile + 0x1393 ProcessParametersV2 no + 0x1394 ReservedBlob2 no + 0x1395 ReservedBlob3 no + 0x1396 ReservedBlob4 no + 0x1397 ReservedBlob5 no + 0x1398 ReservedBlob6 no + 0x1399 ReservedBlob7 no + 0x139a ReservedBlob8 no + 0x139b ReservedBlob9 no + 0x1770 ScriptVersion int32u + 0x177a ImagerTimingData no + 0x1784 ISO int32u + 0x17a2 Scav11Cols no + 0x17a3 Scav12Cols no + 0x17a4 Scav21Cols no + 0x17a5 Scav22Cols no + 0x17a6 ActiveCTEMonitor1Cols no + 0x17a7 ActiveCTEMonitor2Cols no + 0x17a8 ActiveCTEMonitorRows no + 0x17a9 ActiveBuf1Cols no + 0x17aa ActiveBuf2Cols no + 0x17ab ActiveBuf1Rows no + 0x17ac ActiveBuf2Rows no + 0x17c0 HRNoiseLines no + 0x17c1 RNoiseLines no + 0x17c2 ANoiseLines no + 0x17d4 ImagerCols int16u + 0x17de ImagerRows int16u + 0x17e8 PartialActiveCols1 int32u + 0x17f2 PartialActiveCols2 int32u + 0x17fc PartialActiveRows1 int32u + 0x1806 PartialActiveRows2 int32u + 0x1810 ElectricalBlackColumns int32u + 0x181a ResetBlackSegRows int32u + 0x1838 CaptureWidthNormal int32u + 0x1839 CaptureHeightNormal int32u + 0x183a CaptureWidthResetBlackSegNormal no + 0x183b CaptureHeightResetBlackSegNormal no + 0x183c DarkRefOffsetNormal no + 0x1842 CaptureWidthTest int32u + 0x1843 CaptureHeightTest no + 0x1844 CaptureWidthResetBlackSegTest no + 0x1845 CaptureHeightResetBlackSegTest no + 0x1846 DarkRefOffsetTest no + 0x184c ImageSegmentStartLine int32u + 0x184d ImageSegmentLines int32u + 0x184e SkipLineTime int32u + 0x1860 FastResetLineTime int32u + 0x186a NormalLineTime int32u + 0x1874 MinIntegrationRows int32u + 0x187e PreReadFastResetCount int32u + 0x1888 TransferTimeNormal int32u + 0x1889 TransferTimeTest int32u + 0x188a QuietTime int32u + 0x189c OverClockCols int16u + 0x18a6 H2ResetBlackPixels int32u + 0x18b0 H3ResetBlackPixels int32u + 0x18ba BlackAcquireRows int32u + 0x18c4 OverClockRows int16u + 0x18ce H3ResetBlackColumns int32u + 0x18d8 DarkBlackSegRows int32u + 0x1900 CrossbarEnable no + 0x1901 FifoenOnePixelDelay int32u + 0x1902 ReadoutTypeRequested int32u + 0x1903 ReadoutTypeActual int32u + 0x190a OffsetDacValue int32u + 0x1914 TempAmpGainX100 int32u + 0x191e VarrayDacNominalValues int32u[3] + 0x1928 VddimDacNominalValues no + 0x1964 C14Configuration int32u + 0x196e TDA1Offset int32u[3] + 0x196f TDA1Bandwidth int32u + 0x1970 TDA1Gain int32u[3] + 0x1971 TDA1EdgePolarity int32u + 0x1978 TDA2Offset int32u[3] + 0x1979 TDA2Bandwidth int32u + 0x197a TDA2Gain int32u[3] + 0x197b TDA2EdgePolarity int32u + 0x1982 TDA3Offset int32u[3] + 0x1983 TDA3Bandwidth int32u + 0x1984 TDA3Gain int32u[3] + 0x1985 TDA3EdgePolarity int32u + 0x198c TDA4Offset int32u[3] + 0x198d TDA4Bandwidth int32u + 0x198e TDA4Gain int32u[3] + 0x198f TDA4EdgePolarity int32u + 0xfde8 ComLenBlkSize int16u + +=head3 Kodak TextualInfo Tags + +Below is a list of tags which have been observed in the Kodak TextualInfo +data, however ExifTool will extract information from any tags found here. + + Tag ID Tag Name Writable + ------ -------- -------- + 'AF Function' AFMode no + 'Actual Compensation' ActualCompensation no + 'Aperture' Aperture no + 'Auto Bracket' AutoBracket no + 'Brightness Value' BrightnessValue no + 'Camera' CameraModel no + 'Camera body' CameraBody no + 'Compensation' ExposureCompensation no + 'Date' Date no + 'Exposure Bias' ExposureBias no + 'Exposure Mode' ExposureMode no + 'Firmware Version' FirmwareVersion no + 'Flash Compensation' FlashExposureComp no + 'Flash Fired' FlashFired no + 'Flash Sync Mode' FlashSyncMode no + 'Focal Length' FocalLength no + 'Height' KodakImageHeight no + 'ISO' ISO no + 'ISO Speed' ISO no + 'Image Number' ImageNumber no + 'Lens' Lens no + 'Max Aperture' MaxAperture no + 'Meter Mode' MeterMode no + 'Min Aperture' MinAperture no + 'Popup Flash' PopupFlash no + 'Serial Number' SerialNumber no + 'Shooting Mode' ShootingMode no + 'Shutter' ShutterSpeed no + 'Temperature' Temperature no + 'Time' Time no + 'White balance' WhiteBalance no + 'Width' KodakImageWidth no + '_other_info' OtherInfo no + +=head3 Kodak Processing Tags + + Index2 Tag Name Writable + ------ -------- -------- + 20 WB_RGBLevels no + +=head3 Kodak Type2 Tags + +These tags are used by the Kodak DC220, DC260, DC265 and DC290, +Hewlett-Packard PhotoSmart 618, C500 and C912, Pentax EI-200 and EI-2000, +and Minolta EX1500Z. + + Index1 Tag Name Writable + ------ -------- -------- + 8 KodakMaker string[32] + 40 KodakModel string[32] + 108 KodakImageWidth int32u + 112 KodakImageHeight int32u + +=head3 Kodak Type3 Tags + +These tags are used by the DC240, DC280, DC3400 and DC5000. + + Index1 Tag Name Writable + ------ -------- -------- + 12 YearCreated int16u + 14 MonthDayCreated int8u[2] + 16 TimeCreated int8u[4] + 30 OpticalZoom int16u + 55 Sharpness int8s + 56 ExposureTime int32u + 60 FNumber int16u + 78 ISO int16u + +=head3 Kodak Type4 Tags + +These tags are used by the DC200 and DC215. + + Index1 Tag Name Writable + ------ -------- -------- + 32 OriginalFileName string[12] + +=head3 Kodak Type5 Tags + +These tags are used by the CX4200, CX4210, CX4230, CX4300, CX4310, CX6200 +and CX6230. + + Index1 Tag Name Writable + ------ -------- -------- + 20 ExposureTime int32u + 26 WhiteBalance int8u + 28 FNumber int16u + 30 ISO int16u + 32 OpticalZoom int16u + 34 DigitalZoom int16u + 39 FlashMode int8u + 42 ImageRotated int8u + 43 Macro int8u + +=head3 Kodak Type6 Tags + +These tags are used by the DX3215 and DX3700. + + Index1 Tag Name Writable + ------ -------- -------- + 16 ExposureTime int32u + 20 ISOSetting? int32u + 24 FNumber int16u + 26 ISO int16u + 28 OpticalZoom int16u + 30 DigitalZoom int16u + 34 Flash int16u + +=head3 Kodak Type7 Tags + +The maker notes of models such as the C340, C433, CC533, LS755, V803 and +V1003 seem to start with the camera serial number. The C310, C315, C330, +C643, C743, CD33, CD43, CX7220 and CX7300 maker notes are also decoded using +this table, although the strings for these cameras don't conform to the +usual Kodak serial number format, and instead have the model name followed +by 8 digits. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SerialNumber string[16] + +=head3 Kodak Type8 Tags + +Kodak models such as the ZD710, P712, P850, P880, V1233, V1253, V1275, +V1285, Z612, Z712, Z812, Z885 use standard TIFF IFD format for the maker +notes. In keeping with Kodak's strategy of inconsistent makernotes, models +such as the M380, M1033, M1093, V1073, V1273, Z1012, Z1085 and Z8612 +also use these tags, but these makernotes begin with a TIFF header instead +of an IFD entry count and use relative instead of absolute offsets. There +is a large amount of information stored in these maker notes (apparently +with much duplication), but relatively few tags have so far been decoded. + + Tag ID Tag Name Writable + ------ -------- -------- + 0xfc00 SubIFD0 Kodak SubIFD0 + Kodak SubIFD0 + 0xfc01 SubIFD1 Kodak SubIFD1 + Kodak SubIFD1 + 0xfc02 SubIFD2 Kodak SubIFD2 + Kodak SubIFD2 + 0xfc03 SubIFD3 Kodak SubIFD3 + Kodak SubIFD3 + 0xfc04 SubIFD4 Kodak SubIFD4 + Kodak SubIFD4 + 0xfc05 SubIFD5 Kodak SubIFD5 + Kodak SubIFD5 + 0xfc06 SubIFD6 Kodak SubIFD6 + Kodak SubIFD6 + 0xfcff SubIFD255 Kodak SubIFD0 + 0xff00 CameraInfo Kodak CameraInfo + +=head3 Kodak SubIFD0 Tags + +SubIFD0 through SubIFD5 tags are written a number of newer Kodak models. + + Tag ID Tag Name Writable + ------ -------- -------- + 0xfa02 SceneMode int16u + 0xfa19 SerialNumber string + 0xfa1d KodakImageWidth int16u + 0xfa1e KodakImageHeight int16u + 0xfa20 SensorWidth int16u + 0xfa21 SensorHeight int16u + 0xfa23 FNumber int16u + 0xfa24 ExposureTime int32u + 0xfa2e ISO int16u + 0xfa3d OpticalZoom int16u + 0xfa46 ISO int16u + 0xfa51 KodakImageWidth int16u + 0xfa52 KodakImageHeight int16u + 0xfa54 ThumbnailWidth int16u + 0xfa55 ThumbnailHeight int16u + 0xfa57 PreviewImageWidth int16u + 0xfa58 PreviewImageHeight int16u + +=head3 Kodak SubIFD1 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0027 ISO int16u + 0x0028 ISO int16u + +=head3 Kodak SubIFD2 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x6002 SceneModeUsed int32u + 0x6006 OpticalZoom int32u + 0x6103 MaxAperture int32u + 0xf002 SceneModeUsed int32u + 0xf006 OpticalZoom int32u + 0xf103 FNumber int32u + 0xf104 ExposureTime int32u + 0xf105 ISO int32u + +=head3 Kodak SubIFD3 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x1000 OpticalZoom int16u + +=head3 Kodak SubIFD4 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Kodak SubIFD5 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x000f OpticalZoom int16u + +=head3 Kodak SubIFD6 Tags + +SubIFD6 is written by the M580. + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Kodak CameraInfo Tags + +These tags are used by the P712, P850 and P880. + + Tag ID Tag Name Writable + ------ -------- -------- + 0xf900 SensorWidth int16u + 0xf901 SensorHeight int16u + 0xf902 BayerPattern string + 0xf903 SensorFullWidth int16u + 0xf904 SensorFullHeight int16u + 0xf907 KodakImageWidth int16u + 0xf908 KodakImageHeight int16u + 0xfa00 KodakInfoType string + 0xfa04 SerialNumber string + 0xfd04 FNumber int16u + 0xfd05 ExposureTime int32u + 0xfd06 ISO int16u + +=head3 Kodak Type9 Tags + +These tags are used by the Kodak C140, C180, C913, C1013, M320, M340 and +M550, as well as various cameras marketed by other manufacturers. + + Index1 Tag Name Writable + ------ -------- -------- + 12 FNumber int16u + 16 ExposureTime int32u + 20 DateTimeOriginal string[20] + 52 ISO int16u + 87 FirmwareVersion string[16] + 168 UnknownNumber no + 196 UnknownNumber no + +=head3 Kodak Type10 Tags + +Another variation of the IFD-format type, this time with just a byte order +indicator instead of a full TIFF header. These tags are used by the Z980. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0002 PreviewImageSize int16u[2] + 0x0012 ExposureTime int32u + 0x0013 FNumber int16u + 0x0014 ISO int16u + 0x001d FocalLength int32u + +=head3 Kodak Type11 Tags + +These tags are found in models such as the PixPro S-1. They are not +writable because the inconsistency of Kodak maker notes is beginning to get +on my nerves. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0104 FirmwareVersion no + 0x0203 PictureEffect no + 0x0207 KodakModel no + 0x0300 KodakMake no + 0x0308 LensSerialNumber no + 0x0309 LensModel no + 0x030d LevelMeter? no + 0x0311 Pitch no + 0x0312 Yaw no + 0x0313 Roll no + 0x0314 CX? no + 0x0315 CY? no + 0x0316 Rads? no + +=head3 Kodak Unknown Tags + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Kodak Meta Tags + +These tags are found in the APP3 "Meta" segment of JPEG images from Kodak +cameras such as the DC280, DC3400, DC5000, MC3, M580, Z950 and Z981. The +structure of this segment is similar to the APP1 "Exif" segment, but a +different set of tags is used. + + Tag ID Tag Name Writable + ------ -------- -------- + 0xc350 FilmProductCode no + 0xc351 ImageSourceEK no + 0xc352 CaptureConditionsPAR no + 0xc353 CameraOwner undef + 0xc354 SerialNumber undef + 0xc355 UserSelectGroupTitle no + 0xc356 DealerIDNumber no + 0xc357 CaptureDeviceFID no + 0xc358 EnvelopeNumber no + 0xc359 FrameNumber no + 0xc35a FilmCategory no + 0xc35b FilmGencode no + 0xc35c ModelAndVersion no + 0xc35d FilmSize no + 0xc35e SBA_RGBShifts no + 0xc35f SBAInputImageColorspace no + 0xc360 SBAInputImageBitDepth no + 0xc361 SBAExposureRecord no + 0xc362 UserAdjSBA_RGBShifts no + 0xc363 ImageRotationStatus no + 0xc364 RollGuidElements no + 0xc365 MetadataNumber no + 0xc366 EditTagArray no + 0xc367 Magnification no + 0xc36c NativeXResolution no + 0xc36d NativeYResolution no + 0xc36e KodakEffectsIFD Kodak SpecialEffects + 0xc36f KodakBordersIFD Kodak Borders + 0xc37a NativeResolutionUnit no + 0xc418 SourceImageDirectory no + 0xc419 SourceImageFileName no + 0xc41a SourceImageVolumeName no + 0xc46c PrintQuality no + 0xc46e ImagePrintStatus no + +=head3 Kodak SpecialEffects Tags + +The Kodak SpecialEffects and Borders tags are found in sub-IFD's within the +Kodak JPEG APP3 "Meta" segment. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 DigitalEffectsVersion no + 0x0001 DigitalEffectsName no + 0x0002 DigitalEffectsType no + +=head3 Kodak Borders Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 BordersVersion no + 0x0001 BorderName no + 0x0002 BorderID no + 0x0003 BorderLocation no + 0x0004 BorderType no + 0x0008 WatermarkType no + +=head3 Kodak KDC_IFD Tags + +These tags are found in a separate IFD of KDC images from some newer Kodak +models such as the P880 and Z1015IS. + + Tag ID Tag Name Writable + ------ -------- -------- + 0xfa00 SerialNumber string + 0xfa0d WhiteBalance int8u + 0xfa25 WB_RGBLevelsAuto no + 0xfa27 WB_RGBLevelsTungsten no + 0xfa28 WB_RGBLevelsFluorescent no + 0xfa29 WB_RGBLevelsDaylight no + 0xfa2a WB_RGBLevelsShade no + +=head3 Kodak frea Tags + +Information stored in the "frea" atom of Kodak PixPro SP360 MP4 videos. + + Tag ID Tag Name Writable + ------ -------- -------- + 'scra' PreviewImage no + 'thma' ThumbnailImage no + 'tima' Duration no + 'ver ' KodakVersion no + +=head3 Kodak Free Tags + +Information stored in the "free" atom of Kodak MP4 videos. (VERY bad form +for Kodak to store useful information in an atom intended for unused space!) + + Tag ID Tag Name Writable + ------ -------- -------- + 'AprV' ApertureValue no + 'BrtV' BrightnessValue no + 'Expc' ExposureCompensation no + 'Expo' ExposureTime no + 'FL35' FocalLengthIn35mmFormat no + 'FNum' FNumber no + 'FoLn' FocalLength no + 'ISOS' ISO no + 'SVer' FirmwareVersion no + 'Scrn' PreviewInfo Kodak Scrn + 'Seri' SerialNumber no + 'StSV' ShutterSpeedValue no + +=head3 Kodak Scrn Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 PreviewImageWidth no + 1 PreviewImageHeight no + 2 PreviewImageLength no + 4 PreviewImage no + +=head3 Kodak DcMD Tags + +Metadata directory found in MOV and MP4 videos from some Kodak cameras. + + Tag ID Tag Name Writable + ------ -------- -------- + 'CMbo' CameraByteOrder no + 'Cmbo' CameraByteOrder no + 'DcEM' DcEM Kodak DcEM + 'DcME' DcME Kodak DcME + +=head3 Kodak DcEM Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Kodak DcME Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Kodak MOV Tags + +This information is found in the TAGS atom of MOV videos from Kodak models +such as the P880. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 22 Model no + 64 ModelType no + 78 ExposureTime no + 82 FNumber no + 90 ExposureCompensation no + 112 FocalLength no + +=head3 Kodak pose Tags + +Streamed orientation information from the PixPro 4KVR360, extracted as +sub-documents when the Duplicates option is used. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Accelerometer' Accelerometer no + 'AngularVelocity' AngularVelocity no + +=head2 Leaf Tags + +These tags are found in .MOS images from Leaf digital camera backs as +written by Creo Leaf Capture. They exist within the Leaf-specific directory +structure of EXIF tag 0x8606. The tables below list observed Leaf tags, +however ExifTool will extract any tags found in the Leaf directories even if +they don't appear in these tables. + + Tag ID Tag Name Writable + ------ -------- -------- + 'JPEG_preview_data' PreviewImage no + 'JPEG_preview_info' PreviewInfo no + 'PDA_histogram_data' PDAHistogram no + 'back_serial_number' BackSerial no + 'camera_profile' CameraProfile Leaf CameraProfile + 'icc_camera_profile' ICC_Profile ICC_Profile + 'icc_camera_to_tone_matrix' ToneMatrix no + 'icc_camera_to_tone_space_flow' ToneSpaceFlow no + 'icc_rgb_ws_profile' RGB_Profile ICC_Profile + 'image_offset' ImageOffset no + 'pattern_ratation_angle' PatternAngle no + +=head3 Leaf CameraProfile Tags + +All B<Tag ID>'s in the following table have a leading 'CamProf_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'back_type' CameraBackType no + 'capture_profile' CaptureProfile Leaf CaptureProfile + 'image_profile' ImageProfile Leaf ImageProfile + 'name' CameraName no + 'type' CameraType no + 'version' CameraProfileVersion no + +=head3 Leaf CaptureProfile Tags + +All B<Tag ID>'s in the following table have a leading 'CaptProf_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'CCD_rect' CCDRect no + 'CCD_valid_rect' CCDValidRect no + 'CCD_video_rect' CCDVideoRect no + 'back_type' CaptProfBackType no + 'center_dark_rect' CenterDarkRect no + 'color_averages' ColorAverages no + 'color_matrix' ColorMatrix no + 'dark_correction_type' DarkCorrectionType no + 'image_bounds' ImageBounds no + 'image_fields' ImageFields no + 'image_offset' ImageOffset no + 'left_dark_rect' LeftDarkRect no + 'luminance_consts' LuminanceConsts no + 'mosaic_pattern' MosaicPattern no + 'name' CaptProfName no + 'number_of_planes' NumberOfPlanes no + 'raw_data_rotation' RawDataRotation no + 'reconstruction_type' ReconstructionType no + 'right_dark_rect' RightDarkRect no + 'serial_number' CaptureSerial no + 'type' CaptProfType no + 'version' CaptProfVersion no + 'xy_offset_info' XYOffsetInfo no + +=head3 Leaf ImageProfile Tags + +All B<Tag ID>'s in the following table have a leading 'ImgProf_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'back_type' ImgProfBackType no + 'image_status' ImageStatus no + 'name' ImgProfName no + 'rotation_angle' RotationAngle no + 'shoot_setup' ShootSetup Leaf ShootSetup + 'type' ImgProfType no + 'version' ImgProfVersion no + +=head3 Leaf ShootSetup Tags + +All B<Tag ID>'s in the following table have a leading 'ShootObj_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'back_type' ShootObjBackType no + 'camera_setup' CameraSetup Leaf CameraSetup + 'capture_setup' CaptureSetup Leaf CaptureSetup + 'color_setup' ColorSetup Leaf ColorSetup + 'look_header' LookHeader Leaf LookHeader + 'name' ShootObjName no + 'save_setup' SaveSetup Leaf SaveSetup + 'type' ShootObjType no + 'version' ShootObjVersion no + +=head3 Leaf CameraSetup Tags + +All B<Tag ID>'s in the following table have a leading 'CameraObj_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ISO_speed' ISOSpeed no + 'back_type' CameraObjBackType no + 'camera_type' CameraType no + 'lens_ID' LensID no + 'lens_type' LensType no + 'name' CameraObjName no + 'strobe' Strobe no + 'type' CameraObjType no + 'version' CameraObjVersion no + +=head3 Leaf CaptureSetup Tags + +All B<Tag ID>'s in the following table have a leading 'CaptureObj_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Multi_quality' MultiQuality no + 'back_type' CaptureObjBackType no + 'name' CaptureObjName no + 'neutals' Neutals Leaf Neutrals + 'selection' Selection Leaf Selection + 'sharpness' Sharpness Leaf Sharpness + 'single_quality' SingleQuality no + 'tone_curve' ToneCurve Leaf ToneCurve + 'type' CaptureObjType no + 'version' CaptureObjVersion no + +=head3 Leaf Neutrals Tags + +All B<Tag ID>'s in the following table have a leading 'NeutObj_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'back_type' NeutObjBackType no + 'color_casts' ColorCasts no + 'highlight_end_points' HighlightEndPoints no + 'name' NeutObjName no + 'neutrals' Neutrals no + 'shadow_end_points' ShadowEndPoints no + 'type' NeutObjType no + 'version' NeutObjVersion no + +=head3 Leaf Selection Tags + +All B<Tag ID>'s in the following table have a leading 'SelObj_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'back_type' SelObjBackType no + 'locks' Locks no + 'name' SelObjName no + 'orientation' Orientation no + 'rect' Rect no + 'resolution' Resolution no + 'scale' Scale no + 'type' SelObjType no + 'version' SelObjVersion no + +=head3 Leaf Sharpness Tags + +All B<Tag ID>'s in the following table have a leading 'SharpObj_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'back_type' SharpObjBackType no + 'data_len' DataLen no + 'name' SharpObjName no + 'sharp_info' SharpInfo no + 'sharp_method' SharpMethod no + 'type' SharpObjType no + 'version' SharpObjVersion no + +=head3 Leaf ToneCurve Tags + +All B<Tag ID>'s in the following table have a leading 'ToneObj_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'back_type' ToneObjBackType no + 'gamma' Gamma no + 'name' ToneObjName no + 'npts' Npts no + 'tones' Tones no + 'type' ToneObjType no + 'version' ToneObjVersion no + +=head3 Leaf ColorSetup Tags + +All B<Tag ID>'s in the following table have a leading 'ColorObj_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'back_type' ColorObjBackType no + 'color_mode' ColorMode no + 'color_type' ColorType no + 'has_ICC' HasICC no + 'input_profile' InputProfile no + 'name' ColorObjName no + 'output_profile' OutputProfile no + 'type' ColorObjType no + 'version' ColorObjVersion no + +=head3 Leaf LookHeader Tags + +All B<Tag ID>'s in the following table have a leading 'LookHead_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'back_type' LookHeadBackType no + 'name' LookHeadName no + 'type' LookHeadType no + 'version' LookHeadVersion no + +=head3 Leaf SaveSetup Tags + +All B<Tag ID>'s in the following table have a leading 'SaveObj_' which +has been removed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'back_type' SaveObjBackType no + 'leaf_auto_active' LeafAutoActive no + 'leaf_auto_base_name' LeafAutoBaseName no + 'leaf_hot_folder' LeafHotFolder no + 'leaf_open_proc_HDR' LeafOpenProcHDR no + 'leaf_output_file_type' LeafOutputFileType no + 'leaf_save_selection' LeafSaveSelection no + 'name' SaveObjName no + 'std_auto_active' StdAutoActive no + 'std_base_name' StdBaseName no + 'std_hot_folder' StdHotFolder no + 'std_open_in_photoshop' StdOpenInPhotoshop no + 'std_output_bit_depth' StdOutputBitDepth no + 'std_output_color_mode' StdOutputColorMode no + 'std_output_file_type' StdOutputFileType no + 'std_oxygen' StdOxygen no + 'std_save_selection' StdSaveSelection no + 'std_scaled_output' StdScaledOutput no + 'std_sharpen_output' StdSharpenOutput no + 'type' SaveObjType no + 'version' SaveObjVersion no + +=head3 Leaf SubIFD Tags + +Leaf writes a TIFF-format sub-IFD inside IFD0 of a MOS image. No tags in +this sub-IFD are currently known, except for tags 0x8602 and 0x8606 which +really shouldn't be here anyway (so they don't appear in the table below) +because they duplicate references to the same data from tags with the same +ID in IFD0. + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head2 Minolta Tags + +These tags are used by Minolta, Konica/Minolta as well as some Sony cameras. +Minolta doesn't make things easy for decoders because the meaning of some +tags and the location where some information is stored is different for +different camera models. (Take MinoltaQuality for example, which may be +located in 5 different places.) + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 MakerNoteVersion undef[4] + 0x0001 MinoltaCameraSettingsOld Minolta CameraSettings + 0x0003 MinoltaCameraSettings Minolta CameraSettings + 0x0004 MinoltaCameraSettings7D Minolta CameraSettings7D + 0x0010 CameraInfoA100 Minolta CameraInfoA100 + 0x0018 ISInfoA100 Minolta ISInfoA100 + ImageStabilization no + 0x0020 WBInfoA100 Minolta WBInfoA100 + 0x0040 CompressedImageSize int32u + 0x0081 PreviewImage undef + 0x0088 PreviewImageStart int32u* + 0x0089 PreviewImageLength int32u* + 0x0100 SceneMode int32u + 0x0101 ColorMode int32u + 0x0102 MinoltaQuality int32u + 0x0103 MinoltaQuality int32u + MinoltaImageSize int32u + 0x0104 FlashExposureComp rational64s + 0x0105 Teleconverter int32u + 0x0107 ImageStabilization int32u + 0x0109 RawAndJpgRecording int32u + 0x010a ZoneMatching int32u + 0x010b ColorTemperature int32u + 0x010c LensType int32u + 0x0111 ColorCompensationFilter int32s + 0x0112 WhiteBalanceFineTune int32u + 0x0113 ImageStabilization int32u + 0x0114 MinoltaCameraSettings5D Minolta CameraSettings5D + CameraSettingsA100 Minolta CameraSettingsA100 + 0x0115 WhiteBalance int32u + 0x0e00 PrintIM PrintIM + 0x0f00 MinoltaCameraSettings2 no + +=head3 Minolta CameraSettings Tags + +There is some variability in CameraSettings information between different +models (and sometimes even between different firmware versions), so this +information may not be as reliable as it should be. Because of this, tags +in the following tables are set to lower priority to prevent them from +superseding the values of same-named tags in other locations when duplicate +tags are disabled. + + Index4 Tag Name Writable + ------ -------- -------- + 1 ExposureMode int32u + 2 FlashMode int32u + 3 WhiteBalance int32u~ + 4 MinoltaImageSize int32u + 5 MinoltaQuality int32u + 6 DriveMode int32u + 7 MeteringMode int32u + 8 ISO int32u + 9 ExposureTime int32u + 10 FNumber int32u + 11 MacroMode int32u + 12 DigitalZoom int32u + 13 ExposureCompensation int32u + 14 BracketStep int32u + 16 IntervalLength int32u + 17 IntervalNumber int32u + 18 FocalLength int32u + 19 FocusDistance int32u + 20 FlashFired int32u + 21 MinoltaDate int32u + 22 MinoltaTime int32u + 23 MaxAperture int32u + 26 FileNumberMemory int32u + 27 LastFileNumber int32u + 28 ColorBalanceRed int32u + 29 ColorBalanceGreen int32u + 30 ColorBalanceBlue int32u + 31 Saturation int32u + 32 Contrast int32u + 33 Sharpness int32u + 34 SubjectProgram int32u + 35 FlashExposureComp int32u + 36 ISOSetting int32u + 37 MinoltaModelID int32u + 38 IntervalMode int32u + 39 FolderName int32u + 40 ColorMode int32u + 41 ColorFilter int32u + 42 BWFilter int32u + 43 InternalFlash int32u + 44 Brightness int32u + 45 SpotFocusPointX int32u + 46 SpotFocusPointY int32u + 47 WideFocusZone int32u + 48 FocusMode int32u + 49 FocusArea int32u + 50 DECPosition int32u + 51 ColorProfile int32u + 52 DataImprint int32u + 63 FlashMetering int32u + +=head3 Minolta CameraSettings7D Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 ExposureMode int16u + 2 MinoltaImageSize int16u + 3 MinoltaQuality int16u + 4 WhiteBalance int16u + 14 FocusMode int16u + 16 AFPoints int16u + 21 Flash int16u + 22 FlashMode int16u + 28 ISOSetting int16u + 30 ExposureCompensation int16s + 37 ColorSpace int16u + 38 Sharpness int16u + 39 Contrast int16u + 40 Saturation int16u + 45 FreeMemoryCardImages int16u + 63 ColorTemperature int16s + 64 HueAdjustment int16u + 70 Rotation int16u + 71 FNumber int16u + 72 ExposureTime int16u + 74 FreeMemoryCardImages int16u + 94 ImageNumber int16u + 96 NoiseReduction int16u + 98 ImageNumber2 int16u + 113 ImageStabilization int16u + 117 ZoneMatchingOn int16u + +=head3 Minolta CameraInfoA100 Tags + +Camera information for the Sony DSLR-A100. + + Index1 Tag Name Writable + ------ -------- -------- + 1 AFSensorActive int8u + 2 AFStatusActiveSensor int16s + 4 AFStatusTop-right int16s + 6 AFStatusBottom-right int16s + 8 AFStatusBottom int16s + 10 AFStatusMiddleHorizontal int16s + 12 AFStatusCenterVertical int16s + 14 AFStatusTop int16s + 16 AFStatusTop-left int16s + 18 AFStatusBottom-left int16s + 20 FocusLocked int8u + 21 AFPoint int8u + 22 AFMode int8u + 45 AFStatusLeft int16s + 47 AFStatusCenterHorizontal int16s + 49 AFStatusRight int16s + 51 AFAreaMode int8u + +=head3 Minolta ISInfoA100 Tags + +Image stabilization information for the Sony DSLR-A100. + + Index1 Tag Name Writable + ------ -------- -------- + 0 ImageStabilization int16u + +=head3 Minolta WBInfoA100 Tags + +White balance information for the Sony DSLR-A100. + + Index1 Tag Name Writable + ------ -------- -------- + 14 DriveMode int8u + 16 Rotation int8u + 20 ImageStabilizationSetting int8u + 21 DynamicRangeOptimizerMode int8u + 42 ExposureCompensationMode int8u + 43 WBBracketShotNumber int8u + 44 WhiteBalanceBracketing int8u + 45 ExposureBracketShotNumber int8u + 49 FlashFunction int16u + 52 ExposureMode int16u + 54 ColorMode int16u + 56 AverageLV int16u + 60 FrameNumber int8u + 150 WB_RGBLevels int16u[3] + 174 WB_GBRGLevels int16u[4] + 192 WB_RedLevelsTungsten int16u[7] + 206 WB_BlueLevelsTungsten int16u[7] + 220 WB_RedLevelsDaylight int16u[7] + 234 WB_BlueLevelsDaylight int16u[7] + 248 WB_RedLevelsCloudy int16u[7] + 262 WB_BlueLevelsCloudy int16u[7] + 276 WB_RedLevelsFlash int16u[7] + 290 WB_BlueLevelsFlash int16u[7] + 332 WB_RedLevelsFluorescent int16u[7] + 346 WB_BlueLevelsFluorescent int16u[7] + 360 WB_RedLevelsShade int16u[7] + 374 WB_BlueLevelsShade int16u[7] + 392 WB_RedLevel6500K int16u + 394 WB_BlueLevel6500K int16u + 396 WB_RedLevelCustom int16u + 398 WB_BlueLevelCustom int16u + 408 WB_RedLevel3500K int16u + 410 WB_BlueLevel3500K int16u + 446 WB_RedLevelsKelvin int16u[75] + 596 WB_BlueLevelsKelvin int16u[75] + 772 WB_RBLevelsFlash int16u[2] + 776 WB_RBLevelsCoolWhiteF int16u[2] + 1000 WB_RBLevelsTungsten int16u[2] + 1004 WB_RBLevelsDaylight int16u[2] + 1008 WB_RBLevelsCloudy int16u[2] + 1012 WB_RBLevelsFlash int16u[2] + 1020 WB_RedLevelsFluorescent int16u[7] + 1034 WB_BlueLevelsFluorescent int16u[7] + 1048 WB_RBLevelsShade int16u[2] + 1056 WB_RBLevels6500K int16u[2] + 1060 WB_RBLevelsCustom int16u[2] + 1072 WB_RBLevels3500K int16u[2] + 1320 WB_RBLevelsDaylight int16u[2] + 1350 WB_RGBLevels int16u[3] + 1576 AEMeteringSegments int8u[40] + 1680 MeasuredLV int8u + 1681 BrightnessValue int8u + 4172 TiffMeteringImage no + 18872 ExposureTime int8u + 18874 ISO int8u + 18875 FocusDistance int8u + 18877 LensType int16uRev + 18880 ExposureCompensation int8s + 18881 FlashExposureComp int8s + 18882 ImageStabilization int8u + 18883 BrightnessValue int8u + 18885 MaxAperture int8u + 18887 FNumber int8u + 18908 InternalSerialNumber string[12] + +=head3 Minolta CameraSettings5D Tags + + Index2 Tag Name Writable + ------ -------- -------- + 10 ExposureMode int16u + 12 MinoltaImageSize int16u + 13 MinoltaQuality int16u + 14 WhiteBalance int16u + 31 Flash int16u + 32 FlashMode int16u + 37 MeteringMode int16u + 38 ISOSetting int16u + 47 ColorSpace int16u + 48 Sharpness int16u + 49 Contrast int16u + 50 Saturation int16u + 53 ExposureTime int16u + 54 FNumber int16u + 55 FreeMemoryCardImages int16u + 73 ColorTemperature int16s + 74 HueAdjustment int16u + 80 Rotation int16u + 83 ExposureCompensation int16u + 84 FreeMemoryCardImages int16u + 101 Rotation int16u + 110 ColorTemperature int16s + 113 PictureFinish int16u + 174 ImageNumber int16u + 176 NoiseReduction int16u + 189 ImageStabilization int16u + +=head3 Minolta CameraSettingsA100 Tags + +Camera settings information for the Sony DSLR-A100. + + Index2 Tag Name Writable + ------ -------- -------- + 0 ExposureMode int16u + 1 ExposureCompensationSetting int16u + 5 HighSpeedSync int16u + 6 ShutterSpeedSetting int16u + 7 ApertureSetting int16u + 8 ExposureTime int16u + 9 FNumber int16u + 10 DriveMode2 int16u + 11 WhiteBalance int16u + 12 FocusMode int16u + 13 AFPointSelected int16u + 14 AFAreaMode int16u + 15 FlashMode int16u + 16 FlashExposureCompSet int16u + 18 MeteringMode int16u + 19 ISOSetting int16u + 20 ZoneMatchingMode int16u + 21 DynamicRangeOptimizer int16u + 22 ColorMode int16u + 23 ColorSpace int16u + 24 Sharpness int16u + 25 Contrast int16u + 26 Saturation int16u + 28 FlashMetering int16u + 29 PrioritySetupShutterRelease int16u + 30 DriveMode int16u + 31 SelfTimerTime int16u + 32 ContinuousBracketing int16u + 33 SingleFrameBracketing int16u + 34 WhiteBalanceBracketing int16u + 35 WhiteBalanceSetting int16u + 36 PresetWhiteBalance int16u + 37 ColorTemperatureSetting int16u + 38 CustomWBSetting int16u + 39 DynamicRangeOptimizerSetting int16u + 50 FreeMemoryCardImages int16u + 52 CustomWBRedLevel int16u + 53 CustomWBGreenLevel int16u + 54 CustomWBBlueLevel int16u + 55 CustomWBError int16u + 56 WhiteBalanceFineTune int16s + 57 ColorTemperature int16u + 58 ColorCompensationFilter int16s + 59 SonyImageSize int16u + 60 SonyQuality int16u + 61 InstantPlaybackTime int16u + 62 InstantPlaybackSetup int16u + 63 NoiseReduction int16u + 64 EyeStartAF int16u + 65 RedEyeReduction int16u + 66 FlashDefault int16u + 67 AutoBracketOrder int16u + 68 FocusHoldButton int16u + 69 AELButton int16u + 70 ControlDialSet int16u + 71 ExposureCompensationMode int16u + 72 AFAssist int16u + 73 CardShutterLock int16u + 74 LensShutterLock int16u + 75 AFAreaIllumination int16u + 76 MonitorDisplayOff int16u + 77 RecordDisplay int16u + 78 PlayDisplay int16u + 80 ExposureIndicator int16u + 81 AELExposureIndicator int16u + 82 ExposureBracketingIndicatorLast int16u + 83 MeteringOffScaleIndicator int16u + 84 FlashExposureIndicator int16u + 85 FlashExposureIndicatorNext int16u + 86 FlashExposureIndicatorLast int16u + 87 ImageStabilization int16u + 88 FocusModeSwitch int16u + 89 FlashType int16u + 90 Rotation int16u + 91 AELock int16u + 94 ColorTemperature int16u + 95 ColorCompensationFilter int16s + 96 BatteryState int16u + +=head3 Minolta MMA Tags + +This information is found in MOV videos from Minolta models such as the +DiMAGE A2, S414 and 7Hi. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 20 SoftwareVersion no + +=head3 Minolta MOV1 Tags + +This information is found in MOV videos from some Konica Minolta models such +as the DiMage Z10 and X50. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 32 ModelType no + 46 ExposureTime no + 50 FNumber no + 58 ExposureCompensation no + 80 FocalLength no + +=head3 Minolta MOV2 Tags + +This information is found in MOV videos from some Minolta models such as the +DiMAGE X and Xt. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 24 ModelType no + 38 ExposureTime no + 42 FNumber no + 50 ExposureCompensation no + 72 FocalLength no + +=head2 Motorola Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x5500 BuildNumber string + 0x5501 SerialNumber string + 0x6420 CustomRendered string + 0x64d0 DriveMode string + 0x665e Sensor string + 0x6705 ManufactureDate string + +=head2 Nikon Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 MakerNoteVersion undef[4] + 0x0002 ISO int16u[2] + 0x0003 ColorMode string + 0x0004 Quality string + 0x0005 WhiteBalance string + 0x0006 Sharpness string + 0x0007 FocusMode string + 0x0008 FlashSetting string + 0x0009 FlashType string + 0x000b WhiteBalanceFineTune int16s[n] + 0x000c WB_RBLevels rational64u[4] + 0x000d ProgramShift undef[4] + 0x000e ExposureDifference undef[4] + 0x000f ISOSelection string + 0x0010 DataDump no + 0x0011 PreviewIFD Nikon PreviewIFD + 0x0012 FlashExposureComp undef[4] + 0x0013 ISOSetting int16u[2] + 0x0014 ColorBalanceA Nikon ColorBalanceA + NRWData Nikon ColorBalanceB + Nikon ColorBalanceC + 0x0016 ImageBoundary int16u[4] + 0x0017 ExternalFlashExposureComp undef[4] + 0x0018 FlashExposureBracketValue undef[4] + 0x0019 ExposureBracketValue rational64s + 0x001a ImageProcessing string + 0x001b CropHiSpeed int16u[7] + 0x001c ExposureTuning undef[3] + 0x001d SerialNumber string! + 0x001e ColorSpace int16u + 0x001f VRInfo Nikon VRInfo + 0x0020 ImageAuthentication int8u + 0x0021 FaceDetect Nikon FaceDetect + 0x0022 ActiveD-Lighting int16u + 0x0023 PictureControlData Nikon PictureControl + Nikon PictureControl2 + Nikon PictureControl3 + Nikon PictureControlUnknown + 0x0024 WorldTime Nikon WorldTime + 0x0025 ISOInfo Nikon ISOInfo + 0x002a VignetteControl int16u + 0x002b DistortInfo Nikon DistortInfo + 0x002c UnknownInfo Nikon UnknownInfo + 0x0032 UnknownInfo2 Nikon UnknownInfo2 + 0x0034 ShutterMode int16u + 0x0035 HDRInfo Nikon HDRInfo + HDRInfo2 Nikon HDRInfo2 + 0x0037 MechanicalShutterCount int32u + 0x0039 LocationInfo Nikon LocationInfo + 0x003d BlackLevel int16u[4] + 0x003e ImageSizeRAW yes + 0x003f WhiteBalanceFineTune rational64s[2] + 0x0044 JPGCompression yes + 0x0045 CropArea int16u[4] + 0x004e NikonSettings NikonSettings + 0x004f ColorTemperatureAuto int16u + 0x0080 ImageAdjustment string + 0x0081 ToneComp string + 0x0082 AuxiliaryLens string + 0x0083 LensType int8u + 0x0084 Lens rational64u[4] + 0x0085 ManualFocusDistance rational64u + 0x0086 DigitalZoom rational64u + 0x0087 FlashMode int8u + 0x0088 AFInfo Nikon AFInfo + Nikon AFInfo + 0x0089 ShootingMode int16u~ + 0x008b LensFStops undef[4] + 0x008c ContrastCurve undef! + 0x008d ColorHue string + 0x008f SceneMode string + 0x0090 LightSource string + 0x0091 ShotInfoD40 Nikon ShotInfoD40 + ShotInfoD80 Nikon ShotInfoD80 + ShotInfoD90 Nikon ShotInfoD90 + ShotInfoD3a Nikon ShotInfoD3a + ShotInfoD3b Nikon ShotInfoD3b + ShotInfoD3X Nikon ShotInfoD3X + ShotInfoD3S Nikon ShotInfoD3S + ShotInfoD300a Nikon ShotInfoD300a + ShotInfoD300b Nikon ShotInfoD300b + ShotInfoD300S Nikon ShotInfoD300S + ShotInfoD700 Nikon ShotInfoD700 + ShotInfoD780 Nikon ShotInfoD780 + ShotInfoD7500 Nikon ShotInfoD7500 + ShotInfoD800 Nikon ShotInfoD800 + ShotInfoD810 Nikon ShotInfoD810 + ShotInfoD850 Nikon ShotInfoD850 + ShotInfoD5000 Nikon ShotInfoD5000 + ShotInfoD5100 Nikon ShotInfoD5100 + ShotInfoD5200 Nikon ShotInfoD5200 + ShotInfoD7000 Nikon ShotInfoD7000 + ShotInfoD4 Nikon ShotInfoD4 + ShotInfoD4S Nikon ShotInfoD4S + ShotInfoD500 Nikon ShotInfoD500 + ShotInfoD6 Nikon ShotInfoD6 + ShotInfoD610 Nikon ShotInfoD610 + ShotInfoZ7II Nikon ShotInfoZ7II + ShotInfoZ8 Nikon ShotInfoZ8 + ShotInfoZ9 Nikon ShotInfoZ9 + ShotInfo02xx Nikon ShotInfo + ShotInfoUnknown Nikon ShotInfo + 0x0092 HueAdjustment int16s + 0x0093 NEFCompression int16u + 0x0094 SaturationAdj int16s + 0x0095 NoiseReduction string + 0x0096 NEFLinearizationTable undef! + 0x0097 ColorBalance0100 Nikon ColorBalance1 + ColorBalance0102 Nikon ColorBalance2 + ColorBalance0103 Nikon ColorBalance3 + ColorBalance0205 Nikon ColorBalance2 + ColorBalance0209 Nikon ColorBalance4 + ColorBalance02 Nikon ColorBalance2 + ColorBalance0211 Nikon ColorBalance4 + ColorBalance0213 Nikon ColorBalance2 + ColorBalance0215 Nikon ColorBalance4 + ColorBalanceUnknown02 Nikon ColorBalanceUnknown + ColorBalanceUnknown04 Nikon ColorBalanceUnknown + ColorBalanceUnknown Nikon ColorBalanceUnknown + 0x0098 LensData0100 Nikon LensData00 + LensData0101 Nikon LensData01 + LensData0201 Nikon LensData01 + LensData0204 Nikon LensData0204 + LensData0400 Nikon LensData0400 + LensData0402 Nikon LensData0402 + LensData0403 Nikon LensData0403 + LensData0800 Nikon LensData0800 + LensDataUnknown Nikon LensDataUnknown + 0x0099 RawImageCenter int16u[2] + 0x009a SensorPixelSize rational64u[2] + 0x009c SceneAssist string + 0x009d DateStampMode int16u + 0x009e RetouchHistory int16u[10] + 0x00a0 SerialNumber string + 0x00a2 ImageDataSize int32u + 0x00a5 ImageCount int32u + 0x00a6 DeletedImageCount int32u + 0x00a7 ShutterCount int32u! + 0x00a8 FlashInfo0100 Nikon FlashInfo0100 + FlashInfo0102 Nikon FlashInfo0102 + FlashInfo0103 Nikon FlashInfo0103 + FlashInfo0106 Nikon FlashInfo0106 + FlashInfo0107 Nikon FlashInfo0107 + FlashInfo0300 Nikon FlashInfo0300 + FlashInfoUnknown Nikon FlashInfoUnknown + 0x00a9 ImageOptimization string + 0x00aa Saturation string + 0x00ab VariProgram string + 0x00ac ImageStabilization string + 0x00ad AFResponse string + 0x00b0 MultiExposure Nikon MultiExposure + MultiExposure2 Nikon MultiExposure + Nikon MultiExposure2 + 0x00b1 HighISONoiseReduction int16u + 0x00b3 ToningEffect string + 0x00b6 PowerUpTime undef + 0x00b7 AFInfo2 Nikon AFInfo2V0400 + Nikon AFInfo2 + 0x00b8 FileInfo Nikon FileInfo + Nikon FileInfo + 0x00b9 AFTune Nikon AFTune + 0x00bb RetouchInfo Nikon RetouchInfo + 0x00bd PictureControlData Nikon PictureControl + 0x00bf SilentPhotography yes + 0x00c3 BarometerInfo Nikon BarometerInfo + 0x0e00 PrintIM PrintIM + 0x0e01 NikonCaptureData NikonCapture + 0x0e09 NikonCaptureVersion string^ + 0x0e0e NikonCaptureOffsets Nikon CaptureOffsets + 0x0e10 NikonScanIFD Nikon Scan + 0x0e13 NikonCaptureEditVersions NikonCapture + NikonCaptureEditVersions undef!^ + 0x0e1d NikonICCProfile ICC_Profile + 0x0e1e NikonCaptureOutput Nikon CaptureOutput + 0x0e22 NEFBitDepth int16u[4]! + +=head3 Nikon ast Tags + +Tags used by Nikon NX Studio in Nikon NKSC sidecar files and trailers. + +These tags belong to the ExifTool XMP-ast family 1 group. + + Tag Name Writable + -------- -------- + About no + GPSAltitude no + GPSAltitudeRef no + GPSImgDirection no + GPSImgDirectionRef no + GPSLatitude no + GPSLatitudeRef no + GPSLongitude no + GPSLongitudeRef no + GPSMapDatum no + GPSVersionID no + IPTC IPTC + Version no + XMLPackets XMP + +=head3 Nikon nine Tags + +These tags belong to the ExifTool XMP-nine family 1 group. + + Tag Name Writable + -------- -------- + About no + Label no + NineEdits Nikon NineEdits + Rating no + Trim no + Version no + +=head3 Nikon NineEdits Tags + +XML-based tags used to store editing information. + + Tag Name Writable + -------- -------- + FilterParametersBinary no + FilterParametersCustomCustomData no + FilterParametersExportExportData no + +=head3 Nikon sdc Tags + +These tags belong to the ExifTool XMP-sdc family 1 group. + + Tag Name Writable + -------- -------- + About no + AppName no + AppVersion no + Version no + +=head3 Nikon PreviewIFD Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x00fe SubfileType no + 0x0103 Compression no + 0x011a XResolution no + 0x011b YResolution no + 0x0128 ResolutionUnit no + 0x0201 PreviewImageStart int32u* + 0x0202 PreviewImageLength int32u* + 0x0213 YCbCrPositioning no + +=head3 Nikon ColorBalanceA Tags + + Index2 Tag Name Writable + ------ -------- -------- + 624 WB_RBLevels int16u[2]! + 626 WB_RBLevelsAuto int16u[2]! + 628 WB_RBLevelsDaylight int16u[14]! + 642 WB_RBLevelsIncandescent int16u[14]! + 656 WB_RBLevelsFluorescent int16u[6]! + 662 WB_RBLevelsCloudy int16u[14]! + 676 WB_RBLevelsFlash int16u[14]! + 690 WB_RBLevelsShade int16u[14]! + +=head3 Nikon ColorBalanceB Tags + +Color balance tags used by the P6000. + + Index1 Tag Name Writable + ------ -------- -------- + 4 ColorBalanceVersion undef[4] + 5096 WB_RGGBLevels int32u[4]! + 5112 WB_RGGBLevelsDaylight int32u[4]! + 5128 WB_RGGBLevelsCloudy int32u[4]! + 5160 WB_RGGBLevelsTungsten int32u[4]! + 5176 WB_RGGBLevelsFluorescentW int32u[4]! + 5192 WB_RGGBLevelsFlash int32u[4]! + 5224 WB_RGGBLevelsCustom int32u[4]! + 5240 WB_RGGBLevelsAuto int32u[4]! + +=head3 Nikon ColorBalanceC Tags + +Color balance tags used by the P1000, P7000, P7100 and B700. + + Index1 Tag Name Writable + ------ -------- -------- + 4 ColorBalanceVersion undef[4] + 32 BlackLevel int16u + 56 WB_RGGBLevels int32u[4]! + 76 WB_RGGBLevelsDaylight int32u[4]! + 96 WB_RGGBLevelsCloudy int32u[4]! + 116 WB_RGGBLevelsShade int32u[4]! + 136 WB_RGGBLevelsTungsten int32u[4]! + 156 WB_RGGBLevelsFluorescentW int32u[4]! + 176 WB_RGGBLevelsFluorescentN int32u[4]! + 196 WB_RGGBLevelsFluorescentD int32u[4]! + 216 WB_RGGBLevelsHTMercury int32u[4]! + 256 WB_RGGBLevelsCustom int32u[4]! + 276 WB_RGGBLevelsAuto int32u[4]! + +=head3 Nikon VRInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 VRInfoVersion no + 4 VibrationReduction int8u + 6 VRMode int8u + 8 VRType int8u + +=head3 Nikon FaceDetect Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 FaceDetectFrameSize int16u[2] + 3 FacesDetected int16u + 4 Face1Position int16u[4] + 8 Face2Position int16u[4] + 12 Face3Position int16u[4] + 16 Face4Position int16u[4] + 20 Face5Position int16u[4] + 24 Face6Position int16u[4] + 28 Face7Position int16u[4] + 32 Face8Position int16u[4] + 36 Face9Position int16u[4] + 40 Face10Position int16u[4] + 44 Face11Position int16u[4] + 48 Face12Position int16u[4] + +=head3 Nikon PictureControl Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 PictureControlVersion no + 4 PictureControlName string[20] + 24 PictureControlBase string[20] + 48 PictureControlAdjust int8u + 49 PictureControlQuickAdjust int8u + 50 Sharpness int8u + 51 Contrast int8u + 52 Brightness int8u + 53 Saturation int8u + 54 HueAdjustment int8u + 55 FilterEffect int8u + 56 ToningEffect int8u + 57 ToningSaturation int8u + +=head3 Nikon PictureControl2 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 PictureControlVersion no + 4 PictureControlName string[20] + 24 PictureControlBase string[20] + 48 PictureControlAdjust int8u + 49 PictureControlQuickAdjust int8u + 51 Sharpness int8u + 53 Clarity int8u + 55 Contrast int8u + 57 Brightness int8u + 59 Saturation int8u + 61 Hue int8u + 63 FilterEffect int8u + 64 ToningEffect int8u + 65 ToningSaturation int8u + +=head3 Nikon PictureControl3 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 PictureControlVersion no + 8 PictureControlName string[20] + 28 PictureControlBase string[20] + 54 PictureControlAdjust int8u + 55 PictureControlQuickAdjust int8u + 57 Sharpness int8u + 59 MidRangeSharpness int8u + 61 Clarity int8u + 63 Contrast int8u + 65 Brightness int8u + 67 Saturation int8u + 69 Hue int8u + 71 FilterEffect int8u + 72 ToningEffect int8u + 73 ToningSaturation int8u + +=head3 Nikon PictureControlUnknown Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 PictureControlVersion no + +=head3 Nikon WorldTime Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 TimeZone int16s + 2 DaylightSavings int8u + 3 DateDisplayFormat int8u + +=head3 Nikon ISOInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ISO int8u + 4 ISOExpansion int16u + 6 ISO2 int8u + 10 ISOExpansion2 int16u + +=head3 Nikon DistortInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 DistortionVersion? no + 4 AutoDistortionControl int8u + +=head3 Nikon UnknownInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 UnknownInfoVersion? no + +=head3 Nikon UnknownInfo2 Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 UnknownInfo2Version? no + +=head3 Nikon HDRInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 HDRInfoVersion no + 4 HDR int8u + 5 HDRLevel int8u + 6 HDRSmoothing int8u + 7 HDRLevel2 int8u + +=head3 Nikon HDRInfo2 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 HDRInfoVersion no + 4 HDR int8u + 5 HDRLevel int8u + +=head3 Nikon LocationInfo Tags + +Tags written by some Nikon GPS-equipped cameras like the AW100. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LocationInfoVersion undef[4] + 4 TextEncoding int8u + 5 CountryCode undef[3] + 8 POILevel int8u + 9 Location undef[70] + +=head3 Nikon MakerNotes0x51 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FirmwareVersion51 no + 10 NEFCompression int16u[0.5] + +=head3 Nikon MakerNotes0x56 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FirmwareVersion no + 4 BurstGroupID int16u + +=head3 Nikon AFInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 AFAreaMode int8u + 1 AFPoint int8u + 2 AFPointsInFocus int16u + +=head3 Nikon ShotInfoD40 Tags + +These tags are extracted from encrypted data in D40 and D40X images. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 582 ShutterCount int32u + 586.1 VibrationReduction int8u & 0x08 + 729 CustomSettingsD40 NikonCustom SettingsD40 + +=head3 Nikon ShotInfoD80 Tags + +These tags are extracted from encrypted data in D80 images. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 586 ShutterCount int32u + 590.1 Rotation int8u & 0x07 + 590.2 VibrationReduction int8u & 0x18 + 590.3 FlashFired int8u & 0xe0 + 708 NikonImageSize int8u & 0xf0 + 708.1 ImageQuality int8u & 0x0f + 748 CustomSettingsD80 NikonCustom SettingsD80 + +=head3 Nikon ShotInfoD90 Tags + +These tags are extracted from encrypted data in images from the D90 with +firmware 1.00. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 693 ISO2 int8u + 725 ShutterCount int32u + 884 CustomSettingsD90 NikonCustom SettingsD90 + +=head3 Nikon ShotInfoD3a Tags + +These tags are extracted from encrypted data in images from the D3 with +firmware 1.00 and earlier. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 598 ISO2 int8u + 630 ShutterCount int32u + 723.1 NikonImageSize int8u & 0x18 + 723.2 ImageQuality int8u & 0x07 + 769 CustomSettingsD3 NikonCustom SettingsD3 + +=head3 Nikon ShotInfoD3b Tags + +These tags are extracted from encrypted data in images from the D3 with +firmware 1.10, 2.00, 2.01 and 2.02. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 16 ImageArea int8u + 605 ISO2 int8u + 637 ShutterCount int32u + 639 ShutterCount int32u + 650 PreFlashReturnStrength int8u + 732.1 NikonImageSize int8u & 0x18 + 732.2 ImageQuality int8u & 0x07 + 778 CustomSettingsD3 NikonCustom SettingsD3 + +=head3 Nikon ShotInfoD3X Tags + +These tags are extracted from encrypted data in images from the D3X with +firmware 1.00. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 605 ISO2 int8u + 640 ShutterCount int32u + 779 CustomSettingsD3X NikonCustom SettingsD3 + +=head3 Nikon ShotInfoD3S Tags + +These tags are extracted from encrypted data in images from the D3S with +firmware 1.00 and earlier. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 16 ImageArea int8u + 545 ISO2 int8u + 578 ShutterCount int32u + 718 CustomSettingsD3S NikonCustom SettingsD3 + +=head3 Nikon ShotInfoD300a Tags + +These tags are extracted from encrypted data in images from the D300 with +firmware 1.00 and earlier. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 604 ISO2 int8u + 633 ShutterCount int32u + 721 AFFineTuneAdj int16u + 790 CustomSettingsD300 NikonCustom SettingsD3 + +=head3 Nikon ShotInfoD300b Tags + +These tags are extracted from encrypted data in images from the D300 with +firmware 1.10. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 613 ISO2 int8u + 644 ShutterCount int32u + 732 AFFineTuneAdj int16u + 802 CustomSettingsD300 NikonCustom SettingsD3 + +=head3 Nikon ShotInfoD300S Tags + +These tags are extracted from encrypted data in images from the D300S with +firmware 1.00. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 613 ISO2 int8u + 646 ShutterCount int32u + 804 CustomSettingsD300S NikonCustom SettingsD3 + +=head3 Nikon ShotInfoD700 Tags + +These tags are extracted from encrypted data in images from the D700 with +firmware 1.02f. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 613 ISO2 int8u + 647 ShutterCount int32u + 804 CustomSettingsD700 NikonCustom SettingsD700 + +=head3 Nikon ShotInfoD780 Tags + +These tags are extracted from encrypted data in images from the D780. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 156 OrientOffset Nikon OrientationInfo + +=head3 Nikon OrientationInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 RollAngle fixed32u + 4 PitchAngle fixed32u + 8 YawAngle fixed32u + +=head3 Nikon ShotInfoD7500 Tags + +These tags are extracted from encrypted data in images from the D7500. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 160 OrientOffset Nikon OrientationInfo + +=head3 Nikon ShotInfoD800 Tags + +These tags are extracted from encrypted data in images from the D800. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 1216 RepeatingFlashOutputExternal int8u + 1218 RepeatingFlashRateExternal int8u + 1219 RepeatingFlashCountExternal int8u + 1234 FlashExposureComp2 int8s + 1242 RepeatingFlashRateBuilt-in int8u + 1243 RepeatingFlashCountBuilt-in int8u + 1308 SequenceNumber int8u + 1531 ShutterCount int32u + 1772 CustomSettingsD800 NikonCustom SettingsD800 + +=head3 Nikon ShotInfoD810 Tags + +These tags are extracted from encrypted data in images from the D810. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 16 SettingsOffset Nikon SettingsInfoD810 + 36 BracketingOffset Nikon BracketingInfoD810 + 56 ISOAutoOffset Nikon ISOAutoInfoD810 + 64 CustomSettingsOffset NikonCustom SettingsD810 + 132 OrientationOffset Nikon OrientationInfo + +=head3 Nikon SettingsInfoD810 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 316 SecondarySlotFunction int8u & 0x03 + +=head3 Nikon BracketingInfoD810 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 15 AEBracketingSteps int8u & 0xff + 16 WBBracketingSteps int8u & 0xff + 23 NikonMeteringMode int8u & 0x03 + +=head3 Nikon ISOAutoInfoD810 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 ISOAutoShutterTime int8u & 0x3f + 5 ISOAutoHiLimit int8u & 0xff + +=head3 Nikon ShotInfoD850 Tags + +These tags are extracted from encrypted data in images from the D850. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 16 MenuSettingsOffset Nikon MenuSettingsD850 + 76 MoreSettingsOffset Nikon MoreSettingsD850 + 88 CustomSettingsOffset NikonCustom SettingsD850 + 160 OrientationOffset Nikon OrientationInfo + +=head3 Nikon MenuSettingsD850 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1757 PhotoShootingMenuBankImageArea int8u & 0x07 + +=head3 Nikon MoreSettingsD850 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 36 PhotoShootingMenuBank int8u & 0x03 + 37 PrimarySlot int8u & 0x80 + +=head3 Nikon ShotInfoD5000 Tags + +These tags are extracted from encrypted data in images from the D5000 with +firmware 1.00. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 693 ISO2 int8u + 726 ShutterCount int32u + 888 CustomSettingsD5000 NikonCustom SettingsD5000 + +=head3 Nikon ShotInfoD5100 Tags + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 801 ShutterCount int32u + 1031 CustomSettingsD5100 NikonCustom SettingsD5100 + +=head3 Nikon ShotInfoD5200 Tags + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 3032 ShutterCount int32u + 3285 CustomSettingsD5200 NikonCustom SettingsD5200 + +=head3 Nikon ShotInfoD7000 Tags + +These tags are extracted from encrypted data in images from the D7000 with +firmware 1.01b. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 800 ShutterCount int32u + 1028 CustomSettingsD7000 NikonCustom SettingsD7000 + +=head3 Nikon ShotInfoD4 Tags + +These tags are extracted from encrypted data in images from the D4. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 1873 CustomSettingsD4 NikonCustom SettingsD4 + +=head3 Nikon ShotInfoD4S Tags + +These tags are extracted from encrypted data in images from the D4S. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 464 SecondarySlotFunction int8u & 0x03 + 5964 AEBracketingSteps int8u & 0xff + 5965 WBBracketingSteps int8u & 0xff + 6221 ReleaseMode int8u & 0xff + 6301 CustomSettingsD4S NikonCustom SettingsD4 + 6338 MultiSelectorLiveViewMode int8u & 0xc0 + 6378 ISOAutoShutterTime int8u & 0x3f + 6379 ISOAutoHiLimit int8u & 0xff + 6461 CustomSettingsD4S NikonCustom SettingsD4 + 13579 OrientationInfo Nikon OrientationInfo + 13971 Rotation int8u & 0x30 + +=head3 Nikon ShotInfoD500 Tags + +These tags are extracted from encrypted data in images from the D5 and D500. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 16 RotationInfoOffset Nikon RotationInfoD500 + 20 JPGInfoOffset Nikon JPGInfoD500 + 44 BracketingOffset Nikon BracketingInfoD500 + 80 ShootingMenuOffset Nikon ShootingMenuD500 + 88 CustomSettingsOffset Nikon CustomSettingsD500 + 160 OrientationOffset Nikon OrientationInfo + 168 OtherOffset Nikon OtherInfoD500 + +=head3 Nikon RotationInfoD500 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 26 Rotation int8u & 0x03 + 32 Interval int8u~ + 36 IntervalFrame int8u~ + 1330 FlickerReductionIndicator int8u & 0x01 + +=head3 Nikon JPGInfoD500 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 36 JPGCompression int8u & 0x01 + +=head3 Nikon BracketingInfoD500 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 15 AEBracketingSteps int8u & 0xff + 16 WBBracketingSteps int8u & 0xff + 23 ADLBracketingStep int8u & 0xf0 + 24 ADLBracketingType int8u & 0x0f + +=head3 Nikon ShootingMenuD500 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 PhotoShootingMenuBank int8u & 0x03 + 2 PrimarySlot int8u & 0x80 + 4 ISOAutoShutterTime int8u & 0x3f + 5 ISOAutoHiLimit int8u & 0xff + 7 FlickerReduction int8u & 0x20 + 7.1 PhotoShootingMenuBankImageArea int8u & 0x07 + +=head3 Nikon CustomSettingsD500 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 CustomSettingsD5 NikonCustom SettingsD5 + CustomSettingsD500 NikonCustom SettingsD500 + +=head3 Nikon OtherInfoD500 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 532 NikonMeteringMode int8u & 0x03 + +=head3 Nikon ShotInfoD6 Tags + +These tags are extracted from encrypted data in images from the D6. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 48 SequenceOffset Nikon SeqInfoD6 + 156 OrientationOffset Nikon OrientationInfo + 164 IntervalOffset Nikon IntervalInfoD6 + +=head3 Nikon SeqInfoD6 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 36 IntervalShooting int16u~ + +=head3 Nikon IntervalInfoD6 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 380 Intervals int32u + 384 ShotsPerInterval int32u + 388 IntervalExposureSmoothing int8u + 390 IntervalPriority int8u + 424 FocusShiftNumberShots int8u + 428 FocusShiftStepWidth int8u + 432 FocusShiftInterval int8u~ + 436 FocusShiftExposureLock int8u + 526 DiffractionCompensation int8u + 532 FlashControlMode int8u + 538 FlashGNDistance? no + 542 FlashOutput? int8u + 552 FlashRemoteControl? int8u + 556 FlashMasterControlMode int8u + 558 FlashMasterCompensation? int8s + 562 FlashMasterOutput? int8u + 564 FlashWirelessOption? int8u + 714 MovieType? int8u + +=head3 Nikon ShotInfoD610 Tags + +These tags are extracted from encrypted data in images from the D610. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 1999 CustomSettingsD610 NikonCustom SettingsD610 + +=head3 Nikon ShotInfoZ7II Tags + +These tags are extracted from encrypted data in images from the Z7II. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 48 IntervalOffset Nikon IntervalInfoZ7II + 56 PortraitOffset Nikon PortraitInfoZ7II + 152 OrientationOffset Nikon OrientationInfo + 160 MenuOffset Nikon MenuInfoZ7II + +=head3 Nikon IntervalInfoZ7II Tags + + Index1 Tag Name Writable + ------ -------- -------- + 36 IntervalShooting int16u~ + +=head3 Nikon PortraitInfoZ7II Tags + + Index1 Tag Name Writable + ------ -------- -------- + 160 PortraitImpressionBalance int8u[2]~ + +=head3 Nikon MenuInfoZ7II Tags + + Index1 Tag Name Writable + ------ -------- -------- + 16 MenuSettingsOffsetZ7II Nikon MenuSettingsZ7II + +=head3 Nikon MenuSettingsZ7II Tags + +These tags are used by the Z5, Z6, Z7, Z6II, Z7II, Z50 and Zfc. + + Index1 Tag Name Writable + ------ -------- -------- + 92 ReleaseMode no + 160 IntervalDurationHours int32u + 164 IntervalDurationMinutes int32u + 168 IntervalDurationSeconds int32u + 176 Intervals int32u + 180 ShotsPerInterval int32u + 184 IntervalExposureSmoothing int8u + 186 IntervalPriority int8u + 220 FocusShiftNumberShots int8u + 224 FocusShiftStepWidth int8u + 228 FocusShiftInterval int8u~ + 232 FocusShiftExposureLock int8u + 322 DiffractionCompensation int8u + 323 AutoDistortionControl int8u + 326 NikonMeteringMode int8u + 328 FlashControlMode int8u + 334 FlashGNDistance? no + 338 FlashOutput? int8u + 346 FlashWirelessOption? int8u + 348 FlashRemoteControl? int8u + 352 FlashMasterControlMode int8u + 354 FlashMasterCompensation? int8s + 358 FlashMasterOutput? int8u + 502 MovieFrameSize? int8u + 504 MovieFrameRate? int8u + 506 MovieSlowMotion? int8u + 510 MovieType? int8u + 516 MovieISOAutoManualMode? int16u + 568 MovieActiveD-Lighting? int8u + 572 MovieHighISONoiseReduction? int8u + 574 MovieVignetteControl? int8u + 576 MovieVignetteControlSameAsPhoto? int8u + 577 MovieDiffractionCompensation? int8u + 578 MovieAutoDistortionControl? int8u + 584 MovieFocusMode? int8u + 590 MovieVibrationReduction? int8u + 591 MovieVibrationReductionSameAsPhoto? int8u + 858 HDMIOutputN-Log? int8u + +=head3 Nikon ShotInfoZ8 Tags + +These tags are extracted from encrypted data in images from the Z8. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 48 SequenceOffset Nikon SeqInfoZ9 + 132 OrientOffset Nikon OrientationInfo + 140 MenuOffset Nikon MenuInfoZ8 + +=head3 Nikon SeqInfoZ9 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 32 FocusShiftShooting int8u~ + 40 IntervalShooting int16u~ + +=head3 Nikon MenuInfoZ8 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 16 MenuSettingsOffsetZ8 Nikon MenuSettingsZ8 + +=head3 Nikon MenuSettingsZ8 Tags + +These tags are used by the Z8 firmware 1.00. + + Index1 Tag Name Writable + --------------- -------- + 72 HighFrameRate int8u + 152 MultipleExposureMode int8u + 154 MultiExposureShots int8u + 184 IntervalDurationHours int32u + 188 IntervalDurationMinutes int32u + 192 IntervalDurationSeconds int32u + 200 Intervals int32u + 204 ShotsPerInterval int32u + 208 IntervalExposureSmoothing int8u + 210 IntervalPriority int8u + 244 FocusShiftNumberShots int8u + 248 FocusShiftStepWidth int8u + 252 FocusShiftInterval int8u~ + 256 FocusShiftExposureLock? int8u + 286 PhotoShootingMenuBank int8u + 288 ExtendedMenuBanks int8u + 324 PhotoShootingMenuBankImageArea int8u + 338 AutoISO int8u + 340 ISOAutoHiLimit? int16u + 342 ISOAutoFlashLimit? int16u + 350 ISOAutoShutterTime no + 432 MovieVignetteControl? int8u + 434 DiffractionCompensation int8u + 436 FlickerReductionShooting int8u + 440 FlashControlMode int8u + 548 AFAreaMode int8u + 550 VRMode int8u + 554 BracketSet int8u + 556 BracketProgram int8u + 558 BracketIncrement int8u + 570 HDR int8u + 576 SecondarySlotFunction int8u + 582 HDRLevel int8u + 586 Slot2JpgSize? int8u + 592 DXCropAlert int8u + 594 SubjectDetection int8u + 596 DynamicAFAreaSize int8u + 618 ToneMap? int8u + 622 PortraitImpressionBalance int8u + 636 HighFrequencyFlickerReductionShooting? int8u + 730 MovieImageArea? int8u & 0x01 + 740 MovieType? int8u + 742 MovieISOAutoHiLimit? int16u + 744 MovieISOAutoControlManualMode? int8u + 746 MovieISOAutoManualMode? int16u + 820 MovieActiveD-Lighting? int8u + 822 MovieHighISONoiseReduction? int8u + 828 MovieFlickerReduction int8u + 830 MovieMeteringMode? int8u + 832 MovieFocusMode? int8u + 834 MovieAFAreaMode int8u + 836 MovieVRMode? int8u + 840 MovieElectronicVR? int8u + 842 MovieSoundRecording? int8u + 844 MicrophoneSensitivity? int8u + 846 MicrophoneAttenuator? int8u + 848 MicrophoneFrequencyResponse? int8u + 850 WindNoiseReduction? int8u + 882 MovieFrameSize? int8u + 884 MovieFrameRate? int8u + 886 MicrophoneJackPower? int8u + 887 MovieDXCropAlert? int8u + 888 MovieSubjectDetection? int8u + 896 MovieHighResZoom? int8u + 943 CustomSettingsZ8 NikonCustom SettingsZ8 + 1682 Language? int8u + 1684 TimeZone int8u + 1690 MonitorBrightness? int8u + 1712 AFFineTune? int8u + 1716 NonCPULens1FocalLength? int16u~ + 1718 NonCPULens2FocalLength? int16u~ + 1720 NonCPULens3FocalLength? int16u~ + 1722 NonCPULens4FocalLength? int16u~ + 1724 NonCPULens5FocalLength? int16u~ + 1726 NonCPULens6FocalLength? int16u~ + 1728 NonCPULens7FocalLength? int16u~ + 1730 NonCPULens8FocalLength? int16u~ + 1732 NonCPULens9FocalLength? int16u~ + 1734 NonCPULens10FocalLength? int16u~ + 1736 NonCPULens11FocalLength? int16u~ + 1738 NonCPULens12FocalLength? int16u~ + 1740 NonCPULens13FocalLength? int16u~ + 1742 NonCPULens14FocalLength? int16u~ + 1744 NonCPULens15FocalLength? int16u~ + 1746 NonCPULens16FocalLength? int16u~ + 1748 NonCPULens17FocalLength? int16u~ + 1750 NonCPULens18FocalLength? int16u~ + 1752 NonCPULens19FocalLength? int16u~ + 1754 NonCPULens20FocalLength? int16u~ + 1756 NonCPULens1MaxAperture? int16u + 1758 NonCPULens2MaxAperture? int16u + 1760 NonCPULens3MaxAperture? int16u + 1762 NonCPULens4MaxAperture? int16u + 1764 NonCPULens5MaxAperture? int16u + 1766 NonCPULens6MaxAperture? int16u + 1768 NonCPULens7MaxAperture? int16u + 1770 NonCPULens8MaxAperture? int16u + 1772 NonCPULens9MaxAperture? int16u + 1774 NonCPULens10MaxAperture? int16u + 1776 NonCPULens11MaxAperture? int16u + 1778 NonCPULens12MaxAperture? int16u + 1780 NonCPULens13MaxAperture? int16u + 1782 NonCPULens14MaxAperture? int16u + 1784 NonCPULens15MaxAperture? int16u + 1786 NonCPULens16MaxAperture? int16u + 1788 NonCPULens17MaxAperture? int16u + 1790 NonCPULens18MaxAperture? int16u + 1792 NonCPULens19MaxAperture? int16u + 1794 NonCPULens20MaxAperture? int16u + 1808 HDMIOutputResolution int8u + 1826 AirplaneMode? int8u + 1827 EmptySlotRelease? int8u + 1862 EnergySavingMode? int8u + 1890 USBPowerDelivery? int8u + 1899 SensorShield? int8u + +=head3 Nikon ShotInfoZ9 Tags + +These tags are extracted from encrypted data in images from the Z9. + + Index Tag Name Writable + ----- -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 48 SequenceOffset Nikon SeqInfoZ9 + 88 Offset13 Nikon Offset13InfoZ9 + 128 AutoCaptureOffset Nikon AutoCaptureInfo + 132 OrientOffset Nikon OrientationInfo + 140 MenuOffset Nikon MenuInfoZ9 + +=head3 Nikon Offset13InfoZ9 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 3048 AFAreaInitialXPosition int8s~ + 3049 AFAreaInitialYPosition int8s~ + 3050 AFAreaInitialWidth no + 3051 AFAreaInitialHeight no + +=head3 Nikon AutoCaptureInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 AutoCapturedFrame int8u + 1 AutoCaptureCriteria int8u~ + 55 AutoCaptureRecordingTime int8u + 56 AutoCaptureWaitTime int8u + 74 AutoCaptureDistanceFar int8u~ + 78 AutoCaptureDistanceNear int8u~ + 95 AutoCaptureCriteriaMotionDirection int8u~ + 99 AutoCaptureCriteriaMotionSpeed int8u + 100 AutoCaptureCriteriaMotionSize int8u + 105 AutoCaptureCriteriaSubjectSize int8u + 106 AutoCaptureCriteriaSubjectType int8u + +=head3 Nikon MenuInfoZ9 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 16 MenuSettingsOffsetZ9 Nikon MenuSettingsZ9 + MenuSettingsOffsetZ9v3 - + MenuSettingsOffsetZ9v4 Nikon MenuSettingsZ9v3 + - + Nikon MenuSettingsZ9v4 + +=head3 Nikon MenuSettingsZ9 Tags + +These tags are used by the Z9. + + Index1 Tag Name Writable + ------ -------- -------- + 140 MultipleExposureMode int8u + 142 MultiExposureShots int8u + 188 Intervals int32u + 192 ShotsPerInterval int32u + 232 FocusShiftNumberShots int8u + 236 FocusShiftStepWidth int8u + 240 FocusShiftInterval int8u~ + 244 FocusShiftExposureLock? int8u + 274 PhotoShootingMenuBank int8u + 276 ExtendedMenuBanks int8u + 308 PhotoShootingMenuBankImageArea int8u + 322 AutoISO int8u + 324 ISOAutoHiLimit? int16u + 326 ISOAutoFlashLimit? int16u + 334 ISOAutoShutterTime no + 416 MovieVignetteControl? int8u + 418 DiffractionCompensation int8u + 420 FlickerReductionShooting int8u + 424 FlashControlMode int8u + 426 FlashMasterCompensation? int8s + 430 FlashGNDistance? no + 434 FlashOutput? int8u + 444 FlashRemoteControl? int8u + 456 FlashWirelessOption? int8u + 528 AFAreaMode int8u + 530 VRMode int8u + 534 BracketSet int8u + 536 BracketProgram int8u + 538 BracketIncrement int8u + 556 SecondarySlotFunction int8u + 572 DXCropAlert int8u + 574 SubjectDetection int8u + 576 DynamicAFAreaSize int8u + 604 MovieImageArea? int8u & 0x01 + 614 MovieType? int8u + 616 MovieISOAutoHiLimit? int16u + 618 MovieISOAutoControlManualMode? int8u + 620 MovieISOAutoManualMode? int16u + 696 MovieActiveD-Lighting? int8u + 698 MovieHighISONoiseReduction? int8u + 704 MovieFlickerReduction int8u + 706 MovieMeteringMode? int8u + 708 MovieFocusMode? int8u + 710 MovieAFAreaMode int8u + 712 MovieVRMode? int8u + 716 MovieElectronicVR? int8u + 718 MovieSoundRecording? int8u + 720 MicrophoneSensitivity? int8u + 722 MicrophoneAttenuator? int8u + 724 MicrophoneFrequencyResponse? int8u + 726 WindNoiseReduction? int8u + 748 MovieToneMap? int8u + 754 MovieFrameSize? int8u + 756 MovieFrameRate? int8u + 762 MicrophoneJackPower? int8u + 763 MovieDXCropAlert? int8u + 764 MovieSubjectDetection? int8u + 799 CustomSettingsZ9 NikonCustom SettingsZ9 + 1426 Language? int8u + 1428 TimeZone int8u + 1434 MonitorBrightness? no + 1456 AFFineTune? int8u + 1552 HDMIOutputResolution int8u + 1565 SetClockFromLocationData? int8u + 1572 AirplaneMode? int8u + 1573 EmptySlotRelease? int8u + 1608 EnergySavingMode? int8u + 1632 RecordLocationData? int8u + 1636 USBPowerDelivery? int8u + 1645 SensorShield? int8u + +=head3 Nikon MenuSettingsZ9v3 Tags + +These tags are used by the Z9 firmware 3.00. + + Index1 Tag Name Writable + --------------- -------- + 72 HighFrameRate int8u + 154 MultipleExposureMode int8u + 156 MultiExposureShots int8u + 204 Intervals int32u + 208 ShotsPerInterval int32u + 248 FocusShiftNumberShots int8u + 252 FocusShiftStepWidth int8u + 256 FocusShiftInterval int8u~ + 260 FocusShiftExposureLock? int8u + 290 PhotoShootingMenuBank int8u + 292 ExtendedMenuBanks int8u + 328 PhotoShootingMenuBankImageArea int8u + 342 AutoISO int8u + 344 ISOAutoHiLimit? int16u + 346 ISOAutoFlashLimit? int16u + 354 ISOAutoShutterTime no + 436 MovieVignetteControl? int8u + 438 DiffractionCompensation int8u + 440 FlickerReductionShooting int8u + 444 FlashControlMode int8u + 446 FlashMasterCompensation? int8s + 450 FlashGNDistance? no + 454 FlashOutput? int8u + 548 AFAreaMode int8u + 550 VRMode int8u + 554 BracketSet int8u + 556 BracketProgram int8u + 558 BracketIncrement int8u + 576 SecondarySlotFunction int8u + 592 DXCropAlert int8u + 594 SubjectDetection int8u + 596 DynamicAFAreaSize int8u + 636 HighFrequencyFlickerReductionShooting? int8u + 646 MovieImageArea? int8u & 0x01 + 656 MovieType? int8u + 658 MovieISOAutoHiLimit? int16u + 660 MovieISOAutoControlManualMode? int8u + 662 MovieISOAutoManualMode? int16u + 736 MovieActiveD-Lighting? int8u + 738 MovieHighISONoiseReduction? int8u + 744 MovieFlickerReduction int8u + 746 MovieMeteringMode? int8u + 748 MovieFocusMode? int8u + 750 MovieAFAreaMode int8u + 752 MovieVRMode? int8u + 756 MovieElectronicVR? int8u + 758 MovieSoundRecording? int8u + 760 MicrophoneSensitivity? int8u + 762 MicrophoneAttenuator? int8u + 764 MicrophoneFrequencyResponse? int8u + 766 WindNoiseReduction? int8u + 788 MovieToneMap? int8u + 794 MovieFrameSize? int8u + 796 MovieFrameRate? int8u + 802 MicrophoneJackPower? int8u + 803 MovieDXCropAlert? int8u + 804 MovieSubjectDetection? int8u + 812 MovieHighResZoom? int8u + 847 CustomSettingsZ9 NikonCustom SettingsZ9 + 1474 Language? int8u + 1476 TimeZone int8u + 1482 MonitorBrightness? int8u + 1504 AFFineTune? int8u + 1600 HDMIOutputResolution int8u + 1613 SetClockFromLocationData? int8u + 1620 AirplaneMode? int8u + 1621 EmptySlotRelease? int8u + 1656 EnergySavingMode? int8u + 1680 RecordLocationData? int8u + 1684 USBPowerDelivery? int8u + 1693 SensorShield? int8u + 1754 FocusShiftAutoReset? int8u + 1810 PreReleaseBurstLength int8u + 1812 PostReleaseBurstLength int8u + +=head3 Nikon MenuSettingsZ9v4 Tags + +These tags are used by the Z9 firmware 3.00. + + Index1 Tag Name Writable + --------------- -------- + 72 HighFrameRate int8u + 154 MultipleExposureMode int8u + 156 MultiExposureShots int8u + 204 Intervals int32u + 208 ShotsPerInterval int32u + 248 FocusShiftNumberShots int8u + 252 FocusShiftStepWidth int8u + 256 FocusShiftInterval int8u~ + 260 FocusShiftExposureLock? int8u + 290 PhotoShootingMenuBank int8u + 292 ExtendedMenuBanks int8u + 328 PhotoShootingMenuBankImageArea int8u + 342 AutoISO int8u + 344 ISOAutoHiLimit? int16u + 346 ISOAutoFlashLimit? int16u + 354 ISOAutoShutterTime no + 436 MovieVignetteControl? int8u + 438 DiffractionCompensation int8u + 440 FlickerReductionShooting int8u + 444 FlashControlMode int8u + 446 FlashMasterCompensation? int8s + 450 FlashGNDistance? no + 454 FlashOutput? int8u + 548 AFAreaMode int8u + 550 VRMode int8u + 554 BracketSet int8u + 556 BracketProgram int8u + 558 BracketIncrement int8u + 570 HDR int8u + 576 SecondarySlotFunction int8u + 582 HDRLevel int8u + 586 Slot2JpgSize? int8u + 592 DXCropAlert int8u + 594 SubjectDetection int8u + 596 DynamicAFAreaSize int8u + 636 HighFrequencyFlickerReductionShooting? int8u + 646 MovieImageArea? int8u & 0x01 + 656 MovieType? int8u + 658 MovieISOAutoHiLimit? int16u + 660 MovieISOAutoControlManualMode? int8u + 662 MovieISOAutoManualMode? int16u + 736 MovieActiveD-Lighting? int8u + 738 MovieHighISONoiseReduction? int8u + 744 MovieFlickerReduction int8u + 746 MovieMeteringMode? int8u + 748 MovieFocusMode? int8u + 750 MovieAFAreaMode int8u + 752 MovieVRMode? int8u + 756 MovieElectronicVR? int8u + 758 MovieSoundRecording? int8u + 760 MicrophoneSensitivity? int8u + 762 MicrophoneAttenuator? int8u + 764 MicrophoneFrequencyResponse? int8u + 766 WindNoiseReduction? int8u + 788 MovieToneMap? int8u + 794 MovieFrameSize? int8u + 796 MovieFrameRate? int8u + 802 MicrophoneJackPower? int8u + 803 MovieDXCropAlert? int8u + 804 MovieSubjectDetection? int8u + 812 MovieHighResZoom? int8u + 847 CustomSettingsZ9v4 NikonCustom SettingsZ9v4 + 1498 Language? int8u + 1500 TimeZone int8u + 1506 MonitorBrightness? int8u + 1528 AFFineTune? int8u + 1532 NonCPULens1FocalLength? int16s~ + 1536 NonCPULens2FocalLength? int16s~ + 1540 NonCPULens3FocalLength? int16s~ + 1544 NonCPULens4FocalLength? int16s~ + 1548 NonCPULens5FocalLength? int16s~ + 1552 NonCPULens6FocalLength? int16s~ + 1556 NonCPULens7FocalLength? int16s~ + 1560 NonCPULens8FocalLength? int16s~ + 1564 NonCPULens9FocalLength? int16s~ + 1568 NonCPULens10FocalLength? int16s~ + 1572 NonCPULens11FocalLength? int16s~ + 1576 NonCPULens12FocalLength? int16s~ + 1580 NonCPULens13FocalLength? int16s~ + 1584 NonCPULens14FocalLength? int16s~ + 1588 NonCPULens15FocalLength? int16s~ + 1592 NonCPULens16FocalLength? int16s~ + 1596 NonCPULens17FocalLength? int16s~ + 1600 NonCPULens18FocalLength? int16s~ + 1604 NonCPULens19FocalLength? int16s~ + 1608 NonCPULens20FocalLength? int16s~ + 1612 NonCPULens1MaxAperture? int16s~ + 1616 NonCPULens2MaxAperture? int16s~ + 1620 NonCPULens3MaxAperture? int16s~ + 1624 NonCPULens4MaxAperture? int16s~ + 1628 NonCPULens5MaxAperture? int16s~ + 1632 NonCPULens6MaxAperture? int16s~ + 1636 NonCPULens7MaxAperture? int16s~ + 1640 NonCPULens8MaxAperture? int16s~ + 1644 NonCPULens9MaxAperture? int16s~ + 1648 NonCPULens10MaxAperture? int16s~ + 1652 NonCPULens11MaxAperture? int16s~ + 1656 NonCPULens12MaxAperture? int16s~ + 1660 NonCPULens13MaxAperture? int16s~ + 1664 NonCPULens14MaxAperture? int16s~ + 1668 NonCPULens15MaxAperture? int16s~ + 1672 NonCPULens16MaxAperture? int16s~ + 1676 NonCPULens17MaxAperture? int16s~ + 1680 NonCPULens18MaxAperture? int16s~ + 1684 NonCPULens19MaxAperture? int16s~ + 1688 NonCPULens20MaxAperture? int16s~ + 1704 HDMIOutputResolution int8u + 1717 SetClockFromLocationData? int8u + 1724 AirplaneMode? int8u + 1725 EmptySlotRelease? int8u + 1760 EnergySavingMode? int8u + 1784 RecordLocationData? int8u + 1788 USBPowerDelivery? int8u + 1797 SensorShield? int8u + 1862 AutoCapturePreset int8u + 1864 FocusShiftAutoReset? int8u + 1922 PreReleaseBurstLength int8u + 1924 PostReleaseBurstLength int8u + 1938 VerticalISOButton int8u + 1940 ExposureCompensationButton int8u + 1942 ISOButton int8u + 2002 ViewModeShowEffectsOfSettings? int8u + 2004 DispButton int8u + 2048 ExposureDelay fixed32u~ + 2056 PlaybackButton int8u + 2058 WBButton int8u + 2060 BracketButton int8u + 2062 FlashModeButton int8u + 2064 LensFunc1ButtonPlaybackMode int8u + 2066 LensFunc2ButtonPlaybackMode int8u + 2068 PlaybackButtonPlaybackMode int8u + 2070 BracketButtonPlaybackMode int8u + 2072 FlashModeButtonPlaybackMode int8u + +=head3 Nikon ShotInfo Tags + +This information is encrypted for ShotInfoVersion 02xx, and some tags are +only valid for specific models. + + Index1 Tag Name Writable + ------ -------- -------- + 0 ShotInfoVersion no + 4 FirmwareVersion no + 16 DistortionControl int8u + 102 VR_0x66? int8u + 106 ShutterCount int32u + 110 DeletedImageCount int32u + 117 VibrationReduction int8u + 130 VibrationReduction int8u + 343 ShutterCount undef[2] + 430 VibrationReduction int8u + 589 ShutterCount int32u + +=head3 Nikon ColorBalance1 Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 WB_RBGGLevels int16u[4]! + +=head3 Nikon ColorBalance2 Tags + +This information is encrypted for most camera models. + + Index2 Tag Name Writable + ------ -------- -------- + 0 WB_RGGBLevels int16u[4]! + +=head3 Nikon ColorBalance3 Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 WB_RGBGLevels int16u[4]! + +=head3 Nikon ColorBalance4 Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 WB_GRBGLevels int16u[4]! + +=head3 Nikon ColorBalanceUnknown Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ColorBalanceVersion undef[4] + +=head3 Nikon LensData00 Tags + +This structure is used by the D100, and D1X with firmware version 1.1. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensDataVersion no + 6 LensIDNumber int8u + 7 LensFStops int8u + 8 MinFocalLength int8u + 9 MaxFocalLength int8u + 10 MaxApertureAtMinFocal int8u + 11 MaxApertureAtMaxFocal int8u + 12 MCUVersion int8u + +=head3 Nikon LensData01 Tags + +Nikon encrypts the LensData information below if LensDataVersion is 0201 or +higher, but the decryption algorithm is known so the information can be +extracted. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensDataVersion no + 4 ExitPupilPosition int8u + 5 AFAperture int8u + 8 FocusPosition int8u + 9 FocusDistance int8u + 10 FocalLength int8u + 11 LensIDNumber int8u + 12 LensFStops int8u + 13 MinFocalLength int8u + 14 MaxFocalLength int8u + 15 MaxApertureAtMinFocal int8u + 16 MaxApertureAtMaxFocal int8u + 17 MCUVersion int8u + 18 EffectiveMaxAperture int8u + +=head3 Nikon LensData0204 Tags + +Nikon encrypts the LensData information below if LensDataVersion is 0201 or +higher, but the decryption algorithm is known so the information can be +extracted. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensDataVersion no + 4 ExitPupilPosition int8u + 5 AFAperture int8u + 8 FocusPosition int8u + 10 FocusDistance int8u + 11 FocalLength int8u + 12 LensIDNumber int8u + 13 LensFStops int8u + 14 MinFocalLength int8u + 15 MaxFocalLength int8u + 16 MaxApertureAtMinFocal int8u + 17 MaxApertureAtMaxFocal int8u + 18 MCUVersion int8u + 19 EffectiveMaxAperture int8u + +=head3 Nikon LensData0400 Tags + +Tags extracted from the encrypted lens data of the Nikon 1J1/1V1/1J2. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensDataVersion no + 394 LensModel string[64] + +=head3 Nikon LensData0402 Tags + +Tags extracted from the encrypted lens data of the Nikon 1J3/1S1/1V2. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensDataVersion no + 395 LensModel string[64] + +=head3 Nikon LensData0403 Tags + +Tags extracted from the encrypted lens data of the Nikon 1J4/1J5. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensDataVersion no + 684 LensModel string[64] + +=head3 Nikon LensData0800 Tags + +Tags found in the encrypted LensData from cameras such as the Z6 and Z7. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensDataVersion no + 4 ExitPupilPosition int8u + 5 AFAperture int8u + 11 FocusDistance int8u + 12 FocalLength int8u + 13 LensIDNumber int8u + 14 LensFStops int8u + 15 MinFocalLength int8u + 16 MaxFocalLength int8u + 17 MaxApertureAtMinFocal int8u + 18 MaxApertureAtMaxFocal int8u + 19 MCUVersion int8u + 20 EffectiveMaxAperture int8u + 47 NewLensData undef[17] + 48 LensID int16u + 53 LensMountType int8u + 54 MaxAperture int16u + 56 FNumber int16u + 60 FocalLength int16u + 76 FocusDistanceRangeWidth? int8u + 78 FocusDistance int16u~ + 86 LensDriveEnd? int8u + 88 FocusStepsFromInfinity? int8u + 90 LensPositionAbsolute int32s + +=head3 Nikon LensDataUnknown Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensDataVersion no + +=head3 Nikon FlashInfo0100 Tags + +These tags are used by the D2H, D2Hs, D2X, D2Xs, D50, D70, D70s, D80 and +D200. + + Index1 Tag Name Writable + ------ -------- -------- + 0 FlashInfoVersion no + 4 FlashSource int8u + 6 ExternalFlashFirmware int8u[2] + 8 ExternalFlashFlags int8u + 9.1 FlashCommanderMode int8u & 0x80 + 9.2 FlashControlMode int8u & 0x7f + 10 FlashOutput int8u + FlashCompensation int8s + 11 FlashFocalLength int8u + 12 RepeatingFlashRate int8u + 13 RepeatingFlashCount int8u + 14 FlashGNDistance int8u + 15 FlashGroupAControlMode int8u & 0x0f + 16 FlashGroupBControlMode int8u & 0x0f + 17 FlashGroupAOutput int8u + FlashGroupACompensation int8s + 18 FlashGroupBOutput int8u + FlashGroupBCompensation int8s + +=head3 Nikon FlashInfo0102 Tags + +These tags are used by the D3 (firmware 1.x), D40, D40X, D60 and D300 +(firmware 1.00). + + Index1 Tag Name Writable + ------ -------- -------- + 0 FlashInfoVersion no + 4 FlashSource int8u + 6 ExternalFlashFirmware int8u[2] + 8 ExternalFlashFlags int8u + 9.1 FlashCommanderMode int8u & 0x80 + 9.2 FlashControlMode int8u & 0x7f + 10 FlashOutput int8u + FlashCompensation int8s + 12 FlashFocalLength int8u + 13 RepeatingFlashRate int8u + 14 RepeatingFlashCount int8u + 15 FlashGNDistance int8u + 16.1 FlashGroupAControlMode int8u & 0x0f + 17.1 FlashGroupBControlMode int8u & 0xf0 + 17.2 FlashGroupCControlMode int8u & 0x0f + 18 FlashGroupAOutput int8u + FlashGroupACompensation int8s + 19 FlashGroupBOutput int8u + FlashGroupBCompensation int8s + 20 FlashGroupCOutput int8u + FlashGroupCCompensation int8s + +=head3 Nikon FlashInfo0103 Tags + +These tags are used by the D3 (firmware 2.x), D3X, D3S, D4, D90, D300 +(firmware 1.10), D300S, D600, D700, D800, D3000, D3100, D3200, D5000, D5100, +D5200, D7000. + + Index1 Tag Name Writable + ------ -------- -------- + 0 FlashInfoVersion no + 4 FlashSource int8u + 6 ExternalFlashFirmware int8u[2] + 8 ExternalFlashFlags int8u + 9.1 FlashCommanderMode int8u & 0x80 + 9.2 FlashControlMode int8u & 0x7f + 10 FlashOutput int8u + FlashCompensation int8s + 12 FlashFocalLength int8u + 13 RepeatingFlashRate int8u + 14 RepeatingFlashCount int8u + 15 FlashGNDistance int8u + 16 FlashColorFilter int8u + 17.1 FlashGroupAControlMode int8u & 0x0f + 18.1 FlashGroupBControlMode int8u & 0xf0 + 18.2 FlashGroupCControlMode int8u & 0x0f + 19 FlashGroupAOutput int8u + FlashGroupACompensation int8s + 20 FlashGroupBOutput int8u + FlashGroupBCompensation int8s + 21 FlashGroupCOutput int8u + FlashGroupCCompensation int8s + 27 ExternalFlashCompensation int8s + 29 FlashExposureComp3 int8s + 39 FlashExposureComp4 int8s + +=head3 Nikon FlashInfo0106 Tags + +These tags are used by the Df, D610, D3300, D5300, D7100 and Coolpix A. + + Index1 Tag Name Writable + ------ -------- -------- + 0 FlashInfoVersion no + 4 FlashSource int8u + 6 ExternalFlashFirmware int8u[2] + 8 ExternalFlashFlags int8u + 9.1 FlashCommanderMode int8u & 0x80 + 9.2 FlashControlMode int8u & 0x7f + 12 FlashFocalLength int8u + 13 RepeatingFlashRate int8u + 14 RepeatingFlashCount int8u + 15 FlashGNDistance int8u + 16 FlashColorFilter int8u + 17.1 FlashGroupAControlMode int8u & 0x0f + 18.1 FlashGroupBControlMode int8u & 0xf0 + 18.2 FlashGroupCControlMode int8u & 0x0f + 39 FlashOutput int8u + FlashCompensation int8s + 40 FlashGroupAOutput int8u + FlashGroupACompensation int8s + 41 FlashGroupBOutput int8u + FlashGroupBCompensation int8s + 42 FlashGroupCOutput int8u + FlashGroupCCompensation int8s + +=head3 Nikon FlashInfo0107 Tags + +These tags are used by the D4S, D750, D810, D5500, D7200 (FlashInfoVersion +0107) and the D5, D500, D850 and D3400 (FlashInfoVersion 0108). + + Index1 Tag Name Writable + ------ -------- -------- + 0 FlashInfoVersion no + 4 FlashSource int8u + 6 ExternalFlashFirmware int8u[2] + 8.1 ExternalFlashZoomOverride int8u & 0x80 + 8.2 ExternalFlashStatus int8u & 0x01 + 9.1 ExternalFlashReadyState int8u & 0x07 + 10 FlashCompensation int8s + 12 FlashFocalLength int8u + 13 RepeatingFlashRate int8u + 14 RepeatingFlashCount int8u + 15 FlashGNDistance int8u + 17.1 FlashGroupAControlMode int8u & 0x0f + 18.1 FlashGroupBControlMode int8u & 0xf0 + 18.2 FlashGroupCControlMode int8u & 0x0f + 40 FlashGroupAOutput int8u + FlashGroupACompensation int8s + 41 FlashGroupBOutput int8u + FlashGroupBCompensation int8s + 42 FlashGroupCOutput int8u + FlashGroupCCompensation int8s + +=head3 Nikon FlashInfo0300 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FlashInfoVersion no + 4 FlashSource int8u + 6 ExternalFlashFirmware int8u[2] + 8 ExternalFlashFlags int8u + 9.1 FlashCommanderMode int8u & 0x80 + 9.2 FlashControlMode int8u & 0x7f + 10 FlashCompensation int8s + 13 RepeatingFlashRate int8u + 14 RepeatingFlashCount int8u + 15 FlashGNDistance int8u + 16 FlashColorFilter int8u + 17.1 FlashGroupAControlMode int8u & 0x0f + 18.1 FlashGroupBControlMode int8u & 0xf0 + 18.2 FlashGroupCControlMode int8u & 0x0f + 33 FlashOutput int8u + 37 FlashIlluminationPattern int8u + 38 FlashFocalLength int8u + 40 FlashGroupAOutput int8u + FlashGroupACompensation int8s + 41 FlashGroupBOutput int8u + FlashGroupBCompensation int8s + 42 FlashGroupCOutput int8u + FlashGroupCCompensation int8s + +=head3 Nikon FlashInfoUnknown Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FlashInfoVersion no + +=head3 Nikon MultiExposure Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 MultiExposureVersion no + 1 MultiExposureMode int32u + 2 MultiExposureShots int32u + 3 MultiExposureAutoGain int32u + +=head3 Nikon MultiExposure2 Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 MultiExposureVersion no + 1 MultiExposureMode int32u + 2 MultiExposureShots int32u + 3 MultiExposureOverlayMode int32u + +=head3 Nikon AFInfo2V0400 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 AFInfo2Version no + 5 AFAreaMode int8u + 10 AFPointsUsed undef[51] + 62 AFImageWidth int16u + 64 AFImageHeight int16u + 66 AFAreaXPosition int16u + 67 FocusPositionHorizontal int8u~ + 68 AFAreaYPosition int16u + 69 FocusPositionVertical int8u~ + 70 AFAreaWidth int16u + 72 AFAreaHeight int16u + 74 FocusResult int8u + +=head3 Nikon AFInfo2 Tags + +These tags are written by Nikon DSLR's which have the live view feature. + + Index1 Tag Name Writable + ------ -------- -------- + 0 AFInfo2Version no + 4 ContrastDetectAF int8u + 5 AFAreaMode int8u + 6 PhaseDetectAF int8u + 7 PrimaryAFPoint int8u + 8 AFPointsUsed undef[7] + AFPointsUsed undef[2] + AFPointsUsed undef[5] + AFPointsUsed undef[17] + AFPointsUsed undef[21] + AFPointsUsed undef[29] + AFPointsUsed undef[20] + AFPointsUsed undef[7] + PrimaryAFPoint int8u + 10 AFPointsUsed undef[7] + AFPointsUsed undef[11] + AFPointsUsed undef[14] + 16 AFImageWidth int16u + 18 AFImageHeight int16u + 20 AFAreaXPosition int16u + 22 AFAreaYPosition int16u + 24 AFAreaWidth int16u + 26 AFAreaHeight int16u + 28 ContrastDetectAFInFocus int8u + AFPointsSelected undef[20] + 42 AFImageWidth int16u + 44 AFImageHeight int16u + 46 AFAreaXPosition int16u + 47 FocusPositionHorizontal int8u~ + 48 AFAreaYPosition int16u + AFPointsInFocus undef[20] + 49 FocusPositionVertical int8u~ + 50 AFAreaWidth int16u + 52 AFAreaHeight int16u + 56 PrimaryAFPoint int8u + 68 PrimaryAFPoint int8u + 70 AFImageWidth int16u + 72 AFImageHeight int16u + 74 AFAreaXPosition int16u + 76 AFAreaYPosition int16u + 78 AFAreaWidth int16u + 80 AFAreaHeight int16u + 82 ContrastDetectAFInFocus int8u + +=head3 Nikon FileInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 FileInfoVersion no + 2 MemoryCardNumber int16u + 3 DirectoryNumber int16u + 4 FileNumber int16u + +=head3 Nikon AFTune Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 AFFineTune int8u + 1 AFFineTuneIndex int8u + 2 AFFineTuneAdj int8s + 3 AFFineTuneAdjTele int8s + +=head3 Nikon RetouchInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 RetouchInfoVersion no + 5 RetouchNEFProcessing int8s + +=head3 Nikon BarometerInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 BarometerInfoVersion no + 6 Altitude int32s + +=head3 Nikon CaptureOffsets Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 IFD0_Offset no + 0x0002 PreviewIFD_Offset no + 0x0003 SubIFD_Offset no + +=head3 Nikon Scan Tags + +This information is written by the Nikon Scan software. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0002 FilmType string + 0x0040 MultiSample string + 0x0041 BitDepth int16u + 0x0050 MasterGain rational64s + 0x0051 ColorGain rational64s[3] + 0x0060 ScanImageEnhancer int32u + 0x0100 DigitalICE string + 0x0110 ROCInfo Nikon ROC + 0x0120 GEMInfo Nikon GEM + 0x0200 DigitalDEEShadowAdj int32u + 0x0201 DigitalDEEThreshold int32u + 0x0202 DigitalDEEHighlightAdj int32u + +=head3 Nikon ROC Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 DigitalROC int32u + +=head3 Nikon GEM Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 DigitalGEM int32u + +=head3 Nikon CaptureOutput Tags + + Index4 Tag Name Writable + ------ -------- -------- + 2 OutputImageWidth int32u + 3 OutputImageHeight int32u + 4 OutputResolution int32u + +=head3 Nikon Type2 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0003 Quality yes + 0x0004 ColorMode yes + 0x0005 ImageAdjustment yes + 0x0006 CCDSensitivity yes + 0x0007 WhiteBalance yes + 0x0008 Focus yes + 0x000a DigitalZoom yes + 0x000b Converter yes + +=head3 Nikon NEFInfo Tags + +As-yet unknown information found in SubIFD1 tag 0xc7d5 of NEF images from +cameras such as the Z6 and Z7, and NRW images from some Coolpix cameras. + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Nikon AVI Tags + +Nikon-specific RIFF tags found in AVI videos. + + Tag ID Tag Name Writable + ------ -------- -------- + 'nctg' NikonTags Nikon AVITags + 'ncth' ThumbnailImage no + 'ncvr' NikonVers Nikon AVIVers + 'ncvw' PreviewImage no + +=head3 Nikon AVITags Tags + +These tags and the AVIVer tags below are found in proprietary-format records +of Nikon AVI videos. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0003 Make no + 0x0004 Model no + 0x0005 Software no + 0x0006 Equipment no + 0x0007 Orientation no + 0x0008 ExposureTime no + 0x0009 FNumber no + 0x000a ExposureCompensation no + 0x000b MaxApertureValue no + 0x000c MeteringMode no + 0x000f FocalLength no + 0x0010 XResolution no + 0x0011 YResolution no + 0x0012 ResolutionUnit no + 0x0013 DateTimeOriginal no + 0x0014 CreateDate no + 0x0016 Duration no + 0x0018 FocusMode no + 0x001b DigitalZoom no + 0x001d ColorMode no + 0x001e Sharpness no + 0x001f WhiteBalance no + 0x0020 NoiseReduction no + +=head3 Nikon AVIVers Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 MakerNoteType no + 0x0002 MakerNoteVersion no + +=head3 Nikon NCDT Tags + +Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from +various Nikon models. + + Tag ID Tag Name Writable + ------ -------- -------- + 'NCDB' NikonNCDB Nikon NCDB + 'NCHD' MakerNoteVersion no + 'NCM1' PreviewImage1 no + 'NCM2' PreviewImage2 no + 'NCTG' NikonTags Nikon NCTG + 'NCTH' ThumbnailImage no + 'NCVW' PreviewImage no + +=head3 Nikon NCDB Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Nikon NCTG Tags + +These tags are found in proprietary-format records of the NCTG atom in MOV +videos from some Nikon cameras. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 Make no + 0x0002 Model no + 0x0003 Software no + 0x0011 CreateDate no + 0x0012 DateTimeOriginal no + 0x0013 FrameCount no + 0x0016 FrameRate no + 0x0019 TimeZone no + 0x0022 FrameWidth no + 0x0023 FrameHeight no + 0x0032 AudioChannels no + 0x0033 AudioBitsPerSample no + 0x0034 AudioSampleRate no + 0x1002 NikonDateTime no + 0x1013 ElectronicVR no + 0x110829a ExposureTime no + 0x110829d FNumber no + 0x1108822 ExposureProgram no + 0x1109204 ExposureCompensation no + 0x1109207 MeteringMode no + 0x110920a FocalLength no + 0x110a431 SerialNumber no + 0x110a432 LensInfo no + 0x110a433 LensMake no + 0x110a434 LensModel no + 0x110a435 LensSerialNumber no + 0x1200000 GPSVersionID no + 0x1200001 GPSLatitudeRef no + 0x1200002 GPSLatitude no + 0x1200003 GPSLongitudeRef no + 0x1200004 GPSLongitude no + 0x1200005 GPSAltitudeRef no + 0x1200006 GPSAltitude no + 0x1200007 GPSTimeStamp no + 0x1200008 GPSSatellites no + 0x1200010 GPSImgDirectionRef no + 0x1200011 GPSImgDirection no + 0x1200012 GPSMapDatum no + 0x120001d GPSDateStamp no + 0x2000001 MakerNoteVersion no + 0x2000005 WhiteBalance no + 0x2000007 FocusMode no + 0x200000b WhiteBalanceFineTune no + 0x200001b CropHiSpeed no + 0x200001e ColorSpace no + 0x200001f VRInfo Nikon VRInfo + 0x2000022 ActiveD-Lighting no + 0x2000023 PictureControlData Nikon PictureControl + Nikon PictureControl2 + Nikon PictureControl3 + Nikon PictureControlUnknown + 0x2000024 WorldTime Nikon WorldTime + 0x2000025 ISOInfo Nikon ISOInfo + 0x200002a VignetteControl no + 0x200002c UnknownInfo Nikon UnknownInfo + 0x2000032 UnknownInfo2 Nikon UnknownInfo2 + 0x2000039 LocationInfo Nikon LocationInfo + 0x200003f WhiteBalanceFineTune no + 0x200004e NikonSettings NikonSettings + 0x2000083 LensType no + 0x2000084 Lens no + 0x2000087 FlashMode no + 0x2000098 LensData0100 Nikon LensData00 + LensData0101 Nikon LensData01 + LensData0201 Nikon LensData01 + LensData0204 Nikon LensData0204 + LensData0400 Nikon LensData0400 + LensData0402 Nikon LensData0402 + LensData0403 Nikon LensData0403 + LensData0800 Nikon LensData0800 + LensDataUnknown Nikon LensDataUnknown + 0x20000a7 ShutterCount no + 0x20000a8 FlashInfo0100 Nikon FlashInfo0100 + FlashInfo0102 Nikon FlashInfo0102 + FlashInfo0103 Nikon FlashInfo0103 + FlashInfo0106 Nikon FlashInfo0106 + FlashInfo0107 Nikon FlashInfo0107 + FlashInfoUnknown Nikon FlashInfoUnknown + 0x20000ab VariProgram no + 0x20000b1 HighISONoiseReduction no + 0x20000b7 AFInfo2 Nikon AFInfo2 + 0x20000c3 BarometerInfo Nikon BarometerInfo + +=head3 Nikon MOV Tags + +This information is found in MOV and QT videos from some Nikon cameras. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 24 Model no + 38 ExposureTime no + 42 FNumber no + 50 ExposureCompensation no + 68 WhiteBalance no + 72 FocalLength no + 175 Software no + 223 ISO no + +=head2 NikonCustom Tags + +Unfortunately, the NikonCustom settings are stored in a binary data block +which changes from model to model. This means that significant effort must +be spent in decoding these for each model, usually requiring hundreds of +test images from a dedicated Nikon owner. For this reason, the NikonCustom +settings have not been decoded for all models. The tables below list the +custom settings for the currently supported models. + +=head3 NikonCustom SettingsD40 Tags + +Custom settings for the Nikon D40. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 Beep int8u & 0x80 + 0.2 AFAssist int8u & 0x40 + 0.3 NoMemoryCard int8u & 0x20 + 0.4 ImageReview int8u & 0x10 + 1.1 AutoISO int8u & 0x80 + 1.2 AutoISOMax int8u & 0x30 + 1.3 AutoISOMinShutterSpeed int8u & 0x07 + 2.1 ImageReviewTime int8u & 0x07 + 3.1 MonitorOffTime int8u & 0xe0 + 3.2 MeteringTime int8u & 0x1c + 3.3 SelfTimerTime int8u & 0x03 + 3.4 RemoteOnDuration int8u & 0xc0 + 4.1 AELockButton int8u & 0x0e + 4.2 AELock int8u & 0x01 + 5.1 ShootingModeSetting int8u & 0x70 + 5.2 TimerFunctionButton int8u & 0x07 + 6.1 Metering int8u & 0x03 + 8.1 InternalFlash int8u & 0x10 + 8.2 ManualFlashOutput int8u & 0x07 + 9 FlashLevel int8s + 10.1 FocusModeSetting int8u & 0xc0 + 11.1 AFAreaModeSetting int8u & 0x30 + +=head3 NikonCustom SettingsD80 Tags + +Custom settings for the Nikon D80. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 Beep int8u & 0x80 + 0.2 AFAssist int8u & 0x40 + 0.3 NoMemoryCard int8u & 0x20 + 0.4 ImageReview int8u & 0x10 + 0.5 Illumination int8u & 0x08 + 0.6 MainDialExposureComp int8u & 0x04 + 0.7 EVStepSize int8u & 0x01 + 1.1 AutoISO int8u & 0x40 + 1.2 AutoISOMax int8u & 0x30 + 1.3 AutoISOMinShutterSpeed int8u & 0x0f + 2.1 AutoBracketSet int8u & 0xc0 + 2.2 AutoBracketOrder int8u & 0x20 + 3.1 MonitorOffTime int8u & 0xe0 + 3.2 MeteringTime int8u & 0x1c + 3.3 SelfTimerTime int8u & 0x03 + 4.1 AELockButton int8u & 0x1e + 4.2 AELock int8u & 0x01 + 4.3 RemoteOnDuration int8u & 0xc0 + 5.1 CommandDials int8u & 0x80 + 5.2 FunctionButton int8u & 0x78 + 6.1 GridDisplay int8u & 0x80 + 6.2 ViewfinderWarning int8u & 0x40 + 6.3 CenterWeightedAreaSize int8u & 0x0c + 6.4 ExposureDelayMode int8u & 0x20 + 6.5 MB-D80Batteries int8u & 0x03 + 7.1 FlashWarning int8u & 0x80 + 7.2 FlashShutterSpeed int8u & 0x78 + 7.3 AutoFP int8u & 0x04 + 7.4 ModelingFlash int8u & 0x02 + 8.1 InternalFlash int8u & 0xc0 + 8.2 ManualFlashOutput int8u & 0x07 + 9.1 RepeatingFlashOutput int8u & 0x70 + 9.2 RepeatingFlashCount int8u & 0x0f + 10.1 RepeatingFlashRate int8u & 0xf0 + 10.2 CommanderChannel int8u & 0x03 + 11.1 CommanderInternalFlash int8u & 0xc0 + 11.2 CommanderGroupAMode int8u & 0x30 + 11.3 CommanderGroupBMode int8u & 0x0c + 12.1 CommanderInternalTTLComp int8u & 0x1f + 12.2 CommanderInternalManualOutput int8u & 0xe0 + 13.1 CommanderGroupA_TTL-AAComp int8u & 0x1f + 13.2 CommanderGroupAManualOutput int8u & 0xe0 + 14.1 CommanderGroupB_TTL-AAComp int8u & 0x1f + 14.2 CommanderGroupBManualOutput int8u & 0xe0 + 15.1 CenterAFArea int8u & 0x80 + 15.2 FocusAreaSelection int8u & 0x04 + 15.3 AFAreaIllumination int8u & 0x03 + 16.1 AFAreaModeSetting int8u & 0xc0 + +=head3 NikonCustom SettingsD90 Tags + +Custom settings for the D90. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 LightSwitch int8u & 0x08 + 2.1 AFAreaModeSetting int8u & 0x60 + 2.2 CenterFocusPoint int8u & 0x10 + 2.3 AFAssist int8u & 0x01 + 2.4 AFPointIllumination int8u & 0x06 + 2.5 FocusPointWrap int8u & 0x08 + 3.1 AELockForMB-D80 int8u & 0x1c + 3.2 MB-D80BatteryType int8u & 0x03 + 4.1 Beep int8u & 0x40 + 4.2 GridDisplay int8u & 0x02 + 4.3 ISODisplay int8u & 0x0c + 4.4 ViewfinderWarning int8u & 0x01 + 4.5 NoMemoryCard int8u & 0x20 + 5.1 ScreenTips int8u & 0x04 + 5.2 FileNumberSequence int8u & 0x08 + 5.3 ShootingInfoDisplay int8u & 0xc0 + 5.4 LCDIllumination int8u & 0x20 + 6.1 EasyExposureComp int8u & 0x01 + 6.2 ReverseIndicators int8u & 0x80 + 7.1 ExposureControlStepSize int8u & 0x40 + 8.1 CenterWeightedAreaSize int8u & 0x60 + 8.2 FineTuneOptMatrixMetering int8u & 0x0f + 9.1 FineTuneOptCenterWeighted int8u & 0xf0 + 9.2 FineTuneOptSpotMetering int8u & 0x0f + 11.1 CLModeShootingSpeed int8u & 0x07 + 11.2 ExposureDelayMode int8u & 0x40 + 13.1 AutoBracketSet int8u & 0xe0 + 13.2 AutoBracketOrder int8u & 0x10 + 14.1 FuncButton int8u & 0x78 + 16.1 OKButton int8u & 0x18 + 17.1 AELockButton int8u & 0x38 + 18.1 CommandDialsReverseRotation int8u & 0x80 + 18.2 ShutterReleaseButtonAE-L int8u & 0x02 + 19.1 MeteringTime int8u & 0xf0 + 19.2 RemoteOnDuration int8u & 0x03 + 20.1 SelfTimerTime int8u & 0xc0 + 20.2 SelfTimerShotCount int8u & 0x1e + 21.1 PlaybackMonitorOffTime int8u & 0x1c + 21.2 ImageReviewTime int8u & 0xe0 + 22.1 MenuMonitorOffTime int8u & 0xe0 + 22.2 ShootingInfoMonitorOffTime int8u & 0x1c + 23.1 FlashShutterSpeed int8u & 0x0f + 24.1 InternalFlash int8u & 0xc0 + 24.2 ManualFlashOutput int8u & 0x1f + 25.1 RepeatingFlashOutput int8u & 0x70 + 25.2 RepeatingFlashCount int8u & 0x0f + 26.1 RepeatingFlashRate int8u & 0xf0 + 31.1 FlashWarning int8u & 0x80 + 31.2 CommanderInternalTTLComp int8u & 0x1f + 31.3 ModelingFlash int8u & 0x20 + 31.4 AutoFP int8u & 0x40 + 32.1 CommanderGroupA_TTLComp int8u & 0x1f + 33.1 CommanderGroupB_TTLComp int8u & 0x1f + 34.1 LiveViewAF int8u & 0xc0 + +=head3 NikonCustom SettingsD3 Tags + +Custom settings for the D3, D3S, D3X, D300 and D300S. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 CustomSettingsBank int8u & 0x03 + 0.2 CustomSettingsAllDefault int8u & 0x80 + 1.1 AF-CPrioritySelection int8u & 0xc0 + 1.2 AF-SPrioritySelection int8u & 0x20 + 1.3 AFPointSelection int8u & 0x10 + 1.4 DynamicAFArea int8u & 0x0c + 1.5 FocusTrackingLockOn int8u & 0x03 + 2.1 AFActivation int8u & 0x80 + 2.2 FocusPointWrap int8u & 0x08 + 2.3 AFPointIllumination int8u & 0x60 + AFPointIllumination int8u & 0x06 + 2.4 AFPointBrightness int8u & 0x06 + 2.5 AFAssist int8u & 0x01 + 3.1 AFOnButton int8u & 0x07 + 3.2 VerticalAFOnButton int8u & 0x70 + 3.3 AF-OnForMB-D10 int8u & 0x70 + 4.1 FocusTrackingLockOn int8u & 0x07 + 4.2 AssignBktButton int8u & 0x08 + 4.3 MultiSelectorLiveView int8u & 0xc0 + 4.4 InitialZoomLiveView int8u & 0x30 + 6.1 ISOStepSize int8u & 0xc0 + 6.2 ExposureControlStepSize int8u & 0x30 + 6.3 ExposureCompStepSize int8u & 0x0c + 6.4 EasyExposureCompensation int8u & 0x03 + 7.1 CenterWeightedAreaSize int8u & 0xe0 + 7.2 FineTuneOptCenterWeighted int8u & 0x0f + 8.1 FineTuneOptMatrixMetering int8u & 0xf0 + 8.2 FineTuneOptSpotMetering int8u & 0x0f + 9.1 MultiSelectorShootMode int8u & 0xc0 + 9.2 MultiSelectorPlaybackMode int8u & 0x30 + 9.3 InitialZoomSetting int8u & 0x0c + 9.4 MultiSelector int8u & 0x01 + 10.1 ExposureDelayMode int8u & 0x40 + 10.2 CLModeShootingSpeed int8u & 0x07 + 10.3 CHModeShootingSpeed int8u & 0x30 + 11 MaxContinuousRelease int8u + 12.1 ReverseIndicators int8u & 0x20 + 12.2 FileNumberSequence int8u & 0x02 + FileNumberSequence int8u & 0x08 + 12.3 RearDisplay int8u & 0x80 + 12.4 ViewfinderDisplay int8u & 0x40 + 12.5 BatteryOrder int8u & 0x04 + 12.6 MB-D10Batteries int8u & 0x03 + 12.7 ScreenTips int8u & 0x10 + 13.1 Beep int8u & 0xc0 + 13.2 ShootingInfoDisplay int8u & 0x30 + 13.3 GridDisplay int8u & 0x02 + 13.4 ViewfinderWarning int8u & 0x01 + 13.5 MultiSelectorPlaybackMode int8u & 0x03 + 14.1 PreviewButton int8u & 0xf8 + FuncButton int8u & 0xf8 + 14.2 PreviewButtonPlusDials int8u & 0x07 + FuncButtonPlusDials int8u & 0x07 + 15.1 FuncButton int8u & 0xf8 + PreviewButton int8u & 0xf8 + 15.2 FuncButtonPlusDials int8u & 0x07 + PreviewButtonPlusDials int8u & 0x07 + 16.1 AELockButton int8u & 0xf8 + 16.2 AELockButtonPlusDials int8u & 0x07 + 17.1 CommandDialsReverseRotation int8u & 0x80 + 17.2 CommandDialsChangeMainSub int8u & 0x40 + 17.3 CommandDialsApertureSetting int8u & 0x20 + 17.4 CommandDialsMenuAndPlayback int8u & 0x10 + 17.5 LCDIllumination int8u & 0x08 + 17.6 PhotoInfoPlayback int8u & 0x04 + 17.7 ShutterReleaseButtonAE-L int8u & 0x02 + 17.8 ReleaseButtonToUseDial int8u & 0x01 + 18.1 SelfTimerTime int8u & 0x18 + 18.2 MonitorOffTime int8u & 0x07 + 20.1 FlashSyncSpeed int8u & 0xe0 + FlashSyncSpeed int8u & 0xf0 + 20.2 FlashShutterSpeed int8u & 0x0f + 21.1 AutoBracketSet int8u & 0xc0 + AutoBracketSet int8u & 0xe0 + 21.2 AutoBracketModeM int8u & 0x30 + AutoBracketModeM int8u & 0x18 + 21.3 AutoBracketOrder int8u & 0x08 + AutoBracketOrder int8u & 0x04 + 21.4 ModelingFlash int8u & 0x01 + 22.1 NoMemoryCard int8u & 0x80 + 22.2 MeteringTime int8u & 0x0f + 23.1 InternalFlash int8u & 0xc0 + 25.1 ImageReviewTime int8u & 0xe0 + 25.2 PlaybackMonitorOffTime int8u & 0x1c + 26.1 MenuMonitorOffTime int8u & 0xe0 + 26.2 ShootingInfoMonitorOffTime int8u & 0x1c + +=head3 NikonCustom SettingsD700 Tags + +Custom settings for the D700. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 CustomSettingsBank int8u & 0x03 + 0.2 CustomSettingsAllDefault int8u & 0x80 + 1.1 AF-CPrioritySelection int8u & 0xc0 + 1.2 AF-SPrioritySelection int8u & 0x20 + 1.3 AFPointSelection int8u & 0x10 + 1.4 DynamicAFArea int8u & 0x0c + 2.1 AFActivation int8u & 0x80 + 2.2 FocusPointWrap int8u & 0x08 + 2.3 AFPointIllumination int8u & 0x06 + 2.4 AFAssist int8u & 0x01 + 3.1 FocusTrackingLockOn int8u & 0x07 + 3.2 AF-OnForMB-D10 int8u & 0x70 + 4.1 ISOStepSize int8u & 0xc0 + 4.2 ExposureControlStepSize int8u & 0x30 + 4.3 ExposureCompStepSize int8u & 0x0c + 4.4 EasyExposureCompensation int8u & 0x03 + 5.1 CenterWeightedAreaSize int8u & 0x70 + 6.1 FineTuneOptMatrixMetering int8u & 0xf0 + 6.2 FineTuneOptSpotMetering int8u & 0x0f + 7.1 ShutterReleaseButtonAE-L int8u & 0x80 + 7.2 SelfTimerTime int8u & 0x30 + 7.3 MeteringTime int8u & 0x0f + 8.1 PlaybackMonitorOffTime int8u & 0x38 + 8.2 MenuMonitorOffTime int8u & 0x07 + 9.1 ShootingInfoMonitorOffTime int8u & 0x38 + 9.2 ImageReviewTime int8u & 0x07 + 10.1 Beep int8u & 0xc0 + 10.2 ShootingInfoDisplay int8u & 0x30 + 10.3 LCDIllumination int8u & 0x08 + 10.4 ExposureDelayMode int8u & 0x04 + 10.5 GridDisplay int8u & 0x02 + 11.1 FileNumberSequence int8u & 0x40 + 11.2 CLModeShootingSpeed int8u & 0x07 + 12 MaxContinuousRelease int8u + 13.1 ScreenTips int8u & 0x08 + 13.2 BatteryOrder int8u & 0x04 + 13.3 MB-D10BatteryType int8u & 0x03 + 15.1 FlashSyncSpeed int8u & 0xf0 + 15.2 FlashShutterSpeed int8u & 0x0f + 16.1 FlashControlBuilt-in int8u & 0xc0 + 16.2 ManualFlashOutput int8u & 0x1f + 17.1 RepeatingFlashOutput int8u & 0x70 + 17.2 RepeatingFlashCount int8u & 0x0f + 18.1 RepeatingFlashRate int8u & 0xf0 + 18.2 CommanderInternalTTLChannel int8u & 0x03 + 20.1 CommanderInternalTTLCompBuiltin int8u & 0x1f + 21.1 CommanderInternalTTLCompGroupA int8u & 0x1f + 22.1 CommanderInternalTTLCompGroupB int8u & 0x1f + 26.1 AutoBracketSet int8u & 0xc0 + 26.2 AutoBracketModeM int8u & 0x30 + 26.3 AutoBracketOrder int8u & 0x08 + 26.4 ModelingFlash int8u & 0x01 + 27.1 MultiSelectorShootMode int8u & 0xc0 + 27.2 MultiSelectorPlaybackMode int8u & 0x30 + 27.3 InitialZoomSetting int8u & 0x0c + 27.4 MultiSelector int8u & 0x01 + 28.1 FuncButton int8u & 0xf8 + 29.1 PreviewButton int8u & 0xf8 + 30.1 AELockButton int8u & 0xf8 + 31.1 FuncButtonPlusDials int8u & 0x70 + 31.2 PreviewButtonPlusDials int8u & 0x07 + 32.1 AELockButtonPlusDials int8u & 0x70 + 33.1 CommandDialsReverseRotation int8u & 0x80 + 33.2 CommandDialsChangeMainSub int8u & 0x40 + 33.3 CommandDialsApertureSetting int8u & 0x20 + 33.4 CommandDialsMenuAndPlayback int8u & 0x10 + 33.5 ReverseIndicators int8u & 0x08 + 33.6 PhotoInfoPlayback int8u & 0x04 + 33.7 NoMemoryCard int8u & 0x02 + 33.8 ReleaseButtonToUseDial int8u & 0x01 + +=head3 NikonCustom SettingsD800 Tags + +Custom settings for the D800 and D800E. + + Index1 Tag Name Writable + ------ -------- -------- + 12.1 AutoBracketingSet int8u & 0xe0 + 12.2 AutoBracketOrder int8u & 0x10 + 12.3 AutoBracketingMode int8u & 0x0c + 22.1 FlashSyncSpeed int8u & 0xf0 + 22.2 FlashShutterSpeed int8u & 0x0f + 23.1 FlashControlBuilt-in int8u & 0xc0 + 23.2 ManualFlashOutput int8u & 0x1f + 24.1 RepeatingFlashOutput int8u & 0x70 + 24.2 RepeatingFlashCount int8u & 0x0f + 25.1 RepeatingFlashRate int8u & 0xf0 + 25.2 CommanderChannel int8u & 0x03 + 27.1 CommanderInternalFlash int8u & 0xc0 + 27.2 CommanderInternalManualOutput int8u & 0x1f + 28.1 CommanderGroupAMode int8u & 0xc0 + 28.2 CommanderGroupAManualOutput int8u & 0x1f + 29.1 CommanderGroupBMode int8u & 0xc0 + 29.2 CommanderGroupBManualOutput int8u & 0x1f + 30.1 ModelingFlash int8u & 0x20 + 30.2 CommanderInternalTTLComp int8u & 0x1f + 31.1 CommanderGroupA_TTL-AAComp int8u & 0x1f + 32.1 CommanderGroupB_TTL-AAComp int8u & 0x1f + +=head3 NikonCustom SettingsD810 Tags + +Custom settings for the D810. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 LightSwitch int8u & 0x08 + 0.2 CustomSettingsBank int8u & 0x03 + 1.1 AF-CPrioritySelection int8u & 0xc0 + 1.2 AF-SPrioritySelection int8u & 0x20 + 1.3 AFPointSelection int8u & 0x10 + 1.4 FocusTrackingLockOn int8u & 0x07 + 2.1 AFActivation int8u & 0x80 + 2.2 FocusPointWrap int8u & 0x08 + 2.3 AFPointBrightness int8u & 0x06 + 2.4 AFAssist int8u & 0x01 + 3.1 BatteryOrder int8u & 0x40 + 3.2 MB-D12BatteryType int8u & 0x03 + 4.1 Pitch int8u & 0x40 + 4.2 NoMemoryCard int8u & 0x20 + 4.3 ISODisplay int8u & 0x0c + 4.4 GridDisplay int8u & 0x02 + 5.1 ShootingInfoDisplay int8u & 0xc0 + 5.2 LCDIllumination int8u & 0x20 + 5.3 ElectronicFront-CurtainShutter int8u & 0x08 + 5.4 ScreenTips int8u & 0x04 + 5.5 Beep int8u & 0x03 + 6.1 ReverseIndicators int8u & 0x80 + 6.2 CommandDialsReverseRotation int8u & 0x18 + 6.3 EasyExposureCompensation int8u & 0x03 + 7.1 ExposureControlStepSize int8u & 0xc0 + 7.2 ISOStepSize int8u & 0x30 + 7.3 ExposureCompStepSize int8u & 0x0c + 8.1 CenterWeightedAreaSize int8u & 0xe0 + 8.2 FineTuneOptMatrixMetering int8u & 0x0f + 9.1 FineTuneOptCenterWeighted int8u & 0xf0 + 9.2 FineTuneOptSpotMetering int8u & 0x0f + 10.1 MultiSelectorShootMode int8u & 0xc0 + 10.2 MultiSelectorPlaybackMode int8u & 0x30 + 10.3 MultiSelector int8u & 0x01 + 11.1 ExposureDelayMode int8u & 0xc0 + 11.2 CLModeShootingSpeed int8u & 0x0f + 12.1 MaxContinuousRelease int8u + 13.1 AutoBracketSet int8u & 0xe0 + 13.2 AutoBracketOrder int8u & 0x10 + 13.3 AutoBracketModeM int8u & 0x0c + 14.1 FuncButton int8u & 0x1f + 15.1 PreviewButton int8u & 0x1f + 16.1 AssignBktButton int8u & 0x07 + 17.1 AELockButton int8u & 0x1f + 18.1 CommandDialsChangeMainSub int8u & 0xe0 + 18.2 CommandDialsMenuAndPlayback int8u & 0x18 + 18.3 CommandDialsApertureSetting int8u & 0x04 + 18.4 ShutterReleaseButtonAE-L int8u & 0x02 + 18.5 ReleaseButtonToUseDial int8u & 0x01 + 19.1 StandbyTimer int8u & 0xf0 + 20.1 SelfTimerTime int8u & 0xc0 + 20.2 SelfTimerShotInterval int8u & 0x30 + 20.3 SelfTimerShotCount int8u & 0x0f + 21.1 ImageReviewMonitorOffTime int8u & 0xe0 + 21.2 LiveViewMonitorOffTime int8u & 0x1c + 22.1 MenuMonitorOffTime int8u & 0xe0 + 22.2 ShootingInfoMonitorOffTime int8u & 0x1c + 23.1 FlashSyncSpeed int8u & 0xf0 + 23.2 FlashShutterSpeed int8u & 0x0f + 24.1 FlashControlBuilt-in int8u & 0xc0 + 31.1 ModelingFlash int8u & 0x20 + 36.1 PlaybackMonitorOffTime int8u & 0xe0 + 37.1 MultiSelectorLiveView int8u & 0xc0 + 38.1 ShutterSpeedLock int8u & 0x80 + 38.2 ApertureLock int8u & 0x40 + 38.3 MovieShutterButton int8u & 0x20 + 38.4 FlashExposureCompArea int8u & 0x04 + 40.1 MovieAELockButtonAssignment int8u & 0x0f + 41.1 MovieFunctionButton int8u & 0x70 + 41.2 MoviePreviewButton int8u & 0x07 + 42.1 FuncButtonPlusDials int8u & 0x0f + 43.1 PreviewButtonPlusDials int8u & 0x0f + 44.1 AELockButtonPlusDials int8u & 0x0f + 45.1 AssignMovieRecordButton int8u & 0x0f + 46.1 FineTuneOptHighlightWeighted int8u & 0x0f + 47.1 DynamicAreaAFDisplay int8u & 0x80 + 47.2 AFPointIllumination int8u & 0x40 + 47.3 StoreByOrientation int8u & 0x18 + 47.4 GroupAreaAFIllumination int8u & 0x04 + 48.1 MatrixMetering int8u & 0x80 + 48.2 LiveViewButtonOptions int8u & 0x30 + 48.3 AFModeRestrictions int8u & 0x03 + 49.1 LimitAFAreaModeSelection int8u & 0x7e + 50.1 AF-OnForMB-D12 int8u & 0x07 + 51.1 AssignRemoteFnButton int8u & 0x1f + 52.1 LensFocusFunctionButtons int8u & 0x3f + +=head3 NikonCustom SettingsD850 Tags + +Custom settings for the D850. + + Index1 Tag Name Writable + ------ -------- -------- + 0.2 CustomSettingsBank int8u & 0x03 + 1.1 AF-CPrioritySelection int8u & 0xc0 + 1.2 AF-SPrioritySelection int8u & 0x20 + 1.3 AFPointSelection int8u & 0x10 + 1.4 Three-DTrackingFaceDetection int8u & 0x08 + 1.5 BlockShotAFResponse int8u & 0x07 + 2.1 FocusPointWrap int8u & 0x08 + 2.2 AFPointBrightness int8u & 0x06 + 4.1 ISODisplay int8u & 0x08 + 4.2 GridDisplay int8u & 0x02 + 5.1 LCDIllumination int8u & 0x20 + 5.2 ElectronicFront-CurtainShutter int8u & 0x08 + 6.1 ReverseIndicators int8u & 0x80 + 6.2 CommandDialsReverseRotation int8u & 0x18 + 6.3 EasyExposureCompensation int8u & 0x03 + 7.1 ExposureControlStepSize int8u & 0xc0 + 7.2 ISOStepSize int8u & 0x30 + 7.3 ExposureCompStepSize int8u & 0x0c + 8.1 CenterWeightedAreaSize int8u & 0xe0 + 8.2 FineTuneOptMatrixMetering int8u & 0x0f + 9.1 FineTuneOptCenterWeighted int8u & 0xf0 + 9.2 FineTuneOptSpotMetering int8u & 0x0f + 10.1 MultiSelectorShootMode int8u & 0xe0 + 10.2 MultiSelectorPlaybackMode int8u & 0x0c + 10.3 MultiSelector int8u & 0x01 + 11.1 ExposureDelayMode int8u & 0xe0 + 11.2 CLModeShootingSpeed int8u & 0x0f + 12.1 MaxContinuousRelease int8u + 13.1 AutoBracketOrder int8u & 0x10 + 13.2 AutoBracketModeM int8u & 0x0c + 14.1 Func1Button int8u & 0x3f + 15.1 PreviewButton int8u & 0x3f + 16.1 AssignBktButton int8u & 0x07 + 18.1 CommandDialsChangeMainSub int8u & 0xe0 + 18.2 CommandDialsMenuAndPlayback int8u & 0x18 + 18.3 CommandDialsApertureSetting int8u & 0x04 + 18.4 ReleaseButtonToUseDial int8u & 0x01 + 19.1 StandbyTimer int8u & 0xf0 + 20.1 SelfTimerTime int8u & 0xc0 + 20.2 SelfTimerShotInterval int8u & 0x30 + 20.3 SelfTimerShotCount int8u & 0x0f + 21.1 ImageReviewMonitorOffTime int8u & 0xe0 + 21.2 LiveViewMonitorOffTime int8u & 0x1c + 22.1 MenuMonitorOffTime int8u & 0xe0 + 22.2 ShootingInfoMonitorOffTime int8u & 0x1c + 23.1 FlashSyncSpeed int8u & 0xf0 + 23.2 FlashShutterSpeed int8u & 0x0f + 31.1 ModelingFlash int8u & 0x20 + 36.1 PlaybackMonitorOffTime int8u & 0xe0 + 37.1 MultiSelectorLiveView int8u & 0xc0 + 38.1 ShutterSpeedLock int8u & 0x80 + 38.2 ApertureLock int8u & 0x40 + 38.3 MovieShutterButton int8u & 0x10 + 38.4 FlashExposureCompArea int8u & 0x04 + 38.5 AutoFlashISOSensitivity int8u & 0x02 + 41.1 MovieFunc1Button int8u & 0xf0 + 41.2 MoviePreviewButton int8u & 0x0f + 42.1 Func1ButtonPlusDials int8u & 0x0f + 43.1 PreviewButtonPlusDials int8u & 0x0f + 45.1 AssignMovieRecordButtonPlusDials int8u & 0x0f + 46.1 FineTuneOptHighlightWeighted int8u & 0x0f + 47.1 DynamicAreaAFDisplay int8u & 0x80 + 47.2 AFPointIllumination int8u & 0x40 + 47.3 StoreByOrientation int8u & 0x18 + 48.1 MatrixMetering int8u & 0x80 + 48.2 LiveViewButtonOptions int8u & 0x30 + 48.3 AFModeRestrictions int8u & 0x03 + 49.1 LimitAFAreaModeSelection int8u & 0x7e + 52.1 LensFocusFunctionButtons int8u & 0x3f + 66.1 VerticalMultiSelector int8u & 0xff + 67.1 AssignMB-D18FuncButton int8u & 0x3f + 68.1 AssignMB-D18FuncButtonPlusDials int8u & 0x0f + 70.1 AF-OnButton int8u & 0x3f + 71.1 SubSelector int8u & 0x80 + 72.1 SubSelectorCenter int8u & 0x3f + 73.1 SubSelectorPlusDials int8u & 0x0f + 74.1 AssignMovieSubselector int8u & 0xf0 + 75.1 AssignMovieFunc1ButtonPlusDials int8u & 0x10 + 75.2 AssignMoviePreviewButtonPlusDials int8u & 0x01 + 76.1 AssignMovieSubselectorPlusDials int8u & 0x10 + 77.1 SyncReleaseMode int8u & 0x80 + 77.2 ContinuousModeLiveView int8u & 0x40 + 78.1 Three-DTrackingWatchArea int8u & 0x80 + 78.2 SubjectMotion int8u & 0x60 + 78.3 AFActivation int8u & 0x08 + 78.4 ShutterReleaseButtonAE-L int8u & 0x03 + 79.1 AssignMB-D18AF-OnButton int8u & 0x7f + 80.1 Func2Button int8u & 0x3f + 82.1 AssignMovieFunc2Button int8u & 0x70 + +=head3 NikonCustom SettingsD5000 Tags + +Custom settings for the D5000. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 AFAreaModeSetting int8u & 0x60 + 0.2 AFAssist int8u & 0x01 + 2.1 Beep int8u & 0xc0 + 2.2 GridDisplay int8u & 0x02 + 2.3 ISODisplay int8u & 0x08 + 2.4 NoMemoryCard int8u & 0x20 + 3.1 FileNumberSequence int8u & 0x08 + 4.1 RangeFinder int8u & 0x10 + 4.2 DateImprint int8u & 0x08 + 4.3 ReverseIndicators int8u & 0x80 + 5.1 EVStepSize int8u & 0x40 + 9.1 ExposureDelayMode int8u & 0x40 + 11.1 AutoBracketSet int8u & 0xc0 + 12.1 TimerFunctionButton int8u & 0x38 + 15.1 AELockButton int8u & 0x38 + 16.1 ShutterReleaseButtonAE-L int8u & 0x02 + 16.2 CommandDialsReverseRotation int8u & 0x80 + 17.1 MeteringTime int8u & 0x70 + 17.2 RemoteOnDuration int8u & 0x03 + 18.1 SelfTimerTime int8u & 0xc0 + 18.2 SelfTimerShotCount int8u & 0x1e + 19.1 ImageReviewTime int8u & 0xe0 + 20.1 PlaybackMenusTime int8u & 0xe0 + 22.1 InternalFlash int8u & 0xc0 + 22.2 ManualFlashOutput int8u & 0x1f + 32.1 LiveViewAF int8u & 0x60 + +=head3 NikonCustom SettingsD5100 Tags + +Custom settings for the D5100. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 AF-CPrioritySelection int8u & 0x80 + 1.1 AFAssist int8u & 0x01 + 3.1 Beep int8u & 0xc0 + 3.2 NoMemoryCard int8u & 0x20 + 3.3 ISODisplay int8u & 0x08 + 4.1 FileNumberSequence int8u & 0x08 + 5.1 RangeFinder int8u & 0x10 + 5.2 ReverseIndicators int8u & 0x80 + 6.1 EVStepSize int8u & 0x40 + 10.1 ExposureDelayMode int8u & 0x40 + 12.1 AutoBracketSet int8u & 0xc0 + 13.1 TimerFunctionButton int8u & 0x38 + 16.1 AELockButton int8u & 0x38 + 17.1 ShutterReleaseButtonAE-L int8u & 0x02 + 17.2 CommandDialsReverseRotation int8u & 0x80 + 18.1 MeteringTime int8u & 0x70 + 18.2 RemoteOnDuration int8u & 0x03 + 19.1 SelfTimerTime int8u & 0xc0 + 19.2 SelfTimerShotCount int8u & 0x0f + 20.1 ImageReviewTime int8u & 0xe0 + 20.2 LiveViewMonitorOffTime int8u & 0x1c + 21.1 PlaybackMenusTime int8u & 0xe0 + 23.1 ManualFlashOutput int8u & 0x1f + +=head3 NikonCustom SettingsD5200 Tags + +Custom settings for the D5200. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 AF-CPrioritySelection int8u & 0x80 + 0.2 NumberOfFocusPoints int8u & 0x10 + 1.1 AFAssist int8u & 0x01 + 3.1 Beep int8u & 0xc0 + 3.2 NoMemoryCard int8u & 0x20 + 3.3 ISODisplay int8u & 0x08 + 4.1 FileNumberSequence int8u & 0x08 + 5.1 RangeFinder int8u & 0x04 + 5.2 ReverseExposureCompDial int8u & 0x10 + 5.3 ReverseShutterSpeedAperture int8u & 0x08 + 5.4 ReverseIndicators int8u & 0x80 + 6.1 EVStepSize int8u & 0x40 + 10.1 ExposureDelayMode int8u & 0x40 + 12.1 AutoBracketSet int8u & 0xc0 + 13.1 FunctionButton int8u & 0x1f + 16.1 AELockButton int8u & 0x0f + 17.1 ShutterReleaseButtonAE-L int8u & 0x02 + 18.1 StandbyTimer int8u & 0xe0 + 18.2 RemoteOnDuration int8u & 0x03 + 19.1 SelfTimerTime int8u & 0xc0 + 19.2 SelfTimerShotCount int8u & 0x0f + 20.1 ImageReviewTime int8u & 0xe0 + 20.2 LiveViewMonitorOffTime int8u & 0x1c + 21.1 PlaybackMenusTime int8u & 0xe0 + 23.1 InternalFlash int8u & 0xc0 + 23.2 ManualFlashOutput int8u & 0x1f + +=head3 NikonCustom SettingsD7000 Tags + +Custom settings for the D7000. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 AF-CPrioritySelection int8u & 0x80 + 0.2 AF-SPrioritySelection int8u & 0x20 + 0.3 NumberOfFocusPoints int8u & 0x10 + 0.4 FocusTrackingLockOn int8u & 0x07 + 1.1 FocusPointWrap int8u & 0x08 + 1.2 AFPointIllumination int8u & 0x06 + 1.3 AFAssist int8u & 0x01 + 2.1 BatteryOrder int8u & 0x40 + 2.2 AF-OnForMB-D11 int8u & 0x1c + 2.3 MB-D11BatteryType int8u & 0x03 + 3.1 BeepPitch int8u & 0xc0 + 3.2 NoMemoryCard int8u & 0x20 + 3.3 ISODisplay int8u & 0x0c + 3.4 GridDisplay int8u & 0x02 + 3.5 ViewfinderWarning int8u & 0x01 + 4.1 ShootingInfoDisplay int8u & 0xc0 + 4.2 LCDIllumination int8u & 0x20 + 4.3 FileNumberSequence int8u & 0x08 + 4.4 ScreenTips int8u & 0x04 + 4.5 BeepVolume int8u & 0x03 + 5.1 ReverseIndicators int8u & 0x80 + 5.2 EasyExposureCompensation int8u & 0x03 + 6.1 ExposureControlStep int8u & 0x40 + 6.2 ISOSensitivityStep int8u & 0x10 + 7.1 CenterWeightedAreaSize int8u & 0xe0 + 10.1 ExposureDelayMode int8u & 0x40 + 10.2 CLModeShootingSpeed int8u & 0x07 + 11 MaxContinuousRelease int8u + 12.1 AutoBracketSet int8u & 0xe0 + 12.2 AutoBracketOrder int8u & 0x10 + 13.1 FuncButton int8u & 0xf8 + 14.1 PreviewButton int8u & 0xf8 + 15.1 OKButton int8u & 0x18 + 16.1 AELockButton int8u & 0x38 + 17.1 CommandDialsReverseRotation int8u & 0x80 + 17.2 CommandDialsChangeMainSub int8u & 0x60 + 17.3 CommandDialsApertureSetting int8u & 0x04 + 17.4 CommandDialsMenuAndPlayback int8u & 0x18 + 17.5 ShutterReleaseButtonAE-L int8u & 0x02 + 17.6 ReleaseButtonToUseDial int8u & 0x01 + 18.1 MeteringTime int8u & 0xf0 + 18.2 RemoteOnDuration int8u & 0x03 + 19.1 SelfTimerTime int8u & 0xc0 + 19.2 SelfTimerInterval int8u & 0x30 + 19.3 SelfTimerShotCount int8u & 0x0f + 20.1 ImageReviewTime int8u & 0xe0 + 20.2 LiveViewMonitorOffTime int8u & 0x1c + 21.1 MenuMonitorOffTime int8u & 0xe0 + 21.2 ShootingInfoMonitorOffTime int8u & 0x1c + 22.1 FlashSyncSpeed int8u & 0xf0 + 22.2 FlashShutterSpeed int8u & 0x0f + 23.1 FlashControlBuilt-in int8u & 0xc0 + 23.2 ManualFlashOutput int8u & 0x1f + 24.1 RepeatingFlashOutput int8u & 0x70 + 24.2 RepeatingFlashCount int8u & 0x0f + 25.1 RepeatingFlashRate int8u & 0xf0 + 26.1 CommanderInternalTTLCompBuiltin int8u & 0x1f + 27.1 CommanderInternalTTLCompGroupA int8u & 0x1f + 28.1 CommanderInternalTTLCompGroupB int8u & 0x1f + 30.1 FlashWarning int8u & 0x80 + 30.2 ModelingFlash int8u & 0x20 + 34.1 LiveViewAFAreaMode int8u & 0x60 + 34.2 LiveViewAFMode int8u & 0x02 + 35.1 PlaybackMonitorOffTime int8u & 0xe0 + +=head3 NikonCustom SettingsD4 Tags + +Custom settings for the D4 and D4S. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 CustomSettingsBank int8u & 0x03 + 1.1 AF-CPrioritySelection int8u & 0xc0 + 1.2 AF-SPrioritySelection int8u & 0x20 + 1.3 AFPointSelection int8u & 0x10 + 1.4 FocusTrackingLockOn int8u & 0x07 + 2.1 AFActivation int8u & 0x80 + 2.2 FocusPointWrap int8u & 0x08 + 4.1 Pitch int8u & 0x40 + 4.2 NoMemoryCard int8u & 0x20 + 4.3 GridDisplay int8u & 0x02 + 5.1 ShootingInfoDisplay int8u & 0xc0 + 5.2 LCDIllumination int8u & 0x20 + 5.3 ScreenTips int8u & 0x04 + 5.4 Beep int8u & 0x03 + 6.1 ReverseIndicators int8u & 0x80 + 6.2 RearDisplay int8u & 0x40 + 6.3 ViewfinderDisplay int8u & 0x20 + 6.4 CommandDialsReverseRotation int8u & 0x18 + 6.5 EasyExposureCompensation int8u & 0x03 + 7.1 ExposureControlStepSize int8u & 0xc0 + 7.2 ISOStepSize int8u & 0x30 + 7.3 ExposureCompStepSize int8u & 0x0c + 8.1 CenterWeightedAreaSize int8u & 0xe0 + 8.2 FineTuneOptMatrixMetering int8u & 0x0f + 9.1 FineTuneOptCenterWeighted int8u & 0xf0 + 9.2 FineTuneOptSpotMetering int8u & 0x0f + 10.1 MultiSelectorShootMode int8u & 0xc0 + 10.2 MultiSelectorPlaybackMode int8u & 0x30 + 10.3 MultiSelector int8u & 0x01 + 11.1 ExposureDelayMode int8u & 0xc0 + 11.2 CHModeShootingSpeed int8u & 0x10 + 11.3 CLModeShootingSpeed int8u & 0x0f + 12 MaxContinuousRelease int8u + 13.1 AutoBracketSet int8u & 0xe0 + 13.2 AutoBracketOrder int8u & 0x10 + 13.3 AutoBracketModeM int8u & 0x0c + 14.1 FuncButton int8u & 0xf8 + 14.2 FuncButtonPlusDials int8u & 0x07 + 15.1 PreviewButton int8u & 0xf8 + 15.2 PreviewButtonPlusDials int8u & 0x07 + 16.1 AssignBktButton int8u & 0x07 + 18.1 CommandDialsChangeMainSub int8u & 0xe0 + 18.2 CommandDialsMenuAndPlayback int8u & 0x18 + 18.3 CommandDialsApertureSetting int8u & 0x04 + 18.4 ShutterReleaseButtonAE-L int8u & 0x02 + 18.5 ReleaseButtonToUseDial int8u & 0x01 + 19.1 StandbyTimer int8u & 0xf0 + 20.1 SelfTimerTime int8u & 0xc0 + 20.2 SelfTimerShotCount int8u & 0x0f + 20.3 SelfTimerShotInterval int8u & 0x30 + 21.1 ImageReviewMonitorOffTime int8u & 0xe0 + 21.2 LiveViewMonitorOffTime int8u & 0x1c + 22.1 MenuMonitorOffTime int8u & 0xe0 + 22.2 ShootingInfoMonitorOffTime int8u & 0x1c + 23.1 FlashSyncSpeed int8u & 0xf0 + 23.2 FlashShutterSpeed int8u & 0x0f + 31.1 ModelingFlash int8u & 0x20 + 36.1 PlaybackMonitorOffTime int8u & 0xe0 + 37.1 PlaybackZoom int8u & 0x01 + 38.1 ShutterSpeedLock int8u & 0x80 + 38.2 ApertureLock int8u & 0x40 + 38.3 MovieShutterButton int8u & 0x30 + 38.4 FlashExposureCompArea int8u & 0x04 + 41.1 MovieFunctionButton int8u & 0x70 + 41.2 MoviePreviewButton int8u & 0x07 + 42.1 VerticalMultiSelector int8u & 0x60 + 42.2 VerticalFuncButton int8u & 0x1f + 43.1 VerticalFuncButtonPlusDials int8u & 0xf0 + 43.2 AssignMovieRecordButton int8u & 0x07 + 46.1 DynamicAreaAFDisplay int8u & 0x80 + 46.2 AFPointIllumination int8u & 0x60 + 46.3 StoreByOrientation int8u & 0x18 + 46.4 GroupAreaAFIllumination int8u & 0x04 + 46.5 AFPointBrightness int8u & 0x03 + 47.1 AFOnButton int8u & 0x70 + 47.2 VerticalAFOnButton int8u & 0x07 + 48.1 SubSelectorAssignment int8u & 0x80 + 48.2 MovieSubSelectorAssignment int8u & 0x07 + 49.1 SubSelector int8u & 0xf8 + 49.2 SubSelectorPlusDials int8u & 0x07 + 50.1 MatrixMetering int8u & 0x80 + 50.2 LiveViewButtonOptions int8u & 0x30 + 50.3 AFModeRestrictions int8u & 0x03 + 51.1 LimitAFAreaModeSelection int8u & 0x7e + 52.1 MovieFunctionButtonPlusDials int8u & 0x10 + 52.2 MoviePreviewButtonPlusDials int8u & 0x01 + 53.1 MovieSubSelectorAssignmentPlusDials int8u & 0x10 + 54.1 AssignRemoteFnButton int8u & 0x1f + 55.1 LensFocusFunctionButtons int8u & 0x3f + +=head3 NikonCustom SettingsD5 Tags + +Custom settings for the D5. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 CustomSettingsBank int8u & 0x03 + 1.1 AF-CPrioritySelection int8u & 0xc0 + 1.2 AF-SPrioritySelection int8u & 0x20 + 1.3 NumberOfFocusPoints int8u & 0x10 + 1.4 Three-DTrackingFaceDetection int8u & 0x08 + 1.5 BlockShotAFResponse int8u & 0x07 + 2.1 FocusPointWrap int8u & 0x08 + 2.2 AFPointBrightness int8u & 0x06 + 4.1 ISODisplay int8u & 0x08 + 4.2 GridDisplay int8u & 0x02 + 5.1 LCDIllumination int8u & 0x20 + 5.2 ElectronicFront-CurtainShutter int8u & 0x08 + 6.1 ReverseIndicators int8u & 0x80 + 6.2 CommandDialsReverseRotation int8u & 0x18 + 6.3 EasyExposureCompensation int8u & 0x03 + 7.1 ExposureControlStepSize int8u & 0xc0 + 7.2 ISOStepSize int8u & 0x30 + 7.3 ExposureCompStepSize int8u & 0x0c + 8.1 CenterWeightedAreaSize int8u & 0xe0 + 8.2 FineTuneOptMatrixMetering int8u & 0x0f + 9.1 FineTuneOptCenterWeighted int8u & 0xf0 + 9.2 FineTuneOptSpotMetering int8u & 0x0f + 10.1 MultiSelectorShootMode int8u & 0xe0 + 10.3 MultiSelector int8u & 0x01 + 11.1 ExposureDelayMode int8u & 0xc0 + 11.2 CLModeShootingSpeed int8u & 0x0f + 12.1 MaxContinuousRelease int8u + 13.1 AutoBracketOrder int8u & 0x10 + 13.2 AutoBracketModeM int8u & 0x0c + 14.1 Func1Button int8u & 0x3f + 15.1 PreviewButton int8u & 0x3f + 16.1 AssignBktButton int8u & 0x07 + 18.1 CommandDialsChangeMainSub int8u & 0xe0 + 18.2 CommandDialsMenuAndPlayback int8u & 0x18 + 18.3 CommandDialsApertureSetting int8u & 0x04 + 18.4 ReleaseButtonToUseDial int8u & 0x01 + 19.1 StandbyTimer int8u & 0xf0 + 20.1 SelfTimerTime int8u & 0xc0 + 20.2 SelfTimerShotInterval int8u & 0x30 + 20.3 SelfTimerShotCount int8u & 0x0f + 21.1 ImageReviewMonitorOffTime int8u & 0xe0 + 21.2 LiveViewMonitorOffTime int8u & 0x1c + 22.1 MenuMonitorOffTime int8u & 0xe0 + 22.2 ShootingInfoMonitorOffTime int8u & 0x1c + 23.1 FlashSyncSpeed int8u & 0xf0 + 23.2 FlashShutterSpeed int8u & 0x0f + 31.1 ModelingFlash int8u & 0x20 + 36.1 PlaybackMonitorOffTime int8u & 0xe0 + 37.1 MultiSelectorLiveView int8u & 0xc0 + 38.1 ShutterSpeedLock int8u & 0x80 + 38.2 ApertureLock int8u & 0x40 + 38.3 MovieShutterButton int8u & 0x10 + 38.4 FlashExposureCompArea int8u & 0x04 + 38.5 AutoFlashISOSensitivity int8u & 0x02 + 41.1 MovieFunc1Button int8u & 0xf0 + 41.2 MoviePreviewButton int8u & 0x0f + 42.1 Func1ButtonPlusDials int8u & 0x0f + 43.1 PreviewButtonPlusDials int8u & 0x0f + 45.1 AssignMovieRecordButtonPlusDials int8u & 0x0f + 46.1 FineTuneOptHighlightWeighted int8u & 0x0f + 47.1 DynamicAreaAFDisplay int8u & 0x80 + 47.2 AFPointIllumination int8u & 0x40 + 47.3 StoreByOrientation int8u & 0x18 + 48.1 MatrixMetering int8u & 0x80 + 48.2 LiveViewButtonOptions int8u & 0x30 + 48.3 AFModeRestrictions int8u & 0x03 + 49.1 LimitAFAreaModeSelection int8u & 0x7e + 52.1 LensFocusFunctionButtons int8u & 0x3f + 66.1 VerticalMultiSelector int8u & 0xff + 67.1 VerticalFuncButton int8u & 0x3f + 68.1 VerticalFuncPlusDials int8u & 0x0f + 70.1 AF-OnButton int8u & 0x3f + 71.1 SubSelector int8u & 0x80 + 72.1 SubSelectorCenter int8u & 0x3f + 73.1 SubSelectorPlusDials int8u & 0x0f + 74.1 AssignMovieSubselector int8u & 0xf0 + 75.1 AssignMovieFunc1ButtonPlusDials int8u & 0x10 + 75.2 AssignMoviePreviewButtonPlusDials int8u & 0x01 + 76.1 AssignMovieSubselectorPlusDials int8u & 0x10 + 77.1 SyncReleaseMode int8u & 0x80 + 78.1 Three-DTrackingWatchArea int8u & 0x80 + 78.2 SubjectMotion int8u & 0x60 + 78.3 AFActivation int8u & 0x08 + 78.4 ShutterReleaseButtonAE-L int8u & 0x03 + 79.1 VerticalAFOnButton int8u & 0x7f + 80.1 Func2Button int8u & 0x3f + 81.1 Func2ButtonPlusDials int8u & 0x0f + 82.1 AssignMovieFunc2Button int8u & 0x70 + 83.1 Func3Button int8u & 0x03 + +=head3 NikonCustom SettingsD500 Tags + +Custom settings for the D500. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 CustomSettingsBank int8u & 0x03 + 1.1 AF-CPrioritySelection int8u & 0xc0 + 1.2 AF-SPrioritySelection int8u & 0x20 + 1.3 NumberOfFocusPoints int8u & 0x10 + 1.4 Three-DTrackingFaceDetection int8u & 0x08 + 1.5 BlockShotAFResponse int8u & 0x07 + 2.1 FocusPointWrap int8u & 0x08 + 2.2 AFPointBrightness int8u & 0x06 + 4.1 ISODisplay int8u & 0x08 + 4.2 GridDisplay int8u & 0x02 + 5.1 LCDIllumination int8u & 0x20 + 5.2 ElectronicFront-CurtainShutter int8u & 0x08 + 6.1 ReverseIndicators int8u & 0x80 + 6.2 CommandDialsReverseRotation int8u & 0x18 + 6.3 EasyExposureCompensation int8u & 0x03 + 7.1 ExposureControlStepSize int8u & 0xc0 + 7.2 ISOStepSize int8u & 0x30 + 7.3 ExposureCompStepSize int8u & 0x0c + 8.1 CenterWeightedAreaSize int8u & 0xe0 + 8.2 FineTuneOptMatrixMetering int8u & 0x0f + 9.1 FineTuneOptCenterWeighted int8u & 0xf0 + 9.2 FineTuneOptSpotMetering int8u & 0x0f + 10.1 MultiSelectorShootMode int8u & 0xe0 + 10.2 MultiSelectorPlaybackMode int8u & 0x0c + 10.3 MultiSelector int8u & 0x01 + 11.1 ExposureDelayMode int8u & 0xc0 + 11.2 CLModeShootingSpeed int8u & 0x0f + 12.1 MaxContinuousRelease int8u + 13.1 AutoBracketOrder int8u & 0x10 + 13.2 AutoBracketModeM int8u & 0x0c + 14.1 Func1Button int8u & 0x3f + 15.1 PreviewButton int8u & 0x3f + 16.1 AssignBktButton int8u & 0x07 + 18.1 CommandDialsChangeMainSub int8u & 0xe0 + 18.2 CommandDialsMenuAndPlayback int8u & 0x18 + 18.3 CommandDialsApertureSetting int8u & 0x04 + 18.4 ReleaseButtonToUseDial int8u & 0x01 + 19.1 StandbyTimer int8u & 0xf0 + 20.1 SelfTimerTime int8u & 0xc0 + 20.2 SelfTimerShotInterval int8u & 0x30 + 20.3 SelfTimerShotCount int8u & 0x0f + 21.1 ImageReviewMonitorOffTime int8u & 0xe0 + 21.2 LiveViewMonitorOffTime int8u & 0x1c + 22.1 MenuMonitorOffTime int8u & 0xe0 + 22.2 ShootingInfoMonitorOffTime int8u & 0x1c + 23.1 FlashSyncSpeed int8u & 0xf0 + 23.2 FlashShutterSpeed int8u & 0x0f + 31.1 ModelingFlash int8u & 0x20 + 36.1 PlaybackMonitorOffTime int8u & 0xe0 + 37.1 MultiSelectorLiveView int8u & 0xc0 + 38.1 ShutterSpeedLock int8u & 0x80 + 38.2 ApertureLock int8u & 0x40 + 38.3 MovieShutterButton int8u & 0x10 + 38.4 FlashExposureCompArea int8u & 0x04 + 38.5 AutoFlashISOSensitivity int8u & 0x02 + 41.1 MovieFunc1Button int8u & 0xf0 + 41.2 MoviePreviewButton int8u & 0x0f + 42.1 Func1ButtonPlusDials int8u & 0x0f + 43.1 PreviewButtonPlusDials int8u & 0x0f + 45.1 AssignMovieRecordButtonPlusDials int8u & 0x0f + 46.1 FineTuneOptHighlightWeighted int8u & 0x0f + 47.1 DynamicAreaAFDisplay int8u & 0x80 + 47.2 AFPointIllumination int8u & 0x40 + 47.3 StoreByOrientation int8u & 0x18 + 47.4 GroupAreaAFIllumination int8u & 0x04 + 48.1 MatrixMetering int8u & 0x80 + 48.2 LiveViewButtonOptions int8u & 0x30 + 48.3 AFModeRestrictions int8u & 0x03 + 49.1 LimitAFAreaModeSelection int8u & 0x7e + 52.1 LensFocusFunctionButtons int8u & 0x3f + 66.1 VerticalMultiSelector int8u & 0xff + 67.1 AssignMB-D17FuncButton int8u & 0x3f + 68.1 AssignMB-D17FuncButtonPlusDials int8u & 0x0f + 70.1 AF-OnButton int8u & 0x3f + 71.1 SubSelector int8u & 0x80 + 72.1 SubSelectorCenter int8u & 0x3f + 73.1 SubSelectorPlusDials int8u & 0x0f + 74.1 AssignMovieSubselector int8u & 0xf0 + 75.1 AssignMovieFunc1ButtonPlusDials int8u & 0x10 + 75.2 AssignMoviePreviewButtonPlusDials int8u & 0x01 + 76.1 AssignMovieSubselectorPlusDials int8u & 0x10 + 77.1 SyncReleaseMode int8u & 0x80 + 78.1 Three-DTrackingWatchArea int8u & 0x80 + 78.2 SubjectMotion int8u & 0x60 + 78.3 AFActivation int8u & 0x08 + 78.4 ShutterReleaseButtonAE-L int8u & 0x03 + 79.1 AssignMB-D17AF-OnButton int8u & 0x7f + 80.1 Func2Button int8u & 0x3f + 82.1 AssignMovieFunc2Button int8u & 0x70 + +=head3 NikonCustom SettingsD610 Tags + +Custom settings for the D610. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 AF-CPrioritySelection int8u & 0x80 + 0.2 AF-SPrioritySelection int8u & 0x20 + 0.3 NumberOfFocusPoints int8u & 0x10 + 0.4 FocusTrackingLockOn int8u & 0x07 + 1.1 FocusPointWrap int8u & 0x08 + 1.2 AFPointIllumination int8u & 0x06 + 1.3 AFAssist int8u & 0x01 + 5.1 EasyExposureCompensation int8u & 0x03 + 6.1 ExposureControlStep int8u & 0x40 + 6.2 ISOSensitivityStep int8u & 0x10 + 7.1 CenterWeightedAreaSize int8u & 0xe0 + 7.2 FineTuneOptMatrixMetering int8u & 0x0f + 8.1 FineTuneOptCenterWeighted int8u & 0xf0 + 8.2 FineTuneOptSpotMetering int8u & 0x0f + 17.1 ShutterReleaseButtonAE-L int8u & 0x02 + 18.1 StandbyTimer int8u & 0xf0 + 18.2 RemoteOnDuration int8u & 0x03 + 19.1 SelfTimerTime int8u & 0xc0 + 19.2 SelfTimerShotInterval int8u & 0x30 + 19.3 SelfTimerShotCount int8u & 0x0f + 20.1 ImageReviewMonitorOffTime int8u & 0xe0 + 20.2 LiveViewMonitorOffTime int8u & 0x1c + 21.1 MenuMonitorOffTime int8u & 0xe0 + 21.2 ShootingInfoMonitorOffTime int8u & 0x1c + 35.1 PlaybackMonitorOffTime int8u & 0xe0 + +=head3 NikonCustom SettingsZ8 Tags + +Custom settings for the Z8. + + Index1 Tag Name Writable + ------ -------- -------- + 1 CustomSettingsBank int8u + 3 AF-CPrioritySelection int8u + 5 AF-SPrioritySelection int8u + 7 BlockShotAFResponse int8u + 11 AFPointSel int8u + 13 StoreByOrientation int8u + 15 AFActivation int8u + 16 AF-OnOutOfFocusRelease? int8u + 17 LimitAF-AreaModeSelPinpoint? int8u + 19 LimitAF-AreaModeSelWideAF_S? int8u + 20 LimitAF-AreaModeSelWideAF_L? int8u + 21 LimitAFAreaModeSelAuto? int8u + 22 FocusPointWrap? int8u + 23 ManualFocusPointIllumination? int8u + 24 DynamicAreaAFAssist? int8u + 25 AF-AssistIlluminator int8u + 26 ManualFocusRingInAFMode int8u + 27 ExposureControlStepSize int8u + 29 EasyExposureCompensation int8u + 31 CenterWeightedAreaSize int8u + 33 FineTuneOptMatrixMetering int8s + 35 FineTuneOptCenterWeighted int8s + 37 FineTuneOptSpotMetering int8s + 39 FineTuneOptHighlightWeighted int8s + 41 ShutterReleaseButtonAE-L int8u + 43 SelfTimerTime int8u + 45 SelfTimerShotCount int8u + 49 SelfTimerShotInterval int8u + 51 PlaybackMonitorOffTime int8u + 53 MenuMonitorOffTime int8u + 55 ShootingInfoMonitorOffTime int8u + 57 ImageReviewMonitorOffTime int8u + 59 CLModeShootingSpeed int8u + 61 MaxContinuousRelease no + 65 SyncReleaseMode? int8u + 69 LimitSelectableImageAreaDX? int8u + 70 LimitSelectableImageArea1To1? int8u + 71 LimitSelectableImageArea16To9? int8u + 72 FileNumberSequence int8u + 73 FocusPeakingLevel? int8u + 75 FocusPeakingHighlightColor? int8u + 81 ContinuousModeDisplay int8u + 83 FlashSyncSpeed no + 85 HighSpeedSync int8u + 87 FlashShutterSpeed no + 89 FlashExposureCompArea int8u + 91 AutoFlashISOSensitivity int8u + 93 ModelingFlash int8u + 95 AutoBracketModeM int8u + 97 AutoBracketOrder int8u + 99 Func1Button int8u + 115 Func2Button int8u + 131 AFOnButton int8u + 143 SubSelector? int8u + 155 AssignMovieRecordButton? int8u + 159 LensFunc1Button int8u + 167 LensFunc2Button int8u + 173 LensControlRing int8u + 175 MultiSelectorShootMode int8u + 179 MultiSelectorPlaybackMode int8u + 183 ShutterSpeedLock int8u + 184 ApertureLock int8u + 186 CmdDialsReverseRotation int8u + 191 UseDialWithoutHold? int8u + 193 ReverseIndicators? int8u + 195 MovieFunc1Button int8u + 199 MovieFunc2Button int8u + 203 MovieAF-OnButton int8u + 215 MovieLensControlRing int8u + 217 MovieMultiSelector? int8u + 221 MovieAFSpeed int8u + 223 MovieAFSpeedApply int8u + 225 MovieAFTrackingSensitivity int8u + 257 LCDIllumination? int8u + 258 ExtendedShutterSpeeds int8u + 259 SubjectMotion int8u + 261 FocusPointPersistence int8u + 263 AutoFocusModeRestrictions? int8u + 267 CHModeShootingSpeed int8u + 273 FlashBurstPriority? int8u + 335 LimitAF-AreaModeSelDynamic_S? int8u + 336 LimitAF-AreaModeSelDynamic_M? int8u + 337 LimitAF-AreaModeSelDynamic_L? int8u + 339 LimitAF-AreaModeSel3DTracking? int8u + 341 PlaybackFlickUp? int8u + 345 PlaybackFlickDown? int8u + 349 ISOStepSize int8u + 355 ReverseFocusRing int8u + 356 EVFImageFrame? int8u + 357 EVFGrid? int8u + 359 VirtualHorizonStyle? int8u + 421 Func1ButtonPlaybackMode? int8u + 423 Func2ButtonPlaybackMode? int8u + 437 MovieRecordButtonPlaybackMode? int8u + 459 CommandDialPlaybackMode? int8u + 463 SubCommandDialPlaybackMode? int8u + 467 FocusPointLock? int8u + 469 ControlRingResponse int8u + 515 MovieAFAreaMode? int8u + 529 ZebraPatternToneRange? int8u + 531 MovieZebraPattern? int8u + 533 MovieHighlightDisplayThreshold? int8u + 535 MovieMidtoneDisplayValue? int8u + 537 MovieMidtoneDisplayRange? int8u~ + 541 MovieEVFGrid? int8u + 549 MovieShutterSpeedLock? int8u + 550 MovieFocusPointLock? int8u + 563 MatrixMetering? int8u + 564 AF-CFocusDisplay int8u + 565 FocusPeakingDisplay? int8u + 567 KeepExposure int8u + 585 StarlightView? int8u + 587 EVFWarmDisplayMode? int8u + 589 EVFWarmDisplayBrightness? int8s + 591 EVFReleaseIndicator? int8u + 601 MovieApertureLock? int8u + 607 FlickAdvanceDirection? int8u + 647 PreReleaseBurstLength int8u + 649 PostReleaseBurstLength int8u + 681 ViewModeShowEffectsOfSettings? int8u + 683 DispButton int8u + +=head3 NikonCustom SettingsZ9 Tags + +Custom settings for the Z9. + + Index1 Tag Name Writable + ------ -------- -------- + 1 CustomSettingsBank int8u + 3 AF-CPrioritySelection int8u + 5 AF-SPrioritySelection int8u + 7 BlockShotAFResponse int8u + 11 AFPointSel int8u + 13 StoreByOrientation int8u + 15 AFActivation int8u + 16 AF-OnOutOfFocusRelease? int8u + 17 LimitAF-AreaModeSelPinpoint? int8u + 19 LimitAF-AreaModeSelWideAF_S? int8u + 20 LimitAF-AreaModeSelWideAF_L? int8u + 21 LimitAFAreaModeSelAuto? int8u + 22 FocusPointWrap? int8u + 23 ManualFocusPointIllumination? int8u + 24 DynamicAreaAFAssist? int8u + 25 AF-AssistIlluminator int8u + 26 ManualFocusRingInAFMode int8u + 27 ExposureControlStepSize int8u + 29 EasyExposureCompensation int8u + 31 CenterWeightedAreaSize int8u + 33 FineTuneOptMatrixMetering int8s + 35 FineTuneOptCenterWeighted int8s + 37 FineTuneOptSpotMetering int8s + 39 FineTuneOptHighlightWeighted int8s + 41 ShutterReleaseButtonAE-L int8u + 43 SelfTimerTime int8u + 45 SelfTimerShotCount int8u + 49 SelfTimerShotInterval int8u + 51 PlaybackMonitorOffTime int8u + 53 MenuMonitorOffTime int8u + 55 ShootingInfoMonitorOffTime int8u + 57 ImageReviewMonitorOffTime int8u + 59 CLModeShootingSpeed int8u + 61 MaxContinuousRelease no + 65 SyncReleaseMode? int8u + 69 LimitSelectableImageAreaDX? int8u + 70 LimitSelectableImageArea1To1? int8u + 71 LimitSelectableImageArea16To9? int8u + 72 FileNumberSequence int8u + 73 FocusPeakingLevel? int8u + 75 FocusPeakingHighlightColor? int8u + 81 ContinuousModeDisplay int8u + 83 FlashSyncSpeed no + 85 HighSpeedSync int8u + 87 FlashShutterSpeed no + 89 FlashExposureCompArea int8u + 91 AutoFlashISOSensitivity int8u + 93 ModelingFlash int8u + 95 AutoBracketModeM int8u + 97 AutoBracketOrder int8u + 99 Func1Button int8u + 115 Func2Button int8u + 131 AFOnButton int8u + 143 SubSelector? int8u + 155 AssignMovieRecordButton? int8u + 159 LensFunc1Button int8u + 167 LensFunc2Button int8u + 173 LensControlRing int8u + 175 MultiSelectorShootMode int8u + 179 MultiSelectorPlaybackMode int8u + 183 ShutterSpeedLock int8u + 184 ApertureLock int8u + 186 CmdDialsReverseRotation int8u + 191 UseDialWithoutHold? int8u + 193 ReverseIndicators? int8u + 195 MovieFunc1Button int8u + 199 MovieFunc2Button int8u + 203 MovieAF-OnButton int8u + 207 MovieMultiSelector? int8u + 215 MovieLensControlRing int8u + 221 MovieAFSpeed int8u + 223 MovieAFSpeedApply int8u + 225 MovieAFTrackingSensitivity int8u + 257 LCDIllumination? int8u + 258 ExtendedShutterSpeeds int8u + 259 SubjectMotion int8u + 261 FocusPointPersistence int8u + 263 AutoFocusModeRestrictions? int8u + 267 CHModeShootingSpeed int8u + 269.1 LimitReleaseModeSelCL? int8u & 0x02 + 269.2 LimitReleaseModeSelCH? int8u & 0x04 + 269.3 LimitReleaseModeSelC30? int8u & 0x10 + 269.4 LimitReleaseModeSelC120? int8u & 0x40 + 269.5 LimitReleaseModeSelSelf? int8u & 0x80 + 273 FlashBurstPriority? int8u + 277 VerticalFuncButton int8u + 281 Func3Button int8u + 285 VerticalAFOnButton int8u + 293 VerticalMultiSelectorPlaybackMode? int8u + 295 MovieFunc3Button int8u + 335 LimitAF-AreaModeSelDynamic_S? int8u + 336 LimitAF-AreaModeSelDynamic_M? int8u + 337 LimitAF-AreaModeSelDynamic_L? int8u + 339 LimitAF-AreaModeSel3DTracking? int8u + 341 PlaybackFlickUp? int8u + 345 PlaybackFlickDown? int8u + 349 ISOStepSize int8u + 355 ReverseFocusRing int8u + 356 EVFImageFrame? int8u + 357 EVFGrid? int8u + 359 VirtualHorizonStyle? int8u + 373 Func4Button? int8u + 379 AudioButton? int8u + 381 QualityButton? int8u + 399 VerticalMultiSelector? int8u + 421 Func1ButtonPlaybackMode? int8u + 423 Func2ButtonPlaybackMode? int8u + 425 Func3ButtonPlaybackMode? int8u + 431 Func4ButtonPlaybackMode? int8u + 437 MovieRecordButtonPlaybackMode? int8u + 439 VerticalFuncButtonPlaybackMode? int8u + 441 AudioButtonPlaybackMode? int8u + 447 QualityButtonPlaybackMode? int8u + 453 WhiteBalanceButtonPlaybackMode? int8u + 459 CommandDialPlaybackMode? int8u + 463 SubCommandDialPlaybackMode? int8u + 467 FocusPointLock? int8u + 469 ControlRingResponse int8u + 481 VerticalMovieFuncButton? int8u + 505 VerticalMovieAFOnButton? int8u + 515 MovieAFAreaMode? int8u + 527 HDMIViewAssist? int8u + 529 ZebraPatternToneRange? int8u + 531 MovieZebraPattern? int8u + 533 MovieHighlightDisplayThreshold? int8u + 535 MovieMidtoneDisplayValue? int8u + 537 MovieMidtoneDisplayRange? int8u~ + 541 MovieEVFGrid? int8u + 549 MovieShutterSpeedLock? int8u + 550 MovieFocusPointLock? int8u + 563 MatrixMetering? int8u + 564 AF-CFocusDisplay int8u + 565 FocusPeakingDisplay? int8u + 567 KeepExposure int8u + 585 StarlightView? int8u + 587 EVFWarmDisplayMode? int8u + 589 EVFWarmDisplayBrightness? int8s + 591 EVFReleaseIndicator? int8u + 601 MovieApertureLock? int8u + 607 FlickAdvanceDirection? int8u + +=head3 NikonCustom SettingsZ9v4 Tags + +Custom settings for the Z9. + + Index1 Tag Name Writable + ------ -------- -------- + 1 CustomSettingsBank int8u + 3 AF-CPrioritySelection int8u + 5 AF-SPrioritySelection int8u + 7 BlockShotAFResponse int8u + 11 AFPointSel int8u + 13 StoreByOrientation int8u + 15 AFActivation int8u + 16 AF-OnOutOfFocusRelease? int8u + 17 LimitAF-AreaModeSelPinpoint? int8u + 19 LimitAF-AreaModeSelWideAF_S? int8u + 20 LimitAF-AreaModeSelWideAF_L? int8u + 21 LimitAFAreaModeSelAuto? int8u + 22 FocusPointWrap? int8u + 23 ManualFocusPointIllumination? int8u + 24 DynamicAreaAFAssist? int8u + 25 AF-AssistIlluminator int8u + 26 ManualFocusRingInAFMode int8u + 27 ExposureControlStepSize int8u + 29 EasyExposureCompensation int8u + 31 CenterWeightedAreaSize int8u + 33 FineTuneOptMatrixMetering int8s + 35 FineTuneOptCenterWeighted int8s + 37 FineTuneOptSpotMetering int8s + 39 FineTuneOptHighlightWeighted int8s + 41 ShutterReleaseButtonAE-L int8u + 43 SelfTimerTime int8u + 45 SelfTimerShotCount int8u + 49 SelfTimerShotInterval int8u + 51 PlaybackMonitorOffTime int8u + 53 MenuMonitorOffTime int8u + 55 ShootingInfoMonitorOffTime int8u + 57 ImageReviewMonitorOffTime int8u + 59 CLModeShootingSpeed int8u + 61 MaxContinuousRelease no + 65 SyncReleaseMode? int8u + 69 LimitSelectableImageAreaDX? int8u + 70 LimitSelectableImageArea1To1? int8u + 71 LimitSelectableImageArea16To9? int8u + 72 FileNumberSequence int8u + 73 FocusPeakingLevel? int8u + 75 FocusPeakingHighlightColor? int8u + 81 ContinuousModeDisplay int8u + 83 FlashSyncSpeed no + 85 HighSpeedSync int8u + 87 FlashShutterSpeed no + 89 FlashExposureCompArea int8u + 91 AutoFlashISOSensitivity int8u + 93 ModelingFlash int8u + 95 AutoBracketModeM int8u + 97 AutoBracketOrder int8u + 99 Func1Button int8u + 115 Func2Button int8u + 131 AFOnButton int8u + 143 SubSelector? int8u + 155 AssignMovieRecordButton? int8u + 159 LensFunc1Button int8u + 167 LensFunc2Button int8u + 173 LensControlRing int8u + 175 MultiSelectorShootMode int8u + 179 MultiSelectorPlaybackMode int8u + 183 ShutterSpeedLock int8u + 184 ApertureLock int8u + 186 CmdDialsReverseRotation int8u + 191 UseDialWithoutHold? int8u + 193 ReverseIndicators? int8u + 195 MovieFunc1Button int8u + 199 MovieFunc2Button int8u + 203 MovieAF-OnButton int8u + 207 MovieMultiSelector? int8u + 215 MovieLensControlRing int8u + 221 MovieAFSpeed int8u + 223 MovieAFSpeedApply int8u + 225 MovieAFTrackingSensitivity int8u + 279 LCDIllumination? int8u + 280 ExtendedShutterSpeeds int8u + 281 SubjectMotion int8u + 283 FocusPointPersistence int8u + 285 AutoFocusModeRestrictions? int8u + 289 CHModeShootingSpeed int8u + 293.1 LimitReleaseModeSelCL? int8u & 0x02 + 293.2 LimitReleaseModeSelCH? int8u & 0x04 + 293.3 LimitReleaseModeSelC30? int8u & 0x10 + 293.4 LimitReleaseModeSelC120? int8u & 0x40 + 293.5 LimitReleaseModeSelSelf? int8u & 0x80 + 297 FlashBurstPriority? int8u + 301 VerticalFuncButton int8u + 305 Func3Button int8u + 309 VerticalAFOnButton int8u + 317 VerticalMultiSelectorPlaybackMode? int8u + 319 MovieFunc3Button int8u + 359 LimitAF-AreaModeSelDynamic_S? int8u + 360 LimitAF-AreaModeSelDynamic_M? int8u + 361 LimitAF-AreaModeSelDynamic_L? int8u + 363 LimitAF-AreaModeSel3DTracking? int8u + 365 PlaybackFlickUp? int8u + 369 PlaybackFlickDown? int8u + 373 ISOStepSize int8u + 379 ReverseFocusRing int8u + 380 EVFImageFrame? int8u + 381 EVFGrid? int8u + 383 VirtualHorizonStyle? int8u + 397 Func4Button? int8u + 403 AudioButton? int8u + 405 QualityButton? int8u + 423 VerticalMultiSelector? int8u + 445 Func1ButtonPlaybackMode? int8u + 447 Func2ButtonPlaybackMode? int8u + 449 Func3ButtonPlaybackMode? int8u + 455 Func4ButtonPlaybackMode? int8u + 461 MovieRecordButtonPlaybackMode? int8u + 463 VerticalFuncButtonPlaybackMode? int8u + 465 AudioButtonPlaybackMode? int8u + 471 QualityButtonPlaybackMode? int8u + 477 WhiteBalanceButtonPlaybackMode? int8u + 483 CommandDialPlaybackMode? int8u + 487 SubCommandDialPlaybackMode? int8u + 491 FocusPointLock? int8u + 493 ControlRingResponse int8u + 505 VerticalMovieFuncButton? int8u + 529 VerticalMovieAFOnButton? int8u + 539 MovieAFAreaMode? int8u + 551 HDMIViewAssist? int8u + 553 ZebraPatternToneRange? int8u + 555 MovieZebraPattern? int8u + 557 MovieHighlightDisplayThreshold? int8u + 559 MovieMidtoneDisplayValue? int8u + 561 MovieMidtoneDisplayRange? int8u~ + 565 MovieEVFGrid? int8u + 573 MovieShutterSpeedLock? int8u + 574 MovieFocusPointLock? int8u + 587 MatrixMetering? int8u + 588 AF-CFocusDisplay int8u + 589 FocusPeakingDisplay? int8u + 591 KeepExposure int8u + 609 StarlightView? int8u + 611 EVFWarmDisplayMode? int8u + 613 EVFWarmDisplayBrightness? int8s + 615 EVFReleaseIndicator? int8u + 625 MovieApertureLock? int8u + 631 FlickAdvanceDirection? int8u + +=head2 NikonCapture Tags + +This information is written by the Nikon Capture software in tag 0x0e01 of +the maker notes of NEF images. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x8ae85e LCHEditor int8u + 0x83a1a25 HistogramXML undef + 0xc89224b ColorAberrationControl int8u + 0x116fea21 HighlightData NikonCapture HighlightData + 0x2175eb78 D-LightingHQ int8u + 0x2fc08431 StraightenAngle double + 0x374233e0 CropData NikonCapture CropData + 0x39c456ac PictureCtrl NikonCapture PictureCtrl + 0x3cfc73c6 RedEyeData NikonCapture RedEyeData + 0x3d136244 EditVersionName string + 0x416391c6 QuickFix int8u + 0x56a54260 Exposure NikonCapture Exposure + 0x5f0e7d23 ColorBooster int8u + 0x6a6e36b6 D-LightingHQSelected int8u + 0x753dcbc0 NoiseReduction int8u + 0x76a43200 UnsharpMask int8u + 0x76a43201 Curves int8u + 0x76a43202 ColorBalanceAdj int8u + 0x76a43203 AdvancedRaw int8u + 0x76a43204 WhiteBalanceAdj int8u + 0x76a43205 VignetteControl int8u + 0x76a43206 FlipHorizontal int8u + 0x76a43207 Rotation int16u + 0x84589434 BrightnessData NikonCapture Brightness + 0x890ff591 D-LightingHQData NikonCapture DLightingHQ + 0x926f13e0 NoiseReductionData NikonCapture NoiseReduction + 0x9ef5f6e0 IPTCData IPTC + 0xab5eca5e PhotoEffects int8u + 0xac6bd5c0 VignetteControlIntensity int16s + 0xb0384e1e PhotoEffectsData NikonCapture PhotoEffects + 0xb999a36f ColorBoostData NikonCapture ColorBoost + 0xbf3c6c20 WBAdjData NikonCapture WBAdjData + 0xce5554aa D-LightingHS int8u + 0xe2173c47 PictureControl int8u + 0xe37b4337 D-LightingHSData NikonCapture DLightingHS + 0xe42b5161 UnsharpData NikonCapture UnsharpData + 0xe9651831 PhotoEffectHistoryXML undef + 0xfe28a44f AutoRedEye int8u + 0xfe443a45 ImageDustOff int8u + +=head3 NikonCapture HighlightData Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ShadowProtection int8s + 1 SaturationAdj int8s + 6 HighlightProtection int8s + +=head3 NikonCapture CropData Tags + + Index1 Tag Name Writable + ------ -------- -------- + 30 CropLeft double + 38 CropTop double + 46 CropRight double + 54 CropBottom double + 142 CropOutputWidthInches double + 150 CropOutputHeightInches double + 158 CropScaledResolution double + 174 CropSourceResolution double + 182 CropOutputResolution double + 190 CropOutputScale double + 198 CropOutputWidth double + 206 CropOutputHeight double + 214 CropOutputPixels double + +=head3 NikonCapture PictureCtrl Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 PictureControlActive int8u + 19 PictureControlMode string[16] + 42 QuickAdjust int8u + 43 SharpeningAdj int8u + 44 ContrastAdj int8u + 45 BrightnessAdj int8u + 46 SaturationAdj int8u + 47 HueAdj int8u + +=head3 NikonCapture RedEyeData Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 RedEyeCorrection int8u + +=head3 NikonCapture Exposure Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ExposureAdj int16s + 18 ExposureAdj2 double + 36 ActiveD-Lighting int8u + 37 ActiveD-LightingMode int8u + +=head3 NikonCapture Brightness Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 BrightnessAdj double + 8 EnhanceDarkTones int8u + +=head3 NikonCapture DLightingHQ Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 D-LightingHQShadow int32u + 1 D-LightingHQHighlight int32u + 2 D-LightingHQColorBoost int32u + +=head3 NikonCapture NoiseReduction Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 EdgeNoiseReduction int8u + 5 ColorMoireReductionMode int8u + 9 NoiseReductionIntensity int32u + 13 NoiseReductionSharpness int32u + 17 NoiseReductionMethod int16u + 21 ColorMoireReduction int8u + 23 NoiseReduction int8u + 24 ColorNoiseReductionIntensity int32u + 28 ColorNoiseReductionSharpness int32u + +=head3 NikonCapture PhotoEffects Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 PhotoEffectsType int8u + 4 PhotoEffectsRed int16s + 6 PhotoEffectsGreen int16s + 8 PhotoEffectsBlue int16s + +=head3 NikonCapture ColorBoost Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ColorBoostType int8u + 1 ColorBoostLevel int32u + +=head3 NikonCapture WBAdjData Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 WBAdjRedBalance double + 8 WBAdjBlueBalance double + 16 WBAdjMode int8u + 20 WBAdjLighting int16u + 24 WBAdjTemperature int16u + 37 WBAdjTint int32s + +=head3 NikonCapture DLightingHS Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 D-LightingHSAdjustment int32u + 1 D-LightingHSColorBoost int32u + +=head3 NikonCapture UnsharpData Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 UnsharpCount int8u + 19 Unsharp1Color int16u + 23 Unsharp1Intensity int16u + 25 Unsharp1HaloWidth int16u + 27 Unsharp1Threshold int8u + 46 Unsharp2Color int16u + 50 Unsharp2Intensity int16u + 52 Unsharp2HaloWidth int16u + 54 Unsharp2Threshold int8u + 73 Unsharp3Color int16u + 77 Unsharp3Intensity int16u + 79 Unsharp3HaloWidth int16u + 81 Unsharp3Threshold int8u + 100 Unsharp4Color int16u + 104 Unsharp4Intensity int16u + 106 Unsharp4HaloWidth int16u + 108 Unsharp4Threshold int8u + +=head2 Nintendo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x1101 CameraInfo Nintendo CameraInfo + +=head3 Nintendo CameraInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ModelID undef[4] + 8 TimeStamp int32u + 24 InternalSerialNumber undef[4] + 40 Parallax float + 48 Category int16u + +=head2 Olympus Tags + +Tags 0x0000 through 0x0103 are used by some older Olympus cameras, and are +the same as Konica/Minolta tags. These tags are also used for some models +from other brands such as Acer, BenQ, Epson, Hitachi, HP, Maginon, Minolta, +Pentax, Ricoh, Samsung, Sanyo, SeaLife, Sony, Supra and Vivitar. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 MakerNoteVersion undef + 0x0001 MinoltaCameraSettingsOld Minolta CameraSettings + 0x0003 MinoltaCameraSettings Minolta CameraSettings + 0x0040 CompressedImageSize int32u + 0x0081 PreviewImageData no + 0x0088 PreviewImageStart no + 0x0089 PreviewImageLength no + 0x0100 ThumbnailImage undef + 0x0104 BodyFirmwareVersion string + 0x0200 SpecialMode int32u[3]~ + 0x0201 Quality int16u~ + 0x0202 Macro int16u + 0x0203 BWMode int16u + 0x0204 DigitalZoom rational64u + 0x0205 FocalPlaneDiagonal rational64u + 0x0206 LensDistortionParams int16s[6] + 0x0207 CameraType string + 0x0208 TextInfo Olympus TextInfo + 0x0209 CameraID string + 0x020b EpsonImageWidth int32u + 0x020c EpsonImageHeight int32u + 0x020d EpsonSoftware string + 0x0280 PreviewImage int8u + 0x0300 PreCaptureFrames int16u + 0x0301 WhiteBoard int16u + 0x0302 OneTouchWB int16u + 0x0303 WhiteBalanceBracket int16u + 0x0304 WhiteBalanceBias int16u + 0x0400 SensorArea undef[8] + 0x0401 BlackLevel int32u[4] + 0x0403 SceneMode int16u + 0x0404 SerialNumber string + 0x0405 Firmware string + 0x0e00 PrintIM PrintIM + 0x0f00 DataDump no + 0x0f01 DataDump2 no + 0x0f04 ZoomedPreviewStart int32u* + 0x0f05 ZoomedPreviewLength int32u* + 0x0f06 ZoomedPreviewSize int16u[2] + 0x1000 ShutterSpeedValue rational64s + 0x1001 ISOValue rational64s + 0x1002 ApertureValue rational64s + 0x1003 BrightnessValue rational64s + 0x1004 FlashMode int16u + 0x1005 FlashDevice int16u + 0x1006 ExposureCompensation rational64s + 0x1007 SensorTemperature int16s + 0x1008 LensTemperature int16s + 0x1009 LightCondition int16u + 0x100a FocusRange int16u + 0x100b FocusMode int16u + 0x100c ManualFocusDistance rational64u + 0x100d ZoomStepCount int16u + 0x100e FocusStepCount int16u + 0x100f Sharpness int16u + 0x1010 FlashChargeLevel int16u + 0x1011 ColorMatrix int16u[9] + 0x1012 BlackLevel int16u[4] + 0x1013 ColorTemperatureBG? int16u + 0x1014 ColorTemperatureRG? int16u + 0x1015 WBMode int16u[2] + 0x1017 RedBalance int16u[2] + 0x1018 BlueBalance int16u[2] + 0x1019 ColorMatrixNumber int16u + 0x101a SerialNumber string + 0x101b ExternalFlashAE1_0? int32u + 0x101c ExternalFlashAE2_0? int32u + 0x101d InternalFlashAE1_0? int32u + 0x101e InternalFlashAE2_0? int32u + 0x101f ExternalFlashAE1? int32u + 0x1020 ExternalFlashAE2? int32u + 0x1021 InternalFlashAE1? int32u + 0x1022 InternalFlashAE2? int32u + 0x1023 FlashExposureComp rational64s + 0x1024 InternalFlashTable int16u + 0x1025 ExternalFlashGValue rational64s + 0x1026 ExternalFlashBounce int16u + 0x1027 ExternalFlashZoom int16u + 0x1028 ExternalFlashMode int16u + 0x1029 Contrast int16u + 0x102a SharpnessFactor int16u + 0x102b ColorControl int16u[6] + 0x102c ValidBits int16u[2] + 0x102d CoringFilter int16u + 0x102e OlympusImageWidth int32u + 0x102f OlympusImageHeight int32u + 0x1030 SceneDetect int16u + 0x1031 SceneArea? int32u[8] + 0x1033 SceneDetectData? int32u[720] + 0x1034 CompressionRatio rational64u + 0x1035 PreviewImageValid int32u + 0x1036 PreviewImageStart int32u* + 0x1037 PreviewImageLength int32u* + 0x1038 AFResult int16u + 0x1039 CCDScanMode int16u + 0x103a NoiseReduction int16u + 0x103b FocusStepInfinity int16u + 0x103c FocusStepNear int16u + 0x103d LightValueCenter rational64s + 0x103e LightValuePeriphery rational64s + 0x103f FieldCount? int16u + 0x2010 Equipment Olympus Equipment + EquipmentIFD Olympus Equipment + 0x2020 CameraSettings Olympus CameraSettings + CameraSettingsIFD Olympus CameraSettings + 0x2030 RawDevelopment Olympus RawDevelopment + RawDevelopmentIFD Olympus RawDevelopment + 0x2031 RawDev2 Olympus RawDevelopment2 + RawDev2IFD Olympus RawDevelopment2 + 0x2040 ImageProcessing Olympus ImageProcessing + ImageProcessingIFD Olympus ImageProcessing + 0x2050 FocusInfo Olympus FocusInfo + FocusInfoIFD Olympus FocusInfo + CameraParameters undef + 0x2100 Olympus2100 Olympus FE + Olympus2100IFD Olympus FE + 0x2200 Olympus2200 Olympus FE + Olympus2200IFD Olympus FE + 0x2300 Olympus2300 Olympus FE + Olympus2300IFD Olympus FE + 0x2400 Olympus2400 Olympus FE + Olympus2400IFD Olympus FE + 0x2500 Olympus2500 Olympus FE + Olympus2500IFD Olympus FE + 0x2600 Olympus2600 Olympus FE + Olympus2600IFD Olympus FE + 0x2700 Olympus2700 Olympus FE + Olympus2700IFD Olympus FE + 0x2800 Olympus2800 Olympus FE + Olympus2800IFD Olympus FE + 0x2900 Olympus2900 Olympus FE + Olympus2900IFD Olympus FE + 0x3000 RawInfo Olympus RawInfo + RawInfoIFD Olympus RawInfo + 0x4000 MainInfo Olympus + MainInfoIFD Olympus + 0x5000 UnknownInfo Olympus UnknownInfo + UnknownInfoIFD Olympus UnknownInfo + +=head3 Olympus TextInfo Tags + +This information is in text format (similar to APP12 information, but with +spaces instead of linefeeds). Below are tags which have been observed, but +any information found here will be extracted, even if the tag is not listed. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Resolution' Resolution no + 'Type' CameraType no + +=head3 Olympus Equipment Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 EquipmentVersion undef[4] + 0x0100 CameraType2 string[6] + 0x0101 SerialNumber string[32] + 0x0102 InternalSerialNumber string[32] + 0x0103 FocalPlaneDiagonal rational64u + 0x0104 BodyFirmwareVersion int32u + 0x0201 LensType int8u[6] + 0x0202 LensSerialNumber string[32] + 0x0203 LensModel string + 0x0204 LensFirmwareVersion int32u + 0x0205 MaxApertureAtMinFocal int16u + 0x0206 MaxApertureAtMaxFocal int16u + 0x0207 MinFocalLength int16u + 0x0208 MaxFocalLength int16u + 0x020a MaxAperture int16u + 0x020b LensProperties int16u + 0x0301 Extender int8u[6] + 0x0302 ExtenderSerialNumber string[32] + 0x0303 ExtenderModel string + 0x0304 ExtenderFirmwareVersion int32u + 0x0403 ConversionLens string + 0x1000 FlashType int16u + 0x1001 FlashModel int16u + 0x1002 FlashFirmwareVersion int32u + 0x1003 FlashSerialNumber string[32] + +=head3 Olympus CameraSettings Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 CameraSettingsVersion undef[4] + 0x0100 PreviewImageValid int32u + 0x0101 PreviewImageStart int32u* + 0x0102 PreviewImageLength int32u* + 0x0200 ExposureMode int16u + 0x0201 AELock int16u + 0x0202 MeteringMode int16u + 0x0203 ExposureShift rational64s + 0x0204 NDFilter yes + 0x0300 MacroMode int16u + 0x0301 FocusMode int16u[n] + 0x0302 FocusProcess int16u[n] + 0x0303 AFSearch int16u + 0x0304 AFAreas int32u[64]~ + 0x0305 AFPointSelected rational64s[5] + 0x0306 AFFineTune int8u + 0x0307 AFFineTuneAdj int16s[3] + 0x0308 FocusBracketStepSize int8u + 0x0309 AISubjectTrackingMode int16u + 0x0400 FlashMode int16u + 0x0401 FlashExposureComp rational64s + 0x0403 FlashRemoteControl int16u + 0x0404 FlashControlMode int16u[n] + 0x0405 FlashIntensity rational64s[n] + 0x0406 ManualFlashStrength rational64s[n] + 0x0500 WhiteBalance2 int16u + 0x0501 WhiteBalanceTemperature int16u + 0x0502 WhiteBalanceBracket int16s + 0x0503 CustomSaturation int16s[3]~ + 0x0504 ModifiedSaturation int16u + 0x0505 ContrastSetting int16s[3] + 0x0506 SharpnessSetting int16s[3] + 0x0507 ColorSpace int16u + 0x0509 SceneMode int16u + 0x050a NoiseReduction int16u + 0x050b DistortionCorrection int16u + 0x050c ShadingCompensation int16u + 0x050d CompressionFactor rational64u + 0x050f Gradation int16s[n] + 0x0520 PictureMode int16u[n] + 0x0521 PictureModeSaturation int16s[3] + 0x0522 PictureModeHue? int16s + 0x0523 PictureModeContrast int16s[3] + 0x0524 PictureModeSharpness int16s[3] + 0x0525 PictureModeBWFilter int16s + 0x0526 PictureModeTone int16s + 0x0527 NoiseFilter int16s[3] + 0x0529 ArtFilter int16u[4] + 0x052c MagicFilter int16u[4] + 0x052d PictureModeEffect int16s[3] + 0x052e ToneLevel yes + 0x052f ArtFilterEffect int16u[20] + 0x0532 ColorCreatorEffect int16s[6] + 0x0537 MonochromeProfileSettings int16s[6] + 0x0538 FilmGrainEffect int16s + 0x0539 ColorProfileSettings int16s[14] + 0x053a MonochromeVignetting int16s + 0x053b MonochromeColor int16s + 0x0600 DriveMode int16u[n]~ + 0x0601 PanoramaMode int16u~ + 0x0603 ImageQuality2 int16u + 0x0604 ImageStabilization int32u + 0x0804 StackedImage int32u[2] + 0x0900 ManometerPressure int16u + 0x0901 ManometerReading int32s[2] + 0x0902 ExtendedWBDetect int16u + 0x0903 RollAngle int16s[2] + 0x0904 PitchAngle int16s[2] + 0x0908 DateTimeUTC string + +=head3 Olympus RawDevelopment Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 RawDevVersion undef[4] + 0x0100 RawDevExposureBiasValue rational64s + 0x0101 RawDevWhiteBalanceValue int16u + 0x0102 RawDevWBFineAdjustment int16s + 0x0103 RawDevGrayPoint int16u[3] + 0x0104 RawDevSaturationEmphasis int16s[3] + 0x0105 RawDevMemoryColorEmphasis int16u + 0x0106 RawDevContrastValue int16s[3] + 0x0107 RawDevSharpnessValue int16s[3] + 0x0108 RawDevColorSpace int16u + 0x0109 RawDevEngine int16u + 0x010a RawDevNoiseReduction int16u + 0x010b RawDevEditStatus int16u + 0x010c RawDevSettings int16u + +=head3 Olympus RawDevelopment2 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 RawDevVersion undef[4] + 0x0100 RawDevExposureBiasValue rational64s + 0x0101 RawDevWhiteBalance int16u + 0x0102 RawDevWhiteBalanceValue int16u + 0x0103 RawDevWBFineAdjustment int16s + 0x0104 RawDevGrayPoint int16u[3] + 0x0105 RawDevContrastValue int16s[3] + 0x0106 RawDevSharpnessValue int16s[3] + 0x0107 RawDevSaturationEmphasis int16s[3] + 0x0108 RawDevMemoryColorEmphasis int16u + 0x0109 RawDevColorSpace int16u + 0x010a RawDevNoiseReduction int16u + 0x010b RawDevEngine int16u + 0x010c RawDevPictureMode int16u + 0x010d RawDevPMSaturation int16s[3] + 0x010e RawDevPMContrast int16s[3] + 0x010f RawDevPMSharpness int16s[3] + 0x0110 RawDevPM_BWFilter int16u + 0x0111 RawDevPMPictureTone int16u + 0x0112 RawDevGradation int16s[3] + 0x0113 RawDevSaturation3 int16s[3] + 0x0119 RawDevAutoGradation int16u + 0x0120 RawDevPMNoiseFilter int16u + 0x0121 RawDevArtFilter int16u[4] + +=head3 Olympus ImageProcessing Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 ImageProcessingVersion undef[4] + 0x0100 WB_RBLevels int16u[2] + 0x0102 WB_RBLevels3000K int16u[2] + 0x0103 WB_RBLevels3300K int16u[2] + 0x0104 WB_RBLevels3600K int16u[2] + 0x0105 WB_RBLevels3900K int16u[2] + 0x0106 WB_RBLevels4000K int16u[2] + 0x0107 WB_RBLevels4300K int16u[2] + 0x0108 WB_RBLevels4500K int16u[2] + 0x0109 WB_RBLevels4800K int16u[2] + 0x010a WB_RBLevels5300K int16u[2] + 0x010b WB_RBLevels6000K int16u[2] + 0x010c WB_RBLevels6600K int16u[2] + 0x010d WB_RBLevels7500K int16u[2] + 0x010e WB_RBLevelsCWB1 int16u[2] + 0x010f WB_RBLevelsCWB2 int16u[2] + 0x0110 WB_RBLevelsCWB3 int16u[2] + 0x0111 WB_RBLevelsCWB4 int16u[2] + 0x0113 WB_GLevel3000K int16u + 0x0114 WB_GLevel3300K int16u + 0x0115 WB_GLevel3600K int16u + 0x0116 WB_GLevel3900K int16u + 0x0117 WB_GLevel4000K int16u + 0x0118 WB_GLevel4300K int16u + 0x0119 WB_GLevel4500K int16u + 0x011a WB_GLevel4800K int16u + 0x011b WB_GLevel5300K int16u + 0x011c WB_GLevel6000K int16u + 0x011d WB_GLevel6600K int16u + 0x011e WB_GLevel7500K int16u + 0x011f WB_GLevel int16u + 0x0200 ColorMatrix int16u[9] + 0x0300 Enhancer int16u + 0x0301 EnhancerValues int16u[7] + 0x0310 CoringFilter int16u + 0x0311 CoringValues int16u[7] + 0x0600 BlackLevel2 int16u[4] + 0x0610 GainBase int16u + 0x0611 ValidBits int16u[2] + 0x0612 CropLeft int16u[2] + 0x0613 CropTop int16u[2] + 0x0614 CropWidth int32u + 0x0615 CropHeight int32u + 0x0635 UnknownBlock1? undef + 0x0636 UnknownBlock2? undef + 0x0805 SensorCalibration int16s[2] + 0x1010 NoiseReduction2 int16u + 0x1011 DistortionCorrection2 int16u + 0x1012 ShadingCompensation2 int16u + 0x101c MultipleExposureMode int16u[2] + 0x1103 UnknownBlock3? undef + 0x1104 UnknownBlock4? undef + 0x1112 AspectRatio int8u[2] + 0x1113 AspectFrame int16u[4] + 0x1200 FacesDetected int32u[n] + 0x1201 FaceDetectArea int16s[n] + 0x1202 MaxFaces int32u[3] + 0x1203 FaceDetectFrameSize int16u[6] + 0x1207 FaceDetectFrameCrop int16s[12] + 0x1306 CameraTemperature no + 0x1900 KeystoneCompensation int8u[2] + 0x1901 KeystoneDirection int8u[2] + 0x1906 KeystoneValue int16s[3] + +=head3 Olympus FocusInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 FocusInfoVersion undef[4] + 0x0209 AutoFocus? int16u + 0x0210 SceneDetect int16u + 0x0211 SceneArea? int32u[8] + 0x0212 SceneDetectData? int32u[720] + 0x0300 ZoomStepCount int16u + 0x0301 FocusStepCount int16u + 0x0303 FocusStepInfinity int16u + 0x0304 FocusStepNear int16u + 0x0305 FocusDistance rational64u + 0x0308 AFPoint int16u + 0x031b AFPointDetails no + AFPointDetails int16u + 0x0328 AFInfo Olympus AFInfo + 0x1201 ExternalFlash int16u[2] + 0x1203 ExternalFlashGuideNumber? rational64s + 0x1204 ExternalFlashBounce int16u + 0x1205 ExternalFlashZoom rational64u + 0x1208 InternalFlash int16u[n] + 0x1209 ManualFlash int16u[2]~ + 0x120a MacroLED int16u + 0x1500 SensorTemperature int16s + 0x1600 ImageStabilization undef~ + +=head3 Olympus AFInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Olympus FE Tags + +Some FE models write a large number of tags here, but most of this +information remains unknown. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0100 BodyFirmwareVersion string + +=head3 Olympus RawInfo Tags + +These tags are found only in ORF images of some models (eg. C8080WZ). + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 RawInfoVersion undef[4] + 0x0100 WB_RBLevelsUsed int16u[2] + 0x0110 WB_RBLevelsAuto int16u[2] + 0x0120 WB_RBLevelsShade int16u[2] + 0x0121 WB_RBLevelsCloudy int16u[2] + 0x0122 WB_RBLevelsFineWeather int16u[2] + 0x0123 WB_RBLevelsTungsten int16u[2] + 0x0124 WB_RBLevelsEveningSunlight int16u[2] + 0x0130 WB_RBLevelsDaylightFluor int16u[2] + 0x0131 WB_RBLevelsDayWhiteFluor int16u[2] + 0x0132 WB_RBLevelsCoolWhiteFluor int16u[2] + 0x0133 WB_RBLevelsWhiteFluorescent int16u[2] + 0x0200 ColorMatrix2 int16u[9] + 0x0310 CoringFilter int16u + 0x0311 CoringValues int16u[11] + 0x0600 BlackLevel2 int16u[4] + 0x0601 YCbCrCoefficients no + 0x0611 ValidPixelDepth int16u[2] + 0x0612 CropLeft int16u + 0x0613 CropTop int16u + 0x0614 CropWidth int32u + 0x0615 CropHeight int32u + 0x1000 LightSource int16u + 0x1001 WhiteBalanceComp int16s[3] + 0x1010 SaturationSetting int16s[3] + 0x1011 HueSetting int16s[3] + 0x1012 ContrastSetting int16s[3] + 0x1013 SharpnessSetting int16s[3] + 0x2000 CMExposureCompensation rational64s + 0x2001 CMWhiteBalance int16u + 0x2002 CMWhiteBalanceComp int16s + 0x2010 CMWhiteBalanceGrayPoint int16u[3] + 0x2020 CMSaturation int16s[3] + 0x2021 CMHue int16s[3] + 0x2022 CMContrast int16s[3] + 0x2023 CMSharpness int16s[3] + +=head3 Olympus UnknownInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Olympus DSS Tags + +Information extracted from DSS/DS2 files and the ID3 XOLY frame of MP3 files +written by some Olympus voice recorders. + + Index1 Tag Name Writable + ------ -------- -------- + 12 Model no + 38 StartTime no + 50 EndTime no + 62 Duration no + 798 Comment no + +=head3 Olympus AVI Tags + +This information is found in Olympus AVI videos. + + Index1 Tag Name Writable + ------ -------- -------- + 18 Make no + 44 Model no + 94 FNumber no + 131 DateTime1 no + 157 DateTime2 no + 297 ThumbInfo Olympus thmb2 + +=head3 Olympus thmb2 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ThumbnailWidth no + 2 ThumbnailHeight no + 4 ThumbnailLength no + 8 ThumbnailImage no + +=head3 Olympus WAV Tags + +This information is found in WAV files from Olympus PCM linear recorders +like the LS-5, LS-10, LS-11. + + Index1 Tag Name Writable + ------ -------- -------- + 12 Model no + 28 FileNumber no + 38 DateTimeOriginal no + 50 DateTimeEnd no + 62 RecordingTime no + 512 Duration no + 522 Index01 no + 532 Index02 no + 542 Index03 no + 552 Index04 no + 562 Index05 no + 572 Index06 no + 582 Index07 no + 592 Index08 no + 602 Index09 no + 612 Index10 no + 622 Index11 no + 632 Index12 no + 642 Index13 no + 652 Index14 no + 662 Index15 no + 672 Index16 no + +=head3 Olympus MOV1 Tags + +This information is found in MOV videos from Olympus models such as the +D540Z, D595Z, FE100, FE110, FE115, FE170 and FE200. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 24 Model no + 38 ExposureUnknown? no + 42 FNumber no + 50 ExposureCompensation no + 72 FocalLength no + +=head3 Olympus MOV2 Tags + +This information is found in MOV videos from Olympus models such as the +FE120, FE140 and FE190. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 24 Model no + 54 ExposureTime no + 58 FNumber no + 66 ExposureCompensation no + 88 FocalLength no + 193 ISO no + +=head3 Olympus MP4 Tags + +This information is found in MP4 videos from Olympus models such as the +u7040 and u9010. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 24 Model no + 40 FNumber no + 48 ExposureCompensation no + 104 MovableInfo Olympus MovableInfo + 114 MovableInfo Olympus MovableInfo + +=head3 Olympus MovableInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 ISO no + 44 EncoderVersion no + 60 DecoderVersion no + 131 Thumbnail Olympus Thumbnail + +=head3 Olympus Thumbnail Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 ThumbnailWidth no + 2 ThumbnailHeight no + 3 ThumbnailLength no + 4 ThumbnailOffset no + +=head3 Olympus MOV3 Tags + +QuickTime information found in the TAGS atom of MOV videos from the E-M5. + + Tag ID Tag Name Writable + ------ -------- -------- + 'OLYM' OlympusAtom Olympus OLYM2 + +=head3 Olympus OLYM2 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'prms' MakerNotes Olympus prms + 'scrn' PreviewInfo Olympus scrn2 + 'thmb' ThumbInfo Olympus thmb2 + +=head3 Olympus prms Tags + +Information extracted from the "prms" atom in MOV videos from Olympus models +such as the OM E-M5. + + Index1 Tag Name Writable + ------ -------- -------- + 18 Make no + 44 Model no + 131 DateTime1 no + 157 DateTime2 no + 383 LensModel no + +=head3 Olympus scrn2 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 2 OlympusPreview Olympus scrn + +=head3 Olympus scrn Tags + +Information extracted from the "scrn" atom of Olympus MP4 videos. + + Index1 Tag Name Writable + ------ -------- -------- + 0 PreviewImageLength no + 4 PreviewImage no + +=head3 Olympus thmb Tags + +Information extracted from the "thmb" atom of Olympus MP4 videos. + + Index1 Tag Name Writable + ------ -------- -------- + 0 ThumbnailLength no + 4 ThumbnailImage no + +=head3 Olympus OLYM Tags + +Tags found in the OLYM atom of MP4 videos from the TG-810. + + Index1 Tag Name Writable + ------ -------- -------- + 14 Make no + 40 Model no + 90 FNumber no + 127 DateTimeOriginal no + 153 DateTime2 no + 265 ThumbnailWidth no + 267 ThumbnailHeight no + +=head2 Panasonic Tags + +These tags are used in Panasonic/Leica cameras. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 ImageQuality int16u + 0x0002 FirmwareVersion undef + 0x0003 WhiteBalance int16u + 0x0007 FocusMode int16u + 0x000f AFAreaMode int8u[2] + 0x001a ImageStabilization int16u + 0x001c MacroMode int16u + 0x001f ShootingMode int16u + 0x0020 Audio int16u + 0x0021 DataDump no + 0x0023 WhiteBalanceBias int16s + 0x0024 FlashBias int16s + 0x0025 InternalSerialNumber undef[16] + 0x0026 PanasonicExifVersion undef + 0x0027 VideoFrameRate int16u + 0x0028 ColorEffect int16u + 0x0029 TimeSincePowerOn int32u + 0x002a BurstMode int16u + 0x002b SequenceNumber int32u + 0x002c ContrastMode int16u + 0x002d NoiseReduction int16u + 0x002e SelfTimer int16u + 0x0030 Rotation int16u + 0x0031 AFAssistLamp int16u + 0x0032 ColorMode int16u + 0x0033 BabyAge string + 0x0034 OpticalZoomMode int16u + 0x0035 ConversionLens int16u + 0x0036 TravelDay int16u + 0x0038 BatteryLevel int16u + 0x0039 Contrast int16u + 0x003a WorldTimeLocation int16u + 0x003b TextStamp int16u + 0x003c ProgramISO int16u + 0x003d AdvancedSceneType int16u + 0x003e TextStamp int16u + 0x003f FacesDetected int16u + 0x0040 Saturation int16u + 0x0041 Sharpness int16u + 0x0042 FilmMode int16u + 0x0043 JPEGQuality int16u + 0x0044 ColorTempKelvin int16u + 0x0045 BracketSettings int16u + 0x0046 WBShiftAB int16u + 0x0047 WBShiftGM int16u + 0x0048 FlashCurtain int16u + 0x0049 LongExposureNoiseReduction int16u + 0x004b PanasonicImageWidth int32u + 0x004c PanasonicImageHeight int32u + 0x004d AFPointPosition rational64u[2] + 0x004e FaceDetInfo Panasonic FaceDetInfo + 0x0051 LensType string + 0x0052 LensSerialNumber string + 0x0053 AccessoryType string + 0x0054 AccessorySerialNumber string + 0x0059 Transform undef[4] + 0x005d IntelligentExposure int16u + 0x0060 LensFirmwareVersion undef[4] + 0x0061 FaceRecInfo Panasonic FaceRecInfo + 0x0062 FlashWarning int16u + 0x0063 RecognizedFaceFlags? undef[4] + 0x0065 Title undef + 0x0066 BabyName undef + 0x0067 Location undef + 0x0069 Country undef + 0x006b State undef + 0x006d City undef + 0x006f Landmark undef + 0x0070 IntelligentResolution int8u + 0x0076 HDRShot int16u + 0x0077 BurstSpeed int16u + 0x0079 IntelligentD-Range int16u + 0x007c ClearRetouch int16u + 0x0080 City2 undef + 0x0086 ManometerPressure int16u + 0x0089 PhotoStyle int16u + 0x008a ShadingCompensation int16u + 0x008b WBShiftIntelligentAuto int16u + 0x008c AccelerometerZ int16u + 0x008d AccelerometerX int16u + 0x008e AccelerometerY int16u + 0x008f CameraOrientation int8u + 0x0090 RollAngle int16u + 0x0091 PitchAngle int16u + 0x0092 WBShiftCreativeControl int8u + 0x0093 SweepPanoramaDirection int8u + 0x0094 SweepPanoramaFieldOfView int16u + 0x0096 TimerRecording int8u + 0x009d InternalNDFilter rational64u + 0x009e HDR int16u + 0x009f ShutterType int16u + 0x00a1 FilterEffect rational64u[0.5] + 0x00a3 ClearRetouchValue rational64u + 0x00a7 OutputLUT yes + 0x00ab TouchAE int16u + 0x00ac MonochromeFilterEffect int16u + 0x00ad HighlightShadow int16u[2] + 0x00af TimeStamp string + 0x00b3 VideoBurstResolution int16u + 0x00b4 MultiExposure int16u + 0x00b9 RedEyeRemoval int16u + 0x00bb VideoBurstMode int32u + 0x00bc DiffractionCorrection int16u + 0x00bd FocusBracket int16u + 0x00be LongExposureNRUsed int16u + 0x00bf PostFocusMerging int32u[2] + 0x00c1 VideoPreburst int16u + 0x00c4 LensTypeMake int16u + 0x00c5 LensTypeModel int16u + 0x00ca SensorType int16u + 0x00d1 ISO int32u + 0x00d2 MonochromeGrainEffect int16u + 0x00d6 NoiseReductionStrength rational64s + 0x00e4 LensTypeModel int16u + 0x00e8 MinimumISO int32u + 0x00ee DynamicRangeBoost int16u + 0x0e00 PrintIM PrintIM + 0x2003 TimeInfo Panasonic TimeInfo + 0x8000 MakerNoteVersion undef + 0x8001 SceneMode int16u + 0x8002 HighlightWarning int16u + 0x8003 DarkFocusEnvironment int16u + 0x8004 WBRedLevel int16u + 0x8005 WBGreenLevel int16u + 0x8006 WBBlueLevel int16u + 0x8008 TextStamp int16u + 0x8009 TextStamp int16u + 0x8010 BabyAge string + 0x8012 Transform undef[4] + +=head3 Panasonic FaceDetInfo Tags + +Face detection position information. + + Index2 Tag Name Writable + ------ -------- -------- + 0 NumFacePositions int16u + 1 Face1Position int16u[4] + 5 Face2Position int16u[4] + 9 Face3Position int16u[4] + 13 Face4Position int16u[4] + 17 Face5Position int16u[4] + +=head3 Panasonic FaceRecInfo Tags + +Tags written by cameras with facial recognition. These cameras not only +detect faces in an image, but also recognize specific people based a +user-supplied set of known faces. + + Index1 Tag Name Writable + ------ -------- -------- + 0 FacesRecognized int16u + 4 RecognizedFace1Name string[20] + 24 RecognizedFace1Position int16u[4] + 32 RecognizedFace1Age string[20] + 52 RecognizedFace2Name string[20] + 72 RecognizedFace2Position int16u[4] + 80 RecognizedFace2Age string[20] + 100 RecognizedFace3Name string[20] + 120 RecognizedFace3Position int16u[4] + 128 RecognizedFace3Age string[20] + +=head3 Panasonic TimeInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 PanasonicDateTime undef[8] + 16 TimeLapseShotNumber int32u + +=head3 Panasonic Leica2 Tags + +These tags are used by the Leica M8. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0300 Quality int16u + 0x0302 UserProfile int32u + 0x0303 SerialNumber int32u + 0x0304 WhiteBalance int16u + 0x0310 LensType int32u + 0x0311 ExternalSensorBrightnessValue rational64s + 0x0312 MeasuredLV rational64s + 0x0313 ApproximateFNumber rational64u + 0x0320 CameraTemperature int32s + 0x0321 ColorTemperature int32u + 0x0322 WBRedLevel rational64u + 0x0323 WBGreenLevel rational64u + 0x0324 WBBlueLevel rational64u + 0x0325 UV-IRFilterCorrection int32u + 0x0330 CCDVersion int32u + 0x0331 CCDBoardVersion int32u + 0x0332 ControllerBoardVersion int32u + 0x0333 M16CVersion int32u + 0x0340 ImageIDNumber int32u + +=head3 Panasonic Leica3 Tags + +These tags are used by the Leica R8 and R9 digital backs. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x000b SerialInfo Panasonic SerialInfo + 0x000d WB_RGBLevels int16u[3] + +=head3 Panasonic SerialInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 SerialNumber no + +=head3 Panasonic Leica4 Tags + +This information is written by the M9. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x3000 Subdir3000 Panasonic Subdir + 0x3100 Subdir3100 Panasonic Subdir + 0x3400 Subdir3400 Panasonic Subdir + 0x3900 Subdir3900 Panasonic Subdir + +=head3 Panasonic Subdir Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x300a Contrast int32u + 0x300b Sharpening int32u + 0x300d Saturation int32u + 0x3033 WhiteBalance int32u + 0x3034 JPEGQuality int32u + 0x3036 WB_RGBLevels rational64u[3] + 0x3038 UserProfile string + 0x303a JPEGSize int32u + 0x3103 SerialNumber string + 0x3109 FirmwareVersion string + 0x312a BaseISO int32u + 0x312b SensorWidth int32u + 0x312c SensorHeight int32u + 0x312d SensorBitDepth int32u + 0x3402 CameraTemperature int32s + 0x3405 LensType int32u + 0x3406 ApproximateFNumber rational64u + 0x3407 MeasuredLV int32s + 0x3408 ExternalSensorBrightnessValue int32s + 0x3901 Data1 Panasonic Data1 + 0x3902 Data2 Panasonic Data2 + +=head3 Panasonic Data1 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 22 LensType int32u + +=head3 Panasonic Data2 Tags + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Panasonic Leica5 Tags + +This information is written by the X1, X2, X VARIO and T. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0303 LensType string + 0x0305 SerialNumber int32u + 0x0407 OriginalFileName string + 0x0408 OriginalDirectory string + 0x040a FocusInfo Panasonic FocusInfo + 0x040d ExposureMode int8u[4] + 0x0410 ShotInfo Panasonic ShotInfo + 0x0412 FilmMode string + 0x0413 WB_RGBLevels rational64u[3] + 0x0500 InternalSerialNumber undef + +=head3 Panasonic FocusInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 FocusDistance int16u + 1 FocalLength int16u + +=head3 Panasonic ShotInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FileIndex int16u + +=head3 Panasonic Leica6 Tags + +This information is written by the S2 and M (Typ 240), as a trailer in JPEG +images. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0300 PreviewImage undef + 0x0301 UnknownBlock? no + 0x0303 LensType string + 0x0304 FocusDistance int32u + 0x0311 ExternalSensorBrightnessValue rational64s + 0x0312 MeasuredLV rational64s + 0x0320 FirmwareVersion int8u[4] + 0x0321 LensSerialNumber int32u + +=head3 Panasonic Leica9 Tags + +This information is written by the Leica S (Typ 007) and M10 models. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0304 FocusDistance int32u + 0x0311 ExternalSensorBrightnessValue rational64s + 0x0312 MeasuredLV rational64s + 0x034c UserProfile string + 0x0359 ISOSelected int32s + 0x035a FNumber int32s + 0x035b CorrelatedColorTemp int16u + 0x035c ColorTint int16s + 0x035d WhitePoint rational64u[2] + +=head3 Panasonic Type2 Tags + +This type of maker notes is used by models such as the NV-DS65, PV-D2002, +PV-DC3000, PV-DV203, PV-DV401, PV-DV702, PV-L2001, PV-SD4090, PV-SD5000 and +iPalm. + + Index2 Tag Name Writable + ------ -------- -------- + 0 MakerNoteType no + 3 Gain no + +=head3 Panasonic PANA Tags + +Tags extracted from the PANA and LEIC user data found in MP4 videos from +various Panasonic and Leica models. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 4 Model no + 12 Model no + 16 JPEG-likeData EXIF + 22 Model no + 52 Version1 no + 62 Version2 no + 80 MakerNoteLeica5 Panasonic Leica5 + 88 ThumbnailWidth no + 90 ThumbnailHeight no + 92 ThumbnailImage no + 1334 ThumbnailWidth no + 1338 ThumbnailHeight no + 1342 ThumbnailLength no + 1350 ThumbnailImage no + 1358 ThumbnailWidth no + 1362 ThumbnailHeight no + 1366 ThumbnailLength no + 1374 ThumbnailImage no + 16488 ExifData EXIF + 16512 ExifData EXIF + 0x00200080 ExifData EXIF + +=head2 Pentax Tags + +These tags are used in Pentax/Asahi cameras. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 PentaxVersion int8u[4] + 0x0001 PentaxModelType int16u + 0x0002 PreviewImageSize int16u[2] + 0x0003 PreviewImageLength int32u* + 0x0004 PreviewImageStart int32u* + 0x0005 PentaxModelID int32u + 0x0006 Date undef[4] + 0x0007 Time undef[3] + 0x0008 Quality int16u + 0x0009 PentaxImageSize int16u + 0x000b PictureMode int16u[n] + 0x000c FlashMode int16u[n] + 0x000d FocusMode int16u + 0x000e AFPointSelected int16u + 0x000f AFPointsInFocus int32u + AFPointsInFocus int16u + 0x0010 FocusPosition int16u + 0x0012 ExposureTime int32u + 0x0013 FNumber int16u + 0x0014 ISO int16u + 0x0015 LightReading int16u + 0x0016 ExposureCompensation int16u + ExposureCompensation int16u[2] + 0x0017 MeteringMode int16u + 0x0018 AutoBracketing int16u[n] + 0x0019 WhiteBalance int16u + 0x001a WhiteBalanceMode int16u + 0x001b BlueBalance int16u + 0x001c RedBalance int16u + 0x001d FocalLength int32u + 0x001e DigitalZoom int16u + 0x001f Saturation int16u[n] + 0x0020 Contrast int16u[n] + 0x0021 Sharpness int16u[n] + 0x0022 WorldTimeLocation int16u + 0x0023 HometownCity int16u + 0x0024 DestinationCity int16u + 0x0025 HometownDST int16u + 0x0026 DestinationDST int16u + 0x0027 DSPFirmwareVersion undef + 0x0028 CPUFirmwareVersion undef + 0x0029 FrameNumber int32u + 0x002d EffectiveLV int16u + EffectiveLV int32u + 0x0032 ImageEditing undef[4] + 0x0033 PictureMode int8u[3] + 0x0034 DriveMode int8u[4] + 0x0035 SensorSize int16u[2] + 0x0037 ColorSpace int16u + 0x0038 ImageAreaOffset int16u[2] + 0x0039 RawImageSize int16u[2]~ + 0x003c AFPointsInFocus no + 0x003d DataScaling int16u + 0x003e PreviewImageBorders int8u[4] + 0x003f LensRec Pentax LensRec + 0x0040 SensitivityAdjust int16u + 0x0041 ImageEditCount int16u + 0x0047 CameraTemperature int8s + 0x0048 AELock int16u + 0x0049 NoiseReduction int16u + 0x004d FlashExposureComp int32s + FlashExposureComp int8s[2] + 0x004f ImageTone int16u + 0x0050 ColorTemperature int16u + 0x0053 ColorTempDaylight undef[4] + 0x0054 ColorTempShade undef[4] + 0x0055 ColorTempCloudy undef[4] + 0x0056 ColorTempTungsten undef[4] + 0x0057 ColorTempFluorescentD undef[4] + 0x0058 ColorTempFluorescentN undef[4] + 0x0059 ColorTempFluorescentW undef[4] + 0x005a ColorTempFlash undef[4] + 0x005c ShakeReductionInfo Pentax SRInfo + Pentax SRInfo2 + 0x005d ShutterCount undef[4] + 0x0060 FaceInfo Pentax FaceInfo + 0x0062 RawDevelopmentProcess int16u + 0x0067 Hue int16u + 0x0068 AWBInfo Pentax AWBInfo + 0x0069 DynamicRangeExpansion undef[4] + 0x006b TimeInfo Pentax TimeInfo + 0x006c HighLowKeyAdj int16s[2] + 0x006d ContrastHighlight int16s[2] + 0x006e ContrastShadow int16s[2] + 0x006f ContrastHighlightShadowAdj int8u + 0x0070 FineSharpness int8u[n] + 0x0071 HighISONoiseReduction int8u + 0x0072 AFAdjustment int16s + 0x0073 MonochromeFilterEffect int16u + 0x0074 MonochromeToning int16u + 0x0076 FaceDetect int8u[2] + 0x0077 FaceDetectFrameSize int16u[2] + 0x0079 ShadowCorrection int8u[n] + 0x007a ISOAutoParameters int8u[2] + 0x007b CrossProcess int8u + 0x007d LensCorr Pentax LensCorr + 0x007e WhiteLevel int32u + 0x007f BleachBypassToning int16u + 0x0080 AspectRatio yes + 0x0082 BlurControl int8u[4] + 0x0085 HDR int8u[4] + 0x0087 ShutterType int8u + 0x0088 NeutralDensityFilter int8u[n] + 0x008b ISO int32u + 0x0092 IntervalShooting int16u[2] + 0x0095 SkinToneCorrection int8s[2] + SkinToneCorrection int8s[3] + 0x0096 ClarityControl int8s[2] + 0x0200 BlackPoint int16u[4] + 0x0201 WhitePoint int16u[4] + 0x0203 ColorMatrixA int16s[9] + 0x0204 ColorMatrixB int16s[9] + 0x0205 CameraSettings Pentax CameraSettings + CameraSettingsUnknown Pentax CameraSettingsUnknown + 0x0206 AEInfo Pentax AEInfo + AEInfo2 Pentax AEInfo2 + AEInfo3 Pentax AEInfo3 + AEInfoUnknown Pentax AEInfoUnknown + 0x0207 LensInfo Pentax LensInfo + Pentax LensInfo2 + Pentax LensInfo3 + Pentax LensInfo4 + Pentax LensInfo5 + 0x0208 FlashInfo Pentax FlashInfo + FlashInfoUnknown Pentax FlashInfoUnknown + 0x0209 AEMeteringSegments int8u[n] + 0x020a FlashMeteringSegments int8u[n] + 0x020b SlaveFlashMeteringSegments int8u[n] + 0x020d WB_RGGBLevelsDaylight int16u[4] + 0x020e WB_RGGBLevelsShade int16u[4] + 0x020f WB_RGGBLevelsCloudy int16u[4] + 0x0210 WB_RGGBLevelsTungsten int16u[4] + 0x0211 WB_RGGBLevelsFluorescentD int16u[4] + 0x0212 WB_RGGBLevelsFluorescentN int16u[4] + 0x0213 WB_RGGBLevelsFluorescentW int16u[4] + 0x0214 WB_RGGBLevelsFlash int16u[4] + 0x0215 CameraInfo Pentax CameraInfo + 0x0216 BatteryInfo Pentax BatteryInfo + 0x021b SaturationInfo? no + 0x021c ColorMatrixA2 undef[18] + 0x021d ColorMatrixB2 undef[18] + 0x021f AFInfo Pentax AFInfo + 0x0220 HuffmanTable? no + 0x0221 KelvinWB Pentax KelvinWB + 0x0222 ColorInfo Pentax ColorInfo + 0x0224 EVStepInfo Pentax EVStepInfo + 0x0226 ShotInfo Pentax ShotInfo + 0x0227 FacePos Pentax FacePos + 0x0228 FaceSize Pentax FaceSize + 0x0229 SerialNumber string + 0x022a FilterInfo Pentax FilterInfo + Pentax FilterInfo + 0x022b LevelInfo Pentax LevelInfo + 0x022d WBLevels Pentax WBLevels + 0x022e Artist string + 0x022f Copyright string + 0x0230 FirmwareVersion string + 0x0231 ContrastDetectAFArea int16u[4] + 0x0235 CrossProcessParams undef[10] + 0x0239 LensInfoQ Pentax LensInfoQ + 0x023f Model string + 0x0243 PixelShiftInfo Pentax PixelShiftInfo + 0x0245 AFPointInfo Pentax AFPointInfo + 0x03fe DataDump no + 0x03ff TempInfo Pentax TempInfo + UnknownInfo Pentax UnknownInfo + 0x0402 ToneCurve yes~ + 0x0403 ToneCurves yes~ + 0x0405 UnknownBlock? undef + 0x0e00 PrintIM PrintIM + +=head3 Pentax LensRec Tags + +This record stores the LensType, plus one or two unknown bytes for some +models. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensType int8u[2] + 3 ExtenderStatus int8u + +=head3 Pentax SRInfo Tags + +Shake reduction information. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SRResult int8u + 1 ShakeReduction int8u + 2 SRHalfPressTime int8u + 3 SRFocalLength int8u + +=head3 Pentax SRInfo2 Tags + +Shake reduction information for the K-3. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SRResult? int8u + 1 ShakeReduction int8u + +=head3 Pentax FaceInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FacesDetected int8u + 2 FacePosition int8u[2] + +=head3 Pentax AWBInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 WhiteBalanceAutoAdjustment int8u + 1 TungstenAWB int8u + +=head3 Pentax TimeInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 WorldTimeLocation int8u & 0x01 + 0.2 HometownDST int8u & 0x02 + 0.3 DestinationDST int8u & 0x04 + 2 HometownCity int8u + 3 DestinationCity int8u + +=head3 Pentax LensCorr Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 DistortionCorrection int8u + 1 ChromaticAberrationCorrection int8u + 2 PeripheralIlluminationCorr int8u + 3 DiffractionCorrection int8u + +=head3 Pentax CameraSettings Tags + +Camera settings information written by Pentax DSLR cameras. + + Index1 Tag Name Writable + ------ -------- -------- + 0 PictureMode2 int8u + 1.1 ProgramLine int8u & 0x03 + 1.2 EVSteps int8u & 0x20 + 1.3 E-DialInProgram int8u & 0x40 + 1.4 ApertureRingUse int8u & 0x80 + 2 FlashOptions int8u & 0xf0 + 2.1 MeteringMode2 int8u & 0x0f + 3 AFPointMode int8u & 0xf0 + 3.1 FocusMode2 int8u & 0x0f + 4 AFPointSelected2 int16u + 6 ISOFloor int8u + 7 DriveMode2 int8u + 8 ExposureBracketStepSize int8u + 9 BracketShotNumber int8u + 10 WhiteBalanceSet int8u & 0xf0 + 10.1 MultipleExposureSet int8u & 0x0f + 13 RawAndJpgRecording int8u + 14.1 JpgRecordedPixels int8u & 0x03 + 14.2 LinkAEToAFPoint int8u & 0x01 + 14.3 SensitivitySteps int8u & 0x02 + 14.4 ISOAuto int8u & 0x04 + 16 FlashOptions2 int8u & 0xf0 + 16.1 MeteringMode3 int8u & 0x0f + 17.1 SRActive int8u & 0x80 + 17.2 Rotation int8u & 0x60 + 17.3 ISOSetting int8u & 0x04 + 17.4 SensitivitySteps int8u & 0x02 + 18 TvExposureTimeSetting int8u + 19 AvApertureSetting int8u + 20 SvISOSetting int8u + 21 BaseExposureCompensation int8u + +=head3 Pentax CameraSettingsUnknown Tags + +This information has not yet been decoded for models such as the K-01. + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Pentax AEInfo Tags + +Auto-exposure information for most Pentax models. + + Index1 Tag Name Writable + ------ -------- -------- + 0 AEExposureTime int8u + 1 AEAperture int8u + 2 AE_ISO int8u + 3 AEXv int8u + 4 AEBXv int8s + 5 AEMinExposureTime int8u + 6 AEProgramMode int8u + 7 AEFlags no + 8 AEApertureSteps int8u + 9 AEMaxAperture int8u + 10 AEMaxAperture2 int8u + 11 AEMinAperture int8u + 12 AEMeteringMode int8u + 13 AEWhiteBalance int8u & 0xf0 + 13.1 AEMeteringMode2 int8u & 0x0f + 14 FlashExposureCompSet int8s + 21 LevelIndicator int8u + +=head3 Pentax AEInfo2 Tags + +Auto-exposure information for the K-01. + + Index1 Tag Name Writable + ------ -------- -------- + 2 AEExposureTime int8u + 3 AEAperture int8u + 4 AE_ISO int8u + 5 AEXv int8u + 6 AEBXv int8s + 8 AEError int8s + 11 AEApertureSteps int8u + 15 SceneMode int8u + 16 AEMaxAperture int8u + 17 AEMaxAperture2 int8u + 18 AEMinAperture int8u + 19 AEMinExposureTime int8u + +=head3 Pentax AEInfo3 Tags + +Auto-exposure information for the K-3, K-30, K-50 and K-500. + + Index1 Tag Name Writable + ------ -------- -------- + 16 AEExposureTime int8u + 17 AEAperture int8u + 18 AE_ISO int8u + 28 AEMaxAperture int8u + 29 AEMaxAperture2 int8u + 30 AEMinAperture int8u + 31 AEMinExposureTime int8u + +=head3 Pentax AEInfoUnknown Tags + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Pentax LensInfo Tags + +Pentax lens information structure for models such as the *istD. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensType int8u[2] + 3 LensData Pentax LensData + +=head3 Pentax LensInfo2 Tags + +Pentax lens information structure for models such as the K10D and K20D. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensType int8u[4] + 4 LensData Pentax LensData + +=head3 Pentax LensData Tags + +Pentax lens data information. Some of these tags require interesting binary +gymnastics to decode them into useful values. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 AutoAperture int8u & 0x01 + 0.2 MinAperture int8u & 0x06 + 0.3 LensFStops int8u & 0x70 + 1 LensKind? int8u + 2 LC1? int8u + 3 MinFocusDistance int8u & 0xf8 + 3.1 FocusRangeIndex int8u & 0x07 + 4 LC3? int8u + 5 LC4? int8u + 6 LC5? int8u + 7 LC6? int8u + 8 LC7? int8u + 9 LensFocalLength int8u + LC8? int8u + 10 NominalMaxAperture int8u & 0xf0 + 10.1 NominalMinAperture int8u & 0x0f + 11 LC10? int8u + 12 LC11? int8u + 13 LC12? int8u + 14.1 MaxAperture int8u & 0x7f + 15 LC14? int8u + 16 LC15? int8u + +=head3 Pentax LensInfo3 Tags + +Pentax lens information structure for 645D. + + Index1 Tag Name Writable + ------ -------- -------- + 1 LensType int8u[4] + 13 LensData Pentax LensData + +=head3 Pentax LensInfo4 Tags + +Pentax lens information structure for models such as the K-5 and K-r. + + Index1 Tag Name Writable + ------ -------- -------- + 1 LensType int8u[4] + 12 LensData Pentax LensData + +=head3 Pentax LensInfo5 Tags + +Pentax lens information structure for the K-01 and newer models. + + Index1 Tag Name Writable + ------ -------- -------- + 1 LensType int8u[5] + 15 LensData Pentax LensData + +=head3 Pentax FlashInfo Tags + +Flash information tags for the K10D, K20D and K200D. + + Index1 Tag Name Writable + ------ -------- -------- + 0 FlashStatus int8u + 1 InternalFlashMode int8u + 2 ExternalFlashMode int8u + 3 InternalFlashStrength int8u + 4 TTL_DA_AUp int8u + 5 TTL_DA_ADown int8u + 6 TTL_DA_BUp int8u + 7 TTL_DA_BDown int8u + 24.1 ExternalFlashGuideNumber int8u & 0x1f + 25 ExternalFlashExposureComp int8u + 26 ExternalFlashBounce int8u + +=head3 Pentax FlashInfoUnknown Tags + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Pentax CameraInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 PentaxModelID int32u + 1 ManufactureDate int32u + 2 ProductionCode int32u[2] + 4 InternalSerialNumber int32u + +=head3 Pentax BatteryInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 PowerSource int8u & 0x0f + 1.1 BodyBatteryState int8u & 0xf0 + 1.2 GripBatteryState int8u & 0x0f + GripBatteryState? int8u & 0x0f + 2 BodyBatteryADNoLoad int8u + BodyBatteryVoltage1 int16u + 3 BodyBatteryADLoad int8u + 4 GripBatteryADNoLoad int8u + BodyBatteryVoltage2 int16u + 5 GripBatteryADLoad int8u + 6 BodyBatteryVoltage3 int16u + 8 BodyBatteryVoltage4 int16u + +=head3 Pentax AFInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 AFPointsUnknown1? int16u + 2 AFPointsUnknown2? int16u + 4 AFPredictor int16s + 6 AFDefocus int8u + 7 AFIntegrationTime int8u + 11 AFPointsInFocus int8u + 509 AFHold int8u + +=head3 Pentax KelvinWB Tags + +White balance Blue/Red gains as a function of color temperature. + + Index2 Tag Name Writable + ------ -------- -------- + 1 KelvinWB_Daylight int16u[4] + 5 KelvinWB_01 int16u[4] + 9 KelvinWB_02 int16u[4] + 13 KelvinWB_03 int16u[4] + 17 KelvinWB_04 int16u[4] + 21 KelvinWB_05 int16u[4] + 25 KelvinWB_06 int16u[4] + 29 KelvinWB_07 int16u[4] + 33 KelvinWB_08 int16u[4] + 37 KelvinWB_09 int16u[4] + 41 KelvinWB_10 int16u[4] + 45 KelvinWB_11 int16u[4] + 49 KelvinWB_12 int16u[4] + 53 KelvinWB_13 int16u[4] + 57 KelvinWB_14 int16u[4] + 61 KelvinWB_15 int16u[4] + 65 KelvinWB_16 int16u[4] + +=head3 Pentax ColorInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 16 WBShiftAB int8s + 17 WBShiftGM int8s + +=head3 Pentax EVStepInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 EVSteps int8u + 1 SensitivitySteps int8u + +=head3 Pentax ShotInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 CameraOrientation int8u + +=head3 Pentax FacePos Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 Face1Position int16u[2] + 2 Face2Position int16u[2] + 4 Face3Position int16u[2] + 6 Face4Position int16u[2] + 8 Face5Position int16u[2] + 10 Face6Position int16u[2] + 12 Face7Position int16u[2] + 14 Face8Position int16u[2] + 16 Face9Position int16u[2] + 18 Face10Position int16u[2] + 20 Face11Position int16u[2] + 22 Face12Position int16u[2] + 24 Face13Position int16u[2] + 26 Face14Position int16u[2] + 28 Face15Position int16u[2] + 30 Face16Position int16u[2] + 32 Face17Position int16u[2] + 34 Face18Position int16u[2] + 36 Face19Position int16u[2] + 38 Face20Position int16u[2] + 40 Face21Position int16u[2] + 42 Face22Position int16u[2] + 44 Face23Position int16u[2] + 46 Face24Position int16u[2] + 48 Face25Position int16u[2] + 50 Face26Position int16u[2] + 52 Face27Position int16u[2] + 54 Face28Position int16u[2] + 56 Face29Position int16u[2] + 58 Face30Position int16u[2] + 60 Face31Position int16u[2] + 62 Face32Position int16u[2] + +=head3 Pentax FaceSize Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 Face1Size int16u[2] + 2 Face2Size int16u[2] + 4 Face3Size int16u[2] + 6 Face4Size int16u[2] + 8 Face5Size int16u[2] + 10 Face6Size int16u[2] + 12 Face7Size int16u[2] + 14 Face8Size int16u[2] + 16 Face9Size int16u[2] + 18 Face10Size int16u[2] + 20 Face11Size int16u[2] + 22 Face12Size int16u[2] + 24 Face13Size int16u[2] + 26 Face14Size int16u[2] + 28 Face15Size int16u[2] + 30 Face16Size int16u[2] + 32 Face17Size int16u[2] + 34 Face18Size int16u[2] + 36 Face19Size int16u[2] + 38 Face20Size int16u[2] + 40 Face21Size int16u[2] + 42 Face22Size int16u[2] + 44 Face23Size int16u[2] + 46 Face24Size int16u[2] + 48 Face25Size int16u[2] + 50 Face26Size int16u[2] + 52 Face27Size int16u[2] + 54 Face28Size int16u[2] + 56 Face29Size int16u[2] + 58 Face30Size int16u[2] + 60 Face31Size int16u[2] + 62 Face32Size int16u[2] + +=head3 Pentax FilterInfo Tags + +The parameters associated with each type of digital filter are unique, and +these settings are also extracted with the DigitalFilter tag. Information +is not extracted for filters that are "Off" unless the Unknown option is +used. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SourceDirectoryIndex int16u + 2 SourceFileIndex int16u + 5 DigitalFilter01 undef[17] + 22 DigitalFilter02 undef[17] + 39 DigitalFilter03 undef[17] + 56 DigitalFilter04 undef[17] + 73 DigitalFilter05 undef[17] + 90 DigitalFilter06 undef[17] + 107 DigitalFilter07 undef[17] + 124 DigitalFilter08 undef[17] + 141 DigitalFilter09 undef[17] + 158 DigitalFilter10 undef[17] + 175 DigitalFilter11 undef[17] + 192 DigitalFilter12 undef[17] + 209 DigitalFilter13 undef[17] + 226 DigitalFilter14 undef[17] + 243 DigitalFilter15 undef[17] + 260 DigitalFilter16 undef[17] + 277 DigitalFilter17 undef[17] + 294 DigitalFilter18 undef[17] + 311 DigitalFilter19 undef[17] + 328 DigitalFilter20 undef[17] + +=head3 Pentax LevelInfo Tags + +Tags decoded from the electronic level information for the K-5. May not be +valid for other models. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LevelOrientation int8s & 0x0f + 0.1 CompositionAdjust int8s & 0xf0 + 1 RollAngle int8s + 2 PitchAngle int8s + 5 CompositionAdjustX int8s + 6 CompositionAdjustY int8s + 7 CompositionAdjustRotation int8s + +=head3 Pentax WBLevels Tags + + Index1 Tag Name Writable + ------ -------- -------- + 2 WB_RGGBLevelsDaylight int16u[4] + 11 WB_RGGBLevelsShade int16u[4] + 20 WB_RGGBLevelsCloudy int16u[4] + 29 WB_RGGBLevelsTungsten int16u[4] + 38 WB_RGGBLevelsFluorescentD int16u[4] + 47 WB_RGGBLevelsFluorescentN int16u[4] + 56 WB_RGGBLevelsFluorescentW int16u[4] + 65 WB_RGGBLevelsFlash int16u[4] + 74 WB_RGGBLevelsFluorescentL int16u[4] + 83 WB_RGGBLevelsUnknown? int16u[4] + 92 WB_RGGBLevelsUserSelected int16u[4] + +=head3 Pentax LensInfoQ Tags + +More lens information stored by the Pentax Q. + + Index1 Tag Name Writable + ------ -------- -------- + 12 LensModel string[30] + 42 LensInfo string[20] + +=head3 Pentax PixelShiftInfo Tags + +Pixel shift information stored by the K-3 II. + + Index1 Tag Name Writable + ------ -------- -------- + 0 PixelShiftResolution int8u + +=head3 Pentax AFPointInfo Tags + +AF point information written by the K-1. + + Index1 Tag Name Writable + ------ -------- -------- + 2 NumAFPoints int16u + 4 AFPointsInFocus int8u[9]~ + 4.1 AFPointsSelected int8u[9]~ + 4.2 AFPointsSpecial int8u[9]~ + +=head3 Pentax TempInfo Tags + +A number of additional temperature readings are extracted from this 256-byte +binary-data block in images from models such as the K-01, K-3, K-5, K-50 and +K-500. It is currently not known where the corresponding temperature +sensors are located in the camera. + + Index1 Tag Name Writable + ------ -------- -------- + 12 SensorTemperature int16s + 14 SensorTemperature2 int16s + 20 CameraTemperature4 int16s + 22 CameraTemperature5 int16s + +=head3 Pentax UnknownInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Pentax Type2 Tags + +These tags are used by the Pentax Optio 330 and 430, and are similar to the +tags used by Casio. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 RecordingMode int16u + 0x0002 Quality int16u + 0x0003 FocusMode int16u + 0x0004 FlashMode int16u + 0x0007 WhiteBalance int16u + 0x000a DigitalZoom int32u + 0x000b Sharpness int16u + 0x000c Contrast int16u + 0x000d Saturation int16u + 0x0014 ISO int16u + 0x0017 ColorFilter int16u + 0x0e00 PrintIM PrintIM + 0x1000 HometownCityCode undef[4] + 0x1001 DestinationCityCode undef[4] + +=head3 Pentax Type4 Tags + +The following few tags are extracted from the wealth of information +available in maker notes of the Optio E20 and E25. These maker notes are +stored as ASCII text in a format very similar to some HP models. + + Tag ID Tag Name Writable + ------ -------- -------- + 'F/W Version' FirmwareVersion no + +=head3 Pentax S1 Tags + +Tags extracted from the maker notes of AVI videos from the Optio S1. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 MakerNoteVersion no + +=head3 Pentax Junk Tags + +Tags found in the JUNK chunk of AVI videos from the RS1000. + + Index1 Tag Name Writable + ------ -------- -------- + 12 Model no + +=head3 Pentax Junk2 Tags + +This information is found in AVI videos from the Optio RZ18. + + Index1 Tag Name Writable + ------ -------- -------- + 18 Make no + 44 Model no + 94 FNumber no + 131 DateTime1 no + 157 DateTime2 no + 299 ThumbnailWidth no + 301 ThumbnailHeight no + 303 ThumbnailLength no + 307 ThumbnailImage no + +=head3 Pentax AVI Tags + +Pentax-specific RIFF tags found in AVI videos. + + Tag ID Tag Name Writable + ------ -------- -------- + 'hymn' MakerNotes Pentax + 'mknt' MakerNotes Pentax + +=head3 Pentax PENT Tags + +Tags found in the PENT atom of MOV videos from the Optio WG-2 GPS. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 26 Model no + 56 ExposureTime no + 60 FNumber no + 68 ExposureCompensation no + 84 FocalLength no + 113 DateTime1 no + 139 DateTime2 no + 167 ISO no + 199 GPSVersionID no + 207 GPSLatitudeRef no + 209 GPSLatitude no + 233 GPSLongitudeRef no + 235 GPSLongitude no + 259 GPSAltitudeRef no + 260 GPSAltitude no + 284 GPSTimeStamp no + 308 GPSSatellites no + 311 GPSStatus no + 313 GPSMeasureMode no + 315 GPSMapDatum no + 322 GPSDateStamp no + 371 AudioCodecID no + 2003 PreviewImage no + +=head3 Pentax PXTH Tags + +Tags found in the PXTH atom of MOV videos from the K-01. + + Index1 Tag Name Writable + ------ -------- -------- + 0 PreviewImageLength no + 4 PreviewImage no + +=head3 Pentax MOV Tags + +This information is found in MOV videos from cameras such as the Optio WP. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 38 ExposureTime no + 42 FNumber no + 50 ExposureCompensation no + 68 WhiteBalance no + 72 FocalLength no + 175 ISO no + +=head2 PhaseOne Tags + +These tags are extracted from the maker notes of Phase One images. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0100 CameraOrientation no + 0x0102 SerialNumber string + 0x0105 ISO int32s + 0x0106 ColorMatrix1 float[9] + 0x0107 WB_RGBLevels float[3] + 0x0108 SensorWidth int32s + 0x0109 SensorHeight int32s + 0x010a SensorLeftMargin int32s + 0x010b SensorTopMargin int32s + 0x010c ImageWidth int32s + 0x010d ImageHeight int32s + 0x010e RawFormat int32s + 0x010f RawData no + 0x0110 SensorCalibration PhaseOne SensorCalibration + 0x0112 DateTimeOriginal no + 0x0113 ImageNumber int32s + 0x0203 Software string + 0x0204 System string + 0x0210 SensorTemperature float + 0x0211 SensorTemperature2 float + 0x0212 UnknownDate? int32u + 0x021c StripOffsets no + 0x021d BlackLevel int32s + 0x0222 SplitColumn int32s + 0x0223 BlackLevelData int16u[n] + 0x0226 ColorMatrix2 float[9] + 0x0267 AFAdjustment float + 0x0301 FirmwareVersions string + 0x0400 ShutterSpeedValue float + 0x0401 ApertureValue float + 0x0402 ExposureCompensation float + 0x0403 FocalLength float + 0x0410 CameraModel string + 0x0412 LensModel string + 0x0414 MaxApertureValue float + 0x0415 MinApertureValue float + 0x0455 Viewfinder string + +=head3 PhaseOne SensorCalibration Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0400 SensorDefects no + 0x0401 AllColorFlatField1? no + 0x0407 SerialNumber string + 0x040b RedBlueFlatField? no + 0x0410 AllColorFlatField2? no + 0x0416 AllColorFlatField3? no + 0x0419 LinearizationCoefficients1 no + 0x041a LinearizationCoefficients2 no + +=head2 Reconyx Tags + +The following tags are extracted from the maker notes of Reconyx Hyperfire +cameras such as the HC500, HC600 and PC900. + + Index2 Tag Name Writable + ------ -------- -------- + 0 MakerNoteVersion no + 1 FirmwareVersion no + 4 FirmwareDate int16u[2] + 6 TriggerMode string[2] + 7 Sequence int16u[2] + 9 EventNumber int16u[2] + 11 DateTimeOriginal int16u[6] + 18 MoonPhase int16u + 19 AmbientTemperatureFahrenheit int16s + 20 AmbientTemperature int16s + 21 SerialNumber undef[30] + 36 Contrast int16u + 37 Brightness int16u + 38 Sharpness int16u + 39 Saturation int16u + 40 InfraredIlluminator int16u + 41 MotionSensitivity int16u + 42 BatteryVoltage int16u + 43 UserLabel string[22] + +=head3 Reconyx Type2 Tags + +Tags extracted from models such as the UltraFire. + + Index1 Tag Name Writable + ------ -------- -------- + 24 FirmwareVersion undef[7] + 31 Micro1Version undef[7] + 38 BootLoaderVersion undef[7] + 45 Micro2Version undef[7] + 52 TriggerMode undef[1] + 53 Sequence int8u[2] + 55 EventNumber int32u + 59 DateTimeOriginal int8u[7] + 66 DayOfWeek int8u + 67 MoonPhase int8u + 68 AmbientTemperatureFahrenheit int16s + 70 AmbientTemperature int16s + 72 Illumination int8u + 73 BatteryVoltage int16u + 75 SerialNumber string[15] + 90 UserLabel string[21] + +=head3 Reconyx Type3 Tags + +Tags extracted from models such as the HF2 PRO. + + Index1 Tag Name Writable + ------ -------- -------- + 16 FileNumber int16u + 18 DirectoryNumber int16u + 42 FirmwareVersion int16u[3] + 48 FirmwareDate int16u[2] + 52 TriggerMode string[2] + 54 Sequence int16u[2] + 58 EventNumber int16u[2] + 62 DateTimeOriginal int16u[6] + 74 DayOfWeek int16u + 76 MoonPhase int16u + 78 AmbientTemperatureFahrenheit int16s + 80 AmbientTemperature int16s + 82 Contrast int16u + 84 Brightness int16u + 86 Sharpness int16u + 88 Saturation int16u + 90 Flash int16u + 92 AmbientInfrared int16u + 94 AmbientLight int16u + 96 MotionSensitivity int16u + 98 BatteryVoltage int16u + 100 BatteryVoltageAvg int16u + 102 BatteryType int16u + 104 UserLabel string[22] + 126 SerialNumber unicode[15] + +=head2 Sanyo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x00ff MakerNoteOffset int32u + 0x0100 SanyoThumbnail undef + 0x0200 SpecialMode int32u[3] + 0x0201 SanyoQuality int16u + 0x0202 Macro int16u + 0x0204 DigitalZoom rational64u + 0x0207 SoftwareVersion yes + 0x0208 PictInfo yes + 0x0209 CameraID yes + 0x020e SequentialShot int16u + 0x020f WideRange int16u + 0x0210 ColorAdjustmentMode int16u + 0x0213 QuickShot int16u + 0x0214 SelfTimer int16u + 0x0216 VoiceMemo int16u + 0x0217 RecordShutterRelease int16u + 0x0218 FlickerReduce int16u + 0x0219 OpticalZoomOn int16u + 0x021b DigitalZoomOn int16u + 0x021d LightSourceSpecial int16u + 0x021e Resaved int16u + 0x021f SceneSelect int16u + 0x0223 ManualFocusDistance rational64u + FaceInfo - + 0x0224 SequenceShotInterval int16u + 0x0225 FlashMode int16u + 0x0e00 PrintIM PrintIM + 0x0f00 DataDump no + +=head3 Sanyo FaceInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 FacesDetected int32u + 4 FacePosition int32u[4] + +=head3 Sanyo MOV Tags + +This information is found in Sanyo MOV videos. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 24 Model no + 38 ExposureTime no + 42 FNumber no + 50 ExposureCompensation no + 68 WhiteBalance no + 72 FocalLength no + +=head3 Sanyo MP4 Tags + +This information is found in Sanyo MP4 videos. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 24 Model no + 50 FNumber no + 58 ExposureCompensation no + 106 ISO no + 209 Software no + 210 Software no + 241 Thumbnail Sanyo Thumbnail + 242 Thumbnail Sanyo Thumbnail + +=head3 Sanyo Thumbnail Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 ThumbnailWidth no + 2 ThumbnailHeight no + 3 ThumbnailLength no + 4 ThumbnailOffset no + +=head2 Samsung Tags + +Tags found in the binary "STMN" format maker notes written by a number of +Samsung models. + + Index4 Tag Name Writable + ------ -------- -------- + 0 MakerNoteVersion undef[8] + 2 PreviewImageStart int32u* + 3 PreviewImageLength int32u* + 11 SamsungIFD Samsung IFD + +=head3 Samsung IFD Tags + +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. + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Samsung Type2 Tags + +Tags found in the EXIF-format maker notes of newer Samsung models. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 MakerNoteVersion undef[4] + 0x0002 DeviceType int32u + 0x0003 SamsungModelID int32u + 0x0011 OrientationInfo Samsung OrientationInfo + 0x0020 SmartAlbumColor int16u[2] + 0x0021 PictureWizard Samsung PictureWizard + 0x0030 LocalLocationName string + 0x0031 LocationName string + 0x0035 PreviewIFD Nikon PreviewIFD + Nikon PreviewIFD + 0x0040 RawDataByteOrder yes + 0x0041 WhiteBalanceSetup int32u + 0x0043 CameraTemperature rational64s + 0x0050 RawDataCFAPattern yes + 0x0100 FaceDetect int16u + 0x0120 FaceRecognition int32u + 0x0123 FaceName string + 0xa001 FirmwareName string + 0xa002 SerialNumber string + 0xa003 LensType int16u[n] + 0xa004 LensFirmware string + 0xa005 InternalLensSerialNumber string + 0xa010 SensorAreas int32u[8] + 0xa011 ColorSpace int16u + 0xa012 SmartRange int16u + 0xa013 ExposureCompensation rational64s + 0xa014 ISO int32u + 0xa018 ExposureTime rational64u + 0xa019 FNumber rational64u + 0xa01a FocalLengthIn35mmFormat int32u + 0xa020 EncryptionKey int32u[11]! + 0xa021 WB_RGGBLevelsUncorrected int32u[4] + 0xa022 WB_RGGBLevelsAuto int32u[4] + 0xa023 WB_RGGBLevelsIlluminator1 int32u[4] + 0xa024 WB_RGGBLevelsIlluminator2 int32u[4] + 0xa025 HighlightLinearityLimit int32u + 0xa028 WB_RGGBLevelsBlack int32s[4] + 0xa030 ColorMatrix int32s[9] + 0xa031 ColorMatrixSRGB int32s[9] + 0xa032 ColorMatrixAdobeRGB int32s[9] + 0xa033 CbCrMatrixDefault int32s[4] + 0xa034 CbCrMatrix int32s[4] + 0xa035 CbCrGainDefault int32u[2] + 0xa036 CbCrGain int32u[2] + 0xa040 ToneCurveSRGBDefault int32u[23] + 0xa041 ToneCurveAdobeRGBDefault int32u[23] + 0xa042 ToneCurveSRGB int32u[23] + 0xa043 ToneCurveAdobeRGB int32u[23] + 0xa048 RawData? int32s[12] + 0xa050 Distortion? int32s[8] + 0xa051 ChromaticAberration? int16u[22] + 0xa052 Vignetting? int16u[15] + 0xa053 VignettingCorrection? int16u[15] + 0xa054 VignettingSetting? int16u[15] + +=head3 Samsung OrientationInfo Tags + +Camera orientation information written by the Gear 360 (SM-C200). + + Index8 Tag Name Writable + ------ -------- -------- + 0 YawAngle? rational64s + 1 PitchAngle rational64s + 2 RollAngle rational64s + +=head3 Samsung PictureWizard Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 PictureWizardMode int16u + 1 PictureWizardColor int16u + 2 PictureWizardSaturation int16u + 3 PictureWizardSharpness int16u + 4 PictureWizardContrast int16u + +=head3 Samsung APP5 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'ssuniqueid' UniqueID no + +=head3 Samsung Trailer Tags + +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, and from the 'sefd' atom in HEIC images from the +Samsung S10+. + + Tag Name Writable + -------- -------- + DepthMapData no + DepthMapData2 no + DepthMapName no + DualCameraImage no + DualCameraImageName no + DualShotExtra Samsung DualShotExtra + EmbeddedAudioFile no + EmbeddedAudioFileName no + EmbeddedImage no + EmbeddedImage2 no + EmbeddedImageName no + EmbeddedVideoFile no + EmbeddedVideoType no + MCCData no + SingleShotDepthMap no + SingleShotMeta Samsung SingleShotMeta + SurroundShotVideo no + SurroundShotVideoName no + TimeStamp no + +=head3 Samsung DualShotExtra Tags + + Index4 Tag Name Writable + ------ -------- -------- + 16 DepthMapWidth no + 17 DepthMapHeight no + +=head3 Samsung SingleShotMeta Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'beautyColorLevel' BeautyColorLevel no + 'beautyRetouchLevel' BeautyRetouchLevel no + 'blurStrength' BlurStrength no + 'bokehShape' BokehShape no + 'colorpopStrength' ColorpopStrength no + 'depthHWHeight' DepthHWHeight no + 'depthHWWidth' DepthHWWidth no + 'depthSWHeight' DepthSWHeight no + 'depthSWWidth' DepthSWWidth no + 'deviceOrientation' DeviceOrientation no + 'effectStrength' EffectStrength no + 'effectType' EffectType no + 'flipStatus' FlipStatus no + 'inputHeight' InputHeight no + 'inputWidth' InputWidth no + 'isArtBokeh' IsArtBokeh no + 'lensFacing' LensFacing no + 'monoStrength' MonoStrength no + 'objectOrientation' ObjectOrientation no + 'outputHeight' OutputHeight no + 'outputWidth' OutputWidth no + 'perfMode' PerfMode no + 'segHeight' SegHeight no + 'segWidth' SegWidth no + 'sidelightStrength' SidelightStrength no + 'spinStrength' SpinStrength no + 'vintageStrength' VintageStrength no + 'zoomStrength' ZoomStrength no + +=head3 Samsung sec Tags + +This information is found in the @sec atom of Samsung MP4 videos from models +such as the WB30F. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 32 Model no + 512 ThumbnailWidth no + 516 ThumbnailHeight no + 520 ThumbnailLength no + 524 ThumbnailImage no + +=head3 Samsung INFO Tags + +This information is found in MP4 videos from Samsung models such as the +SMX-C20N. + + Tag ID Tag Name Writable + ------ -------- -------- + 'EFCT' Effect no + 'QLTY' Quality no + +=head3 Samsung MP4 Tags + +This information is found in Samsung MP4 videos from models such as the +WP10. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make no + 24 Model no + 46 ExposureTime no + 50 FNumber no + 58 ExposureCompensation no + 106 ISO no + 125 Software no + 244 Thumbnail Samsung Thumbnail + +=head3 Samsung Thumbnail Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 ThumbnailWidth no + 2 ThumbnailHeight no + 3 ThumbnailLength no + 4 ThumbnailOffset no + +=head3 Samsung smta Tags + +This information is found in the smta atom of Samsung MP4 videos from models +such as the Galaxy S4. + + Tag ID Tag Name Writable + ------ -------- -------- + 'svss' SamsungSvss Samsung svss + +=head3 Samsung svss Tags + +This information is found in the svss atom of Samsung MP4 videos from models +such as the Galaxy S4. + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head2 Ricoh Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 MakerNoteType string + 0x0002 FirmwareVersion string + 0x0005 SerialNumber undef[16] + InternalSerialNumber undef[16] + 0x0e00 PrintIM PrintIM + 0x1000 RecordingFormat int16u + 0x1001 ImageInfo Ricoh ImageInfo + ExposureProgram int16u + 0x1002 DriveMode int16u + 0x1003 Sharpness int32u + WhiteBalance int16u + 0x1004 WhiteBalanceFineTune int16u + 0x1006 FocusMode int16u + 0x1007 AutoBracketing int16u + 0x1009 MacroMode int16u + 0x100a FlashMode int16u + 0x100b FlashExposureComp rational64s + 0x100c ManualFlashOutput rational64s + 0x100d FullPressSnap int16u + 0x100e DynamicRangeExpansion int16u + 0x100f NoiseReduction int16u + 0x1010 ImageEffects int16u + 0x1011 Vignetting int16u + 0x1012 Contrast int32u + 0x1013 Saturation int32u + 0x1014 Sharpness int32u + 0x1015 ToningEffect int16u + 0x1016 HueAdjust int16u + 0x1017 WideAdapter int16u + 0x1018 CropMode int16u + 0x1019 NDFilter int16u + 0x101a WBBracketShotNumber int16u + 0x1200 AFStatus int16u + 0x1201 AFAreaXPosition1 int32u + 0x1202 AFAreaYPosition1 int32u + 0x1203 AFAreaXPosition int32u + 0x1204 AFAreaYPosition int32u + 0x1205 AFAreaMode int16u + 0x1307 ColorTempKelvin int32u + 0x1308 ColorTemperature int32u + 0x1500 FocalLength rational64u + 0x1601 SensorWidth int32u + 0x1602 SensorHeight int32u + 0x1603 CroppedImageWidth int32u + 0x1604 CroppedImageHeight int32u + 0x2001 RicohSubdir Ricoh Subdir + RicohSubdirIFD Ricoh Subdir + RicohRR1Subdir Ricoh Subdir + 0x4001 ThetaSubdir Ricoh ThetaSubdir + +=head3 Ricoh ImageInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 RicohImageWidth int16u + 2 RicohImageHeight int16u + 6 RicohDate int8u[7] + 28 PreviewImageStart int16u* + 30 PreviewImageLength int16u* + 32 FlashMode int8u + 33 Macro int8u + 34 Sharpness int8u + 38 WhiteBalance int8u + 39 ISOSetting int8u + 40 Saturation int8u + +=head3 Ricoh Subdir Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 ManufactureDate1 string[20] + 0x0005 ManufactureDate2 string[20] + 0x001a FaceInfo Ricoh FaceInfo + 0x0029 FirmwareInfo Ricoh FirmwareInfo + 0x002a NoiseReduction int32u + 0x002c SerialInfo Ricoh SerialInfo + +=head3 Ricoh FaceInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 181 FacesDetected int8u + 182 FaceDetectFrameSize int16u[2] + 188 Face1Position int16u[4] + 200 Face2Position int16u[4] + 212 Face3Position int16u[4] + 224 Face4Position int16u[4] + 236 Face5Position int16u[4] + 248 Face6Position int16u[4] + 260 Face7Position int16u[4] + 272 Face8Position int16u[4] + +=head3 Ricoh FirmwareInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FirmwareRevision string[12] + 12 FirmwareRevision2 string[12] + +=head3 Ricoh SerialInfo Tags + +This information is found in images from the GXR. + + Index1 Tag Name Writable + ------ -------- -------- + 0 BodyFirmware string[16] + 16 BodySerialNumber string[16] + 32 LensFirmware string[16] + 48 LensSerialNumber string[16] + +=head3 Ricoh ThetaSubdir Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0003 Accelerometer rational64s[2] + 0x0004 Compass rational64u + 0x000a TimeZone string + +=head3 Ricoh Type2 Tags + +Tags written by models such as the Ricoh HZ15 and the Pentax XG-1. These +are not writable due to numerous formatting errors as written by these +cameras. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0207 RicohModel no + 0x0300 RicohMake no + +=head3 Ricoh Text Tags + +Some Ricoh DC and RDC models use a text-based format for their maker notes +instead of the IFD format used by the Caplio models. Below is a list of known +tags in this information. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Bg' BlueGain no + 'Gg' GreenGain no + 'Rev' FirmwareVersion no + 'Rg' RedGain no + 'Rv' FirmwareVersion no + +=head3 Ricoh RMETA Tags + +The Ricoh Caplio Pro G3 has the ability to add custom fields to the APP5 +"RMETA" segment of JPEG images. While only a few observed tags have been +defined below, ExifTool will extract any information found here. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Azimuth' Azimuth no + 'Condition' Condition no + 'Lit' Lit no + 'Location' Location no + 'Sign type' SignType no + '_audio' SoundFile no + '_barcode' Barcodes no+ + +=head3 Ricoh AVI Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'mnrt' MakerNoteRicoh Ricoh + 'rdc2' RicohRDC2? no + 'thum' ThumbnailImage no + 'ucmt' Comment no + +=head2 Sigma Tags + +These tags are written by Sigma/Foveon cameras. In the early days Sigma was +a class leader by releasing their maker note specification to the public, +but since then they have deviated from this standard and newer camera models +are less than consistent about their metadata formats. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0002 SerialNumber string + 0x0003 DriveMode string + 0x0004 ResolutionMode string + 0x0005 AFMode string + 0x0006 FocusSetting string + 0x0007 WhiteBalance string + 0x0008 ExposureMode string + 0x0009 MeteringMode string + 0x000a LensFocalRange string + 0x000b ColorSpace string + 0x000c ExposureCompensation string + ExposureAdjust? rational64s + 0x000d Contrast string + Contrast rational64s + 0x000e Shadow string + Shadow rational64s + 0x000f Highlight string + Highlight rational64s + 0x0010 Saturation string + Saturation rational64s + 0x0011 Sharpness string + Sharpness rational64s + 0x0012 X3FillLight string + X3FillLight rational64s + 0x0014 ColorAdjustment string + ColorAdjustment rational64s[3] + 0x0015 AdjustmentMode string + 0x0016 Quality string + 0x0017 Firmware string + 0x0018 Software string + 0x0019 AutoBracket string + 0x001a PreviewImageStart int32u* + ChrominanceNoiseReduction string + 0x001b PreviewImageLength int32u* + LuminanceNoiseReduction string + 0x001c PreviewImageSize int16u[2] + PreviewImageStart int32u* + 0x001d MakerNoteVersion undef + PreviewImageLength int32u* + 0x001e PreviewImageSize int16u[2] + 0x001f AFPoint string + MakerNoteVersion undef + 0x0022 FileFormat string + 0x0024 Calibration string + 0x0026 FileFormat string + 0x0027 LensType string + LensType - + Sigma LensType + 0x002a LensFocalRange rational64u[2] + 0x002b LensMaxApertureRange rational64u[2] + 0x002c ColorMode int32u + 0x0030 LensApertureRange string + Calibration string + 0x0031 FNumber rational64u + 0x0032 ExposureTime rational64u + 0x0033 ExposureTime2 string + 0x0034 BurstShot int32u + 0x0035 ExposureCompensation rational64s + 0x0039 SensorTemperature string + 0x003a FlashExposureComp rational64s + 0x003b Firmware string + 0x003c WhiteBalance string + 0x003d PictureMode string + 0x0048 LensApertureRange string + 0x0049 FNumber rational64u + 0x004a ExposureTime rational64u + 0x004b ExposureTime2 string + 0x004d ExposureCompensation rational64s + 0x0055 SensorTemperature string + 0x0056 FlashExposureComp rational64s + 0x0057 Firmware2 string + 0x0058 WhiteBalance string + 0x0059 DigitalFilter string + 0x0084 Model string + 0x0086 ISO int16u + 0x0087 ResolutionMode string + 0x0088 WhiteBalance string + 0x008c Firmware string + 0x011f CameraCalibration float[9] + 0x0120 WBSettings Sigma WBSettings + 0x0121 WBSettings2 Sigma WBSettings2 + +=head3 Sigma WBSettings Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 WB_RGBLevelsAuto float[3] + 3 WB_RGBLevelsDaylight float[3] + 6 WB_RGBLevelsShade float[3] + 9 WB_RGBLevelsOvercast float[3] + 12 WB_RGBLevelsIncandescent float[3] + 15 WB_RGBLevelsFluorescent float[3] + 18 WB_RGBLevelsFlash float[3] + 21 WB_RGBLevelsCustom1 float[3] + 24 WB_RGBLevelsCustom2 float[3] + 27 WB_RGBLevelsCustom3 float[3] + +=head3 Sigma WBSettings2 Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 WB_RGBLevelsUnknown0? float[3] + 3 WB_RGBLevelsUnknown1? float[3] + 6 WB_RGBLevelsUnknown2? float[3] + 9 WB_RGBLevelsUnknown3? float[3] + 12 WB_RGBLevelsUnknown4? float[3] + 15 WB_RGBLevelsUnknown5? float[3] + 18 WB_RGBLevelsUnknown6? float[3] + 21 WB_RGBLevelsUnknown7? float[3] + 24 WB_RGBLevelsUnknown8? float[3] + 27 WB_RGBLevelsUnknown9? float[3] + +=head2 Sony Tags + +The following information has been decoded from the MakerNotes of Sony +cameras. Some of these tags have been inherited from the Minolta +MakerNotes. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0010 CameraInfo Sony CameraInfo + CameraInfo2 Sony CameraInfo2 + CameraInfo3 Sony CameraInfo3 + CameraInfoUnknown Sony CameraInfoUnknown + 0x0020 FocusInfo Sony FocusInfo + MoreInfo Sony MoreInfo + 0x0102 Quality int32u + 0x0104 FlashExposureComp rational64s + 0x0105 Teleconverter int32u + 0x0112 WhiteBalanceFineTune int32u + 0x0114 CameraSettings Sony CameraSettings + CameraSettings2 Sony CameraSettings2 + CameraSettings3 Sony CameraSettings3 + CameraSettingsUnknown Sony CameraSettingsUnknown + 0x0115 WhiteBalance int32u + 0x0116 ExtraInfo Sony ExtraInfo + ExtraInfo2 Sony ExtraInfo2 + ExtraInfo3 Sony ExtraInfo3 + 0x0e00 PrintIM PrintIM + 0x1000 MultiBurstMode undef + 0x1001 MultiBurstImageWidth int16u + 0x1002 MultiBurstImageHeight int16u + 0x1003 Panorama Sony Panorama + 0x2001 PreviewImage undef + 0x2002 Rating int32u + 0x2004 Contrast int32s + 0x2005 Saturation int32s + 0x2006 Sharpness int32s + 0x2007 Brightness int32s + 0x2008 LongExposureNoiseReduction int32u + 0x2009 HighISONoiseReduction int16u + 0x200a HDR int32u + 0x200b MultiFrameNoiseReduction int32u + 0x200e PictureEffect int16u + 0x200f SoftSkinEffect int32u + 0x2010 Tag2010a Sony Tag2010a + Tag2010b Sony Tag2010b + Tag2010c Sony Tag2010c + Tag2010d Sony Tag2010d + Tag2010e Sony Tag2010e + Tag2010f Sony Tag2010f + Tag2010g Sony Tag2010g + Tag2010h Sony Tag2010h + Tag2010i Sony Tag2010i + 0x2011 VignettingCorrection int32u + 0x2012 LateralChromaticAberration int32u + 0x2013 DistortionCorrectionSetting int32u + 0x2014 WBShiftAB_GM int32s[2] + 0x2016 AutoPortraitFramed int16u + 0x2017 FlashAction int32u + 0x201a ElectronicFrontCurtainShutter int32u + 0x201b FocusMode int8u + 0x201c AFAreaModeSetting int8u + 0x201d FlexibleSpotPosition int16u[2] + 0x201e AFPointSelected int8u + 0x2020 AFPointsUsed no + 0x2021 AFTracking int8u + 0x2022 FocalPlaneAFPointsUsed no + 0x2023 MultiFrameNREffect int32u + 0x2026 WBShiftAB_GM_Precise int32s[2]~ + 0x2027 FocusLocation int16u[4] + 0x2028 VariableLowPassFilter int16u[2] + 0x2029 RAWFileType int16u + 0x202a Tag202a Sony Tag202a + 0x202b PrioritySetInAWB int8u + 0x202c MeteringMode2 int16u + 0x202d ExposureStandardAdjustment rational64s + 0x202e Quality int16u[2] + 0x202f PixelShiftInfo undef + 0x2031 SerialNumber string + 0x2032 Shadows int32s + 0x2033 Highlights int32s + 0x2034 Fade int32s + 0x2035 SharpnessRange int32s + 0x2036 Clarity int32s + 0x2037 FocusFrameSize no + 0x2039 JPEG-HEIFSwitch int16u + 0x3000 ShotInfo Sony ShotInfo + 0x900b Tag900b Sony Tag900b + 0x9050 Tag9050a Sony Tag9050a + Tag9050b Sony Tag9050b + Tag9050c Sony Tag9050c + Tag9050d Sony Tag9050d + 0x9400 Tag9400a Sony Tag9400a + Tag9400b Sony Tag9400b + Tag9400c Sony Tag9400c + 0x9401 Tag9401 Sony Tag9401 + 0x9402 Tag9402 Sony Tag9402 + 0x9403 Tag9403 Sony Tag9403 + 0x9404 Tag9404a Sony Tag9404a + Tag9404b Sony Tag9404b + Tag9404c Sony Tag9404c + 0x9405 Tag9405a Sony Tag9405a + Tag9405b Sony Tag9405b + 0x9406 Tag9406 Sony Tag9406 + 0x940a Tag940a Sony Tag940a + 0x940c Tag940c Sony Tag940c + 0x940e AFInfo Sony AFInfo + Tag940e Sony Tag940e + 0x9416 Sony_0x9416 Sony Tag9416 + 0xb000 FileFormat int8u[4] + 0xb001 SonyModelID int16u + 0xb020 CreativeStyle string + 0xb021 ColorTemperature int32u + 0xb022 ColorCompensationFilter int32u + 0xb023 SceneMode int32u + 0xb024 ZoneMatching int32u + 0xb025 DynamicRangeOptimizer int32u + 0xb026 ImageStabilization int32u + 0xb027 LensType int32u + 0xb028 MinoltaMakerNote Minolta + 0xb029 ColorMode int32u + 0xb02a LensSpec int8u[8] + 0xb02b FullImageSize int32u[2] + 0xb02c PreviewImageSize int32u[2] + 0xb040 Macro int16u + 0xb041 ExposureMode int16u + 0xb042 FocusMode int16u + 0xb043 AFAreaMode int16u + 0xb044 AFIlluminator int16u + 0xb047 JPEGQuality int16u + 0xb048 FlashLevel int16s + 0xb049 ReleaseMode int16u + 0xb04a SequenceNumber int16u + 0xb04b Anti-Blur int16u + 0xb04e FocusMode int16u + 0xb04f DynamicRangeOptimizer int16u + 0xb050 HighISONoiseReduction2 int16u + 0xb052 IntelligentAuto int16u + 0xb054 WhiteBalance int16u + +=head3 Sony CameraInfo Tags + +Camera information for the A700, A850 and A900. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensSpec undef[8] + 20 FocusModeSetting int8u + 21 AFPointSelected int8u + 25 AFPoint int8u + 30 AFStatusActiveSensor int16s + 32 AFStatusUpper-left int16s + 34 AFStatusLeft int16s + 36 AFStatusLower-left int16s + 38 AFStatusFarLeft int16s + 40 AFStatusBottomAssist-left int16s + 42 AFStatusBottom int16s + 44 AFStatusBottomAssist-right int16s + 46 AFStatusCenter-7 int16s + 48 AFStatusCenter-horizontal int16s + 50 AFStatusCenter-9 int16s + 52 AFStatusCenter-10 int16s + 54 AFStatusCenter-11 int16s + 56 AFStatusCenter-12 int16s + 58 AFStatusCenter-vertical int16s + 60 AFStatusCenter-14 int16s + 62 AFStatusTopAssist-left int16s + 64 AFStatusTop int16s + 66 AFStatusTopAssist-right int16s + 68 AFStatusFarRight int16s + 70 AFStatusUpper-right int16s + 72 AFStatusRight int16s + 74 AFStatusLower-right int16s + 76 AFStatusCenterF2-8 int16s + 304 AFMicroAdjValue int8u + 305 AFMicroAdjMode int8u & 0x80 + 305.1 AFMicroAdjRegisteredLenses int8u & 0x7f + +=head3 Sony CameraInfo2 Tags + +Camera information for the DSLR-A200, A230, A290, A300, A330, A350, A380 and +A390. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensSpec undef[8] + 20 AFPointSelected int8u + 21 FocusModeSetting int8u + 24 AFPoint int8u + 27 AFStatusActiveSensor int16s + 29 AFStatusTop-right int16s + 31 AFStatusBottom-right int16s + 33 AFStatusBottom int16s + 35 AFStatusMiddleHorizontal int16s + 37 AFStatusCenterVertical int16s + 39 AFStatusTop int16s + 41 AFStatusTop-left int16s + 43 AFStatusBottom-left int16s + 45 AFStatusLeft int16s + 47 AFStatusCenterHorizontal int16s + 49 AFStatusRight int16s + +=head3 Sony CameraInfo3 Tags + +Camera information stored by the A33, A35, A55, A450, A500, A550, A560, +A580, NEX-3/5/5C/C3 and VG10E. Some tags are valid only for some of these +models. + + Index1 Tag Name Writable + ------ -------- -------- + 0 LensSpec undef[8] + 14 FocalLength int16u + 16 FocalLengthTeleZoom int16u + 20 AFPointSelected int8u + 21 FocusMode int8u + 24 AFPoint int8u + 25 FocusStatus int8u + 27 AFStatusActiveSensor int16s + 28 AFPointSelected int8u + 29 FocusMode int8u + AFStatusTop-right int16s + 31 AFStatusBottom-right int16s + 32 AFPoint int8u + 33 AFStatusActiveSensor int16s + AFStatusBottom int16s + 35 AFStatus15 Sony AFStatus15 + AFStatusMiddleHorizontal int16s + 37 AFStatusCenterVertical int16s + 39 AFStatusTop int16s + 41 AFStatusTop-left int16s + 43 AFStatusBottom-left int16s + 45 AFStatusLeft int16s + 47 AFStatusCenterHorizontal int16s + 49 AFStatusRight int16s + +=head3 Sony AFStatus15 Tags + +AF Status information for models with 15-point AF. + + Index1 Tag Name Writable + ------ -------- -------- + 0 AFStatusUpper-left int16s + 2 AFStatusLeft int16s + 4 AFStatusLower-left int16s + 6 AFStatusFarLeft int16s + 8 AFStatusTopHorizontal int16s + 10 AFStatusNearRight int16s + 12 AFStatusCenterHorizontal int16s + 14 AFStatusNearLeft int16s + 16 AFStatusBottomHorizontal int16s + 18 AFStatusTopVertical int16s + 20 AFStatusCenterVertical int16s + 22 AFStatusBottomVertical int16s + 24 AFStatusFarRight int16s + 26 AFStatusUpper-right int16s + 28 AFStatusRight int16s + 30 AFStatusLower-right int16s + 32 AFStatusUpper-middle int16s + 34 AFStatusLower-middle int16s + +=head3 Sony CameraInfoUnknown Tags + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Sony FocusInfo Tags + +More camera settings and focus information decoded for models such as the +A200, A230, A290, A300, A330, A350, A380, A390, A700, A850 and A900. + + Index1 Tag Name Writable + ------ -------- -------- + 14 DriveMode2 int8u + 16 Rotation int8u + 20 ImageStabilizationSetting int8u + 21 DynamicRangeOptimizerMode int8u + 43 BracketShotNumber int8u + 44 WhiteBalanceBracketing int8u + 45 BracketShotNumber2 int8u + 46 DynamicRangeOptimizerBracket int8u + 47 ExposureBracketShotNumber int8u + 63 ExposureProgram int8u + 65 CreativeStyle int8u + 109 ISOSetting int8u + 111 ISO int8u + 119 DynamicRangeOptimizerMode int8u + 121 DynamicRangeOptimizerLevel int8u + 2118 ShutterCount int32u + 2491 FocusPosition int8u + 4368 TiffMeteringImage no + +=head3 Sony MoreInfo Tags + +More camera settings information decoded for the A450, A500, A550, A560, +A580, A33, A35, A55, NEX-3/5/C3 and VG10E. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 MoreSettings Sony MoreSettings + 0x0002 FaceInfo Sony FaceInfo + FaceInfoA Sony FaceInfoA + 0x0107 TiffMeteringImage no + 0x0201 MoreInfo0201 Sony MoreInfo0201 + 0x0401 MoreInfo0401 Sony MoreInfo0401 + +=head3 Sony MoreSettings Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 DriveMode2 int8u + 2 ExposureProgram int8u + 3 MeteringMode int8u + 4 DynamicRangeOptimizerSetting int8u + 5 DynamicRangeOptimizerLevel int8u + 6 ColorSpace int8u + 7 CreativeStyleSetting int8u + 8 ContrastSetting int8s + 9 SaturationSetting int8s + 10 SharpnessSetting int8s + 13 WhiteBalanceSetting int8u + 14 ColorTemperatureSetting int8u + 15 ColorCompensationFilterSet int8s + 16 FlashMode int8u + 17 LongExposureNoiseReduction int8u + 18 HighISONoiseReduction int8u + 19 FocusMode int8u + 21 MultiFrameNoiseReduction int8u + 22 HDRSetting int8u + 23 HDRLevel int8u + 24 ViewingMode int8u + 25 FaceDetection int8u + 26 CustomWB_RBLevels int16uRev[2] + 30 BrightnessValue int8u + ExposureCompensationSet int8u + 31 ISO int8u + FlashExposureCompSet int8u + 32 FNumber int8u + LiveViewAFMethod int8u + 33 ExposureTime int8u + ISO int8u + 34 FNumber int8u + 35 FocalLength2 int8u + ExposureTime int8u + 36 ExposureCompensation2 int16s + 37 FocalLength2 int8u + ISO int8u + 38 FlashExposureCompSet2 int16s + ExposureCompensation2 int16s + FNumber int8u + 39 ExposureTime int8u + 40 Orientation2 int8u + 41 FocusPosition2 int8u + FocalLength2 int8u + 42 FlashAction int8u + ExposureCompensation2 int16s + 43 FocusPosition2 int8u + 44 FocusMode2 int8u + FlashAction int8u + FlashExposureCompSet2 int16s + 46 FocusMode2 int8u + Orientation2 int8u + 47 FocusPosition2 int8u + 48 FlashAction int8u + 50 FocusMode2 int8u + 119 FlashAction2 int8u + 120 FlashActionExternal int8u + 124 FlashActionExternal int8u + 130 FlashStatus int8u + 134 FlashStatus int8u + +=head3 Sony FaceInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 FacesDetected int16s + 1 Face1Position int16u[4] + 6 Face2Position int16u[4] + 11 Face3Position int16u[4] + 16 Face4Position int16u[4] + 21 Face5Position int16u[4] + 26 Face6Position int16u[4] + 31 Face7Position int16u[4] + 36 Face8Position int16u[4] + +=head3 Sony FaceInfoA Tags + + Index2 Tag Name Writable + ------ -------- -------- + 3 FacesDetected no + 11 PotentialFace1Position int16u[4] + 21 PotentialFace2Position int16u[4] + 31 PotentialFace3Position int16u[4] + 41 PotentialFace4Position int16u[4] + 51 PotentialFace5Position int16u[4] + 61 PotentialFace6Position int16u[4] + 71 PotentialFace7Position int16u[4] + 81 PotentialFace8Position int16u[4] + 91 Face1Position int16u[4] + 101 Face2Position int16u[4] + 111 Face3Position int16u[4] + 121 Face4Position int16u[4] + +=head3 Sony MoreInfo0201 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 283 ImageCount int32u + 293 ShutterCount int32u + 330 ShutterCount int32u + +=head3 Sony MoreInfo0401 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1102 ShotNumberSincePowerUp int32u + +=head3 Sony CameraSettings Tags + +Camera settings for the A200, A300, A350, A700, A850 and A900. + + Index2 Tag Name Writable + ------ -------- -------- + 0 ExposureTime int16u + 1 FNumber int16u + 2 HighSpeedSync int16u + 3 ExposureCompensationSet int16u + 4 DriveMode int16u & 0xff + 5 WhiteBalanceSetting int16u + 6 WhiteBalanceFineTune int16u + 7 ColorTemperatureSet int16u + 8 ColorCompensationFilterSet int16u + 12 ColorTemperatureCustom int16u + 13 ColorCompensationFilterCustom int16u + 15 WhiteBalance int16u + 16 FocusModeSetting int16u + 17 AFAreaMode int16u + 18 AFPointSetting int16u + 19 FlashMode int16u + 20 FlashExposureCompSet int16u + 21 MeteringMode int16u + 22 ISOSetting int16u + 24 DynamicRangeOptimizerMode int16u + 25 DynamicRangeOptimizerLevel int16u + 26 CreativeStyle int16u + 27 ColorSpace int16u + 28 Sharpness int16u + 29 Contrast int16u + 30 Saturation int16u + 31 ZoneMatchingValue int16u + 34 Brightness int16u + 35 FlashControl int16u + 40 PrioritySetupShutterRelease int16u + 41 AFIlluminator int16u + 42 AFWithShutter int16u + 43 LongExposureNoiseReduction int16u + 44 HighISONoiseReduction int16u + 45 ImageStyle int16u + 46 FocusModeSwitch int16u + 47 ShutterSpeedSetting int16u + 48 ApertureSetting int16u + 60 ExposureProgram int16u + 61 ImageStabilizationSetting int16u + 62 FlashAction int16u + 63 Rotation int16u + 64 AELock int16u + 76 FlashAction2 int16u + 77 FocusMode int16u + 80 BatteryState int16u + 81 BatteryLevel int16u + 83 FocusStatus int16u + 84 SonyImageSize int16u + 85 AspectRatio int16u + 86 Quality int16u + 88 ExposureLevelIncrements int16u + 106 RedEyeReduction int16u + 154 FolderNumber int16u & 0x3ff + 155 ImageNumber int16u & 0x3fff + +=head3 Sony CameraSettings2 Tags + +Camera settings for the A230, A290, A330, A380 and A390. + + Index2 Tag Name Writable + ------ -------- -------- + 0 ExposureTime int16u + 1 FNumber int16u + 2 HighSpeedSync int16u + 3 ExposureCompensationSet int16u + 4 WhiteBalanceSetting int16u + 5 WhiteBalanceFineTune int16u + 6 ColorTemperatureSet int16u + 7 ColorCompensationFilterSet int16u + 8 CustomWB_RGBLevels int16u[3] + 11 ColorTemperatureCustom int16u + 12 ColorCompensationFilterCustom int16u + 14 WhiteBalance int16u + 15 FocusModeSetting int16u + 16 AFAreaMode int16u + 17 AFPointSetting int16u + 18 FlashExposureCompSet int16u + 19 MeteringMode int16u + 20 ISOSetting int16u + 22 DynamicRangeOptimizerMode int16u + 23 DynamicRangeOptimizerLevel int16u + 24 CreativeStyle int16u + 25 Sharpness int16u + 26 Contrast int16u + 27 Saturation int16u + 31 FlashControl int16u + 37 LongExposureNoiseReduction int16u + 38 HighISONoiseReduction int16u + 39 ImageStyle int16u + 40 ShutterSpeedSetting int16u + 41 ApertureSetting int16u + 60 ExposureProgram int16u + 61 ImageStabilizationSetting int16u + 62 FlashAction int16u + 63 Rotation int16u + 64 AELock int16u + 76 FlashAction2 int16u + 77 FocusMode int16u + 83 FocusStatus int16u + 84 SonyImageSize int16u + 85 AspectRatio int16u + 86 Quality int16u + 88 ExposureLevelIncrements int16u + 126 DriveMode int16u & 0xff + 127 FlashMode int16u + 131 ColorSpace int16u + +=head3 Sony CameraSettings3 Tags + +Camera settings for models such as the A33, A35, A55, A450, A500, A550, +A560, A580, NEX-3, NEX-5, NEX-C3 and NEX-VG10E. + + Index1 Tag Name Writable + ------ -------- -------- + 0 ShutterSpeedSetting int8u + 1 ApertureSetting int8u + 2 ISOSetting int8u + 3 ExposureCompensationSet int8u + 4 DriveModeSetting int8u + 5 ExposureProgram int8u + 6 FocusModeSetting int8u + 7 MeteringMode int8u + 9 SonyImageSize int8u + 10 AspectRatio int8u + 11 Quality int8u + 12 DynamicRangeOptimizerSetting int8u + 13 DynamicRangeOptimizerLevel int8u + 14 ColorSpace int8u + 15 CreativeStyleSetting int8u + 16 ContrastSetting int8s + 17 SaturationSetting int8s + 18 SharpnessSetting int8s + 22 WhiteBalanceSetting int8u + 23 ColorTemperatureSetting int8u + 24 ColorCompensationFilterSet int8s + 25 CustomWB_RGBLevels int16uRev[3] + 32 FlashMode int8u + 33 FlashControl int8u + 35 FlashExposureCompSet int8u + 36 AFAreaMode int8u + 37 LongExposureNoiseReduction int8u + 38 HighISONoiseReduction int8u + 39 SmileShutterMode int8u + 40 RedEyeReduction int8u + 45 HDRSetting int8u + 46 HDRLevel int8u + 47 ViewingMode int8u + 48 FaceDetection int8u + 49 SmileShutter int8u + 50 SweepPanoramaSize int8u + 51 SweepPanoramaDirection int8u + 52 DriveMode int8u + 53 MultiFrameNoiseReduction int8u + 54 LiveViewAFSetting int8u + 56 PanoramaSize3D int8u + 131 AFButtonPressed int8u + 132 LiveViewMetering int8u + 133 ViewingMode2 int8u + 134 AELock int8u + 135 FlashStatusBuilt-in int8u + 136 FlashStatusExternal int8u + 139 LiveViewFocusMode int8u + 153 LensMount int8u + 268 SequenceNumber int8u + 276 FolderNumber int32u & 0xffc000 + 276.1 ImageNumber int32u & 0x3fff + 512 ShotNumberSincePowerUp2 int32u + 643 AFButtonPressed int8u + 644 LiveViewMetering int8u + 645 ViewingMode2 int8u + 646 AELock int8u + 647 FlashStatusBuilt-in int8u + 648 FlashStatusExternal int8u + 651 LiveViewFocusMode int8u + 780 SequenceNumber int8u + 788 ImageNumber int16u & 0x3fff + 790 FolderNumber int16u & 0x3ff + 1008 LensE-mountVersion int16u + 1011 LensFirmwareVersion int16u~ + 1015 LensType2 int16u + 1024 ImageNumber int16u & 0x3fff + 1026 FolderNumber int16u & 0x3ff + +=head3 Sony CameraSettingsUnknown Tags + + Index2 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Sony ExtraInfo Tags + +Extra hardware information for the A850 and A900. + + Index1 Tag Name Writable + ------ -------- -------- + 1 BatteryTemperature int8u + 2 BatteryUnknown? no + 8 BatteryVoltage? no + 10 ImageStabilization2? int8u + 12 BatteryLevel int8u + 26 ExtraInfoVersion int8u[4] + +=head3 Sony ExtraInfo2 Tags + +Extra hardware information for the A230/290/330/380/390. + + Index1 Tag Name Writable + ------ -------- -------- + 4 BatteryLevel int8u + 18 ImageStabilization int8u + +=head3 Sony ExtraInfo3 Tags + +Extra hardware information for the A33, A35, A55, A450, A500, A550, A560, +A580 and NEX-3/5/C3/VG10. + + Index1 Tag Name Writable + ------ -------- -------- + 0 BatteryUnknown? int16u + 2 BatteryTemperature int8u + 4 BatteryLevel int8u + 6 BatteryVoltage1 int16u + 8 BatteryVoltage2 int16u + 17 ImageStabilization int8u + 20 BatteryState int8u + ExposureProgram int8u + ModeDialPosition int8u + 22 MemoryCardConfiguration int8u & 0xc0 + CameraOrientation int8u + 24 CameraOrientation int8u & 0x30 + +=head3 Sony Panorama Tags + +Tags found in panorama images from various Sony DSC, NEX, SLT and DSLR +cameras. The width/height values of these tags are not affected by camera +rotation -- the width is always the longer dimension. + + Index4 Tag Name Writable + ------ -------- -------- + 1 PanoramaFullWidth int32u + 2 PanoramaFullHeight int32u + 3 PanoramaDirection int32u + 4 PanoramaCropLeft int32u + 5 PanoramaCropTop int32u + 6 PanoramaCropRight int32u + 7 PanoramaCropBottom int32u + 8 PanoramaFrameWidth int32u + 9 PanoramaFrameHeight int32u + 10 PanoramaSourceWidth int32u + 11 PanoramaSourceHeight int32u + +=head3 Sony Tag2010a Tags + +Valid for NEX-5N. + + Index1 Tag Name Writable + ------ -------- -------- + 1200 MeterInfo? Sony MeterInfo + 4392 ReleaseMode3 int8u + 4396 ReleaseMode2 int8u + 4404 SelfTimer int8u + 4408 FlashMode int8u + 4414 StopsAboveBaseISO int16u + 4416 BrightnessValue int16u + 4420 DynamicRangeOptimizer int8u + 4424 HDRSetting int8u + 4428 ExposureCompensation int16s + 4446 PictureProfile int8u + 4447 PictureProfile int8u + 4451 PictureEffect2 int8u + 4464 Quality2 int8u + 4468 MeteringMode int8u + 4469 ExposureProgram int8u + 4476 WB_RGBLevels int16u[3] + +=head3 Sony MeterInfo Tags + +Information possibly related to metering. Extracted only if the Unknown +option is used. + + Index1 Tag Name Writable + ------ -------- -------- + 0 MeterInfo1Row1 int32u[27] + 108 MeterInfo1Row2 int32u[27] + 216 MeterInfo1Row3 int32u[27] + 324 MeterInfo1Row4 int32u[27] + 432 MeterInfo1Row5 int32u[27] + 540 MeterInfo1Row6 int32u[27] + 648 MeterInfo1Row7 int32u[27] + 756 MeterInfo2Row1 int32u[33] + 888 MeterInfo2Row2 int32u[33] + 1020 MeterInfo2Row3 int32u[33] + 1152 MeterInfo2Row4 int32u[33] + 1284 MeterInfo2Row5 int32u[33] + 1416 MeterInfo2Row6 int32u[33] + 1548 MeterInfo2Row7 int32u[33] + 1680 MeterInfo2Row8 int32u[33] + 1812 MeterInfo2Row9 int32u[33] + +=head3 Sony Tag2010b Tags + +Valid for SLT-A65/A77, NEX-7/VG20E. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SequenceImageNumber int32u + 4 SequenceFileNumber int32u + 8 ReleaseMode2 int32u + 438 SonyDateTime undef[7] + 804 DynamicRangeOptimizer int8u + 1204 MeterInfo? Sony MeterInfo + 4392 ReleaseMode3 int8u + 4396 ReleaseMode2 int8u + 4404 SelfTimer int8u + 4408 FlashMode int8u + 4414 StopsAboveBaseISO int16u + 4416 BrightnessValue int16u + 4420 DynamicRangeOptimizer int8u + 4424 HDRSetting int8u + 4428 ExposureCompensation int16s + 4450 PictureProfile int8u + 4451 PictureProfile int8u + 4455 PictureEffect2 int8u + 4468 Quality2 int8u + 4472 MeteringMode int8u + 4473 ExposureProgram int8u + 4480 WB_RGBLevels int16u[3] + 4632 SonyISO int16u + 6691 DistortionCorrParams int16s[16] + +=head3 Sony Tag2010c Tags + +Valid for SLT-A37/A57 and NEX-F3. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SequenceImageNumber int32u + 4 SequenceFileNumber int32u + 8 ReleaseMode2 int32u + 512 DigitalZoomRatio int8u + 528 SonyDateTime undef[7] + 768 DynamicRangeOptimizer int8u + 1168 MeterInfo? Sony MeterInfo + 4356 ReleaseMode3 int8u + 4360 ReleaseMode2 int8u + 4368 SelfTimer int8u + 4372 FlashMode int8u + 4378 StopsAboveBaseISO int16u + 4380 BrightnessValue int16u + 4384 DynamicRangeOptimizer int8u + 4388 HDRSetting int8u + 4392 ExposureCompensation int16s + 4414 PictureProfile int8u + 4415 PictureProfile int8u + 4419 PictureEffect2 int8u + 4432 Quality2 int8u + 4436 MeteringMode int8u + 4437 ExposureProgram int8u + 4444 WB_RGBLevels int16u[3] + 4596 SonyISO int16u + +=head3 Sony Tag2010d Tags + +Valid for DSC-HX10V/HX20V/HX200V/TX66/TX200V/TX300V/WX50/WX100/WX150, but +not valid for panorama images. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SequenceImageNumber int32u + 4 SequenceFileNumber int32u + 8 ReleaseMode2 int32u + 510 SonyDateTime undef[7] + 892 DynamicRangeOptimizer int8u + 1292 MeterInfo? Sony MeterInfo + 4480 ReleaseMode3 int8u + 4484 ReleaseMode2 int8u + 4492 SelfTimer int8u + 4496 FlashMode int8u + 4502 StopsAboveBaseISO int16u + 4504 BrightnessValue int16u + 4508 DynamicRangeOptimizer int8u + 4512 HDRSetting int8u + 4538 PictureProfile int8u + 4539 PictureProfile int8u + 4543 PictureEffect2 int8u + 4560 MeteringMode int8u + 4561 ExposureProgram int8u + 4568 WB_RGBLevels int16u[3] + 4720 SonyISO int16u + +=head3 Sony Tag2010e Tags + +Valid for SLT-A58/A99, ILCE-3000/3500, NEX-3N/5R/5T/6/VG30E/VG900, +DSC-RX100, DSC-RX1/RX1R. Also valid for DSC-HX300/HX50V/TX30/WX60/WX200/ +WX300, but not for panorama images. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SequenceImageNumber int32u + 4 SequenceFileNumber int32u + 8 ReleaseMode2 int32u + 540 DigitalZoomRatio int8u + 556 SonyDateTime undef[7] + 808 DynamicRangeOptimizer int8u + 1208 MeterInfo? Sony MeterInfo + 4444 ReleaseMode3 int8u + 4448 ReleaseMode2 int8u + 4456 SelfTimer int8u + 4460 FlashMode int8u + 4466 StopsAboveBaseISO int16u + 4468 BrightnessValue int16u + 4472 DynamicRangeOptimizer int8u + 4476 HDRSetting int8u + 4480 ExposureCompensation int16s + 4502 PictureProfile int8u + 4503 PictureProfile int8u + 4507 PictureEffect2 int8u + 4520 Quality2 int8u + 4524 MeteringMode int8u + 4525 ExposureProgram int8u + 4532 WB_RGBLevels int16u[3] + 4692 SonyISO int16u + 4696 SonyISO int16u + 4728 FocalLength int16u + 4730 MinFocalLength int16u + 4732 MaxFocalLength int16u + 4736 SonyISO int16u + 6256 DistortionCorrParams int16s[16] + 6289 LensFormat int8u + 6290 LensMount int8u + 6291 LensType2 int16u + 6294 LensType int16u + 6296 DistortionCorrParamsPresent int8u + 6297 DistortionCorrParamsNumber int8u + 6444 AspectRatio int8u + 6792 AspectRatio int8u + +=head3 Sony Tag2010f Tags + +Valid for DSC-RX100M2, DSC-QX10/QX100. + + Index1 Tag Name Writable + ------ -------- -------- + 4 ReleaseMode2 int32u + 80 DynamicRangeOptimizer int8u + 480 MeterInfo? Sony MeterInfo + 4116 ReleaseMode3 int8u + 4120 ReleaseMode2 int8u + 4128 SelfTimer int8u + 4132 FlashMode int8u + 4138 StopsAboveBaseISO int16u + 4140 BrightnessValue int16u + 4144 DynamicRangeOptimizer int8u + 4148 HDRSetting int8u + 4152 ExposureCompensation int16s + 4174 PictureProfile int8u + 4175 PictureProfile int8u + 4179 PictureEffect2 int8u + 4192 Quality2 int8u + 4196 MeteringMode int8u + 4197 ExposureProgram int8u + 4204 WB_RGBLevels int16u[3] + 4404 FocalLength int16u + 4406 MinFocalLength int16u + 4408 MaxFocalLength int16u + 4412 SonyISO int16u + 6444 AspectRatio int8u + +=head3 Sony Tag2010g Tags + +Valid for DSC-HX60V/HX350/HX400V/QX30/RX10/RX100M3/WX220/WX350, +ILCE-7/7R/7S/7M2/5000/5100/6000/QX1, ILCA-68/77M2. + + Index1 Tag Name Writable + ------ -------- -------- + 4 ReleaseMode2 int32u + 80 DynamicRangeOptimizer int8u + 524 ReleaseMode3 int8u + 528 ReleaseMode2 int8u + 536 SelfTimer int8u + 540 FlashMode int8u + 546 StopsAboveBaseISO int16u + 548 BrightnessValue int16u + 552 DynamicRangeOptimizer int8u + 556 HDRSetting int8u + 560 ExposureCompensation int16s + 582 PictureProfile int8u + 583 PictureProfile int8u + 587 PictureEffect2 int8u + 600 Quality2 int8u + 604 MeteringMode int8u + 605 ExposureProgram int8u + 612 WB_RGBLevels int16u[3] + 812 FocalLength int16u + 814 MinFocalLength int16u + 816 MaxFocalLength int16u + 836 SonyISO int16u + 904 MeterInfo? Sony MeterInfo + 6300 DistortionCorrParams int16s[16] + 6333 LensFormat int8u + 6334 LensMount int8u + 6335 LensType2 int16u + 6338 LensType int16u + 6340 DistortionCorrParamsPresent int8u + 6341 DistortionCorrParamsNumber int8u + 6488 AspectRatio int8u + +=head3 Sony Tag2010h Tags + +Valid for DSC-HX80/HX90V/RX0/RX1RM2/RX10M2/RX10M3/RX100M4/RX100M5/WX500, +ILCE-6300/6500/7RM2/7SM2, ILCA-99M2. + + Index1 Tag Name Writable + ------ -------- -------- + 4 ReleaseMode2 int32u + 80 DynamicRangeOptimizer int8u + 524 ReleaseMode3 int8u + 528 ReleaseMode2 int8u + 536 SelfTimer int8u + 540 FlashMode int8u + 546 StopsAboveBaseISO int16u + 548 BrightnessValue int16u + 552 DynamicRangeOptimizer int8u + 556 HDRSetting int8u + 560 ExposureCompensation int16s + 582 PictureProfile int8u + 583 PictureProfile int8u + 587 PictureEffect2 int8u + 600 Quality2 int8u + 604 MeteringMode int8u + 605 ExposureProgram int8u + 612 WB_RGBLevels int16u[3] + 812 FocalLength int16u + 814 MinFocalLength int16u + 816 MaxFocalLength int16u + 838 SonyISO int16u + 904 MeterInfo? Sony MeterInfo + 920 MeterInfo? Sony MeterInfo + 6348 DistortionCorrParams int16s[16] + 6381 LensFormat int8u + 6382 LensMount int8u + 6383 LensType2 int16u + 6386 LensType int16u + 6388 DistortionCorrParamsPresent int8u + 6389 DistortionCorrParamsNumber int8u + 6444 AspectRatio int8u + +=head3 Sony Tag2010i Tags + +Valid for ILCE-6100/6400/6600/7C/7M3/7RM3/7RM4/9/9M2, DSC-RX0M2/RX10M4/RX100M6/ +RX100M5A/RX100M7/HX99. + + Index1 Tag Name Writable + ------ -------- -------- + 4 ReleaseMode2 int32u + 78 DynamicRangeOptimizer int8u + 516 ReleaseMode3 int8u + 520 ReleaseMode2 int8u + 528 SelfTimer int8u + 529 FlashMode int8u + 535 StopsAboveBaseISO int16u + 537 BrightnessValue int16u + 539 DynamicRangeOptimizer int8u + 543 HDRSetting int8u + 547 ExposureCompensation int16s + 567 PictureProfile int8u + 568 PictureProfile int8u + 572 PictureEffect2 int8u + 583 Quality2 int8u + 587 MeteringMode int8u + 588 ExposureProgram int8u + 594 WB_RGBLevels int16u[3] + 778 FocalLength int16u + 780 MinFocalLength int16u + 782 MaxFocalLength int16u + 800 SonyISO int16u + 877 MeterInfo? Sony MeterInfo9 + 6096 DistortionCorrParams int16s[16] + 6129 LensFormat int8u + 6130 LensMount int8u + 6131 LensType2 int16u + 6134 LensType int16u + 6136 DistortionCorrParamsPresent int8u + 6137 DistortionCorrParamsNumber int8u + 6284 AspectRatio int8u + +=head3 Sony MeterInfo9 Tags + +Information possibly related to metering. Extracted only if the Unknown +option is used. + + Index1 Tag Name Writable + ------ -------- -------- + 0 MeterInfo1Row1 no + 90 MeterInfo1Row2 no + 180 MeterInfo1Row3 no + 270 MeterInfo1Row4 no + 360 MeterInfo1Row5 no + 450 MeterInfo1Row6 no + 540 MeterInfo1Row7 no + 630 MeterInfo2Row1 no + 740 MeterInfo2Row2 no + 850 MeterInfo2Row3 no + 960 MeterInfo2Row4 no + 1070 MeterInfo2Row5 no + 1180 MeterInfo2Row6 no + 1290 MeterInfo2Row7 no + 1400 MeterInfo2Row8 no + 1510 MeterInfo2Row9 no + +=head3 Sony Tag202a Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 FocalPlaneAFPointsUsed int8u + 2 FocalPlaneAFPointArea int16u[2] + 6 FocalPlaneAFPointLocation1 int16u[2] + 10 FocalPlaneAFPointLocation2 int16u[2] + 14 FocalPlaneAFPointLocation3 int16u[2] + 18 FocalPlaneAFPointLocation4 int16u[2] + 22 FocalPlaneAFPointLocation5 int16u[2] + 26 FocalPlaneAFPointLocation6 int16u[2] + 30 FocalPlaneAFPointLocation7 int16u[2] + 34 FocalPlaneAFPointLocation8 int16u[2] + 38 FocalPlaneAFPointLocation9 int16u[2] + 42 FocalPlaneAFPointLocation10 int16u[2] + 46 FocalPlaneAFPointLocation11 int16u[2] + 50 FocalPlaneAFPointLocation12 int16u[2] + 54 FocalPlaneAFPointLocation13 int16u[2] + 58 FocalPlaneAFPointLocation14 int16u[2] + 62 FocalPlaneAFPointLocation15 int16u[2] + +=head3 Sony ShotInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 2 FaceInfoOffset no + 6 SonyDateTime string[20] + 26 SonyImageHeight int16u + 28 SonyImageWidth int16u + 48 FacesDetected int16u + 50 FaceInfoLength no + 52 MetaVersion string[16] + 72 FaceInfo1 Sony FaceInfo1 + 94 FaceInfo2 Sony FaceInfo2 + +=head3 Sony FaceInfo1 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 Face1Position int16u[4] + 32 Face2Position int16u[4] + 64 Face3Position int16u[4] + 96 Face4Position int16u[4] + 128 Face5Position int16u[4] + 160 Face6Position int16u[4] + 192 Face7Position int16u[4] + 224 Face8Position int16u[4] + +=head3 Sony FaceInfo2 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 Face1Position int16u[4] + 37 Face2Position int16u[4] + 74 Face3Position int16u[4] + 111 Face4Position int16u[4] + 148 Face5Position int16u[4] + 185 Face6Position int16u[4] + 222 Face7Position int16u[4] + 259 Face8Position int16u[4] + +=head3 Sony Tag900b Tags + + Index1 Tag Name Writable + ------ -------- -------- + 2 FacesDetected no + 189 FaceDetection no + +=head3 Sony Tag9050a Tags + +Data for tags 0x9050, 0x94xx and 0x2010 is encrypted by a simple +substitution cipher, but the deciphered values are listed below. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SonyMaxAperture int8u + 1 SonyMinAperture int8u + 32 Shutter int16u[3] + 49 FlashStatus int8u + 50 ShutterCount int32u + 58 SonyExposureTime int16u + 60 SonyFNumber int16u + 63 ReleaseMode2 int8u + 76 ShutterCount2 int32u + 81 SonyDateTime2 undef[6] + 103 ReleaseMode2 int8u + 124 InternalSerialNumber int8u[4]~ + 240 InternalSerialNumber int8u[5] + 261 LensMount int8u + 262 LensFormat int8u + 263 LensType2 int16u + 265 LensType int16u + 267 DistortionCorrParamsPresent int8u + 276 APS-CSizeCapture int8u + 277 LensSpecFeatures undef[2] + 278 LensSpecFeatures undef[2] + 416 ShutterCount3 int32u + 426 ShutterCount3 int32u + 445 ShutterCount3 int32u + +=head3 Sony Tag9050b Tags + +Valid from July 2015 for ILCE-6100/6300/6400/6500/6600/7C/7M3/7RM2/7RM3/7RM4/ +7SM2/9/9M2, ILCA-99M2. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SonyMaxAperture int8u + 1 SonyMinAperture int8u + 38 Shutter int16u[3] + 57 FlashStatus int8u + 58 ShutterCount int32u + 70 SonyExposureTime int16u + 72 SonyFNumber int16u + 75 ReleaseMode2 int8u + 80 ShutterCount2 int32u + 82 ShutterCount2 int32u + 88 ShutterCount2 int32u + 97 SonyTimeMinSec no + 107 ReleaseMode2 int8u + 109 ReleaseMode2 int8u + 115 ReleaseMode2 int8u + 136 InternalSerialNumber int8u[6]~ + 261 LensMount int8u + 262 LensFormat int8u + 263 LensType2 int16u + 265 LensType int16u + 267 DistortionCorrParamsPresent int8u + 276 APS-CSizeCapture int8u + 278 LensSpecFeatures undef[2] + 415 ShutterCount3 int32u + 459 ShutterCount3 int32u + 461 ShutterCount3 int32u + 491 APS-CSizeCapture int8u + 493 LensSpecFeatures undef[2] + 494 APS-CSizeCapture int8u + 496 LensSpecFeatures undef[2] + 538 APS-CSizeCapture int8u + 540 LensSpecFeatures undef[2] + APS-CSizeCapture int8u + 542 LensSpecFeatures undef[2] + +=head3 Sony Tag9050c Tags + +Valid from July 2020 for ILCE-1/7SM3, ILME-FX3. + + Index1 Tag Name Writable + ------ -------- -------- + 38 Shutter int16u[3] + 57 FlashStatus int8u + 58 ShutterCount int32u~ + 70 SonyExposureTime int16u + 72 SonyFNumber int16u + 75 ReleaseMode2 int8u + 80 ShutterCount2 int32u + 102 SonyExposureTime int16u + 104 SonyFNumber int16u + 107 ReleaseMode2 int8u + 136 InternalSerialNumber int8u[6]~ + 138 InternalSerialNumber int8u[6]~ + +=head3 Sony Tag9050d Tags + +Valid for ILCE-6700/ZV-E1. + + Index1 Tag Name Writable + ------ -------- -------- + 10 ShutterCount int32u + 26 SonyExposureTime int16u + 28 SonyFNumber int16u + 31 ReleaseMode2 int8u + 56 InternalSerialNumber int8u[6]~ + +=head3 Sony Tag9400a Tags + +Valid for many DSC, NEX and SLT models + + Index1 Tag Name Writable + ------ -------- -------- + 8 SequenceImageNumber int32u + 12 SequenceFileNumber int32u + 16 ReleaseMode2 int8u + 18 DigitalZoom int8u + 26 ShotNumberSincePowerUp int32u + 34 SequenceLength int8u + 40 CameraOrientation int8u + 41 Quality2 int8u + 68 SonyImageHeight int16u~ + 82 ModelReleaseYear int8u~ + +=head3 Sony Tag9400b Tags + +Valid for NEX-3N, ILCE-3000/3500, SLT-A58, DSC-WX60, DSC-WX300, DSC-RX100M2, +DSC-HX50V, DSC-QX10/QX100. + + Index1 Tag Name Writable + ------ -------- -------- + 8 SequenceImageNumber int32u + 12 SequenceFileNumber int32u + 16 ReleaseMode2 int8u + 18 DigitalZoom int8u + 22 ShotNumberSincePowerUp int32u + 30 SequenceLength int8u + 36 CameraOrientation int8u + 37 Quality2 int8u + 63 SonyImageHeight int16u~ + 70 ModelReleaseYear int8u~ + +=head3 Sony Tag9400c Tags + +Valid for DSC-HX60V/HX80/HX90V/HX99/HX350/HX400V/QX30/RX0/RX1RM2/RX10/ +RX10M2/RX10M3/RX10M4/RX100M3/RX100M4/RX100M5/RX100M5A/RX100M6/RX100M7/WX220/ +WX350/WX500, ILCE-1/7/7C/7R/7S/7M2/7M3/7RM2/7RM3/7RM4/7SM2/7SM3/9/9M2/5000/ +5100/6000/6100/6300/6400/6500/6600/QX1, ILCA-68/77M2/99M2. + + Index1 Tag Name Writable + ------ -------- -------- + 9 ReleaseMode2 int8u + 10 ShotNumberSincePowerUp int32u + 18 SequenceImageNumber int32u + 22 SequenceLength int8u + 26 SequenceFileNumber int32u + 30 SequenceLength int8u + 41 CameraOrientation int8u + 42 Quality2 int8u + 71 SonyImageHeight int16u~ + 83 ModelReleaseYear int8u~ + 307 ShutterType int8u + 313 ShutterType int8u + 319 ShutterType int8u + +=head3 Sony Tag9401 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 994 ISOInfo Sony ISOInfo + 1102 ISOInfo Sony ISOInfo + 1176 ISOInfo Sony ISOInfo + 1181 ISOInfo Sony ISOInfo + 1185 ISOInfo Sony ISOInfo + 1186 ISOInfo Sony ISOInfo + 1210 ISOInfo Sony ISOInfo + 1437 ISOInfo Sony ISOInfo + 1588 ISOInfo Sony ISOInfo + 1590 ISOInfo Sony ISOInfo + 1612 ISOInfo Sony ISOInfo + 1619 ISOInfo Sony ISOInfo + 1656 ISOInfo Sony ISOInfo + 1720 ISOInfo Sony ISOInfo + 1758 ISOInfo Sony ISOInfo + 1767 ISOInfo Sony ISOInfo + +=head3 Sony ISOInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ISOSetting int8u + 2 ISOAutoMin int8u + 4 ISOAutoMax int8u + +=head3 Sony Tag9402 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 AmbientTemperature int8s + 22 FocusMode int8u & 0x7f + 23 AFAreaMode int8u + 45 FocusPosition2 int8u + +=head3 Sony Tag9403 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 5 CameraTemperature int8s + +=head3 Sony Tag9404a Tags + + Index1 Tag Name Writable + ------ -------- -------- + 11 ExposureProgram int8u + 13 IntelligentAuto int8u + 25 LensZoomPosition int16u + +=head3 Sony Tag9404b Tags + + Index1 Tag Name Writable + ------ -------- -------- + 12 ExposureProgram int8u + 14 IntelligentAuto int8u + 30 LensZoomPosition int16u + 32 FocusPosition2 int8u + +=head3 Sony Tag9404c Tags + + Index1 Tag Name Writable + ------ -------- -------- + 11 ExposureProgram int8u + 13 IntelligentAuto int8u + +=head3 Sony Tag9405a Tags + +Valid for SLT, NEX, ILCE-3000/3500 and several DSC models. + + Index1 Tag Name Writable + ------ -------- -------- + 1536 DistortionCorrParamsPresent int8u + 1537 DistortionCorrection int8u + 1539 LensFormat int8u + 1540 LensMount int8u + 1541 LensType2 int16u + 1544 LensType int16u + 1610 VignettingCorrParams int16s[16] + 1642 ChromaticAberrationCorrParams int16s[32] + 1738 DistortionCorrParams int16s[16] + +=head3 Sony Tag9405b Tags + +Valid for DSC-HX60V/HX80/HX90V/HX99/HX350/HX400V/QX30/RX0/RX10/RX10M2/ +RX10M3/RX10M4/RX100M3/RX100M4/RX100M5/RX100M5A/RX100M6/RX100M7/WX220/WX350, +ILCE-7/7M2/7M3/7R/7RM2/7RM3/7RM4/7S/7SM2/9/9M2/5000/5100/6000/6100/6300/ +6400/6500/6600/QX1, ILCA-68/77M2/99M2. + + Index1 Tag Name Writable + ------ -------- -------- + 4 SonyISO int16u + 6 BaseISO int16u + 10 StopsAboveBaseISO int16u + 14 SonyExposureTime2 int16u + 16 ExposureTime rational32u + 20 SonyFNumber int16u + 22 SonyMaxApertureValue int16u + 36 SequenceImageNumber int32u + 52 ReleaseMode2 int8u + 62 SonyImageWidthMax int16u + 64 SonyImageHeightMax int16u + 66 HighISONoiseReduction int8u + 68 LongExposureNoiseReduction int8u + 70 PictureEffect2 int8u + 72 ExposureProgram int8u + 74 CreativeStyle int8u + 82 Sharpness int8s + 90 DistortionCorrParamsPresent int8u + 91 DistortionCorrection int8u + 93 LensFormat int8u + 94 LensMount int8u + 96 LensType2 int16u + 98 LensType int16u + 100 DistortionCorrParams int16s[16] + 834 LensZoomPosition int16u + 842 VignettingCorrParams int16s[16] + 846 LensZoomPosition int16u + 848 VignettingCorrParams int16s[16] + 858 LensZoomPosition int16u + 860 VignettingCorrParams int16s[16] + 872 VignettingCorrParams int16s[16] + 892 ChromaticAberrationCorrParams int16s[32] + 900 ChromaticAberrationCorrParams int16s[32] + 924 ChromaticAberrationCorrParams int16s[32] + 944 ChromaticAberrationCorrParams int16s[32] + 952 ChromaticAberrationCorrParams int16s[32] + +=head3 Sony Tag9406 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 5 BatteryTemperature int8u + BatteryLevel int8u + 6 BatteryLevelGrip1 int8u + 7 BatteryLevel int8u + 8 BatteryLevelGrip2 int8u + +=head3 Sony Tag940a Tags + +These tags are currently extracted for SLT models only. + + Index1 Tag Name Writable + ------ -------- -------- + 4 AFPointsSelected int32u + +=head3 Sony Tag940c Tags + +E-mount cameras only. + + Index1 Tag Name Writable + ------ -------- -------- + 8 LensMount2 int8u + 9 LensType3 int16u + 11 CameraE-mountVersion int16u + 13 LensE-mountVersion int16u + 20 LensFirmwareVersion int16u~ + +=head3 Sony AFInfo Tags + +These tags are currently extracted for SLT models only. + + Index1 Tag Name Writable + ------ -------- -------- + 2 AFType int8u + 4 AFStatusActiveSensor int16s + 5 FocusMode int8u + 7 AFPoint int8u + 8 AFPointInFocus int8u + 9 AFPointAtShutterRelease int8u + 10 AFAreaMode int8u + 11 FocusMode int8u + 16 AFPointsUsed int8u[10] + 17 AFStatus15 Sony AFStatus15 + AFStatus19 Sony AFStatus19 + 55 AFPoint int8u + 56 AFPointInFocus int8u + 57 AFPointAtShutterRelease int8u + 58 AFAreaMode int8u + 59 AFStatusActiveSensor int16s + 67 ExposureProgram int8u + 80 AFMicroAdj int8s + 125 AFStatus79 Sony AFStatus79 + 366 AFPointsUsed int32u + 381 AFMicroAdj int8s + 382 ExposureProgram int8u + +=head3 Sony AFStatus19 Tags + +AF Status information for models with 19-point AF. + + Index1 Tag Name Writable + ------ -------- -------- + 0 AFStatusUpperFarLeft int16s + 2 AFStatusUpper-leftHorizontal int16s + 4 AFStatusFarLeftHorizontal int16s + 6 AFStatusLeftHorizontal int16s + 8 AFStatusLowerFarLeft int16s + 10 AFStatusLower-leftHorizontal int16s + 12 AFStatusUpper-leftVertical int16s + 14 AFStatusLeftVertical int16s + 16 AFStatusLower-leftVertical int16s + 18 AFStatusFarLeftVertical int16s + 20 AFStatusTopHorizontal int16s + 22 AFStatusNearRight int16s + 24 AFStatusCenterHorizontal int16s + 26 AFStatusNearLeft int16s + 28 AFStatusBottomHorizontal int16s + 30 AFStatusTopVertical int16s + 32 AFStatusUpper-middle int16s + 34 AFStatusCenterVertical int16s + 36 AFStatusLower-middle int16s + 38 AFStatusBottomVertical int16s + 40 AFStatusUpperFarRight int16s + 42 AFStatusUpper-rightHorizontal int16s + 44 AFStatusFarRightHorizontal int16s + 46 AFStatusRightHorizontal int16s + 48 AFStatusLowerFarRight int16s + 50 AFStatusLower-rightHorizontal int16s + 52 AFStatusFarRightVertical int16s + 54 AFStatusUpper-rightVertical int16s + 56 AFStatusRightVertical int16s + 58 AFStatusLower-rightVertical int16s + +=head3 Sony AFStatus79 Tags + +AF Status information for models with 79-point AF. + + Index1 Tag Name Writable + ------ -------- -------- + 0 AFStatus_00_B4 int16s + 2 AFStatus_01_C4 int16s + 4 AFStatus_02_D4 int16s + 6 AFStatus_03_E4 int16s + 8 AFStatus_04_F4 int16s + 10 AFStatus_05_G4 int16s + 12 AFStatus_06_H4 int16s + 14 AFStatus_07_B3 int16s + 16 AFStatus_08_C3 int16s + 18 AFStatus_09_D3 int16s + 20 AFStatus_10_E3 int16s + 22 AFStatus_11_F3 int16s + 24 AFStatus_12_G3 int16s + 26 AFStatus_13_H3 int16s + 28 AFStatus_14_B2 int16s + 30 AFStatus_15_C2 int16s + 32 AFStatus_16_D2 int16s + 34 AFStatus_17_E2 int16s + 36 AFStatus_18_F2 int16s + 38 AFStatus_19_G2 int16s + 40 AFStatus_20_H2 int16s + 42 AFStatus_21_C1 int16s + 44 AFStatus_22_D1 int16s + 46 AFStatus_23_E1 int16s + 48 AFStatus_24_F1 int16s + 50 AFStatus_25_G1 int16s + 52 AFStatus_26_A7_Vertical int16s + 54 AFStatus_27_A6_Vertical int16s + 56 AFStatus_28_A5_Vertical int16s + 58 AFStatus_29_C7_Vertical int16s + 60 AFStatus_30_C6_Vertical int16s + 62 AFStatus_31_C5_Vertical int16s + 64 AFStatus_32_E7_Vertical int16s + 66 AFStatus_33_E6_Center_Vertical int16s + 68 AFStatus_34_E5_Vertical int16s + 70 AFStatus_35_G7_Vertical int16s + 72 AFStatus_36_G6_Vertical int16s + 74 AFStatus_37_G5_Vertical int16s + 76 AFStatus_38_I7_Vertical int16s + 78 AFStatus_39_I6_Vertical int16s + 80 AFStatus_40_I5_Vertical int16s + 82 AFStatus_41_A7 int16s + 84 AFStatus_42_B7 int16s + 86 AFStatus_43_C7 int16s + 88 AFStatus_44_D7 int16s + 90 AFStatus_45_E7 int16s + 92 AFStatus_46_F7 int16s + 94 AFStatus_47_G7 int16s + 96 AFStatus_48_H7 int16s + 98 AFStatus_49_I7 int16s + 100 AFStatus_50_A6 int16s + 102 AFStatus_51_B6 int16s + 104 AFStatus_52_C6 int16s + 106 AFStatus_53_D6 int16s + 108 AFStatus_54_E6_Center int16s + 110 AFStatus_55_F6 int16s + 112 AFStatus_56_G6 int16s + 114 AFStatus_57_H6 int16s + 116 AFStatus_58_I6 int16s + 118 AFStatus_59_A5 int16s + 120 AFStatus_60_B5 int16s + 122 AFStatus_61_C5 int16s + 124 AFStatus_62_D5 int16s + 126 AFStatus_63_E5 int16s + 128 AFStatus_64_F5 int16s + 130 AFStatus_65_G5 int16s + 132 AFStatus_66_H5 int16s + 134 AFStatus_67_I5 int16s + 136 AFStatus_68_C11 int16s + 138 AFStatus_69_D11 int16s + 140 AFStatus_70_E11 int16s + 142 AFStatus_71_F11 int16s + 144 AFStatus_72_G11 int16s + 146 AFStatus_73_B10 int16s + 148 AFStatus_74_C10 int16s + 150 AFStatus_75_D10 int16s + 152 AFStatus_76_E10 int16s + 154 AFStatus_77_F10 int16s + 156 AFStatus_78_G10 int16s + 158 AFStatus_79_H10 int16s + 160 AFStatus_80_B9 int16s + 162 AFStatus_81_C9 int16s + 164 AFStatus_82_D9 int16s + 166 AFStatus_83_E9 int16s + 168 AFStatus_84_F9 int16s + 170 AFStatus_85_G9 int16s + 172 AFStatus_86_H9 int16s + 174 AFStatus_87_B8 int16s + 176 AFStatus_88_C8 int16s + 178 AFStatus_89_D8 int16s + 180 AFStatus_90_E8 int16s + 182 AFStatus_91_F8 int16s + 184 AFStatus_92_G8 int16s + 186 AFStatus_93_H8 int16s + 188 AFStatus_94_E6_Center_F2-8 int16s + +=head3 Sony Tag940e Tags + +E-mount models. + + Index1 Tag Name Writable + ------ -------- -------- + 6662 TiffMeteringImageWidth no + 6663 TiffMeteringImageHeight no + 6664 TiffMeteringImage no + +=head3 Sony Tag9416 Tags + +Valid for the ILCE-1/6700/7M4/7RM5/7SM3, ILME-FX3/FX30, ZV-E1. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Tag9416_0000 no + 4 SonyISO no + 6 StopsAboveBaseISO no + 10 SonyExposureTime2 no + 12 ExposureTime no + 16 SonyFNumber2 no + 18 SonyMaxApertureValue no + 29 SequenceImageNumber no + 53 ExposureProgram no + 72 LensMount no + 73 LensFormat no + 74 LensMount no + 75 LensType2 no + 77 LensType no + 79 DistortionCorrParams no + 112 PictureProfile no + 113 FocalLength no + 115 MinFocalLength no + 117 MaxFocalLength no + 2191 VignettingCorrParams no + 2193 VignettingCorrParams no + 2205 VignettingCorrParams no + 2229 APS-CSizeCapture no + 2231 APS-CSizeCapture no + 2277 APS-CSizeCapture no + 2324 ChromaticAberrationCorrParams no + 2326 ChromaticAberrationCorrParams no + 2373 ChromaticAberrationCorrParams no + +=head3 Sony PIC Tags + +The TextInfo data is extracted as a block to preserve the formatting, and +some of the more interesting information is extracted as separate tags. + + Tag ID Tag Name Writable + ------ -------- -------- + 'BC:' Barcode no + 'BarCode:' Barcode no + 'Capt:' SensorTemperature no + 'FWVer:' FirmwareVersion no + 'IFD' PIC_IFD Sony + 'Temp:' CameraTemperature no + 'Temp:Clbt:' BoardTemperature no + 'TextInfo1' TextInfo1 no + 'TextInfo2' TextInfo2 no + 'VR Enable C:' VibrationReduction no + 'barcode:' Barcode no + +=head3 Sony Ericsson Tags + +Maker notes found in images from some Sony Ericsson phones. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0201 PreviewImageStart int32u* + 0x0202 PreviewImageLength int32u* + 0x2000 MakerNoteVersion undef[4] + +=head3 Sony SRF Tags + +The maker notes in SRF (Sony Raw Format) images contain 7 IFD's with family +1 group names SRF0 through SRF6. SRF0 and SRF1 use the tags in this table, +while SRF2 through SRF5 use the tags in the next table, and SRF6 uses +standard EXIF tags. All information other than SRF0 is encrypted, but +thanks to Dave Coffin the decryption algorithm is known. SRF images are +written by the Sony DSC-F828 and DSC-V3. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 SRF2Key no + 0x0001 DataKey no + +=head3 Sony SRF2 Tags + +These tags are found in the SRF2 through SRF5 IFD's. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0002 SRF6Offset no + 0x0003 SRFDataOffset? no + 0x0004 RawDataOffset no + 0x0005 RawDataLength no + 0x0043 MaxApertureAtMaxFocal no + 0x0044 MaxApertureAtMinFocal no + 0x0045 MinFocalLength no + 0x0046 MaxFocalLength no + 0x00c0 WBRedDaylight no + 0x00c1 WBGreenDaylight no + 0x00c2 WBBlueDaylight no + 0x00c3 WBRedCloudy no + 0x00c4 WBGreenCloudy no + 0x00c5 WBBlueCloudy no + 0x00c6 WBRedFluorescent no + 0x00c7 WBGreenFluorescent no + 0x00c8 WBBlueFluorescent no + 0x00c9 WBRedTungsten no + 0x00ca WBGreenTungsten no + 0x00cb WBBlueTungsten no + 0x00cc WBRedFlash no + 0x00cd WBGreenFlash no + 0x00ce WBBlueFlash no + 0x00d0 WBRedAsShot no + 0x00d1 WBGreenAsShot no + 0x00d2 WBBlueAsShot no + +=head3 Sony SR2Private Tags + +The SR2 format uses the DNGPrivateData tag to reference a private IFD +containing these tags. SR2 images are written by the Sony DSC-R1, but +this information is also written to ARW images by other models. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x7200 SR2SubIFDOffset no + 0x7201 SR2SubIFDLength no + 0x7221 SR2SubIFDKey no + 0x7240 IDC_IFD SonyIDC + 0x7241 IDC2_IFD SonyIDC + 0x7250 MRWInfo MinoltaRaw + +=head3 Sony SR2SubIFD Tags + +Tags in the encrypted SR2SubIFD + + Tag ID Tag Name Writable + ------ -------- -------- + 0x7300 BlackLevel int16u[4]! + 0x7302 WB_GRBGLevelsAuto int16s[4]! + 0x7303 WB_GRBGLevels int16s[4]! + 0x7310 BlackLevel int16u[4]! + 0x7312 WB_RGGBLevelsAuto int16s[4]! + 0x7313 WB_RGGBLevels int16s[4]! + 0x7480 WB_RGBLevelsDaylight int16s[4]! + 0x7481 WB_RGBLevelsCloudy int16s[4]! + 0x7482 WB_RGBLevelsTungsten int16s[4]! + 0x7483 WB_RGBLevelsFlash int16s[4]! + 0x7484 WB_RGBLevels4500K int16s[4]! + 0x7486 WB_RGBLevelsFluorescent int16s[4]! + 0x74a0 MaxApertureAtMaxFocal no + 0x74a1 MaxApertureAtMinFocal no + 0x74a2 MaxFocalLength no + 0x74a3 MinFocalLength no + 0x74c0 SR2DataIFD Sony SR2DataIFD + 0x7800 ColorMatrix no + 0x7820 WB_RGBLevelsDaylight int16s[3]! + 0x7821 WB_RGBLevelsCloudy int16s[3]! + 0x7822 WB_RGBLevelsTungsten int16s[3]! + 0x7823 WB_RGBLevelsFlash int16s[3]! + 0x7824 WB_RGBLevels4500K int16s[3]! + 0x7825 WB_RGBLevelsShade int16s[3]! + 0x7826 WB_RGBLevelsFluorescent int16s[3]! + 0x7827 WB_RGBLevelsFluorescentP1 int16s[3]! + 0x7828 WB_RGBLevelsFluorescentP2 int16s[3]! + 0x7829 WB_RGBLevelsFluorescentM1 int16s[3]! + 0x782a WB_RGBLevels8500K int16s[3]! + 0x782b WB_RGBLevels6000K int16s[3]! + 0x782c WB_RGBLevels3200K int16s[3]! + 0x782d WB_RGBLevels2500K int16s[3]! + 0x787f WhiteLevel int16u[3]! + 0x797d VignettingCorrParams no + 0x7980 ChromaticAberrationCorrParams no + 0x7982 DistortionCorrParams no + +=head3 Sony SR2DataIFD Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x7770 ColorMode no + +=head3 Sony PMP Tags + +These tags are written in the proprietary-format header of PMP images from +the DSC-F1. + + Index1 Tag Name Writable + ------ -------- -------- + 8 JpgFromRawStart no + 12 JpgFromRawLength no + 22 SonyImageWidth no + 24 SonyImageHeight no + 27 Orientation no + 29 ImageQuality no + 52 Comment no + 76 DateTimeOriginal no + 84 ModifyDate no + 102 ExposureTime no + 106 FNumber no + 108 ExposureCompensation no + 112 FocalLength no + 118 Flash no + +=head3 Sony rtmd Tags + +These tags are extracted from the 'rtmd' timed metadata of MP4 videos from +some models when the ExtractEmbedded option is used. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x8000 FNumber no + 0x8109 ExposureTime no + 0x810a MasterGainAdjustment no + 0x810b ISO no + 0x810c ElectricalExtenderMagnification no + 0x8500 GPSVersionID no + 0x8501 GPSLatitudeRef no + 0x8502 GPSLatitude no + 0x8503 GPSLongitudeRef no + 0x8504 GPSLongitude no + 0x8507 GPSTimeStamp no + 0x8509 GPSStatus no + 0x850a GPSMeasureMode no + 0x8512 GPSMapDatum no + 0x851d GPSDateStamp no + 0xe303 WhiteBalance no + 0xe304 DateTime no + 0xe43b PitchRollYaw no + 0xe44b Accelerometer no + +=head2 SonyIDC Tags + +Tags written by the Sony Image Data Converter utility in ARW images. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0201 IDCPreviewStart int32u* + 0x0202 IDCPreviewLength int32u* + 0x8000 IDCCreativeStyle int32u + 0x8001 CreativeStyleWasChanged int32u + 0x8002 PresetWhiteBalance int32u + 0x8013 ColorTemperatureAdj int16u + 0x8014 PresetWhiteBalanceAdj int32s + 0x8015 ColorCorrection int32s + 0x8016 SaturationAdj int32s + 0x8017 ContrastAdj int32s + 0x8018 BrightnessAdj int32s + 0x8019 HueAdj int32s + 0x801a SharpnessAdj int32s + 0x801b SharpnessOvershoot int32s + 0x801c SharpnessUndershoot int32s + 0x801d SharpnessThreshold int32s + 0x801e NoiseReductionMode int16u + 0x8021 GrayPoint int16u[4] + 0x8022 D-RangeOptimizerMode int16u + 0x8023 D-RangeOptimizerValue int32s + 0x8024 D-RangeOptimizerHighlight int32s + 0x8026 HighlightColorDistortReduct int16u + 0x8027 NoiseReductionValue int32s + 0x8028 EdgeNoiseReduction int32s + 0x8029 ColorNoiseReduction int32s + 0x802d D-RangeOptimizerShadow int32s + 0x8030 PeripheralIllumCentralRadius int32s + 0x8031 PeripheralIllumCentralValue int32s + 0x8032 PeripheralIllumPeriphValue int32s + 0x8040 DistortionCompensation int32s + 0x9000 ToneCurveBrightnessX int16u[n] + 0x9001 ToneCurveRedX int16u[n] + 0x9002 ToneCurveGreenX int16u[n] + 0x9003 ToneCurveBlueX int16u[n] + 0x9004 ToneCurveBrightnessY int16u[n] + 0x9005 ToneCurveRedY int16u[n] + 0x9006 ToneCurveGreenY int16u[n] + 0x9007 ToneCurveBlueY int16u[n] + 0x900d ChromaticAberrationCorrection int32s + 0x900e InclinationCorrection int32u + 0x900f InclinationAngle int32s + 0x9010 Cropping int32u + 0x9011 CropArea int32u[4] + 0x9012 PreviewImageSize int32u[2] + 0x9013 PxShiftPeriphEdgeNR int32s + 0x9014 PxShiftPeriphEdgeNRValue int32s + 0x9017 WhitesAdj int32s + 0x9018 BlacksAdj int32s + 0x9019 HighlightsAdj int32s + 0x901a ShadowsAdj int32s + 0xd000 CurrentVersion int32u + 0xd001 VersionIFD SonyIDC + 0xd100 VersionCreateDate string + 0xd101 VersionModifyDate string + +=head2 Unknown Tags + +The following tags are decoded in unsupported maker notes. Use the Unknown +(-u) option to display other unknown tags. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0e00 PrintIM PrintIM + +=head2 DNG Tags + +The main DNG tags are found in the EXIF table. The tables below define only +information found within structures of these main DNG tag values. See +L<http://www.adobe.com/products/dng/> for the official DNG specification. + +=head3 DNG AdobeData Tags + +This information is found in the "Adobe" DNGPrivateData. + +The maker notes ('MakN') are processed by ExifTool, but some information may +have been lost by the Adobe DNG Converter. This is because the Adobe DNG +Converter (as of version 6.3) doesn't properly handle information referenced +from inside the maker notes that lies outside the original maker notes +block. This information is lost when only the maker note block is copied to +the DNG image. While this doesn't effect all makes of cameras, it is a +problem for some major brands such as Olympus and Sony. + +Other entries in this table represent proprietary information that is +extracted from the original RAW image and restructured to a different (but +still proprietary) Adobe format. + + Tag ID Tag Name Writable + ------ -------- -------- + 'CRW ' AdobeCRW CanonRaw + 'Koda' AdobeKoda Kodak IFD + 'Leaf' AdobeLeaf Leaf SubIFD + 'MRW ' AdobeMRW MinoltaRaw + 'MakN' MakerNoteApple Apple + MakerNoteNikon Nikon + MakerNoteCanon Canon + MakerNoteCasio Casio + MakerNoteCasio2 Casio Type2 + MakerNoteDJIInfo DJI Info + MakerNoteDJI DJI + MakerNoteFLIR FLIR + MakerNoteFujiFilm FujiFilm + MakerNoteGE GE + MakerNoteGE2 FujiFilm + MakerNoteHasselblad Unknown + MakerNoteHP HP + MakerNoteHP2 HP Type2 + MakerNoteHP4 HP Type4 + MakerNoteHP6 HP Type6 + MakerNoteISL Unknown + MakerNoteJVC JVC + MakerNoteJVCText JVC Text + MakerNoteKodak1a Kodak + MakerNoteKodak1b Kodak + MakerNoteKodak2 Kodak Type2 + MakerNoteKodak3 Kodak Type3 + MakerNoteKodak4 Kodak Type4 + MakerNoteKodak5 Kodak Type5 + MakerNoteKodak6a Kodak Type6 + MakerNoteKodak6b Kodak Type6 + MakerNoteKodak7 Kodak Type7 + MakerNoteKodak8a Kodak Type8 + MakerNoteKodak8b Kodak Type8 + MakerNoteKodak8c Kodak Type8 + MakerNoteKodak9 Kodak Type9 + MakerNoteKodak10 Kodak Type10 + MakerNoteKodak11 Kodak Type11 + MakerNoteKodak12 Kodak Type11 + MakerNoteKodakUnknown Kodak Unknown + MakerNoteKyocera Unknown + MakerNoteMinolta Minolta + MakerNoteMinolta2 Olympus + MakerNoteMinolta3 undef + MakerNoteMotorola Motorola + MakerNoteNikon2 Nikon Type2 + MakerNoteNikon3 Nikon + MakerNoteNintendo Nintendo + MakerNoteOlympus Olympus + MakerNoteOlympus2 Olympus + MakerNoteOlympus3 Olympus + MakerNoteLeica Panasonic + MakerNoteLeica2 Panasonic Leica2 + MakerNoteLeica3 Panasonic Leica3 + MakerNoteLeica4 Panasonic Leica4 + MakerNoteLeica5 Panasonic Leica5 + MakerNoteLeica6 Panasonic Leica6 + MakerNoteLeica7 Panasonic Leica6 + MakerNoteLeica8 Panasonic Leica5 + MakerNoteLeica9 Panasonic Leica9 + MakerNoteLeica10 Panasonic + MakerNotePanasonic Panasonic + MakerNotePanasonic2 Panasonic Type2 + MakerNotePanasonic3 Panasonic + MakerNotePentax Pentax + MakerNotePentax2 Pentax Type2 + MakerNotePentax3 Casio Type2 + MakerNotePentax4 Pentax Type4 + MakerNotePentax5 Pentax + MakerNotePentax6 Pentax S1 + MakerNotePhaseOne PhaseOne + MakerNoteReconyx Reconyx + MakerNoteReconyx2 Reconyx Type2 + MakerNoteReconyx3 Reconyx Type3 + MakerNoteRicohPentax Pentax + MakerNoteRicoh Ricoh + MakerNoteRicoh2 Ricoh Type2 + MakerNoteRicohText Ricoh Text + MakerNoteSamsung1a undef + MakerNoteSamsung1b Samsung + MakerNoteSamsung2 Samsung Type2 + MakerNoteSanyo Sanyo + MakerNoteSanyoC4 Sanyo + MakerNoteSanyoPatch Sanyo + MakerNoteSigma Sigma + MakerNoteSony Sony + MakerNoteSony2 Olympus + MakerNoteSony3 Olympus + MakerNoteSony4 Sony PIC + MakerNoteSony5 Sony + MakerNoteSonyEricsson Sony Ericsson + MakerNoteSonySRF Sony SRF + MakerNoteUnknownText undef + MakerNoteUnknownBinary undef + MakerNoteUnknown Unknown + 'Pano' AdobePano PanasonicRaw + 'RAF ' AdobeRAF FujiFilm RAF + 'SR2 ' AdobeSR2 Sony SR2Private + +=head3 DNG OriginalRaw Tags + +This table defines tags extracted from the DNG OriginalRawFileData +information. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 OriginalRawImage no + 0x0001 OriginalRawResource no + 0x0002 OriginalRawFileType no + 0x0003 OriginalRawCreator no + 0x0004 OriginalTHMImage no + 0x0005 OriginalTHMResource no + 0x0006 OriginalTHMFileType no + 0x0007 OriginalTHMCreator no + +=head2 CanonRaw Tags + +These tags apply to CRW-format Canon RAW files and information in the APP0 +"CIFF" segment of JPEG images. When writing CanonRaw/CIFF information, the +length of the information is preserved (and the new information is truncated +or padded as required) unless B<Writable> is C<resize>. Currently, only +JpgFromRaw and ThumbnailImage are allowed to change size. See +L<https://exiftool.org/canon_raw.html> for a description of the Canon CRW +format. + +CRW images also support the addition of a CanonVRD trailer, which in turn +supports XMP. This trailer is created automatically if necessary when +ExifTool is used to write XMP to a CRW image. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 NullRecord undef + 0x0001 FreeBytes undef + 0x0032 CanonColorInfo1 no + 0x0805 CanonFileDescription string[32] + UserComment string[256] + 0x080a CanonRawMakeModel CanonRaw MakeModel + 0x080b CanonFirmwareVersion string[32] + 0x080c ComponentVersion string + 0x080d ROMOperationMode string[8] + 0x0810 OwnerName string[32] + 0x0815 CanonImageType string[32] + 0x0816 OriginalFileName string[32] + 0x0817 ThumbnailFileName string[32] + 0x100a TargetImageType int16u + 0x1010 ShutterReleaseMethod int16u + 0x1011 ShutterReleaseTiming int16u + 0x1016 ReleaseSetting int16u + 0x101c BaseISO int16u + 0x1028 CanonFlashInfo? int16u[4] + 0x1029 CanonFocalLength Canon FocalLength + 0x102a CanonShotInfo Canon ShotInfo + 0x102c CanonColorInfo2 no + 0x102d CanonCameraSettings Canon CameraSettings + 0x1030 WhiteSample CanonRaw WhiteSample + 0x1031 SensorInfo Canon SensorInfo + 0x1033 CustomFunctions10D CanonCustom Functions10D + CustomFunctionsD30 CanonCustom FunctionsD30 + CustomFunctionsD60 CanonCustom FunctionsD30 + CustomFunctionsUnknown CanonCustom FuncsUnknown + 0x1038 CanonAFInfo Canon AFInfo + 0x1093 CanonFileInfo Canon FileInfo + 0x10a9 ColorBalance Canon ColorBalance + 0x10ae ColorTemperature int16u + 0x10b4 ColorSpace int16u + 0x10b5 RawJpgInfo CanonRaw RawJpgInfo + 0x1803 ImageFormat CanonRaw ImageFormat + 0x1804 RecordID int32u + 0x1806 SelfTimerTime int32u + 0x1807 TargetDistanceSetting float + 0x180b SerialNumber int32u + UnknownNumber? yes + 0x180e TimeStamp CanonRaw TimeStamp + 0x1810 ImageInfo CanonRaw ImageInfo + 0x1813 FlashInfo CanonRaw FlashInfo + 0x1814 MeasuredEV float + 0x1817 FileNumber int32u + 0x1818 ExposureInfo CanonRaw ExposureInfo + 0x1834 CanonModelID int32u + 0x1835 DecoderTable CanonRaw DecoderTable + 0x183b SerialNumberFormat int32u + 0x2005 RawData no + 0x2007 JpgFromRaw resize^ + 0x2008 ThumbnailImage resize^ + 0x2804 ImageDescription CanonRaw + 0x2807 CameraObject CanonRaw + 0x3002 ShootingRecord CanonRaw + 0x3003 MeasuredInfo CanonRaw + 0x3004 CameraSpecification CanonRaw + 0x300a ImageProps CanonRaw + 0x300b ExifInformation CanonRaw + +=head3 CanonRaw MakeModel Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 Make string[6] + 6 Model string + +=head3 CanonRaw WhiteSample Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 WhiteSampleWidth no + 2 WhiteSampleHeight no + 3 WhiteSampleLeftBorder no + 4 WhiteSampleTopBorder no + 5 WhiteSampleBits no + +=head3 CanonRaw RawJpgInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1 RawJpgQuality int16u + 2 RawJpgSize int16u + 3 RawJpgWidth int16u + 4 RawJpgHeight int16u + +=head3 CanonRaw ImageFormat Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 FileFormat int32u + 1 TargetCompressionRatio float + +=head3 CanonRaw TimeStamp Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 DateTimeOriginal int32u + 1 TimeZoneCode int32s + 2 TimeZoneInfo int32u + +=head3 CanonRaw ImageInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 ImageWidth no + 1 ImageHeight no + 2 PixelAspectRatio no + 3 Rotation int32s + 4 ComponentBitDepth no + 5 ColorBitDepth no + 6 ColorBW no + +=head3 CanonRaw FlashInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 FlashGuideNumber float + 1 FlashThreshold float + +=head3 CanonRaw ExposureInfo Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 ExposureCompensation float + 1 ShutterSpeedValue float + 2 ApertureValue float + +=head3 CanonRaw DecoderTable Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 DecoderTableNumber no + 2 CompressedDataOffset no + 3 CompressedDataLength no + +=head2 KyoceraRaw Tags + +Tags for Kyocera Contax N Digital RAW images. + + Index1 Tag Name Writable + ------ -------- -------- + 1 FirmwareVersion no + 12 Model no + 25 Make no + 33 DateTimeOriginal no + 52 ISO no + 56 ExposureTime no + 60 WB_RGGBLevels no + 88 FNumber no + 104 MaxAperture no + 112 FocalLength no + 124 Lens no + +=head2 MinoltaRaw Tags + +These tags are used in Minolta RAW format (MRW) images. + + Tag ID Tag Name Writable + ------ -------- -------- + "\0PRD" MinoltaPRD MinoltaRaw PRD + "\0RIF" MinoltaRIF MinoltaRaw RIF + "\0TTW" MinoltaTTW EXIF + "\0WBG" MinoltaWBG MinoltaRaw WBG + +=head3 MinoltaRaw PRD Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FirmwareID string[8] + 8 SensorHeight int16u + 10 SensorWidth int16u + 12 ImageHeight int16u + 14 ImageWidth int16u + 16 RawDepth int8u + 17 BitDepth int8u + 18 StorageMethod int8u + 23 BayerPattern int8u + +=head3 MinoltaRaw RIF Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 Saturation int8s + 2 Contrast int8s + 3 Sharpness int8s + 4 WBMode int8u~ + 5 ProgramMode int8u + 6 ISOSetting int8u + 7 ColorMode int32u[0.25] + 8 WB_RBLevelsTungsten int16u[2] + 12 WB_RBLevelsDaylight int16u[2] + 16 WB_RBLevelsCloudy int16u[2] + 20 WB_RBLevelsCoolWhiteF int16u[2] + 24 WB_RBLevelsFlash int16u[2] + 28 WB_RBLevelsCustom int16u[2] + 32 WB_RBLevelsShade int16u[2] + 36 WB_RBLevelsDaylightF int16u[2] + 40 WB_RBLevelsDayWhiteF int16u[2] + 44 WB_RBLevelsWhiteF int16u[2] + 56 ColorFilter int8s + 57 BWFilter int8u + 58 ZoneMatching int8u + 59 Hue int8s + 60 ColorTemperature int8u + 74 ZoneMatching int8u + 76 ColorTemperature int8u + 77 ColorFilter int8u + 78 ColorTemperature int8u + 79 ColorFilter int8u + 80 RawDataLength no + +=head3 MinoltaRaw WBG Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 WBScale int8u[4] + 4 WB_GBRGLevels int16u[4] + WB_RGGBLevels int16u[4] + +=head2 PanasonicRaw Tags + +These tags are found in IFD0 of Panasonic/Leica RAW, RW2 and RWL images. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 PanasonicRawVersion undef + 0x0002 SensorWidth no + 0x0003 SensorHeight no + 0x0004 SensorTopBorder no + 0x0005 SensorLeftBorder no + 0x0006 SensorBottomBorder no + 0x0007 SensorRightBorder no + 0x0008 SamplesPerPixel int16u! + 0x0009 CFAPattern int16u! + 0x000a BitsPerSample int16u! + 0x000b Compression int16u! + 0x000e LinearityLimitRed int16u + 0x000f LinearityLimitGreen int16u + 0x0010 LinearityLimitBlue int16u + 0x0011 RedBalance int16u + 0x0012 BlueBalance int16u + 0x0013 WBInfo PanasonicRaw WBInfo + 0x0017 ISO int16u + 0x0018 HighISOMultiplierRed int16u + 0x0019 HighISOMultiplierGreen int16u + 0x001a HighISOMultiplierBlue int16u + 0x001b NoiseReductionParams undef[n]! + 0x001c BlackLevelRed int16u + 0x001d BlackLevelGreen int16u + 0x001e BlackLevelBlue int16u + 0x0024 WBRedLevel int16u + 0x0025 WBGreenLevel int16u + 0x0026 WBBlueLevel int16u + 0x0027 WBInfo2 PanasonicRaw WBInfo2 + 0x002d RawFormat int16u! + 0x002e JpgFromRaw JPEG + 0x002f CropTop int16u + 0x0030 CropLeft int16u + 0x0031 CropBottom int16u + 0x0032 CropRight int16u + 0x010f Make string + 0x0110 Model string + 0x0111 StripOffsets no + 0x0112 Orientation int16u + 0x0116 RowsPerStrip no + 0x0117 StripByteCounts no + 0x0118 RawDataOffset no + 0x0119 DistortionInfo PanasonicRaw DistortionInfo + 0x011c Gamma int16u + 0x0120 CameraIFD PanasonicRaw CameraIFD + 0x0121 Multishot int32u + 0x0127 JpgFromRaw2 no + 0x013b Artist string + 0x02bc ApplicationNotes XMP + 0x8298 Copyright string + 0x83bb IPTC-NAA IPTC + 0x8769 ExifOffset EXIF + 0x8825 GPSInfo GPS + +=head3 PanasonicRaw WBInfo Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 NumWBEntries int16u + 1 WBType1 int16u + 2 WB_RBLevels1 int16u[2] + 4 WBType2 int16u + 5 WB_RBLevels2 int16u[2] + 7 WBType3 int16u + 8 WB_RBLevels3 int16u[2] + 10 WBType4 int16u + 11 WB_RBLevels4 int16u[2] + 13 WBType5 int16u + 14 WB_RBLevels5 int16u[2] + 16 WBType6 int16u + 17 WB_RBLevels6 int16u[2] + 19 WBType7 int16u + 20 WB_RBLevels7 int16u[2] + +=head3 PanasonicRaw WBInfo2 Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 NumWBEntries int16u + 1 WBType1 int16u + 2 WB_RGBLevels1 int16u[3] + 5 WBType2 int16u + 6 WB_RGBLevels2 int16u[3] + 9 WBType3 int16u + 10 WB_RGBLevels3 int16u[3] + 13 WBType4 int16u + 14 WB_RGBLevels4 int16u[3] + 17 WBType5 int16u + 18 WB_RGBLevels5 int16u[3] + 21 WBType6 int16u + 22 WB_RGBLevels6 int16u[3] + 25 WBType7 int16u + 26 WB_RGBLevels7 int16u[3] + +=head3 PanasonicRaw DistortionInfo Tags + +Lens distortion correction information. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0002 DistortionParam02 int16s + 0x0004 DistortionParam04 int16s + 0x0005 DistortionScale int16s + 7.1 DistortionCorrection int16s & 0x0f + 0x0008 DistortionParam08 int16s + 0x0009 DistortionParam09 int16s + 0x000b DistortionParam11 int16s + 0x000c DistortionN? int16s + +=head3 PanasonicRaw CameraIFD Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x1001 MultishotOn no + 0x1100 FocusStepNear no + 0x1101 FocusStepCount no + 0x1102 FlashFired no + 0x1105 ZoomPosition no + 0x1200 LensAttached no + 0x1201 LensTypeMake no + 0x1202 LensTypeModel no + 0x1203 FocalLengthIn35mmFormat no + 0x1301 ApertureValue no + 0x1302 ShutterSpeedValue no + 0x1303 SensitivityValue no + 0x1305 HighISOMode no + 0x1412 FacesDetected no + 0x3200 WB_CFA0_LevelDaylight no + 0x3201 WB_CFA1_LevelDaylight no + 0x3202 WB_CFA2_LevelDaylight no + 0x3203 WB_CFA3_LevelDaylight no + 0x3300 WhiteBalanceSet no + 0x3420 WB_RedLevelAuto no + 0x3421 WB_BlueLevelAuto no + 0x3501 Orientation no + 0x3600 WhiteBalanceDetected no + +=head2 SigmaRaw Tags + +These tags are used in Sigma and Foveon RAW (.X3F) images. Metadata is also +extracted from the JpgFromRaw image if it exists (all models but the SD9 and +SD10). Currently, metadata may only be written to the embedded JpgFromRaw. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Header' Header SigmaRaw Header + 'Header4' Header4 SigmaRaw Header4 + 'HeaderExt' HeaderExt SigmaRaw HeaderExt + 'IMA2' PreviewImage no + JpgFromRaw no + 'IMAG' PreviewImage no + 'PROP' Properties SigmaRaw Properties + +=head3 SigmaRaw Header Tags + +Information extracted from the header of an X3F file. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 FileVersion no + 0x0002 ImageUniqueID no + 0x0006 MarkBits no + 0x0007 ImageWidth no + 0x0008 ImageHeight no + 0x0009 Rotation no + 0x000a WhiteBalance no + 0x0012 SceneCaptureType no + +=head3 SigmaRaw Header4 Tags + +Header information for version 4.0 or greater X3F. + + Index4 Tag Name Writable + ------ -------- -------- + 1 FileVersion no + 10 ImageWidth no + 11 ImageHeight no + 12 Rotation no + +=head3 SigmaRaw HeaderExt Tags + +Extended header data found in version 2.1 and 2.2 files + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 Unused no + 0x0001 ExposureAdjust no + 0x0002 Contrast no + 0x0003 Shadow no + 0x0004 Highlight no + 0x0005 Saturation no + 0x0006 Sharpness no + 0x0007 RedAdjust no + 0x0008 GreenAdjust no + 0x0009 BlueAdjust no + 0x000a X3FillLight no + +=head3 SigmaRaw Properties Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'AEMODE' MeteringMode no + 'AFAREA' AFArea no + 'AFINFOCUS' AFInFocus no + 'AFMODE' FocusMode no + 'APERTURE' FNumber no + 'AP_DESC' ApertureDisplayed no + 'BRACKET' BracketShot no + 'BURST' BurstShot no + 'CAMMANUF' Make no + 'CAMMODEL' Model no + 'CAMNAME' CameraName no + 'CAMSERIAL' SerialNumber no + 'CM_DESC' SceneCaptureType no + 'COLORSPACE' ColorSpace no + 'DRIVE' DriveMode no + 'EVAL_STATE' EvalState no + 'EXPCOMP' ExposureCompensation no + 'EXPNET' NetExposureCompensation no + 'EXPTIME' IntegrationTime no + 'FIRMVERS' FirmwareVersion no + 'FLASH' FlashMode no + 'FLASHEXPCOMP' FlashExpComp no + 'FLASHPOWER' FlashPower no + 'FLASHTTLMODE' FlashTTLMode no + 'FLASHTYPE' FlashType no + 'FLENGTH' FocalLength no + 'FLEQ35MM' FocalLengthIn35mmFormat no + 'FOCUS' Focus no + 'IMAGEBOARDID' ImageBoardID no + 'IMAGERBOARDID' ImagerBoardID no + 'IMAGERTEMP' SensorTemperature no + 'ISO' ISO no + 'LENSARANGE' LensApertureRange no + 'LENSFRANGE' LensFocalRange no + 'LENSMODEL' LensType no + 'PMODE' ExposureProgram no + 'RESOLUTION' Quality no + 'SENSORID' SensorID no + 'SHUTTER' ExposureTime no + 'SH_DESC' ShutterSpeedDisplayed no + 'TIME' DateTimeOriginal no + 'VERSION_BF' VersionBF no + 'WB_DESC' WhiteBalance no + +=head2 Lytro Tags + +Tag definitions for Lytro Light Field Picture (LFP) files. ExifTool +extracts the full JSON metadata blocks, as well as breaking them down into +individual tags. All available tags are extracted from the JSON metadata, +even if they don't appear in the table below. + + Tag Name Writable + -------- -------- + AccelerometerTime no + AccelerometerX no + AccelerometerY no + AccelerometerZ no + CameraType no + DateTimeOriginal no + EmbeddedImage no + ExposureTime no + FNumber no + FirmwareVersion no + FocalLength no + FocalPlaneXResolution no + FrameExposureTime no + ISO no + ImageLimitExposureBias no + ImageModulationExposureBias no + JSONMetadata no+ + LensTemperature no + Make no + Model no + Orientation no + SensorSerialNumber no + SerialNumber no + SocTemperature no + +=head2 JFIF Tags + +The following information is extracted from the JPEG JFIF header. See +L<https://www.w3.org/Graphics/JPEG/jfif3.pdf> for the JFIF 1.02 +specification. + + Index1 Tag Name Writable + ------ -------- -------- + 0 JFIFVersion no: + 2 ResolutionUnit int8u: + 3 XResolution int16u: + 5 YResolution int16u: + 7 ThumbnailWidth no + 8 ThumbnailHeight no + 9 ThumbnailTIFF no + +=head3 JFIF Extension Tags + +Thumbnail images extracted from the JFXX segment. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0010 ThumbnailImage no + 0x0011 ThumbnailTIFF no + 0x0013 ThumbnailTIFF no + +=head2 FlashPix Tags + +The FlashPix file format, introduced in 1996, was developed by Kodak, +Hewlett-Packard and Microsoft. Internally the FPX file structure mimics +that of an old DOS disk with fixed-sized "sectors" (usually 512 bytes) and a +"file allocation table" (FAT). No wonder this image format never became +popular. However, some of the structures used in FlashPix streams are part +of the EXIF specification, and are still being used in the APP2 FPXR segment +of JPEG images by some digital cameras from manufacturers such as FujiFilm, +Hewlett-Packard, Kodak and Sanyo. + +ExifTool extracts FlashPix information from both FPX images and the APP2 +FPXR segment of JPEG images. As well, FlashPix information is extracted +from DOC, PPT, XLS (Microsoft Word, PowerPoint and Excel) documents, VSD +(Microsoft Visio) drawings, and FLA (Macromedia/Adobe Flash project) files +since these are based on the same file format as FlashPix (the Windows +Compound Binary File format). Note that ExifTool identifies any +unrecognized Windows Compound Binary file as a FlashPix (FPX) file. See +L<http://graphcomp.com/info/specs/livepicture/fpx.pdf> for the FlashPix +specification. + +Note that Microsoft is not consistent with the time zone used for some +date/time tags, and it may be either UTC or local time depending on the +software used to create the file. + + Tag ID Tag Name Writable + ------ -------- -------- + "\x01CompObj" CompObj FlashPix CompObj + "\x05Audio Info" AudioInfo FlashPix AudioInfo + "\x05Data Object" DataObject FlashPix DataObject + "\x05DocumentSummaryInformation" DocumentInfo FlashPix DocumentInfo + "\x05Extension List" Extensions FlashPix Extensions + "\x05Global Info" GlobalInfo FlashPix GlobalInfo + "\x05Image Contents" Image FlashPix Image + "\x05Image Info" ImageInfo FlashPix ImageInfo + "\x05Operation" Operation FlashPix Operation + "\x05Screen Nail" ScreenNail no + "\x05SummaryInformation" SummaryInfo FlashPix SummaryInfo + "\x05Transform" Transform FlashPix Transform + 'Audio Stream' AudioStream no + 'BasicFileInfo' BasicFileInfo no + 'Contents' Contents XMP + 'Current User' CurrentUser no + 'ICC Profile 0001' ICC_Profile ICC_Profile + 'IeImg' EmbeddedImage no + 'IeImg_class' EmbeddedImageClass no + 'IeImg_rect' EmbeddedImageRectangle no + 'Preview' PreviewImage no + 'Property' PreviewInfo FlashPix PreviewInfo + 'Subimage 0000 Header' SubimageHdr FlashPix SubimageHdr + 'WordDocument' WordDocument FlashPix WordDocument + +=head3 FlashPix CompObj Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 CompObjUserTypeLen no + 1 CompObjUserType no + +=head3 FlashPix AudioInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 FlashPix DataObject Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x10000 DataObjectID no + 0x10002 LockedPropertyList no + 0x10003 DataObjectTitle no + 0x10004 LastModifier no + 0x10005 RevisionNumber no + 0x10006 DataCreateDate no + 0x10007 DataModifyDate no + 0x10008 CreatingApplication no + 0x10100 DataObjectStatus no + 0x10101 CreatingTransform no + 0x10102 UsingTransforms no + 0x10000000 CachedImageHeight no + 0x10000001 CachedImageWidth no + +=head3 FlashPix DocumentInfo Tags + +The DocumentSummaryInformation property set includes a UserDefined property +set for which only the Hyperlinks and HyperlinkBase tags are pre-defined. +However, ExifTool will also extract any other information found in the +UserDefined properties. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0002 Category no + 0x0003 PresentationTarget no + 0x0004 Bytes no + 0x0005 Lines no + 0x0006 Paragraphs no + 0x0007 Slides no + 0x0008 Notes no + 0x0009 HiddenSlides no + 0x000a MMClips no + 0x000b ScaleCrop no + 0x000c HeadingPairs no + 0x000d TitleOfParts no + 0x000e Manager no + 0x000f Company no + 0x0010 LinksUpToDate no + 0x0011 CharCountWithSpaces no + 0x0013 SharedDoc no + 0x0016 HyperlinksChanged no + 0x0017 AppVersion no + 0x001a ContentType no + 0x001b ContentStatus no + 0x001c Language no + 0x001d DocVersion no + '_PID_HLINKS' Hyperlinks no + '_PID_LINKBASE' HyperlinkBase no + +=head3 FlashPix Extensions Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 ExtensionName no + 0x0002 ExtensionClassID no + 0x0003 ExtensionPersistence no + 0x0004 ExtensionCreateDate no + 0x0005 ExtensionModifyDate no + 0x0006 CreatingApplication no + 0x0007 ExtensionDescription no + 0x1000 Storage-StreamPathname no + 0x2000 FlashPixStreamPathname no + 0x2001 FlashPixStreamFieldOffset no + 0x3000 PropertySetPathname no + 0x3001 PropertySetIDCodes no + 0x3002 PropertyVectorElements no + 0x4000 SubimageResolutions no + 0x10000000 UsedExtensionNumbers no + +=head3 FlashPix GlobalInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x10002 LockedPropertyList no + 0x10003 TransformedImageTitle no + 0x10004 LastModifier no + 0x10100 VisibleOutputs no + 0x10101 MaximumImageIndex no + 0x10102 MaximumTransformIndex no + 0x10103 MaximumOperationIndex no + +=head3 FlashPix Image Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x1000000 NumberOfResolutions no + 0x1000002 ImageWidth no + 0x1000003 ImageHeight no + 0x1000004 DefaultDisplayHeight no + 0x1000005 DefaultDisplayWidth no + 0x1000006 DisplayUnits no + 0x2000000 SubimageWidth no + 0x2000001 SubimageHeight no + 0x2000002 SubimageColor no + 0x2000003 SubimageNumericalFormat no + 0x2000004 DecimationMethod no + 0x2000005 DecimationPrefilterWidth no + 0x2000007 SubimageICC_Profile no + 0x3000001 JPEGTables no + 0x3000002 MaxJPEGTableIndex no + +=head3 FlashPix ImageInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x21000000 FileSource no + 0x21000001 SceneType no + 0x21000002 CreationPathVector no + 0x21000003 SoftwareRelease no + 0x21000004 UserDefinedID no + 0x21000005 SharpnessApproximation no + 0x22000000 Copyright no + 0x22000001 OriginalImageBroker no + 0x22000002 DigitalImageBroker no + 0x22000003 Authorship no + 0x22000004 IntellectualPropertyNotes no + 0x23000000 TestTarget no + 0x23000002 GroupCaption no + 0x23000003 CaptionText no + 0x23000004 People no + 0x23000007 Things no + 0x2300000a DateTimeOriginal no + 0x2300000b Events no + 0x2300000c Places no + 0x2300000f ContentDescriptionNotes no + 0x24000000 Make no + 0x24000001 Model no + 0x24000002 SerialNumber no + 0x25000000 CreateDate no + 0x25000001 ExposureTime no + 0x25000002 FNumber no + 0x25000003 ExposureProgram no + 0x25000004 BrightnessValue no + 0x25000005 ExposureCompensation no + 0x25000006 SubjectDistance no + 0x25000007 MeteringMode no + 0x25000008 LightSource no + 0x25000009 FocalLength no + 0x2500000a MaxApertureValue no + 0x2500000b Flash no + 0x2500000c FlashEnergy no + 0x2500000d FlashReturn no + 0x2500000e BackLight no + 0x2500000f SubjectLocation no + 0x25000010 ExposureIndex no + 0x25000011 SpecialEffectsOpticalFilter no + 0x25000012 PerPictureNotes no + 0x26000000 SensingMethod no + 0x26000001 FocalPlaneXResolution no + 0x26000002 FocalPlaneYResolution no + 0x26000003 FocalPlaneResolutionUnit no + 0x26000004 SpatialFrequencyResponse no + 0x26000005 CFAPattern no + 0x26000007 ISO no + 0x26000008 Opto-ElectricConvFactor no + 0x27000000 FilmBrand no + 0x27000001 FilmCategory no + 0x27000002 FilmSize no + 0x27000003 FilmRollNumber no + 0x27000004 FilmFrameNumber no + 0x28000000 ScannerMake no + 0x28000001 ScannerModel no + 0x28000002 ScannerSerialNumber no + 0x28000003 ScanSoftware no + 0x28000004 ScanSoftwareRevisionDate no + 0x28000005 ServiceOrganizationName no + 0x28000006 ScanOperatorID no + 0x28000008 ScanDate no + 0x28000009 ModifyDate no + 0x2800000a ScannerPixelSize no + 0x29000000 OriginalScannedImageSize no + 0x29000001 OriginalDocumentSize no + 0x29000002 OriginalMedium no + 0x29000003 TypeOfOriginal no + +=head3 FlashPix Operation Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x10000 OperationID no + +=head3 FlashPix SummaryInfo Tags + +The Dictionary, CodePage and LocalIndicator tags are common to all FlashPix +property tables, even though they are only listed in the SummaryInfo table. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 Dictionary no + 0x0001 CodePage no + 0x0002 Title no + 0x0003 Subject no + 0x0004 Author no + 0x0005 Keywords no + 0x0006 Comments no + 0x0007 Template no + 0x0008 LastModifiedBy no + 0x0009 RevisionNumber no + 0x000a TotalEditTime no + 0x000b LastPrinted no + 0x000c CreateDate no + 0x000d ModifyDate no + 0x000e Pages no + 0x000f Words no + 0x0010 Characters no + 0x0011 ThumbnailClip no + 0x0012 Software no + 0x0013 Security no + 0x0022 CreatedBy no + 0x0023 DocumentID no + 0x80000000 LocaleIndicator no + +=head3 FlashPix Transform Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x10000 TransformNodeID no + 0x10001 OperationClassID no + 0x10002 LockedPropertyList no + 0x10003 TransformTitle no + 0x10004 LastModifier no + 0x10005 RevisionNumber no + 0x10006 TransformCreateDate no + 0x10007 TransformModifyDate no + 0x10008 CreatingApplication no + 0x10100 InputDataObjectList no + 0x10101 OutputDataObjectList no + 0x10102 OperationNumber no + 0x10000000 ResultAspectRatio no + 0x10000001 RectangleOfInterest no + 0x10000002 Filtering no + 0x10000003 SpatialOrientation no + 0x10000004 ColorTwistMatrix no + 0x10000005 ContrastAdjustment no + +=head3 FlashPix PreviewInfo Tags + +Preview information written by some FujiFilm models. + + Index1 Tag Name Writable + ------ -------- -------- + 13 PreviewImageWidth no + 23 PreviewImageHeight no + +=head3 FlashPix SubimageHdr Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 SubimageWidth no + 2 SubimageHeight no + 3 SubimageTileCount no + 4 SubimageTileWidth no + 5 SubimageTileHeight no + 6 NumChannels no + +=head3 FlashPix WordDocument Tags + +Tags extracted from the Microsoft Word document stream. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 Identification no + 0x0003 LanguageCode no + 0x0005 DocFlags no + 9.1 System no + 9.2 Word97 no + +=head3 FlashPix DocTable Tags + +Tags extracted from the Microsoft Word document table. + + Tag Name Writable + -------- -------- + CommentBy no + DOP FlashPix DOP + LastSavedBy no + ModifyDate no + +=head3 FlashPix DOP Tags + +Microsoft office document properties. + + Index1 Tag Name Writable + ------ -------- -------- + 20 CreateDate no + 24 ModifyDate no + 28 LastPrinted no + 32 RevisionNumber no + 34 TotalEditTime no + 38 Words no + 42 Characters no + 46 Pages no + 48 Paragraphs no + 56 Lines no + +=head2 MPF Tags + +These tags are part of the CIPA Multi-Picture Format specification, and are +found in the APP2 "MPF" segment of JPEG images. MPImage data referenced +from this segment is stored as a JPEG trailer. The MPF tags are not +writable, however the MPF segment may be deleted as a group (with "MPF:All") +but then the JPEG trailer should also be deleted (with "Trailer:All"). See +L<https://web.archive.org/web/20190713230858/http://www.cipa.jp/std/documents/e/DC-007_E.pdf> +for the official specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 0xb000 MPFVersion no + 0xb001 NumberOfImages no + 0xb002 MPImageList MPF MPImage + 0xb003 ImageUIDList no + 0xb004 TotalFrames no + 0xb101 MPIndividualNum no + 0xb201 PanOrientation no + 0xb202 PanOverlapH no + 0xb203 PanOverlapV no + 0xb204 BaseViewpointNum no + 0xb205 ConvergenceAngle no + 0xb206 BaselineLength no + 0xb207 VerticalDivergence no + 0xb208 AxisDistanceX no + 0xb209 AxisDistanceY no + 0xb20a AxisDistanceZ no + 0xb20b YawAngle no + 0xb20c PitchAngle no + 0xb20d RollAngle no + +=head3 MPF MPImage Tags + +The first MPF "Large Thumbnail" image is extracted as PreviewImage, and the +rest of the embedded MPF images are extracted as MPImage#. The +ExtractEmbedded (-ee) option may be used to extract information from these +embedded images. + + Index1 Tag Name Writable + ------ -------- -------- + 0.1 MPImageFlags no + 0.2 MPImageFormat no + 0.3 MPImageType no + 4 MPImageLength no + 8 MPImageStart no + 12 DependentImage1EntryNumber no + 14 DependentImage2EntryNumber no + +=head2 InfiRay Tags + +=head3 InfiRay Version Tags + +This table lists tags found in the InfiRay APP2 IJPEG version header, found +in JPEGs taken with the P2 Pro camera app. + + Index1 Tag Name Writable + ------ -------- -------- + 0 IJPEGVersion no + 12 IJPEGOrgType no + 13 IJPEGDispType no + 14 IJPEGRotate no + 15 IJPEGMirrorFlip no + 16 ImageColorSwitchable no + 17 ThermalColorPalette no + 32 IRDataSize no + 40 IRDataFormat no + 42 IRImageWidth no + 44 IRImageHeight no + 46 IRImageBpp no + 48 TempDataSize no + 56 TempDataFormat no + 58 TempImageWidth no + 60 TempImageHeight no + 62 TempImageBpp no + 64 VisibleDataSize no + 72 VisibleDataFormat no + 74 VisibleImageWidth no + 76 VisibleImageHeight no + 78 VisibleImageBpp no + +=head2 Stim Tags + +These tags are part of the CIPA Stereo Still Image specification, and are +found in the APP3 "Stim" segment of JPEG images. See +L<https://web.archive.org/web/20190718152459/http://www.cipa.jp/std/documents/e/DC-006_E.pdf> +for the official specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 StimVersion no + 0x0001 ApplicationData no + 0x0002 ImageArrangement no + 0x0003 ImageRotation no + 0x0004 ScalingFactor no + 0x0005 CropXSize no + 0x0006 CropYSize no + 0x0007 CropX Stim CropX + 0x0008 CropY Stim CropY + 0x0009 ViewType no + 0x000a RepresentativeImage no + 0x000b ConvergenceBaseImage no + 0x000c AssumedDisplaySize no + 0x000d AssumedDistanceView no + 0x000e RepresentativeDisparityNear no + 0x000f RepresentativeDisparityFar no + 0x0010 InitialDisplayEffect no + 0x0011 ConvergenceDistance no + 0x0012 CameraArrangementInterval no + 0x0013 ShootingCount no + +=head3 Stim CropX Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 CropXCommonOffset no + 2 CropXViewpointNumber no + 3 CropXOffset no + 7 CropXViewpointNumber2 no + 8 CropXOffset2 no + +=head3 Stim CropY Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 CropYCommonOffset no + 2 CropYViewpointNumber no + 3 CropYOffset no + 7 CropYViewpointNumber2 no + 8 CropYOffset2 no + +=head2 Scalado Tags + +Tags extracted from the JPEG APP4 "SCALADO" segment found in images from +HTC, LG and Samsung phones. (Presumably written by Scalado mobile software, +L<http://www.scalado.com/>.) + + Tag ID Tag Name Writable + ------ -------- -------- + 'HGHT' PreviewImageHeight no + 'QUAL' PreviewQuality no + 'SPMO' DataLength? no + 'WDTH' PreviewImageWidth no + +=head2 InfiRay Tags + +=head3 InfiRay Factory Tags + +This table lists tags found in the InfiRay APP4 IJPEG camera factory +defaults and calibration data. + + Index1 Tag Name Writable + ------ -------- -------- + 0 IJPEGTempVersion no + 4 FactDefEmissivity no + 5 FactDefTau no + 6 FactDefTa no + 8 FactDefTu no + 10 FactDefDist no + 12 FactDefA0 no + 16 FactDefB0 no + 20 FactDefA1 no + 24 FactDefB1 no + 28 FactDefP0 no + 32 FactDefP1 no + 36 FactDefP2 no + 68 FactRelSensorTemp no + 70 FactRelShutterTemp no + 72 FactRelLensTemp no + 100 FactStatusGain no + 101 FactStatusEnvOK no + 102 FactStatusDistOK no + 103 FactStatusTempMap no + +=head3 InfiRay Picture Tags + +This table lists tags found in the InfiRay APP5 IJPEG picture temperature +information. + + Index1 Tag Name Writable + ------ -------- -------- + 0 EnvironmentTemp no + 4 Distance no + 8 Emissivity no + 12 Humidity no + 16 ReferenceTemp no + 32 TempUnit no + 33 ShowCenterTemp no + 34 ShowMaxTemp no + 35 ShowMinTemp no + 36 TempMeasureCount no + +=head2 GoPro Tags + +=head3 GoPro GPMF Tags + +Tags extracted from the GPMF box of GoPro MP4 videos, the APP6 "GoPro" +segment of JPEG files, and from the "gpmd" timed metadata if the +ExtractEmbedded (-ee) option is enabled. Many more tags exist, but are +currently unknown and extracted only with the Unknown (-u) option. Please +let me know if you discover the meaning of any of these unknown tags. See +L<https://github.com/gopro/gpmf-parser> for details about this format. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ACCL' Accelerometer no + 'ALLD' AutoLowLightDuration no + 'ATTD' Attitude no + 'ATTR' AttitudeTarget no + 'AUDO' AudioSetting no + 'BPOS' Controller? no + 'CASN' CameraSerialNumber no + 'CSEN' CoyoteSense no + 'CYTS' CoyoteStatus no + 'DEVC' DeviceContainer GoPro GPMF + 'DVID' DeviceID? no + 'DVNM' DeviceName no + 'DZOM' DigitalZoom no + 'EISA' ElectronicImageStabilization no + 'EMPT' Empty? no + 'ESCS' EscapeStatus? no + 'EXPT' MaximumShutterAngle no + 'FACE' FaceDetected no + 'FCNM' FaceNumbers no + 'FMWR' FirmwareVersion no + 'FWVS' OtherFirmware no + 'GLPI' GPSPos GoPro GLPI + 'GPRI' GPSRaw? GoPro GPRI + 'GPS5' GPSInfo GoPro GPS5 + 'GPSF' GPSMeasureMode no + 'GPSP' GPSHPositioningError no + 'GPSU' GPSDateTime no + 'GYRO' Gyroscope no + 'ISOE' ISOSpeeds no + 'ISOG' ImageSensorGain no + 'KBAT' BatteryStatus GoPro KBAT + 'LNED' LocalPositionNED no + 'MAGN' Magnetometer no + 'MINF' Model no + 'MTRX' AccelerometerMatrix no + 'MUID' MediaUniqueID no + 'OREN' AutoRotation no + 'ORIN' InputOrientation no + 'ORIO' OutputOrientation no + 'PHDR' HDRSetting no + 'PIMN' AutoISOMin no + 'PIMX' AutoISOMax no + 'PRES' PhotoResolution no + 'PRTN' ProTune no + 'PTCL' ColorMode no + 'PTEV' ExposureCompensation no + 'PTSH' Sharpness no + 'PTWB' WhiteBalance no + 'RATE' Rate no + 'RMRK' Comments no + 'SCAL' ScaleFactor? no + 'SCPR' ScaledPressure no + 'SHUT' ExposureTimes no + 'SIMU' ScaledIMU no + 'SIUN' SIUnits? no + 'SROT' SensorReadoutTime no + 'STMP' TimeStamp no + 'STNM' StreamName? no + 'STRM' NestedSignalStream GoPro GPMF + 'SYST' SystemTime no + 'TMPC' CameraTemperature no + 'TSMP' TotalSamples? no + 'TYPE' StructureType? no + 'UNIF' InputUniformity no + 'UNIT' Units? no + 'VERS' MetadataVersion no + 'VFOV' FieldOfView no + 'VFRH' VisualFlightRulesHUD no + 'WBAL' ColorTemperatures no + 'WRGB' WhiteBalanceRGB no + +=head3 GoPro GLPI Tags + + Index Tag Name Writable + ----- -------- -------- + 0 GPSDateTime no + 1 GPSLatitude no + 2 GPSLongitude no + 3 GPSAltitude no + 5 GPSSpeedX no + 6 GPSSpeedY no + 7 GPSSpeedZ no + 8 GPSTrack no + +=head3 GoPro GPRI Tags + + Index Tag Name Writable + ----- -------- -------- + 0 GPSDateTimeRaw no + 1 GPSLatitudeRaw no + 2 GPSLongitudeRaw no + 3 GPSAltitudeRaw no + 6 GPSSpeedRaw no + 7 GPSTrackRaw no + +=head3 GoPro GPS5 Tags + + Index Tag Name Writable + ----- -------- -------- + 0 GPSLatitude no + 1 GPSLongitude no + 2 GPSAltitude no + 3 GPSSpeed no + 4 GPSSpeed3D no + +=head3 GoPro KBAT Tags + +Battery status information found in GoPro Karma videos. + + Index Tag Name Writable + ----- -------- -------- + 0 BatteryCurrent no + 1 BatteryCapacity no + 3 BatteryTemperature no + 4 BatteryVoltage1 no + 5 BatteryVoltage2 no + 6 BatteryVoltage3 no + 7 BatteryVoltage4 no + 8 BatteryTime no + 14 BatteryLevel no + +=head3 GoPro fdsc Tags + +Tags extracted from the MP4 "fdsc" timed metadata when the ExtractEmbedded +(-ee) option is used. + + Index1 Tag Name Writable + ------ -------- -------- + 8 FirmwareVersion no + 23 SerialNumber no + 87 OtherSerialNumber no + 102 Model no + +=head2 InfiRay Tags + +=head3 InfiRay MixMode Tags + +This table lists tags found in the InfiRay APP6 IJPEG visual-infrared mixing +mode section. + + Index1 Tag Name Writable + ------ -------- -------- + 0 MixMode no + 1 FusionIntensity no + 5 OffsetAdjustment no + 9 CorrectionAsix no + +=head2 Qualcomm Tags + +The tags below have been observed in the JPEG APP7 "Qualcomm Camera +Attributes" segment written by some cameras such as the HP iPAQ Voice +Messenger. ExifTool will extract any information found from this segment, +even if it is not listed in this table. + + Tag Name Writable + -------- -------- + AECAggressiveness no + AECCurrentExpIndex no + AECCurrentSensorLuma no + AECEnable no + AECExposureIndexAdjStep no + AECHighLumaRegionCount no + AECHighLumaRegionThreshold no + AECIndoorIdx no + AECLumaTarget no + AECLumaTolerance no + AECMode no + AECOdoorIdx no + AECOutdoorBrightDiscarded no + AECOutdoorBrightEnable no + AECOutdoorBrightReduction no + AECOutdoorBrightThresholdHI no + AECOutdoorBrightThresholdLO no + AECOutdoorGammaIndex no + AECSnapshotDigitalGain no + AECSnapshotExposureTimeMs no + AECSnapshotLineCount no + AECSnapshotSensorGain no + AECVfeLuma no + AFBoundary no + AFCollectEndStat no + AFEnable no + AFFarEnd no + AFFineSrchPoints no + AFFineStep no + AFFocusTime no + AFGrossStep no + AFMode no + AFNearEnd no + AFPosDefMacro no + AFPosDefNorm no + AFPosition no + AFProcess no + AFREnable no + AFRFaster0ExpMod no + AFRFaster0Trigger no + AFRFaster1ExpMod no + AFRFaster1Trigger no + AFRFaster2ExpMod no + AFRFaster2Trigger no + AFRFaster3ExpMod no + AFRFaster3Trigger no + AFRFaster4ExpMod no + AFRFaster4Trigger no + AFRPossibleFrameCnt no + AFRSlower0ExpMod no + AFRSlower0Trigger no + AFRSlower1ExpMod no + AFRSlower1Trigger no + AFRSlower2ExpMod no + AFRSlower2Trigger no + AFRSlower3ExpMod no + AFRSlower3Trigger no + AFRSlower4ExpMod no + AFRSlower4Trigger no + AFResetLensAfterSnap no + AFStepsNearFar no + AFStepsNearInfinity no + AFTestMode no + AFTracePositions00 no + AFTracePositions01 no + AFTracePositions02 no + AFTracePositions03 no + AFTracePositions04 no + AFTracePositions05 no + AFTracePositions06 no + AFTracePositions07 no + AFTracePositions08 no + AFTracePositions09 no + AFTracePositions10 no + AFTracePositions11 no + AFTracePositions12 no + AFTracePositions13 no + AFTracePositions14 no + AFTracePositions15 no + AFTracePositions16 no + AFTracePositions17 no + AFTracePositions18 no + AFTracePositions19 no + AFTracePositions20 no + AFTracePositions21 no + AFTracePositions22 no + AFTracePositions23 no + AFTracePositions24 no + AFTracePositions25 no + AFTracePositions26 no + AFTracePositions27 no + AFTracePositions28 no + AFTracePositions29 no + AFTracePositions30 no + AFTracePositions31 no + AFTracePositions32 no + AFTracePositions33 no + AFTracePositions34 no + AFTracePositions35 no + AFTracePositions36 no + AFTracePositions37 no + AFTracePositions38 no + AFTracePositions39 no + AFTracePositions40 no + AFTracePositions41 no + AFTracePositions42 no + AFTracePositions43 no + AFTracePositions44 no + AFTracePositions45 no + AFTracePositions46 no + AFTracePositions47 no + AFTracePositions48 no + AFTracePositions49 no + AFTraceStats00 no + AFTraceStats01 no + AFTraceStats02 no + AFTraceStats03 no + AFTraceStats04 no + AFTraceStats05 no + AFTraceStats06 no + AFTraceStats07 no + AFTraceStats08 no + AFTraceStats09 no + AFTraceStats10 no + AFTraceStats11 no + AFTraceStats12 no + AFTraceStats13 no + AFTraceStats14 no + AFTraceStats15 no + AFTraceStats16 no + AFTraceStats17 no + AFTraceStats18 no + AFTraceStats19 no + AFTraceStats20 no + AFTraceStats21 no + AFTraceStats22 no + AFTraceStats23 no + AFTraceStats24 no + AFTraceStats25 no + AFTraceStats26 no + AFTraceStats27 no + AFTraceStats28 no + AFTraceStats29 no + AFTraceStats30 no + AFTraceStats31 no + AFTraceStats32 no + AFTraceStats33 no + AFTraceStats34 no + AFTraceStats35 no + AFTraceStats36 no + AFTraceStats37 no + AFTraceStats38 no + AFTraceStats39 no + AFTraceStats40 no + AFTraceStats41 no + AFTraceStats42 no + AFTraceStats43 no + AFTraceStats44 no + AFTraceStats45 no + AFTraceStats46 no + AFTraceStats47 no + AFTraceStats48 no + AFTraceStats49 no + AFUndershootProtect no + AFVfeHorzOffset no + AFVfeHorzWidth no + AFVfeMetricMax no + AFVfeVertHeight no + AFVfeVertOffset no + ASF3EdgeDetect no + ASF3EdgeFilterA11 no + ASF3EdgeFilterA12 no + ASF3EdgeFilterA13 no + ASF3EdgeFilterA21 no + ASF3EdgeFilterA22 no + ASF3EdgeFilterA23 no + ASF3EdgeFilterA31 no + ASF3EdgeFilterA32 no + ASF3EdgeFilterA33 no + ASF3Enable no + ASF3LowerThreshold no + ASF3NoiseFilterA11 no + ASF3NoiseFilterA12 no + ASF3NoiseFilterA13 no + ASF3NoiseFilterA21 no + ASF3NoiseFilterA22 no + ASF3NoiseFilterA23 no + ASF3NoiseFilterA31 no + ASF3NoiseFilterA32 no + ASF3NoiseFilterA33 no + ASF3UpperThreshold no + ASF5BrtLoThres no + ASF5BrtShrpDegF1 no + ASF5BrtShrpDegF2 no + ASF5BrtSmthPercent no + ASF5BrtUpThres no + ASF5Enable no + ASF5ExposureIndex1 no + ASF5ExposureIndex2 no + ASF5Filter1A11 no + ASF5Filter1A12 no + ASF5Filter1A13 no + ASF5Filter1A14 no + ASF5Filter1A15 no + ASF5Filter1A21 no + ASF5Filter1A22 no + ASF5Filter1A23 no + ASF5Filter1A24 no + ASF5Filter1A25 no + ASF5Filter1A31 no + ASF5Filter1A32 no + ASF5Filter1A33 no + ASF5Filter1A34 no + ASF5Filter1A35 no + ASF5Filter1A41 no + ASF5Filter1A42 no + ASF5Filter1A43 no + ASF5Filter1A44 no + ASF5Filter1A45 no + ASF5Filter1A51 no + ASF5Filter1A52 no + ASF5Filter1A53 no + ASF5Filter1A54 no + ASF5Filter1A55 no + ASF5Filter2A11 no + ASF5Filter2A12 no + ASF5Filter2A13 no + ASF5Filter2A14 no + ASF5Filter2A15 no + ASF5Filter2A21 no + ASF5Filter2A22 no + ASF5Filter2A23 no + ASF5Filter2A24 no + ASF5Filter2A25 no + ASF5Filter2A31 no + ASF5Filter2A32 no + ASF5Filter2A33 no + ASF5Filter2A34 no + ASF5Filter2A35 no + ASF5Filter2A41 no + ASF5Filter2A42 no + ASF5Filter2A43 no + ASF5Filter2A44 no + ASF5Filter2A45 no + ASF5Filter2A51 no + ASF5Filter2A52 no + ASF5Filter2A53 no + ASF5Filter2A54 no + ASF5Filter2A55 no + ASF5FilterMode no + ASF5LowLoThres no + ASF5LowShrpDegF1 no + ASF5LowShrpDegF2 no + ASF5LowSmthPrcnt no + ASF5LowUpThres no + ASF5LumaFilter00 no + ASF5LumaFilter01 no + ASF5LumaFilter02 no + ASF5LumaFilter03 no + ASF5LumaFilter04 no + ASF5LumaFilter05 no + ASF5LumaFilter06 no + ASF5LumaFilter07 no + ASF5LumaFilter08 no + ASF5MaxExposureIndex no + ASF5NrmLoThres no + ASF5NrmShrpDegF1 no + ASF5NrmShrpDegF2 no + ASF5NrmSmthPrcnt no + ASF5NrmUpThres no + ASF5NrmizeFactor1 no + ASF5NrmizeFactor2 no + AWBAggressiveness no + AWBAgwGridDist2Thresh no + AWBAlgorithm no + AWBAveBgRatio no + AWBAveRgRatio no + AWBBlueGainAdjRef1 no + AWBBlueGainAdjRef2 no + AWBBlueGainAdjRef3 no + AWBBlueGainAdjRef4 no + AWBBlueGainAdjRef5 no + AWBBlueGainAdjRef6 no + AWBBlueGainAdjRef7 no + AWBBlueGainAdjRef8 no + AWBBlueGainRef1 no + AWBBlueGainRef2 no + AWBBlueGainRef3 no + AWBBlueGainRef4 no + AWBBlueGainRef5 no + AWBBlueGainRef6 no + AWBBlueGainRef7 no + AWBBlueGainRef8 no + AWBCcBias no + AWBCompactClusterR2 no + AWBEnable no + AWBGreenOffsetBg no + AWBGreenOffsetRg no + AWBIndoorSampleInfluence no + AWBLoVfeC1 no + AWBLoVfeC2 no + AWBLoVfeC3 no + AWBLoVfeC4 no + AWBLoVfeM1 no + AWBLoVfeM2 no + AWBLoVfeM3 no + AWBLoVfeM4 no + AWBLoVfeMaxY no + AWBLoVfeMinY no + AWBLowLigColCorEna no + AWBMaxBGain no + AWBMaxGGain no + AWBMaxRGain no + AWBMinBGain no + AWBMinGGain no + AWBMinRGain no + AWBNormVfeC1 no + AWBNormVfeC2 no + AWBNormVfeC3 no + AWBNormVfeC4 no + AWBNormVfeM1 no + AWBNormVfeM2 no + AWBNormVfeM3 no + AWBNormVfeM4 no + AWBNormVfeMaxY no + AWBNormVfeMinY no + AWBOudorVfeC1 no + AWBOudorVfeC2 no + AWBOudorVfeC3 no + AWBOudorVfeC4 no + AWBOudorVfeM1 no + AWBOudorVfeM2 no + AWBOudorVfeM3 no + AWBOudorVfeM4 no + AWBOudorVfeMaxY no + AWBOudorVfeMinY no + AWBOutdoorSampleInfluence no + AWBPrevWbBgain no + AWBPrevWbGgain no + AWBPrevWbRgain no + AWBRedGainAdjRef1 no + AWBRedGainAdjRef2 no + AWBRedGainAdjRef3 no + AWBRedGainAdjRef4 no + AWBRedGainAdjRef5 no + AWBRedGainAdjRef6 no + AWBRedGainAdjRef7 no + AWBRedGainAdjRef8 no + AWBRedGainRef1 no + AWBRedGainRef2 no + AWBRedGainRef3 no + AWBRedGainRef4 no + AWBRedGainRef5 no + AWBRedGainRef6 no + AWBRedGainRef7 no + AWBRedGainRef8 no + AWBSampleDecision no + AWBSnapshotBGain no + AWBSnapshotRGain no + AntiBadingPixelClk no + AntiBadingPixelClkPerLine no + AntibandingEnable no + BlckLvlEvenCols no + BlckLvlOddCols no + CamMclkHz no + ChroSupChroThres1 no + ChroSupChroThres2 no + ChroSupLumaThres1 no + ChroSupLumaThres2 no + ChroSupLumaThres3 no + ChroSupLumaThres4 no + ChromSupress no + ClipToAfRato no + CurrResol no + DayltConvChrmA_M no + DayltConvChrmA_P no + DayltConvChrmB_M no + DayltConvChrmB_P no + DayltConvChrmC_M no + DayltConvChrmC_P no + DayltConvChrmD_M no + DayltConvChrmD_P no + DayltConvChrmKCb no + DayltConvChrmKCr no + DayltConvLumaK no + DayltConvLumaV0 no + DayltConvLumaV1 no + DayltConvLumaV2 no + DefConvChrmA_M no + DefConvChrmA_P no + DefConvChrmB_M no + DefConvChrmB_P no + DefConvChrmC_M no + DefConvChrmC_P no + DefConvChrmD_M no + DefConvChrmD_P no + DefConvChrmKCb no + DefConvChrmKCr no + DefConvLumaK no + DefConvLumaV0 no + DefConvLumaV1 no + DefConvLumaV2 no + DefCorC0 no + DefCorC1 no + DefCorC2 no + DefCorC3 no + DefCorC4 no + DefCorC5 no + DefCorC6 no + DefCorC7 no + DefCorC8 no + DefCorK0 no + DefCorK1 no + DefCorK2 no + DefLumaGammaMode no + DefRgbGammaMode no + DefectPixCorEnable no + DefectPixMaxThresh no + DefectPixMinThresh no + DiscardFrstFrm no + FrmSkipPttrn no + GammaEnable no + HJREnable no + HJRMaxNumFrames no + HJROneToTwoOffset no + HJRTextureThreshold no + HJR_NReductionFlat no + HJR_NReductionTexture no + IncandConvChrmA_M no + IncandConvChrmA_P no + IncandConvChrmB_M no + IncandConvChrmB_P no + IncandConvChrmC_M no + IncandConvChrmC_P no + IncandConvChrmD_M no + IncandConvChrmD_P no + IncandConvChrmKCb no + IncandConvChrmKCr no + IncandConvLumaK no + IncandConvLumaV0 no + IncandConvLumaV1 no + IncandConvLumaV2 no + LADetect no + LAEnable no + MaxPrviewFps no + MaxVideoFps no + NghtshtFps no + NightshotMode no + OutlierDistance no + PclkInvert no + PrviewFps no + PrviewResol no + R2ABlueCtbl00 no + R2ABlueCtbl01 no + R2ABlueCtbl02 no + R2ABlueCtbl03 no + R2ABlueCtbl04 no + R2ABlueCtbl05 no + R2ABlueCtbl06 no + R2ABlueCtbl07 no + R2ABlueCtbl08 no + R2ABlueCtbl09 no + R2ABlueCtbl10 no + R2ABlueCtbl11 no + R2ABlueCtbl12 no + R2ABlueCtbl13 no + R2ABlueCtbl14 no + R2ABlueCtbl15 no + R2ABlueCtbl16 no + R2ABlueCtbl17 no + R2ABlueCtbl18 no + R2ABlueCtbl19 no + R2ABlueCtbl20 no + R2ABlueCtbl21 no + R2ABlueCtbl22 no + R2ABlueCtbl23 no + R2ABlueCtbl24 no + R2ABlueCtbl25 no + R2ABlueCtbl26 no + R2ABlueCtbl27 no + R2ABlueCtbl28 no + R2ABlueCtbl29 no + R2ABlueCtbl30 no + R2ABlueCtbl31 no + R2ABlueStbl00 no + R2ABlueStbl01 no + R2ABlueStbl02 no + R2ABlueStbl03 no + R2ABlueStbl04 no + R2ABlueStbl05 no + R2ABlueStbl06 no + R2ABlueStbl07 no + R2ABlueStbl08 no + R2ABlueStbl09 no + R2ABlueStbl10 no + R2ABlueStbl11 no + R2ABlueStbl12 no + R2ABlueStbl13 no + R2ABlueStbl14 no + R2ABlueStbl15 no + R2ABlueStbl16 no + R2ABlueStbl17 no + R2ABlueStbl18 no + R2ABlueStbl19 no + R2ABlueStbl20 no + R2ABlueStbl21 no + R2ABlueStbl22 no + R2ABlueStbl23 no + R2ABlueStbl24 no + R2ABlueStbl25 no + R2ABlueStbl26 no + R2ABlueStbl27 no + R2ABlueStbl28 no + R2ABlueStbl29 no + R2ABlueStbl30 no + R2ABlueStbl31 no + R2ACx no + R2ACy no + R2AGreenCtbl00 no + R2AGreenCtbl01 no + R2AGreenCtbl02 no + R2AGreenCtbl03 no + R2AGreenCtbl04 no + R2AGreenCtbl05 no + R2AGreenCtbl06 no + R2AGreenCtbl07 no + R2AGreenCtbl08 no + R2AGreenCtbl09 no + R2AGreenCtbl10 no + R2AGreenCtbl11 no + R2AGreenCtbl12 no + R2AGreenCtbl13 no + R2AGreenCtbl14 no + R2AGreenCtbl15 no + R2AGreenCtbl16 no + R2AGreenCtbl17 no + R2AGreenCtbl18 no + R2AGreenCtbl19 no + R2AGreenCtbl20 no + R2AGreenCtbl21 no + R2AGreenCtbl22 no + R2AGreenCtbl23 no + R2AGreenCtbl24 no + R2AGreenCtbl25 no + R2AGreenCtbl26 no + R2AGreenCtbl27 no + R2AGreenCtbl28 no + R2AGreenCtbl29 no + R2AGreenCtbl30 no + R2AGreenCtbl31 no + R2AGreenStbl00 no + R2AGreenStbl01 no + R2AGreenStbl02 no + R2AGreenStbl03 no + R2AGreenStbl04 no + R2AGreenStbl05 no + R2AGreenStbl06 no + R2AGreenStbl07 no + R2AGreenStbl08 no + R2AGreenStbl09 no + R2AGreenStbl10 no + R2AGreenStbl11 no + R2AGreenStbl12 no + R2AGreenStbl13 no + R2AGreenStbl14 no + R2AGreenStbl15 no + R2AGreenStbl16 no + R2AGreenStbl17 no + R2AGreenStbl18 no + R2AGreenStbl19 no + R2AGreenStbl20 no + R2AGreenStbl21 no + R2AGreenStbl22 no + R2AGreenStbl23 no + R2AGreenStbl24 no + R2AGreenStbl25 no + R2AGreenStbl26 no + R2AGreenStbl27 no + R2AGreenStbl28 no + R2AGreenStbl29 no + R2AGreenStbl30 no + R2AGreenStbl31 no + R2AHeight no + R2AIntervals no + R2ARedCtbl00 no + R2ARedCtbl01 no + R2ARedCtbl02 no + R2ARedCtbl03 no + R2ARedCtbl04 no + R2ARedCtbl05 no + R2ARedCtbl06 no + R2ARedCtbl07 no + R2ARedCtbl08 no + R2ARedCtbl09 no + R2ARedCtbl10 no + R2ARedCtbl11 no + R2ARedCtbl12 no + R2ARedCtbl13 no + R2ARedCtbl14 no + R2ARedCtbl15 no + R2ARedCtbl16 no + R2ARedCtbl17 no + R2ARedCtbl18 no + R2ARedCtbl19 no + R2ARedCtbl20 no + R2ARedCtbl21 no + R2ARedCtbl22 no + R2ARedCtbl23 no + R2ARedCtbl24 no + R2ARedCtbl25 no + R2ARedCtbl26 no + R2ARedCtbl27 no + R2ARedCtbl28 no + R2ARedCtbl29 no + R2ARedCtbl30 no + R2ARedCtbl31 no + R2ARedStbl00 no + R2ARedStbl01 no + R2ARedStbl02 no + R2ARedStbl03 no + R2ARedStbl04 no + R2ARedStbl05 no + R2ARedStbl06 no + R2ARedStbl07 no + R2ARedStbl08 no + R2ARedStbl09 no + R2ARedStbl10 no + R2ARedStbl11 no + R2ARedStbl12 no + R2ARedStbl13 no + R2ARedStbl14 no + R2ARedStbl15 no + R2ARedStbl16 no + R2ARedStbl17 no + R2ARedStbl18 no + R2ARedStbl19 no + R2ARedStbl20 no + R2ARedStbl21 no + R2ARedStbl22 no + R2ARedStbl23 no + R2ARedStbl24 no + R2ARedStbl25 no + R2ARedStbl26 no + R2ARedStbl27 no + R2ARedStbl28 no + R2ARedStbl29 no + R2ARedStbl30 no + R2ARedStbl31 no + R2ATbl00 no + R2ATbl01 no + R2ATbl02 no + R2ATbl03 no + R2ATbl04 no + R2ATbl05 no + R2ATbl06 no + R2ATbl07 no + R2ATbl08 no + R2ATbl09 no + R2ATbl10 no + R2ATbl11 no + R2ATbl12 no + R2ATbl13 no + R2ATbl14 no + R2ATbl15 no + R2ATbl16 no + R2ATbl17 no + R2ATbl18 no + R2ATbl19 no + R2ATbl20 no + R2ATbl21 no + R2ATbl22 no + R2ATbl23 no + R2ATbl24 no + R2ATbl25 no + R2ATbl26 no + R2ATbl27 no + R2ATbl28 no + R2ATbl29 no + R2ATbl30 no + R2ATbl31 no + R2AWidth no + R2D65BlueCtbl00 no + R2D65BlueCtbl01 no + R2D65BlueCtbl02 no + R2D65BlueCtbl03 no + R2D65BlueCtbl04 no + R2D65BlueCtbl05 no + R2D65BlueCtbl06 no + R2D65BlueCtbl07 no + R2D65BlueCtbl08 no + R2D65BlueCtbl09 no + R2D65BlueCtbl10 no + R2D65BlueCtbl11 no + R2D65BlueCtbl12 no + R2D65BlueCtbl13 no + R2D65BlueCtbl14 no + R2D65BlueCtbl15 no + R2D65BlueCtbl16 no + R2D65BlueCtbl17 no + R2D65BlueCtbl18 no + R2D65BlueCtbl19 no + R2D65BlueCtbl20 no + R2D65BlueCtbl21 no + R2D65BlueCtbl22 no + R2D65BlueCtbl23 no + R2D65BlueCtbl24 no + R2D65BlueCtbl25 no + R2D65BlueCtbl26 no + R2D65BlueCtbl27 no + R2D65BlueCtbl28 no + R2D65BlueCtbl29 no + R2D65BlueCtbl30 no + R2D65BlueCtbl31 no + R2D65BlueStbl00 no + R2D65BlueStbl01 no + R2D65BlueStbl02 no + R2D65BlueStbl03 no + R2D65BlueStbl04 no + R2D65BlueStbl05 no + R2D65BlueStbl06 no + R2D65BlueStbl07 no + R2D65BlueStbl08 no + R2D65BlueStbl09 no + R2D65BlueStbl10 no + R2D65BlueStbl11 no + R2D65BlueStbl12 no + R2D65BlueStbl13 no + R2D65BlueStbl14 no + R2D65BlueStbl15 no + R2D65BlueStbl16 no + R2D65BlueStbl17 no + R2D65BlueStbl18 no + R2D65BlueStbl19 no + R2D65BlueStbl20 no + R2D65BlueStbl21 no + R2D65BlueStbl22 no + R2D65BlueStbl23 no + R2D65BlueStbl24 no + R2D65BlueStbl25 no + R2D65BlueStbl26 no + R2D65BlueStbl27 no + R2D65BlueStbl28 no + R2D65BlueStbl29 no + R2D65BlueStbl30 no + R2D65BlueStbl31 no + R2D65Cx no + R2D65Cy no + R2D65GreenCtbl00 no + R2D65GreenCtbl01 no + R2D65GreenCtbl02 no + R2D65GreenCtbl03 no + R2D65GreenCtbl04 no + R2D65GreenCtbl05 no + R2D65GreenCtbl06 no + R2D65GreenCtbl07 no + R2D65GreenCtbl08 no + R2D65GreenCtbl09 no + R2D65GreenCtbl10 no + R2D65GreenCtbl11 no + R2D65GreenCtbl12 no + R2D65GreenCtbl13 no + R2D65GreenCtbl14 no + R2D65GreenCtbl15 no + R2D65GreenCtbl16 no + R2D65GreenCtbl17 no + R2D65GreenCtbl18 no + R2D65GreenCtbl19 no + R2D65GreenCtbl20 no + R2D65GreenCtbl21 no + R2D65GreenCtbl22 no + R2D65GreenCtbl23 no + R2D65GreenCtbl24 no + R2D65GreenCtbl25 no + R2D65GreenCtbl26 no + R2D65GreenCtbl27 no + R2D65GreenCtbl28 no + R2D65GreenCtbl29 no + R2D65GreenCtbl30 no + R2D65GreenCtbl31 no + R2D65GreenStbl00 no + R2D65GreenStbl01 no + R2D65GreenStbl02 no + R2D65GreenStbl03 no + R2D65GreenStbl04 no + R2D65GreenStbl05 no + R2D65GreenStbl06 no + R2D65GreenStbl07 no + R2D65GreenStbl08 no + R2D65GreenStbl09 no + R2D65GreenStbl10 no + R2D65GreenStbl11 no + R2D65GreenStbl12 no + R2D65GreenStbl13 no + R2D65GreenStbl14 no + R2D65GreenStbl15 no + R2D65GreenStbl16 no + R2D65GreenStbl17 no + R2D65GreenStbl18 no + R2D65GreenStbl19 no + R2D65GreenStbl20 no + R2D65GreenStbl21 no + R2D65GreenStbl22 no + R2D65GreenStbl23 no + R2D65GreenStbl24 no + R2D65GreenStbl25 no + R2D65GreenStbl26 no + R2D65GreenStbl27 no + R2D65GreenStbl28 no + R2D65GreenStbl29 no + R2D65GreenStbl30 no + R2D65GreenStbl31 no + R2D65Height no + R2D65Intervals no + R2D65RedCtbl00 no + R2D65RedCtbl01 no + R2D65RedCtbl02 no + R2D65RedCtbl03 no + R2D65RedCtbl04 no + R2D65RedCtbl05 no + R2D65RedCtbl06 no + R2D65RedCtbl07 no + R2D65RedCtbl08 no + R2D65RedCtbl09 no + R2D65RedCtbl10 no + R2D65RedCtbl11 no + R2D65RedCtbl12 no + R2D65RedCtbl13 no + R2D65RedCtbl14 no + R2D65RedCtbl15 no + R2D65RedCtbl16 no + R2D65RedCtbl17 no + R2D65RedCtbl18 no + R2D65RedCtbl19 no + R2D65RedCtbl20 no + R2D65RedCtbl21 no + R2D65RedCtbl22 no + R2D65RedCtbl23 no + R2D65RedCtbl24 no + R2D65RedCtbl25 no + R2D65RedCtbl26 no + R2D65RedCtbl27 no + R2D65RedCtbl28 no + R2D65RedCtbl29 no + R2D65RedCtbl30 no + R2D65RedCtbl31 no + R2D65RedStbl00 no + R2D65RedStbl01 no + R2D65RedStbl02 no + R2D65RedStbl03 no + R2D65RedStbl04 no + R2D65RedStbl05 no + R2D65RedStbl06 no + R2D65RedStbl07 no + R2D65RedStbl08 no + R2D65RedStbl09 no + R2D65RedStbl10 no + R2D65RedStbl11 no + R2D65RedStbl12 no + R2D65RedStbl13 no + R2D65RedStbl14 no + R2D65RedStbl15 no + R2D65RedStbl16 no + R2D65RedStbl17 no + R2D65RedStbl18 no + R2D65RedStbl19 no + R2D65RedStbl20 no + R2D65RedStbl21 no + R2D65RedStbl22 no + R2D65RedStbl23 no + R2D65RedStbl24 no + R2D65RedStbl25 no + R2D65RedStbl26 no + R2D65RedStbl27 no + R2D65RedStbl28 no + R2D65RedStbl29 no + R2D65RedStbl30 no + R2D65RedStbl31 no + R2D65Tbl00 no + R2D65Tbl01 no + R2D65Tbl02 no + R2D65Tbl03 no + R2D65Tbl04 no + R2D65Tbl05 no + R2D65Tbl06 no + R2D65Tbl07 no + R2D65Tbl08 no + R2D65Tbl09 no + R2D65Tbl10 no + R2D65Tbl11 no + R2D65Tbl12 no + R2D65Tbl13 no + R2D65Tbl14 no + R2D65Tbl15 no + R2D65Tbl16 no + R2D65Tbl17 no + R2D65Tbl18 no + R2D65Tbl19 no + R2D65Tbl20 no + R2D65Tbl21 no + R2D65Tbl22 no + R2D65Tbl23 no + R2D65Tbl24 no + R2D65Tbl25 no + R2D65Tbl26 no + R2D65Tbl27 no + R2D65Tbl28 no + R2D65Tbl29 no + R2D65Tbl30 no + R2D65Tbl31 no + R2D65Width no + R2TL84BlueCtbl00 no + R2TL84BlueCtbl01 no + R2TL84BlueCtbl02 no + R2TL84BlueCtbl03 no + R2TL84BlueCtbl04 no + R2TL84BlueCtbl05 no + R2TL84BlueCtbl06 no + R2TL84BlueCtbl07 no + R2TL84BlueCtbl08 no + R2TL84BlueCtbl09 no + R2TL84BlueCtbl10 no + R2TL84BlueCtbl11 no + R2TL84BlueCtbl12 no + R2TL84BlueCtbl13 no + R2TL84BlueCtbl14 no + R2TL84BlueCtbl15 no + R2TL84BlueCtbl16 no + R2TL84BlueCtbl17 no + R2TL84BlueCtbl18 no + R2TL84BlueCtbl19 no + R2TL84BlueCtbl20 no + R2TL84BlueCtbl21 no + R2TL84BlueCtbl22 no + R2TL84BlueCtbl23 no + R2TL84BlueCtbl24 no + R2TL84BlueCtbl25 no + R2TL84BlueCtbl26 no + R2TL84BlueCtbl27 no + R2TL84BlueCtbl28 no + R2TL84BlueCtbl29 no + R2TL84BlueCtbl30 no + R2TL84BlueCtbl31 no + R2TL84BlueStbl00 no + R2TL84BlueStbl01 no + R2TL84BlueStbl02 no + R2TL84BlueStbl03 no + R2TL84BlueStbl04 no + R2TL84BlueStbl05 no + R2TL84BlueStbl06 no + R2TL84BlueStbl07 no + R2TL84BlueStbl08 no + R2TL84BlueStbl09 no + R2TL84BlueStbl10 no + R2TL84BlueStbl11 no + R2TL84BlueStbl12 no + R2TL84BlueStbl13 no + R2TL84BlueStbl14 no + R2TL84BlueStbl15 no + R2TL84BlueStbl16 no + R2TL84BlueStbl17 no + R2TL84BlueStbl18 no + R2TL84BlueStbl19 no + R2TL84BlueStbl20 no + R2TL84BlueStbl21 no + R2TL84BlueStbl22 no + R2TL84BlueStbl23 no + R2TL84BlueStbl24 no + R2TL84BlueStbl25 no + R2TL84BlueStbl26 no + R2TL84BlueStbl27 no + R2TL84BlueStbl28 no + R2TL84BlueStbl29 no + R2TL84BlueStbl30 no + R2TL84BlueStbl31 no + R2TL84Cx no + R2TL84Cy no + R2TL84GreenCtbl00 no + R2TL84GreenCtbl01 no + R2TL84GreenCtbl02 no + R2TL84GreenCtbl03 no + R2TL84GreenCtbl04 no + R2TL84GreenCtbl05 no + R2TL84GreenCtbl06 no + R2TL84GreenCtbl07 no + R2TL84GreenCtbl08 no + R2TL84GreenCtbl09 no + R2TL84GreenCtbl10 no + R2TL84GreenCtbl11 no + R2TL84GreenCtbl12 no + R2TL84GreenCtbl13 no + R2TL84GreenCtbl14 no + R2TL84GreenCtbl15 no + R2TL84GreenCtbl16 no + R2TL84GreenCtbl17 no + R2TL84GreenCtbl18 no + R2TL84GreenCtbl19 no + R2TL84GreenCtbl20 no + R2TL84GreenCtbl21 no + R2TL84GreenCtbl22 no + R2TL84GreenCtbl23 no + R2TL84GreenCtbl24 no + R2TL84GreenCtbl25 no + R2TL84GreenCtbl26 no + R2TL84GreenCtbl27 no + R2TL84GreenCtbl28 no + R2TL84GreenCtbl29 no + R2TL84GreenCtbl30 no + R2TL84GreenCtbl31 no + R2TL84GreenStbl00 no + R2TL84GreenStbl01 no + R2TL84GreenStbl02 no + R2TL84GreenStbl03 no + R2TL84GreenStbl04 no + R2TL84GreenStbl05 no + R2TL84GreenStbl06 no + R2TL84GreenStbl07 no + R2TL84GreenStbl08 no + R2TL84GreenStbl09 no + R2TL84GreenStbl10 no + R2TL84GreenStbl11 no + R2TL84GreenStbl12 no + R2TL84GreenStbl13 no + R2TL84GreenStbl14 no + R2TL84GreenStbl15 no + R2TL84GreenStbl16 no + R2TL84GreenStbl17 no + R2TL84GreenStbl18 no + R2TL84GreenStbl19 no + R2TL84GreenStbl20 no + R2TL84GreenStbl21 no + R2TL84GreenStbl22 no + R2TL84GreenStbl23 no + R2TL84GreenStbl24 no + R2TL84GreenStbl25 no + R2TL84GreenStbl26 no + R2TL84GreenStbl27 no + R2TL84GreenStbl28 no + R2TL84GreenStbl29 no + R2TL84GreenStbl30 no + R2TL84GreenStbl31 no + R2TL84Height no + R2TL84Intervals no + R2TL84RedCtbl00 no + R2TL84RedCtbl01 no + R2TL84RedCtbl02 no + R2TL84RedCtbl03 no + R2TL84RedCtbl04 no + R2TL84RedCtbl05 no + R2TL84RedCtbl06 no + R2TL84RedCtbl07 no + R2TL84RedCtbl08 no + R2TL84RedCtbl09 no + R2TL84RedCtbl10 no + R2TL84RedCtbl11 no + R2TL84RedCtbl12 no + R2TL84RedCtbl13 no + R2TL84RedCtbl14 no + R2TL84RedCtbl15 no + R2TL84RedCtbl16 no + R2TL84RedCtbl17 no + R2TL84RedCtbl18 no + R2TL84RedCtbl19 no + R2TL84RedCtbl20 no + R2TL84RedCtbl21 no + R2TL84RedCtbl22 no + R2TL84RedCtbl23 no + R2TL84RedCtbl24 no + R2TL84RedCtbl25 no + R2TL84RedCtbl26 no + R2TL84RedCtbl27 no + R2TL84RedCtbl28 no + R2TL84RedCtbl29 no + R2TL84RedCtbl30 no + R2TL84RedCtbl31 no + R2TL84RedStbl00 no + R2TL84RedStbl01 no + R2TL84RedStbl02 no + R2TL84RedStbl03 no + R2TL84RedStbl04 no + R2TL84RedStbl05 no + R2TL84RedStbl06 no + R2TL84RedStbl07 no + R2TL84RedStbl08 no + R2TL84RedStbl09 no + R2TL84RedStbl10 no + R2TL84RedStbl11 no + R2TL84RedStbl12 no + R2TL84RedStbl13 no + R2TL84RedStbl14 no + R2TL84RedStbl15 no + R2TL84RedStbl16 no + R2TL84RedStbl17 no + R2TL84RedStbl18 no + R2TL84RedStbl19 no + R2TL84RedStbl20 no + R2TL84RedStbl21 no + R2TL84RedStbl22 no + R2TL84RedStbl23 no + R2TL84RedStbl24 no + R2TL84RedStbl25 no + R2TL84RedStbl26 no + R2TL84RedStbl27 no + R2TL84RedStbl28 no + R2TL84RedStbl29 no + R2TL84RedStbl30 no + R2TL84RedStbl31 no + R2TL84Tbl00 no + R2TL84Tbl01 no + R2TL84Tbl02 no + R2TL84Tbl03 no + R2TL84Tbl04 no + R2TL84Tbl05 no + R2TL84Tbl06 no + R2TL84Tbl07 no + R2TL84Tbl08 no + R2TL84Tbl09 no + R2TL84Tbl10 no + R2TL84Tbl11 no + R2TL84Tbl12 no + R2TL84Tbl13 no + R2TL84Tbl14 no + R2TL84Tbl15 no + R2TL84Tbl16 no + R2TL84Tbl17 no + R2TL84Tbl18 no + R2TL84Tbl19 no + R2TL84Tbl20 no + R2TL84Tbl21 no + R2TL84Tbl22 no + R2TL84Tbl23 no + R2TL84Tbl24 no + R2TL84Tbl25 no + R2TL84Tbl26 no + R2TL84Tbl27 no + R2TL84Tbl28 no + R2TL84Tbl29 no + R2TL84Tbl30 no + R2TL84Tbl31 no + R2TL84Width no + RolloffEnable no + SensorFmt no + SensorType no + SensrFulHght no + SensrFulWdth no + SensrQtrHght no + SensrQtrWdth no + SnapshotResol no + TL84ConvChrmA_M no + TL84ConvChrmA_P no + TL84ConvChrmB_M no + TL84ConvChrmB_P no + TL84ConvChrmC_M no + TL84ConvChrmC_P no + TL84ConvChrmD_M no + TL84ConvChrmD_P no + TL84ConvChrmKCb no + TL84ConvChrmKCr no + TL84ConvLumaK no + TL84ConvLumaV0 no + TL84ConvLumaV1 no + TL84ConvLumaV2 no + VideoFps no + YhiYloConvChrmA_M no + YhiYloConvChrmA_P no + YhiYloConvChrmB_M no + YhiYloConvChrmB_P no + YhiYloConvChrmC_M no + YhiYloConvChrmC_P no + YhiYloConvChrmD_M no + YhiYloConvChrmD_P no + YhiYloConvChrmKCb no + YhiYloConvChrmKCr no + YhiYloConvLumaK no + YhiYloConvLumaV0 no + YhiYloConvLumaV1 no + YhiYloConvLumaV2 no + YhiYloCorC0 no + YhiYloCorC1 no + YhiYloCorC2 no + YhiYloCorC3 no + YhiYloCorC4 no + YhiYloCorC5 no + YhiYloCorC6 no + YhiYloCorC7 no + YhiYloCorC8 no + YhiYloCorK0 no + YhiYloCorK1 no + YhiYloCorK2 no + +=head2 InfiRay Tags + +=head3 InfiRay OpMode Tags + +This table lists tags found in the InfiRay APP7 IJPEG camera operation mode +section. + + Index1 Tag Name Writable + ------ -------- -------- + 0 WorkingMode no + 1 IntegralTime no + 5 IntegratTimeHdr no + 9 GainStable no + 10 TempControlEnable no + 11 DeviceTemp no + +=head3 InfiRay Isothermal Tags + +This table lists tags found in the InfiRay APP8 IJPEG picture isothermal +information. + + Index1 Tag Name Writable + ------ -------- -------- + 0 IsothermalMax no + 4 IsothermalMin no + 8 ChromaBarMax no + 12 ChromaBarMin no + +=head3 InfiRay Sensor Tags + +This table lists tags found in the InfiRay APP9 IJPEG sensor information +chunk. + + Index1 Tag Name Writable + ------ -------- -------- + 0 IRSensorManufacturer no + 64 IRSensorName no + 128 IRSensorPartNumber no + 192 IRSensorSerialNumber no + 256 IRSensorFirmware no + 320 IRSensorAperture no + 324 IRFocalLength no + 384 VisibleSensorManufacturer no + 448 VisibleSensorName no + 512 VisibleSensorPartNumber no + 576 VisibleSensorSerialNumber no + 640 VisibleSensorFirmware no + 704 VisibleSensorAperture no + 708 VisibleFocalLength no + +=head2 Jpeg2000 Tags + +The tags below are found in JPEG 2000 images and the JUMBF metadata in JPEG +images, but not all of these are extracted. Note that ExifTool currently +writes only EXIF, IPTC and XMP tags in Jpeg2000 images, and EXIF and XMP in +JXL images. ExifTool will read/write Brotli-compressed EXIF and XMP in JXL +images, but the API Compress option must be set to create new EXIF and XMP +in compressed format. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Exif' EXIF EXIF + 'asoc' Association Jpeg2000 + 'bfdb' BinaryDataType no + 'bfil' BinaryFilter no + 'bidb' BinaryData no + 'bpcc' BitsPerComponent no + 'brob' BrotliXMP XMP + BrotliEXIF EXIF + BrotliJUMB Jpeg2000 + 'c2sh' C2PASaltHash no + 'cbor' CBORData CBOR + 'cdef' ComponentDefinition no + 'cgrp' ColorGroup no + 'chck' DigitalSignature no + 'cmap' ComponentMapping no + 'colr' ColorSpecification Jpeg2000 ColorSpec + 'comp' Composition no + 'copt' CompositionOptions no + 'cref' Cross-Reference no + 'creg' CodestreamRegistration no + 'drep' DesiredReproductions no + 'dtbl' DataReference no + 'flst' FragmentList no + 'free' Free no + 'ftbl' FragmentTable no + 'ftyp' FileType Jpeg2000 FileType + 'gtso' GraphicsTechnologyStandardOutput no + 'hrgm' GainMapImage no + 'ihdr' ImageHeader Jpeg2000 ImageHeader + 'inst' InstructionSet no + 'jP ' JP2Signature no + 'jp2c' ContiguousCodestream no + PreviewImage no + 'jp2h' JP2Header Jpeg2000 + 'jp2i' IntellectualProperty XMP + 'jpch' CodestreamHeader Jpeg2000 + 'jplh' CompositingLayerHeader Jpeg2000 + 'json' JSONData JSON + 'jumb' JUMBFBox Jpeg2000 + 'jumd' JUMBFDescr Jpeg2000 JUMD + 'jxlc' JXLCodestream no + 'jxlp' PartialJXLCodestream no + 'lbl ' Label no + 'mdat' MediaData no + 'mp7b' MPEG7Binary no + 'nlst' NumberList no + 'opct' Opacity no + 'pclr' Palette no + 'prfl' Profile no + 'res ' Resolution Jpeg2000 + 'resc' CaptureResolution Jpeg2000 CaptureResolution + 'resd' DisplayResolution Jpeg2000 DisplayResolution + 'roid' ROIDescription no + 'rreq' ReaderRequirements no + 'uinf' UUIDInfo Jpeg2000 + 'ulst' UUIDList no + 'url ' URL no + 'uuid' UUID-EXIF EXIF + UUID-EXIF2 EXIF + UUID-EXIF_bad EXIF + UUID-IPTC IPTC + UUID-IPTC2 IPTC + UUID-XMP XMP + UUID-GeoJP2 EXIF + UUID-Photoshop Photoshop + UUID-Signature no + UUID-C2PAClaimSignature - + UUID-Unknown no + 'xml ' XML XMP XML + XMP - + XMP + +=head3 Jpeg2000 ColorSpec Tags + +The table below contains tags in the color specification (colr) box. This +box may be rewritten by writing either ICC_Profile, ColorSpace or +ColorSpecData. When writing, any existing colr boxes are replaced with the +newly created colr box. + +B<NOTE>: Care must be taken when writing this color specification because +writing a specification that is incompatible with the image data may make +the image undisplayable. + + Index1 Tag Name Writable + ------ -------- -------- + 0 ColorSpecMethod int8s! + 1 ColorSpecPrecedence int8s! + 2 ColorSpecApproximation int8s! + 3 ICC_Profile ICC_Profile + ColorSpace int32u! + ColorSpecData undef! + +=head3 Jpeg2000 FileType Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 MajorBrand no + 1 MinorVersion no + 2 CompatibleBrands no + +=head3 Jpeg2000 ImageHeader Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ImageHeight no + 4 ImageWidth no + 8 NumberOfComponents no + 10 BitsPerComponent no + 11 Compression no + +=head3 Jpeg2000 JUMD Tags + +Information extracted from the JUMBF description box. + + Tag ID Tag Name Writable + ------ -------- -------- + 'id' JUMDID no + 'label' JUMDLabel no + 'sig' JUMDSignature no + 'toggles' JUMDToggles? no + 'type' JUMDType no + +=head3 Jpeg2000 CaptureResolution Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 CaptureYResolution no + 4 CaptureXResolution no + 8 CaptureYResolutionUnit no + 9 CaptureXResolutionUnit no + +=head3 Jpeg2000 DisplayResolution Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 DisplayYResolution no + 4 DisplayXResolution no + 8 DisplayYResolutionUnit no + 9 DisplayXResolutionUnit no + +=head2 JSON Tags + +Other than a few tags in the table below, JSON tags have not been +pre-defined. However, ExifTool will read any existing tags from basic +JSON-formatted files. + + Tag Name Writable + -------- -------- + ON1_SettingsData PLIST + ON1_SettingsMetadataCreated no + ON1_SettingsMetadataModified no + ON1_SettingsMetadataName no + ON1_SettingsMetadataPluginID no + ON1_SettingsMetadataTimestamp no + ON1_SettingsMetadataUsage no + ON1_SettingsMetadataVisibleToUser no + +=head2 CBOR Tags + +The tags below are extracted from CBOR (Concise Binary Object +Representation) metadata. The C2PA specification uses this format for some +metadata. As well as these tags, ExifTool will read any existing tags. + + Tag Name Writable + -------- -------- + AuthorIdentifier no + AuthorName no + DocumentID no + Format no + InstanceID no + Relationship no + ThumbnailHash no+ + ThumbnailURL no + Title no + +=head2 PLIST Tags + +Apple Property List tags. ExifTool reads both XML and binary-format PLIST +files, and will extract any existing tags even if they aren't listed below. +These tags belong to the family 0 "PLIST" group, but family 1 group may be +either "XML" or "PLIST" depending on whether the format is XML or binary. + + Tag ID Tag Name Writable + ------ -------- -------- + 'MetaDataList//DateTimeOriginal' + DateTimeOriginal no + 'MetaDataList//Duration' Duration no + 'MetaDataList//Geolocation/Latitude' + GPSLatitude no + 'MetaDataList//Geolocation/Longitude' + GPSLongitude no + 'MetaDataList//Geolocation/MapDatum' + GPSMapDatum no + 'XMLFileType' XMLFileType no + 'cast//name' Cast no+ + 'codirectors//name' Codirectors no+ + 'directors//name' Directors no+ + 'producers//name' Producers no+ + 'screenwriters//name' Screenwriters no+ + 'studio//name' Studio no+ + +=head2 APP12 Tags + +=head3 APP12 PictureInfo Tags + +The JPEG APP12 "Picture Info" segment was used by some older cameras, and +contains ASCII-based meta information. Below are some tags which have been +observed Agfa and Polaroid images, however ExifTool will extract information +from any tags found in this segment. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Aperture' Aperture no + 'ColorMode' ColorMode no + 'ConTake' ConTake no + 'ExpBias' ExposureCompensation no + 'FNumber' FNumber no + 'FWare' FirmwareVersion no + 'Flash' Flash no + 'FocusMode' FocusMode no + 'FocusPos' FocusPos no + 'ID' ID no + 'ImageSize' ImageSize no + 'LightS' LightS no + 'Macro' Macro no + 'Protect' Protect no + 'Quality' Quality no + 'Resolution' Resolution no + 'Serial#' SerialNumber no + 'Shutter' ExposureTime no + 'StrobeTime' StrobeTime no + 'TimeDate' DateTimeOriginal no + 'Type' CameraType no + 'Version' Version no + 'Ytarget' YTarget no + 'Zoom' Zoom no + 'ZoomPos' ZoomPos no + 'shtr' ExposureTime no + 'ylevel' YLevel no + +=head3 APP12 Ducky Tags + +Photoshop uses the JPEG APP12 "Ducky" segment to store some information in +"Save for Web" images. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 Quality int32u/ + 0x0002 Comment string/ + 0x0003 Copyright string/ + +=head2 AFCP Tags + +AFCP stands for AXS File Concatenation Protocol, and is a poorly designed +protocol for appending information to the end of files. This can be used as +an auxiliary technique to store IPTC information in images, but is +incompatible with some file formats. + +ExifTool will read and write (but not create) AFCP IPTC information in JPEG +and TIFF images. + +See +L<http://web.archive.org/web/20080828211305/http://www.tocarte.com/media/axs_afcp_spec.pdf> +for the AFCP specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'IPTC' IPTC IPTC + 'Nail' ThumbnailImage no + 'PrVw' PreviewImage no + 'TEXT' Text no + +=head2 DarwinCore Tags + +Tags defined in the Darwin Core (dwc) XMP namespace. See +L<http://rs.tdwg.org/dwc/index.htm> for the official specification. + +These tags belong to the ExifTool XMP-dwc family 1 group. + + Tag Name Writable + -------- -------- + DCTermsLocation DarwinCore DCTermsLocation Struct + DCContinent string_ + DCCoordinatePrecision string_ + DCCoordinateUncertaintyInMeters string_ + DCCountry string_ + DCCountryCode string_ + DCCounty string_ + DCDecimalLatitude string_ + DCDecimalLongitude string_ + DCFootprintSpatialFit string_ + DCFootprintSRS string_ + DCFootprintWKT string_ + DCGeodeticDatum string_ + DCGeoreferencedBy string_ + DCGeoreferencedDate string_ + DCGeoreferenceProtocol string_ + DCGeoreferenceRemarks string_ + DCGeoreferenceSources string_ + DCGeoreferenceVerificationStatus string_ + DCHigherGeography string_ + DCHigherGeographyID string_ + DCIsland string_ + DCIslandGroup string_ + DCLocality string_ + DCLocationAccordingTo string_ + DCLocationID string_ + DCLocationRemarks string_ + DCMaximumDepthInMeters string_ + DCMaximumDistanceAboveSurfaceInMeters string_ + DCMaximumElevationInMeters string_ + DCMinimumDepthInMeters string_ + DCMinimumDistanceAboveSurfaceInMeters string_ + DCMinimumElevationInMeters string_ + DCMunicipality string_ + DCPointRadiusSpatialFit string_ + DCStateProvince string_ + DCVerbatimCoordinates string_ + DCVerbatimCoordinateSystem string_ + DCVerbatimDepth string_ + DCVerbatimElevation string_ + DCVerbatimLatitude string_ + DCVerbatimLocality string_ + DCVerbatimLongitude string_ + DCVerbatimSRS string_ + DCVerticalDatum string_ + DCWaterBody string_ + DCEvent DarwinCore Event Struct + EventDay integer_ + EventEarliestDate date_ + EventEndDayOfYear integer_ + EventDate date_ + EventID string/_ + EventRemarks lang-alt_ + EventTime string_ + EventFieldNotes string_ + EventFieldNumber string_ + EventHabitat string_ + EventLatestDate date_ + EventMonth integer_ + EventParentEventID string_ + EventSampleSizeUnit string_ + EventSampleSizeValue string_ + EventSamplingEffort string_ + EventSamplingProtocol string_ + EventStartDayOfYear integer_ + EventVerbatimEventDate string_ + EventYear integer_ + FossilSpecimen DarwinCore MaterialSample Struct + FossilSpecimenMaterialSampleID string_ + GeologicalContext DarwinCore GeologicalContext Struct + GeologicalContextBed string_ + EarliestAgeOrLowestStage string_ + EarliestEonOrLowestEonothem string_ + EarliestEpochOrLowestSeries string_ + EarliestEraOrLowestErathem string_ + EarliestPeriodOrLowestSystem string_ + GeologicalContextFormation string_ + GeologicalContextID string_ + GeologicalContextGroup string_ + HighestBiostratigraphicZone string_ + LatestAgeOrHighestStage string_ + LatestEonOrHighestEonothem string_ + LatestEpochOrHighestSeries string_ + LatestEraOrHighestErathem string_ + LatestPeriodOrHighestSystem string_ + LithostratigraphicTerms string_ + LowestBiostratigraphicZone string_ + GeologicalContextMember string_ + HumanObservation DarwinCore Event Struct + HumanObservationDay integer_ + HumanObservationEarliestDate date_ + HumanObservationEndDayOfYear integer_ + HumanObservationEventDate date_ + HumanObservationEventID string/_ + HumanObservationEventRemarks lang-alt_ + HumanObservationEventTime string_ + HumanObservationFieldNotes string_ + HumanObservationFieldNumber string_ + HumanObservationHabitat string_ + HumanObservationLatestDate date_ + HumanObservationMonth integer_ + HumanObservationParentEventID string_ + HumanObservationSampleSizeUnit string_ + HumanObservationSampleSizeValue string_ + HumanObservationSamplingEffort string_ + HumanObservationSamplingProtocol string_ + HumanObservationStartDayOfYear integer_ + HumanObservationVerbatimEventDate string_ + HumanObservationYear integer_ + Identification DarwinCore Identification Struct + DateIdentified date_ + IdentificationID string_ + IdentificationQualifier string_ + IdentificationReferences string_ + IdentificationRemarks string_ + IdentificationVerificationStatus string_ + IdentifiedBy string_ + IdentifiedByID string_ + TypeStatus string_ + VerbatimIdentification string_ + LivingSpecimen DarwinCore MaterialSample Struct + LivingSpecimenMaterialSampleID string_ + MachineObservation DarwinCore Event Struct + MachineObservationDay integer_ + MachineObservationEarliestDate date_ + MachineObservationEndDayOfYear integer_ + MachineObservationEventDate date_ + MachineObservationEventID string/_ + MachineObservationEventRemarks lang-alt_ + MachineObservationEventTime string_ + MachineObservationFieldNotes string_ + MachineObservationFieldNumber string_ + MachineObservationHabitat string_ + MachineObservationLatestDate date_ + MachineObservationMonth integer_ + MachineObservationParentEventID string_ + MachineObservationSampleSizeUnit string_ + MachineObservationSampleSizeValue string_ + MachineObservationSamplingEffort string_ + MachineObservationSamplingProtocol string_ + MachineObservationStartDayOfYear integer_ + MachineObservationVerbatimEventDate string_ + MachineObservationYear integer_ + MaterialSample DarwinCore MaterialSample Struct + MaterialSampleID string_ + MeasurementOrFact DarwinCore MeasurementOrFact Struct + MeasurementAccuracy string_ + MeasurementDeterminedBy string_ + MeasurementDeterminedDate date_ + MeasurementID string_ + MeasurementMethod string_ + MeasurementRemarks string_ + MeasurementType string_ + MeasurementUnit string_ + MeasurementValue string_ + Occurrence DarwinCore Occurrence Struct + OccurrenceAssociatedMedia string_ + OccurrenceAssociatedOccurrences string_ + OccurrenceAssociatedReferences string_ + OccurrenceAssociatedSequences string_ + OccurrenceAssociatedTaxa string_ + OccurrenceBehavior string_ + OccurrenceCatalogNumber string_ + OccurrenceDegreeOfEstablishment string_ + OccurrenceDisposition string_ + OccurrenceEstablishmentMeans string_ + OccurrenceGeoreferenceVerificationStatus string_ + OccurrenceIndividualCount string_ + OccurrenceIndividualID string_ + OccurrenceLifeStage string_ + OccurrenceDetails string_ + OccurrenceID string_ + OccurrenceRemarks string_ + OccurrenceStatus string_ + OccurrenceOrganismQuantity string_ + OccurrenceOrganismQuantityType string_ + OccurrenceOtherCatalogNumbers string_ + OccurrencePathway string_ + OccurrencePreparations string_ + OccurrencePreviousIdentifications string_ + OccurrenceRecordedBy string_ + OccurrenceRecordedByID string_ + OccurrenceRecordNumber string_ + OccurrenceReproductiveCondition string_ + OccurrenceSex string_ + Organism DarwinCore Organism Struct + OrganismAssociatedOccurrences string_ + OrganismAssociatedOrganisms string_ + OrganismID string_ + OrganismName string_ + OrganismRemarks string_ + OrganismScope string_ + OrganismPreviousIdentifications string_ + PreservedSpecimen DarwinCore MaterialSample Struct + PreservedSpecimenMaterialSampleID string_ + Record DarwinCore Record Struct + RecordBasisOfRecord string_ + RecordCollectionCode string_ + RecordCollectionID string_ + RecordDataGeneralizations string_ + RecordDatasetID string_ + RecordDatasetName string_ + RecordDynamicProperties string_ + RecordInformationWithheld string_ + RecordInstitutionCode string_ + RecordInstitutionID string_ + RecordOwnerInstitutionCode string_ + ResourceRelationship DarwinCore ResourceRelationship Struct + RelatedResourceID string_ + RelationshipAccordingTo string_ + RelationshipEstablishedDate date_ + RelationshipOfResource string_ + RelationshipOfResourceID string_ + RelationshipRemarks string_ + ResourceID string_ + ResourceRelationshipID string_ + Taxon DarwinCore Taxon Struct + TaxonAcceptedNameUsage string_ + TaxonAcceptedNameUsageID string_ + TaxonClass string_ + TaxonCultivarEpithet string_ + TaxonFamily string_ + TaxonGenus string_ + TaxonHigherClassification string_ + TaxonInfraspecificEpithet string_ + TaxonKingdom string_ + TaxonNameAccordingTo string_ + TaxonNameAccordingToID string_ + TaxonNamePublishedIn string_ + TaxonNamePublishedInID string_ + TaxonNamePublishedInYear string_ + TaxonNomenclaturalCode string_ + TaxonNomenclaturalStatus string_ + TaxonOrder string_ + TaxonOriginalNameUsage string_ + TaxonOriginalNameUsageID string_ + TaxonParentNameUsage string_ + TaxonParentNameUsageID string_ + TaxonPhylum string_ + TaxonScientificName string_ + TaxonScientificNameAuthorship string_ + TaxonScientificNameID string_ + TaxonSpecificEpithet string_ + TaxonSubgenus string_ + TaxonConceptID string_ + TaxonID string_ + TaxonTaxonomicStatus string_ + TaxonRank string_ + TaxonRemarks string_ + TaxonVerbatimTaxonRank string_ + TaxonVernacularName lang-alt_ + +=head3 DarwinCore DCTermsLocation Struct + + Field Name Writable + ---------- -------- + Continent string + CoordinatePrecision string + CoordinateUncertaintyInMeters string + Country string + CountryCode string + County string + DecimalLatitude string + DecimalLongitude string + FootprintSRS string + FootprintSpatialFit string + FootprintWKT string + GeodeticDatum string + GeoreferenceProtocol string + GeoreferenceRemarks string + GeoreferenceSources string + GeoreferenceVerificationStatus string + GeoreferencedBy string + GeoreferencedDate string + HigherGeography string + HigherGeographyID string + Island string + IslandGroup string + Locality string + LocationAccordingTo string + LocationID string + LocationRemarks string + MaximumDepthInMeters string + MaximumDistanceAboveSurfaceInMeters string + MaximumElevationInMeters string + MinimumDepthInMeters string + MinimumDistanceAboveSurfaceInMeters string + MinimumElevationInMeters string + Municipality string + PointRadiusSpatialFit string + StateProvince string + VerbatimCoordinateSystem string + VerbatimCoordinates string + VerbatimDepth string + VerbatimElevation string + VerbatimLatitude string + VerbatimLocality string + VerbatimLongitude string + VerbatimSRS string + VerticalDatum string + WaterBody string + +=head3 DarwinCore Event Struct + + Field Name Writable + ---------- -------- + Day integer + EarliestDate date + EndDayOfYear integer + EventDate date + EventID string + EventRemarks lang-alt + EventTime string + FieldNotes string + FieldNumber string + Habitat string + LatestDate date + Month integer + ParentEventID string + SampleSizeUnit string + SampleSizeValue string + SamplingEffort string + SamplingProtocol string + StartDayOfYear integer + VerbatimEventDate string + Year integer + +=head3 DarwinCore MaterialSample Struct + + Field Name Writable + ---------- -------- + MaterialSampleID string + +=head3 DarwinCore GeologicalContext Struct + + Field Name Writable + ---------- -------- + Bed string + EarliestAgeOrLowestStage string + EarliestEonOrLowestEonothem string + EarliestEpochOrLowestSeries string + EarliestEraOrLowestErathem string + EarliestPeriodOrLowestSystem string + Formation string + GeologicalContextID string + Group string + HighestBiostratigraphicZone string + LatestAgeOrHighestStage string + LatestEonOrHighestEonothem string + LatestEpochOrHighestSeries string + LatestEraOrHighestErathem string + LatestPeriodOrHighestSystem string + LithostratigraphicTerms string + LowestBiostratigraphicZone string + Member string + +=head3 DarwinCore Identification Struct + + Field Name Writable + ---------- -------- + DateIdentified date + IdentificationID string + IdentificationQualifier string + IdentificationReferences string + IdentificationRemarks string + IdentificationVerificationStatus string + IdentifiedBy string + IdentifiedByID string + TypeStatus string + VerbatimIdentification string + +=head3 DarwinCore MeasurementOrFact Struct + + Field Name Writable + ---------- -------- + MeasurementAccuracy string + MeasurementDeterminedBy string + MeasurementDeterminedDate date + MeasurementID string + MeasurementMethod string + MeasurementRemarks string + MeasurementType string + MeasurementUnit string + MeasurementValue string + +=head3 DarwinCore Occurrence Struct + + Field Name Writable + ---------- -------- + AssociatedMedia string + AssociatedOccurrences string + AssociatedReferences string + AssociatedSequences string + AssociatedTaxa string + Behavior string + CatalogNumber string + DegreeOfEstablishment string + Disposition string + EstablishmentMeans string + GeoreferenceVerificationStatus string + IndividualCount string + IndividualID string + LifeStage string + OccurrenceDetails string + OccurrenceID string + OccurrenceRemarks string + OccurrenceStatus string + OrganismQuantity string + OrganismQuantityType string + OtherCatalogNumbers string + Pathway string + Preparations string + PreviousIdentifications string + RecordNumber string + RecordedBy string + RecordedByID string + ReproductiveCondition string + Sex string + +=head3 DarwinCore Organism Struct + + Field Name Writable + ---------- -------- + AssociatedOccurrences string + AssociatedOrganisms string + OrganismID string + OrganismName string + OrganismRemarks string + OrganismScope string + PreviousIdentifications string + +=head3 DarwinCore Record Struct + + Field Name Writable + ---------- -------- + BasisOfRecord string + CollectionCode string + CollectionID string + DataGeneralizations string + DatasetID string + DatasetName string + DynamicProperties string + InformationWithheld string + InstitutionCode string + InstitutionID string + OwnerInstitutionCode string + +=head3 DarwinCore ResourceRelationship Struct + + Field Name Writable + ---------- -------- + RelatedResourceID string + RelationshipAccordingTo string + RelationshipEstablishedDate date + RelationshipOfResource string + RelationshipOfResourceID string + RelationshipRemarks string + ResourceID string + ResourceRelationshipID string + +=head3 DarwinCore Taxon Struct + + Field Name Writable + ---------- -------- + AcceptedNameUsage string + AcceptedNameUsageID string + Class string + CultivarEpithet string + Family string + Genus string + HigherClassification string + InfraspecificEpithet string + Kingdom string + NameAccordingTo string + NameAccordingToID string + NamePublishedIn string + NamePublishedInID string + NamePublishedInYear string + NomenclaturalCode string + NomenclaturalStatus string + Order string + OriginalNameUsage string + OriginalNameUsageID string + ParentNameUsage string + ParentNameUsageID string + Phylum string + ScientificName string + ScientificNameAuthorship string + ScientificNameID string + SpecificEpithet string + Subgenus string + TaxonConceptID string + TaxonID string + TaxonRank string + TaxonRemarks string + TaxonomicStatus string + VerbatimTaxonRank string + VernacularName lang-alt + +=head2 FotoStation Tags + +The following tables define information found in the FotoWare FotoStation +trailer. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 IPTC IPTC + 0x0002 SoftEdit FotoStation SoftEdit + 0x0003 ThumbnailImage yes + 0x0004 PreviewImage yes + +=head3 FotoStation SoftEdit Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 OriginalImageWidth int32s + 1 OriginalImageHeight int32s + 2 ColorPlanes int32s + 3 XYResolution int32s + 4 Rotation int32s + 6 CropLeft int32s + 7 CropTop int32s + 8 CropRight int32s + 9 CropBottom int32s + 11 CropRotation int32s + +=head2 PhotoMechanic Tags + +The Photo Mechanic trailer contains data in an IPTC-format structure, with +soft edit information stored under record number 2. + + Record Tag Name Writable + ------ -------- -------- + 2 SoftEdit PhotoMechanic SoftEdit + +=head3 PhotoMechanic SoftEdit Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 209 RawCropLeft int32s + 210 RawCropTop int32s + 211 RawCropRight int32s + 212 RawCropBottom int32s + 213 ConstrainedCropWidth int32s + 214 ConstrainedCropHeight int32s + 215 FrameNum int32s + 216 Rotation int32s + 217 CropLeft int32s + 218 CropTop int32s + 219 CropRight int32s + 220 CropBottom int32s + 221 Tagged int32s + 222 ColorClass int32s + 223 Rating int32s + 236 PreviewCropLeft int32s + 237 PreviewCropTop int32s + 238 PreviewCropRight int32s + 239 PreviewCropBottom int32s + +=head3 PhotoMechanic XMP Tags + +Below is a list of the observed PhotoMechanic XMP tags. The actual +namespace prefix is "photomechanic" but ExifTool shortens this in +the family 1 group name. + +These tags belong to the ExifTool XMP-photomech family 1 group. + + Tag Name Writable + -------- -------- + ColorClass integer + CountryCode string/ + EditStatus string + PMVersion string + Prefs string + Tagged boolean + TimeCreated string/ + +=head2 Microsoft Tags + +=head3 Microsoft XMP Tags + +Microsoft Photo 1.0 schema XMP tags. This is likely not a complete list, +but represents tags which have been observed in sample images. The actual +namespace prefix is "MicrosoftPhoto", but ExifTool shortens this in the +family 1 group name. + +These tags belong to the ExifTool XMP-microsoft family 1 group. + + Tag Name Writable + -------- -------- + CameraSerialNumber string + CreatorAppID string + CreatorOpenWithUIOptions string + DateAcquired date + FlashManufacturer string + FlashModel string + ItemSubType string + LastKeywordIPTC string+ + LastKeywordXMP string+ + LensManufacturer string + LensModel string/ + RatingPercent string + +=head3 Microsoft MP1 Tags + +Microsoft Photo 1.1 schema XMP tags which have been observed. + +These tags belong to the ExifTool XMP-MP1 family 1 group. + + Tag Name Writable + -------- -------- + Brightness string/ + CameraModelID string/ + Contrast string/ + ExposureCompensation string/ + PanoramicStitchCameraMotion string + PanoramicStitchMapType string + PanoramicStitchPhi0 real + PanoramicStitchPhi1 real + PanoramicStitchTheta0 real + PanoramicStitchTheta1 real + PipelineVersion string + StreamType string + WhiteBalance0 real + WhiteBalance1 real + WhiteBalance2 real + +=head3 Microsoft MP Tags + +Microsoft Photo 1.2 schema XMP tags which have been observed. + +These tags belong to the ExifTool XMP-MP family 1 group. + + Tag Name Writable + -------- -------- + RegionInfoDateRegionsValid date_ + RegionInfoMP Microsoft RegionInfo Struct + RegionInfoRegions Microsoft Regions Struct_+ + RegionPersonDisplayName string_+ + RegionPersonEmailDigest string_+ + RegionPersonLiveIdCID string_+ + RegionPersonSourceID string_+ + RegionRectangle string_+ + +=head3 Microsoft RegionInfo Struct + + Field Name Writable + ---------- -------- + DateRegionsValid date + Regions Microsoft Regions Struct+ + +=head3 Microsoft Regions Struct + +Note that PersonLiveIdCID element is called PersonLiveCID according to the +Microsoft specification, but in practice their software actually writes +PersonLiveIdCID, so ExifTool uses this too. + + Field Name Writable + ---------- -------- + PersonDisplayName string + PersonEmailDigest string + PersonLiveIdCID string + PersonSourceID string + Rectangle string + +=head3 Microsoft Stitch Tags + +Information found in the Microsoft custom EXIF tag 0x4748, as written by +Windows Live Photo Gallery. + + Index4 Tag Name Writable + ------ -------- -------- + 0 PanoramicStitchVersion int32u + 1 PanoramicStitchCameraMotion int32u + 2 PanoramicStitchMapType int32u + 3 PanoramicStitchTheta0 float + 4 PanoramicStitchTheta1 float + 5 PanoramicStitchPhi0 float + 6 PanoramicStitchPhi1 float + +=head3 Microsoft Xtra Tags + +Tags found in the Microsoft "Xtra" atom of QuickTime videos. Tag ID's are +not shown because some are unruly GUID's. Currently most of these tags are +not writable because the Microsoft documentation is poor and samples were +not available, but more tags may be made writable in the future if samples +are provided. Note that writable tags in this table are are flagged to +"Avoid", which means that other more common tags will be written instead if +possible unless the Microsoft group is specified explicitly. + + Tag Name Writable + -------- -------- + Abstract no + AccountName no + AcquisitionTime no + AcquisitionTimeDay no + AcquisitionTimeMonth no + AcquisitionTimeYear no + AcquisitionTimeYearMonth no + AcquisitionTimeYearMonthDay no + AlbumArtist Unicode/ + AlbumArtist no + AlbumArtistSortOrder no + AlbumCoverURL Unicode/ + AlbumID no + AlbumIDAlbumArtist no + AlbumTitle Unicode/ + AlbumTitle no + AlbumTitleSortOrder no + AlternateSourceURL no + Anniversary no + Artist no + AssistantsName no + AssistantsPhone no + Attachments no + Attributes no + AudioBitrate no + AudioFormat no + AudioSampleRate no + AudioSampleSize no + Author no + Author no + AuthorSortOrder no + AuthorURL Unicode/ + AutoSummary no + AverageLevel no + BccAddresses no + BccNames no + Beats-per-minute no + BeatsPerMinute no + BillingInformation no + Birthday no + BitDepth no + Bitrate no + Bitrate no + BroadcastDate no + BusinessAddress no + BusinessCity no + BusinessCountry-Region no + BusinessFax no + BusinessHomePage no + BusinessPOBox no + BusinessPhone no + BusinessPostalCode no + BusinessStateOrProvince no + BusinessStreet no + BuyNow no + BuyTickets no + CDTrackEnabled no + CallLetters no + CallbackNumber no + CameraMaker no + CameraManufacturer no + CameraModel no + CameraModel no + CarPhone no + Category Unicode/+ + Category no + CcAddresses no + CcNames no + CellPhone no + ChannelNumber no + Channels no + Channels no + ChapterNum no + Children no + City no + ClientID no + ClosedCaptioning no + Color no + Comment no + Comments no + Company no + CompanyMainPhone no + Complete no + Composer Unicode/ + Composers no + Computer no + Conductor Unicode/+ + Conductors no + ContactNames no + ContentDistributor Unicode/ + ContentDistributorDuration no + ContentDistributorType no + ContentGroupDescription no + ContentType no + Contributors no + ConversationID no + Copyright no + Copyright no + Count no + Country-Region no + Creator no + CurrentBitrate no + DLNAServerUDN no + DLNASourceURI no + DRMIndividualizedVersion no + DRMKeyID no + DTCPIPHost no + DTCPIPPort no + DVDID no + DataRate no + Date no + DateAccessed no + DateAcquired vt_filetime/ + DateArchived no + DateCompleted no + DateCreated no + DateImported no + DateLastSaved no + DateModified no + DatePictureTaken no + DateReceived no + DateReleased no + DateSent no + DateVisited no + Department no + Description no + Description no + Description no + Dimensions no + Director Unicode/+ + Directors no + DisplayArtist no + Division no + DocumentID no + DueDate no + Duration no + Duration no + Duration no + E-mail2 no + E-mail3 no + E-mailAddress no + E-mailDisplayName no + E-mailList no + EncodedBy Unicode/ + EncodedBy no + EncodingTime date/ + EndDate no + EntryType no + EpisodeName no + Event no + Event no + ExifVersion no + ExposureBias no + ExposureProgram no + ExposureTime no + F-stop no + FileAs no + FileCount no + FileSize no + FileType no + FileVersion no + FirstName no + FlagColor no + FlagStatus no + FlashMode no + FocalLength no + FocalLength35mm no + Folder no + FolderName no + FolderPath no + FormatTag no + FourCC no + FrameHeight no + FrameRate no + FrameRate no + FrameWidth no + Free-busyStatus no + Frequency no + FromAddresses no + FromNames no + FullName no + Gender no + Genre no + Genre no + GenreID no + GivenName no + HasAttachments no + HasFlag no + Hobbies no + HomeAddress no + HomeCity no + HomeCountry-Region no + HomeFax no + HomePOBox no + HomePhone no + HomePostalCode no + HomeStateOrProvince no + HomeStreet no + HorizontalResolution no + IMAddresses no + ISOSpeed no + Importance no + Incomplete no + InitialKey Unicode/ + InitialKey no + Initials no + IsAttachment no + IsCompleted no + IsDeleted no + IsNetworkFeed no + IsOnline no + IsProtected no + IsRecurring no + IsVBR no + JobTitle no + Keywords no + Kinds no + Label no + Language no + Language no + LastName no + LastPrinted no + LeadPerformer no + LegalTrademarks no + LensMaker no + LensModel no + LibraryID no + LibraryName no + LightSource no + LinkStatus no + LinkTarget no + Location no + Location no + Lyrics no + MCDI no + MailingAddress no + MaxAperture no + MediaClassPrimaryID GUID/ + MediaClassSecondaryID GUID/ + MediaContentTypes no + MediaCreated no + MediaOriginalBroadcastDateTime no + MediaOriginalChannel no + MediaStationName no + MediaType no + MeteringMode no + MiddleName no + Mileage no + ModifiedBy no + Mood Unicode/ + Mood no + MoreInfo no + Name no + Nickname no + OfficeLocation no + OfflineAvailability no + OfflineStatus no + OptionalAttendeeAddresses no + OptionalAttendees no + OrganizerAddress no + OrganizerName no + Orientation no + OriginalAlbumTitle Unicode/ + OriginalArtist Unicode/ + OriginalLyricist Unicode/ + OtherAddress no + OtherCity no + OtherCountry-Region no + OtherPOBox no + OtherPostalCode no + OtherStateOrProvince no + OtherStreet no + Owner no + POBox no + Pager no + Pages no + ParentalRating Unicode/ + ParentalRating no + ParentalRatingReason no + PartOfSet no + PartOfSet no + PartOfSet no + Participants no + Path no + PeakValue no + PerceivedType no + Period Unicode/ + Period no + PersonalTitle no + PixelAspectRatioX no + PixelAspectRatioY no + PlaylistIndex no + PostalCode no + PrimaryE-mail no + PrimaryPhone no + Priority no + Producer Unicode/+ + Producers no + ProductName no + ProductVersion no + Profession no + ProgramDescription no + ProgramMode no + ProgramName no + Project no + PromotionURL Unicode/ + Protected no + ProtectionType no + Provider no + Provider Unicode/ + ProviderLogoURL no + ProviderRating no + ProviderStyle no + ProviderURL no + Publisher Unicode/ + Publisher no + RadioBand no + RadioFormat no + Rating no + RatingOrg no + ReadStatus no + RecordingTime no + RecordingTime no + RecordingTimeDay no + RecordingTimeMonth no + RecordingTimeYear no + RecordingTimeYearMonth no + RecordingTimeYearMonthDay no + ReleaseDate no + ReleaseDateDay no + ReleaseDateMonth no + ReleaseDateYear no + ReleaseDateYearMonth no + ReleaseDateYearMonthDay no + ReminderTime no + RequestState no + RequiredAttendeeAddresses no + RequiredAttendees no + Rerun no + Resources no + SAP no + Saturation no + SearchRanking no + SenderAddress no + SenderName no + Sensitivity no + ShadowFilePath no + Shared no + SharedUserRating int64u/ + SharedWith no + Size no + Slides no + Source no + SourceURL no + SpaceFree no + SpaceUsed no + Spouse no + StartDate no + StateOrProvince no + StationCallSign no + StationName no + Status no + Status no + Store no + Street no + Subject no + Subject no + SubjectDistance no + SubscriptionContentID no + Subtitle Unicode/ + Subtitle no + SubtitleDescription no + Suffix no + Summary no + Sync01 no + Sync02 no + Sync03 no + Sync04 no + Sync05 no + Sync06 no + Sync07 no + Sync08 no + Sync09 no + Sync10 no + Sync11 no + Sync12 no + Sync13 no + Sync14 no + Sync15 no + Sync16 no + SyncOnly no + SyncState no + TTY-TTDPhone no + TaskOwner no + Telex no + Temporary no + Title no + Title no + TitleNum no + TitleSortOrder no + ToAddresses no + ToDoTitle no + ToNames no + TotalBitrate no + TotalDuration no + TotalEditingTime no + TotalFileSize no + TotalSize no + TrackNumber no + TrackNumber no + TrackingID no + Type no + Type no + URL no + UniqueFileIdentifier no + Untitled0 no + Untitled1 no + Untitled2 no + UserCustom1 no + UserCustom2 no + UserEffectiveRating no + UserLastPlayedTime no + UserPlayCount no + UserPlaycountAfternoon no + UserPlaycountEvening no + UserPlaycountMorning no + UserPlaycountNight no + UserPlaycountWeekday no + UserPlaycountWeekend no + UserRating no + UserServiceRating no + UserWebURL no + VerticalResolution no + VideoBitrate no + VideoCompression no + VideoFormat no + VideoFrameRate no + VideoHeight no + VideoWidth no + WMCollectionGroupID no + WMCollectionID no + WMContentID no + WMShadowFileSourceDRMType no + WMShadowFileSourceFileType no + Webpage no + WhiteBalance no + WindowsFileName no + WordCount no + Writer Unicode/ + Writers no + Year no + Year no + +=head2 GIMP Tags + +The GNU Image Manipulation Program (GIMP) writes these tags in its native +XCF (eXperimental Computing Facilty) images. + + Tag ID Tag Name Writable + ------ -------- -------- + 'header' Header GIMP Header + 0x0011 Compression no + 0x0013 Resolution GIMP Resolution + 0x0014 Tattoo no + 0x0015 Parasites GIMP Parasite + 0x0016 Units no + +=head3 GIMP Header Tags + + Index1 Tag Name Writable + ------ -------- -------- + 9 XCFVersion no + 14 ImageWidth no + 18 ImageHeight no + 22 ColorMode no + +=head3 GIMP Resolution Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 XResolution no + 1 YResolution no + +=head3 GIMP Parasite Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'exif-data' ExifData EXIF + 'gimp-comment' Comment no + 'gimp-image-metadata' XML XMP XML + 'gimp-metadata' XMP XMP + 'icc-profile' ICC_Profile ICC_Profile + 'icc-profile-name' ICCProfileName no + 'iptc-data' IPTCData IPTC + 'jpeg-exif-data' JPEGExifData EXIF + +=head2 MIE Tags + +MIE is a flexible format which may be used as a stand-alone meta information +format, for encapsulation of other files and information, or as a trailer +appended to other file formats. The tables below represent currently +defined MIE tags, however ExifTool will also extract any other information +present in a MIE file. + +When writing MIE information, some special features are supported: + +1) String values may be written as ASCII (ISO 8859-1) or UTF-8. ExifTool +automatically detects the presence of wide characters and treats the string +appropriately. Internally, UTF-8 text may be converted to UTF-16 or UTF-32 +and stored in this format in the file if it is more compact. + +2) All MIE string-value tags support localized text. Localized values are +written by adding a language/country code to the tag name in the form +C<TAG-xx_YY>, where C<TAG> is the tag name, C<xx> is a 2-character lower +case ISO 639-1 language code, and C<YY> is a 2-character upper case ISO +3166-1 alpha 2 country code (eg. C<Title-en_US>). But as usual, the user +interface is case-insensitive, and ExifTool will write the correct case to +the file. + +3) Some numerical MIE tags allow units of measurement to be specified. For +these tags, units may be added in brackets immediately following the value +(eg. C<55(mi/h)>). If no units are specified, the default units are +written. + +4) ExifTool writes compressed metadata to MIE files if the Compress (-z) +option is used and Compress::Zlib is available. + +See L<https://exiftool.org/MIE1.1-20070121.pdf> for the official MIE +specification. + + Tag ID Tag Name Writable + ------ -------- -------- + '0Type' SubfileType string + '0Vers' MIEVersion string + '1Directory' SubfileDirectory string + '1Name' SubfileName string + '2MIME' SubfileMIMEType string + 'Meta' Meta MIE Meta + 'data' SubfileData undef + 'rsrc' SubfileResource undef + 'zmd5' MD5Digest string + 'zmie' TrailerSignature undef + +=head3 MIE Meta Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Audio' Audio MIE Audio + 'Camera' Camera MIE Camera + 'Document' Document MIE Doc + 'EXIF' EXIF EXIF + 'Geo' Geo MIE Geo + 'ICCProfile' ICC_Profile ICC_Profile + 'ID3' ID3 ID3 + 'IPTC' IPTC IPTC + 'Image' Image MIE Image + 'MakerNotes' MakerNotes MIE MakerNotes + 'Preview' Preview MIE Preview + 'Thumbnail' Thumbnail MIE Thumbnail + 'Video' Video MIE Video + 'XMP' XMP XMP + +=head3 MIE Audio Tags + +For the Audio group (and any other group containing a 'data' element), tags +refer to the contained data if present, otherwise they refer to the main +SubfileData. The C<0Type> and C<1Name> elements should exist only if C<data> +is present. + + Tag ID Tag Name Writable + ------ -------- -------- + '0Type' RelatedAudioFileType string + '1Name' RelatedAudioFileName string + 'Channels' Channels int8u + 'Compression' AudioCompression string + 'Duration' Duration rational64u~ + 'SampleBits' SampleBits int16u + 'SampleRate' SampleRate int32u + 'data' RelatedAudioFile undef + +=head3 MIE Camera Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Brightness' Brightness int8s + 'ColorBalance' ColorBalance rational64u[3] + 'ColorTemperature' ColorTemperature int32u + 'Contrast' Contrast int8s + 'DigitalZoom' DigitalZoom rational64u + 'ExposureComp' ExposureCompensation rational64s + 'ExposureMode' ExposureMode string + 'ExposureTime' ExposureTime rational64u + 'FirmwareVersion' FirmwareVersion string + 'Flash' Flash MIE Flash + 'FocusMode' FocusMode string + 'ISO' ISO int16u + 'ISOSetting' ISOSetting int16u + 'ImageNumber' ImageNumber int32u + 'ImageQuality' ImageQuality string + 'ImageStabilization' ImageStabilization int8u + 'Lens' Lens MIE Lens + 'Make' Make string + 'MeasuredEV' MeasuredEV rational64s + 'Model' Model string + 'Orientation' Orientation MIE Orient + 'OwnerName' OwnerName string + 'Saturation' Saturation int8s + 'SensorSize' SensorSize rational64u[2] + 'SerialNumber' SerialNumber string + 'Sharpness' Sharpness int8s + 'ShootingMode' ShootingMode string + +=head3 MIE Flash Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'ExposureComp' FlashExposureComp rational64s + 'Fired' FlashFired int8u + 'GuideNumber' FlashGuideNumber string + 'Make' FlashMake string + 'Mode' FlashMode string + 'Model' FlashModel string + 'SerialNumber' FlashSerialNumber string + 'Type' FlashType string + +=head3 MIE Lens Tags + +All recorded lens parameters (focal length, aperture, etc) include the +effects of the extender if present. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Extender' Extender MIE Extender + 'FNumber' FNumber rational64u + 'FocalLength' FocalLength rational64u + 'FocusDistance' FocusDistance rational64u + 'Make' LensMake string + 'MaxAperture' MaxAperture rational64u + 'MaxApertureAtMaxFocal' MaxApertureAtMaxFocal rational64u + 'MaxFocalLength' MaxFocalLength rational64u + 'MinAperture' MinAperture rational64u + 'MinFocalLength' MinFocalLength rational64u + 'Model' LensModel string + 'OpticalZoom' OpticalZoom rational64u + 'SerialNumber' LensSerialNumber string + +=head3 MIE Extender Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Magnification' ExtenderMagnification rational64s + 'Make' ExtenderMake string + 'Model' ExtenderModel string + 'SerialNumber' ExtenderSerialNumber string + +=head3 MIE Orient Tags + +These tags describe the camera orientation. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Azimuth' Azimuth rational64s + 'Declination' Declination rational64s + 'Elevation' Elevation rational64s + 'RightAscension' RightAscension rational64s + 'Rotation' Rotation rational64s + +=head3 MIE Doc Tags + +Information describing the main document, image or file. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Author' Author string + 'Comment' Comment string + 'Contributors' Contributors string+ + 'Copyright' Copyright string + 'CreateDate' CreateDate string + 'EMail' Email string + 'Keywords' Keywords string+ + 'ModifyDate' ModifyDate string + 'OriginalDate' DateTimeOriginal string + 'Phone' PhoneNumber string + 'References' References string+ + 'Software' Software string + 'Title' Title string + 'URL' URL string + +=head3 MIE Geo Tags + +Information related to geographic location. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Address' Address string + 'City' City string + 'Country' Country string + 'GPS' GPS MIE GPS + 'PostalCode' PostalCode string + 'State' State string + 'UTM' UTM MIE UTM + +=head3 MIE GPS Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Altitude' GPSAltitude rational64s + 'Bearing' GPSDestBearing rational64s + 'DateTime' GPSDateTime string + 'Datum' GPSMapDatum string + 'Differential' GPSDifferential int8u + 'Distance' GPSDestDistance rational64s + 'Heading' GPSTrack rational64s + 'Latitude' GPSLatitude rational64s[n] + 'Longitude' GPSLongitude rational64s[n] + 'MeasureMode' GPSMeasureMode int8u + 'Satellites' GPSSatellites string + 'Speed' GPSSpeed rational64s + +=head3 MIE UTM Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Datum' UTMMapDatum string + 'Easting' UTMEasting string + 'Northing' UTMNorthing string + 'Zone' UTMZone int8s + +=head3 MIE Image Tags + + Tag ID Tag Name Writable + ------ -------- -------- + '0Type' FullSizeImageType string + '1Name' FullSizeImageName string + 'BitDepth' BitDepth int16u + 'ColorSpace' ColorSpace string + 'Components' ComponentsConfiguration string + 'Compression' CompressionRatio rational32u + 'ImageSize' ImageSize int16u[n] + 'OriginalImageSize' OriginalImageSize int16u[n] + 'Resolution' Resolution rational64u[n] + 'data' FullSizeImage undef + +=head3 MIE MakerNotes Tags + +MIE maker notes are contained within separate groups for each manufacturer +to avoid name conflicts. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Canon' Canon MIE Canon + 'Casio' Casio MIE Unknown + 'FujiFilm' FujiFilm MIE Unknown + 'Kodak' Kodak MIE Unknown + 'KonicaMinolta' KonicaMinolta MIE Unknown + 'Nikon' Nikon MIE Unknown + 'Olympus' Olympus MIE Unknown + 'Panasonic' Panasonic MIE Unknown + 'Pentax' Pentax MIE Unknown + 'Ricoh' Ricoh MIE Unknown + 'Sigma' Sigma MIE Unknown + 'Sony' Sony MIE Unknown + +=head3 MIE Canon Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'VRD' CanonVRD CanonVRD + +=head3 MIE Unknown Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 MIE Preview Tags + + Tag ID Tag Name Writable + ------ -------- -------- + '0Type' PreviewImageType string + '1Name' PreviewImageName string + 'ImageSize' PreviewImageSize int16u[n] + 'data' PreviewImage undef + +=head3 MIE Thumbnail Tags + + Tag ID Tag Name Writable + ------ -------- -------- + '0Type' ThumbnailImageType string + '1Name' ThumbnailImageName string + 'ImageSize' ThumbnailImageSize int16u[n] + 'data' ThumbnailImage undef + +=head3 MIE Video Tags + + Tag ID Tag Name Writable + ------ -------- -------- + '0Type' RelatedVideoFileType string + '1Name' RelatedVideoFileName string + 'Codec' Codec string + 'Duration' Duration rational64u~ + 'data' RelatedVideoFile undef + +=head2 GIF Tags + +This table lists information extracted from GIF images. See +L<http://www.w3.org/Graphics/GIF/spec-gif89a.txt> for the official GIF89a +specification. + + Tag Name Writable + -------- -------- + Comment yes + Duration no + Extensions GIF Extensions + FrameCount no + GIFVersion no + ScreenDescriptor GIF Screen + Text no + TransparentColor no + +=head3 GIF Extensions Tags + +Tags extracted from GIF89a application extensions. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ICCRGBG1/012' ICC_Profile ICC_Profile + 'MIDICTRL/Jon' MIDIControl GIF MIDIControl + 'MIDISONG/Dm7' MIDISong no + 'NETSCAPE/2.0' Animation GIF Animation + 'XMP Data/XMP' XMP XMP + +=head3 GIF MIDIControl Tags + +Information extracted from the MIDI control block extension. + + Index1 Tag Name Writable + ------ -------- -------- + 0 MIDIControlVersion no + 1 SequenceNumber no + 2 MelodicPolyphony no + 3 PercussivePolyphony no + 4 ChannelUsage no + 6 DelayTime no + +=head3 GIF Animation Tags + +Information extracted from the "NETSCAPE2.0" animation extension. + + Index1 Tag Name Writable + ------ -------- -------- + 1 AnimationIterations no + +=head3 GIF Screen Tags + +Information extracted from the GIF logical screen descriptor. + + Index1 Tag Name Writable + ------ -------- -------- + 0 ImageWidth no + 2 ImageHeight no + 4.1 HasColorMap no + 4.2 ColorResolutionDepth no + 4.3 BitsPerPixel no + 5 BackgroundColor no + 6 PixelAspectRatio no + +=head2 BMP Tags + +There really isn't much meta information in a BMP file as such, just a bit +of image related information. + + Index1 Tag Name Writable + ------ -------- -------- + 0 BMPVersion no + 4 ImageWidth no + 8 ImageHeight no + 12 Planes no + 14 BitDepth no + 16 Compression no + 20 ImageLength no + 24 PixelsPerMeterX no + 28 PixelsPerMeterY no + 32 NumColors no + 36 NumImportantColors no + 40 RedMask no + 44 GreenMask no + 48 BlueMask no + 52 AlphaMask no + 56 ColorSpace no + 60 RedEndpoint no + 72 GreenEndpoint no + 84 BlueEndpoint no + 96 GammaRed no + 100 GammaGreen no + 104 GammaBlue no + 108 RenderingIntent no + 112 ProfileDataOffset no + 116 ProfileSize no + +=head3 BMP OS2 Tags + +Information extracted from OS/2-format BMP images. + + Index1 Tag Name Writable + ------ -------- -------- + 0 BMPVersion no + 4 ImageWidth no + 6 ImageHeight no + 8 Planes no + 10 BitDepth no + +=head3 BMP Extra Tags + +Extra information extracted from some BMP images. + + Tag Name Writable + -------- -------- + EmbeddedJPG no + EmbeddedPNG no + ICC_Profile ICC_Profile + LinkedProfileName no + +=head2 BPG Tags + +The information listed below is extracted from BPG (Better Portable +Graphics) images. See L<http://bellard.org/bpg/> for the specification. + + Index1 Tag Name Writable + ------ -------- -------- + 4 PixelFormat no + 4.1 Alpha no + 4.2 BitDepth no + 4.3 ColorSpace no + 4.4 Flags no + 6 ImageWidth no + 7 ImageHeight no + 8 ImageLength no + +=head3 BPG Extensions Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 EXIF EXIF + 0x0002 ICC_Profile ICC_Profile + 0x0003 XMP XMP + 0x0004 ThumbnailBPG no + 0x0005 AnimationControl? no + +=head2 WPG Tags + +Tags extracted from WordPerfect Graphics (WPG) images. + + Tag Name Writable + -------- -------- + ImageHeightInches no + ImageWidthInches no + Records no+ + RecordsV2 no+ + WPGVersion no + +=head2 ICO Tags + +Information extracted from Windows ICO (icon) and CUR (cursor) files. + + Index1 Tag Name Writable + ------ -------- -------- + 2 ImageType no + 4 ImageCount no + 6 IconDir ICO IconDir + +=head3 ICO IconDir Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ImageWidth no + 1 ImageHeight no + 2 NumColors no + 4 ColorPlanes no + HotspotX no + 6 BitsPerPixel no + HotspotY no + 8 ImageLength no + +=head2 PICT Tags + +The PICT format contains no true meta information, except for the possible +exception of the LongComment opcode. By default, only ImageWidth, +ImageHeight and X/YResolution are extracted from a PICT image. Tags in the +following table represent image opcodes. Extraction of these tags is +experimental, and is only enabled with the Verbose or Unknown options. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 Nop no + 0x0001 ClipRgn no + 0x0002 BkPat no + 0x0003 TxFont no + 0x0004 TxFace no + 0x0005 TxMode no + 0x0006 SpExtra no + 0x0007 PnSize no + 0x0008 PnMode no + 0x0009 PnPat no + 0x000a FillPat no + 0x000b OvSize no + 0x000c Origin no + 0x000d TxSize no + 0x000e FgColor no + 0x000f BkColor no + 0x0010 TxRatio no + 0x0011 VersionOp no + 0x0012 BkPixPat no + 0x0013 PnPixPat no + 0x0014 FillPixPat no + 0x0015 PnLocHFrac no + 0x0016 ChExtra no + 0x0017 Reserved no + 0x001a RGBFgCol no + 0x001b RGBBkCol no + 0x001c HiliteMode no + 0x001d HiliteColor no + 0x001e DefHilite no + 0x001f OpColor no + 0x0020 Line no + 0x0021 LineFrom no + 0x0022 ShortLine no + 0x0023 ShortLineFrom no + 0x0024 Reserved no + 0x0028 LongText no + 0x0029 DHText no + 0x002a DVText no + 0x002b DHDVText no + 0x002c FontName no + 0x002d LineJustify no + 0x002e GlyphState no + 0x002f Reserved no + 0x0030 FrameRect no + 0x0031 PaintRect no + 0x0032 EraseRect no + 0x0033 InvertRect no + 0x0034 FillRect no + 0x0035 Reserved no + 0x0038 FrameSameRect no + 0x0039 PaintSameRect no + 0x003a EraseSameRect no + 0x003b InvertSameRect no + 0x003c FillSameRect no + 0x003d Reserved no + 0x0040 FrameRRect no + 0x0041 PaintRRect no + 0x0042 EraseRRect no + 0x0043 InvertRRect no + 0x0044 FillRRect no + 0x0045 Reserved no + 0x0048 FrameSameRRect no + 0x0049 PaintSameRRect no + 0x004a EraseSameRRect no + 0x004b InvertSameRRect no + 0x004c FillSameRRect no + 0x004d Reserved no + 0x0050 FrameOval no + 0x0051 PaintOval no + 0x0052 EraseOval no + 0x0053 InvertOval no + 0x0054 FillOval no + 0x0055 Reserved no + 0x0058 FrameSameOval no + 0x0059 PaintSameOval no + 0x005a EraseSameOval no + 0x005b InvertSameOval no + 0x005c FillSameOval no + 0x005d Reserved no + 0x0060 FrameArc no + 0x0061 PaintArc no + 0x0062 EraseArc no + 0x0063 InvertArc no + 0x0064 FillArc no + 0x0065 Reserved no + 0x0068 FrameSameArc no + 0x0069 PaintSameArc no + 0x006a EraseSameArc no + 0x006b InvertSameArc no + 0x006c FillSameArc no + 0x006d Reserved no + 0x0070 FramePoly no + 0x0071 PaintPoly no + 0x0072 ErasePoly no + 0x0073 InvertPoly no + 0x0074 FillPoly no + 0x0075 Reserved no + 0x0078 FrameSamePoly no + 0x0079 PaintSamePoly no + 0x007a EraseSamePoly no + 0x007b InvertSamePoly no + 0x007c FillSamePoly no + 0x007d Reserved no + 0x0080 FrameRgn no + 0x0081 PaintRgn no + 0x0082 EraseRgn no + 0x0083 InvertRgn no + 0x0084 FillRgn no + 0x0085 Reserved no + 0x0088 FrameSameRgn no + 0x0089 PaintSameRgn no + 0x008a EraseSameRgn no + 0x008b InvertSameRgn no + 0x008c FillSameRgn no + 0x008d Reserved no + 0x0090 BitsRect no + 0x0091 BitsRgn no + 0x0092 Reserved no + 0x0098 PackBitsRect no + 0x0099 PackBitsRgn no + 0x009a DirectBitsRect no + 0x009b DirectBitsRgn no + 0x009c Reserved no + 0x009d Reserved no + 0x009e Reserved no + 0x009f Reserved no + 0x00a0 ShortComment no + 0x00a1 LongComment Photoshop + ICC_Profile + 0x00a2 Reserved no + 0x00b0 Reserved no + 0x00d0 Reserved no + 0x00ff OpEndPic no + 0x0100 Reserved no + 0x0200 Reserved no + 0x02ff Version no + 0x0300 Reserved no + 0x0bff Reserved no + 0x0c00 HeaderOp no + 0x0c01 Reserved no + 0x7f00 Reserved no + 0x8000 Reserved no + 0x8100 Reserved no + 0x8200 CompressedQuickTime no + 0x8201 UncompressedQuickTime no + 0xffff Reserved no + +=head2 PNG Tags + +Tags extracted from PNG images. See +L<http://www.libpng.org/pub/png/spec/1.2/> for the official PNG 1.2 +specification. + +According to the specification, a PNG file should end at the IEND chunk, +however ExifTool will preserve any data found after this when writing unless +it is specifically deleted with C<-Trailer:All=>. When reading, a minor +warning is issued if this trailer exists, and ExifTool will attempt to parse +this data as additional PNG chunks. + +Also according to the PNG specification, there is no restriction on the +location of text-type chunks (tEXt, zTXt and iTXt). However, certain +utilities (including some Apple and Adobe utilities) won't read the XMP iTXt +chunk if it comes after the IDAT chunk, and at least one utility won't read +other text chunks here. For this reason, when writing, ExifTool 11.63 and +later create new text chunks (including XMP) before IDAT, and move existing +text chunks to before IDAT. + +The PNG format contains CRC checksums that are validated when reading with +either the Verbose or Validate option. When writing, these checksums are +validated by default, but the FastScan option may be used to bypass this +check if speed is more of a concern. + + Tag ID Tag Name Writable + ------ -------- -------- + 'IHDR' ImageHeader PNG ImageHeader + 'PLTE' Palette no + 'acTL' AnimationControl PNG AnimationControl + 'bKGD' BackgroundColor no + 'cHRM' PrimaryChromaticities PNG PrimaryChromaticities + 'cICP' CICodePoints PNG CICodePoints + 'caBX' JUMBF Jpeg2000 + 'dSIG' DigitalSignature no + 'eXIf' eXIf EXIF + 'fRAc' FractalParameters no + 'gAMA' Gamma yes! + 'gIFg' GIFGraphicControlExtension no + 'gIFt' GIFPlainTextExtension no + 'gIFx' GIFApplicationExtension no + 'hIST' PaletteHistogram no + 'iCCP' ICC_Profile ICC_Profile + 'iCCP-name' ProfileName yes + 'iDOT' AppleDataOffsets no + 'iTXt' InternationalText PNG TextualData + 'oFFs' ImageOffset no + 'pCAL' PixelCalibration no + 'pHYs' PhysicalPixel PNG PhysicalPixel + 'sBIT' SignificantBits no + 'sCAL' SubjectScale PNG SubjectScale + 'sPLT' SuggestedPalette no + 'sRGB' SRGBRendering yes! + 'sTER' StereoImage PNG StereoImage + 'tEXt' TextualData PNG TextualData + 'tIME' ModifyDate yes + 'tRNS' Transparency no + 'tXMP' XMP XMP + 'vpAg' VirtualPage PNG VirtualPage + 'zTXt' CompressedText PNG TextualData + 'zxIf' zxIf EXIF + +=head3 PNG ImageHeader Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ImageWidth no + 4 ImageHeight no + 8 BitDepth no + 9 ColorType no + 10 Compression no + 11 Filter no + 12 Interlace no + +=head3 PNG AnimationControl Tags + +Tags found in the Animation Control chunk. See +L<https://wiki.mozilla.org/APNG_Specification> for details. + + Index4 Tag Name Writable + ------ -------- -------- + 0 AnimationFrames no + 1 AnimationPlays no + +=head3 PNG PrimaryChromaticities Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 WhitePointX no + 1 WhitePointY no + 2 RedX no + 3 RedY no + 4 GreenX no + 5 GreenY no + 6 BlueX no + 7 BlueY no + +=head3 PNG CICodePoints Tags + +These tags are found in the PNG cICP chunk and belong to the PNG-cICP family +1 group. + + Index1 Tag Name Writable + ------ -------- -------- + 0 ColorPrimaries no + 1 TransferCharacteristics no + 2 MatrixCoefficients no + 3 VideoFullRangeFlag no + +=head3 PNG TextualData Tags + +The PNG TextualData format allows arbitrary tag names to be used. The tags +listed below are the only ones that can be written (unless new user-defined +tags are added via the configuration file), however ExifTool will extract +any other TextualData tags that are found. All TextualData tags (including +tags not listed below) are removed when deleting all PNG tags. + +These tags may be stored as tEXt, zTXt or iTXt chunks in the PNG image. By +default ExifTool writes new string-value tags as as uncompressed tEXt, or +compressed zTXt if the Compress (-z) option is used and Compress::Zlib is +available. Alternate language tags and values containing special characters +(unless the Latin character set is used) are written as iTXt, and compressed +if the Compress option is used and Compress::Zlib is available. Raw profile +information is always created as compressed zTXt if Compress::Zlib is +available, or tEXt otherwise. Standard XMP is written as uncompressed iTXt. +User-defined tags may set an 'iTXt' flag in the tag definition to be written +only as iTXt. + +Alternate languages are accessed by suffixing the tag name with a '-', +followed by an RFC 3066 language code (eg. "PNG:Comment-fr", or +"Title-en-US"). See L<http://www.ietf.org/rfc/rfc3066.txt> for the RFC 3066 +specification. + +Some of the tags below are not registered as part of the PNG specification, +but are included here because they are generated by other software such as +ImageMagick. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Artist' Artist string + 'Author' Author string + 'Collection' Collection string + 'Comment' Comment string + 'Copyright' Copyright string + 'Creation Time' CreationTime string + 'Description' Description string + 'Disclaimer' Disclaimer string + 'Document' Document string + 'Label' Label string + 'Make' Make string + 'Model' Model string + 'Raw profile type 8bim' Photoshop_Profile Photoshop + 'Raw profile type APP1' APP1_Profile EXIF + XMP + 'Raw profile type exif' EXIF_Profile EXIF + 'Raw profile type icc' ICC_Profile ICC_Profile + 'Raw profile type icm' ICC_Profile ICC_Profile + 'Raw profile type iptc' IPTC_Profile Photoshop + 'Raw profile type xmp' XMP_Profile XMP + 'Software' Software string + 'Source' Source string + 'TimeStamp' TimeStamp string + 'Title' Title string + 'URL' URL string + 'Warning' PNGWarning string + 'XML:com.adobe.xmp' XMP XMP + 'create-date' CreateDate string + 'modify-date' ModDate string + +=head3 PNG PhysicalPixel Tags + +These tags are found in the PNG pHYs chunk and belong to the PNG-pHYs family +1 group. They are all created together with default values if necessary +when any of these tags is written, and may only be deleted as a group. + + Index1 Tag Name Writable + ------ -------- -------- + 0 PixelsPerUnitX int32u + 4 PixelsPerUnitY int32u + 8 PixelUnits int8u + +=head3 PNG SubjectScale Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 SubjectUnits no + 1 SubjectPixelWidth no + 2 SubjectPixelHeight no + +=head3 PNG StereoImage Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 StereoMode no + +=head3 PNG VirtualPage Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 VirtualImageWidth no + 1 VirtualImageHeight no + 2 VirtualPageUnits no + +=head2 MNG Tags + +This table contains definitions for tags found in MNG and JNG images. MNG +is a superset of PNG and JNG, so a MNG image may contain any of these tags +as well as any PNG tags. Conversely, only some of these tags are valid for +JNG images. + + Tag ID Tag Name Writable + ------ -------- -------- + 'BACK' Background MNG Background + 'BASI' BasisObject MNG BasisObject + 'CLIP' ClipObjects MNG ClipObjects + 'CLON' CloneObject MNG CloneObject + 'DBYK' DropByKeyword no + 'DEFI' DefineObject MNG DefineObject + 'DHDR' DeltaPNGHeader MNG DeltaPNGHeader + 'DISC' DiscardObjects no + 'DROP' DropChunks no + 'FRAM' Frame no + 'JHDR' JNGHeader MNG JNGHeader + 'LOOP' Loop MNG Loop + 'MAGN' MagnifyObject MNG MagnifyObject + 'MHDR' MNGHeader MNG MNGHeader + 'MOVE' MoveObjects MNG MoveObjects + 'ORDR' OrderingRestrictions no + 'PAST' PasteImage MNG PasteImage + 'PPLT' PartialPalette no + 'PROM' PromoteParent MNG PromoteParent + 'SAVE' SaveObjects no + 'SEEK' SeekPoint no + 'SHOW' ShowObjects MNG ShowObjects + 'TERM' TerminationAction MNG TerminationAction + 'eXPi' ExportImage MNG ExportImage + 'fPRI' FramePriority MNG FramePriority + 'nEED' ResourcesNeeded no + 'pHYg' GlobalPixelSize PNG PhysicalPixel + +=head3 MNG Background Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 BackgroundColor no + 6 MandatoryBackground no + 7 BackgroundImageID no + 9 BackgroundTiling no + +=head3 MNG BasisObject Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ImageWidth no + 4 ImageHeight no + 8 BitDepth no + 9 ColorType no + 10 Compression no + 11 Filter no + 12 Interlace no + 13 RedSample no + 17 GreenSample no + 21 BlueSample no + 25 AlphaSample no + 26 Viewable no + +=head3 MNG ClipObjects Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FirstObject no + 2 LastObject no + 4 DeltaType no + 5 ClipBoundary no + +=head3 MNG CloneObject Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 SourceID no + 2 CloneID no + 4 CloneType no + 5 DoNotShow no + 6 ConcreteFlag no + 7 LocalDeltaType no + 8 DeltaXY no + +=head3 MNG DefineObject Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ObjectID no + 2 DoNotShow no + 3 ConcreteFlag no + 4 XYLocation no + 12 ClippingBoundary no + +=head3 MNG DeltaPNGHeader Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ObjectID no + 2 ImageType no + 3 DeltaType no + 4 BlockSize no + 12 BlockLocation no + +=head3 MNG JNGHeader Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ImageWidth no + 4 ImageHeight no + 8 ColorType no + 9 BitDepth no + 10 Compression no + 11 Interlace no + 12 AlphaBitDepth no + 13 AlphaCompression no + 14 AlphaFilter no + 15 AlphaInterlace no + +=head3 MNG Loop Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 NestLevel no + 1 IterationCount no + 5 TerminationCondition no + 6 IterationMinMax no + 14 SignalNumber no + +=head3 MNG MagnifyObject Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FirstObjectID no + 2 LastObjectID no + 4 XMethod no + 5 XMag no + 7 YMag no + 9 LeftMag no + 11 RightMag no + 13 TopMag no + 15 BottomMag no + 17 YMethod no + +=head3 MNG MNGHeader Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 ImageWidth no + 1 ImageHeight no + 2 TicksPerSecond no + 3 NominalLayerCount no + 4 NominalFrameCount no + 5 NominalPlayTime no + 6 SimplicityProfile no + +=head3 MNG MoveObjects Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FirstObject no + 2 LastObject no + 4 DeltaType no + 5 DeltaXY no + +=head3 MNG PasteImage Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 DestinationID no + 2 TargetDeltaType no + 3 TargetXY no + 11 SourceID no + 13 CompositionMode no + 14 Orientation no + 15 OffsetOrigin no + 16 OffsetXY no + 24 BoundaryOrigin no + 25 PastClippingBoundary no + +=head3 MNG PromoteParent Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 NewColorType no + 1 NewBitDepth no + 2 FillMethod no + +=head3 MNG ShowObjects Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FirstObject no + 2 LastObject no + 4 ShowMode no + +=head3 MNG TerminationAction Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 TerminationAction no + 1 IterationEndAction no + 2 Delay no + 6 IterationMax no + +=head3 MNG ExportImage Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 SnapshotID no + 2 SnapshotName no + +=head3 MNG FramePriority Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 DeltaType no + 2 Priority no + +=head2 FLIF Tags + +Information extracted from Free Lossless Image Format files. See +L<http://flif.info/> for more information. + + Tag ID Tag Name Writable + ------ -------- -------- + 0 ImageType no + 1 BitDepth no + 2 ImageWidth no + 3 ImageHeight no + 4 AnimationFrames no + 5 Encoding no + 'eXif' EXIF EXIF + 'eXmp' XMP XMP + 'iCCP' ICC_Profile ICC_Profile + +=head2 DjVu Tags + +Information is extracted from the following chunks in DjVu images. See +L<http://www.djvu.org/> for the DjVu specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ANTa' ANTa DjVu Ant + 'ANTz' CompressedAnnotation DjVu Ant + 'FORM' FORM DjVu Form + 'INCL' IncludedFileID no + 'INFO' INFO DjVu Info + +=head3 DjVu Ant Tags + +Information extracted from annotation chunks. + + Tag ID Tag Name Writable + ------ -------- -------- + 'metadata' Metadata DjVu Meta + 'xmp' XMP XMP + +=head3 DjVu Meta Tags + +This table lists the standard DjVu metadata tags, but ExifTool will extract +any tags that exist even if they don't appear here. The DjVu v3 +documentation endorses tags borrowed from two standards: 1) BibTeX +bibliography system tags (all lowercase Tag ID's in the table below), and 2) +PDF DocInfo tags (capitalized Tag ID's). + + Tag ID Tag Name Writable + ------ -------- -------- + 'Author' Author no + 'CreationDate' CreateDate no + 'Creator' Creator no + 'Keywords' Keywords no + 'ModDate' ModifyDate no + 'Producer' Producer no + 'Subject' Subject no + 'Title' Title no + 'Trapped' Trapped no + 'address' Address no + 'annote' Annotation no + 'author' Author no + 'booktitle' BookTitle no + 'chapter' Chapter no + 'crossref' CrossRef no + 'edition' Edition no + 'eprint' EPrint no + 'howpublished' HowPublished no + 'institution' Institution no + 'journal' Journal no + 'key' Key no + 'month' Month no + 'note' Note no + 'number' Number no + 'organization' Organization no + 'pages' Pages no + 'publisher' Publisher no + 'school' School no + 'series' Series no + 'title' Title no + 'type' Type no + 'url' URL no + 'volume' Volume no + 'year' Year no + +=head3 DjVu Form Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 SubfileType no + +=head3 DjVu Info Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ImageWidth no + 2 ImageHeight no + 4 DjVuVersion no + 6 SpatialResolution no + 8 Gamma no + 9 Orientation no + +=head2 DPX Tags + +Tags extracted from DPX (Digital Picture Exchange) images. + + Index1 Tag Name Writable + ------ -------- -------- + 0 ByteOrder no + 8 HeaderVersion no + 16 DPXFileSize no + 20 DittoKey no + 36 ImageFileName no + 136 CreateDate no + 160 Creator no + 260 Project no + 460 Copyright no + 660 EncryptionKey no + 768 Orientation no + 770 ImageElements no + 772 ImageWidth no + 776 ImageHeight no + 780 DataSign no + 800 ComponentsConfiguration no + 801 TransferCharacteristic no + 802 ColorimetricSpecification no + 803 BitDepth no + 820 ImageDescription no + 892 Image2Description no + 964 Image3Description no + 1036 Image4Description no + 1108 Image5Description no + 1180 Image6Description no + 1252 Image7Description no + 1324 Image8Description no + 1432 SourceFileName no + 1532 SourceCreateDate no + 1556 InputDeviceName no + 1588 InputDeviceSerialNumber no + 1628 AspectRatio no + 1724 OriginalFrameRate no + 1728 ShutterAngle no + 1732 FrameID no + 1764 SlateInformation no + 1920 TimeCode no + 1940 FrameRate no + 1972 Reserved5? no + 2048 UserID no + +=head2 OpenEXR Tags + +Information extracted from EXR images. Use the ExtractEmbedded option to +extract information from all frames of a multipart image. See +L<http://www.openexr.com/> for the official specification. + + Tag ID Tag Name Writable + ------ -------- -------- + '_flags' Flags no + '_ver' EXRVersion no + 'adoptedNeutral' AdoptedNeutral no + 'altitude' GPSAltitude no + 'aperture' Aperture no + 'capDate' DateTimeOriginal no + 'channels' Channels no + 'chromaticities' Chromaticities no + 'chunkCount' ChunkCount no + 'comments' Comments no + 'compression' Compression no + 'dataWindow' DataWindow no + 'displayWindow' DisplayWindow no + 'envmap' EnvironmentMap no + 'expTime' ExposureTime no + 'focus' FocusDistance no + 'framesPerSecond' FramesPerSecond no + 'isoSpeed' ISO no + 'keyCode' KeyCode no + 'latitude' GPSLatitude no + 'lineOrder' LineOrder no + 'longitude' GPSLongitude no + 'lookModTransform' LookModTransform no + 'multiView' MultiView no + 'name' Name no + 'owner' Owner no + 'pixelAspectRatio' PixelAspectRatio no + 'preview' Preview no + 'renderingTransform' RenderingTransform no + 'screenWindowCenter' ScreenWindowCenter no + 'screenWindowWidth' ScreenWindowWidth no + 'tiles' Tiles no + 'timeCode' TimeCode no + 'type' Type no + 'utcOffset' TimeZone no + 'version' Version no + 'whiteLuminance' WhiteLuminance no + 'worldToCamera' WorldToCamera no + 'worldToNDC' WorldToNDC no + 'wrapmodes' WrapModes no + 'xDensity' XResolution no + +=head2 ZISRAW Tags + +As well as the header information listed below, ExifTool also extracts the +top-level XML-based metadata from Zeiss Integrated Software RAW (ZISRAW) CZI +files. + + Index1 Tag Name Writable + ------ -------- -------- + 32 ZISRAWVersion no + 48 PrimaryFileGUID no + 64 FileGUID no + +=head2 MRC Tags + +Tags extracted from Medical Research Council (MRC) format imaging files. +See L<https://www.ccpem.ac.uk/mrc_format/mrc2014.php> for the specification. + + Index4 Tag Name Writable + ------ -------- -------- + 0 ImageWidth no + 1 ImageHeight no + 2 ImageDepth no + 3 ImageMode no + 4 StartPoint no + 7 GridSize no + 10 CellWidth no + 11 CellHeight no + 12 CellDepth no + 13 CellAlpha no + 14 CellBeta no + 15 CellGamma no + 16 ImageWidthAxis no + 17 ImageHeightAxis no + 18 ImageDepthAxis no + 19 DensityMin no + 20 DensityMax no + 21 DensityMean no + 22 SpaceGroupNumber no + 23 ExtendedHeaderSize no + 26 ExtendedHeaderType no + 27 MRCVersion no + 49 Origin no + 53 MachineStamp no + 54 RMSDeviation no + 55 NumberOfLabels no + 56 Label0 no + 76 Label1 no + 96 Label2 no + 116 Label3 no + 136 Label4 no + 156 Label5 no + 176 Label6 no + 196 Label7 no + 216 Label8 no + 236 Label9 no + +=head3 MRC FEI12 Tags + +Tags extracted from FEI1 and FEI2 extended headers. + + Index1 Tag Name Writable + ------ -------- -------- + 0 MetadataSize no + 4 MetadataVersion no + 8 Bitmask1 no + 12 TimeStamp no + 20 MicroscopeType no + 36 MicroscopeID no + 52 Application no + 68 AppVersion no + 84 HighTension no + 92 Dose no + 100 AlphaTilt no + 108 BetaTilt no + 116 XStage no + 124 YStage no + 132 ZStage no + 140 TiltAxisAngle no + 148 DualAxisRot no + 156 PixelSizeX no + 164 PixelSizeY no + 220 Defocus no + 228 STEMDefocus no + 236 AppliedDefocus no + 244 InstrumentMode no + 248 ProjectionMode no + 252 ObjectiveLens no + 268 HighMagnificationMode no + 284 ProbeMode no + 288 EFTEMOn no + 289 Magnification no + 297 Bitmask2 no + 301 CameraLength no + 309 SpotIndex no + 313 IlluminationArea no + 321 Intensity no + 329 ConvergenceAngle no + 337 IlluminationMode no + 353 WideConvergenceAngleRange no + 354 SlitInserted no + 355 SlitWidth no + 363 AccelVoltOffset no + 371 DriftTubeVolt no + 379 EnergyShift no + 387 ShiftOffsetX no + 395 ShiftOffsetY no + 403 ShiftX no + 411 ShiftY no + 419 IntegrationTime no + 427 BinningWidth no + 431 BinningHeight no + 435 CameraName no + 451 ReadoutAreaLeft no + 455 ReadoutAreaTop no + 459 ReadoutAreaRight no + 463 ReadoutAreaBottom no + 467 CetaNoiseReduct no + 468 CetaFramesSummed no + 472 DirectDetElectronCounting no + 473 DirectDetAlignFrames no + 490 Bitmask3 no + 518 PhasePlate no + 519 STEMDetectorName no + 535 Gain no + 543 Offset no + 571 DwellTime no + 579 FrameTime no + 587 ScanSizeLeft no + 591 ScanSizeTop no + 595 ScanSizeRight no + 599 ScanSizeBottom no + 603 FullScanFOV_X no + 611 FullScanFOV_Y no + 619 Element no + 635 EnergyIntervalLower no + 643 EnergyIntervalHigher no + 651 Method no + 655 IsDoseFraction no + 656 FractionNumber no + 660 StartFrame no + 664 EndFrame no + 668 InputStackFilename no + 748 Bitmask4 no + 752 AlphaTiltMin no + 760 AlphaTiltMax no + 768 ScanRotation no + 776 DiffractionPatternRotation no + 784 ImageRotation no + 792 ScanModeEnumeration no + 796 AcquisitionTimeStamp no + 804 DetectorCommercialName no + 820 StartTiltAngle no + 828 EndTiltAngle no + 836 TiltPerImage no + 844 TitlSpeed no + 852 BeamCenterX no + 856 BeamCenterY no + 860 CFEGFlashTimeStamp no + 868 PhasePlatePosition no + 872 ObjectiveAperture no + +=head2 LIF Tags + +Tags extracted from Leica Image Format (LIF) imaging files. As well as the +tags listed below, all available information is extracted from the +XML-format metadata in the LIF header. + + Tag Name Writable + -------- -------- + TimeStampList no + +=head2 MIFF Tags + +The MIFF (Magick Image File Format) format allows aribrary tag names to be +used. Only the standard tag names are listed below, however ExifTool will +decode any tags found in the image. + + Tag ID Tag Name Writable + ------ -------- -------- + 'background-color' BackgroundColor no + 'blue-primary' BluePrimary no + 'border-color' BorderColor no + 'class' Class no + 'colors' Colors no + 'colorspace' ColorSpace no + 'columns' ImageWidth no + 'compression' Compression no + 'delay' Delay no + 'depth' Depth no + 'dispose' Dispose no + 'gamma' Gamma no + 'green-primary' GreenPrimary no + 'id' ID no + 'iterations' Iterations no + 'label' Label no + 'matt-color' MattColor no + 'matte' Matte no + 'montage' Montage no + 'packets' Packets no + 'page' Page no + 'profile-APP1' APP1_Profile EXIF + XMP + 'profile-exif' EXIF_Profile EXIF + 'profile-icc' ICC_Profile ICC_Profile + 'profile-iptc' IPTC_Profile Photoshop + 'profile-xmp' XMP_Profile XMP + 'red-primary' RedPrimary no + 'rendering-intent' RenderingIntent no + 'resolution' Resolution no + 'rows' ImageHeight no + 'scene' Scene no + 'signature' Signature no + 'units' Units no + 'white-point' WhitePoint no + +=head2 PCX Tags + +Tags extracted from PC Paintbrush images. + + Index1 Tag Name Writable + ------ -------- -------- + 0 Manufacturer no + 1 Software no + 2 Encoding no + 3 BitsPerPixel no + 4 LeftMargin no + 6 TopMargin no + 8 ImageWidth no + 10 ImageHeight no + 12 XResolution no + 14 YResolution no + 65 ColorPlanes no + 66 BytesPerLine no + 68 ColorMode no + 70 ScreenWidth no + 72 ScreenHeight no + +=head2 PGF Tags + +The following table lists information extracted from the header of +Progressive Graphics File (PGF) images. As well, information is extracted +from the embedded PNG metadata image if it exists. See +L<http://www.libpgf.org/> for the PGF specification. + + Index1 Tag Name Writable + ------ -------- -------- + 3 PGFVersion no + 8 ImageWidth no + 12 ImageHeight no + 16 PyramidLevels no + 17 Quality no + 18 BitsPerPixel no + 19 ColorComponents no + 20 ColorMode no + 21 BackgroundColor no + +=head2 PSP Tags + +Tags extracted from Paint Shop Pro images (PSP, PSPIMAGE, PSPFRAME, +PSPSHAPE, PSPTUBE and TUB extensions). + + Tag ID Tag Name Writable + ------ -------- -------- + 'FileVersion' FileVersion no + 0x0000 ImageInfo PSP Image + PSP Image + 0x0001 CreatorInfo PSP Creator + 0x000a ExtendedInfo PSP Ext + +=head3 PSP Image Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ImageWidth no + 4 ImageHeight no + 8 ImageResolution no + 16 ResolutionUnit no + 17 Compression no + 19 BitsPerSample no + 21 Planes no + 23 NumColors no + +=head3 PSP Creator Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 Title no + 0x0001 CreateDate no + 0x0002 ModifyDate no + 0x0003 Artist no + 0x0004 Copyright no + 0x0005 Description no + 0x0006 CreatorAppID no + 0x0007 CreatorAppVersion no + +=head3 PSP Ext Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0003 EXIFInfo EXIF + +=head2 PhotoCD Tags + +Tags extracted from Kodak Photo CD Image Pac (PCD) files. + + Index1 Tag Name Writable + ------ -------- -------- + 7 SpecificationVersion no + 9 AuthoringSoftwareRelease no + 11 ImageMagnificationDescriptor no + 13 CreateDate no + 17 ModifyDate no + 21 ImageMedium no + 22 ProductType no + 42 ScannerVendorID no + 62 ScannerProductID no + 78 ScannerFirmwareVersion no + 82 ScannerFirmwareDate no + 90 ScannerSerialNumber no + 110 ScannerPixelSize no + 112 ImageWorkstationMake no + 132 CharacterSet no + 133 CharacterEscapeSequence? no + 165 PhotoFinisherName no + 228 SceneBalanceAlgorithmRevision no + 230 SceneBalanceAlgorithmCommand no + 325 SceneBalanceAlgorithmFilmID no + 331 CopyrightStatus no + 332 CopyrightFileName no + 1538 Orientation no + 1538.1 ImageWidth no + 1538.2 ImageHeight no + 1538.3 CompressionClass no + +=head2 Radiance Tags + +Information extracted from Radiance RGBE HDR images. Tag ID's are all +uppercase as stored in the file, but converted to lowercase by when +extracting to avoid conflicts with internal ExifTool variables. See +L<http://radsite.lbl.gov/radiance/refer/filefmts.pdf> and +L<http://www.graphics.cornell.edu/online/formats/rgbe/> for the +specification. + + Tag ID Tag Name Writable + ------ -------- -------- + '_command' Command no + '_comment' Comment no + '_orient' Orientation no + 'colorcorr' ColorCorrection no + 'exposure' Exposure no + 'format' Format no + 'gamma' Gamma no + 'pixaspect' PixelAspectRatio no + 'primaries' ColorPrimaries no + 'software' Software no + 'view' View no + +=head3 Other PFM Tags + +Tags extracted from Portable FloatMap images. See +L<http://www.pauldebevec.com/Research/HDR/PFM/> for the specification. + + Tag Name Writable + -------- -------- + ByteOrder no + ColorSpace no + ImageHeight no + ImageWidth no + +=head2 PDF Tags + +The tags listed in the PDF tables below are those which are used by ExifTool +to extract meta information, but they are only a small fraction of the total +number of available PDF tags. See +L<http://www.adobe.com/devnet/pdf/pdf_reference.html> for the official PDF +specification. + +ExifTool supports reading and writing PDF documents up to version 2.0, +including support for RC4, AES-128 and AES-256 encryption. A +Password option is provided to allow processing +of password-protected PDF files. + +ExifTool may be used to write native PDF and XMP metadata to PDF files. It +uses an incremental update technique that has the advantages of being both +fast and reversible. If ExifTool was used to modify a PDF file, the +original may be recovered by deleting the C<PDF-update> pseudo-group (with +C<-PDF-update:all=> on the command line). However, there are two main +disadvantages to this technique: + +1) A linearized PDF file is no longer linearized after the update, so it +must be subsequently re-linearized if this is required. + +2) All metadata edits are reversible. While this would normally be +considered an advantage, it is a potential security problem because old +information is never actually deleted from the file. (However, after +running ExifTool the old information may be removed permanently using the +"qpdf" utility with this command: "qpdf --linearize in.pdf out.pdf".) + + Tag ID Tag Name Writable + ------ -------- -------- + 'Encrypt' Encrypt PDF Encrypt + 'Info' Info PDF Info + 'Root' Root PDF Root + '_linearized' Linearized no + +=head3 PDF Encrypt Tags + +Tags extracted from the document Encrypt dictionary. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Filter' Encryption no + 'P' UserAccess no + +=head3 PDF Info Tags + +As well as the tags listed below, the PDF specification allows for +user-defined tags to exist in the Info dictionary. These tags, which should +have corresponding XMP-pdfx entries in the XMP of the PDF XML Metadata +object, are also extracted by ExifTool. + +B<Writable> specifies the value format, and may be C<string>, C<date>, +C<integer>, C<real>, C<boolean> or C<name> for PDF tags. + + Tag ID Tag Name Writable + ------ -------- -------- + 'AAPL:Keywords' AppleKeywords string+ + 'Author' Author string + 'CreationDate' CreateDate date + 'Creator' Creator string + 'Keywords' Keywords string+ + 'ModDate' ModifyDate date + 'Producer' Producer string + 'Subject' Subject string + 'Title' Title string + 'Trapped' Trapped string! + +=head3 PDF Root Tags + +This is the PDF document catalog. + + Tag ID Tag Name Writable + ------ -------- -------- + 'AcroForm' AcroForm PDF AcroForm + 'Lang' Language no + 'MarkInfo' MarkInfo PDF MarkInfo + 'Metadata' Metadata PDF Metadata + 'PageLayout' PageLayout no + 'PageMode' PageMode no + 'Pages' Pages PDF Pages + 'Perms' Perms PDF Perms + 'Version' PDFVersion no + +=head3 PDF AcroForm Tags + + Tag ID Tag Name Writable + ------ -------- -------- + '_has_xfa' HasXFA no + +=head3 PDF MarkInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Marked' TaggedPDF no + +=head3 PDF Metadata Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'XML_stream' XMP XMP + +=head3 PDF Pages Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Count' PageCount no + 'Kids' Kids PDF Kids + +=head3 PDF Kids Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Kids' Kids PDF Kids + 'Metadata' Metadata PDF Metadata + 'PieceInfo' PieceInfo PDF PieceInfo + 'Resources' Resources PDF Resources + +=head3 PDF PieceInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'AdobePhotoshop' AdobePhotoshop PDF AdobePhotoshop + 'Illustrator' Illustrator PDF Illustrator + +=head3 PDF AdobePhotoshop Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Private' Private PDF Private + +=head3 PDF Private Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'ImageResources' ImageResources PDF ImageResources + +=head3 PDF ImageResources Tags + + Tag ID Tag Name Writable + ------ -------- -------- + '_stream' _stream Photoshop + +=head3 PDF Illustrator Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Private' Private PDF AIPrivate + +=head3 PDF AIPrivate Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'AIMetaData' AIMetaData PDF AIMetaData + 'AIPDFPrivateData' AIPDFPrivateData PostScript + 'AIPrivateData' AIPrivateData PostScript + 'ContainerVersion' ContainerVersion no + 'CreatorVersion' CreatorVersion no + 'RoundTripVersion' RoundTripVersion no + +=head3 PDF AIMetaData Tags + + Tag ID Tag Name Writable + ------ -------- -------- + '_stream' _stream PostScript + +=head3 PDF Resources Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'ColorSpace' ColorSpace PDF ColorSpace + 'Properties' Properties PDF Properties + 'XObject' XObject PDF XObject + +=head3 PDF ColorSpace Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'CS0' CS0 PDF DefaultRGB + 'Cs1' Cs1 PDF DefaultRGB + 'DefaultCMYK' DefaultCMYK PDF DefaultRGB + 'DefaultRGB' DefaultRGB PDF DefaultRGB + +=head3 PDF DefaultRGB Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'ICCBased' ICCBased PDF ICCBased + +=head3 PDF ICCBased Tags + + Tag ID Tag Name Writable + ------ -------- -------- + '_stream' _stream ICC_Profile + +=head3 PDF Properties Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'MC' MC PDF MC + +=head3 PDF MC Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Metadata' Metadata PDF Metadata + +=head3 PDF XObject Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Im' Im PDF Im + +=head3 PDF Im Tags + +Information extracted from embedded images with the ExtractEmbedded option. +The EmbeddedImage and its metadata are extracted only for JPEG and Jpeg2000 +image formats. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ColorSpace' EmbeddedImageColorSpace no+ + 'Filter' EmbeddedImageFilter no+ + 'Height' EmbeddedImageHeight no + 'Image_stream' EmbeddedImage no + 'Width' EmbeddedImageWidth no + +=head3 PDF Perms Tags + +Additional document permissions imposed by digital signatures. + + Tag ID Tag Name Writable + ------ -------- -------- + 'DocMDP' DocMDP PDF Signature + 'FieldMDP' FieldMDP PDF Signature + 'UR3' UR3 PDF Signature + +=head3 PDF Signature Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'ContactInfo' SignerContactInfo no + 'Location' SigningLocation no + 'M' SigningDate no + 'Name' SigningAuthority no + 'Prop_AuthTime' AuthenticationTime no + 'Prop_AuthType' AuthenticationType no + 'Reason' SigningReason no + 'Reference' Reference PDF Reference + +=head3 PDF Reference Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'TransformParams' TransformParams PDF TransformParams + +=head3 PDF TransformParams Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Action' FieldPermissions no + 'Annots' AnnotationUsageRights no+ + 'Document' DocumentUsageRights no+ + 'EF' EmbeddedFileUsageRights no+ + 'Fields' FormFields no+ + 'Form' FormUsageRights no+ + 'FormEX' FormExtraUsageRights no+ + 'Msg' UsageRightsMessage no + 'P' ModificationPermissions no + 'Signature' SignatureUsageRights no+ + +=head2 PostScript Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'AI12_BuildNumber' AIBuildNumber no + 'AI3_ColorUsage' AIColorUsage no + 'AI5_FileFormat' AIFileFormat no + 'AI5_NumLayers' AINumLayers no + 'AI5_RulerUnits' AIRulerUnits no + 'AI5_TargetResolution' AITargetResolution no + 'AI8_CreatorVersion' AICreatorVersion no + 'AI9_ColorModel' AIColorModel no + 'Author' Author string + 'BeginDocument' EmbeddedFile PostScript + 'BeginICCProfile' ICC_Profile ICC_Profile + 'BeginPhotoshop' PhotoshopData Photoshop + 'BoundingBox' BoundingBox no + 'Copyright' Copyright string + 'CreationDate' CreateDate string + 'Creator' Creator string + 'EmbeddedFileName' EmbeddedFileName no + 'For' For string + 'ImageData' ImageData no + 'Keywords' Keywords string + 'ModDate' ModifyDate string + 'Pages' Pages no + 'Routing' Routing string + 'Subject' Subject string + 'TIFFPreview' TIFFPreview no + 'Title' Title string + 'Version' Version string + 'begin_xml_packet' XMP XMP + +=head2 ID3 Tags + +ExifTool extracts ID3 and Lyrics3 information from MP3, MPEG, WAV, AIFF, +OGG, FLAC, APE, MPC and RealAudio files. ID3v2 tags which support multiple +languages (eg. Comment and Lyrics) are extracted by specifying the tag name, +followed by a dash ('-'), then a 3-character ISO 639-2 language code (eg. +"Comment-spa"). See L<https://id3.org/> for the official ID3 specification +and L<http://www.loc.gov/standards/iso639-2/php/code_list.php> for a list of +ISO 639-2 language codes. + + Tag Name Writable + -------- -------- + ID3v1 ID3 v1 + ID3v1_Enh ID3 v1_Enh + ID3v2_2 ID3 v2_2 + ID3v2_3 ID3 v2_3 + ID3v2_4 ID3 v2_4 + +=head3 ID3 v1 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 3 Title no + 33 Artist no + 63 Album no + 93 Year no + 97 Comment no + 125 Track no + 127 Genre no + +=head3 ID3 v1_Enh Tags + +ID3 version 1 "Enhanced TAG" information (not part of the official spec). + + Index1 Tag Name Writable + ------ -------- -------- + 4 Title2 no + 64 Artist2 no + 124 Album2 no + 184 Speed no + 185 Genre no + 215 StartTime no + 221 EndTime no + +=head3 ID3 v2_2 Tags + +ExifTool extracts mainly text-based tags from ID3v2 information. The tags +in the tables below are those extracted by ExifTool, and don't represent a +complete list of available ID3v2 tags. + +ID3 version 2.2 tags. (These are the tags written by iTunes 5.0.) + + Tag ID Tag Name Writable + ------ -------- -------- + 'CNT' PlayCounter no + 'COM' Comment no + 'IPL' InvolvedPeople no + 'ITU' iTunesU? no + 'PCS' Podcast? no + 'PIC' Picture no + 'PIC-1' PictureFormat no + 'PIC-2' PictureType no + 'PIC-3' PictureDescription no + 'POP' Popularimeter no + 'RVA' RelativeVolumeAdjustment no + 'SLT' SynLyrics ID3 SynLyrics + 'TAL' Album no + 'TBP' BeatsPerMinute no + 'TCM' Composer no + 'TCO' Genre no + 'TCP' Compilation no + 'TCR' Copyright no + 'TDA' Date no + 'TDY' PlaylistDelay no + 'TEN' EncodedBy no + 'TFT' FileType no + 'TIM' Time no + 'TKE' InitialKey no + 'TLA' Language no + 'TLE' Length no + 'TMT' Media no + 'TOA' OriginalArtist no + 'TOF' OriginalFileName no + 'TOL' OriginalLyricist no + 'TOR' OriginalReleaseYear no + 'TOT' OriginalAlbum no + 'TP1' Artist no + 'TP2' Band no + 'TP3' Conductor no + 'TP4' InterpretedBy no + 'TPA' PartOfSet no + 'TPB' Publisher no + 'TRC' ISRC no + 'TRD' RecordingDates no + 'TRK' Track no + 'TS2' AlbumArtistSortOrder no + 'TSA' AlbumSortOrder no + 'TSC' ComposerSortOrder no + 'TSI' Size no + 'TSP' PerformerSortOrder no + 'TSS' EncoderSettings no + 'TST' TitleSortOrder no + 'TT1' Grouping no + 'TT2' Title no + 'TT3' Subtitle no + 'TXT' Lyricist no + 'TXX' UserDefinedText no + 'TYE' Year no + 'ULT' Lyrics no + 'WAF' FileURL no + 'WAR' ArtistURL no + 'WAS' SourceURL no + 'WCM' CommercialURL no + 'WCP' CopyrightURL no + 'WPB' PublisherURL no + 'WXX' UserDefinedURL no + +=head3 ID3 SynLyrics Tags + +The following tags are extracted from synchronized lyrics/text frames. + + Tag Name Writable + -------- -------- + SynchronizedLyricsDescription no + SynchronizedLyricsText no+ + SynchronizedLyricsType no + +=head3 ID3 v2_3 Tags + +ID3 version 2.3 tags. Includes some non-standard tags written by other +software. + + Tag ID Tag Name Writable + ------ -------- -------- + 'APIC' Picture no + 'APIC-1' PictureMIMEType no + 'APIC-2' PictureType no + 'APIC-3' PictureDescription no + 'COMM' Comment no + 'GRP1' Grouping no + 'IPLS' InvolvedPeople no + 'ITNU' iTunesU? no + 'MCDI' MusicCDIdentifier no + 'MVIN' MovementNumber no + 'MVNM' MovementName no + 'OWNE' Ownership no + 'PCNT' PlayCounter no + 'PCST' Podcast? no + 'POPM' Popularimeter no + 'PRIV' Private ID3 Private + 'SYLT' SynLyrics ID3 SynLyrics + 'TALB' Album no + 'TBPM' BeatsPerMinute no + 'TCAT' PodcastCategory no + 'TCMP' Compilation no + 'TCOM' Composer no + 'TCON' Genre no + 'TCOP' Copyright no + 'TDAT' Date no + 'TDES' PodcastDescription no + 'TDLY' PlaylistDelay no + 'TENC' EncodedBy no + 'TEXT' Lyricist no + 'TFLT' FileType no + 'TGID' PodcastID no + 'TIME' Time no + 'TIT1' Grouping no + 'TIT2' Title no + 'TIT3' Subtitle no + 'TKEY' InitialKey no + 'TKWD' PodcastKeywords no + 'TLAN' Language no + 'TLEN' Length no + 'TMED' Media no + 'TOAL' OriginalAlbum no + 'TOFN' OriginalFileName no + 'TOLY' OriginalLyricist no + 'TOPE' OriginalArtist no + 'TORY' OriginalReleaseYear no + 'TOWN' FileOwner no + 'TPE1' Artist no + 'TPE2' Band no + 'TPE3' Conductor no + 'TPE4' InterpretedBy no + 'TPOS' PartOfSet no + 'TPUB' Publisher no + 'TRCK' Track no + 'TRDA' RecordingDates no + 'TRSN' InternetRadioStationName no + 'TRSO' InternetRadioStationOwner no + 'TSIZ' Size no + 'TSO2' AlbumArtistSortOrder no + 'TSOC' ComposerSortOrder no + 'TSRC' ISRC no + 'TSSE' EncoderSettings no + 'TXXX' UserDefinedText no + 'TYER' Year no + 'USER' TermsOfUse no + 'USLT' Lyrics no + 'WCOM' CommercialURL no + 'WCOP' CopyrightURL no + 'WFED' PodcastURL no + 'WOAF' FileURL no + 'WOAR' ArtistURL no + 'WOAS' SourceURL no + 'WORS' InternetRadioStationURL no + 'WPAY' PaymentURL no + 'WPUB' PublisherURL no + 'WXXX' UserDefinedURL no + 'XDOR' OriginalReleaseTime no + 'XOLY' OlympusDSS Olympus DSS + 'XSOA' AlbumSortOrder no + 'XSOP' PerformerSortOrder no + 'XSOT' TitleSortOrder no + +=head3 ID3 Private Tags + +ID3 private (PRIV) tags. ExifTool will decode any private tags found, even +if they do not appear in this table. + + Tag Name Writable + -------- -------- + AverageLevel no + PeakValue no + WM_CollectionGroupID no + WM_CollectionID no + WM_ContentID no + WM_MediaClassPrimaryID no + WM_MediaClassSecondaryID no + WM_Provider no + XMP XMP + +=head3 ID3 v2_4 Tags + +ID3 version 2.4 tags. Includes some non-standard tags written by other +software. + + Tag ID Tag Name Writable + ------ -------- -------- + 'APIC' Picture no + 'APIC-1' PictureMIMEType no + 'APIC-2' PictureType no + 'APIC-3' PictureDescription no + 'COMM' Comment no + 'GRP1' Grouping no + 'ITNU' iTunesU? no + 'MCDI' MusicCDIdentifier no + 'MVIN' MovementNumber no + 'MVNM' MovementName no + 'OWNE' Ownership no + 'PCNT' PlayCounter no + 'PCST' Podcast? no + 'POPM' Popularimeter no + 'PRIV' Private ID3 Private + 'RVA2' RelativeVolumeAdjustment no + 'SYLT' SynLyrics ID3 SynLyrics + 'TALB' Album no + 'TBPM' BeatsPerMinute no + 'TCAT' PodcastCategory no + 'TCMP' Compilation no + 'TCOM' Composer no + 'TCON' Genre no + 'TCOP' Copyright no + 'TDEN' EncodingTime no + 'TDES' PodcastDescription no + 'TDLY' PlaylistDelay no + 'TDOR' OriginalReleaseTime no + 'TDRC' RecordingTime no + 'TDRL' ReleaseTime no + 'TDTG' TaggingTime no + 'TENC' EncodedBy no + 'TEXT' Lyricist no + 'TFLT' FileType no + 'TGID' PodcastID no + 'TIPL' InvolvedPeople no + 'TIT1' Grouping no + 'TIT2' Title no + 'TIT3' Subtitle no + 'TKEY' InitialKey no + 'TKWD' PodcastKeywords no + 'TLAN' Language no + 'TLEN' Length no + 'TMCL' MusicianCredits no + 'TMED' Media no + 'TMOO' Mood no + 'TOAL' OriginalAlbum no + 'TOFN' OriginalFileName no + 'TOLY' OriginalLyricist no + 'TOPE' OriginalArtist no + 'TOWN' FileOwner no + 'TPE1' Artist no + 'TPE2' Band no + 'TPE3' Conductor no + 'TPE4' InterpretedBy no + 'TPOS' PartOfSet no + 'TPRO' ProducedNotice no + 'TPUB' Publisher no + 'TRCK' Track no + 'TRSN' InternetRadioStationName no + 'TRSO' InternetRadioStationOwner no + 'TSO2' AlbumArtistSortOrder no + 'TSOA' AlbumSortOrder no + 'TSOC' ComposerSortOrder no + 'TSOP' PerformerSortOrder no + 'TSOT' TitleSortOrder no + 'TSRC' ISRC no + 'TSSE' EncoderSettings no + 'TSST' SetSubtitle no + 'TXXX' UserDefinedText no + 'USER' TermsOfUse no + 'USLT' Lyrics no + 'WCOM' CommercialURL no + 'WCOP' CopyrightURL no + 'WFED' PodcastURL no + 'WOAF' FileURL no + 'WOAR' ArtistURL no + 'WOAS' SourceURL no + 'WORS' InternetRadioStationURL no + 'WPAY' PaymentURL no + 'WPUB' PublisherURL no + 'WXXX' UserDefinedURL no + 'XDOR' OriginalReleaseTime no + 'XOLY' OlympusDSS Olympus DSS + 'XSOA' AlbumSortOrder no + 'XSOP' PerformerSortOrder no + 'XSOT' TitleSortOrder no + +=head3 ID3 Lyrics3 Tags + +ExifTool extracts Lyrics3 version 1.00 and 2.00 tags from any file that +supports ID3. See L<https://id3.org/Lyrics3> for the specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'AUT' Author no + 'CRC' CRC no + 'EAL' ExtendedAlbumName no + 'EAR' ExtendedArtistName no + 'ETT' ExtendedTrackTitle no + 'IMG' AssociatedImageFile no + 'IND' Indications no + 'INF' AdditionalInfo no + 'LYR' Lyrics no + +=head2 ITC Tags + +This information is found in iTunes Cover Flow data files. + + Tag ID Tag Name Writable + ------ -------- -------- + 'data' ImageData no + 'itch' Itch ITC Header + 'item' Item ITC Item + +=head3 ITC Header Tags + + Index1 Tag Name Writable + ------ -------- -------- + 16 DataType no + +=head3 ITC Item Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 LibraryID no + 2 TrackID no + 4 DataLocation no + 5 ImageType no + 7 ImageWidth no + 8 ImageHeight no + +=head2 QuickTime Tags + +The QuickTime format is used for many different types of audio, video and +image files (most notably, MOV/MP4 videos and HEIC/CR3 images). ExifTool +extracts standard meta information and a variety of audio, video and image +parameters, as well as proprietary information written by many camera +models. Tags with a question mark after their name are not extracted unless +the Unknown option is set. + +When writing, ExifTool creates both QuickTime and XMP tags by default, but +the group may be specified to write one or the other separately. If no +location is specified, newly created QuickTime tags are added in the +L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> location if +possible, otherwise in +L<UserData|Image::ExifTool::TagNames/QuickTime UserData Tags>, and +finally in L<Keys|Image::ExifTool::TagNames/QuickTime Keys Tags>, +but this order may be changed by setting the PREFERRED level of the +appropriate table in the config file (see +example.config in the full distribution for an +example). Note that some tags with the same name but different ID's may +exist in the same location, but the family 7 group names may be used to +differentiate these. ExifTool currently writes only top-level metadata in +QuickTime-based files; it extracts other track-specific and timed metadata, +but can not yet edit tags in these locations (with the exception of +track-level date/time tags). + +Beware that the Keys tags are actually stored inside the ItemList in the +file, so deleting the ItemList group as a block (ie. C<-ItemList:all=>) also +deletes Keys tags. Instead, to preserve Keys tags the ItemList tags may be +deleted individually with C<-QuickTime:ItemList:all=>. + +Alternate language tags may be accessed for +L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> and +L<Keys|Image::ExifTool::TagNames/QuickTime Keys Tags> tags by adding +a 3-character ISO 639-2 language code and an optional ISO 3166-1 alpha 2 +country code to the tag name (eg. "ItemList:Artist-deu" or +"ItemList::Artist-deu-DE"). Most +L<UserData|Image::ExifTool::TagNames/QuickTime UserData Tags> tags support a +language code, but without a country code. If no language code is specified +when writing, the default language is written and alternate languages for +the tag are deleted. Use the "und" language code to write the default +language without deleting alternate languages. Note that "eng" is treated +as a default language when reading, but not when writing. + +According to the specification, integer-format QuickTime date/time tags +should be stored as UTC. Unfortunately, digital cameras often store local +time values instead (presumably because they don't know the time zone). For +this reason, by default ExifTool does not assume a time zone for these +values. However, if the API QuickTimeUTC option is set, then ExifTool will +assume these values are properly stored as UTC, and will convert them to +local time when extracting. + +When writing string-based date/time tags, the system time zone is added if +the PrintConv option is enabled and no time zone is specified. This is +because Apple software may display crazy values if the time zone is missing +for some tags. + +By default ExifTool will remove null padding from some QuickTime containers +in Canon CR3 files when writing, but the +QuickTimePad option may be used to preserve +the original size by padding with nulls if necessary. + +See +L<https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/> +for the official specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'GPS ' GPSDataList2? no + 'IDIT' DateTimeOriginal string + 'PICT' PreviewPICT no + '_htc' HTCInfo QuickTime HTCInfo + 'ardt' ARDroneFile no + 'frea' Kodak_frea Kodak frea + 'free' KodakFree Kodak Free + Pittasoft QuickTime Pittasoft + ThumbnailImage no + Free? no + 'ftyp' FileType QuickTime FileType + 'gps0' GPSTrack QuickTime Stream + 'gsen' GSensor QuickTime Stream + 'junk' Junk? no + 'mdat' MediaData? no + 'mdat-offset' MediaDataOffset no + 'mdat-size' MediaDataSize no + 'meco' OtherMeta QuickTime OtherMeta + 'meta' Meta QuickTime Meta + 'moof' MovieFragment QuickTime MovieFragment + 'moov' Movie QuickTime Movie + 'mpvd' MotionPhotoVideo yes + 'pict' PreviewPICT no + 'pnot' Preview QuickTime Preview + 'prrt' ARDroneTelemetry no + 'sefd' SamsungTrailer Samsung Trailer + 'skip' CanonSkip Canon Skip + PreviewImage no + SkipInfo - + Skip? no + 'thm ' ThumbnailImage no + 'thum' ThumbnailImage no + 'udat' GPSLog no + 'udta' KenwoodData QuickTime Stream + FLIRData FLIR UserData + 'uuid' XMP XMP + UUID-PROF QuickTime Profile + UUID-Flip QuickTime Flip + UUID-Canon2 Canon uuid2 + SensorData QuickTime Tags360Fly + SensorData no + PreviewImage no + UUID-Unknown? no + 'wide' Wide? no + +=head3 QuickTime Stream Tags + +The tags below are extracted from timed metadata in QuickTime and other +formats of video files when the ExtractEmbedded option is used. Although +most of these tags are combined into the single table below, ExifTool +currently reads 66 different formats of timed GPS metadata from video files. + + Tag Name Writable + -------- -------- + Accelerometer no + AccelerometerData no + AngularVelocity no + CTMD Canon CTMD + CameraDateTime no + Car no + Distance no + ExposureCompensation no + ExposureTime no + FNumber no + FrameNumber no + GPSAltitude no + GPSDOP no + GPSDateTime no + GPSLatitude no + GPSLongitude no + GPSSatellites no + GPSSpeed no + GPSSpeedRef no + GPSTimeStamp no + GPSTrack no + GPSTrackRef no + GSensor no + INSV QuickTime INSV_MakerNotes + ISO no + JpgFromRaw no + KiloCalories no + PreviewInfo QuickTime PreviewInfo + RVMI_gReV QuickTime RVMI_gReV + RVMI_sReV QuickTime RVMI_sReV + RawGSensor no + SampleDateTime no + SampleDuration no + SampleTime no + Text no + TimeCode no + Unknown00? no + Unknown01? no + Unknown02? no + Unknown03? no + UserLabel no + VerticalSpeed no + VideoTimeStamp no + camm0 QuickTime camm0 + camm1 QuickTime camm1 + camm2 QuickTime camm2 + camm3 QuickTime camm3 + camm4 QuickTime camm4 + camm5 QuickTime camm5 + camm6 QuickTime camm6 + camm7 QuickTime camm7 + fdsc GoPro fdsc + gpmd_Kingslim QuickTime Stream + gpmd_Rove QuickTime Stream + gpmd_FMAS QuickTime Stream + gpmd_GoPro GoPro GPMF + marl QuickTime marl + mebx QuickTime Keys + mett Parrot mett + rtmd Sony rtmd + tx3g QuickTime tx3g + +=head3 QuickTime INSV_MakerNotes Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x000a SerialNumber no + 0x0012 Model no + 0x001a Firmware no + 0x002a Parameters no + +=head3 QuickTime PreviewInfo Tags + +Preview stored by TomTom Bandit ActionCam. + + Index1 Tag Name Writable + ------ -------- -------- + 8 PreviewImage no + +=head3 QuickTime RVMI_gReV Tags + +GPS information extracted from the RVMI box of MOV videos. + + Index1 Tag Name Writable + ------ -------- -------- + 4 GPSLatitude no + 8 GPSLongitude no + 16 GPSSpeed no + 18 GPSTrack no + +=head3 QuickTime RVMI_sReV Tags + +G-sensor information extracted from the RVMI box of MOV videos. + + Index1 Tag Name Writable + ------ -------- -------- + 4 GSensor no + +=head3 QuickTime camm0 Tags + +The camm0 through camm7 tables define tags extracted from the Google Street +View Camera Motion Metadata of MP4 videos. See +L<https://developers.google.com/streetview/publish/camm-spec> for the +specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 AngleAxis no + +=head3 QuickTime camm1 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 PixelExposureTime no + 0x0008 RollingShutterSkewTime no + +=head3 QuickTime camm2 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 AngularVelocity no + +=head3 QuickTime camm3 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 Acceleration no + +=head3 QuickTime camm4 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 Position no + +=head3 QuickTime camm5 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 GPSLatitude no + 0x000c GPSLongitude no + 0x0014 GPSAltitude no + +=head3 QuickTime camm6 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 GPSDateTime no + 0x000c GPSMeasureMode no + 0x0010 GPSLatitude no + 0x0018 GPSLongitude no + 0x0020 GPSAltitude no + 0x0024 GPSHorizontalAccuracy no + 0x0028 GPSVerticalAccuracy no + 0x002c GPSVelocityEast no + 0x0030 GPSVelocityNorth no + 0x0034 GPSVelocityUp no + 0x0038 GPSSpeedAccuracy no + +=head3 QuickTime camm7 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 MagneticField no + +=head3 QuickTime marl Tags + +Tags extracted from the marl ctbx timed metadata of GM cars. + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 QuickTime Keys Tags + +This directory contains a list of key names which are used to decode tags +written by the "mdta" handler. Also in this table are a few tags found in +timed metadata that are not yet writable by ExifTool. The prefix of +"com.apple.quicktime." has been removed from the TagID's below. These tags +support alternate languages in the same way as the +L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> tags. Note +that by default, +L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> and +L<UserData|Image::ExifTool::TagNames/QuickTime UserData Tags> tags are +preferred when writing, so to create a tag when a same-named tag exists in +either of these tables, either the "Keys" location must be specified (eg. +C<-Keys:Author=Phil> on the command line), or the PREFERRED level must be +changed via the config file. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Encoded_With' EncodedWith yes + 'album' Album yes + 'apple.photos.variation-identifier' + ApplePhotosVariationIdentifier int64s + 'artist' Artist yes + 'artwork' Artwork yes + 'author' Author yes + 'camera.framereadouttimeinmicroseconds' + FrameReadoutTime yes + 'camera.identifier' CameraIdentifier yes + 'collection.user' UserCollection yes + 'com.android.version' AndroidVersion yes + 'com.apple.photos.captureMode' CaptureMode yes + 'comment' Comment yes + 'content.identifier' ContentIdentifier yes + 'copyright' Copyright yes + 'creationdate' CreationDate yes + 'description' Description yes + 'detected-face' FaceInfo QuickTime FaceInfo + 'detected-face.bounds' DetectedFaceBounds no + 'detected-face.face-id' DetectedFaceID no + 'detected-face.roll-angle' + DetectedFaceRollAngle no + 'detected-face.yaw-angle' + DetectedFaceYawAngle no + 'direction.facing' CameraDirection yes + 'direction.motion' CameraMotion yes + 'director' Director yes + 'displayname' DisplayName yes + 'genre' Genre yes + 'information' Information yes + 'keywords' Keywords yes + 'live-photo-info' LivePhotoInfo no + 'live-photo.auto' LivePhotoAuto int8u + 'live-photo.vitality-score' + LivePhotoVitalityScore float + 'live-photo.vitality-scoring-version' + LivePhotoVitalityScoringVersion int64s + 'location.ISO6709' GPSCoordinates yes + 'location.accuracy.horizontal' + LocationAccuracyHorizontal yes + 'location.body' LocationBody yes + 'location.date' LocationDate yes + 'location.name' LocationName yes + 'location.note' LocationNote yes + 'location.role' LocationRole yes + 'make' Make yes + 'model' Model yes + 'player.movie.audio.balance' Balance yes + 'player.movie.audio.bass' Bass yes + 'player.movie.audio.gain' AudioGain yes + 'player.movie.audio.mute' Mute int8u + 'player.movie.audio.pitchshift' PitchShift yes + 'player.movie.audio.treble' Treble yes + 'player.movie.visual.brightness' Brightness yes + 'player.movie.visual.color' Color yes + 'player.movie.visual.contrast' Contrast yes + 'player.movie.visual.tint' Tint yes + 'player.version' PlayerVersion yes + 'producer' Producer yes + 'publisher' Publisher yes + 'rating.user' UserRating yes + 'software' Software yes + 'still-image-time' StillImageTime no + 'title' Title yes + 'version' Version yes + 'video-orientation' VideoOrientation no + 'year' Year yes + +=head3 QuickTime FaceInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'crec' FaceRec QuickTime FaceRec + +=head3 QuickTime FaceRec Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'cits' FaceItem QuickTime Keys + +=head3 QuickTime tx3g Tags + +Tags extracted from the tx3g sbtl timed metadata of Yuneec drones, and +subtitle text in some other videos. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Alt' GPSAltitude no + 'DateTime' DateTime no + 'GimPitch' GimbalPitch no + 'GimRoll' GimbalRoll no + 'GimYaw' GimbalYaw no + 'Lat' GPSLatitude no + 'Lon' GPSLongitude no + 'Pitch' Pitch no + 'Roll' Roll no + 'Text' Text no + 'Yaw' Yaw no + +=head3 QuickTime HTCInfo Tags + +Tags written by some HTC camera phones. + + Tag ID Tag Name Writable + ------ -------- -------- + 'slmt' Unknown_slmt? no + +=head3 QuickTime Pittasoft Tags + +Tags found in Pittasoft Blackvue dashcam "free" data. + + Tag ID Tag Name Writable + ------ -------- -------- + '3gf ' AccelData QuickTime Stream + 'cprt' Copyright no + 'gps ' GPSLog no + 'ptnm' OriginalFileName no + 'ptrh' Ptrh QuickTime Pittasoft + 'sttm' StartTime no + 'thum' PreviewImage no + +=head3 QuickTime FileType Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 MajorBrand no + 1 MinorVersion no + 2 CompatibleBrands no + +=head3 QuickTime OtherMeta Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'mere' MetaRelation QuickTime MetaRelation + 'meta' Meta QuickTime Meta + +=head3 QuickTime MetaRelation Tags + + Index4 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 QuickTime Meta Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'bxml' BinaryXML? no + 'dinf' DataInfo QuickTime DataInfo + 'free' Free? no + 'hdlr' Handler QuickTime Handler + 'idat' MetaImageSize no + 'iinf' ItemInformation QuickTime ItemInfo + QuickTime ItemInfo + 'iloc' ItemLocation no + 'ilst' ItemList QuickTime ItemList + 'ipmc' IPMPControl? no + 'ipro' ItemProtection? no + 'iprp' ItemProperties QuickTime ItemProp + 'iref' ItemReference QuickTime ItemRef + 'keys' Keys QuickTime Keys + 'pitm' PrimaryItemReference no + 'uuid' MetaVersion no + UUID-Unknown? no + 'xml ' XML XMP XML + +=head3 QuickTime DataInfo Tags + +MP4 data information box. + + Tag ID Tag Name Writable + ------ -------- -------- + 'dref' DataRef QuickTime DataRef + +=head3 QuickTime DataRef Tags + +MP4 data reference box. + + Tag ID Tag Name Writable + ------ -------- -------- + "url\0" URL no + 'url ' URL no + 'urn ' URN no + +=head3 QuickTime Handler Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 HandlerClass no + 8 HandlerType no + 12 HandlerVendorID no + 24 HandlerDescription no + +=head3 QuickTime ItemInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'infe' ItemInfoEntry no + +=head3 QuickTime ItemList Tags + +This is the preferred location for creating new QuickTime tags. Tags in +this table support alternate languages which are accessed by adding a +3-character ISO 639-2 language code and an optional ISO 3166-1 alpha 2 +country code to the tag name (eg. "ItemList:Title-fra" or +"ItemList::Title-fra-FR"). When creating a new Meta box to contain the +ItemList directory, by default ExifTool adds an 'mdir' (Metadata) Handler +box because Apple software may ignore ItemList tags otherwise, but the API +QuickTimeHandler option may be set to 0 to avoid this. + + Tag ID Tag Name Writable + ------ -------- -------- + '----' iTunesInfo QuickTime iTunesInfo + '@PST' ParentShortTitle string + '@ppi' ParentProductID string + '@pti' ParentTitle string + '@sti' ShortTitle string + 'AACR' Unknown_AACR? string + 'CDEK' Unknown_CDEK? string + 'CDET' Unknown_CDET? string + 'GUID' GUID string + 'VERS' ProductVersion string + 'aART' AlbumArtist string + 'akID' AppleStoreAccountType int8s + 'albm' Album string/ + 'apID' AppleStoreAccount string + 'atID' ArtistID int32s + 'auth' Author string + 'catg' Category string + 'cmID' ComposerID string + 'cnID' AppleStoreCatalogID int32s + 'covr' CoverArt string + 'cpil' Compilation int8s + 'cprt' Copyright string + 'desc' Description string/ + 'disk' DiskNumber undef + 'dscp' Description string/ + 'egid' EpisodeGlobalUniqueID string + 'geID' GenreID int32s + 'gnre' Genre undef/ + 'grup' Grouping string/ + 'gshh' GoogleHostHeader string + 'gspm' GooglePingMessage string + 'gspu' GooglePingURL string + 'gssd' GoogleSourceData string + 'gsst' GoogleStartTime string + 'gstd' GoogleTrackDuration string + 'hdvd' HDVideo int8s + 'itnu' iTunesU int8s + 'keyw' Keyword string + 'ldes' LongDescription string + 'pcst' Podcast int8s + 'perf' Performer string + 'pgap' PlayGap int8s + 'plID' AlbumID int32s[2] + 'prID' ProductID string + 'purd' PurchaseDate string + 'purl' PodcastURL string + 'rate' RatingPercent string + 'rldt' ReleaseDate string + 'rtng' Rating int8s + 'sdes' StoreDescription string + 'sfID' AppleStoreCountry int32s + 'shwm' ShowMovement int8s + 'soaa' SortAlbumArtist string + 'soal' SortAlbum string + 'soar' SortArtist string + 'soco' SortComposer string + 'sonm' SortName string + 'sosn' SortShow string + 'stik' MediaType int8s + 'titl' Title string/ + 'tmpo' BeatsPerMinute int16s + 'trkn' TrackNumber undef + 'tven' TVEpisodeID string + 'tves' TVEpisode int32s + 'tvnn' TVNetworkName string + 'tvsh' TVShow string + 'tvsn' TVSeason int32u + 'yrrc' Year string + "\xa9ART" Artist string + "\xa9alb" Album string + "\xa9ard" ArtDirector string + "\xa9arg" Arranger string + "\xa9aut" Author string/ + "\xa9cmt" Comment string + "\xa9com" Composer string/ + "\xa9con" Conductor string + "\xa9cpy" Copyright string/ + "\xa9day" ContentCreateDate string + "\xa9des" Description string + "\xa9dir" Director string + "\xa9enc" EncodedBy string + "\xa9gen" Genre string + "\xa9grp" Grouping string + "\xa9lyr" Lyrics string + "\xa9mvc" MovementCount int16s + "\xa9mvi" MovementNumber int16s + "\xa9mvn" MovementName string + "\xa9nam" Title string + "\xa9nrt" Narrator string + "\xa9ope" OriginalArtist string + "\xa9prd" Producer string + "\xa9pub" Publisher string + "\xa9sne" SoundEngineer string + "\xa9sol" Soloist string + "\xa9st3" Subtitle string + "\xa9too" Encoder string + "\xa9trk" Track string + "\xa9wrk" Work string + "\xa9wrt" Composer string + "\xa9xpd" ExecutiveProducer string + "\xa9xyz" GPSCoordinates string + +=head3 QuickTime iTunesInfo Tags + +ExifTool will extract any iTunesInfo tags that exist, even if they are not +defined in this table. These tags belong to the family 1 "iTunes" group, +and are not currently writable. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ARTISTS' Artists no + 'BARCODE' Barcode no + 'CATALOGNUMBER' CatalogNumber no + 'DISCNUMBER' DiscNumber no + 'Dynamic Range (DR)' DynamicRange no + 'Dynamic Range (R128)' DynamicRangeR128 no + 'Encoding Params' EncodingParams QuickTime EncodingParams + 'LABEL' Label no + 'MEDIA' Media no + 'MOOD' Mood no + 'Peak Level (R128)' PeakLevelR128 no + 'Peak Level (Sample)' PeakLevelSample no + 'RATING' Rating no + 'SCRIPT' Script no + 'TRACKNUMBER' TrackNumber no + 'Volume Level (R128)' VolumeLevelR128 no + 'Volume Level (ReplayGain)' ReplayVolumeLevel no + 'iTunEXTC' ContentRating no + 'iTunMOVI' iTunMOVI PLIST + 'iTunNORM' VolumeNormalization no + 'iTunSMPB' iTunSMPB no + 'iTunes_CDDB_1' CDDB1Info no + 'iTunes_CDDB_TrackNumber' CDDBTrackNumber no + 'initialkey' InitialKey no + 'originaldate' OriginalDate no + 'originalyear' OriginalYear no + 'popularimeter' Popularimeter no + 'replaygain_track_gain' ReplayTrackGain no + 'replaygain_track_peak' ReplayTrackPeak no + 'tool' iTunTool no + '~length' Length no + +=head3 QuickTime EncodingParams Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'abrt' AudioAvailableBitRateRange no + 'acbf' AudioBitRateControlMode no + 'acef' AudioExtendFrequencies no + 'brat' AudioCurrentTargetBitRate no + 'cdcv' AudioComponentVersion no + 'cmnc' AudioAvailableNumberChannels no + 'init' AudioIsInitialized no + 'lmrc' AudioDoesSampleRateConversion no + 'mdel' AudioMinimumDelayMode no + 'mnip' AudioMinimumNumberInputPackets no + 'mnop' AudioMinimumNumberOutputPackets no + 'oppr' AudioOutputPrecedence no + 'pad0' AudioZeroFramesPadded no + 'pakb' AudioMaximumPacketByteSize no + 'pakd' AudioRequiresPacketDescription no + 'pakf' AudioPacketFrameSize no + 'prmm' AudioCodecPrimeMethod no + 'srcq' AudioQualitySetting no + 'tbuf' AudioInputBufferSize no + 'ubuf' AudioUsedInputBufferSize no + 'ursr' AudioUseRecommendedSampleRate no + 'vbrq' AudioVBRQuality no + 'vers' AudioEncodingParamsVersion no + 'vpk?' AudioHasVariablePacketByteSizes no + +=head3 QuickTime ItemProp Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'ipco' ItemPropertyContainer QuickTime ItemPropCont + 'ipma' ItemPropertyAssociation no + +=head3 QuickTime ItemPropCont Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'auxC' AuxiliaryImageType no + 'av1C' AV1Configuration QuickTime AV1Config + 'clap' CleanAperture no + 'clli' ContentLightLevel QuickTime ContentLightLevel + 'colr' ICC_Profile ICC_Profile + ColorRepresentation QuickTime ColorRep + 'hvcC' HEVCConfiguration QuickTime HEVCConfig + 'irot' Rotation int8u! + 'ispe' ImageSpatialExtent no + 'pasp' PixelAspectRatio int32u! + 'pixi' ImagePixelDepth no + 'rloc' RelativeLocation no + +=head3 QuickTime AV1Config Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 AV1ConfigurationVersion no + 1 SeqProfile? no + 1.1 SeqLevelIdx0? no + 2 SeqTier0? no + 2.1 HighBitDepth? no + 2.2 TwelveBit? no + 2.3 ChromaFormat no + 2.4 ChromaSamplePosition no + 3 InitialDelaySamples? no + +=head3 QuickTime ContentLightLevel Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 MaxContentLightLevel no + 1 MaxPicAverageLightLevel no + +=head3 QuickTime ColorRep Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ColorProfiles no + 4 ColorPrimaries no + 6 TransferCharacteristics no + 8 MatrixCoefficients no + +=head3 QuickTime HEVCConfig Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 HEVCConfigurationVersion no + 1 GeneralProfileSpace no + 1.1 GeneralTierFlag no + 1.2 GeneralProfileIDC no + 2 GenProfileCompatibilityFlags no + 6 ConstraintIndicatorFlags no + 12 GeneralLevelIDC no + 13 MinSpatialSegmentationIDC no + 15 ParallelismType no + 16 ChromaFormat no + 17 BitDepthLuma no + 18 BitDepthChroma no + 19 AverageFrameRate no + 21 ConstantFrameRate no + 21.1 NumTemporalLayers no + 21.2 TemporalIDNested no + +=head3 QuickTime ItemRef Tags + +The Item reference entries listed in the table below contain information about +the associations between items in the file. This information is used by +ExifTool, but these entries are not extracted as tags. + + Tag ID Tag Name Writable + ------ -------- -------- + 'auxl' AuxiliaryImageRef no + 'cdsc' ContentDescribes no + 'dimg' DerivedImageRef no + 'thmb' ThumbnailRef no + +=head3 QuickTime MovieFragment Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'meta' Meta QuickTime Meta + 'mfhd' MovieFragmentHeader QuickTime MovieFragHdr + 'traf' TrackFragment QuickTime TrackFragment + +=head3 QuickTime MovieFragHdr Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 MovieFragmentSequence no + +=head3 QuickTime TrackFragment Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'meta' Meta QuickTime Meta + +=head3 QuickTime Movie Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'cmov' CompressedMovie QuickTime CMovie + 'gps ' GPSDataList? no + 'htka' HTCTrack QuickTime Track + 'iods' InitialObjectDescriptor? no + 'meco' OtherMeta QuickTime OtherMeta + 'meta' Meta QuickTime Meta + 'mvhd' MovieHeader QuickTime MovieHeader + 'trak' Track QuickTime Track + 'udta' UserData QuickTime UserData + 'uuid' UUID-USMT QuickTime UserMedia + UUID-Canon Canon uuid + GarminGPS QuickTime Stream + GarminGPS no + UUID-Unknown? no + +=head3 QuickTime CMovie Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'dcom' Compression no + +=head3 QuickTime Track Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'mdia' Media QuickTime Media + 'meco' OtherMeta QuickTime OtherMeta + 'meta' Meta QuickTime Meta + 'tapt' TrackAperture QuickTime TrackAperture + 'tkhd' TrackHeader QuickTime TrackHeader + 'tref' TrackRef QuickTime TrackRef + 'udta' UserData QuickTime UserData + 'uuid' UUID-USMT QuickTime UserMedia + SphericalVideoXML XMP + UUID-Unknown? no + +=head3 QuickTime Media Tags + +MP4 media box. + + Tag ID Tag Name Writable + ------ -------- -------- + 'hdlr' Handler QuickTime Handler + 'mdhd' MediaHeader QuickTime MediaHeader + 'minf' MediaInfo QuickTime MediaInfo + +=head3 QuickTime MediaHeader Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 MediaHeaderVersion no + 1 MediaCreateDate int32u + 2 MediaModifyDate int32u + 3 MediaTimeScale no + 4 MediaDuration no + 5 MediaLanguageCode no + +=head3 QuickTime MediaInfo Tags + +MP4 media info box. + + Tag ID Tag Name Writable + ------ -------- -------- + 'dinf' DataInfo QuickTime DataInfo + 'gmhd' GenMediaHeader QuickTime GenMediaHeader + 'hdlr' Handler QuickTime Handler + 'hmhd' HintHeader QuickTime HintHeader + 'nmhd' NullMediaHeader? no + 'smhd' AudioHeader QuickTime AudioHeader + 'stbl' SampleTable QuickTime SampleTable + 'vmhd' VideoHeader QuickTime VideoHeader + +=head3 QuickTime GenMediaHeader Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'gmin' GenMediaInfo QuickTime GenMediaInfo + 'text' Text? no + 'tmcd' TimeCode QuickTime TimeCode + +=head3 QuickTime GenMediaInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 GenMediaVersion no + 1 GenFlags no + 4 GenGraphicsMode no + 6 GenOpColor no + 12 GenBalance no + +=head3 QuickTime TimeCode Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'tcmi' TCMediaInfo QuickTime TCMediaInfo + +=head3 QuickTime TCMediaInfo Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 TextFont no + 6 TextFace no + 8 TextSize no + 12 TextColor no + 18 BackgroundColor no + 24 FontName no + +=head3 QuickTime HintHeader Tags + +MP4 hint media header. + + Index2 Tag Name Writable + ------ -------- -------- + 2 MaxPDUSize no + 3 AvgPDUSize no + 4 MaxBitrate no + 6 AvgBitrate no + +=head3 QuickTime AudioHeader Tags + +MP4 audio media header. + + Index2 Tag Name Writable + ------ -------- -------- + 2 Balance no + +=head3 QuickTime SampleTable Tags + +MP4 sample table box. + + Tag ID Tag Name Writable + ------ -------- -------- + 'co64' ChunkOffset64? no + 'cslg' CompositionToDecodeTimelineMapping? no + 'ctts' CompositionTimeToSample? no + 'padb' SamplePaddingBits? no + 'sbgp' SampleToGroup? no + 'sdtp' IdependentAndDisposableSamples? no + 'sgpd' SampleGroupDescription? no + 'stco' ChunkOffset? no + 'stdp' SampleDegradationPriority? no + 'stps' PartialSyncSamples no + 'stsc' SampleToChunk? no + 'stsd' AudioSampleDesc QuickTime AudioSampleDesc + VideoSampleDesc QuickTime ImageDesc + HintSampleDesc QuickTime HintSampleDesc + MetaSampleDesc QuickTime MetaSampleDesc + OtherSampleDesc QuickTime OtherSampleDesc + 'stsh' ShadowSyncSampleTable? no + 'stss' SyncSampleTable? no + 'stsz' SampleSizes? no + 'stts' VideoFrameRate no + TimeToSampleTable? no + 'stz2' CompactSampleSizes? no + 'subs' Sub-sampleInformation? no + +=head3 QuickTime AudioSampleDesc Tags + +MP4 audio sample description. This hybrid atom contains both data and child +atoms. + + ID/Index Tag Name Writable + -------- -------- -------- + 4 AudioFormat no + 20 AudioVendorID no + 24 AudioChannels no + 26 AudioBitsPerSample no + 32 AudioSampleRate no + 'SA3D' SpatialAudio QuickTime SpatialAudio + 'chan' AudioChannelLayout QuickTime ChannelLayout + 'damr' DecodeConfig QuickTime DecodeConfig + 'pinf' PurchaseInfo QuickTime ProtectionInfo + 'sinf' ProtectionInfo QuickTime ProtectionInfo + 'wave' Wave QuickTime Wave + +=head3 QuickTime SpatialAudio Tags + +Spatial Audio tags. + + Index1 Tag Name Writable + ------ -------- -------- + 0 SpatialAudioVersion no + 1 AmbisonicType no + 2 AmbisonicOrder no + 6 AmbisonicChannelOrdering no + 7 AmbisonicNormalization no + 8 AmbisonicChannels no + 12 AmbisonicChannelMap no + +=head3 QuickTime ChannelLayout Tags + +Audio channel layout. + + Index1 Tag Name Writable + ------ -------- -------- + 4 LayoutFlags no + 6 AudioChannels no + 8 AudioChannelTypes no + 12 NumChannelDescriptions no + 16 Channel1Label no + 20 Channel1Flags no + 24 Channel1Coordinates no + 36 Channel2Label no + 40 Channel2Flags no + 44 Channel2Coordinates no + 56 Channel3Label no + 60 Channel3Flags no + 64 Channel3Coordinates no + 76 Channel4Label no + 80 Channel4Flags no + 84 Channel4Coordinates no + 96 Channel5Label no + 100 Channel5Flags no + 104 Channel5Coordinates no + 116 Channel6Label no + 120 Channel6Flags no + 124 Channel6Coordinates no + 136 Channel7Label no + 140 Channel7Flags no + 144 Channel7Coordinates no + 156 Channel8Label no + 160 Channel8Flags no + 164 Channel8Coordinates no + +=head3 QuickTime DecodeConfig Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 EncoderVendor no + 4 EncoderVersion no + +=head3 QuickTime ProtectionInfo Tags + +Child atoms found in "sinf" and/or "pinf" atoms. + + Tag ID Tag Name Writable + ------ -------- -------- + 'enda' Endianness no + 'frma' OriginalFormat no + 'schi' SchemeInfo QuickTime SchemeInfo + 'schm' SchemeType QuickTime SchemeType + +=head3 QuickTime SchemeInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'cert' Certificate no + 'iviv' InitializationVector no + 'key ' KeyID no + 'name' UserName no + 'righ' Rights QuickTime Rights + 'user' UserID no + +=head3 QuickTime Rights Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'aver' VersionRestrictions no + 'medi' MediaFlags no + 'mode' ModeFlags no + 'plat' Platform no + 'song' ItemID no + 'tool' ItemTool no + 'tran' TransactionID no + 'veID' ItemVendorID no + +=head3 QuickTime SchemeType Tags + + Index1 Tag Name Writable + ------ -------- -------- + 4 SchemeType no + 8 SchemeVersion no + 10 SchemeURL no + +=head3 QuickTime Wave Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'enda' Endianness no + 'frma' PurchaseFileFormat no + +=head3 QuickTime ImageDesc Tags + + ID/Index Tag Name Writable + -------- -------- -------- + 2 CompressorID no + 10 VendorID no + 16 SourceImageWidth no + 17 SourceImageHeight no + 18 XResolution no + 20 YResolution no + 25 CompressorName no + 41 BitDepth no + 'CDI1' CDI1 Canon CDI1 + 'CMP1' CMP1 Canon CMP1 + 'JPEG' JPEGInfo? no + 'avcC' AVCConfiguration? no + 'btrt' BitrateInfo QuickTime Bitrate + 'clap' CleanAperture QuickTime CleanAperture + 'colr' ColorRepresentation QuickTime ColorRep + 'fiel' VideoFieldOrder no + 'gama' Gamma no + 'pasp' PixelAspectRatio no + 'st3d' Stereoscopic3D no + 'sv3d' SphericalVideo QuickTime sv3d + +=head3 QuickTime Bitrate Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 BufferSize no + 1 MaxBitrate no + 2 AverageBitrate no + +=head3 QuickTime CleanAperture Tags + + Index8 Tag Name Writable + ------ -------- -------- + 0 CleanApertureWidth no + 1 CleanApertureHeight no + 2 CleanApertureOffsetX no + 3 CleanApertureOffsetY no + +=head3 QuickTime sv3d Tags + +Tags defined by the Spherical Video V2 specification. See +L<https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md> +for the specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'proj' Projection QuickTime proj + 'svhd' MetadataSource no + +=head3 QuickTime proj Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'cbmp' CubemapProj QuickTime cbmp + 'equi' EquirectangularProj QuickTime equi + 'prhd' ProjectionHeader QuickTime prhd + +=head3 QuickTime cbmp Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 Layout no + 2 Padding no + +=head3 QuickTime equi Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 ProjectionBoundsTop no + 2 ProjectionBoundsBottom no + 3 ProjectionBoundsLeft no + 4 ProjectionBoundsRight no + +=head3 QuickTime prhd Tags + + Index4 Tag Name Writable + ------ -------- -------- + 1 PoseYawDegrees no + 2 PosePitchDegrees no + 3 PoseRollDegrees no + +=head3 QuickTime HintSampleDesc Tags + +MP4 hint sample description. + + ID/Index Tag Name Writable + -------- -------- -------- + 4 HintFormat no + 16 HintTrackVersion no + 20 MaxPacketSize no + 'snro' SequenceNumberRandomOffset no + 'tims' RTPTimeScale no + 'tsro' TimestampRandomOffset no + +=head3 QuickTime MetaSampleDesc Tags + +MP4 metadata sample description. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 MetaFormat no + 0x0008 MetaType no + 'btrt' BitrateInfo QuickTime Bitrate + 'keys' Keys QuickTime Keys + +=head3 QuickTime OtherSampleDesc Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0004 OtherFormat no + 0x0018 PlaybackFrameRate no + 'ftab' FontTable no + 'name' OtherName no + +=head3 QuickTime VideoHeader Tags + +MP4 video media header. + + Index2 Tag Name Writable + ------ -------- -------- + 2 GraphicsMode no + 3 OpColor no + +=head3 QuickTime TrackAperture Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'clef' CleanApertureDimensions no + 'enof' EncodedPixelsDimensions no + 'prof' ProductionApertureDimensions no + +=head3 QuickTime TrackHeader Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 TrackHeaderVersion no + 1 TrackCreateDate int32u + 2 TrackModifyDate int32u + 3 TrackID no + 5 TrackDuration no + 8 TrackLayer no + 9 TrackVolume no + 10 MatrixStructure fixed32s[9]! + 19 ImageWidth no + 20 ImageHeight no + +=head3 QuickTime TrackRef Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'cdsc' ContentDescribes no + 'chap' ChapterListTrackID no + 'mpod' ElementaryStreamTrack no + 'tmcd' TimeCode no + +=head3 QuickTime UserData Tags + +Tag ID's beginning with the copyright symbol (hex 0xa9) are multi-language +text. Alternate language tags are accessed by adding a dash followed by a +3-character ISO 639-2 language code to the tag name. ExifTool will extract +any multi-language user data tags found, even if they aren't in this table. +Note when creating new tags, +L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> tags are +preferred over these, so to create the tag when a same-named ItemList tag +exists, either "UserData" must be specified (eg. C<-UserData:Artist=Monet> +on the command line), or the PREFERRED level must be changed via +the config file. + + Tag ID Tag Name Writable + ------ -------- -------- + '@day' ContentCreateDate string/ + '@mak' Make string/ + '@mod' Model string/ + '@sec' SamsungSec Samsung sec + '@swr' SoftwareVersion string/ + '@xyz' GPSCoordinates string/ + 'AllF' PlayAllFrames int8u + 'CAME' SerialNumberHash string + 'CNCV' CompressorVersion string + 'CNFV' FirmwareVersion string + 'CNMN' Model string/ + 'CNOP' CanonCNOP Canon CNOP + 'CNTH' CanonCNTH Canon CNTH + 'DcMD' KodakDcMD Kodak DcMD + 'FFMV' FujiFilmFFMV FujiFilm FFMV + 'FIRM' FirmwareVersion string/ + "FOV\0" FieldOfView string + 'GPMF' GoProGPMF GoPro GPMF + 'GoPr' GoProType string + 'INFO' SamsungINFO Samsung INFO + 'LEIC' LeicaLEIC Panasonic PANA + 'LENS' LensSerialNumber string + 'LOOP' LoopStyle int32u + 'Lvlm' LevelMeter? rational64s + 'MMA0' MinoltaMMA0 Minolta MMA + 'MMA1' MinoltaMMA1 Minolta MMA + 'MVTG' FujiFilmMVTG EXIF + 'NCDT' NikonNCDT Nikon NCDT + 'PANA' PanasonicPANA Panasonic PANA + 'PENT' PentaxPENT Pentax PENT + 'PXMN' MakerNotePentax5b Pentax + MakerNotePentax5c Pentax + MakerNotePentaxUnknown string + 'PXTH' PentaxPreview Pentax PXTH + 'QVMI' CasioQVMI EXIF + 'RMKN' RicohRMKN EXIF + 'RTHU' PreviewImage no + 'SDLN' PlayMode string + 'SNum' SerialNumber string/ + 'SelO' PlaySelection int8u + 'TAGS' FujiFilmTags FujiFilm MOV + KodakTags Kodak MOV + KonicaMinoltaTags Minolta MOV1 + MinoltaTags Minolta MOV2 + NikonTags Nikon MOV + OlympusTags1 Olympus MOV1 + OlympusTags2 Olympus MOV2 + OlympusTags3 Olympus MP4 + OlympusTags4 Olympus MOV3 + PentaxTags Pentax MOV + SamsungTags Samsung MP4 + SanyoMOV Sanyo MOV + SanyoMP4 Sanyo MP4 + UnknownTags? string + 'TTMD' TomTomMetaData QuickTime TomTom + 'WLOC' WindowLocation int16u + 'XMP_' XMP XMP + 'Xtra' MicrosoftXtra Microsoft Xtra + '_cx_' CX? rational64s + '_cy_' CY? rational64s + '_yaw' Yaw rational64s/ + 'albm' Album string/ + 'albr' AlbumArtist string + 'angl' CameraAngle string + 'apmd' ApertureMode undef + 'auth' Author string/ + 'ccid' ContentID string + 'cdis' ContentDistributorID string + 'chpl' ChapterList no + 'clfn' ClipFileName string + 'clid' ClipID string + 'clsf' Classification undef/ + 'cmid' CameraID string + 'cmnm' Model string/ + 'coll' CollectionName string/ + 'cprt' Copyright string/ + 'cver' CodeVersion string + 'cvru' CoverURI string + 'date' DateTimeOriginal string + 'dscp' Description string/ + 'gnre' Genre string/ + 'hinf' HintTrackInfo QuickTime HintTrackInfo + 'hinv' HintVersion string + 'hnti' HintInfo QuickTime HintInfo + 'htcb' HTCBinary QuickTime HTCBinary + 'icnu' IconURI string + 'infu' InfoURL string + 'kgtt' TrackType string + 'kywd' Keywords no + 'loci' LocationInformation undef/ + 'lrcu' LyricsURI string + 'lvlm' LevelMeter? rational64s + 'manu' Make no + 'mcvr' PreviewImage string + 'meta' Meta QuickTime Meta + 'modl' Model no + 'name' Name string + 'perf' Performer string/ + 'pmcc' GarminSettings string + 'pose' pose Kodak pose + 'ptch' Pitch rational64s/ + 'ptv ' PrintToVideo QuickTime Video + 'rads' Rads? rational64s + 'reel' ReelName string + 'roll' Roll rational64s/ + 'rtng' Rating undef/ + 'scen' Scene string + 'scrn' OlympusPreview Olympus scrn + 'shot' ShotName string + 'slno' SerialNumber string + 'smta' SamsungSmta Samsung smta + 'tags' Audible_tags Audible tags + 'thmb' MakerNotePentax5a Pentax + OlympusThumbnail Olympus thmb + ThumbnailImage string + ThumbnailPNG string + UnknownThumbnail string + 'titl' Title string/ + 'urat' UserRating undef/ + 'uuid' GarminSoftware string + UUID-Unknown? no + 'vndr' Vendor string + 'vrot' AccelerometerData no + 'yrrc' Year undef/ + "\xa9ART" Artist string + "\xa9TIM" StartTimecode string + "\xa9TSC" StartTimeScale string + "\xa9TSZ" StartTimeSampleSize string + "\xa9alb" Album string + "\xa9arg" Arranger string + "\xa9ark" ArrangerKeywords string + "\xa9cmt" Comment string + "\xa9cok" ComposerKeywords string + "\xa9com" Composer string + "\xa9cpy" Copyright string + "\xa9day" ContentCreateDate string + "\xa9dir" Director string + "\xa9ed1" Edit1 string + "\xa9ed2" Edit2 string + "\xa9ed3" Edit3 string + "\xa9ed4" Edit4 string + "\xa9ed5" Edit5 string + "\xa9ed6" Edit6 string + "\xa9ed7" Edit7 string + "\xa9ed8" Edit8 string + "\xa9ed9" Edit9 string + "\xa9enc" EncoderID string + "\xa9fmt" Format string + "\xa9fpt" Pitch string + "\xa9frl" Roll string + "\xa9fyw" Yaw string + "\xa9gen" Genre string + "\xa9gpt" CameraPitch string + "\xa9grl" CameraRoll string + "\xa9grp" Grouping string + "\xa9gyw" CameraYaw string + "\xa9inf" Information string + "\xa9isr" ISRCCode string + "\xa9lab" RecordLabelName string + "\xa9lal" RecordLabelURL string + "\xa9lyr" Lyrics string + "\xa9mak" Make string + "\xa9mal" MakerURL string + "\xa9mdl" Model string/ + "\xa9mod" Model string + "\xa9nam" Title string + "\xa9pdk" ProducerKeywords string + "\xa9phg" RecordingCopyright string + "\xa9prd" Producer string + "\xa9prf" Performers string + "\xa9prk" PerformerKeywords string + "\xa9prl" PerformerURL string + "\xa9req" Requirements string + "\xa9snk" SubtitleKeywords string + "\xa9snm" Subtitle string + "\xa9src" SourceCredits string + "\xa9swf" SongWriter string + "\xa9swk" SongWriterKeywords string + "\xa9swr" SoftwareVersion string + "\xa9too" Encoder string + "\xa9trk" Track string + "\xa9wrt" Composer string/ + "\xa9xsp" SpeedX string + "\xa9xyz" GPSCoordinates string + "\xa9ysp" SpeedY string + "\xa9zsp" SpeedZ string + +=head3 QuickTime TomTom Tags + +Tags found in TomTom Bandit Action Cam MP4 videos. + + Tag ID Tag Name Writable + ------ -------- -------- + 'TTAD' TomTomAD QuickTime Stream + 'TTHL' TomTomHL? no + 'TTID' TomTomID no + 'TTVD' TomTomVD no + 'TTVI' TomTomVI? no + +=head3 QuickTime HintTrackInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'dimm' ImmediateDataBytes no + 'dmax' LargestPacketDuration no + 'dmed' MediaTrackBytes no + 'drep' RepeatedDataBytes no + 'maxr' MaxDataRate no + 'npck' NumPackets no + 'nump' NumPackets no + 'payt' PayloadType no + 'pmax' LargestPacketSize no + 'tmax' MaxTransmissionTime no + 'tmin' MinTransmissionTime no + 'totl' TotalBytes no + 'tpaY' TotalBytesNoRTPHeaders no + 'tpay' TotalBytesNoRTPHeaders no + 'tpyl' TotalBytesNoRTPHeaders no + 'trpY' TotalBytes no + 'trpy' TotalBytes no + +=head3 QuickTime HintInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'rtp ' RealtimeStreamingProtocol no + 'sdp ' StreamingDataProtocol no + +=head3 QuickTime HTCBinary Tags + + Index4 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 QuickTime Video Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 DisplaySize no + 6 SlideShow no + +=head3 QuickTime UserMedia Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'MTDT' MetaData QuickTime MetaData + +=head3 QuickTime MetaData Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 Title no + 0x0003 ProductionDate no + 0x0004 Software no + 0x0005 Product no + 0x000a TrackProperty no + 0x000b TimeZone no + 0x000c ModifyDate no + +=head3 QuickTime MovieHeader Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 MovieHeaderVersion no + 1 CreateDate int32u + 2 ModifyDate int32u + 3 TimeScale no + 4 Duration no + 5 PreferredRate no + 6 PreferredVolume no + 9 MatrixStructure no + 18 PreviewTime no + 19 PreviewDuration no + 20 PosterTime no + 21 SelectionTime no + 22 SelectionDuration no + 23 CurrentTime no + 24 NextTrackID no + +=head3 QuickTime Preview Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 PreviewDate int32u + 2 PreviewVersion no + 3 PreviewAtomType no + 5 PreviewAtomIndex no + +=head3 QuickTime SkipInfo Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'thma' ThumbnailImage no + 'ver ' Version no + +=head3 QuickTime Profile Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'APRF' AudioProfile QuickTime AudioProf + 'FPRF' FileGlobalProfile QuickTime FileProf + 'OLYM' OlympusOLYM Olympus OLYM + 'VPRF' VideoProfile QuickTime VideoProf + +=head3 QuickTime AudioProf Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 AudioProfileVersion? no + 1 AudioTrackID no + 2 AudioCodec no + 3 AudioCodecInfo? no + 4 AudioAttributes no + 5 AudioAvgBitrate no + 6 AudioMaxBitrate no + 7 AudioSampleRate no + 8 AudioChannels no + +=head3 QuickTime FileProf Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 FileProfileVersion? no + 1 FileFunctionFlags no + +=head3 QuickTime VideoProf Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 VideoProfileVersion? no + 1 VideoTrackID no + 2 VideoCodec no + 3 VideoCodecInfo? no + 4 VideoAttributes no + 5 VideoAvgBitrate no + 6 VideoMaxBitrate no + 7 VideoAvgFrameRate no + 8 VideoMaxFrameRate no + 9 VideoSize no + 10 PixelAspectRatio no + +=head3 QuickTime Flip Tags + +Found in MP4 files from Flip Video cameras. + + Index4 Tag Name Writable + ------ -------- -------- + 1 PreviewImageWidth no + 2 PreviewImageHeight no + 13 PreviewImageLength no + 14 SerialNumber no + 28 PreviewImage no + +=head3 QuickTime Tags360Fly Tags + +Timed metadata found in MP4 videos from the 360Fly. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 Accel360Fly QuickTime Accel360Fly + 0x0002 Gyro360Fly QuickTime Gyro360Fly + 0x0003 Mag360Fly QuickTime Mag360Fly + 0x0005 GPS360Fly QuickTime GPS360Fly + 0x0006 Rot360Fly QuickTime Rot360Fly + 0x00fa Fusion360Fly QuickTime Fusion360Fly + +=head3 QuickTime Accel360Fly Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 AccelMode? no + 2 SampleTime no + 10 AccelYPR no + +=head3 QuickTime Gyro360Fly Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 GyroMode? no + 2 SampleTime no + 10 GyroYPR no + +=head3 QuickTime Mag360Fly Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 MagMode? no + 2 SampleTime no + 10 MagnetometerXYZ no + +=head3 QuickTime GPS360Fly Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 GPSMode? no + 2 SampleTime no + 10 GPSLatitude no + 14 GPSLongitude no + 18 GPSAltitude no + 22 GPSSpeed no + 24 GPSTrack no + 26 Acceleration no + +=head3 QuickTime Rot360Fly Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 RotMode? no + 2 SampleTime no + 10 RotationXYZ no + +=head3 QuickTime Fusion360Fly Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 FusionMode? no + 2 SampleTime no + 10 FusionYPR no + +=head3 QuickTime ImageFile Tags + +Tags used in QTIF QuickTime Image Files. + + Tag ID Tag Name Writable + ------ -------- -------- + 'idat' ImageData no + 'idsc' ImageDescription QuickTime ImageDesc + 'iicc' ICC_Profile ICC_Profile + +=head2 RIFF Tags + +The RIFF container format is used various types of fines including AVI, WAV, +WEBP, LA, OFR, PAC and WV. According to the EXIF specification, Meta +information is embedded in two types of RIFF C<LIST> chunks: C<INFO> and +C<exif>, and information about the audio content is stored in the C<fmt > +chunk. As well as this information, some video information and proprietary +manufacturer-specific information is also extracted. + +Large AVI videos may be a concatenation of two or more RIFF chunks. For +these files, information is extracted from subsequent RIFF chunks as +sub-documents, but the Duration is calculated for the full video. + +ExifTool currently has the ability to write EXIF, XMP and ICC_Profile +metadata to WEBP images, but can't yet write to other RIFF-based formats. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ALPH' ALPH RIFF ALPH + 'ANIM' ANIM RIFF ANIM + 'ANMF' ANMF RIFF ANMF + 'CSET' CharacterSet RIFF CSET + 'EXIF' EXIF EXIF + UnknownEXIF no + 'ICCP' ICC_Profile ICC_Profile + 'IDIT' DateTimeOriginal no + 'JUNK' OlympusJunk Olympus AVI + CasioJunk EXIF + RicohJunk Ricoh AVI + PentaxJunk Pentax Junk + PentaxJunk2 Pentax Junk2 + LucasJunk QuickTime Stream + TextJunk no + 'JUNQ' OldXMP no + 'LIST_INF0' Info RIFF Info + 'LIST_INFO' Info RIFF Info + 'LIST_Tdat' Tdat RIFF Tdat + 'LIST_adtl' AssociatedDataList RIFF + 'LIST_exif' Exif RIFF Exif + 'LIST_hdrl' Hdrl RIFF Hdrl + 'LIST_hydt' PentaxData Pentax AVI + 'LIST_ncdt' NikonData Nikon AVI + 'LIST_pntx' PentaxData2 Pentax AVI + 'SGLT' BikeBroAccel QuickTime Stream + 'SLLT' BikeBroGPS QuickTime Stream + 'VP8 ' VP8Bitstream RIFF VP8 + 'VP8L' VP8L RIFF VP8L + 'VP8X' VP8X RIFF VP8X + 'XMP ' XMP XMP + '_PMX' XMP XMP + 'aXML' AXML XMP XML + 'acid' Acidizer RIFF Acidizer + 'afsp' Afsp no + 'bext' BroadcastExtension RIFF BroadcastExt + 'cue ' CuePoints no + 'ds64' DataSize64 RIFF DS64 + 'fact' NumberOfSamples no + 'fmt ' AudioFormat RIFF AudioFormat + 'gps0' GPSTrack QuickTime Stream + 'gsen' GSensor QuickTime Stream + 'guan' Guano no + 'iXML' IXML XMP XML + 'id3 ' ID3 ID3 + 'inst' Instrument RIFF Instrument + 'labl' CuePointLabel no + 'list' ListType no + 'ltxt' LabeledText no + 'note' CuePointNote no + 'olym' Olym Olympus WAV + 'plst' Playlist no + 'smpl' Sampler RIFF Sampler + 'tx_USER' UserText RIFF UserText + 'tx_Unknown' Text no + +=head3 RIFF ALPH Tags + +WebP alpha chunk. + + Index1 Tag Name Writable + ------ -------- -------- + 0 AlphaPreprocessing no + 0.1 AlphaFiltering no + 0.2 AlphaCompression no + +=head3 RIFF ANIM Tags + +WebP animation chunk. + + Index1 Tag Name Writable + ------ -------- -------- + 0 BackgroundColor no + 4 AnimationLoopCount no + +=head3 RIFF ANMF Tags + +WebP animation frame chunk. + + Index1 Tag Name Writable + ------ -------- -------- + 12 Duration no + +=head3 RIFF CSET Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 CodePage no + 1 CountryCode no + 2 LanguageCode no + 3 Dialect no + +=head3 RIFF Info Tags + +RIFF INFO tags found in AVI video and WAV audio files. Tags which are part +of the EXIF 2.3 specification have an underlined Tag Name in the HTML +version of this documentation. Other tags are found in AVI files generated +by some software. + + Tag ID Tag Name Writable + ------ -------- -------- + 'AGES' Rated no + 'CMNT' Comment no + 'CODE' EncodedBy no + 'COMM' Comments no + 'DIRC' Directory no + 'DISP' SoundSchemeTitle no + 'DTIM' DateTimeOriginal no + 'GENR' Genre no + 'IARL' ArchivalLocation no + 'IART' Artist no + 'IAS1' FirstLanguage no + 'IAS2' SecondLanguage no + 'IAS3' ThirdLanguage no + 'IAS4' FourthLanguage no + 'IAS5' FifthLanguage no + 'IAS6' SixthLanguage no + 'IAS7' SeventhLanguage no + 'IAS8' EighthLanguage no + 'IAS9' NinthLanguage no + 'IBSU' BaseURL no + 'ICAS' DefaultAudioStream no + 'ICDS' CostumeDesigner no + 'ICMS' Commissioned no + 'ICMT' Comment no + 'ICNM' Cinematographer no + 'ICNT' Country no + 'ICOP' Copyright no + 'ICRD' DateCreated no + 'ICRP' Cropped no + 'IDIM' Dimensions no + 'IDIT' DateTimeOriginal no + 'IDPI' DotsPerInch no + 'IDST' DistributedBy no + 'IEDT' EditedBy no + 'IENC' EncodedBy no + 'IENG' Engineer no + 'IGNR' Genre no + 'IKEY' Keywords no + 'ILGT' Lightness no + 'ILGU' LogoURL no + 'ILIU' LogoIconURL no + 'ILNG' Language no + 'IMBI' MoreInfoBannerImage no + 'IMBU' MoreInfoBannerURL no + 'IMED' Medium no + 'IMIT' MoreInfoText no + 'IMIU' MoreInfoURL no + 'IMUS' MusicBy no + 'INAM' Title no + 'IPDS' ProductionDesigner no + 'IPLT' NumColors no + 'IPRD' Product no + 'IPRO' ProducedBy no + 'IRIP' RippedBy no + 'IRTD' Rating no + 'ISBJ' Subject no + 'ISFT' Software no + 'ISGN' SecondaryGenre no + 'ISHP' Sharpness no + 'ISMP' TimeCode no + 'ISRC' Source no + 'ISRF' SourceForm no + 'ISTD' ProductionStudio no + 'ISTR' Starring no + 'ITCH' Technician no + 'ITRK' TrackNumber no + 'IWMU' WatermarkURL no + 'IWRI' WrittenBy no + 'LANG' Language no + 'LOCA' Location no + 'PRT1' Part no + 'PRT2' NumberOfParts no + 'RATE' Rate no + 'STAR' Starring no + 'STAT' Statistics no + 'TAPE' TapeName no + 'TCDO' EndTimecode no + 'TCOD' StartTimecode no + 'TITL' Title no + 'TLEN' Length no + 'TORG' Organization no + 'TRCK' TrackNumber no + 'TURL' URL no + 'TVER' Version no + 'VMAJ' VegasVersionMajor no + 'VMIN' VegasVersionMinor no + 'YEAR' Year no + +=head3 RIFF Tdat Tags + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 RIFF Exif Tags + +These tags are part of the EXIF 2.3 specification for WAV audio files. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ecor' Make no + 'emdl' Model no + 'emnt' MakerNotes no + 'erel' RelatedImageFile no + 'etim' TimeCreated no + 'eucm' UserComment no + 'ever' ExifVersion no + +=head3 RIFF Hdrl Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'IDIT' DateTimeOriginal no + 'ISMP' TimeCode no + 'LIST_odml' OpenDML RIFF OpenDML + 'LIST_strl' Stream RIFF Stream + 'avih' AVIHeader RIFF AVIHeader + +=head3 RIFF OpenDML Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'dmlh' ExtendedAVIHeader RIFF ExtAVIHdr + +=head3 RIFF ExtAVIHdr Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 TotalFrameCount no + +=head3 RIFF Stream Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'strd' StreamData RIFF StreamData + 'strf' AudioFormat RIFF AudioFormat + VideoFormat BMP + 'strh' StreamHeader RIFF StreamHeader + 'strn' StreamName no + +=head3 RIFF StreamData Tags + +This chunk is used to store proprietary information in AVI videos from some +cameras. The first 4 characters of the data are used as the Tag ID below. + + Tag ID Tag Name Writable + ------ -------- -------- + 'AVIF' AVIF EXIF + 'CASI' CasioData Casio AVI + 'Zora' VendorName no + 'unknown' UnknownData no + +=head3 RIFF AudioFormat Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 Encoding no + 1 NumChannels no + 2 SampleRate no + 4 AvgBytesPerSec no + 7 BitsPerSample no + +=head3 RIFF StreamHeader Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 StreamType no + 1 AudioCodec no + VideoCodec no + Codec no + 5 AudioSampleRate no + VideoFrameRate no + StreamSampleRate no + 8 AudioSampleCount no + VideoFrameCount no + StreamSampleCount no + 10 Quality no + 11 SampleSize no + +=head3 RIFF AVIHeader Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 FrameRate no + 1 MaxDataRate no + 4 FrameCount no + 6 StreamCount no + 8 ImageWidth no + 9 ImageHeight no + +=head3 RIFF VP8 Tags + +This chunk is found in simple-format (lossy) WebP files. See +L<https://developers.google.com/speed/webp/docs/riff_container> for the WebP +container specification. + + Index1 Tag Name Writable + ------ -------- -------- + 0 VP8Version no + 6 ImageWidth no + 6.1 HorizontalScale no + 8 ImageHeight no + 8.1 VerticalScale no + +=head3 RIFF VP8L Tags + +This chunk is found in lossless WebP files. + + Index1 Tag Name Writable + ------ -------- -------- + 1 ImageWidth no + 2 ImageHeight no + +=head3 RIFF VP8X Tags + +This chunk is found in extended WebP files. + + Index1 Tag Name Writable + ------ -------- -------- + 0 WebP_Flags no + 4 ImageWidth no + 6 ImageHeight no + +=head3 RIFF Acidizer Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 AcidizerFlags no + 4 RootNote no + 12 Beats no + 16 Meter no + 20 Tempo no + +=head3 RIFF BroadcastExt Tags + +Information found in the Broadcast Audio Extension chunk (see +L<http://tech.ebu.ch/docs/tech/tech3285.pdf>). + + Index1 Tag Name Writable + ------ -------- -------- + 0 Description no + 256 Originator no + 288 OriginatorReference no + 320 DateTimeOriginal no + 338 TimeReference no + 346 BWFVersion no + 348 BWF_UMID no + 602 CodingHistory no + +=head3 RIFF DS64 Tags + +64-bit data sizes for MBWF/RF64 files. See +L<https://tech.ebu.ch/docs/tech/tech3306-2009.pdf> for the specification. + + Index8 Tag Name Writable + ------ -------- -------- + 0 RIFFSize64 no + 1 DataSize64 no + 2 NumberOfSamples64 no + +=head3 RIFF Instrument Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 UnshiftedNote no + 1 FineTune no + 2 Gain no + 3 LowNote no + 4 HighNote no + 5 LowVelocity no + 6 HighVelocity no + +=head3 RIFF Sampler Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 Manufacturer no + 1 Product no + 2 SamplePeriod no + 3 MIDIUnityNote no + 4 MIDIPitchFraction no + 5 SMPTEFormat no + 6 SMPTEOffset no + 7 NumSampleLoops no + 8 SamplerDataLen no + 9 SamplerData no + +=head3 RIFF UserText Tags + +Tags decoded from the USER-format txts stream written by Momento M6 dashcam. +Extracted only if the ExtractEmbedded option is used. + + Index1 Tag Name Writable + ------ -------- -------- + 28 GPSAltitude no + 40 Accelerometer no + 56 GPSSpeed no + 60 GPSLatitude no + 64 GPSLongitude no + 68 GPSDateTime no + +=head2 FLAC Tags + +Free Lossless Audio Codec (FLAC) meta information. ExifTool also extracts +ID3 information from these files. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 StreamInfo FLAC StreamInfo + 0x0001 Padding? no + 0x0002 Application_riff RIFF + ApplicationUnknown? no + 0x0003 SeekTable? no + 0x0004 VorbisComment Vorbis Comments + 0x0005 CueSheet? no + 0x0006 Picture FLAC Picture + +=head3 FLAC StreamInfo Tags + +FLAC is big-endian, so bit 0 is the high-order bit in this table. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Bit000-015' BlockSizeMin no + 'Bit016-031' BlockSizeMax no + 'Bit032-055' FrameSizeMin no + 'Bit056-079' FrameSizeMax no + 'Bit080-099' SampleRate no + 'Bit100-102' Channels no + 'Bit103-107' BitsPerSample no + 'Bit108-143' TotalSamples no + 'Bit144-271' MD5Signature no + +=head3 FLAC Picture Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 PictureType no + 1 PictureMIMEType no + 2 PictureDescription no + 3 PictureWidth no + 4 PictureHeight no + 5 PictureBitsPerPixel no + 6 PictureIndexedColors no + 7 PictureLength no + 8 Picture no + +=head2 Parrot Tags + +=head3 Parrot mett Tags + +Streaming metadata found in Parrot drone videos. See +L<https://developer.parrot.com/docs/pdraw/metadata.html> for the +specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'P1' ParrotV1 Parrot V1 + 'P2' ParrotV2 Parrot V2 + 'P3' ParrotV3 Parrot V3 + 'E1' ParrotTimeStamp Parrot TimeStamp + 'E2' ParrotFollowMe Parrot FollowMe + 'E3' ParrotAutomation Parrot Automation + 'application/arcore-accel' ARCoreAccel Parrot ARCoreAccel + 'application/arcore-custom-event' + ARCoreCustom Parrot ARCoreCustom + 'application/arcore-gyro' ARCoreGyro Parrot ARCoreGyro + 'application/arcore-video-0' ARCoreVideo Parrot ARCoreVideo + +=head3 Parrot V1 Tags + +Parrot version 1 streaming metadata. + + Index1 Tag Name Writable + ------ -------- -------- + 4 DroneYaw no + 6 DronePitch no + 8 DroneRoll no + 10 CameraPan no + 12 CameraTilt no + 14 FrameView no + 22 ExposureTime no + 24 ISO no + 26 WifiRSSI no + 27 Battery no + 28 GPSLatitude no + 32 GPSLongitude no + 36 GPSAltitude no + 36.1 GPSSatellites no + 40 AltitudeFromTakeOff no + 44 DistanceFromHome no + 48 SpeedX no + 50 SpeedY no + 52 SpeedZ no + 54 Binning no + 54.1 FlyingState no + 55 Animation no + 55.1 PilotingMode no + +=head3 Parrot V2 Tags + +Parrot version 2 basic streaming metadata. + + Index1 Tag Name Writable + ------ -------- -------- + 4 Elevation no + 8 GPSLatitude no + 12 GPSLongitude no + 16 GPSAltitude no + 16.1 GPSSatellites no + 20 GPSVelocityNorth no + 22 GPSVelocityEast no + 24 GPSVelocityDown no + 26 AirSpeed no + 28 DroneQuaternion no + 36 FrameView no + 44 CameraPan no + 46 CameraTilt no + 48 ExposureTime no + 50 ISO no + 52 Binning no + 52.1 FlyingState no + 53 Animation no + 53.1 PilotingMode no + 54 WifiRSSI no + 55 Battery no + 'Groups' Groups no + +=head3 Parrot V3 Tags + +Parrot version 3 basic streaming metadata. + + Index1 Tag Name Writable + ------ -------- -------- + 4 Elevation no + 8 GPSLatitude no + 12 GPSLongitude no + 16 GPSAltitude no + 16.1 GPSSatellites no + 20 GPSVelocityNorth no + 22 GPSVelocityEast no + 24 GPSVelocityDown no + 26 AirSpeed no + 28 DroneQuaternion no + 36 FrameBaseView no + 44 FrameView no + 52 ExposureTime no + 54 ISO no + 56 RedBalance no + 58 BlueBalance no + 60 FOV no + 64 LinkGoodput no + 64.1 LinkQuality no + 68 WifiRSSI no + 69 Battery no + 70 Binning no + 70.1 FlyingState no + 71 Animation no + 71.1 PilotingMode no + +=head3 Parrot TimeStamp Tags + +Parrot streaming metadata timestamp extension. + + Index1 Tag Name Writable + ------ -------- -------- + 4 TimeStamp no + +=head3 Parrot FollowMe Tags + +Parrot streaming metadata follow-me extension. + + Index1 Tag Name Writable + ------ -------- -------- + 4 GPSTargetLatitude no + 8 GPSTargetLongitude no + 12 GPSTargetAltitude no + 16 Follow-meMode no + 17 Follow-meAnimation no + +=head3 Parrot Automation Tags + +Parrot streaming metadata automation extension. + + Index1 Tag Name Writable + ------ -------- -------- + 4 GPSFramingLatitude no + 8 GPSFramingLongitude no + 12 GPSFramingAltitude no + 16 GPSDestLatitude no + 20 GPSDestLongitude no + 24 GPSDestAltitude no + 28 AutomationAnimation no + 29 AutomationFlags no + +=head3 Parrot ARCoreAccel Tags + +ARCore accelerometer data. + + Index1 Tag Name Writable + ------ -------- -------- + 4 AccelerometerUnknown? no + 5 Accelerometer no + +=head3 Parrot ARCoreCustom Tags + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 Parrot ARCoreGyro Tags + +ARCore accelerometer data. + + Index1 Tag Name Writable + ------ -------- -------- + 4 GyroscopeUnknown? no + 5 Gyroscope no + +=head3 Parrot ARCoreVideo Tags + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head2 Ogg Tags + +ExifTool extracts the following types of information from Ogg files. See +L<http://www.xiph.org/vorbis/doc/> for the Ogg specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'FLAC' FLAC FLAC + 'ID3' ID3 ID3 + 'Opus' Opus Opus + 'theora' Theora Theora + 'vorbis' Vorbis Vorbis + +=head2 Vorbis Tags + +Information extracted from Ogg Vorbis files. See +L<http://www.xiph.org/vorbis/doc/> for the Vorbis specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 Identification Vorbis Identification + 0x0003 Comments Vorbis Comments + +=head3 Vorbis Comments Tags + +The tags below are only some common tags found in the Vorbis comments of Ogg +Vorbis and Ogg FLAC audio files, however ExifTool will extract values from +any tag found, even if not listed here. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ACTOR' Actor no + 'ALBUM' Album no + 'ARTIST' Artist no+ + 'COMMENT' Comment no + 'COMPOSER' Composer no + 'CONTACT' Contact no+ + 'COPYRIGHT' Copyright no + 'COVERART' CoverArt no + 'COVERARTMIME' CoverArtMIMEType no + 'DATE' Date no + 'DESCRIPTION' Description no + 'DIRECTOR' Director no + 'ENCODED_BY' EncodedBy no + 'ENCODED_USING' EncodedUsing no + 'ENCODER' Encoder no + 'ENCODER_OPTIONS' EncoderOptions no + 'GENRE' Genre no + 'ISRC' ISRCNumber no + 'LICENSE' License no + 'LOCATION' Location no + 'METADATA_BLOCK_PICTURE' Picture FLAC Picture + 'ORGANIZATION' Organization no + 'PERFORMER' Performer no+ + 'PRODUCER' Producer no + 'REPLAYGAIN_ALBUM_GAIN' ReplayGainAlbumGain no + 'REPLAYGAIN_ALBUM_PEAK' ReplayGainAlbumPeak no + 'REPLAYGAIN_TRACK_GAIN' ReplayGainTrackGain no + 'REPLAYGAIN_TRACK_PEAK' ReplayGainTrackPeak no + 'TITLE' Title no + 'TRACKNUMBER' TrackNumber no + 'VERSION' Version no + 'vendor' Vendor no + +=head3 Vorbis Identification Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 VorbisVersion no + 4 AudioChannels no + 5 SampleRate no + 9 MaximumBitrate no + 13 NominalBitrate no + 17 MinimumBitrate no + +=head2 Opus Tags + +Information extracted from Ogg Opus files. See +L<https://www.opus-codec.org/docs/> for the specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'OpusHead' Header Opus Header + 'OpusTags' Comments Vorbis Comments + +=head3 Opus Header Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 OpusVersion no + 1 AudioChannels no + 4 SampleRate no + 8 OutputGain no + +=head2 Theora Tags + +Information extracted from Ogg Theora video files. See +L<http://www.theora.org/doc/Theora.pdf> for the Theora specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0080 Identification Theora Identification + 0x0081 Comments Vorbis Comments + +=head3 Theora Identification Tags + +Tags extracted from the Theora identification header. + + Index1 Tag Name Writable + ------ -------- -------- + 0 TheoraVersion no + 7 ImageWidth no + 10 ImageHeight no + 13 XOffset no + 14 YOffset no + 15 FrameRate no + 23 PixelAspectRatio no + 29 ColorSpace no + 30 NominalVideoBitrate no + 33 Quality no + 34 PixelFormat no + +=head2 APE Tags + +Tags found in Monkey's Audio (APE) information. Only a few common tags are +listed below, but ExifTool will extract any tag found. ExifTool supports +APEv1 and APEv2 tags, as well as ID3 information in APE files, and will also +read APE metadata from MP3 and MPC files. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Album' Album no + 'Artist' Artist no + 'DURATION' Duration no + 'Genre' Genre no + 'Title' Title no + 'Tool Name' ToolName no + 'Tool Version' ToolVersion no + 'Track' Track no + 'Year' Year no + +=head3 APE NewHeader Tags + +APE MAC audio header for version 3.98 or later. + + Index2 Tag Name Writable + ------ -------- -------- + 0 CompressionLevel no + 2 BlocksPerFrame no + 4 FinalFrameBlocks no + 6 TotalFrames no + 8 BitsPerSample no + 9 Channels no + 10 SampleRate no + +=head3 APE OldHeader Tags + +APE MAC audio header for version 3.97 or earlier. + + Index2 Tag Name Writable + ------ -------- -------- + 0 APEVersion no + 1 CompressionLevel no + 3 Channels no + 4 SampleRate no + 10 TotalFrames no + 12 FinalFrameBlocks no + +=head2 Audible Tags + +ExifTool will extract any information found in the metadata dictionary of +Audible .AA files, even if not listed in the table below. + + Tag ID Tag Name Writable + ------ -------- -------- + '_chapter_count' ChapterCount no + '_cover_art' CoverArt no + 'author' Author no + 'copyright' Copyright no + 'pub_date_start' PublishDateStart no + 'pubdate' PublishDate no + +=head3 Audible tags Tags + +Information found in "tags" atom of Audible M4B audio books. + + Tag ID Tag Name Writable + ------ -------- -------- + 'cvrx' Audible_cvrx Audible cvrx + 'meta' Audible_meta Audible meta + 'tseg' Audible_tseg Audible tseg + +=head3 Audible cvrx Tags + +Audible cover art information in M4B audio books. + + Tag Name Writable + -------- -------- + CoverArt no + CoverArtType no + +=head3 Audible meta Tags + +Information found in Audible M4B "meta" atom. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ALBUMARTIST' AlbumArtist no + 'Album' Album no + 'Artist' Artist no + 'Comment' Comment no + 'Genre' Genre no + 'SUBTITLE' Subtitle no + 'TOOL' CreatorTool no + 'Title' Title no + 'Year' Year no + 'itunesmediatype' iTunesMediaType no + 'track' ChapterName no + +=head3 Audible tseg Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'meta' Audible_meta2 Audible meta + 'tshd' ChapterNumber no + +=head2 MPC Tags + +Tags used in Musepack (MPC) audio files. ExifTool also extracts ID3 and APE +information from these files. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Bit032-063' TotalFrames no + 'Bit080-081' SampleRate no + 'Bit084-087' Quality no + 'Bit088-093' MaxBand no + 'Bit096-111' ReplayGainTrackPeak no + 'Bit112-127' ReplayGainTrackGain no + 'Bit128-143' ReplayGainAlbumPeak no + 'Bit144-159' ReplayGainAlbumGain no + 'Bit179' FastSeek no + 'Bit191' Gapless no + 'Bit216-223' EncoderVersion no + +=head2 MPEG Tags + +The MPEG format doesn't specify any file-level meta information. In lieu of +this, information is extracted from the first audio and video frame headers +in the file. + +=head3 MPEG Audio Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Bit11-12' MPEGAudioVersion no + 'Bit13-14' AudioLayer no + 'Bit16-19' AudioBitrate no + 'Bit20-21' SampleRate no + 'Bit24-25' ChannelMode no + 'Bit26' MSStereo no + 'Bit26-27' ModeExtension no + 'Bit27' IntensityStereo no + 'Bit28' CopyrightFlag no + 'Bit29' OriginalMedia no + 'Bit30-31' Emphasis no + +=head3 MPEG Video Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'Bit00-11' ImageWidth no + 'Bit12-23' ImageHeight no + 'Bit24-27' AspectRatio no + 'Bit28-31' FrameRate no + 'Bit32-49' VideoBitrate no + +=head3 MPEG Xing Tags + +These tags are extracted from the Xing/Info frame. + + Tag Name Writable + -------- -------- + Encoder no + LameHeader MPEG Lame + LameQuality no + LameVBRQuality no + VBRBytes no + VBRFrames no + VBRScale no + +=head3 MPEG Lame Tags + +Tags extracted from Lame 3.90 or later header. + + Index1 Tag Name Writable + ------ -------- -------- + 9 LameMethod no + 10 LameLowPassFilter no + 20 LameBitrate no + 24 LameStereoMode no + +=head2 M2TS Tags + +The MPEG-2 transport stream is used as a container for many different +audio/video formats (including AVCHD). This table lists information +extracted from M2TS files. + + Tag Name Writable + -------- -------- + AudioStreamType no + Duration no + VideoStreamType no + _AC3 M2TS AC3 + _H264 H264 + _MISB MISB + +=head3 M2TS AC3 Tags + +Tags extracted from AC-3 audio streams. + + Tag Name Writable + -------- -------- + AudioBitrate no + AudioChannels no + AudioSampleRate no + SurroundMode no + +=head2 H264 Tags + +Tags extracted from H.264 video streams. The metadata for AVCHD videos is +stored in this stream. + + Tag Name Writable + -------- -------- + ImageHeight no + ImageWidth no + MDPM H264 MDPM + +=head3 H264 MDPM Tags + +The following tags are decoded from the Modified Digital Video Pack Metadata +(MDPM) of the unregistered user data with UUID +17ee8c60f84d11d98cd60800200c9a66 in the H.264 Supplemental Enhancement +Information (SEI). I<[Yes, this description is confusing, but nothing +compared to the challenge of actually decoding the data!]> This information +may exist at regular intervals through the entire video, but only the first +occurrence is extracted unless the ExtractEmbedded (-ee) option is used (in +which case subsequent occurrences are extracted as sub-documents). + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0013 TimeCode no + 0x0018 DateTimeOriginal no + 0x0070 Camera1 H264 Camera1 + 0x0071 Camera2 H264 Camera2 + 0x007f Shutter H264 Shutter + 0x00a0 ExposureTime no + 0x00a1 FNumber no + 0x00a2 ExposureProgram no + 0x00a3 BrightnessValue no + 0x00a4 ExposureCompensation no + 0x00a5 MaxApertureValue no + 0x00a6 Flash no + 0x00a7 CustomRendered no + 0x00a8 WhiteBalance no + 0x00a9 FocalLengthIn35mmFormat no + 0x00aa SceneCaptureType no + 0x00b0 GPSVersionID no + 0x00b1 GPSLatitudeRef no + 0x00b2 GPSLatitude no + 0x00b5 GPSLongitudeRef no + 0x00b6 GPSLongitude no + 0x00b9 GPSAltitudeRef no + 0x00ba GPSAltitude no + 0x00bb GPSTimeStamp no + 0x00be GPSStatus no + 0x00bf GPSMeasureMode no + 0x00c0 GPSDOP no + 0x00c1 GPSSpeedRef no + 0x00c2 GPSSpeed no + 0x00c3 GPSTrackRef no + 0x00c4 GPSTrack no + 0x00c5 GPSImgDirectionRef no + 0x00c6 GPSImgDirection no + 0x00c7 GPSMapDatum no + 0x00ca GPSDateStamp no + 0x00e0 MakeModel H264 MakeModel + 0x00e1 RecInfo H264 RecInfo + 0x00e4 Model no + 0x00ee FrameInfo H264 FrameInfo + +=head3 H264 Camera1 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 ApertureSetting no + 1 Gain no + 1.1 ExposureProgram no + 2.1 WhiteBalance no + 3 Focus no + +=head3 H264 Camera2 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 1 ImageStabilization no + +=head3 H264 Shutter Tags + + Index2 Tag Name Writable + ------ -------- -------- + 1.1 ExposureTime no + +=head3 H264 MakeModel Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 Make no + +=head3 H264 RecInfo Tags + +Recording information stored by some Canon video cameras. + + Index1 Tag Name Writable + ------ -------- -------- + 0 RecordingMode no + +=head3 H264 FrameInfo Tags + +Frame rate information stored by some Canon video cameras. + + Index1 Tag Name Writable + ------ -------- -------- + 0 CaptureFrameRate no + 1 VideoFrameRate no + +=head2 MISB Tags + +These tags are extracted from STANAG-4609 MISB (Motion Industry Standards +Board) KLV-format metadata in M2TS videos. + + Tag ID Tag Name Writable + ------ -------- -------- + '060E2B34030101010E01030302000000' Security MISB Security + '060e2b3402030101434e415644494147' + ChurchillNav MISB ChurchillNav + '060e2b34020b01010e01030101000000' + UASDataLink MISB UASDatalink + '<other>' Unknown MISB Unknown + +=head3 MISB Security Tags + +Tags extracted from the MISB ST 0102.11 Security Metadata local set. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 SecurityClassification no + 0x0002 ClassifyingCountryCodeMethod no + 0x0003 ClassifyingCountry no + 0x0004 SecuritySCI-SHIInformation no + 0x0005 Caveats no + 0x0006 ReleasingInstructions no + 0x0007 ClassifiedBy no + 0x0008 DerivedFrom no + 0x0009 ClassificationReason no + 0x000a DeclassificationDate no + 0x000b ClassificationAndMarkingSystem no + 0x000c ObjectCountryCodingMethod no + 0x000d ObjectCountryCodes no + 0x000e ClassificationComments no + 0x000f UMID no + 0x0010 StreamID no + 0x0011 TransportStreamID no + 0x0015 ItemDesignatorID no + 0x0016 SecurityVersion no + 0x0017 ClassifyingCountryCodingMethodDate no + 0x0018 ObjectCountryCodingMethodDate no + +=head3 MISB ChurchillNav Tags + +Proprietary tags used by Churchill Navigation units. These tags are all +currently unknown, but extracted with the Unknown option. + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 MISB UASDatalink Tags + +Tags extracted from the MISB ST 0601.11 UAS Datalink local set. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 Checksum no + 0x0002 GPSDateTime no + 0x0003 MissionID no + 0x0004 TailNumber no + 0x0005 GPSTrack no + 0x0006 PitchAngle no + 0x0007 RollAngle no + 0x0008 TrueAirspeed no + 0x0009 IndicatedAirspeed no + 0x000a ProjectIDCode no + 0x000b SensorName no + 0x000c ImageCoordinateSystem no + 0x000d GPSLatitude no + 0x000e GPSLongitude no + 0x000f GPSAltitude no + 0x0010 HorizontalFieldOfView no + 0x0011 VerticalFieldOfView no + 0x0012 SensorRelativeAzimuthAngle no + 0x0013 SensorRelativeElevationAngle no + 0x0014 SensorRelativeRollAngle no + 0x0015 SlantRange no + 0x0016 TargetWidth no + 0x0017 FrameCenterLatitude no + 0x0018 FrameCenterLongitude no + 0x0019 FrameCenterElevation no + 0x001a OffsetCornerLatitude1 no + 0x001b OffsetCornerLongitude1 no + 0x001c OffsetCornerLatitude2 no + 0x001d OffsetCornerLongitude2 no + 0x001e OffsetCornerLatitude3 no + 0x001f OffsetCornerLongitude3 no + 0x0020 OffsetCornerLatitude4 no + 0x0021 OffsetCornerLongitude4 no + 0x0022 IcingDetected no + 0x0023 WindDirection no + 0x0024 WindSpeed no + 0x0025 StaticPressure no + 0x0026 DensityAltitude no + 0x0027 AirTemperature no + 0x0028 TargetLocationLatitude no + 0x0029 TargetLocationLongitude no + 0x002a TargetLocationElevation no + 0x002b TargetTrackGateWidth no + 0x002c TargetTrackGateHeight no + 0x002d TargetErrorEstimateCE90 no + 0x002e TargetErrorEstimateLE90 no + 0x002f GenericFlagData01 no + 0x0030 SecurityLocalMetadataSet MISB Security + 0x0031 DifferentialPressure no + 0x0032 AngleOfAttack no + 0x0033 VerticalSpeed no + 0x0034 SideslipAngle no + 0x0035 AirfieldBarometricPressure no + 0x0036 AirfieldElevation no + 0x0037 RelativeHumidity no + 0x0038 GPSSpeed no + 0x0039 GroundRange no + 0x003a FuelRemaining no + 0x003b CallSign no + 0x003c WeaponLoad no + 0x003d WeaponFired no + 0x003e LaserPRFCode no + 0x003f SensorFieldOfViewName no + 0x0040 MagneticHeading no + 0x0041 UAS_LSVersionNumber no + 0x0042 TargetLocationCovarianceMatrix no + 0x0043 AlternateLatitude no + 0x0044 AlternateLongitude no + 0x0045 AlternateAltitude no + 0x0046 AlternateName no + 0x0047 AlternateHeading no + 0x0048 EventStartTime no + 0x0049 RVTLocalSet MISB Unknown + 0x004a VMTIDataSet MISB Unknown + 0x004b SensorEllipsoidHeight no + 0x004c AlternateEllipsoidHeight no + 0x004d OperationalMode no + 0x004e FrameCenterHeightAboveEllipsoid no + 0x004f SensorVelocityNorth no + 0x0050 SensorVelocityEast no + 0x0051 ImageHorizonPixelPack no + 0x0052 CornerLatitude1 no + 0x0053 CornerLongitude1 no + 0x0054 CornerLatitude2 no + 0x0055 CornerLongitude2 no + 0x0056 CornerLatitude3 no + 0x0057 CornerLongitude3 no + 0x0058 CornerLatitude4 no + 0x0059 CornerLongitude4 no + 0x005a FullPitchAngle no + 0x005b FullRollAngle no + 0x005c FullAngleOfAttack no + 0x005d FullSideslipAngle no + 0x005e MIISCoreIdentifier no + 0x005f SARMotionImageryData MISB Unknown + 0x0060 TargetWidthExtended no + 0x0061 RangeImageLocalSet MISB Unknown + 0x0062 GeoregistrationLocalSet MISB Unknown + 0x0063 CompositeImagingLocalSet MISB Unknown + 0x0064 SegmentLocalSet MISB Unknown + 0x0065 AmendLocalSet MISB Unknown + 0x0066 SDCC-FLP no + 0x0067 DensityAltitudeExtended no + 0x0068 SensorEllipsoidHeightExtended no + 0x0069 AlternateEllipsoidHeightExtended no + +=head3 MISB Unknown Tags + +Other tags are extracted with the Unknown option. + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head2 Matroska Tags + +The following tags are extracted from Matroska multimedia container files. +This container format is used by file types such as MKA, MKV, MKS and WEBM. +For speed, by default ExifTool extracts tags only up to the first Cluster. +However, the Verbose (-v) and Unknown = 2 (-U) options force processing of +Cluster data, and the ExtractEmbedded (-ee) option skips over Clusters to +read subsequent tags. See +L<http://www.matroska.org/technical/specs/index.html> for the official +Matroska specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 ChapterDisplay Matroska + 0x0003 TrackType no + 0x0005 ChapterString no + 0x0006 VideoCodecID no + AudioCodecID no + CodecID no + 0x0008 TrackDefault no + 0x0009 ChapterTrackNumber? no + 0x000e Slices Matroska + 0x000f ChapterTrack Matroska + 0x0011 ChapterTimeStart no + 0x0012 ChapterTimeEnd no + 0x0016 CueRefTime? no + 0x0017 CueRefCluster? no + 0x0018 ChapterFlagHidden? no + 0x001a VideoScanType no + 0x001b BlockDuration? no + 0x001c TrackLacing? no + 0x001f AudioChannels no + 0x0020 BlockGroup Matroska + 0x0021 Block? no + 0x0022 BlockVirtual? no + 0x0023 SimpleBlock? no + 0x0024 CodecState? no + 0x0025 BlockAdditional? no + 0x0026 BlockMore Matroska + 0x0027 Position no + 0x002a CodecDecodeAll no + 0x002b PrevSize no + 0x002e TrackEntry Matroska + 0x002f EncryptedBlock? no + 0x0030 ImageWidth no + 0x0033 CueTime? no + 0x0035 AudioSampleRate no + 0x0036 ChapterAtom Matroska + 0x0037 CueTrackPositions Matroska + 0x0039 TrackUsed no + 0x003a ImageHeight no + 0x003b CuePoint Matroska + 0x003f CRC-32? no + 0x004b BlockAdditionalID? no + 0x004c LaceNumber? no + 0x004d FrameNumber? no + 0x004e Delay? no + 0x004f ClusterDuration? no + 0x0057 TrackNumber no + 0x005b CueReference Matroska + 0x0060 Video Matroska + 0x0061 Audio Matroska + 0x0067 TimeCode? no + 0x0068 TimeSlice Matroska + 0x006a CueCodecState? no + 0x006b CueRefCodecState? no + 0x006c Void? no + 0x006e BlockAddID? no + 0x0071 CueClusterPosition? no + 0x0077 CueTrack? no + 0x007a ReferencePriority? no + 0x007b ReferenceBlock? no + 0x007d ReferenceVirtual? no + 0x0254 ContentCompressionAlgorithm no + 0x0255 ContentCompressionSettings? no + 0x0282 DocType no + 0x0285 DocTypeReadVersion no + 0x0286 EBMLVersion no + 0x0287 DocTypeVersion no + 0x02f2 EBMLMaxIDLength? no + 0x02f3 EBMLMaxSizeLength? no + 0x02f7 EBMLReadVersion no + 0x037c ChapterLanguage no + 0x037e ChapterCountry no + 0x0444 SegmentFamily? no + 0x0461 DateTimeOriginal no + 0x047a TagLanguageBCP47 no + 0x0484 TagDefault no + 0x0485 TagBinary no + 0x0487 TagString no + 0x0489 Duration no + 0x050d ChapterProcessPrivate? no + 0x0598 ChapterFlagEnabled? no + 0x05a3 TagName no + 0x05b9 EditionEntry Matroska + 0x05bc EditionUID? no + 0x05bd EditionFlagHidden? no + 0x05db EditionFlagDefault? no + 0x05dd EditionFlagOrdered? no + 0x065c AttachedFileData no + 0x0660 AttachedFileMIMEType no + 0x066e AttachedFileName no + 0x0675 AttachedFileReferral? no + 0x067e AttachedFileDescription no + 0x06ae AttachedFileUID no + 0x07e1 ContentEncryptionAlgorithm no + 0x07e2 ContentEncryptionKeyID? no + 0x07e3 ContentSignature? no + 0x07e4 ContentSignatureKeyID? no + 0x07e5 ContentSignatureAlgorithm no + 0x07e6 ContentSignatureHashAlgorithm no + 0x0d80 MuxingApp no + 0x0dbb Seek Matroska + 0x1031 ContentEncodingOrder? no + 0x1032 ContentEncodingScope? no + 0x1033 ContentEncodingType no + 0x1034 ContentCompression Matroska + 0x1035 ContentEncryption Matroska + 0x135f CueRefNumber? no + 0x136e TrackName no + 0x1378 CueBlockNumber? no + 0x137f TrackOffset? no + 0x13ab SeekID? no + 0x13ac SeekPosition? no + 0x13b8 Stereo3DMode no + 0x14aa CropBottom no + 0x14b0 DisplayWidth no + 0x14b2 DisplayUnit no + 0x14b3 AspectRatioType no + 0x14ba DisplayHeight no + 0x14bb CropTop no + 0x14cc CropLeft no + 0x14dd CropRight no + 0x15aa TrackForced no + 0x15ee MaxBlockAdditionID? no + 0x1741 WritingApp no + 0x1854 SilentTracks Matroska + 0x18d7 SilentTrackNumber no + 0x21a7 AttachedFile Matroska + 0x2240 ContentEncoding Matroska + 0x2264 AudioBitsPerSample no + 0x23a2 CodecPrivate? no + 0x23c0 Targets Matroska + 0x23c3 ChapterPhysicalEquivalent no + 0x23c4 TagChapterUID no + 0x23c5 TagTrackUID no + 0x23c6 TagAttachmentUID no + 0x23c9 TagEditionUID no + 0x23ca TargetType no + 0x2532 SignedElement? no + 0x2624 TrackTranslate Matroska + 0x26a5 TrackTranslateTrackID? no + 0x26bf TrackTranslateCodec no + 0x26fc TrackTranslateEditionUID? no + 0x27c8 SimpleTag Matroska + 0x28ca TargetTypeValue no + 0x2911 ChapterProcessCommand Matroska + 0x2922 ChapterProcessTime? no + 0x2924 ChapterTranslate Matroska + 0x2933 ChapterProcessData? no + 0x2944 ChapterProcess Matroska + 0x2955 ChapterProcessCodecID? no + 0x29a5 ChapterTranslateID? no + 0x29bf ChapterTranslateCodec no + 0x29fc ChapterTranslateEditionUID? no + 0x2d80 ContentEncodings Matroska + 0x2de7 MinCache? no + 0x2df8 MaxCache? no + 0x2e67 ChapterSegmentUID? no + 0x2ebc ChapterSegmentEditionUID? no + 0x2fab TrackOverlay? no + 0x3373 Tag Matroska + 0x3384 SegmentFileName no + 0x33a4 SegmentUID? no + 0x33c4 ChapterUID? no + 0x33c5 TrackUID no + 0x3446 TrackAttachmentUID no + 0x35a1 BlockAdditions Matroska + 0x38b5 OutputAudioSampleRate no + 0x3ba9 Title no + 0x3d7b ChannelPositions? no + 0x3e5b SignatureElements Matroska + 0x3e7b SignatureElementList Matroska + 0x3e8a SignatureAlgo no + 0x3e9a SignatureHash no + 0x3ea5 SignaturePublicKey? no + 0x3eb5 Signature? no + 0x7670 Projection Matroska Projection + 0x2b59c TrackLanguage no + 0x2b59d TrackLanguageIETF no + 0x3314f TrackTimecodeScale no + 0x383e3 FrameRate no + 0x3e383 VideoFrameRate no + DefaultDuration no + 0x58688 VideoCodecName no + AudioCodecName no + CodecName no + 0x6b240 CodecDownloadURL no + 0xad7b1 TimecodeScale no + 0xeb524 ColorSpace? no + 0xfb523 Gamma no + 0x1a9697 CodecSettings no + 0x1b4040 CodecInfoURL no + 0x1c83ab PrevFileName no + 0x1cb923 PrevUID? no + 0x1e83bb NextFileName no + 0x1eb923 NextUID? no + 0x43a770 Chapters Matroska + 0x14d9b74 SeekHead Matroska + 0x254c367 Tags Matroska + 0x549a966 Info Matroska + 0x654ae6b Tracks Matroska + 0x8538067 SegmentHeader Matroska + 0x941a469 Attachments Matroska + 0xa45dfa3 EBMLHeader Matroska + 0xb538667 SignatureSlot Matroska + 0xc53bb6b Cues Matroska + 0xf43b675 Cluster Matroska + +=head3 Matroska Projection Tags + +Projection tags defined by the Spherical Video V2 specification. See +L<https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md> +for the specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x7671 ProjectionType no + 0x7672 EquirectangularProj QuickTime equi + CubemapProj QuickTime cbmp + ProjectionPrivate no + 0x7673 ProjectionPoseYaw no + 0x7674 ProjectionPosePitch no + 0x7675 ProjectionPoseRoll no + +=head3 Matroska StdTag Tags + +Standardized Matroska tags, stored in a SimpleTag structure (see +L<https://www.matroska.org/technical/tagging.html>). + + Tag ID Tag Name Writable + ------ -------- -------- + 'ACCOMPANIMENT' Accompaniment no + 'ACTOR' Actor no + 'ADDRESS' Address no + 'ARRANGER' Arranger no + 'ARTIST' Artist no + 'ART_DIRECTOR' ArtDirector no + 'ASSISTANT_DIRECTOR' AssistantDirector no + 'BARCODE' Barcode no + 'BPM' BPM no + 'BPS' BPS no + 'CATALOG_NUMBER' CatalogNumber no + 'CHARACTER' Character no + 'CHOREGRAPHER' Choregrapher no + 'COMMENT' Comment no + 'COMPOSER' Composer no + 'COMPOSER_NATIONALITY' ComposerNationality no + 'COMPOSITION_LOCATION' CompositionLocation no + 'CONDUCTOR' Conductor no + 'CONTENT_TYPE' ContentType no + 'COPRODUCER' Coproducer no + 'COPYRIGHT' Copyright no + 'COSTUME_DESIGNER' CostumeDesigner no + 'COUNTRY' Country no + 'DATE_DIGITIZED' CreateDate no + 'DATE_ENCODED' DateEncoded no + 'DATE_PURCHASED' DatePurchased no + 'DATE_RECORDED' DateTimeOriginal no + 'DATE_RELEASED' DateReleased no + 'DATE_TAGGED' DateTagged no + 'DATE_WRITTEN' DateWritten no + 'DESCRIPTION' Description no + 'DIRECTOR' Director no + 'DIRECTOR_OF_PHOTOGRAPHY' + DirectorOfPhotography no + 'DISTRIBUTED_BY' DistributedBy no + 'EDITED_BY' EditedBy no + 'EMAIL' Email no + 'ENCODED_BY' EncodedBy no + 'ENCODER' Encoder no + 'ENCODER_SETTINGS' EncoderSettings no + 'EXECUTIVE_PRODUCER' ExecutiveProducer no + 'FAX' FAX no + 'FPS' FPS no + 'GENRE' Genre no + 'IMDB' IMDB no + 'INITIAL_KEY' InitialKey no + 'INSTRUMENTS' Instruments no + 'ISBN' ISBN no + 'ISRC' ISRC no + 'KEYWORDS' Keywords no + 'LABEL' Label no + 'LABEL_CODE' LabelCode no + 'LAW_RATING' LawRating no + 'LCCN' Lccn no + 'LEAD_PERFORMER' LeadPerformer no + 'LICENSE' License no + 'LYRICIST' Lyricist no + 'LYRICS' Lyrics no + 'MASTERED_BY' MasteredBy no + 'MCDI' MCDI no + 'MEASURE' Measure no + 'MIXED_BY' MixedBy no + 'MOOD' Mood no + 'ORIGINAL' Original no + 'ORIGINAL_MEDIA_TYPE' OriginalMediaType no + 'PART_NUMBER' PartNumber no + 'PART_OFFSET' PartOffset no + 'PERIOD' Period no + 'PHONE' Phone no + 'PLAY_COUNTER' PlayCounter no + 'PRODUCER' Producer no + 'PRODUCTION_COPYRIGHT' ProductionCopyright no + 'PRODUCTION_DESIGNER' ProductionDesigner no + 'PRODUCTION_STUDIO' ProductionStudio no + 'PUBLISHER' Publisher no + 'PURCHASE_CURRENCY' PurchaseCurrency no + 'PURCHASE_INFO' PurchaseInfo no + 'PURCHASE_ITEM' PurchaseItem no + 'PURCHASE_OWNER' PurchaseOwner no + 'PURCHASE_PRICE' PurchasePrice no + 'RATING' Rating no + 'RECORDING_LOCATION' RecordingLocation no + 'REMIXED_BY' RemixedBy no + 'REPLAYGAIN_GAIN' ReplaygainGain no + 'REPLAYGAIN_PEAK' ReplaygainPeak no + 'SAMPLE' Sample no + 'SCREENPLAY_BY' ScreenplayBy no + 'SORT_WITH' SortWith no + 'SOUND_ENGINEER' SoundEngineer no + 'SPHERICAL-VIDEO' SphericalVideoXML XMP + 'SUBJECT' Subject no + 'SUBTITLE' Subtitle no + 'SUMMARY' Summary no + 'SYNOPSIS' Synopsis no + 'TERMS_OF_USE' TermsOfUse no + 'THANKS_TO' ThanksTo no + 'TITLE' Title no + 'TMDB' TMDB no + 'TOTAL_PARTS' TotalParts no + 'TUNING' Tuning no + 'TVDB' TVDB no + 'URL' URL no + 'WRITTEN_BY' WrittenBy no + 'spherical-video' SphericalVideoXML XMP + +=head2 MOI Tags + +MOI files store information about associated MOD or TOD files, and are +written by some JVC, Canon and Panasonic camcorders. + + Index1 Tag Name Writable + ------ -------- -------- + 0 MOIVersion no + 6 DateTimeOriginal no + 14 Duration no + 128 AspectRatio no + 132 AudioCodec no + 134 AudioBitrate no + 218 VideoBitrate no + +=head2 MXF Tags + +Tags extracted from Material Exchange Format files. Tag ID's are not listed +because they are bulky 16-byte binary values. + + Tag Name Writable + -------- -------- + AAFManufacturerID? no + AES3PCMDescriptor MXF + AFDAndBarData? no + AGICOAID? no + AICI? no + AIFCSummary? no + ANCPacketCount no + ANCPayloadByteArray no + ANCPayloadSampleCoding no + ANCPayloadSampleCount no + ANCWrappingType no + Abstract no + Abstract no + AccountingReferenceNumber no + ActiveFormatDescriptor no + ActiveLinesperFrame no + ActiveSamplesperLine no + ActiveState no + Ad-ID? no + Address MXF + AddressLine no + AddressLine no + AddressNameValueSets? no + AddressSets? no + AdvertisingMaterialReference no + AestheticValue no + AlphaMaximumRef no + AlphaMinimumRef no + AlphaSampleDepth no + AlphaTransparency no + AlternateName no + AlternateName no + Alternates? no + AnalogDataCodingKind no + AnalogMetadataCarrier no + AnalogMonitoringAndControlCodingKind no + AnalogSystem no + AnalogVideoSystemName no + AnalogVideoSystemName? no + AnamorphicLensCharacteristic no + AnchorOffset no + AncillaryResourceID? no + AngleToNorth no + AngularUnitKind no + Annotation? no + Annotation MXF + AnnotationCueWordsSet? no + AnnotationDescription no + AnnotationDescription no + AnnotationKind no + AnnotationKind no + AnnotationParticipantSets? no + AnnotationSets? no + AnnotationSynopsis no + AnnotationSynopsis no + ApplicationEnvironmentID no + ApplicationInformationArray? no + ApplicationName no + ApplicationName no + ApplicationPlatform no + ApplicationPlatform no + ApplicationPlug-InBatch? no + ApplicationPlug-InInstanceID? no + ApplicationProductID? no + ApplicationScheme? no + ApplicationSchemeBatch? no + ApplicationSupplierName no + ApplicationSupplierName no + ApplicationVersionNumber no + ApplicationVersionString no + ApplicationVersionString no + ApproximateImageContainerSize no + ArchiveID no + AssetValue no + AssignedCategoryName no + AssignedCategoryName no + AssignedCategoryValue no + AssignedCategoryValue no + AssociatedMetadataDefinition? no + AstronomicalBodyName no + AstronomicalBodyName no + AudienceAppreciation no + AudienceRating no + AudienceReach no + AudienceShare no + AudioAverageBitrate no + AudioCodingSchemeCode no + AudioCodingSchemeID? no + AudioCodingSchemeName no + AudioCompressionAlgorithm no + AudioDeviceKind no + AudioDeviceParameter no + AudioDeviceParameterSetting no + AudioEnhancementOrModificationDescription no + AudioFirstMix-DownProcess no + AudioFixedBitrateFlag no + AudioMonoChannelCount no + AudioNoiseReductionAlgorithm no + AudioReferenceLevel no + AudioReferenceLevel no + AudioSampleRate no + AudioSampleRate no + AudioStereoChannelCount no + AudioWatermarkKind no + AuthenticationFlag no + AuxiliaryBitsMode no + AvailableRepresentations? no + AverageBytesPerSecond no + Award MXF + AwardCategory no + AwardCategory no + AwardName no + AwardName no + AwardParticipantSets? no + AwardSets? no + BICI? no + BackgroundMusicFlag no + BankDetailsSet? no + BeginAnchor no + BeginAnchor no + BextCodingHistory no + BextCodingHistory no + Bitrate no + BitsPerAudioSample no + BitsPerPixel no + BitsPerPixel no + BitsPerSample no + BlackReferenceLevel no + BlockAlign no + BlockContinuityCount no + BlockStartOffset no + BoundingRectangle? no + BrandMainTitle no + BrandMainTitle no + BrandOriginalTitle no + BrandOriginalTitle no + Branding MXF + BrandingSets? no + BroadcastChannel no + BroadcastDate no + BroadcastMediumCode no + BroadcastMediumKind no + BroadcastOrganizationName no + BroadcastOrganizationName no + BroadcastRegion no + BroadcastRegion no + BroadcastServiceName no + BroadcastTime no + BroadcasterRightsToCopy no + BufferDelay? no + BuildingName no + BuildingName no + Bypass no + BypassOverride no + ByteOffset no + ByteOrder no + CBEStartOffset no + CDCIEssenceDescriptor MXF + CISACLegalEntityID? no + CaptionDescription MXF + CaptionDescriptionSets? no + CaptionKind no + CaptionKind no + CaptionTitles no + CaptionsDescriptionParticipantSets? no + CaptionsViaTeletext no + CaptureAspectRatio no + CaptureFilmFrameRate no + CaptureGammaEquation no + CaptureGammaEquation no + CaptureGammaEquation? no + CatalogDataStatus no + CatalogDataStatus no + CatalogPrefixNumber no + CatalogingSystemName no + CentralTelephoneNumber no + ChannelAssignment? no + ChannelCount no + ChannelHandle no + ChannelID no + ChannelIDs no + ChannelStatusMode no + CipherAlgorithm? no + CipherAlgorithmAES128CBC? no + Citizenship no + Citizenship no + CityName no + CityName no + ClassDefinitions? no + Classification MXF + ClassificationAndMarkingSystem no + ClassificationComment no + ClassificationComment no + ClassificationNameValueSets? no + ClassificationReason no + ClassificationSets? no + ClassifiedBy no + ClassifyingCountryCode no + ClipCreationDateTime no + ClipFramework MXF + ClipID no + ClipIDArray? no + ClipKind no + ClipNumber no + ClipNumber no + ClipShotSets? no + CloneCount no + ClosedBodyPartition? no + ClosedCaptionSubtitlesFlag no + ClosedCompleteBodyPartition? no + ClosedCompleteHeader MXF Header + ClosedGOPIndicator no + ClosedHeader MXF Header + CodecDefinition? no + CodecDefinition? no + CodecDefinitions? no + CodecEssenceDescriptor? no + CodecEssenceKinds? no + CodedContentScanningKind no + CodingLawKind no + CodingLawName no + CodingStyleDefault? no + CollectionName no + CollectionName no + ColorDescriptor no + ColorDescriptor no + ColorFieldCode no + ColorPrimaries no + ColorPrimaries? no + ColorRangeLevels no + ColorSiting no + ColorimetryCode no + ColorimetryCode? no + CommunicationSets? no + Communications MXF + CompleteFooter? no + ComponentAttributes? no + ComponentDataDefinition no + ComponentDepth no + ComponentKLVData? no + ComponentMaximumRef no + ComponentMinimumRef no + ComponentUserComments? no + ComponentsInSequence? no + CompositionRendering? no + ComputedKeyData no + ComputedKeyFrames no + ComputedKeySounds no + ComputedKeywords no + ComputedKeywords no + ComputedObjectKind no + ComputedObjectKind no + ComputedStratumKind no + ConstantBPictureFlag no + ConsumerRightsToCopy no + ContactDepartmentName no + ContactDepartmentName no + ContactID? no + ContactKind no + ContactKind no + ContactNameValueSets? no + ContactsList MXF + ContactsListSet? no + ContainerDefinitions? no + ContainerLastModifyDate no + ContainerVersion no + ContentClassification no + ContentClassification no + ContentCodingSystem no + ContentMaturityDescription no + ContentMaturityGraphic no + ContentMaturityRating no + ContentPackageIndexArray? no + ContentPackageMetadataLink no + ContentStorage? no + ContentStorageSet MXF + ContentValue no + ContextDescription no + ContiguousDataFlag no + Contract MXF + ContractClauseDescription no + ContractClauseDescription no + ContractDateTime no + ContractEntity no + ContractInstallmentPercentage no + ContractLineCode no + ContractLineName no + ContractLineName no + ContractParticipantSets? no + ContractSets? no + ContractTermsOfBusiness no + ContractTermsOfBusiness no + ContractType no + ContractType no + ContractTypeCode no + ContractTypeLink no + ContributionStatus no + ContributionStatus no + ControlPointList? no + ControlPointTime no + CopyCount no + CopyrightLicenseCountryCode no + CopyrightLicenseRegionCode no + CopyrightLicenseRegionName no + CopyrightOwnerName no + CopyrightOwnerName no + CopyrightStatus no + CopyrightStatus no + CornerLatitudePoint1 no + CornerLatitudePoint1 no + CornerLatitudePoint2 no + CornerLatitudePoint2 no + CornerLatitudePoint3 no + CornerLatitudePoint3 no + CornerLatitudePoint4 no + CornerLatitudePoint4 no + CornerLongitudePoint1 no + CornerLongitudePoint1 no + CornerLongitudePoint2 no + CornerLongitudePoint2 no + CornerLongitudePoint3 no + CornerLongitudePoint3 no + CornerLongitudePoint4 no + CornerLongitudePoint4 no + CountryCodeMethod no + CountryCodeMethod no + CountryName no + CountryName no + CountryName no + CountryName no + CreateDate no + CryptographicContext MXF + CryptographicContextID? no + CryptographicContextLink? no + CryptographicContextObject? no + CryptographicFramework MXF + CryptographicFrameworkLabel? no + CryptographicKeyID? no + Csiz no + Cue-InWords no + Cue-InWords no + Cue-OutWords no + Cue-OutWords no + CueWords MXF + CulturalValue no + CurrencyCode no + CurrencyName no + CurrentNumberInSequence no + CurrentNumberInSequence no + CurrentRepeatNumber no + CutPoint no + DMFramework MXF + DMSegment MXF + DMSet MXF + DMSourceClip MXF + DOI? no + DVBParentalRating no + DataDefinition? no + DataDefinitions? no + DataDeviceKind no + DataDeviceParameterName no + DataDeviceParameterSetting no + DataEnhancementOrModificationDescription no + DataEssenceCoding no + DataEssenceCodingID? no + DateTimeDropFrameFlag no + DateTimeEmbeddedFlag no + DateTimeKind? no + DateTimeRate no + DeclassificationDate no + DefaultDataValue? no + DefaultFadeDuration no + DefaultFadeEditRate no + DefaultFadeType? no + DefaultNamespaceURI no + DefaultNamespaceURI no + DefaultObject? no + DefinedName no + DefinedName no + DefinitionObjectID? no + DefinitionObjectName no + DefinitionObjectName no + DegradedEffects? no + DeltaEntryArray? no + DerivedFrom no + Description no + Description no + DescriptionKind no + DescriptionKind no + DescriptiveComment no + DescriptiveComment no + DescriptiveMetadataApplicationEnvironmentID no + DescriptiveMetadataFramework? no + DescriptiveMetadataPlug-InID? no + DescriptiveMetadataScheme? no + DescriptiveMetadataSchemes? no + DescriptiveMetadataSetReferences? no + DescriptiveMetadataSets? no + DescriptiveMetadataTrackIDs no + DeviceAbsoluteHeading no + DeviceAbsolutePositionalAccuracy no + DeviceAbsoluteSpeed no + DeviceAltitude no + DeviceAltitude no + DeviceAssetNumber no + DeviceDesignation no + DeviceIDKind no + DeviceKind no + DeviceKind no + DeviceKindCode no + DeviceLatitude no + DeviceLatitude no + DeviceLatitude no + DeviceLongitude no + DeviceLongitude no + DeviceLongitude no + DeviceManufacturerName no + DeviceManufacturerName no + DeviceModel no + DeviceParameterNameValueSets? no + DeviceParameters MXF + DeviceParametersSets? no + DeviceRelativeHeading no + DeviceRelativePositionX no + DeviceRelativePositionY no + DeviceRelativePositionZ no + DeviceRelativePositionalAccuracy no + DeviceRelativeSpeed no + DeviceSerialNumber no + DeviceUsageDescription no + DeviceUsageDescription no + DeviceXDimension no + DeviceYDimension no + DialNorm no + Dictionary? no + DictionaryDescription no + DictionaryDescription no + DictionaryIdentifier? no + DigitalEncodingBitrate no + DigitalMetadataCarrier no + DigitalOrAnalogOrigination no + DigitalVideoFileFormat no + DirectorName no + DiscPartitionCapacity no + DisplayF2Offset no + DisplayHeight no + DisplayUnits no + DisplayUnits no + DisplayWidth no + DisplayXOffset no + DisplayYOffset no + Dither no + DropFrame no + Duration no + DynamicSourcePackageID? no + DynamicSourceTrackIDs no + E-mailAddress no + E-mailAddress no + EPGProgramSynopsis no + EdgeCode no + EdgeCodeFilmGauge? no + EdgeCodeFormat? no + EdgeCodeHeader? no + EdgeCodeStart no + EditHint? no + EditRate no + EditUnitFlags no + EditUnitLength no + EditingEventComment no + EditingEventComment no + EffectRendering? no + ElectrospatialFormulation no + ElementCount no + ElementDelta no + ElementLength no + ElementNameList no + Emphasis no + EncryptedContainerLabel? no + EncryptedSourceValue? no + EncryptedTrackFileID? no + EndAnchor no + EndAnchor no + EnhancementOrModificationDescription no + EnumerationUnderlyingIntegerType? no + EpisodeEndNumber no + EpisodeNumber no + EpisodeNumber no + EpisodeStartNumber no + EpisodicItemSets? no + EssenceContainerArray? no + EssenceContainerDataSet MXF + EssenceContainerFormat? no + EssenceContainerFormat? no + EssenceContainers? no + EssenceData? no + EssenceDescription? no + EssenceIsIdentified no + EssenceLength no + EssenceLocators? no + EssenceStreamID no + Event MXF + EventAbsoluteDuration no + EventAbsoluteDuration? no + EventAbsoluteDurationFrameCount no + EventAnnotationSets? no + EventElapsedTimeToEnd no + EventElapsedTimeToStart no + EventEndTimeOffset no + EventEndTimecodeOffset? no + EventIndication no + EventIndication no + EventOrigin no + EventSets? no + EventStart no + EventStartTimeOffset no + EventStartTimecodeOffset? no + EventTrack MXF + EventTrackEditRate no + ExCCIData? no + ExposedAspectRatio no + ExtendedCaptionsLanguageCode no + ExtendedClipID no + ExtendedClipIDArray? no + ExtendedTextLanguageCode no + ExtendibleElementNameList no + FNumber no + Fade-InDuration no + Fade-InType? no + Fade-OutDuration no + Fade-OutType? no + FamilyName no + FamilyName no + FaxNumber no + FaxNumber no + FemaleLeadActressName no + FestivalName no + FestivalName no + FieldDominance no + FieldFrameTypeCode no + FieldOfViewHorizontal no + FieldOfViewHorizontal no + FieldOfViewVertical no + FieldOfViewVerticalFP no + FieldRate no + FileDescriptor MXF + FileDescriptors? no + FileSecurityReport no + FileSecurityWave no + FillerData? no + FilmBatchNumber no + FilmBatchNumber no + FilmCaptureAperture no + FilmColorProcess no + FilmFormatName no + FilmFormatName no + FilmFormatName? no + FilmGauge? no + FilmStockKind no + FilmStockKind no + FilmStockManufacturerName no + FilmStockManufacturerName no + FilmTestParameter no + FilmTestResult no + FilmTestResult? no + FilmToVideoTransferDirection? no + FilmToVideoTransferKind? no + FilmToVideoTransferPhase? no + FilteringApplied no + FilteringCode no + FirstBroadcastFlag no + FirstGivenName no + FirstGivenName no + FirstNumberInSequence no + FirstNumberInSequence no + FirstTransmissionInfo no + FixedArrayElementType? no + FixedChannelStatusData? no + FixedUserData? no + FocalLength no + FocalLength no + Footer? no + FormatDescriptor no + FormatDescriptor no + FormerFamilyName no + FormerFamilyName no + FrameCenterElevation no + FrameCenterLatitude no + FrameCenterLatitude no + FrameCenterLatitude no + FrameCenterLatitudeLongitude no + FrameCenterLongitude no + FrameCenterLongitude no + FrameCenterLongitude no + FrameCode no + FrameCount no + FrameCountOffset no + FrameLayout no + FramePositionalAccuracy no + FrameRate no + FrameworkExtendedTextLanguageCode no + FrameworkTextLanguageCode no + FrameworkThesaurusName no + FrameworkThesaurusName no + FrameworkTitle no + FrameworkTitle no + GenerationCloneNumber no + GenerationCopyNumber no + GenerationID? no + GenericDataEssenceDescriptor MXF + GenericPackage MXF + GenericPayloads? no + GenericPictureEssenceDescriptor MXF + GenericSoundEssenceDescriptor MXF + GenericTrack MXF + Genre no + Genre no + GeographicAreaNorthwest? no + GeographicAreaSourceDatum no + GeographicAreaSoutheast? no + GeographicLocation? no + GeographicPolygonCoordinates? no + GeographicPolygonSourceDatum no + GeographicalCoordinates? no + GlobalNumber no + GraphicKind no + GraphicUsageKind no + GraphicUsageKind no + GroupRelationship MXF + GroupSet? no + GroupSets? no + GroupSynopsis no + GroupSynopsis no + HMACAlgorithmSHA1128? no + HTMLDOCTYPE no + HTMLDOCTYPE no + HTMLMetaDescription no + HTMLMetaDescription no + HardwareAcceleratorFlag no + HasAudioWatermark no + HasVideoWatermark no + HeaderByteCount no + HistoricalValue no + HonorsAndQualifications no + HonorsAndQualifications no + HorizontalActionSafePercentage no + HorizontalDatum no + HorizontalGraphicsSafePercentage no + HorizontalSubsampling no + IBTN? no + IEEEDeviceID no + IEEEManufacturerID no + ISAN? no + ISBD? no + ISBN? no + ISCI? no + ISMN? no + ISO3166CountryCode no + ISO639-1LanguageCode no + ISO639-1LanguageCode no + ISO639CaptionsLanguageCode no + ISO639TextLanguageCode no + ISRC? no + ISRN? no + ISSN? no + ISTC? no + ISWC? no + IdenticalGOPIndicator no + Identification MXF + Identification MXF + IdentificationList? no + IdentificationSets? no + IdentificationUL? no + IdentifierIssuingAuthority no + IdentifierIssuingAuthority no + IdentifierKind no + IdentifierValue no + ImageAlignmentOffset no + ImageCategory no + ImageCoordinateSystem no + ImageEndOffset no + ImageFormatSet? no + ImageHeight no + ImageSourceDeviceKind no + ImageSourceDeviceKind no + ImageStartOffset no + ImageWidth no + IncludeSync no + IndexByteCount no + IndexDuration no + IndexEditRate no + IndexEntryArray? no + IndexStreamID no + IndexTableSegment? no + IndexingStartPosition no + IndividualAwardName no + InkNumber no + InputSegment? no + InputSegmentCount no + InputSegments? no + InsertMusicFlag no + InstallmentNumber no + InstanceUID? no + IntegrationIndication no + IntegrationIndication no + IntellectualPropertyDescription no + IntellectualPropertyDescription no + IntellectualPropertyLicenseCountryCode no + IntellectualPropertyLicenseRegionCode no + IntellectualPropertyLicenseRegionName no + IntellectualPropertyRights no + IntellectualPropertyRights no + IntendedAFD no + IntentDescriptor no + IntentDescriptor no + InterestedPartyName no + Interpolation? no + InterpolationDefinitions? no + IsConcrete no + IsDubbed no + IsLiveProduction no + IsLiveTransmission no + IsOptional no + IsRecording no + IsRepeat no + IsSearchable no + IsSigned no + IsUniqueIdentifier no + IsVoiceover no + ItemDesignatorID? no + ItemID no + ItemName no + ItemName no + ItemValue no + ItemValue no + JFIFMarkerDescription no + JFIFMarkerDescription no + JPEG2000PictureSubDescriptor MXF + JPEGTableID? no + JobFunctionCode no + JobFunctionName no + JobFunctionName no + JobTitle no + JobTitle no + Jurisdiction no + Jurisdiction no + KAGSize no + KLVDataDefinitions? no + KLVDataParentProperties? no + KLVDataType? no + KLVDataValue? no + KLVMetadataSequence? no + KeyCode? no + KeyData no + KeyDataOrProgram no + KeyFrame no + KeyFrameSampleCount no + KeyFrames no + KeyPoint MXF + KeyPointSets? no + KeySound no + KeySounds no + KeyText no + KeyTimePoint no + KeypointKind no + KeypointKind no + KeypointValue no + KeypointValue no + Keywords no + Keywords no + LUID no + LanguageName no + LanguageName no + LastNumberInSequence no + LastNumberInSequence no + LayerNumber no + LeadingLines no + LengthSystemName no + LengthUnitKind no + LicenseOptionsDescription no + LineNumber no + LinkedApplicationPlug-InInstanceID? no + LinkedDescriptiveFrameworkPlug-InID? no + LinkedDescriptiveObjectPlug-InID? no + LinkedGenerationID? no + LinkedPackageID? no + LinkedTimecodeTrackID? no + LinkedTrackID no + LinkingName no + LinkingName no + LocalCreationDateTime no + LocalDatumAbsolutePositionAccuracy no + LocalDatumRelativePositionAccuracy no + LocalEndDateTime no + LocalEventEndDateTime no + LocalEventEndDateTime no + LocalEventStartDateTime no + LocalEventStartDateTime no + LocalFestivalDateTime no + LocalFilePath no + LocalFilePath no + LocalID no + LocalLastModifyDate no + LocalModifyDate no + LocalStartDateTime no + LocalTagEntries? no + LocalTagUniqueID? no + LocalTagValue no + LocalTapeNumber no + LocalTargetID no + LocalTargetID no + LocalUserDateTime no + Location MXF + LocationDescription no + LocationDescription no + LocationKind no + LocationKind no + LocationSets? no + LockedIndicator no + LogoFlag no + LowDelayIndicator no + LumaEquation no + LuminanceSampleRate no + MIC? no + MICAlgorithm? no + MIMECharSet no + MIMECharSet no + MIMEEncoding no + MIMEEncoding no + MIMEMediaType no + MIMEMediaType no + MPEG2VideoDescriptor MXF + MPEG7BiMAccessUnitFrame1? no + MPEG7BiMAccessUnitFrame2? no + MPEG7BiMAccessUnitFrame3? no + MPEG7BiMAccessUnitFrame4? no + MPEG7BiMAccessUnitFrame5? no + MPEG7BiMAccessUnitFrame6? no + MPEG7BiMAccessUnitFrame7? no + MPEG7BiMAccessUnitFrame8? no + MPEG7BiMDecoderInitFrame1? no + MPEG7BiMDecoderInitFrame2? no + MPEG7BiMDecoderInitFrame3? no + MPEG7BiMDecoderInitFrame4? no + MPEG7BiMDecoderInitFrame5? no + MPEG7BiMDecoderInitFrame6? no + MPEG7BiMDecoderInitFrame7? no + MPEG7BiMDecoderInitFrame8? no + MPEGAudioBitrate no + MPEGAudioRecodingDataset? no + MPEGVideoRecodingDataset? no + MagneticDiskNumber no + MagneticTrack no + MainCatalogNumber no + MainName no + MainName no + MainSponsorName no + MainTitle no + MainTitle no + MajorVersion no + MaleLeadActorName no + ManufacturerID? no + ManufacturerInformationObject? no + MapDatumUsed no + MarkIn no + MarkOut no + MaterialAbsoluteDuration no + MaterialAbsoluteDuration? no + MaterialEndTimeOffset no + MaterialEndTimecodeOffset? no + MaterialPackage MXF + MaximumAPIVersion no + MaximumBPictureCount no + MaximumGOPSize no + MaximumSupportedEngineVersion no + MaximumSupportedPlatformVersion no + MaximumUseCount no + MediaLocation no + MemberNameList no + MemberTypes? no + MetadataEncodingSchemeCode no + MetadataItemName no + MetadataItemName no + MetadataServerLocators? no + MicrophonePlacementTechniques no + MinimumAPIVersion no + MinimumSupportedEngineVersion no + MinimumSupportedPlatformVersion no + MinorVersion no + MissionID no + MissionID no + MobileTelephoneNumber no + ModifyDate no + MonoSourceTrackIDs no + MultipleDescriptor MXF + NITFLayerTargetID no + NITFLayerTargetID no + NMEADocumentText no + NOLACode no + NameSuffix no + NameSuffix no + NameValue MXF + NamespacePrefix no + NamespacePrefix no + NamespacePrefixes no + NamespacePrefixes no + NamespaceURI no + NamespaceURI no + NamespaceURIs no + NamespaceURIs no + Nationality no + Nationality no + NatureOfPersonality no + NatureOfPersonality no + NetworkLocator MXF + NextNumberInSequence no + NextNumberInSequence no + NielsenStreamIdentifier no + NominationCategory no + NominationCategory no + Non-USClassifyingCountryCode no + ObjectAreaDimension no + ObjectClass? no + ObjectClassDefinition? no + ObjectCountryCode no + ObjectCountryCode no + ObjectCountryCodeMethod no + ObjectDescription no + ObjectDescription no + ObjectDescriptionCode no + ObjectHorizontalAverageDimension no + ObjectIdentificationConfidence no + ObjectKind no + ObjectKind no + ObjectModelVersion no + ObjectName no + ObjectRegionCode no + ObjectRegionName no + ObjectVerticalAverageDimension no + ObliquityAngle no + OffsetToIndexTable no + OffsetToIndexTable no + OffsetToMetadata no + OffsetToMetadata no + OpenBodyPartition? no + OpenCompleteBodyPartition? no + OpenCompleteHeader MXF Header + OpenHeader MXF Header + OperatingSystemInterpretations no + OperationCategory? no + OperationDataDefinition? no + OperationDefinitionID? no + OperationDefinitions? no + OperationParameters? no + OperationalPatternUL? no + OpticalDiscNumber no + OpticalTestParameterName no + OpticalTestResult no + OpticalTestResult no + OpticalTrack no + Organisation MXF + OrganizationCode no + OrganizationCode no + OrganizationID no + OrganizationID no + OrganizationIDKind no + OrganizationIDKind no + OrganizationKind no + OrganizationKind no + OrganizationMainName no + OrganizationMainName no + OrganizationSets? no + OrganizationalProgramNumber no + OrganizationalProgramNumber no + Origin no + OriginCode no + OriginalExtendedSpokenPrimaryLanguageCode no + OriginalProducerName no + OriginalProducerName no + OriginalTitle no + OriginalTitle no + OtherGivenNames no + OtherGivenNames no + OtherValues no + PII? no + POSIXMicroseconds no + PURL no + PackLength no + PackageAttributes? no + PackageID? no + PackageKLVData? no + PackageLastModifyDate no + PackageMarkInPosition no + PackageMarkOutPosition no + PackageMarker? no + PackageName no + PackageName no + PackageTimelineMarkerRef? no + PackageTracks? no + PackageUsageKind? no + PackageUserComments? no + Packages? no + PaddingBits no + Palette? no + PaletteLayout? no + PanScanInformation? no + ParameterDataType? no + ParameterDefinition? no + ParameterDefinitions? no + Parameters? no + ParentClass? no + Participant MXF + ParticipantID? no + ParticipantOrganizationSets? no + PartitionMetadata? no + Password no + Password no + PayeeAccountName no + PayeeAccountNumber no + PayeeAccountSortCode no + PayerAccountName no + PayerAccountNumber no + PayerAccountSortCode no + PaymentDueDateTime no + PaymentsSets? no + PeakChannelCount no + PeakEnvelope no + PeakEnvelopeBlockSize no + PeakEnvelopeData? no + PeakEnvelopeData? no + PeakEnvelopeFormat no + PeakEnvelopeTimestamp no + PeakEnvelopeVersion no + PeakFrameCount no + PeakOfPeaksPosition no + PerceivedDisplayFormatCode no + PerceivedDisplayFormatName no + PerforationsPerFrame no + PerforationsPerFrame no + Person MXF + PersonDescription no + PersonDescription no + PersonOrganizationSets? no + PersonSets? no + PhysicalInstanceKind no + PhysicalMediaLength? no + PhysicalMediaLocation no + PictureComponentSizing? no + PictureDisplayRate no + PictureFormat MXF + PixelLayout? no + PlaceKeyword no + PlaceKeyword no + PlaceName no + PlaceName no + PlaintextOffset no + PlatformDesignation no + PlatformDesignation no + PlatformHeadingAngle no + PlatformModel no + PlatformPitchAngle no + PlatformRollAngle no + PlatformSerialNumber no + Plug-InAPIID? no + Plug-InCategoryID? no + Plug-InDefinitions? no + Plug-InEngineID? no + Plug-InLocatorSet? no + Plug-InPlatformID? no + PointsPerPeakValue no + PolarCharacteristic no + PosTableArray? no + PositionInSequence no + PositionTable? no + PositionTableCount no + PositionTableIndexing no + PositionWithinViewportImageXCoordinate no + PositionWithinViewportImageYCoordinate no + PostCodeForPostbox no + PostalCode no + PostalCode no + PostalTown no + PostalTown no + PostboxNumber no + Preface MXF + PresentationAspectRatio no + PresentationGammaEquation no + PresentationGammaEquation? no + PresenterName no + PreviousNumberInSequence no + PreviousNumberInSequence no + PreviousRepeatNumber no + PrimaryExtendedSpokenLanguageCode no + PrimaryOriginalLanguageCode no + PrimaryPackage? no + PrimarySpokenLanguageCode no + Primer MXF + Processing MXF + ProcessingSet? no + ProducerName no + ProductFormat no + ProductFormat no + ProductionFramework MXF + ProductionOrganizationRole no + ProductionOrganizationRole no + ProductionScriptReference no + ProductionScriptReference no + ProductionSettingPeriodSets? no + ProfileAndLevel no + ProgramAwardName no + ProgramCommercialMaterialReference no + ProgramIdentifier no + ProgramIdentifierString no + ProgramKind no + ProgramMaterialClassificationCode no + ProgramNumber no + ProgramSupportMaterialReference no + ProgrammingGroupKind no + ProgrammingGroupKind no + ProgrammingGroupTitle no + ProgrammingGroupTitle no + ProjectName no + ProjectName no + ProjectNumber no + ProjectSet? no + Projects MXF + Properties? no + PropertyType? no + Publication MXF + PublicationSets? no + PublishingMediumName no + PublishingMediumName no + PublishingOrganizationName no + PublishingOrganizationName no + PublishingRegionName no + PublishingRegionName no + PublishingServiceName no + PublishingServiceName no + PulldownFieldDominance no + PulldownSequence? no + PurchaserAccountName no + PurchaserAccountName no + PurchaserAccountNumber no + PurchaserIdentificationKind no + PurchaserIdentificationValue no + PurchasingDepartment no + PurchasingOrganizationName no + Purpose no + Purpose no + QltyBasicData no + QltyBasicData no + QltyCueSheet no + QltyCueSheet no + QltyEndOfModulation no + QltyEndOfModulation no + QltyOperatorComment no + QltyOperatorComment no + QltyQualityEvent no + QltyQualityEvent no + QltyQualityParameter no + QltyQualityParameter no + QltyStartOfModulation no + QltyStartOfModulation no + QualityFlag no + QuantizationDefault? no + RGBAEssenceDescriptor MXF + RIFFChunkData? no + RIFFChunkID no + RIFFChunkLength no + RP217DataStreamPID no + RP217VideoStreamPID no + RandomIndexMetadata? no + RandomIndexMetadataV10? no + Rating no + RecordedFormat no + RecordedFormat no + RecordedTrackNumber no + RecordingLabelName no + RecordingLabelName no + ReelOrRollNumber no + RegionCode no + RegionName no + RegionName no + RegisterAction no + RegisterAdministrationArray? no + RegisterAdministrationNotes no + RegisterAdministrationObject? no + RegisterApproverName no + RegisterChildEntryArray? no + RegisterCreationTime no + RegisterEditorName no + RegisterEntryAdministrationObject? no + RegisterEntryArray? no + RegisterEntryStatus? no + RegisterItemDefiningDocumentName no + RegisterItemDefinition no + RegisterItemHierarchyLevel no + RegisterItemIntroductionVersion no + RegisterItemName no + RegisterItemNotes no + RegisterItemOriginatorName no + RegisterItemStatusChangeDateTime no + RegisterItemSymbol? no + RegisterItemUL? no + RegisterKind? no + RegisterReleaseDateTime no + RegisterStatusKind? no + RegisterUserName no + RegisterUserTime no + RegisterVersion no + RegistrantName no + RelatedMaterialDescription no + RelatedMaterialDescription no + RelatedMaterialLocators? no + RelativePositionInSequenceName no + RelativePositionInSequenceOffset no + RelativeScope no + RelativeTrack no + ReleasableCountryCode no + ReleasableCountryCode no + RenamedType? no + ResourceID? no + RestrictionsonUse no + ReversePlay no + ReversedByteOrder no + Rights MXF + RightsComment no + RightsComment no + RightsConditionDescription no + RightsConditionDescription no + RightsManagementAuthority no + RightsManagementAuthority no + RightsSets? no + RightsStartDateTime no + RightsStopDateTime no + Rightsholder no + Rightsholder no + RoleName no + RoleName no + RoomNumber no + RoomNumber no + RoomOrSuiteName no + RoomOrSuiteName no + RootFormatVersion no + RootMetaDictionary? no + RootObjectDirectory? no + RootPreface? no + RoundedCaptureFilmFrameRate no + RoundedTimecodeTimebase no + RoundingLaw no + RoundingMethodCode no + RoyaltyIncomeInformation no + RoyaltyPaymentInformation no + Rsiz no + SDKVersion no + SICI? no + SMPTE12MUserDateTime? no + SMPTE309MUserDateTime? no + SMPTE337MDataStreamNumber no + SMPTEUL? no + SalesContractNumber no + Salutation no + Salutation no + SampleIndex? no + SampleRate no + SampledHeight no + SampledWidth no + SampledXOffset no + SampledYOffset no + SamplingHierarchyCode no + SamplingStructureCode no + ScanningDirection no + SceneFramework MXF + SceneNumber no + SceneNumber no + SceneSettingPeriodSets? no + SceneShotSets? no + ScramblingKeyKind no + ScramblingKeyValue no + Scripting MXF + ScriptingKind no + ScriptingKind no + ScriptingLocators? no + ScriptingSets? no + ScriptingText no + ScriptingText no + SeasonEpisodeNumber no + SeasonEpisodeTitle no + SecondGivenName no + SecondGivenName no + SecondaryExtendedSpokenLanguageCode no + SecondaryOriginalExtendedSpokenLanguageCode no + SecondaryOriginalLanguageCode no + SecondarySpokenLanguageCode no + SecondaryTitle no + SecondaryTitle no + SectorSize no + SecurityClassification no + SecurityClassification no + SecurityClassificationCaveats no + SecurityClassificationCaveats no + Selected? no + SensorMode no + SensorRollAngle no + SensorSize no + SensorType no + SensorTypeCode no + Sequence? no + SequenceOffset no + SequenceSet MXF + SeriesNumber no + SeriesNumber no + SeriesinaSeriesGroupCount no + SetElementType? no + SettingCityName no + SettingCityName no + SettingCountryCode no + SettingCountryName no + SettingCountryName no + SettingDateTime no + SettingDescription no + SettingDescription no + SettingPeriod MXF + SettingPeriodDescription no + SettingPeriodDescription no + SettingPostalCode no + SettingPostalCode no + SettingRegionCode no + SettingRegionName no + SettingRoomNumber no + SettingRoomNumber no + SettingStateOrProvinceOrCountyName no + SettingStateOrProvinceOrCountyName no + SettingStreetName no + SettingStreetName no + SettingStreetNumberOrBuildingName no + SettingStreetNumberOrBuildingName no + SettingTownName no + SettingTownName no + ShimName no + ShootingCountryCode no + ShootingRegionCode no + ShootingRegionName no + Shot MXF + ShotComment no + ShotComment no + ShotCommentKind no + ShotCommentKind no + ShotCueWordsSet? no + ShotDescription no + ShotDescription no + ShotDuration no + ShotList no + ShotLocationSets? no + ShotParticipantRoleSets? no + ShotPersonSets? no + ShotStartPosition no + ShotTrackIDs no + SideNumber no + Signal-to-NoiseRatio no + SignalFormCode no + SignalStandard no + SignalStandard no + SignatureTuneFlag no + SimpleFlaggingCount no + SingleSequenceFlag no + Size no + SlantRange no + SlateInformation no + SlateTitle no + SliceCount no + SliceNumber no + SliceOffsetList? no + Software-OnlySupportFlag no + SourceClip? no + SourceContainerFormat? no + SourceImageCenterXCoordinate no + SourceImageCenterYCoordinate no + SourceIndex? no + SourceKey? no + SourceLength no + SourceOrganization no + SourceOrganization no + SourcePackage MXF + SourcePackageID? no + SourceSpecies? no + SourceTrackID no + SourceTrackIDs no + SourceValue? no + SpeedChangeEffectFlag no + SplicingMetadata? no + StartTimeRelativeToReference no + StartTimeRelativeToReference no + StartTimecode no + StartTimecodeRelativeToReference? no + StateOrProvinceOrCountyName no + StateOrProvinceOrCountyName no + StaticTrack MXF + StillFrame? no + StorageDeviceKind no + StorageKind no + StorageKind no + StorageKindCode no + StorageMediaID no + StorageMediaKind no + StoredANCLineNumber no + StoredF2Offset no + StoredVBILineNumber no + StratumKind no + StreamData? no + StreamElementType? no + StreamID no + StreamOffset no + StreamPositionIndicator no + StreamPositionIndicator no + StreamPositionIndicator no + StreamPositionIndicator no + StreetName no + StreetName no + StreetNumber no + StreetNumber no + StringElementType? no + StructuralComponent MXF + Sub-descriptors? no + SubDescriptor? no + SubDescriptors? no + SubjectAbsoluteHeading no + SubjectAbsoluteSpeed no + SubjectDistance no + SubjectName no + SubjectName no + SubjectRelativeHeading no + SubjectRelativePositionalAccuracy no + SubjectRelativeSpeed no + SubtitleDatafileFlag no + SubtitlesPresent no + SupplementaryName no + SupplementaryName no + SupplementaryOrganizationName no + SupplementaryOrganizationName no + SupplierAccountName no + SupplierAccountName no + SupplierAccountNumber no + SupplierIdentificationKind no + SupplierIdentificationValue no + SupplyContractNumber no + SupplyingDepartmentName no + SupportOrAdministrationStatus no + SupportOrAdministrationStatus no + SupportOrganizationRole no + SupportOrganizationRole no + SystemNameOrNumber no + TIFFSummary? no + TaggedValueDefinitions? no + TaggedValueParentProperties? no + TakeNumber no + TapeBatchNumber no + TapeBatchNumber no + TapeCapacity no + TapeFormat? no + TapeFormulation no + TapeFormulation no + TapeManufacturer no + TapeManufacturer no + TapePartitionCapacity no + TapeShellKind no + TapeShellKind no + TapeStock no + TapeStock no + TargetAudience no + TargetAudience no + TargetClassOfStrongReference? no + TargetClassOfWeakReference? no + TargetSet? no + TargetWidth no + TechnicalValue no + TelephoneNumber no + TelephoneNumber no + TeletextSubtitlesAvailable no + TeletextSubtitlesFlag no + TemporalOffset no + TerminatingFillerData no + TextLocator MXF + TextlessBlackDuration no + TextlessMaterial no + TextualDescriptionKind no + TextualDescriptionKind no + Theme no + Theme no + ThemeMusicFlag no + ThesaurusName no + ThesaurusName no + ThirdGivenName no + ThirdGivenName no + TimePeriodName no + TimePeriodName no + TimeSystemOffset no + TimeUnitKind no + TimebaseReferenceTrackID no + TimecodeArray? no + TimecodeComponent MXF + TimecodeCreationDateTime? no + TimecodeEndDateTime? no + TimecodeEventEndDateTime? no + TimecodeEventStartDateTime? no + TimecodeKind no + TimecodeLastModifyDate? no + TimecodeModifyDate? no + TimecodeSourceKind no + TimecodeStartDateTime? no + TimecodeStreamData? no + TimecodeTimebase no + TimecodeTimebase no + TimecodeUserBitsFlag no + TimepointValue? no + TimingBiasCorrection no + TimingBiasCorrectionDescription no + TitleKind no + TitleKind no + Titles MXF + TitlesSets? no + ToleranceInterpolationMethod? no + ToleranceMode? no + ToleranceWindow? no + ToolkitVersion no + TotalCurrencyAmount no + TotalEpisodeCount no + TotalIncome no + TotalLinesperFrame no + TotalNumberInSequence no + TotalPayment no + TotalSamplesperLine no + Track MXF + TrackID no + TrackName no + TrackName no + TrackNumber no + TrackNumberBatch no + Tracks? no + TrafficID no + TrailingLines no + TranscriptReference no + TranscriptReference no + TransferFilmFrameRate no + TransitionEffect? no + TransmissionID no + TransportStreamID no + TripletSequenceNumber no + TypeDefinitionElementValueList no + TypeDefinitionExtendibleElementValues? no + TypeDefinitions? no + UCSEncoding no + UPID? no + UPN? no + URL no + URL no + URL no + URL no + URN no + UTCEndDateTime no + UTCEventEndDateTime no + UTCEventEndDateTime no + UTCEventStartDateTime no + UTCEventStartDateTime no + UTCInstantDateTime no + UTCInstantDateTime no + UTCLastModifyDate no + UTCLastModifyDate no + UTCStartDateTime no + UTCStartDateTime no + UTCUserDateTime no + UniformDataFlag no + UnknownBWFChunks? no + UpstreamAudioCompressionAlgorithm no + UseDefaultValue no + UserDataMode? no + UserName no + UserName no + UserPosition no + V10IndexTableSegment? no + VBEEndOffset no + VBIDataDescriptor MXF + VBILineCount no + VBIPayloadByteArray no + VBIPayloadSampleCoding no + VBIPayloadSampleCount no + VBIWrappingType no + VC-1AverageBitrate no + VC-1BPictureCount no + VC-1CodedContentType no + VC-1IdenticalGOP no + VC-1InitializationMetadata? no + VC-1Level no + VC-1MaximumBitrate no + VC-1MaximumGOP no + VC-1Profile no + VC-1SingleSequence no + Value? no + VariableArrayElementType? no + VersionNumber no + VersionNumberString no + VersionNumberString no + VersionTitle no + VersionTitle no + VerticalActionSafePercentage no + VerticalDatum no + VerticalGraphicsSafePercentage no + VerticalSub-sampling no + VideoAndFilmFrameRelationship no + VideoAverageBitrate no + VideoClipDuration no + VideoCodingSchemeID no + VideoColorKind no + VideoCompressionAlgorithm no + VideoDeviceKind no + VideoDeviceParameterName no + VideoDeviceParameterSetting no + VideoFixedBitrate no + VideoIndexArray? no + VideoLineMap no + VideoNoiseReductionAlgorithm no + VideoOrImageCompressionAlgorithm no + VideoPayloadIdentifier no + VideoPayloadIdentifier2002 no + VideoTestParameter no + VideoTestResult no + VideoTestResult no + VideoWatermarkKind no + ViewportAspectRatio no + ViewportHeight no + ViewportImageCenterCCoordinate no + ViewportImageCenterYCoordinate no + ViewportWidth no + VoiceTalentName no + WAVESummary? no + WaveAudioDescriptor MXF + Weighting no + WhiteReferenceLevel no + Work-in-ProgressFlag no + WorkingTitle no + WorkingTitle no + XMLDocumentText? no + XMLDocumentText no + XMLDocumentText no + XMLDocumentText? no + XOsiz no + XTOsiz no + XTsiz no + Xsiz no + YOsiz no + YTOsiz no + YTsiz no + Ysiz no + +=head3 MXF Header Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 MXFVersion no + 24 FooterPosition no + 32 HeaderSize no + +=head2 DV Tags + +The following tags are extracted from DV videos. + + Tag Name Writable + -------- -------- + AspectRatio no + AudioBitsPerSample no + AudioChannels no + AudioSampleRate no + Colorimetry no + DateTimeOriginal no + Duration no + FrameRate no + ImageHeight no + ImageWidth no + TotalBitrate no + VideoFormat no + VideoScanType no + +=head2 Flash Tags + +The information below is extracted from SWF (Shockwave Flash) files. Tags +with string ID's represent information extracted from the file header. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Compressed' Compressed no + 'Duration' Duration no + 'FlashVersion' FlashVersion no + 'FrameCount' FrameCount no + 'FrameRate' FrameRate no + 'ImageHeight' ImageHeight no + 'ImageWidth' ImageWidth no + 0x0045 FlashAttributes no + 0x004d XMP XMP + +=head3 Flash FLV Tags + +Information is extracted from the following packets in FLV (Flash Video) +files. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0008 Audio Flash Audio + 0x0009 Video Flash Video + 0x0012 Meta Flash Meta + +=head3 Flash Audio Tags + +Information extracted from the Flash Audio header. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Bit0-3' AudioEncoding no + 'Bit4-5' AudioSampleRate no + 'Bit6' AudioBitsPerSample no + 'Bit7' AudioChannels no + +=head3 Flash Video Tags + +Information extracted from the Flash Video header. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Bit4-7' VideoEncoding no + +=head3 Flash Meta Tags + +Below are a few observed FLV Meta tags, but ExifTool will attempt to extract +information from any tag found. + + Tag ID Tag Name Writable + ------ -------- -------- + 'audiocodecid' AudioCodecID no + 'audiodatarate' AudioBitrate no + 'audiodelay' AudioDelay no + 'audiosamplerate' AudioSampleRate no + 'audiosamplesize' AudioSampleSize no + 'audiosize' AudioSize no + 'bytelength' ByteLength no + 'canSeekToEnd' CanSeekToEnd no + 'canseekontime' CanSeekOnTime no + 'createdby' CreatedBy no + 'creationdate' CreateDate no + 'cuePoints' CuePoint Flash CuePoint + 'datasize' DataSize no + 'duration' Duration no + 'filesize' FileSizeBytes no + 'framerate' FrameRate no + 'hasAudio' HasAudio no + 'hasCuePoints' HasCuePoints no + 'hasKeyframes' HasKeyFrames no + 'hasMetadata' HasMetadata no + 'hasVideo' HasVideo no + 'height' ImageHeight no + 'httphostheader' HTTPHostHeader no + 'keyframesFilepositions' KeyFramePositions no + 'keyframesTimes' KeyFramesTimes no + 'lastkeyframetimestamp' LastKeyFrameTime no + 'lasttimestamp' LastTimeStamp no + 'liveXML' XMP XMP + 'metadatacreator' MetadataCreator no + 'metadatadate' MetadataDate no + 'pmsg' Message no + 'purl' URL no + 'sourcedata' SourceData no + 'starttime' StartTime no + 'stereo' Stereo no + 'totaldatarate' TotalDataRate no + 'totalduration' TotalDuration no + 'videocodecid' VideoCodecID no + 'videodatarate' VideoBitrate no + 'videosize' VideoSize no + 'width' ImageWidth no + +=head3 Flash CuePoint Tags + +These tag names are added to the CuePoint name to generate complete tag +names like "CuePoint0Name". + + Tag ID Tag Name Writable + ------ -------- -------- + 'name' Name no + 'parameters' Parameter Flash Parameter + 'time' Time no + 'type' Type no + +=head3 Flash Parameter Tags + +There are no pre-defined parameter tags, but ExifTool will extract any +existing parameters, with tag names like "CuePoint0ParameterXxx". + + Tag ID Tag Name Writable + ------ -------- -------- + [no tags known] + +=head2 Real Tags + +ExifTool recognizes three basic types of Real audio/video files: 1) +RealMedia (RM, RV and RMVB), 2) RealAudio (RA), and 3) Real Metafile (RAM +and RPM). + +=head3 Real Media Tags + +These B<Tag ID>'s are Chunk ID's used in RealMedia and RealVideo (RM, RV and +RMVB) files. + + Tag ID Tag Name Writable + ------ -------- -------- + 'CONT' CONT Real ContentDescr + 'MDPR' MDPR Real MediaProps + 'PROP' PROP Real Properties + 'RJMD' RJMD Real Metadata + +=head3 Real ContentDescr Tags + + Sequence Tag Name Writable + -------- -------- -------- + 0 TitleLen? no + 1 Title no + 2 AuthorLen? no + 3 Author no + 4 CopyrightLen? no + 5 Copyright no + 6 CommentLen? no + 7 Comment no + +=head3 Real MediaProps Tags + + Sequence Tag Name Writable + -------- -------- -------- + 0 StreamNumber no + 1 StreamMaxBitrate no + 2 StreamAvgBitrate no + 3 StreamMaxPacketSize no + 4 StreamAvgPacketSize no + 5 StreamStartTime no + 6 StreamPreroll no + 7 StreamDuration no + 8 StreamNameLen? no + 9 StreamName no + 10 StreamMimeLen? no + 11 StreamMimeType no + 12 FileInfoLen? no + 13 FileInfoLen2? no + 14 FileInfoVersion no + 15 PhysicalStreams? no + 16 PhysicalStreamNumbers? no + 17 DataOffsets? no + 18 NumRules? no + 19 PhysicalStreamNumberMap? no + 20 NumProperties? no + 21 FileInfoProperties Real FileInfo + +=head3 Real FileInfo Tags + +The following tags have been observed in the FileInfo properties, but any +other existing information will also be extracted. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Audiences' Audiences no + 'Audio Format' AudioFormat no + 'Content Rating' ContentRating no + 'Creation Date' CreateDate no + 'Description' Description no + 'File ID' FileID no + 'Generated By' Software no + 'Indexable' Indexable no + 'Keywords' Keywords no + 'Modification Date' ModifyDate no + 'Target Audiences' TargetAudiences no + 'Video Quality' VideoQuality no + 'audioMode' AudioMode no + 'videoMode' VideoMode no + +=head3 Real Properties Tags + + Sequence Tag Name Writable + -------- -------- -------- + 0 MaxBitrate no + 1 AvgBitrate no + 2 MaxPacketSize no + 3 AvgPacketSize no + 4 NumPackets no + 5 Duration no + 6 Preroll no + 7 IndexOffset? no + 8 DataOffset? no + 9 NumStreams no + 10 Flags no + +=head3 Real Metadata Tags + +The tags below represent information which has been observed in the Real +Metadata format, but ExifTool will extract any information it finds in this +format. (As far as I can tell from the referenced documentation, string +values should be plain text, but this is not the case for the only sample +file I have been able to obtain containing this information. These tags +could also be split into separate sub-directories, but this will wait until +I have better documentation or a more complete set of samples.) + + Tag ID Tag Name Writable + ------ -------- -------- + 'Album/Name' AlbumName no + 'Track/Category' TrackCategory no + 'Track/Comments' TrackComments no + 'Track/Lyrics' TrackLyrics no + +=head3 Real Audio Tags + +Tags in the following table reference information extracted from various +versions of RealAudio (RA) files. + + Tag ID Tag Name Writable + ------ -------- -------- + '.ra3' RA3 Real AudioV3 + '.ra4' RA4 Real AudioV4 + '.ra5' RA5 Real AudioV5 + +=head3 Real AudioV3 Tags + + Sequence Tag Name Writable + -------- -------- -------- + 0 Channels no + 1 Unknown? no + 2 BytesPerMinute no + 3 AudioBytes no + 4 TitleLen? no + 5 Title no + 6 ArtistLen? no + 7 Artist no + 8 CopyrightLen? no + 9 Copyright no + 10 CommentLen? no + 11 Comment no + +=head3 Real AudioV4 Tags + + Sequence Tag Name Writable + -------- -------- -------- + 0 FourCC1? no + 1 AudioFileSize? no + 2 Version2? no + 3 HeaderSize? no + 4 CodecFlavorID? no + 5 CodedFrameSize? no + 6 AudioBytes no + 7 BytesPerMinute no + 8 Unknown? no + 9 SubPacketH? no + 10 AudioFrameSize no + 11 SubPacketSize? no + 12 Unknown? no + 13 SampleRate no + 14 Unknown? no + 15 BitsPerSample no + 16 Channels no + 17 FourCC2Len? no + 18 FourCC2? no + 19 FourCC3Len? no + 20 FourCC3? no + 21 Unknown? no + 22 Unknown? no + 23 TitleLen? no + 24 Title no + 25 ArtistLen? no + 26 Artist no + 27 CopyrightLen? no + 28 Copyright no + 29 CommentLen? no + 30 Comment no + +=head3 Real AudioV5 Tags + + Sequence Tag Name Writable + -------- -------- -------- + 0 FourCC1? no + 1 AudioFileSize? no + 2 Version2? no + 3 HeaderSize? no + 4 CodecFlavorID? no + 5 CodedFrameSize? no + 6 AudioBytes no + 7 BytesPerMinute no + 8 Unknown? no + 9 SubPacketH? no + 10 FrameSize? no + 11 SubPacketSize? no + 12 SampleRate no + 13 SampleRate2? no + 14 BitsPerSample no + 15 Channels no + 16 Genr? no + 17 FourCC3? no + +=head3 Real Metafile Tags + +Tags representing information extracted from Real Audio Metafile and +RealMedia Plug-in Metafile (RAM and RPM) files. + + Tag ID Tag Name Writable + ------ -------- -------- + 'txt' Text no + 'url' URL no + +=head2 Red Tags + +Tags extracted from Redcode R3D video files. + + Tag ID Tag Name Writable + ------ -------- -------- + 'RED1' Red1Header Red RED1 + 'RED2' Red2Header Red RED2 + 0x1000 StartEdgeCode no + 0x1001 StartTimecode no + 0x1002 OtherDate1 no + 0x1003 OtherDate2 no + 0x1004 OtherDate3 no + 0x1005 DateTimeOriginal no + 0x1006 SerialNumber no + 0x1019 CameraType no + 0x101a ReelNumber no + 0x101b Take no + 0x1023 DateCreated no + 0x1024 TimeCreated no + 0x1025 FirmwareVersion no + 0x1029 ReelTimecode no + 0x102a StorageType no + 0x1030 StorageFormatDate no + 0x1031 StorageFormatTime no + 0x1032 StorageSerialNumber no + 0x1033 StorageModel no + 0x1036 AspectRatio no + 0x1042 Revision no + 0x1056 OriginalFileName no + 0x106e LensMake no + 0x106f LensNumber no + 0x1070 LensModel no + 0x1071 Model no + 0x107c CameraOperator no + 0x1086 VideoFormat no + 0x1096 Filter no + 0x10a0 Brain no + 0x10a1 Sensor no + 0x200d ColorTemperature no + 0x204b RGBCurves no + 0x2066 OriginalFrameRate no + 0x4037 CropArea no + 0x403b ISO no + 0x406a FNumber no + 0x406b FocalLength no + 0x606c FocusDistance no + +=head3 Red RED1 Tags + +Redcode version 1 header. + + Index1 Tag Name Writable + ------ -------- -------- + 7 RedcodeVersion no + 54 ImageWidth no + 58 ImageHeight no + 62 FrameRate no + 67 OriginalFileName no + +=head3 Red RED2 Tags + +Redcode version 2 header. + + Index1 Tag Name Writable + ------ -------- -------- + 7 RedcodeVersion no + 76 ImageWidth no + 80 ImageHeight no + 86 FrameRate no + +=head2 AIFF Tags + +Tags extracted from Audio Interchange File Format (AIFF) files. See +L<http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/AIFF.html> for +the AIFF specification. + + Tag ID Tag Name Writable + ------ -------- -------- + '(c) ' Copyright no + 'ANNO' Annotation no + 'APPL' ApplicationData no + 'AUTH' Author no + 'COMM' Common AIFF Common + 'COMT' Comment AIFF Comment + 'FVER' FormatVersion AIFF FormatVers + 'ID3 ' ID3 ID3 + 'NAME' Name no + +=head3 AIFF Common Tags + + Index2 Tag Name Writable + ------ -------- -------- + 0 NumChannels no + 1 NumSampleFrames no + 3 SampleSize no + 4 SampleRate no + 9 CompressionType no + 11 CompressorName no + +=head3 AIFF Comment Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 CommentTime no + 0x0001 MarkerID no + 0x0002 Comment no + +=head3 AIFF FormatVers Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 FormatVersionTime no + +=head2 ASF Tags + +The ASF format is used by Windows WMA and WMV files, and DIVX videos. Tag +ID's aren't listed because they are huge 128-bit GUID's that would ruin the +formatting of this table. + + Tag Name Writable + -------- -------- + SimpleIndex no + TimecodeIndex no + Header ASF Header + Data no + XMP XMP + Index no + MediaIndex no + +=head3 ASF Header Tags + + Tag Name Writable + -------- -------- + Padding no + ScriptCommand no + ContentBranding ASF ContentBranding + ContentEncryption no + DigitalSignature no + ExtendedContentEncryption no + HeaderExtension ASF HeaderExtension + ContentDescription ASF ContentDescr + ErrorCorrection no + StreamBitrateProps no + CodecList ASF CodecList + FileProperties ASF FileProperties + StreamProperties ASF StreamProperties + ExtendedContentDescr ASF ExtendedDescr + BitrateMutualExclusion no + Marker no + +=head3 ASF ContentBranding Tags + + Tag Name Writable + -------- -------- + BannerImageType no + BannerImage no + BannerImageURL no + CopyrightURL no + +=head3 ASF HeaderExtension Tags + + Tag Name Writable + -------- -------- + ExtendedStreamProps no + AdvancedContentEncryption no + MetadataLibrary ASF ExtendedDescr + TimecodeIndexParms no + Compatibility no + LanguageList no + AdvancedMutualExcl no + BandwidthSharing no + Reserved1 no + Metadata ASF ExtendedDescr + GroupMutualExclusion no + StreamPrioritization no + IndexParameters no + +=head3 ASF ExtendedDescr Tags + + Tag Name Writable + -------- -------- + ASFLeakyBucketPairs no + ASFPacketCount no + ASFSecurityObjectsSize no + AlbumArtist no + AlbumCoverURL no + AlbumTitle no + AspectRatioX no + AspectRatioY no + AudioFileURL no + AudioSourceURL no + Author no + AuthorURL no + AverageLevel no + BannerImageData no + BannerImageType no + BannerImageURL no + BeatsPerMinute no + Bitrate no + Broadcast no + BufferAverage no + Can_Skip_Backward no + Can_Skip_Forward no + Category no + Codec no + Composer no + Conductor no + ContainerFormat no + ContentDistributor no + ContentGroupDescription no + Copyright no + CopyrightURL no + CurrentBitrate no + DRM no + DRM_ContentID no + DRM_DRMHeader no + DRM_DRMHeader_ContentDistributor no + DRM_DRMHeader_ContentID no + DRM_DRMHeader_IndividualizedVersion no + DRM_DRMHeader_KeyID no + DRM_DRMHeader_LicenseAcqURL no + DRM_DRMHeader_SubscriptionContentID no + DRM_IndividualizedVersion no + DRM_KeyID no + DRM_LASignatureCert no + DRM_LASignatureLicSrvCert no + DRM_LASignaturePrivKey no + DRM_LASignatureRootCert no + DRM_LicenseAcqURL no + DRM_V1LicenseAcqURL no + DVDID no + Description no + Director no + Duration no + EncodedBy no + EncodingSettings no + EncodingTime no + FileSize no + Genre no + GenreID no + HasArbitraryDataStream no + HasAttachedImages no + HasAudio no + HasFileTransferStream no + HasImage no + HasScript no + HasVideo no + ISRC no + InitialKey no + IsVBR no + Is_Protected no + Is_Trusted no + Language no + Lyrics no + Lyrics_Synchronised no + MCDI no + MediaClassPrimaryID no + MediaClassSecondaryID no + MediaCredits no + MediaIsDelay no + MediaIsFinale no + MediaIsLive no + MediaIsPremiere no + MediaIsRepeat no + MediaIsSAP no + MediaIsStereo no + MediaIsSubtitled no + MediaIsTape no + MediaNetworkAffiliation no + MediaOriginalBroadcastDateTime no + MediaOriginalChannel no + MediaStationCallSign no + MediaStationName no + ModifiedBy no + Mood no + NSC_Address no + NSC_Description no + NSC_Email no + NSC_Name no + NSC_Phone no + NumberOfFrames no + OptimalBitrate no + OriginalAlbumTitle no + OriginalArtist no + OriginalFileName no + OriginalLyricist no + OriginalReleaseTime no + OriginalReleaseYear no + ParentalRating no + ParentalRatingReason no + PartOfSet no + PeakBitrate no + PeakValue no + Period no + Picture ASF Picture + PlaylistDelay no + Producer no + PromotionURL no + ProtectionType no + Provider no + ProviderCopyright no + ProviderRating no + ProviderStyle no + Publisher no + RadioStationName no + RadioStationOwner no + Rating no + Seekable no + SharedUserRating no + Signature_Name no + StreamTypeInfo no + Stridable no + Subtitle no + SubtitleDescription no + SubscriptionContentID no + Text no + Title no + ToolName no + ToolVersion no + Track no + TrackNumber no + UniqueFileIdentifier no + UserWebURL no + VBRPeak no + VideoClosedCaptioning no + VideoFrameRate no + VideoHeight no + VideoWidth no + WMADRCAverageReference no + WMADRCAverageTarget no + WMADRCPeakReference no + WMADRCPeakTarget no + WMCollectionGroupID no + WMCollectionID no + WMContentID no + Writer no + Year no + +=head3 ASF Picture Tags + + Tag Name Writable + -------- -------- + PictureType no + PictureMIMEType no + PictureDescription no + Picture no + +=head3 ASF ContentDescr Tags + + Tag Name Writable + -------- -------- + Title no + Author no + Copyright no + Description no + Rating no + +=head3 ASF CodecList Tags + + Tag Name Writable + -------- -------- + AudioCodecDescription no + AudioCodecName no + OtherCodecDescription no + OtherCodecName no + VideoCodecDescription no + VideoCodecName no + +=head3 ASF FileProperties Tags + + Index1 Tag Name Writable + ------ -------- -------- + 0 FileID no + 16 FileLength no + 24 CreationDate no + 32 DataPackets no + 40 Duration no + 48 SendDuration no + 56 Preroll no + 64 Flags no + 68 MinPacketSize no + 72 MaxPacketSize no + 76 MaxBitrate no + +=head3 ASF StreamProperties Tags + +Tags with index 54 and greater are conditional based on the StreamType. + + Index1 Tag Name Writable + ------ -------- -------- + 0 StreamType no + 16 ErrorCorrectionType no + 32 TimeOffset no + 48 StreamNumber no + 54 AudioCodecID no + ImageWidth no + 56 AudioChannels no + 58 AudioSampleRate no + ImageHeight no + +=head2 WTV Tags + +Tags found in Windows recorded TV (WTV) videos. + + Tag ID Tag Name Writable + ------ -------- -------- + 'table.0.entries.legacy_attrib' Metdata WTV Metadata + +=head3 WTV Metadata Tags + +ExifTool will extract any tag found, even if not in this table. + + Tag Name Writable + -------- -------- + ATSCContent no + ActualSoftPostPadding no + ActualSoftPrePadding no + Bitrate? no + BrandingImageID no + BrandingName no + ContentProtected no + ContentProtectedPercent no + DTVContent no + Duration no + EncodeTime no + EndTime no + ExpirationDate? no + ExpirationSpan? no + Genre no + HDContent no + HardPostPadding no + HardPrePadding no + InBandRatingAttributes no + InBandRatingLevel no + InBandRatingSystem no + KeepUntil no + Language no + MediaClassPrimaryID no + MediaClassSecondaryID no + MediaCredits no + MediaIsDelay no + MediaIsFinale no + MediaIsLive no + MediaIsMovie no + MediaIsPremiere no + MediaIsRepeat no + MediaIsSAP no + MediaIsSport no + MediaIsStereo no + MediaIsSubtitled no + MediaIsTape no + MediaNetworkAffiliation no + MediaOriginalBroadcastDateTime no + MediaOriginalChannel no + MediaOriginalChannelSubNumber no + MediaOriginalRunTime no + MediaStationCallSign no + MediaStationName no + MediaThumbAspectRatioX no + MediaThumbAspectRatioY no + MediaThumbHeight no + MediaThumbRatingAttributes no + MediaThumbRatingLevel no + MediaThumbRatingSystem no + MediaThumbRet no + MediaThumbStride no + MediaThumbTimeStamp? no + MediaThumbWidth no + OriginalReleaseTime no + OriginalSoftPostPadding no + OriginalSoftPrePadding no + ParentalRating no + ParentalRatingReason no + ProgramID no + Provider no + ProviderCopyright no + ProviderRating no + Quality no + RequestID no + ScheduleItemID no + SeriesUID no + ServiceID no + Subtitle no + SubtitleDescription no + Title no + VideoClosedCaptioning no + Watched no + +=head2 DICOM Tags + +The DICOM format is based on the ACR-NEMA specification, but adds a file +header and a number of new tags. ExifTool will extract information from +either type of file. The Tag ID's in the following table are the tag group +and element numbers in hexadecimal, as given in the DICOM specification (see +L<http://medical.nema.org/>). The table below contains tags from the DICOM +2009 and earlier specifications plus some vendor-specific private tags. + +Note that DICOM information may be saved in other file formats using the +L<XMP DICOM Tags|Image::ExifTool::TagNames/XMP DICOM Tags>. + + Tag ID Tag Name Writable + ------ -------- -------- + 0002,0000 FileMetaInfoGroupLength no + 0002,0001 FileMetaInfoVersion no + 0002,0002 MediaStorageSOPClassUID no + 0002,0003 MediaStorageSOPInstanceUID no + 0002,0010 TransferSyntaxUID no + 0002,0012 ImplementationClassUID no + 0002,0013 ImplementationVersionName no + 0002,0016 SourceApplicationEntityTitle no + 0002,0100 PrivateInformationCreatorUID no + 0002,0102 PrivateInformation no + 0004,1130 FileSetID no + 0004,1141 FileSetDescriptorFileID no + 0004,1142 SpecificCharacterSetOfFile no + 0004,1200 FirstDirectoryRecordOffset no + 0004,1202 LastDirectoryRecordOffset no + 0004,1212 FileSetConsistencyFlag no + 0004,1220 DirectoryRecordSequence no + 0004,1400 OffsetOfNextDirectoryRecord no + 0004,1410 RecordInUseFlag no + 0004,1420 LowerLevelDirectoryEntityOffset no + 0004,1430 DirectoryRecordType no + 0004,1432 PrivateRecordUID no + 0004,1500 ReferencedFileID no + 0004,1504 MRDRDirectoryRecordOffset no + 0004,1510 ReferencedSOPClassUIDInFile no + 0004,1511 ReferencedSOPInstanceUIDInFile no + 0004,1512 ReferencedTransferSyntaxUIDInFile no + 0004,151A ReferencedRelatedSOPClassUIDInFile no + 0004,1600 NumberOfReferences no + 0008,0000 IdentifyingGroupLength no + 0008,0001 LengthToEnd no + 0008,0005 SpecificCharacterSet no + 0008,0006 LanguageCodeSequence no + 0008,0008 ImageType no + 0008,0010 RecognitionCode no + 0008,0012 InstanceCreationDate no + 0008,0013 InstanceCreationTime no + 0008,0014 InstanceCreatorUID no + 0008,0016 SOPClassUID no + 0008,0018 SOPInstanceUID no + 0008,001A RelatedGeneralSOPClassUID no + 0008,001B OriginalSpecializedSOPClassUID no + 0008,0020 StudyDate no + 0008,0021 SeriesDate no + 0008,0022 AcquisitionDate no + 0008,0023 ContentDate no + 0008,0024 OverlayDate no + 0008,0025 CurveDate no + 0008,002A AcquisitionDateTime no + 0008,0030 StudyTime no + 0008,0031 SeriesTime no + 0008,0032 AcquisitionTime no + 0008,0033 ContentTime no + 0008,0034 OverlayTime no + 0008,0035 CurveTime no + 0008,0040 DataSetType no + 0008,0041 DataSetSubtype no + 0008,0042 NuclearMedicineSeriesType no + 0008,0050 AccessionNumber no + 0008,0052 QueryRetrieveLevel no + 0008,0054 RetrieveAETitle no + 0008,0056 InstanceAvailability no + 0008,0058 FailedSOPInstanceUIDList no + 0008,0060 Modality no + 0008,0061 ModalitiesInStudy no + 0008,0062 SOPClassesInStudy no + 0008,0064 ConversionType no + 0008,0068 PresentationIntentType no + 0008,0070 Manufacturer no + 0008,0080 InstitutionName no + 0008,0081 InstitutionAddress no + 0008,0082 InstitutionCodeSequence no + 0008,0090 ReferringPhysicianName no + 0008,0092 ReferringPhysicianAddress no + 0008,0094 ReferringPhysicianTelephoneNumber no + 0008,0096 ReferringPhysicianIDSequence no + 0008,0100 CodeValue no + 0008,0102 CodingSchemeDesignator no + 0008,0103 CodingSchemeVersion no + 0008,0104 CodeMeaning no + 0008,0105 MappingResource no + 0008,0106 ContextGroupVersion no + 0008,0107 ContextGroupLocalVersion no + 0008,010B ContextGroupExtensionFlag no + 0008,010C CodingSchemeUID no + 0008,010D ContextGroupExtensionCreatorUID no + 0008,010F ContextIdentifier no + 0008,0110 CodingSchemeIDSequence no + 0008,0112 CodingSchemeRegistry no + 0008,0114 CodingSchemeExternalID no + 0008,0115 CodingSchemeName no + 0008,0116 ResponsibleOrganization no + 0008,0117 ContextUID no + 0008,0201 TimezoneOffsetFromUTC no + 0008,1000 NetworkID no + 0008,1010 StationName no + 0008,1030 StudyDescription no + 0008,1032 ProcedureCodeSequence no + 0008,103E SeriesDescription no + 0008,1040 InstitutionalDepartmentName no + 0008,1048 PhysiciansOfRecord no + 0008,1049 PhysiciansOfRecordIDSequence no + 0008,1050 PerformingPhysicianName no + 0008,1052 PerformingPhysicianIDSequence no + 0008,1060 NameOfPhysicianReadingStudy no + 0008,1062 PhysicianReadingStudyIDSequence no + 0008,1070 OperatorsName no + 0008,1072 OperatorIDSequence no + 0008,1080 AdmittingDiagnosesDescription no + 0008,1084 AdmittingDiagnosesCodeSequence no + 0008,1090 ManufacturersModelName no + 0008,1100 ReferencedResultsSequence no + 0008,1110 ReferencedStudySequence no + 0008,1111 ReferencedProcedureStepSequence no + 0008,1115 ReferencedSeriesSequence no + 0008,1120 ReferencedPatientSequence no + 0008,1125 ReferencedVisitSequence no + 0008,1130 ReferencedOverlaySequence no + 0008,113A ReferencedWaveformSequence no + 0008,1140 ReferencedImageSequence no + 0008,1145 ReferencedCurveSequence no + 0008,114A ReferencedInstanceSequence no + 0008,1150 ReferencedSOPClassUID no + 0008,1155 ReferencedSOPInstanceUID no + 0008,115A SOPClassesSupported no + 0008,1160 ReferencedFrameNumber no + 0008,1161 SimpleFrameList no + 0008,1162 CalculatedFrameList no + 0008,1163 TimeRange no + 0008,1164 FrameExtractionSequence no + 0008,1195 TransactionUID no + 0008,1197 FailureReason no + 0008,1198 FailedSOPSequence no + 0008,1199 ReferencedSOPSequence no + 0008,1200 OtherReferencedStudiesSequence no + 0008,1250 RelatedSeriesSequence no + 0008,2110 LossyImageCompression no + 0008,2111 DerivationDescription no + 0008,2112 SourceImageSequence no + 0008,2120 StageName no + 0008,2122 StageNumber no + 0008,2124 NumberOfStages no + 0008,2127 ViewName no + 0008,2128 ViewNumber no + 0008,2129 NumberOfEventTimers no + 0008,212A NumberOfViewsInStage no + 0008,2130 EventElapsedTimes no + 0008,2132 EventTimerNames no + 0008,2133 EventTimerSequence no + 0008,2134 EventTimeOffset no + 0008,2135 EventCodeSequence no + 0008,2142 StartTrim no + 0008,2143 StopTrim no + 0008,2144 RecommendedDisplayFrameRate no + 0008,2200 TransducerPosition no + 0008,2204 TransducerOrientation no + 0008,2208 AnatomicStructure no + 0008,2218 AnatomicRegionSequence no + 0008,2220 AnatomicRegionModifierSequence no + 0008,2228 PrimaryAnatomicStructureSequence no + 0008,2229 AnatomicStructureOrRegionSequence no + 0008,2230 AnatomicStructureModifierSequence no + 0008,2240 TransducerPositionSequence no + 0008,2242 TransducerPositionModifierSequence no + 0008,2244 TransducerOrientationSequence no + 0008,2246 TransducerOrientationModifierSeq no + 0008,2253 AnatomicEntrancePortalCodeSeqTrial no + 0008,2255 AnatomicApproachDirCodeSeqTrial no + 0008,2256 AnatomicPerspectiveDescrTrial no + 0008,2257 AnatomicPerspectiveCodeSeqTrial no + 0008,3001 AlternateRepresentationSequence no + 0008,3010 IrradiationEventUID no + 0008,4000 IdentifyingComments no + 0008,9007 FrameType no + 0008,9092 ReferencedImageEvidenceSequence no + 0008,9121 ReferencedRawDataSequence no + 0008,9123 CreatorVersionUID no + 0008,9124 DerivationImageSequence no + 0008,9154 SourceImageEvidenceSequence no + 0008,9205 PixelPresentation no + 0008,9206 VolumetricProperties no + 0008,9207 VolumeBasedCalculationTechnique no + 0008,9208 ComplexImageComponent no + 0008,9209 AcquisitionContrast no + 0008,9215 DerivationCodeSequence no + 0008,9237 GrayscalePresentationStateSequence no + 0008,9410 ReferencedOtherPlaneSequence no + 0008,9458 FrameDisplaySequence no + 0008,9459 RecommendedDisplayFrameRateInFloat no + 0008,9460 SkipFrameRangeFlag no + 0009,1001 FullFidelity no + 0009,1002 SuiteID no + 0009,1004 ProductID no + 0009,1027 ImageActualDate no + 0009,1030 ServiceID no + 0009,1031 MobileLocationNumber no + 0009,10E3 EquipmentUID no + 0009,10E6 GenesisVersionNow no + 0009,10E7 ExamRecordChecksum no + 0009,10E9 ActualSeriesDataTimeStamp no + 0010,0000 PatientGroupLength no + 0010,0010 PatientName no + 0010,0020 PatientID no + 0010,0021 IssuerOfPatientID no + 0010,0022 TypeOfPatientID no + 0010,0030 PatientBirthDate no + 0010,0032 PatientBirthTime no + 0010,0040 PatientSex no + 0010,0050 PatientInsurancePlanCodeSequence no + 0010,0101 PatientPrimaryLanguageCodeSeq no + 0010,0102 PatientPrimaryLanguageCodeModSeq no + 0010,1000 OtherPatientIDs no + 0010,1001 OtherPatientNames no + 0010,1002 OtherPatientIDsSequence no + 0010,1005 PatientBirthName no + 0010,1010 PatientAge no + 0010,1020 PatientSize no + 0010,1030 PatientWeight no + 0010,1040 PatientAddress no + 0010,1050 InsurancePlanIdentification no + 0010,1060 PatientMotherBirthName no + 0010,1080 MilitaryRank no + 0010,1081 BranchOfService no + 0010,1090 MedicalRecordLocator no + 0010,2000 MedicalAlerts no + 0010,2110 Allergies no + 0010,2150 CountryOfResidence no + 0010,2152 RegionOfResidence no + 0010,2154 PatientTelephoneNumbers no + 0010,2160 EthnicGroup no + 0010,2180 Occupation no + 0010,21A0 SmokingStatus no + 0010,21B0 AdditionalPatientHistory no + 0010,21C0 PregnancyStatus no + 0010,21D0 LastMenstrualDate no + 0010,21F0 PatientReligiousPreference no + 0010,2201 PatientSpeciesDescription no + 0010,2202 PatientSpeciesCodeSequence no + 0010,2203 PatientSexNeutered no + 0010,2210 AnatomicalOrientationType no + 0010,2292 PatientBreedDescription no + 0010,2293 PatientBreedCodeSequence no + 0010,2294 BreedRegistrationSequence no + 0010,2295 BreedRegistrationNumber no + 0010,2296 BreedRegistryCodeSequence no + 0010,2297 ResponsiblePerson no + 0010,2298 ResponsiblePersonRole no + 0010,2299 ResponsibleOrganization no + 0010,4000 PatientComments no + 0010,9431 ExaminedBodyThickness no + 0011,1010 PatientStatus no + 0012,0010 ClinicalTrialSponsorName no + 0012,0020 ClinicalTrialProtocolID no + 0012,0021 ClinicalTrialProtocolName no + 0012,0030 ClinicalTrialSiteID no + 0012,0031 ClinicalTrialSiteName no + 0012,0040 ClinicalTrialSubjectID no + 0012,0042 ClinicalTrialSubjectReadingID no + 0012,0050 ClinicalTrialTimePointID no + 0012,0051 ClinicalTrialTimePointDescription no + 0012,0060 ClinicalTrialCoordinatingCenter no + 0012,0062 PatientIdentityRemoved no + 0012,0063 DeidentificationMethod no + 0012,0064 DeidentificationMethodCodeSequence no + 0012,0071 ClinicalTrialSeriesID no + 0012,0072 ClinicalTrialSeriesDescription no + 0012,0084 DistributionType no + 0012,0085 ConsentForDistributionFlag no + 0018,0000 AcquisitionGroupLength no + 0018,0010 ContrastBolusAgent no + 0018,0012 ContrastBolusAgentSequence no + 0018,0014 ContrastBolusAdministrationRoute no + 0018,0015 BodyPartExamined no + 0018,0020 ScanningSequence no + 0018,0021 SequenceVariant no + 0018,0022 ScanOptions no + 0018,0023 MRAcquisitionType no + 0018,0024 SequenceName no + 0018,0025 AngioFlag no + 0018,0026 InterventionDrugInformationSeq no + 0018,0027 InterventionDrugStopTime no + 0018,0028 InterventionDrugDose no + 0018,0029 InterventionDrugSequence no + 0018,002A AdditionalDrugSequence no + 0018,0030 Radionuclide no + 0018,0031 Radiopharmaceutical no + 0018,0032 EnergyWindowCenterline no + 0018,0033 EnergyWindowTotalWidth no + 0018,0034 InterventionDrugName no + 0018,0035 InterventionDrugStartTime no + 0018,0036 InterventionSequence no + 0018,0037 TherapyType no + 0018,0038 InterventionStatus no + 0018,0039 TherapyDescription no + 0018,003A InterventionDescription no + 0018,0040 CineRate no + 0018,0042 InitialCineRunState no + 0018,0050 SliceThickness no + 0018,0060 KVP no + 0018,0070 CountsAccumulated no + 0018,0071 AcquisitionTerminationCondition no + 0018,0072 EffectiveDuration no + 0018,0073 AcquisitionStartCondition no + 0018,0074 AcquisitionStartConditionData no + 0018,0075 AcquisitionEndConditionData no + 0018,0080 RepetitionTime no + 0018,0081 EchoTime no + 0018,0082 InversionTime no + 0018,0083 NumberOfAverages no + 0018,0084 ImagingFrequency no + 0018,0085 ImagedNucleus no + 0018,0086 EchoNumber no + 0018,0087 MagneticFieldStrength no + 0018,0088 SpacingBetweenSlices no + 0018,0089 NumberOfPhaseEncodingSteps no + 0018,0090 DataCollectionDiameter no + 0018,0091 EchoTrainLength no + 0018,0093 PercentSampling no + 0018,0094 PercentPhaseFieldOfView no + 0018,0095 PixelBandwidth no + 0018,1000 DeviceSerialNumber no + 0018,1002 DeviceUID no + 0018,1003 DeviceID no + 0018,1004 PlateID no + 0018,1005 GeneratorID no + 0018,1006 GridID no + 0018,1007 CassetteID no + 0018,1008 GantryID no + 0018,1010 SecondaryCaptureDeviceID no + 0018,1011 HardcopyCreationDeviceID no + 0018,1012 DateOfSecondaryCapture no + 0018,1014 TimeOfSecondaryCapture no + 0018,1016 SecondaryCaptureDeviceManufacturer no + 0018,1017 HardcopyDeviceManufacturer no + 0018,1018 SecondaryCaptureDeviceModelName no + 0018,1019 SecondaryCaptureDeviceSoftwareVers no + 0018,101A HardcopyDeviceSoftwareVersion no + 0018,101B HardcopyDeviceModelName no + 0018,1020 SoftwareVersion no + 0018,1022 VideoImageFormatAcquired no + 0018,1023 DigitalImageFormatAcquired no + 0018,1030 ProtocolName no + 0018,1040 ContrastBolusRoute no + 0018,1041 ContrastBolusVolume no + 0018,1042 ContrastBolusStartTime no + 0018,1043 ContrastBolusStopTime no + 0018,1044 ContrastBolusTotalDose no + 0018,1045 SyringeCounts no + 0018,1046 ContrastFlowRate no + 0018,1047 ContrastFlowDuration no + 0018,1048 ContrastBolusIngredient no + 0018,1049 ContrastBolusConcentration no + 0018,1050 SpatialResolution no + 0018,1060 TriggerTime no + 0018,1061 TriggerSourceOrType no + 0018,1062 NominalInterval no + 0018,1063 FrameTime no + 0018,1064 CardiacFramingType no + 0018,1065 FrameTimeVector no + 0018,1066 FrameDelay no + 0018,1067 ImageTriggerDelay no + 0018,1068 MultiplexGroupTimeOffset no + 0018,1069 TriggerTimeOffset no + 0018,106A SynchronizationTrigger no + 0018,106C SynchronizationChannel no + 0018,106E TriggerSamplePosition no + 0018,1070 RadiopharmaceuticalRoute no + 0018,1071 RadiopharmaceuticalVolume no + 0018,1072 RadiopharmaceuticalStartTime no + 0018,1073 RadiopharmaceuticalStopTime no + 0018,1074 RadionuclideTotalDose no + 0018,1075 RadionuclideHalfLife no + 0018,1076 RadionuclidePositronFraction no + 0018,1077 RadiopharmaceuticalSpecActivity no + 0018,1078 RadiopharmaceuticalStartDateTime no + 0018,1079 RadiopharmaceuticalStopDateTime no + 0018,1080 BeatRejectionFlag no + 0018,1081 LowRRValue no + 0018,1082 HighRRValue no + 0018,1083 IntervalsAcquired no + 0018,1084 IntervalsRejected no + 0018,1085 PVCRejection no + 0018,1086 SkipBeats no + 0018,1088 HeartRate no + 0018,1090 CardiacNumberOfImages no + 0018,1094 TriggerWindow no + 0018,1100 ReconstructionDiameter no + 0018,1110 DistanceSourceToDetector no + 0018,1111 DistanceSourceToPatient no + 0018,1114 EstimatedRadiographicMagnification no + 0018,1120 GantryDetectorTilt no + 0018,1121 GantryDetectorSlew no + 0018,1130 TableHeight no + 0018,1131 TableTraverse no + 0018,1134 TableMotion no + 0018,1135 TableVerticalIncrement no + 0018,1136 TableLateralIncrement no + 0018,1137 TableLongitudinalIncrement no + 0018,1138 TableAngle no + 0018,113A TableType no + 0018,1140 RotationDirection no + 0018,1141 AngularPosition no + 0018,1142 RadialPosition no + 0018,1143 ScanArc no + 0018,1144 AngularStep no + 0018,1145 CenterOfRotationOffset no + 0018,1146 RotationOffset no + 0018,1147 FieldOfViewShape no + 0018,1149 FieldOfViewDimensions no + 0018,1150 ExposureTime no + 0018,1151 XRayTubeCurrent no + 0018,1152 Exposure no + 0018,1153 ExposureInMicroAmpSec no + 0018,1154 AveragePulseWidth no + 0018,1155 RadiationSetting no + 0018,1156 RectificationType no + 0018,115A RadiationMode no + 0018,115E ImageAreaDoseProduct no + 0018,1160 FilterType no + 0018,1161 TypeOfFilters no + 0018,1162 IntensifierSize no + 0018,1164 ImagerPixelSpacing no + 0018,1166 Grid no + 0018,1170 GeneratorPower no + 0018,1180 CollimatorGridName no + 0018,1181 CollimatorType no + 0018,1182 FocalDistance no + 0018,1183 XFocusCenter no + 0018,1184 YFocusCenter no + 0018,1190 FocalSpots no + 0018,1191 AnodeTargetMaterial no + 0018,11A0 BodyPartThickness no + 0018,11A2 CompressionForce no + 0018,1200 DateOfLastCalibration no + 0018,1201 TimeOfLastCalibration no + 0018,1210 ConvolutionKernel no + 0018,1240 UpperLowerPixelValues no + 0018,1242 ActualFrameDuration no + 0018,1243 CountRate no + 0018,1244 PreferredPlaybackSequencing no + 0018,1250 ReceiveCoilName no + 0018,1251 TransmitCoilName no + 0018,1260 PlateType no + 0018,1261 PhosphorType no + 0018,1300 ScanVelocity no + 0018,1301 WholeBodyTechnique no + 0018,1302 ScanLength no + 0018,1310 AcquisitionMatrix no + 0018,1312 InPlanePhaseEncodingDirection no + 0018,1314 FlipAngle no + 0018,1315 VariableFlipAngleFlag no + 0018,1316 SAR no + 0018,1318 DB-Dt no + 0018,1400 AcquisitionDeviceProcessingDescr no + 0018,1401 AcquisitionDeviceProcessingCode no + 0018,1402 CassetteOrientation no + 0018,1403 CassetteSize no + 0018,1404 ExposuresOnPlate no + 0018,1405 RelativeXRayExposure no + 0018,1450 ColumnAngulation no + 0018,1460 TomoLayerHeight no + 0018,1470 TomoAngle no + 0018,1480 TomoTime no + 0018,1490 TomoType no + 0018,1491 TomoClass no + 0018,1495 NumberOfTomosynthesisSourceImages no + 0018,1500 PositionerMotion no + 0018,1508 PositionerType no + 0018,1510 PositionerPrimaryAngle no + 0018,1511 PositionerSecondaryAngle no + 0018,1520 PositionerPrimaryAngleIncrement no + 0018,1521 PositionerSecondaryAngleIncrement no + 0018,1530 DetectorPrimaryAngle no + 0018,1531 DetectorSecondaryAngle no + 0018,1600 ShutterShape no + 0018,1602 ShutterLeftVerticalEdge no + 0018,1604 ShutterRightVerticalEdge no + 0018,1606 ShutterUpperHorizontalEdge no + 0018,1608 ShutterLowerHorizontalEdge no + 0018,1610 CenterOfCircularShutter no + 0018,1612 RadiusOfCircularShutter no + 0018,1620 VerticesOfPolygonalShutter no + 0018,1622 ShutterPresentationValue no + 0018,1623 ShutterOverlayGroup no + 0018,1624 ShutterPresentationColorCIELabVal no + 0018,1700 CollimatorShape no + 0018,1702 CollimatorLeftVerticalEdge no + 0018,1704 CollimatorRightVerticalEdge no + 0018,1706 CollimatorUpperHorizontalEdge no + 0018,1708 CollimatorLowerHorizontalEdge no + 0018,1710 CenterOfCircularCollimator no + 0018,1712 RadiusOfCircularCollimator no + 0018,1720 VerticesOfPolygonalCollimator no + 0018,1800 AcquisitionTimeSynchronized no + 0018,1801 TimeSource no + 0018,1802 TimeDistributionProtocol no + 0018,1803 NTPSourceAddress no + 0018,2001 PageNumberVector no + 0018,2002 FrameLabelVector no + 0018,2003 FramePrimaryAngleVector no + 0018,2004 FrameSecondaryAngleVector no + 0018,2005 SliceLocationVector no + 0018,2006 DisplayWindowLabelVector no + 0018,2010 NominalScannedPixelSpacing no + 0018,2020 DigitizingDeviceTransportDirection no + 0018,2030 RotationOfScannedFilm no + 0018,3100 IVUSAcquisition no + 0018,3101 IVUSPullbackRate no + 0018,3102 IVUSGatedRate no + 0018,3103 IVUSPullbackStartFrameNumber no + 0018,3104 IVUSPullbackStopFrameNumber no + 0018,3105 LesionNumber no + 0018,4000 AcquisitionComments no + 0018,5000 OutputPower no + 0018,5010 TransducerData no + 0018,5012 FocusDepth no + 0018,5020 ProcessingFunction no + 0018,5021 PostprocessingFunction no + 0018,5022 MechanicalIndex no + 0018,5024 BoneThermalIndex no + 0018,5026 CranialThermalIndex no + 0018,5027 SoftTissueThermalIndex no + 0018,5028 SoftTissueFocusThermalIndex no + 0018,5029 SoftTissueSurfaceThermalIndex no + 0018,5030 DynamicRange no + 0018,5040 TotalGain no + 0018,5050 DepthOfScanField no + 0018,5100 PatientPosition no + 0018,5101 ViewPosition no + 0018,5104 ProjectionEponymousNameCodeSeq no + 0018,5210 ImageTransformationMatrix no + 0018,5212 ImageTranslationVector no + 0018,6000 Sensitivity no + 0018,6011 SequenceOfUltrasoundRegions no + 0018,6012 RegionSpatialFormat no + 0018,6014 RegionDataType no + 0018,6016 RegionFlags no + 0018,6018 RegionLocationMinX0 no + 0018,601A RegionLocationMinY0 no + 0018,601C RegionLocationMaxX1 no + 0018,601E RegionLocationMaxY1 no + 0018,6020 ReferencePixelX0 no + 0018,6022 ReferencePixelY0 no + 0018,6024 PhysicalUnitsXDirection no + 0018,6026 PhysicalUnitsYDirection no + 0018,6028 ReferencePixelPhysicalValueX no + 0018,602A ReferencePixelPhysicalValueY no + 0018,602C PhysicalDeltaX no + 0018,602E PhysicalDeltaY no + 0018,6030 TransducerFrequency no + 0018,6031 TransducerType no + 0018,6032 PulseRepetitionFrequency no + 0018,6034 DopplerCorrectionAngle no + 0018,6036 SteeringAngle no + 0018,6038 DopplerSampleVolumeXPosRetired no + 0018,6039 DopplerSampleVolumeXPosition no + 0018,603A DopplerSampleVolumeYPosRetired no + 0018,603B DopplerSampleVolumeYPosition no + 0018,603C TMLinePositionX0Retired no + 0018,603D TMLinePositionX0 no + 0018,603E TMLinePositionY0Retired no + 0018,603F TMLinePositionY0 no + 0018,6040 TMLinePositionX1Retired no + 0018,6041 TMLinePositionX1 no + 0018,6042 TMLinePositionY1Retired no + 0018,6043 TMLinePositionY1 no + 0018,6044 PixelComponentOrganization no + 0018,6046 PixelComponentMask no + 0018,6048 PixelComponentRangeStart no + 0018,604A PixelComponentRangeStop no + 0018,604C PixelComponentPhysicalUnits no + 0018,604E PixelComponentDataType no + 0018,6050 NumberOfTableBreakPoints no + 0018,6052 TableOfXBreakPoints no + 0018,6054 TableOfYBreakPoints no + 0018,6056 NumberOfTableEntries no + 0018,6058 TableOfPixelValues no + 0018,605A TableOfParameterValues no + 0018,6060 RWaveTimeVector no + 0018,7000 DetectorConditionsNominalFlag no + 0018,7001 DetectorTemperature no + 0018,7004 DetectorType no + 0018,7005 DetectorConfiguration no + 0018,7006 DetectorDescription no + 0018,7008 DetectorMode no + 0018,700A DetectorID no + 0018,700C DateOfLastDetectorCalibration no + 0018,700E TimeOfLastDetectorCalibration no + 0018,7010 DetectorExposuresSinceCalibration no + 0018,7011 DetectorExposuresSinceManufactured no + 0018,7012 DetectorTimeSinceLastExposure no + 0018,7014 DetectorActiveTime no + 0018,7016 DetectorActiveOffsetFromExposure no + 0018,701A DetectorBinning no + 0018,7020 DetectorElementPhysicalSize no + 0018,7022 DetectorElementSpacing no + 0018,7024 DetectorActiveShape no + 0018,7026 DetectorActiveDimensions no + 0018,7028 DetectorActiveOrigin no + 0018,702A DetectorManufacturerName no + 0018,702B DetectorManufacturersModelName no + 0018,7030 FieldOfViewOrigin no + 0018,7032 FieldOfViewRotation no + 0018,7034 FieldOfViewHorizontalFlip no + 0018,7040 GridAbsorbingMaterial no + 0018,7041 GridSpacingMaterial no + 0018,7042 GridThickness no + 0018,7044 GridPitch no + 0018,7046 GridAspectRatio no + 0018,7048 GridPeriod no + 0018,704C GridFocalDistance no + 0018,7050 FilterMaterial no + 0018,7052 FilterThicknessMinimum no + 0018,7054 FilterThicknessMaximum no + 0018,7060 ExposureControlMode no + 0018,7062 ExposureControlModeDescription no + 0018,7064 ExposureStatus no + 0018,7065 PhototimerSetting no + 0018,8150 ExposureTimeInMicroSec no + 0018,8151 XRayTubeCurrentInMicroAmps no + 0018,9004 ContentQualification no + 0018,9005 PulseSequenceName no + 0018,9006 MRImagingModifierSequence no + 0018,9008 EchoPulseSequence no + 0018,9009 InversionRecovery no + 0018,9010 FlowCompensation no + 0018,9011 MultipleSpinEcho no + 0018,9012 MultiPlanarExcitation no + 0018,9014 PhaseContrast no + 0018,9015 TimeOfFlightContrast no + 0018,9016 Spoiling no + 0018,9017 SteadyStatePulseSequence no + 0018,9018 EchoPlanarPulseSequence no + 0018,9019 TagAngleFirstAxis no + 0018,9020 MagnetizationTransfer no + 0018,9021 T2Preparation no + 0018,9022 BloodSignalNulling no + 0018,9024 SaturationRecovery no + 0018,9025 SpectrallySelectedSuppression no + 0018,9026 SpectrallySelectedExcitation no + 0018,9027 SpatialPresaturation no + 0018,9028 Tagging no + 0018,9029 OversamplingPhase no + 0018,9030 TagSpacingFirstDimension no + 0018,9032 GeometryOfKSpaceTraversal no + 0018,9033 SegmentedKSpaceTraversal no + 0018,9034 RectilinearPhaseEncodeReordering no + 0018,9035 TagThickness no + 0018,9036 PartialFourierDirection no + 0018,9037 CardiacSynchronizationTechnique no + 0018,9041 ReceiveCoilManufacturerName no + 0018,9042 MRReceiveCoilSequence no + 0018,9043 ReceiveCoilType no + 0018,9044 QuadratureReceiveCoil no + 0018,9045 MultiCoilDefinitionSequence no + 0018,9046 MultiCoilConfiguration no + 0018,9047 MultiCoilElementName no + 0018,9048 MultiCoilElementUsed no + 0018,9049 MRTransmitCoilSequence no + 0018,9050 TransmitCoilManufacturerName no + 0018,9051 TransmitCoilType no + 0018,9052 SpectralWidth no + 0018,9053 ChemicalShiftReference no + 0018,9054 VolumeLocalizationTechnique no + 0018,9058 MRAcquisitionFrequencyEncodeSteps no + 0018,9059 Decoupling no + 0018,9060 DecoupledNucleus no + 0018,9061 DecouplingFrequency no + 0018,9062 DecouplingMethod no + 0018,9063 DecouplingChemicalShiftReference no + 0018,9064 KSpaceFiltering no + 0018,9065 TimeDomainFiltering no + 0018,9066 NumberOfZeroFills no + 0018,9067 BaselineCorrection no + 0018,9069 ParallelReductionFactorInPlane no + 0018,9070 CardiacRRIntervalSpecified no + 0018,9073 AcquisitionDuration no + 0018,9074 FrameAcquisitionDateTime no + 0018,9075 DiffusionDirectionality no + 0018,9076 DiffusionGradientDirectionSequence no + 0018,9077 ParallelAcquisition no + 0018,9078 ParallelAcquisitionTechnique no + 0018,9079 InversionTimes no + 0018,9080 MetaboliteMapDescription no + 0018,9081 PartialFourier no + 0018,9082 EffectiveEchoTime no + 0018,9083 MetaboliteMapCodeSequence no + 0018,9084 ChemicalShiftSequence no + 0018,9085 CardiacSignalSource no + 0018,9087 DiffusionBValue no + 0018,9089 DiffusionGradientOrientation no + 0018,9090 VelocityEncodingDirection no + 0018,9091 VelocityEncodingMinimumValue no + 0018,9093 NumberOfKSpaceTrajectories no + 0018,9094 CoverageOfKSpace no + 0018,9095 SpectroscopyAcquisitionPhaseRows no + 0018,9096 ParallelReductFactorInPlaneRetired no + 0018,9098 TransmitterFrequency no + 0018,9100 ResonantNucleus no + 0018,9101 FrequencyCorrection no + 0018,9103 MRSpectroscopyFOV-GeometrySequence no + 0018,9104 SlabThickness no + 0018,9105 SlabOrientation no + 0018,9106 MidSlabPosition no + 0018,9107 MRSpatialSaturationSequence no + 0018,9112 MRTimingAndRelatedParametersSeq no + 0018,9114 MREchoSequence no + 0018,9115 MRModifierSequence no + 0018,9117 MRDiffusionSequence no + 0018,9118 CardiacTriggerSequence no + 0018,9119 MRAveragesSequence no + 0018,9125 MRFOV-GeometrySequence no + 0018,9126 VolumeLocalizationSequence no + 0018,9127 SpectroscopyAcquisitionDataColumns no + 0018,9147 DiffusionAnisotropyType no + 0018,9151 FrameReferenceDateTime no + 0018,9152 MRMetaboliteMapSequence no + 0018,9155 ParallelReductionFactorOutOfPlane no + 0018,9159 SpectroscopyOutOfPlanePhaseSteps no + 0018,9166 BulkMotionStatus no + 0018,9168 ParallelReductionFactSecondInPlane no + 0018,9169 CardiacBeatRejectionTechnique no + 0018,9170 RespiratoryMotionCompTechnique no + 0018,9171 RespiratorySignalSource no + 0018,9172 BulkMotionCompensationTechnique no + 0018,9173 BulkMotionSignalSource no + 0018,9174 ApplicableSafetyStandardAgency no + 0018,9175 ApplicableSafetyStandardDescr no + 0018,9176 OperatingModeSequence no + 0018,9177 OperatingModeType no + 0018,9178 OperatingMode no + 0018,9179 SpecificAbsorptionRateDefinition no + 0018,9180 GradientOutputType no + 0018,9181 SpecificAbsorptionRateValue no + 0018,9182 GradientOutput no + 0018,9183 FlowCompensationDirection no + 0018,9184 TaggingDelay no + 0018,9185 RespiratoryMotionCompTechDescr no + 0018,9186 RespiratorySignalSourceID no + 0018,9195 ChemicalShiftsMinIntegrateLimitHz no + 0018,9196 ChemicalShiftsMaxIntegrateLimitHz no + 0018,9197 MRVelocityEncodingSequence no + 0018,9198 FirstOrderPhaseCorrection no + 0018,9199 WaterReferencedPhaseCorrection no + 0018,9200 MRSpectroscopyAcquisitionType no + 0018,9214 RespiratoryCyclePosition no + 0018,9217 VelocityEncodingMaximumValue no + 0018,9218 TagSpacingSecondDimension no + 0018,9219 TagAngleSecondAxis no + 0018,9220 FrameAcquisitionDuration no + 0018,9226 MRImageFrameTypeSequence no + 0018,9227 MRSpectroscopyFrameTypeSequence no + 0018,9231 MRAcqPhaseEncodingStepsInPlane no + 0018,9232 MRAcqPhaseEncodingStepsOutOfPlane no + 0018,9234 SpectroscopyAcqPhaseColumns no + 0018,9236 CardiacCyclePosition no + 0018,9239 SpecificAbsorptionRateSequence no + 0018,9240 RFEchoTrainLength no + 0018,9241 GradientEchoTrainLength no + 0018,9295 ChemicalShiftsMinIntegrateLimitPPM no + 0018,9296 ChemicalShiftsMaxIntegrateLimitPPM no + 0018,9301 CTAcquisitionTypeSequence no + 0018,9302 AcquisitionType no + 0018,9303 TubeAngle no + 0018,9304 CTAcquisitionDetailsSequence no + 0018,9305 RevolutionTime no + 0018,9306 SingleCollimationWidth no + 0018,9307 TotalCollimationWidth no + 0018,9308 CTTableDynamicsSequence no + 0018,9309 TableSpeed no + 0018,9310 TableFeedPerRotation no + 0018,9311 SpiralPitchFactor no + 0018,9312 CTGeometrySequence no + 0018,9313 DataCollectionCenterPatient no + 0018,9314 CTReconstructionSequence no + 0018,9315 ReconstructionAlgorithm no + 0018,9316 ConvolutionKernelGroup no + 0018,9317 ReconstructionFieldOfView no + 0018,9318 ReconstructionTargetCenterPatient no + 0018,9319 ReconstructionAngle no + 0018,9320 ImageFilter no + 0018,9321 CTExposureSequence no + 0018,9322 ReconstructionPixelSpacing no + 0018,9323 ExposureModulationType no + 0018,9324 EstimatedDoseSaving no + 0018,9325 CTXRayDetailsSequence no + 0018,9326 CTPositionSequence no + 0018,9327 TablePosition no + 0018,9328 ExposureTimeInMilliSec no + 0018,9329 CTImageFrameTypeSequence no + 0018,9330 XRayTubeCurrentInMilliAmps no + 0018,9332 ExposureInMilliAmpSec no + 0018,9333 ConstantVolumeFlag no + 0018,9334 FluoroscopyFlag no + 0018,9335 SourceToDataCollectionCenterDist no + 0018,9337 ContrastBolusAgentNumber no + 0018,9338 ContrastBolusIngredientCodeSeq no + 0018,9340 ContrastAdministrationProfileSeq no + 0018,9341 ContrastBolusUsageSequence no + 0018,9342 ContrastBolusAgentAdministered no + 0018,9343 ContrastBolusAgentDetected no + 0018,9344 ContrastBolusAgentPhase no + 0018,9345 CTDIvol no + 0018,9346 CTDIPhantomTypeCodeSequence no + 0018,9351 CalciumScoringMassFactorPatient no + 0018,9352 CalciumScoringMassFactorDevice no + 0018,9353 EnergyWeightingFactor no + 0018,9360 CTAdditionalXRaySourceSequence no + 0018,9401 ProjectionPixelCalibrationSequence no + 0018,9402 DistanceSourceToIsocenter no + 0018,9403 DistanceObjectToTableTop no + 0018,9404 ObjectPixelSpacingInCenterOfBeam no + 0018,9405 PositionerPositionSequence no + 0018,9406 TablePositionSequence no + 0018,9407 CollimatorShapeSequence no + 0018,9412 XA-XRFFrameCharacteristicsSequence no + 0018,9417 FrameAcquisitionSequence no + 0018,9420 XRayReceptorType no + 0018,9423 AcquisitionProtocolName no + 0018,9424 AcquisitionProtocolDescription no + 0018,9425 ContrastBolusIngredientOpaque no + 0018,9426 DistanceReceptorPlaneToDetHousing no + 0018,9427 IntensifierActiveShape no + 0018,9428 IntensifierActiveDimensions no + 0018,9429 PhysicalDetectorSize no + 0018,9430 PositionOfIsocenterProjection no + 0018,9432 FieldOfViewSequence no + 0018,9433 FieldOfViewDescription no + 0018,9434 ExposureControlSensingRegionsSeq no + 0018,9435 ExposureControlSensingRegionShape no + 0018,9436 ExposureControlSensRegionLeftEdge no + 0018,9437 ExposureControlSensRegionRightEdge no + 0018,9440 CenterOfCircExposControlSensRegion no + 0018,9441 RadiusOfCircExposControlSensRegion no + 0018,9447 ColumnAngulationPatient no + 0018,9449 BeamAngle no + 0018,9451 FrameDetectorParametersSequence no + 0018,9452 CalculatedAnatomyThickness no + 0018,9455 CalibrationSequence no + 0018,9456 ObjectThicknessSequence no + 0018,9457 PlaneIdentification no + 0018,9461 FieldOfViewDimensionsInFloat no + 0018,9462 IsocenterReferenceSystemSequence no + 0018,9463 PositionerIsocenterPrimaryAngle no + 0018,9464 PositionerIsocenterSecondaryAngle no + 0018,9465 PositionerIsocenterDetRotAngle no + 0018,9466 TableXPositionToIsocenter no + 0018,9467 TableYPositionToIsocenter no + 0018,9468 TableZPositionToIsocenter no + 0018,9469 TableHorizontalRotationAngle no + 0018,9470 TableHeadTiltAngle no + 0018,9471 TableCradleTiltAngle no + 0018,9472 FrameDisplayShutterSequence no + 0018,9473 AcquiredImageAreaDoseProduct no + 0018,9474 CArmPositionerTabletopRelationship no + 0018,9476 XRayGeometrySequence no + 0018,9477 IrradiationEventIDSequence no + 0018,9504 XRay3DFrameTypeSequence no + 0018,9506 ContributingSourcesSequence no + 0018,9507 XRay3DAcquisitionSequence no + 0018,9508 PrimaryPositionerScanArc no + 0018,9509 SecondaryPositionerScanArc no + 0018,9510 PrimaryPositionerScanStartAngle no + 0018,9511 SecondaryPositionerScanStartAngle no + 0018,9514 PrimaryPositionerIncrement no + 0018,9515 SecondaryPositionerIncrement no + 0018,9516 StartAcquisitionDateTime no + 0018,9517 EndAcquisitionDateTime no + 0018,9524 ApplicationName no + 0018,9525 ApplicationVersion no + 0018,9526 ApplicationManufacturer no + 0018,9527 AlgorithmType no + 0018,9528 AlgorithmDescription no + 0018,9530 XRay3DReconstructionSequence no + 0018,9531 ReconstructionDescription no + 0018,9538 PerProjectionAcquisitionSequence no + 0018,9601 DiffusionBMatrixSequence no + 0018,9602 DiffusionBValueXX no + 0018,9603 DiffusionBValueXY no + 0018,9604 DiffusionBValueXZ no + 0018,9605 DiffusionBValueYY no + 0018,9606 DiffusionBValueYZ no + 0018,9607 DiffusionBValueZZ no + 0018,9701 DecayCorrectionDateTime no + 0018,9715 StartDensityThreshold no + 0018,9722 TerminationTimeThreshold no + 0018,9725 DetectorGeometry no + 0018,9727 AxialDetectorDimension no + 0018,9735 PETPositionSequence no + 0018,9739 NumberOfIterations no + 0018,9740 NumberOfSubsets no + 0018,9751 PETFrameTypeSequence no + 0018,9756 ReconstructionType no + 0018,9758 DecayCorrected no + 0018,9759 AttenuationCorrected no + 0018,9760 ScatterCorrected no + 0018,9761 DeadTimeCorrected no + 0018,9762 GantryMotionCorrected no + 0018,9763 PatientMotionCorrected no + 0018,9765 RandomsCorrected no + 0018,9767 SensitivityCalibrated no + 0018,9801 DepthsOfFocus no + 0018,9804 ExclusionStartDatetime no + 0018,9805 ExclusionDuration no + 0018,9807 ImageDataTypeSequence no + 0018,9808 DataType no + 0018,980B AliasedDataType no + 0018,A001 ContributingEquipmentSequence no + 0018,A002 ContributionDateTime no + 0018,A003 ContributionDescription no + 0019,1002 NumberOfCellsIInDetector no + 0019,1003 CellNumberAtTheta no + 0019,1004 CellSpacing no + 0019,100F HorizFrameOfRef no + 0019,1011 SeriesContrast no + 0019,1012 LastPseq no + 0019,1013 StartNumberForBaseline no + 0019,1014 EndNumberForBaseline no + 0019,1015 StartNumberForEnhancedScans no + 0019,1016 EndNumberForEnhancedScans no + 0019,1017 SeriesPlane no + 0019,1018 FirstScanRas no + 0019,1019 FirstScanLocation no + 0019,101A LastScanRas no + 0019,101B LastScanLoc no + 0019,101E DisplayFieldOfView no + 0019,1023 TableSpeed no + 0019,1024 MidScanTime no + 0019,1025 MidScanFlag no + 0019,1026 DegreesOfAzimuth no + 0019,1027 GantryPeriod no + 0019,102A XRayOnPosition no + 0019,102B XRayOffPosition no + 0019,102C NumberOfTriggers no + 0019,102E AngleOfFirstView no + 0019,102F TriggerFrequency no + 0019,1039 ScanFOVType no + 0019,1040 StatReconFlag no + 0019,1041 ComputeType no + 0019,1042 SegmentNumber no + 0019,1043 TotalSegmentsRequested no + 0019,1044 InterscanDelay no + 0019,1047 ViewCompressionFactor no + 0019,104A TotalNoOfRefChannels no + 0019,104B DataSizeForScanData no + 0019,1052 ReconPostProcflag no + 0019,1057 CTWaterNumber no + 0019,1058 CTBoneNumber no + 0019,105A AcquisitionDuration no + 0019,105E NumberOfChannels no + 0019,105F IncrementBetweenChannels no + 0019,1060 StartingView no + 0019,1061 NumberOfViews no + 0019,1062 IncrementBetweenViews no + 0019,106A DependantOnNoViewsProcessed no + 0019,106B FieldOfViewInDetectorCells no + 0019,1070 ValueOfBackProjectionButton no + 0019,1071 SetIfFatqEstimatesWereUsed no + 0019,1072 ZChanAvgOverViews no + 0019,1073 AvgOfLeftRefChansOverViews no + 0019,1074 MaxLeftChanOverViews no + 0019,1075 AvgOfRightRefChansOverViews no + 0019,1076 MaxRightChanOverViews no + 0019,107D SecondEcho no + 0019,107E NumberOfEchoes no + 0019,107F TableDelta no + 0019,1081 Contiguous no + 0019,1084 PeakSAR no + 0019,1085 MonitorSAR no + 0019,1087 CardiacRepetitionTime no + 0019,1088 ImagesPerCardiacCycle no + 0019,108A ActualReceiveGainAnalog no + 0019,108B ActualReceiveGainDigital no + 0019,108D DelayAfterTrigger no + 0019,108F Swappf no + 0019,1090 PauseInterval no + 0019,1091 PulseTime no + 0019,1092 SliceOffsetOnFreqAxis no + 0019,1093 CenterFrequency no + 0019,1094 TransmitGain no + 0019,1095 AnalogReceiverGain no + 0019,1096 DigitalReceiverGain no + 0019,1097 BitmapDefiningCVs no + 0019,1098 CenterFreqMethod no + 0019,109B PulseSeqMode no + 0019,109C PulseSeqName no + 0019,109D PulseSeqDate no + 0019,109E InternalPulseSeqName no + 0019,109F TransmittingCoil no + 0019,10A0 SurfaceCoilType no + 0019,10A1 ExtremityCoilFlag no + 0019,10A2 RawDataRunNumber no + 0019,10A3 CalibratedFieldStrength no + 0019,10A4 SATFatWaterBone no + 0019,10A5 ReceiveBandwidth no + 0019,10A7 UserData01 no + 0019,10A8 UserData02 no + 0019,10A9 UserData03 no + 0019,10AA UserData04 no + 0019,10AB UserData05 no + 0019,10AC UserData06 no + 0019,10AD UserData07 no + 0019,10AE UserData08 no + 0019,10AF UserData09 no + 0019,10B0 UserData10 no + 0019,10B1 UserData11 no + 0019,10B2 UserData12 no + 0019,10B3 UserData13 no + 0019,10B4 UserData14 no + 0019,10B5 UserData15 no + 0019,10B6 UserData16 no + 0019,10B7 UserData17 no + 0019,10B8 UserData18 no + 0019,10B9 UserData19 no + 0019,10BA UserData20 no + 0019,10BB UserData21 no + 0019,10BC UserData22 no + 0019,10BD UserData23 no + 0019,10BE ProjectionAngle no + 0019,10C0 SaturationPlanes no + 0019,10C1 SurfaceCoilIntensity no + 0019,10C2 SATLocationR no + 0019,10C3 SATLocationL no + 0019,10C4 SATLocationA no + 0019,10C5 SATLocationP no + 0019,10C6 SATLocationH no + 0019,10C7 SATLocationF no + 0019,10C8 SATThicknessR-L no + 0019,10C9 SATThicknessA-P no + 0019,10CA SATThicknessH-F no + 0019,10CB PrescribedFlowAxis no + 0019,10CC VelocityEncoding no + 0019,10CD ThicknessDisclaimer no + 0019,10CE PrescanType no + 0019,10CF PrescanStatus no + 0019,10D0 RawDataType no + 0019,10D2 ProjectionAlgorithm no + 0019,10D3 ProjectionAlgorithm no + 0019,10D5 FractionalEcho no + 0019,10D6 PrepPulse no + 0019,10D7 CardiacPhases no + 0019,10D8 VariableEchoflag no + 0019,10D9 ConcatenatedSAT no + 0019,10DA ReferenceChannelUsed no + 0019,10DB BackProjectorCoefficient no + 0019,10DC PrimarySpeedCorrectionUsed no + 0019,10DD OverrangeCorrectionUsed no + 0019,10DE DynamicZAlphaValue no + 0019,10DF UserData no + 0019,10E0 UserData no + 0019,10E2 VelocityEncodeScale no + 0019,10F2 FastPhases no + 0019,10F9 TransmissionGain no + 0020,0000 RelationshipGroupLength no + 0020,000D StudyInstanceUID no + 0020,000E SeriesInstanceUID no + 0020,0010 StudyID no + 0020,0011 SeriesNumber no + 0020,0012 AcquisitionNumber no + 0020,0013 InstanceNumber no + 0020,0014 IsotopeNumber no + 0020,0015 PhaseNumber no + 0020,0016 IntervalNumber no + 0020,0017 TimeSlotNumber no + 0020,0018 AngleNumber no + 0020,0019 ItemNumber no + 0020,0020 PatientOrientation no + 0020,0022 OverlayNumber no + 0020,0024 CurveNumber no + 0020,0026 LookupTableNumber no + 0020,0030 ImagePosition no + 0020,0032 ImagePositionPatient no + 0020,0035 ImageOrientation no + 0020,0037 ImageOrientationPatient no + 0020,0050 Location no + 0020,0052 FrameOfReferenceUID no + 0020,0060 Laterality no + 0020,0062 ImageLaterality no + 0020,0070 ImageGeometryType no + 0020,0080 MaskingImage no + 0020,0100 TemporalPositionIdentifier no + 0020,0105 NumberOfTemporalPositions no + 0020,0110 TemporalResolution no + 0020,0200 SynchronizationFrameOfReferenceUID no + 0020,1000 SeriesInStudy no + 0020,1001 AcquisitionsInSeries no + 0020,1002 ImagesInAcquisition no + 0020,1003 ImagesInSeries no + 0020,1004 AcquisitionsInStudy no + 0020,1005 ImagesInStudy no + 0020,1020 Reference no + 0020,1040 PositionReferenceIndicator no + 0020,1041 SliceLocation no + 0020,1070 OtherStudyNumbers no + 0020,1200 NumberOfPatientRelatedStudies no + 0020,1202 NumberOfPatientRelatedSeries no + 0020,1204 NumberOfPatientRelatedInstances no + 0020,1206 NumberOfStudyRelatedSeries no + 0020,1208 NumberOfStudyRelatedInstances no + 0020,1209 NumberOfSeriesRelatedInstances no + 0020,31xx SourceImageIDs no + 0020,3401 ModifyingDeviceID no + 0020,3402 ModifiedImageID no + 0020,3403 ModifiedImageDate no + 0020,3404 ModifyingDeviceManufacturer no + 0020,3405 ModifiedImageTime no + 0020,3406 ModifiedImageDescription no + 0020,4000 ImageComments no + 0020,5000 OriginalImageIdentification no + 0020,5002 OriginalImageIdentNomenclature no + 0020,9056 StackID no + 0020,9057 InStackPositionNumber no + 0020,9071 FrameAnatomySequence no + 0020,9072 FrameLaterality no + 0020,9111 FrameContentSequence no + 0020,9113 PlanePositionSequence no + 0020,9116 PlaneOrientationSequence no + 0020,9128 TemporalPositionIndex no + 0020,9153 TriggerDelayTime no + 0020,9156 FrameAcquisitionNumber no + 0020,9157 DimensionIndexValues no + 0020,9158 FrameComments no + 0020,9161 ConcatenationUID no + 0020,9162 InConcatenationNumber no + 0020,9163 InConcatenationTotalNumber no + 0020,9164 DimensionOrganizationUID no + 0020,9165 DimensionIndexPointer no + 0020,9167 FunctionalGroupPointer no + 0020,9213 DimensionIndexPrivateCreator no + 0020,9221 DimensionOrganizationSequence no + 0020,9222 DimensionIndexSequence no + 0020,9228 ConcatenationFrameOffsetNumber no + 0020,9238 FunctionalGroupPrivateCreator no + 0020,9241 NominalPercentageOfCardiacPhase no + 0020,9245 NominalPercentOfRespiratoryPhase no + 0020,9246 StartingRespiratoryAmplitude no + 0020,9247 StartingRespiratoryPhase no + 0020,9248 EndingRespiratoryAmplitude no + 0020,9249 EndingRespiratoryPhase no + 0020,9250 RespiratoryTriggerType no + 0020,9251 RRIntervalTimeNominal no + 0020,9252 ActualCardiacTriggerDelayTime no + 0020,9253 RespiratorySynchronizationSequence no + 0020,9254 RespiratoryIntervalTime no + 0020,9255 NominalRespiratoryTriggerDelayTime no + 0020,9256 RespiratoryTriggerDelayThreshold no + 0020,9257 ActualRespiratoryTriggerDelayTime no + 0020,9301 ImagePositionVolume no + 0020,9302 ImageOrientationVolume no + 0020,9308 ApexPosition no + 0020,9421 DimensionDescriptionLabel no + 0020,9450 PatientOrientationInFrameSequence no + 0020,9453 FrameLabel no + 0020,9518 AcquisitionIndex no + 0020,9529 ContributingSOPInstancesRefSeq no + 0020,9536 ReconstructionIndex no + 0021,1003 SeriesFromWhichPrescribed no + 0021,1005 GenesisVersionNow no + 0021,1007 SeriesRecordChecksum no + 0021,1018 GenesisVersionNow no + 0021,1019 AcqreconRecordChecksum no + 0021,1020 TableStartLocation no + 0021,1035 SeriesFromWhichPrescribed no + 0021,1036 ImageFromWhichPrescribed no + 0021,1037 ScreenFormat no + 0021,104A AnatomicalReferenceForScout no + 0021,104F LocationsInAcquisition no + 0021,1050 GraphicallyPrescribed no + 0021,1051 RotationFromSourceXRot no + 0021,1052 RotationFromSourceYRot no + 0021,1053 RotationFromSourceZRot no + 0021,1054 ImagePosition no + 0021,1055 ImageOrientation no + 0021,1056 IntegerSlop no + 0021,1057 IntegerSlop no + 0021,1058 IntegerSlop no + 0021,1059 IntegerSlop no + 0021,105A IntegerSlop no + 0021,105B FloatSlop no + 0021,105C FloatSlop no + 0021,105D FloatSlop no + 0021,105E FloatSlop no + 0021,105F FloatSlop no + 0021,1081 AutoWindowLevelAlpha no + 0021,1082 AutoWindowLevelBeta no + 0021,1083 AutoWindowLevelWindow no + 0021,1084 ToWindowLevelLevel no + 0021,1090 TubeFocalSpotPosition no + 0021,1091 BiopsyPosition no + 0021,1092 BiopsyTLocation no + 0021,1093 BiopsyRefLocation no + 0022,0001 LightPathFilterPassThroughWavelen no + 0022,0002 LightPathFilterPassBand no + 0022,0003 ImagePathFilterPassThroughWavelen no + 0022,0004 ImagePathFilterPassBand no + 0022,0005 PatientEyeMovementCommanded no + 0022,0006 PatientEyeMovementCommandCodeSeq no + 0022,0007 SphericalLensPower no + 0022,0008 CylinderLensPower no + 0022,0009 CylinderAxis no + 0022,000A EmmetropicMagnification no + 0022,000B IntraOcularPressure no + 0022,000C HorizontalFieldOfView no + 0022,000D PupilDilated no + 0022,000E DegreeOfDilation no + 0022,0010 StereoBaselineAngle no + 0022,0011 StereoBaselineDisplacement no + 0022,0012 StereoHorizontalPixelOffset no + 0022,0013 StereoVerticalPixelOffset no + 0022,0014 StereoRotation no + 0022,0015 AcquisitionDeviceTypeCodeSequence no + 0022,0016 IlluminationTypeCodeSequence no + 0022,0017 LightPathFilterTypeStackCodeSeq no + 0022,0018 ImagePathFilterTypeStackCodeSeq no + 0022,0019 LensesCodeSequence no + 0022,001A ChannelDescriptionCodeSequence no + 0022,001B RefractiveStateSequence no + 0022,001C MydriaticAgentCodeSequence no + 0022,001D RelativeImagePositionCodeSequence no + 0022,0020 StereoPairsSequence no + 0022,0021 LeftImageSequence no + 0022,0022 RightImageSequence no + 0022,0030 AxialLengthOfTheEye no + 0022,0031 OphthalmicFrameLocationSequence no + 0022,0032 ReferenceCoordinates no + 0022,0035 DepthSpatialResolution no + 0022,0036 MaximumDepthDistortion no + 0022,0037 AlongScanSpatialResolution no + 0022,0038 MaximumAlongScanDistortion no + 0022,0039 OphthalmicImageOrientation no + 0022,0041 DepthOfTransverseImage no + 0022,0042 MydriaticAgentConcUnitsSeq no + 0022,0048 AcrossScanSpatialResolution no + 0022,0049 MaximumAcrossScanDistortion no + 0022,004E MydriaticAgentConcentration no + 0022,0055 IlluminationWaveLength no + 0022,0056 IlluminationPower no + 0022,0057 IlluminationBandwidth no + 0022,0058 MydriaticAgentSequence no + 0023,1001 NumberOfSeriesInStudy no + 0023,1002 NumberOfUnarchivedSeries no + 0023,1010 ReferenceImageField no + 0023,1050 SummaryImage no + 0023,1070 StartTimeSecsInFirstAxial no + 0023,1074 NoofUpdatesToHeader no + 0023,107D IndicatesIfTheStudyHasCompleteInfo no + 0025,1006 LastPulseSequenceUsed no + 0025,1007 ImagesInSeries no + 0025,1010 LandmarkCounter no + 0025,1011 NumberOfAcquisitions no + 0025,1014 IndicatesNoofUpdatesToHeader no + 0025,1017 SeriesCompleteFlag no + 0025,1018 NumberOfImagesArchived no + 0025,1019 LastImageNumberUsed no + 0025,101A PrimaryReceiverSuiteAndHost no + 0027,1006 ImageArchiveFlag no + 0027,1010 ScoutType no + 0027,101C VmaMamp no + 0027,101D VmaPhase no + 0027,101E VmaMod no + 0027,101F VmaClip no + 0027,1020 SmartScanOnOffFlag no + 0027,1030 ForeignImageRevision no + 0027,1031 ImagingMode no + 0027,1032 PulseSequence no + 0027,1033 ImagingOptions no + 0027,1035 PlaneType no + 0027,1036 ObliquePlane no + 0027,1040 RASLetterOfImageLocation no + 0027,1041 ImageLocation no + 0027,1042 CenterRCoordOfPlaneImage no + 0027,1043 CenterACoordOfPlaneImage no + 0027,1044 CenterSCoordOfPlaneImage no + 0027,1045 NormalRCoord no + 0027,1046 NormalACoord no + 0027,1047 NormalSCoord no + 0027,1048 RCoordOfTopRightCorner no + 0027,1049 ACoordOfTopRightCorner no + 0027,104A SCoordOfTopRightCorner no + 0027,104B RCoordOfBottomRightCorner no + 0027,104C ACoordOfBottomRightCorner no + 0027,104D SCoordOfBottomRightCorner no + 0027,1050 TableStartLocation no + 0027,1051 TableEndLocation no + 0027,1052 RASLetterForSideOfImage no + 0027,1053 RASLetterForAnteriorPosterior no + 0027,1054 RASLetterForScoutStartLoc no + 0027,1055 RASLetterForScoutEndLoc no + 0027,1060 ImageDimensionX no + 0027,1061 ImageDimensionY no + 0027,1062 NumberOfExcitations no + 0028,0000 ImagePresentationGroupLength no + 0028,0002 SamplesPerPixel no + 0028,0003 SamplesPerPixelUsed no + 0028,0004 PhotometricInterpretation no + 0028,0005 ImageDimensions no + 0028,0006 PlanarConfiguration no + 0028,0008 NumberOfFrames no + 0028,0009 FrameIncrementPointer no + 0028,000A FrameDimensionPointer no + 0028,0010 Rows no + 0028,0011 Columns no + 0028,0012 Planes no + 0028,0014 UltrasoundColorDataPresent no + 0028,0030 PixelSpacing no + 0028,0031 ZoomFactor no + 0028,0032 ZoomCenter no + 0028,0034 PixelAspectRatio no + 0028,0040 ImageFormat no + 0028,0050 ManipulatedImage no + 0028,0051 CorrectedImage no + 0028,005F CompressionRecognitionCode no + 0028,0060 CompressionCode no + 0028,0061 CompressionOriginator no + 0028,0062 CompressionLabel no + 0028,0063 CompressionDescription no + 0028,0065 CompressionSequence no + 0028,0066 CompressionStepPointers no + 0028,0068 RepeatInterval no + 0028,0069 BitsGrouped no + 0028,0070 PerimeterTable no + 0028,0071 PerimeterValue no + 0028,0080 PredictorRows no + 0028,0081 PredictorColumns no + 0028,0082 PredictorConstants no + 0028,0090 BlockedPixels no + 0028,0091 BlockRows no + 0028,0092 BlockColumns no + 0028,0093 RowOverlap no + 0028,0094 ColumnOverlap no + 0028,0100 BitsAllocated no + 0028,0101 BitsStored no + 0028,0102 HighBit no + 0028,0103 PixelRepresentation no + 0028,0104 SmallestValidPixelValue no + 0028,0105 LargestValidPixelValue no + 0028,0106 SmallestImagePixelValue no + 0028,0107 LargestImagePixelValue no + 0028,0108 SmallestPixelValueInSeries no + 0028,0109 LargestPixelValueInSeries no + 0028,0110 SmallestImagePixelValueInPlane no + 0028,0111 LargestImagePixelValueInPlane no + 0028,0120 PixelPaddingValue no + 0028,0121 PixelPaddingRangeLimit no + 0028,0200 ImageLocation no + 0028,0300 QualityControlImage no + 0028,0301 BurnedInAnnotation no + 0028,0400 TransformLabel no + 0028,0401 TransformVersionNumber no + 0028,0402 NumberOfTransformSteps no + 0028,0403 SequenceOfCompressedData no + 0028,0404 DetailsOfCoefficients no + 0028,04x2 CoefficientCoding no + 0028,04x3 CoefficientCodingPointers no + 0028,0700 DCTLabel no + 0028,0701 DataBlockDescription no + 0028,0702 DataBlock no + 0028,0710 NormalizationFactorFormat no + 0028,0720 ZonalMapNumberFormat no + 0028,0721 ZonalMapLocation no + 0028,0722 ZonalMapFormat no + 0028,0730 AdaptiveMapFormat no + 0028,0740 CodeNumberFormat no + 0028,08x0 CodeLabel no + 0028,08x2 NumberOfTables no + 0028,08x3 CodeTableLocation no + 0028,08x4 BitsForCodeWord no + 0028,08x8 ImageDataLocation no + 0028,0A02 PixelSpacingCalibrationType no + 0028,0A04 PixelSpacingCalibrationDescription no + 0028,1040 PixelIntensityRelationship no + 0028,1041 PixelIntensityRelationshipSign no + 0028,1050 WindowCenter no + 0028,1051 WindowWidth no + 0028,1052 RescaleIntercept no + 0028,1053 RescaleSlope no + 0028,1054 RescaleType no + 0028,1055 WindowCenterAndWidthExplanation no + 0028,1056 VOI_LUTFunction no + 0028,1080 GrayScale no + 0028,1090 RecommendedViewingMode no + 0028,1100 GrayLookupTableDescriptor no + 0028,1101 RedPaletteColorTableDescriptor no + 0028,1102 GreenPaletteColorTableDescriptor no + 0028,1103 BluePaletteColorTableDescriptor no + 0028,1111 LargeRedPaletteColorTableDescr no + 0028,1112 LargeGreenPaletteColorTableDescr no + 0028,1113 LargeBluePaletteColorTableDescr no + 0028,1199 PaletteColorTableUID no + 0028,1200 GrayLookupTableData no + 0028,1201 RedPaletteColorTableData no + 0028,1202 GreenPaletteColorTableData no + 0028,1203 BluePaletteColorTableData no + 0028,1211 LargeRedPaletteColorTableData no + 0028,1212 LargeGreenPaletteColorTableData no + 0028,1213 LargeBluePaletteColorTableData no + 0028,1214 LargePaletteColorLookupTableUID no + 0028,1221 SegmentedRedColorTableData no + 0028,1222 SegmentedGreenColorTableData no + 0028,1223 SegmentedBlueColorTableData no + 0028,1300 BreastImplantPresent no + 0028,1350 PartialView no + 0028,1351 PartialViewDescription no + 0028,1352 PartialViewCodeSequence no + 0028,135A SpatialLocationsPreserved no + 0028,1402 DataPathAssignment no + 0028,1404 BlendingLUT1Sequence no + 0028,1406 BlendingWeightConstant no + 0028,1408 BlendingLookupTableData no + 0028,140C BlendingLUT2Sequence no + 0028,140E DataPathID no + 0028,140F RGBLUTTransferFunction no + 0028,1410 AlphaLUTTransferFunction no + 0028,2000 ICCProfile no + 0028,2110 LossyImageCompression no + 0028,2112 LossyImageCompressionRatio no + 0028,2114 LossyImageCompressionMethod no + 0028,3000 ModalityLUTSequence no + 0028,3002 LUTDescriptor no + 0028,3003 LUTExplanation no + 0028,3004 ModalityLUTType no + 0028,3006 LUTData no + 0028,3010 VOILUTSequence no + 0028,3110 SoftcopyVOILUTSequence no + 0028,4000 ImagePresentationComments no + 0028,5000 BiPlaneAcquisitionSequence no + 0028,6010 RepresentativeFrameNumber no + 0028,6020 FrameNumbersOfInterest no + 0028,6022 FrameOfInterestDescription no + 0028,6023 FrameOfInterestType no + 0028,6030 MaskPointers no + 0028,6040 RWavePointer no + 0028,6100 MaskSubtractionSequence no + 0028,6101 MaskOperation no + 0028,6102 ApplicableFrameRange no + 0028,6110 MaskFrameNumbers no + 0028,6112 ContrastFrameAveraging no + 0028,6114 MaskSubPixelShift no + 0028,6120 TIDOffset no + 0028,6190 MaskOperationExplanation no + 0028,7FE0 PixelDataProviderURL no + 0028,9001 DataPointRows no + 0028,9002 DataPointColumns no + 0028,9003 SignalDomainColumns no + 0028,9099 LargestMonochromePixelValue no + 0028,9108 DataRepresentation no + 0028,9110 PixelMeasuresSequence no + 0028,9132 FrameVOILUTSequence no + 0028,9145 PixelValueTransformationSequence no + 0028,9235 SignalDomainRows no + 0028,9411 DisplayFilterPercentage no + 0028,9415 FramePixelShiftSequence no + 0028,9416 SubtractionItemID no + 0028,9422 PixelIntensityRelationshipLUTSeq no + 0028,9443 FramePixelDataPropertiesSequence no + 0028,9444 GeometricalProperties no + 0028,9445 GeometricMaximumDistortion no + 0028,9446 ImageProcessingApplied no + 0028,9454 MaskSelectionMode no + 0028,9474 LUTFunction no + 0028,9478 MaskVisibilityPercentage no + 0028,9501 PixelShiftSequence no + 0028,9502 RegionPixelShiftSequence no + 0028,9503 VerticesOfTheRegion no + 0028,9506 PixelShiftFrameRange no + 0028,9507 LUTFrameRange no + 0028,9520 ImageToEquipmentMappingMatrix no + 0028,9537 EquipmentCoordinateSystemID no + 0029,1004 LowerRangeOfPixels1a no + 0029,1005 LowerRangeOfPixels1b no + 0029,1006 LowerRangeOfPixels1c no + 0029,1007 LowerRangeOfPixels1d no + 0029,1008 LowerRangeOfPixels1e no + 0029,1009 LowerRangeOfPixels1f no + 0029,100A LowerRangeOfPixels1g no + 0029,1015 LowerRangeOfPixels1h no + 0029,1016 LowerRangeOfPixels1i no + 0029,1017 LowerRangeOfPixels2 no + 0029,1018 UpperRangeOfPixels2 no + 0029,101A LenOfTotHdrInBytes no + 0029,1026 VersionOfTheHdrStruct no + 0029,1034 AdvantageCompOverflow no + 0029,1035 AdvantageCompUnderflow no + 0032,0000 StudyGroupLength no + 0032,000A StudyStatusID no + 0032,000C StudyPriorityID no + 0032,0012 StudyIDIssuer no + 0032,0032 StudyVerifiedDate no + 0032,0033 StudyVerifiedTime no + 0032,0034 StudyReadDate no + 0032,0035 StudyReadTime no + 0032,1000 ScheduledStudyStartDate no + 0032,1001 ScheduledStudyStartTime no + 0032,1010 ScheduledStudyStopDate no + 0032,1011 ScheduledStudyStopTime no + 0032,1020 ScheduledStudyLocation no + 0032,1021 ScheduledStudyLocationAETitle no + 0032,1030 ReasonForStudy no + 0032,1031 RequestingPhysicianIDSequence no + 0032,1032 RequestingPhysician no + 0032,1033 RequestingService no + 0032,1040 StudyArrivalDate no + 0032,1041 StudyArrivalTime no + 0032,1050 StudyCompletionDate no + 0032,1051 StudyCompletionTime no + 0032,1055 StudyComponentStatusID no + 0032,1060 RequestedProcedureDescription no + 0032,1064 RequestedProcedureCodeSequence no + 0032,1070 RequestedContrastAgent no + 0032,4000 StudyComments no + 0038,0004 ReferencedPatientAliasSequence no + 0038,0008 VisitStatusID no + 0038,0010 AdmissionID no + 0038,0011 IssuerOfAdmissionID no + 0038,0016 RouteOfAdmissions no + 0038,001A ScheduledAdmissionDate no + 0038,001B ScheduledAdmissionTime no + 0038,001C ScheduledDischargeDate no + 0038,001D ScheduledDischargeTime no + 0038,001E ScheduledPatientInstitResidence no + 0038,0020 AdmittingDate no + 0038,0021 AdmittingTime no + 0038,0030 DischargeDate no + 0038,0032 DischargeTime no + 0038,0040 DischargeDiagnosisDescription no + 0038,0044 DischargeDiagnosisCodeSequence no + 0038,0050 SpecialNeeds no + 0038,0060 ServiceEpisodeID no + 0038,0061 IssuerOfServiceEpisodeID no + 0038,0062 ServiceEpisodeDescription no + 0038,0100 PertinentDocumentsSequence no + 0038,0300 CurrentPatientLocation no + 0038,0400 PatientInstitutionResidence no + 0038,0500 PatientState no + 0038,0502 PatientClinicalTrialParticipSeq no + 0038,4000 VisitComments no + 003A,0004 WaveformOriginality no + 003A,0005 NumberOfWaveformChannels no + 003A,0010 NumberOfWaveformSamples no + 003A,001A SamplingFrequency no + 003A,0020 MultiplexGroupLabel no + 003A,0200 ChannelDefinitionSequence no + 003A,0202 WaveformChannelNumber no + 003A,0203 ChannelLabel no + 003A,0205 ChannelStatus no + 003A,0208 ChannelSourceSequence no + 003A,0209 ChannelSourceModifiersSequence no + 003A,020A SourceWaveformSequence no + 003A,020C ChannelDerivationDescription no + 003A,0210 ChannelSensitivity no + 003A,0211 ChannelSensitivityUnitsSequence no + 003A,0212 ChannelSensitivityCorrectionFactor no + 003A,0213 ChannelBaseline no + 003A,0214 ChannelTimeSkew no + 003A,0215 ChannelSampleSkew no + 003A,0218 ChannelOffset no + 003A,021A WaveformBitsStored no + 003A,0220 FilterLowFrequency no + 003A,0221 FilterHighFrequency no + 003A,0222 NotchFilterFrequency no + 003A,0223 NotchFilterBandwidth no + 003A,0230 WaveformDataDisplayScale no + 003A,0231 WaveformDisplayBkgCIELabValue no + 003A,0240 WaveformPresentationGroupSequence no + 003A,0241 PresentationGroupNumber no + 003A,0242 ChannelDisplaySequence no + 003A,0244 ChannelRecommendDisplayCIELabValue no + 003A,0245 ChannelPosition no + 003A,0246 DisplayShadingFlag no + 003A,0247 FractionalChannelDisplayScale no + 003A,0248 AbsoluteChannelDisplayScale no + 003A,0300 MultiplexAudioChannelsDescrCodeSeq no + 003A,0301 ChannelIdentificationCode no + 003A,0302 ChannelMode no + 0040,0001 ScheduledStationAETitle no + 0040,0002 ScheduledProcedureStepStartDate no + 0040,0003 ScheduledProcedureStepStartTime no + 0040,0004 ScheduledProcedureStepEndDate no + 0040,0005 ScheduledProcedureStepEndTime no + 0040,0006 ScheduledPerformingPhysiciansName no + 0040,0007 ScheduledProcedureStepDescription no + 0040,0008 ScheduledProtocolCodeSequence no + 0040,0009 ScheduledProcedureStepID no + 0040,000A StageCodeSequence no + 0040,000B ScheduledPerformingPhysicianIDSeq no + 0040,0010 ScheduledStationName no + 0040,0011 ScheduledProcedureStepLocation no + 0040,0012 PreMedication no + 0040,0020 ScheduledProcedureStepStatus no + 0040,0031 LocalNamespaceEntityID no + 0040,0032 UniversalEntityID no + 0040,0033 UniversalEntityIDType no + 0040,0035 IdentifierTypeCode no + 0040,0036 AssigningFacilitySequence no + 0040,0100 ScheduledProcedureStepSequence no + 0040,0220 ReferencedNonImageCompositeSOPSeq no + 0040,0241 PerformedStationAETitle no + 0040,0242 PerformedStationName no + 0040,0243 PerformedLocation no + 0040,0244 PerformedProcedureStepStartDate no + 0040,0245 PerformedProcedureStepStartTime no + 0040,0250 PerformedProcedureStepEndDate no + 0040,0251 PerformedProcedureStepEndTime no + 0040,0252 PerformedProcedureStepStatus no + 0040,0253 PerformedProcedureStepID no + 0040,0254 PerformedProcedureStepDescription no + 0040,0255 PerformedProcedureTypeDescription no + 0040,0260 PerformedProtocolCodeSequence no + 0040,0261 PerformedProtocolType no + 0040,0270 ScheduledStepAttributesSequence no + 0040,0275 RequestAttributesSequence no + 0040,0280 CommentsOnPerformedProcedureStep no + 0040,0281 ProcStepDiscontinueReasonCodeSeq no + 0040,0293 QuantitySequence no + 0040,0294 Quantity no + 0040,0295 MeasuringUnitsSequence no + 0040,0296 BillingItemSequence no + 0040,0300 TotalTimeOfFluoroscopy no + 0040,0301 TotalNumberOfExposures no + 0040,0302 EntranceDose no + 0040,0303 ExposedArea no + 0040,0306 DistanceSourceToEntrance no + 0040,0307 DistanceSourceToSupport no + 0040,030E ExposureDoseSequence no + 0040,0310 CommentsOnRadiationDose no + 0040,0312 XRayOutput no + 0040,0314 HalfValueLayer no + 0040,0316 OrganDose no + 0040,0318 OrganExposed no + 0040,0320 BillingProcedureStepSequence no + 0040,0321 FilmConsumptionSequence no + 0040,0324 BillingSuppliesAndDevicesSequence no + 0040,0330 ReferencedProcedureStepSequence no + 0040,0340 PerformedSeriesSequence no + 0040,0400 CommentsOnScheduledProcedureStep no + 0040,0440 ProtocolContextSequence no + 0040,0441 ContentItemModifierSequence no + 0040,050A SpecimenAccessionNumber no + 0040,0512 ContainerIdentifier no + 0040,051A ContainerDescription no + 0040,0550 SpecimenSequence no + 0040,0551 SpecimenIdentifier no + 0040,0552 SpecimenDescriptionSequenceTrial no + 0040,0553 SpecimenDescriptionTrial no + 0040,0554 SpecimenUID no + 0040,0555 AcquisitionContextSequence no + 0040,0556 AcquisitionContextDescription no + 0040,059A SpecimenTypeCodeSequence no + 0040,0600 SpecimenShortDescription no + 0040,06FA SlideIdentifier no + 0040,071A ImageCenterPointCoordinatesSeq no + 0040,072A XOffsetInSlideCoordinateSystem no + 0040,073A YOffsetInSlideCoordinateSystem no + 0040,074A ZOffsetInSlideCoordinateSystem no + 0040,08D8 PixelSpacingSequence no + 0040,08DA CoordinateSystemAxisCodeSequence no + 0040,08EA MeasurementUnitsCodeSequence no + 0040,09F8 VitalStainCodeSequenceTrial no + 0040,1001 RequestedProcedureID no + 0040,1002 ReasonForRequestedProcedure no + 0040,1003 RequestedProcedurePriority no + 0040,1004 PatientTransportArrangements no + 0040,1005 RequestedProcedureLocation no + 0040,1006 PlacerOrderNumber-Procedure no + 0040,1007 FillerOrderNumber-Procedure no + 0040,1008 ConfidentialityCode no + 0040,1009 ReportingPriority no + 0040,100A ReasonForRequestedProcedureCodeSeq no + 0040,1010 NamesOfIntendedRecipientsOfResults no + 0040,1011 IntendedRecipientsOfResultsIDSeq no + 0040,1101 PersonIdentificationCodeSequence no + 0040,1102 PersonAddress no + 0040,1103 PersonTelephoneNumbers no + 0040,1400 RequestedProcedureComments no + 0040,2001 ReasonForImagingServiceRequest no + 0040,2004 IssueDateOfImagingServiceRequest no + 0040,2005 IssueTimeOfImagingServiceRequest no + 0040,2006 PlacerOrderNum-ImagingServiceReq no + 0040,2007 FillerOrderNum-ImagingServiceReq no + 0040,2008 OrderEnteredBy no + 0040,2009 OrderEntererLocation no + 0040,2010 OrderCallbackPhoneNumber no + 0040,2016 PlacerOrderNum-ImagingServiceReq no + 0040,2017 FillerOrderNum-ImagingServiceReq no + 0040,2400 ImagingServiceRequestComments no + 0040,3001 ConfidentialityOnPatientDataDescr no + 0040,4001 GenPurposeScheduledProcStepStatus no + 0040,4002 GenPurposePerformedProcStepStatus no + 0040,4003 GenPurposeSchedProcStepPriority no + 0040,4004 SchedProcessingApplicationsCodeSeq no + 0040,4005 SchedProcedureStepStartDateAndTime no + 0040,4006 MultipleCopiesFlag no + 0040,4007 PerformedProcessingAppsCodeSeq no + 0040,4009 HumanPerformerCodeSequence no + 0040,4010 SchedProcStepModificationDateTime no + 0040,4011 ExpectedCompletionDateAndTime no + 0040,4015 ResultingGenPurposePerfProcStepSeq no + 0040,4016 RefGenPurposeSchedProcStepSeq no + 0040,4018 ScheduledWorkitemCodeSequence no + 0040,4019 PerformedWorkitemCodeSequence no + 0040,4020 InputAvailabilityFlag no + 0040,4021 InputInformationSequence no + 0040,4022 RelevantInformationSequence no + 0040,4023 RefGenPurSchedProcStepTransUID no + 0040,4025 ScheduledStationNameCodeSequence no + 0040,4026 ScheduledStationClassCodeSequence no + 0040,4027 SchedStationGeographicLocCodeSeq no + 0040,4028 PerformedStationNameCodeSequence no + 0040,4029 PerformedStationClassCodeSequence no + 0040,4030 PerformedStationGeogLocCodeSeq no + 0040,4031 RequestedSubsequentWorkItemCodeSeq no + 0040,4032 NonDICOMOutputCodeSequence no + 0040,4033 OutputInformationSequence no + 0040,4034 ScheduledHumanPerformersSequence no + 0040,4035 ActualHumanPerformersSequence no + 0040,4036 HumanPerformersOrganization no + 0040,4037 HumanPerformerName no + 0040,4040 RawDataHandling no + 0040,8302 EntranceDoseInMilliGy no + 0040,9094 RefImageRealWorldValueMappingSeq no + 0040,9096 RealWorldValueMappingSequence no + 0040,9098 PixelValueMappingCodeSequence no + 0040,9210 LUTLabel no + 0040,9211 RealWorldValueLastValueMapped no + 0040,9212 RealWorldValueLUTData no + 0040,9216 RealWorldValueFirstValueMapped no + 0040,9224 RealWorldValueIntercept no + 0040,9225 RealWorldValueSlope no + 0040,A010 RelationshipType no + 0040,A027 VerifyingOrganization no + 0040,A030 VerificationDateTime no + 0040,A032 ObservationDateTime no + 0040,A040 ValueType no + 0040,A043 ConceptNameCodeSequence no + 0040,A050 ContinuityOfContent no + 0040,A073 VerifyingObserverSequence no + 0040,A075 VerifyingObserverName no + 0040,A078 AuthorObserverSequence no + 0040,A07A ParticipantSequence no + 0040,A07C CustodialOrganizationSequence no + 0040,A080 ParticipationType no + 0040,A082 ParticipationDateTime no + 0040,A084 ObserverType no + 0040,A088 VerifyingObserverIdentCodeSequence no + 0040,A090 EquivalentCDADocumentSequence no + 0040,A0B0 ReferencedWaveformChannels no + 0040,A120 DateTime no + 0040,A121 Date no + 0040,A122 Time no + 0040,A123 PersonName no + 0040,A124 UID no + 0040,A130 TemporalRangeType no + 0040,A132 ReferencedSamplePositions no + 0040,A136 ReferencedFrameNumbers no + 0040,A138 ReferencedTimeOffsets no + 0040,A13A ReferencedDateTime no + 0040,A160 TextValue no + 0040,A168 ConceptCodeSequence no + 0040,A170 PurposeOfReferenceCodeSequence no + 0040,A180 AnnotationGroupNumber no + 0040,A195 ModifierCodeSequence no + 0040,A300 MeasuredValueSequence no + 0040,A301 NumericValueQualifierCodeSequence no + 0040,A30A NumericValue no + 0040,A353 AddressTrial no + 0040,A354 TelephoneNumberTrial no + 0040,A360 PredecessorDocumentsSequence no + 0040,A370 ReferencedRequestSequence no + 0040,A372 PerformedProcedureCodeSequence no + 0040,A375 CurrentRequestedProcEvidenceSeq no + 0040,A385 PertinentOtherEvidenceSequence no + 0040,A390 HL7StructuredDocumentRefSeq no + 0040,A491 CompletionFlag no + 0040,A492 CompletionFlagDescription no + 0040,A493 VerificationFlag no + 0040,A494 ArchiveRequested no + 0040,A496 PreliminaryFlag no + 0040,A504 ContentTemplateSequence no + 0040,A525 IdenticalDocumentsSequence no + 0040,A730 ContentSequence no + 0040,B020 AnnotationSequence no + 0040,DB00 TemplateIdentifier no + 0040,DB06 TemplateVersion no + 0040,DB07 TemplateLocalVersion no + 0040,DB0B TemplateExtensionFlag no + 0040,DB0C TemplateExtensionOrganizationUID no + 0040,DB0D TemplateExtensionCreatorUID no + 0040,DB73 ReferencedContentItemIdentifier no + 0040,E001 HL7InstanceIdentifier no + 0040,E004 HL7DocumentEffectiveTime no + 0040,E006 HL7DocumentTypeCodeSequence no + 0040,E010 RetrieveURI no + 0040,E011 RetrieveLocationUID no + 0042,0010 DocumentTitle no + 0042,0011 EncapsulatedDocument no + 0042,0012 MIMETypeOfEncapsulatedDocument no + 0042,0013 SourceInstanceSequence no + 0042,0014 ListOfMIMETypes no + 0043,1001 BitmapOfPrescanOptions no + 0043,1002 GradientOffsetInX no + 0043,1003 GradientOffsetInY no + 0043,1004 GradientOffsetInZ no + 0043,1005 ImgIsOriginalOrUnoriginal no + 0043,1006 NumberOfEPIShots no + 0043,1007 ViewsPerSegment no + 0043,1008 RespiratoryRateBpm no + 0043,1009 RespiratoryTriggerPoint no + 0043,100A TypeOfReceiverUsed no + 0043,100B PeakRateOfChangeOfGradientField no + 0043,100C LimitsInUnitsOfPercent no + 0043,100D PSDEstimatedLimit no + 0043,100E PSDEstimatedLimitInTeslaPerSecond no + 0043,100F Saravghead no + 0043,1010 WindowValue no + 0043,1011 TotalInputViews no + 0043,1012 X-RayChain no + 0043,1013 DeconKernelParameters no + 0043,1014 CalibrationParameters no + 0043,1015 TotalOutputViews no + 0043,1016 NumberOfOverranges no + 0043,1017 IBHImageScaleFactors no + 0043,1018 BBHCoefficients no + 0043,1019 NumberOfBBHChainsToBlend no + 0043,101A StartingChannelNumber no + 0043,101B PpscanParameters no + 0043,101C GEImageIntegrity no + 0043,101D LevelValue no + 0043,101E DeltaStartTime no + 0043,101F MaxOverrangesInAView no + 0043,1020 AvgOverrangesAllViews no + 0043,1021 CorrectedAfterGlowTerms no + 0043,1025 ReferenceChannels no + 0043,1026 NoViewsRefChansBlocked no + 0043,1027 ScanPitchRatio no + 0043,1028 UniqueImageIden no + 0043,1029 HistogramTables no + 0043,102A UserDefinedData no + 0043,102B PrivateScanOptions no + 0043,102C EffectiveEchoSpacing no + 0043,102D StringSlopField1 no + 0043,102E StringSlopField2 no + 0043,102F RawDataType no + 0043,1030 RawDataType no + 0043,1031 RACordOfTargetReconCenter no + 0043,1032 RawDataType no + 0043,1033 NegScanspacing no + 0043,1034 OffsetFrequency no + 0043,1035 UserUsageTag no + 0043,1036 UserFillMapMSW no + 0043,1037 UserFillMapLSW no + 0043,1038 User25-48 no + 0043,1039 SlopInt6-9 no + 0043,1040 TriggerOnPosition no + 0043,1041 DegreeOfRotation no + 0043,1042 DASTriggerSource no + 0043,1043 DASFpaGain no + 0043,1044 DASOutputSource no + 0043,1045 DASAdInput no + 0043,1046 DASCalMode no + 0043,1047 DASCalFrequency no + 0043,1048 DASRegXm no + 0043,1049 DASAutoZero no + 0043,104A StartingChannelOfView no + 0043,104B DASXmPattern no + 0043,104C TGGCTriggerMode no + 0043,104D StartScanToXrayOnDelay no + 0043,104E DurationOfXrayOn no + 0043,1060 SlopInt10-17 no + 0043,1061 ScannerStudyEntityUID no + 0043,1062 ScannerStudyID no + 0043,106f ScannerTableEntry no + 0044,0001 ProductPackageIdentifier no + 0044,0002 SubstanceAdministrationApproval no + 0044,0003 ApprovalStatusFurtherDescription no + 0044,0004 ApprovalStatusDateTime no + 0044,0007 ProductTypeCodeSequence no + 0044,0008 ProductName no + 0044,0009 ProductDescription no + 0044,000A ProductLotIdentifier no + 0044,000B ProductExpirationDateTime no + 0044,0010 SubstanceAdministrationDateTime no + 0044,0011 SubstanceAdministrationNotes no + 0044,0012 SubstanceAdministrationDeviceID no + 0044,0013 ProductParameterSequence no + 0044,0019 SubstanceAdminParameterSeq no + 0045,1001 NumberOfMacroRowsInDetector no + 0045,1002 MacroWidthAtISOCenter no + 0045,1003 DASType no + 0045,1004 DASGain no + 0045,1005 DASTemperature no + 0045,1006 TableDirectionInOrOut no + 0045,1007 ZSmoothingFactor no + 0045,1008 ViewWeightingMode no + 0045,1009 SigmaRowNumberWhichRowsWereUsed no + 0045,100A MinimumDasValueFoundInTheScanData no + 0045,100B MaximumOffsetShiftValueUsed no + 0045,100C NumberOfViewsShifted no + 0045,100D ZTrackingFlag no + 0045,100E MeanZError no + 0045,100F ZTrackingMaximumError no + 0045,1010 StartingViewForRow2a no + 0045,1011 NumberOfViewsInRow2a no + 0045,1012 StartingViewForRow1a no + 0045,1013 SigmaMode no + 0045,1014 NumberOfViewsInRow1a no + 0045,1015 StartingViewForRow2b no + 0045,1016 NumberOfViewsInRow2b no + 0045,1017 StartingViewForRow1b no + 0045,1018 NumberOfViewsInRow1b no + 0045,1019 AirFilterCalibrationDate no + 0045,101A AirFilterCalibrationTime no + 0045,101B PhantomCalibrationDate no + 0045,101C PhantomCalibrationTime no + 0045,101D ZSlopeCalibrationDate no + 0045,101E ZSlopeCalibrationTime no + 0045,101F CrosstalkCalibrationDate no + 0045,1020 CrosstalkCalibrationTime no + 0045,1021 IterboneOptionFlag no + 0045,1022 PeristalticFlagOption no + 0046,0012 LensDescription no + 0046,0014 RightLensSequence no + 0046,0015 LeftLensSequence no + 0046,0018 CylinderSequence no + 0046,0028 PrismSequence no + 0046,0030 HorizontalPrismPower no + 0046,0032 HorizontalPrismBase no + 0046,0034 VerticalPrismPower no + 0046,0036 VerticalPrismBase no + 0046,0038 LensSegmentType no + 0046,0040 OpticalTransmittance no + 0046,0042 ChannelWidth no + 0046,0044 PupilSize no + 0046,0046 CornealSize no + 0046,0060 DistancePupillaryDistance no + 0046,0062 NearPupillaryDistance no + 0046,0064 OtherPupillaryDistance no + 0046,0075 RadiusOfCurvature no + 0046,0076 KeratometricPower no + 0046,0077 KeratometricAxis no + 0046,0092 BackgroundColor no + 0046,0094 Optotype no + 0046,0095 OptotypePresentation no + 0046,0100 AddNearSequence no + 0046,0101 AddIntermediateSequence no + 0046,0102 AddOtherSequence no + 0046,0104 AddPower no + 0046,0106 ViewingDistance no + 0046,0125 ViewingDistanceType no + 0046,0135 VisualAcuityModifiers no + 0046,0137 DecimalVisualAcuity no + 0046,0139 OptotypeDetailedDefinition no + 0046,0146 SpherePower no + 0046,0147 CylinderPower no + 0050,0004 CalibrationImage no + 0050,0010 DeviceSequence no + 0050,0014 DeviceLength no + 0050,0015 ContainerComponentWidth no + 0050,0016 DeviceDiameter no + 0050,0017 DeviceDiameterUnits no + 0050,0018 DeviceVolume no + 0050,0019 InterMarkerDistance no + 0050,001B ContainerComponentID no + 0050,0020 DeviceDescription no + 0054,0010 EnergyWindowVector no + 0054,0011 NumberOfEnergyWindows no + 0054,0012 EnergyWindowInformationSequence no + 0054,0013 EnergyWindowRangeSequence no + 0054,0014 EnergyWindowLowerLimit no + 0054,0015 EnergyWindowUpperLimit no + 0054,0016 RadiopharmaceuticalInformationSeq no + 0054,0017 ResidualSyringeCounts no + 0054,0018 EnergyWindowName no + 0054,0020 DetectorVector no + 0054,0021 NumberOfDetectors no + 0054,0022 DetectorInformationSequence no + 0054,0030 PhaseVector no + 0054,0031 NumberOfPhases no + 0054,0032 PhaseInformationSequence no + 0054,0033 NumberOfFramesInPhase no + 0054,0036 PhaseDelay no + 0054,0038 PauseBetweenFrames no + 0054,0039 PhaseDescription no + 0054,0050 RotationVector no + 0054,0051 NumberOfRotations no + 0054,0052 RotationInformationSequence no + 0054,0053 NumberOfFramesInRotation no + 0054,0060 RRIntervalVector no + 0054,0061 NumberOfRRIntervals no + 0054,0062 GatedInformationSequence no + 0054,0063 DataInformationSequence no + 0054,0070 TimeSlotVector no + 0054,0071 NumberOfTimeSlots no + 0054,0072 TimeSlotInformationSequence no + 0054,0073 TimeSlotTime no + 0054,0080 SliceVector no + 0054,0081 NumberOfSlices no + 0054,0090 AngularViewVector no + 0054,0100 TimeSliceVector no + 0054,0101 NumberOfTimeSlices no + 0054,0200 StartAngle no + 0054,0202 TypeOfDetectorMotion no + 0054,0210 TriggerVector no + 0054,0211 NumberOfTriggersInPhase no + 0054,0220 ViewCodeSequence no + 0054,0222 ViewModifierCodeSequence no + 0054,0300 RadionuclideCodeSequence no + 0054,0302 AdministrationRouteCodeSequence no + 0054,0304 RadiopharmaceuticalCodeSequence no + 0054,0306 CalibrationDataSequence no + 0054,0308 EnergyWindowNumber no + 0054,0400 ImageID no + 0054,0410 PatientOrientationCodeSequence no + 0054,0412 PatientOrientationModifierCodeSeq no + 0054,0414 PatientGantryRelationshipCodeSeq no + 0054,0500 SliceProgressionDirection no + 0054,1000 SeriesType no + 0054,1001 Units no + 0054,1002 CountsSource no + 0054,1004 ReprojectionMethod no + 0054,1100 RandomsCorrectionMethod no + 0054,1101 AttenuationCorrectionMethod no + 0054,1102 DecayCorrection no + 0054,1103 ReconstructionMethod no + 0054,1104 DetectorLinesOfResponseUsed no + 0054,1105 ScatterCorrectionMethod no + 0054,1200 AxialAcceptance no + 0054,1201 AxialMash no + 0054,1202 TransverseMash no + 0054,1203 DetectorElementSize no + 0054,1210 CoincidenceWindowWidth no + 0054,1220 SecondaryCountsType no + 0054,1300 FrameReferenceTime no + 0054,1310 PrimaryCountsAccumulated no + 0054,1311 SecondaryCountsAccumulated no + 0054,1320 SliceSensitivityFactor no + 0054,1321 DecayFactor no + 0054,1322 DoseCalibrationFactor no + 0054,1323 ScatterFractionFactor no + 0054,1324 DeadTimeFactor no + 0054,1330 ImageIndex no + 0054,1400 CountsIncluded no + 0054,1401 DeadTimeCorrectionFlag no + 0060,3000 HistogramSequence no + 0060,3002 HistogramNumberOfBins no + 0060,3004 HistogramFirstBinValue no + 0060,3006 HistogramLastBinValue no + 0060,3008 HistogramBinWidth no + 0060,3010 HistogramExplanation no + 0060,3020 HistogramData no + 0062,0001 SegmentationType no + 0062,0002 SegmentSequence no + 0062,0003 SegmentedPropertyCategoryCodeSeq no + 0062,0004 SegmentNumber no + 0062,0005 SegmentLabel no + 0062,0006 SegmentDescription no + 0062,0008 SegmentAlgorithmType no + 0062,0009 SegmentAlgorithmName no + 0062,000A SegmentIdentificationSequence no + 0062,000B ReferencedSegmentNumber no + 0062,000C RecommendedDisplayGrayscaleValue no + 0062,000D RecommendedDisplayCIELabValue no + 0062,000E MaximumFractionalValue no + 0062,000F SegmentedPropertyTypeCodeSequence no + 0062,0010 SegmentationFractionalType no + 0064,0002 DeformableRegistrationSequence no + 0064,0003 SourceFrameOfReferenceUID no + 0064,0005 DeformableRegistrationGridSequence no + 0064,0007 GridDimensions no + 0064,0008 GridResolution no + 0064,0009 VectorGridData no + 0064,000F PreDeformationMatrixRegistSeq no + 0064,0010 PostDeformationMatrixRegistSeq no + 0066,0001 NumberOfSurfaces no + 0066,0002 SurfaceSequence no + 0066,0003 SurfaceNumber no + 0066,0004 SurfaceComments no + 0066,0009 SurfaceProcessing no + 0066,000A SurfaceProcessingRatio no + 0066,000E FiniteVolume no + 0066,0010 Manifold no + 0066,0011 SurfacePointsSequence no + 0066,0015 NumberOfSurfacePoints no + 0066,0016 PointCoordinatesData no + 0066,0017 PointPositionAccuracy no + 0066,0018 MeanPointDistance no + 0066,0019 MaximumPointDistance no + 0066,001B AxisOfRotation no + 0066,001C CenterOfRotation no + 0066,001E NumberOfVectors no + 0066,001F VectorDimensionality no + 0066,0020 VectorAccuracy no + 0066,0021 VectorCoordinateData no + 0066,0023 TrianglePointIndexList no + 0066,0024 EdgePointIndexList no + 0066,0025 VertexPointIndexList no + 0066,0026 TriangleStripSequence no + 0066,0027 TriangleFanSequence no + 0066,0028 LineSequence no + 0066,0029 PrimitivePointIndexList no + 0066,002A SurfaceCount no + 0066,002F AlgorithmFamilyCodeSequ no + 0066,0031 AlgorithmVersion no + 0066,0032 AlgorithmParameters no + 0066,0034 FacetSequence no + 0066,0036 AlgorithmName no + 0070,0001 GraphicAnnotationSequence no + 0070,0002 GraphicLayer no + 0070,0003 BoundingBoxAnnotationUnits no + 0070,0004 AnchorPointAnnotationUnits no + 0070,0005 GraphicAnnotationUnits no + 0070,0006 UnformattedTextValue no + 0070,0008 TextObjectSequence no + 0070,0009 GraphicObjectSequence no + 0070,0010 BoundingBoxTopLeftHandCorner no + 0070,0011 BoundingBoxBottomRightHandCorner no + 0070,0012 BoundingBoxTextHorizJustification no + 0070,0014 AnchorPoint no + 0070,0015 AnchorPointVisibility no + 0070,0020 GraphicDimensions no + 0070,0021 NumberOfGraphicPoints no + 0070,0022 GraphicData no + 0070,0023 GraphicType no + 0070,0024 GraphicFilled no + 0070,0040 ImageRotationRetired no + 0070,0041 ImageHorizontalFlip no + 0070,0042 ImageRotation no + 0070,0050 DisplayedAreaTopLeftTrial no + 0070,0051 DisplayedAreaBottomRightTrial no + 0070,0052 DisplayedAreaTopLeft no + 0070,0053 DisplayedAreaBottomRight no + 0070,005A DisplayedAreaSelectionSequence no + 0070,0060 GraphicLayerSequence no + 0070,0062 GraphicLayerOrder no + 0070,0066 GraphicLayerRecDisplayGraysclValue no + 0070,0067 GraphicLayerRecDisplayRGBValue no + 0070,0068 GraphicLayerDescription no + 0070,0080 ContentLabel no + 0070,0081 ContentDescription no + 0070,0082 PresentationCreationDate no + 0070,0083 PresentationCreationTime no + 0070,0084 ContentCreatorName no + 0070,0086 ContentCreatorIDCodeSequence no + 0070,0100 PresentationSizeMode no + 0070,0101 PresentationPixelSpacing no + 0070,0102 PresentationPixelAspectRatio no + 0070,0103 PresentationPixelMagRatio no + 0070,0306 ShapeType no + 0070,0308 RegistrationSequence no + 0070,0309 MatrixRegistrationSequence no + 0070,030A MatrixSequence no + 0070,030C FrameOfRefTransformationMatrixType no + 0070,030D RegistrationTypeCodeSequence no + 0070,030F FiducialDescription no + 0070,0310 FiducialIdentifier no + 0070,0311 FiducialIdentifierCodeSequence no + 0070,0312 ContourUncertaintyRadius no + 0070,0314 UsedFiducialsSequence no + 0070,0318 GraphicCoordinatesDataSequence no + 0070,031A FiducialUID no + 0070,031C FiducialSetSequence no + 0070,031E FiducialSequence no + 0070,0401 GraphicLayerRecomDisplayCIELabVal no + 0070,0402 BlendingSequence no + 0070,0403 RelativeOpacity no + 0070,0404 ReferencedSpatialRegistrationSeq no + 0070,0405 BlendingPosition no + 0072,0002 HangingProtocolName no + 0072,0004 HangingProtocolDescription no + 0072,0006 HangingProtocolLevel no + 0072,0008 HangingProtocolCreator no + 0072,000A HangingProtocolCreationDateTime no + 0072,000C HangingProtocolDefinitionSequence no + 0072,000E HangingProtocolUserIDCodeSequence no + 0072,0010 HangingProtocolUserGroupName no + 0072,0012 SourceHangingProtocolSequence no + 0072,0014 NumberOfPriorsReferenced no + 0072,0020 ImageSetsSequence no + 0072,0022 ImageSetSelectorSequence no + 0072,0024 ImageSetSelectorUsageFlag no + 0072,0026 SelectorAttribute no + 0072,0028 SelectorValueNumber no + 0072,0030 TimeBasedImageSetsSequence no + 0072,0032 ImageSetNumber no + 0072,0034 ImageSetSelectorCategory no + 0072,0038 RelativeTime no + 0072,003A RelativeTimeUnits no + 0072,003C AbstractPriorValue no + 0072,003E AbstractPriorCodeSequence no + 0072,0040 ImageSetLabel no + 0072,0050 SelectorAttributeVR no + 0072,0052 SelectorSequencePointer no + 0072,0054 SelectorSeqPointerPrivateCreator no + 0072,0056 SelectorAttributePrivateCreator no + 0072,0060 SelectorATValue no + 0072,0062 SelectorCSValue no + 0072,0064 SelectorISValue no + 0072,0066 SelectorLOValue no + 0072,0068 SelectorLTValue no + 0072,006A SelectorPNValue no + 0072,006C SelectorSHValue no + 0072,006E SelectorSTValue no + 0072,0070 SelectorUTValue no + 0072,0072 SelectorDSValue no + 0072,0074 SelectorFDValue no + 0072,0076 SelectorFLValue no + 0072,0078 SelectorULValue no + 0072,007A SelectorUSValue no + 0072,007C SelectorSLValue no + 0072,007E SelectorSSValue no + 0072,0080 SelectorCodeSequenceValue no + 0072,0100 NumberOfScreens no + 0072,0102 NominalScreenDefinitionSequence no + 0072,0104 NumberOfVerticalPixels no + 0072,0106 NumberOfHorizontalPixels no + 0072,0108 DisplayEnvironmentSpatialPosition no + 0072,010A ScreenMinimumGrayscaleBitDepth no + 0072,010C ScreenMinimumColorBitDepth no + 0072,010E ApplicationMaximumRepaintTime no + 0072,0200 DisplaySetsSequence no + 0072,0202 DisplaySetNumber no + 0072,0203 DisplaySetLabel no + 0072,0204 DisplaySetPresentationGroup no + 0072,0206 DisplaySetPresentationGroupDescr no + 0072,0208 PartialDataDisplayHandling no + 0072,0210 SynchronizedScrollingSequence no + 0072,0212 DisplaySetScrollingGroup no + 0072,0214 NavigationIndicatorSequence no + 0072,0216 NavigationDisplaySet no + 0072,0218 ReferenceDisplaySets no + 0072,0300 ImageBoxesSequence no + 0072,0302 ImageBoxNumber no + 0072,0304 ImageBoxLayoutType no + 0072,0306 ImageBoxTileHorizontalDimension no + 0072,0308 ImageBoxTileVerticalDimension no + 0072,0310 ImageBoxScrollDirection no + 0072,0312 ImageBoxSmallScrollType no + 0072,0314 ImageBoxSmallScrollAmount no + 0072,0316 ImageBoxLargeScrollType no + 0072,0318 ImageBoxLargeScrollAmount no + 0072,0320 ImageBoxOverlapPriority no + 0072,0330 CineRelativeToRealTime no + 0072,0400 FilterOperationsSequence no + 0072,0402 FilterByCategory no + 0072,0404 FilterByAttributePresence no + 0072,0406 FilterByOperator no + 0072,0432 SynchronizedImageBoxList no + 0072,0434 TypeOfSynchronization no + 0072,0500 BlendingOperationType no + 0072,0510 ReformattingOperationType no + 0072,0512 ReformattingThickness no + 0072,0514 ReformattingInterval no + 0072,0516 ReformattingOpInitialViewDir no + 0072,0520 RenderingType3D no + 0072,0600 SortingOperationsSequence no + 0072,0602 SortByCategory no + 0072,0604 SortingDirection no + 0072,0700 DisplaySetPatientOrientation no + 0072,0702 VOIType no + 0072,0704 PseudoColorType no + 0072,0706 ShowGrayscaleInverted no + 0072,0710 ShowImageTrueSizeFlag no + 0072,0712 ShowGraphicAnnotationFlag no + 0072,0714 ShowPatientDemographicsFlag no + 0072,0716 ShowAcquisitionTechniquesFlag no + 0072,0717 DisplaySetHorizontalJustification no + 0072,0718 DisplaySetVerticalJustification no + 0074,1000 UnifiedProcedureStepState no + 0074,1002 UPSProgressInformationSequence no + 0074,1004 UnifiedProcedureStepProgress no + 0074,1006 UnifiedProcedureStepProgressDescr no + 0074,1008 UnifiedProcedureStepComURISeq no + 0074,100a ContactURI no + 0074,100c ContactDisplayName no + 0074,1020 BeamTaskSequence no + 0074,1022 BeamTaskType no + 0074,1024 BeamOrderIndex no + 0074,1030 DeliveryVerificationImageSequence no + 0074,1032 VerificationImageTiming no + 0074,1034 DoubleExposureFlag no + 0074,1036 DoubleExposureOrdering no + 0074,1038 DoubleExposureMeterset no + 0074,103A DoubleExposureFieldDelta no + 0074,1040 RelatedReferenceRTImageSequence no + 0074,1042 GeneralMachineVerificationSequence no + 0074,1044 ConventionalMachineVerificationSeq no + 0074,1046 IonMachineVerificationSequence no + 0074,1048 FailedAttributesSequence no + 0074,104A OverriddenAttributesSequence no + 0074,104C ConventionalControlPointVerifySeq no + 0074,104E IonControlPointVerificationSeq no + 0074,1050 AttributeOccurrenceSequence no + 0074,1052 AttributeOccurrencePointer no + 0074,1054 AttributeItemSelector no + 0074,1056 AttributeOccurrencePrivateCreator no + 0074,1200 ScheduledProcedureStepPriority no + 0074,1202 WorklistLabel no + 0074,1204 ProcedureStepLabel no + 0074,1210 ScheduledProcessingParametersSeq no + 0074,1212 PerformedProcessingParametersSeq no + 0074,1216 UPSPerformedProcedureSequence no + 0074,1220 RelatedProcedureStepSequence no + 0074,1222 ProcedureStepRelationshipType no + 0074,1230 DeletionLock no + 0074,1234 ReceivingAE no + 0074,1236 RequestingAE no + 0074,1238 ReasonForCancellation no + 0074,1242 SCPStatus no + 0074,1244 SubscriptionListStatus no + 0074,1246 UPSListStatus no + 0088,0130 StorageMediaFileSetID no + 0088,0140 StorageMediaFileSetUID no + 0088,0200 IconImageSequence no + 0088,0904 TopicTitle no + 0088,0906 TopicSubject no + 0088,0910 TopicAuthor no + 0088,0912 TopicKeywords no + 0100,0410 SOPInstanceStatus no + 0100,0420 SOPAuthorizationDateAndTime no + 0100,0424 SOPAuthorizationComment no + 0100,0426 AuthorizationEquipmentCertNumber no + 0400,0005 MACIDNumber no + 0400,0010 MACCalculationTransferSyntaxUID no + 0400,0015 MACAlgorithm no + 0400,0020 DataElementsSigned no + 0400,0100 DigitalSignatureUID no + 0400,0105 DigitalSignatureDateTime no + 0400,0110 CertificateType no + 0400,0115 CertificateOfSigner no + 0400,0120 Signature no + 0400,0305 CertifiedTimestampType no + 0400,0310 CertifiedTimestamp no + 0400,0401 DigitalSignaturePurposeCodeSeq no + 0400,0402 ReferencedDigitalSignatureSeq no + 0400,0403 ReferencedSOPInstanceMACSeq no + 0400,0404 MAC no + 0400,0500 EncryptedAttributesSequence no + 0400,0510 EncryptedContentTransferSyntaxUID no + 0400,0520 EncryptedContent no + 0400,0550 ModifiedAttributesSequence no + 0400,0561 OriginalAttributesSequence no + 0400,0562 AttributeModificationDateTime no + 0400,0563 ModifyingSystem no + 0400,0564 SourceOfPreviousValues no + 0400,0565 ReasonForTheAttributeModification no + 1000,xxx0 EscapeTriplet no + 1000,xxx1 RunLengthTriplet no + 1000,xxx2 HuffmanTableSize no + 1000,xxx3 HuffmanTableTriplet no + 1000,xxx4 ShiftTableSize no + 1000,xxx5 ShiftTableTriplet no + 1010,xxxx ZonalMap no + 2000,0010 NumberOfCopies no + 2000,001E PrinterConfigurationSequence no + 2000,0020 PrintPriority no + 2000,0030 MediumType no + 2000,0040 FilmDestination no + 2000,0050 FilmSessionLabel no + 2000,0060 MemoryAllocation no + 2000,0061 MaximumMemoryAllocation no + 2000,0062 ColorImagePrintingFlag no + 2000,0063 CollationFlag no + 2000,0065 AnnotationFlag no + 2000,0067 ImageOverlayFlag no + 2000,0069 PresentationLUTFlag no + 2000,006A ImageBoxPresentationLUTFlag no + 2000,00A0 MemoryBitDepth no + 2000,00A1 PrintingBitDepth no + 2000,00A2 MediaInstalledSequence no + 2000,00A4 OtherMediaAvailableSequence no + 2000,00A8 SupportedImageDisplayFormatSeq no + 2000,0500 ReferencedFilmBoxSequence no + 2000,0510 ReferencedStoredPrintSequence no + 2010,0010 ImageDisplayFormat no + 2010,0030 AnnotationDisplayFormatID no + 2010,0040 FilmOrientation no + 2010,0050 FilmSizeID no + 2010,0052 PrinterResolutionID no + 2010,0054 DefaultPrinterResolutionID no + 2010,0060 MagnificationType no + 2010,0080 SmoothingType no + 2010,00A6 DefaultMagnificationType no + 2010,00A7 OtherMagnificationTypesAvailable no + 2010,00A8 DefaultSmoothingType no + 2010,00A9 OtherSmoothingTypesAvailable no + 2010,0100 BorderDensity no + 2010,0110 EmptyImageDensity no + 2010,0120 MinDensity no + 2010,0130 MaxDensity no + 2010,0140 Trim no + 2010,0150 ConfigurationInformation no + 2010,0152 ConfigurationInformationDescr no + 2010,0154 MaximumCollatedFilms no + 2010,015E Illumination no + 2010,0160 ReflectedAmbientLight no + 2010,0376 PrinterPixelSpacing no + 2010,0500 ReferencedFilmSessionSequence no + 2010,0510 ReferencedImageBoxSequence no + 2010,0520 ReferencedBasicAnnotationBoxSeq no + 2020,0010 ImageBoxPosition no + 2020,0020 Polarity no + 2020,0030 RequestedImageSize no + 2020,0040 RequestedDecimate-CropBehavior no + 2020,0050 RequestedResolutionID no + 2020,00A0 RequestedImageSizeFlag no + 2020,00A2 DecimateCropResult no + 2020,0110 BasicGrayscaleImageSequence no + 2020,0111 BasicColorImageSequence no + 2020,0130 ReferencedImageOverlayBoxSequence no + 2020,0140 ReferencedVOILUTBoxSequence no + 2030,0010 AnnotationPosition no + 2030,0020 TextString no + 2040,0010 ReferencedOverlayPlaneSequence no + 2040,0011 ReferencedOverlayPlaneGroups no + 2040,0020 OverlayPixelDataSequence no + 2040,0060 OverlayMagnificationType no + 2040,0070 OverlaySmoothingType no + 2040,0072 OverlayOrImageMagnification no + 2040,0074 MagnifyToNumberOfColumns no + 2040,0080 OverlayForegroundDensity no + 2040,0082 OverlayBackgroundDensity no + 2040,0090 OverlayMode no + 2040,0100 ThresholdDensity no + 2040,0500 ReferencedImageBoxSequence no + 2050,0010 PresentationLUTSequence no + 2050,0020 PresentationLUTShape no + 2050,0500 ReferencedPresentationLUTSequence no + 2100,0010 PrintJobID no + 2100,0020 ExecutionStatus no + 2100,0030 ExecutionStatusInfo no + 2100,0040 CreationDate no + 2100,0050 CreationTime no + 2100,0070 Originator no + 2100,0140 DestinationAE no + 2100,0160 OwnerID no + 2100,0170 NumberOfFilms no + 2100,0500 ReferencedPrintJobSequence no + 2110,0010 PrinterStatus no + 2110,0020 PrinterStatusInfo no + 2110,0030 PrinterName no + 2110,0099 PrintQueueID no + 2120,0010 QueueStatus no + 2120,0050 PrintJobDescriptionSequence no + 2120,0070 ReferencedPrintJobSequence no + 2130,0010 PrintManagementCapabilitiesSeq no + 2130,0015 PrinterCharacteristicsSequence no + 2130,0030 FilmBoxContentSequence no + 2130,0040 ImageBoxContentSequence no + 2130,0050 AnnotationContentSequence no + 2130,0060 ImageOverlayBoxContentSequence no + 2130,0080 PresentationLUTContentSequence no + 2130,00A0 ProposedStudySequence no + 2130,00C0 OriginalImageSequence no + 2200,0001 LabelFromInfoExtractedFromInstance no + 2200,0002 LabelText no + 2200,0003 LabelStyleSelection no + 2200,0004 MediaDisposition no + 2200,0005 BarcodeValue no + 2200,0006 BarcodeSymbology no + 2200,0007 AllowMediaSplitting no + 2200,0008 IncludeNonDICOMObjects no + 2200,0009 IncludeDisplayApplication no + 2200,000A SaveCompInstancesAfterMediaCreate no + 2200,000B TotalNumberMediaPiecesCreated no + 2200,000C RequestedMediaApplicationProfile no + 2200,000D ReferencedStorageMediaSequence no + 2200,000E FailureAttributes no + 2200,000F AllowLossyCompression no + 2200,0020 RequestPriority no + 3002,0002 RTImageLabel no + 3002,0003 RTImageName no + 3002,0004 RTImageDescription no + 3002,000A ReportedValuesOrigin no + 3002,000C RTImagePlane no + 3002,000D XRayImageReceptorTranslation no + 3002,000E XRayImageReceptorAngle no + 3002,0010 RTImageOrientation no + 3002,0011 ImagePlanePixelSpacing no + 3002,0012 RTImagePosition no + 3002,0020 RadiationMachineName no + 3002,0022 RadiationMachineSAD no + 3002,0024 RadiationMachineSSD no + 3002,0026 RTImageSID no + 3002,0028 SourceToReferenceObjectDistance no + 3002,0029 FractionNumber no + 3002,0030 ExposureSequence no + 3002,0032 MetersetExposure no + 3002,0034 DiaphragmPosition no + 3002,0040 FluenceMapSequence no + 3002,0041 FluenceDataSource no + 3002,0042 FluenceDataScale no + 3002,0051 FluenceMode no + 3002,0052 FluenceModeID no + 3004,0001 DVHType no + 3004,0002 DoseUnits no + 3004,0004 DoseType no + 3004,0006 DoseComment no + 3004,0008 NormalizationPoint no + 3004,000A DoseSummationType no + 3004,000C GridFrameOffsetVector no + 3004,000E DoseGridScaling no + 3004,0010 RTDoseROISequence no + 3004,0012 DoseValue no + 3004,0014 TissueHeterogeneityCorrection no + 3004,0040 DVHNormalizationPoint no + 3004,0042 DVHNormalizationDoseValue no + 3004,0050 DVHSequence no + 3004,0052 DVHDoseScaling no + 3004,0054 DVHVolumeUnits no + 3004,0056 DVHNumberOfBins no + 3004,0058 DVHData no + 3004,0060 DVHReferencedROISequence no + 3004,0062 DVHROIContributionType no + 3004,0070 DVHMinimumDose no + 3004,0072 DVHMaximumDose no + 3004,0074 DVHMeanDose no + 3006,0002 StructureSetLabel no + 3006,0004 StructureSetName no + 3006,0006 StructureSetDescription no + 3006,0008 StructureSetDate no + 3006,0009 StructureSetTime no + 3006,0010 ReferencedFrameOfReferenceSequence no + 3006,0012 RTReferencedStudySequence no + 3006,0014 RTReferencedSeriesSequence no + 3006,0016 ContourImageSequence no + 3006,0020 StructureSetROISequence no + 3006,0022 ROINumber no + 3006,0024 ReferencedFrameOfReferenceUID no + 3006,0026 ROIName no + 3006,0028 ROIDescription no + 3006,002A ROIDisplayColor no + 3006,002C ROIVolume no + 3006,0030 RTRelatedROISequence no + 3006,0033 RTROIRelationship no + 3006,0036 ROIGenerationAlgorithm no + 3006,0038 ROIGenerationDescription no + 3006,0039 ROIContourSequence no + 3006,0040 ContourSequence no + 3006,0042 ContourGeometricType no + 3006,0044 ContourSlabThickness no + 3006,0045 ContourOffsetVector no + 3006,0046 NumberOfContourPoints no + 3006,0048 ContourNumber no + 3006,0049 AttachedContours no + 3006,0050 ContourData no + 3006,0080 RTROIObservationsSequence no + 3006,0082 ObservationNumber no + 3006,0084 ReferencedROINumber no + 3006,0085 ROIObservationLabel no + 3006,0086 RTROIIdentificationCodeSequence no + 3006,0088 ROIObservationDescription no + 3006,00A0 RelatedRTROIObservationsSequence no + 3006,00A4 RTROIInterpretedType no + 3006,00A6 ROIInterpreter no + 3006,00B0 ROIPhysicalPropertiesSequence no + 3006,00B2 ROIPhysicalProperty no + 3006,00B4 ROIPhysicalPropertyValue no + 3006,00B6 ROIElementalCompositionSequence no + 3006,00B7 ROIElementalCompAtomicNumber no + 3006,00B8 ROIElementalCompAtomicMassFraction no + 3006,00C0 FrameOfReferenceRelationshipSeq no + 3006,00C2 RelatedFrameOfReferenceUID no + 3006,00C4 FrameOfReferenceTransformType no + 3006,00C6 FrameOfReferenceTransformMatrix no + 3006,00C8 FrameOfReferenceTransformComment no + 3008,0010 MeasuredDoseReferenceSequence no + 3008,0012 MeasuredDoseDescription no + 3008,0014 MeasuredDoseType no + 3008,0016 MeasuredDoseValue no + 3008,0020 TreatmentSessionBeamSequence no + 3008,0021 TreatmentSessionIonBeamSequence no + 3008,0022 CurrentFractionNumber no + 3008,0024 TreatmentControlPointDate no + 3008,0025 TreatmentControlPointTime no + 3008,002A TreatmentTerminationStatus no + 3008,002B TreatmentTerminationCode no + 3008,002C TreatmentVerificationStatus no + 3008,0030 ReferencedTreatmentRecordSequence no + 3008,0032 SpecifiedPrimaryMeterset no + 3008,0033 SpecifiedSecondaryMeterset no + 3008,0036 DeliveredPrimaryMeterset no + 3008,0037 DeliveredSecondaryMeterset no + 3008,003A SpecifiedTreatmentTime no + 3008,003B DeliveredTreatmentTime no + 3008,0040 ControlPointDeliverySequence no + 3008,0041 IonControlPointDeliverySequence no + 3008,0042 SpecifiedMeterset no + 3008,0044 DeliveredMeterset no + 3008,0045 MetersetRateSet no + 3008,0046 MetersetRateDelivered no + 3008,0047 ScanSpotMetersetsDelivered no + 3008,0048 DoseRateDelivered no + 3008,0050 TreatmentSummaryCalcDoseRefSeq no + 3008,0052 CumulativeDoseToDoseReference no + 3008,0054 FirstTreatmentDate no + 3008,0056 MostRecentTreatmentDate no + 3008,005A NumberOfFractionsDelivered no + 3008,0060 OverrideSequence no + 3008,0061 ParameterSequencePointer no + 3008,0062 OverrideParameterPointer no + 3008,0063 ParameterItemIndex no + 3008,0064 MeasuredDoseReferenceNumber no + 3008,0065 ParameterPointer no + 3008,0066 OverrideReason no + 3008,0068 CorrectedParameterSequence no + 3008,006A CorrectionValue no + 3008,0070 CalculatedDoseReferenceSequence no + 3008,0072 CalculatedDoseReferenceNumber no + 3008,0074 CalculatedDoseReferenceDescription no + 3008,0076 CalculatedDoseReferenceDoseValue no + 3008,0078 StartMeterset no + 3008,007A EndMeterset no + 3008,0080 ReferencedMeasuredDoseReferenceSeq no + 3008,0082 ReferencedMeasuredDoseReferenceNum no + 3008,0090 ReferencedCalculatedDoseRefSeq no + 3008,0092 ReferencedCalculatedDoseRefNumber no + 3008,00A0 BeamLimitingDeviceLeafPairsSeq no + 3008,00B0 RecordedWedgeSequence no + 3008,00C0 RecordedCompensatorSequence no + 3008,00D0 RecordedBlockSequence no + 3008,00E0 TreatmentSummaryMeasuredDoseRefSeq no + 3008,00F0 RecordedSnoutSequence no + 3008,00F2 RecordedRangeShifterSequence no + 3008,00F4 RecordedLateralSpreadingDeviceSeq no + 3008,00F6 RecordedRangeModulatorSequence no + 3008,0100 RecordedSourceSequence no + 3008,0105 SourceSerialNumber no + 3008,0110 TreatmentSessionAppSetupSeq no + 3008,0116 ApplicationSetupCheck no + 3008,0120 RecordedBrachyAccessoryDeviceSeq no + 3008,0122 ReferencedBrachyAccessoryDeviceNum no + 3008,0130 RecordedChannelSequence no + 3008,0132 SpecifiedChannelTotalTime no + 3008,0134 DeliveredChannelTotalTime no + 3008,0136 SpecifiedNumberOfPulses no + 3008,0138 DeliveredNumberOfPulses no + 3008,013A SpecifiedPulseRepetitionInterval no + 3008,013C DeliveredPulseRepetitionInterval no + 3008,0140 RecordedSourceApplicatorSequence no + 3008,0142 ReferencedSourceApplicatorNumber no + 3008,0150 RecordedChannelShieldSequence no + 3008,0152 ReferencedChannelShieldNumber no + 3008,0160 BrachyControlPointDeliveredSeq no + 3008,0162 SafePositionExitDate no + 3008,0164 SafePositionExitTime no + 3008,0166 SafePositionReturnDate no + 3008,0168 SafePositionReturnTime no + 3008,0200 CurrentTreatmentStatus no + 3008,0202 TreatmentStatusComment no + 3008,0220 FractionGroupSummarySequence no + 3008,0223 ReferencedFractionNumber no + 3008,0224 FractionGroupType no + 3008,0230 BeamStopperPosition no + 3008,0240 FractionStatusSummarySequence no + 3008,0250 TreatmentDate no + 3008,0251 TreatmentTime no + 300A,0002 RTPlanLabel no + 300A,0003 RTPlanName no + 300A,0004 RTPlanDescription no + 300A,0006 RTPlanDate no + 300A,0007 RTPlanTime no + 300A,0009 TreatmentProtocols no + 300A,000A PlanIntent no + 300A,000B TreatmentSites no + 300A,000C RTPlanGeometry no + 300A,000E PrescriptionDescription no + 300A,0010 DoseReferenceSequence no + 300A,0012 DoseReferenceNumber no + 300A,0013 DoseReferenceUID no + 300A,0014 DoseReferenceStructureType no + 300A,0015 NominalBeamEnergyUnit no + 300A,0016 DoseReferenceDescription no + 300A,0018 DoseReferencePointCoordinates no + 300A,001A NominalPriorDose no + 300A,0020 DoseReferenceType no + 300A,0021 ConstraintWeight no + 300A,0022 DeliveryWarningDose no + 300A,0023 DeliveryMaximumDose no + 300A,0025 TargetMinimumDose no + 300A,0026 TargetPrescriptionDose no + 300A,0027 TargetMaximumDose no + 300A,0028 TargetUnderdoseVolumeFraction no + 300A,002A OrganAtRiskFullVolumeDose no + 300A,002B OrganAtRiskLimitDose no + 300A,002C OrganAtRiskMaximumDose no + 300A,002D OrganAtRiskOverdoseVolumeFraction no + 300A,0040 ToleranceTableSequence no + 300A,0042 ToleranceTableNumber no + 300A,0043 ToleranceTableLabel no + 300A,0044 GantryAngleTolerance no + 300A,0046 BeamLimitingDeviceAngleTolerance no + 300A,0048 BeamLimitingDeviceToleranceSeq no + 300A,004A BeamLimitingDevicePositionTol no + 300A,004B SnoutPositionTolerance no + 300A,004C PatientSupportAngleTolerance no + 300A,004E TableTopEccentricAngleTolerance no + 300A,004F TableTopPitchAngleTolerance no + 300A,0050 TableTopRollAngleTolerance no + 300A,0051 TableTopVerticalPositionTolerance no + 300A,0052 TableTopLongitudinalPositionTol no + 300A,0053 TableTopLateralPositionTolerance no + 300A,0055 RTPlanRelationship no + 300A,0070 FractionGroupSequence no + 300A,0071 FractionGroupNumber no + 300A,0072 FractionGroupDescription no + 300A,0078 NumberOfFractionsPlanned no + 300A,0079 NumberFractionPatternDigitsPerDay no + 300A,007A RepeatFractionCycleLength no + 300A,007B FractionPattern no + 300A,0080 NumberOfBeams no + 300A,0082 BeamDoseSpecificationPoint no + 300A,0084 BeamDose no + 300A,0086 BeamMeterset no + 300A,0088 BeamDosePointDepth no + 300A,0089 BeamDosePointEquivalentDepth no + 300A,008A BeamDosePointSSD no + 300A,00A0 NumberOfBrachyApplicationSetups no + 300A,00A2 BrachyAppSetupDoseSpecPoint no + 300A,00A4 BrachyApplicationSetupDose no + 300A,00B0 BeamSequence no + 300A,00B2 TreatmentMachineName no + 300A,00B3 PrimaryDosimeterUnit no + 300A,00B4 SourceAxisDistance no + 300A,00B6 BeamLimitingDeviceSequence no + 300A,00B8 RTBeamLimitingDeviceType no + 300A,00BA SourceToBeamLimitingDeviceDistance no + 300A,00BB IsocenterToBeamLimitingDeviceDist no + 300A,00BC NumberOfLeafJawPairs no + 300A,00BE LeafPositionBoundaries no + 300A,00C0 BeamNumber no + 300A,00C2 BeamName no + 300A,00C3 BeamDescription no + 300A,00C4 BeamType no + 300A,00C6 RadiationType no + 300A,00C7 HighDoseTechniqueType no + 300A,00C8 ReferenceImageNumber no + 300A,00CA PlannedVerificationImageSequence no + 300A,00CC ImagingDeviceSpecificAcqParams no + 300A,00CE TreatmentDeliveryType no + 300A,00D0 NumberOfWedges no + 300A,00D1 WedgeSequence no + 300A,00D2 WedgeNumber no + 300A,00D3 WedgeType no + 300A,00D4 WedgeID no + 300A,00D5 WedgeAngle no + 300A,00D6 WedgeFactor no + 300A,00D7 TotalWedgeTrayWaterEquivThickness no + 300A,00D8 WedgeOrientation no + 300A,00D9 IsocenterToWedgeTrayDistance no + 300A,00DA SourceToWedgeTrayDistance no + 300A,00DB WedgeThinEdgePosition no + 300A,00DC BolusID no + 300A,00DD BolusDescription no + 300A,00E0 NumberOfCompensators no + 300A,00E1 MaterialID no + 300A,00E2 TotalCompensatorTrayFactor no + 300A,00E3 CompensatorSequence no + 300A,00E4 CompensatorNumber no + 300A,00E5 CompensatorID no + 300A,00E6 SourceToCompensatorTrayDistance no + 300A,00E7 CompensatorRows no + 300A,00E8 CompensatorColumns no + 300A,00E9 CompensatorPixelSpacing no + 300A,00EA CompensatorPosition no + 300A,00EB CompensatorTransmissionData no + 300A,00EC CompensatorThicknessData no + 300A,00ED NumberOfBoli no + 300A,00EE CompensatorType no + 300A,00F0 NumberOfBlocks no + 300A,00F2 TotalBlockTrayFactor no + 300A,00F3 TotalBlockTrayWaterEquivThickness no + 300A,00F4 BlockSequence no + 300A,00F5 BlockTrayID no + 300A,00F6 SourceToBlockTrayDistance no + 300A,00F7 IsocenterToBlockTrayDistance no + 300A,00F8 BlockType no + 300A,00F9 AccessoryCode no + 300A,00FA BlockDivergence no + 300A,00FB BlockMountingPosition no + 300A,00FC BlockNumber no + 300A,00FE BlockName no + 300A,0100 BlockThickness no + 300A,0102 BlockTransmission no + 300A,0104 BlockNumberOfPoints no + 300A,0106 BlockData no + 300A,0107 ApplicatorSequence no + 300A,0108 ApplicatorID no + 300A,0109 ApplicatorType no + 300A,010A ApplicatorDescription no + 300A,010C CumulativeDoseReferenceCoefficient no + 300A,010E FinalCumulativeMetersetWeight no + 300A,0110 NumberOfControlPoints no + 300A,0111 ControlPointSequence no + 300A,0112 ControlPointIndex no + 300A,0114 NominalBeamEnergy no + 300A,0115 DoseRateSet no + 300A,0116 WedgePositionSequence no + 300A,0118 WedgePosition no + 300A,011A BeamLimitingDevicePositionSequence no + 300A,011C LeafJawPositions no + 300A,011E GantryAngle no + 300A,011F GantryRotationDirection no + 300A,0120 BeamLimitingDeviceAngle no + 300A,0121 BeamLimitingDeviceRotateDirection no + 300A,0122 PatientSupportAngle no + 300A,0123 PatientSupportRotationDirection no + 300A,0124 TableTopEccentricAxisDistance no + 300A,0125 TableTopEccentricAngle no + 300A,0126 TableTopEccentricRotateDirection no + 300A,0128 TableTopVerticalPosition no + 300A,0129 TableTopLongitudinalPosition no + 300A,012A TableTopLateralPosition no + 300A,012C IsocenterPosition no + 300A,012E SurfaceEntryPoint no + 300A,0130 SourceToSurfaceDistance no + 300A,0134 CumulativeMetersetWeight no + 300A,0140 TableTopPitchAngle no + 300A,0142 TableTopPitchRotationDirection no + 300A,0144 TableTopRollAngle no + 300A,0146 TableTopRollRotationDirection no + 300A,0148 HeadFixationAngle no + 300A,014A GantryPitchAngle no + 300A,014C GantryPitchRotationDirection no + 300A,014E GantryPitchAngleTolerance no + 300A,0180 PatientSetupSequence no + 300A,0182 PatientSetupNumber no + 300A,0183 PatientSetupLabel no + 300A,0184 PatientAdditionalPosition no + 300A,0190 FixationDeviceSequence no + 300A,0192 FixationDeviceType no + 300A,0194 FixationDeviceLabel no + 300A,0196 FixationDeviceDescription no + 300A,0198 FixationDevicePosition no + 300A,0199 FixationDevicePitchAngle no + 300A,019A FixationDeviceRollAngle no + 300A,01A0 ShieldingDeviceSequence no + 300A,01A2 ShieldingDeviceType no + 300A,01A4 ShieldingDeviceLabel no + 300A,01A6 ShieldingDeviceDescription no + 300A,01A8 ShieldingDevicePosition no + 300A,01B0 SetupTechnique no + 300A,01B2 SetupTechniqueDescription no + 300A,01B4 SetupDeviceSequence no + 300A,01B6 SetupDeviceType no + 300A,01B8 SetupDeviceLabel no + 300A,01BA SetupDeviceDescription no + 300A,01BC SetupDeviceParameter no + 300A,01D0 SetupReferenceDescription no + 300A,01D2 TableTopVerticalSetupDisplacement no + 300A,01D4 TableTopLongitudinalSetupDisplace no + 300A,01D6 TableTopLateralSetupDisplacement no + 300A,0200 BrachyTreatmentTechnique no + 300A,0202 BrachyTreatmentType no + 300A,0206 TreatmentMachineSequence no + 300A,0210 SourceSequence no + 300A,0212 SourceNumber no + 300A,0214 SourceType no + 300A,0216 SourceManufacturer no + 300A,0218 ActiveSourceDiameter no + 300A,021A ActiveSourceLength no + 300A,0222 SourceEncapsulationNomThickness no + 300A,0224 SourceEncapsulationNomTransmission no + 300A,0226 SourceIsotopeName no + 300A,0228 SourceIsotopeHalfLife no + 300A,0229 SourceStrengthUnits no + 300A,022A ReferenceAirKermaRate no + 300A,022B SourceStrength no + 300A,022C SourceStrengthReferenceDate no + 300A,022E SourceStrengthReferenceTime no + 300A,0230 ApplicationSetupSequence no + 300A,0232 ApplicationSetupType no + 300A,0234 ApplicationSetupNumber no + 300A,0236 ApplicationSetupName no + 300A,0238 ApplicationSetupManufacturer no + 300A,0240 TemplateNumber no + 300A,0242 TemplateType no + 300A,0244 TemplateName no + 300A,0250 TotalReferenceAirKerma no + 300A,0260 BrachyAccessoryDeviceSequence no + 300A,0262 BrachyAccessoryDeviceNumber no + 300A,0263 BrachyAccessoryDeviceID no + 300A,0264 BrachyAccessoryDeviceType no + 300A,0266 BrachyAccessoryDeviceName no + 300A,026A BrachyAccessoryDeviceNomThickness no + 300A,026C BrachyAccessoryDevNomTransmission no + 300A,0280 ChannelSequence no + 300A,0282 ChannelNumber no + 300A,0284 ChannelLength no + 300A,0286 ChannelTotalTime no + 300A,0288 SourceMovementType no + 300A,028A NumberOfPulses no + 300A,028C PulseRepetitionInterval no + 300A,0290 SourceApplicatorNumber no + 300A,0291 SourceApplicatorID no + 300A,0292 SourceApplicatorType no + 300A,0294 SourceApplicatorName no + 300A,0296 SourceApplicatorLength no + 300A,0298 SourceApplicatorManufacturer no + 300A,029C SourceApplicatorWallNomThickness no + 300A,029E SourceApplicatorWallNomTrans no + 300A,02A0 SourceApplicatorStepSize no + 300A,02A2 TransferTubeNumber no + 300A,02A4 TransferTubeLength no + 300A,02B0 ChannelShieldSequence no + 300A,02B2 ChannelShieldNumber no + 300A,02B3 ChannelShieldID no + 300A,02B4 ChannelShieldName no + 300A,02B8 ChannelShieldNominalThickness no + 300A,02BA ChannelShieldNominalTransmission no + 300A,02C8 FinalCumulativeTimeWeight no + 300A,02D0 BrachyControlPointSequence no + 300A,02D2 ControlPointRelativePosition no + 300A,02D4 ControlPoint3DPosition no + 300A,02D6 CumulativeTimeWeight no + 300A,02E0 CompensatorDivergence no + 300A,02E1 CompensatorMountingPosition no + 300A,02E2 SourceToCompensatorDistance no + 300A,02E3 TotalCompTrayWaterEquivThickness no + 300A,02E4 IsocenterToCompensatorTrayDistance no + 300A,02E5 CompensatorColumnOffset no + 300A,02E6 IsocenterToCompensatorDistances no + 300A,02E7 CompensatorRelStoppingPowerRatio no + 300A,02E8 CompensatorMillingToolDiameter no + 300A,02EA IonRangeCompensatorSequence no + 300A,02EB CompensatorDescription no + 300A,0302 RadiationMassNumber no + 300A,0304 RadiationAtomicNumber no + 300A,0306 RadiationChargeState no + 300A,0308 ScanMode no + 300A,030A VirtualSourceAxisDistances no + 300A,030C SnoutSequence no + 300A,030D SnoutPosition no + 300A,030F SnoutID no + 300A,0312 NumberOfRangeShifters no + 300A,0314 RangeShifterSequence no + 300A,0316 RangeShifterNumber no + 300A,0318 RangeShifterID no + 300A,0320 RangeShifterType no + 300A,0322 RangeShifterDescription no + 300A,0330 NumberOfLateralSpreadingDevices no + 300A,0332 LateralSpreadingDeviceSequence no + 300A,0334 LateralSpreadingDeviceNumber no + 300A,0336 LateralSpreadingDeviceID no + 300A,0338 LateralSpreadingDeviceType no + 300A,033A LateralSpreadingDeviceDescription no + 300A,033C LateralSpreadingDevWaterEquivThick no + 300A,0340 NumberOfRangeModulators no + 300A,0342 RangeModulatorSequence no + 300A,0344 RangeModulatorNumber no + 300A,0346 RangeModulatorID no + 300A,0348 RangeModulatorType no + 300A,034A RangeModulatorDescription no + 300A,034C BeamCurrentModulationID no + 300A,0350 PatientSupportType no + 300A,0352 PatientSupportID no + 300A,0354 PatientSupportAccessoryCode no + 300A,0356 FixationLightAzimuthalAngle no + 300A,0358 FixationLightPolarAngle no + 300A,035A MetersetRate no + 300A,0360 RangeShifterSettingsSequence no + 300A,0362 RangeShifterSetting no + 300A,0364 IsocenterToRangeShifterDistance no + 300A,0366 RangeShifterWaterEquivThickness no + 300A,0370 LateralSpreadingDeviceSettingsSeq no + 300A,0372 LateralSpreadingDeviceSetting no + 300A,0374 IsocenterToLateralSpreadingDevDist no + 300A,0380 RangeModulatorSettingsSequence no + 300A,0382 RangeModulatorGatingStartValue no + 300A,0384 RangeModulatorGatingStopValue no + 300A,038A IsocenterToRangeModulatorDistance no + 300A,0390 ScanSpotTuneID no + 300A,0392 NumberOfScanSpotPositions no + 300A,0394 ScanSpotPositionMap no + 300A,0396 ScanSpotMetersetWeights no + 300A,0398 ScanningSpotSize no + 300A,039A NumberOfPaintings no + 300A,03A0 IonToleranceTableSequence no + 300A,03A2 IonBeamSequence no + 300A,03A4 IonBeamLimitingDeviceSequence no + 300A,03A6 IonBlockSequence no + 300A,03A8 IonControlPointSequence no + 300A,03AA IonWedgeSequence no + 300A,03AC IonWedgePositionSequence no + 300A,0401 ReferencedSetupImageSequence no + 300A,0402 SetupImageComment no + 300A,0410 MotionSynchronizationSequence no + 300A,0412 ControlPointOrientation no + 300A,0420 GeneralAccessorySequence no + 300A,0421 GeneralAccessoryID no + 300A,0422 GeneralAccessoryDescription no + 300A,0423 GeneralAccessoryType no + 300A,0424 GeneralAccessoryNumber no + 300C,0002 ReferencedRTPlanSequence no + 300C,0004 ReferencedBeamSequence no + 300C,0006 ReferencedBeamNumber no + 300C,0007 ReferencedReferenceImageNumber no + 300C,0008 StartCumulativeMetersetWeight no + 300C,0009 EndCumulativeMetersetWeight no + 300C,000A ReferencedBrachyAppSetupSeq no + 300C,000C ReferencedBrachyAppSetupNumber no + 300C,000E ReferencedSourceNumber no + 300C,0020 ReferencedFractionGroupSequence no + 300C,0022 ReferencedFractionGroupNumber no + 300C,0040 ReferencedVerificationImageSeq no + 300C,0042 ReferencedReferenceImageSequence no + 300C,0050 ReferencedDoseReferenceSequence no + 300C,0051 ReferencedDoseReferenceNumber no + 300C,0055 BrachyReferencedDoseReferenceSeq no + 300C,0060 ReferencedStructureSetSequence no + 300C,006A ReferencedPatientSetupNumber no + 300C,0080 ReferencedDoseSequence no + 300C,00A0 ReferencedToleranceTableNumber no + 300C,00B0 ReferencedBolusSequence no + 300C,00C0 ReferencedWedgeNumber no + 300C,00D0 ReferencedCompensatorNumber no + 300C,00E0 ReferencedBlockNumber no + 300C,00F0 ReferencedControlPointIndex no + 300C,00F2 ReferencedControlPointSequence no + 300C,00F4 ReferencedStartControlPointIndex no + 300C,00F6 ReferencedStopControlPointIndex no + 300C,0100 ReferencedRangeShifterNumber no + 300C,0102 ReferencedLateralSpreadingDevNum no + 300C,0104 ReferencedRangeModulatorNumber no + 300E,0002 ApprovalStatus no + 300E,0004 ReviewDate no + 300E,0005 ReviewTime no + 300E,0008 ReviewerName no + 4000,0000 TextGroupLength no + 4000,0010 Arbitrary no + 4000,4000 TextComments no + 4008,0040 ResultsID no + 4008,0042 ResultsIDIssuer no + 4008,0050 ReferencedInterpretationSequence no + 4008,0100 InterpretationRecordedDate no + 4008,0101 InterpretationRecordedTime no + 4008,0102 InterpretationRecorder no + 4008,0103 ReferenceToRecordedSound no + 4008,0108 InterpretationTranscriptionDate no + 4008,0109 InterpretationTranscriptionTime no + 4008,010A InterpretationTranscriber no + 4008,010B InterpretationText no + 4008,010C InterpretationAuthor no + 4008,0111 InterpretationApproverSequence no + 4008,0112 InterpretationApprovalDate no + 4008,0113 InterpretationApprovalTime no + 4008,0114 PhysicianApprovingInterpretation no + 4008,0115 InterpretationDiagnosisDescription no + 4008,0117 InterpretationDiagnosisCodeSeq no + 4008,0118 ResultsDistributionListSequence no + 4008,0119 DistributionName no + 4008,011A DistributionAddress no + 4008,0200 InterpretationID no + 4008,0202 InterpretationIDIssuer no + 4008,0210 InterpretationTypeID no + 4008,0212 InterpretationStatusID no + 4008,0300 Impressions no + 4008,4000 ResultsComments no + 4FFE,0001 MACParametersSequence no + 50xx,0005 CurveDimensions no + 50xx,0010 NumberOfPoints no + 50xx,0020 TypeOfData no + 50xx,0022 CurveDescription no + 50xx,0030 AxisUnits no + 50xx,0040 AxisLabels no + 50xx,0103 DataValueRepresentation no + 50xx,0104 MinimumCoordinateValue no + 50xx,0105 MaximumCoordinateValue no + 50xx,0106 CurveRange no + 50xx,0110 CurveDataDescriptor no + 50xx,0112 CoordinateStartValue no + 50xx,0114 CoordinateStepValue no + 50xx,1001 CurveActivationLayer no + 50xx,2000 AudioType no + 50xx,2002 AudioSampleFormat no + 50xx,2004 NumberOfChannels no + 50xx,2006 NumberOfSamples no + 50xx,2008 SampleRate no + 50xx,200A TotalTime no + 50xx,200C AudioSampleData no + 50xx,200E AudioComments no + 50xx,2500 CurveLabel no + 50xx,2600 ReferencedOverlaySequence no + 50xx,2610 ReferencedOverlayGroup no + 50xx,3000 CurveData no + 5200,9229 SharedFunctionalGroupsSequence no + 5200,9230 PerFrameFunctionalGroupsSequence no + 5400,0100 WaveformSequence no + 5400,0110 ChannelMinimumValue no + 5400,0112 ChannelMaximumValue no + 5400,1004 WaveformBitsAllocated no + 5400,1006 WaveformSampleInterpretation no + 5400,100A WaveformPaddingValue no + 5400,1010 WaveformData no + 5600,0010 FirstOrderPhaseCorrectionAngle no + 5600,0020 SpectroscopyData no + 6000,0000 OverlayGroupLength no + 60xx,0010 OverlayRows no + 60xx,0011 OverlayColumns no + 60xx,0012 OverlayPlanes no + 60xx,0015 NumberOfFramesInOverlay no + 60xx,0022 OverlayDescription no + 60xx,0040 OverlayType no + 60xx,0045 OverlaySubtype no + 60xx,0050 OverlayOrigin no + 60xx,0051 ImageFrameOrigin no + 60xx,0052 OverlayPlaneOrigin no + 60xx,0060 OverlayCompressionCode no + 60xx,0061 OverlayCompressionOriginator no + 60xx,0062 OverlayCompressionLabel no + 60xx,0063 OverlayCompressionDescription no + 60xx,0066 OverlayCompressionStepPointers no + 60xx,0068 OverlayRepeatInterval no + 60xx,0069 OverlayBitsGrouped no + 60xx,0100 OverlayBitsAllocated no + 60xx,0102 OverlayBitPosition no + 60xx,0110 OverlayFormat no + 60xx,0200 OverlayLocation no + 60xx,0800 OverlayCodeLabel no + 60xx,0802 OverlayNumberOfTables no + 60xx,0803 OverlayCodeTableLocation no + 60xx,0804 OverlayBitsForCodeWord no + 60xx,1001 OverlayActivationLayer no + 60xx,1100 OverlayDescriptorGray no + 60xx,1101 OverlayDescriptorRed no + 60xx,1102 OverlayDescriptorGreen no + 60xx,1103 OverlayDescriptorBlue no + 60xx,1200 OverlaysGray no + 60xx,1201 OverlaysRed no + 60xx,1202 OverlaysGreen no + 60xx,1203 OverlaysBlue no + 60xx,1301 ROIArea no + 60xx,1302 ROIMean no + 60xx,1303 ROIStandardDeviation no + 60xx,1500 OverlayLabel no + 60xx,3000 OverlayData no + 60xx,4000 OverlayComments no + 7Fxx,0000 PixelDataGroupLength no + 7Fxx,0010 PixelData no + 7Fxx,0011 VariableNextDataGroup no + 7Fxx,0020 VariableCoefficientsSDVN no + 7Fxx,0030 VariableCoefficientsSDHN no + 7Fxx,0040 VariableCoefficientsSDDN no + FFFA,FFFA DigitalSignaturesSequence no + FFFC,FFFC DataSetTrailingPadding no + FFFE,E000 StartOfItem no + FFFE,E00D EndOfItems no + FFFE,E0DD EndOfSequence no + +=head2 FITS Tags + +This table lists some standard Flexible Image Transport System (FITS) tags, +but ExifTool will extract any other tags found. See +L<https://fits.gsfc.nasa.gov/fits_standard.html> for the specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'AUTHOR' Author no + 'BACKGRND' Background no + 'COMMENT' Comment no + 'DATE' CreateDate no + 'DATE-END' ObservationDateEnd no + 'DATE-OBS' ObservationDate no + 'HISTORY' History no + 'INSTRUME' Instrument no + 'OBJECT' Object no + 'OBSERVER' Observer no + 'REFERENC' Reference no + 'TELESCOP' Telescope no + 'TIME-END' ObservationTimeEnd no + 'TIME-OBS' ObservationTime no + +=head2 HTML Tags + +Meta information extracted from the header of HTML and XHTML files. This is +a mix of information found in the C<META> elements, C<XML> element, and the +C<TITLE> element. + + Tag ID Tag Name Writable + ------ -------- -------- + 'abstract' Abstract no + 'author' Author no + 'classification' Classification no + 'content-language' ContentLanguage no + 'copyright' Copyright no + 'dc' DC HTML dc + 'description' Description no + 'distribution' Distribution no + 'doc-class' DocClass no + 'doc-rights' DocRights no + 'doc-type' DocType no + 'formatter' Formatter no + 'generator' Generator no + 'generatorversion' GeneratorVersion no + 'googlebot' GoogleBot no + 'http-equiv' HTTP-equiv HTML equiv + 'keywords' Keywords no+ + 'mssmarttagspreventparsing' NoMSSmartTags no + 'ncc' NCC HTML ncc + 'o' Office HTML Office + 'originator' Originator no + 'owner' Owner no + 'prod' Prod HTML prod + 'progid' ProgID no + 'rating' Rating no + 'refresh' Refresh no + 'resource-type' ResourceType no + 'revisit-after' RevisitAfter no + 'robots' Robots no+ + 'title' Title no + 'vw96' VW96 HTML vw96 + +=head3 HTML dc Tags + +Dublin Core schema tags (also used in XMP). + + Tag ID Tag Name Writable + ------ -------- -------- + 'contributor' Contributor no+ + 'coverage' Coverage no + 'creator' Creator no+ + 'date' Date no+ + 'description' Description no + 'format' Format no + 'identifier' Identifier no + 'language' Language no+ + 'publisher' Publisher no+ + 'relation' Relation no+ + 'rights' Rights no + 'source' Source no + 'subject' Subject no+ + 'title' Title no + 'type' Type no+ + +=head3 HTML equiv Tags + +These tags have a family 1 group name of "HTTP-equiv". + + Tag ID Tag Name Writable + ------ -------- -------- + 'cache-control' CacheControl no + 'content-disposition' ContentDisposition no + 'content-language' ContentLanguage no + 'content-script-type' ContentScriptType no + 'content-style-type' ContentStyleType no + 'content-type' ContentType no + 'default-style' DefaultStyle no + 'expires' Expires no + 'ext-cache' ExtCache no + 'imagetoolbar' ImageToolbar no + 'lotus' Lotus no + 'page-enter' PageEnter no + 'page-exit' PageExit no + 'pics-label' PicsLabel no + 'pragma' Pragma no + 'refresh' Refresh no + 'reply-to' ReplyTo no + 'set-cookie' SetCookie no + 'site-enter' SiteEnter no + 'site-exit' SiteExit no + 'vary' Vary no + 'window-target' WindowTarget no + +=head3 HTML ncc Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'charset' CharacterSet no + 'depth' Depth no + 'files' Files no + 'footnotes' Footnotes no + 'generator' Generator no + 'kbytesize' KByteSize no + 'maxpagenormal' MaxPageNormal no + 'multimediatype' MultimediaType no + 'narrator' Narrator no + 'pagefront' PageFront no + 'pagenormal' PageNormal no + 'pagespecial' PageSpecial no + 'prodnotes' ProdNotes no + 'produceddate' ProducedDate no + 'producer' Producer no + 'revision' Revision no + 'revisiondate' RevisionDate no + 'setinfo' SetInfo no + 'sidebars' Sidebars no + 'sourcedate' SourceDate no + 'sourceedition' SourceEdition no + 'sourcepublisher' SourcePublisher no + 'sourcerights' SourceRights no + 'sourcetitle' SourceTitle no + 'tocitems' TOCItems no + 'totaltime' Duration no + +=head3 HTML Office Tags + +Tags written by Microsoft Office applications. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Author' Author no + 'Category' Category no + 'Characters' Characters no + 'CharactersWithSpaces' CharactersWithSpaces no + 'Company' Company no + 'Created' CreateDate no + 'Description' Description no + 'Keywords' Keywords no + 'LastAuthor' LastAuthor no + 'LastPrinted' LastPrinted no + 'LastSaved' ModifyDate no + 'Lines' Lines no + 'Manager' Manager no + 'Pages' Pages no + 'Paragraphs' Paragraphs no + 'Revision' RevisionNumber no + 'Subject' Subject no + 'Template' Template no + 'TotalTime' TotalEditTime no + 'Version' RevisionNumber no + 'Words' Words no + +=head3 HTML prod Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'recengineer' RecEngineer no + 'reclocation' RecLocation no + +=head3 HTML vw96 Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'objecttype' ObjectType no + +=head2 Palm Tags + +Information extracted from Palm database files (PDB and PRC extensions), +Mobipocket electronic books (MOBI), and Amazon Kindle KF7 and KF8 books (AZW +and AZW3). + + Index4 Tag Name Writable + ------ -------- -------- + 0 DatabaseName no + 9 CreateDate no + 10 ModifyDate no + 11 LastBackupDate no + 12 ModificationNumber no + 15 PalmFileType no + +=head3 Palm MOBI Tags + +Information extracted from the MOBI header of Mobipocket and Amazon Kindle +KF7 and KF8 files. + + Index4 Tag Name Writable + ------ -------- -------- + 0 Compression no + 1 UncompressedTextLength no + 3 Encryption no + 6 MobiType no + 7 CodePage no + 9 MobiVersion no + 21 BookName no + 26 MinimumVersion no + +=head3 Palm EXTH Tags + +Information extracted from the MOBI extended header. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0001 DRMServerID no + 0x0002 DRMCommerceID no + 0x0003 DRM_E-BookBaseID no + 0x0064 Author no + 0x0065 Publisher no + 0x0066 Imprint no + 0x0067 Description no + 0x0068 ISBN no + 0x0069 Subject no+ + 0x006a PublishDate no + 0x006b Review no + 0x006c Contributor no + 0x006d Rights no + 0x006e SubjectCode no + 0x006f BookType no + 0x0070 Source no + 0x0071 ASIN no + 0x0072 BookVersion no + 0x0073 SampleFlag no + 0x0074 StartReading no + 0x0075 Adult no + 0x0076 RetailPrice no + 0x0077 RetailPriceCurrency no + 0x007d ResourceCount no + 0x0081 KF8CoverURI no + 0x00c8 DictionaryShortName no + 0x00cc CreatorSoftware no + 0x00cd CreatorMajorVersion no + 0x00ce CreatorMinorVersion no + 0x00cf CreatorBuildNumber no + 0x00d0 Watermark no + 0x00d1 Tamper-proofKeys no + 0x0191 ClippingLimit no + 0x0192 PublisherLimit no + 0x0194 TextToSpeech no + 0x0195 RentalFlag no + 0x0196 RentalExpirationDate no + 0x01f5 CDEType no + 0x01f6 LastUpdateTime no + 0x01f7 UpdatedTitle no + 0x01f8 ASIN2 no + 0x020c Language no + 0x020d Alignment no + 0x0217 CreatorBuildNumber2 no + +=head2 Torrent Tags + +Below are tags commonly found in BitTorrent files. As well as these tags, +any other existing tags will be extracted. For convenience, list items are +expanded into individual tags with an index in the tag name, but only the +tags with index "1" are listed in the tables below. See +L<https://wiki.theory.org/BitTorrentSpecification> for the BitTorrent +specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'announce' Announce no + 'announce-list' AnnounceList1 no + 'comment' Comment no + 'created by' Creator no + 'creation date' CreateDate no + 'encoding' Encoding no + 'info' Info Torrent Info + 'url-list' URLList1 no + +=head3 Torrent Info Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'file-duration' File1Duration no + 'file-media' File1Media no + 'files' Files Torrent Files + 'length' Length no + 'md5sum' MD5Sum no + 'name' Name no + 'name.utf-8' NameUTF-8 no + 'piece length' PieceLength no + 'pieces' Pieces no + 'private' Private no + 'profiles' Profiles Torrent Profiles + +=head3 Torrent Files Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'length' File1Length no + 'md5sum' File1MD5Sum no + 'path' File1Path no + 'path.utf-8' File1PathUTF-8 no + +=head3 Torrent Profiles Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 'acodec' Profile1AudioCodec no + 'height' Profile1Height no + 'vcodec' Profile1VideoCodec no + 'width' Profile1Width no + +=head2 EXE Tags + +This module extracts information from various types of Windows, MacOS and +Unix executable and library files. The first table below lists information +extracted from the header of Windows PE (Portable Executable) EXE files and +DLL libraries. + + Index2 Tag Name Writable + ------ -------- -------- + 0 MachineType no + 2 TimeStamp no + 9 ImageFileCharacteristics no + 10 PEType no + 11 LinkerVersion no + 12 CodeSize no + 14 InitializedDataSize no + 16 UninitializedDataSize no + 18 EntryPoint no + 30 OSVersion no + 32 ImageVersion no + 34 SubsystemVersion no + 44 Subsystem no + +=head3 EXE PEVersion Tags + +Information extracted from the VS_VERSION_INFO structure of Windows PE +files. + + Index4 Tag Name Writable + ------ -------- -------- + 2 FileVersionNumber no + 4 ProductVersionNumber no + 6 FileFlagsMask no + 7 FileFlags no + 8 FileOS no + 9 ObjectFileType no + 10 FileSubtype no + +=head3 EXE PEString Tags + +Resource strings found in Windows PE files. The B<TagID>'s are not shown +because they are the same as the B<Tag Name>. ExifTool will extract any +existing StringFileInfo tags even if not listed in this table. + + Tag Name Writable + -------- -------- + BuildDate no + BuildVersion no + CharacterSet no + Comments no + CompanyName no + Copyright no + FileDescription no + FileVersion no + InternalName no + LanguageCode no + LegalCopyright no + LegalTrademarks no + OriginalFileName no + PrivateBuild no + ProductName no + ProductVersion no + SpecialBuild no + +=head3 EXE MachO Tags + +Information extracted from Mach-O (Mac OS X) executable files and DYLIB +libraries. + + Index Tag Name Writable + ----- -------- -------- + 0 CPUArchitecture no + 1 CPUByteOrder no + 2 CPUCount no + 3 CPUType no+ + 4 CPUSubtype no+ + 5 ObjectFileType no + 6 ObjectFlags no + +=head3 EXE PEF Tags + +Information extracted from PEF (Classic MacOS) executable files and +libraries. + + Index4 Tag Name Writable + ------ -------- -------- + 2 CPUArchitecture no + 3 PEFVersion no + 4 TimeStamp no + +=head3 EXE ELF Tags + +Information extracted from ELF (Unix) executable files and SO libraries. + + Index1 Tag Name Writable + ------ -------- -------- + 4 CPUArchitecture no + 5 CPUByteOrder no + 16 ObjectFileType no + 18 CPUType no + +=head3 EXE AR Tags + +Information extracted from static libraries. + + Index1 Tag Name Writable + ------ -------- -------- + 16 CreateDate no + +=head3 EXE CHM Tags + +Tags extracted from Microsoft Compiled HTML files. + + Index4 Tag Name Writable + ------ -------- -------- + 1 CHMVersion no + 5 LanguageCode no + +=head2 LNK Tags + +Information extracted from MS Shell Link (Windows shortcut) files. + + Index1 Tag Name Writable + ------ -------- -------- + 0x0014 Flags no + 0x0018 FileAttributes no + 0x001c CreateDate no + 0x0024 AccessDate no + 0x002c ModifyDate no + 0x0034 TargetFileSize no + 0x0038 IconIndex no + 0x003c RunWindow no + 0x0040 HotKey no + 0x10000 ItemID LNK ItemID + 0x20000 LinkInfo LNK LinkInfo + 0x30004 Description no + 0x30008 RelativePath no + 0x30010 WorkingDirectory no + 0x30020 CommandLineArguments no + 0x30040 IconFileName no + 0xa0000000 UnknownData LNK UnknownData + 0xa0000001 EnvVarData LNK UnknownData + 0xa0000002 ConsoleData LNK ConsoleData + 0xa0000003 TrackerData LNK TrackerData + 0xa0000004 ConsoleFEData LNK ConsoleFEData + 0xa0000005 SpecialFolderData LNK UnknownData + 0xa0000006 DarwinData LNK UnknownData + 0xa0000007 IconEnvData LNK UnknownData + 0xa0000008 ShimData LNK UnknownData + 0xa0000009 PropertyStoreData LNK UnknownData + 0xa000000b KnownFolderData LNK UnknownData + 0xa000000c VistaIDListData LNK UnknownData + +=head3 LNK ItemID Tags + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0032 Item0032 LNK Item0032 + +=head3 LNK Item0032 Tags + + Index1 Tag Name Writable + ------ -------- -------- + 14 TargetFileDOSName no + +=head3 LNK LinkInfo Tags + + Tag Name Writable + -------- -------- + CommonNetworkRelLink no + CommonPathSuffix no + DeviceName no + DriveSerialNumber no + DriveType no + LocalBasePath no + NetName no + NetProviderType no + VolumeID no + VolumeLabel no + +=head3 LNK UnknownData Tags + + Index1 Tag Name Writable + ------ -------- -------- + [no tags known] + +=head3 LNK ConsoleData Tags + + Index1 Tag Name Writable + ------ -------- -------- + 8 FillAttributes no + 10 PopupFillAttributes no + 12 ScreenBufferSize no + 16 WindowSize no + 20 WindowOrigin no + 32 FontSize no + 36 FontFamily no + 40 FontWeight no + 44 FontName no + 108 CursorSize no + 112 FullScreen no + 116 QuickEdit no + 120 InsertMode no + 124 WindowOriginAuto no + 128 HistoryBufferSize no + 132 NumHistoryBuffers no + 136 RemoveHistoryDuplicates no + +=head3 LNK TrackerData Tags + + Index1 Tag Name Writable + ------ -------- -------- + 16 MachineID no + +=head3 LNK ConsoleFEData Tags + + Index1 Tag Name Writable + ------ -------- -------- + 8 CodePage no + +=head2 Font Tags + +This table contains a collection of tags found in font files of various +formats. ExifTool current recognizes OTF, TTF, TTC, DFONT, PFA, PFB, PFM, +AFM, ACFM and AMFM font files. + + Tag ID Tag Name Writable + ------ -------- -------- + 'AFM' AFM Font AFM + 'PFM' PFMHeader Font PFM + 'PSInfo' PSFontInfo Font PSInfo + 'fontname' FontName no + 'name' Name Font Name + 'numfonts' NumFonts no + 'postfont' PostScriptFontName no + +=head3 Font AFM Tags + +Tags extracted from Adobe Font Metrics files (AFM, ACFM and AMFM). + + Tag ID Tag Name Writable + ------ -------- -------- + 'Ascender' Ascender no + 'CapHeight' CapHeight no + 'CharacterSet' CharacterSet no + 'Characters' Characters no + 'Creation Date' CreateDate no + 'Descender' Descender no + 'EncodingScheme' EncodingScheme no + 'EscChar' EscChar no + 'FamilyName' FontFamily no + 'FontName' FontName no + 'FullName' FullName no + 'IsBaseFont' IsBaseFont no + 'IsFixedV' IsFixedV no + 'MappingScheme' MappingScheme no + 'Notice' Notice no + 'Version' Version no + 'Weight' Weight no + 'XHeight' XHeight no + +=head3 Font PFM Tags + +Tags extracted from the PFM file header. + + Index1 Tag Name Writable + ------ -------- -------- + 0 PFMVersion no + 6 Copyright no + 66 FontType no + 68 PointSize no + 70 YResolution no + 72 XResolution no + 74 Ascent no + 76 InternalLeading no + 78 ExternalLeading no + 80 Italic no + 81 Underline no + 82 Strikeout no + 83 Weight no + 85 CharacterSet no + 86 PixWidth no + 88 PixHeight no + 90 PitchAndFamily no + 91 AvgWidth no + 93 MaxWidth no + 95 FirstChar no + 96 LastChar no + 97 DefaultChar no + 98 BreakChar no + 99 WidthBytes no + +=head3 Font PSInfo Tags + +Tags extracted from PostScript font files (PFA and PFB). + + Tag ID Tag Name Writable + ------ -------- -------- + 'Copyright' Copyright no + 'FSType' FSType no + 'FamilyName' FontFamily no + 'FontName' FontName no + 'FontType' FontType no + 'FullName' FullName no + 'ItalicAngle' ItalicAngle no + 'Notice' Notice no + 'UnderlinePosition' UnderlinePosition no + 'UnderlineThickness' UnderlineThickness no + 'Weight' Weight no + 'isFixedPitch' IsFixedPitch no + 'version' Version no + +=head3 Font Name Tags + +The following tags are extracted from the TrueType font "name" table found +in OTF, TTF, TTC and DFONT files. These tags support localized languages by +adding a hyphen followed by a language code to the end of the tag name (eg. +"Copyright-fr" or "License-en-US"). Tags with no language code use the +default language of "en". + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 Copyright no + 0x0001 FontFamily no + 0x0002 FontSubfamily no + 0x0003 FontSubfamilyID no + 0x0004 FontName no + 0x0005 NameTableVersion no + 0x0006 PostScriptFontName no + 0x0007 Trademark no + 0x0008 Manufacturer no + 0x0009 Designer no + 0x000a Description no + 0x000b VendorURL no + 0x000c DesignerURL no + 0x000d License no + 0x000e LicenseInfoURL no + 0x0010 PreferredFamily no + 0x0011 PreferredSubfamily no + 0x0012 CompatibleFontName no + 0x0013 SampleText no + 0x0014 PostScriptFontName no + 0x0015 WWSFamilyName no + 0x0016 WWSSubfamilyName no + +=head2 VCard Tags + +This table lists common vCard tags, but ExifTool will also extract any other +vCard tags found. Tag names may have "Pref" added to indicate the preferred +instance of a vCard property, and other "TYPE" parameters may also added to +the tag name. VCF files may contain multiple vCard entries which are +distinguished by the ExifTool family 3 group name (document number). See +L<http://tools.ietf.org/html/rfc6350> for the vCard 4.0 specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Adr' Address no + 'Anniversary' Anniversary no + 'Bday' Birthday no + 'Email' Email no + 'Fn' FormattedName no + 'Gender' Gender no + 'Geo' Geolocation no + 'Impp' IMPP no + 'Lang' Language no + 'Logo' Logo no + 'N' Name no + 'Nickname' Nickname no + 'Note' Note no + 'Org' Organization no + 'Photo' Photo no + 'Prodid' Software no + 'Rev' Revision no + 'Sound' Sound no + 'Tel' Telephone no + 'Title' JobTitle no + 'Tz' TimeZone no + 'Uid' UID no + 'Url' URL no + 'Version' VCardVersion no + 'X-abdate' ABDate no + 'X-ablabel' ABLabel no + 'X-abrelatednames' ABRelatedNames no + 'X-abuid' AB_UID no + 'X-aim' AIM no + 'X-icq' ICQ no + 'X-socialprofile' SocialProfile no + +=head3 VCard VCalendar Tags + +The VCard module is also used to process iCalendar ICS files since they use +a format similar to vCard. The following table lists standard iCalendar +tags, but any existing tags will be extracted. Top-level iCalendar +components (eg. Event, Todo, Timezone, etc.) are used for the family 1 group +names, and embedded components (eg. Alarm) are added as a prefix to the tag +name. See L<http://tools.ietf.org/html/rfc5545> for the official iCalendar +2.0 specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Acknowledged' Acknowledged no + 'Action' Action no + 'Attach' Attachment no + 'Attendee' Attendee no + 'Calscale' CalendarScale no + 'Categories' Categories no + 'Class' Classification no + 'Comment' Comment no + 'Completed' DateTimeCompleted no + 'Contact' Contact no + 'Created' DateCreated no + 'Description' Description no + 'Dtend' DateTimeEnd no + 'Dtstamp' DateTimeStamp no + 'Dtstart' DateTimeStart no + 'Due' DateTimeDue no + 'Duration' Duration no + 'Exdate' ExceptionDateTimes no + 'Freebusy' FreeBusyTime no + 'Geo' Geolocation no + 'Last-modified' ModifyDate no + 'Location' Location no + 'Method' Method no + 'Organizer' Organizer no + 'Percent-complete' PercentComplete no + 'Priority' Priority no + 'Prodid' Software no + 'Rdate' RecurrenceDateTimes no + 'Recurrence-id' RecurrenceID no + 'Related-to' RelatedTo no + 'Repeat' Repeat no + 'Request-status' RequestStatus no + 'Resources' Resources no + 'Rrule' RecurrenceRule no + 'Sequence' SequenceNumber no + 'Status' Status no + 'Summary' Summary no + 'Transp' TimeTransparency no + 'Trigger' Trigger no + 'Tzid' TimezoneID no + 'Tzname' TimezoneName no + 'Tzoffsetfrom' TimezoneOffsetFrom no + 'Tzoffsetto' TimezoneOffsetTo no + 'Tzurl' TimeZoneURL no + 'Uid' UID no + 'Url' URL no + 'Version' VCalendarVersion no + 'X-apple-calendar-color' CalendarColor no + 'X-apple-default-alarm' DefaultAlarm no + 'X-apple-local-default-alarm' + LocalDefaultAlarm no + 'X-microsoft-cdo-alldayevent' AllDayEvent no + 'X-microsoft-cdo-appt-sequence' + AppointmentSequence no + 'X-microsoft-cdo-busystatus' BusyStatus no + 'X-microsoft-cdo-importance' Importance no + 'X-microsoft-cdo-insttype' InstanceType no + 'X-microsoft-cdo-intendedstatus' + IntendedBusyStatus no + 'X-microsoft-cdo-ownerapptid' + OwnerAppointmentID no + 'X-microsoft-disallow-counter' + DisallowCounterProposal no + 'X-microsoft-donotforwardmeeting' + DoNotForwardMeeting no + 'X-microsoft-locations' MeetingLocations no + 'X-wr-alarmuid' AlarmUID no + 'X-wr-caldesc' CalendarDescription no + 'X-wr-calname' CalendarName no + 'X-wr-relcalid' CalendarID no + 'X-wr-timezone' TimeZone2 no + +=head3 VCard VNote Tags + +Tags extracted from V-Note VNT files. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Body' Body no + 'Dcreated' CreateDate no + 'Last-modified' ModifyDate no + 'Version' Version no + +=head2 Text Tags + +Although basic text files contain no metadata, the following tags are +determined from a simple analysis of the data in TXT and CSV files. +Statistics are generated only for 8-bit encodings, but the FastScan (-fast) +option may be used to limit processing to the first 64 kB in which case some +tags are not produced. To avoid long processing delays, ExifTool will issue +a minor warning and process only the first 64 kB of any file larger than 20 +MB unless the IgnoreMinorErrors (-m) +option is used. + + Tag Name Writable + -------- -------- + ByteOrderMark no + ColumnCount no + Delimiter no + LineCount no + MIMEEncoding no + Newlines no + Quoting no + RowCount no + WordCount no + +=head2 RSRC Tags + +Tags extracted from Mac OS resource files, DFONT files and "._" sidecar +files. These tags may also be extracted from the resource fork of any file +in OS X, either by adding "/..namedfork/rsrc" to the filename to process the +resource fork alone, or by using the ExtractEmbedded (-ee) option to process +the resource fork as a sub-document of the main file. When writing, +ExifTool preserves the Mac OS resource fork by default, but it may deleted +with C<-rsrc:all=> on the command line. + + Tag ID Tag Name Writable + ------ -------- -------- + '8BIM' PhotoshopInfo Photoshop + 'POST_0x01f5' PostscriptFont PostScript + 'STR _0xbff3' ApplicationMissingMsg no + 'STR _0xbff4' CreatorApplication no + 'STR#_0x0080' Keywords no + 'TEXT_0x0080' Description no + 'sfnt' Font Font Name + 'usro_0x0000' OpenWithApplication no + 'vers_0x0001' ApplicationVersion no + +=head2 Rawzor Tags + +Rawzor files store compressed images of other formats. As well as the +information listed below, exiftool uncompresses and extracts the meta +information from the original image. + + Tag Name Writable + -------- -------- + CompressionFactor no + OriginalFileSize no + OriginalFileType no + RawzorCreatorVersion no + RawzorRequiredVersion no + +=head2 ZIP Tags + +The following tags are extracted from ZIP archives. ExifTool also extracts +additional meta information from compressed documents inside some ZIP-based +files such Office Open XML (DOCX, PPTX and XLSX), Open Document (ODB, ODC, +ODF, ODG, ODI, ODP, ODS and ODT), iWork (KEY, PAGES, NUMBERS), Capture One +Enhanced Image Package (EIP), Adobe InDesign Markup Language (IDML), +Electronic Publication (EPUB), and Sketch design files (SKETCH). The +ExifTool family 3 groups may be used to organize ZIP tags by embedded +document number (ie. the exiftool C<-g3> option). + + Index2 Tag Name Writable + ------ -------- -------- + 2 ZipRequiredVersion no + 3 ZipBitFlag no + 4 ZipCompression no + 5 ZipModifyDate no + 7 ZipCRC no + 9 ZipCompressedSize no + 11 ZipUncompressedSize no + 15 ZipFileName no + '_com' ZipFileComment no + +=head3 ZIP GZIP Tags + +These tags are extracted from GZIP (GNU ZIP) archives, but currently only +for the first file in the archive. + + Index1 Tag Name Writable + ------ -------- -------- + 2 Compression no + 3 Flags no + 4 ModifyDate no + 8 ExtraFlags no + 9 OperatingSystem no + 10 ArchivedFileName no + 11 Comment no + +=head3 ZIP RAR Tags + +These tags are extracted from RAR archive files. + + Index1 Tag Name Writable + ------ -------- -------- + 0 CompressedSize no + 4 UncompressedSize no + 8 OperatingSystem no + 13 ModifyDate no + 18 PackingMethod no + 25 ArchivedFileName no + +=head3 ZIP RAR5 Tags + +These tags are extracted from RAR v5 and 7z archive files. + + Tag Name Writable + -------- -------- + ArchivedFileName no + CompressedSize no + FileVersion no + ModifyDate no + OperatingSystem no + UncompressedSize no + +=head2 RTF Tags + +This table lists standard tags of the RTF information group, but ExifTool +will also extract any non-standard tags found in this group. As well, +ExifTool will extract any custom properties that are found. See +L<http://www.microsoft.com/en-ca/download/details.aspx?id=10725> for the +specification. + + Tag ID Tag Name Writable + ------ -------- -------- + 'author' Author no + 'buptim' BackupTime no + 'category' Category no + 'comment' Comment no + 'company' Company no + 'copyright' Copyright no + 'creatim' CreateDate no + 'doccomm' Comments no + 'edmins' TotalEditTime no + 'hlinkbase' HyperlinkBase no + 'id' InternalIDNumber no + 'keywords' Keywords no + 'manager' Manager no + 'nofchars' Characters no + 'nofcharsws' CharactersWithSpaces no + 'nofpages' Pages no + 'nofwords' Words no + 'operator' LastModifiedBy no + 'printim' LastPrinted no + 'revtim' ModifyDate no + 'subject' Subject no + 'title' Title no + 'vern' InternalVersionNumber no + 'version' RevisionNumber no + +=head2 OOXML Tags + +The Office Open XML (OOXML) format was introduced with Microsoft Office 2007 +and is used by file types such as DOCX, PPTX and XLSX. These are +essentially ZIP archives containing XML files. The table below lists some +tags which have been observed in OOXML documents, but ExifTool will extract +any tags found from XML files of the OOXML document properties ("docProps") +directory. + +B<Tips:> + +1) Structural ZIP tags may be ignored (if desired) with C<--ZIP:all> on the +command line. + +2) Tags may be grouped by their document number in the ZIP archive with the +C<-g3> or C<-G3> option. + + Tag Name Writable + -------- -------- + AppVersion no + Application no + Category no + Characters no + CharactersWithSpaces no + CheckedBy no + Client no + Company no + CreateDate no + DateCompleted no + Department no + Destination no + Disposition no + Division no + DocSecurity no + DocumentNumber no + Editor no + ForwardTo no + Group no + HeadingPairs no + HiddenSlides no + HyperlinkBase no + HyperlinksChanged no + Keywords no + Language no + LastModifiedBy no + LastPrinted no + Lines no + LinksUpToDate no + MMClips no + Mailstop no + Manager no + Matter no + ModifyDate no + Notes no + Office no + Owner no + Pages no + Paragraphs no + PresentationFormat no + Project no + Publisher no + Purpose no + ReceivedFrom no + RecordedBy no + RecordedDate no + Reference no + RevisionNumber no + ScaleCrop no + SharedDoc no + Slides no + Source no + Status no + TelephoneNumber no + Template no + TitlesOfParts no + TotalEditTime no + Typist no + Words no + +=head2 iWork Tags + +The Apple iWork '09 file format is a ZIP archive containing XML files +similar to the Office Open XML (OOXML) format. Metadata tags in iWork +files are extracted even if they don't appear below. + + Tag Name Writable + -------- -------- + Author no + Comment no + Copyright no + Keywords no + Projects no+ + Title no + +=head2 ISO Tags + +Tags extracted from ISO 9660 disk images. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0000 BootRecord ISO BootRecord + 0x0001 PrimaryVolume ISO PrimaryVolume + +=head3 ISO BootRecord Tags + + Index1 Tag Name Writable + ------ -------- -------- + 7 BootSystem no + 39 BootIdentifier no + +=head3 ISO PrimaryVolume Tags + + Index1 Tag Name Writable + ------ -------- -------- + 8 System no + 40 VolumeName no + 80 VolumeBlockCount no + 120 VolumeSetDiskCount? no + 124 VolumeSetDiskNumber? no + 128 VolumeBlockSize no + 132 PathTableSize? no + 140 PathTableLocation? no + 174 RootDirectoryCreateDate no + 190 VolumeSetName no + 318 Publisher no + 446 DataPreparer no + 574 Software no + 702 CopyrightFileName no + 740 AbstractFileName no + 776 BibligraphicFileName no + 813 VolumeCreateDate no + 830 VolumeModifyDate no + 847 VolumeExpirationDate no + 864 VolumeEffectiveDate no + +=head2 MacOS Tags + +On MacOS systems, the there are additional MDItem and XAttr Finder tags that +may be extracted. These tags are not extracted by default -- they must be +specifically requested or enabled via an API option. (Except when reading +MacOS "._" files directly, see below.) + +The tables below list some of the tags that may be extracted, but ExifTool +will extract all available information even for tags not listed. + +Tags in these tables are referred to as "pseudo" tags because their +information is not stored in the file itself. As such, B<Writable> tags in +these tables may be changed without having to rewrite the file. + +Note that on some filesystems, MacOS creates sidecar files with names that +begin with "._". ExifTool will read these files if specified, and extract +the information listed in the following table without the need for extra +options, but these files are not writable directly. + + Tag ID Tag Name Writable + ------ -------- -------- + 0x0002 RSRC RSRC + 0x0009 ATTR MacOS XAttr + +=head3 MacOS XAttr Tags + +XAttr tags are extracted using the "xattr" utility. They are extracted if +any "XAttr*" tag or the MacOS group is specifically requested, or by setting +the API XAttrTags option to 1 or the API RequestAll option to 2 or higher. +And they are extracted by default from MacOS "._" files when reading +these files directly. + + Tag Name Writable + -------- -------- + XAttrAppleMailDateReceived no + XAttrAppleMailDateSent no + XAttrAppleMailIsRemoteAttachment no + XAttrFinderInfo no + XAttrLastUsedDate no + XAttrMDItemDownloadedDate no + XAttrMDItemFinderComment no + XAttrMDItemWhereFroms no + XAttrMDLabel no + XAttrQuarantine yes! + XAttrResourceFork no + +=head3 MacOS MDItem Tags + +MDItem tags are extracted using the "mdls" utility. They are extracted if +any "MDItem*" tag or the MacOS group is specifically requested, or by +setting the API MDItemTags option to 1 or the API RequestAll option to 2 or +higher. Note that these tags do not necessarily reflect the current +metadata of a file -- it may take some time for the MacOS mdworker daemon to +index the file after a metadata change. + + Tag Name Writable + -------- -------- + AppleMailDateReceived no + AppleMailDateSent no + AppleMailFlagged no + AppleMailIsRemoteAttachment no + AppleMailMessageID no + AppleMailPriority no + AppleMailRead no + AppleMailRepliedTo no + MDItemAccountHandles no + MDItemAccountIdentifier no + MDItemAcquisitionMake no + MDItemAcquisitionModel no + MDItemAltitude no + MDItemAperture no + MDItemAudioBitRate no + MDItemAudioChannelCount no + MDItemAuthorEmailAddresses no + MDItemAuthors no + MDItemBitsPerSample no + MDItemBundleIdentifier no + MDItemCity no + MDItemCodecs no + MDItemColorSpace no + MDItemComment no + MDItemContentCreationDate no + MDItemContentCreationDateRanking no + MDItemContentCreationDate_Ranking no + MDItemContentModificationDate no + MDItemContentType no + MDItemContentTypeTree no + MDItemContributors no + MDItemCopyright no + MDItemCountry no + MDItemCreator no + MDItemDateAdded no + MDItemDateAdded_Ranking no + MDItemDescription no + MDItemDisplayName no + MDItemDownloadedDate no + MDItemDurationSeconds no + MDItemEXIFGPSVersion no + MDItemEXIFVersion no + MDItemEmailConversationID no + MDItemEncodingApplications no + MDItemExposureMode no + MDItemExposureProgram no + MDItemExposureTimeSeconds no + MDItemFNumber no + MDItemFSContentChangeDate no + MDItemFSCreationDate yes! + MDItemFSCreatorCode no + MDItemFSFinderFlags no + MDItemFSHasCustomIcon no + MDItemFSInvisible no + MDItemFSIsExtensionHidden no + MDItemFSIsStationery no + MDItemFSLabel yes! + MDItemFSName no + MDItemFSNodeCount no + MDItemFSOwnerGroupID no + MDItemFSOwnerUserID no + MDItemFSSize no + MDItemFSTypeCode no + MDItemFinderComment yes! + MDItemFlashOnOff no + MDItemFocalLength no + MDItemGPSDateStamp no + MDItemGPSStatus no + MDItemGPSTrack no + MDItemHasAlphaChannel no + MDItemISOSpeed no + MDItemIdentifier no + MDItemImageDirection no + MDItemInterestingDateRanking no + MDItemInterestingDate_Ranking no + MDItemIsApplicationManaged no + MDItemIsExistingThread no + MDItemIsLikelyJunk no + MDItemKeywords no + MDItemKind no + MDItemLastUsedDate no + MDItemLastUsedDate_Ranking no + MDItemLatitude no + MDItemLensModel no + MDItemLogicalSize no + MDItemLongitude no + MDItemMailDateReceived_Ranking no + MDItemMailboxes no + MDItemMediaTypes no + MDItemNumberOfPages no + MDItemOrientation no + MDItemOriginApplicationIdentifier no + MDItemOriginMessageID no + MDItemOriginSenderDisplayName no + MDItemOriginSenderHandle no + MDItemOriginSubject no + MDItemPageHeight no + MDItemPageWidth no + MDItemPhysicalSize no + MDItemPixelCount no + MDItemPixelHeight no + MDItemPixelWidth no + MDItemPrimaryRecipientEmailAddresses no + MDItemProfileName no + MDItemRecipients no + MDItemRedEyeOnOff no + MDItemResolutionHeightDPI no + MDItemResolutionWidthDPI no + MDItemSecurityMethod no + MDItemSpeed no + MDItemStateOrProvince no + MDItemStreamable no + MDItemSubject no + MDItemTimestamp no + MDItemTitle no + MDItemTotalBitRate no + MDItemUseCount no + MDItemUsedDates no + MDItemUserDownloadedDate no + MDItemUserDownloadedUserHandle no + MDItemUserSharedReceivedDate no + MDItemUserSharedReceivedRecipient no + MDItemUserSharedReceivedRecipientHandle no + MDItemUserSharedReceivedSender no + MDItemUserSharedReceivedSenderHandle no + MDItemUserSharedReceivedTransport no + MDItemUserTags yes!+ + MDItemVersion no + MDItemVideoBitRate no + MDItemWhereFroms no + MDItemWhiteBalance no + +=head2 Extra Tags + +The extra tags provide extra features or extra information extracted or +generated by ExifTool that is not directly associated with another tag +group. The B<Group> column lists the family 1 group name when reading. +Tags with a "-" in this column are write-only. + +Tags in the family 1 "System" group are referred to as "pseudo" tags because +they don't represent real metadata in the file. Instead, this information +is stored in the directory structure of the filesystem. The B<Writable> +System "pseudo" tags in this table may be written without modifying the file +itself. The TestName tag is used for dry-run testing before writing +FileName. + + Tag Name Group Writable + -------- ----- -------- + Adobe Adobe yes! + BaseName System no + CanonDR4 CanonVRD yes!^ + CanonVRD CanonVRD yes!^ + Comment File yes + CurrentIPTCDigest File no + Directory System yes! + EXIF EXIF yes! + EmbeddedVideo File no + Error ExifTool no + ExifByteOrder File yes + ExifToolVersion ExifTool no + ExifUnicodeByteOrder - yes + FileAccessDate System no + FileAttributes System no + FileBlockCount System no + FileBlockSize System no + FileCreateDate System yes! + FileDeviceID System no + FileDeviceNumber System no + FileGroupID System yes! + FileHardLinks System no + FileInodeChangeDate System no + FileInodeNumber System no + FileModifyDate System yes! + FileName System yes! + FilePath System no + FilePermissions System yes! + FileSequence ExifTool no + FileSize System no + FileType File no + FileTypeExtension File no + FileUserID System yes! + ForceWrite - yes + Geosync - yes + Geotag - yes + Geotime - yes + HardLink - yes! + ICC_Profile ICC_Profile yes! + ID3Size File no + IPTC IPTC yes! + ImageDataHash File no + ImageHeight File no + ImageWidth File no + JPEGDigest File no + JPEGImageLength File no + JPEGQualityEstimate File no + MIMEType File no + MaxVal File no + NewGUID ExifTool no + Now ExifTool no + OtherImage File no + PageCount File no + PreviewImage File yes + PreviewPDF File no + PreviewPNG File no + PreviewTIFF File no + PreviewWMF File no + ProcessingTime ExifTool no + RAFVersion File no + ResourceForkSize System no + SphericalVideoXML GSpherical yes! + SymLink - yes! + TestName - yes! + ThumbnailImage File no + Trailer File yes! + Validate ExifTool no + Warning ExifTool no + XML XML no + XMP XMP yes! + XResolution File no + YResolution File no + ZoneIdentifier System yes! + +=head2 Composite Tags + +The values of the composite tags are B<Derived From> the values of other +tags. These are convenience tags which are calculated after all other +information is extracted. Only a few of these tags are writable directly, +the others are changed by writing the corresponding B<Derived From> tags. +User-defined Composite tags, also useful for custom-formatting of tag +values, may created via the ExifTool configuration file. + + Tag Name Derived From Writable + -------- ------------ -------- + AdvancedSceneMode Model no + SceneMode + AdvancedSceneType + Aperture FNumber no + ApertureValue + AudioBitrate MPEG:MPEGAudioVersion no + MPEG:SampleRate + MPEG:VBRBytes + MPEG:VBRFrames + AutoFocus Nikon:PhaseDetectAF no + Nikon:ContrastDetectAF + AvgBitrate QuickTime::MediaDataSize no + QuickTime::Duration + BlueBalance WB_RGGBLevels no + WB_RGBGLevels + WB_RBGGLevels + WB_GRBGLevels + WB_GRGBLevels + WB_GBRGLevels + WB_RGBLevels + WB_GRBLevels + WB_RBLevels + WBBlueLevel + WBGreenLevel + CDDBDiscPlayTime CDDB1Info no + CDDBDiscTracks CDDB1Info no + CFAPattern CFARepeatPatternDim no + CFAPattern2 + CircleOfConfusion ScaleFactor35efl no + ConditionalFEC FlashExposureComp no + FlashBits + DOF FocalLength no + Aperture + CircleOfConfusion + FocusDistance + SubjectDistance + ObjectDistance + ApproximateFocusDistance + FocusDistanceLower + FocusDistanceUpper + DateCreated Kodak:YearCreated no + Kodak:MonthDayCreated + DateTimeCreated IPTC:DateCreated no + IPTC:TimeCreated + DateTimeOriginal DateTimeCreated no + DateCreated + TimeCreated + DateTimeOriginal ID3:RecordingTime no + ID3:Year + ID3:Date + ID3:Time + DepthMapTiff DepthMapData no + DepthMapWidth + DepthMapHeight + DigitalCreationDateTime IPTC:DigitalCreationDate no + IPTC:DigitalCreationTime + DigitalZoom Canon:ZoomSourceWidth no + Canon:ZoomTargetWidth + Canon:DigitalZoom + DriveMode ContinuousDrive no + SelfTimer + Duration AIFF:SampleRate no + AIFF:NumSampleFrames + Duration APE:SampleRate no + APE:TotalFrames + APE:BlocksPerFrame + APE:FinalFrameBlocks + Duration FLAC:SampleRate no + FLAC:TotalSamples + Duration FileSize no + ID3Size + MPEG:AudioBitrate + MPEG:VideoBitrate + MPEG:VBRFrames + MPEG:SampleRate + MPEG:MPEGAudioVersion + Duration RIFF:FrameRate no + RIFF:FrameCount + VideoFrameRate + VideoFrameCount + Duration RIFF:AvgBytesPerSec no + FileSize + FrameCount + VideoFrameCount + Duration Vorbis:NominalBitrate no + FileSize + ExtenderStatus Olympus:Extender no + Olympus:LensType + MaxApertureValue + FOV FocalLength no + ScaleFactor35efl + FocusDistance + FileNumber DirectoryIndex yes + FileIndex + Flash XMP:FlashFired yes + XMP:FlashReturn + XMP:FlashMode + XMP:FlashFunction + XMP:FlashRedEyeMode + XMP:Flash + FlashType FlashBits no + FocalLength35efl FocalLength no + ScaleFactor35efl + FocusDistance Sony:FocusPosition no + FocalLength + FocusDistance2 Sony:FocusPosition2 no + FocalLengthIn35mmFormat + GPSAltitude GPS:GPSAltitude no + GPS:GPSAltitudeRef + XMP:GPSAltitude + XMP:GPSAltitudeRef + GPSAltitude QuickTime:GPSCoordinates no + GPSAltitude QuickTime:LocationInformation no + GPSAltitudeRef QuickTime:GPSCoordinates no + GPSAltitudeRef QuickTime:LocationInformation no + GPSDateTime GPS:GPSDateStamp no + GPS:GPSTimeStamp + GPSDateTime Parrot:GPSLatitude no + Main:CreateDate + SampleTime + GPSDateTime Sony:GPSDateStamp no + Sony:GPSTimeStamp + GPSDestLatitude GPS:GPSDestLatitude no + GPS:GPSDestLatitudeRef + GPSDestLatitudeRef XMP-exif:GPSDestLatitude no + GPSDestLongitude GPS:GPSDestLongitude no + GPS:GPSDestLongitudeRef + GPSDestLongitudeRef XMP-exif:GPSDestLongitude no + GPSLatitude GPS:GPSLatitude yes/ + GPS:GPSLatitudeRef + GPSLatitude QuickTime:GPSCoordinates no + GPSLatitude QuickTime:LocationInformation no + GPSLatitude Sony:GPSLatitude no + Sony:GPSLatitudeRef + GPSLatitudeRef XMP-exif:GPSLatitude no + GPSLongitude GPS:GPSLongitude yes/ + GPS:GPSLongitudeRef + GPSLongitude QuickTime:GPSCoordinates no + GPSLongitude QuickTime:LocationInformation no + GPSLongitude Sony:GPSLongitude no + Sony:GPSLongitudeRef + GPSLongitudeRef XMP-exif:GPSLongitude no + GPSPosition GPSLatitude yes! + GPSLongitude + HyperfocalDistance FocalLength no + Aperture + CircleOfConfusion + IDCPreviewImage IDCPreviewStart no + IDCPreviewLength + ISO Canon:CameraISO no + Canon:BaseISO + Canon:AutoISO + ImageHeight IFD0:SensorTopBorder no + IFD0:SensorBottomBorder + ImageHeight Main:PostScript:ImageData no + PostScript:BoundingBox + ImageSize ImageWidth no + ImageHeight + ExifImageWidth + ExifImageHeight + RawImageCroppedSize + ImageWidth IFD0:SensorLeftBorder no + IFD0:SensorRightBorder + ImageWidth Main:PostScript:ImageData no + PostScript:BoundingBox + JpgFromRaw JpgFromRawStart yes + JpgFromRawLength + Lens Canon:MinFocalLength no + Canon:MaxFocalLength + Lens35efl Canon:MinFocalLength no + Canon:MaxFocalLength + Lens + ScaleFactor35efl + LensID LensType no + FocalLength + MaxAperture + MaxApertureValue + MinFocalLength + MaxFocalLength + LensModel + LensFocalRange + LensSpec + LensType2 + LensType3 + LensFocalLength + RFLensType + LensID LensModel no + Lens + XMP-aux:LensID + Make + LensID Nikon:LensIDNumber no + LensFStops + MinFocalLength + MaxFocalLength + MaxApertureAtMinFocal + MaxApertureAtMaxFocal + MCUVersion + Nikon:LensType + LensID Ricoh:LensFirmware no + LensID XMP-aux:LensID no + Make + LensInfo + FocalLength + LensModel + MaxApertureValue + LensSpec Nikon:Lens no + Nikon:LensType + LensType LensTypeMake no + LensTypeModel + LightValue Aperture no + ShutterSpeed + ISO + MPImage MPImageStart no + MPImageLength + MPImageType + Megapixels ImageSize no + OriginalDecisionData OriginalDecisionDataOffset yes! + OtherImage OtherImageStart yes + OtherImageLength + OtherImageStart (1) + OtherImageLength (1) + PeakSpectralSensitivity FLIR:PlanckB no + PreviewImage PreviewImageStart yes + PreviewImageLength + PreviewImageValid + PreviewImageStart (1) + PreviewImageLength (1) + PreviewImage ScreenNail no + PreviewImageSize PreviewImageWidth no + PreviewImageHeight + RedBalance WB_RGGBLevels no + WB_RGBGLevels + WB_RBGGLevels + WB_GRBGLevels + WB_GRGBLevels + WB_GBRGLevels + WB_RGBLevels + WB_GRBLevels + WB_RBLevels + WBRedLevel + WBGreenLevel + RedEyeReduction CanonFlashMode no + FlashBits + RicohPitch Ricoh:Accelerometer no + RicohRoll Ricoh:Accelerometer no + Rotation QuickTime:MatrixStructure yes! + QuickTime:HandlerType + RunTimeSincePowerUp Apple:RunTimeValue no + Apple:RunTimeScale + ScaleFactor35efl FocalLength no + FocalLengthIn35mmFormat + Composite:DigitalZoom + FocalPlaneDiagonal + SensorSize + FocalPlaneXSize + FocalPlaneYSize + FocalPlaneResolutionUnit + FocalPlaneXResolution + FocalPlaneYResolution + ExifImageWidth + ExifImageHeight + CanonImageWidth + CanonImageHeight + ImageWidth + ImageHeight + ShootingMode CanonExposureMode no + EasyMode + BulbDuration + ShutterCurtainHack FlashBits no + ShutterCurtainSync + ShutterSpeed ExposureTime no + ShutterSpeedValue + BulbDuration + SingleShotDepthMapTiff SingleShotDepthMap no + SegWidth + SegHeight + SubSecCreateDate EXIF:CreateDate yes + SubSecTimeDigitized + OffsetTimeDigitized + SubSecDateTimeOriginal EXIF:DateTimeOriginal yes + SubSecTimeOriginal + OffsetTimeOriginal + SubSecModifyDate EXIF:ModifyDate yes + SubSecTime + OffsetTime + ThumbnailImage ThumbnailOffset yes + ThumbnailLength + ThumbnailTIFF SubfileType no + Compression + ImageWidth + ImageHeight + BitsPerSample + PhotometricInterpretation + StripOffsets + SamplesPerPixel + RowsPerStrip + StripByteCounts + PlanarConfiguration + Orientation + VolumeSize ISO:VolumeBlockCount no + ISO:VolumeBlockSize + WB_RGBLevels KDC_IFD:WhiteBalance no + WB_RGBLevelsAuto + WB_RGBLevelsFluorescent + WB_RGBLevelsTungsten + WB_RGBLevelsDaylight + WB_RGBLevels4 + WB_RGBLevels5 + WB_RGBLevelsShade + WB_RGBLevels KodakIFD:WhiteBalance no + WB_RGBMul0 + WB_RGBMul1 + WB_RGBMul2 + WB_RGBMul3 + WB_RGBCoeffs0 + WB_RGBCoeffs1 + WB_RGBCoeffs2 + WB_RGBCoeffs3 + KodakIFD:ColorTemperature + Kodak:WB_RGBLevels + WB_RGGBLevels Canon:WhiteBalance no + WB_RGGBLevelsAsShot + WB_RGGBLevelsAuto + WB_RGGBLevelsDaylight + WB_RGGBLevelsCloudy + WB_RGGBLevelsTungsten + WB_RGGBLevelsFluorescent + WB_RGGBLevelsFlash + WB_RGGBLevelsCustom + WB_RGGBLevelsShade + WB_RGGBLevelsKelvin + WB_RGGBLevels WB_RGGBLevelsUncorrected no + WB_RGGBLevelsBlack + ZoomedPreviewImage ZoomedPreviewStart no + ZoomedPreviewLength + +=head2 Shortcuts Tags + +Shortcut tags are convenience tags that represent one or more other tag +names. They are used like regular tags to read and write the information +for a specified set of tags. + +The shortcut tags below have been pre-defined, but user-defined shortcuts +may be added via the %Image::ExifTool::UserDefined::Shortcuts lookup in the +=/.ExifTool_config file. See the Image::ExifTool::Shortcuts documentation +for more details. + + Tag Name Refers To Writable + -------- --------- -------- + AllDates DateTimeOriginal yes + CreateDate + ModifyDate + Canon FileName yes + Model + DateTimeOriginal + ShootingMode + ShutterSpeed + Aperture + MeteringMode + ExposureCompensation + ISO + Lens + FocalLength + ImageSize + Quality + Flash + FlashType + ConditionalFEC + RedEyeReduction + ShutterCurtainHack + WhiteBalance + FocusMode + Contrast + Sharpness + Saturation + ColorTone + ColorSpace + LongExposureNoiseReduction + FileSize + FileNumber + DriveMode + OwnerName + SerialNumber + ColorSpaceTags ExifIFD:ColorSpace yes + ExifIFD:Gamma + InteropIFD:InteropIndex + ICC_Profile + Common FileName yes + FileSize + Model + DateTimeOriginal + ImageSize + Quality + FocalLength + ShutterSpeed + Aperture + ISO + WhiteBalance + Flash + CommonIFD0 IFD0:ImageDescription yes + IFD0:Make + IFD0:Model + IFD0:Software + IFD0:ModifyDate + IFD0:Artist + IFD0:Copyright + IFD0:Rating + IFD0:RatingPercent + IFD0:DNGLensInfo + IFD0:PanasonicTitle + IFD0:PanasonicTitle2 + IFD0:XPTitle + IFD0:XPComment + IFD0:XPAuthor + IFD0:XPKeywords + IFD0:XPSubject + ImageDataMD5 ImageDataHash yes + LargeTags CanonVRD yes + DLOData + EXIF + ICC_Profile + IDCPreviewImage + ImageData + IPTC + JpgFromRaw + OriginalRawImage + OtherImage + PreviewImage + ThumbnailImage + TIFFPreview + XML + XMP + ZoomedPreviewImage + MakerNotes MakerNotes yes + MakerNoteApple + MakerNoteCanon + MakerNoteCasio + MakerNoteCasio2 + MakerNoteDJI + MakerNoteDJIInfo + MakerNoteFLIR + MakerNoteFujiFilm + MakerNoteGE + MakerNoteGE2 + MakerNoteHasselblad + MakerNoteHP + MakerNoteHP2 + MakerNoteHP4 + MakerNoteHP6 + MakerNoteISL + MakerNoteJVC + MakerNoteJVCText + MakerNoteKodak1a + MakerNoteKodak1b + MakerNoteKodak2 + MakerNoteKodak3 + MakerNoteKodak4 + MakerNoteKodak5 + MakerNoteKodak6a + MakerNoteKodak6b + MakerNoteKodak7 + MakerNoteKodak8a + MakerNoteKodak8b + MakerNoteKodak8c + MakerNoteKodak9 + MakerNoteKodak10 + MakerNoteKodak11 + MakerNoteKodak12 + MakerNoteKodakUnknown + MakerNoteKyocera + MakerNoteMinolta + MakerNoteMinolta2 + MakerNoteMinolta3 + MakerNoteMotorola + MakerNoteNikon + MakerNoteNikon2 + MakerNoteNikon3 + MakerNoteNintendo + MakerNoteOlympus + MakerNoteOlympus2 + MakerNoteOlympus3 + MakerNoteLeica + MakerNoteLeica2 + MakerNoteLeica3 + MakerNoteLeica4 + MakerNoteLeica5 + MakerNoteLeica6 + MakerNoteLeica7 + MakerNoteLeica8 + MakerNoteLeica9 + MakerNoteLeica10 + MakerNotePanasonic + MakerNotePanasonic2 + MakerNotePanasonic3 + MakerNotePentax + MakerNotePentax2 + MakerNotePentax3 + MakerNotePentax4 + MakerNotePentax5 + MakerNotePentax6 + MakerNotePhaseOne + MakerNoteReconyx + MakerNoteReconyx2 + MakerNoteReconyx3 + MakerNoteRicoh + MakerNoteRicoh2 + MakerNoteRicohPentax + MakerNoteRicohText + MakerNoteSamsung1a + MakerNoteSamsung1b + MakerNoteSamsung2 + MakerNoteSanyo + MakerNoteSanyoC4 + MakerNoteSanyoPatch + MakerNoteSigma + MakerNoteSony + MakerNoteSony2 + MakerNoteSony3 + MakerNoteSony4 + MakerNoteSony5 + MakerNoteSonyEricsson + MakerNoteSonySRF + MakerNoteUnknownText + MakerNoteUnknownBinary + MakerNoteUnknown + Nikon Model yes + SubSecDateTimeOriginal + ShutterCount + LensSpec + FocalLength + ImageSize + ShutterSpeed + Aperture + ISO + NoiseReduction + ExposureProgram + ExposureCompensation + WhiteBalance + WhiteBalanceFineTune + ShootingMode + Quality + MeteringMode + FocusMode + ImageOptimization + ToneComp + ColorHue + ColorSpace + HueAdjustment + Saturation + Sharpness + Flash + FlashMode + FlashExposureComp + Unsafe IFD0:YCbCrPositioning yes + IFD0:YCbCrCoefficients + IFD0:TransferFunction + ExifIFD:ComponentsConfiguration + ExifIFD:CompressedBitsPerPixel + InteropIFD:InteropIndex + InteropIFD:InteropVersion + InteropIFD:RelatedImageWidth + InteropIFD:RelatedImageHeight + ls-l FilePermissions yes + FileHardLinks + FileUserID + FileGroupID + FileSize# + FileModifyDate + FileName + +=head2 MWG Tags + +The Metadata Working Group (MWG) recommends techniques to allow certain +overlapping EXIF, IPTC and XMP tags to be reconciled when reading, and +synchronized when writing. The MWG Composite tags below are designed to aid +in the implementation of these recommendations. As well, the MWG defines +new XMP tags which are listed in the subsequent tables below. See +L<https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf> +for the official MWG specification. + +=head3 MWG Composite Tags + +The table below lists special Composite tags which are used to access other +tags based on the MWG 2.0 recommendations. These tags are only accessible +when explicitly loaded, but this is done automatically by the exiftool +application if MWG is specified as a group for any tag on the command line, +or manually with the C<-use MWG> option. Via the API, the MWG Composite +tags are loaded by calling "C<Image::ExifTool::MWG::Load()>". + +When reading, the value of each MWG tag is B<Derived From> the specified +tags based on the MWG guidelines. When writing, the appropriate associated +tags are written. The value of the IPTCDigest tag is updated automatically +when the IPTC is changed if either the IPTCDigest tag didn't exist +beforehand or its value agreed with the original IPTC digest (indicating +that the XMP is synchronized with the IPTC). IPTC information is written +only if the original file contained IPTC. + +Loading the MWG module activates "strict MWG conformance mode", which has +the effect of causing EXIF, IPTC and XMP in non-standard locations to be +ignored when reading, as per the MWG recommendations. Instead, a "Warning" +tag is generated when non-standard metadata is encountered. This feature +may be disabled by setting C<$Image::ExifTool::MWG::strict = 0> in the +ExifTool config file (or from your Perl script when using the API). Note +that the behaviour when writing is not changed: ExifTool always creates new +records only in the standard location, but writes new tags to any +EXIF/IPTC/XMP records that exist. + +Contrary to the EXIF specification, the MWG recommends that EXIF "ASCII" +string values be stored as UTF-8. To honour this, the exiftool application +sets the default internal EXIF string encoding to "UTF8" when the MWG module +is loaded, but via the API this must be done manually by setting the +CharsetEXIF option. + +A complication of the MWG specification is that although the MWG:Creator +property may consist of multiple values, the associated EXIF tag +(EXIF:Artist) is only a simple string. To resolve this discrepancy the MWG +recommends a technique which allows a list of values to be stored in a +string by using a semicolon-space separator (with quotes around values if +necessary). When the MWG module is loaded, ExifTool automatically +implements this policy and changes EXIF:Artist to a list-type tag. + + Tag Name Derived From Writable + -------- ------------ -------- + City IPTC:City yes + XMP-photoshop:City + XMP-iptcExt:LocationShownCity + CurrentIPTCDigest + IPTCDigest + Copyright EXIF:Copyright yes + IPTC:CopyrightNotice + XMP-dc:Rights + CurrentIPTCDigest + IPTCDigest + Country IPTC:Country-PrimaryLocationName yes + XMP-photoshop:Country + XMP-iptcExt:LocationShownCountryName + CurrentIPTCDigest + IPTCDigest + CreateDate Composite:SubSecCreateDate yes + EXIF:CreateDate + IPTC:DigitalCreationDate + IPTC:DigitalCreationTime + XMP-xmp:CreateDate + CurrentIPTCDigest + IPTCDigest + Creator EXIF:Artist yes+ + IPTC:By-line + XMP-dc:Creator + CurrentIPTCDigest + IPTCDigest + DateTimeOriginal Composite:SubSecDateTimeOriginal yes + EXIF:DateTimeOriginal + IPTC:DateCreated + IPTC:TimeCreated + XMP-photoshop:DateCreated + CurrentIPTCDigest + IPTCDigest + Description EXIF:ImageDescription yes + IPTC:Caption-Abstract + XMP-dc:Description + CurrentIPTCDigest + IPTCDigest + Keywords IPTC:Keywords yes+ + XMP-dc:Subject + CurrentIPTCDigest + IPTCDigest + Location IPTC:Sub-location yes + XMP-iptcCore:Location + XMP-iptcExt:LocationShownSublocation + CurrentIPTCDigest + IPTCDigest + ModifyDate Composite:SubSecModifyDate yes + EXIF:ModifyDate + XMP-xmp:ModifyDate + CurrentIPTCDigest + IPTCDigest + Orientation EXIF:Orientation yes + Rating XMP-xmp:Rating yes + State IPTC:Province-State yes + XMP-photoshop:State + XMP-iptcExt:LocationShownProvinceState + CurrentIPTCDigest + IPTCDigest + +=head3 MWG Regions Tags + +Image region metadata defined by the MWG 2.0 specification. These tags +may be accessed without the need to load the MWG Composite tags above. See +L<https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf> +for the official specification. + +These tags belong to the ExifTool XMP-mwg-rs family 1 group. + + Tag Name Writable + -------- -------- + RegionInfo MWG RegionInfo Struct + RegionAppliedToDimensions Dimensions Struct_ + RegionAppliedToDimensionsH real_ + RegionAppliedToDimensionsUnit string_ + RegionAppliedToDimensionsW real_ + RegionList MWG RegionStruct Struct_+ + RegionArea Area Struct_+ + RegionAreaD real_+ + RegionAreaH real_+ + RegionAreaUnit string_+ + RegionAreaW real_+ + RegionAreaX real_+ + RegionAreaY real_+ + RegionBarCodeValue string_+ + RegionDescription string_+ + RegionExtensions MWG Extensions Struct_+ + RegionFocusUsage string_+ + RegionName string_+ + RegionRotation real_+ + RegionSeeAlso string_+ + RegionType string_+ + +=head3 MWG RegionInfo Struct + + Field Name Writable + ---------- -------- + AppliedToDimensions Dimensions Struct + RegionList MWG RegionStruct Struct+ + +=head3 MWG RegionStruct Struct + + Field Name Writable + ---------- -------- + Area Area Struct + BarCodeValue string + Description string + Extensions MWG Extensions Struct + FocusUsage string + Name string + Rotation real + Type string + SeeAlso string + +=head3 MWG Extensions Struct + +This structure may contain any top-level XMP tags, but none have been +pre-defined in ExifTool. Since no flattened tags have been pre-defined, +RegionExtensions is writable only as a structure (eg. +C<{xmp-dc:creator=me,rating=5}>). Fields for this structure are identified +using the standard ExifTool tag name (with optional leading group name, +and/or trailing language code, and/or trailing C<#> symbol to disable print +conversion). + + Field Name Writable + ---------- -------- + [no tags known] + +=head3 MWG Keywords Tags + +Hierarchical keywords metadata defined by the MWG 2.0 specification. +ExifTool unrolls keyword structures to an arbitrary depth of 6 to allow +individual levels to be accessed with different tag names, and to avoid +infinite recursion. See +L<https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf> +for the official specification. + +These tags belong to the ExifTool XMP-mwg-kw family 1 group. + + Tag Name Writable + -------- -------- + KeywordInfo MWG KeywordInfo Struct + HierarchicalKeywords MWG KeywordStruct Struct_+ + HierarchicalKeywords1Applied boolean_+ + HierarchicalKeywords1Children MWG KeywordStruct Struct_+ + HierarchicalKeywords2Applied boolean_+ + HierarchicalKeywords2Children MWG KeywordStruct Struct_+ + HierarchicalKeywords3Applied boolean_+ + HierarchicalKeywords3Children MWG KeywordStruct Struct_+ + HierarchicalKeywords4Applied boolean_+ + HierarchicalKeywords4Children MWG KeywordStruct Struct_+ + HierarchicalKeywords5Applied boolean_+ + HierarchicalKeywords5Children MWG KeywordStruct Struct_+ + HierarchicalKeywords6Applied boolean_+ + HierarchicalKeywords6 string_+ + HierarchicalKeywords5 string_+ + HierarchicalKeywords4 string_+ + HierarchicalKeywords3 string_+ + HierarchicalKeywords2 string_+ + HierarchicalKeywords1 string_+ + +=head3 MWG KeywordInfo Struct + + Field Name Writable + ---------- -------- + Hierarchy MWG KeywordStruct Struct+ + +=head3 MWG KeywordStruct Struct + + Field Name Writable + ---------- -------- + Applied boolean + Children MWG KeywordStruct Struct+ + Keyword string + +=head3 MWG Collections Tags + +Collections metadata defined by the MWG 2.0 specification. See +L<https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf> +for the official specification. + +These tags belong to the ExifTool XMP-mwg-coll family 1 group. + + Tag Name Writable + -------- -------- + Collections MWG CollectionInfo Struct+ + CollectionName string_+ + CollectionURI string_+ + +=head3 MWG CollectionInfo Struct + + Field Name Writable + ---------- -------- + CollectionName string + CollectionURI string + +=head1 NOTES + +This document generated automatically by +L<Image::ExifTool::BuildTagLookup|Image::ExifTool::BuildTagLookup>. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Text.pm b/ExifTool/lib/Image/ExifTool/Text.pm new file mode 100644 index 0000000..1a069b2 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Text.pm @@ -0,0 +1,244 @@ +#------------------------------------------------------------------------------ +# File: Text.pm +# +# Description: Deduce characteristics of TXT and CSV files +# +# Revisions: 2019-11-01 - P. Harvey Created +# 2020-02-13 - PH Added CSV file support +# +# References: 1) https://github.com/file/file +#------------------------------------------------------------------------------ + +package Image::ExifTool::Text; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.04'; + +# Text tags +%Image::ExifTool::Text::Main = ( + VARS => { NO_ID => 1 }, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Document' }, + NOTES => q{ + Although basic text files contain no metadata, the following tags are + determined from a simple analysis of the data in TXT and CSV files. + Statistics are generated only for 8-bit encodings, but the L<FastScan|../ExifTool.html#FastScan> (-fast) + option may be used to limit processing to the first 64 kB in which case some + tags are not produced. To avoid long processing delays, ExifTool will issue + a minor warning and process only the first 64 kB of any file larger than 20 + MB unless the L<IgnoreMinorErrors|../ExifTool.html#IgnoreMinorErrors> (-m) + option is used. + }, + MIMEEncoding => { Groups => { 2 => 'Other' } }, + Newlines => { + PrintConv => { + "\r\n" => 'Windows CRLF', + "\r" => 'Macintosh CR', + "\n" => 'Unix LF', + '' => '(none)', + }, + }, + ByteOrderMark => { PrintConv => { 0 => 'No', 1 => 'Yes' } }, + LineCount => { }, + WordCount => { }, + Delimiter => { PrintConv => { '' => '(none)', ',' => 'Comma', ';' => 'Semicolon', "\t" => 'Tab' }}, + Quoting => { PrintConv => { '' => '(none)', '"' => 'Double quotes', "'" => 'Single quotes' }}, + RowCount => { }, + ColumnCount => { }, +); + +#------------------------------------------------------------------------------ +# Extract some stats from a text file +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a text file +sub ProcessTXT($$) +{ + my ($et, $dirInfo) = @_; + my $dataPt = $$dirInfo{TestBuff}; + my $raf = $$dirInfo{RAF}; + my $fast = $et->Options('FastScan') || 0; + my ($buff, $enc, $isBOM, $isUTF8); + my $nl = ''; + + return 0 unless length $$dataPt; # can't call it a text file if it has no text + + # read more from the file if necessary + if ($fast < 3 and length($$dataPt) == $Image::ExifTool::testLen) { + $raf->Read($buff, 65536) or return 0; + $dataPt = \$buff; + } +# +# make our best guess at the character encoding (EBCDIC is not supported) +# + if ($$dataPt =~ /([\0-\x06\x0e-\x1a\x1c-\x1f\x7f])/) { + # file contains weird control characters, could be multi-byte Unicode + if ($$dataPt =~ /^(\xff\xfe\0\0|\0\0\xfe\xff)/) { + if ($1 eq "\xff\xfe\0\0") { + $enc = 'utf-32le'; + $nl = $1 if $$dataPt =~ /(\r\0\0\0\n|\r|\n)\0\0\0/; + } else { + $enc = 'utf-32be'; + $nl = $1 if $$dataPt =~ /\0\0\0(\r\0\0\0\n|\r|\n)/; + } + } elsif ($$dataPt =~ /^(\xff\xfe|\xfe\xff)/) { + if ($1 eq "\xff\xfe") { + $enc = 'utf-16le'; + $nl = $1 if $$dataPt =~ /(\r\0\n|\r|\n)\0/; + } else { + $enc = 'utf-16be'; + $nl = $1 if $$dataPt =~ /\0(\r\0\n|\r|\n)/; + } + } else { + return 0; # probably not a text file + } + $nl =~ tr/\0//d; # remove nulls from newline sequence + $isBOM = 1; # (we don't recognize UTF-16/UTF-32 without one) + } else { + $isUTF8 = Image::ExifTool::IsUTF8($dataPt, 1); + if ($isUTF8 == 0) { + $enc = 'us-ascii'; + } elsif ($isUTF8 > 0) { + $enc = 'utf-8'; + $isBOM = ($$dataPt =~ /^\xef\xbb\xbf/ ? 1 : 0); + } elsif ($$dataPt !~ /[\x80-\x9f]/) { + $enc = 'iso-8859-1'; + } else { + $enc = 'unknown-8bit'; + } + $nl = $1 if $$dataPt =~ /(\r\n|\r|\n)/; + } + + my $tagTablePtr = GetTagTable('Image::ExifTool::Text::Main'); + + $et->SetFileType(); + $et->HandleTag($tagTablePtr, MIMEEncoding => $enc); + + return 1 if $fast == 3 or not $raf->Seek(0,0); + + $et->HandleTag($tagTablePtr, ByteOrderMark => $isBOM) if defined $isBOM; + $et->HandleTag($tagTablePtr, Newlines => $nl); + + return 1 if $fast or not defined $isUTF8; +# +# generate stats for CSV files +# + if ($$et{FileType} eq 'CSV') { + my ($delim, $quot, $ncols); + my $nrows = 0; + while ($raf->ReadLine($buff)) { + if (not defined $delim) { + my %count = ( ',' => 0, ';' => 0, "\t" => 0 ); + ++$count{$_} foreach $buff =~ /[,;\t]/g; + if ($count{','} > $count{';'} and $count{','} > $count{"\t"}) { + $delim = ','; + } elsif ($count{';'} > $count{"\t"}) { + $delim = ';'; + } elsif ($count{"\t"}) { + $delim = "\t"; + } else { + $delim = ''; + $ncols = 1; + } + unless ($ncols) { + # account for delimiters in quotes (simplistically) + while ($buff =~ /(^|$delim)(["'])(.*?)\2(?=$delim|$)/sg) { + $quot = $2; + my $field = $3; + $count{$delim} -= () = $field =~ /$delim/g; + } + $ncols = $count{$delim} + 1; + } + } elsif (not $quot) { + $quot = $2 if $buff =~ /(^|$delim)(["'])(.*?)\2(?=$delim|$)/sg; + } + if (++$nrows == 1000 and $et->Warn('Not counting rows past 1000', 2)) { + undef $nrows; + last; + } + } + $et->HandleTag($tagTablePtr, Delimiter => ($delim || '')); + $et->HandleTag($tagTablePtr, Quoting => ($quot || '')); + $et->HandleTag($tagTablePtr, ColumnCount => $ncols); + $et->HandleTag($tagTablePtr, RowCount => $nrows) if $nrows; + return 1; + } + return 1 if $$et{VALUE}{FileSize} and $$et{VALUE}{FileSize} > 20000000 and + $et->Warn('Not counting lines/words in text file larger than 20 MB', 2); +# +# count lines/words and check encoding of the rest of the file +# + my ($lines, $words) = (0, 0); + my $oldNL = $/; + $/ = $nl if $nl; + while ($raf->ReadLine($buff)) { + ++$lines; + ++$words while $buff =~ /\S+/g; + if (not $nl and $buff =~ /(\r\n|\r|\n)$/) { + # (the first line must have been longer than 64 kB) + $$et{VALUE}{Newlines} = $nl = $1; + } + next if $raf->Tell() < 65536; + # continue to check encoding after the first 64 kB + if ($isUTF8 >= 0) { # (if ascii or utf8) + $isUTF8 = Image::ExifTool::IsUTF8(\$buff); + if ($isUTF8 > 0) { + $enc = 'utf-8'; + } elsif ($isUTF8 < 0) { + $enc = $buff =~ /[\x80-\x9f]/ ? 'unknown-8bit' : 'iso-8859-1'; + } + } elsif ($enc eq 'iso-8859-1' and $buff =~ /[\x80-\x9f]/) { + $enc = 'unknown-8bit'; + } + } + if ($$et{VALUE}{MIMEEncoding} ne $enc) { + $$et{VALUE}{MIMEEncoding} = $enc; + $et->VPrint(0," MIMEEncoding [override] = $enc\n"); + } + $/ = $oldNL; + $et->HandleTag($tagTablePtr, LineCount => $lines); + $et->HandleTag($tagTablePtr, WordCount => $words); + return 1; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Text - Read Text meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to deduce some +characteristics of TXT and CSV files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://github.com/file/file> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Text Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Theora.pm b/ExifTool/lib/Image/ExifTool/Theora.pm new file mode 100644 index 0000000..fc1c79f --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Theora.pm @@ -0,0 +1,145 @@ +#------------------------------------------------------------------------------ +# File: Theora.pm +# +# Description: Read Theora video meta information +# +# Revisions: 2011/07/13 - P. Harvey Created +# +# References: 1) http://www.theora.org/doc/Theora.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::Theora; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.00'; + +# Theora header types +%Image::ExifTool::Theora::Main = ( + NOTES => q{ + Information extracted from Ogg Theora video files. See + L<http://www.theora.org/doc/Theora.pdf> for the Theora specification. + }, + 0x80 => { + Name => 'Identification', + SubDirectory => { + TagTable => 'Image::ExifTool::Theora::Identification', + ByteOrder => 'BigEndian', + }, + }, + 0x81 => { + Name => 'Comments', + SubDirectory => { + TagTable => 'Image::ExifTool::Vorbis::Comments', + }, + }, + # 0x82 - Setup +); + +# tags extracted from Theora Idenfication header +%Image::ExifTool::Theora::Identification = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Video' }, + NOTES => 'Tags extracted from the Theora identification header.', + 0 => { + Name => 'TheoraVersion', + Format => 'int8u[3]', + PrintConv => '$val =~ tr/ /./; $val', + }, + 7 => { + Name => 'ImageWidth', + Format => 'int32u', + ValueConv => '$val >> 8', + }, + 10 => { + Name => 'ImageHeight', + Format => 'int32u', + ValueConv => '$val >> 8', + }, + 13 => 'XOffset', + 14 => 'YOffset', + 15 => { + Name => 'FrameRate', + Format => 'rational64u', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + 23 => { + Name => 'PixelAspectRatio', + Format => 'int16u[3]', + ValueConv => 'my @a=split(" ",$val); (($a[0]<<8)+($a[1]>>8)) / ((($a[1]&0xff)<<8)+$a[2])', + PrintConv => 'int($val * 1000 + 0.5) / 1000', + }, + 29 => { + Name => 'ColorSpace', + PrintConv => { + 0 => 'Undefined', + 1 => 'Rec. 470M', + 2 => 'Rec. 470BG', + }, + }, + 30 => { + Name => 'NominalVideoBitrate', + Format => 'int32u', + ValueConv => '$val >> 8', + PrintConv => { + 0 => 'Unspecified', + OTHER => \&Image::ExifTool::ConvertBitrate, + }, + }, + 33 => { + Name => 'Quality', + ValueConv => '$val >> 2', + }, + 34 => { + Name => 'PixelFormat', + ValueConv => '($val >> 3) & 0x3', + PrintConv => { + 0 => '4:2:0', + 2 => '4:2:2', + 3 => '4:4:4', + }, + }, +); + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Theora - Read Theora video meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Theora video streams. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.theora.org/doc/Theora.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Theora Tags>, +L<Image::ExifTool::TagNames/Ogg Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Torrent.pm b/ExifTool/lib/Image/ExifTool/Torrent.pm new file mode 100644 index 0000000..e6a0890 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Torrent.pm @@ -0,0 +1,331 @@ +#------------------------------------------------------------------------------ +# File: Torrent.pm +# +# Description: Read information from BitTorrent file +# +# Revisions: 2013/08/27 - P. Harvey Created +# +# References: 1) https://wiki.theory.org/BitTorrentSpecification +#------------------------------------------------------------------------------ + +package Image::ExifTool::Torrent; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.06'; + +sub ReadBencode($$$); +sub ExtractTags($$$;$$@); + +# tags extracted from BitTorrent files +%Image::ExifTool::Torrent::Main = ( + GROUPS => { 2 => 'Document' }, + NOTES => q{ + Below are tags commonly found in BitTorrent files. As well as these tags, + any other existing tags will be extracted. For convenience, list items are + expanded into individual tags with an index in the tag name, but only the + tags with index "1" are listed in the tables below. See + L<https://wiki.theory.org/BitTorrentSpecification> for the BitTorrent + specification. + }, + 'announce' => { }, + 'announce-list' => { Name => 'AnnounceList1' }, + 'comment' => { }, + 'created by' => { Name => 'Creator' }, # software used to create the torrent + 'creation date' => { + Name => 'CreateDate', + Groups => { 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 'encoding' => { }, + 'info' => { + SubDirectory => { TagTable => 'Image::ExifTool::Torrent::Info' }, + Notes => 'extracted as a structure with the Struct option', + }, + 'url-list' => { Name => 'URLList1' }, +); + +%Image::ExifTool::Torrent::Info = ( + GROUPS => { 2 => 'Document' }, + 'file-duration' => { Name => 'File1Duration' }, + 'file-media' => { Name => 'File1Media' }, + 'files' => { SubDirectory => { TagTable => 'Image::ExifTool::Torrent::Files' } }, + 'length' => { }, + 'md5sum' => { Name => 'MD5Sum' }, + 'name' => { }, + 'name.utf-8' => { Name => 'NameUTF-8' }, + 'piece length' => { Name => 'PieceLength' }, + 'pieces' => { + Name => 'Pieces', + Notes => 'concatenation of 20-byte SHA-1 digests for each piece', + }, + 'private' => { }, + 'profiles' => { SubDirectory => { TagTable => 'Image::ExifTool::Torrent::Profiles' } }, +); + +%Image::ExifTool::Torrent::Profiles = ( + GROUPS => { 2 => 'Document' }, + 'width' => { Name => 'Profile1Width' }, + 'height' => { Name => 'Profile1Height' }, + 'acodec' => { Name => 'Profile1AudioCodec' }, + 'vcodec' => { Name => 'Profile1VideoCodec' }, +); + +%Image::ExifTool::Torrent::Files = ( + GROUPS => { 2 => 'Document' }, + 'length' => { Name => 'File1Length', PrintConv => 'ConvertFileSize($val)' }, + 'md5sum' => { Name => 'File1MD5Sum' }, + 'path' => { Name => 'File1Path', JoinPath => 1 }, + 'path.utf-8' => { Name => 'File1PathUTF-8', JoinPath => 1 }, +); + +#------------------------------------------------------------------------------ +# Read 64kB more data into buffer +# Inputs: 0) RAF ref, 1) buffer ref +# Returns: number of bytes read +# Notes: Sets BencodeEOF element of RAF on end of file +sub ReadMore($$) +{ + my ($raf, $dataPt) = @_; + my $buf2; + my $n = $raf->Read($buf2, 65536); + $$raf{BencodeEOF} = 1 if $n != 65536; + $$dataPt = substr($$dataPt, pos($$dataPt)) . $buf2 if $n; + return $n; +} + +#------------------------------------------------------------------------------ +# Read bencoded value +# Inputs: 0) ExifTool ref, 1) input file, 2) buffer (pos must be set to current position) +# Returns: HASH ref, ARRAY ref, SCALAR ref, SCALAR, or undef on error or end of data +# Notes: Sets BencodeError element of RAF on any error +sub ReadBencode($$$) +{ + my ($et, $raf, $dataPt) = @_; + + # read more if necessary (keep a minimum of 64 bytes in the buffer) + my $pos = pos($$dataPt); + return undef unless defined $pos; + my $remaining = length($$dataPt) - $pos; + ReadMore($raf, $dataPt) if $remaining < 64 and not $$raf{BencodeEOF}; + + # read next token + $$dataPt =~ /(.)/sg or return undef; + + my $val; + my $tok = $1; + if ($tok eq 'i') { # integer + $$dataPt =~ /\G(-?\d+)e/g or return $val; + $val = $1; + } elsif ($tok eq 'd') { # dictionary + $val = { }; + for (;;) { + my $k = ReadBencode($et, $raf, $dataPt); + last unless defined $k; + # the key must be a byte string + if (ref $k) { + ref $k ne 'SCALAR' and $$raf{BencodeError} = 'Bad dictionary key', last; + $k = $$k; + } + my $v = ReadBencode($et, $raf, $dataPt); + last unless defined $v; + $$val{$k} = $v; + } + } elsif ($tok eq 'l') { # list + $val = [ ]; + for (;;) { + my $v = ReadBencode($et, $raf, $dataPt); + last unless defined $v; + push @$val, $v; + } + } elsif ($tok eq 'e') { # end of dictionary or list + # return undef (no error) + } elsif ($tok =~ /^\d$/ and $$dataPt =~ /\G(\d*):/g) { # byte string + my $len = $tok . $1; + my $more = $len - (length($$dataPt) - pos($$dataPt)); + my $value; + if ($more <= 0) { + $value = substr($$dataPt,pos($$dataPt),$len); + pos($$dataPt) = pos($$dataPt) + $len; + } elsif ($more > 10000000) { + # just skip over really long values + $val = \ "(Binary data $len bytes)" if $raf->Seek($more, 1); + } else { + # need to read more from file + my $buff; + my $n = $raf->Read($buff, $more); + if ($n == $more) { + $value = substr($$dataPt,pos($$dataPt)) . $buff; + $$dataPt = ''; + pos($$dataPt) = 0; + } + } + if (defined $value) { + # return as binary data unless it is a reasonable-length ASCII string + if (length($value) > 256) { + $val = \$value; + } elsif ($value =~ /[^\t\x20-\x7e]/) { + if (Image::ExifTool::IsUTF8(\$value) >= 0) { + $val = $et->Decode($value, 'UTF8'); + } else { + $val = \$value; + } + } else { + $val = $value; + } + } elsif (not defined $val) { + $$raf{BencodeError} = 'Truncated byte string'; + } + } else { + $$raf{BencodeError} = 'Bad format'; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Extract tags from dictionary hash +# Inputs: 0) ExifTool ref, 1) dictionary hash reference, 2) tag table ref, +# 3) parent hash ID, 4) parent hash name, 5-N) list indices +# Returns: number of tags extracted +sub ExtractTags($$$;$$@) +{ + my ($et, $hashPtr, $tagTablePtr, $baseID, $baseName, @index) = @_; + my $count = 0; + my $tag; + foreach $tag (sort keys %$hashPtr) { + my $val = $$hashPtr{$tag}; + my ($i, $j, @more); + for (; defined $val; $val = shift @more) { + my $id = defined $baseID ? "$baseID/$tag" : $tag; + unless ($$tagTablePtr{$id}) { + my $name = ucfirst $tag; + # capitalize all words in tag name and remove illegal characters + $name =~ s/[^-_a-zA-Z0-9]+(.?)/\U$1/g; + $name = "Tag$name" if length($name) < 2 or $name !~ /^[A-Z]/; + $name = $baseName . $name if defined $baseName; # add base name if necessary + AddTagToTable($tagTablePtr, $id, { Name => $name }); + $et->VPrint(0, " [adding $id '${name}']\n"); + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $id) or next; + if (ref $val eq 'ARRAY') { + if ($$tagInfo{JoinPath}) { + $val = join '/', map { ref $_ ? '(Binary data)' : $_ } @$val; + } else { + push @more, @$val; + next if ref $more[0] eq 'ARRAY'; # continue expanding nested lists + $val = shift @more; + $i or $i = 0, push(@index, $i); + } + } + $index[-1] = ++$i if defined $i; + if (@index) { + $id .= join '_', @index; # add instance number(s) to tag ID + unless ($$tagTablePtr{$id}) { + my $name = $$tagInfo{Name}; + # embed indices at position of '1' in tag name + my $n = ($name =~ tr/1/#/); + for ($j=0; $j<$n; ++$j) { + my $idx = $index[$j] || ''; + $name =~ s/#/$idx/; + } + # put remaining indices at end of tag name + for (; $j<@index; ++$j) { + $name .= '_' if $name =~ /\d$/; + $name .= $index[$j]; + } + AddTagToTable($tagTablePtr, $id, { %$tagInfo, Name => $name }); + } + $tagInfo = $et->GetTagInfo($tagTablePtr, $id) or next; + } + if (ref $val eq 'HASH') { + if ($et->Options('Struct') and $tagInfo and $$tagInfo{Name} eq 'Info') { + $et->FoundTag($tagInfo, $val); + ++$count; + next; + } + # extract tags from this dictionary + my ($table, $rootID, $rootName); + if ($$tagInfo{SubDirectory}) { + $table = GetTagTable($$tagInfo{SubDirectory}{TagTable}); + } else { + $table = $tagTablePtr; + # use hash ID and Name as base for contained tags to avoid conflicts + $rootID = $id; + $rootName = $$tagInfo{Name}; + } + $count += ExtractTags($et, $val, $table, $rootID, $rootName, @index); + } else { + # handle this simple tag value + $et->HandleTag($tagTablePtr, $id, $val); + ++$count; + } + } + pop @index if defined $i; + } + return $count; +} + +#------------------------------------------------------------------------------ +# Process BitTorrent file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference (with RAF set) +# Returns: 1 on success, 0 if this wasn't a valid BitTorrent file +sub ProcessTorrent($$) +{ + my ($et, $dirInfo) = @_; + my $success = 0; + my $raf = $$dirInfo{RAF}; + my $buff = ''; + pos($buff) = 0; + my $dict = ReadBencode($et, $raf, \$buff); + my $err = $$raf{BencodeError}; + $et->Warn("Bencode error: $err") if $err; + if (ref $dict eq 'HASH' and ($$dict{announce} or $$dict{'created by'})) { + $et->SetFileType(); + my $tagTablePtr = GetTagTable('Image::ExifTool::Torrent::Main'); + ExtractTags($et, $dict, $tagTablePtr) and $success = 1; + } + return $success; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Torrent - Read information from BitTorrent file + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +bencoded information from BitTorrent files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://wiki.theory.org/BitTorrentSpecification> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Torrent Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Unknown.pm b/ExifTool/lib/Image/ExifTool/Unknown.pm new file mode 100644 index 0000000..fffc231 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Unknown.pm @@ -0,0 +1,66 @@ +#------------------------------------------------------------------------------ +# File: Unknown.pm +# +# Description: Unknown EXIF maker notes tags +# +# Revisions: 04/07/2004 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::Unknown; + +use strict; +use vars qw($VERSION); +use Image::ExifTool::Exif; + +$VERSION = '1.13'; + +# Unknown maker notes +%Image::ExifTool::Unknown::Main = ( + WRITE_PROC => \&Image::ExifTool::Exif::WriteExif, + CHECK_PROC => \&Image::ExifTool::Exif::CheckExif, + GROUPS => { 0 => 'MakerNotes', 1 => 'MakerUnknown', 2 => 'Camera' }, + + # this seems to be a common fixture, so look for it in unknown maker notes + 0x0e00 => { + Name => 'PrintIM', + Description => 'Print Image Matching', + SubDirectory => { + TagTable => 'Image::ExifTool::PrintIM::Main', + }, + }, +); + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Unknown - Unknown EXIF maker notes tags + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +Image::ExifTool has definitions for the maker notes from many manufacturers, +however information can sometimes be extracted from unknown manufacturers if +the maker notes are in standard IFD format. This module contains the +definitions necessary for Image::ExifTool to read the maker notes from +unknown manufacturers. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Unknown Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/VCard.pm b/ExifTool/lib/Image/ExifTool/VCard.pm new file mode 100644 index 0000000..840d3b2 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/VCard.pm @@ -0,0 +1,457 @@ +#------------------------------------------------------------------------------ +# File: VCard.pm +# +# Description: Read vCard and iCalendar meta information +# +# Revisions: 2015/04/05 - P. Harvey Created +# 2015/05/02 - PH Added iCalendar support +# +# References: 1) http://en.m.wikipedia.org/wiki/VCard +# 2) http://tools.ietf.org/html/rfc6350 +# 3) http://tools.ietf.org/html/rfc5545 +#------------------------------------------------------------------------------ + +package Image::ExifTool::VCard; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.07'; + +my %unescapeVCard = ( '\\'=>'\\', ','=>',', 'n'=>"\n", 'N'=>"\n" ); + +# lookup for iCalendar components (used to generate family 1 group names if top level) +my %isComponent = ( Event=>1, Todo=>1, Journal=>1, Freebusy=>1, Timezone=>1, Alarm=>1 ); + +my %timeInfo = ( + # convert common date/time formats to EXIF style + ValueConv => q{ + $val =~ s/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z?)/$1:$2:$3 $4:$5:$6$7/g; + $val =~ s/(\d{4})(\d{2})(\d{2})/$1:$2:$3/g; + $val =~ s/(\d{4})-(\d{2})-(\d{2})/$1:$2:$3/g; + return $val; + }, + PrintConv => '$self->ConvertDateTime($val)', +); + +# vCard tags (ref 1/2/PH) +# Note: The case of all tag ID's is normalized to lowercase with uppercase first letter +%Image::ExifTool::VCard::Main = ( + GROUPS => { 2 => 'Document' }, + VARS => { NO_LOOKUP => 1 }, # omit tags from lookup + NOTES => q{ + This table lists common vCard tags, but ExifTool will also extract any other + vCard tags found. Tag names may have "Pref" added to indicate the preferred + instance of a vCard property, and other "TYPE" parameters may also added to + the tag name. VCF files may contain multiple vCard entries which are + distinguished by the ExifTool family 3 group name (document number). See + L<http://tools.ietf.org/html/rfc6350> for the vCard 4.0 specification. + }, + Version => { Name => 'VCardVersion', Description => 'VCard Version' }, + Fn => { Name => 'FormattedName', Groups => { 2 => 'Author' } }, + N => { Name => 'Name', Groups => { 2 => 'Author' } }, + Bday => { Name => 'Birthday', Groups => { 2 => 'Time' }, %timeInfo }, + Tz => { Name => 'TimeZone', Groups => { 2 => 'Time' } }, + Adr => { Name => 'Address', Groups => { 2 => 'Location' } }, + Geo => { + Name => 'Geolocation', + Groups => { 2 => 'Location' }, + # when used as a parameter, VCard 4.0 adds a "geo:" prefix that we need to remove + ValueConv => '$val =~ s/^geo://; $val', + }, + Anniversary => { }, + Email => { }, + Gender => { }, + Impp => 'IMPP', + Lang => 'Language', + Logo => { }, + Nickname => { }, + Note => { }, + Org => 'Organization', + Photo => { Groups => { 2 => 'Preview' } }, + Prodid => 'Software', + Rev => 'Revision', + Sound => { }, + Tel => 'Telephone', + Title => 'JobTitle', + Uid => 'UID', + Url => 'URL', + 'X-ablabel' => { Name => 'ABLabel', PrintConv => '$val =~ s/^_\$!<(.*)>!\$_$/$1/; $val' }, + 'X-abdate' => { Name => 'ABDate', Groups => { 2 => 'Time' }, %timeInfo }, + 'X-aim' => 'AIM', + 'X-icq' => 'ICQ', + 'X-abuid' => 'AB_UID', + 'X-abrelatednames' => 'ABRelatedNames', + 'X-socialprofile' => 'SocialProfile', +); + +%Image::ExifTool::VCard::VCalendar = ( + GROUPS => { 1 => 'VCalendar', 2 => 'Document' }, + VARS => { + NO_LOOKUP => 1, # omit tags from lookup + LONG_TAGS => 6, # some X-microsoft tags have unavoidably long ID's + }, + NOTES => q{ + The VCard module is also used to process iCalendar ICS files since they use + a format similar to vCard. The following table lists standard iCalendar + tags, but any existing tags will be extracted. Top-level iCalendar + components (eg. Event, Todo, Timezone, etc.) are used for the family 1 group + names, and embedded components (eg. Alarm) are added as a prefix to the tag + name. See L<http://tools.ietf.org/html/rfc5545> for the official iCalendar + 2.0 specification. + }, + Version => { Name => 'VCalendarVersion', Description => 'VCalendar Version' }, + Calscale => 'CalendarScale', + Method => { }, + Prodid => 'Software', + Attach => 'Attachment', + Categories => { }, + Class => 'Classification', + Comment => { }, + Description => { }, + Geo => { + Name => 'Geolocation', + Groups => { 2 => 'Location' }, + ValueConv => '$val =~ s/^geo://; $val', + }, + Location => { Name => 'Location', Groups => { 2 => 'Location' } }, + 'Percent-complete' => 'PercentComplete', + Priority => { }, + Resources => { }, + Status => { }, + Summary => { }, + Completed => { Name => 'DateTimeCompleted', Groups => { 2 => 'Time' }, %timeInfo }, + Dtend => { Name => 'DateTimeEnd', Groups => { 2 => 'Time' }, %timeInfo }, + Due => { Name => 'DateTimeDue', Groups => { 2 => 'Time' }, %timeInfo }, + Dtstart => { Name => 'DateTimeStart', Groups => { 2 => 'Time' }, %timeInfo }, + Duration => { }, + Freebusy => 'FreeBusyTime', + Transp => 'TimeTransparency', + Tzid => { Name => 'TimezoneID', Groups => { 2 => 'Time' } }, + Tzname => { Name => 'TimezoneName', Groups => { 2 => 'Time' } }, + Tzoffsetfrom=> { Name => 'TimezoneOffsetFrom', Groups => { 2 => 'Time' } }, + Tzoffsetto => { Name => 'TimezoneOffsetTo', Groups => { 2 => 'Time' } }, + Tzurl => { Name => 'TimeZoneURL', Groups => { 2 => 'Time' } }, + Attendee => { }, + Contact => { }, + Organizer => { }, + 'Recurrence-id' => 'RecurrenceID', + 'Related-to' => 'RelatedTo', + Url => 'URL', + Uid => 'UID', + Exdate => { Name => 'ExceptionDateTimes', Groups => { 2 => 'Time' }, %timeInfo }, + Rdate => { Name => 'RecurrenceDateTimes', Groups => { 2 => 'Time' }, %timeInfo }, + Rrule => { Name => 'RecurrenceRule', Groups => { 2 => 'Time' } }, + Action => { }, + Repeat => { }, + Trigger => { }, + Created => { Name => 'DateCreated', Groups => { 2 => 'Time' }, %timeInfo }, + Dtstamp => { Name => 'DateTimeStamp', Groups => { 2 => 'Time' }, %timeInfo }, + 'Last-modified' => { Name => 'ModifyDate', Groups => { 2 => 'Time' }, %timeInfo }, + Sequence => 'SequenceNumber', + 'Request-status' => 'RequestStatus', + Acknowledged=> { Name => 'Acknowledged', Groups => { 2 => 'Time' }, %timeInfo }, +# +# Observed X-tags (not a comprehensive list): +# + 'X-apple-calendar-color'=> 'CalendarColor', + 'X-apple-default-alarm' => 'DefaultAlarm', + 'X-apple-local-default-alarm' => 'LocalDefaultAlarm', + 'X-microsoft-cdo-appt-sequence' => 'AppointmentSequence', + 'X-microsoft-cdo-ownerapptid' => 'OwnerAppointmentID', + 'X-microsoft-cdo-busystatus' => 'BusyStatus', + 'X-microsoft-cdo-intendedstatus' => 'IntendedBusyStatus', + 'X-microsoft-cdo-alldayevent' => 'AllDayEvent', + 'X-microsoft-cdo-importance' => { + Name => 'Importance', + PrintConv => { + 0 => 'Low', + 1 => 'Normal', + 2 => 'High', + }, + }, + 'X-microsoft-cdo-insttype' => { + Name => 'InstanceType', + PrintConv => { + 0 => 'Non-recurring Appointment', + 1 => 'Recurring Appointment', + 2 => 'Single Instance of Recurring Appointment', + 3 => 'Exception to Recurring Appointment', + }, + }, + 'X-microsoft-donotforwardmeeting' => 'DoNotForwardMeeting', + 'X-microsoft-disallow-counter' => 'DisallowCounterProposal', + 'X-microsoft-locations' => { Name => 'MeetingLocations', Groups => { 2 => 'Location' } }, + 'X-wr-caldesc' => 'CalendarDescription', + 'X-wr-calname' => 'CalendarName', + 'X-wr-relcalid' => 'CalendarID', + 'X-wr-timezone' => { Name => 'TimeZone2', Groups => { 2 => 'Time' } }, + 'X-wr-alarmuid' => 'AlarmUID', +); + +%Image::ExifTool::VCard::VNote = ( + GROUPS => { 1 => 'VNote', 2 => 'Document' }, + NOTES => 'Tags extracted from V-Note VNT files.', + Version => { }, + Body => { }, + Dcreated => { Name => 'CreateDate', Groups => { 2 => 'Time' }, %timeInfo }, + 'Last-modified' => { Name => 'ModifyDate', Groups => { 2 => 'Time' }, %timeInfo }, +); + +#------------------------------------------------------------------------------ +# Get vCard tag, creating if necessary +# Inputs: 0) ExifTool ref, 1) tag table ref, 2) tag ID, 3) tag Name, +# 4) source tagInfo ref, 5) lang code +# Returns: tagInfo ref +sub GetVCardTag($$$$;$$) +{ + my ($et, $tagTablePtr, $tag, $name, $srcInfo, $langCode) = @_; + my $tagInfo = $$tagTablePtr{$tag}; + unless ($tagInfo) { + if ($srcInfo) { + $tagInfo = { %$srcInfo }; + } else { + $tagInfo = { }; + $et->VPrint(0, $$et{INDENT}, "[adding $tag]\n"); + } + $$tagInfo{Name} = $name; + delete $$tagInfo{Description}; # create new description + AddTagToTable($tagTablePtr, $tag, $tagInfo); + } + # handle alternate languages (the "language" parameter) + $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode) if $langCode; + return $tagInfo; +} + +#------------------------------------------------------------------------------ +# Decode vCard text +# Inputs: 0) ExifTool ref, 1) vCard text, 2) encoding +# Returns: decoded text (or array ref for a list of values) +sub DecodeVCardText($$;$) +{ + my ($et, $val, $enc) = @_; + $enc = defined($enc) ? lc $enc : ''; + if ($enc eq 'b' or $enc eq 'base64') { + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::DecodeBase64($val); + } else { + if ($enc eq 'quoted-printable') { + # convert "=HH" hex codes to characters + $val =~ s/=([0-9a-f]{2})/chr(hex($1))/ige; + } + $val = $et->Decode($val, 'UTF8'); # convert from UTF-8 + # convert unescaped commas to nulls to separate list items + $val =~ s/(\\.)|(,)/$1 || "\0"/sge; + # unescape necessary characters in value + $val =~ s/\\(.)/$unescapeVCard{$1}||$1/sge; + # split into list if necessary + my @vals = split /\0/, $val; + $val = \@vals if @vals > 1; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Read information in a vCard file +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid vCard file +sub ProcessVCard($$) +{ + local $_; + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $val, $ok, $component, %compNum, @count); + + return 0 unless $raf->Read($buff, 24) and $raf->Seek(0,0) and $buff=~/^BEGIN:(VCARD|VCALENDAR|VNOTE)\r\n/i; + my %info = ( + VCARD => [ qw(VCard vCard Main VCF) ], + VCALENDAR => [ qw(ICS iCalendar VCalendar ICS) ], + VNOTE => [ qw(VNote vNote VNote VNT text/v-note) ], + ); + my ($type, $lbl, $tbl, $ext, $mime) = @{$info{uc($1)}}; + $et->SetFileType($type, $mime, $ext); + return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3; + local $/ = "\r\n"; + my $tagTablePtr = GetTagTable("Image::ExifTool::VCard::$tbl"); + my $more = $raf->ReadLine($buff); # read first line + chomp $buff if $more; + while ($more) { + # retrieve previous line from $buff + $val = $buff if defined $buff; + # read ahead to next line to see if is a continuation + $more = $raf->ReadLine($buff); + if ($more) { + chomp $buff; + # add continuation line if necessary + $buff =~ s/^[ \t]// and $val .= $buff, undef($buff), next; + } + if ($val =~ /^(BEGIN|END):(V?)(\w+)$/i) { + my ($begin, $v, $what) = ((lc($1) eq 'begin' ? 1 : 0), $2, ucfirst lc $3); + if ($what eq 'Card' or $what eq 'Calendar' or $what eq 'Note') { + if ($begin) { + @count = ( { } ); # reset group counters + } else { + $ok = 1; # ok if we read at least on full VCARD or VCALENDAR + } + next; + } + # absorb top-level component into family 1 group name + if ($isComponent{$what}) { + if ($begin) { + unless ($component) { + # begin a new top-level component + @count = ( { } ); + $component = $what; + $compNum{$component} = ($compNum{$component} || 0) + 1; + next; + } + } elsif ($component and $component eq $what) { + # this top-level component has ended + undef $component; + next; + } + } + # keep count of each component at this level + if ($begin) { + $count[-1]{$what} = ($count[-1]{$what} || 0) + 1 if $v; + push @count, { obj => $what }; + } elsif (@count > 1) { + pop @count; + } + next; + } elsif ($ok) { + $ok = 0; + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; # read next card as a new document + } + unless ($val =~ s/^([-A-Za-z0-9.]+)//) { + $et->WarnOnce("Unrecognized line in $lbl file"); + next; + } + my $tag = $1; + # set group if it exists + if ($tag =~ s/^([-A-Za-z0-9]+)\.//) { + $$et{SET_GROUP1} = ucfirst lc $1; + } elsif ($component) { + $$et{SET_GROUP1} = $component . $compNum{$component}; + } else { + delete $$et{SET_GROUP1}; + } + my ($name, %param, $p); + # vCard tag ID's are case-insensitive, so normalize to lowercase with + # an uppercase first letter for use as a tag name + $name = ucfirst $tag if $tag =~ /[a-z]/; # preserve mixed case in name if it exists + $tag = ucfirst lc $tag; + # get source tagInfo reference + my $srcInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($srcInfo) { + $name = $$srcInfo{Name}; # use our name + } else { + $name or $name = $tag; + # remove leading "X-" from name if it exists + $name =~ s/^X-// and $name = ucfirst $name; + } + # add object name(s) to tag if necessary + if (@count > 1) { + my $i; + for ($i=$#count-1; $i>=0; --$i) { + my $pre = $count[$i-1]{obj}; # use containing object name as tag prefix + my $c = $count[$i]{$pre}; # add index for object number + $c = '' unless defined $c; + $tag = $pre . $c . $tag; + $name = $pre . $c . $name; + } + } + # parse parameters + while ($val =~ s/^;([-A-Za-z0-9]*)(=?)//) { + $p = ucfirst lc $1; + # convert old vCard 2.x parameters to the new "TYPE=" format + $2 or $val = $1 . $val, $p = 'Type'; + # read parameter value + for (;;) { + last unless $val =~ s/^"([^"]*)",?// or $val =~ s/^([^";:,]+,?)//; + my $v = $p eq 'Type' ? ucfirst lc $1 : $1; + $param{$p} = defined($param{$p}) ? $param{$p} . $v : $v; + } + if (defined $param{$p}) { + $param{$p} =~ s/\\(.)/$unescapeVCard{$1}||$1/sge; + } else { + $param{$p} = ''; + } + } + $val =~ s/^:// or $et->WarnOnce("Invalid line in $lbl file"), next; + # add 'Type' parameter to id and name if it exists + $param{Type} and $tag .= $param{Type}, $name .= $param{Type}; + # convert base64-encoded data + if ($val =~ s{^data:(\w+)/(\w+);base64,}{}) { + my $xtra = ucfirst(lc $1) . ucfirst(lc $2); + $tag .= $xtra; + $name .= $xtra; + $param{Encoding} = 'base64'; + } + $val = DecodeVCardText($et, $val, $param{Encoding}); + my $tagInfo = GetVCardTag($et, $tagTablePtr, $tag, $name, $srcInfo, $param{Language}); + $et->HandleTag($tagTablePtr, $tag, $val, TagInfo => $tagInfo); + # handle some other parameters that we care about (ignore the rest for now) + foreach $p (qw(Geo Label Tzid)) { + next unless defined $param{$p}; + # use tag attributes from our table if it exists + my $srcTag2 = $et->GetTagInfo($tagTablePtr, $p); + my $pn = $srcTag2 ? $$srcTag2{Name} : $p; + $val = DecodeVCardText($et, $param{$p}); + # add parameter to tag ID and name + my ($tg, $nm) = ($tag . $p, $name . $pn); + $tagInfo = GetVCardTag($et, $tagTablePtr, $tg, $nm, $srcTag2, $param{Language}); + $et->HandleTag($tagTablePtr, $tg, $val, TagInfo => $tagInfo); + } + } + delete $$et{SET_GROUP1}; + delete $$et{DOC_NUM}; + $ok or $et->Warn("Missing $lbl end"); + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::VCard - Read vCard and iCalendar meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read meta +information from vCard VCF and iCalendar ICS files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://en.m.wikipedia.org/wiki/VCard> + +=item L<http://tools.ietf.org/html/rfc6350> + +=item L<http://tools.ietf.org/html/rfc5545> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/VCard Tags>, +L<Image::ExifTool::TagNames/VCard VCalendar Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/Validate.pm b/ExifTool/lib/Image/ExifTool/Validate.pm new file mode 100644 index 0000000..c629ae4 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Validate.pm @@ -0,0 +1,688 @@ +#------------------------------------------------------------------------------ +# File: Validate.pm +# +# Description: Additional metadata validation +# +# Created: 2017/01/18 - P. Harvey +# +# Notes: My apologies for the convoluted logic contained herein, but it +# is done this way to retro-fit the Validate feature into the +# existing ExifTool code while reducing the possibility of +# introducing bugs or slowing down processing when this feature +# is not used. +#------------------------------------------------------------------------------ + +package Image::ExifTool::Validate; + +use strict; +use vars qw($VERSION %exifSpec); + +$VERSION = '1.21'; + +use Image::ExifTool qw(:Utils); +use Image::ExifTool::Exif; + +# EXIF table tag ID's which are part of the EXIF 2.32 specification +# (with ExifVersion numbers for tags where I can determine the version) +# (also used by BuildTagLookup to add underlines in HTML version of EXIF Tag Table) +%exifSpec = ( + 0x1 => 210, + 0x100 => 1, 0x8298 => 1, 0x9207 => 1, 0xa217 => 1, + 0x101 => 1, 0x829a => 1, 0x9208 => 1, 0xa300 => 1, + 0x102 => 1, 0x829d => 1, 0x9209 => 1, 0xa301 => 1, + 0x103 => 1, 0x8769 => 1, 0x920a => 1, 0xa302 => 1, + 0x106 => 1, 0x8822 => 1, 0x9214 => 220, 0xa401 => 220, + 0x10e => 1, 0x8824 => 1, 0x927c => 1, 0xa402 => 220, + 0x10f => 1, 0x8825 => 200, 0x9286 => 1, 0xa403 => 220, + 0x110 => 1, 0x8827 => 1, 0x9290 => 1, 0xa404 => 220, + 0x111 => 1, 0x8828 => 1, 0x9291 => 1, 0xa405 => 220, + 0x112 => 1, 0x8830 => 230, 0x9292 => 1, 0xa406 => 220, + 0x115 => 1, 0x8831 => 230, 0x9400 => 231, 0xa407 => 220, + 0x116 => 1, 0x8832 => 230, 0x9401 => 231, 0xa408 => 220, + 0x117 => 1, 0x8833 => 230, 0x9402 => 231, 0xa409 => 220, + 0x11a => 1, 0x8834 => 230, 0x9403 => 231, 0xa40a => 220, + 0x11b => 1, 0x8835 => 230, 0x9404 => 231, 0xa40b => 220, + 0x11c => 1, 0x9000 => 1, 0x9405 => 231, 0xa40c => 220, + 0x128 => 1, 0x9003 => 1, 0xa000 => 1, 0xa460 => 232, + 0x12d => 1, 0x9004 => 1, 0xa001 => 1, 0xa461 => 232, + 0x131 => 1, 0x9010 => 231, 0xa002 => 1, 0xa462 => 232, + 0x132 => 1, 0x9011 => 231, 0xa003 => 1, 0xa420 => 220, + 0x13b => 1, 0x9012 => 231, 0xa004 => 1, 0xa430 => 230, + 0x13e => 1, 0x9101 => 1, 0xa005 => 210, 0xa431 => 230, + 0x13f => 1, 0x9102 => 1, 0xa20b => 1, 0xa432 => 230, + 0x201 => 1, 0x9201 => 1, 0xa20c => 1, 0xa433 => 230, + 0x202 => 1, 0x9202 => 1, 0xa20e => 1, 0xa434 => 230, + 0x211 => 1, 0x9203 => 1, 0xa20f => 1, 0xa435 => 230, + 0x212 => 1, 0x9204 => 1, 0xa210 => 1, 0xa500 => 221, + 0x213 => 1, 0x9205 => 1, 0xa214 => 1, + 0x214 => 1, 0x9206 => 1, 0xa215 => 1, + + # new Exif 3.0 tags + 0xa436 => 300, + 0xa437 => 300, + 0xa438 => 300, + 0xa439 => 300, + 0xa43a => 300, + 0xa43b => 300, + 0xa43c => 300, +); + +# GPSVersionID numbers when each tag was introduced +my %gpsVer = ( + 0x01 => 2000, 0x09 => 2000, 0x11 => 2000, 0x19 => 2000, + 0x02 => 2000, 0x0a => 2000, 0x12 => 2000, 0x1a => 2000, + 0x03 => 2000, 0x0b => 2000, 0x13 => 2000, 0x1b => 2200, + 0x04 => 2000, 0x0c => 2000, 0x14 => 2000, 0x1c => 2200, + 0x05 => 2000, 0x0d => 2000, 0x15 => 2000, 0x1d => 2200, + 0x06 => 2000, 0x0e => 2000, 0x16 => 2000, 0x1e => 2200, + 0x07 => 2000, 0x0f => 2000, 0x17 => 2000, 0x1f => 2300, + 0x08 => 2000, 0x10 => 2000, 0x18 => 2000, +); + +# lookup to check version numbers +my %verCheck = ( + ExifIFD => { ExifVersion => \%exifSpec }, + InteropIFD => { ExifVersion => \%exifSpec }, + GPS => { GPSVersionID => \%gpsVer }, +); + +# tags standard in various RAW file formats +my %otherSpec = ( + CR2 => { 0xc5d8 => 1, 0xc5d9 => 1, 0xc5e0 => 1, 0xc640 => 1, 0xc6dc => 1, 0xc6dd => 1 }, + NEF => { 0x9216 => 1, 0x9217 => 1 }, + DNG => { 0x882a => 1, 0x9211 => 1, 0x9216 => 1 }, + ARW => { 0x7000 => 1, 0x7001 => 1, 0x7010 => 1, 0x7011 => 1, 0x7020 => 1, 0x7031 => 1, + 0x7032 => 1, 0x7034 => 1, 0x7035 => 1, 0x7036 => 1, 0x7037 => 1, 0x7038 => 1, + 0x7310 => 1, 0x7313 => 1, 0x7316 => 1, 0x74c7 => 1, 0x74c8 => 1, 0xa500 => 1 }, + RW2 => { All => 1 }, # ignore all unknown tags in RW2 + RWL => { All => 1 }, + RAF => { All => 1 }, # (temporary) + DCR => { All => 1 }, + KDC => { All => 1 }, + JXR => { All => 1 }, + SRW => { 0xa010 => 1, 0xa011 => 1, 0xa101 => 1, 0xa102 => 1 }, + NRW => { 0x9216 => 1, 0x9217 => 1 }, + X3F => { 0xa500 => 1 }, +); + +# standard format for tags (not necessary for exifSpec or GPS tags where Writable is defined) +my %stdFormat = ( + ExifIFD => { + 0xa002 => 'int(16|32)u', + 0xa003 => 'int(16|32)u', + }, + InteropIFD => { + 0x01 => 'string', + 0x02 => 'undef', + 0x1000 => 'string', + 0x1001 => 'int(16|32)u', + 0x1002 => 'int(16|32)u', + }, + IFD => { + # TIFF, EXIF, XMP, IPTC, ICC_Profile and PrintIM standard tags: + 0xfe => 'int32u', 0x11f => 'rational64u', 0x14a => 'int32u', 0x205 => 'int16u', + 0xff => 'int16u', 0x120 => 'int32u', 0x14c => 'int16u', 0x206 => 'int16u', + 0x100 => 'int(16|32)u', 0x121 => 'int32u', 0x14d => 'string', 0x207 => 'int32u', + 0x101 => 'int(16|32)u', 0x122 => 'int16u', 0x14e => 'int16u', 0x208 => 'int32u', + 0x107 => 'int16u', 0x123 => 'int16u', 0x150 => 'int(8|16)u', 0x209 => 'int32u', + 0x108 => 'int16u', 0x124 => 'int32u', 0x151 => 'string', 0x211 => 'rational64u', + 0x109 => 'int16u', 0x125 => 'int32u', 0x152 => 'int16u', 0x212 => 'int16u', + 0x10a => 'int16u', 0x129 => 'int16u', 0x153 => 'int16u', 0x213 => 'int16u', + 0x10d => 'string', 0x13c => 'string', 0x154 => '.*', 0x214 => 'rational64u', + 0x111 => 'int(16|32)u', 0x13d => 'int16u', 0x155 => '.*', 0x2bc => 'int8u', + 0x116 => 'int(16|32)u', 0x140 => 'int16u', 0x156 => 'int16u', 0x828d => 'int16u', + 0x117 => 'int(16|32)u', 0x141 => 'int16u', 0x15b => 'undef', 0x828e => 'int8u', + 0x118 => 'int16u', 0x142 => 'int(16|32)u', 0x200 => 'int16u', 0x83bb => 'int32u', + 0x119 => 'int16u', 0x143 => 'int(16|32)u', 0x201 => 'int32u', 0x8649 => 'int8u', + 0x11d => 'string', 0x144 => 'int32u', 0x202 => 'int32u', 0x8773 => 'undef', + 0x11e => 'rational64u', 0x145 => 'int(16|32)u', 0x203 => 'int16u', 0xc4a5 => 'undef', + # Windows Explorer tags: + 0x9c9b => 'int8u', 0x9c9d => 'int8u', 0x9c9f => 'int8u', + 0x9c9c => 'int8u', 0x9c9e => 'int8u', + # GeoTiff tags: + 0x830e => 'double', 0x8482 => 'double', 0x87af => 'int16u', 0x87b1 => 'string', + 0x8480 => 'double', 0x85d8 => 'double', 0x87b0 => 'double', + # DNG tags: + 0xc615 => '(string|int8u)', 0xc6d3 => '', + 0xc61a => '(int16u|int32u|rational64u)', 0xc6f4 => '(string|int8u)', + 0xc61d => 'int(16|32)u', 0xc6f6 => '(string|int8u)', + 0xc61f => '(int16u|int32u|rational64u)', 0xc6f8 => '(string|int8u)', + 0xc620 => '(int16u|int32u|rational64u)', 0xc6fe => '(string|int8u)', + 0xc628 => '(int16u|rational64u)', 0xc716 => '(string|int8u)', + 0xc634 => 'int8u', 0xc717 => '(string|int8u)', + 0xc640 => '', 0xc718 => '(string|int8u)', + 0xc660 => '', 0xc71e => 'int(16|32)u', + 0xc68b => '(string|int8u)', 0xc71f => 'int(16|32)u', + 0xc68d => 'int(16|32)u', 0xc791 => 'int(16|32)u', + 0xc68e => 'int(16|32)u', 0xc792 => 'int(16|32)u', + 0xc6d2 => '', 0xc793 => '(int16u|int32u|rational64u)', + # Exif 3.0 spec + 0x10e => 'string|utf8', 0xa430 => 'string|utf8', 0xa439 => 'string|utf8', + 0x10f => 'string|utf8', 0xa433 => 'string|utf8', 0xa43a => 'string|utf8', + 0x110 => 'string|utf8', 0xa434 => 'string|utf8', 0xa43b => 'string|utf8', + 0x131 => 'string|utf8', 0xa436 => 'string|utf8', 0xa43c => 'string|utf8', + 0x13b => 'string|utf8', 0xa437 => 'string|utf8', 0xa43a => 'string|utf8', + 0x8298 => 'string|utf8', 0xa438 => 'string|utf8', + }, +); + +# generate lookup for any IFD +my %stdFormatAnyIFD = map { %{$stdFormat{$_}} } keys %stdFormat; + +# tag values to validate based on file type (from EXIF specification) +# - validation code may access $val and %val, and returns 1 on success, +# or error message otherwise ('' for a generic message) +# - entry is undef if tag must not exist (same as 'not defined $val' in code) +my %validValue = ( + JPEG => { + IFD0 => { + 0x100 => undef, # ImageWidth + 0x101 => undef, # ImageLength + 0x102 => undef, # BitsPerSample + 0x103 => undef, # Compression + 0x106 => undef, # PhotometricInterpretation + 0x111 => undef, # StripOffsets + 0x115 => undef, # SamplesPerPixel + 0x116 => undef, # RowsPerStrip + 0x117 => undef, # StripByteCounts + 0x11a => 'defined $val', # XResolution + 0x11b => 'defined $val', # YResolution + 0x11c => undef, # PlanarConfiguration + 0x128 => '$val =~ /^[123]$/', # ResolutionUnit + 0x201 => undef, # JPEGInterchangeFormat + 0x202 => undef, # JPEGInterchangeFormatLength + 0x212 => undef, # YCbCrSubSampling + 0x213 => '$val =~ /^[12]$/', # YCbCrPositioning + }, + IFD1 => { + 0x100 => undef, # ImageWidth + 0x101 => undef, # ImageLength + 0x102 => undef, # BitsPerSample + 0x103 => '$val == 6', # Compression + 0x106 => undef, # PhotometricInterpretation + 0x111 => undef, # StripOffsets + 0x115 => undef, # SamplesPerPixel + 0x116 => undef, # RowsPerStrip + 0x117 => undef, # StripByteCounts + 0x11a => 'defined $val', # XResolution + 0x11b => 'defined $val', # YResolution + 0x11c => undef, # PlanarConfiguration + 0x128 => '$val =~ /^[123]$/', # ResolutionUnit + 0x201 => 'defined $val', # JPEGInterchangeFormat + 0x202 => 'defined $val', # JPEGInterchangeFormatLength + 0x212 => undef, # YCbCrSubSampling + }, + ExifIFD => { + 0x9000 => 'defined $val and $val =~ /^\d{4}$/', # ExifVersion + 0x9101 => 'defined $val', # ComponentsConfiguration + 0xa000 => 'defined $val', # FlashpixVersion + 0xa001 => '$val == 1 or $val == 0xffff', # ColorSpace + 0xa002 => 'defined $val', # PixelXDimension + 0xa003 => 'defined $val', # PixelYDimension + }, + GPS => { + 0x00 => 'defined $val and $val =~ /^\d \d \d \d$/', # GPSVersionID + 0x1b => 'not defined $val or $val =~ /^(GPS|CELLID|WLAN|MANUAL)$/', # GPSProcessingMethod + }, + InteropIFD => { }, # (needed for ExifVersion check) + }, + TIFF => { + IFD0 => { + 0x100 => 'defined $val', # ImageWidth + 0x101 => 'defined $val', # ImageLength + # (default is 1) 0x102 => 'defined $val', # BitsPerSample + 0x103 => q{ + not defined $val or $val =~ /^(1|5|6|32773)$/ or + ($val == 2 and (not defined $val{0x102} or $val{0x102} == 1)); + }, # Compression + 0x106 => '$val =~ /^[0123]$/', # PhotometricInterpretation + 0x111 => 'defined $val', # StripOffsets + # SamplesPerPixel + 0x115 => q{ + my $pi = $val{0x106} || 0; + my $xtra = ($val{0x152} ? scalar(split ' ', $val{0x152}) : 0); + if ($pi == 2 or $pi == 6) { + return $val == 3 + $xtra; + } elsif ($pi == 5) { + return $val == 4 + $xtra; + } else { + return 1; + } + }, + 0x116 => 'defined $val', # RowsPerStrip + 0x117 => 'defined $val', # StripByteCounts + 0x11a => 'defined $val', # XResolution + 0x11b => 'defined $val', # YResolution + 0x128 => 'not defined $val or $val =~ /^[123]$/', # ResolutionUnit + # ColorMap (must be palette image with correct number of colors) + 0x140 => q{ + return '' if defined $val{0x106} and $val{0x106} == 3 xor defined $val; + return 1 if not defined $val or length($val) == 6 * 2 ** ($val{0x102} || 0); + return 'Invalid count for'; + }, + 0x201 => undef, # JPEGInterchangeFormat + 0x202 => undef, # JPEGInterchangeFormatLength + }, + ExifIFD => { + 0x9000 => 'defined $val', # ExifVersion + 0x9101 => undef, # ComponentsConfiguration + 0x9102 => undef, # CompressedBitsPerPixel + 0xa000 => 'defined $val', # FlashpixVersion + 0xa001 => '$val == 1 or $val == 0xffff', # ColorSpace + 0xa002 => undef, # PixelXDimension + 0xa003 => undef, # PixelYDimension + }, + InteropIFD => { + 0x0001 => undef, # InteropIndex + }, + GPS => { + 0x00 => 'defined $val and $val =~ /^\d \d \d \d$/', # GPSVersionID + 0x1b => '$val =~ /^(GPS|CELLID|WLAN|MANUAL)$/', # GPSProcessingMethod + }, + }, +); + +# validity ranges for constrained date/time fields +my @validDateField = ( + [ 'Month', 1, 12 ], + [ 'Day', 1, 31 ], + [ 'Hour', 0, 23 ], + [ 'Minutes', 0, 59 ], + [ 'Seconds', 0, 59 ], + [ 'TZhr', 0, 14 ], + [ 'TZmin', 0, 59 ], +); + +# "Validate" tag information +my %validateInfo = ( + Groups => { 0 => 'ExifTool', 1 => 'ExifTool', 2 => 'ExifTool' }, + Notes => q{ + generated only if specifically requested. Requesting this tag automatically + enables the API L<Validate|../ExifTool.html#Validate> option, imposing + additional validation checks when extracting metadata. Returns the number + of errors, warnings and minor warnings encountered. Note that the Validate + feature focuses mainly on validation of EXIF/TIFF metadata + }, + PrintConv => { + '0 0 0' => 'OK', + OTHER => sub { + my @val = split ' ', shift; + my @rtn; + push @rtn, sprintf('%d Error%s', $val[0], $val[0] == 1 ? '' : 's') if $val[0]; + push @rtn, sprintf('%d Warning%s', $val[1], $val[1] == 1 ? '' : 's') if $val[1]; + if ($val[2]) { + my $str = ($val[1] == $val[2] ? ($val[1] == 1 ? '' : 'all ') : "$val[2] "); + $rtn[-1] .= " (${str}minor)"; + } + return join(' and ', @rtn); + }, + }, +); + +# add "Validate" tag to Extra table +AddTagToTable(\%Image::ExifTool::Extra, Validate => \%validateInfo, 1); + +#------------------------------------------------------------------------------ +# Validate the raw value of a tag +# Inputs: 0) ExifTool ref, 1) tag key, 2) raw tag value +# Returns: nothing, but issues a minor Warning if a problem was detected +sub ValidateRaw($$$) +{ + my ($self, $tag, $val) = @_; + my $tagInfo = $$self{TAG_INFO}{$tag}; + my $wrn; + + # evaluate Validate code if specified + if ($$tagInfo{Validate}) { + local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning; + undef $Image::ExifTool::evalWarning; + #### eval Validate ($self, $val, $tagInfo) + my $wrn = eval $$tagInfo{Validate}; + my $err = $Image::ExifTool::evalWarning || $@; + if ($wrn or $err) { + my $name = $$tagInfo{Table}{GROUPS}{0} . ':' . Image::ExifTool::GetTagName($tag); + $self->Warn("Validate $name: $err", 1) if $err; + $self->Warn("$wrn for $name", 1) if $wrn; + } + } + # check for unknown values in PrintConv lookup for all standard EXIF tags + if (ref $$tagInfo{PrintConv} eq 'HASH' and ($$tagInfo{Table}{SHORT_NAME} eq 'GPS::Main' or + ($$tagInfo{Table} eq \%Image::ExifTool::Exif::Main and $exifSpec{$$tagInfo{TagID}}))) + { + my $prt = $self->GetValue($tag, 'PrintConv'); + $wrn = 'Unknown value for' if $prt and $prt =~ /^Unknown \(/; + } + $wrn = 'Undefined value for' if $val eq 'undef'; + if ($wrn) { + my $name = $$self{DIR_NAME} . ':' . Image::ExifTool::GetTagName($tag); + $self->Warn("$wrn $name", 1); + } +} + +#------------------------------------------------------------------------------ +# Validate raw EXIF date/time value +# Inputs: 0) date/time value +# Returns: error string +sub ValidateExifDate($) +{ + my $val = shift; + if ($val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/) { + my @a = ($1,$2,$3,$4,$5); + my ($i, @bad); + for ($i=0; $i<@a; ++$i) { + next if $a[$i] eq ' ' or ($a[$i] >= $validDateField[$i][1] and $a[$i] <= $validDateField[$i][2]); + push @bad, $validDateField[$i][0]; + } + return join('+', @bad) . ' out of range' if @bad; + # the EXIF specification allows blank fields or an entire blank value + } elsif ($val ne ' : : : : ' and $val ne ' ') { + return 'Invalid date/time format'; + } + return undef; # OK! +} + +#------------------------------------------------------------------------------ +# Validate EXIF-reformatted XMP date/time value +# Inputs: 0) date/time value +# Returns: error string +sub ValidateXMPDate($) +{ + my $val = shift; + if ($val =~ /^\d{4}$/ or + $val =~ /^\d{4}:(\d{2})$/ or + $val =~ /^\d{4}:(\d{2}):(\d{2})$/ or + $val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2})()(Z|[-+](\d{2}):(\d{2}))?$/ or + $val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})(Z|[-+](\d{2}):(\d{2}))?$/ or + $val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})\.?\d*(Z|[-+](\d{2}):(\d{2}))?$/) + { + my @a = ($1,$2,$3,$4,$5,$7,$8); + my ($i, @bad); + for ($i=0; $i<@a; ++$i) { + last unless defined $a[$i]; + next if $a[$i] eq '' or ($a[$i] >= $validDateField[$i][1] and $a[$i] <= $validDateField[$i][2]); + push @bad, $validDateField[$i][0]; + } + return join('+', @bad) . ' out of range' if @bad; + } else { + return 'Invalid date/time format'; + } + return undef; # OK! +} + +#------------------------------------------------------------------------------ +# Validate EXIF tag +# Inputs: 0) ExifTool ref, 1) tag table ref, 2) tag ID, 3) tagInfo ref, +# 4) previous tag ID, 5) IFD name, 6) number of values, 7) value format string +# Returns: Nothing, but sets Warning tags if any problems are found +sub ValidateExif($$$$$$$$) +{ + my ($et, $tagTablePtr, $tag, $tagInfo, $lastTag, $ifd, $count, $formatStr) = @_; + + $et->WarnOnce("Entries in $ifd are out of order") if $tag <= $lastTag; + + # (get tagInfo for unknown tags if Unknown option not used) + if (not defined $tagInfo and $$tagTablePtr{$tag} and ref $$tagTablePtr{$tag} eq 'HASH') { + $tagInfo = $$tagTablePtr{$tag}; + } + if (defined $tagInfo) { + my $ti = $tagInfo || $$tagTablePtr{$tag}; + $ti = $$ti[-1] if ref $ti eq 'ARRAY'; + my $stdFmt = $stdFormat{$ifd} || $stdFormat{IFD}; + if (defined $$stdFmt{All} or ($tagTablePtr eq \%Image::ExifTool::Exif::Main and + ($exifSpec{$tag} or $$stdFmt{$tag} or + ($tag >= 0xc612 and $tag <= 0xc7b5 and not defined $$stdFmt{$tag}))) or # (DNG tags) + $$tagTablePtr{SHORT_NAME} eq 'GPS::Main') + { + my $wgp = $$ti{WriteGroup} || $$tagTablePtr{WRITE_GROUP}; + if ($wgp and $wgp ne $ifd and $wgp ne 'All' and not $$ti{OffsetPair} and + ($ifd =~ /^(Sub|Profile)?IFD\d*$/ xor $wgp =~ /^(Sub)?IFD\d*$/) and + ($$ti{Writable} or $$ti{WriteGroup}) and $ifd !~ /^SRF\d+$/) + { + $et->Warn(sprintf('Wrong IFD for 0x%.4x %s (should be %s not %s)', $tag, $$ti{Name}, $wgp, $ifd)); + } + my $fmt = $$stdFmt{$tag} || $$ti{Writable}; + if ($fmt and $formatStr !~ /^$fmt$/ and (not $tagInfo or + not $$tagInfo{IsOffset} or $Image::ExifTool::Exif::intFormat{$formatStr})) + { + $et->Warn(sprintf('Non-standard format (%s) for %s 0x%.4x %s', $formatStr, $ifd, $tag, $$ti{Name})) + } + } elsif ($stdFormatAnyIFD{$tag}) { + if ($$ti{Writable} || $$ti{WriteGroup}) { + my $wgp = $$ti{WriteGroup} || $$tagTablePtr{WRITE_GROUP}; + if ($wgp and $wgp ne $ifd) { + $et->Warn(sprintf('Wrong IFD for 0x%.4x %s (should be %s not %s)', $tag, $$ti{Name}, $wgp, $ifd)); + } + } + } elsif (not $otherSpec{$$et{FileType}} or + (not $otherSpec{$$et{FileType}}{$tag} and not $otherSpec{$$et{FileType}}{All})) + { + if ($tagTablePtr eq \%Image::ExifTool::Exif::Main or $$tagInfo{Unknown}) { + $et->Warn(sprintf('Non-standard %s tag 0x%.4x %s', $ifd, $tag, $$ti{Name}), 1); + } + } + # change expected count from read Format to Writable size + my $tiCount = $$ti{Count}; + if ($tiCount) { + if ($$ti{Format} and $$ti{Writable} and + $Image::ExifTool::Exif::formatNumber{$$ti{Format}} and + $Image::ExifTool::Exif::formatNumber{$$ti{Writable}}) + { + my $s1 = $Image::ExifTool::Exif::formatSize[$Image::ExifTool::Exif::formatNumber{$$ti{Format}}]; + my $s2 = $Image::ExifTool::Exif::formatSize[$Image::ExifTool::Exif::formatNumber{$$ti{Writable}}]; + $tiCount = int($tiCount * $s1 / $s2); + } + if ($tiCount > 0 and $count != $tiCount) { + $et->Warn(sprintf('Non-standard count (%d) for %s 0x%.4x %s', $count, $ifd, $tag, $$ti{Name})); + } + } + } elsif (not $otherSpec{$$et{FileType}} or + (not $otherSpec{$$et{FileType}}{$tag} and not $otherSpec{$$et{FileType}}{All})) + { + $et->Warn(sprintf('Unknown %s tag 0x%.4x', $ifd, $tag), 1); + } +} + +#------------------------------------------------------------------------------ +# Validate image data offsets/sizes +# Inputs: 0) ExifTool ref, 1) offset info hash ref (arrays of tagInfo/value pairs, keyed by tagID) +# 2) directory name, 3) optional flag for minor warning +sub ValidateOffsetInfo($$$;$) +{ + local $_; + my ($et, $offsetInfo, $dirName, $minor) = @_; + + my $fileSize = $$et{VALUE}{FileSize} or return; + + # (don't test RWZ files and some other file types) + return if $$et{DontValidateImageData}; + # (Minolta A200 uses wrong byte order for these) + return if $$et{TIFF_TYPE} eq 'MRW' and $dirName eq 'IFD0' and $$et{Model} =~ /^DiMAGE A200/; + # (don't test 3FR, RWL or RW2 files) + return if $$et{TIFF_TYPE} =~ /^(3FR|RWL|RW2)$/; + + Image::ExifTool::Exif::ValidateImageData($et, $offsetInfo, $dirName); + + # loop through all offsets + while (%$offsetInfo) { + my ($id1) = sort keys %$offsetInfo; + my $offsets = $$offsetInfo{$id1}; + delete $$offsetInfo{$id1}; + next unless ref $offsets eq 'ARRAY'; + my $id2 = $$offsets[0]{OffsetPair}; + unless (defined $id2 and $$offsetInfo{$id2}) { + unless ($$offsets[0]{NotRealPair} or (defined $id2 and $id2 == -1)) { + my $corr = $$offsets[0]{IsOffset} ? 'size' : 'offset'; + $et->Warn("$dirName:$$offsets[0]{Name} is missing the corresponding $corr tag") unless $minor; + } + next; + } + my $sizes = $$offsetInfo{$id2}; + delete $$offsetInfo{$id2}; + ($sizes, $offsets) = ($offsets, $sizes) if $$sizes[0]{IsOffset}; + my @offsets = split ' ', $$offsets[1]; + my @sizes = split ' ', $$sizes[1]; + if (@sizes != @offsets) { + $et->Warn(sprintf('Wrong number of values in %s 0x%.4x %s', + $dirName, $$offsets[0]{TagID}, $$offsets[0]{Name}), $minor); + next; + } + while (@offsets) { + my $start = pop @offsets; + my $end = $start + pop @sizes; + $et->WarnOnce("$dirName:$$offsets[0]{Name} is zero", $minor) if $start == 0; + $et->WarnOnce("$dirName:$$sizes[0]{Name} is zero", $minor) if $start == $end; + next unless $end > $fileSize; + if ($start >= $fileSize) { + if ($start == 0xffffffff) { + $et->Warn("$dirName:$$offsets[0]{Name} is invalid (0xffffffff)", $minor); + } else { + $et->Warn("$dirName:$$offsets[0]{Name} is past end of file", $minor); + } + } else { + $et->Warn("$dirName:$$offsets[0]{Name}+$$sizes[0]{Name} runs past end of file", $minor); + } + last; + } + } +} + +#------------------------------------------------------------------------------ +# Finish Validating tags +# Inputs: 0) ExifTool ref, 1) True to generate Validate tag +sub FinishValidate($$) +{ + local $_; + my ($et, $mkTag) = @_; + + my $fileType = $$et{FILE_TYPE} || ''; + $fileType = $$et{TIFF_TYPE} if $fileType eq 'TIFF'; + + if ($validValue{$fileType}) { + my ($grp, $tag, %val); + local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning; + foreach $grp (sort keys %{$validValue{$fileType}}) { + next unless $$et{FOUND_DIR}{$grp}; + my ($key, %val, %info, $minor, $verTag, $ver, $vstr); + my $verCheck = $verCheck{$grp}; + if ($verCheck) { + ($verTag) = keys %$verCheck; + ($ver = $$et{VALUE}{$verTag}) =~ tr/0-9//dc; # (remove non-digits) + undef $ver unless $ver =~ /^\d{4}$/; # (already warned if invalid version) + } + # get all tags in this group + foreach $key (sort keys %{$$et{VALUE}}) { + next unless $et->GetGroup($key, 1) eq $grp; + next if $$et{TAG_EXTRA}{$key} and $$et{TAG_EXTRA}{$key}{G3}; # ignore sub-documents + # fill in %val lookup with values based on tag ID + my $tag = $$et{TAG_INFO}{$key}{TagID}; + $val{$tag} = $$et{VALUE}{$key}; + # save TagInfo ref for later + $info{$tag} = $$et{TAG_INFO}{$key}; + next unless defined $ver; + my $chk = $$verCheck{$verTag}; + next if not defined $$chk{$tag} or $$chk{$tag} == 1 or $ver >= $$chk{$tag}; + if ($verTag eq 'GPSVersionID') { + ($vstr = $$chk{$tag}) =~ s/^(\d)(\d)(\d)/$1.$2.$3./; + } else { + $vstr = sprintf('%.4d', $$chk{$tag}); + } + $et->Warn(sprintf('%s tag 0x%.4x %s requires %s %s or higher', + $grp, $tag, $$et{TAG_INFO}{$key}{Name}, $verTag, $vstr)); + } + # make quick lookup for values based on tag ID + my $validValue = $validValue{$fileType}{$grp}; + foreach $tag (sort { $a <=> $b } keys %$validValue) { + my $val = $val{$tag}; + my ($pre, $post); + if (defined $$validValue{$tag}) { + #### eval ($val, %val) + my $result = eval $$validValue{$tag}; + if (not defined $result) { + $pre = 'Internal error validating'; + } elsif ($result eq '') { + $pre = defined $val ? 'Invalid value for' : "Missing required $fileType"; + } else { + next if $result eq '1'; + $pre = $result; + } + } else { + next unless defined $val; + $post = "is not allowed in $fileType"; + $minor = 1; + } + my $name; + if ($info{$tag}) { + $name = $info{$tag}{Name}; + } else { + my $table = 'Image::ExifTool::'.($grp eq 'GPS' ? 'GPS' : 'Exif').'::Main'; + my $tagInfo = GetTagTable($table)->{$tag}; + $tagInfo = $$tagInfo[0] if ref $tagInfo eq 'ARRAY'; + $name = $tagInfo ? $$tagInfo{Name} : '<unknown>'; + } + next if $$et{WrongFormat} and $$et{WrongFormat}{"$grp:$name"}; + $pre ? ($pre .= ' ') : ($pre = ''); + $post ? ($post = ' '.$post) : ($post = ''); + $et->Warn(sprintf('%s%s tag 0x%.4x %s%s', $pre, $grp, $tag, $name, $post), $minor); + } + } + } + # validate file extension + if ($$et{FILENAME} ne '') { + my $fileExt = ($$et{FILENAME} =~ /^.*\.([^.]+)$/s) ? uc($1) : ''; + my $extFileType = Image::ExifTool::GetFileType($fileExt); + if ($extFileType and $extFileType ne $fileType) { + my $normExt = $$et{VALUE}{FileTypeExtension}; + if ($normExt and $normExt ne $fileExt) { + my $lkup = $Image::ExifTool::fileTypeLookup{$fileExt}; + if (ref $lkup or $lkup ne $normExt) { + $et->Warn("File has wrong extension (should be $normExt, not $fileExt)"); + } + } + } + } + # issue warning if FastScan option used + $et->Warn('Validation incomplete because FastScan option used') if $et->Options('FastScan'); + + # generate Validate tag if necessary + if ($mkTag) { + my (@num, $key); + push @num, $$et{VALUE}{Error} ? ($$et{DUPL_TAG}{Error} || 0) + 1 : 0, + $$et{VALUE}{Warning} ? ($$et{DUPL_TAG}{Warning} || 0) + 1 : 0, 0; + for ($key = 'Warning'; ; ) { + ++$num[2] if $$et{VALUE}{$key} and $$et{VALUE}{$key} =~ /^\[minor\]/i; + $key = $et->NextTagKey($key) or last; + } + $et->FoundTag(Validate => "@num"); + } +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Validate - Additional metadata validation + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains additional routines and definitions used when the +ExifTool Validate option is enabled. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<Image::ExifTool::TagNames/Extra Tags> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Vorbis.pm b/ExifTool/lib/Image/ExifTool/Vorbis.pm new file mode 100644 index 0000000..7b475fc --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Vorbis.pm @@ -0,0 +1,255 @@ +#------------------------------------------------------------------------------ +# File: Vorbis.pm +# +# Description: Read Ogg Vorbis audio meta information +# +# Revisions: 2006/11/10 - P. Harvey Created +# 2011/07/12 - PH Moved Ogg to a separate module and added Theora +# +# References: 1) http://www.xiph.org/vorbis/doc/ +# 2) http://flac.sourceforge.net/ogg_mapping.html +# 3) http://www.theora.org/doc/Theora.pdf +#------------------------------------------------------------------------------ + +package Image::ExifTool::Vorbis; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.08'; + +sub ProcessComments($$$); + +# Vorbis header types +%Image::ExifTool::Vorbis::Main = ( + NOTES => q{ + Information extracted from Ogg Vorbis files. See + L<http://www.xiph.org/vorbis/doc/> for the Vorbis specification. + }, + 1 => { + Name => 'Identification', + SubDirectory => { TagTable => 'Image::ExifTool::Vorbis::Identification' }, + }, + 3 => { + Name => 'Comments', + SubDirectory => { TagTable => 'Image::ExifTool::Vorbis::Comments' }, + }, +); + +%Image::ExifTool::Vorbis::Identification = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Audio' }, + 0 => { + Name => 'VorbisVersion', + Format => 'int32u', + }, + 4 => 'AudioChannels', + 5 => { + Name => 'SampleRate', + Format => 'int32u', + }, + 9 => { + Name => 'MaximumBitrate', + Format => 'int32u', + RawConv => '$val || undef', + PrintConv => 'ConvertBitrate($val)', + }, + 13 => { + Name => 'NominalBitrate', + Format => 'int32u', + RawConv => '$val || undef', + PrintConv => 'ConvertBitrate($val)', + }, + 17 => { + Name => 'MinimumBitrate', + Format => 'int32u', + RawConv => '$val || undef', + PrintConv => 'ConvertBitrate($val)', + }, +); + +%Image::ExifTool::Vorbis::Comments = ( + PROCESS_PROC => \&ProcessComments, + GROUPS => { 2 => 'Audio' }, + NOTES => q{ + The tags below are only some common tags found in the Vorbis comments of Ogg + Vorbis and Ogg FLAC audio files, however ExifTool will extract values from + any tag found, even if not listed here. + }, + vendor => { Notes => 'from comment header' }, + TITLE => { Name => 'Title' }, + VERSION => { Name => 'Version' }, + ALBUM => { Name => 'Album' }, + TRACKNUMBER=>{ Name => 'TrackNumber' }, + ARTIST => { Name => 'Artist', Groups => { 2 => 'Author' }, List => 1 }, + PERFORMER => { Name => 'Performer', Groups => { 2 => 'Author' }, List => 1 }, + COPYRIGHT => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + LICENSE => { Name => 'License', Groups => { 2 => 'Author' } }, + ORGANIZATION=>{Name => 'Organization', Groups => { 2 => 'Author' } }, + DESCRIPTION=>{ Name => 'Description' }, + GENRE => { Name => 'Genre' }, + DATE => { Name => 'Date', Groups => { 2 => 'Time' } }, + LOCATION => { Name => 'Location', Groups => { 2 => 'Location' } }, + CONTACT => { Name => 'Contact', Groups => { 2 => 'Author' }, List => 1 }, + ISRC => { Name => 'ISRCNumber' }, + COVERARTMIME => { Name => 'CoverArtMIMEType' }, + COVERART => { + Name => 'CoverArt', + Groups => { 2 => 'Preview' }, + Notes => 'base64-encoded image', + ValueConv => q{ + require Image::ExifTool::XMP; + Image::ExifTool::XMP::DecodeBase64($val); + }, + }, + REPLAYGAIN_TRACK_PEAK => { Name => 'ReplayGainTrackPeak' }, + REPLAYGAIN_TRACK_GAIN => { Name => 'ReplayGainTrackGain' }, + REPLAYGAIN_ALBUM_PEAK => { Name => 'ReplayGainAlbumPeak' }, + REPLAYGAIN_ALBUM_GAIN => { Name => 'ReplayGainAlbumGain' }, + # observed in "Xiph.Org libVorbis I 20020717" ogg: + ENCODED_USING => { Name => 'EncodedUsing' }, + ENCODED_BY => { Name => 'EncodedBy' }, + COMMENT => { Name => 'Comment' }, + # in Theora documentation (ref 3) + DIRECTOR => { Name => 'Director' }, + PRODUCER => { Name => 'Producer' }, + COMPOSER => { Name => 'Composer' }, + ACTOR => { Name => 'Actor' }, + # Opus tags + ENCODER => { Name => 'Encoder' }, + ENCODER_OPTIONS => { Name => 'EncoderOptions' }, + METADATA_BLOCK_PICTURE => { + Name => 'Picture', + Binary => 1, + # ref https://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE + RawConv => q{ + require Image::ExifTool::XMP; + Image::ExifTool::XMP::DecodeBase64($val); + }, + SubDirectory => { + TagTable => 'Image::ExifTool::FLAC::Picture', + ByteOrder => 'BigEndian', + }, + }, +); + +# Vorbis composite tags +%Image::ExifTool::Vorbis::Composite = ( + Duration => { + Require => { + 0 => 'Vorbis:NominalBitrate', + 1 => 'FileSize', + }, + RawConv => '$val[0] ? $val[1] * 8 / $val[0] : undef', + PrintConv => 'ConvertDuration($val) . " (approx)"', # (only approximate) + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::Vorbis'); + + +#------------------------------------------------------------------------------ +# Process Vorbis Comments +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 on success, otherwise returns 0 and sets a Warning +sub ProcessComments($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $pos = $$dirInfo{DirStart} || 0; + my $end = $$dirInfo{DirLen} ? $pos + $$dirInfo{DirLen} : length $$dataPt; + my ($num, $index); + + SetByteOrder('II'); + for (;;) { + last if $pos + 4 > $end; + my $len = Get32u($dataPt, $pos); + last if $pos + 4 + $len > $end; + my $start = $pos + 4; + my $buff = substr($$dataPt, $start, $len); + $pos = $start + $len; + my ($tag, $val); + if (defined $num) { + $buff =~ /(.*?)=(.*)/s or last; + ($tag, $val) = (uc $1, $2); + # Vorbis tag ID's are all capitals, so they may conflict with our internal tags + # --> protect against this by adding a trailing underline if necessary + $tag .= '_' if $Image::ExifTool::specialTags{$tag}; + } else { + $tag = 'vendor'; + $val = $buff; + $num = ($pos + 4 < $end) ? Get32u($dataPt, $pos) : 0; + $et->VPrint(0, " + [Vorbis comments with $num entries]\n"); + $pos += 4; + } + # add tag to table unless it exists already + unless ($$tagTablePtr{$tag}) { + my $name = ucfirst(lc($tag)); + # remove invalid characters in tag name and capitalize following letters + $name =~ s/[^\w-]+(.?)/\U$1/sg; + $name =~ s/([a-z0-9])_([a-z])/$1\U$2/g; + $et->VPrint(0, " | [adding $tag]\n"); + AddTagToTable($tagTablePtr, $tag, { Name => $name }); + } + $et->HandleTag($tagTablePtr, $tag, $et->Decode($val, 'UTF8'), + Index => $index, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $start, + Size => $len, + ); + # all done if this was our last tag + $num-- or return 1; + $index = (defined $index) ? $index + 1 : 0; + } + $et->Warn('Format error in Vorbis comments'); + return 0; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Vorbis - Read Ogg Vorbis audio meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Ogg Vorbis audio headers. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.xiph.org/vorbis/doc/> + +=item L<http://flac.sourceforge.net/ogg_mapping.html> + +=item L<http://www.theora.org/doc/Theora.pdf> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/Vorbis Tags>, +L<Image::ExifTool::TagNames/Ogg Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/WPG.pm b/ExifTool/lib/Image/ExifTool/WPG.pm new file mode 100644 index 0000000..624627b --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WPG.pm @@ -0,0 +1,296 @@ +#------------------------------------------------------------------------------ +# File: WPG.pm +# +# Description: Read WordPerfect Graphics meta information +# +# Revisions: 2023-05-01 - P. Harvey Created +# +# References: 1) https://www.fileformat.info/format/wpg/egff.htm +# 2) https://archive.org/details/mac_Graphics_File_Formats_Second_Edition_1996/page/n991/mode/2up +# 3) http://libwpg.sourceforge.net/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::WPG; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.00'; + +sub PrintRecord($$$); + +# WPG metadata +%Image::ExifTool::WPG::Main = ( + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + VARS => { NO_ID => 1 }, + NOTES => 'Tags extracted from WordPerfect Graphics (WPG) images.', + WPGVersion => { }, + ImageWidthInches => { PrintConv => 'sprintf("%.2f",$val)' }, + ImageHeightInches => { PrintConv => 'sprintf("%.2f",$val)' }, + Records => { + Notes => 'records for version 1.0 files', + List => 1, + PrintHex => 2, + PrintConvColumns => 2, + PrintConv => { + OTHER => \&PrintRecord, + 0x01 => 'Fill Attributes', + 0x02 => 'Line Attributes', + 0x03 => 'Marker Attributes', + 0x04 => 'Polymarker', + 0x05 => 'Line', + 0x06 => 'Polyline', + 0x07 => 'Rectangle', + 0x08 => 'Polygon', + 0x09 => 'Ellipse', + 0x0a => 'Reserved', + 0x0b => 'Bitmap (Type 1)', + 0x0c => 'Graphics Text (Type 1)', + 0x0d => 'Graphics Text Attributes', + 0x0e => 'Color Map', + 0x0f => 'Start WPG (Type 1)', + 0x10 => 'End WPG', + 0x11 => 'PostScript Data (Type 1)', + 0x12 => 'Output Attributes', + 0x13 => 'Curved Polyline', + 0x14 => 'Bitmap (Type 2)', + 0x15 => 'Start Figure', + 0x16 => 'Start Chart', + 0x17 => 'PlanPerfect Data', + 0x18 => 'Graphics Text (Type 2)', + 0x19 => 'Start WPG (Type 2)', + 0x1a => 'Graphics Text (Type 3)', + 0x1b => 'PostScript Data (Type 2)', + }, + }, + RecordsV2 => { + Notes => 'records for version 2.0 files', + List => 1, + PrintHex => 2, + PrintConvColumns => 2, + PrintConv => { + OTHER => \&PrintRecord, + 0x00 => 'End Marker', + 0x01 => 'Start WPG', + 0x02 => 'End WPG', + 0x03 => 'Form Settings', + 0x04 => 'Ruler Settings', + 0x05 => 'Grid Settings', + 0x06 => 'Layer', + 0x08 => 'Pen Style Definition', + 0x09 => 'Pattern Definition', + 0x0a => 'Comment', + 0x0b => 'Color Transfer', + 0x0c => 'Color Palette', + 0x0d => 'DP Color Palette', + 0x0e => 'Bitmap Data', + 0x0f => 'Text Data', + 0x10 => 'Chart Style', + 0x11 => 'Chart Data', + 0x12 => 'Object Image', + 0x15 => 'Polyline', + 0x16 => 'Polyspline', + 0x17 => 'Polycurve', + 0x18 => 'Rectangle', + 0x19 => 'Arc', + 0x1a => 'Compound Polygon', + 0x1b => 'Bitmap', + 0x1c => 'Text Line', + 0x1d => 'Text Block', + 0x1e => 'Text Path', + 0x1f => 'Chart', + 0x20 => 'Group', + 0x21 => 'Object Capsule', + 0x22 => 'Font Settings', + 0x25 => 'Pen Fore Color', + 0x26 => 'DP Pen Fore Color', + 0x27 => 'Pen Back Color', + 0x28 => 'DP Pen Back Color', + 0x29 => 'Pen Style', + 0x2a => 'Pen Pattern', + 0x2b => 'Pen Size', + 0x2c => 'DP Pen Size', + 0x2d => 'Line Cap', + 0x2e => 'Line Join', + 0x2f => 'Brush Gradient', + 0x30 => 'DP Brush Gradient', + 0x31 => 'Brush Fore Color', + 0x32 => 'DP Brush Fore Color', + 0x33 => 'Brush Back Color', + 0x34 => 'DP Brush Back Color', + 0x35 => 'Brush Pattern', + 0x36 => 'Horizontal Line', + 0x37 => 'Vertical Line', + 0x38 => 'Poster Settings', + 0x39 => 'Image State', + 0x3a => 'Envelope Definition', + 0x3b => 'Envelope', + 0x3c => 'Texture Definition', + 0x3d => 'Brush Texture', + 0x3e => 'Texture Alignment', + 0x3f => 'Pen Texture ', + } + }, +); + +#------------------------------------------------------------------------------ +# Print record type +# Inputs: 0) record type and count, 1) inverse flag, 2) PrintConv hash ref +# Returns: converted record name +sub PrintRecord($$$) +{ + my ($val, $inv, $printConv) = @_; + my ($type, $count) = split 'x', $val; + my $prt = $$printConv{$type} || sprintf('Unknown (0x%.2x)', $type); + $prt .= " x $count" if $count; + return $prt; +} + +#------------------------------------------------------------------------------ +# Read variable-length integer +# Inputs: 0) RAF ref +# Returns: integer value +sub ReadVarInt($) +{ + my $raf = shift; + my $buff; + $raf->Read($buff, 1) or return 0; + my $val = ord($buff); + if ($val == 0xff) { + $raf->Read($buff, 2) == 2 or return 0; + $val = unpack('v', $buff); + if ($val & 0x8000) { + $raf->Read($buff, 2) == 2 or return 0; + $val = (($val & 0x7fff) << 16) | unpack('v', $buff); + } + } + return $val; +} + +#------------------------------------------------------------------------------ +# Read WPG version 1 or 2 image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid WPG file +sub ProcessWPG($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $lastType, $count); + + # verify this is a valid WPG file + return 0 unless $raf->Read($buff, 16) == 16; + return 0 unless $buff =~ /^\xff\x57\x50\x43/; + $et->SetFileType(); + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::WPG::Main'); + my $offset = Get32u(\$buff, 4); + my ($ver, $rev) = unpack('x10CC', $buff); + $et->HandleTag($tagTablePtr, WPGVersion => "$ver.$rev"); + if ($ver < 1 or $ver > 2) { + # currently support only version 1 and 2 formats + $et->Warn('Unsupported WPG version'); + return 1; + } + my $tag = $ver == 1 ? 'Records' : 'RecordsV2'; + $raf->Seek($offset - 16, 1) or return 1 if $offset > 16; + # loop through records + for (;;) { + my ($type, $len, $getSize); + if ($raf->Read($buff, $ver) == $ver) { # read 1 or 2 bytes, based on version + if ($ver == 1) { + # read version 1 record header + $type = ord($buff); + $len = ReadVarInt($raf); + $getSize = 1 if $type == 0x0f; # Start WPG (Type 1) + } else { + # read version 2 record header + $type = unpack('xC', $buff); + ReadVarInt($raf); # skip extensions + $len = ReadVarInt($raf); + $getSize = 1 if $type == 0x01; # Start WPG + undef $type if $type > 0x3f; + } + if ($getSize) { + # read Start record to obtain image size + $raf->Read($buff, $len) == $len or $et->Warn('File format error'), last; + my ($w, $h, $xres, $yres); + if ($ver == 1) { + ($w, $h) = unpack('x2vv', $buff); + } else { + my ($precision, $format); + ($xres, $yres, $precision) = unpack('vvC', $buff); + if ($precision == 0 and $len >= 21) { + $format = 'int16s'; + } elsif ($precision == 1 and $len >= 29) { + $format = 'int32s'; + } else { + $et->Warn('Invalid integer precision'); + next; + } + my ($x1,$y1,$x2,$y2) = ReadValue(\$buff, 13, $format, 4, $len-13); + $w = abs($x2 - $x1); + $h = abs($y2 - $y1); + } + $et->HandleTag($tagTablePtr, ImageWidthInches => $w / ($xres || 1200)); + $et->HandleTag($tagTablePtr, ImageHeightInches => $h / ($yres || 1200)); + } else { + $raf->Seek($len, 1) or last; # skip to the next record + } + } + # go to some trouble to collapse identical sequential entries in record list + # (trying to keep the length of the list managable for complex images) + $lastType and $type and $type == $lastType and ++$count, next; + if ($lastType) { + my $val = $count > 1 ? "${lastType}x$count" : $lastType; + $et->HandleTag($tagTablePtr, $tag => $val); + } + last unless $type; + $lastType = $type; + $count = 1; + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WPG - Read WPG meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read WPG +(WordPerfect Graphics) images. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://www.fileformat.info/format/wpg/egff.htm> + +=item L<https://archive.org/details/mac_Graphics_File_Formats_Second_Edition_1996/page/n991/mode/2up> + +=item L<http://libwpg.sourceforge.net/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/WPG Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/WTV.pm b/ExifTool/lib/Image/ExifTool/WTV.pm new file mode 100644 index 0000000..bed5772 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WTV.pm @@ -0,0 +1,319 @@ +#------------------------------------------------------------------------------ +# File: WTV.pm +# +# Description: Read WTV meta information +# +# Revisions: 2018-05-30 - P. Harvey Created +# +# References: 1) https://wiki.multimedia.cx/index.php?title=WTV +#------------------------------------------------------------------------------ + +package Image::ExifTool::WTV; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.00'; + +sub ProcessMetadata($$$); + +my %timeInfo = ( + # time looks like 100 ns intervals since 0:00 UTC Jan 1, 0001 (ref PH) + ValueConv => q{ # (719162 days from 0001:01:01 to 1970:01:01) + my $t = $val / 1e7 - 719162*24*3600; + return Image::ExifTool::ConvertUnixTime($t) . 'Z'; + }, + PrintConv => '$self->ConvertDateTime($val)', +); + +my %bool = ( PrintConv => { 0 => 'No', 1 => 'Yes' }, PrintConvColumns => 2 ); + +# WTV chunks +%Image::ExifTool::WTV::Main = ( + GROUPS => { 0 => 'WTV', 1 => 'WTV', 2 => 'Video' }, + NOTES => 'Tags found in Windows recorded TV (WTV) videos.', + # 'timeline.table.0.header.Events' (not decoded) + # 'timeline.table.0.entries.Events' (not decoded) + # 'timeline' (not decoded) + # 'table.0.header.legacy_attrib' (not decoded) + 'table.0.entries.legacy_attrib' => { + Name => 'Metdata', + SubDirectory => { TagTable => 'Image::ExifTool::WTV::Metadata' }, + }, + # 'table.0.redirector.legacy_attrib' (not decoded) + # 'table.0.header.time' (not decoded) + # 'table.0.entries.time' (not decoded) +); + +# Note: Many of these tags are similar to those found in Image::ExifTool::Microsoft::Xtra +# and Image::ExifTool::ASF::ExtendedDescr +%Image::ExifTool::WTV::Metadata = ( + GROUPS => { 0 => 'WTV', 1 => 'WTV', 2 => 'Video' }, + PROCESS_PROC => \&ProcessMetadata, + NOTES => 'ExifTool will extract any tag found, even if not in this table.', + VARS => { NO_ID => 1 }, + 'Duration' => { + Name => 'Duration', + ValueConv => '$val/1e7', + PrintConv => 'ConvertDuration($val)', + }, + 'Title' => { }, + 'WM/Genre' => 'Genre', + 'WM/Language' => 'Language', + 'WM/MediaClassPrimaryID' => 'MediaClassPrimaryID', + 'WM/MediaClassSecondaryID' => 'MediaClassSecondaryID', + 'WM/MediaCredits' => 'MediaCredits', + 'WM/MediaIsDelay' => { Name => 'MediaIsDelay', %bool }, + 'WM/MediaIsFinale' => { Name => 'MediaIsFinale', %bool }, + 'WM/MediaIsLive' => { Name => 'MediaIsLive', %bool }, + 'WM/MediaIsMovie' => { Name => 'MediaIsMovie', %bool }, + 'WM/MediaIsPremiere' => { Name => 'MediaIsPremiere', %bool }, + 'WM/MediaIsRepeat' => { Name => 'MediaIsRepeat', %bool }, + 'WM/MediaIsSAP' => { Name => 'MediaIsSAP', %bool }, + 'WM/MediaIsSport' => { Name => 'MediaIsSport', %bool }, + 'WM/MediaIsStereo' => { Name => 'MediaIsStereo', %bool, Groups => { 2 => 'Audio' } }, + 'WM/MediaIsSubtitled' => { Name => 'MediaIsSubtitled',%bool }, + 'WM/MediaIsTape' => { Name => 'MediaIsTape', %bool }, + 'WM/MediaNetworkAffiliation'=> 'MediaNetworkAffiliation', + 'WM/MediaOriginalBroadcastDateTime' => { + Name => 'MediaOriginalBroadcastDateTime', + Groups => { 2 => 'Time' }, + ValueConv => '$val =~ tr/-T/: /; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + 'WM/MediaOriginalChannel' => { Name => 'MediaOriginalChannel' }, + 'WM/MediaOriginalChannelSubNumber' => { Name => 'MediaOriginalChannelSubNumber' }, + 'WM/MediaOriginalRunTime' => { + Name => 'MediaOriginalRunTime', + ValueConv => '$val / 1e7', + PrintConv => 'ConvertDuration($val)', + }, + 'WM/MediaStationCallSign' => 'MediaStationCallSign', + 'WM/MediaStationName' => 'MediaStationName', + 'WM/MediaThumbAspectRatioX' => 'MediaThumbAspectRatioX', + 'WM/MediaThumbAspectRatioY' => 'MediaThumbAspectRatioY', + 'WM/MediaThumbHeight' => 'MediaThumbHeight', + 'WM/MediaThumbRatingAttributes' => { Name => 'MediaThumbRatingAttributes' }, + 'WM/MediaThumbRatingLevel' => 'MediaThumbRatingLevel', + 'WM/MediaThumbRatingSystem' => 'MediaThumbRatingSystem', + 'WM/MediaThumbRet' => 'MediaThumbRet', + 'WM/MediaThumbStride' => 'MediaThumbStride', + 'WM/MediaThumbTimeStamp' => { Name => 'MediaThumbTimeStamp', Notes => 'unknown units', Unknown => 1 }, + 'WM/MediaThumbWidth' => 'MediaThumbWidth', + 'WM/OriginalReleaseTime' => { + Name => 'OriginalReleaseTime', + Groups => { 2 => 'Time' }, + ValueConv => '$val=~tr/-T/: /; $val', + PrintConv => '$self->ConvertDateTime($val)', + }, + 'WM/ParentalRating' => 'ParentalRating', + 'WM/ParentalRatingReason' => 'ParentalRatingReason', + 'WM/Provider' => 'Provider', + 'WM/ProviderCopyright' => 'ProviderCopyright', + 'WM/ProviderRating' => 'ProviderRating', + 'WM/SubTitle' => 'Subtitle', + 'WM/SubTitleDescription' => 'SubtitleDescription', + 'WM/VideoClosedCaptioning' => { Name => 'VideoClosedCaptioning', %bool }, + 'WM/WMRVATSCContent' => { Name => 'ATSCContent', %bool }, + 'WM/WMRVActualSoftPostPadding' => 'ActualSoftPostPadding', + 'WM/WMRVActualSoftPrePadding' => 'ActualSoftPrePadding', + 'WM/WMRVBitrate' => { Name => 'Bitrate', Notes => 'unknown units', Unknown => 1 }, + 'WM/WMRVBrandingImageID' => 'BrandingImageID', + 'WM/WMRVBrandingName' => 'BrandingName', + 'WM/WMRVContentProtected' => { Name => 'ContentProtected', %bool }, + 'WM/WMRVContentProtectedPercent' => 'ContentProtectedPercent', + 'WM/WMRVDTVContent' => { Name => 'DTVContent', %bool }, + 'WM/WMRVEncodeTime' => { Name => 'EncodeTime', Groups => { 2 => 'Time' }, %timeInfo }, + 'WM/WMRVEndTime' => { Name => 'EndTime', Groups => { 2 => 'Time' }, %timeInfo }, + 'WM/WMRVExpirationDate' => { Name => 'ExpirationDate', Groups => { 2 => 'Time' }, %timeInfo, Unknown => 1 }, + 'WM/WMRVExpirationSpan' => { Name => 'ExpirationSpan', Notes => 'unknown units', Unknown => 1 }, + 'WM/WMRVHDContent' => { Name => 'HDContent', %bool }, + 'WM/WMRVHardPostPadding' => 'HardPostPadding', + 'WM/WMRVHardPrePadding' => 'HardPrePadding', + 'WM/WMRVInBandRatingAttributes' => 'InBandRatingAttributes', + 'WM/WMRVInBandRatingLevel' => 'InBandRatingLevel', + 'WM/WMRVInBandRatingSystem' => 'InBandRatingSystem', + 'WM/WMRVKeepUntil' => 'KeepUntil', + 'WM/WMRVOriginalSoftPostPadding'=> 'OriginalSoftPostPadding', + 'WM/WMRVOriginalSoftPrePadding' => 'OriginalSoftPrePadding', + 'WM/WMRVProgramID' => 'ProgramID', + 'WM/WMRVQuality' => 'Quality', + 'WM/WMRVRequestID' => 'RequestID', + 'WM/WMRVScheduleItemID' => 'ScheduleItemID', + 'WM/WMRVSeriesUID' => 'SeriesUID', + 'WM/WMRVServiceID' => 'ServiceID', + 'WM/WMRVWatched' => { Name => 'Watched', %bool }, +); + +#------------------------------------------------------------------------------ +# Read specified sectors from the file +# Inputs: 0) raf ref, 1) sector table ref, 2) offset in sector table, 3) sector size +# Returns: Data or undef on error +sub ReadSectors($$$$) +{ + my ($raf, $secPt, $pos, $secSize) = @_; + my ($data, $buff); + while ($pos <= length($$secPt) - 4) { + my $sec = Get32u($secPt, $pos); + return undef if $sec == 0xffff; # (just in case) + last unless $sec; # a null marks the end of the sector table + defined($data) ? ($data .= $buff) : ($data = $buff); + return undef unless $raf->Seek($sec*$secSize,0) and $raf->Read($buff,$secSize) == $secSize; + $pos += 4; + } + return defined($data) ? $data . $buff : $buff; +} + +#------------------------------------------------------------------------------ +# Process WTV metadata +# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref +# Returns: 1 on success +sub ProcessMetadata($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $pos = 0; + my $end = length $$dataPt; + $et->VerboseDir('WTV Metadata', undef, $end); + while ($pos + 0x18 < $end) { + last unless substr($$dataPt,$pos,16) eq "\x5a\xfe\xd7\x6d\xc8\x1d\x8f\x4a\x99\x22\xfa\xb1\x1c\x38\x14\x53"; + my $fmt = Get32u($dataPt, $pos + 0x10); + my $len = Get32u($dataPt, $pos + 0x14); + my $str = ''; + $pos += 0x18; + for (;;) { + $pos + 2 > $end and $et->Warn('Corrupt metadata directory'), last; + my $ch = substr($$dataPt, $pos, 2); + $pos += 2; + last if $ch eq "\0\0"; + $str .= $ch; + } + last if $pos + $len > $end; + my $tag = $et->Decode($str, 'UCS2', undef, 'UTF8'); + my $dat = substr($$dataPt, $pos, $len); + # add tag if not already there + unless ($$tagTablePtr{$tag}) { + my $name = $tag; + $name =~ s{^(WTV_Metadata_)?WM/(WMRV)?}{}; + AddTagToTable($tagTablePtr, $tag, $name); + $et->VPrint(0, $$et{INDENT}, "[adding WTV:$name]\n"); + } + my $val; + if ($fmt==0 or $fmt==3) { # int32u or boolean32 + $val = Get32s(\$dat, 0); + } elsif ($fmt == 1) { # string + $val = $et->Decode($dat, 'UCS2'); + } elsif ($fmt == 6) { # GUID + $val = unpack('H*', $dat); + } elsif ($fmt == 4) { # int64u (date/time values use this) + $val = Get64u(\$dat, 0); + } else { + $val = $dat; + $fmt = "Unknown($fmt)"; + } + $et->HandleTag($tagTablePtr, $tag, $val, + Format => "format $fmt", + Size => length $dat, + ); + $et->VerboseDump(\$dat); + $pos += $len; + } +} + +#------------------------------------------------------------------------------ +# Extract information from a WTV video +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid WTV file +sub ProcessWTV($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $verbose = $et->Options('Verbose'); + my ($buff, $tagTablePtr, $pos, $len); + + # verify this is a valid WTV file + return 0 unless $raf->Read($buff, 0x60) == 0x60; + return 0 unless $buff =~ /^\xb7\xd8\x00\x20\x37\x49\xda\x11\xa6\x4e\x00\x07\xe9\x5e\xad\x8d/; + $et->SetFileType(); + SetByteOrder('II'); + # 0x28 - int32u: sector size? (=0x1000) (PH NC) + # 0x38 - int32u: sector number for main WTV directory (PH assume this is a sector table, NC) + # 0x58 - int32u: total number of sectors in file + my $secSize = Get32u(\$buff, 0x28); + # in case I'm wrong about this, constrain sector size to + # either 0x1000 (standard) or 0x100 (ExifTool test file) - PH + $secSize = 0x1000 unless $secSize == 0x1000 or $secSize == 0x100; + $buff = ReadSectors($raf, \$buff, 0x38, $secSize); # read the WTV directory + return 0 unless defined $buff; + $tagTablePtr = GetTagTable('Image::ExifTool::WTV::Main'); + # parse the WTV directory + $et->VerboseDir('WTV'); + for ($pos=0; $pos<length($buff)-0x28; $pos+=$len) { + unless (substr($buff,$pos,0x10) eq "\x92\xb7\x74\x91\x59\x70\x70\x44\x88\xdf\x06\x3b\x82\xcc\x21\x3d") { + $et->Warn("WTV directory wasn't at expected location") unless $pos; + last; + } + $len = Get32u(\$buff, $pos+0x10); + last if $pos + $len > length($buff); + my $n = Get32u(\$buff, $pos + 0x20); + 0x28 + $n*2 + 8 > $len and $et->Warn('WTV directory error'), last; + my $tag = $et->Decode(substr($buff,$pos+0x28,$n*2), 'UCS2', undef, 'UTF8'); + my $ptr = $pos + 0x28 + $n * 2; + my $flg = Get32u(\$buff, $ptr + 4); + if ($verbose) { + my $s = Get32s(\$buff, $ptr); + $s = sprintf('0x%x', $s) unless $s < 0; + $et->VPrint(1,"- Tag '${tag}' (sector=$s, flag=$flg)"); + } + next unless $$tagTablePtr{$tag} and ($flg == 0 or $flg == 1); + my $sec = substr($buff, $ptr, 4); + my $data = ReadSectors($raf, \$sec, 0, $secSize); + last unless defined $data; + # read sectors from table if necessary (flag=1 indicates a sector table) + $data = ReadSectors($raf, \$data, 0, $secSize) if $flg == 1; + defined $data or $et->Warn("Error fetching data for $tag"), next; + $et->HandleTag($tagTablePtr, $tag, $data); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WTV - Read WTV meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read WTV +(Windows recorded TV show) videos. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://wiki.multimedia.cx/index.php?title=WTV> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/WTV Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/WriteCanonRaw.pl b/ExifTool/lib/Image/ExifTool/WriteCanonRaw.pl new file mode 100644 index 0000000..668e617 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WriteCanonRaw.pl @@ -0,0 +1,644 @@ +#------------------------------------------------------------------------------ +# File: WriteCanonRaw.pl +# +# Description: Write Canon RAW (CRW and CR2) meta information +# +# Revisions: 01/25/2005 - P. Harvey Created +# 09/16/2010 - PH Added ability to write XMP in CRW images +#------------------------------------------------------------------------------ +package Image::ExifTool::CanonRaw; + +use strict; +use vars qw($VERSION $AUTOLOAD %crwTagFormat); +use Image::ExifTool::Fixup; + +# map for adding directories to CRW +my %crwMap = ( + XMP => 'CanonVRD', + CanonVRD => 'Trailer', +); + +# mappings to from RAW tagID to MakerNotes tagID +# (Note: upper two bits of RawTagID are zero) +my %mapRawTag = ( + # RawTagID => Canon TagID + 0x080b => 0x07, # CanonFirmwareVersion + 0x0810 => 0x09, # OwnerName + 0x0815 => 0x06, # CanonImageType + 0x1028 => 0x03, # (unknown if no tag name specified) + 0x1029 => 0x02, # FocalLength + 0x102a => 0x04, # CanonShotInfo + 0x102d => 0x01, # CanonCameraSettings + 0x1033 => 0x0f, # CanonCustomFunctions (only verified for 10D) + 0x1038 => 0x12, # CanonAFInfo + 0x1039 => 0x13, + 0x1093 => 0x93, + 0x10a8 => 0xa8, + 0x10a9 => 0xa9, # WhiteBalanceTable + 0x10aa => 0xaa, + 0x10ae => 0xae, # ColorTemperature + 0x10b4 => 0xb4, # ColorSpace + 0x10b5 => 0xb5, + 0x10c0 => 0xc0, + 0x10c1 => 0xc1, + 0x180b => 0x0c, # SerialNumber + 0x1817 => 0x08, # FileNumber + 0x1834 => 0x10, + 0x183b => 0x15, +); +# translation from Rotation to Orientation values +my %mapRotation = ( + 0 => 1, + 90 => 6, + 180 => 3, + 270 => 8, +); + + +#------------------------------------------------------------------------------ +# Initialize buffers for building MakerNotes from RAW data +# Inputs: 0) ExifTool object reference +sub InitMakerNotes($) +{ + my $et = shift; + $$et{MAKER_NOTE_INFO} = { + Entries => { }, # directory entries keyed by tagID + ValBuff => "\0\0\0\0", # value data buffer (start with zero nextIFD pointer) + FixupTags => { }, # flags for tags with data in value buffer + }; +} + +#------------------------------------------------------------------------------ +# Build maker notes from CanonRaw information +# Inputs: 0) ExifTool object reference, 1) raw tag ID, 2) reference to tagInfo +# 3) reference to value, 4) format name, 5) count +# Notes: This will build the directory in the order the tags are found in the CRW +# file, which isn't sequential (but Canon's version isn't sequential either...) +sub BuildMakerNotes($$$$$$) +{ + my ($et, $rawTag, $tagInfo, $valuePt, $formName, $count) = @_; + + my $tagID = $mapRawTag{$rawTag} || return; + $formName or warn(sprintf "No format for tag 0x%x!\n",$rawTag), return; + # special case: ignore user comment because it gets saved in EXIF + # (and has the same raw tagID as CanonFileDescription) + return if $tagInfo and $$tagInfo{Name} eq 'UserComment'; + my $format = $Image::ExifTool::Exif::formatNumber{$formName}; + my $fsiz = $Image::ExifTool::Exif::formatSize[$format]; + my $size = length($$valuePt); + my $value; + if ($count and $size != $count * $fsiz) { + if ($size < $count * $fsiz) { + warn sprintf("Value too short for raw tag 0x%x\n",$rawTag); + return; + } + # shorten value appropriately + $size = $count * $fsiz; + $value = substr($$valuePt, 0, $size); + } else { + $count = $size / $fsiz; + $value = $$valuePt; + } + my $offsetVal; + my $makerInfo = $$et{MAKER_NOTE_INFO}; + if ($size > 4) { + my $len = length $makerInfo->{ValBuff}; + $offsetVal = Set32u($len); + $makerInfo->{ValBuff} .= $value; + # pad to an even number of bytes + $size & 0x01 and $makerInfo->{ValBuff} .= "\0"; + # set flag indicating that this tag needs a fixup + $makerInfo->{FixupTags}->{$tagID} = 1; + } else { + $offsetVal = $value; + $size < 4 and $offsetVal .= "\0" x (4 - $size); + } + $makerInfo->{Entries}->{$tagID} = Set16u($tagID) . Set16u($format) . + Set32u($count) . $offsetVal; +} + +#------------------------------------------------------------------------------ +# Finish building and save MakerNotes +# Inputs: 0) ExifTool object reference +sub SaveMakerNotes($) +{ + my $et = shift; + # save maker notes + my $makerInfo = $$et{MAKER_NOTE_INFO}; + delete $$et{MAKER_NOTE_INFO}; + my $dirEntries = $makerInfo->{Entries}; + my $numEntries = scalar(keys %$dirEntries); + my $fixup = new Image::ExifTool::Fixup; + return unless $numEntries; + # build the MakerNotes directory + my $makerNotes = Set16u($numEntries); + my $tagID; + # write the entries in proper tag order (even though Canon doesn't do this...) + foreach $tagID (sort { $a <=> $b } keys %$dirEntries) { + $makerNotes .= $$dirEntries{$tagID}; + next unless $makerInfo->{FixupTags}->{$tagID}; + # add fixup for this pointer + $fixup->AddFixup(length($makerNotes) - 4); + } + # save position of maker notes for pointer fixups + $fixup->{Shift} += length($makerNotes); + $$et{MAKER_NOTE_FIXUP} = $fixup; + $$et{MAKER_NOTE_BYTE_ORDER} = GetByteOrder(); + # add value data + $makerNotes .= $makerInfo->{ValBuff}; + # get MakerNotes tag info + my $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::Exif::Main'); + my $tagInfo = $et->GetTagInfo($tagTablePtr, 0x927c, \$makerNotes); + # save the MakerNotes + $et->FoundTag($tagInfo, $makerNotes); + # save the garbage collection some work later + delete $makerInfo->{Entries}; + delete $makerInfo->{ValBuff}; + delete $makerInfo->{FixupTags}; + # also generate Orientation tag since Rotation isn't transferred from RAW info + my $rotation = $et->GetValue('Rotation', 'ValueConv'); + if (defined $rotation and defined $mapRotation{$rotation}) { + $tagInfo = $et->GetTagInfo($tagTablePtr, 0x112); + $et->FoundTag($tagInfo, $mapRotation{$rotation}); + } +} + +#------------------------------------------------------------------------------ +# Check CanonRaw information +# Inputs: 0) ExifTool object reference, 1) tagInfo hash reference, +# 2) raw value reference +# Returns: error string or undef (and may change value) on success +sub CheckCanonRaw($$$) +{ + my ($et, $tagInfo, $valPtr) = @_; + my $tagName = $$tagInfo{Name}; + if ($tagName eq 'JpgFromRaw' or $tagName eq 'ThumbnailImage') { + unless ($$valPtr =~ /^\xff\xd8/ or $et->Options('IgnoreMinorErrors')) { + return '[Minor] Not a valid image'; + } + } else { + my $format = $$tagInfo{Format}; + my $count = $$tagInfo{Count}; + unless ($format) { + my $tagType = ($$tagInfo{TagID} >> 8) & 0x38; + $format = $crwTagFormat{$tagType}; + } + $format and return Image::ExifTool::CheckValue($valPtr, $format, $count); + } + return undef; +} + +#------------------------------------------------------------------------------ +# Write CR2 file +# Inputs: 0) ExifTool ref, 1) dirInfo reference (must have read first 16 bytes) +# 2) tag table reference +# Returns: true on success +sub WriteCR2($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt} or return 0; + my $outfile = $$dirInfo{OutFile} or return 0; + $$dirInfo{RAF} or return 0; + + # check CR2 signature + if ($$dataPt !~ /^.{8}CR\x02\0/s) { + my ($msg, $minor); + if ($$dataPt =~ /^.{8}CR/s) { + $msg = 'Unsupported Canon RAW file. May cause problems if rewritten'; + $minor = 1; + } elsif ($$dataPt =~ /^.{8}\xba\xb0\xac\xbb/s) { + $msg = 'Can not currently write Canon 1D RAW images'; + } else { + $msg = 'Unrecognized Canon RAW file'; + } + return 0 if $et->Error($msg, $minor); + } + + # CR2 has a 16-byte header + $$dirInfo{NewDataPos} = 16; + my $newData = $et->WriteDirectory($dirInfo, $tagTablePtr); + return 0 unless defined $newData; + unless ($$dirInfo{LastIFD}) { + $et->Error("CR2 image IFD may not be deleted"); + return 0; + } + + if (length($newData)) { + # build 16 byte header for Canon RAW file + my $header = substr($$dataPt, 0, 16); + # set IFD0 pointer (may not be 16 if edited by PhotoMechanic) + Set32u(16, \$header, 4); + # last 4 bytes of header is pointer to last IFD + Set32u($$dirInfo{LastIFD}, \$header, 12); + Write($outfile, $header, $newData) or return 0; + undef $newData; # free memory + + # copy over image data now if necessary + if (ref $$dirInfo{ImageData}) { + $et->CopyImageData($$dirInfo{ImageData}, $outfile) or return 0; + delete $$dirInfo{ImageData}; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Write CanonRaw (CRW) information +# Inputs: 0) ExifTool object reference, 1) source dirInfo reference, +# 2) tag table reference +# Returns: true on success +# Notes: Increments ExifTool CHANGED flag for each tag changed This routine is +# different from all of the other write routines because Canon RAW files are +# designed well! So it isn't necessary to buffer the data in memory before +# writing it out. Therefore this routine doesn't return the directory data as +# the rest of the Write routines do. Instead, it writes to the dirInfo +# OutFile on the fly --> much faster, efficient, and less demanding on memory! +sub WriteCanonRaw($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + my $blockStart = $$dirInfo{DirStart}; + my $blockSize = $$dirInfo{DirLen}; + my $raf = $$dirInfo{RAF} or return 0; + my $outfile = $$dirInfo{OutFile} or return 0; + my $outPos = $$dirInfo{OutPos} or return 0; + my $outBase = $outPos; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my ($buff, $tagInfo); + + # 4 bytes at end of block give directory position within block + $raf->Seek($blockStart+$blockSize-4, 0) or return 0; + $raf->Read($buff, 4) == 4 or return 0; + my $dirOffset = Get32u(\$buff,0) + $blockStart; + # avoid infinite recursion + $$et{ProcessedCanonRaw} or $$et{ProcessedCanonRaw} = { }; + if ($$et{ProcessedCanonRaw}{$dirOffset}) { + $et->Error("Double-referenced $$dirInfo{DirName} directory"); + return 0; + } + $$et{ProcessedCanonRaw}{$dirOffset} = 1; + $raf->Seek($dirOffset, 0) or return 0; + $raf->Read($buff, 2) == 2 or return 0; + my $entries = Get16u(\$buff,0); # get number of entries in directory + # read the directory (10 bytes per entry) + $raf->Read($buff, 10 * $entries) == 10 * $entries or return 0; + my $newDir = ''; + + # get hash of new information keyed by tagID + my $newTags = $et->GetNewTagInfoHash($tagTablePtr); + + # generate list of tags to add or delete (currently, we only allow JpgFromRaw + # and ThumbnailImage, to be added or deleted from the root CanonRaw directory) + my (@addTags, %delTag); + if ($$dirInfo{Nesting} == 0) { + my $tagID; + foreach $tagID (keys %$newTags) { + my $permanent = $newTags->{$tagID}->{Permanent}; + push(@addTags, $tagID) if defined($permanent) and not $permanent; + } + } + + my $index; + for ($index=0; ; ++$index) { + my ($pt, $tag, $size, $valuePtr, $ptr, $value); + if ($index<$entries) { + $pt = 10 * $index; + $tag = Get16u(\$buff, $pt); + $size = Get32u(\$buff, $pt+2); + $valuePtr = Get32u(\$buff, $pt+6); + $ptr = $valuePtr + $blockStart; # all pointers relative to block start + } + # add any required new tags + # NOTE: can't currently add tags where value is stored in directory + if (@addTags and (not defined($tag) or $tag >= $addTags[0])) { + my $addTag = shift @addTags; + $tagInfo = $$newTags{$addTag}; + my $newVal = $et->GetNewValue($tagInfo); + if (defined $newVal) { + # pad value to an even length (Canon ImageBrowser and ZoomBrowser + # version 6.1.1 have problems with odd-sized embedded JPEG images + # even if the value is padded to maintain alignment, so do this + # before calculating the size for the directory entry) + $newVal .= "\0" if length($newVal) & 0x01; + # add new directory entry + $newDir .= Set16u($addTag) . Set32u(length($newVal)) . + Set32u($outPos - $outBase); + # write new value data + Write($outfile, $newVal) or return 0; + $outPos += length($newVal); # update current position + $verbose > 1 and print $out " + CanonRaw:$$tagInfo{Name}\n"; + ++$$et{CHANGED}; + } + # set flag to delete this tag if found later + $delTag{$addTag} = 1; + } + last unless defined $tag; # all done if no more directory entries + return 0 if $tag & 0x8000; # top bit should not be set + my $tagID = $tag & 0x3fff; # get tag ID + my $tagType = ($tag >> 8) & 0x38; # get tag type + my $valueInDir = ($tag & 0x4000); # flag for value in directory + + my $tagInfo = $et->GetTagInfo($tagTablePtr,$tagID); + my $format = $crwTagFormat{$tagType}; + my ($count, $subdir); + if ($tagInfo) { + $subdir = $$tagInfo{SubDirectory}; + $format = $$tagInfo{Format} if $$tagInfo{Format}; + $count = $$tagInfo{Count}; + } + if ($valueInDir) { + $size = 8; + $value = substr($buff, $pt+2, $size); + # set count to 1 by default for normal values in directory + $count = 1 if not defined $count and $format and + $format ne 'string' and not $subdir; + } else { + if ($tagType==0x28 or $tagType==0x30) { + # this type of tag specifies a raw subdirectory + my $name; + $tagInfo and $name = $$tagInfo{Name}; + $name or $name = sprintf("CanonRaw_0x%.4x", $tagID); + my %subdirInfo = ( + DirName => $name, + DataLen => 0, + DirStart => $ptr, + DirLen => $size, + Nesting => $$dirInfo{Nesting} + 1, + RAF => $raf, + Parent => $$dirInfo{DirName}, + OutFile => $outfile, + OutPos => $outPos, + ); + my $result = $et->WriteDirectory(\%subdirInfo, $tagTablePtr); + return 0 unless $result; + # set size and pointer for this new directory + $size = $subdirInfo{OutPos} - $outPos; + $valuePtr = $outPos - $outBase; + $outPos = $subdirInfo{OutPos}; + } else { + # verify that the value data is within this block + $valuePtr + $size <= $blockSize or return 0; + # read value from file + $raf->Seek($ptr, 0) or return 0; + $raf->Read($value, $size) == $size or return 0; + } + } + # set count from tagInfo count if necessary + if ($format and not $count) { + # set count according to format and size + my $fnum = $Image::ExifTool::Exif::formatNumber{$format}; + my $fsiz = $Image::ExifTool::Exif::formatSize[$fnum]; + $count = int($size / $fsiz); + } + # edit subdirectory if necessary + if ($tagInfo) { + if ($subdir and $$subdir{TagTable}) { + my $name = $$tagInfo{Name}; + my $newTagTable = Image::ExifTool::GetTagTable($$subdir{TagTable}); + return 0 unless $newTagTable; + my $subdirStart = 0; + #### eval Start () + $subdirStart = eval $$subdir{Start} if $$subdir{Start}; + my $dirData = \$value; + my %subdirInfo = ( + Name => $name, + DataPt => $dirData, + DataLen => $size, + DirStart => $subdirStart, + DirLen => $size - $subdirStart, + Nesting => $$dirInfo{Nesting} + 1, + RAF => $raf, + Parent => $$dirInfo{DirName}, + ); + #### eval Validate ($dirData, $subdirStart, $size) + if (defined $$subdir{Validate} and not eval $$subdir{Validate}) { + $et->Warn("Invalid $name data"); + } else { + $subdir = $et->WriteDirectory(\%subdirInfo, $newTagTable); + if (defined $subdir and length $subdir) { + if ($subdirStart) { + # add header before data directory + $value = substr($value, 0, $subdirStart) . $subdir; + } else { + $value = $subdir; + } + } + } + } elsif ($$newTags{$tagID}) { + if ($delTag{$tagID}) { + $verbose > 1 and print $out " - CanonRaw:$$tagInfo{Name}\n"; + ++$$et{CHANGED}; + next; # next since we already added this tag + } + my $oldVal; + if ($format) { + $oldVal = ReadValue(\$value, 0, $format, $count, $size); + } else { + $oldVal = $value; + } + my $nvHash = $et->GetNewValueHash($tagInfo); + if ($et->IsOverwriting($nvHash, $oldVal)) { + my $newVal = $et->GetNewValue($nvHash); + my $verboseVal; + $verboseVal = $newVal if $verbose > 1; + # convert to specified format if necessary + if (defined $newVal and $format) { + $newVal = WriteValue($newVal, $format, $count); + } + if (defined $newVal) { + $value = $newVal; + ++$$et{CHANGED}; + $et->VerboseValue("- CanonRaw:$$tagInfo{Name}", $oldVal); + $et->VerboseValue("+ CanonRaw:$$tagInfo{Name}", $verboseVal); + } + } + } + } + if ($valueInDir) { + my $len = length $value; + if ($len < 8) { + # pad with original garbage in case it contained something useful + $value .= substr($buff, $pt+2+8-$len, 8-$len); + } elsif ($len > 8) { # this shouldn't happen + warn "Value too long! -- truncated\n"; + $value = substr($value, 0, 8); + } + # create new directory entry + $newDir .= Set16u($tag) . $value; + next; # all done this entry + } + if (defined $value) { + # don't allow value to change length unless Writable is 'resize' + my $writable = $$tagInfo{Writable}; + my $diff = length($value) - $size; + if ($diff) { + if ($writable and $writable eq 'resize') { + $size += $diff; # allow size to change + } elsif ($diff > 0) { + $value .= ("\0" x $diff); + } else { + $value = substr($value, 0, $size); + } + } + # pad value if necessary to align on even-byte boundary (as per CIFF spec) + $value .= "\0" if $size & 0x01; + $valuePtr = $outPos - $outBase; + # write out value data + Write($outfile, $value) or return 0; + $outPos += length($value); # update current position in outfile + } + # create new directory entry + $newDir .= Set16u($tag) . Set32u($size) . Set32u($valuePtr); + } + # add the directory counts and offset to the directory start, + $entries = length($newDir) / 10; + $newDir = Set16u($entries) . $newDir . Set32u($outPos - $outBase); + # write directory data + Write($outfile, $newDir) or return 0; + + # update current output file position in dirInfo + $$dirInfo{OutPos} = $outPos + length($newDir); + # save outfile directory start (needed for rewriting VRD trailer) + $$dirInfo{OutDirStart} = $outPos - $outBase; + + return 1; +} + +#------------------------------------------------------------------------------ +# write Canon RAW (CRW) file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid CRW file, +# or -1 if a write error occurred +sub WriteCRW($$) +{ + my ($et, $dirInfo) = @_; + my $outfile = $$dirInfo{OutFile}; + my $raf = $$dirInfo{RAF}; + my $rtnVal = 0; + my ($buff, $err, $sig); + + $raf->Read($buff,2) == 2 or return 0; + SetByteOrder($buff) or return 0; + $raf->Read($buff,4) == 4 or return 0; + $raf->Read($sig,8) == 8 or return 0; # get file signature + $sig =~ /^HEAP(CCDR|JPGM)/ or return 0; # validate signature + my $type = $1; + my $hlen = Get32u(\$buff, 0); # get header length + + if ($$et{DEL_GROUP}{MakerNotes}) { + if ($type eq 'CCDR') { + $et->Error("Can't delete MakerNotes from CRW"); + return 0; + } else { + ++$$et{CHANGED}; + return 1; + } + } + # make XMP the preferred group for CRW files + if ($$et{FILE_TYPE} eq 'CRW') { + $et->InitWriteDirs(\%crwMap, 'XMP'); + } + + # write header + $raf->Seek(0, 0) or return 0; + $raf->Read($buff, $hlen) == $hlen or return 0; + Write($outfile, $buff) or $err = 1; + + $raf->Seek(0, 2) or return 0; # seek to end of file + my $filesize = $raf->Tell() or return 0; + + # build directory information for main raw directory + my %dirInfo = ( + DataLen => 0, + DirStart => $hlen, + DirLen => $filesize - $hlen, + Nesting => 0, + RAF => $raf, + Parent => 'CRW', + OutFile => $outfile, + OutPos => $hlen, + ); + # process the raw directory + my $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::CanonRaw::Main'); + my $success = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + + my $trailPt; + while ($success) { + # check to see if trailer(s) exist(s) + my $trailInfo = Image::ExifTool::IdentifyTrailer($raf) or last; + # rewrite the trailer(s) + $buff = ''; + $$trailInfo{OutFile} = \$buff; + $success = $et->ProcessTrailers($trailInfo) or last; + $trailPt = $$trailInfo{OutFile}; + # nothing to write if trailers were deleted + undef $trailPt if length($$trailPt) < 4; + last; + } + if ($success) { + # add CanonVRD trailer if writing as a block + $trailPt = $et->AddNewTrailers($trailPt,'CanonVRD'); + if (not $trailPt and $$et{ADD_DIRS}{CanonVRD}) { + # create CanonVRD from scratch if necessary + my $outbuff = ''; + my $saveOrder = GetByteOrder(); + require Image::ExifTool::CanonVRD; + if (Image::ExifTool::CanonVRD::ProcessCanonVRD($et, { OutFile => \$outbuff }) > 0) { + $trailPt = \$outbuff; + } + SetByteOrder($saveOrder); + } + # write trailer + if ($trailPt) { + # must append DirStart pointer to end of trailer + my $newDirStart = Set32u($dirInfo{OutDirStart}); + my $len = length $$trailPt; + my $pad = ($len & 0x01) ? ' ' : ''; # add pad byte if necessary + Write($outfile, $pad, substr($$trailPt,0,$len-4), $newDirStart) or $err = 1; + } + $rtnVal = $err ? -1 : 1; + } else { + $et->Error('Error rewriting CRW file'); + } + return $rtnVal; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WriteCanonRaw.pl - Write Canon RAW (CRW and CR2) information + +=head1 SYNOPSIS + +These routines are autoloaded by Image::ExifTool::CanonRaw. + +=head1 DESCRIPTION + +This file contains routines used by ExifTool to write Canon CRW and CR2 +files and metadata. + +=head1 NOTES + +The CRW format is a pleasure to work with. All pointer offsets are relative +to the start of the data for each directory. If EXIF/TIFF had implemented +pointers in this way, it would be MUCH easier to read and write TIFF and +JPEG files, and would lead to far fewer problems with corrupted metadata. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::CanonRaw(3pm)|Image::ExifTool::CanonRaw>, +L<Image::ExifTool(3pm)|Image::ExifTool>, +L<https://exiftool.org/canon_raw.html> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/WriteExif.pl b/ExifTool/lib/Image/ExifTool/WriteExif.pl new file mode 100644 index 0000000..4d3da0e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WriteExif.pl @@ -0,0 +1,2710 @@ +#------------------------------------------------------------------------------ +# File: WriteExif.pl +# +# Description: Write EXIF meta information +# +# Revisions: 12/13/2004 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::Exif; + +use strict; +use vars qw($VERSION $AUTOLOAD @formatSize @formatName %formatNumber + %compression %photometricInterpretation %orientation); + +use Image::ExifTool::Fixup; + +# some information may be stored in different IFD's with the same meaning. +# Use this lookup to decide when we should delete information that is stored +# in another IFD when we write it to the preferred IFD. +my %crossDelete = ( + ExifIFD => 'IFD0', + IFD0 => 'ExifIFD', +); + +# mandatory tag default values +my %mandatory = ( + IFD0 => { + 0x011a => 72, # XResolution + 0x011b => 72, # YResolution + 0x0128 => 2, # ResolutionUnit (inches) + 0x0213 => 1, # YCbCrPositioning (centered) + # 0x8769 => ????, # ExifOffset + }, + IFD1 => { + 0x0103 => 6, # Compression (JPEG) + 0x011a => 72, # XResolution + 0x011b => 72, # YResolution + 0x0128 => 2, # ResolutionUnit (inches) + }, + ExifIFD => { + 0x9000 => '0232', # ExifVersion + 0x9101 => "1 2 3 0",# ComponentsConfiguration + 0xa000 => '0100', # FlashpixVersion + 0xa001 => 0xffff, # ColorSpace (uncalibrated) + # 0xa002 => ????, # ExifImageWidth + # 0xa003 => ????, # ExifImageHeight + }, + GPS => { + 0x0000 => '2 3 0 0',# GPSVersionID + }, + InteropIFD => { + 0x0002 => '0100', # InteropVersion + }, +); + +#------------------------------------------------------------------------------ +# Inverse print conversion for OffsetTime tags +# Inputs: 0) input time zone or date/time value, 1) ExifTool ref +# Returns: Time zone string for writing to EXIF +sub InverseOffsetTime($$) +{ + my ($val, $et) = @_; + $val = $et->TimeNow() if lc($val) eq 'now'; + return '+00:00' if $val =~ /Z$/; + return sprintf('%s%.2d:%.2d',$1,$2,$3) if $val =~ /([-+])(\d{1,2}):?(\d{2})/; + return undef; +} + +#------------------------------------------------------------------------------ +# Inverse print conversion for LensInfo +# Inputs: 0) lens info string +# Returns: PrintConvInv of string +sub ConvertLensInfo($) +{ + my $val = shift; + my @a = GetLensInfo($val, 1); # (allow unknown "?" values) + return @a ? join(' ', @a) : $val; +} + +#------------------------------------------------------------------------------ +# Get binary CFA Pattern from a text string +# Inputs: Print-converted CFA pattern (eg. '[Blue,Green][Green,Red]') +# Returns: CFA pattern as a string of numbers +sub GetCFAPattern($) +{ + my $val = shift; + my @rows = split /\]\s*\[/, $val; + @rows or warn("Rows not properly bracketed by '[]'\n"), return undef; + my @cols = split /,/, $rows[0]; + @cols or warn("Colors not separated by ','\n"), return undef; + my $ny = @cols; + my @a = (scalar(@rows), scalar(@cols)); + my %cfaLookup = (red=>0, green=>1, blue=>2, cyan=>3, magenta=>4, yellow=>5, white=>6); + my $row; + foreach $row (@rows) { + @cols = split /,/, $row; + @cols == $ny or warn("Inconsistent number of colors in each row\n"), return undef; + foreach (@cols) { + tr/ \]\[//d; # remove remaining brackets and any spaces + my $c = $cfaLookup{lc($_)}; + defined $c or warn("Unknown color '${_}'\n"), return undef; + push @a, $c; + } + } + return "@a"; +} + +#------------------------------------------------------------------------------ +# validate raw values for writing +# Inputs: 0) ExifTool ref, 1) tagInfo hash ref, 2) raw value ref +# Returns: error string or undef (and possibly changes value) on success +sub CheckExif($$$) +{ + my ($et, $tagInfo, $valPtr) = @_; + my $format = $$tagInfo{Format} || $$tagInfo{Writable} || $$tagInfo{Table}{WRITABLE}; + if (not $format or $format eq '1') { + if ($$tagInfo{Groups}{0} eq 'MakerNotes') { + return undef; # OK to have no format for makernotes + } else { + return 'No writable format'; + } + } + return Image::ExifTool::CheckValue($valPtr, $format, $$tagInfo{Count}); +} + +#------------------------------------------------------------------------------ +# encode exif ASCII/Unicode text from UTF8 or Latin +# Inputs: 0) ExifTool ref, 1) text string +# Returns: encoded string +# Note: MUST be called Raw conversion time so the EXIF byte order is known! +sub EncodeExifText($$) +{ + my ($et, $val) = @_; + # does the string contain special characters? + if ($val =~ /[\x80-\xff]/) { + my $order = $et->GetNewValue('ExifUnicodeByteOrder'); + return "UNICODE\0" . $et->Encode($val,'UTF16',$order); + } else { + return "ASCII\0\0\0$val"; + } +} + +#------------------------------------------------------------------------------ +# rebuild maker notes to properly contain all value data +# (some manufacturers put value data outside maker notes!!) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: new maker note data (and creates MAKER_NOTE_FIXUP), or undef on error +sub RebuildMakerNotes($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dirStart = $$dirInfo{DirStart}; + my $dirLen = $$dirInfo{DirLen}; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos} || 0; + my $rtnValue; + my %subdirInfo = %$dirInfo; + + delete $$et{MAKER_NOTE_FIXUP}; + + # don't need to rebuild text, BinaryData or PreviewImage maker notes + my $tagInfo = $$dirInfo{TagInfo}; + my $subdir = $$tagInfo{SubDirectory}; + my $proc = $$subdir{ProcessProc} || $$tagTablePtr{PROCESS_PROC} || \&ProcessExif; + if (($proc ne \&ProcessExif and $$tagInfo{Name} =~ /Text/) or + $proc eq \&Image::ExifTool::ProcessBinaryData or + ($$tagInfo{PossiblePreview} and $dirLen > 6 and + substr($$dataPt, $dirStart, 3) eq "\xff\xd8\xff")) + { + return substr($$dataPt, $dirStart, $dirLen); + } + my $saveOrder = GetByteOrder(); + my $loc = Image::ExifTool::MakerNotes::LocateIFD($et,\%subdirInfo); + if (defined $loc) { + my $makerFixup = $subdirInfo{Fixup} = new Image::ExifTool::Fixup; + # create new exiftool object to rewrite the directory without changing it + my $newTool = new Image::ExifTool; + $newTool->Options( + IgnoreMinorErrors => $$et{OPTIONS}{IgnoreMinorErrors}, + FixBase => $$et{OPTIONS}{FixBase}, + ); + $newTool->Init(); # must do this before calling WriteDirectory()! + # don't copy over preview image + $newTool->SetNewValue(PreviewImage => ''); + # copy all transient members over in case they are used for writing + # (Make, Model, etc) + foreach (grep /[a-z]/, keys %$et) { + $$newTool{$_} = $$et{$_}; + } + # fix base offsets if specified + $newTool->Options(FixBase => $et->Options('FixBase')); + # set GENERATE_PREVIEW_INFO flag so PREVIEW_INFO will be generated + $$newTool{GENERATE_PREVIEW_INFO} = 1; + # drop any large tags + $$newTool{DropTags} = 1; + # initialize other necessary data members + $$newTool{FILE_TYPE} = $$et{FILE_TYPE}; + $$newTool{TIFF_TYPE} = $$et{TIFF_TYPE}; + # rewrite maker notes + $rtnValue = $newTool->WriteDirectory(\%subdirInfo, $tagTablePtr); + if (defined $rtnValue and length $rtnValue) { + # add the dummy/empty preview image if necessary + if ($$newTool{PREVIEW_INFO}) { + $makerFixup->SetMarkerPointers(\$rtnValue, 'PreviewImage', length($rtnValue)); + $rtnValue .= $$newTool{PREVIEW_INFO}{Data}; + delete $$newTool{PREVIEW_INFO}; + } + # add makernote header + if ($loc) { + my $hdr = substr($$dataPt, $dirStart, $loc); + # special case: convert Pentax/Samsung DNG maker notes to JPEG style + # (in JPEG, Pentax makernotes are absolute and start with "AOC\0" for some + # models, but in DNG images they are stored in tag 0xc634 of IFD0 and + # start with either "PENTAX \0" or "SAMSUNG\0") + if ($$dirInfo{Parent} eq 'IFD0' and $hdr =~ /^(PENTAX |SAMSUNG)\0/) { + # convert to JPEG-style AOC maker notes if used by this model + # (Note: this expression also appears in Exif.pm) + if ($$et{Model} =~ /\b(K(-[57mrx]|(10|20|100|110|200)D|2000)|GX(10|20))\b/) { + $hdr =~ s/^(PENTAX |SAMSUNG)\0/AOC\0/; + # save fixup because AOC maker notes have absolute offsets + $$et{MAKER_NOTE_FIXUP} = $makerFixup; + } + } + $rtnValue = $hdr . $rtnValue; + # adjust fixup for shift in start position + $$makerFixup{Start} += length $hdr; + } + # shift offsets according to original position of maker notes, + # and relative to the makernotes Base + $$makerFixup{Shift} += $dataPos + $dirStart + + $$dirInfo{Base} - $subdirInfo{Base}; + # repair incorrect offsets if offsets were fixed + $$makerFixup{Shift} += $subdirInfo{FixedBy} || 0; + # fix up pointers to the specified offset + $makerFixup->ApplyFixup(\$rtnValue); + # save fixup information unless offsets were relative + unless ($subdirInfo{Relative}) { + # set shift so offsets are all relative to start of maker notes + $$makerFixup{Shift} -= $dataPos + $dirStart; + $$et{MAKER_NOTE_FIXUP} = $makerFixup; # save fixup for later + } + } + } + SetByteOrder($saveOrder); + + return $rtnValue; +} + +#------------------------------------------------------------------------------ +# Sort IFD directory entries +# Inputs: 0) data reference, 1) directory start, 2) number of entries, +# 3) flag to treat 0 as a valid tag ID (as opposed to an empty IFD entry) +sub SortIFD($$$;$) +{ + my ($dataPt, $dirStart, $numEntries, $allowZero) = @_; + my ($index, %entries); + # split the directory into separate entries + for ($index=0; $index<$numEntries; ++$index) { + my $entry = $dirStart + 2 + 12 * $index; + my $tagID = Get16u($dataPt, $entry); + my $entryData = substr($$dataPt, $entry, 12); + # silly software can pad directories with zero entries -- put these at the end + $tagID = 0x10000 unless $tagID or $index == 0 or $allowZero; + # add new entry (allow for duplicate tag ID's, which shouldn't normally happen) + if ($entries{$tagID}) { + $entries{$tagID} .= $entryData; + } else { + $entries{$tagID} = $entryData; + } + } + # sort the directory entries + my @sortedTags = sort { $a <=> $b } keys %entries; + # generate the sorted IFD + my $newDir = ''; + foreach (@sortedTags) { + $newDir .= $entries{$_}; + } + # replace original directory with new, sorted one + substr($$dataPt, $dirStart + 2, 12 * $numEntries) = $newDir; +} + +#------------------------------------------------------------------------------ +# Validate IFD entries (strict validation to test possible chained IFD's) +# Inputs: 0) dirInfo ref (must have RAF set), 1) optional DirStart +# Returns: true if IFD looks OK +sub ValidateIFD($;$) +{ + my ($dirInfo, $dirStart) = @_; + my $raf = $$dirInfo{RAF} or return 0; + my $base = $$dirInfo{Base}; + $dirStart = $$dirInfo{DirStart} || 0 unless defined $dirStart; + my $offset = $dirStart + ($$dirInfo{DataPos} || 0); + my ($buff, $index); + $raf->Seek($offset + $base, 0) and $raf->Read($buff,2) == 2 or return 0; + my $numEntries = Get16u(\$buff,0); + $numEntries > 1 and $numEntries < 64 or return 0; + my $len = 12 * $numEntries; + $raf->Read($buff, $len) == $len or return 0; + my $lastID = -1; + for ($index=0; $index<$numEntries; ++$index) { + my $entry = 12 * $index; + my $tagID = Get16u(\$buff, $entry); + $tagID > $lastID or $$dirInfo{AllowOutOfOrderTags} or return 0; + my $format = Get16u(\$buff, $entry+2); + $format > 0 and $format <= 13 or return 0; + my $count = Get32u(\$buff, $entry+4); + $count > 0 or return 0; + $lastID = $tagID; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Get sorted list of offsets used in IFD +# Inputs: 0) data ref, 1) directory start, 2) dataPos, 3) IFD entries, 4) tag table ref +# Returns: 0) sorted list of offsets (only offsets after the end of the IFD) +# 1) hash of list indices keyed by offset value +# Notes: This is used in a patch to fix the count for tags in Kodak SubIFD3 +sub GetOffList($$$$$) +{ + my ($dataPt, $dirStart, $dataPos, $numEntries, $tagTablePtr) = @_; + my $ifdEnd = $dirStart + 2 + 12 * $numEntries + $dataPos; + my ($index, $offset, %offHash); + for ($index=0; $index<$numEntries; ++$index) { + my $entry = $dirStart + 2 + 12 * $index; + my $format = Get16u($dataPt, $entry + 2); + next if $format < 1 or $format > 13; + my $count = Get16u($dataPt, $entry + 4); + my $size = $formatSize[$format] * $count; + if ($size <= 4) { + my $tagID = Get16u($dataPt, $entry); + next unless ref $$tagTablePtr{$tagID} eq 'HASH' and $$tagTablePtr{$tagID}{FixCount}; + } + my $offset = Get16u($dataPt, $entry + 8); + $offHash{$offset} = 1 if $offset >= $ifdEnd; + } + # set offset hash values to indices in list + my @offList = sort keys %offHash; + $index = 0; + foreach $offset (@offList) { + $offHash{$offset} = $index++; + } + return(\@offList, \%offHash); +} + +#------------------------------------------------------------------------------ +# Update TIFF_END member if defined +# Inputs: 0) ExifTool ref, 1) end of valid TIFF data +sub UpdateTiffEnd($$) +{ + my ($et, $end) = @_; + if (defined $$et{TIFF_END} and + $$et{TIFF_END} < $end) + { + $$et{TIFF_END} = $end; + } +} + +#------------------------------------------------------------------------------ +# Validate image data size +# Inputs: 0) ExifTool ref, 1) validate info hash ref, +# 2) flag to issue error (ie. we're writing) +# - issues warning or error if problems found +sub ValidateImageData($$$;$) +{ + local $_; + my ($et, $vInfo, $dirName, $errFlag) = @_; + + # determine the expected size of the image data for an uncompressed image + # (0x102 BitsPerSample, 0x103 Compression and 0x115 SamplesPerPixel + # all default to a value of 1 if they don't exist) + if ((not defined $$vInfo{0x103} or $$vInfo{0x103} eq '1') and + $$vInfo{0x100} and $$vInfo{0x101} and ($$vInfo{0x117} or $$vInfo{0x145})) + { + my $samplesPerPix = $$vInfo{0x115} || 1; + my @bitsPerSample = $$vInfo{0x102} ? split(' ',$$vInfo{0x102}) : (1) x $samplesPerPix; + my $byteCountInfo = $$vInfo{0x117} || $$vInfo{0x145}; + my $byteCounts = $$byteCountInfo[1]; + my $totalBytes = 0; + $totalBytes += $_ foreach split ' ', $byteCounts; + my $minor; + $minor = 1 if $$et{DOC_NUM} or $$et{FILE_TYPE} ne 'TIFF'; + unless (@bitsPerSample == $samplesPerPix) { + unless ($$et{FILE_TYPE} eq 'EPS' and @bitsPerSample == 1) { + # (just a warning for this problem) + my $s = $samplesPerPix eq '1' ? '' : 's'; + $et->Warn("$dirName BitsPerSample should have $samplesPerPix value$s", $minor); + } + push @bitsPerSample, $bitsPerSample[0] while @bitsPerSample < $samplesPerPix; + foreach (@bitsPerSample) { + $et->WarnOnce("$dirName BitsPerSample values are different", $minor) if $_ ne $bitsPerSample[0]; + $et->WarnOnce("Invalid $dirName BitsPerSample value", $minor) if $_ < 1 or $_ > 32; + } + } + my $bitsPerPixel = 0; + $bitsPerPixel += $_ foreach @bitsPerSample; + my $expectedBytes = int(($$vInfo{0x100} * $$vInfo{0x101} * $bitsPerPixel + 7) / 8); + if ($expectedBytes != $totalBytes and + # (this problem seems normal for certain types of RAW files...) + $$et{TIFF_TYPE} !~ /^(K25|KDC|MEF|ORF|SRF)$/) + { + my ($adj, $minor); + if ($expectedBytes > $totalBytes) { + $adj = 'Under'; # undersized is a bigger problem because we may lose data + $minor = 0 unless $errFlag; + } else { + $adj = 'Over'; + $minor = 1; + } + my $msg = "${adj}sized $dirName $$byteCountInfo[0]{Name} ($totalBytes bytes, but expected $expectedBytes)"; + if (not defined $minor) { + # this is a serious error if we are writing the file and there + # is a chance that we may not copy all of the image data + # (but make it minor to allow the file to be written anyway) + $et->Error($msg, 1); + } else { + $et->Warn($msg, $minor); + } + } + } +} + +#------------------------------------------------------------------------------ +# Add specified image data to ImageDataHash hash +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) lookup for [tagInfo,value] based on tagID +sub AddImageDataHash($$$) +{ + my ($et, $dirInfo, $offsetInfo) = @_; + my ($tagID, $offset, $buff); + + my $verbose = $et->Options('Verbose'); + my $hash = $$et{ImageDataHash}; + my $raf = $$dirInfo{RAF}; + + foreach $tagID (sort keys %$offsetInfo) { + next unless ref $$offsetInfo{$tagID} eq 'ARRAY'; # ignore scalar tag values used for Validate + my $tagInfo = $$offsetInfo{$tagID}[0]; + next unless $$tagInfo{IsImageData}; # only consider image data + my $sizeID = $$tagInfo{OffsetPair}; + my @sizes; + if ($$tagInfo{NotRealPair}) { + @sizes = 999999999; # (Panasonic hack: raw data runs to end of file) + } elsif ($sizeID and $$offsetInfo{$sizeID}) { + @sizes = split ' ', $$offsetInfo{$sizeID}[1]; + } else { + next; + } + my @offsets = split ' ', $$offsetInfo{$tagID}[1]; + $sizes[0] = 999999999 if $$tagInfo{NotRealPair}; + my $total = 0; + foreach $offset (@offsets) { + my $size = shift @sizes; + next unless $offset =~ /^\d+$/ and $size and $size =~ /^\d+$/ and $size; + next unless $raf->Seek($offset, 0); # (offset is absolute) + $total += $et->ImageDataHash($raf, $size); + } + if ($verbose) { + my $name = "$$dirInfo{DirName}:$$tagInfo{Name}"; + $name =~ s/Offsets?|Start$//; + $et->VPrint(0, "$$et{INDENT}(ImageDataHash: $total bytes of $name data)\n"); + } + } +} + +#------------------------------------------------------------------------------ +# Handle error while writing EXIF +# Inputs: 0) ExifTool ref, 1) error string, 2) tag table ref +# Returns: undef on fatal error, or '' if minor error is ignored +sub ExifErr($$$) +{ + my ($et, $errStr, $tagTablePtr) = @_; + # MakerNote errors are minor by default + my $minor = ($$tagTablePtr{GROUPS}{0} eq 'MakerNotes' or $$et{FILE_TYPE} eq 'MOV'); + if ($$tagTablePtr{VARS} and $$tagTablePtr{VARS}{MINOR_ERRORS}) { + $et->Warn("$errStr. IFD dropped.") and return '' if $minor; + $minor = 1; + } + return undef if $et->Error($errStr, $minor); + return ''; +} + +#------------------------------------------------------------------------------ +# Read/Write IFD with TIFF-like header (used by DNG 1.2) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: Reading: 1 on success, otherwise returns 0 and sets a Warning +# Writing: new data block or undef on error +sub ProcessTiffIFD($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access + my $raf = $$dirInfo{RAF}; + my $base = $$dirInfo{Base} || 0; + my $dirName = $$dirInfo{DirName}; + my $magic = $$dirInfo{Subdir}{Magic} || 0x002a; + my $buff; + + # structured with a TIFF-like header and relative offsets + $raf->Seek($base, 0) and $raf->Read($buff, 8) == 8 or return 0; + unless (SetByteOrder(substr($buff,0,2)) and Get16u(\$buff, 2) == $magic) { + my $msg = "Invalid $dirName header"; + if ($$dirInfo{IsWriting}) { + $et->Error($msg); + return undef; + } else { + $et->Warn($msg); + return 0; + } + } + my $offset = Get32u(\$buff, 4); + my %dirInfo = ( + DirName => $$dirInfo{DirName}, + Parent => $$dirInfo{Parent}, + Base => $base, + DataPt => \$buff, + DataLen => length $buff, + DataPos => 0, + DirStart => $offset, + DirLen => length($buff) - $offset, + RAF => $raf, + NewDataPos => 8, + ); + if ($$dirInfo{IsWriting}) { + # rewrite the Camera Profile IFD + my $newDir = WriteExif($et, \%dirInfo, $tagTablePtr); + # don't add header if error writing directory ($newDir is undef) + # or if directory is being deleted ($newDir is empty) + return $newDir unless $newDir; + # return directory with TIFF-like header + return GetByteOrder() . Set16u($magic) . Set32u(8) . $newDir; + } + if ($$et{HTML_DUMP}) { + my $tip = sprintf("Byte order: %s endian\nIdentifier: 0x%.4x\n%s offset: 0x%.4x", + (GetByteOrder() eq 'II') ? 'Little' : 'Big', $magic, $dirName, $offset); + $et->HDump($base, 8, "$dirName header", $tip, 0); + } + return ProcessExif($et, \%dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Write EXIF directory +# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref +# Returns: Exif data block (may be empty if no Exif data) or undef on error +# Notes: Increments ExifTool CHANGED flag for each tag changed. Also updates +# TIFF_END if defined with location of end of original TIFF image. +# Returns IFD data in the following order: +# 1. IFD0 directory followed by its data +# 2. SubIFD directory followed by its data, thumbnail and image +# 3. GlobalParameters, EXIF, GPS, Interop IFD's each with their data +# 4. IFD1,IFD2,... directories each followed by their data +# 5. Thumbnail and/or image data for each IFD, with IFD0 image last +sub WriteExif($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + my $origDirInfo = $dirInfo; # save original dirInfo + my $dataPt = $$dirInfo{DataPt}; + unless ($dataPt) { + my $emptyData = ''; + $dataPt = \$emptyData; + } + my $dataPos = $$dirInfo{DataPos} || 0; + my $dirStart = $$dirInfo{DirStart} || 0; + my $dataLen = $$dirInfo{DataLen} || length($$dataPt); + my $dirLen = $$dirInfo{DirLen} || ($dataLen - $dirStart); + my $base = $$dirInfo{Base} || 0; + my $firstBase = $base; + my $raf = $$dirInfo{RAF}; + my $dirName = $$dirInfo{DirName} || 'unknown'; + my $fixup = $$dirInfo{Fixup} || new Image::ExifTool::Fixup; + my $imageDataFlag = $$dirInfo{ImageData} || ''; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my ($nextIfdPos, %offsetData, $inMakerNotes); + my (@offsetInfo, %validateInfo, %xDelete, $strEnc); + my $deleteAll = 0; + my $newData = ''; # initialize buffer to receive new directory data + my @imageData; # image data blocks to copy later if requested + my $name = $$dirInfo{Name}; + $name = $dirName unless $name and $dirName eq 'MakerNotes' and $name !~ /^MakerNote/; + + # save byte order of existing EXIF + $$et{SaveExifByteOrder} = GetByteOrder() if $dirName eq 'IFD0' or $dirName eq 'ExifIFD'; + + # set encoding for strings + $strEnc = $et->Options('CharsetEXIF') if $$tagTablePtr{GROUPS}{0} eq 'EXIF'; + + # allow multiple IFD's in IFD0-IFD1-IFD2... chain + $$dirInfo{Multi} = 1 if $dirName =~ /^(IFD0|SubIFD)$/ and not defined $$dirInfo{Multi}; + $inMakerNotes = 1 if $$tagTablePtr{GROUPS}{0} eq 'MakerNotes'; + my $ifd; + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# loop through each IFD +# + for ($ifd=0; ; ++$ifd) { # loop through multiple IFD's + + # make sure that Compression and SubfileType are defined for this IFD (for Condition's) + $$et{Compression} = $$et{SubfileType} = ''; + + # save pointer to start of this IFD within the newData + my $newStart = length($newData); + my @subdirs; # list of subdirectory data and tag table pointers + # determine if directory is contained within our data + my $mustRead; + if ($dirStart < 0 or $dirStart > $dataLen-2) { + $mustRead = 1; + } elsif ($dirLen >= 2) { + my $len = 2 + 12 * Get16u($dataPt, $dirStart); + $mustRead = 1 if $dirStart + $len > $dataLen; + } + # read IFD from file if necessary + if ($mustRead) { + if ($raf) { + # read the count of entries in this IFD + my $offset = $dirStart + $dataPos; + my ($buff, $buf2); + unless ($raf->Seek($offset + $base, 0) and $raf->Read($buff,2) == 2) { + return ExifErr($et, "Bad IFD or truncated file in $name", $tagTablePtr); + } + my $len = 12 * Get16u(\$buff,0); + # (also read next IFD pointer if available) + unless ($raf->Read($buf2, $len+4) >= $len) { + return ExifErr($et, "Error reading $name", $tagTablePtr); + } + $buff .= $buf2; + # make copy of dirInfo since we're going to modify it + my %newDirInfo = %$dirInfo; + $dirInfo = \%newDirInfo; + # update directory parameters for the newly loaded IFD + $dataPt = $$dirInfo{DataPt} = \$buff; + $dirStart = $$dirInfo{DirStart} = 0; + $dataPos = $$dirInfo{DataPos} = $offset; + $dataLen = $$dirInfo{DataLen} = length $buff; + $dirLen = $$dirInfo{DirLen} = $dataLen; + # only account for nextIFD pointer if we are going to use it + $len += 4 if $dataLen==$len+6 and ($$dirInfo{Multi} or $buff =~ /\0{4}$/); + UpdateTiffEnd($et, $offset+$base+2+$len); + } elsif ($dirLen and $dirStart + 4 >= $dataLen) { + # error if we can't load IFD (unless we are creating + # from scratch, in which case dirLen will be zero) + my $str = $et->Options('IgnoreMinorErrors') ? 'Deleted bad' : 'Bad'; + $et->Error("$str $name directory", 1); + } + } + my ($index, $dirEnd, $numEntries, %hasOldID, $unsorted); + if ($dirStart + 4 < $dataLen) { + $numEntries = Get16u($dataPt, $dirStart); + $dirEnd = $dirStart + 2 + 12 * $numEntries; + if ($dirEnd > $dataLen) { + my $n = int(($dataLen - $dirStart - 2) / 12); + my $rtn = ExifErr($et, "Truncated $name directory", $tagTablePtr); + return undef unless $n and defined $rtn; + $numEntries = $n; # continue processing the entries we have + } + # create lookup for existing tag ID's and determine if directory is sorted + my $lastID = -1; + for ($index=0; $index<$numEntries; ++$index) { + my $tagID = Get16u($dataPt, $dirStart + 2 + 12 * $index); + $hasOldID{$tagID} = 1; + # check for proper sequence (but ignore null entries at end) + $unsorted = 1 if $tagID < $lastID and ($tagID or $$tagTablePtr{0}); + $lastID = $tagID; + } + # sort entries if out-of-order (but not in maker notes IFDs or RAW files) + if ($unsorted and not ($inMakerNotes or $et->IsRawType())) { + SortIFD($dataPt, $dirStart, $numEntries, $$tagTablePtr{0}); + $et->Warn("Entries in $name were out of sequence. Fixed.",1); + $unsorted = 0; + } + } else { + $numEntries = 0; + $dirEnd = $dirStart; + } + + # loop through new values and accumulate all information for this IFD + my (%set, %mayDelete, $tagInfo, %hasNewID); + my $wrongDir = $crossDelete{$dirName}; + my @newTagInfo = $et->GetNewTagInfoList($tagTablePtr); + foreach $tagInfo (@newTagInfo) { + my $tagID = $$tagInfo{TagID}; + $hasNewID{$tagID} = 1; + # must evaluate Condition later when we have all DataMember's available + $set{$tagID} = (ref $$tagTablePtr{$tagID} eq 'ARRAY' or $$tagInfo{Condition}) ? '' : $tagInfo; + } + + # fix base offsets (some cameras incorrectly write maker notes in IFD0) + if ($dirName eq 'MakerNotes' and $$dirInfo{Parent} =~ /^(ExifIFD|IFD0)$/ and + $$et{TIFF_TYPE} !~ /^(ARW|SR2)$/ and not $$et{LeicaTrailerPos} and + Image::ExifTool::MakerNotes::FixBase($et, $dirInfo)) + { + # update local variables from fixed values + $base = $$dirInfo{Base}; + $dataPos = $$dirInfo{DataPos}; + # changed if ForceWrite tag was was set to "FixBase" + ++$$et{CHANGED} if $$et{FORCE_WRITE}{FixBase}; + if ($$et{TIFF_TYPE} eq 'SRW' and $$et{Make} eq 'SAMSUNG' and $$et{Model} eq 'EK-GN120') { + $et->Error("EK-GN120 SRW files are too buggy to write"); + } + } + + # initialize variables to handle mandatory tags + my $mandatory = $mandatory{$dirName}; + my ($allMandatory, $addMandatory); + if ($mandatory) { + # use X/Y resolution values from JFIF if available + if ($dirName eq 'IFD0' and defined $$et{JFIFYResolution}) { + my %ifd0Vals = %$mandatory; + $ifd0Vals{0x011a} = $$et{JFIFXResolution}; + $ifd0Vals{0x011b} = $$et{JFIFYResolution}; + $ifd0Vals{0x0128} = $$et{JFIFResolutionUnit} + 1; + $mandatory = \%ifd0Vals; + } + $allMandatory = $addMandatory = 0; # initialize to zero + # add mandatory tags if creating a new directory + unless ($numEntries) { + foreach (keys %$mandatory) { + defined $set{$_} or $set{$_} = $$tagTablePtr{$_}; + } + } + } else { + undef $deleteAll; # don't remove directory (no mandatory entries) + } + my ($addDirs, @newTags); + if ($inMakerNotes) { + $addDirs = { }; # can't currently add new directories in MakerNotes + # allow non-permanent makernotes tags to be added + # (note: we may get into trouble if there are too many of these + # because we allow out-of-order tags in MakerNote IFD's but our + # logic to add new tags relies on ordered entries) + foreach (keys %set) { + next unless $set{$_}; + my $perm = $set{$_}{Permanent}; + push @newTags, $_ if defined $perm and not $perm; + } + @newTags = sort { $a <=> $b } @newTags if @newTags > 1; + } else { + # get a hash of directories we will be writing in this one + $addDirs = $et->GetAddDirHash($tagTablePtr, $dirName); + # make a union of tags & dirs (can set whole dirs, like MakerNotes) + my %allTags = ( %set, %$addDirs ); + # make sorted list of new tags to be added + @newTags = sort { $a <=> $b } keys(%allTags); + } + my $dirBuff = ''; # buffer for directory data + my $valBuff = ''; # buffer for value data + my @valFixups; # list of fixups for offsets in valBuff + # fixup for offsets in dirBuff + my $dirFixup = new Image::ExifTool::Fixup; + my $entryBasedFixup; + my $lastTagID = -1; + my ($oldInfo, $oldFormat, $oldFormName, $oldCount, $oldSize, $oldValue, $oldImageData); + my ($readFormat, $readFormName, $readCount); # format for reading old value(s) + my ($entry, $valueDataPt, $valueDataPos, $valueDataLen, $valuePtr, $valEnd); + my ($offList, $offHash, $ignoreCount, $fixCount); + my $oldID = -1; + my $newID = -1; + + # patch for Canon EOS 40D firmware 1.0.4 bug (incorrect directory counts) + if ($inMakerNotes and $$et{Model} eq 'Canon EOS 40D') { + my $fmt = Get16u($dataPt, $dirStart + 2 + 12 * ($numEntries - 1) + 2); + if ($fmt < 1 or $fmt > 13) { + # adjust the number of directory entries + --$numEntries; + $dirEnd -= 12; + $ignoreCount = 1; + } + } +#.............................................................................. +# loop through entries in new directory +# + $index = 0; +Entry: for (;;) { + + if (defined $oldID and $oldID == $newID) { +# +# read next entry from existing directory +# + if ($index < $numEntries) { + $entry = $dirStart + 2 + 12 * $index; + $oldID = Get16u($dataPt, $entry); + $readFormat = $oldFormat = Get16u($dataPt, $entry+2); + $readCount = $oldCount = Get32u($dataPt, $entry+4); + undef $oldImageData; + if (($oldFormat < 1 or $oldFormat > 13) and $oldFormat != 129 and not ($oldFormat == 16 and $$et{Make} eq 'Apple' and $inMakerNotes)) { + my $msg = "Bad format ($oldFormat) for $name entry $index"; + # patch to preserve invalid directory entries in SubIFD3 of + # various Kodak Z-series cameras (Z812, Z1085IS, Z1275) + # and some Sony cameras such as the DSC-P10 + if ($dirName eq 'MakerNotes' and (($$et{Make}=~/KODAK/i and + $$dirInfo{Name} and $$dirInfo{Name} eq 'SubIFD3') or + ($numEntries == 12 and $$et{Make} eq 'SONY' and $index >= 8))) + { + $dirBuff .= substr($$dataPt, $entry, 12); + ++$index; + $newID = $oldID; # we wrote this + $et->Warn($msg, 1); + next; + } + # don't write out null directory entry + if ($oldFormat==0 and $index and $oldCount==0) { + $ignoreCount = ($ignoreCount || 0) + 1; + # must keep same directory size to avoid messing up our fixed offsets + $dirBuff .= ("\0" x 12) if $$dirInfo{FixBase}; + ++$index; + $newID = $oldID; # pretend we wrote this + next; + } + return ExifErr($et, $msg, $tagTablePtr); + } + $readFormName = $oldFormName = $formatName[$oldFormat]; + $valueDataPt = $dataPt; + $valueDataPos = $dataPos; + $valueDataLen = $dataLen; + $valuePtr = $entry + 8; + # try direct method first for speed + $oldInfo = $$tagTablePtr{$oldID}; + if (ref $oldInfo ne 'HASH' or $$oldInfo{Condition}) { + # must get unknown tags too + # (necessary so we don't miss a tag we want to Drop) + my $unk = $et->Options(Unknown => 1); + $oldInfo = $et->GetTagInfo($tagTablePtr, $oldID); + $et->Options(Unknown => $unk); + } + # patch incorrect count in Kodak SubIFD3 tags + if ($oldCount < 2 and $oldInfo and $$oldInfo{FixCount}) { + $offList or ($offList, $offHash) = GetOffList($dataPt, $dirStart, $dataPos, + $numEntries, $tagTablePtr); + my $i = $$offHash{Get32u($dataPt, $valuePtr)}; + if (defined $i and $i < $#$offList) { + $oldCount = int(($$offList[$i+1] - $$offList[$i]) / $formatSize[$oldFormat]); + $fixCount = ($fixCount || 0) + 1 if $oldCount != $readCount; + } + } + $oldSize = $oldCount * $formatSize[$oldFormat]; + my $readFromFile; + if ($oldSize > 4) { + $valuePtr = Get32u($dataPt, $valuePtr); + # fix valuePtr if necessary + if ($$dirInfo{FixOffsets}) { + $valEnd or $valEnd = $dataPos + $dirStart + 2 + 12 * $numEntries + 4; + my ($tagID, $size, $wFlag) = ($oldID, $oldSize, 1); + #### eval FixOffsets ($valuePtr, $valEnd, $size, $tagID, $wFlag) + eval $$dirInfo{FixOffsets}; + unless (defined $valuePtr) { + unless ($$et{DropTags}) { + my $tagStr = $oldInfo ? $$oldInfo{Name} : sprintf("tag 0x%.4x",$oldID); + return undef if $et->Error("Bad $name offset for $tagStr", $inMakerNotes); + } + ++$index; $oldID = $newID; next; # drop this tag + } + } + # offset shouldn't point into TIFF or IFD header + my $suspect = ($valuePtr < 8); + # convert offset to pointer in $$dataPt + if ($$dirInfo{EntryBased} or (ref $$tagTablePtr{$oldID} eq 'HASH' and + $$tagTablePtr{$oldID}{EntryBased})) + { + $valuePtr += $entry; + } else { + $valuePtr -= $dataPos; + } + # value shouldn't overlap our directory + $suspect = 1 if $valuePtr < $dirEnd and $valuePtr+$oldSize > $dirStart; + # get value by seeking in file if we are allowed + if ($valuePtr < 0 or $valuePtr+$oldSize > $dataLen) { + my ($pos, $tagStr, $invalidPreview, $tmpInfo, $leicaTrailer); + if ($oldInfo) { + $tagStr = $$oldInfo{Name}; + $leicaTrailer = $$oldInfo{LeicaTrailer}; + } elsif (defined $oldInfo) { + $tmpInfo = $et->GetTagInfo($tagTablePtr, $oldID, \ '', $oldFormName, $oldCount); + if ($tmpInfo) { + $tagStr = $$tmpInfo{Name}; + $leicaTrailer = $$tmpInfo{LeicaTrailer}; + } + } + $tagStr or $tagStr = sprintf("tag 0x%.4x",$oldID); + # allow PreviewImage to run outside EXIF segment in JPEG images + if (not $raf) { + if ($tagStr eq 'PreviewImage') { + $raf = $$et{RAF}; + if ($raf) { + $pos = $raf->Tell(); + if ($oldInfo and $$oldInfo{ChangeBase}) { + # adjust base offset for this tag only + #### eval ChangeBase ($dirStart,$dataPos) + my $newBase = eval $$oldInfo{ChangeBase}; + $valuePtr += $newBase; + } + } else { + $invalidPreview = 1; + } + } elsif ($leicaTrailer) { + # save information about Leica makernote trailer + $$et{LeicaTrailer} = { + TagInfo => $oldInfo || $tmpInfo, + Offset => $base + $valuePtr + $dataPos, + Size => $oldSize, + Fixup => new Image::ExifTool::Fixup, + }, + $invalidPreview = 2; + # remove SubDirectory to prevent processing (for now) + my %copy = %{$oldInfo || $tmpInfo}; + delete $copy{SubDirectory}; + delete $copy{MakerNotes}; + $oldInfo = \%copy; + } + } + if ($oldSize > BINARY_DATA_LIMIT and $$origDirInfo{ImageData} and + (not defined $oldInfo or ($oldInfo and + (not $$oldInfo{SubDirectory} or $$oldInfo{ReadFromRAF})))) + { + # copy huge data blocks later instead of loading into memory + $oldValue = ''; # dummy empty value + # copy this value later unless writing a new value + unless (defined $set{$oldID}) { + my $pad = $oldSize & 0x01 ? 1 : 0; + # save block information to copy later (set directory offset later) + $oldImageData = [$base+$valuePtr+$dataPos, $oldSize, $pad]; + } + } elsif ($raf) { + my $success = ($raf->Seek($base+$valuePtr+$dataPos, 0) and + $raf->Read($oldValue, $oldSize) == $oldSize); + if (defined $pos) { + $raf->Seek($pos, 0); + undef $raf; + # (sony A700 has 32-byte header on PreviewImage) + unless ($success and $oldValue =~ /^(\xff\xd8\xff|(.|.{33})\xd8\xff\xdb)/s) { + $invalidPreview = 1; + $success = 1; # continue writing directory anyway + } + } + unless ($success) { + return undef if $et->Error("Error reading value for $name entry $index", $inMakerNotes); + ++$index; $oldID = $newID; next; # drop this tag + } + } elsif (not $invalidPreview) { + return undef if $et->Error("Bad $name offset for $tagStr", $inMakerNotes); + ++$index; $oldID = $newID; next; # drop this tag + } + if ($invalidPreview) { + # set value for invalid preview + if ($$et{FILE_TYPE} eq 'JPEG') { + # define dummy value for preview (or Leica MakerNote) to write later + # (value must be larger than 4 bytes to generate PREVIEW_INFO, + # and an even number of bytes so it won't be padded) + $oldValue = 'LOAD_PREVIEW'; + } else { + $oldValue = 'none'; + $oldSize = length $oldValue; + } + $valuePtr = 0; + } else { + UpdateTiffEnd($et, $base+$valuePtr+$dataPos+$oldSize); + } + # update pointers for value just read from file + $valueDataPt = \$oldValue; + $valueDataPos = $valuePtr + $dataPos; + $valueDataLen = $oldSize; + $valuePtr = 0; + $readFromFile = 1; + } + if ($suspect) { + my $tagStr = $oldInfo ? $$oldInfo{Name} : sprintf('tag 0x%.4x', $oldID); + my $str = "Suspicious $name offset for $tagStr"; + if ($inMakerNotes) { + $et->Warn($str, 1); + } else { + return undef if $et->Error($str, 1); + } + } + } + # read value if we haven't already + $oldValue = substr($$valueDataPt, $valuePtr, $oldSize) unless $readFromFile; + # get tagInfo using value if necessary + if (defined $oldInfo and not $oldInfo) { + my $unk = $et->Options(Unknown => 1); + $oldInfo = $et->GetTagInfo($tagTablePtr, $oldID, \$oldValue, $oldFormName, $oldCount); + $et->Options(Unknown => $unk); + # now that we have the value, we can resolve the Condition to finally + # determine whether we want to delete this tag or not + if ($mayDelete{$oldID} and $oldInfo and (not @newTags or $newTags[0] != $oldID)) { + my $nvHash = $et->GetNewValueHash($oldInfo, $dirName); + if (not $nvHash and $wrongDir) { + # delete from wrong directory if necessary + $nvHash = $et->GetNewValueHash($oldInfo, $wrongDir); + $nvHash and $xDelete{$oldID} = 1; + } + if ($nvHash) { + # we want to delete this tag after all, so insert it into our list + $set{$oldID} = $oldInfo; + unshift @newTags, $oldID; + } + } + } + # make sure we are handling the 'ifd' format properly + if (($oldFormat == 13 or $oldFormat == 18) and + (not $oldInfo or not $$oldInfo{SubIFD})) + { + my $str = sprintf('%s tag 0x%.4x IFD format not handled', $name, $oldID); + $et->Error($str, $inMakerNotes); + } + # override format we use to read the value if specified + if ($oldInfo) { + # check for tags which must be integers + if (($$oldInfo{IsOffset} or $$oldInfo{SubIFD}) and + not $intFormat{$oldFormName}) + { + $et->Error("Invalid format ($oldFormName) for $name $$oldInfo{Name}", $inMakerNotes); + ++$index; $oldID = $newID; next; # drop this tag + } + if ($$oldInfo{Drop} and $$et{DropTags} and + ($$oldInfo{Drop} == 1 or $$oldInfo{Drop} < $oldSize)) + { + ++$index; $oldID = $newID; next; # drop this tag + } + if ($$oldInfo{Format}) { + $readFormName = $$oldInfo{Format}; + $readFormat = $formatNumber{$readFormName}; + unless ($readFormat) { + # we aren't reading in a standard EXIF format, so rewrite in old format + $readFormName = $oldFormName; + $readFormat = $oldFormat; + } + if ($$oldInfo{FixedSize}) { + $oldSize = $$oldInfo{FixedSize} if $$oldInfo{FixedSize}; + $oldValue = substr($$valueDataPt, $valuePtr, $oldSize); + } + # adjust number of items to read if format size changed + $readCount = $oldSize / $formatSize[$readFormat]; + } + } + if ($oldID <= $lastTagID and not ($inMakerNotes or $et->IsRawType())) { + my $str = $oldInfo ? "$$oldInfo{Name} tag" : sprintf('tag 0x%x',$oldID); + if ($oldID == $lastTagID) { + $et->Warn("Duplicate $str in $name"); + # put this tag back into the newTags list if necessary + unshift @newTags, $oldID if defined $set{$oldID}; + } else { + $et->Warn("\u$str out of sequence in $name"); + } + } + $lastTagID = $oldID; + ++$index; # increment index for next time + } else { + undef $oldID; # no more existing entries + } + } +# +# write out all new tags, up to and including this one +# + $newID = $newTags[0]; + my $isNew; # -1=tag is old, 0=tag same as existing, 1=tag is new + if (not defined $oldID) { + last unless defined $newID; + $isNew = 1; + } elsif (not defined $newID) { + # maker notes will have no new tags defined + if (defined $set{$oldID}) { + $newID = $oldID; + $isNew = 0; + } else { + $isNew = -1; + } + } else { + $isNew = $oldID <=> $newID; + # special logic needed if directory has out-of-order entries + if ($unsorted and $isNew) { + if ($isNew > 0 and $hasOldID{$newID}) { + # we wanted to create the new tag, but an old tag + # does exist with this ID, so defer writing the new tag + $isNew = -1; + } + if ($isNew < 0 and $hasNewID{$oldID}) { + # we wanted to write the old tag, but we have + # a new tag with this ID, so move it up in the order + my @tmpTags = ( $oldID ); + $_ == $oldID or push @tmpTags, $_ foreach @newTags; + @newTags = @tmpTags; + $newID = $oldID; + $isNew = 0; + } + } + } + my $newInfo = $oldInfo; + my $newFormat = $oldFormat; + my $newFormName = $oldFormName; + my $newCount = $oldCount; + my $ifdFormName; + my $newValue; + my $newValuePt = $isNew >= 0 ? \$newValue : \$oldValue; + my $isOverwriting; + + if ($isNew >= 0) { + # add, edit or delete this tag + shift @newTags; # remove from list + my $curInfo = $set{$newID}; + unless ($curInfo or $$addDirs{$newID}) { + # we can finally get the specific tagInfo reference for this tag + # (because we can now evaluate the Condition statement since all + # DataMember's have been obtained for tags up to this one) + $curInfo = $et->GetTagInfo($tagTablePtr, $newID); + if (defined $curInfo and not $curInfo) { + # need value to evaluate the condition + # (tricky because we need the tagInfo ref to get the value, + # so we must loop through all new tagInfo's...) + foreach $tagInfo (@newTagInfo) { + next unless $$tagInfo{TagID} == $newID; + my $val = $et->GetNewValue($tagInfo); + defined $val or $mayDelete{$newID} = 1, next; + # must convert to binary for evaluating in Condition + my $fmt = $$tagInfo{Format} || $$tagInfo{Writable}; + if ($fmt) { + $val = WriteValue($val, $fmt, $$tagInfo{Count}); + defined $val or $mayDelete{$newID} = 1, next; + } + $curInfo = $et->GetTagInfo($tagTablePtr, $newID, \$val, $oldFormName, $oldCount); + if ($curInfo) { + last if $curInfo eq $tagInfo; + undef $curInfo; + } + } + # may want to delete this, but we need to see the old value first + $mayDelete{$newID} = 1 unless $curInfo; + } + # don't set this tag unless valid for the current condition + if ($curInfo and $$et{NEW_VALUE}{$curInfo}) { + $set{$newID} = $curInfo; + } else { + next if $isNew > 0; + $isNew = -1; + undef $curInfo; + } + } + if ($curInfo) { + if ($$curInfo{WriteCondition}) { + my $self = $et; # set $self to be used in eval + #### eval WriteCondition ($self) + unless (eval $$curInfo{WriteCondition}) { + $@ and warn $@; + goto NoWrite; # GOTO ! + } + } + my $nvHash; + $nvHash = $et->GetNewValueHash($curInfo, $dirName) if $isNew >= 0; + unless ($nvHash or defined $$mandatory{$newID}) { + goto NoWrite unless $wrongDir; # GOTO ! + # delete stuff from the wrong directory if setting somewhere else + $nvHash = $et->GetNewValueHash($curInfo, $wrongDir); + # don't cross delete if not overwriting + goto NoWrite unless $et->IsOverwriting($nvHash); # GOTO ! + # don't cross delete if specifically deleting from the other directory + # (Note: don't call GetValue() here because it shouldn't be called + # if IsOverwriting returns < 0 -- eg. when shifting) + if (not defined $$nvHash{Value} and $$nvHash{WantGroup} and + lc($$nvHash{WantGroup}) eq lc($wrongDir)) + { + goto NoWrite; # GOTO ! + } else { + # remove this tag if found in this IFD + $xDelete{$newID} = 1; + } + } + } elsif (not $$addDirs{$newID}) { +NoWrite: next if $isNew > 0; + delete $set{$newID}; + $isNew = -1; + } + if ($set{$newID}) { +# +# set the new tag value (or 'next' if deleting tag) +# + $newInfo = $set{$newID}; + $newCount = $$newInfo{Count}; + my ($val, $newVal, $n); + my $nvHash = $et->GetNewValueHash($newInfo, $dirName); + if ($isNew > 0) { + # don't create new entry unless requested + if ($nvHash) { + next unless $$nvHash{IsCreating}; + if ($$newInfo{IsOverwriting}) { + my $proc = $$newInfo{IsOverwriting}; + $isOverwriting = &$proc($et, $nvHash, $val, \$newVal); + } else { + $isOverwriting = $et->IsOverwriting($nvHash); + } + } else { + next if $xDelete{$newID}; # don't create if cross deleting + $newVal = $$mandatory{$newID}; # get value for mandatory tag + $isOverwriting = 1; + } + # convert using new format + if ($$newInfo{Format}) { + $newFormName = $$newInfo{Format}; + # use Writable flag to specify IFD format code + $ifdFormName = $$newInfo{Writable}; + } else { + $newFormName = $$newInfo{Writable}; + unless ($newFormName) { + warn("No format for $name $$newInfo{Name}\n"); + next; + } + } + $newFormat = $formatNumber{$newFormName}; + } elsif ($nvHash or $xDelete{$newID}) { + unless ($nvHash) { + $nvHash = $et->GetNewValueHash($newInfo, $wrongDir); + } + # read value + if (length $oldValue >= $oldSize) { + $val = ReadValue(\$oldValue, 0, $readFormName, $readCount, $oldSize); + } else { + $val = ''; + } + # determine write format (by default, use 'Writable' format) + my $writable = $$newInfo{Writable}; + # (or use existing format if 'Writable' not specified) + $writable = $oldFormName unless $writable and $writable ne '1'; + # (and override write format with 'Format' if specified) + my $writeForm = $$newInfo{Format} || $writable; + if ($writeForm ne $newFormName) { + # write in specified format + $newFormName = $writeForm; + $newFormat = $formatNumber{$newFormName}; + # use different IFD format code if necessary + if ($inMakerNotes) { + # always preserve IFD format in maker notes + $ifdFormName = $oldFormName; + } elsif ($writable ne $newFormName) { + # use specified IFD format + $ifdFormName = $writable; + } + } + if ($inMakerNotes and $readFormName ne 'string' and $readFormName ne 'undef') { + # keep same size in maker notes unless string or binary + $newCount = $oldCount * $formatSize[$oldFormat] / $formatSize[$newFormat]; + } + if ($$newInfo{IsOverwriting}) { + my $proc = $$newInfo{IsOverwriting}; + $isOverwriting = &$proc($et, $nvHash, $val, \$newVal); + } else { + $isOverwriting = $et->IsOverwriting($nvHash, $val); + } + } + if ($isOverwriting) { + $newVal = $et->GetNewValue($nvHash) unless defined $newVal; + # value undefined if deleting this tag + # (also delete tag if cross-deleting and this isn't a date/time shift) + if (not defined $newVal or ($xDelete{$newID} and not defined $$nvHash{Shift})) { + if (not defined $newVal and $$newInfo{RawConvInv} and defined $$nvHash{Value}) { + # error in RawConvInv, so rewrite existing tag + goto NoOverwrite; # GOTO! + } + unless ($isNew) { + ++$$et{CHANGED}; + $et->VerboseValue("- $dirName:$$newInfo{Name}", $val); + } + next; + } + if ($newCount and $newCount < 0) { + # set count to number of values if variable + my @vals = split ' ',$newVal; + $newCount = @vals; + } + # convert to binary format + $newValue = WriteValue($newVal, $newFormName, $newCount); + unless (defined $newValue) { + $et->Warn("Invalid value for $dirName:$$newInfo{Name}"); + goto NoOverwrite; # GOTO! + } + if (length $newValue) { + # limit maximum value length in JPEG images + # (max segment size is 65533 bytes and the min EXIF size is 96 incl an additional IFD entry) + if ($$et{FILE_TYPE} eq 'JPEG' and length($newValue) > 65436 and + $$newInfo{Name} ne 'PreviewImage') + { + my $name = $$newInfo{MakerNotes} ? 'MakerNotes' : $$newInfo{Name}; + $et->Warn("Writing large value for $name",1); + } + # re-code if necessary + if ($newFormName eq 'utf8') { + $newValue = $et->Encode($newValue, 'UTF8'); + } elsif ($strEnc and $newFormName eq 'string') { + $newValue = $et->Encode($newValue, $strEnc); + } + } else { + $et->Warn("Can't write zero length $$newInfo{Name} in $$tagTablePtr{GROUPS}{1}"); + goto NoOverwrite; # GOTO! + } + if ($isNew >= 0) { + $newCount = length($newValue) / $formatSize[$newFormat]; + ++$$et{CHANGED}; + if (defined $allMandatory) { + # not all mandatory if we are writing any tag specifically + if ($nvHash) { + undef $allMandatory; + undef $deleteAll; + } else { + ++$addMandatory; # count mandatory tags that we added + } + } + if ($verbose > 1) { + $et->VerboseValue("- $dirName:$$newInfo{Name}", $val) unless $isNew; + if ($$newInfo{OffsetPair} and $newVal eq '4277010157') { # (0xfeedfeed) + print { $$et{OPTIONS}{TextOut} } " + $dirName:$$newInfo{Name} = <tbd>\n"; + } else { + my $str = $nvHash ? '' : ' (mandatory)'; + $et->VerboseValue("+ $dirName:$$newInfo{Name}", $newVal, $str); + } + } + } + } else { +NoOverwrite: next if $isNew > 0; + $isNew = -1; # rewrite existing tag + } + # set format for EXIF IFD if different than conversion format + if ($ifdFormName) { + $newFormName = $ifdFormName; + $newFormat = $formatNumber{$newFormName}; + } + + } elsif ($isNew > 0) { +# +# create new subdirectory +# + # newInfo may not be defined if we try to add a mandatory tag + # to a directory that doesn't support it (eg. IFD1 in RW2 images) + $newInfo = $$addDirs{$newID} or next; + # make sure we don't try to generate a new MakerNotes directory + # or a SubIFD + next if $$newInfo{MakerNotes} or $$newInfo{Name} eq 'SubIFD'; + my $subTable; + if ($$newInfo{SubDirectory}{TagTable}) { + $subTable = Image::ExifTool::GetTagTable($$newInfo{SubDirectory}{TagTable}); + } else { + $subTable = $tagTablePtr; + } + # create empty source directory + my %sourceDir = ( + Parent => $dirName, + Fixup => new Image::ExifTool::Fixup, + ); + $sourceDir{DirName} = $$newInfo{Groups}{1} if $$newInfo{SubIFD}; + $newValue = $et->WriteDirectory(\%sourceDir, $subTable); + # only add new directory if it isn't empty + next unless defined $newValue and length($newValue); + # set the fixup start location + if ($$newInfo{SubIFD}) { + # subdirectory is referenced by an offset in value buffer + my $subdir = $newValue; + $newValue = Set32u(0xfeedf00d); + push @subdirs, { + DataPt => \$subdir, + Table => $subTable, + Fixup => $sourceDir{Fixup}, + Offset => length($dirBuff) + 8, + Where => 'dirBuff', + }; + $newFormName = 'int32u'; + $newFormat = $formatNumber{$newFormName}; + } else { + # subdirectory goes directly into value buffer + $sourceDir{Fixup}{Start} += length($valBuff); + # use Writable to set format, otherwise 'undef' + $newFormName = $$newInfo{Writable}; + unless ($newFormName and $formatNumber{$newFormName}) { + $newFormName = 'undef'; + } + $newFormat = $formatNumber{$newFormName}; + push @valFixups, $sourceDir{Fixup}; + } + } elsif ($$newInfo{Format} and $$newInfo{Writable} and $$newInfo{Writable} ne '1') { + # use specified write format + $newFormName = $$newInfo{Writable}; + $newFormat = $formatNumber{$newFormName}; + } elsif ($$addDirs{$newID} and $newInfo ne $$addDirs{$newID}) { + # this can happen if we are trying to add a directory that doesn't exist + # in this type of file (eg. try adding a SubIFD tag to an A100 image) + $isNew = -1; + } + } + if ($isNew < 0) { + # just rewrite existing tag + $newID = $oldID; + $newValue = $oldValue; + $newFormat = $oldFormat; # (just in case it changed) + $newFormName = $oldFormName; + # set offset of this entry in the directory so we can update the pointer + # and save block information to copy this large block later + if ($oldImageData) { + $$oldImageData[3] = $newStart + length($dirBuff) + 2; + push @imageData, $oldImageData; + $$origDirInfo{ImageData} = \@imageData; + } + } + if ($newInfo) { +# +# load necessary data for this tag (thumbnail image, etc) +# + if ($$newInfo{DataTag} and $isNew >= 0) { + my $dataTag = $$newInfo{DataTag}; + # load data for this tag + unless (defined $offsetData{$dataTag} or $dataTag eq 'LeicaTrailer') { + # prefer tag from Composite table if it exists (otherwise + # PreviewImage data would be taken from Extra tag) + my $compInfo = Image::ExifTool::GetCompositeTagInfo($dataTag); + $offsetData{$dataTag} = $et->GetNewValue($compInfo || $dataTag); + my $err; + if (defined $offsetData{$dataTag}) { + my $len = length $offsetData{$dataTag}; + if ($dataTag eq 'PreviewImage') { + # must set DEL_PREVIEW flag now if preview fit into IFD + $$et{DEL_PREVIEW} = 1 if $len <= 4; + } + } else { + $err = "$dataTag not found"; + } + if ($err) { + $et->Warn($err) if $$newInfo{IsOffset}; + delete $set{$newID}; # remove from list of tags we are setting + next; + } + } + } +# +# write maker notes +# + if ($$newInfo{MakerNotes}) { + # don't write new makernotes if we are deleting this group + if ($$et{DEL_GROUP}{MakerNotes} and + ($$et{DEL_GROUP}{MakerNotes} != 2 or $isNew <= 0)) + { + if ($et->IsRawType()) { + $et->WarnOnce("Can't delete MakerNotes from $$et{FileType}",1); + } else { + if ($isNew <= 0) { + ++$$et{CHANGED}; + $verbose and print $out " Deleting MakerNotes\n"; + } + next; + } + } + my $saveOrder = GetByteOrder(); + if ($isNew >= 0 and defined $set{$newID}) { + # we are writing a whole new maker note block + # --> add fixup information if necessary + my $nvHash = $et->GetNewValueHash($newInfo, $dirName); + if ($nvHash and $$nvHash{MAKER_NOTE_FIXUP}) { + # must clone fixup because we will be shifting it + my $makerFixup = $$nvHash{MAKER_NOTE_FIXUP}->Clone(); + my $valLen = length($valBuff); + $$makerFixup{Start} += $valLen; + push @valFixups, $makerFixup; + } + } else { + # update maker notes if possible + my %subdirInfo = ( + Base => $base, + DataPt => $valueDataPt, + DataPos => $valueDataPos, + DataLen => $valueDataLen, + DirStart => $valuePtr, + DirLen => $oldSize, + DirName => 'MakerNotes', + Name => $$newInfo{Name}, + Parent => $dirName, + TagInfo => $newInfo, + RAF => $raf, + ); + my ($subTable, $subdir, $loc, $writeProc, $notIFD); + if ($$newInfo{SubDirectory}) { + my $sub = $$newInfo{SubDirectory}; + $subdirInfo{FixBase} = 1 if $$sub{FixBase}; + $subdirInfo{FixOffsets} = $$sub{FixOffsets}; + $subdirInfo{EntryBased} = $$sub{EntryBased}; + $subdirInfo{NoFixBase} = 1 if defined $$sub{Base}; + $subdirInfo{AutoFix} = $$sub{AutoFix}; + SetByteOrder($$sub{ByteOrder}) if $$sub{ByteOrder}; + } + # get the proper tag table for these maker notes + if ($oldInfo and $$oldInfo{SubDirectory}) { + $subTable = $$oldInfo{SubDirectory}{TagTable}; + $subTable and $subTable = Image::ExifTool::GetTagTable($subTable); + $writeProc = $$oldInfo{SubDirectory}{WriteProc}; + $notIFD = $$oldInfo{NotIFD}; + } else { + $et->Warn('Internal problem getting maker notes tag table'); + } + $writeProc or $writeProc = $$subTable{WRITE_PROC} if $subTable; + $subTable or $subTable = $tagTablePtr; + if ($writeProc and + $writeProc eq \&Image::ExifTool::MakerNotes::WriteUnknownOrPreview and + $oldValue =~ /^\xff\xd8\xff/) + { + $loc = 0; + } elsif (not $notIFD) { + # look for IFD-style maker notes + $loc = Image::ExifTool::MakerNotes::LocateIFD($et,\%subdirInfo); + } + if (defined $loc) { + # we need fixup data for this subdirectory + $subdirInfo{Fixup} = new Image::ExifTool::Fixup; + # rewrite maker notes + my $changed = $$et{CHANGED}; + $subdir = $et->WriteDirectory(\%subdirInfo, $subTable, $writeProc); + if ($changed == $$et{CHANGED} and $subdirInfo{Fixup}->IsEmpty()) { + # return original data if nothing changed and no fixups + undef $subdir; + } + } elsif ($$subTable{PROCESS_PROC} and + $$subTable{PROCESS_PROC} eq \&Image::ExifTool::ProcessBinaryData) + { + my $sub = $$oldInfo{SubDirectory}; + if (defined $$sub{Start}) { + #### eval Start ($valuePtr) + my $start = eval $$sub{Start}; + $loc = $start - $valuePtr; + $subdirInfo{DirStart} = $start; + $subdirInfo{DirLen} -= $loc; + } else { + $loc = 0; + } + # rewrite maker notes + $subdir = $et->WriteDirectory(\%subdirInfo, $subTable); + } elsif ($notIFD) { + if ($writeProc) { + $loc = 0; + $subdir = $et->WriteDirectory(\%subdirInfo, $subTable); + } + } else { + my $msg = 'Maker notes could not be parsed'; + if ($$et{FILE_TYPE} eq 'JPEG') { + $et->Warn($msg, 1); + } else { + $et->Error($msg, 1); + } + } + if (defined $subdir) { + length $subdir or SetByteOrder($saveOrder), next; + my $valLen = length($valBuff); + # restore existing header and substitute the new + # maker notes for the old value + $newValue = substr($oldValue, 0, $loc) . $subdir; + my $makerFixup = $subdirInfo{Fixup}; + my $previewInfo = $$et{PREVIEW_INFO}; + if ($subdirInfo{Relative}) { + # apply a one-time fixup to $loc since offsets are relative + $$makerFixup{Start} += $loc; + # shift all offsets to be relative to new base + my $baseShift = $valueDataPos + $valuePtr + $base - $subdirInfo{Base}; + $$makerFixup{Shift} += $baseShift; + $makerFixup->ApplyFixup(\$newValue); + if ($previewInfo) { + # remove all but PreviewImage fixup (since others shouldn't change) + foreach (keys %{$$makerFixup{Pointers}}) { + /_PreviewImage$/ or delete $$makerFixup{Pointers}{$_}; + } + # zero pointer so we can see how it gets shifted later + $makerFixup->SetMarkerPointers(\$newValue, 'PreviewImage', 0); + # set the pointer to the start of the EXIF information + # add preview image fixup to list of value fixups + $$makerFixup{Start} += $valLen; + push @valFixups, $makerFixup; + $$previewInfo{BaseShift} = $baseShift; + $$previewInfo{Relative} = 1; + } + # don't shift anything if relative flag set to zero (Pentax patch) + } elsif (not defined $subdirInfo{Relative}) { + # shift offset base if shifted in the original image or if FixBase + # was used, but be careful of automatic FixBase with negative shifts + # since they may lead to negative (invalid) offsets (casio_edit_problem.jpg) + my $baseShift = $base - $subdirInfo{Base}; + if ($subdirInfo{AutoFix}) { + $baseShift = 0; + } elsif ($subdirInfo{FixBase} and $baseShift < 0 and + # allow negative base shift if offsets are bigger (PentaxOptioWP.jpg) + (not $subdirInfo{MinOffset} or $subdirInfo{MinOffset} + $baseShift < 0)) + { + my $fixBase = $et->Options('FixBase'); + if (not defined $fixBase) { + my $str = $et->Options('IgnoreMinorErrors') ? 'ignored' : 'fix or ignore?'; + $et->Error("MakerNotes offsets may be incorrect ($str)", 1); + } elsif ($fixBase eq '') { + $et->Warn('Fixed incorrect MakerNotes offsets'); + $baseShift = 0; + } + } + $$makerFixup{Start} += $valLen + $loc; + $$makerFixup{Shift} += $baseShift; + # permanently fix makernote offset errors + $$makerFixup{Shift} += $subdirInfo{FixedBy} || 0; + push @valFixups, $makerFixup; + if ($previewInfo and not $$previewInfo{NoBaseShift}) { + $$previewInfo{BaseShift} = $baseShift; + } + } + $newValuePt = \$newValue; # write new value + } + } + SetByteOrder($saveOrder); + + # process existing subdirectory unless we are overwriting it entirely + } elsif ($$newInfo{SubDirectory} and $isNew <= 0 and not $isOverwriting + # don't edit directory if Writable is set to 0 + and (not defined $$newInfo{Writable} or $$newInfo{Writable}) and + not $$newInfo{ReadFromRAF}) + { + + my $subdir = $$newInfo{SubDirectory}; + if ($$newInfo{SubIFD}) { +# +# rewrite existing sub IFD's +# + my $subTable = $tagTablePtr; + if ($$subdir{TagTable}) { + $subTable = Image::ExifTool::GetTagTable($$subdir{TagTable}); + } + # determine directory name for this IFD + my $subdirName = $$newInfo{Groups}{1} || $$newInfo{Name}; + # all makernotes directory names must be 'MakerNotes' + $subdirName = 'MakerNotes' if $$subTable{GROUPS}{0} eq 'MakerNotes'; + # must handle sub-IFD's specially since the values + # are actually offsets to subdirectories + unless ($readCount) { # can't have zero count + return undef if $et->Error("$name entry $index has zero count", 2); + next; + } + my $writeCount = 0; + my $i; + $newValue = ''; # reset value because we regenerate it below + for ($i=0; $i<$readCount; ++$i) { + my $off = $i * $formatSize[$readFormat]; + my $val = ReadValue($valueDataPt, $valuePtr + $off, + $readFormName, 1, $oldSize - $off); + my $subdirStart = $val - $dataPos; + my $subdirBase = $base; + my $hdrLen; + if (defined $$subdir{Start}) { + #### eval Start ($val) + my $newStart = eval $$subdir{Start}; + unless (Image::ExifTool::IsInt($newStart)) { + $et->Error("Bad subdirectory start for $$newInfo{Name}"); + next; + } + $newStart -= $dataPos; + $hdrLen = $newStart - $subdirStart; + $subdirStart = $newStart; + } + if ($$subdir{Base}) { + my $start = $subdirStart + $dataPos; + #### eval Base ($start,$base) + $subdirBase += eval $$subdir{Base}; + } + # add IFD number if more than one + $subdirName =~ s/\d*$/$i/ if $i; + my %subdirInfo = ( + Base => $subdirBase, + DataPt => $dataPt, + DataPos => $dataPos - $subdirBase + $base, + DataLen => $dataLen, + DirStart => $subdirStart, + DirName => $subdirName, + Name => $$newInfo{Name}, + TagInfo => $newInfo, + Parent => $dirName, + Fixup => new Image::ExifTool::Fixup, + RAF => $raf, + Subdir => $subdir, + # set ImageData only for 1st level SubIFD's + ImageData=> $imageDataFlag eq 'Main' ? 'SubIFD' : undef, + ); + # pass on header pointer only for certain sub IFD's + $subdirInfo{HeaderPtr} = $$dirInfo{HeaderPtr} if $$newInfo{SubIFD} == 2; + if ($$subdir{RelativeBase}) { + # apply one-time fixup if offsets are relative (Sony IDC hack) + delete $subdirInfo{Fixup}; + delete $subdirInfo{ImageData}; + } + # is the subdirectory outside our current data? + if ($subdirStart < 0 or $subdirStart + 2 > $dataLen) { + if ($raf) { + # reset SubDirectory buffer (we will load it later) + my $buff = ''; + $subdirInfo{DataPt} = \$buff; + $subdirInfo{DataLen} = 0; + } else { + my @err = ("Can't read $subdirName data", $inMakerNotes); + if ($$subTable{VARS} and $$subTable{VARS}{MINOR_ERRORS}) { + $et->Warn($err[0] . '. Ignored.'); + } elsif ($et->Error(@err)) { + return undef; + } + next Entry; # don't write this directory + } + } + my $subdirData = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc}); + unless (defined $subdirData) { + # WriteDirectory should have issued an error, but check just in case + $et->Error("Error writing $subdirName") unless $$et{VALUE}{Error}; + return undef; + } + # add back original header if necessary (eg. Ricoh GR) + if ($hdrLen and $hdrLen > 0 and $subdirStart <= $dataLen) { + $subdirData = substr($$dataPt, $subdirStart - $hdrLen, $hdrLen) . $subdirData; + $subdirInfo{Fixup}{Start} += $hdrLen; + } + unless (length $subdirData) { + next unless $inMakerNotes; + # don't delete MakerNote Sub-IFD's, write empty IFD instead + $subdirData = "\0" x 6; + # reset SubIFD ImageData and Fixup just to be safe + delete $subdirInfo{ImageData}; + delete $subdirInfo{Fixup}; + } + # handle data blocks that we will transfer later + if (ref $subdirInfo{ImageData}) { + push @imageData, @{$subdirInfo{ImageData}}; + $$origDirInfo{ImageData} = \@imageData; + } + # temporarily set value to subdirectory index + # (will set to actual offset later when we know what it is) + $newValue .= Set32u(0xfeedf00d); + my ($offset, $where); + if ($readCount > 1) { + $offset = length($valBuff) + $i * 4; + $where = 'valBuff'; + } else { + $offset = length($dirBuff) + 8; + $where = 'dirBuff'; + } + # add to list of subdirectories we will append later + push @subdirs, { + DataPt => \$subdirData, + Table => $subTable, + Fixup => $subdirInfo{Fixup}, + Offset => $offset, + Where => $where, + ImageData => $subdirInfo{ImageData}, + }; + ++$writeCount; # count number of subdirs written + } + next unless length $newValue; + # must change location of subdir offset if we deleted + # a directory and only one remains + if ($writeCount < $readCount and $writeCount == 1) { + $subdirs[-1]{Where} = 'dirBuff'; + $subdirs[-1]{Offset} = length($dirBuff) + 8; + } + # set new format to int32u for IFD + $newFormName = $$newInfo{FixFormat} || 'int32u'; + $newFormat = $formatNumber{$newFormName}; + $newValuePt = \$newValue; + + } elsif ((not defined $$subdir{Start} or + $$subdir{Start} =~ /\$valuePtr/) and + $$subdir{TagTable}) + { +# +# rewrite other existing subdirectories ('$valuePtr' type only) +# + # set subdirectory Start and Base + my $subdirStart = $valuePtr; + if ($$subdir{Start}) { + #### eval Start ($valuePtr) + $subdirStart = eval $$subdir{Start}; + # must adjust directory size if start changed + $oldSize -= $subdirStart - $valuePtr; + } + my $subdirBase = $base; + if ($$subdir{Base}) { + my $start = $subdirStart + $valueDataPos; + #### eval Base ($start,$base) + $subdirBase += eval $$subdir{Base}; + } + my $subFixup = new Image::ExifTool::Fixup; + my %subdirInfo = ( + Base => $subdirBase, + DataPt => $valueDataPt, + DataPos => $valueDataPos - $subdirBase + $base, + DataLen => $valueDataLen, + DirStart => $subdirStart, + DirName => $$subdir{DirName}, + DirLen => $oldSize, + Parent => $dirName, + Fixup => $subFixup, + RAF => $raf, + TagInfo => $newInfo, + ); + unless ($oldSize) { + # replace with dummy data if empty to prevent WriteDirectory + # routines from accessing data they shouldn't + my $tmp = ''; + $subdirInfo{DataPt} = \$tmp; + $subdirInfo{DataLen} = 0; + $subdirInfo{DirStart} = 0; + $subdirInfo{DataPos} += $subdirStart; + } + my $subTable = Image::ExifTool::GetTagTable($$subdir{TagTable}); + my $oldOrder = GetByteOrder(); + SetByteOrder($$subdir{ByteOrder}) if $$subdir{ByteOrder}; + $newValue = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc}); + SetByteOrder($oldOrder); + if (defined $newValue) { + my $hdrLen = $subdirStart - $valuePtr; + if ($hdrLen) { + $newValue = substr($$valueDataPt, $valuePtr, $hdrLen) . $newValue; + $$subFixup{Start} += $hdrLen; + } + $newValuePt = \$newValue; + } else { + $newValuePt = \$oldValue; + } + unless (length $$newValuePt) { + # don't delete a previously empty makernote directory + next if $oldSize or not $inMakerNotes; + } + if ($$subFixup{Pointers} and $subdirInfo{Base} == $base) { + $$subFixup{Start} += length $valBuff; + push @valFixups, $subFixup; + } else { + # apply fixup in case we added a header ($hdrLen above) + $subFixup->ApplyFixup(\$newValue); + } + } + + } elsif ($$newInfo{OffsetPair}) { +# +# keep track of offsets +# + my $dataTag = $$newInfo{DataTag} || ''; + if ($dataTag eq 'CanonVRD') { + # must decide now if we will write CanonVRD information + my $hasVRD; + if ($$et{NEW_VALUE}{$Image::ExifTool::Extra{CanonVRD}}) { + # adding or deleting as a block + $hasVRD = $et->GetNewValue('CanonVRD') ? 1 : 0; + } elsif ($$et{DEL_GROUP}{CanonVRD} or + $$et{DEL_GROUP}{Trailer}) + { + $hasVRD = 0; # deleting as a group + } else { + $hasVRD = ($$newValuePt ne "\0\0\0\0"); + } + if ($hasVRD) { + # add a fixup, and set this offset later + $dirFixup->AddFixup(length($dirBuff) + 8, $dataTag); + } else { + # there is (or will soon be) no VRD information, so set pointer to zero + $newValue = "\0" x length($$newValuePt); + $newValuePt = \$newValue; + } + } elsif ($dataTag eq 'OriginalDecisionData') { + # handle Canon OriginalDecisionData (no associated length tag) + # - I'm going out of my way here to preserve data which is + # invalidated anyway by our edits + my $odd; + my $oddInfo = Image::ExifTool::GetCompositeTagInfo('OriginalDecisionData'); + if ($oddInfo and $$et{NEW_VALUE}{$oddInfo}) { + $odd = $et->GetNewValue($dataTag); + if ($verbose > 1) { + print $out " - $dirName:$dataTag\n" if $$newValuePt ne "\0\0\0\0"; + print $out " + $dirName:$dataTag\n" if $odd; + } + ++$$et{CHANGED}; + } elsif ($$newValuePt ne "\0\0\0\0") { + if (length($$newValuePt) == 4) { + require Image::ExifTool::Canon; + my $offset = Get32u($newValuePt,0); + # absolute offset in JPEG images only + $offset += $base unless $$et{FILE_TYPE} eq 'JPEG'; + $odd = Image::ExifTool::Canon::ReadODD($et, $offset); + $odd = $$odd if ref $odd; + } else { + $et->Error("Invalid $$newInfo{Name}",1); + } + } + if ($odd) { + my $newOffset = length($valBuff); + # (ODD offset is absolute in JPEG, so add base offset!) + $newOffset += $base if $$et{FILE_TYPE} eq 'JPEG'; + $newValue = Set32u($newOffset); + $dirFixup->AddFixup(length($dirBuff) + 8, $dataTag); + $valBuff .= $odd; # add original decision data + } else { + $newValue = "\0\0\0\0"; + } + $newValuePt = \$newValue; + } else { + my $offsetInfo = $offsetInfo[$ifd]; + # save original values (for updating TIFF_END later) + my @vals; + if ($isNew <= 0) { + my $oldOrder = GetByteOrder(); + # Minolta A200 stores these in the wrong byte order! + SetByteOrder($$newInfo{ByteOrder}) if $$newInfo{ByteOrder}; + @vals = ReadValue(\$oldValue, 0, $readFormName, $readCount, $oldSize); + SetByteOrder($oldOrder); + $validateInfo{$newID} = [$newInfo, join(' ',@vals)] unless $$newInfo{IsOffset}; + } + # only support int32 pointers (for now) + if ($formatSize[$newFormat] != 4 and $$newInfo{IsOffset}) { + $isNew > 0 and warn("Internal error (Offset not int32)"), return undef; + $newCount != $readCount and warn("Wrong count!"), return undef; + # change to int32 + $newFormName = 'int32u'; + $newFormat = $formatNumber{$newFormName}; + $newValue = WriteValue(join(' ',@vals), $newFormName, $newCount); + unless (defined $newValue) { + warn "Internal error writing offsets for $$newInfo{Name}\n"; + return undef; + } + $newValuePt = \$newValue; + } + $offsetInfo or $offsetInfo = $offsetInfo[$ifd] = { }; + # save location of valuePtr in new directory + # (notice we add 10 instead of 8 for valuePtr because + # we will put a 2-byte count at start of directory later) + my $ptr = $newStart + length($dirBuff) + 10; + $newCount or $newCount = 1; # make sure count is set for offsetInfo + # save value pointer and value count for each tag + $$offsetInfo{$newID} = [$newInfo, $ptr, $newCount, \@vals, $newFormat]; + } + + } elsif ($$newInfo{DataMember}) { + + # save any necessary data members (Make, Model, etc) + my $formatStr = $newFormName; + my $count = $newCount; + # change to specified format if necessary + if ($$newInfo{Format} and $$newInfo{Format} ne $formatStr) { + $formatStr = $$newInfo{Format}; + my $format = $formatNumber{$formatStr}; + # adjust number of items for new format size + $count = int(length($$newValuePt) / $formatSize[$format]) if $format; + } + my $val = ReadValue($newValuePt,0,$formatStr,$count,length($$newValuePt)); + my $conv = $$newInfo{RawConv}; + if ($conv) { + # let the RawConv store the (possibly converted) data member + if (ref $conv eq 'CODE') { + &$conv($val, $et); + } else { + my ($priority, @grps); + my ($self, $tag, $tagInfo) = ($et, $$newInfo{Name}, $newInfo); + #### eval RawConv ($self, $val, $tag, $tagInfo, $priority, @grps) + eval $conv; + } + } else { + $$et{$$newInfo{DataMember}} = $val; + } + } + } +# +# write out the directory entry +# + my $newSize = length($$newValuePt); + my $fsize = $formatSize[$newFormat]; + my $offsetVal; + # set proper count + $newCount = int(($newSize + $fsize - 1) / $fsize) unless $oldInfo and $$oldInfo{FixedSize}; + if ($saveForValidate{$newID} and $tagTablePtr eq \%Image::ExifTool::Exif::Main) { + my @vals = ReadValue(\$newValue, 0, $newFormName, $newCount, $newSize); + $validateInfo{$newID} = join ' ',@vals; + } + if ($newSize > 4) { + # zero-pad to an even number of bytes (required by EXIF standard) + # and make sure we are a multiple of the format size + while ($newSize & 0x01 or $newSize < $newCount * $fsize) { + $$newValuePt .= "\0"; + ++$newSize; + } + my $entryBased; + if ($$dirInfo{EntryBased} or ($newInfo and $$newInfo{EntryBased})) { + $entryBased = 1; + $offsetVal = Set32u(length($valBuff) - length($dirBuff)); + } else { + $offsetVal = Set32u(length $valBuff); + } + my ($dataTag, $putFirst); + ($dataTag, $putFirst) = @$newInfo{'DataTag','PutFirst'} if $newInfo; + if ($dataTag) { + if ($dataTag eq 'PreviewImage' and ($$et{FILE_TYPE} eq 'JPEG' or + $$et{GENERATE_PREVIEW_INFO})) + { + # hold onto the PreviewImage until we can determine if it fits + $$et{PREVIEW_INFO} or $$et{PREVIEW_INFO} = { + Data => $$newValuePt, + Fixup => new Image::ExifTool::Fixup, + }; + $$et{PREVIEW_INFO}{ChangeBase} = 1 if $$newInfo{ChangeBase}; + if ($$newInfo{IsOffset} and $$newInfo{IsOffset} eq '2') { + $$et{PREVIEW_INFO}{NoBaseShift} = 1; + } + # use original preview size if we will attempt to load it later + $newCount = $oldCount if $$newValuePt eq 'LOAD_PREVIEW'; + $$newValuePt = ''; + } elsif ($dataTag eq 'LeicaTrailer' and $$et{LeicaTrailer}) { + $$newValuePt = ''; + } + } + if ($putFirst and $$dirInfo{HeaderPtr}) { + my $hdrPtr = $$dirInfo{HeaderPtr}; + # place this value immediately after the TIFF header (eg. IIQ maker notes) + $offsetVal = Set32u(length $$hdrPtr); + $$hdrPtr .= $$newValuePt; + } else { + $valBuff .= $$newValuePt; # add value data to buffer + # must save a fixup pointer for every pointer in the directory + if ($entryBased) { + $entryBasedFixup or $entryBasedFixup = new Image::ExifTool::Fixup; + $entryBasedFixup->AddFixup(length($dirBuff) + 8, $dataTag); + } else { + $dirFixup->AddFixup(length($dirBuff) + 8, $dataTag); + } + } + } else { + $offsetVal = $$newValuePt; # save value in offset if 4 bytes or less + # must pad value with zeros if less than 4 bytes + $newSize < 4 and $offsetVal .= "\0" x (4 - $newSize); + } + # write the directory entry + $dirBuff .= Set16u($newID) . Set16u($newFormat) . + Set32u($newCount) . $offsetVal; + # update flag to keep track of mandatory tags + while (defined $allMandatory) { + if (defined $$mandatory{$newID}) { + # values must correspond to mandatory values + my $form = $$newInfo{Format} || $newFormName; + my $mandVal = WriteValue($$mandatory{$newID}, $form, $newCount); + if (defined $mandVal and $mandVal eq $$newValuePt) { + ++$allMandatory; # count mandatory tags + last; + } + } + undef $deleteAll; + undef $allMandatory; + } + } + if (%validateInfo) { + ValidateImageData($et, \%validateInfo, $dirName, 1); + undef %validateInfo; + } + if ($ignoreCount) { + my $y = $ignoreCount > 1 ? 'ies' : 'y'; + my $verb = $$dirInfo{FixBase} ? 'Ignored' : 'Removed'; + $et->Warn("$verb $ignoreCount invalid entr$y from $name", 1); + } + if ($fixCount) { + my $s = $fixCount > 1 ? 's' : ''; + $et->Warn("Fixed invalid count$s for $fixCount $name tag$s", 1); + } +#.............................................................................. +# write directory counts and nextIFD pointer and add value data to end of IFD +# + # determine now if there is or will be another IFD after this one + my $nextIfdOffset; + if ($dirEnd + 4 <= $dataLen) { + $nextIfdOffset = Get32u($dataPt, $dirEnd); + } else { + $nextIfdOffset = 0; + } + my $isNextIFD = ($$dirInfo{Multi} and ($nextIfdOffset or + # account for the case where we will create the next IFD + # (IFD1 only, but not in TIFF-format images) + ($dirName eq 'IFD0' and $$et{ADD_DIRS}{'IFD1'} and + $$et{FILE_TYPE} ne 'TIFF'))); + # calculate number of entries in new directory + my $newEntries = length($dirBuff) / 12; + # delete entire directory if we deleted a tag and only mandatory tags remain or we + # attempted to create a directory with only mandatory tags and there is no nextIFD + if ($allMandatory and not $isNextIFD and ($newEntries < $numEntries or $numEntries == 0)) { + $newEntries = 0; + $dirBuff = ''; + $valBuff = ''; + undef $dirFixup; # no fixups in this directory + ++$deleteAll if defined $deleteAll; + $verbose > 1 and print $out " - $allMandatory mandatory tag(s)\n"; + $$et{CHANGED} -= $addMandatory; # didn't change these after all + } + if ($ifd and not $newEntries) { + $verbose and print $out " Deleting IFD1\n"; + last; # don't write IFD1 if empty + } + # apply one-time fixup for entry-based offsets + if ($entryBasedFixup) { + $$entryBasedFixup{Shift} = length($dirBuff) + 4; + $entryBasedFixup->ApplyFixup(\$dirBuff); + undef $entryBasedFixup; + } + # initialize next IFD pointer to zero + my $nextIFD = Set32u(0); + # some cameras use a different amount of padding after the makernote IFD + if ($dirName eq 'MakerNotes' and $$dirInfo{Parent} =~ /^(ExifIFD|IFD0)$/) { + my ($rel, $pad) = Image::ExifTool::MakerNotes::GetMakerNoteOffset($et); + $nextIFD = "\0" x $pad if defined $pad and ($pad==0 or ($pad>4 and $pad<=32)); + } + # add directory entry count to start of IFD and next IFD pointer to end + $newData .= Set16u($newEntries) . $dirBuff . $nextIFD; + # get position of value data in newData + my $valPos = length($newData); + # go back now and set next IFD pointer if this isn't the first IFD + if ($nextIfdPos) { + # set offset to next IFD + Set32u($newStart, \$newData, $nextIfdPos); + $fixup->AddFixup($nextIfdPos,'NextIFD'); # add fixup for this offset in newData + } + # remember position of 'next IFD' pointer so we can set it next time around + $nextIfdPos = length($nextIFD) ? $valPos - length($nextIFD) : undef; + # add value data after IFD + $newData .= $valBuff; +# +# add any subdirectories, adding fixup information +# + if (@subdirs) { + my $subdir; + foreach $subdir (@subdirs) { + my $len = length($newData); # position of subdirectory in data + my $subdirFixup = $$subdir{Fixup}; + if ($subdirFixup) { + $$subdirFixup{Start} += $len; + $fixup->AddFixup($subdirFixup); + } + my $imageData = $$subdir{ImageData}; + my $blockSize = 0; + # must also update start position for ImageData fixups + if (ref $imageData) { + my $blockInfo; + foreach $blockInfo (@$imageData) { + my ($pos, $size, $pad, $entry, $subFix) = @$blockInfo; + if ($subFix) { + $$subFix{Start} += $len; + # save expected image data offset for calculating shift later + $$subFix{BlockLen} = length(${$$subdir{DataPt}}) + $blockSize; + } + $blockSize += $size + $pad; + } + } + $newData .= ${$$subdir{DataPt}}; # add subdirectory to our data + undef ${$$subdir{DataPt}}; # free memory now + # set the pointer + my $offset = $$subdir{Offset}; + # if offset is in valBuff, it was added to the end of dirBuff + # (plus 4 bytes for nextIFD pointer) + $offset += length($dirBuff) + 4 if $$subdir{Where} eq 'valBuff'; + $offset += $newStart + 2; # get offset in newData + # check to be sure we got the right offset + unless (Get32u(\$newData, $offset) == 0xfeedf00d) { + $et->Error("Internal error while rewriting $name"); + return undef; + } + # set the offset to the subdirectory data + Set32u($len, \$newData, $offset); + $fixup->AddFixup($offset); # add fixup for this offset in newData + } + } + # add fixup for all offsets in directory according to value data position + # (which is at the end of this directory) + if ($dirFixup) { + $$dirFixup{Start} = $newStart + 2; + $$dirFixup{Shift} = $valPos - $$dirFixup{Start}; + $fixup->AddFixup($dirFixup); + } + # add valueData fixups, adjusting for position of value data + my $valFixup; + foreach $valFixup (@valFixups) { + $$valFixup{Start} += $valPos; + $fixup->AddFixup($valFixup); + } + # stop if no next IFD pointer + last unless $isNextIFD; # stop unless scanning for multiple IFD's + if ($nextIfdOffset) { + # continue with next IFD + $dirStart = $nextIfdOffset - $dataPos; + } else { + # create IFD1 if necessary + $verbose and print $out " Creating IFD1\n"; + my $ifd1 = "\0" x 2; # empty IFD1 data (zero entry count) + $dataPt = \$ifd1; + $dirStart = 0; + $dirLen = $dataLen = 2; + } + # increment IFD name + my $ifdNum = $dirName =~ s/(\d+)$// ? $1 : 0; + $dirName .= $ifdNum + 1; + $name =~ s/\d+$//; + $name .= $ifdNum + 1; + $$et{DIR_NAME} = $$et{PATH}[-1] = $dirName; + next unless $nextIfdOffset; + + # guard against writing the same directory twice + my $addr = $nextIfdOffset + $base; + if ($$et{PROCESSED}{$addr}) { + $et->Error("$name pointer references previous $$et{PROCESSED}{$addr} directory", 1); + last; + } + $$et{PROCESSED}{$addr} = $name; + + if ($dirName eq 'SubIFD1' and not ValidateIFD($dirInfo, $dirStart)) { + if ($$et{TIFF_TYPE} eq 'TIFF') { + $et->Error('Ignored bad IFD linked from SubIFD', 1); + } elsif ($verbose) { + $et->Warn('Ignored bad IFD linked from SubIFD'); + } + last; # don't write bad IFD + } + if ($$et{DEL_GROUP}{$dirName}) { + $verbose and print $out " Deleting $dirName\n"; + $raf and $et->Error("Deleting $dirName also deletes subsequent" . + " IFD's and possibly image data", 1); + ++$$et{CHANGED}; + if ($$et{DEL_GROUP}{$dirName} == 2 and + $$et{ADD_DIRS}{$dirName}) + { + my $emptyIFD = "\0" x 2; # start with empty IFD + $dataPt = \$emptyIFD; + $dirStart = 0; + $dirLen = $dataLen = 2; + } else { + last; # don't write this IFD (or any subsequent IFD) + } + } else { + $verbose and print $out " Rewriting $name\n"; + } + } +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + # do our fixups now so we can more easily calculate offsets below + $fixup->ApplyFixup(\$newData); +# +# determine total block size for deferred data +# + my $numBlocks = scalar @imageData; # save this so we scan only existing blocks later + my $blockSize = 0; # total size of blocks to copy later + my $blockInfo; + foreach $blockInfo (@imageData) { + my ($pos, $size, $pad) = @$blockInfo; + $blockSize += $size + $pad; + } +# +# copy over image data for IFD's, starting with the last IFD first +# + if (@offsetInfo) { + my $ttwLen; # length of MRW TTW segment + my @writeLater; # write image data last + for ($ifd=$#offsetInfo; $ifd>=-1; --$ifd) { + # build list of offsets to process + my @offsetList; + if ($ifd >= 0) { + my $offsetInfo = $offsetInfo[$ifd] or next; + if ($$offsetInfo{0x111} and $$offsetInfo{0x144}) { + # SubIFD may contain double-referenced data as both strips and tiles + # for Sony ARW files when SonyRawFileType is "Lossless Compressed RAW 2" + if ($dirName eq 'SubIFD' and $$et{TIFF_TYPE} eq 'ARW' and + $$offsetInfo{0x117} and $$offsetInfo{0x145} and + $$offsetInfo{0x111}[2]==1) # (must be a single strip or the tile offsets could get out of sync) + { + # some Sony ARW images contain double-referenced raw data stored as both strips + # and tiles. Copy the data using only the strip tags, but store the TileOffets + # information for updating later (see PanasonicRaw:PatchRawDataOffset for a + # description of offsetInfo elements) + $$offsetInfo{0x111}[5] = $$offsetInfo{0x144}; # hack to save TileOffsets + # delete tile information from offsetInfo because we will copy as strips + delete $$offsetInfo{0x144}; + delete $$offsetInfo{0x145}; + } else { + $et->Error("TIFF $dirName contains both strip and tile data"); + } + } + # patch Panasonic RAW/RW2 StripOffsets/StripByteCounts if necessary + my $stripOffsets = $$offsetInfo{0x111}; + my $rawDataOffset = $$offsetInfo{0x118}; + if ($stripOffsets and $$stripOffsets[0]{PanasonicHack} or + $rawDataOffset and $$rawDataOffset[0]{PanasonicHack}) + { + require Image::ExifTool::PanasonicRaw; + my $err = Image::ExifTool::PanasonicRaw::PatchRawDataOffset($offsetInfo, $raf, $ifd); + $err and $et->Error($err); + } + my $tagID; + # loop through all tags in reverse numerical order so we save thumbnail + # data before main image data if both exist in the same IFD + foreach $tagID (reverse sort { $a <=> $b } keys %$offsetInfo) { + my $tagInfo = $$offsetInfo{$tagID}[0]; + next unless $$tagInfo{IsOffset}; # handle byte counts with offsets + my $sizeInfo = $$offsetInfo{$$tagInfo{OffsetPair}}; + $sizeInfo or $et->Error("No size tag for $dirName:$$tagInfo{Name}"), next; + my $dataTag = $$tagInfo{DataTag}; + # write TIFF image data (strips or tiles) later if requested + if ($raf and defined $$origDirInfo{ImageData} and + ($tagID == 0x111 or $tagID == 0x144 or + # also defer writing of other big data such as JpgFromRaw in NEF + ($$sizeInfo[3][0] and + # (calculate approximate combined size of all blocks) + $$sizeInfo[3][0] * scalar(@{$$sizeInfo[3]}) > 1000000)) and + # but don't defer writing if replacing with new value + (not defined $dataTag or not defined $offsetData{$dataTag})) + { + push @writeLater, [ $$offsetInfo{$tagID}, $sizeInfo ]; + } else { + push @offsetList, [ $$offsetInfo{$tagID}, $sizeInfo ]; + } + } + } else { + last unless @writeLater; + # finally, copy all deferred data + @offsetList = @writeLater; + } + my $offsetPair; + foreach $offsetPair (@offsetList) { + my ($tagInfo, $offsets, $count, $oldOffset) = @{$$offsetPair[0]}; + my ($cntInfo, $byteCounts, $count2, $oldSize, $format) = @{$$offsetPair[1]}; + # must be the same number of offset and byte count values + unless ($count == $count2) { + $et->Error("Offsets/ByteCounts disagree on count for $$tagInfo{Name}"); + return undef; + } + my $formatStr = $formatName[$format]; + # follow pointer to value data if necessary + $count > 1 and $offsets = Get32u(\$newData, $offsets); + my $n = $count * $formatSize[$format]; + $n > 4 and $byteCounts = Get32u(\$newData, $byteCounts); + if ($byteCounts < 0 or $byteCounts + $n > length($newData)) { + $et->Error("Error reading $$tagInfo{Name} byte counts"); + return undef; + } + # get offset base and data pos (abnormal for some preview images) + my ($dbase, $dpos, $wrongBase, $subIfdDataFixup); + if ($$tagInfo{IsOffset} eq '2') { + $dbase = $firstBase; + $dpos = $dataPos + $base - $firstBase; + } else { + $dbase = $base; + $dpos = $dataPos; + } + # use different base if necessary for some offsets (Minolta A200) + if ($$tagInfo{WrongBase}) { + my $self = $et; + #### eval WrongBase ($self) + $wrongBase = eval $$tagInfo{WrongBase} || 0; + $dbase += $wrongBase; + $dpos -= $wrongBase; + } else { + $wrongBase = 0; + } + my $oldOrder = GetByteOrder(); + my $dataTag = $$tagInfo{DataTag}; + # use different byte order for values of this offset pair if required (Minolta A200) + SetByteOrder($$tagInfo{ByteOrder}) if $$tagInfo{ByteOrder}; + # transfer the data referenced by all offsets of this tag + for ($n=0; $n<$count; ++$n) { + my ($oldEnd, $size); + if (@$oldOffset and @$oldSize) { + # calculate end offset of this block + $oldEnd = $$oldOffset[$n] + $$oldSize[$n]; + # update TIFF_END as if we read this data from file + UpdateTiffEnd($et, $oldEnd + $dbase); + } + my $offsetPos = $offsets + $n * 4; + my $byteCountPos = $byteCounts + $n * $formatSize[$format]; + if ($$tagInfo{PanasonicHack}) { + # use actual raw data length (may be different than StripByteCounts!) + $size = $$oldSize[$n]; + } else { + # use size of new data + $size = ReadValue(\$newData, $byteCountPos, $formatStr, 1, 4); + } + my $offset = $$oldOffset[$n]; + if (defined $offset) { + $offset -= $dpos; + } elsif ($size != 0xfeedfeed) { + $et->Error('Internal error (no offset)'); + return undef; + } + my $newOffset = length($newData) - $wrongBase; + my $buff; + # look for 'feed' code to use our new data + if ($size == 0xfeedfeed) { + unless (defined $dataTag) { + $et->Error("No DataTag defined for $$tagInfo{Name}"); + return undef; + } + unless (defined $offsetData{$dataTag}) { + $et->Error("Internal error (no $dataTag)"); + return undef; + } + if ($count > 1) { + $et->Error("Can't modify $$tagInfo{Name} with count $count"); + return undef; + } + $buff = $offsetData{$dataTag}; + if ($formatSize[$format] != 4) { + $et->Error("$$cntInfo{Name} is not int32"); + return undef; + } + # set the data size + $size = length($buff); + Set32u($size, \$newData, $byteCountPos); + } elsif ($ifd < 0) { + # hack for fixed-offset data (Panasonic GH6) + if ($$offsetPair[0][6]) { + if ($count > 1) { + $et->Error("Can't handle fixed offsets with count > 1"); + } else { + my $fixedOffset = Get32u(\$newData, $offsets); + my $padToFixedOffset = $fixedOffset - ($newOffset + $dpos); + if ($padToFixedOffset < 0) { + $et->Error('Metadata too large to fit before fixed-offset image data'); + } else { + # add necessary padding before raw data + push @imageData, [$offset+$dbase+$dpos, 0, $padToFixedOffset]; + $newOffset += $padToFixedOffset; + $et->Warn("Adding $padToFixedOffset bytes of padding before fixed-offset image data", 1); + } + } + } + # pad if necessary (but don't pad contiguous image blocks) + my $pad = 0; + ++$pad if ($blockSize + $size) & 0x01 and ($n+1 >= $count or + not $oldEnd or $oldEnd != $$oldOffset[$n+1]); + # preserve original image padding if specified + if ($$origDirInfo{PreserveImagePadding} and $n+1 < $count and + $oldEnd and $$oldOffset[$n+1] > $oldEnd) + { + $pad = $$oldOffset[$n+1] - $oldEnd; + } + # copy data later + push @imageData, [$offset+$dbase+$dpos, $size, $pad]; + $newOffset += $blockSize; # data comes after other deferred data + # create fixup for SubIFD ImageData + if ($imageDataFlag eq 'SubIFD' and not $subIfdDataFixup) { + $subIfdDataFixup = new Image::ExifTool::Fixup; + $imageData[-1][4] = $subIfdDataFixup; + } + $size += $pad; # account for pad byte if necessary + # return ImageData list + $$origDirInfo{ImageData} = \@imageData; + } elsif ($offset >= 0 and $offset+$size <= $dataLen) { + # take data from old dir data buffer + $buff = substr($$dataPt, $offset, $size); + } elsif ($$et{TIFF_TYPE} eq 'MRW') { + # TTW segment must be an even 4 bytes long, so pad now if necessary + my $n = length $newData; + $buff = ($n & 0x03) ? "\0" x (4 - ($n & 0x03)) : ''; + $size = length($buff); + # data exists after MRW TTW segment + $ttwLen = length($newData) + $size unless defined $ttwLen; + $newOffset = $offset + $dpos + $ttwLen - $dataLen; + } elsif ($raf and $raf->Seek($offset+$dbase+$dpos,0) and + $raf->Read($buff,$size) == $size) + { + # (data was read OK) + # patch incorrect ThumbnailOffset in Sony A100 1.00 ARW images + if ($$et{TIFF_TYPE} eq 'ARW' and $$tagInfo{Name} eq 'ThumbnailOffset' and + $$et{Model} eq 'DSLR-A100' and $buff !~ /^\xff\xd8\xff/) + { + my $pos = $offset + $dbase + $dpos; + my $try; + if ($pos < 0x10000 and $raf->Seek($pos+0x10000,0) and + $raf->Read($try,$size) == $size and $try =~ /^\xff\xd8\xff/) + { + $buff = $try; + $et->Warn('Adjusted incorrect A100 ThumbnailOffset', 1); + } else { + $et->Error('Invalid ThumbnailImage'); + } + } + } elsif ($$tagInfo{Name} eq 'ThumbnailOffset' and $offset>=0 and $offset<$dataLen) { + # Grrr. The Canon 350D writes the thumbnail with an incorrect byte count + my $diff = $offset + $size - $dataLen; + $et->Warn("ThumbnailImage runs outside EXIF data by $diff bytes (truncated)",1); + # set the size to the available data + $size -= $diff; + unless (WriteValue($size, $formatStr, 1, \$newData, $byteCountPos)) { + warn 'Internal error writing thumbnail size'; + } + # get the truncated image + $buff = substr($$dataPt, $offset, $size); + } elsif ($$tagInfo{Name} eq 'PreviewImageStart' and $$et{FILE_TYPE} eq 'JPEG') { + # try to load the preview image using the specified offset + undef $buff; + my $r = $$et{RAF}; + if ($r and not $raf) { + my $tell = $r->Tell(); + # read and validate + undef $buff unless $r->Seek($offset+$base+$dataPos,0) and + $r->Read($buff,$size) == $size and + $buff =~ /^.\xd8\xff[\xc4\xdb\xe0-\xef]/s; + $r->Seek($tell, 0) or $et->Error('Seek error'), return undef; + } + # set flag if we must load PreviewImage + $buff = 'LOAD_PREVIEW' unless defined $buff; + } else { + my $dataName = $dataTag || $$tagInfo{Name}; + return undef if $et->Error("Error reading $dataName data in $name", $inMakerNotes); + $buff = ''; + } + if ($$tagInfo{Name} eq 'PreviewImageStart') { + if ($$et{FILE_TYPE} eq 'JPEG' and not $$tagInfo{MakerPreview}) { + # hold onto the PreviewImage until we can determine if it fits + $$et{PREVIEW_INFO} or $$et{PREVIEW_INFO} = { + Data => $buff, + Fixup => new Image::ExifTool::Fixup, + }; + if ($$tagInfo{IsOffset} and $$tagInfo{IsOffset} eq '2') { + $$et{PREVIEW_INFO}{NoBaseShift} = 1; + } + if ($offset >= 0 and $offset+$size <= $dataLen) { + # set flag indicating this preview wasn't in a trailer + $$et{PREVIEW_INFO}{WasContained} = 1; + } + $buff = ''; + } elsif ($$et{TIFF_TYPE} eq 'ARW' and $$et{Model} eq 'DSLR-A100') { + # the A100 double-references the same preview, so ignore the + # second one (the offset and size will be patched later) + next if $$et{A100PreviewLength}; + $$et{A100PreviewLength} = length $buff if defined $buff; + } + } + # update offset accordingly and add to end of new data + Set32u($newOffset, \$newData, $offsetPos); + # add a pointer to fix up this offset value (marked with DataTag name) + $fixup->AddFixup($offsetPos, $dataTag); + # also add to subIfdDataFixup if necessary + $subIfdDataFixup->AddFixup($offsetPos, $dataTag) if $subIfdDataFixup; + # must also (sometimes) update StripOffsets in Panasonic RW2 images + # and TileOffsets in Sony ARW images + my $otherPos = $$offsetPair[0][5]; + if ($otherPos) { + if ($$tagInfo{PanasonicHack}) { + Set32u($newOffset, \$newData, $otherPos); + $fixup->AddFixup($otherPos, $dataTag); + } elsif (ref $otherPos eq 'ARRAY') { + # the image data was copied as one large strip, and is double-referenced + # as tile data, so all we need to do now is properly update the tile offsets + my $oldRawDataOffset = $$offsetPair[0][3][0]; + my $count = $$otherPos[2]; + my $i; + # point to offsets in value data if more than one pointer + $$otherPos[1] = Get32u(\$newData, $$otherPos[1]) if $count > 1; + for ($i=0; $i<$count; ++$i) { + my $oldTileOffset = $$otherPos[3][$i]; + my $ptrPos = $$otherPos[1] + 4 * $i; + Set32u($newOffset + $oldTileOffset - $oldRawDataOffset, \$newData, $ptrPos); + $fixup->AddFixup($ptrPos, $dataTag); + $subIfdDataFixup->AddFixup($ptrPos, $dataTag) if $subIfdDataFixup; + } + } + } + if ($ifd >= 0) { + # buff length must be even (Note: may have changed since $size was set) + $buff .= "\0" if length($buff) & 0x01; + $newData .= $buff; # add this strip to the data + } else { + $blockSize += $size; # keep track of total size + } + } + SetByteOrder($oldOrder); + } + } + # verify that nothing else got written after determining TTW length + if (defined $ttwLen and $ttwLen != length($newData)) { + $et->Error('Internal error writing MRW TTW'); + } + } +# +# set offsets and generate fixups for tag values which were too large for memory +# + $blockSize = 0; + foreach $blockInfo (@imageData) { + my ($pos, $size, $pad, $entry, $subFix) = @$blockInfo; + if (defined $entry) { + my $format = Get16u(\$newData, $entry + 2); + if ($format < 1 or $format > 13) { + $et->Error('Internal error copying huge value'); + last; + } else { + # set count and offset in directory entry + Set32u($size / $formatSize[$format], \$newData, $entry + 4); + Set32u(length($newData)+$blockSize, \$newData, $entry + 8); + $fixup->AddFixup($entry + 8); + # create special fixup for SubIFD data + if ($imageDataFlag eq 'SubIFD') { + my $subIfdDataFixup = new Image::ExifTool::Fixup; + $subIfdDataFixup->AddFixup($entry + 8); + # save fixup in imageData list + $$blockInfo[4] = $subIfdDataFixup; + } + # must reset entry pointer so we don't use it again in a parent IFD! + $$blockInfo[3] = undef; + } + } + # apply additional shift required for contained SubIFD image data offsets + if ($subFix and defined $$subFix{BlockLen} and $numBlocks > 0) { + # our offset expects the data at the end of the SubIFD block (BlockLen + Start), + # but it will actually be at length($newData) + $blockSize. So adjust + # accordingly (and subtract an extra Start because this shift is applied later) + $$subFix{Shift} += length($newData) - $$subFix{BlockLen} - 2 * $$subFix{Start} + $blockSize; + $subFix->ApplyFixup(\$newData); + } + $blockSize += $size + $pad; + --$numBlocks; + } +# +# apply final shift to new data position if this is the top level IFD +# + unless ($$dirInfo{Fixup}) { + my $hdrPtr = $$dirInfo{HeaderPtr}; + my $newDataPos = $hdrPtr ? length $$hdrPtr : $$dirInfo{NewDataPos} || 0; + # adjust CanonVRD offset to point to end of regular TIFF if necessary + # (NOTE: This will be incorrect if multiple trailers exist, + # but it is unlikely that it could ever be correct in this case anyway. + # Also, this doesn't work for JPEG images (but CanonDPP doesn't set + # this when editing JPEG images anyway)) + $fixup->SetMarkerPointers(\$newData, 'CanonVRD', length($newData) + $blockSize); + if ($newDataPos) { + $$fixup{Shift} += $newDataPos; + $fixup->ApplyFixup(\$newData); + } + # save fixup for adjusting Leica trailer offset if necessary + $$et{LeicaTrailer}{Fixup}->AddFixup($fixup) if $$et{LeicaTrailer}; + # save fixup for PreviewImage in JPEG file if necessary + my $previewInfo = $$et{PREVIEW_INFO}; + if ($previewInfo) { + my $pt = \$$previewInfo{Data}; # image data or 'LOAD_PREVIEW' flag + # now that we know the size of the EXIF data, first test to see if our new image fits + # inside the EXIF segment (remember about the TIFF and EXIF headers: 8+6 bytes) + if (($$pt ne 'LOAD_PREVIEW' and length($$pt) + length($newData) + 14 <= 0xfffd and + not $$previewInfo{IsTrailer}) or + $$previewInfo{IsShort}) # must fit in this segment if using short pointers + { + # It fits! (or must exist in EXIF segment), so fixup the + # PreviewImage pointers and stuff the preview image in here + my $newPos = length($newData) + $newDataPos; + $newPos += ($$previewInfo{BaseShift} || 0); + if ($$previewInfo{Relative}) { + # calculate our base by looking at how far the pointer got shifted + $newPos -= ($fixup->GetMarkerPointers(\$newData, 'PreviewImage') || 0); + } + $fixup->SetMarkerPointers(\$newData, 'PreviewImage', $newPos); + $newData .= $$pt; + # set flag to delete old preview unless it was contained in the EXIF + $$et{DEL_PREVIEW} = 1 unless $$et{PREVIEW_INFO}{WasContained}; + delete $$et{PREVIEW_INFO}; # done with our preview data + } else { + # Doesn't fit, or we still don't know, so save fixup information + # and put the preview at the end of the file + $$previewInfo{Fixup} or $$previewInfo{Fixup} = new Image::ExifTool::Fixup; + $$previewInfo{Fixup}->AddFixup($fixup); + } + } elsif (defined $newData and $deleteAll) { + $newData = ''; # delete both IFD0 and IFD1 since only mandatory tags remain + } elsif ($$et{A100PreviewLength}) { + # save preview image start for patching A100 quirks later + $$et{A100PreviewStart} = $fixup->GetMarkerPointers(\$newData, 'PreviewImage'); + } + # save location of last IFD for use in Canon RAW header + if ($newDataPos == 16) { + my @ifdPos = $fixup->GetMarkerPointers(\$newData,'NextIFD'); + $$origDirInfo{LastIFD} = pop @ifdPos; + } + # recrypt SR2 SubIFD data if necessary + my $key = $$et{SR2SubIFDKey}; + if ($key) { + my $start = $fixup->GetMarkerPointers(\$newData, 'SR2SubIFDOffset'); + my $len = $$et{SR2SubIFDLength}; + # (must subtract 8 for size of TIFF header) + if ($start and $start - 8 + $len <= length $newData) { + require Image::ExifTool::Sony; + Image::ExifTool::Sony::Decrypt(\$newData, $start - 8, $len, $key); + } + } + } + # return empty string if no entries in directory + # (could be up to 10 bytes and still be empty) + $newData = '' if defined $newData and length($newData) < 12; + + # set changed if ForceWrite tag was set to "EXIF" + ++$$et{CHANGED} if defined $newData and length $newData and $$et{FORCE_WRITE}{EXIF}; + + return $newData; # return our directory data +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WriteExif.pl - Write EXIF meta information + +=head1 SYNOPSIS + +This file is autoloaded by Image::ExifTool::Exif. + +=head1 DESCRIPTION + +This file contains routines to write EXIF metadata. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::Exif(3pm)|Image::ExifTool::Exif>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/WriteIPTC.pl b/ExifTool/lib/Image/ExifTool/WriteIPTC.pl new file mode 100644 index 0000000..284cf1b --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WriteIPTC.pl @@ -0,0 +1,724 @@ +#------------------------------------------------------------------------------ +# File: WriteIPTC.pl +# +# Description: Write IPTC meta information +# +# Revisions: 12/15/2004 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::IPTC; + +use strict; + +# mandatory IPTC tags for each record +my %mandatory = ( + 1 => { + 0 => 4, # EnvelopeRecordVersion + }, + 2 => { + 0 => 4, # ApplicationRecordVersion + }, + 3 => { + 0 => 4, # NewsPhotoVersion + }, +); + +# manufacturer strings for IPTCPictureNumber +my %manufacturer = ( + 1 => 'Associated Press, USA', + 2 => 'Eastman Kodak Co, USA', + 3 => 'Hasselblad Electronic Imaging, Sweden', + 4 => 'Tecnavia SA, Switzerland', + 5 => 'Nikon Corporation, Japan', + 6 => 'Coatsworth Communications Inc, Canada', + 7 => 'Agence France Presse, France', + 8 => 'T/One Inc, USA', + 9 => 'Associated Newspapers, UK', + 10 => 'Reuters London', + 11 => 'Sandia Imaging Systems Inc, USA', + 12 => 'Visualize, Spain', +); + +my %iptcCharsetInv = ( 'UTF8' => "\x1b%G", 'UTF-8' => "\x1b%G" ); + +# ISO 2022 Character Coding Notes +# ------------------------------- +# Character set designation: (0x1b I F, or 0x1b I I F) +# Initial character 0x1b (ESC) +# Intermediate character I: +# 0x28 ('(') - G0, 94 chars +# 0x29 (')') - G1, 94 chars +# 0x2a ('*') - G2, 94 chars +# 0x2b ('+') - G3, 94 chars +# 0x2c (',') - G1, 96 chars +# 0x2d ('-') - G2, 96 chars +# 0x2e ('.') - G3, 96 chars +# 0x24 I ('$I') - multiple byte graphic sets (I from above) +# I 0x20 ('I ') - dynamically redefinable character sets +# Final character: +# 0x30 - 0x3f = private character set +# 0x40 - 0x7f = standardized character set +# Character set invocation: +# G0 : SI = 0x15 +# G1 : SO = 0x14, LS1R = 0x1b 0x7e ('~') +# G2 : LS2 = 0x1b 0x6e ('n'), LS2R = 0x1b 0x7d ('}') +# G3 : LS3 = 0x1b 0x6f ('o'), LS3R = 0x1b 0x7c ('|') +# (the locking shift "R" codes shift into 0x80-0xff space) +# Single character invocation: +# G2 : SS2 = 0x1b 0x8e (or 0x4e in 7-bit) +# G3 : SS3 = 0x1b 0x8f (or 0x4f in 7-bit) +# Control chars (designated and invoked) +# C0 : 0x1b 0x21 F (0x21 = '!') +# C1 : 0x1b 0x22 F (0x22 = '"') +# Complete codes (control+graphics, designated and invoked) +# 0x1b 0x25 F (0x25 = '%') +# 0x1b 0x25 I F +# 0x1b 0x25 0x47 ("\x1b%G") - UTF-8 +# 0x1b 0x25 0x40 ("\x1b%@") - return to ISO 2022 +# ------------------------------- + +#------------------------------------------------------------------------------ +# Inverse print conversion for CodedCharacterSet +# Inputs: 0) value +sub PrintInvCodedCharset($) +{ + my $val = shift; + my $code = $iptcCharsetInv{uc($val)}; + unless ($code) { + if (($code = $val) =~ s/ESC */\x1b/ig) { # translate ESC chars + $code =~ s/, \x1b/\x1b/g; # remove comma separators + $code =~ tr/ //d; # remove spaces + } else { + warn "Bad syntax (use 'UTF8' or 'ESC X Y[, ...]')\n"; + } + } + return $code; +} + +#------------------------------------------------------------------------------ +# validate raw values for writing +# Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref +# Returns: error string or undef (and possibly changes value) on success +sub CheckIPTC($$$) +{ + my ($et, $tagInfo, $valPtr) = @_; + my $format = $$tagInfo{Format} || $$tagInfo{Table}{FORMAT} || ''; + if ($format =~ /^int(\d+)/) { + my $bytes = int(($1 || 0) / 8); + if ($bytes != 1 and $bytes != 2 and $bytes != 4) { + return "Can't write $bytes-byte integer"; + } + my $val = $$valPtr; + unless (Image::ExifTool::IsInt($val)) { + return 'Not an integer' unless Image::ExifTool::IsHex($val); + $val = $$valPtr = hex($val); + } + my $n; + for ($n=0; $n<$bytes; ++$n) { $val >>= 8; } + return "Value too large for $bytes-byte format" if $val; + } elsif ($format =~ /^(string|digits|undef)\[?(\d+),?(\d*)\]?$/) { + my ($fmt, $minlen, $maxlen) = ($1, $2, $3); + my $len = length $$valPtr; + if ($fmt eq 'digits') { + return 'Non-numeric characters in value' unless $$valPtr =~ /^\d*$/; + if ($len < $minlen and $len) { + # left pad with zeros if necessary + $$valPtr = ('0' x ($minlen - $len)) . $$valPtr; + $len = $minlen; + } + } + if (defined $minlen and $fmt ne 'string') { # (must truncate strings later, after recoding) + $maxlen or $maxlen = $minlen; + if ($len < $minlen) { + unless ($$et{OPTIONS}{IgnoreMinorErrors}) { + return "[Minor] String too short (minlen is $minlen)"; + } + $$et{CHECK_WARN} = "String too short for IPTC:$$tagInfo{Name} (written anyway)"; + } elsif ($len > $maxlen and not $$et{OPTIONS}{IgnoreMinorErrors}) { + $$et{CHECK_WARN} = "[Minor] IPTC:$$tagInfo{Name} exceeds length limit (truncated)"; + $$valPtr = substr($$valPtr, 0, $maxlen); + } + } + } else { + return "Bad IPTC Format ($format)"; + } + return undef; +} + +#------------------------------------------------------------------------------ +# format IPTC data for writing +# Inputs: 0) ExifTool object ref, 1) tagInfo pointer, +# 2) value reference (changed if necessary), +# 3) reference to character set for translation (changed if necessary) +# 4) record number, 5) flag set to read value (instead of write) +sub FormatIPTC($$$$$;$) +{ + my ($et, $tagInfo, $valPtr, $xlatPtr, $rec, $read) = @_; + my $format = $$tagInfo{Format} || $$tagInfo{Table}{FORMAT}; + return unless $format; + if ($format =~ /^int(\d+)/) { + if ($read) { + my $len = length($$valPtr); + if ($len <= 8) { # limit integer conversion to 8 bytes long + my $val = 0; + my $i; + for ($i=0; $i<$len; ++$i) { + $val = $val * 256 + ord(substr($$valPtr, $i, 1)); + } + $$valPtr = $val; + } + } else { + my $len = int(($1 || 0) / 8); + if ($len == 1) { # 1 byte + $$valPtr = chr($$valPtr & 0xff); + } elsif ($len == 2) { # 2-byte integer + $$valPtr = pack('n', $$valPtr); + } else { # 4-byte integer + $$valPtr = pack('N', $$valPtr); + } + } + } elsif ($format =~ /^string/) { + if ($rec == 1) { + if ($$tagInfo{Name} eq 'CodedCharacterSet') { + $$xlatPtr = HandleCodedCharset($et, $$valPtr); + } + } elsif ($$xlatPtr and $rec < 7 and $$valPtr =~ /[\x80-\xff]/) { + TranslateCodedString($et, $valPtr, $xlatPtr, $read); + } + # must check length now (after any string recoding) + if (not $read and $format =~ /^string\[(\d+),?(\d*)\]$/) { + my ($minlen, $maxlen) = ($1, $2); + my $len = length $$valPtr; + $maxlen or $maxlen = $minlen; + if ($len < $minlen) { + if ($et->Warn("String too short for IPTC:$$tagInfo{Name} (padded)", 2)) { + $$valPtr .= ' ' x ($minlen - $len); + } + } elsif ($len > $maxlen) { + if ($et->Warn("IPTC:$$tagInfo{Name} exceeds length limit (truncated)", 2)) { + $$valPtr = substr($$valPtr, 0, $maxlen); + # make sure UTF-8 is still valid + if (($$xlatPtr || $et->Options('Charset')) eq 'UTF8') { + require Image::ExifTool::XMP; + Image::ExifTool::XMP::FixUTF8($valPtr,'.'); + } + } + } + } + } +} + +#------------------------------------------------------------------------------ +# generate IPTC-format date +# Inputs: 0) EXIF-format date string (YYYY:mm:dd) or date/time string +# Returns: IPTC-format date string (YYYYmmdd), or undef and issue warning on error +sub IptcDate($) +{ + my $val = shift; + unless ($val =~ s{^.*(\d{4})[-:/.]?(\d{2})[-:/.]?(\d{2}).*}{$1$2$3}s) { + warn "Invalid date format (use YYYY:mm:dd)\n"; + undef $val; + } + return $val; +} + +#------------------------------------------------------------------------------ +# generate IPTC-format time +# Inputs: 0) EXIF-format time string (HH:MM:SS[+/-HH:MM]) or date/time string +# Returns: IPTC-format time string (HHMMSS+HHMM), or undef and issue warning on error +sub IptcTime($) +{ + my $val = shift; + if ($val =~ /(.*?)\b(\d{1,2})(:?)(\d{2})(:?)(\d{2})(\S*)\s*$/s and ($3 or not $5)) { + $val = sprintf("%.2d%.2d%.2d",$2,$4,$6); + my ($date, $tz) = ($1, $7); + if ($tz =~ /([+-]\d{1,2}):?(\d{2})/) { + $tz = sprintf("%+.2d%.2d",$1,$2); + } elsif ($tz =~ /Z/i) { + $tz = '+0000'; # UTC + } else { + # use local system timezone by default + my (@tm, $time); + if ($date and $date =~ /^(\d{4}):(\d{2}):(\d{2})\s*$/ and eval { require Time::Local }) { + # we were given a date too, so determine the local timezone + # offset at the specified date/time + my @d = ($3,$2-1,$1); + $val =~ /(\d{2})(\d{2})(\d{2})/; + @tm = ($3,$2,$1,@d); + $time = Image::ExifTool::TimeLocal(@tm); + } else { + # it is difficult to get the proper local timezone offset for this + # time because the date tag is written separately. (The offset may be + # different on a different date due to daylight savings time.) In this + # case the best we can do easily is to use the current timezone offset. + $time = time; + @tm = localtime($time); + } + ($tz = Image::ExifTool::TimeZoneString(\@tm, $time)) =~ tr/://d; + } + $val .= $tz; + } else { + warn "Invalid time format (use HH:MM:SS[+/-HH:MM])\n"; + undef $val; # time format error + } + return $val; +} + +#------------------------------------------------------------------------------ +# Inverse print conversion for IPTC date or time value +# Inputs: 0) ExifTool ref, 1) IPTC date or 'now' +# Returns: IPTC date +sub InverseDateOrTime($$) +{ + my ($et, $val) = @_; + return $et->TimeNow() if lc($val) eq 'now'; + return $val; +} + +#------------------------------------------------------------------------------ +# Convert picture number +# Inputs: 0) value +# Returns: Converted value +sub ConvertPictureNumber($) +{ + my $val = shift; + if ($val eq "\0" x 16) { + $val = 'Unknown'; + } elsif (length $val >= 16) { + my @vals = unpack('nNA8n', $val); + $val = $vals[0]; + my $manu = $manufacturer{$val}; + $val .= " ($manu)" if $manu; + $val .= ', equip ' . $vals[1]; + $vals[2] =~ s/(\d{4})(\d{2})(\d{2})/$1:$2:$3/; + $val .= ", $vals[2], no. $vals[3]"; + } else { + $val = '<format error>' + } + return $val; +} + +#------------------------------------------------------------------------------ +# Inverse picture number conversion +# Inputs: 0) value +# Returns: Converted value (or undef on error) +sub InvConvertPictureNumber($) +{ + my $val = shift; + $val =~ s/\(.*\)//g; # remove manufacturer description + $val =~ tr/://d; # remove date separators + $val =~ tr/0-9/ /c; # turn remaining non-numbers to spaces + my @vals = split ' ', $val; + if (@vals >= 4) { + $val = pack('nNA8n', @vals); + } elsif ($val =~ /unknown/i) { + $val = "\0" x 16; + } else { + undef $val; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Write IPTC data record +# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref +# Returns: IPTC data block (may be empty if no IPTC data) +# Notes: Increments ExifTool CHANGED flag for each tag changed +sub DoWriteIPTC($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + + # avoid editing IPTC directory unless necessary: + # - improves speed + # - avoids changing current MD5 digest unnecessarily + # - avoids adding mandatory tags unless some other IPTC is changed + return undef unless exists $$et{EDIT_DIRS}{$$dirInfo{DirName}} or + # standard IPTC tags in other locations should be edited too (eg. AFCP_IPTC) + ($tagTablePtr eq \%Image::ExifTool::IPTC::Main and exists $$et{EDIT_DIRS}{IPTC}); + my $dataPt = $$dirInfo{DataPt}; + unless ($dataPt) { + my $emptyData = ''; + $dataPt = \$emptyData; + } + my $start = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen}; + my ($tagInfo, %iptcInfo, $tag); + + # start by assuming default IPTC encoding + my $xlat = $et->Options('CharsetIPTC'); + undef $xlat if $xlat eq $et->Options('Charset'); + + # make sure our dataLen is defined (note: allow zero length directory) + unless (defined $dirLen) { + my $dataLen = $$dirInfo{DataLen}; + $dataLen = length($$dataPt) unless defined $dataLen; + $dirLen = $dataLen - $start; + } + # quick check for improperly byte-swapped IPTC + if ($dirLen >= 4 and substr($$dataPt, $start, 1) ne "\x1c" and + substr($$dataPt, $start + 3, 1) eq "\x1c") + { + $et->Warn('IPTC data was improperly byte-swapped'); + my $newData = pack('N*', unpack('V*', substr($$dataPt, $start, $dirLen) . "\0\0\0")); + $dataPt = \$newData; + $start = 0; + # NOTE: MUST NOT access $dirInfo DataPt, DirStart or DataLen after this! + } + # generate lookup so we can find the record numbers + my %recordNum; + foreach $tag (Image::ExifTool::TagTableKeys($tagTablePtr)) { + $tagInfo = $$tagTablePtr{$tag}; + $$tagInfo{SubDirectory} or next; + my $table = $$tagInfo{SubDirectory}{TagTable} or next; + my $subTablePtr = Image::ExifTool::GetTagTable($table); + $recordNum{$subTablePtr} = $tag; + } + + # loop through new values and accumulate all IPTC information + # into lists based on their IPTC record type + foreach $tagInfo ($et->GetNewTagInfoList()) { + my $table = $$tagInfo{Table}; + my $record = $recordNum{$table}; + # ignore tags we aren't writing to this directory + next unless defined $record; + $iptcInfo{$record} = [] unless defined $iptcInfo{$record}; + push @{$iptcInfo{$record}}, $tagInfo; + } + + # get sorted list of records used. Might as well be organized and + # write our records in order of record number first, then tag number + my @recordList = sort { $a <=> $b } keys %iptcInfo; + my ($record, %set); + foreach $record (@recordList) { + # sort tagInfo lists by tagID + @{$iptcInfo{$record}} = sort { $$a{TagID} <=> $$b{TagID} } @{$iptcInfo{$record}}; + # build hash of all tagIDs to set + foreach $tagInfo (@{$iptcInfo{$record}}) { + $set{$record}->{$$tagInfo{TagID}} = $tagInfo; + } + } + # run through the old IPTC data, inserting our records in + # sequence and deleting existing records where necessary + # (the IPTC specification states that records must occur in + # numerical order, but tags within records need not be ordered) + my $pos = $start; + my $tail = $pos; # old data written up to this point + my $dirEnd = $start + $dirLen; + my $newData = ''; + my $lastRec = -1; + my $lastRecPos = 0; + my $allMandatory = 0; + my %foundRec; # found flags: 0x01-existed before, 0x02-deleted, 0x04-created + my $addNow; + + for (;;$tail=$pos) { + # get next IPTC record from input directory + my ($id, $rec, $tag, $len, $valuePtr); + if ($pos + 5 <= $dirEnd) { + my $buff = substr($$dataPt, $pos, 5); + ($id, $rec, $tag, $len) = unpack("CCCn", $buff); + if ($id == 0x1c) { + if ($rec < $lastRec) { + if ($rec == 0) { + return undef if $et->Warn("IPTC record 0 encountered, subsequent records ignored", 2); + undef $rec; + $pos = $dirEnd; + $len = 0; + } else { + return undef if $et->Warn("IPTC doesn't conform to spec: Records out of sequence", 2); + } + } + # handle extended IPTC entry if necessary + $pos += 5; # step to after field header + if ($len & 0x8000) { + my $n = $len & 0x7fff; # get num bytes in length field + if ($pos + $n <= $dirEnd and $n <= 8) { + # determine length (a big-endian, variable sized int) + for ($len = 0; $n; ++$pos, --$n) { + $len = $len * 256 + ord(substr($$dataPt, $pos, 1)); + } + } else { + $len = $dirEnd; # invalid length + } + } + $valuePtr = $pos; + $pos += $len; # step $pos to next entry + # make sure we don't go past the end of data + # (this can only happen if original data is bad) + $pos = $dirEnd if $pos > $dirEnd; + } else { + undef $rec; + } + } + # write out all our records that come before this one + my $writeRec = (not defined $rec or $rec != $lastRec); + if ($writeRec or $addNow) { + for (;;) { + my $newRec = $recordList[0]; + if ($addNow) { + $tagInfo = $addNow; + } elsif (not defined $newRec or $newRec != $lastRec) { + # handle mandatory tags in last record unless it was empty + if (length $newData > $lastRecPos) { + if ($allMandatory > 1) { + # entire lastRec contained mandatory tags, and at least one tag + # was deleted, so delete entire record unless we specifically + # added a mandatory tag + my $num = 0; + foreach (keys %{$foundRec{$lastRec}}) { + my $code = $foundRec{$lastRec}->{$_}; + $num = 0, last if $code & 0x04; + ++$num if ($code & 0x03) == 0x01; + } + if ($num) { + $newData = substr($newData, 0, $lastRecPos); + $verbose > 1 and print $out " - $num mandatory tags\n"; + } + } elsif ($mandatory{$lastRec} and + $tagTablePtr eq \%Image::ExifTool::IPTC::Main) + { + # add required mandatory tags + my $mandatory = $mandatory{$lastRec}; + my ($mandTag, $subTablePtr); + foreach $mandTag (sort { $a <=> $b } keys %$mandatory) { + next if $foundRec{$lastRec}->{$mandTag}; + unless ($subTablePtr) { + $tagInfo = $$tagTablePtr{$lastRec}; + $tagInfo and $$tagInfo{SubDirectory} or warn("WriteIPTC: Internal error 1\n"), next; + $$tagInfo{SubDirectory}{TagTable} or next; + $subTablePtr = Image::ExifTool::GetTagTable($$tagInfo{SubDirectory}{TagTable}); + } + $tagInfo = $$subTablePtr{$mandTag} or warn("WriteIPTC: Internal error 2\n"), next; + my $value = $$mandatory{$mandTag}; + $et->VerboseValue("+ IPTC:$$tagInfo{Name}", $value, ' (mandatory)'); + # apply necessary format conversions + FormatIPTC($et, $tagInfo, \$value, \$xlat, $lastRec); + $len = length $value; + # generate our new entry + my $entry = pack("CCCn", 0x1c, $lastRec, $mandTag, length($value)); + $newData .= $entry . $value; # add entry to new IPTC data + # (don't mark as changed if just mandatory tags changed) + # ++$$et{CHANGED}; + } + } + } + last unless defined $newRec; + $lastRec = $newRec; + $lastRecPos = length $newData; + $allMandatory = 1; + } + unless ($addNow) { + # compare current entry with entry next in line to write out + # (write out our tags in numerical order even though + # this isn't required by the IPTC spec) + last if defined $rec and $rec <= $newRec; + $tagInfo = ${$iptcInfo{$newRec}}[0]; + } + my $newTag = $$tagInfo{TagID}; + my $nvHash = $et->GetNewValueHash($tagInfo); + # only add new values if... + my ($doSet, @values); + my $found = $foundRec{$newRec}->{$newTag} || 0; + if ($found & 0x02) { + # ...tag existed before and was deleted (unless we already added it) + $doSet = 1 unless $found & 0x04; + } elsif ($$tagInfo{List}) { + # ...tag is List and it existed before or we are creating it + $doSet = 1 if $found ? not $$nvHash{CreateOnly} : $$nvHash{IsCreating}; + } else { + # ...tag didn't exist before and we are creating it + $doSet = 1 if not $found and $$nvHash{IsCreating}; + } + if ($doSet) { + @values = $et->GetNewValue($nvHash); + @values and $foundRec{$newRec}->{$newTag} = $found | 0x04; + # write tags for each value in list + my $value; + foreach $value (@values) { + $et->VerboseValue("+ $$dirInfo{DirName}:$$tagInfo{Name}", $value); + # reset allMandatory flag if a non-mandatory tag is written + if ($allMandatory) { + my $mandatory = $mandatory{$newRec}; + $allMandatory = 0 unless $mandatory and $$mandatory{$newTag}; + } + # apply necessary format conversions + FormatIPTC($et, $tagInfo, \$value, \$xlat, $newRec); + # (note: IPTC string values are NOT null terminated) + $len = length $value; + # generate our new entry + my $entry = pack("CCC", 0x1c, $newRec, $newTag); + if ($len <= 0x7fff) { + $entry .= pack("n", $len); + } else { + # extended dataset tag + $entry .= pack("nN", 0x8004, $len); + } + $newData .= $entry . $value; # add entry to new IPTC data + ++$$et{CHANGED}; + } + } + # continue on with regular programming if done adding tag now + if ($addNow) { + undef $addNow; + next if $writeRec; + last; + } + # remove this tagID from the sorted write list + shift @{$iptcInfo{$newRec}}; + shift @recordList unless @{$iptcInfo{$newRec}}; + } + if ($writeRec) { + # all done if no more records to write + last unless defined $rec; + # update last record variables + $lastRec = $rec; + $lastRecPos = length $newData; + $allMandatory = 1; + } + } + # set flag indicating we found this tag + $foundRec{$rec}->{$tag} = ($foundRec{$rec}->{$tag} || 0) || 0x01; + # write out this record unless we are setting it with a new value + $tagInfo = $set{$rec}->{$tag}; + if ($tagInfo) { + my $nvHash = $et->GetNewValueHash($tagInfo); + $len = $pos - $valuePtr; + my $val = substr($$dataPt, $valuePtr, $len); + # remove null terminator if it exists (written by braindead software like Picasa 2.0) + $val =~ s/\0+$// if $$tagInfo{Format} and $$tagInfo{Format} =~ /^string/; + my $oldXlat = $xlat; + FormatIPTC($et, $tagInfo, \$val, \$xlat, $rec, 1); + if ($et->IsOverwriting($nvHash, $val)) { + $xlat = $oldXlat; # don't change translation (not writing this value) + $et->VerboseValue("- $$dirInfo{DirName}:$$tagInfo{Name}", $val); + ++$$et{CHANGED}; + # set deleted flag to indicate we found and deleted this tag + $foundRec{$rec}->{$tag} |= 0x02; + # increment allMandatory flag to indicate a tag was removed + $allMandatory and ++$allMandatory; + # write this tag now if overwriting an existing value + if ($$nvHash{Value} and @{$$nvHash{Value}} and @recordList and + $recordList[0] == $rec and not $foundRec{$rec}->{$tag} & 0x04) + { + $addNow = $tagInfo; + } + next; + } + } elsif ($rec == 1 and $tag == 90) { + # handle CodedCharacterSet tag + my $val = substr($$dataPt, $valuePtr, $pos - $valuePtr); + $xlat = HandleCodedCharset($et, $val); + } + # reset allMandatory flag if a non-mandatory tag is written + if ($allMandatory) { + my $mandatory = $mandatory{$rec}; + unless ($mandatory and $$mandatory{$tag}) { + $allMandatory = 0; + } + } + # write out the record + $newData .= substr($$dataPt, $tail, $pos-$tail); + } + # make sure the rest of the data is zero + if ($tail < $dirEnd) { + my $pad = substr($$dataPt, $tail, $dirEnd-$tail); + if ($pad =~ /[^\0]/) { + return undef if $et->Warn('Unrecognized data in IPTC padding', 2); + } + } + return $newData; +} + +#------------------------------------------------------------------------------ +# Write IPTC data record and calculate NewIPTCDigest +# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref +# Returns: IPTC data block (may be empty if no IPTC data) +# Notes: Increments ExifTool CHANGED flag for each tag changed +sub WriteIPTC($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + + my $newData = DoWriteIPTC($et, $dirInfo, $tagTablePtr); + + # calculate standard IPTC digests only if we are writing or deleting + # Photoshop:IPTCDigest with a value of 'new' or 'old' + while ($Image::ExifTool::Photoshop::iptcDigestInfo) { + my $nvHash = $$et{NEW_VALUE}{$Image::ExifTool::Photoshop::iptcDigestInfo}; + last unless defined $nvHash; + last unless IsStandardIPTC($et->MetadataPath()); + my @values = $et->GetNewValue($nvHash); + push @values, @{$$nvHash{DelValue}} if $$nvHash{DelValue}; + my $new = grep /^new$/, @values; + my $old = grep /^old$/, @values; + last unless $new or $old; + unless (eval { require Digest::MD5 }) { + $et->Warn('Digest::MD5 must be installed to calculate IPTC digest'); + last; + } + my $dataPt; + if ($new) { + if (defined $newData) { + $dataPt = \$newData; + } else { + $dataPt = $$dirInfo{DataPt}; + if ($$dirInfo{DirStart} or length($$dataPt) != $$dirInfo{DirLen}) { + my $buff = substr($$dataPt, $$dirInfo{DirStart}, $$dirInfo{DirLen}); + $dataPt = \$buff; + } + } + # set NewIPTCDigest data member unless IPTC is being deleted + $$et{NewIPTCDigest} = Digest::MD5::md5($$dataPt) if length $$dataPt; + } + if ($old) { + if ($new and not defined $newData) { + $$et{OldIPTCDigest} = $$et{NewIPTCDigest}; + } elsif ($$dirInfo{DataPt}) { #(may be undef if creating new IPTC) + $dataPt = $$dirInfo{DataPt}; + if ($$dirInfo{DirStart} or length($$dataPt) != $$dirInfo{DirLen}) { + my $buff = substr($$dataPt, $$dirInfo{DirStart}, $$dirInfo{DirLen}); + $dataPt = \$buff; + } + $$et{OldIPTCDigest} = Digest::MD5::md5($$dataPt) if length $$dataPt; + } + } + last; + } + # set changed if ForceWrite tag was set to "IPTC" + ++$$et{CHANGED} if defined $newData and length $newData and $$et{FORCE_WRITE}{IPTC}; + return $newData; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WriteIPTC.pl - Write IPTC meta information + +=head1 SYNOPSIS + +This file is autoloaded by Image::ExifTool::IPTC. + +=head1 DESCRIPTION + +This file contains routines to write IPTC metadata, plus a few other +seldom-used routines. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::IPTC(3pm)|Image::ExifTool::IPTC>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/WritePDF.pl b/ExifTool/lib/Image/ExifTool/WritePDF.pl new file mode 100644 index 0000000..02b89b0 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WritePDF.pl @@ -0,0 +1,774 @@ +#------------------------------------------------------------------------------ +# File: WritePDF.pl +# +# Description: Write PDF meta information +# +# Revisions: 12/08/2007 - P. Harvey Created +# +# References: 1) http://partners.adobe.com/public/developer/pdf/index_reference.html +# +# Notes: The special "PDF-update" group can be deleted to revert exiftool updates +#------------------------------------------------------------------------------ +package Image::ExifTool::PDF; + +use strict; +use vars qw($lastFetched); + +sub WriteObject($$); +sub EncodeString($); +sub CryptObject($); + +# comments to mark beginning and end of ExifTool incremental update +my $beginComment = '%BeginExifToolUpdate'; +my $endComment = '%EndExifToolUpdate '; + +my $keyExt; # crypt key extension +my $pdfVer; # version of PDF file we are writing (highest Version in Root dictionaries) + +# internal tags used in dictionary objects +my %myDictTags = ( + _tags => 1, _stream => 1, _decrypted => 1, _needCrypt => 1, + _filtered => 1, _entry_size => 1, _table => 1, +); + +# map for directories that we can add +my %pdfMap = ( + XMP => 'PDF', +); + +#------------------------------------------------------------------------------ +# Validate raw PDF values for writing (string date integer real boolean name) +# Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref +# Returns: error string or undef (and possibly changes value) on success +sub CheckPDF($$$) +{ + my ($et, $tagInfo, $valPtr) = @_; + my $format = $$tagInfo{Writable} || $tagInfo->{Table}->{WRITABLE}; + if (not $format) { + return 'No writable format'; + } elsif ($format eq 'string') { + # (encode later because list-type string tags need to be encoded as a unit) + } elsif ($format eq 'date') { + # be flexible about this for now + return 'Bad date format' unless $$valPtr =~ /^\d{4}/; + } elsif ($format eq 'integer') { + return 'Not an integer' unless Image::ExifTool::IsInt($$valPtr); + } elsif ($format eq 'real') { + return 'Not a real number' unless $$valPtr =~ /^[+-]?(?=\d|\.\d)\d*(\.\d*)?$/; + } elsif ($format eq 'boolean') { + $$valPtr = ($$valPtr and $$valPtr !~ /^f/i) ? 'true' : 'false'; + } elsif ($format eq 'name') { + return 'Invalid PDF name' if $$valPtr =~ /\0/; + } else { + return "Invalid PDF format '${format}'"; + } + return undef; # value is OK +} + +#------------------------------------------------------------------------------ +# Format value for writing to PDF file +# Inputs: 0) ExifTool ref, 1) value, 2) format string (string,date,integer,real,boolean,name) +# Returns: formatted value or undef on error +# Notes: Called at write time, so $pdfVer may be checked +sub WritePDFValue($$$) +{ + my ($et, $val, $format) = @_; + if (not $format) { + return undef; + } elsif ($format eq 'string') { + # encode as UCS2 if it contains any special characters + $val = "\xfe\xff" . $et->Encode($val,'UCS2','MM') if $val =~ /[\x80-\xff]/; + EncodeString(\$val); + } elsif ($format eq 'date') { + # convert date to "D:YYYYmmddHHMMSS+-HH'MM'" format + $val =~ s/([-+]\d{2}):(\d{2})/${1}'${2}'/; # change timezone delimiters if necessary + $val =~ tr/ ://d; # remove spaces and colons + $val = "D:$val"; # add leading "D:" + EncodeString(\$val); + } elsif ($format =~ /^(integer|real|boolean)$/) { + # no reformatting necessary + } elsif ($format eq 'name') { + return undef if $val =~ /\0/; + if ($pdfVer >= 1.2) { + $val =~ s/([\t\n\f\r ()<>[\]{}\/%#])/sprintf('#%.2x',ord $1)/sge; + } else { + return undef if $val =~ /[\t\n\f\r ()<>[\]{}\/%]/; + } + $val = "/$val"; # add leading '/' + } else { + return undef; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Encode PDF string +# Inputs: 0) reference to PDF string +# Returns: (updates string with encoded data) +sub EncodeString($) +{ + my $strPt = shift; + if (ref $$strPt eq 'ARRAY') { + my $str; + foreach $str (@{$$strPt}) { + EncodeString(\$str); + } + return; + } + Crypt($strPt, $keyExt, 1); # encrypt if necessary + # encode as hex if we have any control characters (except tab) + if ($$strPt=~/[\x00-\x08\x0a-\x1f\x7f\xff]/) { + # encode as hex + my $str=''; + my $len = length $$strPt; + my $i = 0; + for (;;) { + my $n = $len - $i or last; + $n = 40 if $n > 40; # break into reasonable-length lines + $str .= $/ if $i; + $str .= unpack('H*', substr($$strPt, $i, $n)); + $i += $n; + } + $$strPt = "<$str>"; + } else { + $$strPt =~ s/([()\\])/\\$1/g; # must escape round brackets and backslashes + $$strPt = "($$strPt)"; + } +} + +#------------------------------------------------------------------------------ +# Encrypt an object +# Inputs: 0) PDF object (encrypts in place) +# Notes: Encrypts according to "_needCrypt" dictionary entry, +# then deletes "_needCrypt" when done +sub CryptObject($) +{ + my $obj = $_[0]; + if (not ref $obj) { + # only literal strings and hex strings are encrypted + if ($obj =~ /^[(<]/) { + undef $lastFetched; # (reset this just in case) + my $val = ReadPDFValue($obj); + EncodeString(\$val); + $_[0] = $val; + } + } elsif (ref $obj eq 'HASH') { + my $tag; + my $needCrypt = $$obj{_needCrypt}; + foreach $tag (keys %$obj) { + next if $myDictTags{$tag}; + # re-encrypt necessary objects only (others are still encrypted) + # (this is really annoying, but is necessary because objects stored + # in encrypted streams are decrypted when extracting, but strings stored + # as direct objects are decrypted later since they must be decoded + # before being decrypted) + if ($needCrypt) { + next unless defined $$needCrypt{$tag} ? $$needCrypt{$tag} : $$needCrypt{'*'}; + } + CryptObject($$obj{$tag}); + } + delete $$obj{_needCrypt}; # avoid re-re-crypting + } elsif (ref $obj eq 'ARRAY') { + my $val; + foreach $val (@$obj) { + CryptObject($val); + } + } +} + +#------------------------------------------------------------------------------ +# Get free entries from xref stream dictionary that we wrote previously +# Inputs: 0) xref dictionary reference +# Returns: free entry hash (keys are object numbers, values are xref entry list refs) +sub GetFreeEntries($) +{ + my $dict = shift; + my %xrefFree; + # from the start we have only written xref stream entries in 'CNn' format, + # so we can simplify things for now and only support this type of entry + my $w = $$dict{W}; + if (ref $w eq 'ARRAY' and "@$w" eq '1 4 2') { + my $size = $$dict{_entry_size}; # this will be 7 for 'CNn' + my $index = $$dict{Index}; + my $len = length $$dict{_stream}; + # scan the table for free objects + my $num = scalar(@$index) / 2; + my $pos = 0; + my ($i, $j); + for ($i=0; $i<$num; ++$i) { + my $start = $$index[$i*2]; + my $count = $$index[$i*2+1]; + for ($j=0; $j<$count; ++$j) { + last if $pos + $size > $len; + my @t = unpack("x$pos CNn", $$dict{_stream}); + # add entry if object was free + $xrefFree{$start+$j} = [ $t[1], $t[2], 'f' ] if $t[0] == 0; + $pos += $size; # step to next entry + } + } + } + return \%xrefFree; +} + +#------------------------------------------------------------------------------ +# Write PDF object +# Inputs: 0) output file or scalar ref, 1) PDF object +# Returns: true on success +# Notes: inserts white space before object, but none afterward +sub WriteObject($$) +{ + my ($outfile, $obj) = @_; + if (ref $obj eq 'SCALAR') { + Write($outfile, ' ', $$obj) or return 0; + } elsif (ref $obj eq 'ARRAY') { + # write array + Write($outfile, @$obj > 10 ? $/ : ' ', '[') or return 0; + my $item; + foreach $item (@$obj) { + WriteObject($outfile, $item) or return 0; + } + Write($outfile, ' ]') or return 0; + } elsif (ref $obj eq 'HASH') { + # write dictionary + my $tag; + Write($outfile, $/, '<<') or return 0; + # prepare object as required if it has a stream + if ($$obj{_stream}) { + # encrypt stream if necessary (must be done before determining Length) + CryptStream($obj, $keyExt) if $$obj{_decrypted}; + # write "Length" entry in dictionary + $$obj{Length} = length $$obj{_stream}; + push @{$$obj{_tags}}, 'Length'; + # delete Filter-related entries since we don't yet write filtered streams + delete $$obj{Filter}; + delete $$obj{DecodeParms}; + delete $$obj{DL}; + } + # don't write my internal entries + my %wrote = %myDictTags; + # write tags in original order, adding new ones later alphabetically + foreach $tag (@{$$obj{_tags}}, sort keys %$obj) { + # ignore already-written or missing entries + next if $wrote{$tag} or not defined $$obj{$tag}; + Write($outfile, $/, "/$tag") or return 0; + WriteObject($outfile, $$obj{$tag}) or return 0; + $wrote{$tag} = 1; + } + Write($outfile, $/, '>>') or return 0; + if ($$obj{_stream}) { + # write object stream + # (a single 0x0d may not follow 'stream', so use 0x0d+0x0a here to be sure) + Write($outfile, $/, "stream\x0d\x0a") or return 0; + Write($outfile, $$obj{_stream}, $/, 'endstream') or return 0; + } + } else { + # write string, number, name or object reference + Write($outfile, ' ', $obj); + } + return 1; +} + +#------------------------------------------------------------------------------ +# Write PDF File +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if not valid PDF file, -1 on write error +# Notes: dictionary structure: Main --+--> Info +# +--> Root --> Metadata +sub WritePDF($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my ($buff, %capture, %newXRef, %newObj, $objRef); + my ($out, $id, $gen, $obj); + + # make sure this is a PDF file + my $pos = $raf->Tell(); + $raf->Read($buff, 1024) >= 8 or return 0; + $buff =~ /^(\s*)%PDF-(\d+\.\d+)/ or return 0; + $$et{PDFBase} = length $1; + $raf->Seek($pos, 0); + + # create a new ExifTool object and use it to read PDF and XMP information + my $newTool = new Image::ExifTool; + $newTool->Options(List => 1); + $newTool->Options(Password => $et->Options('Password')); + $newTool->Options(NoPDFList => $et->Options('NoPDFList')); + $$newTool{PDF_CAPTURE} = \%capture; + my $info = $newTool->ImageInfo($raf, 'XMP', 'PDF:*', 'Error', 'Warning'); + # not a valid PDF file unless we got a version number + $pdfVer = $$newTool{PDFVersion}; + $pdfVer or $et->Error('Missing PDF:PDFVersion'), return 0; + # check version number + if ($pdfVer > 2.0) { + $et->Error("Writing PDF $pdfVer is untested", 1) and return 0; + } + # fail if we had any serious errors while extracting information + if ($capture{Error} or $$info{Error}) { + $et->Error($capture{Error} || $$info{Error}); + return 1; + } + # make sure we have everything we need to rewrite this file + foreach $obj (qw(Main Root xref)) { + next if $capture{$obj}; + # any warning we received may give a clue about why this object is missing + $et->Error($$info{Warning}) if $$info{Warning}; + $et->Error("Can't find $obj object"); + return 1; + } + $et->InitWriteDirs(\%pdfMap, 'XMP'); + + # copy file up to start of previous exiftool update or end of file + # (comment, startxref & EOF with 11-digit offsets and 2-byte newlines is 63 bytes) + $raf->Seek(-64,2) and $raf->Read($buff,64) and $raf->Seek(0,0) or return -1; + my $rtn = 1; + my $prevUpdate; + # (now $endComment is before "startxref", but pre-7.41 we wrote it after the EOF) + if ($buff =~ /$endComment(\d+)\s+(startxref\s+\d+\s+%%EOF\s+)?$/s) { + $prevUpdate = $1; + # rewrite the file up to the original EOF + Image::ExifTool::CopyBlock($raf, $outfile, $prevUpdate + $$et{PDFBase}) or $rtn = -1; + # verify that we are now at the start of an ExifTool update + unless ($raf->Read($buff, length $beginComment) and $buff eq $beginComment) { + $et->Error('Previous ExifTool update is corrupted'); + return $rtn; + } + $raf->Seek($prevUpdate+$$et{PDFBase}, 0) or $rtn = -1; + if ($$et{DEL_GROUP}{'PDF-update'}) { + $et->VPrint(0, " Reverted previous ExifTool updates\n"); + ++$$et{CHANGED}; + return $rtn; + } + } elsif ($$et{DEL_GROUP}{'PDF-update'}) { + $et->Error('File contains no previous ExifTool update'); + return $rtn; + } else { + # rewrite the whole file + while ($raf->Read($buff, 65536)) { + Write($outfile, $buff) or $rtn = -1; + } + } + $out = $et->Options('TextOut') if $et->Options('Verbose'); +# +# create our new PDF objects to write +# + my $xref = $capture{xref}; + my $mainDict = $capture{Main}; + my $metaRef = $capture{Root}->{Metadata}; + my $nextObject; + + # start by finding reference for info object in case it was deleted + # in a previous edit so we can re-use it here if adding PDF Info + my $prevInfoRef; + if ($prevUpdate) { + unless ($capture{Prev}) { + $et->Error("Can't locate trailer dictionary prior to last edit"); + return $rtn; + } + $prevInfoRef = $capture{Prev}->{Info}; + # start from previous size so the xref table doesn't continue + # to grow if we repeatedly add and delete the Metadata object + $nextObject = $capture{Prev}->{Size}; + # don't re-use Meta reference if object was added in a previous update + undef $metaRef if $metaRef and $$metaRef=~/^(\d+)/ and $1 >= $nextObject; + } else { + $prevInfoRef = $$mainDict{Info}; + $nextObject = $$mainDict{Size}; + } + + # delete entire PDF group if specified + my $infoChanged = 0; + if ($$et{DEL_GROUP}{PDF} and $capture{Info}) { + delete $capture{Info}; + $info = { XMP => $$info{XMP} }; # remove extracted PDF tags + print $out " Deleting PDF Info dictionary\n" if $out; + ++$infoChanged; + } + + # create new Info dictionary if necessary + $capture{Info} = { _tags => [ ] } unless $capture{Info}; + my $infoDict = $capture{Info}; + + # must pre-determine Info reference to be used in encryption + my $infoRef = $prevInfoRef || \ "$nextObject 0 R"; + unless (ref $infoRef eq 'SCALAR') { + $et->Error("Info dictionary is not an indirect object"); + return $rtn; + } + $keyExt = $$infoRef; + + # must encrypt all values in dictionary if they came from an encrypted stream + CryptObject($infoDict) if $$infoDict{_needCrypt}; + + # must set line separator before calling WritePDFValue() + local $/ = $capture{newline}; + + # rewrite PDF Info tags + my $newTags = $et->GetNewTagInfoHash(\%Image::ExifTool::PDF::Info); + my $tagID; + foreach $tagID (sort keys %$newTags) { + my $tagInfo = $$newTags{$tagID}; + if ($pdfVer >= 2.0 and not $$tagInfo{PDF2}) { + next if $et->Warn("Writing PDF:$$tagInfo{Name} is deprecated for PDF 2.0 documents",2); + } + my $nvHash = $et->GetNewValueHash($tagInfo); + my (@vals, $deleted); + my $tag = $$tagInfo{Name}; + my $val = $$info{$tag}; + my $tagKey = $tag; + unless (defined $val) { + # must check for tag key with copy number + ($tagKey) = grep /^$tag/, keys %$info; + $val = $$info{$tagKey} if $tagKey; + } + if (defined $val) { + my @oldVals; + if (ref $val eq 'ARRAY') { + @oldVals = @$val; + $val = shift @oldVals; + } + for (;;) { + if ($et->IsOverwriting($nvHash, $val) > 0) { + $deleted = 1; + $et->VerboseValue("- PDF:$tag", $val); + ++$infoChanged; + } else { + push @vals, $val; + } + last unless @oldVals; + $val = shift @oldVals; + } + # don't write this out if we deleted all values + delete $$infoDict{$tagID} unless @vals; + } elsif ($$nvHash{EditOnly}) { + next; + } + # decide whether we want to write this tag + # (native PDF information is always preferred, so don't check IsCreating) + next unless $deleted or $$tagInfo{List} or not exists $$infoDict{$tagID}; + + # add new values to existing ones + my @newVals = $et->GetNewValue($nvHash); + if (@newVals) { + push @vals, @newVals; + ++$infoChanged; + if ($out) { + foreach $val (@newVals) { + $et->VerboseValue("+ PDF:$tag", $val); + } + } + } + unless (@vals) { + # remove this entry from the Info dictionary if no values remain + delete $$infoDict{$tagID}; + next; + } + # format value(s) for writing to PDF file + my $writable = $$tagInfo{Writable} || $Image::ExifTool::PDF::Info{WRITABLE}; + if (not $$tagInfo{List}) { + $val = WritePDFValue($et, shift(@vals), $writable); + } elsif ($$tagInfo{List} eq 'array') { + foreach $val (@vals) { + $val = WritePDFValue($et, $val, $writable); + defined $val or undef(@vals), last; + } + $val = @vals ? \@vals : undef; + } else { + $val = WritePDFValue($et, join($et->Options('ListSep'), @vals), $writable); + } + if (defined $val) { + $$infoDict{$tagID} = $val; + ++$infoChanged; + } else { + $et->Warn("Error converting $$tagInfo{Name} value"); + } + } + if ($infoChanged) { + $$et{CHANGED} += $infoChanged; + } elsif ($prevUpdate) { + # must still write Info dictionary if it was previously updated + my $oldPos = LocateObject($xref, $$infoRef); + $infoChanged = 1 if $oldPos and $oldPos > $prevUpdate; + } + + # create new Info dictionary if necessary + if ($infoChanged) { + # increment object count if we used a new object here + if (scalar(keys %{$capture{Info}}) > 1) { + $newObj{$$infoRef} = $capture{Info};# save to write later + $$mainDict{Info} = $infoRef; # add reference to trailer dictionary + ++$nextObject unless $prevInfoRef; + } else { + # remove Info from Main (trailer) dictionary + delete $$mainDict{Info}; + # write free entry in xref table if Info existed prior to all edits + $newObj{$$infoRef} = '' if $prevInfoRef; + } + } + + # rewrite XMP + my %xmpInfo = ( + DataPt => $$info{XMP}, + Parent => 'PDF', + ); + my $xmpTable = Image::ExifTool::GetTagTable('Image::ExifTool::XMP::Main'); + my $oldChanged = $$et{CHANGED}; + my $newXMP = $et->WriteDirectory(\%xmpInfo, $xmpTable); + $newXMP = $$info{XMP} ? ${$$info{XMP}} : '' unless defined $newXMP; + + # WriteDirectory() will increment CHANGED erroneously if non-existent + # XMP is deleted as a block -- so check for this + unless ($newXMP or $$info{XMP}) { + $$et{CHANGED} = $oldChanged; + $et->VPrint(0, " (XMP not changed -- still empty)\n"); + } + my ($metaChanged, $rootChanged); + + if ($$et{CHANGED} != $oldChanged and defined $newXMP) { + $metaChanged = 1; + } elsif ($prevUpdate and $capture{Root}->{Metadata}) { + # must still write Metadata dictionary if it was previously updated + my $oldPos = LocateObject($xref, ${$capture{Root}->{Metadata}}); + $metaChanged = 1 if $oldPos and $oldPos > $prevUpdate; + } + if ($metaChanged) { + if ($newXMP) { + unless ($metaRef) { + # allocate new PDF object + $metaRef = \ "$nextObject 0 R"; + ++$nextObject; + $capture{Root}->{Metadata} = $metaRef; + $rootChanged = 1; # set flag to replace Root dictionary + } + # create the new metadata dictionary to write later + $newObj{$$metaRef} = { + Type => '/Metadata', + Subtype => '/XML', + # Length => length $newXMP, (set by WriteObject) + _tags => [ qw(Type Subtype) ], + _stream => $newXMP, + _decrypted => 1, # (this will be ignored if EncryptMetadata is false) + }; + } elsif ($capture{Root}->{Metadata}) { + # free existing metadata object + $newObj{${$capture{Root}->{Metadata}}} = ''; + delete $capture{Root}->{Metadata}; + $rootChanged = 1; # set flag to replace Root dictionary + } + } + # add new Root dictionary if necessary + my $rootRef = $$mainDict{Root}; + unless ($rootRef) { + $et->Error("Can't find Root dictionary"); + return $rtn; + } + if (not $rootChanged and $prevUpdate) { + # must still write Root dictionary if it was previously updated + my $oldPos = LocateObject($xref, $$rootRef); + $rootChanged = 1 if $oldPos and $oldPos > $prevUpdate; + } + $newObj{$$rootRef} = $capture{Root} if $rootChanged; +# +# write incremental update if anything was changed +# + if ($$et{CHANGED}) { + # remember position of original EOF + my $oldEOF = Tell($outfile) - $$et{PDFBase}; + Write($outfile, $beginComment) or $rtn = -1; + + # write new objects + foreach $objRef (sort keys %newObj) { + $objRef =~ /^(\d+) (\d+)/ or $rtn = -1, last; + ($id, $gen) = ($1, $2); + if (not $newObj{$objRef}) { + ++$gen if $gen < 65535; + # write free entry in xref table + $newXRef{$id} = [ 0, $gen, 'f' ]; + next; + } + # create new entry for xref table + $newXRef{$id} = [ Tell($outfile) - $$et{PDFBase} + length($/), $gen, 'n' ]; + $keyExt = "$id $gen obj"; # (must set for stream encryption) + Write($outfile, $/, $keyExt) or $rtn = -1; + WriteObject($outfile, $newObj{$objRef}) or $rtn = -1; + Write($outfile, $/, 'endobj') or $rtn = -1; + } + + # Prev points to old xref table + $$mainDict{Prev} = $capture{startxref} unless $prevUpdate; + + # add xref entry for head of free-object list + $newXRef{0} = [ 0, 65535, 'f' ]; + + # must insert free xref entries from previous exiftool update if applicable + if ($prevUpdate) { + my $mainFree; + # extract free entries from our previous Main xref stream + if ($$mainDict{Type} and $$mainDict{Type} eq '/XRef') { + $mainFree = GetFreeEntries($xref->{dicts}->[0]); + } else { + # free entries from Main xref table already captured for us + $mainFree = $capture{mainFree}; + } + foreach $id (sort { $a <=> $b } keys %$mainFree) { + $newXRef{$id} = $$mainFree{$id} unless $newXRef{$id}; + } + } + + # connect linked list of free object in our xref table + my $prevFree = 0; + foreach $id (sort { $b <=> $a } keys %newXRef) { # (reverse sort) + next unless $newXRef{$id}->[2] eq 'f'; # skip if not free + # no need to add free entry for objects added by us + # in previous edits then freed again + if ($id >= $nextObject) { + delete $newXRef{$id}; # Note: deleting newXRef entry! + next; + } + $newXRef{$id}->[0] = $prevFree; + $prevFree = $id; + } + + # prepare our main dictionary for writing + $$mainDict{Size} = $nextObject; # update number of objects + # must change the ID if it exists + if (ref $$mainDict{ID} eq 'ARRAY' and @{$$mainDict{ID}} > 1) { + # increment first byte since this is an easy change to make + $id = $mainDict->{ID}->[1]; + if ($id =~ /^<([0-9a-f]{2})/i) { + my $byte = unpack('H2',chr((hex($1) + 1) & 0xff)); + substr($id, 1, 2) = $byte; + } elsif ($id =~ /^\((.)/s and $1 ne '\\' and $1 ne ')' and $1 ne '(') { + my $ch = chr((ord($1) + 1) & 0xff); + # avoid generating characters that could cause problems + $ch = 'a' if $ch =~ /[()\\\x00-\x08\x0a-\x1f\x7f\xff]/; + substr($id, 1, 1) = $ch; + } + $mainDict->{ID}->[1] = $id; + } + + # remember position of xref table in file (we will write this next) + my $startxref = Tell($outfile) - $$et{PDFBase} + length($/); + + # must write xref as a stream in xref-stream-only files + if ($$mainDict{Type} and $$mainDict{Type} eq '/XRef') { + + # create entry for the xref stream object itself + $newXRef{$nextObject++} = [ Tell($outfile) - $$et{PDFBase} + length($/), 0, 'n' ]; + $$mainDict{Size} = $nextObject; + # create xref stream and Index entry + $$mainDict{W} = [ 1, 4, 2 ]; # int8u, int32u, int16u ('CNn') + $$mainDict{Index} = [ ]; + $$mainDict{_stream} = ''; + my @ids = sort { $a <=> $b } keys %newXRef; + while (@ids) { + my $startID = $ids[0]; + for (;;) { + $id = shift @ids; + my ($pos, $gen, $type) = @{$newXRef{$id}}; + if ($pos > 0xffffffff) { + $et->Error('Huge files not yet supported'); + last; + } + $$mainDict{_stream} .= pack('CNn', $type eq 'f' ? 0 : 1, $pos, $gen); + last if not @ids or $ids[0] != $id + 1; + } + # add Index entries for this section of the xref stream + push @{$$mainDict{Index}}, $startID, $id - $startID + 1; + } + # write the xref stream object + $keyExt = "$id 0 obj"; # (set anyway, but xref stream should NOT be encrypted) + Write($outfile, $/, $keyExt) or $rtn = -1; + WriteObject($outfile, $mainDict) or $rtn = -1; + Write($outfile, $/, 'endobj') or $rtn = -1; + + } else { + + # write new xref table + Write($outfile, $/, 'xref', $/) or $rtn = -1; + # lines must be exactly 20 bytes, so pad newline if necessary + my $endl = (length($/) == 1 ? ' ' : '') . $/; + my @ids = sort { $a <=> $b } keys %newXRef; + while (@ids) { + my $startID = $ids[0]; + $buff = ''; + for (;;) { + $id = shift @ids; + $buff .= sprintf("%.10d %.5d %s%s", @{$newXRef{$id}}, $endl); + last if not @ids or $ids[0] != $id + 1; + } + # write this (contiguous-numbered object) section of the xref table + Write($outfile, $startID, ' ', $id - $startID + 1, $/, $buff) or $rtn = -1; + } + + # write main (trailer) dictionary + Write($outfile, 'trailer') or $rtn = -1; + WriteObject($outfile, $mainDict) or $rtn = -1; + } + # write trailing comment (marker to allow edits to be reverted) + Write($outfile, $/, $endComment, $oldEOF, $/) or $rtn = -1; + + # write pointer to main xref table and EOF marker + Write($outfile, 'startxref', $/, $startxref, $/, '%%EOF', $/) or $rtn = -1; + + } elsif ($prevUpdate) { + + # nothing new changed, so copy over previous incremental update + $raf->Seek($prevUpdate+$$et{PDFBase}, 0) or $rtn = -1; + while ($raf->Read($buff, 65536)) { + Write($outfile, $buff) or $rtn = -1; + } + } + if ($rtn > 0 and $$et{CHANGED} and ($$et{DEL_GROUP}{PDF} or $$et{DEL_GROUP}{XMP})) { + $et->Warn('ExifTool PDF edits are reversible. Deleted tags may be recovered!', 1); + } + undef $newTool; + undef %capture; + return $rtn; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WritePDF.pl - Write PDF meta information + +=head1 SYNOPSIS + +These routines are autoloaded by Image::ExifTool::PDF. + +=head1 DESCRIPTION + +This file contains routines to write PDF metadata. + +=head1 NOTES + +When writing a PDF, exiftool does not modify the existing data. Instead, +the PDF file is appended with an incremental update which can easily be +removed to revert the file (by using ExifTool to delete the special +C<PDF-update> pseudo group). + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://partners.adobe.com/public/developer/pdf/index_reference.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::PDF(3pm)|Image::ExifTool::PDF>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/WritePNG.pl b/ExifTool/lib/Image/ExifTool/WritePNG.pl new file mode 100644 index 0000000..ad8e69e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WritePNG.pl @@ -0,0 +1,414 @@ +#------------------------------------------------------------------------------ +# File: WritePNG.pl +# +# Description: Write PNG meta information +# +# Revisions: 09/16/2005 - P. Harvey Created +# +# References: 1) http://www.libpng.org/pub/png/spec/1.2/ +#------------------------------------------------------------------------------ +package Image::ExifTool::PNG; + +use strict; + +#------------------------------------------------------------------------------ +# Calculate CRC or update running CRC (ref 1) +# Inputs: 0) data reference, 1) running crc to update (undef initially) +# 2) data position (undef for 0), 3) data length (undef for all data), +# Returns: updated CRC +my @crcTable; +sub CalculateCRC($;$$$) +{ + my ($dataPt, $crc, $pos, $len) = @_; + $crc = 0 unless defined $crc; + $pos = 0 unless defined $pos; + $len = length($$dataPt) - $pos unless defined $len; + $crc ^= 0xffffffff; # undo 1's complement + # build lookup table unless done already + unless (@crcTable) { + my ($c, $n, $k); + for ($n=0; $n<256; ++$n) { + for ($k=0, $c=$n; $k<8; ++$k) { + $c = ($c & 1) ? 0xedb88320 ^ ($c >> 1) : $c >> 1; + } + $crcTable[$n] = $c; + } + } + # calculate the CRC + foreach (unpack("x${pos}C$len", $$dataPt)) { + $crc = $crcTable[($crc^$_) & 0xff] ^ ($crc >> 8); + } + return $crc ^ 0xffffffff; # return 1's complement +} + +#------------------------------------------------------------------------------ +# Encode data in ASCII Hex +# Inputs: 0) input data reference +# Returns: Hex-encoded data (max 72 chars per line) +sub HexEncode($) +{ + my $dataPt = shift; + my $len = length($$dataPt); + my $hex = ''; + my $pos; + for ($pos = 0; $pos < $len; $pos += 36) { + my $n = $len - $pos; + $n > 36 and $n = 36; + $hex .= unpack('H*',substr($$dataPt,$pos,$n)) . "\n"; + } + return $hex; +} + +#------------------------------------------------------------------------------ +# Write profile chunk (possibly compressed if Zlib is available) +# Inputs: 0) outfile, 1) Raw profile type (SCALAR ref for iCCP name), 2) data ref +# 3) profile header type (undef if not a text profile) +# Returns: 1 on success +sub WriteProfile($$$;$) +{ + my ($outfile, $rawType, $dataPt, $profile) = @_; + my ($buff, $prefix, $chunk, $deflate); + if ($rawType ne $stdCase{exif} and eval { require Compress::Zlib }) { + $deflate = Compress::Zlib::deflateInit(); + } + if (not defined $profile) { + # write ICC profile as compressed iCCP chunk if possible + if (ref $rawType) { + return 0 unless $deflate; + $chunk = 'iCCP'; + $prefix = "$$rawType\0\0"; + } else { + $chunk = $rawType; + if ($rawType eq $stdCase{zxif}) { + $prefix = "\0" . pack('N', length $$dataPt); # (proposed compressed EXIF) + } else { + $prefix = ''; # standard EXIF + } + } + if ($deflate) { + $buff = $deflate->deflate($$dataPt); + return 0 unless defined $buff; + $buff .= $deflate->flush(); + $dataPt = \$buff; + } + } else { + # write as ASCII-hex encoded profile in tEXt or zTXt chunk + my $txtHdr = sprintf("\n$profile profile\n%8d\n", length($$dataPt)); + $buff = $txtHdr . HexEncode($dataPt); + $chunk = 'tEXt'; # write as tEXt if deflate not available + $prefix = "Raw profile type $rawType\0"; + $dataPt = \$buff; + # write profile as zTXt chunk if possible + if ($deflate) { + my $buf2 = $deflate->deflate($buff); + if (defined $buf2) { + $dataPt = \$buf2; + $buf2 .= $deflate->flush(); + $chunk = 'zTXt'; + $prefix .= "\0"; # compression type byte (0=deflate) + } + } + } + my $hdr = pack('Na4', length($prefix) + length($$dataPt), $chunk) . $prefix; + my $crc = CalculateCRC(\$hdr, undef, 4); + $crc = CalculateCRC($dataPt, $crc); + return Write($outfile, $hdr, $$dataPt, pack('N',$crc)); +} + +#------------------------------------------------------------------------------ +# Add iCCP-related chunks to the PNG image if necessary (must come before PLTE and IDAT) +# Inputs: 0) ExifTool object ref, 1) output file or scalar ref +# Returns: true on success +sub Add_iCCP($$) +{ + my ($et, $outfile) = @_; + if ($$et{ADD_DIRS}{ICC_Profile}) { + # write new ICC data + my $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::ICC_Profile::Main'); + my %dirInfo = ( Parent => 'PNG', DirName => 'ICC_Profile' ); + my $buff = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $buff and length $buff) { + my $profileName = $et->GetNewValue($Image::ExifTool::PNG::Main{'iCCP-name'}); + $profileName = 'icm' unless defined $profileName; + if (WriteProfile($outfile, \$profileName, \$buff)) { + $et->VPrint(0, "Created ICC profile with name '${profileName}'\n"); + delete $$et{ADD_DIRS}{ICC_Profile}; # don't add it again + } + } + } + # must also add sRGB and gAMA before PLTE and IDAT + if ($$et{ADD_PNG}) { + my ($tag, %addBeforePLTE); + foreach $tag (qw(sRGB gAMA)) { + next unless $$et{ADD_PNG}{$tag}; + $addBeforePLTE{$tag} = $$et{ADD_PNG}{$tag}; + delete $$et{ADD_PNG}{$tag}; + } + if (%addBeforePLTE) { + my $save = $$et{ADD_PNG}; + $$et{ADD_PNG} = \%addBeforePLTE; + AddChunks($et, $outfile); + $$et{ADD_PNG} = $save; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# This routine is called after we edit an existing directory +# Inputs: 0) ExifTool ref, 1) dir name, 2) output data ref +# 3) flag set if location is non-standard (to update, but not create from scratch) +# - on return, $$outBuff is set to '' if the directory is to be deleted +sub DoneDir($$$;$) +{ + my ($et, $dir, $outBuff, $nonStandard) = @_; + my $saveDir = $dir; + $dir = 'EXIF' if $dir eq 'IFD0'; + # don't add this directory again unless this is in a non-standard location + if (not $nonStandard) { + delete $$et{ADD_DIRS}{$dir}; + delete $$et{ADD_DIRS}{IFD0} if $dir eq 'EXIF'; + } elsif ($$et{DEL_GROUP}{$dir} or $$et{DEL_GROUP}{$saveDir}) { + $et->VPrint(0," Deleting non-standard $dir\n"); + $$outBuff = ''; + } +} + +#------------------------------------------------------------------------------ +# Generate tEXt, zTXt or iTXt data for writing +# Inputs: 0) ExifTool ref, 1) tagID, 2) tagInfo ref, 3) value string, 4) language code +# Returns: chunk data (not including 8-byte chunk header) +# Notes: Sets ExifTool TextChunkType member to the type of chunk written +sub BuildTextChunk($$$$$) +{ + my ($et, $tag, $tagInfo, $val, $lang) = @_; + my ($xtra, $compVal, $iTXt, $comp); + if ($$tagInfo{SubDirectory}) { + if ($$tagInfo{Name} eq 'XMP') { + $iTXt = 2; # write as iTXt but flag to avoid encoding + # (never compress XMP) + } else { + $comp = 2; # compress raw profile if possible + } + } else { + # compress if specified + $comp = 1 if $et->Options('Compress'); + if ($lang) { + $iTXt = 1; # write as iTXt if it has a language code + $tag =~ s/-$lang$//; # remove language code from tagID + } elsif ($$et{OPTIONS}{Charset} ne 'Latin' and $val =~ /[\x80-\xff]/) { + $iTXt = 1; # write as iTXt if it contains non-Latin special characters + } elsif ($$tagInfo{iTXt}) { + $iTXt = 1; # write as iTXt if specified in user-defined tag + } + } + if ($comp) { + my $warn; + if (eval { require Compress::Zlib }) { + my $deflate = Compress::Zlib::deflateInit(); + $compVal = $deflate->deflate($val) if $deflate; + if (defined $compVal) { + $compVal .= $deflate->flush(); + # only compress if it actually saves space + unless (length($compVal) < length($val)) { + undef $compVal; + $warn = 'uncompressed data is smaller'; + } + } else { + $warn = 'deflate error'; + } + } else { + $warn = 'Compress::Zlib not available'; + } + # warn if any user-specified compression fails + if ($warn and $comp == 1) { + $et->Warn("PNG:$$tagInfo{Name} not compressed ($warn)", 1); + } + } + # decide whether to write as iTXt, zTXt or tEXt + if ($iTXt) { + $$et{TextChunkType} = 'iTXt'; + $xtra = (defined $compVal ? "\x01\0" : "\0\0") . ($lang || '') . "\0\0"; + # iTXt is encoded as UTF-8 (but note that XMP is already UTF-8) + $val = $et->Encode($val, 'UTF8') if $iTXt == 1; + } elsif (defined $compVal) { + $$et{TextChunkType} = 'zTXt'; + $xtra = "\0"; + } else { + $$et{TextChunkType} = 'tEXt'; + $xtra = ''; + } + return $tag . "\0" . $xtra . (defined $compVal ? $compVal : $val); +} + +#------------------------------------------------------------------------------ +# Add any outstanding new chunks to the PNG image +# Inputs: 0) ExifTool object ref, 1) output file or scalar ref +# 2-N) dirs to add (empty to add all except EXIF 'IFD0', including PNG tags) +# Returns: true on success +sub AddChunks($$;@) +{ + my ($et, $outfile, @add) = @_; + my ($addTags, $tag, $dir, $err, $tagTablePtr, $specified); + + if (@add) { + $addTags = { }; # don't add any PNG tags + $specified = 1; + } else { + $addTags = $$et{ADD_PNG}; # add all PNG tags... + delete $$et{ADD_PNG}; # ...once + # add all directories + @add = sort keys %{$$et{ADD_DIRS}}; + } + # write any outstanding PNG tags + foreach $tag (sort keys %$addTags) { + my $tagInfo = $$addTags{$tag}; + next if $$tagInfo{FakeTag}; # (iCCP-name) + my $nvHash = $et->GetNewValueHash($tagInfo); + # (native PNG information is always preferred, so don't rely on just IsCreating) + next unless $$nvHash{IsCreating} or $et->IsOverwriting($nvHash) > 0; + my $val = $et->GetNewValue($nvHash); + if (defined $val) { + next if $$nvHash{EditOnly}; + my $data; + if ($$tagInfo{Table} eq \%Image::ExifTool::PNG::TextualData) { + $data = BuildTextChunk($et, $tag, $tagInfo, $val, $$tagInfo{LangCode}); + $data = $$et{TextChunkType} . $data; + delete $$et{TextChunkType}; + } else { + $data = "$tag$val"; + } + my $hdr = pack('N', length($data) - 4); + my $cbuf = pack('N', CalculateCRC(\$data, undef)); + Write($outfile, $hdr, $data, $cbuf) or $err = 1; + $et->VerboseValue("+ PNG:$$tagInfo{Name}", $val); + ++$$et{CHANGED}; + } + } + # create any necessary directories + foreach $dir (@add) { + next unless $$et{ADD_DIRS}{$dir}; # make sure we want to add it first + my $buff; + my %dirInfo = ( + Parent => 'PNG', + DirName => $dir, + ); + if ($dir eq 'IFD0') { + next unless $specified; # wait until specifically asked to write EXIF 'IFD0' + my $chunk = $stdCase{exif}; + # (zxIf was not adopted) + #if ($et->Options('Compress')) { + # if (eval { require Compress::Zlib }) { + # $chunk = $stdCase{zxif}; + # } else { + # $et->Warn("Creating uncompressed $stdCase{exif} chunk (Compress::Zlib not available)"); + # } + #} + $et->VPrint(0, "Creating $chunk chunk:\n"); + $$et{TIFF_TYPE} = 'APP1'; + $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::Exif::Main'); + $buff = $et->WriteDirectory(\%dirInfo, $tagTablePtr, \&Image::ExifTool::WriteTIFF); + if (defined $buff and length $buff) { + WriteProfile($outfile, $chunk, \$buff) or $err = 1; + } + } elsif ($dir eq 'XMP') { + $et->VPrint(0, "Creating XMP iTXt chunk:\n"); + $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::XMP::Main'); + $dirInfo{ReadOnly} = 1; + $buff = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $buff and length $buff and + # the packet is read-only (because of CRC) + Image::ExifTool::XMP::ValidateXMP(\$buff, 'r')) + { + # (previously, XMP was created as a non-standard XMP profile chunk) + # $buff = $Image::ExifTool::xmpAPP1hdr . $buff; + # WriteProfile($outfile, 'APP1', \$buff, 'generic') or $err = 1; + # (but now write XMP iTXt chunk according to XMP specification) + $buff = "iTXtXML:com.adobe.xmp\0\0\0\0\0" . $buff; + my $hdr = pack('N', length($buff) - 4); + my $cbuf = pack('N', CalculateCRC(\$buff, undef)); + Write($outfile, $hdr, $buff, $cbuf) or $err = 1; + } + } elsif ($dir eq 'IPTC') { + $et->Warn('Creating non-standard IPTC in PNG', 1); + $et->VPrint(0, "Creating IPTC profile:\n"); + # write new IPTC data (stored in a Photoshop directory) + $dirInfo{DirName} = 'Photoshop'; + $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::Photoshop::Main'); + $buff = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $buff and length $buff) { + WriteProfile($outfile, 'iptc', \$buff, 'IPTC') or $err = 1; + } + } elsif ($dir eq 'ICC_Profile') { + $et->VPrint(0, "Creating ICC profile:\n"); + # write new ICC data (only done if we couldn't create iCCP chunk) + $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::ICC_Profile::Main'); + $buff = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $buff and length $buff) { + WriteProfile($outfile, 'icm', \$buff, 'ICC') or $err = 1; + $et->Warn('Wrote ICC as a raw profile (no Compress::Zlib)'); + } + } elsif ($dir eq 'PNG-pHYs') { + $et->VPrint(0, "Creating pHYs chunk (default 2834 pixels per meter):\n"); + $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::PNG::PhysicalPixel'); + my $blank = "\0\0\x0b\x12\0\0\x0b\x12\x01"; # 2834 pixels per meter (72 dpi) + $dirInfo{DataPt} = \$blank; + $buff = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $buff and length $buff) { + $buff = 'pHYs' . $buff; # CRC includes chunk name + my $hdr = pack('N', length($buff) - 4); + my $cbuf = pack('N', CalculateCRC(\$buff, undef)); + Write($outfile, $hdr, $buff, $cbuf) or $err = 1; + } + } else { + next; + } + delete $$et{ADD_DIRS}{$dir}; # don't add again + } + return not $err; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WritePNG.pl - Write PNG meta information + +=head1 SYNOPSIS + +These routines are autoloaded by Image::ExifTool::PNG. + +=head1 DESCRIPTION + +This file contains routines to write PNG metadata. + +=head1 NOTES + +Compress::Zlib is required to write compressed text. + +Existing text tags are always rewritten in their original form (compressed +zTXt, uncompressed tEXt or international iTXt), so pre-existing compressed +information can only be modified if Compress::Zlib is available. + +Newly created textual information is written in uncompressed tEXt form by +default, or as compressed zTXt if the Compress option is used and +Compress::Zlib is available (but only if the resulting compressed data is +smaller than the original text, which isn't always the case for short text +strings). + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::PNG(3pm)|Image::ExifTool::PNG>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/WritePhotoshop.pl b/ExifTool/lib/Image/ExifTool/WritePhotoshop.pl new file mode 100644 index 0000000..217a3c9 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WritePhotoshop.pl @@ -0,0 +1,270 @@ +#------------------------------------------------------------------------------ +# File: WritePhotoshop.pl +# +# Description: Write Photoshop IRB meta information +# +# Revisions: 12/17/2004 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::Photoshop; + +use strict; + +#------------------------------------------------------------------------------ +# Strip resource name from value prepare resource name for writing into IRB +# Inputs: 0) tagInfo ref, 1) resource name (padded pascal string), 2) new value ref +# Returns: none (updates name and value if necessary) +sub SetResourceName($$$) +{ + my ($tagInfo, $name, $valPt) = @_; + my $setName = $$tagInfo{SetResourceName}; + if (defined $setName) { + # extract resource name from value + if ($$valPt =~ m{.*/#(.{0,255})#/$}s) { + $name = $1; + # strip name from value + $$valPt = substr($$valPt, 0, -4 - length($name)); + } elsif ($setName eq '1') { + return; # use old name + } else { + $name = $setName; + } + # convert to padded pascal string + $name = chr(length $name) . $name; + $name .= "\0" if length($name) & 0x01; + $_[1] = $name; # return new name + } +} + +#------------------------------------------------------------------------------ +# Write Photoshop IRB resource +# Inputs: 0) ExifTool object reference, 1) source dirInfo reference, +# 2) tag table reference +# Returns: IRB resource data (may be empty if no Photoshop data) +# Notes: Increments ExifTool CHANGED flag for each tag changed +sub WritePhotoshop($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + my $dataPt = $$dirInfo{DataPt}; + unless ($dataPt) { + my $emptyData = ''; + $dataPt = \$emptyData; + } + my $start = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $start); + my $dirEnd = $start + $dirLen; + my $newData = ''; + + # make a hash of new tag info, keyed on tagID + my $newTags = $et->GetNewTagInfoHash($tagTablePtr); + + my ($addDirs, $editDirs) = $et->GetAddDirHash($tagTablePtr); + + SetByteOrder('MM'); # Photoshop is always big-endian +# +# rewrite existing tags in the old directory, deleting ones as necessary +# (the Photoshop directory entries aren't in any particular order) +# + # Format: 0) Type, 4 bytes - '8BIM' (or the rare 'PHUT', 'DCSR', 'AgHg' or 'MeSa') + # 1) TagID,2 bytes + # 2) Name, pascal string padded to even no. bytes + # 3) Size, 4 bytes - N + # 4) Data, N bytes + my ($pos, $value, $size, $tagInfo, $tagID); + for ($pos=$start; $pos+8<$dirEnd; $pos+=$size) { + # each entry must be on same even byte boundary as directory start + ++$pos if ($pos ^ $start) & 0x01; + my $type = substr($$dataPt, $pos, 4); + if ($type !~ /^(8BIM|PHUT|DCSR|AgHg|MeSa)$/) { + $et->Error("Bad Photoshop IRB resource"); + undef $newData; + last; + } + $tagID = Get16u($dataPt, $pos + 4); + # get resource block name (pascal string padded to an even # of bytes) + my $namelen = 1 + Get8u($dataPt, $pos + 6); + ++$namelen if $namelen & 0x01; + if ($pos + $namelen + 10 > $dirEnd) { + $et->Error("Bad APP13 resource block"); + undef $newData; + last; + } + my $name = substr($$dataPt, $pos + 6, $namelen); + $size = Get32u($dataPt, $pos + 6 + $namelen); + $pos += $namelen + 10; + if ($size + $pos > $dirEnd) { + $et->Error("Bad APP13 resource data size $size"); + undef $newData; + last; + } + if ($$newTags{$tagID} and $type eq '8BIM') { + $tagInfo = $$newTags{$tagID}; + delete $$newTags{$tagID}; + my $nvHash = $et->GetNewValueHash($tagInfo); + # check to see if we are overwriting this tag + $value = substr($$dataPt, $pos, $size); + my $isOverwriting = $et->IsOverwriting($nvHash, $value); + # handle special 'new' and 'old' values for IPTCDigest + if (not $isOverwriting and $tagInfo eq $iptcDigestInfo) { + if (grep /^new$/, @{$$nvHash{DelValue}}) { + $isOverwriting = 1 if $$et{NewIPTCDigest} and + $$et{NewIPTCDigest} eq $value; + } + if (grep /^old$/, @{$$nvHash{DelValue}}) { + $isOverwriting = 1 if $$et{OldIPTCDigest} and + $$et{OldIPTCDigest} eq $value; + } + } + if ($isOverwriting) { + $et->VerboseValue("- Photoshop:$$tagInfo{Name}", $value); + # handle IPTCDigest specially because we want to write it last + # so the new IPTC digest will be known + if ($tagInfo eq $iptcDigestInfo) { + $$newTags{$tagID} = $tagInfo; # add later + $value = undef; + } else { + $value = $et->GetNewValue($nvHash); + } + ++$$et{CHANGED}; + next unless defined $value; # next if tag is being deleted + # set resource name if necessary + SetResourceName($tagInfo, $name, \$value); + $et->VerboseValue("+ Photoshop:$$tagInfo{Name}", $value); + } + } else { + if ($type eq '8BIM') { + $tagInfo = $$editDirs{$tagID}; + unless ($tagInfo) { + # process subdirectory anyway if writable (except EXIF to avoid recursion) + # --> this allows IPTC to be processed if found here in TIFF images + # (but allow EXIF to be written in PSD files if XMP or IPTC tags are + # being written because I have seen cases of XMP in PSD-EXIFInfo-IFD0 + # and IPTC in PSD-EXIFInfo-IFD0-IPTC, see forum10768 and forum13198) + my $tmpInfo = $et->GetTagInfo($tagTablePtr, $tagID); + if ($tmpInfo and $$tmpInfo{SubDirectory} and + ($tmpInfo->{SubDirectory}->{TagTable} ne 'Image::ExifTool::Exif::Main' or + ($$et{FILE_TYPE} eq 'PSD' and ($$editDirs{0x0404} or $$editDirs{0x0424})))) + { + my $table = Image::ExifTool::GetTagTable($tmpInfo->{SubDirectory}->{TagTable}); + $tagInfo = $tmpInfo if $$table{WRITE_PROC}; + } + } + } + if ($tagInfo) { + $$addDirs{$tagID} and delete $$addDirs{$tagID}; + my %subdirInfo = ( + DataPt => $dataPt, + DirStart => $pos, + DataLen => $dirLen, + DirLen => $size, + Parent => $$dirInfo{DirName}, + ); + my $subTable = Image::ExifTool::GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + my $writeProc = $tagInfo->{SubDirectory}->{WriteProc}; + my $newValue = $et->WriteDirectory(\%subdirInfo, $subTable, $writeProc); + if (defined $newValue) { + next unless length $newValue; # remove subdirectory entry + $value = $newValue; + SetResourceName($tagInfo, $name, \$value); + } else { + $value = substr($$dataPt, $pos, $size); # rewrite old directory + } + } else { + $value = substr($$dataPt, $pos, $size); + } + } + my $newSize = length $value; + # write this directory entry + $newData .= $type . Set16u($tagID) . $name . Set32u($newSize) . $value; + $newData .= "\0" if $newSize & 0x01; # must null pad to even byte + } +# +# write any remaining entries we didn't find in the old directory +# (might as well write them in numerical tag order) +# + my @tagsLeft = sort { $a <=> $b } keys(%$newTags), keys(%$addDirs); + foreach $tagID (@tagsLeft) { + my $name = "\0\0"; + if ($$newTags{$tagID}) { + $tagInfo = $$newTags{$tagID}; + my $nvHash = $et->GetNewValueHash($tagInfo); + $value = $et->GetNewValue($nvHash); + # handle new IPTCDigest value specially + if ($tagInfo eq $iptcDigestInfo and defined $value) { + if ($value eq 'new') { + $value = $$et{NewIPTCDigest}; + } elsif ($value eq 'old') { + $value = $$et{OldIPTCDigest}; + } + # (we already know we want to create this tag) + } else { + # don't add this tag unless specified + next unless $$nvHash{IsCreating}; + } + next unless defined $value; # next if tag is being deleted + $et->VerboseValue("+ Photoshop:$$tagInfo{Name}", $value); + ++$$et{CHANGED}; + } else { + $tagInfo = $$addDirs{$tagID}; + # create new directory + my %subdirInfo = ( + Parent => $$dirInfo{DirName}, + ); + my $subTable = Image::ExifTool::GetTagTable($tagInfo->{SubDirectory}->{TagTable}); + my $writeProc = $tagInfo->{SubDirectory}->{WriteProc}; + $value = $et->WriteDirectory(\%subdirInfo, $subTable, $writeProc); + next unless $value; + } + # set resource name if necessary + SetResourceName($tagInfo, $name, \$value); + $size = length($value); + # write the new directory entry + $newData .= '8BIM' . Set16u($tagID) . $name . Set32u($size) . $value; + $newData .= "\0" if $size & 0x01; # must null pad to even numbered byte + ++$$et{CHANGED}; + } + return $newData; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WritePhotoshop.pl - Write Photoshop IRB meta information + +=head1 SYNOPSIS + +This file is autoloaded by Image::ExifTool::Photoshop. + +=head1 DESCRIPTION + +This file contains routines to write Photoshop metadata. + +=head1 NOTES + +Photoshop IRB blocks may have an associated resource name. By default, the +existing name is preserved when rewriting a resource, and an empty name is +used when creating a new resource. However, a different resource name may +be specified by defining a C<SetResourceName> entry in the tag information +hash. With this defined, a new resource name may be appended to the value +in the form "VALUE/#NAME#/" (the slashes and hashes are literal). If +C<SetResourceName> is anything other than '1', the value is used as a +default resource name, and applied if no appended name is provided. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::Photoshop(3pm)|Image::ExifTool::Photoshop>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/WritePostScript.pl b/ExifTool/lib/Image/ExifTool/WritePostScript.pl new file mode 100644 index 0000000..39acbc8 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WritePostScript.pl @@ -0,0 +1,700 @@ +#------------------------------------------------------------------------------ +# File: WritePostScript.pl +# +# Description: Write PostScript meta information +# +# Revisions: 03/03/2006 - P. Harvey Created +# +# References: (see references in PostScript.pm, plus:) +# 1) http://www.adobe.com/products/postscript/pdfs/PLRM.pdf +# 2) http://www-cdf.fnal.gov/offline/PostScript/PLRM2.pdf +# 3) http://partners.adobe.com/public/developer/en/acrobat/sdk/pdf/pdf_creation_apis_and_specs/pdfmarkReference.pdf +# 4) http://www.npes.org/standards/Tools/DCS20Spec.pdf +# +# Notes: (see NOTES in POD doc below) +#------------------------------------------------------------------------------ + +package Image::ExifTool::PostScript; + +use strict; + +# Structure of a DSC PS/EPS document: +# +# %!PS-Adobe-3.0 [plus " EPSF-3.0" for EPS] +# <comments> +# %%EndComments [optional] +# %%BeginXxxx +# <stuff to ignore> +# %%EndXxxx +# %%BeginProlog +# <prolog stuff> +# %%EndProlog +# %%BeginSetup +# <setup stuff> +# %%EndSetup +# %ImageData x x x x [written by Photoshop] +# %BeginPhotoshop: xxxx +# <ascii-hex IRB information> +# %EndPhotosop +# %%BeginICCProfile: (name) <num> <type> +# <ICC Profile info> +# %%EndICCProfile +# %begin_xml_code +# <postscript code to define and read the XMP stream object> +# %begin_xml_packet: xxxx +# <XMP data> +# %end_xml_packet +# <postscript code to add XMP stream to dictionary> +# %end_xml_code +# %%Page: x x [PS only (optional?)] +# <graphics commands> +# %%PageTrailer +# %%Trailer +# <a bit more code to bracket EPS content for distiller> +# %%EOF + +# map of where information is stored in PS image +my %psMap = ( + XMP => 'PostScript', + Photoshop => 'PostScript', + IPTC => 'Photoshop', + EXIFInfo => 'Photoshop', + EXIF => 'EXIFInfo', + IFD0 => 'EXIFInfo', + IFD1 => 'IFD0', + ICC_Profile => 'PostScript', + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', +); + + +#------------------------------------------------------------------------------ +# Write XMP directory to file, with begin/end tokens if necessary +# Inputs: 0) outfile ref, 1) flags hash ref, 2-N) data to write +# Returns: true on success +sub WriteXMPDir($$@) +{ + my $outfile = shift; + my $flags = shift; + my $success = 1; + Write($outfile, "%begin_xml_code$/") or $success = 0 unless $$flags{WROTE_BEGIN}; + Write($outfile, @_) or $success = 0; + Write($outfile, "%end_xml_code$/") or $success = 0 unless $$flags{WROTE_BEGIN}; + return $success; +} + +#------------------------------------------------------------------------------ +# Write a directory inside a PS document +# Inputs: 0) ExifTool object ref, 1) output file reference, +# 2) Directory name, 3) data reference, 4) flags hash ref +# Returns: 0=error, 1=nothing written, 2=dir written ok +sub WritePSDirectory($$$$$) +{ + my ($et, $outfile, $dirName, $dataPt, $flags) = @_; + my $success = 2; + my $len = $dataPt ? length($$dataPt) : 0; + my $create = $len ? 0 : 1; + my %dirInfo = ( + DataPt => $dataPt, + DataLen => $len, + DirStart => 0, + DirLen => $len, + DirName => $dirName, + Parent => 'PostScript', + ); + # Note: $$flags{WROTE_BEGIN} may be 1 for XMP (it is always 0 for + # other dirs, but if 1, the begin/end markers were already written) +# +# prepare necessary postscript code to support embedded XMP +# + my ($beforeXMP, $afterXMP, $reportedLen); + if ($dirName eq 'XMP' and $len) { + # isolate the XMP + pos($$dataPt) = 0; + unless ($$dataPt =~ /(.*)(<\?xpacket begin=.{7,13}W5M0MpCehiHzreSzNTczkc9d)/sg) { + $et->Warn('No XMP packet start'); + return WriteXMPDir($outfile, $flags, $$dataPt); + } + $beforeXMP = $1; + my $xmp = $2; + my $p1 = pos($$dataPt); + unless ($$dataPt =~ m{<\?xpacket end=.(w|r).\?>}sg) { + $et->Warn('No XMP packet end'); + return WriteXMPDir($outfile, $flags, $$dataPt); + } + my $p2 = pos($$dataPt); + $xmp .= substr($$dataPt, $p1, $p2-$p1); + $afterXMP = substr($$dataPt, $p2); + # determine if we can adjust the XMP size + if ($beforeXMP =~ /%begin_xml_packet: (\d+)/s) { + $reportedLen = $1; + my @matches= ($beforeXMP =~ /\b$reportedLen\b/sg); + undef $reportedLen unless @matches == 2; + } + # must edit in place if we can't reliably change the XMP length + $dirInfo{InPlace} = 1 unless $reportedLen; + # process XMP only + $dirInfo{DataLen} = $dirInfo{DirLen} = length $xmp; + $dirInfo{DataPt} = \$xmp; + } + my $tagTablePtr = Image::ExifTool::GetTagTable("Image::ExifTool::${dirName}::Main"); + my $val = $et->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $val) { + $dataPt = \$val; # use modified directory + $len = length $val; + } elsif ($dirName eq 'XMP') { + return 1 unless $len; + # just write the original XMP + return WriteXMPDir($outfile, $flags, $$dataPt); + } + unless ($len) { + return 1 if $create or $dirName ne 'XMP'; # nothing to create + # it would be really difficult to delete the XMP, + # so instead we write a blank XMP record + $val = <<EMPTY_XMP; +<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool $Image::ExifTool::VERSION'> +</x:xmpmeta> +EMPTY_XMP + $val .= ((' ' x 100) . "\n") x 24 unless $$et{OPTIONS}{Compact}{NoPadding}; + $val .= q{<?xpacket end='w'?>}; + $dataPt = \$val; + $len = length $val; + } +# +# write XMP directory +# + if ($dirName eq 'XMP') { + if ($create) { + # create necessary PS/EPS code to support XMP + $beforeXMP = <<HDR_END; +/pdfmark where {pop true} {false} ifelse +/currentdistillerparams where {pop currentdistillerparams +/CoreDistVersion get 5000 ge } {false} ifelse +and not {userdict /pdfmark /cleartomark load put} if +[/NamespacePush pdfmark +[/_objdef {exiftool_metadata_stream} /type /stream /OBJ pdfmark +[{exiftool_metadata_stream} 2 dict begin /Type /Metadata def + /Subtype /XML def currentdict end /PUT pdfmark +/MetadataString $len string def % exact length of metadata +/TempString 100 string def +/ConsumeMetadata { +currentfile TempString readline pop pop +currentfile MetadataString readstring pop pop +} bind def +ConsumeMetadata +%begin_xml_packet: $len +HDR_END + # note: use q() to get necessary linefeed before %end_xml_packet + $afterXMP = q( +%end_xml_packet +[{exiftool_metadata_stream} MetadataString /PUT pdfmark +); + if ($$flags{EPS}) { + $afterXMP .= <<EPS_AFTER; +[/Document 1 dict begin + /Metadata {exiftool_metadata_stream} def currentdict end /BDC pdfmark +[/NamespacePop pdfmark +EPS_AFTER + # write this at end of file + $$flags{TRAILER} = "[/EMC pdfmark$/"; + } else { # PS + $afterXMP .= <<PS_AFTER; +[{Catalog} {exiftool_metadata_stream} /Metadata pdfmark +[/NamespacePop pdfmark +PS_AFTER + } + $beforeXMP =~ s{\n}{$/}sg; # use proper newline characters + $afterXMP =~ s{\n}{$/}sg; + } else { + # replace xmp size in PS code + $reportedLen and $beforeXMP =~ s/\b$reportedLen\b/$len/sg; + } + WriteXMPDir($outfile, $flags, $beforeXMP, $$dataPt, $afterXMP) or $success = 0; +# +# Write Photoshop or ICC_Profile directory +# + } elsif ($dirName eq 'Photoshop' or $dirName eq 'ICC_Profile') { + my ($startToken, $endToken); + if ($dirName eq 'Photoshop') { + $startToken = "%BeginPhotoshop: $len"; + $endToken = '%EndPhotoshop'; + } else { + $startToken = '%%BeginICCProfile: (Photoshop Profile) -1 Hex'; + $endToken = '%%EndICCProfile'; + } + Write($outfile, $startToken, $/) or $success = 0; + # write as an ASCII-hex comment + my $i; + my $wid = 32; + for ($i=0; $i<$len; $i+=$wid) { + $wid > $len-$i and $wid = $len-$i; + my $dat = substr($$dataPt, $i, $wid); + Write($outfile, "% ", uc(unpack('H*',$dat)), $/) or $success = 0; + } + Write($outfile, $endToken, $/) or $success = 0; + } else { + $et->Warn("Can't write PS directory $dirName"); + } + undef $val; + return $success; +} + +#------------------------------------------------------------------------------ +# Encode postscript tag/value +# Inputs: 0) tag ID, 1) value +# Returns: postscript comment +# - adds brackets, escapes special characters, and limits line length +sub EncodeTag($$) +{ + my ($tag, $val) = @_; + unless ($val =~ /^\d+$/) { + $val =~ s/([()\\])/\\$1/g; # escape brackets and backslashes + $val =~ s/\n/\\n/g; # escape newlines + $val =~ s/\r/\\r/g; # escape carriage returns + $val =~ s/\t/\\t/g; # escape tabs + # use octal escape codes for other control characters + $val =~ s/([\x00-\x1f\x7f\xff])/sprintf("\\%.3o",ord($1))/ge; + $val = "($val)"; + } + my $line = "%%$tag: $val"; + # postscript line limit is 255 characters (but it seems that + # the limit may be 254 characters if the DOS CR/LF is used) + # --> split if necessary using continuation comment "%%+" + my $n; + for ($n=254; length($line)>$n; $n+=254+length($/)) { + substr($line, $n, 0) = "$/%%+"; + } + return $line . $/; +} + +#------------------------------------------------------------------------------ +# Write new tags information in comments section +# Inputs: 0) ExifTool object ref, 1) output file ref, 2) reference to new tag hash +# Returns: true on success +sub WriteNewTags($$$) +{ + my ($et, $outfile, $newTags) = @_; + my $success = 1; + my $tag; + + # get XMP hint and remove from tags hash + my $xmpHint = $$newTags{XMP_HINT}; + delete $$newTags{XMP_HINT}; + + foreach $tag (sort keys %$newTags) { + my $tagInfo = $$newTags{$tag}; + my $nvHash = $et->GetNewValueHash($tagInfo); + next unless $$nvHash{IsCreating}; + my $val = $et->GetNewValue($nvHash); + $et->VerboseValue("+ PostScript:$$tagInfo{Name}", $val); + Write($outfile, EncodeTag($tag, $val)) or $success = 0; + ++$$et{CHANGED}; + } + # write XMP hint if necessary + Write($outfile, "%ADO_ContainsXMP: MainFirst$/") or $success = 0 if $xmpHint; + + %$newTags = (); # all done with new tags + return $success; +} + +#------------------------------------------------------------------------------ +# Write PS file +# Inputs: 0) ExifTool object reference, 1) source dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid PS file, +# or -1 if a write error occurred +sub WritePS($$) +{ + my ($et, $dirInfo) = @_; + $et or return 1; # allow dummy access to autoload this package + my $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::PostScript::Main'); + my $raf = $$dirInfo{RAF}; + my $outfile = $$dirInfo{OutFile}; + my $verbose = $et->Options('Verbose'); + my $out = $et->Options('TextOut'); + my ($data, $buff, %flags, $err, $mode, $endToken); + my ($dos, $psStart, $psNewStart, $xmpHint, @lines); + + $raf->Read($data, 4) == 4 or return 0; + return 0 unless $data =~ /^(%!PS|%!Ad|\xc5\xd0\xd3\xc6)/; + + if ($data =~ /^%!Ad/) { + # I've seen PS files start with "%!Adobe-PS"... + return 0 unless $raf->Read($buff, 6) == 6 and $buff eq "obe-PS"; + $data .= $buff; + + } elsif ($data =~ /^\xc5\xd0\xd3\xc6/) { +# +# process DOS binary PS files +# + # save DOS header then seek ahead and check PS header + $raf->Read($dos, 26) == 26 or return 0; + $dos = $data . $dos; + SetByteOrder('II'); + $psStart = Get32u(\$dos, 4); + unless ($raf->Seek($psStart, 0) and + $raf->Read($data, 4) == 4 and $data eq '%!PS') + { + $et->Error('Invalid PS header'); + return 1; + } + $$raf{PSEnd} = $psStart + Get32u(\$dos, 8); + my $base = Get32u(\$dos, 20); + Set16u(0xffff, \$dos, 28); # ignore checksum + if ($base) { + my %dirInfo = ( + Parent => 'PS', + RAF => $raf, + Base => $base, + NoTiffEnd => 1, # no end-of-TIFF check + ); + $buff = $et->WriteTIFF(\%dirInfo); + SetByteOrder('II'); # (WriteTIFF may change this) + if ($buff) { + $buff = substr($buff, $base); # remove header written by WriteTIFF() + } else { + # error rewriting TIFF, so just copy over original data + my $len = Get32u(\$dos, 24); + unless ($raf->Seek($base, 0) and $raf->Read($buff, $len) == $len) { + $et->Error('Error reading embedded TIFF'); + return 1; + } + $et->Warn('Bad embedded TIFF'); + } + Set32u(0, \$dos, 12); # zero metafile pointer + Set32u(0, \$dos, 16); # zero metafile length + Set32u(length($dos), \$dos, 20); # set TIFF pointer + Set32u(length($buff), \$dos, 24); # set TIFF length + } elsif (($base = Get32u(\$dos, 12)) != 0) { + # copy over metafile section + my $len = Get32u(\$dos, 16); + unless ($raf->Seek($base, 0) and $raf->Read($buff, $len) == $len) { + $et->Error('Error reading metafile section'); + return 1; + } + Set32u(length($dos), \$dos, 12); # set metafile pointer + } else { + $buff = ''; + } + $psNewStart = length($dos) + length($buff); + Set32u($psNewStart, \$dos, 4); # set pointer to start of PS + Write($outfile, $dos, $buff) or $err = 1; + $raf->Seek($psStart + 4, 0); # seek back to where we were + } +# +# rewrite PostScript data +# + local $/ = GetInputRecordSeparator($raf); + unless ($/ and $raf->ReadLine($buff)) { + $et->Error('Invalid PostScript data'); + return 1; + } + $data .= $buff; + unless ($data =~ /^%!PS-Adobe-3\.(\d+)\b/ and $1 < 2) { + if ($et->Error("Document does not conform to DSC spec. Metadata may be unreadable by other apps", 2)) { + return 1; + } + } + my $psRev = $1; # save PS revision number (3.x) + Write($outfile, $data) or $err = 1; + $flags{EPS} = 1 if $data =~ /EPSF/; + + # get hash of new information keyed by tagID and directories to add/edit + my $newTags = $et->GetNewTagInfoHash($tagTablePtr); + + # figure out which directories we need to write (PostScript takes priority) + $et->InitWriteDirs(\%psMap, 'PostScript'); + my $addDirs = $$et{ADD_DIRS}; + my $editDirs = $$et{EDIT_DIRS}; + my %doneDir; + + # set XMP hint flag (1 for adding, 0 for deleting, undef for no change) + $xmpHint = 1 if $$addDirs{XMP}; + $xmpHint = 0 if $$et{DEL_GROUP}{XMP}; + $$newTags{XMP_HINT} = $xmpHint if $xmpHint; # add special tag to newTags list + + for (;;) { + @lines or GetNextLine($raf, \@lines) or last; + $data = shift @lines; + if ($endToken) { + # look for end token + if ($data =~ m/^$endToken\s*$/is) { + undef $endToken; + # found end: process this information + if ($mode) { + $doneDir{$mode} and $et->Error("Multiple $mode directories", 1); + $doneDir{$mode} = 1; + WritePSDirectory($et, $outfile, $mode, \$buff, \%flags) or $err = 1; + # write end token if we wrote the begin token + Write($outfile, $data) or $err = 1 if $flags{WROTE_BEGIN}; + undef $buff; + } else { + Write($outfile, $data) or $err = 1; + } + } else { + # buffer data in current begin/end block + if (not defined $mode) { + # pick up XMP in unrecognized blocks for editing in place + if ($data =~ /^<\?xpacket begin=.{7,13}W5M0MpCehiHzreSzNTczkc9d/ and + $$editDirs{XMP}) + { + $buff = $data; + $mode = 'XMP'; + } else { + Write($outfile, $data) or $err = 1; + } + } elsif ($mode eq 'XMP') { + $buff .= $data; + } else { + # data is ASCII-hex encoded + $data =~ tr/0-9A-Fa-f//dc; # remove all but hex characters + $buff .= pack('H*', $data); # translate from hex + } + } + next; + } elsif ($data =~ m{^(%{1,2})(Begin)(?!Object:)(.*?)[:\x0d\x0a]}i) { + # comments section is over... write any new tags now + WriteNewTags($et, $outfile, $newTags) or $err = 1 if %$newTags; + undef $xmpHint; + # the beginning of a data block (can only write XMP and Photoshop) + my %modeLookup = ( + _xml_code => 'XMP', + photoshop => 'Photoshop', + iccprofile => 'ICC_Profile', + ); + $verbose > 1 and print $out "$2$3\n"; + $endToken = $1 . ($2 eq 'begin' ? 'end' : 'End') . $3; + $mode = $modeLookup{lc($3)}; + if ($mode and $$editDirs{$mode}) { + $buff = ''; # initialize buffer for this block + $flags{WROTE_BEGIN} = 0; + } else { + undef $mode; # not editing this directory + Write($outfile, $data) or $err = 1; + $flags{WROTE_BEGIN} = 1; + } + next; + } elsif ($data =~ /^%%(?!Page:|PlateFile:|BeginObject:)(\w+): ?(.*)/s) { + # rewrite information from PostScript tags in comments + my ($tag, $val) = ($1, $2); + # handle Adobe Illustrator files specially + # - EVENTUALLY IT WOULD BE BETTER TO FIND ANOTHER IDENTIFICATION METHOD + # (because Illustrator doesn't care if the Creator is changed) + if ($tag eq 'Creator' and $val =~ /^Adobe Illustrator/) { + # disable writing XMP to PostScript-format Adobe Illustrator files + # because it confuses Illustrator + if ($$editDirs{XMP}) { + $et->Warn("Can't write XMP to PostScript-format Illustrator files"); + # pretend like we wrote it already so we won't try to add it later + $doneDir{XMP} = 1; + } + # don't allow "Creator" to be changed in Illustrator files + # (we need it to be able to recognize these files) + # --> find a better way to do this! + if ($$newTags{$tag}) { + $et->Warn("Can't change Postscript:Creator of Illustrator files"); + delete $$newTags{$tag}; + } + } + if ($$newTags{$tag}) { + my $tagInfo = $$newTags{$tag}; + delete $$newTags{$tag}; # write it then forget it + next unless ref $tagInfo; + # decode comment string (reading continuation lines if necessary) + $val = DecodeComment($val, $raf, \@lines, \$data); + $val = join $et->Options('ListSep'), @$val if ref $val eq 'ARRAY'; + my $nvHash = $et->GetNewValueHash($tagInfo); + if ($et->IsOverwriting($nvHash, $val)) { + $et->VerboseValue("- PostScript:$$tagInfo{Name}", $val); + $val = $et->GetNewValue($nvHash); + ++$$et{CHANGED}; + next unless defined $val; # next if tag is being deleted + $et->VerboseValue("+ PostScript:$$tagInfo{Name}", $val); + $data = EncodeTag($tag, $val); + } + } + # (note: Adobe InDesign doesn't put colon after %ADO_ContainsXMP -- doh!) + } elsif (defined $xmpHint and $data =~ m{^%ADO_ContainsXMP:? ?(.+?)[\x0d\x0a]*$}s) { + # change the XMP hint if necessary + if ($xmpHint) { + $data = "%ADO_ContainsXMP: MainFirst$/" if $1 eq 'NoMain'; + } else { + $data = "%ADO_ContainsXMP: NoMain$/"; + } + # delete XMP hint flags + delete $$newTags{XMP_HINT}; + undef $xmpHint; + } else { + # look for end of comments section + if (%$newTags and ($data !~ /^%\S/ or + $data =~ /^%(%EndComments|%Page:|%PlateFile:|%BeginObject:|.*BeginLayer)/)) + { + # write new tags at end of comments section + WriteNewTags($et, $outfile, $newTags) or $err = 1; + undef $xmpHint; + } + # look for start of drawing commands (AI uses "%AI5_BeginLayer", + # and Helios uses "%%BeginObject:") + if ($data =~ /^%(%Page:|%PlateFile:|%BeginObject:|.*BeginLayer)/ or + $data !~ m{^(%.*|\s*)$}s) + { + # we have reached the first page or drawing command, so create necessary + # directories and copy the rest of the file, then all done + my $dir; + my $plateFile = ($data =~ /^%%PlateFile:/); + # create Photoshop first, then XMP if necessary + foreach $dir (qw{Photoshop ICC_Profile XMP}) { + next unless $$editDirs{$dir} and not $doneDir{$dir}; + if ($plateFile) { + # PlateFile comments may contain offsets so we can't edit these files! + $et->Warn("Can only edit PostScript information DCS Plate files"); + last; + } + next unless $$addDirs{$dir} or $dir eq 'XMP'; + $flags{WROTE_BEGIN} = 0; + WritePSDirectory($et, $outfile, $dir, undef, \%flags) or $err = 1; + $doneDir{$dir} = 1; + } + # copy rest of file + if ($flags{TRAILER}) { + # write trailer before %%EOF + for (;;) { + Write($outfile, $data) or $err = 1; + if (@lines) { + $data = shift @lines; + } else { + $raf->ReadLine($data) or undef($data), last; + $dos and CheckPSEnd($raf, \$data); + if ($data =~ /[\x0d\x0a]%%EOF\b/g) { + # split data before "%%EOF" + # (necessary if data contains other newline sequences) + my $pos = pos($data) - 5; + push @lines, substr($data, $pos); + $data = substr($data, 0, $pos); + } + } + last if $data =~ /^%%EOF\b/; + } + Write($outfile, $flags{TRAILER}) or $err = 1; + } + # simply copy the rest of the file if any data is left + if (defined $data) { + Write($outfile, $data) or $err = 1; + Write($outfile, @lines) or $err = 1 if @lines; + while ($raf->Read($data, 65536)) { + $dos and CheckPSEnd($raf, \$data); + Write($outfile, $data) or $err = 1; + } + } + last; # all done! + } + } + # write new information or copy existing line + Write($outfile, $data) or $err = 1; + } + if ($dos and not $err) { + # must go back and set length of PS section in DOS header (very dumb design) + if (ref $outfile eq 'SCALAR') { + Set32u(length($$outfile) - $psNewStart, $outfile, 8); + } else { + my $pos = tell $outfile; + unless (seek($outfile, 8, 0) and + print $outfile Set32u($pos - $psNewStart) and + seek($outfile, $pos, 0)) + { + $et->Error("Can't write DOS-style PS files in non-seekable stream"); + $err = 1; + } + } + } + # issue warning if we couldn't write any information + unless ($err) { + my (@notDone, $dir); + delete $$newTags{XMP_HINT}; + push @notDone, 'PostScript' if %$newTags; + foreach $dir (qw{Photoshop ICC_Profile XMP}) { + push @notDone, $dir if $$editDirs{$dir} and not $doneDir{$dir} and + not $$et{DEL_GROUP}{$dir}; + } + @notDone and $et->Warn("Couldn't write ".join('/',@notDone).' information'); + } + $endToken and $et->Error("File missing $endToken"); + return $err ? -1 : 1; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WritePostScript.pl - Write PostScript meta information + +=head1 SYNOPSIS + +This file is autoloaded by Image::ExifTool::PostScript. + +=head1 DESCRIPTION + +This file contains routines to write meta information in PostScript +documents. Six forms of meta information may be written: + + 1) PostScript comments (Adobe DSC specification) + 2) XMP information embedded in a document-level XMP stream + 3) EXIF information embedded in a Photoshop record + 4) IPTC information embedded in a PhotoShop record + 5) ICC_Profile information embedded in an ICCProfile record + 6) TIFF information embedded in DOS-style binary header + +=head1 NOTES + +Currently, information is written only in the outer-level document. + +Photoshop will discard meta information in a PostScript document if it has +to rasterize the image, and it will rasterize anything that doesn't contain +the Photoshop-specific 'ImageData' tag. So don't expect Photoshop to read +any meta information added to EPS images that it didn't create. + +The following two acronyms may be confusing since they are so similar and +have different meanings with respect to PostScript documents: + + DSC = Document Structuring Conventions + DCS = Desktop Color Separation + +=head1 REFERENCES + +See references in L<PostScript.pm|Image::ExifTool::PostScript>, plus: + +=over 4 + +=item L<http://www.adobe.com/products/postscript/pdfs/PLRM.pdf> + +=item L<http://www-cdf.fnal.gov/offline/PostScript/PLRM2.pdf> + +=item L<http://partners.adobe.com/public/developer/en/acrobat/sdk/pdf/pdf_creation_apis_and_specs/pdfmarkReference.pdf> + +=back + +=head1 ACKNOWLEDGEMENTS + +Thanks to Tim Kordick for his help testing the EPS writer. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::PostScript(3pm)|Image::ExifTool::PostScript>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/WriteQuickTime.pl b/ExifTool/lib/Image/ExifTool/WriteQuickTime.pl new file mode 100644 index 0000000..be287ce --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WriteQuickTime.pl @@ -0,0 +1,1968 @@ +#------------------------------------------------------------------------------ +# File: WriteQuickTime.pl +# +# Description: Write XMP to QuickTime (MOV and MP4) files +# +# Revisions: 2013-10-29 - P. Harvey Created +#------------------------------------------------------------------------------ +package Image::ExifTool::QuickTime; + +use strict; + +# maps for adding metadata to various QuickTime-based file types +my %movMap = ( + # MOV (no 'ftyp', or 'ftyp'='qt ') -> XMP in 'moov'-'udta'-'XMP_' + QuickTime => 'ItemList', # (default location for QuickTime tags) + ItemList => 'Meta', # MOV-Movie-UserData-Meta-ItemList + Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location) + Meta => 'UserData', + XMP => 'UserData', # MOV-Movie-UserData-XMP + Microsoft => 'UserData', # MOV-Movie-UserData-Microsoft + UserData => 'Movie', # MOV-Movie-UserData + Movie => 'MOV', + GSpherical => 'SphericalVideoXML', # MOV-Movie-Track-SphericalVideoXML + SphericalVideoXML => 'Track', # (video track specifically, don't create if it doesn't exist) + Track => 'Movie', +); +my %mp4Map = ( + # MP4 ('ftyp' compatible brand 'mp41', 'mp42' or 'f4v ') -> XMP at top level + QuickTime => 'ItemList', # (default location for QuickTime tags) + ItemList => 'Meta', # MOV-Movie-UserData-Meta-ItemList + Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location) + Meta => 'UserData', + UserData => 'Movie', # MOV-Movie-UserData + Microsoft => 'UserData', # MOV-Movie-UserData-Microsoft + Movie => 'MOV', + XMP => 'MOV', # MOV-XMP + GSpherical => 'SphericalVideoXML', # MOV-Movie-Track-SphericalVideoXML + SphericalVideoXML => 'Track', # (video track specifically, don't create if it doesn't exist) + Track => 'Movie', +); +my %heicMap = ( + # HEIC/HEIF/AVIF ('ftyp' compatible brand 'heic','mif1','avif') -> XMP/EXIF in top level 'meta' + Meta => 'MOV', + ItemInformation => 'Meta', + ItemPropertyContainer => 'Meta', + XMP => 'ItemInformation', + EXIF => 'ItemInformation', + ICC_Profile => 'ItemPropertyContainer', + IFD0 => 'EXIF', + IFD1 => 'IFD0', + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', +); +my %cr3Map = ( + # CR3 ('ftyp' compatible brand 'crx ') -> XMP at top level + Movie => 'MOV', + XMP => 'MOV', + 'UUID-Canon'=>'Movie', + ExifIFD => 'UUID-Canon', + IFD0 => 'UUID-Canon', + GPS => 'UUID-Canon', + #MakerNoteCanon => 'UUID-Canon', # (doesn't yet work -- goes into ExifIFD instead) + 'UUID-Canon2' => 'MOV', + CanonVRD => 'UUID-Canon2', +); +my %dirMap = ( + MOV => \%movMap, + MP4 => \%mp4Map, + CR3 => \%cr3Map, + HEIC => \%heicMap, +); + +# convert ExifTool Format to QuickTime type +my %qtFormat = ( + 'undef' => 0x00, string => 0x01, + int8s => 0x15, int16s => 0x15, int32s => 0x15, int64s => 0x15, + int8u => 0x16, int16u => 0x16, int32u => 0x16, int64u => 0x16, + float => 0x17, double => 0x18, +); +my $undLang = 0x55c4; # numeric code for default ('und') language + +my $maxReadLen = 100000000; # maximum size of atom to read into memory (100 MB) + +# boxes that may exist in an "empty" Meta box: +my %emptyMeta = ( + hdlr => 'Handler', 'keys' => 'Keys', lang => 'Language', ctry => 'Country', free => 'Free', +); + +# lookup for CTBO ID number based on uuid for Canon CR3 files +my %ctboID = ( + "\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac" => 1, # XMP + "\xea\xf4\x2b\x5e\x1c\x98\x4b\x88\xb9\xfb\xb7\xdc\x40\x6e\x4d\x16" => 2, # PreviewImage + # ID 3 is used for 'mdat' atom (not a uuid) +); + +# mark UserData tags that don't have ItemList counterparts as Preferred +# - and set Preferred to 0 for any Avoid-ed tag +# - also, for now, set Writable to 0 for any tag with a RawConv and no RawConvInv +{ + my $itemList = \%Image::ExifTool::QuickTime::ItemList; + my $userData = \%Image::ExifTool::QuickTime::UserData; + my (%pref, $tag); + foreach $tag (TagTableKeys($itemList)) { + my $tagInfo = $$itemList{$tag}; + if (ref $tagInfo ne 'HASH') { + next if ref $tagInfo; + $tagInfo = $$itemList{$tag} = { Name => $tagInfo }; + } else { + $$tagInfo{Writable} = 0 if $$tagInfo{RawConv} and not $$tagInfo{RawConvInv}; + $$tagInfo{Avoid} and $$tagInfo{Preferred} = 0, next; + next if defined $$tagInfo{Preferred} and not $$tagInfo{Preferred}; + } + $pref{$$tagInfo{Name}} = 1; + } + foreach $tag (TagTableKeys($userData)) { + my $tagInfo = $$userData{$tag}; + if (ref $tagInfo ne 'HASH') { + next if ref $tagInfo; + $tagInfo = $$userData{$tag} = { Name => $tagInfo }; + } else { + $$tagInfo{Writable} = 0 if $$tagInfo{RawConv} and not $$tagInfo{RawConvInv}; + $$tagInfo{Avoid} and $$tagInfo{Preferred} = 0, next; + next if defined $$tagInfo{Preferred} or $pref{$$tagInfo{Name}}; + } + $$tagInfo{Preferred} = 1; + } +} + +#------------------------------------------------------------------------------ +# Format GPSCoordinates for writing +# Inputs: 0) PrintConv value +# Returns: ValueConv value +sub PrintInvGPSCoordinates($) +{ + my ($val, $et) = @_; + my @v = split /, */, $val; + if (@v == 2 or @v == 3) { + my $below = ($v[2] and $v[2] =~ /below/i); + $v[0] = Image::ExifTool::GPS::ToDegrees($v[0], 1); + $v[1] = Image::ExifTool::GPS::ToDegrees($v[1], 1); + $v[2] = Image::ExifTool::ToFloat($v[2]) * ($below ? -1 : 1) if @v == 3; + return "@v"; + } + return $val if $val =~ /^([-+]\d+(\.\d*)?){2,3}(CRS.*)?\/?$/; # already in ISO6709 format? + return undef; +} + +#------------------------------------------------------------------------------ +# Convert GPS coordinates back to ISO6709 format +# Inputs: 0) ValueConv value +# Returns: ISO6709 coordinates +sub ConvInvISO6709($) +{ + local $_; + my $val = shift; + my @a = split ' ', $val; + if (@a == 2 or @a == 3) { + # latitude must have 2 digits before the decimal, and longitude 3, + # and all values must start with a "+" or "-", and Google Photos + # requires at least 3 digits after the decimal point + # (and as of Apr 2021, Google Photos doesn't accept coordinats + # with more than 5 digits after the decimal place: + # https://exiftool.org/forum/index.php?topic=11055.msg67171#msg67171 ) + my @fmt = ('%s%02d.%s%s','%s%03d.%s%s','%s%d.%s%s'); + foreach (@a) { + return undef unless Image::ExifTool::IsFloat($_); + $_ =~ s/^([-+]?)(\d+)\.?(\d*)/sprintf(shift(@fmt),$1||'+',$2,$3,length($3)<3 ? '0'x(3-length($3)) : '')/e; + } + return join '', @a, '/'; + } + return $val if $val =~ /^([-+]\d+(\.\d*)?){2,3}(CRS.*)?\/?$/; # already in ISO6709 format? + return undef; +} + +#------------------------------------------------------------------------------ +# Handle offsets in iloc (ItemLocation) atom when writing (ref ISO 14496-12:2015 pg.79) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) data ref, 3) output buffer ref +# Returns: true on success +# Notes: see also ParseItemLocation() in QuickTime.pm +# (variable names with underlines correspond to names in ISO 14496-12) +sub Handle_iloc($$$$) +{ + my ($et, $dirInfo, $dataPt, $outfile) = @_; + my ($i, $j, $num, $pos, $id); + + my $off = $$dirInfo{ChunkOffset}; + my $len = length $$dataPt; + return 0 if $len < 8; + my $ver = Get8u($dataPt, 0); + my $siz = Get16u($dataPt, 4); + my $noff = ($siz >> 12); + my $nlen = ($siz >> 8) & 0x0f; + my $nbas = ($siz >> 4) & 0x0f; + my $nind = $siz & 0x0f; + my %ok = ( 0 => 1, 4 => 1, 8 => 8 ); + return 0 unless $ok{$noff} and $ok{$nlen} and $ok{$nbas} and $ok{$nind}; + # piggy-back on existing code to fix up stco/co64 4/8-byte offsets + my $tag = $noff == 4 ? 'stco_iloc' : 'co64_iloc'; + if ($ver < 2) { + $num = Get16u($dataPt, 6); + $pos = 8; + } else { + return 0 if $len < 10; + $num = Get32u($dataPt, 6); + $pos = 10; + } + for ($i=0; $i<$num; ++$i) { + if ($ver < 2) { + return 0 if $pos + 2 > $len; + $id = Get16u($dataPt, $pos); + $pos += 2; + } else { + return 0 if $pos + 4 > $len; + $id = Get32u($dataPt, $pos); + $pos += 4; + } + my ($constOff, @offBase, @offItem, $minOffset); + if ($ver == 1 or $ver == 2) { + return 0 if $pos + 2 > $len; + # offsets are absolute only if ConstructionMethod is 0, otherwise + # the relative offsets are constant as far as we are concerned + $constOff = Get16u($dataPt, $pos) & 0x0f; + $pos += 2; + } + return 0 if $pos + 2 > $len; + my $drefIdx = Get16u($dataPt, $pos); + if ($drefIdx) { + if ($$et{QtDataRef} and $$et{QtDataRef}[$drefIdx - 1]) { + my $dref = $$et{QtDataRef}[$drefIdx - 1]; + # these offsets are constant unless the data is in this file + $constOff = 1 unless $$dref[1] == 1 and $$dref[0] ne 'rsrc'; + } else { + $et->Error("No data reference for iloc entry $i"); + return 0; + } + } + $pos += 2; + # get base offset and save its location if in this file + my $base_offset = GetVarInt($dataPt, $pos, $nbas); + if ($base_offset and not $constOff) { + my $tg = ($nbas == 4 ? 'stco' : 'co64') . '_iloc'; + push @offBase, [ $tg, length($$outfile) + 8 + $pos - $nbas, $nbas, 0, $id ]; + } + return 0 if $pos + 2 > $len; + my $ext_num = Get16u($dataPt, $pos); + $pos += 2; + my $listStartPos = $pos; + # run through the item list to get offset locations and the minimum offset in this file + for ($j=0; $j<$ext_num; ++$j) { + $pos += $nind if $ver == 1 or $ver == 2; + my $extent_offset = GetVarInt($dataPt, $pos, $noff); + return 0 unless defined $extent_offset; + unless ($constOff) { + push @offItem, [ $tag, length($$outfile) + 8 + $pos - $noff, $noff, 0, $id ] if $noff; + $minOffset = $extent_offset if not defined $minOffset or $minOffset > $extent_offset; + } + return 0 if $pos + $nlen > length $$dataPt; + $pos += $nlen; + } + # decide whether to fix up the base offset or individual item offsets + # (adjust the one that is larger) + if (defined $minOffset and $minOffset > $base_offset) { + $$_[3] = $base_offset foreach @offItem; + push @$off, @offItem; + } else { + $$_[3] = $minOffset foreach @offBase; + push @$off, @offBase; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Get localized version of tagInfo hash +# Inputs: 0) tagInfo hash ref, 1) language code (eg. "fra-FR") +# Returns: new tagInfo hash ref, or undef if invalid or no language code +sub GetLangInfo($$) +{ + my ($tagInfo, $langCode) = @_; + return undef unless $langCode; + # only allow alternate language tags in lang-alt lists + my $writable = $$tagInfo{Writable}; + $writable = $$tagInfo{Table}{WRITABLE} unless defined $writable; + return undef unless $writable; + $langCode =~ tr/_/-/; # RFC 3066 specifies '-' as a separator + my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode); + return $langInfo; +} + +#------------------------------------------------------------------------------ +# validate raw values for writing +# Inputs: 0) ExifTool ref, 1) tagInfo hash ref, 2) raw value ref +# Returns: error string or undef (and possibly changes value) on success +sub CheckQTValue($$$) +{ + my ($et, $tagInfo, $valPtr) = @_; + my $format = $$tagInfo{Format} || $$tagInfo{Writable} || $$tagInfo{Table}{FORMAT}; + return undef unless $format; + return Image::ExifTool::CheckValue($valPtr, $format, $$tagInfo{Count}); +} + +#------------------------------------------------------------------------------ +# Format QuickTime value for writing +# Inputs: 0) ExifTool ref, 1) value ref, 2) tagInfo ref, 3) Format (or undef) +# Returns: Flags for QT data type, and reformats value as required (sets to undef on error) +sub FormatQTValue($$;$$) +{ + my ($et, $valPt, $tagInfo, $format) = @_; + my $writable = $$tagInfo{Writable}; + my $count = $$tagInfo{Count}; + my $flags; + $format or $format = $$tagInfo{Format}; + if ($format and $format ne 'string' or not $format and $writable and $writable ne 'string') { + $$valPt = WriteValue($$valPt, $format || $writable, $count); + if ($writable and $qtFormat{$writable}) { + $flags = $qtFormat{$writable}; + } else { + $flags = $qtFormat{$format || 0} || 0; + } + } elsif ($$valPt =~ /^\xff\xd8\xff/) { + $flags = 0x0d; # JPG + } elsif ($$valPt =~ /^(\x89P|\x8aM|\x8bJ)NG\r\n\x1a\n/) { + $flags = 0x0e; # PNG + } elsif ($$valPt =~ /^BM.{15}\0/s) { + $flags = 0x1b; # BMP + } else { + $flags = 0x01; # UTF8 + $$valPt = $et->Encode($$valPt, 'UTF8'); + } + defined $$valPt or $et->WarnOnce("Error converting value for $$tagInfo{Name}"); + return $flags; +} + +#------------------------------------------------------------------------------ +# Set variable-length integer (used by WriteItemInfo) +# Inputs: 0) value, 1) integer size in bytes (0, 4 or 8), +# Returns: packed integer +sub SetVarInt($$) +{ + my ($val, $n) = @_; + if ($n == 4) { + return Set32u($val); + } elsif ($n == 8) { + return Set64u($val); + } + return ''; +} + +#------------------------------------------------------------------------------ +# Write Meta Keys to add/delete entries as necessary ('mdta' handler) (ref PH) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: updated keys box data +sub WriteKeys($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + my $dataPt = $$dirInfo{DataPt}; + my $dirLen = length $$dataPt; + my $outfile = $$dirInfo{OutFile}; + my ($tag, %done, %remap, %info, %add, $i); + + $dirLen < 8 and $et->Warn('Short Keys box'), $dirLen = 8, $$dataPt = "\0" x 8; + if ($$et{DEL_GROUP}{Keys}) { + $dirLen = 8; # delete all existing keys + # deleted keys are identified by a zero entry in the Remap lookup + my $n = Get32u($dataPt, 4); + for ($i=1; $i<=$n; ++$i) { $remap{$i} = 0; } + $et->VPrint(0, " [deleting $n Keys entr".($n==1 ? 'y' : 'ies')."]\n"); + ++$$et{CHANGED}; + } + my $pos = 8; + my $newTags = $et->GetNewTagInfoHash($tagTablePtr); + my $newData = substr($$dataPt, 0, $pos); + + my $newIndex = 1; + my $index = 1; + while ($pos < $dirLen - 4) { + my $len = unpack("x${pos}N", $$dataPt); + last if $len < 8 or $pos + $len > $dirLen; + my $ns = substr($$dataPt, $pos + 4, 4); + $tag = substr($$dataPt, $pos + 8, $len - 8); + $tag =~ s/\0.*//s; # truncate at null + $tag =~ s/^com\.apple\.quicktime\.// if $ns eq 'mdta'; # remove apple quicktime domain + $tag = "Tag_$ns" unless $tag; + $done{$tag} = 1; # set flag to avoid creating this tag + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + if ($tagInfo) { + $info{$index} = $tagInfo; + if ($$newTags{$tag}) { + my $nvHash = $et->GetNewValueHash($tagInfo); + # drop this tag if it is being deleted + if ($nvHash and $et->IsOverwriting($nvHash) > 0 and not defined $et->GetNewValue($nvHash)) { + # don't delete this key if we could be writing any alternate-language version of this tag + my ($t, $dontDelete); + foreach $t (keys %$newTags) { + next unless $$newTags{$t}{SrcTagInfo} and $$newTags{$t}{SrcTagInfo} eq $tagInfo; + my $nv = $et->GetNewValueHash($$newTags{$t}); + next unless $et->IsOverwriting($nv) and defined $et->GetNewValue($nv); + $dontDelete = 1; + last; + } + unless ($dontDelete) { + # delete this key + $et->VPrint(1, "$$et{INDENT}\[deleting Keys entry $index '${tag}']\n"); + $pos += $len; + $remap{$index++} = 0; + ++$$et{CHANGED}; + next; + } + } + } + } + # add to the Keys box data + $newData .= substr($$dataPt, $pos, $len); + $remap{$index++} = $newIndex++; + $pos += $len; + } + # add keys for any tags we need to create + foreach $tag (sort keys %$newTags) { + my $tagInfo = $$newTags{$tag}; + my $id; + if ($$tagInfo{LangCode} and $$tagInfo{SrcTagInfo}) { + $id = $$tagInfo{SrcTagInfo}{TagID}; + } else { + $id = $tag; + } + next if $done{$id}; + my $nvHash = $et->GetNewValueHash($tagInfo); + next unless $$nvHash{IsCreating} and $et->IsOverwriting($nvHash) and + defined $et->GetNewValue($nvHash); + # add new entry to 'keys' data + my $val = $id =~ /^com\./ ? $id : "com.apple.quicktime.$id"; + $newData .= Set32u(8 + length($val)) . 'mdta' . $val; + $et->VPrint(1, "$$et{INDENT}\[adding Keys entry $newIndex '${id}']\n"); + $add{$newIndex++} = $tagInfo; + ++$$et{CHANGED}; + } + my $num = $newIndex - 1; + if ($num) { + Set32u($num, \$newData, 4); # update count in header + } else { + $newData = ''; # delete empty Keys box + } + # save temporary variables for use when writing ItemList: + # Remap - lookup for remapping Keys ID numbers (0 if item is deleted) + # Info - Keys tag information, based on old index value + # Add - Keys items deleted, based on old index value + # Num - Number of items in edited Keys box + $$et{Keys} = { Remap => \%remap, Info => \%info, Add => \%add, Num => $num }; + + return $newData; # return updated Keys box +} + +#------------------------------------------------------------------------------ +# Write ItemInformation in HEIC files +# Inputs: 0) ExifTool ref, 1) dirInfo ref (with BoxPos entry), 2) output buffer ref +# Returns: mdat edit list ref (empty if nothing changed) +sub WriteItemInfo($$$) +{ + my ($et, $dirInfo, $outfile) = @_; + my $boxPos = $$dirInfo{BoxPos}; # hash of [length,position] for each box + my $raf = $$et{RAF}; + my $items = $$et{ItemInfo}; + my (%did, @mdatEdit, $name); + + return () unless $items and $raf; + + # extract information from EXIF/XMP metadata items + my $primary = $$et{PrimaryItem}; + my $curPos = $raf->Tell(); + my $id; + foreach $id (sort { $a <=> $b } keys %$items) { + $primary = $id unless defined $primary; # assume primary is lowest-number item if pitm missing + my $item = $$items{$id}; + # only edit primary EXIF/XMP metadata + next unless $$item{RefersTo} and $$item{RefersTo}{$primary}; + my $type = $$item{ContentType} || $$item{Type} || next; + # get ExifTool name for this item + $name = { Exif => 'EXIF', 'application/rdf+xml' => 'XMP' }->{$type}; + next unless $name; # only care about EXIF and XMP + next unless $$et{EDIT_DIRS}{$name}; + $did{$name} = 1; # set flag to prevent creating this metadata + my ($warn, $extent, $buff, @edit); + $warn = 'Missing iloc box' unless $$boxPos{iloc}; + $warn = "No Extents for $type item" unless $$item{Extents} and @{$$item{Extents}}; + $warn = "Can't currently decode encoded $type metadata" if $$item{ContentEncoding}; + $warn = "Can't currently decode protected $type metadata" if $$item{ProtectionIndex}; + $warn = "Can't currently extract $type with construction method $$item{ConstructionMethod}" if $$item{ConstructionMethod}; + $warn = "$type metadata is not this file" if $$item{DataReferenceIndex}; + $warn and $et->Warn($warn), next; + my $base = $$item{BaseOffset} || 0; + my $val = ''; + foreach $extent (@{$$item{Extents}}) { + $val .= $buff if defined $buff; + my $pos = $$extent[1] + $base; + if ($$extent[2]) { + $raf->Seek($pos, 0) or last; + $raf->Read($buff, $$extent[2]) or last; + } else { + $buff = ''; + } + push @edit, [ $pos, $pos + $$extent[2] ]; # replace or delete this if changed + } + next unless defined $buff; + $buff = $val . $buff if length $val; + my ($hdr, $subTable, $proc); + if ($name eq 'EXIF') { + if (not length $buff) { + # create EXIF from scratch + $hdr = "\0\0\0\x06Exif\0\0"; + } elsif ($buff =~ /^(MM\0\x2a|II\x2a\0)/) { + $et->Warn('Missing Exif header'); + $hdr = ''; + } elsif (length($buff) >= 4 and length($buff) >= 4 + unpack('N',$buff)) { + $hdr = substr($buff, 0, 4 + unpack('N',$buff)); + } else { + $et->Warn('Invalid Exif header'); + next; + } + $subTable = GetTagTable('Image::ExifTool::Exif::Main'); + $proc = \&Image::ExifTool::WriteTIFF; + } else { + $hdr = ''; + $subTable = GetTagTable('Image::ExifTool::XMP::Main'); + } + my %dirInfo = ( + DataPt => \$buff, + DataLen => length $buff, + DirStart => length $hdr, + DirLen => length($buff) - length $hdr, + ); + my $changed = $$et{CHANGED}; + my $newVal = $et->WriteDirectory(\%dirInfo, $subTable, $proc); + if (defined $newVal and $changed ne $$et{CHANGED} and + # nothing changed if deleting an empty directory + ($dirInfo{DirLen} or length $newVal)) + { + $newVal = $hdr . $newVal if length $hdr and length $newVal; + $edit[0][2] = \$newVal; # replace the old chunk with the new data + $edit[0][3] = $id; # mark this chunk with the item ID + push @mdatEdit, @edit; + # update item extent_length + my $n = length $newVal; + foreach $extent (@{$$item{Extents}}) { + my ($nlen, $lenPt) = @$extent[3,4]; + if ($nlen == 8) { + Set64u($n, $outfile, $$boxPos{iloc}[0] + 8 + $lenPt); + } elsif ($n <= 0xffffffff) { + Set32u($n, $outfile, $$boxPos{iloc}[0] + 8 + $lenPt); + } else { + $et->Error("Can't yet promote iloc length to 64 bits"); + return (); + } + $n = 0; + } + if (@{$$item{Extents}} != 1) { + $et->Error("Can't yet handle $name in multiple parts. Please submit sample for testing"); + } + } + $$et{CHANGED} = $changed; # (will set this later if successful in editing mdat) + } + $raf->Seek($curPos, 0); # seek back to original position + + # add necessary metadata types if they didn't already exist + my ($countNew, %add, %usedID); + foreach $name ('EXIF','XMP') { + next if $did{$name} or not $$et{ADD_DIRS}{$name}; + my @missing; + $$boxPos{$_} or push @missing, $_ foreach qw(iinf iloc); + if (@missing) { + my $str = @missing > 1 ? join(' and ', @missing) . ' boxes' : "@missing box"; + $et->Warn("Can't create $name. Missing expected $str"); + last; + } + unless (defined $$et{PrimaryItem}) { + unless (defined $primary) { + $et->Warn("Can't create $name. No items to reference"); + last; + } + # add new primary item reference box after hdrl box + if ($primary < 0x10000) { + $add{hdlr} = pack('Na4Nn', 14, 'pitm', 0, $primary); + } else { + $add{hdlr} = pack('Na4CCCCN', 16, 'pitm', 1, 0, 0, 0, $primary); + } + $et->Warn("Added missing PrimaryItemReference (for item $primary)", 1); + } + my $buff = ''; + my ($hdr, $subTable, $proc); + if ($name eq 'EXIF') { + $hdr = "\0\0\0\x06Exif\0\0"; + $subTable = GetTagTable('Image::ExifTool::Exif::Main'); + $proc = \&Image::ExifTool::WriteTIFF; + } else { + $hdr = ''; + $subTable = GetTagTable('Image::ExifTool::XMP::Main'); + } + my %dirInfo = ( + DataPt => \$buff, + DataLen => 0, + DirStart => 0, + DirLen => 0, + ); + my $changed = $$et{CHANGED}; + my $newVal = $et->WriteDirectory(\%dirInfo, $subTable, $proc); + if (defined $newVal and $changed ne $$et{CHANGED}) { + my $irefVer; + if ($$boxPos{iref}) { + $irefVer = Get8u($outfile, $$boxPos{iref}[0] + 8); + } else { + # create iref box after end of iinf box (and save version in boxPos list) + $irefVer = ($primary < 0x10000 ? 0 : 1); + $$boxPos{iref} = [ $$boxPos{iinf}[0] + $$boxPos{iinf}[1], 0, $irefVer ]; + } + $newVal = $hdr . $newVal if length $hdr; + # add new infe to iinf + $add{iinf} = $add{iref} = $add{iloc} = '' unless defined $add{iinf}; + my ($type, $mime); + if ($name eq 'XMP') { + $type = "mime\0"; + $mime = "application/rdf+xml\0"; + } else { + $type = "Exif\0"; + $mime = ''; + } + my $id = 1; + ++$id while $$items{$id} or $usedID{$id}; # find next unused item ID + my $n = length($type) + length($mime) + 16; + if ($id < 0x10000) { + $add{iinf} .= pack('Na4CCCCnn', $n, 'infe', 2, 0, 0, 1, $id, 0) . $type . $mime; + } else { + $n += 2; + $add{iinf} .= pack('Na4CCCCNn', $n, 'infe', 3, 0, 0, 1, $id, 0) . $type . $mime; + } + # add new cdsc to iref + if ($irefVer) { + $add{iref} .= pack('Na4NnN', 18, 'cdsc', $id, 1, $primary); + } else { + $add{iref} .= pack('Na4nnn', 14, 'cdsc', $id, 1, $primary); + } + # add new entry to iloc table (see ISO14496-12:2015 pg.79) + my $ilocVer = Get8u($outfile, $$boxPos{iloc}[0] + 8); + my $siz = Get16u($outfile, $$boxPos{iloc}[0] + 12); # get size information + my $noff = ($siz >> 12); + my $nlen = ($siz >> 8) & 0x0f; + my $nbas = ($siz >> 4) & 0x0f; + my $nind = $siz & 0x0f; + my ($pbas, $poff); + if ($ilocVer == 0) { + # set offset to 0 as flag that this is a new idat chunk being added + $pbas = length($add{iloc}) + 4; + $poff = $pbas + $nbas + 2; + $add{iloc} .= pack('nn',$id,0) . SetVarInt(0,$nbas) . Set16u(1) . + SetVarInt(0,$noff) . SetVarInt(length($newVal),$nlen); + } elsif ($ilocVer == 1) { + $pbas = length($add{iloc}) + 6; + $poff = $pbas + $nbas + 2 + $nind; + $add{iloc} .= pack('nnn',$id,0,0) . SetVarInt(0,$nbas) . Set16u(1) . SetVarInt(0,$nind) . + SetVarInt(0,$noff) . SetVarInt(length($newVal),$nlen); + } elsif ($ilocVer == 2) { + $pbas = length($add{iloc}) + 8; + $poff = $pbas + $nbas + 2 + $nind; + $add{iloc} .= pack('Nnn',$id,0,0) . SetVarInt(0,$nbas) . Set16u(1) . SetVarInt(0,$nind) . + SetVarInt(0,$noff) . SetVarInt(length($newVal),$nlen); + } else { + $et->Warn("Can't create $name. Unsupported iloc version $ilocVer"); + last; + } + # add new ChunkOffset entry to update this new offset + my $off = $$dirInfo{ChunkOffset} or $et->Warn('Internal error. Missing ChunkOffset'), last; + my $newOff; + if ($noff == 4) { + $newOff = [ 'stco_iloc', $$boxPos{iloc}[0] + $$boxPos{iloc}[1] + $poff, $noff, 0, $id ]; + } elsif ($noff == 8) { + $newOff = [ 'co64_iloc', $$boxPos{iloc}[0] + $$boxPos{iloc}[1] + $poff, $noff, 0, $id ]; + } elsif ($noff == 0) { + # offset_size is zero, so store the offset in base_offset instead + if ($nbas == 4) { + $newOff = [ 'stco_iloc', $$boxPos{iloc}[0] + $$boxPos{iloc}[1] + $pbas, $nbas, 0, $id ]; + } elsif ($nbas == 8) { + $newOff = [ 'co64_iloc', $$boxPos{iloc}[0] + $$boxPos{iloc}[1] + $pbas, $nbas, 0, $id ]; + } else { + $et->Warn("Can't create $name. Invalid iloc offset+base size"); + last; + } + } else { + $et->Warn("Can't create $name. Invalid iloc offset size"); + last; + } + # add directory as a new mdat chunk + push @$off, $newOff; + push @mdatEdit, [ 0, 0, \$newVal, $id ]; + $usedID{$id} = 1; + $countNew = ($countNew || 0) + 1; + $$et{CHANGED} = $changed; # set this later if successful in editing mdat + } + } + if ($countNew) { + # insert new entries into iinf, iref and iloc boxes, + # and add new pitm box after hdlr if necessary + my $added = 0; + my $tag; + foreach $tag (sort { $$boxPos{$a}[0] <=> $$boxPos{$b}[0] } keys %$boxPos) { + next unless $add{$tag}; + my $pos = $$boxPos{$tag}[0] + $added; + unless ($$boxPos{$tag}[1]) { + $tag eq 'iref' or $et->Error('Internal error adding iref box'), last; + # create new iref box + $add{$tag} = Set32u(12 + length $add{$tag}) . $tag . + Set8u($$boxPos{$tag}[2]) . "\0\0\0" . $add{$tag}; + } elsif ($tag ne 'hdlr') { + my $n = Get32u($outfile, $pos); + Set32u($n + length($add{$tag}), $outfile, $pos); # increase box size + } + if ($tag eq 'iinf') { + my $iinfVer = Get8u($outfile, $pos + 8); + if ($iinfVer == 0) { + my $n = Get16u($outfile, $pos + 12); + Set16u($n + $countNew, $outfile, $pos + 12); # incr count + } else { + my $n = Get32u($outfile, $pos + 12); + Set32u($n + $countNew, $outfile, $pos + 12); # incr count + } + } elsif ($tag eq 'iref') { + # nothing more to do + } elsif ($tag eq 'iloc') { + my $ilocVer = Get8u($outfile, $pos + 8); + if ($ilocVer < 2) { + my $n = Get16u($outfile, $pos + 14); + Set16u($n + $countNew, $outfile, $pos + 14); # incr count + } else { + my $n = Get32u($outfile, $pos + 14); + Set32u($n + $countNew, $outfile, $pos + 14); # incr count + } + # must also update pointer locations in this box + if ($added) { + $$_[1] += $added foreach @{$$dirInfo{ChunkOffset}}; + } + } elsif ($tag ne 'hdlr') { + next; + } + # add new entries to this box (or add pitm after hdlr) + substr($$outfile, $pos + $$boxPos{$tag}[1], 0) = $add{$tag}; + $added += length $add{$tag}; # positions are shifted by length of new entries + } + } + delete $$et{ItemInfo}; + return @mdatEdit ? \@mdatEdit : undef; +} + +#------------------------------------------------------------------------------ +# Write a series of QuickTime atoms from file or in memory +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: A) if dirInfo contains DataPt: new directory data +# B) otherwise: true on success, 0 if a write error occurred +# (true but sets an Error on a file format error) +# Notes: Yes, this is a real mess. Just like the QuickTime metadata situation. +sub WriteQuickTime($$$) +{ + local $_; + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + my ($mdat, @mdat, @mdatEdit, $edit, $track, $outBuff, $co, $term, $delCount); + my (%langTags, $canCreate, $delGrp, %boxPos, %didDir, $writeLast, $err, $atomCount); + my $outfile = $$dirInfo{OutFile} || return 0; + my $raf = $$dirInfo{RAF}; # (will be null for lower-level atoms) + my $dataPt = $$dirInfo{DataPt}; # (will be null for top-level atoms) + my $dirName = $$dirInfo{DirName}; + my $dirStart = $$dirInfo{DirStart} || 0; + my $parent = $$dirInfo{Parent}; + my $addDirs = $$et{ADD_DIRS}; + my $didTag = $$et{DidTag}; + my $newTags = { }; + my $createKeys = 0; + my ($rtnVal, $rtnErr) = $dataPt ? (undef, undef) : (1, 0); + + if ($dataPt) { + $raf = new File::RandomAccess($dataPt); + } else { + return 0 unless $raf; + } + # use buffered output for everything but 'mdat' atoms + $outBuff = ''; + $outfile = \$outBuff; + + $raf->Seek($dirStart, 1) if $dirStart; # skip header if it exists + + my $curPath = join '-', @{$$et{PATH}}; + my ($dir, $writePath) = ($dirName, $dirName); + $writePath = "$dir-$writePath" while defined($dir = $$et{DirMap}{$dir}); + # hack to create Keys directories if necessary (its containing Meta is in a different location) + if ($$addDirs{Keys} and $curPath =~ /^MOV-Movie(-Meta)?$/) { + $createKeys = 1; # create new Keys directories + } elsif ($curPath eq 'MOV-Movie-Meta-ItemList') { + $createKeys = 2; # create new Keys tags + my $keys = $$et{Keys}; + if ($keys) { + # add new tag entries for existing Keys tags, now that we know their ID's + # - first make lookup to convert Keys tagInfo ref to index number + my ($index, %keysInfo); + foreach $index (keys %{$$keys{Info}}) { + $keysInfo{$$keys{Info}{$index}} = $index if $$keys{Remap}{$index}; + } + my $keysTable = GetTagTable('Image::ExifTool::QuickTime::Keys'); + my $newKeysTags = $et->GetNewTagInfoHash($keysTable); + foreach (keys %$newKeysTags) { + my $tagInfo = $$newKeysTags{$_}; + $index = $keysInfo{$tagInfo} || ($$tagInfo{SrcTagInfo} and $keysInfo{$$tagInfo{SrcTagInfo}}); + next unless $index; + my $id = Set32u($index); + if ($$tagInfo{LangCode}) { + # add to lookup of language tags we are writing with this ID + $langTags{$id} = { } unless $langTags{$id}; + $langTags{$id}{$_} = $tagInfo; + $id .= '-' . $$tagInfo{LangCode}; + } + $$newTags{$id} = $tagInfo; + } + } + } else { + # get hash of new tags to edit/create in this directory + $newTags = $et->GetNewTagInfoHash($tagTablePtr); + # make lookup of language tags for each ID + foreach (keys %$newTags) { + next unless $$newTags{$_}{LangCode} and $$newTags{$_}{SrcTagInfo}; + my $id = $$newTags{$_}{SrcTagInfo}{TagID}; + $langTags{$id} = { } unless $langTags{$id}; + $langTags{$id}{$_} = $$newTags{$_}; + } + } + if ($curPath eq $writePath or $createKeys) { + $canCreate = 1; + $delGrp = $$et{DEL_GROUP}{$dirName}; + } + $atomCount = $$tagTablePtr{VARS}{ATOM_COUNT} if $$tagTablePtr{VARS}; + + for (;;) { # loop through all atoms at this level + if (defined $atomCount and --$atomCount < 0 and $dataPt) { + # stop processing now and just copy the rest of the atom + Write($outfile, substr($$dataPt, $raf->Tell())) or $rtnVal=$rtnErr, $err=1; + last; + } + my ($hdr, $buff, $keysIndex); + my $n = $raf->Read($hdr, 8); + unless ($n == 8) { + if ($n == 4 and $hdr eq "\0\0\0\0") { + # "for historical reasons" the udta is optionally terminated by 4 zeros (ref 1) + # --> hold this terminator to the end + $term = $hdr; + } elsif ($n != 0) { + $et->Error("Unknown $n bytes at end of file", 1); + } + last; + } + my $size = Get32u(\$hdr, 0) - 8; # (atom size without 8-byte header) + my $tag = substr($hdr, 4, 4); + if ($size == -7) { + # read the extended size + $raf->Read($buff, 8) == 8 or $et->Error('Truncated extended atom'), last; + $hdr .= $buff; + my ($hi, $lo) = unpack('NN', $buff); + if ($hi or $lo > 0x7fffffff) { + if ($hi > 0x7fffffff) { + $et->Error('Invalid atom size'); + last; + } elsif (not $et->Options('LargeFileSupport')) { + $et->Error('End of processing at large atom (LargeFileSupport not enabled)'); + last; + } + } + $size = $hi * 4294967296 + $lo - 16; + $size < 0 and $et->Error('Invalid extended atom size'), last; + } elsif ($size == -8) { + if ($dataPt) { + last if $$dirInfo{DirName} eq 'CanonCNTH'; # (this is normal for Canon CNTH atom) + my $pos = $raf->Tell() - 4; + $raf->Seek(0,2); + my $str = $$dirInfo{DirName} . ' with ' . ($raf->Tell() - $pos) . ' bytes'; + $et->Error("Terminator found in $str remaining", 1); + } else { + # size of zero is only valid for top-level atom, and + # indicates the atom extends to the end of file + # (save in mdat list to write later; with zero end position to copy rest of file) + push @mdat, [ $raf->Tell(), 0, $hdr ]; + } + last; + } elsif ($size < 0) { + $et->Error('Invalid atom size'); + last; + } + + # keep track of 'mdat' atom locations for writing later + if ($tag eq 'mdat') { + if ($dataPt) { + $et->Error("'mdat' not at top level"); + last; + } + push @mdat, [ $raf->Tell(), $raf->Tell() + $size, $hdr ]; + $raf->Seek($size, 1) or $et->Error("Seek error in mdat atom"), return $rtnVal; + next; + } elsif ($tag eq 'cmov') { + $et->Error("Can't yet write compressed movie metadata"); + return $rtnVal; + } elsif ($tag eq 'wide') { + next; # drop 'wide' tag + } + + # read the atom data + my $got; + if (not $size) { + $buff = ''; + $got = 0; + } else { + # read the atom data (but only first 64kB if data is huge) + $got = $raf->Read($buff, $size > $maxReadLen ? 0x10000 : $size); + } + if ($got != $size) { + # ignore up to 256 bytes of garbage at end of file + if ($got <= 256 and $size >= 1024 and $tag ne 'mdat') { + my $bytes = $got + length $hdr; + if ($$et{OPTIONS}{IgnoreMinorErrors}) { + $et->Warn("Deleted garbage at end of file ($bytes bytes)"); + $buff = $hdr = ''; + } else { + $et->Error("Possible garbage at end of file ($bytes bytes)", 1); + return $rtnVal; + } + } else { + $tag = PrintableTagID($tag,3); + if ($size > $maxReadLen and $got == 0x10000) { + my $mb = int($size / 0x100000 + 0.5); + $et->Error("'${tag}' atom is too large for rewriting ($mb MB)"); + } else { + $et->Error("Truncated '${tag}' atom"); + } + return $rtnVal; + } + } + # save the handler type for this track + if ($tag eq 'hdlr' and length $buff >= 12) { + my $hdlr = substr($buff,8,4); + $$et{HandlerType} = $hdlr if $hdlr =~ /^(vide|soun)$/; + } + + # if this atom stores offsets, save its location so we can fix up offsets later + # (are there any other atoms that may store absolute file offsets?) + if ($tag =~ /^(stco|co64|iloc|mfra|moof|sidx|saio|gps |CTBO|uuid)$/) { + # (note that we only need to do this if the media data is stored in this file) + my $flg = $$et{QtDataFlg}; + if ($tag eq 'mfra' or $tag eq 'moof') { + $et->Error("Can't yet handle movie fragments when writing"); + return $rtnVal; + } elsif ($tag eq 'sidx' or $tag eq 'saio') { + $et->Error("Can't yet handle $tag box when writing"); + return $rtnVal; + } elsif ($tag eq 'iloc') { + Handle_iloc($et, $dirInfo, \$buff, $outfile) or $et->Error('Error parsing iloc atom'); + } elsif ($tag eq 'gps ') { + # (only care about the 'gps ' box in 'moov') + if ($$dirInfo{DirID} and $$dirInfo{DirID} eq 'moov' and length $buff > 8) { + my $off = $$dirInfo{ChunkOffset}; + my $num = Get32u(\$buff, 4); + $num = int((length($buff) - 8) / 8) if $num * 8 + 8 > length($buff); + my $i; + for ($i=0; $i<$num; ++$i) { + push @$off, [ 'stco_gps ', length($$outfile) + length($hdr) + 8 + $i * 8, 4 ]; + } + } + } elsif ($tag eq 'CTBO' or $tag eq 'uuid') { # hack for updating CR3 CTBO offsets + push @{$$dirInfo{ChunkOffset}}, [ $tag, length($$outfile), length($hdr) + $size ]; + } elsif (not $flg or $flg == 1) { + # assume "1" if stsd is yet to be read + $flg or $$et{AssumedDataRef} = 1; + # must update offsets since the data is in this file + push @{$$dirInfo{ChunkOffset}}, [ $tag, length($$outfile) + length($hdr), $size ]; + } elsif ($flg == 3) { + $et->Error("Can't write files with mixed internal/external media data"); + return $rtnVal; + } + } + + # rewrite this atom + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag, \$buff); + + # call write hook if it exists + &{$$tagInfo{WriteHook}}($buff,$et) if $tagInfo and $$tagInfo{WriteHook}; + + # allow numerical tag ID's (ItemList entries defined by Keys) + if (not $tagInfo and $dirName eq 'ItemList' and $$et{Keys}) { + $keysIndex = unpack('N', $tag); + my $newIndex = $$et{Keys}{Remap}{$keysIndex}; + if (defined $newIndex) { + $tagInfo = $$et{Keys}{Info}{$keysIndex}; + unless ($newIndex) { + if ($tagInfo) { + $et->VPrint(1," - Keys:$$tagInfo{Name}"); + } else { + $delCount = ($delCount || 0) + 1; + } + ++$$et{CHANGED}; + next; + } + # use the new Keys index of this item if it changed + unless ($keysIndex == $newIndex) { + $tag = Set32u($newIndex); + substr($hdr, 4, 4) = $tag; + } + } else { + undef $keysIndex; + } + } + # delete all ItemList tags when deleting group, but take care not to delete UserData Meta + if ($delGrp) { + if ($dirName eq 'ItemList') { + $delCount = ($delCount || 0) + 1; + ++$$et{CHANGED}; + next; + } elsif ($dirName eq 'UserData' and (not $tagInfo or not $$tagInfo{SubDirectory})) { + $delCount = ($delCount || 0) + 1; + ++$$et{CHANGED}; + next; + } + } + undef $tagInfo if $tagInfo and $$tagInfo{Unknown}; + + if ($tagInfo and (not defined $$tagInfo{Writable} or $$tagInfo{Writable})) { + my $subdir = $$tagInfo{SubDirectory}; + my ($newData, @chunkOffset); + + if ($subdir) { # process atoms in this container from a buffer in memory + + if ($tag eq 'trak') { + undef $$et{HandlerType}; # init handler type for this track + delete $$et{AssumedDataRef}; + } + my $subName = $$subdir{DirName} || $$tagInfo{Name}; + my $start = $$subdir{Start} || 0; + my $base = ($$dirInfo{Base} || 0) + $raf->Tell() - $size; + my $dPos = 0; + my $hdrLen = $start; + if ($$subdir{Base}) { + my $localBase = eval $$subdir{Base}; + $dPos -= $localBase; + $base -= $dPos; + # get length of header before base offset + $hdrLen -= $localBase if $localBase <= $hdrLen; + } + my %subdirInfo = ( + Parent => $dirName, + DirName => $subName, + Name => $$tagInfo{Name}, + DirID => $tag, + DataPt => \$buff, + DataLen => $size, + DataPos => $dPos, + DirStart => $start, + DirLen => $size - $start, + Base => $base, + HasData => $$subdir{HasData}, + Multi => $$subdir{Multi}, # necessary? + OutFile => $outfile, + NoRefTest=> 1, # don't check directory references + WriteGroup => $$tagInfo{WriteGroup}, + # initialize array to hold details about chunk offset table + # (each entry has 3-5 items: 0=atom type, 1=table offset, 2=table size, + # 3=optional base offset, 4=optional item ID) + ChunkOffset => \@chunkOffset, + ); + # set InPlace flag so XMP will be padded properly when + # QuickTimePad is used if this is an XMP directory + $subdirInfo{InPlace} = 2 if $et->Options('QuickTimePad'); + # pass the header pointer if necessary (for EXIF IFD's + # where the Base offset is at the end of the header) + if ($hdrLen and $hdrLen < $size) { + my $header = substr($buff,0,$hdrLen); + $subdirInfo{HeaderPtr} = \$header; + } + SetByteOrder('II') if $$subdir{ByteOrder} and $$subdir{ByteOrder} =~ /^Little/; + my $oldWriteGroup = $$et{CUR_WRITE_GROUP}; + if ($subName eq 'Track') { + $track or $track = 0; + $$et{CUR_WRITE_GROUP} = 'Track' . (++$track); + } + my $subTable = GetTagTable($$subdir{TagTable}); + # demote non-QuickTime errors to warnings + $$et{DemoteErrors} = 1 unless $$subTable{GROUPS}{0} eq 'QuickTime'; + my $oldChanged = $$et{CHANGED}; + $newData = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc}); + if ($$et{DemoteErrors}) { + # just copy existing subdirectory if a non-quicktime error occurred + $$et{CHANGED} = $oldChanged if $$et{DemoteErrors} > 1; + delete $$et{DemoteErrors}; + } + if (defined $newData and not length $newData and ($$tagInfo{Permanent} or + ($$tagTablePtr{PERMANENT} and not defined $$tagInfo{Permanent}))) + { + # do nothing if trying to delete tag from a PERMANENT table + $$et{CHANGED} = $oldChanged; + undef $newData; + } + if ($tag eq 'trak' and $$et{AssumedDataRef}) { + my $grp = $$et{CUR_WRITE_GROUP} || $dirName; + $et->Error("Can't locate data reference to update offsets for $grp"); + delete $$et{AssumedDataRef}; + } + $$et{CUR_WRITE_GROUP} = $oldWriteGroup; + SetByteOrder('MM'); + # add back header if necessary + if ($start and defined $newData and (length $newData or + (defined $$tagInfo{Permanent} and not $$tagInfo{Permanent}))) + { + $newData = substr($buff,0,$start) . $newData; + $$_[1] += $start foreach @chunkOffset; + } + # the directory exists, so we don't need to add it + if ($curPath eq $writePath and $$addDirs{$subName} and $$addDirs{$subName} eq $dirName) { + delete $$addDirs{$subName}; + } + $didDir{$tag} = 1; # (note: keyed by tag ID) + + } else { # modify existing QuickTime tags in various formats + + my $nvHash = $et->GetNewValueHash($tagInfo); + if ($nvHash or $langTags{$tag} or $delGrp) { + my $nvHashNoLang = $nvHash; + my ($val, $len, $lang, $type, $flags, $ctry, $charsetQuickTime); + my $format = $$tagInfo{Format}; + my $hasData = ($$dirInfo{HasData} and $buff =~ /\0...data\0/s); + my $langInfo = $tagInfo; + if ($hasData) { + my $pos = 0; + for (;;$pos+=$len) { + if ($pos + 16 > $size) { + # add any new alternate language tags now + if ($langTags{$tag}) { + my $tg; + foreach $tg ('', sort keys %{$langTags{$tag}}) { + my $ti = $tg ? $langTags{$tag}{$tg} : $nvHashNoLang; + $nvHash = $et->GetNewValueHash($ti); + next unless $nvHash and not $$didTag{$nvHash}; + $$didTag{$nvHash} = 1; + next unless $$nvHash{IsCreating} and $et->IsOverwriting($nvHash); + my $newVal = $et->GetNewValue($nvHash); + next unless defined $newVal; + my $prVal = $newVal; + my $flags = FormatQTValue($et, \$newVal, $tagInfo, $format); + next unless defined $newVal; + my ($ctry, $lang) = (0, 0); + if ($$ti{LangCode}) { + unless ($$ti{LangCode} =~ /^([A-Z]{3})?[-_]?([A-Z]{2})?$/i) { + $et->Warn("Invalid language code for $$ti{Name}"); + next; + } + # pack language and country codes + if ($1 and $1 ne 'und') { + $lang = ($lang << 5) | ($_ - 0x60) foreach unpack 'C*', lc($1); + } + $ctry = unpack('n', pack('a2',uc($2))) if $2 and $2 ne 'ZZ'; + } + $newData = substr($buff, 0, $pos) unless defined $newData; + $newData .= pack('Na4Nnn',16+length($newVal),'data',$flags,$ctry,$lang).$newVal; + my $grp = $et->GetGroup($ti, 1); + $et->VerboseValue("+ $grp:$$ti{Name}", $prVal); + ++$$et{CHANGED}; + } + } + last; + } + ($len, $type, $flags, $ctry, $lang) = unpack("x${pos}Na4Nnn", $buff); + $lang or $lang = $undLang; # treat both 0 and 'und' as 'und' + $langInfo = $tagInfo; + my $delTag = $delGrp; + my $newVal; + my $langCode = GetLangCode($lang, $ctry, 1); + for (;;) { + $langInfo = GetLangInfo($tagInfo, $langCode); + $nvHash = $et->GetNewValueHash($langInfo); + last if $nvHash or not $ctry or $lang ne $undLang or length($langCode)==2; + # check to see if tag was written with a 2-char country code only + $langCode = lc unpack('a2',pack('n',$ctry)); + } + # set flag to delete language tag when writing default + # (except for a default-language Keys entry) + if (not $nvHash and $nvHashNoLang) { + if ($lang eq $undLang and not $ctry and not $$didTag{$nvHashNoLang}) { + $nvHash = $nvHashNoLang; # write existing default + } else { + $delTag = 1; # delete tag + } + } + last if $pos + $len > $size; + if ($type eq 'data' and $len >= 16) { + $pos += 16; + $len -= 16; + $val = substr($buff, $pos, $len); + # decode value (see QuickTime.pm for an explanation) + if ($stringEncoding{$flags}) { + $val = $et->Decode($val, $stringEncoding{$flags}); + $val =~ s/\0$// unless $$tagInfo{Binary}; + $flags = 0x01; # write all strings as UTF-8 + } else { + if ($format) { + # update flags for the format we are writing + if ($$tagInfo{Writable} and $qtFormat{$$tagInfo{Writable}}) { + $flags = $qtFormat{$$tagInfo{Writable}}; + } elsif ($qtFormat{$format}) { + $flags = $qtFormat{$format}; + } + } else { + $format = QuickTimeFormat($flags, $len); + } + $val = ReadValue(\$val, 0, $format, $$tagInfo{Count}, $len) if $format; + } + if (($nvHash and $et->IsOverwriting($nvHash, $val)) or $delTag) { + $newVal = $et->GetNewValue($nvHash) if defined $nvHash; + if ($delTag or not defined $newVal or $$didTag{$nvHash}) { + # delete the tag + my $grp = $et->GetGroup($langInfo, 1); + $et->VerboseValue("- $grp:$$langInfo{Name}", $val); + # copy data up to start of this tag to delete this value + $newData = substr($buff, 0, $pos-16) unless defined $newData; + ++$$et{CHANGED}; + next; + } + my $prVal = $newVal; + # format new value for writing (and get new flags) + $flags = FormatQTValue($et, \$newVal, $tagInfo, $format); + next unless defined $newVal; + my $grp = $et->GetGroup($langInfo, 1); + $et->VerboseValue("- $grp:$$langInfo{Name}", $val); + $et->VerboseValue("+ $grp:$$langInfo{Name}", $prVal); + $newData = substr($buff, 0, $pos-16) unless defined $newData; + my $wLang = $lang eq $undLang ? 0 : $lang; + $newData .= pack('Na4Nnn', length($newVal)+16, $type, $flags, $ctry, $wLang); + $newData .= $newVal; + ++$$et{CHANGED}; + } elsif (defined $newData) { + $newData .= substr($buff, $pos-16, $len+16); + } + } elsif (defined $newData) { + $newData .= substr($buff, $pos, $len); + } + $$didTag{$nvHash} = 1 if $nvHash; + } + $newData .= substr($buff, $pos) if defined $newData and $pos < $size; + undef $val; # (already constructed $newData) + } elsif ($format) { + $val = ReadValue(\$buff, 0, $format, undef, $size); + } elsif (($tag =~ /^\xa9/ or $$tagInfo{IText}) and $size >= ($$tagInfo{IText} || 4)) { + my $hdr; + if ($$tagInfo{IText} and $$tagInfo{IText} >= 6) { + my $iText = $$tagInfo{IText}; + my $pos = $iText - 2; + $lang = unpack("x${pos}n", $buff); + $hdr = substr($buff,4,$iText-6); + $len = $size - $iText; + $val = substr($buff, $iText, $len); + } else { + ($len, $lang) = unpack('nn', $buff); + $len -= 4 if 4 + $len > $size; # (see QuickTime.pm for explanation) + $len = $size - 4 if $len > $size - 4 or $len < 0; + $val = substr($buff, 4, $len); + } + $lang or $lang = $undLang; # treat both 0 and 'und' as 'und' + my $enc; + if ($lang < 0x400 and $val !~ /^\xfe\xff/) { + $charsetQuickTime = $et->Options('CharsetQuickTime'); + $enc = $charsetQuickTime; + } else { + $enc = $val=~s/^\xfe\xff// ? 'UTF16' : 'UTF8'; + } + unless ($$tagInfo{NoDecode}) { + $val = $et->Decode($val, $enc); + $val =~ s/\0+$//; # remove trailing nulls if they exist + } + $val = $hdr . $val if defined $hdr; + my $langCode = UnpackLang($lang, 1); + $langInfo = GetLangInfo($tagInfo, $langCode); + $nvHash = $et->GetNewValueHash($langInfo); + if (not $nvHash and $nvHashNoLang) { + if ($lang eq $undLang and not $$didTag{$nvHashNoLang}) { + $nvHash = $nvHashNoLang; + } elsif ($canCreate) { + # delete other languages when writing default + my $grp = $et->GetGroup($langInfo, 1); + $et->VerboseValue("- $grp:$$langInfo{Name}", $val); + ++$$et{CHANGED}; + next; + } + } + } else { + $val = $buff; + if ($tag =~ /^\xa9/ or $$tagInfo{IText}) { + $et->Warn("Corrupted $$tagInfo{Name} value"); + } + } + if ($nvHash and defined $val) { + if ($et->IsOverwriting($nvHash, $val)) { + $newData = $et->GetNewValue($nvHash); + $newData = '' unless defined $newData or $canCreate; + ++$$et{CHANGED}; + my $grp = $et->GetGroup($langInfo, 1); + $et->VerboseValue("- $grp:$$langInfo{Name}", $val); + next unless defined $newData and not $$didTag{$nvHash}; + $et->VerboseValue("+ $grp:$$langInfo{Name}", $newData); + # add back necessary header and encode as necessary + if (defined $lang) { + my $iText = $$tagInfo{IText} || 0; + my $hdr; + if ($iText > 6) { + $newData .= ' 'x($iText-6) if length($newData) < $iText-6; + $hdr = substr($newData, 0, $iText-6); + $newData = substr($newData, $iText-6); + } + unless ($$tagInfo{NoDecode}) { + $newData = $et->Encode($newData, $lang < 0x400 ? $charsetQuickTime : 'UTF8'); + } + my $wLang = $lang eq $undLang ? 0 : $lang; + if ($iText < 6) { + $newData = pack('nn', length($newData), $wLang) . $newData; + } elsif ($iText == 6) { + $newData = pack('Nn', 0, $wLang) . $newData . "\0"; + } else { + $newData = "\0\0\0\0" . $hdr . pack('n', $wLang) . $newData . "\0"; + } + } elsif (not $format or $format =~ /^string/ and + not $$tagInfo{Binary} and not $$tagInfo{ValueConv}) + { + # write all strings as UTF-8 + $newData = $et->Encode($newData, 'UTF8'); + } elsif ($format and not $$tagInfo{Binary}) { + # format new value for writing + $newData = WriteValue($newData, $format); + } + } + $$didTag{$nvHash} = 1; # set flag so we don't add this tag again + } + } + } + # write the new atom if it was modified + if (defined $newData) { + my $sizeDiff = length($buff) - length($newData); + # pad to original size if specified, otherwise give verbose message about the changed size + if ($sizeDiff > 0 and $$tagInfo{PreservePadding} and $et->Options('QuickTimePad')) { + $newData .= "\0" x $sizeDiff; + $et->VPrint(1, " ($$tagInfo{Name} padded to original size)"); + } elsif ($sizeDiff) { + $et->VPrint(1, " ($$tagInfo{Name} changed size)"); + } + my $len = length($newData) + 8; + $len > 0x7fffffff and $et->Error("$$tagInfo{Name} to large to write"), last; + # update size in ChunkOffset list for modified 'uuid' atom + $$dirInfo{ChunkOffset}[-1][2] = $len if $tag eq 'uuid'; + next unless $len > 8; # don't write empty atom header + # maintain pointer to chunk offsets if necessary + if (@chunkOffset) { + $$_[1] += 8 + length $$outfile foreach @chunkOffset; + push @{$$dirInfo{ChunkOffset}}, @chunkOffset; + } + if ($$tagInfo{WriteLast}) { + $writeLast = ($writeLast || '') . Set32u($len) . $tag . $newData; + } else { + $boxPos{$tag} = [ length($$outfile), length($newData) + 8 ]; + # write the updated directory with its atom header + Write($outfile, Set32u($len), $tag, $newData) or $rtnVal=$rtnErr, $err=1, last; + } + next; + } + } + # keep track of data references in this track + if ($tag eq 'dinf') { + $$et{QtDataRef} = [ ]; # initialize list of data references + } elsif ($parent eq 'DataInfo' and length($buff) >= 4) { + # save data reference type and version/flags + push @{$$et{QtDataRef}}, [ $tag, Get32u(\$buff,0) ]; + } elsif ($tag eq 'stsd' and length($buff) >= 8) { + my $n = Get32u(\$buff, 4); # get number of sample descriptions in table + my ($pos, $flg) = (8, 0); + my ($i, $msg); + for ($i=0; $i<$n; ++$i) { # loop through sample descriptions + $pos + 16 <= length($buff) or $msg = 'Truncated sample table', last; + my $siz = Get32u(\$buff, $pos); + $pos + $siz <= length($buff) or $msg = 'Truncated sample table', last; + my $drefIdx = Get16u(\$buff, $pos + 14); + my $drefTbl = $$et{QtDataRef}; + if (not $drefIdx) { + $flg |= 0x01; # in this file if data reference index is 0 (if like iloc) + } elsif ($drefTbl and $$drefTbl[$drefIdx-1]) { + my $dref = $$drefTbl[$drefIdx-1]; + # $flg = 0x01-in this file, 0x02-in some other file + $flg |= ($$dref[1] == 1 and $$dref[0] ne 'rsrc') ? 0x01 : 0x02; + } else { + $msg = "No data reference for sample description $i"; + last; + } + $pos += $siz; + } + if ($msg) { + # (allow empty sample description for non-audio/video handler types, eg. 'url ', 'meta') + if ($$et{HandlerType}) { + my $grp = $$et{CUR_WRITE_GROUP} || $parent; + $et->Error("$msg for $grp"); + return $rtnErr; + } + $flg = 1; # (this seems to be the case) + } + $$et{QtDataFlg} = $flg; + if ($$et{AssumedDataRef}) { + if ($flg != $$et{AssumedDataRef}) { + my $grp = $$et{CUR_WRITE_GROUP} || $parent; + $et->Error("Assumed incorrect data reference for $grp (was $flg)"); + } + delete $$et{AssumedDataRef}; + } + } + if ($tagInfo and $$tagInfo{WriteLast}) { + $writeLast = ($writeLast || '') . $hdr . $buff; + } else { + # save position of this box in the output buffer + $boxPos{$tag} = [ length($$outfile), length($hdr) + length($buff) ]; + # copy the existing atom + Write($outfile, $hdr, $buff) or $rtnVal=$rtnErr, $err=1, last; + } + } + $et->VPrint(0, " [deleting $delCount $dirName tag".($delCount==1 ? '' : 's')."]\n") if $delCount; + + $createKeys &= ~0x01 unless $$addDirs{Keys}; # (Keys may have been written) + + # add new directories/tags at this level if necessary + if ($canCreate and (exists $$et{EDIT_DIRS}{$dirName} or $createKeys)) { + # get a hash of tagInfo references to add to this directory + my $dirs = $et->GetAddDirHash($tagTablePtr, $dirName); + # make sorted list of new tags to be added + my @addTags = sort(keys(%$dirs), keys %$newTags); + my ($tag, $index); + # add Keys tags if necessary + if ($createKeys) { + if ($curPath eq 'MOV-Movie') { + # add Meta for Keys if necessary + unless ($didDir{meta}) { + $$dirs{meta} = $Image::ExifTool::QuickTime::Movie{meta}; + push @addTags, 'meta'; + } + } elsif ($curPath eq 'MOV-Movie-Meta') { + # special case for Keys Meta -- reset directories and start again + undef @addTags; + $dirs = { }; + foreach ('keys','ilst') { + next if $didDir{$_}; # don't add again + $$dirs{$_} = $Image::ExifTool::QuickTime::Meta{$_}; + push @addTags, $_; + } + } elsif ($curPath eq 'MOV-Movie-Meta-ItemList' and $$et{Keys}) { + foreach $index (sort { $a <=> $b } keys %{$$et{Keys}{Add}}) { + my $id = Set32u($index); + $$newTags{$id} = $$et{Keys}{Add}{$index}; + push @addTags, $id; + } + } else { + $dirs = $et->GetAddDirHash($tagTablePtr, $dirName); + push @addTags, sort keys %$dirs; + } + } + # (note that $tag may be a binary Keys index here) + foreach $tag (@addTags) { + my $tagInfo = $$dirs{$tag} || $$newTags{$tag}; + next if defined $$tagInfo{CanCreate} and not $$tagInfo{CanCreate}; + next if defined $$tagInfo{HandlerType} and + (not $$et{HandlerType} or $$et{HandlerType} ne $$tagInfo{HandlerType}); + my $subdir = $$tagInfo{SubDirectory}; + unless ($subdir) { + my $nvHash = $et->GetNewValueHash($tagInfo); + next unless $nvHash and not $$didTag{$nvHash}; + next unless $$nvHash{IsCreating} and $et->IsOverwriting($nvHash); + my $newVal = $et->GetNewValue($nvHash); + next unless defined $newVal; + my $prVal = $newVal; + my $flags = FormatQTValue($et, \$newVal, $tagInfo); + next unless defined $newVal; + my ($ctry, $lang) = (0, 0); + # handle alternate languages + if ($$tagInfo{LangCode}) { + $tag = substr($tag, 0, 4); # strip language code from tag ID + unless ($$tagInfo{LangCode} =~ /^([A-Z]{3})?[-_]?([A-Z]{2})?$/i) { + $et->Warn("Invalid language code for $$tagInfo{Name}"); + next; + } + # pack language and country codes + if ($1 and $1 ne 'und') { + $lang = ($lang << 5) | ($_ - 0x60) foreach unpack 'C*', lc($1); + } + $ctry = unpack('n', pack('a2',uc($2))) if $2 and $2 ne 'ZZ'; + } + if ($$dirInfo{HasData}) { + # add 'data' header + $newVal = pack('Na4Nnn',16+length($newVal),'data',$flags,$ctry,$lang).$newVal; + } elsif ($tag =~ /^\xa9/ or $$tagInfo{IText}) { + if ($ctry) { + my $grp = $et->GetGroup($tagInfo,1); + $et->Warn("Can't use country code for $grp:$$tagInfo{Name}"); + next; + } elsif ($$tagInfo{IText} and $$tagInfo{IText} >= 6) { + # add 6-byte langText header and trailing null + # (with extra junk before language code if IText > 6) + my $n = $$tagInfo{IText} - 6; + $newVal .= ' ' x $n if length($newVal) < $n; + $newVal = "\0\0\0\0" . substr($newVal,0,$n) . pack('n',0,$lang) . substr($newVal,$n) . "\0"; + } else { + # add IText header + $newVal = pack('nn',length($newVal),$lang) . $newVal; + } + } elsif ($ctry or $lang) { + my $grp = $et->GetGroup($tagInfo,1); + $et->Warn("Can't use language code for $grp:$$tagInfo{Name}"); + next; + } + if ($$tagInfo{WriteLast}) { + $writeLast = ($writeLast || '') . Set32u(8+length($newVal)) . $tag . $newVal; + } else { + $boxPos{$tag} = [ length($$outfile), 8 + length($newVal) ]; + Write($outfile, Set32u(8+length($newVal)), $tag, $newVal) or $rtnVal=$rtnErr, $err=1; + } + my $grp = $et->GetGroup($tagInfo, 1); + $et->VerboseValue("+ $grp:$$tagInfo{Name}", $prVal); + $$didTag{$nvHash} = 1; + ++$$et{CHANGED}; + next; + } + my $subName = $$subdir{DirName} || $$tagInfo{Name}; + # QuickTime hierarchy is complex, so check full directory path before adding + my $buff; + if ($createKeys and $curPath eq 'MOV-Movie' and $subName eq 'Meta') { + $et->VPrint(0, " Creating Meta with mdta Handler and Keys\n"); + # init Meta box for Keys tags with mdta Handler and empty Keys+ItemList + $buff = "\0\0\0\x20hdlr\0\0\0\0\0\0\0\0mdta\0\0\0\0\0\0\0\0\0\0\0\0" . + "\0\0\0\x10keys\0\0\0\0\0\0\0\0" . + "\0\0\0\x08ilst"; + } elsif ($createKeys and $curPath eq 'MOV-Movie-Meta') { + $buff = ($subName eq 'Keys' ? "\0\0\0\0\0\0\0\0" : ''); + } elsif ($subName eq 'Meta' and $$et{OPTIONS}{QuickTimeHandler}) { + $et->VPrint(0, " Creating Meta with mdir Handler\n"); + # init Meta box for ItemList tags with mdir Handler + $buff = "\0\0\0\x20hdlr\0\0\0\0\0\0\0\0mdir\0\0\0\0\0\0\0\0\0\0\0\0"; + } else { + next unless $curPath eq $writePath and $$addDirs{$subName} and $$addDirs{$subName} eq $dirName; + $buff = ''; # write from scratch + } + my %subdirInfo = ( + Parent => $dirName, + DirName => $subName, + DataPt => \$buff, + DirStart => 0, + HasData => $$subdir{HasData}, + OutFile => $outfile, + ChunkOffset => [ ], # (just to be safe) + WriteGroup => $$tagInfo{WriteGroup}, + ); + my $subTable = GetTagTable($$subdir{TagTable}); + my $newData = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc}); + if ($newData and length($newData) <= 0x7ffffff7) { + my $prefix = ''; + # add atom version or ID if necessary + if ($$subdir{Start}) { + if ($$subdir{Start} == 4) { + $prefix = "\0\0\0\0"; # a simple version number + } else { + # get UUID from Condition expression + my $cond = $$tagInfo{Condition}; + $prefix = eval qq("$1") if $cond and $cond =~ m{=~\s*\/\^(.*)/}; + length($prefix) == $$subdir{Start} or $et->Error('Internal UUID error'); + } + } + my $newHdr = Set32u(8+length($newData)+length($prefix)) . $tag . $prefix; + if ($$tagInfo{WriteLast}) { + $writeLast = ($writeLast || '') . $newHdr . $newData; + } else { + if ($tag eq 'uuid') { + # add offset for new uuid (needed for CR3 CTBO offsets) + my $off = $$dirInfo{ChunkOffset}; + push @$off, [ $tag, length($$outfile), length($newHdr) + length($newData) ]; + } + $boxPos{$tag} = [ length($$outfile), length($newHdr) + length($newData) ]; + Write($outfile, $newHdr, $newData) or $rtnVal=$rtnErr, $err=1; + } + } + # add only once (must delete _after_ call to WriteDirectory()) + # (Keys is a special case, and will be removed after Meta is processed) + delete $$addDirs{$subName} unless $subName eq 'Keys'; + } + } + # write HEIC metadata after top-level 'meta' box has been processed if editing this information + if ($curPath eq 'MOV-Meta' and $$et{EDIT_DIRS}{ItemInformation}) { + $$dirInfo{BoxPos} = \%boxPos; + my $mdatEdit = WriteItemInfo($et, $dirInfo, $outfile); + if ($mdatEdit) { + $et->Error('Multiple top-level Meta containers') if $$et{mdatEdit}; + $$et{mdatEdit} = $mdatEdit; + } + } + # write out any necessary terminator + Write($outfile, $term) or $rtnVal=$rtnErr, $err=1 if $term and length $$outfile; + + # delete temporary Keys variables after Meta is processed + if ($dirName eq 'Meta') { + # delete any Meta box with no useful information (ie. only 'hdlr','keys','lang','ctry') + my $isEmpty = 1; + $emptyMeta{$_} or $isEmpty = 0, last foreach keys %boxPos; + if ($isEmpty) { + $et->VPrint(0,' Deleting ' . join('+', sort map { $emptyMeta{$_} } keys %boxPos)) if %boxPos; + $$outfile = ''; + ++$$et{CHANGED}; + } + if ($curPath eq 'MOV-Movie-Meta') { + delete $$addDirs{Keys}; # prevent creation of another Meta for Keys tags + delete $$et{Keys}; + } + } + + # return now if writing subdirectory + if ($dataPt) { + $et->Error("Internal error: WriteLast not on top-level atom!\n") if $writeLast; + return $err ? undef : $$outfile; + } + + # issue minor error if we didn't find an 'mdat' atom + my $off = $$dirInfo{ChunkOffset}; + if (not @mdat) { + foreach $co (@$off) { + next if $$co[0] eq 'uuid'; + $et->Error('Media data referenced but not found'); + return $rtnVal; + } + $et->Warn('No media data', 1); + } + + # edit mdat blocks as required + # (0=old pos [0 if creating], 1=old end [0 if creating], 2=new data ref or undef to delete, + # 3=new data item id) + if ($$et{mdatEdit}) { + @mdatEdit = @{$$et{mdatEdit}}; + delete $$et{mdatEdit}; + } + foreach $edit (@mdatEdit) { + my (@thisMdat, @newMdat, $changed); + foreach $mdat (@mdat) { + # keep track of all chunks for the mdat with this header + if (length $$mdat[2]) { + push @newMdat, @thisMdat; + undef @thisMdat; + } + push @thisMdat, $mdat; + # is this edit inside this mdat chunk? + # - $$edit[0] and $$edit[1] will both be zero if we are creating a new chunk + # - $$mdat[1] is zero if mdat runs to end of file + # - $$edit[0] == $$edit[1] == $$mdat[0] if reviving a deleted chunk + # - $$mdat[5] is defined if this was a newly added/edited chunk + next if defined $$mdat[5] or $changed; # don't replace a newly added chunk + if (not $$edit[0] or # (newly created chunk) + # (edit is inside chunk) + ((($$edit[0] < $$mdat[1] or not $$mdat[1]) and $$edit[1] > $$mdat[0]) or + # (edit inserted at start or end of chunk) + ($$edit[0] == $$edit[1] and ($$edit[0] == $$mdat[0] or $$edit[0] == $$mdat[1])))) + { + if (not $$edit[0]) { + $$edit[0] = $$edit[1] = $$mdat[0]; # insert at start of this mdat + } elsif ($$edit[0] < $$mdat[0] or ($$edit[1] > $$mdat[1] and $$mdat[1])) { + $et->Error('ItemInfo runs across mdat boundary'); + return $rtnVal; + } + my $hdrChunk = $thisMdat[0]; + $hdrChunk or $et->Error('Internal error finding mdat header'), return $rtnVal; + # calculate difference in mdat size + my $diff = ($$edit[2] ? length(${$$edit[2]}) : 0) - ($$edit[1] - $$edit[0]); + # edit size of mdat in header if necessary + if ($diff) { + if (length($$hdrChunk[2]) == 8) { + my $size = Get32u(\$$hdrChunk[2], 0); + if ($size) { # (0 size = extends to end of file) + $size += $diff; + $size > 0xffffffff and $et->Error("Can't yet grow mdat across 4GB boundary"), return $rtnVal; + Set32u($size, \$$hdrChunk[2], 0); + } + } elsif (length($$hdrChunk[2]) == 16) { + my $size = Get64u(\$$hdrChunk[2], 8); + if ($size) { + $size += $diff; + Set64u($size, \$$hdrChunk[2], 8); + } + } else { + $et->Error('Internal error. Invalid mdat header'); + return $rtnVal; + } + } + $changed = 1; + # remove the edited section of this chunk (if any) and replace with new data (if any) + if ($$edit[0] > $$mdat[0]) { + push @thisMdat, [ $$edit[0], $$edit[1], '', 0, $$edit[2], $$edit[3] ] if $$edit[2]; + # add remaining data after edit (or empty stub in case it is referenced by an offset) + push @thisMdat, [ $$edit[1], $$mdat[1], '' ]; + $$mdat[1] = $$edit[0]; # now ends at start of edit + } else { + if ($$edit[2]) { + # insert the new chunk before this chunk, moving the header to the new chunk + splice @thisMdat, -1, 0, [ $$edit[0],$$edit[1],$$mdat[2],0,$$edit[2],$$edit[3] ]; + $$mdat[2] = ''; # (header was moved to new chunk) + # initialize ChunkOffset pointer if necessary + if ($$edit[3]) { + my $n = 0; + foreach $co (@$off) { + next unless defined $$co[4] and $$co[4] == $$edit[3]; + ++$n; + if ($$co[0] eq 'stco_iloc') { + Set32u($$mdat[0], $outfile, $$co[1]); + } else { + Set64u($$mdat[0], $outfile, $$co[1]); + } + } + $n == 1 or $et->Error('Internal error updating chunk offsets'); + } + } + $$mdat[0] = $$edit[1]; # remove old data + } + } + } + if ($changed) { + @mdat = ( @newMdat, @thisMdat ); + ++$$et{CHANGED}; + } else { + $et->Error('Internal error modifying mdat'); + } + } + + # determine our new mdat positions + # (0=old pos, 1=old end, 2=mdat header, 3=new pos, 4=new data ref if changed, 5=new item ID) + my $pos = length $$outfile; + foreach $mdat (@mdat) { + $pos += length $$mdat[2]; + $$mdat[3] = $pos; + $pos += $$mdat[4] ? length(${$$mdat[4]}) : $$mdat[1] - $$mdat[0]; + } + + # fix up offsets for new mdat position(s) (and uuid positions in CR3 images) + foreach $co (@$off) { + my ($type, $ptr, $len, $base, $id) = @$co; + $base = 0 unless $base; + unless ($type =~ /^(stco|co64)_?(.*)$/) { + next if $type eq 'uuid'; + $type eq 'CTBO' or $et->Error('Internal error fixing offsets'), last; + # update 'CTBO' item offsets/sizes in Canon CR3 images + $$co[2] > 12 or $et->Error('Invalid CTBO atom'), last; + @mdat or $et->Error('Missing CR3 image data'), last; + my $n = Get32u($outfile, $$co[1] + 8); + $$co[2] < $n * 20 + 12 and $et->Error('Truncated CTBO atom'), last; + my (%ctboOff, $i); + # determine uuid types, and build an offset lookup based on CTBO ID number + foreach (@$off) { + next unless $$_[0] eq 'uuid' and $$_[2] >= 24; # (ignore undersized and deleted uuid boxes) + my $pos = $$_[1]; + next if $pos + 24 > length $$outfile; # (will happen for WriteLast uuid tags) + my $siz = Get32u($outfile, $pos); # get size of uuid atom + if ($siz == 1) { # check for extended (8-byte) size + next unless $$_[2] >= 32; + $pos += 8; + } + # get CTBO entry ID based on 16-byte UUID identifier + my $id = $ctboID{substr($$outfile, $pos+8, 16)}; + $ctboOff{$id} = $_ if defined $id; + } + # calculate new offset for the first mdat (size of -1 indicates it didn't change) + $ctboOff{3} = [ 'mdat', $mdat[0][3] - length $mdat[0][2], -1 ]; + for ($i=0; $i<$n; ++$i) { + my $pos = $$co[1] + 12 + $i * 20; + my $id = Get32u($outfile, $pos); + # ignore if size is zero unless we can add this entry + # (note: can't yet add/delete PreviewImage, but leave this possibility open) + next unless Get64u($outfile, $pos + 12) or $id == 1 or $id == 2; + if (not defined $ctboOff{$id}) { + $id==1 or $id==2 or $et->Error("Can't handle CR3 CTBO ID number $id"), last; + # XMP or PreviewImage was deleted -- set offset and size to zero + $ctboOff{$id} = [ 'uuid', 0, 0 ]; + } + # update the new offset and size of this entry + Set64u($ctboOff{$id}[1], $outfile, $pos + 4); + Set64u($ctboOff{$id}[2], $outfile, $pos + 12) unless $ctboOff{$id}[2] < 0; + } + next; + } + my $siz = $1 eq 'co64' ? 8 : 4; + my ($n, $tag); + if ($2) { # is this an offset in an iloc or 'gps ' atom? + $n = 1; + $type = $1; + $tag = $2; + } else { # this is an stco or co84 atom + next if $len < 8; + $n = Get32u($outfile, $ptr + 4); # get number of entries in table + $ptr += 8; + $len -= 8; + $tag = $1; + } + my $end = $ptr + $n * $siz; + $end > $ptr + $len and $et->Error("Invalid $tag table"), return $rtnVal; + for (; $ptr<$end; $ptr+=$siz) { + my ($ok, $i); + my $val = $type eq 'co64' ? Get64u($outfile, $ptr) : Get32u($outfile, $ptr); + for ($i=0; $i<@mdat; ++$i) { + $mdat = $mdat[$i]; + my $pos = $val + $base; + if (defined $$mdat[5]) { # is this chunk associated with an item we edited? + # set offset only for the corresponding new chunk + unless (defined $id and $id == $$mdat[5]) { + # could have pointed to empty chunk before inserted chunk + next unless $pos == $$mdat[0] and $$mdat[0] != $$mdat[1]; + } + } else { + # (have seen $pos == $$mdat[1], which is a real PITA) + next unless $pos >= $$mdat[0] and ($pos <= $$mdat[1] or not $$mdat[1]); + # step to next chunk if contiguous and at the end of this one + next if $pos == $$mdat[1] and $i+1 < @mdat and $pos == $mdat[$i+1][0]; + } + $val += $$mdat[3] - $$mdat[0]; + if ($val < 0) { + $et->Error("Error fixing up $tag offset"); + return $rtnVal; + } + if ($type eq 'co64') { + Set64u($val, $outfile, $ptr); + } elsif ($val <= 0xffffffff) { + Set32u($val, $outfile, $ptr); + } else { + $et->Error("Can't yet promote $tag offset to 64 bits"); + return $rtnVal; + } + $ok = 1; + last; + } + unless ($ok) { + $et->Error("Chunk offset in $tag atom is outside media data"); + return $rtnVal; + } + } + } + + # switch back to actual output file + $outfile = $$dirInfo{OutFile}; + + # write the metadata + Write($outfile, $outBuff) or $rtnVal = 0; + + # write the media data + foreach $mdat (@mdat) { + Write($outfile, $$mdat[2]) or $rtnVal = 0; # write mdat header + if ($$mdat[4]) { + Write($outfile, ${$$mdat[4]}) or $rtnVal = 0; + } else { + $raf->Seek($$mdat[0], 0) or $et->Error('Seek error'), last; + if ($$mdat[1]) { + my $result = Image::ExifTool::CopyBlock($raf, $outfile, $$mdat[1] - $$mdat[0]); + defined $result or $rtnVal = 0, last; + $result or $et->Error("Truncated mdat atom"), last; + } else { + # mdat continues to end of file + my $buff; + while ($raf->Read($buff, 65536)) { + Write($outfile, $buff) or $rtnVal = 0, last; + } + } + } + } + + # write the stuff that must come last + Write($outfile, $writeLast) or $rtnVal = 0 if $writeLast; + + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Write QuickTime-format MOV/MP4 file +# Inputs: 0) ExifTool ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid QuickTime file, +# or -1 if a write error occurred +sub WriteMOV($$) +{ + my ($et, $dirInfo) = @_; + $et or return 1; # allow dummy access to autoload this package + my $raf = $$dirInfo{RAF} or return 0; + my ($buff, $ftype); + + # read the first atom header + return 0 unless $raf->Read($buff, 8) == 8; + my ($size, $tag) = unpack('Na4', $buff); + return 0 if $size < 8 and $size != 1; + + # validate the file format + my $tagTablePtr = GetTagTable('Image::ExifTool::QuickTime::Main'); + return 0 unless $$tagTablePtr{$tag}; + + # determine the file type (by default, assume MP4 if 'ftyp' exists + # without 'qt ' as a compatible brand, but HEIC is an exception) + if ($tag eq 'ftyp' and $size >= 12 and $size < 100000 and + $raf->Read($buff, $size-8) == $size-8 and + $buff !~ /^(....)+(qt )/s) + { + if ($buff =~ /^crx /) { + $ftype = 'CR3', + } elsif ($buff =~ /^(heic|mif1|msf1|heix|hevc|hevx|avif)/) { + $ftype = 'HEIC'; + } else { + $ftype = 'MP4'; + } + } else { + $ftype = 'MOV'; + } + $et->SetFileType($ftype); # need to set "FileType" tag for a Condition + if ($ftype eq 'HEIC') { + # EXIF is preferred in HEIC files + $et->InitWriteDirs($dirMap{$ftype}, 'EXIF', 'QuickTime'); + } else { + $et->InitWriteDirs($dirMap{$ftype}, 'XMP', 'QuickTime'); + } + $$et{DirMap} = $dirMap{$ftype}; # need access to directory map when writing + # track tags globally to avoid creating multiple tags in the case of duplicate directories + $$et{DidTag} = { }; + SetByteOrder('MM'); + $raf->Seek(0,0); + + # write the file + $$dirInfo{Parent} = ''; + $$dirInfo{DirName} = 'MOV'; + $$dirInfo{ChunkOffset} = [ ]; # (just to be safe) + return WriteQuickTime($et, $dirInfo, $tagTablePtr) ? 1 : -1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WriteQuickTime.pl - Write XMP to QuickTime (MOV and MP4) files + +=head1 SYNOPSIS + +These routines are autoloaded by Image::ExifTool::QuickTime. + +=head1 DESCRIPTION + +This file contains routines used by ExifTool to write XMP metadata to +QuickTime-based file formats like MOV and MP4. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::QuickTime(3pm)|Image::ExifTool::QuickTime>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/WriteRIFF.pl b/ExifTool/lib/Image/ExifTool/WriteRIFF.pl new file mode 100644 index 0000000..8ee62ac --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WriteRIFF.pl @@ -0,0 +1,359 @@ +#------------------------------------------------------------------------------ +# File: WriteRIFF.pl +# +# Description: Write RIFF-format files +# +# Revisions: 2020-09-26 - P. Harvey Created +# +# Notes: Currently writes only WEBP files +# +# References: https://developers.google.com/speed/webp/docs/riff_container +#------------------------------------------------------------------------------ + +package Image::ExifTool::RIFF; + +use strict; + +# map of where information is stored in WebP image +my %webpMap = ( + 'XMP ' => 'RIFF', # (the RIFF chunk name is 'XMP ') + EXIF => 'RIFF', + ICCP => 'RIFF', + XMP => 'XMP ', + IFD0 => 'EXIF', + IFD1 => 'IFD0', + ICC_Profile => 'ICCP', + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', +); + +#------------------------------------------------------------------------------ +# Write RIFF file (currently WebP-type only) +# Inputs: 0) ExifTool object ref, 1) dirInfo ref +# Returns: 1 on success, 0 if this wasn't a valid RIFF file, or -1 if +# an output file was specified and a write error occurred +sub WriteRIFF($$) +{ + my ($et, $dirInfo) = @_; + $et or return 1; # allow dummy access to autoload this package + my $outfile = $$dirInfo{OutFile}; + my $outsize = 0; + my $raf = $$dirInfo{RAF}; + my ($buff, $err, $pass, %has, %dirDat, $imageWidth, $imageHeight); + + # do this in 2 passes so we can set the size of the containing RIFF chunk + # without having to buffer the output (also to set the WebP_Flags) + for ($pass=0; ; ++$pass) { + my %doneDir; + # verify this is a valid RIFF file + return 0 unless $raf->Read($buff, 12) == 12; + return 0 unless $buff =~ /^(RIFF|RF64)....(.{4})/s; + + unless ($1 eq 'RIFF' and $2 eq 'WEBP') { + my $type = $2; + $type =~ tr/-_a-zA-Z//dc; + $et->Error("Can't currently write $1 $type files"); + return 1; + } + SetByteOrder('II'); + + # determine which directories we must write for this file type + $et->InitWriteDirs(\%webpMap); + my $addDirs = $$et{ADD_DIRS}; + my $editDirs = $$et{EDIT_DIRS}; + my ($createVP8X, $deleteVP8X); + + # write header + if ($pass) { + my $needsVP8X = ($has{ANIM} or $has{'XMP '} or $has{EXIF} or + $has{ALPH} or $has{ICCP}); + if ($has{VP8X} and not $needsVP8X and $$et{CHANGED}) { + $deleteVP8X = 1; # delete the VP8X chunk + $outsize -= 18; # account for missing VP8X + } elsif ($needsVP8X and not $has{VP8X}) { + if (defined $imageWidth) { + ++$$et{CHANGED}; + $createVP8X = 1; # add VP8X chunk + $outsize += 18; # account for VP8X size + } else { + $et->Warn('Error getting image size for required VP8X chunk'); + } + } + # finally we can set the overall RIFF chunk size: + Set32u($outsize - 8, \$buff, 4); + Write($outfile, $buff) or $err = 1; + # create VP8X chunk if necessary + if ($createVP8X) { + $et->VPrint(0," Adding required VP8X chunk (Extended WEBP)\n"); + my $flags = 0; + $flags |= 0x02 if $has{ANIM}; + $flags |= 0x04 if $has{'XMP '}; + $flags |= 0x08 if $has{EXIF}; + $flags |= 0x10 if $has{ALPH}; + $flags |= 0x20 if $has{ICCP}; + Write($outfile, 'VP8X', pack('V3v', 10, $flags, + ($imageWidth-1) | ((($imageHeight-1) & 0xff) << 24), + ($imageHeight-1) >> 8)); + # write ICCP after VP8X + Write($outfile, $dirDat{ICCP}) or $err = 1 if $dirDat{ICCP}; + } + } else { + $outsize += length $buff; + } + my $pos = 12; +# +# Read chunks in RIFF image +# + for (;;) { + my ($tag, $len); + my $num = $raf->Read($buff, 8); + if ($num < 8) { + $num and $et->Error('RIFF format error'), return 1; + # all done if we hit end of file unless we need to add EXIF or XMP + last unless $$addDirs{EXIF} or $$addDirs{'XMP '} or $$addDirs{ICCP}; + # continue to add required EXIF or XMP chunks + $num = $len = 0; + $buff = $tag = ''; + } else { + $pos += 8; + ($tag, $len) = unpack('a4V', $buff); + if ($len <= 0) { + if ($len < 0) { + $et->Error('Invalid chunk length'); + return 1; + } elsif ($tag eq "\0\0\0\0") { + # avoid reading through corrupted files filled with nulls because it takes forever + $et->Error('Encountered empty null chunk. Processing aborted'); + return 1; + } else { # (just in case a tag may have no data) + if ($pass) { + Write($outfile, $buff) or $err = 1; + } else { + $outsize += length $buff; + } + next; + } + } + } + # RIFF chunks are padded to an even number of bytes + my $len2 = $len + ($len & 0x01); + # edit/add/delete necessary metadata chunks (EXIF must come before XMP) + if ($$editDirs{$tag} or $tag eq '' or ($tag eq 'XMP ' and $$addDirs{EXIF})) { + my $handledTag; + if ($len2) { + $et->Warn("Duplicate '${tag}' chunk") if $doneDir{$tag} and not $pass; + $doneDir{$tag} = 1; + $raf->Read($buff, $len2) == $len2 or $et->Error("Truncated '${tag}' chunk"), last; + $pos += $len2; # update current position + } else { + $buff = ''; + } +# +# add/edit/delete EXIF/XMP/ICCP (note: EXIF must come before XMP, and ICCP is written elsewhere) +# + my %dirName = ( EXIF => 'IFD0', 'XMP ' => 'XMP', ICCP => 'ICC_Profile' ); + my %tblName = ( EXIF => 'Exif', 'XMP ' => 'XMP', ICCP => 'ICC_Profile' ); + my $dir; + foreach $dir ('EXIF', 'XMP ', 'ICCP' ) { + next unless $tag eq $dir or ($$addDirs{$dir} and + ($tag eq '' or ($tag eq 'XMP ' and $dir eq 'EXIF'))); + delete $$addDirs{$dir}; # (don't try to add again) + my $start; + unless ($pass) { + # write the EXIF and save the result for the next pass + my $dataPt = \$buff; + if ($tag eq 'EXIF') { + # (only need to set directory $start for EXIF) + if ($buff =~ /^Exif\0\0/) { + $et->Warn('Improper EXIF header') unless $pass; + $start = 6; + } else { + $start = 0; + } + } elsif ($dir ne $tag) { + # create from scratch + my $buf2 = ''; + $dataPt = \$buf2; + } + # write the new directory to memory + my %dirInfo = ( + DataPt => $dataPt, + DataPos => 0, # (relative to Base) + DirStart => $start, + Base => $pos - $len2, + Parent => $dir, + DirName => $dirName{$dir}, + ); + my $tagTablePtr = GetTagTable("Image::ExifTool::$tblName{$dir}::Main"); + # (override writeProc for EXIF because it has the TIFF header) + my $writeProc = $dir eq 'EXIF' ? \&Image::ExifTool::WriteTIFF : undef; + $dirDat{$dir} = $et->WriteDirectory(\%dirInfo, $tagTablePtr, $writeProc); + } + if (defined $dirDat{$dir}) { + if ($dir eq $tag) { + $handledTag = 1; # set flag indicating we edited this tag + # increment CHANGED count if we are deleting the directory + ++$$et{CHANGED} unless length $dirDat{$dir}; + } + if (length $dirDat{$dir}) { + if ($pass) { + # write metadata chunk now (but not ICCP because it was added earlier) + Write($outfile, $dirDat{$dir}) or $err = 1 unless $dir eq 'ICCP'; + } else { + # preserve (incorrect EXIF) header if it existed + my $hdr = $start ? substr($buff,0,$start) : ''; + # (don't overwrite $len here because it may be XMP length) + my $dirLen = length($dirDat{$dir}) + length($hdr); + # add chunk header and padding + my $pad = $dirLen & 0x01 ? "\0" : ''; + $dirDat{$dir} = $dir . Set32u($dirLen) . $hdr . $dirDat{$dir} . $pad; + $outsize += length($dirDat{$dir}); + $has{$dir} = 1; + } + } + } + } +# +# just copy XMP, EXIF or ICC if nothing changed +# + if (not $handledTag and length $buff) { + # write the chunk without changes + if ($pass) { + Write($outfile, $tag, Set32u($len), $buff) or $err = 1; + } else { + $outsize += 8 + length($buff); + $has{$tag} = 1; + } + } + next; + } + $pos += $len2; # set read position at end of chunk data +# +# update necessary flags in VP8X chunk +# + if ($tag eq 'VP8X') { + my $buf2; + if ($len2 < 10 or $raf->Read($buf2, $len2) != $len2) { + $et->Error('Truncated VP8X chunk'); + return 1; + } + if ($pass) { + if ($deleteVP8X) { + $et->VPrint(0," Deleting unnecessary VP8X chunk (Standard WEBP)\n"); + next; + } + # ...but first set the VP8X flags + my $flags = Get32u(\$buf2, 0); + $flags &= ~0x2c; # (reset flags for everything we can write) + $flags |= 0x04 if $has{'XMP '}; + $flags |= 0x08 if $has{EXIF}; + $flags |= 0x20 if $has{ICCP}; + Set32u($flags, \$buf2, 0); + Write($outfile, $buff, $buf2) or $err = 1; + } else { + # get the image size + $imageWidth = (Get32u(\$buf2, 4) & 0xffffff) + 1; + $imageHeight = (Get32u(\$buf2, 6) >> 8) + 1; + $outsize += 8 + $len2; + $has{$tag} = 1; + } + # write ICCP after VP8X + Write($outfile, $dirDat{ICCP}) or $err = 1 if $dirDat{ICCP}; + next; + } +# +# just copy all other chunks +# + if ($pass) { + # write chunk header (still in $buff) + Write($outfile, $buff) or $err = 1; + } else { + $outsize += length $buff; + $has{$tag} = 1; + } + unless ($pass or defined $imageWidth) { + # get WebP image size from VP8 or VP8L header + if ($tag eq 'VP8 ' and $len2 >= 16) { + $raf->Read($buff, 16) == 16 or $et->Error('Truncated VP8 chunk'), return 1; + $outsize += 16; + if ($buff =~ /^...\x9d\x01\x2a/s) { + $imageWidth = Get16u(\$buff, 6) & 0x3fff; + $imageHeight = Get16u(\$buff, 8) & 0x3fff; + } + $len2 -= 16; + } elsif ($tag eq 'VP8L' and $len2 >= 6) { + $raf->Read($buff, 6) == 6 or $et->Error('Truncated VP8L chunk'), return 1; + $outsize += 6; + if ($buff =~ /^\x2f/s) { + $imageWidth = (Get16u(\$buff, 1) & 0x3fff) + 1; + $imageHeight = ((Get32u(\$buff, 2) >> 6) & 0x3fff) + 1; + } + $len2 -= 6; + } + } + if ($pass) { + # copy the chunk data in 64k blocks + while ($len2) { + my $num = $len2; + $num = 65536 if $num > 65536; + $raf->Read($buff, $num) == $num or $et->Error('Truncated RIFF chunk'), last; + Write($outfile, $buff) or $err = 1, last; + $len2 -= $num; + } + } else { + $raf->Seek($len2, 1) or $et->Error('Seek error'), last; + $outsize += $len2; + } + } + last if $pass; + $raf->Seek(0,0) or $et->Error('Seek error'), last; + } + return $err ? -1 : 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WriteRIFF.pl - Write RIFF-format files + +=head1 SYNOPSIS + +This file is autoloaded by Image::ExifTool::RIFF. + +=head1 DESCRIPTION + +This file contains routines to write metadata to RIFF-format files. + +=head1 NOTES + +Currently writes only WebP files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://developers.google.com/speed/webp/docs/riff_container> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::Photoshop(3pm)|Image::ExifTool::RIFF>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/WriteXMP.pl b/ExifTool/lib/Image/ExifTool/WriteXMP.pl new file mode 100644 index 0000000..3d7c243 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/WriteXMP.pl @@ -0,0 +1,1648 @@ +#------------------------------------------------------------------------------ +# File: WriteXMP.pl +# +# Description: Write XMP meta information +# +# Revisions: 12/19/2004 - P. Harvey Created +#------------------------------------------------------------------------------ +package Image::ExifTool::XMP; + +use strict; +use vars qw(%specialStruct %dateTimeInfo %stdXlatNS); + +use Image::ExifTool qw(:DataAccess :Utils); + +sub CheckXMP($$$;$); +sub CaptureXMP($$$;$); +sub SetPropertyPath($$;$$$$); + +my $debug = 0; +my $numPadLines = 24; # number of blank padding lines + +# when writing extended XMP, resources bigger than this get placed in their own +# rdf:Description so they can be moved to the extended segments if necessary +my $newDescThresh = 10240; # 10 kB + +# individual resources and namespaces to place last in separate rdf:Description's +# so they can be moved to extended XMP segments if required (see Oct. 2008 XMP spec) +my %extendedRes = ( + 'photoshop:History' => 1, + 'xap:Thumbnails' => 1, + 'xmp:Thumbnails' => 1, + 'crs' => 1, + 'crss' => 1, +); + +my $rdfDesc = 'rdf:Description'; +# +# packet/xmp/rdf headers and trailers +# +my $pktOpen = "<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'?>\n"; +my $xmlOpen = "<?xml version='1.0' encoding='UTF-8'?>\n"; +my $xmpOpenPrefix = "<x:xmpmeta xmlns:x='$nsURI{x}'"; +my $rdfOpen = "<rdf:RDF xmlns:rdf='$nsURI{rdf}'>\n"; +my $rdfClose = "</rdf:RDF>\n"; +my $xmpClose = "</x:xmpmeta>\n"; +my $pktCloseW = "<?xpacket end='w'?>"; # writable by default +my $pktCloseR = "<?xpacket end='r'?>"; +my ($sp, $nl); + +#------------------------------------------------------------------------------ +# Get XMP opening tag (and set x:xmptk appropriately) +# Inputs: 0) ExifTool object ref +# Returns: x:xmpmeta opening tag +sub XMPOpen($) +{ + my $et = shift; + my $nv = $$et{NEW_VALUE}{$Image::ExifTool::XMP::x{xmptk}}; + my $tk; + if (defined $nv) { + $tk = $et->GetNewValue($nv); + $et->VerboseValue(($tk ? '+' : '-') . ' XMP-x:XMPToolkit', $tk); + ++$$et{CHANGED}; + } else { + $tk = "Image::ExifTool $Image::ExifTool::VERSION"; + } + my $str = $tk ? (" x:xmptk='" . EscapeXML($tk) . "'") : ''; + return "$xmpOpenPrefix$str>\n"; +} + +#------------------------------------------------------------------------------ +# Validate XMP packet and set read or read/write mode +# Inputs: 0) XMP data reference, 1) 'r' = read only, 'w' or undef = read/write +# Returns: true if XMP is good (and adds packet header/trailer if necessary) +sub ValidateXMP($;$) +{ + my ($xmpPt, $mode) = @_; + $$xmpPt =~ s/^\s*<!--.*?-->\s*//s; # remove leading comment if it exists + unless ($$xmpPt =~ /^\0*<\0*\?\0*x\0*p\0*a\0*c\0*k\0*e\0*t/) { + return '' unless $$xmpPt =~ /^<x(mp)?:x[ma]pmeta/; + # add required xpacket header/trailer + $$xmpPt = $pktOpen . $$xmpPt . $pktCloseW; + } + $mode = 'w' unless $mode; + my $end = substr($$xmpPt, -32, 32); + # check for proper xpacket trailer and set r/w mode if necessary + return '' unless $end =~ s/(e\0*n\0*d\0*=\0*['"]\0*)([rw])(\0*['"]\0*\?\0*>)/$1$mode$3/; + substr($$xmpPt, -32, 32) = $end if $2 ne $mode; + return 1; +} + +#------------------------------------------------------------------------------ +# Validate XMP property +# Inputs: 0) ExifTool ref, 1) validate hash ref, 2) attribute hash ref +# - issues warnings if problems detected +sub ValidateProperty($$;$) +{ + my ($et, $propList, $attr) = @_; + + if ($$et{XmpValidate} and @$propList > 2) { + if ($$propList[0] =~ /^x:x[ma]pmeta$/ and + $$propList[1] eq 'rdf:RDF' and + $$propList[2] =~ /rdf:Description( |$)/) + { + if (@$propList > 3) { + if ($$propList[-1] =~ /^rdf:(Bag|Seq|Alt)$/) { + $et->Warn("Ignored empty $$propList[-1] list for $$propList[-2]", 1); + } else { + if ($$propList[-2] eq 'rdf:Alt' and $attr) { + my $lang = $$attr{'xml:lang'}; + if ($lang and @$propList >= 5) { + my $langPath = join('/', @$propList[3..($#$propList-2)]); + my $valLang = $$et{XmpValidateLangAlt} || ($$et{XmpValidateLangAlt} = { }); + $$valLang{$langPath} or $$valLang{$langPath} = { }; + if ($$valLang{$langPath}{$lang}) { + $et->WarnOnce("Duplicate language ($lang) in lang-alt list: $langPath"); + } else { + $$valLang{$langPath}{$lang} = 1; + } + } + } + my $xmpValidate = $$et{XmpValidate}; + my $path = join('/', @$propList[3..$#$propList]); + if (defined $$xmpValidate{$path}) { + $et->Warn("Duplicate XMP property: $path"); + } else { + $$xmpValidate{$path} = 1; + } + } + } + } elsif ($$propList[0] ne 'rdf:RDF' or + $$propList[1] !~ /rdf:Description( |$)/) + { + $et->Warn('Improperly enclosed XMP property: ' . join('/',@$propList)); + } + } +} + +#------------------------------------------------------------------------------ +# Check XMP date values for validity and format accordingly +# Inputs: 1) EXIF-format date string +# Returns: XMP date/time string (or undef on error) +sub FormatXMPDate($) +{ + my $val = shift; + my ($y, $m, $d, $t, $tz); + if ($val =~ /(\d{4}):(\d{2}):(\d{2}) (\d{2}:\d{2}(?::\d{2}(?:\.\d*)?)?)(.*)/) { + ($y, $m, $d, $t, $tz) = ($1, $2, $3, $4, $5); + $val = "$y-$m-${d}T$t"; + } elsif ($val =~ /^\s*\d{4}(:\d{2}){0,2}\s*$/) { + # this is just a date (YYYY, YYYY-mm or YYYY-mm-dd) + $val =~ tr/:/-/; + } elsif ($val =~ /^\s*(\d{2}:\d{2}(?::\d{2}(?:\.\d*)?)?)(.*)\s*$/) { + # this is just a time + ($t, $tz) = ($1, $2); + $val = $t; + } else { + return undef; + } + if ($tz) { + $tz =~ /^(Z|[+-]\d{2}:\d{2})$/ or return undef; + $val .= $tz; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Check XMP values for validity and format accordingly +# Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref, 3) conversion type +# Returns: error string or undef (and may change value) on success +# Note: copies structured information to avoid conflicts with calling code +sub CheckXMP($$$;$) +{ + my ($et, $tagInfo, $valPtr, $convType) = @_; + + if ($$tagInfo{Struct}) { + require 'Image/ExifTool/XMPStruct.pl'; + my ($item, $err, $w, $warn); + unless (ref $$valPtr) { + ($$valPtr, $warn) = InflateStruct($et, $valPtr); + # expect a structure HASH ref or ARRAY of structures + unless (ref $$valPtr) { + $$valPtr eq '' and $$valPtr = { }, return undef; # allow empty structures + return 'Improperly formed structure'; + } + } + if (ref $$valPtr eq 'ARRAY') { + return 'Not a list tag' unless $$tagInfo{List}; + my @copy = ( @{$$valPtr} ); # copy the list for ExifTool to use + $$valPtr = \@copy; # return the copy + foreach $item (@copy) { + unless (ref $item eq 'HASH') { + ($item, $w) = InflateStruct($et, \$item); # deserialize structure + $w and $warn = $w; + next if ref $item eq 'HASH'; + $err = 'Improperly formed structure'; + last; + } + ($item, $err) = CheckStruct($et, $item, $$tagInfo{Struct}); + last if $err; + } + } else { + ($$valPtr, $err) = CheckStruct($et, $$valPtr, $$tagInfo{Struct}); + } + $warn and $$et{CHECK_WARN} = $warn; + return $err; + } + my $format = $$tagInfo{Writable}; + # (if no format specified, value is a simple string) + if (not $format or $format eq 'string' or $format eq 'lang-alt') { + # convert value to UTF8 if necessary + if ($$et{OPTIONS}{Charset} ne 'UTF8') { + if ($$valPtr =~ /[\x80-\xff]/) { + # convert from Charset to UTF-8 + $$valPtr = $et->Encode($$valPtr,'UTF8'); + } + } else { + # translate invalid XML characters to "." + $$valPtr =~ tr/\0-\x08\x0b\x0c\x0e-\x1f/./; + # fix any malformed UTF-8 characters + if (FixUTF8($valPtr) and not $$et{WarnBadUTF8}) { + $et->Warn('Malformed UTF-8 character(s)'); + $$et{WarnBadUTF8} = 1; + } + } + return undef; # success + } + if ($format eq 'rational' or $format eq 'real') { + # make sure the value is a valid floating point number + unless (Image::ExifTool::IsFloat($$valPtr) or + # allow 'inf' and 'undef' rational values + ($format eq 'rational' and ($$valPtr eq 'inf' or + $$valPtr eq 'undef' or Image::ExifTool::IsRational($$valPtr)))) + { + return 'Not a floating point number'; + } + if ($format eq 'rational') { + $$valPtr = join('/', Image::ExifTool::Rationalize($$valPtr)); + } + } elsif ($format eq 'integer') { + # make sure the value is integer + if (Image::ExifTool::IsInt($$valPtr)) { + # no conversion required (converting to 'int' would remove leading '+') + } elsif (Image::ExifTool::IsHex($$valPtr)) { + $$valPtr = hex($$valPtr); + } else { + return 'Not an integer'; + } + } elsif ($format eq 'date') { + my $newDate = FormatXMPDate($$valPtr); + return "Invalid date/time (use YYYY:mm:dd HH:MM:SS[.ss][+/-HH:MM|Z])" unless $newDate; + $$valPtr = $newDate; + } elsif ($format eq 'boolean') { + # (allow lower-case 'true' and 'false' if not setting PrintConv value) + if (not $$valPtr or $$valPtr =~ /false/i or $$valPtr =~ /^no$/i) { + if (not $$valPtr or $$valPtr ne 'false' or not $convType or $convType eq 'PrintConv') { + $$valPtr = 'False'; + } + } elsif ($$valPtr ne 'true' or not $convType or $convType eq 'PrintConv') { + $$valPtr = 'True'; + } + } elsif ($format eq '1') { + # this is the entire XMP data block + return 'Invalid XMP data' unless ValidateXMP($valPtr); + } else { + return "Unknown XMP format: $format"; + } + return undef; # success! +} + +#------------------------------------------------------------------------------ +# Get PropertyPath for specified tagInfo +# Inputs: 0) tagInfo reference +# Returns: PropertyPath string +sub GetPropertyPath($) +{ + my $tagInfo = shift; + SetPropertyPath($$tagInfo{Table}, $$tagInfo{TagID}) unless $$tagInfo{PropertyPath}; + return $$tagInfo{PropertyPath}; +} + +#------------------------------------------------------------------------------ +# Set PropertyPath for specified tag (also for associated flattened tags and structure elements) +# Inputs: 0) tagTable reference, 1) tagID, 2) tagID of parent structure, +# 3) structure definition ref (or undef), 4) property list up to this point (or undef), +# 5) flag set if any containing structure has a TYPE +# Notes: also generates flattened tags if they don't already exist +sub SetPropertyPath($$;$$$$) +{ + my ($tagTablePtr, $tagID, $parentID, $structPtr, $propList, $isType) = @_; + my $table = $structPtr || $tagTablePtr; + my $tagInfo = $$table{$tagID}; + my $flatInfo; + + return if ref($tagInfo) ne 'HASH'; # (shouldn't happen) + + if ($structPtr) { + my $flatID = $parentID . ucfirst($tagID); + $flatInfo = $$tagTablePtr{$flatID}; + if ($flatInfo) { + return if $$flatInfo{PropertyPath}; + } elsif (@$propList > 50) { + return; # avoid deep recursion + } else { + # flattened tag doesn't exist, so create it now + # (could happen if we were just writing a structure) + $flatInfo = { Name => ucfirst($flatID), Flat => 1 }; + AddTagToTable($tagTablePtr, $flatID, $flatInfo); + } + $isType = 1 if $$structPtr{TYPE}; + } else { + # don't override existing main table entry if already set by a Struct + return if $$tagInfo{PropertyPath}; + # use property path from original tagInfo if this is an alternate-language tag + my $srcInfo = $$tagInfo{SrcTagInfo}; + $$tagInfo{PropertyPath} = GetPropertyPath($srcInfo) if $srcInfo; + return if $$tagInfo{PropertyPath}; + # set property path for all flattened tags in structure if necessary + if ($$tagInfo{RootTagInfo}) { + SetPropertyPath($tagTablePtr, $$tagInfo{RootTagInfo}{TagID}); + return if $$tagInfo{PropertyPath}; + warn "Internal Error: Didn't set path from root for $tagID\n"; + warn "(Is the Struct NAMESPACE defined?)\n"; + } + } + my $ns = $$tagInfo{Namespace} || $$table{NAMESPACE}; + $ns or warn("No namespace for $tagID\n"), return; + my (@propList, $listType); + $propList and @propList = @$propList; + push @propList, "$ns:$tagID"; + # lang-alt lists are handled specially, signified by Writable='lang-alt' + if ($$tagInfo{Writable} and $$tagInfo{Writable} eq 'lang-alt') { + $listType = 'Alt'; + # remove language code from property path if it exists + $propList[-1] =~ s/-$$tagInfo{LangCode}$// if $$tagInfo{LangCode}; + # handle lists of lang-alt lists (eg. XMP-plus:Custom tags) + if ($$tagInfo{List} and $$tagInfo{List} ne '1') { + push @propList, "rdf:$$tagInfo{List}", 'rdf:li 10'; + } + } else { + $listType = $$tagInfo{List}; + } + # add required properties if this is a list + push @propList, "rdf:$listType", 'rdf:li 10' if $listType and $listType ne '1'; + # set PropertyPath for all flattened tags of this structure if necessary + my $strTable = $$tagInfo{Struct}; + if ($strTable and not ($parentID and + # must test NoSubStruct flag to avoid infinite recursion + (($$tagTablePtr{$parentID} and $$tagTablePtr{$parentID}{NoSubStruct}) or + length $parentID > 500))) # avoid deep recursion + { + # make sure the structure namespace has been registered + # (user-defined namespaces may not have been) + RegisterNamespace($strTable) if ref $$strTable{NAMESPACE}; + my $tag; + foreach $tag (keys %$strTable) { + # ignore special fields and any lang-alt fields we may have added + next if $specialStruct{$tag} or $$strTable{$tag}{LangCode}; + my $fullID = $parentID ? $parentID . ucfirst($tagID) : $tagID; + SetPropertyPath($tagTablePtr, $tag, $fullID, $strTable, \@propList, $isType); + } + } + # if this was a structure field and not a normal tag, + # we set PropertyPath in the corresponding flattened tag + if ($structPtr) { + $tagInfo = $flatInfo; + # set StructType flag if any containing structure has a TYPE + $$tagInfo{StructType} = 1 if $isType; + } + # set property path for tagInfo in main table + $$tagInfo{PropertyPath} = join '/', @propList; +} + +#------------------------------------------------------------------------------ +# Save XMP property name/value for rewriting +# Inputs: 0) ExifTool object reference +# 1) reference to array of XMP property path (last is current property) +# 2) property value, 3) optional reference to hash of property attributes +sub CaptureXMP($$$;$) +{ + my ($et, $propList, $val, $attrs) = @_; + return unless defined $val and @$propList > 2; + if ($$propList[0] =~ /^x:x[ma]pmeta$/ and + $$propList[1] eq 'rdf:RDF' and + $$propList[2] =~ /$rdfDesc( |$)/) + { + # no properties to save yet if this is just the description + return unless @$propList > 3; + # ignore empty list properties + if ($$propList[-1] =~ /^rdf:(Bag|Seq|Alt)$/) { + $et->Warn("Ignored empty $$propList[-1] list for $$propList[-2]", 1); + return; + } + # save information about this property + my $capture = $$et{XMP_CAPTURE}; + my $path = join('/', @$propList[3..$#$propList]); + if (defined $$capture{$path}) { + $$et{XMP_ERROR} = "Duplicate XMP property: $path"; + } else { + $$capture{$path} = [$val, $attrs || { }]; + } + } elsif ($$propList[0] eq 'rdf:RDF' and + $$propList[1] =~ /$rdfDesc( |$)/) + { + # set flag so we don't write x:xmpmeta element + $$et{XMP_NO_XMPMETA} = 1; + # add missing x:xmpmeta element and try again + unshift @$propList, 'x:xmpmeta'; + CaptureXMP($et, $propList, $val, $attrs); + } else { + $$et{XMP_ERROR} = 'Improperly enclosed XMP property: ' . join('/',@$propList); + } +} + +#------------------------------------------------------------------------------ +# Save information about resource containing blank node with nodeID +# Inputs: 0) reference to blank node information hash +# 1) reference to property list +# 2) property value +# 3) [optional] reference to attribute hash +# Notes: This routine and ProcessBlankInfo() are also used for reading information, but +# are uncommon so are put in this file to reduce compile time for the common case +sub SaveBlankInfo($$$;$) +{ + my ($blankInfo, $propListPt, $val, $attrs) = @_; + + my $propPath = join '/', @$propListPt; + my @ids = ($propPath =~ m{ #([^ /]*)}g); + my $id; + # split the property path at each nodeID + foreach $id (@ids) { + my ($pre, $prop, $post) = ($propPath =~ m{^(.*?)/([^/]*) #$id((/.*)?)$}); + defined $pre or warn("internal error parsing nodeID's"), next; + # the element with the nodeID should be in the path prefix for subject + # nodes and the path suffix for object nodes + unless ($prop eq $rdfDesc) { + if ($post) { + $post = "/$prop$post"; + } else { + $pre = "$pre/$prop"; + } + } + $$blankInfo{Prop}{$id}{Pre}{$pre} = 1; + if ((defined $post and length $post) or (defined $val and length $val)) { + # save the property value and attributes for each unique path suffix + $$blankInfo{Prop}{$id}{Post}{$post} = [ $val, $attrs, $propPath ]; + } + } +} + +#------------------------------------------------------------------------------ +# Process blank-node information +# Inputs: 0) ExifTool object ref, 1) tag table ref, +# 2) blank node information hash ref, 3) flag set for writing +sub ProcessBlankInfo($$$;$) +{ + my ($et, $tagTablePtr, $blankInfo, $isWriting) = @_; + $et->VPrint(1, " [Elements with nodeID set:]\n") unless $isWriting; + my ($id, $pre, $post); + # handle each nodeID separately + foreach $id (sort keys %{$$blankInfo{Prop}}) { + my $path = $$blankInfo{Prop}{$id}; + # flag all resource names so we can warn later if some are unused + my %unused; + foreach $post (keys %{$$path{Post}}) { + $unused{$post} = 1; + } + # combine property paths for all possible paths through this node + foreach $pre (sort keys %{$$path{Pre}}) { + # there will be no description for the object of a blank node + next unless $pre =~ m{/$rdfDesc/}; + foreach $post (sort keys %{$$path{Post}}) { + my @propList = split m{/}, "$pre$post"; + my ($val, $attrs) = @{$$path{Post}{$post}}; + if ($isWriting) { + CaptureXMP($et, \@propList, $val, $attrs); + } else { + FoundXMP($et, $tagTablePtr, \@propList, $val); + } + delete $unused{$post}; + } + } + # save information from unused properties (if RDF is malformed like f-spot output) + if (%unused) { + $et->Options('Verbose') and $et->Warn('An XMP resource is about nothing'); + foreach $post (sort keys %unused) { + my ($val, $attrs, $propPath) = @{$$path{Post}{$post}}; + my @propList = split m{/}, $propPath; + if ($isWriting) { + CaptureXMP($et, \@propList, $val, $attrs); + } else { + FoundXMP($et, $tagTablePtr, \@propList, $val); + } + } + } + } +} + +#------------------------------------------------------------------------------ +# Convert path to namespace used in file (this is a pain, but the XMP +# spec only suggests 'preferred' namespace prefixes...) +# Inputs: 0) ExifTool object reference, 1) property path +# Returns: conforming property path +sub ConformPathToNamespace($$) +{ + my ($et, $path) = @_; + my @propList = split('/',$path); + my $nsUsed = $$et{XMP_NS}; + my $prop; + foreach $prop (@propList) { + my ($ns, $tag) = $prop =~ /(.+?):(.*)/; + next if not defined $ns or $$nsUsed{$ns}; + my $uri = $nsURI{$ns}; + unless ($uri) { + warn "No URI for namespace prefix $ns!\n"; + next; + } + my $ns2; + foreach $ns2 (keys %$nsUsed) { + next unless $$nsUsed{$ns2} eq $uri; + # use the existing namespace prefix instead of ours + $prop = "$ns2:$tag"; + last; + } + } + return join('/',@propList); +} + +#------------------------------------------------------------------------------ +# Add necessary rdf:type element when writing structure +# Inputs: 0) ExifTool ref, 1) tag table ref, 2) capture hash ref, 3) path string +# 4) optional base path (already conformed to namespace) for elements in +# variable-namespace structures +sub AddStructType($$$$;$) +{ + my ($et, $tagTablePtr, $capture, $path, $basePath) = @_; + my @props = split '/', $path; + my %doneID; + for (;;) { + pop @props; + last unless @props; + my $tagID = GetXMPTagID(\@props); + next if $doneID{$tagID}; + $doneID{$tagID} = 1; + my $tagInfo = $$tagTablePtr{$tagID}; + last unless ref $tagInfo eq 'HASH'; + if ($$tagInfo{Struct}) { + my $type = $$tagInfo{Struct}{TYPE}; + if ($type) { + my $pat = $$tagInfo{PropertyPath}; + $pat or warn("Missing PropertyPath in AddStructType\n"), last; + $pat = ConformPathToNamespace($et, $pat); + $pat =~ s/ \d+/ \\d\+/g; + $path =~ /^($pat)/ or warn("Wrong path in AddStructType\n"), last; + my $p = $1 . '/rdf:type'; + $p = "$basePath/$p" if $basePath; + $$capture{$p} = [ '', { 'rdf:resource' => $type } ] unless $$capture{$p}; + } + } + last unless $$tagInfo{StructType}; + } +} + +#------------------------------------------------------------------------------ +# Process SphericalVideoXML (see XMP-GSpherical tags documentation) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: SphericalVideoXML data +sub ProcessGSpherical($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + # extract SphericalVideoXML as a block if requested + if ($$et{REQ_TAG_LOOKUP}{sphericalvideoxml}) { + $et->FoundTag(SphericalVideoXML => substr(${$$dirInfo{DataPt}}, 16)); + } + return Image::ExifTool::XMP::ProcessXMP($et, $dirInfo, $tagTablePtr); +} + +#------------------------------------------------------------------------------ +# Hack to use XMP writer for SphericalVideoXML (see XMP-GSpherical tags documentation) +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: SphericalVideoXML data +sub WriteGSpherical($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $$dirInfo{Compact} = 1, + my $dataPt = $$dirInfo{DataPt}; + if ($dataPt and $$dataPt) { + # make it look like XMP for writing + my $buff = $$dataPt; + $buff =~ s/<rdf:SphericalVideo/<?xpacket begin='.*?' id='W5M0MpCehiHzreSzNTczkc9d'?>\n<x:xmpmeta xmlns:x='adobe:ns:meta\/'><rdf:RDF/; + $buff =~ s/\s*xmlns:GSpherical/>\n<rdf:Description xmlns:GSpherical/s; + $buff =~ s/<\/rdf:SphericalVideo>/<\/rdf:Description>/; + $buff .= "</rdf:RDF></x:xmpmeta><?xpacket end='w'?>"; + $$dirInfo{DataPt} = \$buff; + $$dirInfo{DirLen} = length($buff) - ($$dirInfo{DirStart} || 0); + } + my $xmp = Image::ExifTool::XMP::WriteXMP($et, $dirInfo, $tagTablePtr); + if ($xmp) { + # change back to rdf:SphericalVideo structure + $xmp =~ s/^<\?xpacket begin.*?<rdf:RDF/<rdf:SphericalVideo\n/s; + $xmp =~ s/>\s*<rdf:Description rdf:about=''\s*/\n /; + $xmp =~ s/\s*<\/rdf:Description>\s*(<\/rdf:RDF>)/\n<\/rdf:SphericalVideo>$1/s; + $xmp =~ s/\s*<\/rdf:RDF>\s*<\/x:xmpmeta>.*//s; + } + return $xmp; +} + +#------------------------------------------------------------------------------ +# Utility routine to encode data in base64 +# Inputs: 0) binary data string, 1) flag to avoid inserting newlines +# Returns: base64-encoded string +sub EncodeBase64($;$) +{ + # encode the data in 45-byte chunks + my $chunkSize = 45; + my $len = length $_[0]; + my $str = ''; + my $i; + for ($i=0; $i<$len; $i+=$chunkSize) { + my $n = $len - $i; + $n = $chunkSize if $n > $chunkSize; + # add uuencoded data to output (minus size byte, but including trailing newline) + $str .= substr(pack('u', substr($_[0], $i, $n)), 1); + } + # convert to base64 (remember that "\0" may be encoded as ' ' or '`') + $str =~ tr/` -_/AA-Za-z0-9+\//; + # convert pad characters at the end (remember to account for trailing newline) + my $pad = 3 - ($len % 3); + substr($str, -$pad-1, $pad) = ('=' x $pad) if $pad < 3; + $str =~ tr/\n//d if $_[1]; # remove newlines if specified + return $str; +} + +#------------------------------------------------------------------------------ +# sort tagInfo hash references by tag name +sub ByTagName +{ + return $$a{Name} cmp $$b{Name}; +} + +#------------------------------------------------------------------------------ +# sort alphabetically, but with rdf:type first in the structure +sub TypeFirst +{ + if ($a =~ /rdf:type$/) { + return substr($a, 0, -8) cmp $b unless $b =~ /rdf:type$/; + } elsif ($b =~ /rdf:type$/) { + return $a cmp substr($b, 0, -8); + } + return $a cmp $b; +} + +#------------------------------------------------------------------------------ +# Limit size of XMP +# Inputs: 0) ExifTool object ref, 1) XMP data ref (written up to start of $rdfClose), +# 2) max XMP len, 3) rdf:about string, 4) list ref for description start offsets +# 5) start offset of first description recommended for extended XMP +# Returns: 0) extended XMP ref, 1) GUID and updates $$dataPt (or undef if no extended XMP) +sub LimitXMPSize($$$$$$) +{ + my ($et, $dataPt, $maxLen, $about, $startPt, $extStart) = @_; + + # return straight away if it isn't too big + return undef if length($$dataPt) < $maxLen; + + push @$startPt, length($$dataPt); # add end offset to list + my $newData = substr($$dataPt, 0, $$startPt[0]); + my $guid = '0' x 32; + # write the required xmpNote:HasExtendedXMP property + $newData .= "$nl$sp<$rdfDesc rdf:about='${about}'\n$sp${sp}xmlns:xmpNote='$nsURI{xmpNote}'"; + if ($$et{OPTIONS}{Compact}{Shorthand}) { + $newData .= "\n$sp${sp}xmpNote:HasExtendedXMP='${guid}'/>\n"; + } else { + $newData .= ">$nl$sp$sp<xmpNote:HasExtendedXMP>$guid</xmpNote:HasExtendedXMP>$nl$sp</$rdfDesc>\n"; + } + + my ($i, %descSize, $start); + # calculate all description block sizes + for ($i=1; $i<@$startPt; ++$i) { + $descSize{$$startPt[$i-1]} = $$startPt[$i] - $$startPt[$i-1]; + } + pop @$startPt; # remove end offset + # write the descriptions from smallest to largest, as many in main XMP as possible + my @descStart = sort { $descSize{$a} <=> $descSize{$b} } @$startPt; + my $extData = XMPOpen($et) . $rdfOpen; + for ($i=0; $i<2; ++$i) { + foreach $start (@descStart) { + # write main XMP first (in order of size), then extended XMP afterwards (in order) + next if $i xor $start >= $extStart; + my $pt = (length($newData) + $descSize{$start} > $maxLen) ? \$extData : \$newData; + $$pt .= substr($$dataPt, $start, $descSize{$start}); + } + } + $extData .= $rdfClose . $xmpClose; # close rdf:RDF and x:xmpmeta + # calculate GUID from MD5 of extended XMP data + if (eval { require Digest::MD5 }) { + $guid = uc unpack('H*', Digest::MD5::md5($extData)); + $newData =~ s/0{32}/$guid/; # update GUID in main XMP segment + } + $et->VerboseValue('+ XMP-xmpNote:HasExtendedXMP', $guid); + $$dataPt = $newData; # return main XMP block + return (\$extData, $guid); # return extended XMP and its GUID +} + +#------------------------------------------------------------------------------ +# Close out bottom-level property +# Inputs: 0) current property path list ref, 1) longhand properties at each resource +# level, 2) shorthand properties at each resource level, 3) resource flag for +# each property path level (set only if Shorthand is enabled) +sub CloseProperty($$$$) +{ + my ($curPropList, $long, $short, $resFlag) = @_; + + my $prop = pop @$curPropList; + $prop =~ s/ .*//; # remove list index if it exists + my $pad = $sp x (scalar(@$curPropList) + 1); + if ($$resFlag[@$curPropList]) { + # close this XMP structure with possible shorthand properties + if (length $$short[-1]) { + if (length $$long[-1]) { + # require a new Description if both longhand and shorthand properties + $$long[-2] .= ">$nl$pad<$rdfDesc"; + $$short[-1] .= ">$nl"; + $$long[-1] .= "$pad</$rdfDesc>$nl"; + } else { + # simply close empty property if all shorthand + $$short[-1] .= "/>$nl"; + } + } else { + # use "parseType" instead of opening a new Description + $$long[-2] .= ' rdf:parseType="Resource"'; + $$short[-1] = length $$long[-1] ? ">$nl" : "/>$nl"; + } + $$long[-1] .= "$pad</$prop>$nl" if length $$long[-1]; + $$long[-2] .= $$short[-1] . $$long[-1]; + pop @$short; + pop @$long; + } elsif (defined $$resFlag[@$curPropList]) { + # close this top level Description with possible shorthand values + if (length $$long[-1]) { + $$long[-2] .= $$short[-1] . ">$nl" . $$long[-1] . "$pad</$prop>$nl"; + } else { + $$long[-2] .= $$short[-1] . "/>$nl"; # empty element (ie. all shorthand) + } + $$short[-1] = $$long[-1] = ''; + } else { + # close this property (no chance of shorthand) + $$long[-1] .= "$pad</$prop>$nl"; + unless (@$curPropList) { + # add properties now that this top-level Description is complete + $$long[-2] .= ">$nl" . $$long[-1]; + $$long[-1] = ''; + } + } + $#$resFlag = $#$curPropList; # remove expired resource flags +} + +#------------------------------------------------------------------------------ +# Write XMP information +# Inputs: 0) ExifTool ref, 1) source dirInfo ref (with optional WriteGroup), +# 2) [optional] tag table ref +# Returns: with tag table: new XMP data (may be empty if no XMP data) or undef on error +# without tag table: 1 on success, 0 if not valid XMP file, -1 on write error +# Notes: May set dirInfo InPlace flag to rewrite with specified DirLen (=2 to allow larger) +# May set dirInfo ReadOnly flag to write as read-only XMP ('r' mode and no padding) +# May set dirInfo Compact flag to force compact (drops 2kB of padding) +# May set dirInfo MaxDataLen to limit output data length -- this causes ExtendedXMP +# and ExtendedGUID to be returned in dirInfo if extended XMP was required +sub WriteXMP($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + $et or return 1; # allow dummy access to autoload this package + my $dataPt = $$dirInfo{DataPt}; + my (%capture, %nsUsed, $xmpErr, $about); + my $changed = 0; + my $xmpFile = (not $tagTablePtr); # this is an XMP data file if no $tagTablePtr + # prefer XMP over other metadata formats in some types of files + my $preferred = $xmpFile || ($$et{PreferredGroup} and $$et{PreferredGroup} eq 'XMP'); + my $verbose = $$et{OPTIONS}{Verbose}; + my %compact = ( %{$$et{OPTIONS}{Compact}} ); # (make a copy so we can change settings) + my $dirLen = $$dirInfo{DirLen}; + $dirLen = length($$dataPt) if not defined $dirLen and $dataPt; +# +# extract existing XMP information into %capture hash +# + # define hash in ExifTool object to capture XMP information (also causes + # CaptureXMP() instead of FoundXMP() to be called from ParseXMPElement()) + # + # The %capture hash is keyed on the complete property path beginning after + # rdf:RDF/rdf:Description/. The values are array references with the + # following entries: 0) value, 1) attribute hash reference. + $$et{XMP_CAPTURE} = \%capture; + $$et{XMP_NS} = \%nsUsed; + delete $$et{XMP_NO_XMPMETA}; + delete $$et{XMP_NO_XPACKET}; + delete $$et{XMP_IS_XML}; + delete $$et{XMP_IS_SVG}; + + # set current padding characters + ($sp, $nl) = ($compact{NoIndent} ? '' : ' ', $compact{NoNewline} ? '' : "\n"); + + # get value for new rdf:about + my $tagInfo = $Image::ExifTool::XMP::rdf{about}; + if (defined $$et{NEW_VALUE}{$tagInfo}) { + $about = $et->GetNewValue($$et{NEW_VALUE}{$tagInfo}) || ''; + } + + if ($xmpFile or $dirLen) { + delete $$et{XMP_ERROR}; + # extract all existing XMP information (to the XMP_CAPTURE hash) + my $success = ProcessXMP($et, $dirInfo, $tagTablePtr); + # don't continue if there is nothing to parse or if we had a parsing error + unless ($success and not $$et{XMP_ERROR}) { + my $err = $$et{XMP_ERROR} || 'Error parsing XMP'; + # may ignore this error only if we were successful + if ($xmpFile) { + my $raf = $$dirInfo{RAF}; + # allow empty XMP data so we can create something from nothing + if ($success or not $raf->Seek(0,2) or $raf->Tell()) { + # no error message if not an XMP file + return 0 unless $$et{XMP_ERROR}; + if ($et->Error($err, $success)) { + delete $$et{XMP_CAPTURE}; + return 0; + } + } + } else { + $success = 2 if $success and $success eq '1'; + if ($et->Warn($err, $success)) { + delete $$et{XMP_CAPTURE}; + return undef; + } + } + } + if (defined $about) { + if ($verbose > 1) { + my $wasAbout = $$et{XmpAbout}; + $et->VerboseValue('- XMP-rdf:About', UnescapeXML($wasAbout)) if defined $wasAbout; + $et->VerboseValue('+ XMP-rdf:About', $about); + } + $about = EscapeXML($about); # must escape for XML + ++$changed; + } else { + $about = $$et{XmpAbout} || ''; + } + delete $$et{XMP_ERROR}; + + # call InitWriteDirs to initialize FORCE_WRITE flags if necessary + $et->InitWriteDirs({}, 'XMP') if $xmpFile and $et->GetNewValue('ForceWrite'); + # set changed if we are ForceWrite tag was set to "XMP" + ++$changed if $$et{FORCE_WRITE}{XMP}; + + } elsif (defined $about) { + $et->VerboseValue('+ XMP-rdf:About', $about); + $about = EscapeXML($about); # must escape for XML + # (don't increment $changed here because we need another tag to be written) + } else { + $about = ''; + } +# +# handle writing XMP as a block to XMP file +# + if ($xmpFile) { + $tagInfo = $Image::ExifTool::Extra{XMP}; + if ($tagInfo and $$et{NEW_VALUE}{$tagInfo}) { + my $rtnVal = 1; + my $newVal = $et->GetNewValue($$et{NEW_VALUE}{$tagInfo}); + if (defined $newVal and length $newVal) { + $et->VPrint(0, " Writing XMP as a block\n"); + ++$$et{CHANGED}; + Write($$dirInfo{OutFile}, $newVal) or $rtnVal = -1; + } + delete $$et{XMP_CAPTURE}; + return $rtnVal; + } + } +# +# delete groups in family 1 if requested +# + if (%{$$et{DEL_GROUP}} and (grep /^XMP-.+$/, keys %{$$et{DEL_GROUP}} or + # (logic is a bit more complex for group names in exiftool XML files) + grep m{^http://ns.exiftool.(?:ca|org)/}, values %nsUsed)) + { + my $del = $$et{DEL_GROUP}; + my $path; + foreach $path (keys %capture) { + my @propList = split('/',$path); # get property list + my ($tag, $ns) = GetXMPTagID(\@propList); + # translate namespace if necessary + $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns}; + my ($grp, @g); + # no "XMP-" added to most groups in exiftool RDF/XML output file + if ($nsUsed{$ns} and (@g = ($nsUsed{$ns} =~ m{^http://ns.exiftool.(?:ca|org)/(.*?)/(.*?)/}))) { + if ($g[1] =~ /^\d/) { + $grp = "XML-$g[0]"; + #(all XML-* groups stored as uppercase DEL_GROUP key) + my $ucg = uc $grp; + next unless $$del{$ucg} or ($$del{'XML-*'} and not $$del{"-$ucg"}); + } else { + $grp = $g[1]; + next unless $$del{$grp} or ($$del{$g[0]} and not $$del{"-$grp"}); + } + } else { + $grp = "XMP-$ns"; + my $ucg = uc $grp; + next unless $$del{$ucg} or ($$del{'XMP-*'} and not $$del{"-$ucg"}); + } + $et->VerboseValue("- $grp:$tag", $capture{$path}->[0]); + delete $capture{$path}; + ++$changed; + } + } + # delete HasExtendedXMP tag (we create it as needed) + my $hasExtTag = 'xmpNote:HasExtendedXMP'; + if ($capture{$hasExtTag}) { + $et->VerboseValue("- XMP-$hasExtTag", $capture{$hasExtTag}->[0]); + delete $capture{$hasExtTag}; + } + # set $xmpOpen now to to handle xmptk tag first + my $xmpOpen = $$et{XMP_NO_XMPMETA} ? '' : XMPOpen($et); +# +# add, delete or change information as specified +# + # get hash of all information we want to change + # (sorted by tag name so alternate languages come last, but with structures + # first so flattened tags may be used to override individual structure elements) + my (@tagInfoList, $delLangPath, %delLangPaths, %delAllLang, $firstNewPath); + my $writeGroup = $$dirInfo{WriteGroup}; + foreach $tagInfo (sort ByTagName $et->GetNewTagInfoList()) { + next unless $et->GetGroup($tagInfo, 0) eq 'XMP'; + next if $$tagInfo{Name} eq 'XMP'; # (ignore full XMP block if we didn't write it already) + next if $writeGroup and $writeGroup ne $$et{NEW_VALUE}{$tagInfo}{WriteGroup}; + if ($$tagInfo{Struct}) { + unshift @tagInfoList, $tagInfo; + } else { + push @tagInfoList, $tagInfo; + } + } + foreach $tagInfo (@tagInfoList) { + my @delPaths; # list of deleted paths + my $tag = $$tagInfo{TagID}; + my $path = GetPropertyPath($tagInfo); + unless ($path) { + $et->Warn("Can't write XMP:$tag (namespace unknown)"); + next; + } + # skip tags that were handled specially + if ($path eq 'rdf:about' or $path eq 'x:xmptk') { + ++$changed; + next; + } + my $isStruct = $$tagInfo{Struct}; + # change our property path namespace prefixes to conform + # to the ones used in this file + $path = ConformPathToNamespace($et, $path); + # find existing property + my $cap = $capture{$path}; + # MicrosoftPhoto screws up the case of some tags, and some other software, + # including Adobe software, has been known to write the wrong list type or + # not properly enclose properties in a list, so we check for this + until ($cap) { + # find and fix all incorrect property names if this is a structure or a flattened tag + my @fixInfo; + if ($isStruct or defined $$tagInfo{Flat}) { + # get tagInfo for all containing (possibly nested) structures + my @props = split '/', $path; + my $tbl = $$tagInfo{Table}; + while (@props) { + my $info = $$tbl{GetXMPTagID(\@props)}; + unshift @fixInfo, $info if ref $info eq 'HASH' and $$info{Struct} and + (not @fixInfo or $fixInfo[0] ne $info); + pop @props; + } + $et->WarnOnce("Error finding parent structure for $$tagInfo{Name}") unless @fixInfo; + } + # fix property path for this tag (last in the @fixInfo list) + push @fixInfo, $tagInfo unless @fixInfo and $isStruct; + # start from outermost containing structure, fixing incorrect list types, etc, + # finally fixing the actual tag properties after all containing structures + my $err; + while (@fixInfo) { + my $fixInfo = shift @fixInfo; + my $fixPath = ConformPathToNamespace($et, GetPropertyPath($fixInfo)); + my $regex = quotemeta($fixPath); + $regex =~ s/ \d+/ \\d\+/g; # match any list index + my $ok = $regex; + my ($ok2, $match, $i, @fixed, %fixed, $fixed); + # check for incorrect list types + if ($regex =~ s{\\/rdf\\:(Bag|Seq|Alt)\\/}{/rdf:(Bag|Seq|Alt)/}g) { + # also look for missing bottom-level list + if ($regex =~ s{/rdf:\(Bag\|Seq\|Alt\)\/rdf\\:li\\ \\d\+$}{}) { + $regex .= '(/.*)?' unless @fixInfo; + } + } elsif (not @fixInfo) { + $ok2 = $regex; + # check for properties in lists that shouldn't be (ref forum4325) + $regex .= '(/rdf:(Bag|Seq|Alt)/rdf:li \d+)?'; + } + if (@fixInfo) { + $regex .= '(/.*)?'; + $ok .= '(/.*)?'; + } + my @matches = sort grep m{^$regex$}i, keys %capture; + last unless @matches; + if ($matches[0] =~ m{^$ok$}) { + unless (@fixInfo) { + $path = $matches[0]; + $cap = $capture{$path}; + } + next; + } + # needs fixing... + my @fixProps = split '/', $fixPath; + foreach $match (@matches) { + my @matchProps = split '/', $match; + # remove superfluous list properties if necessary + $#matchProps = $#fixProps if $ok2 and $#matchProps > $#fixProps; + for ($i=0; $i<@fixProps; ++$i) { + defined $matchProps[$i] or $matchProps[$i] = $fixProps[$i], next; + next if $matchProps[$i] =~ / \d+$/ or $matchProps[$i] eq $fixProps[$i]; + $matchProps[$i] = $fixProps[$i]; + } + $fixed = join '/', @matchProps; + $err = 1 if $fixed{$fixed} or ($capture{$fixed} and $match ne $fixed); + push @fixed, $fixed; + $fixed{$fixed} = 1; + } + my $tg = $et->GetGroup($fixInfo, 1) . ':' . $$fixInfo{Name}; + my $wrn = lc($fixed[0]) eq lc($matches[0]) ? 'tag ID case' : 'list type'; + if ($err) { + $et->Warn("Incorrect $wrn for existing $tg (not changed)"); + } else { + # fix the incorrect property paths for all values of this tag + my $didFix; + foreach $fixed (@fixed) { + my $match = shift @matches; + next if $fixed eq $match; + $capture{$fixed} = $capture{$match}; + delete $capture{$match}; + # remove xml:lang attribute from incorrect lang-alt list if necessary + delete $capture{$fixed}[1]{'xml:lang'} if $ok2 and $match !~ /^$ok2$/; + $didFix = 1; + } + $cap = $capture{$path} || $capture{$fixed[0]} unless @fixInfo; + if ($didFix) { + $et->Warn("Fixed incorrect $wrn for $tg", 1); + ++$changed; + } + } + } + last; + } + my $nvHash = $et->GetNewValueHash($tagInfo); + my $overwrite = $et->IsOverwriting($nvHash); + my $writable = $$tagInfo{Writable} || ''; + my (%attrs, $deleted, $added, $existed, $newLang); + # set up variables to save/restore paths of deleted lang-alt tags + if ($writable eq 'lang-alt') { + $newLang = lc($$tagInfo{LangCode} || 'x-default'); + if ($delLangPath and $delLangPath eq $path) { + # restore paths of deleted entries for this language + @delPaths = @{$delLangPaths{$newLang}} if $delLangPaths{$newLang}; + } else { + undef %delLangPaths; + $delLangPath = $path; # base path for deleted lang-alt tags + undef %delAllLang; + undef $firstNewPath; # reset first path for new lang-alt tag + } + if (%delAllLang) { + # add missing paths to delete list for entries where all languages were deleted + my ($prefix, $reSort); + foreach $prefix (keys %delAllLang) { + next if grep /^$prefix/, @delPaths; + push @delPaths, "${prefix}10"; + $reSort = 1; + } + @delPaths = sort @delPaths if $reSort; + } + } + # delete existing entry if necessary + if ($isStruct) { + # delete all structure (or pseudo-structure) elements + require 'Image/ExifTool/XMPStruct.pl'; + ($deleted, $added, $existed) = DeleteStruct($et, \%capture, \$path, $nvHash, \$changed); + next unless $deleted or $added or $et->IsOverwriting($nvHash); + next if $existed and $$nvHash{CreateOnly}; + } elsif ($cap) { + next if $$nvHash{CreateOnly}; # (necessary for List-type tags) + # take attributes from old values if they exist + %attrs = %{$$cap[1]}; + if ($overwrite) { + my ($oldLang, $delLang, $addLang, @matchingPaths, $langPathPat, %langsHere); + # check to see if this is an indexed list item + if ($path =~ / /) { + my $pp; + ($pp = $path) =~ s/ \d+/ \\d\+/g; + @matchingPaths = sort grep(/^$pp$/, keys %capture); + } else { + push @matchingPaths, $path; + } + my $oldOverwrite = $overwrite; + foreach $path (@matchingPaths) { + my ($val, $attrs) = @{$capture{$path}}; + if ($writable eq 'lang-alt') { + # get original language code (lc for comparisons) + $oldLang = lc($$attrs{'xml:lang'} || 'x-default'); + # revert to original overwrite flag if this is in a different structure + if (not $langPathPat or $path !~ /^$langPathPat$/) { + $overwrite = $oldOverwrite; + ($langPathPat = $path) =~ s/\d+$/\\d+/; + } + # remember languages in this lang-alt list + $langsHere{$langPathPat}{$oldLang} = 1; + unless (defined $addLang) { + # add to lang-alt list by default if creating this tag from scratch + $addLang = $$nvHash{IsCreating} ? 1 : 0; + } + if ($overwrite < 0) { + next unless $oldLang eq $newLang; + # only add new tag if we are overwriting this one + # (note: this won't match if original XML contains CDATA!) + $addLang = $et->IsOverwriting($nvHash, UnescapeXML($val)); + next unless $addLang; + } + # delete all if deleting "x-default" and writing with no LangCode + # (XMP spec requires x-default language exist and be first in list) + if ($oldLang eq 'x-default' and not $$tagInfo{LangCode}) { + $delLang = 1; # delete all languages + $overwrite = 1; # force overwrite + } elsif ($$tagInfo{LangCode} and not $delLang) { + # only overwrite specified language + next unless lc($$tagInfo{LangCode}) eq $oldLang; + } + } elsif ($overwrite < 0) { + # only overwrite specific values + if ($$nvHash{Shift}) { + # values to be shifted are checked (hence re-formatted) late, + # so we must un-format the to-be-shifted value for IsOverwriting() + my $fmt = $$tagInfo{Writable} || ''; + if ($fmt eq 'rational') { + ConvertRational($val); + } elsif ($fmt eq 'date') { + $val = ConvertXMPDate($val); + } + } + # (note: this won't match if original XML contains CDATA!) + next unless $et->IsOverwriting($nvHash, UnescapeXML($val)); + } + if ($verbose > 1) { + my $grp = $et->GetGroup($tagInfo, 1); + my $tagName = $$tagInfo{Name}; + $tagName =~ s/-$$tagInfo{LangCode}$// if $$tagInfo{LangCode}; + $tagName .= '-' . $$attrs{'xml:lang'} if $$attrs{'xml:lang'}; + $et->VerboseValue("- $grp:$tagName", $val); + } + # save attributes and path from first deleted property + # so we can replace it exactly + %attrs = %$attrs unless @delPaths; + if ($writable eq 'lang-alt') { + $langsHere{$langPathPat}{$oldLang} = 0; # (lang was deleted) + } + # save deleted paths so we can replace the same elements + # (separately for each language of a lang-alt list) + if ($writable ne 'lang-alt' or $oldLang eq $newLang) { + push @delPaths, $path; + } else { + $delLangPaths{$oldLang} or $delLangPaths{$oldLang} = [ ]; + push @{$delLangPaths{$oldLang}}, $path; + } + # keep track of paths where we deleted all languages of a lang-alt tag + if ($delLang) { + my $p; + ($p = $path) =~ s/\d+$//; + $delAllLang{$p} = 1; + } + # delete this tag + delete $capture{$path}; + ++$changed; + # delete rdf:type tag if it is the only thing left in this structure + if ($path =~ /^(.*)\// and $capture{"$1/rdf:type"}) { + my $pp = $1; + my @a = grep /^\Q$pp\E\/[^\/]+/, keys %capture; + delete $capture{"$pp/rdf:type"} if @a == 1; + } + } + next unless @delPaths or $$tagInfo{List} or $addLang; + if (@delPaths) { + $path = shift @delPaths; + # make sure new path is unique + while ($capture{$path}) { + last unless $path =~ s/ \d(\d+)$/' '.length($1+1).($1+1)/e; + } + $deleted = 1; + } else { + # don't change tag if we couldn't delete old copy + # unless this is a list or an lang-alt tag + next unless $$tagInfo{List} or $oldLang; + # avoid adding duplicate entry to lang-alt in a list + if ($writable eq 'lang-alt' and %langsHere) { + foreach (sort keys %langsHere) { + next unless $path =~ /^$_$/; + last unless $langsHere{$_}{$newLang}; + $path =~ /(.* )\d(\d+)(.*? \d+)$/ or $et->Error('Internal error writing lang-alt list'), last; + my $nxt = $2 + 1; + $path = $1 . length($nxt) . ($nxt) . $3; # step to next index + } + } + # (match last index to put in same lang-alt list for Bag of lang-alt items) + $path =~ m/.* (\d+)/g or warn "Internal error: no list index!\n", next; + $added = $1; + } + } else { + # we are never overwriting, so we must be adding to a list + # match the last index unless this is a list of lang-alt lists + my $pat = '.* (\d+)'; + if ($writable eq 'lang-alt') { + if ($firstNewPath) { + $path = $firstNewPath; + $overwrite = 1; # necessary to put x-default entry first below + } else { + $pat = '.* (\d+)(.*? \d+)'; + } + } + if ($path =~ m/$pat/g) { + $added = $1; + # set position to end of matching index number + pos($path) = pos($path) - length($2) if $2; + } + } + if (defined $added) { + my $len = length $added; + my $pos = pos($path) - $len; + my $nxt = substr($added, 1) + 1; + # always insert x-default lang-alt entry first (as per XMP spec) + # (need to test $overwrite because this will be a new lang-alt entry otherwise) + if ($overwrite and $writable eq 'lang-alt' and (not $$tagInfo{LangCode} or + $$tagInfo{LangCode} eq 'x-default')) + { + my $saveCap = $capture{$path}; + while ($saveCap) { + my $p = $path; + substr($p, $pos, $len) = length($nxt) . $nxt; + # increment index in the path of the existing item + my $nextCap = $capture{$p}; + $capture{$p} = $saveCap; + last unless $nextCap; + $saveCap = $nextCap; + ++$nxt; + } + } else { + # add to end of list + while ($capture{$path}) { + my $try = length($nxt) . $nxt; + substr($path, $pos, $len) = $try; + $len = length $try; + ++$nxt; + } + } + } + } + # check to see if we want to create this tag + # (create non-avoided tags in XMP data files by default) + my $isCreating = ($$nvHash{IsCreating} or (($isStruct or + ($preferred and not $$tagInfo{Avoid} and + not defined $$nvHash{Shift})) and not $$nvHash{EditOnly})); + + # don't add new values unless... + # ...tag existed before and was deleted, or we added it to a list + next unless $deleted or defined $added or + # ...tag didn't exist before and we are creating it + (not $cap and $isCreating); + + # get list of new values (all done if no new values specified) + my @newValues = $et->GetNewValue($nvHash) or next; + + # set language attribute for lang-alt lists + if ($writable eq 'lang-alt') { + $attrs{'xml:lang'} = $$tagInfo{LangCode} || 'x-default'; + $firstNewPath = $path if defined $added; # save path of first lang-alt tag added + } + # add new value(s) to %capture hash + my $subIdx; + for (;;) { + my $newValue = shift @newValues; + if ($isStruct) { + ++$changed if AddNewStruct($et, $tagInfo, \%capture, + $path, $newValue, $$tagInfo{Struct}); + } else { + $newValue = EscapeXML($newValue); + for (;;) { # (a cheap 'goto') + if ($$tagInfo{Resource}) { + # only store as a resource if it doesn't contain any illegal characters + if ($newValue !~ /[^a-z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\.\-\_\~]/i) { + $capture{$path} = [ '', { %attrs, 'rdf:resource' => $newValue } ]; + last; + } + my $grp = $et->GetGroup($tagInfo, 1); + $et->Warn("$grp:$$tagInfo{Name} written as a literal because value is not a valid URI", 1); + # fall through to write as a string literal + } + # remove existing value and/or resource attribute if they exist + delete $attrs{'rdf:value'}; + delete $attrs{'rdf:resource'}; + $capture{$path} = [ $newValue, \%attrs ]; + last; + } + if ($verbose > 1) { + my $grp = $et->GetGroup($tagInfo, 1); + $et->VerboseValue("+ $grp:$$tagInfo{Name}", $newValue); + } + ++$changed; + # add rdf:type if necessary + if ($$tagInfo{StructType}) { + AddStructType($et, $$tagInfo{Table}, \%capture, $path); + } + } + last unless @newValues; + # match last index except for lang-alt items where we want to put each + # item in a different lang-alt list (so match the 2nd-last for these) + my $pat = $writable eq 'lang-alt' ? '.* (\d+)(.*? \d+)' : '.* (\d+)'; + pos($path) = 0; + $path =~ m/$pat/g or warn("Internal error: no list index for $tag ($path) ($pat)!\n"), next; + my $idx = $1; + my $len = length $1; + my $pos = pos($path) - $len - ($2 ? length $2 : 0); + # use sub-indices if necessary to store additional values in sequence + if ($subIdx) { + $idx = substr($idx, 0, -length($subIdx)); # remove old sub-index + $subIdx = substr($subIdx, 1) + 1; + $subIdx = length($subIdx) . $subIdx; + } elsif (@delPaths) { + $path = shift @delPaths; + # make sure new path is unique + while ($capture{$path}) { + last unless $path =~ s/ \d(\d+)$/' '.length($1+1).($1+1)/e; + } + next; + } else { + $subIdx = '10'; + } + substr($path, $pos, $len) = $idx . $subIdx; + } + # make sure any empty structures are deleted + # (ExifTool shouldn't write these, but other software may) + if (defined $$tagInfo{Flat}) { + my $p = $path; + while ($p =~ s/\/[^\/]+$//) { + next unless $capture{$p}; + # it is an error if this property has a value + $et->Error("Improperly structured XMP ($p)",1) if $capture{$p}[0] =~ /\S/; + delete $capture{$p}; # delete the (hopefully) empty structure + } + } + } + # remove the ExifTool members we created + delete $$et{XMP_CAPTURE}; + delete $$et{XMP_NS}; + + my $maxDataLen = $$dirInfo{MaxDataLen}; + # get DataPt again because it may have been set by ProcessXMP + $dataPt = $$dirInfo{DataPt}; + + # return now if we didn't change anything + unless ($changed or ($maxDataLen and $dataPt and defined $$dataPt and + length($$dataPt) > $maxDataLen)) + { + return undef unless $xmpFile; # just rewrite original XMP + Write($$dirInfo{OutFile}, $$dataPt) or return -1 if $dataPt and defined $$dataPt; + return 1; + } +# +# write out the new XMP information (serialize it) +# + # start writing the XMP data + my (@long, @short, @resFlag); + $long[0] = $long[1] = $short[0] = ''; + if ($$et{XMP_NO_XPACKET}) { + # write BOM if flag is set + $long[-2] .= "\xef\xbb\xbf" if $$et{XMP_NO_XPACKET} == 2; + } else { + $long[-2] .= $pktOpen; + } + $long[-2] .= $xmlOpen if $$et{XMP_IS_XML}; + $long[-2] .= $xmpOpen . $rdfOpen; + + # initialize current property path list + my (@curPropList, @writeLast, @descStart, $extStart); + my (%nsCur, $prop, $n, $path); + my @pathList = sort TypeFirst keys %capture; + # order properties to write large values last if we have a MaxDataLen limit + if ($maxDataLen and @pathList) { + my @pathTmp; + my ($lastProp, $lastNS, $propSize) = ('', '', 0); + my @pathLoop = (@pathList, ''); # add empty path to end of list for loop + undef @pathList; + foreach $path (@pathLoop) { + $path =~ /^((\w*)[^\/]*)/; # get path element ($1) and ns ($2) + if ($1 eq $lastProp) { + push @pathTmp, $path; # accumulate all paths with same root + } else { + # put in list to write last if recommended or values are too large + if ($extendedRes{$lastProp} or $extendedRes{$lastNS} or + $propSize > $newDescThresh) + { + push @writeLast, @pathTmp; + } else { + push @pathList, @pathTmp; + } + last unless $path; # all done if we hit empty path + @pathTmp = ( $path ); + ($lastProp, $lastNS, $propSize) = ($1, $2, 0); + } + $propSize += length $capture{$path}->[0]; + } + } + + # write out all properties + for (;;) { + my (%nsNew, $newDesc); + unless (@pathList) { + last unless @writeLast; + @pathList = @writeLast; + undef @writeLast; + $newDesc = 2; # start with a new description for the extended data + } + $path = shift @pathList; + my @propList = split('/',$path); # get property list + # must open/close rdf:Description too + unshift @propList, $rdfDesc; + # make sure we have defined all necessary namespaces + foreach $prop (@propList) { + $prop =~ /(.*):/ or next; + $1 eq 'rdf' and next; # rdf namespace already defined + my $uri = $nsUsed{$1}; + unless ($uri) { + $uri = $nsURI{$1}; # we must have added a namespace + unless ($uri) { + # (namespace prefix may be empty if trying to write empty XMP structure, forum12384) + if (length $1) { + my $err = "Undefined XMP namespace: $1"; + if (not $xmpErr or $err ne $xmpErr) { + $xmpFile ? $et->Error($err) : $et->Warn($err); + $xmpErr = $err; + } + } + next; + } + } + $nsNew{$1} = $uri; + # need a new description if any new namespaces + $newDesc = 1 unless $nsCur{$1}; + } + my $closeTo = 0; + if ($newDesc) { + # look forward to see if we will want to also open other namespaces + # at this level (this is necessary to keep lists and structures from + # being broken if a property introduces a new namespace; plus it + # improves formatting) + my ($path2, $ns2); + foreach $path2 (@pathList) { + my @ns2s = ($path2 =~ m{(?:^|/)([^/]+?):}g); + my $opening = $compact{OneDesc} ? 1 : 0; + foreach $ns2 (@ns2s) { + next if $ns2 eq 'rdf'; + $nsNew{$ns2} and ++$opening, next; + last unless $opening; + # get URI for this existing or new namespace + my $uri = $nsUsed{$ns2} || $nsURI{$ns2} or last; + $nsNew{$ns2} = $uri; # also open this namespace + } + last unless $opening; + } + } else { + # find first property where the current path differs from the new path + for ($closeTo=0; $closeTo<@curPropList; ++$closeTo) { + last unless $closeTo < @propList; + last unless $propList[$closeTo] eq $curPropList[$closeTo]; + } + } + # close out properties down to the common base path + CloseProperty(\@curPropList, \@long, \@short, \@resFlag) while @curPropList > $closeTo; + + # open new description if necessary + if ($newDesc) { + $extStart = length($long[-2]) if $newDesc == 2; # extended data starts after this + # save rdf:Description start positions so we can reorder them if necessary + push @descStart, length($long[-2]) if $maxDataLen; + # open the new description + $prop = $rdfDesc; + %nsCur = %nsNew; # save current namespaces + my @ns = sort keys %nsCur; + $long[-2] .= "$nl$sp<$prop rdf:about='${about}'"; + # generate et:toolkit attribute if this is an exiftool RDF/XML output file + if ($$et{XMP_NO_XMPMETA} and @ns and $nsCur{$ns[0]} =~ m{^http://ns.exiftool.(?:ca|org)/}) { + $long[-2] .= "\n$sp${sp}xmlns:et='http://ns.exiftool.org/1.0/'" . + " et:toolkit='Image::ExifTool $Image::ExifTool::VERSION'"; + } + $long[-2] .= "\n$sp${sp}xmlns:$_='$nsCur{$_}'" foreach @ns; + push @curPropList, $prop; + # set resFlag to 0 to indicate base description when Shorthand enabled + $resFlag[0] = 0 if $compact{Shorthand}; + } + my ($val, $attrs) = @{$capture{$path}}; + $debug and print "$path = $val\n"; + # open new properties if necessary + my ($attr, $dummy); + for ($n=@curPropList; $n<$#propList; ++$n) { + $prop = $propList[$n]; + push @curPropList, $prop; + $prop =~ s/ .*//; # remove list index if it exists + # (we may add parseType and shorthand properties later, + # so leave off the trailing ">" for now) + $long[-1] .= ($compact{NoIndent} ? '' : ' ' x scalar(@curPropList)) . "<$prop"; + if ($prop ne $rdfDesc and ($propList[$n+1] !~ /^rdf:/ or + ($propList[$n+1] eq 'rdf:type' and $n+1 == $#propList))) + { + # check for empty structure + if ($propList[$n+1] =~ /:~dummy~$/) { + $long[-1] .= " rdf:parseType='Resource'/>$nl"; + pop @curPropList; + $dummy = 1; + last; + } + if ($compact{Shorthand}) { + $resFlag[$#curPropList] = 1; + push @long, ''; + push @short, ''; + } else { + # use rdf:parseType='Resource' to avoid new 'rdf:Description' + $long[-1] .= " rdf:parseType='Resource'>$nl"; + } + } else { + $long[-1] .= ">$nl"; # (will be no shorthand properties) + } + } + my $prop2 = pop @propList; # get new property name + # add element unless it was a dummy structure field + unless ($dummy or ($val eq '' and $prop2 =~ /:~dummy~$/)) { + $prop2 =~ s/ .*//; # remove list index if it exists + my $pad = $compact{NoIndent} ? '' : ' ' x (scalar(@curPropList) + 1); + # (can't write as shortcut if it has attributes or CDATA) + if (defined $resFlag[$#curPropList] and not %$attrs and $val !~ /<!\[CDATA\[/) { + $short[-1] .= "\n$pad$prop2='${val}'"; + } else { + $long[-1] .= "$pad<$prop2"; + # write out attributes + foreach $attr (sort keys %$attrs) { + my $attrVal = $$attrs{$attr}; + my $quot = ($attrVal =~ /'/) ? '"' : "'"; + $long[-1] .= " $attr=$quot$attrVal$quot"; + } + $long[-1] .= length $val ? ">$val</$prop2>$nl" : "/>$nl"; + } + } + } + # close out all open properties + CloseProperty(\@curPropList, \@long, \@short, \@resFlag) while @curPropList; + + # limit XMP length and re-arrange if necessary to fit inside specified size + if ($maxDataLen) { + # adjust maxDataLen to allow room for closing elements + $maxDataLen -= length($rdfClose) + length($xmpClose) + length($pktCloseW); + $extStart or $extStart = length $long[-2]; + my @rtn = LimitXMPSize($et, \$long[-2], $maxDataLen, $about, \@descStart, $extStart); + # return extended XMP information in $dirInfo + $$dirInfo{ExtendedXMP} = $rtn[0]; + $$dirInfo{ExtendedGUID} = $rtn[1]; + # compact if necessary to fit + $compact{NoPadding} = 1 if length($long[-2]) + 101 * $numPadLines > $maxDataLen; + } + $compact{NoPadding} = 1 if $$dirInfo{Compact}; +# +# close out the XMP, clean up, and return our data +# + $long[-2] .= $rdfClose; + $long[-2] .= $xmpClose unless $$et{XMP_NO_XMPMETA}; + + # remove the ExifTool members we created + delete $$et{XMP_CAPTURE}; + delete $$et{XMP_NS}; + delete $$et{XMP_NO_XMPMETA}; + + # (the XMP standard recommends writing 2k-4k of white space before the + # packet trailer, with a newline every 100 characters) + unless ($$et{XMP_NO_XPACKET}) { + my $pad = (' ' x 100) . "\n"; + # get current XMP length without padding + my $len = length($long[-2]) + length($pktCloseW); + if ($$dirInfo{InPlace} and not ($$dirInfo{InPlace} == 2 and $len > $dirLen)) { + # pad to specified DirLen + if ($len > $dirLen) { + my $str = 'Not enough room to edit XMP in place'; + $str .= '. Try Shorthand feature' unless $compact{Shorthand}; + $et->Warn($str); + return undef; + } + my $num = int(($dirLen - $len) / length($pad)); + if ($num) { + $long[-2] .= $pad x $num; + $len += length($pad) * $num; + } + $len < $dirLen and $long[-2] .= (' ' x ($dirLen - $len - 1)) . "\n"; + } elsif (not $compact{NoPadding} and not $xmpFile and not $$dirInfo{ReadOnly}) { + $long[-2] .= $pad x $numPadLines; + } + $long[-2] .= ($$dirInfo{ReadOnly} ? $pktCloseR : $pktCloseW); + } + # return empty data if no properties exist and this is allowed + unless (%capture or $xmpFile or $$dirInfo{InPlace} or $$dirInfo{NoDelete}) { + $long[-2] = ''; + } + return($xmpFile ? -1 : undef) if $xmpErr; + $$et{CHANGED} += $changed; + $debug > 1 and $long[-2] and print $long[-2],"\n"; + return $long[-2] unless $xmpFile; + Write($$dirInfo{OutFile}, $long[-2]) or return -1; + return 1; +} + + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::WriteXMP.pl - Write XMP meta information + +=head1 SYNOPSIS + +These routines are autoloaded by Image::ExifTool::XMP. + +=head1 DESCRIPTION + +This file contains routines to write XMP metadata. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::XMP(3pm)|Image::ExifTool::XMP>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/Writer.pl b/ExifTool/lib/Image/ExifTool/Writer.pl new file mode 100644 index 0000000..98f8874 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/Writer.pl @@ -0,0 +1,7223 @@ +#------------------------------------------------------------------------------ +# File: Writer.pl +# +# Description: ExifTool write routines +# +# Notes: Also contains some less used ExifTool functions +# +# URL: https://exiftool.org/ +# +# Revisions: 12/16/2004 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool; + +use strict; + +use Image::ExifTool::TagLookup qw(FindTagInfo TagExists); +use Image::ExifTool::Fixup; + +sub AssembleRational($$@); +sub LastInList($); +sub CreateDirectory($$); +sub NextFreeTagKey($$); +sub RemoveNewValueHash($$$); +sub RemoveNewValuesForGroup($$); +sub GetWriteGroup1($$); +sub Sanitize($$); +sub ConvInv($$$$$;$$); + +my $loadedAllTables; # flag indicating we loaded all tables +my $advFmtSelf; # ExifTool object during evaluation of advanced formatting expr + +# the following is a road map of where we write each directory +# in the different types of files. +my %tiffMap = ( + IFD0 => 'TIFF', + IFD1 => 'IFD0', + XMP => 'IFD0', + ICC_Profile => 'IFD0', + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + IPTC => 'IFD0', + Photoshop => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', + CanonVRD => 'MakerNotes', # (so VRDOffset will get updated) + NikonCapture => 'MakerNotes', # (to allow delete by group) + PhaseOne => 'MakerNotes', # (for editing PhaseOne SensorCalibration tags) +); +my %exifMap = ( + IFD1 => 'IFD0', + EXIF => 'IFD0', # to write EXIF as a block + ExifIFD => 'IFD0', + GPS => 'IFD0', + SubIFD => 'IFD0', + GlobParamIFD => 'IFD0', + PrintIM => 'IFD0', + InteropIFD => 'ExifIFD', + MakerNotes => 'ExifIFD', + NikonCapture => 'MakerNotes', # (to allow delete by group) + # (no CanonVRD trailer allowed) +); +my %jpegMap = ( + %exifMap, # covers all JPEG EXIF mappings + JFIF => 'APP0', + CIFF => 'APP0', + IFD0 => 'APP1', + XMP => 'APP1', + ICC_Profile => 'APP2', + FlashPix => 'APP2', + MPF => 'APP2', + Meta => 'APP3', + MetaIFD => 'Meta', + RMETA => 'APP5', + Ducky => 'APP12', + Photoshop => 'APP13', + Adobe => 'APP14', + IPTC => 'Photoshop', + MakerNotes => ['ExifIFD', 'CIFF'], # (first parent is the default) + CanonVRD => 'MakerNotes', # (so VRDOffset will get updated) + NikonCapture => 'MakerNotes', # (to allow delete by group) + Comment => 'COM', +); +my %dirMap = ( + JPEG => \%jpegMap, + EXV => \%jpegMap, + TIFF => \%tiffMap, + ORF => \%tiffMap, + RAW => \%tiffMap, + EXIF => \%exifMap, +); + +# module names and write functions for each writable file type +# (defaults to "$type" and "Process$type" if not defined) +# - types that are handled specially will not appear in this list +my %writableType = ( + CRW => [ 'CanonRaw', 'WriteCRW' ], + DR4 => 'CanonVRD', + EPS => [ 'PostScript', 'WritePS' ], + FLIF=> [ undef, 'WriteFLIF'], + GIF => undef, + ICC => [ 'ICC_Profile', 'WriteICC' ], + IND => 'InDesign', + JP2 => 'Jpeg2000', + JXL => 'Jpeg2000', + MIE => undef, + MOV => [ 'QuickTime', 'WriteMOV' ], + MRW => 'MinoltaRaw', + PDF => [ undef, 'WritePDF' ], + PNG => undef, + PPM => undef, + PS => [ 'PostScript', 'WritePS' ], + PSD => 'Photoshop', + RAF => [ 'FujiFilm', 'WriteRAF' ], + RIFF=> [ 'RIFF', 'WriteRIFF'], + VRD => 'CanonVRD', + WEBP=> [ 'RIFF', 'WriteRIFF'], + X3F => 'SigmaRaw', + XMP => [ undef, 'WriteXMP' ], +); + +# RAW file types +my %rawType = ( + '3FR'=> 1, CR3 => 1, IIQ => 1, NEF => 1, RW2 => 1, + ARQ => 1, CRW => 1, K25 => 1, NRW => 1, RWL => 1, + ARW => 1, DCR => 1, KDC => 1, ORF => 1, SR2 => 1, + ARW => 1, ERF => 1, MEF => 1, PEF => 1, SRF => 1, + CR2 => 1, FFF => 1, MOS => 1, RAW => 1, SRW => 1, +); + +# groups we are allowed to delete +# Notes: +# 1) these names must either exist in %dirMap, or be translated in InitWriteDirs()) +# 2) any dependencies must be added to %excludeGroups +my @delGroups = qw( + Adobe AFCP APP0 APP1 APP2 APP3 APP4 APP5 APP6 APP7 APP8 APP9 APP10 APP11 + APP12 APP13 APP14 APP15 CanonVRD CIFF Ducky EXIF ExifIFD File FlashPix + FotoStation GlobParamIFD GPS ICC_Profile IFD0 IFD1 Insta360 InteropIFD IPTC + ItemList JFIF Jpeg2000 JUMBF Keys MakerNotes Meta MetaIFD Microsoft MIE MPF + NikonApp NikonCapture PDF PDF-update PhotoMechanic Photoshop PNG PNG-pHYs + PrintIM QuickTime RMETA RSRC SubIFD Trailer UserData XML XML-* XMP XMP-* +); +# family 2 group names that we can delete +my @delGroup2 = qw( + Audio Author Camera Document ExifTool Image Location Other Preview Printing + Time Video +); +# Extra groups to delete when deleting another group +my %delMore = ( + QuickTime => [ qw(ItemList UserData Keys) ], + XMP => [ 'XMP-*' ], + XML => [ 'XML-*' ], +); + +# family 0 groups where directories should never be deleted +my %permanentDir = ( QuickTime => 1, Jpeg2000 => 1 ); + +# lookup for all valid family 2 groups (lower case) +my %family2groups = map { lc $_ => 1 } @delGroup2, 'Unknown'; + +# groups we don't delete when deleting all information +my $protectedGroups = '(IFD1|SubIFD|InteropIFD|GlobParamIFD|PDF-update|Adobe)'; + +# other group names of new tag values to remove when deleting an entire group +my %removeGroups = ( + IFD0 => [ 'EXIF', 'MakerNotes' ], + EXIF => [ 'MakerNotes' ], + ExifIFD => [ 'MakerNotes', 'InteropIFD' ], + Trailer => [ 'CanonVRD' ], #(because we can add back CanonVRD as a block) +); +# related family 0/1 groups in @delGroups (and not already in %jpegMap) +# that must be removed from delete list when excluding a group +my %excludeGroups = ( + EXIF => [ qw(IFD0 IFD1 ExifIFD GPS MakerNotes GlobParamIFD InteropIFD PrintIM SubIFD) ], + IFD0 => [ 'EXIF' ], + IFD1 => [ 'EXIF' ], + ExifIFD => [ 'EXIF' ], + GPS => [ 'EXIF' ], + MakerNotes => [ 'EXIF' ], + InteropIFD => [ 'EXIF' ], + GlobParamIFD => [ 'EXIF' ], + PrintIM => [ 'EXIF' ], + CIFF => [ 'MakerNotes' ], + # technically correct, but very uncommon and not a good reason to avoid deleting trailer + # IPTC => [ qw(AFCP FotoStation Trailer) ], + AFCP => [ 'Trailer' ], + FotoStation => [ 'Trailer' ], + CanonVRD => [ 'Trailer' ], + PhotoMechanic=> [ 'Trailer' ], + MIE => [ 'Trailer' ], + QuickTime => [ qw(ItemList UserData Keys) ], +); +# translate (lower case) wanted group when writing for tags where group name may change +my %translateWantGroup = ( + ciff => 'canonraw', +); +# group names to translate for writing +my %translateWriteGroup = ( + EXIF => 'ExifIFD', + Meta => 'MetaIFD', + File => 'Comment', + # any entry in this table causes the write group to be set from the + # tag information instead of whatever the user specified... + MIE => 'MIE', + APP14 => 'APP14', +); +# names of valid EXIF and Meta directories (lower case keys): +my %exifDirs = ( + gps => 'GPS', + exififd => 'ExifIFD', + subifd => 'SubIFD', + globparamifd => 'GlobParamIFD', + interopifd => 'InteropIFD', + previewifd => 'PreviewIFD', # (in MakerNotes) + metaifd => 'MetaIFD', # Kodak APP3 Meta + makernotes => 'MakerNotes', +); +# valid family 0 groups when WriteGroup is set to "All" +my %allFam0 = ( + exif => 1, + makernotes => 1, +); + +my @writableMacOSTags = qw( + FileCreateDate MDItemFinderComment MDItemFSCreationDate MDItemFSLabel MDItemUserTags + XAttrQuarantine +); + +# min/max values for integer formats +my %intRange = ( + 'int8u' => [0, 0xff], + 'int8s' => [-0x80, 0x7f], + 'int16u' => [0, 0xffff], + 'int16uRev' => [0, 0xffff], + 'int16s' => [-0x8000, 0x7fff], + 'int32u' => [0, 0xffffffff], + 'int32s' => [-0x80000000, 0x7fffffff], + 'int64u' => [0, 18446744073709551615], + 'int64s' => [-9223372036854775808, 9223372036854775807], +); +# lookup for file types with block-writable EXIF +my %blockExifTypes = map { $_ => 1 } qw(JPEG PNG JP2 JXL MIE EXIF FLIF MOV MP4 RIFF); + +my $maxSegmentLen = 0xfffd; # maximum length of data in a JPEG segment +my $maxXMPLen = $maxSegmentLen; # maximum length of XMP data in JPEG + +# value separators when conversion list is used (in SetNewValue) +my %listSep = ( PrintConv => '; ?', ValueConv => ' ' ); + +# printConv hash keys to ignore when doing reverse lookup +my %ignorePrintConv = map { $_ => 1 } qw(OTHER BITMASK Notes); + +#------------------------------------------------------------------------------ +# Set tag value +# Inputs: 0) ExifTool object reference +# 1) tag key, tag name, or '*' (optionally prefixed by group name), +# or undef to reset all previous SetNewValue() calls +# 2) new value (scalar, scalar ref or list ref), or undef to delete tag +# 3-N) Options: +# Type => PrintConv, ValueConv or Raw - specifies value type +# AddValue => true to add to list of existing values instead of overwriting +# DelValue => true to delete this existing value value from a list, or +# or doing a conditional delete, or to shift a time value +# Group => family 0 or 1 group name (case insensitive) +# Replace => 0, 1 or 2 - overwrite previous new values (2=reset) +# Protected => bitmask to write tags with specified protections +# EditOnly => true to only edit existing tags (don't create new tag) +# EditGroup => true to only edit existing groups (don't create new group) +# Shift => undef, 0, +1 or -1 - shift value if possible +# NoFlat => treat flattened tags as 'unsafe' +# NoShortcut => true to prevent looking up shortcut tags +# ProtectSaved => protect existing new values with a save count greater than this +# IgnorePermanent => ignore attempts to delete a permanent tag +# CreateGroups => [internal use] createGroups hash ref from related tags +# ListOnly => [internal use] set only list or non-list tags +# SetTags => [internal use] hash ref to return tagInfo refs of set tags +# Sanitized => [internal use] set to avoid double-sanitizing the value +# Returns: number of tags set (plus error string in list context) +# Notes: For tag lists (like Keywords), call repeatedly with the same tag name for +# each value in the list. Internally, the new information is stored in +# the following members of the $$self{NEW_VALUE}{$tagInfo} hash: +# TagInfo - tag info ref +# DelValue - list ref for raw values to delete +# Value - list ref for raw values to add (not defined if deleting the tag) +# IsCreating - must be set for the tag to be added for the standard file types, +# otherwise just changed if it already exists. This may be +# overridden for file types with a PREFERRED metadata type. +# Set to 2 to create individual tags but not new groups +# EditOnly - flag set if tag should never be created (regardless of file type). +# If this is set, then IsCreating must be false +# CreateOnly - flag set if creating only (never edit existing tag) +# CreateGroups - hash of all family 0 group names where tag may be created +# WriteGroup - group name where information is being written (correct case) +# WantGroup - group name as specified in call to function (case insensitive) +# Next - pointer to next new value hash (if more than one) +# NoReplace - set if value was created with Replace=0 +# AddBefore - number of list items added by a subsequent Replace=0 call +# IsNVH - Flag indicating this is a new value hash +# Shift - shift value +# Save - counter used by SaveNewValues()/RestoreNewValues() +# MAKER_NOTE_FIXUP - pointer to fixup if necessary for a maker note value +sub SetNewValue($;$$%) +{ + local $_; + my ($self, $tag, $value, %options) = @_; + my ($err, $tagInfo, $family); + my $verbose = $$self{OPTIONS}{Verbose}; + my $out = $$self{OPTIONS}{TextOut}; + my $protected = $options{Protected} || 0; + my $listOnly = $options{ListOnly}; + my $setTags = $options{SetTags}; + my $noFlat = $options{NoFlat}; + my $numSet = 0; + + unless (defined $tag) { + delete $$self{NEW_VALUE}; + $$self{SAVE_COUNT} = 0; + $$self{DEL_GROUP} = { }; + return 1; + } + # allow value to be scalar or list reference + if (ref $value) { + if (ref $value eq 'ARRAY') { + # value is an ARRAY so it may have more than one entry + # - set values both separately and as a combined string if there are more than one + if (@$value > 1) { + # set all list-type tags first + my $replace = $options{Replace}; + my $noJoin; + foreach (@$value) { + $noJoin = 1 if ref $_; + my ($n, $e) = SetNewValue($self, $tag, $_, %options, ListOnly => 1); + $err = $e if $e; + $numSet += $n; + delete $options{Replace}; # don't replace earlier values in list + } + return $numSet if $noJoin; # don't join if list contains objects + # and now set only non-list tags + $value = join $$self{OPTIONS}{ListSep}, @$value; + $options{Replace} = $replace; + $listOnly = $options{ListOnly} = 0; + } else { + $value = $$value[0]; + $value = $$value if ref $value eq 'SCALAR'; # (handle single scalar ref in a list) + } + } elsif (ref $value eq 'SCALAR') { + $value = $$value; + } + } + # un-escape as necessary and make sure the Perl UTF-8 flag is OFF for the value + # if perl is 5.6 or greater (otherwise our byte manipulations get corrupted!!) + $self->Sanitize(\$value) if defined $value and not ref $value and not $options{Sanitized}; + + # set group name in options if specified + ($options{Group}, $tag) = ($1, $2) if $tag =~ /(.*):(.+)/; + + # allow trailing '#' for ValueConv value + $options{Type} = 'ValueConv' if $tag =~ s/#$//; + my $convType = $options{Type} || ($$self{OPTIONS}{PrintConv} ? 'PrintConv' : 'ValueConv'); + + # filter value if necessary + $self->Filter($$self{OPTIONS}{FilterW}, \$value) or return 0 if $convType eq 'PrintConv'; + + my (@wantGroup, $family2); + my $wantGroup = $options{Group}; + if ($wantGroup) { + foreach (split /:/, $wantGroup) { + next unless length($_) and /^(\d+)?(.*)/; # separate family number and group name + my ($f, $g) = ($1, $2); + my $lcg = lc $g; + # save group/family unless '*' or 'all' + push @wantGroup, [ $f, $lcg ] unless $lcg eq '*' or $lcg eq 'all'; + if ($g =~ s/^ID-//i) { # family 7 is a tag ID + return 0 if defined $f and $f ne 7; + $wantGroup[-1] = [ 7, $g ]; # group name with 'ID-' removed and case preserved + } elsif (defined $f) { + $f > 2 and return 0; # only allow family 0, 1 or 2 + $family2 = 1 if $f == 2; # set flag indicating family 2 was used + } else { + $family2 = 1 if $family2groups{$lcg}; + } + } + undef $wantGroup unless @wantGroup; + } + + $tag =~ s/ .*//; # convert from tag key to tag name if necessary + $tag = '*' if lc($tag) eq 'all'; # use '*' instead of 'all' +# +# handle group delete +# + while ($tag eq '*' and not defined $value and not $family2 and @wantGroup < 2) { + # set groups to delete + my (@del, $grp); + my $remove = ($options{Replace} and $options{Replace} > 1); + if ($wantGroup) { + @del = grep /^$wantGroup$/i, @delGroups unless $wantGroup =~ /^XM[LP]-\*$/i; + # remove associated groups when excluding from mass delete + if (@del and $remove) { + # remove associated groups in other family + push @del, @{$excludeGroups{$del[0]}} if $excludeGroups{$del[0]}; + # remove upstream groups according to JPEG map + my $dirName = $del[0]; + my @dirNames; + for (;;) { + my $parent = $jpegMap{$dirName}; + if (ref $parent) { + push @dirNames, @$parent; + $parent = pop @dirNames; + } + $dirName = $parent || shift @dirNames or last; + push @del, $dirName; # exclude this too + } + } + # allow MIE groups to be deleted by number, + # and allow any XMP family 1 group to be deleted + push @del, uc($wantGroup) if $wantGroup =~ /^(MIE\d+|XM[LP]-[-\w]*\w)$/i; + } else { + # push all groups plus '*', except the protected groups + push @del, (grep !/^$protectedGroups$/, @delGroups), '*'; + } + if (@del) { + ++$numSet; + my @donegrps; + my $delGroup = $$self{DEL_GROUP}; + foreach $grp (@del) { + if ($remove) { + my $didExcl; + if ($grp =~ /^(XM[LP])(-.*)?$/) { + my $x = $1; + if ($grp eq $x) { + # exclude all related family 1 groups too + foreach (keys %$delGroup) { + next unless /^(-?)$x-/; + push @donegrps, $_ unless $1; + delete $$delGroup{$_}; + } + } elsif ($$delGroup{"$x-*"} and not $$delGroup{"-$grp"}) { + # must also exclude XMP or XML to prevent bulk delete + if ($$delGroup{$x}) { + push @donegrps, $x; + delete $$delGroup{$x}; + } + # flag XMP/XML family 1 group for exclusion with leading '-' + $$delGroup{"-$grp"} = 1; + $didExcl = 1; + } + } + if (exists $$delGroup{$grp}) { + delete $$delGroup{$grp}; + } else { + next unless $didExcl; + } + } else { + $$delGroup{$grp} = 1; + # add extra groups to delete if necessary + if ($delMore{$grp}) { + $$delGroup{$_} = 1, push @donegrps, $_ foreach @{$delMore{$grp}}; + } + # remove all of this group from previous new values + $self->RemoveNewValuesForGroup($grp); + } + push @donegrps, $grp; + } + if ($verbose > 1 and @donegrps) { + @donegrps = sort @donegrps; + my $msg = $remove ? 'Excluding from deletion' : 'Deleting tags in'; + print $out " $msg: @donegrps\n"; + } + } elsif (grep /^$wantGroup$/i, @delGroup2) { + last; # allow tags to be deleted by group2 name + } else { + $err = "Not a deletable group: $wantGroup"; + } + # all done + return ($numSet, $err) if wantarray; + $err and warn "$err\n"; + return $numSet; + } + + # initialize write/create flags + my $createOnly; + my $editOnly = $options{EditOnly}; + my $editGroup = $options{EditGroup}; + my $writeMode = $$self{OPTIONS}{WriteMode}; + if ($writeMode ne 'wcg') { + $createOnly = 1 if $writeMode !~ /w/i; # don't write existing tags + if ($writeMode !~ /c/i) { + return 0 if $createOnly; # nothing to do unless writing existing tags + $editOnly = 1; # don't create new tags + } elsif ($writeMode !~ /g/i) { + $editGroup = 1; # don't create new groups + } + } + my ($ifdName, $mieGroup, $movGroup, $fg); + # set family 1 group names + foreach $fg (@wantGroup) { + next if defined $$fg[0] and $$fg[0] != 1; + $_ = $$fg[1]; + # set $ifdName if this group is a valid IFD or SubIFD name + my $grpName; + if (/^IFD(\d+)$/i) { + $grpName = $ifdName = "IFD$1"; + } elsif (/^SubIFD(\d+)$/i) { + $grpName = $ifdName = "SubIFD$1"; + } elsif (/^Version(\d+)$/i) { + $grpName = $ifdName = "Version$1"; # Sony IDC VersionIFD + } elsif ($exifDirs{$_}) { + $grpName = $exifDirs{$_}; + $ifdName = $grpName unless $ifdName and $allFam0{$_}; + } elsif ($allFam0{$_}) { + $grpName = $allFam0{$_}; + } elsif (/^Track(\d+)$/i) { + $grpName = $movGroup = "Track$1"; # QuickTime track + } elsif (/^MIE(\d*-?)(\w+)$/i) { + $grpName = $mieGroup = "MIE$1" . ucfirst(lc($2)); + } elsif (not $ifdName and /^XMP\b/i) { + # must load XMP table to set group1 names + my $table = GetTagTable('Image::ExifTool::XMP::Main'); + my $writeProc = $$table{WRITE_PROC}; + if ($writeProc) { + no strict 'refs'; + &$writeProc(); + } + } + # fix case for known groups + $wantGroup =~ s/$grpName/$grpName/i if $grpName and $grpName ne $_; + } +# +# get list of tags we want to set +# + my $origTag = $tag; + my @matchingTags = FindTagInfo($tag); + until (@matchingTags) { + my $langCode; + # allow language suffix of form "-en_CA" or "-<rfc3066>" on tag name + if ($tag =~ /^([?*\w]+)-([a-z]{2})(_[a-z]{2})$/i or # MIE + $tag =~ /^([?*\w]+)-([a-z]{2,3}|[xi])(-[a-z\d]{2,8}(-[a-z\d]{1,8})*)?$/i) # XMP/PNG/QuickTime + { + $tag = $1; + # normalize case of language codes + $langCode = lc($2); + $langCode .= (length($3) == 3 ? uc($3) : lc($3)) if $3; + my @newMatches = FindTagInfo($tag); + foreach $tagInfo (@newMatches) { + # only allow language codes in tables which support them + next unless $$tagInfo{Table}; + my $langInfoProc = $$tagInfo{Table}{LANG_INFO} or next; + my $langInfo = &$langInfoProc($tagInfo, $langCode); + push @matchingTags, $langInfo if $langInfo; + } + last if @matchingTags; + } elsif (not $options{NoShortcut}) { + # look for a shortcut or alias + require Image::ExifTool::Shortcuts; + my ($match) = grep /^\Q$tag\E$/i, keys %Image::ExifTool::Shortcuts::Main; + undef $err; + if ($match) { + $options{NoShortcut} = $options{Sanitized} = 1; + foreach $tag (@{$Image::ExifTool::Shortcuts::Main{$match}}) { + my ($n, $e) = $self->SetNewValue($tag, $value, %options); + $numSet += $n; + $e and $err = $e; + } + undef $err if $numSet; # no error if any set successfully + return ($numSet, $err) if wantarray; + $err and warn "$err\n"; + return $numSet; + } + } + unless ($listOnly) { + if (not TagExists($tag)) { + if ($tag =~ /^[-\w*?]+$/) { + my $pre = $wantGroup ? $wantGroup . ':' : ''; + $err = "Tag '$pre${origTag}' is not defined"; + $err .= ' or has a bad language code' if $origTag =~ /-/; + if (not $pre and uc($origTag) eq 'TAG') { + $err .= " (specify a writable tag name, not '${origTag}' literally)" + } + } else { + $err = "Invalid tag name '${tag}'"; + $err .= " (remove the leading '\$')" if $tag =~ /^\$/; + } + } elsif ($langCode) { + $err = "Tag '${tag}' does not support alternate languages"; + } elsif ($wantGroup) { + $err = "Sorry, $wantGroup:$origTag doesn't exist or isn't writable"; + } else { + $err = "Sorry, $origTag is not writable"; + } + $verbose > 2 and print $out "$err\n"; + } + # all done + return ($numSet, $err) if wantarray; + $err and warn "$err\n"; + return $numSet; + } + # get group name that we're looking for + my $foundMatch = 0; +# +# determine the groups for all tags found, and the tag with +# the highest priority group +# + my (@tagInfoList, @writeAlsoList, %writeGroup, %preferred, %tagPriority); + my (%avoid, $wasProtected, $noCreate, %highestPriority, %highestQT); + +TAG: foreach $tagInfo (@matchingTags) { + $tag = $$tagInfo{Name}; # get tag name for warnings + my $lcTag = lc $tag; # get lower-case tag name for use in variables + # initialize highest priority if we are starting a new tag + $highestPriority{$lcTag} = -999 unless defined $highestPriority{$lcTag}; + my ($priority, $writeGroup); + my $prfTag = defined $$tagInfo{Preferred} ? $$tagInfo{Preferred} : $$tagInfo{Table}{PREFERRED}; + if ($wantGroup) { + # a WriteGroup of All is special + my $wgAll = ($$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All'); + my @grp = $self->GetGroup($tagInfo); + my $hiPri = 1000; + foreach $fg (@wantGroup) { + my ($fam, $lcWant) = @$fg; + $lcWant = $translateWantGroup{$lcWant} if $translateWantGroup{$lcWant}; + # only set tag in specified group + # bump priority of preferred tag + $hiPri += $prfTag if $prfTag; + if (not defined $fam) { + if ($lcWant eq lc $grp[0]) { + # don't go to more general write group of "All" + # if something more specific was wanted + $writeGroup = $grp[0] if $wgAll and not $writeGroup; + next; + } + next if $lcWant eq lc $grp[2]; + } elsif ($fam == 7) { + next if IsSameID($$tagInfo{TagID}, $lcWant); + } elsif ($fam != 1 and not $$tagInfo{AllowGroup}) { + next if $lcWant eq lc $grp[$fam]; + if ($wgAll and not $fam and $allFam0{$lcWant}) { + $writeGroup or $writeGroup = $allFam0{$lcWant}; + next; + } + next TAG; # wrong group + } + # handle family 1 groups specially + if ($grp[0] eq 'EXIF' or $grp[0] eq 'SonyIDC' or $wgAll) { + unless ($ifdName and $lcWant eq lc $ifdName) { + next TAG unless $wgAll and not $fam and $allFam0{$lcWant}; + $writeGroup = $allFam0{$lcWant} unless $writeGroup; + next; + } + next TAG if $wgAll and $allFam0{$lcWant} and $fam; + # can't yet write PreviewIFD tags (except for image) + $lcWant eq 'PreviewIFD' and ++$foundMatch, next TAG; + $writeGroup = $ifdName; # write to the specified IFD + } elsif ($grp[0] eq 'QuickTime') { + if ($grp[1] eq 'Track#') { + next TAG unless $movGroup and $lcWant eq lc($movGroup); + $writeGroup = $movGroup; + } else { + my $grp = $$tagInfo{Table}{WRITE_GROUP}; + next TAG unless $grp and $lcWant eq lc $grp; + $writeGroup = $grp; + } + } elsif ($grp[0] eq 'MIE') { + next TAG unless $mieGroup and $lcWant eq lc($mieGroup); + $writeGroup = $mieGroup; # write to specific MIE group + # set specific write group with document number if specified + if ($writeGroup =~ /^MIE\d+$/ and $$tagInfo{Table}{WRITE_GROUP}) { + $writeGroup = $$tagInfo{Table}{WRITE_GROUP}; + $writeGroup =~ s/^MIE/$mieGroup/; + } + } elsif (not $$tagInfo{AllowGroup} or $lcWant !~ /^$$tagInfo{AllowGroup}$/i) { + # allow group1 name to be specified + next TAG unless $lcWant eq lc $grp[1]; + } + } + $writeGroup or $writeGroup = ($$tagInfo{WriteGroup} || $$tagInfo{Table}{WRITE_GROUP} || $grp[0]); + $priority = $hiPri; # highest priority since group was specified + } + ++$foundMatch; + # must do a dummy call to the write proc to autoload write package + # before checking Writable flag + my $table = $$tagInfo{Table}; + my $writeProc = $$table{WRITE_PROC}; + # load source table if this was a user-defined table + if ($$table{SRC_TABLE}) { + my $src = GetTagTable($$table{SRC_TABLE}); + $writeProc = $$src{WRITE_PROC} unless $writeProc; + } + { + no strict 'refs'; + next unless $writeProc and &$writeProc(); + } + # must still check writable flags in case of UserDefined tags + my $writable = $$tagInfo{Writable}; + next unless $writable or ($$table{WRITABLE} and + not defined $writable and not $$tagInfo{SubDirectory}); + # set specific write group (if we didn't already) + if (not $writeGroup or ($translateWriteGroup{$writeGroup} and + (not $$tagInfo{WriteGroup} or $$tagInfo{WriteGroup} ne 'All'))) + { + # use default write group + $writeGroup = $$tagInfo{WriteGroup} || $$tagInfo{Table}{WRITE_GROUP}; + # use group 0 name if no WriteGroup specified + my $group0 = $self->GetGroup($tagInfo, 0); + $writeGroup or $writeGroup = $group0; + # get priority for this group + unless ($priority) { + if ($$tagInfo{Avoid} and $$tagInfo{WriteAlso}) { + $priority = 0; + } else { + $priority = $$self{WRITE_PRIORITY}{lc($writeGroup)}; + unless ($priority) { + $priority = $$self{WRITE_PRIORITY}{lc($group0)} || 0; + } + } + } + # adjust priority based on Preferred level for this tag + $priority += $prfTag if $prfTag; + } + # don't write tag if protected + my $prot = $$tagInfo{Protected}; + $prot = 1 if $noFlat and defined $$tagInfo{Flat}; + if ($prot) { + $prot &= ~$protected; + if ($prot) { + my %lkup = ( 1=>'unsafe', 2=>'protected', 3=>'unsafe and protected'); + $wasProtected = $lkup{$prot}; + if ($verbose > 1) { + my $wgrp1 = $self->GetWriteGroup1($tagInfo, $writeGroup); + print $out "Sorry, $wgrp1:$tag is $wasProtected for writing\n"; + } + next; + } + } + # set priority for this tag + $tagPriority{$tagInfo} = $priority; + # keep track of highest priority QuickTime tag + $highestQT{$lcTag} = $priority if $$table{GROUPS}{0} eq 'QuickTime' and + (not defined $highestQT{$lcTag} or $highestQT{$lcTag} < $priority); + if ($priority > $highestPriority{$lcTag}) { + $highestPriority{$lcTag} = $priority; + $preferred{$lcTag} = { $tagInfo => 1 }; + $avoid{$lcTag} = $$tagInfo{Avoid} ? 1 : 0; + } elsif ($priority == $highestPriority{$lcTag}) { + # create all tags with highest priority + $preferred{$lcTag}{$tagInfo} = 1; + ++$avoid{$lcTag} if $$tagInfo{Avoid}; + } + if ($$tagInfo{WriteAlso}) { + # store WriteAlso tags separately so we can set them first + push @writeAlsoList, $tagInfo; + } else { + push @tagInfoList, $tagInfo; + } + # special case to allow override of XMP WriteGroup + if ($writeGroup eq 'XMP') { + my $wg = $$tagInfo{WriteGroup} || $$table{WRITE_GROUP}; + $writeGroup = $wg if $wg; + } + $writeGroup{$tagInfo} = $writeGroup; + } + # sort tag info list in reverse order of priority (highest number last) + # so we get the highest priority error message in the end + @tagInfoList = sort { $tagPriority{$a} <=> $tagPriority{$b} } @tagInfoList; + # must write any tags which also write other tags first + unshift @tagInfoList, @writeAlsoList if @writeAlsoList; + + # check priorities for each set of tags we are writing + my $lcTag; + foreach $lcTag (keys %preferred) { + # don't create tags with priority 0 if group priorities are set + if ($preferred{$lcTag} and $highestPriority{$lcTag} == 0 and + %{$$self{WRITE_PRIORITY}}) + { + delete $preferred{$lcTag} + } + # avoid creating tags with 'Avoid' flag set if there are other alternatives + if ($avoid{$lcTag} and $preferred{$lcTag}) { + if ($avoid{$lcTag} < scalar(keys %{$preferred{$lcTag}})) { + # just remove the 'Avoid' tags since there are other preferred tags + foreach $tagInfo (@tagInfoList) { + next unless $lcTag eq lc $$tagInfo{Name}; + delete $preferred{$lcTag}{$tagInfo} if $$tagInfo{Avoid}; + } + } elsif ($highestPriority{$lcTag} < 1000) { + # look for another priority tag to create instead + my $nextHighest = 0; + my @nextBestTags; + foreach $tagInfo (@tagInfoList) { + next unless $lcTag eq lc $$tagInfo{Name}; + my $priority = $tagPriority{$tagInfo} or next; + next if $priority == $highestPriority{$lcTag}; + next if $priority < $nextHighest; + my $permanent = $$tagInfo{Permanent}; + $permanent = $$tagInfo{Table}{PERMANENT} unless defined $permanent; + next if $$tagInfo{Avoid} or $permanent; + next if $writeGroup{$tagInfo} eq 'MakerNotes'; + if ($nextHighest < $priority) { + $nextHighest = $priority; + undef @nextBestTags; + } + push @nextBestTags, $tagInfo; + } + if (@nextBestTags) { + # change our preferred tags to the next best tags + delete $preferred{$lcTag}; + foreach $tagInfo (@nextBestTags) { + $preferred{$lcTag}{$tagInfo} = 1; + } + } + } + } + } +# +# generate new value hash for each tag +# + my ($prioritySet, $createGroups, %alsoWrote); + + delete $$self{CHECK_WARN}; # reset CHECK_PROC warnings + + # loop through all valid tags to find the one(s) to write + foreach $tagInfo (@tagInfoList) { + next if $alsoWrote{$tagInfo}; # don't rewrite tags we already wrote + # only process List or non-List tags if specified + next if defined $listOnly and ($listOnly xor $$tagInfo{List}); + my $noConv; + my $writeGroup = $writeGroup{$tagInfo}; + my $permanent = $$tagInfo{Permanent}; + $permanent = $$tagInfo{Table}{PERMANENT} unless defined $permanent; + $writeGroup eq 'MakerNotes' and $permanent = 1 unless defined $permanent; + my $wgrp1 = $self->GetWriteGroup1($tagInfo, $writeGroup); + $tag = $$tagInfo{Name}; # get tag name for warnings + my $lcTag = lc $tag; + my $pref = $preferred{$lcTag} || { }; + # don't write Avoid-ed tags with side effect unless preferred + next if not $$pref{$tagInfo} and $$tagInfo{Avoid} and $$tagInfo{WriteAlso}; + my $shift = $options{Shift}; + my $addValue = $options{AddValue}; + if (defined $shift) { + # (can't currently shift list-type tags) + my $shiftable; + if ($$tagInfo{List}) { + $shiftable = ''; # can add/delete but not shift + } else { + $shiftable = $$tagInfo{Shift}; + unless ($shift) { + # set shift according to AddValue/DelValue + $shift = 1 if $addValue; + # can shift a date/time with -=, but this is + # a conditional delete operation for other tags + $shift = -1 if $options{DelValue} and defined $shiftable and $shiftable eq 'Time'; + } + if ($shift and (not defined $value or not length $value)) { + # (now allow -= to be used for shiftable tag - v8.05) + #$err = "No value for time shift of $wgrp1:$tag"; + #$verbose > 2 and print $out "$err\n"; + #next; + undef $shift; + } + } + # can't shift List-type tag + if ((defined $shiftable and not $shiftable) and + # and don't try to conditionally delete if Shift is "0" + ($shift or ($shiftable eq '0' and $options{DelValue}))) + { + $err = "$wgrp1:$tag is not shiftable"; + $verbose and print $out "$err\n"; + next; + } + } + my $val = $value; + if (defined $val) { + # check to make sure this is a List or Shift tag if adding + if ($addValue and not ($shift or $$tagInfo{List})) { + if ($addValue eq '2') { + undef $addValue; # quietly reset this option + } else { + $err = "Can't add $wgrp1:$tag (not a List type)"; + $verbose > 2 and print $out "$err\n"; + next; + } + } + if ($shift) { + if ($$tagInfo{Shift} and $$tagInfo{Shift} eq 'Time') { + # add '+' or '-' prefix to indicate shift direction + $val = ($shift > 0 ? '+' : '-') . $val; + # check the shift for validity + require 'Image/ExifTool/Shift.pl'; + my $err2 = CheckShift($$tagInfo{Shift}, $val); + if ($err2) { + $err = "$err2 for $wgrp1:$tag"; + $verbose > 2 and print $out "$err\n"; + next; + } + } elsif (IsFloat($val)) { + $val *= $shift; + } else { + $err = "Shift value for $wgrp1:$tag is not a number"; + $verbose > 2 and print $out "$err\n"; + next; + } + $noConv = 1; # no conversions if shifting tag + } elsif (not length $val and $options{DelValue}) { + $noConv = 1; # no conversions for deleting empty value + } elsif (ref $val eq 'HASH' and not $$tagInfo{Struct}) { + $err = "Can't write a structure to $wgrp1:$tag"; + $verbose > 2 and print $out "$err\n"; + next; + } + } elsif ($permanent) { + return 0 if $options{IgnorePermanent}; + # can't delete permanent tags, so set them to DelValue or empty string instead + if (defined $$tagInfo{DelValue}) { + $val = $$tagInfo{DelValue}; + $noConv = 1; # DelValue is the raw value, so no conversion necessary + } else { + $val = ''; + } + } elsif ($addValue or $options{DelValue}) { + $err = "No value to add or delete in $wgrp1:$tag"; + $verbose > 2 and print $out "$err\n"; + next; + } else { + if ($$tagInfo{DelCheck}) { + #### eval DelCheck ($self, $tagInfo, $wantGroup) + my $err2 = eval $$tagInfo{DelCheck}; + $@ and warn($@), $err2 = 'Error evaluating DelCheck'; + if (defined $err2) { + # (allow other tags to be set using DelCheck as a hook) + $err2 or goto WriteAlso; # GOTO! + $err2 .= ' for' unless $err2 =~ /delete$/; + $err = "$err2 $wgrp1:$tag"; + $verbose > 2 and print $out "$err\n"; + next; + } + } + # set group delete flag if this tag represents an entire group + if ($$tagInfo{DelGroup} and not $options{DelValue}) { + my @del = ( $tag ); + $$self{DEL_GROUP}{$tag} = 1; + # delete extra groups if necessary + if ($delMore{$tag}) { + $$self{DEL_GROUP}{$_} = 1, push(@del,$_) foreach @{$delMore{$tag}}; + } + # remove all of this group from previous new values + $self->RemoveNewValuesForGroup($tag); + $verbose and print $out " Deleting tags in: @del\n"; + ++$numSet; + next; + } + $noConv = 1; # value is not defined, so don't do conversion + } + # apply inverse PrintConv and ValueConv conversions + # save ValueConv setting for use in ConvInv() + unless ($noConv) { + # set default conversion type used by ConvInv() and CHECK_PROC routines + $$self{ConvType} = $convType; + my $e; + ($val,$e) = $self->ConvInv($val,$tagInfo,$tag,$wgrp1,$$self{ConvType},$wantGroup); + if (defined $e) { + # empty error string causes error to be ignored without setting the value + $e or goto WriteAlso; # GOTO! + $err = $e; + } + } + if (not defined $val and defined $value) { + # if value conversion failed, we must still add a NEW_VALUE + # entry for this tag it it was a DelValue + next unless $options{DelValue}; + $val = 'xxx never delete xxx'; + } + $$self{NEW_VALUE} or $$self{NEW_VALUE} = { }; + if ($options{Replace}) { + # delete the previous new value + $self->GetNewValueHash($tagInfo, $writeGroup, 'delete', $options{ProtectSaved}); + # also delete related tag previous new values + if ($$tagInfo{WriteAlso}) { + $$self{INDENT2} = '+'; + my ($wgrp, $wtag); + if ($$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All' and $writeGroup) { + $wgrp = $writeGroup . ':'; + } else { + $wgrp = ''; + } + foreach $wtag (sort keys %{$$tagInfo{WriteAlso}}) { + my ($n,$e) = $self->SetNewValue($wgrp . $wtag, undef, Replace=>2); + $numSet += $n; + } + $$self{INDENT2} = ''; + } + $options{Replace} == 2 and ++$numSet, next; + } + + if (defined $val) { + # we are editing this tag, so create a NEW_VALUE hash entry + my $nvHash = $self->GetNewValueHash($tagInfo, $writeGroup, 'create', + $options{ProtectSaved}, ($options{DelValue} and not $shift)); + # ignore new values protected with ProtectSaved + $nvHash or ++$numSet, next; # (increment $numSet to avoid warning) + $$nvHash{NoReplace} = 1 if $$tagInfo{List} and not $options{Replace}; + $$nvHash{WantGroup} = $wantGroup; + $$nvHash{EditOnly} = 1 if $editOnly; + # save maker note information if writing maker notes + if ($$tagInfo{MakerNotes}) { + $$nvHash{MAKER_NOTE_FIXUP} = $$self{MAKER_NOTE_FIXUP}; + } + if ($createOnly) { # create only (never edit) + # empty item in DelValue list to never edit existing value + $$nvHash{DelValue} = [ '' ]; + $$nvHash{CreateOnly} = 1; + } elsif ($options{DelValue} or $addValue or $shift) { + # flag any AddValue or DelValue by creating the DelValue list + $$nvHash{DelValue} or $$nvHash{DelValue} = [ ]; + if ($shift) { + # add shift value to list + $$nvHash{Shift} = $val; + } elsif ($options{DelValue}) { + # don't create if we are replacing a specific value + $$nvHash{IsCreating} = 0 unless $val eq '' or $$tagInfo{List}; + # add delete value to list + push @{$$nvHash{DelValue}}, ref $val eq 'ARRAY' ? @$val : $val; + if ($verbose > 1) { + my $verb = $permanent ? 'Replacing' : 'Deleting'; + my $fromList = $$tagInfo{List} ? ' from list' : ''; + my @vals = (ref $val eq 'ARRAY' ? @$val : $val); + foreach (@vals) { + if (ref $_ eq 'HASH') { + require 'Image/ExifTool/XMPStruct.pl'; + $_ = Image::ExifTool::XMP::SerializeStruct($self, $_); + } + print $out "$$self{INDENT2}$verb $wgrp1:$tag$fromList if value is '${_}'\n"; + } + } + } + } + # set priority flag to add only the high priority info + # (will only create the priority tag if it doesn't exist, + # others get changed only if they already exist) + my $prf = defined $$tagInfo{Preferred} ? $$tagInfo{Preferred} : $$tagInfo{Table}{PREFERRED}; + # hack to prefer only a single tag in the QuickTime group + if ($$tagInfo{Table}{GROUPS}{0} eq 'QuickTime') { + $prf = 0 if $tagPriority{$tagInfo} < $highestQT{$lcTag}; + } + if ($$pref{$tagInfo} or $prf) { + if ($permanent or $shift) { + # don't create permanent or Shift-ed tag but define IsCreating + # so we know that it is the preferred tag + $$nvHash{IsCreating} = 0; + } elsif (($$tagInfo{List} and not $options{DelValue}) or + not ($$nvHash{DelValue} and @{$$nvHash{DelValue}}) or + # also create tag if any DelValue value is empty ('') + grep(/^$/,@{$$nvHash{DelValue}})) + { + $$nvHash{IsCreating} = $editOnly ? 0 : ($editGroup ? 2 : 1); + # add to hash of groups where this tag is being created + $createGroups or $createGroups = $options{CreateGroups} || { }; + $$createGroups{$self->GetGroup($tagInfo, 0)} = 1; + $$nvHash{CreateGroups} = $createGroups; + } + } + if ($$nvHash{IsCreating}) { + if (%{$$self{DEL_GROUP}}) { + my ($grp, @grps); + foreach $grp (keys %{$$self{DEL_GROUP}}) { + next if $$self{DEL_GROUP}{$grp} == 2; + # set flag indicating tags were written after this group was deleted + $$self{DEL_GROUP}{$grp} = 2; + push @grps, $grp; + } + if ($verbose > 1 and @grps) { + @grps = sort @grps; + print $out " Writing new tags after deleting groups: @grps\n"; + } + } + } elsif ($createOnly) { + $noCreate = $permanent ? 'permanent' : ($$tagInfo{Avoid} ? 'avoided' : ''); + $noCreate or $noCreate = $shift ? 'shifting' : 'not preferred'; + $verbose > 2 and print $out "Not creating $wgrp1:$tag ($noCreate)\n"; + next; # nothing to do (not creating and not editing) + } + if ($shift or not $options{DelValue}) { + $$nvHash{Value} or $$nvHash{Value} = [ ]; + if (not $$tagInfo{List}) { + # not a List tag -- overwrite existing value + $$nvHash{Value}[0] = $val; + } elsif (defined $$nvHash{AddBefore} and @{$$nvHash{Value}} >= $$nvHash{AddBefore}) { + # values from a later argument have been added (ie. Replace=0) + # to this list, so the new values should come before these + splice @{$$nvHash{Value}}, -$$nvHash{AddBefore}, 0, ref $val eq 'ARRAY' ? @$val : $val; + } else { + # add at end of existing list + push @{$$nvHash{Value}}, ref $val eq 'ARRAY' ? @$val : $val; + } + if ($verbose > 1) { + my $ifExists; + if ($$tagInfo{IsComposite}) { + # (composite tags don't technically exist in the file) + if ($$tagInfo{WriteAlso}) { + $ifExists = ' (+' . join(',+',sort keys %{$$tagInfo{WriteAlso}}) . '):'; + } else { + $ifExists = ''; + } + } else { + $ifExists = $$nvHash{IsCreating} ? ( $createOnly ? + ($$nvHash{IsCreating} == 2 ? + " if $writeGroup exists and tag doesn't" : + " if tag doesn't exist") : + ($$nvHash{IsCreating} == 2 ? " if $writeGroup exists" : '')) : + (($$nvHash{DelValue} and @{$$nvHash{DelValue}}) ? + ' if tag was deleted' : ' if tag exists'); + } + my $verb = ($shift ? 'Shifting' : ($addValue ? 'Adding' : 'Writing')); + print $out "$$self{INDENT2}$verb $wgrp1:$tag$ifExists\n"; + } + } + } elsif ($permanent) { + $err = "Can't delete Permanent tag $wgrp1:$tag"; + $verbose > 1 and print $out "$err\n"; + next; + } elsif ($addValue or $options{DelValue}) { + $verbose > 1 and print $out "Adding/Deleting nothing does nothing\n"; + next; + } else { + # create empty new value hash entry to delete this tag + $self->GetNewValueHash($tagInfo, $writeGroup, 'delete'); + my $nvHash = $self->GetNewValueHash($tagInfo, $writeGroup, 'create'); + $$nvHash{WantGroup} = $wantGroup; + $verbose > 1 and print $out "$$self{INDENT2}Deleting $wgrp1:$tag\n"; + } + $$setTags{$tagInfo} = 1 if $setTags; + $prioritySet = 1 if $$pref{$tagInfo}; +WriteAlso: + ++$numSet; + # also write related tags + my $writeAlso = $$tagInfo{WriteAlso}; + if ($writeAlso) { + $$self{INDENT2} = '+'; # indicate related tag with a leading "+" + my ($wgrp, $wtag, $n); + if ($$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All' and $writeGroup) { + $wgrp = $writeGroup . ':'; + } else { + $wgrp = ''; + } + local $SIG{'__WARN__'} = \&SetWarning; + foreach $wtag (sort keys %$writeAlso) { + my %opts = ( + Type => 'ValueConv', + Protected => $protected | 0x02, + AddValue => $addValue, + DelValue => $options{DelValue}, + Shift => $options{Shift}, + Replace => $options{Replace}, # handle lists properly + CreateGroups=> $createGroups, + SetTags => \%alsoWrote, # remember tags already written + ); + undef $evalWarning; + #### eval WriteAlso ($val,%opts) + my $v = eval $$writeAlso{$wtag}; + # we wanted to do the eval in case there are side effect, but we + # don't want to write a value for a tag that is being deleted: + undef $v unless defined $val; + $@ and $evalWarning = $@; + unless ($evalWarning) { + ($n,$evalWarning) = $self->SetNewValue($wgrp . $wtag, $v, %opts); + $numSet += $n; + # count this as being set if any related tag is set + $prioritySet = 1 if $n and $$pref{$tagInfo}; + } + if ($evalWarning and (not $err or $verbose > 2)) { + my $str = CleanWarning(); + if ($str) { + $str .= " for $wtag" unless $str =~ / for [-\w:]+$/; + $str .= " in $wgrp1:$tag (WriteAlso)"; + $err or $err = $str; + print $out "$str\n" if $verbose > 2; + } + } + } + $$self{INDENT2} = ''; + } + } + # print warning if we couldn't set our priority tag + if (defined $err and not $prioritySet) { + warn "$err\n" if $err and not wantarray; + } elsif (not $numSet) { + my $pre = $wantGroup ? $wantGroup . ':' : ''; + if ($wasProtected) { + $verbose = 0; # we already printed this verbose message + unless ($options{Replace} and $options{Replace} == 2) { + $err = "Sorry, $pre$tag is $wasProtected for writing"; + } + } elsif (not $listOnly) { + if ($origTag =~ /[?*]/) { + if ($noCreate) { + $err = "No tags matching 'pre${origTag}' will be created"; + $verbose = 0; # (already printed) + } elsif ($foundMatch) { + $err = "Sorry, no writable tags matching '$pre${origTag}'"; + } else { + $err = "No matching tags for '$pre${origTag}'"; + } + } elsif ($noCreate) { + $err = "Not creating $pre$tag"; + $verbose = 0; # (already printed) + } elsif ($foundMatch) { + $err = "Sorry, $pre$tag is not writable"; + } elsif ($wantGroup and @matchingTags) { + $err = "Sorry, $pre$tag doesn't exist or isn't writable"; + } else { + $err = "Tag '$pre${tag}' is not defined"; + } + } + if ($err) { + $verbose > 2 and print $out "$err\n"; + warn "$err\n" unless wantarray; + } + } elsif ($$self{CHECK_WARN}) { + $err = $$self{CHECK_WARN}; + $verbose > 2 and print $out "$err\n"; + } elsif ($err and not $verbose) { + undef $err; + } + return ($numSet, $err) if wantarray; + return $numSet; +} + +#------------------------------------------------------------------------------ +# set new values from information in specified file +# Inputs: 0) ExifTool object reference, 1) source file name or reference, etc +# 2-N) List of tags to set (or all if none specified), or reference(s) to +# hash for options to pass to SetNewValue. The Replace option defaults +# to 1 for SetNewValuesFromFile -- set this to 0 to allow multiple tags +# to be copied to a list +# Returns: Hash of information set successfully (includes Warning or Error messages) +# Notes: Tag names may contain a group prefix, a leading '-' to exclude from copy, +# and/or a trailing '#' to copy the ValueConv value. The tag name '*' may +# be used to represent all tags in a group. An optional destination tag +# may be specified with '>DSTTAG' ('DSTTAG<TAG' also works, but in this +# case the source tag may also be an expression involving tag names). +sub SetNewValuesFromFile($$;@) +{ + local $_; + my ($self, $srcFile, @setTags) = @_; + my ($key, $tag, @exclude, @reqTags); + + # get initial SetNewValuesFromFile options + my %opts = ( Replace => 1 ); # replace existing list items by default + while (ref $setTags[0] eq 'HASH') { + $_ = shift @setTags; + foreach $key (keys %$_) { + $opts{$key} = $$_{$key}; + } + } + # expand shortcuts + @setTags and ExpandShortcuts(\@setTags); + my $srcExifTool = new Image::ExifTool; + # set flag to indicate we are being called from inside SetNewValuesFromFile() + $$srcExifTool{TAGS_FROM_FILE} = 1; + # synchronize and increment the file sequence number + $$srcExifTool{FILE_SEQUENCE} = $$self{FILE_SEQUENCE}++; + # set options for our extraction tool + my $options = $$self{OPTIONS}; + # copy both structured and flattened tags by default (but flattened tags are "unsafe") + my $structOpt = defined $$options{Struct} ? $$options{Struct} : 2; + # copy structures only if no tags specified (since flattened tags are "unsafe") + $structOpt = 1 if $structOpt eq '2' and not @setTags; + # +------------------------------------------+ + # ! DON'T FORGET!! Must consider each new ! + # ! option to decide how it is handled here. ! + # +------------------------------------------+ + $srcExifTool->Options( + Binary => 1, + Charset => $$options{Charset}, + CharsetEXIF => $$options{CharsetEXIF}, + CharsetFileName => $$options{CharsetFileName}, + CharsetID3 => $$options{CharsetID3}, + CharsetIPTC => $$options{CharsetIPTC}, + CharsetPhotoshop=> $$options{CharsetPhotoshop}, + Composite => $$options{Composite}, + CoordFormat => $$options{CoordFormat} || '%d %d %.8f', # copy coordinates at high resolution unless otherwise specified + DateFormat => $$options{DateFormat}, + Duplicates => 1, + Escape => $$options{Escape}, + # Exclude (set below) + ExtendedXMP => $$options{ExtendedXMP}, + ExtractEmbedded => $$options{ExtractEmbedded}, + FastScan => $$options{FastScan}, + Filter => $$options{Filter}, + FixBase => $$options{FixBase}, + GlobalTimeShift => $$options{GlobalTimeShift}, + HexTagIDs => $$options{HexTagIDs}, + IgnoreMinorErrors=>$$options{IgnoreMinorErrors}, + IgnoreTags => $$options{IgnoreTags}, + ImageHashType => $$options{ImageHashType}, + Lang => $$options{Lang}, + LargeFileSupport=> $$options{LargeFileSupport}, + List => 1, + ListItem => $$options{ListItem}, + ListSep => $$options{ListSep}, + MakerNotes => $$options{FastScan} && $$options{FastScan} > 1 ? undef : 1, + MDItemTags => $$options{MDItemTags}, + MissingTagValue => $$options{MissingTagValue}, + NoPDFList => $$options{NoPDFList}, + NoWarning => $$options{NoWarning}, + Password => $$options{Password}, + PrintConv => $$options{PrintConv}, + QuickTimeUTC => $$options{QuickTimeUTC}, + RequestAll => $$options{RequestAll} || 1, # (is this still necessary now that RequestTags are being set?) + RequestTags => $$options{RequestTags}, + SaveFormat => $$options{SaveFormat}, + SavePath => $$options{SavePath}, + ScanForXMP => $$options{ScanForXMP}, + StrictDate => defined $$options{StrictDate} ? $$options{StrictDate} : 1, + Struct => $structOpt, + StructFormat => $$options{StructFormat}, + SystemTags => $$options{SystemTags}, + TimeZone => $$options{TimeZone}, + Unknown => $$options{Unknown}, + UserParam => $$options{UserParam}, + Validate => $$options{Validate}, + WindowsWideFile => $$options{WindowsWideFile}, + XAttrTags => $$options{XAttrTags}, + XMPAutoConv => $$options{XMPAutoConv}, + ); + $$srcExifTool{GLOBAL_TIME_OFFSET} = $$self{GLOBAL_TIME_OFFSET}; + $$srcExifTool{ALT_EXIFTOOL} = $$self{ALT_EXIFTOOL}; + foreach $tag (@setTags) { + next if ref $tag; + if ($tag =~ /^-(.*)/) { + # avoid extracting tags that are excluded + push @exclude, $1; + next; + } + # add specified tags to list of requested tags + $_ = $tag; + if (/(.+?)\s*(>|<)\s*(.+)/) { + if ($2 eq '>') { + $_ = $1; + } else { + $_ = $3; + /\$/ and push(@reqTags, /\$\{?(?:[-\w]+:)*([-\w?*]+)/g), next; + } + } + push @reqTags, $2 if /(^|:)([-\w?*]+)#?$/; + } + if (@exclude) { + ExpandShortcuts(\@exclude, 1); + $srcExifTool->Options(Exclude => \@exclude); + } + $srcExifTool->Options(RequestTags => \@reqTags) if @reqTags; + my $printConv = $$options{PrintConv}; + if ($opts{Type}) { + # save source type separately because it may be different than dst Type + $opts{SrcType} = $opts{Type}; + # override PrintConv option with initial Type if given + $printConv = ($opts{Type} eq 'PrintConv' ? 1 : 0); + $srcExifTool->Options(PrintConv => $printConv); + } + my $srcType = $printConv ? 'PrintConv' : 'ValueConv'; + + # get all tags from source file (including MakerNotes block) + my $info = $srcExifTool->ImageInfo($srcFile); + return $info if $$info{Error} and $$info{Error} eq 'Error opening file'; + delete $$srcExifTool{VALUE}{Error}; # delete so we can check this later + + # sort tags in reverse order so we get priority tag last + my @tags = reverse sort keys %$info; +# +# simply transfer all tags from source image if no tags specified +# + unless (@setTags) { + # transfer maker note information to this object + $$self{MAKER_NOTE_FIXUP} = $$srcExifTool{MAKER_NOTE_FIXUP}; + $$self{MAKER_NOTE_BYTE_ORDER} = $$srcExifTool{MAKER_NOTE_BYTE_ORDER}; + foreach $tag (@tags) { + # don't try to set errors or warnings + next if $tag =~ /^(Error|Warning)\b/; + # get appropriate value type if necessary + if ($opts{SrcType} and $opts{SrcType} ne $srcType) { + $$info{$tag} = $srcExifTool->GetValue($tag, $opts{SrcType}); + } + # set value for this tag + my ($n, $e) = $self->SetNewValue($tag, $$info{$tag}, %opts); + # delete this tag if we couldn't set it + $n or delete $$info{$tag}; + } + return $info; + } +# +# transfer specified tags in the proper order +# + # 1) loop through input list of tags to set, and build @setList + my (@setList, $set, %setMatches, $t, %altFiles); + foreach $t (@setTags) { + if (ref $t eq 'HASH') { + # update current options + foreach $key (keys %$t) { + $opts{$key} = $$t{$key}; + } + next; + } + # make a copy of the current options for this setTag + # (also use this hash to store expression and wildcard flags, EXPR and WILD) + my $opts = { %opts }; + $tag = lc $t; # change tag/group names to all lower case + my (@fg, $grp, $dst, $dstGrp, $dstTag, $isExclude); + # handle redirection to another tag + if ($tag =~ /(.+?)\s*(>|<)\s*(.+)/) { + $dstGrp = ''; + my $opt; + if ($2 eq '>') { + ($tag, $dstTag) = ($1, $3); + # flag add and delete (eg. '+<' and '-<') redirections + $opt = $1 if $tag =~ s/\s*([-+])$// or $dstTag =~ s/^([-+])\s*//; + } else { + ($tag, $dstTag) = ($3, $1); + $opt = $1 if $dstTag =~ s/\s*([-+])$//; + # handle expressions + if ($tag =~ /\$/) { + $tag = $t; # restore original case + # recover leading whitespace (except for initial single space) + $tag =~ s/(.+?)\s*(>|<) ?//; + $$opts{EXPR} = 1; # flag this expression + } else { + # (not sure why this is here because sign should be before '<') + # (--> allows "<+" or "<-", which is an undocumented feature) + $opt = $1 if $tag =~ s/^([-+])\s*//; + } + } + $$opts{Replace} = 0 if $dstTag =~ s/^\+//; + # validate tag name(s) + unless ($$opts{EXPR} or ValidTagName($tag)) { + $self->Warn("Invalid tag name '${tag}'. Use '=' not '<' to assign a tag value"); + next; + } + ValidTagName($dstTag) or $self->Warn("Invalid tag name '${dstTag}'"), next; + # translate '+' and '-' to appropriate SetNewValue option + if ($opt) { + $$opts{{ '+' => 'AddValue', '-' => 'DelValue' }->{$opt}} = 1; + $$opts{Shift} = 0; # shift if shiftable + } + ($dstGrp, $dstTag) = ($1, $2) if $dstTag =~ /(.*):(.+)/; + # ValueConv may be specified separately on the destination with '#' + $$opts{Type} = 'ValueConv' if $dstTag =~ s/#$//; + # replace tag name of 'all' with '*' + $dstTag = '*' if $dstTag eq 'all'; + } else { + $$opts{Replace} = 0 if $tag =~ s/^\+//; + } + unless ($$opts{EXPR}) { + $isExclude = ($tag =~ s/^-//); + if ($tag =~ /(.*):(.+)/) { + ($grp, $tag) = ($1, $2); + foreach (split /:/, $grp) { + # save family/groups in list (ignoring 'all' and '*') + next unless length($_) and /^(\d+)?(.*)/; + my ($f, $g) = ($1, $2); + $f = 7 if (not $f or $f eq '7') and $g =~ s/^ID-//i; + if ($g =~ /^file\d+$/i and (not $f or $f eq '8')) { + $f = 8; + my $g8 = ucfirst $g; + if ($$srcExifTool{ALT_EXIFTOOL}{$g8}) { + $$opts{GROUP8} = $g8; + $altFiles{$g8} or $altFiles{$g8} = [ ]; + # save list of requested tags for this alternate ExifTool object + push @{$altFiles{$g8}}, "$grp:$tag"; + } + } + push @fg, [ $f, $g ] unless $g eq '*' or $g eq 'all'; + } + } + # allow ValueConv to be specified by a '#' on the tag name + if ($tag =~ s/#$//) { + $$opts{SrcType} = 'ValueConv'; + $$opts{Type} = 'ValueConv' unless $dstTag; + } + # replace 'all' with '*' in tag and group names + $tag = '*' if $tag eq 'all'; + # allow wildcards in tag names (handle differently from all tags: '*') + if ($tag =~ /[?*]/ and $tag ne '*') { + $$opts{WILD} = 1; # set flag indicating wildcards were used in source tag + $tag =~ s/\*/[-\\w]*/g; + $tag =~ s/\?/[-\\w]/g; + } + } + # redirect, exclude or set this tag (Note: @fg is empty if we don't care about the group) + if ($dstTag) { + # redirect this tag + $isExclude and return { Error => "Can't redirect excluded tag" }; + # set destination group the same as source if necessary + # (removed in 7.72 so '-*:*<xmp:*' will preserve XMP family 1 groups) + # $dstGrp = $grp if $dstGrp eq '*' and $grp; + # write to specified destination group/tag + $dst = [ $dstGrp, $dstTag ]; + } elsif ($isExclude) { + # implicitly assume '*' if first entry is an exclusion + unshift @setList, [ [ ], '*', [ '', '*' ], $opts ] unless @setList; + # exclude this tag by leaving $dst undefined + } else { + $dst = [ $grp || '', $$opts{WILD} ? '*' : $tag ]; # use same group name for dest + } + # save in reverse order so we don't set tags before an exclude + unshift @setList, [ \@fg, $tag, $dst, $opts ]; + } + # 1b) copy requested tags for each alternate ExifTool object + my $g8; + foreach $g8 (sort keys %altFiles) { + # request specific alternate tags to load them from the alternate ExifTool object + my $altInfo = $srcExifTool->GetInfo($altFiles{$g8}); + # add to tags list after dummy entry to signify start of tags for this alt file + if (%$altInfo) { + push @tags, 'Warning DUMMY', reverse sort keys %$altInfo; + $$info{$_} = $$altInfo{$_} foreach keys %$altInfo; + } + } + # 2) initialize lists of matching tags for each setTag + foreach $set (@setList) { + $$set[2] and $setMatches{$set} = [ ]; + } + # 3) loop through all tags in source image and save tags matching each setTag + my (%rtnInfo, $isAlt); + foreach $tag (@tags) { + # don't try to set errors or warnings + if ($tag =~ /^(Error|Warning)( |$)/) { + if ($tag eq 'Warning DUMMY') { + $isAlt = 1; # start of the alt tags + } else { + $rtnInfo{$tag} = $$info{$tag}; + } + next; + } + # only set specified tags + my $lcTag = lc(GetTagName($tag)); + my (@grp, %grp); +SET: foreach $set (@setList) { + my $opts = $$set[3]; + next if $$opts{EXPR}; # (expressions handled in step 4) + next if $$opts{GROUP8} xor $isAlt; + # check first for matching tag + unless ($$set[1] eq $lcTag or $$set[1] eq '*') { + # handle wildcards + next unless $$opts{WILD} and $lcTag =~ /^$$set[1]$/; + } + # then check for matching group + if (@{$$set[0]}) { + # get lower case group names if not done already + unless (@grp) { + @grp = map(lc, $srcExifTool->GetGroup($tag)); + $grp{$_} = 1 foreach @grp; + } + foreach (@{$$set[0]}) { + my ($f, $g) = @$_; + if (not defined $f) { + next SET unless $grp{$g}; + } elsif ($f == 7) { + next SET unless IsSameID($srcExifTool->GetTagID($tag), $g); + } else { + next SET unless defined $grp[$f] and $g eq $grp[$f]; + } + } + } + last unless $$set[2]; # all done if we hit an exclude + # add to the list of tags matching this setTag + push @{$setMatches{$set}}, $tag; + } + } + # 4) loop through each setTag in original order, setting new tag values + foreach $set (reverse @setList) { + # get options for SetNewValue + my $opts = $$set[3]; + # handle expressions + if ($$opts{EXPR}) { + my $val = $srcExifTool->InsertTagValues(\@tags, $$set[1], 'Error'); + my $err = $$srcExifTool{VALUE}{Error}; + if ($err) { + # pass on any error as a warning unless it is suppressed + my $noWarn = $$srcExifTool{OPTIONS}{NoWarning}; + unless ($noWarn and (eval { $err =~ /$noWarn/ } or + # (also apply expression to warning without "[minor] " prefix) + ($err =~ s/^\[minor\] //i and eval { $err =~ /$noWarn/ }))) + { + $tag = NextFreeTagKey(\%rtnInfo, 'Warning'); + $rtnInfo{$tag} = $$srcExifTool{VALUE}{Error}; + } + delete $$srcExifTool{VALUE}{Error}; + next unless defined $val; + } + my ($dstGrp, $dstTag) = @{$$set[2]}; + $$opts{Protected} = 1 unless $dstTag =~ /[?*]/ and $dstTag ne '*'; + $$opts{Group} = $dstGrp if $dstGrp; + my @rtnVals = $self->SetNewValue($dstTag, $val, %$opts); + $rtnInfo{$dstTag} = $val if $rtnVals[0]; # tag was set successfully + next; + } + foreach $tag (@{$setMatches{$set}}) { + my ($val, $noWarn); + if ($$opts{SrcType} and $$opts{SrcType} ne $srcType) { + $val = $srcExifTool->GetValue($tag, $$opts{SrcType}); + } else { + $val = $$info{$tag}; + } + my ($dstGrp, $dstTag) = @{$$set[2]}; + if ($dstGrp) { + my @dstGrp = split /:/, $dstGrp; + # destination group of '*' writes to same group as source tag + # (family 1 unless otherwise specified) + foreach (@dstGrp) { + next unless /^(\d*)(all|\*)$/i; + $_ = $1 . $srcExifTool->GetGroup($tag, length $1 ? $1 : 1); + $noWarn = 1; # don't warn on wildcard destinations + } + $$opts{Group} = join ':', @dstGrp; + } else { + delete $$opts{Group}; + } + # transfer maker note information if setting this tag + if ($$srcExifTool{TAG_INFO}{$tag}{MakerNotes}) { + $$self{MAKER_NOTE_FIXUP} = $$srcExifTool{MAKER_NOTE_FIXUP}; + $$self{MAKER_NOTE_BYTE_ORDER} = $$srcExifTool{MAKER_NOTE_BYTE_ORDER}; + } + if ($dstTag eq '*') { + $dstTag = $tag; + $noWarn = 1; + } + if ($$set[1] eq '*' or $$set[3]{WILD}) { + # don't copy from protected binary tags when using wildcards + next if $$srcExifTool{TAG_INFO}{$tag}{Protected} and + $$srcExifTool{TAG_INFO}{$tag}{Binary}; + # don't copy to protected tags when using wildcards + delete $$opts{Protected}; + # don't copy flattened tags if copying structures too when copying all + $$opts{NoFlat} = $structOpt eq '2' ? 1 : 0; + } else { + # allow protected tags to be copied if specified explicitly + $$opts{Protected} = 1 unless $dstTag =~ /[?*]/; + delete $$opts{NoFlat}; + } + # set value(s) for this tag + my ($rtn, $wrn) = $self->SetNewValue($dstTag, $val, %$opts); + # this was added in version 9.14, and allowed actions like "-subject<all" to + # write values of multiple tags into a list, but it had the side effect of + # duplicating items if there were multiple list tags with the same name + # (eg. -use mwg "-creator<creator"), so disable this as of ExifTool 9.36: + # $$opts{Replace} = 0; # accumulate values from tags matching a single argument + if ($wrn and not $noWarn) { + # return this warning + $rtnInfo{NextFreeTagKey(\%rtnInfo, 'Warning')} = $wrn; + $noWarn = 1; + } + $rtnInfo{$tag} = $val if $rtn; # tag was set successfully + } + } + return \%rtnInfo; # return information that we set +} + +#------------------------------------------------------------------------------ +# Get new value(s) for tag +# Inputs: 0) ExifTool object reference, 1) tag name (or tagInfo or nvHash ref, not public) +# 2) optional pointer to return new value hash reference (not part of public API) +# Returns: List of new Raw values (list may be empty if tag is being deleted) +# Notes: 1) Preferentially returns new value from Extra table if writable Extra tag exists +# 2) Must call AFTER IsOverwriting() returns 1 to get proper value for shifted times +# 3) Tag name is case sensitive and may be prefixed by family 0 or 1 group name +# 4) Value may have been modified by CHECK_PROC routine after ValueConv +sub GetNewValue($$;$) +{ + local $_; + my $self = shift; + my $tag = shift; + my $nvHash; + if ((ref $tag eq 'HASH' and $$tag{IsNVH}) or not defined $tag) { + $nvHash = $tag; + } else { + my $newValueHashPt = shift; + if ($$self{NEW_VALUE}) { + my ($group, $tagInfo); + if (ref $tag) { + $nvHash = $self->GetNewValueHash($tag); + } elsif (defined($tagInfo = $Image::ExifTool::Extra{$tag}) and + $$tagInfo{Writable}) + { + $nvHash = $self->GetNewValueHash($tagInfo); + } else { + # separate group from tag name + my @groups; + @groups = split ':', $1 if $tag =~ s/(.*)://; + my @tagInfoList = FindTagInfo($tag); + # decide which tag we want +GNV_TagInfo: foreach $tagInfo (@tagInfoList) { + my $nvh = $self->GetNewValueHash($tagInfo) or next; + # select tag in specified group(s) if necessary + foreach (@groups) { + next if $_ eq $$nvh{WriteGroup}; + my @grps = $self->GetGroup($tagInfo); + if ($grps[0] eq $$nvh{WriteGroup}) { + # check family 1 group only if WriteGroup is not specific + next if $_ eq $grps[1]; + } else { + # otherwise check family 0 group + next if $_ eq $grps[0]; + } + # also check family 7 + next if /^ID-(.*)/i and IsSameID($$tagInfo{TagID}, $1); + # step to next entry in list + $nvh = $$nvh{Next} or next GNV_TagInfo; + } + $nvHash = $nvh; + # give priority to the one we are creating + last if defined $$nvHash{IsCreating}; + } + } + } + # return new value hash if requested + $newValueHashPt and $$newValueHashPt = $nvHash; + } + unless ($nvHash and $$nvHash{Value}) { + return () if wantarray; # return empty list + return undef; + } + my $vals = $$nvHash{Value}; + # do inverse raw conversion if necessary + # - must also check after doing a Shift + if ($$nvHash{TagInfo}{RawConvInv} or $$nvHash{Shift}) { + my @copyVals = @$vals; # modify a copy of the values + $vals = \@copyVals; + my $tagInfo = $$nvHash{TagInfo}; + my $conv = $$tagInfo{RawConvInv}; + my $table = $$tagInfo{Table}; + my ($val, $checkProc); + $checkProc = $$table{CHECK_PROC} if $$nvHash{Shift} and $table; + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + foreach $val (@$vals) { + # must check value now if it was shifted + if ($checkProc) { + my $err = &$checkProc($self, $tagInfo, \$val); + if ($err or not defined $val) { + $err or $err = 'Error generating raw value'; + $self->WarnOnce("$err for $$tagInfo{Name}"); + @$vals = (); + last; + } + next unless $conv; + } else { + last unless $conv; + } + # do inverse raw conversion + if (ref($conv) eq 'CODE') { + $val = &$conv($val, $self); + } else { + #### eval RawConvInv ($self, $val, $tagInfo) + $val = eval $conv; + $@ and $evalWarning = $@; + } + if ($evalWarning) { + # an empty warning ("\n") ignores tag with no error + if ($evalWarning ne "\n") { + my $err = CleanWarning() . " in $$tagInfo{Name} (RawConvInv)"; + $self->WarnOnce($err); + } + @$vals = (); + last; + } + } + } + # return our value(s) + if (wantarray) { + # remove duplicates if requested + if (@$vals > 1 and $self->Options('NoDups')) { + my %seen; + @$vals = grep { !$seen{$_}++ } @$vals; + } + return @$vals; + } + return $$vals[0]; +} + +#------------------------------------------------------------------------------ +# Return the total number of new values set +# Inputs: 0) ExifTool object reference +# Returns: Scalar context) Number of new values that have been set (incl pseudo) +# List context) Number of new values (incl pseudo), number of "pseudo" values +# ("pseudo" values are those which don't require rewriting the file to change) +sub CountNewValues($) +{ + my $self = shift; + my $newVal = $$self{NEW_VALUE}; + my ($num, $pseudo) = (0, 0); + if ($newVal) { + $num = scalar keys %$newVal; + my $nv; + foreach $nv (values %$newVal) { + my $tagInfo = $$nv{TagInfo}; + # don't count tags that don't write anything + $$tagInfo{WriteNothing} and --$num, next; + # count the number of pseudo tags included + $$tagInfo{WritePseudo} and ++$pseudo; + } + } + $num += scalar keys %{$$self{DEL_GROUP}}; + return $num unless wantarray; + return ($num, $pseudo); +} + +#------------------------------------------------------------------------------ +# Save new values for subsequent restore +# Inputs: 0) ExifTool object reference +# Returns: Number of times new values have been saved +# Notes: increments SAVE_COUNT flag each time routine is called +sub SaveNewValues($) +{ + my $self = shift; + my $newValues = $$self{NEW_VALUE}; + my $saveCount = ++$$self{SAVE_COUNT}; + my $key; + foreach $key (keys %$newValues) { + my $nvHash = $$newValues{$key}; + while ($nvHash) { + # set Save count if not done already + $$nvHash{Save} or $$nvHash{Save} = $saveCount; + $nvHash = $$nvHash{Next}; + } + } + # initialize hash for saving overwritten new values + $$self{SAVE_NEW_VALUE} = { }; + # make a copy of the delete group hash + my %delGrp = %{$$self{DEL_GROUP}}; + $$self{SAVE_DEL_GROUP} = \%delGrp; + return $saveCount; +} + +#------------------------------------------------------------------------------ +# Restore new values to last saved state +# Inputs: 0) ExifTool object reference +# Notes: Restores saved new values, but currently doesn't restore them in the +# original order, so there may be some minor side-effects when restoring tags +# with overlapping groups. eg) XMP:Identifier, XMP-dc:Identifier +# Also, this doesn't do the right thing for list-type tags which accumulate +# values across a save point +sub RestoreNewValues($) +{ + my $self = shift; + my $newValues = $$self{NEW_VALUE}; + my $savedValues = $$self{SAVE_NEW_VALUE}; + my $key; + # 1) remove any new values which don't have the Save flag set + if ($newValues) { + my @keys = keys %$newValues; + foreach $key (@keys) { + my $lastHash; + my $nvHash = $$newValues{$key}; + while ($nvHash) { + if ($$nvHash{Save}) { + $lastHash = $nvHash; + } else { + # remove this entry from the list + if ($lastHash) { + $$lastHash{Next} = $$nvHash{Next}; + } elsif ($$nvHash{Next}) { + $$newValues{$key} = $$nvHash{Next}; + } else { + delete $$newValues{$key}; + } + } + $nvHash = $$nvHash{Next}; + } + } + } + # 2) restore saved new values + if ($savedValues) { + $newValues or $newValues = $$self{NEW_VALUE} = { }; + foreach $key (keys %$savedValues) { + if ($$newValues{$key}) { + # add saved values to end of list + my $nvHash = LastInList($$newValues{$key}); + $$nvHash{Next} = $$savedValues{$key}; + } else { + $$newValues{$key} = $$savedValues{$key}; + } + } + $$self{SAVE_NEW_VALUE} = { }; # reset saved new values + } + # 3) restore delete groups + my %delGrp = %{$$self{SAVE_DEL_GROUP}}; + $$self{DEL_GROUP} = \%delGrp; +} + +#------------------------------------------------------------------------------ +# Set alternate file for extracting information +# Inputs: 0) ExifTool ref, 1) family 8 group name (of the form "File#" where # is any number) +# 2) alternate file name, or undef to reset +# Returns: 1 on success, or 0 on invalid group name +sub SetAlternateFile($$$) +{ + my ($self, $g8, $file) = @_; + $g8 = ucfirst lc $g8; + return 0 unless $g8 =~ /^File\d+$/; + # keep the same file if already initialized (possibly has metadata extracted) + if (not defined $file) { + delete $$self{ALT_EXIFTOOL}{$g8}; + } elsif (not ($$self{ALT_EXIFTOOL}{$g8} and $$self{ALT_EXIFTOOL}{$g8}{ALT_FILE} eq $file)) { + my $altExifTool = Image::ExifTool->new; + $$altExifTool{ALT_FILE} = $file; + $$self{ALT_EXIFTOOL}{$g8} = $altExifTool; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Set filesystem time from from FileModifyDate or FileCreateDate tag +# Inputs: 0) ExifTool object reference, 1) file name or file ref +# 2) time (-M or -C) of original file (used for shift; obtained from file if not given) +# 3) tag name to write (undef for 'FileModifyDate') +# 4) flag set if argument 2 has already been converted to Unix seconds +# Returns: 1=time changed OK, 0=nothing done, -1=error setting time +# (increments CHANGED flag and sets corresponding WRITTEN tag) +sub SetFileModifyDate($$;$$$) +{ + my ($self, $file, $originalTime, $tag, $isUnixTime) = @_; + my $nvHash; + $tag = 'FileModifyDate' unless defined $tag; + my $val = $self->GetNewValue($tag, \$nvHash); + return 0 unless defined $val; + my $isOverwriting = $self->IsOverwriting($nvHash); + return 0 unless $isOverwriting; + # can currently only set creation date on Windows systems + # (and Mac now too, but that is handled with the MacOS tags) + return 0 if $tag eq 'FileCreateDate' and $^O ne 'MSWin32'; + if ($isOverwriting < 0) { # are we shifting time? + # use original time of this file if not specified + unless (defined $originalTime) { + my ($aTime, $mTime, $cTime) = $self->GetFileTime($file); + $originalTime = ($tag eq 'FileCreateDate') ? $cTime : $mTime; + return 0 unless defined $originalTime; + $isUnixTime = 1; + } + $originalTime = int($^T - $originalTime*(24*3600) + 0.5) unless $isUnixTime; + return 0 unless $self->IsOverwriting($nvHash, $originalTime); + $val = $$nvHash{Value}[0]; # get shifted value + } + my ($aTime, $mTime, $cTime); + if ($tag eq 'FileCreateDate') { + eval { require Win32::API } or $self->WarnOnce("Install Win32::API to set $tag"), return -1; + eval { require Win32API::File } or $self->WarnOnce("Install Win32API::File to set $tag"), return -1; + $cTime = $val; + } else { + $aTime = $mTime = $val; + } + $self->SetFileTime($file, $aTime, $mTime, $cTime, 1) or $self->Warn("Error setting $tag"), return -1; + ++$$self{CHANGED}; + $$self{WRITTEN}{$tag} = $val; # remember that we wrote this tag + $self->VerboseValue("+ $tag", $val); + return 1; +} + +#------------------------------------------------------------------------------ +# Change file name and/or directory from FileName and Directory tags +# Inputs: 0) ExifTool object reference, 1) current file name (including path) +# 2) new name (or undef to build from FileName and Directory tags) +# 3) option: 'HardLink'/'SymLink' to create hard/symbolic link instead of renaming +# 'Test' to only print new file name +# 4) 0 to indicate that a file will no longer exist (used for 'Test' only) +# Returns: 1=name changed OK, 0=nothing changed, -1=error changing name +# (and increments CHANGED flag if filename changed) +# Notes: Will not overwrite existing file. Creates directories as necessary. +sub SetFileName($$;$$$) +{ + my ($self, $file, $newName, $opt, $usedFlag) = @_; + my ($nvHash, $doName, $doDir); + + $opt or $opt = ''; + # determine the new file name + unless (defined $newName) { + if ($opt) { + if ($opt eq 'HardLink' or $opt eq 'Link') { + $newName = $self->GetNewValue('HardLink'); + } elsif ($opt eq 'SymLink') { + $newName = $self->GetNewValue('SymLink'); + } elsif ($opt eq 'Test') { + $newName = $self->GetNewValue('TestName'); + } + return 0 unless defined $newName; + } else { + my $filename = $self->GetNewValue('FileName', \$nvHash); + $doName = 1 if defined $filename and $self->IsOverwriting($nvHash, $file); + my $dir = $self->GetNewValue('Directory', \$nvHash); + $doDir = 1 if defined $dir and $self->IsOverwriting($nvHash, $file); + return 0 unless $doName or $doDir; # nothing to do + if ($doName) { + $newName = GetNewFileName($file, $filename); + $newName = GetNewFileName($newName, $dir) if $doDir; + } else { + $newName = GetNewFileName($file, $dir); + } + } + } + # validate new file name in Windows + if ($^O eq 'MSWin32') { + if ($newName =~ /[\0-\x1f<>"|*]/) { + $self->Warn('New file name not allowed in Windows (contains reserved characters)'); + return -1; + } + if ($newName =~ /:/ and $newName !~ /^[A-Z]:[^:]*$/i) { + $self->Warn("New file name not allowed in Windows (contains ':')"); + return -1; + } + if ($newName =~ /\?/ and $newName !~ m{^[\\/]{2}\?[\\/][^?]*$}) { + $self->Warn("New file name not allowed in Windows (contains '?')"); + return -1; + } + if ($newName =~ m{(^|[\\/])(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\.[^.]*)?$}i) { + $self->Warn('New file name not allowed in Windows (reserved device name)'); + return -1; + } + if ($newName =~ /([. ])$/) { + $self->Warn("New file name not recommended for Windows (ends with '${1}')", 2) and return -1; + } + if (length $newName > 259 and $newName !~ /\?/) { + $self->Warn('New file name not recommended for Windows (exceeds 260 chars)', 2) and return -1; + } + } else { + $newName =~ tr/\0//d; # make sure name doesn't contain nulls + } + # protect against empty file name + length $newName or $self->Warn('New file name is empty'), return -1; + # don't replace existing file + if ($self->Exists($newName, 1) and (not defined $usedFlag or $usedFlag)) { + if ($file ne $newName or $opt =~ /Link$/) { + # allow for case-insensitive filesystem + if ($opt =~ /Link$/ or not $self->IsSameFile($file, $newName)) { + $self->Warn("File '${newName}' already exists"); + return -1; + } + } else { + $self->Warn('File name is unchanged'); + return 0; + } + } + if ($opt eq 'Test') { + my $out = $$self{OPTIONS}{TextOut}; + print $out "'${file}' --> '${newName}'\n"; + return 1; + } + # create directory for new file if necessary + my $result; + if (($result = $self->CreateDirectory($newName)) != 0) { + if ($result < 0) { + $self->Warn("Error creating directory for '${newName}'"); + return -1; + } + $self->VPrint(0, "Created directory for '${newName}'\n"); + } + if ($opt eq 'HardLink' or $opt eq 'Link') { + unless (link $file, $newName) { + $self->Warn("Error creating hard link '${newName}'"); + return -1; + } + ++$$self{CHANGED}; + $self->VerboseValue('+ HardLink', $newName); + return 1; + } elsif ($opt eq 'SymLink') { + $^O eq 'MSWin32' and $self->Warn('SymLink not supported in Windows'), return -1; + $newName =~ s(^\./)(); # remove leading "./" from link name if it exists + # path to linked file must be relative to the $newName directory, but $file + # is relative to the current directory, so convert it to an absolute path + # if using a relative directory and $newName isn't in the current directory + if ($file !~ m(^/) and $newName =~ m(/)) { + unless (eval { require Cwd }) { + $self->Warn('Install Cwd to make symlinks to other directories'); + return -1; + } + $file = eval { Cwd::abs_path($file) }; + unless (defined $file) { + $self->Warn('Error in Cwd::abs_path when creating symlink'); + return -1; + } + } + unless (eval { symlink $file, $newName } ) { + $self->Warn("Error creating symbolic link '${newName}'"); + return -1; + } + ++$$self{CHANGED}; + $self->VerboseValue('+ SymLink', $newName); + return 1; + } + # attempt to rename the file + unless ($self->Rename($file, $newName)) { + local (*EXIFTOOL_SFN_IN, *EXIFTOOL_SFN_OUT); + # renaming didn't work, so copy the file instead + unless ($self->Open(\*EXIFTOOL_SFN_IN, $file)) { + $self->Error("Error opening '${file}'"); + return -1; + } + unless ($self->Open(\*EXIFTOOL_SFN_OUT, $newName, '>')) { + close EXIFTOOL_SFN_IN; + $self->Error("Error creating '${newName}'"); + return -1; + } + binmode EXIFTOOL_SFN_IN; + binmode EXIFTOOL_SFN_OUT; + my ($buff, $err); + while (read EXIFTOOL_SFN_IN, $buff, 65536) { + print EXIFTOOL_SFN_OUT $buff or $err = 1; + } + close EXIFTOOL_SFN_OUT or $err = 1; + close EXIFTOOL_SFN_IN; + if ($err) { + $self->Unlink($newName); # erase bad output file + $self->Error("Error writing '${newName}'"); + return -1; + } + # preserve modification time + my ($aTime, $mTime, $cTime) = $self->GetFileTime($file); + $self->SetFileTime($newName, $aTime, $mTime, $cTime); + # remove the original file + $self->Unlink($file) or $self->Warn('Error removing old file'); + } + $$self{NewName} = $newName; # remember new file name + ++$$self{CHANGED}; + $self->VerboseValue('+ FileName', $newName); + return 1; +} + +#------------------------------------------------------------------------------ +# Set file permissions, group/user id and various MDItem tags from new tag values +# Inputs: 0) ExifTool ref, 1) file name or glob (must be a name for MDItem tags) +# Returns: 1=something was set OK, 0=didn't try, -1=error (and warning set) +# Notes: There may be errors even if 1 is returned +sub SetSystemTags($$) +{ + my ($self, $file) = @_; + my $result = 0; + + my $perm = $self->GetNewValue('FilePermissions'); + if (defined $perm) { + if (eval { chmod($perm & 07777, $file) }) { + $self->VerboseValue('+ FilePermissions', $perm); + $result = 1; + } else { + $self->WarnOnce('Error setting FilePermissions'); + $result = -1; + } + } + my $uid = $self->GetNewValue('FileUserID'); + my $gid = $self->GetNewValue('FileGroupID'); + if (defined $uid or defined $gid) { + defined $uid or $uid = -1; + defined $gid or $gid = -1; + if (eval { chown($uid, $gid, $file) }) { + $self->VerboseValue('+ FileUserID', $uid) if $uid >= 0; + $self->VerboseValue('+ FileGroupID', $gid) if $gid >= 0; + $result = 1; + } else { + $self->WarnOnce('Error setting FileGroup/UserID'); + $result = -1 unless $result; + } + } + my $tag; + foreach $tag (@writableMacOSTags) { + my $nvHash; + my $val = $self->GetNewValue($tag, \$nvHash); + next unless $nvHash; + if ($^O eq 'darwin') { + ref $file and $self->Warn('Setting MDItem tags requires a file name'), last; + require Image::ExifTool::MacOS; + my $res = Image::ExifTool::MacOS::SetMacOSTags($self, $file, \@writableMacOSTags); + $result = $res if $res == 1 or not $result; + last; + } elsif ($tag ne 'FileCreateDate') { + $self->WarnOnce('Can only set MDItem tags on OS X'); + last; + } + } + # delete Windows Zone.Identifier if specified + my $zhash = $self->GetNewValueHash($Image::ExifTool::Extra{ZoneIdentifier}); + if ($zhash) { + my $res = -1; + if ($^O ne 'MSWin32') { + $self->Warn('ZoneIdentifer is a Windows-only tag'); + } elsif (ref $file) { + $self->Warn('Writing ZoneIdentifer requires a file name'); + } elsif (defined $self->GetNewValue('ZoneIdentifier', \$zhash)) { + $self->Warn('ZoneIndentifier may only be delted'); + } elsif (not eval { require Win32API::File }) { + $self->Warn('Install Win32API::File to write ZoneIdentifier'); + } else { + my ($wattr, $wide); + my $zfile = "${file}:Zone.Identifier"; + if ($self->EncodeFileName($zfile)) { + $wide = 1; + $wattr = eval { Win32API::File::GetFileAttributesW($zfile) }; + } else { + $wattr = eval { Win32API::File::GetFileAttributes($zfile) }; + } + if ($wattr == Win32API::File::INVALID_FILE_ATTRIBUTES()) { + $res = 0; # file doesn't exist, nothing to do + } elsif ($wattr & Win32API::File::FILE_ATTRIBUTE_READONLY()) { + $self->Warn('Zone.Identifier stream is read-only'); + } else { + if ($wide) { + $res = 1 if eval { Win32API::File::DeleteFileW($zfile) }; + } else { + $res = 1 if eval { Win32API::File::DeleteFile($zfile) }; + } + if ($res > 0) { + $self->VPrint(0, " Deleting Zone.Identifier stream\n"); + } else { + $self->Warn('Error deleting Zone.Identifier stream'); + } + } + } + $result = $res if $res == 1 or not $result; + } + return $result; +} + +#------------------------------------------------------------------------------ +# Write information back to file +# Inputs: 0) ExifTool object reference, +# 1) input filename, file ref, RAF ref, or scalar ref (or '' or undef to create from scratch) +# 2) output filename, file ref, or scalar ref (or undef to overwrite) +# 3) optional output file type (required only if input file is not specified +# and output file is a reference) +# Returns: 1=file written OK, 2=file written but no changes made, 0=file write error +sub WriteInfo($$;$$) +{ + local ($_, *EXIFTOOL_FILE2, *EXIFTOOL_OUTFILE); + my ($self, $infile, $outfile, $outType) = @_; + my (@fileTypeList, $fileType, $tiffType, $hdr, $seekErr, $type, $tmpfile); + my ($inRef, $outRef, $closeIn, $closeOut, $outPos, $outBuff, $eraseIn, $raf, $fileExt); + my ($hardLink, $symLink, $testName); + my $oldRaf = $$self{RAF}; + my $rtnVal = 0; + + # initialize member variables + $self->Init(); + $$self{IsWriting} = 1; + + # first, save original file modify date if necessary + # (do this now in case we are modifying file in place and shifting date) + my ($nvHash, $nvHash2, $originalTime, $createTime); + my $setModDate = defined $self->GetNewValue('FileModifyDate', \$nvHash); + my $setCreateDate = defined $self->GetNewValue('FileCreateDate', \$nvHash2); + my ($aTime, $mTime, $cTime); + if ($setModDate and $self->IsOverwriting($nvHash) < 0 and + defined $infile and ref $infile ne 'SCALAR') + { + ($aTime, $mTime, $cTime) = $self->GetFileTime($infile); + $originalTime = $mTime; + } + if ($setCreateDate and $self->IsOverwriting($nvHash2) < 0 and + defined $infile and ref $infile ne 'SCALAR') + { + ($aTime, $mTime, $cTime) = $self->GetFileTime($infile) unless defined $cTime; + $createTime = $cTime; + } +# +# do quick in-place change of file dir/name or date if that is all we are doing +# + my ($numNew, $numPseudo) = $self->CountNewValues(); + if (not defined $outfile and defined $infile) { + $hardLink = $self->GetNewValue('HardLink'); + $symLink = $self->GetNewValue('SymLink'); + $testName = $self->GetNewValue('TestName'); + undef $hardLink if defined $hardLink and not length $hardLink; + undef $symLink if defined $symLink and not length $symLink; + undef $testName if defined $testName and not length $testName; + my $newFileName = $self->GetNewValue('FileName', \$nvHash); + my $newDir = $self->GetNewValue('Directory'); + if (defined $newDir and length $newDir) { + $newDir .= '/' unless $newDir =~ m{/$}; + } else { + undef $newDir; + } + if ($numNew == $numPseudo) { + $rtnVal = 2; + if ((defined $newFileName or defined $newDir) and not ref $infile) { + my $result = $self->SetFileName($infile); + if ($result > 0) { + $infile = $$self{NewName}; # file name changed + $rtnVal = 1; + } elsif ($result < 0) { + return 0; # don't try to do anything else + } + } + if (not ref $infile or UNIVERSAL::isa($infile,'GLOB')) { + $self->SetFileModifyDate($infile) > 0 and $rtnVal = 1 if $setModDate; + $self->SetFileModifyDate($infile, undef, 'FileCreateDate') > 0 and $rtnVal = 1 if $setCreateDate; + $self->SetSystemTags($infile) > 0 and $rtnVal = 1; + } + if (defined $hardLink or defined $symLink or defined $testName) { + $hardLink and $self->SetFileName($infile, $hardLink, 'HardLink') and $rtnVal = 1; + $symLink and $self->SetFileName($infile, $symLink, 'SymLink') and $rtnVal = 1; + $testName and $self->SetFileName($infile, $testName, 'Test') and $rtnVal = 1; + } + return $rtnVal; + } elsif (defined $newFileName and length $newFileName) { + # can't simply rename file, so just set the output name if new FileName + # --> in this case, must erase original copy + if (ref $infile) { + $outfile = $newFileName; + # can't delete original + } elsif ($self->IsOverwriting($nvHash, $infile)) { + $outfile = GetNewFileName($infile, $newFileName); + $eraseIn = 1; # delete original + } + } + # set new directory if specified + if (defined $newDir) { + $outfile = $infile unless defined $outfile or ref $infile; + if (defined $outfile) { + $outfile = GetNewFileName($outfile, $newDir); + $eraseIn = 1 unless ref $infile; + } + } + } +# +# set up input file +# + if (ref $infile) { + $inRef = $infile; + if (UNIVERSAL::isa($inRef,'GLOB')) { + seek($inRef, 0, 0); # make sure we are at the start of the file + } elsif (UNIVERSAL::isa($inRef,'File::RandomAccess')) { + $inRef->Seek(0); + $raf = $inRef; + } elsif ($] >= 5.006 and (eval { require Encode; Encode::is_utf8($$inRef) } or $@)) { + # convert image data from UTF-8 to character stream if necessary + my $buff = $@ ? pack('C*',unpack($] < 5.010000 ? 'U0C*' : 'C0C*',$$inRef)) : Encode::encode('utf8',$$inRef); + if (defined $outfile) { + $inRef = \$buff; + } else { + $$inRef = $buff; + } + } + } elsif (defined $infile and $infile ne '') { + # write to a temporary file if no output file given + $outfile = $tmpfile = "${infile}_exiftool_tmp" unless defined $outfile; + if ($self->Open(\*EXIFTOOL_FILE2, $infile)) { + $fileExt = GetFileExtension($infile); + $fileType = GetFileType($infile); + @fileTypeList = GetFileType($infile); + $tiffType = $$self{FILE_EXT} = GetFileExtension($infile); + $self->VPrint(0, "Rewriting $infile...\n"); + $inRef = \*EXIFTOOL_FILE2; + $closeIn = 1; # we must close the file since we opened it + } else { + $self->Error('Error opening file'); + return 0; + } + } elsif (not defined $outfile) { + $self->Error("WriteInfo(): Must specify infile or outfile\n"); + return 0; + } else { + # create file from scratch + $outType = GetFileExtension($outfile) unless $outType or ref $outfile; + if (CanCreate($outType)) { + if ($$self{OPTIONS}{WriteMode} =~ /g/i) { + $fileType = $tiffType = $outType; # use output file type if no input file + $infile = "$fileType file"; # make bogus file name + $self->VPrint(0, "Creating $infile...\n"); + $inRef = \ ''; # set $inRef to reference to empty data + } else { + $self->Error("Not creating new $outType file (disallowed by WriteMode)"); + return 0; + } + } elsif ($outType) { + $self->Error("Can't create $outType files"); + return 0; + } else { + $self->Error("Can't create file (unknown type)"); + return 0; + } + } + unless (@fileTypeList) { + if ($fileType) { + @fileTypeList = ( $fileType ); + } else { + @fileTypeList = @fileTypes; + $tiffType = 'TIFF'; + } + } +# +# set up output file +# + if (ref $outfile) { + $outRef = $outfile; + if (UNIVERSAL::isa($outRef,'GLOB')) { + binmode($outRef); + $outPos = tell($outRef); + } else { + # initialize our output buffer if necessary + defined $$outRef or $$outRef = ''; + $outPos = length($$outRef); + } + } elsif (not defined $outfile) { + # editing in place, so write to memory first + # (only when infile is a file ref or scalar ref) + if ($raf) { + $self->Error("Can't edit File::RandomAccess object in place"); + return 0; + } + $outBuff = ''; + $outRef = \$outBuff; + $outPos = 0; + } elsif ($self->Exists($outfile, 1)) { + $self->Error("File already exists: $outfile"); + } elsif ($self->Open(\*EXIFTOOL_OUTFILE, $outfile, '>')) { + $outRef = \*EXIFTOOL_OUTFILE; + $closeOut = 1; # we must close $outRef + binmode($outRef); + $outPos = 0; + } else { + my $tmp = $tmpfile ? ' temporary' : ''; + $self->Error("Error creating$tmp file: $outfile"); + } +# +# write the file +# + until ($$self{VALUE}{Error}) { + # create random access file object (disable seek test in case of straight copy) + $raf or $raf = new File::RandomAccess($inRef, 1); + $raf->BinMode(); + if ($numNew == $numPseudo) { + $rtnVal = 1; + # just do a straight copy of the file (no "real" tags are being changed) + my $buff; + while ($raf->Read($buff, 65536)) { + Write($outRef, $buff) or $rtnVal = -1, last; + } + last; + } elsif (not ref $infile and ($infile eq '-' or $infile =~ /\|$/)) { + # patch for Windows command shell pipe + $$raf{TESTED} = -1; # force buffering + } else { + $raf->SeekTest(); + } + # $raf->Debug() and warn " RAF debugging enabled!\n"; + my $inPos = $raf->Tell(); + $$self{RAF} = $raf; + my %dirInfo = ( + RAF => $raf, + OutFile => $outRef, + ); + $raf->Read($hdr, 1024) or $hdr = ''; + $raf->Seek($inPos, 0) or $seekErr = 1; + my $wrongType; + until ($seekErr) { + $type = shift @fileTypeList; + # do quick test to see if this is the right file type + if ($magicNumber{$type} and length($hdr) and $hdr !~ /^$magicNumber{$type}/s) { + next if @fileTypeList; + $wrongType = 1; + last; + } + # save file type in member variable + $dirInfo{Parent} = $$self{FILE_TYPE} = $$self{PATH}[0] = $type; + # determine which directories we must write for this file type + $self->InitWriteDirs($type); + if ($type eq 'JPEG' or $type eq 'EXV') { + $rtnVal = $self->WriteJPEG(\%dirInfo); + } elsif ($type eq 'TIFF') { + # disallow writing of some TIFF-based RAW images: + if (grep /^$tiffType$/, @{$noWriteFile{TIFF}}) { + $fileType = $tiffType; + undef $rtnVal; + } else { + if ($tiffType eq 'FFF') { + # (see https://exiftool.org/forum/index.php?topic=10848.0) + $self->Error('Phocus may not properly update previews of edited FFF images', 1); + } + $dirInfo{Parent} = $tiffType; + $rtnVal = $self->ProcessTIFF(\%dirInfo); + } + } elsif (exists $writableType{$type}) { + my ($module, $func); + if (ref $writableType{$type} eq 'ARRAY') { + $module = $writableType{$type}[0] || $type; + $func = $writableType{$type}[1]; + } else { + $module = $writableType{$type} || $type; + } + require "Image/ExifTool/$module.pm"; + $func = "Image::ExifTool::${module}::" . ($func || "Process$type"); + no strict 'refs'; + $rtnVal = &$func($self, \%dirInfo); + use strict 'refs'; + } elsif ($type eq 'ORF' or $type eq 'RAW') { + $rtnVal = $self->ProcessTIFF(\%dirInfo); + } elsif ($type eq 'EXIF') { + # go through WriteDirectory so block writes, etc are handled + my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); + my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr, \&WriteTIFF); + if (defined $buff) { + $rtnVal = Write($outRef, $buff) ? 1 : -1; + } else { + $rtnVal = 0; + } + } else { + undef $rtnVal; # flag that we don't write this type of file + } + # all done unless we got the wrong type + last if $rtnVal; + last unless @fileTypeList; + # seek back to original position in files for next try + $raf->Seek($inPos, 0) or $seekErr = 1, last; + if (UNIVERSAL::isa($outRef,'GLOB')) { + seek($outRef, 0, $outPos); + } else { + $$outRef = substr($$outRef, 0, $outPos); + } + } + # print file format errors + unless ($rtnVal) { + my $err; + if ($seekErr) { + $err = 'Error seeking in file'; + } elsif ($fileType and defined $rtnVal) { + if ($$self{VALUE}{Error}) { + # existing error message will do + } elsif ($fileType eq 'RAW') { + $err = 'Writing this type of RAW file is not supported'; + } else { + if ($wrongType) { + my $type = $fileExt || ($fileType eq 'TIFF' ? $tiffType : $fileType); + $err = "Not a valid $type"; + # do a quick check to see what this file looks like + foreach $type (@fileTypes) { + next unless $magicNumber{$type}; + next unless $hdr =~ /^$magicNumber{$type}/s; + $err .= " (looks more like a $type)"; + last; + } + } else { + $err = 'Format error in file'; + } + } + } elsif ($fileType) { + # get specific type of file from extension + $fileType = GetFileExtension($infile) if $infile and GetFileType($infile); + $err = "Writing of $fileType files is not yet supported"; + } else { + $err = 'Writing of this type of file is not supported'; + } + $self->Error($err) if $err; + $rtnVal = 0; # (in case it was undef) + } + # $raf->Close(); # only used to force debug output + last; # (didn't really want to loop) + } + # don't return success code if any error occurred + if ($rtnVal > 0) { + if ($outType and $type and $outType ne $type) { + my @types = GetFileType($outType); + unless (grep /^$type$/, @types) { + $self->Error("Can't create $outType file from $type"); + $rtnVal = 0; + } + } + if ($rtnVal > 0 and not Tell($outRef) and not $$self{VALUE}{Error}) { + # don't write a file with zero length + if (defined $hdr and length $hdr) { + $type = '<unk>' unless defined $type; + $self->Error("Can't delete all meta information from $type file"); + } else { + $self->Error('Nothing to write'); + } + } + $rtnVal = 0 if $$self{VALUE}{Error}; + } + + # rewrite original file in place if required + if (defined $outBuff) { + if ($rtnVal <= 0 or not $$self{CHANGED}) { + # nothing changed, so no need to write $outBuff + } elsif (UNIVERSAL::isa($inRef,'GLOB')) { + my $len = length($outBuff); + my $size; + $rtnVal = -1 unless + seek($inRef, 0, 2) and # seek to the end of file + ($size = tell $inRef) >= 0 and # get the file size + seek($inRef, 0, 0) and # seek back to the start + print $inRef $outBuff and # write the new data + ($len >= $size or # if necessary: + eval { truncate($inRef, $len) }); # shorten output file + } else { + $$inRef = $outBuff; # replace original data + } + $outBuff = ''; # free memory but leave $outBuff defined + } + # close input file if we opened it + if ($closeIn) { + # errors on input file are significant if we edited the file in place + $rtnVal and $rtnVal = -1 unless close($inRef) or not defined $outBuff; + if ($rtnVal > 0) { + # copy Mac OS resource fork if it exists + if ($^O eq 'darwin' and -s "$infile/..namedfork/rsrc") { + if ($$self{DEL_GROUP}{RSRC}) { + $self->VPrint(0,"Deleting Mac OS resource fork\n"); + ++$$self{CHANGED}; + } else { + $self->VPrint(0,"Copying Mac OS resource fork\n"); + my ($buf, $err); + local (*SRC, *DST); + if ($self->Open(\*SRC, "$infile/..namedfork/rsrc")) { + if ($self->Open(\*DST, "$outfile/..namedfork/rsrc", '>')) { + binmode SRC; # (not necessary for Darwin, but let's be thorough) + binmode DST; + while (read SRC, $buf, 65536) { + print DST $buf or $err = 'copying', last; + } + close DST or $err or $err = 'closing'; + } else { + # (this is normal if the destination filesystem isn't Mac OS) + $self->Warn('Error creating Mac OS resource fork'); + } + close SRC; + } else { + $err = 'opening'; + } + $rtnVal = 0 if $err and $self->Error("Error $err Mac OS resource fork", 2); + } + } + # erase input file if renaming while editing information in place + $self->Unlink($infile) or $self->Warn('Error erasing original file') if $eraseIn; + } + } + # close output file if we created it + if ($closeOut) { + # close file and set $rtnVal to -1 if there was an error + $rtnVal and $rtnVal = -1 unless close($outRef); + # erase the output file if we weren't successful + if ($rtnVal <= 0) { + $self->Unlink($outfile); + # else rename temporary file if necessary + } elsif ($tmpfile) { + $self->CopyFileAttrs($infile, $tmpfile); # copy attributes to new file + unless ($self->Rename($tmpfile, $infile)) { + # some filesystems won't overwrite with 'rename', so try erasing original + if (not $self->Unlink($infile)) { + $self->Unlink($tmpfile); + $self->Error('Error renaming temporary file'); + $rtnVal = 0; + } elsif (not $self->Rename($tmpfile, $infile)) { + $self->Error('Error renaming temporary file after deleting original'); + $rtnVal = 0; + } + } + # the output file should now have the name of the original infile + $outfile = $infile if $rtnVal > 0; + } + } + # set filesystem attributes if requested (and if possible!) + if ($rtnVal > 0 and ($closeOut or (defined $outBuff and ($closeIn or UNIVERSAL::isa($infile,'GLOB'))))) { + my $target = $closeOut ? $outfile : $infile; + # set file permissions if requested + ++$$self{CHANGED} if $self->SetSystemTags($target) > 0; + if ($closeIn) { # (no use setting file times unless the input file is closed) + ++$$self{CHANGED} if $setModDate and $self->SetFileModifyDate($target, $originalTime, undef, 1) > 0; + # set FileCreateDate if requested (and if possible!) + ++$$self{CHANGED} if $setCreateDate and $self->SetFileModifyDate($target, $createTime, 'FileCreateDate', 1) > 0; + # create hard link if requested and no output filename specified (and if possible!) + ++$$self{CHANGED} if defined $hardLink and $self->SetFileName($target, $hardLink, 'HardLink'); + ++$$self{CHANGED} if defined $symLink and $self->SetFileName($target, $symLink, 'SymLink'); + defined $testName and $self->SetFileName($target, $testName, 'Test'); + } + } + # check for write error and set appropriate error message and return value + if ($rtnVal < 0) { + $self->Error('Error writing output file') unless $$self{VALUE}{Error}; + $rtnVal = 0; # return 0 on failure + } elsif ($rtnVal > 0) { + ++$rtnVal unless $$self{CHANGED}; + } + # set things back to the way they were + $$self{RAF} = $oldRaf; + + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Get list of all available tags for specified group +# Inputs: 0) optional group name (or string of names separated by colons) +# Returns: tag list (sorted alphabetically) +# Notes: Can't get tags for specific IFD +sub GetAllTags(;$) +{ + local $_; + my $group = shift; + my (%allTags, @groups); + @groups = split ':', $group if $group; + + my $et = new Image::ExifTool; + LoadAllTables(); # first load all our tables + my @tableNames = keys %allTables; + + # loop through all tables and save tag names to %allTags hash + while (@tableNames) { + my $table = GetTagTable(pop @tableNames); + # generate flattened tag names for structure fields if this is an XMP table + if ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP') { + Image::ExifTool::XMP::AddFlattenedTags($table); + } + my $tagID; + foreach $tagID (TagTableKeys($table)) { + my @infoArray = GetTagInfoList($table,$tagID); + my $tagInfo; +GATInfo: foreach $tagInfo (@infoArray) { + my $tag = $$tagInfo{Name}; + $tag or warn("no name for tag!\n"), next; + # don't list subdirectories unless they are writable + next if $$tagInfo{SubDirectory} and not $$tagInfo{Writable}; + next if $$tagInfo{Hidden}; # ignore hidden tags + if (@groups) { + my @tg = $et->GetGroup($tagInfo); + foreach $group (@groups) { + next GATInfo unless grep /^$group$/i, @tg; + } + } + $allTags{$tag} = 1; + } + } + } + return sort keys %allTags; +} + +#------------------------------------------------------------------------------ +# Get list of all writable tags +# Inputs: 0) optional group name (or names separated by colons) +# Returns: tag list (sorted alphabetically) +sub GetWritableTags(;$) +{ + local $_; + my $group = shift; + my (%writableTags, @groups); + @groups = split ':', $group if $group; + + my $et = new Image::ExifTool; + LoadAllTables(); + my @tableNames = keys %allTables; + + while (@tableNames) { + my $tableName = pop @tableNames; + my $table = GetTagTable($tableName); + # generate flattened tag names for structure fields if this is an XMP table + if ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP') { + Image::ExifTool::XMP::AddFlattenedTags($table); + } + # attempt to load Write tables if autoloaded + my @parts = split(/::/,$tableName); + if (@parts > 3) { + my $i = $#parts - 1; + $parts[$i] = "Write$parts[$i]"; # add 'Write' before class name + my $module = join('::',@parts[0..$i]); + eval { require $module }; # (fails silently if nothing loaded) + } + my $tagID; + foreach $tagID (TagTableKeys($table)) { + my @infoArray = GetTagInfoList($table,$tagID); + my $tagInfo; +GWTInfo: foreach $tagInfo (@infoArray) { + my $tag = $$tagInfo{Name}; + $tag or warn("no name for tag!\n"), next; + my $writable = $$tagInfo{Writable}; + next unless $writable or ($$table{WRITABLE} and + not defined $writable and not $$tagInfo{SubDirectory}); + next if $$tagInfo{Hidden}; # ignore hidden tags + if (@groups) { + my @tg = $et->GetGroup($tagInfo); + foreach $group (@groups) { + next GWTInfo unless grep /^$group$/i, @tg; + } + } + $writableTags{$tag} = 1; + } + } + } + return sort keys %writableTags; +} + +#------------------------------------------------------------------------------ +# Get list of all group names +# Inputs: 0) [optional] ExifTool ref, 1) Group family number +# Returns: List of group names (sorted alphabetically) +sub GetAllGroups($;$) +{ + local $_; + my $family = shift || 0; + my $self; + ref $family and $self = $family, $family = shift || 0; + + $family == 3 and return('Doc#', 'Main'); + $family == 4 and return('Copy#'); + $family == 5 and return('[too many possibilities to list]'); + $family == 6 and return(@Image::ExifTool::Exif::formatName[1..$#Image::ExifTool::Exif::formatName]); + $family == 8 and return('File#'); + + LoadAllTables(); # first load all our tables + + my @tableNames = keys %allTables; + + my %allGroups; + # add family 1 groups not in tables + $family == 1 and map { $allGroups{$_} = 1 } qw(Garmin); + # loop through all tag tables and get all group names + while (@tableNames) { + my $table = GetTagTable(pop @tableNames); + my ($grps, $grp, $tag, $tagInfo); + $allGroups{$grp} = 1 if ($grps = $$table{GROUPS}) and ($grp = $$grps{$family}); + foreach $tag (TagTableKeys($table)) { + my @infoArray = GetTagInfoList($table, $tag); + if ($family == 7) { + foreach $tagInfo (@infoArray) { + my $id = $$tagInfo{TagID}; + if (not defined $id) { + $id = ''; # (just to be safe) + } elsif ($id =~ /^\d+$/) { + $id = sprintf('0x%x', $id) if $self and $$self{OPTIONS}{HexTagIDs}; + } else { + $id =~ s/([^-_A-Za-z0-9])/sprintf('%.2x',ord $1)/ge; + } + $allGroups{'ID-' . $id} = 1; + } + } else { + foreach $tagInfo (@infoArray) { + next unless ($grps = $$tagInfo{Groups}) and ($grp = $$grps{$family}); + $allGroups{$grp} = 1; + } + } + } + } + delete $allGroups{'*'}; # (not a real group) + return sort keys %allGroups; +} + +#------------------------------------------------------------------------------ +# get priority group list for new values +# Inputs: 0) ExifTool object reference +# Returns: List of group names +sub GetNewGroups($) +{ + my $self = shift; + return @{$$self{WRITE_GROUPS}}; +} + +#------------------------------------------------------------------------------ +# Get list of all deletable group names +# Returns: List of group names (sorted alphabetically) +sub GetDeleteGroups() +{ + return sort @delGroups, @delGroup2; +} + +#------------------------------------------------------------------------------ +# Add user-defined tags at run time +# Inputs: 0) destination table name, 1) tagID/tagInfo pairs for tags to add +# Returns: number of tags added +# Notes: will replace existing tags +sub AddUserDefinedTags($%) +{ + local $_; + my ($tableName, %addTags) = @_; + my $table = GetTagTable($tableName) or return 0; + # add tags to writer lookup + Image::ExifTool::TagLookup::AddTags(\%addTags, $tableName); + my $tagID; + my $num = 0; + foreach $tagID (keys %addTags) { + next if $specialTags{$tagID}; + delete $$table{$tagID}; # delete old entry if it existed + AddTagToTable($table, $tagID, $addTags{$tagID}, 1); + ++$num; + } + return $num; +} + +#============================================================================== +# Functions below this are not part of the public API + +#------------------------------------------------------------------------------ +# Maintain backward compatibility for old GetNewValues function name +sub GetNewValues($$;$) +{ + my ($self, $tag, $nvHashPt) = @_; + return $self->GetNewValue($tag, $nvHashPt); +} + +#------------------------------------------------------------------------------ +# Un-escape string according to options settings and clear UTF-8 flag +# Inputs: 0) ExifTool ref, 1) string ref or string ref ref +# Notes: also de-references SCALAR values +sub Sanitize($$) +{ + my ($self, $valPt) = @_; + # de-reference SCALAR references + $$valPt = $$$valPt if ref $$valPt eq 'SCALAR'; + # make sure the Perl UTF-8 flag is OFF for the value if perl 5.6 or greater + # (otherwise our byte manipulations get corrupted!!) + if ($] >= 5.006 and (eval { require Encode; Encode::is_utf8($$valPt) } or $@)) { + local $SIG{'__WARN__'} = \&SetWarning; + # repack by hand if Encode isn't available + $$valPt = $@ ? pack('C*',unpack($] < 5.010000 ? 'U0C*' : 'C0C*',$$valPt)) : Encode::encode('utf8',$$valPt); + } + # un-escape value if necessary + if ($$self{OPTIONS}{Escape}) { + # (XMP.pm and HTML.pm were require'd as necessary when option was set) + if ($$self{OPTIONS}{Escape} eq 'XML') { + $$valPt = Image::ExifTool::XMP::UnescapeXML($$valPt); + } elsif ($$self{OPTIONS}{Escape} eq 'HTML') { + $$valPt = Image::ExifTool::HTML::UnescapeHTML($$valPt, $$self{OPTIONS}{Charset}); + } + } +} + +#------------------------------------------------------------------------------ +# Apply inverse conversions +# Inputs: 0) ExifTool ref, 1) value, 2) tagInfo (or Struct item) ref, +# 3) tag name, 4) group 1 name, 5) conversion type (or undef), +# 6) [optional] want group ("" for structure field) +# Returns: 0) converted value, 1) error string (or undef on success) +# Notes: +# - uses ExifTool "ConvType" member when conversion type is undef +# - conversion types other than 'ValueConv' and 'PrintConv' are treated as 'Raw' +sub ConvInv($$$$$;$$) +{ + my ($self, $val, $tagInfo, $tag, $wgrp1, $convType, $wantGroup) = @_; + my ($err, $type); + + $convType or $convType = $$self{ConvType} || 'PrintConv'; + +Conv: for (;;) { + if (not defined $type) { + # split value into list if necessary + if ($$tagInfo{List}) { + my $listSplit = $$tagInfo{AutoSplit} || $$self{OPTIONS}{ListSplit}; + if (defined $listSplit and not $$tagInfo{Struct} and + ($wantGroup or not defined $wantGroup)) + { + $listSplit = ',?\s+' if $listSplit eq '1' and $$tagInfo{AutoSplit}; + my @splitVal = split /$listSplit/, $val, -1; + $val = @splitVal > 1 ? \@splitVal : @splitVal ? $splitVal[0] : ''; + } + } + $type = $convType; + } elsif ($type eq 'PrintConv') { + $type = 'ValueConv'; + } else { + # split raw value if necessary + if ($$tagInfo{RawJoin} and $$tagInfo{List} and not ref $val) { + my @splitVal = split ' ', $val; + $val = \@splitVal if @splitVal > 1; + } + # finally, do our value check + my ($err2, $v); + if ($$tagInfo{WriteCheck}) { + #### eval WriteCheck ($self, $tagInfo, $val) + $err2 = eval $$tagInfo{WriteCheck}; + $@ and warn($@), $err2 = 'Error evaluating WriteCheck'; + } + unless ($err2) { + my $table = $$tagInfo{Table}; + if ($table and $$table{CHECK_PROC} and not $$tagInfo{RawConvInv}) { + my $checkProc = $$table{CHECK_PROC}; + if (ref $val eq 'ARRAY') { + # loop through array values + foreach $v (@$val) { + $err2 = &$checkProc($self, $tagInfo, \$v, $convType); + last if $err2; + } + } else { + $err2 = &$checkProc($self, $tagInfo, \$val, $convType); + } + } + } + if (defined $err2) { + if ($err2) { + $err = "$err2 for $wgrp1:$tag"; + $self->VPrint(2, "$err\n"); + undef $val; # value was invalid + } else { + $err = $err2; # empty error (quietly don't write tag) + } + } + last; + } + my $conv = $$tagInfo{$type}; + my $convInv = $$tagInfo{"${type}Inv"}; + # nothing to do at this level if no conversion defined + next unless defined $conv or defined $convInv; + + my (@valList, $index, $convList, $convInvList); + if (ref $val eq 'ARRAY') { + # handle ValueConv of ListSplit and AutoSplit values + @valList = @$val; + $val = $valList[$index = 0]; + } elsif (ref $conv eq 'ARRAY' or ref $convInv eq 'ARRAY') { + # handle conversion lists + @valList = split /$listSep{$type}/, $val; + $val = $valList[$index = 0]; + if (ref $conv eq 'ARRAY') { + $convList = $conv; + $conv = $$conv[0]; + } + if (ref $convInv eq 'ARRAY') { + $convInvList = $convInv; + $convInv = $$convInv[0]; + } + } + # loop through multiple values if necessary + for (;;) { + if ($convInv) { + # capture eval warnings too + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + if (ref($convInv) eq 'CODE') { + $val = &$convInv($val, $self); + } else { + #### eval PrintConvInv/ValueConvInv ($val, $self, $wantGroup) + $val = eval $convInv; + $@ and $evalWarning = $@; + } + if ($evalWarning) { + # an empty warning ("\n") ignores tag with no error + if ($evalWarning eq "\n") { + $err = '' unless defined $err; + } else { + $err = CleanWarning() . " in $wgrp1:$tag (${type}Inv)"; + $self->VPrint(2, "$err\n"); + } + undef $val; + last Conv; + } elsif (not defined $val) { + $err = "Error converting value for $wgrp1:$tag (${type}Inv)"; + $self->VPrint(2, "$err\n"); + last Conv; + } + } elsif ($conv) { + if (ref $conv eq 'HASH' and (not exists $$tagInfo{"${type}Inv"} or $convInvList)) { + my ($multi, $lc); + # insert alternate language print conversions if required + if ($$self{CUR_LANG} and $type eq 'PrintConv' and + ref($lc = $$self{CUR_LANG}{$tag}) eq 'HASH' and + ($lc = $$lc{PrintConv})) + { + my %newConv; + foreach (keys %$conv) { + my $val = $$conv{$_}; + defined $$lc{$val} or $newConv{$_} = $val, next; + $newConv{$_} = $self->Decode($$lc{$val}, 'UTF8'); + } + if ($$conv{BITMASK}) { + foreach (keys %{$$conv{BITMASK}}) { + my $val = $$conv{BITMASK}{$_}; + defined $$lc{$val} or $newConv{BITMASK}{$_} = $val, next; + $newConv{BITMASK}{$_} = $self->Decode($$lc{$val}, 'UTF8'); + } + } + $conv = \%newConv; + } + undef $evalWarning; + if ($$conv{BITMASK}) { + my $lookupBits = $$conv{BITMASK}; + my ($wbits, $tbits) = @$tagInfo{'BitsPerWord','BitsTotal'}; + my ($val2, $err2) = EncodeBits($val, $lookupBits, $wbits, $tbits); + if ($err2) { + # ok, try matching a straight value + ($val, $multi) = ReverseLookup($val, $conv); + unless (defined $val) { + $err = "Can't encode $wgrp1:$tag ($err2)"; + $self->VPrint(2, "$err\n"); + last Conv; + } + } elsif (defined $val2) { + $val = $val2; + } else { + delete $$conv{BITMASK}; + ($val, $multi) = ReverseLookup($val, $conv); + $$conv{BITMASK} = $lookupBits; + } + } else { + ($val, $multi) = ReverseLookup($val, $conv); + } + if (not defined $val) { + my $prob = $evalWarning ? lcfirst CleanWarning() : ($multi ? 'matches more than one ' : 'not in ') . $type; + $err = "Can't convert $wgrp1:$tag ($prob)"; + $self->VPrint(2, "$err\n"); + last Conv; + } elsif ($evalWarning) { + $self->VPrint(2, CleanWarning() . " for $wgrp1:$tag\n"); + } + } elsif (not $$tagInfo{WriteAlso}) { + $err = "Can't convert value for $wgrp1:$tag (no ${type}Inv)"; + $self->VPrint(2, "$err\n"); + undef $val; + last Conv; + } + } + last unless @valList; + $valList[$index] = $val; + if (++$index >= @valList) { + # leave AutoSplit lists in ARRAY form, or join conversion lists + $val = $$tagInfo{List} ? \@valList : join ' ', @valList; + last; + } + $conv = $$convList[$index] if $convList; + $convInv = $$convInvList[$index] if $convInvList; + $val = $valList[$index]; + } + } # end ValueConv/PrintConv loop + + return($val, $err); +} + +#------------------------------------------------------------------------------ +# Convert tag names to values or variables in a string +# (eg. '${EXIF:ISO}x $$' --> '100x $' without hash ref, or "$info{'EXIF:ISO'}x $" with) +# Inputs: 0) ExifTool object ref, 1) reference to list of found tags +# 2) string with embedded tag names, 3) Options: +# undef - set missing tags to '' +# 'Error' - issue minor error on missing tag (and return undef) +# 'Warn' - issue minor warning on missing tag (and return undef) +# 'Silent' - just return undef on missing tag (no errors/warnings) +# Hash ref - defined to interpolate as variables in string instead of values +# --> receives tag/value pairs for interpolation of the variables +# 4) document group name if extracting from a specific document +# 5) hash ref to cache tag keys for subsequent calls in document loop +# Returns: string with embedded tag values (or '$info{TAGNAME}' entries with Hash ref option) +# Notes: +# - tag names are not case sensitive and may end with '#' for ValueConv value +# - uses MissingTagValue option if set +# - '$GROUP:all' evaluates to 1 if any tag from GROUP exists, or 0 otherwise +# - advanced feature allows Perl expressions inside braces (eg. '${model;tr/ //d}') +# - an error/warning in an advanced expression ("${TAG;EXPR}") generates an error +# if option set to 'Error', or a warning otherwise +sub InsertTagValues($$$;$$$) +{ + local $_; + my ($self, $foundTags, $line, $opt, $docGrp, $cache) = @_; + my $rtnStr = ''; + my ($docNum, $tag); + if ($docGrp) { + $docNum = $docGrp =~ /(\d+)$/ ? $1 : 0; + } else { + undef $cache; # no cache if no document groups + } + while ($line =~ s/(.*?)\$(\{\s*)?([-\w]*\w|\$|\/)//s) { + my ($pre, $bra, $var) = ($1, $2, $3); + my (@tags, $val, $tg, @val, $type, $expr, $didExpr, $level, $asList); + # "$$" represents a "$" symbol, and "$/" is a newline + if ($var eq '$' or $var eq '/') { + $line =~ s/^\s*\}// if $bra; + if ($var eq '/') { + $var = "\n"; + } elsif ($line =~ /^self\b/ and not $rtnStr =~ /\$$/) { + $var = '$$'; # ("$$self{var}" in string) + } + $rtnStr .= "$pre$var"; + next; + } + # allow multiple group names + while ($line =~ /^:([-\w]*\w)(.*)/s) { + my $group = $var; + ($var, $line) = ($1, $2); + $var = "$group:$var"; + } + # allow trailing '#' to indicate ValueConv value + $type = 'ValueConv' if $line =~ s/^#//; + # special advanced formatting '@' feature to evaluate list values separately + if ($bra and $line =~ s/^\@(#)?//) { + $asList = 1; + $type = 'ValueConv' if $1; + } + # remove trailing bracket if there was a leading one + # and extract Perl expression from inside brackets if it exists + if ($bra and $line !~ s/^\s*\}// and $line =~ s/^\s*;\s*(.*?)\s*\}//s) { + my $part = $1; + $expr = ''; + for ($level=0; ; --$level) { + # increase nesting level for each opening brace + ++$level while $part =~ /\{/g; + $expr .= $part; + last unless $level and $line =~ s/^(.*?)\s*\}//s; # get next part + $part = $1; + $expr .= '}'; # this brace was part of the expression + } + # use default Windows filename filter if expression is empty + $expr = 'tr(/\\\\?*:|"<>\\0)()d' unless length $expr; + } + push @tags, $var; + ExpandShortcuts(\@tags); + @tags or $rtnStr .= $pre, next; + # save advanced formatting expression to allow access by user-defined ValueConv + $$self{FMT_EXPR} = $expr; + + for (;;) { + # temporarily reset ListJoin option if evaluating list values separately + my $oldListJoin; + $oldListJoin = $self->Options(ListJoin => undef) if $asList; + $tag = shift @tags; + my $lcTag = lc $tag; + if ($cache and $lcTag !~ /(^|:)all$/) { + # remove group from tag name (but not lower-case version) + my $group; + $tag =~ s/^(.*):// and $group = $1; + # cache tag keys to speed processing for a large number of sub-documents + # (similar to code in BuildCompositeTags(), but this is case-insensitive) + my $cacheTag = $$cache{$lcTag}; + unless ($cacheTag) { + $cacheTag = $$cache{$lcTag} = [ ]; + # find all matching keys, organize into groups, and store in cache + my $ex = $$self{TAG_EXTRA}; + my @matches = grep /^$tag(\s|$)/i, @$foundTags; + @matches = $self->GroupMatches($group, \@matches) if defined $group; + foreach (@matches) { + my $doc = $$ex{$_} ? $$ex{$_}{G3} || 0 : 0; + if (defined $$cacheTag[$doc]) { + next unless $$cacheTag[$doc] =~ / \((\d+)\)$/; + my $cur = $1; + # keep the most recently extracted tag + next if / \((\d+)\)$/ and $1 < $cur; + } + $$cacheTag[$doc] = $_; + } + } + my $doc = $lcTag =~ /\b(main|doc(\d+)):/ ? ($2 || 0) : $docNum; + if ($$cacheTag[$doc]) { + $tag = $$cacheTag[$doc]; + $val = $self->GetValue($tag, $type); + } + } else { + # add document number to tag if specified and it doesn't already exist + if ($docGrp and $lcTag !~ /\b(main|doc\d+):/) { + $tag = $docGrp . ':' . $tag; + $lcTag = lc $tag; + } + my $et = $self; + if ($tag =~ s/(\bfile\d+)://i) { + $et = $$self{ALT_EXIFTOOL}{ucfirst lc $1} or $et=$self, $tag = 'no_alt_file'; + } + if ($lcTag eq 'all') { + $val = 1; # always some tag available + } elsif (defined $$et{OPTIONS}{UserParam}{$lcTag}) { + $val = $$et{OPTIONS}{UserParam}{$lcTag}; + } elsif ($tag =~ /(.*):(.+)/) { + my $group; + ($group, $tag) = ($1, $2); + if (lc $tag eq 'all') { + # see if any tag from the specified group exists + my $match = $et->GroupMatches($group, $foundTags); + $val = $match ? 1 : 0; + } else { + # find the specified tag + my @matches = grep /^$tag(\s|$)/i, @$foundTags; + @matches = $et->GroupMatches($group, \@matches); + foreach $tg (@matches) { + if (defined $val and $tg =~ / \((\d+)\)$/) { + # take the most recently extracted tag + my $tagNum = $1; + next if $tag !~ / \((\d+)\)$/ or $1 > $tagNum; + } + $val = $et->GetValue($tg, $type); + $tag = $tg; + last unless $tag =~ / /; # all done if we got our best match + } + } + } elsif ($tag eq 'self') { + $val = $et; # ("$self{var}" or "$file1:self{var}" in string) + } else { + # get the tag value + $val = $et->GetValue($tag, $type); + unless (defined $val) { + # check for tag name with different case + ($tg) = grep /^$tag$/i, @$foundTags; + if (defined $tg) { + $val = $et->GetValue($tg, $type); + $tag = $tg; + } + } + } + } + $self->Options(ListJoin => $oldListJoin) if $asList; + if (ref $val eq 'ARRAY') { + push @val, @$val; + undef $val; + last unless @tags; + } elsif (ref $val eq 'SCALAR') { + if ($$self{OPTIONS}{Binary} or $$val =~ /^Binary data/) { + $val = $$val; + } else { + $val = 'Binary data ' . length($$val) . ' bytes'; + } + } elsif (ref $val eq 'HASH') { + require 'Image/ExifTool/XMPStruct.pl'; + $val = Image::ExifTool::XMP::SerializeStruct($self, $val); + } elsif (not defined $val) { + $val = $$self{OPTIONS}{MissingTagValue} if $asList; + } + last unless @tags; + push @val, $val if defined $val; + undef $val; + } + if (@val) { + push @val, $val if defined $val; + $val = join $$self{OPTIONS}{ListSep}, @val; + } else { + push @val, $val if defined $val; # (so the eval has access to @val if required) + } + # evaluate advanced formatting expression if given (eg. "${TAG;EXPR}") + if (defined $expr and defined $val) { + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + $advFmtSelf = $self; + if ($asList) { + foreach (@val) { + #### eval advanced formatting expression ($_, $self, @val, $advFmtSelf) + eval $expr; + $@ and $evalWarning = $@; + } + # join back together if any values are still defined + @val = grep defined, @val; + $val = @val ? join $$self{OPTIONS}{ListSep}, @val : undef; + } else { + $_ = $val; + #### eval advanced formatting expression ($_, $self, @val, $advFmtSelf) + eval $expr; + $@ and $evalWarning = $@; + $val = ref $_ eq 'ARRAY' ? join($$self{OPTIONS}{ListSep}, @$_): $_; + } + if ($evalWarning) { + my $g3 = ($docGrp and $var !~ /\b(main|doc\d+):/i) ? $docGrp . ':' : ''; + my $str = CleanWarning() . " for '$g3${var}'"; + if ($opt) { + if ($opt eq 'Error') { + $self->Error($str); + } elsif ($opt ne 'Silent') { + $self->Warn($str); + } + } + } + undef $advFmtSelf; + $didExpr = 1; # set flag indicating an expression was evaluated + } + unless (defined $val) { + $val = $$self{OPTIONS}{MissingTagValue}; + unless (defined $val) { + my $g3 = ($docGrp and $var !~ /\b(main|doc\d+):/i) ? $docGrp . ':' : ''; + my $msg = $didExpr ? "Advanced formatting expression returned undef for '$g3${var}'" : + "Tag '$g3${var}' not defined"; + if (ref $opt) { + $self->Warn($msg,2) or $val = ''; + } elsif ($opt) { + no strict 'refs'; + ($opt eq 'Silent' or &$opt($self, $msg, 2)) and return $$self{FMT_EXPR} = undef; + $val = ''; + } + } + } + if (ref $opt eq 'HASH') { + $var .= '#' if $type; + if (defined $expr) { + # generate unique variable name for this modified tag value + my $i = 1; + ++$i while exists $$opt{"$var.expr$i"}; + $var .= '.expr' . $i; + } + $rtnStr .= "$pre\$info{'${var}'}"; + $$opt{$var} = $val; + } else { + $rtnStr .= "$pre$val"; + } + } + $$self{FMT_EXPR} = undef; + return $rtnStr . $line; +} + +#------------------------------------------------------------------------------ +# Reformat date/time value in $_ based on specified format string +# Inputs: 0) date/time format string +sub DateFmt($) +{ + my $et = bless { OPTIONS => { DateFormat => shift, StrictDate => 1 } }; + my $shift; + if ($advFmtSelf and defined($shift = $$advFmtSelf{OPTIONS}{GlobalTimeShift})) { + $$et{OPTIONS}{GlobalTimeShift} = $shift; + $$et{GLOBAL_TIME_OFFSET} = $$advFmtSelf{GLOBAL_TIME_OFFSET}; + } + $_ = $et->ConvertDateTime($_); + defined $_ or warn "Error converting date/time\n"; + $$advFmtSelf{GLOBAL_TIME_OFFSET} = $$et{GLOBAL_TIME_OFFSET} if $shift; +} + +#------------------------------------------------------------------------------ +# Utility routine to remove duplicate items from default input string +# Inputs: 0) true to set $_ to undef if not changed +# Notes: - for use only in advanced formatting expressions +sub NoDups +{ + my %seen; + my $sep = $advFmtSelf ? $$advFmtSelf{OPTIONS}{ListSep} : ', '; + my $new = join $sep, grep { !$seen{$_}++ } split /\Q$sep\E/, $_; + $_ = ($_[0] and $new eq $_) ? undef : $new; +} + +#------------------------------------------------------------------------------ +# Is specified tag writable +# Inputs: 0) tag name, case insensitive (optional group name currently ignored) +# Returns: 0=exists but not writable, 1=writable, undef=doesn't exist +sub IsWritable($) +{ + my $tag = shift; + $tag =~ s/^(.*)://; # ignore group name + my @tagInfo = FindTagInfo($tag); + unless (@tagInfo) { + return 0 if TagExists($tag); + return undef; + } + my $tagInfo; + foreach $tagInfo (@tagInfo) { + return $$tagInfo{Writable} ? 1 : 0 if defined $$tagInfo{Writable}; + return 1 if $$tagInfo{Table}{WRITABLE}; + # must call WRITE_PROC to autoload writer because this may set the writable tag + my $writeProc = $$tagInfo{Table}{WRITE_PROC}; + if ($writeProc) { + no strict 'refs'; + &$writeProc(); # dummy call to autoload writer + return 1 if $$tagInfo{Writable}; + } + } + return 0; +} + +#------------------------------------------------------------------------------ +# Check to see if these are the same file +# Inputs: 0) ExifTool ref, 1) first file name, 2) second file name +# Returns: true if file names reference the same file +sub IsSameFile($$$) +{ + my ($self, $file, $file2) = @_; + return 0 unless lc $file eq lc $file2; # (only looking for differences in case) + my ($isSame, $interrupted); + my $tmp1 = "${file}_ExifTool_tmp_$$"; + my $tmp2 = "${file2}_ExifTool_tmp_$$"; + { + local *TMP1; + local $SIG{INT} = sub { $interrupted = 1 }; + if ($self->Open(\*TMP1, $tmp1, '>')) { + close TMP1; + $isSame = 1 if $self->Exists($tmp2); + $self->Unlink($tmp1); + } + } + if ($interrupted and $SIG{INT}) { + no strict 'refs'; + &{$SIG{INT}}(); + } + return $isSame; +} + +#------------------------------------------------------------------------------ +# Is this a raw file type? +# Inputs: 0) ExifTool ref +# Returns: true if FileType is a type of RAW image +sub IsRawType($) +{ + my $self = shift; + return $rawType{$$self{FileType}}; +} + +#------------------------------------------------------------------------------ +# Create directory for specified file +# Inputs: 0) ExifTool ref, 1) complete file name including path +# Returns: 1 = directory created, 0 = nothing done, -1 = error +my $k32CreateDir; +sub CreateDirectory($$) +{ + local $_; + my ($self, $file) = @_; + my $rtnVal = 0; + my $enc = $$self{OPTIONS}{CharsetFileName}; + my $dir; + ($dir = $file) =~ s/[^\/]*$//; # remove filename from path specification + # recode as UTF-8 if necessary + if ($dir and not $self->IsDirectory($dir)) { + my @parts = split /\//, $dir; + $dir = ''; + foreach (@parts) { + $dir .= $_; + if (length $dir and not $self->IsDirectory($dir)) { + # create directory since it doesn't exist + my $d2 = $dir; # (must make a copy in case EncodeFileName recodes it) + if ($self->EncodeFileName($d2)) { + # handle Windows Unicode directory names + unless (eval { require Win32::API }) { + $self->Warn('Install Win32::API to create directories with Unicode names'); + return -1; + } + unless ($k32CreateDir) { + return -1 if defined $k32CreateDir; + $k32CreateDir = new Win32::API('KERNEL32', 'CreateDirectoryW', 'PP', 'I'); + unless ($k32CreateDir) { + $self->Warn('Error calling Win32::API::CreateDirectoryW'); + $k32CreateDir = 0; + return -1; + } + } + $k32CreateDir->Call($d2, 0) or return -1; + } else { + mkdir($d2, 0777) or return -1; + } + $rtnVal = 1; + } + $dir .= '/'; + } + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Copy file attributes from one file to another +# Inputs: 0) ExifTool ref, 1) source file name, 2) destination file name +# Notes: eventually add support for extended attributes? +sub CopyFileAttrs($$$) +{ + my ($self, $src, $dst) = @_; + my ($mode, $uid, $gid) = (stat($src))[2, 4, 5]; + # copy file attributes unless we already set them + if (defined $mode and not defined $self->GetNewValue('FilePermissions')) { + eval { chmod($mode & 07777, $dst) }; + } + my $newUid = $self->GetNewValue('FileUserID'); + my $newGid = $self->GetNewValue('FileGroupID'); + if (defined $uid and defined $gid and (not defined $newUid or not defined $newGid)) { + defined $newGid and $gid = $newGid; + defined $newUid and $uid = $newUid; + eval { chown($uid, $gid, $dst) }; + } +} + +#------------------------------------------------------------------------------ +# Get new file path name +# Inputs: 0) existing name (may contain directory), +# 1) new file name, new directory, or new path (dir+name) +# Returns: new file path name +sub GetNewFileName($$) +{ + my ($oldName, $newName) = @_; + my ($dir, $name) = ($oldName =~ m{(.*/)(.*)}); + ($dir, $name) = ('', $oldName) unless defined $dir; + if ($newName =~ m{/$}) { + $newName = "$newName$name"; # change dir only + } elsif ($newName !~ m{/}) { + $newName = "$dir$newName"; # change name only if newname doesn't specify dir + } # else change dir and name + return $newName; +} + +#------------------------------------------------------------------------------ +# Get next available tag key +# Inputs: 0) hash reference (keys are tag keys), 1) tag name +# Returns: next available tag key +sub NextFreeTagKey($$) +{ + my ($info, $tag) = @_; + return $tag unless exists $$info{$tag}; + my $i; + for ($i=1; ; ++$i) { + my $key = "$tag ($i)"; + return $key unless exists $$info{$key}; + } +} + +#------------------------------------------------------------------------------ +# Reverse hash lookup +# Inputs: 0) value, 1) hash reference +# Returns: Hash key or undef if not found (plus flag for multiple matches in list context) +sub ReverseLookup($$) +{ + my ($val, $conv) = @_; + return undef unless defined $val; + my $multi; + if ($val =~ /^Unknown\s*\((.*)\)$/i) { + $val = $1; # was unknown + if ($val =~ /^0x([\da-fA-F]+)$/) { + # disable "Hexadecimal number > 0xffffffff non-portable" warning + local $SIG{'__WARN__'} = sub { }; + $val = hex($val); # convert hex value + } + } else { + my $qval = $val; + $qval =~ s/\s+$//; # remove trailing whitespace + $qval = quotemeta $qval; + my @patterns = ( + "^$qval\$", # exact match + "^(?i)$qval\$", # case-insensitive + "^(?i)$qval", # beginning of string + "(?i)$qval", # substring + ); + # hash entries to ignore in reverse lookup + my ($pattern, $found, $matches); +PAT: foreach $pattern (@patterns) { + $matches = scalar grep /$pattern/, values(%$conv); + next unless $matches; + # multiple matches are bad unless they were exact + if ($matches > 1 and $pattern !~ /\$$/) { + # don't match entries that we should ignore + foreach (keys %ignorePrintConv) { + --$matches if defined $$conv{$_} and $$conv{$_} =~ /$pattern/; + } + last if $matches > 1; + } + foreach (sort keys %$conv) { + next if $$conv{$_} !~ /$pattern/ or $ignorePrintConv{$_}; + $val = $_; + $found = 1; + last PAT; + } + } + unless ($found) { + # call OTHER conversion routine if available + if ($$conv{OTHER}) { + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + $val = &{$$conv{OTHER}}($val,1,$conv); + } else { + $val = undef; + } + $multi = 1 if $matches > 1; + } + } + return ($val, $multi) if wantarray; + return $val; +} + +#------------------------------------------------------------------------------ +# Return true if we are deleting or overwriting the specified tag +# Inputs: 0) ExifTool object ref, 1) new value hash reference +# 2) optional tag value (before RawConv) if deleting specific values +# Returns: >0 - tag should be overwritten +# =0 - the tag should be preserved +# <0 - not sure, we need the old value to tell (if there is no old value +# then the tag should be written if $$nvHash{IsCreating} is true) +# Notes: $$nvHash{Value} is updated with the new value when shifting a value +sub IsOverwriting($$;$) +{ + my ($self, $nvHash, $val) = @_; + return 0 unless $nvHash; + # overwrite regardless if no DelValues specified + return 1 unless $$nvHash{DelValue}; + # never overwrite if DelValue list exists but is empty + my $shift = $$nvHash{Shift}; + return 0 unless @{$$nvHash{DelValue}} or defined $shift; + # return "don't know" if we don't have a value to test + return -1 unless defined $val; + # apply raw conversion if necessary + my $tagInfo = $$nvHash{TagInfo}; + my $conv = $$tagInfo{RawConv}; + if ($conv) { + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + if (ref $conv eq 'CODE') { + $val = &$conv($val, $self); + } else { + my ($priority, @grps); + my $tag = $$tagInfo{Name}; + #### eval RawConv ($self, $val, $tag, $tagInfo, $priority, @grps) + $val = eval $conv; + $@ and $evalWarning = $@; + } + return -1 unless defined $val; + } + # do not overwrite if only creating + return 0 if $$nvHash{CreateOnly}; + # apply time/number shift if necessary + if (defined $shift) { + my $shiftType = $$tagInfo{Shift}; + unless ($shiftType and $shiftType eq 'Time') { + unless (IsFloat($val)) { + # do the ValueConv to try to get a number + my $conv = $$tagInfo{ValueConv}; + if (defined $conv) { + local $SIG{'__WARN__'} = \&SetWarning; + undef $evalWarning; + if (ref $conv eq 'CODE') { + $val = &$conv($val, $self); + } elsif (not ref $conv) { + #### eval ValueConv ($val, $self) + $val = eval $conv; + $@ and $evalWarning = $@; + } + if ($evalWarning) { + $self->Warn("ValueConv $$tagInfo{Name}: " . CleanWarning()); + return 0; + } + } + unless (defined $val and IsFloat($val)) { + $self->Warn("Can't shift $$tagInfo{Name} (not a number)"); + return 0; + } + } + $shiftType = 'Number'; # allow any number to be shifted + } + require 'Image/ExifTool/Shift.pl'; + my $err = $self->ApplyShift($shiftType, $shift, $val, $nvHash); + if ($err) { + $self->Warn("$err when shifting $$tagInfo{Name}"); + return 0; + } + # ensure that the shifted value is valid and reformat if necessary + my $checkVal = $self->GetNewValue($nvHash); + return 0 unless defined $checkVal; + # don't bother overwriting if value is the same + return 0 if $val eq $$nvHash{Value}[0]; + return 1; + } + # return 1 if value matches a DelValue + my $delVal; + foreach $delVal (@{$$nvHash{DelValue}}) { + return 1 if $val eq $delVal; + } + return 0; +} + +#------------------------------------------------------------------------------ +# Get write group for specified tag +# Inputs: 0) new value hash reference +# Returns: Write group name +sub GetWriteGroup($) +{ + return $_[0]{WriteGroup}; +} + +#------------------------------------------------------------------------------ +# Get name of write group or family 1 group +# Inputs: 0) ExifTool ref, 1) tagInfo ref, 2) write group name +# Returns: Name of group for verbose message +sub GetWriteGroup1($$) +{ + my ($self, $tagInfo, $writeGroup) = @_; + return $writeGroup unless $writeGroup =~ /^(MakerNotes|XMP|Composite|QuickTime)$/; + return $self->GetGroup($tagInfo, 1); +} + +#------------------------------------------------------------------------------ +# Get new value hash for specified tagInfo/writeGroup +# Inputs: 0) ExifTool object reference, 1) reference to tag info hash +# 2) Write group name, 3) Options: 'delete' or 'create' new value hash +# 4) optional ProtectSaved value, 5) true if we are deleting a value +# Returns: new value hash reference for specified write group +# (or first new value hash in linked list if write group not specified) +# Notes: May return undef when 'create' is used with ProtectSaved +sub GetNewValueHash($$;$$$$) +{ + my ($self, $tagInfo, $writeGroup, $opts) = @_; + return undef unless $tagInfo; + my $nvHash = $$self{NEW_VALUE}{$tagInfo}; + + my %opts; # quick lookup for options + $opts and $opts{$opts} = 1; + $writeGroup = '' unless defined $writeGroup; + + if ($writeGroup) { + # find the new value in the list with the specified write group + while ($nvHash and $$nvHash{WriteGroup} ne $writeGroup) { + # QuickTime and All are special cases because all group1 tags may be updated at once + last if $$nvHash{WriteGroup} =~ /^(QuickTime|All)$/; + # replace existing entry if WriteGroup is 'All' (avoids confusion of forum10349) + last if $$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All'; + $nvHash = $$nvHash{Next}; + } + } + # remove this entry if deleting, or if creating a new entry and + # this entry is marked with "Save" flag + if (defined $nvHash and ($opts{'delete'} or ($opts{'create'} and $$nvHash{Save}))) { + my $protect = (defined $_[4] and defined $$nvHash{Save} and $$nvHash{Save} > $_[4]); + # this is a bit tricky: we want to add to a protected nvHash only if we + # are adding a conditional delete ($_[5] true or DelValue with no Shift) + # or accumulating List items (NoReplace true) + # (NOTE: this should be looked into --> lists may be accumulated instead of being replaced + # as expected when copying to the same list from different dynamic -tagsFromFile source files) + if ($protect and not ($opts{create} and ($$nvHash{NoReplace} or $_[5] or + ($$nvHash{DelValue} and not defined $$nvHash{Shift})))) + { + return undef; # honour ProtectSaved value by not writing this tag + } elsif ($opts{'delete'}) { + $self->RemoveNewValueHash($nvHash, $tagInfo); + undef $nvHash; + } else { + # save a copy of this new value hash + my %copy = %$nvHash; + # make copy of Value and DelValue lists + my $key; + foreach $key (keys %copy) { + next unless ref $copy{$key} eq 'ARRAY'; + $copy{$key} = [ @{$copy{$key}} ]; + } + my $saveHash = $$self{SAVE_NEW_VALUE}; + # add to linked list of saved new value hashes + $copy{Next} = $$saveHash{$tagInfo}; + $$saveHash{$tagInfo} = \%copy; + delete $$nvHash{Save}; # don't save it again + $$nvHash{AddBefore} = scalar @{$$nvHash{Value}} if $protect and $$nvHash{Value}; + } + } + if (not defined $nvHash and $opts{'create'}) { + # create a new entry + $nvHash = { + TagInfo => $tagInfo, + WriteGroup => $writeGroup, + IsNVH => 1, # set flag so we can recognize a new value hash + }; + # add entry to our NEW_VALUE hash + if ($$self{NEW_VALUE}{$tagInfo}) { + # add to end of linked list + my $lastHash = LastInList($$self{NEW_VALUE}{$tagInfo}); + $$lastHash{Next} = $nvHash; + } else { + $$self{NEW_VALUE}{$tagInfo} = $nvHash; + } + } + return $nvHash; +} + +#------------------------------------------------------------------------------ +# Load all tag tables +sub LoadAllTables() +{ + return if $loadedAllTables; + + # load all of our non-referenced tables (first our modules) + my $table; + foreach $table (@loadAllTables) { + my $tableName = "Image::ExifTool::$table"; + $tableName .= '::Main' unless $table =~ /:/; + GetTagTable($tableName); + } + # (then our special tables) + GetTagTable('Image::ExifTool::Extra'); + GetTagTable('Image::ExifTool::Composite'); + # recursively load all tables referenced by the current tables + my @tableNames = keys %allTables; + my %pushedTables; + while (@tableNames) { + $table = GetTagTable(shift @tableNames); + # call write proc if it exists in case it adds tags to the table + my $writeProc = $$table{WRITE_PROC}; + if ($writeProc) { + no strict 'refs'; + &$writeProc(); + } + # recursively scan through tables in subdirectories + foreach (TagTableKeys($table)) { + my @infoArray = GetTagInfoList($table,$_); + my $tagInfo; + foreach $tagInfo (@infoArray) { + my $subdir = $$tagInfo{SubDirectory} or next; + my $tableName = $$subdir{TagTable} or next; + # next if table already loaded or queued for loading + next if $allTables{$tableName} or $pushedTables{$tableName}; + push @tableNames, $tableName; # must scan this one too + $pushedTables{$tableName} = 1; + } + } + } + $loadedAllTables = 1; +} + +#------------------------------------------------------------------------------ +# Remove new value hash from linked list (and save if necessary) +# Inputs: 0) ExifTool object reference, 1) new value hash ref, 2) tagInfo ref +sub RemoveNewValueHash($$$) +{ + my ($self, $nvHash, $tagInfo) = @_; + my $firstHash = $$self{NEW_VALUE}{$tagInfo}; + if ($nvHash eq $firstHash) { + # remove first entry from linked list + if ($$nvHash{Next}) { + $$self{NEW_VALUE}{$tagInfo} = $$nvHash{Next}; + } else { + delete $$self{NEW_VALUE}{$tagInfo}; + } + } else { + # find the list element pointing to this hash + $firstHash = $$firstHash{Next} while $$firstHash{Next} ne $nvHash; + # remove from linked list + $$firstHash{Next} = $$nvHash{Next}; + } + # save the existing entry if necessary + if ($$nvHash{Save}) { + my $saveHash = $$self{SAVE_NEW_VALUE}; + # add to linked list of saved new value hashes + $$nvHash{Next} = $$saveHash{$tagInfo}; + $$saveHash{$tagInfo} = $nvHash; + } +} + +#------------------------------------------------------------------------------ +# Remove all new value entries for specified group +# Inputs: 0) ExifTool object reference, 1) group name +sub RemoveNewValuesForGroup($$) +{ + my ($self, $group) = @_; + + return unless $$self{NEW_VALUE}; + + # make list of all groups we must remove + my @groups = ( $group ); + push @groups, @{$removeGroups{$group}} if $removeGroups{$group}; + + my ($out, @keys, $hashKey); + $out = $$self{OPTIONS}{TextOut} if $$self{OPTIONS}{Verbose} > 1; + + # loop though all new values, and remove any in this group + @keys = keys %{$$self{NEW_VALUE}}; + foreach $hashKey (@keys) { + my $nvHash = $$self{NEW_VALUE}{$hashKey}; + # loop through each entry in linked list + for (;;) { + my $nextHash = $$nvHash{Next}; + my $tagInfo = $$nvHash{TagInfo}; + my ($grp0,$grp1) = $self->GetGroup($tagInfo); + my $wgrp = $$nvHash{WriteGroup}; + # use group1 if write group is not specific + $wgrp = $grp1 if $wgrp eq $grp0; + if (grep /^($grp0|$wgrp)$/i, @groups) { + $out and print $out "Removed new value for $wgrp:$$tagInfo{Name}\n"; + # remove from linked list + $self->RemoveNewValueHash($nvHash, $tagInfo); + } + $nvHash = $nextHash or last; + } + } +} + +#------------------------------------------------------------------------------ +# Get list of tagInfo hashes for all new data +# Inputs: 0) ExifTool object reference, 1) optional tag table pointer +# Returns: list of tagInfo hashes +sub GetNewTagInfoList($;$) +{ + my ($self, $tagTablePtr) = @_; + my @tagInfoList; + my $nv = $$self{NEW_VALUE}; + if ($nv) { + my $hashKey; + foreach $hashKey (keys %$nv) { + my $tagInfo = $$nv{$hashKey}{TagInfo}; + next if $tagTablePtr and $tagTablePtr ne $$tagInfo{Table}; + push @tagInfoList, $tagInfo; + } + } + return @tagInfoList; +} + +#------------------------------------------------------------------------------ +# Get hash of tagInfo references keyed on tagID for a specific table +# Inputs: 0) ExifTool object reference, 1-N) tag table pointers +# Returns: hash reference +# Notes: returns only one tagInfo ref for each conditional list +sub GetNewTagInfoHash($@) +{ + my $self = shift; + my (%tagInfoHash, $hashKey); + my $nv = $$self{NEW_VALUE}; + while ($nv) { + my $tagTablePtr = shift || last; + foreach $hashKey (keys %$nv) { + my $tagInfo = $$nv{$hashKey}{TagInfo}; + next if $tagTablePtr and $tagTablePtr ne $$tagInfo{Table}; + $tagInfoHash{$$tagInfo{TagID}} = $tagInfo; + } + } + return \%tagInfoHash; +} + +#------------------------------------------------------------------------------ +# Get a tagInfo/tagID hash for subdirectories we need to add +# Inputs: 0) ExifTool object reference, 1) parent tag table reference +# 2) parent directory name (taken from GROUP0 of tag table if not defined) +# Returns: Reference to Hash of subdirectory tagInfo references keyed by tagID +# (plus Reference to edit directory hash in list context) +sub GetAddDirHash($$;$) +{ + my ($self, $tagTablePtr, $parent) = @_; + $parent or $parent = $$tagTablePtr{GROUPS}{0}; + my $tagID; + my %addDirHash; + my %editDirHash; + my $addDirs = $$self{ADD_DIRS}; + my $editDirs = $$self{EDIT_DIRS}; + foreach $tagID (TagTableKeys($tagTablePtr)) { + my @infoArray = GetTagInfoList($tagTablePtr,$tagID); + my $tagInfo; + foreach $tagInfo (@infoArray) { + next unless $$tagInfo{SubDirectory}; + # get name for this sub directory + # (take directory name from SubDirectory DirName if it exists, + # otherwise Group0 name of SubDirectory TagTable or tag Group1 name) + my $dirName = $$tagInfo{SubDirectory}{DirName}; + unless ($dirName) { + # use tag name for directory name and save for next time + $dirName = $$tagInfo{Name}; + $$tagInfo{SubDirectory}{DirName} = $dirName; + } + # save this directory information if we are writing it + if ($$editDirs{$dirName} and $$editDirs{$dirName} eq $parent) { + $editDirHash{$tagID} = $tagInfo; + $addDirHash{$tagID} = $tagInfo if $$addDirs{$dirName}; + } + } + } + return (\%addDirHash, \%editDirHash) if wantarray; + return \%addDirHash; +} + +#------------------------------------------------------------------------------ +# Get localized version of tagInfo hash (used by MIE, XMP, PNG and QuickTime) +# Inputs: 0) tagInfo hash ref, 1) locale code (eg. "en_CA" for MIE) +# Returns: new tagInfo hash ref, or undef if invalid +# - sets LangCode member in new tagInfo +sub GetLangInfo($$) +{ + my ($tagInfo, $langCode) = @_; + # make a new tagInfo hash for this locale + my $table = $$tagInfo{Table}; + my $tagID = $$tagInfo{TagID} . '-' . $langCode; + my $langInfo = $$table{$tagID}; + unless ($langInfo) { + # make a new tagInfo entry for this locale + $langInfo = { + %$tagInfo, + Name => $$tagInfo{Name} . '-' . $langCode, + Description => Image::ExifTool::MakeDescription($$tagInfo{Name}) . + " ($langCode)", + LangCode => $langCode, + SrcTagInfo => $tagInfo, # save reference to original tagInfo + }; + AddTagToTable($table, $tagID, $langInfo); + } + return $langInfo; +} + +#------------------------------------------------------------------------------ +# initialize ADD_DIRS and EDIT_DIRS hashes for all directories that need +# to be created or will have tags changed in them +# Inputs: 0) ExifTool object reference, 1) file type string (or map hash ref) +# 2) preferred family 0 group for creating tags, 3) alternate preferred group +# Notes: +# - the ADD_DIRS and EDIT_DIRS keys are the directory names, and the values +# are the names of the parent directories (undefined for a top-level directory) +# - also initializes FORCE_WRITE lookup +sub InitWriteDirs($$;$$) +{ + my ($self, $fileType, $preferredGroup, $altGroup) = @_; + my $editDirs = $$self{EDIT_DIRS} = { }; + my $addDirs = $$self{ADD_DIRS} = { }; + my $fileDirs = $dirMap{$fileType}; + unless ($fileDirs) { + return unless ref $fileType eq 'HASH'; + $fileDirs = $fileType; + } + my @tagInfoList = $self->GetNewTagInfoList(); + my ($tagInfo, $nvHash); + + # save the preferred group + $$self{PreferredGroup} = $preferredGroup; + + foreach $tagInfo (@tagInfoList) { + # cycle through all hashes in linked list + for ($nvHash=$self->GetNewValueHash($tagInfo); $nvHash; $nvHash=$$nvHash{Next}) { + # are we creating this tag? (otherwise just deleting or editing it) + my $isCreating = $$nvHash{IsCreating}; + if ($preferredGroup) { + my $g0 = $self->GetGroup($tagInfo, 0); + if ($isCreating) { + # if another group is taking priority, only create + # directory if specifically adding tags to this group + # or if this tag isn't being added to the priority group + $isCreating = 0 if $preferredGroup ne $g0 and + $$nvHash{CreateGroups}{$preferredGroup} and + (not $altGroup or $altGroup ne $g0); + } else { + # create this directory if any tag is preferred and has a value + # (unless group creation is disabled via the WriteMode option) + $isCreating = 1 if $$nvHash{Value} and $preferredGroup eq $g0 and + not $$nvHash{EditOnly} and $$self{OPTIONS}{WriteMode} =~ /g/; + } + } + # tag belongs to directory specified by WriteGroup, or by + # the Group0 name if WriteGroup not defined + my $dirName = $$nvHash{WriteGroup}; + # remove MIE copy number(s) if they exist + if ($dirName =~ /^MIE\d*(-[a-z]+)?\d*$/i) { + $dirName = 'MIE' . ($1 || ''); + } + my @dirNames; + # allow a group name of '*' to force writing EXIF/IPTC/XMP/PNG (ForceWrite tag) + if ($dirName eq '*' and $$nvHash{Value}) { + my $val = $$nvHash{Value}[0]; + if ($val) { + foreach (qw(EXIF IPTC XMP PNG FixBase)) { + next unless $val =~ /\b($_|All)\b/i; + push @dirNames, $_; + push @dirNames, 'EXIF' if $_ eq 'FixBase'; + $$self{FORCE_WRITE}{$_} = 1; + } + } + $dirName = shift @dirNames; + } elsif ($dirName eq 'QuickTime') { + # write to specific QuickTime group + $dirName = $self->GetGroup($tagInfo, 1); + } + while ($dirName) { + my $parent = $$fileDirs{$dirName}; + if (ref $parent) { + push @dirNames, reverse @$parent; + $parent = pop @dirNames; + } + $$editDirs{$dirName} = $parent; + $$addDirs{$dirName} = $parent if $isCreating and $isCreating != 2; + $dirName = $parent || shift @dirNames + } + } + } + if (%{$$self{DEL_GROUP}}) { + # add delete groups to list of edited groups + foreach (keys %{$$self{DEL_GROUP}}) { + next if /^-/; # ignore excluded groups + my $dirName = $_; + # translate necessary group 0 names + $dirName = $translateWriteGroup{$dirName} if $translateWriteGroup{$dirName}; + # convert XMP group 1 names + $dirName = 'XMP' if $dirName =~ /^XMP-/; + my @dirNames; + while ($dirName) { + my $parent = $$fileDirs{$dirName}; + if (ref $parent) { + push @dirNames, reverse @$parent; + $parent = pop @dirNames; + } + $$editDirs{$dirName} = $parent; + $dirName = $parent || shift @dirNames + } + } + } + # special case to edit JFIF to get resolutions if editing EXIF information + if ($$editDirs{IFD0} and $$fileDirs{JFIF}) { + $$editDirs{JFIF} = 'IFD1'; + $$editDirs{APP0} = undef; + } + + if ($$self{OPTIONS}{Verbose}) { + my $out = $$self{OPTIONS}{TextOut}; + print $out " Editing tags in: "; + foreach (sort keys %$editDirs) { print $out "$_ "; } + print $out "\n"; + return unless $$self{OPTIONS}{Verbose} > 1; + print $out " Creating tags in: "; + foreach (sort keys %$addDirs) { print $out "$_ "; } + print $out "\n"; + } +} + +#------------------------------------------------------------------------------ +# Write an image directory +# Inputs: 0) ExifTool object reference, 1) source directory information reference +# 2) tag table reference, 3) optional reference to writing procedure +# Returns: New directory data or undefined on error (or empty string to delete directory) +sub WriteDirectory($$$;$) +{ + my ($self, $dirInfo, $tagTablePtr, $writeProc) = @_; + my ($out, $nvHash, $delFlag); + + $tagTablePtr or return undef; + $out = $$self{OPTIONS}{TextOut} if $$self{OPTIONS}{Verbose}; + # set directory name from default group0 name if not done already + my $dirName = $$dirInfo{DirName}; + my $dataPt = $$dirInfo{DataPt}; + my $grp0 = $$tagTablePtr{GROUPS}{0}; + $dirName or $dirName = $$dirInfo{DirName} = $grp0; + if (%{$$self{DEL_GROUP}}) { + my $delGroup = $$self{DEL_GROUP}; + # delete entire directory if specified + my $grp1 = $dirName; + $delFlag = ($$delGroup{$grp0} or $$delGroup{$grp1}) unless $permanentDir{$grp0}; + # (never delete an entire QuickTime group) + if ($delFlag) { + if (($grp0 =~ /^(MakerNotes)$/ or $grp1 =~ /^(IFD0|ExifIFD|MakerNotes)$/) and + $self->IsRawType() and + # allow non-permanent MakerNote directories to be deleted (ie. NikonCapture) + (not $$dirInfo{TagInfo} or not defined $$dirInfo{TagInfo}{Permanent} or + $$dirInfo{TagInfo}{Permanent})) + { + $self->WarnOnce("Can't delete $1 from $$self{FileType}",1); + undef $grp1; + } elsif (not $blockExifTypes{$$self{FILE_TYPE}}) { + # restrict delete logic to prevent entire tiff image from being killed + # (don't allow IFD0 to be deleted, and delete only ExifIFD if EXIF specified) + if ($$self{FILE_TYPE} eq 'PSD') { + # don't delete Photoshop directories from PSD image + undef $grp1 if $grp0 eq 'Photoshop'; + } elsif ($$self{FILE_TYPE} =~ /^(EPS|PS)$/) { + # allow anything to be deleted from PostScript files + } elsif ($grp1 eq 'IFD0') { + my $type = $$self{TIFF_TYPE} || $$self{FILE_TYPE}; + $$delGroup{IFD0} and $self->Warn("Can't delete IFD0 from $type",1); + undef $grp1; + } elsif ($grp0 eq 'EXIF' and $$delGroup{$grp0}) { + undef $grp1 unless $$delGroup{$grp1} or $grp1 eq 'ExifIFD'; + } + } + if ($grp1) { + if ($dataPt or $$dirInfo{RAF}) { + ++$$self{CHANGED}; + $out and print $out " Deleting $grp1\n"; + $self->Warn('ICC_Profile deleted. Image colors may be affected') if $grp1 eq 'ICC_Profile'; + # can no longer validate TIFF_END if deleting an entire IFD + delete $$self{TIFF_END} if $dirName =~ /IFD/; + } + # don't add back into the wrong location + my $right = $$self{ADD_DIRS}{$grp1}; + # (take care because EXIF directory name may be either EXIF or IFD0, + # but IFD0 will be the one that appears in the directory map) + $right = $$self{ADD_DIRS}{IFD0} if not $right and $grp1 eq 'EXIF'; + if ($delFlag == 2 and $right) { + # also check grandparent because some routines create 2 levels in 1 + my $right2 = $$self{ADD_DIRS}{$right} || ''; + my $parent = $$dirInfo{Parent}; + if (not $parent or $parent eq $right or $parent eq $right2) { + # prevent duplicate directories from being recreated at the same path + my $path = join '-', @{$$self{PATH}}, $dirName; + $$self{Recreated} or $$self{Recreated} = { }; + if ($$self{Recreated}{$path}) { + my $p = $parent ? " in $parent" : ''; + $self->Warn("Not recreating duplicate $grp1$p",1); + return ''; + } + $$self{Recreated}{$path} = 1; + # empty the directory + my $data = ''; + $$dirInfo{DataPt} = \$data; + $$dirInfo{DataLen} = 0; + $$dirInfo{DirStart} = 0; + $$dirInfo{DirLen} = 0; + delete $$dirInfo{RAF}; + delete $$dirInfo{Base}; + delete $$dirInfo{DataPos}; + } else { + $self->Warn("Not recreating $grp1 in $parent (should be in $right)",1); + return ''; + } + } else { + return '' unless $$dirInfo{NoDelete}; + } + } + } + } + # use default proc from tag table if no proc specified + $writeProc or $writeProc = $$tagTablePtr{WRITE_PROC} or return undef; + + # are we rewriting a pre-existing directory? + my $isRewriting = ($$dirInfo{DirLen} or (defined $dataPt and length $$dataPt) or $$dirInfo{RAF}); + + # copy or delete new directory as a block if specified + my $blockName = $dirName; + $blockName = 'EXIF' if $blockName eq 'IFD0'; + my $tagInfo = $Image::ExifTool::Extra{$blockName} || $$dirInfo{TagInfo}; + while ($tagInfo and ($nvHash = $$self{NEW_VALUE}{$tagInfo}) and + $self->IsOverwriting($nvHash) and not ($$nvHash{CreateOnly} and $isRewriting)) + { + # protect against writing EXIF to wrong file types, etc + if ($blockName eq 'EXIF') { + unless ($blockExifTypes{$$self{FILE_TYPE}}) { + $self->Warn("Can't write EXIF as a block to $$self{FILE_TYPE} file"); + last; + } + # this can happen if we call WriteDirectory for an EXIF directory without going + # through WriteTIFF as the WriteProc (which happens if conditionally replacing + # the EXIF block and the condition fails), but we never want to do a block write + # in this case because the EXIF block would end up with two TIFF headers + last unless $writeProc eq \&Image::ExifTool::WriteTIFF; + } + last unless $self->IsOverwriting($nvHash, $dataPt ? $$dataPt : ''); + my $verb = 'Writing'; + my $newVal = $self->GetNewValue($nvHash); + unless (defined $newVal and length $newVal) { + return '' unless $dataPt or $$dirInfo{RAF}; # nothing to do if block never existed + # don't allow MakerNotes to be removed from RAW files + if ($blockName eq 'MakerNotes' and $rawType{$$self{FileType}}) { + $self->Warn("Can't delete MakerNotes from $$self{FileType}",1); + return undef; + } + $verb = 'Deleting'; + $newVal = ''; + } + $$dirInfo{BlockWrite} = 1; # set flag indicating we did a block write + $out and print $out " $verb $blockName as a block\n"; + ++$$self{CHANGED}; + return $newVal; + } + # guard against writing the same directory twice + if (defined $dataPt and defined $$dirInfo{DirStart} and defined $$dirInfo{DataPos} and + not $$dirInfo{NoRefTest}) + { + my $addr = $$dirInfo{DirStart} + $$dirInfo{DataPos} + ($$dirInfo{Base}||0) + $$self{BASE}; + # (Phase One P25 IIQ files have ICC_Profile duplicated in IFD0 and IFD1) + if ($$self{PROCESSED}{$addr} and ($dirName ne 'ICC_Profile' or $$self{TIFF_TYPE} ne 'IIQ')) { + if (defined $$dirInfo{DirLen} and not $$dirInfo{DirLen} and $dirName ne $$self{PROCESSED}{$addr}) { + # it is hypothetically possible to have 2 different directories + # with the same address if one has a length of zero + } elsif ($self->Error("$dirName pointer references previous $$self{PROCESSED}{$addr} directory", 2)) { + return undef; + } else { + $self->Warn("Deleting duplicate $dirName directory"); + $out and print $out " Deleting $dirName\n"; + # delete the duplicate directory (don't recreate it when writing new + # tags to prevent propagating a duplicate IFD in cases like when the + # same ExifIFD exists in both IFD0 and IFD1) + return ''; + } + } else { + $$self{PROCESSED}{$addr} = $dirName; + } + } + my $oldDir = $$self{DIR_NAME}; + my @save = @$self{'Compression','SubfileType'}; + my $name; + if ($out) { + $name = ($dirName eq 'MakerNotes' and $$dirInfo{TagInfo}) ? + $$dirInfo{TagInfo}{Name} : $dirName; + if (not defined $oldDir or $oldDir ne $name) { + my $verb = $isRewriting ? 'Rewriting' : 'Creating'; + print $out " $verb $name\n"; + } + } + my $saveOrder = GetByteOrder(); + my $oldChanged = $$self{CHANGED}; + $$self{DIR_NAME} = $dirName; + push @{$$self{PATH}}, $dirName; + $$dirInfo{IsWriting} = 1; + my $newData; + { + no strict 'refs'; + $newData = &$writeProc($self, $dirInfo, $tagTablePtr); + } + pop @{$$self{PATH}}; + # nothing changed if error occurred or nothing was created + $$self{CHANGED} = $oldChanged unless defined $newData and (length($newData) or $isRewriting); + $$self{DIR_NAME} = $oldDir; + @$self{'Compression','SubfileType'} = @save; + SetByteOrder($saveOrder); + if ($out) { + print $out " Deleting $name\n" if defined $newData and not length $newData; + if ($$self{CHANGED} == $oldChanged and $$self{OPTIONS}{Verbose} > 2) { + print $out "$$self{INDENT} [nothing changed in $name]\n"; + } + } + return $newData; +} + +#------------------------------------------------------------------------------ +# Uncommon utility routines to for reading binary data values +# Inputs: 0) data reference, 1) offset into data +sub Get64s($$) +{ + my ($dataPt, $pos) = @_; + my $pt = GetByteOrder() eq 'MM' ? 0 : 4; # get position of high word + my $hi = Get32s($dataPt, $pos + $pt); # preserve sign bit of high word + my $lo = Get32u($dataPt, $pos + 4 - $pt); + return $hi * 4294967296 + $lo; +} +sub Get64u($$) +{ + my ($dataPt, $pos) = @_; + my $pt = GetByteOrder() eq 'MM' ? 0 : 4; # get position of high word + my $hi = Get32u($dataPt, $pos + $pt); # (unsigned this time) + my $lo = Get32u($dataPt, $pos + 4 - $pt); + return $hi * 4294967296 + $lo; +} +sub GetFixed64s($$) +{ + my ($dataPt, $pos) = @_; + my $val = Get64s($dataPt, $pos) / 4294967296; + # remove insignificant digits + return int($val * 1e10 + ($val>0 ? 0.5 : -0.5)) / 1e10; +} +# Decode extended 80-bit float used by Apple SANE and Intel 8087 +# (note: different than the IEEE standard 80-bit float) +sub GetExtended($$) +{ + my ($dataPt, $pos) = @_; + my $pt = GetByteOrder() eq 'MM' ? 0 : 2; # get position of exponent + my $exp = Get16u($dataPt, $pos + $pt); + my $sig = Get64u($dataPt, $pos + 2 - $pt); # get significand as int64u + my $sign = $exp & 0x8000 ? -1 : 1; + $exp = ($exp & 0x7fff) - 16383 - 63; # (-63 to fractionalize significand) + return $sign * $sig * 2 ** $exp; +} + +#------------------------------------------------------------------------------ +# Dump data in hex and ASCII to console +# Inputs: 0) data reference, 1) length or undef, 2-N) Options: +# Options: Start => offset to start of data (default=0) +# Addr => address to print for data start (default=DataPos+Base+Start) +# DataPos => position of data within block (relative to Base) +# Base => base offset for pointers from start of file +# Width => width of printout (bytes, default=16) +# Prefix => prefix to print at start of line (default='') +# MaxLen => maximum length to dump +# Out => output file reference +# Len => data length +sub HexDump($;$%) +{ + my $dataPt = shift; + my $len = shift; + my %opts = @_; + my $start = $opts{Start} || 0; + my $addr = $opts{Addr}; + my $wid = $opts{Width} || 16; + my $prefix = $opts{Prefix} || ''; + my $out = $opts{Out} || \*STDOUT; + my $maxLen = $opts{MaxLen}; + my $datLen = length($$dataPt) - $start; + my $more; + $len = $opts{Len} if defined $opts{Len}; + + $addr = $start + ($opts{DataPos} || 0) + ($opts{Base} || 0) unless defined $addr; + $len = $datLen unless defined $len; + if ($maxLen and $len > $maxLen) { + # print one line less to allow for $more line below + $maxLen = int(($maxLen - 1) / $wid) * $wid; + $more = $len - $maxLen; + $len = $maxLen; + } + if ($len > $datLen) { + print $out "$prefix Warning: Attempted dump outside data\n"; + print $out "$prefix ($len bytes specified, but only $datLen available)\n"; + $len = $datLen; + } + my $format = sprintf("%%-%ds", $wid * 3); + my $tmpl = 'H2' x $wid; # ('(H2)*' would have been nice, but older perl versions don't support it) + my $i; + for ($i=0; $i<$len; $i+=$wid) { + $wid > $len-$i and $wid = $len-$i, $tmpl = 'H2' x $wid; + printf $out "$prefix%8.4x: ", $addr+$i; + my $dat = substr($$dataPt, $i+$start, $wid); + my $s = join(' ', unpack($tmpl, $dat)); + printf $out $format, $s; + $dat =~ tr /\x00-\x1f\x7f-\xff/./; + print $out "[$dat]\n"; + } + $more and print $out "$prefix [snip $more bytes]\n"; +} + +#------------------------------------------------------------------------------ +# Print verbose tag information +# Inputs: 0) ExifTool object reference, 1) tag ID +# 2) tag info reference (or undef) +# 3-N) extra parms: +# Parms: Index => Index of tag in menu (starting at 0) +# Value => Tag value +# DataPt => reference to value data block +# DataPos => location of data block in file +# Base => base added to all offsets +# Size => length of value data within block +# Format => value format string +# Count => number of values +# Extra => Extra Verbose=2 information to put after tag number +# Table => Reference to tag table +# --> plus any of these HexDump() options: Start, Addr, Width +sub VerboseInfo($$$%) +{ + my ($self, $tagID, $tagInfo, %parms) = @_; + my $verbose = $$self{OPTIONS}{Verbose}; + my $out = $$self{OPTIONS}{TextOut}; + my ($tag, $line, $hexID); + + # generate hex number if tagID is numerical + if (defined $tagID) { + $tagID =~ /^\d+$/ and $hexID = sprintf("0x%.4x", $tagID); + } else { + $tagID = 'Unknown'; + } + # get tag name + if ($tagInfo and $$tagInfo{Name}) { + $tag = $$tagInfo{Name}; + } else { + my $prefix; + $prefix = $parms{Table}{TAG_PREFIX} if $parms{Table}; + if ($prefix or $hexID) { + $prefix = 'Unknown' unless $prefix; + $tag = $prefix . '_' . ($hexID ? $hexID : $tagID); + } else { + $tag = $tagID; + } + } + my $dataPt = $parms{DataPt}; + my $size = $parms{Size}; + $size = length $$dataPt unless defined $size or not $dataPt; + my $indent = $$self{INDENT}; + + # Level 1: print tag/value information + $line = $indent; + my $index = $parms{Index}; + if (defined $index) { + $line .= $index . ') '; + $line .= ' ' if length($index) < 2; + $indent .= ' '; # indent everything else to align with tag name + } + $line .= $tag; + if ($tagInfo and $$tagInfo{SubDirectory}) { + $line .= ' (SubDirectory) -->'; + } else { + my $maxLen = 90 - length($line); + my $val = $parms{Value}; + if (defined $val) { + $val = '[' . join(',',@$val) . ']' if ref $val eq 'ARRAY'; + $line .= ' = ' . $self->Printable($val, $maxLen); + } elsif ($dataPt) { + my $start = $parms{Start} || 0; + $line .= ' = ' . $self->Printable(substr($$dataPt,$start,$size), $maxLen); + } + } + print $out "$line\n"; + + # Level 2: print detailed information about the tag + if ($verbose > 1 and ($parms{Extra} or $parms{Format} or + $parms{DataPt} or defined $size or $tagID =~ /\//)) + { + $line = $indent . '- Tag '; + if ($hexID) { + $line .= $hexID; + } else { + $tagID =~ s/([\0-\x1f\x7f-\xff])/sprintf('\\x%.2x',ord $1)/ge; + $line .= "'${tagID}'"; + } + $line .= $parms{Extra} if defined $parms{Extra}; + my $format = $parms{Format}; + if ($format or defined $size) { + $line .= ' ('; + if (defined $size) { + $line .= "$size bytes"; + $line .= ', ' if $format; + } + if ($format) { + $line .= $format; + $line .= '['.$parms{Count}.']' if $parms{Count}; + } + $line .= ')'; + } + $line .= ':' if $verbose > 2 and $parms{DataPt}; + print $out "$line\n"; + } + + # Level 3: do hex dump of value + if ($verbose > 2 and $parms{DataPt} and (not $tagInfo or not $$tagInfo{ReadFromRAF})) { + $parms{Out} = $out; + $parms{Prefix} = $indent; + # limit dump length if Verbose < 5 + $parms{MaxLen} = $verbose == 3 ? 96 : 2048 if $verbose < 5; + HexDump($dataPt, $size, %parms); + } +} + +#------------------------------------------------------------------------------ +# Dump trailer information +# Inputs: 0) ExifTool object ref, 1) dirInfo hash (RAF, DirName, DataPos, DirLen) +# Notes: Restores current file position before returning +sub DumpTrailer($$) +{ + my ($self, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my $curPos = $raf->Tell(); + my $trailer = $$dirInfo{DirName} || 'Unknown'; + my $pos = $$dirInfo{DataPos}; + my $verbose = $$self{OPTIONS}{Verbose}; + my $htmlDump = $$self{HTML_DUMP}; + my ($buff, $buf2); + my $size = $$dirInfo{DirLen}; + $pos = $curPos unless defined $pos; + + # get full trailer size if not specified + for (;;) { + unless ($size) { + $raf->Seek(0, 2) or last; + $size = $raf->Tell() - $pos; + last unless $size; + } + $raf->Seek($pos, 0) or last; + if ($htmlDump) { + my $num = $raf->Read($buff, $size) or return; + my $desc = "$trailer trailer"; + $desc = "[$desc]" if $trailer eq 'Unknown'; + $self->HDump($pos, $num, $desc, undef, 0x08); + last; + } + my $out = $$self{OPTIONS}{TextOut}; + printf $out "$trailer trailer (%d bytes at offset 0x%.4x):\n", $size, $pos; + last unless $verbose > 2; + my $num = $size; # number of bytes to read + # limit size if not very verbose + if ($verbose < 5) { + my $limit = $verbose < 4 ? 96 : 512; + $num = $limit if $num > $limit; + } + $raf->Read($buff, $num) == $num or return; + # read the end of the trailer too if not done already + if ($size > 2 * $num) { + $raf->Seek($pos + $size - $num, 0); + $raf->Read($buf2, $num); + } elsif ($size > $num) { + $raf->Seek($pos + $num, 0); + $raf->Read($buf2, $size - $num); + $buff .= $buf2; + undef $buf2; + } + HexDump(\$buff, undef, Addr => $pos, Out => $out); + if (defined $buf2) { + print $out " [snip ", $size - $num * 2, " bytes]\n"; + HexDump(\$buf2, undef, Addr => $pos + $size - $num, Out => $out); + } + last; + } + $raf->Seek($curPos, 0); +} + +#------------------------------------------------------------------------------ +# Dump unknown trailer information +# Inputs: 0) ExifTool ref, 1) dirInfo ref (with RAF, DataPos and DirLen defined) +# Notes: changes dirInfo elements +sub DumpUnknownTrailer($$) +{ + my ($self, $dirInfo) = @_; + my $pos = $$dirInfo{DataPos}; + my $endPos = $pos + $$dirInfo{DirLen}; + # account for preview/MPF image trailer + my $prePos = $$self{VALUE}{PreviewImageStart} || $$self{PreviewImageStart}; + my $preLen = $$self{VALUE}{PreviewImageLength} || $$self{PreviewImageLength}; + my $tag = 'PreviewImage'; + my $mpImageNum = 0; + my (%image, $lastOne); + for (;;) { + # add to Preview block list if valid and in the trailer + $image{$prePos} = [$tag, $preLen] if $prePos and $preLen and $prePos+$preLen > $pos; + last if $lastOne; # checked all images + # look for MPF images (in the proper order) + ++$mpImageNum; + $prePos = $$self{VALUE}{"MPImageStart ($mpImageNum)"}; + if (defined $prePos) { + $preLen = $$self{VALUE}{"MPImageLength ($mpImageNum)"}; + } else { + $prePos = $$self{VALUE}{'MPImageStart'}; + $preLen = $$self{VALUE}{'MPImageLength'}; + $lastOne = 1; + } + $tag = "MPImage$mpImageNum"; + } + # dump trailer sections in order + $image{$endPos} = [ '', 0 ]; # add terminator "image" + foreach $prePos (sort { $a <=> $b } keys %image) { + if ($pos < $prePos) { + # dump unknown trailer data + $$dirInfo{DirName} = 'Unknown'; + $$dirInfo{DataPos} = $pos; + $$dirInfo{DirLen} = $prePos - $pos; + $self->DumpTrailer($dirInfo); + } + ($tag, $preLen) = @{$image{$prePos}}; + last unless $preLen; + # dump image if verbose (it is htmlDump'd by ExtractImage) + if ($$self{OPTIONS}{Verbose}) { + $$dirInfo{DirName} = $tag; + $$dirInfo{DataPos} = $prePos; + $$dirInfo{DirLen} = $preLen; + $self->DumpTrailer($dirInfo); + } + $pos = $prePos + $preLen; + } +} + +#------------------------------------------------------------------------------ +# Find last element in linked list +# Inputs: 0) element in list +# Returns: Last element in list +sub LastInList($) +{ + my $element = shift; + while ($$element{Next}) { + $element = $$element{Next}; + } + return $element; +} + +#------------------------------------------------------------------------------ +# Print verbose value while writing +# Inputs: 0) ExifTool object ref, 1) heading "eg. '+ IPTC:Keywords', +# 2) value, 3) [optional] extra text after value +sub VerboseValue($$$;$) +{ + return unless $_[0]{OPTIONS}{Verbose} > 1; + my ($self, $str, $val, $xtra) = @_; + my $out = $$self{OPTIONS}{TextOut}; + $xtra or $xtra = ''; + my $maxLen = 81 - length($str) - length($xtra); + $val = $self->Printable($val, $maxLen); + print $out " $str = '${val}'$xtra\n"; +} + +#------------------------------------------------------------------------------ +# Pack Unicode numbers into UTF8 string +# Inputs: 0-N) list of Unicode numbers +# Returns: Packed UTF-8 string +sub PackUTF8(@) +{ + my @out; + while (@_) { + my $ch = pop; + unshift(@out, $ch), next if $ch < 0x80; + unshift(@out, 0x80 | ($ch & 0x3f)); + $ch >>= 6; + unshift(@out, 0xc0 | $ch), next if $ch < 0x20; + unshift(@out, 0x80 | ($ch & 0x3f)); + $ch >>= 6; + unshift(@out, 0xe0 | $ch), next if $ch < 0x10; + unshift(@out, 0x80 | ($ch & 0x3f)); + $ch >>= 6; + unshift(@out, 0xf0 | ($ch & 0x07)); + } + return pack('C*', @out); +} + +#------------------------------------------------------------------------------ +# Unpack numbers from UTF8 string +# Inputs: 0) UTF-8 string +# Returns: List of Unicode numbers (sets $evalWarning on error) +sub UnpackUTF8($) +{ + my (@out, $pos); + pos($_[0]) = $pos = 0; # start at beginning of string + for (;;) { + my ($ch, $newPos, $val, $byte); + if ($_[0] =~ /([\x80-\xff])/g) { + $ch = ord($1); + $newPos = pos($_[0]) - 1; + } else { + $newPos = length $_[0]; + } + # unpack 7-bit characters + my $len = $newPos - $pos; + push @out, unpack("x${pos}C$len",$_[0]) if $len; + last unless defined $ch; + $pos = $newPos + 1; + # minimum lead byte for 2-byte sequence is 0xc2 (overlong sequences + # not allowed), 0xf8-0xfd are restricted by RFC 3629 (no 5 or 6 byte + # sequences), and 0xfe and 0xff are not valid in UTF-8 strings + if ($ch < 0xc2 or $ch >= 0xf8) { + push @out, ord('?'); # invalid UTF-8 + $evalWarning = 'Bad UTF-8'; + next; + } + # decode 2, 3 and 4-byte sequences + my $n = 1; + if ($ch < 0xe0) { + $val = $ch & 0x1f; # 2-byte sequence + } elsif ($ch < 0xf0) { + $val = $ch & 0x0f; # 3-byte sequence + ++$n; + } else { + $val = $ch & 0x07; # 4-byte sequence + $n += 2; + } + unless ($_[0] =~ /\G([\x80-\xbf]{$n})/g) { + pos($_[0]) = $pos; # restore position + push @out, ord('?'); # invalid UTF-8 + $evalWarning = 'Bad UTF-8'; + next; + } + foreach $byte (unpack 'C*', $1) { + $val = ($val << 6) | ($byte & 0x3f); + } + push @out, $val; # save Unicode character value + $pos += $n; # position at end of UTF-8 character + } + return @out; +} + +#------------------------------------------------------------------------------ +# Generate a new, random GUID +# Inputs: <none> +# Returns: GUID string +my $guidCount; +sub NewGUID() +{ + my @tm = localtime time; + $guidCount = 0 unless defined $guidCount and ++$guidCount < 0x100; + return sprintf('%.4d%.2d%.2d%.2d%.2d%.2d%.2X%.4X%.4X%.4X%.4X', + $tm[5]+1900, $tm[4]+1, $tm[3], $tm[2], $tm[1], $tm[0], $guidCount, + $$ & 0xffff, rand(0x10000), rand(0x10000), rand(0x10000)); +} + +#------------------------------------------------------------------------------ +# Make TIFF header for raw data +# Inputs: 0) width, 1) height, 2) num colour components, 3) bits, 4) resolution +# 5) color-map data for palette-color image (8 or 16 bit) +# Returns: TIFF header +# Notes: Multi-byte data must be little-endian +sub MakeTiffHeader($$$$;$$) +{ + my ($w, $h, $cols, $bits, $res, $cmap) = @_; + $res or $res = 72; + my $saveOrder = GetByteOrder(); + SetByteOrder('II'); + if (not $cmap) { + $cmap = ''; + } elsif (length $cmap == 3 * 2**$bits) { + # convert to short + $cmap = pack 'v*', map { $_ | ($_<<8) } unpack 'C*', $cmap; + } elsif (length $cmap != 6 * 2**$bits) { + $cmap = ''; + } + my $cmo = $cmap ? 12 : 0; # offset due to ColorMap IFD entry + my $hdr = + "\x49\x49\x2a\0\x08\0\0\0\x0e\0" . # 0x00 14 menu entries: + "\xfe\x00\x04\0\x01\0\0\0\x00\0\0\0" . # 0x0a SubfileType = 0 + "\x00\x01\x04\0\x01\0\0\0" . Set32u($w) . # 0x16 ImageWidth + "\x01\x01\x04\0\x01\0\0\0" . Set32u($h) . # 0x22 ImageHeight + "\x02\x01\x03\0" . Set32u($cols) . # 0x2e BitsPerSample + Set32u($cols == 1 ? $bits : 0xb6 + $cmo) . + "\x03\x01\x03\0\x01\0\0\0\x01\0\0\0" . # 0x3a Compression = 1 + "\x06\x01\x03\0\x01\0\0\0" . # 0x46 PhotometricInterpretation + Set32u($cmap ? 3 : $cols == 1 ? 1 : 2) . + "\x11\x01\x04\0\x01\0\0\0" . # 0x52 StripOffsets + Set32u(0xcc + $cmo + length($cmap)) . + "\x15\x01\x03\0\x01\0\0\0" . Set32u($cols) . # 0x5e SamplesPerPixel + "\x16\x01\x04\0\x01\0\0\0" . Set32u($h) . # 0x6a RowsPerStrip + "\x17\x01\x04\0\x01\0\0\0" . # 0x76 StripByteCounts + Set32u($w * $h * $cols * int(($bits+7)/8)) . + "\x1a\x01\x05\0\x01\0\0\0" . Set32u(0xbc + $cmo) . # 0x82 XResolution + "\x1b\x01\x05\0\x01\0\0\0" . Set32u(0xc4 + $cmo) . # 0x8e YResolution + "\x1c\x01\x03\0\x01\0\0\0\x01\0\0\0" . # 0x9a PlanarConfiguration = 1 + "\x28\x01\x03\0\x01\0\0\0\x02\0\0\0" . # 0xa6 ResolutionUnit = 2 + ($cmap ? # 0xb2 ColorMap [optional] + "\x40\x01\x03\0" . Set32u(3 * 2**$bits) . "\xd8\0\0\0" : '') . + "\0\0\0\0" . # 0xb2+$cmo (no IFD1) + (Set16u($bits) x 3) . # 0xb6+$cmo BitsPerSample value + Set32u($res) . "\x01\0\0\0" . # 0xbc+$cmo XResolution = 72 + Set32u($res) . "\x01\0\0\0" . # 0xc4+$cmo YResolution = 72 + $cmap; # 0xcc or 0xd8 (cmap and data go here) + SetByteOrder($saveOrder); + return $hdr; +} + +#------------------------------------------------------------------------------ +# Return current time in EXIF format +# Inputs: 0) [optional] ExifTool ref, 1) flag to include timezone (0 to disable, +# undef or 1 to include) +# Returns: time string +# - a consistent value is returned for each processed file +sub TimeNow(;$$) +{ + my ($self, $tzFlag) = @_; + my $timeNow; + ref $self or $tzFlag = $self, $self = { }; + if ($$self{Now}) { + $timeNow = $$self{Now}[0]; + } else { + my $time = time(); + my @tm = localtime $time; + my $tz = TimeZoneString(\@tm, $time); + $timeNow = sprintf("%4d:%.2d:%.2d %.2d:%.2d:%.2d", + $tm[5]+1900, $tm[4]+1, $tm[3], + $tm[2], $tm[1], $tm[0]); + $$self{Now} = [ $timeNow, $tz ]; + } + $timeNow .= $$self{Now}[1] if $tzFlag or not defined $tzFlag; + return $timeNow; +} + +#------------------------------------------------------------------------------ +# Inverse date/time print conversion (reformat to YYYY:mm:dd HH:MM:SS[.ss][+-HH:MM|Z]) +# Inputs: 0) ExifTool object ref, 1) Date/Time string, 2) timezone flag: +# 0 - remove timezone and sub-seconds if they exist +# 1 - add timezone if it doesn't exist +# undef - leave timezone alone +# 3) flag to allow date-only (YYYY, YYYY:mm or YYYY:mm:dd) or time without seconds +# Returns: formatted date/time string (or undef and issues warning on error) +# Notes: currently accepts different separators, but doesn't use DateFormat yet +my $strptimeLib; # strptime library name if available +sub InverseDateTime($$;$$) +{ + my ($self, $val, $tzFlag, $dateOnly) = @_; + my ($rtnVal, $tz); + my $fmt = $$self{OPTIONS}{DateFormat}; + # strip off timezone first if it exists + if (not $fmt and $val =~ s/([-+])(\d{1,2}):?(\d{2})\s*(DST)?$//i) { + $tz = sprintf("$1%.2d:$3", $2); + } elsif (not $fmt and $val =~ s/Z$//i) { + $tz = 'Z'; + } else { + $tz = ''; + # allow special value of 'now' + return $self->TimeNow($tzFlag) if lc($val) eq 'now'; + } + # only convert date if a format was specified and the date is recognizable + if ($fmt) { + unless (defined $strptimeLib) { + if (eval { require POSIX::strptime }) { + $strptimeLib = 'POSIX::strptime'; + } elsif (eval { require Time::Piece }) { + $strptimeLib = 'Time::Piece'; + # (call use_locale() to convert localized date/time, + # only available in Time::Piece 1.32 and later) + eval { Time::Piece->use_locale() }; + } else { + $strptimeLib = ''; + } + } + # handle factional seconds (%f), but only at the end of the string + my $fs = ($fmt =~ s/%f$// and $val =~ s/(\.\d+)\s*$//) ? $1 : ''; + my ($lib, $wrn, @a); +TryLib: for ($lib=$strptimeLib; ; $lib='') { + # handle %s format ourself (not supported in Fedora, see forum15032) + if ($fmt eq '%s') { + $val = ConvertUnixTime($val, 1); + last; + } + if (not $lib) { + last unless $$self{OPTIONS}{StrictDate}; + warn $wrn || "Install POSIX::strptime or Time::Piece for inverse date/time conversions\n"; + return undef; + } elsif ($lib eq 'POSIX::strptime') { + @a = eval { POSIX::strptime($val, $fmt) }; + } else { + # protect against a negative epoch time, it can cause a hard crash in Windows + if ($^O eq 'MSWin32' and $fmt =~ /%s/ and $val =~ /-\d/) { + warn "Can't convert negative epoch time\n"; + return undef; + } + @a = eval { + my $t = Time::Piece->strptime($val, $fmt); + return ($t->sec, $t->min, $t->hour, $t->mday, $t->_mon, $t->_year); + }; + } + if (defined $a[5] and length $a[5]) { + $a[5] += 1900; # add 1900 to year + } else { + $wrn = "Invalid date/time (no year) using $lib\n"; + next; + } + ++$a[4] if defined $a[4] and length $a[4]; # add 1 to month + my $i; + foreach $i (0..4) { + if (not defined $a[$i] or not length $a[$i]) { + if ($i < 2 or $dateOnly) { # (allow missing minutes/seconds) + $a[$i] = ' '; + } else { + $wrn = "Incomplete date/time specification using $lib\n"; + next TryLib; + } + } elsif (length($a[$i]) < 2) { + $a[$i] = "0$a[$i]"; # pad to 2 digits if necessary + } + } + $val = join(':', @a[5,4,3]) . ' ' . join(':', @a[2,1,0]) . $fs; + last; + } + } + if ($val =~ /(\d{4})/g) { # get YYYY + my $yr = $1; + my @a = ($val =~ /\d{1,2}/g); # get mm, dd, HH, and maybe MM, SS + length($_) < 2 and $_ = "0$_" foreach @a; # pad to 2 digits if necessary + if (@a >= 3) { + my $ss = $a[4]; # get SS + push @a, '00' while @a < 5; # add MM, SS if not given + # get sub-seconds if they exist (must be after SS, and have leading ".") + my $fs = (@a > 5 and $val =~ /(\.\d+)\s*$/) ? $1 : ''; + # add/remove timezone if necessary + if ($tzFlag) { + if (not $tz) { + if (eval { require Time::Local }) { + # determine timezone offset for this time + my @args = ($a[4],$a[3],$a[2],$a[1],$a[0]-1,$yr); + my $diff = Time::Local::timegm(@args) - TimeLocal(@args); + $tz = TimeZoneString($diff / 60); + } else { + $tz = 'Z'; # don't know time zone + } + } + } elsif (defined $tzFlag) { + $tz = $fs = ''; # remove timezone and sub-seconds + } + if (defined $ss and $ss < 60) { + $ss = ":$ss"; + } elsif ($dateOnly) { + $ss = ''; + } else { + $ss = ':00'; + } + # construct properly formatted date/time string + if ($a[0] < 1 or $a[0] > 12) { + warn "Month '$a[0]' out of range 1..12\n"; + return undef; + } + if ($a[1] < 1 or $a[1] > 31) { + warn "Day '$a[1]' out of range 1..31\n"; + return undef; + } + $a[2] > 24 and warn("Hour '$a[2]' out of range 0..24\n"), return undef; + $a[3] > 59 and warn("Minutes '$a[3]' out of range 0..59\n"), return undef; + $rtnVal = "$yr:$a[0]:$a[1] $a[2]:$a[3]$ss$fs$tz"; + } elsif ($dateOnly) { + $rtnVal = join ':', $yr, @a; + } + } + $rtnVal or warn "Invalid date/time (use YYYY:mm:dd HH:MM:SS[.ss][+/-HH:MM|Z])\n"; + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Set byte order according to our current preferences +# Inputs: 0) ExifTool object ref, 1) default byte order +# Returns: new byte order ('II' or 'MM') and sets current byte order +# Notes: takes the first of the following that is valid: +# 1) ByteOrder option +# 2) new value for ExifByteOrder +# 3) default byte order passed to this routine +# 4) makenote byte order from last file read +# 5) big endian +sub SetPreferredByteOrder($;$) +{ + my ($self, $default) = @_; + my $byteOrder = $self->Options('ByteOrder') || + $self->GetNewValue('ExifByteOrder') || + $default || $$self{MAKER_NOTE_BYTE_ORDER} || 'MM'; + unless (SetByteOrder($byteOrder)) { + warn "Invalid byte order '${byteOrder}'\n" if $self->Options('Verbose'); + $byteOrder = $$self{MAKER_NOTE_BYTE_ORDER} || 'MM'; + SetByteOrder($byteOrder); + } + return GetByteOrder(); +} + +#------------------------------------------------------------------------------ +# Assemble a continuing fraction into a rational value +# Inputs: 0) numerator, 1) denominator +# 2-N) list of fraction denominators, deepest first +# Returns: numerator, denominator (in list context) +sub AssembleRational($$@) +{ + @_ < 3 and return @_; + my ($num, $denom, $frac) = splice(@_, 0, 3); + return AssembleRational($frac*$num+$denom, $num, @_); +} + +#------------------------------------------------------------------------------ +# Convert a floating point number (or 'inf' or 'undef' or a fraction) into a rational +# Inputs: 0) floating point number, 1) optional maximum value (defaults to 0x7fffffff) +# Returns: numerator, denominator (in list context) +# Notes: +# - the returned rational will be accurate to at least 8 significant figures if possible +# - eg. an input of 3.14159265358979 returns a rational of 104348/33215, +# which equals 3.14159265392142 and is accurate to 10 significant figures +# - the returned rational will be reduced to the lowest common denominator except when +# the input is a fraction in which case the input is returned unchanged +# - these routines were a bit tricky, but fun to write! +sub Rationalize($;$) +{ + my $val = shift; + return (1, 0) if $val eq 'inf'; + return (0, 0) if $val eq 'undef'; + return ($1,$2) if $val =~ m{^([-+]?\d+)/(\d+)$}; # accept fractional values + # Note: Just testing "if $val" doesn't work because '0.0' is true! (ugghh!) + return (0, 1) if $val == 0; + my $sign = $val < 0 ? ($val = -$val, -1) : 1; + my ($num, $denom, @fracs); + my $frac = $val; + my $maxInt = shift || 0x7fffffff; + for (;;) { + my ($n, $d) = AssembleRational(int($frac + 0.5), 1, @fracs); + if ($n > $maxInt or $d > $maxInt) { + last if defined $num; + return ($sign, $maxInt) if $val < 1; + return ($sign * $maxInt, 1); + } + ($num, $denom) = ($n, $d); # save last good values + my $err = ($n/$d-$val) / $val; # get error of this rational + last if abs($err) < 1e-8; # all done if error is small + my $int = int($frac); + unshift @fracs, $int; + last unless $frac -= $int; + $frac = 1 / $frac; + } + return ($num * $sign, $denom); +} + +#------------------------------------------------------------------------------ +# Utility routines to for writing binary data values +# Inputs: 0) value, 1) data ref, 2) offset +# Notes: prototype is (@) so values can be passed from list if desired +sub Set16s(@) +{ + my $val = shift; + $val < 0 and $val += 0x10000; + return Set16u($val, @_); +} +sub Set32s(@) +{ + my $val = shift; + $val < 0 and $val += 0xffffffff, ++$val; + return Set32u($val, @_); +} +sub Set64u(@) +{ + my $val = $_[0]; + my $hi = int($val / 4294967296); + my $lo = Set32u($val - $hi * 4294967296); + $hi = Set32u($hi); + $val = GetByteOrder() eq 'MM' ? $hi . $lo : $lo . $hi; + $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; + return $val; +} +sub Set64s(@) +{ + my $val = shift; + $val < 0 and $val += 4294967296 * 4294967296; # (temporary hack won't really work due to round-off errors) + return Set64u($val, @_); +} +sub SetRational64u(@) { + my ($numer,$denom) = Rationalize($_[0],0xffffffff); + my $val = Set32u($numer) . Set32u($denom); + $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; + return $val; +} +sub SetRational64s(@) { + my ($numer,$denom) = Rationalize($_[0]); + my $val = Set32s($numer) . Set32u($denom); + $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; + return $val; +} +sub SetRational32u(@) { + my ($numer,$denom) = Rationalize($_[0],0xffff); + my $val = Set16u($numer) . Set16u($denom); + $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; + return $val; +} +sub SetRational32s(@) { + my ($numer,$denom) = Rationalize($_[0],0x7fff); + my $val = Set16s($numer) . Set16u($denom); + $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; + return $val; +} +sub SetFixed16u(@) { + my $val = int(shift() * 0x100 + 0.5); + return Set16u($val, @_); +} +sub SetFixed16s(@) { + my $val = shift; + return Set16s(int($val * 0x100 + ($val < 0 ? -0.5 : 0.5)), @_); +} +sub SetFixed32u(@) { + my $val = int(shift() * 0x10000 + 0.5); + return Set32u($val, @_); +} +sub SetFixed32s(@) { + my $val = shift; + return Set32s(int($val * 0x10000 + ($val < 0 ? -0.5 : 0.5)), @_); +} +sub SetFloat(@) { + my $val = SwapBytes(pack('f',$_[0]), 4); + $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; + return $val; +} +sub SetDouble(@) { + # swap 32-bit words (ARM quirk) and bytes if necessary + my $val = SwapBytes(SwapWords(pack('d',$_[0])), 8); + $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; + return $val; +} +#------------------------------------------------------------------------------ +# hash lookups for writing binary data values +my %writeValueProc = ( + int8s => \&Set8s, + int8u => \&Set8u, + int16s => \&Set16s, + int16u => \&Set16u, + int16uRev => \&Set16uRev, + int32s => \&Set32s, + int32u => \&Set32u, + int64s => \&Set64s, + int64u => \&Set64u, + rational32s => \&SetRational32s, + rational32u => \&SetRational32u, + rational64s => \&SetRational64s, + rational64u => \&SetRational64u, + fixed16u => \&SetFixed16u, + fixed16s => \&SetFixed16s, + fixed32u => \&SetFixed32u, + fixed32s => \&SetFixed32s, + float => \&SetFloat, + double => \&SetDouble, + ifd => \&Set32u, +); +# verify that we can write floats on this platform +{ + my %writeTest = ( + float => [ -3.14159, 'c0490fd0' ], + double => [ -3.14159, 'c00921f9f01b866e' ], + ); + my $format; + my $oldOrder = GetByteOrder(); + SetByteOrder('MM'); + foreach $format (keys %writeTest) { + my ($val, $hex) = @{$writeTest{$format}}; + # add floating point entries if we can write them + next if unpack('H*', &{$writeValueProc{$format}}($val)) eq $hex; + delete $writeValueProc{$format}; # we can't write them + } + SetByteOrder($oldOrder); +} + +#------------------------------------------------------------------------------ +# write binary data value (with current byte ordering) +# Inputs: 0) value, 1) format string +# 2) number of values: +# undef = 1 for numerical types, or data length for string/undef types +# -1 = number of space-delimited values in the input string +# 3) optional data reference, 4) value offset (may be negative for bytes from end) +# Returns: packed value (and sets value in data) or undef on error +# Notes: May modify input value to round for integer formats +sub WriteValue($$;$$$$) +{ + my ($val, $format, $count, $dataPt, $offset) = @_; + my $proc = $writeValueProc{$format}; + my $packed; + + if ($proc) { + my @vals = split(' ',$val); + if ($count) { + $count = @vals if $count < 0; + } else { + $count = 1; # assume 1 if count not specified + } + $packed = ''; + while ($count--) { + $val = shift @vals; + return undef unless defined $val; + # validate numerical formats + if ($format =~ /^int/) { + unless (IsInt($val) or IsHex($val)) { + return undef unless IsFloat($val); + # round to nearest integer + $val = int($val + ($val < 0 ? -0.5 : 0.5)); + $_[0] = $val; + } + } elsif (not IsFloat($val)) { + return undef unless $format =~ /^rational/ and ($val eq 'inf' or + $val eq 'undef' or IsRational($val)); + } + $packed .= &$proc($val); + } + } elsif ($format eq 'string' or $format eq 'undef') { + $format eq 'string' and $val .= "\0"; # null-terminate strings + if ($count and $count > 0) { + my $diff = $count - length($val); + if ($diff) { + #warn "wrong string length!\n"; + # adjust length of string to match specified count + if ($diff < 0) { + if ($format eq 'string') { + return undef unless $count; + $val = substr($val, 0, $count - 1) . "\0"; + } else { + $val = substr($val, 0, $count); + } + } else { + $val .= "\0" x $diff; + } + } + } else { + $count = length($val); + } + $dataPt and substr($$dataPt, $offset, $count) = $val; + return $val; + } else { + warn "Sorry, Can't write $format values on this platform\n"; + return undef; + } + $dataPt and substr($$dataPt, $offset, length($packed)) = $packed; + return $packed; +} + +#------------------------------------------------------------------------------ +# Encode bit mask (the inverse of DecodeBits()) +# Inputs: 0) value to encode, 1) Reference to hash for encoding (or undef) +# 2) optional number of bits per word (defaults to 32), 3) total bits +# Returns: bit mask or undef on error (plus error string in list context) +sub EncodeBits($$;$$) +{ + my ($val, $lookup, $bits, $num) = @_; + $bits or $bits = 32; + $num or $num = $bits; + my $words = int(($num + $bits - 1) / $bits); + my @outVal = (0) x $words; + if ($val ne '(none)') { + my @vals = split /\s*,\s*/, $val; + foreach $val (@vals) { + my $bit; + if ($lookup) { + $bit = ReverseLookup($val, $lookup); + # (Note: may get non-numerical $bit values from Unknown() tags) + unless (defined $bit) { + if ($val =~ /\[(\d+)\]/) { # numerical bit specification + $bit = $1; + } else { + # don't return error string unless more than one value + return undef unless @vals > 1 and wantarray; + return (undef, "no match for '${val}'"); + } + } + } else { + $bit = $val; + } + unless (IsInt($bit) and $bit < $num) { + return undef unless wantarray; + return (undef, IsInt($bit) ? 'bit number too high' : 'not an integer'); + } + my $word = int($bit / $bits); + $outVal[$word] |= (1 << ($bit - $word * $bits)); + } + } + return "@outVal"; +} + +#------------------------------------------------------------------------------ +# get current position in output file (or end of file if a scalar reference) +# Inputs: 0) file or scalar reference +# Returns: Current position or -1 on error +sub Tell($) +{ + my $outfile = shift; + if (UNIVERSAL::isa($outfile,'GLOB')) { + return tell($outfile); + } else { + return length($$outfile); + } +} + +#------------------------------------------------------------------------------ +# write to file or memory +# Inputs: 0) file or scalar reference, 1-N) list of stuff to write +# Returns: true on success +sub Write($@) +{ + my $outfile = shift; + if (UNIVERSAL::isa($outfile,'GLOB')) { + return print $outfile @_; + } elsif (ref $outfile eq 'SCALAR') { + $$outfile .= join('', @_); + return 1; + } + return 0; +} + +#------------------------------------------------------------------------------ +# Write trailer buffer to file (applying fixups if necessary) +# Inputs: 0) ExifTool object ref, 1) trailer dirInfo ref, 2) output file ref +# Returns: 1 on success +sub WriteTrailerBuffer($$$) +{ + my ($self, $trailInfo, $outfile) = @_; + if ($$self{DEL_GROUP}{Trailer}) { + $self->VPrint(0, " Deleting trailer ($$trailInfo{Offset} bytes)\n"); + ++$$self{CHANGED}; + return 1; + } + my $pos = Tell($outfile); + my $trailPt = $$trailInfo{OutFile}; + # apply fixup if necessary (AFCP requires this) + if ($$trailInfo{Fixup}) { + if ($pos > 0) { + # shift offsets to final AFCP location and write it out + $$trailInfo{Fixup}{Shift} += $pos; + $$trailInfo{Fixup}->ApplyFixup($trailPt); + } else { + $self->Error("Can't get file position for trailer offset fixup",1); + } + } + return Write($outfile, $$trailPt); +} + +#------------------------------------------------------------------------------ +# Add trailers as a block +# Inputs: 0) ExifTool object ref, 1) [optional] trailer data raf, +# 1 or 2-N) trailer types to add (or none to add all) +# Returns: new trailer ref, or undef +# - increments CHANGED if trailer was added +sub AddNewTrailers($;@) +{ + my ($self, @types) = @_; + my $trailPt; + ref $types[0] and $trailPt = shift @types; + $types[0] or shift @types; # (in case undef data ref is passed) + # add all possible trailers if none specified (currently only CanonVRD) + @types or @types = qw(CanonVRD CanonDR4); + # add trailers as a block (if not done already) + my $type; + foreach $type (@types) { + next unless $$self{NEW_VALUE}{$Image::ExifTool::Extra{$type}}; + next if $$self{"Did$type"}; + my $val = $self->GetNewValue($type) or next; + # DR4 record must be wrapped in VRD trailer package + if ($type eq 'CanonDR4') { + next if $$self{DidCanonVRD}; # (only allow one VRD trailer) + require Image::ExifTool::CanonVRD; + $val = Image::ExifTool::CanonVRD::WrapDR4($val); + $$self{DidCanonVRD} = 1; + } + my $verb = $trailPt ? 'Writing' : 'Adding'; + $self->VPrint(0, " $verb $type as a block\n"); + if ($trailPt) { + $$trailPt .= $val; + } else { + $trailPt = \$val; + } + $$self{"Did$type"} = 1; + ++$$self{CHANGED}; + } + return $trailPt; +} + +#------------------------------------------------------------------------------ +# Write segment, splitting up into multiple segments if necessary +# Inputs: 0) file or scalar reference, 1) segment marker +# 2) segment header, 3) segment data ref, 4) segment type +# Returns: number of segments written, or 0 on error +# Notes: Writes a single empty segment if data is empty +sub WriteMultiSegment($$$$;$) +{ + my ($outfile, $marker, $header, $dataPt, $type) = @_; + $type or $type = ''; + my $len = length($$dataPt); + my $hdr = "\xff" . chr($marker); + my $count = 0; + my $maxLen = $maxSegmentLen - length($header); + $maxLen -= 2 if $type eq 'ICC'; # leave room for segment counters + my $num = int(($len + $maxLen - 1) / $maxLen); # number of segments to write + my $n = 0; + # write data, splitting into multiple segments if necessary + # (each segment gets its own header) + for (;;) { + ++$count; + my $size = $len - $n; + if ($size > $maxLen) { + $size = $maxLen; + # avoid starting an Extended EXIF segment with a valid TIFF header + # (because we would interpret that as a separate EXIF segment) + --$size if $type eq 'EXIF' and $n+$maxLen <= $len-4 and + substr($$dataPt, $n+$maxLen, 4) =~ /^(MM\0\x2a|II\x2a\0)/; + } + my $buff = substr($$dataPt,$n,$size); + $n += $size; + $size += length($header); + if ($type eq 'ICC') { + $buff = pack('CC', $count, $num) . $buff; + $size += 2; + } + # write the new segment with appropriate header + my $segHdr = $hdr . pack('n', $size + 2); + Write($outfile, $segHdr, $header, $buff) or return 0; + last if $n >= $len; + } + return $count; +} + +#------------------------------------------------------------------------------ +# Write XMP segment(s) to JPEG file +# Inputs: 0) ExifTool object ref, 1) outfile ref, 2) XMP data ref, +# 3) extended XMP data ref, 4) 32-char extended XMP GUID (or undef if no extended data) +# Returns: true on success, false on write error +sub WriteMultiXMP($$$$$) +{ + my ($self, $outfile, $dataPt, $extPt, $guid) = @_; + my $success = 1; + + # write main XMP segment + my $size = length($$dataPt) + length($xmpAPP1hdr); + if ($size > $maxXMPLen) { + $self->Error("XMP block too large for JPEG segment! ($size bytes)", 1); + return 1; + } + my $app1hdr = "\xff\xe1" . pack('n', $size + 2); + Write($outfile, $app1hdr, $xmpAPP1hdr, $$dataPt) or $success = 0; + # write extended XMP segment(s) if necessary + if (defined $guid) { + $size = length($$extPt); + my $maxLen = $maxXMPLen - 75; # maximum size without 75-byte header + my $off; + for ($off=0; $off<$size; $off+=$maxLen) { + # header(75) = signature(35) + guid(32) + size(4) + offset(4) + my $len = $size - $off; + $len = $maxLen if $len > $maxLen; + $app1hdr = "\xff\xe1" . pack('n', $len + 75 + 2); + $self->VPrint(0, "Writing extended XMP segment ($len bytes)\n"); + Write($outfile, $app1hdr, $xmpExtAPP1hdr, $guid, pack('N2', $size, $off), + substr($$extPt, $off, $len)) or $success = 0; + } + } + return $success; +} + +#------------------------------------------------------------------------------ +# WriteJPEG : Write JPEG image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid JPEG file, or -1 if +# an output file was specified and a write error occurred +sub WriteJPEG($$) +{ + my ($self, $dirInfo) = @_; + my $outfile = $$dirInfo{OutFile}; + my $raf = $$dirInfo{RAF}; + my ($ch, $s, $length,$err, %doneDir, $isEXV, $creatingEXV); + my $verbose = $$self{OPTIONS}{Verbose}; + my $out = $$self{OPTIONS}{TextOut}; + my $rtnVal = 0; + my ($writeBuffer, $oldOutfile); # used to buffer writing until PreviewImage position is known + + # check to be sure this is a valid JPG or EXV file + unless ($raf->Read($s,2) == 2 and $s eq "\xff\xd8") { + if (defined $s and length $s) { + return 0 unless $s eq "\xff\x01" and $raf->Read($s,5) == 5 and $s eq 'Exiv2'; + } else { + return 0 unless $$self{FILE_TYPE} eq 'EXV'; + $s = 'Exiv2'; + $creatingEXV = 1; + } + Write($outfile,"\xff\x01") or $err = 1; + $isEXV = 1; + } + + delete $$self{PREVIEW_INFO}; # reset preview information + delete $$self{DEL_PREVIEW}; # reset flag to delete preview + + Write($outfile, $s) or $err = 1; + # figure out what segments we need to write for the tags we have set + my $addDirs = $$self{ADD_DIRS}; + my $editDirs = $$self{EDIT_DIRS}; + my $delGroup = $$self{DEL_GROUP}; + my $path = $$self{PATH}; + my $pn = scalar @$path; + + # set input record separator to 0xff (the JPEG marker) to make reading quicker + local $/ = "\xff"; +# +# pre-scan image to determine if any create-able segment already exists +# + my $pos = $raf->Tell(); + my ($marker, @dirOrder, %dirCount); + Prescan: for (;;) { + # read up to next marker (JPEG markers begin with 0xff) + $raf->ReadLine($s) or last; + # JPEG markers can be padded with unlimited 0xff's + for (;;) { + $raf->Read($ch, 1) or last Prescan; + $marker = ord($ch); + last unless $marker == 0xff; + } + my $dirName; + # stop pre-scan at SOS (end of meta information) or EOI (end of image) + if ($marker == 0xda or $marker == 0xd9) { + $dirName = $jpegMarker{$marker}; + push(@dirOrder, $dirName); + $dirCount{$dirName} = 1; + last; + } + # handle SOF markers: SOF0-SOF15, except DHT(0xc4), JPGA(0xc8) and DAC(0xcc) + if (($marker & 0xf0) == 0xc0 and ($marker == 0xc0 or $marker & 0x03)) { + last unless $raf->Seek(7, 1); + # read data for all markers except stand-alone + # markers 0x00, 0x01 and 0xd0-0xd7 (NULL, TEM, RST0-RST7) + } elsif ($marker!=0x00 and $marker!=0x01 and ($marker<0xd0 or $marker>0xd7)) { + # read record length word + last unless $raf->Read($s, 2) == 2; + my $len = unpack('n',$s); # get data length + last unless defined($len) and $len >= 2; + $len -= 2; # subtract size of length word + if (($marker & 0xf0) == 0xe0) { # is this an APP segment? + my $n = $len < 64 ? $len : 64; + $raf->Read($s, $n) == $n or last; + $len -= $n; + # Note: only necessary to recognize APP segments that we can create, + # or delete as a group (and the names below should match @delGroups) + if ($marker == 0xe0) { + $s =~ /^JFIF\0/ and $dirName = 'JFIF'; + $s =~ /^JFXX\0\x10/ and $dirName = 'JFXX'; + $s =~ /^(II|MM).{4}HEAPJPGM/s and $dirName = 'CIFF'; + } elsif ($marker == 0xe1) { + if ($s =~ /^(.{0,4})Exif\0.(.{1,4})/is) { + $dirName = 'IFD0'; + my ($junk, $bytes) = ($1, $2); + # support multi-segment EXIF + if (@dirOrder and $dirOrder[-1] =~ /^(IFD0|ExtendedEXIF)$/ and + not length $junk and $bytes !~ /^(MM\0\x2a|II\x2a\0)/) + { + $dirName = 'ExtendedEXIF'; + } + } + $s =~ /^$xmpAPP1hdr/ and $dirName = 'XMP'; + $s =~ /^$xmpExtAPP1hdr/ and $dirName = 'XMP'; + } elsif ($marker == 0xe2) { + $s =~ /^ICC_PROFILE\0/ and $dirName = 'ICC_Profile'; + $s =~ /^FPXR\0/ and $dirName = 'FlashPix'; + $s =~ /^MPF\0/ and $dirName = 'MPF'; + } elsif ($marker == 0xe3) { + $s =~ /^(Meta|META|Exif)\0\0/ and $dirName = 'Meta'; + } elsif ($marker == 0xe5) { + $s =~ /^RMETA\0/ and $dirName = 'RMETA'; + } elsif ($marker == 0xeb) { + $s =~ /^JP/ and $dirName = 'JUMBF'; + } elsif ($marker == 0xec) { + $s =~ /^Ducky/ and $dirName = 'Ducky'; + } elsif ($marker == 0xed) { + $s =~ /^$psAPP13hdr/ and $dirName = 'Photoshop'; + } elsif ($marker == 0xee) { + $s =~ /^Adobe/ and $dirName = 'Adobe'; + } + # initialize doneDir as a flag that the directory exists + # (unless we are deleting it anyway) + $doneDir{$dirName} = 0 if defined $dirName and not $$delGroup{$dirName}; + } + $raf->Seek($len, 1) or last; + } + $dirName or $dirName = JpegMarkerName($marker); + $dirCount{$dirName} = ($dirCount{$dirName} || 0) + 1; + push @dirOrder, $dirName; + } + unless ($marker and $marker == 0xda) { + $isEXV or $self->Error('Corrupted JPEG image'), return 1; + $marker and $marker != 0xd9 and $self->Error('Corrupted EXV file'), return 1; + } + $raf->Seek($pos, 0) or $self->Error('Seek error'), return 1; +# +# re-write the image +# + my ($combinedSegData, $segPos, $firstSegPos, %extendedXMP); + my (@iccChunk, $iccChunkCount, $iccChunksTotal); + # read through each segment in the JPEG file + Marker: for (;;) { + + # read up to next marker (JPEG markers begin with 0xff) + my $segJunk; + $raf->ReadLine($segJunk) or $segJunk = ''; + # remove the 0xff but write the rest of the junk up to this point + # (this will handle the data after the first 7 bytes of SOF segments) + chomp($segJunk); + Write($outfile, $segJunk) if length $segJunk; + # JPEG markers can be padded with unlimited 0xff's + for (;;) { + if ($raf->Read($ch, 1)) { + $marker = ord($ch); + last unless $marker == 0xff; + } elsif ($creatingEXV) { + # create EXV from scratch + $marker = 0xd9; # EOI + push @dirOrder, 'EOI'; + $dirCount{EOI} = 1; + last; + } else { + $self->Error('Format error'); + return 1; + } + } + # read the segment data + my $segData; + # handle SOF markers: SOF0-SOF15, except DHT(0xc4), JPGA(0xc8) and DAC(0xcc) + if (($marker & 0xf0) == 0xc0 and ($marker == 0xc0 or $marker & 0x03)) { + last unless $raf->Read($segData, 7) == 7; + # read data for all markers except stand-alone + # markers 0x00, 0x01 and 0xd0-0xd7 (NULL, TEM, EOI, RST0-RST7) + } elsif ($marker!=0x00 and $marker!=0x01 and $marker!=0xd9 and + ($marker<0xd0 or $marker>0xd7)) + { + # read record length word + last unless $raf->Read($s, 2) == 2; + my $len = unpack('n',$s); # get data length + last unless defined($len) and $len >= 2; + $segPos = $raf->Tell(); + $len -= 2; # subtract size of length word + last unless $raf->Read($segData, $len) == $len; + } + # initialize variables for this segment + my $hdr = "\xff" . chr($marker); # segment header + my $markerName = JpegMarkerName($marker); + my $dirName = shift @dirOrder; # get directory name +# +# create all segments that must come before this one +# (nothing comes before SOI or after SOS) +# + while ($markerName ne 'SOI') { + if (exists $$addDirs{JFIF} and not defined $doneDir{JFIF}) { + $doneDir{JFIF} = 1; + if (defined $doneDir{Adobe}) { + # JFIF overrides Adobe APP14 colour components, so don't allow this + # (ref https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html) + $self->Warn('Not creating JFIF in JPEG with Adobe APP14'); + } else { + if ($verbose) { + print $out "Creating APP0:\n"; + print $out " Creating JFIF with default values\n"; + } + my $jfif = "\x01\x02\x01\0\x48\0\x48\0\0"; + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::JFIF::Main'); + my %dirInfo = ( + DataPt => \$jfif, + DirStart => 0, + DirLen => length $jfif, + Parent => 'JFIF', + ); + # must temporarily remove JFIF from DEL_GROUP so we can + # delete JFIF and add it back again in a single step + my $delJFIF = $$delGroup{JFIF}; + delete $$delGroup{JFIF}; + $$path[$pn] = 'JFIF'; + my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + $$delGroup{JFIF} = $delJFIF if defined $delJFIF; + if (defined $newData and length $newData) { + my $app0hdr = "\xff\xe0" . pack('n', length($newData) + 7); + Write($outfile,$app0hdr,"JFIF\0",$newData) or $err = 1; + } + } + } + # don't create anything before APP0 or APP1 EXIF (containing IFD0) + last if $markerName eq 'APP0' or $dirCount{IFD0} or $dirCount{ExtendedEXIF}; + # EXIF information must come immediately after APP0 + if (exists $$addDirs{IFD0} and not defined $doneDir{IFD0}) { + $doneDir{IFD0} = 1; + $verbose and print $out "Creating APP1:\n"; + # write new EXIF data + $$self{TIFF_TYPE} = 'APP1'; + my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); + my %dirInfo = ( + DirName => 'IFD0', + Parent => 'APP1', + ); + $$path[$pn] = 'APP1'; + my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr, \&WriteTIFF); + if (defined $buff and length $buff) { + if (length($buff) + length($exifAPP1hdr) > $maxSegmentLen) { + if ($self->Options('NoMultiExif')) { + $self->Error('EXIF is too large for JPEG segment'); + } else { + $self->Warn('Creating multi-segment EXIF',1); + } + } + # switch to buffered output if required + if (($$self{PREVIEW_INFO} or $$self{LeicaTrailer}) and not $oldOutfile) { + $writeBuffer = ''; + $oldOutfile = $outfile; + $outfile = \$writeBuffer; + # account for segment, EXIF and TIFF headers + $$self{PREVIEW_INFO}{Fixup}{Start} += 18 if $$self{PREVIEW_INFO}; + $$self{LeicaTrailer}{Fixup}{Start} += 18 if $$self{LeicaTrailer}; + } + # write as multi-segment + my $n = WriteMultiSegment($outfile, 0xe1, $exifAPP1hdr, \$buff, 'EXIF'); + if (not $n) { + $err = 1; + } elsif ($n > 1 and $oldOutfile) { + # (punt on this because updating the pointers would be a real pain) + $self->Error("Can't write multi-segment EXIF with external pointers"); + } + ++$$self{CHANGED}; + } + } + # APP13 Photoshop segment next + last if $dirCount{Photoshop}; + if (exists $$addDirs{Photoshop} and not defined $doneDir{Photoshop}) { + $doneDir{Photoshop} = 1; + $verbose and print $out "Creating APP13:\n"; + # write new APP13 Photoshop record to memory + my $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main'); + my %dirInfo = ( + Parent => 'APP13', + ); + $$path[$pn] = 'APP13'; + my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $buff and length $buff) { + WriteMultiSegment($outfile, 0xed, $psAPP13hdr, \$buff) or $err = 1; + ++$$self{CHANGED}; + } + } + # then APP1 XMP segment + last if $dirCount{XMP}; + if (exists $$addDirs{XMP} and not defined $doneDir{XMP}) { + $doneDir{XMP} = 1; + $verbose and print $out "Creating APP1:\n"; + # write new XMP data + my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); + my %dirInfo = ( + Parent => 'APP1', + # specify MaxDataLen so XMP is split if required + MaxDataLen => $maxXMPLen - length($xmpAPP1hdr), + ); + $$path[$pn] = 'APP1'; + my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $buff and length $buff) { + WriteMultiXMP($self, $outfile, \$buff, $dirInfo{ExtendedXMP}, + $dirInfo{ExtendedGUID}) or $err = 1; + } + } + # then APP2 ICC_Profile segment + last if $dirCount{ICC_Profile}; + if (exists $$addDirs{ICC_Profile} and not defined $doneDir{ICC_Profile}) { + $doneDir{ICC_Profile} = 1; + next if $$delGroup{ICC_Profile} and $$delGroup{ICC_Profile} != 2; + $verbose and print $out "Creating APP2:\n"; + # write new ICC_Profile data + my $tagTablePtr = GetTagTable('Image::ExifTool::ICC_Profile::Main'); + my %dirInfo = ( + Parent => 'APP2', + ); + $$path[$pn] = 'APP2'; + my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $buff and length $buff) { + WriteMultiSegment($outfile, 0xe2, "ICC_PROFILE\0", \$buff, 'ICC') or $err = 1; + ++$$self{CHANGED}; + } + } + # then APP12 Ducky segment + last if $dirCount{Ducky}; + if (exists $$addDirs{Ducky} and not defined $doneDir{Ducky}) { + $doneDir{Ducky} = 1; + $verbose and print $out "Creating APP12 Ducky:\n"; + # write new Ducky segment data + my $tagTablePtr = GetTagTable('Image::ExifTool::APP12::Ducky'); + my %dirInfo = ( + Parent => 'APP12', + ); + $$path[$pn] = 'APP12'; + my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $buff and length $buff) { + my $size = length($buff) + 5; + if ($size <= $maxSegmentLen) { + # write the new segment with appropriate header + my $app12hdr = "\xff\xec" . pack('n', $size + 2); + Write($outfile, $app12hdr, 'Ducky', $buff) or $err = 1; + } else { + $self->Warn("APP12 Ducky segment too large! ($size bytes)"); + } + } + } + # then APP14 Adobe segment + last if $dirCount{Adobe}; + if (exists $$addDirs{Adobe} and not defined $doneDir{Adobe}) { + $doneDir{Adobe} = 1; + my $buff = $self->GetNewValue('Adobe'); + if ($buff) { + $verbose and print $out "Creating APP14:\n Creating Adobe segment\n"; + my $size = length($buff); + if ($size <= $maxSegmentLen) { + # write the new segment with appropriate header + my $app14hdr = "\xff\xee" . pack('n', $size + 2); + Write($outfile, $app14hdr, $buff) or $err = 1; + ++$$self{CHANGED}; + } else { + $self->Warn("APP14 Adobe segment too large! ($size bytes)"); + } + } + } + # finally, COM segment + last if $dirCount{COM}; + if (exists $$addDirs{COM} and not defined $doneDir{COM}) { + $doneDir{COM} = 1; + next if $$delGroup{File} and $$delGroup{File} != 2; + my $newComment = $self->GetNewValue('Comment'); + if (defined $newComment) { + if ($verbose) { + print $out "Creating COM:\n"; + $self->VerboseValue('+ Comment', $newComment); + } + WriteMultiSegment($outfile, 0xfe, '', \$newComment) or $err = 1; + ++$$self{CHANGED}; + } + } + last; # didn't want to loop anyway + } + $$path[$pn] = $markerName; + # decrement counter for this directory since we are about to process it + --$dirCount{$dirName}; +# +# rewrite existing segments +# + # handle SOF markers: SOF0-SOF15, except DHT(0xc4), JPGA(0xc8) and DAC(0xcc) + if (($marker & 0xf0) == 0xc0 and ($marker == 0xc0 or $marker & 0x03)) { + $verbose and print $out "JPEG $markerName:\n"; + Write($outfile, $hdr, $segData) or $err = 1; + next; + } elsif ($marker == 0xda) { # SOS + pop @$path; + $verbose and print $out "JPEG SOS\n"; + # write SOS segment + $s = pack('n', length($segData) + 2); + Write($outfile, $hdr, $s, $segData) or $err = 1; + my ($buff, $endPos, $trailInfo); + my $delPreview = $$self{DEL_PREVIEW}; + $trailInfo = IdentifyTrailer($raf) unless $$delGroup{Trailer}; + my $nvTrail = $self->GetNewValueHash($Image::ExifTool::Extra{Trailer}); + unless ($oldOutfile or $delPreview or $trailInfo or $$delGroup{Trailer} or $nvTrail) { + # blindly copy the rest of the file + while ($raf->Read($buff, 65536)) { + Write($outfile, $buff) or $err = 1, last; + } + $rtnVal = 1; # success unless we have a file write error + last; # all done + } + # write the rest of the image (as quickly as possible) up to the EOI + my $endedWithFF; + for (;;) { + my $n = $raf->Read($buff, 65536) or last Marker; + if (($endedWithFF and $buff =~ m/^\xd9/sg) or + $buff =~ m/\xff\xd9/sg) + { + $rtnVal = 1; # the JPEG is OK + # write up to the EOI + my $pos = pos($buff); + Write($outfile, substr($buff, 0, $pos)) or $err = 1; + $buff = substr($buff, $pos); + last; + } + unless ($n == 65536) { + $self->Error('JPEG EOI marker not found'); + last Marker; + } + Write($outfile, $buff) or $err = 1; + $endedWithFF = substr($buff, 65535, 1) eq "\xff" ? 1 : 0; + } + # remember position of last data copied + $endPos = $raf->Tell() - length($buff); + # write new trailer if specified + if ($nvTrail) { + # access new value directly to avoid copying a potentially very large data block + if ($$nvTrail{Value} and $$nvTrail{Value}[0]) { # (note: "0" will also delete the trailer) + $self->VPrint(0, ' Writing new trailer'); + Write($outfile, $$nvTrail{Value}[0]) or $err = 1; + ++$$self{CHANGED}; + } elsif ($raf->Seek(0, 2) and $raf->Tell() != $endPos) { + $self->VPrint(0, ' Deleting trailer (', $raf->Tell() - $endPos, ' bytes)'); + ++$$self{CHANGED}; # changed if there was previously a trailer + } + last; # all done + } + # rewrite existing trailers + if ($trailInfo) { + my $tbuf = ''; + $raf->Seek(-length($buff), 1); # seek back to just after EOI + $$trailInfo{OutFile} = \$tbuf; # rewrite the trailer + $$trailInfo{ScanForAFCP} = 1; # scan if necessary + $self->ProcessTrailers($trailInfo) or undef $trailInfo; + } + if (not $oldOutfile) { + # do nothing special + } elsif ($$self{LeicaTrailer}) { + my $trailLen; + if ($trailInfo) { + $trailLen = $$trailInfo{DataPos} - $endPos; + } else { + $raf->Seek(0, 2) or $err = 1; + $trailLen = $raf->Tell() - $endPos; + } + my $fixup = $$self{LeicaTrailer}{Fixup}; + $$self{LeicaTrailer}{TrailPos} = $endPos; + $$self{LeicaTrailer}{TrailLen} = $trailLen; + # get _absolute_ position of new Leica trailer + my $absPos = Tell($oldOutfile) + length($$outfile); + require Image::ExifTool::Panasonic; + my $dat = Image::ExifTool::Panasonic::ProcessLeicaTrailer($self, $absPos); + # allow some junk before Leica trailer (just in case) + my $junk = $$self{LeicaTrailerPos} - $endPos; + # set MakerNote pointer and size (subtract 10 for segment and EXIF headers) + $fixup->SetMarkerPointers($outfile, 'LeicaTrailer', length($$outfile) - 10 + $junk); + # use this fixup to set the size too (sneaky) + my $trailSize = defined($dat) ? length($dat) - $junk : $$self{LeicaTrailer}{Size}; + $$fixup{Start} -= 4; $$fixup{Shift} += 4; + $fixup->SetMarkerPointers($outfile, 'LeicaTrailer', $trailSize) if defined $trailSize; + $$fixup{Start} += 4; $$fixup{Shift} -= 4; + # clean up and write the buffered data + $outfile = $oldOutfile; + undef $oldOutfile; + Write($outfile, $writeBuffer) or $err = 1; + undef $writeBuffer; + if (defined $dat) { + Write($outfile, $dat) or $err = 1; # write new Leica trailer + $delPreview = 1; # delete existing Leica trailer + } + } else { + # locate preview image and fix up preview offsets + my $scanLen = $$self{Make} =~ /^SONY/i ? 65536 : 1024; + if (length($buff) < $scanLen) { # make sure we have enough trailer to scan + my $buf2; + $buff .= $buf2 if $raf->Read($buf2, $scanLen - length($buff)); + } + # get new preview image position, relative to EXIF base + my $newPos = length($$outfile) - 10; # (subtract 10 for segment and EXIF headers) + my $junkLen; + # adjust position if image isn't at the start (eg. Olympus E-1/E-300) + if ($buff =~ /(\xff\xd8\xff.|.\xd8\xff\xdb)(..)/sg) { + my ($jpegHdr, $segLen) = ($1, $2); + $junkLen = pos($buff) - 6; + # Sony previewimage trailer has a 32 byte header + if ($$self{Make} =~ /^SONY/i and $junkLen > 32) { + # with some newer Sony models, the makernotes preview pointer + # points to JPEG at end of EXIF inside MPImage preview (what a pain!) + if ($jpegHdr eq "\xff\xd8\xff\xe1") { # is the first segment EXIF? + $segLen = unpack('n', $segLen); # the EXIF segment length + # Sony PreviewImage starts with last 2 bytes of EXIF segment + # (and first byte is usually "\0", not "\xff", so don't check this) + if (length($buff) > $junkLen + $segLen + 6 and + substr($buff, $junkLen + $segLen + 3, 3) eq "\xd8\xff\xdb") + { + $junkLen += $segLen + 2; + # (note: this will not copy the trailer after PreviewImage, + # which is a 14kB block full of zeros for the A77) + } + } + $junkLen -= 32; + } + $newPos += $junkLen; + } + # fix up the preview offsets to point to the start of the new image + my $previewInfo = $$self{PREVIEW_INFO}; + delete $$self{PREVIEW_INFO}; + my $fixup = $$previewInfo{Fixup}; + $newPos += ($$previewInfo{BaseShift} || 0); + # adjust to absolute file offset if necessary (Samsung STMN) + $newPos += Tell($oldOutfile) + 10 if $$previewInfo{Absolute}; + if ($$previewInfo{Relative}) { + # adjust for our base by looking at how far the pointer got shifted + $newPos -= ($fixup->GetMarkerPointers($outfile, 'PreviewImage') || 0); + } elsif ($$previewInfo{ChangeBase}) { + # Leica S2 uses relative offsets for the preview only (leica sucks) + my $makerOffset = $fixup->GetMarkerPointers($outfile, 'LeicaTrailer'); + $newPos -= $makerOffset if $makerOffset; + } + $fixup->SetMarkerPointers($outfile, 'PreviewImage', $newPos); + # clean up and write the buffered data + $outfile = $oldOutfile; + undef $oldOutfile; + Write($outfile, $writeBuffer) or $err = 1; + undef $writeBuffer; + # write preview image + if ($$previewInfo{Data} ne 'LOAD_PREVIEW') { + # write any junk that existed before the preview image + Write($outfile, substr($buff,0,$junkLen)) or $err = 1 if $junkLen; + # write the saved preview image + Write($outfile, $$previewInfo{Data}) or $err = 1; + delete $$previewInfo{Data}; + # (don't increment CHANGED because we could be rewriting existing preview) + $delPreview = 1; # remove old preview + } + } + # copy over preview image if necessary + unless ($delPreview) { + my $extra; + if ($trailInfo) { + # copy everything up to start of first processed trailer + $extra = $$trailInfo{DataPos} - $endPos; + } else { + # copy everything up to end of file + $raf->Seek(0, 2) or $err = 1; + $extra = $raf->Tell() - $endPos; + } + if ($extra > 0) { + if ($$delGroup{Trailer}) { + $verbose and print $out " Deleting unknown trailer ($extra bytes)\n"; + ++$$self{CHANGED}; + } else { + # copy over unknown trailer + $verbose and print $out " Preserving unknown trailer ($extra bytes)\n"; + $raf->Seek($endPos, 0) or $err = 1; + CopyBlock($raf, $outfile, $extra) or $err = 1; + } + } + } + # write trailer if necessary + if ($trailInfo) { + $self->WriteTrailerBuffer($trailInfo, $outfile) or $err = 1; + undef $trailInfo; + } + last; # all done parsing file + + } elsif ($marker==0xd9 and $isEXV) { + # write EXV EOI (any trailer will be lost) + Write($outfile, "\xff\xd9") or $err = 1; + $rtnVal = 1; + last; + + } elsif ($marker==0x00 or $marker==0x01 or ($marker>=0xd0 and $marker<=0xd7)) { + $verbose and $marker and print $out "JPEG $markerName:\n"; + # handle stand-alone markers 0x00, 0x01 and 0xd0-0xd7 (NULL, TEM, RST0-RST7) + Write($outfile, $hdr) or $err = 1; + next; + } + # + # NOTE: A 'next' statement after this point will cause $$segDataPt + # not to be written if there is an output file, so in this case + # the $$self{CHANGED} flags must be updated + # + my $segDataPt = \$segData; + $length = length($segData); + print $out "JPEG $markerName ($length bytes)\n" if $verbose; + # group delete of APP segments + if ($$delGroup{$dirName}) { + $verbose and print $out " Deleting $dirName segment\n"; + $self->Warn('ICC_Profile deleted. Image colors may be affected') if $dirName eq 'ICC_Profile'; + ++$$self{CHANGED}; + next Marker; + } + my ($segType, $del); + # rewrite this segment only if we are changing a tag which is contained in its + # directory (or deleting '*', in which case we need to identify the segment type) + while (exists $$editDirs{$markerName} or $$delGroup{'*'}) { + if ($marker == 0xe0) { # APP0 (JFIF, CIFF) + if ($$segDataPt =~ /^JFIF\0/) { + $segType = 'JFIF'; + $$delGroup{JFIF} and $del = 1, last; + last unless $$editDirs{JFIF}; + SetByteOrder('MM'); + my $tagTablePtr = GetTagTable('Image::ExifTool::JFIF::Main'); + my %dirInfo = ( + DataPt => $segDataPt, + DataPos => $segPos, + DataLen => $length, + DirStart => 5, # directory starts after identifier + DirLen => $length-5, + Parent => $markerName, + ); + my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $newData and length $newData) { + $$segDataPt = "JFIF\0" . $newData; + } + } elsif ($$segDataPt =~ /^JFXX\0\x10/) { + $segType = 'JFXX'; + $$delGroup{JFIF} and $del = 1; + } elsif ($$segDataPt =~ /^(II|MM).{4}HEAPJPGM/s) { + $segType = 'CIFF'; + $$delGroup{CIFF} and $del = 1, last; + last unless $$editDirs{CIFF}; + my $newData = ''; + my %dirInfo = ( + RAF => new File::RandomAccess($segDataPt), + OutFile => \$newData, + ); + require Image::ExifTool::CanonRaw; + if (Image::ExifTool::CanonRaw::WriteCRW($self, \%dirInfo) > 0) { + if (length $newData) { + $$segDataPt = $newData; + } else { + undef $segDataPt; + $del = 1; # delete this segment + } + } + } + } elsif ($marker == 0xe1) { # APP1 (EXIF, XMP) + # check for EXIF data + if ($$segDataPt =~ /^(.{0,4})Exif\0./is) { + my $hdrLen = length $exifAPP1hdr; + if (length $1) { + $hdrLen += length $1; + $self->Error('Unknown garbage at start of EXIF segment',1); + } elsif ($$segDataPt !~ /^Exif\0/) { + $self->Error('Incorrect EXIF segment identifier',1); + } + $segType = 'EXIF'; + last unless $$editDirs{IFD0}; + # add this data to the combined data if it exists + if (defined $combinedSegData) { + $combinedSegData .= substr($$segDataPt,$hdrLen); + $segDataPt = \$combinedSegData; + $segPos = $firstSegPos; + $length = length $combinedSegData; # update length + } + # peek ahead to see if the next segment is extended EXIF + if ($dirOrder[0] eq 'ExtendedEXIF') { + # initialize combined data if necessary + unless (defined $combinedSegData) { + $combinedSegData = $$segDataPt; + $firstSegPos = $segPos; + $self->Warn('File contains multi-segment EXIF',1); + } + next Marker; # get the next segment to combine + } + $doneDir{IFD0} and $self->Warn('Multiple APP1 EXIF records'); + $doneDir{IFD0} = 1; + # check del groups now so we can change byte order in one step + if ($$delGroup{IFD0} or $$delGroup{EXIF}) { + delete $doneDir{IFD0}; # delete so we will create a new one + $del = 1; + last; + } + # rewrite EXIF as if this were a TIFF file in memory + my %dirInfo = ( + DataPt => $segDataPt, + DataPos => -$hdrLen, # (remember: relative to Base!) + DirStart => $hdrLen, + Base => $segPos + $hdrLen, + Parent => $markerName, + DirName => 'IFD0', + ); + # write new EXIF data to memory + my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); + my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr, \&WriteTIFF); + if (defined $buff) { + undef $$segDataPt; # free the old buffer + $segDataPt = \$buff; + } else { + last Marker unless $self->Options('IgnoreMinorErrors'); + } + # delete segment if IFD contains no entries + length $$segDataPt or $del = 1, last; + if (length($$segDataPt) + length($exifAPP1hdr) > $maxSegmentLen) { + if ($self->Options('NoMultiExif')) { + $self->Error('EXIF is too large for JPEG segment'); + } else { + $self->Warn('Writing multi-segment EXIF',1); + } + } + # switch to buffered output if required + if (($$self{PREVIEW_INFO} or $$self{LeicaTrailer}) and not $oldOutfile) { + $writeBuffer = ''; + $oldOutfile = $outfile; + $outfile = \$writeBuffer; + # must account for segment, EXIF and TIFF headers + $$self{PREVIEW_INFO}{Fixup}{Start} += 18 if $$self{PREVIEW_INFO}; + $$self{LeicaTrailer}{Fixup}{Start} += 18 if $$self{LeicaTrailer}; + } + # write as multi-segment + my $n = WriteMultiSegment($outfile, $marker, $exifAPP1hdr, $segDataPt, 'EXIF'); + if (not $n) { + $err = 1; + } elsif ($n > 1 and $oldOutfile) { + # (punt on this because updating the pointers would be a real pain) + $self->Error("Can't write multi-segment EXIF with external pointers"); + } + undef $combinedSegData; + undef $$segDataPt; + next Marker; + # check for XMP data + } elsif ($$segDataPt =~ /^($xmpAPP1hdr|$xmpExtAPP1hdr)/) { + $segType = 'XMP'; + $$delGroup{XMP} and $del = 1, last; + $doneDir{XMP} = ($doneDir{XMP} || 0) + 1; + last unless $$editDirs{XMP}; + if ($doneDir{XMP} + $dirCount{XMP} > 1) { + # must assemble all XMP segments before writing + my ($guid, $extXMP); + if ($$segDataPt =~ /^$xmpExtAPP1hdr/) { + # save extended XMP data + if (length $$segDataPt < 75) { + $extendedXMP{Error} = 'Truncated data'; + } else { + my ($size, $off) = unpack('x67N2', $$segDataPt); + $guid = substr($$segDataPt, 35, 32); + if ($guid =~ /[^A-Za-z0-9]/) { # (technically, should be uppercase) + $extendedXMP{Error} = 'Invalid GUID'; + } else { + # remember extended data for each GUID + $extXMP = $extendedXMP{$guid}; + if ($extXMP) { + $size == $$extXMP{Size} or $extendedXMP{Error} = 'Inconsistent size'; + } else { + $extXMP = $extendedXMP{$guid} = { }; + } + $$extXMP{Size} = $size; + $$extXMP{$off} = substr($$segDataPt, 75); + } + } + } else { + # save all main XMP segments (should normally be only one) + $extendedXMP{Main} = [] unless $extendedXMP{Main}; + push @{$extendedXMP{Main}}, substr($$segDataPt, length $xmpAPP1hdr); + } + # continue processing only if we have read all the segments + next Marker if $dirCount{XMP}; + # reconstruct an XMP super-segment + $$segDataPt = $xmpAPP1hdr; + my $goodGuid = ''; + foreach (@{$extendedXMP{Main}}) { + # get the HasExtendedXMP GUID if it exists + if (/:HasExtendedXMP\s*(=\s*['"]|>)(\w{32})/) { + # warn of subsequent XMP blocks specifying a different + # HasExtendedXMP (have never seen this) + if ($goodGuid and $goodGuid ne $2) { + $self->WarnOnce('Multiple XMP segments specifying different extended XMP GUID'); + } + $goodGuid = $2; # GUID for the standard extended XMP + } + $$segDataPt .= $_; + } + # GUID of the extended XMP that we want to read + my $readGuid = $$self{OPTIONS}{ExtendedXMP} || 0; + $readGuid = $goodGuid if $readGuid eq '1'; + foreach $guid (sort keys %extendedXMP) { + next unless length $guid == 32; # ignore other (internal) keys + if ($guid ne $readGuid and $readGuid ne '2') { + my $non = $guid eq $goodGuid ? '' : 'non-'; + $self->Warn("Ignored ${non}standard extended XMP (GUID $guid)"); + next; + } + if ($guid ne $goodGuid) { + $self->Warn("Reading non-standard extended XMP (GUID $guid)"); + } + $extXMP = $extendedXMP{$guid}; + next unless ref $extXMP eq 'HASH'; # (just to be safe) + my $size = $$extXMP{Size}; + my (@offsets, $off); + for ($off=0; $off<$size; ) { + last unless defined $$extXMP{$off}; + push @offsets, $off; + $off += length $$extXMP{$off}; + } + if ($off == $size) { + # add all XMP to super-segment + $$segDataPt .= $$extXMP{$_} foreach @offsets; + } else { + $self->Error("Incomplete extended XMP (GUID $guid)", 1); + } + } + $self->Error("$extendedXMP{Error} in extended XMP", 1) if $extendedXMP{Error}; + } + my $start = length $xmpAPP1hdr; + my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); + my %dirInfo = ( + DataPt => $segDataPt, + DirStart => $start, + Parent => $markerName, + # limit XMP size and create extended XMP if necessary + MaxDataLen => $maxXMPLen - length($xmpAPP1hdr), + ); + my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $newData) { + undef %extendedXMP; + if (length $newData) { + # write multi-segment XMP (XMP plus extended XMP if necessary) + WriteMultiXMP($self, $outfile, \$newData, $dirInfo{ExtendedXMP}, + $dirInfo{ExtendedGUID}) or $err = 1; + undef $$segDataPt; # free the old buffer + next Marker; + } else { + $$segDataPt = ''; # delete the XMP + } + } else { + $verbose and print $out " [XMP rewritten with no changes]\n"; + if ($doneDir{XMP} > 1) { + # re-write original multi-segment XMP + my ($dat, $guid, $extXMP, $off); + foreach $dat (@{$extendedXMP{Main}}) { # main XMP + next unless length $dat; + $s = pack('n', length($xmpAPP1hdr) + length($dat) + 2); + Write($outfile, $hdr, $s, $xmpAPP1hdr, $dat) or $err = 1; + } + foreach $guid (sort keys %extendedXMP) { # extended XMP + next unless length $guid == 32; + $extXMP = $extendedXMP{$guid}; + next unless ref $extXMP eq 'HASH'; + my $size = $$extXMP{Size} or next; + for ($off=0; defined $$extXMP{$off}; $off += length $$extXMP{$off}) { + $s = pack('n', length($xmpExtAPP1hdr) + length($$extXMP{$off}) + 42); + Write($outfile, $hdr, $s, $xmpExtAPP1hdr, $guid, + pack('N2', $size, $off), $$extXMP{$off}) or $err = 1; + } + } + undef $$segDataPt; # free the old buffer + undef %extendedXMP; + next Marker; + } + # continue on to re-write original single-segment XMP + } + $del = 1 unless length $$segDataPt; + } elsif ($$segDataPt =~ /^http/ or $$segDataPt =~ /<exif:/) { + $self->Warn('Ignored APP1 XMP segment with non-standard header', 1); + } + } elsif ($marker == 0xe2) { # APP2 (ICC Profile, FPXR, MPF) + if ($$segDataPt =~ /^ICC_PROFILE\0/ and $length >= 14) { + $segType = 'ICC_Profile'; + $$delGroup{ICC_Profile} and $del = 1, last; + # must concatenate blocks of profile + my $chunkNum = Get8u($segDataPt, 12); + my $chunksTot = Get8u($segDataPt, 13); + if (defined $iccChunksTotal) { + # abort parsing ICC_Profile if the total chunk count is inconsistent + if ($chunksTot != $iccChunksTotal and defined $iccChunkCount) { + # an error because the accumulated profile data will be lost + $self->Error('Inconsistent ICC_Profile chunk count', 1); + undef $iccChunkCount; # abort ICC_Profile parsing + undef $chunkNum; # avoid 2nd warning below + ++$$self{CHANGED}; # we are deleting the bad chunks before this one + } + } else { + $iccChunkCount = 0; + $iccChunksTotal = $chunksTot; + $self->Warn('ICC_Profile chunk count is zero') if !$chunksTot; + } + if (defined $iccChunkCount) { + # save this chunk + if (defined $iccChunk[$chunkNum]) { + $self->Warn("Duplicate ICC_Profile chunk number $chunkNum"); + $iccChunk[$chunkNum] .= substr($$segDataPt, 14); + } else { + $iccChunk[$chunkNum] = substr($$segDataPt, 14); + } + # continue accumulating chunks unless we have all of them + next Marker unless ++$iccChunkCount >= $iccChunksTotal; + undef $iccChunkCount; # prevent reprocessing + $doneDir{ICC_Profile} = 1; + # combine the ICC_Profile chunks + my $icc_profile = ''; + defined $_ and $icc_profile .= $_ foreach @iccChunk; + undef @iccChunk; # free memory + $segDataPt = \$icc_profile; + $length = length $icc_profile; + my $tagTablePtr = GetTagTable('Image::ExifTool::ICC_Profile::Main'); + my %dirInfo = ( + DataPt => $segDataPt, + DataPos => $segPos + 14, + DataLen => $length, + DirStart => 0, + DirLen => $length, + Parent => $markerName, + ); + my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $newData) { + undef $$segDataPt; # free the old buffer + $segDataPt = \$newData; + } + length $$segDataPt or $del = 1, last; + # write as ICC multi-segment + WriteMultiSegment($outfile, $marker, "ICC_PROFILE\0", $segDataPt, 'ICC') or $err = 1; + undef $$segDataPt; + next Marker; + } elsif (defined $chunkNum) { + $self->WarnOnce('Invalid or extraneous ICC_Profile chunk(s)'); + # fall through to preserve this extra profile... + } + } elsif ($$segDataPt =~ /^FPXR\0/) { + $segType = 'FPXR'; + $$delGroup{FlashPix} and $del = 1; + } elsif ($$segDataPt =~ /^MPF\0/) { + $segType = 'MPF'; + $$delGroup{MPF} and $del = 1; + } + } elsif ($marker == 0xe3) { # APP3 (Kodak Meta) + if ($$segDataPt =~ /^(Meta|META|Exif)\0\0/) { + $segType = 'Kodak Meta'; + $$delGroup{Meta} and $del = 1, last; + $doneDir{Meta} and $self->Warn('Multiple APP3 Meta segments'); + $doneDir{Meta} = 1; + last unless $$editDirs{Meta}; + # rewrite Meta IFD as if this were a TIFF file in memory + my %dirInfo = ( + DataPt => $segDataPt, + DataPos => -6, # (remember: relative to Base!) + DirStart => 6, + Base => $segPos + 6, + Parent => $markerName, + DirName => 'Meta', + ); + # write new data to memory + my $tagTablePtr = GetTagTable('Image::ExifTool::Kodak::Meta'); + my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr, \&WriteTIFF); + if (defined $buff) { + # update segment with new data + $$segDataPt = substr($$segDataPt,0,6) . $buff; + } else { + last Marker unless $self->Options('IgnoreMinorErrors'); + } + # delete segment if IFD contains no entries + $del = 1 unless length($$segDataPt) > 6; + } + } elsif ($marker == 0xe5) { # APP5 (Ricoh RMETA) + if ($$segDataPt =~ /^RMETA\0/) { + $segType = 'Ricoh RMETA'; + $$delGroup{RMETA} and $del = 1; + } + } elsif ($marker == 0xeb) { # APP10 (JUMBF) + if ($$segDataPt =~ /^JP/) { + $segType = 'JUMBF'; + $$delGroup{JUMBF} and $del = 1; + } + } elsif ($marker == 0xec) { # APP12 (Ducky) + if ($$segDataPt =~ /^Ducky/) { + $segType = 'Ducky'; + $$delGroup{Ducky} and $del = 1, last; + $doneDir{Ducky} and $self->Warn('Multiple APP12 Ducky segments'); + $doneDir{Ducky} = 1; + last unless $$editDirs{Ducky}; + my $tagTablePtr = GetTagTable('Image::ExifTool::APP12::Ducky'); + my %dirInfo = ( + DataPt => $segDataPt, + DataPos => $segPos, + DataLen => $length, + DirStart => 5, # directory starts after identifier + DirLen => $length-5, + Parent => $markerName, + ); + my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $newData) { + undef $$segDataPt; # free the old buffer + # add header to new segment unless empty + $newData = 'Ducky' . $newData if length $newData; + $segDataPt = \$newData; + } + $del = 1 unless length $$segDataPt; + } + } elsif ($marker == 0xed) { # APP13 (Photoshop) + if ($$segDataPt =~ /^$psAPP13hdr/) { + $segType = 'Photoshop'; + # add this data to the combined data if it exists + if (defined $combinedSegData) { + $combinedSegData .= substr($$segDataPt,length($psAPP13hdr)); + $segDataPt = \$combinedSegData; + $length = length $combinedSegData; # update length + } + # peek ahead to see if the next segment is photoshop data too + if ($dirOrder[0] eq 'Photoshop') { + # initialize combined data if necessary + $combinedSegData = $$segDataPt unless defined $combinedSegData; + next Marker; # get the next segment to combine + } + if ($doneDir{Photoshop}) { + $self->Warn('Multiple Photoshop records'); + # only rewrite the first Photoshop segment when deleting this group + # (to remove multiples when deleting and adding back in one step) + $$delGroup{Photoshop} and $del = 1, last; + } + $doneDir{Photoshop} = 1; + # process APP13 Photoshop record + my $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main'); + my %dirInfo = ( + DataPt => $segDataPt, + DataPos => $segPos, + DataLen => $length, + DirStart => 14, # directory starts after identifier + DirLen => $length-14, + Parent => $markerName, + ); + my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); + if (defined $newData) { + undef $$segDataPt; # free the old buffer + $segDataPt = \$newData; + } + length $$segDataPt or $del = 1, last; + # write as multi-segment + WriteMultiSegment($outfile, $marker, $psAPP13hdr, $segDataPt) or $err = 1; + undef $combinedSegData; + undef $$segDataPt; + next Marker; + } + } elsif ($marker == 0xee) { # APP14 (Adobe) + if ($$segDataPt =~ /^Adobe/) { + $segType = 'Adobe'; + # delete it and replace it later if editing + if ($$delGroup{Adobe} or $$editDirs{Adobe}) { + $del = 1; + undef $doneDir{Adobe}; # so we can add it back again above + } + } + } elsif ($marker == 0xfe) { # COM (JPEG comment) + my $newComment; + unless ($doneDir{COM}) { + $doneDir{COM} = 1; + unless ($$delGroup{File} and $$delGroup{File} != 2) { + my $tagInfo = $Image::ExifTool::Extra{Comment}; + my $nvHash = $self->GetNewValueHash($tagInfo); + my $val = $segData; + $val =~ s/\0+$//; # allow for stupid software that adds NULL terminator + if ($self->IsOverwriting($nvHash, $val) or $$delGroup{File}) { + $newComment = $self->GetNewValue($nvHash); + } else { + delete $$editDirs{COM}; # we aren't editing COM after all + last; + } + } + } + $self->VerboseValue('- Comment', $$segDataPt); + if (defined $newComment) { + # write out the comments + $self->VerboseValue('+ Comment', $newComment); + WriteMultiSegment($outfile, 0xfe, '', \$newComment) or $err = 1; + } else { + $verbose and print $out " Deleting COM segment\n"; + } + ++$$self{CHANGED}; # increment the changed flag + undef $segDataPt; # don't write existing comment + } + last; # didn't want to loop anyway + } + + # delete necessary segments (including unknown segments if deleting all) + if ($del or ($$delGroup{'*'} and not $segType and $marker>=0xe0 and $marker<=0xef)) { + $segType = 'unknown' unless $segType; + $verbose and print $out " Deleting $markerName $segType segment\n"; + ++$$self{CHANGED}; + next Marker; + } + # write out this segment if $segDataPt is still defined + if (defined $segDataPt and defined $$segDataPt) { + # write the data for this record (the data could have been + # modified, so recalculate the length word) + my $size = length($$segDataPt); + if ($size > $maxSegmentLen) { + $segType or $segType = 'Unknown'; + $self->Error("$segType $markerName segment too large! ($size bytes)"); + $err = 1; + } else { + $s = pack('n', length($$segDataPt) + 2); + Write($outfile, $hdr, $s, $$segDataPt) or $err = 1; + } + undef $$segDataPt; # free the buffer + undef $segDataPt; + } + } + # make sure the ICC_Profile was complete + $self->Error('Incomplete ICC_Profile record', 1) if defined $iccChunkCount; + pop @$path if @$path > $pn; + # if oldOutfile is still set, there was an error copying the JPEG + $oldOutfile and return 0; + if ($rtnVal) { + # add any new trailers we are creating + my $trailPt = $self->AddNewTrailers(); + Write($outfile, $$trailPt) or $err = 1 if $trailPt; + } + # set return value to -1 if we only had a write error + $rtnVal = -1 if $rtnVal and $err; + if ($creatingEXV and $rtnVal > 0 and not $$self{CHANGED}) { + $self->Error('Nothing written'); + $rtnVal = -1; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Validate an image for writing +# Inputs: 0) ExifTool object reference, 1) raw value reference +# Returns: error string or undef on success +sub CheckImage($$) +{ + my ($self, $valPtr) = @_; + if (length($$valPtr) and $$valPtr!~/^\xff\xd8/ and not + $self->Options('IgnoreMinorErrors')) + { + return '[Minor] Not a valid image'; + } + return undef; +} + +#------------------------------------------------------------------------------ +# check a value for validity +# Inputs: 0) value reference, 1) format string, 2) optional count +# Returns: error string, or undef on success +# Notes: May modify value (if a count is specified for a string, it is null-padded +# to the specified length, and floating point values are rounded to integer if required) +sub CheckValue($$;$) +{ + my ($valPtr, $format, $count) = @_; + my (@vals, $val, $n); + + if ($format eq 'string' or $format eq 'undef') { + return undef unless $count and $count > 0; + my $len = length($$valPtr); + if ($format eq 'string') { + $len >= $count and return 'String too long'; + } else { + $len > $count and return 'Data too long'; + } + if ($len < $count) { + $$valPtr .= "\0" x ($count - $len); + } + return undef; + } + if ($count and $count != 1) { + @vals = split(' ',$$valPtr); + $count < 0 and ($count = @vals or return undef); + } else { + $count = 1; + @vals = ( $$valPtr ); + } + if (@vals != $count) { + my $str = @vals > $count ? 'Too many' : 'Not enough'; + return "$str values specified ($count required)"; + } + for ($n=0; $n<$count; ++$n) { + $val = shift @vals; + if ($format =~ /^int/) { + # make sure the value is integer + unless (IsInt($val)) { + if (IsHex($val)) { + $val = $$valPtr = hex($val); + } else { + # round single floating point values to the nearest integer + return 'Not an integer' unless IsFloat($val) and $count == 1; + $val = $$valPtr = int($val + ($val < 0 ? -0.5 : 0.5)); + } + } + my $rng = $intRange{$format} or return "Bad int format: $format"; + return "Value below $format minimum" if $val < $$rng[0]; + # (allow 0xfeedfeed code as value for 16-bit pointers) + return "Value above $format maximum" if $val > $$rng[1] and $val != 0xfeedfeed; + } elsif ($format =~ /^rational/ or $format eq 'float' or $format eq 'double') { + # make sure the value is a valid floating point number + unless (IsFloat($val)) { + # allow 'inf', 'undef' and fractional rational values + if ($format =~ /^rational/) { + next if $val eq 'inf' or $val eq 'undef'; + if ($val =~ m{^([-+]?\d+)/(\d+)$}) { + next unless $1 < 0 and $format =~ /u$/; + return 'Must be an unsigned rational'; + } + } + return 'Not a floating point number'; + } + if ($format =~ /^rational\d+u$/ and $val < 0) { + return 'Must be a positive number'; + } + } + } + return undef; # success! +} + +#------------------------------------------------------------------------------ +# check new value for binary data block +# Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref +# Returns: error string or undef (and may modify value) on success +sub CheckBinaryData($$$) +{ + my ($self, $tagInfo, $valPtr) = @_; + my $format = $$tagInfo{Format}; + unless ($format) { + my $table = $$tagInfo{Table}; + if ($table and $$table{FORMAT}) { + $format = $$table{FORMAT}; + } else { + # use default 'int8u' unless specified + $format = 'int8u'; + } + } + my $count; + if ($format =~ /(.*)\[(.*)\]/) { + $format = $1; + $count = $2; + # can't evaluate $count now because we don't know $size yet + undef $count if $count =~ /\$size/; + } + return CheckValue($valPtr, $format, $count); +} + +#------------------------------------------------------------------------------ +# Rename a file (with patch for Windows Unicode file names, and other problem) +# Inputs: 0) ExifTool ref, 1) old name, 2) new name +# Returns: true on success +sub Rename($$$) +{ + my ($self, $old, $new) = @_; + my ($result, $try, $winUni); + + if ($self->EncodeFileName($old)) { + $self->EncodeFileName($new, 1); + $winUni = 1; + } elsif ($self->EncodeFileName($new)) { + $old = $_[1]; + $self->EncodeFileName($old, 1); + $winUni = 1; + } + for (;;) { + if ($winUni) { + $result = eval { Win32API::File::MoveFileExW($old, $new, + Win32API::File::MOVEFILE_REPLACE_EXISTING() | + Win32API::File::MOVEFILE_COPY_ALLOWED()) }; + } else { + $result = rename($old, $new); + } + last if $result or $^O ne 'MSWin32'; + # keep trying for up to 0.5 seconds + # (patch for Windows denial-of-service susceptibility) + $try = ($try || 1) + 1; + last if $try > 50; + select(undef,undef,undef,0.01); # sleep for 0.01 sec + } + return $result; +} + +#------------------------------------------------------------------------------ +# Delete a file (with patch for Windows Unicode file names) +# Inputs: 0) ExifTool ref, 1-N) names of files to delete +# Returns: number of files deleted +sub Unlink($@) +{ + my $self = shift; + my $result = 0; + while (@_) { + my $file = shift; + if ($self->EncodeFileName($file)) { + ++$result if eval { Win32API::File::DeleteFileW($file) }; + } else { + ++$result if unlink $file; + } + } + return $result; +} + +#------------------------------------------------------------------------------ +# Set file times (Unix seconds since the epoch) +# Inputs: 0) ExifTool ref, 1) file name or ref, 2) access time, 3) modification time, +# 4) inode change or creation time (or undef for any time to avoid setting) +# 5) flag to suppress warning +# Returns: 1 on success, 0 on error +my $k32SetFileTime; +sub SetFileTime($$;$$$$) +{ + my ($self, $file, $atime, $mtime, $ctime, $noWarn) = @_; + my $saveFile; + local *FH; + + # open file by name if necessary + unless (ref $file) { + # (file will be automatically closed when *FH goes out of scope) + unless ($self->Open(\*FH, $file, '+<')) { + my $success; + if (defined $atime or defined $mtime) { + my ($a, $m, $c) = $self->GetFileTime($file); + $atime = $a unless defined $atime; + $mtime = $m unless defined $mtime; + $success = eval { utime($atime, $mtime, $file) } if defined $atime and defined $mtime; + } + $self->Warn('Error opening file for update') unless $success; + return $success; + } + $saveFile = $file; + $file = \*FH; + } + # on Windows, try to work around incorrect file times when daylight saving time is in effect + if ($^O eq 'MSWin32') { + if (not eval { require Win32::API }) { + $self->WarnOnce('Install Win32::API for proper handling of Windows file times'); + } elsif (not eval { require Win32API::File }) { + $self->WarnOnce('Install Win32API::File for proper handling of Windows file times'); + } else { + # get Win32 handle, needed for SetFileTime + my $win32Handle = eval { Win32API::File::GetOsFHandle($file) }; + unless ($win32Handle) { + $self->Warn('Win32API::File::GetOsFHandle returned invalid handle'); + return 0; + } + # convert Unix seconds to FILETIME structs + my $time; + foreach $time ($atime, $mtime, $ctime) { + # set to NULL if not defined (i.e. do not change) + defined $time or $time = 0, next; + # convert to 100 ns intervals since 0:00 UTC Jan 1, 1601 + # (89 leap years between 1601 and 1970) + my $wt = ($time + (((1970-1601)*365+89)*24*3600)) * 1e7; + my $hi = int($wt / 4294967296); + $time = pack 'LL', int($wt - $hi * 4294967296), $hi; # pack FILETIME struct + } + unless ($k32SetFileTime) { + return 0 if defined $k32SetFileTime; + $k32SetFileTime = new Win32::API('KERNEL32', 'SetFileTime', 'NPPP', 'I'); + unless ($k32SetFileTime) { + $self->Warn('Error calling Win32::API::SetFileTime'); + $k32SetFileTime = 0; + return 0; + } + } + unless ($k32SetFileTime->Call($win32Handle, $ctime, $atime, $mtime)) { + $self->Warn('Win32::API::SetFileTime returned ' . Win32::GetLastError()); + return 0; + } + return 1; + } + } + # other OS (or Windows fallback) + if (defined $atime and defined $mtime) { + my $success; + local $SIG{'__WARN__'} = \&SetWarning; # (this may not be necessary) + for (;;) { + undef $evalWarning; + # (this may fail on the first try if futimes is not implemented) + $success = eval { utime($atime, $mtime, $file) }; + last if $success or not defined $saveFile; + close $file; + $file = $saveFile; + undef $saveFile; + } + unless ($noWarn) { + if ($@ or $evalWarning) { + $self->Warn(CleanWarning($@ || $evalWarning)); + } elsif (not $success) { + $self->Warn('Error setting file time'); + } + } + return $success; + } + return 1; # (nothing to do) +} + +#------------------------------------------------------------------------------ +# Add data to hash checksum +# Inputs: 0) ExifTool ref, 1) RAF ref, 2) data size (or undef to read to end of file), +# 3) data name (or undef for no warnings or messages), 4) flag for no verbose message +# Returns: number of bytes read and hashed +sub ImageDataHash($$$;$$) +{ + my ($self, $raf, $size, $type, $noMsg) = @_; + my $hash = $$self{ImageDataHash} or return; + my ($bytesRead, $n) = (0, 65536); + my $buff; + for (;;) { + if (defined $size) { + last unless $size; + $n = $size > 65536 ? 65536 : $size; + $size -= $n; + } + unless ($raf->Read($buff, $n)) { + $self->Warn("Error reading $type data") if $type and defined $size; + last; + } + $hash->add($buff); + $bytesRead += length $buff; + } + if ($$self{OPTIONS}{Verbose} and $bytesRead and $type and not $noMsg) { + $self->VPrint(0, "$$self{INDENT}(ImageDataHash: $bytesRead bytes of $type data)\n"); + } + return $bytesRead; +} + +#------------------------------------------------------------------------------ +# Copy data block from RAF to output file in max 64kB chunks +# Inputs: 0) RAF ref, 1) outfile ref, 2) block size +# Returns: 1 on success, 0 on read error, undef on write error +sub CopyBlock($$$) +{ + my ($raf, $outfile, $size) = @_; + my $buff; + for (;;) { + last unless $size > 0; + my $n = $size > 65536 ? 65536 : $size; + $raf->Read($buff, $n) == $n or return 0; + Write($outfile, $buff) or return undef; + $size -= $n; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Copy image data from one file to another +# Inputs: 0) ExifTool object reference +# 1) reference to list of image data [ position, size, pad bytes ] +# 2) output file ref +# Returns: true on success +sub CopyImageData($$$) +{ + my ($self, $imageDataBlocks, $outfile) = @_; + my $raf = $$self{RAF}; + my ($dataBlock, $err); + my $num = @$imageDataBlocks; + $self->VPrint(0, " Copying $num image data blocks\n") if $num; + foreach $dataBlock (@$imageDataBlocks) { + my ($pos, $size, $pad) = @$dataBlock; + $raf->Seek($pos, 0) or $err = 'read', last; + my $result = CopyBlock($raf, $outfile, $size); + $result or $err = defined $result ? 'read' : 'writ'; + # pad if necessary + Write($outfile, "\0" x $pad) or $err = 'writ' if $pad; + last if $err; + } + if ($err) { + $self->Error("Error ${err}ing image data"); + return 0; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Write to binary data block +# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref +# Returns: Binary data block or undefined on error +sub WriteBinaryData($$$) +{ + my ($self, $dirInfo, $tagTablePtr) = @_; + $self or return 1; # allow dummy access to autoload this package + + # get default format ('int8u' unless specified) + my $dataPt = $$dirInfo{DataPt} or return undef; + my $dataLen = length $$dataPt; + my $defaultFormat = $$tagTablePtr{FORMAT} || 'int8u'; + my $increment = FormatSize($defaultFormat); + unless ($increment) { + warn "Unknown format $defaultFormat\n"; + return undef; + } + # extract data members first if necessary + my @varOffsets; + if ($$tagTablePtr{DATAMEMBER}) { + $$dirInfo{DataMember} = $$tagTablePtr{DATAMEMBER}; + $$dirInfo{VarFormatData} = \@varOffsets; + $self->ProcessBinaryData($dirInfo, $tagTablePtr); + delete $$dirInfo{DataMember}; + delete $$dirInfo{VarFormatData}; + } + my $dirStart = $$dirInfo{DirStart} || 0; + my $dirLen = $$dirInfo{DirLen}; + $dirLen = $dataLen - $dirStart if not defined $dirLen or $dirLen > $dataLen - $dirStart; + my $newData = substr($$dataPt, $dirStart, $dirLen) or return undef; + my $dirName = $$dirInfo{DirName}; + my $varSize = 0; + my @varInfo = @varOffsets; + my $tagInfo; + $dataPt = \$newData; + foreach $tagInfo (sort { $$a{TagID} <=> $$b{TagID} } $self->GetNewTagInfoList($tagTablePtr)) { + my $tagID = $$tagInfo{TagID}; + # evaluate conditional tags now if necessary + if (ref $$tagTablePtr{$tagID} eq 'ARRAY' or $$tagInfo{Condition}) { + my $writeInfo = $self->GetTagInfo($tagTablePtr, $tagID); + next unless $writeInfo and $writeInfo eq $tagInfo; + } + # add offsets for variable-sized tags if necessary + while (@varInfo and $varInfo[0][0] < $tagID) { + $varSize = $varInfo[0][1]; # get accumulated variable size + shift @varInfo; + } + my $count = 1; + my $format = $$tagInfo{Format}; + my $entry = int($tagID) * $increment + $varSize; # relative offset of this entry + if ($format) { + if ($format =~ /(.*)\[(.*)\]/) { + $format = $1; + $count = $2; + my $size = $dirLen; # used in eval + # evaluate count to allow count to be based on previous values + #### eval Format size ($size, $self) - NOTE: %val not supported for writing + $count = eval $count; + $@ and warn($@), next; + } elsif ($format eq 'string') { + # string with no specified count runs to end of block + $count = ($dirLen > $entry) ? $dirLen - $entry : 0; + } + } else { + $format = $defaultFormat; + } + # read/write using variable format if changed in Hook + $format = $varInfo[0][2] if @varInfo and $varInfo[0][0] == $tagID; + my $val = ReadValue($dataPt, $entry, $format, $count, $dirLen-$entry); + next unless defined $val; + my $nvHash = $self->GetNewValueHash($tagInfo, $$self{CUR_WRITE_GROUP}); + next unless $self->IsOverwriting($nvHash, $val) > 0; + my $newVal = $self->GetNewValue($nvHash); + next unless defined $newVal; # can't delete from a binary table + # update DataMember with new value if necessary + $$self{$$tagInfo{DataMember}} = $newVal if $$tagInfo{DataMember}; + # only write masked bits if specified + my $mask = $$tagInfo{Mask}; + $newVal = (($newVal << $$tagInfo{BitShift}) & $mask) | ($val & ~$mask) if $mask; + # set the size + if ($$tagInfo{DataTag} and not $$tagInfo{IsOffset}) { + warn 'Internal error' unless $newVal == 0xfeedfeed; + my $data = $self->GetNewValue($$tagInfo{DataTag}); + $newVal = length($data) if defined $data; + my $format = $$tagInfo{Format} || $$tagTablePtr{FORMAT} || 'int32u'; + if ($format =~ /^int16/ and $newVal > 0xffff) { + $self->Error("$$tagInfo{DataTag} is too large (64 kB max. for this file)"); + } + } + my $rtnVal = WriteValue($newVal, $format, $count, $dataPt, $entry); + if (defined $rtnVal) { + $self->VerboseValue("- $dirName:$$tagInfo{Name}", $val); + $self->VerboseValue("+ $dirName:$$tagInfo{Name}", $newVal); + ++$$self{CHANGED}; + } + } + # add necessary fixups for any offsets + if ($$tagTablePtr{IS_OFFSET} and $$dirInfo{Fixup}) { + $varSize = 0; + @varInfo = @varOffsets; + my $fixup = $$dirInfo{Fixup}; + my $tagID; + foreach $tagID (@{$$tagTablePtr{IS_OFFSET}}) { + $tagInfo = $self->GetTagInfo($tagTablePtr, $tagID) or next; + while (@varInfo and $varInfo[0][0] < $tagID) { + $varSize = $varInfo[0][1]; + shift @varInfo; + } + my $entry = $tagID * $increment + $varSize; # (no offset to dirStart for new dir data) + next unless $entry <= $dirLen - 4; + # (Ricoh has 16-bit preview image offsets, so can't just assume int32u) + my $format = $$tagInfo{Format} || $$tagTablePtr{FORMAT} || 'int32u'; + my $offset = ReadValue($dataPt, $entry, $format, 1, $dirLen-$entry); + # ignore if offset is zero (eg. Ricoh DNG uses this to indicate no preview) + next unless $offset; + $fixup->AddFixup($entry, $$tagInfo{DataTag}, $format); + # handle the preview image now if this is a JPEG file + next unless $$self{FILE_TYPE} eq 'JPEG' and $$tagInfo{DataTag} and + $$tagInfo{DataTag} eq 'PreviewImage' and defined $$tagInfo{OffsetPair}; + # NOTE: here we assume there are no var-sized tags between the + # OffsetPair tags. If this ever becomes possible we must recalculate + # $varSize for the OffsetPair tag here! + $entry = $$tagInfo{OffsetPair} * $increment + $varSize; + my $size = ReadValue($dataPt, $entry, $format, 1, $dirLen-$entry); + my $previewInfo = $$self{PREVIEW_INFO}; + $previewInfo or $previewInfo = $$self{PREVIEW_INFO} = { + Fixup => new Image::ExifTool::Fixup, + }; + # set flag indicating we are using short pointers + $$previewInfo{IsShort} = 1 unless $format eq 'int32u'; + $$previewInfo{Absolute} = 1 if $$tagInfo{IsOffset} and $$tagInfo{IsOffset} eq '3'; + # get the value of the Composite::PreviewImage tag + $$previewInfo{Data} = $self->GetNewValue(GetCompositeTagInfo('PreviewImage')); + unless (defined $$previewInfo{Data}) { + if ($offset >= 0 and $offset + $size <= $$dirInfo{DataLen}) { + $$previewInfo{Data} = substr(${$$dirInfo{DataPt}},$offset,$size); + } else { + $$previewInfo{Data} = 'LOAD_PREVIEW'; # flag to load preview later + } + } + } + } + # write any necessary SubDirectories + if ($$tagTablePtr{IS_SUBDIR}) { + $varSize = 0; + @varInfo = @varOffsets; + my $tagID; + foreach $tagID (@{$$tagTablePtr{IS_SUBDIR}}) { + my $tagInfo = $self->GetTagInfo($tagTablePtr, $tagID); + next unless defined $tagInfo; + while (@varInfo and $varInfo[0][0] < $tagID) { + $varSize = $varInfo[0][1]; + shift @varInfo; + } + my $entry = int($tagID) * $increment + $varSize; + last if $entry >= $dirLen; + # get value for Condition if necessary + unless ($tagInfo) { + my $more = $dirLen - $entry; + $more = 128 if $more > 128; + my $v = substr($newData, $entry, $more); + $tagInfo = $self->GetTagInfo($tagTablePtr, $tagID, \$v); + next unless $tagInfo; + } + my $subdir = $$tagInfo{SubDirectory} or next; + my $start = $$subdir{Start}; + my $len; + if (not $start) { + $start = $entry; + $len = $dirLen - $start; + } elsif ($start =~ /\$/) { + my $count = 1; + my $format = $$tagInfo{Format} || $defaultFormat; + $format =~ /(.*)\[(.*)\]/ and ($format, $count) = ($1, $2); + my $val = ReadValue($dataPt, $entry, $format, $count, $dirLen - $entry); + # ignore directories with a zero offset (ie. missing Nikon ShotInfo entries) + next unless $val; + my $dirStart = 0; + #### eval Start ($val, $dirStart) + $start = eval($start); + next if $start < $dirStart or $start > $dataLen; + $len = $$subdir{DirLen}; + $len = $dataLen - $start unless $len and $len <= $dataLen - $start; + } + my %subdirInfo = ( + DataPt => \$newData, + DirStart => $start, + DirLen => $len, + TagInfo => $tagInfo, + ); + my $dat = $self->WriteDirectory(\%subdirInfo, GetTagTable($$subdir{TagTable})); + substr($newData, $start, $len) = $dat if defined $dat and length $dat; + } + } + return $newData; +} + +#------------------------------------------------------------------------------ +# Write TIFF as a directory +# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref +# Returns: New directory data or undefined on error +sub WriteTIFF($$$) +{ + my ($self, $dirInfo, $tagTablePtr) = @_; + $self or return 1; # allow dummy access + my $buff = ''; + $$dirInfo{OutFile} = \$buff; + return $buff if $self->ProcessTIFF($dirInfo, $tagTablePtr) > 0; + return undef; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::Writer.pl - ExifTool routines for writing meta information + +=head1 SYNOPSIS + +These routines are autoloaded by Image::ExifTool when required. + +=head1 DESCRIPTION + +This module contains ExifTool write routines and other infrequently +used routines. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/XMP.pm b/ExifTool/lib/Image/ExifTool/XMP.pm new file mode 100644 index 0000000..dfcff1f --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/XMP.pm @@ -0,0 +1,4476 @@ +#------------------------------------------------------------------------------ +# File: XMP.pm +# +# Description: Read XMP meta information +# +# Revisions: 11/25/2003 - P. Harvey Created +# 10/28/2004 - P. Harvey Major overhaul to conform with XMP spec +# 02/27/2005 - P. Harvey Also read UTF-16 and UTF-32 XMP +# 08/30/2005 - P. Harvey Split tag tables into separate namespaces +# 10/24/2005 - P. Harvey Added ability to parse .XMP files +# 08/25/2006 - P. Harvey Added ability to handle blank nodes +# 08/22/2007 - P. Harvey Added ability to handle alternate language tags +# 09/26/2008 - P. Harvey Added Iptc4xmpExt tags (version 1.0 rev 2) +# +# References: 1) http://www.adobe.com/products/xmp/pdfs/xmpspec.pdf +# 2) http://www.w3.org/TR/rdf-syntax-grammar/ (20040210) +# 3) http://www.portfoliofaq.com/pfaq/v7mappings.htm +# 4) http://www.iptc.org/IPTC4XMP/ +# 5) http://creativecommons.org/technology/xmp +# --> changed to http://wiki.creativecommons.org/Companion_File_metadata_specification (2007/12/21) +# 6) http://www.optimasc.com/products/fileid/xmp-extensions.pdf +# 7) Lou Salkind private communication +# 8) http://partners.adobe.com/public/developer/en/xmp/sdk/XMPspecification.pdf +# 9) http://www.w3.org/TR/SVG11/ +# 10) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf (Oct 2008) +# 11) http://www.extensis.com/en/support/kb_article.jsp?articleNumber=6102211 +# 12) http://www.cipa.jp/std/documents/e/DC-010-2012_E.pdf +# 13) http://www.cipa.jp/std/documents/e/DC-010-2017_E.pdf (changed to +# http://www.cipa.jp/std/documents/e/DC-X010-2017.pdf) +# +# Notes: - Property qualifiers are handled as if they were separate +# properties (with no associated namespace). +# +# - Currently, there is no special treatment of the following +# properties which could potentially affect the extracted +# information: xml:base, rdf:parseType (note that parseType +# Literal isn't allowed by the XMP spec). +# +# - The family 2 group names will be set to 'Unknown' for any XMP +# tags not found in the XMP or Exif tag tables. +#------------------------------------------------------------------------------ + +package Image::ExifTool::XMP; + +use strict; +use vars qw($VERSION $AUTOLOAD @ISA @EXPORT_OK %stdXlatNS %nsURI %latConv %longConv + %dateTimeInfo %xmpTableDefaults %specialStruct %sDimensions %sArea %sColorant); +use Image::ExifTool qw(:Utils); +use Image::ExifTool::Exif; +use Image::ExifTool::GPS; +require Exporter; + +$VERSION = '3.60'; +@ISA = qw(Exporter); +@EXPORT_OK = qw(EscapeXML UnescapeXML); + +sub ProcessXMP($$;$); +sub WriteXMP($$;$); +sub CheckXMP($$$;$); +sub ParseXMPElement($$$;$$$$); +sub DecodeBase64($); +sub EncodeBase64($;$); +sub SaveBlankInfo($$$;$); +sub ProcessBlankInfo($$$;$); +sub ValidateXMP($;$); +sub ValidateProperty($$;$); +sub UnescapeChar($$;$); +sub AddFlattenedTags($;$$); +sub FormatXMPDate($); +sub ConvertRational($); +sub ConvertRationalList($); +sub WriteGSpherical($$$); + +# standard path locations for XMP in major file types +my %stdPath = ( + JPEG => 'JPEG-APP1-XMP', + TIFF => 'TIFF-IFD0-XMP', + PSD => 'PSD-XMP', +); + +# lookup for translating to ExifTool namespaces (and family 1 group names) +%stdXlatNS = ( + # shorten ugly namespace prefixes + 'Iptc4xmpCore' => 'iptcCore', + 'Iptc4xmpExt' => 'iptcExt', + 'photomechanic'=> 'photomech', + 'MicrosoftPhoto' => 'microsoft', + 'prismusagerights' => 'pur', + 'GettyImagesGIFT' => 'getty', + 'hdr_metadata' => 'hdr', +); + +# translate ExifTool XMP family 1 group names back to standard XMP namespace prefixes +my %xmpNS = ( + 'iptcCore' => 'Iptc4xmpCore', + 'iptcExt' => 'Iptc4xmpExt', + 'photomech'=> 'photomechanic', + 'microsoft' => 'MicrosoftPhoto', + 'getty' => 'GettyImagesGIFT', + # (prism changed their spec to now use 'pur') + # 'pur' => 'prismusagerights', +); + +# Lookup to translate standard XMP namespace prefixes into URI's. This list +# need not be complete, but it must contain an entry for each namespace prefix +# (NAMESPACE) for writable tags in the XMP tables or in structures that doesn't +# define a URI. Also, the namespace must be defined here for non-standard +# namespace prefixes to be recognized. +%nsURI = ( + aux => 'http://ns.adobe.com/exif/1.0/aux/', + album => 'http://ns.adobe.com/album/1.0/', + cc => 'http://creativecommons.org/ns#', # changed 2007/12/21 - PH + crd => 'http://ns.adobe.com/camera-raw-defaults/1.0/', + crs => 'http://ns.adobe.com/camera-raw-settings/1.0/', + crss => 'http://ns.adobe.com/camera-raw-saved-settings/1.0/', + dc => 'http://purl.org/dc/elements/1.1/', + exif => 'http://ns.adobe.com/exif/1.0/', + exifEX => 'http://cipa.jp/exif/1.0/', + iX => 'http://ns.adobe.com/iX/1.0/', + pdf => 'http://ns.adobe.com/pdf/1.3/', + pdfx => 'http://ns.adobe.com/pdfx/1.3/', + photoshop => 'http://ns.adobe.com/photoshop/1.0/', + rdf => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + rdfs => 'http://www.w3.org/2000/01/rdf-schema#', + stDim => 'http://ns.adobe.com/xap/1.0/sType/Dimensions#', + stEvt => 'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#', + stFnt => 'http://ns.adobe.com/xap/1.0/sType/Font#', + stJob => 'http://ns.adobe.com/xap/1.0/sType/Job#', + stRef => 'http://ns.adobe.com/xap/1.0/sType/ResourceRef#', + stVer => 'http://ns.adobe.com/xap/1.0/sType/Version#', + stMfs => 'http://ns.adobe.com/xap/1.0/sType/ManifestItem#', + stCamera => 'http://ns.adobe.com/photoshop/1.0/camera-profile', + crlcp => 'http://ns.adobe.com/camera-raw-embedded-lens-profile/1.0/', + tiff => 'http://ns.adobe.com/tiff/1.0/', + 'x' => 'adobe:ns:meta/', + xmpG => 'http://ns.adobe.com/xap/1.0/g/', + xmpGImg => 'http://ns.adobe.com/xap/1.0/g/img/', + xmp => 'http://ns.adobe.com/xap/1.0/', + xmpBJ => 'http://ns.adobe.com/xap/1.0/bj/', + xmpDM => 'http://ns.adobe.com/xmp/1.0/DynamicMedia/', + xmpMM => 'http://ns.adobe.com/xap/1.0/mm/', + xmpRights => 'http://ns.adobe.com/xap/1.0/rights/', + xmpNote => 'http://ns.adobe.com/xmp/note/', + xmpTPg => 'http://ns.adobe.com/xap/1.0/t/pg/', + xmpidq => 'http://ns.adobe.com/xmp/Identifier/qual/1.0/', + xmpPLUS => 'http://ns.adobe.com/xap/1.0/PLUS/', + panorama => 'http://ns.adobe.com/photoshop/1.0/panorama-profile', + dex => 'http://ns.optimasc.com/dex/1.0/', + mediapro => 'http://ns.iview-multimedia.com/mediapro/1.0/', + expressionmedia => 'http://ns.microsoft.com/expressionmedia/1.0/', + Iptc4xmpCore => 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/', + Iptc4xmpExt => 'http://iptc.org/std/Iptc4xmpExt/2008-02-29/', + MicrosoftPhoto => 'http://ns.microsoft.com/photo/1.0', + MP1 => 'http://ns.microsoft.com/photo/1.1', #PH (MP1 is fabricated) + MP => 'http://ns.microsoft.com/photo/1.2/', + MPRI => 'http://ns.microsoft.com/photo/1.2/t/RegionInfo#', + MPReg => 'http://ns.microsoft.com/photo/1.2/t/Region#', + lr => 'http://ns.adobe.com/lightroom/1.0/', + DICOM => 'http://ns.adobe.com/DICOM/', + 'drone-dji'=> 'http://www.dji.com/drone-dji/1.0/', + svg => 'http://www.w3.org/2000/svg', + et => 'http://ns.exiftool.org/1.0/', +# +# namespaces defined in XMP2.pl: +# + plus => 'http://ns.useplus.org/ldf/xmp/1.0/', + # (prism recommendations from http://www.prismstandard.org/specifications/3.0/Image_Guide_3.0.htm) + prism => 'http://prismstandard.org/namespaces/basic/2.0/', + prl => 'http://prismstandard.org/namespaces/prl/2.1/', + pur => 'http://prismstandard.org/namespaces/prismusagerights/2.1/', + pmi => 'http://prismstandard.org/namespaces/pmi/2.2/', + prm => 'http://prismstandard.org/namespaces/prm/3.0/', + acdsee => 'http://ns.acdsee.com/iptc/1.0/', + digiKam => 'http://www.digikam.org/ns/1.0/', + swf => 'http://ns.adobe.com/swf/1.0/', + cell => 'http://developer.sonyericsson.com/cell/1.0/', + aas => 'http://ns.apple.com/adjustment-settings/1.0/', + 'mwg-rs' => 'http://www.metadataworkinggroup.com/schemas/regions/', + 'mwg-kw' => 'http://www.metadataworkinggroup.com/schemas/keywords/', + 'mwg-coll' => 'http://www.metadataworkinggroup.com/schemas/collections/', + stArea => 'http://ns.adobe.com/xmp/sType/Area#', + extensis => 'http://ns.extensis.com/extensis/1.0/', + ics => 'http://ns.idimager.com/ics/1.0/', + fpv => 'http://ns.fastpictureviewer.com/fpv/1.0/', + creatorAtom=>'http://ns.adobe.com/creatorAtom/1.0/', + 'apple-fi' => 'http://ns.apple.com/faceinfo/1.0/', + GAudio => 'http://ns.google.com/photos/1.0/audio/', + GImage => 'http://ns.google.com/photos/1.0/image/', + GPano => 'http://ns.google.com/photos/1.0/panorama/', + GSpherical=> 'http://ns.google.com/videos/1.0/spherical/', + GDepth => 'http://ns.google.com/photos/1.0/depthmap/', + GFocus => 'http://ns.google.com/photos/1.0/focus/', + GCamera => 'http://ns.google.com/photos/1.0/camera/', + GCreations=> 'http://ns.google.com/photos/1.0/creations/', + dwc => 'http://rs.tdwg.org/dwc/index.htm', + GettyImagesGIFT => 'http://xmp.gettyimages.com/gift/1.0/', + LImage => 'http://ns.leiainc.com/photos/1.0/image/', + Profile => 'http://ns.google.com/photos/dd/1.0/profile/', + sdc => 'http://ns.nikon.com/sdc/1.0/', + ast => 'http://ns.nikon.com/asteroid/1.0/', + nine => 'http://ns.nikon.com/nine/1.0/', + hdr_metadata => 'http://ns.adobe.com/hdr-metadata/1.0/', + hdrgm => 'http://ns.adobe.com/hdr-gain-map/1.0/', +); + +# build reverse namespace lookup +my %uri2ns = ( 'http://ns.exiftool.ca/1.0/' => 'et' ); # (allow exiftool.ca as well as exiftool.org) +{ + my $ns; + foreach $ns (keys %nsURI) { + $uri2ns{$nsURI{$ns}} = $ns; + } +} + +# conversions for GPS coordinates +%latConv = ( + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)', + ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 2, "N")', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")', +); +%longConv = ( + ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)', + ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 2, "E")', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")', +); +%dateTimeInfo = ( + # NOTE: Do NOT put "Groups" here because Groups hash must not be common! + Writable => 'date', + Shift => 'Time', + Validate => 'ValidateXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', +); + +# this conversion allows alternate language support for designated boolean tags +my %boolConv = ( + PrintConv => { + OTHER => sub { # (inverse conversion is the same) + my $val = shift; + return 'False' if lc $val eq 'false'; + return 'True' if lc $val eq 'true'; + return $val; + }, + True => 'True', + False => 'False', + }, +); + +# XMP namespaces which we don't want to contribute to generated EXIF tag names +# (Note: namespaces with non-standard prefixes aren't currently ignored) +my %ignoreNamespace = ( 'x'=>1, rdf=>1, xmlns=>1, xml=>1, svg=>1, office=>1 ); + +# ExifTool properties that don't generate tag names (et:tagid is historic) +my %ignoreEtProp = ( 'et:desc'=>1, 'et:prt'=>1, 'et:val'=>1 , 'et:id'=>1, 'et:tagid'=>1, + 'et:toolkit'=>1, 'et:table'=>1, 'et:index'=>1 ); + +# XMP properties to ignore (set dynamically via dirInfo IgnoreProp) +my %ignoreProp; + +# these are the attributes that we handle for properties that contain +# sub-properties. Attributes for simple properties are easy, and we +# just copy them over. These are harder since we don't store attributes +# for properties without simple values. (maybe this will change...) +# (special attributes are indicated by a list reference of tag information) +my %recognizedAttrs = ( + 'rdf:about' => [ 'Image::ExifTool::XMP::rdf', 'about', 'About' ], + 'x:xmptk' => [ 'Image::ExifTool::XMP::x', 'xmptk', 'XMPToolkit' ], + 'x:xaptk' => [ 'Image::ExifTool::XMP::x', 'xmptk', 'XMPToolkit' ], + 'rdf:parseType' => 1, + 'rdf:nodeID' => 1, + 'et:toolkit' => 1, + 'rdf:xmlns' => 1, # this is presumably the default namespace, which we currently ignore + 'lastUpdate' => [ 'Image::ExifTool::XMP::XML', 'lastUpdate', 'LastUpdate' ], # found in XML from Sony ILCE-7S MP4 +); + +# special tags in structures below +# NOTE: this lookup is duplicated in TagLookup.pm!! +%specialStruct = ( + STRUCT_NAME => 1, # [optional] name of structure + NAMESPACE => 1, # [mandatory] namespace prefix used for fields of this structure + NOTES => 1, # [optional] notes for documentation about this structure + TYPE => 1, # [optional] rdf:type resource for struct (if used, the StructType flag + # will be set automatically for all derived flattened tags when writing) + GROUPS => 1, # [optional] specifies family group 2 name for the structure +); +# XMP structures (each structure is similar to a tag table so we can +# recurse through them in SetPropertyPath() as if they were tag tables) +# The main differences between structure field information and tagInfo hashes are: +# 1) Field information hashes do not contain Name, Groups or Table entries, and +# 2) The TagID entry is optional, and is used only if the key in the structure hash +# is different from the TagID (currently only true for alternate language fields) +# 3) Field information hashes support a additional "Namespace" property. +my %sResourceRef = ( + STRUCT_NAME => 'ResourceRef', + NAMESPACE => 'stRef', + documentID => { }, + instanceID => { }, + manager => { }, + managerVariant => { }, + manageTo => { }, + manageUI => { }, + renditionClass => { }, + renditionParams => { }, + versionID => { }, + # added Oct 2008 + alternatePaths => { List => 'Seq' }, + filePath => { }, + fromPart => { }, + lastModifyDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + maskMarkers => { PrintConv => { All => 'All', None => 'None' } }, + partMapping => { }, + toPart => { }, + # added May 2010 + originalDocumentID => { }, # (undocumented property written by Adobe InDesign) + # added Aug 2016 (INDD again) + lastURL => { }, + linkForm => { }, + linkCategory => { }, + placedXResolution => { }, + placedYResolution => { }, + placedResolutionUnit => { }, +); +my %sResourceEvent = ( + STRUCT_NAME => 'ResourceEvent', + NAMESPACE => 'stEvt', + action => { }, + instanceID => { }, + parameters => { }, + softwareAgent => { }, + when => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + # added Oct 2008 + changed => { }, +); +my %sJobRef = ( + STRUCT_NAME => 'JobRef', + NAMESPACE => 'stJob', + id => { }, + name => { }, + url => { }, +); +my %sVersion = ( + STRUCT_NAME => 'Version', + NAMESPACE => 'stVer', + comments => { }, + event => { Struct => \%sResourceEvent }, + modifier => { }, + modifyDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + version => { }, +); +my %sThumbnail = ( + STRUCT_NAME => 'Thumbnail', + NAMESPACE => 'xmpGImg', + height => { Writable => 'integer' }, + width => { Writable => 'integer' }, + 'format' => { }, + image => { + Avoid => 1, + Groups => { 2 => 'Preview' }, + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, +); +my %sPageInfo = ( + STRUCT_NAME => 'PageInfo', + NAMESPACE => 'xmpGImg', + PageNumber => { Writable => 'integer', Namespace => 'xmpTPg' }, # override default namespace + height => { Writable => 'integer' }, + width => { Writable => 'integer' }, + 'format' => { }, + image => { + Groups => { 2 => 'Preview' }, + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, +); +#my %sIdentifierScheme = ( +# NAMESPACE => 'xmpidq', +# Scheme => { }, # qualifier for xmp:Identifier only +#); +%sDimensions = ( + STRUCT_NAME => 'Dimensions', + NAMESPACE => 'stDim', + w => { Writable => 'real' }, + h => { Writable => 'real' }, + unit => { }, +); +%sArea = ( + STRUCT_NAME => 'Area', + NAMESPACE => 'stArea', + 'x' => { Writable => 'real' }, + 'y' => { Writable => 'real' }, + w => { Writable => 'real' }, + h => { Writable => 'real' }, + d => { Writable => 'real' }, + unit => { }, +); +%sColorant = ( + STRUCT_NAME => 'Colorant', + NAMESPACE => 'xmpG', + swatchName => { }, + mode => { PrintConv => { CMYK=>'CMYK', RGB=>'RGB', LAB=>'Lab' } }, + # note: do not implement closed choice for "type" because Adobe can't + # get the case right: spec. says "PROCESS" but Indesign writes "Process" + type => { }, + cyan => { Writable => 'real' }, + magenta => { Writable => 'real' }, + yellow => { Writable => 'real' }, + black => { Writable => 'real' }, + red => { Writable => 'integer' }, + green => { Writable => 'integer' }, + blue => { Writable => 'integer' }, + gray => { Writable => 'integer' }, + L => { Writable => 'real' }, + A => { Writable => 'integer' }, + B => { Writable => 'integer' }, + # 'tint' observed in INDD sample - PH + tint => { Writable => 'integer', Notes => 'not part of 2010 XMP specification' }, +); +my %sSwatchGroup = ( + STRUCT_NAME => 'SwatchGroup', + NAMESPACE => 'xmpG', + groupName => { }, + groupType => { Writable => 'integer' }, + Colorants => { + FlatName => 'SwatchColorant', + Struct => \%sColorant, + List => 'Seq', + }, +); +my %sFont = ( + STRUCT_NAME => 'Font', + NAMESPACE => 'stFnt', + fontName => { }, + fontFamily => { }, + fontFace => { }, + fontType => { }, + versionString => { }, + composite => { Writable => 'boolean' }, + fontFileName=> { }, + childFontFiles => { List => 'Seq' }, +); +my %sOECF = ( + STRUCT_NAME => 'OECF', + NAMESPACE => 'exif', + Columns => { Writable => 'integer' }, + Rows => { Writable => 'integer' }, + Names => { List => 'Seq' }, + Values => { List => 'Seq', Writable => 'rational' }, +); +my %sAreaModels = ( + STRUCT_NAME => 'AreaModels', + NAMESPACE => 'crs', + ColorRangeMaskAreaSampleInfo => { FlatName => 'ColorSampleInfo' }, + AreaComponents => { FlatName => 'Components', List => 'Seq' }, +); +my %sCorrRangeMask = ( + STRUCT_NAME => 'CorrRangeMask', + NAMESPACE => 'crs', + NOTES => 'Called CorrectionRangeMask by the spec.', + Version => { }, + Type => { }, + ColorAmount => { Writable => 'real' }, + LumMin => { Writable => 'real' }, + LumMax => { Writable => 'real' }, + LumFeather => { Writable => 'real' }, + DepthMin => { Writable => 'real' }, + DepthMax => { Writable => 'real' }, + DepthFeather=> { Writable => 'real' }, + # new in LR 11.0 + Invert => { Writable => 'boolean' }, + SampleType => { Writable => 'integer' }, + AreaModels => { + List => 'Seq', + Struct => \%sAreaModels, + }, + LumRange => { }, + LuminanceDepthSampleInfo => { }, +); +# new LR2 crs structures (PH) +my %sCorrectionMask; +%sCorrectionMask = ( + STRUCT_NAME => 'CorrectionMask', + NAMESPACE => 'crs', + # disable List behaviour of flattened Gradient/PaintBasedCorrections + # because these are nested in lists and the flattened tags can't + # do justice to this complex structure + What => { List => 0 }, + MaskValue => { Writable => 'real', List => 0, FlatName => 'Value' }, + Radius => { Writable => 'real', List => 0 }, + Flow => { Writable => 'real', List => 0 }, + CenterWeight => { Writable => 'real', List => 0 }, + Dabs => { List => 'Seq' }, + ZeroX => { Writable => 'real', List => 0 }, + ZeroY => { Writable => 'real', List => 0 }, + FullX => { Writable => 'real', List => 0 }, + FullY => { Writable => 'real', List => 0 }, + # new elements used in CircularGradientBasedCorrections CorrectionMasks + # and RetouchAreas Masks + Top => { Writable => 'real', List => 0 }, + Left => { Writable => 'real', List => 0 }, + Bottom => { Writable => 'real', List => 0 }, + Right => { Writable => 'real', List => 0 }, + Angle => { Writable => 'real', List => 0 }, + Midpoint => { Writable => 'real', List => 0 }, + Roundness => { Writable => 'real', List => 0 }, + Feather => { Writable => 'real', List => 0 }, + Flipped => { Writable => 'boolean', List => 0 }, + Version => { Writable => 'integer', List => 0 }, + SizeX => { Writable => 'real', List => 0 }, + SizeY => { Writable => 'real', List => 0 }, + X => { Writable => 'real', List => 0 }, + Y => { Writable => 'real', List => 0 }, + Alpha => { Writable => 'real', List => 0 }, + CenterValue => { Writable => 'real', List => 0 }, + PerimeterValue=>{ Writable => 'real', List => 0 }, + # new in LR 11.0 MaskGroupBasedCorrections + MaskActive => { Writable => 'boolean', List => 0 }, + MaskName => { List => 0 }, + MaskBlendMode=> { Writable => 'integer', List => 0 }, + MaskInverted => { Writable => 'boolean', List => 0 }, + MaskSyncID => { List => 0 }, + MaskVersion => { List => 0 }, + MaskSubType => { List => 0 }, + ReferencePoint => { List => 0 }, + InputDigest => { List => 0 }, + MaskDigest => { List => 0 }, + WholeImageArea => { List => 0 }, + Origin => { List => 0 }, + Masks => { Struct => \%sCorrectionMask, NoSubStruct => 1 }, + CorrectionRangeMask => { + Name => 'CorrRangeMask', + Notes => 'called CorrectionRangeMask by the spec', + FlatName => 'Range', + Struct => \%sCorrRangeMask, + }, +); +my %sCorrection = ( + STRUCT_NAME => 'Correction', + NAMESPACE => 'crs', + What => { List => 0 }, + CorrectionAmount => { FlatName => 'Amount', Writable => 'real', List => 0 }, + CorrectionActive => { FlatName => 'Active', Writable => 'boolean', List => 0 }, + LocalExposure => { FlatName => 'Exposure', Writable => 'real', List => 0 }, + LocalSaturation => { FlatName => 'Saturation', Writable => 'real', List => 0 }, + LocalContrast => { FlatName => 'Contrast', Writable => 'real', List => 0 }, + LocalClarity => { FlatName => 'Clarity', Writable => 'real', List => 0 }, + LocalSharpness => { FlatName => 'Sharpness', Writable => 'real', List => 0 }, + LocalBrightness => { FlatName => 'Brightness', Writable => 'real', List => 0 }, + LocalToningHue => { FlatName => 'ToningHue', Writable => 'real', List => 0 }, + LocalToningSaturation => { FlatName => 'ToningSaturation', Writable => 'real', List => 0 }, + LocalExposure2012 => { FlatName => 'Exposure2012', Writable => 'real', List => 0 }, + LocalContrast2012 => { FlatName => 'Contrast2012', Writable => 'real', List => 0 }, + LocalHighlights2012 => { FlatName => 'Highlights2012', Writable => 'real', List => 0 }, + LocalShadows2012 => { FlatName => 'Shadows2012', Writable => 'real', List => 0 }, + LocalClarity2012 => { FlatName => 'Clarity2012', Writable => 'real', List => 0 }, + LocalLuminanceNoise => { FlatName => 'LuminanceNoise', Writable => 'real', List => 0 }, + LocalMoire => { FlatName => 'Moire', Writable => 'real', List => 0 }, + LocalDefringe => { FlatName => 'Defringe', Writable => 'real', List => 0 }, + LocalTemperature => { FlatName => 'Temperature',Writable => 'real', List => 0 }, + LocalTint => { FlatName => 'Tint', Writable => 'real', List => 0 }, + LocalHue => { FlatName => 'Hue', Writable => 'real', List => 0 }, + LocalWhites2012 => { FlatName => 'Whites2012', Writable => 'real', List => 0 }, + LocalBlacks2012 => { FlatName => 'Blacks2012', Writable => 'real', List => 0 }, + LocalDehaze => { FlatName => 'Dehaze', Writable => 'real', List => 0 }, + LocalTexture => { FlatName => 'Texture', Writable => 'real', List => 0 }, + # new in LR 11.0 + CorrectionRangeMask => { + Name => 'CorrRangeMask', + Notes => 'called CorrectionRangeMask by the spec', + FlatName => 'RangeMask', + Struct => \%sCorrRangeMask, + }, + CorrectionMasks => { + FlatName => 'Mask', + Struct => \%sCorrectionMask, + List => 'Seq', + }, + CorrectionName => { }, + CorrectionSyncID => { }, +); +my %sRetouchArea = ( + STRUCT_NAME => 'RetouchArea', + NAMESPACE => 'crs', + SpotType => { List => 0 }, + SourceState => { List => 0 }, + Method => { List => 0 }, + SourceX => { Writable => 'real', List => 0 }, + OffsetY => { Writable => 'real', List => 0 }, + Opacity => { Writable => 'real', List => 0 }, + Feather => { Writable => 'real', List => 0 }, + Seed => { Writable => 'integer', List => 0 }, + Masks => { + FlatName => 'Mask', + Struct => \%sCorrectionMask, + List => 'Seq', + }, +); +my %sMapInfo = ( + STRUCT_NAME => 'MapInfo', + NAMESPACE => 'crs', + NOTES => q{ + Called RangeMaskMapInfo by the specification, the same as the containing + structure. + }, + RGBMin => { }, + RGBMax => { }, + LabMin => { }, + LabMax => { }, + LumEq => { List => 'Seq' }, +); +my %sRangeMask = ( + STRUCT_NAME => 'RangeMask', + NAMESPACE => 'crs', + NOTES => q{ + This structure is actually called RangeMaskMapInfo, but it only contains one + element which is a RangeMaskMapInfo structure (Yes, really!). So these are + renamed to RangeMask and MapInfo respectively to avoid confusion and + redundancy in the tag names. + }, + RangeMaskMapInfo => { FlatName => 'MapInfo', Struct => \%sMapInfo }, +); + +# main XMP tag table (tag ID's are used for the family 1 group names) +%Image::ExifTool::XMP::Main = ( + GROUPS => { 2 => 'Unknown' }, + PROCESS_PROC => \&ProcessXMP, + WRITE_PROC => \&WriteXMP, + dc => { + Name => 'dc', # (otherwise generated name would be 'Dc') + SubDirectory => { TagTable => 'Image::ExifTool::XMP::dc' }, + }, + xmp => { + Name => 'xmp', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmp' }, + }, + xmpDM => { + Name => 'xmpDM', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpDM' }, + }, + xmpRights => { + Name => 'xmpRights', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpRights' }, + }, + xmpNote => { + Name => 'xmpNote', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpNote' }, + }, + xmpMM => { + Name => 'xmpMM', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpMM' }, + }, + xmpBJ => { + Name => 'xmpBJ', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpBJ' }, + }, + xmpTPg => { + Name => 'xmpTPg', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpTPg' }, + }, + pdf => { + Name => 'pdf', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::pdf' }, + }, + pdfx => { + Name => 'pdfx', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::pdfx' }, + }, + photoshop => { + Name => 'photoshop', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::photoshop' }, + }, + crd => { + Name => 'crd', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::crd' }, + }, + crs => { + Name => 'crs', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::crs' }, + }, + # crss - it would be tedious to add the ability to write this + aux => { + Name => 'aux', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::aux' }, + }, + tiff => { + Name => 'tiff', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::tiff' }, + }, + exif => { + Name => 'exif', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::exif' }, + }, + exifEX => { + Name => 'exifEX', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::exifEX' }, + }, + iptcCore => { + Name => 'iptcCore', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::iptcCore' }, + }, + iptcExt => { + Name => 'iptcExt', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::iptcExt' }, + }, + PixelLive => { + SubDirectory => { TagTable => 'Image::ExifTool::XMP::PixelLive' }, + }, + xmpPLUS => { + Name => 'xmpPLUS', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpPLUS' }, + }, + panorama => { + Name => 'panorama', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::panorama' }, + }, + plus => { + Name => 'plus', + SubDirectory => { TagTable => 'Image::ExifTool::PLUS::XMP' }, + }, + cc => { + Name => 'cc', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::cc' }, + }, + dex => { + Name => 'dex', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::dex' }, + }, + photomech => { + Name => 'photomech', + SubDirectory => { TagTable => 'Image::ExifTool::PhotoMechanic::XMP' }, + }, + mediapro => { + Name => 'mediapro', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::MediaPro' }, + }, + expressionmedia => { + Name => 'expressionmedia', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::ExpressionMedia' }, + }, + microsoft => { + Name => 'microsoft', + SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::XMP' }, + }, + MP => { + Name => 'MP', + SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::MP' }, + }, + MP1 => { + Name => 'MP1', + SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::MP1' }, + }, + lr => { + Name => 'lr', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Lightroom' }, + }, + DICOM => { + Name => 'DICOM', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::DICOM' }, + }, + album => { + Name => 'album', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Album' }, + }, + et => { + Name => 'et', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::ExifTool' }, + }, + prism => { + Name => 'prism', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::prism' }, + }, + prl => { + Name => 'prl', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::prl' }, + }, + pur => { + Name => 'pur', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::pur' }, + }, + pmi => { + Name => 'pmi', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::pmi' }, + }, + prm => { + Name => 'prm', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::prm' }, + }, + rdf => { + Name => 'rdf', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::rdf' }, + }, + 'x' => { + Name => 'x', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::x' }, + }, + acdsee => { + Name => 'acdsee', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::acdsee' }, + }, + digiKam => { + Name => 'digiKam', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::digiKam' }, + }, + swf => { + Name => 'swf', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::swf' }, + }, + cell => { + Name => 'cell', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::cell' }, + }, + aas => { + Name => 'aas', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::aas' }, + }, + 'mwg-rs' => { + Name => 'mwg-rs', + SubDirectory => { TagTable => 'Image::ExifTool::MWG::Regions' }, + }, + 'mwg-kw' => { + Name => 'mwg-kw', + SubDirectory => { TagTable => 'Image::ExifTool::MWG::Keywords' }, + }, + 'mwg-coll' => { + Name => 'mwg-coll', + SubDirectory => { TagTable => 'Image::ExifTool::MWG::Collections' }, + }, + extensis => { + Name => 'extensis', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::extensis' }, + }, + ics => { + Name => 'ics', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::ics' }, + }, + fpv => { + Name => 'fpv', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::fpv' }, + }, + creatorAtom => { + Name => 'creatorAtom', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::creatorAtom' }, + }, + 'apple-fi' => { + Name => 'apple-fi', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::apple_fi' }, + }, + GAudio => { + Name => 'GAudio', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::GAudio' }, + }, + GImage => { + Name => 'GImage', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::GImage' }, + }, + GPano => { + Name => 'GPano', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::GPano' }, + }, + GSpherical => { + Name => 'GSpherical', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::GSpherical' }, + }, + GDepth => { + Name => 'GDepth', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::GDepth' }, + }, + GFocus => { + Name => 'GFocus', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::GFocus' }, + }, + GCamera => { + Name => 'GCamera', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::GCamera' }, + }, + GCreations => { + Name => 'GCreations', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::GCreations' }, + }, + dwc => { + Name => 'dwc', + SubDirectory => { TagTable => 'Image::ExifTool::DarwinCore::Main' }, + }, + getty => { + Name => 'getty', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::GettyImages' }, + }, + 'drone-dji' => { + Name => 'drone-dji', + SubDirectory => { TagTable => 'Image::ExifTool::DJI::XMP' }, + }, + LImage => { + Name => 'LImage', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::LImage' }, + }, + Device => { + Name => 'Device', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::Device' }, + }, + sdc => { + Name => 'sdc', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::sdc' }, + }, + ast => { + Name => 'ast', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::ast' }, + }, + nine => { + Name => 'nine', + SubDirectory => { TagTable => 'Image::ExifTool::Nikon::nine' }, + }, + hdr => { + Name => 'hdr', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::hdr' }, + }, + hdrgm => { + Name => 'hdrgm', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::hdrgm' }, + }, +); + +# hack to allow XML containing Dublin Core metadata to be handled like XMP (eg. EPUB - see ZIP.pm) +%Image::ExifTool::XMP::XML = ( + GROUPS => { 0 => 'XML', 1 => 'XML', 2 => 'Unknown' }, + PROCESS_PROC => \&ProcessXMP, + dc => { + Name => 'dc', + SubDirectory => { TagTable => 'Image::ExifTool::XMP::dc' }, + }, + lastUpdate => { + Groups => { 2 => 'Time' }, + ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)', + PrintConv => '$self->ConvertDateTime($val)', + }, +); + +# +# Tag tables for all XMP namespaces: +# +# Writable - only need to define this for writable tags if not plain text +# (boolean, integer, rational, real, date or lang-alt) +# List - XMP list type (Bag, Seq or Alt, or set to 1 for elements in Struct lists -- +# this is necessary to obtain proper list behaviour when reading/writing) +# +# (Note that family 1 group names are generated from the property namespace, not +# the group1 names below which exist so the groups will appear in the list.) +# +%xmpTableDefaults = ( + WRITE_PROC => \&WriteXMP, + CHECK_PROC => \&CheckXMP, + WRITABLE => 'string', + LANG_INFO => \&GetLangInfo, +); + +# rdf attributes extracted +%Image::ExifTool::XMP::rdf = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-rdf', 2 => 'Document' }, + NAMESPACE => 'rdf', + NOTES => q{ + Most RDF attributes are handled internally, but the "about" attribute is + treated specially to allow it to be set to a specific value if required. + }, + about => { Protected => 1 }, +); + +# x attributes extracted +%Image::ExifTool::XMP::x = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-x', 2 => 'Document' }, + NAMESPACE => 'x', + NOTES => qq{ + The "x" namespace is used for the "xmpmeta" wrapper, and may contain an + "xmptk" attribute that is extracted as the XMPToolkit tag. When writing, + the XMPToolkit tag is generated automatically by ExifTool unless + specifically set to another value. + }, + xmptk => { Name => 'XMPToolkit', Protected => 1 }, +); + +# Dublin Core namespace properties (dc) +%Image::ExifTool::XMP::dc = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-dc', 2 => 'Other' }, + NAMESPACE => 'dc', + TABLE_DESC => 'XMP Dublin Core', + NOTES => 'Dublin Core namespace tags.', + contributor => { Groups => { 2 => 'Author' }, List => 'Bag' }, + coverage => { }, + creator => { Groups => { 2 => 'Author' }, List => 'Seq' }, + date => { Groups => { 2 => 'Time' }, List => 'Seq', %dateTimeInfo }, + description => { Groups => { 2 => 'Image' }, Writable => 'lang-alt' }, + 'format' => { Groups => { 2 => 'Image' } }, + identifier => { Groups => { 2 => 'Image' } }, + language => { List => 'Bag' }, + publisher => { Groups => { 2 => 'Author' }, List => 'Bag' }, + relation => { List => 'Bag' }, + rights => { Groups => { 2 => 'Author' }, Writable => 'lang-alt' }, + source => { Groups => { 2 => 'Author' }, Avoid => 1 }, + subject => { Groups => { 2 => 'Image' }, List => 'Bag' }, + title => { Groups => { 2 => 'Image' }, Writable => 'lang-alt' }, + type => { Groups => { 2 => 'Image' }, List => 'Bag' }, +); + +# XMP namespace properties (xmp, xap) +%Image::ExifTool::XMP::xmp = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-xmp', 2 => 'Image' }, + NAMESPACE => 'xmp', + NOTES => q{ + XMP namespace tags. If the older "xap", "xapBJ", "xapMM" or "xapRights" + namespace prefixes are found, they are translated to the newer "xmp", + "xmpBJ", "xmpMM" and "xmpRights" prefixes for use in family 1 group names. + }, + Advisory => { List => 'Bag', Notes => 'deprecated' }, + BaseURL => { }, + # (date/time tags not as reliable as EXIF) + CreateDate => { Groups => { 2 => 'Time' }, %dateTimeInfo, Priority => 0 }, + CreatorTool => { }, + Identifier => { Avoid => 1, List => 'Bag' }, + Label => { }, + MetadataDate=> { Groups => { 2 => 'Time' }, %dateTimeInfo }, + ModifyDate => { Groups => { 2 => 'Time' }, %dateTimeInfo, Priority => 0 }, + Nickname => { }, + Rating => { Writable => 'real', Notes => 'a value from 0 to 5, or -1 for "rejected"' }, + RatingPercent=>{ Writable => 'real', Avoid => 1, Notes => 'non-standard' }, + Thumbnails => { + FlatName => 'Thumbnail', + Struct => \%sThumbnail, + List => 'Alt', + }, + # the following written by Adobe InDesign, not part of XMP spec: + PageInfo => { + FlatName => 'PageImage', + Struct => \%sPageInfo, + List => 'Seq', + }, + PageInfoImage => { Name => 'PageImage', Flat => 1 }, + Title => { Avoid => 1, Notes => 'non-standard', Writable => 'lang-alt' }, #11 + Author => { Avoid => 1, Notes => 'non-standard', Groups => { 2 => 'Author' } }, #11 + Keywords => { Avoid => 1, Notes => 'non-standard' }, #11 + Description => { Avoid => 1, Notes => 'non-standard', Writable => 'lang-alt' }, #11 + Format => { Avoid => 1, Notes => 'non-standard' }, #11 +); + +# XMP Rights Management namespace properties (xmpRights, xapRights) +%Image::ExifTool::XMP::xmpRights = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-xmpRights', 2 => 'Author' }, + NAMESPACE => 'xmpRights', + NOTES => 'XMP Rights Management namespace tags.', + Certificate => { }, + Marked => { Writable => 'boolean' }, + Owner => { List => 'Bag' }, + UsageTerms => { Writable => 'lang-alt' }, + WebStatement => { }, +); + +# XMP Note namespace properties (xmpNote) +%Image::ExifTool::XMP::xmpNote = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-xmpNote' }, + NAMESPACE => 'xmpNote', + NOTES => 'XMP Note namespace tags.', + HasExtendedXMP => { + Notes => q{ + this tag is protected so it is not writable directly. Instead, it is set + automatically to the GUID of the extended XMP when writing extended XMP to a + JPEG image + }, + Protected => 2, + }, +); + +# XMP xmpMM ManifestItem struct (ref PH, written by Adobe PDF library 8.0) +my %sManifestItem = ( + STRUCT_NAME => 'ManifestItem', + NAMESPACE => 'stMfs', + linkForm => { }, + placedXResolution => { Namespace => 'xmpMM', Writable => 'real' }, + placedYResolution => { Namespace => 'xmpMM', Writable => 'real' }, + placedResolutionUnit=> { Namespace => 'xmpMM' }, + reference => { Struct => \%sResourceRef }, +); + +# the xmpMM Pantry +my %sPantryItem = ( + STRUCT_NAME => 'PantryItem', + NAMESPACE => undef, # stores any top-level XMP tags + NOTES => q{ + This structure must have an InstanceID field, but may also contain any other + XMP properties. + }, + InstanceID => { Namespace => 'xmpMM', List => 0 }, +); + +# XMP Media Management namespace properties (xmpMM, xapMM) +%Image::ExifTool::XMP::xmpMM = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-xmpMM', 2 => 'Other' }, + NAMESPACE => 'xmpMM', + TABLE_DESC => 'XMP Media Management', + NOTES => 'XMP Media Management namespace tags.', + DerivedFrom => { Struct => \%sResourceRef }, + DocumentID => { }, + History => { Struct => \%sResourceEvent, List => 'Seq' }, + # we treat these like list items since History is a list + Ingredients => { Struct => \%sResourceRef, List => 'Bag' }, + InstanceID => { }, #PH (CS3) + ManagedFrom => { Struct => \%sResourceRef }, + Manager => { Groups => { 2 => 'Author' } }, + ManageTo => { Groups => { 2 => 'Author' } }, + ManageUI => { }, + ManagerVariant => { }, + Manifest => { Struct => \%sManifestItem, List => 'Bag' }, + OriginalDocumentID=> { }, + Pantry => { Struct => \%sPantryItem, List => 'Bag' }, + PreservedFileName => { }, # undocumented + RenditionClass => { }, + RenditionParams => { }, + VersionID => { }, + Versions => { Struct => \%sVersion, List => 'Seq' }, + LastURL => { }, # (deprecated) + RenditionOf => { Struct => \%sResourceRef }, # (deprecated) + SaveID => { Writable => 'integer' }, # (deprecated) + subject => { List => 'Seq', Avoid => 1, Notes => 'undocumented' }, +); + +# XMP Basic Job Ticket namespace properties (xmpBJ, xapBJ) +%Image::ExifTool::XMP::xmpBJ = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-xmpBJ', 2 => 'Other' }, + NAMESPACE => 'xmpBJ', + TABLE_DESC => 'XMP Basic Job Ticket', + NOTES => 'XMP Basic Job Ticket namespace tags.', + # Note: JobRef is a List of structures. To accomplish this, we set the XMP + # List=>'Bag', but since SubDirectory is defined, this tag isn't writable + # directly. Then we need to set List=>1 for the members so the Writer logic + # will allow us to add list items. + JobRef => { Struct => \%sJobRef, List => 'Bag' }, +); + +# XMP Paged-Text namespace properties (xmpTPg) +%Image::ExifTool::XMP::xmpTPg = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-xmpTPg', 2 => 'Image' }, + NAMESPACE => 'xmpTPg', + TABLE_DESC => 'XMP Paged-Text', + NOTES => 'XMP Paged-Text namespace tags.', + MaxPageSize => { Struct => \%sDimensions }, + NPages => { Writable => 'integer' }, + Fonts => { + FlatName => '', + Struct => \%sFont, + List => 'Bag', + }, + FontsVersionString => { Name => 'FontVersion', Flat => 1 }, + FontsComposite => { Name => 'FontComposite', Flat => 1 }, + Colorants => { + FlatName => 'Colorant', + Struct => \%sColorant, + List => 'Seq', + }, + PlateNames => { List => 'Seq' }, + # the following found in an AI file: + HasVisibleTransparency => { Writable => 'boolean' }, + HasVisibleOverprint => { Writable => 'boolean' }, + SwatchGroups => { + Struct => \%sSwatchGroup, + List => 'Seq', + }, + SwatchGroupsColorants => { Name => 'SwatchGroupsColorants', Flat => 1 }, + SwatchGroupsGroupName => { Name => 'SwatchGroupName', Flat => 1 }, + SwatchGroupsGroupType => { Name => 'SwatchGroupType', Flat => 1 }, +); + +# PDF namespace properties (pdf) +%Image::ExifTool::XMP::pdf = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-pdf', 2 => 'Image' }, + NAMESPACE => 'pdf', + TABLE_DESC => 'XMP PDF', + NOTES => q{ + Adobe PDF namespace tags. The official XMP specification defines only + Keywords, PDFVersion, Producer and Trapped. The other tags are included + because they have been observed in PDF files, but some are avoided when + writing due to name conflicts with other XMP namespaces. + }, + Author => { Groups => { 2 => 'Author' } }, #PH + ModDate => { Groups => { 2 => 'Time' }, %dateTimeInfo }, #PH + CreationDate=> { Groups => { 2 => 'Time' }, %dateTimeInfo }, #PH + Creator => { Groups => { 2 => 'Author' }, Avoid => 1 }, + Copyright => { Groups => { 2 => 'Author' }, Avoid => 1 }, #PH + Marked => { Avoid => 1, Writable => 'boolean' }, #PH + Subject => { Avoid => 1 }, + Title => { Avoid => 1 }, + Trapped => { #PH + # remove leading '/' from '/True' or '/False' + ValueConv => '$val=~s{^/}{}; $val', + ValueConvInv => '"/$val"', + PrintConv => { True => 'True', False => 'False', Unknown => 'Unknown' }, + }, + Keywords => { Priority => -1 }, # (-1 to get below Priority 0 PDF:Keywords) + PDFVersion => { }, + Producer => { Groups => { 2 => 'Author' } }, +); + +# PDF extension namespace properties (pdfx) +%Image::ExifTool::XMP::pdfx = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-pdfx', 2 => 'Document' }, + NAMESPACE => 'pdfx', + NOTES => q{ + PDF extension tags. This namespace is used to store application-defined PDF + information, so there are no pre-defined tags. User-defined tags must be + created to enable writing of XMP-pdfx information. + }, +); + +# Photoshop namespace properties (photoshop) +%Image::ExifTool::XMP::photoshop = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-photoshop', 2 => 'Image' }, + NAMESPACE => 'photoshop', + TABLE_DESC => 'XMP Photoshop', + NOTES => 'Adobe Photoshop namespace tags.', + AuthorsPosition => { Groups => { 2 => 'Author' } }, + CaptionWriter => { Groups => { 2 => 'Author' } }, + Category => { }, + City => { Groups => { 2 => 'Location' } }, + ColorMode => { + Writable => 'integer', # (as of July 2010 spec, courtesy of yours truly) + PrintConvColumns => 2, + PrintConv => { + 0 => 'Bitmap', + 1 => 'Grayscale', + 2 => 'Indexed', + 3 => 'RGB', + 4 => 'CMYK', + 7 => 'Multichannel', + 8 => 'Duotone', + 9 => 'Lab', + }, + }, + Country => { Groups => { 2 => 'Location' } }, + Credit => { Groups => { 2 => 'Author' } }, + DateCreated => { Groups => { 2 => 'Time' }, %dateTimeInfo }, + DocumentAncestors => { + List => 'Bag', + # Contrary to their own XMP specification, Adobe writes this as a simple Bag + # of strings instead of structures, so comment out the structure definition... + # FlatName => 'Document', + # Struct => { + # STRUCT_NAME => 'Ancestor', + # NAMESPACE => 'photoshop', + # AncestorID => { }, + # }, + }, + Headline => { }, + History => { }, #PH (CS3) + ICCProfile => { Name => 'ICCProfileName' }, #PH + Instructions => { }, + LegacyIPTCDigest=> { }, #PH + SidecarForExtension => { }, #PH (CS3) + Source => { Groups => { 2 => 'Author' } }, + State => { Groups => { 2 => 'Location' } }, + # the XMP spec doesn't show SupplementalCategories as a 'Bag', but + # that's the way Photoshop writes it [fixed in the June 2005 XMP spec]. + # Also, it is incorrectly listed as "SupplementalCategory" in the + # IPTC Standard Photo Metadata docs (2008rev2 and July 2009rev1) - PH + SupplementalCategories => { List => 'Bag' }, + TextLayers => { + FlatName => 'Text', + List => 'Seq', + Struct => { + STRUCT_NAME => 'Layer', + NAMESPACE => 'photoshop', + LayerName => { }, + LayerText => { }, + }, + }, + TransmissionReference => { Notes => 'Now used as a job identifier' }, + Urgency => { + Writable => 'integer', + Notes => 'should be in the range 1-8 to conform with the XMP spec', + PrintConv => { # (same values as IPTC:Urgency) + 0 => '0 (reserved)', # (not standard XMP) + 1 => '1 (most urgent)', + 2 => 2, + 3 => 3, + 4 => 4, + 5 => '5 (normal urgency)', + 6 => 6, + 7 => 7, + 8 => '8 (least urgent)', + 9 => '9 (user-defined priority)', # (not standard XMP) + }, + }, + EmbeddedXMPDigest => { }, #PH (LR5) + CameraProfiles => { #PH (2022-10-11) + List => 'Seq', + Struct => { + NAMESPACE => 'stCamera', + STRUCT_NAME => 'Camera', + Author => { }, + Make => { }, + Model => { }, + UniqueCameraModel => { }, + CameraRawProfile => { Writable => 'boolean' }, + AutoScale => { Writable => 'boolean' }, + Lens => { }, + CameraPrettyName => { }, + LensPrettyName => { }, + ProfileName => { }, + SensorFormatFactor => { Writable => 'real' }, + FocalLength => { Writable => 'real' }, + FocusDistance => { Writable => 'real' }, + ApertureValue => { Writable => 'real' }, + PerspectiveModel => { + Namespace => 'crlcp', + Struct => { + NAMESPACE => 'stCamera', + STRUCT_NAME => 'PerspectiveModel', + Version => { }, + ImageXCenter => { Writable => 'real' }, + ImageYCenter => { Writable => 'real' }, + ScaleFactor => { Writable => 'real' }, + RadialDistortParam1 => { Writable => 'real' }, + RadialDistortParam2 => { Writable => 'real' }, + RadialDistortParam3 => { Writable => 'real' }, + }, + }, + }, + }, +); + +# Photoshop Camera Raw namespace properties (crs) - (ref 8,PH) +%Image::ExifTool::XMP::crs = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-crs', 2 => 'Image' }, + NAMESPACE => 'crs', + TABLE_DESC => 'Photoshop Camera Raw namespace', + NOTES => q{ + Photoshop Camera Raw namespace tags. It is a shame that Adobe pollutes the + metadata space with these incredibly bulky image editing parameters. + }, + AlreadyApplied => { Writable => 'boolean' }, #PH (written by LightRoom beta 4.1) + AutoBrightness => { Writable => 'boolean' }, + AutoContrast => { Writable => 'boolean' }, + AutoExposure => { Writable => 'boolean' }, + AutoShadows => { Writable => 'boolean' }, + BlueHue => { Writable => 'integer' }, + BlueSaturation => { Writable => 'integer' }, + Brightness => { Writable => 'integer' }, + CameraProfile => { }, + ChromaticAberrationB=> { Writable => 'integer' }, + ChromaticAberrationR=> { Writable => 'integer' }, + ColorNoiseReduction => { Writable => 'integer' }, + Contrast => { Writable => 'integer', Avoid => 1 }, + Converter => { }, #PH guess (found in EXIF) + CropTop => { Writable => 'real' }, + CropLeft => { Writable => 'real' }, + CropBottom => { Writable => 'real' }, + CropRight => { Writable => 'real' }, + CropAngle => { Writable => 'real' }, + CropWidth => { Writable => 'real' }, + CropHeight => { Writable => 'real' }, + CropUnits => { + Writable => 'integer', + PrintConv => { + 0 => 'pixels', + 1 => 'inches', + 2 => 'cm', + }, + }, + Exposure => { Writable => 'real' }, + GreenHue => { Writable => 'integer' }, + GreenSaturation => { Writable => 'integer' }, + HasCrop => { Writable => 'boolean' }, + HasSettings => { Writable => 'boolean' }, + LuminanceSmoothing => { Writable => 'integer' }, + MoireFilter => { PrintConv => { Off=>'Off', On=>'On' } }, + RawFileName => { }, + RedHue => { Writable => 'integer' }, + RedSaturation => { Writable => 'integer' }, + Saturation => { Writable => 'integer', Avoid => 1 }, + Shadows => { Writable => 'integer' }, + ShadowTint => { Writable => 'integer' }, + Sharpness => { Writable => 'integer', Avoid => 1 }, + Smoothness => { Writable => 'integer' }, + Temperature => { Writable => 'integer', Name => 'ColorTemperature' }, + Tint => { Writable => 'integer' }, + ToneCurve => { List => 'Seq' }, + ToneCurveName => { + PrintConv => { + Linear => 'Linear', + 'Medium Contrast' => 'Medium Contrast', + 'Strong Contrast' => 'Strong Contrast', + Custom => 'Custom', + }, + }, + Version => { }, + VignetteAmount => { Writable => 'integer' }, + VignetteMidpoint=> { Writable => 'integer' }, + WhiteBalance => { + Avoid => 1, + PrintConv => { + 'As Shot' => 'As Shot', + Auto => 'Auto', + Daylight => 'Daylight', + Cloudy => 'Cloudy', + Shade => 'Shade', + Tungsten => 'Tungsten', + Fluorescent => 'Fluorescent', + Flash => 'Flash', + Custom => 'Custom', + }, + }, + # new tags observed in Adobe Lightroom output - PH + CameraProfileDigest => { }, + Clarity => { Writable => 'integer' }, + ConvertToGrayscale => { Writable => 'boolean' }, + Defringe => { Writable => 'integer' }, + FillLight => { Writable => 'integer' }, + HighlightRecovery => { Writable => 'integer' }, + HueAdjustmentAqua => { Writable => 'integer' }, + HueAdjustmentBlue => { Writable => 'integer' }, + HueAdjustmentGreen => { Writable => 'integer' }, + HueAdjustmentMagenta => { Writable => 'integer' }, + HueAdjustmentOrange => { Writable => 'integer' }, + HueAdjustmentPurple => { Writable => 'integer' }, + HueAdjustmentRed => { Writable => 'integer' }, + HueAdjustmentYellow => { Writable => 'integer' }, + IncrementalTemperature => { Writable => 'integer' }, + IncrementalTint => { Writable => 'integer' }, + LuminanceAdjustmentAqua => { Writable => 'integer' }, + LuminanceAdjustmentBlue => { Writable => 'integer' }, + LuminanceAdjustmentGreen => { Writable => 'integer' }, + LuminanceAdjustmentMagenta => { Writable => 'integer' }, + LuminanceAdjustmentOrange => { Writable => 'integer' }, + LuminanceAdjustmentPurple => { Writable => 'integer' }, + LuminanceAdjustmentRed => { Writable => 'integer' }, + LuminanceAdjustmentYellow => { Writable => 'integer' }, + ParametricDarks => { Writable => 'integer' }, + ParametricHighlights => { Writable => 'integer' }, + ParametricHighlightSplit => { Writable => 'integer' }, + ParametricLights => { Writable => 'integer' }, + ParametricMidtoneSplit => { Writable => 'integer' }, + ParametricShadows => { Writable => 'integer' }, + ParametricShadowSplit => { Writable => 'integer' }, + SaturationAdjustmentAqua => { Writable => 'integer' }, + SaturationAdjustmentBlue => { Writable => 'integer' }, + SaturationAdjustmentGreen => { Writable => 'integer' }, + SaturationAdjustmentMagenta => { Writable => 'integer' }, + SaturationAdjustmentOrange => { Writable => 'integer' }, + SaturationAdjustmentPurple => { Writable => 'integer' }, + SaturationAdjustmentRed => { Writable => 'integer' }, + SaturationAdjustmentYellow => { Writable => 'integer' }, + SharpenDetail => { Writable => 'integer' }, + SharpenEdgeMasking => { Writable => 'integer' }, + SharpenRadius => { Writable => 'real' }, + SplitToningBalance => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' }, + SplitToningHighlightHue => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' }, + SplitToningHighlightSaturation => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' }, + SplitToningShadowHue => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' }, + SplitToningShadowSaturation => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' }, + Vibrance => { Writable => 'integer' }, + # new tags written by LR 1.4 (not sure in what version they first appeared) + GrayMixerRed => { Writable => 'integer' }, + GrayMixerOrange => { Writable => 'integer' }, + GrayMixerYellow => { Writable => 'integer' }, + GrayMixerGreen => { Writable => 'integer' }, + GrayMixerAqua => { Writable => 'integer' }, + GrayMixerBlue => { Writable => 'integer' }, + GrayMixerPurple => { Writable => 'integer' }, + GrayMixerMagenta => { Writable => 'integer' }, + RetouchInfo => { List => 'Seq' }, + RedEyeInfo => { List => 'Seq' }, + # new tags written by LR 2.0 (ref PH) + CropUnit => { # was the XMP documentation wrong with "CropUnits"?? + Writable => 'integer', + PrintConv => { + 0 => 'pixels', + 1 => 'inches', + 2 => 'cm', + # have seen a value of 3 here! - PH + }, + }, + PostCropVignetteAmount => { Writable => 'integer' }, + PostCropVignetteMidpoint => { Writable => 'integer' }, + PostCropVignetteFeather => { Writable => 'integer' }, + PostCropVignetteRoundness => { Writable => 'integer' }, + PostCropVignetteStyle => { + Writable => 'integer', + PrintConv => { #forum14011 + 1 => 'Highlight Priority', + 2 => 'Color Priority', + 3 => 'Paint Overlay', + }, + }, + # disable List behaviour of flattened Gradient/PaintBasedCorrections + # because these are nested in lists and the flattened tags can't + # do justice to this complex structure + GradientBasedCorrections => { + FlatName => 'GradientBasedCorr', + Struct => \%sCorrection, + List => 'Seq', + }, + GradientBasedCorrectionsCorrectionMasks => { + Name => 'GradientBasedCorrMasks', + FlatName => 'GradientBasedCorrMask', + Flat => 1 + }, + GradientBasedCorrectionsCorrectionMasksDabs => { + Name => 'GradientBasedCorrMaskDabs', + Flat => 1, List => 0, + }, + PaintBasedCorrections => { + FlatName => 'PaintCorrection', + Struct => \%sCorrection, + List => 'Seq', + }, + PaintBasedCorrectionsCorrectionMasks => { + Name => 'PaintBasedCorrectionMasks', + FlatName => 'PaintCorrectionMask', + Flat => 1, + }, + PaintBasedCorrectionsCorrectionMasksDabs => { + Name => 'PaintCorrectionMaskDabs', + Flat => 1, List => 0, + }, + # new tags written by LR 3 (thanks Wolfgang Guelcker) + ProcessVersion => { }, + LensProfileEnable => { Writable => 'integer' }, + LensProfileSetup => { }, + LensProfileName => { }, + LensProfileFilename => { }, + LensProfileDigest => { }, + LensProfileDistortionScale => { Writable => 'integer' }, + LensProfileChromaticAberrationScale => { Writable => 'integer' }, + LensProfileVignettingScale => { Writable => 'integer' }, + LensManualDistortionAmount => { Writable => 'integer' }, + PerspectiveVertical => { Writable => 'integer' }, + PerspectiveHorizontal => { Writable => 'integer' }, + PerspectiveRotate => { Writable => 'real' }, + PerspectiveScale => { Writable => 'integer' }, + CropConstrainToWarp => { Writable => 'integer' }, + LuminanceNoiseReductionDetail => { Writable => 'integer' }, + LuminanceNoiseReductionContrast => { Writable => 'integer' }, + ColorNoiseReductionDetail => { Writable => 'integer' }, + GrainAmount => { Writable => 'integer' }, + GrainSize => { Writable => 'integer' }, + GrainFrequency => { Writable => 'integer' }, + # new tags written by LR4 + AutoLateralCA => { Writable => 'integer' }, + Exposure2012 => { Writable => 'real' }, + Contrast2012 => { Writable => 'integer' }, + Highlights2012 => { Writable => 'integer' }, + Highlight2012 => { Writable => 'integer' }, # (written by Nikon software) + Shadows2012 => { Writable => 'integer' }, + Whites2012 => { Writable => 'integer' }, + Blacks2012 => { Writable => 'integer' }, + Clarity2012 => { Writable => 'integer' }, + PostCropVignetteHighlightContrast => { Writable => 'integer' }, + ToneCurveName2012 => { }, + ToneCurveRed => { List => 'Seq' }, + ToneCurveGreen => { List => 'Seq' }, + ToneCurveBlue => { List => 'Seq' }, + ToneCurvePV2012 => { List => 'Seq' }, + ToneCurvePV2012Red => { List => 'Seq' }, + ToneCurvePV2012Green => { List => 'Seq' }, + ToneCurvePV2012Blue => { List => 'Seq' }, + DefringePurpleAmount => { Writable => 'integer' }, + DefringePurpleHueLo => { Writable => 'integer' }, + DefringePurpleHueHi => { Writable => 'integer' }, + DefringeGreenAmount => { Writable => 'integer' }, + DefringeGreenHueLo => { Writable => 'integer' }, + DefringeGreenHueHi => { Writable => 'integer' }, + # new tags written by LR5 + AutoWhiteVersion => { Writable => 'integer' }, + CircularGradientBasedCorrections => { + FlatName => 'CircGradBasedCorr', + Struct => \%sCorrection, + List => 'Seq', + }, + CircularGradientBasedCorrectionsCorrectionMasks => { + Name => 'CircGradBasedCorrMasks', + FlatName => 'CircGradBasedCorrMask', + Flat => 1 + }, + CircularGradientBasedCorrectionsCorrectionMasksDabs => { + Name => 'CircGradBasedCorrMaskDabs', + Flat => 1, List => 0, + }, + ColorNoiseReductionSmoothness => { Writable => 'integer' }, + PerspectiveAspect => { Writable => 'integer' }, + PerspectiveUpright => { + Writable => 'integer', + PrintConv => { #forum14012 + 0 => 'Off', # Disable Upright + 1 => 'Auto', # Apply balanced perspective corrections + 2 => 'Full', # Apply level, horizontal, and vertical perspective corrections + 3 => 'Level', # Apply only level correction + 4 => 'Vertical',# Apply level and vertical perspective corrections + 5 => 'Guided', # Draw two or more guides to customize perspective corrections + }, + }, + RetouchAreas => { + FlatName => 'RetouchArea', + Struct => \%sRetouchArea, + List => 'Seq', + }, + RetouchAreasMasks => { + Name => 'RetouchAreaMasks', + FlatName => 'RetouchAreaMask', + Flat => 1 + }, + RetouchAreasMasksDabs => { + Name => 'RetouchAreaMaskDabs', + Flat => 1, List => 0, + }, + UprightVersion => { Writable => 'integer' }, + UprightCenterMode => { Writable => 'integer' }, + UprightCenterNormX => { Writable => 'real' }, + UprightCenterNormY => { Writable => 'real' }, + UprightFocalMode => { Writable => 'integer' }, + UprightFocalLength35mm => { Writable => 'real' }, + UprightPreview => { Writable => 'boolean' }, + UprightTransformCount => { Writable => 'integer' }, + UprightDependentDigest => { }, + UprightGuidedDependentDigest => { }, + UprightTransform_0 => { }, + UprightTransform_1 => { }, + UprightTransform_2 => { }, + UprightTransform_3 => { }, + UprightTransform_4 => { }, + UprightTransform_5 => { }, + UprightFourSegments_0 => { }, + UprightFourSegments_1 => { }, + UprightFourSegments_2 => { }, + UprightFourSegments_3 => { }, + # more stuff seen in lens profile file (unknown source) + What => { }, # (with value "LensProfileDefaultSettings") + LensProfileMatchKeyExifMake => { }, + LensProfileMatchKeyExifModel => { }, + LensProfileMatchKeyCameraModelName => { }, + LensProfileMatchKeyLensInfo => { }, + LensProfileMatchKeyLensID => { }, + LensProfileMatchKeyLensName => { }, + LensProfileMatchKeyIsRaw => { Writable => 'boolean' }, + LensProfileMatchKeySensorFormatFactor=>{ Writable => 'real' }, + # more stuff (ref forum6993) + DefaultAutoTone => { Writable => 'boolean' }, + DefaultAutoGray => { Writable => 'boolean' }, + DefaultsSpecificToSerial => { Writable => 'boolean' }, + DefaultsSpecificToISO => { Writable => 'boolean' }, + DNGIgnoreSidecars => { Writable => 'boolean' }, + NegativeCachePath => { }, + NegativeCacheMaximumSize => { Writable => 'real' }, + NegativeCacheLargePreviewSize => { Writable => 'integer' }, + JPEGHandling => { }, + TIFFHandling => { }, + Dehaze => { Writable => 'real' }, + ToneMapStrength => { Writable => 'real' }, + # yet more + PerspectiveX => { Writable => 'real' }, + PerspectiveY => { Writable => 'real' }, + UprightFourSegmentsCount => { Writable => 'integer' }, + AutoTone => { Writable => 'boolean' }, + Texture => { Writable => 'integer' }, + # more stuff (ref forum10721) + OverrideLookVignette => { Writable => 'boolean' }, + Look => { + Struct => { + STRUCT_NAME => 'Look', + NAMESPACE => 'crs', + Name => { }, + Amount => { }, + Cluster => { }, + UUID => { }, + SupportsMonochrome => { }, + SupportsAmount => { }, + SupportsOutputReferred => { }, + Copyright => { }, + Group => { Writable => 'lang-alt' }, + Parameters => { + Struct => { + STRUCT_NAME => 'LookParms', + NAMESPACE => 'crs', + Version => { }, + ProcessVersion => { }, + Clarity2012 => { }, + ConvertToGrayscale => { }, + CameraProfile => { }, + LookTable => { }, + ToneCurvePV2012 => { List => 'Seq' }, + ToneCurvePV2012Red => { List => 'Seq' }, + ToneCurvePV2012Green => { List => 'Seq' }, + ToneCurvePV2012Blue => { List => 'Seq' }, + }, + }, + } + }, + # more again (ref forum11258) + GrainSeed => { }, + ClipboardOrientation => { Writable => 'integer' }, + ClipboardAspectRatio => { Writable => 'integer' }, + PresetType => { }, + Cluster => { }, + UUID => { Avoid => 1 }, + SupportsAmount => { Writable => 'boolean' }, + SupportsColor => { Writable => 'boolean' }, + SupportsMonochrome => { Writable => 'boolean' }, + SupportsHighDynamicRange=> { Writable => 'boolean' }, + SupportsNormalDynamicRange=> { Writable => 'boolean' }, + SupportsSceneReferred => { Writable => 'boolean' }, + SupportsOutputReferred => { Writable => 'boolean' }, + CameraModelRestriction => { }, + Copyright => { Avoid => 1 }, + ContactInfo => { }, + GrainSeed => { Writable => 'integer' }, + Name => { Writable => 'lang-alt', Avoid => 1 }, + ShortName => { Writable => 'lang-alt' }, + SortName => { Writable => 'lang-alt' }, + Group => { Writable => 'lang-alt', Avoid => 1 }, + Description => { Writable => 'lang-alt', Avoid => 1 }, + # new for DNG converter 13.0 + LookName => { NotFlat => 1 }, # (grr... conflicts with "Name" element of "Look" struct!) + # new for Lightroom CC 2021 (ref forum11745) + ColorGradeMidtoneHue => { Writable => 'integer' }, + ColorGradeMidtoneSat => { Writable => 'integer' }, + ColorGradeShadowLum => { Writable => 'integer' }, + ColorGradeMidtoneLum => { Writable => 'integer' }, + ColorGradeHighlightLum => { Writable => 'integer' }, + ColorGradeBlending => { Writable => 'integer' }, + ColorGradeGlobalHue => { Writable => 'integer' }, + ColorGradeGlobalSat => { Writable => 'integer' }, + ColorGradeGlobalLum => { Writable => 'integer' }, + # new for Adobe Camera Raw 13 (ref forum11745) + LensProfileIsEmbedded => { Writable => 'boolean'}, + AutoToneDigest => { }, + AutoToneDigestNoSat => { }, + ToggleStyleDigest => { }, + ToggleStyleAmount => { Writable => 'integer' }, + # new for LightRoom 11.0 + CompatibleVersion => { }, + MaskGroupBasedCorrections => { + FlatName => 'MaskGroupBasedCorr', + Struct => \%sCorrection, + List => 'Seq', + }, + RangeMaskMapInfo => { Name => 'RangeMask', Struct => \%sRangeMask, FlatName => 'RangeMask' }, + # new for ACR 15.1 (not sure if these are integer or real, so just guess) + HDREditMode => { Writable => 'integer' }, + SDRBrightness => { Writable => 'real' }, + SDRContrast => { Writable => 'real' }, + SDRHighlights => { Writable => 'real' }, + SDRShadows => { Writable => 'real' }, + SDRWhites => { Writable => 'real' }, + SDRBlend => { Writable => 'real' }, +); + +# Tiff namespace properties (tiff) +%Image::ExifTool::XMP::tiff = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-tiff', 2 => 'Image' }, + NAMESPACE => 'tiff', + PRIORITY => 0, # not as reliable as actual TIFF tags + TABLE_DESC => 'XMP TIFF', + NOTES => q{ + EXIF namespace for TIFF tags. See + L<https://web.archive.org/web/20180921145139if_/http://www.cipa.jp:80/std/documents/e/DC-010-2017_E.pdf> + for the specification. + }, + ImageWidth => { Writable => 'integer' }, + ImageLength => { Writable => 'integer', Name => 'ImageHeight' }, + BitsPerSample => { Writable => 'integer', List => 'Seq', AutoSplit => 1 }, + Compression => { + Writable => 'integer', + SeparateTable => 'EXIF Compression', + PrintConv => \%Image::ExifTool::Exif::compression, + }, + PhotometricInterpretation => { + Writable => 'integer', + PrintConv => \%Image::ExifTool::Exif::photometricInterpretation, + }, + Orientation => { + Writable => 'integer', + PrintConv => \%Image::ExifTool::Exif::orientation, + }, + SamplesPerPixel => { Writable => 'integer' }, + PlanarConfiguration => { + Writable => 'integer', + PrintConv => { + 1 => 'Chunky', + 2 => 'Planar', + }, + }, + YCbCrSubSampling => { + Writable => 'integer', + List => 'Seq', + # join the raw values before conversion to allow PrintConv to operate on + # the combined string as it does for the corresponding EXIF tag + RawJoin => 1, + Notes => q{ + while technically this is a list-type tag, for compatibility with its EXIF + counterpart it is written and read as a simple string + }, + PrintConv => \%Image::ExifTool::JPEG::yCbCrSubSampling, + }, + YCbCrPositioning => { + Writable => 'integer', + PrintConv => { + 1 => 'Centered', + 2 => 'Co-sited', + }, + }, + XResolution => { Writable => 'rational' }, + YResolution => { Writable => 'rational' }, + ResolutionUnit => { + Writable => 'integer', + Notes => 'the value 1 is not standard EXIF', + PrintConv => { + 1 => 'None', + 2 => 'inches', + 3 => 'cm', + }, + }, + TransferFunction => { Writable => 'integer', List => 'Seq', AutoSplit => 1 }, + WhitePoint => { Writable => 'rational', List => 'Seq', AutoSplit => 1 }, + PrimaryChromaticities => { Writable => 'rational', List => 'Seq', AutoSplit => 1 }, + YCbCrCoefficients => { Writable => 'rational', List => 'Seq', AutoSplit => 1 }, + ReferenceBlackWhite => { Writable => 'rational', List => 'Seq', AutoSplit => 1 }, + DateTime => { # (EXIF tag named ModifyDate, but this exists in XMP-xmp) + Description => 'Date/Time Modified', + Groups => { 2 => 'Time' }, + %dateTimeInfo, + }, + ImageDescription => { Writable => 'lang-alt' }, + Make => { + Groups => { 2 => 'Camera' }, + RawConv => '$$self{Make} ? $val : $$self{Make} = $val', + }, + Model => { + Groups => { 2 => 'Camera' }, + Description => 'Camera Model Name', + RawConv => '$$self{Model} ? $val : $$self{Model} = $val', + }, + Software => { }, + Artist => { Groups => { 2 => 'Author' } }, + Copyright => { Groups => { 2 => 'Author' }, Writable => 'lang-alt' }, + NativeDigest => { Avoid => 1 }, #PH +); + +# Exif namespace properties (exif) +%Image::ExifTool::XMP::exif = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-exif', 2 => 'Image' }, + NAMESPACE => 'exif', + PRIORITY => 0, # not as reliable as actual EXIF tags + NOTES => q{ + EXIF namespace for EXIF tags. See + L<https://web.archive.org/web/20180921145139if_/http://www.cipa.jp:80/std/documents/e/DC-010-2017_E.pdf> + for the specification. + }, + ExifVersion => { }, + FlashpixVersion => { }, + ColorSpace => { + Writable => 'integer', + # (some applications incorrectly write -1 as a long integer) + ValueConv => '$val == 0xffffffff ? 0xffff : $val', + ValueConvInv => '$val', + PrintConv => { + 1 => 'sRGB', + 2 => 'Adobe RGB', + 0xffff => 'Uncalibrated', + }, + }, + ComponentsConfiguration => { + Writable => 'integer', + List => 'Seq', + AutoSplit => 1, + PrintConvColumns => 2, + PrintConv => { + 0 => '-', + 1 => 'Y', + 2 => 'Cb', + 3 => 'Cr', + 4 => 'R', + 5 => 'G', + 6 => 'B', + }, + }, + CompressedBitsPerPixel => { Writable => 'rational' }, + PixelXDimension => { Name => 'ExifImageWidth', Writable => 'integer' }, + PixelYDimension => { Name => 'ExifImageHeight', Writable => 'integer' }, + MakerNote => { }, + UserComment => { Writable => 'lang-alt' }, + RelatedSoundFile => { }, + DateTimeOriginal => { + Description => 'Date/Time Original', + Groups => { 2 => 'Time' }, + %dateTimeInfo, + }, + DateTimeDigitized => { # (EXIF tag named CreateDate, but this exists in XMP-xmp) + Description => 'Date/Time Digitized', + Groups => { 2 => 'Time' }, + %dateTimeInfo, + }, + ExposureTime => { + Writable => 'rational', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + PrintConvInv => '$val', + }, + FNumber => { + Writable => 'rational', + PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', + PrintConvInv => '$val', + }, + ExposureProgram => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + PrintConv => { + 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', + }, + }, + SpectralSensitivity => { Groups => { 2 => 'Camera' } }, + ISOSpeedRatings => { + Name => 'ISO', + Writable => 'integer', + List => 'Seq', + AutoSplit => 1, + }, + OECF => { + Name => 'Opto-ElectricConvFactor', + FlatName => 'OECF', + Groups => { 2 => 'Camera' }, + Struct => \%sOECF, + }, + ShutterSpeedValue => { + Writable => 'rational', + ValueConv => 'abs($val)<100 ? 1/(2**$val) : 0', + PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', + ValueConvInv => '$val>0 ? -log($val)/log(2) : 0', + PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)', + }, + ApertureValue => { + Writable => 'rational', + ValueConv => 'sqrt(2) ** $val', + PrintConv => 'sprintf("%.1f",$val)', + ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0', + PrintConvInv => '$val', + }, + BrightnessValue => { Writable => 'rational' }, + ExposureBiasValue => { + Name => 'ExposureCompensation', + Writable => 'rational', + PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', + PrintConvInv => '$val', + }, + MaxApertureValue => { + Groups => { 2 => 'Camera' }, + Writable => 'rational', + ValueConv => 'sqrt(2) ** $val', + PrintConv => 'sprintf("%.1f",$val)', + ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0', + PrintConvInv => '$val', + }, + SubjectDistance => { + Groups => { 2 => 'Camera' }, + Writable => 'rational', + PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"', + PrintConvInv => '$val=~s/\s*m$//;$val', + }, + MeteringMode => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + PrintConv => { + 1 => 'Average', + 2 => 'Center-weighted average', + 3 => 'Spot', + 4 => 'Multi-spot', + 5 => 'Multi-segment', + 6 => 'Partial', + 255 => 'Other', + }, + }, + LightSource => { + Groups => { 2 => 'Camera' }, + SeparateTable => 'EXIF LightSource', + PrintConv => \%Image::ExifTool::Exif::lightSource, + }, + Flash => { + Groups => { 2 => 'Camera' }, + Struct => { + STRUCT_NAME => 'Flash', + NAMESPACE => 'exif', + Fired => { Writable => 'boolean', %boolConv }, + Return => { + Writable => 'integer', + PrintConv => { + 0 => 'No return detection', + 2 => 'Return not detected', + 3 => 'Return detected', + }, + }, + Mode => { + Writable => 'integer', + PrintConv => { + 0 => 'Unknown', + 1 => 'On', + 2 => 'Off', + 3 => 'Auto', + }, + }, + Function => { Writable => 'boolean', %boolConv }, + RedEyeMode => { Writable => 'boolean', %boolConv }, + }, + }, + FocalLength=> { + Groups => { 2 => 'Camera' }, + Writable => 'rational', + PrintConv => 'sprintf("%.1f mm",$val)', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + SubjectArea => { Writable => 'integer', List => 'Seq', AutoSplit => 1 }, + FlashEnergy => { Groups => { 2 => 'Camera' }, Writable => 'rational' }, + SpatialFrequencyResponse => { + Groups => { 2 => 'Camera' }, + Struct => \%sOECF, + }, + FocalPlaneXResolution => { Groups => { 2 => 'Camera' }, Writable => 'rational' }, + FocalPlaneYResolution => { Groups => { 2 => 'Camera' }, Writable => 'rational' }, + FocalPlaneResolutionUnit => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + Notes => 'values 1, 4 and 5 are not standard EXIF', + PrintConv => { + 1 => 'None', # (not standard EXIF) + 2 => 'inches', + 3 => 'cm', + 4 => 'mm', # (not standard EXIF) + 5 => 'um', # (not standard EXIF) + }, + }, + SubjectLocation => { Writable => 'integer', List => 'Seq', AutoSplit => 1 }, + ExposureIndex => { Writable => 'rational' }, + SensingMethod => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + Notes => 'values 1 and 6 are not standard EXIF', + PrintConv => { + 1 => 'Monochrome area', # (not standard EXIF) + 2 => 'One-chip color area', + 3 => 'Two-chip color area', + 4 => 'Three-chip color area', + 5 => 'Color sequential area', + 6 => 'Monochrome linear', # (not standard EXIF) + 7 => 'Trilinear', + 8 => 'Color sequential linear', + }, + }, + FileSource => { + Writable => 'integer', + PrintConv => { + 1 => 'Film Scanner', + 2 => 'Reflection Print Scanner', + 3 => 'Digital Camera', + } + }, + SceneType => { Writable => 'integer', PrintConv => { 1 => 'Directly photographed' } }, + CFAPattern => { + Struct => { + STRUCT_NAME => 'CFAPattern', + NAMESPACE => 'exif', + Columns => { Writable => 'integer' }, + Rows => { Writable => 'integer' }, + Values => { Writable => 'integer', List => 'Seq' }, + }, + }, + CustomRendered => { + Writable => 'integer', + PrintConv => { + 0 => 'Normal', + 1 => 'Custom', + }, + }, + ExposureMode => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + PrintConv => { + 0 => 'Auto', + 1 => 'Manual', + 2 => 'Auto bracket', + }, + }, + WhiteBalance => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + PrintConv => { + 0 => 'Auto', + 1 => 'Manual', + }, + }, + DigitalZoomRatio => { Writable => 'rational' }, + FocalLengthIn35mmFilm => { + Name => 'FocalLengthIn35mmFormat', + Writable => 'integer', + Groups => { 2 => 'Camera' }, + PrintConv => '"$val mm"', + PrintConvInv => '$val=~s/\s*mm$//;$val', + }, + SceneCaptureType => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + PrintConv => { + 0 => 'Standard', + 1 => 'Landscape', + 2 => 'Portrait', + 3 => 'Night', + }, + }, + GainControl => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + PrintConv => { + 0 => 'None', + 1 => 'Low gain up', + 2 => 'High gain up', + 3 => 'Low gain down', + 4 => 'High gain down', + }, + }, + Contrast => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + PrintConv => { + 0 => 'Normal', + 1 => 'Low', + 2 => 'High', + }, + PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)', + }, + Saturation => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + PrintConv => { + 0 => 'Normal', + 1 => 'Low', + 2 => 'High', + }, + PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)', + }, + Sharpness => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + PrintConv => { + 0 => 'Normal', + 1 => 'Soft', + 2 => 'Hard', + }, + PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)', + }, + DeviceSettingDescription => { + Groups => { 2 => 'Camera' }, + Struct => { + STRUCT_NAME => 'DeviceSettings', + NAMESPACE => 'exif', + Columns => { Writable => 'integer' }, + Rows => { Writable => 'integer' }, + Settings => { List => 'Seq' }, + }, + }, + SubjectDistanceRange => { + Groups => { 2 => 'Camera' }, + Writable => 'integer', + PrintConv => { + 0 => 'Unknown', + 1 => 'Macro', + 2 => 'Close', + 3 => 'Distant', + }, + }, + ImageUniqueID => { }, + GPSVersionID => { Groups => { 2 => 'Location' } }, + GPSLatitude => { Groups => { 2 => 'Location' }, %latConv }, + GPSLongitude => { Groups => { 2 => 'Location' }, %longConv }, + GPSAltitudeRef => { + Groups => { 2 => 'Location' }, + Writable => 'integer', + PrintConv => { + OTHER => sub { + my ($val, $inv) = @_; + return undef unless $inv and $val =~ /^([-+0-9])/; + return($1 eq '-' ? 1 : 0); + }, + 0 => 'Above Sea Level', + 1 => 'Below Sea Level', + }, + }, + GPSAltitude => { + Groups => { 2 => 'Location' }, + Writable => 'rational', + # extricate unsigned decimal number from string + ValueConvInv => '$val=~/((?=\d|\.\d)\d*(?:\.\d*)?)/ ? $1 : undef', + PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"', + PrintConvInv => '$val=~s/\s*m$//;$val', + }, + GPSTimeStamp => { + Name => 'GPSDateTime', + Description => 'GPS Date/Time', + Groups => { 2 => 'Time' }, + Notes => q{ + a date/time tag called GPSTimeStamp by the XMP specification. This tag is + renamed here to prevent direct copy from EXIF:GPSTimeStamp which is a + time-only tag. Instead, the value of this tag should be taken from + Composite:GPSDateTime when copying from EXIF + }, + %dateTimeInfo, + }, + GPSSatellites => { Groups => { 2 => 'Location' } }, + GPSStatus => { + Groups => { 2 => 'Location' }, + PrintConv => { + A => 'Measurement Active', + V => 'Measurement Void', + }, + }, + GPSMeasureMode => { + Groups => { 2 => 'Location' }, + Writable => 'integer', + PrintConv => { + 2 => '2-Dimensional Measurement', + 3 => '3-Dimensional Measurement', + }, + }, + GPSDOP => { Groups => { 2 => 'Location' }, Writable => 'rational' }, + GPSSpeedRef => { + Groups => { 2 => 'Location' }, + PrintConv => { + K => 'km/h', + M => 'mph', + N => 'knots', + }, + }, + GPSSpeed => { Groups => { 2 => 'Location' }, Writable => 'rational' }, + GPSTrackRef => { + Groups => { 2 => 'Location' }, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + GPSTrack => { Groups => { 2 => 'Location' }, Writable => 'rational' }, + GPSImgDirectionRef => { + Groups => { 2 => 'Location' }, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + GPSImgDirection => { Groups => { 2 => 'Location' }, Writable => 'rational' }, + GPSMapDatum => { Groups => { 2 => 'Location' } }, + GPSDestLatitude => { Groups => { 2 => 'Location' }, %latConv }, + GPSDestLongitude=> { Groups => { 2 => 'Location' }, %longConv }, + GPSDestBearingRef => { + Groups => { 2 => 'Location' }, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + GPSDestBearing => { Groups => { 2 => 'Location' }, Writable => 'rational' }, + GPSDestDistanceRef => { + Groups => { 2 => 'Location' }, + PrintConv => { + K => 'Kilometers', + M => 'Miles', + N => 'Nautical Miles', + }, + }, + GPSDestDistance => { + Groups => { 2 => 'Location' }, + Writable => 'rational', + }, + GPSProcessingMethod => { Groups => { 2 => 'Location' } }, + GPSAreaInformation => { Groups => { 2 => 'Location' } }, + GPSDifferential => { + Groups => { 2 => 'Location' }, + Writable => 'integer', + PrintConv => { + 0 => 'No Correction', + 1 => 'Differential Corrected', + }, + }, + GPSHPositioningError => { #12 + Description => 'GPS Horizontal Positioning Error', + Groups => { 2 => 'Location' }, + Writable => 'rational', + PrintConv => '"$val m"', + PrintConvInv => '$val=~s/\s*m$//; $val', + }, + NativeDigest => { }, #PH + # the following written incorrectly by ACR 15.1 + # SubSecTime (should not be written according to Exif4XMP 2.32 specification) + # SubSecTimeOriginal (should not be written according to Exif4XMP 2.32 specification) + # SubSecTimeDigitized (should not be written according to Exif4XMP 2.32 specification) + # SerialNumber (should be BodySerialNumber) + # Lens (should be XMP-aux) + # LensInfo (should be XMP-aux) +); + +# Exif extended properties (exifEX, ref 12) +%Image::ExifTool::XMP::exifEX = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-exifEX', 2 => 'Image' }, + NAMESPACE => 'exifEX', + PRIORITY => 0, # not as reliable as actual EXIF tags + NOTES => q{ + EXIF tags added by the EXIF 2.32 for XMP specification (see + L<https://cipa.jp/std/documents/download_e.html?DC-010-2020_E>). + }, + Gamma => { Writable => 'rational' }, + PhotographicSensitivity => { Writable => 'integer' }, + SensitivityType => { + Writable => 'integer', + PrintConv => { + 0 => 'Unknown', + 1 => 'Standard Output Sensitivity', + 2 => 'Recommended Exposure Index', + 3 => 'ISO Speed', + 4 => 'Standard Output Sensitivity and Recommended Exposure Index', + 5 => 'Standard Output Sensitivity and ISO Speed', + 6 => 'Recommended Exposure Index and ISO Speed', + 7 => 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed', + }, + }, + StandardOutputSensitivity => { Writable => 'integer' }, + RecommendedExposureIndex => { Writable => 'integer' }, + ISOSpeed => { Writable => 'integer' }, + ISOSpeedLatitudeyyy => { + Description => 'ISO Speed Latitude yyy', + Writable => 'integer', + }, + ISOSpeedLatitudezzz => { + Description => 'ISO Speed Latitude zzz', + Writable => 'integer', + }, + CameraOwnerName => { Name => 'OwnerName' }, + BodySerialNumber => { Name => 'SerialNumber', Groups => { 2 => 'Camera' } }, + LensSpecification => { + Name => 'LensInfo', + Writable => 'rational', + Groups => { 2 => 'Camera' }, + List => 'Seq', + RawJoin => 1, # join list into a string before ValueConv + ValueConv => \&ConvertRationalList, + ValueConvInv => sub { + my $val = shift; + my @vals = split ' ', $val; + return $val unless @vals == 4; + foreach (@vals) { + $_ eq 'inf' and $_ = '1/0', next; + $_ eq 'undef' and $_ = '0/0', next; + Image::ExifTool::IsFloat($_) or return $val; + my @a = Image::ExifTool::Rationalize($_); + $_ = join '/', @a; + } + return \@vals; # return list reference (List-type tag) + }, + PrintConv => \&Image::ExifTool::Exif::PrintLensInfo, + PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo, + Notes => q{ + unfortunately the EXIF 2.3 for XMP specification defined this new tag + instead of using the existing XMP-aux:LensInfo + }, + }, + LensMake => { Groups => { 2 => 'Camera' } }, + LensModel => { Groups => { 2 => 'Camera' } }, + LensSerialNumber => { Groups => { 2 => 'Camera' } }, + InteroperabilityIndex => { + Name => 'InteropIndex', + Description => 'Interoperability Index', + PrintConv => { + R98 => 'R98 - DCF basic file (sRGB)', + R03 => 'R03 - DCF option file (Adobe RGB)', + THM => 'THM - DCF thumbnail file', + }, + }, + # new in Exif 2.31 + Temperature => { Writable => 'rational', Name => 'AmbientTemperature' }, + Humidity => { Writable => 'rational' }, + Pressure => { Writable => 'rational' }, + WaterDepth => { Writable => 'rational' }, + Acceleration => { Writable => 'rational' }, + CameraElevationAngle=> { Writable => 'rational' }, + # new in Exif 2.32 (according to the spec, these should use a different namespace + # URI, but the same namespace prefix... Exactly how is that supposed to work?!! + # -- I'll just stick with the same URI) + CompositeImage => { Writable => 'integer', + PrintConv => { + 0 => 'Unknown', + 1 => 'Not a Composite Image', + 2 => 'General Composite Image', + 3 => 'Composite Image Captured While Shooting', + }, + }, + CompositeImageCount => { List => 'Seq', Writable => 'integer' }, + CompositeImageExposureTimes => { + FlatName => 'CompImage', + Struct => { + STRUCT_NAME => 'CompImageExp', + NAMESPACE => 'exifEX', + TotalExposurePeriod => { Writable => 'rational' }, + SumOfExposureTimesOfAll => { Writable => 'rational', FlatName => 'SumExposureAll' }, + SumOfExposureTimesOfUsed=> { Writable => 'rational', FlatName => 'SumExposureUsed' }, + MaxExposureTimesOfAll => { Writable => 'rational', FlatName => 'MaxExposureAll' }, + MaxExposureTimesOfUsed => { Writable => 'rational', FlatName => 'MaxExposureUsed' }, + MinExposureTimesOfAll => { Writable => 'rational', FlatName => 'MinExposureAll' }, + MinExposureTimesOfUsed => { Writable => 'rational', FlatName => 'MinExposureUsed' }, + NumberOfSequences => { Writable => 'integer', FlatName => 'NumSequences' }, + NumberOfImagesInSequences=>{ Writable => 'integer', FlatName => 'ImagesPerSequence' }, + Values => { List => 'Seq', Writable => 'rational' }, + }, + }, +); + +# Auxiliary namespace properties (aux) - not fully documented (ref PH) +%Image::ExifTool::XMP::aux = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-aux', 2 => 'Camera' }, + NAMESPACE => 'aux', + NOTES => q{ + Adobe-defined auxiliary EXIF tags. This namespace existed in the XMP + specification until it was dropped in 2012, presumably due to the + introduction of the EXIF 2.3 for XMP specification and the exifEX namespace + at this time. For this reason, tags below with equivalents in the + L<exifEX namespace|/XMP exifEX Tags> are avoided when writing. + }, + Firmware => { }, #7 + FlashCompensation => { Writable => 'rational' }, #7 + ImageNumber => { }, #7 + LensInfo => { #7 + Notes => '4 rational values giving focal and aperture ranges', + Avoid => 1, + # convert to floating point values (or 'inf' or 'undef') + ValueConv => \&ConvertRationalList, + ValueConvInv => sub { + my $val = shift; + my @vals = split ' ', $val; + return $val unless @vals == 4; + foreach (@vals) { + $_ eq 'inf' and $_ = '1/0', next; + $_ eq 'undef' and $_ = '0/0', next; + Image::ExifTool::IsFloat($_) or return $val; + my @a = Image::ExifTool::Rationalize($_); + $_ = join '/', @a; + } + return join ' ', @vals; # return string (string tag) + }, + # convert to the form "12-20mm f/3.8-4.5" or "50mm f/1.4" + PrintConv => \&Image::ExifTool::Exif::PrintLensInfo, + PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo, + }, + Lens => { }, + OwnerName => { Avoid => 1 }, #7 + SerialNumber => { Avoid => 1 }, + LensSerialNumber=> { Avoid => 1 }, + LensID => { + Priority => 0, + # prevent this from getting set from a LensID that has been converted + ValueConvInv => q{ + warn "Expected one or more integer values" if $val =~ /[^-\d ]/; + return $val; + }, + }, + ApproximateFocusDistance => { Writable => 'rational' }, #PH (LR3) + # the following new in LR6 (ref forum6497) + IsMergedPanorama => { Writable => 'boolean' }, + IsMergedHDR => { Writable => 'boolean' }, + DistortionCorrectionAlreadyApplied => { Writable => 'boolean' }, + VignetteCorrectionAlreadyApplied => { Writable => 'boolean' }, + LateralChromaticAberrationCorrectionAlreadyApplied => { Writable => 'boolean' }, + LensDistortInfo => { }, # (LR 7.5.1, 4 signed rational values) + NeutralDensityFactor => { }, # (LR 11.0 - rational value, but denominator seems significant) + # the following are ref forum13747 + EnhanceDetailsAlreadyApplied => { Writable => 'boolean' }, + EnhanceDetailsVersion => { }, # integer? + EnhanceSuperResolutionAlreadyApplied => { Writable => 'boolean' }, + EnhanceSuperResolutionVersion => { }, # integer? + EnhanceSuperResolutionScale => { Writable => 'rational' }, + EnhanceDenoiseAlreadyApplied => { Writable => 'boolean' }, #forum14760 + EnhanceDenoiseVersion => { }, #forum14760 integer? + EnhanceDenoiseLumaAmount => { }, #forum14760 integer? +); + +# IPTC Core namespace properties (Iptc4xmpCore) (ref 4) +%Image::ExifTool::XMP::iptcCore = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-iptcCore', 2 => 'Author' }, + NAMESPACE => 'Iptc4xmpCore', + TABLE_DESC => 'XMP IPTC Core', + NOTES => q{ + IPTC Core namespace tags. The actual IPTC Core namespace prefix is + "Iptc4xmpCore", which is the prefix recorded in the file, but ExifTool + shortens this for the family 1 group name. (see + L<http://www.iptc.org/IPTC4XMP/>) + }, + CountryCode => { Groups => { 2 => 'Location' } }, + CreatorContactInfo => { + Struct => { + STRUCT_NAME => 'ContactInfo', + NAMESPACE => 'Iptc4xmpCore', + CiAdrCity => { }, + CiAdrCtry => { }, + CiAdrExtadr => { }, + CiAdrPcode => { }, + CiAdrRegion => { }, + CiEmailWork => { }, + CiTelWork => { }, + CiUrlWork => { }, + }, + }, + CreatorContactInfoCiAdrCity => { Flat => 1, Name => 'CreatorCity' }, + CreatorContactInfoCiAdrCtry => { Flat => 1, Name => 'CreatorCountry' }, + CreatorContactInfoCiAdrExtadr => { Flat => 1, Name => 'CreatorAddress' }, + CreatorContactInfoCiAdrPcode => { Flat => 1, Name => 'CreatorPostalCode' }, + CreatorContactInfoCiAdrRegion => { Flat => 1, Name => 'CreatorRegion' }, + CreatorContactInfoCiEmailWork => { Flat => 1, Name => 'CreatorWorkEmail' }, + CreatorContactInfoCiTelWork => { Flat => 1, Name => 'CreatorWorkTelephone' }, + CreatorContactInfoCiUrlWork => { Flat => 1, Name => 'CreatorWorkURL' }, + IntellectualGenre => { Groups => { 2 => 'Other' } }, + Location => { Groups => { 2 => 'Location' } }, + Scene => { Groups => { 2 => 'Other' }, List => 'Bag' }, + SubjectCode => { Groups => { 2 => 'Other' }, List => 'Bag' }, + # Copyright - have seen this in a sample (Jan 2021), but I think it is non-standard + # new IPTC Core 1.3 properties + AltTextAccessibility => { Groups => { 2 => 'Other' }, Writable => 'lang-alt' }, + ExtDescrAccessibility => { Groups => { 2 => 'Other' }, Writable => 'lang-alt' }, +); + +# Adobe Lightroom namespace properties (lr) (ref PH) +%Image::ExifTool::XMP::Lightroom = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-lr', 2 => 'Image' }, + NAMESPACE => 'lr', + TABLE_DESC => 'XMP Adobe Lightroom', + NOTES => 'Adobe Lightroom "lr" namespace tags.', + privateRTKInfo => { }, + hierarchicalSubject => { List => 'Bag' }, + weightedFlatSubject => { List => 'Bag' }, +); + +# Adobe Album namespace properties (album) (ref PH) +%Image::ExifTool::XMP::Album = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-album', 2 => 'Image' }, + NAMESPACE => 'album', + TABLE_DESC => 'XMP Adobe Album', + NOTES => 'Adobe Album namespace tags.', + Notes => { }, +); + +# ExifTool namespace properties (et) +%Image::ExifTool::XMP::ExifTool = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-et', 2 => 'Image' }, + NAMESPACE => 'et', + OriginalImageHash => { Notes => 'used to store ExifTool ImageDataHash digest' }, + OriginalImageHashType => { Notes => "ImageHashType API setting, default 'MD5'" }, + OriginalImageMD5 => { Notes => 'deprecated' }, +); + +# table to add tags in other namespaces +%Image::ExifTool::XMP::other = ( + GROUPS => { 2 => 'Unknown' }, + LANG_INFO => \&GetLangInfo, +); + +# Composite XMP tags +%Image::ExifTool::XMP::Composite = ( + # get latitude/longitude reference from XMP lat/long tags + # (used to set EXIF GPS position from XMP tags) + GPSLatitudeRef => { + Require => 'XMP-exif:GPSLatitude', + Groups => { 2 => 'Location' }, + # Note: Do not Inihibit based on EXIF:GPSLatitudeRef (see forum10192) + ValueConv => q{ + IsFloat($val[0]) and return $val[0] < 0 ? "S" : "N"; + $val[0] =~ /^.*([NS])/; + return $1; + }, + PrintConv => { N => 'North', S => 'South' }, + }, + GPSLongitudeRef => { + Require => 'XMP-exif:GPSLongitude', + Groups => { 2 => 'Location' }, + ValueConv => q{ + IsFloat($val[0]) and return $val[0] < 0 ? "W" : "E"; + $val[0] =~ /^.*([EW])/; + return $1; + }, + PrintConv => { E => 'East', W => 'West' }, + }, + GPSDestLatitudeRef => { + Require => 'XMP-exif:GPSDestLatitude', + Groups => { 2 => 'Location' }, + ValueConv => q{ + IsFloat($val[0]) and return $val[0] < 0 ? "S" : "N"; + $val[0] =~ /^.*([NS])/; + return $1; + }, + PrintConv => { N => 'North', S => 'South' }, + }, + GPSDestLongitudeRef => { + Require => 'XMP-exif:GPSDestLongitude', + Groups => { 2 => 'Location' }, + ValueConv => q{ + IsFloat($val[0]) and return $val[0] < 0 ? "W" : "E"; + $val[0] =~ /^.*([EW])/; + return $1; + }, + PrintConv => { E => 'East', W => 'West' }, + }, + LensID => { + Notes => 'attempt to convert numerical XMP-aux:LensID stored by Adobe applications', + Require => { + 0 => 'XMP-aux:LensID', + 1 => 'Make', + }, + Desire => { + 2 => 'LensInfo', + 3 => 'FocalLength', + 4 => 'LensModel', + 5 => 'MaxApertureValue', + }, + Inhibit => { + 6 => 'Composite:LensID', # don't override existing Composite:LensID + }, + Groups => { 2 => 'Camera' }, + ValueConv => '$val', + PrintConv => 'Image::ExifTool::XMP::PrintLensID($self, @val)', + }, + Flash => { + Notes => 'facilitates copying camera flash information between XMP and EXIF', + Desire => { + 0 => 'XMP:FlashFired', + 1 => 'XMP:FlashReturn', + 2 => 'XMP:FlashMode', + 3 => 'XMP:FlashFunction', + 4 => 'XMP:FlashRedEyeMode', + 5 => 'XMP:Flash', # handle structured flash information too + }, + Groups => { 2 => 'Camera' }, + Writable => 1, + PrintHex => 1, + SeparateTable => 'EXIF Flash', + ValueConv => q{ + if (ref $val[5] eq 'HASH') { + # copy structure fields into value array + my $i = 0; + $val[$i++] = $val[5]{$_} foreach qw(Fired Return Mode Function RedEyeMode); + } + return((($val[0] and lc($val[0]) eq 'true') ? 0x01 : 0) | + (($val[1] || 0) << 1) | + (($val[2] || 0) << 3) | + (($val[3] and lc($val[3]) eq 'true') ? 0x20 : 0) | + (($val[4] and lc($val[4]) eq 'true') ? 0x40 : 0)); + }, + PrintConv => \%Image::ExifTool::Exif::flash, + WriteAlso => { + 'XMP:FlashFired' => '$val & 0x01 ? "True" : "False"', + 'XMP:FlashReturn' => '($val & 0x06) >> 1', + 'XMP:FlashMode' => '($val & 0x18) >> 3', + 'XMP:FlashFunction' => '$val & 0x20 ? "True" : "False"', + 'XMP:FlashRedEyeMode' => '$val & 0x40 ? "True" : "False"', + }, + }, +); + +# add our composite tags +Image::ExifTool::AddCompositeTags('Image::ExifTool::XMP'); + +#------------------------------------------------------------------------------ +# AutoLoad our writer routines when necessary +# +sub AUTOLOAD +{ + return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_); +} + +#------------------------------------------------------------------------------ +# Escape necessary XML characters in UTF-8 string +# Inputs: 0) string to be escaped +# Returns: escaped string +my %charName = ('"'=>'quot', '&'=>'amp', "'"=>'#39', '<'=>'lt', '>'=>'gt'); +sub EscapeXML($) +{ + my $str = shift; + $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters + return $str; +} + +#------------------------------------------------------------------------------ +# Unescape XML character references (entities and numerical) +# Inputs: 0) string to be unescaped +# 1) optional hash reference to convert entity names to numbers +# 2) optional character encoding +# Returns: unescaped string +my %charNum = ('quot'=>34, 'amp'=>38, 'apos'=>39, 'lt'=>60, 'gt'=>62); +sub UnescapeXML($;$$) +{ + my ($str, $conv, $enc) = @_; + $conv = \%charNum unless $conv; + $str =~ s/&(#?\w+);/UnescapeChar($1,$conv,$enc)/sge; + return $str; +} + +#------------------------------------------------------------------------------ +# Escape string for XML, ensuring valid XML and UTF-8 +# Inputs: 0) string +# Returns: escaped string +sub FullEscapeXML($) +{ + my $str = shift; + $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters + $str =~ s/\\/&#92;/sg; # escape backslashes too + # then use C-escape sequences for invalid characters + if ($str =~ /[\0-\x1f]/ or Image::ExifTool::IsUTF8(\$str) < 0) { + $str =~ s/([\0-\x1f\x80-\xff])/sprintf("\\x%.2x",ord $1)/sge; + } + return $str; +} + +#------------------------------------------------------------------------------ +# Unescape XML/C escaped string +# Inputs: 0) string +# Returns: unescaped string +sub FullUnescapeXML($) +{ + my $str = shift; + # unescape C escape sequences first + $str =~ s/\\x([\da-f]{2})/chr(hex($1))/sge; + my $conv = \%charNum; + $str =~ s/&(#?\w+);/UnescapeChar($1,$conv)/sge; + return $str; +} + +#------------------------------------------------------------------------------ +# Convert XML character reference to UTF-8 +# Inputs: 0) XML character reference stripped of the '&' and ';' (eg. 'quot', '#34', '#x22') +# 1) hash reference for looking up character numbers by name +# 2) optional character encoding (default 'UTF8') +# Returns: UTF-8 equivalent (or original character on conversion error) +sub UnescapeChar($$;$) +{ + my ($ch, $conv, $enc) = @_; + my $val = $$conv{$ch}; + unless (defined $val) { + if ($ch =~ /^#x([0-9a-fA-F]+)$/) { + $val = hex($1); + } elsif ($ch =~ /^#(\d+)$/) { + $val = $1; + } else { + return "&$ch;"; # should issue a warning here? [no] + } + } + return chr($val) if $val < 0x80; # simple ASCII + $val = $] >= 5.006001 ? pack('C0U', $val) : Image::ExifTool::PackUTF8($val); + $val = Image::ExifTool::Decode(undef, $val, 'UTF8', undef, $enc) if $enc and $enc ne 'UTF8'; + return $val; +} + +#------------------------------------------------------------------------------ +# Fix malformed UTF8 (by replacing bad bytes with specified character) +# Inputs: 0) string reference, 1) string to replace each bad byte, +# may be '' to delete bad bytes, or undef to use '?' +# Returns: true if string was fixed, and updates string +sub FixUTF8($;$) +{ + my ($strPt, $bad) = @_; + my $fixed; + pos($$strPt) = 0; # start at beginning of string + for (;;) { + last unless $$strPt =~ /([\x80-\xff])/g; + my $ch = ord($1); + my $pos = pos($$strPt); + # (see comments in Image::ExifTool::IsUTF8()) + if ($ch >= 0xc2 and $ch < 0xf8) { + my $n = $ch < 0xe0 ? 1 : ($ch < 0xf0 ? 2 : 3); + if ($$strPt =~ /\G([\x80-\xbf]{$n})/g) { + next if $n == 1; + if ($n == 2) { + next unless ($ch == 0xe0 and (ord($1) & 0xe0) == 0x80) or + ($ch == 0xed and (ord($1) & 0xe0) == 0xa0) or + ($ch == 0xef and ord($1) == 0xbf and + (ord(substr $1, 1) & 0xfe) == 0xbe); + } else { + next unless ($ch == 0xf0 and (ord($1) & 0xf0) == 0x80) or + ($ch == 0xf4 and ord($1) > 0x8f) or $ch > 0xf4; + } + } + } + # replace bad character + $bad = '?' unless defined $bad; + substr($$strPt, $pos-1, 1) = $bad; + pos($$strPt) = $pos-1 + length $bad; + $fixed = 1; + } + return $fixed; +} + +#------------------------------------------------------------------------------ +# Utility routine to decode a base64 string +# Inputs: 0) base64 string +# Returns: reference to decoded data +sub DecodeBase64($) +{ + local($^W) = 0; # unpack('u',...) gives bogus warning in 5.00[123] + my $str = shift; + + # truncate at first unrecognized character (base 64 data + # may only contain A-Z, a-z, 0-9, +, /, =, or white space) + $str =~ s/[^A-Za-z0-9+\/= \t\n\r\f].*//s; + # translate to uucoded and remove padding and white space + $str =~ tr/A-Za-z0-9+\/= \t\n\r\f/ -_/d; + + # convert the data to binary in chunks + my $chunkSize = 60; + my $uuLen = pack('c', 32 + $chunkSize * 3 / 4); # calculate length byte + my $dat = ''; + my ($i, $substr); + # loop through the whole chunks + my $len = length($str) - $chunkSize; + for ($i=0; $i<=$len; $i+=$chunkSize) { + $substr = substr($str, $i, $chunkSize); # get a chunk of the data + $dat .= unpack('u', $uuLen . $substr); # decode it + } + $len += $chunkSize; + # handle last partial chunk if necessary + if ($i < $len) { + $uuLen = pack('c', 32 + ($len-$i) * 3 / 4); # recalculate length + $substr = substr($str, $i, $len-$i); # get the last partial chunk + $dat .= unpack('u', $uuLen . $substr); # decode it + } + return \$dat; +} + +#------------------------------------------------------------------------------ +# Generate a tag ID for this XMP tag +# Inputs: 0) tag property name list ref, 1) array ref for receiving structure property list +# 2) array for receiving namespace list +# Returns: tagID and outtermost interesting namespace (or '' if no namespace) +sub GetXMPTagID($;$$) +{ + my ($props, $structProps, $nsList) = @_; + my ($tag, $prop, $namespace); + foreach $prop (@$props) { + # split name into namespace and property name + # (Note: namespace can be '' for property qualifiers) + my ($ns, $nm) = ($prop =~ /(.*?):(.*)/) ? ($1, $2) : ('', $prop); + if ($ignoreNamespace{$ns} or $ignoreProp{$prop} or $ignoreEtProp{$prop}) { + # special case: don't ignore rdf numbered items + # (not technically allowed in XMP, but used in RDF/XML) + unless ($prop =~ /^rdf:(_\d+)$/) { + # save list index if necessary for structures + if ($structProps and @$structProps and $prop =~ /^rdf:li (\d+)$/) { + push @{$$structProps[-1]}, $1; + } + next; + } + $tag .= $1 if defined $tag; + } else { + $nm =~ s/ .*//; # remove nodeID if it exists + # all uppercase is ugly, so convert it + if ($nm !~ /[a-z]/) { + my $xlat = $stdXlatNS{$ns} || $ns; + my $info = $Image::ExifTool::XMP::Main{$xlat}; + my $table; + if (ref $info eq 'HASH' and $$info{SubDirectory}) { + $table = GetTagTable($$info{SubDirectory}{TagTable}); + } + unless ($table and $$table{$nm}) { + $nm = lc($nm); + $nm =~ s/_([a-z])/\u$1/g; + } + } + if (defined $tag) { + $tag .= ucfirst($nm); # add to tag name + } else { + $tag = $nm; + } + # save structure information if necessary + if ($structProps) { + push @$structProps, [ $nm ]; + push @$nsList, $ns if $nsList; + } + } + # save namespace of first property to contribute to tag name + $namespace = $ns unless $namespace; + } + if (wantarray) { + return ($tag, $namespace || ''); + } else { + return $tag; + } +} + +#------------------------------------------------------------------------------ +# Register namespace for specified user-defined table +# Inputs: 0) tag/structure table ref +# Returns: namespace prefix +sub RegisterNamespace($) +{ + my $table = shift; + return $$table{NAMESPACE} unless ref $$table{NAMESPACE}; + my $nsRef = $$table{NAMESPACE}; + # recognize as either a list or hash + my $ns; + if (ref $nsRef eq 'ARRAY') { + $ns = $$nsRef[0]; + $nsURI{$ns} = $$nsRef[1]; + $uri2ns{$$nsRef[1]} = $ns; + } else { # must be a hash + my @ns = sort keys %$nsRef; # allow multiple namespace definitions + while (@ns) { + $ns = pop @ns; + if ($nsURI{$ns} and $nsURI{$ns} ne $$nsRef{$ns}) { + warn "User-defined namespace prefix '${ns}' conflicts with existing namespace\n"; + } + $nsURI{$ns} = $$nsRef{$ns}; + $uri2ns{$$nsRef{$ns}} = $ns; + } + } + return $$table{NAMESPACE} = $ns; +} + +#------------------------------------------------------------------------------ +# Generate flattened tags and add to table +# Inputs: 0) tag table ref, 1) tag ID for Struct tag (if not defined, whole table is done), +# 2) flag to not expand sub-structures +# Returns: number of tags added (not counting those just initialized) +# Notes: Must have verified that $$tagTablePtr{$tagID}{Struct} exists before calling this routine +# - makes sure that the tagInfo Struct is a HASH reference +sub AddFlattenedTags($;$$) +{ + local $_; + my ($tagTablePtr, $tagID, $noSubStruct) = @_; + my $count = 0; + my @tagIDs; + + if (defined $tagID) { + push @tagIDs, $tagID; + } else { + foreach $tagID (TagTableKeys($tagTablePtr)) { + my $tagInfo = $$tagTablePtr{$tagID}; + next unless ref $tagInfo eq 'HASH' and $$tagInfo{Struct}; + push @tagIDs, $tagID; + } + } + + # loop through specified tags + foreach $tagID (@tagIDs) { + + my $tagInfo = $$tagTablePtr{$tagID}; + + $$tagInfo{Flattened} and next; # only generate flattened tags once + $$tagInfo{Flattened} = 1; + + my $strTable = $$tagInfo{Struct}; + unless (ref $strTable) { # (allow a structure name for backward compatibility only) + my $strName = $strTable; + $strTable = $Image::ExifTool::UserDefined::xmpStruct{$strTable} or next; + $$strTable{STRUCT_NAME} or $$strTable{STRUCT_NAME} = "XMP $strName"; + $$tagInfo{Struct} = $strTable; # replace old-style name with HASH ref + delete $$tagInfo{SubDirectory}; # deprecated use of SubDirectory in Struct tags + } + + # get prefix for flattened tag names + my $flat = (defined $$tagInfo{FlatName} ? $$tagInfo{FlatName} : $$tagInfo{Name}); + + # get family 2 group name for this structure tag + my ($tagG2, $field); + $tagG2 = $$tagInfo{Groups}{2} if $$tagInfo{Groups}; + $tagG2 or $tagG2 = $$tagTablePtr{GROUPS}{2}; + + foreach $field (keys %$strTable) { + next if $specialStruct{$field}; + my $fieldInfo = $$strTable{$field}; + next if $$fieldInfo{LangCode}; # don't flatten lang-alt tags + next if $$fieldInfo{Struct} and $noSubStruct; # don't expand sub-structures if specified + # build a tag ID for the corresponding flattened tag + my $fieldName = ucfirst($field); + my $flatField = $$fieldInfo{FlatName} || $fieldName; + my $flatID = $tagID . $fieldName; + my $flatInfo = $$tagTablePtr{$flatID}; + if ($flatInfo) { + ref $flatInfo eq 'HASH' or warn("$flatInfo is not a HASH!\n"), next; # (to be safe) + # pre-defined flattened tags should have Flat flag set + if (not defined $$flatInfo{Flat}) { + next if $$flatInfo{NotFlat}; + warn "Missing Flat flag for $$flatInfo{Name}\n" if $Image::ExifTool::debug; + } + $$flatInfo{Flat} = 0; + # copy all missing entries from field information + foreach (keys %$fieldInfo) { + # must not copy PropertyPath (but can't delete it afterwards + # because the flat tag may already have this set) + next if $_ eq 'PropertyPath' or defined $$flatInfo{$_}; + # copy the property (making a copy of the Groups hash) + $$flatInfo{$_} = $_ eq 'Groups' ? { %{$$fieldInfo{$_}} } : $$fieldInfo{$_}; + } + # (NOTE: Can NOT delete Groups because we need them if GotGroups was done) + # re-generate List flag unless it is set to 0 + delete $$flatInfo{List} if $$flatInfo{List}; + } else { + # generate new flattened tag information based on structure field + my $flatName = $flat . $flatField; + $flatInfo = { %$fieldInfo, Name => $flatName, Flat => 0 }; + $$flatInfo{FlatName} = $flatName if $$fieldInfo{FlatName}; + # make a copy of the Groups hash if necessary + $$flatInfo{Groups} = { %{$$fieldInfo{Groups}} } if $$fieldInfo{Groups}; + # add new flattened tag to table + AddTagToTable($tagTablePtr, $flatID, $flatInfo); + ++$count; + } + # propagate List flag (unless set to 0 in pre-defined flattened tag) + unless (defined $$flatInfo{List}) { + $$flatInfo{List} = $$fieldInfo{List} || 1 if $$fieldInfo{List} or $$tagInfo{List}; + } + # set group 2 name from the first existing family 2 group in the: + # 1) structure field Groups, 2) structure table GROUPS, 3) structure tag Groups + if ($$fieldInfo{Groups} and $$fieldInfo{Groups}{2}) { + $$flatInfo{Groups}{2} = $$fieldInfo{Groups}{2}; + } elsif ($$strTable{GROUPS} and $$strTable{GROUPS}{2}) { + $$flatInfo{Groups}{2} = $$strTable{GROUPS}{2}; + } else { + $$flatInfo{Groups}{2} = $tagG2; + } + # save reference to top-level and parent structures + $$flatInfo{RootTagInfo} = $$tagInfo{RootTagInfo} || $tagInfo; + $$flatInfo{ParentTagInfo} = $tagInfo; + # recursively generate flattened tags for sub-structures + next unless $$flatInfo{Struct}; + length($flatID) > 250 and warn("Possible deep recursion for tag $flatID\n"), last; + # reset flattened tag just in case we flattened hierarchy in the wrong order + # because we must start from the outtermost structure to get the List flags right + # (this should only happen when building tag tables) + delete $$flatInfo{Flattened}; + $count += AddFlattenedTags($tagTablePtr, $flatID, $$flatInfo{NoSubStruct}); + } + } + return $count; +} + +#------------------------------------------------------------------------------ +# Get localized version of tagInfo hash +# Inputs: 0) tagInfo hash ref, 1) language code (eg. "x-default") +# Returns: new tagInfo hash ref, or undef if invalid +sub GetLangInfo($$) +{ + my ($tagInfo, $langCode) = @_; + # only allow alternate language tags in lang-alt lists + return undef unless $$tagInfo{Writable} and $$tagInfo{Writable} eq 'lang-alt'; + $langCode =~ tr/_/-/; # RFC 3066 specifies '-' as a separator + my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode); + return $langInfo; +} + +#------------------------------------------------------------------------------ +# Get standard case for language code +# Inputs: 0) Language code +# Returns: Language code in standard case +sub StandardLangCase($) +{ + my $lang = shift; + # make 2nd subtag uppercase only if it is 2 letters + return lc($1) . uc($2) . lc($3) if $lang =~ /^([a-z]{2,3}|[xi])(-[a-z]{2})\b(.*)/i; + return lc($lang); +} + +#------------------------------------------------------------------------------ +# Scan for XMP in a file +# Inputs: 0) ExifTool object ref, 1) RAF reference +# Returns: 1 if xmp was found, 0 otherwise +# Notes: Currently only recognizes UTF8-encoded XMP +sub ScanForXMP($$) +{ + my ($et, $raf) = @_; + my ($buff, $xmp); + my $lastBuff = ''; + + $et->VPrint(0,"Scanning for XMP\n"); + for (;;) { + defined $buff or $raf->Read($buff, 65536) or return 0; + unless (defined $xmp) { + $lastBuff .= $buff; + unless ($lastBuff =~ /(<\?xpacket begin=)/g) { + # must keep last 15 bytes to match 16-byte "xpacket begin" string + $lastBuff = length($buff) <= 15 ? $buff : substr($buff, -15); + undef $buff; + next; + } + $xmp = $1; + $buff = substr($lastBuff, pos($lastBuff)); + } + my $pos = length($xmp) - 18; # (18 = length("<?xpacket end...") - 1) + $xmp .= $buff; # add new data to our XMP + pos($xmp) = $pos if $pos > 0; # set start for "xpacket end" scan + if ($xmp =~ /<\?xpacket end=['"][wr]['"]\?>/g) { + $buff = substr($xmp, pos($xmp)); # save data after end of XMP + $xmp = substr($xmp, 0, pos($xmp)); # isolate XMP + # check XMP for validity (not valid if it contains null bytes) + $pos = rindex($xmp, "\0") + 1 or last; + $lastBuff = substr($xmp, $pos); # re-parse beginning after last null byte + undef $xmp; + } else { + undef $buff; + } + } + unless ($$et{FileType}) { + $$et{FILE_TYPE} = $$et{FILE_EXT}; + $et->SetFileType('<unknown file containing XMP>', undef, ''); + } + my %dirInfo = ( + DataPt => \$xmp, + DirLen => length $xmp, + DataLen => length $xmp, + ); + ProcessXMP($et, \%dirInfo); + return 1; +} + +#------------------------------------------------------------------------------ +# Print conversion for XMP-aux:LensID +# Inputs: 0) ExifTool ref, 1) LensID, 2) Make, 3) LensInfo, 4) FocalLength, +# 5) LensModel, 6) MaxApertureValue +# (yes, this is ugly -- blame Adobe) +sub PrintLensID(@) +{ + local $_; + my ($et, $id, $make, $info, $focalLength, $lensModel, $maxAv) = @_; + my ($mk, $printConv); + my %alt = ( Pentax => 'Ricoh' ); # Pentax changed its name to Ricoh + # missing: Olympus (no XMP:LensID written by Adobe) + foreach $mk (qw(Canon Nikon Pentax Sony Sigma Samsung Leica)) { + next unless $make =~ /$mk/i or ($alt{$mk} and $make =~ /$alt{$mk}/i); + # get name of module containing the lens lookup (default "Make.pm") + my $mod = { Sigma => 'SigmaRaw', Leica => 'Panasonic' }->{$mk} || $mk; + require "Image/ExifTool/$mod.pm"; + # get the name of the lens name lookup (default "makeLensTypes") + # (canonLensTypes, pentaxLensTypes, nikonLensIDs, etc) + my $convName = "Image::ExifTool::${mod}::" . + ({ Nikon => 'nikonLensIDs' }->{$mk} || lc($mk) . 'LensTypes'); + no strict 'refs'; + %$convName or last; + my $printConv = \%$convName; + use strict 'refs'; + # sf = short focal + # lf = long focal + # sa = max aperture at short focal + # la = max aperture at long focal + my ($sf, $lf, $sa, $la); + if ($info) { + my @a = split ' ', $info; + $_ eq 'undef' and $_ = undef foreach @a; + ($sf, $lf, $sa, $la) = @a; + # for Sony and ambiguous LensID, $info data may be incorrect: + # use only if it agrees with $focalLength and $maxAv (ref JR) + if ($mk eq 'Sony' and + (($focalLength and (($sf and $focalLength < $sf - 0.5) or + ($lf and $focalLength > $lf + 0.5))) or + ($maxAv and (($sa and $maxAv < $sa - 0.15) or + ($la and $maxAv > $la + 0.15))))) + { + undef $sf; + undef $lf; + undef $sa; + undef $la; + } elsif ($maxAv) { + # (using the short-focal-length max aperture in place of MaxAperture + # is a bad approximation, so don't do this if MaxApertureValue exists) + undef $sa; + } + } + if ($mk eq 'Pentax' and $id =~ /^\d+$/) { + # for Pentax, CS4 stores an int16u, but we use 2 x int8u + $id = join(' ', unpack('C*', pack('n', $id))); + } + # Nikon is a special case because Adobe doesn't store the full LensID + # (Apple Photos does, but we have to convert back to hex) + if ($mk eq 'Nikon') { + $id = sprintf('%X', $id); + $id = "0$id" if length($id) & 0x01; # pad with leading 0 if necessary + $id =~ s/(..)/$1 /g and $id =~ s/ $//; # put spaces between bytes + my (%newConv, %used); + my $i = 0; + foreach (grep /^$id/, keys %$printConv) { + my $lens = $$printConv{$_}; + next if $used{$lens}; # avoid duplicates + $used{$lens} = 1; + $newConv{$i ? "$id.$i" : $id} = $lens; + ++$i; + } + $printConv = \%newConv; + } + my $str = $$printConv{$id} || "Unknown ($id)"; + return Image::ExifTool::Exif::PrintLensID($et, $str, $printConv, + undef, $id, $focalLength, $sa, $maxAv, $sf, $lf, $lensModel); + } + return "Unknown ($id)"; +} + +#------------------------------------------------------------------------------ +# Convert XMP date/time to EXIF format +# Inputs: 0) XMP date/time string, 1) set if we aren't sure this is a date +# Returns: EXIF date/time +sub ConvertXMPDate($;$) +{ + my ($val, $unsure) = @_; + if ($val =~ /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}:\d{2})(:\d{2})?\s*(\S*)$/) { + my $s = $5 || ''; # seconds may be missing + $val = "$1:$2:$3 $4$s$6"; # convert back to EXIF time format + } elsif (not $unsure and $val =~ /^(\d{4})(-\d{2}){0,2}/) { + $val =~ tr/-/:/; + } + return $val; +} + +#------------------------------------------------------------------------------ +# Convert rational string value +# Inputs: 0) string (converted to number, 'inf' or 'undef' on return if rational) +# Returns: true if value was converted +sub ConvertRational($) +{ + my $val = $_[0]; + $val =~ m{^(-?\d+)/(-?\d+)$} or return undef; + if ($2 != 0) { + $_[0] = $1 / $2; # calculate quotient + } elsif ($1) { + $_[0] = 'inf'; + } else { + $_[0] = 'undef'; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Convert a string of floating point values to rationals +# Inputs: 0) string of floating point numbers separated by spaces +# Returns: string of rational numbers separated by spaces +sub ConvertRationalList($) +{ + my $val = shift; + my @vals = split ' ', $val; + return $val unless @vals == 4; + foreach (@vals) { + ConvertRational($_) or return $val; + } + return join ' ', @vals; +} + +#------------------------------------------------------------------------------ +# We found an XMP property name/value +# Inputs: 0) ExifTool object ref, 1) Pointer to tag table +# 2) reference to array of XMP property names (last is current property) +# 3) property value, 4) attribute hash ref (for 'xml:lang' or 'rdf:datatype') +# Returns: 1 if valid tag was found +sub FoundXMP($$$$;$) +{ + local $_; + my ($et, $tagTablePtr, $props, $val, $attrs) = @_; + my ($lang, @structProps, $rawVal, $rational); + my ($tag, $ns) = GetXMPTagID($props, $$et{OPTIONS}{Struct} ? \@structProps : undef); + return 0 unless $tag; # ignore things that aren't valid tags + + # translate namespace if necessary + $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns}; + my $info = $$tagTablePtr{$ns}; + my ($table, $added, $xns, $tagID); + if ($info) { + $table = $$info{SubDirectory}{TagTable} or warn "Missing TagTable for $tag!\n"; + } elsif ($$props[0] eq 'svg:svg') { + if (not $ns) { + # disambiguate MetadataID by adding back the 'metadata' we ignored + $tag = 'metadataId' if $tag eq 'id' and $$props[1] eq 'svg:metadata'; + # use SVG namespace in SVG files if nothing better to use + $table = 'Image::ExifTool::XMP::SVG'; + } elsif (not grep /^rdf:/, @$props) { + # only other SVG information if not inside RDF (call it XMP if in RDF) + $table = 'Image::ExifTool::XMP::otherSVG'; + } + } + + my $xmlGroups; + my $grp0 = $$tagTablePtr{GROUPS}{0}; + if (not $ns and $grp0 ne 'XMP') { + $tagID = $tag; + } elsif ($grp0 eq 'XML' and not $table) { + # this is an XML table (no namespace lookup) + $tagID = "$ns:$tag"; + } else { + $xmlGroups = 1 if $grp0 eq 'XML'; + # look up this tag in the appropriate table + $table or $table = 'Image::ExifTool::XMP::other'; + $tagTablePtr = GetTagTable($table); + if ($$tagTablePtr{NAMESPACE}) { + $tagID = $tag; + } else { + $xns = $xmpNS{$ns}; + unless (defined $xns) { + $xns = $ns; + # validate namespace prefix + unless ($ns =~ /^[A-Z_a-z\x80-\xff][-.0-9A-Z_a-z\x80-\xff]*$/ or $ns eq '') { + $et->Warn("Invalid XMP namespace prefix '${ns}'"); + # clean up prefix for use as an ExifTool group name + $ns =~ tr/-.0-9A-Z_a-z\x80-\xff//dc; + $ns =~ /^[A-Z_a-z\x80-\xff]/ or $ns = "ns_$ns"; + $stdXlatNS{$xns} = $ns; + $xmpNS{$ns} = $xns; + } + } + # add XMP namespace prefix to avoid collisions in variable-namespace tables + $tagID = "$xns:$tag"; + # add namespace to top-level structure property + $structProps[0][0] = "$xns:" . $structProps[0][0] if @structProps; + } + } + my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID); + + $lang = $$attrs{'xml:lang'} if $attrs; + + # must add a new tag table entry if this tag isn't pre-defined + # (or initialize from structure field if this is a pre-defined flattened tag) +NoLoop: + while (not $tagInfo or $$tagInfo{Flat}) { + my (@tagList, @nsList); + GetXMPTagID($props, \@tagList, \@nsList); + my ($ta, $t, $ti, $addedFlat, $i, $j); + # build tag ID strings for each level in the property path + foreach $ta (@tagList) { + # insert tag ID in index 1 of tagList list + $t = $$ta[1] = $t ? $t . ucfirst($$ta[0]) : $$ta[0]; + # generate flattened tags for top-level structure if necessary + next if defined $addedFlat; + $ti = $$tagTablePtr{$t} or next; + next unless ref $ti eq 'HASH' and $$ti{Struct}; + $addedFlat = AddFlattenedTags($tagTablePtr, $t); + # all done if we generated the tag we are looking for + $tagInfo = $$tagTablePtr{$tagID} and last NoLoop if $addedFlat; + } + my $name = ucfirst($tag); + + # search for the innermost containing structure + # (in case tag is an unknown field in a known structure) + # (only necessary if we found a structure above) + if (defined $addedFlat) { + my $t2 = ''; + for ($i=$#tagList-1; $i>=0; --$i) { + $t = $tagList[$i][1]; + $t2 = $tagList[$i+1][0] . ucfirst($t2); # build relative tag id + $ti = $$tagTablePtr{$t} or next; + next unless ref $ti eq 'HASH'; + my $strTable = $$ti{Struct} or next; + my $flat = (defined $$ti{FlatName} ? $$ti{FlatName} : $$ti{Name}); + $name = $flat . ucfirst($t2); + # don't continue if structure is known but field is not + last if $$strTable{NAMESPACE} or not exists $$strTable{NAMESPACE}; + # this is a variable-namespace structure, so we must: + # 1) get tagInfo from corresponding top-level XMP tag if it exists + # 2) add new entry in this tag table, but with namespace prefix on tag ID + my $n = $nsList[$i+1]; # namespace of structure field + # translate to standard ExifTool namespace + $n = $stdXlatNS{$n} if $stdXlatNS{$n}; + my $xn = $xmpNS{$n} || $n; # standard XMP namespace + # no need to continue with variable-namespace logic if + # we are in our own namespace (right?) + last if $xn eq ($$tagTablePtr{NAMESPACE} || ''); + $tagID = "$xn:$tag"; # add namespace to avoid collisions + # change structure properties to add the standard XMP namespace + # prefix for this field (needed for variable-namespace fields) + if (@structProps) { + $structProps[$i+1][0] = "$xn:" . $structProps[$i+1][0]; + } + # copy tagInfo entries from the existing top-level XMP tag + my $tg = $Image::ExifTool::XMP::Main{$n}; + last unless ref $tg eq 'HASH' and $$tg{SubDirectory}; + my $tbl = GetTagTable($$tg{SubDirectory}{TagTable}) or last; + my $sti = $et->GetTagInfo($tbl, $t2); + if (not $sti or $$sti{Flat}) { + # again, we must initialize flattened tags if necessary + # (but don't bother to recursively apply full logic to + # allow nested variable-namespace strucures until someone + # actually wants to do such a silly thing) + my $t3 = ''; + for ($j=$i+1; $j<@tagList; ++$j) { + $t3 = $tagList[$j][0] . ucfirst($t3); + my $ti3 = $$tbl{$t3} or next; + next unless ref $ti3 eq 'HASH' and $$ti3{Struct}; + last unless AddFlattenedTags($tbl, $t3); + $sti = $$tbl{$t2}; + last; + } + last unless $sti; + } + # generate new tagInfo hash based on existing top-level tag + $tagInfo = { %$sti, Name => $flat . $$sti{Name} }; + # be careful not to copy elements we shouldn't... + delete $$tagInfo{Description}; # Description will be different + # can't copy group hash because group 1 will be different and + # we need to check this when writing tag to a specific group + delete $$tagInfo{Groups}; + $$tagInfo{Groups}{2} = $$sti{Groups}{2} if $$sti{Groups}; + last; + } + } + # generate a default tagInfo hash if necessary + unless ($tagInfo) { + # shorten tag name if necessary + if ($$et{ShortenXmpTags}) { + my $shorten = $$et{ShortenXmpTags}; + $name = &$shorten($name); + } + $tagInfo = { Name => $name, IsDefault => 1, Priority => 0 }; + } + # add tag Namespace entry for tags in variable-namespace tables + $$tagInfo{Namespace} = $xns if $xns; + if ($$et{curURI}{$ns} and $$et{curURI}{$ns} =~ m{^http://ns.exiftool.(?:ca|org)/(.*?)/(.*?)/}) { + my %grps = ( 0 => $1, 1 => $2 ); + # apply a little magic to recover original group names + # from this exiftool-written RDF/XML file + if ($grps{1} eq 'System') { + $grps{1} = 'XML-System'; + $grps{0} = 'XML'; + } elsif ($grps{1} =~ /^\d/) { + # URI's with only family 0 are internal tags from the source file, + # so change the group name to avoid confusion with tags from this file + $grps{1} = "XML-$grps{0}"; + $grps{0} = 'XML'; + } + $$tagInfo{Groups} = \%grps; + # flag to avoid setting group 1 later + $$tagInfo{StaticGroup1} = 1; + } + # construct tag information for this unknown tag + # -> make this a List or lang-alt tag if necessary + if (@$props > 2 and $$props[-1] =~ /^rdf:li \d+$/ and + $$props[-2] =~ /^rdf:(Bag|Seq|Alt)$/) + { + if ($lang and $1 eq 'Alt') { + $$tagInfo{Writable} = 'lang-alt'; + } else { + $$tagInfo{List} = $1; + } + # tried this, but maybe not a good idea for complex structures: + #} elsif (grep / /, @$props) { + # $$tagInfo{List} = 1; + } + # save property list for verbose "adding" message unless this tag already exists + $added = \@tagList unless $$tagTablePtr{$tagID}; + # if this is an empty structure, we must add a Struct field + if (not length $val and $$attrs{'rdf:parseType'} and $$attrs{'rdf:parseType'} eq 'Resource') { + $$tagInfo{Struct} = { STRUCT_NAME => 'XMP Unknown' }; + } + AddTagToTable($tagTablePtr, $tagID, $tagInfo); + last; + } + # decode value if necessary (et:encoding was used before exiftool 7.71) + if ($attrs) { + my $enc = $$attrs{'rdf:datatype'} || $$attrs{'et:encoding'}; + if ($enc and $enc =~ /base64/) { + $val = DecodeBase64($val); # (now a value ref) + $val = $$val unless length $$val > 100 or $$val =~ /[\0-\x08\x0b\0x0c\x0e-\x1f]/; + } + } + if (defined $lang and lc($lang) ne 'x-default') { + $lang = StandardLangCase($lang); + my $langInfo = GetLangInfo($tagInfo, $lang); + $tagInfo = $langInfo if $langInfo; + } + # un-escape XML character entities (handling CDATA) + pos($val) = 0; + if ($val =~ /<!\[CDATA\[(.*?)\]\]>/sg) { + my $p = pos $val; + # unescape everything up to the start of the CDATA section + # (the length of "<[[CDATA[]]>" is 12 characters) + my $v = UnescapeXML(substr($val, 0, $p - length($1) - 12)) . $1; + while ($val =~ /<!\[CDATA\[(.*?)\]\]>/sg) { + my $p1 = pos $val; + $v .= UnescapeXML(substr($val, $p, $p1 - length($1) - 12)) . $1; + $p = $p1; + } + $val = $v . UnescapeXML(substr($val, $p)); + } else { + $val = UnescapeXML($val); + } + # decode from UTF8 + $val = $et->Decode($val, 'UTF8'); + # convert rational and date values to a more sensible format + my $fmt = $$tagInfo{Writable}; + my $new = $$tagInfo{IsDefault} && $$et{OPTIONS}{XMPAutoConv}; + if ($fmt or $new) { + $rawVal = $val; # save raw value for verbose output + if (($new or $fmt eq 'rational') and ConvertRational($val)) { + $rational = $rawVal; + } else { + $val = ConvertXMPDate($val, $new) if $new or $fmt eq 'date'; + } + if ($$et{XmpValidate} and $fmt and $fmt eq 'boolean' and $val!~/^True|False$/) { + if ($val =~ /^true|false$/) { + $et->WarnOnce("Boolean value for XMP-$ns:$$tagInfo{Name} should be capitalized",1); + } else { + $et->WarnOnce(qq(Boolean value for XMP-$ns:$$tagInfo{Name} should be "True" or "False"),1); + } + } + # protect against large binary data in unknown tags + $$tagInfo{Binary} = 1 if $new and length($val) > 65536; + } + # store the value for this tag + my $key = $et->FoundTag($tagInfo, $val) or return 0; + # save original components of rational numbers (used when copying) + $$et{RATIONAL}{$key} = $rational if defined $rational; + # save structure/list information if necessary + if (@structProps and (@structProps > 1 or defined $structProps[0][1]) and + not $$et{NO_STRUCT}) + { + $$et{TAG_EXTRA}{$key}{Struct} = \@structProps; + $$et{IsStruct} = 1; + } + if ($xmlGroups) { + $et->SetGroup($key, 'XML', 0); + $et->SetGroup($key, "XML-$ns", 1); + } elsif ($ns and not $$tagInfo{StaticGroup1}) { + # set group1 dynamically according to the namespace + $et->SetGroup($key, "$$tagTablePtr{GROUPS}{0}-$ns"); + } + if ($$et{OPTIONS}{Verbose}) { + if ($added) { + my $props; + if (@$added > 1) { + $$tagInfo{Flat} = 0; # this is a flattened tag + my @props = map { $$_[0] } @$added; + $props = ' (' . join('/',@props) . ')'; + } else { + $props = ''; + } + my $g1 = $et->GetGroup($key, 1); + $et->VPrint(0, $$et{INDENT}, "[adding $g1:$tag]$props\n"); + } + my $tagID = join('/',@$props); + $et->VerboseInfo($tagID, $tagInfo, Value => $rawVal || $val); + } + # allow read-only subdirectories (eg. embedded base64 XMP/IPTC in NKSC files) + if ($$tagInfo{SubDirectory} and not $$et{IsWriting}) { + my $subdir = $$tagInfo{SubDirectory}; + my $dataPt = ref $$et{VALUE}{$key} ? $$et{VALUE}{$key} : \$$et{VALUE}{$key}; + # decode if necessary (eg. Nikon XMP-ast:XMLPackets) + $dataPt = DecodeBase64($$dataPt) if $$tagInfo{Encoding} and $$tagInfo{Encoding} eq 'Base64'; + # process subdirectory information + my %dirInfo = ( + DirName => $$subdir{DirName} || $$tagInfo{Name}, + DataPt => $dataPt, + DirLen => length $$dataPt, + IgnoreProp => $$subdir{IgnoreProp}, # (allow XML to ignore specified properties) + IsExtended => 1, # (hack to avoid Duplicate warning for embedded XMP) + NoStruct => 1, # (don't try to build structures since this isn't true XMP) + ); + my $oldOrder = GetByteOrder(); + SetByteOrder($$subdir{ByteOrder}) if $$subdir{ByteOrder}; + my $oldNS = $$et{definedNS}; + delete $$et{definedNS}; + my $subTablePtr = GetTagTable($$subdir{TagTable}) || $tagTablePtr; + $et->ProcessDirectory(\%dirInfo, $subTablePtr, $$subdir{ProcessProc}); + SetByteOrder($oldOrder); + $$et{definedNS} = $oldNS; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Recursively parse nested XMP data element +# Inputs: 0) ExifTool ref, 1) tag table ref, 2) XMP data ref +# 3) offset to start of XMP element, 4) offset to end of XMP element +# 5) reference to array of enclosing XMP property names (undef if none) +# 6) reference to blank node information hash +# Returns: Number of contained XMP elements +sub ParseXMPElement($$$;$$$$) +{ + local $_; + my ($et, $tagTablePtr, $dataPt, $start, $end, $propList, $blankInfo) = @_; + my ($count, $nItems) = (0, 0); + my $isWriting = $$et{XMP_CAPTURE}; + my $isSVG = $$et{XMP_IS_SVG}; + my $saveNS; # save xlatNS lookup if changed for the scope of this element + my (%definedNS, %usedNS); # namespaces defined and used in this scope + + # get our parse procs + my ($attrProc, $foundProc); + if ($$et{XMPParseOpts}) { + $attrProc = $$et{XMPParseOpts}{AttrProc}; + $foundProc = $$et{XMPParseOpts}{FoundProc} || \&FoundXMP; + } else { + $foundProc = \&FoundXMP; + } + $start or $start = 0; + $end or $end = length $$dataPt; + $propList or $propList = [ ]; + + my $processBlankInfo; + # create empty blank node information hash if necessary + $blankInfo or $blankInfo = $processBlankInfo = { Prop => { } }; + # keep track of current nodeID at this nesting level + my $oldNodeID = $$blankInfo{NodeID}; + pos($$dataPt) = $start; + + # lookup for translating namespace prefixes + my $xlatNS = $$et{xlatNS}; + + Element: for (;;) { + # all done if there isn't enough data for another element + # (the smallest possible element is 4 bytes, eg. "<a/>") + last if pos($$dataPt) > $end - 4; + # reset nodeID before processing each element + my $nodeID = $$blankInfo{NodeID} = $oldNodeID; + # get next element + last if $$dataPt !~ m{<([?/]?)([-\w:.\x80-\xff]+|!--)([^>]*)>}sg or pos($$dataPt) > $end; + # (the only reason we match '<[?/]' is to keep from scanning past the + # "<?xpacket end..." terminator or other closing token, so + next if $1; + my ($prop, $attrs) = ($2, $3); + # skip comments + if ($prop eq '!--') { + next if $attrs =~ /--$/ or $$dataPt =~ /-->/sg; + last; + } + my $valStart = pos($$dataPt); + my $valEnd; + # only look for closing token if this is not an empty element + # (empty elements end with '/', eg. <a:b/>) + if ($attrs !~ s/\/$//) { + my $nesting = 1; + for (;;) { +# this match fails with perl 5.6.2 (perl bug!), but it works without +# the '(.*?)', so we must do it differently... +# $$dataPt =~ m/(.*?)<\/$prop>/sg or last Element; +# my $val2 = $1; + # find next matching closing token, or the next opening token + # of a nested same-named element + if ($$dataPt !~ m{<(/?)$prop([-\w:.\x80-\xff]*)(.*?(/?))>}sg or + pos($$dataPt) > $end) + { + $et->Warn("XMP format error (no closing tag for $prop)"); + last Element; + } + next if $2; # ignore opening properties with different names + if ($1) { + next if --$nesting; + $valEnd = pos($$dataPt) - length($prop) - length($3) - 3; + last; # this element is complete + } + # this is a nested opening token (or empty element) + ++$nesting unless $4; + } + } else { + $valEnd = $valStart; + } + $start = pos($$dataPt); # start from here the next time around + + # ignore specified XMP namespaces/properties + if ($$et{EXCL_XMP_LOOKUP} and not $isWriting and $prop =~ /^(.+):(.*)/) { + my ($ns, $nm) = (lc($stdXlatNS{$1} || $1), lc($2)); + if ($$et{EXCL_XMP_LOOKUP}{"xmp-$ns:all"} or $$et{EXCL_XMP_LOOKUP}{"xmp-$ns:$nm"} or + $$et{EXCL_XMP_LOOKUP}{"xmp-all:$nm"}) + { + ++$count; # (pretend we found something so we don't store as a tag value) + next; + } + } + + # extract property attributes + my ($parseResource, %attrs, @attrs); + while ($attrs =~ m/(\S+?)\s*=\s*(['"])(.*?)\2/sg) { + my ($attr, $val) = ($1, $3); + # handle namespace prefixes (defined by xmlns:PREFIX, or used with PREFIX:tag) + if ($attr =~ /(.*?):/) { + if ($1 eq 'xmlns') { + my $ns = substr($attr, 6); + my $stdNS = $uri2ns{$val}; + # keep track of namespace prefixes defined in this scope (for Validate) + $$et{definedNS}{$ns} = $definedNS{$ns} = 1 unless $$et{definedNS}{$ns}; + unless ($stdNS) { + my $try = $val; + # patch for Nikon NX2 URI bug for Microsoft PhotoInfo namespace + $try =~ s{/$}{} or $try .= '/'; + $stdNS = $uri2ns{$try}; + if ($stdNS) { + $val = $try; + $et->WarnOnce("Fixed incorrect URI for xmlns:$ns", 1); + } else { + # look for same namespace with different version number + $try = quotemeta $val; # (note: escapes slashes too) + $try =~ s{\\/\d+\\\.\d+(\\/|$)}{\\/\\d+\\\.\\d+$1}; + my ($good) = grep /^$try$/, keys %uri2ns; + if ($good) { + $stdNS = $uri2ns{$good}; + $et->VPrint(0, $$et{INDENT}, "[different $stdNS version: $val]\n"); + } + } + } + # tame wild namespace prefixes (patches Microsoft stupidity) + my $newNS; + if ($stdNS) { + # use standard namespace prefix if pre-defined + if ($stdNS ne $ns) { + $newNS = $stdNS; + } elsif ($$xlatNS{$ns}) { + # this prefix is re-defined to the standard prefix in this scope + $newNS = ''; + } + } elsif ($$et{curNS}{$val}) { + # use a consistent prefix over the entire XMP for a given namespace URI + $newNS = $$et{curNS}{$val} if $$et{curNS}{$val} ne $ns; + } else { + my $curURI = $$et{curURI}; + my $curNS = $$et{curNS}; + my $usedNS = $ns; + # use unique prefixes for all namespaces across the entire XMP + if ($$curURI{$ns} or $nsURI{$ns}) { + # generate a temporary namespace prefix to resolve any conflict + my $i = 0; + ++$i while $$curURI{"tmp$i"}; + $newNS = $usedNS = "tmp$i"; + } + # keep track of the namespace prefixes and URI's used in this XMP + $$curNS{$val} = $usedNS; + $$curURI{$usedNS} = $val; + } + if (defined $newNS) { + # save translation used in containing scope if necessary + # create new namespace translation for the scope of this element + $saveNS or $saveNS = $xlatNS, $xlatNS = $$et{xlatNS} = { %$xlatNS }; + if (length $newNS) { + # use the new namespace prefix + $$xlatNS{$ns} = $newNS; + $attr = 'xmlns:' . $newNS; + # must go through previous attributes and change prefixes if necessary + foreach (@attrs) { + next unless /(.*?):/ and $1 eq $ns and $1 ne $newNS; + my $newAttr = $newNS . substr($_, length($ns)); + $attrs{$newAttr} = $attrs{$_}; + delete $attrs{$_}; + $_ = $newAttr; + } + } else { + delete $$xlatNS{$ns}; + } + } + } else { + $attr = $$xlatNS{$1} . substr($attr, length($1)) if $$xlatNS{$1}; + $usedNS{$1} = 1; + } + } + push @attrs, $attr; # preserve order + $attrs{$attr} = $val; + } + if ($prop =~ /(.*?):/) { + $usedNS{$1} = 1; + # tame wild namespace prefixes (patch for Microsoft stupidity) + $prop = $$xlatNS{$1} . substr($prop, length($1)) if $$xlatNS{$1}; + } + + if ($prop eq 'rdf:li') { + # impose a reasonable maximum on the number of items in a list + if ($nItems == 1000) { + my ($tg,$ns) = GetXMPTagID($propList); + if ($isWriting) { + $et->WarnOnce("Excessive number of items for $ns:$tg. Processing may be slow", 1); + } elsif (not $$et{OPTIONS}{IgnoreMinorErrors}) { + $et->WarnOnce("Extracted only 1000 $ns:$tg items. Ignore minor errors to extract all", 2); + last; + } + } + # add index to list items so we can keep them in order + # (this also enables us to keep structure elements grouped properly + # for lists of structures, like JobRef) + # Note: the list index is prefixed by the number of digits so sorting + # alphabetically gives the correct order while still allowing a flexible + # number of digits -- this scheme allows up to 9 digits in the index, + # with index numbers ranging from 0 to 999999999. The sequence is: + # 10,11,12-19,210,211-299,3100,3101-3999,41000...9999999999. + $prop .= ' ' . length($nItems) . $nItems; + # reset LIST_TAGS at the start of the outtermost list + # (avoids accumulating incorrectly-written elements in a correctly-written list) + if (not $nItems and not grep /^rdf:li /, @$propList) { + $$et{LIST_TAGS} = { }; + } + ++$nItems; + } elsif ($prop eq 'rdf:Description') { + # remove unnecessary rdf:Description elements since parseType='Resource' + # is more efficient (also necessary to make property path consistent) + if (grep /^rdf:Description$/, @$propList) { + $parseResource = 1; + # set parseType so we know this is a structure + $attrs{'rdf:parseType'} = 'Resource'; + } + } elsif ($prop eq 'xmp:xmpmeta') { + # patch MicrosoftPhoto unconformity + $prop = 'x:xmpmeta'; + $et->Warn('Wrong namespace for xmpmeta') if $$et{XmpValidate}; + } + + # hook for special parsing of attributes + my $val; + if ($attrProc) { + $val = substr($$dataPt, $valStart, $valEnd - $valStart); + if (&$attrProc(\@attrs, \%attrs, \$prop, \$val)) { + # the value was changed, so reset $valStart/$valEnd to use $val instead + $valStart = $valEnd; + } + } + + # add nodeID to property path (with leading ' #') if it exists + if (defined $attrs{'rdf:nodeID'}) { + $nodeID = $$blankInfo{NodeID} = $attrs{'rdf:nodeID'}; + delete $attrs{'rdf:nodeID'}; + $prop .= ' #' . $nodeID; + undef $parseResource; # can't ignore if this is a node + } + + # push this property name onto our hierarchy list + push @$propList, $prop unless $parseResource; + + if ($isSVG) { + # ignore everything but top level SVG tags and metadata unless Unknown set + unless ($$et{OPTIONS}{Unknown} > 1 or $$et{OPTIONS}{Verbose}) { + if (@$propList > 1 and $$propList[1] !~ /\b(metadata|desc|title)$/) { + pop @$propList; + next; + } + } + if ($prop eq 'svg' or $prop eq 'metadata') { + # add svg namespace prefix if missing to ignore these entries in the tag name + $$propList[-1] = "svg:$prop"; + } + } elsif ($$et{XmpIgnoreProps}) { # ignore specified properties for tag name + foreach (@{$$et{XmpIgnoreProps}}) { + last unless @$propList; + pop @$propList if $_ eq $$propList[0]; + } + } + + # handle properties inside element attributes (RDF shorthand format): + # (attributes take the form a:b='c' or a:b="c") + my ($shortName, $shorthand, $ignored); + foreach $shortName (@attrs) { + next unless defined $attrs{$shortName}; + my $propName = $shortName; + my ($ns, $name); + if ($propName =~ /(.*?):(.*)/) { + $ns = $1; # specified namespace + $name = $2; + } elsif ($prop =~ /(\S*?):/) { + $ns = $1; # assume same namespace as parent + $name = $propName; + $propName = "$ns:$name"; # generate full property name + } else { + # a property qualifier is the only property name that may not + # have a namespace, and a qualifier shouldn't have attributes, + # but what the heck, let's allow this anyway + $ns = ''; + $name = $propName; + } + if ($propName eq 'rdf:about') { + if (not $$et{XmpAbout}) { + $$et{XmpAbout} = $attrs{$shortName}; + } elsif ($$et{XmpAbout} ne $attrs{$shortName}) { + if ($isWriting) { + my $str = "Different 'rdf:about' attributes not handled"; + unless ($$et{WARNED_ONCE}{$str}) { + $et->Error($str, 1); + $$et{WARNED_ONCE}{$str} = 1; + } + } elsif ($$et{XmpValidate}) { + $et->WarnOnce("Different 'rdf:about' attributes"); + } + } + } + if ($isWriting) { + # keep track of our namespaces when writing + if ($ns eq 'xmlns') { + my $stdNS = $uri2ns{$attrs{$shortName}}; + unless ($stdNS and ($stdNS eq 'x' or $stdNS eq 'iX')) { + my $nsUsed = $$et{XMP_NS}; + $$nsUsed{$name} = $attrs{$shortName} unless defined $$nsUsed{$name}; + } + delete $attrs{$shortName}; # (handled by namespace logic) + next; + } elsif ($recognizedAttrs{$propName}) { + next; + } + } + my $shortVal = $attrs{$shortName}; + # Note: $prop is the containing property in this loop (not the shorthand property) + # so $ignoreProp ignores all attributes of the ignored property + if ($ignoreNamespace{$ns} or $ignoreProp{$prop} or $ignoreEtProp{$propName}) { + $ignored = $propName; + # handle special attributes (extract as tags only once if not empty) + if (ref $recognizedAttrs{$propName} and $shortVal) { + my ($tbl, $id, $name) = @{$recognizedAttrs{$propName}}; + my $tval = UnescapeXML($shortVal); + unless (defined $$et{VALUE}{$name} and $$et{VALUE}{$name} eq $tval) { + $et->HandleTag(GetTagTable($tbl), $id, $tval); + } + } + next; + } + delete $attrs{$shortName}; # don't re-use this attribute + push @$propList, $propName; + # save this shorthand XMP property + if (defined $nodeID) { + SaveBlankInfo($blankInfo, $propList, $shortVal); + } elsif ($isWriting) { + CaptureXMP($et, $propList, $shortVal); + } else { + ValidateProperty($et, $propList) if $$et{XmpValidate}; + &$foundProc($et, $tagTablePtr, $propList, $shortVal); + } + pop @$propList; + $shorthand = 1; + } + if ($isWriting) { + if (ParseXMPElement($et, $tagTablePtr, $dataPt, $valStart, $valEnd, + $propList, $blankInfo)) + { + # (no value since we found more properties within this one) + # set an error on any ignored attributes here, because they will be lost + $$et{XMP_ERROR} = "Can't handle XMP attribute '${ignored}'" if $ignored; + } elsif (not $shorthand or $valEnd != $valStart) { + $val = substr($$dataPt, $valStart, $valEnd - $valStart); + # remove comments and whitespace from rdf:Description only + if ($prop eq 'rdf:Description') { + $val =~ s/<!--.*?-->//g; $val =~ s/^\s+//; $val =~ s/\s+$//; + } + if (defined $nodeID) { + SaveBlankInfo($blankInfo, $propList, $val, \%attrs); + } else { + CaptureXMP($et, $propList, $val, \%attrs); + } + } + } else { + # look for additional elements contained within this one + if ($valStart == $valEnd or + !ParseXMPElement($et, $tagTablePtr, $dataPt, $valStart, $valEnd, + $propList, $blankInfo)) + { + my $wasEmpty; + unless (defined $val) { + $val = substr($$dataPt, $valStart, $valEnd - $valStart); + # remove comments and whitespace from rdf:Description only + if ($prop eq 'rdf:Description' and $val) { + $val =~ s/<!--.*?-->//g; $val =~ s/^\s+//; $val =~ s/\s+$//; + } + # if element value is empty, take value from RDF 'value' or 'resource' attribute + # (preferentially) or 'about' attribute (if no 'value' or 'resource') + if ($val eq '' and ($attrs =~ /\brdf:(?:value|resource)=(['"])(.*?)\1/ or + $attrs =~ /\brdf:about=(['"])(.*?)\1/)) + { + $val = $2; + $wasEmpty = 1; + } + } + # there are no contained elements, so this must be a simple property value + # (unless we already extracted shorthand values from this element) + if (length $val or not $shorthand) { + my $lastProp = $$propList[-1]; + $lastProp = '' unless defined $lastProp; + if (defined $nodeID) { + SaveBlankInfo($blankInfo, $propList, $val); + } elsif ($lastProp eq 'rdf:type' and $wasEmpty) { + # do not extract empty structure types (for now) + } elsif ($lastProp =~ /^et:(desc|prt|val)$/ and ($count or $1 eq 'desc')) { + # ignore et:desc, and et:val if preceded by et:prt + --$count; + } else { + ValidateProperty($et, $propList, \%attrs) if $$et{XmpValidate}; + &$foundProc($et, $tagTablePtr, $propList, $val, \%attrs); + } + } + } + } + pop @$propList unless $parseResource; + ++$count; + + # validate namespace prefixes used at this level if necessary + if ($$et{XmpValidate}) { + foreach (sort keys %usedNS) { + next if $$et{definedNS}{$_} or $_ eq 'xml'; + if (defined $$et{definedNS}{$_}) { + $et->Warn("XMP namespace $_ is used out of scope"); + } else { + $et->Warn("Undefined XMP namespace: $_"); + } + $$et{definedNS}{$_} = -1; # (don't warn again for this namespace) + } + # reset namespaces that went out of scope + $$et{definedNS}{$_} = 0 foreach keys %definedNS; + undef %usedNS; + undef %definedNS; + } + + last if $start >= $end; + pos($$dataPt) = $start; + $$dataPt =~ /\G\s+/gc; # skip white space after closing token + } +# +# process resources referenced by blank nodeID's +# + if ($processBlankInfo and %{$$blankInfo{Prop}}) { + ProcessBlankInfo($et, $tagTablePtr, $blankInfo, $isWriting); + %$blankInfo = (); # free some memory + } + # restore namespace lookup from the containing scope + $$et{xlatNS} = $saveNS if $saveNS; + + return $count; # return the number of elements found at this level +} + +#------------------------------------------------------------------------------ +# Process XMP data +# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) Pointer to tag table +# Returns: 1 on success +# Notes: The following flavours of XMP files are currently recognized: +# - standard XMP with xpacket, x:xmpmeta and rdf:RDF elements +# - XMP that is missing the xpacket and/or x:xmpmeta elements +# - mutant Microsoft XMP with xmp:xmpmeta element +# - XML files beginning with "<xml" +# - SVG files that begin with "<svg" or "<!DOCTYPE svg" +# - XMP and XML files beginning with a UTF-8 byte order mark +# - UTF-8, UTF-16 and UTF-32 encoded XMP +# - erroneously double-UTF8 encoded XMP +# - otherwise valid files with leading XML comment +sub ProcessXMP($$;$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my ($dirStart, $dirLen, $dataLen, $double); + my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG); + my $rtnVal = 0; + my $bom = 0; + my $path = $et->MetadataPath(); + + # namespaces and prefixes currently in effect while parsing the file, + # and lookup to translate brain-dead-Microsoft-Photo-software prefixes + $$et{curURI} = { }; + $$et{curNS} = { }; + $$et{xlatNS} = { }; + $$et{definedNS} = { }; + delete $$et{XmpAbout}; + delete $$et{XmpValidate}; # don't validate by default + delete $$et{XmpValidateLangAlt}; + + # ignore non-standard XMP while in strict MWG compatibility mode + if (($Image::ExifTool::MWG::strict or $$et{OPTIONS}{Validate}) and + not ($$et{XMP_CAPTURE} or $$et{DOC_NUM}) and + (($$dirInfo{DirName} || '') eq 'XMP' or $$et{FILE_TYPE} eq 'XMP')) + { + $$et{XmpValidate} = { } if $$et{OPTIONS}{Validate}; + my $nonStd = ($stdPath{$$et{FILE_TYPE}} and $path ne $stdPath{$$et{FILE_TYPE}}); + if ($nonStd and $Image::ExifTool::MWG::strict) { + $et->Warn("Ignored non-standard XMP at $path"); + return 1; + } + if ($nonStd) { + $et->Warn("Non-standard XMP at $path", 1); + } elsif (not $$dirInfo{IsExtended}) { + $et->Warn("Duplicate XMP at $path") if $$et{DIR_COUNT}{XMP}; + $$et{DIR_COUNT}{XMP} = ($$et{DIR_COUNT}{XMP} || 0) + 1; # count standard XMP + } + } + if ($dataPt) { + $dirStart = $$dirInfo{DirStart} || 0; + $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart); + $dataLen = $$dirInfo{DataLen} || length($$dataPt); + # check leading BOM (may indicate double-encoded UTF) + pos($$dataPt) = $dirStart; + $double = $1 if $$dataPt =~ /\G((\0\0)?\xfe\xff|\xff\xfe(\0\0)?|\xef\xbb\xbf)\0*<\0*\?\0*x\0*p\0*a\0*c\0*k\0*e\0*t/g; + } else { + my ($type, $mime, $buf2, $buf3); + # read information from XMP file + my $raf = $$dirInfo{RAF} or return 0; + $raf->Read($buff, 256) or return 0; + ($buf2 = $buff) =~ tr/\0//d; # cheap conversion to UTF-8 + # remove leading comments if they exist (eg. ImageIngester) + while ($buf2 =~ /^\s*<!--/) { + # remove the comment if it is complete + if ($buf2 =~ s/^\s*<!--.*?-->\s+//s) { + # continue with parsing if we have more than 128 bytes remaining + next if length $buf2 > 128; + } else { + # don't read more than 10k when looking for the end of comment + return 0 if length($buf2) > 10000; + } + $raf->Read($buf3, 256) or last; # read more data if available + $buff .= $buf3; + $buf3 =~ tr/\0//d; + $buf2 .= $buf3; + } + # check to see if this is XMP format + # (CS2 writes .XMP files without the "xpacket begin") + if ($buf2 =~ /^\s*(<\?xpacket begin=|<x(mp)?:x[ma]pmeta)/) { + $hasXMP = 1; + } else { + # also recognize XML files and .XMP files with BOM and without x:xmpmeta + if ($buf2 =~ /^(\xfe\xff)(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta)/g) { + $fmt = 'n'; # UTF-16 or 32 MM with BOM + } elsif ($buf2 =~ /^(\xff\xfe)(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta)/g) { + $fmt = 'v'; # UTF-16 or 32 II with BOM + } elsif ($buf2 =~ /^(\xef\xbb\xbf)?(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta|<svg\b)/g) { + $fmt = 0; # UTF-8 with BOM or unknown encoding without BOM + } elsif ($buf2 =~ /^(\xfe\xff|\xff\xfe|\xef\xbb\xbf)(<\?xpacket begin=)/g) { + $double = $1; # double-encoded UTF + } else { + return 0; # not recognized XMP or XML + } + $bom = 1 if $1; + if ($2 eq '<?xml') { + if (defined $fmt and not $fmt and $buf2 =~ /^[^\n\r]*[\n\r]+<\?aid /s) { + undef $$et{XmpValidate}; # don't validate INX + if ($$et{XMP_CAPTURE}) { + $et->Error("ExifTool does not yet support writing of INX files"); + return 0; + } + $type = 'INX'; + } elsif ($buf2 =~ /<x(mp)?:x[ma]pmeta/) { + $hasXMP = 1; + } else { + undef $$et{XmpValidate}; # don't validate XML + # identify SVG images and PLIST files by DOCTYPE if available + if ($buf2 =~ /<!DOCTYPE\s+(\w+)/) { + if ($1 eq 'svg') { + $isSVG = 1; + } elsif ($1 eq 'plist') { + $type = 'PLIST'; + } elsif ($1 eq 'REDXIF') { + $type = 'RMD'; + $mime = 'application/xml'; + } elsif ($1 ne 'fcpxml') { # Final Cut Pro XML + return 0; + } + } elsif ($buf2 =~ /<svg[\s>]/) { + $isSVG = 1; + } elsif ($buf2 =~ /<rdf:RDF/) { + $isRDF = 1; + } elsif ($buf2 =~ /<plist[\s>]/) { + $type = 'PLIST'; + } + } + $isXML = 1; + } elsif ($2 eq '<rdf:RDF') { + $isRDF = 1; # recognize XMP without x:xmpmeta element + } elsif ($2 eq '<svg') { + $isSVG = $isXML = 1; + } + if ($isSVG and $$et{XMP_CAPTURE}) { + $et->Error("ExifTool does not yet support writing of SVG images"); + return 0; + } + if ($buff =~ /^\0\0/) { + $fmt = 'N'; # UTF-32 MM with or without BOM + } elsif ($buff =~ /^..\0\0/s) { + $fmt = 'V'; # UTF-32 II with or without BOM + } elsif (not $fmt) { + if ($buff =~ /^\0/) { + $fmt = 'n'; # UTF-16 MM without BOM + } elsif ($buff =~ /^.\0/s) { + $fmt = 'v'; # UTF-16 II without BOM + } + } + } + my $size; + if ($type) { + if ($type eq 'PLIST') { + my $ext = $$et{FILE_EXT}; + $type = $ext if $ext and $ext eq 'MODD'; + $tagTablePtr = GetTagTable('Image::ExifTool::PLIST::Main'); + $$dirInfo{XMPParseOpts}{FoundProc} = \&Image::ExifTool::PLIST::FoundTag; + } + } else { + if ($isSVG) { + $type = 'SVG'; + } elsif ($isXML and not $hasXMP and not $isRDF) { + $type = 'XML'; + my $ext = $$et{FILE_EXT}; + $type = $ext if $ext and $ext eq 'COS'; # recognize COS by extension + } + } + $et->SetFileType($type, $mime); + + my $fast = $et->Options('FastScan'); + return 1 if $fast and $fast == 3; + + if ($type and $type eq 'INX') { + # brute force search for first XMP packet in INX file + # start: '<![CDATA[<?xpacket begin' (24 bytes) + # end: '<?xpacket end="r"?>]]>' (22 bytes) + $raf->Seek(0, 0) or return 0; + $raf->Read($buff, 65536) or return 1; + for (;;) { + last if $buff =~ /<!\[CDATA\[<\?xpacket begin/g; + $raf->Read($buf2, 65536) or return 1; + $buff = substr($buff, -24) . $buf2; + } + $buff = substr($buff, pos($buff) - 15); # (discard '<![CDATA[' and before) + for (;;) { + last if $buff =~ /<\?xpacket end="[rw]"\?>\]\]>/g; + my $n = length $buff; + $raf->Read($buf2, 65536) or $et->Warn('Missing xpacket end'), return 1; + $buff .= $buf2; + pos($buff) = $n - 22; # don't miss end pattern if it was split + } + $size = pos($buff) - 3; # (discard ']]>' and after) + $buff = substr($buff, 0, $size); + } else { + # read the entire file + $raf->Seek(0, 2) or return 0; + $size = $raf->Tell() or return 0; + $raf->Seek(0, 0) or return 0; + $raf->Read($buff, $size) == $size or return 0; + } + $dataPt = \$buff; + $dirStart = 0; + $dirLen = $dataLen = $size; + } + + # decode the first layer of double-encoded UTF text (if necessary) + if ($double) { + my ($buf2, $fmt); + $buff = substr($$dataPt, $dirStart + length $double); # remove leading BOM + Image::ExifTool::SetWarning(undef); # clear old warning + local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning; + # assume that character data has been re-encoded in UTF, so re-pack + # as characters and look for warnings indicating a false assumption + if ($double eq "\xef\xbb\xbf") { + require Image::ExifTool::Charset; + my $uni = Image::ExifTool::Charset::Decompose(undef,$buff,'UTF8'); + $buf2 = pack('C*', @$uni); + } else { + if (length($double) == 2) { + $fmt = ($double eq "\xfe\xff") ? 'n' : 'v'; + } else { + $fmt = ($double eq "\0\0\xfe\xff") ? 'N' : 'V'; + } + $buf2 = pack('C*', unpack("$fmt*",$buff)); + } + if (Image::ExifTool::GetWarning()) { + $et->Warn('Superfluous BOM at start of XMP'); + $dataPt = \$buff; # use XMP with the BOM removed + } else { + $et->Warn('XMP is double UTF-encoded'); + $dataPt = \$buf2; # use the decoded XMP + } + $dirStart = 0; + $dirLen = $dataLen = length $$dataPt; + } + + # extract XMP/XML as a block if specified + my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : 'XMP'; + my $blockExtract = $et->Options('BlockExtract'); + if (($$et{REQ_TAG_LOOKUP}{lc $blockName} or ($$et{TAGS_FROM_FILE} and + not $$et{EXCL_TAG_LOOKUP}{lc $blockName}) or $blockExtract) and + (($$et{FileType} eq 'XMP' and $blockName eq 'XMP') or + ($$dirInfo{DirName} and $$dirInfo{DirName} eq $blockName))) + { + $et->FoundTag($$dirInfo{BlockInfo} || 'XMP', substr($$dataPt, $dirStart, $dirLen)); + return 1 if $blockExtract and $blockExtract > 1; + } + + $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); + if ($et->Options('Verbose') and not $$et{XMP_CAPTURE}) { + my $dirType = $isSVG ? 'SVG' : $$tagTablePtr{GROUPS}{1}; + $et->VerboseDir($dirType, 0, $dirLen); + } +# +# convert UTF-16 or UTF-32 encoded XMP to UTF-8 if necessary +# + my $begin = '<?xpacket begin='; + my $dirEnd = $dirStart + $dirLen; + pos($$dataPt) = $dirStart; + delete $$et{XMP_IS_XML}; + delete $$et{XMP_IS_SVG}; + if ($isXML or $isRDF) { + $$et{XMP_IS_XML} = $isXML; + $$et{XMP_IS_SVG} = $isSVG; + $$et{XMP_NO_XPACKET} = 1 + $bom; + } elsif ($$dataPt =~ /\G\Q$begin\E/gc) { + delete $$et{XMP_NO_XPACKET}; + } elsif ($$dataPt =~ /<x(mp)?:x[ma]pmeta/gc and + pos($$dataPt) > $dirStart and pos($$dataPt) < $dirEnd) + { + $$et{XMP_NO_XPACKET} = 1 + $bom; + } else { + delete $$et{XMP_NO_XPACKET}; + # check for UTF-16 encoding (insert one \0 between characters) + $begin = join "\0", split //, $begin; + # must reset pos because it was killed by previous unsuccessful //g match + pos($$dataPt) = $dirStart; + if ($$dataPt =~ /\G(\0)?\Q$begin\E\0./sg) { + # validate byte ordering by checking for U+FEFF character + if ($1) { + # should be big-endian since we had a leading \0 + $fmt = 'n' if $$dataPt =~ /\G\xfe\xff/g; + } else { + $fmt = 'v' if $$dataPt =~ /\G\0\xff\xfe/g; + } + } else { + # check for UTF-32 encoding (with three \0's between characters) + $begin =~ s/\0/\0\0\0/g; + pos($$dataPt) = $dirStart; + if ($$dataPt !~ /\G(\0\0\0)?\Q$begin\E\0\0\0./sg) { + $fmt = 0; # set format to zero as indication we didn't find encoded XMP + } elsif ($1) { + # should be big-endian + $fmt = 'N' if $$dataPt =~ /\G\0\0\xfe\xff/g; + } else { + $fmt = 'V' if $$dataPt =~ /\G\0\0\0\xff\xfe\0\0/g; + } + } + defined $fmt or $et->Warn('XMP character encoding error'); + } + # warn if standard XMP is missing xpacket wrapper + if ($$et{XMP_NO_XPACKET} and $$et{OPTIONS}{Validate} and + $stdPath{$$et{FILE_TYPE}} and $path eq $stdPath{$$et{FILE_TYPE}} and + not $$dirInfo{IsExtended} and not $$et{DOC_NUM}) + { + $et->Warn('XMP is missing xpacket wrapper', 1); + } + if ($fmt) { + # trim if necessary to avoid converting non-UTF data + if ($dirStart or $dirEnd != length($$dataPt)) { + $buff = substr($$dataPt, $dirStart, $dirLen); + $dataPt = \$buff; + } + # convert into UTF-8 + if ($] >= 5.006001) { + $buff = pack('C0U*', unpack("$fmt*",$$dataPt)); + } else { + $buff = Image::ExifTool::PackUTF8(unpack("$fmt*",$$dataPt)); + } + $dataPt = \$buff; + $dirStart = 0; + $dirLen = length $$dataPt; + $dirEnd = $dirStart + $dirLen; + } + # avoid scanning for XMP later in case ScanForXMP is set + $$et{FoundXMP} = 1 if $tagTablePtr eq \%Image::ExifTool::XMP::Main; + + # set XMP parsing options + $$et{XMPParseOpts} = $$dirInfo{XMPParseOpts}; + + # ignore any specified properties (XML hack) + if ($$dirInfo{IgnoreProp}) { + %ignoreProp = %{$$dirInfo{IgnoreProp}}; + } else { + undef %ignoreProp; + } + + # need to preserve list indices to be able to handle multi-dimensional lists + my $keepFlat; + if ($$et{OPTIONS}{Struct}) { + if ($$et{OPTIONS}{Struct} eq '2') { + $keepFlat = 1; # preserve flattened tags + # setting NO_LIST to 0 combines list items in a TAG_EXTRA "NoList" element + # to allow them to be re-listed later if necessary. A "NoListDel" element + # is also created for tags that wouldn't have existed. + $$et{NO_LIST} = 0; + } else { + $$et{NO_LIST} = 1; + } + } + + # don't generate structures if this isn't real XMP + $$et{NO_STRUCT} = 1 if $$dirInfo{BlockInfo} or $$dirInfo{NoStruct}; + + # parse the XMP + if (ParseXMPElement($et, $tagTablePtr, $dataPt, $dirStart, $dirEnd)) { + $rtnVal = 1; + } elsif ($$dirInfo{DirName} and $$dirInfo{DirName} eq 'XMP') { + # if DirName was 'XMP' we expect well-formed XMP, so set Warning since it wasn't + # (but allow empty XMP as written by some PhaseOne cameras) + my $xmp = substr($$dataPt, $dirStart, $dirLen); + if ($xmp =~ /^ *\0*$/) { + $et->Warn('Invalid XMP'); + } else { + $et->Warn('Empty XMP',1); + $rtnVal = 1; + } + } + delete $$et{NO_STRUCT}; + + # return DataPt if successful in case we want it for writing + $$dirInfo{DataPt} = $dataPt if $rtnVal and $$dirInfo{RAF}; + + # restore structures if necessary + if ($$et{IsStruct}) { + unless ($$dirInfo{NoStruct}) { + require 'Image/ExifTool/XMPStruct.pl'; + RestoreStruct($et, $keepFlat); + } + delete $$et{IsStruct}; + } + # reset NO_LIST flag (must do this _after_ RestoreStruct() above) + delete $$et{NO_LIST}; + delete $$et{XMPParseOpts}; + delete $$et{curURI}; + delete $$et{curNS}; + delete $$et{xlatNS}; + delete $$et{definedNS}; + + return $rtnVal; +} + + +1; #end + +__END__ + +=head1 NAME + +Image::ExifTool::XMP - Read XMP meta information + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +XMP stands for Extensible Metadata Platform. It is a format based on XML +that Adobe developed for embedding metadata information in image files. +This module contains the definitions required by Image::ExifTool to read XMP +information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.adobe.com/devnet/xmp/> + +=item L<http://www.w3.org/TR/rdf-syntax-grammar/> + +=item L<http://www.iptc.org/IPTC4XMP/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/XMP Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/XMP2.pl b/ExifTool/lib/Image/ExifTool/XMP2.pl new file mode 100644 index 0000000..d270106 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/XMP2.pl @@ -0,0 +1,2226 @@ +#------------------------------------------------------------------------------ +# File: XMP2.pl +# +# Description: Additional XMP namespace definitions +# +# Revisions: 10/12/2008 - P. Harvey Created +# +# References: 1) PLUS - http://ns.useplus.org/ +# 2) PRISM - http://www.prismstandard.org/ +# 3) http://www.portfoliofaq.com/pfaq/v7mappings.htm +# 4) http://www.iptc.org/IPTC4XMP/ +# 5) http://creativecommons.org/technology/xmp +# --> changed to http://wiki.creativecommons.org/Companion_File_metadata_specification (2007/12/21) +# 6) http://www.optimasc.com/products/fileid/xmp-extensions.pdf +# 9) http://www.w3.org/TR/SVG11/ +# 11) http://www.extensis.com/en/support/kb_article.jsp?articleNumber=6102211 +# 12) XMPSpecificationPart3_May2013, page 58 +# 13) https://developer.android.com/training/camera2/Dynamic-depth-v1.0.pdf +# 14) http://www.iptc.org/standards/photo-metadata/iptc-standard/ +#------------------------------------------------------------------------------ + +package Image::ExifTool::XMP; + +use strict; +use Image::ExifTool qw(:Utils); +use Image::ExifTool::XMP; + +sub Init_crd($); + +#------------------------------------------------------------------------------ + +# xmpDM structure definitions +my %sCuePointParam = ( + STRUCT_NAME => 'CuePointParam', + NAMESPACE => 'xmpDM', + key => { }, + value => { }, +); +my %sMarker = ( + STRUCT_NAME => 'Marker', + NAMESPACE => 'xmpDM', + comment => { }, + duration => { }, + location => { }, + name => { }, + startTime => { }, + target => { }, + type => { }, + # added Oct 2008 + cuePointParams => { Struct => \%sCuePointParam, List => 'Seq' }, + cuePointType=> { }, + probability => { Writable => 'real' }, + speaker => { }, +); +my %sTime = ( + STRUCT_NAME => 'Time', + NAMESPACE => 'xmpDM', + scale => { Writable => 'rational' }, + value => { Writable => 'integer' }, +); +my %sTimecode = ( + STRUCT_NAME => 'Timecode', + NAMESPACE => 'xmpDM', + timeFormat => { + PrintConv => { + '24Timecode' => '24 fps', + '25Timecode' => '25 fps', + '2997DropTimecode' => '29.97 fps (drop)', + '2997NonDropTimecode' => '29.97 fps (non-drop)', + '30Timecode' => '30 fps', + '50Timecode' => '50 fps', + '5994DropTimecode' => '59.94 fps (drop)', + '5994NonDropTimecode' => '59.94 fps (non-drop)', + '60Timecode' => '60 fps', + '23976Timecode' => '23.976 fps', + }, + }, + timeValue => { }, + value => { Writable => 'integer', Notes => 'only in XMP 2008 spec; an error?' }, +); +my %sPose = ( + STRUCT_NAME => 'Pose', + NAMESPACE => { Pose => 'http://ns.google.com/photos/dd/1.0/pose/' }, + PositionX => { Writable => 'real', Groups => { 2 => 'Location' } }, + PositionY => { Writable => 'real', Groups => { 2 => 'Location' } }, + PositionZ => { Writable => 'real', Groups => { 2 => 'Location' } }, + RotationX => { Writable => 'real', Groups => { 2 => 'Location' } }, + RotationY => { Writable => 'real', Groups => { 2 => 'Location' } }, + RotationZ => { Writable => 'real', Groups => { 2 => 'Location' } }, + RotationW => { Writable => 'real', Groups => { 2 => 'Location' } }, + Timestamp => { + Writable => 'integer', + Shift => 'Time', + Groups => { 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val / 1000, 1, 3)', + ValueConvInv => 'int(GetUnixTime($val, 1) * 1000)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + }, +); +my %sEarthPose = ( + STRUCT_NAME => 'EarthPose', + NAMESPACE => { EarthPose => 'http://ns.google.com/photos/dd/1.0/earthpose/' }, + Latitude => { Writable => 'real', Groups => { 2 => 'Location' }, %latConv }, + Longitude => { Writable => 'real', Groups => { 2 => 'Location' }, %longConv }, + Altitude => { + Writable => 'real', + Groups => { 2 => 'Location' }, + PrintConv => '"$val m"', + PrintConvInv => '$val=~s/\s*m$//;$val', + }, + RotationX => { Writable => 'real', Groups => { 2 => 'Location' } }, + RotationY => { Writable => 'real', Groups => { 2 => 'Location' } }, + RotationZ => { Writable => 'real', Groups => { 2 => 'Location' } }, + RotationW => { Writable => 'real', Groups => { 2 => 'Location' } }, + Timestamp => { + Writable => 'integer', + Shift => 'Time', + Groups => { 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val / 1000, 1, 3)', + ValueConvInv => 'int(GetUnixTime($val, 1) * 1000)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val,undef,1)', + }, +); +my %sVendorInfo = ( + STRUCT_NAME => 'VendorInfo', + NAMESPACE => { VendorInfo => 'http://ns.google.com/photos/dd/1.0/vendorinfo/' }, + Model => { }, + Manufacturer => { }, + Notes => { }, +); +my %sAppInfo = ( + STRUCT_NAME => 'AppInfo', + NAMESPACE => { AppInfo => 'http://ns.google.com/photos/dd/1.0/appinfo/' }, + Application => { }, + Version => { }, + ItemURI => { }, +); + +# camera-raw defaults +%Image::ExifTool::XMP::crd = ( + %xmpTableDefaults, + INIT_TABLE => \&Init_crd, + GROUPS => { 1 => 'XMP-crd', 2 => 'Image' }, + NAMESPACE => 'crd', + AVOID => 1, + TABLE_DESC => 'Photoshop Camera Defaults namespace', + NOTES => 'Adobe Camera Raw Defaults tags.', + # (tags added dynamically when WRITE_PROC is called) +); + +# XMP Dynamic Media namespace properties (xmpDM) +%Image::ExifTool::XMP::xmpDM = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-xmpDM', 2 => 'Image' }, + NAMESPACE => 'xmpDM', + NOTES => 'XMP Dynamic Media namespace tags.', + absPeakAudioFilePath=> { }, + album => { }, + altTapeName => { }, + altTimecode => { Struct => \%sTimecode }, + artist => { Avoid => 1, Groups => { 2 => 'Author' } }, + audioModDate => { Groups => { 2 => 'Time' }, %dateTimeInfo }, + audioSampleRate => { Writable => 'integer' }, + audioSampleType => { + PrintConv => { + '8Int' => '8-bit integer', + '16Int' => '16-bit integer', + '24Int' => '24-bit integer', + '32Int' => '32-bit integer', + '32Float' => '32-bit float', + 'Compressed' => 'Compressed', + 'Packed' => 'Packed', + 'Other' => 'Other', + }, + }, + audioChannelType => { + PrintConv => { + 'Mono' => 'Mono', + 'Stereo' => 'Stereo', + '5.1' => '5.1', + '7.1' => '7.1', + '16 Channel' => '16 Channel', + 'Other' => 'Other', + }, + }, + audioCompressor => { }, + beatSpliceParams => { + Struct => { + STRUCT_NAME => 'BeatSpliceStretch', + NAMESPACE => 'xmpDM', + riseInDecibel => { Writable => 'real' }, + riseInTimeDuration => { Struct => \%sTime }, + useFileBeatsMarker => { Writable => 'boolean' }, + }, + }, + cameraAngle => { }, + cameraLabel => { }, + cameraModel => { }, + cameraMove => { }, + client => { }, + comment => { Name => 'DMComment' }, + composer => { Groups => { 2 => 'Author' } }, + contributedMedia => { + Struct => { + STRUCT_NAME => 'Media', + NAMESPACE => 'xmpDM', + duration => { Struct => \%sTime }, + managed => { Writable => 'boolean' }, + path => { }, + startTime => { Struct => \%sTime }, + track => { }, + webStatement=> { }, + }, + List => 'Bag', + }, + copyright => { Avoid => 1, Groups => { 2 => 'Author' } }, # (deprecated) + director => { }, + directorPhotography => { }, + discNumber => { }, #12 + duration => { Struct => \%sTime }, + engineer => { }, + fileDataRate => { Writable => 'rational' }, + genre => { }, + good => { Writable => 'boolean' }, + instrument => { }, + introTime => { Struct => \%sTime }, + key => { + PrintConvColumns => 3, + PrintConv => { + 'C' => 'C', 'C#' => 'C#', 'D' => 'D', 'D#' => 'D#', + 'E' => 'E', 'F' => 'F', 'F#' => 'F#', 'G' => 'G', + 'G#' => 'G#', 'A' => 'A', 'A#' => 'A#', 'B' => 'B', + }, + }, + logComment => { }, + loop => { Writable => 'boolean' }, + lyrics => { }, #12 + numberOfBeats => { Writable => 'real' }, + markers => { Struct => \%sMarker, List => 'Seq' }, + metadataModDate => { Groups => { 2 => 'Time' }, %dateTimeInfo }, + outCue => { Struct => \%sTime }, + partOfCompilation=>{ Writable => 'boolean' }, #12 + projectName => { }, + projectRef => { + Struct => { + STRUCT_NAME => 'ProjectLink', + NAMESPACE => 'xmpDM', + path => { }, + type => { + PrintConv => { + movie => 'Movie', + still => 'Still Image', + audio => 'Audio', + custom => 'Custom', + }, + }, + }, + }, + pullDown => { + PrintConvColumns => 2, + PrintConv => { + 'WSSWW' => 'WSSWW', 'SSWWW' => 'SSWWW', + 'SWWWS' => 'SWWWS', 'WWWSS' => 'WWWSS', + 'WWSSW' => 'WWSSW', 'WWWSW' => 'WWWSW', + 'WWSWW' => 'WWSWW', 'WSWWW' => 'WSWWW', + 'SWWWW' => 'SWWWW', 'WWWWS' => 'WWWWS', + }, + }, + relativePeakAudioFilePath => { }, + relativeTimestamp => { Struct => \%sTime }, + releaseDate => { Groups => { 2 => 'Time' }, %dateTimeInfo }, + resampleParams => { + Struct => { + STRUCT_NAME => 'ResampleStretch', + NAMESPACE => 'xmpDM', + quality => { PrintConv => { Low => 'Low', Medium => 'Medium', High => 'High' } }, + }, + }, + scaleType => { + PrintConv => { + Major => 'Major', + Minor => 'Minor', + Both => 'Both', + Neither => 'Neither', + }, + }, + scene => { Avoid => 1 }, + shotDate => { Groups => { 2 => 'Time' }, %dateTimeInfo }, + shotDay => { }, + shotLocation => { }, + shotName => { }, + shotNumber => { }, + shotSize => { }, + speakerPlacement=> { }, + startTimecode => { Struct => \%sTimecode }, + startTimeSampleSize => { Writable => 'integer' }, #PH + startTimeScale => { }, #PH (real?) + stretchMode => { + PrintConv => { + 'Fixed length' => 'Fixed length', + 'Time-Scale' => 'Time-Scale', + 'Resample' => 'Resample', + 'Beat Splice' => 'Beat Splice', + 'Hybrid' => 'Hybrid', + }, + }, + takeNumber => { Writable => 'integer' }, + tapeName => { }, + tempo => { Writable => 'real' }, + timeScaleParams => { + Struct => { + STRUCT_NAME => 'TimeScaleStretch', + NAMESPACE => 'xmpDM', + frameOverlappingPercentage => { Writable => 'real' }, + frameSize => { Writable => 'real' }, + quality => { PrintConv => { Low => 'Low', Medium => 'Medium', High => 'High' } }, + }, + }, + timeSignature => { + PrintConvColumns => 3, + PrintConv => { + '2/4' => '2/4', '3/4' => '3/4', '4/4' => '4/4', + '5/4' => '5/4', '7/4' => '7/4', '6/8' => '6/8', + '9/8' => '9/8', '12/8'=> '12/8', 'other' => 'other', + }, + }, + trackNumber => { Writable => 'integer' }, + Tracks => { + Struct => { + STRUCT_NAME => 'Track', + NAMESPACE => 'xmpDM', + frameRate => { }, + markers => { Struct => \%sMarker, List => 'Seq' }, + trackName => { }, + trackType => { }, + }, + List => 'Bag', + }, + videoAlphaMode => { + PrintConv => { + 'straight' => 'Straight', + 'pre-multiplied', => 'Pre-multiplied', + 'none' => 'None', + }, + }, + videoAlphaPremultipleColor => { Struct => \%sColorant }, + videoAlphaUnityIsTransparent => { Writable => 'boolean' }, + videoColorSpace => { + PrintConv => { + 'sRGB' => 'sRGB', + 'CCIR-601' => 'CCIR-601', + 'CCIR-709' => 'CCIR-709', + }, + }, + videoCompressor => { }, + videoFieldOrder => { + PrintConv => { + Upper => 'Upper', + Lower => 'Lower', + Progressive => 'Progressive', + }, + }, + videoFrameRate => { Writable => 'real' }, + videoFrameSize => { Struct => \%sDimensions }, + videoModDate => { Groups => { 2 => 'Time' }, %dateTimeInfo }, + videoPixelAspectRatio => { Writable => 'rational' }, + videoPixelDepth => { + PrintConv => { + '8Int' => '8-bit integer', + '16Int' => '16-bit integer', + '24Int' => '24-bit integer', + '32Int' => '32-bit integer', + '32Float' => '32-bit float', + 'Other' => 'Other', + }, + }, +); + +#------------------------------------------------------------------------------ +# IPTC Extensions version 1.3 (+ proposed video extensions) + +# IPTC Extension 1.0 structures +my %sLocationDetails = ( + STRUCT_NAME => 'LocationDetails', + NAMESPACE => 'Iptc4xmpExt', + GROUPS => { 2 => 'Location' }, + Identifier => { List => 'Bag', Namespace => 'xmp' }, + City => { }, + CountryCode => { }, + CountryName => { }, + ProvinceState => { }, + Sublocation => { }, + WorldRegion => { }, + LocationId => { List => 'Bag' }, + LocationName => { Writable => 'lang-alt' }, + GPSLatitude => { Namespace => 'exif', %latConv }, + GPSLongitude => { Namespace => 'exif', %longConv }, + GPSAltitude => { + Namespace => 'exif', + Writable => 'rational', + PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"', + PrintConvInv => '$val=~s/\s*m$//;$val', + }, +); +my %sCVTermDetails = ( + STRUCT_NAME => 'CVTermDetails', + NAMESPACE => 'Iptc4xmpExt', + CvTermId => { }, + CvTermName => { Writable => 'lang-alt' }, + CvId => { }, + CvTermRefinedAbout => { }, +); + +# IPTC video extensions +my %sPublicationEvent = ( + STRUCT_NAME => 'PublicationEvent', + NAMESPACE => 'Iptc4xmpExt', + Date => { Groups => { 2 => 'Time' }, %dateTimeInfo }, + Name => { }, + Identifier => { }, +); +my %sEntity = ( + STRUCT_NAME => 'Entity', + NAMESPACE => 'Iptc4xmpExt', + Identifier => { List => 'Bag', Namespace => 'xmp' }, + Name => { Writable => 'lang-alt' }, +); +my %sEntityWithRole = ( + STRUCT_NAME => 'EntityWithRole', + NAMESPACE => 'Iptc4xmpExt', + Identifier => { List => 'Bag', Namespace => 'xmp' }, + Name => { Writable => 'lang-alt' }, + Role => { List => 'Bag' }, +); +# (no longer used) +#my %sFrameSize = ( +# STRUCT_NAME => 'FrameSize', +# NAMESPACE => 'Iptc4xmpExt', +# WidthPixels => { Writable => 'integer' }, +# HeightPixels => { Writable => 'integer' }, +#); +my %sRating = ( + STRUCT_NAME => 'Rating', + NAMESPACE => 'Iptc4xmpExt', + RatingValue => { FlatName => 'Value' }, + RatingSourceLink => { FlatName => 'SourceLink' }, + RatingScaleMinValue => { FlatName => 'ScaleMinValue' }, + RatingScaleMaxValue => { FlatName => 'ScaleMaxValue' }, + RatingValueLogoLink => { FlatName => 'ValueLogoLink' }, + RatingRegion => { + FlatName => 'Region', + Struct => \%sLocationDetails, + List => 'Bag', + }, +); +my %sEpisode = ( + STRUCT_NAME => 'EpisodeOrSeason', + NAMESPACE => 'Iptc4xmpExt', + Name => { }, + Number => { }, + Identifier => { }, +); +my %sSeries = ( + STRUCT_NAME => 'Series', + NAMESPACE => 'Iptc4xmpExt', + Name => { }, + Identifier => { }, +); +my %sTemporalCoverage = ( + STRUCT_NAME => 'TemporalCoverage', + NAMESPACE => 'Iptc4xmpExt', + tempCoverageFrom => { FlatName => 'From', %dateTimeInfo, Groups => { 2 => 'Time' } }, + tempCoverageTo => { FlatName => 'To', %dateTimeInfo, Groups => { 2 => 'Time' } }, +); +my %sQualifiedLink = ( + STRUCT_NAME => 'QualifiedLink', + NAMESPACE => 'Iptc4xmpExt', + Link => { }, + LinkQualifier => { }, +); +my %sTextRegion = ( + STRUCT_NAME => 'TextRegion', + NAMESPACE => 'Iptc4xmpExt', + RegionText => { }, + Region => { Struct => \%Image::ExifTool::XMP::sArea }, +); +my %sLinkedImage = ( + STRUCT_NAME => 'LinkedImage', + NAMESPACE => 'Iptc4xmpExt', + Link => { }, + LinkQualifier => { List => 'Bag' }, + ImageRole => { }, + 'format' => { Namespace => 'dc' }, + WidthPixels => { Writable => 'integer' }, + HeightPixels=> { Writable => 'integer' }, + UsedVideoFrame => { Struct => \%sTimecode }, +); +my %sBoundaryPoint = ( # new in 1.5 + STRUCT_NAME => 'BoundaryPoint', + NAMESPACE => 'Iptc4xmpExt', + rbX => { FlatName => 'X', Writable => 'real' }, + rbY => { FlatName => 'Y', Writable => 'real' }, +); +my %sRegionBoundary = ( # new in 1.5 + STRUCT_NAME => 'RegionBoundary', + NAMESPACE => 'Iptc4xmpExt', + rbShape => { FlatName => 'Shape', PrintConv => { rectangle => 'Rectangle', circle => 'Circle', polygon => 'Polygon' } }, + rbUnit => { FlatName => 'Unit', PrintConv => { pixel => 'Pixel', relative => 'Relative' } }, + rbX => { FlatName => 'X', Writable => 'real' }, + rbY => { FlatName => 'Y', Writable => 'real' }, + rbW => { FlatName => 'W', Writable => 'real' }, + rbH => { FlatName => 'H', Writable => 'real' }, + rbRx => { FlatName => 'Rx', Writable => 'real' }, + rbVertices => { FlatName => 'Vertices', List => 'Seq', Struct => \%sBoundaryPoint }, +); +my %sImageRegion = ( # new in 1.5 + STRUCT_NAME => 'ImageRegion', + NAMESPACE => undef, # undefined to allow variable-namespace extensions + NOTES => q{ + This structure is new in the IPTC Extension version 1.5 specification. As + well as the fields defined below, this structure may contain any top-level + XMP tags, but since they aren't pre-defined the only way to add these tags + is to write ImageRegion as a structure with these tags as new fields. + }, + RegionBoundary => { Namespace => 'Iptc4xmpExt', FlatName => 'Boundary', Struct => \%sRegionBoundary }, + rId => { Namespace => 'Iptc4xmpExt', FlatName => 'ID' }, + Name => { Namespace => 'Iptc4xmpExt', Writable => 'lang-alt' }, + rCtype => { Namespace => 'Iptc4xmpExt', FlatName => 'Ctype', List => 'Bag', Struct => \%sEntity }, + rRole => { Namespace => 'Iptc4xmpExt', FlatName => 'Role', List => 'Bag', Struct => \%sEntity }, +); + +# IPTC Extension namespace properties (Iptc4xmpExt) (ref 4, 14) +%Image::ExifTool::XMP::iptcExt = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-iptcExt', 2 => 'Author' }, + NAMESPACE => 'Iptc4xmpExt', + TABLE_DESC => 'XMP IPTC Extension', + NOTES => q{ + This table contains tags defined by the IPTC Extension schema version 1.7 + and IPTC Video Metadata version 1.3. The actual namespace prefix is + "Iptc4xmpExt", but ExifTool shortens this for the family 1 group name. (See + L<http://www.iptc.org/standards/photo-metadata/iptc-standard/> and + L<https://iptc.org/standards/video-metadata-hub/>.) + }, + AboutCvTerm => { + Struct => \%sCVTermDetails, + List => 'Bag', + }, + AboutCvTermCvId => { Flat => 1, Name => 'AboutCvTermCvId' }, + AboutCvTermCvTermId => { Flat => 1, Name => 'AboutCvTermId' }, + AboutCvTermCvTermName => { Flat => 1, Name => 'AboutCvTermName' }, + AboutCvTermCvTermRefinedAbout => { Flat => 1, Name => 'AboutCvTermRefinedAbout' }, + AddlModelInfo => { Name => 'AdditionalModelInformation' }, + ArtworkOrObject => { + Struct => { + STRUCT_NAME => 'ArtworkOrObjectDetails', + NAMESPACE => 'Iptc4xmpExt', + AOCopyrightNotice => { }, + AOCreator => { List => 'Seq' }, + AODateCreated=> { Groups => { 2 => 'Time' }, %dateTimeInfo }, + AOSource => { }, + AOSourceInvNo=> { }, + AOTitle => { Writable => 'lang-alt' }, + AOCurrentCopyrightOwnerName => { }, + AOCurrentCopyrightOwnerId => { }, + AOCurrentLicensorName => { }, + AOCurrentLicensorId => { }, + AOCreatorId => { List => 'Seq' }, + AOCircaDateCreated => { Groups => { 2 => 'Time' }, Protected => 1 }, + AOStylePeriod => { List => 'Bag' }, + AOSourceInvURL => { }, + AOContentDescription => { Writable => 'lang-alt' }, + AOContributionDescription => { Writable => 'lang-alt' }, + AOPhysicalDescription => { Writable => 'lang-alt' }, + }, + List => 'Bag', + }, + ArtworkOrObjectAOCopyrightNotice => { Flat => 1, Name => 'ArtworkCopyrightNotice' }, + ArtworkOrObjectAOCreator => { Flat => 1, Name => 'ArtworkCreator' }, + ArtworkOrObjectAODateCreated => { Flat => 1, Name => 'ArtworkDateCreated' }, + ArtworkOrObjectAOSource => { Flat => 1, Name => 'ArtworkSource' }, + ArtworkOrObjectAOSourceInvNo => { Flat => 1, Name => 'ArtworkSourceInventoryNo' }, + ArtworkOrObjectAOTitle => { Flat => 1, Name => 'ArtworkTitle' }, + ArtworkOrObjectAOCurrentCopyrightOwnerName => { Flat => 1, Name => 'ArtworkCopyrightOwnerName' }, + ArtworkOrObjectAOCurrentCopyrightOwnerId => { Flat => 1, Name => 'ArtworkCopyrightOwnerID' }, + ArtworkOrObjectAOCurrentLicensorName => { Flat => 1, Name => 'ArtworkLicensorName' }, + ArtworkOrObjectAOCurrentLicensorId => { Flat => 1, Name => 'ArtworkLicensorID' }, + ArtworkOrObjectAOCreatorId => { Flat => 1, Name => 'ArtworkCreatorID' }, + ArtworkOrObjectAOCircaDateCreated => { Flat => 1, Name => 'ArtworkCircaDateCreated' }, + ArtworkOrObjectAOStylePeriod => { Flat => 1, Name => 'ArtworkStylePeriod' }, + ArtworkOrObjectAOSourceInvURL => { Flat => 1, Name => 'ArtworkSourceInvURL' }, + ArtworkOrObjectAOContentDescription => { Flat => 1, Name => 'ArtworkContentDescription' }, + ArtworkOrObjectAOContributionDescription => { Flat => 1, Name => 'ArtworkContributionDescription' }, + ArtworkOrObjectAOPhysicalDescription => { Flat => 1, Name => 'ArtworkPhysicalDescription' }, + CVterm => { + Name => 'ControlledVocabularyTerm', + List => 'Bag', + Notes => 'deprecated by version 1.2', + }, + DigImageGUID => { Groups => { 2 => 'Image' }, Name => 'DigitalImageGUID' }, + DigitalSourcefileType => { + Name => 'DigitalSourceFileType', + Notes => 'now deprecated -- replaced by DigitalSourceType', + Groups => { 2 => 'Image' }, + }, + DigitalSourceType => { Name => 'DigitalSourceType', Groups => { 2 => 'Image' } }, + EmbdEncRightsExpr => { + Struct => { + STRUCT_NAME => 'EEREDetails', + NAMESPACE => 'Iptc4xmpExt', + EncRightsExpr => { }, + RightsExprEncType => { }, + RightsExprLangId => { }, + }, + List => 'Bag', + }, + EmbdEncRightsExprEncRightsExpr => { Flat => 1, Name => 'EmbeddedEncodedRightsExpr' }, + EmbdEncRightsExprRightsExprEncType => { Flat => 1, Name => 'EmbeddedEncodedRightsExprType' }, + EmbdEncRightsExprRightsExprLangId => { Flat => 1, Name => 'EmbeddedEncodedRightsExprLangID' }, + Event => { Writable => 'lang-alt' }, + IptcLastEdited => { + Name => 'IPTCLastEdited', + Groups => { 2 => 'Time' }, + %dateTimeInfo, + }, + LinkedEncRightsExpr => { + Struct => { + STRUCT_NAME => 'LEREDetails', + NAMESPACE => 'Iptc4xmpExt', + LinkedRightsExpr => { }, + RightsExprEncType => { }, + RightsExprLangId => { }, + }, + List => 'Bag', + }, + LinkedEncRightsExprLinkedRightsExpr => { Flat => 1, Name => 'LinkedEncodedRightsExpr' }, + LinkedEncRightsExprRightsExprEncType => { Flat => 1, Name => 'LinkedEncodedRightsExprType' }, + LinkedEncRightsExprRightsExprLangId => { Flat => 1, Name => 'LinkedEncodedRightsExprLangID' }, + LocationCreated => { + Struct => \%sLocationDetails, + Groups => { 2 => 'Location' }, + List => 'Bag', + }, + LocationShown => { + Struct => \%sLocationDetails, + Groups => { 2 => 'Location' }, + List => 'Bag', + }, + MaxAvailHeight => { Groups => { 2 => 'Image' }, Writable => 'integer' }, + MaxAvailWidth => { Groups => { 2 => 'Image' }, Writable => 'integer' }, + ModelAge => { List => 'Bag', Writable => 'integer' }, + OrganisationInImageCode => { List => 'Bag' }, + OrganisationInImageName => { List => 'Bag' }, + PersonInImage => { List => 'Bag' }, + PersonInImageWDetails => { + Struct => { + STRUCT_NAME => 'PersonDetails', + NAMESPACE => 'Iptc4xmpExt', + PersonId => { List => 'Bag' }, + PersonName => { Writable => 'lang-alt' }, + PersonCharacteristic => { + Struct => \%sCVTermDetails, + List => 'Bag', + }, + PersonDescription => { Writable => 'lang-alt' }, + }, + List => 'Bag', + }, + PersonInImageWDetailsPersonId => { Flat => 1, Name => 'PersonInImageId' }, + PersonInImageWDetailsPersonName => { Flat => 1, Name => 'PersonInImageName' }, + PersonInImageWDetailsPersonCharacteristic => { Flat => 1, Name => 'PersonInImageCharacteristic' }, + PersonInImageWDetailsPersonCharacteristicCvId => { Flat => 1, Name => 'PersonInImageCvTermCvId' }, + PersonInImageWDetailsPersonCharacteristicCvTermId => { Flat => 1, Name => 'PersonInImageCvTermId' }, + PersonInImageWDetailsPersonCharacteristicCvTermName => { Flat => 1, Name => 'PersonInImageCvTermName' }, + PersonInImageWDetailsPersonCharacteristicCvTermRefinedAbout => { Flat => 1, Name => 'PersonInImageCvTermRefinedAbout' }, + PersonInImageWDetailsPersonDescription => { Flat => 1, Name => 'PersonInImageDescription' }, + ProductInImage => { + Struct => { + STRUCT_NAME => 'ProductDetails', + NAMESPACE => 'Iptc4xmpExt', + ProductName => { Writable => 'lang-alt' }, + ProductGTIN => { }, + ProductDescription => { Writable => 'lang-alt' }, + ProductId => { }, # added in version 2022.1 + }, + List => 'Bag', + }, + ProductInImageProductName => { Flat => 1, Name => 'ProductInImageName' }, + ProductInImageProductGTIN => { Flat => 1, Name => 'ProductInImageGTIN' }, + ProductInImageProductDescription => { Flat => 1, Name => 'ProductInImageDescription' }, + RegistryId => { + Name => 'RegistryID', + Struct => { + STRUCT_NAME => 'RegistryEntryDetails', + NAMESPACE => 'Iptc4xmpExt', + RegItemId => { }, + RegOrgId => { }, + RegEntryRole=> { }, # (new in 1.3) + }, + List => 'Bag', + }, + RegistryIdRegItemId => { Flat => 1, Name => 'RegistryItemID' }, + RegistryIdRegOrgId => { Flat => 1, Name => 'RegistryOrganisationID' }, + RegistryIdRegEntryRole => { Flat => 1, Name => 'RegistryEntryRole' }, + + # new Extension 1.3 properties + Genre => { Groups => { 2 => 'Image' }, List => 'Bag', Struct => \%sCVTermDetails }, + + # new video properties (Oct 2016, ref Michael Steidl) + # (see http://www.iptc.org/std/videometadatahub/recommendation/IPTC-VideoMetadataHub-props-Rec_1.0.html) + CircaDateCreated=> { Groups => { 2 => 'Time' } }, + Episode => { Groups => { 2 => 'Video' }, Struct => \%sEpisode }, + ExternalMetadataLink => { Groups => { 2 => 'Other' }, List => 'Bag' }, + FeedIdentifier => { Groups => { 2 => 'Video' } }, + PublicationEvent=> { Groups => { 2 => 'Video' }, List => 'Bag', Struct => \%sPublicationEvent }, + Rating => { + Groups => { 2 => 'Other' }, + Struct => \%sRating, + List => 'Bag', + }, + ReleaseReady => { Groups => { 2 => 'Other' }, Writable => 'boolean' }, + Season => { Groups => { 2 => 'Video' }, Struct => \%sEpisode }, + Series => { Groups => { 2 => 'Video' }, Struct => \%sSeries }, + StorylineIdentifier => { Groups => { 2 => 'Video' }, List => 'Bag' }, + StylePeriod => { Groups => { 2 => 'Video' } }, + TemporalCoverage=> { Groups => { 2 => 'Video' }, Struct => \%sTemporalCoverage }, + WorkflowTag => { Groups => { 2 => 'Video' }, Struct => \%sCVTermDetails }, + DataOnScreen => { Groups => { 2 => 'Video' }, List => 'Bag', Struct => \%sTextRegion }, + Dopesheet => { Groups => { 2 => 'Video' }, Writable => 'lang-alt' }, + DopesheetLink => { Groups => { 2 => 'Video' }, List => 'Bag', Struct => \%sQualifiedLink }, + Headline => { Groups => { 2 => 'Video' }, Writable => 'lang-alt', Avoid => 1 }, + PersonHeard => { Groups => { 2 => 'Audio' }, List => 'Bag', Struct => \%sEntity }, + VideoShotType => { Groups => { 2 => 'Video' }, List => 'Bag', Struct => \%sEntity }, + EventExt => { Groups => { 2 => 'Video' }, List => 'Bag', Struct => \%sEntity, Name => 'ShownEvent' }, + Transcript => { Groups => { 2 => 'Video' }, Writable => 'lang-alt' }, + TranscriptLink => { Groups => { 2 => 'Video' }, List => 'Bag', Struct => \%sQualifiedLink }, + VisualColour => { + Name => 'VisualColor', + Groups => { 2 => 'Video' }, + PrintConv => { + 'bw-monochrome' => 'Monochrome', + 'colour' => 'Color', + }, + }, + Contributor => { List => 'Bag', Struct => \%sEntityWithRole }, + CopyrightYear => { Groups => { 2 => 'Time' }, Writable => 'integer' }, + Creator => { List => 'Bag', Struct => \%sEntityWithRole }, + SupplyChainSource => { Groups => { 2 => 'Other' }, List => 'Bag', Struct => \%sEntity }, + audioBitRate => { Groups => { 2 => 'Audio' }, Writable => 'integer', Name => 'AudioBitrate' }, + audioBitRateMode=> { + Name => 'AudioBitrateMode', + Groups => { 2 => 'Audio' }, + PrintConv => { + fixed => 'Fixed', + variable => 'Variable', + }, + }, + audioChannelCount => { Groups => { 2 => 'Audio' }, Writable => 'integer' }, + videoDisplayAspectRatio => { Groups => { 2 => 'Audio' }, Writable => 'rational' }, + ContainerFormat => { Groups => { 2 => 'Video' }, Struct => \%sEntity }, + StreamReady => { + Groups => { 2 => 'Video' }, + PrintConv => { + true => 'True', + false => 'False', + unknown => 'Unknown', + }, + }, + videoBitRate => { Groups => { 2 => 'Video' }, Writable => 'integer', Name => 'VideoBitrate' }, + videoBitRateMode => { + Name => 'VideoBitrateMode', + Groups => { 2 => 'Video' }, + PrintConv => { + fixed => 'Fixed', + variable => 'Variable', + }, + }, + videoEncodingProfile => { Groups => { 2 => 'Video' } }, + videoStreamsCount => { Groups => { 2 => 'Video' }, Writable => 'integer' }, + # new IPTC video metadata 1.1 properties + # (ref https://www.iptc.org/std/videometadatahub/recommendation/IPTC-VideoMetadataHub-props-Rec_1.1.html) + SnapshotLink => { Groups => { 2 => 'Image' }, List => 'Bag', Struct => \%sLinkedImage, Name => 'Snapshot' }, + # new IPTC video metadata 1.2 properties + # (ref http://www.iptc.org/std/videometadatahub/recommendation/IPTC-VideoMetadataHub-props-Rec_1.2.html) + RecDevice => { + Groups => { 2 => 'Device' }, + Struct => { + STRUCT_NAME => 'Device', + NAMESPACE => 'Iptc4xmpExt', + Manufacturer => { }, + ModelName => { }, + SerialNumber => { }, + AttLensDescription => { }, + OwnersDeviceId => { }, + }, + }, + PlanningRef => { List => 'Bag', Struct => \%sEntityWithRole }, + audioBitsPerSample => { Groups => { 2 => 'Audio' }, Writable => 'integer' }, + # new IPTC video metadata 1.3 properties + # (ref https://iptc.org/std/videometadatahub/recommendation/IPTC-VideoMetadataHub-props-Rec_1.3.html) + metadataLastEdited => { Groups => { 2 => 'Time' }, %dateTimeInfo }, + metadataLastEditor => { Struct => \%sEntity }, + metadataAuthority => { Struct => \%sEntity }, + parentId => { Name => 'ParentID' }, + # new IPTC Extension schema 1.5 property + ImageRegion => { Groups => { 2 => 'Image' }, List => 'Bag', Struct => \%sImageRegion }, + # new Extension 1.6 property + EventId => { Name => 'EventID', List => 'Bag' }, +); + +#------------------------------------------------------------------------------ +# PRISM +# +# NOTE: The "Avoid" flag is set for all PRISM tags (via tag table AVOID flag) + +# my %obsolete = ( +# Notes => 'obsolete in 2.0', +# ValueConvInv => sub { +# my ($val, $self) = @_; +# unless ($self->Options('IgnoreMinorErrors')) { +# warn "Warning: [minor] Attempt to write obsolete tag\n"; +# return undef; +# } +# return $val; +# } +# ); + +# PRISM structure definitions +my %prismPublicationDate = ( + STRUCT_NAME => 'prismPublicationDate', + NAMESPACE => 'prism', + date => { %dateTimeInfo, Groups => { 2 => 'Time'} }, + 'a-platform'=> { }, +); + +# Publishing Requirements for Industry Standard Metadata (prism) (ref 2) +%Image::ExifTool::XMP::prism = ( + %xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-prism', 2 => 'Document' }, + NAMESPACE => 'prism', + AVOID => 1, + NOTES => q{ + Publishing Requirements for Industry Standard Metadata 3.0 namespace + tags. (see + L<https://www.w3.org/Submission/2020/SUBM-prism-20200910/prism-basic.html/>) + }, + academicField => { }, # (3.0) + aggregateIssueNumber => { Writable => 'integer' }, # (3.0) + aggregationType => { List => 'Bag' }, + alternateTitle => { + List => 'Bag', + Struct => { # (becomes a structure in 3.0) + STRUCT_NAME => 'prismAlternateTitle', + NAMESPACE => 'prism', + text => { }, + 'a-platform'=> { }, + 'a-lang' => { }, + }, + }, + blogTitle => { }, # (3.0) + blogURL => { }, # (3.0) + bookEdition => { }, # (3.0) + byteCount => { Writable => 'integer' }, + channel => { + List => 'Bag', + Struct => { # (becomes a structure in 3.0) + STRUCT_NAME => 'prismChannel', + NAMESPACE => 'prism', + channel => { }, + subchannel1 => { }, + subchannel2 => { }, + subchannel3 => { }, + subchannel4 => { }, + 'a-lang' => { }, + }, + }, + complianceProfile=>{ PrintConv => { three => 'Three' } }, + contentType => { }, # (3.0) + copyrightYear => { }, # (3.0) + # copyright => { Groups => { 2 => 'Author' } }, # (deprecated in 3.0) + corporateEntity => { List => 'Bag' }, + coverDate => { %dateTimeInfo, Groups => { 2 => 'Time'} }, + coverDisplayDate=> { }, + creationDate => { %dateTimeInfo, Groups => { 2 => 'Time'} }, + dateRecieved => { %dateTimeInfo, Groups => { 2 => 'Time'} }, + device => { }, # (3.0) + distributor => { }, + doi => { Name => 'DOI', Description => 'Digital Object Identifier' }, + edition => { }, + eIssn => { }, + #embargoDate => { List => 'Bag', %dateTimeInfo, Groups => { 2 => 'Time'} }, # (deprecated in 3.0) + endingPage => { }, + event => { List => 'Bag' }, + #expirationDate => { List => 'Bag', %dateTimeInfo, Groups => { 2 => 'Time'} }, # (deprecated in 3.0) + genre => { List => 'Bag' }, + hasAlternative => { List => 'Bag' }, + hasCorrection => { + Struct => { # (becomes a structure in 3.0) + STRUCT_NAME => 'prismHasCorrection', + NAMESPACE => 'prism', + text => { }, + 'a-platform'=> { }, + 'a-lang' => { }, + }, + }, + # hasPreviousVersion => { }, # (not in 3.0) + hasTranslation => { List => 'Bag' }, + industry => { List => 'Bag' }, + isAlternativeOf => { List => 'Bag' }, # (3.0) + isbn => { Name => 'ISBN', List => 'Bag' }, # 2.1 (becomes a list in 3.0) + isCorrectionOf => { List => 'Bag' }, + issn => { Name => 'ISSN' }, + issueIdentifier => { }, + issueName => { }, + issueTeaser => { }, # (3.0) + issueType => { }, # (3.0) + isTranslationOf => { }, + keyword => { List => 'Bag' }, + killDate => { + Struct => { # (becomes a structure in 3.0) + STRUCT_NAME => 'prismKillDate', + NAMESPACE => 'prism', + date => { %dateTimeInfo, Groups => { 2 => 'Time'} }, + 'a-platform'=> { }, #PH (missed in spec?) + }, + }, + 'link' => { List => 'Bag' }, # (3.0) + location => { List => 'Bag' }, + # metadataContainer => { }, (not valid for PRISM XMP) + modificationDate=> { %dateTimeInfo, Groups => { 2 => 'Time'} }, + nationalCatalogNumber => { }, # (3.0) + number => { }, + object => { List => 'Bag' }, + onSaleDate => { # (3.0) + List => 'Bag', + Struct => { + STRUCT_NAME => 'prismOnSaleDate', + NAMESPACE => 'prism', + date => { %dateTimeInfo, Groups => { 2 => 'Time'} }, + 'a-platform'=> { }, + }, + }, + onSaleDay => { # (3.0) + List => 'Bag', + Struct => { + STRUCT_NAME => 'prismOnSaleDay', + NAMESPACE => 'prism', + day => { }, #PH (not named in spec) + 'a-platform'=> { }, + }, + }, + offSaleDate => { # (3.0) + List => 'Bag', + Struct => { + STRUCT_NAME => 'prismOffSaleDate', + NAMESPACE => 'prism', + date => { %dateTimeInfo, Groups => { 2 => 'Time'} }, + 'a-platform'=> { }, + }, + }, + organization => { List => 'Bag' }, + originPlatform => { + List => 'Bag', + PrintConv => { + email => 'E-Mail', + mobile => 'Mobile', + broadcast => 'Broadcast', + web => 'Web', + 'print' => 'Print', + recordableMedia => 'Recordable Media', + other => 'Other', + }, + }, + pageCount => { Writable => 'integer' }, # (3.0) + pageProgressionDirection => { # (3.0) + PrintConv => { LTR => 'Left to Right', RTL => 'Right to Left' }, + }, + pageRange => { List => 'Bag' }, + person => { }, + platform => { }, # (3.0) + productCode => { }, # (3.0) + profession => { }, # (3.0) + publicationDate => { + List => 'Bag', + Struct => \%prismPublicationDate, # (becomes a structure in 3.0) + }, + publicationDisplayDate => { # (3.0) + List => 'Bag', + Struct => \%prismPublicationDate, + }, + publicationName => { }, + publishingFrequency => { }, # (3.0) + rating => { }, + # rightsAgent => { }, # (deprecated in 3.0) + samplePageRange => { }, # (3.0) + section => { }, + sellingAgency => { }, # (3.0) + seriesNumber => { Writable => 'integer' }, # (3.0) + seriesTitle => { }, # (3.0) + sport => { }, # (3.0) + startingPage => { }, + subsection1 => { }, + subsection2 => { }, + subsection3 => { }, + subsection4 => { }, + subtitle => { }, # (3.0) + supplementDisplayID => { }, # (3.0) + supplementStartingPage => { }, # (3.0) + supplementTitle => { }, # (3.0) + teaser => { List => 'Bag' }, + ticker => { List => 'Bag' }, + timePeriod => { }, + url => { + Name => 'URL', + List => 'Bag', + Struct => { # (becomes a structure in 3.0) + STRUCT_NAME => 'prismUrl', + NAMESPACE => 'prism', + url => { }, + 'a-platform'=> { }, + }, + }, + uspsNumber => { }, # (3.0) + versionIdentifier => { }, + volume => { }, + wordCount => { Writable => 'integer' }, +# tags that existed in version 1.3 +# category => { %obsolete, List => 'Bag' }, +# hasFormat => { %obsolete, List => 'Bag' }, +# hasPart => { %obsolete, List => 'Bag' }, +# isFormatOf => { %obsolete, List => 'Bag' }, +# isPartOf => { %obsolete }, +# isReferencedBy => { %obsolete, List => 'Bag' }, +# isRequiredBy => { %obsolete, List => 'Bag' }, +# isVersionOf => { %obsolete }, +# objectTitle => { %obsolete, List => 'Bag' }, +# receptionDate => { %obsolete }, +# references => { %obsolete, List => 'Bag' }, +# requires => { %obsolete, List => 'Bag' }, +# tags in older versions +# page +# contentLength +# creationTime +# expirationTime +# hasVersion +# isAlternativeFor +# isBasedOn +# isBasisFor +# modificationTime +# publicationTime +# receptionTime +# releaseTime +); + +# PRISM Rights Language namespace (prl) (ref 2) +%Image::ExifTool::XMP::prl = ( + %xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-prl', 2 => 'Document' }, + NAMESPACE => 'prl', + AVOID => 1, + NOTES => q{ + PRISM Rights Language 2.1 namespace tags. These tags have been deprecated + since the release of the PRISM Usage Rights 3.0. (see + L<https://www.w3.org/submissions/2020/SUBM-prism-20200910/prism-image.html>) + }, + geography => { List => 'Bag' }, + industry => { List => 'Bag' }, + usage => { List => 'Bag' }, +); + +# PRISM Usage Rights namespace (prismusagerights) (ref 2) +%Image::ExifTool::XMP::pur = ( + %xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-pur', 2 => 'Document' }, + NAMESPACE => 'pur', + AVOID => 1, + NOTES => q{ + PRISM Usage Rights 3.0 namespace tags. (see + L<http://www.prismstandard.org/>) + }, + adultContentWarning => { List => 'Bag' }, + agreement => { List => 'Bag' }, + copyright => { + # (not clear in 3.0 spec, which lists only "bag Text", and called + # "copyrightDate" instead of "copyright" the PRISM basic 3.0 spec) + Writable => 'lang-alt', + Groups => { 2 => 'Author' }, + }, + creditLine => { List => 'Bag' }, + embargoDate => { List => 'Bag', %dateTimeInfo, Groups => { 2 => 'Time'} }, + exclusivityEndDate => { List => 'Bag', %dateTimeInfo, Groups => { 2 => 'Time'} }, + expirationDate => { List => 'Bag', %dateTimeInfo, Groups => { 2 => 'Time'} }, + imageSizeRestriction=> { }, + optionEndDate => { List => 'Bag', %dateTimeInfo, Groups => { 2 => 'Time'} }, + permissions => { List => 'Bag' }, + restrictions => { List => 'Bag' }, + reuseProhibited => { Writable => 'boolean' }, + rightsAgent => { }, + rightsOwner => { }, + # usageFee => { List => 'Bag' }, # (not in 3.0) +); + +# PRISM Metadata for Images namespace (pmi) (ref 2) +%Image::ExifTool::XMP::pmi = ( + %xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-pmi', 2 => 'Image' }, + NAMESPACE => 'pmi', + AVOID => 1, + NOTES => q{ + PRISM Metadata for Images 3.0 namespace tags. (see + L<http://www.prismstandard.org/>) + }, + color => { + PrintConv => { + bw => 'BW', + color => 'Color', + sepia => 'Sepia', + duotone => 'Duotone', + tritone => 'Tritone', + quadtone => 'Quadtone', + }, + }, + contactInfo => { }, + displayName => { }, + distributorProductID => { }, + eventAlias => { }, + eventEnd => { }, + eventStart => { }, + eventSubtype => { }, + eventType => { }, + field => { }, + framing => { }, + location => { }, + make => { }, + manufacturer => { }, + model => { }, + modelYear => { }, + objectDescription=>{ }, + objectSubtype => { }, + objectType => { }, + orientation => { + PrintConv => { + horizontal => 'Horizontal', + vertical => 'Vertical', + } + }, + positionDescriptor => { }, + productID => { }, + productIDType => { }, + season => { + PrintConv => { + spring => 'Spring', + summer => 'Summer', + fall => 'Fall', + winter => 'Winter', + }, + }, + sequenceName => { }, + sequenceNumber => { }, + sequenceTotalNumber => { }, + setting => { }, + shootID => { }, + slideshowName => { }, + slideshowNumber => { Writable => 'integer' }, + slideshowTotalNumber => { Writable => 'integer' }, + viewpoint => { }, + visualTechnique => { }, +); + +# PRISM Recipe Metadata (prm) (ref 2) +%Image::ExifTool::XMP::prm = ( + %xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-prm', 2 => 'Document' }, + NAMESPACE => 'prm', + AVOID => 1, + NOTES => q{ + PRISM Recipe Metadata 3.0 namespace tags. (see + L<http://www.prismstandard.org/>) + }, + cookingEquipment => { }, + cookingMethod => { }, + course => { }, + cuisine => { }, + dietaryNeeds => { }, + dishType => { }, + duration => { }, + ingredientExclusion => { }, + mainIngredient => { }, + meal => { }, + recipeEndingPage => { }, + recipePageRange => { }, + recipeSource => { }, + recipeStartingPage => { }, + recipeTitle => { }, + servingSize => { }, + skillLevel => { }, + specialOccasion => { }, + yield => { }, +); + +#------------------------------------------------------------------------------ + +# DICOM namespace properties (DICOM) (ref PH, written by CS3) +%Image::ExifTool::XMP::DICOM = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-DICOM', 2 => 'Image' }, + NAMESPACE => 'DICOM', + NOTES => q{ + DICOM namespace tags. These XMP tags allow some DICOM information to be + stored in files of other than DICOM format. See the + L<DICOM Tags documentation|Image::ExifTool::TagNames/DICOM Tags> for a list + of tags available in DICOM-format files. + }, + # change some tag names to correspond with DICOM tags + PatientName => { }, + PatientID => { }, + PatientSex => { }, + PatientDOB => { + Name => 'PatientBirthDate', + Groups => { 2 => 'Time' }, + %dateTimeInfo, + }, + StudyID => { }, + StudyPhysician => { }, + StudyDateTime => { Groups => { 2 => 'Time' }, %dateTimeInfo }, + StudyDescription => { }, + SeriesNumber => { }, + SeriesModality => { }, + SeriesDateTime => { Groups => { 2 => 'Time' }, %dateTimeInfo }, + SeriesDescription => { }, + EquipmentInstitution => { }, + EquipmentManufacturer => { }, +); + +# PixelLive namespace properties (PixelLive) (ref 3) +%Image::ExifTool::XMP::PixelLive = ( + GROUPS => { 1 => 'XMP-PixelLive', 2 => 'Image' }, + NAMESPACE => 'PixelLive', + AVOID => 1, + NOTES => q{ + PixelLive namespace tags. These tags are not writable because they are very + uncommon and I haven't been able to locate a reference which gives the + namespace URI. + }, + AUTHOR => { Name => 'Author', Groups => { 2 => 'Author' } }, + COMMENTS => { Name => 'Comments' }, + COPYRIGHT => { Name => 'Copyright', Groups => { 2 => 'Author' } }, + DATE => { Name => 'Date', Groups => { 2 => 'Time' } }, + GENRE => { Name => 'Genre' }, + TITLE => { Name => 'Title' }, +); + +# Extensis Portfolio tags (extensis) (ref 11) +%Image::ExifTool::XMP::extensis = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-extensis', 2 => 'Image' }, + NAMESPACE => 'extensis', + NOTES => 'Tags used by Extensis Portfolio.', + Approved => { Writable => 'boolean' }, + ApprovedBy => { }, + ClientName => { }, + JobName => { }, + JobStatus => { }, + RoutedTo => { }, + RoutingNotes => { }, + WorkToDo => { }, +); + +# IDimager structures (ref PH) +my %sTagStruct; +%sTagStruct = ( + STRUCT_NAME => 'TagStructure', + NAMESPACE => 'ics', + LabelName => { }, + Reference => { }, + ParentReference => { }, + SubLabels => { Struct => \%sTagStruct, List => 'Bag' }, +); +my %sSubVersion = ( + STRUCT_NAME => 'SubVersion', + NAMESPACE => 'ics', + VersRef => { }, + FileName => { }, +); + +# IDimager namespace (ics) (ref PH) +%Image::ExifTool::XMP::ics = ( + %xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-ics', 2 => 'Image' }, + NAMESPACE => 'ics', + NOTES => q{ + Tags used by IDimager. Nested TagStructure structures are unrolled to an + arbitrary depth of 6 to avoid infinite recursion. + }, + ImageRef => { }, + TagStructure => { Struct => \%sTagStruct, List => 'Bag' }, + TagStructureLabelName => { Name => 'LabelName1', Flat => 1 }, + TagStructureReference => { Name => 'Reference1', Flat => 1 }, + TagStructureSubLabels => { Name => 'SubLabels1', Flat => 1 }, + TagStructureParentReference => { Name => 'ParentReference1', Flat => 1 }, + TagStructureSubLabelsLabelName => { Name => 'LabelName2', Flat => 1 }, + TagStructureSubLabelsReference => { Name => 'Reference2', Flat => 1 }, + TagStructureSubLabelsSubLabels => { Name => 'SubLabels2', Flat => 1 }, + TagStructureSubLabelsParentReference => { Name => 'ParentReference2', Flat => 1 }, + TagStructureSubLabelsSubLabelsLabelName => { Name => 'LabelName3', Flat => 1 }, + TagStructureSubLabelsSubLabelsReference => { Name => 'Reference3', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabels => { Name => 'SubLabels3', Flat => 1 }, + TagStructureSubLabelsSubLabelsParentReference => { Name => 'ParentReference3', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabelsLabelName => { Name => 'LabelName4', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabelsReference => { Name => 'Reference4', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabelsSubLabels => { Name => 'SubLabels4', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabelsParentReference => { Name => 'ParentReference4', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabelsSubLabelsLabelName => { Name => 'LabelName5', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabelsSubLabelsReference => { Name => 'Reference5', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabels => { Name => 'SubLabels5', Flat => 1, NoSubStruct => 1 }, # break infinite recursion + TagStructureSubLabelsSubLabelsSubLabelsSubLabelsParentReference => { Name => 'ParentReference5', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabelsLabelName => { Name => 'LabelName6', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabelsReference => { Name => 'Reference6', Flat => 1 }, + TagStructureSubLabelsSubLabelsSubLabelsSubLabelsSubLabelsParentReference => { Name => 'ParentReference6', Flat => 1 }, + SubVersions => { Struct => \%sSubVersion, List => 'Bag' }, + SubVersionsVersRef => { Name => 'SubVersionReference', Flat => 1 }, + SubVersionsFileName => { Name => 'SubVersionFileName', Flat => 1 }, + TimeStamp => { Avoid => 1, Groups => { 2 => 'Time' }, %dateTimeInfo }, + AppVersion => { Avoid => 1 }, +); + +# ACDSee namespace (acdsee) (ref PH) +%Image::ExifTool::XMP::acdsee = ( + %xmpTableDefaults, + GROUPS => { 0 => 'XMP', 1 => 'XMP-acdsee', 2 => 'Image' }, + NAMESPACE => 'acdsee', + AVOID => 1, + NOTES => q{ + ACD Systems ACDSee namespace tags. + + (A note to software developers: Re-inventing your own private tags instead + of using the equivalent tags in standard XMP namespaces defeats one of the + most valuable features of metadata: interoperability. Your applications + mumble to themselves instead of speaking out for the rest of the world to + hear.) + }, + author => { Groups => { 2 => 'Author' } }, + caption => { }, + categories => { }, + collections=> { }, + datetime => { Name => 'DateTime', Groups => { 2 => 'Time' }, %dateTimeInfo }, + keywords => { List => 'Bag' }, + notes => { }, + rating => { Writable => 'real' }, # integer? + tagged => { Writable => 'boolean' }, + rawrppused => { Writable => 'boolean' }, + rpp => { + Name => 'RPP', + Writable => 'lang-alt', + Notes => 'raw processing settings in XML format', + Binary => 1, + }, + dpp => { + Name => 'DPP', + Writable => 'lang-alt', + Notes => 'newer version of XML raw processing settings', + Binary => 1, + }, + # more tags (ref forum6840) + FixtureIdentifier => { }, + EditStatus => { }, + ReleaseDate => { }, + ReleaseTime => { }, + OriginatingProgram => { }, + ObjectCycle => { }, + Snapshots => { List => 'Bag', Binary => 1 }, +); + +# Picture Licensing Universal System namespace properties (xmpPLUS) +%Image::ExifTool::XMP::xmpPLUS = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-xmpPLUS', 2 => 'Author' }, + NAMESPACE => 'xmpPLUS', + AVOID => 1, + NOTES => q{ + XMP Picture Licensing Universal System (PLUS) tags as written by some older + Adobe applications. See L<PLUS XMP Tags|Image::ExifTool::TagNames/PLUS XMP Tags> + for the current PLUS tags. + }, + CreditLineReq => { Writable => 'boolean' }, + ReuseAllowed => { Writable => 'boolean' }, +); + +%Image::ExifTool::XMP::panorama = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-panorama', 2 => 'Image' }, + NAMESPACE => 'panorama', + NOTES => 'Adobe Photoshop Panorama-profile tags.', + Transformation => { }, + VirtualFocalLength => { Writable => 'real' }, + VirtualImageXCenter => { Writable => 'real' }, + VirtualImageYCenter => { Writable => 'real' }, +); + +# Creative Commons namespace properties (cc) (ref 5) +%Image::ExifTool::XMP::cc = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-cc', 2 => 'Author' }, + NAMESPACE => 'cc', + NOTES => q{ + Creative Commons namespace tags. Note that the CC specification for XMP is + non-existent, so ExifTool must make some assumptions about the format of the + specific properties in XMP (see L<http://creativecommons.org/ns>). + }, + # Work properties + license => { Resource => 1 }, + attributionName => { }, + attributionURL => { Resource => 1 }, + morePermissions => { Resource => 1 }, + useGuidelines => { Resource => 1 }, + # License properties + permits => { + List => 'Bag', + Resource => 1, + PrintConv => { + 'cc:Sharing' => 'Sharing', + 'cc:DerivativeWorks' => 'Derivative Works', + 'cc:Reproduction' => 'Reproduction', + 'cc:Distribution' => 'Distribution', + }, + }, + requires => { + List => 'Bag', + Resource => 1, + PrintConv => { + 'cc:Copyleft' => 'Copyleft', + 'cc:LesserCopyleft' => 'Lesser Copyleft', + 'cc:SourceCode' => 'Source Code', + 'cc:ShareAlike' => 'Share Alike', + 'cc:Notice' => 'Notice', + 'cc:Attribution' => 'Attribution', + }, + }, + prohibits => { + List => 'Bag', + Resource => 1, + PrintConv => { + 'cc:HighIncomeNationUse' => 'High Income Nation Use', + 'cc:CommercialUse' => 'Commercial Use', + }, + }, + jurisdiction => { Resource => 1 }, + legalcode => { Name => 'LegalCode', Resource => 1 }, + deprecatedOn => { %dateTimeInfo, Groups => { 2 => 'Time' } }, +); + +# Description Explorer namespace properties (dex) (ref 6) +%Image::ExifTool::XMP::dex = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-dex', 2 => 'Image' }, + NAMESPACE => 'dex', + NOTES => q{ + Description Explorer namespace tags. These tags are not very common. The + Source and Rating tags are avoided when writing due to name conflicts with + other XMP tags. (see L<http://www.optimasc.com/products/fileid/>) + }, + crc32 => { Name => 'CRC32', Writable => 'integer' }, + source => { Avoid => 1 }, + shortdescription => { + Name => 'ShortDescription', + Writable => 'lang-alt', + }, + licensetype => { + Name => 'LicenseType', + PrintConv => { + unknown => 'Unknown', + shareware => 'Shareware', + freeware => 'Freeware', + adware => 'Adware', + demo => 'Demo', + commercial => 'Commercial', + 'public domain' => 'Public Domain', + 'open source' => 'Open Source', + }, + }, + revision => { }, + rating => { Avoid => 1 }, + os => { Name => 'OS', Writable => 'integer' }, + ffid => { Name => 'FFID' }, +); + +# iView MediaPro namespace properties (mediapro) (ref PH) +%Image::ExifTool::XMP::MediaPro = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-mediapro', 2 => 'Image' }, + NAMESPACE => 'mediapro', + NOTES => 'iView MediaPro namespace tags.', + Event => { + Avoid => 1, + Notes => 'avoided due to conflict with XMP-iptcExt:Event', + }, + Location => { + Avoid => 1, + Groups => { 2 => 'Location' }, + Notes => 'avoided due to conflict with XMP-iptcCore:Location', + }, + Status => { }, + People => { List => 'Bag' }, + UserFields => { List => 'Bag' }, + CatalogSets => { List => 'Bag' }, +); + +# Microsoft ExpressionMedia namespace properties (expressionmedia) +# (ref https://exiftool.org/forum/index.php/topic,4235.0.html) +%Image::ExifTool::XMP::ExpressionMedia = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-expressionmedia', 2 => 'Image' }, + NAMESPACE => 'expressionmedia', + AVOID => 1, + NOTES => q{ + Microsoft Expression Media namespace tags. These tags are avoided when + writing due to name conflicts with tags in other schemas. + }, + Event => { }, + Status => { }, + People => { List => 'Bag' }, + CatalogSets => { List => 'Bag' }, +); + +# DigiKam namespace tags (ref PH) +%Image::ExifTool::XMP::digiKam = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-digiKam', 2 => 'Image' }, + NAMESPACE => 'digiKam', + NOTES => 'DigiKam namespace tags.', + CaptionsAuthorNames => { Writable => 'lang-alt' }, + CaptionsDateTimeStamps => { Writable => 'lang-alt', Groups => { 2 => 'Time' } }, + TagsList => { List => 'Seq' }, + ColorLabel => { }, + PickLabel => { }, + ImageHistory => { Avoid => 1, Notes => 'different format from EXIF:ImageHistory' }, + LensCorrectionSettings => { }, + ImageUniqueID => { Avoid => 1 }, + picasawebGPhotoId => { }, #forum14108 +); + +# SWF namespace tags (ref PH) +%Image::ExifTool::XMP::swf = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-swf', 2 => 'Image' }, + NAMESPACE => 'swf', + NOTES => 'Adobe SWF namespace tags.', + type => { Avoid => 1 }, + bgalpha => { Name => 'BackgroundAlpha', Writable => 'integer' }, + forwardlock => { Name => 'ForwardLock', Writable => 'boolean' }, + maxstorage => { Name => 'MaxStorage', Writable => 'integer' }, # (CS5) +); + +# Sony Ericsson cell phone location tags +# refs: http://www.opencellid.org/api +# http://zonetag.research.yahoo.com/faq_location.php +# http://www.cs.columbia.edu/sip/drafts/LIF%20TS%20101%20v2.0.0.pdf +%Image::ExifTool::XMP::cell = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-cell', 2 => 'Location' }, + NAMESPACE => 'cell', + NOTES => 'Location tags written by some Sony Ericsson phones.', + mcc => { Name => 'MobileCountryCode' }, + mnc => { Name => 'MobileNetworkCode' }, + lac => { Name => 'LocationAreaCode' }, + cellid => { Name => 'CellTowerID' }, + cgi => { Name => 'CellGlobalID' }, + r => { Name => 'CellR' }, # (what is this? Radius?) +); + +# Apple adjustment settings (ref PH) +%Image::ExifTool::XMP::aas = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-aas', 2 => 'Image' }, + NAMESPACE => 'aas', + NOTES => 'Apple Adjustment Settings used by iPhone/iPad.', + CropX => { Writable => 'integer', Avoid => 1 }, + CropY => { Writable => 'integer', Avoid => 1 }, + CropW => { Writable => 'integer', Avoid => 1 }, + CropH => { Writable => 'integer', Avoid => 1 }, + AffineA => { Writable => 'real' }, + AffineB => { Writable => 'real' }, + AffineC => { Writable => 'real' }, + AffineD => { Writable => 'real' }, + AffineX => { Writable => 'real' }, + AffineY => { Writable => 'real' }, + Vibrance => { Writable => 'real', Avoid => 1 }, + Curve0x => { Writable => 'real' }, + Curve0y => { Writable => 'real' }, + Curve1x => { Writable => 'real' }, + Curve1y => { Writable => 'real' }, + Curve2x => { Writable => 'real' }, + Curve2y => { Writable => 'real' }, + Curve3x => { Writable => 'real' }, + Curve3y => { Writable => 'real' }, + Curve4x => { Writable => 'real' }, + Curve4y => { Writable => 'real' }, + Shadows => { Writable => 'real', Avoid => 1 }, + Highlights => { Writable => 'real', Avoid => 1 }, + # the following from StarGeek + FaceBalanceOrigI => { Writable => 'real' }, + FaceBalanceOrigQ => { Writable => 'real' }, + FaceBalanceStrength => { Writable => 'real' }, + FaceBalanceWarmth => { Writable => 'real' }, +); + +# Adobe creatorAtom properties (ref PH) +%Image::ExifTool::XMP::creatorAtom = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-creatorAtom', 2 => 'Image' }, + NAMESPACE => 'creatorAtom', + NOTES => 'Adobe creatorAtom tags, written by After Effects.', + macAtom => { + Struct => { + STRUCT_NAME => 'MacAtom', + NAMESPACE => 'creatorAtom', + applicationCode => { }, + invocationAppleEvent => { }, + posixProjectPath => { }, + }, + }, + windowsAtom => { + Struct => { + STRUCT_NAME => 'WindowsAtom', + NAMESPACE => 'creatorAtom', + extension => { }, + invocationFlags => { }, + uncProjectPath => { }, + }, + }, + aeProjectLink => { # (After Effects Project Link) + Struct => { + STRUCT_NAME => 'AEProjectLink', + NAMESPACE => 'creatorAtom', + renderTimeStamp => { Writable => 'integer' }, + compositionID => { }, + renderQueueItemID => { }, + renderOutputModuleIndex => { }, + fullPath => { }, + }, + }, +); + +# FastPictureViewer namespace properties (http://www.fastpictureviewer.com/help/#rtfcomments) +%Image::ExifTool::XMP::fpv = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-fpv', 2 => 'Image' }, + NAMESPACE => 'fpv', + NOTES => q{ + Fast Picture Viewer tags (see + L<http://www.fastpictureviewer.com/help/#rtfcomments>). + }, + RichTextComment => { }, +); + +# Apple FaceInfo namespace properties (ref PH) +%Image::ExifTool::XMP::apple_fi = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-apple-fi', 2 => 'Image' }, + NAMESPACE => 'apple-fi', + NOTES => q{ + Face information tags written by the Apple iPhone 5 inside the mwg-rs + RegionExtensions. + }, + Timestamp => { + Name => 'TimeStamp', + Writable => 'integer', + # (don't know how to convert this) + }, + FaceID => { Writable => 'integer' }, + AngleInfoRoll => { Writable => 'integer' }, + AngleInfoYaw => { Writable => 'integer' }, + ConfidenceLevel => { Writable => 'integer' }, +); + +# Google audio namespace +%Image::ExifTool::XMP::GAudio = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-GAudio', 2 => 'Audio' }, + NAMESPACE => 'GAudio', + Data => { + Name => 'AudioData', + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, + Mime => { Name => 'AudioMimeType' }, +); + +# Google image namespace +%Image::ExifTool::XMP::GImage = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-GImage', 2 => 'Image' }, + NAMESPACE => 'GImage', + Data => { + Name => 'ImageData', + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, + Mime => { Name => 'ImageMimeType' }, +); + +# Google panorama namespace properties +# (ref https://exiftool.org/forum/index.php/topic,4569.0.html) +%Image::ExifTool::XMP::GPano = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-GPano', 2 => 'Image' }, + NAMESPACE => 'GPano', + NOTES => q{ + Panorama tags written by Google Photosphere. See + L<https://developers.google.com/panorama/metadata/> for the specification. + }, + UsePanoramaViewer => { Writable => 'boolean' }, + CaptureSoftware => { }, + StitchingSoftware => { }, + ProjectionType => { }, + PoseHeadingDegrees => { Writable => 'real' }, + PosePitchDegrees => { Writable => 'real' }, + PoseRollDegrees => { Writable => 'real' }, + InitialViewHeadingDegrees => { Writable => 'real' }, + InitialViewPitchDegrees => { Writable => 'real' }, + InitialViewRollDegrees => { Writable => 'real' }, + InitialHorizontalFOVDegrees => { Writable => 'real' }, + InitialVerticalFOVDegrees => { Writable => 'real' }, + FirstPhotoDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + LastPhotoDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, + SourcePhotosCount => { Writable => 'integer' }, + ExposureLockUsed => { Writable => 'boolean' }, + CroppedAreaImageWidthPixels => { Writable => 'real' }, + CroppedAreaImageHeightPixels => { Writable => 'real' }, + FullPanoWidthPixels => { Writable => 'real' }, + FullPanoHeightPixels => { Writable => 'real' }, + CroppedAreaLeftPixels => { Writable => 'real' }, + CroppedAreaTopPixels => { Writable => 'real' }, + InitialCameraDolly => { Writable => 'real' }, + # (the following have been observed, but are not in the specification) + LargestValidInteriorRectLeft => { Writable => 'real' }, + LargestValidInteriorRectTop => { Writable => 'real' }, + LargestValidInteriorRectWidth => { Writable => 'real' }, + LargestValidInteriorRectHeight => { Writable => 'real' }, +); + +# Google Spherical Images namespace (ref https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md) +%Image::ExifTool::XMP::GSpherical = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-GSpherical', 2 => 'Image' }, + WRITE_GROUP => 'GSpherical', # write in special location for video files + NAMESPACE => 'GSpherical', + AVOID => 1, + NOTES => q{ + Not actually XMP. These RDF/XML tags are used in Google spherical MP4 + videos. These tags are written into the video track of MOV/MP4 files, and + not at the top level like other XMP tags. See + L<https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md> + for the specification. + }, + # (avoid due to conflicts with XMP-GPano tags) + Spherical => { Writable => 'boolean' }, + Stitched => { Writable => 'boolean' }, + StitchingSoftware => { }, + ProjectionType => { }, + StereoMode => { }, + SourceCount => { Writable => 'integer' }, + InitialViewHeadingDegrees => { Writable => 'real' }, + InitialViewPitchDegrees => { Writable => 'real' }, + InitialViewRollDegrees => { Writable => 'real' }, + Timestamp => { + Name => 'TimeStamp', + Groups => { 2 => 'Time' }, + Writable => 'integer', + Shift => 'Time', + ValueConv => 'ConvertUnixTime($val)', #(NC) + ValueConvInv => 'GetUnixTime($val)', + PrintConv => '$self->ConvertDateTime($val)', + PrintConvInv => '$self->InverseDateTime($val)', + }, + FullPanoWidthPixels => { Writable => 'integer' }, + FullPanoHeightPixels => { Writable => 'integer' }, + CroppedAreaImageWidthPixels => { Writable => 'integer' }, + CroppedAreaImageHeightPixels=> { Writable => 'integer' }, + CroppedAreaLeftPixels => { Writable => 'integer' }, + CroppedAreaTopPixels => { Writable => 'integer' }, +); + +# Google depthmap information (ref https://developers.google.com/depthmap-metadata/reference) +%Image::ExifTool::XMP::GDepth = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-GDepth', 2 => 'Image' }, + NAMESPACE => 'GDepth', + AVOID => 1, # (too many potential tag name conflicts) + NOTES => q{ + Google depthmap information. See + L<https://developers.google.com/depthmap-metadata/> for the specification. + }, + WRITABLE => 'string', # (default to string-type tags) + PRIORITY => 0, + Format => { + PrintConv => { + RangeInverse => 'RangeInverse', + RangeLinear => 'RangeLinear', + }, + }, + Near => { Writable => 'real' }, + Far => { Writable => 'real' }, + Mime => { }, + Data => { + Name => 'DepthImage', + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, + Units => { }, + MeasureType => { + PrintConv => { + OpticalAxis => 'OpticalAxis', + OpticalRay => 'OpticalRay', + }, + }, + ConfidenceMime => { }, + Confidence => { + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, + Manufacturer=> { }, + Model => { }, + Software => { }, + ImageWidth => { Writable => 'real' }, + ImageHeight => { Writable => 'real' }, +); + +# Google focus namespace +%Image::ExifTool::XMP::GFocus = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-GFocus', 2 => 'Image' }, + NAMESPACE => 'GFocus', + NOTES => 'Focus information found in Google depthmap images.', + BlurAtInfinity => { Writable => 'real' }, + FocalDistance => { Writable => 'real' }, + FocalPointX => { Writable => 'real' }, + FocalPointY => { Writable => 'real' }, +); + +# Google camera namespace (ref PH) +%Image::ExifTool::XMP::GCamera = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-GCamera', 2 => 'Camera' }, + NAMESPACE => 'GCamera', + NOTES => 'Camera information found in Google panorama images.', + BurstID => { }, + BurstPrimary => { }, + PortraitNote => { }, + PortraitRequest => { + Notes => 'High Definition Render Pipeline (HDRP) data', #PH (guess) + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, + PortraitVersion => { }, + SpecialTypeID => { List => 'Bag' }, + PortraitNote => { }, + DisableAutoCreation => { List => 'Bag' }, + hdrp_makernote => { + Name => 'HDRPMakerNote', + # decoded data starts with the following bytes, but nothing yet is known about its contents: + # 48 44 52 50 02 ef 64 35 6d 5e 70 1e 2c ea e3 4c [HDRP..d5m^p.,..L] + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, + MicroVideo => { Writable => 'integer' }, + MicroVideoVersion => { Writable => 'integer' }, + MicroVideoOffset => { Writable => 'integer' }, + MicroVideoPresentationTimestampUs => { Writable => 'integer' }, + shot_log_data => { #forum14108 + Name => 'ShotLogData', + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, +); + +# Google creations namespace (ref PH) +%Image::ExifTool::XMP::GCreations = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-GCreations', 2 => 'Camera' }, + NAMESPACE => 'GCreations', + NOTES => 'Google creations tags.', + CameraBurstID => { }, + Type => { Avoid => 1 }, +); + +# Google depth-map Device namespace (ref 13) +%Image::ExifTool::XMP::Device = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-Device', 2 => 'Camera' }, + NAMESPACE => { Device => 'http://ns.google.com/photos/dd/1.0/device/' }, + NOTES => q{ + Google depth-map Device tags. See + L<https://developer.android.com/training/camera2/Dynamic-depth-v1.0.pdf> for + the specification. + }, + Container => { + Struct => { + STRUCT_NAME => 'DeviceContainer', + NAMESPACE => { Container => 'http://ns.google.com/photos/dd/1.0/container/' }, + Directory => { + List => 'Seq', + Struct => { + STRUCT_NAME => 'DeviceDirectory', + NAMESPACE => { Container => 'http://ns.google.com/photos/dd/1.0/container/' }, + Item => { + Struct => { + STRUCT_NAME => 'DeviceItem', + NAMESPACE => { Item => 'http://ns.google.com/photos/dd/1.0/item/' }, + Mime => { }, + Length => { Writable => 'integer' }, + Padding => { Writable => 'integer' }, + DataURI => { }, + }, + }, + }, + } + }, + }, + Profiles => { + List => 'Seq', + FlatName => '', + Struct => { + STRUCT_NAME => 'DeviceProfiles', + NAMESPACE => { Device => 'http://ns.google.com/photos/dd/1.0/device/' }, + Profile => { + Struct => { + STRUCT_NAME => 'DeviceProfile', + NAMESPACE => { Profile => 'http://ns.google.com/photos/dd/1.0/profile/' }, + CameraIndices => { List => 'Seq', Writable => 'integer' }, + Type => { }, + }, + }, + }, + }, + Cameras => { + List => 'Seq', + FlatName => '', + Struct => { + STRUCT_NAME => 'DeviceCameras', + NAMESPACE => { Device => 'http://ns.google.com/photos/dd/1.0/device/' }, + Camera => { + Struct => { + STRUCT_NAME => 'DeviceCamera', + NAMESPACE => { Camera => 'http://ns.google.com/photos/dd/1.0/camera/' }, + DepthMap => { + Struct => { + STRUCT_NAME => 'DeviceDepthMap', + NAMESPACE => { DepthMap => 'http://ns.google.com/photos/dd/1.0/depthmap/' }, + ConfidenceURI => { }, + DepthURI => { }, + Far => { Writable => 'real' }, + Format => { }, + ItemSemantic=> { }, + MeasureType => { }, + Near => { Writable => 'real' }, + Units => { }, + Software => { }, + FocalTableEntryCount => { Writable => 'integer' }, + FocalTable => { }, # (base64) + }, + }, + Image => { + Struct => { + STRUCT_NAME => 'DeviceImage', + NAMESPACE => { Image => 'http://ns.google.com/photos/dd/1.0/image/' }, + ItemSemantic=> { }, + ItemURI => { }, + }, + }, + ImagingModel => { + Struct => { + STRUCT_NAME => 'DeviceImagingModel', + NAMESPACE => { ImagingModel => 'http://ns.google.com/photos/dd/1.0/imagingmodel/' }, + Distortion => { }, # (base64) + DistortionCount => { Writable => 'integer' }, + FocalLengthX => { Writable => 'real' }, + FocalLengthY => { Writable => 'real' }, + ImageHeight => { Writable => 'integer' }, + ImageWidth => { Writable => 'integer' }, + PixelAspectRatio=> { Writable => 'real' }, + PrincipalPointX => { Writable => 'real' }, + PrincipalPointY => { Writable => 'real' }, + Skew => { Writable => 'real' }, + }, + }, + PointCloud => { + Struct => { + STRUCT_NAME => 'DevicePointCloud', + NAMESPACE => { PointCloud => 'http://ns.google.com/photos/dd/1.0/pointcloud/' }, + PointCloud => { Writable => 'integer' }, + Points => { }, + Metric => { Writable => 'boolean' }, + }, + }, + Pose => { Struct => \%sPose }, + LightEstimate => { + Struct => { + STRUCT_NAME => 'DeviceLightEstimate', + NAMESPACE => { LightEstimate => 'http://ns.google.com/photos/dd/1.0/lightestimate/' }, + ColorCorrectionR => { Writable => 'real' }, + ColorCorrectionG => { Writable => 'real' }, + ColorCorrectionB => { Writable => 'real' }, + PixelIntensity => { Writable => 'real' }, + }, + }, + VendorInfo => { Struct => \%sVendorInfo }, + AppInfo => { Struct => \%sAppInfo }, + Trait => { }, + }, + }, + }, + }, + VendorInfo => { Struct => \%sVendorInfo }, + AppInfo => { Struct => \%sAppInfo }, + EarthPos => { Struct => \%sEarthPose }, + Pose => { Struct => \%sPose }, + Planes => { + List => 'Seq', + FlatName => '', + Struct => { + STRUCT_NAME => 'DevicePlanes', + NAMESPACE => { Device => 'http://ns.google.com/photos/dd/1.0/device/' }, + Plane => { + Struct => { + STRUCT_NAME => 'DevicePlane', + NAMESPACE => { Plane => 'http://ns.google.com/photos/dd/1.0/plane/' }, + Pose => { Struct => \%sPose }, + ExtentX => { Writable => 'real' }, + ExtentZ => { Writable => 'real' }, + BoundaryVertexCount => { Writable => 'integer' }, + Boundary => { }, + }, + }, + }, + }, +); + +# Getty Images namespace (ref PH) +%Image::ExifTool::XMP::GettyImages = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-getty', 2 => 'Image' }, + NAMESPACE => 'GettyImagesGIFT', + NOTES => q{ + The actual Getty Images namespace prefix is "GettyImagesGIFT", which is the + prefix recorded in the file, but ExifTool shortens this for the family 1 + group name. + }, + Personality => { List => 'Bag' }, + OriginalFilename => { Name => 'OriginalFileName' }, + ParentMEID => { }, + # the following from StarGeek + AssetID => { }, + CallForImage => { }, + CameraFilename => { }, + CameraMakeModel => { Avoid => 1 }, + Composition => { }, + CameraSerialNumber => { Avoid => 1 }, + ExclusiveCoverage => { }, + GIFTFtpPriority => { }, + ImageRank => { }, + MediaEventIdDate => { }, + OriginalCreateDateTime => { %dateTimeInfo, Groups => { 2 => 'Time' }, Avoid => 1 }, + ParentMediaEventID => { }, + PrimaryFTP => { List => 'Bag' }, + RoutingDestinations => { List => 'Bag' }, + RoutingExclusions => { List => 'Bag' }, + SecondaryFTP => { List => 'Bag' }, + TimeShot => { }, +); + +# RED smartphone images (ref PH) +%Image::ExifTool::XMP::LImage = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-LImage', 2 => 'Image' }, + NAMESPACE => 'LImage', + NOTES => 'Tags written by RED smartphones.', + MajorVersion => { }, + MinorVersion => { }, + RightAlbedo => { + Notes => 'Right stereoscopic image', + Groups => { 2 => 'Preview' }, + ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)', + ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)', + }, +); + +# hdr metadata namespace used by ACR 15.1 +%Image::ExifTool::XMP::hdr = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-hdr', 2 => 'Image' }, + NAMESPACE => 'hdr_metadata', + TABLE_DESC => 'XMP HDR Metadata', + NOTES => q{ + HDR metadata namespace tags written by ACR 15.1. The actual namespace + prefix is "hdr_metadata", which is the prefix recorded in the file, but + ExifTool shortens this for the family 1 group name. + }, + ccv_primaries_xy => { Name => 'CCVPrimariesXY' }, # (comma-separated string of 6 reals) + ccv_white_xy => { Name => 'CCVWhiteXY' }, # (comma-separated string of 2 reals) + ccv_min_luminance_nits => { Name => 'CCVMinLuminanceNits', Writable => 'real' }, + ccv_max_luminance_nits => { Name => 'CCVMaxLuminanceNits', Writable => 'real' }, + ccv_avg_luminance_nits => { Name => 'CCVAvgLuminanceNits', Writable => 'real' }, + scene_referred => { Name => 'SceneReferred', Writable => 'boolean' }, +); + +# HDR Gain Map metadata namespace +%Image::ExifTool::XMP::hdrgm = ( + %xmpTableDefaults, + GROUPS => { 1 => 'XMP-hdrgm', 2 => 'Image' }, + NAMESPACE => 'hdrgm', + TABLE_DESC => 'XMP HDR Gain Map Metadata', + NOTES => 'Tags used in Adobe gain map images.', + Version => { Avoid => 1 }, + BaseRenditionIsHDR => { Writable => 'boolean' }, + # this is a pain in the ass: List items below may or may not be lists + # according to the Adobe specification -- I don't know how to handle tags + # with a variable format like this, so just make them lists here for now + OffsetSDR => { Writable => 'real', List => 'Seq' }, + OffsetHDR => { Writable => 'real', List => 'Seq' }, + HDRCapacityMin => { Writable => 'real' }, + HDRCapacityMax => { Writable => 'real' }, + GainMapMin => { Writable => 'real', List => 'Seq' }, + GainMapMax => { Writable => 'real', List => 'Seq' }, + Gamma => { Writable => 'real', List => 'Seq', Avoid => 1 }, +); + +# SVG namespace properties (ref 9) +%Image::ExifTool::XMP::SVG = ( + GROUPS => { 0 => 'SVG', 1 => 'SVG', 2 => 'Image' }, + NAMESPACE => 'svg', + LANG_INFO => \&GetLangInfo, + NOTES => q{ + SVG (Scalable Vector Graphics) image tags. By default, only the top-level + SVG and Metadata tags are extracted from these images, but all graphics tags + may be extracted by setting the Unknown option to 2 (-U on the command + line). The SVG tags are not part of XMP as such, but are included with the + XMP module for convenience. (see L<http://www.w3.org/TR/SVG11/>) + }, + version => 'SVGVersion', + id => 'ID', + metadataId => 'MetadataID', + width => { + Name => 'ImageWidth', + ValueConv => '$val =~ s/px$//; $val', + }, + height => { + Name => 'ImageHeight', + ValueConv => '$val =~ s/px$//; $val', + }, +); + +# table to add tags in other namespaces +%Image::ExifTool::XMP::otherSVG = ( + GROUPS => { 0 => 'SVG', 2 => 'Unknown' }, + LANG_INFO => \&GetLangInfo, + NAMESPACE => undef, # variable namespace +); + +#------------------------------------------------------------------------------ +# Generate crd tags +# Inputs: 0) tag table ref +sub Init_crd($) +{ + my $tagTablePtr = shift; + # import tags from CRS namespace + my $crsTable = GetTagTable('Image::ExifTool::XMP::crs'); + my $tag; + foreach $tag (Image::ExifTool::TagTableKeys($crsTable)) { + my $crsInfo = $$crsTable{$tag}; + my $tagInfo = $$tagTablePtr{$tag} = { %$crsInfo }; + $$tagInfo{Groups} = { 0 => 'XMP', 1 => 'XMP-crd' , 2 => $$crsInfo{Groups}{2} } if $$crsInfo{Groups}; + } +} + + +1; #end + +__END__ + +=head1 NAME + +Image::ExifTool::XMP2.pl - Additional XMP namespace definitions + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This file contains definitions for less common XMP namespaces. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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://ns.useplus.org/> + +=item L<http://www.prismstandard.org/> + +=item L<http://www.portfoliofaq.com/pfaq/v7mappings.htm> + +=item L<http://www.iptc.org/IPTC4XMP/> + +=item L<http://creativecommons.org/technology/xmp> + +=item L<http://www.optimasc.com/products/fileid/xmp-extensions.pdf> + +=item L<http://www.w3.org/TR/SVG11/> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/XMP Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/XMPStruct.pl b/ExifTool/lib/Image/ExifTool/XMPStruct.pl new file mode 100644 index 0000000..6ea7358 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/XMPStruct.pl @@ -0,0 +1,950 @@ +#------------------------------------------------------------------------------ +# File: XMPStruct.pl +# +# Description: XMP structure support +# +# Revisions: 01/01/2011 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::XMP; + +use strict; +use vars qw(%specialStruct %stdXlatNS); + +use Image::ExifTool qw(:Utils); +use Image::ExifTool::XMP; + +sub SerializeStruct($$;$); +sub InflateStruct($$;$); +sub DumpStruct($;$); +sub CheckStruct($$$); +sub AddNewStruct($$$$$$); +sub ConvertStruct($$$$;$); +sub EscapeJSON($;$); + +# lookups for JSON characters that we escape specially +my %jsonChar = ( '"'=>'"', '\\'=>'\\', "\b"=>'b', "\f"=>'f', "\n"=>'n', "\r"=>'r', "\t"=>'t' ); +my %jsonEsc = ( '"'=>'"', '\\'=>'\\', 'b'=>"\b", 'f'=>"\f", 'n'=>"\n", 'r'=>"\r", 't'=>"\t" ); + +#------------------------------------------------------------------------------ +# Serialize a structure (or other object) into a simple string +# Inputs: 0) ExifTool ref, 1) HASH ref, ARRAY ref, or SCALAR, 2) closing bracket (or undef) +# Returns: serialized structure string (in format specified by StructFormat option) +# eg) "{field=text with {braces|}|, and a comma, field2=val2,field3={field4=[a,b]}}" +sub SerializeStruct($$;$) +{ + my ($et, $obj, $ket) = @_; + my ($key, $val, @vals, $rtnVal); + my $sfmt = $et->Options('StructFormat'); + + if (ref $obj eq 'HASH') { + # support hashes with ordered keys + my @keys = $$obj{_ordered_keys_} ? @{$$obj{_ordered_keys_}} : sort keys %$obj; + foreach $key (@keys) { + my $hdr = $sfmt ? EscapeJSON($key) . ':' : $key . '='; + push @vals, $hdr . SerializeStruct($et, $$obj{$key}, '}'); + } + $rtnVal = '{' . join(',', @vals) . '}'; + } elsif (ref $obj eq 'ARRAY') { + foreach $val (@$obj) { + push @vals, SerializeStruct($et, $val, ']'); + } + $rtnVal = '[' . join(',', @vals) . ']'; + } elsif (defined $obj) { + $obj = $$obj if ref $obj eq 'SCALAR'; + # escape necessary characters in string (closing bracket plus "," and "|") + if ($sfmt) { + $rtnVal = EscapeJSON($obj, $sfmt eq 'JSONQ'); + } else { + my $pat = $ket ? "\\$ket|,|\\|" : ',|\\|'; + ($rtnVal = $obj) =~ s/($pat)/|$1/g; + # also must escape opening bracket or whitespace at start of string + $rtnVal =~ s/^([\s\[\{])/|$1/; + } + } elsif ($sfmt) { + $rtnVal = 'null'; + } else { + $rtnVal = ''; # allow undefined list items + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Inflate structure (or other object) from a serialized string +# Inputs: 0) ExifTool ref, 1) reference to object in string form +# (serialized using the '|' escape, or JSON) +# 2) extra delimiter for scalar values delimiters +# Returns: 0) object as a SCALAR, HASH ref, or ARRAY ref (or undef on error), +# 1) warning string (or undef) +# Notes: modifies input string to remove parsed objects +sub InflateStruct($$;$) +{ + my ($et, $obj, $delim) = @_; + my ($val, $warn, $part); + my $sfmt = $et->Options('StructFormat'); + + if ($$obj =~ s/^\s*\{//) { + my %struct; + for (;;) { + last unless $sfmt ? $$obj =~ s/^\s*"(.*?)"\s*://s : + $$obj =~ s/^\s*([-\w:]+#?)\s*=//s; + my $tag = $1; + my ($v, $w) = InflateStruct($et, $obj, '}'); + $warn = $w if $w and not $warn; + return(undef, $warn) unless defined $v; + $struct{$tag} = $v; + # eat comma separator, or all done if there wasn't one + last unless $$obj =~ s/^\s*,//s; + } + # eat closing brace and warn if we didn't find one + unless ($$obj =~ s/^\s*\}//s or $warn) { + if (length $$obj) { + ($part = $$obj) =~ s/^\s*//s; + $part =~ s/[\x0d\x0a].*//s; + $part = substr($part,0,27) . '...' if length($part) > 30; + $warn = "Invalid structure field at '${part}'"; + } else { + $warn = 'Missing closing brace for structure'; + } + } + $val = \%struct; + } elsif ($$obj =~ s/^\s*\[//) { + my @list; + for (;;) { + my ($v, $w) = InflateStruct($et, $obj, ']'); + $warn = $w if $w and not $warn; + return(undef, $warn) unless defined $v; + push @list, $v; + last unless $$obj =~ s/^\s*,//s; + } + # eat closing bracket and warn if we didn't find one + $$obj =~ s/^\s*\]//s or $warn or $warn = 'Missing closing bracket for list'; + $val = \@list; + } else { + $$obj =~ s/^\s+//s; # remove leading whitespace + if ($sfmt) { + if ($$obj =~ s/^"//) { + $val = ''; + while ($$obj =~ s/(.*?)"//) { + $val .= $1; + last unless $val =~ /([\\]+)$/ and length($1) & 0x01; + substr($val, -1, 1) = '"'; # (was an escaped quote) + } + if ($val =~ s/^base64://) { + $val = DecodeBase64($val); + } else { + # un-escape characters in JSON string + $val =~ s/\\(.)/$jsonEsc{$1}||'\\'.$1/egs; + } + } elsif ($$obj =~ s/^(true|false)\b//) { + $val = '"' . ucfirst($1) . '"'; + } elsif ($$obj =~ s/^([+-]?(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?)//) { + $val = $1; + } else { + $warn or $warn = 'Unknown JSON object'; + $val = '""'; + } + } else { + # read scalar up to specified delimiter (or "," if not defined) + $delim = $delim ? "\\$delim|,|\\||\$" : ',|\\||$'; + $val = ''; + for (;;) { + $$obj =~ s/^(.*?)($delim)//s or last; + $val .= $1; + last unless $2; + $2 eq '|' or $$obj = $2 . $$obj, last; + $$obj =~ s/^(.)//s and $val .= $1; # add escaped character + } + } + } + return($val, $warn); +} + +#------------------------------------------------------------------------------ +# Escape string for JSON +# Inputs: 0) string, 1) flag to force numbers to be quoted too +# Returns: Escaped string (quoted if necessary) +sub EscapeJSON($;$) +{ + my ($str, $quote) = @_; + unless ($quote) { + return 'null' unless defined $str; + # JSON boolean (true or false) + return lc($str) if $str =~ /^(true|false)$/i; + # JSON number (see json.org for numerical format) + # return $str if $str =~ /^-?(\d|[1-9]\d+)(\.\d+)?(e[-+]?\d+)?$/i; + # (these big numbers caused problems for some JSON parsers, so be more conservative) + return $str if $str =~ /^-?(\d|[1-9]\d{1,14})(\.\d{1,16})?(e[-+]?\d{1,3})?$/i; + } + return '""' unless defined $str; + # encode JSON string in base64 if necessary + return '"base64:' . EncodeBase64($str, 1) . '"' if Image::ExifTool::IsUTF8(\$str) < 0; + # escape special characters + $str =~ s/(["\t\n\r\\])/\\$jsonChar{$1}/sg; + $str =~ tr/\0//d; # remove all nulls + # escape other control characters with \u + $str =~ s/([\0-\x1f])/sprintf("\\u%.4X",ord $1)/sge; + return '"' . $str . '"'; # return the quoted string +} + +#------------------------------------------------------------------------------ +# Get XMP language code from tag name string +# Inputs: 0) tag name string +# Returns: 0) separated tag name, 1) language code (in standard case), or '' if +# language code was 'x-default', or undef if the tag had no language code +sub GetLangCode($) +{ + my $tag = shift; + if ($tag =~ /^(\w+)[-_]([a-z]{2,3}|[xi])([-_][a-z\d]{2,8}([-_][a-z\d]{1,8})*)?$/i) { + # normalize case of language codes + my ($tg, $langCode) = ($1, lc($2)); + $langCode .= (length($3) == 3 ? uc($3) : lc($3)) if $3; + $langCode =~ tr/_/-/; # RFC 3066 specifies '-' as a separator + $langCode = '' if lc($langCode) eq 'x-default'; + return($tg, $langCode); + } else { + return($tag, undef); + } +} + +#------------------------------------------------------------------------------ +# Debugging routine to dump a structure, list or scalar +# Inputs: 0) scalar, ARRAY ref or HASH ref, 1) indent (or undef) +sub DumpStruct($;$) +{ + local $_; + my ($obj, $indent) = @_; + + $indent or $indent = ''; + if (ref $obj eq 'HASH') { + print "{\n"; + foreach (sort keys %$obj) { + print "$indent $_ = "; + DumpStruct($$obj{$_}, "$indent "); + } + print $indent, "},\n"; + } elsif (ref $obj eq 'ARRAY') { + print "[\n"; + foreach (@$obj) { + print "$indent "; + DumpStruct($_, "$indent "); + } + print $indent, "],\n", + } else { + print "\"$obj\",\n"; + } +} + +#------------------------------------------------------------------------------ +# Recursively validate structure fields (tags) +# Inputs: 0) ExifTool ref, 1) Structure ref, 2) structure table definition ref +# Returns: 0) validated structure ref, 1) error string, or undef on success +# Notes: +# - fixes field names in structure and applies inverse conversions to values +# - copies structure to avoid interdependencies with calling code on referenced values +# - handles lang-alt tags, and '#' on field names +# - resets UTF-8 flag of SCALAR values +# - un-escapes for XML or HTML as per Escape option setting +sub CheckStruct($$$) +{ + my ($et, $struct, $strTable) = @_; + + my $strName = $$strTable{STRUCT_NAME} || ('XMP ' . RegisterNamespace($strTable)); + ref $struct eq 'HASH' or return wantarray ? (undef, "Expecting $strName structure") : undef; + + my ($key, $err, $warn, %copy, $rtnVal, $val); +Key: + foreach $key (keys %$struct) { + my $tag = $key; + # allow trailing '#' to disable print conversion on a per-field basis + my ($type, $fieldInfo); + $type = 'ValueConv' if $tag =~ s/#$//; + $fieldInfo = $$strTable{$tag} unless $specialStruct{$tag}; + # fix case of field name if necessary + unless ($fieldInfo) { + # (sort in reverse to get lower case (not special) tags first) + my ($fix) = reverse sort grep /^$tag$/i, keys %$strTable; + $fieldInfo = $$strTable{$tag = $fix} if $fix and not $specialStruct{$fix}; + } + until (ref $fieldInfo eq 'HASH') { + # generate wildcard fields on the fly (eg. mwg-rs:Extensions) + unless ($$strTable{NAMESPACE}) { + my ($grp, $tg, $langCode); + ($grp, $tg) = $tag =~ /^(.+):(.+)/ ? (lc $1, $2) : ('', $tag); + undef $grp if $grp eq 'XMP'; # (a group of 'XMP' is implied) + require Image::ExifTool::TagLookup; + my @matches = Image::ExifTool::TagLookup::FindTagInfo($tg); + # also look for lang-alt tags + unless (@matches) { + ($tg, $langCode) = GetLangCode($tg); + @matches = Image::ExifTool::TagLookup::FindTagInfo($tg) if defined $langCode; + } + my ($tagInfo, $priority, $ti, $g1); + # find best matching tag + foreach $ti (@matches) { + my @grps = $et->GetGroup($ti); + next unless $grps[0] eq 'XMP'; + next if $grp and $grp ne lc $grps[1]; + # must be lang-alt tag if we are writing an alternate language + next if defined $langCode and not ($$ti{Writable} and $$ti{Writable} eq 'lang-alt'); + my $pri = $$ti{Priority} || 1; + $pri -= 10 if $$ti{Avoid}; + next if defined $priority and $priority >= $pri; + $priority = $pri; + $tagInfo = $ti; + $g1 = $grps[1]; + } + $tagInfo or $warn = "'${tag}' is not a writable XMP tag", next Key; + GetPropertyPath($tagInfo); # make sure property path is generated for this tag + $tag = $$tagInfo{Name}; + $tag = "$g1:$tag" if $grp; + $tag .= "-$langCode" if $langCode; + $fieldInfo = $$strTable{$tag}; + # create new structure field if necessary + $fieldInfo or $fieldInfo = $$strTable{$tag} = { + %$tagInfo, # (also copies the necessary TagID and PropertyPath) + Namespace => $$tagInfo{Namespace} || $$tagInfo{Table}{NAMESPACE}, + LangCode => $langCode, + }; + # delete stuff we don't need (shouldn't cause harm, but better safe than sorry) + # - need to keep StructType and Table in case we need to call AddStructType later + delete $$fieldInfo{Description}; + delete $$fieldInfo{Groups}; + last; # write this dynamically-generated field + } + # generate lang-alt fields on the fly (eg. Iptc4xmpExt:AOTitle) + my ($tg, $langCode) = GetLangCode($tag); + if (defined $langCode) { + $fieldInfo = $$strTable{$tg} unless $specialStruct{$tg}; + unless ($fieldInfo) { + my ($fix) = reverse sort grep /^$tg$/i, keys %$strTable; + $fieldInfo = $$strTable{$tg = $fix} if $fix and not $specialStruct{$fix}; + } + if (ref $fieldInfo eq 'HASH' and $$fieldInfo{Writable} and + $$fieldInfo{Writable} eq 'lang-alt') + { + my $srcInfo = $fieldInfo; + $tag = $tg . '-' . $langCode if $langCode; + $fieldInfo = $$strTable{$tag}; + # create new structure field if necessary + $fieldInfo or $fieldInfo = $$strTable{$tag} = { + %$srcInfo, + TagID => $tg, + LangCode => $langCode, + }; + last; # write this lang-alt field + } + } + $warn = "'${tag}' is not a field of $strName"; + next Key; + } + if (ref $$struct{$key} eq 'HASH') { + $$fieldInfo{Struct} or $warn = "$tag is not a structure in $strName", next Key; + # recursively check this structure + ($val, $err) = CheckStruct($et, $$struct{$key}, $$fieldInfo{Struct}); + $err and $warn = $err, next Key; + $copy{$tag} = $val; + } elsif (ref $$struct{$key} eq 'ARRAY') { + $$fieldInfo{List} or $warn = "$tag is not a list in $strName", next Key; + # check all items in the list + my ($item, @copy); + my $i = 0; + foreach $item (@{$$struct{$key}}) { + if (not ref $item) { + $item = '' unless defined $item; # use empty string for missing items + if ($$fieldInfo{Struct}) { + # (allow empty structures) + $item =~ /^\s*$/ or $warn = "$tag items are not valid structures", next Key; + $copy[$i] = { }; # create hash for empty structure + } else { + $et->Sanitize(\$item); + ($copy[$i],$err) = $et->ConvInv($item,$fieldInfo,$tag,$strName,$type,''); + $copy[$i] = '' unless defined $copy[$i]; # avoid undefined item + $err and $warn = $err, next Key; + $err = CheckXMP($et, $fieldInfo, \$copy[$i]); + $err and $warn = "$err in $strName $tag", next Key; + } + } elsif (ref $item eq 'HASH') { + $$fieldInfo{Struct} or $warn = "$tag is not a structure in $strName", next Key; + ($copy[$i], $err) = CheckStruct($et, $item, $$fieldInfo{Struct}); + $err and $warn = $err, next Key; + } else { + $warn = "Invalid value for $tag in $strName"; + next Key; + } + ++$i; + } + $copy{$tag} = \@copy; + } elsif ($$fieldInfo{Struct}) { + $warn = "Improperly formed structure in $strName $tag"; + } else { + $et->Sanitize(\$$struct{$key}); + ($val,$err) = $et->ConvInv($$struct{$key},$fieldInfo,$tag,$strName,$type,''); + $err and $warn = $err, next Key; + next Key unless defined $val; # check for undefined + $err = CheckXMP($et, $fieldInfo, \$val); + $err and $warn = "$err in $strName $tag", next Key; + # turn this into a list if necessary + $copy{$tag} = $$fieldInfo{List} ? [ $val ] : $val; + } + } + if (%copy or not $warn) { + $rtnVal = \%copy; + undef $err; + $$et{CHECK_WARN} = $warn if $warn; + } else { + $err = $warn; + } + return wantarray ? ($rtnVal, $err) : $rtnVal; +} + +#------------------------------------------------------------------------------ +# Delete matching structures from existing linearized XMP +# Inputs: 0) ExifTool ref, 1) capture hash ref, 2) structure path ref, +# 3) new value hash ref, 4) reference to change counter +# Returns: 0) delete flag, 1) list index of deleted structure if adding to list +# 2) flag set if structure existed +# Notes: updates path to new base path for structure to be added +sub DeleteStruct($$$$$) +{ + my ($et, $capture, $pathPt, $nvHash, $changed) = @_; + my ($deleted, $added, $existed, $p, $pp, $val, $delPath); + my (@structPaths, @matchingPaths, @delPaths); + + # find all existing elements belonging to this structure + ($pp = $$pathPt) =~ s/ \d+/ \\d\+/g; + @structPaths = sort grep(/^$pp(\/|$)/, keys %$capture); + $existed = 1 if @structPaths; + # delete only structures with matching fields if necessary + if ($$nvHash{DelValue}) { + if (@{$$nvHash{DelValue}}) { + my $strTable = $$nvHash{TagInfo}{Struct}; + # all fields must match corresponding elements in the same + # root structure for it to be deleted + foreach $val (@{$$nvHash{DelValue}}) { + next unless ref $val eq 'HASH'; + my (%cap, $p2, %match); + next unless AddNewStruct(undef, undef, \%cap, $$pathPt, $val, $strTable); + foreach $p (keys %cap) { + if ($p =~ / /) { + ($p2 = $p) =~ s/ \d+/ \\d\+/g; + @matchingPaths = sort grep(/^$p2$/, @structPaths); + } else { + push @matchingPaths, $p; + } + foreach $p2 (@matchingPaths) { + $p2 =~ /^($pp)/ or next; + # language attribute must also match if it exists + my $attr = $cap{$p}[1]; + if ($$attr{'xml:lang'}) { + my $a2 = $$capture{$p2}[1]; + next unless $$a2{'xml:lang'} and $$a2{'xml:lang'} eq $$attr{'xml:lang'}; + } + if ($$capture{$p2} and $$capture{$p2}[0] eq $cap{$p}[0]) { + # ($1 contains root path for this structure) + $match{$1} = ($match{$1} || 0) + 1; + } + } + } + my $num = scalar(keys %cap); + foreach $p (keys %match) { + # do nothing unless all fields matched the same structure + next unless $match{$p} == $num; + # delete all elements of this structure + foreach $p2 (@structPaths) { + push @delPaths, $p2 if $p2 =~ /^$p/; + } + # remember path of first deleted structure + $delPath = $p if not $delPath or $delPath gt $p; + } + } + } # (else don't delete anything) + } elsif (@structPaths) { + @delPaths = @structPaths; # delete all + $structPaths[0] =~ /^($pp)/; + $delPath = $1; + } + if (@delPaths) { + my $verbose = $et->Options('Verbose'); + @delPaths = sort @delPaths if $verbose > 1; + foreach $p (@delPaths) { + if ($verbose > 1) { + my $p2 = $p; + $p2 =~ s/^(\w+)/$stdXlatNS{$1} || $1/e; + $et->VerboseValue("- XMP-$p2", $$capture{$p}[0]); + } + delete $$capture{$p}; + $deleted = 1; + ++$$changed; + } + $delPath or warn("Internal error 1 in DeleteStruct\n"), return(undef,undef,$existed); + $$pathPt = $delPath; # return path of first element deleted + } elsif ($$nvHash{TagInfo}{List}) { + # NOTE: we don't yet properly handle lang-alt elements!!!! + if (@structPaths) { + $structPaths[-1] =~ /^($pp)/ or warn("Internal error 2 in DeleteStruct\n"), return(undef,undef,$existed); + my $path = $1; + # delete any improperly formatted xmp + if ($$capture{$path}) { + my $cap = $$capture{$path}; + # an error unless this was an empty structure + $et->Error("Improperly structured XMP ($path)",1) if ref $cap ne 'ARRAY' or $$cap[0]; + delete $$capture{$path}; + } + # (match last index to put in same lang-alt list for Bag of lang-alt items) + $path =~ m/.* (\d+)/g or warn("Internal error 3 in DeleteStruct\n"), return(undef,undef,$existed); + $added = $1; + # add after last item in list + my $len = length $added; + my $pos = pos($path) - $len; + my $nxt = substr($added, 1) + 1; + substr($path, $pos, $len) = length($nxt) . $nxt; + $$pathPt = $path; + } else { + $added = '10'; + } + } + return($deleted, $added, $existed); +} + +#------------------------------------------------------------------------------ +# Add new element to XMP capture hash +# Inputs: 0) ExifTool ref, 1) TagInfo ref, 2) capture hash ref, +# 3) resource path, 4) value ref, 5) hash ref for last used index numbers +sub AddNewTag($$$$$$) +{ + my ($et, $tagInfo, $capture, $path, $valPtr, $langIdx) = @_; + my $val = EscapeXML($$valPtr); + my %attrs; + # support writing RDF "resource" values + if ($$tagInfo{Resource}) { + $attrs{'rdf:resource'} = $val; + $val = ''; + } + if ($$tagInfo{Writable} and $$tagInfo{Writable} eq 'lang-alt') { + # write the lang-alt tag + my $langCode = $$tagInfo{LangCode}; + # add indexed lang-alt list properties + my $i = $$langIdx{$path} || 0; + $$langIdx{$path} = $i + 1; # save next list index + if ($i) { + my $idx = length($i) . $i; + $path =~ s/(.*) \d+/$1 $idx/; # set list index + } + $attrs{'xml:lang'} = $langCode || 'x-default'; + } + $$capture{$path} = [ $val, \%attrs ]; + # print verbose message + if ($et and $et->Options('Verbose') > 1) { + my $p = $path; + $p =~ s/^(\w+)/$stdXlatNS{$1} || $1/e; + $et->VerboseValue("+ XMP-$p", $val); + } +} + +#------------------------------------------------------------------------------ +# Add new structure to capture hash for writing +# Inputs: 0) ExifTool object ref (or undef for no warnings), +# 1) tagInfo ref (or undef if no ExifTool), 2) capture hash ref, +# 3) base path, 4) struct ref, 5) struct hash ref +# Returns: number of tags changed +# Notes: Escapes values for XML +sub AddNewStruct($$$$$$) +{ + my ($et, $tagInfo, $capture, $basePath, $struct, $strTable) = @_; + my $verbose = $et ? $et->Options('Verbose') : 0; + my ($tag, %langIdx); + + my $ns = $$strTable{NAMESPACE} || ''; + my $changed = 0; + + # add dummy field to allow empty structures (name starts with '~' so it will come + # after all valid structure fields, which is necessary when serializing the XMP later) + %$struct or $$struct{'~dummy~'} = ''; + + foreach $tag (sort keys %$struct) { + my $fieldInfo = $$strTable{$tag}; + unless ($fieldInfo) { + next unless $tag eq '~dummy~'; # check for dummy field + $fieldInfo = { }; # create dummy field info for dummy structure + } + my $val = $$struct{$tag}; + my $propPath = $$fieldInfo{PropertyPath}; + unless ($propPath) { + $propPath = ($$fieldInfo{Namespace} || $ns) . ':' . ($$fieldInfo{TagID} || $tag); + if ($$fieldInfo{List}) { + $propPath .= "/rdf:$$fieldInfo{List}/rdf:li 10"; + } + if ($$fieldInfo{Writable} and $$fieldInfo{Writable} eq 'lang-alt') { + $propPath .= "/rdf:Alt/rdf:li 10"; + } + $$fieldInfo{PropertyPath} = $propPath; # save for next time + } + my $path = $basePath . '/' . ConformPathToNamespace($et, $propPath); + my $addedTag; + if (ref $val eq 'HASH') { + my $subStruct = $$fieldInfo{Struct} or next; + $changed += AddNewStruct($et, $tagInfo, $capture, $path, $val, $subStruct); + } elsif (ref $val eq 'ARRAY') { + next unless $$fieldInfo{List}; + my $i = 0; + my ($item, $p); + my $level = scalar(() = ($propPath =~ / \d+/g)); + # loop through all list items (note: can't yet write multi-dimensional lists) + foreach $item (@{$val}) { + if ($i) { + # update first index in field property (may be list of lang-alt lists) + $p = ConformPathToNamespace($et, $propPath); + my $idx = length($i) . $i; + $p =~ s/ \d+/ $idx/; + $p = "$basePath/$p"; + } else { + $p = $path; + } + if (ref $item eq 'HASH') { + my $subStruct = $$fieldInfo{Struct} or next; + AddNewStruct($et, $tagInfo, $capture, $p, $item, $subStruct) or next; + # don't write empty items in upper-level list + } elsif (length $item or (defined $item and $level == 1)) { + AddNewTag($et, $fieldInfo, $capture, $p, \$item, \%langIdx); + $addedTag = 1; + } + ++$changed; + ++$i; + } + } else { + AddNewTag($et, $fieldInfo, $capture, $path, \$val, \%langIdx); + $addedTag = 1; + ++$changed; + } + # this is tricky, but we must add the rdf:type for contained structures + # in the case that a whole hierarchy was added at once by writing a + # flattened tag inside a variable-namespace structure + if ($addedTag and $$fieldInfo{StructType} and $$fieldInfo{Table}) { + AddStructType($et, $$fieldInfo{Table}, $capture, $propPath, $basePath); + } + } + # add 'rdf:type' property if necessary + if ($$strTable{TYPE} and $changed) { + my $path = $basePath . '/' . ConformPathToNamespace($et, "rdf:type"); + unless ($$capture{$path}) { + $$capture{$path} = [ '', { 'rdf:resource' => $$strTable{TYPE} } ]; + if ($verbose > 1) { + my $p = $path; + $p =~ s/^(\w+)/$stdXlatNS{$1} || $1/e; + $et->VerboseValue("+ XMP-$p", $$strTable{TYPE}); + } + } + } + return $changed; +} + +#------------------------------------------------------------------------------ +# Convert structure field values for printing +# Inputs: 0) ExifTool ref, 1) tagInfo ref for structure tag, 2) value, +# 3) conversion type: PrintConv, ValueConv or Raw (Both not allowed) +# 4) tagID of parent structure (needed only if there was no flattened tag) +# Notes: Makes a copy of the hash so any applied escapes won't affect raw values +sub ConvertStruct($$$$;$) +{ + my ($et, $tagInfo, $value, $type, $parentID) = @_; + if (ref $value eq 'HASH') { + my (%struct, $key); + my $table = $$tagInfo{Table}; + $parentID = $$tagInfo{TagID} unless $parentID; + foreach $key (keys %$value) { + my $tagID = $parentID . ucfirst($key); + my $flatInfo = $$table{$tagID}; + unless ($flatInfo) { + # handle variable-namespace structures + if ($key =~ /^XMP-(.*?:)(.*)/) { + $tagID = $1 . $parentID . ucfirst($2); + $flatInfo = $$table{$tagID}; + } + $flatInfo or $flatInfo = $tagInfo; + } + my $v = $$value{$key}; + if (ref $v) { + $v = ConvertStruct($et, $flatInfo, $v, $type, $tagID); + } else { + $v = $et->GetValue($flatInfo, $type, $v); + } + $struct{$key} = $v if defined $v; # save the converted value + } + return \%struct; + } elsif (ref $value eq 'ARRAY') { + if (defined $$et{OPTIONS}{ListItem}) { + my $li = $$et{OPTIONS}{ListItem}; + return undef unless defined $$value[$li]; + undef $$et{OPTIONS}{ListItem}; # only do top-level list + my $val = ConvertStruct($et, $tagInfo, $$value[$li], $type, $parentID); + $$et{OPTIONS}{ListItem} = $li; + return $val; + } else { + my (@list, $val); + foreach $val (@$value) { + my $v = ConvertStruct($et, $tagInfo, $val, $type, $parentID); + push @list, $v if defined $v; + } + return \@list; + } + } else { + return $et->GetValue($tagInfo, $type, $value); + } +} + +#------------------------------------------------------------------------------ +# Restore XMP structures in extracted information +# Inputs: 0) ExifTool object ref, 1) flag to keep original flattened tags +# Notes: also restores lists (including multi-dimensional) +sub RestoreStruct($;$) +{ + local $_; + my ($et, $keepFlat) = @_; + my ($key, %structs, %var, %lists, $si, %listKeys, @siList); + my $valueHash = $$et{VALUE}; + my $fileOrder = $$et{FILE_ORDER}; + my $tagExtra = $$et{TAG_EXTRA}; + foreach $key (keys %{$$et{TAG_INFO}}) { + $$tagExtra{$key} or next; + my $structProps = $$tagExtra{$key}{Struct} or next; + delete $$tagExtra{$key}{Struct}; # (don't re-use) + my $tagInfo = $$et{TAG_INFO}{$key}; # tagInfo for flattened tag + my $table = $$tagInfo{Table}; + my $prop = shift @$structProps; + my $tag = $$prop[0]; + # get reference to structure tag (or normal list tag if not a structure) + my $strInfo = @$structProps ? $$table{$tag} : $tagInfo; + if ($strInfo) { + ref $strInfo eq 'HASH' or next; # (just to be safe) + if (@$structProps and not $$strInfo{Struct}) { + # this could happen for invalid XMP containing mixed lists + # (or for something like this -- what should we do here?: + # <meta:user-defined meta:name="License">test</meta:user-defined>) + $et->Warn("$$strInfo{Name} is not a structure!") unless $$et{NO_STRUCT_WARN}; + next; + } + } else { + # create new entry in tag table for this structure + my $g1 = $$table{GROUPS}{0} || 'XMP'; + my $name = $tag; + # tag keys will have a group 1 prefix when coming from import of XML from -X option + if ($tag =~ /(.+):(.+)/) { + my $ns; + ($ns, $name) = ($1, $2); + $ns =~ s/^XMP-//; # remove leading "XMP-" if it exists because we add it later + $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns}; + $g1 .= "-$ns"; + } + $strInfo = { + Name => ucfirst $name, + Groups => { 1 => $g1 }, + Struct => 'Unknown', + }; + # add Struct entry if this is a structure + if (@$structProps) { + # this is a structure + $$strInfo{Struct} = { STRUCT_NAME => 'XMP Unknown' } if @$structProps; + } elsif ($$tagInfo{LangCode}) { + # this is lang-alt list + $tag = $tag . '-' . $$tagInfo{LangCode}; + $$strInfo{LangCode} = $$tagInfo{LangCode}; + } + AddTagToTable($table, $tag, $strInfo); + } + # use strInfo ref for base key to avoid collisions + $tag = $strInfo; + my $struct = \%structs; + my $oldStruct = $structs{$strInfo}; + # (fyi: 'lang-alt' Writable type will be valid even if tag is not pre-defined) + my $writable = $$tagInfo{Writable} || ''; + # walk through the stored structure property information + # to rebuild this structure + my ($err, $i); + for (;;) { + my $index = $$prop[1]; + if ($index and not @$structProps) { + # ignore this list if it is a simple lang-alt tag + if ($writable eq 'lang-alt') { + pop @$prop; # remove lang-alt index + undef $index if @$prop < 2; + } + # add language code if necessary + if ($$tagInfo{LangCode} and not ref $tag) { + $tag = $tag . '-' . $$tagInfo{LangCode}; + } + } + my $nextStruct = $$struct{$tag}; + if (defined $index) { + # the field is a list + $index = substr $index, 1; # remove digit count + if ($nextStruct) { + ref $nextStruct eq 'ARRAY' or $err = 2, last; + $struct = $nextStruct; + } else { + $struct = $$struct{$tag} = [ ]; + } + $nextStruct = $$struct[$index]; + # descend into multi-dimensional lists + for ($i=2; $$prop[$i]; ++$i) { + if ($nextStruct) { + ref $nextStruct eq 'ARRAY' or last; + $struct = $nextStruct; + } else { + $lists{$struct} = $struct; + $struct = $$struct[$index] = [ ]; + } + $nextStruct = $$struct[$index]; + $index = substr $$prop[$i], 1; + } + if (ref $nextStruct eq 'HASH') { + $struct = $nextStruct; # continue building sub-structure + } elsif (@$structProps) { + $lists{$struct} = $struct; + $struct = $$struct[$index] = { }; + } else { + $lists{$struct} = $struct; + $$struct[$index] = $$valueHash{$key}; + last; + } + } else { + if ($nextStruct) { + ref $nextStruct eq 'HASH' or $err = 3, last; + $struct = $nextStruct; + } elsif (@$structProps) { + $struct = $$struct{$tag} = { }; + } else { + $$struct{$tag} = $$valueHash{$key}; + last; + } + } + $prop = shift @$structProps or last; + $tag = $$prop[0]; + if ($tag =~ /(.+):(.+)/) { + # tag in variable-namespace tables will have a leading + # XMP namespace on the tag name. In this case, add + # the corresponding group1 name to the tag ID. + my ($ns, $name) = ($1, $2); + $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns}; + $tag = "XMP-$ns:" . ucfirst $name; + } else { + $tag = ucfirst $tag; + } + } + if ($err) { + # this may happen if we have a structural error in the XMP + # (like an improperly contained list for example) + unless ($$et{NO_STRUCT_WARN}) { + my $ns = $$tagInfo{Namespace} || $$tagInfo{Table}{NAMESPACE} || ''; + $et->Warn("Error $err placing $ns:$$tagInfo{TagID} in structure or list", 1); + } + delete $structs{$strInfo} unless $oldStruct; + } elsif ($tagInfo eq $strInfo) { + # just a regular list tag (or an empty structure) + if ($oldStruct) { + # keep tag with lowest numbered key (well, not exactly, since + # "Tag (10)" is lt "Tag (2)", but at least "Tag" is lt + # everything else, and this is really what we care about) + my $k = $listKeys{$oldStruct}; + if ($k) { # ($k will be undef for an empty structure) + if ($k lt $key) { + # keep lowest file order + $$fileOrder{$k} = $$fileOrder{$key} if $$fileOrder{$k} > $$fileOrder{$key}; + $et->DeleteTag($key); + next; + } + $$fileOrder{$key} = $$fileOrder{$k} if $$fileOrder{$key} > $$fileOrder{$k}; + $et->DeleteTag($k); # remove tag with greater copy number + } + } + # replace existing value with new list + $$valueHash{$key} = $structs{$strInfo}; + $listKeys{$structs{$strInfo}} = $key; # save key for this list tag + } else { + # save strInfo ref and file order + if ($var{$strInfo}) { + # set file order to just before the first associated flattened tag + if ($var{$strInfo}[1] > $$fileOrder{$key}) { + $var{$strInfo}[1] = $$fileOrder{$key} - 0.5; + } + } else { + $var{$strInfo} = [ $strInfo, $$fileOrder{$key} - 0.5 ]; + } + # preserve original flattened tags if requested + if ($keepFlat) { + my $extra = $$tagExtra{$key} or next; + # restore list behaviour of this flattened tag + if ($$extra{NoList}) { + $$valueHash{$key} = $$extra{NoList}; + delete $$extra{NoList}; + } elsif ($$extra{NoListDel}) { + # delete this tag since its value was included another list + $et->DeleteTag($key); + } + } else { + $et->DeleteTag($key); # delete the flattened tag + } + } + } + # fill in undefined items in lists. In theory, undefined list items should + # be fine, but in practice the calling code may not check for this (and + # historically this wasn't necessary, so do this for backward compatibility) + foreach $si (keys %lists) { + defined $_ or $_ = '' foreach @{$lists{$si}}; + } + # make a list of all new structures we generated + $var{$_} and push @siList, $_ foreach keys %structs; + # save new structures in the same order they were read from file + foreach $si (sort { $var{$a}[1] <=> $var{$b}[1] } @siList) { + # test to see if a tag for this structure has already been generated + # (this could happen only if one of the structures in a list was empty) + $key = $var{$si}[0]{Name}; + my $found; + if ($$valueHash{$key}) { + my @keys = grep /^$key( \(\d+\))?$/, keys %$valueHash; + foreach $key (@keys) { + next unless $$valueHash{$key} eq $structs{$si}; + $found = 1; + last; + } + } + unless ($found) { + # otherwise, generate a new tag for this structure + $key = $et->FoundTag($var{$si}[0], ''); + $$valueHash{$key} = $structs{$si}; + } + $$fileOrder{$key} = $var{$si}[1]; + } +} + + +1; #end + +__END__ + +=head1 NAME + +Image::ExifTool::XMPStruct.pl - XMP structure support + +=head1 SYNOPSIS + +This module is loaded automatically by Image::ExifTool when required. + +=head1 DESCRIPTION + +This file contains routines to provide read/write support of structured XMP +information. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/XMP Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut diff --git a/ExifTool/lib/Image/ExifTool/ZIP.pm b/ExifTool/lib/Image/ExifTool/ZIP.pm new file mode 100644 index 0000000..2fdef80 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/ZIP.pm @@ -0,0 +1,865 @@ +#------------------------------------------------------------------------------ +# File: ZIP.pm +# +# Description: Read ZIP archive meta information +# +# Revisions: 10/28/2007 - P. Harvey Created +# +# References: 1) http://www.pkware.com/documents/casestudies/APPNOTE.TXT +# 2) http://www.cpanforum.com/threads/9046 +# 3) http://www.gzip.org/zlib/rfc-gzip.html +# 4) http://DataCompression.info/ArchiveFormats/RAR202.txt +# 5) https://jira.atlassian.com/browse/CONF-21706 +# 6) http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/indesign/cs55-docs/IDML/idml-specification.pdf +# 7) https://www.rarlab.com/technote.htm +#------------------------------------------------------------------------------ + +package Image::ExifTool::ZIP; + +use strict; +use vars qw($VERSION $warnString); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.30'; + +sub WarnProc($) { $warnString = $_[0]; } + +# file types for recognized Open Document "mimetype" values +my %openDocType = ( + 'application/vnd.oasis.opendocument.database' => 'ODB', #5 + 'application/vnd.oasis.opendocument.chart' => 'ODC', #5 + 'application/vnd.oasis.opendocument.formula' => 'ODF', #5 + 'application/vnd.oasis.opendocument.graphics' => 'ODG', #5 + 'application/vnd.oasis.opendocument.image' => 'ODI', #5 + 'application/vnd.oasis.opendocument.presentation' => 'ODP', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ODS', + 'application/vnd.oasis.opendocument.text' => 'ODT', + 'application/vnd.adobe.indesign-idml-package' => 'IDML', #6 (not open doc) + 'application/epub+zip' => 'EPUB', #PH (not open doc) +); + +# iWork file types based on names of files found in the zip archive +my %iWorkFile = ( + 'Index/Slide.iwa' => 'KEY', + 'Index/Tables/DataList.iwa' => 'NUMBERS', +); + +my %iWorkType = ( + NUMBERS => 'NUMBERS', + PAGES => 'PAGES', + KEY => 'KEY', + KTH => 'KTH', + NMBTEMPLATE => 'NMBTEMPLATE', +); + +# ZIP metadata blocks +%Image::ExifTool::ZIP::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + FORMAT => 'int16u', + NOTES => q{ + The following tags are extracted from ZIP archives. ExifTool also extracts + additional meta information from compressed documents inside some ZIP-based + files such Office Open XML (DOCX, PPTX and XLSX), Open Document (ODB, ODC, + ODF, ODG, ODI, ODP, ODS and ODT), iWork (KEY, PAGES, NUMBERS), Capture One + Enhanced Image Package (EIP), Adobe InDesign Markup Language (IDML), + Electronic Publication (EPUB), and Sketch design files (SKETCH). The + ExifTool family 3 groups may be used to organize ZIP tags by embedded + document number (ie. the exiftool C<-g3> option). + }, + 2 => 'ZipRequiredVersion', + 3 => { + Name => 'ZipBitFlag', + PrintConv => '$val ? sprintf("0x%.4x",$val) : $val', + }, + 4 => { + Name => 'ZipCompression', + PrintConv => { + 0 => 'None', + 1 => 'Shrunk', + 2 => 'Reduced with compression factor 1', + 3 => 'Reduced with compression factor 2', + 4 => 'Reduced with compression factor 3', + 5 => 'Reduced with compression factor 4', + 6 => 'Imploded', + 7 => 'Tokenized', + 8 => 'Deflated', + 9 => 'Enhanced Deflate using Deflate64(tm)', + 10 => 'Imploded (old IBM TERSE)', + 12 => 'BZIP2', + 14 => 'LZMA (EFS)', + 18 => 'IBM TERSE (new)', + 19 => 'IBM LZ77 z Architecture (PFS)', + 96 => 'JPEG recompressed', #2 + 97 => 'WavPack compressed', #2 + 98 => 'PPMd version I, Rev 1', + }, + }, + 5 => { + Name => 'ZipModifyDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + ValueConv => sub { + my $val = shift; + return sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', + ($val >> 25) + 1980, # year + ($val >> 21) & 0x0f, # month + ($val >> 16) & 0x1f, # day + ($val >> 11) & 0x1f, # hour + ($val >> 5) & 0x3f, # minute + ($val & 0x1f) * 2 # second + ); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 7 => { Name => 'ZipCRC', Format => 'int32u', PrintConv => 'sprintf("0x%.8x",$val)' }, + 9 => { Name => 'ZipCompressedSize', Format => 'int32u' }, + 11 => { Name => 'ZipUncompressedSize', Format => 'int32u' }, + 13 => { + Name => 'ZipFileNameLength', + # don't store a tag -- just extract the value for use with ZipFileName + Hidden => 1, + RawConv => '$$self{ZipFileNameLength} = $val; undef', + }, + # 14 => 'ZipExtraFieldLength', + 15 => { + Name => 'ZipFileName', + Format => 'string[$$self{ZipFileNameLength}]', + }, + _com => 'ZipFileComment', +); + +# GNU ZIP tags (ref 3) +%Image::ExifTool::ZIP::GZIP = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + NOTES => q{ + These tags are extracted from GZIP (GNU ZIP) archives, but currently only + for the first file in the archive. + }, + 2 => { + Name => 'Compression', + PrintConv => { + 8 => 'Deflated', + }, + }, + 3 => { + Name => 'Flags', + PrintConv => { BITMASK => { + 0 => 'Text', + 1 => 'CRC16', + 2 => 'ExtraFields', + 3 => 'FileName', + 4 => 'Comment', + }}, + }, + 4 => { + Name => 'ModifyDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + 8 => { + Name => 'ExtraFlags', + PrintConv => { + 0 => '(none)', + 2 => 'Maximum Compression', + 4 => 'Fastest Algorithm', + }, + }, + 9 => { + Name => 'OperatingSystem', + PrintConv => { + 0 => 'FAT filesystem (MS-DOS, OS/2, NT/Win32)', + 1 => 'Amiga', + 2 => 'VMS (or OpenVMS)', + 3 => 'Unix', + 4 => 'VM/CMS', + 5 => 'Atari TOS', + 6 => 'HPFS filesystem (OS/2, NT)', + 7 => 'Macintosh', + 8 => 'Z-System', + 9 => 'CP/M', + 10 => 'TOPS-20', + 11 => 'NTFS filesystem (NT)', + 12 => 'QDOS', + 13 => 'Acorn RISCOS', + 255 => 'unknown', + }, + }, + 10 => 'ArchivedFileName', + 11 => 'Comment', +); + +# RAR v4 tags (ref 4) +%Image::ExifTool::ZIP::RAR = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 2 => 'Other' }, + NOTES => 'These tags are extracted from RAR archive files.', + 0 => { + Name => 'CompressedSize', + Format => 'int32u', + }, + 4 => { + Name => 'UncompressedSize', + Format => 'int32u', + }, + 8 => { + Name => 'OperatingSystem', + PrintConv => { + 0 => 'MS-DOS', + 1 => 'OS/2', + 2 => 'Win32', + 3 => 'Unix', + }, + }, + 13 => { + Name => 'ModifyDate', + Format => 'int32u', + Groups => { 2 => 'Time' }, + ValueConv => sub { + my $val = shift; + return sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', + ($val >> 25) + 1980, # year + ($val >> 21) & 0x0f, # month + ($val >> 16) & 0x1f, # day + ($val >> 11) & 0x1f, # hour + ($val >> 5) & 0x3f, # minute + ($val & 0x1f) * 2 # second + ); + }, + PrintConv => '$self->ConvertDateTime($val)', + }, + 18 => { + Name => 'PackingMethod', + PrintHex => 1, + PrintConv => { + 0x30 => 'Stored', + 0x31 => 'Fastest', + 0x32 => 'Fast', + 0x33 => 'Normal', + 0x34 => 'Good Compression', + 0x35 => 'Best Compression', + }, + }, + 19 => { + Name => 'FileNameLength', + Format => 'int16u', + Hidden => 1, + RawConv => '$$self{FileNameLength} = $val; undef', + }, + 25 => { + Name => 'ArchivedFileName', + Format => 'string[$$self{FileNameLength}]', + }, +); + +# RAR v5 tags (ref 7, github#203) +%Image::ExifTool::ZIP::RAR5 = ( + GROUPS => { 2 => 'Other' }, + VARS => { NO_ID => 1 }, + NOTES => 'These tags are extracted from RAR v5 and 7z archive files.', + FileVersion => { }, + CompressedSize => { }, + ModifyDate => { + Groups => { 2 => 'Time' }, + ValueConv => 'ConvertUnixTime($val,1)', + PrintConv => '$self->ConvertDateTime($val)', + }, + UncompressedSize => { }, + OperatingSystem => { + PrintConv => { 0 => 'Win32', 1 => 'Unix' }, + }, + ArchivedFileName => { }, +); + +#------------------------------------------------------------------------------ +# Read unsigned LEB (Little Endian Base) from file +# Inputs: 0) RAF ref +# Returns: integer value +sub ReadULEB($) +{ + my $raf = shift; + my ($i, $buff); + my $rtnVal = 0; + for ($i=0; ; ++$i) { + $raf->Read($buff, 1) or last; + my $num = ord($buff); + $rtnVal += ($num & 0x7f) << ($i * 7); + $num & 0x80 or last; + } + return $rtnVal; +} + +#------------------------------------------------------------------------------ +# Extract information from a RAR file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid RAR file +sub ProcessRAR($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($flags, $buff); + my $docNum = 0; + + return 0 unless $raf->Read($buff, 7) and $buff =~ "Rar!\x1a\x07[\0\x01]"; + + if ($buff eq "Rar!\x1a\x07\0") { # RARv4 (ref 4) + + $et->SetFileType(); + SetByteOrder('II'); + my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR5'); + $et->HandleTag($tagTablePtr, 'FileVersion', 'RAR v4'); + $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR'); + + for (;;) { + # read block header + $raf->Read($buff, 7) == 7 or last; + my ($type, $flags, $size) = unpack('xxCvv', $buff); + $size -= 7; + if ($flags & 0x8000) { + $raf->Read($buff, 4) == 4 or last; + $size += unpack('V',$buff) - 4; + } + last if $size < 0; + next unless $size; # ignore blocks with no data + # don't try to read very large blocks unless LargeFileSupport is enabled + if ($size >= 0x80000000 and not $et->Options('LargeFileSupport')) { + $et->Warn('Large block encountered. Aborting.'); + last; + } + # process the block + if ($type == 0x74) { # file block + # read maximum 4 KB from a file block + my $n = $size > 4096 ? 4096 : $size; + $raf->Read($buff, $n) == $n or last; + # add compressed size to start of data so we can extract it with the other tags + $buff = pack('V',$size) . $buff; + $$et{DOC_NUM} = ++$docNum; + $et->ProcessDirectory({ DataPt => \$buff }, $tagTablePtr); + $size -= $n; + } elsif ($type == 0x75 and $size > 6) { # comment block + $raf->Read($buff, $size) == $size or last; + # save comment, only if "Stored" (this is untested) + if (Get8u(\$buff, 3) == 0x30) { + $et->FoundTag('Comment', substr($buff, 6)); + } + next; + } + # seek to the start of the next block + $raf->Seek($size, 1) or last if $size; + } + + } else { # RARv5 (ref 7, github#203) + + return 0 unless $raf->Read($buff, 1) and $buff eq "\0"; + $et->SetFileType(); + my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR5'); + $et->HandleTag($tagTablePtr, 'FileVersion', 'RAR v5'); + $$et{INDENT} .= '| '; + + # loop through header blocks + for (;;) { + $raf->Seek(4, 1); # skip header CRC + my $headSize = ReadULEB($raf); + last if $headSize == 0; + # read the header and create new RAF object for reading it + my $header; + $raf->Read($header, $headSize) == $headSize or last; + my $rafHdr = new File::RandomAccess(\$header); + my $headType = ReadULEB($rafHdr); # get header type + + if ($headType == 4) { # encryption block + $et->Warn("File is encrypted.", 0); + last; + } + # skip over all headers except file or service header + next unless $headType == 2 or $headType == 3; + $et->VerboseDir('RAR5 file', undef, $headSize) if $headType == 2; + + my $headFlag = ReadULEB($rafHdr); + ReadULEB($rafHdr); # skip extraSize + my $dataSize; + if ($headFlag & 0x0002) { + $dataSize = ReadULEB($rafHdr); # compressed data size + if ($headType == 2) { + $et->HandleTag($tagTablePtr, 'CompressedSize', $dataSize); + } else { + $raf->Seek($dataSize, 1); # skip service data section + next; + } + } else { + next if $headType == 3; # all done with service header + $dataSize = 0; + } + my $fileFlag = ReadULEB($rafHdr); + my $uncompressedSize = ReadULEB($rafHdr); + $et->HandleTag($tagTablePtr, 'UncompressedSize', $uncompressedSize) unless $fileFlag & 0x0008; + ReadULEB($rafHdr); # skip file attributes + if ($fileFlag & 0x0002) { + $rafHdr->Read($buff, 4) == 4 or last; + # (untested) + $et->HandleTag($tagTablePtr, 'ModifyDate', unpack('V', $buff)); + } + $rafHdr->Seek(4, 1) if $fileFlag & 0x0004; # skip CRC if present + + ReadULEB($rafHdr); # skip compressionInfo + + # get operating system + my $os = ReadULEB($rafHdr); + $et->HandleTag($tagTablePtr, 'OperatingSystem', $os); + + # get filename + $rafHdr->Read($buff, 1) == 1 or last; + my $nameLen = ord($buff); + $rafHdr->Read($buff, $nameLen) == $nameLen or last; + $buff =~ s/\0+$//; # remove trailing nulls (if any) + $et->HandleTag($tagTablePtr, 'ArchivedFileName', $buff); + + $$et{DOC_NUM} = ++$docNum; + + $raf->Seek($dataSize, 1); # skip data section + } + $$et{INDENT} = substr($$et{INDENT}, 0, -2); + } + + $$et{DOC_NUM} = 0; + if ($docNum > 1 and not $et->Options('Duplicates')) { + $et->Warn("Use the Duplicates option to extract tags for all $docNum files", 1); + } + + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from a GNU ZIP file (ref 3) +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid GZIP file +sub ProcessGZIP($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($flags, $buff); + + return 0 unless $raf->Read($buff, 10) and $buff =~ /^\x1f\x8b\x08/; + + $et->SetFileType(); + SetByteOrder('II'); + + my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::GZIP'); + $et->HandleTag($tagTablePtr, 2, Get8u(\$buff, 2)); + $et->HandleTag($tagTablePtr, 3, $flags = Get8u(\$buff, 3)); + $et->HandleTag($tagTablePtr, 4, Get32u(\$buff, 4)); + $et->HandleTag($tagTablePtr, 8, Get8u(\$buff, 8)); + $et->HandleTag($tagTablePtr, 9, Get8u(\$buff, 9)); + + # extract file name and comment if they exist + if ($flags & 0x18) { + if ($flags & 0x04) { + # skip extra field + $raf->Read($buff, 2) == 2 or return 1; + my $len = Get16u(\$buff, 0); + $raf->Read($buff, $len) == $len or return 1; + } + $raf->Read($buff, 4096) or return 1; + my $pos = 0; + my $tagID; + # loop for ArchivedFileName (10) and Comment (11) tags + foreach $tagID (10, 11) { + my $mask = $tagID == 10 ? 0x08 : 0x10; + next unless $flags & $mask; + my $end = $buff =~ /\0/g ? pos($buff) - 1 : length($buff); + # (the doc specifies the string should be ISO 8859-1, + # but in OS X it seems to be UTF-8, so don't translate + # it because I could just as easily screw it up) + my $str = substr($buff, $pos, $end - $pos); + $et->HandleTag($tagTablePtr, $tagID, $str); + last if $end >= length $buff; + $pos = $end + 1; + } + } + return 1; +} + +#------------------------------------------------------------------------------ +# Call HandleTags for attributes of an Archive::Zip member +# Inputs: 0) ExifTool object ref, 1) member ref, 2) optional tag table ref +sub HandleMember($$;$) +{ + my ($et, $member, $tagTablePtr) = @_; + $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::Main'); + $et->HandleTag($tagTablePtr, 2, $member->versionNeededToExtract()); + $et->HandleTag($tagTablePtr, 3, $member->bitFlag()); + $et->HandleTag($tagTablePtr, 4, $member->compressionMethod()); + $et->HandleTag($tagTablePtr, 5, $member->lastModFileDateTime()); + $et->HandleTag($tagTablePtr, 7, $member->crc32()); + $et->HandleTag($tagTablePtr, 9, $member->compressedSize()); + $et->HandleTag($tagTablePtr, 11, $member->uncompressedSize()); + $et->HandleTag($tagTablePtr, 15, $member->fileName()); + my $com = $member->fileComment(); + $et->HandleTag($tagTablePtr, '_com', $com) if defined $com and length $com; +} + +#------------------------------------------------------------------------------ +# Extract file from ZIP archive +# Inputs: 0) ExifTool ref, 1) Zip object ref, 2) file name +# Returns: zip member or undef it it didn't exist +sub ExtractFile($$$) +{ + my ($et, $zip, $file) = @_; + my $result = $zip->memberNamed($file); + $et->VPrint(1, " (Extracting '${file}' from zip archive)\n"); + return $result; +} + +#------------------------------------------------------------------------------ +# Extract information from a ZIP file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid ZIP file +sub ProcessZIP($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $buf2, $zip); + + return 0 unless $raf->Read($buff, 30) == 30 and $buff =~ /^PK\x03\x04/; + + my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::Main'); + my $docNum = 0; + + # use Archive::Zip if available + for (;;) { + unless (eval { require Archive::Zip } and eval { require IO::File }) { + if ($$et{FILE_EXT} and $$et{FILE_EXT} ne 'ZIP') { + $et->Warn("Install Archive::Zip to decode compressed ZIP information"); + } + last; + } + # Archive::Zip requires a seekable IO::File object + my $fh; + if ($raf->{TESTED} >= 0) { + unless (eval { require IO::File }) { + # (this shouldn't happen because IO::File is a prerequisite of Archive::Zip) + $et->Warn("Install IO::File to decode compressed ZIP information"); + last; + } + $raf->Seek(0,0); + $fh = $raf->{FILE_PT}; + bless $fh, 'IO::File'; # Archive::Zip expects an IO::File object + } elsif (eval { require IO::String }) { + # read the whole file into memory (what else can I do?) + $raf->Slurp(); + $fh = new IO::String ${$raf->{BUFF_PT}}; + } else { + my $type = $raf->{FILE_PT} ? 'pipe or socket' : 'scalar reference'; + $et->Warn("Install IO::String to decode compressed ZIP information from a $type"); + last; + } + $et->VPrint(1, " --- using Archive::Zip ---\n"); + $zip = new Archive::Zip; + # catch all warnings! (Archive::Zip is bad for this) + local $SIG{'__WARN__'} = \&WarnProc; + my $status = $zip->readFromFileHandle($fh); + if ($status eq '4' and $raf->{TESTED} >= 0 and eval { require IO::String } and + $raf->Seek(0,2) and $raf->Tell() < 100000000) + { + # try again, reading it ourself this time in an attempt to avoid + # a failed test with Perl 5.6.2 GNU/Linux 2.6.32-5-686 i686-linux-64int-ld + $raf->Seek(0,0); + $raf->Slurp(); + $fh = new IO::String ${$raf->{BUFF_PT}}; + $zip = new Archive::Zip; + $status = $zip->readFromFileHandle($fh); + } + if ($status) { + undef $zip; + my %err = ( 1=>'Stream end error', 3=>'Format error', 4=>'IO error' ); + my $err = $err{$status} || "Error $status"; + $et->Warn("$err reading ZIP file"); + last; + } + # extract zip file comment + my $comment = $zip->zipfileComment(); + $et->FoundTag(Comment => $comment) if defined $comment and length $comment; + + $$dirInfo{ZIP} = $zip; + + # check for an Office Open file (DOCX, etc) + # --> read '[Content_Types].xml' to determine the file type + my ($mime, @members); + my $cType = ExtractFile($et, $zip, '[Content_Types].xml'); + if ($cType) { + ($buff, $status) = $zip->contents($cType); + if (not $status and ( + # first look for the main document with the expected name + $buff =~ m{\sPartName\s*=\s*['"](?:/ppt/presentation.xml|/word/document.xml|/xl/workbook.xml)['"][^>]*\sContentType\s*=\s*(['"])([^"']+)\.main(\+xml)?\1} or + # then look for the main part + $buff =~ /<Override[^>]*\sPartName[^<]+\sContentType\s*=\s*(['"])([^"']+)\.main(\+xml)?\1/ or + # and if all else fails, use the default main + $buff =~ /ContentType\s*=\s*(['"])([^"']+)\.main(\+xml)?\1/)) + { + $mime = $2; + } + } + # check for docProps if we couldn't find a MIME type + $mime or @members = $zip->membersMatching('^docProps/.*\.(xml|XML)$'); + if ($mime or @members) { + $$dirInfo{MIME} = $mime; + require Image::ExifTool::OOXML; + Image::ExifTool::OOXML::ProcessDOCX($et, $dirInfo); + delete $$dirInfo{MIME}; + last; + } + + # check for an EIP file + @members = $zip->membersMatching('^CaptureOne/.*\.(cos|COS)$'); + if (@members) { + require Image::ExifTool::CaptureOne; + Image::ExifTool::CaptureOne::ProcessEIP($et, $dirInfo); + last; + } + + # check for an iWork file + @members = $zip->membersMatching('(?i)^(index\.(xml|apxl)|QuickLook/Thumbnail\.jpg|[^/]+\.(pages|numbers|key)/Index.(zip|xml|apxl))$'); + if (@members) { + require Image::ExifTool::iWork; + Image::ExifTool::iWork::Process_iWork($et, $dirInfo); + last; + } + + # check for an Open Document, IDML or EPUB file + my $mType = ExtractFile($et, $zip, 'mimetype'); + if ($mType) { + ($mime, $status) = $zip->contents($mType); + if (not $status and $mime =~ /([\x21-\xfe]+)/s) { + # clean up MIME type just in case (note that MIME is case insensitive) + $mime = lc $1; + $et->SetFileType($openDocType{$mime} || 'ZIP', $mime); + $et->Warn("Unrecognized MIMEType $mime") unless $openDocType{$mime}; + # extract Open Document metadata from "meta.xml" + my $meta = ExtractFile($et, $zip, 'meta.xml'); + # IDML files have metadata in a different place (ref 6) + $meta or $meta = ExtractFile($et, $zip, 'META-INF/metadata.xml'); + if ($meta) { + ($buff, $status) = $zip->contents($meta); + unless ($status) { + my %dirInfo = ( + DirName => 'XML', + DataPt => \$buff, + DirLen => length $buff, + DataLen => length $buff, + ); + # (avoid structure warnings when copying from XML) + my $oldWarn = $$et{NO_STRUCT_WARN}; + $$et{NO_STRUCT_WARN} = 1; + $et->ProcessDirectory(\%dirInfo, GetTagTable('Image::ExifTool::XMP::Main')); + $$et{NO_STRUCT_WARN} = $oldWarn; + } + } + # process rootfile of EPUB container if applicable + for (;;) { + last if $meta and $mime ne 'application/epub+zip'; + my $container = ExtractFile($et, $zip, 'META-INF/container.xml'); + ($buff, $status) = $zip->contents($container); + last if $status; + $buff =~ /<rootfile\s+[^>]*?\bfull-path=(['"])(.*?)\1/s or last; + # load the rootfile data (OPF extension; contains XML metadata) + my $meta2 = $zip->memberNamed($2) or last; + $meta = $meta2; + ($buff, $status) = $zip->contents($meta); + last if $status; + # use opf:event to generate more meaningful tag names for dc:date + while ($buff =~ s{<dc:date opf:event="(\w+)">([^<]+)</dc:date>}{<dc:${1}Date>$2</dc:${1}Date>}s) { + my $dcTable = GetTagTable('Image::ExifTool::XMP::dc'); + my $tag = "${1}Date"; + AddTagToTable($dcTable, $tag, { + Name => ucfirst $tag, + Groups => { 2 => 'Time' }, + List => 'Seq', + %Image::ExifTool::XMP::dateTimeInfo + }) unless $$dcTable{$tag}; + } + my %dirInfo = ( + DataPt => \$buff, + DirLen => length $buff, + DataLen => length $buff, + IgnoreProp => { 'package' => 1, metadata => 1 }, + ); + # (avoid structure warnings when copying from XML) + my $oldWarn = $$et{NO_STRUCT_WARN}; + $$et{NO_STRUCT_WARN} = 1; + $et->ProcessDirectory(\%dirInfo, GetTagTable('Image::ExifTool::XMP::XML')); + $$et{NO_STRUCT_WARN} = $oldWarn; + last; + } + if ($openDocType{$mime} or $meta) { + # extract preview image(s) from "Thumbnails" directory if they exist + my $type; + my %tag = ( jpg => 'PreviewImage', png => 'PreviewPNG' ); + foreach $type ('jpg', 'png') { + my $thumb = ExtractFile($et, $zip, "Thumbnails/thumbnail.$type"); + next unless $thumb; + ($buff, $status) = $zip->contents($thumb); + $et->FoundTag($tag{$type}, $buff) unless $status; + } + last; # all done since we recognized the MIME type or found metadata + } + # continue on to list ZIP contents... + } + } + + # otherwise just extract general ZIP information + $et->SetFileType(); + @members = $zip->members(); + my ($member, $iWorkType); + # special files to extract + my %extract = ( + 'meta.json' => 1, + 'previews/preview.png' => 'PreviewPNG', + 'preview.jpg' => 'PreviewImage', # (iWork 2013 files) + 'preview-web.jpg' => 'OtherImage', # (iWork 2013 files) + 'preview-micro.jpg' => 'ThumbnailImage', # (iWork 2013 files) + 'QuickLook/Thumbnail.jpg' => 'ThumbnailImage', # (iWork 2009 files) + 'QuickLook/Preview.pdf' => 'PreviewPDF', # (iWork 2009 files) + ); + foreach $member (@members) { + $$et{DOC_NUM} = ++$docNum; + HandleMember($et, $member, $tagTablePtr); + my $file = $member->fileName(); + # extract things from Sketch files + if ($extract{$file}) { + ($buff, $status) = $zip->contents($member); + $status and $et->Warn("Error extracting $file"), next; + if ($file eq 'meta.json') { + $et->ExtractInfo(\$buff, { ReEntry => 1 }); + if ($$et{VALUE}{App} and $$et{VALUE}{App} =~ /sketch/i) { + $et->OverrideFileType('SKETCH'); + } + } else { + $et->FoundTag($extract{$file} => $buff); + } + } elsif ($file eq 'Index/Document.iwa' and not $iWorkType) { + my $type = $iWorkType{$$et{FILE_EXT} || ''}; + $iWorkType = $type || 'PAGES'; + } elsif ($iWorkFile{$file}) { + $iWorkType = $iWorkFile{$file}; + } + } + $et->OverrideFileType($iWorkType) if $iWorkType; + last; + } + # all done if we processed this using Archive::Zip + if ($zip) { + delete $$dirInfo{ZIP}; + delete $$et{DOC_NUM}; + if ($docNum > 1 and not $et->Options('Duplicates')) { + $et->Warn("Use the Duplicates option to extract tags for all $docNum files", 1); + } + return 1; + } +# +# process the ZIP file by hand (funny, but this seems easier than using Archive::Zip) +# + $et->VPrint(1, " -- processing as binary data --\n"); + $raf->Seek(30, 0); + $et->SetFileType(); + SetByteOrder('II'); + + # A. Local file header: + # local file header signature 0) 4 bytes (0x04034b50) + # version needed to extract 4) 2 bytes + # general purpose bit flag 6) 2 bytes + # compression method 8) 2 bytes + # last mod file time 10) 2 bytes + # last mod file date 12) 2 bytes + # crc-32 14) 4 bytes + # compressed size 18) 4 bytes + # uncompressed size 22) 4 bytes + # file name length 26) 2 bytes + # extra field length 28) 2 bytes + for (;;) { + my $len = Get16u(\$buff, 26) + Get16u(\$buff, 28); + $raf->Read($buf2, $len) == $len or last; + + $$et{DOC_NUM} = ++$docNum; + $buff .= $buf2; + my %dirInfo = ( + DataPt => \$buff, + DataPos => $raf->Tell() - 30 - $len, + DataLen => 30 + $len, + DirStart => 0, + DirLen => 30 + $len, + MixedTags => 1, # (to ignore FileComment tag) + ); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + my $flags = Get16u(\$buff, 6); + if ($flags & 0x08) { + # we don't yet support skipping stream mode data + # (when this happens, the CRC, compressed size and uncompressed + # sizes are set to 0 in the header. Instead, they are stored + # after the compressed data with an optional header of 0x08074b50) + $et->Warn('Stream mode data encountered, file list may be incomplete'); + last; + } + $len = Get32u(\$buff, 18); # file data length + $raf->Seek($len, 1) or last; # skip file data + $raf->Read($buff, 30) == 30 and $buff =~ /^PK\x03\x04/ or last; + } + delete $$et{DOC_NUM}; + if ($docNum > 1 and not $et->Options('Duplicates')) { + $et->Warn("Use the Duplicates option to extract tags for all $docNum files", 1); + } + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::ZIP - Read ZIP archive meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from ZIP, GZIP and RAR archives. This includes ZIP-based file +types like Office Open XML (DOCX, PPTX and XLSX), Open Document (ODB, ODC, +ODF, ODG, ODI, ODP, ODS and ODT), iWork (KEY, PAGES, NUMBERS), Capture One +Enhanced Image Package (EIP), Adobe InDesign Markup Language (IDML), +Electronic Publication (EPUB), and Sketch design files (SKETCH). + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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.pkware.com/documents/casestudies/APPNOTE.TXT> + +=item L<http://www.gzip.org/zlib/rfc-gzip.html> + +=item L<http://DataCompression.info/ArchiveFormats/RAR202.txt> + +=item L<https://www.rarlab.com/technote.htm> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/ZIP Tags>, +L<Image::ExifTool::TagNames/OOXML Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/ZISRAW.pm b/ExifTool/lib/Image/ExifTool/ZISRAW.pm new file mode 100644 index 0000000..c2ce47e --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/ZISRAW.pm @@ -0,0 +1,242 @@ +#------------------------------------------------------------------------------ +# File: ZISRAW.pm +# +# Description: Read ZISRAW (CZI) meta information +# +# Revisions: 2020-08-07 - P. Harvey Created +# +# References: 1) https://www.zeiss.com/microscopy/us/products/microscope-software/zen/czi.html +#------------------------------------------------------------------------------ + +package Image::ExifTool::ZISRAW; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); + +$VERSION = '1.01'; + +%Image::ExifTool::ZISRAW::Main = ( + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' }, + NOTES => q{ + As well as the header information listed below, ExifTool also extracts the + top-level XML-based metadata from Zeiss Integrated Software RAW (ZISRAW) CZI + files. + }, + 0x20 => { + Name => 'ZISRAWVersion', + Format => 'int32u[2]', + PrintConv => '$val =~ tr/ /./; $val', + }, + 0x30 => { + Name => 'PrimaryFileGUID', + Format => 'undef[16]', + ValueConv => 'unpack("H*",$val)', + }, + 0x40 => { + Name => 'FileGUID', + Format => 'undef[16]', + ValueConv => 'unpack("H*",$val)', + }, +); + +#------------------------------------------------------------------------------ +# Shorten obscenely long CZI tag names +# Inputs: Tag name +# Returns: Shortened tag name +sub ShortenTagNames($) +{ + local $_; + $_ = shift; + s/^HardwareSetting//; + s/^DevicesDevice/Device/; + s/LightPathNode//g; + s/Successors//g; + s/ExperimentExperiment/Experiment/g; + s/ObjectivesObjective/Objective/; + s/ChannelsChannel/Channel/; + s/TubeLensesTubeLens/TubeLens/; + s/^ExperimentHardwareSettingsPoolHardwareSetting/HardwareSetting/; + s/SharpnessMeasureSetSharpnessMeasure/Sharpness/; + s/FocusSetupAutofocusSetup/Autofocus/; + s/TracksTrack/Track/; + s/ChannelRefsChannelRef/ChannelRef/; + s/ChangerChanger/Changer/; + s/ElementsChangerElement/Changer/; + s/ChangerElements/Changer/; + s/ContrastChangerContrast/Contrast/; + s/KeyFunctionsKeyFunction/KeyFunction/; + s/ManagerContrastManager(Contrast)?/ManagerContrast/; + s/ObjectiveChangerObjective/ObjectiveChanger/; + s/ManagerLightManager/ManagerLight/; + s/WavelengthAreasWavelengthArea/WavelengthArea/; + s/ReflectorChangerReflector/ReflectorChanger/; + s/^StageStageAxesStageAxis/StageAxis/; + s/ShutterChangerShutter/ShutterChanger/; + s/OnOffChangerOnOff/OnOffChanger/; + s/UnsharpMaskStateUnsharpMask/UnsharpMask/; + s/Acquisition/Acq/; + s/Continuous/Cont/; + s/Resolution/Res/; + s/Experiment/Expt/g; + s/Threshold/Thresh/; + s/Reference/Ref/; + s/Magnification/Mag/; + s/Original/Orig/; + s/FocusSetupFocusStrategySetup/Focus/; + s/ParametersParameter/Parameter/; + s/IntervalInfo/Interval/; + s/ExptBlocksAcqBlock/AcqBlock/; + s/MicroscopesMicroscope/Microscope/; + s/TimeSeriesInterval/TimeSeries/; + s/Interval(.*Interval)/$1/; + s/SingleTileRegionsSingleTileRegion/SingleTileRegion/; + s/AcquisitionMode//; + s/DetectorsDetector/Detector/; + s/Setup//; + s/Setting//; + s/TrackTrack/Track/; + s/AnalogOutMaximumsAnalogOutMaximum/AnalogOutMaximum/; + s/AnalogOutMinimumsAnalogOutMinimum/AnalogOutMinimum/; + s/DigitalOutLabelsDigitalOutLabelLabel/DigitalOutLabelLabel/; + s/(VivaTomeOpticalSectionInformation)+VivaTomeOpticalSectionInformation/VivaTomeOpticalSectionInformation/; + s/FocusDefiniteFocus/FocusDefinite/; + s/ChangerChanger/Changer/; + s/Calibration/Cal/; + s/LightSwitchChangerRLTLSwitch/LightSwitchChangerRLTL/; + s/Parameters//; + s/Fluorescence/Fluor/; + s/CameraGeometryCameraGeometry/CameraGeometry/; + s/CameraCamera/Camera/; + s/DetectorsCamera/Camera/; + s/FilterChangerLeftChangerEmissionFilter/LeftChangerEmissionFilter/; + s/SwitchingStatesSwitchingState/SwitchingState/; + s/Information/Info/; + s/SubDimensions?//g; + s/Setups?//; + s/Parameters?//; + s/Calculate/Calc/; + s/Visibility/Vis/; + s/Orientation/Orient/; + s/ListItems/Items/; + s/Increment/Incr/; + s/Parameter/Param/; + s/(ParfocalParcentralValues)+ParfocalParcentralValue/Parcentral/; + s/ParcentralParcentral/Parcentral/; + s/CorrFocusCorrection/FocusCorr/; + s/(ApoTomeDepthInfo)+Element/ApoTomeDepth/; + s/(ApoTomeClickStopInfo)+Element/ApoTomeClickStop/; + s/DepthDepth/Depth/; + s/(Devices?)+Device/Device/; + s/(BeamPathNode)+/BeamPathNode/; + s/BeamPathsBeamPath/BeamPath/g; + s/BeamPathBeamPath/BeamPath/g; + s/Configuration/Config/; + s/StageAxesStageAxis/StageAxis/; + s/RangesRange/Range/; + s/DataGridDatasGridData(Grid)?/DataGrid/; + s/DataMicroscopeDatasMicroscopeData(Microscope)?/DataMicroscope/; + s/DataWegaDatasWegaData/DataWega/; + s/ClickStopPositionsClickStopPosition/ClickStopPosition/; + s/LightSourcess?LightSource(Settings)?(LightSource)?/LightSource/; + s/FilterSetsFilterSet/FilterSet/; + s/EmissionFiltersEmissionFilter/EmissionFilter/; + s/ExcitationFiltersExcitationFilter/ExcitationFilter/; + s/FiltersFilter/Filter/; + s/DichroicsDichroic/Dichronic/; + s/WavelengthsWavelength/Wavelength/; + s/MultiTrackSetup/MultiTrack/; + s/TrackTrack/Track/; + s/DataGrabberSetup/DataGrabber/; + s/CameraFrameSetup/CameraFrame/; + s/TimeSeries(TimeSeries|Setups)/TimeSeries/; + s/FocusFocus/Focus/; + s/FocusAutofocus/Autofocus/; + s/Focus(Hardware|Software)(Autofocus)+/Autofocus$1/; + s/AutofocusAutofocus/Autofocus/; + return $_; +} + +#------------------------------------------------------------------------------ +# Extract metadata from a ZISRAW (CZI) image +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 on success, 0 if this wasn't a valid CZI file +sub ProcessCZI($$) +{ + my ($et, $dirInfo) = @_; + my $raf = $$dirInfo{RAF}; + my ($buff, $tagTablePtr); + + # verify this is a valid CZI file + return 0 unless $raf->Read($buff, 100) == 100; + return 0 unless $buff =~ /^ZISRAWFILE\0{6}/; + $et->SetFileType(); + SetByteOrder('II'); + my %dirInfo = ( + DataPt => \$buff, + DirStart => 0, + DirLen => length($buff), + ); + $tagTablePtr = GetTagTable('Image::ExifTool::ZISRAW::Main'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + + # read the metadata section + my $pos = Get64u(\$buff, 92) or return 1; + $raf->Seek($pos, 0) or $et->Warn('Error seeking to metadata'), return 0; + $raf->Read($buff, 288) == 288 or $et->Warn('Error reading metadata header'), return 0; + $buff =~ /^ZISRAWMETADATA\0\0/ or $et->Warn('Invalid metadata header'), return 0; + my $len = Get32u(\$buff, 32); + $len < 200000000 or $et->Warn('Metadata section too large. Ignoring'), return 0; + $raf->Read($buff, $len) or $et->Warn('Error reading XML metadata'), return 0; + $et->FoundTag('XML', $buff); # extract as a block + $tagTablePtr = GetTagTable('Image::ExifTool::XMP::XML'); + $dirInfo{DirLen} = length $buff; + # shorten tag names somewhat by removing 'ImageDocumentMetadata' prefix from all + $$et{XmpIgnoreProps} = [ 'ImageDocument', 'Metadata', 'Information' ]; + $$et{ShortenXmpTags} = \&ShortenTagNames; + + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::ZISRAW - Read ZISRAW (CZI) meta information + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to read +metadata from Zeiss Integrated Software RAW (ZISRAW) CZI files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +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<https://www.zeiss.com/microscopy/us/products/microscope-software/zen/czi.html> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/ZISRAW Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/lib/Image/ExifTool/iWork.pm b/ExifTool/lib/Image/ExifTool/iWork.pm new file mode 100644 index 0000000..8ce1ef3 --- /dev/null +++ b/ExifTool/lib/Image/ExifTool/iWork.pm @@ -0,0 +1,230 @@ +#------------------------------------------------------------------------------ +# File: iWork.pm +# +# Description: Read Apple iWork '09 XML+ZIP files +# +# Revisions: 2009/11/11 - P. Harvey Created +#------------------------------------------------------------------------------ + +package Image::ExifTool::iWork; + +use strict; +use vars qw($VERSION); +use Image::ExifTool qw(:DataAccess :Utils); +use Image::ExifTool::XMP; +use Image::ExifTool::ZIP; + +$VERSION = '1.06'; + +# test for recognized iWork document extensions and outer XML elements +my %iWorkType = ( + # file extensions + NUMBERS => 'NUMBERS', + PAGES => 'PAGES', + KEY => 'KEY', + KTH => 'KTH', + NMBTEMPLATE => 'NMBTEMPLATE', + # we don't support double extensions -- + # "PAGES.TEMPLATE" => 'Apple Pages Template', + # outer XML elements + 'ls:document' => 'NUMBERS', + 'sl:document' => 'PAGES', + 'key:presentation' => 'KEY', +); + +# MIME types for iWork files (Apple has not registered these yet, but these +# are my best guess after doing some googling. I'm not 100% sure what "sff" +# indicates, but I think it refers to the new "flattened" package format) +my %mimeType = ( + 'NUMBERS' => 'application/x-iwork-numbers-sffnumbers', + 'PAGES' => 'application/x-iwork-pages-sffpages', + 'KEY' => 'application/x-iWork-keynote-sffkey', + 'NMBTEMPLATE' => 'application/x-iwork-numbers-sfftemplate', + 'PAGES.TEMPLATE'=> 'application/x-iwork-pages-sfftemplate', + 'KTH' => 'application/x-iWork-keynote-sffkth', +); + +# iWork tags +%Image::ExifTool::iWork::Main = ( + GROUPS => { 0 => 'XML', 1 => 'XML', 2 => 'Document' }, + PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP, + VARS => { NO_ID => 1 }, + NOTES => q{ + The Apple iWork '09 file format is a ZIP archive containing XML files + similar to the Office Open XML (OOXML) format. Metadata tags in iWork + files are extracted even if they don't appear below. + }, + authors => { Name => 'Author', Groups => { 2 => 'Author' } }, + comment => { }, + copyright => { Groups => { 2 => 'Author' } }, + keywords => { }, + projects => { List => 1 }, + title => { }, +); + +#------------------------------------------------------------------------------ +# Generate a tag ID for this XML tag +# Inputs: 0) tag property name list ref +# Returns: tagID +sub GetTagID($) +{ + my $props = shift; + return 0 if $$props[-1] =~ /^\w+:ID$/; # ignore ID tags + return $$props[0] =~ /^.*?:(.*)/ ? $1 : $$props[0]; +} + +#------------------------------------------------------------------------------ +# We found an XMP property name/value +# Inputs: 0) ExifTool object ref, 1) tag table ref +# 2) reference to array of XMP property names (last is current property) +# 3) property value, 4) attribute hash ref (not used here) +# Returns: 1 if valid tag was found +sub FoundTag($$$$;$) +{ + my ($et, $tagTablePtr, $props, $val, $attrs) = @_; + return 0 unless @$props; + my $verbose = $et->Options('Verbose'); + + $et->VPrint(0, " | - Tag '", join('/',@$props), "'\n") if $verbose > 1; + + # un-escape XML character entities + $val = Image::ExifTool::XMP::UnescapeXML($val); + # convert from UTF8 to ExifTool Charset + $val = $et->Decode($val, 'UTF8'); + my $tag = GetTagID($props) or return 0; + + # add any unknown tags to table + unless ($$tagTablePtr{$tag}) { + $et->VPrint(0, " [adding $tag]\n") if $verbose; + AddTagToTable($tagTablePtr, $tag, { Name => ucfirst $tag }); + } + # save the tag + $et->HandleTag($tagTablePtr, $tag, $val); + + return 1; +} + +#------------------------------------------------------------------------------ +# Extract information from an iWork file +# Inputs: 0) ExifTool object reference, 1) dirInfo reference +# Returns: 1 +# Notes: Upon entry to this routine, the file type has already been verified +# as ZIP and the dirInfo hash contains a 'ZIP' Archive::Zip object reference +sub Process_iWork($$) +{ + my ($et, $dirInfo) = @_; + my $zip = $$dirInfo{ZIP}; + my ($type, $index, $indexFile, $status); + + # try to determine the file type + local $SIG{'__WARN__'} = \&Image::ExifTool::ZIP::WarnProc; + # trust type given by file extension if available + $type = $iWorkType{$$et{FILE_EXT}} if $$et{FILE_EXT}; + unless ($type) { + # read the index file + my @members = $zip->membersMatching('^index\.(xml|apxl)$'); + if (@members) { + ($index, $status) = $zip->contents($members[0]); + unless ($status) { + $indexFile = $members[0]->fileName(); + if ($index =~ /^\s*<\?xml version=[^<]+<(\w+:\w+)/s) { + $type = $iWorkType{$1} if $iWorkType{$1}; + } + } + } else { + @members = $zip->membersMatching('(?i)^.*\.(pages|numbers|key)/Index.*'); + if (@members) { + my $tmp = $members[0]->fileName(); + $type = $iWorkType{uc $1} if $tmp =~ /\.(pages|numbers|key)/i; + } + } + $type or $type = 'ZIP'; # assume ZIP by default + } + $et->SetFileType($type, $mimeType{$type}); + + my @members = $zip->members(); + my $docNum = 0; + my $member; + foreach $member (@members) { + # get filename of this ZIP member + my $file = $member->fileName(); + next unless defined $file; + $et->VPrint(0, "File: $file\n"); + # set the document number and extract ZIP tags + $$et{DOC_NUM} = ++$docNum; + Image::ExifTool::ZIP::HandleMember($et, $member); + + # process only the index XML and JPEG thumbnail/preview files + next unless $file =~ m{^(index\.(xml|apxl)|QuickLook/Thumbnail\.jpg|[^/]+/preview(-micro|-web)?.jpg)$}i; + # get the file contents if necessary + # (CAREFUL! $buff MUST be local since we hand off a value ref to PreviewImage) + my ($buff, $buffPt); + if ($indexFile and $indexFile eq $file) { + # use the index file we already loaded + $buffPt = \$index; + } else { + ($buff, $status) = $zip->contents($member); + $status and $et->Warn("Error extracting $file"), next; + $buffPt = \$buff; + } + # extract JPEG as PreviewImage (should only be QuickLook/Thumbnail.jpg) + if ($file =~ /\.jpg$/) { + my $type = ($file =~ /preview-(\w+)/) ? ($1 eq 'web' ? 'Other' : 'Thumbnail') : 'Preview'; + $et->FoundTag($type . 'Image', $buffPt); + next; + } + # process "metadata" section of XML index file + next unless $$buffPt =~ /<(\w+):metadata>/g; + my $ns = $1; + my $p1 = pos $$buffPt; + next unless $$buffPt =~ m{</${ns}:metadata>}g; + # construct XML data from "metadata" section only + $$buffPt = '<?xml version="1.0"?>' . substr($$buffPt, $p1, pos($$buffPt)-$p1); + my %dirInfo = ( + DataPt => $buffPt, + DirLen => length $$buffPt, + DataLen => length $$buffPt, + XMPParseOpts => { + FoundProc => \&FoundTag, + }, + ); + my $tagTablePtr = GetTagTable('Image::ExifTool::iWork::Main'); + $et->ProcessDirectory(\%dirInfo, $tagTablePtr); + undef $$buffPt; # (free memory now) + } + delete $$et{DOC_NUM}; + return 1; +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::iWork - Read Apple iWork '09 XML+ZIP files + +=head1 SYNOPSIS + +This module is used by Image::ExifTool + +=head1 DESCRIPTION + +This module contains definitions required by Image::ExifTool to extract meta +information from Apple iWork '09 XML+ZIP files. + +=head1 AUTHOR + +Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Image::ExifTool::TagNames/iWork Tags>, +L<Image::ExifTool::TagNames/OOXML Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/ExifTool/perl-Image-ExifTool.spec b/ExifTool/perl-Image-ExifTool.spec new file mode 100644 index 0000000..a90d7f1 --- /dev/null +++ b/ExifTool/perl-Image-ExifTool.spec @@ -0,0 +1,120 @@ +Summary: perl module for image data extraction +Name: perl-Image-ExifTool +Version: 12.67 +Release: 1 +License: Artistic/GPL +Group: Development/Libraries/Perl +URL: https://exiftool.org/ +Source0: Image-ExifTool-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root + +%description +ExifTool is a customizable set of Perl modules plus a full-featured +command-line application for reading and writing meta information in a wide +variety of files, including the maker note information of many digital +cameras by various manufacturers such as Canon, Casio, DJI, FLIR, FujiFilm, +GE, GoPro, HP, JVC/Victor, Kodak, Leaf, Minolta/Konica-Minolta, Nikon, +Nintendo, Olympus/Epson, Panasonic/Leica, Pentax/Asahi, Phase One, Reconyx, +Ricoh, Samsung, Sanyo, Sigma/Foveon and Sony. + +Below is a list of file types and meta information formats currently +supported by ExifTool (r = read, w = write, c = create): + + File Types + ------------+-------------+-------------+-------------+------------ + 360 r/w | DPX r | ITC r | NUMBERS r | RAW r/w + 3FR r | DR4 r/w/c | J2C r | O r | RIFF r + 3G2 r/w | DSS r | JNG r/w | ODP r | RSRC r + 3GP r/w | DV r | JP2 r/w | ODS r | RTF r + 7Z r | DVB r/w | JPEG r/w | ODT r | RW2 r/w + A r | DVR-MS r | JSON r | OFR r | RWL r/w + AA r | DYLIB r | JXL r | OGG r | RWZ r + AAE r | EIP r | K25 r | OGV r | RM r + AAX r/w | EPS r/w | KDC r | ONP r | SEQ r + ACR r | EPUB r | KEY r | OPUS r | SKETCH r + AFM r | ERF r/w | LA r | ORF r/w | SO r + AI r/w | EXE r | LFP r | ORI r/w | SR2 r/w + AIFF r | EXIF r/w/c | LIF r | OTF r | SRF r + APE r | EXR r | LNK r | PAC r | SRW r/w + ARQ r/w | EXV r/w/c | LRV r/w | PAGES r | SVG r + ARW r/w | F4A/V r/w | M2TS r | PBM r/w | SWF r + ASF r | FFF r/w | M4A/V r/w | PCD r | THM r/w + AVI r | FITS r | MACOS r | PCX r | TIFF r/w + AVIF r/w | FLA r | MAX r | PDB r | TORRENT r + AZW r | FLAC r | MEF r/w | PDF r/w | TTC r + BMP r | FLIF r/w | MIE r/w/c | PEF r/w | TTF r + BPG r | FLV r | MIFF r | PFA r | TXT r + BTF r | FPF r | MKA r | PFB r | VCF r + CHM r | FPX r | MKS r | PFM r | VNT r + COS r | GIF r/w | MKV r | PGF r | VRD r/w/c + CR2 r/w | GLV r/w | MNG r/w | PGM r/w | VSD r + CR3 r/w | GPR r/w | MOBI r | PLIST r | WAV r + CRM r/w | GZ r | MODD r | PICT r | WDP r/w + CRW r/w | HDP r/w | MOI r | PMP r | WEBP r/w + CS1 r/w | HDR r | MOS r/w | PNG r/w | WEBM r + CSV r | HEIC r/w | MOV r/w | PPM r/w | WMA r + CUR r | HEIF r/w | MP3 r | PPT r | WMV r + CZI r | HTML r | MP4 r/w | PPTX r | WPG r + DCM r | ICC r/w/c | MPC r | PS r/w | WTV r + DCP r/w | ICO r | MPG r | PSB r/w | WV r + DCR r | ICS r | MPO r/w | PSD r/w | X3F r/w + DFONT r | IDML r | MQV r/w | PSP r | XCF r + DIVX r | IIQ r/w | MRC r | QTIF r/w | XLS r + DJVU r | IND r/w | MRW r/w | R3D r | XLSX r + DLL r | INSP r/w | MXF r | RA r | XMP r/w/c + DNG r/w | INSV r | NEF r/w | RAF r/w | ZIP r + DOC r | INX r | NKSC r/w | RAM r | + DOCX r | ISO r | NRW r/w | RAR r | + + Meta Information + ----------------------+----------------------+--------------------- + EXIF r/w/c | CIFF r/w | Ricoh RMETA r + GPS r/w/c | AFCP r/w | Picture Info r + IPTC r/w/c | Kodak Meta r/w | Adobe APP14 r + XMP r/w/c | FotoStation r/w | MPF r + MakerNotes r/w/c | PhotoMechanic r/w | Stim r + Photoshop IRB r/w/c | JPEG 2000 r | DPX r + ICC Profile r/w/c | DICOM r | APE r + MIE r/w/c | Flash r | Vorbis r + JFIF r/w/c | FlashPix r | SPIFF r + Ducky APP12 r/w/c | QuickTime r | DjVu r + PDF r/w/c | Matroska r | M2TS r + PNG r/w/c | MXF r | PE/COFF r + Canon VRD r/w/c | PrintIM r | AVCHD r + Nikon Capture r/w/c | FLAC r | ZIP r + GeoTIFF r/w/c | ID3 r | (and more) + +See html/index.html for more details about ExifTool features. + +%prep +%setup -n Image-ExifTool-%{version} + +%build +perl Makefile.PL INSTALLDIRS=vendor + +%install +rm -rf $RPM_BUILD_ROOT +%makeinstall DESTDIR=%{?buildroot:%{buildroot}} +find $RPM_BUILD_ROOT -name perllocal.pod | xargs rm + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +%doc Changes html +%{_libdir}/perl5/* +/usr/share/*/* +%{_mandir}/*/* +%{_bindir}/* + +%changelog +* Tue May 06 2014 - Norbert de Rooy <nsrderooy@gmail.com> +- Spec file fixed for Redhat 6 +* Tue May 09 2006 - Niels Kristian Bech Jensen <nkbj@mail.tele.dk> +- Spec file fixed for Mandriva Linux 2006. +* Mon May 08 2006 - Volker Kuhlmann <VolkerKuhlmann@gmx.de> +- Spec file fixed for SUSE. +- Package available from: http://volker.dnsalias.net/soft/ +* Sat Jun 19 2004 Kayvan Sylvan <kayvan@sylvan.com> - Image-ExifTool +- Initial build. diff --git a/ExifTool/t/AFCP.t b/ExifTool/t/AFCP.t new file mode 100644 index 0000000..c1cb7f6 --- /dev/null +++ b/ExifTool/t/AFCP.t @@ -0,0 +1,45 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/AFCP.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::AFCP; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'AFCP'; +my $testnum = 1; + +# test 2: Extract information from AFCP.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/AFCP.jpg', {Duplicates => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Test writing a bunch of information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/IPTC.jpg'); + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->WriteInfo('t/images/AFCP.jpg',$testfile); + my $info = $exifTool->ImageInfo($testfile, {Group1 => 'IPTC2'}); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/AFCP_2.out b/ExifTool/t/AFCP_2.out new file mode 100644 index 0000000..13f34f8 --- /dev/null +++ b/ExifTool/t/AFCP_2.out @@ -0,0 +1,39 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: AFCP.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1110 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:03:59-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[IPTC, IPTC2, Other] 0 - Application Record Version: 2 +[IPTC, IPTC2, Other] 15 - Category: p +[IPTC, IPTC2, Time] 55 - Date Created: 2005:12:23 +[IPTC, IPTC2, Other] 5 - Object Name: object name +[IPTC, IPTC2, Other] 10 - Urgency: 2 +[IPTC, IPTC2, Other] 20 - Supplemental Categories: supp cat +[IPTC, IPTC2, Other] 25 - Keywords: keyword +[IPTC, IPTC2, Other] 40 - Special Instructions: special instructions +[IPTC, IPTC2, Author] 80 - By-line: byline +[IPTC, IPTC2, Author] 85 - By-line Title: byline title +[IPTC, IPTC2, Location] 90 - City: city +[IPTC, IPTC2, Location] 101 - Country-Primary Location Name: country name +[IPTC, IPTC2, Other] 103 - Original Transmission Reference: otr +[IPTC, IPTC2, Other] 105 - Headline: headline +[IPTC, IPTC2, Author] 110 - Credit: credit +[IPTC, IPTC2, Author] 115 - Source: source +[IPTC, IPTC2, Author] 116 - Copyright Notice: copy freely +[IPTC, IPTC2, Other] 120 - Caption-Abstract: ExifTool AFCP test +[IPTC, IPTC2, Author] 122 - Writer-Editor: caption writer +[IPTC, IPTC2, Location] 95 - Province-State: state +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/AFCP_3.out b/ExifTool/t/AFCP_3.out new file mode 100644 index 0000000..7f2911a --- /dev/null +++ b/ExifTool/t/AFCP_3.out @@ -0,0 +1,20 @@ +[IPTC, IPTC2, Other] 0 - Application Record Version: 2 +[IPTC, IPTC2, Other] 15 - Category: 1 +[IPTC, IPTC2, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC2, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC2, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC2, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC2, Other] 25 - Keywords: ExifTool, Test, IPTC +[IPTC, IPTC2, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC2, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC2, Author] 85 - By-line Title: My Position +[IPTC, IPTC2, Location] 90 - City: Kingston +[IPTC, IPTC2, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC2, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC2, Other] 105 - Headline: No headline +[IPTC, IPTC2, Author] 110 - Credit: My Credit +[IPTC, IPTC2, Author] 115 - Source: I'm the source +[IPTC, IPTC2, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[IPTC, IPTC2, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC2, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC2, Location] 95 - Province-State: Ont diff --git a/ExifTool/t/AIFF.t b/ExifTool/t/AIFF.t new file mode 100644 index 0000000..37830b7 --- /dev/null +++ b/ExifTool/t/AIFF.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/AIFF.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::AIFF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'AIFF'; +my $testnum = 1; + +# test 2: Extract information from AIFF.aif +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/AIFF.aif'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/AIFF_2.out b/ExifTool/t/AIFF_2.out new file mode 100644 index 0000000..6e05057 --- /dev/null +++ b/ExifTool/t/AIFF_2.out @@ -0,0 +1,33 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: AIFF.aif +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 290 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:06 15:05:23-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:03:59-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: AIFF +[File, File, Other] FileTypeExtension - File Type Extension: aiff +[File, File, Other] MIMEType - MIME Type: audio/x-aiff +[File, File, Image] ID3Size - ID3 Size: 172 +[AIFF, AIFF, Time] 0 - Comment Time: 2004:03:08 05:28:46 +[AIFF, AIFF, Audio] 2 - Comment: ding.wav +[AIFF, AIFF, Audio] 0 - Num Channels: 1 +[AIFF, AIFF, Audio] 1 - Num Sample Frames: 11554 +[AIFF, AIFF, Audio] 3 - Sample Size: 8 +[AIFF, AIFF, Audio] 4 - Sample Rate: 22050 +[AIFF, AIFF, Audio] NAME - Name: ExifTool test AIFF +[AIFF, AIFF, Author] AUTH - Author: Phil Harvey +[ID3, ID3v2_2, Audio] ULT - Lyrics: my lyrics +[ID3, ID3v2_2, Audio] TCM - Composer: Composer +[ID3, ID3v2_2, Audio] COM - Comment: comments +[ID3, ID3v2_2, Author] TP1 - Artist: the artist +[ID3, ID3v2_2, Audio] TAL - Album: the album +[ID3, ID3v2_2, Audio] TT1 - Grouping: grouping +[ID3, ID3v2_2, Audio] TRK - Track: 1 +[ID3, ID3v2_2, Audio] TPA - Part Of Set: 1/1 +[ID3, ID3v2_2, Time] TYE - Year: 2006 +[ID3, ID3v2_2, Audio] TCO - Genre: Techno +[ID3, ID3v2_2, Audio] TCP - Compilation: Yes +[Composite, Composite, Other] AIFF-Duration - Duration: 0.52 s +[Composite, Composite, Time] ID3-DateTimeOriginal - Date/Time Original: 2006 diff --git a/ExifTool/t/APE.t b/ExifTool/t/APE.t new file mode 100644 index 0000000..ff82be1 --- /dev/null +++ b/ExifTool/t/APE.t @@ -0,0 +1,38 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/APE.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::APE; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'APE'; +my $testnum = 1; + +# test 2: Extract information from APE test file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/APE.ape'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Extract information from MCP test file containing 3 different +# types of meta information: ID3v1, ID3v2 and APE +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/APE.mpc'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/APE_2.out b/ExifTool/t/APE_2.out new file mode 100644 index 0000000..933438e --- /dev/null +++ b/ExifTool/t/APE_2.out @@ -0,0 +1,30 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: APE.ape +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:11:14 11:04:22-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:30-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: APE +[File, File, Other] FileTypeExtension - File Type Extension: ape +[File, File, Other] MIMEType - MIME Type: audio/x-monkeys-audio +[APE, MAC, Audio] 0 - Compression Level: 3000 +[APE, MAC, Audio] 2 - Blocks Per Frame: 73728 +[APE, MAC, Audio] 4 - Final Frame Blocks: 42662 +[APE, MAC, Audio] 6 - Total Frames: 2 +[APE, MAC, Audio] 8 - Bits Per Sample: 16 +[APE, MAC, Audio] 9 - Channels: 2 +[APE, MAC, Audio] 10 - Sample Rate: 44100 +[APE, APE, Audio] Track - Track: 4 +[APE, APE, Audio] Year - Year: 2005 +[APE, APE, Audio] Genre - Genre: Electronic +[APE, APE, Audio] Artist - Artist: Kraftwerk +[APE, APE, Audio] Album - Album: Cover Art Test +[APE, APE, Audio] Tool Version - Tool Version: 11.1.102 +[APE, APE, Audio] Tool Name - Tool Name: Media Center +[APE, APE, Audio] Title - Title: Men Machine Live +[APE, APE, Audio] Media Jukebox: Date - Media Jukebox Date: 38353 +[APE, APE, Audio] Cover Art (front) Desc - Cover Art Front Desc: X:\_kuvat\Kraftwerk - Cover Art Test.jpg +[APE, APE, Preview] Cover Art (front) - Cover Art Front: (Binary data 1761 bytes) +[Composite, Composite, Audio] APE-Duration - Duration: 2.64 s diff --git a/ExifTool/t/APE_3.out b/ExifTool/t/APE_3.out new file mode 100644 index 0000000..95eb274 --- /dev/null +++ b/ExifTool/t/APE_3.out @@ -0,0 +1,58 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: APE.mpc +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.5 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:11:14 15:06:40-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:30-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: MPC +[File, File, Other] FileTypeExtension - File Type Extension: mpc +[File, File, Other] MIMEType - MIME Type: audio/x-musepack +[File, File, Image] ID3Size - ID3 Size: 391 +[MPC, MPC, Audio] Bit032-063 - Total Frames: 102 +[MPC, MPC, Audio] Bit080-081 - Sample Rate: 44100 +[MPC, MPC, Audio] Bit084-087 - Quality: 5 (Standard) +[MPC, MPC, Audio] Bit088-093 - Max Band: 28 +[MPC, MPC, Audio] Bit096-111 - Replay Gain Track Peak: 0 +[MPC, MPC, Audio] Bit112-127 - Replay Gain Track Gain: 0 +[MPC, MPC, Audio] Bit128-143 - Replay Gain Album Peak: 0 +[MPC, MPC, Audio] Bit144-159 - Replay Gain Album Gain: 0 +[MPC, MPC, Audio] Bit179 - Fast Seek: No +[MPC, MPC, Audio] Bit191 - Gapless: Yes +[MPC, MPC, Audio] Bit216-223 - Encoder Version: 1.1.5 +[APE, APE, Audio] Track - Track: 4 +[APE, APE, Audio] Year - Year: 2005 +[APE, APE, Audio] Genre - Genre: Electronic +[APE, APE, Audio] Artist - Artist: Kraftwerk +[APE, APE, Audio] Album - Album: Cover Art Test +[APE, APE, Audio] Tool Version - Tool Version: 11.1.102 +[APE, APE, Audio] Tool Name - Tool Name: Media Center +[APE, APE, Audio] Title - Title: Men Machine Live +[APE, APE, Audio] Media Jukebox: Date - Media Jukebox Date: 38353 +[APE, APE, Audio] Cover Art (front) Desc - Cover Art Front Desc: X:\_kuvat\Kraftwerk - Cover Art Test.jpg +[APE, APE, Preview] Cover Art (front) - Cover Art Front: (Binary data 1761 bytes) +[ID3, ID3v2_2, Audio] TRK - Track: 1/5 +[ID3, ID3v2_2, Audio] TPA - Part Of Set: 1/2 +[ID3, ID3v2_2, Audio] RVA - Relative Volume Adjustment: +18.0% Right, +18.0% Left +[ID3, ID3v2_2, Audio] ULT - Lyrics: Do-wap she-bang +[ID3, ID3v2_2, Image] PIC-1 - Picture Format: JPG +[ID3, ID3v2_2, Image] PIC-2 - Picture Type: Other +[ID3, ID3v2_2, Image] PIC-3 - Picture Description: comment +[ID3, ID3v2_2, Preview] PIC - Picture: (Binary data 15 bytes) +[ID3, ID3v2_2, Audio] TT2 - Title: ExifTool Test +[ID3, ID3v2_2, Author] TP1 - Artist: Phil Harvey +[ID3, ID3v2_2, Audio] TCM - Composer: A Composer +[ID3, ID3v2_2, Audio] TAL - Album: Phil's Greatest Hits +[ID3, ID3v2_2, Audio] TT1 - Grouping: This group +[ID3, ID3v2_2, Time] TYE - Year: 2005 +[ID3, ID3v2_2, Audio] TCO - Genre: Testing +[ID3, ID3v2_2, Audio] COM - Comment: My Comments +[ID3, ID3v1, Audio] 3 - Title: A 4s sample for testing embedd +[ID3, ID3v1, Author] 33 - Artist: Who Knows +[ID3, ID3v1, Audio] 63 - Album: The Test Album +[ID3, ID3v1, Time] 93 - Year: 2006 +[ID3, ID3v1, Audio] 97 - Comment: a nice comment +[ID3, ID3v1, Audio] 125 - Track: 1 +[ID3, ID3v1, Audio] 127 - Genre: Funk +[Composite, Composite, Time] ID3-DateTimeOriginal - Date/Time Original: 2005 diff --git a/ExifTool/t/ASF.t b/ExifTool/t/ASF.t new file mode 100644 index 0000000..659abdc --- /dev/null +++ b/ExifTool/t/ASF.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/ASF.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::ASF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'ASF'; +my $testnum = 1; + +# test 2: Extract information from ASF.wmv +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ASF.wmv'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/ASF_2.out b/ExifTool/t/ASF_2.out new file mode 100644 index 0000000..3761187 --- /dev/null +++ b/ExifTool/t/ASF_2.out @@ -0,0 +1,56 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: ASF.wmv +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 12 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2005:12:27 07:29:29-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:30-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: WMV +[File, File, Other] FileTypeExtension - File Type Extension: wmv +[File, File, Other] MIMEType - MIME Type: video/x-ms-wmv +[ASF, ASF, Video] ToolName - Tool Name: Photo Story 3 for Windows +[ASF, ASF, Video] Publisher - Publisher: Photo Story 3 for Windows +[ASF, ASF, Video] ToolVersion - Tool Version: 3.0.1115.0 +[ASF, ASF, Video] Genre - Genre: Photo Story +[ASF, ASF, Image] 0 - Picture Type: Front Cover +[ASF, ASF, Image] 1 - Picture MIME Type: image/jpeg +[ASF, ASF, Preview] 3 - Picture: (Binary data 6587 bytes) +[ASF, ASF, Video] IsVBR - Is VBR: True +[ASF, ASF, Video] ASFLeakyBucketPairs - ASF Leaky Bucket Pairs: (Binary data 114 bytes) +[ASF, ASF, Video] 0 - File ID: 5F69B0C4-04F7-4B21-9842-46CCA542D8D3 +[ASF, ASF, Video] 16 - File Length: 414891 +[ASF, ASF, Time] 24 - Creation Date: 2004:10:28 17:23:34Z +[ASF, ASF, Video] 32 - Data Packets: 287 +[ASF, ASF, Video] 40 - Duration: 0:01:39 +[ASF, ASF, Video] 48 - Send Duration: 0:01:37 +[ASF, ASF, Video] 56 - Preroll: 5000 +[ASF, ASF, Video] 64 - Flags: 2 +[ASF, ASF, Video] 68 - Min Packet Size: 1400 +[ASF, ASF, Video] 72 - Max Packet Size: 1400 +[ASF, ASF, Video] 76 - Max Bitrate: 30.6 kbps +[ASF, ASF, Video] MediaClassPrimaryID - Media Class Primary ID: DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B +[ASF, ASF, Video] MediaClassSecondaryID - Media Class Secondary ID: 0B710218-8C0C-475E-AF73-4C41C0C8F8CE +[ASF, ASF, Video] IsVBR - Is VBR: False +[ASF, ASF, Video] IsVBR - Is VBR: False +[ASF, ASF, Video] WMADRCPeakReference - WMADRC Peak Reference: 23311 +[ASF, ASF, Video] WMADRCAverageReference - WMADRC Average Reference: 5966 +[ASF, ASF, Other] AudioCodecName - Audio Codec Name: Windows Media Audio 9.1 +[ASF, ASF, Other] AudioCodecDescription - Audio Codec Description: 20 kbps, 22 kHz, stereo 1-pass CBR +[ASF, ASF, Other] VideoCodecName - Video Codec Name: Windows Media Video 9 Image v2 +[ASF, ASF, Other] VideoCodecDescription - Video Codec Description: +[ASF, ASF, Video] 0 - Stream Type: Audio +[ASF, ASF, Video] 16 - Error Correction Type: Audio Spread +[ASF, ASF, Video] 32 - Time Offset: 0 s +[ASF, ASF, Video] 48 - Stream Number: 1 +[ASF, ASF, Video] 54 - Audio Codec ID: Windows Media Audio V2 V7 V8 V9 / DivX audio (WMA) / Alex AC3 Audio +[ASF, ASF, Video] 56 - Audio Channels: 2 +[ASF, ASF, Video] 58 - Audio Sample Rate: 22050 +[ASF, ASF, Video] 0 - Stream Type: Video +[ASF, ASF, Video] 16 - Error Correction Type: No Error Correction +[ASF, ASF, Video] 32 - Time Offset: 0 s +[ASF, ASF, Video] 48 - Stream Number: 2 +[ASF, ASF, Video] 54 - Image Width: 160 +[ASF, ASF, Video] 58 - Image Height: 120 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 160x120 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.019 diff --git a/ExifTool/t/Apple.t b/ExifTool/t/Apple.t new file mode 100644 index 0000000..6f25293 --- /dev/null +++ b/ExifTool/t/Apple.t @@ -0,0 +1,29 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Apple.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Apple; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Apple'; +my $testnum = 1; + +# test 2: Extract information from Apple.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Unknown => 1, Composite => 0); + my $info = $exifTool->ImageInfo('t/images/Apple.jpg', '-file:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Apple_2.out b/ExifTool/t/Apple_2.out new file mode 100644 index 0000000..6bf52b8 --- /dev/null +++ b/ExifTool/t/Apple_2.out @@ -0,0 +1,79 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.66 +[EXIF, IFD0, Camera] 271 - Make: Apple +[EXIF, IFD0, Camera] 272 - Camera Model Name: iPhone 7 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: 10.0.2 +[EXIF, IFD0, Time] 306 - Modify Date: 2016:09:28 18:18:05 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/20 +[EXIF, ExifIFD, Image] 33437 - F Number: 1.8 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 40 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2016:09:28 18:18:05 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2016:09:28 18:18:05 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/20 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 1.8 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2.846551724 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 4.0 mm +[EXIF, ExifIFD, Camera] 37396 - Subject Area: 2015 1511 2217 1330 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 813 +[EXIF, ExifIFD, Time] 37522 - Sub Sec Time Digitized: 813 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 4032 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 3024 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 28 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Image] 42034 - Lens Info: 3.99mm f/1.8 +[EXIF, ExifIFD, Image] 42035 - Lens Make: Apple +[EXIF, ExifIFD, Image] 42036 - Lens Model: iPhone 7 back camera 3.99mm f/1.8 +[EXIF, GPS, Location] 1 - GPS Latitude Ref: North +[EXIF, GPS, Location] 2 - GPS Latitude: 53 deg 22' 58.25" +[EXIF, GPS, Location] 3 - GPS Longitude Ref: West +[EXIF, GPS, Location] 4 - GPS Longitude: 1 deg 27' 24.04" +[EXIF, GPS, Location] 5 - GPS Altitude Ref: Above Sea Level +[EXIF, GPS, Location] 6 - GPS Altitude: 72.36649215 m +[EXIF, GPS, Time] 7 - GPS Time Stamp: 17:17:58.65 +[EXIF, GPS, Location] 12 - GPS Speed Ref: km/h +[EXIF, GPS, Location] 13 - GPS Speed: 0 +[EXIF, GPS, Location] 16 - GPS Img Direction Ref: True North +[EXIF, GPS, Location] 17 - GPS Img Direction: 342.0498753 +[EXIF, GPS, Location] 23 - GPS Dest Bearing Ref: True North +[EXIF, GPS, Location] 24 - GPS Dest Bearing: 342.0498753 +[EXIF, GPS, Time] 29 - GPS Date Stamp: 2016:09:28 +[EXIF, GPS, Location] 31 - GPS Horizontal Positioning Error: 165 m +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 2078 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[MakerNotes, Apple, Image] 1 - Maker Note Version: 4 +[MakerNotes, Apple, Image] 2 - AE Matrix: (Binary data 512 bytes) +[MakerNotes, Apple, Image] flags - Run Time Flags: Valid +[MakerNotes, Apple, Image] value - Run Time Value: 39772089846958 +[MakerNotes, Apple, Image] epoch - Run Time Epoch: 0 +[MakerNotes, Apple, Image] timescale - Run Time Scale: 1000000000 +[MakerNotes, Apple, Image] 4 - AE Stable: Yes +[MakerNotes, Apple, Image] 5 - AE Target: 177 +[MakerNotes, Apple, Image] 6 - AE Average: 185 +[MakerNotes, Apple, Image] 7 - AF Stable: Yes +[MakerNotes, Apple, Camera] 8 - Acceleration Vector: -0.6483164083 0.002264119004 -0.7500767578 +[MakerNotes, Apple, Image] 12 - Focus Distance Range: 0.54 - 0.68 m +[MakerNotes, Apple, Image] 13 - Apple 0x000d: 20 +[MakerNotes, Apple, Image] 14 - Apple 0x000e: 0 +[MakerNotes, Apple, Image] 15 - OIS Mode: 2 +[MakerNotes, Apple, Image] 16 - Apple 0x0010: 1 +[MakerNotes, Apple, Image] 20 - Image Capture Type: Unknown (5) diff --git a/ExifTool/t/Audible.t b/ExifTool/t/Audible.t new file mode 100644 index 0000000..78f22d2 --- /dev/null +++ b/ExifTool/t/Audible.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Audible.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Audible; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Audible'; +my $testnum = 1; + +# test 2: Extract information from test file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Audible.aa'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Audible_2.out b/ExifTool/t/Audible_2.out new file mode 100644 index 0000000..029420a --- /dev/null +++ b/ExifTool/t/Audible_2.out @@ -0,0 +1,40 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Audible.aa +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1322 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2015:04:10 06:46:11-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:11:01 10:11:40-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: AA +[File, File, Other] FileTypeExtension - File Type Extension: aa +[File, File, Other] MIMEType - MIME Type: audio/audible +[Audible, Audible, Audio] product_id - Product Id: BK_ADBL_123456a_mp332 +[Audible, Audible, Audio] parent_id - Parent Id: BK_ADBL_123456 +[Audible, Audible, Audio] title - Title: Test: This is the title of the big book (Unabridged) +[Audible, Audible, Author] author - Author: Philip J Harvey +[Audible, Audible, Audio] provider - Provider: ExifTool Test Lib +[Audible, Audible, Audio] narrator - Narrator: Nobody that I know +[Audible, Audible, Audio] price - Price: 98.95 +[Audible, Audible, Time] pubdate - Publish Date: 08-APR-2015 +[Audible, Audible, Audio] description - Description: This is the book description +[Audible, Audible, Audio] long_description - Long Description: This is the long book description +[Audible, Audible, Author] copyright - Copyright: ©2015, Philip J Harvey; (P)2015 ExifTool Publisher +[Audible, Audible, Audio] short_title - Short Title: Short Titley (Unabridged) +[Audible, Audible, Audio] is_aggregation - Is Aggregation: collection +[Audible, Audible, Audio] title_id - Title Id: BK_ADBL_123456a +[Audible, Audible, Audio] codec - Codec: mp332 +[Audible, Audible, Audio] HeaderSeed - Header Seed: 1234567890 +[Audible, Audible, Audio] EncryptedBlocks - Encrypted Blocks: 23421 +[Audible, Audible, Audio] HeaderKey - Header Key: 1234569717 192057361 0712658018 4878930500 +[Audible, Audible, Audio] license_list - License List: 12345678 +[Audible, Audible, Audio] CPUType - CPU Type: 1 +[Audible, Audible, Audio] license_count - License Count: 1 +[Audible, Audible, Audio] 7eb298ac1328 - Tag 7eb 298ac 1328: 64863450EA7B67906FE619AC697E60D13630E760 +[Audible, Audible, Audio] parent_short_title - Parent Short Title: Test2 (Unabridged) +[Audible, Audible, Audio] parent_title - Parent Title: This is the title of the parent (Unabridged) +[Audible, Audible, Audio] aggregation_id - Aggregation Id: BK_ADBL_123456 +[Audible, Audible, Time] pub_date_start - Publish Date Start: 08-APR-2015 +[Audible, Audible, Audio] short_description - Short Description: This is the short description +[Audible, Audible, Audio] user_alias - User Alias: LKJ4HDY872HSVJ +[Audible, Audible, Preview] _cover_art - Cover Art: (Binary data 18 bytes) diff --git a/ExifTool/t/BMP.t b/ExifTool/t/BMP.t new file mode 100644 index 0000000..10670ca --- /dev/null +++ b/ExifTool/t/BMP.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/BMP.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::BMP; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'BMP'; +my $testnum = 1; + +# test 2: Extract information from BMP.bmp +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/BMP.bmp'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/BMP_2.out b/ExifTool/t/BMP_2.out new file mode 100644 index 0000000..e6a7687 --- /dev/null +++ b/ExifTool/t/BMP_2.out @@ -0,0 +1,24 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: BMP.bmp +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1142 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2005:11:14 14:47:21-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:00-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: BMP +[File, File, Other] FileTypeExtension - File Type Extension: bmp +[File, File, Other] MIMEType - MIME Type: image/bmp +[File, File, Image] 0 - BMP Version: Windows V3 +[File, File, Image] 4 - Image Width: 8 +[File, File, Image] 8 - Image Height: 8 +[File, File, Image] 12 - Planes: 1 +[File, File, Image] 14 - Bit Depth: 8 +[File, File, Image] 16 - Compression: None +[File, File, Image] 20 - Image Length: 64 +[File, File, Image] 24 - Pixels Per Meter X: 2835 +[File, File, Image] 28 - Pixels Per Meter Y: 2835 +[File, File, Image] 32 - Num Colors: 256 +[File, File, Image] 36 - Num Important Colors: 256 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/BPG.t b/ExifTool/t/BPG.t new file mode 100644 index 0000000..f160053 --- /dev/null +++ b/ExifTool/t/BPG.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/BPG.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::BPG; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'BPG'; +my $testnum = 1; + +# test 2: Extract information from BPG.bpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/BPG.bpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/BPG_2.out b/ExifTool/t/BPG_2.out new file mode 100644 index 0000000..bc4fed9 --- /dev/null +++ b/ExifTool/t/BPG_2.out @@ -0,0 +1,92 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[ExifTool, ExifTool, ExifTool] Warning - Warning: [minor] Ignored extra byte at start of EXIF extension +[File, System, Other] FileName - File Name: BPG.bpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1863 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2016:07:07 11:21:11-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:00-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: BPG +[File, File, Other] FileTypeExtension - File Type Extension: bpg +[File, File, Other] MIMEType - MIME Type: image/bpg +[File, File, Image] 4 - Pixel Format: 4:2:2 (chroma at 0.5, 0) +[File, File, Image] 4.1 - Alpha: No Alpha Plane +[File, File, Image] 4.2 - Bit Depth: 8 +[File, File, Image] 4.3 - Color Space: YCbCr (BT 601) +[File, File, Image] 4.4 - Flags: Extension Present +[File, File, Image] 6 - Image Width: 16 +[File, File, Image] 7 - Image Height: 16 +[File, File, Image] 8 - Image Length: 0 +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2003:10:31 15:44:19 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/60 +[EXIF, ExifIFD, Image] 33437 - F Number: 5.6 +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:10:31 15:44:19 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:10:31 15:44:19 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/60 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 5.6 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37385 - Flash: On, Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 55.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2048 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 3072 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3442.016807 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3443.946154 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 10.22 +[XMP, XMP-exif, Image] ComponentsConfiguration - Components Configuration: Y, Cb, Cr, - +[XMP, XMP-tiff, Image] YCbCrPositioning - Y Cb Cr Positioning: Centered +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:06:03 00:00:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple Computer Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright 1999 Adobe Systems Incorporated +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Adobe RGB (1998) +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.60974 0.31111 0.01947 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.20528 0.62567 0.06087 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14919 0.06322 0.74457 +[Composite, Composite, Image] Exif-Aperture - Aperture: 5.6 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 16x16 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000256 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/60 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 23.3 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 55.0 mm (35 mm equivalent: 87.4 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 28.56 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 10.9 diff --git a/ExifTool/t/BigTIFF.t b/ExifTool/t/BigTIFF.t new file mode 100644 index 0000000..aa31745 --- /dev/null +++ b/ExifTool/t/BigTIFF.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/BigTIFF.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::BigTIFF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'BigTIFF'; +my $testnum = 1; + +# test 2: Extract information from BigTIFF.btf +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/BigTIFF.btf'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/BigTIFF_2.out b/ExifTool/t/BigTIFF_2.out new file mode 100644 index 0000000..2ad5a81 --- /dev/null +++ b/ExifTool/t/BigTIFF_2.out @@ -0,0 +1,21 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: BigTIFF.btf +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 384 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:03:16 13:49:45-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:00-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: BTF +[File, File, Other] FileTypeExtension - File Type Extension: btf +[File, File, Other] MIMEType - MIME Type: image/x-tiff-big +[EXIF, IFD0, Image] 256 - Image Width: 8 +[EXIF, IFD0, Image] 257 - Image Height: 8 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD0, Image] 273 - Strip Offsets: 192 +[EXIF, IFD0, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD0, Image] 278 - Rows Per Strip: 8 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 192 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Canon.t b/ExifTool/t/Canon.t new file mode 100644 index 0000000..5a0c183 --- /dev/null +++ b/ExifTool/t/Canon.t @@ -0,0 +1,41 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Canon.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Canon; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Canon'; +my $testnum = 1; + +# test 2: Extract information from Canon1DmkIII.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Canon1DmkIII.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write CanonCustom2 information +{ + ++$testnum; + my @writeInfo = ( + [ISOSpeedRange => 'Enable; 1600; 200'], + [TimerLength => 'Enable; 6 sec: 5; 16 sec: 20; After release: 6'], + ); + my @check = qw(ISOSpeedRange TimerLength OriginalDecisionData Warning); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, + 't/images/Canon1DmkIII.jpg', \@check); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/CanonRaw.t b/ExifTool/t/CanonRaw.t new file mode 100644 index 0000000..fcbb729 --- /dev/null +++ b/ExifTool/t/CanonRaw.t @@ -0,0 +1,163 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/CanonRaw.t". + +BEGIN { + $| = 1; print "1..9\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::CanonRaw; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'CanonRaw'; +my $testnum = 1; + +# test 2: Extract information from CRW +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/CanonRaw.crw'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Extract JpgFromRaw from CRW +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(PrintConv => 0, IgnoreMinorErrors => 1); + my $info = $exifTool->ImageInfo('t/images/CanonRaw.crw','JpgFromRaw'); + notOK() unless ${$info->{JpgFromRaw}} eq '<Dummy JpgFromRaw image data>'; + print "ok $testnum\n"; +} + +# test 4: Write a whole pile of tags to a CRW +{ + ++$testnum; + if (eval { require Time::Local }) { + my $exifTool = Image::ExifTool->new; + # set IgnoreMinorErrors option to allow invalid JpgFromRaw to be written + $exifTool->Options(IgnoreMinorErrors => 1); + $exifTool->SetNewValuesFromFile('t/images/Canon.jpg'); + $exifTool->SetNewValue(SerialNumber => 1234); + $exifTool->SetNewValue(OwnerName => 'Phil Harvey'); + $exifTool->SetNewValue(JpgFromRaw => 'not a real image'); + $exifTool->SetNewValue(ROMOperationMode => 'CDN'); + $exifTool->SetNewValue(FocalPlaneXSize => '35 mm'); + $exifTool->SetNewValue(FocalPlaneYSize => '24 mm'); + my $testfile = "t/${testname}_${testnum}_failed.crw"; + unlink $testfile; + $exifTool->WriteInfo('t/images/CanonRaw.crw', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + } else { + print "ok $testnum # skip Requires Time::Local\n"; + } +} + +# test 5: Test verbose output +{ + ++$testnum; + notOK() unless testVerbose($testname, $testnum, 't/images/CanonRaw.crw', 1); + print "ok $testnum\n"; +} + +# test 6: Write to CR2 file +{ + ++$testnum; + if (eval { require Time::Local }) { + my $exifTool = Image::ExifTool->new; + # set IgnoreMinorErrors option to allow invalid JpgFromRaw to be written + $exifTool->SetNewValue(Keywords => 'CR2 test'); + $exifTool->SetNewValue(OwnerName => 'Phil Harvey'); + $exifTool->SetNewValue(FocalPlaneXSize => '35mm'); + my $testfile = "t/${testname}_${testnum}_failed.cr2"; + unlink $testfile; + $exifTool->WriteInfo('t/images/CanonRaw.cr2', $testfile); + my $info = $exifTool->ImageInfo($testfile); + my $success = check($exifTool, $info, $testname, $testnum); + # make sure file suffix was copied properly + while ($success) { + open(TESTFILE, $testfile) or last; + binmode(TESTFILE); + my $endStr = '<Dummy preview image data>Non-TIFF data test'; + my $len = length $endStr; + seek(TESTFILE, -$len, 2) or last; + my $buff; + read(TESTFILE, $buff, $len) == $len or last; + close(TESTFILE); + if ($buff eq $endStr) { + unlink $testfile; + $success = 2; + } else { + warn "\n Test $testnum failed to copy file suffix:\n"; + warn " Test gave: '$buff'\n"; + warn " Should be: '$endStr'\n"; + $success = 0; + } + last; + } + warn "\n Test $testnum: Error reading file suffix\n" if $success == 1; + notOK() unless $success == 2; + print "ok $testnum\n"; + } else { + print "ok $testnum # skip Requires Time::Local\n"; + } +} + +# test 7: Test copying all information from a CR2 image to a JPEG +{ + ++$testnum; + if (eval { require Time::Local }) { + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/CanonRaw.cr2'); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->WriteInfo('t/images/Writer.jpg', $testfile); + $exifTool->Options(Unknown => 1); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + } else { + print "ok $testnum # skip Requires Time::Local\n"; + } +} + +# test 8: Extract information from a CR3 image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/CanonRaw.cr3'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 9: Write to CR3 file +{ + ++$testnum; + if (eval { require Time::Local }) { + my @writeInfo = ( + [Subject => 'CR3 test'], + [ExposureCompensation => -1.3], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/CanonRaw.cr3'); + print "ok $testnum\n"; + } else { + print "ok $testnum # skip Requires Time::Local\n"; + } +} + +done(); # end diff --git a/ExifTool/t/CanonRaw_2.out b/ExifTool/t/CanonRaw_2.out new file mode 100644 index 0000000..ff4d719 --- /dev/null +++ b/ExifTool/t/CanonRaw_2.out @@ -0,0 +1,169 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: CanonRaw.crw +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 6.8 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2012:01:22 13:31:59-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:31-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: CRW +[File, File, Other] FileTypeExtension - File Type Extension: crw +[File, File, Other] MIMEType - MIME Type: image/x-canon-crw +[MakerNotes, CanonRaw, Camera] 8197 - Raw Data: (Binary data 22 bytes) +[MakerNotes, CanonRaw, Preview] 8199 - Jpg From Raw: (Binary data 29 bytes) +[MakerNotes, CanonRaw, Image] 0 - File Format: CRW +[MakerNotes, CanonRaw, Image] 1 - Target Compression Ratio: 10 +[MakerNotes, CanonRaw, Image] 0 - Image Width: 3072 +[MakerNotes, CanonRaw, Image] 1 - Image Height: 2048 +[MakerNotes, CanonRaw, Image] 2 - Pixel Aspect Ratio: 1 +[MakerNotes, CanonRaw, Image] 3 - Rotation: 0 +[MakerNotes, CanonRaw, Image] 4 - Component Bit Depth: 8 +[MakerNotes, CanonRaw, Image] 5 - Color Bit Depth: 24 +[MakerNotes, CanonRaw, Image] 6 - Color BW: 257 +[MakerNotes, CanonRaw, Camera] 4106 - Target Image Type: Real-world Subject +[MakerNotes, CanonRaw, Camera] 6148 - Record ID: 0 +[MakerNotes, CanonRaw, Image] 6167 - File Number: 116-1602 +[MakerNotes, CanonRaw, Time] 0 - Date/Time Original: 2003:11:10 17:39:26 +[MakerNotes, CanonRaw, Time] 1 - Time Zone Code: 0 +[MakerNotes, CanonRaw, Time] 2 - Time Zone Info: 0 +[MakerNotes, CanonRaw, Camera] 2070 - Original File Name: CRW_1602.CRW +[MakerNotes, CanonRaw, Camera] 2071 - Thumbnail File Name: CRW_1602.THM +[MakerNotes, CanonRaw, Camera] 2053 - User Comment: +[MakerNotes, CanonRaw, Camera] 6164 - Measured EV: 4.625 +[MakerNotes, CanonRaw, Camera] 2053 - Canon File Description: EOS DIGITAL REBEL CMOS RAW +[MakerNotes, CanonRaw, Camera] 2069 - Canon Image Type: CRW:EOS DIGITAL REBEL CMOS RAW +[MakerNotes, CanonRaw, Camera] 2064 - Owner Name: Phil Harvey +[MakerNotes, CanonRaw, Camera] 0 - Make: Canon +[MakerNotes, CanonRaw, Camera] 6 - Camera Model Name: Canon EOS DIGITAL REBEL +[MakerNotes, CanonRaw, Camera] 6155 - Serial Number: 0560018150 +[MakerNotes, CanonRaw, Camera] 4124 - Base ISO: 100 +[MakerNotes, CanonRaw, Camera] 2061 - ROM Operation Mode: USA +[MakerNotes, CanonRaw, Camera] 2059 - Canon Firmware Version: Firmware Version 1.1.1 +[MakerNotes, Canon, Image] 1 - Focal Length: 24 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.22 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.49 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 100 +[MakerNotes, Canon, Image] 3 - Measured EV: 0.00 +[MakerNotes, Canon, Image] 4 - Target Aperture: 3.6 +[MakerNotes, Canon, Image] 5 - Target Exposure Time: 1/60 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: +2/3 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 19 - Focus Distance Upper: 1.42 m +[MakerNotes, Canon, Image] 20 - Focus Distance Lower: 1.19 m +[MakerNotes, Canon, Image] 21 - F Number: 3.6 +[MakerNotes, Canon, Image] 22 - Exposure Time: 1/64 +[MakerNotes, Canon, Image] 23 - Measured EV 2: -0.125 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 0 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Camera] 1 - Macro Mode: Unknown (0) +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: On +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Continuous +[MakerNotes, Canon, Camera] 7 - Focus Mode: AI Focus AF +[MakerNotes, Canon, Camera] 9 - Record Mode: CRW+THM +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: Unknown (-1) +[MakerNotes, Canon, Camera] 13 - Contrast: +1 +[MakerNotes, Canon, Camera] 14 - Saturation: +1 +[MakerNotes, Canon, Camera] 15 - Sharpness: +1 +[MakerNotes, Canon, Camera] 16 - Camera ISO: n/a +[MakerNotes, Canon, Camera] 17 - Metering Mode: Evaluative +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 19 - AF Point: Manual AF point selection +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Program AE +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 3.6 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 22 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: E-TTL, Built-in +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 3072 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 3072 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, CanonRaw, Camera] 6196 - Canon Model ID: EOS Digital Rebel / 300D / Kiss Digital +[MakerNotes, Canon, Image] 1 - Sensor Width: 3152 +[MakerNotes, Canon, Image] 2 - Sensor Height: 2068 +[MakerNotes, Canon, Image] 5 - Sensor Left Border: 72 +[MakerNotes, Canon, Image] 6 - Sensor Top Border: 16 +[MakerNotes, Canon, Image] 7 - Sensor Right Border: 3143 +[MakerNotes, Canon, Image] 8 - Sensor Bottom Border: 2063 +[MakerNotes, Canon, Image] 9 - Black Mask Left Border: 24 +[MakerNotes, Canon, Image] 10 - Black Mask Top Border: 224 +[MakerNotes, Canon, Image] 11 - Black Mask Right Border: 40 +[MakerNotes, Canon, Image] 12 - Black Mask Bottom Border: 1856 +[MakerNotes, CanonRaw, Camera] 0 - Decoder Table Number: 1 +[MakerNotes, CanonRaw, Camera] 2 - Compressed Data Offset: 514 +[MakerNotes, CanonRaw, Camera] 3 - Compressed Data Length: 4120111 +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3072 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2048 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3072 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2048 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 151 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 151 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 1014 608 0 0 0 -608 -1014 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: 0 0 -506 0 506 0 0 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: 3 +[MakerNotes, CanonRaw, Camera] 4270 - Color Temperature: 5200 +[MakerNotes, Canon, Camera] 1 - WB RGGB Levels Auto: 1740 832 831 931 +[MakerNotes, Canon, Camera] 5 - WB RGGB Levels Daylight: 1722 832 831 989 +[MakerNotes, Canon, Camera] 9 - WB RGGB Levels Shade: 2035 832 831 839 +[MakerNotes, Canon, Camera] 13 - WB RGGB Levels Cloudy: 1878 832 831 903 +[MakerNotes, Canon, Camera] 17 - WB RGGB Levels Tungsten: 1228 913 912 1668 +[MakerNotes, Canon, Camera] 21 - WB RGGB Levels Fluorescent: 1506 842 841 1381 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels Flash: 1964 832 831 877 +[MakerNotes, Canon, Camera] 29 - WB RGGB Levels Custom: 1722 832 831 989 +[MakerNotes, Canon, Camera] 33 - WB RGGB Levels Kelvin: 1722 832 831 988 +[MakerNotes, Canon, Camera] 37 - WB RGGB Black Levels: 125 124 125 124 +[MakerNotes, CanonRaw, Camera] 4276 - Color Space: sRGB +[MakerNotes, CanonRaw, Image] 1 - Raw Jpg Quality: Fine +[MakerNotes, CanonRaw, Image] 2 - Raw Jpg Size: Medium +[MakerNotes, CanonRaw, Image] 3 - Raw Jpg Width: 2048 +[MakerNotes, CanonRaw, Image] 4 - Raw Jpg Height: 1360 +[MakerNotes, CanonRaw, Camera] 6203 - Serial Number Format: Format 1 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 8.31 +[XMP, XMP-dc, Image] description - Description: CRW with embedded XMP +[Composite, Composite, Camera] Canon-ConditionalFEC - Flash Exposure Compensation: +2/3 +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Continuous Shooting +[Composite, Composite, Camera] Canon-FlashType - Flash Type: Built-In Flash +[Composite, Composite, Camera] Canon-ISO - ISO: 100 +[Composite, Composite, Camera] Canon-Lens - Lens: 18.0 - 55.0 mm +[Composite, Composite, Camera] Canon-RedEyeReduction - Red Eye Reduction: Off +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Program AE +[Composite, Composite, Camera] Canon-ShutterCurtainHack - Shutter Curtain Sync: 1st-curtain sync +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 1740 832 831 931 +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.6 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.119663 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3072x2048 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown 18-55mm +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 6.3 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.092604 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 18.0 - 55.0 mm (35 mm equivalent: 27.9 - 85.3 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.41 m (1.13 - 1.54 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 51.6 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 24.0 mm (35 mm equivalent: 37.2 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 8.34 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.7 diff --git a/ExifTool/t/CanonRaw_4.out b/ExifTool/t/CanonRaw_4.out new file mode 100644 index 0000000..b004292 --- /dev/null +++ b/ExifTool/t/CanonRaw_4.out @@ -0,0 +1,221 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: CanonRaw_4_failed.crw +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 13 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:31-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:31-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:31-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: CRW +[File, File, Other] FileTypeExtension - File Type Extension: crw +[File, File, Other] MIMEType - MIME Type: image/x-canon-crw +[MakerNotes, CanonRaw, Camera] 8197 - Raw Data: (Binary data 22 bytes) +[MakerNotes, CanonRaw, Preview] 8199 - Jpg From Raw: (Binary data 16 bytes) +[MakerNotes, CanonRaw, Image] 0 - File Format: CRW +[MakerNotes, CanonRaw, Image] 1 - Target Compression Ratio: 10 +[MakerNotes, CanonRaw, Image] 0 - Image Width: 3072 +[MakerNotes, CanonRaw, Image] 1 - Image Height: 2048 +[MakerNotes, CanonRaw, Image] 2 - Pixel Aspect Ratio: 1 +[MakerNotes, CanonRaw, Image] 3 - Rotation: 0 +[MakerNotes, CanonRaw, Image] 4 - Component Bit Depth: 8 +[MakerNotes, CanonRaw, Image] 5 - Color Bit Depth: 24 +[MakerNotes, CanonRaw, Image] 6 - Color BW: 257 +[MakerNotes, CanonRaw, Camera] 4106 - Target Image Type: Real-world Subject +[MakerNotes, CanonRaw, Camera] 6148 - Record ID: 0 +[MakerNotes, CanonRaw, Image] 6167 - File Number: 118-1861 +[MakerNotes, CanonRaw, Time] 0 - Date/Time Original: 2003:12:04 06:46:52 +[MakerNotes, CanonRaw, Time] 1 - Time Zone Code: 0 +[MakerNotes, CanonRaw, Time] 2 - Time Zone Info: 0 +[MakerNotes, CanonRaw, Camera] 2070 - Original File Name: CRW_1602.CRW +[MakerNotes, CanonRaw, Camera] 2071 - Thumbnail File Name: CRW_1602.THM +[MakerNotes, CanonRaw, Camera] 2053 - User Comment: +[MakerNotes, CanonRaw, Camera] 6164 - Measured EV: -1.25 +[MakerNotes, CanonRaw, Camera] 2053 - Canon File Description: EOS DIGITAL REBEL CMOS RAW +[MakerNotes, CanonRaw, Camera] 2069 - Canon Image Type: CRW:EOS DIGITAL REBEL CMOS RAW +[MakerNotes, CanonRaw, Camera] 2064 - Owner Name: Phil Harvey +[MakerNotes, CanonRaw, Camera] 0 - Make: Canon +[MakerNotes, CanonRaw, Camera] 6 - Camera Model Name: Canon EOS DIGITAL REBEL +[MakerNotes, CanonRaw, Camera] 6155 - Serial Number: 0000001234 +[MakerNotes, CanonRaw, Camera] 4124 - Base ISO: 100 +[MakerNotes, CanonRaw, Camera] 2061 - ROM Operation Mode: CDN +[MakerNotes, CanonRaw, Camera] 2059 - Canon Firmware Version: Firmware Version 1.1.1 +[MakerNotes, Canon, Image] 1 - Focal Length: 34 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 35.00 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 24.00 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 100 +[MakerNotes, Canon, Image] 3 - Measured EV: -1.25 +[MakerNotes, Canon, Image] 4 - Target Aperture: 14 +[MakerNotes, Canon, Image] 5 - Target Exposure Time: 1/60 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 19 - Focus Distance Upper: inf +[MakerNotes, Canon, Image] 20 - Focus Distance Lower: 5.46 m +[MakerNotes, Canon, Image] 21 - F Number: 14 +[MakerNotes, Canon, Image] 22 - Exposure Time: 4 +[MakerNotes, Canon, Image] 23 - Measured EV 2: -1.25 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 4 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Camera] 1 - Macro Mode: Unknown (0) +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Continuous +[MakerNotes, Canon, Camera] 7 - Focus Mode: Manual Focus (3) +[MakerNotes, Canon, Camera] 9 - Record Mode: CRW+THM +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: Unknown (-1) +[MakerNotes, Canon, Camera] 13 - Contrast: +1 +[MakerNotes, Canon, Camera] 14 - Saturation: +1 +[MakerNotes, Canon, Camera] 15 - Sharpness: +1 +[MakerNotes, Canon, Camera] 16 - Camera ISO: n/a +[MakerNotes, Canon, Camera] 17 - Metering Mode: Center-weighted average +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 19 - AF Point: Manual AF point selection +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Manual +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 4 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 27 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 3072 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 3072 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, CanonRaw, Camera] 6196 - Canon Model ID: EOS Digital Rebel / 300D / Kiss Digital +[MakerNotes, Canon, Image] 1 - Sensor Width: 3152 +[MakerNotes, Canon, Image] 2 - Sensor Height: 2068 +[MakerNotes, Canon, Image] 5 - Sensor Left Border: 72 +[MakerNotes, Canon, Image] 6 - Sensor Top Border: 16 +[MakerNotes, Canon, Image] 7 - Sensor Right Border: 3143 +[MakerNotes, Canon, Image] 8 - Sensor Bottom Border: 2063 +[MakerNotes, Canon, Image] 9 - Black Mask Left Border: 24 +[MakerNotes, Canon, Image] 10 - Black Mask Top Border: 224 +[MakerNotes, Canon, Image] 11 - Black Mask Right Border: 40 +[MakerNotes, Canon, Image] 12 - Black Mask Bottom Border: 1856 +[MakerNotes, CanonRaw, Camera] 0 - Decoder Table Number: 1 +[MakerNotes, CanonRaw, Camera] 2 - Compressed Data Offset: 514 +[MakerNotes, CanonRaw, Camera] 3 - Compressed Data Length: 4120111 +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3072 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2048 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3072 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2048 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 151 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 151 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 1014 608 0 0 0 -608 -1014 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: 0 0 -506 0 506 0 0 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: 3 +[MakerNotes, CanonRaw, Camera] 4270 - Color Temperature: 5200 +[MakerNotes, Canon, Camera] 1 - WB RGGB Levels Auto: 1719 832 831 990 +[MakerNotes, Canon, Camera] 5 - WB RGGB Levels Daylight: 1722 832 831 989 +[MakerNotes, Canon, Camera] 9 - WB RGGB Levels Shade: 2035 832 831 839 +[MakerNotes, Canon, Camera] 13 - WB RGGB Levels Cloudy: 1878 832 831 903 +[MakerNotes, Canon, Camera] 17 - WB RGGB Levels Tungsten: 1228 913 912 1668 +[MakerNotes, Canon, Camera] 21 - WB RGGB Levels Fluorescent: 1506 842 841 1381 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels Flash: 1933 832 831 895 +[MakerNotes, Canon, Camera] 29 - WB RGGB Levels Custom: 1722 832 831 989 +[MakerNotes, Canon, Camera] 33 - WB RGGB Levels Kelvin: 1722 832 831 988 +[MakerNotes, Canon, Camera] 37 - WB RGGB Black Levels: 124 123 124 123 +[MakerNotes, CanonRaw, Camera] 4276 - Color Space: sRGB +[MakerNotes, CanonRaw, Image] 1 - Raw Jpg Quality: Fine +[MakerNotes, CanonRaw, Image] 2 - Raw Jpg Size: Medium +[MakerNotes, CanonRaw, Image] 3 - Raw Jpg Width: 2048 +[MakerNotes, CanonRaw, Image] 4 - Raw Jpg Height: 1360 +[MakerNotes, CanonRaw, Camera] 6203 - Serial Number Format: Format 1 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-aux, Camera] Lens - Lens: 18.0 - 55.0 mm +[XMP, XMP-crs, Image] Temperature - Color Temperature: 5200 +[XMP, XMP-dc, Image] description - Description: CRW with embedded XMP +[XMP, XMP-exif, Image] ApertureValue - Aperture Value: 14.0 +[XMP, XMP-exif, Image] ColorSpace - Color Space: sRGB +[XMP, XMP-exif, Image] ComponentsConfiguration - Components Configuration: Y, Cb, Cr, - +[XMP, XMP-exif, Image] CompressedBitsPerPixel - Compressed Bits Per Pixel: 9 +[XMP, XMP-exif, Camera] Contrast - Contrast: High +[XMP, XMP-exif, Image] CustomRendered - Custom Rendered: Normal +[XMP, XMP-exif, Time] DateTimeOriginal - Date/Time Original: 2003:12:04 06:46:52 +[XMP, XMP-exif, Image] ExifVersion - Exif Version: 0221 +[XMP, XMP-exif, Image] ExposureBiasValue - Exposure Compensation: 0 +[XMP, XMP-exif, Camera] ExposureMode - Exposure Mode: Manual +[XMP, XMP-exif, Image] ExposureTime - Exposure Time: 4 +[XMP, XMP-exif, Image] FNumber - F Number: 14.0 +[XMP, XMP-exif, Image] FileSource - File Source: Digital Camera +[XMP, XMP-exif, Camera] FlashFired - Flash Fired: False +[XMP, XMP-exif, Camera] FlashFunction - Flash Function: False +[XMP, XMP-exif, Camera] FlashMode - Flash Mode: Unknown +[XMP, XMP-exif, Camera] FlashRedEyeMode - Flash Red Eye Mode: False +[XMP, XMP-exif, Camera] FlashReturn - Flash Return: No return detection +[XMP, XMP-exif, Image] FlashpixVersion - Flashpix Version: 0100 +[XMP, XMP-exif, Camera] FocalLength - Focal Length: 34.0 mm +[XMP, XMP-exif, Camera] FocalPlaneResolutionUnit - Focal Plane Resolution Unit: inches +[XMP, XMP-exif, Camera] FocalPlaneXResolution - Focal Plane X Resolution: 3443.94615384615 +[XMP, XMP-exif, Camera] FocalPlaneYResolution - Focal Plane Y Resolution: 3442.01680672269 +[XMP, XMP-exif, Image] ISOSpeedRatings - ISO: 100 +[XMP, XMP-exif, Camera] MaxApertureValue - Max Aperture Value: 4.5 +[XMP, XMP-exif, Camera] MeteringMode - Metering Mode: Center-weighted average +[XMP, XMP-exif, Image] PixelXDimension - Exif Image Width: 160 +[XMP, XMP-exif, Image] PixelYDimension - Exif Image Height: 120 +[XMP, XMP-exif, Camera] Saturation - Saturation: High +[XMP, XMP-exif, Camera] SceneCaptureType - Scene Capture Type: Standard +[XMP, XMP-exif, Camera] SensingMethod - Sensing Method: One-chip color area +[XMP, XMP-exif, Camera] Sharpness - Sharpness: Hard +[XMP, XMP-exif, Image] ShutterSpeedValue - Shutter Speed Value: 1 +[XMP, XMP-exif, Image] UserComment - User Comment: +[XMP, XMP-exif, Camera] WhiteBalance - White Balance: Auto +[XMP, XMP-exifEX, Camera] BodySerialNumber - Serial Number: 1234 +[XMP, XMP-exifEX, Image] CameraOwnerName - Owner Name: Phil Harvey +[XMP, XMP-exifEX, Image] InteroperabilityIndex - Interoperability Index: THM - DCF thumbnail file +[XMP, XMP-pmi, Image] sequenceNumber - Sequence Number: 0 +[XMP, XMP-tiff, Image] BitsPerSample - Bits Per Sample: 8 +[XMP, XMP-tiff, Image] ImageLength - Image Height: 8 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 8 +[XMP, XMP-tiff, Camera] Make - Make: Canon +[XMP, XMP-tiff, Camera] Model - Camera Model Name: Canon EOS DIGITAL REBEL +[XMP, XMP-tiff, Image] Orientation - Orientation: Horizontal (normal) +[XMP, XMP-tiff, Image] ResolutionUnit - Resolution Unit: inches +[XMP, XMP-tiff, Image] XResolution - X Resolution: 180 +[XMP, XMP-tiff, Image] YCbCrPositioning - Y Cb Cr Positioning: Centered +[XMP, XMP-tiff, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[XMP, XMP-tiff, Image] YResolution - Y Resolution: 180 +[XMP, XMP-xmp, Time] CreateDate - Create Date: 2003:12:04 06:46:52 +[XMP, XMP-xmp, Time] ModifyDate - Modify Date: 2003:12:04 06:46:52 +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Continuous Shooting +[Composite, Composite, Camera] Canon-ISO - ISO: 100 +[Composite, Composite, Camera] Canon-Lens - Lens: 18.0 - 55.0 mm +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Bulb +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 1719 832 831 990 +[Composite, Composite, Image] Exif-Aperture - Aperture: 14.3 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 4 +[Composite, Composite, Camera] XMP-Flash - Flash: No Flash +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.190619 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3072x2048 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown 18-55mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 5.7 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 6.3 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.067348 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.0 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 18.0 - 55.0 mm (35 mm equivalent: 18.4 - 56.1 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.029 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: inf (2.73 m - inf) +[Composite, Composite, Image] Exif-FOV - Field Of View: 54.9 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 34.0 mm (35 mm equivalent: 34.7 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 2.75 m diff --git a/ExifTool/t/CanonRaw_5.out b/ExifTool/t/CanonRaw_5.out new file mode 100644 index 0000000..49a3673 --- /dev/null +++ b/ExifTool/t/CanonRaw_5.out @@ -0,0 +1,209 @@ + ExifToolVersion = 10.12 + FileName = CanonRaw.crw + Directory = t/images + FileSize = 6816 + FileModifyDate = 1327257119 + FileAccessDate = 1457094142 + FileInodeChangeDate = 1455728654 + FilePermissions = 33188 + FileType = CRW + FileTypeExtension = CRW + MIMEType = image/x-canon-crw + + [CIFF directory with 3 entries] + | 0) RawData = <Dummy raw image data> + | 1) JpgFromRaw = <Dummy JpgFromRaw image data> + | 2) ImageProps (SubDirectory) --> + | + [CIFF directory with 13 entries] + | | 0) ImageFormat (SubDirectory) --> + | | + [BinaryData directory, 8 bytes] + | | | FileFormat = 131073 + | | | TargetCompressionRatio = 10 + | | 1) ImageInfo (SubDirectory) --> + | | + [BinaryData directory, 28 bytes] + | | | ImageWidth = 3072 + | | | ImageHeight = 2048 + | | | PixelAspectRatio = 1 + | | | Rotation = 0 + | | | ComponentBitDepth = 8 + | | | ColorBitDepth = 24 + | | | ColorBW = 257 + | | 2) TargetImageType = 0 + | | 3) RecordID = 0 + | | 4) FileNumber = 1161602 + | | 5) TimeStamp (SubDirectory) --> + | | + [BinaryData directory, 12 bytes] + | | | DateTimeOriginal = 1068485966 + | | | TimeZoneCode = 0 + | | | TimeZoneInfo = 0 + | | 6) OriginalFileName = CRW_1602.CRW + | | 7) ThumbnailFileName = CRW_1602.THM + | | 8) UserComment = + | | 9) MeasuredInfo (SubDirectory) --> + | | + [CIFF directory with 1 entries] + | | | 0) MeasuredEV = -0.375 + | | 10) ImageDescription (SubDirectory) --> + | | + [CIFF directory with 2 entries] + | | | 0) CanonFileDescription = EOS DIGITAL REBEL CMOS RAW + | | | 1) CanonImageType = CRW:EOS DIGITAL REBEL CMOS RAW + | | 11) CameraObject (SubDirectory) --> + | | + [CIFF directory with 3 entries] + | | | 0) OwnerName = Phil Harvey + | | | 1) CanonRawMakeModel (SubDirectory) --> + | | | + [BinaryData directory, 32 bytes] + | | | | Make = Canon + | | | | Model = Canon EOS DIGITAL REBEL + | | | 2) CameraSpecification (SubDirectory) --> + | | | + [CIFF directory with 4 entries] + | | | | 0) SerialNumber = 560018150 + | | | | 1) BaseISO = 100 + | | | | 2) ROMOperationMode = USA + | | | | 3) CanonFirmwareVersion = Firmware Version 1.1.1 + | | 12) ExifInformation (SubDirectory) --> + | | + [CIFF directory with 23 entries] + | | | 0) CanonFlashInfo = 0 100 0 0 + | | | 1) CanonFocalLength (SubDirectory) --> + | | | + [BinaryData directory, 8 bytes] + | | | | FocalType = 0 + | | | | FocalLength = 24 + | | | | FocalPlaneXSize = 914 + | | | | FocalPlaneYSize = 610 + | | | 2) CanonShotInfo (SubDirectory) --> + | | | + [BinaryData directory, 66 bytes] + | | | | AutoISO = 0 + | | | | BaseISO = 160 + | | | | MeasuredEV = -160 + | | | | TargetAperture = 116 + | | | | TargetExposureTime = 189 + | | | | ExposureCompensation = 0 + | | | | WhiteBalance = 0 + | | | | SlowShutter = 3 + | | | | SequenceNumber = 0 + | | | | OpticalZoomCode = 8 + | | | | CameraTemperature = 0 + | | | | FlashGuideNumber = -1 + | | | | AFPointsInFocus = 0 + | | | | FlashExposureComp = 20 + | | | | AutoExposureBracketing = 0 + | | | | AEBBracketValue = 0 + | | | | ControlMode = 1 + | | | | FocusDistanceUpper = 142 + | | | | FocusDistanceLower = 119 + | | | | FNumber = 116 + | | | | ExposureTime = 192 + | | | | MeasuredEV2 = 47 + | | | | BulbDuration = 0 + | | | | CameraType = 252 + | | | | AutoRotate = 0 + | | | | NDFilter = -1 + | | | | SelfTimer2 = 0 + | | | 3) CanonFileInfo (SubDirectory) --> + | | | + [BinaryData directory, 18 bytes] + | | | | BracketMode = 0 + | | | | BracketValue = 0 + | | | | BracketShotNumber = 0 + | | | | RawJpgQuality = -1 + | | | | RawJpgSize = -1 + | | | | LongExposureNoiseReduction2 = -1 + | | | 4) CanonCameraSettings (SubDirectory) --> + | | | + [BinaryData directory, 92 bytes] + | | | | MacroMode = 0 + | | | | SelfTimer = 0 + | | | | Quality = 4 + | | | | CanonFlashMode = 2 + | | | | ContinuousDrive = 1 + | | | | FocusMode = 2 + | | | | RecordMode = 2 + | | | | CanonImageSize = 0 + | | | | EasyMode = 1 + | | | | DigitalZoom = -1 + | | | | Contrast = 1 + | | | | Saturation = 1 + | | | | Sharpness = 1 + | | | | CameraISO = 0 + | | | | MeteringMode = 3 + | | | | FocusRange = 2 + | | | | AFPoint = 8197 + | | | | CanonExposureMode = 1 + | | | | LensType = 65535 + | | | | MaxFocalLength = 55 + | | | | MinFocalLength = 18 + | | | | FocalUnits = 1 + | | | | MaxAperture = 116 + | | | | MinAperture = 285 + | | | | FlashActivity = 0 + | | | | FlashBits = 8200 + | | | | FocusContinuous = -1 + | | | | AESetting = -1 + | | | | ImageStabilization = -1 + | | | | DisplayAperture = 0 + | | | | ZoomSourceWidth = 3072 + | | | | ZoomTargetWidth = 3072 + | | | | SpotMeteringMode = -1 + | | | | PhotoEffect = -1 + | | | | ManualFlashOutput = 0 + | | | | ColorTone = 0 + | | | 5) CanonRaw_0x0036 = ...........................1..:..........1..:.........1..[snip] + | | | 6) CanonModelID = 2147484016 + | | | 7) SensorInfo (SubDirectory) --> + | | | + [BinaryData directory, 34 bytes] + | | | | SensorWidth = 3152 + | | | | SensorHeight = 2068 + | | | | SensorLeftBorder = 72 + | | | | SensorTopBorder = 16 + | | | | SensorRightBorder = 3143 + | | | | SensorBottomBorder = 2063 + | | | | BlackMaskLeftBorder = 24 + | | | | BlackMaskTopBorder = 224 + | | | | BlackMaskRightBorder = 40 + | | | | BlackMaskBottomBorder = 1856 + | | | 8) DecoderTable (SubDirectory) --> + | | | + [BinaryData directory, 16 bytes] + | | | | DecoderTableNumber = 1 + | | | | CompressedDataOffset = 514 + | | | | CompressedDataLength = 4120111 + | | | 9) CanonAFInfo (SubDirectory) --> + | | | + [SerialData directory, 48 bytes] + | | | | 0) NumAFPoints = 7 + | | | | 1) ValidAFPoints = 7 + | | | | 2) CanonImageWidth = 3072 + | | | | 3) CanonImageHeight = 2048 + | | | | 4) AFImageWidth = 3072 + | | | | 5) AFImageHeight = 2048 + | | | | 6) AFAreaWidth = 151 + | | | | 7) AFAreaHeight = 151 + | | | | 8) AFAreaXPositions = 1014 608 0 0 0 -608 -1014 + | | | | 9) AFAreaYPositions = 0 0 -506 0 506 0 0 + | | | | 10) AFPointsInFocus = 8 + | | | 10) CanonRaw_0x10c0 = 26 331 372 372 177 240 428 429 277 186 510 511 442 + | | | 11) CanonRaw_0x10c1 = 26 299 375 375 170 202 394 395 240 153 453 454 375 + | | | 12) CanonRaw_0x10c2 = t......a.................c...............c.X.............[snip] + | | | 13) CanonRaw_0x10aa = 10 554 1022 1026 754 + | | | 14) CanonRaw_0x10a8 = 20 5528 5190 7000 5987 3214 3897 6457 5190 5200 + | | | 15) CanonRaw_0x10ad = 62 20127 8480 7000 6000 5600 5200 4724 4200 3266 1446 648[snip] + | | | 16) ColorTemperature = 5200 + | | | 17) CanonRaw_0x10af = 1024 + | | | 18) ColorBalance (SubDirectory) --> + | | | + [BinaryData directory, 82 bytes] + | | | | WB_RGGBLevelsAuto = 1740 832 831 931 + | | | | WB_RGGBLevelsDaylight = 1722 832 831 989 + | | | | WB_RGGBLevelsShade = 2035 832 831 839 + | | | | WB_RGGBLevelsCloudy = 1878 832 831 903 + | | | | WB_RGGBLevelsTungsten = 1228 913 912 1668 + | | | | WB_RGGBLevelsFluorescent = 1506 842 841 1381 + | | | | WB_RGGBLevelsFlash = 1964 832 831 877 + | | | | WB_RGGBLevelsCustom = 1722 832 831 989 + | | | | WB_RGGBLevelsKelvin = 1722 832 831 988 + | | | | WB_RGGBBlackLevels = 125 124 125 124 + | | | 19) ColorSpace = 1 + | | | 20) RawJpgInfo (SubDirectory) --> + | | | + [BinaryData directory, 10 bytes] + | | | | RawJpgQuality = 3 + | | | | RawJpgSize = 1 + | | | | RawJpgWidth = 2048 + | | | | RawJpgHeight = 1360 + | | | 21) CanonRaw_0x1039 = 0 159 7 112 + | | | 22) SerialNumberFormat = 2415919104 +CanonVRD trailer (560 bytes at offset 0x1870): + + [XMP directory, 460 bytes] + | XMPToolkit = Image::ExifTool 8.31 + | Description = CRW with embedded XMP diff --git a/ExifTool/t/CanonRaw_6.out b/ExifTool/t/CanonRaw_6.out new file mode 100644 index 0000000..ef39a6b --- /dev/null +++ b/ExifTool/t/CanonRaw_6.out @@ -0,0 +1,234 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: CanonRaw_6_failed.cr2 +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 8.8 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:31-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:31-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:31-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: CR2 +[File, File, Other] FileTypeExtension - File Type Extension: cr2 +[File, File, Other] MIMEType - MIME Type: image/x-canon-cr2 +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 74386a2844191f61317ffa999148834a +[EXIF, IFD0, Image] 256 - Image Width: 1536 +[EXIF, IFD0, Image] 257 - Image Height: 1024 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS 350D DIGITAL +[EXIF, IFD0, Image] 273 - Preview Image Start: 8738 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 279 - Preview Image Length: 26 +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2005:08:03 18:59:18 +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/15 +[EXIF, ExifIFD, Image] 33437 - F Number: 8.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 400 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:08:03 18:59:18 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:08:03 18:59:18 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: -, -, -, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/15 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 8.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 55.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3456 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2304 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3954.23341 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3958.762887 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Image] 42032 - Owner Name: Phil Harvey +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 8678 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD2, Image] 256 - Image Width: 384 +[EXIF, IFD2, Image] 257 - Image Height: 256 +[EXIF, IFD2, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD2, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD2, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD2, Image] 273 - Strip Offsets: 8722 +[EXIF, IFD2, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD2, Image] 278 - Rows Per Strip: 256 +[EXIF, IFD2, Image] 279 - Strip Byte Counts: 16 +[EXIF, IFD2, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD3, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD3, Image] 273 - Strip Offsets: 8706 +[EXIF, IFD3, Image] 279 - Strip Byte Counts: 16 +[EXIF, IFD3, Image] 50656 - CR2 CFA Pattern: [Red,Green][Green,Blue] +[EXIF, IFD3, Image] 50752 - Raw Image Segmentation: 1 1758 1758 +[EXIF, IFD0, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[IPTC, IPTC, Other] 25 - Keywords: CR2 test +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[MakerNotes, Canon, Camera] 1 - Macro Mode: Normal +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Single +[MakerNotes, Canon, Camera] 7 - Focus Mode: One-shot AF +[MakerNotes, Canon, Camera] 9 - Record Mode: CR2 +[MakerNotes, Canon, Camera] 10 - Canon Image Size: n/a +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: None +[MakerNotes, Canon, Camera] 13 - Contrast: Normal +[MakerNotes, Canon, Camera] 14 - Saturation: Normal +[MakerNotes, Canon, Camera] 15 - Sharpness: 0 +[MakerNotes, Canon, Camera] 17 - Metering Mode: Center-weighted average +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Aperture-priority AE +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 4 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 27 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 32 - Focus Continuous: Single +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 0 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 0 +[MakerNotes, Canon, Camera] 40 - Photo Effect: Off +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Image] 0 - Focal Type: Zoom +[MakerNotes, Canon, Image] 1 - Focal Length: 55 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 35.00 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.37 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 400 +[MakerNotes, Canon, Image] 3 - Measured EV: 8.00 +[MakerNotes, Canon, Image] 4 - Target Aperture: 8 +[MakerNotes, Canon, Image] 5 - Target Exposure Time: 1/15 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 21 - F Number: 8 +[MakerNotes, Canon, Image] 22 - Exposure Time: 1/16 +[MakerNotes, Canon, Image] 23 - Measured EV 2: 8.125 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 0 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: Canon EOS 350D DIGITAL +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware 1.0.2 +[MakerNotes, Canon, Camera] 9 - Owner Name: Phil Harvey +[MakerNotes, Canon, Camera] 12 - Serial Number: 0123456789 +[MakerNotes, CanonCustom, Camera] 0 - Set Button Cross Keys Func: Normal +[MakerNotes, CanonCustom, Camera] 1 - Long Exposure Noise Reduction: Off +[MakerNotes, CanonCustom, Camera] 2 - Flash Sync Speed Av: Auto +[MakerNotes, CanonCustom, Camera] 3 - Shutter-AE Lock: AF/AE lock +[MakerNotes, CanonCustom, Camera] 4 - AF Assist Beam: Emits +[MakerNotes, CanonCustom, Camera] 5 - Exposure Level Increments: 1/3 Stop +[MakerNotes, CanonCustom, Camera] 6 - Mirror Lockup: Disable +[MakerNotes, CanonCustom, Camera] 7 - E-TTL II: Evaluative +[MakerNotes, CanonCustom, Camera] 8 - Shutter Curtain Sync: 1st-curtain sync +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS Digital Rebel XT / 350D / Kiss Digital N +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3456 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2304 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3456 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2304 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 189 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 188 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 0 -1237 -742 0 742 1237 0 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: -617 0 0 0 0 0 617 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: 3 +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 2 +[MakerNotes, Canon, Camera] 131 - Original Decision Data Offset: 0 +[MakerNotes, Canon, Image] 1 - File Number: 100-0024 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 8 - Long Exposure Noise Reduction 2: Off +[MakerNotes, Canon, Image] 9 - WB Bracket Mode: Off +[MakerNotes, Canon, Image] 12 - WB Bracket Value AB: 0 +[MakerNotes, Canon, Image] 13 - WB Bracket Value GM: 0 +[MakerNotes, Canon, Image] 14 - Filter Effect: None +[MakerNotes, Canon, Image] 15 - Toning Effect: None +[MakerNotes, Canon, Image] 1 - Tone Curve: Standard +[MakerNotes, Canon, Image] 3 - Sharpness Frequency: n/a +[MakerNotes, Canon, Image] 4 - Sensor Red Level: 0 +[MakerNotes, Canon, Image] 5 - Sensor Blue Level: 0 +[MakerNotes, Canon, Image] 6 - White Balance Red: 0 +[MakerNotes, Canon, Image] 7 - White Balance Blue: 0 +[MakerNotes, Canon, Image] 9 - Color Temperature: 5200 +[MakerNotes, Canon, Image] 10 - Picture Style: None +[MakerNotes, Canon, Image] 11 - Digital Gain: 0 +[MakerNotes, Canon, Image] 12 - WB Shift AB: 0 +[MakerNotes, Canon, Image] 13 - WB Shift GM: 0 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 580 1024 1024 500 +[MakerNotes, Canon, Camera] 208 - VRD Offset: 0 +[MakerNotes, Canon, Image] 1 - Sensor Width: 3516 +[MakerNotes, Canon, Image] 2 - Sensor Height: 2328 +[MakerNotes, Canon, Image] 5 - Sensor Left Border: 52 +[MakerNotes, Canon, Image] 6 - Sensor Top Border: 19 +[MakerNotes, Canon, Image] 7 - Sensor Right Border: 3507 +[MakerNotes, Canon, Image] 8 - Sensor Bottom Border: 2322 +[MakerNotes, Canon, Image] 9 - Black Mask Left Border: 0 +[MakerNotes, Canon, Image] 10 - Black Mask Top Border: 0 +[MakerNotes, Canon, Image] 11 - Black Mask Right Border: 0 +[MakerNotes, Canon, Image] 12 - Black Mask Bottom Border: 0 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels As Shot: 2002 1185 1187 2364 +[MakerNotes, Canon, Camera] 29 - Color Temp As Shot: 3064 +[MakerNotes, Canon, Camera] 30 - WB RGGB Levels Auto: 1720 1022 1024 2031 +[MakerNotes, Canon, Camera] 34 - Color Temp Auto: 3064 +[MakerNotes, Canon, Camera] 35 - WB RGGB Levels Daylight: 2312 1022 1024 1501 +[MakerNotes, Canon, Camera] 39 - Color Temp Daylight: 5200 +[MakerNotes, Canon, Camera] 40 - WB RGGB Levels Shade: 2714 1022 1024 1246 +[MakerNotes, Canon, Camera] 44 - Color Temp Shade: 7000 +[MakerNotes, Canon, Camera] 45 - WB RGGB Levels Cloudy: 2512 1022 1024 1355 +[MakerNotes, Canon, Camera] 49 - Color Temp Cloudy: 6000 +[MakerNotes, Canon, Camera] 50 - WB RGGB Levels Tungsten: 1523 1022 1024 2397 +[MakerNotes, Canon, Camera] 54 - Color Temp Tungsten: 3201 +[MakerNotes, Canon, Camera] 55 - WB RGGB Levels Fluorescent: 1944 1022 1024 2015 +[MakerNotes, Canon, Camera] 59 - Color Temp Fluorescent: 3905 +[MakerNotes, Canon, Camera] 60 - WB RGGB Levels Flash: 2606 1022 1024 1308 +[MakerNotes, Canon, Camera] 64 - Color Temp Flash: 6440 +[MakerNotes, Canon, Camera] 65 - WB RGGB Levels Custom 1: 2312 1022 1024 1501 +[MakerNotes, Canon, Camera] 69 - Color Temp Custom 1: 5200 +[MakerNotes, Canon, Camera] 70 - WB RGGB Levels Custom 2: 2312 1022 1024 1501 +[MakerNotes, Canon, Camera] 74 - Color Temp Custom 2: 5200 +[MakerNotes, Canon, Camera] 2 - Color Tone: Normal +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Single-frame Shooting +[Composite, Composite, Camera] Canon-ISO - ISO: 400 +[Composite, Composite, Camera] Canon-Lens - Lens: 18.0 - 55.0 mm +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Aperture-priority AE +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 2002 1185 1187 2364 +[Composite, Composite, Image] Exif-Aperture - Aperture: 8.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/15 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.993255 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3456x2304 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown 18-55mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 7.9 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 8.0 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.688027 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 18.0 - 55.0 mm (35 mm equivalent: 29.2 - 89.2 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 22.8 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 55.0 mm (35 mm equivalent: 89.2 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 20.42 m diff --git a/ExifTool/t/CanonRaw_7.out b/ExifTool/t/CanonRaw_7.out new file mode 100644 index 0000000..7a8b29b --- /dev/null +++ b/ExifTool/t/CanonRaw_7.out @@ -0,0 +1,251 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: CanonRaw_7_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 7.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:32-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:32-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:32-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS 350D DIGITAL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2005:08:03 18:59:18 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/15 +[EXIF, ExifIFD, Image] 33437 - F Number: 8.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 400 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:08:03 18:59:18 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:08:03 18:59:18 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/15 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 8.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Center-weighted average +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 55.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3456 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2304 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3954.23341 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3958.762887 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Image] 42032 - Owner Name: unknown +[EXIF, ExifIFD, Image] 42033 - Serial Number: 0123456789 +[MakerNotes, Canon, Camera] 1 - Macro Mode: Normal +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Single +[MakerNotes, Canon, Camera] 7 - Focus Mode: One-shot AF +[MakerNotes, Canon, Camera] 9 - Record Mode: CR2 +[MakerNotes, Canon, Camera] 10 - Canon Image Size: n/a +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: None +[MakerNotes, Canon, Camera] 13 - Contrast: Normal +[MakerNotes, Canon, Camera] 14 - Saturation: Normal +[MakerNotes, Canon, Camera] 15 - Sharpness: 0 +[MakerNotes, Canon, Camera] 17 - Metering Mode: Center-weighted average +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Aperture-priority AE +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 4 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 27 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 32 - Focus Continuous: Single +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 0 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 0 +[MakerNotes, Canon, Camera] 40 - Photo Effect: Off +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Image] 0 - Focal Type: Zoom +[MakerNotes, Canon, Image] 1 - Focal Length: 55 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.04 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.37 mm +[MakerNotes, Canon, Camera] 3 - Canon Flash Info: 100 0 0 0 +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 400 +[MakerNotes, Canon, Image] 3 - Measured EV: 8.00 +[MakerNotes, Canon, Image] 4 - Target Aperture: 8 +[MakerNotes, Canon, Image] 5 - Target Exposure Time: 1/15 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 21 - F Number: 8 +[MakerNotes, Canon, Image] 22 - Exposure Time: 1/16 +[MakerNotes, Canon, Image] 23 - Measured EV 2: 8.125 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 0 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: Canon EOS 350D DIGITAL +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware 1.0.2 +[MakerNotes, Canon, Camera] 9 - Owner Name: unknown +[MakerNotes, Canon, Camera] 12 - Serial Number: 0123456789 +[MakerNotes, CanonCustom, Camera] 0 - Set Button Cross Keys Func: Normal +[MakerNotes, CanonCustom, Camera] 1 - Long Exposure Noise Reduction: Off +[MakerNotes, CanonCustom, Camera] 2 - Flash Sync Speed Av: Auto +[MakerNotes, CanonCustom, Camera] 3 - Shutter-AE Lock: AF/AE lock +[MakerNotes, CanonCustom, Camera] 4 - AF Assist Beam: Emits +[MakerNotes, CanonCustom, Camera] 5 - Exposure Level Increments: 1/3 Stop +[MakerNotes, CanonCustom, Camera] 6 - Mirror Lockup: Disable +[MakerNotes, CanonCustom, Camera] 7 - E-TTL II: Evaluative +[MakerNotes, CanonCustom, Camera] 8 - Shutter Curtain Sync: 1st-curtain sync +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS Digital Rebel XT / 350D / Kiss Digital N +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3456 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2304 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3456 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2304 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 189 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 188 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 0 -1237 -742 0 742 1237 0 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: -617 0 0 0 0 0 617 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: 3 +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 2 +[MakerNotes, Canon, Camera] 25 - Canon 0x0019: 1 +[MakerNotes, Canon, Camera] 131 - Original Decision Data Offset: 0 +[MakerNotes, Canon, Image] 1 - File Number: 100-0024 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 8 - Long Exposure Noise Reduction 2: Off +[MakerNotes, Canon, Image] 9 - WB Bracket Mode: Off +[MakerNotes, Canon, Image] 12 - WB Bracket Value AB: 0 +[MakerNotes, Canon, Image] 13 - WB Bracket Value GM: 0 +[MakerNotes, Canon, Image] 14 - Filter Effect: None +[MakerNotes, Canon, Image] 15 - Toning Effect: None +[MakerNotes, Canon, Image] 1 - Tone Curve: Standard +[MakerNotes, Canon, Image] 3 - Sharpness Frequency: n/a +[MakerNotes, Canon, Image] 4 - Sensor Red Level: 0 +[MakerNotes, Canon, Image] 5 - Sensor Blue Level: 0 +[MakerNotes, Canon, Image] 6 - White Balance Red: 0 +[MakerNotes, Canon, Image] 7 - White Balance Blue: 0 +[MakerNotes, Canon, Image] 9 - Color Temperature: 5200 +[MakerNotes, Canon, Image] 10 - Picture Style: None +[MakerNotes, Canon, Image] 11 - Digital Gain: 0 +[MakerNotes, Canon, Image] 12 - WB Shift AB: 0 +[MakerNotes, Canon, Image] 13 - WB Shift GM: 0 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 580 1024 1024 500 +[MakerNotes, Canon, Camera] 208 - VRD Offset: 0 +[MakerNotes, Canon, Image] 1 - Sensor Width: 3516 +[MakerNotes, Canon, Image] 2 - Sensor Height: 2328 +[MakerNotes, Canon, Image] 5 - Sensor Left Border: 52 +[MakerNotes, Canon, Image] 6 - Sensor Top Border: 19 +[MakerNotes, Canon, Image] 7 - Sensor Right Border: 3507 +[MakerNotes, Canon, Image] 8 - Sensor Bottom Border: 2322 +[MakerNotes, Canon, Image] 9 - Black Mask Left Border: 0 +[MakerNotes, Canon, Image] 10 - Black Mask Top Border: 0 +[MakerNotes, Canon, Image] 11 - Black Mask Right Border: 0 +[MakerNotes, Canon, Image] 12 - Black Mask Bottom Border: 0 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels As Shot: 2002 1185 1187 2364 +[MakerNotes, Canon, Camera] 29 - Color Temp As Shot: 3064 +[MakerNotes, Canon, Camera] 30 - WB RGGB Levels Auto: 1720 1022 1024 2031 +[MakerNotes, Canon, Camera] 34 - Color Temp Auto: 3064 +[MakerNotes, Canon, Camera] 35 - WB RGGB Levels Daylight: 2312 1022 1024 1501 +[MakerNotes, Canon, Camera] 39 - Color Temp Daylight: 5200 +[MakerNotes, Canon, Camera] 40 - WB RGGB Levels Shade: 2714 1022 1024 1246 +[MakerNotes, Canon, Camera] 44 - Color Temp Shade: 7000 +[MakerNotes, Canon, Camera] 45 - WB RGGB Levels Cloudy: 2512 1022 1024 1355 +[MakerNotes, Canon, Camera] 49 - Color Temp Cloudy: 6000 +[MakerNotes, Canon, Camera] 50 - WB RGGB Levels Tungsten: 1523 1022 1024 2397 +[MakerNotes, Canon, Camera] 54 - Color Temp Tungsten: 3201 +[MakerNotes, Canon, Camera] 55 - WB RGGB Levels Fluorescent: 1944 1022 1024 2015 +[MakerNotes, Canon, Camera] 59 - Color Temp Fluorescent: 3905 +[MakerNotes, Canon, Camera] 60 - WB RGGB Levels Flash: 2606 1022 1024 1308 +[MakerNotes, Canon, Camera] 64 - Color Temp Flash: 6440 +[MakerNotes, Canon, Camera] 65 - WB RGGB Levels Custom 1: 2312 1022 1024 1501 +[MakerNotes, Canon, Camera] 69 - Color Temp Custom 1: 5200 +[MakerNotes, Canon, Camera] 70 - WB RGGB Levels Custom 2: 2312 1022 1024 1501 +[MakerNotes, Canon, Camera] 74 - Color Temp Custom 2: 5200 +[MakerNotes, Canon, Camera] 0 - Camera Color Calibration 01: 333 1013 -410 (11109K) +[MakerNotes, Canon, Camera] 4 - Camera Color Calibration 02: 346 963 -376 (9523K) +[MakerNotes, Canon, Camera] 8 - Camera Color Calibration 03: 364 897 -329 (8000K) +[MakerNotes, Canon, Camera] 12 - Camera Color Calibration 04: 386 841 -284 (7000K) +[MakerNotes, Canon, Camera] 16 - Camera Color Calibration 05: 417 773 -225 (6000K) +[MakerNotes, Canon, Camera] 20 - Camera Color Calibration 06: 433 738 -194 (5600K) +[MakerNotes, Canon, Camera] 24 - Camera Color Calibration 07: 453 698 -156 (5200K) +[MakerNotes, Canon, Camera] 28 - Camera Color Calibration 08: 482 650 -108 (4749K) +[MakerNotes, Canon, Camera] 32 - Camera Color Calibration 09: 518 600 -53 (4312K) +[MakerNotes, Canon, Camera] 36 - Camera Color Calibration 10: 549 565 -10 (4023K) +[MakerNotes, Canon, Camera] 40 - Camera Color Calibration 11: 596 516 51 (3696K) +[MakerNotes, Canon, Camera] 44 - Camera Color Calibration 12: 677 443 151 (3250K) +[MakerNotes, Canon, Camera] 48 - Camera Color Calibration 13: 720 415 196 (3051K) +[MakerNotes, Canon, Camera] 52 - Camera Color Calibration 14: 770 382 248 (2871K) +[MakerNotes, Canon, Camera] 56 - Camera Color Calibration 15: 949 307 397 (2413K) +[MakerNotes, Canon, Camera] 2 - Color Tone: Normal +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-aux, Camera] Lens - Lens: 18.0 - 55.0 mm +[XMP, XMP-exif, Image] ComponentsConfiguration - Components Configuration: -, -, -, - +[XMP, XMP-exif, Camera] FlashFired - Flash Fired: False +[XMP, XMP-exif, Camera] FlashFunction - Flash Function: False +[XMP, XMP-exif, Camera] FlashMode - Flash Mode: Off +[XMP, XMP-exif, Camera] FlashRedEyeMode - Flash Red Eye Mode: False +[XMP, XMP-exif, Camera] FlashReturn - Flash Return: No return detection +[XMP, XMP-exifEX, Image] InteroperabilityIndex - Interoperability Index: R98 - DCF basic file (sRGB) +[XMP, XMP-pmi, Image] sequenceNumber - Sequence Number: 0 +[XMP, XMP-tiff, Image] BitsPerSample - Bits Per Sample: 8, 8, 8 +[XMP, XMP-tiff, Image] Compression - Compression: JPEG (old-style) +[XMP, XMP-tiff, Image] ImageLength - Image Height: 1024 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 1536 +[XMP, XMP-tiff, Image] PhotometricInterpretation - Photometric Interpretation: RGB +[XMP, XMP-tiff, Image] PlanarConfiguration - Planar Configuration: Chunky +[XMP, XMP-tiff, Image] SamplesPerPixel - Samples Per Pixel: 3 +[XMP, XMP-crs, Image] Temperature - Color Temperature: 5200 +[XMP, XMP-crs, Image] ToneCurve - Tone Curve: Standard +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Single-frame Shooting +[Composite, Composite, Camera] Canon-ISO - ISO: 400 +[Composite, Composite, Camera] Canon-Lens - Lens: 18.0 - 55.0 mm +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Aperture-priority AE +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 2002 1185 1187 2364 +[Composite, Composite, Image] Exif-Aperture - Aperture: 8.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/15 +[Composite, Composite, Camera] XMP-Flash - Flash: Off, Did not fire +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.993255 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown 18-55mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 7.9 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.688027 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 18.0 - 55.0 mm (35 mm equivalent: 28.1 - 85.9 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 23.7 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 55.0 mm (35 mm equivalent: 85.9 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 19.66 m diff --git a/ExifTool/t/CanonRaw_8.out b/ExifTool/t/CanonRaw_8.out new file mode 100644 index 0000000..002cbc0 --- /dev/null +++ b/ExifTool/t/CanonRaw_8.out @@ -0,0 +1,510 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.53 +[File, System, Other] FileName - File Name: CanonRaw.cr3 +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 53 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:03:26 22:05:16-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:12:01 14:25:17-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:18 10:49:22-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: CR3 +[File, File, Other] FileTypeExtension - File Type Extension: cr3 +[File, File, Other] MIMEType - MIME Type: image/x-canon-cr3 +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, Track4, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[QuickTime, QuickTime, Video] 0 - Major Brand: Canon Raw (.CRX) +[QuickTime, QuickTime, Video] 1 - Minor Version: 0.0.1 +[QuickTime, QuickTime, Video] 2 - Compatible Brands: crx , isom +[QuickTime, QuickTime, Video] 0 - Movie Header Version: 0 +[QuickTime, QuickTime, Time] 1 - Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, QuickTime, Time] 2 - Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, QuickTime, Video] 3 - Time Scale: 1 +[QuickTime, QuickTime, Video] 4 - Duration: 1.00 s +[QuickTime, QuickTime, Video] 5 - Preferred Rate: 1 +[QuickTime, QuickTime, Video] 6 - Preferred Volume: 100.00% +[QuickTime, QuickTime, Video] 9 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, QuickTime, Video] 18 - Preview Time: 0 s +[QuickTime, QuickTime, Video] 19 - Preview Duration: 0 s +[QuickTime, QuickTime, Video] 20 - Poster Time: 0 s +[QuickTime, QuickTime, Video] 21 - Selection Time: 0 s +[QuickTime, QuickTime, Video] 22 - Selection Duration: 0 s +[QuickTime, QuickTime, Video] 23 - Current Time: 0 s +[QuickTime, QuickTime, Video] 24 - Next Track ID: 5 +[QuickTime, Track1, Video] 0 - Track Header Version: 0 +[QuickTime, Track1, Time] 1 - Track Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track1, Time] 2 - Track Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track1, Video] 3 - Track ID: 1 +[QuickTime, Track1, Video] 5 - Track Duration: 1.00 s +[QuickTime, Track1, Video] 8 - Track Layer: 0 +[QuickTime, Track1, Video] 9 - Track Volume: 0.00% +[QuickTime, Track1, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track1, Video] 19 - Image Width: 6000 +[QuickTime, Track1, Video] 20 - Image Height: 4000 +[QuickTime, Track1, Video] 0 - Media Header Version: 0 +[QuickTime, Track1, Time] 1 - Media Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track1, Time] 2 - Media Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track1, Video] 3 - Media Time Scale: 1 +[QuickTime, Track1, Video] 4 - Media Duration: 1.00 s +[QuickTime, Track1, Video] 5 - Media Language Code: eng +[QuickTime, Track1, Video] 8 - Handler Type: Video Track +[QuickTime, Track1, Video] 2 - Graphics Mode: srcCopy +[QuickTime, Track1, Video] 3 - Op Color: 0 0 0 +[QuickTime, Track1, Image] 2 - Compressor ID: CRAW +[QuickTime, Track1, Image] 16 - Source Image Width: 6000 +[QuickTime, Track1, Image] 17 - Source Image Height: 4000 +[QuickTime, Track1, Image] 18 - X Resolution: 72 +[QuickTime, Track1, Image] 20 - Y Resolution: 72 +[QuickTime, Track1, Image] 41 - Bit Depth: 24 +[QuickTime, Track1, Video] stts - Video Frame Rate: 1 +[QuickTime, Track1, Video] SampleTime - Sample Time: 0 s +[QuickTime, Track1, Video] SampleDuration - Sample Duration: 1.00 s +[QuickTime, Track1, Preview] JPEG - Jpg From Raw: (Binary data 29 bytes) +[QuickTime, Track2, Video] 0 - Track Header Version: 0 +[QuickTime, Track2, Time] 1 - Track Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track2, Time] 2 - Track Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track2, Video] 3 - Track ID: 2 +[QuickTime, Track2, Video] 5 - Track Duration: 1.00 s +[QuickTime, Track2, Video] 8 - Track Layer: 0 +[QuickTime, Track2, Video] 9 - Track Volume: 0.00% +[QuickTime, Track2, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track2, Video] 19 - Image Width: 1624 +[QuickTime, Track2, Video] 20 - Image Height: 1080 +[QuickTime, Track2, Video] 0 - Media Header Version: 0 +[QuickTime, Track2, Time] 1 - Media Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track2, Time] 2 - Media Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track2, Video] 3 - Media Time Scale: 1 +[QuickTime, Track2, Video] 4 - Media Duration: 1.00 s +[QuickTime, Track2, Video] 5 - Media Language Code: eng +[QuickTime, Track2, Video] 8 - Handler Type: Video Track +[QuickTime, Track2, Video] 2 - Graphics Mode: srcCopy +[QuickTime, Track2, Video] 3 - Op Color: 0 0 0 +[QuickTime, Track2, Image] 2 - Compressor ID: CRAW +[QuickTime, Track2, Image] 16 - Source Image Width: 1624 +[QuickTime, Track2, Image] 17 - Source Image Height: 1080 +[QuickTime, Track2, Image] 18 - X Resolution: 72 +[QuickTime, Track2, Image] 20 - Y Resolution: 72 +[QuickTime, Track2, Image] 41 - Bit Depth: 24 +[QuickTime, Track2, Video] stts - Video Frame Rate: 1 +[QuickTime, Track3, Video] 0 - Track Header Version: 0 +[QuickTime, Track3, Time] 1 - Track Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track3, Time] 2 - Track Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track3, Video] 3 - Track ID: 3 +[QuickTime, Track3, Video] 5 - Track Duration: 1.00 s +[QuickTime, Track3, Video] 8 - Track Layer: 0 +[QuickTime, Track3, Video] 9 - Track Volume: 0.00% +[QuickTime, Track3, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track3, Video] 19 - Image Width: 6288 +[QuickTime, Track3, Video] 20 - Image Height: 4056 +[QuickTime, Track3, Video] 0 - Media Header Version: 0 +[QuickTime, Track3, Time] 1 - Media Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track3, Time] 2 - Media Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track3, Video] 3 - Media Time Scale: 1 +[QuickTime, Track3, Video] 4 - Media Duration: 1.00 s +[QuickTime, Track3, Video] 5 - Media Language Code: eng +[QuickTime, Track3, Video] 8 - Handler Type: Video Track +[QuickTime, Track3, Video] 2 - Graphics Mode: srcCopy +[QuickTime, Track3, Video] 3 - Op Color: 0 0 0 +[QuickTime, Track3, Image] 2 - Compressor ID: CRAW +[QuickTime, Track3, Image] 16 - Source Image Width: 6288 +[QuickTime, Track3, Image] 17 - Source Image Height: 4056 +[QuickTime, Track3, Image] 18 - X Resolution: 72 +[QuickTime, Track3, Image] 20 - Y Resolution: 72 +[QuickTime, Track3, Image] 41 - Bit Depth: 24 +[QuickTime, Track3, Video] stts - Video Frame Rate: 1 +[QuickTime, Track4, Video] 0 - Track Header Version: 0 +[QuickTime, Track4, Time] 1 - Track Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track4, Time] 2 - Track Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track4, Video] 3 - Track ID: 4 +[QuickTime, Track4, Video] 5 - Track Duration: 1.00 s +[QuickTime, Track4, Video] 8 - Track Layer: 0 +[QuickTime, Track4, Video] 9 - Track Volume: 0.00% +[QuickTime, Track4, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track4, Video] 0 - Media Header Version: 0 +[QuickTime, Track4, Time] 1 - Media Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track4, Time] 2 - Media Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track4, Video] 3 - Media Time Scale: 1 +[QuickTime, Track4, Video] 4 - Media Duration: 1.00 s +[QuickTime, Track4, Video] 5 - Media Language Code: eng +[QuickTime, Track4, Video] 8 - Handler Type: NRT Metadata +[QuickTime, Track4, Other] 4 - Meta Format: CTMD +[QuickTime, Track4, Video] SampleTime - Sample Time: 0 s +[QuickTime, Track4, Video] SampleDuration - Sample Duration: 1.00 s +[QuickTime, QuickTime, Preview] uuid - Preview Image: (Binary data 26 bytes) +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 41065 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 11437 +[MakerNotes, Canon, Video] CNCV - Compressor Version: CanonCR3_001/00.09.00/00.00.00 +[MakerNotes, Canon, Camera] 1 - Macro Mode: Normal +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Single +[MakerNotes, Canon, Camera] 7 - Focus Mode: AF + MF +[MakerNotes, Canon, Camera] 9 - Record Mode: CR3+JPEG +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: None +[MakerNotes, Canon, Camera] 13 - Contrast: Normal +[MakerNotes, Canon, Camera] 14 - Saturation: Normal +[MakerNotes, Canon, Camera] 16 - Camera ISO: Auto +[MakerNotes, Canon, Camera] 17 - Metering Mode: Evaluative +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Aperture-priority AE +[MakerNotes, Canon, Camera] 22 - Lens Type: Canon EF-M 15-45mm f/3.5-6.3 IS STM +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 45 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 15 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 3.6 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 23 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: E-TTL +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 0 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 0 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Camera] 46 - SRAW Quality: n/a +[MakerNotes, Canon, Image] 1 - Focal Length: 15 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 12800 +[MakerNotes, Canon, Image] 3 - Measured EV: 2.75 +[MakerNotes, Canon, Image] 4 - Target Aperture: 3.6 +[MakerNotes, Canon, Image] 5 - Target Exposure Time: 1/81 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Camera] 12 - Camera Temperature: 38 C +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 21 - F Number: 3.6 +[MakerNotes, Canon, Image] 22 - Exposure Time: 1/70 +[MakerNotes, Canon, Image] 23 - Measured EV 2: 1.625 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 0 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS High-end +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 6 - Canon Image Type: Canon EOS M50 +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware Version 1.0.0 +[MakerNotes, Canon, Camera] 9 - Owner Name: +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS M50 / Kiss M +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 6 113 +[MakerNotes, Canon, Camera] 1 - AF Area Mode: Face + Tracking +[MakerNotes, Canon, Camera] 2 - Num AF Points: 143 +[MakerNotes, Canon, Camera] 3 - Valid AF Points: 1 +[MakerNotes, Canon, Image] 4 - Canon Image Width: 6000 +[MakerNotes, Canon, Image] 5 - Canon Image Height: 4000 +[MakerNotes, Canon, Camera] 6 - AF Image Width: 6000 +[MakerNotes, Canon, Camera] 7 - AF Image Height: 4000 +[MakerNotes, Canon, Camera] 8 - AF Area Widths: 337 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Canon, Camera] 9 - AF Area Heights: 299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Canon, Camera] 10 - AF Area X Positions: 392 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Canon, Camera] 11 - AF Area Y Positions: -327 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Canon, Camera] 12 - AF Points In Focus: 0 +[MakerNotes, Canon, Camera] 13 - AF Points Selected: 0 +[MakerNotes, Canon, Image] 40 - Image Unique ID: 3b7f679f6bf5d5e1b60ca2b2f051a029 +[MakerNotes, Canon, Time] 1 - Time Zone: +00:00 +[MakerNotes, Canon, Time] 2 - Time Zone City: London +[MakerNotes, Canon, Time] 3 - Daylight Savings: Off +[MakerNotes, Canon, Camera] 56 - Battery Type: LP-E12 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 7 - Raw Jpg Size: Large +[MakerNotes, Canon, Image] 9 - WB Bracket Mode: Off +[MakerNotes, Canon, Image] 12 - WB Bracket Value AB: 0 +[MakerNotes, Canon, Image] 13 - WB Bracket Value GM: 0 +[MakerNotes, Canon, Image] 19 - Live View Shooting: On +[MakerNotes, Canon, Image] 20 - Focus Distance Upper: 0.24 m +[MakerNotes, Canon, Image] 21 - Focus Distance Lower: 0.22 m +[MakerNotes, Canon, Image] 23 - Shutter Mode: Electronic First Curtain +[MakerNotes, Canon, Image] 25 - Flash Exposure Lock: Off +[MakerNotes, Canon, Image] 61 - RF Lens Type: n/a +[MakerNotes, Canon, Camera] 149 - Lens Model: EF-M15-45mm f/3.5-6.3 IS STM +[MakerNotes, Canon, Camera] 150 - Internal Serial Number: CG0156580 +[MakerNotes, Canon, Camera] 151 - Dust Removal Data: (Binary data 1024 bytes) +[MakerNotes, Canon, Camera] 0 - Crop Left Margin: 0 +[MakerNotes, Canon, Camera] 1 - Crop Right Margin: 0 +[MakerNotes, Canon, Camera] 2 - Crop Top Margin: 0 +[MakerNotes, Canon, Camera] 3 - Crop Bottom Margin: 0 +[MakerNotes, CanonCustom, Camera] 259 - ISO Expansion: Off +[MakerNotes, CanonCustom, Camera] 264 - Safety Shift: Disable +[MakerNotes, CanonCustom, Camera] 1809 - Shutter Release Without Lens: Disable +[MakerNotes, CanonCustom, Camera] 2068 - Retract Lens On Power Off: Enable +[MakerNotes, CanonCustom, Camera] 1804 - Custom Controls: 0 19 3 10 23 73 71 61 29 0 7 3 4 +[MakerNotes, Canon, Camera] 0 - Aspect Ratio: 3:2 +[MakerNotes, Canon, Camera] 1 - Cropped Image Width: 6000 +[MakerNotes, Canon, Camera] 2 - Cropped Image Height: 4000 +[MakerNotes, Canon, Camera] 3 - Cropped Image Left: 0 +[MakerNotes, Canon, Camera] 4 - Cropped Image Top: 0 +[MakerNotes, Canon, Image] 1 - Tone Curve: Standard +[MakerNotes, Canon, Image] 2 - Sharpness: 4 +[MakerNotes, Canon, Image] 3 - Sharpness Frequency: n/a +[MakerNotes, Canon, Image] 4 - Sensor Red Level: 0 +[MakerNotes, Canon, Image] 5 - Sensor Blue Level: 0 +[MakerNotes, Canon, Image] 6 - White Balance Red: 0 +[MakerNotes, Canon, Image] 7 - White Balance Blue: 0 +[MakerNotes, Canon, Image] 9 - Color Temperature: 5200 +[MakerNotes, Canon, Image] 10 - Picture Style: Auto +[MakerNotes, Canon, Image] 11 - Digital Gain: 0 +[MakerNotes, Canon, Image] 12 - WB Shift AB: 0 +[MakerNotes, Canon, Image] 13 - WB Shift GM: 0 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 1183 1024 1024 461 +[MakerNotes, Canon, Camera] 180 - Color Space: sRGB +[MakerNotes, Canon, Camera] 208 - VRD Offset: 0 +[MakerNotes, Canon, Image] 1 - Sensor Width: 6288 +[MakerNotes, Canon, Image] 2 - Sensor Height: 4056 +[MakerNotes, Canon, Image] 5 - Sensor Left Border: 276 +[MakerNotes, Canon, Image] 6 - Sensor Top Border: 48 +[MakerNotes, Canon, Image] 7 - Sensor Right Border: 6275 +[MakerNotes, Canon, Image] 8 - Sensor Bottom Border: 4047 +[MakerNotes, Canon, Image] 9 - Black Mask Left Border: 0 +[MakerNotes, Canon, Image] 10 - Black Mask Top Border: 0 +[MakerNotes, Canon, Image] 11 - Black Mask Right Border: 0 +[MakerNotes, Canon, Image] 12 - Black Mask Bottom Border: 0 +[MakerNotes, Canon, Camera] 16392 - Picture Style User Def: Auto; Auto; Auto +[MakerNotes, Canon, Camera] 16393 - Picture Style PC: None; None; None +[MakerNotes, Canon, Camera] 16400 - Custom Picture Style File Name: +[MakerNotes, Canon, Camera] 1 - AF Micro Adj Mode: Disable +[MakerNotes, Canon, Camera] 2 - AF Micro Adj Value: 0 +[MakerNotes, Canon, Camera] 5 - Peripheral Lighting Setting: On +[MakerNotes, Canon, Camera] 6 - Chromatic Aberration Setting: On +[MakerNotes, Canon, Camera] 7 - Distortion Correction Setting: Off +[MakerNotes, Canon, Camera] 9 - Digital Lens Optimizer Setting: Off +[MakerNotes, Canon, Camera] 1 - Peripheral Illumination Corr: Off +[MakerNotes, Canon, Camera] 2 - Auto Lighting Optimizer: Standard +[MakerNotes, Canon, Camera] 3 - Highlight Tone Priority: Off +[MakerNotes, Canon, Camera] 4 - Long Exposure Noise Reduction: Off +[MakerNotes, Canon, Camera] 5 - High ISO Noise Reduction: Standard +[MakerNotes, Canon, Camera] 10 - Digital Lens Optimizer: Off +[MakerNotes, Canon, Camera] 1 - Ambience Selection: Standard +[MakerNotes, Canon, Camera] 257 - Grainy B/W Filter: Off +[MakerNotes, Canon, Camera] 513 - Soft Focus Filter: Off +[MakerNotes, Canon, Camera] 769 - Toy Camera Filter: Off +[MakerNotes, Canon, Camera] 1025 - Miniature Filter: Off +[MakerNotes, Canon, Camera] 1026 - Miniature Filter Orientation: Horizontal +[MakerNotes, Canon, Camera] 1027 - Miniature Filter Position: 0 +[MakerNotes, Canon, Camera] 1028 - Miniature Filter Parameter: 0 +[MakerNotes, Canon, Camera] 1281 - Fisheye Filter: Off +[MakerNotes, Canon, Camera] 1537 - Painting Filter: Off +[MakerNotes, Canon, Camera] 1793 - Watercolor Filter: Off +[MakerNotes, Canon, Image] 1 - HDR: Off +[MakerNotes, Canon, Image] 2 - HDR Effect: Natural +[MakerNotes, Canon, Preview] THMB - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Track2, Image] 8 - Image Width: 1624 +[MakerNotes, Track2, Image] 10 - Image Height: 1080 +[MakerNotes, Track3, Image] 8 - Image Width: 3144 +[MakerNotes, Track3, Image] 10 - Image Height: 4056 +[MakerNotes, Track4, Time] 1 - Time Stamp: 2018:02:21 12:08:56.21 +[MakerNotes, Track4, Image] 0 - Focal Length: 15.0 mm +[MakerNotes, Track4, Image] 0 - F Number: 3.5 +[MakerNotes, Track4, Image] 1 - Exposure Time: 1/80 +[MakerNotes, Track4, Image] 2 - ISO: 12800 +[MakerNotes, Track4, Camera] 1 - Macro Mode: Normal +[MakerNotes, Track4, Camera] 2 - Self Timer: Off +[MakerNotes, Track4, Camera] 3 - Quality: RAW +[MakerNotes, Track4, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Track4, Camera] 5 - Continuous Drive: Single +[MakerNotes, Track4, Camera] 7 - Focus Mode: AF + MF +[MakerNotes, Track4, Camera] 9 - Record Mode: CR3+JPEG +[MakerNotes, Track4, Camera] 10 - Canon Image Size: Large +[MakerNotes, Track4, Camera] 11 - Easy Mode: Manual +[MakerNotes, Track4, Camera] 12 - Digital Zoom: None +[MakerNotes, Track4, Camera] 13 - Contrast: Normal +[MakerNotes, Track4, Camera] 14 - Saturation: Normal +[MakerNotes, Track4, Camera] 16 - Camera ISO: Auto +[MakerNotes, Track4, Camera] 17 - Metering Mode: Evaluative +[MakerNotes, Track4, Camera] 18 - Focus Range: Not Known +[MakerNotes, Track4, Camera] 20 - Canon Exposure Mode: Aperture-priority AE +[MakerNotes, Track4, Camera] 22 - Lens Type: Canon EF-M 15-45mm f/3.5-6.3 IS STM +[MakerNotes, Track4, Camera] 23 - Max Focal Length: 45 mm +[MakerNotes, Track4, Camera] 24 - Min Focal Length: 15 mm +[MakerNotes, Track4, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Track4, Camera] 26 - Max Aperture: 3.6 +[MakerNotes, Track4, Camera] 27 - Min Aperture: 23 +[MakerNotes, Track4, Camera] 28 - Flash Activity: 0 +[MakerNotes, Track4, Camera] 29 - Flash Bits: E-TTL +[MakerNotes, Track4, Camera] 36 - Zoom Source Width: 0 +[MakerNotes, Track4, Camera] 37 - Zoom Target Width: 0 +[MakerNotes, Track4, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Track4, Camera] 42 - Color Tone: Normal +[MakerNotes, Track4, Camera] 46 - SRAW Quality: n/a +[MakerNotes, Track4, Image] 1 - Auto ISO: 100 +[MakerNotes, Track4, Image] 2 - Base ISO: 12800 +[MakerNotes, Track4, Image] 3 - Measured EV: 2.75 +[MakerNotes, Track4, Image] 4 - Target Aperture: 3.6 +[MakerNotes, Track4, Image] 5 - Target Exposure Time: 1/81 +[MakerNotes, Track4, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Track4, Image] 7 - White Balance: Auto +[MakerNotes, Track4, Image] 8 - Slow Shutter: None +[MakerNotes, Track4, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Track4, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Track4, Camera] 12 - Camera Temperature: 38 C +[MakerNotes, Track4, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Track4, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Track4, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Track4, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Track4, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Track4, Image] 21 - F Number: 3.6 +[MakerNotes, Track4, Image] 22 - Exposure Time: 1/70 +[MakerNotes, Track4, Image] 23 - Measured EV 2: 1.625 +[MakerNotes, Track4, Image] 24 - Bulb Duration: 0 +[MakerNotes, Track4, Camera] 26 - Camera Type: EOS High-end +[MakerNotes, Track4, Image] 27 - Auto Rotate: None +[MakerNotes, Track4, Image] 28 - ND Filter: n/a +[MakerNotes, Track4, Camera] 1 - AF Area Mode: Face + Tracking +[MakerNotes, Track4, Camera] 2 - Num AF Points: 143 +[MakerNotes, Track4, Camera] 3 - Valid AF Points: 1 +[MakerNotes, Track4, Image] 4 - Canon Image Width: 6000 +[MakerNotes, Track4, Image] 5 - Canon Image Height: 4000 +[MakerNotes, Track4, Camera] 6 - AF Image Width: 6000 +[MakerNotes, Track4, Camera] 7 - AF Image Height: 4000 +[MakerNotes, Track4, Camera] 8 - AF Area Widths: 337 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Track4, Camera] 9 - AF Area Heights: 299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Track4, Camera] 10 - AF Area X Positions: 392 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Track4, Camera] 11 - AF Area Y Positions: -327 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Track4, Camera] 12 - AF Points In Focus: 0 +[MakerNotes, Track4, Camera] 13 - AF Points Selected: 0 +[MakerNotes, Track4, Image] 3 - Bracket Mode: Off +[MakerNotes, Track4, Image] 4 - Bracket Value: 0 +[MakerNotes, Track4, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Track4, Image] 7 - Raw Jpg Size: Large +[MakerNotes, Track4, Image] 9 - WB Bracket Mode: Off +[MakerNotes, Track4, Image] 12 - WB Bracket Value AB: 0 +[MakerNotes, Track4, Image] 13 - WB Bracket Value GM: 0 +[MakerNotes, Track4, Image] 19 - Live View Shooting: On +[MakerNotes, Track4, Image] 20 - Focus Distance Upper: 0.24 m +[MakerNotes, Track4, Image] 21 - Focus Distance Lower: 0.22 m +[MakerNotes, Track4, Image] 23 - Shutter Mode: Electronic First Curtain +[MakerNotes, Track4, Image] 25 - Flash Exposure Lock: Off +[MakerNotes, Track4, Image] 61 - RF Lens Type: n/a +[MakerNotes, Track4, Image] 1 - Tone Curve: Standard +[MakerNotes, Track4, Image] 2 - Sharpness: 4 +[MakerNotes, Track4, Image] 3 - Sharpness Frequency: n/a +[MakerNotes, Track4, Image] 4 - Sensor Red Level: 0 +[MakerNotes, Track4, Image] 5 - Sensor Blue Level: 0 +[MakerNotes, Track4, Image] 6 - White Balance Red: 0 +[MakerNotes, Track4, Image] 7 - White Balance Blue: 0 +[MakerNotes, Track4, Image] 9 - Color Temperature: 5200 +[MakerNotes, Track4, Image] 10 - Picture Style: Auto +[MakerNotes, Track4, Image] 11 - Digital Gain: 0 +[MakerNotes, Track4, Image] 12 - WB Shift AB: 0 +[MakerNotes, Track4, Image] 13 - WB Shift GM: 0 +[MakerNotes, Track4, Camera] 5 - Peripheral Lighting Setting: On +[MakerNotes, Track4, Camera] 6 - Chromatic Aberration Setting: On +[MakerNotes, Track4, Camera] 7 - Distortion Correction Setting: Off +[MakerNotes, Track4, Camera] 9 - Digital Lens Optimizer Setting: Off +[MakerNotes, Track4, Camera] 0 - Color Data Version: 16 (M50) +[MakerNotes, Track4, Camera] 71 - WB RGGB Levels As Shot: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 75 - Color Temp As Shot: 3284 +[MakerNotes, Track4, Camera] 76 - WB RGGB Levels Auto: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 80 - Color Temp Auto: 3284 +[MakerNotes, Track4, Camera] 81 - WB RGGB Levels Measured: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 85 - Color Temp Measured: 3284 +[MakerNotes, Track4, Camera] 136 - WB RGGB Levels Daylight: 1549 1024 1024 1316 +[MakerNotes, Track4, Camera] 140 - Color Temp Daylight: 5200 +[MakerNotes, Track4, Camera] 141 - WB RGGB Levels Shade: 1792 1024 1024 1147 +[MakerNotes, Track4, Camera] 145 - Color Temp Shade: 7000 +[MakerNotes, Track4, Camera] 146 - WB RGGB Levels Cloudy: 1670 1024 1024 1229 +[MakerNotes, Track4, Camera] 150 - Color Temp Cloudy: 6000 +[MakerNotes, Track4, Camera] 151 - WB RGGB Levels Tungsten: 1104 1024 1024 1879 +[MakerNotes, Track4, Camera] 155 - Color Temp Tungsten: 3200 +[MakerNotes, Track4, Camera] 156 - WB RGGB Levels Fluorescent: 1303 1024 1024 1771 +[MakerNotes, Track4, Camera] 160 - Color Temp Fluorescent: 3687 +[MakerNotes, Track4, Camera] 161 - WB RGGB Levels Kelvin: 1549 1024 1024 1316 +[MakerNotes, Track4, Camera] 165 - Color Temp Kelvin: 5200 +[MakerNotes, Track4, Camera] 166 - WB RGGB Levels Flash: 1697 1024 1024 1218 +[MakerNotes, Track4, Camera] 170 - Color Temp Flash: 6166 +[MakerNotes, Track4, Camera] 329 - Per Channel Black Level: 2049 2045 2045 2050 +[MakerNotes, Track4, Camera] 796 - Normal White Level: 13035 +[MakerNotes, Track4, Camera] 797 - Specular White Level: 14338 +[MakerNotes, Track4, Camera] 798 - Linearity Upper Margin: 10025 +[MakerNotes, Track4, Camera] 1473 - Firmware Version: 1.0.0 +[MakerNotes, Track4, Camera] 0 - Vignetting Corr Version: 32 +[EXIF, IFD0, Image] 256 - Image Width: 6000 +[EXIF, IFD0, Image] 257 - Image Height: 4000 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS M50 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2018:02:21 12:08:56 +[EXIF, IFD0, Author] 315 - Artist: +[EXIF, IFD0, Author] 33432 - Copyright: +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/80 +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 12800 +[EXIF, ExifIFD, Image] 34864 - Sensitivity Type: Recommended Exposure Index +[EXIF, ExifIFD, Image] 34866 - Recommended Exposure Index: 12800 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0231 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2018:02:21 12:08:56 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2018:02:21 12:08:56 +[EXIF, ExifIFD, Time] 36880 - Offset Time: +00:00 +[EXIF, ExifIFD, Time] 36881 - Offset Time Original: +00:00 +[EXIF, ExifIFD, Time] 36882 - Offset Time Digitized: +00:00 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/83 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 15.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Time] 37520 - Sub Sec Time: 21 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 21 +[EXIF, ExifIFD, Time] 37522 - Sub Sec Time Digitized: 21 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 6000 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 4000 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 6514.65798 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 6734.006734 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Image] 42032 - Owner Name: +[EXIF, ExifIFD, Image] 42033 - Serial Number: 613040000565 +[EXIF, ExifIFD, Image] 42034 - Lens Info: 15-45mm f/0 +[EXIF, ExifIFD, Image] 42036 - Lens Model: EF-M15-45mm f/3.5-6.3 IS STM +[EXIF, ExifIFD, Image] 42037 - Lens Serial Number: 0000000000 +[EXIF, GPS, Location] 0 - GPS Version ID: 2.3.0.0 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[XMP, XMP-xmp, Image] Rating - Rating: 0 +[Composite, Composite, Camera] Canon-ConditionalFEC - Flash Exposure Compensation: 0 +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Single-frame Shooting +[Composite, Composite, Camera] Canon-FlashType - Flash Type: Built-In Flash +[Composite, Composite, Camera] Canon-ISO - ISO: 12800 +[Composite, Composite, Camera] Canon-Lens - Lens: 15.0 - 45.0 mm +[Composite, Composite, Camera] Canon-RedEyeReduction - Red Eye Reduction: Off +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Aperture-priority AE +[Composite, Composite, Camera] Canon-ShutterCurtainHack - Shutter Curtain Sync: 1st-curtain sync +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 1104 1024 1024 1789 +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/80 +[Composite, Composite, Time] Exif-SubSecCreateDate - Create Date: 2018:02:21 12:08:56.21+00:00 +[Composite, Composite, Time] Exif-SubSecDateTimeOriginal - Date/Time Original: 2018:02:21 12:08:56.21+00:00 +[Composite, Composite, Time] Exif-SubSecModifyDate - Modify Date: 2018:02:21 12:08:56.21+00:00 +[Composite, Composite, Video] QuickTime-AvgBitrate - Avg Bitrate: 329 kbps +[Composite, Composite, Video] QuickTime-Rotation - Rotation: 0 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.74707 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 6000x4000 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Canon EF-M 15-45mm f/3.5-6.3 IS STM +[Composite, Composite, Image] Exif-LightValue - Light Value: 2.9 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 24.0 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.078125 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 15.0 - 45.0 mm (35 mm equivalent: 23.3 - 69.9 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.03 m (0.22 - 0.25 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 75.3 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 15.0 mm (35 mm equivalent: 23.3 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 3.33 m diff --git a/ExifTool/t/CanonRaw_9.out b/ExifTool/t/CanonRaw_9.out new file mode 100644 index 0000000..f82255b --- /dev/null +++ b/ExifTool/t/CanonRaw_9.out @@ -0,0 +1,634 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.53 +[File, System, Other] FileName - File Name: CanonRaw_9_failed.cr3 +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 53 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:12:21 10:47:57-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:12:21 10:47:57-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:12:21 10:47:57-05:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: CR3 +[File, File, Other] FileTypeExtension - File Type Extension: cr3 +[File, File, Other] MIMEType - MIME Type: image/x-canon-cr3 +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, Track4, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[QuickTime, QuickTime, Video] 0 - Major Brand: Canon Raw (.CRX) +[QuickTime, QuickTime, Video] 1 - Minor Version: 0.0.1 +[QuickTime, QuickTime, Video] 2 - Compatible Brands: crx , isom +[QuickTime, QuickTime, Video] 0 - Movie Header Version: 0 +[QuickTime, QuickTime, Time] 1 - Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, QuickTime, Time] 2 - Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, QuickTime, Video] 3 - Time Scale: 1 +[QuickTime, QuickTime, Video] 4 - Duration: 1.00 s +[QuickTime, QuickTime, Video] 5 - Preferred Rate: 1 +[QuickTime, QuickTime, Video] 6 - Preferred Volume: 100.00% +[QuickTime, QuickTime, Video] 9 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, QuickTime, Video] 18 - Preview Time: 0 s +[QuickTime, QuickTime, Video] 19 - Preview Duration: 0 s +[QuickTime, QuickTime, Video] 20 - Poster Time: 0 s +[QuickTime, QuickTime, Video] 21 - Selection Time: 0 s +[QuickTime, QuickTime, Video] 22 - Selection Duration: 0 s +[QuickTime, QuickTime, Video] 23 - Current Time: 0 s +[QuickTime, QuickTime, Video] 24 - Next Track ID: 5 +[QuickTime, Track1, Video] 0 - Track Header Version: 0 +[QuickTime, Track1, Time] 1 - Track Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track1, Time] 2 - Track Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track1, Video] 3 - Track ID: 1 +[QuickTime, Track1, Video] 5 - Track Duration: 1.00 s +[QuickTime, Track1, Video] 8 - Track Layer: 0 +[QuickTime, Track1, Video] 9 - Track Volume: 0.00% +[QuickTime, Track1, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track1, Video] 19 - Image Width: 6000 +[QuickTime, Track1, Video] 20 - Image Height: 4000 +[QuickTime, Track1, Video] 0 - Media Header Version: 0 +[QuickTime, Track1, Time] 1 - Media Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track1, Time] 2 - Media Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track1, Video] 3 - Media Time Scale: 1 +[QuickTime, Track1, Video] 4 - Media Duration: 1.00 s +[QuickTime, Track1, Video] 5 - Media Language Code: eng +[QuickTime, Track1, Video] 8 - Handler Type: Video Track +[QuickTime, Track1, Video] 2 - Graphics Mode: srcCopy +[QuickTime, Track1, Video] 3 - Op Color: 0 0 0 +[QuickTime, Track1, Image] 2 - Compressor ID: CRAW +[QuickTime, Track1, Image] 16 - Source Image Width: 6000 +[QuickTime, Track1, Image] 17 - Source Image Height: 4000 +[QuickTime, Track1, Image] 18 - X Resolution: 72 +[QuickTime, Track1, Image] 20 - Y Resolution: 72 +[QuickTime, Track1, Image] 41 - Bit Depth: 24 +[QuickTime, Track1, Image] JPEG - JPEG Info: (Binary data 4 bytes) +[QuickTime, Track1, Image] free - Unknown free: +[QuickTime, Track1, Video] stts - Video Frame Rate: 1 +[QuickTime, Track1, Video] stsc - Sample To Chunk: (Binary data 20 bytes) +[QuickTime, Track1, Video] stsz - Sample Sizes: (Binary data 12 bytes) +[QuickTime, Track1, Video] free - Unknown free: +[QuickTime, Track1, Video] co64 - Chunk Offset 64: (Binary data 16 bytes) +[QuickTime, Track1, Video] SampleTime - Sample Time: 0 s +[QuickTime, Track1, Video] SampleDuration - Sample Duration: 1.00 s +[QuickTime, Track1, Preview] JPEG - Jpg From Raw: (Binary data 29 bytes) +[QuickTime, Track2, Video] 0 - Track Header Version: 0 +[QuickTime, Track2, Time] 1 - Track Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track2, Time] 2 - Track Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track2, Video] 3 - Track ID: 2 +[QuickTime, Track2, Video] 5 - Track Duration: 1.00 s +[QuickTime, Track2, Video] 8 - Track Layer: 0 +[QuickTime, Track2, Video] 9 - Track Volume: 0.00% +[QuickTime, Track2, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track2, Video] 19 - Image Width: 1624 +[QuickTime, Track2, Video] 20 - Image Height: 1080 +[QuickTime, Track2, Video] 0 - Media Header Version: 0 +[QuickTime, Track2, Time] 1 - Media Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track2, Time] 2 - Media Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track2, Video] 3 - Media Time Scale: 1 +[QuickTime, Track2, Video] 4 - Media Duration: 1.00 s +[QuickTime, Track2, Video] 5 - Media Language Code: eng +[QuickTime, Track2, Video] 8 - Handler Type: Video Track +[QuickTime, Track2, Video] 2 - Graphics Mode: srcCopy +[QuickTime, Track2, Video] 3 - Op Color: 0 0 0 +[QuickTime, Track2, Image] 2 - Compressor ID: CRAW +[QuickTime, Track2, Image] 16 - Source Image Width: 1624 +[QuickTime, Track2, Image] 17 - Source Image Height: 1080 +[QuickTime, Track2, Image] 18 - X Resolution: 72 +[QuickTime, Track2, Image] 20 - Y Resolution: 72 +[QuickTime, Track2, Image] 41 - Bit Depth: 24 +[QuickTime, Track2, Image] free - Unknown free: +[QuickTime, Track2, Video] stts - Video Frame Rate: 1 +[QuickTime, Track2, Video] stsc - Sample To Chunk: (Binary data 20 bytes) +[QuickTime, Track2, Video] stsz - Sample Sizes: (Binary data 12 bytes) +[QuickTime, Track2, Video] free - Unknown free: +[QuickTime, Track2, Video] co64 - Chunk Offset 64: (Binary data 16 bytes) +[QuickTime, Track3, Video] 0 - Track Header Version: 0 +[QuickTime, Track3, Time] 1 - Track Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track3, Time] 2 - Track Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track3, Video] 3 - Track ID: 3 +[QuickTime, Track3, Video] 5 - Track Duration: 1.00 s +[QuickTime, Track3, Video] 8 - Track Layer: 0 +[QuickTime, Track3, Video] 9 - Track Volume: 0.00% +[QuickTime, Track3, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track3, Video] 19 - Image Width: 6288 +[QuickTime, Track3, Video] 20 - Image Height: 4056 +[QuickTime, Track3, Video] 0 - Media Header Version: 0 +[QuickTime, Track3, Time] 1 - Media Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track3, Time] 2 - Media Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track3, Video] 3 - Media Time Scale: 1 +[QuickTime, Track3, Video] 4 - Media Duration: 1.00 s +[QuickTime, Track3, Video] 5 - Media Language Code: eng +[QuickTime, Track3, Video] 8 - Handler Type: Video Track +[QuickTime, Track3, Video] 2 - Graphics Mode: srcCopy +[QuickTime, Track3, Video] 3 - Op Color: 0 0 0 +[QuickTime, Track3, Image] 2 - Compressor ID: CRAW +[QuickTime, Track3, Image] 16 - Source Image Width: 6288 +[QuickTime, Track3, Image] 17 - Source Image Height: 4056 +[QuickTime, Track3, Image] 18 - X Resolution: 72 +[QuickTime, Track3, Image] 20 - Y Resolution: 72 +[QuickTime, Track3, Image] 41 - Bit Depth: 24 +[QuickTime, Track3, Image] free - Unknown free: +[QuickTime, Track3, Video] stts - Video Frame Rate: 1 +[QuickTime, Track3, Video] stsc - Sample To Chunk: (Binary data 20 bytes) +[QuickTime, Track3, Video] stsz - Sample Sizes: (Binary data 12 bytes) +[QuickTime, Track3, Video] free - Unknown free: +[QuickTime, Track3, Video] co64 - Chunk Offset 64: (Binary data 16 bytes) +[QuickTime, Track4, Video] 0 - Track Header Version: 0 +[QuickTime, Track4, Time] 1 - Track Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track4, Time] 2 - Track Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track4, Video] 3 - Track ID: 4 +[QuickTime, Track4, Video] 5 - Track Duration: 1.00 s +[QuickTime, Track4, Video] 8 - Track Layer: 0 +[QuickTime, Track4, Video] 9 - Track Volume: 0.00% +[QuickTime, Track4, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track4, Video] 0 - Media Header Version: 0 +[QuickTime, Track4, Time] 1 - Media Create Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track4, Time] 2 - Media Modify Date: 2018:02:21 07:08:56-05:00 +[QuickTime, Track4, Video] 3 - Media Time Scale: 1 +[QuickTime, Track4, Video] 4 - Media Duration: 1.00 s +[QuickTime, Track4, Video] 5 - Media Language Code: eng +[QuickTime, Track4, Video] 8 - Handler Type: NRT Metadata +[QuickTime, Track4, Video] nmhd - Null Media Header: (Binary data 4 bytes) +[QuickTime, Track4, Other] 4 - Meta Format: CTMD +[QuickTime, Track4, Video] stts - Time To Sample Table: (Binary data 16 bytes) +[QuickTime, Track4, Video] stsc - Sample To Chunk: (Binary data 20 bytes) +[QuickTime, Track4, Video] stsz - Sample Sizes: (Binary data 12 bytes) +[QuickTime, Track4, Video] free - Unknown free: +[QuickTime, Track4, Video] co64 - Chunk Offset 64: (Binary data 16 bytes) +[QuickTime, Track4, Video] SampleTime - Sample Time: 0 s +[QuickTime, Track4, Video] SampleDuration - Sample Duration: 1.00 s +[QuickTime, QuickTime, Preview] uuid - Preview Image: (Binary data 26 bytes) +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 41065 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 12190 +[QuickTime, QuickTime, Video] mdat - Media Data: (Binary data 41065 bytes) +[MakerNotes, Canon, Video] CNCV - Compressor Version: CanonCR3_001/00.09.00/00.00.00 +[MakerNotes, Canon, Video] CCDT - Unknown CCDT: (Binary data 16 bytes) +[MakerNotes, Canon, Video] CCDT - Unknown CCDT: (Binary data 16 bytes) +[MakerNotes, Canon, Video] CCDT - Unknown CCDT: (Binary data 16 bytes) +[MakerNotes, Canon, Video] CTBO - Unknown CTBO: (Binary data 84 bytes) +[MakerNotes, Canon, Video] free - Unknown free: +[MakerNotes, Canon, Camera] 1 - Macro Mode: Normal +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Single +[MakerNotes, Canon, Camera] 7 - Focus Mode: AF + MF +[MakerNotes, Canon, Camera] 9 - Record Mode: CR3+JPEG +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: None +[MakerNotes, Canon, Camera] 13 - Contrast: Normal +[MakerNotes, Canon, Camera] 14 - Saturation: Normal +[MakerNotes, Canon, Camera] 16 - Camera ISO: Auto +[MakerNotes, Canon, Camera] 17 - Metering Mode: Evaluative +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Aperture-priority AE +[MakerNotes, Canon, Camera] 22 - Lens Type: Canon EF-M 15-45mm f/3.5-6.3 IS STM +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 45 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 15 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 3.6 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 23 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: E-TTL +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 0 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 0 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Camera] 46 - SRAW Quality: n/a +[MakerNotes, Canon, Image] 1 - Focal Length: 15 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Unknown: 25518 +[MakerNotes, Canon, Image] 3 - Focal Plane Y Unknown: 18461 +[MakerNotes, Canon, Camera] 3 - Canon Flash Info: 100 0 0 0 +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 12800 +[MakerNotes, Canon, Image] 3 - Measured EV: 2.75 +[MakerNotes, Canon, Image] 4 - Target Aperture: 3.6 +[MakerNotes, Canon, Image] 5 - Target Exposure Time: 1/81 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: -4/3 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Camera] 12 - Camera Temperature: 38 C +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 21 - F Number: 3.6 +[MakerNotes, Canon, Image] 22 - Exposure Time: 1/70 +[MakerNotes, Canon, Image] 23 - Measured EV 2: 1.625 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 0 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS High-end +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 6 - Canon Image Type: Canon EOS M50 +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware Version 1.0.0 +[MakerNotes, Canon, Camera] 9 - Owner Name: +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS M50 / Kiss M +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 6 113 +[MakerNotes, Canon, Camera] 25 - Canon 0x0019: 1 +[MakerNotes, Canon, Camera] 0 - AF Info Size: 1216 +[MakerNotes, Canon, Camera] 1 - AF Area Mode: Face + Tracking +[MakerNotes, Canon, Camera] 2 - Num AF Points: 143 +[MakerNotes, Canon, Camera] 3 - Valid AF Points: 1 +[MakerNotes, Canon, Image] 4 - Canon Image Width: 6000 +[MakerNotes, Canon, Image] 5 - Canon Image Height: 4000 +[MakerNotes, Canon, Camera] 6 - AF Image Width: 6000 +[MakerNotes, Canon, Camera] 7 - AF Image Height: 4000 +[MakerNotes, Canon, Camera] 8 - AF Area Widths: 337 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Canon, Camera] 9 - AF Area Heights: 299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Canon, Camera] 10 - AF Area X Positions: 392 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Canon, Camera] 11 - AF Area Y Positions: -327 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Canon, Camera] 12 - AF Points In Focus: 0 +[MakerNotes, Canon, Camera] 13 - AF Points Selected: 0 +[MakerNotes, Canon, Image] 40 - Image Unique ID: 3b7f679f6bf5d5e1b60ca2b2f051a029 +[MakerNotes, Canon, Camera] 51 - Canon 0x0033: 1574403960 3788849265 2996964534 698372592 +[MakerNotes, Canon, Time] 1 - Time Zone: +00:00 +[MakerNotes, Canon, Time] 2 - Time Zone City: London +[MakerNotes, Canon, Time] 3 - Daylight Savings: Off +[MakerNotes, Canon, Camera] 56 - Battery Type: LP-E12 +[MakerNotes, Canon, Camera] 61 - Canon 0x003d: 48 1 1 0 0 0 0 0 0 0 0 1 +[MakerNotes, Canon, Camera] 63 - Canon 0x003f: 17 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 7 - Raw Jpg Size: Large +[MakerNotes, Canon, Image] 9 - WB Bracket Mode: Off +[MakerNotes, Canon, Image] 12 - WB Bracket Value AB: 0 +[MakerNotes, Canon, Image] 13 - WB Bracket Value GM: 0 +[MakerNotes, Canon, Image] 19 - Live View Shooting: On +[MakerNotes, Canon, Image] 20 - Focus Distance Upper: 0.24 m +[MakerNotes, Canon, Image] 21 - Focus Distance Lower: 0.22 m +[MakerNotes, Canon, Image] 23 - Shutter Mode: Electronic First Curtain +[MakerNotes, Canon, Image] 25 - Flash Exposure Lock: Off +[MakerNotes, Canon, Image] 61 - RF Lens Type: n/a +[MakerNotes, Canon, Camera] 149 - Lens Model: EF-M15-45mm f/3.5-6.3 IS STM +[MakerNotes, Canon, Camera] 150 - Internal Serial Number: CG0156580 +[MakerNotes, Canon, Camera] 151 - Dust Removal Data: (Binary data 1024 bytes) +[MakerNotes, Canon, Camera] 0 - Crop Left Margin: 0 +[MakerNotes, Canon, Camera] 1 - Crop Right Margin: 0 +[MakerNotes, Canon, Camera] 2 - Crop Top Margin: 0 +[MakerNotes, Canon, Camera] 3 - Crop Bottom Margin: 0 +[MakerNotes, CanonCustom, Camera] 259 - ISO Expansion: Off +[MakerNotes, CanonCustom, Camera] 264 - Safety Shift: Disable +[MakerNotes, CanonCustom, Camera] 1809 - Shutter Release Without Lens: Disable +[MakerNotes, CanonCustom, Camera] 2068 - Retract Lens On Power Off: Enable +[MakerNotes, CanonCustom, Camera] 1804 - Custom Controls: 0 19 3 10 23 73 71 61 29 0 7 3 4 +[MakerNotes, Canon, Camera] 0 - Aspect Ratio: 3:2 +[MakerNotes, Canon, Camera] 1 - Cropped Image Width: 6000 +[MakerNotes, Canon, Camera] 2 - Cropped Image Height: 4000 +[MakerNotes, Canon, Camera] 3 - Cropped Image Left: 0 +[MakerNotes, Canon, Camera] 4 - Cropped Image Top: 0 +[MakerNotes, Canon, Image] 1 - Tone Curve: Standard +[MakerNotes, Canon, Image] 2 - Sharpness: 4 +[MakerNotes, Canon, Image] 3 - Sharpness Frequency: n/a +[MakerNotes, Canon, Image] 4 - Sensor Red Level: 0 +[MakerNotes, Canon, Image] 5 - Sensor Blue Level: 0 +[MakerNotes, Canon, Image] 6 - White Balance Red: 0 +[MakerNotes, Canon, Image] 7 - White Balance Blue: 0 +[MakerNotes, Canon, Image] 9 - Color Temperature: 5200 +[MakerNotes, Canon, Image] 10 - Picture Style: Auto +[MakerNotes, Canon, Image] 11 - Digital Gain: 0 +[MakerNotes, Canon, Image] 12 - WB Shift AB: 0 +[MakerNotes, Canon, Image] 13 - WB Shift GM: 0 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 1183 1024 1024 461 +[MakerNotes, Canon, Camera] 180 - Color Space: sRGB +[MakerNotes, Canon, Camera] 208 - VRD Offset: 0 +[MakerNotes, Canon, Image] 1 - Sensor Width: 6288 +[MakerNotes, Canon, Image] 2 - Sensor Height: 4056 +[MakerNotes, Canon, Image] 5 - Sensor Left Border: 276 +[MakerNotes, Canon, Image] 6 - Sensor Top Border: 48 +[MakerNotes, Canon, Image] 7 - Sensor Right Border: 6275 +[MakerNotes, Canon, Image] 8 - Sensor Bottom Border: 4047 +[MakerNotes, Canon, Image] 9 - Black Mask Left Border: 0 +[MakerNotes, Canon, Image] 10 - Black Mask Top Border: 0 +[MakerNotes, Canon, Image] 11 - Black Mask Right Border: 0 +[MakerNotes, Canon, Image] 12 - Black Mask Bottom Border: 0 +[MakerNotes, Canon, Camera] 16392 - Picture Style User Def: Auto; Auto; Auto +[MakerNotes, Canon, Camera] 16393 - Picture Style PC: None; None; None +[MakerNotes, Canon, Camera] 16400 - Custom Picture Style File Name: +[MakerNotes, Canon, Camera] 16401 - Canon 0x4011: [...] +[MakerNotes, Canon, Camera] 16402 - Canon 0x4012: +[MakerNotes, Canon, Camera] 1 - AF Micro Adj Mode: Disable +[MakerNotes, Canon, Camera] 2 - AF Micro Adj Value: 0 +[MakerNotes, Canon, Camera] 5 - Peripheral Lighting Setting: On +[MakerNotes, Canon, Camera] 6 - Chromatic Aberration Setting: On +[MakerNotes, Canon, Camera] 7 - Distortion Correction Setting: Off +[MakerNotes, Canon, Camera] 9 - Digital Lens Optimizer Setting: Off +[MakerNotes, Canon, Camera] 1 - Peripheral Illumination Corr: Off +[MakerNotes, Canon, Camera] 2 - Auto Lighting Optimizer: Standard +[MakerNotes, Canon, Camera] 3 - Highlight Tone Priority: Off +[MakerNotes, Canon, Camera] 4 - Long Exposure Noise Reduction: Off +[MakerNotes, Canon, Camera] 5 - High ISO Noise Reduction: Standard +[MakerNotes, Canon, Camera] 10 - Digital Lens Optimizer: Off +[MakerNotes, Canon, Camera] 1 - Ambience Selection: Standard +[MakerNotes, Canon, Camera] 257 - Grainy B/W Filter: Off +[MakerNotes, Canon, Camera] 513 - Soft Focus Filter: Off +[MakerNotes, Canon, Camera] 769 - Toy Camera Filter: Off +[MakerNotes, Canon, Camera] 1025 - Miniature Filter: Off +[MakerNotes, Canon, Camera] 1026 - Miniature Filter Orientation: Horizontal +[MakerNotes, Canon, Camera] 1027 - Miniature Filter Position: 0 +[MakerNotes, Canon, Camera] 1028 - Miniature Filter Parameter: 0 +[MakerNotes, Canon, Camera] 1281 - Fisheye Filter: Off +[MakerNotes, Canon, Camera] 1537 - Painting Filter: Off +[MakerNotes, Canon, Camera] 1793 - Watercolor Filter: Off +[MakerNotes, Canon, Image] 1 - HDR: Off +[MakerNotes, Canon, Image] 2 - HDR Effect: Natural +[MakerNotes, Canon, Camera] 16423 - Canon 0x4027: 24 393984 872415232 0 13684944 131072 +[MakerNotes, Canon, Camera] 16428 - Canon 0x402c: 8 0 +[MakerNotes, Canon, Camera] 16437 - Canon 0x4035: -.‘.;.¥.¨...ådŸÂ"´.0.G.A.Í_7,;¤ N.é;À.:.w9$[...] +[MakerNotes, Canon, Camera] 16439 - Canon 0x4037: €.ª...ô..); +[MakerNotes, Canon, Camera] 16441 - Canon 0x4039: 59 127 103 159 107 245 213 225 182 12 162 178 240 81 160 41 +[MakerNotes, Canon, Camera] 16444 - Canon 0x403c: 12 1 24 +[MakerNotes, Canon, Preview] THMB - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Track2, Image] 8 - Image Width: 1624 +[MakerNotes, Track2, Image] 10 - Image Height: 1080 +[MakerNotes, Track3, Image] 8 - Image Width: 3144 +[MakerNotes, Track3, Image] 10 - Image Height: 4056 +[MakerNotes, Track4, Time] 1 - Time Stamp: 2018:02:21 12:08:56.21 +[MakerNotes, Track4, Image] 0 - Focal Length: 15.0 mm +[MakerNotes, Track4, Image] 0 - F Number: 3.5 +[MakerNotes, Track4, Image] 1 - Exposure Time: 1/80 +[MakerNotes, Track4, Image] 2 - ISO: 12800 +[MakerNotes, Track4, Camera] 1 - Macro Mode: Normal +[MakerNotes, Track4, Camera] 2 - Self Timer: Off +[MakerNotes, Track4, Camera] 3 - Quality: RAW +[MakerNotes, Track4, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Track4, Camera] 5 - Continuous Drive: Single +[MakerNotes, Track4, Camera] 7 - Focus Mode: AF + MF +[MakerNotes, Track4, Camera] 9 - Record Mode: CR3+JPEG +[MakerNotes, Track4, Camera] 10 - Canon Image Size: Large +[MakerNotes, Track4, Camera] 11 - Easy Mode: Manual +[MakerNotes, Track4, Camera] 12 - Digital Zoom: None +[MakerNotes, Track4, Camera] 13 - Contrast: Normal +[MakerNotes, Track4, Camera] 14 - Saturation: Normal +[MakerNotes, Track4, Camera] 16 - Camera ISO: Auto +[MakerNotes, Track4, Camera] 17 - Metering Mode: Evaluative +[MakerNotes, Track4, Camera] 18 - Focus Range: Not Known +[MakerNotes, Track4, Camera] 20 - Canon Exposure Mode: Aperture-priority AE +[MakerNotes, Track4, Camera] 22 - Lens Type: Canon EF-M 15-45mm f/3.5-6.3 IS STM +[MakerNotes, Track4, Camera] 23 - Max Focal Length: 45 mm +[MakerNotes, Track4, Camera] 24 - Min Focal Length: 15 mm +[MakerNotes, Track4, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Track4, Camera] 26 - Max Aperture: 3.6 +[MakerNotes, Track4, Camera] 27 - Min Aperture: 23 +[MakerNotes, Track4, Camera] 28 - Flash Activity: 0 +[MakerNotes, Track4, Camera] 29 - Flash Bits: E-TTL +[MakerNotes, Track4, Camera] 36 - Zoom Source Width: 0 +[MakerNotes, Track4, Camera] 37 - Zoom Target Width: 0 +[MakerNotes, Track4, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Track4, Camera] 42 - Color Tone: Normal +[MakerNotes, Track4, Camera] 46 - SRAW Quality: n/a +[MakerNotes, Track4, Image] 1 - Auto ISO: 100 +[MakerNotes, Track4, Image] 2 - Base ISO: 12800 +[MakerNotes, Track4, Image] 3 - Measured EV: 2.75 +[MakerNotes, Track4, Image] 4 - Target Aperture: 3.6 +[MakerNotes, Track4, Image] 5 - Target Exposure Time: 1/81 +[MakerNotes, Track4, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Track4, Image] 7 - White Balance: Auto +[MakerNotes, Track4, Image] 8 - Slow Shutter: None +[MakerNotes, Track4, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Track4, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Track4, Camera] 12 - Camera Temperature: 38 C +[MakerNotes, Track4, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Track4, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Track4, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Track4, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Track4, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Track4, Image] 21 - F Number: 3.6 +[MakerNotes, Track4, Image] 22 - Exposure Time: 1/70 +[MakerNotes, Track4, Image] 23 - Measured EV 2: 1.625 +[MakerNotes, Track4, Image] 24 - Bulb Duration: 0 +[MakerNotes, Track4, Camera] 26 - Camera Type: EOS High-end +[MakerNotes, Track4, Image] 27 - Auto Rotate: None +[MakerNotes, Track4, Image] 28 - ND Filter: n/a +[MakerNotes, Track4, Camera] 0 - AF Info Size: 1216 +[MakerNotes, Track4, Camera] 1 - AF Area Mode: Face + Tracking +[MakerNotes, Track4, Camera] 2 - Num AF Points: 143 +[MakerNotes, Track4, Camera] 3 - Valid AF Points: 1 +[MakerNotes, Track4, Image] 4 - Canon Image Width: 6000 +[MakerNotes, Track4, Image] 5 - Canon Image Height: 4000 +[MakerNotes, Track4, Camera] 6 - AF Image Width: 6000 +[MakerNotes, Track4, Camera] 7 - AF Image Height: 4000 +[MakerNotes, Track4, Camera] 8 - AF Area Widths: 337 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Track4, Camera] 9 - AF Area Heights: 299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Track4, Camera] 10 - AF Area X Positions: 392 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Track4, Camera] 11 - AF Area Y Positions: -327 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Track4, Camera] 12 - AF Points In Focus: 0 +[MakerNotes, Track4, Camera] 13 - AF Points Selected: 0 +[MakerNotes, Track4, Image] 3 - Bracket Mode: Off +[MakerNotes, Track4, Image] 4 - Bracket Value: 0 +[MakerNotes, Track4, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Track4, Image] 7 - Raw Jpg Size: Large +[MakerNotes, Track4, Image] 9 - WB Bracket Mode: Off +[MakerNotes, Track4, Image] 12 - WB Bracket Value AB: 0 +[MakerNotes, Track4, Image] 13 - WB Bracket Value GM: 0 +[MakerNotes, Track4, Image] 19 - Live View Shooting: On +[MakerNotes, Track4, Image] 20 - Focus Distance Upper: 0.24 m +[MakerNotes, Track4, Image] 21 - Focus Distance Lower: 0.22 m +[MakerNotes, Track4, Image] 23 - Shutter Mode: Electronic First Curtain +[MakerNotes, Track4, Image] 25 - Flash Exposure Lock: Off +[MakerNotes, Track4, Image] 61 - RF Lens Type: n/a +[MakerNotes, Track4, Image] 1 - Tone Curve: Standard +[MakerNotes, Track4, Image] 2 - Sharpness: 4 +[MakerNotes, Track4, Image] 3 - Sharpness Frequency: n/a +[MakerNotes, Track4, Image] 4 - Sensor Red Level: 0 +[MakerNotes, Track4, Image] 5 - Sensor Blue Level: 0 +[MakerNotes, Track4, Image] 6 - White Balance Red: 0 +[MakerNotes, Track4, Image] 7 - White Balance Blue: 0 +[MakerNotes, Track4, Image] 9 - Color Temperature: 5200 +[MakerNotes, Track4, Image] 10 - Picture Style: Auto +[MakerNotes, Track4, Image] 11 - Digital Gain: 0 +[MakerNotes, Track4, Image] 12 - WB Shift AB: 0 +[MakerNotes, Track4, Image] 13 - WB Shift GM: 0 +[MakerNotes, Track4, Camera] 5 - Peripheral Lighting Setting: On +[MakerNotes, Track4, Camera] 6 - Chromatic Aberration Setting: On +[MakerNotes, Track4, Camera] 7 - Distortion Correction Setting: Off +[MakerNotes, Track4, Camera] 9 - Digital Lens Optimizer Setting: Off +[MakerNotes, Track4, Camera] 0 - Color Data Version: 16 (M50) +[MakerNotes, Track4, Camera] 71 - WB RGGB Levels As Shot: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 75 - Color Temp As Shot: 3284 +[MakerNotes, Track4, Camera] 76 - WB RGGB Levels Auto: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 80 - Color Temp Auto: 3284 +[MakerNotes, Track4, Camera] 81 - WB RGGB Levels Measured: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 85 - Color Temp Measured: 3284 +[MakerNotes, Track4, Camera] 86 - WB RGGB Levels Unknown: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 90 - Color Temp Unknown: 3284 +[MakerNotes, Track4, Camera] 91 - WB RGGB Levels Unknown 2: 1111 1024 1024 1786 +[MakerNotes, Track4, Camera] 95 - Color Temp Unknown 2: 3303 +[MakerNotes, Track4, Camera] 96 - WB RGGB Levels Unknown 3: 1111 1024 1024 1780 +[MakerNotes, Track4, Camera] 100 - Color Temp Unknown 3: 3307 +[MakerNotes, Track4, Camera] 101 - WB RGGB Levels Unknown 4: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 105 - Color Temp Unknown 4: 3284 +[MakerNotes, Track4, Camera] 106 - WB RGGB Levels Unknown 5: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 110 - Color Temp Unknown 5: 3284 +[MakerNotes, Track4, Camera] 111 - WB RGGB Levels Unknown 6: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 115 - Color Temp Unknown 6: 3284 +[MakerNotes, Track4, Camera] 116 - WB RGGB Levels Unknown 7: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 120 - Color Temp Unknown 7: 3284 +[MakerNotes, Track4, Camera] 121 - WB RGGB Levels Unknown 8: 1104 1024 1024 1789 +[MakerNotes, Track4, Camera] 125 - Color Temp Unknown 8: 3284 +[MakerNotes, Track4, Camera] 126 - WB RGGB Levels Unknown 9: 962 1024 1022 2009 +[MakerNotes, Track4, Camera] 130 - Color Temp Unknown 9: 2831 +[MakerNotes, Track4, Camera] 131 - WB RGGB Levels Unknown 10: 962 1024 1022 2009 +[MakerNotes, Track4, Camera] 135 - Color Temp Unknown 10: 2831 +[MakerNotes, Track4, Camera] 136 - WB RGGB Levels Daylight: 1549 1024 1024 1316 +[MakerNotes, Track4, Camera] 140 - Color Temp Daylight: 5200 +[MakerNotes, Track4, Camera] 141 - WB RGGB Levels Shade: 1792 1024 1024 1147 +[MakerNotes, Track4, Camera] 145 - Color Temp Shade: 7000 +[MakerNotes, Track4, Camera] 146 - WB RGGB Levels Cloudy: 1670 1024 1024 1229 +[MakerNotes, Track4, Camera] 150 - Color Temp Cloudy: 6000 +[MakerNotes, Track4, Camera] 151 - WB RGGB Levels Tungsten: 1104 1024 1024 1879 +[MakerNotes, Track4, Camera] 155 - Color Temp Tungsten: 3200 +[MakerNotes, Track4, Camera] 156 - WB RGGB Levels Fluorescent: 1303 1024 1024 1771 +[MakerNotes, Track4, Camera] 160 - Color Temp Fluorescent: 3687 +[MakerNotes, Track4, Camera] 161 - WB RGGB Levels Kelvin: 1549 1024 1024 1316 +[MakerNotes, Track4, Camera] 165 - Color Temp Kelvin: 5200 +[MakerNotes, Track4, Camera] 166 - WB RGGB Levels Flash: 1697 1024 1024 1218 +[MakerNotes, Track4, Camera] 170 - Color Temp Flash: 6166 +[MakerNotes, Track4, Camera] 171 - WB RGGB Levels Unknown 11: 1547 1024 1024 1316 +[MakerNotes, Track4, Camera] 175 - Color Temp Unknown 11: 5190 +[MakerNotes, Track4, Camera] 176 - WB RGGB Levels Unknown 12: 8191 1024 1024 8191 +[MakerNotes, Track4, Camera] 180 - Color Temp Unknown 12: 4441 +[MakerNotes, Track4, Camera] 181 - WB RGGB Levels Unknown 13: 8191 1024 1024 8191 +[MakerNotes, Track4, Camera] 185 - Color Temp Unknown 13: 4441 +[MakerNotes, Track4, Camera] 186 - WB RGGB Levels Unknown 14: 8191 1024 1024 8191 +[MakerNotes, Track4, Camera] 190 - Color Temp Unknown 14: 4441 +[MakerNotes, Track4, Camera] 191 - WB RGGB Levels Unknown 15: 8191 1024 1024 8191 +[MakerNotes, Track4, Camera] 195 - Color Temp Unknown 15: 4441 +[MakerNotes, Track4, Camera] 196 - WB RGGB Levels Unknown 16: 1547 1024 1024 1316 +[MakerNotes, Track4, Camera] 200 - Color Temp Unknown 16: 5190 +[MakerNotes, Track4, Camera] 201 - WB RGGB Levels Unknown 17: 8191 1024 1024 8191 +[MakerNotes, Track4, Camera] 205 - Color Temp Unknown 17: 4441 +[MakerNotes, Track4, Camera] 206 - WB RGGB Levels Unknown 18: 8191 1024 1024 8191 +[MakerNotes, Track4, Camera] 210 - Color Temp Unknown 18: 4441 +[MakerNotes, Track4, Camera] 211 - WB RGGB Levels Unknown 19: 8191 1024 1024 8191 +[MakerNotes, Track4, Camera] 215 - Color Temp Unknown 19: 4441 +[MakerNotes, Track4, Camera] 216 - WB RGGB Levels Unknown 20: 8191 1024 1024 8191 +[MakerNotes, Track4, Camera] 220 - Color Temp Unknown 20: 4441 +[MakerNotes, Track4, Camera] 221 - WB RGGB Levels Unknown 21: 967 1024 1024 2081 +[MakerNotes, Track4, Camera] 225 - Color Temp Unknown 21: 2792 +[MakerNotes, Track4, Camera] 226 - WB RGGB Levels Unknown 22: 967 1024 1024 2081 +[MakerNotes, Track4, Camera] 230 - Color Temp Unknown 22: 2792 +[MakerNotes, Track4, Camera] 231 - WB RGGB Levels Unknown 23: 967 1024 1024 2081 +[MakerNotes, Track4, Camera] 235 - Color Temp Unknown 23: 2792 +[MakerNotes, Track4, Camera] 236 - WB RGGB Levels Unknown 24: 968 1024 1024 2048 +[MakerNotes, Track4, Camera] 240 - Color Temp Unknown 24: 2815 +[MakerNotes, Track4, Camera] 241 - WB RGGB Levels Unknown 25: 967 1024 1024 2044 +[MakerNotes, Track4, Camera] 245 - Color Temp Unknown 25: 2815 +[MakerNotes, Track4, Camera] 246 - WB RGGB Levels Unknown 26: 967 1024 1024 2081 +[MakerNotes, Track4, Camera] 250 - Color Temp Unknown 26: 2792 +[MakerNotes, Track4, Camera] 251 - WB RGGB Levels Unknown 27: 967 1024 1024 2081 +[MakerNotes, Track4, Camera] 255 - Color Temp Unknown 27: 2792 +[MakerNotes, Track4, Camera] 256 - WB RGGB Levels Unknown 28: 958 1024 1024 2106 +[MakerNotes, Track4, Camera] 260 - Color Temp Unknown 28: 2759 +[MakerNotes, Track4, Camera] 261 - WB RGGB Levels Unknown 29: 906 1024 1022 2180 +[MakerNotes, Track4, Camera] 265 - Color Temp Unknown 29: 2618 +[MakerNotes, Track4, Camera] 0 - Camera Color Calibration 01: -311 513 1064 (10900K) +[MakerNotes, Track4, Camera] 4 - Camera Color Calibration 02: -292 525 1039 (10000K) +[MakerNotes, Track4, Camera] 8 - Camera Color Calibration 03: -245 551 978 (8300K) +[MakerNotes, Track4, Camera] 12 - Camera Color Calibration 04: -191 585 914 (7000K) +[MakerNotes, Track4, Camera] 16 - Camera Color Calibration 05: -131 628 853 (6000K) +[MakerNotes, Track4, Camera] 20 - Camera Color Calibration 06: -102 649 825 (5600K) +[MakerNotes, Track4, Camera] 24 - Camera Color Calibration 07: -70 677 797 (5200K) +[MakerNotes, Track4, Camera] 28 - Camera Color Calibration 08: -18 717 747 (4700K) +[MakerNotes, Track4, Camera] 32 - Camera Color Calibration 09: 44 766 691 (4200K) +[MakerNotes, Track4, Camera] 36 - Camera Color Calibration 10: 105 821 640 (3800K) +[MakerNotes, Track4, Camera] 40 - Camera Color Calibration 11: 162 879 601 (3500K) +[MakerNotes, Track4, Camera] 44 - Camera Color Calibration 12: 226 950 558 (3200K) +[MakerNotes, Track4, Camera] 48 - Camera Color Calibration 13: 275 1007 526 (3000K) +[MakerNotes, Track4, Camera] 52 - Camera Color Calibration 14: 325 1075 499 (2800K) +[MakerNotes, Track4, Camera] 56 - Camera Color Calibration 15: 435 1251 453 (2400K) +[MakerNotes, Track4, Camera] 329 - Per Channel Black Level: 2049 2045 2045 2050 +[MakerNotes, Track4, Camera] 796 - Normal White Level: 13035 +[MakerNotes, Track4, Camera] 797 - Specular White Level: 14338 +[MakerNotes, Track4, Camera] 798 - Linearity Upper Margin: 10025 +[MakerNotes, Track4, Camera] 16389 - Flavor: (Binary data 32476 bytes) +[MakerNotes, Track4, Camera] 1473 - Firmware Version: 1.0.0 +[MakerNotes, Track4, Camera] 0 - Vignetting Corr Version: 32 +[MakerNotes, Track4, Camera] 16435 - Canon 0x4033: [...] +[MakerNotes, Track4, Camera] 16430 - Canon 0x402e: ."..@.þÿ.þñ?...,I“À[...] +[EXIF, IFD0, Image] 256 - Image Width: 6000 +[EXIF, IFD0, Image] 257 - Image Height: 4000 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS M50 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2018:02:21 12:08:56 +[EXIF, IFD0, Author] 315 - Artist: +[EXIF, IFD0, Author] 33432 - Copyright: +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/80 +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 12800 +[EXIF, ExifIFD, Image] 34864 - Sensitivity Type: Recommended Exposure Index +[EXIF, ExifIFD, Image] 34866 - Recommended Exposure Index: 12800 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0231 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2018:02:21 12:08:56 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2018:02:21 12:08:56 +[EXIF, ExifIFD, Time] 36880 - Offset Time: +00:00 +[EXIF, ExifIFD, Time] 36881 - Offset Time Original: +00:00 +[EXIF, ExifIFD, Time] 36882 - Offset Time Digitized: +00:00 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/83 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: -1.3 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 15.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Time] 37520 - Sub Sec Time: 21 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 21 +[EXIF, ExifIFD, Time] 37522 - Sub Sec Time Digitized: 21 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 6000 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 4000 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 6514.65798 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 6734.006734 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Image] 42032 - Owner Name: +[EXIF, ExifIFD, Image] 42033 - Serial Number: 613040000565 +[EXIF, ExifIFD, Image] 42034 - Lens Info: 15-45mm f/0 +[EXIF, ExifIFD, Image] 42036 - Lens Model: EF-M15-45mm f/3.5-6.3 IS STM +[EXIF, ExifIFD, Image] 42037 - Lens Serial Number: 0000000000 +[EXIF, GPS, Location] 0 - GPS Version ID: 2.3.0.0 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.53 +[XMP, XMP-dc, Image] subject - Subject: CR3 test +[XMP, XMP-exif, Image] ExposureBiasValue - Exposure Compensation: -1.3 +[XMP, XMP-xmp, Image] Rating - Rating: 0 +[Composite, Composite, Camera] Canon-ConditionalFEC - Flash Exposure Compensation: 0 +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Single-frame Shooting +[Composite, Composite, Camera] Canon-FlashType - Flash Type: Built-In Flash +[Composite, Composite, Camera] Canon-ISO - ISO: 12800 +[Composite, Composite, Camera] Canon-Lens - Lens: 15.0 - 45.0 mm +[Composite, Composite, Camera] Canon-RedEyeReduction - Red Eye Reduction: Off +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Aperture-priority AE +[Composite, Composite, Camera] Canon-ShutterCurtainHack - Shutter Curtain Sync: 1st-curtain sync +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 1104 1024 1024 1789 +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/80 +[Composite, Composite, Time] Exif-SubSecCreateDate - Create Date: 2018:02:21 12:08:56.21+00:00 +[Composite, Composite, Time] Exif-SubSecDateTimeOriginal - Date/Time Original: 2018:02:21 12:08:56.21+00:00 +[Composite, Composite, Time] Exif-SubSecModifyDate - Modify Date: 2018:02:21 12:08:56.21+00:00 +[Composite, Composite, Video] QuickTime-AvgBitrate - Avg Bitrate: 329 kbps +[Composite, Composite, Video] QuickTime-Rotation - Rotation: 0 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.74707 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 6000x4000 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Canon EF-M 15-45mm f/3.5-6.3 IS STM +[Composite, Composite, Image] Exif-LightValue - Light Value: 2.9 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 24.0 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.078125 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 15.0 - 45.0 mm (35 mm equivalent: 23.3 - 69.9 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.03 m (0.22 - 0.25 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 75.3 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 15.0 mm (35 mm equivalent: 23.3 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 3.33 m diff --git a/ExifTool/t/CanonVRD.t b/ExifTool/t/CanonVRD.t new file mode 100644 index 0000000..eb02904 --- /dev/null +++ b/ExifTool/t/CanonVRD.t @@ -0,0 +1,232 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/CanonVRD.t". + +BEGIN { + $| = 1; print "1..24\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::CanonVRD; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'CanonVRD'; +my $testnum = 1; + +# short list of tags to check in tests +my @checkTags = qw(FileSize Warning VRDVersion VRDOffset); +my @checkDR4 = qw(FileSize Warning GammaBlackPoint RedHSL GreenHSL SharpnessAdjOn); + +# test 2: Extract information from CanonVRD.vrd +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/CanonVRD.vrd'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Test writing some information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/ExifTool.jpg'); + $exifTool->SetNewValue('xmp:*'); + my $testfile = "t/${testname}_${testnum}_failed.vrd"; + unlink $testfile; + $exifTool->WriteInfo('t/images/CanonVRD.vrd', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 4-8: Write CanonVRD as a block to various images +{ + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/CanonVRD.vrd', 'CanonVRD'); + $exifTool->Options(PrintConv => 0); + my ($file, $ext); + foreach $file (qw(Writer.jpg ExifTool.jpg CanonRaw.cr2 CanonRaw.crw CanonVRD.vrd)) { + ++$testnum; + if ($file eq 'CanonVRD.vrd') { + $exifTool->SetNewValuesFromFile('t/images/ExifTool.jpg', 'CanonVRD'); + } + ($ext = $file) =~ s/^\w+//; + my $testfile = "t/${testname}_${testnum}_failed$ext"; + unlink $testfile; + $exifTool->WriteInfo("t/images/$file", $testfile); + my $info = $exifTool->ImageInfo($testfile, @checkTags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + } +} + +# test 9: Delete VRD as a block +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue(CanonVRD => undef, Protected => 1); + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->WriteInfo('t/images/ExifTool.jpg', $testfile); + $exifTool->Options(PrintConv => 0); + my $info = $exifTool->ImageInfo($testfile, @checkTags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 10: Create a VRD file from scratch +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/ExifTool.jpg', 'CanonVRD'); + $exifTool->Options(PrintConv => 0); + my $testfile = "t/${testname}_${testnum}_failed.vrd"; + unlink $testfile; + $exifTool->WriteInfo(undef, $testfile); + my $info = $exifTool->ImageInfo($testfile, @checkTags); + if (check($exifTool, $info, $testname, $testnum, 8)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 11-12: Add XMP to a VRD file +{ + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('XMP:Title', 'XMP in VRD test'); + my $srcfile; + foreach $srcfile ('t/images/CanonVRD.vrd', undef) { + ++$testnum; + my $testfile = "t/${testname}_${testnum}_failed.vrd"; + unlink $testfile; + $exifTool->WriteInfo($srcfile, $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + } +} + +# test 13: Extract information from CanonVRD.dr4 +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/CanonVRD.dr4'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 14: Test writing to DR4 +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(PrintConv => 0); + $exifTool->SetNewValue(CropX => 123); + $exifTool->SetNewValue(SharpnessAdjOn => 0); + $exifTool->SetNewValue(RedHSL => '-4.3 1.2 3.8'); + $exifTool->SetNewValue('CanonVRD:GammaBlackPoint' => '1.234'); + my $testfile = "t/${testname}_${testnum}_failed.dr4"; + unlink $testfile; + $exifTool->WriteInfo('t/images/CanonVRD.dr4', $testfile); + my $info = $exifTool->ImageInfo($testfile, '-filename'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# tests 15-21: Write CanonDR4 as a block to various images +{ + my $exifTool = Image::ExifTool->new; + my $srcfile = "t/${testname}_14_failed.dr4"; + $exifTool->SetNewValuesFromFile($srcfile, 'CanonDR4'); + $exifTool->Options(PrintConv => 0); + my ($file, $ext); + foreach $file (qw(Writer.jpg ExifTool.jpg CanonRaw.cr2 CanonRaw.crw CanonVRD.vrd CanonVRD.dr4 CanonRaw.cr3)) { + ++$testnum; + ($ext = $file) =~ s/^\w+//; + my $testfile = "t/${testname}_${testnum}_failed$ext"; + unlink $testfile; + $exifTool->WriteInfo("t/images/$file", $testfile); + my $info = $exifTool->ImageInfo($testfile, @checkDR4); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile unless $testnum == 15 or $testnum == 17 or $testnum == 21; + unlink $srcfile if $testnum == 20; + } else { + notOK(); + } + print "ok $testnum\n"; + } +} + +# test 22: Delete DR4(VRD) as a block +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $srcfile = "t/${testname}_15_failed.jpg"; + $exifTool->SetNewValue(CanonDR4 => undef, Protected => 1); + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->WriteInfo($srcfile, $testfile); + $exifTool->Options(PrintConv => 0); + my $info = $exifTool->ImageInfo($testfile, @checkDR4); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + unlink $srcfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 23: Create a DR4 file from scratch +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(PrintConv => 0); + my $srcfile = "t/${testname}_17_failed.cr2"; + $exifTool->SetNewValuesFromFile($srcfile, 'CanonDR4'); + my $testfile = "t/${testname}_${testnum}_failed.dr4"; + unlink $testfile; + $exifTool->WriteInfo(undef, $testfile); + my $info = $exifTool->ImageInfo($testfile, '-filename'); + if (check($exifTool, $info, $testname, $testnum, 14)) { + unlink $testfile; + unlink $srcfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 24: Edit DR4 information in CR3 image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my @writeInfo = ( ['CanonVRD:GammaBlackPoint' => 1.5] ); + my $srcfile = "t/${testname}_21_failed.cr3"; + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, $srcfile, \@checkDR4); + print "ok $testnum\n"; + unlink $srcfile; +} + +done(); # end diff --git a/ExifTool/t/CanonVRD_11.out b/ExifTool/t/CanonVRD_11.out new file mode 100644 index 0000000..b802164 --- /dev/null +++ b/ExifTool/t/CanonVRD_11.out @@ -0,0 +1,121 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: CanonVRD_11_failed.vrd +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 4.6 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:33-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:33-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:33-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: VRD +[File, File, Other] FileTypeExtension - File Type Extension: vrd +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 2.0.0 +[CanonVRD, CanonVRD, Image] 6 - WB Adj RGGB Levels: 1856 832 832 928 +[CanonVRD, CanonVRD, Image] 24 - White Balance Adj: Manual (Click) +[CanonVRD, CanonVRD, Image] 26 - WB Adj Color Temp: 5600 +[CanonVRD, CanonVRD, Image] 36 - WB Fine Tune Active: Yes +[CanonVRD, CanonVRD, Image] 40 - WB Fine Tune Saturation: 0 +[CanonVRD, CanonVRD, Image] 44 - WB Fine Tune Tone: 358 +[CanonVRD, CanonVRD, Image] 46 - Raw Color Adj: Shot Settings +[CanonVRD, CanonVRD, Image] 48 - Raw Custom Saturation: 0 +[CanonVRD, CanonVRD, Image] 52 - Raw Custom Tone: 0 +[CanonVRD, CanonVRD, Image] 56 - Raw Brightness Adj: -0.83 +[CanonVRD, CanonVRD, Image] 60 - Tone Curve Property: Shot Settings +[CanonVRD, CanonVRD, Image] 122 - Dynamic Range Min: 0 +[CanonVRD, CanonVRD, Image] 124 - Dynamic Range Max: 4095 +[CanonVRD, CanonVRD, Image] 272 - Tone Curve Active: Yes +[CanonVRD, CanonVRD, Image] 275 - Tone Curve Mode: RGB +[CanonVRD, CanonVRD, Image] 276 - Brightness Adj: 0 +[CanonVRD, CanonVRD, Image] 277 - Contrast Adj: 0 +[CanonVRD, CanonVRD, Image] 278 - Saturation Adj: 119 +[CanonVRD, CanonVRD, Image] 286 - Color Tone Adj: 0 +[CanonVRD, CanonVRD, Image] 294 - Luminance Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 336 - Luminance Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 345 - Tone Curve Interpolation: Curve +[CanonVRD, CanonVRD, Image] 352 - Red Curve Points: (0,0) (5,16) (11,30) (58,101) (106,154) (154,198) (202,233) (228,246) (255,255) +[CanonVRD, CanonVRD, Image] 394 - Red Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 410 - Green Curve Points: (7,4) (11,23) (16,34) (59,100) (104,147) (149,187) (196,219) (221,232) (248,240) +[CanonVRD, CanonVRD, Image] 452 - Green Curve Limits: 248 7 240 4 +[CanonVRD, CanonVRD, Image] 468 - Blue Curve Points: (0,0) (5,19) (10,32) (57,104) (104,156) (152,199) (200,233) (227,247) (255,255) +[CanonVRD, CanonVRD, Image] 510 - Blue Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 526 - RGB Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 568 - RGB Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 580 - Crop Active: Yes +[CanonVRD, CanonVRD, Image] 582 - Crop Left: 1164 +[CanonVRD, CanonVRD, Image] 584 - Crop Top: 608 +[CanonVRD, CanonVRD, Image] 586 - Crop Width: 564 +[CanonVRD, CanonVRD, Image] 588 - Crop Height: 123 +[CanonVRD, CanonVRD, Image] 602 - Sharpness Adj: 0 +[CanonVRD, CanonVRD, Image] 608 - Crop Aspect Ratio: Free +[CanonVRD, CanonVRD, Image] 610 - Constrained Crop Width: 5.5 +[CanonVRD, CanonVRD, Image] 614 - Constrained Crop Height: 1.2 +[CanonVRD, CanonVRD, Image] 618 - Check Mark: 3 +[CanonVRD, CanonVRD, Image] 622 - Rotation: 0 +[CanonVRD, CanonVRD, Image] 624 - Work Color Space: ColorMatch RGB +[CanonVRD, CanonVRD, Image] 2 - Picture Style: Standard +[CanonVRD, CanonVRD, Image] 3 - Is Custom Picture Style: No +[CanonVRD, CanonVRD, Image] 13 - Standard Raw Color Tone: -4 +[CanonVRD, CanonVRD, Image] 14 - Standard Raw Saturation: 0 +[CanonVRD, CanonVRD, Image] 15 - Standard Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 16 - Standard Raw Linear: No +[CanonVRD, CanonVRD, Image] 17 - Standard Raw Sharpness: 1 +[CanonVRD, CanonVRD, Image] 18 - Standard Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 19 - Standard Raw Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 20 - Standard Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 21 - Standard Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 22 - Portrait Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 23 - Portrait Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 24 - Portrait Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 25 - Portrait Raw Linear: No +[CanonVRD, CanonVRD, Image] 26 - Portrait Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 27 - Portrait Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 28 - Portrait Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 29 - Portrait Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 30 - Portrait Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 31 - Landscape Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 32 - Landscape Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 33 - Landscape Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 34 - Landscape Raw Linear: No +[CanonVRD, CanonVRD, Image] 35 - Landscape Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 36 - Landscape Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 37 - Landscape Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 38 - Landscape Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 39 - Landscape Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 40 - Neutral Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 41 - Neutral Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 42 - Neutral Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 43 - Neutral Raw Linear: No +[CanonVRD, CanonVRD, Image] 44 - Neutral Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 45 - Neutral Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 46 - Neutral Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 47 - Neutral Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 48 - Neutral Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 49 - Faithful Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 50 - Faithful Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 51 - Faithful Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 52 - Faithful Raw Linear: No +[CanonVRD, CanonVRD, Image] 53 - Faithful Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 54 - Faithful Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 55 - Faithful Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 56 - Faithful Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 57 - Faithful Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 58 - Monochrome Filter Effect: Yellow +[CanonVRD, CanonVRD, Image] 59 - Monochrome Toning Effect: Purple +[CanonVRD, CanonVRD, Image] 60 - Monochrome Contrast: 3 +[CanonVRD, CanonVRD, Image] 61 - Monochrome Linear: No +[CanonVRD, CanonVRD, Image] 62 - Monochrome Sharpness: 4 +[CanonVRD, CanonVRD, Image] 63 - Monochrome Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 64 - Monochrome Raw Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 65 - Monochrome Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 66 - Monochrome Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 76 - Custom Color Tone: 4 +[CanonVRD, CanonVRD, Image] 77 - Custom Saturation: 2 +[CanonVRD, CanonVRD, Image] 78 - Custom Contrast: 0 +[CanonVRD, CanonVRD, Image] 79 - Custom Linear: No +[CanonVRD, CanonVRD, Image] 80 - Custom Sharpness: 7 +[CanonVRD, CanonVRD, Image] 81 - Custom Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 82 - Custom Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 83 - Custom Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 84 - Custom Output Shadow Point: 0 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-dc, Image] title - Title: XMP in VRD test diff --git a/ExifTool/t/CanonVRD_12.out b/ExifTool/t/CanonVRD_12.out new file mode 100644 index 0000000..fed3bd7 --- /dev/null +++ b/ExifTool/t/CanonVRD_12.out @@ -0,0 +1,13 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: CanonVRD_12_failed.vrd +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.0 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:33-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:33-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:33-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: VRD +[File, File, Other] FileTypeExtension - File Type Extension: vrd +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-dc, Image] title - Title: XMP in VRD test diff --git a/ExifTool/t/CanonVRD_13.out b/ExifTool/t/CanonVRD_13.out new file mode 100644 index 0000000..63f75e1 --- /dev/null +++ b/ExifTool/t/CanonVRD_13.out @@ -0,0 +1,102 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.53 +[File, System, Other] FileName - File Name: CanonVRD.dr4 +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 5.5 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2015:05:18 15:43:16-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:12:28 12:46:11-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:18 10:49:22-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: DR4 +[File, File, Other] FileTypeExtension - File Type Extension: dr4 +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[CanonVRD, CanonDR4, Image] 3 - DR4 Camera Model: EOS 7D Mark II +[CanonVRD, CanonDR4, Image] 65538 - Rotation: 0 +[CanonVRD, CanonDR4, Image] 65539 - Angle Adj: 13.08 +[CanonVRD, CanonDR4, Image] 65793 - Check Mark: Clear +[CanonVRD, CanonDR4, Image] 66048 - Work Color Space: Wide Gamut RGB +[CanonVRD, CanonDR4, Image] 131073 - Raw Brightness Adj: 0 +[CanonVRD, CanonDR4, Image] 131329 - White Balance Adj: Cloudy +[CanonVRD, CanonDR4, Image] 131330 - WB Adj Color Temp: 5200 +[CanonVRD, CanonDR4, Image] 131333 - WB Adj Magenta Green: -5 +[CanonVRD, CanonDR4, Image] 131334 - WB Adj Blue Amber: 3 +[CanonVRD, CanonDR4, Image] 131584 - Gamma Linear: No +[CanonVRD, CanonDR4, Image] 131841 - Picture Style: Neutral +[CanonVRD, CanonDR4, Image] 131843 - Contrast Adj: -1 +[CanonVRD, CanonDR4, Image] 131844 - Color Tone Adj: 0.7 +[CanonVRD, CanonDR4, Image] 131845 - Color Saturation Adj: 1.6 +[CanonVRD, CanonDR4, Image] 131846 - Monochrome Toning Effect: None +[CanonVRD, CanonDR4, Image] 131847 - Monochrome Filter Effect: None +[CanonVRD, CanonDR4, Image] 131848 - Unsharp Mask Strength: 3 +[CanonVRD, CanonDR4, Image] 131849 - Unsharp Mask Fineness: 2 +[CanonVRD, CanonDR4, Image] 131850 - Unsharp Mask Threshold: 7.2 +[CanonVRD, CanonDR4, Image] 131851 - Shadow Adj: -0.1 +[CanonVRD, CanonDR4, Image] 131852 - Highlight Adj: 0.5 +[CanonVRD, CanonDR4, Image] 0 - Tone Curve Color Space: Luminance +[CanonVRD, CanonDR4, Image] 1 - Tone Curve Shape: Straight +[CanonVRD, CanonDR4, Image] 3 - Tone Curve Input Range: 0 242 +[CanonVRD, CanonDR4, Image] 5 - Tone Curve Output Range: 79 255 +[CanonVRD, CanonDR4, Image] 7 - RGB Curve Points: (0,79) (117,172) (242,255) +[CanonVRD, CanonDR4, Image] 10 - Tone Curve X: 117 +[CanonVRD, CanonDR4, Image] 11 - Tone Curve Y: 172 +[CanonVRD, CanonDR4, Image] 45 - Red Curve Points: (2,3) (126,116) (254,253) +[CanonVRD, CanonDR4, Image] 83 - Green Curve Points: (1,2) (125,139) (254,253) +[CanonVRD, CanonDR4, Image] 121 - Blue Curve Points: (3,4) (132,113) (255,255) +[CanonVRD, CanonDR4, Image] 0x20400.1 - Tone Curve Original: No +[CanonVRD, CanonDR4, Image] 132112 - Tone Curve Brightness: 34 +[CanonVRD, CanonDR4, Image] 132113 - Tone Curve Contrast: -20 +[CanonVRD, CanonDR4, Image] 132352 - Auto Lighting Optimizer: Strong +[CanonVRD, CanonDR4, Image] 0x20500.0 - Auto Lighting Optimizer On: Yes +[CanonVRD, CanonDR4, Image] 131856 - Sharpness Adj: Unsharp Mask +[CanonVRD, CanonDR4, Image] 0x20310.0 - Sharpness Adj On: Yes +[CanonVRD, CanonDR4, Image] 131857 - Sharpness Strength: 0 +[CanonVRD, CanonDR4, Image] 133376 - Color Hue: -10 +[CanonVRD, CanonDR4, Image] 133377 - Saturation Adj: 98 +[CanonVRD, CanonDR4, Image] 133392 - Red HSL: -1 -0.9 -0.8 +[CanonVRD, CanonDR4, Image] 133393 - Orange HSL: -0.7 -0.6 -0.5 +[CanonVRD, CanonDR4, Image] 133394 - Yellow HSL: -0.4 -0.3 -0.2 +[CanonVRD, CanonDR4, Image] 133395 - Green HSL: -0.1 0 0.1 +[CanonVRD, CanonDR4, Image] 133396 - Aqua HSL: 0.2 0.3 0.4 +[CanonVRD, CanonDR4, Image] 133397 - Blue HSL: 0.5 0.6 0.7 +[CanonVRD, CanonDR4, Image] 133398 - Purple HSL: 0.8 0.9 1 +[CanonVRD, CanonDR4, Image] 133399 - Magenta HSL: 1.1 1.2 1.3 +[CanonVRD, CanonDR4, Image] 132608 - Luminance Noise Reduction: 4.4 +[CanonVRD, CanonDR4, Image] 132609 - Chrominance Noise Reduction: 2.7 +[CanonVRD, CanonDR4, Image] 132865 - Shooting Distance: 100% +[CanonVRD, CanonDR4, Image] 132866 - Peripheral Illumination: 34 +[CanonVRD, CanonDR4, Image] 0x20702.0 - Peripheral Illumination On: Yes +[CanonVRD, CanonDR4, Image] 132867 - Chromatic Aberration: 121 +[CanonVRD, CanonDR4, Image] 0x20703.0 - Chromatic Aberration On: Yes +[CanonVRD, CanonDR4, Image] 132868 - Color Blur On: Yes +[CanonVRD, CanonDR4, Image] 132869 - Distortion Correction: 48 +[CanonVRD, CanonDR4, Image] 0x20705.0 - Distortion Correction On: Yes +[CanonVRD, CanonDR4, Image] 132870 - DLO Setting: 50 +[CanonVRD, CanonDR4, Image] 0x20706.0 - DLO On: No +[CanonVRD, CanonDR4, Image] 132871 - Chromatic Aberration Red: -0.1 +[CanonVRD, CanonDR4, Image] 132872 - Chromatic Aberration Blue: -0.5 +[CanonVRD, CanonDR4, Image] 2 - Gamma Contrast: 0 +[CanonVRD, CanonDR4, Image] 3 - Gamma Color Tone: 0 +[CanonVRD, CanonDR4, Image] 4 - Gamma Saturation: 0 +[CanonVRD, CanonDR4, Image] 5 - Gamma Unsharp Mask Strength: 3 +[CanonVRD, CanonDR4, Image] 6 - Gamma Unsharp Mask Fineness: 4 +[CanonVRD, CanonDR4, Image] 7 - Gamma Unsharp Mask Threshold: 4 +[CanonVRD, CanonDR4, Image] 8 - Gamma Sharpness Strength: 3 +[CanonVRD, CanonDR4, Image] 9 - Gamma Shadow: 0 +[CanonVRD, CanonDR4, Image] 10 - Gamma Highlight: 0 +[CanonVRD, CanonDR4, Image] 12 - Gamma Black Point: +0.000 +[CanonVRD, CanonDR4, Image] 13 - Gamma White Point: +0.000 +[CanonVRD, CanonDR4, Image] 14 - Gamma Mid Point: +0.000 +[CanonVRD, CanonDR4, Image] 15 - Gamma Curve Output Range: 0 16383 +[CanonVRD, CanonDR4, Image] 0 - Crop Active: Yes +[CanonVRD, CanonDR4, Image] 1 - Crop Rotated Original Width: 6089 +[CanonVRD, CanonDR4, Image] 2 - Crop Rotated Original Height: 4740 +[CanonVRD, CanonDR4, Image] 3 - Crop X: 2060 +[CanonVRD, CanonDR4, Image] 4 - Crop Y: 1291 +[CanonVRD, CanonDR4, Image] 5 - Crop Width: 2952 +[CanonVRD, CanonDR4, Image] 6 - Crop Height: 1476 +[CanonVRD, CanonDR4, Image] 8 - Crop Rotation: 13.08 +[CanonVRD, CanonDR4, Image] 10 - Crop Original Width: 5472 +[CanonVRD, CanonDR4, Image] 11 - Crop Original Height: 3648 +[CanonVRD, CanonDR4, Image] 2 - Stamp Tool Count: 0 +[CanonVRD, CanonDR4, Image] 196865 - Crop Aspect Ratio: Custom +[CanonVRD, CanonDR4, Image] 196866 - Crop Aspect Ratio Custom: 2 1 +[CanonVRD, CanonDR4, Image] 984338 - Lens Focal Length: 135 diff --git a/ExifTool/t/CanonVRD_14.out b/ExifTool/t/CanonVRD_14.out new file mode 100644 index 0000000..6544353 --- /dev/null +++ b/ExifTool/t/CanonVRD_14.out @@ -0,0 +1,101 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.53 +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 5472 +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:12:28 12:46:23-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:12:28 12:46:23-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:12:28 12:46:23-05:00 +[File, System, Other] FilePermissions - File Permissions: 100644 +[File, File, Other] FileType - File Type: DR4 +[File, File, Other] FileTypeExtension - File Type Extension: DR4 +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[CanonVRD, CanonDR4, Image] 3 - DR4 Camera Model: 2147484297 +[CanonVRD, CanonDR4, Image] 65538 - Rotation: 0 +[CanonVRD, CanonDR4, Image] 65539 - Angle Adj: 13.08 +[CanonVRD, CanonDR4, Image] 65793 - Check Mark: 0 +[CanonVRD, CanonDR4, Image] 66048 - Work Color Space: 3 +[CanonVRD, CanonDR4, Image] 131073 - Raw Brightness Adj: 0 +[CanonVRD, CanonDR4, Image] 131329 - White Balance Adj: 2 +[CanonVRD, CanonDR4, Image] 131330 - WB Adj Color Temp: 5200 +[CanonVRD, CanonDR4, Image] 131333 - WB Adj Magenta Green: -5 +[CanonVRD, CanonDR4, Image] 131334 - WB Adj Blue Amber: 3 +[CanonVRD, CanonDR4, Image] 131584 - Gamma Linear: 0 +[CanonVRD, CanonDR4, Image] 131841 - Picture Style: 132 +[CanonVRD, CanonDR4, Image] 131843 - Contrast Adj: -1 +[CanonVRD, CanonDR4, Image] 131844 - Color Tone Adj: 0.7 +[CanonVRD, CanonDR4, Image] 131845 - Color Saturation Adj: 1.6 +[CanonVRD, CanonDR4, Image] 131846 - Monochrome Toning Effect: 0 +[CanonVRD, CanonDR4, Image] 131847 - Monochrome Filter Effect: 0 +[CanonVRD, CanonDR4, Image] 131848 - Unsharp Mask Strength: 3 +[CanonVRD, CanonDR4, Image] 131849 - Unsharp Mask Fineness: 2 +[CanonVRD, CanonDR4, Image] 131850 - Unsharp Mask Threshold: 7.2 +[CanonVRD, CanonDR4, Image] 131851 - Shadow Adj: -0.1 +[CanonVRD, CanonDR4, Image] 131852 - Highlight Adj: 0.5 +[CanonVRD, CanonDR4, Image] 0 - Tone Curve Color Space: 1 +[CanonVRD, CanonDR4, Image] 1 - Tone Curve Shape: 1 +[CanonVRD, CanonDR4, Image] 3 - Tone Curve Input Range: 0 242 +[CanonVRD, CanonDR4, Image] 5 - Tone Curve Output Range: 79 255 +[CanonVRD, CanonDR4, Image] 7 - RGB Curve Points: 3 0 79 117 172 242 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[CanonVRD, CanonDR4, Image] 10 - Tone Curve X: 117 +[CanonVRD, CanonDR4, Image] 11 - Tone Curve Y: 172 +[CanonVRD, CanonDR4, Image] 45 - Red Curve Points: 3 2 3 126 116 254 253 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[CanonVRD, CanonDR4, Image] 83 - Green Curve Points: 3 1 2 125 139 254 253 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[CanonVRD, CanonDR4, Image] 121 - Blue Curve Points: 3 3 4 132 113 255 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[CanonVRD, CanonDR4, Image] 0x20400.1 - Tone Curve Original: 0 +[CanonVRD, CanonDR4, Image] 132112 - Tone Curve Brightness: 34 +[CanonVRD, CanonDR4, Image] 132113 - Tone Curve Contrast: -20 +[CanonVRD, CanonDR4, Image] 132352 - Auto Lighting Optimizer: 2 +[CanonVRD, CanonDR4, Image] 0x20500.0 - Auto Lighting Optimizer On: 1 +[CanonVRD, CanonDR4, Image] 131856 - Sharpness Adj: 1 +[CanonVRD, CanonDR4, Image] 0x20310.0 - Sharpness Adj On: 0 +[CanonVRD, CanonDR4, Image] 131857 - Sharpness Strength: 0 +[CanonVRD, CanonDR4, Image] 133376 - Color Hue: -10 +[CanonVRD, CanonDR4, Image] 133377 - Saturation Adj: 98 +[CanonVRD, CanonDR4, Image] 133392 - Red HSL: -4.3 1.2 3.8 +[CanonVRD, CanonDR4, Image] 133393 - Orange HSL: -0.7 -0.6 -0.5 +[CanonVRD, CanonDR4, Image] 133394 - Yellow HSL: -0.4 -0.3 -0.2 +[CanonVRD, CanonDR4, Image] 133395 - Green HSL: -0.1 0 0.1 +[CanonVRD, CanonDR4, Image] 133396 - Aqua HSL: 0.2 0.3 0.4 +[CanonVRD, CanonDR4, Image] 133397 - Blue HSL: 0.5 0.6 0.7 +[CanonVRD, CanonDR4, Image] 133398 - Purple HSL: 0.8 0.9 1 +[CanonVRD, CanonDR4, Image] 133399 - Magenta HSL: 1.1 1.2 1.3 +[CanonVRD, CanonDR4, Image] 132608 - Luminance Noise Reduction: 4.4 +[CanonVRD, CanonDR4, Image] 132609 - Chrominance Noise Reduction: 2.7 +[CanonVRD, CanonDR4, Image] 132865 - Shooting Distance: 1 +[CanonVRD, CanonDR4, Image] 132866 - Peripheral Illumination: 34 +[CanonVRD, CanonDR4, Image] 0x20702.0 - Peripheral Illumination On: 1 +[CanonVRD, CanonDR4, Image] 132867 - Chromatic Aberration: 121 +[CanonVRD, CanonDR4, Image] 0x20703.0 - Chromatic Aberration On: 1 +[CanonVRD, CanonDR4, Image] 132868 - Color Blur On: 1 +[CanonVRD, CanonDR4, Image] 132869 - Distortion Correction: 48 +[CanonVRD, CanonDR4, Image] 0x20705.0 - Distortion Correction On: 1 +[CanonVRD, CanonDR4, Image] 132870 - DLO Setting: 50 +[CanonVRD, CanonDR4, Image] 0x20706.0 - DLO On: 0 +[CanonVRD, CanonDR4, Image] 132871 - Chromatic Aberration Red: -0.1 +[CanonVRD, CanonDR4, Image] 132872 - Chromatic Aberration Blue: -0.5 +[CanonVRD, CanonDR4, Image] 2 - Gamma Contrast: 0 +[CanonVRD, CanonDR4, Image] 3 - Gamma Color Tone: 0 +[CanonVRD, CanonDR4, Image] 4 - Gamma Saturation: 0 +[CanonVRD, CanonDR4, Image] 5 - Gamma Unsharp Mask Strength: 3 +[CanonVRD, CanonDR4, Image] 6 - Gamma Unsharp Mask Fineness: 4 +[CanonVRD, CanonDR4, Image] 7 - Gamma Unsharp Mask Threshold: 4 +[CanonVRD, CanonDR4, Image] 8 - Gamma Sharpness Strength: 3 +[CanonVRD, CanonDR4, Image] 9 - Gamma Shadow: 0 +[CanonVRD, CanonDR4, Image] 10 - Gamma Highlight: 0 +[CanonVRD, CanonDR4, Image] 12 - Gamma Black Point: 1.23403077716592 +[CanonVRD, CanonDR4, Image] 13 - Gamma White Point: 0 +[CanonVRD, CanonDR4, Image] 14 - Gamma Mid Point: 0 +[CanonVRD, CanonDR4, Image] 15 - Gamma Curve Output Range: 0 16383 +[CanonVRD, CanonDR4, Image] 0 - Crop Active: 1 +[CanonVRD, CanonDR4, Image] 1 - Crop Rotated Original Width: 6089 +[CanonVRD, CanonDR4, Image] 2 - Crop Rotated Original Height: 4740 +[CanonVRD, CanonDR4, Image] 3 - Crop X: 123 +[CanonVRD, CanonDR4, Image] 4 - Crop Y: 1291 +[CanonVRD, CanonDR4, Image] 5 - Crop Width: 2952 +[CanonVRD, CanonDR4, Image] 6 - Crop Height: 1476 +[CanonVRD, CanonDR4, Image] 8 - Crop Rotation: 13.08 +[CanonVRD, CanonDR4, Image] 10 - Crop Original Width: 5472 +[CanonVRD, CanonDR4, Image] 11 - Crop Original Height: 3648 +[CanonVRD, CanonDR4, Image] 2 - Stamp Tool Count: 0 +[CanonVRD, CanonDR4, Image] 196865 - Crop Aspect Ratio: 1 +[CanonVRD, CanonDR4, Image] 196866 - Crop Aspect Ratio Custom: 2 1 +[CanonVRD, CanonDR4, Image] 984338 - Lens Focal Length: 135 diff --git a/ExifTool/t/CanonVRD_15.out b/ExifTool/t/CanonVRD_15.out new file mode 100644 index 0000000..d8a4d8e --- /dev/null +++ b/ExifTool/t/CanonVRD_15.out @@ -0,0 +1,5 @@ +[File, System, Other] FileSize - File Size: 5831 +[CanonVRD, CanonDR4, Image] 0x20310.0 - Sharpness Adj On: 0 +[CanonVRD, CanonDR4, Image] 133392 - Red HSL: -4.3 1.2 3.8 +[CanonVRD, CanonDR4, Image] 133395 - Green HSL: -0.1 0 0.1 +[CanonVRD, CanonDR4, Image] 12 - Gamma Black Point: 1.23403077716592 diff --git a/ExifTool/t/CanonVRD_16.out b/ExifTool/t/CanonVRD_16.out new file mode 100644 index 0000000..40a2242 --- /dev/null +++ b/ExifTool/t/CanonVRD_16.out @@ -0,0 +1,6 @@ +[File, System, Other] FileSize - File Size: 34630 +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest is not current. XMP may be out of sync +[CanonVRD, CanonDR4, Image] 0x20310.0 - Sharpness Adj On: 0 +[CanonVRD, CanonDR4, Image] 133392 - Red HSL: -4.3 1.2 3.8 +[CanonVRD, CanonDR4, Image] 133395 - Green HSL: -0.1 0 0.1 +[CanonVRD, CanonDR4, Image] 12 - Gamma Black Point: 1.23403077716592 diff --git a/ExifTool/t/CanonVRD_17.out b/ExifTool/t/CanonVRD_17.out new file mode 100644 index 0000000..e9bb695 --- /dev/null +++ b/ExifTool/t/CanonVRD_17.out @@ -0,0 +1,5 @@ +[File, System, Other] FileSize - File Size: 14306 +[CanonVRD, CanonDR4, Image] 0x20310.0 - Sharpness Adj On: 0 +[CanonVRD, CanonDR4, Image] 133392 - Red HSL: -4.3 1.2 3.8 +[CanonVRD, CanonDR4, Image] 133395 - Green HSL: -0.1 0 0.1 +[CanonVRD, CanonDR4, Image] 12 - Gamma Black Point: 1.23403077716592 diff --git a/ExifTool/t/CanonVRD_18.out b/ExifTool/t/CanonVRD_18.out new file mode 100644 index 0000000..23b59e6 --- /dev/null +++ b/ExifTool/t/CanonVRD_18.out @@ -0,0 +1,5 @@ +[File, System, Other] FileSize - File Size: 11836 +[CanonVRD, CanonDR4, Image] 0x20310.0 - Sharpness Adj On: 0 +[CanonVRD, CanonDR4, Image] 133392 - Red HSL: -4.3 1.2 3.8 +[CanonVRD, CanonDR4, Image] 133395 - Green HSL: -0.1 0 0.1 +[CanonVRD, CanonDR4, Image] 12 - Gamma Black Point: 1.23403077716592 diff --git a/ExifTool/t/CanonVRD_19.out b/ExifTool/t/CanonVRD_19.out new file mode 100644 index 0000000..84415af --- /dev/null +++ b/ExifTool/t/CanonVRD_19.out @@ -0,0 +1,5 @@ +[File, System, Other] FileSize - File Size: 5580 +[CanonVRD, CanonDR4, Image] 0x20310.0 - Sharpness Adj On: 0 +[CanonVRD, CanonDR4, Image] 133392 - Red HSL: -4.3 1.2 3.8 +[CanonVRD, CanonDR4, Image] 133395 - Green HSL: -0.1 0 0.1 +[CanonVRD, CanonDR4, Image] 12 - Gamma Black Point: 1.23403077716592 diff --git a/ExifTool/t/CanonVRD_2.out b/ExifTool/t/CanonVRD_2.out new file mode 100644 index 0000000..12c15c1 --- /dev/null +++ b/ExifTool/t/CanonVRD_2.out @@ -0,0 +1,119 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.76 +[File, System, Other] FileName - File Name: CanonVRD.vrd +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1768 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:11:07 22:13:23-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:11:07 08:35:50-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:11:04 10:47:12-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: VRD +[File, File, Other] FileTypeExtension - File Type Extension: vrd +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 2.0.0 +[CanonVRD, CanonVRD, Image] 6 - WB Adj RGGB Levels: 1856 832 832 928 +[CanonVRD, CanonVRD, Image] 24 - White Balance Adj: Manual (Click) +[CanonVRD, CanonVRD, Image] 26 - WB Adj Color Temp: 5600 +[CanonVRD, CanonVRD, Image] 36 - WB Fine Tune Active: Yes +[CanonVRD, CanonVRD, Image] 40 - WB Fine Tune Saturation: 0 +[CanonVRD, CanonVRD, Image] 44 - WB Fine Tune Tone: 358 +[CanonVRD, CanonVRD, Image] 46 - Raw Color Adj: Shot Settings +[CanonVRD, CanonVRD, Image] 48 - Raw Custom Saturation: 0 +[CanonVRD, CanonVRD, Image] 52 - Raw Custom Tone: 0 +[CanonVRD, CanonVRD, Image] 56 - Raw Brightness Adj: -0.83 +[CanonVRD, CanonVRD, Image] 60 - Tone Curve Property: Shot Settings +[CanonVRD, CanonVRD, Image] 122 - Dynamic Range Min: 0 +[CanonVRD, CanonVRD, Image] 124 - Dynamic Range Max: 4095 +[CanonVRD, CanonVRD, Image] 272 - Tone Curve Active: Yes +[CanonVRD, CanonVRD, Image] 275 - Tone Curve Mode: RGB +[CanonVRD, CanonVRD, Image] 276 - Brightness Adj: 0 +[CanonVRD, CanonVRD, Image] 277 - Contrast Adj: 0 +[CanonVRD, CanonVRD, Image] 278 - Saturation Adj: 119 +[CanonVRD, CanonVRD, Image] 286 - Color Tone Adj: 0 +[CanonVRD, CanonVRD, Image] 294 - Luminance Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 336 - Luminance Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 345 - Tone Curve Interpolation: Curve +[CanonVRD, CanonVRD, Image] 352 - Red Curve Points: (0,0) (5,16) (11,30) (58,101) (106,154) (154,198) (202,233) (228,246) (255,255) +[CanonVRD, CanonVRD, Image] 394 - Red Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 410 - Green Curve Points: (7,4) (11,23) (16,34) (59,100) (104,147) (149,187) (196,219) (221,232) (248,240) +[CanonVRD, CanonVRD, Image] 452 - Green Curve Limits: 248 7 240 4 +[CanonVRD, CanonVRD, Image] 468 - Blue Curve Points: (0,0) (5,19) (10,32) (57,104) (104,156) (152,199) (200,233) (227,247) (255,255) +[CanonVRD, CanonVRD, Image] 510 - Blue Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 526 - RGB Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 568 - RGB Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 580 - Crop Active: Yes +[CanonVRD, CanonVRD, Image] 582 - Crop Left: 1164 +[CanonVRD, CanonVRD, Image] 584 - Crop Top: 608 +[CanonVRD, CanonVRD, Image] 586 - Crop Width: 564 +[CanonVRD, CanonVRD, Image] 588 - Crop Height: 123 +[CanonVRD, CanonVRD, Image] 602 - Sharpness Adj: 0 +[CanonVRD, CanonVRD, Image] 608 - Crop Aspect Ratio: Free +[CanonVRD, CanonVRD, Image] 610 - Constrained Crop Width: 5.5 +[CanonVRD, CanonVRD, Image] 614 - Constrained Crop Height: 1.2 +[CanonVRD, CanonVRD, Image] 618 - Check Mark: 3 +[CanonVRD, CanonVRD, Image] 622 - Rotation: 0 +[CanonVRD, CanonVRD, Image] 624 - Work Color Space: ColorMatch RGB +[CanonVRD, CanonVRD, Image] 2 - Picture Style: Standard +[CanonVRD, CanonVRD, Image] 3 - Is Custom Picture Style: No +[CanonVRD, CanonVRD, Image] 13 - Standard Raw Color Tone: -4 +[CanonVRD, CanonVRD, Image] 14 - Standard Raw Saturation: 0 +[CanonVRD, CanonVRD, Image] 15 - Standard Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 16 - Standard Raw Linear: No +[CanonVRD, CanonVRD, Image] 17 - Standard Raw Sharpness: 1 +[CanonVRD, CanonVRD, Image] 18 - Standard Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 19 - Standard Raw Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 20 - Standard Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 21 - Standard Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 22 - Portrait Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 23 - Portrait Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 24 - Portrait Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 25 - Portrait Raw Linear: No +[CanonVRD, CanonVRD, Image] 26 - Portrait Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 27 - Portrait Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 28 - Portrait Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 29 - Portrait Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 30 - Portrait Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 31 - Landscape Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 32 - Landscape Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 33 - Landscape Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 34 - Landscape Raw Linear: No +[CanonVRD, CanonVRD, Image] 35 - Landscape Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 36 - Landscape Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 37 - Landscape Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 38 - Landscape Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 39 - Landscape Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 40 - Neutral Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 41 - Neutral Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 42 - Neutral Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 43 - Neutral Raw Linear: No +[CanonVRD, CanonVRD, Image] 44 - Neutral Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 45 - Neutral Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 46 - Neutral Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 47 - Neutral Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 48 - Neutral Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 49 - Faithful Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 50 - Faithful Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 51 - Faithful Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 52 - Faithful Raw Linear: No +[CanonVRD, CanonVRD, Image] 53 - Faithful Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 54 - Faithful Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 55 - Faithful Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 56 - Faithful Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 57 - Faithful Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 58 - Monochrome Filter Effect: Yellow +[CanonVRD, CanonVRD, Image] 59 - Monochrome Toning Effect: Purple +[CanonVRD, CanonVRD, Image] 60 - Monochrome Contrast: 3 +[CanonVRD, CanonVRD, Image] 61 - Monochrome Linear: No +[CanonVRD, CanonVRD, Image] 62 - Monochrome Sharpness: 4 +[CanonVRD, CanonVRD, Image] 63 - Monochrome Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 64 - Monochrome Raw Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 65 - Monochrome Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 66 - Monochrome Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 76 - Custom Color Tone: 4 +[CanonVRD, CanonVRD, Image] 77 - Custom Saturation: 2 +[CanonVRD, CanonVRD, Image] 78 - Custom Contrast: 0 +[CanonVRD, CanonVRD, Image] 79 - Custom Linear: No +[CanonVRD, CanonVRD, Image] 80 - Custom Sharpness: 7 +[CanonVRD, CanonVRD, Image] 81 - Custom Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 82 - Custom Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 83 - Custom Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 84 - Custom Output Shadow Point: 0 diff --git a/ExifTool/t/CanonVRD_20.out b/ExifTool/t/CanonVRD_20.out new file mode 100644 index 0000000..6e7d99b --- /dev/null +++ b/ExifTool/t/CanonVRD_20.out @@ -0,0 +1,5 @@ +[File, System, Other] FileSize - File Size: 5472 +[CanonVRD, CanonDR4, Image] 0x20310.0 - Sharpness Adj On: 0 +[CanonVRD, CanonDR4, Image] 133392 - Red HSL: -4.3 1.2 3.8 +[CanonVRD, CanonDR4, Image] 133395 - Green HSL: -0.1 0 0.1 +[CanonVRD, CanonDR4, Image] 12 - Gamma Black Point: 1.23403077716592 diff --git a/ExifTool/t/CanonVRD_21.out b/ExifTool/t/CanonVRD_21.out new file mode 100644 index 0000000..c9439c5 --- /dev/null +++ b/ExifTool/t/CanonVRD_21.out @@ -0,0 +1,5 @@ +[File, System, Other] FileSize - File Size: 56054 +[CanonVRD, CanonDR4, Image] 0x20310.0 - Sharpness Adj On: 0 +[CanonVRD, CanonDR4, Image] 133392 - Red HSL: -4.3 1.2 3.8 +[CanonVRD, CanonDR4, Image] 133395 - Green HSL: -0.1 0 0.1 +[CanonVRD, CanonDR4, Image] 12 - Gamma Black Point: 1.23403077716592 diff --git a/ExifTool/t/CanonVRD_22.out b/ExifTool/t/CanonVRD_22.out new file mode 100644 index 0000000..d6740e6 --- /dev/null +++ b/ExifTool/t/CanonVRD_22.out @@ -0,0 +1 @@ +[File, System, Other] FileSize - File Size: 251 diff --git a/ExifTool/t/CanonVRD_24.out b/ExifTool/t/CanonVRD_24.out new file mode 100644 index 0000000..d314118 --- /dev/null +++ b/ExifTool/t/CanonVRD_24.out @@ -0,0 +1,5 @@ +[File, System, Other] FileSize - File Size: 56 kB +[CanonVRD, CanonDR4, Image] 0x20310.0 - Sharpness Adj On: No +[CanonVRD, CanonDR4, Image] 133392 - Red HSL: -4.3 1.2 3.8 +[CanonVRD, CanonDR4, Image] 133395 - Green HSL: -0.1 0 0.1 +[CanonVRD, CanonDR4, Image] 12 - Gamma Black Point: +1.500 diff --git a/ExifTool/t/CanonVRD_3.out b/ExifTool/t/CanonVRD_3.out new file mode 100644 index 0000000..ee94b2b --- /dev/null +++ b/ExifTool/t/CanonVRD_3.out @@ -0,0 +1,119 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.76 +[File, System, Other] FileName - File Name: CanonVRD_3_failed.vrd +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 1768 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:11:07 08:40:23-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:11:07 08:40:23-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:11:07 08:40:23-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: VRD +[File, File, Other] FileTypeExtension - File Type Extension: vrd +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 2.0.0 +[CanonVRD, CanonVRD, Image] 6 - WB Adj RGGB Levels: 0 0 0 0 +[CanonVRD, CanonVRD, Image] 24 - White Balance Adj: Shot Settings +[CanonVRD, CanonVRD, Image] 26 - WB Adj Color Temp: 5600 +[CanonVRD, CanonVRD, Image] 36 - WB Fine Tune Active: No +[CanonVRD, CanonVRD, Image] 40 - WB Fine Tune Saturation: 0 +[CanonVRD, CanonVRD, Image] 44 - WB Fine Tune Tone: 0 +[CanonVRD, CanonVRD, Image] 46 - Raw Color Adj: Shot Settings +[CanonVRD, CanonVRD, Image] 48 - Raw Custom Saturation: 0 +[CanonVRD, CanonVRD, Image] 52 - Raw Custom Tone: 0 +[CanonVRD, CanonVRD, Image] 56 - Raw Brightness Adj: 0.00 +[CanonVRD, CanonVRD, Image] 60 - Tone Curve Property: Shot Settings +[CanonVRD, CanonVRD, Image] 122 - Dynamic Range Min: 0 +[CanonVRD, CanonVRD, Image] 124 - Dynamic Range Max: 4095 +[CanonVRD, CanonVRD, Image] 272 - Tone Curve Active: No +[CanonVRD, CanonVRD, Image] 275 - Tone Curve Mode: RGB +[CanonVRD, CanonVRD, Image] 276 - Brightness Adj: 0 +[CanonVRD, CanonVRD, Image] 277 - Contrast Adj: 0 +[CanonVRD, CanonVRD, Image] 278 - Saturation Adj: 100 +[CanonVRD, CanonVRD, Image] 286 - Color Tone Adj: 0 +[CanonVRD, CanonVRD, Image] 294 - Luminance Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 336 - Luminance Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 345 - Tone Curve Interpolation: Curve +[CanonVRD, CanonVRD, Image] 352 - Red Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 394 - Red Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 410 - Green Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 452 - Green Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 468 - Blue Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 510 - Blue Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 526 - RGB Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 568 - RGB Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 580 - Crop Active: No +[CanonVRD, CanonVRD, Image] 582 - Crop Left: 438 +[CanonVRD, CanonVRD, Image] 584 - Crop Top: 618 +[CanonVRD, CanonVRD, Image] 586 - Crop Width: 0 +[CanonVRD, CanonVRD, Image] 588 - Crop Height: 0 +[CanonVRD, CanonVRD, Image] 602 - Sharpness Adj: 0 +[CanonVRD, CanonVRD, Image] 608 - Crop Aspect Ratio: Free +[CanonVRD, CanonVRD, Image] 610 - Constrained Crop Width: 0 +[CanonVRD, CanonVRD, Image] 614 - Constrained Crop Height: 0 +[CanonVRD, CanonVRD, Image] 618 - Check Mark: Clear +[CanonVRD, CanonVRD, Image] 622 - Rotation: 0 +[CanonVRD, CanonVRD, Image] 624 - Work Color Space: sRGB +[CanonVRD, CanonVRD, Image] 2 - Picture Style: Standard +[CanonVRD, CanonVRD, Image] 3 - Is Custom Picture Style: No +[CanonVRD, CanonVRD, Image] 13 - Standard Raw Color Tone: -4 +[CanonVRD, CanonVRD, Image] 14 - Standard Raw Saturation: 0 +[CanonVRD, CanonVRD, Image] 15 - Standard Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 16 - Standard Raw Linear: No +[CanonVRD, CanonVRD, Image] 17 - Standard Raw Sharpness: 1 +[CanonVRD, CanonVRD, Image] 18 - Standard Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 19 - Standard Raw Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 20 - Standard Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 21 - Standard Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 22 - Portrait Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 23 - Portrait Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 24 - Portrait Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 25 - Portrait Raw Linear: No +[CanonVRD, CanonVRD, Image] 26 - Portrait Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 27 - Portrait Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 28 - Portrait Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 29 - Portrait Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 30 - Portrait Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 31 - Landscape Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 32 - Landscape Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 33 - Landscape Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 34 - Landscape Raw Linear: No +[CanonVRD, CanonVRD, Image] 35 - Landscape Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 36 - Landscape Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 37 - Landscape Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 38 - Landscape Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 39 - Landscape Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 40 - Neutral Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 41 - Neutral Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 42 - Neutral Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 43 - Neutral Raw Linear: No +[CanonVRD, CanonVRD, Image] 44 - Neutral Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 45 - Neutral Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 46 - Neutral Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 47 - Neutral Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 48 - Neutral Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 49 - Faithful Raw Color Tone: 4 +[CanonVRD, CanonVRD, Image] 50 - Faithful Raw Saturation: 2 +[CanonVRD, CanonVRD, Image] 51 - Faithful Raw Contrast: 0 +[CanonVRD, CanonVRD, Image] 52 - Faithful Raw Linear: No +[CanonVRD, CanonVRD, Image] 53 - Faithful Raw Sharpness: 7 +[CanonVRD, CanonVRD, Image] 54 - Faithful Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 55 - Faithful Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 56 - Faithful Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 57 - Faithful Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 58 - Monochrome Filter Effect: Yellow +[CanonVRD, CanonVRD, Image] 59 - Monochrome Toning Effect: Purple +[CanonVRD, CanonVRD, Image] 60 - Monochrome Contrast: 3 +[CanonVRD, CanonVRD, Image] 61 - Monochrome Linear: No +[CanonVRD, CanonVRD, Image] 62 - Monochrome Sharpness: 4 +[CanonVRD, CanonVRD, Image] 63 - Monochrome Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 64 - Monochrome Raw Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 65 - Monochrome Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 66 - Monochrome Output Shadow Point: 0 +[CanonVRD, CanonVRD, Image] 76 - Custom Color Tone: 4 +[CanonVRD, CanonVRD, Image] 77 - Custom Saturation: 2 +[CanonVRD, CanonVRD, Image] 78 - Custom Contrast: 0 +[CanonVRD, CanonVRD, Image] 79 - Custom Linear: No +[CanonVRD, CanonVRD, Image] 80 - Custom Sharpness: 7 +[CanonVRD, CanonVRD, Image] 81 - Custom Raw Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 82 - Custom Raw Shadow Point: 6 +[CanonVRD, CanonVRD, Image] 83 - Custom Output Highlight Point: 4095 +[CanonVRD, CanonVRD, Image] 84 - Custom Output Shadow Point: 0 diff --git a/ExifTool/t/CanonVRD_4.out b/ExifTool/t/CanonVRD_4.out new file mode 100644 index 0000000..495ad48 --- /dev/null +++ b/ExifTool/t/CanonVRD_4.out @@ -0,0 +1,2 @@ +[File, System, Other] FileSize - File Size: 2019 +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 200 diff --git a/ExifTool/t/CanonVRD_5.out b/ExifTool/t/CanonVRD_5.out new file mode 100644 index 0000000..7cdea37 --- /dev/null +++ b/ExifTool/t/CanonVRD_5.out @@ -0,0 +1,3 @@ +[File, System, Other] FileSize - File Size: 27046 +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest is not current. XMP may be out of sync +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 200 diff --git a/ExifTool/t/CanonVRD_6.out b/ExifTool/t/CanonVRD_6.out new file mode 100644 index 0000000..de398ea --- /dev/null +++ b/ExifTool/t/CanonVRD_6.out @@ -0,0 +1,3 @@ +[File, System, Other] FileSize - File Size: 10494 +[MakerNotes, Canon, Camera] 208 - VRD Offset: 8708 +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 200 diff --git a/ExifTool/t/CanonVRD_7.out b/ExifTool/t/CanonVRD_7.out new file mode 100644 index 0000000..0fbfc7d --- /dev/null +++ b/ExifTool/t/CanonVRD_7.out @@ -0,0 +1,2 @@ +[File, System, Other] FileSize - File Size: 8024 +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 200 diff --git a/ExifTool/t/CanonVRD_8.out b/ExifTool/t/CanonVRD_8.out new file mode 100644 index 0000000..0892cae --- /dev/null +++ b/ExifTool/t/CanonVRD_8.out @@ -0,0 +1,2 @@ +[File, System, Other] FileSize - File Size: 2296 +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 100 diff --git a/ExifTool/t/CanonVRD_9.out b/ExifTool/t/CanonVRD_9.out new file mode 100644 index 0000000..d3c4ae7 --- /dev/null +++ b/ExifTool/t/CanonVRD_9.out @@ -0,0 +1,2 @@ +[File, System, Other] FileSize - File Size: 23470 +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest is not current. XMP may be out of sync diff --git a/ExifTool/t/Canon_2.out b/ExifTool/t/Canon_2.out new file mode 100644 index 0000000..177fb20 --- /dev/null +++ b/ExifTool/t/Canon_2.out @@ -0,0 +1,358 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Canon1DmkIII.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 8.3 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2008:01:18 14:10:05-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:30-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS-1D Mark III +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2007:02:22 17:02:42 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/125 +[EXIF, ExifIFD, Image] 33437 - F Number: 5.6 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 3200 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2007:02:22 17:02:42 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2007:02:22 17:02:42 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/128 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 5.7 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: On, Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 35.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Time] 37520 - Sub Sec Time: 81 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 81 +[EXIF, ExifIFD, Time] 37522 - Sub Sec Time Digitized: 81 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3888 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2592 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3512.195122 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3521.73913 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, GPS, Location] 0 - GPS Version ID: 2.2.0.0 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 8060 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Canon, Camera] 1 - Macro Mode: Normal +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: Fine +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: External flash +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Continuous, High +[MakerNotes, Canon, Camera] 7 - Focus Mode: AI Servo AF +[MakerNotes, Canon, Camera] 9 - Record Mode: CR2+JPEG +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: None +[MakerNotes, Canon, Camera] 13 - Contrast: Normal +[MakerNotes, Canon, Camera] 14 - Saturation: Normal +[MakerNotes, Canon, Camera] 17 - Metering Mode: Evaluative +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Program AE +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 35 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 16 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 2.8 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 23 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 145 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 0 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 0 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Camera] 46 - SRAW Quality: n/a +[MakerNotes, Canon, Image] 1 - Focal Length: 35 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 3200 +[MakerNotes, Canon, Image] 3 - Measured EV: 3.62 +[MakerNotes, Canon, Image] 4 - Target Aperture: 5.7 +[MakerNotes, Canon, Image] 5 - Target Exposure Time: 1/128 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Camera] 12 - Camera Temperature: 26 C +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 21 - F Number: 5.7 +[MakerNotes, Canon, Image] 22 - Exposure Time: 1/128 +[MakerNotes, Canon, Image] 23 - Measured EV 2: 3.75 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 0 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS High-end +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 6 - Canon Image Type: Canon EOS-1D Mark III +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware Version 5.3.1 +[MakerNotes, Canon, Camera] 9 - Owner Name: +[MakerNotes, Canon, Camera] 12 - Serial Number: 500292 +[MakerNotes, Canon, Image] 3 - F Number: 5.7 +[MakerNotes, Canon, Image] 4 - Exposure Time: 1/128 +[MakerNotes, Canon, Image] 6 - ISO: 3200 +[MakerNotes, Canon, Camera] 24 - Camera Temperature: 26 C +[MakerNotes, Canon, Camera] 29 - Focal Length: 35 mm +[MakerNotes, Canon, Camera] 48 - Camera Orientation: Horizontal (normal) +[MakerNotes, Canon, Camera] 67 - Focus Distance Upper: 2.19 m +[MakerNotes, Canon, Camera] 69 - Focus Distance Lower: 1.13 m +[MakerNotes, Canon, Camera] 94 - White Balance: Auto +[MakerNotes, Canon, Camera] 98 - Color Temperature: 5200 +[MakerNotes, Canon, Camera] 134 - Picture Style: Neutral +[MakerNotes, Canon, Camera] 273 - Lens Type: Canon EF 16-35mm f/2.8L II USM +[MakerNotes, Canon, Camera] 275 - Min Focal Length: 16 mm +[MakerNotes, Canon, Camera] 277 - Max Focal Length: 35 mm +[MakerNotes, Canon, Camera] 310 - Firmware Version: 5.3.1 +[MakerNotes, Canon, Image] 370 - File Index: 232 +[MakerNotes, Canon, Camera] 374 - Shutter Count: 1 +[MakerNotes, Canon, Image] 382 - Directory Index: 108 +[MakerNotes, Canon, Camera] 0 - Contrast Standard: 0 +[MakerNotes, Canon, Camera] 4 - Sharpness Standard: 3 +[MakerNotes, Canon, Camera] 8 - Saturation Standard: 0 +[MakerNotes, Canon, Camera] 12 - Color Tone Standard: 0 +[MakerNotes, Canon, Camera] 24 - Contrast Portrait: 0 +[MakerNotes, Canon, Camera] 28 - Sharpness Portrait: 2 +[MakerNotes, Canon, Camera] 32 - Saturation Portrait: 0 +[MakerNotes, Canon, Camera] 36 - Color Tone Portrait: 0 +[MakerNotes, Canon, Camera] 48 - Contrast Landscape: 0 +[MakerNotes, Canon, Camera] 52 - Sharpness Landscape: 4 +[MakerNotes, Canon, Camera] 56 - Saturation Landscape: 0 +[MakerNotes, Canon, Camera] 60 - Color Tone Landscape: 0 +[MakerNotes, Canon, Camera] 72 - Contrast Neutral: 0 +[MakerNotes, Canon, Camera] 76 - Sharpness Neutral: 0 +[MakerNotes, Canon, Camera] 80 - Saturation Neutral: 0 +[MakerNotes, Canon, Camera] 84 - Color Tone Neutral: 0 +[MakerNotes, Canon, Camera] 96 - Contrast Faithful: 0 +[MakerNotes, Canon, Camera] 100 - Sharpness Faithful: 0 +[MakerNotes, Canon, Camera] 104 - Saturation Faithful: 0 +[MakerNotes, Canon, Camera] 108 - Color Tone Faithful: 0 +[MakerNotes, Canon, Camera] 120 - Contrast Monochrome: 0 +[MakerNotes, Canon, Camera] 124 - Sharpness Monochrome: 3 +[MakerNotes, Canon, Camera] 136 - Filter Effect Monochrome: None +[MakerNotes, Canon, Camera] 140 - Toning Effect Monochrome: None +[MakerNotes, Canon, Camera] 144 - Contrast User Def 1: 0 +[MakerNotes, Canon, Camera] 148 - Sharpness User Def 1: 3 +[MakerNotes, Canon, Camera] 152 - Saturation User Def 1: 0 +[MakerNotes, Canon, Camera] 156 - Color Tone User Def 1: 0 +[MakerNotes, Canon, Camera] 160 - Filter Effect User Def 1: None +[MakerNotes, Canon, Camera] 164 - Toning Effect User Def 1: None +[MakerNotes, Canon, Camera] 168 - Contrast User Def 2: 0 +[MakerNotes, Canon, Camera] 172 - Sharpness User Def 2: 3 +[MakerNotes, Canon, Camera] 176 - Saturation User Def 2: 0 +[MakerNotes, Canon, Camera] 180 - Color Tone User Def 2: 0 +[MakerNotes, Canon, Camera] 184 - Filter Effect User Def 2: None +[MakerNotes, Canon, Camera] 188 - Toning Effect User Def 2: None +[MakerNotes, Canon, Camera] 192 - Contrast User Def 3: 0 +[MakerNotes, Canon, Camera] 196 - Sharpness User Def 3: 3 +[MakerNotes, Canon, Camera] 200 - Saturation User Def 3: 0 +[MakerNotes, Canon, Camera] 204 - Color Tone User Def 3: 0 +[MakerNotes, Canon, Camera] 208 - Filter Effect User Def 3: None +[MakerNotes, Canon, Camera] 212 - Toning Effect User Def 3: None +[MakerNotes, Canon, Camera] 216 - User Def 1 Picture Style: Standard +[MakerNotes, Canon, Camera] 218 - User Def 2 Picture Style: Standard +[MakerNotes, Canon, Camera] 220 - User Def 3 Picture Style: Standard +[MakerNotes, Canon, Time] 1114 - Time Stamp 1: 2007:02:22 17:02:42 +[MakerNotes, Canon, Time] 1118 - Time Stamp: 1970:01:02 01:30:10 +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS-1D Mark III +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 2 +[MakerNotes, Canon, Camera] 1 - AF Area Mode: Single-point AF +[MakerNotes, Canon, Camera] 2 - Num AF Points: 45 +[MakerNotes, Canon, Camera] 3 - Valid AF Points: 45 +[MakerNotes, Canon, Image] 4 - Canon Image Width: 3888 +[MakerNotes, Canon, Image] 5 - Canon Image Height: 2592 +[MakerNotes, Canon, Camera] 6 - AF Image Width: 3888 +[MakerNotes, Canon, Camera] 7 - AF Image Height: 2592 +[MakerNotes, Canon, Camera] 8 - AF Area Widths: 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 112 +[MakerNotes, Canon, Camera] 9 - AF Area Heights: 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 168 +[MakerNotes, Canon, Camera] 10 - AF Area X Positions: -625 -416 -209 0 209 416 625 -917 -723 -520 -311 -104 104 311 520 723 917 -1041 -832 -625 -416 -206 0 206 416 625 832 1041 -917 -723 -520 -311 -104 104 311 520 723 917 -625 -416 -209 0 209 416 625 +[MakerNotes, Canon, Camera] 11 - AF Area Y Positions: -554 -554 -554 -554 -554 -554 -554 -277 -277 -277 -277 -277 -277 -277 -277 -277 -277 0 0 0 0 0 0 0 0 0 0 0 277 277 277 277 277 277 277 277 277 277 554 554 554 554 554 554 554 +[MakerNotes, Canon, Camera] 12 - AF Points In Focus: 13 +[MakerNotes, Canon, Camera] 13 - AF Points Selected: 13 +[MakerNotes, Canon, Camera] 131 - Original Decision Data Offset: 3326 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 7 - Raw Jpg Size: Large +[MakerNotes, Canon, Image] 8 - Long Exposure Noise Reduction 2: Off +[MakerNotes, Canon, Image] 9 - WB Bracket Mode: Off +[MakerNotes, Canon, Image] 12 - WB Bracket Value AB: 0 +[MakerNotes, Canon, Image] 13 - WB Bracket Value GM: 0 +[MakerNotes, Canon, Image] 19 - Live View Shooting: Off +[MakerNotes, Canon, Image] 20 - Focus Distance Upper: 2.19 m +[MakerNotes, Canon, Image] 21 - Focus Distance Lower: 1.13 m +[MakerNotes, Canon, Camera] 149 - Lens Model: EF16-35mm f/2.8L II USM +[MakerNotes, Canon, Camera] 150 - Internal Serial Number: G002669 +[MakerNotes, Canon, Camera] 151 - Dust Removal Data: (Binary data 1024 bytes) +[MakerNotes, Canon, Camera] 0 - Crop Left Margin: 0 +[MakerNotes, Canon, Camera] 1 - Crop Right Margin: 0 +[MakerNotes, Canon, Camera] 2 - Crop Top Margin: 0 +[MakerNotes, Canon, Camera] 3 - Crop Bottom Margin: 0 +[MakerNotes, CanonCustom, Camera] 257 - Exposure Level Increments: 1/3-stop set, 1/3-stop comp. +[MakerNotes, CanonCustom, Camera] 258 - ISO Speed Increments: 1/3 Stop +[MakerNotes, CanonCustom, Camera] 259 - ISO Speed Range: Disable; Max 3200; Min 100 +[MakerNotes, CanonCustom, Camera] 260 - AEB Auto Cancel: On +[MakerNotes, CanonCustom, Camera] 261 - AEB Sequence: 0,-,+ +[MakerNotes, CanonCustom, Camera] 262 - AEB Shot Count: 7 shots +[MakerNotes, CanonCustom, Camera] 263 - Spot Meter Link To AF Point: Disable (use center AF point) +[MakerNotes, CanonCustom, Camera] 264 - Safety Shift: Disable +[MakerNotes, CanonCustom, Camera] 265 - Usable Shooting Modes: Disable; Flags 0xfc +[MakerNotes, CanonCustom, Camera] 266 - Usable Metering Modes: Disable; Flags 0xf0 +[MakerNotes, CanonCustom, Camera] 267 - Exposure Mode In Manual: Specified metering mode +[MakerNotes, CanonCustom, Camera] 268 - Shutter Speed Range: Disable; Hi 1/8192; Lo 32 +[MakerNotes, CanonCustom, Camera] 269 - Aperture Range: Disable; Closed 91; Open 1 +[MakerNotes, CanonCustom, Camera] 270 - Apply Shooting Metering Mode: Disable; 0; 0; 3; 112; 48; 0; 0 +[MakerNotes, CanonCustom, Camera] 271 - Flash Sync Speed Av: Auto +[MakerNotes, CanonCustom, Camera] 513 - Long Exposure Noise Reduction: Off +[MakerNotes, CanonCustom, Camera] 514 - High ISO Noise Reduction: Off +[MakerNotes, CanonCustom, Camera] 515 - Highlight Tone Priority: Disable +[MakerNotes, CanonCustom, Camera] 772 - E-TTL II: Evaluative +[MakerNotes, CanonCustom, Camera] 773 - Shutter Curtain Sync: 1st-curtain sync +[MakerNotes, CanonCustom, Camera] 774 - Flash Firing: Fires +[MakerNotes, CanonCustom, Camera] 1031 - View Info During Exposure: Disable +[MakerNotes, CanonCustom, Camera] 1032 - LCD Illumination During Bulb: Off +[MakerNotes, CanonCustom, Camera] 1033 - Info Button When Shooting: Displays camera settings +[MakerNotes, CanonCustom, Camera] 1281 - USM Lens Electronic MF: Enable after one-shot AF +[MakerNotes, CanonCustom, Camera] 1282 - AI Servo Tracking Sensitivity: Standard +[MakerNotes, CanonCustom, Camera] 1283 - AI Servo Image Priority: 1: AF, 2: Tracking +[MakerNotes, CanonCustom, Camera] 1284 - AI Servo Tracking Method: Main focus point priority +[MakerNotes, CanonCustom, Camera] 1285 - Lens Drive No AF: Focus search on +[MakerNotes, CanonCustom, Camera] 1286 - Lens AF Stop Button: AF stop +[MakerNotes, CanonCustom, Camera] 1287 - AF Microadjustment: Disable; 0; 0; 0; 0 +[MakerNotes, CanonCustom, Camera] 1288 - AF Point Area Expansion: Disable +[MakerNotes, CanonCustom, Camera] 1289 - Selectable AF Point: 19 points +[MakerNotes, CanonCustom, Camera] 1290 - Switch To Registered AF Point: Disable +[MakerNotes, CanonCustom, Camera] 1291 - AF Point Auto Selection: Control-direct:disable/Main:enable +[MakerNotes, CanonCustom, Camera] 1292 - AF Point Display During Focus: On +[MakerNotes, CanonCustom, Camera] 1293 - AF Point Brightness: Normal +[MakerNotes, CanonCustom, Camera] 1294 - AF Assist Beam: Emits +[MakerNotes, CanonCustom, Camera] 1551 - Mirror Lockup: Disable +[MakerNotes, CanonCustom, Camera] 1552 - Continuous Shooting Speed: Disable; Hi 10; Lo 3 +[MakerNotes, CanonCustom, Camera] 1553 - Continuous Shot Limit: Disable; 99 shots +[MakerNotes, CanonCustom, Camera] 1793 - Shutter Button AF On Button: Metering + AF start +[MakerNotes, CanonCustom, Camera] 1794 - AF On AE Lock Button Switch: Disable +[MakerNotes, CanonCustom, Camera] 1795 - Quick Control Dial In Meter: Exposure comp/Aperture +[MakerNotes, CanonCustom, Camera] 1796 - Set Button When Shooting: Normal (disabled) +[MakerNotes, CanonCustom, Camera] 1797 - Manual Tv/Av For M: Tv=Main/Av=Control +[MakerNotes, CanonCustom, Camera] 1798 - Dial Direction Tv Av: Normal +[MakerNotes, CanonCustom, Camera] 1799 - Av Setting Without Lens: Disable +[MakerNotes, CanonCustom, Camera] 1800 - WB Media Image Size Setting: Rear LCD panel +[MakerNotes, CanonCustom, Camera] 1801 - Lock Microphone Button: Protect (hold:record memo) +[MakerNotes, CanonCustom, Camera] 1802 - Button Function Control Off: Normal (enable) +[MakerNotes, CanonCustom, Camera] 2059 - Focusing Screen: Ec-CIV +[MakerNotes, CanonCustom, Camera] 2060 - Timer Length: Disable; 6 s: 6; 16 s: 16; After release: 2 +[MakerNotes, CanonCustom, Camera] 2061 - Short Release Time Lag: Disable +[MakerNotes, CanonCustom, Camera] 2062 - Add Aspect Ratio Info: Off +[MakerNotes, CanonCustom, Camera] 2063 - Add Original Decision Data: Off +[MakerNotes, CanonCustom, Camera] 2064 - Live View Exposure Simulation: Disable (LCD auto adjust) +[MakerNotes, Canon, Camera] 0 - Aspect Ratio: 3:2 +[MakerNotes, Canon, Camera] 1 - Cropped Image Width: 3888 +[MakerNotes, Canon, Camera] 2 - Cropped Image Height: 2592 +[MakerNotes, Canon, Camera] 3 - Cropped Image Left: 0 +[MakerNotes, Canon, Camera] 4 - Cropped Image Top: 0 +[MakerNotes, Canon, Image] 1 - Tone Curve: Standard +[MakerNotes, Canon, Image] 2 - Sharpness: 0 +[MakerNotes, Canon, Image] 3 - Sharpness Frequency: n/a +[MakerNotes, Canon, Image] 4 - Sensor Red Level: 0 +[MakerNotes, Canon, Image] 5 - Sensor Blue Level: 0 +[MakerNotes, Canon, Image] 6 - White Balance Red: 0 +[MakerNotes, Canon, Image] 7 - White Balance Blue: 0 +[MakerNotes, Canon, Image] 9 - Color Temperature: 5200 +[MakerNotes, Canon, Image] 10 - Picture Style: Neutral +[MakerNotes, Canon, Image] 11 - Digital Gain: 0 +[MakerNotes, Canon, Image] 12 - WB Shift AB: 0 +[MakerNotes, Canon, Image] 13 - WB Shift GM: 0 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 663 1024 1024 611 +[MakerNotes, Canon, Camera] 180 - Color Space: sRGB +[MakerNotes, Canon, Camera] 208 - VRD Offset: 0 +[MakerNotes, Canon, Image] 1 - Sensor Width: 3984 +[MakerNotes, Canon, Image] 2 - Sensor Height: 2622 +[MakerNotes, Canon, Image] 5 - Sensor Left Border: 88 +[MakerNotes, Canon, Image] 6 - Sensor Top Border: 25 +[MakerNotes, Canon, Image] 7 - Sensor Right Border: 3975 +[MakerNotes, Canon, Image] 8 - Sensor Bottom Border: 2616 +[MakerNotes, Canon, Image] 9 - Black Mask Left Border: 0 +[MakerNotes, Canon, Image] 10 - Black Mask Top Border: 0 +[MakerNotes, Canon, Image] 11 - Black Mask Right Border: 0 +[MakerNotes, Canon, Image] 12 - Black Mask Bottom Border: 0 +[MakerNotes, Canon, Camera] 0 - Color Data Version: 2 (1DmkIII) +[MakerNotes, Canon, Camera] 0 - WB RGGB Levels As Shot: 2275 1024 1024 1357 +[MakerNotes, Canon, Camera] 4 - Color Temp As Shot: 6142 +[MakerNotes, Canon, Camera] 5 - WB RGGB Levels Auto: 2275 1024 1024 1357 +[MakerNotes, Canon, Camera] 9 - Color Temp Auto: 6142 +[MakerNotes, Canon, Camera] 10 - WB RGGB Levels Measured: 2272 1026 1021 1355 +[MakerNotes, Canon, Camera] 14 - Color Temp Measured: 6142 +[MakerNotes, Canon, Camera] 20 - WB RGGB Levels Daylight: 2101 1024 1024 1535 +[MakerNotes, Canon, Camera] 24 - Color Temp Daylight: 5200 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels Shade: 2433 1024 1024 1259 +[MakerNotes, Canon, Camera] 29 - Color Temp Shade: 7000 +[MakerNotes, Canon, Camera] 30 - WB RGGB Levels Cloudy: 2270 1024 1024 1382 +[MakerNotes, Canon, Camera] 34 - Color Temp Cloudy: 6000 +[MakerNotes, Canon, Camera] 35 - WB RGGB Levels Tungsten: 1603 1134 1134 2760 +[MakerNotes, Canon, Camera] 39 - Color Temp Tungsten: 3200 +[MakerNotes, Canon, Camera] 40 - WB RGGB Levels Fluorescent: 1938 1073 1073 2419 +[MakerNotes, Canon, Camera] 44 - Color Temp Fluorescent: 3768 +[MakerNotes, Canon, Camera] 45 - WB RGGB Levels Kelvin: 2101 1024 1024 1535 +[MakerNotes, Canon, Camera] 49 - Color Temp Kelvin: 5210 +[MakerNotes, Canon, Camera] 50 - WB RGGB Levels Flash: 2422 1024 1024 1295 +[MakerNotes, Canon, Camera] 54 - Color Temp Flash: 6777 +[MakerNotes, Canon, Camera] 231 - Average Black Level: 1024 1024 1024 1024 +[MakerNotes, Canon, Camera] 640 - Raw Measured RGGB: 294111 455756 464315 279659 +[MakerNotes, Canon, Camera] 16392 - Picture Style User Def: Standard; Standard; Standard +[MakerNotes, Canon, Camera] 16393 - Picture Style PC: n/a; n/a; n/a +[MakerNotes, Canon, Camera] 16400 - Custom Picture Style File Name: +[MakerNotes, Canon, Camera] 1 - AF Micro Adj Mode: Disable +[MakerNotes, Canon, Camera] 2 - AF Micro Adj Value: 0 +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Continuous Shooting +[Composite, Composite, Image] Canon-FileNumber - File Number: 108-0232 +[Composite, Composite, Camera] Canon-ISO - ISO: 3200 +[Composite, Composite, Camera] Canon-Lens - Lens: 16.0 - 35.0 mm +[Composite, Composite, Camera] Canon-OriginalDecisionData - Original Decision Data: (Binary data 512 bytes) +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Program AE +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 2275 1024 1024 1357 +[Composite, Composite, Image] Exif-Aperture - Aperture: 5.6 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.325195 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Canon EF 16-35mm f/2.8L II USM +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.22168 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.3 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/125 +[Composite, Composite, Time] Exif-SubSecCreateDate - Create Date: 2007:02:22 17:02:42.81 +[Composite, Composite, Time] Exif-SubSecDateTimeOriginal - Date/Time Original: 2007:02:22 17:02:42.81 +[Composite, Composite, Time] Exif-SubSecModifyDate - Modify Date: 2007:02:22 17:02:42.81 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 16.0 - 35.0 mm (35 mm equivalent: 20.5 - 44.8 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.023 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.60 m (1.41 - 2.01 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 43.7 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 35.0 mm (35 mm equivalent: 44.8 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 9.33 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 6.9 diff --git a/ExifTool/t/Canon_3.out b/ExifTool/t/Canon_3.out new file mode 100644 index 0000000..dbec15d --- /dev/null +++ b/ExifTool/t/Canon_3.out @@ -0,0 +1,3 @@ +[MakerNotes, CanonCustom, Camera] 259 - ISO Speed Range: Enable; Max 1600; Min 200 +[MakerNotes, CanonCustom, Camera] 2060 - Timer Length: Enable; 6 s: 5; 16 s: 20; After release: 6 +[Composite, Composite, Camera] Canon-OriginalDecisionData - Original Decision Data: (Binary data 512 bytes) diff --git a/ExifTool/t/Casio.t b/ExifTool/t/Casio.t new file mode 100644 index 0000000..6dafe8a --- /dev/null +++ b/ExifTool/t/Casio.t @@ -0,0 +1,54 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Casio.t". + +BEGIN { + $| = 1; print "1..6\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Casio; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Casio'; +my $testnum = 1; + +# test 2-4: Extract information from Casio images +{ + my $file; + my $exifTool = Image::ExifTool->new; + foreach $file ('Casio.jpg', 'Casio2.jpg', 'CasioQVCI.jpg') { + ++$testnum; + my $info = $exifTool->ImageInfo("t/images/$file"); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +# test 5: Write some new information +{ + ++$testnum; + my @writeInfo = ( + [MaxApertureValue => 4], + [FocusMode => 'Macro'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 6: Write some new information in type 2 file +{ + ++$testnum; + my @writeInfo = ( + ['XResolution',300], + ['YResolution',300], + ['ObjectDistance','3.5'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/Casio2.jpg'); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Casio_2.out b/ExifTool/t/Casio_2.out new file mode 100644 index 0000000..0b9b75c --- /dev/null +++ b/ExifTool/t/Casio_2.out @@ -0,0 +1,72 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Casio.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1203 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:03-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: CASIO +[EXIF, IFD0, Camera] 272 - Camera Model Name: QV-3000EX +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Ver1.000 +[EXIF, IFD0, Time] 306 - Modify Date: 2000:03:31 10:44:15 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/114 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2000:03:31 10:44:15 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2000:03:31 10:44:15 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3.645833333 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 13.3 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2048 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1536 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 926 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Casio, Camera] 1 - Recording Mode: Single Shutter +[MakerNotes, Casio, Camera] 2 - Quality: Fine +[MakerNotes, Casio, Camera] 3 - Focus Mode: Auto +[MakerNotes, Casio, Camera] 4 - Flash Mode: Red-eye Reduction +[MakerNotes, Casio, Camera] 5 - Flash Intensity: Normal +[MakerNotes, Casio, Camera] 6 - Object Distance: 2.5 m +[MakerNotes, Casio, Camera] 7 - White Balance: Auto +[MakerNotes, Casio, Camera] 10 - Digital Zoom: Off +[MakerNotes, Casio, Camera] 11 - Sharpness: Normal +[MakerNotes, Casio, Camera] 12 - Contrast: Normal +[MakerNotes, Casio, Camera] 13 - Saturation: Normal +[MakerNotes, Casio, Camera] 20 - ISO: 64 +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/114 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 13.3 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.5 diff --git a/ExifTool/t/Casio_3.out b/ExifTool/t/Casio_3.out new file mode 100644 index 0000000..87f69d1 --- /dev/null +++ b/ExifTool/t/Casio_3.out @@ -0,0 +1,102 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Casio2.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1699 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:03-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: CASIO COMPUTER CO.,LTD +[EXIF, IFD0, Camera] 272 - Camera Model Name: EX-Z3 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: 1.00 +[EXIF, IFD0, Time] 306 - Modify Date: 2003:06:18 16:54:37 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/80 +[EXIF, ExifIFD, Image] 33437 - F Number: 3.9 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:06:18 16:54:37 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:06:18 16:54:37 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 0.533203125 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.6 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 12.2 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2048 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1536 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Manual +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: undef +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 74 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1396 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Casio, Image] 2 - Preview Image Size: 320x240 +[MakerNotes, Casio, Image] 3 - Preview Image Length: 26 +[MakerNotes, Casio, Image] 4 - Preview Image Start: 1424 +[MakerNotes, Casio, Preview] 8192 - Preview Image: (Binary data 26 bytes) +[MakerNotes, Casio, Camera] 8193 - Firmware Date: 2003:02:28 09:45 +[MakerNotes, Casio, Camera] 8209 - White Balance Bias: 87 93 +[MakerNotes, Casio, Camera] 8210 - White Balance: Unknown (13) +[MakerNotes, Casio, Camera] 8225 - AF Point Position: 0.5 0.5 +[MakerNotes, Casio, Camera] 8226 - Object Distance: 1 m +[MakerNotes, Casio, Camera] 8244 - Flash Distance: 0 +[MakerNotes, Casio, Camera] 12288 - Record Mode: Program AE +[MakerNotes, Casio, Camera] 12289 - Release Mode: Normal +[MakerNotes, Casio, Camera] 12290 - Quality: Fine +[MakerNotes, Casio, Camera] 12291 - Focus Mode: Single-Area Auto Focus +[MakerNotes, Casio, Camera] 12294 - Hometown City: New York +[MakerNotes, Casio, Camera] 12295 - Best Shot Mode: Off +[MakerNotes, Casio, Camera] 12305 - Sharpness: 0 +[MakerNotes, Casio, Camera] 12306 - Contrast: 0 +[MakerNotes, Casio, Camera] 12307 - Saturation: 0 +[MakerNotes, Casio, Camera] 12308 - ISO: 50 +[MakerNotes, Casio, Camera] 12309 - Color Mode: Off +[MakerNotes, Casio, Camera] 12310 - Enhancement: Off +[MakerNotes, Casio, Camera] 12311 - Color Filter: Off +[MakerNotes, Casio, Camera] 12315 - Art Mode: Normal +[MakerNotes, Casio, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.9 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 6.1 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/80 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.005 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.26 m (0.89 - 1.15 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 27.3 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 12.2 mm (35 mm equivalent: 74.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 7.70 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 11.2 diff --git a/ExifTool/t/Casio_4.out b/ExifTool/t/Casio_4.out new file mode 100644 index 0000000..5d40964 --- /dev/null +++ b/ExifTool/t/Casio_4.out @@ -0,0 +1,28 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: CasioQVCI.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 407 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:05:31 09:18:08-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:03-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[JFIF, JFIF, Image] 0 - JFIF Version: 1.02 +[JFIF, JFIF, Image] 2 - Resolution Unit: None +[JFIF, JFIF, Image] 3 - X Resolution: 1 +[JFIF, JFIF, Image] 5 - Y Resolution: 1 +[MakerNotes, Casio, Camera] 44 - Casio Quality: Economy +[MakerNotes, Casio, Time] 77 - Date/Time Original: 1998:10:24 12:08:57 +[MakerNotes, Casio, Camera] 98 - Model Type: KX-778 +[MakerNotes, Casio, Camera] 114 - Manufacture Index: 98082901 +[MakerNotes, Casio, Camera] 124 - Manufacture Code: 98000829 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Casio_5.out b/ExifTool/t/Casio_5.out new file mode 100644 index 0000000..7139a0d --- /dev/null +++ b/ExifTool/t/Casio_5.out @@ -0,0 +1,80 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Casio_5_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 1203 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:03-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:03-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:03-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: CASIO +[EXIF, IFD0, Camera] 272 - Camera Model Name: QV-3000EX +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Ver1.000 +[EXIF, IFD0, Time] 306 - Modify Date: 2000:03:31 10:44:15 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/114 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2000:03:31 10:44:15 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2000:03:31 10:44:15 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3.645833333 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 13.3 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2048 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1536 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 926 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Casio, Camera] 1 - Recording Mode: Single Shutter +[MakerNotes, Casio, Camera] 2 - Quality: Fine +[MakerNotes, Casio, Camera] 3 - Focus Mode: Macro +[MakerNotes, Casio, Camera] 4 - Flash Mode: Red-eye Reduction +[MakerNotes, Casio, Camera] 5 - Flash Intensity: Normal +[MakerNotes, Casio, Camera] 6 - Object Distance: 2.5 m +[MakerNotes, Casio, Camera] 7 - White Balance: Auto +[MakerNotes, Casio, Camera] 8 - Casio 0x0008: 1 +[MakerNotes, Casio, Camera] 9 - Casio 0x0009: 1 +[MakerNotes, Casio, Camera] 10 - Digital Zoom: Off +[MakerNotes, Casio, Camera] 11 - Sharpness: Normal +[MakerNotes, Casio, Camera] 12 - Contrast: Normal +[MakerNotes, Casio, Camera] 13 - Saturation: Normal +[MakerNotes, Casio, Camera] 14 - Casio 0x000e: 0 +[MakerNotes, Casio, Camera] 15 - Casio 0x000f: 0 +[MakerNotes, Casio, Camera] 16 - Casio 0x0010: 0 +[MakerNotes, Casio, Camera] 17 - Casio 0x0011: 8126553 +[MakerNotes, Casio, Camera] 18 - Casio 0x0012: 16 +[MakerNotes, Casio, Camera] 19 - Casio 0x0013: 16 +[MakerNotes, Casio, Camera] 20 - ISO: 64 +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/114 +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.5 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 13.3 mm diff --git a/ExifTool/t/Casio_6.out b/ExifTool/t/Casio_6.out new file mode 100644 index 0000000..3a09156 --- /dev/null +++ b/ExifTool/t/Casio_6.out @@ -0,0 +1,117 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Casio_6_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 1699 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:03-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:03-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:03-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: CASIO COMPUTER CO.,LTD +[EXIF, IFD0, Camera] 272 - Camera Model Name: EX-Z3 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: 1.00 +[EXIF, IFD0, Time] 306 - Modify Date: 2003:06:18 16:54:37 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/80 +[EXIF, ExifIFD, Image] 33437 - F Number: 3.9 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:06:18 16:54:37 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:06:18 16:54:37 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 0.533203125 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.6 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 12.2 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2048 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1536 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Manual +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: undef +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 74 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1396 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Casio, Image] 2 - Preview Image Size: 320x240 +[MakerNotes, Casio, Image] 3 - Preview Image Length: 26 +[MakerNotes, Casio, Image] 4 - Preview Image Start: 1424 +[MakerNotes, Casio, Preview] 8192 - Preview Image: (Binary data 26 bytes) +[MakerNotes, Casio, Camera] 8193 - Firmware Date: 2003:02:28 09:45 +[MakerNotes, Casio, Camera] 8194 - Casio Type 2 0x2002: +[MakerNotes, Casio, Camera] 8195 - Casio Type 2 0x2003: +[MakerNotes, Casio, Camera] 8209 - White Balance Bias: 87 93 +[MakerNotes, Casio, Camera] 8210 - White Balance: Unknown (13) +[MakerNotes, Casio, Camera] 8211 - Casio Type 2 0x2013: 2 +[MakerNotes, Casio, Camera] 8225 - AF Point Position: 0.5 0.5 +[MakerNotes, Casio, Camera] 8226 - Object Distance: 3.5 m +[MakerNotes, Casio, Camera] 8227 - Casio Type 2 0x2023: 1 +[MakerNotes, Casio, Camera] 8241 - Casio Type 2 0x2031: +[MakerNotes, Casio, Camera] 8242 - Casio Type 2 0x2032: d +[MakerNotes, Casio, Camera] 8243 - Casio Type 2 0x2033: 1 +[MakerNotes, Casio, Camera] 8244 - Flash Distance: 0 +[MakerNotes, Casio, Camera] 12288 - Record Mode: Program AE +[MakerNotes, Casio, Camera] 12289 - Release Mode: Normal +[MakerNotes, Casio, Camera] 12290 - Quality: Fine +[MakerNotes, Casio, Camera] 12291 - Focus Mode: Single-Area Auto Focus +[MakerNotes, Casio, Camera] 12293 - Casio Type 2 0x3005: 0 1 +[MakerNotes, Casio, Camera] 12294 - Hometown City: New York +[MakerNotes, Casio, Camera] 12295 - Best Shot Mode: Off +[MakerNotes, Casio, Camera] 12305 - Sharpness: 0 +[MakerNotes, Casio, Camera] 12306 - Contrast: 0 +[MakerNotes, Casio, Camera] 12307 - Saturation: 0 +[MakerNotes, Casio, Camera] 12308 - ISO: 50 +[MakerNotes, Casio, Camera] 12309 - Color Mode: Off +[MakerNotes, Casio, Camera] 12310 - Enhancement: Off +[MakerNotes, Casio, Camera] 12311 - Color Filter: Off +[MakerNotes, Casio, Camera] 12312 - Casio Type 2 0x3018: 0 +[MakerNotes, Casio, Camera] 12313 - Casio Type 2 0x3019: 1 +[MakerNotes, Casio, Camera] 12314 - Casio Type 2 0x301a: 0 +[MakerNotes, Casio, Camera] 12315 - Art Mode: Normal +[MakerNotes, Casio, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00140014 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x01000000 +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x05000000 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x01000000 +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.9 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/80 +[Composite, Composite, Image] Exif-LightValue - Light Value: 11.2 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 6.1 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.005 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 3.99 m (2.41 - 6.40 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 27.3 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 12.2 mm (35 mm equivalent: 74.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 7.70 m diff --git a/ExifTool/t/DICOM.t b/ExifTool/t/DICOM.t new file mode 100644 index 0000000..241b3fe --- /dev/null +++ b/ExifTool/t/DICOM.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/DICOM.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::DICOM; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'DICOM'; +my $testnum = 1; + +# test 2: Extract information from DICOM.dcm +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/DICOM.dcm'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/DICOM_2.out b/ExifTool/t/DICOM_2.out new file mode 100644 index 0000000..20bde76 --- /dev/null +++ b/ExifTool/t/DICOM_2.out @@ -0,0 +1,109 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: DICOM.dcm +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1860 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2005:11:14 14:47:21-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: DICOM +[File, File, Other] FileTypeExtension - File Type Extension: dcm +[File, File, Other] MIMEType - MIME Type: application/dicom +[DICOM, DICOM, Image] 0002,0000 - File Meta Info Group Length: 180 +[DICOM, DICOM, Image] 0002,0001 - File Meta Info Version: 0 1 +[DICOM, DICOM, Image] 0002,0002 - Media Storage SOP Class UID: MR Image Storage +[DICOM, DICOM, Image] 0002,0003 - Media Storage SOP Instance UID: 0.0.0.0.1.8811.2.20.20010413115754.12432 +[DICOM, DICOM, Image] 0002,0010 - Transfer Syntax UID: Explicit VR Little Endian +[DICOM, DICOM, Image] 0002,0012 - Implementation Class UID: 0.0.0.0 +[DICOM, DICOM, Image] 0002,0013 - Implementation Version Name: NOTSPECIFIED +[DICOM, DICOM, Image] 0002,0016 - Source Application Entity Title: NOTSPECIFIED +[DICOM, DICOM, Image] 0008,0008 - Image Type: ORIGINAL\PRIMARY\MPR +[DICOM, DICOM, Image] 0008,0016 - SOP Class UID: MR Image Storage +[DICOM, DICOM, Image] 0008,0018 - SOP Instance UID: 0.0.0.0.1.8811.2.20.20010413115754.12432 +[DICOM, DICOM, Image] 0008,0020 - Study Date: 2001:03:16 +[DICOM, DICOM, Image] 0008,0021 - Series Date: 2001:03:16 +[DICOM, DICOM, Image] 0008,0022 - Acquisition Date: 2001:03:16 +[DICOM, DICOM, Image] 0008,0023 - Content Date: 2001:03:23 +[DICOM, DICOM, Image] 0008,0030 - Study Time: 14:30:08 +[DICOM, DICOM, Image] 0008,0031 - Series Time: 14:34:14 +[DICOM, DICOM, Image] 0008,0032 - Acquisition Time: 14:34:15 +[DICOM, DICOM, Image] 0008,0033 - Content Time: 14:30:22 +[DICOM, DICOM, Image] 0008,0050 - Accession Number: +[DICOM, DICOM, Image] 0008,0060 - Modality: MR +[DICOM, DICOM, Image] 0008,0070 - Manufacturer: GE Medical Systems +[DICOM, DICOM, Image] 0008,0080 - Institution Name: +[DICOM, DICOM, Image] 0008,0090 - Referring Physician Name: +[DICOM, DICOM, Image] 0008,1010 - Station Name: MRS1 +[DICOM, DICOM, Image] 0008,1030 - Study Description: BRAIN +[DICOM, DICOM, Image] 0008,103E - Series Description: FSE PD AXIAL OBL +[DICOM, DICOM, Image] 0008,1050 - Performing Physician Name: +[DICOM, DICOM, Image] 0008,1070 - Operators Name: EC +[DICOM, DICOM, Image] 0008,1090 - Manufacturers Model Name: SIGNA +[DICOM, DICOM, Image] 0010,0010 - Patient Name: +[DICOM, DICOM, Image] 0010,0020 - Patient ID: 123456 +[DICOM, DICOM, Image] 0010,0030 - Patient Birth Date: +[DICOM, DICOM, Image] 0010,0040 - Patient Sex: F +[DICOM, DICOM, Image] 0010,1010 - Patient Age: 028Y +[DICOM, DICOM, Image] 0010,1030 - Patient Weight: 61.2350 +[DICOM, DICOM, Image] 0010,21B0 - Additional Patient History: +[DICOM, DICOM, Image] 0018,0020 - Scanning Sequence: SE +[DICOM, DICOM, Image] 0018,0021 - Sequence Variant: SK +[DICOM, DICOM, Image] 0018,0022 - Scan Options: SP +[DICOM, DICOM, Image] 0018,0023 - MR Acquisition Type: 2D +[DICOM, DICOM, Image] 0018,0024 - Sequence Name: fse +[DICOM, DICOM, Image] 0018,0050 - Slice Thickness: 5.00000 +[DICOM, DICOM, Image] 0018,0080 - Repetition Time: 2300.00 +[DICOM, DICOM, Image] 0018,0081 - Echo Time: 22.0000 +[DICOM, DICOM, Image] 0018,0083 - Number Of Averages: 1.00000 +[DICOM, DICOM, Image] 0018,0084 - Imaging Frequency: 63.8615 +[DICOM, DICOM, Image] 0018,0086 - Echo Number: 1 +[DICOM, DICOM, Image] 0018,0087 - Magnetic Field Strength: 1.50000 +[DICOM, DICOM, Image] 0018,0088 - Spacing Between Slices: 2.00000 +[DICOM, DICOM, Image] 0018,0089 - Number Of Phase Encoding Steps: 256 +[DICOM, DICOM, Image] 0018,0091 - Echo Train Length: 8 +[DICOM, DICOM, Image] 0018,0095 - Pixel Bandwidth: 31.2500 +[DICOM, DICOM, Image] 0018,1020 - Software Version: 3 +[DICOM, DICOM, Image] 0018,1030 - Protocol Name: CLINICAL BRAIN +[DICOM, DICOM, Image] 0018,1088 - Heart Rate: 0 +[DICOM, DICOM, Image] 0018,1090 - Cardiac Number Of Images: 0 +[DICOM, DICOM, Image] 0018,1094 - Trigger Window: 0 +[DICOM, DICOM, Image] 0018,1100 - Reconstruction Diameter: 220.000 +[DICOM, DICOM, Image] 0018,1250 - Receive Coil Name: HEAD +[DICOM, DICOM, Image] 0018,1310 - Acquisition Matrix: 0 256 256 0 +[DICOM, DICOM, Image] 0018,1312 - In Plane Phase Encoding Direction: ROW +[DICOM, DICOM, Image] 0018,1314 - Flip Angle: 90 +[DICOM, DICOM, Image] 0018,1316 - SAR: 0.0313309 +[DICOM, DICOM, Image] 0018,5100 - Patient Position: HFS +[DICOM, DICOM, Image] 0020,000D - Study Instance UID: 0.0.0.0.2.8811.20010413115754.12432 +[DICOM, DICOM, Image] 0020,000E - Series Instance UID: 0.0.0.0.3.8811.2.20010413115754.12432 +[DICOM, DICOM, Image] 0020,0010 - Study ID: 8811 +[DICOM, DICOM, Image] 0020,0011 - Series Number: 2 +[DICOM, DICOM, Image] 0020,0012 - Acquisition Number: 31763 +[DICOM, DICOM, Image] 0020,0013 - Instance Number: 20 +[DICOM, DICOM, Image] 0020,0020 - Patient Orientation: L\PH +[DICOM, DICOM, Image] 0020,0030 - Image Position: -110.500\-96.2063\59.0425 +[DICOM, DICOM, Image] 0020,0032 - Image Position Patient: -110.500\-96.2063\59.0425 +[DICOM, DICOM, Image] 0020,0035 - Image Orientation: 1.00000\0.00000\0.00000\0.00000\0.990960\0.134158 +[DICOM, DICOM, Image] 0020,0037 - Image Orientation Patient: 1.00000\0.00000\0.00000\0.00000\0.990960\0.134158 +[DICOM, DICOM, Image] 0020,0052 - Frame Of Reference UID: 0.0.0.0.4.8811.2.20010413115754.12432 +[DICOM, DICOM, Image] 0020,1002 - Images In Acquisition: 1 +[DICOM, DICOM, Image] 0020,1040 - Position Reference Indicator: NA +[DICOM, DICOM, Image] 0020,1041 - Slice Location: 73.8000 +[DICOM, DICOM, Image] 0028,0002 - Samples Per Pixel: 1 +[DICOM, DICOM, Image] 0028,0004 - Photometric Interpretation: MONOCHROME2 +[DICOM, DICOM, Image] 0028,0010 - Rows: 256 +[DICOM, DICOM, Image] 0028,0011 - Columns: 256 +[DICOM, DICOM, Image] 0028,0030 - Pixel Spacing: 0.859375\0.859375 +[DICOM, DICOM, Image] 0028,0100 - Bits Allocated: 16 +[DICOM, DICOM, Image] 0028,0101 - Bits Stored: 16 +[DICOM, DICOM, Image] 0028,0102 - High Bit: 15 +[DICOM, DICOM, Image] 0028,0103 - Pixel Representation: Signed +[DICOM, DICOM, Image] 0028,0106 - Smallest Image Pixel Value: 0 +[DICOM, DICOM, Image] 0028,0107 - Largest Image Pixel Value: 784 +[DICOM, DICOM, Image] 0028,0120 - Pixel Padding Value: 0 +[DICOM, DICOM, Image] 0028,1050 - Window Center: 0 +[DICOM, DICOM, Image] 0028,1051 - Window Width: 0 +[DICOM, DICOM, Image] 0028,1052 - Rescale Intercept: 0 +[DICOM, DICOM, Image] 0028,1053 - Rescale Slope: 1 +[DICOM, DICOM, Image] 0028,1054 - Rescale Type: SIGNAL INTENSITY (UNITLESS) +[DICOM, DICOM, Image] 7Fxx,0010 - Pixel Data: (Binary data 53 bytes) diff --git a/ExifTool/t/DNG.t b/ExifTool/t/DNG.t new file mode 100644 index 0000000..ca37222 --- /dev/null +++ b/ExifTool/t/DNG.t @@ -0,0 +1,40 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/DNG.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::DNG; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'DNG'; +my $testnum = 1; + +# test 2: Extract information from DNG.dng +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/DNG.dng'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Test writing maker notes information +{ + ++$testnum; + my @writeInfo = ( + [ OwnerName => 'Just Me' ], + [ OriginalDecisionData => "\xff\xff\xff\xff\x03\0\0\0\0\0\0\0\0\0\0\0\x08\0\0\0Test", Protected => 1 ], + ); + my @tags = qw(OwnerName OriginalDecisionData Warning Error); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/DNG.dng', \@tags); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/DNG_2.out b/ExifTool/t/DNG_2.out new file mode 100644 index 0000000..d93e2dc --- /dev/null +++ b/ExifTool/t/DNG_2.out @@ -0,0 +1,329 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: DNG.dng +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 14 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:02:26 09:30:53-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:34-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: DNG +[File, File, Other] FileTypeExtension - File Type Extension: dng +[File, File, Other] MIMEType - MIME Type: image/x-adobe-dng +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[EXIF, IFD0, Image] 254 - Subfile Type: Reduced-resolution image +[EXIF, IFD0, Image] 256 - Image Width: 8 +[EXIF, IFD0, Image] 257 - Image Height: 8 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 259 - Compression: Uncompressed +[EXIF, IFD0, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS 350D DIGITAL +[EXIF, IFD0, Image] 273 - Strip Offsets: 13470 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD0, Image] 278 - Rows Per Strip: 8 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 192 +[EXIF, IFD0, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD0, Time] 306 - Modify Date: 2005:08:03 18:59:18 +[EXIF, IFD0, Author] 315 - Artist: unknown +[EXIF, SubIFD, Image] 254 - Subfile Type: Full-resolution image +[EXIF, SubIFD, Image] 256 - Image Width: 3516 +[EXIF, SubIFD, Image] 257 - Image Height: 2328 +[EXIF, SubIFD, Image] 258 - Bits Per Sample: 16 +[EXIF, SubIFD, Image] 259 - Compression: JPEG +[EXIF, SubIFD, Image] 262 - Photometric Interpretation: Color Filter Array +[EXIF, SubIFD, Image] 277 - Samples Per Pixel: 1 +[EXIF, SubIFD, Image] 284 - Planar Configuration: Chunky +[EXIF, SubIFD, Image] 322 - Tile Width: 256 +[EXIF, SubIFD, Image] 323 - Tile Length: 240 +[EXIF, SubIFD, Image] 324 - Tile Offsets: 12494 +[EXIF, SubIFD, Image] 325 - Tile Byte Counts: 22 +[EXIF, SubIFD, Image] 33421 - CFA Repeat Pattern Dim: 2 2 +[EXIF, SubIFD, Image] 33422 - CFA Pattern 2: 0 1 1 2 +[EXIF, SubIFD, Image] 50710 - CFA Plane Color: Red,Green,Blue +[EXIF, SubIFD, Image] 50711 - CFA Layout: Rectangular +[EXIF, SubIFD, Image] 50713 - Black Level Repeat Dim: 1 1 +[EXIF, SubIFD, Image] 50714 - Black Level: 0 +[EXIF, SubIFD, Image] 50717 - White Level: 4095 +[EXIF, SubIFD, Image] 50718 - Default Scale: 1 1 +[EXIF, SubIFD, Image] 50719 - Default Crop Origin: 10 5 +[EXIF, SubIFD, Image] 50720 - Default Crop Size: 3456 2304 +[EXIF, SubIFD, Image] 50733 - Bayer Green Split: 0 +[EXIF, SubIFD, Image] 50738 - Anti Alias Strength: 1 +[EXIF, SubIFD, Image] 50780 - Best Quality Scale: 1 +[EXIF, SubIFD, Image] 50829 - Active Area: 14 42 2328 3516 +[EXIF, SubIFD, Image] 50830 - Masked Areas: 14 0 2328 40 +[EXIF, SubIFD1, Image] 254 - Subfile Type: Reduced-resolution image +[EXIF, SubIFD1, Image] 256 - Image Width: 1024 +[EXIF, SubIFD1, Image] 257 - Image Height: 683 +[EXIF, SubIFD1, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, SubIFD1, Image] 259 - Compression: JPEG +[EXIF, SubIFD1, Image] 262 - Photometric Interpretation: YCbCr +[EXIF, SubIFD1, Image] 273 - Preview Image Start: 12780 +[EXIF, SubIFD1, Image] 277 - Samples Per Pixel: 3 +[EXIF, SubIFD1, Image] 278 - Rows Per Strip: 683 +[EXIF, SubIFD1, Image] 279 - Preview Image Length: 26 +[EXIF, SubIFD1, Image] 284 - Planar Configuration: Chunky +[EXIF, SubIFD1, Image] 529 - Y Cb Cr Coefficients: 0.299 0.587 0.114 +[EXIF, SubIFD1, Image] 530 - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, SubIFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, SubIFD1, Image] 532 - Reference Black White: 0 255 128 255 128 255 +[EXIF, SubIFD2, Image] 254 - Subfile Type: Reduced-resolution image +[EXIF, SubIFD2, Image] 256 - Image Width: 3456 +[EXIF, SubIFD2, Image] 257 - Image Height: 2304 +[EXIF, SubIFD2, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, SubIFD2, Image] 259 - Compression: JPEG +[EXIF, SubIFD2, Image] 262 - Photometric Interpretation: YCbCr +[EXIF, SubIFD2, Image] 273 - Jpg From Raw Start: 13070 +[EXIF, SubIFD2, Image] 277 - Samples Per Pixel: 3 +[EXIF, SubIFD2, Image] 278 - Rows Per Strip: 2304 +[EXIF, SubIFD2, Image] 279 - Jpg From Raw Length: 29 +[EXIF, SubIFD2, Image] 284 - Planar Configuration: Chunky +[EXIF, SubIFD2, Image] 529 - Y Cb Cr Coefficients: 0.299 0.587 0.114 +[EXIF, SubIFD2, Image] 530 - Y Cb Cr Sub Sampling: YCbCr4:4:4 (1 1) +[EXIF, SubIFD2, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, SubIFD2, Image] 532 - Reference Black White: 0 255 128 255 128 255 +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/15 +[EXIF, ExifIFD, Image] 33437 - F Number: 8.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 400 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:08:03 18:59:18 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:08:03 18:59:18 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/15 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 8.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 55.0 mm +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3954.23341 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3958.762887 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, IFD0, Image] 37393 - Image Number: 24 +[EXIF, IFD0, Image] 50706 - DNG Version: 1.1.0.0 +[EXIF, IFD0, Image] 50707 - DNG Backward Version: 1.1.0.0 +[EXIF, IFD0, Image] 50708 - Unique Camera Model: Canon EOS 350D +[EXIF, IFD0, Image] 50709 - Localized Camera Model: Canon EOS 350D Digital +[EXIF, IFD0, Image] 50721 - Color Matrix 1: 0.6159 -0.0945 -0.0745 -0.6846 1.3563 0.3684 -0.0802 0.1086 0.7555 +[EXIF, IFD0, Image] 50722 - Color Matrix 2: 0.6018 -0.0617 -0.0965 -0.8645 1.5881 0.2975 -0.153 0.1719 0.7642 +[EXIF, IFD0, Image] 50723 - Camera Calibration 1: 1.0069 0 0 0 1 0 0 0 1.0027 +[EXIF, IFD0, Image] 50724 - Camera Calibration 2: 1.0069 0 0 0 1 0 0 0 1.0027 +[EXIF, IFD0, Image] 50727 - Analog Balance: 1 1 1 +[EXIF, IFD0, Image] 50728 - As Shot Neutral: 0.592408 1 0.501692 +[EXIF, IFD0, Image] 50730 - Baseline Exposure: 0.25 +[EXIF, IFD0, Image] 50731 - Baseline Noise: 1.33 +[EXIF, IFD0, Image] 50732 - Baseline Sharpness: 1.5 +[EXIF, IFD0, Image] 50734 - Linear Response Limit: 1 +[EXIF, IFD0, Camera] 50735 - Camera Serial Number: 012345678 +[EXIF, IFD0, Camera] 50736 - DNG Lens Info: 18-55mm f/? +[EXIF, IFD0, Image] 50739 - Shadow Scale: 1 +[EXIF, IFD0, Image] 50778 - Calibration Illuminant 1: Standard Light A +[EXIF, IFD0, Image] 50779 - Calibration Illuminant 2: D65 +[EXIF, IFD0, Image] 50781 - Raw Data Unique ID: 0358DB4E08632D90925171A6BB8848A2 +[EXIF, IFD0, Image] 50827 - Original Raw File Name: Canon350D.CR2 +[EXIF, SubIFD2, Preview] Exif-JpgFromRaw - Jpg From Raw: (Binary data 29 bytes) +[EXIF, SubIFD1, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[EXIF, IFD0, Preview] Exif-ThumbnailTIFF - Thumbnail TIFF: (Binary data 408 bytes) +[XMP, XMP-x, Document] xmptk - XMP Toolkit: XMP toolkit 3.0-28, framework 1.6 +[XMP, XMP-exif, Image] ExifVersion - Exif Version: 0221 +[XMP, XMP-exif, Image] ExposureTime - Exposure Time: 1/15 +[XMP, XMP-exif, Image] ShutterSpeedValue - Shutter Speed Value: 1/15 +[XMP, XMP-exif, Image] FNumber - F Number: 8.0 +[XMP, XMP-exif, Image] ApertureValue - Aperture Value: 8.0 +[XMP, XMP-exif, Camera] ExposureProgram - Exposure Program: Aperture-priority AE +[XMP, XMP-exif, Image] ISOSpeedRatings - ISO: 400 +[XMP, XMP-exif, Time] DateTimeOriginal - Date/Time Original: 2005:08:03 18:59:18-04:00 +[XMP, XMP-exif, Time] DateTimeDigitized - Date/Time Digitized: 2005:08:03 18:59:18-04:00 +[XMP, XMP-exif, Image] ExposureBiasValue - Exposure Compensation: 0 +[XMP, XMP-exif, Camera] MaxApertureValue - Max Aperture Value: 4.0 +[XMP, XMP-exif, Camera] MeteringMode - Metering Mode: Average +[XMP, XMP-exif, Camera] FlashFired - Flash Fired: False +[XMP, XMP-exif, Camera] FlashReturn - Flash Return: No return detection +[XMP, XMP-exif, Camera] FlashMode - Flash Mode: Off +[XMP, XMP-exif, Camera] FlashFunction - Flash Function: False +[XMP, XMP-exif, Camera] FlashRedEyeMode - Flash Red Eye Mode: False +[XMP, XMP-exif, Camera] FocalLength - Focal Length: 55.0 mm +[XMP, XMP-exif, Image] CustomRendered - Custom Rendered: Normal +[XMP, XMP-exif, Camera] ExposureMode - Exposure Mode: Auto +[XMP, XMP-exif, Camera] WhiteBalance - White Balance: Auto +[XMP, XMP-exif, Camera] SceneCaptureType - Scene Capture Type: Standard +[XMP, XMP-exif, Camera] FocalPlaneXResolution - Focal Plane X Resolution: 3954.23340961098 +[XMP, XMP-exif, Camera] FocalPlaneYResolution - Focal Plane Y Resolution: 3958.76288659794 +[XMP, XMP-exif, Camera] FocalPlaneResolutionUnit - Focal Plane Resolution Unit: inches +[XMP, XMP-aux, Camera] SerialNumber - Serial Number: 012345678 +[XMP, XMP-aux, Camera] LensInfo - Lens Info: 18-55mm f/? +[XMP, XMP-aux, Camera] Lens - Lens: 18.0-55.0 mm +[XMP, XMP-aux, Camera] ImageNumber - Image Number: 24 +[XMP, XMP-aux, Camera] FlashCompensation - Flash Compensation: 0 +[XMP, XMP-aux, Camera] OwnerName - Owner Name: unknown +[XMP, XMP-aux, Camera] Firmware - Firmware: 1.0.2 +[XMP, XMP-tiff, Camera] Make - Make: Canon +[XMP, XMP-tiff, Camera] Model - Camera Model Name: Canon EOS 350D DIGITAL +[XMP, XMP-tiff, Time] DateTime - Date/Time Modified: 2005:08:03 18:59:18-04:00 +[XMP, XMP-tiff, Image] Orientation - Orientation: Horizontal (normal) +[XMP, XMP-dc, Other] Creator - Creator: unknown +[MakerNotes, Canon, Camera] 1 - Macro Mode: Normal +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Single +[MakerNotes, Canon, Camera] 7 - Focus Mode: One-shot AF +[MakerNotes, Canon, Camera] 9 - Record Mode: CR2 +[MakerNotes, Canon, Camera] 10 - Canon Image Size: n/a +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: None +[MakerNotes, Canon, Camera] 13 - Contrast: Normal +[MakerNotes, Canon, Camera] 14 - Saturation: Normal +[MakerNotes, Canon, Camera] 15 - Sharpness: 0 +[MakerNotes, Canon, Camera] 17 - Metering Mode: Center-weighted average +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Aperture-priority AE +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 4 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 27 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 32 - Focus Continuous: Single +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 0 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 0 +[MakerNotes, Canon, Camera] 40 - Photo Effect: Off +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Image] 0 - Focal Type: Zoom +[MakerNotes, Canon, Image] 1 - Focal Length: 55 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.04 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.37 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 400 +[MakerNotes, Canon, Image] 3 - Measured EV: 8.00 +[MakerNotes, Canon, Image] 4 - Target Aperture: 8 +[MakerNotes, Canon, Image] 5 - Target Exposure Time: 1/15 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 21 - F Number: 8 +[MakerNotes, Canon, Image] 22 - Exposure Time: 1/16 +[MakerNotes, Canon, Image] 23 - Measured EV 2: 8.125 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 0 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: Canon EOS 350D DIGITAL +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware 1.0.2 +[MakerNotes, Canon, Camera] 9 - Owner Name: unknown +[MakerNotes, Canon, Camera] 12 - Serial Number: 0012345678 +[MakerNotes, CanonCustom, Camera] 0 - Set Button Cross Keys Func: Normal +[MakerNotes, CanonCustom, Camera] 1 - Long Exposure Noise Reduction: Off +[MakerNotes, CanonCustom, Camera] 2 - Flash Sync Speed Av: Auto +[MakerNotes, CanonCustom, Camera] 3 - Shutter-AE Lock: AF/AE lock +[MakerNotes, CanonCustom, Camera] 4 - AF Assist Beam: Emits +[MakerNotes, CanonCustom, Camera] 5 - Exposure Level Increments: 1/3 Stop +[MakerNotes, CanonCustom, Camera] 6 - Mirror Lockup: Disable +[MakerNotes, CanonCustom, Camera] 7 - E-TTL II: Evaluative +[MakerNotes, CanonCustom, Camera] 8 - Shutter Curtain Sync: 1st-curtain sync +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS Digital Rebel XT / 350D / Kiss Digital N +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3456 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2304 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3456 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2304 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 189 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 188 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 0 -1237 -742 0 742 1237 0 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: -617 0 0 0 0 0 617 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: 3 +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 2 +[MakerNotes, Canon, Camera] 131 - Original Decision Data Offset: 0 +[MakerNotes, Canon, Image] 1 - File Number: 100-0024 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 8 - Long Exposure Noise Reduction 2: Off +[MakerNotes, Canon, Image] 9 - WB Bracket Mode: Off +[MakerNotes, Canon, Image] 12 - WB Bracket Value AB: 0 +[MakerNotes, Canon, Image] 13 - WB Bracket Value GM: 0 +[MakerNotes, Canon, Image] 14 - Filter Effect: None +[MakerNotes, Canon, Image] 15 - Toning Effect: None +[MakerNotes, Canon, Image] 1 - Tone Curve: Standard +[MakerNotes, Canon, Image] 3 - Sharpness Frequency: n/a +[MakerNotes, Canon, Image] 4 - Sensor Red Level: 0 +[MakerNotes, Canon, Image] 5 - Sensor Blue Level: 0 +[MakerNotes, Canon, Image] 6 - White Balance Red: 0 +[MakerNotes, Canon, Image] 7 - White Balance Blue: 0 +[MakerNotes, Canon, Image] 9 - Color Temperature: 5200 +[MakerNotes, Canon, Image] 10 - Picture Style: None +[MakerNotes, Canon, Image] 11 - Digital Gain: 0 +[MakerNotes, Canon, Image] 12 - WB Shift AB: 0 +[MakerNotes, Canon, Image] 13 - WB Shift GM: 0 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 580 1024 1024 500 +[MakerNotes, Canon, Camera] 208 - VRD Offset: 0 +[MakerNotes, Canon, Image] 1 - Sensor Width: 3516 +[MakerNotes, Canon, Image] 2 - Sensor Height: 2328 +[MakerNotes, Canon, Image] 5 - Sensor Left Border: 52 +[MakerNotes, Canon, Image] 6 - Sensor Top Border: 19 +[MakerNotes, Canon, Image] 7 - Sensor Right Border: 3507 +[MakerNotes, Canon, Image] 8 - Sensor Bottom Border: 2322 +[MakerNotes, Canon, Image] 9 - Black Mask Left Border: 0 +[MakerNotes, Canon, Image] 10 - Black Mask Top Border: 0 +[MakerNotes, Canon, Image] 11 - Black Mask Right Border: 0 +[MakerNotes, Canon, Image] 12 - Black Mask Bottom Border: 0 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels As Shot: 2002 1185 1187 2364 +[MakerNotes, Canon, Camera] 29 - Color Temp As Shot: 3064 +[MakerNotes, Canon, Camera] 30 - WB RGGB Levels Auto: 1720 1022 1024 2031 +[MakerNotes, Canon, Camera] 34 - Color Temp Auto: 3064 +[MakerNotes, Canon, Camera] 35 - WB RGGB Levels Daylight: 2312 1022 1024 1501 +[MakerNotes, Canon, Camera] 39 - Color Temp Daylight: 5200 +[MakerNotes, Canon, Camera] 40 - WB RGGB Levels Shade: 2714 1022 1024 1246 +[MakerNotes, Canon, Camera] 44 - Color Temp Shade: 7000 +[MakerNotes, Canon, Camera] 45 - WB RGGB Levels Cloudy: 2512 1022 1024 1355 +[MakerNotes, Canon, Camera] 49 - Color Temp Cloudy: 6000 +[MakerNotes, Canon, Camera] 50 - WB RGGB Levels Tungsten: 1523 1022 1024 2397 +[MakerNotes, Canon, Camera] 54 - Color Temp Tungsten: 3201 +[MakerNotes, Canon, Camera] 55 - WB RGGB Levels Fluorescent: 1944 1022 1024 2015 +[MakerNotes, Canon, Camera] 59 - Color Temp Fluorescent: 3905 +[MakerNotes, Canon, Camera] 60 - WB RGGB Levels Flash: 2606 1022 1024 1308 +[MakerNotes, Canon, Camera] 64 - Color Temp Flash: 6440 +[MakerNotes, Canon, Camera] 65 - WB RGGB Levels Custom 1: 2312 1022 1024 1501 +[MakerNotes, Canon, Camera] 69 - Color Temp Custom 1: 5200 +[MakerNotes, Canon, Camera] 70 - WB RGGB Levels Custom 2: 2312 1022 1024 1501 +[MakerNotes, Canon, Camera] 74 - Color Temp Custom 2: 5200 +[MakerNotes, Canon, Camera] 2 - Color Tone: Normal +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Single-frame Shooting +[Composite, Composite, Camera] Canon-ISO - ISO: 400 +[Composite, Composite, Camera] Canon-Lens - Lens: 18.0 - 55.0 mm +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Aperture-priority AE +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 2002 1185 1187 2364 +[Composite, Composite, Image] Exif-Aperture - Aperture: 8.0 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.993255 +[Composite, Composite, Image] Exif-CFAPattern - CFA Pattern: [Red,Green][Green,Blue] +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3516x2328 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown 18-55mm +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 8.2 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.688027 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/15 +[Composite, Composite, Camera] XMP-Flash - Flash: Off, Did not fire +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 18.0 - 55.0 mm (35 mm equivalent: 29.2 - 89.2 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 22.8 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 55.0 mm (35 mm equivalent: 89.2 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 20.42 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 7.9 diff --git a/ExifTool/t/DNG_3.out b/ExifTool/t/DNG_3.out new file mode 100644 index 0000000..b01e01e --- /dev/null +++ b/ExifTool/t/DNG_3.out @@ -0,0 +1,4 @@ +[XMP, XMP-aux, Camera] OwnerName - Owner Name: Just Me +[EXIF, ExifIFD, Image] 42032 - Owner Name: Just Me +[MakerNotes, Canon, Camera] 9 - Owner Name: Just Me +[Composite, Composite, Camera] Canon-OriginalDecisionData - Original Decision Data: (Binary data 24 bytes) diff --git a/ExifTool/t/DPX.t b/ExifTool/t/DPX.t new file mode 100644 index 0000000..718af78 --- /dev/null +++ b/ExifTool/t/DPX.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/DPX.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::DPX; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'DPX'; +my $testnum = 1; + +# test 2: Extract information from DPX.dpx +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/DPX.dpx'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/DPX_2.out b/ExifTool/t/DPX_2.out new file mode 100644 index 0000000..a2a0f52 --- /dev/null +++ b/ExifTool/t/DPX_2.out @@ -0,0 +1,42 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: DPX.dpx +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.1 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2013:09:20 08:49:38-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:34-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: DPX +[File, File, Other] FileTypeExtension - File Type Extension: dpx +[File, File, Other] MIMEType - MIME Type: image/x-dpx +[File, File, Image] 0 - Byte Order: Big-endian +[File, File, Image] 8 - Header Version: V1.0 +[File, File, Image] 16 - DPX File Size: 12812288 +[File, File, Image] 20 - Ditto Key: New +[File, File, Image] 36 - Image File Name: Image filename +[File, File, Time] 136 - Create Date: 2010:08:03 08:38:16 +[File, File, Author] 160 - Creator: Creator +[File, File, Image] 260 - Project: Project name +[File, File, Author] 460 - Copyright: Copyright info +[File, File, Image] 660 - Encryption Key: ffffffff +[File, File, Image] 768 - Orientation: Horizontal (normal) +[File, File, Image] 770 - Image Elements: 1 +[File, File, Image] 772 - Image Width: 2048 +[File, File, Image] 776 - Image Height: 1556 +[File, File, Image] 780 - Data Sign: Unsigned +[File, File, Image] 800 - Components Configuration: R, G, B +[File, File, Image] 801 - Transfer Characteristic: Printing density +[File, File, Image] 802 - Colorimetric Specification: Printing density +[File, File, Image] 803 - Bit Depth: 10 +[File, File, Image] 820 - Image Description: CPD +[File, File, Image] 1432 - Source File Name: +[File, File, Image] 1532 - Source Create Date: +[File, File, Image] 1556 - Input Device Name: SPIRIT-4K DATACINE +[File, File, Image] 1588 - Input Device Serial Number: 01018 +[File, File, Image] 1724 - Original Frame Rate: 0 +[File, File, Image] 1732 - Frame ID: +[File, File, Image] 1764 - Slate Information: +[File, File, Image] 1920 - Time Code: 4117 +[File, File, Image] 2048 - User ID: Thomson BTS +[Composite, Composite, Image] Exif-ImageSize - Image Size: 2048x1556 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 3.2 diff --git a/ExifTool/t/DV.t b/ExifTool/t/DV.t new file mode 100644 index 0000000..db5648c --- /dev/null +++ b/ExifTool/t/DV.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/DV.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::DV; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'DV'; +my $testnum = 1; + +# test 2: Extract information from an DV file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/DV.dv'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/DV_2.out b/ExifTool/t/DV_2.out new file mode 100644 index 0000000..0929d30 --- /dev/null +++ b/ExifTool/t/DV_2.out @@ -0,0 +1,26 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: DV.dv +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 4.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:12:25 08:53:20-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:34-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: DV +[File, File, Other] FileTypeExtension - File Type Extension: dv +[File, File, Other] MIMEType - MIME Type: video/x-dv +[DV, DV, Time] DateTimeOriginal - Date/Time Original: 2010:02:16 21:36:28 +[DV, DV, Video] ImageWidth - Image Width: 720 +[DV, DV, Video] ImageHeight - Image Height: 576 +[DV, DV, Video] Duration - Duration: 0.00 s +[DV, DV, Video] TotalBitrate - Total Bitrate: 28.8 Mbps +[DV, DV, Video] VideoFormat - Video Format: IEC 61834 - 625/50 (PAL) +[DV, DV, Video] VideoScanType - Video Scan Type: Interlaced +[DV, DV, Video] FrameRate - Frame Rate: 25 +[DV, DV, Video] AspectRatio - Aspect Ratio: 16:9 +[DV, DV, Video] Colorimetry - Colorimetry: 4:2:0 +[DV, DV, Audio] AudioChannels - Audio Channels: 4 +[DV, DV, Audio] AudioSampleRate - Audio Sample Rate: 32000 +[DV, DV, Audio] AudioBitsPerSample - Audio Bits Per Sample: 12 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 720x576 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.415 diff --git a/ExifTool/t/DjVu.t b/ExifTool/t/DjVu.t new file mode 100644 index 0000000..173d013 --- /dev/null +++ b/ExifTool/t/DjVu.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/DjVu.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::DjVu; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'DjVu'; +my $testnum = 1; + +# test 2: Extract information from DjVu.djvu +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/DjVu.djvu'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/DjVu_2.out b/ExifTool/t/DjVu_2.out new file mode 100644 index 0000000..9f6765e --- /dev/null +++ b/ExifTool/t/DjVu_2.out @@ -0,0 +1,46 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: DjVu.djvu +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 930 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:03-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: DJVU (multi-page) +[File, File, Other] FileTypeExtension - File Type Extension: djvu +[File, File, Other] MIMEType - MIME Type: image/vnd.djvu +[DjVu, DjVu, Image] 0 - Subfile Type: Single-page image +[DjVu, DjVu, Image] 0 - Image Width: 8 +[DjVu, DjVu, Image] 2 - Image Height: 8 +[DjVu, DjVu, Image] 4 - DjVu Version: 0.24 +[DjVu, DjVu, Image] 6 - Spatial Resolution: 100 +[DjVu, DjVu, Image] 8 - Gamma: 2.2 +[DjVu, DjVu, Image] 9 - Orientation: Unknown (0) +[DjVu, DjVu, Image] INCL - Included File ID: shared_anno.iff +[DjVu, DjVu, Image] 0 - Subfile Type: Shared component +[DjVu, DjVu-Meta, Author] Author - Author: Phil Harvey +[DjVu, DjVu-Meta, Image] Title - Title: DjVu Metadata Sample +[DjVu, DjVu-Meta, Image] Subject - Subject: ExifTool DjVu test image +[DjVu, DjVu-Meta, Image] Creator - Creator: ExifTool +[DjVu, DjVu-Meta, Time] CreationDate - Create Date: 2008:09:23 12:31:34-04:00 +[DjVu, DjVu-Meta, Time] ModDate - Modify Date: 2008:11:11 09:17:10-05:00 +[DjVu, DjVu-Meta, Image] Keywords - Keywords: ExifTool, Test, DjVu, XMP +[DjVu, DjVu-Meta, Image] Producer - Producer: djvused +[DjVu, DjVu-Meta, Image] note - Note: Must escape double quotes (") and backslashes (\) +[DjVu, DjVu-Meta, Image] Trapped - Trapped: Unknown +[DjVu, DjVu-Meta, Image] annote - Annotation: Did you get this? +[DjVu, DjVu-Meta, Image] url - URL: https://exiftool.org/ +[XMP, XMP-album, Image] Notes - Notes: Must escape double quotes (") and backslashes (\) +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: ExifTool DjVu test image +[XMP, XMP-dc, Author] rights - Rights: Copyright 2008 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, DjVu, XMP +[XMP, XMP-dc, Image] title - Title: DjVu Metadata Sample +[XMP, XMP-pdf, Image] Keywords - Keywords: ExifTool, Test, DjVu, XMP +[XMP, XMP-pdf, Author] Producer - Producer: djvused +[XMP, XMP-pdf, Image] Trapped - Trapped: Unknown +[XMP, XMP-xmp, Time] CreateDate - Create Date: 2008:09:23 12:31:34-04:00 +[XMP, XMP-xmp, Image] CreatorTool - Creator Tool: ExifTool +[XMP, XMP-xmp, Time] ModifyDate - Modify Date: 2008:11:11 09:17:10-05:00 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/EXE.t b/ExifTool/t/EXE.t new file mode 100644 index 0000000..7ef3c94 --- /dev/null +++ b/ExifTool/t/EXE.t @@ -0,0 +1,31 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/EXE.t". + +BEGIN { + $| = 1; print "1..7\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::EXE; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'EXE'; +my $testnum = 1; + +# tests 2-7: Extract information from various types of executable files and libraries +{ + my $exifTool = Image::ExifTool->new; + my $ext; + foreach $ext ('exe', 'macho', 'elf', 'a', 'so', 'dylib') { + ++$testnum; + my $info = $exifTool->ImageInfo("t/images/EXE.$ext", '-system:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +done(); # end diff --git a/ExifTool/t/EXE_2.out b/ExifTool/t/EXE_2.out new file mode 100644 index 0000000..b52b6c1 --- /dev/null +++ b/ExifTool/t/EXE_2.out @@ -0,0 +1,35 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: Win32 EXE +[File, File, Other] FileTypeExtension - File Type Extension: exe +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[EXE, EXE, Other] 0 - Machine Type: Intel 386 or later, and compatibles +[EXE, EXE, Time] 2 - Time Stamp: 2008:09:08 08:01:24-04:00 +[EXE, EXE, Other] 9 - Image File Characteristics: No relocs, Executable, No line numbers, No symbols, 32-bit, No debug +[EXE, EXE, Other] 10 - PE Type: PE32 +[EXE, EXE, Other] 11 - Linker Version: 2.56 +[EXE, EXE, Other] 12 - Code Size: 2048 +[EXE, EXE, Other] 14 - Initialized Data Size: 4096 +[EXE, EXE, Other] 16 - Uninitialized Data Size: 512 +[EXE, EXE, Other] 18 - Entry Point: 0x1000 +[EXE, EXE, Other] 30 - OS Version: 4.0 +[EXE, EXE, Other] 32 - Image Version: 1.0 +[EXE, EXE, Other] 34 - Subsystem Version: 4.0 +[EXE, EXE, Other] 44 - Subsystem: Windows command line +[EXE, EXE, Other] 2 - File Version Number: 1.2.3.4 +[EXE, EXE, Other] 4 - Product Version Number: 5.6.7.8 +[EXE, EXE, Other] 6 - File Flags Mask: 0x003f +[EXE, EXE, Other] 7 - File Flags: Debug +[EXE, EXE, Other] 8 - File OS: Win32 +[EXE, EXE, Other] 9 - Object File Type: Executable application +[EXE, EXE, Other] 10 - File Subtype: 0 +[EXE, EXE, Other] LanguageCode - Language Code: English (U.S.) +[EXE, EXE, Other] CharacterSet - Character Set: Unicode +[EXE, EXE, Other] CompanyName - Company Name: Phil Harvey +[EXE, EXE, Other] FileDescription - File Description: ExifTool EXE Test File +[EXE, EXE, Other] FileVersion - File Version: 1.2.3.4b +[EXE, EXE, Other] InternalName - Internal Name: Skippy the wonder cow +[EXE, EXE, Other] LegalCopyright - Legal Copyright: Copyright (c) 2008 +[EXE, EXE, Other] LegalTrademarks - Legal Trademarks: none +[EXE, EXE, Other] OriginalFilename - Original File Name: EXE.exe +[EXE, EXE, Other] ProductName - Product Name: This is not a product +[EXE, EXE, Other] ProductVersion - Product Version: 5.6.7.8b diff --git a/ExifTool/t/EXE_3.out b/ExifTool/t/EXE_3.out new file mode 100644 index 0000000..bea2d5f --- /dev/null +++ b/ExifTool/t/EXE_3.out @@ -0,0 +1,10 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: Mach-O executable +[File, File, Other] FileTypeExtension - File Type Extension: +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[EXE, EXE, Other] 0 - CPU Architecture: 32 bit +[EXE, EXE, Other] 1 - CPU Byte Order: Big endian +[EXE, EXE, Other] 3 - CPU Type: PowerPC +[EXE, EXE, Other] 4 - CPU Subtype: PowerPC (all) +[EXE, EXE, Other] 5 - Object File Type: Demand paged executable +[EXE, EXE, Other] 6 - Object Flags: No undefs, Dyld link, Two level diff --git a/ExifTool/t/EXE_4.out b/ExifTool/t/EXE_4.out new file mode 100644 index 0000000..7c67d9e --- /dev/null +++ b/ExifTool/t/EXE_4.out @@ -0,0 +1,8 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: ELF executable +[File, File, Other] FileTypeExtension - File Type Extension: +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[EXE, EXE, Other] 4 - CPU Architecture: 32 bit +[EXE, EXE, Other] 5 - CPU Byte Order: Little endian +[EXE, EXE, Other] 16 - Object File Type: Executable file +[EXE, EXE, Other] 18 - CPU Type: i386 diff --git a/ExifTool/t/EXE_5.out b/ExifTool/t/EXE_5.out new file mode 100644 index 0000000..844b8bb --- /dev/null +++ b/ExifTool/t/EXE_5.out @@ -0,0 +1,9 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: Mach-O static library +[File, File, Other] FileTypeExtension - File Type Extension: a +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[EXE, EXE, Time] 16 - Create Date: 2016:04:13 07:19:19-04:00 +[EXE, EXE, Other] 0 - CPU Architecture: 64 bit +[EXE, EXE, Other] 1 - CPU Byte Order: Little endian +[EXE, EXE, Other] 3 - CPU Type: x86 64-bit +[EXE, EXE, Other] 4 - CPU Subtype: i386 (all) 64-bit diff --git a/ExifTool/t/EXE_6.out b/ExifTool/t/EXE_6.out new file mode 100644 index 0000000..83c8d89 --- /dev/null +++ b/ExifTool/t/EXE_6.out @@ -0,0 +1,8 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: ELF shared library +[File, File, Other] FileTypeExtension - File Type Extension: so +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[EXE, EXE, Other] 4 - CPU Architecture: 64 bit +[EXE, EXE, Other] 5 - CPU Byte Order: Little endian +[EXE, EXE, Other] 16 - Object File Type: Shared object file +[EXE, EXE, Other] 18 - CPU Type: AMD x86-64 diff --git a/ExifTool/t/EXE_7.out b/ExifTool/t/EXE_7.out new file mode 100644 index 0000000..5165f94 --- /dev/null +++ b/ExifTool/t/EXE_7.out @@ -0,0 +1,10 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: Mach-O dynamic link library +[File, File, Other] FileTypeExtension - File Type Extension: dylib +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[EXE, EXE, Other] 0 - CPU Architecture: 64 bit +[EXE, EXE, Other] 1 - CPU Byte Order: Little endian +[EXE, EXE, Other] 3 - CPU Type: x86 64-bit +[EXE, EXE, Other] 4 - CPU Subtype: i386 (all) 64-bit +[EXE, EXE, Other] 5 - Object File Type: Dynamically bound shared library +[EXE, EXE, Other] 6 - Object Flags: No undefs, Dyld link, Two level, No reexported dylibs diff --git a/ExifTool/t/ExifTool.t b/ExifTool/t/ExifTool.t new file mode 100644 index 0000000..9e936ea --- /dev/null +++ b/ExifTool/t/ExifTool.t @@ -0,0 +1,403 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/ExifTool.t". + +BEGIN { + $| = 1; print "1..35\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'ExifTool'; +my $testnum = 1; + +# test 2: extract information from JPG file using name +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ExifTool.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: TIFF file using file reference and ExifTool object with options +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 1, Unknown => 1); + open(TESTFILE, 't/images/ExifTool.tif'); + my $info = $exifTool->ImageInfo(\*TESTFILE); + close(TESTFILE); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: test the Group option to extract EXIF info only +{ + ++$testnum; + my $info = ImageInfo('t/images/Canon.jpg', {Group0 => 'EXIF'}); + notOK() unless check($info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 5: extract specified tags only +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; +# don't test DateFormat because strftime output varies with locale +# $exifTool->Options(DateFormat => '%H:%M:%S %a. %b. %e, %Y'); + my @tags = ('CreateDate', 'DateTimeOriginal', 'ModifyDate', 'Orientation#', '?Resolution'); + my $info = $exifTool->ImageInfo('t/images/Canon.jpg', \@tags); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 6: test the 5 different ways to exclude tags... +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Exclude => 'ImageWidth'); + my @tagList = ( '-ImageHeight', '-Make' ); + my $info = $exifTool->ImageInfo('t/images/Canon.jpg', '-FileSize', '-*resolution', + \@tagList, {Group0 => '-MakerNotes'}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# tests 7/8: test ExtractInfo(), GetInfo(), CombineInfo() +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 0); # don't allow duplicates + $exifTool->ExtractInfo('t/images/Canon.jpg'); + my $info1 = $exifTool->GetInfo({Group0 => 'MakerNotes'}); + my $info2 = $exifTool->GetInfo({Group0 => 'EXIF'}); + my $info = $exifTool->CombineInfo($info1, $info2); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + + # combine information in different order + ++$testnum; + $info = $exifTool->CombineInfo($info2, $info1); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 9: test group options across different families +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Canon.jpg', + { Group1 => 'Canon', Group2 => '-Camera' }); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# tests 10/11: test ExtractInfo() and GetInfo() +# (uses output from test 5 for comparison) +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; +# don't test DateFormat because strftime output is system dependent +# $exifTool->Options(DateFormat => '%H:%M:%S %a. %b. %e, %Y'); + $exifTool->ExtractInfo('t/images/Canon.jpg'); + my @tags = ('createdate', 'datetimeoriginal', 'modifydate', 'orientation#', '?resolution'); + my $info = $exifTool->GetInfo(\@tags); + my $good = 1; + my @expectedTags = ('CreateDate', 'DateTimeOriginal', 'ModifyDate', 'Orientation', + 'XResolution', 'YResolution'); + for (my $i=0; $i<scalar(@tags); ++$i) { + $tags[$i] = $expectedTags[$i] or $good = 0; + } + notOK() unless $good; + print "ok $testnum\n"; + + ++$testnum; + notOK() unless check($exifTool, $info, $testname, $testnum, 5); + print "ok $testnum\n"; +} + +# tests 12/13: check precedence of tags extracted from groups +# (Note: these tests should produce the same output as 7/8, +# so the .out files from tests 7/8 are used) +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 0); # don't allow duplicates + my $info = $exifTool->ImageInfo('t/images/Canon.jpg',{Group0=>['MakerNotes','EXIF']}); + notOK() unless check($exifTool, $info, $testname, $testnum, 7); + print "ok $testnum\n"; + + # combine information in different order + ++$testnum; + $info = $exifTool->ImageInfo('t/images/Canon.jpg',{Group0=>['EXIF','MakerNotes']}); + notOK() unless check($exifTool, $info, $testname, $testnum, 8); + print "ok $testnum\n"; +} + +# tests 14/15/16: test GetGroups() +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->ExtractInfo('t/images/Canon.jpg'); + my @groups = $exifTool->GetGroups(2); + my $not; + foreach ('Camera','ExifTool','Image','Other','Time') { + my $g = shift @groups || ''; + $_ eq $g or warn("\nWrong group: $_ ne $g\n"), $not = 1; + } + @groups and $not = 1; + notOK() if $not; + print "ok $testnum\n"; + + ++$testnum; + my $info = $exifTool->GetInfo({Group0 => 'EXIF'}); + @groups = $exifTool->GetGroups($info,0); + notOK() unless @groups==1 and $groups[0] eq 'EXIF'; + print "ok $testnum\n"; + + ++$testnum; + my $testfile = "t/ExifTool_$testnum"; + open(TESTFILE,">$testfile.failed"); + my $oldSep = $/; + $/ = "\x0a"; # set input line separator + $exifTool->ExtractInfo('t/images/Canon.jpg'); + my $family = '1:2'; + @groups = $exifTool->GetGroups($family); + my $group; + foreach $group (@groups) { + next if $group eq 'ExifTool'; + print TESTFILE "---- $group ----\n"; + my $info = $exifTool->GetInfo({"Group$family" => $group}); + foreach (sort $exifTool->GetTagList($info)) { + print TESTFILE "$_ : $$info{$_}\n"; + } + } + $/ = $oldSep; # restore input line separator + close(TESTFILE); + notOK() unless testCompare("$testfile.out","$testfile.failed",$testnum); + print "ok $testnum\n"; +} + +# test 17: Test verbose output +{ + ++$testnum; + notOK() unless testVerbose($testname, $testnum, 't/images/Canon.jpg', 3); + print "ok $testnum\n"; +} + +# tests 18/19: Test Group# option with multiple groups and no duplicates +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 0); # don't allow duplicates + my $info = $exifTool->ImageInfo('t/images/Canon.jpg', + { Group0 => ['MakerNotes','EXIF'] }); + notOK() unless check($exifTool, $info, $testname, $testnum, 7); + print "ok $testnum\n"; + + ++$testnum; + $info = $exifTool->ImageInfo('t/images/Canon.jpg', + { Group0 => ['EXIF','MakerNotes'] }); + notOK() unless check($exifTool, $info, $testname, $testnum, 8); + print "ok $testnum\n"; +} + +# test 20: Test extracting a single, non-priority tag with duplicates set to 0 +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 0); + my $info = $exifTool->ImageInfo('t/images/Canon.jpg', 'EXIF:WhiteBalance'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 21: Test extracting ICC_Profile as a block +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ExifTool.tif', 'ICC_Profile'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 22: Test InsertTagValues +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my @foundTags; + $exifTool->ImageInfo('t/images/ExifTool.jpg', \@foundTags); + my $str = $exifTool->InsertTagValues(\@foundTags, '${ifd0:model;tr/i/_/} - $1ciff:3main:model'); + my $testfile = "t/ExifTool_$testnum"; + open(TESTFILE,">$testfile.failed"); + my $oldSep = $/; + $/ = "\x0a"; # set input line separator + print TESTFILE $str, "\n"; + $/ = $oldSep; # restore input line separator + close(TESTFILE); + notOK() unless testCompare("$testfile.out","$testfile.failed",$testnum); + print "ok $testnum\n"; +} + +# test 23: Test the multi-group feature in a tag name +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ExifTool.jpg', 'main:Author:IPTC3:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 24: Test a shortcut with multiple group names and a ValueConv suffix +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Canon.jpg', 'exififd:camera:common#'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 25: Test GlobalTimeShift option +{ + ++$testnum; + if (eval { require Time::Local }) { + my $exifTool = Image::ExifTool->new; + $exifTool->Options(GlobalTimeShift => '-0:1:0 0:0:0'); + # Note: can't extract system times because this could result in a different + # calculated global time offset (since I am shifting by 1 month) + my $info = $exifTool->ImageInfo('t/images/ExifTool.jpg', 'time:all', '-system:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } else { + print "ok $testnum # skip Requires Time::Local\n"; + } +} + +# test 26: Test wildcards using '#' suffix with duplicate PrintConv tags and exclusions +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + # (hack to avoid sorting in TestLib.pm because order of duplicate tags would be indeterminate) + $$exifTool{NO_SORT} = 1; + my $info = $exifTool->ImageInfo('t/images/Canon.jpg', 'encodingprocess', 'E*#', 'exposureMode', + '-ExifVersion'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 27: Test ListItem option +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(ListItem => -3); + my $info = $exifTool->ImageInfo('t/images/ExifTool.jpg', 'Subject', 'SupplementalCategories'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 28: Test FastScan = 3 +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(FastScan => 3); + my $info = $exifTool->ImageInfo('t/images/ExifTool.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 29: Test Filter +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Filter => 'tr/ /_/;tr/0-9/#/'); + my $info = $exifTool->ImageInfo('t/images/ExifTool.jpg', '-ExifToolVersion'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 30: Calculate JPEGDigest and JPEGQualityEstimate +{ + ++$testnum; + my $skip = ''; + if (eval 'require Digest::MD5') { + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Writer.jpg', 'JPEGDigest', 'JPEGQualityEstimate'); + notOK() unless check($exifTool, $info, $testname, $testnum); + } else { + $skip = ' # skip Requires Digest::MD5'; + } + print "ok $testnum$skip\n"; +} + +# test 31: Test Validate feature +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/CanonRaw.cr2', 'Validate', 'Warning', 'Error'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 32: Read JPS file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ExifTool.jps', 'jps:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 33: Test SetAlternateFile() +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetAlternateFile(File1 => 't/images/Nikon.jpg'); + $exifTool->SetAlternateFile(File3 => 't/images/FujiFilm.jpg'); + my $info = $exifTool->ImageInfo('t/images/Canon.jpg', 'file3:make', 'make', 'file1:make', 'file1:mo*'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 34: Test SetAlternateFile() with InsertTagValues() +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my @foundTags; + $exifTool->SetAlternateFile(File010 => 't/images/Nikon.jpg'); + $exifTool->ImageInfo('t/images/Canon.jpg', \@foundTags); + my $val = $exifTool->InsertTagValues(\@foundTags, '$file010:make - $make'); + my $testfile = "t/${testname}_$testnum.failed"; + my $goodfile = "t/${testname}_$testnum.out"; + open OUT, ">$testfile"; + print OUT $val,"\n"; + close OUT; + notOK() unless testCompare($goodfile, $testfile, $testnum); + print "ok $testnum\n"; +} + +# test 35: Test SetAlternateFile() with user-defined tags +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my %tags = ( + TestTag => { + Require => { + 0 => 'Model', + 1 => 'File1:Model', + }, + ValueConv => '"$val[0] -- $val[1]"', + }, + ); + Image::ExifTool::AddUserDefinedTags('Image::ExifTool::Composite', %tags); + $exifTool->SetAlternateFile(File1 => 't/images/Nikon.jpg'); + my $info = $exifTool->ImageInfo('t/images/Canon.jpg', 'TestTag'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/ExifTool_16.out b/ExifTool/t/ExifTool_16.out new file mode 100644 index 0000000..bfb1f27 --- /dev/null +++ b/ExifTool/t/ExifTool_16.out @@ -0,0 +1,185 @@ +---- Canon:Camera ---- +AFAreaHeight : 151 +AFAreaWidth : 151 +AFAreaXPositions : 1014 608 0 0 0 -608 -1014 +AFAreaYPositions : 0 0 -506 0 506 0 0 +AFImageHeight : 2048 +AFImageWidth : 3072 +AFPointsInFocus : (none) +CameraISO : n/a +CameraType : EOS Mid-range +CanonExposureMode : Manual +CanonFirmwareVersion : Firmware Version 1.1.1 +CanonFlashMode : Off +CanonImageSize : Large +CanonModelID : EOS Digital Rebel / 300D / Kiss Digital +ColorSpace (1) : sRGB +ColorTemperature : 5200 +ColorTone : Normal +ContinuousDrive : Continuous +Contrast : +1 +DigitalZoom : Unknown (-1) +EasyMode : Manual +FlashActivity : 0 +FlashBits : (none) +FocalUnits : 1/mm +FocusMode : Manual Focus (3) +FocusRange : Not Known +LensType : n/a +MacroMode : Unknown (0) +ManualFlashOutput : n/a +MaxAperture : 4 +MaxFocalLength : 55 mm +MeasuredRGGB : 998 1022 1026 808 +MeteringMode : Center-weighted average +MinAperture : 27 +MinFocalLength : 18 mm +NumAFPoints : 7 +OpticalZoomCode : n/a +OwnerName : Phil Harvey +Quality : RAW +RecordMode : CRW+THM +Saturation : +1 +SelfTimer : Off +SerialNumber : 0560018150 +SerialNumberFormat : Format 1 +Sharpness : +1 +ThumbnailImageValidArea : 0 159 7 112 +ValidAFPoints : 7 +WB_RGGBBlackLevels : 124 123 124 123 +WB_RGGBLevelsAuto : 1719 832 831 990 +WB_RGGBLevelsCloudy : 1878 832 831 903 +WB_RGGBLevelsCustom : 1722 832 831 989 +WB_RGGBLevelsDaylight : 1722 832 831 989 +WB_RGGBLevelsFlash : 1933 832 831 895 +WB_RGGBLevelsFluorescent : 1506 842 841 1381 +WB_RGGBLevelsKelvin : 1722 832 831 988 +WB_RGGBLevelsShade : 2035 832 831 839 +WB_RGGBLevelsTungsten : 1228 913 912 1668 +ZoomSourceWidth : 3072 +ZoomTargetWidth : 3072 +---- Canon:Image ---- +AEBBracketValue : 0 +AutoExposureBracketing : Off +AutoISO : 100 +AutoRotate : None +BaseISO : 100 +BracketMode : Off +BracketShotNumber : 0 +BracketValue : 0 +BulbDuration : 4 +CanonFileLength : 4480822 +CanonImageHeight : 2048 +CanonImageType : CRW:EOS DIGITAL REBEL CMOS RAW +CanonImageWidth : 3072 +ControlMode : Camera Local Control +ExposureCompensation : 0 +ExposureTime (1) : 128 +FNumber (1) : 14 +FileNumber : 118-1861 +FlashExposureComp : 0 +FlashGuideNumber : 0 +FocalLength (1) : 34 mm +FocalPlaneXSize : 23.22 mm +FocalPlaneYSize : 15.49 mm +FocusDistanceLower : 5.46 m +FocusDistanceUpper : inf +MeasuredEV : -1.25 +MeasuredEV2 : -1.25 +NDFilter : n/a +SelfTimer2 : 0 +SequenceNumber : 0 +SlowShutter : None +TargetAperture : 14 +WhiteBalance : Auto +---- Composite:Camera ---- +BlueBalance : 1.190619 +CircleOfConfusion : 0.019 mm +DriveMode : Continuous Shooting +FocalLength35efl : 34.0 mm (35 mm equivalent: 54.0 mm) +HyperfocalDistance : 4.37 m +ISO (1) : 100 +Lens : 18.0 - 55.0 mm +Lens35efl : 18.0 - 55.0 mm (35 mm equivalent: 28.6 - 87.4 mm) +LensID : Unknown 18-55mm +RedBalance : 2.067348 +ScaleFactor35efl : 1.6 +ShootingMode : Bulb +WB_RGGBLevels : 1719 832 831 990 +---- Composite:Image ---- +Aperture : 14.0 +DOF : inf (4.31 m - inf) +FOV : 36.9 deg +ImageSize : 8x8 +LightValue : 5.6 +Megapixels : 0.000064 +ShutterSpeed : 4 +---- ExifIFD:Camera ---- +ExposureMode : Manual +Flash : No Flash +FocalLength : 34.0 mm +FocalPlaneResolutionUnit : inches +FocalPlaneXResolution : 3443.946188 +FocalPlaneYResolution : 3442.016807 +MaxApertureValue : 4.5 +MeteringMode (1) : Average +SceneCaptureType : Standard +SensingMethod : One-chip color area +WhiteBalance (1) : Auto +---- ExifIFD:Image ---- +ApertureValue : 14.0 +ColorSpace : sRGB +ComponentsConfiguration : Y, Cb, Cr, - +CompressedBitsPerPixel : 9 +CustomRendered : Normal +ExifImageHeight : 120 +ExifImageWidth : 160 +ExifVersion : 0221 +ExposureCompensation (1) : 0 +ExposureTime : 4 +FNumber : 14.0 +FileSource : Digital Camera +FlashpixVersion : 0100 +ISO : 100 +ShutterSpeedValue : 0 +UserComment : +---- ExifIFD:Time ---- +CreateDate : 2003:12:04 06:46:52 +DateTimeOriginal : 2003:12:04 06:46:52 +---- File:Image ---- +BitsPerSample : 8 +ColorComponents : 3 +EncodingProcess : Baseline DCT, Huffman coding +ExifByteOrder : Little-endian (Intel, II) +ImageHeight : 8 +ImageWidth : 8 +YCbCrSubSampling : YCbCr4:2:0 (2 2) +---- File:Other ---- +FileType : JPEG +FileTypeExtension : jpg +MIMEType : image/jpeg +---- IFD0:Camera ---- +Make : Canon +Model : Canon EOS DIGITAL REBEL +---- IFD0:Image ---- +Orientation : Horizontal (normal) +ResolutionUnit : inches +XResolution : 180 +YCbCrPositioning : Centered +YResolution : 180 +---- IFD0:Time ---- +ModifyDate : 2003:12:04 06:46:52 +---- InteropIFD:Image ---- +InteropIndex : THM - DCF thumbnail file +InteropVersion : 0100 +RelatedImageHeight : 2048 +RelatedImageWidth : 3072 +---- System:Other ---- +Directory : t/images +FileName : Canon.jpg +FilePermissions : -rw-r--r-- +FileSize : 2.7 kB +---- System:Time ---- +FileAccessDate : 2022:06:01 14:16:34-04:00 +FileInodeChangeDate : 2021:10:31 20:34:50-04:00 +FileModifyDate : 2006:10:03 15:10:31-04:00 diff --git a/ExifTool/t/ExifTool_17.out b/ExifTool/t/ExifTool_17.out new file mode 100644 index 0000000..001dff8 --- /dev/null +++ b/ExifTool/t/ExifTool_17.out @@ -0,0 +1,599 @@ + ExifToolVersion = 11.70 + FileName = Canon.jpg + Directory = t/images + FileSize = 2697 + FileModifyDate = 1159902631 + FileAccessDate = 1570452438 + FileInodeChangeDate = 1570016457 + FilePermissions = 33188 + FileType = JPEG + FileTypeExtension = JPG + MIMEType = image/jpeg +JPEG APP1 (2442 bytes): + 0006: 45 78 69 66 00 00 49 49 2a 00 08 00 00 00 09 00 [Exif..II*.......] + 0016: 0f 01 02 00 06 00 00 00 7a 00 00 00 10 01 02 00 [........z.......] + 0026: 18 00 00 00 80 00 00 00 12 01 03 00 01 00 00 00 [................] + 0036: 01 00 00 00 1a 01 05 00 01 00 00 00 98 00 00 00 [................] + 0046: 1b 01 05 00 01 00 00 00 a0 00 00 00 28 01 03 00 [............(...] + 0056: 01 00 00 00 02 00 00 00 32 01 02 00 14 00 00 00 [........2.......] + 0066: a8 00 00 00 13 02 03 00 01 00 00 00 01 00 00 00 [................] + [snip 2330 bytes] + ExifByteOrder = II + + [IFD0 directory with 9 entries] + | 0) Make = Canon + | - Tag 0x010f (6 bytes, string[6]): + | 0086: 43 61 6e 6f 6e 00 [Canon.] + | 1) Model = Canon EOS DIGITAL REBEL + | - Tag 0x0110 (24 bytes, string[24]): + | 008c: 43 61 6e 6f 6e 20 45 4f 53 20 44 49 47 49 54 41 [Canon EOS DIGITA] + | 009c: 4c 20 52 45 42 45 4c 00 [L REBEL.] + | 2) Orientation = 1 + | - Tag 0x0112 (2 bytes, int16u[1]): + | 0036: 01 00 [..] + | 3) XResolution = 180 (180/1) + | - Tag 0x011a (8 bytes, rational64u[1]): + | 00a4: b4 00 00 00 01 00 00 00 [........] + | 4) YResolution = 180 (180/1) + | - Tag 0x011b (8 bytes, rational64u[1]): + | 00ac: b4 00 00 00 01 00 00 00 [........] + | 5) ResolutionUnit = 2 + | - Tag 0x0128 (2 bytes, int16u[1]): + | 005a: 02 00 [..] + | 6) ModifyDate = 2003:12:04 06:46:52 + | - Tag 0x0132 (20 bytes, string[20]): + | 00b4: 32 30 30 33 3a 31 32 3a 30 34 20 30 36 3a 34 36 [2003:12:04 06:46] + | 00c4: 3a 35 32 00 [:52.] + | 7) YCbCrPositioning = 1 + | - Tag 0x0213 (2 bytes, int16u[1]): + | 0072: 01 00 [..] + | 8) ExifOffset (SubDirectory) --> + | - Tag 0x8769 (4 bytes, int32u[1]): + | 007e: bc 00 00 00 [....] + | + [ExifIFD directory with 31 entries] + | | 0) ExposureTime = 4 (4/1) + | | - Tag 0x829a (8 bytes, rational64u[1]): + | | 0242: 04 00 00 00 01 00 00 00 [........] + | | 1) FNumber = 14 (14/1) + | | - Tag 0x829d (8 bytes, rational64u[1]): + | | 024a: 0e 00 00 00 01 00 00 00 [........] + | | 2) ISO = 100 + | | - Tag 0x8827 (2 bytes, int16u[1]): + | | 00ea: 64 00 [d.] + | | 3) ExifVersion = 0221 + | | - Tag 0x9000 (4 bytes, undef[4]): + | | 00f6: 30 32 32 31 [0221] + | | 4) DateTimeOriginal = 2003:12:04 06:46:52 + | | - Tag 0x9003 (20 bytes, string[20]): + | | 0252: 32 30 30 33 3a 31 32 3a 30 34 20 30 36 3a 34 36 [2003:12:04 06:46] + | | 0262: 3a 35 32 00 [:52.] + | | 5) CreateDate = 2003:12:04 06:46:52 + | | - Tag 0x9004 (20 bytes, string[20]): + | | 0266: 32 30 30 33 3a 31 32 3a 30 34 20 30 36 3a 34 36 [2003:12:04 06:46] + | | 0276: 3a 35 32 00 [:52.] + | | 6) ComponentsConfiguration = 1 2 3 0 + | | - Tag 0x9101 (4 bytes, undef[4] read as int8u[4]): + | | 011a: 01 02 03 00 [....] + | | 7) CompressedBitsPerPixel = 9 (9/1) + | | - Tag 0x9102 (8 bytes, rational64u[1]): + | | 027a: 09 00 00 00 01 00 00 00 [........] + | | 8) ShutterSpeedValue = -2147483648 (-2147483648/1) + | | - Tag 0x9201 (8 bytes, rational64s[1]): + | | 0282: 00 00 00 80 01 00 00 00 [........] + | | 9) ApertureValue = 7.614715576 (499038/65536) + | | - Tag 0x9202 (8 bytes, rational64u[1]): + | | 028a: 5e 9d 07 00 00 00 01 00 [^.......] + | | 10) ExposureCompensation = 0 (0/3) + | | - Tag 0x9204 (8 bytes, rational64s[1]): + | | 0292: 00 00 00 00 03 00 00 00 [........] + | | 11) MaxApertureValue = 4.33984375 (284416/65536) + | | - Tag 0x9205 (8 bytes, rational64u[1]): + | | 029a: 00 57 04 00 00 00 01 00 [.W......] + | | 12) MeteringMode = 1 + | | - Tag 0x9207 (2 bytes, int16u[1]): + | | 0162: 01 00 [..] + | | 13) Flash = 0 + | | - Tag 0x9209 (2 bytes, int16u[1]): + | | 016e: 00 00 [..] + | | 14) FocalLength = 34 (34/1) + | | - Tag 0x920a (8 bytes, rational64u[1]): + | | 02a2: 22 00 00 00 01 00 00 00 [".......] + | | 15) MakerNoteCanon (SubDirectory) --> + | | - Tag 0x927c (1432 bytes, undef[1432]): + | | 02aa: 1a 00 01 00 03 00 2e 00 00 00 dc 03 00 00 02 00 [................] + | | 02ba: 03 00 04 00 00 00 38 04 00 00 03 00 03 00 04 00 [......8.........] + | | 02ca: 00 00 40 04 00 00 04 00 03 00 21 00 00 00 48 04 [..@.......!...H.] + | | 02da: 00 00 93 00 03 00 09 00 00 00 8a 04 00 00 06 00 [................] + | | 02ea: 02 00 20 00 00 00 9c 04 00 00 07 00 02 00 20 00 [.. ........... .] + | | [snip 1352 bytes] + | | + [MakerNotes directory with 26 entries] + | | | 0) CanonCameraSettings (SubDirectory) --> + | | | - Tag 0x0001 (92 bytes, int16u[46] read as undef[92]): + | | | 03e8: 5c 00 00 00 00 00 04 00 00 00 01 00 00 00 03 00 [\...............] + | | | 03f8: 00 00 02 00 00 00 01 00 ff ff 01 00 01 00 01 00 [................] + | | | 0408: 00 00 05 00 02 00 00 00 04 00 ff 7f ff ff 37 00 [..............7.] + | | | 0418: 12 00 01 00 80 00 30 01 00 00 00 00 00 00 00 00 [......0.........] + | | | 0428: ff ff ff ff ff ff 00 00 00 0c 00 0c 00 00 ff ff [................] + | | | 0438: ff ff 00 00 00 00 ff 7f ff ff ff ff [............] + | | | + [BinaryData directory, 92 bytes] + | | | | MacroMode = 0 + | | | | - Tag 0x0001 (2 bytes, int16s[1]): + | | | | 03ea: 00 00 [..] + | | | | SelfTimer = 0 + | | | | - Tag 0x0002 (2 bytes, int16s[1]): + | | | | 03ec: 00 00 [..] + | | | | Quality = 4 + | | | | - Tag 0x0003 (2 bytes, int16s[1]): + | | | | 03ee: 04 00 [..] + | | | | CanonFlashMode = 0 + | | | | - Tag 0x0004 (2 bytes, int16s[1]): + | | | | 03f0: 00 00 [..] + | | | | ContinuousDrive = 1 + | | | | - Tag 0x0005 (2 bytes, int16s[1]): + | | | | 03f2: 01 00 [..] + | | | | FocusMode = 3 + | | | | - Tag 0x0007 (2 bytes, int16s[1]): + | | | | 03f6: 03 00 [..] + | | | | RecordMode = 2 + | | | | - Tag 0x0009 (2 bytes, int16s[1]): + | | | | 03fa: 02 00 [..] + | | | | CanonImageSize = 0 + | | | | - Tag 0x000a (2 bytes, int16s[1]): + | | | | 03fc: 00 00 [..] + | | | | EasyMode = 1 + | | | | - Tag 0x000b (2 bytes, int16s[1]): + | | | | 03fe: 01 00 [..] + | | | | DigitalZoom = -1 + | | | | - Tag 0x000c (2 bytes, int16s[1]): + | | | | 0400: ff ff [..] + | | | | Contrast = 1 + | | | | - Tag 0x000d (2 bytes, int16s[1]): + | | | | 0402: 01 00 [..] + | | | | Saturation = 1 + | | | | - Tag 0x000e (2 bytes, int16s[1]): + | | | | 0404: 01 00 [..] + | | | | Sharpness = 1 + | | | | - Tag 0x000f (2 bytes, int16s[1]): + | | | | 0406: 01 00 [..] + | | | | CameraISO = 0 + | | | | - Tag 0x0010 (2 bytes, int16s[1]): + | | | | 0408: 00 00 [..] + | | | | MeteringMode = 5 + | | | | - Tag 0x0011 (2 bytes, int16s[1]): + | | | | 040a: 05 00 [..] + | | | | FocusRange = 2 + | | | | - Tag 0x0012 (2 bytes, int16s[1]): + | | | | 040c: 02 00 [..] + | | | | AFPoint = 0 + | | | | - Tag 0x0013 (2 bytes, int16s[1]): + | | | | 040e: 00 00 [..] + | | | | CanonExposureMode = 4 + | | | | - Tag 0x0014 (2 bytes, int16s[1]): + | | | | 0410: 04 00 [..] + | | | | LensType = 65535 + | | | | - Tag 0x0016 (2 bytes, int16u[1]): + | | | | 0414: ff ff [..] + | | | | MaxFocalLength = 55 + | | | | - Tag 0x0017 (2 bytes, int16u[1]): + | | | | 0416: 37 00 [7.] + | | | | MinFocalLength = 18 + | | | | - Tag 0x0018 (2 bytes, int16u[1]): + | | | | 0418: 12 00 [..] + | | | | FocalUnits = 1 + | | | | - Tag 0x0019 (2 bytes, int16s[1]): + | | | | 041a: 01 00 [..] + | | | | MaxAperture = 128 + | | | | - Tag 0x001a (2 bytes, int16s[1]): + | | | | 041c: 80 00 [..] + | | | | MinAperture = 304 + | | | | - Tag 0x001b (2 bytes, int16s[1]): + | | | | 041e: 30 01 [0.] + | | | | FlashActivity = 0 + | | | | - Tag 0x001c (2 bytes, int16s[1]): + | | | | 0420: 00 00 [..] + | | | | FlashBits = 0 + | | | | - Tag 0x001d (2 bytes, int16s[1]): + | | | | 0422: 00 00 [..] + | | | | FocusContinuous = -1 + | | | | - Tag 0x0020 (2 bytes, int16s[1]): + | | | | 0428: ff ff [..] + | | | | AESetting = -1 + | | | | - Tag 0x0021 (2 bytes, int16s[1]): + | | | | 042a: ff ff [..] + | | | | ImageStabilization = -1 + | | | | - Tag 0x0022 (2 bytes, int16s[1]): + | | | | 042c: ff ff [..] + | | | | DisplayAperture = 0 + | | | | - Tag 0x0023 (2 bytes, int16s[1]): + | | | | 042e: 00 00 [..] + | | | | ZoomSourceWidth = 3072 + | | | | - Tag 0x0024 (2 bytes, int16s[1]): + | | | | 0430: 00 0c [..] + | | | | ZoomTargetWidth = 3072 + | | | | - Tag 0x0025 (2 bytes, int16s[1]): + | | | | 0432: 00 0c [..] + | | | | SpotMeteringMode = -1 + | | | | - Tag 0x0027 (2 bytes, int16s[1]): + | | | | 0436: ff ff [..] + | | | | PhotoEffect = -1 + | | | | - Tag 0x0028 (2 bytes, int16s[1]): + | | | | 0438: ff ff [..] + | | | | ManualFlashOutput = 0 + | | | | - Tag 0x0029 (2 bytes, int16s[1]): + | | | | 043a: 00 00 [..] + | | | | ColorTone = 0 + | | | | - Tag 0x002a (2 bytes, int16s[1]): + | | | | 043c: 00 00 [..] + | | | 1) CanonFocalLength (SubDirectory) --> + | | | - Tag 0x0002 (8 bytes, int16u[4] read as undef[8]): + | | | 0444: 00 00 22 00 92 03 62 02 [.."...b.] + | | | + [BinaryData directory, 8 bytes] + | | | | FocalType = 0 + | | | | - Tag 0x0000 (2 bytes, int16u[1]): + | | | | 0444: 00 00 [..] + | | | | FocalLength = 34 + | | | | - Tag 0x0001 (2 bytes, int16u[1]): + | | | | 0446: 22 00 [".] + | | | | FocalPlaneXSize = 914 + | | | | - Tag 0x0002 (2 bytes, int16u[1]): + | | | | 0448: 92 03 [..] + | | | | FocalPlaneYSize = 610 + | | | | - Tag 0x0003 (2 bytes, int16u[1]): + | | | | 044a: 62 02 [b.] + | | | 2) CanonFlashInfo = 100 0 0 0 + | | | - Tag 0x0003 (8 bytes, int16u[4]): + | | | 044c: 64 00 00 00 00 00 00 00 [d.......] + | | | 3) CanonShotInfo (SubDirectory) --> + | | | - Tag 0x0004 (66 bytes, int16u[33] read as undef[66]): + | | | 0454: 42 00 00 00 a0 00 38 ff f4 00 00 80 00 00 00 00 [B.....8.........] + | | | 0464: 03 00 00 00 08 00 08 00 00 00 00 00 00 00 00 00 [................] + | | | 0474: 00 00 00 00 01 00 ff ff 22 02 f4 00 20 ff 26 00 [........"... .&.] + | | | 0484: 28 00 00 00 fc 00 00 00 ff ff 00 00 00 00 00 00 [(...............] + | | | 0494: 00 00 [..] + | | | + [BinaryData directory, 66 bytes] + | | | | AutoISO = 0 + | | | | - Tag 0x0001 (2 bytes, int16s[1]): + | | | | 0456: 00 00 [..] + | | | | BaseISO = 160 + | | | | - Tag 0x0002 (2 bytes, int16s[1]): + | | | | 0458: a0 00 [..] + | | | | MeasuredEV = -200 + | | | | - Tag 0x0003 (2 bytes, int16s[1]): + | | | | 045a: 38 ff [8.] + | | | | TargetAperture = 244 + | | | | - Tag 0x0004 (2 bytes, int16s[1]): + | | | | 045c: f4 00 [..] + | | | | TargetExposureTime = -32768 + | | | | - Tag 0x0005 (2 bytes, int16s[1]): + | | | | 045e: 00 80 [..] + | | | | ExposureCompensation = 0 + | | | | - Tag 0x0006 (2 bytes, int16s[1]): + | | | | 0460: 00 00 [..] + | | | | WhiteBalance = 0 + | | | | - Tag 0x0007 (2 bytes, int16s[1]): + | | | | 0462: 00 00 [..] + | | | | SlowShutter = 3 + | | | | - Tag 0x0008 (2 bytes, int16s[1]): + | | | | 0464: 03 00 [..] + | | | | SequenceNumber = 0 + | | | | - Tag 0x0009 (2 bytes, int16s[1]): + | | | | 0466: 00 00 [..] + | | | | OpticalZoomCode = 8 + | | | | - Tag 0x000a (2 bytes, int16s[1]): + | | | | 0468: 08 00 [..] + | | | | CameraTemperature = 0 + | | | | - Tag 0x000c (2 bytes, int16s[1]): + | | | | 046c: 00 00 [..] + | | | | FlashGuideNumber = 0 + | | | | - Tag 0x000d (2 bytes, int16s[1]): + | | | | 046e: 00 00 [..] + | | | | AFPointsInFocus = 0 + | | | | - Tag 0x000e (2 bytes, int16s[1]): + | | | | 0470: 00 00 [..] + | | | | FlashExposureComp = 0 + | | | | - Tag 0x000f (2 bytes, int16s[1]): + | | | | 0472: 00 00 [..] + | | | | AutoExposureBracketing = 0 + | | | | - Tag 0x0010 (2 bytes, int16s[1]): + | | | | 0474: 00 00 [..] + | | | | AEBBracketValue = 0 + | | | | - Tag 0x0011 (2 bytes, int16s[1]): + | | | | 0476: 00 00 [..] + | | | | ControlMode = 1 + | | | | - Tag 0x0012 (2 bytes, int16s[1]): + | | | | 0478: 01 00 [..] + | | | | FocusDistanceUpper = 65535 + | | | | - Tag 0x0013 (2 bytes, int16u[1]): + | | | | 047a: ff ff [..] + | | | | FocusDistanceLower = 546 + | | | | - Tag 0x0014 (2 bytes, int16u[1]): + | | | | 047c: 22 02 [".] + | | | | FNumber = 244 + | | | | - Tag 0x0015 (2 bytes, int16s[1]): + | | | | 047e: f4 00 [..] + | | | | ExposureTime = -224 + | | | | - Tag 0x0016 (2 bytes, int16s[1]): + | | | | 0480: 20 ff [ .] + | | | | MeasuredEV2 = 38 + | | | | - Tag 0x0017 (2 bytes, int16s[1]): + | | | | 0482: 26 00 [&.] + | | | | BulbDuration = 40 + | | | | - Tag 0x0018 (2 bytes, int16s[1]): + | | | | 0484: 28 00 [(.] + | | | | CameraType = 252 + | | | | - Tag 0x001a (2 bytes, int16s[1]): + | | | | 0488: fc 00 [..] + | | | | AutoRotate = 0 + | | | | - Tag 0x001b (2 bytes, int16s[1]): + | | | | 048a: 00 00 [..] + | | | | NDFilter = -1 + | | | | - Tag 0x001c (2 bytes, int16s[1]): + | | | | 048c: ff ff [..] + | | | | SelfTimer2 = 0 + | | | | - Tag 0x001d (2 bytes, int16s[1]): + | | | | 048e: 00 00 [..] + | | | 4) CanonFileInfo (SubDirectory) --> + | | | - Tag 0x0093 (18 bytes, int16u[9] read as undef[18]): + | | | 0496: 12 00 00 00 00 00 00 00 00 00 00 00 ff ff ff ff [................] + | | | 04a6: ff ff [..] + | | | + [BinaryData directory, 18 bytes] + | | | | BracketMode = 0 + | | | | - Tag 0x0003 (2 bytes, int16s[1]): + | | | | 049c: 00 00 [..] + | | | | BracketValue = 0 + | | | | - Tag 0x0004 (2 bytes, int16s[1]): + | | | | 049e: 00 00 [..] + | | | | BracketShotNumber = 0 + | | | | - Tag 0x0005 (2 bytes, int16s[1]): + | | | | 04a0: 00 00 [..] + | | | | RawJpgQuality = -1 + | | | | - Tag 0x0006 (2 bytes, int16s[1]): + | | | | 04a2: ff ff [..] + | | | | RawJpgSize = -1 + | | | | - Tag 0x0007 (2 bytes, int16s[1]): + | | | | 04a4: ff ff [..] + | | | | LongExposureNoiseReduction2 = -1 + | | | | - Tag 0x0008 (2 bytes, int16s[1]): + | | | | 04a6: ff ff [..] + | | | 5) CanonImageType = CRW:EOS DIGITAL REBEL CMOS RAW + | | | - Tag 0x0006 (32 bytes, string[32]): + | | | 04a8: 43 52 57 3a 45 4f 53 20 44 49 47 49 54 41 4c 20 [CRW:EOS DIGITAL ] + | | | 04b8: 52 45 42 45 4c 20 43 4d 4f 53 20 52 41 57 00 00 [REBEL CMOS RAW..] + | | | 6) CanonFirmwareVersion = Firmware Version 1.1.1 + | | | - Tag 0x0007 (32 bytes, string[32]): + | | | 04c8: 46 69 72 6d 77 61 72 65 20 56 65 72 73 69 6f 6e [Firmware Version] + | | | 04d8: 20 31 2e 31 2e 31 00 00 00 00 00 00 00 00 00 00 [ 1.1.1..........] + | | | 7) SerialNumber = 560018150 + | | | - Tag 0x000c (4 bytes, int32u[1]): + | | | 0308: e6 32 61 21 [.2a!] + | | | 8) SerialNumberFormat = 2415919104 + | | | - Tag 0x0015 (4 bytes, int32u[1]): + | | | 0314: 00 00 00 90 [....] + | | | 9) FileNumber = 1181861 + | | | - Tag 0x0008 (4 bytes, int32u[1]): + | | | 0320: a5 08 12 00 [....] + | | | 10) OwnerName = Phil Harvey + | | | - Tag 0x0009 (32 bytes, string[32]): + | | | 04e8: 50 68 69 6c 20 48 61 72 76 65 79 00 00 00 00 00 [Phil Harvey.....] + | | | 04f8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | | 11) CanonModelID = 2147484016 + | | | - Tag 0x0010 (4 bytes, int32u[1]): + | | | 0338: 70 01 00 80 [p...] + | | | 12) CanonFileLength = 4480822 + | | | - Tag 0x000e (4 bytes, int32u[1]): + | | | 0344: 36 5f 44 00 [6_D.] + | | | 13) CanonCameraInfoUnknown (SubDirectory) --> + | | | - Tag 0x000d (512 bytes, int8u[512] read as undef[512]): + | | | 0508: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | | 0518: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | | 0528: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | | 0538: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | | 0548: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | | [snip 432 bytes] + | | | + [BinaryData directory, 512 bytes] + | | | 14) Canon_0x0000 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + | | | - Tag 0x0000 (36 bytes, int16u[18]): + | | | 0708: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | | 0718: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | | 0728: 00 00 00 00 [....] + | | | 15) Canon_0x00c0 = 26 331 372 372 177 240 428 429 277 186 510 511 442 + | | | - Tag 0x00c0 (26 bytes, int16u[13]): + | | | 072c: 1a 00 4b 01 74 01 74 01 b1 00 f0 00 ac 01 ad 01 [..K.t.t.........] + | | | 073c: 15 01 ba 00 fe 01 ff 01 ba 01 [..........] + | | | 16) Canon_0x00c1 = 26 299 375 375 170 202 394 395 240 153 453 454 375 + | | | - Tag 0x00c1 (26 bytes, int16u[13]): + | | | 0746: 1a 00 2b 01 77 01 77 01 aa 00 ca 00 8a 01 8b 01 [..+.w.w.........] + | | | 0756: f0 00 99 00 c5 01 c6 01 77 01 [........w.] + | | | 17) MeasuredColor (SubDirectory) --> + | | | - Tag 0x00aa (10 bytes, int16u[5] read as undef[10]): + | | | 0760: 0a 00 e6 03 fe 03 02 04 28 03 [........(.] + | | | + [BinaryData directory, 10 bytes] + | | | | MeasuredRGGB = 998 1022 1026 808 + | | | | - Tag 0x0001 (8 bytes, int16u[4]): + | | | | 0762: e6 03 fe 03 02 04 28 03 [......(.] + | | | 18) Canon_0x00a8 = 20 5190 5190 7000 5987 3214 3897 6203 5190 5200 + | | | - Tag 0x00a8 (20 bytes, int16u[10]): + | | | 076a: 14 00 46 14 46 14 58 1b 63 17 8e 0c 39 0f 3b 18 [..F.F.X.c...9.;.] + | | | 077a: 46 14 50 14 [F.P.] + | | | 19) ColorBalance (SubDirectory) --> + | | | - Tag 0x00a9 (82 bytes, int16u[41] read as undef[82]): + | | | 077e: 52 00 b7 06 40 03 3f 03 de 03 ba 06 40 03 3f 03 [R...@.?.....@.?.] + | | | 078e: dd 03 f3 07 40 03 3f 03 47 03 56 07 40 03 3f 03 [....@.?.G.V.@.?.] + | | | 079e: 87 03 cc 04 91 03 90 03 84 06 e2 05 4a 03 49 03 [............J.I.] + | | | 07ae: 65 05 8d 07 40 03 3f 03 7f 03 ba 06 40 03 3f 03 [e...@.?.....@.?.] + | | | 07be: dd 03 ba 06 40 03 3f 03 dc 03 7c 00 7b 00 7c 00 [....@.?...|.{.|.] + | | | 07ce: 7b 00 [{.] + | | | + [BinaryData directory, 82 bytes] + | | | | WB_RGGBLevelsAuto = 1719 832 831 990 + | | | | - Tag 0x0001 (8 bytes, int16s[4]): + | | | | 0780: b7 06 40 03 3f 03 de 03 [..@.?...] + | | | | WB_RGGBLevelsDaylight = 1722 832 831 989 + | | | | - Tag 0x0005 (8 bytes, int16s[4]): + | | | | 0788: ba 06 40 03 3f 03 dd 03 [..@.?...] + | | | | WB_RGGBLevelsShade = 2035 832 831 839 + | | | | - Tag 0x0009 (8 bytes, int16s[4]): + | | | | 0790: f3 07 40 03 3f 03 47 03 [..@.?.G.] + | | | | WB_RGGBLevelsCloudy = 1878 832 831 903 + | | | | - Tag 0x000d (8 bytes, int16s[4]): + | | | | 0798: 56 07 40 03 3f 03 87 03 [V.@.?...] + | | | | WB_RGGBLevelsTungsten = 1228 913 912 1668 + | | | | - Tag 0x0011 (8 bytes, int16s[4]): + | | | | 07a0: cc 04 91 03 90 03 84 06 [........] + | | | | WB_RGGBLevelsFluorescent = 1506 842 841 1381 + | | | | - Tag 0x0015 (8 bytes, int16s[4]): + | | | | 07a8: e2 05 4a 03 49 03 65 05 [..J.I.e.] + | | | | WB_RGGBLevelsFlash = 1933 832 831 895 + | | | | - Tag 0x0019 (8 bytes, int16s[4]): + | | | | 07b0: 8d 07 40 03 3f 03 7f 03 [..@.?...] + | | | | WB_RGGBLevelsCustom = 1722 832 831 989 + | | | | - Tag 0x001d (8 bytes, int16s[4]): + | | | | 07b8: ba 06 40 03 3f 03 dd 03 [..@.?...] + | | | | WB_RGGBLevelsKelvin = 1722 832 831 988 + | | | | - Tag 0x0021 (8 bytes, int16s[4]): + | | | | 07c0: ba 06 40 03 3f 03 dc 03 [..@.?...] + | | | | WB_RGGBBlackLevels = 124 123 124 123 + | | | | - Tag 0x0025 (8 bytes, int16s[4]): + | | | | 07c8: 7c 00 7b 00 7c 00 7b 00 [|.{.|.{.] + | | | 20) ColorTemperature = 5200 + | | | - Tag 0x00ae (2 bytes, int16u[1]): + | | | 03a4: 50 14 [P.] + | | | 21) ColorSpace = 1 + | | | - Tag 0x00b4 (2 bytes, int16u[1]): + | | | 03b0: 01 00 [..] + | | | 22) CanonAFInfo (SubDirectory) --> + | | | - Tag 0x0012 (48 bytes, int16u[24] read as undef[48]): + | | | 07d0: 07 00 07 00 00 0c 00 08 00 0c 00 08 97 00 97 00 [................] + | | | 07e0: f6 03 60 02 00 00 00 00 00 00 a0 fd 0a fc 00 00 [..`.............] + | | | 07f0: 00 00 06 fe 00 00 fa 01 00 00 00 00 00 00 ff ff [................] + | | | + [SerialData directory, 48 bytes] + | | | | 0) NumAFPoints = 7 + | | | | - Tag 0x0000 (2 bytes, int16u[1]): + | | | | 07d0: 07 00 [..] + | | | | 1) ValidAFPoints = 7 + | | | | - Tag 0x0001 (2 bytes, int16u[1]): + | | | | 07d2: 07 00 [..] + | | | | 2) CanonImageWidth = 3072 + | | | | - Tag 0x0002 (2 bytes, int16u[1]): + | | | | 07d4: 00 0c [..] + | | | | 3) CanonImageHeight = 2048 + | | | | - Tag 0x0003 (2 bytes, int16u[1]): + | | | | 07d6: 00 08 [..] + | | | | 4) AFImageWidth = 3072 + | | | | - Tag 0x0004 (2 bytes, int16u[1]): + | | | | 07d8: 00 0c [..] + | | | | 5) AFImageHeight = 2048 + | | | | - Tag 0x0005 (2 bytes, int16u[1]): + | | | | 07da: 00 08 [..] + | | | | 6) AFAreaWidth = 151 + | | | | - Tag 0x0006 (2 bytes, int16u[1]): + | | | | 07dc: 97 00 [..] + | | | | 7) AFAreaHeight = 151 + | | | | - Tag 0x0007 (2 bytes, int16u[1]): + | | | | 07de: 97 00 [..] + | | | | 8) AFAreaXPositions = 1014 608 0 0 0 -608 -1014 + | | | | - Tag 0x0008 (14 bytes, int16s[7]): + | | | | 07e0: f6 03 60 02 00 00 00 00 00 00 a0 fd 0a fc [..`...........] + | | | | 9) AFAreaYPositions = 0 0 -506 0 506 0 0 + | | | | - Tag 0x0009 (14 bytes, int16s[7]): + | | | | 07ee: 00 00 00 00 06 fe 00 00 fa 01 00 00 00 00 [..............] + | | | | 10) AFPointsInFocus = 0 + | | | | - Tag 0x000a (2 bytes, int16s[1]): + | | | | 07fc: 00 00 [..] + | | | 23) ThumbnailImageValidArea = 0 159 7 112 + | | | - Tag 0x0013 (8 bytes, int16u[4]): + | | | 0800: 00 00 9f 00 07 00 70 00 [......p.] + | | | 24) Canon_0x00b5 = 10 3 1 2048 1360 + | | | - Tag 0x00b5 (10 bytes, int16u[5]): + | | | 0808: 0a 00 03 00 01 00 00 08 50 05 [........P.] + | | | 25) Canon_0x0000 = 0 0 0 0 0 0 3072000 892 2048000 595 65540 262146 + | | | - Tag 0x0000 (48 bytes, int32u[12]): + | | | 0812: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | | 0822: 00 00 00 00 00 00 00 00 00 e0 2e 00 7c 03 00 00 [............|...] + | | | 0832: 00 40 1f 00 53 02 00 00 04 00 01 00 02 00 04 00 [.@..S...........] + | | 16) UserComment = + | | - Tag 0x9286 (264 bytes, undef[264]): + | | 0842: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | 0852: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | 0862: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | 0872: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | 0882: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + | | [snip 184 bytes] + | | 17) FlashpixVersion = 0100 + | | - Tag 0xa000 (4 bytes, undef[4]): + | | 019e: 30 31 30 30 [0100] + | | 18) ColorSpace = 1 + | | - Tag 0xa001 (2 bytes, int16u[1]): + | | 01aa: 01 00 [..] + | | 19) ExifImageWidth = 160 + | | - Tag 0xa002 (2 bytes, int16u[1]): + | | 01b6: a0 00 [..] + | | 20) ExifImageHeight = 120 + | | - Tag 0xa003 (2 bytes, int16u[1]): + | | 01c2: 78 00 [x.] + | | 21) InteropOffset (SubDirectory) --> + | | - Tag 0xa005 (4 bytes, int32u[1]): + | | 01ce: 4e 09 00 00 [N...] + | | + [InteropIFD directory with 4 entries] + | | | 0) InteropIndex = THM + | | | - Tag 0x0001 (4 bytes, string[4]): + | | | 0964: 54 48 4d 00 [THM.] + | | | 1) InteropVersion = 0100 + | | | - Tag 0x0002 (4 bytes, undef[4]): + | | | 0970: 30 31 30 30 [0100] + | | | 2) RelatedImageWidth = 3072 + | | | - Tag 0x1001 (2 bytes, int16u[1]): + | | | 097c: 00 0c [..] + | | | 3) RelatedImageHeight = 2048 + | | | - Tag 0x1002 (2 bytes, int16u[1]): + | | | 0988: 00 08 [..] + | | 22) FocalPlaneXResolution = 3443.946188 (3072000/892) + | | - Tag 0xa20e (8 bytes, rational64u[1]): + | | 094a: 00 e0 2e 00 7c 03 00 00 [....|...] + | | 23) FocalPlaneYResolution = 3442.016807 (2048000/595) + | | - Tag 0xa20f (8 bytes, rational64u[1]): + | | 0952: 00 40 1f 00 53 02 00 00 [.@..S...] + | | 24) FocalPlaneResolutionUnit = 2 + | | - Tag 0xa210 (2 bytes, int16u[1]): + | | 01f2: 02 00 [..] + | | 25) SensingMethod = 2 + | | - Tag 0xa217 (2 bytes, int16u[1]): + | | 01fe: 02 00 [..] + | | 26) FileSource = 3 + | | - Tag 0xa300 (1 bytes, undef[1]): + | | 020a: 03 [.] + | | 27) CustomRendered = 0 + | | - Tag 0xa401 (2 bytes, int16u[1]): + | | 0216: 00 00 [..] + | | 28) ExposureMode = 1 + | | - Tag 0xa402 (2 bytes, int16u[1]): + | | 0222: 01 00 [..] + | | 29) WhiteBalance = 0 + | | - Tag 0xa403 (2 bytes, int16u[1]): + | | 022e: 00 00 [..] + | | 30) SceneCaptureType = 0 + | | - Tag 0xa406 (2 bytes, int16u[1]): + | | 023a: 00 00 [..] +JPEG DQT (130 bytes): + 0994: 00 14 10 10 19 12 19 27 17 17 27 32 26 1f 26 32 [.......'..'2&.&2] + 09a4: 2e 26 26 26 26 2e 3e 35 35 35 35 35 3e 44 41 41 [.&&&&.>55555>DAA] + 09b4: 41 41 41 41 44 44 44 44 44 44 44 44 44 44 44 44 [AAAADDDDDDDDDDDD] + 09c4: 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 [DDDDDDDDDDDDDDDD] + 09d4: 44 01 15 19 19 20 1c 20 26 18 18 26 36 26 20 26 [D.... . &..&6& &] + 09e4: 36 44 36 2b 2b 36 44 44 44 42 35 42 44 44 44 44 [6D6++6DDDB5BDDDD] + 09f4: 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 [DDDDDDDDDDDDDDDD] + [snip 18 bytes] +JPEG SOF0 (15 bytes): + 0a1a: 08 00 08 00 08 03 01 22 00 02 11 01 03 11 01 [.......".......] + ImageWidth = 8 + ImageHeight = 8 + EncodingProcess = 0 + BitsPerSample = 8 + ColorComponents = 3 + YCbCrSubSampling = 2 2 +JPEG DHT (73 bytes): + 0a2d: 00 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + 0a3d: 00 00 06 01 01 00 00 00 00 00 00 00 00 00 00 00 [................] + 0a4d: 00 00 00 00 00 10 01 00 00 00 00 00 00 00 00 00 [................] + 0a5d: 00 00 00 00 00 00 00 11 01 00 00 00 00 00 00 00 [................] + 0a6d: 00 00 00 00 00 00 00 00 00 [.........] +JPEG SOS +JPEG EOI diff --git a/ExifTool/t/ExifTool_2.out b/ExifTool/t/ExifTool_2.out new file mode 100644 index 0000000..74e4a1d --- /dev/null +++ b/ExifTool/t/ExifTool_2.out @@ -0,0 +1,442 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.46 +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest is not current. XMP may be out of sync +[File, System, Other] FileName - File Name: ExifTool.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 26 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2021:03:19 14:14:12-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:09:19 14:15:32-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:18 10:49:22-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] Comment - Comment: © PhotoStudio Unicode comment;; +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 6895be53ef9a287520f400aa17242c09 +[File, File, Image] Comment - Comment: a comment +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: A witty caption +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2004:02:26 09:36:46 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: Copyright 2004 Phil Harvey +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 100 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 80 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 852 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 0 +[JFIF, JFIF, Image] 0 - JFIF Version: 1.01 +[JFIF, JFIF, Image] 2 - Resolution Unit: inches +[JFIF, JFIF, Image] 3 - X Resolution: 144 +[JFIF, JFIF, Image] 5 - Y Resolution: 144 +[JFIF, JFXX, Preview] 16 - Thumbnail Image: (Binary data 1558 bytes) +[APP8, SPIFF, Image] 0 - SPIFF Version: 1.2 +[APP8, SPIFF, Image] 2 - Profile ID: Continuous-tone Base +[APP8, SPIFF, Image] 3 - Color Components: 3 +[APP8, SPIFF, Image] 6 - Image Height: 4500 +[APP8, SPIFF, Image] 10 - Image Width: 3000 +[APP8, SPIFF, Image] 14 - Color Space: YCbCr, ITU-R BT 709, video +[APP8, SPIFF, Image] 15 - Bits Per Sample: 8 +[APP8, SPIFF, Image] 16 - Compression: JPEG +[APP8, SPIFF, Image] 17 - Resolution Unit: inches +[APP8, SPIFF, Image] 18 - Y Resolution: 300 +[APP8, SPIFF, Image] 22 - X Resolution: 300 +[MakerNotes, CIFF, Image] 0 - File Format: JPEG (lossy) +[MakerNotes, CIFF, Image] 1 - Target Compression Ratio: 2 +[MakerNotes, CIFF, Image] 0 - Image Width: 512 +[MakerNotes, CIFF, Image] 1 - Image Height: 384 +[MakerNotes, CIFF, Image] 2 - Pixel Aspect Ratio: 1 +[MakerNotes, CIFF, Image] 3 - Rotation: 0 +[MakerNotes, CIFF, Image] 4 - Component Bit Depth: 8 +[MakerNotes, CIFF, Image] 5 - Color Bit Depth: 24 +[MakerNotes, CIFF, Image] 6 - Color BW: 1 +[MakerNotes, CIFF, Camera] 4106 - Target Image Type: Real-world Subject +[MakerNotes, CIFF, Camera] 6148 - Record ID: 0 +[MakerNotes, CIFF, Image] 6167 - File Number: 45 +[MakerNotes, CIFF, Time] 0 - Date/Time Original: 1998:05:01 21:33:18 +[MakerNotes, CIFF, Time] 1 - Time Zone Code: 0 +[MakerNotes, CIFF, Time] 2 - Time Zone Info: 0 +[MakerNotes, CIFF, Camera] 2070 - Original File Name: C:\DC97\CTG_0000\AUT_0045.JPG +[MakerNotes, CIFF, Camera] 2071 - Thumbnail File Name: C:\DC97\CTG_0000\THM_0045.JPG +[MakerNotes, CIFF, Camera] 4112 - Shutter Release Method: Single Shot +[MakerNotes, CIFF, Camera] 4113 - Shutter Release Timing: Priority on focus +[MakerNotes, CIFF, Image] 0 - Flash Guide Number: 0 +[MakerNotes, CIFF, Image] 1 - Flash Threshold: 0 +[MakerNotes, CIFF, Image] 0 - Exposure Compensation: 1 +[MakerNotes, CIFF, Image] 1 - Shutter Speed Value: 1/83 +[MakerNotes, CIFF, Image] 2 - Aperture Value: 6.2 +[MakerNotes, CIFF, Camera] 6151 - Target Distance Setting: 476 mm +[MakerNotes, CIFF, Camera] 6164 - Measured EV: 16.25 +[MakerNotes, CIFF, Camera] 2053 - Canon File Description: +[MakerNotes, CIFF, Camera] 2069 - Canon Image Type: AUT:Full automatic mode +[MakerNotes, CIFF, Camera] 2064 - Owner Name: +[MakerNotes, CIFF, Camera] 0 - Make: Canon +[MakerNotes, CIFF, Camera] 6 - Camera Model Name: Canon PowerShot A5 +[MakerNotes, CIFF, Camera] 4124 - Base ISO: 100 +[MakerNotes, CIFF, Camera] 2061 - ROM Operation Mode: USA +[MakerNotes, CIFF, Camera] 2059 - Canon Firmware Version: Firmware Version 01.00 +[MakerNotes, CIFF, Camera] 1 - Free Bytes: (Binary data 12 bytes) +[MakerNotes, CIFF, Image] 0 - Focal Type: Fixed +[MakerNotes, CIFF, Image] 1 - Focal Length: 5 mm +[MakerNotes, CIFF, Image] 2 - Focal Plane X Size: 5.05 mm +[MakerNotes, CIFF, Image] 3 - Focal Plane Y Size: 3.71 mm +[MakerNotes, Qualcomm, Camera] aec_current_sensor_luma - AEC Current Sensor Luma: 22 +[MakerNotes, Qualcomm, Camera] af_position - AF Position: 26 +[MakerNotes, Qualcomm, Camera] aec_current_exp_index - AEC Current Exp Index: 308 +[MakerNotes, Qualcomm, Camera] awb_sample_decision - AWB Sample Decision: 7 +[MakerNotes, Qualcomm, Camera] asf5_enable - ASF5 Enable: 1 +[MakerNotes, Qualcomm, Camera] asf5_filter_mode - ASF5 Filter Mode: 0 +[MakerNotes, Qualcomm, Camera] asf5_exposure_index_1 - ASF5 Exposure Index 1: 180 +[MakerNotes, Qualcomm, Camera] asf5_exposure_index_2 - ASF5 Exposure Index 2: 270 +[MakerNotes, Qualcomm, Camera] asf5_max_exposure_index - ASF5 Max Exposure Index: 290 +[MakerNotes, Samsung, Other] 0x0100-name - Embedded Audio File Name: SoundShot_000 +[MakerNotes, Samsung, Audio] 0x0100 - Embedded Audio File: (Binary data 16 bytes) +[APP0, AVI1, Image] 0 - Interleaved Field: Not Interleaved +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 4.73 +[XMP, XMP-rdf, Document] about - About: uuid:80056b68-1045-fa97-3401-6f4ed84cd53d +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Image] Category - Category: 1 +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image] Headline - Headline: No headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Author] Source - Source: I'm the source +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference? +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:06:03 00:00:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple Computer Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright 1999 Adobe Systems Incorporated +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Adobe RGB (1998) +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.60974 0.31111 0.01947 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.20528 0.62567 0.06087 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14919 0.06322 0.74457 +[FlashPix, FlashPix, Other] 1 - Code Page: Unicode UTF-16, little endian +[FlashPix, FlashPix, Other] 268435456 - Used Extension Numbers: 1 +[FlashPix, FlashPix, Other] 1 - Extension Name: Screen nail +[FlashPix, FlashPix, Other] 2 - Extension Class ID: 10000230-6FC0-11D0-BD01-00609719A180 +[FlashPix, FlashPix, Other] 3 - Extension Persistence: Invalidated By Modification +[FlashPix, FlashPix, Time] 4 - Extension Create Date: 1999:05:14 21:47:25 +[FlashPix, FlashPix, Time] 5 - Extension Modify Date: 1999:05:14 21:47:25 +[FlashPix, FlashPix, Other] 6 - Creating Application: Digita +[FlashPix, FlashPix, Other] 7 - Extension Description: Presized image for LCD display +[FlashPix, FlashPix, Other] 4096 - Storage-Stream Pathname: /.Screen Nail_bd0100609719a180 +[FlashPix, FlashPix, Other] Screen Nail - Screen Nail: (Binary data 5917 bytes) +[FlashPix, FlashPix, Preview] FlashPix-PreviewImage - Preview Image: (Binary data 5777 bytes) +[MPF, MPF0, Image] 45056 - MPF Version: 0100 +[MPF, MPF0, Image] 45057 - Number Of Images: 2 +[MPF, MPImage1, Image] 0.1 - MP Image Flags: Dependent parent image +[MPF, MPImage1, Image] 0.2 - MP Image Format: JPEG +[MPF, MPImage1, Image] 0.3 - MP Image Type: Baseline MP Primary Image +[MPF, MPImage1, Image] 4 - MP Image Length: 5959981 +[MPF, MPImage1, Image] 8 - MP Image Start: 0 +[MPF, MPImage1, Image] 12 - Dependent Image 1 Entry Number: 2 +[MPF, MPImage1, Image] 14 - Dependent Image 2 Entry Number: 0 +[MPF, MPImage2, Image] 0.1 - MP Image Flags: Dependent child image +[MPF, MPImage2, Image] 0.2 - MP Image Format: JPEG +[MPF, MPImage2, Image] 0.3 - MP Image Type: Large Thumbnail (full HD equivalent) +[MPF, MPImage2, Image] 4 - MP Image Length: 1039273 +[MPF, MPImage2, Image] 8 - MP Image Start: 5945405 +[MPF, MPImage2, Image] 12 - Dependent Image 1 Entry Number: 0 +[MPF, MPImage2, Image] 14 - Dependent Image 2 Entry Number: 0 +[MPF, MPImage2, Preview] PreviewImage - Preview Image: (Binary data 1039273 bytes) +[Meta, MetaIFD, Image] 50000 - Film Product Code: 37 +[Meta, MetaIFD, Image] 50001 - Image Source EK: 1 +[Meta, MetaIFD, Image] 50002 - Capture Conditions PAR: 1 +[Meta, MetaIFD, Image] 50009 - Frame Number: 0 +[Meta, MetaIFD, Image] 50010 - Film Category: 2 +[Meta, MetaIFD, Image] 50011 - Film Gencode: 2 +[Meta, MetaIFD, Image] 50012 - Model And Version: Version 9 +[Meta, MetaIFD, Image] 50013 - Film Size: 1 +[Meta, MetaIFD, Image] 50014 - SBA RGB Shifts: 0 0 0 +[Meta, MetaIFD, Image] 50015 - SBA Input Image Colorspace: 3 +[Meta, MetaIFD, Image] 50016 - SBA Input Image Bit Depth: 12 12 12 +[Meta, MetaIFD, Image] 50017 - SBA Exposure Record: (Binary data 368 bytes) +[Meta, MetaIFD, Image] 50018 - User Adj SBA RGB Shifts: (Binary data 5 bytes) +[Meta, MetaIFD, Image] 50019 - Image Rotation Status: 0 +[Meta, MetaIFD, Image] 50020 - Roll Guid Elements: 00000000000000000000000000000000 +[Meta, MetaIFD, Image] 50021 - Metadata Number: 0100 +[APP5, RMETA, Image] Sign type - Sign Type: Information +[APP5, RMETA, Image] Location - Location: Roundabout +[APP5, RMETA, Image] Lit - Lit: No +[APP5, RMETA, Image] Condition - Condition: Good +[APP5, RMETA, Image] Azimuth - Azimuth: E +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[APP6, NITF, Image] 0 - NITF Version: 2.00 +[APP6, NITF, Image] 2 - Image Format: IMode B +[APP6, NITF, Image] 3 - Blocks Per Row: 1 +[APP6, NITF, Image] 5 - Blocks Per Column: 1 +[APP6, NITF, Image] 7 - Image Color: Monochrome +[APP6, NITF, Image] 8 - Bit Depth: 8 +[APP6, NITF, Image] 9 - Image Class: General Purpose +[APP6, NITF, Image] 10 - JPEG Process: Baseline sequential DCT, Huffman coding, 8-bit samples +[APP6, NITF, Image] 11 - Quality: 1 +[APP6, NITF, Image] 12 - Stream Color: Monochrome +[APP6, NITF, Image] 13 - Stream Bit Depth: 8 +[APP6, NITF, Image] 14 - Flags: 0x1010000 +[XML, MediaJukebox, Image] Tool_Name - Tool Name: Media Center +[XML, MediaJukebox, Image] Tool_Version - Tool Version: 19.0.67 +[XML, MediaJukebox, Image] People - People: Santa +[XML, MediaJukebox, Image] Places - Places: Jamaica +[XML, MediaJukebox, Time] Date - Date: 2013:09:01 20:12:19 +[XML, MediaJukebox, Image] Album - Album: 2013-09-01 +[XML, MediaJukebox, Image] Name - Name: Glass home at night +[APP11, JPEG-HDR, Image] ver - JPEG-HDR Version: 11 +[APP11, JPEG-HDR, Image] ln0 - Ln0: 0.122262 +[APP11, JPEG-HDR, Image] ln1 - Ln1: 2.634655 +[APP11, JPEG-HDR, Image] s2n - S2n: 2.269635e+03 +[APP11, JPEG-HDR, Image] alp - Alpha: 1.000000 +[APP11, JPEG-HDR, Image] bet - Beta: 1.000000 +[APP11, JPEG-HDR, Image] cor - Correction Method: 0 +[APP11, JPEG-HDR, Preview] RatioImage - Ratio Image: (Binary data 19 bytes) +[JUMBF, JUMBF, Image] type - JUMD Type: (cacb)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: cai +[JUMBF, JUMBF, Image] type - JUMD Type: (cast)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: cb.reuters_1 +[JUMBF, JUMBF, Image] type - JUMD Type: (caas)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: cai.assertions +[JUMBF, JUMBF, Image] type - JUMD Type: (json)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: adobe.asset.info +[JUMBF, JSON, Other] title - Title: HEALTHCORONAVIRUSUSAOREGON_SALEM_08.jpg +[JUMBF, JUMBF, Image] type - JUMD Type: (json)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: cai.location.broad +[JUMBF, JSON, Other] location - Location: Salem, Oregon +[JUMBF, JUMBF, Image] type - JUMD Type: (json)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: cai.rights +[JUMBF, JSON, Other] copyright - Copyright: Alisha Jucevic +[APP12, PictureInfo, Time] TimeDate - Date/Time Original: 1998:12:31 15:17:20 +[APP12, PictureInfo, Image] Shutter - Exposure Time: 1/155 +[APP12, PictureInfo, Image] Flash - Flash: Off +[APP12, PictureInfo, Image] Resolution - Resolution: 5 +[APP12, PictureInfo, Image] Protect - Protect: 0 +[APP12, PictureInfo, Image] ContTake - Cont Take: 0 +[APP12, PictureInfo, Image] ImageSize - Image Size: 1280x960 +[APP12, PictureInfo, Image] ColorMode - Color Mode: 1 +[APP12, PictureInfo, Image] FNumber - F Number: 11.0 +[APP12, PictureInfo, Image] Zoom - Zoom: 2.1 +[APP12, PictureInfo, Image] Macro - Macro: Off +[APP12, PictureInfo, Image] LightS - Light S: 0 +[APP12, PictureInfo, Image] ExpBias - Exposure Compensation: +2.0 +[APP12, PictureInfo, Camera] Type - Camera Type: SR84 +[APP12, PictureInfo, Camera] Serial# - Serial Number: #00000001 +[APP12, PictureInfo, Camera] Version - Version: v84-71 +[APP12, PictureInfo, Camera] ID - ID: AGFA DIGITAL CAMERA +[APP12, PictureInfo, Image] PicLen - Pic Len: 561039 +[APP12, PictureInfo, Image] ThmLen - Thm Len: 3802 +[APP12, PictureInfo, Image] Q - Tag Q: 96 +[APP12, PictureInfo, Image] R - Tag R: 293 +[APP12, PictureInfo, Image] B - Tag B: 332 +[APP12, PictureInfo, Image] s0 - S0: 1e8,0,11b0,6f72,15cf,4225,4225,1050000,a1e0004,0,2f0030d,2f102c5,2880090,0,0 +[APP12, PictureInfo, Image] T0 - T0: 11b15600,1290000,e00c0f,2,0,0 +[Ducky, Ducky, Image] 1 - Quality: 84% +[Ducky, Ducky, Author] 3 - Copyright: Copyright 2004 Phil Harvey +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other] 105 - Headline: No headline +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My Position +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Author] 115 - Source: I'm the source +[IPTC, IPTC, Other] 25 - Keywords: jambalaya +[IPTC, IPTC2, Other] 0 - Application Record Version: 2 +[IPTC, IPTC3, Other] 0 - Application Record Version: 2 +[IPTC, IPTC3, Other] 15 - Category: p +[IPTC, IPTC3, Time] 55 - Date Created: 2005:12:23 +[IPTC, IPTC3, Other] 5 - Object Name: object name +[IPTC, IPTC3, Other] 10 - Urgency: 2 +[IPTC, IPTC3, Other] 20 - Supplemental Categories: supp cat +[IPTC, IPTC3, Other] 40 - Special Instructions: special instructions +[IPTC, IPTC3, Author] 80 - By-line: byline +[IPTC, IPTC3, Author] 85 - By-line Title: byline title +[IPTC, IPTC3, Location] 90 - City: city +[IPTC, IPTC3, Location] 101 - Country-Primary Location Name: country name +[IPTC, IPTC3, Other] 103 - Original Transmission Reference: otr +[IPTC, IPTC3, Other] 105 - Headline: headline +[IPTC, IPTC3, Author] 110 - Credit: credit +[IPTC, IPTC3, Author] 115 - Source: source +[IPTC, IPTC3, Author] 116 - Copyright Notice: copy freely +[IPTC, IPTC3, Other] 120 - Caption-Abstract: ExifTool AFCP test +[IPTC, IPTC3, Author] 122 - Writer-Editor: caption writer +[IPTC, IPTC3, Location] 95 - Province-State: state +[IPTC, IPTC3, Other] 25 - Keywords: jambalaya +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: 05ad1770b1a95f1f9788ac995fa647da +[Photoshop, Photoshop, Image] 0 - X Resolution: 72 +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: 72 +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Image] 0 - Print Style: Centered +[Photoshop, Photoshop, Image] 2 - Print Position: 0 0 +[Photoshop, Photoshop, Image] 10 - Print Scale: 1 +[Photoshop, Photoshop, Image] 1037 - Global Angle: 30 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 30 +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image] 1054 - URL List: +[Photoshop, Photoshop, Other] 20 - Slices Group Name: IPTC +[Photoshop, Photoshop, Other] 24 - Num Slices: 1 +[Photoshop, Photoshop, Image] 4 - Has Real Merged Data: Yes +[Photoshop, Photoshop, Image] 5 - Writer Name: Adobe Photoshop +[Photoshop, Photoshop, Image] 9 - Reader Name: Adobe Photoshop 7.0 +[Photoshop, Photoshop, Image] 0 - Photoshop Quality: 7 +[Photoshop, Photoshop, Image] 1 - Photoshop Format: Standard +[APP13, AdobeCM, Image] 0 - Adobe CM Type: 3 +[APP14, Adobe, Image] 0 - DCT Encode Version: 100 +[APP14, Adobe, Image] 1 - APP14 Flags 0: (none) +[APP14, Adobe, Image] 2 - APP14 Flags 1: (none) +[APP14, Adobe, Image] 3 - Color Transform: YCbCr +[APP15, GraphConv, Image] Q - Quality: 70 +[MIE, MIE-Doc, Author] Copyright - Copyright: © 2006 Phil Harvey +[MIE, MIE-Main, Other] zmie - Trailer Signature: +[PhotoMechanic, PhotoMechanic, Image] 221 - Tagged: Yes +[PhotoMechanic, PhotoMechanic, Image] 222 - Color Class: 6 (Typical alt) +[PhotoMechanic, PhotoMechanic, Image] 216 - Rotation: 180 +[PhotoMechanic, PhotoMechanic, Image] 217 - Crop Left: 438 +[PhotoMechanic, PhotoMechanic, Image] 218 - Crop Top: 618 +[PhotoMechanic, PhotoMechanic, Image] 219 - Crop Right: 890 +[PhotoMechanic, PhotoMechanic, Image] 220 - Crop Bottom: 1072 +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 1.0.0 +[CanonVRD, CanonVRD, Image] 6 - WB Adj RGGB Levels: 0 0 0 0 +[CanonVRD, CanonVRD, Image] 24 - White Balance Adj: Shot Settings +[CanonVRD, CanonVRD, Image] 26 - WB Adj Color Temp: 5600 +[CanonVRD, CanonVRD, Image] 36 - WB Fine Tune Active: No +[CanonVRD, CanonVRD, Image] 40 - WB Fine Tune Saturation: 0 +[CanonVRD, CanonVRD, Image] 44 - WB Fine Tune Tone: 0 +[CanonVRD, CanonVRD, Image] 46 - Raw Color Adj: Shot Settings +[CanonVRD, CanonVRD, Image] 48 - Raw Custom Saturation: 0 +[CanonVRD, CanonVRD, Image] 52 - Raw Custom Tone: 0 +[CanonVRD, CanonVRD, Image] 56 - Raw Brightness Adj: 0.00 +[CanonVRD, CanonVRD, Image] 60 - Tone Curve Property: Shot Settings +[CanonVRD, CanonVRD, Image] 122 - Dynamic Range Min: 0 +[CanonVRD, CanonVRD, Image] 124 - Dynamic Range Max: 4095 +[CanonVRD, CanonVRD, Image] 272 - Tone Curve Active: No +[CanonVRD, CanonVRD, Image] 275 - Tone Curve Mode: RGB +[CanonVRD, CanonVRD, Image] 276 - Brightness Adj: 0 +[CanonVRD, CanonVRD, Image] 277 - Contrast Adj: 0 +[CanonVRD, CanonVRD, Image] 278 - Saturation Adj: 100 +[CanonVRD, CanonVRD, Image] 286 - Color Tone Adj: 0 +[CanonVRD, CanonVRD, Image] 294 - Luminance Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 336 - Luminance Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 345 - Tone Curve Interpolation: Curve +[CanonVRD, CanonVRD, Image] 352 - Red Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 394 - Red Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 410 - Green Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 452 - Green Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 468 - Blue Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 510 - Blue Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 526 - RGB Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 568 - RGB Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 580 - Crop Active: No +[CanonVRD, CanonVRD, Image] 582 - Crop Left: 0 +[CanonVRD, CanonVRD, Image] 584 - Crop Top: 0 +[CanonVRD, CanonVRD, Image] 586 - Crop Width: 0 +[CanonVRD, CanonVRD, Image] 588 - Crop Height: 0 +[CanonVRD, CanonVRD, Image] 602 - Sharpness Adj: 0 +[CanonVRD, CanonVRD, Image] 608 - Crop Aspect Ratio: Free +[CanonVRD, CanonVRD, Image] 610 - Constrained Crop Width: 0 +[CanonVRD, CanonVRD, Image] 614 - Constrained Crop Height: 0 +[CanonVRD, CanonVRD, Image] 618 - Check Mark: Clear +[CanonVRD, CanonVRD, Image] 622 - Rotation: 90 +[CanonVRD, CanonVRD, Image] 624 - Work Color Space: sRGB +[FotoStation, FotoStation, Image] 0 - Original Image Width: 16 +[FotoStation, FotoStation, Image] 1 - Original Image Height: 16 +[FotoStation, FotoStation, Image] 2 - Color Planes: 3 +[FotoStation, FotoStation, Image] 3 - XY Resolution: 9 +[FotoStation, FotoStation, Image] 4 - Rotation: 0 +[FotoStation, FotoStation, Image] 6 - Crop Left: 24.557% +[FotoStation, FotoStation, Image] 7 - Crop Top: 21.25% +[FotoStation, FotoStation, Image] 8 - Crop Right: 30.676% +[FotoStation, FotoStation, Image] 9 - Crop Bottom: 86.25% +[FotoStation, FotoStation, Image] 11 - Crop Rotation: 0 +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/155 +[Composite, Composite, Image] Exif-LightValue - Light Value: 10.9 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 6.9 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.004 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 47.0 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.0 mm (35 mm equivalent: 41.4 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 2.36 m diff --git a/ExifTool/t/ExifTool_20.out b/ExifTool/t/ExifTool_20.out new file mode 100644 index 0000000..914f324 --- /dev/null +++ b/ExifTool/t/ExifTool_20.out @@ -0,0 +1 @@ +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto diff --git a/ExifTool/t/ExifTool_21.out b/ExifTool/t/ExifTool_21.out new file mode 100644 index 0000000..0c18e97 --- /dev/null +++ b/ExifTool/t/ExifTool_21.out @@ -0,0 +1 @@ +[ICC_Profile, ICC_Profile, Image] ICC_Profile - ICC Profile: (Binary data 3144 bytes) diff --git a/ExifTool/t/ExifTool_22.out b/ExifTool/t/ExifTool_22.out new file mode 100644 index 0000000..514629e --- /dev/null +++ b/ExifTool/t/ExifTool_22.out @@ -0,0 +1 @@ +F_neP_x2400Zoom - Canon PowerShot A5 diff --git a/ExifTool/t/ExifTool_23.out b/ExifTool/t/ExifTool_23.out new file mode 100644 index 0000000..cefa4c7 --- /dev/null +++ b/ExifTool/t/ExifTool_23.out @@ -0,0 +1,6 @@ +[IPTC, IPTC3, Author] 80 - By-line: byline +[IPTC, IPTC3, Author] 85 - By-line Title: byline title +[IPTC, IPTC3, Author] 110 - Credit: credit +[IPTC, IPTC3, Author] 115 - Source: source +[IPTC, IPTC3, Author] 116 - Copyright Notice: copy freely +[IPTC, IPTC3, Author] 122 - Writer-Editor: caption writer diff --git a/ExifTool/t/ExifTool_24.out b/ExifTool/t/ExifTool_24.out new file mode 100644 index 0000000..e49a205 --- /dev/null +++ b/ExifTool/t/ExifTool_24.out @@ -0,0 +1,3 @@ +[EXIF, ExifIFD, Camera] 37385 - Flash: 0 +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 34 +[EXIF, ExifIFD, Camera] 41987 - White Balance: 0 diff --git a/ExifTool/t/ExifTool_25.out b/ExifTool/t/ExifTool_25.out new file mode 100644 index 0000000..41dcd3b --- /dev/null +++ b/ExifTool/t/ExifTool_25.out @@ -0,0 +1,14 @@ +[EXIF, IFD0, Time] 306 - Modify Date: 2004:01:26 09:36:46 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:04:18 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:04:18 18:36:41 +[MakerNotes, CIFF, Time] 0 - Date/Time Original: 1998:03:31 21:33:18 +[MakerNotes, CIFF, Time] 1 - Time Zone Code: 0 +[MakerNotes, CIFF, Time] 2 - Time Zone Info: 0 +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:01:26 +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:05:03 00:00:00 +[FlashPix, FlashPix, Time] 4 - Extension Create Date: 1999:05:14 21:47:25 +[FlashPix, FlashPix, Time] 5 - Extension Modify Date: 1999:05:14 21:47:25 +[XML, MediaJukebox, Time] Date - Date: 2013:08:01 20:12:19 +[APP12, PictureInfo, Time] TimeDate - Date/Time Original: 1998:11:30 15:17:20 +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC3, Time] 55 - Date Created: 2005:12:23 diff --git a/ExifTool/t/ExifTool_26.out b/ExifTool/t/ExifTool_26.out new file mode 100644 index 0000000..9f23dc3 --- /dev/null +++ b/ExifTool/t/ExifTool_26.out @@ -0,0 +1,13 @@ +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 10.66 +[File, File, Image] ExifByteOrder - Exif Byte Order: II +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 4 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[MakerNotes, Canon, Camera] 11 - Easy Mode: 1 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 22 - Exposure Time: 128 +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 160 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 120 +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: 1 +[File, File, Image] EncodingProcess - Encoding Process: 0 +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual diff --git a/ExifTool/t/ExifTool_27.out b/ExifTool/t/ExifTool_27.out new file mode 100644 index 0000000..61674f5 --- /dev/null +++ b/ExifTool/t/ExifTool_27.out @@ -0,0 +1,4 @@ +[XMP, XMP-dc, Image] subject - Subject: ExifTool +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing +[IPTC, IPTC3, Other] 20 - Supplemental Categories: supp cat diff --git a/ExifTool/t/ExifTool_28.out b/ExifTool/t/ExifTool_28.out new file mode 100644 index 0000000..ed8ae58 --- /dev/null +++ b/ExifTool/t/ExifTool_28.out @@ -0,0 +1,11 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: ExifTool.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 26 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2021:03:19 14:14:12-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:35-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg diff --git a/ExifTool/t/ExifTool_29.out b/ExifTool/t/ExifTool_29.out new file mode 100644 index 0000000..21add7a --- /dev/null +++ b/ExifTool/t/ExifTool_29.out @@ -0,0 +1,441 @@ +[File, System, Other] FileName - File Name: ExifTool.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: ##_kB +[File, System, Time] FileModifyDate - File Modification Date/Time: ####:##:##_##:##:##-##:## +[File, System, Time] FileAccessDate - File Access Date/Time: ####:##:##_##:##:##-##:## +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: ####:##:##_##:##:##-##:## +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian_(Intel,_II) +[File, File, Image] Comment - Comment: ©_PhotoStudio_Unicode_comment;; +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: ####be##ef#a######f###aa#####c## +[File, File, Image] Comment - Comment: a_comment +[File, File, Image] ImageWidth - Image Width: # +[File, File, Image] ImageHeight - Image Height: # +[File, File, Image] EncodingProcess - Encoding Process: Baseline_DCT,_Huffman_coding +[File, File, Image] BitsPerSample - Bits Per Sample: # +[File, File, Image] ColorComponents - Color Components: # +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr#:#:#_(#_#) +[EXIF, IFD0, Image] 270 - Image Description: A_witty_caption +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix####Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal_(normal) +[EXIF, IFD0, Image] 282 - X Resolution: ## +[EXIF, IFD0, Image] 283 - Y Resolution: ## +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe_Photoshop_#.# +[EXIF, IFD0, Time] 306 - Modify Date: ####:##:##_##:##:## +[EXIF, IFD0, Author] 315 - Artist: Phil_Harvey +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: Copyright_####_Phil_Harvey +[EXIF, ExifIFD, Image] 33437 - F Number: #.# +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program_AE +[EXIF, ExifIFD, Image] 34855 - ISO: ### +[EXIF, ExifIFD, Image] 36864 - Exif Version: #### +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: ####:##:##_##:##:## +[EXIF, ExifIFD, Time] 36868 - Create Date: ####:##:##_##:##:## +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y,_Cb,_Cr,_- +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: #.# +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: #/## +[EXIF, ExifIFD, Image] 37378 - Aperture Value: #.# +[EXIF, ExifIFD, Image] 37379 - Brightness Value: # +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: # +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: #.# +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: #.#_mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: #### +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: ### +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: ## +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: #### +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: #### +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip_color_area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital_Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly_photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG_(old-style) +[EXIF, IFD1, Image] 282 - X Resolution: ## +[EXIF, IFD1, Image] 283 - Y Resolution: ## +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: ### +[EXIF, IFD1, Image] 514 - Thumbnail Length: # +[JFIF, JFIF, Image] 0 - JFIF Version: #.## +[JFIF, JFIF, Image] 2 - Resolution Unit: inches +[JFIF, JFIF, Image] 3 - X Resolution: ### +[JFIF, JFIF, Image] 5 - Y Resolution: ### +[JFIF, JFXX, Preview] 16 - Thumbnail Image: (Binary data 1558 bytes) +[APP8, SPIFF, Image] 0 - SPIFF Version: #.# +[APP8, SPIFF, Image] 2 - Profile ID: Continuous-tone_Base +[APP8, SPIFF, Image] 3 - Color Components: # +[APP8, SPIFF, Image] 6 - Image Height: #### +[APP8, SPIFF, Image] 10 - Image Width: #### +[APP8, SPIFF, Image] 14 - Color Space: YCbCr,_ITU-R_BT_###,_video +[APP8, SPIFF, Image] 15 - Bits Per Sample: # +[APP8, SPIFF, Image] 16 - Compression: JPEG +[APP8, SPIFF, Image] 17 - Resolution Unit: inches +[APP8, SPIFF, Image] 18 - Y Resolution: ### +[APP8, SPIFF, Image] 22 - X Resolution: ### +[MakerNotes, CIFF, Image] 0 - File Format: JPEG_(lossy) +[MakerNotes, CIFF, Image] 1 - Target Compression Ratio: # +[MakerNotes, CIFF, Image] 0 - Image Width: ### +[MakerNotes, CIFF, Image] 1 - Image Height: ### +[MakerNotes, CIFF, Image] 2 - Pixel Aspect Ratio: # +[MakerNotes, CIFF, Image] 3 - Rotation: # +[MakerNotes, CIFF, Image] 4 - Component Bit Depth: # +[MakerNotes, CIFF, Image] 5 - Color Bit Depth: ## +[MakerNotes, CIFF, Image] 6 - Color BW: # +[MakerNotes, CIFF, Camera] 4106 - Target Image Type: Real-world_Subject +[MakerNotes, CIFF, Camera] 6148 - Record ID: # +[MakerNotes, CIFF, Image] 6167 - File Number: ## +[MakerNotes, CIFF, Time] 0 - Date/Time Original: ####:##:##_##:##:## +[MakerNotes, CIFF, Time] 1 - Time Zone Code: # +[MakerNotes, CIFF, Time] 2 - Time Zone Info: # +[MakerNotes, CIFF, Camera] 2070 - Original File Name: C:\DC##\CTG_####\AUT_####.JPG +[MakerNotes, CIFF, Camera] 2071 - Thumbnail File Name: C:\DC##\CTG_####\THM_####.JPG +[MakerNotes, CIFF, Camera] 4112 - Shutter Release Method: Single_Shot +[MakerNotes, CIFF, Camera] 4113 - Shutter Release Timing: Priority_on_focus +[MakerNotes, CIFF, Image] 0 - Flash Guide Number: # +[MakerNotes, CIFF, Image] 1 - Flash Threshold: # +[MakerNotes, CIFF, Image] 0 - Exposure Compensation: # +[MakerNotes, CIFF, Image] 1 - Shutter Speed Value: #/## +[MakerNotes, CIFF, Image] 2 - Aperture Value: #.# +[MakerNotes, CIFF, Camera] 6151 - Target Distance Setting: ###_mm +[MakerNotes, CIFF, Camera] 6164 - Measured EV: ##.## +[MakerNotes, CIFF, Camera] 2053 - Canon File Description: +[MakerNotes, CIFF, Camera] 2069 - Canon Image Type: AUT:Full_automatic_mode +[MakerNotes, CIFF, Camera] 2064 - Owner Name: +[MakerNotes, CIFF, Camera] 0 - Make: Canon +[MakerNotes, CIFF, Camera] 6 - Camera Model Name: Canon_PowerShot_A# +[MakerNotes, CIFF, Camera] 4124 - Base ISO: ### +[MakerNotes, CIFF, Camera] 2061 - ROM Operation Mode: USA +[MakerNotes, CIFF, Camera] 2059 - Canon Firmware Version: Firmware_Version_##.## +[MakerNotes, CIFF, Camera] 1 - Free Bytes: (Binary data 12 bytes) +[MakerNotes, CIFF, Image] 0 - Focal Type: Fixed +[MakerNotes, CIFF, Image] 1 - Focal Length: #_mm +[MakerNotes, CIFF, Image] 2 - Focal Plane X Size: #.##_mm +[MakerNotes, CIFF, Image] 3 - Focal Plane Y Size: #.##_mm +[MakerNotes, Qualcomm, Camera] aec_current_sensor_luma - AEC Current Sensor Luma: ## +[MakerNotes, Qualcomm, Camera] af_position - AF Position: ## +[MakerNotes, Qualcomm, Camera] aec_current_exp_index - AEC Current Exp Index: ### +[MakerNotes, Qualcomm, Camera] awb_sample_decision - AWB Sample Decision: # +[MakerNotes, Qualcomm, Camera] asf5_enable - ASF5 Enable: # +[MakerNotes, Qualcomm, Camera] asf5_filter_mode - ASF5 Filter Mode: # +[MakerNotes, Qualcomm, Camera] asf5_exposure_index_1 - ASF5 Exposure Index 1: ### +[MakerNotes, Qualcomm, Camera] asf5_exposure_index_2 - ASF5 Exposure Index 2: ### +[MakerNotes, Qualcomm, Camera] asf5_max_exposure_index - ASF5 Max Exposure Index: ### +[MakerNotes, Samsung, Other] 0x0100-name - Embedded Audio File Name: SoundShot_### +[MakerNotes, Samsung, Audio] 0x0100 - Embedded Audio File: (Binary data 16 bytes) +[APP0, AVI1, Image] 0 - Interleaved Field: Not_Interleaved +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool_#.## +[XMP, XMP-rdf, Document] about - About: uuid:#####b##-####-fa##-####-#f#ed##cd##d +[XMP, XMP-dc, Author] creator - Creator: Phil_Harvey +[XMP, XMP-dc, Image] description - Description: UTF-##_(big-endian)_encoded_XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright_####_Phil_Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test_IPTC_picture +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My_Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I_wrote_it +[XMP, XMP-photoshop, Image] Category - Category: # +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My_Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: ####:##:## +[XMP, XMP-photoshop, Image] Headline - Headline: No_headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: What_instructions +[XMP, XMP-photoshop, Author] Source - Source: I'm_the_source +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What_is_a_transmission_reference? +[XMP, XMP-photoshop, Image] Urgency - Urgency: #_(least_urgent) +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My_Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:#cc#b###-##d#-##d#-#ac#-bb##c##f#d#a +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Adobe_Systems_Inc. +[ICC_Profile, ICC-header, Image] 8 - Profile Version: #.#.# +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display_Device_Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB_ +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ_ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: ####:##:##_##:##:## +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple_Computer_Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not_Embedded,_Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective,_Glossy,_Positive,_Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: #.####_#_#.##### +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Adobe_Systems_Inc. +[ICC_Profile, ICC-header, Image] 84 - Profile ID: # +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright_####_Adobe_Systems_Incorporated +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Adobe_RGB_(####) +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: #.#####_#_#.##### +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: #_#_# +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: #.#####_#.#####_#.##### +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: #.#####_#.#####_#.##### +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: #.#####_#.#####_#.##### +[FlashPix, FlashPix, Other] 1 - Code Page: Unicode_UTF-##,_little_endian +[FlashPix, FlashPix, Other] 268435456 - Used Extension Numbers: # +[FlashPix, FlashPix, Other] 1 - Extension Name: Screen_nail +[FlashPix, FlashPix, Other] 2 - Extension Class ID: ########-#FC#-##D#-BD##-########A### +[FlashPix, FlashPix, Other] 3 - Extension Persistence: Invalidated_By_Modification +[FlashPix, FlashPix, Time] 4 - Extension Create Date: ####:##:##_##:##:## +[FlashPix, FlashPix, Time] 5 - Extension Modify Date: ####:##:##_##:##:## +[FlashPix, FlashPix, Other] 6 - Creating Application: Digita +[FlashPix, FlashPix, Other] 7 - Extension Description: Presized_image_for_LCD_display +[FlashPix, FlashPix, Other] 4096 - Storage-Stream Pathname: /.Screen_Nail_bd##########a### +[FlashPix, FlashPix, Other] Screen Nail - Screen Nail: (Binary data 5917 bytes) +[FlashPix, FlashPix, Preview] FlashPix-PreviewImage - Preview Image: (Binary data 5777 bytes) +[MPF, MPF0, Image] 45056 - MPF Version: #### +[MPF, MPF0, Image] 45057 - Number Of Images: # +[MPF, MPImage1, Image] 0.1 - MP Image Flags: Dependent_parent_image +[MPF, MPImage1, Image] 0.2 - MP Image Format: JPEG +[MPF, MPImage1, Image] 0.3 - MP Image Type: Baseline_MP_Primary_Image +[MPF, MPImage1, Image] 4 - MP Image Length: ####### +[MPF, MPImage1, Image] 8 - MP Image Start: # +[MPF, MPImage1, Image] 12 - Dependent Image 1 Entry Number: # +[MPF, MPImage1, Image] 14 - Dependent Image 2 Entry Number: # +[MPF, MPImage2, Image] 0.1 - MP Image Flags: Dependent_child_image +[MPF, MPImage2, Image] 0.2 - MP Image Format: JPEG +[MPF, MPImage2, Image] 0.3 - MP Image Type: Large_Thumbnail_(full_HD_equivalent) +[MPF, MPImage2, Image] 4 - MP Image Length: ####### +[MPF, MPImage2, Image] 8 - MP Image Start: ####### +[MPF, MPImage2, Image] 12 - Dependent Image 1 Entry Number: # +[MPF, MPImage2, Image] 14 - Dependent Image 2 Entry Number: # +[MPF, MPImage2, Preview] PreviewImage - Preview Image: (Binary data 25 bytes) +[Meta, MetaIFD, Image] 50000 - Film Product Code: ## +[Meta, MetaIFD, Image] 50001 - Image Source EK: # +[Meta, MetaIFD, Image] 50002 - Capture Conditions PAR: # +[Meta, MetaIFD, Image] 50009 - Frame Number: # +[Meta, MetaIFD, Image] 50010 - Film Category: # +[Meta, MetaIFD, Image] 50011 - Film Gencode: # +[Meta, MetaIFD, Image] 50012 - Model And Version: Version_# +[Meta, MetaIFD, Image] 50013 - Film Size: # +[Meta, MetaIFD, Image] 50014 - SBA RGB Shifts: #_#_# +[Meta, MetaIFD, Image] 50015 - SBA Input Image Colorspace: # +[Meta, MetaIFD, Image] 50016 - SBA Input Image Bit Depth: ##_##_## +[Meta, MetaIFD, Image] 50017 - SBA Exposure Record: (Binary data 368 bytes) +[Meta, MetaIFD, Image] 50018 - User Adj SBA RGB Shifts: (Binary data 5 bytes) +[Meta, MetaIFD, Image] 50019 - Image Rotation Status: # +[Meta, MetaIFD, Image] 50020 - Roll Guid Elements: ################################ +[Meta, MetaIFD, Image] 50021 - Metadata Number: #### +[APP5, RMETA, Image] Sign type - Sign Type: Information +[APP5, RMETA, Image] Location - Location: Roundabout +[APP5, RMETA, Image] Lit - Lit: No +[APP5, RMETA, Image] Condition - Condition: Good +[APP5, RMETA, Image] Azimuth - Azimuth: E +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: #### +[APP6, NITF, Image] 0 - NITF Version: #.## +[APP6, NITF, Image] 2 - Image Format: IMode_B +[APP6, NITF, Image] 3 - Blocks Per Row: # +[APP6, NITF, Image] 5 - Blocks Per Column: # +[APP6, NITF, Image] 7 - Image Color: Monochrome +[APP6, NITF, Image] 8 - Bit Depth: # +[APP6, NITF, Image] 9 - Image Class: General_Purpose +[APP6, NITF, Image] 10 - JPEG Process: Baseline_sequential_DCT,_Huffman_coding,_#-bit_samples +[APP6, NITF, Image] 11 - Quality: # +[APP6, NITF, Image] 12 - Stream Color: Monochrome +[APP6, NITF, Image] 13 - Stream Bit Depth: # +[APP6, NITF, Image] 14 - Flags: #x####### +[XML, MediaJukebox, Image] Tool_Name - Tool Name: Media_Center +[XML, MediaJukebox, Image] Tool_Version - Tool Version: ##.#.## +[XML, MediaJukebox, Image] People - People: Santa +[XML, MediaJukebox, Image] Places - Places: Jamaica +[XML, MediaJukebox, Time] Date - Date: ####:##:##_##:##:## +[XML, MediaJukebox, Image] Album - Album: ####-##-## +[XML, MediaJukebox, Image] Name - Name: Glass_home_at_night +[APP11, JPEG-HDR, Image] ver - JPEG-HDR Version: ## +[APP11, JPEG-HDR, Image] ln0 - Ln0: #.###### +[APP11, JPEG-HDR, Image] ln1 - Ln1: #.###### +[APP11, JPEG-HDR, Image] s2n - S2n: #.######e+## +[APP11, JPEG-HDR, Image] alp - Alpha: #.###### +[APP11, JPEG-HDR, Image] bet - Beta: #.###### +[APP11, JPEG-HDR, Image] cor - Correction Method: # +[APP11, JPEG-HDR, Preview] RatioImage - Ratio Image: (Binary data 19 bytes) +[JUMBF, JUMBF, Image] type - JUMD Type: (cacb)-####-####-######aa#####b## +[JUMBF, JUMBF, Image] label - JUMD Label: cai +[JUMBF, JUMBF, Image] type - JUMD Type: (cast)-####-####-######aa#####b## +[JUMBF, JUMBF, Image] label - JUMD Label: cb.reuters_# +[JUMBF, JUMBF, Image] type - JUMD Type: (caas)-####-####-######aa#####b## +[JUMBF, JUMBF, Image] label - JUMD Label: cai.assertions +[JUMBF, JUMBF, Image] type - JUMD Type: (json)-####-####-######aa#####b## +[JUMBF, JUMBF, Image] label - JUMD Label: adobe.asset.info +[JUMBF, JSON, Other] title - Title: HEALTHCORONAVIRUSUSAOREGON_SALEM_##.jpg +[JUMBF, JUMBF, Image] type - JUMD Type: (json)-####-####-######aa#####b## +[JUMBF, JUMBF, Image] label - JUMD Label: cai.location.broad +[JUMBF, JSON, Other] location - Location: Salem,_Oregon +[JUMBF, JUMBF, Image] type - JUMD Type: (json)-####-####-######aa#####b## +[JUMBF, JUMBF, Image] label - JUMD Label: cai.rights +[JUMBF, JSON, Other] copyright - Copyright: Alisha_Jucevic +[APP12, PictureInfo, Time] TimeDate - Date/Time Original: ####:##:##_##:##:## +[APP12, PictureInfo, Image] Shutter - Exposure Time: #/### +[APP12, PictureInfo, Image] Flash - Flash: Off +[APP12, PictureInfo, Image] Resolution - Resolution: # +[APP12, PictureInfo, Image] Protect - Protect: # +[APP12, PictureInfo, Image] ContTake - Cont Take: # +[APP12, PictureInfo, Image] ImageSize - Image Size: ####x### +[APP12, PictureInfo, Image] ColorMode - Color Mode: # +[APP12, PictureInfo, Image] FNumber - F Number: ##.# +[APP12, PictureInfo, Image] Zoom - Zoom: #.# +[APP12, PictureInfo, Image] Macro - Macro: Off +[APP12, PictureInfo, Image] LightS - Light S: # +[APP12, PictureInfo, Image] ExpBias - Exposure Compensation: +#.# +[APP12, PictureInfo, Camera] Type - Camera Type: SR## +[APP12, PictureInfo, Camera] Serial# - Serial Number: ######### +[APP12, PictureInfo, Camera] Version - Version: v##-## +[APP12, PictureInfo, Camera] ID - ID: AGFA_DIGITAL_CAMERA +[APP12, PictureInfo, Image] PicLen - Pic Len: ###### +[APP12, PictureInfo, Image] ThmLen - Thm Len: #### +[APP12, PictureInfo, Image] Q - Tag Q: ## +[APP12, PictureInfo, Image] R - Tag R: ### +[APP12, PictureInfo, Image] B - Tag B: ### +[APP12, PictureInfo, Image] s0 - S0: #e#,#,##b#,#f##,##cf,####,####,#######,a#e####,#,#f####d,#f###c#,#######,#,# +[APP12, PictureInfo, Image] T0 - T0: ##b#####,#######,e##c#f,#,#,# +[Ducky, Ducky, Image] 1 - Quality: ##% +[Ducky, Ducky, Author] 3 - Copyright: Copyright_####_Phil_Harvey +[IPTC, IPTC, Other] 0 - Application Record Version: # +[IPTC, IPTC, Other] 120 - Caption-Abstract: A_witty_caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I_wrote_it +[IPTC, IPTC, Other] 105 - Headline: No_headline +[IPTC, IPTC, Other] 40 - Special Instructions: What_instructions +[IPTC, IPTC, Author] 80 - By-line: Phil_Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My_Position +[IPTC, IPTC, Author] 110 - Credit: My_Credit +[IPTC, IPTC, Other] 5 - Object Name: Test_IPTC_picture +[IPTC, IPTC, Time] 55 - Date Created: ####:##:## +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What_is_a_transmission_reference +[IPTC, IPTC, Other] 15 - Category: # +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright_####_Phil_Harvey +[IPTC, IPTC, Other] 10 - Urgency: #_(least_urgent) +[IPTC, IPTC, Author] 115 - Source: I'm_the_source +[IPTC, IPTC, Other] 25 - Keywords: jambalaya +[IPTC, IPTC2, Other] 0 - Application Record Version: # +[IPTC, IPTC3, Other] 0 - Application Record Version: # +[IPTC, IPTC3, Other] 15 - Category: p +[IPTC, IPTC3, Time] 55 - Date Created: ####:##:## +[IPTC, IPTC3, Other] 5 - Object Name: object_name +[IPTC, IPTC3, Other] 10 - Urgency: # +[IPTC, IPTC3, Other] 20 - Supplemental Categories: supp_cat +[IPTC, IPTC3, Other] 40 - Special Instructions: special_instructions +[IPTC, IPTC3, Author] 80 - By-line: byline +[IPTC, IPTC3, Author] 85 - By-line Title: byline_title +[IPTC, IPTC3, Location] 90 - City: city +[IPTC, IPTC3, Location] 101 - Country-Primary Location Name: country_name +[IPTC, IPTC3, Other] 103 - Original Transmission Reference: otr +[IPTC, IPTC3, Other] 105 - Headline: headline +[IPTC, IPTC3, Author] 110 - Credit: credit +[IPTC, IPTC3, Author] 115 - Source: source +[IPTC, IPTC3, Author] 116 - Copyright Notice: copy_freely +[IPTC, IPTC3, Other] 120 - Caption-Abstract: ExifTool_AFCP_test +[IPTC, IPTC3, Author] 122 - Writer-Editor: caption_writer +[IPTC, IPTC3, Location] 95 - Province-State: state +[IPTC, IPTC3, Other] 25 - Keywords: jambalaya +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: ##ad####b#a##f#f####ac###fa###da +[Photoshop, Photoshop, Image] 0 - X Resolution: ## +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: ## +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Image] 0 - Print Style: Centered +[Photoshop, Photoshop, Image] 2 - Print Position: #_# +[Photoshop, Photoshop, Image] 10 - Print Scale: # +[Photoshop, Photoshop, Image] 1037 - Global Angle: ## +[Photoshop, Photoshop, Image] 1049 - Global Altitude: ## +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/____________________ +[Photoshop, Photoshop, Image] 1054 - URL List: +[Photoshop, Photoshop, Other] 20 - Slices Group Name: IPTC +[Photoshop, Photoshop, Other] 24 - Num Slices: # +[Photoshop, Photoshop, Image] 4 - Has Real Merged Data: Yes +[Photoshop, Photoshop, Image] 5 - Writer Name: Adobe_Photoshop +[Photoshop, Photoshop, Image] 9 - Reader Name: Adobe_Photoshop_#.# +[Photoshop, Photoshop, Image] 0 - Photoshop Quality: # +[Photoshop, Photoshop, Image] 1 - Photoshop Format: Standard +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest_is_not_current._XMP_may_be_out_of_sync +[APP13, AdobeCM, Image] 0 - Adobe CM Type: # +[APP14, Adobe, Image] 0 - DCT Encode Version: ### +[APP14, Adobe, Image] 1 - APP14 Flags 0: (none) +[APP14, Adobe, Image] 2 - APP14 Flags 1: (none) +[APP14, Adobe, Image] 3 - Color Transform: YCbCr +[APP15, GraphConv, Image] Q - Quality: ## +[MIE, MIE-Doc, Author] Copyright - Copyright: ©_####_Phil_Harvey +[MIE, MIE-Main, Other] zmie - Trailer Signature: +[PhotoMechanic, PhotoMechanic, Image] 221 - Tagged: Yes +[PhotoMechanic, PhotoMechanic, Image] 222 - Color Class: #_(Typical_alt) +[PhotoMechanic, PhotoMechanic, Image] 216 - Rotation: ### +[PhotoMechanic, PhotoMechanic, Image] 217 - Crop Left: ### +[PhotoMechanic, PhotoMechanic, Image] 218 - Crop Top: ### +[PhotoMechanic, PhotoMechanic, Image] 219 - Crop Right: ### +[PhotoMechanic, PhotoMechanic, Image] 220 - Crop Bottom: #### +[CanonVRD, CanonVRD, Image] 2 - VRD Version: #.#.# +[CanonVRD, CanonVRD, Image] 6 - WB Adj RGGB Levels: #_#_#_# +[CanonVRD, CanonVRD, Image] 24 - White Balance Adj: Shot_Settings +[CanonVRD, CanonVRD, Image] 26 - WB Adj Color Temp: #### +[CanonVRD, CanonVRD, Image] 36 - WB Fine Tune Active: No +[CanonVRD, CanonVRD, Image] 40 - WB Fine Tune Saturation: # +[CanonVRD, CanonVRD, Image] 44 - WB Fine Tune Tone: # +[CanonVRD, CanonVRD, Image] 46 - Raw Color Adj: Shot_Settings +[CanonVRD, CanonVRD, Image] 48 - Raw Custom Saturation: # +[CanonVRD, CanonVRD, Image] 52 - Raw Custom Tone: # +[CanonVRD, CanonVRD, Image] 56 - Raw Brightness Adj: #.## +[CanonVRD, CanonVRD, Image] 60 - Tone Curve Property: Shot_Settings +[CanonVRD, CanonVRD, Image] 122 - Dynamic Range Min: # +[CanonVRD, CanonVRD, Image] 124 - Dynamic Range Max: #### +[CanonVRD, CanonVRD, Image] 272 - Tone Curve Active: No +[CanonVRD, CanonVRD, Image] 275 - Tone Curve Mode: RGB +[CanonVRD, CanonVRD, Image] 276 - Brightness Adj: # +[CanonVRD, CanonVRD, Image] 277 - Contrast Adj: # +[CanonVRD, CanonVRD, Image] 278 - Saturation Adj: ### +[CanonVRD, CanonVRD, Image] 286 - Color Tone Adj: # +[CanonVRD, CanonVRD, Image] 294 - Luminance Curve Points: (#,#)_(###,###) +[CanonVRD, CanonVRD, Image] 336 - Luminance Curve Limits: ###_#_###_# +[CanonVRD, CanonVRD, Image] 345 - Tone Curve Interpolation: Curve +[CanonVRD, CanonVRD, Image] 352 - Red Curve Points: (#,#)_(###,###) +[CanonVRD, CanonVRD, Image] 394 - Red Curve Limits: ###_#_###_# +[CanonVRD, CanonVRD, Image] 410 - Green Curve Points: (#,#)_(###,###) +[CanonVRD, CanonVRD, Image] 452 - Green Curve Limits: ###_#_###_# +[CanonVRD, CanonVRD, Image] 468 - Blue Curve Points: (#,#)_(###,###) +[CanonVRD, CanonVRD, Image] 510 - Blue Curve Limits: ###_#_###_# +[CanonVRD, CanonVRD, Image] 526 - RGB Curve Points: (#,#)_(###,###) +[CanonVRD, CanonVRD, Image] 568 - RGB Curve Limits: ###_#_###_# +[CanonVRD, CanonVRD, Image] 580 - Crop Active: No +[CanonVRD, CanonVRD, Image] 582 - Crop Left: # +[CanonVRD, CanonVRD, Image] 584 - Crop Top: # +[CanonVRD, CanonVRD, Image] 586 - Crop Width: # +[CanonVRD, CanonVRD, Image] 588 - Crop Height: # +[CanonVRD, CanonVRD, Image] 602 - Sharpness Adj: # +[CanonVRD, CanonVRD, Image] 608 - Crop Aspect Ratio: Free +[CanonVRD, CanonVRD, Image] 610 - Constrained Crop Width: # +[CanonVRD, CanonVRD, Image] 614 - Constrained Crop Height: # +[CanonVRD, CanonVRD, Image] 618 - Check Mark: Clear +[CanonVRD, CanonVRD, Image] 622 - Rotation: ## +[CanonVRD, CanonVRD, Image] 624 - Work Color Space: sRGB +[FotoStation, FotoStation, Image] 0 - Original Image Width: ## +[FotoStation, FotoStation, Image] 1 - Original Image Height: ## +[FotoStation, FotoStation, Image] 2 - Color Planes: # +[FotoStation, FotoStation, Image] 3 - XY Resolution: # +[FotoStation, FotoStation, Image] 4 - Rotation: # +[FotoStation, FotoStation, Image] 6 - Crop Left: ##.###% +[FotoStation, FotoStation, Image] 7 - Crop Top: ##.##% +[FotoStation, FotoStation, Image] 8 - Crop Right: ##.###% +[FotoStation, FotoStation, Image] 9 - Crop Bottom: ##.##% +[FotoStation, FotoStation, Image] 11 - Crop Rotation: # +[Composite, Composite, Image] Exif-Aperture - Aperture: #.# +[Composite, Composite, Image] Exif-ImageSize - Image Size: #x# +[Composite, Composite, Image] Exif-Megapixels - Megapixels: #.###### +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: #/### +[Composite, Composite, Image] Exif-LightValue - Light Value: ##.# +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: #.# +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: #.###_mm +[Composite, Composite, Image] Exif-FOV - Field Of View: ##.#_deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: #.#_mm_(##_mm_equivalent:_##.#_mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: #.##_m diff --git a/ExifTool/t/ExifTool_3.out b/ExifTool/t/ExifTool_3.out new file mode 100644 index 0000000..ce37561 --- /dev/null +++ b/ExifTool/t/ExifTool_3.out @@ -0,0 +1,89 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileSize - File Size: 4.9 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2005:11:23 15:49:37-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:34-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: TIFF +[File, File, Other] FileTypeExtension - File Type Extension: tif +[File, File, Other] MIMEType - MIME Type: image/tiff +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 82a5301def77e837ceb56158abd65512 +[EXIF, IFD0, Image] 254 - Subfile Type: Full-resolution image +[EXIF, IFD0, Image] 256 - Image Width: 160 +[EXIF, IFD0, Image] 257 - Image Height: 120 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 259 - Compression: LZW +[EXIF, IFD0, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD0, Image] 270 - Image Description: The picture caption +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 273 - Strip Offsets: 3816 +[EXIF, IFD0, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD0, Image] 278 - Rows Per Strip: 120 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 1048 +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: GraphicConverter +[EXIF, IFD0, Time] 306 - Modify Date: 2004:02:20 08:07:49 +[EXIF, IFD0, Image] 317 - Predictor: None +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: The picture caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other] 40 - Special Instructions: no instructions +[IPTC, IPTC, Author] 80 - By-line: I'm the author +[IPTC, IPTC, Author] 85 - By-line Title: On top +[IPTC, IPTC, Author] 110 - Credit: Phil Harvey +[IPTC, IPTC, Author] 115 - Source: My camera +[IPTC, IPTC, Other] 5 - Object Name: This is the title +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:20 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ontario +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: no reference +[IPTC, IPTC, Other] 25 - Keywords: exiftool, test, picture +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright notice +[IPTC, IPTC, Other] 105 - Headline: headline +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Linotronic +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1998:02:09 06:49:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Microsoft Corporation +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: Hewlett-Packard +[ICC_Profile, ICC-header, Image] 52 - Device Model: sRGB +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Hewlett-Packard +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright (c) 1998 Hewlett-Packard Company +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: sRGB IEC61966-2.1 +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.43607 0.22249 0.01392 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.38515 0.71687 0.09708 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14307 0.06061 0.7141 +[ICC_Profile, ICC_Profile, Camera] dmnd - Device Mfg Desc: IEC http://www.iec.ch +[ICC_Profile, ICC_Profile, Camera] dmdd - Device Model Desc: IEC 61966-2.1 Default RGB colour space - sRGB +[ICC_Profile, ICC_Profile, Image] vued - Viewing Cond Desc: Reference Viewing Condition in IEC61966-2.1 +[ICC_Profile, ICC-view, Image] 8 - Viewing Cond Illuminant: 19.6445 20.3718 16.8089 +[ICC_Profile, ICC-view, Image] 20 - Viewing Cond Surround: 3.92889 4.07439 3.36179 +[ICC_Profile, ICC-view, Image] 32 - Viewing Cond Illuminant Type: D50 +[ICC_Profile, ICC_Profile, Image] lumi - Luminance: 76.03647 80 87.12462 +[ICC_Profile, ICC-meas, Image] 8 - Measurement Observer: CIE 1931 +[ICC_Profile, ICC-meas, Image] 12 - Measurement Backing: 0 0 0 +[ICC_Profile, ICC-meas, Image] 24 - Measurement Geometry: Unknown +[ICC_Profile, ICC-meas, Image] 28 - Measurement Flare: 0.999% +[ICC_Profile, ICC-meas, Image] 32 - Measurement Illuminant: D65 +[ICC_Profile, ICC_Profile, Image] tech - Technology: Cathode Ray Tube Display +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 2060 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 2060 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 2060 bytes) +[Composite, Composite, Image] Exif-ImageSize - Image Size: 160x120 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.019 diff --git a/ExifTool/t/ExifTool_30.out b/ExifTool/t/ExifTool_30.out new file mode 100644 index 0000000..5460f10 --- /dev/null +++ b/ExifTool/t/ExifTool_30.out @@ -0,0 +1,2 @@ +[File, File, Image] JPEGQualityEstimate - JPEG Quality Estimate: 61 +[File, File, Image] JPEGDigest - JPEG Digest: Adobe Photoshop, Save for web, Quality 10 diff --git a/ExifTool/t/ExifTool_31.out b/ExifTool/t/ExifTool_31.out new file mode 100644 index 0000000..c64c67a --- /dev/null +++ b/ExifTool/t/ExifTool_31.out @@ -0,0 +1 @@ +[ExifTool, ExifTool, ExifTool] Validate - Validate: OK diff --git a/ExifTool/t/ExifTool_32.out b/ExifTool/t/ExifTool_32.out new file mode 100644 index 0000000..8fdf640 --- /dev/null +++ b/ExifTool/t/ExifTool_32.out @@ -0,0 +1,5 @@ +[APP3, JPS, Image] 10 - JPS Separation: 0 +[APP3, JPS, Image] 11 - JPS Flags: Left field first +[APP3, JPS, Image] 12 - JPS Layout: Over Under +[APP3, JPS, Image] 13 - JPS Type: Stereo +[APP3, JPS, Image] 16 - JPS Comment: test comment diff --git a/ExifTool/t/ExifTool_33.out b/ExifTool/t/ExifTool_33.out new file mode 100644 index 0000000..4edd290 --- /dev/null +++ b/ExifTool/t/ExifTool_33.out @@ -0,0 +1,5 @@ +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 271 - Make: NIKON +[EXIF, IFD0, Camera] 272 - Camera Model Name: E775 +[EXIF, IFD0, Time] 306 - Modify Date: 2001:08:01 12:57:23 +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM diff --git a/ExifTool/t/ExifTool_34.out b/ExifTool/t/ExifTool_34.out new file mode 100644 index 0000000..cdc66f5 --- /dev/null +++ b/ExifTool/t/ExifTool_34.out @@ -0,0 +1 @@ +NIKON - Canon diff --git a/ExifTool/t/ExifTool_35.out b/ExifTool/t/ExifTool_35.out new file mode 100644 index 0000000..07d0c96 --- /dev/null +++ b/ExifTool/t/ExifTool_35.out @@ -0,0 +1 @@ +[Composite, Composite, Other] TestTag - Test Tag: Canon EOS DIGITAL REBEL -- E775 diff --git a/ExifTool/t/ExifTool_4.out b/ExifTool/t/ExifTool_4.out new file mode 100644 index 0000000..aa65ad6 --- /dev/null +++ b/ExifTool/t/ExifTool_4.out @@ -0,0 +1,41 @@ +ApertureValue: 14.0 +ColorSpace: sRGB +ComponentsConfiguration: Y, Cb, Cr, - +CompressedBitsPerPixel: 9 +CreateDate: 2003:12:04 06:46:52 +CustomRendered: Normal +DateTimeOriginal: 2003:12:04 06:46:52 +ExifImageHeight: 120 +ExifImageWidth: 160 +ExifVersion: 0221 +ExposureCompensation (1): 0 +ExposureMode: Manual +ExposureTime: 4 +FNumber: 14.0 +FileSource: Digital Camera +Flash: No Flash +FlashpixVersion: 0100 +FocalLength: 34.0 mm +FocalPlaneResolutionUnit: inches +FocalPlaneXResolution: 3443.946188 +FocalPlaneYResolution: 3442.016807 +ISO: 100 +InteropIndex: THM - DCF thumbnail file +InteropVersion: 0100 +Make: Canon +MaxApertureValue: 4.5 +MeteringMode (1): Average +Model: Canon EOS DIGITAL REBEL +ModifyDate: 2003:12:04 06:46:52 +Orientation: Horizontal (normal) +RelatedImageHeight: 2048 +RelatedImageWidth: 3072 +ResolutionUnit: inches +SceneCaptureType: Standard +SensingMethod: One-chip color area +ShutterSpeedValue: 0 +UserComment: +WhiteBalance (1): Auto +XResolution: 180 +YCbCrPositioning: Centered +YResolution: 180 diff --git a/ExifTool/t/ExifTool_5.out b/ExifTool/t/ExifTool_5.out new file mode 100644 index 0000000..8d70973 --- /dev/null +++ b/ExifTool/t/ExifTool_5.out @@ -0,0 +1,6 @@ +[EXIF, IFD0, Image] 274 - Orientation: 1 +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:04 06:46:52 diff --git a/ExifTool/t/ExifTool_6.out b/ExifTool/t/ExifTool_6.out new file mode 100644 index 0000000..91a58e3 --- /dev/null +++ b/ExifTool/t/ExifTool_6.out @@ -0,0 +1,71 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Canon.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:10:03 15:10:31-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:04-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:04 06:46:52 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 4 +[EXIF, ExifIFD, Image] 33437 - F Number: 14.0 +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 9 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 14.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 34.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 160 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 120 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: THM - DCF thumbnail file +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, InteropIFD, Image] 4097 - Related Image Width: 3072 +[EXIF, InteropIFD, Image] 4098 - Related Image Height: 2048 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Continuous Shooting +[Composite, Composite, Camera] Canon-ISO - ISO: 100 +[Composite, Composite, Camera] Canon-Lens - Lens: 18.0 - 55.0 mm +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Bulb +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 1719 832 831 990 +[Composite, Composite, Image] Exif-Aperture - Aperture: 14.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown 18-55mm +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 4 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 18.0 - 55.0 mm (35 mm equivalent: 28.6 - 87.4 mm) +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.190619 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: inf (4.31 m - inf) +[Composite, Composite, Image] Exif-FOV - Field Of View: 36.9 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 34.0 mm (35 mm equivalent: 54.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 4.37 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 5.6 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.067348 diff --git a/ExifTool/t/ExifTool_7.out b/ExifTool/t/ExifTool_7.out new file mode 100644 index 0000000..83f17b5 --- /dev/null +++ b/ExifTool/t/ExifTool_7.out @@ -0,0 +1,126 @@ +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:04 06:46:52 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 9 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 14.0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.5 +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 160 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 120 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: THM - DCF thumbnail file +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, InteropIFD, Image] 4097 - Related Image Width: 3072 +[EXIF, InteropIFD, Image] 4098 - Related Image Height: 2048 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3443.946188 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3442.016807 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[MakerNotes, Canon, Camera] 1 - Macro Mode: Unknown (0) +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Continuous +[MakerNotes, Canon, Camera] 7 - Focus Mode: Manual Focus (3) +[MakerNotes, Canon, Camera] 9 - Record Mode: CRW+THM +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: Unknown (-1) +[MakerNotes, Canon, Camera] 13 - Contrast: +1 +[MakerNotes, Canon, Camera] 14 - Saturation: +1 +[MakerNotes, Canon, Camera] 15 - Sharpness: +1 +[MakerNotes, Canon, Camera] 16 - Camera ISO: n/a +[MakerNotes, Canon, Camera] 17 - Metering Mode: Center-weighted average +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Manual +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 4 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 27 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 3072 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 3072 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Image] 1 - Focal Length: 34 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.22 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.49 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 100 +[MakerNotes, Canon, Image] 3 - Measured EV: -1.25 +[MakerNotes, Canon, Image] 4 - Target Aperture: 14 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 19 - Focus Distance Upper: inf +[MakerNotes, Canon, Image] 20 - Focus Distance Lower: 5.46 m +[MakerNotes, Canon, Image] 21 - F Number: 14 +[MakerNotes, Canon, Image] 22 - Exposure Time: 128 +[MakerNotes, Canon, Image] 23 - Measured EV 2: -1.25 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 4 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: CRW:EOS DIGITAL REBEL CMOS RAW +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware Version 1.1.1 +[MakerNotes, Canon, Camera] 12 - Serial Number: 0560018150 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 1 +[MakerNotes, Canon, Image] 8 - File Number: 118-1861 +[MakerNotes, Canon, Camera] 9 - Owner Name: Phil Harvey +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS Digital Rebel / 300D / Kiss Digital +[MakerNotes, Canon, Image] 14 - Canon File Length: 4480822 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 998 1022 1026 808 +[MakerNotes, Canon, Camera] 1 - WB RGGB Levels Auto: 1719 832 831 990 +[MakerNotes, Canon, Camera] 5 - WB RGGB Levels Daylight: 1722 832 831 989 +[MakerNotes, Canon, Camera] 9 - WB RGGB Levels Shade: 2035 832 831 839 +[MakerNotes, Canon, Camera] 13 - WB RGGB Levels Cloudy: 1878 832 831 903 +[MakerNotes, Canon, Camera] 17 - WB RGGB Levels Tungsten: 1228 913 912 1668 +[MakerNotes, Canon, Camera] 21 - WB RGGB Levels Fluorescent: 1506 842 841 1381 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels Flash: 1933 832 831 895 +[MakerNotes, Canon, Camera] 29 - WB RGGB Levels Custom: 1722 832 831 989 +[MakerNotes, Canon, Camera] 33 - WB RGGB Levels Kelvin: 1722 832 831 988 +[MakerNotes, Canon, Camera] 37 - WB RGGB Black Levels: 124 123 124 123 +[MakerNotes, Canon, Camera] 174 - Color Temperature: 5200 +[MakerNotes, Canon, Camera] 180 - Color Space: sRGB +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3072 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2048 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3072 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2048 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 151 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 151 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 1014 608 0 0 0 -608 -1014 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: 0 0 -506 0 506 0 0 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: (none) +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 diff --git a/ExifTool/t/ExifTool_8.out b/ExifTool/t/ExifTool_8.out new file mode 100644 index 0000000..4b9ac7c --- /dev/null +++ b/ExifTool/t/ExifTool_8.out @@ -0,0 +1,126 @@ +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:04 06:46:52 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 4 +[EXIF, ExifIFD, Image] 33437 - F Number: 14.0 +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 9 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 14.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 34.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 160 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 120 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: THM - DCF thumbnail file +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, InteropIFD, Image] 4097 - Related Image Width: 3072 +[EXIF, InteropIFD, Image] 4098 - Related Image Height: 2048 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3443.946188 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3442.016807 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[MakerNotes, Canon, Camera] 1 - Macro Mode: Unknown (0) +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Continuous +[MakerNotes, Canon, Camera] 7 - Focus Mode: Manual Focus (3) +[MakerNotes, Canon, Camera] 9 - Record Mode: CRW+THM +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: Unknown (-1) +[MakerNotes, Canon, Camera] 13 - Contrast: +1 +[MakerNotes, Canon, Camera] 14 - Saturation: +1 +[MakerNotes, Canon, Camera] 15 - Sharpness: +1 +[MakerNotes, Canon, Camera] 16 - Camera ISO: n/a +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Manual +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 4 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 27 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 3072 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 3072 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.22 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.49 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 100 +[MakerNotes, Canon, Image] 3 - Measured EV: -1.25 +[MakerNotes, Canon, Image] 4 - Target Aperture: 14 +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 19 - Focus Distance Upper: inf +[MakerNotes, Canon, Image] 20 - Focus Distance Lower: 5.46 m +[MakerNotes, Canon, Image] 23 - Measured EV 2: -1.25 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 4 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: CRW:EOS DIGITAL REBEL CMOS RAW +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware Version 1.1.1 +[MakerNotes, Canon, Camera] 12 - Serial Number: 0560018150 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 1 +[MakerNotes, Canon, Image] 8 - File Number: 118-1861 +[MakerNotes, Canon, Camera] 9 - Owner Name: Phil Harvey +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS Digital Rebel / 300D / Kiss Digital +[MakerNotes, Canon, Image] 14 - Canon File Length: 4480822 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 998 1022 1026 808 +[MakerNotes, Canon, Camera] 1 - WB RGGB Levels Auto: 1719 832 831 990 +[MakerNotes, Canon, Camera] 5 - WB RGGB Levels Daylight: 1722 832 831 989 +[MakerNotes, Canon, Camera] 9 - WB RGGB Levels Shade: 2035 832 831 839 +[MakerNotes, Canon, Camera] 13 - WB RGGB Levels Cloudy: 1878 832 831 903 +[MakerNotes, Canon, Camera] 17 - WB RGGB Levels Tungsten: 1228 913 912 1668 +[MakerNotes, Canon, Camera] 21 - WB RGGB Levels Fluorescent: 1506 842 841 1381 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels Flash: 1933 832 831 895 +[MakerNotes, Canon, Camera] 29 - WB RGGB Levels Custom: 1722 832 831 989 +[MakerNotes, Canon, Camera] 33 - WB RGGB Levels Kelvin: 1722 832 831 988 +[MakerNotes, Canon, Camera] 37 - WB RGGB Black Levels: 124 123 124 123 +[MakerNotes, Canon, Camera] 174 - Color Temperature: 5200 +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3072 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2048 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3072 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2048 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 151 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 151 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 1014 608 0 0 0 -608 -1014 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: 0 0 -506 0 506 0 0 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: (none) +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 diff --git a/ExifTool/t/ExifTool_9.out b/ExifTool/t/ExifTool_9.out new file mode 100644 index 0000000..848690e --- /dev/null +++ b/ExifTool/t/ExifTool_9.out @@ -0,0 +1,33 @@ +[MakerNotes, Canon, Image] 1 - Focal Length: 34 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.22 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.49 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 100 +[MakerNotes, Canon, Image] 3 - Measured EV: -1.25 +[MakerNotes, Canon, Image] 4 - Target Aperture: 14 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 19 - Focus Distance Upper: inf +[MakerNotes, Canon, Image] 20 - Focus Distance Lower: 5.46 m +[MakerNotes, Canon, Image] 21 - F Number: 14 +[MakerNotes, Canon, Image] 22 - Exposure Time: 128 +[MakerNotes, Canon, Image] 23 - Measured EV 2: -1.25 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 4 +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: CRW:EOS DIGITAL REBEL CMOS RAW +[MakerNotes, Canon, Image] 8 - File Number: 118-1861 +[MakerNotes, Canon, Image] 14 - Canon File Length: 4480822 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3072 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2048 diff --git a/ExifTool/t/FITS.t b/ExifTool/t/FITS.t new file mode 100644 index 0000000..8fdd19f --- /dev/null +++ b/ExifTool/t/FITS.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/FITS.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::FITS; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'FITS'; +my $testnum = 1; + +# test 2: Extract information from an FITS file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/FITS.fits'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/FITS_2.out b/ExifTool/t/FITS_2.out new file mode 100644 index 0000000..6b1ded9 --- /dev/null +++ b/ExifTool/t/FITS_2.out @@ -0,0 +1,47 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: FITS.fits +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 3.0 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:03:07 12:31:28-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:35-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: FITS +[File, File, Other] FileTypeExtension - File Type Extension: fits +[File, File, Other] MIMEType - MIME Type: image/fits +[FITS, FITS, Image] BITPIX - Bitpix: 8 +[FITS, FITS, Image] NAXIS - Naxis: 0 +[FITS, FITS, Image] EXTEND - Extend: T +[FITS, FITS, Image] COMMENT - Comment: +------------------+ +[FITS, FITS, Image] COMMENT - Comment: | HEXTE INDEX | +[FITS, FITS, Image] COMMENT - Comment: | XTE - FITS File | +[FITS, FITS, Image] COMMENT - Comment: | Generated by XFF | +[FITS, FITS, Image] COMMENT - Comment: +------------------+ +[FITS, FITS, Image] ORIGIN - Origin: XTE-SOC +[FITS, FITS, Image] CREATOR - Creator: XFF - Revision: 5.3.2 +[FITS, FITS, Time] DATE - Create Date: 28/01/97 +[FITS, FITS, Time] DATE-OBS - Observation Date: 09/12/96 +[FITS, FITS, Time] TIME-OBS - Observation Time: 11:56:26 +[FITS, FITS, Time] DATE-END - Observation Date End: 09/12/96 +[FITS, FITS, Time] TIME-END - Observation Time End: 12:01:40 +[FITS, FITS, Image] TIMESYS - Timesys: TT +[FITS, FITS, Image] MJDREFI - Mjdrefi: 49353 +[FITS, FITS, Image] MJDREFF - Mjdreff: 6.965740740000e-04 +[FITS, FITS, Image] COMMENT - Comment: MJD = JD - 2400000.5 +[FITS, FITS, Image] TIMEZERO - Timezero: 3.37843167e+00 +[FITS, FITS, Image] TIMEUNIT - Timeunit: s +[FITS, FITS, Image] TIMEREF - Timeref: LOCAL +[FITS, FITS, Image] TASSIGN - Tassign: SATELLITE +[FITS, FITS, Image] TIERRELA - Tierrela: 1.000e-06 +[FITS, FITS, Image] TIERABSO - Tierabso: 1.000e+00 +[FITS, FITS, Image] CLOCKAPP - Clockapp: T +[FITS, FITS, Image] TIMVERSN - Timversn: XFF/95-004 +[FITS, FITS, Image] OBJECT - Object: 47_TUCANAE_Slew +[FITS, FITS, Image] RA_OBJ - Ra Obj: 6.02170000e+00 +[FITS, FITS, Image] DEC_OBJ - Dec Obj: -7.20808030e+01 +[FITS, FITS, Image] EQUINOX - Equinox: 2000.0 +[FITS, FITS, Image] RADECSYS - Radecsys: FK5 +[FITS, FITS, Image] OBSERVER - Observer: FAN LEI +[FITS, FITS, Image] OBS_ID - Obs Id: 10080-01-02-00A +[FITS, FITS, Image] CHECKSUM - Checksum: mB6cn96ZmA6bm96Z +[FITS, FITS, Image] DATASUM - Datasum: 0 diff --git a/ExifTool/t/FLAC.t b/ExifTool/t/FLAC.t new file mode 100644 index 0000000..1310e0f --- /dev/null +++ b/ExifTool/t/FLAC.t @@ -0,0 +1,37 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/FLAC.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::FLAC; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'FLAC'; +my $testnum = 1; + +# test 2: Extract information from FLAC test file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/FLAC.flac'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Extract information from Ogg test file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/FLAC.ogg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/FLAC_2.out b/ExifTool/t/FLAC_2.out new file mode 100644 index 0000000..2387071 --- /dev/null +++ b/ExifTool/t/FLAC_2.out @@ -0,0 +1,27 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: FLAC.flac +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 282 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:11:13 15:12:26-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: FLAC +[File, File, Other] FileTypeExtension - File Type Extension: flac +[File, File, Other] MIMEType - MIME Type: audio/flac +[FLAC, FLAC, Audio] Bit000-015 - Block Size Min: 4608 +[FLAC, FLAC, Audio] Bit016-031 - Block Size Max: 4608 +[FLAC, FLAC, Audio] Bit032-055 - Frame Size Min: 16777215 +[FLAC, FLAC, Audio] Bit056-079 - Frame Size Max: 0 +[FLAC, FLAC, Audio] Bit080-099 - Sample Rate: 8000 +[FLAC, FLAC, Audio] Bit100-102 - Channels: 2 +[FLAC, FLAC, Audio] Bit103-107 - Bits Per Sample: 8 +[FLAC, FLAC, Audio] Bit108-143 - Total Samples: 0 +[FLAC, FLAC, Audio] Bit144-271 - MD5 Signature: d41d8cd98f00b204e9800998ecf8427e +[Vorbis, Vorbis, Audio] vendor - Vendor: reference libFLAC 1.1.2 20050205 +[Vorbis, Vorbis, Audio] REPLAYGAIN_TRACK_PEAK - Replay Gain Track Peak: 0.00000000 +[Vorbis, Vorbis, Audio] REPLAYGAIN_TRACK_GAIN - Replay Gain Track Gain: -24601.00 dB +[Vorbis, Vorbis, Audio] REPLAYGAIN_ALBUM_PEAK - Replay Gain Album Peak: 0.00000000 +[Vorbis, Vorbis, Audio] REPLAYGAIN_ALBUM_GAIN - Replay Gain Album Gain: -24601.00 dB +[Vorbis, Vorbis, Audio] TITLE - Title: ExifTool test +[Vorbis, Vorbis, Author] COPYRIGHT - Copyright: Phil Harvey diff --git a/ExifTool/t/FLAC_3.out b/ExifTool/t/FLAC_3.out new file mode 100644 index 0000000..1675e8d --- /dev/null +++ b/ExifTool/t/FLAC_3.out @@ -0,0 +1,21 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: FLAC.ogg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 151 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:11:13 16:10:54-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: OGG +[File, File, Other] FileTypeExtension - File Type Extension: ogg +[File, File, Other] MIMEType - MIME Type: audio/ogg +[FLAC, FLAC, Audio] Bit000-015 - Block Size Min: 4608 +[FLAC, FLAC, Audio] Bit016-031 - Block Size Max: 4608 +[FLAC, FLAC, Audio] Bit032-055 - Frame Size Min: 0 +[FLAC, FLAC, Audio] Bit056-079 - Frame Size Max: 0 +[FLAC, FLAC, Audio] Bit080-099 - Sample Rate: 8000 +[FLAC, FLAC, Audio] Bit100-102 - Channels: 2 +[FLAC, FLAC, Audio] Bit103-107 - Bits Per Sample: 8 +[FLAC, FLAC, Audio] Bit108-143 - Total Samples: 0 +[FLAC, FLAC, Audio] Bit144-271 - MD5 Signature: 00000000000000000000000000000000 +[Vorbis, Vorbis, Audio] vendor - Vendor: reference libFLAC 1.1.2 20050205 diff --git a/ExifTool/t/FLIF.t b/ExifTool/t/FLIF.t new file mode 100644 index 0000000..098bc38 --- /dev/null +++ b/ExifTool/t/FLIF.t @@ -0,0 +1,133 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/ZIP.t". + +BEGIN { + $| = 1; print "1..6\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::FLIF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'FLIF'; +my $testnum = 1; + +my @checkTags = qw(Artist Creator XResolution ProfileCMMType XMP); + +# test 2: Extract information from FLIF.flif +{ + ++$testnum; + my $skip = ''; + if (eval { require IO::Uncompress::RawInflate }) { + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/FLIF.flif'); + notOK() unless check($exifTool, $info, $testname, $testnum); + } else { + $skip = ' # skip Requires IO::Uncompress::RawInflate'; + } + print "ok $testnum$skip\n"; +} + +# test 3: Edit FLIF information +{ + ++$testnum; + my $skip = ''; + if (eval { require IO::Uncompress::RawInflate and require IO::Compress::RawDeflate }) { + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/XMP.jpg','ICC_Profile'); + $exifTool->SetNewValue('EXIF:XResolution' => 234); + $exifTool->SetNewValue('XMP:Creator' => 'just me'); + my $testfile = "t/${testname}_${testnum}_failed.flif"; + unlink $testfile; + $exifTool->WriteInfo('t/images/FLIF.flif', $testfile); + my $info = $exifTool->ImageInfo($testfile, @checkTags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + } else { + $skip = ' # skip Requires IO::Compress::RawDeflate'; + } + print "ok $testnum$skip\n"; +} + +# test 4: Delete FLIF information +my $testfile; +{ + ++$testnum; + my $skip = ''; + if (eval { require IO::Uncompress::RawInflate and require IO::Compress::RawDeflate }) { + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue(ICC_Profile => undef, Protected => 1); + $exifTool->SetNewValue(EXIF => undef, Protected => 1); + $exifTool->SetNewValue('XMP:all' => undef); + $testfile = "t/${testname}_${testnum}_failed.flif"; + unlink $testfile; + $exifTool->WriteInfo('t/images/FLIF.flif', $testfile); + my $info = $exifTool->ImageInfo($testfile); + unless (check($exifTool, $info, $testname, $testnum)) { + notOK(); + undef $testfile; + } + } else { + $skip = ' # skip Requires IO::Compress::RawDeflate'; + } + print "ok $testnum$skip\n"; +} + +# test 5: Add back FLIF information +{ + ++$testnum; + my $skip = ''; + if (defined $testfile) { + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/Photoshop.psd','ICC_Profile'); + $exifTool->SetNewValue('EXIF:XResolution' => 123); + $exifTool->SetNewValue('XMP:Creator' => 'me again'); + my $testfile2 = "t/${testname}_${testnum}_failed.flif"; + unlink $testfile2; + $exifTool->WriteInfo($testfile, $testfile2); + my $info = $exifTool->ImageInfo($testfile2, @checkTags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + unlink $testfile2; + } else { + notOK(); + } + } else { + $skip = ' # skip Requires test 4 pass'; + } + print "ok $testnum$skip\n"; +} + +# test 6: Delete all then add back in one step +{ + ++$testnum; + my $skip = ''; + if (eval { require IO::Uncompress::RawInflate and require IO::Compress::RawDeflate }) { + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue(all => undef); + $exifTool->SetNewValuesFromFile('t/images/Photoshop.psd','ICC_Profile'); + $exifTool->SetNewValue('EXIF:XResolution' => 456); + $exifTool->SetNewValue('XMP:Creator' => 'me too'); + $testfile = "t/${testname}_${testnum}_failed.flif"; + unlink $testfile; + $exifTool->WriteInfo('t/images/FLIF.flif', $testfile); + my $info = $exifTool->ImageInfo($testfile, @checkTags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + } else { + $skip = ' # skip Requires IO::Compress::RawDeflate'; + } + print "ok $testnum$skip\n"; +} + +done(); # end diff --git a/ExifTool/t/FLIF_2.out b/ExifTool/t/FLIF_2.out new file mode 100644 index 0000000..b970503 --- /dev/null +++ b/ExifTool/t/FLIF_2.out @@ -0,0 +1,51 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: FLIF.flif +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 674 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2016:10:27 14:56:15-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:05-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rwxr-xr-x +[File, File, Other] FileType - File Type: FLIF +[File, File, Other] FileTypeExtension - File Type Extension: flif +[File, File, Other] MIMEType - MIME Type: image/flif +[File, File, Image] 0 - Image Type: RGB (non-interlaced) +[File, File, Image] 1 - Bit Depth: 8 +[File, File, Image] 2 - Image Width: 16 +[File, File, Image] 3 - Image Height: 16 +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] 5 - Encoding: FLIF16 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 6.51 +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: Phil +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Nikon Corporation +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.2.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:12:07 18:59:22 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple Computer Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Nikon Adobe RGB 4.0.0.3000 +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.60976 0.31113 0.01947 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.20525 0.62566 0.06087 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.1492 0.06322 0.74463 +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.9505 1 1.0891 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Nikon Inc. & Nikon Corporation 2001 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 16x16 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000256 diff --git a/ExifTool/t/FLIF_3.out b/ExifTool/t/FLIF_3.out new file mode 100644 index 0000000..1e56a04 --- /dev/null +++ b/ExifTool/t/FLIF_3.out @@ -0,0 +1,5 @@ +[XMP, XMP, Image] XMP - XMP: (Binary data 418 bytes) +[XMP, XMP-dc, Author] creator - Creator: just me +[EXIF, IFD0, Image] 282 - X Resolution: 234 +[EXIF, IFD0, Author] 315 - Artist: Phil +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Linotronic diff --git a/ExifTool/t/FLIF_4.out b/ExifTool/t/FLIF_4.out new file mode 100644 index 0000000..29ada51 --- /dev/null +++ b/ExifTool/t/FLIF_4.out @@ -0,0 +1,18 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: FLIF_4_failed.flif +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 23 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:05-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:05-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:05-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: FLIF +[File, File, Other] FileTypeExtension - File Type Extension: flif +[File, File, Other] MIMEType - MIME Type: image/flif +[File, File, Image] 0 - Image Type: RGB (non-interlaced) +[File, File, Image] 1 - Bit Depth: 8 +[File, File, Image] 2 - Image Width: 16 +[File, File, Image] 3 - Image Height: 16 +[File, File, Image] 5 - Encoding: FLIF16 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 16x16 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000256 diff --git a/ExifTool/t/FLIF_5.out b/ExifTool/t/FLIF_5.out new file mode 100644 index 0000000..277759b --- /dev/null +++ b/ExifTool/t/FLIF_5.out @@ -0,0 +1,4 @@ +[EXIF, IFD0, Image] 282 - X Resolution: 123 +[XMP, XMP, Image] XMP - XMP: (Binary data 419 bytes) +[XMP, XMP-dc, Author] creator - Creator: me again +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Adobe Systems Inc. diff --git a/ExifTool/t/FLIF_6.out b/ExifTool/t/FLIF_6.out new file mode 100644 index 0000000..a4fa98f --- /dev/null +++ b/ExifTool/t/FLIF_6.out @@ -0,0 +1,4 @@ +[XMP, XMP, Image] XMP - XMP: (Binary data 417 bytes) +[XMP, XMP-dc, Author] creator - Creator: me too +[EXIF, IFD0, Image] 282 - X Resolution: 456 +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Adobe Systems Inc. diff --git a/ExifTool/t/FLIR.t b/ExifTool/t/FLIR.t new file mode 100644 index 0000000..4c3999a --- /dev/null +++ b/ExifTool/t/FLIR.t @@ -0,0 +1,31 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/FLIR.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::FLIR; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'FLIR'; +my $testnum = 1; + +# test 2: Extract information from FLIR JPG and FPF files +{ + my $file; + foreach $file ('FLIR.jpg', 'FLIR.fpf') { + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo("t/images/$file"); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +done(); # end diff --git a/ExifTool/t/FLIR_2.out b/ExifTool/t/FLIR_2.out new file mode 100644 index 0000000..197cd0c --- /dev/null +++ b/ExifTool/t/FLIR_2.out @@ -0,0 +1,121 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: FLIR.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 7.3 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2013:04:02 13:11:16-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:36-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[JFIF, JFIF, Image] 0 - JFIF Version: 1.01 +[JFIF, JFIF, Image] 2 - Resolution Unit: None +[JFIF, JFIF, Image] 3 - X Resolution: 1 +[JFIF, JFIF, Image] 5 - Y Resolution: 1 +[EXIF, IFD0, Camera] 271 - Make: FLIR Systems AB +[EXIF, IFD0, Camera] 272 - Camera Model Name: FLIR_i7 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Common_dll v8.0.6b1 +[EXIF, IFD0, Time] 306 - Modify Date: 2012:02:11 14:17:08 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/32 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2012:02:11 14:17:08 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2012:02:11 14:17:08 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: -, Cr, Cb, Y +[EXIF, ExifIFD, Camera] 37382 - Subject Distance: 1 m +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.7 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 240 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 240 +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 1 +[EXIF, ExifIFD, Image] 42016 - Image Unique ID: 72AE2CD63D6C4AE1678418BE48230029 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 872 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 2314 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 2314 bytes) +[MakerNotes, FLIR, Camera] 1 - Image Temperature Max: 308 +[MakerNotes, FLIR, Camera] 2 - Image Temperature Min: 281 +[MakerNotes, FLIR, Camera] 3 - Emissivity: 0.80 +[APP1, FLIR, Image] 4 - Creator Software: +[APP1, FLIR, Image] 0 - Palette Colors: 224 +[APP1, FLIR, Image] 6 - Above Color: 170 128 128 +[APP1, FLIR, Image] 9 - Below Color: 50 128 128 +[APP1, FLIR, Image] 12 - Overflow Color: 67 216 98 +[APP1, FLIR, Image] 15 - Underflow Color: 41 110 240 +[APP1, FLIR, Image] 18 - Isotherm 1 Color: 100 128 128 +[APP1, FLIR, Image] 21 - Isotherm 2 Color: 100 110 240 +[APP1, FLIR, Image] 26 - Palette Method: 0 +[APP1, FLIR, Image] 27 - Palette Stretch: 2 +[APP1, FLIR, Image] 48 - Palette File Name: \FlashFS\system\iron.pal +[APP1, FLIR, Image] 80 - Palette Name: Iron +[APP1, FLIR, Image] 112 - Palette: (Binary data 672 bytes) +[APP1, FLIR, Camera] 32 - Emissivity: 0.80 +[APP1, FLIR, Camera] 36 - Object Distance: 1.00 m +[APP1, FLIR, Camera] 40 - Reflected Apparent Temperature: 20.0 C +[APP1, FLIR, Camera] 44 - Atmospheric Temperature: 20.0 C +[APP1, FLIR, Camera] 48 - IR Window Temperature: 20.0 C +[APP1, FLIR, Camera] 52 - IR Window Transmission: 1.00 +[APP1, FLIR, Camera] 60 - Relative Humidity: 50.0 % +[APP1, FLIR, Camera] 88 - Planck R1: 13799.269 +[APP1, FLIR, Camera] 92 - Planck B: 1374.5 +[APP1, FLIR, Camera] 96 - Planck F: 1.35 +[APP1, FLIR, Camera] 112 - Atmospheric Trans Alpha 1: 0.006569 +[APP1, FLIR, Camera] 116 - Atmospheric Trans Alpha 2: 0.012620 +[APP1, FLIR, Camera] 120 - Atmospheric Trans Beta 1: -0.002276 +[APP1, FLIR, Camera] 124 - Atmospheric Trans Beta 2: -0.006670 +[APP1, FLIR, Camera] 128 - Atmospheric Trans X: 1.900000 +[APP1, FLIR, Camera] 144 - Camera Temperature Range Max: 250.0 C +[APP1, FLIR, Camera] 148 - Camera Temperature Range Min: -0.0 C +[APP1, FLIR, Camera] 152 - Camera Temperature Max Clip: 270.0 C +[APP1, FLIR, Camera] 156 - Camera Temperature Min Clip: -40.0 C +[APP1, FLIR, Camera] 160 - Camera Temperature Max Warn: 250.0 C +[APP1, FLIR, Camera] 164 - Camera Temperature Min Warn: -0.0 C +[APP1, FLIR, Camera] 168 - Camera Temperature Max Saturated: 270.0 C +[APP1, FLIR, Camera] 172 - Camera Temperature Min Saturated: -60.0 C +[APP1, FLIR, Camera] 212 - Camera Model: FLIR_i7 +[APP1, FLIR, Camera] 244 - Camera Part Number: T197600 +[APP1, FLIR, Camera] 260 - Camera Serial Number: 470023842 +[APP1, FLIR, Camera] 276 - Camera Software: 8.1.1 +[APP1, FLIR, Camera] 368 - Lens Model: FOL7 +[APP1, FLIR, Camera] 400 - Lens Part Number: +[APP1, FLIR, Camera] 416 - Lens Serial Number: +[APP1, FLIR, Camera] 436 - Field Of View: 25.0 deg +[APP1, FLIR, Camera] 492 - Filter Model: +[APP1, FLIR, Camera] 508 - Filter Part Number: +[APP1, FLIR, Camera] 540 - Filter Serial Number: +[APP1, FLIR, Camera] 776 - Planck O: -6646 +[APP1, FLIR, Camera] 780 - Planck R2: 0.022241818 +[APP1, FLIR, Image] 784 - Raw Value Range Min: 7630 +[APP1, FLIR, Image] 786 - Raw Value Range Max: 61986 +[APP1, FLIR, Image] 824 - Raw Value Median: 12582 +[APP1, FLIR, Image] 828 - Raw Value Range: 1980 +[APP1, FLIR, Time] 900 - Date/Time Original: 2012:02:11 14:17:08.253+01:00 +[APP1, FLIR, Camera] 912 - Focus Step Count: 0 +[APP1, FLIR, Camera] 1116 - Focus Distance: 2.0 m +[APP1, FLIR, Camera] 1124 - Frame Rate: 9 +[APP1, FLIR, Image] 1 - Raw Thermal Image Width: 120 +[APP1, FLIR, Image] 2 - Raw Thermal Image Height: 120 +[APP1, FLIR, Image] 16 - Raw Thermal Image Type: PNG +[APP1, FLIR, Preview] 16.1 - Raw Thermal Image: (Binary data 79 bytes) +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/32 +[Composite, FLIR, Camera] FLIR-PeakSpectralSensitivity - Peak Spectral Sensitivity: 10.5 um +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.7 mm diff --git a/ExifTool/t/FLIR_3.out b/ExifTool/t/FLIR_3.out new file mode 100644 index 0000000..9f1aea1 --- /dev/null +++ b/ExifTool/t/FLIR_3.out @@ -0,0 +1,49 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: FLIR.fpf +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 914 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2013:04:11 15:17:20-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:05-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: FPF +[File, File, Other] FileTypeExtension - File Type Extension: fpf +[File, File, Other] MIMEType - MIME Type: image/x-flir-fpf +[FLIR, FLIR, Image] 32 - FPF Version: 2 +[FLIR, FLIR, Image] 36 - Image Data Offset: 892 +[FLIR, FLIR, Image] 40 - Image Type: Temperature +[FLIR, FLIR, Image] 42 - Image Pixel Format: 4-byte float +[FLIR, FLIR, Image] 44 - Image Width: 160 +[FLIR, FLIR, Image] 46 - Image Height: 120 +[FLIR, FLIR, Image] 48 - External Trigger Count: 0 +[FLIR, FLIR, Image] 52 - Sequence Frame Number: 1 +[FLIR, FLIR, Camera] 120 - Camera Model: FLIR E40 +[FLIR, FLIR, Camera] 152 - Camera Part Number: 49001-2001 +[FLIR, FLIR, Camera] 184 - Camera Serial Number: 49033333 +[FLIR, FLIR, Camera] 216 - Camera Temperature Range Min: -20.0 C +[FLIR, FLIR, Camera] 220 - Camera Temperature Range Max: 120.0 C +[FLIR, FLIR, Camera] 224 - Lens Model: FOL18 +[FLIR, FLIR, Camera] 256 - Lens Part Number: +[FLIR, FLIR, Camera] 288 - Lens Serial Number: +[FLIR, FLIR, Camera] 320 - Filter Model: +[FLIR, FLIR, Camera] 336 - Filter Part Number: +[FLIR, FLIR, Camera] 384 - Filter Serial Number: +[FLIR, FLIR, Image] 480 - Emissivity: 0.95 +[FLIR, FLIR, Image] 484 - Object Distance: 1.00 m +[FLIR, FLIR, Image] 488 - Reflected Apparent Temperature: 20.0 C +[FLIR, FLIR, Image] 492 - Atmospheric Temperature: 20.0 C +[FLIR, FLIR, Image] 496 - Relative Humidity: 50.0 % +[FLIR, FLIR, Image] 500 - Computed Atmospheric Trans: 0.99 +[FLIR, FLIR, Image] 504 - Estimated Atmospheric Trans: 0.00 +[FLIR, FLIR, Image] 508 - Reference Temperature: 20.0 C +[FLIR, FLIR, Camera] 512 - IR Window Temperature: 20.0 C +[FLIR, FLIR, Camera] 516 - IR Window Transmission: 1.00 +[FLIR, FLIR, Time] 584 - Date/Time Original: 2013:02:22 11:19:20.891 +[FLIR, FLIR, Image] 676 - Camera Scale Min: 267.3 +[FLIR, FLIR, Image] 680 - Camera Scale Max: 277.1 +[FLIR, FLIR, Image] 684 - Calculated Scale Min: 260.0 +[FLIR, FLIR, Image] 688 - Calculated Scale Max: 279.6 +[FLIR, FLIR, Image] 692 - Actual Scale Min: 267.3 +[FLIR, FLIR, Image] 696 - Actual Scale Max: 277.1 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 160x120 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.019 diff --git a/ExifTool/t/Flash.t b/ExifTool/t/Flash.t new file mode 100644 index 0000000..37ecf97 --- /dev/null +++ b/ExifTool/t/Flash.t @@ -0,0 +1,37 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Flash.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Flash; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Flash'; +my $testnum = 1; + +# test 2: Extract information from Flash.swf +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Flash.swf'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Extract information from Flash.flv +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Flash.flv'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/FlashPix.t b/ExifTool/t/FlashPix.t new file mode 100644 index 0000000..2f3b1ca --- /dev/null +++ b/ExifTool/t/FlashPix.t @@ -0,0 +1,30 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/FlashPix.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::FlashPix; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'FlashPix'; +my $testnum = 1; + +# test 2: Extract information from FlashPix.ppt +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + # omit FileSize since file is 9.5kB exactly and rounding errors + # in Windows Cygwin Perl 5.8.2 round this down to 9kB in printout + my $info = $exifTool->ImageInfo('t/images/FlashPix.ppt','-filesize'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/FlashPix_2.out b/ExifTool/t/FlashPix_2.out new file mode 100644 index 0000000..98adb51 --- /dev/null +++ b/ExifTool/t/FlashPix_2.out @@ -0,0 +1,49 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.78 +[File, System, Other] FileName - File Name: FlashPix.ppt +[File, System, Other] Directory - Directory: t/images +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 10:59:23-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:12:04 10:59:39-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:12:04 10:59:23-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PPT +[File, File, Other] FileTypeExtension - File Type Extension: ppt +[File, File, Other] MIMEType - MIME Type: application/vnd.ms-powerpoint +[FlashPix, FlashPix, Other] 1 - Code Page: Mac Roman (Western European) +[FlashPix, FlashPix, Document] 2 - Title: title +[FlashPix, FlashPix, Document] 3 - Subject: subject +[FlashPix, FlashPix, Author] 4 - Author: author +[FlashPix, FlashPix, Document] 5 - Keywords: keywords +[FlashPix, FlashPix, Document] 6 - Comments: comments +[FlashPix, FlashPix, Author] 8 - Last Modified By: user name +[FlashPix, FlashPix, Document] 9 - Revision Number: 1 +[FlashPix, FlashPix, Document] 18 - Software: Microsoft PowerPoint +[FlashPix, FlashPix, Document] 10 - Total Edit Time: 4.4 minutes +[FlashPix, FlashPix, Time] 12 - Create Date: 2007:02:09 16:23:23 +[FlashPix, FlashPix, Time] 13 - Modify Date: 2007:02:09 16:27:49 +[FlashPix, FlashPix, Document] 15 - Words: 4 +[FlashPix, FlashPix, Other] 1 - Code Page: Mac Roman (Western European) +[FlashPix, FlashPix, Document] 2 - Category: category +[FlashPix, FlashPix, Document] 3 - Presentation Target: On-screen Show +[FlashPix, FlashPix, Document] 14 - Manager: manager +[FlashPix, FlashPix, Document] 15 - Company: company +[FlashPix, FlashPix, Document] 4 - Bytes: 4610 +[FlashPix, FlashPix, Document] 6 - Paragraphs: 4 +[FlashPix, FlashPix, Document] 7 - Slides: 1 +[FlashPix, FlashPix, Document] 8 - Notes: 0 +[FlashPix, FlashPix, Document] 9 - Hidden Slides: 0 +[FlashPix, FlashPix, Document] 10 - MM Clips: 0 +[FlashPix, FlashPix, Document] 23 - App Version: 10.2418 +[FlashPix, FlashPix, Document] 11 - Scale Crop: No +[FlashPix, FlashPix, Document] 16 - Links Up To Date: No +[FlashPix, FlashPix, Document] 19 - Shared Doc: No +[FlashPix, FlashPix, Document] 22 - Hyperlinks Changed: No +[FlashPix, FlashPix, Document] 13 - Title Of Parts: Times, Blank Presentation, Title +[FlashPix, FlashPix, Document] 12 - Heading Pairs: Fonts Used, 1, Design Template, 1, Slide Titles, 1 +[FlashPix, FlashPix, Other] 1 - Code Page: Mac Roman (Western European) +[FlashPix, FlashPix, Document] _PID_LINKBASE - Hyperlink Base: hyperlink base +[FlashPix, FlashPix, Document] _PID_HLINKS - Hyperlinks: https://exiftool.org/ , http://www.microsoft.com/mac/#test, mailto:phil?subject=subject +[FlashPix, FlashPix, Document] Custom Text - Custom Text: customtext +[FlashPix, FlashPix, Document] Custom Number - Custom Number: 42 +[FlashPix, FlashPix, Document] Custom Date - Custom Date: 2007:01:09 05:00:00 +[FlashPix, FlashPix, Document] Custom Boolean - Custom Boolean: 1 +[FlashPix, FlashPix, Image] Current User - Current User: user name diff --git a/ExifTool/t/Flash_2.out b/ExifTool/t/Flash_2.out new file mode 100644 index 0000000..8b66130 --- /dev/null +++ b/ExifTool/t/Flash_2.out @@ -0,0 +1,22 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Flash.swf +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 384 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2008:10:23 16:27:45-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:05-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: SWF +[File, File, Other] FileTypeExtension - File Type Extension: swf +[File, File, Other] MIMEType - MIME Type: application/x-shockwave-flash +[Flash, Flash, Video] FlashVersion - Flash Version: 6 +[Flash, Flash, Video] Compressed - Compressed: False +[Flash, Flash, Video] ImageWidth - Image Width: 50 +[Flash, Flash, Video] ImageHeight - Image Height: 50 +[Flash, Flash, Video] FrameRate - Frame Rate: 12 +[Flash, Flash, Video] FrameCount - Frame Count: 1 +[Flash, Flash, Video] Duration - Duration: 0.08 s +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 7.50 +[XMP, XMP-pdf, Author] Author - Author: Phil +[Composite, Composite, Image] Exif-ImageSize - Image Size: 50x50 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.003 diff --git a/ExifTool/t/Flash_3.out b/ExifTool/t/Flash_3.out new file mode 100644 index 0000000..376aa45 --- /dev/null +++ b/ExifTool/t/Flash_3.out @@ -0,0 +1,55 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Flash.flv +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1358 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2007:06:12 12:15:04-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:05-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: FLV +[File, File, Other] FileTypeExtension - File Type Extension: flv +[File, File, Other] MIMEType - MIME Type: video/x-flv +[Flash, Flash, Video] hasKeyframes - Has Key Frames: Yes +[Flash, Flash, Video] CuePoint0Name - Cue Point 0 Name: Cue1 +[Flash, Flash, Video] CuePoint0Time - Cue Point 0 Time: 0 +[Flash, Flash, Video] CuePoint0Type - Cue Point 0 Type: event +[Flash, Flash, Video] CuePoint1Name - Cue Point 1 Name: Cue2 +[Flash, Flash, Video] CuePoint1Time - Cue Point 1 Time: 0.324 +[Flash, Flash, Video] CuePoint1ParameterParam1 - Cue Point 1 Parameter Param 1: value1 +[Flash, Flash, Video] CuePoint1ParameterParam2 - Cue Point 1 Parameter Param 2: value2 +[Flash, Flash, Video] CuePoint1Type - Cue Point 1 Type: navigation +[Flash, Flash, Audio] audiodatarate - Audio Bitrate: 65.8 kbps +[Flash, Flash, Video] hasVideo - Has Video: Yes +[Flash, Flash, Audio] stereo - Stereo: No +[Flash, Flash, Video] canSeekToEnd - Can Seek To End: Yes +[Flash, Flash, Video] framerate - Frame Rate: 20 +[Flash, Flash, Audio] audiosamplerate - Audio Sample Rate: 44000 +[Flash, Flash, Video] videocodecid - Video Codec ID: 4 +[Flash, Flash, Video] datasize - Data Size: 187002 +[Flash, Flash, Video] lasttimestamp - Last Time Stamp: 3.039 +[Flash, Flash, Audio] audiosamplesize - Audio Sample Size: 16 +[Flash, Flash, Audio] audiosize - Audio Size: 26299 +[Flash, Flash, Audio] hasAudio - Has Audio: Yes +[Flash, Flash, Audio] audiodelay - Audio Delay: 0.039 +[Flash, Flash, Video] videosize - Video Size: 159544 +[Flash, Flash, Time] metadatadate - Metadata Date: 2007:06:12 15:03:11.528553-04:00 +[Flash, Flash, Video] metadatacreator - Metadata Creator: inlet media FLVTool2 v1.0.6 - http://www.inlet-media.de/flvtool2 +[Flash, Flash, Video] test - Test: this +[Flash, Flash, Video] lastkeyframetimestamp - Last Key Frame Time: 3.039 +[Flash, Flash, Video] height - Image Height: 240 +[Flash, Flash, Video] filesize - File Size Bytes: 187691 +[Flash, Flash, Video] hasMetadata - Has Metadata: Yes +[Flash, Flash, Video] keyframesTimes - Key Frames Times: 0.039, 0.363, 1.839, 3.039 +[Flash, Flash, Video] keyframesFilepositions - Key Frame Positions: 1463, 32674, 143320, 181650 +[Flash, Flash, Audio] audiocodecid - Audio Codec ID: 2 +[Flash, Flash, Video] videodatarate - Video Bitrate: 419 kbps +[Flash, Flash, Video] duration - Duration: 3.09 s +[Flash, Flash, Video] hasCuePoints - Has Cue Points: Yes +[Flash, Flash, Video] width - Image Width: 320 +[Flash, Flash, Audio] Bit0-3 - Audio Encoding: MP3 +[Flash, Flash, Audio] Bit4-5 - Audio Sample Rate: 11025 +[Flash, Flash, Audio] Bit6 - Audio Bits Per Sample: 16 +[Flash, Flash, Audio] Bit7 - Audio Channels: 1 (mono) +[Flash, Flash, Video] Bit4-7 - Video Encoding: On2 VP6 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 320x240 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.077 diff --git a/ExifTool/t/Font.t b/ExifTool/t/Font.t new file mode 100644 index 0000000..eb57cf8 --- /dev/null +++ b/ExifTool/t/Font.t @@ -0,0 +1,31 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Font.t". + +BEGIN { + $| = 1; print "1..7\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Font; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Font'; +my $testnum = 1; + +# tests 2-7: Extract information from test Font files +{ + my $exifTool = Image::ExifTool->new; + my $type; + foreach $type (qw(afm dfont pfa pfb pfm ttf)) { + ++$testnum; + my $info = $exifTool->ImageInfo("t/images/Font.$type"); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +done(); # end diff --git a/ExifTool/t/Font_2.out b/ExifTool/t/Font_2.out new file mode 100644 index 0000000..f0de6b5 --- /dev/null +++ b/ExifTool/t/Font_2.out @@ -0,0 +1,24 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Font.afm +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 735 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:01:18 14:13:19-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: AFM +[File, File, Other] FileTypeExtension - File Type Extension: afm +[File, File, Other] MIMEType - MIME Type: application/x-font-afm +[File, File, Image] Comment - Comment: Generated by pfaedit +[Font, Font, Time] Creation Date - Create Date: Sat Sep 4 16:12:41 2004 +[Font, Font, Document] FontName - Font Name: NimbusSanL-ReguCondItal +[Font, Font, Document] FullName - Full Name: Nimbus Sans L Condensed Regular Italic +[Font, Font, Document] FamilyName - Font Family: Nimbus Sans L Condensed +[Font, Font, Document] Weight - Weight: Regular +[Font, Font, Author] Notice - Notice: Copyright (URW)++,Copyright 1999 by (URW)++ Design & Development; Cyrillic glyphs added by Valek Filippov (C) 2001-2004 +[Font, Font, Document] Version - Version: 1.06 +[Font, Font, Document] EncodingScheme - Encoding Scheme: AdobeStandardEncoding +[Font, Font, Document] CapHeight - Cap Height: 718 +[Font, Font, Document] XHeight - X Height: 523 +[Font, Font, Document] Ascender - Ascender: 718 +[Font, Font, Document] Descender - Descender: -207 diff --git a/ExifTool/t/Font_3.out b/ExifTool/t/Font_3.out new file mode 100644 index 0000000..5b82e1b --- /dev/null +++ b/ExifTool/t/Font_3.out @@ -0,0 +1,36 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Font.dfont +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1744 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:01:18 19:08:47-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: DFONT +[File, File, Other] FileTypeExtension - File Type Extension: dfont +[File, File, Other] MIMEType - MIME Type: application/x-dfont +[Font, Font, Author] 0 - Copyright: © 1987, 1990, 1994, 2001, 2002 Adobe Systems Incorporated. All rights reserved. +[Font, Font, Document] 1 - Font Family: Stencil Std +[Font, Font, Document] 2 - Font Subfamily: Bold +[Font, Font, Document] 3 - Font Subfamily ID: 2.025;ADBE;StencilStd +[Font, Font, Document] 4 - Font Name: Stencil Std Bold +[Font, Font, Document] 5 - Name Table Version: Version 2.025;PS 002.000;hotconv 1.0.50;makeotf.lib2.0.16970 +[Font, Font, Document] 6 - PostScript Font Name: StencilStd +[Font, Font, Document] 7 - Trademark: Please refer to the Copyright section for the font trademark attribution notices. +[Font, Font, Document] 9 - Designer: Gerry Powell +[Font, Font, Document] 11 - Vendor URL: http://www.adobe.com/type +[Font, Font, Document] 14 - License Info URL: http://www.adobe.com/type/legal.html +[Font, Font, Document] 18 - Compatible Font Name: Stencil Std +[Font, Font, Author] 0-en-US - Copyright (en-US): © 1987, 1990, 1994, 2001, 2002 Adobe Systems Incorporated. All rights reserved. +[Font, Font, Document] 1-en-US - Font Family (en-US): Stencil Std +[Font, Font, Document] 2-en-US - Font Subfamily (en-US): Regular +[Font, Font, Document] 3-en-US - Font Subfamily ID (en-US): 2.025;ADBE;StencilStd +[Font, Font, Document] 4-en-US - Font Name (en-US): StencilStd +[Font, Font, Document] 5-en-US - Name Table Version (en-US): Version 2.025;PS 002.000;hotconv 1.0.50;makeotf.lib2.0.16970 +[Font, Font, Document] 6-en-US - Post Script Font Name (en-US): StencilStd +[Font, Font, Document] 7-en-US - Trademark (en-US): Please refer to the Copyright section for the font trademark attribution notices. +[Font, Font, Document] 9-en-US - Designer (en-US): Gerry Powell +[Font, Font, Document] 11-en-US - Vendor URL (en-US): http://www.adobe.com/type +[Font, Font, Document] 14-en-US - License Info URL (en-US): http://www.adobe.com/type/legal.html +[Font, Font, Document] 17-en-US - Preferred Subfamily (en-US): Bold +[RSRC, RSRC, Document] vers_0x0001 - Application Version: ExifTool 8.0.7 DFONT Test diff --git a/ExifTool/t/Font_4.out b/ExifTool/t/Font_4.out new file mode 100644 index 0000000..60a5ea7 --- /dev/null +++ b/ExifTool/t/Font_4.out @@ -0,0 +1,27 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Font.pfa +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1559 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:01:18 14:10:30-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PFA +[File, File, Other] FileTypeExtension - File Type Extension: pfa +[File, File, Other] MIMEType - MIME Type: application/x-font-type1 +[File, File, Image] Comment - Comment: 1;% 2004-2-16: Created.;Generated by FontForge 20040531 (http://fontforge.sf.net/) +[PostScript, PostScript, Image] Title - Title: YehudaCLM-Bold +[PostScript, PostScript, Time] CreationDate - Create Date: Sat Jun 12 19:16:03 2004 +[PostScript, PostScript, Image] Creator - Creator: Maxim Iorsh +[Font, Font, Document] FontType - Font Type: 1 +[Font, Font, Document] FontName - Font Name: YehudaCLM-Bold +[Font, Font, Document] version - Version: 0.100 +[Font, Font, Author] Notice - Notice: Copyright .51 2004 by Maxim Iorsh (iorsh@math.technion.ac.il). Distributed under the terms of GNU General Public License version 2(http://www.gnu.org/licenses/gpl.html). ;All rights reserved. +[Font, Font, Document] FullName - Full Name: Yehuda CLM Bold +[Font, Font, Document] FamilyName - Font Family: Yehuda CLM +[Font, Font, Document] Weight - Weight: Bold +[Font, Font, Document] FSType - FS Type: 0 +[Font, Font, Document] ItalicAngle - Italic Angle: 0 +[Font, Font, Document] isFixedPitch - Is Fixed Pitch: false +[Font, Font, Document] UnderlinePosition - Underline Position: -100 +[Font, Font, Document] UnderlineThickness - Underline Thickness: 50 diff --git a/ExifTool/t/Font_5.out b/ExifTool/t/Font_5.out new file mode 100644 index 0000000..a628cbd --- /dev/null +++ b/ExifTool/t/Font_5.out @@ -0,0 +1,27 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Font.pfb +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1423 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:01:18 14:10:30-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PFB +[File, File, Other] FileTypeExtension - File Type Extension: pfb +[File, File, Other] MIMEType - MIME Type: application/x-font-type1 +[File, File, Image] Comment - Comment: 1;Copyright (URW)++,Copyright 1999 by (URW)++ Design & Development; Cyri;Generated by FontForge 20040824 (http://fontforge.sf.net/) +[PostScript, PostScript, Image] Title - Title: NimbusSanL-ReguCondItal +[PostScript, PostScript, Time] CreationDate - Create Date: Sat Sep 4 16:12:41 2004 +[PostScript, PostScript, Image] Creator - Creator: frob +[Font, Font, Document] FontType - Font Type: 1 +[Font, Font, Document] FontName - Font Name: NimbusSanL-ReguCondItal +[Font, Font, Document] version - Version: 1.06 +[Font, Font, Author] Notice - Notice: Copyright (URW)++,Copyright 1999 by (URW)++ Design & Development; Cyrillic glyphs added by Valek Filippov (C) 2001-2004 +[Font, Font, Document] FullName - Full Name: Nimbus Sans L Condensed Regular Italic +[Font, Font, Document] FamilyName - Font Family: Nimbus Sans L Condensed +[Font, Font, Document] Weight - Weight: Regular +[Font, Font, Document] FSType - FS Type: 12 +[Font, Font, Document] ItalicAngle - Italic Angle: -9.9 +[Font, Font, Document] isFixedPitch - Is Fixed Pitch: false +[Font, Font, Document] UnderlinePosition - Underline Position: -100 +[Font, Font, Document] UnderlineThickness - Underline Thickness: 50 diff --git a/ExifTool/t/Font_6.out b/ExifTool/t/Font_6.out new file mode 100644 index 0000000..60631fc --- /dev/null +++ b/ExifTool/t/Font_6.out @@ -0,0 +1,37 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Font.pfm +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 240 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:01:18 14:10:30-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PFM +[File, File, Other] FileTypeExtension - File Type Extension: pfm +[File, File, Other] MIMEType - MIME Type: application/x-font-type1 +[Font, Font, Document] 0 - PFM Version: 1.00 +[Font, Font, Author] 6 - Copyright: Copyright URW Software, Copyright 1992 by URW +[Font, Font, Document] 66 - Font Type: 129 +[Font, Font, Document] 68 - Point Size: 10 +[Font, Font, Document] 70 - Y Resolution: 300 +[Font, Font, Document] 72 - X Resolution: 300 +[Font, Font, Document] 74 - Ascent: 700 +[Font, Font, Document] 76 - Internal Leading: 0 +[Font, Font, Document] 78 - External Leading: 0 +[Font, Font, Document] 80 - Italic: 0 +[Font, Font, Document] 81 - Underline: 0 +[Font, Font, Document] 82 - Strikeout: 0 +[Font, Font, Document] 83 - Weight: 600 +[Font, Font, Document] 85 - Character Set: 0 +[Font, Font, Document] 86 - Pix Width: 0 +[Font, Font, Document] 88 - Pix Height: 0 +[Font, Font, Document] 90 - Pitch And Family: 1 +[Font, Font, Document] 91 - Avg Width: 578 +[Font, Font, Document] 93 - Max Width: 1092 +[Font, Font, Document] 95 - First Char: 32 +[Font, Font, Document] 96 - Last Char: 255 +[Font, Font, Document] 97 - Default Char: 32 +[Font, Font, Document] 98 - Break Char: 0 +[Font, Font, Document] 99 - Width Bytes: 0 +[Font, Font, Document] fontname - Font Name: URWGroT +[Font, Font, Document] postfont - PostScript Font Name: URWGroteskT-Bold diff --git a/ExifTool/t/Font_7.out b/ExifTool/t/Font_7.out new file mode 100644 index 0000000..2c9b431 --- /dev/null +++ b/ExifTool/t/Font_7.out @@ -0,0 +1,37 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Font.ttf +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1252 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:01:21 09:37:50-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: TTF +[File, File, Other] FileTypeExtension - File Type Extension: ttf +[File, File, Other] MIMEType - MIME Type: application/x-font-ttf +[Font, Font, Author] 0 - Copyright: © Apple Computer, Inc. 1991-1995 +[Font, Font, Document] 1 - Font Family: Raanana +[Font, Font, Document] 2 - Font Subfamily: Regular +[Font, Font, Document] 3 - Font Subfamily ID: Raanana; 5.0d2e1; Wed, May 26, 2004 +[Font, Font, Document] 4 - Font Name: Raanana +[Font, Font, Document] 5 - Name Table Version: 5.0d2e1 +[Font, Font, Document] 6 - PostScript Font Name: Raanana +[Font, Font, Document] 8 - Manufacturer: Apple Computer, Inc. +[Font, Font, Document] 2-fr - Font Subfamily (fr): Régulier +[Font, Font, Document] 2-de - Font Subfamily (de): Normal +[Font, Font, Document] 2-it - Font Subfamily (it): Regolare +[Font, Font, Document] 2-nl-NL - Font Subfamily (nl-NL): Regelmatig +[Font, Font, Document] 2-sv - Font Subfamily (sv): Normalt +[Font, Font, Document] 2-es - Font Subfamily (es): Normal +[Font, Font, Document] 2-da - Font Subfamily (da): Almindelig +[Font, Font, Document] 2-pt - Font Subfamily (pt): Normal +[Font, Font, Document] 2-no - Font Subfamily (no): Vanlig +[Font, Font, Document] 2-fi - Font Subfamily (fi): Normaali +[Font, Font, Document] 2-ja - Font Subfamily (ja): レギュラー +[Font, Font, Document] 2-zh-TW - Font Subfamily (zh-TW): 標準體 +[Font, Font, Document] 2-ko - Font Subfamily (ko): ì¼ë°˜ +[Font, Font, Author] 0-he - Copyright (he): (c) Apple Computer, Inc. 1991-1995 +[Font, Font, Document] 1-he - Font Family (he): רעננה +[Font, Font, Document] 2-he - Font Subfamily (he): Regular +[Font, Font, Document] 4-he - Font Name (he): רעננה +[Font, Font, Document] 2-zh-CN - Font Subfamily (zh-CN): 常规 diff --git a/ExifTool/t/FotoStation.t b/ExifTool/t/FotoStation.t new file mode 100644 index 0000000..c19f49d --- /dev/null +++ b/ExifTool/t/FotoStation.t @@ -0,0 +1,39 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/FotoStation.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::FotoStation; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'FotoStation'; +my $testnum = 1; + +# test 2: Extract information from FotoStation.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/FotoStation.jpg', {Duplicates => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Test writing some information +{ + ++$testnum; + my @writeInfo = ( + ['Rotation' => 0 ], + ['Keywords' => 'FotoStation' ], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, undef, 1); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/FotoStation_2.out b/ExifTool/t/FotoStation_2.out new file mode 100644 index 0000000..5f09c9b --- /dev/null +++ b/ExifTool/t/FotoStation_2.out @@ -0,0 +1,41 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: FotoStation.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 4.3 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:10:28 14:44:59-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:36-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[FotoStation, FotoStation, Image] 0 - Original Image Width: 1536 +[FotoStation, FotoStation, Image] 1 - Original Image Height: 1024 +[FotoStation, FotoStation, Image] 2 - Color Planes: 3 +[FotoStation, FotoStation, Image] 3 - XY Resolution: 38.626 +[FotoStation, FotoStation, Image] 4 - Rotation: 90 +[FotoStation, FotoStation, Image] 6 - Crop Left: 18.422% +[FotoStation, FotoStation, Image] 7 - Crop Top: 24.458% +[FotoStation, FotoStation, Image] 8 - Crop Right: 83.035% +[FotoStation, FotoStation, Image] 9 - Crop Bottom: 77.817% +[FotoStation, FotoStation, Image] 11 - Crop Rotation: 0 +[IPTC, IPTC2, Other] 0 - Application Record Version: 2 +[IPTC, IPTC2, Other] 7 - Edit Status: Edit Status +[IPTC, IPTC2, Other] 10 - Urgency: 1 (most urgent) +[IPTC, IPTC2, Other] 15 - Category: Cat +[IPTC, IPTC2, Other] 120 - Caption-Abstract: Caption *** Local Caption *** Local Caption +[IPTC, IPTC2, Other] 40 - Special Instructions: Special Instructions +[IPTC, IPTC2, Other] 75 - Object Cycle: Unknown (Afternoon) +[IPTC, IPTC2, Other] 103 - Original Transmission Reference: OTR +[IPTC, IPTC2, Image] 200 - Object Preview File Format: Unknown (Custom Field 01) +[IPTC, IPTC2, Image] 201 - Object Preview File Version: Custom Field 02 +[IPTC, IPTC2, Preview] 202 - Object Preview Data: (Binary data 15 bytes) +[IPTC, IPTC2, Other] 230 - Document Notes: Document Notes +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/FotoStation_3.out b/ExifTool/t/FotoStation_3.out new file mode 100644 index 0000000..7c1552b --- /dev/null +++ b/ExifTool/t/FotoStation_3.out @@ -0,0 +1,3 @@ +[IPTC, IPTC, Other] 25 - Keywords: FotoStation +[IPTC, IPTC2, Other] 25 - Keywords: FotoStation +[FotoStation, FotoStation, Image] 4 - Rotation: 0 diff --git a/ExifTool/t/FujiFilm.t b/ExifTool/t/FujiFilm.t new file mode 100644 index 0000000..3114875 --- /dev/null +++ b/ExifTool/t/FujiFilm.t @@ -0,0 +1,78 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/FujiFilm.t". + +BEGIN { + $| = 1; print "1..6\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::FujiFilm; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'FujiFilm'; +my $testnum = 1; + +# test 2: Extract information from FujiFilm.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/FujiFilm.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + ['CreateDate','2005:01:06 11:51:09'], + ['WhiteBalance', 'day white', 'Group', 'MakerNotes'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: Extract information from FujiFilm.raf +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my @tags = qw(-filename -directory -filemodifydate -fileaccessdate + -filecreatedate -fileinodechangedate -filepermissions); + my $info = $exifTool->ImageInfo('t/images/FujiFilm.raf', @tags, {Duplicates=>1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# tests 5-6: Write writing a RAF and changing it back again in memory +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + # set IgnoreMinorErrors option to allow invalid JpgFromRaw to be written + $exifTool->SetNewValue(UserComment => 'test comment'); + my $testfile = "t/${testname}_${testnum}_failed.raf"; + unlink $testfile; + $exifTool->WriteInfo('t/images/FujiFilm.raf', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'UserComment'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + + ++$testnum; + my $outfile; + # pad out comment to make image the same size as the original + $exifTool->SetNewValue(UserComment => ' ' x 248); + $exifTool->WriteInfo($testfile, \$outfile); + $info = $exifTool->ImageInfo(\$outfile, {Duplicates=>1}); + if (check($exifTool, $info, $testname, $testnum, 4)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + + +done(); # end diff --git a/ExifTool/t/FujiFilm_2.out b/ExifTool/t/FujiFilm_2.out new file mode 100644 index 0000000..b8278a7 --- /dev/null +++ b/ExifTool/t/FujiFilm_2.out @@ -0,0 +1,89 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: FujiFilm.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1373 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:06-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Modify Date: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1600 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, FujiFilm, Camera] 0 - Version: 0130 +[MakerNotes, FujiFilm, Camera] 4096 - Quality: NORMAL +[MakerNotes, FujiFilm, Camera] 4097 - Sharpness: 0 (normal) +[MakerNotes, FujiFilm, Camera] 4098 - White Balance: Auto +[MakerNotes, FujiFilm, Camera] 4112 - Fuji Flash Mode: Red-eye reduction +[MakerNotes, FujiFilm, Camera] 4113 - Flash Exposure Comp: 0 +[MakerNotes, FujiFilm, Camera] 4128 - Macro: Off +[MakerNotes, FujiFilm, Camera] 4129 - Focus Mode: Auto +[MakerNotes, FujiFilm, Camera] 4144 - Slow Sync: Off +[MakerNotes, FujiFilm, Camera] 4145 - Picture Mode: Auto +[MakerNotes, FujiFilm, Camera] 4352 - Auto Bracketing: Off +[MakerNotes, FujiFilm, Camera] 4864 - Blur Warning: None +[MakerNotes, FujiFilm, Camera] 4865 - Focus Warning: Good +[MakerNotes, FujiFilm, Camera] 4866 - Exposure Warning: Good +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 6.6 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.005 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 48.9 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.0 mm (35 mm equivalent: 39.6 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 2.26 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.6 diff --git a/ExifTool/t/FujiFilm_3.out b/ExifTool/t/FujiFilm_3.out new file mode 100644 index 0000000..91f6745 --- /dev/null +++ b/ExifTool/t/FujiFilm_3.out @@ -0,0 +1,90 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: FujiFilm_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 1373 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:06-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:06-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:06-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Modify Date: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:01:06 11:51:09 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1600 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, FujiFilm, Camera] 0 - Version: 0130 +[MakerNotes, FujiFilm, Camera] 4096 - Quality: NORMAL +[MakerNotes, FujiFilm, Camera] 4097 - Sharpness: 0 (normal) +[MakerNotes, FujiFilm, Camera] 4098 - White Balance: Day White Fluorescent +[MakerNotes, FujiFilm, Camera] 4112 - Fuji Flash Mode: Red-eye reduction +[MakerNotes, FujiFilm, Camera] 4113 - Flash Exposure Comp: 0 +[MakerNotes, FujiFilm, Camera] 4128 - Macro: Off +[MakerNotes, FujiFilm, Camera] 4129 - Focus Mode: Auto +[MakerNotes, FujiFilm, Camera] 4144 - Slow Sync: Off +[MakerNotes, FujiFilm, Camera] 4145 - Picture Mode: Auto +[MakerNotes, FujiFilm, Camera] 4352 - Auto Bracketing: Off +[MakerNotes, FujiFilm, Camera] 4608 - Fuji Film 0x1200: 0 +[MakerNotes, FujiFilm, Camera] 4864 - Blur Warning: None +[MakerNotes, FujiFilm, Camera] 4865 - Focus Warning: Good +[MakerNotes, FujiFilm, Camera] 4866 - Exposure Warning: Good +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.6 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 6.6 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.005 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 48.9 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.0 mm (35 mm equivalent: 39.6 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 2.26 m diff --git a/ExifTool/t/FujiFilm_4.out b/ExifTool/t/FujiFilm_4.out new file mode 100644 index 0000000..0415892 --- /dev/null +++ b/ExifTool/t/FujiFilm_4.out @@ -0,0 +1,147 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileSize - File Size: 38 kB +[File, File, Other] FileType - File Type: RAF +[File, File, Other] FileTypeExtension - File Type Extension: raf +[File, File, Other] MIMEType - MIME Type: image/x-fujifilm-raf +[File, File, Image] RAFVersion - RAF Version: 0106 +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[File, File, Preview] PreviewImage - Preview Image: (Binary data 12167 bytes) +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix S5Pro +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Digital Camera FinePix S5Pro Ver1.06 +[EXIF, IFD0, Time] 306 - Modify Date: 2007:05:22 13:58:30 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/250 +[EXIF, ExifIFD, Image] 33437 - F Number: 8.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Manual +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2007:05:22 13:58:30 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2007:05:22 13:58:30 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/256 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 8.0 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 8.41 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 70.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1440 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 960 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 630 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 630 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 105 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Unknown +[EXIF, GPS, Location] 0 - GPS Version ID: 2.2.0.0 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 2704 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 9362 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 9362 bytes) +[MakerNotes, FujiFilm, Camera] 0 - Version: 0130 +[MakerNotes, FujiFilm, Camera] 16 - Internal Serial Number: FPX20582698 Y-1146 2007:02:19 8C0020100A84 +[MakerNotes, FujiFilm, Camera] 4096 - Quality: NORMAL +[MakerNotes, FujiFilm, Camera] 4097 - Sharpness: 0 (normal) +[MakerNotes, FujiFilm, Camera] 4098 - White Balance: Auto +[MakerNotes, FujiFilm, Camera] 4099 - Saturation: 0 (normal) +[MakerNotes, FujiFilm, Camera] 4100 - Contrast: Normal +[MakerNotes, FujiFilm, Camera] 4106 - White Balance Fine Tune: Red +0, Blue +0 +[MakerNotes, FujiFilm, Camera] 4107 - Noise Reduction: Normal +[MakerNotes, FujiFilm, Camera] 4112 - Fuji Flash Mode: Off +[MakerNotes, FujiFilm, Camera] 4113 - Flash Exposure Comp: 0 +[MakerNotes, FujiFilm, Camera] 4129 - Focus Mode: Auto +[MakerNotes, FujiFilm, Camera] 4130 - AF Mode: Single Point +[MakerNotes, FujiFilm, Camera] 4144 - Slow Sync: Off +[MakerNotes, FujiFilm, Camera] 4145 - Picture Mode: Manual +[MakerNotes, FujiFilm, Camera] 4146 - Exposure Count: 1 +[MakerNotes, FujiFilm, Camera] 4352 - Auto Bracketing: Off +[MakerNotes, FujiFilm, Camera] 4353 - Sequence Number: 0 +[MakerNotes, FujiFilm, Camera] 5120 - Dynamic Range: Wide +[MakerNotes, FujiFilm, Camera] 5121 - Film Mode: F0/Standard (Provia) +[MakerNotes, FujiFilm, Camera] 5122 - Dynamic Range Setting: Auto +[MakerNotes, FujiFilm, Camera] 5124 - Min Focal Length: 28 +[MakerNotes, FujiFilm, Camera] 5125 - Max Focal Length: 70 +[MakerNotes, FujiFilm, Camera] 5126 - Max Aperture At Min Focal: 2.8 +[MakerNotes, FujiFilm, Camera] 5127 - Max Aperture At Max Focal: 2.8 +[MakerNotes, FujiFilm, Camera] 16640 - Faces Detected: 0 +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[RAF, RAF, Image] 256 - Raw Image Full Size: 4352x1444 +[RAF, RAF, Image] 272 - Raw Image Crop Top Left: 10 48 +[RAF, RAF, Image] 273 - Raw Image Cropped Size: 4256x1424 +[RAF, RAF, Image] 289 - Raw Image Size: 4288x1440 +[RAF, RAF, Image] 304 - Fuji Layout: 1 2 1 0 +[RAF, RAF, Image] 8192 - WB GRGB Levels Auto: 384 601 384 515 +[RAF, RAF, Image] 8448 - WB GRGB Levels Daylight: 384 601 384 515 +[RAF, RAF, Image] 8704 - WB GRGB Levels Cloudy: 384 679 384 438 +[RAF, RAF, Image] 8960 - WB GRGB Levels Daylight Fluor: 384 775 384 460 +[RAF, RAF, Image] 8961 - WB GRGB Levels Day White Fluor: 384 648 384 573 +[RAF, RAF, Image] 8962 - WB GRGB Levels White Fluorescent: 384 644 384 711 +[RAF, RAF, Image] 8976 - WB GRGB Levels Warm White Fluor: 384 509 384 878 +[RAF, RAF, Image] 8977 - WB GRGB Levels Living Room Warm White Fluor: 384 419 384 920 +[RAF, RAF, Image] 9216 - WB GRGB Levels Tungsten: 384 384 384 832 +[RAF, RAF, Image] 12272 - WB GRGB Levels: 384 601 384 515 +[RAF, RAF, Image] 37376 - Relative Exposure: 0 +[RAF, RAF, Image] 38480 - Raw Exposure Bias: 0 +[RAF, RAF2, Image] 256 - Raw Image Full Size: 4352x1444 +[RAF, RAF2, Image] 272 - Raw Image Crop Top Left: 10 48 +[RAF, RAF2, Image] 273 - Raw Image Cropped Size: 4256x1424 +[RAF, RAF2, Image] 289 - Raw Image Size: 4284x1440 +[RAF, RAF2, Image] 304 - Fuji Layout: 1 2 1 0 +[RAF, RAF2, Image] 8192 - WB GRGB Levels Auto: 384 685 384 603 +[RAF, RAF2, Image] 8448 - WB GRGB Levels Daylight: 384 685 384 603 +[RAF, RAF2, Image] 8704 - WB GRGB Levels Cloudy: 384 774 384 513 +[RAF, RAF2, Image] 8960 - WB GRGB Levels Daylight Fluor: 384 883 384 539 +[RAF, RAF2, Image] 8961 - WB GRGB Levels Day White Fluor: 384 739 384 671 +[RAF, RAF2, Image] 8962 - WB GRGB Levels White Fluorescent: 384 734 384 833 +[RAF, RAF2, Image] 8976 - WB GRGB Levels Warm White Fluor: 384 580 384 1028 +[RAF, RAF2, Image] 8977 - WB GRGB Levels Living Room Warm White Fluor: 384 477 384 1078 +[RAF, RAF2, Image] 9216 - WB GRGB Levels Tungsten: 384 438 384 975 +[RAF, RAF2, Image] 12272 - WB GRGB Levels: 384 685 384 603 +[RAF, RAF2, Image] 37376 - Relative Exposure: -4.0 +[RAF, RAF2, Image] 38480 - Raw Exposure Bias: 0 +[Composite, Composite, Image] Exif-Aperture - Aperture: 8.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/250 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.341146 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 4256x1424 +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.0 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 6.1 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.565104 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.5 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.020 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 19.5 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 70.0 mm (35 mm equivalent: 105.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 30.58 m diff --git a/ExifTool/t/FujiFilm_5.out b/ExifTool/t/FujiFilm_5.out new file mode 100644 index 0000000..6abb011 --- /dev/null +++ b/ExifTool/t/FujiFilm_5.out @@ -0,0 +1 @@ +[EXIF, ExifIFD, Image] 37510 - User Comment: test comment diff --git a/ExifTool/t/GE.t b/ExifTool/t/GE.t new file mode 100644 index 0000000..eb0cbca --- /dev/null +++ b/ExifTool/t/GE.t @@ -0,0 +1,46 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/GE.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::GE; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'GE'; +my $testnum = 1; + +# test 2: Extract information from GE.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Unknown => 1); + my $info = $exifTool->ImageInfo('t/images/GE.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue(GEModel => 'test model'); + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->WriteInfo('t/images/GE.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile,{Duplicates=>1,Unknown=>1}); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/GE_2.out b/ExifTool/t/GE_2.out new file mode 100644 index 0000000..2e9a3be --- /dev/null +++ b/ExifTool/t/GE_2.out @@ -0,0 +1,86 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[ExifTool, ExifTool, ExifTool] Warning - Warning: [minor] Suspicious MakerNotes offset for GE_0x0200 +[ExifTool, ExifTool, ExifTool] Warning - Warning: [minor] Suspicious MakerNotes offset for GE_0x0206 +[File, System, Other] FileName - File Name: GE.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1467 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:12:14 19:15:30-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:06-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[JFIF, JFIF, Image] 0 - JFIF Version: 1.01 +[JFIF, JFIF, Image] 2 - Resolution Unit: inches +[JFIF, JFIF, Image] 3 - X Resolution: 96 +[JFIF, JFIF, Image] 5 - Y Resolution: 96 +[EXIF, IFD0, Image] 270 - Image Description: GE DIGITAL CAMERA +[EXIF, IFD0, Camera] 271 - Make: General Imaging Co. +[EXIF, IFD0, Camera] 272 - Camera Model Name: E1035 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 96 +[EXIF, IFD0, Image] 283 - Y Resolution: 96 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: GIMP 2.6.3 +[EXIF, IFD0, Time] 306 - Modify Date: 2009:07:05 23:58:52 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/77 +[EXIF, ExifIFD, Image] 33437 - F Number: 5.1 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 64 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2008:09:17 04:18:57 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2008:09:17 04:18:57 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/77 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 5.1 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 5.1 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Auto, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 22.5 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: R: 139 G: 255 B: 189 X:39852 Y: 0 S: 0 Zs: 6 Zp: 115 F: 531 I: 1 ImgVer:08.01.09.10 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 0 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 0 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 1 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 0.01041666667 +[EXIF, IFD1, Image] 283 - Y Resolution: 0.01041666667 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1190 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, GE, Camera] 260 - GE 0x0104: 1694568960 +[MakerNotes, GE, Camera] 514 - Macro: Off +[MakerNotes, GE, Camera] 515 - GE 0x0203: 0 +[MakerNotes, GE, Camera] 516 - GE 0x0204: 1 +[MakerNotes, GE, Camera] 517 - GE 0x0205: 9.47 +[MakerNotes, GE, Camera] 519 - GE Model: E1035 +[MakerNotes, GE, Camera] 768 - GE Make: GE DIGITAL CAMERA +[MakerNotes, GE, Camera] 1280 - GE 0x0500: 0 +[MakerNotes, GE, Camera] 1536 - GE 0x0600: 0 +[Composite, Composite, Image] Exif-Aperture - Aperture: 5.1 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/77 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 22.5 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 11.6 diff --git a/ExifTool/t/GE_3.out b/ExifTool/t/GE_3.out new file mode 100644 index 0000000..86644c9 --- /dev/null +++ b/ExifTool/t/GE_3.out @@ -0,0 +1,86 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: GE_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 1483 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:07-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:07-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:07-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[JFIF, JFIF, Image] 0 - JFIF Version: 1.01 +[JFIF, JFIF, Image] 2 - Resolution Unit: inches +[JFIF, JFIF, Image] 3 - X Resolution: 96 +[JFIF, JFIF, Image] 5 - Y Resolution: 96 +[EXIF, IFD0, Image] 270 - Image Description: GE DIGITAL CAMERA +[EXIF, IFD0, Camera] 271 - Make: General Imaging Co. +[EXIF, IFD0, Camera] 272 - Camera Model Name: E1035 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 96 +[EXIF, IFD0, Image] 283 - Y Resolution: 96 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: GIMP 2.6.3 +[EXIF, IFD0, Time] 306 - Modify Date: 2009:07:05 23:58:52 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/77 +[EXIF, ExifIFD, Image] 33437 - F Number: 5.1 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 64 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2008:09:17 04:18:57 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2008:09:17 04:18:57 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/77 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 5.1 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 5.1 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Auto, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 22.5 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: R: 139 G: 255 B: 189 X:39852 Y: 0 S: 0 Zs: 6 Zp: 115 F: 531 I: 1 ImgVer:08.01.09.10 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 0 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 0 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 1 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 0.01041666667 +[EXIF, IFD1, Image] 283 - Y Resolution: 0.01041666667 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1206 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, GE, Camera] 260 - GE 0x0104: 1694568960 +[MakerNotes, GE, Camera] 512 - GE 0x0200: 673605 0 16777216 +[MakerNotes, GE, Camera] 514 - Macro: Off +[MakerNotes, GE, Camera] 515 - GE 0x0203: 0 +[MakerNotes, GE, Camera] 516 - GE 0x0204: 1 +[MakerNotes, GE, Camera] 517 - GE 0x0205: 9.47 +[MakerNotes, GE, Camera] 518 - GE 0x0206: 10 18245 0 0 256 0 +[MakerNotes, GE, Camera] 519 - GE Model: test model +[MakerNotes, GE, Camera] 768 - GE Make: GE DIGITAL CAMERA +[MakerNotes, GE, Camera] 1280 - GE 0x0500: 0 +[MakerNotes, GE, Camera] 1536 - GE 0x0600: 0 +[Composite, Composite, Image] Exif-Aperture - Aperture: 5.1 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/77 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 22.5 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 11.6 diff --git a/ExifTool/t/GIF.t b/ExifTool/t/GIF.t new file mode 100644 index 0000000..b4fcc00 --- /dev/null +++ b/ExifTool/t/GIF.t @@ -0,0 +1,84 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/GIF.t". + +BEGIN { + $| = 1; print "1..5\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::GIF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'GIF'; +my $testnum = 1; + +# test 2: GIF file using data in memory +{ + ++$testnum; + open(TESTFILE, 't/images/GIF.gif'); + binmode(TESTFILE); + my $gifImage; + read(TESTFILE, $gifImage, 100000); + close(TESTFILE); + my $info = ImageInfo(\$gifImage); + notOK() unless check($info, $testname, $testnum); + print "ok $testnum\n"; +} + +# tests 3-5: Test adding/editing/deleting various types of metadata for GIF images in memory +{ + ++$testnum; + open(TESTFILE, 't/images/GIF.gif'); + binmode(TESTFILE); + my $gifImage; + read(TESTFILE, $gifImage, 100000); + close(TESTFILE); + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue(Comment => 'a new comment'); + $exifTool->SetNewValue(City => 'Kingston'); + my $image1; + $exifTool->WriteInfo(\$gifImage, \$image1); + $info = ImageInfo(\$image1); + notOK() unless check($info, $testname, $testnum); + print "ok $testnum\n"; + + ++$testnum; + $exifTool->SetNewValue(); # clear previous new values + $exifTool->SetNewValue('all'); # delete everything... + # add back some XMP tags + $exifTool->SetNewValue(Comment => 'x'); + $exifTool->SetNewValue(Subject => ['one','two','three']); + $exifTool->SetNewValue(Country => 'Canada'); + my $image2; + $exifTool->WriteInfo(\$image1, \$image2); + $info = ImageInfo(\$image2); + notOK() unless check($info, $testname, $testnum); + print "ok $testnum\n"; + + ++$testnum; + $info = ImageInfo(\$gifImage, 'Comment', 'XMP', 'ICC_Profile'); + $exifTool->SetNewValue(); # clear previous new values + $exifTool->SetNewValue(Comment => $$info{Comment}); + $exifTool->SetNewValue(XMP => $$info{XMP}, Protected => 1); + $exifTool->SetNewValue(ICC_Profile => $$info{ICC_Profile}, Protected => 1); + my $image3; + $exifTool->WriteInfo(\$image2, \$image3); + my $testfile = "t/${testname}_${testnum}_failed.gif"; + if ($image3 eq $gifImage) { + unlink $testfile; + } else { + # save the bad image + open(TESTFILE,">$testfile"); + binmode(TESTFILE); + print TESTFILE $image3; + close(TESTFILE); + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/GIF_2.out b/ExifTool/t/GIF_2.out new file mode 100644 index 0000000..17ed71a --- /dev/null +++ b/ExifTool/t/GIF_2.out @@ -0,0 +1,43 @@ +BackgroundColor: 0 +BitsPerPixel: 8 +BlueMatrixColumn: 0.1492 0.06322 0.74463 +BlueTRC: (Binary data 14 bytes) +CMMFlags: Not Embedded, Independent +ColorResolutionDepth: 8 +ColorSpaceData: RGB +Comment: SCANNERMAKER: Canon;;SCANNER: Canon EOS DIGITAL REBEL;;SOFTWARE: GraphicConverter;;DATE: 2004:02:20 08:07:49;;DESCRIPTION: The picture caption;;;;Ignored Tags: $02BC, $9000, $9004, $9101, $9286, $A000, $A001, $A002, $A003, $A20E, $A20F, $A210, $A217, $A300, $A401, $A402, $A403, $A406;; +ConnectionSpaceIlluminant: 0.9642 1 0.82491 +DeviceAttributes: Reflective, Glossy, Positive, Color +DeviceManufacturer: none +DeviceModel: +ExifToolVersion: 12.42 +FileSize: 2.3 kB +FileType: GIF +FileTypeExtension: gif +GIFVersion: 89a +GreenMatrixColumn: 0.20525 0.62566 0.06087 +GreenTRC: (Binary data 14 bytes) +HasColorMap: Yes +ImageHeight: 8 +ImageSize: 8x8 +ImageWidth: 8 +MIMEType: image/gif +MediaWhitePoint: 0.9505 1 1.0891 +Megapixels: 0.000064 +PixelAspectRatio: 1 +PrimaryPlatform: Apple Computer Inc. +ProfileCMMType: Nikon Corporation +ProfileClass: Display Device Profile +ProfileConnectionSpace: XYZ +ProfileCopyright: Nikon Inc. & Nikon Corporation 2001 +ProfileCreator: +ProfileDateTime: 1999:12:07 18:59:22 +ProfileDescription: Nikon Adobe RGB 4.0.0.3000 +ProfileFileSignature: acsp +ProfileID: 0 +ProfileVersion: 2.2.0 +RedMatrixColumn: 0.60976 0.31113 0.01947 +RedTRC: (Binary data 14 bytes) +RenderingIntent: Perceptual +Title: GIF with XMP +XMPToolkit: Image::ExifTool 7.27 diff --git a/ExifTool/t/GIF_3.out b/ExifTool/t/GIF_3.out new file mode 100644 index 0000000..278496f --- /dev/null +++ b/ExifTool/t/GIF_3.out @@ -0,0 +1,44 @@ +BackgroundColor: 0 +BitsPerPixel: 8 +BlueMatrixColumn: 0.1492 0.06322 0.74463 +BlueTRC: (Binary data 14 bytes) +CMMFlags: Not Embedded, Independent +City: Kingston +ColorResolutionDepth: 8 +ColorSpaceData: RGB +Comment: a new comment +ConnectionSpaceIlluminant: 0.9642 1 0.82491 +DeviceAttributes: Reflective, Glossy, Positive, Color +DeviceManufacturer: none +DeviceModel: +ExifToolVersion: 12.42 +FileSize: 4.6 kB +FileType: GIF +FileTypeExtension: gif +GIFVersion: 89a +GreenMatrixColumn: 0.20525 0.62566 0.06087 +GreenTRC: (Binary data 14 bytes) +HasColorMap: Yes +ImageHeight: 8 +ImageSize: 8x8 +ImageWidth: 8 +MIMEType: image/gif +MediaWhitePoint: 0.9505 1 1.0891 +Megapixels: 0.000064 +PixelAspectRatio: 1 +PrimaryPlatform: Apple Computer Inc. +ProfileCMMType: Nikon Corporation +ProfileClass: Display Device Profile +ProfileConnectionSpace: XYZ +ProfileCopyright: Nikon Inc. & Nikon Corporation 2001 +ProfileCreator: +ProfileDateTime: 1999:12:07 18:59:22 +ProfileDescription: Nikon Adobe RGB 4.0.0.3000 +ProfileFileSignature: acsp +ProfileID: 0 +ProfileVersion: 2.2.0 +RedMatrixColumn: 0.60976 0.31113 0.01947 +RedTRC: (Binary data 14 bytes) +RenderingIntent: Perceptual +Title: GIF with XMP +XMPToolkit: Image::ExifTool 12.42 diff --git a/ExifTool/t/GIF_4.out b/ExifTool/t/GIF_4.out new file mode 100644 index 0000000..34166af --- /dev/null +++ b/ExifTool/t/GIF_4.out @@ -0,0 +1,19 @@ +BackgroundColor: 0 +BitsPerPixel: 8 +ColorResolutionDepth: 8 +Comment: x +Country: Canada +ExifToolVersion: 12.42 +FileSize: 4.1 kB +FileType: GIF +FileTypeExtension: gif +GIFVersion: 89a +HasColorMap: Yes +ImageHeight: 8 +ImageSize: 8x8 +ImageWidth: 8 +MIMEType: image/gif +Megapixels: 0.000064 +PixelAspectRatio: 1 +Subject: one, two, three +XMPToolkit: Image::ExifTool 12.42 diff --git a/ExifTool/t/GIMP.t b/ExifTool/t/GIMP.t new file mode 100644 index 0000000..f7c4561 --- /dev/null +++ b/ExifTool/t/GIMP.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/GIMP.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::GIMP; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'GIMP'; +my $testnum = 1; + +# test 2: Extract information from GIMP.xcf +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/GIMP.xcf'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/GIMP_2.out b/ExifTool/t/GIMP_2.out new file mode 100644 index 0000000..6883aa1 --- /dev/null +++ b/ExifTool/t/GIMP_2.out @@ -0,0 +1,67 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: GIMP.xcf +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:38-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: XCF +[File, File, Other] FileTypeExtension - File Type Extension: xcf +[File, File, Other] MIMEType - MIME Type: image/x-xcf +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[GIMP, GIMP, Image] 9 - XCF Version: 0 +[GIMP, GIMP, Image] 14 - Image Width: 8 +[GIMP, GIMP, Image] 18 - Image Height: 8 +[GIMP, GIMP, Image] 22 - Color Mode: RGB Color +[GIMP, GIMP, Image] 17 - Compression: RLE Encoding +[GIMP, GIMP, Image] 0 - X Resolution: 72 +[GIMP, GIMP, Image] 1 - Y Resolution: 72 +[GIMP, GIMP, Image] 20 - Tattoo: 2 +[GIMP, GIMP, Image] 22 - Units: Inches +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: ExifTool XCF test +[XMP, XMP-dc, Author] rights - Rights: Free for all +[XMP, XMP-dc, Image] title - Title: Test Picture +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[EXIF, IFD0, Image] 270 - Image Description: This is a teeny weeny white square +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 08:27:14 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Author] 33432 - Copyright: Free for all +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 8 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 8 +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Nikon Corporation +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.2.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:12:07 18:59:22 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple Computer Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Nikon Adobe RGB 4.0.0.3000 +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.60976 0.31113 0.01947 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.20525 0.62566 0.06087 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.1492 0.06322 0.74463 +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.9505 1 1.0891 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Nikon Inc. & Nikon Corporation 2001 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/GPS.t b/ExifTool/t/GPS.t new file mode 100644 index 0000000..022b2f2 --- /dev/null +++ b/ExifTool/t/GPS.t @@ -0,0 +1,46 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/GPS.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::GPS; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'GPS'; +my $testnum = 1; + +# test 2: Extract information from GPS.jpg with specified coordinate format +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(CoordFormat => '%d degrees %.2f minutes'); + my $info = $exifTool->ImageInfo('t/images/GPS.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + if (eval { require Time::Local }) { + my @writeInfo = ( + ['GPSLatitude' => "12 deg 21' 23.345"], + ['GPSLatitudeRef' => 'south' ], + ['GPSTimeStamp' => '2007:03:02 18:46:10.55-05:30' ], + ['GPSDateStamp' => '2007:03:02 18:46:10.55-05:30' ], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; + } else { + print "ok $testnum # skip Requires Time::Local\n"; + } +} + +done(); # end diff --git a/ExifTool/t/GPS_2.out b/ExifTool/t/GPS_2.out new file mode 100644 index 0000000..478dea1 --- /dev/null +++ b/ExifTool/t/GPS_2.out @@ -0,0 +1,96 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: GPS.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.1 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2008:10:14 11:39:46-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:38-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 09f7f522cf163e96cf778a81de1a9c2b +[File, File, Image] ImageWidth - Image Width: 120 +[File, File, Image] ImageHeight - Image Height: 80 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: Communications +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePixS1Pro +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2002:07:19 13:28:10 +[EXIF, IFD0, Author] 315 - Artist: Ian Britton +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Image] 532 - Reference Black White: 0 255 128 255 128 255 +[EXIF, IFD0, Author] 33432 - Copyright: ian Britton - FreeFoto.com +[EXIF, ExifIFD, Image] 33437 - F Number: 0.64 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Shutter speed priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 0 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0200 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2002:07:13 15:58:28 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2002:07:13 15:58:28 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/724 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 16.0 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 0.26015625 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: -0.65 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 0.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2400 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1600 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 12.05078125 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 12.05078125 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Unknown (0) +[EXIF, ExifIFD, Image] 41729 - Scene Type: Unknown (0) +[EXIF, GPS, Location] 0 - GPS Version ID: 2.0.0.0 +[EXIF, GPS, Location] 1 - GPS Latitude Ref: North +[EXIF, GPS, Location] 2 - GPS Latitude: 54 degrees 59.38 minutes +[EXIF, GPS, Location] 3 - GPS Longitude Ref: West +[EXIF, GPS, Location] 4 - GPS Longitude: 1 degrees 54.85 minutes +[EXIF, GPS, Time] 7 - GPS Time Stamp: 14:58:24 +[EXIF, GPS, Location] 18 - GPS Map Datum: WGS84 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1050 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: Communications +[IPTC, IPTC, Author] 122 - Writer-Editor: Ian Britton +[IPTC, IPTC, Other] 105 - Headline: Communications +[IPTC, IPTC, Author] 80 - By-line: Ian Britton +[IPTC, IPTC, Author] 85 - By-line Title: Photographer +[IPTC, IPTC, Author] 110 - Credit: Ian Britton +[IPTC, IPTC, Author] 115 - Source: FreeFoto.com +[IPTC, IPTC, Other] 5 - Object Name: Communications +[IPTC, IPTC, Time] 55 - Date Created: 2002:06:20 +[IPTC, IPTC, Location] 90 - City: +[IPTC, IPTC, Location] 95 - Province-State: +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: United Kingdom +[IPTC, IPTC, Other] 15 - Category: BUS +[IPTC, IPTC, Other] 20 - Supplemental Categories: Communications +[IPTC, IPTC, Other] 10 - Urgency: 5 (normal urgency) +[IPTC, IPTC, Other] 25 - Keywords: Communications +[IPTC, IPTC, Author] 116 - Copyright Notice: ian Britton - FreeFoto.com +[Composite, Composite, Image] Exif-Aperture - Aperture: 0.64 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 120x80 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.010 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/724 +[Composite, Composite, Location] GPS-GPSLatitude - GPS Latitude: 54 degrees 59.38 minutes N +[Composite, Composite, Location] GPS-GPSLongitude - GPS Longitude: 1 degrees 54.85 minutes W +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 0.0 mm +[Composite, Composite, Location] Exif-GPSPosition - GPS Position: 54 degrees 59.38 minutes N, 1 degrees 54.85 minutes W diff --git a/ExifTool/t/GPS_3.out b/ExifTool/t/GPS_3.out new file mode 100644 index 0000000..a13e83a --- /dev/null +++ b/ExifTool/t/GPS_3.out @@ -0,0 +1,98 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: GPS_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 2.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:38-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:38-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:38-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 09f7f522cf163e96cf778a81de1a9c2b +[File, File, Image] ImageWidth - Image Width: 120 +[File, File, Image] ImageHeight - Image Height: 80 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: Communications +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePixS1Pro +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2002:07:19 13:28:10 +[EXIF, IFD0, Author] 315 - Artist: Ian Britton +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Image] 532 - Reference Black White: 0 255 128 255 128 255 +[EXIF, IFD0, Author] 33432 - Copyright: ian Britton - FreeFoto.com +[EXIF, ExifIFD, Image] 33437 - F Number: 0.64 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Shutter speed priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 0 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0200 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2002:07:13 15:58:28 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2002:07:13 15:58:28 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/724 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 16.0 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 0.26015625 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: -0.65 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 0.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2400 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1600 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 12.05078125 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 12.05078125 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Unknown (0) +[EXIF, ExifIFD, Image] 41729 - Scene Type: Unknown (0) +[EXIF, GPS, Location] 0 - GPS Version ID: 2.0.0.0 +[EXIF, GPS, Location] 1 - GPS Latitude Ref: South +[EXIF, GPS, Location] 2 - GPS Latitude: 12 deg 21' 23.34" +[EXIF, GPS, Location] 3 - GPS Longitude Ref: West +[EXIF, GPS, Location] 4 - GPS Longitude: 1 deg 54' 51.00" +[EXIF, GPS, Time] 7 - GPS Time Stamp: 00:16:10.55 +[EXIF, GPS, Location] 18 - GPS Map Datum: WGS84 +[EXIF, GPS, Time] 29 - GPS Date Stamp: 2007:03:03 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1074 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: Communications +[IPTC, IPTC, Author] 122 - Writer-Editor: Ian Britton +[IPTC, IPTC, Other] 105 - Headline: Communications +[IPTC, IPTC, Author] 80 - By-line: Ian Britton +[IPTC, IPTC, Author] 85 - By-line Title: Photographer +[IPTC, IPTC, Author] 110 - Credit: Ian Britton +[IPTC, IPTC, Author] 115 - Source: FreeFoto.com +[IPTC, IPTC, Other] 5 - Object Name: Communications +[IPTC, IPTC, Time] 55 - Date Created: 2002:06:20 +[IPTC, IPTC, Location] 90 - City: +[IPTC, IPTC, Location] 95 - Province-State: +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: United Kingdom +[IPTC, IPTC, Other] 15 - Category: BUS +[IPTC, IPTC, Other] 20 - Supplemental Categories: Communications +[IPTC, IPTC, Other] 10 - Urgency: 5 (normal urgency) +[IPTC, IPTC, Other] 25 - Keywords: Communications +[IPTC, IPTC, Author] 116 - Copyright Notice: ian Britton - FreeFoto.com +[Composite, Composite, Image] Exif-Aperture - Aperture: 0.64 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 120x80 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.010 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/724 +[Composite, Composite, Time] GPS-GPSDateTime - GPS Date/Time: 2007:03:03 00:16:10.55Z +[Composite, Composite, Location] GPS-GPSLatitude - GPS Latitude: 12 deg 21' 23.34" S +[Composite, Composite, Location] GPS-GPSLongitude - GPS Longitude: 1 deg 54' 51.00" W +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 0.0 mm +[Composite, Composite, Location] Exif-GPSPosition - GPS Position: 12 deg 21' 23.34" S, 1 deg 54' 51.00" W diff --git a/ExifTool/t/GeoTiff.t b/ExifTool/t/GeoTiff.t new file mode 100644 index 0000000..ac7ed7f --- /dev/null +++ b/ExifTool/t/GeoTiff.t @@ -0,0 +1,53 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/GeoTiff.t". + +BEGIN { + $| = 1; print "1..4\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::GeoTiff; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'GeoTiff'; +my $testnum = 1; + +# test 2: Extract information from GeoTiff.tif +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/GeoTiff.tif'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = (['ResolutionUnit','cm']); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/GeoTiff.tif'); + print "ok $testnum\n"; +} + +# test 4: Copy GeoTiff information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.out"; + unlink $testfile; + $exifTool->SetNewValuesFromFile('t/images/GeoTiff.tif', 'GeoTiff*'); + my $ok = writeInfo($exifTool,'t/images/ExifTool.tif',$testfile); + my $info = $exifTool->ImageInfo($testfile, 'GeoTiff:*'); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/GeoTiff_2.out b/ExifTool/t/GeoTiff_2.out new file mode 100644 index 0000000..eb19e3f --- /dev/null +++ b/ExifTool/t/GeoTiff_2.out @@ -0,0 +1,38 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: GeoTiff.tif +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.7 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2005:11:14 14:47:21-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:37-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: TIFF +[File, File, Other] FileTypeExtension - File Type Extension: tif +[File, File, Other] MIMEType - MIME Type: image/tiff +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[EXIF, IFD0, Image] 256 - Image Width: 25 +[EXIF, IFD0, Image] 257 - Image Height: 24 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 +[EXIF, IFD0, Image] 259 - Compression: Uncompressed +[EXIF, IFD0, Image] 262 - Photometric Interpretation: RGB Palette +[EXIF, IFD0, Image] 273 - Strip Offsets: 2060 +[EXIF, IFD0, Image] 278 - Rows Per Strip: 24 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 600 +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 320 - Color Map: (Binary data 1536 bytes) +[EXIF, IFD0, Location] 34264 - Model Transform: 33.4179196429669 35.8363313794284 0 691955.165684031 35.8363313794284 -33.4179196429669 0 2791710.99012603 0 0 0 0 0 0 0 1 +[GeoTiff, GeoTiff, Location] 1 - Geo Tiff Version: 1.1.0 +[GeoTiff, GeoTiff, Location] 1024 - GT Model Type: Projected +[GeoTiff, GeoTiff, Location] 1025 - GT Raster Type: Pixel Is Area +[GeoTiff, GeoTiff, Location] 2048 - Geographic Type: User Defined +[GeoTiff, GeoTiff, Location] 2049 - Geog Citation: Hough UTM zone 17N +[GeoTiff, GeoTiff, Location] 2050 - Geog Geodetic Datum: User Defined +[GeoTiff, GeoTiff, Location] 2057 - Geog Semi Major Axis: 6378270 +[GeoTiff, GeoTiff, Location] 2058 - Geog Semi Minor Axis: 6356794.343479 +[GeoTiff, GeoTiff, Location] 3072 - Projected CS Type: User Defined +[GeoTiff, GeoTiff, Location] 3073 - PCS Citation: Hough UTM zone 17N +[GeoTiff, GeoTiff, Location] 3074 - Projection: UTM zone 17N +[Composite, Composite, Image] Exif-ImageSize - Image Size: 25x24 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000600 diff --git a/ExifTool/t/GeoTiff_3.out b/ExifTool/t/GeoTiff_3.out new file mode 100644 index 0000000..2a5cc59 --- /dev/null +++ b/ExifTool/t/GeoTiff_3.out @@ -0,0 +1,38 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: GeoTiff_3_failed.tif +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 2.7 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:38-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:38-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:38-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: TIFF +[File, File, Other] FileTypeExtension - File Type Extension: tif +[File, File, Other] MIMEType - MIME Type: image/tiff +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[EXIF, IFD0, Image] 256 - Image Width: 25 +[EXIF, IFD0, Image] 257 - Image Height: 24 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 +[EXIF, IFD0, Image] 259 - Compression: Uncompressed +[EXIF, IFD0, Image] 262 - Photometric Interpretation: RGB Palette +[EXIF, IFD0, Image] 273 - Strip Offsets: 2054 +[EXIF, IFD0, Image] 278 - Rows Per Strip: 24 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 600 +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: cm +[EXIF, IFD0, Image] 320 - Color Map: (Binary data 1536 bytes) +[EXIF, IFD0, Location] 34264 - Model Transform: 33.4179196429669 35.8363313794284 0 691955.165684031 35.8363313794284 -33.4179196429669 0 2791710.99012603 0 0 0 0 0 0 0 1 +[GeoTiff, GeoTiff, Location] 1 - Geo Tiff Version: 1.1.0 +[GeoTiff, GeoTiff, Location] 1024 - GT Model Type: Projected +[GeoTiff, GeoTiff, Location] 1025 - GT Raster Type: Pixel Is Area +[GeoTiff, GeoTiff, Location] 2048 - Geographic Type: User Defined +[GeoTiff, GeoTiff, Location] 2049 - Geog Citation: Hough UTM zone 17N +[GeoTiff, GeoTiff, Location] 2050 - Geog Geodetic Datum: User Defined +[GeoTiff, GeoTiff, Location] 2057 - Geog Semi Major Axis: 6378270 +[GeoTiff, GeoTiff, Location] 2058 - Geog Semi Minor Axis: 6356794.343479 +[GeoTiff, GeoTiff, Location] 3072 - Projected CS Type: User Defined +[GeoTiff, GeoTiff, Location] 3073 - PCS Citation: Hough UTM zone 17N +[GeoTiff, GeoTiff, Location] 3074 - Projection: UTM zone 17N +[Composite, Composite, Image] Exif-ImageSize - Image Size: 25x24 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000600 diff --git a/ExifTool/t/GeoTiff_4.out b/ExifTool/t/GeoTiff_4.out new file mode 100644 index 0000000..f4aa7dc --- /dev/null +++ b/ExifTool/t/GeoTiff_4.out @@ -0,0 +1,11 @@ +[GeoTiff, GeoTiff, Location] 1 - Geo Tiff Version: 1.1.0 +[GeoTiff, GeoTiff, Location] 1024 - GT Model Type: Projected +[GeoTiff, GeoTiff, Location] 1025 - GT Raster Type: Pixel Is Area +[GeoTiff, GeoTiff, Location] 2048 - Geographic Type: User Defined +[GeoTiff, GeoTiff, Location] 2049 - Geog Citation: Hough UTM zone 17N +[GeoTiff, GeoTiff, Location] 2050 - Geog Geodetic Datum: User Defined +[GeoTiff, GeoTiff, Location] 2057 - Geog Semi Major Axis: 6378270 +[GeoTiff, GeoTiff, Location] 2058 - Geog Semi Minor Axis: 6356794.343479 +[GeoTiff, GeoTiff, Location] 3072 - Projected CS Type: User Defined +[GeoTiff, GeoTiff, Location] 3073 - PCS Citation: Hough UTM zone 17N +[GeoTiff, GeoTiff, Location] 3074 - Projection: UTM zone 17N diff --git a/ExifTool/t/Geotag.t b/ExifTool/t/Geotag.t new file mode 100644 index 0000000..865070d --- /dev/null +++ b/ExifTool/t/Geotag.t @@ -0,0 +1,223 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Geotag.t". + +my $numTests; + +BEGIN { + $numTests = 12; + $| = 1; print "1..$numTests\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); + # must create user-defined tags before loading ExifTool (used in test 8) + %Image::ExifTool::UserDefined = ( + 'Image::ExifTool::GPS::Main' => { + 0xd000 => { + Name => 'GPSPitch', + Writable => 'rational64s', + }, + 0xd001 => { + Name => 'GPSRoll', + Writable => 'rational64s', + }, + }, + ); +} +END {print "not ok 1\n" unless $loaded;} +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Geotag; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Geotag'; +my $testnum = 1; +my @testTags = ('Error', 'Warning', 'GPS:*', 'XMP:*'); +my $testfile2; + +unless (eval { require Time::Local }) { + warn "Install Time::Local to use the Geotag feature\n"; + while (++$testnum <= $numTests) { + print "ok $testnum # skip Requires Time::Local\n"; + } + goto IgnoreAll; +} + +# test 2: Geotag from GPX track log +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $testfile2 = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile2; + $exifTool->SetNewValue(Geotag => 't/images/Geotag.gpx'); + $exifTool->SetNewValue(Geotime => '2003:05:24 17:09:31Z'); + $exifTool->WriteInfo('t/images/Writer.jpg', $testfile2); + my $info = $exifTool->ImageInfo($testfile2, @testTags); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# tests 3-5: Geotag tests using Magellan track log +{ + # geotag to XMP + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValue(Geotag => 't/images/Geotag.log'); + $exifTool->SetNewValue('XMP:Geotime' => '2009:04:03 06:11:30-05:00'); + $exifTool->WriteInfo('t/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, @testTags); + if (check($exifTool, $info, $testname, $testnum, 3)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + + # point too far outside track + ++$testnum; + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + my ($num, $err) = $exifTool->SetNewValue(Geotime => '2009:04:03 08:00:00-05:00'); + $exifTool->WriteInfo($testfile2, $testfile); + $info = $exifTool->ImageInfo($testfile, @testTags); + if (check($exifTool, $info, $testname, $testnum, 2)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + + # delete geotags + ++$testnum; + my $testfile5 = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile5; + ($num, $err) = $exifTool->SetNewValue(Geotime => undef); + $exifTool->WriteInfo($testfile2, $testfile5); + $info = $exifTool->ImageInfo($testfile5, 'Filename', @testTags); + if (check($exifTool, $info, $testname, $testnum) and not $err) { + unlink $testfile2; + unlink $testfile5; + } else { + warn "\n $err\n" if $err; + notOK(); + } + print "ok $testnum\n"; +} + +# test 6: Geotag from Garmin XML track log and test Geosync too +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValue(Geosync => '1:30'); + $exifTool->SetNewValue(Geotag => 't/images/Geotag.xml'); + $exifTool->SetNewValuesFromFile('t/images/Panasonic.jpg', + 'Geotime<${DateTimeOriginal}+02:00' + ); + $exifTool->WriteInfo('t/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, @testTags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 7: Geotag from IGC log with time drift correction +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + my $txtfile = "t/${testname}_${testnum}.failed"; + unlink $testfile; + open GEOTAG_TEST_7, ">$txtfile" or warn "Error opening $txtfile\n"; + $exifTool->Options(Verbose => 2); + $exifTool->Options(TextOut => \*GEOTAG_TEST_7); + $exifTool->SetNewValue(Geosync => '2010:01:05 07:00:00Z@2001:08:01 12:00:00-02:00'); + $exifTool->SetNewValue(Geosync => '2010:01:05 09:01:00Z@2001:08:01 14:00:00-02:00'); + $exifTool->SetNewValue(Geotag => 't/images/Geotag.igc'); + $exifTool->SetNewValuesFromFile('t/images/Nikon.jpg', + 'Geotime<${DateTimeOriginal}-02:00' + ); + $exifTool->WriteInfo('t/images/Writer.jpg', $testfile); + close GEOTAG_TEST_7; + if (testCompare('t/Geotag_7.out', $txtfile, $testnum)) { + unlink $testfile; + unlink $txtfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 8: Geotag with attitude information from PTNTHPR sentence +# test 9: Geotag from KML track log (obtained from Google Location), +# test 10: Geotag from Bramor gEO log +{ + my %dat = ( + 8 => { file => 'Geotag2.log', geotime => '2010:04:24 06:27:30-05:00' }, + 9 => { file => 'Geotag.kml', geotime => '2013:11:13 09:04:31Z' }, + 10 => { file => 'Geotag3.log', geotime => '2014:04:21 07:06:42Z' }, + ); + my $exifTool = Image::ExifTool->new; + while ($testnum < 10) { + ++$testnum; + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValue(Geotag => 't/images/' . $dat{$testnum}{file}); + $exifTool->SetNewValue(Geotime => $dat{$testnum}{geotime}); + $exifTool->WriteInfo('t/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, @testTags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + } +} + +# test 11: Geotag date/time only with drift correction +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValue(Geotag => 'DATETIMEONLY'); + $exifTool->SetNewValue(Geosync => '2009:01:01 01:00:00Z@2009:01:01 01:00:00Z'); + $exifTool->SetNewValue(Geosync => '2011:01:01 02:00:00Z@2011:01:01 01:00:00Z'); + $exifTool->SetNewValue(Geotime => '2010:01:01 01:00:00Z'); + $exifTool->WriteInfo('t/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, @testTags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 12: Geotag from DJI CSV log file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValue(Geotag => 't/images/Geotag_DJI_2020-12-02_[07-50-31].csv'); + $exifTool->SetNewValue(Geotime => '2020:12:02 07:50:35.3'); + $exifTool->WriteInfo('t/images/Writer.jpg', $testfile); + # (ignore GPSDate/TimeStamp because they will depend on the system time zone) + my $info = $exifTool->ImageInfo($testfile, 'gps:all', '-gpstimestamp', '-gpsdatestamp'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +IgnoreAll: + +done(); # end diff --git a/ExifTool/t/Geotag_10.out b/ExifTool/t/Geotag_10.out new file mode 100644 index 0000000..432b24b --- /dev/null +++ b/ExifTool/t/Geotag_10.out @@ -0,0 +1,15 @@ +[EXIF, GPS, Location] 0 - GPS Version ID: 2.3.0.0 +[EXIF, GPS, Location] 1 - GPS Latitude Ref: North +[EXIF, GPS, Location] 2 - GPS Latitude: 42 deg 43' 17.99" +[EXIF, GPS, Location] 3 - GPS Longitude Ref: East +[EXIF, GPS, Location] 4 - GPS Longitude: 61 deg 40' 19.30" +[EXIF, GPS, Location] 5 - GPS Altitude Ref: Above Sea Level +[EXIF, GPS, Location] 6 - GPS Altitude: 250 m +[EXIF, GPS, Time] 7 - GPS Time Stamp: 07:06:42 +[EXIF, GPS, Location] 14 - GPS Track Ref: True North +[EXIF, GPS, Location] 15 - GPS Track: 21.94 +[EXIF, GPS, Location] 16 - GPS Img Direction Ref: True North +[EXIF, GPS, Location] 17 - GPS Img Direction: 21.94 +[EXIF, GPS, Time] 29 - GPS Date Stamp: 2014:04:21 +[EXIF, GPS, Location] 53248 - GPS Pitch: 7.33 +[EXIF, GPS, Location] 53249 - GPS Roll: 1.26 diff --git a/ExifTool/t/Geotag_11.out b/ExifTool/t/Geotag_11.out new file mode 100644 index 0000000..6c4ce32 --- /dev/null +++ b/ExifTool/t/Geotag_11.out @@ -0,0 +1,3 @@ +[EXIF, GPS, Location] 0 - GPS Version ID: 2.3.0.0 +[EXIF, GPS, Time] 7 - GPS Time Stamp: 01:30:00 +[EXIF, GPS, Time] 29 - GPS Date Stamp: 2010:01:01 diff --git a/ExifTool/t/Geotag_12.out b/ExifTool/t/Geotag_12.out new file mode 100644 index 0000000..394594d --- /dev/null +++ b/ExifTool/t/Geotag_12.out @@ -0,0 +1,5 @@ +[EXIF, GPS, Location] 0 - GPS Version ID: 2.3.0.0 +[EXIF, GPS, Location] 1 - GPS Latitude Ref: South +[EXIF, GPS, Location] 2 - GPS Latitude: 44 deg 41' 24.32" +[EXIF, GPS, Location] 3 - GPS Longitude Ref: East +[EXIF, GPS, Location] 4 - GPS Longitude: 170 deg 25' 28.45" diff --git a/ExifTool/t/Geotag_2.out b/ExifTool/t/Geotag_2.out new file mode 100644 index 0000000..d3d7354 --- /dev/null +++ b/ExifTool/t/Geotag_2.out @@ -0,0 +1,9 @@ +[EXIF, GPS, Location] 0 - GPS Version ID: 2.3.0.0 +[EXIF, GPS, Location] 1 - GPS Latitude Ref: North +[EXIF, GPS, Location] 2 - GPS Latitude: 43 deg 38' 30.66" +[EXIF, GPS, Location] 3 - GPS Longitude Ref: West +[EXIF, GPS, Location] 4 - GPS Longitude: 116 deg 3' 44.05" +[EXIF, GPS, Location] 5 - GPS Altitude Ref: Above Sea Level +[EXIF, GPS, Location] 6 - GPS Altitude: 1485.648936 m +[EXIF, GPS, Time] 7 - GPS Time Stamp: 17:09:31 +[EXIF, GPS, Time] 29 - GPS Date Stamp: 2003:05:24 diff --git a/ExifTool/t/Geotag_3.out b/ExifTool/t/Geotag_3.out new file mode 100644 index 0000000..e502f6e --- /dev/null +++ b/ExifTool/t/Geotag_3.out @@ -0,0 +1,6 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 7.77 +[XMP, XMP-exif, Location] GPSAltitude - GPS Altitude: 103.647 m +[XMP, XMP-exif, Location] GPSAltitudeRef - GPS Altitude Ref: Above Sea Level +[XMP, XMP-exif, Location] GPSLatitude - GPS Latitude: 44 deg 14' 10.30" N +[XMP, XMP-exif, Location] GPSLongitude - GPS Longitude: 76 deg 30' 56.52" W +[XMP, XMP-exif, Time] GPSTimeStamp - GPS Date/Time: 2009:04:03 11:11:30 diff --git a/ExifTool/t/Geotag_5.out b/ExifTool/t/Geotag_5.out new file mode 100644 index 0000000..aef72a4 --- /dev/null +++ b/ExifTool/t/Geotag_5.out @@ -0,0 +1 @@ +[File, System, Other] FileName - File Name: Geotag_5_failed.jpg diff --git a/ExifTool/t/Geotag_6.out b/ExifTool/t/Geotag_6.out new file mode 100644 index 0000000..d8a2b25 --- /dev/null +++ b/ExifTool/t/Geotag_6.out @@ -0,0 +1,9 @@ +[EXIF, GPS, Location] 0 - GPS Version ID: 2.3.0.0 +[EXIF, GPS, Location] 1 - GPS Latitude Ref: North +[EXIF, GPS, Location] 2 - GPS Latitude: 43 deg 38' 59.51" +[EXIF, GPS, Location] 3 - GPS Longitude Ref: West +[EXIF, GPS, Location] 4 - GPS Longitude: 79 deg 34' 59.54" +[EXIF, GPS, Location] 5 - GPS Altitude Ref: Above Sea Level +[EXIF, GPS, Location] 6 - GPS Altitude: 145.9518987 m +[EXIF, GPS, Time] 7 - GPS Time Stamp: 13:57:16 +[EXIF, GPS, Time] 29 - GPS Date Stamp: 2004:08:28 diff --git a/ExifTool/t/Geotag_7.out b/ExifTool/t/Geotag_7.out new file mode 100644 index 0000000..efc7f53 --- /dev/null +++ b/ExifTool/t/Geotag_7.out @@ -0,0 +1,54 @@ +Added Geosync point: + GPS time stamp: 2010:01:05 07:00:00.000 UTC + Image date/time: 2001:08:01 14:00:00.000 UTC +Writing File:Geosync +Added Geosync point: + GPS time stamp: 2010:01:05 09:01:00.000 UTC + Image date/time: 2001:08:01 16:00:00.000 UTC +Writing File:Geosync +Loaded 10 points from IGC-format GPS track log file 't/images/Geotag.igc' + GPS track start: 2010:01:05 07:23:15.000 UTC + GPS track end: 2010:01:05 08:52:56.000 UTC +Writing File:Geotag + Geotime value: 2010:01:05 07:57:51.692 UTC (incl. Geosync offset of +266000428.692 sec) +Writing MIE-GPS:GPSLatitude +Writing XMP-drone-dji:GPSLatitude if tag exists +Writing XMP-exif:GPSLatitude if tag exists +Writing GPS:GPSLatitude +Writing MIE-GPS:GPSLongitude +Writing XMP-drone-dji:GPSLongitude if tag exists +Writing XMP-exif:GPSLongitude if tag exists +Writing GPS:GPSLongitude +Writing MIE-GPS:GPSAltitude +Writing XMP-exif:GPSAltitude if tag exists +Writing GPS:GPSAltitude +Writing XMP-exif:GPSAltitudeRef if tag exists +Writing GPS:GPSAltitudeRef +Writing GPS:GPSLatitudeRef +Writing GPS:GPSLongitudeRef +Writing GPS:GPSDateStamp +Writing GPS:GPSTimeStamp +Writing XMP-exif:GPSDateTime if tag exists +Rewriting t/images/Writer.jpg... + Editing tags in: APP0 APP1 File GPS IFD0 JFIF MIE-GPS XMP + Creating tags in: APP1 File GPS IFD0 MIE-GPS +Creating APP1: + Creating IFD0 + + IFD0:XResolution = '72' (mandatory) + + IFD0:YResolution = '72' (mandatory) + + IFD0:ResolutionUnit = '2' (mandatory) + + IFD0:YCbCrPositioning = '1' (mandatory) + Creating GPS + + GPS:GPSVersionID = '2 3 0 0' (mandatory) + + GPS:GPSLatitudeRef = 'N' + + GPS:GPSLatitude = '49 10 45.9657779460145' + + GPS:GPSLongitudeRef = 'E' + + GPS:GPSLongitude = '6 51 35.6260862642929' + + GPS:GPSAltitudeRef = '0' + + GPS:GPSAltitude = '848.896982535614' + + GPS:GPSTimeStamp = '07 57 51.69167' + + GPS:GPSDateStamp = '2010:01:05' +JPEG DQT (130 bytes) +JPEG SOF0: +JPEG DHT (73 bytes) +JPEG SOS diff --git a/ExifTool/t/Geotag_8.out b/ExifTool/t/Geotag_8.out new file mode 100644 index 0000000..322c7e3 --- /dev/null +++ b/ExifTool/t/Geotag_8.out @@ -0,0 +1,15 @@ +[EXIF, GPS, Location] 0 - GPS Version ID: 2.3.0.0 +[EXIF, GPS, Location] 1 - GPS Latitude Ref: North +[EXIF, GPS, Location] 2 - GPS Latitude: 50 deg 40' 16.53" +[EXIF, GPS, Location] 3 - GPS Longitude Ref: East +[EXIF, GPS, Location] 4 - GPS Longitude: 5 deg 6' 0.43" +[EXIF, GPS, Time] 7 - GPS Time Stamp: 11:27:30 +[EXIF, GPS, Location] 12 - GPS Speed Ref: knots +[EXIF, GPS, Location] 13 - GPS Speed: 28.37631579 +[EXIF, GPS, Location] 14 - GPS Track Ref: True North +[EXIF, GPS, Location] 15 - GPS Track: 138.8394737 +[EXIF, GPS, Location] 16 - GPS Img Direction Ref: True North +[EXIF, GPS, Location] 17 - GPS Img Direction: 256.3315789 +[EXIF, GPS, Time] 29 - GPS Date Stamp: 2010:04:24 +[EXIF, GPS, Location] 53248 - GPS Pitch: -4.152631579 +[EXIF, GPS, Location] 53249 - GPS Roll: -10.18421053 diff --git a/ExifTool/t/Geotag_9.out b/ExifTool/t/Geotag_9.out new file mode 100644 index 0000000..53c1d20 --- /dev/null +++ b/ExifTool/t/Geotag_9.out @@ -0,0 +1,7 @@ +[EXIF, GPS, Location] 0 - GPS Version ID: 2.3.0.0 +[EXIF, GPS, Location] 1 - GPS Latitude Ref: North +[EXIF, GPS, Location] 2 - GPS Latitude: 34 deg 9' 55.90" +[EXIF, GPS, Location] 3 - GPS Longitude Ref: West +[EXIF, GPS, Location] 4 - GPS Longitude: 106 deg 1' 37.01" +[EXIF, GPS, Time] 7 - GPS Time Stamp: 09:04:31 +[EXIF, GPS, Time] 29 - GPS Date Stamp: 2013:11:13 diff --git a/ExifTool/t/GoPro.t b/ExifTool/t/GoPro.t new file mode 100644 index 0000000..81517aa --- /dev/null +++ b/ExifTool/t/GoPro.t @@ -0,0 +1,29 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/GoPro.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::GoPro; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'GoPro'; +my $testnum = 1; + +# test 2: Extract information from GoPro.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Unknown => 1); + my $info = $exifTool->ImageInfo('t/images/GoPro.jpg', 'GoPro:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/GoPro_2.out b/ExifTool/t/GoPro_2.out new file mode 100644 index 0000000..ea72725 --- /dev/null +++ b/ExifTool/t/GoPro_2.out @@ -0,0 +1,30 @@ +[APP6, GoPro, Camera] DVID - Device ID: 1 +[APP6, GoPro, Camera] DVNM - Device Name: Photo Global Settings +[APP6, GoPro, Camera] TICK - GoPro TICK: 85473 +[APP6, GoPro, Camera] TSMP - Total Samples: 192 +[APP6, GoPro, Camera] TICK - GoPro TICK: 85473 +[APP6, GoPro, Camera] FMWR - Firmware Version: HD6.01.01.51.00 +[APP6, GoPro, Camera] LINF - GoPro LINF: LAJ7061317601275 +[APP6, GoPro, Camera] CINF - GoPro CINF: 60 49 27 73 22 20 157 168 225 252 86 165 90 126 204 208 +[APP6, GoPro, Camera] CASN - Camera Serial Number: C3221324657219 +[APP6, GoPro, Camera] MINF - Camera Model Name: HERO6 Black +[APP6, GoPro, Camera] MUID - Media Unique ID: 491b313ca89d1416a556fce1d0cc7e5a00000000000000000000000000000000 +[APP6, GoPro, Camera] CMOD - GoPro CMOD: 17 +[APP6, GoPro, Camera] MTYP - GoPro MTYP: 11 +[APP6, GoPro, Camera] OREN - Auto Rotation: Up +[APP6, GoPro, Camera] DZOM - Digital Zoom: Yes +[APP6, GoPro, Camera] DZST - GoPro DZST: 0 +[APP6, GoPro, Camera] SMTR - GoPro SMTR: N +[APP6, GoPro, Camera] PRTN - Pro Tune: On +[APP6, GoPro, Camera] PTWB - White Balance: AUTO +[APP6, GoPro, Camera] PTSH - Sharpness: HIGH +[APP6, GoPro, Camera] PTCL - Color Mode: FLAT +[APP6, GoPro, Camera] EXPT - Maximum Shutter Angle: +[APP6, GoPro, Camera] PIMX - Auto ISO Max: 3200 +[APP6, GoPro, Camera] PIMN - Auto ISO Min: 3200 +[APP6, GoPro, Camera] PTEV - Exposure Compensation: 0.0 +[APP6, GoPro, Camera] RATE - Rate: 4_1SEC +[APP6, GoPro, Camera] PRES - Photo Resolution: 12MP_W +[APP6, GoPro, Camera] PHDR - HDR Setting: 0 +[APP6, GoPro, Camera] PRAW - GoPro PRAW: 0 +[APP6, GoPro, Camera] HFLG - GoPro HFLG: 0 diff --git a/ExifTool/t/HTML.t b/ExifTool/t/HTML.t new file mode 100644 index 0000000..aa4d36f --- /dev/null +++ b/ExifTool/t/HTML.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/HTML.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::HTML; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'HTML'; +my $testnum = 1; + +# test 2: Extract information from HTML.html +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/HTML.html'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/HTML_2.out b/ExifTool/t/HTML_2.out new file mode 100644 index 0000000..1631fd5 --- /dev/null +++ b/ExifTool/t/HTML_2.out @@ -0,0 +1,69 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: HTML.html +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 3.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:06:28 20:35:26-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:38-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: HTML +[File, File, Other] FileTypeExtension - File Type Extension: html +[File, File, Other] MIMEType - MIME Type: text/html +[HTML, HTML, Document] title - Title: ExifTool HTML Test +[HTML, HTTP-equiv, Document] content-type - Content Type: text/html; charset="iso-8859-1" +[HTML, HTML-dc, Document] title - Title: ExifTool HTML Test +[HTML, HTML-dc, Author] creator - Creator: Phil Harvey, Another Creator +[HTML, HTML-dc, Time] date - Date: 2007-30-01 +[HTML, HTML-dc, Document] format - Format: Daisy 2.02 +[HTML, HTML-dc, Document] identifier - Identifier: DTB00345 +[HTML, HTML-dc, Document] language - Language: EN +[HTML, HTML-dc, Author] publisher - Publisher: TPB +[HTML, HTML-dc, Author] source - Source: 0-065-01022-1 +[HTML, HTML-dc, Document] subject - Subject: Greek: α β γ +[HTML, HTML-ncc, Time] sourcedate - Source Date: 1993 +[HTML, HTML-ncc, Document] sourceedition - Source Edition: 1 +[HTML, HTML-ncc, Document] sourcepublisher - Source Publisher: Phil's Desktop +[HTML, HTML-ncc, Document] charset - Character Set: iso-8859-1 +[HTML, HTML-ncc, Document] generator - Generator: ExifTool 6.73 +[HTML, HTML-ncc, Document] narrator - Narrator: a narrator +[HTML, HTML-ncc, Document] tocitems - TOC Items: 1024 +[HTML, HTML-ncc, Document] totaltime - Duration: 91:27:21 +[HTML, HTML-ncc, Document] pagenormal - Page Normal: 881 +[HTML, HTML-ncc, Document] maxpagenormal - Max Page Normal: 881 +[HTML, HTML-ncc, Document] pagefront - Page Front: 27 +[HTML, HTML-ncc, Document] pagespecial - Page Special: 45 +[HTML, HTML-ncc, Document] prodnotes - Prod Notes: 0 +[HTML, HTML-ncc, Document] footnotes - Footnotes: 0 +[HTML, HTML-ncc, Document] sidebars - Sidebars: 0 +[HTML, HTML-ncc, Document] setinfo - Set Info: 1 of 3 +[HTML, HTML-ncc, Document] depth - Depth: 4 +[HTML, HTML-ncc, Document] kbytesize - K Byte Size: 1530000 +[HTML, HTML-ncc, Document] multimediatype - Multimedia Type: audioNCC +[HTML, HTML-ncc, Document] files - Files: 97 +[HTML, HTML-prod, Document] reclocation - Rec Location: Studio 2 +[HTML, HTML-prod, Document] recengineer - Rec Engineer: P Harvey +[HTML, HTML-office, Document] Subject - Subject: a subject +[HTML, HTML-office, Author] Author - Author: an author +[HTML, HTML-office, Document] Keywords - Keywords: keyword1, keyword2 +[HTML, HTML-office, Document] Description - Description: a comments;a new line +[HTML, HTML-office, Document] Template - Template: Normal.dotm +[HTML, HTML-office, Author] LastAuthor - Last Author: Phil Harvey +[HTML, HTML-office, Document] Revision - Revision Number: 2 +[HTML, HTML-office, Document] TotalTime - Total Edit Time: 1 minute +[HTML, HTML-office, Time] Created - Create Date: 2010:06:28 23:52:00Z +[HTML, HTML-office, Time] LastSaved - Modify Date: 2010:06:28 23:52:00Z +[HTML, HTML-office, Document] Pages - Pages: 1 +[HTML, HTML-office, Document] Words - Words: 84 +[HTML, HTML-office, Document] Characters - Characters: 324 +[HTML, HTML-office, Document] Category - Category: a catégory +[HTML, HTML-office, Document] Manager - Manager: a manager +[HTML, HTML-office, Document] Company - Company: a company +[HTML, HTML-office, Document] Lines - Lines: 26 +[HTML, HTML-office, Document] Paragraphs - Paragraphs: 26 +[HTML, HTML-office, Document] CharactersWithSpaces - Characters With Spaces: 382 +[HTML, HTML-office, Document] Version - Revision Number: 12.0 +[HTML, HTML-office, Document] Checked_x0020_by - Checked By: Phil +[HTML, HTML-office, Document] test1 - Test 1: 1 +[HTML, HTML-office, Document] test2 - Test 2: 15 +[HTML, HTML-office, Document] test3 - Test 3: 2010-02-05T05:00:00Z +[HTML, HTML-office, Document] test4 - Test 4: text diff --git a/ExifTool/t/ICO.t b/ExifTool/t/ICO.t new file mode 100644 index 0000000..b1d1507 --- /dev/null +++ b/ExifTool/t/ICO.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/ICO.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::ICO; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'ICO'; +my $testnum = 1; + +# test 2: Extract information from ICO.ico +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ICO.ico'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/ICO_2.out b/ExifTool/t/ICO_2.out new file mode 100644 index 0000000..de793c5 --- /dev/null +++ b/ExifTool/t/ICO_2.out @@ -0,0 +1,20 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.50 +[File, System, Other] FileName - File Name: ICO.ico +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 78 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:10:19 15:17:45-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:10:20 11:53:31-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:10:19 15:22:45-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: ICO +[File, File, Other] FileTypeExtension - File Type Extension: ico +[File, File, Other] MIMEType - MIME Type: image/x-icon +[File, File, Image] 4 - Image Count: 1 +[File, File, Image] 0 - Image Width: 1 +[File, File, Image] 1 - Image Height: 1 +[File, File, Image] 2 - Num Colors: 2 +[File, File, Image] 4 - Color Planes: 1 +[File, File, Image] 6 - Bits Per Pixel: 1 +[File, File, Image] 8 - Image Length: 56 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 1x1 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000001 diff --git a/ExifTool/t/IPTC.t b/ExifTool/t/IPTC.t new file mode 100644 index 0000000..6b23067 --- /dev/null +++ b/ExifTool/t/IPTC.t @@ -0,0 +1,158 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/IPTC.t". + +BEGIN { + $| = 1; print "1..8\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::IPTC; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'IPTC'; +my $testnum = 1; + +# test 2: Extract information from IPTC.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/IPTC.jpg', {Duplicates => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Test GetValue() in list context +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->ExtractInfo('t/images/IPTC.jpg', {JoinLists => 0}); + my @values = $exifTool->GetValue('Keywords','ValueConv'); + my $values = join '-', @values; + my $expected = 'ExifTool-Test-IPTC'; + unless ($values eq $expected) { + warn "\n Test $testnum differs with \"$values\"\n"; + notOK(); + } + print "ok $testnum\n"; +} + +# test 4: Test rewriting everything with slightly different values +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 1, Binary => 1, ListJoin => undef); + my $info = $exifTool->ImageInfo('t/images/IPTC.jpg'); + my $tag; + foreach $tag (keys %$info) { + my $group = $exifTool->GetGroup($tag); + my $val = $$info{$tag}; + if (ref $val eq 'ARRAY') { + push @$val, 'v2'; + } elsif (ref $val eq 'SCALAR') { + $val = 'v2'; + } elsif ($val =~ /^\d+(\.\d*)?$/) { + # (add extra .001 to avoid problem with aperture of 4.85 + # getting rounded to 4.8 or 4.9 and causing failed tests) + $val += ($val / 10) + 1.001; + $1 or $val = int($val); + } else { + $val .= '-v2'; + } + # eat return values so warnings don't get printed + my @x = $exifTool->SetNewValue($tag, $val, Group=>$group, Replace=>1); + } + # also try writing a few specific tags + $exifTool->SetNewValue(CreatorCountry => 'Canada'); + $exifTool->SetNewValue(CodedCharacterSet => 'UTF8', Protected => 1); + undef $info; + my $image; + my $ok = writeInfo($exifTool, 't/images/IPTC.jpg', \$image, undef, 1); + # this is effectively what the RHEL 3 UTF8 LANG problem does: + # $image = pack("U*", unpack("C*", $image)); + + my $exifTool2 = Image::ExifTool->new; + $exifTool2->Options(Duplicates => 1); + $info = $exifTool2->ImageInfo(\$image); + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + if (check($exifTool2, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + # save bad file + open(TESTFILE,">$testfile"); + binmode(TESTFILE); + print TESTFILE $image; + close(TESTFILE); + notOK(); + } + print "ok $testnum\n"; +} + +# test 5: Test IPTC special characters +{ + ++$testnum; + my @writeInfo = ( + # (don't put special character hex codes in string in an attempt to patch failed + # test by dcollins on Perl 5.95 and i686-linux-thread-multi 2.6.28-11-generic) + # ['IPTC:CopyrightNotice' => chr(0xc2) . chr(0xa9) . " 2008 Phil Harvey"], + # - didn't fix it, so change it back again: + # (dcollins is the only tester with this problem) + ['IPTC:CopyrightNotice' => "\xc2\xa9 2008 Phil Harvey"], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/Writer.jpg', 1); + print "ok $testnum\n"; +} + +# test 6: Write and read using different default IPTC encoding +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->Options(Charset => 'Cyrillic'); + $exifTool->SetNewValuesFromFile('t/images/MIE.mie', 'Comment-ru_RU>Caption-Abstract'); + $exifTool->Options(IPTCCharset => 'Cyrillic'); + my $ok = writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + $exifTool->Options(Charset => 'UTF8'); + my $info = $exifTool->ImageInfo($testfile, 'IPTC:*'); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 7: Replace an entry in a list +{ + ++$testnum; + my @writeInfo = ( + ['IPTC:Keywords' => 'Test', DelValue => 1], + ['IPTC:Keywords' => 'One'], + ['IPTC:Keywords' => 'Two'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/IPTC.jpg', 1); + print "ok $testnum\n"; +} + +# test 8: Write IPTC as a block +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValuesFromFile('t/images/IPTC.jpg', 'IPTC'); + my $ok = writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'IPTC:*'); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/IPTC_2.out b/ExifTool/t/IPTC_2.out new file mode 100644 index 0000000..ac3f502 --- /dev/null +++ b/ExifTool/t/IPTC_2.out @@ -0,0 +1,69 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.46 +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest is not current. XMP may be out of sync +[File, System, Other] FileName - File Name: IPTC.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 9.9 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:09:19 14:15:36-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:18 10:49:22-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 3ab612778f9944a999853a0cf0cf6b37 +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[JFIF, JFIF, Image] 0 - JFIF Version: 1.02 +[JFIF, JFIF, Image] 2 - Resolution Unit: inches +[JFIF, JFIF, Image] 3 - X Resolution: 72 +[JFIF, JFIF, Image] 5 - Y Resolution: 72 +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other] 105 - Headline: No headline +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My Position +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Other] 25 - Keywords: ExifTool, Test, IPTC +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Author] 115 - Source: I'm the source +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: 05ad1770b1a95f1f9788ac995fa647da +[Photoshop, Photoshop, Image] 0 - X Resolution: 72 +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: 72 +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Image] 0 - Print Style: Centered +[Photoshop, Photoshop, Image] 2 - Print Position: 0 0 +[Photoshop, Photoshop, Image] 10 - Print Scale: 1 +[Photoshop, Photoshop, Image] 1037 - Global Angle: 30 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 30 +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image] 1054 - URL List: +[Photoshop, Photoshop, Other] 20 - Slices Group Name: IPTC +[Photoshop, Photoshop, Other] 24 - Num Slices: 1 +[Photoshop, Photoshop, Image] 4 - Has Real Merged Data: Yes +[Photoshop, Photoshop, Image] 5 - Writer Name: Adobe Photoshop +[Photoshop, Photoshop, Image] 9 - Reader Name: Adobe Photoshop 7.0 +[Photoshop, Photoshop, Image] 0 - Photoshop Quality: 7 +[Photoshop, Photoshop, Image] 1 - Photoshop Format: Standard +[APP14, Adobe, Image] 0 - DCT Encode Version: 100 +[APP14, Adobe, Image] 1 - APP14 Flags 0: (none) +[APP14, Adobe, Image] 2 - APP14 Flags 1: (none) +[APP14, Adobe, Image] 3 - Color Transform: YCbCr +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/IPTC_4.out b/ExifTool/t/IPTC_4.out new file mode 100644 index 0000000..4926f58 --- /dev/null +++ b/ExifTool/t/IPTC_4.out @@ -0,0 +1,67 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.46 +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest is not current. XMP may be out of sync +[File, System, Other] FileSize - File Size: 13 kB +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 6d8db9a8e51a489368148270c914e73d +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[JFIF, JFIF, Image] 0 - JFIF Version: 1.02 +[JFIF, JFIF, Image] 2 - Resolution Unit: inches +[JFIF, JFIF, Image] 3 - X Resolution: 80 +[JFIF, JFIF, Image] 5 - Y Resolution: 80 +[IPTC, IPTC, Other] 90 - Coded Character Set: UTF8 +[IPTC, IPTC, Other] 0 - Envelope Record Version: 4 +[IPTC, IPTC, Other] 0 - Application Record Version: 3 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption-v2 +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it-v2 +[IPTC, IPTC, Other] 105 - Headline: No headline-v2 +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions-v2 +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey-v2 +[IPTC, IPTC, Author] 85 - By-line Title: My Position-v2 +[IPTC, IPTC, Author] 110 - Credit: My Credit-v2 +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture-v2 +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location] 90 - City: Kingston-v2 +[IPTC, IPTC, Location] 95 - Province-State: Ont-v2 +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada-v2 +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 15 - Category: 2 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities, v2 +[IPTC, IPTC, Other] 25 - Keywords: ExifTool, Test, IPTC, v2 +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey-v2 +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Author] 115 - Source: I'm the source-v2 +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: 05ad1770b1a95f1f9788ac995fa647da +[Photoshop, Photoshop, Image] 0 - X Resolution: 80 +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: 80 +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Image] 0 - Print Style: Centered +[Photoshop, Photoshop, Image] 2 - Print Position: 0 0 +[Photoshop, Photoshop, Image] 10 - Print Scale: 1 +[Photoshop, Photoshop, Image] 1037 - Global Angle: 34 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 34 +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/-v2 +[Photoshop, Photoshop, Image] 1054 - URL List: +[Photoshop, Photoshop, Other] 20 - Slices Group Name: IPTC +[Photoshop, Photoshop, Other] 24 - Num Slices: 1 +[Photoshop, Photoshop, Image] 4 - Has Real Merged Data: Yes +[Photoshop, Photoshop, Image] 5 - Writer Name: Adobe Photoshop +[Photoshop, Photoshop, Image] 9 - Reader Name: Adobe Photoshop 7.0 +[Photoshop, Photoshop, Image] 0 - Photoshop Quality: 8 +[Photoshop, Photoshop, Image] 1 - Photoshop Format: Standard +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.46 +[XMP, XMP-iptcCore, Author] CreatorContactInfoCiAdrCtry - Creator Country: Canada +[APP14, Adobe, Image] 0 - DCT Encode Version: 100 +[APP14, Adobe, Image] 1 - APP14 Flags 0: (none) +[APP14, Adobe, Image] 2 - APP14 Flags 1: (none) +[APP14, Adobe, Image] 3 - Color Transform: YCbCr +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/IPTC_5.out b/ExifTool/t/IPTC_5.out new file mode 100644 index 0000000..7c4700e --- /dev/null +++ b/ExifTool/t/IPTC_5.out @@ -0,0 +1 @@ +[IPTC, IPTC, Author] 116 - Copyright Notice: © 2008 Phil Harvey diff --git a/ExifTool/t/IPTC_6.out b/ExifTool/t/IPTC_6.out new file mode 100644 index 0000000..12627d4 --- /dev/null +++ b/ExifTool/t/IPTC_6.out @@ -0,0 +1,2 @@ +[IPTC, IPTC, Other] 120 - Caption-Abstract: Я могу еÑть Ñтекло, оно мне не вредит. +[IPTC, IPTC, Other] 0 - Application Record Version: 4 diff --git a/ExifTool/t/IPTC_7.out b/ExifTool/t/IPTC_7.out new file mode 100644 index 0000000..399c846 --- /dev/null +++ b/ExifTool/t/IPTC_7.out @@ -0,0 +1 @@ +[IPTC, IPTC, Other] 25 - Keywords: ExifTool, One, Two, IPTC diff --git a/ExifTool/t/IPTC_8.out b/ExifTool/t/IPTC_8.out new file mode 100644 index 0000000..0c4fbf5 --- /dev/null +++ b/ExifTool/t/IPTC_8.out @@ -0,0 +1,20 @@ +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other] 105 - Headline: No headline +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My Position +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Other] 25 - Keywords: ExifTool, Test, IPTC +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Author] 115 - Source: I'm the source diff --git a/ExifTool/t/ISO.t b/ExifTool/t/ISO.t new file mode 100644 index 0000000..6edff46 --- /dev/null +++ b/ExifTool/t/ISO.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/ISO.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::ISO; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'ISO'; +my $testnum = 1; + +# test 2: Extract information from ISO.iso +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ISO.iso', '-system:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/ISO_2.out b/ExifTool/t/ISO_2.out new file mode 100644 index 0000000..6d2949c --- /dev/null +++ b/ExifTool/t/ISO_2.out @@ -0,0 +1,19 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, File, Other] FileType - File Type: ISO +[File, File, Other] FileTypeExtension - File Type Extension: iso +[File, File, Other] MIMEType - MIME Type: application/x-iso9660-image +[ISO, ISO, Other] 40 - Volume Name: ExifTool Test Vol. 1 +[ISO, ISO, Other] 80 - Volume Block Count: 190976 +[ISO, ISO, Other] 128 - Volume Block Size: 2048 +[ISO, ISO, Time] 174 - Root Directory Create Date: 2016:01:08 10:00:26+00:00 +[ISO, ISO, Other] 190 - Volume Set Name: ExifTool Test Set +[ISO, ISO, Other] 318 - Publisher: PH Publishing +[ISO, ISO, Other] 446 - Data Preparer: Phil Harvey +[ISO, ISO, Other] 574 - Software: hdiutil 10.10v398 +[ISO, ISO, Other] 702 - Copyright File Name: copyright.txt +[ISO, ISO, Other] 740 - Abstract File Name: abstract.txt +[ISO, ISO, Other] 776 - Bibligraphic File Name: index.txt +[ISO, ISO, Time] 813 - Volume Create Date: 2016:01:08 10:00:26.00+00:00 +[ISO, ISO, Time] 830 - Volume Modify Date: 2016:01:08 10:00:26.00+00:00 +[ISO, ISO, Other] 7 - Boot System: EL TORITO SPECIFICATION +[Composite, Composite, Other] ISO-VolumeSize - Volume Size: 391 MB diff --git a/ExifTool/t/ITC.t b/ExifTool/t/ITC.t new file mode 100644 index 0000000..9a93e0a --- /dev/null +++ b/ExifTool/t/ITC.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/ITC.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::ITC; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'ITC'; +my $testnum = 1; + +# test 2: Extract information from ITC.itc +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ITC.itc'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/ITC_2.out b/ExifTool/t/ITC_2.out new file mode 100644 index 0000000..69636eb --- /dev/null +++ b/ExifTool/t/ITC_2.out @@ -0,0 +1,21 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: ITC.itc +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 672 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2008:01:12 21:30:37-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:09-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: ITC +[File, File, Other] FileTypeExtension - File Type Extension: itc +[File, File, Other] MIMEType - MIME Type: application/itunes +[ITC, ITC, Image] 16 - Data Type: Artwork +[ITC, ITC, Image] 0 - Library ID: 914A6DE01A279611 +[ITC, ITC, Image] 2 - Track ID: 195770115E4BA2B6 +[ITC, ITC, Image] 4 - Data Location: Local Music File +[ITC, ITC, Image] 5 - Image Type: PNG +[ITC, ITC, Image] 7 - Image Width: 8 +[ITC, ITC, Image] 8 - Image Height: 8 +[ITC, ITC, Other] data - Image Data: (Binary data 180 bytes) +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/InDesign.t b/ExifTool/t/InDesign.t new file mode 100644 index 0000000..b49dc11 --- /dev/null +++ b/ExifTool/t/InDesign.t @@ -0,0 +1,62 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/InDesign.t". + +BEGIN { + $| = 1; print "1..4\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::InDesign; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'InDesign'; +my $testnum = 1; + +# test 2: Extract information from InDesign.indd +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/InDesign.indd'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# tests 3-4: Write some XMP tags then delete all XMP (writes empty XMP record) +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue(Author => 'Phil Harvey'); + $exifTool->SetNewValue(ImageDescription => 'A description'); + my $testfile = "t/${testname}_${testnum}_failed.indd"; + unlink $testfile; + $exifTool->WriteInfo('t/images/InDesign.indd', $testfile); + my $info = $exifTool->ImageInfo($testfile); + my $not; + unless (check($exifTool, $info, $testname, $testnum)) { + notOK(); + $not = 1; + } + print "ok $testnum\n"; + + ++$testnum; + $exifTool->Options(PrintConv => 0); + $exifTool->SetNewValue(); + $exifTool->SetNewValue('XMP:*'); + my $testfile2 = "t/${testname}_${testnum}_failed.indd"; + unlink $testfile2; + $exifTool->WriteInfo($testfile, $testfile2); + $info = $exifTool->ImageInfo($testfile2); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile unless $not; + unlink $testfile2; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/InDesign_2.out b/ExifTool/t/InDesign_2.out new file mode 100644 index 0000000..4dc3eb9 --- /dev/null +++ b/ExifTool/t/InDesign_2.out @@ -0,0 +1,20 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: InDesign.indd +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 12 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2009:06:19 13:09:44-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:39-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: INDD +[File, File, Other] FileTypeExtension - File Type Extension: indd +[File, File, Other] MIMEType - MIME Type: application/x-indesign +[XMP, XMP-x, Document] xmptk - XMP Toolkit: XMP toolkit 3.0-29, framework 1.6 +[XMP, XMP-rdf, Document] about - About: d5d09d4b-2831-11dc-bfa2-d89eae7bab84 +[XMP, XMP-xmp, Time] CreateDate - Create Date: 2007:06:30 00:19:02Z +[XMP, XMP-xmp, Image] CreatorTool - Creator Tool: Adobe InDesign 3.0 +[XMP, XMP-xmp, Time] MetadataDate - Metadata Date: 2007:06:30 00:19:17Z +[XMP, XMP-xmp, Time] ModifyDate - Modify Date: 2007:06:30 00:19:17Z +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:indd:d5d09d4a-2831-11dc-bfa2-d89eae7bab84 +[XMP, XMP-xmpMM, Other] RenditionClass - Rendition Class: default +[XMP, XMP-dc, Image] format - Format: application/x-indesign diff --git a/ExifTool/t/InDesign_3.out b/ExifTool/t/InDesign_3.out new file mode 100644 index 0000000..3b02581 --- /dev/null +++ b/ExifTool/t/InDesign_3.out @@ -0,0 +1,22 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: InDesign_3_failed.indd +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 12 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:39-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:39-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:39-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: INDD +[File, File, Other] FileTypeExtension - File Type Extension: indd +[File, File, Other] MIMEType - MIME Type: application/x-indesign +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-rdf, Document] about - About: d5d09d4b-2831-11dc-bfa2-d89eae7bab84 +[XMP, XMP-dc, Image] format - Format: application/x-indesign +[XMP, XMP-pdf, Author] Author - Author: Phil Harvey +[XMP, XMP-tiff, Image] ImageDescription - Image Description: A description +[XMP, XMP-xmp, Time] CreateDate - Create Date: 2007:06:30 00:19:02Z +[XMP, XMP-xmp, Image] CreatorTool - Creator Tool: Adobe InDesign 3.0 +[XMP, XMP-xmp, Time] MetadataDate - Metadata Date: 2007:06:30 00:19:17Z +[XMP, XMP-xmp, Time] ModifyDate - Modify Date: 2007:06:30 00:19:17Z +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:indd:d5d09d4a-2831-11dc-bfa2-d89eae7bab84 +[XMP, XMP-xmpMM, Other] RenditionClass - Rendition Class: default diff --git a/ExifTool/t/InDesign_4.out b/ExifTool/t/InDesign_4.out new file mode 100644 index 0000000..a76f31c --- /dev/null +++ b/ExifTool/t/InDesign_4.out @@ -0,0 +1,12 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: InDesign_4_failed.indd +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 12288 +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:11:01 10:15:27-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:11:01 10:15:27-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:11:01 10:15:27-04:00 +[File, System, Other] FilePermissions - File Permissions: 644 +[File, File, Other] FileType - File Type: INDD +[File, File, Other] FileTypeExtension - File Type Extension: INDD +[File, File, Other] MIMEType - MIME Type: application/x-indesign +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 11.75 diff --git a/ExifTool/t/InfiRay.t b/ExifTool/t/InfiRay.t new file mode 100644 index 0000000..b84237e --- /dev/null +++ b/ExifTool/t/InfiRay.t @@ -0,0 +1,30 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/InfiRay.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::InfiRay; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'InfiRay'; +my $testnum = 1; + +# test 2: Extract information from InfiRay.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Struct => 2); + $exifTool->Options(MissingTagValue => 'null'); + my $info = $exifTool->ImageInfo('t/images/InfiRay.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/InfiRay_2.out b/ExifTool/t/InfiRay_2.out new file mode 100644 index 0000000..d824dec --- /dev/null +++ b/ExifTool/t/InfiRay_2.out @@ -0,0 +1,126 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.56 +[File, System, Other] FileName - File Name: InfiRay.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2023:02:09 10:42:51-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2023:02:09 10:42:52-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2023:02:09 10:42:51-05:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 256 - Image Width: 720 +[EXIF, IFD0, Camera] 272 - Camera Model Name: P2_USB_IR +[EXIF, IFD0, Image] 257 - Image Height: 960 +[EXIF, IFD0, Camera] 271 - Make: Infisense +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Time] 306 - Modify Date: 2023:02:08 12:24:35 +[EXIF, GPS, Location] 2 - GPS Latitude: +[EXIF, GPS, Location] 6 - GPS Altitude: undef +[EXIF, GPS, Location] 1 - GPS Latitude Ref: Unknown () +[EXIF, GPS, Location] 13 - GPS Speed: undef +[EXIF, GPS, Location] 27 - GPS Processing Method: +[EXIF, GPS, Location] 5 - GPS Altitude Ref: Above Sea Level +[EXIF, GPS, Location] 12 - GPS Speed Ref: Unknown () +[EXIF, GPS, Location] 3 - GPS Longitude Ref: Unknown () +[EXIF, GPS, Time] 7 - GPS Time Stamp: 00:00:00 +[EXIF, GPS, Location] 4 - GPS Longitude: +[EXIF, GPS, Time] 29 - GPS Date Stamp: +[APP2, InfiRay, Image] 0 - IJPEG Version: 0 2 0 1 +[APP2, InfiRay, Image] 12 - IJPEG Org Type: 4 +[APP2, InfiRay, Image] 13 - IJPEG Disp Type: 1 +[APP2, InfiRay, Image] 14 - IJPEG Rotate: 0 +[APP2, InfiRay, Image] 15 - IJPEG Mirror Flip: 0 +[APP2, InfiRay, Image] 16 - Image Color Switchable: 1 +[APP2, InfiRay, Image] 17 - Thermal Color Palette: 3 +[APP2, InfiRay, Image] 32 - IR Data Size: 98304 +[APP2, InfiRay, Image] 40 - IR Data Format: 0 +[APP2, InfiRay, Image] 42 - IR Image Width: 256 +[APP2, InfiRay, Image] 44 - IR Image Height: 384 +[APP2, InfiRay, Image] 46 - IR Image Bpp: 8 +[APP2, InfiRay, Image] 48 - Temp Data Size: 98304 +[APP2, InfiRay, Image] 56 - Temp Data Format: 0 +[APP2, InfiRay, Image] 58 - Temp Image Width: 256 +[APP2, InfiRay, Image] 60 - Temp Image Height: 384 +[APP2, InfiRay, Image] 62 - Temp Image Bpp: 8 +[APP2, InfiRay, Image] 64 - Visible Data Size: 98304 +[APP2, InfiRay, Image] 72 - Visible Data Format: 0 +[APP2, InfiRay, Image] 74 - Visible Image Width: 0 +[APP2, InfiRay, Image] 76 - Visible Image Height: 0 +[APP2, InfiRay, Image] 78 - Visible Image Bpp: 8 +[APP3, InfiRay, Image] APP3 - Imaging Data: (Binary data 20 bytes) +[APP4, InfiRay, Image] 0 - IJPEG Temp Version: 0 1 0 0 +[APP4, InfiRay, Image] 4 - Fact Def Emissivity: -128 +[APP4, InfiRay, Image] 5 - Fact Def Tau: -128 +[APP4, InfiRay, Image] 6 - Fact Def Ta: 300 +[APP4, InfiRay, Image] 8 - Fact Def Tu: 300 +[APP4, InfiRay, Image] 10 - Fact Def Dist: 32 +[APP4, InfiRay, Image] 12 - Fact Def A0: 0 +[APP4, InfiRay, Image] 16 - Fact Def B0: 0 +[APP4, InfiRay, Image] 20 - Fact Def A1: 0 +[APP4, InfiRay, Image] 24 - Fact Def B1: 0 +[APP4, InfiRay, Image] 28 - Fact Def P0: 9178 +[APP4, InfiRay, Image] 32 - Fact Def P1: 621 +[APP4, InfiRay, Image] 36 - Fact Def P2: 65534 +[APP4, InfiRay, Image] 68 - Fact Rel Sensor Temp: 7301 +[APP4, InfiRay, Image] 70 - Fact Rel Shutter Temp: 128 +[APP4, InfiRay, Image] 72 - Fact Rel Lens Temp: 128 +[APP4, InfiRay, Image] 100 - Fact Status Gain: 1 +[APP4, InfiRay, Image] 101 - Fact Status Env OK: 1 +[APP4, InfiRay, Image] 102 - Fact Status Dist OK: 1 +[APP4, InfiRay, Image] 103 - Fact Status Temp Map: 1 +[APP5, InfiRay, Image] 0 - Environment Temp: 25.00 C +[APP5, InfiRay, Image] 4 - Distance: 0.25 m +[APP5, InfiRay, Image] 8 - Emissivity: 0.99 +[APP5, InfiRay, Image] 12 - Humidity: 50.0 % +[APP5, InfiRay, Image] 16 - Reference Temp: 25.00 C +[APP5, InfiRay, Image] 32 - Temp Unit: 0 +[APP5, InfiRay, Image] 33 - Show Center Temp: 1 +[APP5, InfiRay, Image] 34 - Show Max Temp: 1 +[APP5, InfiRay, Image] 35 - Show Min Temp: 1 +[APP5, InfiRay, Image] 36 - Temp Measure Count: 0 +[APP6, InfiRay, Image] 0 - Mix Mode: 0 +[APP6, InfiRay, Image] 1 - Fusion Intensity: 100.0 % +[APP6, InfiRay, Image] 5 - Offset Adjustment: 2 +[APP6, InfiRay, Image] 9 - Correction Asix: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[APP7, InfiRay, Image] 0 - Working Mode: 1 +[APP7, InfiRay, Image] 1 - Integral Time: 400 +[APP7, InfiRay, Image] 5 - Integrat Time Hdr: 500 +[APP7, InfiRay, Image] 9 - Gain Stable: 1 +[APP7, InfiRay, Image] 10 - Temp Control Enable: 1 +[APP7, InfiRay, Image] 11 - Device Temp: 25.00 C +[APP8, InfiRay, Image] 0 - Isothermal Max: 80 +[APP8, InfiRay, Image] 4 - Isothermal Min: -20 +[APP8, InfiRay, Image] 8 - Chroma Bar Max: 80 +[APP8, InfiRay, Image] 12 - Chroma Bar Min: -20 +[APP9, InfiRay, Image] 0 - IR Sensor Manufacturer: infisense +[APP9, InfiRay, Image] 64 - IR Sensor Name: P2_USB_IR +[APP9, InfiRay, Image] 128 - IR Sensor Part Number: P2_B_V2.0_2080100019B15656148 +[APP9, InfiRay, Image] 192 - IR Sensor Serial Number: P200019B15656148 +[APP9, InfiRay, Image] 256 - IR Sensor Firmware: 2.07.01.00 +[APP9, InfiRay, Image] 320 - IR Sensor Aperture: 1.10 +[APP9, InfiRay, Image] 324 - IR Focal Length: 3.20 +[APP9, InfiRay, Image] 384 - Visible Sensor Manufacturer: infisense +[APP9, InfiRay, Image] 448 - Visible Sensor Name: P2_USB_IR +[APP9, InfiRay, Image] 512 - Visible Sensor Part Number: P2_B_V2.0_2080100019B15656148 +[APP9, InfiRay, Image] 576 - Visible Sensor Serial Number: P200019B15656148 +[APP9, InfiRay, Image] 640 - Visible Sensor Firmware: 2.07.01.00 +[APP9, InfiRay, Image] 704 - Visible Sensor Aperture: 1.10000002384186 +[APP9, InfiRay, Image] 708 - Visible Focal Length: 3.20000004768372 +[JFIF, JFIF, Image] 0 - JFIF Version: 1.01 +[JFIF, JFIF, Image] 2 - Resolution Unit: None +[JFIF, JFIF, Image] 3 - X Resolution: 1 +[JFIF, JFIF, Image] 5 - Y Resolution: 1 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Time] GPS-GPSDateTime - GPS Date/Time: 00:00:00Z +[Composite, Composite, Location] GPS-GPSLatitude - GPS Latitude: +[Composite, Composite, Location] GPS-GPSLongitude - GPS Longitude: diff --git a/ExifTool/t/JSON.t b/ExifTool/t/JSON.t new file mode 100644 index 0000000..d0027ec --- /dev/null +++ b/ExifTool/t/JSON.t @@ -0,0 +1,30 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/JSON.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::JSON; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'JSON'; +my $testnum = 1; + +# test 2: Extract information from JSON.json +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Struct => 2); + $exifTool->Options(MissingTagValue => 'null'); + my $info = $exifTool->ImageInfo('t/images/JSON.json'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/JSON_2.out b/ExifTool/t/JSON_2.out new file mode 100644 index 0000000..552c7c1 --- /dev/null +++ b/ExifTool/t/JSON_2.out @@ -0,0 +1,19 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: JSON.json +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 344 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2017:03:14 09:08:39-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JSON +[File, File, Other] FileTypeExtension - File Type Extension: json +[File, File, Other] MIMEType - MIME Type: application/json +[JSON, JSON, Other] description - Description: test description +[JSON, JSON, Other] imageViews - Image Views: 0 +[JSON, JSON, Other] people - People: one, two, three +[JSON, JSON, Other] test - Test: [{thing=[3,4],this=[1,2]},{thing=[7,8],this=[5,6]}] +[JSON, JSON, Other] testThing - Test Thing: 3, 4, 7, 8 +[JSON, JSON, Other] testThis - Test This: 1, 2, 5, 6 +[JSON, JSON, Other] title - Title: 2013-02-02 +[JSON, JSON, Other] url - Url: https://(removed) diff --git a/ExifTool/t/JVC.t b/ExifTool/t/JVC.t new file mode 100644 index 0000000..2b47dcf --- /dev/null +++ b/ExifTool/t/JVC.t @@ -0,0 +1,37 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/JVC.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::JVC; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'JVC'; +my $testnum = 1; + +# test 2: Extract information from EXIF-based JVC maker notes +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/JVC.jpg', { Group1 => 'JVC', Unknown => 1 }); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Extract information from text-based JVC maker notes +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/JVC.jpg', { Group1 => 'JVC', Unknown => 1 }); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/JVC_2.out b/ExifTool/t/JVC_2.out new file mode 100644 index 0000000..873daeb --- /dev/null +++ b/ExifTool/t/JVC_2.out @@ -0,0 +1,3 @@ +[MakerNotes, JVC, Camera] 1 - JVC 0x0001: 2 +[MakerNotes, JVC, Camera] 2 - CPU Versions: CPU1 2.00, 0, CPU2 0496, 0 +[MakerNotes, JVC, Camera] 3 - Quality: Normal diff --git a/ExifTool/t/JVC_3.out b/ExifTool/t/JVC_3.out new file mode 100644 index 0000000..873daeb --- /dev/null +++ b/ExifTool/t/JVC_3.out @@ -0,0 +1,3 @@ +[MakerNotes, JVC, Camera] 1 - JVC 0x0001: 2 +[MakerNotes, JVC, Camera] 2 - CPU Versions: CPU1 2.00, 0, CPU2 0496, 0 +[MakerNotes, JVC, Camera] 3 - Quality: Normal diff --git a/ExifTool/t/JXL.t b/ExifTool/t/JXL.t new file mode 100644 index 0000000..00a3f01 --- /dev/null +++ b/ExifTool/t/JXL.t @@ -0,0 +1,64 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Jpeg2000.t". + +BEGIN { + $| = 1; print "1..4\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Jpeg2000; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'JXL'; +my $testnum = 1; + +# test 2: Extract information from JXL.jxl +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/JXL.jxl', '-system:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jxl"; + unlink $testfile; + my @writeInfo = ( + [ 'XMP:Subject' => 'test subject' ], + [ 'EXIF:Artist' => 'test artist' ], + ); + $exifTool->Options(IgnoreMinorErrors => 1); + $exifTool->SetNewValue(@$_) foreach @writeInfo; + my $ok = writeInfo($exifTool, 't/images/JXL.jxl', $testfile, undef, 1); + my $info = $exifTool->ImageInfo($testfile, '-system:all'); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 4: Read compressed metadata and ImageSize from partial JXL codestream +{ + ++$testnum; + my $skip = ''; + if (eval { require IO::Uncompress::Brotli }) { + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/JXL2.jxl', 'imagesize', 'exif:all', 'xmp:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + } else { + $skip = ' # skip Requires IO::Unompress::Brotli'; + } + print "ok $testnum$skip\n"; +} + +done(); # end diff --git a/ExifTool/t/JXL_2.out b/ExifTool/t/JXL_2.out new file mode 100644 index 0000000..130a997 --- /dev/null +++ b/ExifTool/t/JXL_2.out @@ -0,0 +1,8 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.25 +[File, File, Other] FileType - File Type: JXL Codestream +[File, File, Other] FileTypeExtension - File Type Extension: jxl +[File, File, Other] MIMEType - MIME Type: image/jxl +[File, File, Image] ImageWidth - Image Width: 200 +[File, File, Image] ImageHeight - Image Height: 130 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 200x130 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.026 diff --git a/ExifTool/t/JXL_3.out b/ExifTool/t/JXL_3.out new file mode 100644 index 0000000..6b905fc --- /dev/null +++ b/ExifTool/t/JXL_3.out @@ -0,0 +1,19 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.25 +[File, File, Other] FileType - File Type: JXL +[File, File, Other] FileTypeExtension - File Type Extension: jxl +[File, File, Other] MIMEType - MIME Type: image/jxl +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 200 +[File, File, Image] ImageHeight - Image Height: 130 +[Jpeg2000, Jpeg2000, Video] 0 - Major Brand: JPEG XL Image (.JXL) +[Jpeg2000, Jpeg2000, Video] 1 - Minor Version: 0.0.0 +[Jpeg2000, Jpeg2000, Video] 2 - Compatible Brands: jxl +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: test artist +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.25 +[XMP, XMP-dc, Image] subject - Subject: test subject +[Composite, Composite, Image] Exif-ImageSize - Image Size: 200x130 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.026 diff --git a/ExifTool/t/JXL_4.out b/ExifTool/t/JXL_4.out new file mode 100644 index 0000000..156b9e8 --- /dev/null +++ b/ExifTool/t/JXL_4.out @@ -0,0 +1,8 @@ +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: me +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.63 +[XMP, XMP-dc, Image] subject - Subject: ExifTool test +[Composite, Composite, Image] Exif-ImageSize - Image Size: 200x130 diff --git a/ExifTool/t/Jpeg2000.t b/ExifTool/t/Jpeg2000.t new file mode 100644 index 0000000..d83da5e --- /dev/null +++ b/ExifTool/t/Jpeg2000.t @@ -0,0 +1,61 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Jpeg2000.t". + +BEGIN { + $| = 1; print "1..5\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Jpeg2000; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Jpeg2000'; +my $testnum = 1; + +# test 2: Extract information from Jpeg2000.jp2 +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Jpeg2000.jp2'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + ['IPTC:Keywords' => 'test keyword'], + ['XMP:City' => 'a city'], + ['EXIF:ImageDescription' => 'a description'], + ['XML' => '<test>Yippee</test>', Protected => 1 ], + ['Jpeg2000:ColorSpace' => 'Grayscale', Protected => 1 ], + ['ColorSpecPrecedence' => 1, Protected => 1 ], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/Jpeg2000.jp2'); + print "ok $testnum\n"; +} + +# test 4: Extract information from Jpeg2000.j2c +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Jpeg2000.j2c'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 5: Extract XML as a block from JP2 image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Jpeg2000.jp2','xml'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Jpeg2000_2.out b/ExifTool/t/Jpeg2000_2.out new file mode 100644 index 0000000..0ee4ab0 --- /dev/null +++ b/ExifTool/t/Jpeg2000_2.out @@ -0,0 +1,71 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Jpeg2000.jp2 +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.0 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2016:10:10 15:46:32-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:39-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JP2 +[File, File, Other] FileTypeExtension - File Type Extension: jp2 +[File, File, Other] MIMEType - MIME Type: image/jp2 +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[Jpeg2000, Jpeg2000, Video] 0 - Major Brand: JPEG 2000 Image (.JP2) +[Jpeg2000, Jpeg2000, Video] 1 - Minor Version: 0.0.0 +[Jpeg2000, Jpeg2000, Video] 2 - Compatible Brands: jp2 +[Jpeg2000, Jpeg2000, Image] 0 - Image Height: 16 +[Jpeg2000, Jpeg2000, Image] 4 - Image Width: 16 +[Jpeg2000, Jpeg2000, Image] 8 - Number Of Components: 3 +[Jpeg2000, Jpeg2000, Image] 10 - Bits Per Component: 8 Bits, Unsigned +[Jpeg2000, Jpeg2000, Image] 11 - Compression: JPEG 2000 +[Jpeg2000, Jpeg2000, Image] 0 - Color Spec Method: Enumerated +[Jpeg2000, Jpeg2000, Image] 1 - Color Spec Precedence: 0 +[Jpeg2000, Jpeg2000, Image] 2 - Color Spec Approximation: Not Specified +[Jpeg2000, Jpeg2000, Image] 3 - Color Space: sRGB +[EXIF, IFD0, Image] 256 - Image Width: 1 +[EXIF, IFD0, Image] 257 - Image Height: 1 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 +[EXIF, IFD0, Image] 259 - Compression: Uncompressed +[EXIF, IFD0, Image] 273 - Strip Offsets: 101 +[EXIF, IFD0, Image] 277 - Samples Per Pixel: 1 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 0 +[EXIF, IFD0, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD0, Image] 33550 - Pixel Scale: 1 1 0 +[EXIF, IFD0, Location] 33922 - Model Tie Point: 0 0 0 334159.28 4429865.18 0 +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: me +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[GeoTiff, GeoTiff, Location] 1 - Geo Tiff Version: 1.1.0 +[GeoTiff, GeoTiff, Location] 1024 - GT Model Type: Projected +[GeoTiff, GeoTiff, Location] 1025 - GT Raster Type: Pixel Is Area +[GeoTiff, GeoTiff, Location] 1026 - GT Citation: NAD83 / UTM zone 18N +[GeoTiff, GeoTiff, Location] 3072 - Projected CS Type: NAD83 UTM zone 18N +[GeoTiff, GeoTiff, Location] 3076 - Proj Linear Units: Linear Meter +[XML, XML, Unknown] imageCreationXmlns - Image Creation Xmlns: http://www.jpeg.org/jpx/1.0/xml +[XML, XML-xsi, Unknown] xsi:imageCreationSchemaLocation - Image Creation Schema Location: http://www.jpeg.org/jpx/1.0/xml;http://www.jpeg.org/metadata/15444-2.xsd +[XML, XML, Unknown] imageCreationGeneralCreationInfoCreationTime - Image Creation General Creation Info Creation Time: 2001:08:01 15:40:00.000-06:00 +[XML, XML, Unknown] contentDescriptionXmlns - Content Description Xmlns: http://www.jpeg.org/jpx/1.0/xml +[XML, XML-xsi, Unknown] xsi:contentDescriptionSchemaLocation - Content Description Schema Location: http://www.jpeg.org/jpx/1.0/xml;http://www.jpeg.org/metadata/15444-2.xsd +[XML, XML, Unknown] contentDescriptionCaption - Content Description Caption: Wide Dynamic Range Scene +[XML, XML, Unknown] contentDescriptionLocationAddressType - Content Description Location Address Type: Test Scene Location +[XML, XML, Unknown] contentDescriptionLocationAddressAddrCompType - Content Description Location Address Addr Comp Type: Street +[XML, XML, Unknown] contentDescriptionLocationAddressAddrComp - Content Description Location Address Addr Comp: 70 Lighthouse St +[XML, XML, Unknown] contentDescriptionLocationAddressAddrCompType - Content Description Location Address Addr Comp Type: City +[XML, XML, Unknown] contentDescriptionLocationAddressAddrComp - Content Description Location Address Addr Comp: Charlotte +[XML, XML, Unknown] contentDescriptionLocationAddressAddrCompType - Content Description Location Address Addr Comp Type: State +[XML, XML, Unknown] contentDescriptionLocationAddressAddrComp - Content Description Location Address Addr Comp: New York +[XML, XML, Unknown] contentDescriptionLocationAddressZipcode - Content Description Location Address Zipcode: 14612 +[XML, XML, Unknown] contentDescriptionLocationAddressCountry - Content Description Location Address Country: US +[XML, XML, Unknown] contentDescriptionThingId - Content Description Thing Id: 5431 +[XML, XML, Unknown] contentDescriptionThingName - Content Description Thing Name: Macbeth chart +[XML, XML, Unknown] contentDescriptionThingPositionRectX - Content Description Thing Position Rect X: 562 +[XML, XML, Unknown] contentDescriptionThingPositionRectY - Content Description Thing Position Rect Y: 286 +[XML, XML, Unknown] contentDescriptionThingPositionRectWidth - Content Description Thing Position Rect Width: 59 +[XML, XML, Unknown] contentDescriptionThingPositionRectHeight - Content Description Thing Position Rect Height: 39 +[XML, XML, Unknown] contentDescriptionEventEventType - Content Description Event Event Type: Capture Device Test 1234 +[XML, XML, Unknown] contentDescriptionEventDescription - Content Description Event Description: Project Prototype +[Composite, Composite, Image] Exif-ImageSize - Image Size: 16x16 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000256 diff --git a/ExifTool/t/Jpeg2000_3.out b/ExifTool/t/Jpeg2000_3.out new file mode 100644 index 0000000..407ba3e --- /dev/null +++ b/ExifTool/t/Jpeg2000_3.out @@ -0,0 +1,54 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Jpeg2000_3_failed.jp2 +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.7 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:40-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:40-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:40-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JP2 +[File, File, Other] FileTypeExtension - File Type Extension: jp2 +[File, File, Other] MIMEType - MIME Type: image/jp2 +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[Jpeg2000, Jpeg2000, Video] 0 - Major Brand: JPEG 2000 Image (.JP2) +[Jpeg2000, Jpeg2000, Video] 1 - Minor Version: 0.0.0 +[Jpeg2000, Jpeg2000, Video] 2 - Compatible Brands: jp2 +[Jpeg2000, Jpeg2000, Image] 0 - Image Height: 16 +[Jpeg2000, Jpeg2000, Image] 4 - Image Width: 16 +[Jpeg2000, Jpeg2000, Image] 8 - Number Of Components: 3 +[Jpeg2000, Jpeg2000, Image] 10 - Bits Per Component: 8 Bits, Unsigned +[Jpeg2000, Jpeg2000, Image] 11 - Compression: JPEG 2000 +[Jpeg2000, Jpeg2000, Image] 0 - Color Spec Method: Enumerated +[Jpeg2000, Jpeg2000, Image] 1 - Color Spec Precedence: 1 +[Jpeg2000, Jpeg2000, Image] 2 - Color Spec Approximation: Not Specified +[Jpeg2000, Jpeg2000, Image] 3 - Color Space: Grayscale +[EXIF, IFD0, Image] 256 - Image Width: 1 +[EXIF, IFD0, Image] 257 - Image Height: 1 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 +[EXIF, IFD0, Image] 259 - Compression: Uncompressed +[EXIF, IFD0, Image] 273 - Strip Offsets: 101 +[EXIF, IFD0, Image] 277 - Samples Per Pixel: 1 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 0 +[EXIF, IFD0, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD0, Image] 33550 - Pixel Scale: 1 1 0 +[EXIF, IFD0, Location] 33922 - Model Tie Point: 0 0 0 334159.28 4429865.18 0 +[EXIF, IFD0, Image] 270 - Image Description: a description +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: me +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[GeoTiff, GeoTiff, Location] 1 - Geo Tiff Version: 1.1.0 +[GeoTiff, GeoTiff, Location] 1024 - GT Model Type: Projected +[GeoTiff, GeoTiff, Location] 1025 - GT Raster Type: Pixel Is Area +[GeoTiff, GeoTiff, Location] 1026 - GT Citation: NAD83 / UTM zone 18N +[GeoTiff, GeoTiff, Location] 3072 - Projected CS Type: NAD83 UTM zone 18N +[GeoTiff, GeoTiff, Location] 3076 - Proj Linear Units: Linear Meter +[XML, XML, Unknown] test - Test: Yippee +[IPTC, IPTC, Other] 25 - Keywords: test keyword +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-photoshop, Location] City - City: a city +[Composite, Composite, Image] Exif-ImageSize - Image Size: 16x16 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000256 diff --git a/ExifTool/t/Jpeg2000_4.out b/ExifTool/t/Jpeg2000_4.out new file mode 100644 index 0000000..f843edb --- /dev/null +++ b/ExifTool/t/Jpeg2000_4.out @@ -0,0 +1,17 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Jpeg2000.j2c +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 618 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2011:08:15 11:40:57-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:09-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: J2C +[File, File, Other] FileTypeExtension - File Type Extension: j2c +[File, File, Other] MIMEType - MIME Type: image/x-j2c +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] Comment - Comment: Kakadu-v4.2.1 +[File, File, Image] Comment - Comment: Kdu-Layer-Info: log_2{Delta-D(MSE)/[2^16*Delta-L(bytes)]}, L(bytes); -0.0, 4.2e+02; -0.0, 4.3e+02; -0.0, 4.5e+02; -0.0, 4.7e+02; -0.0, 4.9e+02; -0.0, 5.0e+02; -0.0, 5.2e+02; -0.0, 5.4e+02; -0.0, 5.6e+02; -0.0, 5.8e+02; -0.0, 6.0e+02;-256.0, 6.2e+02; +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Jpeg2000_5.out b/ExifTool/t/Jpeg2000_5.out new file mode 100644 index 0000000..98478f2 --- /dev/null +++ b/ExifTool/t/Jpeg2000_5.out @@ -0,0 +1,2 @@ +[Jpeg2000, Jpeg2000, Image] xml - XML: (Binary data 377 bytes) +[Jpeg2000, Jpeg2000, Image] xml - XML: (Binary data 902 bytes) diff --git a/ExifTool/t/Kodak.t b/ExifTool/t/Kodak.t new file mode 100644 index 0000000..1f29cf3 --- /dev/null +++ b/ExifTool/t/Kodak.t @@ -0,0 +1,40 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Kodak.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Kodak; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Kodak'; +my $testnum = 1; + +# test 2: Extract information from Kodak.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Kodak.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + [YearCreated => '2005', Group => 'Kodak'], + [MonthDayCreated => '03:31', Group => 'Kodak'], + [DigitalZoom => '2'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Kodak_2.out b/ExifTool/t/Kodak_2.out new file mode 100644 index 0000000..f65fa34 --- /dev/null +++ b/ExifTool/t/Kodak_2.out @@ -0,0 +1,111 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Kodak.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 3.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:06:05 13:12:56-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:40-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: EASTMAN KODAK COMPANY +[EXIF, IFD0, Camera] 272 - Camera Model Name: KODAK DX4900 ZOOM DIGITAL CAMERA +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 230 +[EXIF, IFD0, Image] 283 - Y Resolution: 230 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/180 +[EXIF, ExifIFD, Image] 33437 - F Number: 6.7 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2002:05:01 10:22:28 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2002:05:01 10:22:28 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/181 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 6.7 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.1 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 10.2 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2448 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1632 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41493 - Exposure Index: 100 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1664 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Kodak, Camera] 0 - Kodak Model: DX4900 +[MakerNotes, Kodak, Camera] 9 - Quality: Fine +[MakerNotes, Kodak, Camera] 10 - Burst Mode: Off +[MakerNotes, Kodak, Camera] 12 - Kodak Image Width: 2448 +[MakerNotes, Kodak, Camera] 14 - Kodak Image Height: 1632 +[MakerNotes, Kodak, Time] 16 - Year Created: 2002 +[MakerNotes, Kodak, Time] 18 - Month Day Created: 05:01 +[MakerNotes, Kodak, Time] 20 - Time Created: 10:22:28.62 +[MakerNotes, Kodak, Camera] 27 - Shutter Mode: Auto +[MakerNotes, Kodak, Camera] 28 - Metering Mode: Multi-segment +[MakerNotes, Kodak, Camera] 29 - Sequence Number: 0 +[MakerNotes, Kodak, Camera] 30 - F Number: 6.73 +[MakerNotes, Kodak, Camera] 32 - Exposure Time: 1/216 +[MakerNotes, Kodak, Camera] 36 - Exposure Compensation: 0 +[MakerNotes, Kodak, Camera] 56 - Focus Mode: Normal +[MakerNotes, Kodak, Camera] 64 - White Balance: Auto +[MakerNotes, Kodak, Camera] 92 - Flash Mode: Auto +[MakerNotes, Kodak, Camera] 93 - Flash Fired: No +[MakerNotes, Kodak, Camera] 94 - ISO Setting: Auto +[MakerNotes, Kodak, Camera] 96 - ISO: 108 +[MakerNotes, Kodak, Camera] 98 - Total Zoom: 1.4 +[MakerNotes, Kodak, Camera] 100 - Date Time Stamp: Off +[MakerNotes, Kodak, Camera] 102 - Color Mode: Saturated Color +[MakerNotes, Kodak, Camera] 104 - Digital Zoom: 1 +[MakerNotes, Kodak, Camera] 107 - Sharpness: Normal +[FlashPix, FlashPix, Other] 1 - Code Page: Unicode UTF-16, little endian +[FlashPix, FlashPix, Other] 268435456 - Used Extension Numbers: 1, 2 +[FlashPix, FlashPix, Other] 1 - Extension Name: Screen nail +[FlashPix, FlashPix, Other] 2 - Extension Class ID: 10000230-6FC0-11D0-BD01-00609719A180 +[FlashPix, FlashPix, Other] 3 - Extension Persistence: Invalidated By Modification +[FlashPix, FlashPix, Time] 4 - Extension Create Date: 1998:01:08 08:28:54 +[FlashPix, FlashPix, Time] 5 - Extension Modify Date: 1998:01:08 08:28:54 +[FlashPix, FlashPix, Other] 6 - Creating Application: Digita +[FlashPix, FlashPix, Other] 7 - Extension Description: Presized image for LCD display +[FlashPix, FlashPix, Other] 4096 - Storage-Stream Pathname: /.Screen Nail_bd0100609719a180 +[FlashPix, FlashPix, Other] 1 - Extension Name: Audio +[FlashPix, FlashPix, Other] 2 - Extension Class ID: 10000100-6FC0-11D0-BD01-00609719A180 +[FlashPix, FlashPix, Other] 3 - Extension Persistence: Always Valid +[FlashPix, FlashPix, Time] 4 - Extension Create Date: 1998:12:17 22:04:36 +[FlashPix, FlashPix, Time] 5 - Extension Modify Date: 1998:12:17 22:04:36 +[FlashPix, FlashPix, Other] 6 - Creating Application: KIES_Toolkit +[FlashPix, FlashPix, Other] 7 - Extension Description: Audio storage +[FlashPix, FlashPix, Other] 4096 - Storage-Stream Pathname: /Audio_bd0100609719a180 +[FlashPix, FlashPix, Audio] Audio Stream - Audio Stream: (Binary data 18 bytes) +[FlashPix, FlashPix, Other] 1 - Code Page: Unicode UTF-16, little endian +[FlashPix, FlashPix, Other] Screen Nail - Screen Nail: (Binary data 166 bytes) +[Composite, Composite, Image] Exif-Aperture - Aperture: 6.7 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/216 +[Composite, Composite, Time] Kodak-DateCreated - Date Created: 2002:05:01 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 10.2 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 13.1 diff --git a/ExifTool/t/Kodak_3.out b/ExifTool/t/Kodak_3.out new file mode 100644 index 0000000..855e3a0 --- /dev/null +++ b/ExifTool/t/Kodak_3.out @@ -0,0 +1,121 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Kodak_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:40-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:40-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:40-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: EASTMAN KODAK COMPANY +[EXIF, IFD0, Camera] 272 - Camera Model Name: KODAK DX4900 ZOOM DIGITAL CAMERA +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 230 +[EXIF, IFD0, Image] 283 - Y Resolution: 230 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/180 +[EXIF, ExifIFD, Image] 33437 - F Number: 6.7 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2002:05:01 10:22:28 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2002:05:01 10:22:28 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/181 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 6.7 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.1 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 10.2 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2448 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1632 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41493 - Exposure Index: 100 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1664 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Kodak, Camera] 0 - Kodak Model: DX4900 +[MakerNotes, Kodak, Camera] 9 - Quality: Fine +[MakerNotes, Kodak, Camera] 10 - Burst Mode: Off +[MakerNotes, Kodak, Camera] 12 - Kodak Image Width: 2448 +[MakerNotes, Kodak, Camera] 14 - Kodak Image Height: 1632 +[MakerNotes, Kodak, Time] 16 - Year Created: 2005 +[MakerNotes, Kodak, Time] 18 - Month Day Created: 03:31 +[MakerNotes, Kodak, Time] 20 - Time Created: 10:22:28.62 +[MakerNotes, Kodak, Camera] 24 - Burst Mode 2: 0 +[MakerNotes, Kodak, Camera] 27 - Shutter Mode: Auto +[MakerNotes, Kodak, Camera] 28 - Metering Mode: Multi-segment +[MakerNotes, Kodak, Camera] 29 - Sequence Number: 0 +[MakerNotes, Kodak, Camera] 30 - F Number: 6.73 +[MakerNotes, Kodak, Camera] 32 - Exposure Time: 1/216 +[MakerNotes, Kodak, Camera] 36 - Exposure Compensation: 0 +[MakerNotes, Kodak, Camera] 38 - Various Modes: 65535 +[MakerNotes, Kodak, Camera] 40 - Distance 1: 13125 +[MakerNotes, Kodak, Camera] 44 - Distance 2: 13125 +[MakerNotes, Kodak, Camera] 48 - Distance 3: 12125 +[MakerNotes, Kodak, Camera] 52 - Distance 4: 12250 +[MakerNotes, Kodak, Camera] 56 - Focus Mode: Normal +[MakerNotes, Kodak, Camera] 58 - Various Modes 2: 65535 +[MakerNotes, Kodak, Camera] 60 - Panorama Mode: 0 +[MakerNotes, Kodak, Camera] 62 - Subject Distance: 0 +[MakerNotes, Kodak, Camera] 64 - White Balance: Auto +[MakerNotes, Kodak, Camera] 92 - Flash Mode: Auto +[MakerNotes, Kodak, Camera] 93 - Flash Fired: No +[MakerNotes, Kodak, Camera] 94 - ISO Setting: Auto +[MakerNotes, Kodak, Camera] 96 - ISO: 108 +[MakerNotes, Kodak, Camera] 98 - Total Zoom: 1.4 +[MakerNotes, Kodak, Camera] 100 - Date Time Stamp: Off +[MakerNotes, Kodak, Camera] 102 - Color Mode: Saturated Color +[MakerNotes, Kodak, Camera] 104 - Digital Zoom: 2 +[MakerNotes, Kodak, Camera] 107 - Sharpness: Normal +[FlashPix, FlashPix, Other] 1 - Code Page: Unicode UTF-16, little endian +[FlashPix, FlashPix, Other] 268435456 - Used Extension Numbers: 1, 2 +[FlashPix, FlashPix, Other] 1 - Extension Name: Screen nail +[FlashPix, FlashPix, Other] 2 - Extension Class ID: 10000230-6FC0-11D0-BD01-00609719A180 +[FlashPix, FlashPix, Other] 3 - Extension Persistence: Invalidated By Modification +[FlashPix, FlashPix, Time] 4 - Extension Create Date: 1998:01:08 08:28:54 +[FlashPix, FlashPix, Time] 5 - Extension Modify Date: 1998:01:08 08:28:54 +[FlashPix, FlashPix, Other] 6 - Creating Application: Digita +[FlashPix, FlashPix, Other] 7 - Extension Description: Presized image for LCD display +[FlashPix, FlashPix, Other] 4096 - Storage-Stream Pathname: /.Screen Nail_bd0100609719a180 +[FlashPix, FlashPix, Other] 1 - Extension Name: Audio +[FlashPix, FlashPix, Other] 2 - Extension Class ID: 10000100-6FC0-11D0-BD01-00609719A180 +[FlashPix, FlashPix, Other] 3 - Extension Persistence: Always Valid +[FlashPix, FlashPix, Time] 4 - Extension Create Date: 1998:12:17 22:04:36 +[FlashPix, FlashPix, Time] 5 - Extension Modify Date: 1998:12:17 22:04:36 +[FlashPix, FlashPix, Other] 6 - Creating Application: KIES_Toolkit +[FlashPix, FlashPix, Other] 7 - Extension Description: Audio storage +[FlashPix, FlashPix, Other] 4096 - Storage-Stream Pathname: /Audio_bd0100609719a180 +[FlashPix, FlashPix, Audio] Audio Stream - Audio Stream: (Binary data 18 bytes) +[FlashPix, FlashPix, Other] 1 - Code Page: Unicode UTF-16, little endian +[FlashPix, FlashPix, Audio] 2 - Flash Pix Audio Info 0x0002: 0 +[FlashPix, FlashPix, Other] Screen Nail - Screen Nail: (Binary data 166 bytes) +[Composite, Composite, Image] Exif-Aperture - Aperture: 6.7 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/216 +[Composite, Composite, Time] Kodak-DateCreated - Date Created: 2005:03:31 +[Composite, Composite, Image] Exif-LightValue - Light Value: 13.1 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 10.2 mm diff --git a/ExifTool/t/KyoceraRaw.t b/ExifTool/t/KyoceraRaw.t new file mode 100644 index 0000000..3449c2f --- /dev/null +++ b/ExifTool/t/KyoceraRaw.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/KyoceraRaw.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::KyoceraRaw; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'KyoceraRaw'; +my $testnum = 1; + +# test 2: Extract information from KyoceraRaw.raw +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/KyoceraRaw.raw'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/KyoceraRaw_2.out b/ExifTool/t/KyoceraRaw_2.out new file mode 100644 index 0000000..2fc9b50 --- /dev/null +++ b/ExifTool/t/KyoceraRaw_2.out @@ -0,0 +1,28 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: KyoceraRaw.raw +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 166 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:02:17 11:59:53-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:10-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: RAW +[File, File, Other] FileTypeExtension - File Type Extension: raw +[File, File, Other] MIMEType - MIME Type: image/x-raw +[MakerNotes, KyoceraRaw, Camera] 1 - Firmware Version: Ver. 1.07 +[MakerNotes, KyoceraRaw, Camera] 12 - Model: N DIGITAL +[MakerNotes, KyoceraRaw, Camera] 25 - Make: KYOCERA +[MakerNotes, KyoceraRaw, Time] 33 - Date/Time Original: 2005:07:16 18:14:30 +[MakerNotes, KyoceraRaw, Image] 52 - ISO: 100 +[MakerNotes, KyoceraRaw, Image] 56 - Exposure Time: 1/125 +[MakerNotes, KyoceraRaw, Image] 60 - WB RGGB Levels: 84 64 64 86 +[MakerNotes, KyoceraRaw, Image] 88 - F Number: 11 +[MakerNotes, KyoceraRaw, Camera] 104 - Max Aperture: 5.2 +[MakerNotes, KyoceraRaw, Camera] 112 - Focal Length: 55 mm +[MakerNotes, KyoceraRaw, Camera] 124 - Lens: VS28-80/3.5 +[Composite, Composite, Image] Exif-Aperture - Aperture: 11.3 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.34375 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.3125 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/125 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 55.0 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.0 diff --git a/ExifTool/t/LNK.t b/ExifTool/t/LNK.t new file mode 100644 index 0000000..93d1cff --- /dev/null +++ b/ExifTool/t/LNK.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/LNK.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::LNK; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'LNK'; +my $testnum = 1; + +# test 2: Extract information from LNK file +{ + my $exifTool = Image::ExifTool->new; + ++$testnum; + my $info = $exifTool->ImageInfo('t/images/LNK.lnk'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/LNK_2.out b/ExifTool/t/LNK_2.out new file mode 100644 index 0000000..9a602c8 --- /dev/null +++ b/ExifTool/t/LNK_2.out @@ -0,0 +1,47 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.43 +[File, System, Other] FileName - File Name: LNK.lnk +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 876 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2009:09:19 23:38:35-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:21 16:17:22-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rwxr-xr-x +[File, File, Other] FileType - File Type: LNK +[File, File, Other] FileTypeExtension - File Type Extension: lnk +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[LNK, LNK, Other] 20 - Flags: IDList, LinkInfo, Description, RelativePath, WorkingDir, CommandArgs, Unicode +[LNK, LNK, Other] 24 - File Attributes: Normal +[LNK, LNK, Time] 28 - Create Date: 2009:09:05 06:18:49-04:00 +[LNK, LNK, Time] 36 - Access Date: 2009:09:19 22:42:55-04:00 +[LNK, LNK, Time] 44 - Modify Date: 2009:09:11 06:38:00-04:00 +[LNK, LNK, Other] 52 - Target File Size: 3988946 +[LNK, LNK, Other] 56 - Icon Index: (none) +[LNK, LNK, Other] 60 - Run Window: Normal +[LNK, LNK, Other] 64 - Hot Key: (none) +[LNK, LNK, Other] 14 - Target File DOS Name: EXIFTO~2.EXE +[LNK, LNK, Other] DriveType - Drive Type: Fixed Disk +[LNK, LNK, Other] DriveSerialNumber - Drive Serial Number: C8F0-D326 +[LNK, LNK, Other] VolumeLabel - Volume Label: +[LNK, LNK, Other] LocalBasePath - Local Base Path: C:\Documents and Settings\Phil\Desktop\exiftool(-k).exe +[LNK, LNK, Other] 196612 - Description: Rename file name with image date and time +[LNK, LNK, Other] 196616 - Relative Path: .\exiftool(-k).exe +[LNK, LNK, Other] 196624 - Working Directory: C:\Documents and Settings\Phil\Desktop +[LNK, LNK, Other] 196640 - Command Line Arguments: -d %Y%m%d-%H%M%S.%%e "-filename<datetimeoriginal" -v +[LNK, LNK, Other] 16 - Machine ID: yukkypc +[LNK, LNK, Other] 8 - Fill Attributes: 0x07 +[LNK, LNK, Other] 10 - Popup Fill Attributes: 0xf5 +[LNK, LNK, Other] 12 - Screen Buffer Size: 80 x 500 +[LNK, LNK, Other] 16 - Window Size: 80 x 25 +[LNK, LNK, Other] 20 - Window Origin: 135 x 80 +[LNK, LNK, Other] 32 - Font Size: 10 x 20 +[LNK, LNK, Other] 36 - Font Family: Modern +[LNK, LNK, Other] 40 - Font Weight: 400 +[LNK, LNK, Other] 44 - Font Name: 8514oem +[LNK, LNK, Other] 108 - Cursor Size: 25 +[LNK, LNK, Other] 112 - Full Screen: No +[LNK, LNK, Other] 116 - Quick Edit: No +[LNK, LNK, Other] 120 - Insert Mode: Yes +[LNK, LNK, Other] 124 - Window Origin Auto: Yes +[LNK, LNK, Other] 128 - History Buffer Size: 50 +[LNK, LNK, Other] 132 - Num History Buffers: 4 +[LNK, LNK, Other] 136 - Remove History Duplicates: No diff --git a/ExifTool/t/Lang.t b/ExifTool/t/Lang.t new file mode 100644 index 0000000..c3ec714 --- /dev/null +++ b/ExifTool/t/Lang.t @@ -0,0 +1,44 @@ +# Before `make install' is performed this script should be runnable with +# `make test'. After `make install' it should work as `perl t/Lang.t' + +BEGIN { + $| = 1; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} + +use Image::ExifTool; + +print "1..", scalar(@Image::ExifTool::langs), "\n"; + +my $testname = 'Lang'; +my $testnum = 0; + +# test 1: Test localized language description for a lang-alt tag +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Lang => 'de'); + my $info = $exifTool->ImageInfo('t/images/MIE.mie', 'Comment-fr_FR'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# tests 2-N: Test all languages +my $exifTool = Image::ExifTool->new; +my $lang; +foreach $lang (@Image::ExifTool::langs) { + next if $lang eq 'en'; # skip english + ++$testnum; + my $not = 'not '; + $exifTool->Options(Lang => $lang); + if ($exifTool->Options('Lang') eq $lang) { + my $info = $exifTool->ImageInfo('t/images/FujiFilm.jpg', 'Exif:All'); + $not = '' if check($exifTool, $info, $testname, $testnum); + } else { + warn "\n Error loading language $lang\n"; + } + notOK() if $not; + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Lang_1.out b/ExifTool/t/Lang_1.out new file mode 100644 index 0000000..74d1b53 --- /dev/null +++ b/ExifTool/t/Lang_1.out @@ -0,0 +1 @@ +[MIE, MIE-Doc, Document] Comment-fr_FR - Kommentar (fr_FR): Je peux manger du verre, ça ne me fait pas de mal. diff --git a/ExifTool/t/Lang_10.out b/ExifTool/t/Lang_10.out new file mode 100644 index 0000000..0386eb2 --- /dev/null +++ b/ExifTool/t/Lang_10.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - メーカー: FUJIFILM +[EXIF, IFD0, Camera] 272 - ç”»åƒå…¥åŠ›æ©Ÿå™¨ãƒ¢ãƒ‡ãƒ«: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - ç”»åƒã®å‘ã: 水平(標準) +[EXIF, IFD0, Image] 282 - ç”»åƒå¹…ã®è§£åƒåº¦: 72 +[EXIF, IFD0, Image] 283 - ç”»åƒé«˜ã•ã®è§£åƒåº¦: 72 +[EXIF, IFD0, Image] 296 - Xã¨Yè§£åƒåº¦å˜ä½: インム+[EXIF, IFD0, Image] 305 - ソフトウェア: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - ãƒ•ã‚¡ã‚¤ãƒ«ä½œæˆæ—¥æ™‚: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Yã¨Cã®ä½ç½®: 相互é…ç½® +[EXIF, IFD0, Author] 33432 - 版権所有者: +[EXIF, ExifIFD, Image] 33437 - F値: 3.5 +[EXIF, ExifIFD, Camera] 34850 - 露出プログラム: ノーマルプログラム +[EXIF, ExifIFD, Image] 34855 - ISOスピードレート: 100 +[EXIF, ExifIFD, Image] 36864 - Exifãƒãƒ¼ã‚¸ãƒ§ãƒ³: 0210 +[EXIF, ExifIFD, Time] 36867 - ã‚ªãƒªã‚¸ãƒŠãƒ«ãƒ‡ãƒ¼ã‚¿ä½œæˆæ—¥æ™‚: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - ãƒ‡ã‚¸ã‚¿ãƒ«ãƒ‡ãƒ¼ã‚¿ä½œæˆæ—¥æ™‚: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - 儿§‹æˆè¦ç´ ã®æ„味: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - ç”»åƒåœ§ç¸®ãƒ¢ãƒ¼ãƒ‰: 1.6 +[EXIF, ExifIFD, Image] 37377 - シャッタースピード: 1/64 +[EXIF, ExifIFD, Image] 37378 - 絞り: 3.5 +[EXIF, ExifIFD, Image] 37379 - ブライトãƒã‚¹: 2 +[EXIF, ExifIFD, Image] 37380 - 露出補正値: 0 +[EXIF, ExifIFD, Camera] 37381 - 最大レンズå£å¾„: 3.5 +[EXIF, ExifIFD, Camera] 37383 - 測光モード: パターン +[EXIF, ExifIFD, Camera] 37385 - ストロボ: フラッシュ発光 +[EXIF, ExifIFD, Camera] 37386 - レンズ焦点è·é›¢: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - サãƒãƒ¼ãƒˆãƒ•ラッシュピックスãƒãƒ¼ã‚¸ãƒ§ãƒ³: 0100 +[EXIF, ExifIFD, Image] 40961 - 色空間: sRGB +[EXIF, ExifIFD, Image] 40962 - ç”»åƒå¹…: 1600 +[EXIF, ExifIFD, Image] 40963 - ç”»åƒé«˜ã•: 1200 +[EXIF, InteropIFD, Image] 1 - インターオペラビリティID: R98: DCF基本ファイル(sRGB) +[EXIF, InteropIFD, Image] 2 - インターオペラビリティãƒãƒ¼ã‚¸ãƒ§ãƒ³: 0100 +[EXIF, ExifIFD, Camera] 41486 - 焦点é¢Xè§£åƒåº¦: 3053 +[EXIF, ExifIFD, Camera] 41487 - 焦点é¢Yè§£åƒåº¦: 3053 +[EXIF, ExifIFD, Camera] 41488 - 焦点é¢è§£åƒåº¦å˜ä½: cm +[EXIF, ExifIFD, Camera] 41495 - センサー方å¼: å˜æ¿å¼ã‚«ãƒ©ãƒ¼ã‚»ãƒ³ã‚µãƒ¼ +[EXIF, ExifIFD, Image] 41728 - ファイルソース: デジタルカメラ +[EXIF, ExifIFD, Image] 41729 - シーンタイプ: ç›´æŽ¥æ’®å½±ç”»åƒ +[EXIF, IFD1, Image] 259 - 圧縮計画: JPEG (å¤ã„å½¢å¼) +[EXIF, IFD1, Image] 274 - ç”»åƒã®å‘ã: 水平(標準) +[EXIF, IFD1, Image] 282 - ç”»åƒå¹…ã®è§£åƒåº¦: 72 +[EXIF, IFD1, Image] 283 - ç”»åƒé«˜ã•ã®è§£åƒåº¦: 72 +[EXIF, IFD1, Image] 296 - Xã¨Yè§£åƒåº¦å˜ä½: インム+[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Yã¨Cã®ä½ç½®: 相互é…ç½® +[EXIF, IFD1, Preview] Exif-ThumbnailImage - サムãƒã‚¤ãƒ«ç”»åƒ: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_11.out b/ExifTool/t/Lang_11.out new file mode 100644 index 0000000..30b0dfa --- /dev/null +++ b/ExifTool/t/Lang_11.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - ë©”ì´ì»¤: FUJIFILM +[EXIF, IFD0, Camera] 272 - ì¹´ë©”ë¼ ëª¨ë¸: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - ì´ë¯¸ì§€ 위치: 0° (위쪽/좌측) +[EXIF, IFD0, Image] 282 - ìˆ˜í‰ í•´ìƒë„: 72 +[EXIF, IFD0, Image] 283 - ìˆ˜ì§ í•´ìƒë„: 72 +[EXIF, IFD0, Image] 296 - X 와 Y í•´ìƒë„ 단위: ì¸ì¹˜ +[EXIF, IFD0, Image] 305 - 소프트웨어: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - íŒŒì¼ ë³€ê²½ ë‚ ì§œ ë° ì‹œê°„: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y and C 위치: 주변 +[EXIF, IFD0, Author] 33432 - 저작권 소유ìž: +[EXIF, ExifIFD, Image] 33437 - F 숫ìž: 3.5 +[EXIF, ExifIFD, Camera] 34850 - 노출 프로그램: 보통 프로그램 +[EXIF, ExifIFD, Image] 34855 - ISO ì†ë„: 100 +[EXIF, ExifIFD, Image] 36864 - Exif 버전: 0210 +[EXIF, ExifIFD, Time] 36867 - ì›ë³¸ ë°ì´í„° ìƒì„± ì¼ì‹œ: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - 디지털 ë°ì´í„° ìƒì„± ì¼ì‹œ: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - ê° êµ¬ì„± ìš”ì†Œì˜ ì˜ë¯¸: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - ì´ë¯¸ì§€ ì••ì¶• 모드: 1.6 +[EXIF, ExifIFD, Image] 37377 - 셔터 ì†ë„: 1/64 +[EXIF, ExifIFD, Image] 37378 - 조리개: 3.5 +[EXIF, ExifIFD, Image] 37379 - ë°ê¸°: 2 +[EXIF, ExifIFD, Image] 37380 - 노출 ë³´ì •: 0 +[EXIF, ExifIFD, Camera] 37381 - 최대 렌즈 조리개: 3.5 +[EXIF, ExifIFD, Camera] 37383 - 측광 모드: 멀티 패턴 +[EXIF, ExifIFD, Camera] 37385 - 플래시: 플래시 발광 +[EXIF, ExifIFD, Camera] 37386 - ì´ˆì  ê¸¸ì´: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - ì§€ì›ë˜ëŠ” Flashpix 버전: 0100 +[EXIF, ExifIFD, Image] 40961 - 색공간: sRGB +[EXIF, ExifIFD, Image] 40962 - ì´ë¯¸ì§€ ë„“ì´: 1600 +[EXIF, ExifIFD, Image] 40963 - ì´ë¯¸ì§€ 높ì´: 1200 +[EXIF, InteropIFD, Image] 1 - ìƒí˜¸ìš´ìš©ì„± ì¦ëª…: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - ìƒí˜¸ 운용성 버전: 0100 +[EXIF, ExifIFD, Camera] 41486 - ìˆ˜í‰ í•´ìƒë„ ì´ˆì ë©´: 3053 +[EXIF, ExifIFD, Camera] 41487 - ìˆ˜ì§ í•´ìƒë„ ì´ˆì ë©´: 3053 +[EXIF, ExifIFD, Camera] 41488 - ì´ˆì ë©´ í•´ìƒë„ 단위: cm +[EXIF, ExifIFD, Camera] 41495 - 검출 ë°©ì‹: One-chip ìƒ‰ìƒ ì˜ì—­ 센서 +[EXIF, ExifIFD, Image] 41728 - íŒŒì¼ ì¶œì²˜: 디지털 ì¹´ë©”ë¼ +[EXIF, ExifIFD, Image] 41729 - 장면 형ì‹: ì§ì ‘ ì´¬ì˜ëœ ì´ë¯¸ì§€ +[EXIF, IFD1, Image] 259 - ì••ì¶• 설계: JPEG (예전 스타ì¼) +[EXIF, IFD1, Image] 274 - ì´ë¯¸ì§€ 위치: 0° (위쪽/좌측) +[EXIF, IFD1, Image] 282 - ìˆ˜í‰ í•´ìƒë„: 72 +[EXIF, IFD1, Image] 283 - ìˆ˜ì§ í•´ìƒë„: 72 +[EXIF, IFD1, Image] 296 - X 와 Y í•´ìƒë„ 단위: ì¸ì¹˜ +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y and C 위치: 주변 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - 축소 그림: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_12.out b/ExifTool/t/Lang_12.out new file mode 100644 index 0000000..a079d05 --- /dev/null +++ b/ExifTool/t/Lang_12.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Fabrikant: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera model: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Oriëntatie van de afbeelding: 0° (boven/links) +[EXIF, IFD0, Image] 282 - Horizontale afbeelding resolutie: 72 +[EXIF, IFD0, Image] 283 - Vertikale afbeelding resolutie: 72 +[EXIF, IFD0, Image] 296 - Eenheid van de X und Y resolutie: inches +[EXIF, IFD0, Image] 305 - Gebruikte software: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Datum bestand wijziging: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y en C positie: Naast liggend +[EXIF, IFD0, Author] 33432 - Copyright houder: +[EXIF, ExifIFD, Image] 33437 - F waarde: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Belichtingsprogramma: Normaal programma +[EXIF, ExifIFD, Image] 34855 - ISO gevoeligheid: 100 +[EXIF, ExifIFD, Image] 36864 - Exif versie: 0210 +[EXIF, ExifIFD, Time] 36867 - Datum van de originele data generatie: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Datum van de originele data generatie: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Betekenis van elke component: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Afbeelding compressie modus: 1.6 +[EXIF, ExifIFD, Image] 37377 - Belichtingstijd: 1/64 +[EXIF, ExifIFD, Image] 37378 - Diafragma: 3.5 +[EXIF, ExifIFD, Image] 37379 - Helderheid: 2 +[EXIF, ExifIFD, Image] 37380 - Belichtingscorrectie: 0 +[EXIF, ExifIFD, Camera] 37381 - Grootste diafragma: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Belichting meet methode: Multi segment +[EXIF, ExifIFD, Camera] 37385 - Flits: Flits afgevuurd +[EXIF, ExifIFD, Camera] 37386 - Brandpuntafstand: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Ondersteunde Flashpix versie: 0100 +[EXIF, ExifIFD, Image] 40961 - Kleur ruimte: sRGB +[EXIF, ExifIFD, Image] 40962 - Afbeelding breedte: 1600 +[EXIF, ExifIFD, Image] 40963 - Afbeelding hoogte: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperabiliteits Identificatie: R98: DCF Basis formaat (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperabiliteits versie: 0100 +[EXIF, ExifIFD, Camera] 41486 - Horizontale sensor resolutie: 3053 +[EXIF, ExifIFD, Camera] 41487 - Verticale sensor resolutie: 3053 +[EXIF, ExifIFD, Camera] 41488 - Sensor resolutie eenheid: cm +[EXIF, ExifIFD, Camera] 41495 - Meet methode: Één chip kleur sensor +[EXIF, ExifIFD, Image] 41728 - Bestand bron: Digitale camera +[EXIF, ExifIFD, Image] 41729 - Scene type: Direkt opgenomen afbeelding +[EXIF, IFD1, Image] 259 - Compressie schema: JPEG (oude versie) +[EXIF, IFD1, Image] 274 - Oriëntatie van de afbeelding: 0° (boven/links) +[EXIF, IFD1, Image] 282 - Horizontale afbeelding resolutie: 72 +[EXIF, IFD1, Image] 283 - Vertikale afbeelding resolutie: 72 +[EXIF, IFD1, Image] 296 - Eenheid van de X und Y resolutie: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y en C positie: Naast liggend +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Miniatuur: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_13.out b/ExifTool/t/Lang_13.out new file mode 100644 index 0000000..a964898 --- /dev/null +++ b/ExifTool/t/Lang_13.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Producent: FUJIFILM +[EXIF, IFD0, Camera] 272 - Aparat: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientacja obrazu: 0° (góra/lewo) +[EXIF, IFD0, Image] 282 - Rozdzielczość obrazu w poziomie: 72 +[EXIF, IFD0, Image] 283 - Rozdzielczość obrazu w pionie: 72 +[EXIF, IFD0, Image] 296 - Jednostka rozdzielczoÅ›ci X i Y: Cal +[EXIF, IFD0, Image] 305 - Oprogramowanie: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Data i godzina zmiany pliku: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Pozycje Y i C: Obok siebie (?) +[EXIF, IFD0, Author] 33432 - Posiadacz praw autorskich: +[EXIF, ExifIFD, Image] 33437 - PrzysÅ‚ona: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Program ekspozycji: Normalny program +[EXIF, ExifIFD, Image] 34855 - CzuÅ‚ość ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Wersja Exif: 0210 +[EXIF, ExifIFD, Time] 36867 - Pierwotna data i godzina: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Data utworzenia: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Znaczenie każdego komponentu: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Tryb kompresji obrazu: 1.6 +[EXIF, ExifIFD, Image] 37377 - PrÄ™dkość migawki: 1/64 +[EXIF, ExifIFD, Image] 37378 - PrzysÅ‚ona: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Różnica ekspozycji: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Tryb pomiaru: Wzór +[EXIF, ExifIFD, Camera] 37385 - Lampa: NastÄ…piÅ‚ bÅ‚ysk lampy +[EXIF, ExifIFD, Camera] 37386 - Ogniskowa: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - ObsÅ‚ugiwana wersja Flashpix: 0100 +[EXIF, ExifIFD, Image] 40961 - Informacja o przestrzeni barwowej: sRGB +[EXIF, ExifIFD, Image] 40962 - Szerokość obrazu: 1600 +[EXIF, ExifIFD, Image] 40963 - Wysokość obrazu: 1200 +[EXIF, InteropIFD, Image] 1 - Identyfikacja wzajemnej zgodnoÅ›ci: R98: Plik zasadniczy DCF (sRGB) +[EXIF, InteropIFD, Image] 2 - Wersja wzajemnej zgodnoÅ›ci: 0100 +[EXIF, ExifIFD, Camera] 41486 - Rozdzielczość w pÅ‚aszczyźnie ogniskowej - oÅ› x: 3053 +[EXIF, ExifIFD, Camera] 41487 - Rozdzielczość w pÅ‚aszczyźnie ogniskowej - oÅ› y: 3053 +[EXIF, ExifIFD, Camera] 41488 - Jednostka rozdzielczoÅ›ci w pÅ‚aszczyźnie ogniskowej: cm +[EXIF, ExifIFD, Camera] 41495 - Metoda pomiaru: Jednoprocesorowy sensor obszaru koloru +[EXIF, ExifIFD, Image] 41728 - ŹródÅ‚o pliku: DSC +[EXIF, ExifIFD, Image] 41729 - Rodzaj sceny: ZdjÄ™cie uzyskane bezpoÅ›rednio +[EXIF, IFD1, Image] 259 - Algorytm kompresji: JPEG (w starym stylu) +[EXIF, IFD1, Image] 274 - Orientacja obrazu: 0° (góra/lewo) +[EXIF, IFD1, Image] 282 - Rozdzielczość obrazu w poziomie: 72 +[EXIF, IFD1, Image] 283 - Rozdzielczość obrazu w pionie: 72 +[EXIF, IFD1, Image] 296 - Jednostka rozdzielczoÅ›ci X i Y: Cal +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Pozycje Y i C: Obok siebie (?) +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Miniatura: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_14.out b/ExifTool/t/Lang_14.out new file mode 100644 index 0000000..d5d0648 --- /dev/null +++ b/ExifTool/t/Lang_14.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Производитель: FUJIFILM +[EXIF, IFD0, Camera] 272 - Модель камеры: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - ОриентациÑ: Ð“Ð¾Ñ€Ð¸Ð·Ð¾Ð½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ +[EXIF, IFD0, Image] 282 - Разрешение по X: 72 +[EXIF, IFD0, Image] 283 - Разрешение по Y: 72 +[EXIF, IFD0, Image] 296 - Единицы Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¿Ð¾ X и Y: дюймы +[EXIF, IFD0, Image] 305 - Приложение: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Дата редактированиÑ: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Положение точки, определÑющей цвет в Y Cb Cr: СовмеÑтимый +[EXIF, IFD0, Author] 33432 - ÐвторÑкое право: +[EXIF, ExifIFD, Image] 33437 - Диафрагменное чиÑло: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Программа ÑкÑпозиции: ÐŸÑ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð½Ð°Ñ Ð°Ð²Ñ‚Ð¾ÑкÑÐ¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - ВерÑÐ¸Ñ Exif: 0210 +[EXIF, ExifIFD, Time] 36867 - Дата Ñъёмки: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Дата оцифровки: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñ‚Ð¾Ð²: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Сжатых бит на пикÑель: 1.6 +[EXIF, ExifIFD, Image] 37377 - СкороÑть ÑÑ€Ð°Ð±Ð°Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð·Ð°Ñ‚Ð²Ð¾Ñ€Ð°: 1/64 +[EXIF, ExifIFD, Image] 37378 - Диафрагма: 3.5 +[EXIF, ExifIFD, Image] 37379 - Значение ÑркоÑти: 2 +[EXIF, ExifIFD, Image] 37380 - КомпенÑÐ°Ñ†Ð¸Ñ ÑкÑпозиции: 0 +[EXIF, ExifIFD, Camera] 37381 - МакÑимальное значение диафрагмы: 3.5 +[EXIF, ExifIFD, Camera] 37383 - ЭкÑпозамер: Оценочный/Многозонный +[EXIF, ExifIFD, Camera] 37385 - СоÑтоÑние вÑпышки при Ñъёмке: Ð’Ñпышка Ñработала +[EXIF, ExifIFD, Camera] 37386 - ФокуÑное раÑÑтоÑние: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - ВерÑÐ¸Ñ Flashpix: 0100 +[EXIF, ExifIFD, Image] 40961 - Цветовое проÑтранÑтво: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif – Ширина изображениÑ: 1600 +[EXIF, ExifIFD, Image] 40963 - Exif – Ð’Ñ‹Ñота изображениÑ: 1200 +[EXIF, InteropIFD, Image] 1 - Ð˜Ð½Ð´ÐµÐºÑ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ð¹ ÑовмеÑтимоÑти: R98 – ОÑновной файл DCF (sRGB) +[EXIF, InteropIFD, Image] 2 - ВерÑÐ¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ð¹ ÑовмеÑтимоÑти: 0100 +[EXIF, ExifIFD, Camera] 41486 - Разрешение в фокальной плоÑкоÑти по X: 3053 +[EXIF, ExifIFD, Camera] 41487 - Разрешение в фокальной плоÑкоÑти по Y: 3053 +[EXIF, ExifIFD, Camera] 41488 - Единицы Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð² фокальной плоÑкоÑти: Ñм +[EXIF, ExifIFD, Camera] 41495 - Тип датчика: Одночиповый цветной ÑенÑор +[EXIF, ExifIFD, Image] 41728 - ИÑточник файла: Ð¦Ð¸Ñ„Ñ€Ð¾Ð²Ð°Ñ Ñ„Ð¾Ñ‚Ð¾ÐºÐ°Ð¼ÐµÑ€Ð° +[EXIF, ExifIFD, Image] 41729 - Тип Ñцены: Сфотографировано цифровой камерой +[EXIF, IFD1, Image] 259 - Метод ÑжатиÑ: JPEG (Ñтарый Ñтиль) +[EXIF, IFD1, Image] 274 - ОриентациÑ: Ð“Ð¾Ñ€Ð¸Ð·Ð¾Ð½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ +[EXIF, IFD1, Image] 282 - Разрешение по X: 72 +[EXIF, IFD1, Image] 283 - Разрешение по Y: 72 +[EXIF, IFD1, Image] 296 - Единицы Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¿Ð¾ X и Y: дюймы +[EXIF, IFD1, Image] 513 - Смещение миниатюры: 1096 +[EXIF, IFD1, Image] 514 - Строк в миниатюре: 28 +[EXIF, IFD1, Image] 531 - Положение точки, определÑющей цвет в Y Cb Cr: СовмеÑтимый +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Миниатюра изображениÑ: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_15.out b/ExifTool/t/Lang_15.out new file mode 100644 index 0000000..2980d54 --- /dev/null +++ b/ExifTool/t/Lang_15.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Výrobca: FUJIFILM +[EXIF, IFD0, Camera] 272 - Názov modelu fotoaparátu: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientácia: Horizontálna (normálne) +[EXIF, IFD0, Image] 282 - Horizontálne rozlíšenie: 72 +[EXIF, IFD0, Image] 283 - Vertikálne rozlíšenie: 72 +[EXIF, IFD0, Image] 296 - Jednotka rozlíšenia: palce +[EXIF, IFD0, Image] 305 - Softvér: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Dátum úpravy: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Umiestnenie Y Cb Cr: SpoloÄné umiestnenie +[EXIF, IFD0, Author] 33432 - Autorské právo: +[EXIF, ExifIFD, Image] 33437 - Clonové Äíslo: 3.5 +[EXIF, ExifIFD, Camera] 34850 - ExpoziÄný program: Program AE +[EXIF, ExifIFD, Image] 34855 - CitlivosÅ¥ ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Verzia Exif: 0210 +[EXIF, ExifIFD, Time] 36867 - Dátum/ÄŒas zhotovenia: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Dátum digitalizácie: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Konfigurácia komponentov: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Kompresia bitov na pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Hodnota rýchlosti uzávierky: 1/64 +[EXIF, ExifIFD, Image] 37378 - Hodnota clony: 3.5 +[EXIF, ExifIFD, Image] 37379 - Hodnota jasu: 2 +[EXIF, ExifIFD, Image] 37380 - Kompenzácia expozície: 0 +[EXIF, ExifIFD, Camera] 37381 - Maximálna hodnota clony: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Režim merania: Viacsegmentovo +[EXIF, ExifIFD, Camera] 37385 - Stav blesku pri snímaní: Odpálený +[EXIF, ExifIFD, Camera] 37386 - Ohnisková vzdialenosÅ¥: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Verzia Flashpix: 0100 +[EXIF, ExifIFD, Image] 40961 - Farebný priestor: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif - Šírka obrázku: 1600 +[EXIF, ExifIFD, Image] 40963 - Exif - Výška obrázku: 1200 +[EXIF, InteropIFD, Image] 1 - Index kompatibility súborov: R98 - základný súbor DCF (sRGB) +[EXIF, InteropIFD, Image] 2 - Verzia kompatibility súborov: 0100 +[EXIF, ExifIFD, Camera] 41486 - Horizontálne rozlíšenie ohniskovej roviny: 3053 +[EXIF, ExifIFD, Camera] 41487 - Vertikálne rozlíšenie ohniskovej roviny: 3053 +[EXIF, ExifIFD, Camera] 41488 - Jednotka rozlíšenia ohniskovej roviny: cm +[EXIF, ExifIFD, Camera] 41495 - Spôsob snímania: JednoÄipová farebná oblasÅ¥ +[EXIF, ExifIFD, Image] 41728 - Zdroj súboru: Digitálny fotoaparát +[EXIF, ExifIFD, Image] 41729 - Typ scény: Priamo odfotografované +[EXIF, IFD1, Image] 259 - Kompresia: JPEG (zastaralý) +[EXIF, IFD1, Image] 274 - Orientácia: Horizontálna (normálne) +[EXIF, IFD1, Image] 282 - Horizontálne rozlíšenie: 72 +[EXIF, IFD1, Image] 283 - Vertikálne rozlíšenie: 72 +[EXIF, IFD1, Image] 296 - Jednotka rozlíšenia: palce +[EXIF, IFD1, Image] 513 - Posun miniatúry: 1096 +[EXIF, IFD1, Image] 514 - Dĺžka miniatúry: 28 +[EXIF, IFD1, Image] 531 - Umiestnenie Y Cb Cr: SpoloÄné umiestnenie +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Miniatúra obrázku: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_16.out b/ExifTool/t/Lang_16.out new file mode 100644 index 0000000..3eff3e6 --- /dev/null +++ b/ExifTool/t/Lang_16.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Tillverkare: FUJIFILM +[EXIF, IFD0, Camera] 272 - Kamera: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientering: Positiv riktning +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Enhet för X- och Y-upplösning: tum +[EXIF, IFD0, Image] 305 - Programvara: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Filändringsdatum och -tid: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y- och C-placering: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright-innehavare: +[EXIF, ExifIFD, Image] 33437 - Bländare: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exponeringsprogram: Normalt program +[EXIF, ExifIFD, Image] 34855 - ISO värde: 100 +[EXIF, ExifIFD, Image] 36864 - Exif-version: 0210 +[EXIF, ExifIFD, Time] 36867 - Ursprungligt datum & tid: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Skapat datum: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Enskilda komponenters betydelse: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Bildkomprimeringsläge: 1.6 +[EXIF, ExifIFD, Image] 37377 - Slutartid: 1/64 +[EXIF, ExifIFD, Image] 37378 - Bländare: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exponeringsförskjutning: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Mätningstyp: Mönster +[EXIF, ExifIFD, Camera] 37385 - Blixt: Blixten utlöstes +[EXIF, ExifIFD, Camera] 37386 - Brännvidd: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix-version som stöds: 0100 +[EXIF, ExifIFD, Image] 40961 - FärgomrÃ¥desinformation: sRGB +[EXIF, ExifIFD, Image] 40962 - Giltig bildbredd: 1600 +[EXIF, ExifIFD, Image] 40963 - Giltig bildhöjd: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperability Identification: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Avkänningsmetod: One-chip color area sensor +[EXIF, ExifIFD, Image] 41728 - Filkälla: DSC +[EXIF, ExifIFD, Image] 41729 - Motivtyp: En direktfotograferad bild +[EXIF, IFD1, Image] 259 - Komprimeringsschema: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientering: Positiv riktning +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Enhet för X- och Y-upplösning: tum +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y- och C-placering: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Miniatyr: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_17.out b/ExifTool/t/Lang_17.out new file mode 100644 index 0000000..940d993 --- /dev/null +++ b/ExifTool/t/Lang_17.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Üretici: FUJIFILM +[EXIF, IFD0, Camera] 272 - Kamera: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Yönelim: Pozitif yön +[EXIF, IFD0, Image] 282 - X çözünürlüğü: 72 +[EXIF, IFD0, Image] 283 - Y çözünürlüğü: 72 +[EXIF, IFD0, Image] 296 - X ve Y birim çözünürlüğü: inç +[EXIF, IFD0, Image] 305 - Kullanılan yazılım: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Dosya deÄŸiÅŸim tarih ve zamanı: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y ve C konumlama: Birlikte-konumlanmış +[EXIF, IFD0, Author] 33432 - Telif hakkı sahibi: +[EXIF, ExifIFD, Image] 33437 - Açıklık: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Pozlama program: Normal program +[EXIF, ExifIFD, Image] 34855 - ISO deÄŸeri: 100 +[EXIF, ExifIFD, Image] 36864 - Exif sürüm: 0210 +[EXIF, ExifIFD, Time] 36867 - Orjinal Tarih & Zaman: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - OluÅŸturma Tarihi: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Her komponentin anlamı: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - İmaj sıkıştıma modu: 1.6 +[EXIF, ExifIFD, Image] 37377 - Deklanşör hızı: 1/64 +[EXIF, ExifIFD, Image] 37378 - Açıklık: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Pozlama Sapması: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Ölçü modu: Desen +[EXIF, ExifIFD, Camera] 37385 - FlaÅŸ: FlaÅŸ patladı +[EXIF, ExifIFD, Camera] 37386 - Odak uzunluÄŸu: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Desteklenen Flashpix sürümü: 0100 +[EXIF, ExifIFD, Image] 40961 - Renk alanı bilgisi: sRGB +[EXIF, ExifIFD, Image] 40962 - Geçerli imaj eni: 1600 +[EXIF, ExifIFD, Image] 40963 - Geçerli imaj yüksekliÄŸi: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperabilite Tanımı: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperabilite Sürümü: 0100 +[EXIF, ExifIFD, Camera] 41486 - Odak düzlemi X çözünürlüğü: 3053 +[EXIF, ExifIFD, Camera] 41487 - Odak düzlemi Y çözünürlüğü: 3053 +[EXIF, ExifIFD, Camera] 41488 - Odak düzlemi çözünürlük birimi: cm +[EXIF, ExifIFD, Camera] 41495 - Alıcı metodu: Tek-çip renk alanı alıcı +[EXIF, ExifIFD, Image] 41728 - Dosya kaynağı: DSC +[EXIF, ExifIFD, Image] 41729 - Senaryo tipi: DoÄŸrudan fotograflanmış imaj +[EXIF, IFD1, Image] 259 - Sıkıştırma planı: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Yönelim: Pozitif yön +[EXIF, IFD1, Image] 282 - X çözünürlüğü: 72 +[EXIF, IFD1, Image] 283 - Y çözünürlüğü: 72 +[EXIF, IFD1, Image] 296 - X ve Y birim çözünürlüğü: inç +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y ve C konumlama: Birlikte-konumlanmış +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Küçük Resim: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_18.out b/ExifTool/t/Lang_18.out new file mode 100644 index 0000000..27ecf29 --- /dev/null +++ b/ExifTool/t/Lang_18.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - 厂商: FUJIFILM +[EXIF, IFD0, Camera] 272 - åž‹å·: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - 图åƒå–å‘: 0° (上/å·¦) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - 图åƒé«˜å®½åˆ†è¾¨çއå•ä½: 英寸 +[EXIF, IFD0, Image] 305 - 使用软件: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - 文件改å˜çš„æ—¥æœŸå’Œæ—¶é—´: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - YCC åƒç´ ç»“æž„(Y å’Œ C çš„ä½ç½®): 一致 +[EXIF, IFD0, Author] 33432 - 专利拥有者: +[EXIF, ExifIFD, Image] 33437 - 光圈数: 3.5 +[EXIF, ExifIFD, Camera] 34850 - æ›å…‰ç¨‹åº: ä¸€èˆ¬ç¨‹åº +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif 版本: 0210 +[EXIF, ExifIFD, Time] 36867 - 原始数æ®äº§ç”Ÿçš„æ—¥æœŸå’Œæ—¶é—´: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - æ•°å­—æ•°æ®äº§ç”Ÿçš„æ—¥æœŸå’Œæ—¶é—´: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - å„分组的å«ä¹‰: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - 图åƒåŽ‹ç¼©æ¨¡å¼: 1.6 +[EXIF, ExifIFD, Image] 37377 - 快门速度: 1/64 +[EXIF, ExifIFD, Image] 37378 - 光圈: 3.5 +[EXIF, ExifIFD, Image] 37379 - 亮度: 2 +[EXIF, ExifIFD, Image] 37380 - æ›å…‰åå·®: 0 +[EXIF, ExifIFD, Camera] 37381 - 最大镜头光圈: 3.5 +[EXIF, ExifIFD, Camera] 37383 - æµ‹é‡æ–¹å¼: 分割测光 +[EXIF, ExifIFD, Camera] 37385 - 闪光: 闪光ç¯äº® +[EXIF, ExifIFD, Camera] 37386 - 焦è·: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - 支æŒçš„ Flashpix 版本: 0100 +[EXIF, ExifIFD, Image] 40961 - 色彩空间信æ¯: sRGB +[EXIF, ExifIFD, Image] 40962 - åƒå®½: 1600 +[EXIF, ExifIFD, Image] 40963 - åƒé«˜: 1200 +[EXIF, InteropIFD, Image] 1 - 互用标识: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - 互用版本: 0100 +[EXIF, ExifIFD, Camera] 41486 - ç„¦å¹³é¢ X 分辨率: 3053 +[EXIF, ExifIFD, Camera] 41487 - ç„¦å¹³é¢ Y 分辨率: 3053 +[EXIF, ExifIFD, Camera] 41488 - 焦平é¢åˆ†è¾¨çއå•ä½: cm +[EXIF, ExifIFD, Camera] 41495 - 感应方法: å•片色彩区感应器 +[EXIF, ExifIFD, Image] 41728 - æ–‡ä»¶æ¥æº: æ•°ç ç›¸æœº +[EXIF, ExifIFD, Image] 41729 - 场景类型: ç›´æŽ¥æ‹æ‘„çš„å›¾åƒ +[EXIF, IFD1, Image] 259 - 压缩方案: JPEG (æ—§æ ·å¼) +[EXIF, IFD1, Image] 274 - 图åƒå–å‘: 0° (上/å·¦) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - 图åƒé«˜å®½åˆ†è¾¨çއå•ä½: 英寸 +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - YCC åƒç´ ç»“æž„(Y å’Œ C çš„ä½ç½®): 一致 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - 缩略图: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_19.out b/ExifTool/t/Lang_19.out new file mode 100644 index 0000000..28ddd09 --- /dev/null +++ b/ExifTool/t/Lang_19.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - 製造商: FUJIFILM +[EXIF, IFD0, Camera] 272 - 相機型號: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - å½±åƒçš„æ–¹å‘: 0° (頂端/左邊) +[EXIF, IFD0, Image] 282 - 水平解æžåº¦: 72 +[EXIF, IFD0, Image] 283 - 垂直解æžåº¦: 72 +[EXIF, IFD0, Image] 296 - 寬與高的單ä½: è‹±å‹ +[EXIF, IFD0, Image] 305 - 軟體: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - æª”æ¡ˆå»ºç«‹æ—¥æœŸåŠæ™‚é–“: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y åŠ C 的設定: 一致 +[EXIF, IFD0, Author] 33432 - ç‰ˆæ¬Šæ“æœ‰äºº: +[EXIF, ExifIFD, Image] 33437 - 光圈: 3.5 +[EXIF, ExifIFD, Camera] 34850 - æ‹æ”模å¼: 正常 +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif 版本: 0210 +[EXIF, ExifIFD, Time] 36867 - åŽŸå§‹å½±åƒæ—¥æœŸæ™‚é–“: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - 數ä½åŒ–的日期時間: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - æ¯å€‹çµ„æˆéƒ¨åˆ†çš„æ„ç¾©: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - å½±åƒå£“縮模å¼: 1.6 +[EXIF, ExifIFD, Image] 37377 - å¿«é–€: 1/64 +[EXIF, ExifIFD, Image] 37378 - 光圈: 3.5 +[EXIF, ExifIFD, Image] 37379 - 亮度: 2 +[EXIF, ExifIFD, Image] 37380 - æ›å…‰è£œå„Ÿ: 0 +[EXIF, ExifIFD, Camera] 37381 - é¡é ­æœ€å¤§å…‰åœˆ: 3.5 +[EXIF, ExifIFD, Camera] 37383 - 測光模å¼: 評價測光 +[EXIF, ExifIFD, Camera] 37385 - 閃光燈: 閃光燈擊發 +[EXIF, ExifIFD, Camera] 37386 - 焦è·: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - æ”¯æ´ Flashpix 版本: 0100 +[EXIF, ExifIFD, Image] 40961 - 色彩空間: sRGB +[EXIF, ExifIFD, Image] 40962 - å½±åƒå¯¬åº¦: 1600 +[EXIF, ExifIFD, Image] 40963 - å½±åƒé«˜åº¦: 1200 +[EXIF, InteropIFD, Image] 1 - 互通性鑑定: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - 互通性版本: 0100 +[EXIF, ExifIFD, Camera] 41486 - X軸焦平é¢åˆ†è¾¨çއ: 3053 +[EXIF, ExifIFD, Camera] 41487 - Y軸焦平é¢åˆ†è¾¨çއ: 3053 +[EXIF, ExifIFD, Camera] 41488 - 焦平é¢åˆ†è¾¨çއ單ä½: cm +[EXIF, ExifIFD, Camera] 41495 - 感測器類型: 單晶片彩色感測器 +[EXIF, ExifIFD, Image] 41728 - 檔案來æº: 數ä½ç›¸æ©Ÿ +[EXIF, ExifIFD, Image] 41729 - 場景類型: ç›´æŽ¥æ‹æ”çš„å½±åƒ +[EXIF, IFD1, Image] 259 - 壓縮方å¼: JPEG (舊å¼) +[EXIF, IFD1, Image] 274 - å½±åƒçš„æ–¹å‘: 0° (頂端/左邊) +[EXIF, IFD1, Image] 282 - 水平解æžåº¦: 72 +[EXIF, IFD1, Image] 283 - 垂直解æžåº¦: 72 +[EXIF, IFD1, Image] 296 - 寬與高的單ä½: è‹±å‹ +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y åŠ C 的設定: 一致 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - 縮略圖: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_2.out b/ExifTool/t/Lang_2.out new file mode 100644 index 0000000..47d643a --- /dev/null +++ b/ExifTool/t/Lang_2.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Výrobce: FUJIFILM +[EXIF, IFD0, Camera] 272 - Typ fotoaparátu: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientace: 0° (nahoru/vlevo) +[EXIF, IFD0, Image] 282 - RozliÅ¡ení obrázku na šířku: 72 +[EXIF, IFD0, Image] 283 - RozliÅ¡ení obrázku na výšku: 72 +[EXIF, IFD0, Image] 296 - Jednotka X a Y rozliÅ¡ení: Palce +[EXIF, IFD0, Image] 305 - Použitý software: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Datum a Äas zmÄ›ny souboru: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y a C pozice: Po stranách +[EXIF, IFD0, Author] 33432 - Držitel práv: +[EXIF, ExifIFD, Image] 33437 - F hodnota: 3.5 +[EXIF, ExifIFD, Camera] 34850 - ExpoziÄní mod: Normální program +[EXIF, ExifIFD, Image] 34855 - Citlivost ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif verze: 0210 +[EXIF, ExifIFD, Time] 36867 - Datum a Äas vzniku originálních dat: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Datum a Äas generování digitálních dat: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - UrÄení složek: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - KomprimaÄní mod: 1.6 +[EXIF, ExifIFD, Image] 37377 - ÄŒas závÄ›rky: 1/64 +[EXIF, ExifIFD, Image] 37378 - Clona: 3.5 +[EXIF, ExifIFD, Image] 37379 - Jas: 2 +[EXIF, ExifIFD, Image] 37380 - Korekce expozice: 0 +[EXIF, ExifIFD, Camera] 37381 - Max clona objektivu: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Režim měření expozice: Multi segment +[EXIF, ExifIFD, Camera] 37385 - Blesk: Blesk ano +[EXIF, ExifIFD, Camera] 37386 - Ohnisková vzdálenost: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Podporovaná verze Flashpix: 0100 +[EXIF, ExifIFD, Image] 40961 - Barevný prostor: sRGB +[EXIF, ExifIFD, Image] 40962 - Šířka: 1600 +[EXIF, ExifIFD, Image] 40963 - Výška: 1200 +[EXIF, InteropIFD, Image] 1 - Identifikace: R98: DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Verze kompatibility: 0100 +[EXIF, ExifIFD, Camera] 41486 - Horizontální rozliÅ¡ení senzoru: 3053 +[EXIF, ExifIFD, Camera] 41487 - Vertikální rozliÅ¡ení senzoru: 3053 +[EXIF, ExifIFD, Camera] 41488 - Jednotka rozliÅ¡ení senzoru: cm +[EXIF, ExifIFD, Camera] 41495 - Metoda měření: JednoÄipový barevný senzor +[EXIF, ExifIFD, Image] 41728 - Zdroj dat: Digitální fotoaparát +[EXIF, ExifIFD, Image] 41729 - Typ scény: Přímo pořízený snímek +[EXIF, IFD1, Image] 259 - Kompresní algoritmus: JPEG (pův. verze) +[EXIF, IFD1, Image] 274 - Orientace: 0° (nahoru/vlevo) +[EXIF, IFD1, Image] 282 - RozliÅ¡ení obrázku na šířku: 72 +[EXIF, IFD1, Image] 283 - RozliÅ¡ení obrázku na výšku: 72 +[EXIF, IFD1, Image] 296 - Jednotka X a Y rozliÅ¡ení: Palce +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y a C pozice: Po stranách +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Náhled: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_3.out b/ExifTool/t/Lang_3.out new file mode 100644 index 0000000..c811d8c --- /dev/null +++ b/ExifTool/t/Lang_3.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Gerätehersteller: FUJIFILM +[EXIF, IFD0, Camera] 272 - Kameramodell: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Ausrichtung: Horizontal (normal) +[EXIF, IFD0, Image] 282 - Horizontale Bildauflösung: 72 +[EXIF, IFD0, Image] 283 - Vertikale Bildauflösung: 72 +[EXIF, IFD0, Image] 296 - Einheit der X- und Y-Auflösung: Zoll +[EXIF, IFD0, Image] 305 - Software: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Änderungsdatum: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y und C Ausrichtung: Benachbart +[EXIF, IFD0, Author] 33432 - Urheberrechtsvermerk: +[EXIF, ExifIFD, Image] 33437 - F-Wert: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Belichtungsprogramm: Programmautomatik +[EXIF, ExifIFD, Image] 34855 - ISO-Empfindlichkeit: 100 +[EXIF, ExifIFD, Image] 36864 - Exif-Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Erstellungsdatum/-uhrzeit: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Digitalisierungsdatum/-uhrzeit: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Bedeutung jeder Komponente: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Bildkomprimierungsmodus: 1.6 +[EXIF, ExifIFD, Image] 37377 - Belichtungszeit: 1/64 +[EXIF, ExifIFD, Image] 37378 - Blende: 3.5 +[EXIF, ExifIFD, Image] 37379 - Helligkeit: 2 +[EXIF, ExifIFD, Image] 37380 - Belichtungskorrektur: 0 +[EXIF, ExifIFD, Camera] 37381 - Größtmögliche Blende: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Belichtungsmessmethode: Multi-Segment +[EXIF, ExifIFD, Camera] 37385 - Blitzmodus: Blitz wurde ausgelöst +[EXIF, ExifIFD, Camera] 37386 - Brennweite: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Unterstützte Flashpix-Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Farbraum: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif-Bildbreite: 1600 +[EXIF, ExifIFD, Image] 40963 - Exif-Bildhöhe: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperabilität Identifikation: R98: DCF Basic-Format (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperabilitäts-Version: 0100 +[EXIF, ExifIFD, Camera] 41486 - Sensorauflösung horizontal: 3053 +[EXIF, ExifIFD, Camera] 41487 - Sensorauflösung vertikal: 3053 +[EXIF, ExifIFD, Camera] 41488 - Einheit der Sensorauflösung: cm +[EXIF, ExifIFD, Camera] 41495 - Messmethode: Ein-Chip-Farbsensor +[EXIF, ExifIFD, Image] 41728 - Dateiquelle: Digital-Kamera +[EXIF, ExifIFD, Image] 41729 - Szenentyp: Direkt aufgenommenes Bild +[EXIF, IFD1, Image] 259 - Komprimierungsschema: JPEG (alte Version) +[EXIF, IFD1, Image] 274 - Ausrichtung: Horizontal (normal) +[EXIF, IFD1, Image] 282 - Horizontale Bildauflösung: 72 +[EXIF, IFD1, Image] 283 - Vertikale Bildauflösung: 72 +[EXIF, IFD1, Image] 296 - Einheit der X- und Y-Auflösung: Zoll +[EXIF, IFD1, Image] 513 - Miniaturbild-Datenposition: 1096 +[EXIF, IFD1, Image] 514 - Miniaturbild-Datenlänge: 28 +[EXIF, IFD1, Image] 531 - Y und C Ausrichtung: Benachbart +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Miniaturbild: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_4.out b/ExifTool/t/Lang_4.out new file mode 100644 index 0000000..b8b05f2 --- /dev/null +++ b/ExifTool/t/Lang_4.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Modify Date: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Colour Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1600 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip colour area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_5.out b/ExifTool/t/Lang_5.out new file mode 100644 index 0000000..b8b05f2 --- /dev/null +++ b/ExifTool/t/Lang_5.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Modify Date: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Colour Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1600 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip colour area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_6.out b/ExifTool/t/Lang_6.out new file mode 100644 index 0000000..446f4ae --- /dev/null +++ b/ExifTool/t/Lang_6.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Marca: FUJIFILM +[EXIF, IFD0, Camera] 272 - Modelo: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientación de Imagen: 0° (arriba/izquierda) +[EXIF, IFD0, Image] 282 - Resolución Imagen Horizontal: 72 +[EXIF, IFD0, Image] 283 - Resolución Imagen Vertical: 72 +[EXIF, IFD0, Image] 296 - Unidad de Resolución de X e Y: Pulgada +[EXIF, IFD0, Image] 305 - Programa Utilizado: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Fecha y Hora de Cambio del Archivo: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Posicionamiento Y y C: Vecino +[EXIF, IFD0, Author] 33432 - Copyright Perfil: +[EXIF, ExifIFD, Image] 33437 - Número F: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Programa Exposición: Programa normal +[EXIF, ExifIFD, Image] 34855 - Ratio Velocidad ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Versión Exif: 0210 +[EXIF, ExifIFD, Time] 36867 - Fecha y Hora de Datos Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Fecha y Hora de Datos Digital: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Configuración de Componentes: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Modo Compresión Imagen: 1.6 +[EXIF, ExifIFD, Image] 37377 - Velocidad Obturación: 1/64 +[EXIF, ExifIFD, Image] 37378 - Apertura: 3.5 +[EXIF, ExifIFD, Image] 37379 - Luminosidad: 2 +[EXIF, ExifIFD, Image] 37380 - Compensación Exposición: 0 +[EXIF, ExifIFD, Camera] 37381 - Apertura Lente Máxima: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Modo Medición: Multi-segmento +[EXIF, ExifIFD, Camera] 37385 - Flash: Flash disparado +[EXIF, ExifIFD, Camera] 37386 - Distancia Focal Objetivo: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Versión Flashpix Soportado: 0100 +[EXIF, ExifIFD, Image] 40961 - Espacio Color: sRGB +[EXIF, ExifIFD, Image] 40962 - Ancho Imagen: 1600 +[EXIF, ExifIFD, Image] 40963 - Alto Imagen: 1200 +[EXIF, InteropIFD, Image] 1 - Identificación Interoperabilidad: R98: Archivo básico DCF (sRGB) +[EXIF, InteropIFD, Image] 2 - Versión Interoperabilidad: 0100 +[EXIF, ExifIFD, Camera] 41486 - Resolución X Plano Focal: 3053 +[EXIF, ExifIFD, Camera] 41487 - Resolución Y Plano Focal: 3053 +[EXIF, ExifIFD, Camera] 41488 - Unidad Resolución Plano Focal: cm +[EXIF, ExifIFD, Camera] 41495 - Método Sensor: Sensor monochip de color +[EXIF, ExifIFD, Image] 41728 - Fuente Archivo: Cámara Digital +[EXIF, ExifIFD, Image] 41729 - Tipo Escena: Imagen fotografiada directamente +[EXIF, IFD1, Image] 259 - Compresión: JPEG (estilo antiguo) +[EXIF, IFD1, Image] 274 - Orientación de Imagen: 0° (arriba/izquierda) +[EXIF, IFD1, Image] 282 - Resolución Imagen Horizontal: 72 +[EXIF, IFD1, Image] 283 - Resolución Imagen Vertical: 72 +[EXIF, IFD1, Image] 296 - Unidad de Resolución de X e Y: Pulgada +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Posicionamiento Y y C: Vecino +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Miniatura: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_7.out b/ExifTool/t/Lang_7.out new file mode 100644 index 0000000..385c296 --- /dev/null +++ b/ExifTool/t/Lang_7.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Valmistaja: FUJIFILM +[EXIF, IFD0, Camera] 272 - Malli: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Kuvan suunta: 0° (ylä/vasen) +[EXIF, IFD0, Image] 282 - Kuvan vaakaresoluutio: 72 +[EXIF, IFD0, Image] 283 - Kuvan pystyresoluutio: 72 +[EXIF, IFD0, Image] 296 - X- ja Y-resoluution yksikkö: Tuuma +[EXIF, IFD0, Image] 305 - Ohjelmisto: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Tiedostomuutoksen päiväys ja aika: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Y:n ja C:n sijoitus: Vierekkäin +[EXIF, IFD0, Author] 33432 - Profiilin copyright: +[EXIF, ExifIFD, Image] 33437 - Aukkoarvo: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Valotusohjelma: Normaali ohjelma +[EXIF, ExifIFD, Image] 34855 - ISO-herkkyys: 100 +[EXIF, ExifIFD, Image] 36864 - Exif-versio: 0210 +[EXIF, ExifIFD, Time] 36867 - Alkuperäisen datan luonnin päiväys ja aika: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Digitaalisen datan luonnin päiväys ja aika: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Kunkin komponentin tarkoitus: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Kuvan pakkausmuoto: 1.6 +[EXIF, ExifIFD, Image] 37377 - Suljinnopeuden arvo: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aukkoarvo: 3.5 +[EXIF, ExifIFD, Image] 37379 - Kirkkausarvo: 2 +[EXIF, ExifIFD, Image] 37380 - Valotuksen korjaus: 0 +[EXIF, ExifIFD, Camera] 37381 - Objektiivin maksimiaukko: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Valonmittaustapa: Monisegmenttimittaus +[EXIF, ExifIFD, Camera] 37385 - Salama: Salama lauennut +[EXIF, ExifIFD, Camera] 37386 - Polttoväli: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Tuettu Flashpix-versio: 0100 +[EXIF, ExifIFD, Image] 40961 - Väriavaruus: sRGB +[EXIF, ExifIFD, Image] 40962 - Kuvan leveys: 1600 +[EXIF, ExifIFD, Image] 40963 - Kuvan korkeus: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperabiliteetin identifiointi: R98: DCF-perustiedosto (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperabiliteetin versio: 0100 +[EXIF, ExifIFD, Camera] 41486 - Polttopistetason vaakaresoluutio: 3053 +[EXIF, ExifIFD, Camera] 41487 - Polttopistetason pystyresoluutio: 3053 +[EXIF, ExifIFD, Camera] 41488 - Polttopistetason resoluutioyksikkö: cm +[EXIF, ExifIFD, Camera] 41495 - Mittausmetodi: Yhden chipin värialueen anturi +[EXIF, ExifIFD, Image] 41728 - Tiedoston lähde: Digitaalikamera +[EXIF, ExifIFD, Image] 41729 - Näkymätyyppi: Suoraan otettu valokuva +[EXIF, IFD1, Image] 259 - Pakkausskeema: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Kuvan suunta: 0° (ylä/vasen) +[EXIF, IFD1, Image] 282 - Kuvan vaakaresoluutio: 72 +[EXIF, IFD1, Image] 283 - Kuvan pystyresoluutio: 72 +[EXIF, IFD1, Image] 296 - X- ja Y-resoluution yksikkö: Tuuma +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y:n ja C:n sijoitus: Vierekkäin +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Näytekuva: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_8.out b/ExifTool/t/Lang_8.out new file mode 100644 index 0000000..e88cd19 --- /dev/null +++ b/ExifTool/t/Lang_8.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Fabricant: FUJIFILM +[EXIF, IFD0, Camera] 272 - Modèle de l'appareil photo: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation de l'image: Horizontale (normale) +[EXIF, IFD0, Image] 282 - Résolution horizontale de l'image: 72 +[EXIF, IFD0, Image] 283 - Résolution verticale de l'image: 72 +[EXIF, IFD0, Image] 296 - Unité de résolutions: pouce +[EXIF, IFD0, Image] 305 - Logiciel: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Date de modification du fichier: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Positionnement YCbCr: Côte à côte +[EXIF, IFD0, Author] 33432 - Propriétaire du copyright: +[EXIF, ExifIFD, Image] 33437 - Diaphragme Æ’: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Programme d'exposition: Programme normal +[EXIF, ExifIFD, Image] 34855 - Sensibilité ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Version Exif: 0210 +[EXIF, ExifIFD, Time] 36867 - Date de création des données originales: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Date de création des données numériques: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Configuration des composants: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Mode de compression de l'image: 1.6 +[EXIF, ExifIFD, Image] 37377 - Vitesse d'obturation: 1/64 +[EXIF, ExifIFD, Image] 37378 - Ouverture: 3.5 +[EXIF, ExifIFD, Image] 37379 - Luminosité: 2 +[EXIF, ExifIFD, Image] 37380 - Décalage d'exposition: 0 +[EXIF, ExifIFD, Camera] 37381 - Ouverture maximale de l'objectif: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Mode de mesure: Multi-segments +[EXIF, ExifIFD, Camera] 37385 - Flash : Flash déclenché +[EXIF, ExifIFD, Camera] 37386 - Focale de l'objectif: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Version Flashpix: 0100 +[EXIF, ExifIFD, Image] 40961 - Espace colorimétrique: sRVB +[EXIF, ExifIFD, Image] 40962 - Largeur de l'image: 1600 +[EXIF, ExifIFD, Image] 40963 - Hauteur de l'image: 1200 +[EXIF, InteropIFD, Image] 1 - Identification d'interopérabilité: R98: fichier de base DCF (sRVB) +[EXIF, InteropIFD, Image] 2 - Version d'interopérabilité: 0100 +[EXIF, ExifIFD, Camera] 41486 - Résolution du plan focal horizontal: 3053 +[EXIF, ExifIFD, Camera] 41487 - Résolution du plan focal vertical: 3053 +[EXIF, ExifIFD, Camera] 41488 - Unité de résolution de plan focal: cm +[EXIF, ExifIFD, Camera] 41495 - Méthode de capture: Capteur mono-puce couleur +[EXIF, ExifIFD, Image] 41728 - Source du fichier: Appareil photo numérique +[EXIF, ExifIFD, Image] 41729 - Type de scène: Photographié directement +[EXIF, IFD1, Image] 259 - Schéma de compression: JPEG (ancien schéma) +[EXIF, IFD1, Image] 274 - Orientation de l'image: Horizontale (normale) +[EXIF, IFD1, Image] 282 - Résolution horizontale de l'image: 72 +[EXIF, IFD1, Image] 283 - Résolution verticale de l'image: 72 +[EXIF, IFD1, Image] 296 - Unité de résolutions: pouce +[EXIF, IFD1, Image] 513 - Décalage de la vignette: 1096 +[EXIF, IFD1, Image] 514 - Longueur de la vignette: 28 +[EXIF, IFD1, Image] 531 - Positionnement YCbCr: Côte à côte +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Vignette: (Binary data 28 bytes) diff --git a/ExifTool/t/Lang_9.out b/ExifTool/t/Lang_9.out new file mode 100644 index 0000000..1f23653 --- /dev/null +++ b/ExifTool/t/Lang_9.out @@ -0,0 +1,47 @@ +[EXIF, IFD0, Camera] 271 - Costruttore: FUJIFILM +[EXIF, IFD0, Camera] 272 - Nome modello fotocamera: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientamento: Orizzontale (normale) +[EXIF, IFD0, Image] 282 - Risoluzione orizzontale immagine: 72 +[EXIF, IFD0, Image] 283 - Risoluzione verticale immagine: 72 +[EXIF, IFD0, Image] 296 - Unità risoluzione: Pollici +[EXIF, IFD0, Image] 305 - Software utilizzato: Digital Camera FinePix2400Zoom Ver1.70 +[EXIF, IFD0, Time] 306 - Data modifica: 2001:05:19 18:36:41 +[EXIF, IFD0, Image] 531 - Posizionamento Y e C: Affiancato +[EXIF, IFD0, Author] 33432 - Titolare del copyright: +[EXIF, ExifIFD, Image] 33437 - Numero F: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Programma esposizione: Programma AE +[EXIF, ExifIFD, Image] 34855 - Sensibilità ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Versione Exif: 0210 +[EXIF, ExifIFD, Time] 36867 - Data/ora originale di creazione: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Data di creazione: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Configurazione componenti: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Bit per pixel compressi: 1.6 +[EXIF, ExifIFD, Image] 37377 - Velocità otturatore: 1/64 +[EXIF, ExifIFD, Image] 37378 - Apertura diaframma: 3.5 +[EXIF, ExifIFD, Image] 37379 - Valore di luminosità: 2 +[EXIF, ExifIFD, Image] 37380 - Compensazione esposizione: 0 +[EXIF, ExifIFD, Camera] 37381 - Diaframma massimo obiettivo: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Modalità misurazione: Multi-zona +[EXIF, ExifIFD, Camera] 37385 - Flash: Emesso +[EXIF, ExifIFD, Camera] 37386 - Lunghezza focale: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Versione Flashpix: 0100 +[EXIF, ExifIFD, Image] 40961 - Spazio colore: sRGB +[EXIF, ExifIFD, Image] 40962 - Larghezza immagine Exif: 1600 +[EXIF, ExifIFD, Image] 40963 - Altezza immagine Exif: 1200 +[EXIF, InteropIFD, Image] 1 - Identificazione interoperatività: R98 - file base DCF (sRGB) +[EXIF, InteropIFD, Image] 2 - Versione interoperatività: 0100 +[EXIF, ExifIFD, Camera] 41486 - Risoluzione X del piano focale: 3053 +[EXIF, ExifIFD, Camera] 41487 - Risoluzione Y del piano focale: 3053 +[EXIF, ExifIFD, Camera] 41488 - Unità risoluzione piano focale: cm +[EXIF, ExifIFD, Camera] 41495 - Metodo misurazione esposimetrica: Sensore area a colori a un chip +[EXIF, ExifIFD, Image] 41728 - Origine file: Fotocamera digitale +[EXIF, ExifIFD, Image] 41729 - Tipo scena: Immagine fotografata direttamente +[EXIF, IFD1, Image] 259 - Compressione: JPEG (vecchio stile) +[EXIF, IFD1, Image] 274 - Orientamento: Orizzontale (normale) +[EXIF, IFD1, Image] 282 - Risoluzione orizzontale immagine: 72 +[EXIF, IFD1, Image] 283 - Risoluzione verticale immagine: 72 +[EXIF, IFD1, Image] 296 - Unità risoluzione: Pollici +[EXIF, IFD1, Image] 513 - Offset miniatura: 1096 +[EXIF, IFD1, Image] 514 - Dimensioni miniatura: 28 +[EXIF, IFD1, Image] 531 - Posizionamento Y e C: Affiancato +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Miniatura: (Binary data 28 bytes) diff --git a/ExifTool/t/Lytro.t b/ExifTool/t/Lytro.t new file mode 100644 index 0000000..f89bd2c --- /dev/null +++ b/ExifTool/t/Lytro.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Lytro.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Lytro; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Lytro'; +my $testnum = 1; + +# test 2: Extract information from Lytro.lfp +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Lytro.lfp'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Lytro_2.out b/ExifTool/t/Lytro_2.out new file mode 100644 index 0000000..4c6c982 --- /dev/null +++ b/ExifTool/t/Lytro_2.out @@ -0,0 +1,109 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Lytro.lfp +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 4.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2014:07:17 11:22:05-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:41-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: LFP +[File, File, Other] FileTypeExtension - File Type Extension: lfp +[File, File, Other] MIMEType - MIME Type: image/x-lytro-lfp +[Lytro, Lytro, Camera] JSONMetadata - JSON Metadata: [(Binary data 778 bytes),(Binary data 3155 bytes),(Binary data 131 bytes)] +[Lytro, Lytro, Image] PictureDerivationArray - Picture Derivation Array: sha1-f58530c3902263bfc1d411cac2b965d635fb6287 +[Lytro, Lytro, Image] PictureFrameArrayFrameImageRef - Picture Frame Array Frame Image Ref: sha1-0360f825e6eed3c7c470fb79a755a54df07fae84 +[Lytro, Lytro, Image] PictureFrameArrayFrameMetadataRef - Picture Frame Array Frame Metadata Ref: sha1-031d1e1d0e65efcd6ea74c811a0bc170498d8574 +[Lytro, Lytro, Image] PictureFrameArrayFramePrivateMetadataRef - Picture Frame Array Frame Private Metadata Ref: sha1-990a1858fe077fa0601df485704fc1d674a5e6b0 +[Lytro, Lytro, Image] PictureFrameArrayParametersVendorContentCom.lytro.tagsDarkFrame - Picture Frame Array Dark Frame: false +[Lytro, Lytro, Image] PictureFrameArrayParametersVendorContentCom.lytro.tagsModulationFrame - Picture Frame Array Modulation Frame: false +[Lytro, Lytro, Image] PictureViewArrayType - Picture View Array Type: com.lytro.stars +[Lytro, Lytro, Image] PictureViewArrayVendorContentStarred - Picture View Array Vendor Content Starred: true +[Lytro, Lytro, Image] VersionMajor - Version Major: 1 +[Lytro, Lytro, Image] VersionMinor - Version Minor: 0 +[Lytro, Lytro, Image] VersionProvisionalDate - Version Provisional Date: 2011-08-03 +[Lytro, Lytro, Camera] CameraFirmware - Firmware Version: v1.0a60, vv1.0.0, Thu Feb 23 15:02:57 PST 2012, e10fcca0668db3dbf94ae347248db3da070d21e9, mods=0, ofw=0 +[Lytro, Lytro, Camera] CameraMake - Make: Lytro, Inc. +[Lytro, Lytro, Camera] CameraModel - Camera Model Name: F01 +[Lytro, Lytro, Camera] DevicesAccelerometerSampleArrayTime - Accelerometer Time: 0 +[Lytro, Lytro, Camera] DevicesAccelerometerSampleArrayX - Accelerometer X: -0.039215687662363052368 +[Lytro, Lytro, Camera] DevicesAccelerometerSampleArrayY - Accelerometer Y: 0.94117647409439086914 +[Lytro, Lytro, Camera] DevicesAccelerometerSampleArrayZ - Accelerometer Z: 0.2509804069995880127 +[Lytro, Lytro, Time] DevicesClockZuluTime - Date/Time Original: 2012:04:12 14:10:55.000Z +[Lytro, Lytro, Camera] DevicesLensExitPupilOffsetZ - Lens Exit Pupil Offset Z: 0.11637913513183592573 +[Lytro, Lytro, Camera] DevicesLensFNumber - F Number: 1.9 +[Lytro, Lytro, Camera] DevicesLensFocalLength - Focal Length: 6.4 mm +[Lytro, Lytro, Camera] DevicesLensFocusStep - Lens Focus Step: 721 +[Lytro, Lytro, Camera] DevicesLensFocusStepperOffset - Lens Focus Stepper Offset: -13 +[Lytro, Lytro, Camera] DevicesLensInfinityLambda - Lens Infinity Lambda: 43.904331207275390625 +[Lytro, Lytro, Camera] DevicesLensTemperature - Lens Temperature: 34.1 C +[Lytro, Lytro, Camera] DevicesLensTemperatureAdc - Lens Temperature Adc: 2633 +[Lytro, Lytro, Camera] DevicesLensZoomStep - Lens Zoom Step: 981 +[Lytro, Lytro, Camera] DevicesLensZoomStepperOffset - Lens Zoom Stepper Offset: -1 +[Lytro, Lytro, Camera] DevicesMlaLensPitch - Mla Lens Pitch: 1.3898614883422850808e-05 +[Lytro, Lytro, Camera] DevicesMlaRotation - Mla Rotation: -0.00028155732434242963791 +[Lytro, Lytro, Camera] DevicesMlaScaleFactorX - Mla Scale Factor X: 1 +[Lytro, Lytro, Camera] DevicesMlaScaleFactorY - Mla Scale Factor Y: 1.0004389286041259766 +[Lytro, Lytro, Camera] DevicesMlaSensorOffsetX - Mla Sensor Offset X: -3.5040323734283452459e-06 +[Lytro, Lytro, Camera] DevicesMlaSensorOffsetY - Mla Sensor Offset Y: -1.6298500299453736302e-06 +[Lytro, Lytro, Camera] DevicesMlaSensorOffsetZ - Mla Sensor Offset Z: 2.5000000000000001198e-05 +[Lytro, Lytro, Camera] DevicesMlaTiling - Mla Tiling: hexUniformRowMajor +[Lytro, Lytro, Camera] DevicesNdfilterExposureBias - Ndfilter Exposure Bias: 3.8281672000885009766 +[Lytro, Lytro, Camera] DevicesSensorAnalogGainB - Sensor Analog Gain B: 2.03125 +[Lytro, Lytro, Camera] DevicesSensorAnalogGainGb - Sensor Analog Gain Gb: 1.75 +[Lytro, Lytro, Camera] DevicesSensorAnalogGainGr - Sensor Analog Gain Gr: 1.75 +[Lytro, Lytro, Camera] DevicesSensorAnalogGainR - Sensor Analog Gain R: 2.375 +[Lytro, Lytro, Camera] DevicesSensorBitsPerPixel - Sensor Bits Per Pixel: 12 +[Lytro, Lytro, Camera] DevicesSensorIso - ISO: 87 +[Lytro, Lytro, Camera] DevicesSensorMosaicTile - Sensor Mosaic Tile: r,gr:gb,b +[Lytro, Lytro, Camera] DevicesSensorMosaicUpperLeftPixel - Sensor Mosaic Upper Left Pixel: b +[Lytro, Lytro, Camera] DevicesSensorPixelPitch - Focal Plane X Resolution: 18142.8574518282 +[Lytro, Lytro, Camera] DevicesShutterFrameExposureDuration - Frame Exposure Time: 1/250 +[Lytro, Lytro, Camera] DevicesShutterMechanism - Shutter Mechanism: sensorOpenApertureClose +[Lytro, Lytro, Camera] DevicesShutterPixelExposureDuration - Exposure Time: 1/250 +[Lytro, Lytro, Camera] DevicesSocTemperature - Soc Temperature: 43.5 C +[Lytro, Lytro, Camera] DevicesSocTemperatureAdc - Soc Temperature Adc: 2776 +[Lytro, Lytro, Image] ImageColorCcmRgbToSrgbArray - Image Color Ccm Rgb To Srgb Array: 3.1115827560424804688, -1.9393929243087768555, -0.17218986153602600098, -0.36290559172630310059, 1.6408803462982177734, -0.27797481417655944824, 0.078967012465000152588, -1.1558042764663696289, 2.0768373012542724609 +[Lytro, Lytro, Image] ImageColorGamma - Image Color Gamma: 0.41666001081466674805 +[Lytro, Lytro, Image] ImageColorWhiteBalanceGainB - Image Color White Balance Gain B: 1.48046875 +[Lytro, Lytro, Image] ImageColorWhiteBalanceGainGb - Image Color White Balance Gain Gb: 1 +[Lytro, Lytro, Image] ImageColorWhiteBalanceGainGr - Image Color White Balance Gain Gr: 1 +[Lytro, Lytro, Image] ImageColorWhiteBalanceGainR - Image Color White Balance Gain R: 1.01171875 +[Lytro, Lytro, Image] ImageHeight - Image Height: 3280 +[Lytro, Lytro, Image] ImageLimitExposureBias - Image Limit Exposure Bias: +0.0 +[Lytro, Lytro, Image] ImageModulationExposureBias - Image Modulation Exposure Bias: -1.2 +[Lytro, Lytro, Image] ImageOrientation - Orientation: Horizontal (normal) +[Lytro, Lytro, Image] ImageRawDetailsMosaicTile - Image Raw Details Mosaic Tile: r,gr:gb,b +[Lytro, Lytro, Image] ImageRawDetailsMosaicUpperLeftPixel - Image Raw Details Mosaic Upper Left Pixel: b +[Lytro, Lytro, Image] ImageRawDetailsPixelFormatBlackB - Image Raw Details Pixel Format Black B: 168 +[Lytro, Lytro, Image] ImageRawDetailsPixelFormatBlackGb - Image Raw Details Pixel Format Black Gb: 168 +[Lytro, Lytro, Image] ImageRawDetailsPixelFormatBlackGr - Image Raw Details Pixel Format Black Gr: 168 +[Lytro, Lytro, Image] ImageRawDetailsPixelFormatBlackR - Image Raw Details Pixel Format Black R: 168 +[Lytro, Lytro, Image] ImageRawDetailsPixelFormatRightShift - Image Raw Details Pixel Format Right Shift: 0 +[Lytro, Lytro, Image] ImageRawDetailsPixelFormatWhiteB - Image Raw Details Pixel Format White B: 4095 +[Lytro, Lytro, Image] ImageRawDetailsPixelFormatWhiteGb - Image Raw Details Pixel Format White Gb: 4095 +[Lytro, Lytro, Image] ImageRawDetailsPixelFormatWhiteGr - Image Raw Details Pixel Format White Gr: 4095 +[Lytro, Lytro, Image] ImageRawDetailsPixelFormatWhiteR - Image Raw Details Pixel Format White R: 4095 +[Lytro, Lytro, Image] ImageRawDetailsPixelPackingBitsPerPixel - Image Raw Details Pixel Packing Bits Per Pixel: 12 +[Lytro, Lytro, Image] ImageRawDetailsPixelPackingEndianness - Image Raw Details Pixel Packing Endianness: big +[Lytro, Lytro, Image] ImageRepresentation - Image Representation: rawPacked +[Lytro, Lytro, Image] ImageWidth - Image Width: 3280 +[Lytro, Lytro, Image] ModesCreative - Modes Creative: tap +[Lytro, Lytro, Image] ModesRegionOfInterestArrayType - Modes Region Of Interest Array Type: exposure +[Lytro, Lytro, Image] ModesRegionOfInterestArrayX - Modes Region Of Interest Array X: 0.26874190568923950195 +[Lytro, Lytro, Image] ModesRegionOfInterestArrayY - Modes Region Of Interest Array Y: 0.55208665132522583008 +[Lytro, Lytro, Image] ModesRegionOfInterestArrayType - Modes Region Of Interest Array Type: creative +[Lytro, Lytro, Image] ModesRegionOfInterestArrayX - Modes Region Of Interest Array X: 0.26874190568923950195 +[Lytro, Lytro, Image] ModesRegionOfInterestArrayY - Modes Region Of Interest Array Y: 0.55208665132522583008 +[Lytro, Lytro, Camera] Type - Camera Type: lightField +[Lytro, Lytro, Camera] CameraSerialNumber - Serial Number: A202110453 +[Lytro, Lytro, Camera] DevicesSensorSensorSerial - Sensor Serial Number: 0x7096E34F554BDE10 +[Composite, Composite, Image] Exif-Aperture - Aperture: 1.9 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3280x3280 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 10.8 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 6.7 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/250 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.005 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 45.5 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.4 mm (35 mm equivalent: 43.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 4.83 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 10.0 diff --git a/ExifTool/t/M2TS.t b/ExifTool/t/M2TS.t new file mode 100644 index 0000000..b2949ef --- /dev/null +++ b/ExifTool/t/M2TS.t @@ -0,0 +1,29 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/M2TS.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::M2TS; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'M2TS'; +my $testnum = 1; + +# test 2: Extract information from test image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Unknown => 1); + my $info = $exifTool->ImageInfo('t/images/M2TS.mts'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/M2TS_2.out b/ExifTool/t/M2TS_2.out new file mode 100644 index 0000000..f95ee8e --- /dev/null +++ b/ExifTool/t/M2TS_2.out @@ -0,0 +1,31 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.22 +[ExifTool, ExifTool, ExifTool] Warning - Warning: [minor] The ExtractEmbedded option may find more tags in the video data +[File, System, Other] FileName - File Name: M2TS.mts +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1344 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2009:07:08 13:57:28-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2021:01:25 10:41:15-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: M2TS +[File, File, Other] FileTypeExtension - File Type Extension: mts +[File, File, Other] MIMEType - MIME Type: video/m2ts +[M2TS, M2TS, Video] VideoStreamType - Video Stream Type: H.264 (AVC) Video +[M2TS, M2TS, Video] AudioStreamType - Audio Stream Type: A52/AC-3 Audio +[M2TS, AC3, Audio] AudioBitrate - Audio Bitrate: 256 kbps +[M2TS, AC3, Audio] SurroundMode - Surround Mode: Not indicated +[M2TS, AC3, Audio] AudioChannels - Audio Channels: 2 +[M2TS, M2TS, Video] Duration - Duration: 0 s +[M2TS, AC3, Audio] AudioSampleRate - Audio Sample Rate: 48000 +[H264, H264, Video] ImageWidth - Image Width: 1920 +[H264, H264, Video] ImageHeight - Image Height: 1080 +[H264, H264, Time] 24 - Date/Time Original: 2008:12:31 23:16:24+01:00 +[H264, H264, Camera] 0 - Aperture Setting: 2.4 +[H264, H264, Camera] 1 - Gain: 9 dB +[H264, H264, Camera] 1 - Image Stabilization: n/a +[H264, H264, Image] 1.1 - Exposure Time: 1/25 +[H264, H264, Camera] 0 - Make: Canon +[H264, H264, Camera] 0 - Recording Mode: FXP +[Composite, Composite, Image] Exif-ImageSize - Image Size: 1920x1080 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 2.1 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/25 diff --git a/ExifTool/t/MIE.t b/ExifTool/t/MIE.t new file mode 100644 index 0000000..6938d2d --- /dev/null +++ b/ExifTool/t/MIE.t @@ -0,0 +1,84 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/MIE.t". + +BEGIN { + $| = 1; print "1..6\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::MIE; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'MIE'; +my $testnum = 1; + +# test 2: Extract information from MIE.mie +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/MIE.mie', '-filename', '-directory'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write MIE information (also test Escape option when writing) +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(IgnoreMinorErrors => 1); # to copy invalid thumbnail + $exifTool->SetNewValuesFromFile('t/images/Nikon.jpg','*:*'); + $exifTool->SetNewValue('EXIF:XResolution' => 200); + $exifTool->SetNewValue('MIE:FNumber' => 11); + $exifTool->SetNewValue('XMP:Creator' => 'phil'); + $exifTool->SetNewValue('IPTC:Keywords' => 'cool'); + $exifTool->SetNewValue('MIE:GPSLongitude' => -1.5); + $exifTool->Options(Escape => 'HTML'); + $exifTool->SetNewValue('MIE:PhoneNumber' => 'k&uuml;hl'); + $exifTool->Options(Escape => undef); + my $testfile = "t/${testname}_${testnum}_failed.mie"; + unlink $testfile; + $exifTool->WriteInfo('t/images/MIE.mie', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 4: Create a MIE file from scratch (also test Escape option when copying) +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(IgnoreMinorErrors => 1); # to copy invalid thumbnail + $exifTool->Options(Escape => 'HTML'); + $exifTool->SetNewValuesFromFile('t/images/MIE.mie'); + my $testfile = "t/${testname}_${testnum}_failed.mie"; + unlink $testfile; + $exifTool->WriteInfo(undef, $testfile); + $exifTool->Options(Escape => undef); # reset Escape option + my $info = $exifTool->ImageInfo($testfile, '-filename', '-directory'); + if (check($exifTool, $info, $testname, $testnum, 2)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 5-6: Test reading different Charsets +foreach (qw(Latin Cyrillic)) { + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Charset => $_); + my $info = $exifTool->ImageInfo('t/images/MIE.mie', 'comment-ru_ru'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/MIE_2.out b/ExifTool/t/MIE_2.out new file mode 100644 index 0000000..a3c71ab --- /dev/null +++ b/ExifTool/t/MIE_2.out @@ -0,0 +1,69 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileSize - File Size: 2.1 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:42-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: MIE +[File, File, Other] FileTypeExtension - File Type Extension: mie +[File, File, Other] MIMEType - MIME Type: image/x-mie-jpeg +[MIE, MIE-Main, Other] 0Type - Subfile Type: JPEG +[MIE, MIE-Main, Other] 0Vers - MIE Version: 1.1 +[MIE, MIE-Main, Other] 1Name - Subfile Name: dummy.jpg +[MIE, MIE-Main, Other] 2MIME - Subfile MIME Type: image/jpeg +[MIE, MIE-Camera, Camera] ColorTemperature - Color Temperature: 5200 +[MIE, MIE-Camera, Camera] Contrast - Contrast: 1 +[MIE, MIE-Camera, Camera] ExposureComp - Exposure Compensation: 0 +[MIE, MIE-Camera, Camera] ExposureMode - Exposure Mode: Manual +[MIE, MIE-Camera, Camera] ExposureTime - Exposure Time: 4 +[MIE, MIE-Flash, Camera] ExposureComp - Flash Exposure Comp: 0 +[MIE, MIE-Flash, Camera] GuideNumber - Flash Guide Number: 0 +[MIE, MIE-Camera, Camera] FocusMode - Focus Mode: Manual Focus +[MIE, MIE-Camera, Camera] ISO - ISO: 100 +[MIE, MIE-Lens, Camera] FNumber - F Number: 3.5 +[MIE, MIE-Lens, Camera] MaxAperture - Max Aperture: 4 +[MIE, MIE-Lens, Camera] MinAperture - Min Aperture: 27 +[MIE, MIE-Camera, Camera] Make - Make: FUJIFILM +[MIE, MIE-Camera, Camera] Model - Model: FinePix2400Zoom +[MIE, MIE-Orient, Camera] Rotation - Rotation: 90 +[MIE, MIE-Camera, Camera] OwnerName - Owner Name: Phil Harvey +[MIE, MIE-Camera, Camera] Saturation - Saturation: 1 +[MIE, MIE-Camera, Camera] SerialNumber - Serial Number: 0560012345 +[MIE, MIE-Camera, Camera] Sharpness - Sharpness: 1 +[MIE, MIE-Camera, Camera] ShootingMode - Shooting Mode: Manual +[MIE, MIE-Doc, Document] Comment - Comment: I can eat glass and it doesn't hurt me. +[MIE, MIE-Doc, Document] Comment-cn_ZH - Comment (cn_ZH): 我能åžä¸‹çŽ»ç’ƒè€Œä¸ä¼¤èº«ä½“。 +[MIE, MIE-Doc, Document] Comment-de_DE - Comment (de_DE): Ich kann Glas essen, ohne mir weh zu tun. +[MIE, MIE-Doc, Document] Comment-el_GR - Comment (el_GR): ΜποÏÏŽ να φάω σπασμένα γυαλιά χωÏίς να πάθω τίποτα. +[MIE, MIE-Doc, Document] Comment-en_CA - Comment (en_CA): I can eat glass and it doesn't hurt me, eh. +[MIE, MIE-Doc, Document] Comment-en_GB - Comment (en_GB): I say, I can eat glass and it doesn't hurt me old boy. +[MIE, MIE-Doc, Document] Comment-en_US - Comment (en_US): Dude! Munching glass don't bust my chops. +[MIE, MIE-Doc, Document] Comment-es_ES - Comment (es_ES): Puedo comer vidrio, no me hace daño. +[MIE, MIE-Doc, Document] Comment-fr_FR - Comment (fr_FR): Je peux manger du verre, ça ne me fait pas de mal. +[MIE, MIE-Doc, Document] Comment-hi_IN - Comment (hi_IN): मैं काà¤à¤š खा सकता हूà¤, मà¥à¤à¥‡ उस से कोई पीडा नहीं होती. +[MIE, MIE-Doc, Document] Comment-it_IT - Comment (it_IT): Posso mangiare il vetro e non mi fa male. +[MIE, MIE-Doc, Document] Comment-ja_JP - Comment (ja_JP): ç§ã¯ã‚¬ãƒ©ã‚¹ã‚’食ã¹ã‚‰ã‚Œã¾ã™ã€‚ãれã¯ç§ã‚’å‚·ã¤ã‘ã¾ã›ã‚“。 +[MIE, MIE-Doc, Document] Comment-ru_RU - Comment (ru_RU): Я могу еÑть Ñтекло, оно мне не вредит. +[MIE, MIE-Doc, Author] Copyright - Copyright: © Copyright Phil Harvey +[MIE, MIE-Doc, Time] CreateDate - Create Date: 2001:05:19 18:36:41 +[MIE, MIE-Doc, Document] Keywords - Keywords: ExifTool, Test, MIE +[MIE, MIE-Doc, Time] ModifyDate - Modify Date: 2004:02:26 09:36:46 +[MIE, MIE-Doc, Time] OriginalDate - Date/Time Original: 2001:05:19 18:36:41 +[MIE, MIE-Doc, Document] References - References: http://www.columbia.edu/kermit/utf8.html (Comment strings) +[MIE, MIE-Doc, Document] Software - Software: Adobe Photoshop 7.0 +[MIE, MIE-Doc, Document] Title - Title: Test IPTC picture +[MIE, MIE-Doc, Document] URL - URL: https://exiftool.org/ +[MIE, MIE-Geo, Location] City - City: Kingston +[MIE, MIE-Geo, Location] Country - Country: Canada +[MIE, MIE-Geo, Location] State - State: Ont +[MIE, MIE-Image, Image] ColorSpace - Color Space: sRGB +[MIE, MIE-Image, Image] Components - Components Configuration: YCbCr +[MIE, MIE-Image, Image] ImageSize - Image Size: 100x80 +[MIE, MIE-Image, Image] Resolution - Resolution: 118x118(/cm) +[MIE, MIE-Thumbnail, Image] ImageSize - Thumbnail Image Size: 160x120 +[MIE, MIE-Thumbnail, Preview] data - Thumbnail Image: (Binary data 28 bytes) +[MIE, MIE-Main, Other] data - Subfile Data: (Binary data 20 bytes) +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 4 +[Composite, Composite, Image] Exif-LightValue - Light Value: 1.6 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.008 diff --git a/ExifTool/t/MIE_3.out b/ExifTool/t/MIE_3.out new file mode 100644 index 0000000..6020074 --- /dev/null +++ b/ExifTool/t/MIE_3.out @@ -0,0 +1,137 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.57 +[File, System, Other] FileName - File Name: MIE_3_failed.mie +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 6.3 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2023:02:13 20:46:22-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2023:02:13 20:46:22-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2023:02:13 20:46:22-05:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: MIE +[File, File, Other] FileTypeExtension - File Type Extension: mie +[File, File, Other] MIMEType - MIME Type: image/x-mie-jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: ff6b1fa510bbe0d2c2323752af38a86b +[MIE, MIE-Main, Other] 0Type - Subfile Type: JPEG +[MIE, MIE-Main, Other] 0Vers - MIE Version: 1.1 +[MIE, MIE-Main, Other] 1Name - Subfile Name: dummy.jpg +[MIE, MIE-Main, Other] 2MIME - Subfile MIME Type: image/jpeg +[MIE, MIE-Camera, Camera] ColorTemperature - Color Temperature: 5200 +[MIE, MIE-Camera, Camera] Contrast - Contrast: 1 +[MIE, MIE-Camera, Camera] ExposureComp - Exposure Compensation: 0 +[MIE, MIE-Camera, Camera] ExposureMode - Exposure Mode: Manual +[MIE, MIE-Camera, Camera] ExposureTime - Exposure Time: 4 +[MIE, MIE-Flash, Camera] ExposureComp - Flash Exposure Comp: 0 +[MIE, MIE-Flash, Camera] GuideNumber - Flash Guide Number: 0 +[MIE, MIE-Camera, Camera] FocusMode - Focus Mode: Manual Focus +[MIE, MIE-Camera, Camera] ISO - ISO: 100 +[MIE, MIE-Lens, Camera] FNumber - F Number: 11 +[MIE, MIE-Lens, Camera] MaxAperture - Max Aperture: 4 +[MIE, MIE-Lens, Camera] MinAperture - Min Aperture: 27 +[MIE, MIE-Camera, Camera] Make - Make: FUJIFILM +[MIE, MIE-Camera, Camera] Model - Model: FinePix2400Zoom +[MIE, MIE-Orient, Camera] Rotation - Rotation: 90 +[MIE, MIE-Camera, Camera] OwnerName - Owner Name: Phil Harvey +[MIE, MIE-Camera, Camera] Saturation - Saturation: 1 +[MIE, MIE-Camera, Camera] SerialNumber - Serial Number: 0560012345 +[MIE, MIE-Camera, Camera] Sharpness - Sharpness: 1 +[MIE, MIE-Camera, Camera] ShootingMode - Shooting Mode: Manual +[MIE, MIE-Doc, Document] Comment - Comment: I can eat glass and it doesn't hurt me. +[MIE, MIE-Doc, Document] Comment-cn_ZH - Comment (cn_ZH): 我能åžä¸‹çŽ»ç’ƒè€Œä¸ä¼¤èº«ä½“。 +[MIE, MIE-Doc, Document] Comment-de_DE - Comment (de_DE): Ich kann Glas essen, ohne mir weh zu tun. +[MIE, MIE-Doc, Document] Comment-el_GR - Comment (el_GR): ΜποÏÏŽ να φάω σπασμένα γυαλιά χωÏίς να πάθω τίποτα. +[MIE, MIE-Doc, Document] Comment-en_CA - Comment (en_CA): I can eat glass and it doesn't hurt me, eh. +[MIE, MIE-Doc, Document] Comment-en_GB - Comment (en_GB): I say, I can eat glass and it doesn't hurt me old boy. +[MIE, MIE-Doc, Document] Comment-en_US - Comment (en_US): Dude! Munching glass don't bust my chops. +[MIE, MIE-Doc, Document] Comment-es_ES - Comment (es_ES): Puedo comer vidrio, no me hace daño. +[MIE, MIE-Doc, Document] Comment-fr_FR - Comment (fr_FR): Je peux manger du verre, ça ne me fait pas de mal. +[MIE, MIE-Doc, Document] Comment-hi_IN - Comment (hi_IN): मैं काà¤à¤š खा सकता हूà¤, मà¥à¤à¥‡ उस से कोई पीडा नहीं होती. +[MIE, MIE-Doc, Document] Comment-it_IT - Comment (it_IT): Posso mangiare il vetro e non mi fa male. +[MIE, MIE-Doc, Document] Comment-ja_JP - Comment (ja_JP): ç§ã¯ã‚¬ãƒ©ã‚¹ã‚’食ã¹ã‚‰ã‚Œã¾ã™ã€‚ãれã¯ç§ã‚’å‚·ã¤ã‘ã¾ã›ã‚“。 +[MIE, MIE-Doc, Document] Comment-ru_RU - Comment (ru_RU): Я могу еÑть Ñтекло, оно мне не вредит. +[MIE, MIE-Doc, Author] Copyright - Copyright: © Copyright Phil Harvey +[MIE, MIE-Doc, Time] CreateDate - Create Date: 2001:05:19 18:36:41 +[MIE, MIE-Doc, Document] Keywords - Keywords: ExifTool, Test, MIE +[MIE, MIE-Doc, Time] ModifyDate - Modify Date: 2004:02:26 09:36:46 +[MIE, MIE-Doc, Time] OriginalDate - Date/Time Original: 2001:05:19 18:36:41 +[MIE, MIE-Doc, Author] Phone - Phone Number: kühl +[MIE, MIE-Doc, Document] References - References: http://www.columbia.edu/kermit/utf8.html (Comment strings) +[MIE, MIE-Doc, Document] Software - Software: Adobe Photoshop 7.0 +[MIE, MIE-Doc, Document] Title - Title: Test IPTC picture +[MIE, MIE-Doc, Document] URL - URL: https://exiftool.org/ +[MIE, MIE-Geo, Location] City - City: Kingston +[MIE, MIE-Geo, Location] Country - Country: Canada +[MIE, MIE-GPS, Location] Longitude - GPS Longitude: 1 deg 30' 0.00" W +[MIE, MIE-Geo, Location] State - State: Ont +[MIE, MIE-Image, Image] ColorSpace - Color Space: sRGB +[MIE, MIE-Image, Image] Components - Components Configuration: YCbCr +[MIE, MIE-Image, Image] ImageSize - Image Size: 100x80 +[MIE, MIE-Image, Image] Resolution - Resolution: 118x118(/cm) +[MIE, MIE-Thumbnail, Image] ImageSize - Thumbnail Image Size: 160x120 +[MIE, MIE-Thumbnail, Preview] data - Thumbnail Image: (Binary data 28 bytes) +[MIE, MIE-Main, Other] data - Subfile Data: (Binary data 20 bytes) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: NIKON +[EXIF, IFD0, Camera] 272 - Camera Model Name: E775 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 200 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: E775v1.3u +[EXIF, IFD0, Time] 306 - Modify Date: 2001:08:01 12:57:23 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/213 +[EXIF, ExifIFD, Image] 33437 - F Number: 9.4 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:08:01 12:57:23 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:08:01 12:57:23 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.4 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 8.6 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1600 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1200 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 300 +[EXIF, IFD1, Image] 283 - Y Resolution: 300 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 3043 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Nikon, Camera] 1 - Maker Note Version: 1.00 +[MakerNotes, Nikon, Image] 2 - ISO: 0 +[MakerNotes, Nikon, Camera] 3 - Color Mode: Color +[MakerNotes, Nikon, Camera] 4 - Quality: Fine +[MakerNotes, Nikon, Camera] 5 - White Balance: Auto +[MakerNotes, Nikon, Camera] 6 - Sharpness: Auto +[MakerNotes, Nikon, Camera] 7 - Focus Mode: AF-C +[MakerNotes, Nikon, Camera] 8 - Flash Setting: +[MakerNotes, Nikon, Camera] 15 - ISO Selection: Auto +[MakerNotes, Nikon, Camera] 128 - Image Adjustment: Normal +[MakerNotes, Nikon, Camera] 130 - Auxiliary Lens: Off +[MakerNotes, Nikon, Camera] 133 - Manual Focus Distance: undef +[MakerNotes, Nikon, Camera] 134 - Digital Zoom: 1 +[MakerNotes, Nikon, Camera] 0 - AF Area Mode: Single Area +[MakerNotes, Nikon, Camera] 1 - AF Point: Center +[MakerNotes, Nikon, Camera] 2 - AF Points In Focus: (none) +[MakerNotes, Nikon, Camera] 143 - Scene Mode: +[MakerNotes, Nikon, Camera] 16 - Data Dump: (Binary data 122 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0100 +[IPTC, IPTC, Other] 25 - Keywords: cool +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.57 +[XMP, XMP-dc, Author] creator - Creator: phil +[Composite, Composite, Image] Exif-Aperture - Aperture: 9.4 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/213 +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.2 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 8.6 mm +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.008 diff --git a/ExifTool/t/MIE_5.out b/ExifTool/t/MIE_5.out new file mode 100644 index 0000000..9552277 --- /dev/null +++ b/ExifTool/t/MIE_5.out @@ -0,0 +1 @@ +[MIE, MIE-Doc, Document] Comment-ru_RU - Comment (ru_RU): ? ???? ???? ??????, ??? ??? ?? ??????. diff --git a/ExifTool/t/MIE_6.out b/ExifTool/t/MIE_6.out new file mode 100644 index 0000000..0059a94 --- /dev/null +++ b/ExifTool/t/MIE_6.out @@ -0,0 +1 @@ +[MIE, MIE-Doc, Document] Comment-ru_RU - Comment (ru_RU): ß ìîãó åñòü ñòåêëî, îíî ìíå íå âðåäèò. diff --git a/ExifTool/t/MIFF.t b/ExifTool/t/MIFF.t new file mode 100644 index 0000000..e51aad5 --- /dev/null +++ b/ExifTool/t/MIFF.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/MIFF.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::MIFF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'MIFF'; +my $testnum = 1; + +# test 2: Extract information from MIFF.miff +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/MIFF.miff'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/MIFF_2.out b/ExifTool/t/MIFF_2.out new file mode 100644 index 0000000..9b0fde8 --- /dev/null +++ b/ExifTool/t/MIFF_2.out @@ -0,0 +1,113 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.46 +[File, System, Other] FileName - File Name: MIFF.miff +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 3.8 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:09:19 14:15:40-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:18 10:49:22-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: MIFF +[File, File, Other] FileTypeExtension - File Type Extension: miff +[File, File, Other] MIMEType - MIME Type: application/x-magick-image +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[MIFF, MIFF, Image] id - ID: ImageMagick +[MIFF, MIFF, Image] class - Class: DirectClass +[MIFF, MIFF, Image] matte - Matte: False +[MIFF, MIFF, Image] columns - Image Width: 8 +[MIFF, MIFF, Image] rows - Image Height: 8 +[MIFF, MIFF, Image] depth - Depth: 8 +[MIFF, MIFF, Image] Resolution - Resolution: 72x72 +[MIFF, MIFF, Image] units - Units: pixels-per-inch +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Other] 25 - Keywords: ExifTool, Test, MIFF +[IPTC, IPTC, Other] 120 - Caption-Abstract: MIFF test +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: 9e02efbd9bdc1dc192279e30a0e27323 +[Photoshop, Photoshop, Image] 0 - X Resolution: 72 +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: 72 +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Image] 0 - Print Style: Centered +[Photoshop, Photoshop, Image] 2 - Print Position: 0 0 +[Photoshop, Photoshop, Image] 10 - Print Scale: 1 +[Photoshop, Photoshop, Image] 1037 - Global Angle: 30 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 30 +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image] 1054 - URL List: +[Photoshop, Photoshop, Image] 0 - Photoshop Quality: 2 +[Photoshop, Photoshop, Image] 1 - Photoshop Format: Standard +[EXIF, IFD0, Image] 270 - Image Description: A witty caption +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:21 07:47:01 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: Copyright 2004 Phil Harvey +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 8 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 8 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 854 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 0 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 5.47 +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: MIFF, Test +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-photoshop, Image] Category - Category: 1 +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.0 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.6 diff --git a/ExifTool/t/MOI.t b/ExifTool/t/MOI.t new file mode 100644 index 0000000..69bed63 --- /dev/null +++ b/ExifTool/t/MOI.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/MOI.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::MOI; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'MOI'; +my $testnum = 1; + +# test 2: Extract information from a MOI file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/MOI.moi', '-system:*'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/MOI_2.out b/ExifTool/t/MOI_2.out new file mode 100644 index 0000000..ff0a702 --- /dev/null +++ b/ExifTool/t/MOI_2.out @@ -0,0 +1,11 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.76 +[File, File, Other] FileType - File Type: MOI +[File, File, Other] FileTypeExtension - File Type Extension: moi +[File, File, Other] MIMEType - MIME Type: application/octet-stream +[MOI, MOI, Video] 0 - MOI Version: V6 +[MOI, MOI, Time] 6 - Date/Time Original: 2011:05:15 17:58:48.000 +[MOI, MOI, Video] 14 - Duration: 8.16 s +[MOI, MOI, Video] 128 - Aspect Ratio: 4:3 PAL +[MOI, MOI, Audio] 132 - Audio Codec: AC3 +[MOI, MOI, Audio] 134 - Audio Bitrate: 224 kbps +[MOI, MOI, Video] 218 - Video Bitrate: 8.5 Mbps diff --git a/ExifTool/t/MP3.t b/ExifTool/t/MP3.t new file mode 100644 index 0000000..232e3db --- /dev/null +++ b/ExifTool/t/MP3.t @@ -0,0 +1,29 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/MP3.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::ID3; +use Image::ExifTool::MPEG; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'MP3'; +my $testnum = 1; + +# test 2: Extract information from test image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/MP3.mp3'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/MP3_2.out b/ExifTool/t/MP3_2.out new file mode 100644 index 0000000..d7c15cd --- /dev/null +++ b/ExifTool/t/MP3_2.out @@ -0,0 +1,46 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: MP3.mp3 +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 395 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:05:13 07:46:22-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:12-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rwxr-xr-x +[File, File, Other] FileType - File Type: MP3 +[File, File, Other] FileTypeExtension - File Type Extension: mp3 +[File, File, Other] MIMEType - MIME Type: audio/mpeg +[File, File, Image] ID3Size - ID3 Size: 391 +[MPEG, MPEG, Audio] Bit11-12 - MPEG Audio Version: 1 +[MPEG, MPEG, Audio] Bit13-14 - Audio Layer: 3 +[MPEG, MPEG, Audio] Bit16-19 - Audio Bitrate: 128 kbps +[MPEG, MPEG, Audio] Bit20-21 - Sample Rate: 44100 +[MPEG, MPEG, Audio] Bit24-25 - Channel Mode: Joint Stereo +[MPEG, MPEG, Audio] Bit26 - MS Stereo: On +[MPEG, MPEG, Audio] Bit27 - Intensity Stereo: Off +[MPEG, MPEG, Audio] Bit28 - Copyright Flag: True +[MPEG, MPEG, Audio] Bit29 - Original Media: True +[MPEG, MPEG, Audio] Bit30-31 - Emphasis: None +[ID3, ID3v2_2, Audio] TRK - Track: 1/5 +[ID3, ID3v2_2, Audio] TPA - Part Of Set: 1/2 +[ID3, ID3v2_2, Audio] RVA - Relative Volume Adjustment: +18.0% Right, +18.0% Left +[ID3, ID3v2_2, Audio] ULT - Lyrics: Do-wap she-bang +[ID3, ID3v2_2, Image] PIC-1 - Picture Format: JPG +[ID3, ID3v2_2, Image] PIC-2 - Picture Type: Other +[ID3, ID3v2_2, Image] PIC-3 - Picture Description: comment +[ID3, ID3v2_2, Preview] PIC - Picture: (Binary data 15 bytes) +[ID3, ID3v2_2, Audio] TT2 - Title: ExifTool Test +[ID3, ID3v2_2, Author] TP1 - Artist: Phil Harvey +[ID3, ID3v2_2, Audio] TCM - Composer: A Composer +[ID3, ID3v2_2, Audio] TAL - Album: Phil's Greatest Hits +[ID3, ID3v2_2, Audio] TT1 - Grouping: This group +[ID3, ID3v2_2, Time] TYE - Year: 2005 +[ID3, ID3v2_2, Audio] TCO - Genre: Testing +[ID3, ID3v2_2, Audio] COM - Comment: My Comments +[ID3, ID3v1, Audio] 3 - Title: Title +[ID3, ID3v1, Author] 33 - Artist: Artist +[ID3, ID3v1, Audio] 63 - Album: Album +[ID3, ID3v1, Time] 93 - Year: 2003 +[ID3, ID3v1, Audio] 97 - Comment: Comment +[ID3, ID3v1, Audio] 127 - Genre: Hip-Hop +[Composite, Composite, Time] ID3-DateTimeOriginal - Date/Time Original: 2005 +[Composite, Composite, Video] MPEG-Duration - Duration: 0.00 s (approx) diff --git a/ExifTool/t/MRC.t b/ExifTool/t/MRC.t new file mode 100644 index 0000000..bd2e676 --- /dev/null +++ b/ExifTool/t/MRC.t @@ -0,0 +1,29 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/MRC.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::MRC; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'MRC'; +my $testnum = 1; + +# test 2: Extract information from MRC.mrc +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(ExtractEmbedded => 1); + my $info = $exifTool->ImageInfo('t/images/MRC.mrc'); + notOK() unless check($exifTool, $info, $testname, $testnum, undef, 3); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/MRC_2.out b/ExifTool/t/MRC_2.out new file mode 100644 index 0000000..ca635e7 --- /dev/null +++ b/ExifTool/t/MRC_2.out @@ -0,0 +1,157 @@ +[ExifTool, ExifTool, ExifTool, Main] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other, Main] FileName - File Name: MRC.mrc +[File, System, Other, Main] Directory - Directory: t/images +[File, System, Other, Main] FileSize - File Size: 2.6 kB +[File, System, Time, Main] FileModifyDate - File Modification Date/Time: 2021:04:22 09:31:39-04:00 +[File, System, Time, Main] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:43-04:00 +[File, System, Time, Main] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other, Main] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other, Main] FileType - File Type: MRC +[File, File, Other, Main] FileTypeExtension - File Type Extension: mrc +[File, File, Other, Main] MIMEType - MIME Type: image/x-mrc +[File, File, Image, Main] 0 - Image Width: 4096 +[File, File, Image, Main] 1 - Image Height: 4096 +[File, File, Image, Main] 2 - Image Depth: 2 +[File, File, Image, Main] 3 - Image Mode: 16-bit unsigned integer +[File, File, Image, Main] 4 - Start Point: 0 0 0 +[File, File, Image, Main] 7 - Grid Size: 4096 4096 2 +[File, File, Image, Main] 10 - Cell Width: 15289.4248046875 +[File, File, Image, Main] 11 - Cell Height: 15289.4248046875 +[File, File, Image, Main] 12 - Cell Depth: 0 +[File, File, Image, Main] 13 - Cell Alpha: 90 +[File, File, Image, Main] 14 - Cell Beta: 90 +[File, File, Image, Main] 15 - Cell Gamma: 90 +[File, File, Image, Main] 16 - Image Width Axis: X +[File, File, Image, Main] 17 - Image Height Axis: Y +[File, File, Image, Main] 18 - Image Depth Axis: Z +[File, File, Image, Main] 19 - Density Min: 1958 +[File, File, Image, Main] 20 - Density Max: 5662 +[File, File, Image, Main] 21 - Density Mean: 3155.2578125 +[File, File, Image, Main] 22 - Space Group Number: 0 +[File, File, Image, Main] 23 - Extended Header Size: 1536 +[File, File, Image, Main] 26 - Extended Header Type: FEI1 +[File, File, Image, Main] 27 - MRC Version: 20140 +[File, File, Image, Main] 49 - Origin: 0 0 0 +[File, File, Image, Main] 53 - Machine Stamp: 0x44 0x44 0x00 0x00 +[File, File, Image, Main] 54 - RMS Deviation: -1 +[File, File, Image, Main] 55 - Number Of Labels: 0 +[File, File, Image, Main] 0 - Metadata Size: 768 +[File, File, Image, Main] 4 - Metadata Version: 0 +[File, File, Image, Main] 8 - Bitmask 1: 0xffffffff +[File, File, Time, Main] 12 - Time Stamp: 2020:10:21 13:54:27 +[File, File, Image, Main] 20 - Microscope Type: TALOS-D5197 +[File, File, Image, Main] 36 - Microscope ID: 6419 +[File, File, Image, Main] 52 - Application: Tomography +[File, File, Image, Main] 68 - App Version: 4.10.0.65 +[File, File, Image, Main] 84 - High Tension: 200000 +[File, File, Image, Main] 92 - Dose: 3.51360899930879e+20 +[File, File, Image, Main] 100 - Alpha Tilt: -64.0024700625157 +[File, File, Image, Main] 108 - Beta Tilt: 0 +[File, File, Image, Main] 116 - X Stage: -8.8173405e-05 +[File, File, Image, Main] 124 - Y Stage: -2.8139594e-05 +[File, File, Image, Main] 132 - Z Stage: 3.536763e-05 +[File, File, Image, Main] 140 - Tilt Axis Angle: -0.847676487969243 +[File, File, Image, Main] 148 - Dual Axis Rot: 0 +[File, File, Image, Main] 156 - Pixel Size X: 3.73276964893421e-10 +[File, File, Image, Main] 164 - Pixel Size Y: 3.73276964893421e-10 +[File, File, Image, Main] 220 - Defocus: -1.5e-06 +[File, File, Image, Main] 228 - STEM Defocus: -1.5e-06 +[File, File, Image, Main] 236 - Applied Defocus: -1.5e-06 +[File, File, Image, Main] 244 - Instrument Mode: TEM +[File, File, Image, Main] 248 - Projection Mode: Imaging +[File, File, Image, Main] 252 - Objective Lens: HM +[File, File, Image, Main] 268 - High Magnification Mode: SA +[File, File, Image, Main] 284 - Probe Mode: Nano +[File, File, Image, Main] 288 - EFTEM On: No +[File, File, Image, Main] 289 - Magnification: 28000 +[File, File, Image, Main] 297 - Bitmask 2: 0x0cfff01f +[File, File, Image, Main] 301 - Camera Length: 0 +[File, File, Image, Main] 309 - Spot Index: 7 +[File, File, Image, Main] 313 - Illumination Area: 0.473917752802174 +[File, File, Image, Main] 321 - Intensity: 0.473917752802174 +[File, File, Image, Main] 329 - Convergence Angle: 0 +[File, File, Image, Main] 387 - Shift Offset X: 0 +[File, File, Image, Main] 395 - Shift Offset Y: 0 +[File, File, Image, Main] 403 - Shift X: -0.00204008771106601 +[File, File, Image, Main] 411 - Shift Y: 0.009031618013978 +[File, File, Image, Main] 419 - Integration Time: 0.299076 +[File, File, Image, Main] 427 - Binning Width: 1 +[File, File, Image, Main] 431 - Binning Height: 1 +[File, File, Image, Main] 435 - Camera Name: BM-Falcon +[File, File, Image, Main] 451 - Readout Area Left: 0 +[File, File, Image, Main] 455 - Readout Area Top: 0 +[File, File, Image, Main] 459 - Readout Area Right: 4096 +[File, File, Image, Main] 463 - Readout Area Bottom: 4096 +[File, File, Image, Main] 472 - Direct Det Electron Counting: No +[File, File, Image, Main] 473 - Direct Det Align Frames: No +[File, File, Image, Main] 490 - Bitmask 3: 0x007e8040 +[File, File, Image, Main] 518 - Phase Plate: Yes +[File, File, Image, Main] 571 - Dwell Time: 0 +[File, File, Image, Main] 587 - Scan Size Left: 0 +[File, File, Image, Main] 591 - Scan Size Top: 0 +[File, File, Image, Main] 595 - Scan Size Right: 0 +[File, File, Image, Main] 599 - Scan Size Bottom: 0 +[File, File, Image, Main] 603 - Full Scan FOV X: 0.473917752802174 +[File, File, Image, Main] 611 - Full Scan FOV Y: 0.473917752802174 +[File, File, Image, Main] 748 - Bitmask 4: 0x00000000 +[File, File, Image, Doc1] 0 - Metadata Size: 768 +[File, File, Image, Doc1] 4 - Metadata Version: 0 +[File, File, Image, Doc1] 8 - Bitmask 1: 0xffffffff +[File, File, Time, Doc1] 12 - Time Stamp: 2020:10:21 13:55:19 +[File, File, Image, Doc1] 20 - Microscope Type: TALOS-D5197 +[File, File, Image, Doc1] 36 - Microscope ID: 6419 +[File, File, Image, Doc1] 52 - Application: Tomography +[File, File, Image, Doc1] 68 - App Version: 4.10.0.65 +[File, File, Image, Doc1] 84 - High Tension: 200000 +[File, File, Image, Doc1] 92 - Dose: 3.56817464869021e+20 +[File, File, Image, Doc1] 100 - Alpha Tilt: -62.0036705832692 +[File, File, Image, Doc1] 108 - Beta Tilt: 0 +[File, File, Image, Doc1] 116 - X Stage: -8.8182495e-05 +[File, File, Image, Doc1] 124 - Y Stage: -2.8140608e-05 +[File, File, Image, Doc1] 132 - Z Stage: 3.536763e-05 +[File, File, Image, Doc1] 140 - Tilt Axis Angle: -0.847676487969243 +[File, File, Image, Doc1] 148 - Dual Axis Rot: 0 +[File, File, Image, Doc1] 156 - Pixel Size X: 3.73276964893421e-10 +[File, File, Image, Doc1] 164 - Pixel Size Y: 3.73276964893421e-10 +[File, File, Image, Doc1] 220 - Defocus: -2.81146426624101e-06 +[File, File, Image, Doc1] 228 - STEM Defocus: -2.81146426624101e-06 +[File, File, Image, Doc1] 236 - Applied Defocus: -1.5e-06 +[File, File, Image, Doc1] 244 - Instrument Mode: TEM +[File, File, Image, Doc1] 248 - Projection Mode: Imaging +[File, File, Image, Doc1] 252 - Objective Lens: HM +[File, File, Image, Doc1] 268 - High Magnification Mode: SA +[File, File, Image, Doc1] 284 - Probe Mode: Nano +[File, File, Image, Doc1] 288 - EFTEM On: No +[File, File, Image, Doc1] 289 - Magnification: 28000 +[File, File, Image, Doc1] 297 - Bitmask 2: 0x0cfff01f +[File, File, Image, Doc1] 301 - Camera Length: 0 +[File, File, Image, Doc1] 309 - Spot Index: 7 +[File, File, Image, Doc1] 313 - Illumination Area: 0.473917752802174 +[File, File, Image, Doc1] 321 - Intensity: 0.473917752802174 +[File, File, Image, Doc1] 329 - Convergence Angle: 0 +[File, File, Image, Doc1] 387 - Shift Offset X: 0 +[File, File, Image, Doc1] 395 - Shift Offset Y: 0 +[File, File, Image, Doc1] 403 - Shift X: -0.00886260531842709 +[File, File, Image, Doc1] 411 - Shift Y: 0.00178224826231599 +[File, File, Image, Doc1] 419 - Integration Time: 0.299076 +[File, File, Image, Doc1] 427 - Binning Width: 1 +[File, File, Image, Doc1] 431 - Binning Height: 1 +[File, File, Image, Doc1] 435 - Camera Name: BM-Falcon +[File, File, Image, Doc1] 451 - Readout Area Left: 0 +[File, File, Image, Doc1] 455 - Readout Area Top: 0 +[File, File, Image, Doc1] 459 - Readout Area Right: 4096 +[File, File, Image, Doc1] 463 - Readout Area Bottom: 4096 +[File, File, Image, Doc1] 472 - Direct Det Electron Counting: No +[File, File, Image, Doc1] 473 - Direct Det Align Frames: No +[File, File, Image, Doc1] 490 - Bitmask 3: 0x007e8040 +[File, File, Image, Doc1] 518 - Phase Plate: Yes +[File, File, Image, Doc1] 571 - Dwell Time: 0 +[File, File, Image, Doc1] 587 - Scan Size Left: 0 +[File, File, Image, Doc1] 591 - Scan Size Top: 0 +[File, File, Image, Doc1] 595 - Scan Size Right: 0 +[File, File, Image, Doc1] 599 - Scan Size Bottom: 0 +[File, File, Image, Doc1] 603 - Full Scan FOV X: 0.473917752802174 +[File, File, Image, Doc1] 611 - Full Scan FOV Y: 0.473917752802174 +[File, File, Image, Doc1] 748 - Bitmask 4: 0x00000000 +[Composite, Composite, Image, Main] Exif-ImageSize - Image Size: 4096x4096 +[Composite, Composite, Image, Main] Exif-Megapixels - Megapixels: 16.8 diff --git a/ExifTool/t/MWG.t b/ExifTool/t/MWG.t new file mode 100644 index 0000000..50b6f97 --- /dev/null +++ b/ExifTool/t/MWG.t @@ -0,0 +1,100 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/MWG.t". + +BEGIN { + $| = 1; print "1..7\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::MWG; +Image::ExifTool::MWG::Load(); +$loaded = 1; +print "ok 1\n"; + +my $testname = 'MWG'; +my $testnum = 1; + +# test 2: Extract MWG information from test image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 0); + my $info = $exifTool->ImageInfo('t/images/MWG.jpg', 'MWG:*'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# tests 3-4: Write some MWG tags +{ + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('MWG:DateTimeOriginal' => '2009:10:25 15:13:44.567-04:00'); + $exifTool->SetNewValue('MWG:Creator' => 'Creator One'); + $exifTool->SetNewValue('MWG:Creator' => 'Creator Two'); + $exifTool->SetNewValue('MWG:City' => 'Some city'); + my @tags = qw( + EXIF:DateTimeOriginal EXIF:SubSecTimeOriginal + IPTC:DateCreated IPTC:TimeCreated XMP-photoshop:DateCreated + EXIF:Artist IPTC:By-line XMP-dc:Creator IPTC:City + XMP-photoshop:City XMP-iptcExt:LocationShownCity + ); + my $src; + foreach $src('MWG.jpg', 'Writer.jpg') { + ++$testnum; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->WriteInfo("t/images/$src", $testfile); + my $info = $exifTool->GetInfo('Warning'); + if ($$info{Warning}) { + warn "\n Warning: $$info{Warning}\n"; + notOK(); + } else { + $info = $exifTool->ImageInfo($testfile, @tags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + } + print "ok $testnum\n"; + } +} + +# test 5: Extract IPTC information from non-standard image while in strict MWG mode +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ExifTool.jpg', 'IPTC:*', 'Warning'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 6: Copy a tag with MWG feature active +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/MWG.jpg', 'Creator'); + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->WriteInfo(undef, $testfile); + my $info = $exifTool->ImageInfo($testfile, 'Creator'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 7: Extract MWG information from ExifTool.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ExifTool.jpg', 'MWG:*'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/MWG_2.out b/ExifTool/t/MWG_2.out new file mode 100644 index 0000000..42251d7 --- /dev/null +++ b/ExifTool/t/MWG_2.out @@ -0,0 +1,13 @@ +[Composite, MWG, Location] MWG-City - City: RIGHT-XMP-City +[Composite, MWG, Author] MWG-Copyright - Copyright: RIGHT-XMP-Copyright +[Composite, MWG, Location] MWG-Country - Country: RIGHT-IPTC-Country +[Composite, MWG, Author] MWG-Creator - Creator: RIGHT1of2-EXIF-Creator, RIGHT2of2-EXIF-Creator +[Composite, MWG, Time] MWG-DateTimeOriginal - Date/Time Original: 2001:01:01 01:11:11.111 +[Composite, MWG, Image] MWG-Description - Description: RIGHT-EXIF-Description +[Composite, MWG, Image] MWG-Keywords - Keywords: RIGHT1of2-XMP-Keywords, RIGHT2of2-XMP-Keywords +[Composite, MWG, Location] MWG-Location - Location: RIGHT-IPTC-Location +[Composite, MWG, Image] MWG-Orientation - Orientation: Rotate 90 CW +[Composite, MWG, Image] MWG-Rating - Rating: 3 +[Composite, MWG, Location] MWG-State - State: RIGHT-IPTC-State +[Composite, MWG, Time] MWG-CreateDate - Create Date: 2002:02:02 02:22:22+02:00 +[Composite, MWG, Time] MWG-ModifyDate - Modify Date: 2003:03:03 03:33:33.333+03:00 diff --git a/ExifTool/t/MWG_3.out b/ExifTool/t/MWG_3.out new file mode 100644 index 0000000..c6b280b --- /dev/null +++ b/ExifTool/t/MWG_3.out @@ -0,0 +1,11 @@ +[EXIF, IFD0, Author] 315 - Artist: Creator One, Creator Two +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2009:10:25 15:13:44 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 567 +[IPTC, IPTC, Time] 55 - Date Created: 2009:10:25 +[IPTC, IPTC, Time] 60 - Time Created: 15:13:44-04:00 +[IPTC, IPTC, Author] 80 - By-line: Creator One, Creator Two +[IPTC, IPTC, Location] 90 - City: Some city +[XMP, XMP-iptcExt, Location] LocationShownCity - Location Shown City: Some city +[XMP, XMP-dc, Author] creator - Creator: Creator One, Creator Two +[XMP, XMP-photoshop, Location] City - City: Some city +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2009:10:25 15:13:44.567-04:00 diff --git a/ExifTool/t/MWG_4.out b/ExifTool/t/MWG_4.out new file mode 100644 index 0000000..fcf1a5e --- /dev/null +++ b/ExifTool/t/MWG_4.out @@ -0,0 +1,7 @@ +[EXIF, IFD0, Author] 315 - Artist: Creator One, Creator Two +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2009:10:25 15:13:44 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 567 +[XMP, XMP-iptcExt, Location] LocationShownCity - Location Shown City: Some city +[XMP, XMP-dc, Author] creator - Creator: Creator One, Creator Two +[XMP, XMP-photoshop, Location] City - City: Some city +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2009:10:25 15:13:44.567-04:00 diff --git a/ExifTool/t/MWG_5.out b/ExifTool/t/MWG_5.out new file mode 100644 index 0000000..5f29fab --- /dev/null +++ b/ExifTool/t/MWG_5.out @@ -0,0 +1,23 @@ +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other] 105 - Headline: No headline +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My Position +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Author] 115 - Source: I'm the source +[IPTC, IPTC, Other] 25 - Keywords: jambalaya +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest is not current. XMP may be out of sync +[ExifTool, ExifTool, ExifTool] Warning - Warning: Ignored non-standard IPTC at JPEG-Trailer-FotoStation-IPTC +[ExifTool, ExifTool, ExifTool] Warning - Warning: Ignored non-standard IPTC at JPEG-Trailer-AFCP-IPTC diff --git a/ExifTool/t/MWG_6.out b/ExifTool/t/MWG_6.out new file mode 100644 index 0000000..d3227eb --- /dev/null +++ b/ExifTool/t/MWG_6.out @@ -0,0 +1,2 @@ +[XMP, XMP-dc, Author] creator - Creator: RIGHT1of2-EXIF-Creator, RIGHT2of2-EXIF-Creator +[Composite, MWG, Author] MWG-Creator - Creator: RIGHT1of2-EXIF-Creator, RIGHT2of2-EXIF-Creator diff --git a/ExifTool/t/MWG_7.out b/ExifTool/t/MWG_7.out new file mode 100644 index 0000000..bfc2890 --- /dev/null +++ b/ExifTool/t/MWG_7.out @@ -0,0 +1,11 @@ +[Composite, MWG, Location] MWG-City - City: Kingston +[Composite, MWG, Author] MWG-Copyright - Copyright: Copyright 2004 Phil Harvey +[Composite, MWG, Location] MWG-Country - Country: Canada +[Composite, MWG, Time] MWG-CreateDate - Create Date: 2001:05:19 18:36:41 +[Composite, MWG, Author] MWG-Creator - Creator: Phil Harvey +[Composite, MWG, Time] MWG-DateTimeOriginal - Date/Time Original: 2001:05:19 18:36:41 +[Composite, MWG, Image] MWG-Description - Description: A witty caption +[Composite, MWG, Image] MWG-Keywords - Keywords: jambalaya +[Composite, MWG, Time] MWG-ModifyDate - Modify Date: 2004:02:26 09:36:46 +[Composite, MWG, Image] MWG-Orientation - Orientation: Horizontal (normal) +[Composite, MWG, Location] MWG-State - State: Ont diff --git a/ExifTool/t/MXF.t b/ExifTool/t/MXF.t new file mode 100644 index 0000000..2612082 --- /dev/null +++ b/ExifTool/t/MXF.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/MXF.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::MXF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'MXF'; +my $testnum = 1; + +# test 2: Extract information from an MXF file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/MXF.mxf'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/MXF_2.out b/ExifTool/t/MXF_2.out new file mode 100644 index 0000000..e7149c3 --- /dev/null +++ b/ExifTool/t/MXF_2.out @@ -0,0 +1,79 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: MXF.mxf +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 7.5 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:12:19 19:21:47-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:43-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: MXF +[File, File, Other] FileTypeExtension - File Type Extension: mxf +[File, File, Other] MIMEType - MIME Type: application/mxf +[MXF, MXF, Video] 0 - MXF Version: 1.2 +[MXF, MXF, Time] 060e2b34.0101.0102.07020110.02040000 - Container Last Modify Date: 2010:12:20 00:14:40.228 +[MXF, MXF, Video] 060e2b34.0101.0102.03010201.05000000 - SDK Version: 1.2 +[MXF, MXF, Video] 060e2b34.0101.0102.05200701.02010000 - Application Supplier Name: Phil Harvey +[MXF, MXF, Video] 060e2b34.0101.0102.05200701.03010000 - Application Name: ExifTool +[MXF, MXF, Video] 060e2b34.0101.0102.05200701.05010000 - Application Version String: Based on MXFLib 1.0.1(16)-Release +[MXF, MXF, Video] 060e2b34.0101.0102.05200701.0a000000 - Toolkit Version: 1.0.1.16 released +[MXF, MXF, Video] 060e2b34.0101.0102.05200701.06010000 - Application Platform: MXFLib (darwin on x86_64) +[MXF, MXF, Time] 060e2b34.0101.0102.07020110.02030000 - Modify Date: 2010:12:20 00:14:40.224 +[MXF, MXF, Video] 060e2b34.0101.0102.05200701.02010000 - Application Supplier Name: Phil Harvey +[MXF, MXF, Video] 060e2b34.0101.0102.05200701.03010000 - Application Name: ExifTool +[MXF, MXF, Video] 060e2b34.0101.0102.05200701.05010000 - Application Version String: Based on MXFLib 1.0.1(16)-Release +[MXF, MXF, Video] 060e2b34.0101.0102.05200701.0a000000 - Toolkit Version: 1.0.1.16 released +[MXF, MXF, Video] 060e2b34.0101.0102.05200701.06010000 - Application Platform: MXFLib (darwin on x86_64) +[MXF, MXF, Time] 060e2b34.0101.0102.07020110.02030000 - Modify Date: 2010:12:20 00:14:40.228 +[MXF, MXF, Time] 060e2b34.0101.0102.07020110.01030000 - Create Date: 2010:12:20 00:14:40.224 +[MXF, MXF, Time] 060e2b34.0101.0102.07020110.02050000 - Package Last Modify Date: 2010:12:20 00:14:40.224 +[MXF, Track1, Video] 060e2b34.0101.0102.01070102.01000000 - Track Name: Timecode Track +[MXF, Track1, Video] 060e2b34.0101.0102.01040103.00000000 - Track Number: 0 +[MXF, Track1, Video] 060e2b34.0101.0102.07020103.01030000 - Origin: 0 s +[MXF, Track1, Video] 060e2b34.0101.0102.05300405.00000000 - Edit Rate: 1 +[MXF, Track1, Video] 060e2b34.0101.0102.01070101.00000000 - Track ID: 1 +[MXF, Track1, Video] 060e2b34.0101.0102.04070100.00000000 - Component Data Definition: SMPTE 12M Timecode Track +[MXF, Track1, Video] 060e2b34.0101.0102.07020201.01030000 - Duration: 0 s +[MXF, Track1, Video] 060e2b34.0101.0102.04040101.02060000 - Rounded Timecode Timebase: 1 +[MXF, Track1, Video] 060e2b34.0101.0101.04040101.05000000 - Drop Frame: False +[MXF, Track1, Video] 060e2b34.0101.0102.07020103.01050000 - Start Timecode: 0 s +[MXF, Track1, Video] 060e2b34.0101.0102.07020201.01030000 - Duration: 0 s +[MXF, Track1, Video] 060e2b34.0101.0102.04070100.00000000 - Component Data Definition: SMPTE 12M Timecode Track +[MXF, Track2, Video] 060e2b34.0101.0102.01070102.01000000 - Track Name: Sound Track +[MXF, Track2, Video] 060e2b34.0101.0102.01040103.00000000 - Track Number: 0 +[MXF, Track2, Video] 060e2b34.0101.0102.07020103.01030000 - Origin: 0 s +[MXF, Track2, Video] 060e2b34.0101.0102.05300405.00000000 - Edit Rate: 1 +[MXF, Track2, Video] 060e2b34.0101.0102.01070101.00000000 - Track ID: 2 +[MXF, Track2, Video] 060e2b34.0101.0102.04070100.00000000 - Component Data Definition: Sound Essence Track +[MXF, Track2, Video] 060e2b34.0101.0102.07020201.01030000 - Duration: 0 s +[MXF, MXF, Time] 060e2b34.0101.0102.07020110.01030000 - Create Date: 2010:12:20 00:14:40.224 +[MXF, MXF, Time] 060e2b34.0101.0102.07020110.02050000 - Package Last Modify Date: 2010:12:20 00:14:40.224 +[MXF, Track1, Video] 060e2b34.0101.0102.01070102.01000000 - Track Name: Timecode Track +[MXF, Track1, Video] 060e2b34.0101.0102.01040103.00000000 - Track Number: 0 +[MXF, Track1, Video] 060e2b34.0101.0102.07020103.01030000 - Origin: 0 s +[MXF, Track1, Video] 060e2b34.0101.0102.05300405.00000000 - Edit Rate: 1 +[MXF, Track1, Video] 060e2b34.0101.0102.01070101.00000000 - Track ID: 1 +[MXF, Track1, Video] 060e2b34.0101.0102.04070100.00000000 - Component Data Definition: SMPTE 12M Timecode Track +[MXF, Track1, Video] 060e2b34.0101.0102.07020201.01030000 - Duration: 0 s +[MXF, Track1, Video] 060e2b34.0101.0102.04040101.02060000 - Rounded Timecode Timebase: 1 +[MXF, Track1, Video] 060e2b34.0101.0101.04040101.05000000 - Drop Frame: False +[MXF, Track1, Video] 060e2b34.0101.0102.07020103.01050000 - Start Timecode: 0 s +[MXF, Track1, Video] 060e2b34.0101.0102.07020201.01030000 - Duration: 0 s +[MXF, Track1, Video] 060e2b34.0101.0102.04070100.00000000 - Component Data Definition: SMPTE 12M Timecode Track +[MXF, Track2, Video] 060e2b34.0101.0102.01070102.01000000 - Track Name: Sound Track +[MXF, Track2, Video] 060e2b34.0101.0102.01040103.00000000 - Track Number: 369164801 +[MXF, Track2, Video] 060e2b34.0101.0102.07020103.01030000 - Origin: 0 s +[MXF, Track2, Video] 060e2b34.0101.0102.05300405.00000000 - Edit Rate: 1 +[MXF, Track2, Video] 060e2b34.0101.0102.01070101.00000000 - Track ID: 2 +[MXF, Track2, Video] 060e2b34.0101.0102.04070100.00000000 - Component Data Definition: Sound Essence Track +[MXF, Track2, Video] 060e2b34.0101.0102.07020201.01030000 - Duration: 0 s +[MXF, Track2, Video] 060e2b34.0101.0101.04060101.00000000 - Sample Rate: 1 +[MXF, Track2, Audio] 060e2b34.0101.0105.04020301.01010000 - Audio Sample Rate: 7872 +[MXF, Track2, Video] 060e2b34.0101.0104.04020301.04000000 - Locked Indicator: False +[MXF, Track2, Audio] 060e2b34.0101.0105.04020101.04000000 - Channel Count: 1 +[MXF, Track2, Audio] 060e2b34.0101.0104.04020303.04000000 - Bits Per Audio Sample: 8 +[MXF, Track2, Video] 060e2b34.0101.0105.04020302.01000000 - Block Align: 1 +[MXF, Track2, Audio] 060e2b34.0101.0105.04020303.05000000 - Average Bytes Per Second: 7872 +[MXF, Track2, Video] 060e2b34.0101.0105.06010103.05000000 - Linked Track ID: 2 +[MXF, Track2, Video] 060e2b34.0101.0101.04060102.00000000 - Essence Length: 0 s +[MXF, MXF, Video] 060e2b34.0101.0104.01030404.00000000 - Essence Stream ID: 1 +[MXF, MXF, Video] 060e2b34.0101.0102.07020201.01030000 - Duration: 0 s diff --git a/ExifTool/t/MacOS.t b/ExifTool/t/MacOS.t new file mode 100644 index 0000000..3972293 --- /dev/null +++ b/ExifTool/t/MacOS.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/MacOS.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::MacOS; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'MacOS'; +my $testnum = 1; + +# test 2: Extract information from MacOS.macos (MacOS "._" sidecar file) +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/MacOS.macos'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/MacOS_2.out b/ExifTool/t/MacOS_2.out new file mode 100644 index 0000000..e6b90b3 --- /dev/null +++ b/ExifTool/t/MacOS_2.out @@ -0,0 +1,19 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: MacOS.macos +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 4.1 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:11:12 07:42:35-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:41-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rwxr-xr-x +[File, File, Other] FileType - File Type: MacOS +[File, File, Other] FileTypeExtension - File Type Extension: macos +[File, File, Other] MIMEType - MIME Type: application/unknown +[File, MacOS, Other] com.apple.quarantine - X Attr Quarantine: Flags=0082 set at 2020:11:12 12:27:26 by Safari +[File, MacOS, Time] com.apple.lastuseddate#PS - X Attr Last Used Date: 2020:11:12 12:27:26 +[File, MacOS, Time] com.apple.metadata:kMDItemDownloadedDate - X Attr MD Item Downloaded Date: 2020:11:12 07:27:26-05:00 +[File, MacOS, Other] com.apple.metadata:kMDItemWhereFroms - X Attr MD Item Where Froms: https://exiftool.org/test/sample.jpg +[File, MacOS, Other] com.apple.metadata:kMDLabel - X Attr MD Label: (Binary data 89 bytes) +[File, MacOS, Other] org.exiftool.metadata:TestTag - X Attr Org Exiftool Metadata Test Tag: this is a test tag +[File, MacOS, Other] com.apple.metadata:kMDItemFinderComment - X Attr MD Item Finder Comment: A Finder comment +[File, MacOS, Other] com.apple.metadata:_kMDItemUserTags - X Attr MD Item User Tags: Red;6, Custom1, Yellow;5, Custom2 diff --git a/ExifTool/t/Matroska.t b/ExifTool/t/Matroska.t new file mode 100644 index 0000000..7cd2498 --- /dev/null +++ b/ExifTool/t/Matroska.t @@ -0,0 +1,29 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Matroska.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Matroska; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Matroska'; +my $testnum = 1; + +# test 2: Extract information from test image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Unknown => 1); + my $info = $exifTool->ImageInfo('t/images/Matroska.mkv'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Matroska_2.out b/ExifTool/t/Matroska_2.out new file mode 100644 index 0000000..185881e --- /dev/null +++ b/ExifTool/t/Matroska_2.out @@ -0,0 +1,66 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.52 +[File, System, Other] FileName - File Name: Matroska.mkv +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 507 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:05:28 18:24:56-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:09:26 20:08:35-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:18 10:49:22-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: MKV +[File, File, Other] FileTypeExtension - File Type Extension: mkv +[File, File, Other] MIMEType - MIME Type: video/x-matroska +[Matroska, Matroska, Video] 642 - Doc Type: matroska +[Matroska, Matroska, Video] 647 - Doc Type Version: 1 +[Matroska, Matroska, Video] 645 - Doc Type Read Version: 1 +[Matroska, Matroska, Video] 5035 - Seek ID: (Binary data 4 bytes) +[Matroska, Matroska, Video] 5036 - Seek Position: 4099 +[Matroska, Matroska, Video] 5035 - Seek ID: (Binary data 4 bytes) +[Matroska, Matroska, Video] 5036 - Seek Position: 4264 +[Matroska, Matroska, Video] 5035 - Seek ID: (Binary data 4 bytes) +[Matroska, Matroska, Video] 5036 - Seek Position: 17889670 +[Matroska, Matroska, Video] 5035 - Seek ID: (Binary data 4 bytes) +[Matroska, Matroska, Video] 5036 - Seek Position: 17889269 +[Matroska, Matroska, Video] 710577 - Timecode Scale: 1 ms +[Matroska, Matroska, Video] 3456 - Muxing App: libebml v0.7.8 + libmatroska v0.8.1 +[Matroska, Matroska, Video] 5953 - Writing App: mkvmerge v2.4.0 ('Fumbling Towards Ecstasy') built on Dec 3 2008 16:22:41 +[Matroska, Matroska, Video] 1161 - Duration: 0:02:29 +[Matroska, Matroska, Time] 1121 - Date/Time Original: 2010:02:03 21:17:48Z +[Matroska, Matroska, Video] 13220 - Segment UID: 567c66ee723eb3fb134811344da9a391 +[Matroska, Track1, Video] 87 - Track Number: 1 +[Matroska, Track1, Video] 13253 - Track UID: a169290f +[Matroska, Track1, Video] 3 - Track Type: Video +[Matroska, Track1, Video] 57 - Track Used: Yes +[Matroska, Track1, Video] 8 - Track Default: Yes +[Matroska, Track1, Video] 5546 - Track Forced: No +[Matroska, Track1, Video] 28 - Track Lacing: No +[Matroska, Track1, Video] 11751 - Min Cache: 1 +[Matroska, Track1, Video] 209231 - Track Timecode Scale: 1 +[Matroska, Track1, Video] 5614 - Max Block Addition ID: 0 +[Matroska, Track1, Video] 6 - Video Codec ID: V_MPEG4/ISO/AVC +[Matroska, Track1, Video] 42 - Codec Decode All: Yes +[Matroska, Track1, Video] 9122 - Codec Private: (Binary data 41 bytes) +[Matroska, Track1, Video] 254851 - Video Frame Rate: 25 +[Matroska, Track1, Video] 177564 - Track Language: und +[Matroska, Track1, Video] 48 - Image Width: 704 +[Matroska, Track1, Video] 58 - Image Height: 576 +[Matroska, Track1, Video] 26 - Video Scan Type: Progressive +[Matroska, Track1, Video] 5296 - Display Width: 768 +[Matroska, Track1, Video] 5306 - Display Height: 576 +[Matroska, Track2, Video] 87 - Track Number: 2 +[Matroska, Track2, Video] 13253 - Track UID: e1183bf4 +[Matroska, Track2, Video] 3 - Track Type: Audio +[Matroska, Track2, Video] 57 - Track Used: Yes +[Matroska, Track2, Video] 8 - Track Default: Yes +[Matroska, Track2, Video] 5546 - Track Forced: No +[Matroska, Track2, Video] 28 - Track Lacing: Yes +[Matroska, Track2, Video] 11751 - Min Cache: 0 +[Matroska, Track2, Video] 209231 - Track Timecode Scale: 1 +[Matroska, Track2, Video] 5614 - Max Block Addition ID: 0 +[Matroska, Track2, Video] 6 - Audio Codec ID: A_MPEG/L3 +[Matroska, Track2, Video] 42 - Codec Decode All: Yes +[Matroska, Track2, Video] 254851 - Default Duration: 24 ms +[Matroska, Track2, Video] 177564 - Track Language: und +[Matroska, Track2, Audio] 53 - Audio Sample Rate: 48000 +[Matroska, Track2, Audio] 31 - Audio Channels: 2 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 704x576 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.406 diff --git a/ExifTool/t/Minolta.t b/ExifTool/t/Minolta.t new file mode 100644 index 0000000..2f4b23c --- /dev/null +++ b/ExifTool/t/Minolta.t @@ -0,0 +1,50 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Minolta.t". + +BEGIN { + $| = 1; print "1..4\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Minolta; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Minolta'; +my $testnum = 1; + +# test 2: Extract information from Minolta.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Minolta.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + ['Caption-Abstract' => 'A new caption/abstract'], + ['MinoltaDate' => '2005:01:16'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: Write rewriting MRW image +{ + ++$testnum; + my @writeInfo = ( + ['FocusMode' => 'MF'], + ['LastFileNumber' => '123'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/Minolta.mrw'); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Minolta_2.out b/ExifTool/t/Minolta_2.out new file mode 100644 index 0000000..6010eea --- /dev/null +++ b/ExifTool/t/Minolta_2.out @@ -0,0 +1,135 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Minolta.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 14 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:42-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: DCF 1.0 +[EXIF, IFD0, Camera] 271 - Make: Minolta Co., Ltd. +[EXIF, IFD0, Camera] 272 - Camera Model Name: DiMAGE 7i +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Ver.1.00u +[EXIF, IFD0, Time] 306 - Modify Date: 2002:06:01 12:37:27 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/180 +[EXIF, ExifIFD, Image] 33437 - F Number: 5.6 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2002:06:01 12:37:27 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2002:06:01 12:37:27 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 7.5 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.4 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 42.3 mm +[EXIF, ExifIFD, Camera] 37396 - Subject Area: 960 960 320 192 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2560 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1920 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Custom +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 167 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Close +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 13224 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Minolta, Camera] 0 - Maker Note Version: MLT0 +[MakerNotes, Minolta, Camera] 1 - Exposure Mode: Program +[MakerNotes, Minolta, Camera] 2 - Flash Mode: Fill flash +[MakerNotes, Minolta, Camera] 3 - White Balance: Auto +[MakerNotes, Minolta, Camera] 4 - Minolta Image Size: Full +[MakerNotes, Minolta, Camera] 5 - Minolta Quality: Fine +[MakerNotes, Minolta, Camera] 6 - Drive Mode: Single +[MakerNotes, Minolta, Camera] 7 - Metering Mode: Multi-segment +[MakerNotes, Minolta, Camera] 8 - ISO: 100 +[MakerNotes, Minolta, Camera] 9 - Exposure Time: 1/181 +[MakerNotes, Minolta, Camera] 10 - F Number: 5.7 +[MakerNotes, Minolta, Camera] 11 - Macro Mode: Off +[MakerNotes, Minolta, Camera] 12 - Digital Zoom: Off +[MakerNotes, Minolta, Camera] 13 - Exposure Compensation: 0 +[MakerNotes, Minolta, Camera] 14 - Bracket Step: 1/3 EV +[MakerNotes, Minolta, Camera] 16 - Interval Length: 0 +[MakerNotes, Minolta, Camera] 17 - Interval Number: 2 +[MakerNotes, Minolta, Camera] 18 - Focal Length: 41.4 mm +[MakerNotes, Minolta, Camera] 19 - Focus Distance: 2 m +[MakerNotes, Minolta, Camera] 20 - Flash Fired: No +[MakerNotes, Minolta, Time] 21 - Minolta Date: 2002:06:01 +[MakerNotes, Minolta, Time] 22 - Minolta Time: 12:37:27 +[MakerNotes, Minolta, Camera] 23 - Max Aperture: 3.4 +[MakerNotes, Minolta, Camera] 26 - File Number Memory: On +[MakerNotes, Minolta, Camera] 27 - Last File Number: 32 +[MakerNotes, Minolta, Camera] 28 - Color Balance Red: 1.49609375 +[MakerNotes, Minolta, Camera] 29 - Color Balance Green: 1 +[MakerNotes, Minolta, Camera] 30 - Color Balance Blue: 1.375 +[MakerNotes, Minolta, Camera] 31 - Saturation: Normal +[MakerNotes, Minolta, Camera] 32 - Contrast: Normal +[MakerNotes, Minolta, Camera] 33 - Sharpness: Normal +[MakerNotes, Minolta, Camera] 34 - Subject Program: None +[MakerNotes, Minolta, Camera] 35 - Flash Exposure Compensation: 0 +[MakerNotes, Minolta, Camera] 36 - ISO Setting: 100 +[MakerNotes, Minolta, Camera] 37 - Minolta Model ID: DiMAGE 7i +[MakerNotes, Minolta, Camera] 38 - Interval Mode: Still Image +[MakerNotes, Minolta, Camera] 39 - Folder Name: Standard Form +[MakerNotes, Minolta, Camera] 40 - Color Mode: Natural color +[MakerNotes, Minolta, Camera] 41 - Color Filter: 0 +[MakerNotes, Minolta, Camera] 42 - BW Filter: 0 +[MakerNotes, Minolta, Camera] 43 - Internal Flash: No +[MakerNotes, Minolta, Camera] 44 - Brightness: 7.5 +[MakerNotes, Minolta, Camera] 45 - Spot Focus Point X: 1280 +[MakerNotes, Minolta, Camera] 46 - Spot Focus Point Y: 960 +[MakerNotes, Minolta, Camera] 47 - Wide Focus Zone: Left zone +[MakerNotes, Minolta, Camera] 48 - Focus Mode: AF +[MakerNotes, Minolta, Camera] 49 - Focus Area: Wide Focus (normal) +[MakerNotes, Minolta, Camera] 50 - DEC Position: Exposure +[MakerNotes, Minolta, Camera] 64 - Compressed Image Size: 2396300 +[MakerNotes, Minolta, Camera] 136 - Preview Image Start: 13042 +[MakerNotes, Minolta, Camera] 137 - Preview Image Length: 26 +[MakerNotes, Minolta, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0100 +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[Composite, Composite, Image] Exif-Aperture - Aperture: 5.6 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 3.9 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/180 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.008 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.19 m (1.91 - 2.10 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 12.0 deg (0.42 m) +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 42.3 mm (35 mm equivalent: 167.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 42.01 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 12.5 diff --git a/ExifTool/t/Minolta_3.out b/ExifTool/t/Minolta_3.out new file mode 100644 index 0000000..e483fc0 --- /dev/null +++ b/ExifTool/t/Minolta_3.out @@ -0,0 +1,148 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Minolta_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 14 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:42-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:42-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:42-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 55a74ece68e6b31776ae6870c7a02dd6 +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: DCF 1.0 +[EXIF, IFD0, Camera] 271 - Make: Minolta Co., Ltd. +[EXIF, IFD0, Camera] 272 - Camera Model Name: DiMAGE 7i +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Ver.1.00u +[EXIF, IFD0, Time] 306 - Modify Date: 2002:06:01 12:37:27 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/180 +[EXIF, ExifIFD, Image] 33437 - F Number: 5.6 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2002:06:01 12:37:27 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2002:06:01 12:37:27 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 7.5 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.4 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 42.3 mm +[EXIF, ExifIFD, Camera] 37396 - Subject Area: 960 960 320 192 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2560 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1920 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Custom +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 167 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Close +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 13198 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Minolta, Camera] 0 - Maker Note Version: MLT0 +[MakerNotes, Minolta, Camera] 1 - Exposure Mode: Program +[MakerNotes, Minolta, Camera] 2 - Flash Mode: Fill flash +[MakerNotes, Minolta, Camera] 3 - White Balance: Auto +[MakerNotes, Minolta, Camera] 4 - Minolta Image Size: Full +[MakerNotes, Minolta, Camera] 5 - Minolta Quality: Fine +[MakerNotes, Minolta, Camera] 6 - Drive Mode: Single +[MakerNotes, Minolta, Camera] 7 - Metering Mode: Multi-segment +[MakerNotes, Minolta, Camera] 8 - ISO: 100 +[MakerNotes, Minolta, Camera] 9 - Exposure Time: 1/181 +[MakerNotes, Minolta, Camera] 10 - F Number: 5.7 +[MakerNotes, Minolta, Camera] 11 - Macro Mode: Off +[MakerNotes, Minolta, Camera] 12 - Digital Zoom: Off +[MakerNotes, Minolta, Camera] 13 - Exposure Compensation: 0 +[MakerNotes, Minolta, Camera] 14 - Bracket Step: 1/3 EV +[MakerNotes, Minolta, Camera] 16 - Interval Length: 0 +[MakerNotes, Minolta, Camera] 17 - Interval Number: 2 +[MakerNotes, Minolta, Camera] 18 - Focal Length: 41.4 mm +[MakerNotes, Minolta, Camera] 19 - Focus Distance: 2 m +[MakerNotes, Minolta, Camera] 20 - Flash Fired: No +[MakerNotes, Minolta, Time] 21 - Minolta Date: 2005:01:16 +[MakerNotes, Minolta, Time] 22 - Minolta Time: 12:37:27 +[MakerNotes, Minolta, Camera] 23 - Max Aperture: 3.4 +[MakerNotes, Minolta, Camera] 26 - File Number Memory: On +[MakerNotes, Minolta, Camera] 27 - Last File Number: 32 +[MakerNotes, Minolta, Camera] 28 - Color Balance Red: 1.49609375 +[MakerNotes, Minolta, Camera] 29 - Color Balance Green: 1 +[MakerNotes, Minolta, Camera] 30 - Color Balance Blue: 1.375 +[MakerNotes, Minolta, Camera] 31 - Saturation: Normal +[MakerNotes, Minolta, Camera] 32 - Contrast: Normal +[MakerNotes, Minolta, Camera] 33 - Sharpness: Normal +[MakerNotes, Minolta, Camera] 34 - Subject Program: None +[MakerNotes, Minolta, Camera] 35 - Flash Exposure Compensation: 0 +[MakerNotes, Minolta, Camera] 36 - ISO Setting: 100 +[MakerNotes, Minolta, Camera] 37 - Minolta Model ID: DiMAGE 7i +[MakerNotes, Minolta, Camera] 38 - Interval Mode: Still Image +[MakerNotes, Minolta, Camera] 39 - Folder Name: Standard Form +[MakerNotes, Minolta, Camera] 40 - Color Mode: Natural color +[MakerNotes, Minolta, Camera] 41 - Color Filter: 0 +[MakerNotes, Minolta, Camera] 42 - BW Filter: 0 +[MakerNotes, Minolta, Camera] 43 - Internal Flash: No +[MakerNotes, Minolta, Camera] 44 - Brightness: 7.5 +[MakerNotes, Minolta, Camera] 45 - Spot Focus Point X: 1280 +[MakerNotes, Minolta, Camera] 46 - Spot Focus Point Y: 960 +[MakerNotes, Minolta, Camera] 47 - Wide Focus Zone: Left zone +[MakerNotes, Minolta, Camera] 48 - Focus Mode: AF +[MakerNotes, Minolta, Camera] 49 - Focus Area: Wide Focus (normal) +[MakerNotes, Minolta, Camera] 50 - DEC Position: Exposure +[MakerNotes, Minolta, Camera] 16 - Minolta 0x0010: ¯¯¯¯ÿ...’.–Œ.P…þÿ~½ÿßßÓÿÿÿïýÿÿ[...] +[MakerNotes, Minolta, Camera] 32 - Minolta 0x0020: øøøm.n.nn..¤/&#¡......(,;...k(S.[...] +[MakerNotes, Minolta, Camera] 64 - Compressed Image Size: 2396300 +[MakerNotes, Minolta, Camera] 136 - Preview Image Start: 13226 +[MakerNotes, Minolta, Camera] 137 - Preview Image Length: 26 +[MakerNotes, Minolta, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0100 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00160016 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x01000000 +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x01000000 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000000 +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00160016 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x01000000 +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x01000000 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000000 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A new caption/abstract +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[Composite, Composite, Image] Exif-Aperture - Aperture: 5.6 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 3.9 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/180 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.008 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.19 m (1.91 - 2.10 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 12.0 deg (0.42 m) +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 42.3 mm (35 mm equivalent: 167.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 42.01 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 12.5 diff --git a/ExifTool/t/Minolta_4.out b/ExifTool/t/Minolta_4.out new file mode 100644 index 0000000..5a0c0f2 --- /dev/null +++ b/ExifTool/t/Minolta_4.out @@ -0,0 +1,156 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Minolta_4_failed.mrw +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 2.6 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:42-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:42-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:42-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: MRW +[File, File, Other] FileTypeExtension - File Type Extension: mrw +[File, File, Other] MIMEType - MIME Type: image/x-minolta-mrw +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[MakerNotes, MinoltaRaw, Camera] 0 - Firmware ID: 27200001 +[MakerNotes, MinoltaRaw, Camera] 8 - Sensor Height: 2456 +[MakerNotes, MinoltaRaw, Camera] 10 - Sensor Width: 3272 +[MakerNotes, MinoltaRaw, Camera] 12 - Image Height: 2448 +[MakerNotes, MinoltaRaw, Camera] 14 - Image Width: 3264 +[MakerNotes, MinoltaRaw, Camera] 16 - Raw Depth: 12 +[MakerNotes, MinoltaRaw, Camera] 17 - Bit Depth: 12 +[MakerNotes, MinoltaRaw, Camera] 18 - Storage Method: Linear +[MakerNotes, MinoltaRaw, Camera] 23 - Bayer Pattern: RGGB +[MakerNotes, Minolta, Camera] 0 - Maker Note Version: MLT0 +[MakerNotes, Minolta, Camera] 1 - Exposure Mode: Manual +[MakerNotes, Minolta, Camera] 2 - Flash Mode: Fill flash +[MakerNotes, Minolta, Camera] 3 - White Balance: Auto +[MakerNotes, Minolta, Camera] 4 - Minolta Image Size: 640x480 +[MakerNotes, Minolta, Camera] 5 - Minolta Quality: Raw +[MakerNotes, Minolta, Camera] 6 - Drive Mode: Single +[MakerNotes, Minolta, Camera] 7 - Metering Mode: Multi-segment +[MakerNotes, Minolta, Camera] 8 - ISO: 71 +[MakerNotes, Minolta, Camera] 9 - Exposure Time: 1/5 +[MakerNotes, Minolta, Camera] 10 - F Number: 2.7 +[MakerNotes, Minolta, Camera] 11 - Macro Mode: Off +[MakerNotes, Minolta, Camera] 12 - Digital Zoom: Off +[MakerNotes, Minolta, Camera] 13 - Exposure Compensation: 0 +[MakerNotes, Minolta, Camera] 14 - Bracket Step: 2/3 EV +[MakerNotes, Minolta, Camera] 16 - Interval Length: 60 +[MakerNotes, Minolta, Camera] 17 - Interval Number: 2 +[MakerNotes, Minolta, Camera] 18 - Focal Length: 7.4 mm +[MakerNotes, Minolta, Camera] 19 - Focus Distance: inf +[MakerNotes, Minolta, Camera] 20 - Flash Fired: No +[MakerNotes, Minolta, Time] 21 - Minolta Date: 2004:11:18 +[MakerNotes, Minolta, Time] 22 - Minolta Time: 23:14:58 +[MakerNotes, Minolta, Camera] 23 - Max Aperture: 2.7 +[MakerNotes, Minolta, Camera] 26 - File Number Memory: On +[MakerNotes, Minolta, Camera] 27 - Last File Number: 123 +[MakerNotes, Minolta, Camera] 28 - Color Balance Red: 1.7109375 +[MakerNotes, Minolta, Camera] 29 - Color Balance Green: 1 +[MakerNotes, Minolta, Camera] 30 - Color Balance Blue: 1.98828125 +[MakerNotes, Minolta, Camera] 31 - Saturation: Normal +[MakerNotes, Minolta, Camera] 32 - Contrast: Normal +[MakerNotes, Minolta, Camera] 33 - Sharpness: Normal +[MakerNotes, Minolta, Camera] 34 - Subject Program: None +[MakerNotes, Minolta, Camera] 35 - Flash Exposure Compensation: 0 +[MakerNotes, Minolta, Camera] 36 - ISO Setting: 64 +[MakerNotes, Minolta, Camera] 37 - Minolta Model ID: DiMAGE A2 or S414 +[MakerNotes, Minolta, Camera] 38 - Interval Mode: Still Image +[MakerNotes, Minolta, Camera] 39 - Folder Name: Standard Form +[MakerNotes, Minolta, Camera] 40 - Color Mode: Natural color +[MakerNotes, Minolta, Camera] 41 - Color Filter: 0 +[MakerNotes, Minolta, Camera] 42 - BW Filter: 0 +[MakerNotes, Minolta, Camera] 43 - Internal Flash: No +[MakerNotes, Minolta, Camera] 44 - Brightness: 0.75 +[MakerNotes, Minolta, Camera] 45 - Spot Focus Point X: 1632 +[MakerNotes, Minolta, Camera] 46 - Spot Focus Point Y: 1224 +[MakerNotes, Minolta, Camera] 47 - Wide Focus Zone: Unknown (7) +[MakerNotes, Minolta, Camera] 48 - Focus Mode: MF +[MakerNotes, Minolta, Camera] 49 - Focus Area: Spot Focus +[MakerNotes, Minolta, Camera] 50 - DEC Position: Filter +[MakerNotes, Minolta, Camera] 63 - Flash Metering: ADI (Advanced Distance Integration) +[MakerNotes, Minolta, Camera] 16 - Minolta 0x0010: ¯¯¯¯.vÿð......#.....[...] +[MakerNotes, Minolta, Camera] 24 - Image Stabilization: On +[MakerNotes, Minolta, Camera] 32 - Minolta 0x0020: ®®® .ÿ....ü.........`..I[...] +[MakerNotes, Minolta, Camera] 64 - Compressed Image Size: 16072064 +[MakerNotes, Minolta, Camera] 136 - Preview Image Start: 2082 +[MakerNotes, Minolta, Camera] 137 - Preview Image Length: 26 +[MakerNotes, Minolta, Camera] 256 - Scene Mode: Standard +[MakerNotes, Minolta, Camera] 257 - Color Mode: Natural +[MakerNotes, Minolta, Camera] 258 - Minolta Quality: Raw +[MakerNotes, Minolta, Camera] 260 - Flash Exposure Compensation: 0 +[MakerNotes, MinoltaRaw, Camera] 0 - WB Scale: 2 2 2 2 +[MakerNotes, MinoltaRaw, Camera] 4 - WB RGGB Levels: 438 256 256 509 +[MakerNotes, MinoltaRaw, Image] 1 - Saturation: 0 +[MakerNotes, MinoltaRaw, Image] 2 - Contrast: 0 +[MakerNotes, MinoltaRaw, Image] 3 - Sharpness: 0 +[MakerNotes, MinoltaRaw, Image] 4 - WB Mode: Auto (0) +[MakerNotes, MinoltaRaw, Image] 5 - Program Mode: None +[MakerNotes, MinoltaRaw, Image] 6 - ISO Setting: 65 +[MakerNotes, MinoltaRaw, Image] 7 - Color Mode: Natural color +[MakerNotes, MinoltaRaw, Image] 8 - WB RB Levels Tungsten: 278 738 +[MakerNotes, MinoltaRaw, Image] 12 - WB RB Levels Daylight: 503 357 +[MakerNotes, MinoltaRaw, Image] 16 - WB RB Levels Cloudy: 565 330 +[MakerNotes, MinoltaRaw, Image] 20 - WB RB Levels Cool White F: 441 545 +[MakerNotes, MinoltaRaw, Image] 24 - WB RB Levels Flash: 523 339 +[MakerNotes, MinoltaRaw, Image] 28 - WB RB Levels Custom: 565 330 +[MakerNotes, MinoltaRaw, Image] 56 - Color Filter: 0 +[MakerNotes, MinoltaRaw, Image] 57 - BW Filter: 0 +[MakerNotes, MinoltaRaw, Image] 58 - Zone Matching: ISO Setting Used +[MakerNotes, MinoltaRaw, Image] 59 - Hue: 0 +[MakerNotes, Minolta, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[EXIF, IFD0, Image] 256 - Image Width: 3264 +[EXIF, IFD0, Image] 257 - Image Height: 2448 +[EXIF, IFD0, Image] 259 - Compression: Uncompressed +[EXIF, IFD0, Image] 270 - Image Description: KONICA MINOLTA DIGITAL CAMERA +[EXIF, IFD0, Camera] 271 - Make: Konica Minolta Camera, Inc. +[EXIF, IFD0, Camera] 272 - Camera Model Name: DiMAGE A2 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 305 - Software: DiMAGE A2 Ver.1.12 +[EXIF, IFD0, Time] 306 - Modify Date: 2004:11:18 23:14:58 +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/5 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.8 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Manual +[EXIF, ExifIFD, Image] 34855 - ISO: 64 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2004:11:18 23:14:58 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2004:11:18 23:14:58 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 0.7 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Unknown +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 7.2 mm +[EXIF, ExifIFD, Camera] 37396 - Subject Area: 1632 1224 320 384 +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 28 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Distant +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00160016 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x01000000 +[PrintIM, PrintIM, Printing] 3 - Print IM 0x0003: 0x0000002e +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x01000000 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000000 +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.8 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3264x2448 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 8.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/5 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.988281 +[Composite, Composite, Image] Exif-LightValue - Light Value: 5.9 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.710938 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 3.9 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.008 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: inf (2.40 m - inf) +[Composite, Composite, Image] Exif-FOV - Field Of View: 65.5 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 7.2 mm (35 mm equivalent: 28.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 2.40 m diff --git a/ExifTool/t/Motorola.t b/ExifTool/t/Motorola.t new file mode 100644 index 0000000..366c861 --- /dev/null +++ b/ExifTool/t/Motorola.t @@ -0,0 +1,29 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Motorola.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Motorola; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Motorola'; +my $testnum = 1; + +# test 2: Extract information from Motorola.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Unknown => 1); + my $info = $exifTool->ImageInfo('t/images/Motorola.jpg', 'MakerNotes:*'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Motorola_2.out b/ExifTool/t/Motorola_2.out new file mode 100644 index 0000000..15d9da4 --- /dev/null +++ b/ExifTool/t/Motorola_2.out @@ -0,0 +1,97 @@ +[MakerNotes, Motorola, Camera] 21760 - Build Number: LPH23.116-18 +[MakerNotes, Motorola, Camera] 21761 - Serial Number: NX0A3S0075 +[MakerNotes, Motorola, Camera] 21762 - Motorola 0x5502: 96 +[MakerNotes, Motorola, Camera] 21763 - Motorola 0x5503: 30 +[MakerNotes, Motorola, Camera] 21728 - Motorola 0x54e0: 1 +[MakerNotes, Motorola, Camera] 21776 - Motorola 0x5510: 1 +[MakerNotes, Motorola, Camera] 21888 - Motorola 0x5580: 15000 +[MakerNotes, Motorola, Camera] 21904 - Motorola 0x5590: 30000 +[MakerNotes, Motorola, Camera] 21920 - Motorola 0x55a0: 1440 +[MakerNotes, Motorola, Camera] 21936 - Motorola 0x55b0: 1080 +[MakerNotes, Motorola, Camera] 21952 - Motorola 0x55c0: 30 +[MakerNotes, Motorola, Camera] 21968 - Motorola 0x55d0: yuv420sp +[MakerNotes, Motorola, Camera] 21744 - Motorola 0x54f0: 92.9883575439453 +[MakerNotes, Motorola, Camera] 25600 - Motorola 0x6400: OFF +[MakerNotes, Motorola, Camera] 21777 - Motorola 0x5511: 926 +[MakerNotes, Motorola, Camera] 25616 - Motorola 0x6410: NO +[MakerNotes, Motorola, Camera] 25648 - Motorola 0x6430: 0 +[MakerNotes, Motorola, Camera] 25679 - Motorola 0x644f: 0 +[MakerNotes, Motorola, Camera] 25792 - Motorola 0x64c0: 2 +[MakerNotes, Motorola, Camera] 25793 - Motorola 0x64c1: 1 +[MakerNotes, Motorola, Camera] 25794 - Motorola 0x64c2: 1 +[MakerNotes, Motorola, Camera] 25795 - Motorola 0x64c3: 0 +[MakerNotes, Motorola, Camera] 25796 - Motorola 0x64c4: 583 +[MakerNotes, Motorola, Camera] 25797 - Motorola 0x64c5: 912 +[MakerNotes, Motorola, Camera] 25904 - Motorola 0x6530: 494 +[MakerNotes, Motorola, Camera] 25905 - Motorola 0x6531: 11 +[MakerNotes, Motorola, Camera] 25906 - Motorola 0x6532: 0 +[MakerNotes, Motorola, Camera] 25907 - Motorola 0x6533: 0 +[MakerNotes, Motorola, Camera] 25908 - Motorola 0x6534: 372423 +[MakerNotes, Motorola, Camera] 25909 - Motorola 0x6535: 372822 +[MakerNotes, Motorola, Camera] 28672 - Motorola 0x7000: 0 +[MakerNotes, Motorola, Camera] 28673 - Motorola 0x7001: -479 +[MakerNotes, Motorola, Camera] 28674 - Motorola 0x7002: -637 +[MakerNotes, Motorola, Camera] 28675 - Motorola 0x7003: 958 +[MakerNotes, Motorola, Camera] 28676 - Motorola 0x7004: 1274 +[MakerNotes, Motorola, Camera] 28677 - Motorola 0x7005: 0 +[MakerNotes, Motorola, Camera] 21778 - Motorola 0x5512: 0 +[MakerNotes, Motorola, Camera] 21792 - Motorola 0x5520: 1 +[MakerNotes, Motorola, Camera] 21808 - Motorola 0x5530: continuous-picture +[MakerNotes, Motorola, Camera] 21824 - Motorola 0x5540: 95 +[MakerNotes, Motorola, Camera] 21840 - Motorola 0x5550: 85 +[MakerNotes, Motorola, Camera] 21856 - Motorola 0x5560: auto +[MakerNotes, Motorola, Camera] 21872 - Motorola 0x5570: auto +[MakerNotes, Motorola, Camera] 25856 - Motorola 0x6500: 1 +[MakerNotes, Motorola, Camera] 26177 - Motorola 0x6641: 1 +[MakerNotes, Motorola, Camera] 21991 - Motorola 0x55e7: 51392 +[MakerNotes, Motorola, Camera] 21992 - Motorola 0x55e8: 13376 +[MakerNotes, Motorola, Camera] 21989 - Motorola 0x55e5: 47264 +[MakerNotes, Motorola, Camera] 21990 - Motorola 0x55e6: 17504 +[MakerNotes, Motorola, Camera] 26112 - Motorola 0x6600: 0 +[MakerNotes, Motorola, Camera] 26113 - Motorola 0x6601: 1 +[MakerNotes, Motorola, Camera] 26114 - Motorola 0x6602: 1 +[MakerNotes, Motorola, Camera] 26176 - Motorola 0x6640: 0 +[MakerNotes, Motorola, Camera] 26116 - Motorola 0x6604: 0 +[MakerNotes, Motorola, Camera] 26206 - Sensor: BACK,IMX230 +[MakerNotes, Motorola, Camera] 26118 - Motorola 0x6606: 5000 +[MakerNotes, Motorola, Camera] 26130 - Motorola 0x6612: 461 +[MakerNotes, Motorola, Camera] 26131 - Motorola 0x6613: 817 +[MakerNotes, Motorola, Camera] 26132 - Motorola 0x6614: 817 +[MakerNotes, Motorola, Camera] 26133 - Motorola 0x6615: 527 +[MakerNotes, Motorola, Camera] 26178 - Motorola 0x6642: 471 +[MakerNotes, Motorola, Camera] 26179 - Motorola 0x6643: 818 +[MakerNotes, Motorola, Camera] 26180 - Motorola 0x6644: 817 +[MakerNotes, Motorola, Camera] 26181 - Motorola 0x6645: 526 +[MakerNotes, Motorola, Camera] 26119 - Motorola 0x6607: 3000 +[MakerNotes, Motorola, Camera] 26134 - Motorola 0x6616: 660 +[MakerNotes, Motorola, Camera] 26135 - Motorola 0x6617: 819 +[MakerNotes, Motorola, Camera] 26136 - Motorola 0x6618: 818 +[MakerNotes, Motorola, Camera] 26137 - Motorola 0x6619: 374 +[MakerNotes, Motorola, Camera] 26182 - Motorola 0x6646: 673 +[MakerNotes, Motorola, Camera] 26183 - Motorola 0x6647: 820 +[MakerNotes, Motorola, Camera] 26184 - Motorola 0x6648: 820 +[MakerNotes, Motorola, Camera] 26185 - Motorola 0x6649: 374 +[MakerNotes, Motorola, Camera] 26190 - Motorola 0x664e: 10 +[MakerNotes, Motorola, Camera] 26191 - Motorola 0x664f: 64 +[MakerNotes, Motorola, Camera] 26192 - Motorola 0x6650: 64 +[MakerNotes, Motorola, Camera] 26193 - Motorola 0x6651: 64 +[MakerNotes, Motorola, Camera] 26194 - Motorola 0x6652: 64 +[MakerNotes, Motorola, Camera] 26196 - Motorola 0x6654: 0 +[MakerNotes, Motorola, Camera] 26197 - Motorola 0x6655: 0 +[MakerNotes, Motorola, Camera] 26198 - Motorola 0x6656: 0 +[MakerNotes, Motorola, Camera] 26205 - Motorola 0x665d: 1 +[MakerNotes, Motorola, Camera] 26195 - Motorola 0x6653: AL +[MakerNotes, Motorola, Camera] 26368 - Motorola 0x6700: 00000000904c2ca2 +[MakerNotes, Motorola, Camera] 26369 - Motorola 0x6701: 94014037 +[MakerNotes, Motorola, Camera] 26370 - Motorola 0x6702: 32 +[MakerNotes, Motorola, Camera] 26371 - Motorola 0x6703: SO +[MakerNotes, Motorola, Camera] 26372 - Motorola 0x6704: GU +[MakerNotes, Motorola, Camera] 26373 - Manufacture Date: 03Jun2015 +[MakerNotes, Motorola, Camera] 26374 - Motorola 0x6706: 904c2ca2 +[MakerNotes, Motorola, Camera] 26375 - Motorola 0x6707: 69 +[MakerNotes, Motorola, Camera] 26376 - Motorola 0x6708: 47264 +[MakerNotes, Motorola, Camera] 26377 - Motorola 0x6709: 17504 +[MakerNotes, Motorola, Camera] 26378 - Motorola 0x670a: 47264 +[MakerNotes, Motorola, Camera] 26380 - Motorola 0x670c: 0 +[MakerNotes, Motorola, Camera] 25888 - Motorola 0x6520: 1 +[MakerNotes, Motorola, Camera] 25889 - Motorola 0x6521: 46 diff --git a/ExifTool/t/Nikon.t b/ExifTool/t/Nikon.t new file mode 100644 index 0000000..9a77e01 --- /dev/null +++ b/ExifTool/t/Nikon.t @@ -0,0 +1,123 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Nikon.t". + +BEGIN { + $| = 1; print "1..9\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Nikon; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Nikon'; +my $testnum = 1; + +# test 2: Extract information from Nikon.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Nikon.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + [ Creator => 'Phil' ], + [ ImageAdjustment => 'Yes, lots of it' ], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: Test writing all D70 image information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/NikonD70.jpg'); + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->WriteInfo('t/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 5: Extract information from a D2Hs image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/NikonD2Hs.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 6: Test Nikon decryption +{ + ++$testnum; + my $data = pack('N', 0x34a290d3); + $data = Image::ExifTool::Nikon::Decrypt(\$data, undef, undef, 0x12345678, 0x00000123); + my $expected = 0xcae17d2f; + my $got = unpack('N', $data); + unless ($got == $expected) { + warn "\n Test $testnum (decryption) returned wrong value:\n"; + warn sprintf(" Expected 0x%x but got 0x%x\n", $expected, $got); + notOK(); + } + print "ok $testnum\n"; +} + +# test 7: Test reading NEF image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 1); + my $info = $exifTool->ImageInfo('t/images/Nikon.nef'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 8: Test writing Nikon Capture information in NEF image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(IgnoreMinorErrors => 1); + $exifTool->SetNewValue('PhotoEffects' => 'Off'); + $exifTool->SetNewValue('Caption-abstract' => 'A new caption'); + $exifTool->SetNewValue('VignetteControlIntensity' => '70'); + my $testfile = "t/${testname}_${testnum}_failed.nef"; + unlink $testfile; + $exifTool->WriteInfo('t/images/Nikon.nef', $testfile); + my $info = $exifTool->ImageInfo($testfile, qw(PhotoEffects Caption-abstract VignetteControlIntensity)); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 9: Validate Nikon LensID values (internal check) +{ + ++$testnum; + my $lensIDs = $Image::ExifTool::Nikon::Composite{LensID}->{PrintConv}; + foreach (sort keys %$lensIDs) { + next if /^(([0-9A-F]{2} ){7}[0-9A-F]{2}(\.\d+)?|Notes|OTHER)$/; + warn "\n Bad LensID '$_' in test $testnum\n"; + notOK(); + last; + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Nikon_2.out b/ExifTool/t/Nikon_2.out new file mode 100644 index 0000000..14f69d5 --- /dev/null +++ b/ExifTool/t/Nikon_2.out @@ -0,0 +1,84 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Nikon.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1703 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:13-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: NIKON +[EXIF, IFD0, Camera] 272 - Camera Model Name: E775 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: E775v1.3u +[EXIF, IFD0, Time] 306 - Modify Date: 2001:08:01 12:57:23 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/213 +[EXIF, ExifIFD, Image] 33437 - F Number: 9.4 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:08:01 12:57:23 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:08:01 12:57:23 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.4 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 8.6 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1600 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 300 +[EXIF, IFD1, Image] 283 - Y Resolution: 300 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1426 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Nikon, Camera] 1 - Maker Note Version: 1.00 +[MakerNotes, Nikon, Image] 2 - ISO: 0 +[MakerNotes, Nikon, Camera] 3 - Color Mode: Color +[MakerNotes, Nikon, Camera] 4 - Quality: Fine +[MakerNotes, Nikon, Camera] 5 - White Balance: Auto +[MakerNotes, Nikon, Camera] 6 - Sharpness: Auto +[MakerNotes, Nikon, Camera] 7 - Focus Mode: AF-C +[MakerNotes, Nikon, Camera] 8 - Flash Setting: +[MakerNotes, Nikon, Camera] 15 - ISO Selection: Auto +[MakerNotes, Nikon, Camera] 128 - Image Adjustment: Normal +[MakerNotes, Nikon, Camera] 130 - Auxiliary Lens: Off +[MakerNotes, Nikon, Camera] 133 - Manual Focus Distance: undef +[MakerNotes, Nikon, Camera] 134 - Digital Zoom: 1 +[MakerNotes, Nikon, Camera] 0 - AF Area Mode: Single Area +[MakerNotes, Nikon, Camera] 1 - AF Point: Center +[MakerNotes, Nikon, Camera] 2 - AF Points In Focus: (none) +[MakerNotes, Nikon, Camera] 143 - Scene Mode: +[MakerNotes, Nikon, Camera] 16 - Data Dump: (Binary data 122 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0100 +[Composite, Composite, Image] Exif-Aperture - Aperture: 9.4 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/213 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 8.6 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.2 diff --git a/ExifTool/t/Nikon_3.out b/ExifTool/t/Nikon_3.out new file mode 100644 index 0000000..fa59489 --- /dev/null +++ b/ExifTool/t/Nikon_3.out @@ -0,0 +1,100 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Nikon_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 4.6 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:44-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:44-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:44-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: NIKON +[EXIF, IFD0, Camera] 272 - Camera Model Name: E775 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: E775v1.3u +[EXIF, IFD0, Time] 306 - Modify Date: 2001:08:01 12:57:23 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/213 +[EXIF, ExifIFD, Image] 33437 - F Number: 9.4 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:08:01 12:57:23 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:08:01 12:57:23 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.4 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 8.6 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1600 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1200 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 300 +[EXIF, IFD1, Image] 283 - Y Resolution: 300 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1428 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Nikon, Camera] 1 - Maker Note Version: 1.00 +[MakerNotes, Nikon, Image] 2 - ISO: 0 +[MakerNotes, Nikon, Camera] 3 - Color Mode: Color +[MakerNotes, Nikon, Camera] 4 - Quality: Fine +[MakerNotes, Nikon, Camera] 5 - White Balance: Auto +[MakerNotes, Nikon, Camera] 6 - Sharpness: Auto +[MakerNotes, Nikon, Camera] 7 - Focus Mode: AF-C +[MakerNotes, Nikon, Camera] 8 - Flash Setting: +[MakerNotes, Nikon, Camera] 10 - Nikon 0x000a: 8.832 +[MakerNotes, Nikon, Camera] 15 - ISO Selection: Auto +[MakerNotes, Nikon, Camera] 128 - Image Adjustment: Yes, lots of it +[MakerNotes, Nikon, Camera] 130 - Auxiliary Lens: Off +[MakerNotes, Nikon, Camera] 133 - Manual Focus Distance: undef +[MakerNotes, Nikon, Camera] 134 - Digital Zoom: 1 +[MakerNotes, Nikon, Camera] 0 - AF Area Mode: Single Area +[MakerNotes, Nikon, Camera] 1 - AF Point: Center +[MakerNotes, Nikon, Camera] 2 - AF Points In Focus: (none) +[MakerNotes, Nikon, Camera] 143 - Scene Mode: +[MakerNotes, Nikon, Camera] 16 - Data Dump: (Binary data 122 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0100 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00160016 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x00000001 +[PrintIM, PrintIM, Printing] 3 - Print IM 0x0003: 0x0000005e +[PrintIM, PrintIM, Printing] 7 - Print IM 0x0007: 0x00000000 +[PrintIM, PrintIM, Printing] 8 - Print IM 0x0008: 0x00000000 +[PrintIM, PrintIM, Printing] 9 - Print IM 0x0009: 0x00000000 +[PrintIM, PrintIM, Printing] 10 - Print IM 0x000a: 0x00000000 +[PrintIM, PrintIM, Printing] 11 - Print IM 0x000b: 0x000000a6 +[PrintIM, PrintIM, Printing] 12 - Print IM 0x000c: 0x00000000 +[PrintIM, PrintIM, Printing] 13 - Print IM 0x000d: 0x00000000 +[PrintIM, PrintIM, Printing] 14 - Print IM 0x000e: 0x000000be +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x00000005 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000001 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-dc, Author] creator - Creator: Phil +[Composite, Composite, Image] Exif-Aperture - Aperture: 9.4 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/213 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.2 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 8.6 mm diff --git a/ExifTool/t/Nikon_4.out b/ExifTool/t/Nikon_4.out new file mode 100644 index 0000000..9609e01 --- /dev/null +++ b/ExifTool/t/Nikon_4.out @@ -0,0 +1,176 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Nikon_4_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 7.0 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:44-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:44-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:44-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: NIKON CORPORATION +[EXIF, IFD0, Camera] 272 - Camera Model Name: NIKON D70 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Ver.1.02 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:01:14 08:57:59 +[EXIF, IFD0, Camera] 318 - White Point: 0.313 0.329 +[EXIF, IFD0, Image] 319 - Primary Chromaticities: 0.64 0.33 0.21 0.71 0.15 0.06 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/60 +[EXIF, ExifIFD, Image] 33437 - F Number: 5.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 200 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:01:14 08:57:59 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:01:14 08:57:59 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.4 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 56.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: curve: fotogenics point and shoot +[EXIF, ExifIFD, Time] 37520 - Sub Sec Time: 20 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 20 +[EXIF, ExifIFD, Time] 37522 - Sub Sec Time Digitized: 20 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3008 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2000 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41730 - CFA Pattern: [Blue,Green][Green,Red] +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Custom +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 1 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 84 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: High +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Hard +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Unknown +[EXIF, ExifIFD, Image] 42033 - Serial Number: No= 20025585 +[EXIF, ExifIFD, Image] 42240 - Gamma: 2.2 +[MakerNotes, Nikon, Camera] 1 - Maker Note Version: 2.10 +[MakerNotes, Nikon, Image] 2 - ISO: 200 +[MakerNotes, Nikon, Camera] 4 - Quality: Fine +[MakerNotes, Nikon, Camera] 5 - White Balance: Auto +[MakerNotes, Nikon, Camera] 6 - Sharpness: Med.H +[MakerNotes, Nikon, Camera] 7 - Focus Mode: AF-S +[MakerNotes, Nikon, Camera] 8 - Flash Setting: Normal +[MakerNotes, Nikon, Camera] 9 - Flash Type: Built-in,TTL +[MakerNotes, Nikon, Camera] 11 - White Balance Fine Tune: 0 +[MakerNotes, Nikon, Camera] 13 - Program Shift: 0 +[MakerNotes, Nikon, Camera] 14 - Exposure Difference: -4.9 +[MakerNotes, PreviewIFD, Image] 259 - Compression: JPEG (old-style) +[MakerNotes, PreviewIFD, Image] 282 - X Resolution: 300 +[MakerNotes, PreviewIFD, Image] 283 - Y Resolution: 300 +[MakerNotes, PreviewIFD, Image] 296 - Resolution Unit: inches +[MakerNotes, PreviewIFD, Image] 513 - Preview Image Start: 2392 +[MakerNotes, PreviewIFD, Image] 514 - Preview Image Length: 0 +[MakerNotes, PreviewIFD, Image] 531 - Y Cb Cr Positioning: Co-sited +[MakerNotes, Nikon, Camera] 18 - Flash Exposure Compensation: -5/3 +[MakerNotes, Nikon, Camera] 19 - ISO Setting: 200 +[MakerNotes, Nikon, Camera] 22 - Image Boundary: 0 0 3008 2000 +[MakerNotes, Nikon, Camera] 23 - External Flash Exposure Comp: 0 +[MakerNotes, Nikon, Camera] 24 - Flash Exposure Bracket Value: 0.0 +[MakerNotes, Nikon, Camera] 25 - Exposure Bracket Value: 0 +[MakerNotes, Nikon, Camera] 129 - Tone Comp: CS +[MakerNotes, Nikon, Camera] 131 - Lens Type: G +[MakerNotes, Nikon, Camera] 132 - Lens: 18-70mm f/3.5-4.5 +[MakerNotes, Nikon, Camera] 135 - Flash Mode: Fired, TTL Mode +[MakerNotes, Nikon, Camera] 0 - AF Area Mode: Single Area +[MakerNotes, Nikon, Camera] 1 - AF Point: Center +[MakerNotes, Nikon, Camera] 2 - AF Points In Focus: Center +[MakerNotes, Nikon, Camera] 137 - Shooting Mode: Continuous +[MakerNotes, Nikon, Camera] 139 - Lens F Stops: 5.33 +[MakerNotes, Nikon, Camera] 141 - Color Hue: Mode2 +[MakerNotes, Nikon, Camera] 144 - Light Source: Speedlight +[MakerNotes, Nikon, Camera] 0 - Shot Info Version: 0103 +[MakerNotes, Nikon, Camera] 146 - Hue Adjustment: 0 +[MakerNotes, Nikon, Camera] 149 - Noise Reduction: Off +[MakerNotes, Nikon, Camera] 0 - WB RGBG Levels: 597 256 361 256 +[MakerNotes, Nikon, Camera] 0 - Lens Data Version: 0101 +[MakerNotes, Nikon, Camera] 4 - Exit Pupil Position: 89.0 mm +[MakerNotes, Nikon, Camera] 5 - AF Aperture: 4.6 +[MakerNotes, Nikon, Camera] 8 - Focus Position: 0x21 +[MakerNotes, Nikon, Camera] 9 - Focus Distance: 0.63 m +[MakerNotes, Nikon, Camera] 10 - Focal Length: 56.6 mm +[MakerNotes, Nikon, Camera] 11 - Lens ID Number: 127 +[MakerNotes, Nikon, Camera] 12 - Lens F Stops: 5.33 +[MakerNotes, Nikon, Camera] 13 - Min Focal Length: 18.3 mm +[MakerNotes, Nikon, Camera] 14 - Max Focal Length: 71.3 mm +[MakerNotes, Nikon, Camera] 15 - Max Aperture At Min Focal: 3.6 +[MakerNotes, Nikon, Camera] 16 - Max Aperture At Max Focal: 4.5 +[MakerNotes, Nikon, Camera] 17 - MCU Version: 132 +[MakerNotes, Nikon, Camera] 18 - Effective Max Aperture: 4.5 +[MakerNotes, Nikon, Camera] 154 - Sensor Pixel Size: 7.8 x 7.8 um +[MakerNotes, Nikon, Camera] 160 - Serial Number: No= 20025585 +[MakerNotes, Nikon, Camera] 162 - Image Data Size: 2361498 +[MakerNotes, Nikon, Camera] 167 - Shutter Count: 526 +[MakerNotes, Nikon, Camera] 0 - Flash Info Version: 0100 +[MakerNotes, Nikon, Camera] 4 - Flash Source: None +[MakerNotes, Nikon, Camera] 6 - External Flash Firmware: n/a +[MakerNotes, Nikon, Camera] 8 - External Flash Flags: (none) +[MakerNotes, Nikon, Camera] 9.1 - Flash Commander Mode: Off +[MakerNotes, Nikon, Camera] 9.2 - Flash Control Mode: Off +[MakerNotes, Nikon, Camera] 10 - Flash Compensation: 0 +[MakerNotes, Nikon, Camera] 14 - Flash GN Distance: 0 +[MakerNotes, Nikon, Camera] 15 - Flash Group A Control Mode: Off +[MakerNotes, Nikon, Camera] 16 - Flash Group B Control Mode: Off +[MakerNotes, Nikon, Camera] 17 - Flash Group A Compensation: 0 +[MakerNotes, Nikon, Camera] 18 - Flash Group B Compensation: 0 +[MakerNotes, Nikon, Camera] 169 - Image Optimization: Custom +[MakerNotes, Nikon, Camera] 170 - Saturation: Enhanced +[MakerNotes, Nikon, Camera] 171 - Vari Program: +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-aux, Camera] FlashCompensation - Flash Compensation: 0 +[XMP, XMP-aux, Camera] Lens - Lens: 18-70mm f/3.5-4.5 +[XMP, XMP-exif, Image] ComponentsConfiguration - Components Configuration: Y, Cb, Cr, - +[XMP, XMP-exif, Image] CompressedBitsPerPixel - Compressed Bits Per Pixel: 4 +[XMP, XMP-exif, Camera] FlashFired - Flash Fired: True +[XMP, XMP-exif, Camera] FlashFunction - Flash Function: False +[XMP, XMP-exif, Camera] FlashMode - Flash Mode: Auto +[XMP, XMP-exif, Camera] FlashRedEyeMode - Flash Red Eye Mode: False +[XMP, XMP-exif, Camera] FlashReturn - Flash Return: Return detected +[XMP, XMP-exifEX, Image] InteroperabilityIndex - Interoperability Index: R03 - DCF option file (Adobe RGB) +[XMP, XMP-tiff, Image] BitsPerSample - Bits Per Sample: 8 +[XMP, XMP-tiff, Image] Compression - Compression: JPEG (old-style) +[XMP, XMP-tiff, Image] ImageLength - Image Height: 47 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 71 +[XMP, XMP-tiff, Image] YCbCrCoefficients - Y Cb Cr Coefficients: 0.299, 0.587, 0.114 +[XMP, XMP-tiff, Image] YCbCrPositioning - Y Cb Cr Positioning: Co-sited +[XMP, XMP-tiff, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[Composite, Composite, Image] Exif-Aperture - Aperture: 5.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/60 +[Composite, Composite, Time] Exif-SubSecCreateDate - Create Date: 2005:01:14 08:57:59.20 +[Composite, Composite, Time] Exif-SubSecDateTimeOriginal - Date/Time Original: 2005:01:14 08:57:59.20 +[Composite, Composite, Time] Exif-SubSecModifyDate - Modify Date: 2005:01:14 08:57:59.20 +[Composite, Composite, Camera] Nikon-LensID - Lens ID: AF-S DX Zoom-Nikkor 18-70mm f/3.5-4.5G IF-ED +[Composite, Composite, Camera] Nikon-LensSpec - Lens Spec: 18-70mm f/3.5-4.5 G +[Composite, Composite, Camera] XMP-Flash - Flash: Auto, Fired, Return detected +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.410156 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.6 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.332031 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.5 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.020 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.02 m (0.62 - 0.64 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 22.1 deg (0.25 m) +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 56.0 mm (35 mm equivalent: 84.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 31.31 m diff --git a/ExifTool/t/Nikon_5.out b/ExifTool/t/Nikon_5.out new file mode 100644 index 0000000..6348c8e --- /dev/null +++ b/ExifTool/t/Nikon_5.out @@ -0,0 +1,169 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: NikonD2Hs.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 3.5 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:44-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: NIKON CORPORATION +[EXIF, IFD0, Camera] 272 - Camera Model Name: NIKON D2Hs +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Ver.1.00 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:03:18 02:55:18 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/125 +[EXIF, ExifIFD, Image] 33437 - F Number: 4.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 800 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:03:18 02:55:18 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:03:18 02:55:18 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 4 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 1.7 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 50.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Time] 37520 - Sub Sec Time: 16 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 16 +[EXIF, ExifIFD, Time] 37522 - Sub Sec Time Digitized: 16 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2464 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1632 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41730 - CFA Pattern: [Green,Blue][Red,Green] +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Manual +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 1 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 75 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: Low gain up +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Unknown +[EXIF, GPS, Location] 0 - GPS Version ID: 2.2.0.0 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 300 +[EXIF, IFD1, Image] 283 - Y Resolution: 300 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 3202 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Nikon, Camera] 1 - Maker Note Version: 2.10 +[MakerNotes, Nikon, Image] 2 - ISO: 800 +[MakerNotes, Nikon, Camera] 4 - Quality: Fine +[MakerNotes, Nikon, Camera] 5 - White Balance: Preset0 +[MakerNotes, Nikon, Camera] 6 - Sharpness: Auto +[MakerNotes, Nikon, Camera] 7 - Focus Mode: AF-C +[MakerNotes, Nikon, Camera] 8 - Flash Setting: Normal +[MakerNotes, Nikon, Camera] 9 - Flash Type: +[MakerNotes, Nikon, Camera] 11 - White Balance Fine Tune: 0 +[MakerNotes, Nikon, Camera] 13 - Program Shift: 0 +[MakerNotes, Nikon, Camera] 14 - Exposure Difference: 0 +[MakerNotes, PreviewIFD, Image] 259 - Compression: JPEG (old-style) +[MakerNotes, PreviewIFD, Image] 282 - X Resolution: 300 +[MakerNotes, PreviewIFD, Image] 283 - Y Resolution: 300 +[MakerNotes, PreviewIFD, Image] 296 - Resolution Unit: inches +[MakerNotes, PreviewIFD, Image] 513 - Preview Image Start: 3230 +[MakerNotes, PreviewIFD, Image] 514 - Preview Image Length: 26 +[MakerNotes, PreviewIFD, Image] 531 - Y Cb Cr Positioning: Co-sited +[MakerNotes, Nikon, Camera] 19 - ISO Setting: 800 +[MakerNotes, Nikon, Camera] 22 - Image Boundary: 0 0 2464 1632 +[MakerNotes, Nikon, Camera] 23 - External Flash Exposure Comp: 0 +[MakerNotes, Nikon, Camera] 24 - Flash Exposure Bracket Value: 0.0 +[MakerNotes, Nikon, Camera] 25 - Exposure Bracket Value: 0 +[MakerNotes, Nikon, Camera] 28 - Exposure Tuning: 0 +[MakerNotes, Nikon, Camera] 29 - Serial Number: 3001006 +[MakerNotes, Nikon, Camera] 30 - Color Space: sRGB +[MakerNotes, Nikon, Camera] 129 - Tone Comp: Auto +[MakerNotes, Nikon, Camera] 131 - Lens Type: D +[MakerNotes, Nikon, Camera] 132 - Lens: 50mm f/1.8 +[MakerNotes, Nikon, Camera] 135 - Flash Mode: Did Not Fire +[MakerNotes, Nikon, Camera] 0 - AF Area Mode: Single Area +[MakerNotes, Nikon, Camera] 1 - AF Point: Center +[MakerNotes, Nikon, Camera] 2 - AF Points In Focus: Center +[MakerNotes, Nikon, Camera] 137 - Shooting Mode: Delay +[MakerNotes, Nikon, Camera] 139 - Lens F Stops: 7.33 +[MakerNotes, Nikon, Camera] 141 - Color Hue: Mode3 +[MakerNotes, Nikon, Camera] 144 - Light Source: Colored +[MakerNotes, Nikon, Camera] 0 - Shot Info Version: 0206 +[MakerNotes, Nikon, Camera] 146 - Hue Adjustment: 0 +[MakerNotes, Nikon, Camera] 149 - Noise Reduction: Off +[MakerNotes, Nikon, Camera] 0 - WB RGGB Levels: 562 256 256 537 +[MakerNotes, Nikon, Camera] 0 - Lens Data Version: 0201 +[MakerNotes, Nikon, Camera] 4 - Exit Pupil Position: 60.2 mm +[MakerNotes, Nikon, Camera] 5 - AF Aperture: 1.9 +[MakerNotes, Nikon, Camera] 8 - Focus Position: 0x11 +[MakerNotes, Nikon, Camera] 9 - Focus Distance: 0.71 m +[MakerNotes, Nikon, Camera] 10 - Focal Length: 50.4 mm +[MakerNotes, Nikon, Camera] 11 - Lens ID Number: 118 +[MakerNotes, Nikon, Camera] 12 - Lens F Stops: 7.33 +[MakerNotes, Nikon, Camera] 13 - Min Focal Length: 50.4 mm +[MakerNotes, Nikon, Camera] 14 - Max Focal Length: 50.4 mm +[MakerNotes, Nikon, Camera] 15 - Max Aperture At Min Focal: 1.8 +[MakerNotes, Nikon, Camera] 16 - Max Aperture At Max Focal: 1.8 +[MakerNotes, Nikon, Camera] 17 - MCU Version: 122 +[MakerNotes, Nikon, Camera] 18 - Effective Max Aperture: 1.8 +[MakerNotes, Nikon, Camera] 154 - Sensor Pixel Size: 9.4 x 9.4 um +[MakerNotes, Nikon, Camera] 162 - Image Data Size: 1369271 +[MakerNotes, Nikon, Camera] 165 - Image Count: 2 +[MakerNotes, Nikon, Camera] 166 - Deleted Image Count: 0 +[MakerNotes, Nikon, Camera] 167 - Shutter Count: 2 +[MakerNotes, Nikon, Camera] 0 - Flash Info Version: 0100 +[MakerNotes, Nikon, Camera] 4 - Flash Source: None +[MakerNotes, Nikon, Camera] 6 - External Flash Firmware: n/a +[MakerNotes, Nikon, Camera] 8 - External Flash Flags: (none) +[MakerNotes, Nikon, Camera] 9.1 - Flash Commander Mode: Off +[MakerNotes, Nikon, Camera] 9.2 - Flash Control Mode: Off +[MakerNotes, Nikon, Camera] 10 - Flash Compensation: 0 +[MakerNotes, Nikon, Camera] 14 - Flash GN Distance: 0 +[MakerNotes, Nikon, Camera] 15 - Flash Group A Control Mode: Off +[MakerNotes, Nikon, Camera] 16 - Flash Group B Control Mode: Off +[MakerNotes, Nikon, Camera] 17 - Flash Group A Compensation: 0 +[MakerNotes, Nikon, Camera] 18 - Flash Group B Compensation: 0 +[MakerNotes, Nikon, Camera] 170 - Saturation: Normal +[MakerNotes, Nikon, Camera] 177 - High ISO Noise Reduction: Normal +[MakerNotes, PreviewIFD, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[Composite, Composite, Image] Exif-Aperture - Aperture: 4.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/125 +[Composite, Composite, Time] Exif-SubSecCreateDate - Create Date: 2005:03:18 02:55:18.16 +[Composite, Composite, Time] Exif-SubSecDateTimeOriginal - Date/Time Original: 2005:03:18 02:55:18.16 +[Composite, Composite, Time] Exif-SubSecModifyDate - Modify Date: 2005:03:18 02:55:18.16 +[Composite, Composite, Camera] Nikon-LensID - Lens ID: AF Nikkor 50mm f/1.8D +[Composite, Composite, Camera] Nikon-LensSpec - Lens Spec: 50mm f/1.8 D +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 2.097656 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-LightValue - Light Value: 8.0 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.195313 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.5 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.020 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.03 m (0.69 - 0.72 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 25.1 deg (0.32 m) +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 50.0 mm (35 mm equivalent: 75.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 31.20 m diff --git a/ExifTool/t/Nikon_7.out b/ExifTool/t/Nikon_7.out new file mode 100644 index 0000000..bd29b4f --- /dev/null +++ b/ExifTool/t/Nikon_7.out @@ -0,0 +1,261 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Nikon.nef +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 6.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2007:01:05 13:00:46-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:44-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: NEF +[File, File, Other] FileTypeExtension - File Type Extension: nef +[File, File, Other] MIMEType - MIME Type: image/x-nikon-nef +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 4996c61239acf3d1d9fa08228a6f6825 +[EXIF, IFD0, Image] 254 - Subfile Type: Reduced-resolution image +[EXIF, IFD0, Image] 256 - Image Width: 160 +[EXIF, IFD0, Image] 257 - Image Height: 106 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 259 - Compression: Uncompressed +[EXIF, IFD0, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD0, Camera] 271 - Make: NIKON CORPORATION +[EXIF, IFD0, Camera] 272 - Camera Model Name: NIKON D70 +[EXIF, IFD0, Image] 273 - Strip Offsets: 6170 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD0, Image] 278 - Rows Per Strip: 106 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 18 +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Nikon Capture Editor 4.3.1 W +[EXIF, IFD0, Time] 306 - Modify Date: 2005:08:25 13:09:05 +[EXIF, SubIFD, Image] 254 - Subfile Type: Reduced-resolution image +[EXIF, SubIFD, Image] 259 - Compression: JPEG (old-style) +[EXIF, SubIFD, Image] 274 - Orientation: Horizontal (normal) +[EXIF, SubIFD, Image] 282 - X Resolution: 1 +[EXIF, SubIFD, Image] 283 - Y Resolution: 0 +[EXIF, SubIFD, Image] 296 - Resolution Unit: inches +[EXIF, SubIFD, Image] 513 - Jpg From Raw Start: 1120 +[EXIF, SubIFD, Image] 514 - Jpg From Raw Length: 29 +[EXIF, SubIFD1, Image] 254 - Subfile Type: Full-resolution image +[EXIF, SubIFD1, Image] 256 - Image Width: 3040 +[EXIF, SubIFD1, Image] 257 - Image Height: 2014 +[EXIF, SubIFD1, Image] 258 - Bits Per Sample: 12 +[EXIF, SubIFD1, Image] 259 - Compression: Nikon NEF Compressed +[EXIF, SubIFD1, Image] 262 - Photometric Interpretation: Color Filter Array +[EXIF, SubIFD1, Image] 273 - Strip Offsets: 1376 +[EXIF, SubIFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, SubIFD1, Image] 277 - Samples Per Pixel: 1 +[EXIF, SubIFD1, Image] 278 - Rows Per Strip: 2014 +[EXIF, SubIFD1, Image] 279 - Strip Byte Counts: 18 +[EXIF, SubIFD1, Image] 282 - X Resolution: 300 +[EXIF, SubIFD1, Image] 283 - Y Resolution: 300 +[EXIF, SubIFD1, Image] 284 - Planar Configuration: Chunky +[EXIF, SubIFD1, Image] 296 - Resolution Unit: inches +[EXIF, SubIFD1, Image] 33421 - CFA Repeat Pattern Dim: 2 2 +[EXIF, SubIFD1, Image] 33422 - CFA Pattern 2: 2 1 1 0 +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/20 +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 200 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2004:06:09 16:02:35 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2004:06:09 16:02:35 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 18.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Time] 37520 - Sub Sec Time: 00 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 00 +[EXIF, ExifIFD, Time] 37522 - Sub Sec Time Digitized: 00 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41730 - CFA Pattern: [Blue,Green][Green,Red] +[EXIF, IFD0, Time] 36867 - Date/Time Original: 2004:06:09 16:02:35 +[EXIF, IFD0, Image] 37398 - TIFF-EP Standard ID: 1 0 0 0 +[EXIF, SubIFD, Preview] Exif-JpgFromRaw - Jpg From Raw: (Binary data 29 bytes) +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A caption +[IPTC, IPTC, Other] 40 - Special Instructions: none +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ontario +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC2, Other] 0 - Application Record Version: 4 +[IPTC, IPTC2, Other] 120 - Caption-Abstract: A caption +[IPTC, IPTC2, Other] 40 - Special Instructions: none +[IPTC, IPTC2, Location] 90 - City: Kingston +[IPTC, IPTC2, Location] 95 - Province-State: Ontario +[IPTC, IPTC2, Location] 101 - Country-Primary Location Name: Canada +[MakerNotes, Nikon, Camera] 1 - Maker Note Version: 2.10 +[MakerNotes, Nikon, Image] 2 - ISO: 200 +[MakerNotes, Nikon, Camera] 4 - Quality: RAW +[MakerNotes, Nikon, Camera] 5 - White Balance: Auto +[MakerNotes, Nikon, Camera] 6 - Sharpness: None +[MakerNotes, Nikon, Camera] 7 - Focus Mode: AF-S +[MakerNotes, Nikon, Camera] 11 - White Balance Fine Tune: 0 +[MakerNotes, Nikon, Camera] 13 - Program Shift: 0 +[MakerNotes, Nikon, Camera] 14 - Exposure Difference: 0 +[MakerNotes, PreviewIFD, Image] 259 - Compression: JPEG (old-style) +[MakerNotes, PreviewIFD, Image] 282 - X Resolution: 72 +[MakerNotes, PreviewIFD, Image] 283 - Y Resolution: 72 +[MakerNotes, PreviewIFD, Image] 296 - Resolution Unit: inches +[MakerNotes, PreviewIFD, Image] 513 - Preview Image Start: 5958 +[MakerNotes, PreviewIFD, Image] 514 - Preview Image Length: 26 +[MakerNotes, Nikon, Camera] 19 - ISO Setting: 200 +[MakerNotes, Nikon, Camera] 25 - Exposure Bracket Value: 0 +[MakerNotes, Nikon, Camera] 129 - Tone Comp: CS +[MakerNotes, Nikon, Camera] 131 - Lens Type: G +[MakerNotes, Nikon, Camera] 132 - Lens: 18-70mm f/3.5-4.5 +[MakerNotes, Nikon, Camera] 135 - Flash Mode: Did Not Fire +[MakerNotes, Nikon, Camera] 0 - AF Area Mode: Single Area +[MakerNotes, Nikon, Camera] 1 - AF Point: Center +[MakerNotes, Nikon, Camera] 2 - AF Points In Focus: Center +[MakerNotes, Nikon, Camera] 137 - Shooting Mode: Single-Frame +[MakerNotes, Nikon, Camera] 139 - Lens F Stops: 5.33 +[MakerNotes, Nikon, Camera] 140 - Contrast Curve: (Binary data 17 bytes) +[MakerNotes, Nikon, Camera] 141 - Color Hue: Mode2 +[MakerNotes, Nikon, Camera] 144 - Light Source: Natural +[MakerNotes, Nikon, Camera] 146 - Hue Adjustment: 0 +[MakerNotes, Nikon, Camera] 147 - NEF Compression: Lossy (type 1) +[MakerNotes, Nikon, Camera] 149 - Noise Reduction: Off +[MakerNotes, Nikon, Camera] 150 - NEF Linearization Table: (Binary data 17 bytes) +[MakerNotes, Nikon, Camera] 0 - WB RGBG Levels: 478 265 493 265 +[MakerNotes, Nikon, Camera] 0 - Lens Data Version: 0101 +[MakerNotes, Nikon, Camera] 4 - Exit Pupil Position: 102.4 mm +[MakerNotes, Nikon, Camera] 5 - AF Aperture: 3.6 +[MakerNotes, Nikon, Camera] 8 - Focus Position: 0xf1 +[MakerNotes, Nikon, Camera] 9 - Focus Distance: 2.37 m +[MakerNotes, Nikon, Camera] 10 - Focal Length: 18.3 mm +[MakerNotes, Nikon, Camera] 11 - Lens ID Number: 127 +[MakerNotes, Nikon, Camera] 12 - Lens F Stops: 5.33 +[MakerNotes, Nikon, Camera] 13 - Min Focal Length: 18.3 mm +[MakerNotes, Nikon, Camera] 14 - Max Focal Length: 71.3 mm +[MakerNotes, Nikon, Camera] 15 - Max Aperture At Min Focal: 3.6 +[MakerNotes, Nikon, Camera] 16 - Max Aperture At Max Focal: 4.5 +[MakerNotes, Nikon, Camera] 17 - MCU Version: 132 +[MakerNotes, Nikon, Camera] 18 - Effective Max Aperture: 3.6 +[MakerNotes, Nikon, Camera] 153 - Raw Image Center: 1520 1008 +[MakerNotes, Nikon, Camera] 154 - Sensor Pixel Size: 7.8 x 7.8 um +[MakerNotes, Nikon, Camera] 160 - Serial Number: 12345678 +[MakerNotes, Nikon, Camera] 167 - Shutter Count: 3619 +[MakerNotes, Nikon, Camera] 169 - Image Optimization: Custom +[MakerNotes, Nikon, Camera] 170 - Saturation: Normal +[MakerNotes, Nikon, Camera] 171 - Vari Program: +[MakerNotes, NikonCapture, Image] 1990472192 - Unsharp Mask: Off +[MakerNotes, NikonCapture, Image] 0 - Unsharp Count: 1 +[MakerNotes, NikonCapture, Image] 19 - Unsharp 1 Color: RGB +[MakerNotes, NikonCapture, Image] 23 - Unsharp 1 Intensity: 100 +[MakerNotes, NikonCapture, Image] 25 - Unsharp 1 Halo Width: 4 +[MakerNotes, NikonCapture, Image] 27 - Unsharp 1 Threshold: 6 +[MakerNotes, NikonCapture, Image] 1990472193 - Curves: On +[MakerNotes, NikonCapture, Image] 1990472194 - Color Balance Adj: On +[MakerNotes, NikonCapture, Image] 9103454 - LCH Editor: On +[MakerNotes, NikonCapture, Image] 1990472198 - Flip Horizontal: No +[MakerNotes, NikonCapture, Image] 1990472199 - Rotation: 0 +[MakerNotes, NikonCapture, Image] 4265884229 - Image Dust Off: On +[MakerNotes, NikonCapture, Image] 30 - Crop Left: 0 +[MakerNotes, NikonCapture, Image] 38 - Crop Top: 0 +[MakerNotes, NikonCapture, Image] 46 - Crop Right: 3008 +[MakerNotes, NikonCapture, Image] 54 - Crop Bottom: 2000 +[MakerNotes, NikonCapture, Image] 142 - Crop Output Width Inches: 10.0266666666667 +[MakerNotes, NikonCapture, Image] 150 - Crop Output Height Inches: 6.66666666666667 +[MakerNotes, NikonCapture, Image] 158 - Crop Scaled Resolution: 300 +[MakerNotes, NikonCapture, Image] 174 - Crop Source Resolution: 300 +[MakerNotes, NikonCapture, Image] 182 - Crop Output Resolution: 300 +[MakerNotes, NikonCapture, Image] 190 - Crop Output Scale: 1 +[MakerNotes, NikonCapture, Image] 198 - Crop Output Width: 3008 +[MakerNotes, NikonCapture, Image] 206 - Crop Output Height: 2000 +[MakerNotes, NikonCapture, Image] 214 - Crop Output Pixels: 6016000 +[MakerNotes, NikonCapture, Image] 1990472195 - Advanced Raw: On +[MakerNotes, NikonCapture, Image] 0 - Exposure Adj: 0.3 +[MakerNotes, NikonCapture, Image] 18 - Exposure Adj 2: 0.3000 +[MakerNotes, NikonCapture, Image] 1990472196 - White Balance Adj: On +[MakerNotes, NikonCapture, Image] 0 - WB Adj Red Balance: 2.015162472363 +[MakerNotes, NikonCapture, Image] 8 - WB Adj Blue Balance: 1.6951313394899 +[MakerNotes, NikonCapture, Image] 16 - WB Adj Mode: Use Temperature +[MakerNotes, NikonCapture, Image] 20 - WB Adj Lighting: High Color Rendering Fluorescent (5000K) +[MakerNotes, NikonCapture, Image] 24 - WB Adj Temperature: 5000 +[MakerNotes, NikonCapture, Image] 1966984128 - Noise Reduction: On +[MakerNotes, NikonCapture, Image] 4 - Edge Noise Reduction: Off +[MakerNotes, NikonCapture, Image] 5 - Color Moire Reduction Mode: Off +[MakerNotes, NikonCapture, Image] 9 - Noise Reduction Intensity: 0 +[MakerNotes, NikonCapture, Image] 13 - Noise Reduction Sharpness: 5 +[MakerNotes, NikonCapture, Image] 17 - Noise Reduction Method: Faster +[MakerNotes, NikonCapture, Image] 1594785059 - Color Booster: Off +[MakerNotes, NikonCapture, Image] 0 - Color Boost Type: People +[MakerNotes, NikonCapture, Image] 1 - Color Boost Level: 10 +[MakerNotes, NikonCapture, Image] 1990472197 - Vignette Control: Off +[MakerNotes, NikonCapture, Image] 2892748224 - Vignette Control Intensity: 80 +[MakerNotes, NikonCapture, Image] 4264076367 - Auto Red Eye: On +[MakerNotes, NikonCapture, Image] 0 - Red Eye Correction: Off +[MakerNotes, NikonCapture, Image] 2875116126 - Photo Effects: On +[MakerNotes, NikonCapture, Image] 0 - Brightness Adj: 0 +[MakerNotes, NikonCapture, Image] 8 - Enhance Dark Tones: Off +[MakerNotes, NikonCapture, Image] 0 - Photo Effects Type: None +[MakerNotes, NikonCapture, Image] 4 - Photo Effects Red: 0 +[MakerNotes, NikonCapture, Image] 6 - Photo Effects Green: 0 +[MakerNotes, NikonCapture, Image] 8 - Photo Effects Blue: 0 +[MakerNotes, NikonCapture, Image] 801145905 - Straighten Angle: 0 +[MakerNotes, NikonCapture, Image] 1785607862 - D-Lighting HQ Selected: No +[MakerNotes, NikonCapture, Image] 210313803 - Color Aberration Control: Off +[MakerNotes, NikonCapture, Image] 3461698730 - D-Lighting HS: Off +[MakerNotes, NikonCapture, Image] 0 - D-Lighting HS Adjustment: 25 +[MakerNotes, NikonCapture, Image] 1 - D-Lighting HS Color Boost: 60 +[MakerNotes, NikonCapture, Image] 561376120 - D-Lighting HQ: Off +[MakerNotes, NikonCapture, Image] 0 - D-Lighting HQ Shadow: 50 +[MakerNotes, NikonCapture, Image] 1 - D-Lighting HQ Highlight: 1 +[MakerNotes, NikonCapture, Image] 2 - D-Lighting HQ Color Boost: 60 +[MakerNotes, Nikon, Camera] 1 - IFD0 Offset: 8 +[MakerNotes, Nikon, Camera] 2 - Preview IFD Offset: 11362 +[MakerNotes, Nikon, Camera] 3 - Sub IFD Offset: 1440 +[MakerNotes, NikonScan, Image] 2 - Film Type: POSITIVE +[MakerNotes, NikonScan, Image] 65 - Bit Depth: 8 +[MakerNotes, NikonScan, Image] 80 - Master Gain: 0.00 +[MakerNotes, NikonScan, Image] 81 - Color Gain: 0.00 0.00 0.00 +[MakerNotes, NikonScan, Image] 96 - Scan Image Enhancer: Off +[MakerNotes, NikonScan, Image] 256 - Digital ICE: Normal +[MakerNotes, PreviewIFD, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Nikon Corporation +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.2.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:12:07 18:59:22 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple Computer Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Nikon Adobe RGB 4.0.0.3000 +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.60976 0.31113 0.01947 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.20525 0.62566 0.06087 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.1492 0.06322 0.74463 +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.9505 1 1.0891 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Nikon Inc. & Nikon Corporation 2001 +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-CFAPattern - CFA Pattern: [Blue,Green][Green,Red] +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/20 +[Composite, Composite, Time] Exif-SubSecCreateDate - Create Date: 2004:06:09 16:02:35.00 +[Composite, Composite, Time] Exif-SubSecDateTimeOriginal - Date/Time Original: 2004:06:09 16:02:35.00 +[Composite, Composite, Time] Exif-SubSecModifyDate - Modify Date: 2005:08:25 13:09:05.00 +[Composite, Composite, Camera] Nikon-LensID - Lens ID: AF-S DX Zoom-Nikkor 18-70mm f/3.5-4.5G IF-ED +[Composite, Composite, Camera] Nikon-LensSpec - Lens Spec: 18-70mm f/3.5-4.5 G +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.860377 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3040x2014 +[Composite, Composite, Image] Exif-LightValue - Light Value: 6.9 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 6.1 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.803774 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 18.0 mm diff --git a/ExifTool/t/Nikon_8.out b/ExifTool/t/Nikon_8.out new file mode 100644 index 0000000..88c4445 --- /dev/null +++ b/ExifTool/t/Nikon_8.out @@ -0,0 +1,4 @@ +[IPTC, IPTC, Other] 120 - Caption-Abstract: A new caption +[IPTC, IPTC2, Other] 120 - Caption-Abstract: A new caption +[MakerNotes, NikonCapture, Image] 2892748224 - Vignette Control Intensity: 70 +[MakerNotes, NikonCapture, Image] 2875116126 - Photo Effects: Off diff --git a/ExifTool/t/Olympus.t b/ExifTool/t/Olympus.t new file mode 100644 index 0000000..ddb4a2b --- /dev/null +++ b/ExifTool/t/Olympus.t @@ -0,0 +1,94 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Olympus.t". + +BEGIN { + $| = 1; print "1..8\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Olympus; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Olympus'; +my $testnum = 1; + +# test 2: Extract information from Olympus.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Olympus.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + [Software => 'ExifTool', Group => 'XMP'], + [Macro => 'On'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: Extract information from OlympusE1.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/OlympusE1.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 5: Rewrite Olympus E1 image +{ + ++$testnum; + my @writeInfo = ( + [LensSerialNumber => '012345678'], + [CoringFilter => 0], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/OlympusE1.jpg'); + print "ok $testnum\n"; +} + +# test 6: Test reading Olympus type 2 maker notes +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Olympus2.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 7: Rewrite type 2 maker notes +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValue(FocusDistance => 100); + $exifTool->SetNewValue(Macro => 'On'); + $exifTool->WriteInfo('t/images/Olympus2.jpg', $testfile); + if (testVerbose($testname, $testnum, $testfile, 2)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 8: Extract information from Olympus.dss +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Olympus.dss'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Olympus_2.out b/ExifTool/t/Olympus_2.out new file mode 100644 index 0000000..1b84bc3 --- /dev/null +++ b/ExifTool/t/Olympus_2.out @@ -0,0 +1,81 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Olympus.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1573 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:13-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: OLYMPUS DIGITAL CAMERA +[EXIF, IFD0, Camera] 271 - Make: OLYMPUS OPTICAL CO.,LTD +[EXIF, IFD0, Camera] 272 - Camera Model Name: C2000Z +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: v951p-80 +[EXIF, IFD0, Time] 306 - Modify Date: 1999:12:06 16:38:40 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/30 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 1999:12:06 16:38:40 +[EXIF, ExifIFD, Time] 36868 - Create Date: 1999:12:06 16:38:40 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 7.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 640 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 480 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1296 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Olympus, Camera] 512 - Special Mode: Normal, Sequence: 0, Panorama: (none) +[MakerNotes, Olympus, Camera] 513 - Quality: SQ (Low) +[MakerNotes, Olympus, Camera] 514 - Macro: Off +[MakerNotes, Olympus, Camera] 515 - Black And White Mode: Off +[MakerNotes, Olympus, Camera] 516 - Digital Zoom: 0.0 +[MakerNotes, Olympus, Camera] 517 - Focal Plane Diagonal: 7.8 mm +[MakerNotes, Olympus, Camera] 518 - Lens Distortion Params: -215 -388 -418 -185 -310 -314 +[MakerNotes, Olympus, Camera] 519 - Camera Type: C2000Z +[MakerNotes, Olympus, Image] Resolution - Resolution: 1 +[MakerNotes, Olympus, Camera] Type - Camera Type: C2000Z +[MakerNotes, Olympus, Camera] 521 - Camera ID: OLYMPUS DIGITAL CAMERA +[MakerNotes, Olympus, Camera] 3840 - Data Dump: (Binary data 186 bytes) +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 5.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/30 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.005 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 49.7 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 7.0 mm (35 mm equivalent: 38.8 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 4.52 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 6.9 diff --git a/ExifTool/t/Olympus_3.out b/ExifTool/t/Olympus_3.out new file mode 100644 index 0000000..9ac8798 --- /dev/null +++ b/ExifTool/t/Olympus_3.out @@ -0,0 +1,83 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Olympus_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 4.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:44-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: OLYMPUS DIGITAL CAMERA +[EXIF, IFD0, Camera] 271 - Make: OLYMPUS OPTICAL CO.,LTD +[EXIF, IFD0, Camera] 272 - Camera Model Name: C2000Z +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: v951p-80 +[EXIF, IFD0, Time] 306 - Modify Date: 1999:12:06 16:38:40 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/30 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 1999:12:06 16:38:40 +[EXIF, ExifIFD, Time] 36868 - Create Date: 1999:12:06 16:38:40 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 7.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 640 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 480 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1296 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Olympus, Camera] 512 - Special Mode: Normal, Sequence: 0, Panorama: (none) +[MakerNotes, Olympus, Camera] 513 - Quality: SQ (Low) +[MakerNotes, Olympus, Camera] 514 - Macro: On +[MakerNotes, Olympus, Camera] 515 - Black And White Mode: Off +[MakerNotes, Olympus, Camera] 516 - Digital Zoom: 0.0 +[MakerNotes, Olympus, Camera] 517 - Focal Plane Diagonal: 7.8 mm +[MakerNotes, Olympus, Camera] 518 - Lens Distortion Params: -215 -388 -418 -185 -310 -314 +[MakerNotes, Olympus, Camera] 519 - Camera Type: C2000Z +[MakerNotes, Olympus, Image] Resolution - Resolution: 1 +[MakerNotes, Olympus, Camera] Type - Camera Type: C2000Z +[MakerNotes, Olympus, Camera] 521 - Camera ID: OLYMPUS DIGITAL CAMERA +[MakerNotes, Olympus, Camera] 3840 - Data Dump: (Binary data 186 bytes) +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-tiff, Image] Software - Software: ExifTool +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 5.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/30 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.005 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 49.7 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 7.0 mm (35 mm equivalent: 38.8 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 4.52 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 6.9 diff --git a/ExifTool/t/Olympus_4.out b/ExifTool/t/Olympus_4.out new file mode 100644 index 0000000..6f9fc87 --- /dev/null +++ b/ExifTool/t/Olympus_4.out @@ -0,0 +1,195 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.59 +[File, System, Other] FileName - File Name: OlympusE1.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 7.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2023:03:27 08:21:17-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2023:02:21 15:57:49-05:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: OLYMPUS DIGITAL CAMERA +[EXIF, IFD0, Camera] 271 - Make: OLYMPUS CORPORATION +[EXIF, IFD0, Camera] 272 - Camera Model Name: E-1 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 314 +[EXIF, IFD0, Image] 283 - Y Resolution: 314 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Version 1.3 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:01:05 10:53:41 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/100 +[EXIF, ExifIFD, Image] 33437 - F Number: 4.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 400 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:01:05 10:53:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:01:05 10:53:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 20.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2560 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1920 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: High gain up +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Hard +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 7088 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Olympus, Camera] 512 - Special Mode: Fast, Sequence: 2, Panorama: (none) +[MakerNotes, Olympus, Camera] 513 - Quality: SHQ (Fine) +[MakerNotes, Olympus, Camera] 514 - Macro: Off +[MakerNotes, Olympus, Camera] 515 - Black And White Mode: Off +[MakerNotes, Olympus, Camera] 516 - Digital Zoom: 0.0 +[MakerNotes, Olympus, Camera] 517 - Focal Plane Diagonal: 21.6 mm +[MakerNotes, Olympus, Camera] 518 - Lens Distortion Params: 0 0 0 0 0 0 +[MakerNotes, Olympus, Camera] 519 - Camera Type: E-1 +[MakerNotes, Olympus, Camera] 521 - Camera ID: OLYMPUS DIGITAL CAMERA +[MakerNotes, Olympus, Camera] 4111 - Sharpness: Hard +[MakerNotes, Olympus, Camera] 4113 - Color Matrix: 356 -34 -66 -22 308 -30 6 -126 376 +[MakerNotes, Olympus, Camera] 4114 - Black Level: 69 69 69 68 +[MakerNotes, Olympus, Camera] 4119 - Red Balance: 1.609375 +[MakerNotes, Olympus, Camera] 4120 - Blue Balance: 1.1328125 +[MakerNotes, Olympus, Camera] 4137 - Contrast: Normal +[MakerNotes, Olympus, Camera] 4138 - Sharpness Factor: 576 +[MakerNotes, Olympus, Camera] 4139 - Color Control: 96 4096 2944 4096 16 128 +[MakerNotes, Olympus, Camera] 4140 - Valid Bits: 12 0 +[MakerNotes, Olympus, Camera] 4141 - Coring Filter: 1536 +[MakerNotes, Olympus, Camera] 4142 - Olympus Image Width: 2560 +[MakerNotes, Olympus, Camera] 4143 - Olympus Image Height: 1920 +[MakerNotes, Olympus, Camera] 0 - Equipment Version: 010 +[MakerNotes, Olympus, Camera] 256 - Camera Type 2: E-1 +[MakerNotes, Olympus, Camera] 257 - Serial Number: 143008611 +[MakerNotes, Olympus, Camera] 258 - Internal Serial Number: 4001310004375102 +[MakerNotes, Olympus, Camera] 259 - Focal Plane Diagonal: 21.6 mm +[MakerNotes, Olympus, Camera] 260 - Body Firmware Version: 1.300 +[MakerNotes, Olympus, Camera] 513 - Lens Type: Olympus Zuiko Digital 14-54mm F2.8-3.5 +[MakerNotes, Olympus, Camera] 514 - Lens Serial Number: 050105886 +[MakerNotes, Olympus, Camera] 516 - Lens Firmware Version: 1.100 +[MakerNotes, Olympus, Camera] 518 - Max Aperture At Max Focal: 3.5 +[MakerNotes, Olympus, Camera] 519 - Min Focal Length: 14 +[MakerNotes, Olympus, Camera] 520 - Max Focal Length: 54 +[MakerNotes, Olympus, Camera] 522 - Max Aperture: 2.8 +[MakerNotes, Olympus, Camera] 523 - Lens Properties: 0xc043 +[MakerNotes, Olympus, Camera] 769 - Extender: None +[MakerNotes, Olympus, Camera] 770 - Extender Serial Number: +[MakerNotes, Olympus, Camera] 772 - Extender Firmware Version: 0 +[MakerNotes, Olympus, Camera] 4096 - Flash Type: None +[MakerNotes, Olympus, Camera] 4097 - Flash Model: None +[MakerNotes, Olympus, Camera] 4098 - Flash Firmware Version: 0 +[MakerNotes, Olympus, Camera] 4099 - Flash Serial Number: +[MakerNotes, Olympus, Camera] 0 - Camera Settings Version: 010 +[MakerNotes, Olympus, Camera] 256 - Preview Image Valid: Yes +[MakerNotes, Olympus, Camera] 257 - Preview Image Start: 2568 +[MakerNotes, Olympus, Camera] 258 - Preview Image Length: 26 +[MakerNotes, Olympus, Camera] 512 - Exposure Mode: Program +[MakerNotes, Olympus, Camera] 513 - AE Lock: Off +[MakerNotes, Olympus, Camera] 514 - Metering Mode: ESP +[MakerNotes, Olympus, Camera] 768 - Macro Mode: Off +[MakerNotes, Olympus, Camera] 769 - Focus Mode: Single AF +[MakerNotes, Olympus, Camera] 770 - Focus Process: AF Used +[MakerNotes, Olympus, Camera] 771 - AF Search: Ready +[MakerNotes, Olympus, Camera] 772 - AF Areas: Center (121,121)-(133,133) +[MakerNotes, Olympus, Camera] 1024 - Flash Mode: Off +[MakerNotes, Olympus, Camera] 1025 - Flash Exposure Comp: 0 +[MakerNotes, Olympus, Camera] 1280 - White Balance 2: Auto +[MakerNotes, Olympus, Camera] 1281 - White Balance Temperature: Auto +[MakerNotes, Olympus, Camera] 1282 - White Balance Bracket: 0 +[MakerNotes, Olympus, Camera] 1283 - Custom Saturation: CS2 (min CS0, max CS4) +[MakerNotes, Olympus, Camera] 1284 - Modified Saturation: Off +[MakerNotes, Olympus, Camera] 1285 - Contrast Setting: 0 (min -2, max 2) +[MakerNotes, Olympus, Camera] 1286 - Sharpness Setting: 1 (min -3, max 5) +[MakerNotes, Olympus, Camera] 1287 - Color Space: sRGB +[MakerNotes, Olympus, Camera] 1290 - Noise Reduction: (none) +[MakerNotes, Olympus, Camera] 1291 - Distortion Correction: Off +[MakerNotes, Olympus, Camera] 1292 - Shading Compensation: Off +[MakerNotes, Olympus, Camera] 1293 - Compression Factor: 2.7 +[MakerNotes, Olympus, Camera] 1536 - Drive Mode: Continuous Shooting, Shot 2 +[MakerNotes, Olympus, Camera] 1539 - Image Quality 2: SHQ +[MakerNotes, Olympus, Camera] 0 - Raw Dev Version: 010 +[MakerNotes, Olympus, Camera] 256 - Raw Dev Exposure Bias Value: 0 +[MakerNotes, Olympus, Camera] 257 - Raw Dev White Balance Value: 0 +[MakerNotes, Olympus, Camera] 258 - Raw Dev WB Fine Adjustment: 0 +[MakerNotes, Olympus, Camera] 259 - Raw Dev Gray Point: 0 0 0 +[MakerNotes, Olympus, Camera] 260 - Raw Dev Saturation Emphasis: 0 0 0 +[MakerNotes, Olympus, Camera] 261 - Raw Dev Memory Color Emphasis: 0 +[MakerNotes, Olympus, Camera] 262 - Raw Dev Contrast Value: 0 0 0 +[MakerNotes, Olympus, Camera] 263 - Raw Dev Sharpness Value: 0 0 0 +[MakerNotes, Olympus, Camera] 264 - Raw Dev Color Space: sRGB +[MakerNotes, Olympus, Camera] 265 - Raw Dev Engine: High Speed +[MakerNotes, Olympus, Camera] 266 - Raw Dev Noise Reduction: (none) +[MakerNotes, Olympus, Camera] 267 - Raw Dev Edit Status: Original +[MakerNotes, Olympus, Camera] 268 - Raw Dev Settings: (none) +[MakerNotes, Olympus, Camera] 0 - Image Processing Version: 010 +[MakerNotes, Olympus, Camera] 256 - WB RB Levels: 412 290 +[MakerNotes, Olympus, Camera] 287 - WB G Level: 256 +[MakerNotes, Olympus, Camera] 512 - Color Matrix: 356 -34 -66 -22 308 -30 6 -126 376 +[MakerNotes, Olympus, Camera] 768 - Enhancer: 576 +[MakerNotes, Olympus, Camera] 784 - Coring Filter: 1536 +[MakerNotes, Olympus, Camera] 1536 - Black Level 2: 69 69 69 68 +[MakerNotes, Olympus, Camera] 1552 - Gain Base: 256 +[MakerNotes, Olympus, Camera] 1553 - Valid Bits: 12 0 +[MakerNotes, Olympus, Camera] 1554 - Crop Left: 0 0 +[MakerNotes, Olympus, Camera] 1555 - Crop Top: 0 0 +[MakerNotes, Olympus, Camera] 1556 - Crop Width: 2560 +[MakerNotes, Olympus, Camera] 1557 - Crop Height: 1920 +[MakerNotes, Olympus, Camera] 4112 - Noise Reduction 2: (none) +[MakerNotes, Olympus, Camera] 4113 - Distortion Correction 2: Off +[MakerNotes, Olympus, Camera] 4114 - Shading Compensation 2: Off +[MakerNotes, Olympus, Camera] 0 - Focus Info Version: 010 +[MakerNotes, Olympus, Camera] 528 - Scene Detect: 0 +[MakerNotes, Olympus, Camera] 768 - Zoom Step Count: 3 +[MakerNotes, Olympus, Camera] 769 - Focus Step Count: 332 +[MakerNotes, Olympus, Camera] 773 - Focus Distance: 0.705 m +[MakerNotes, Olympus, Camera] 776 - AF Point: Center (horizontal) +[MakerNotes, Olympus, Camera] 795 - AF Point Details: 0 +[MakerNotes, Olympus, Camera] 4609 - External Flash: Off +[MakerNotes, Olympus, Camera] 4612 - External Flash Bounce: Bounce or Off +[MakerNotes, Olympus, Camera] 4613 - External Flash Zoom: 0 +[MakerNotes, Olympus, Camera] 4616 - Internal Flash: Off +[MakerNotes, Olympus, Camera] 5376 - Sensor Temperature: 20 C +[MakerNotes, Olympus, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[Composite, Composite, Image] Exif-Aperture - Aperture: 4.5 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 2.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/100 +[Composite, Composite, Camera] Olympus-ExtenderStatus - Extender Status: Not attached +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.132813 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.015 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.17 m (0.63 - 0.80 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 47.2 deg (0.62 m) +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 20.0 mm (35 mm equivalent: 40.1 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 5.93 m +[Composite, Composite, Camera] Exif-LensID - Lens ID: Olympus Zuiko Digital 14-54mm F2.8-3.5 +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.0 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.609375 diff --git a/ExifTool/t/Olympus_5.out b/ExifTool/t/Olympus_5.out new file mode 100644 index 0000000..8aa91b4 --- /dev/null +++ b/ExifTool/t/Olympus_5.out @@ -0,0 +1,266 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.59 +[File, System, Other] FileName - File Name: Olympus_5_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 7.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2023:03:27 08:29:44-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2023:03:27 08:29:44-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2023:03:27 08:29:44-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: OLYMPUS DIGITAL CAMERA +[EXIF, IFD0, Camera] 271 - Make: OLYMPUS CORPORATION +[EXIF, IFD0, Camera] 272 - Camera Model Name: E-1 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 314 +[EXIF, IFD0, Image] 283 - Y Resolution: 314 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Version 1.3 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:01:05 10:53:41 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/100 +[EXIF, ExifIFD, Image] 33437 - F Number: 4.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 400 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:01:05 10:53:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:01:05 10:53:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 20.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2560 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1920 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: High gain up +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Hard +[EXIF, ExifIFD, Image] 42037 - Lens Serial Number: 012345678 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 7096 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Olympus, Camera] 512 - Special Mode: Fast, Sequence: 2, Panorama: (none) +[MakerNotes, Olympus, Camera] 513 - Quality: SHQ (Fine) +[MakerNotes, Olympus, Camera] 514 - Macro: Off +[MakerNotes, Olympus, Camera] 515 - Black And White Mode: Off +[MakerNotes, Olympus, Camera] 516 - Digital Zoom: 0.0 +[MakerNotes, Olympus, Camera] 517 - Focal Plane Diagonal: 21.6 mm +[MakerNotes, Olympus, Camera] 518 - Lens Distortion Params: 0 0 0 0 0 0 +[MakerNotes, Olympus, Camera] 519 - Camera Type: E-1 +[MakerNotes, Olympus, Camera] 521 - Camera ID: OLYMPUS DIGITAL CAMERA +[MakerNotes, Olympus, Camera] 4111 - Sharpness: Hard +[MakerNotes, Olympus, Camera] 4113 - Color Matrix: 356 -34 -66 -22 308 -30 6 -126 376 +[MakerNotes, Olympus, Camera] 4114 - Black Level: 69 69 69 68 +[MakerNotes, Olympus, Camera] 4119 - Red Balance: 1.609375 +[MakerNotes, Olympus, Camera] 4120 - Blue Balance: 1.1328125 +[MakerNotes, Olympus, Camera] 4137 - Contrast: Normal +[MakerNotes, Olympus, Camera] 4138 - Sharpness Factor: 576 +[MakerNotes, Olympus, Camera] 4139 - Color Control: 96 4096 2944 4096 16 128 +[MakerNotes, Olympus, Camera] 4140 - Valid Bits: 12 0 +[MakerNotes, Olympus, Camera] 4141 - Coring Filter: 0 +[MakerNotes, Olympus, Camera] 4142 - Olympus Image Width: 2560 +[MakerNotes, Olympus, Camera] 4143 - Olympus Image Height: 1920 +[MakerNotes, Olympus, Camera] 0 - Equipment Version: 010 +[MakerNotes, Olympus, Camera] 256 - Camera Type 2: E-1 +[MakerNotes, Olympus, Camera] 257 - Serial Number: 143008611 +[MakerNotes, Olympus, Camera] 258 - Internal Serial Number: 4001310004375102 +[MakerNotes, Olympus, Camera] 259 - Focal Plane Diagonal: 21.6 mm +[MakerNotes, Olympus, Camera] 260 - Body Firmware Version: 1.300 +[MakerNotes, Olympus, Camera] 513 - Lens Type: Olympus Zuiko Digital 14-54mm F2.8-3.5 +[MakerNotes, Olympus, Camera] 514 - Lens Serial Number: 012345678 +[MakerNotes, Olympus, Camera] 516 - Lens Firmware Version: 1.100 +[MakerNotes, Olympus, Camera] 518 - Max Aperture At Max Focal: 3.5 +[MakerNotes, Olympus, Camera] 519 - Min Focal Length: 14 +[MakerNotes, Olympus, Camera] 520 - Max Focal Length: 54 +[MakerNotes, Olympus, Camera] 522 - Max Aperture: 2.8 +[MakerNotes, Olympus, Camera] 523 - Lens Properties: 0xc043 +[MakerNotes, Olympus, Camera] 769 - Extender: None +[MakerNotes, Olympus, Camera] 770 - Extender Serial Number: +[MakerNotes, Olympus, Camera] 772 - Extender Firmware Version: 0 +[MakerNotes, Olympus, Camera] 4096 - Flash Type: None +[MakerNotes, Olympus, Camera] 4097 - Flash Model: None +[MakerNotes, Olympus, Camera] 4098 - Flash Firmware Version: 0 +[MakerNotes, Olympus, Camera] 4099 - Flash Serial Number: +[MakerNotes, Olympus, Camera] 0 - Camera Settings Version: 010 +[MakerNotes, Olympus, Camera] 256 - Preview Image Valid: Yes +[MakerNotes, Olympus, Camera] 257 - Preview Image Start: 7124 +[MakerNotes, Olympus, Camera] 258 - Preview Image Length: 26 +[MakerNotes, Olympus, Camera] 512 - Exposure Mode: Program +[MakerNotes, Olympus, Camera] 513 - AE Lock: Off +[MakerNotes, Olympus, Camera] 514 - Metering Mode: ESP +[MakerNotes, Olympus, Camera] 768 - Macro Mode: Off +[MakerNotes, Olympus, Camera] 769 - Focus Mode: Single AF +[MakerNotes, Olympus, Camera] 770 - Focus Process: AF Used +[MakerNotes, Olympus, Camera] 771 - AF Search: Ready +[MakerNotes, Olympus, Camera] 772 - AF Areas: Center (121,121)-(133,133) +[MakerNotes, Olympus, Camera] 1024 - Flash Mode: Off +[MakerNotes, Olympus, Camera] 1025 - Flash Exposure Comp: 0 +[MakerNotes, Olympus, Camera] 1026 - Olympus Camera Settings 0x0402: 0 +[MakerNotes, Olympus, Camera] 1280 - White Balance 2: Auto +[MakerNotes, Olympus, Camera] 1281 - White Balance Temperature: Auto +[MakerNotes, Olympus, Camera] 1282 - White Balance Bracket: 0 +[MakerNotes, Olympus, Camera] 1283 - Custom Saturation: CS2 (min CS0, max CS4) +[MakerNotes, Olympus, Camera] 1284 - Modified Saturation: Off +[MakerNotes, Olympus, Camera] 1285 - Contrast Setting: 0 (min -2, max 2) +[MakerNotes, Olympus, Camera] 1286 - Sharpness Setting: 1 (min -3, max 5) +[MakerNotes, Olympus, Camera] 1287 - Color Space: sRGB +[MakerNotes, Olympus, Camera] 1288 - Olympus Camera Settings 0x0508: 0 +[MakerNotes, Olympus, Camera] 1290 - Noise Reduction: (none) +[MakerNotes, Olympus, Camera] 1291 - Distortion Correction: Off +[MakerNotes, Olympus, Camera] 1292 - Shading Compensation: Off +[MakerNotes, Olympus, Camera] 1293 - Compression Factor: 2.7 +[MakerNotes, Olympus, Camera] 1294 - Olympus Camera Settings 0x050e: 1 +[MakerNotes, Olympus, Camera] 1536 - Drive Mode: Continuous Shooting, Shot 2 +[MakerNotes, Olympus, Camera] 1539 - Image Quality 2: SHQ +[MakerNotes, Olympus, Camera] 0 - Raw Dev Version: 010 +[MakerNotes, Olympus, Camera] 256 - Raw Dev Exposure Bias Value: 0 +[MakerNotes, Olympus, Camera] 257 - Raw Dev White Balance Value: 0 +[MakerNotes, Olympus, Camera] 258 - Raw Dev WB Fine Adjustment: 0 +[MakerNotes, Olympus, Camera] 259 - Raw Dev Gray Point: 0 0 0 +[MakerNotes, Olympus, Camera] 260 - Raw Dev Saturation Emphasis: 0 0 0 +[MakerNotes, Olympus, Camera] 261 - Raw Dev Memory Color Emphasis: 0 +[MakerNotes, Olympus, Camera] 262 - Raw Dev Contrast Value: 0 0 0 +[MakerNotes, Olympus, Camera] 263 - Raw Dev Sharpness Value: 0 0 0 +[MakerNotes, Olympus, Camera] 264 - Raw Dev Color Space: sRGB +[MakerNotes, Olympus, Camera] 265 - Raw Dev Engine: High Speed +[MakerNotes, Olympus, Camera] 266 - Raw Dev Noise Reduction: (none) +[MakerNotes, Olympus, Camera] 267 - Raw Dev Edit Status: Original +[MakerNotes, Olympus, Camera] 268 - Raw Dev Settings: (none) +[MakerNotes, Olympus, Camera] 0 - Image Processing Version: 010 +[MakerNotes, Olympus, Camera] 256 - WB RB Levels: 412 290 +[MakerNotes, Olympus, Camera] 287 - WB G Level: 256 +[MakerNotes, Olympus, Camera] 512 - Color Matrix: 356 -34 -66 -22 308 -30 6 -126 376 +[MakerNotes, Olympus, Camera] 768 - Enhancer: 576 +[MakerNotes, Olympus, Camera] 784 - Coring Filter: 0 +[MakerNotes, Olympus, Camera] 1536 - Black Level 2: 69 69 69 68 +[MakerNotes, Olympus, Camera] 1552 - Gain Base: 256 +[MakerNotes, Olympus, Camera] 1553 - Valid Bits: 12 0 +[MakerNotes, Olympus, Camera] 1554 - Crop Left: 0 0 +[MakerNotes, Olympus, Camera] 1555 - Crop Top: 0 0 +[MakerNotes, Olympus, Camera] 1556 - Crop Width: 2560 +[MakerNotes, Olympus, Camera] 1557 - Crop Height: 1920 +[MakerNotes, Olympus, Camera] 1559 - Olympus Image Processing 0x0617: 1 +[MakerNotes, Olympus, Camera] 2048 - Olympus Image Processing 0x0800: -0.0549249015748501 0.0199025515466928 -0.0028715955559[...] +[MakerNotes, Olympus, Camera] 2049 - Olympus Image Processing 0x0801: 8194 8208 8250 8292 8352 8393 8469 8521 8587 8668 8725 [...] +[MakerNotes, Olympus, Camera] 2050 - Olympus Image Processing 0x0802: 102.3897059 +[MakerNotes, Olympus, Camera] 4096 - Olympus Image Processing 0x1000: 1770 +[MakerNotes, Olympus, Camera] 4097 - Olympus Image Processing 0x1001: 233 +[MakerNotes, Olympus, Camera] 4098 - Olympus Image Processing 0x1002: 164 +[MakerNotes, Olympus, Camera] 4099 - Olympus Image Processing 0x1003: 1 +[MakerNotes, Olympus, Camera] 4100 - Olympus Image Processing 0x1004: 5 +[MakerNotes, Olympus, Camera] 4112 - Noise Reduction 2: (none) +[MakerNotes, Olympus, Camera] 4113 - Distortion Correction 2: Off +[MakerNotes, Olympus, Camera] 4114 - Shading Compensation 2: Off +[MakerNotes, Olympus, Camera] 0 - Focus Info Version: 010 +[MakerNotes, Olympus, Camera] 512 - Olympus Focus Info 0x0200: 1711 1646 +[MakerNotes, Olympus, Camera] 513 - Olympus Focus Info 0x0201: 1792 1825 +[MakerNotes, Olympus, Camera] 514 - Olympus Focus Info 0x0202: 1146 1210 +[MakerNotes, Olympus, Camera] 515 - Olympus Focus Info 0x0203: 1063 +[MakerNotes, Olympus, Camera] 516 - Olympus Focus Info 0x0204: 1028 1137 +[MakerNotes, Olympus, Camera] 517 - Olympus Focus Info 0x0205: 1334 +[MakerNotes, Olympus, Camera] 518 - Olympus Focus Info 0x0206: 917 +[MakerNotes, Olympus, Camera] 519 - Olympus Focus Info 0x0207: 0 +[MakerNotes, Olympus, Camera] 521 - Auto Focus: Off +[MakerNotes, Olympus, Camera] 522 - Olympus Focus Info 0x020a: 0 +[MakerNotes, Olympus, Camera] 523 - Olympus Focus Info 0x020b: 256 +[MakerNotes, Olympus, Camera] 524 - Olympus Focus Info 0x020c: -494 -538 +[MakerNotes, Olympus, Camera] 525 - Olympus Focus Info 0x020d: 0 +[MakerNotes, Olympus, Camera] 526 - Olympus Focus Info 0x020e: 0 0 0 0 0 +[MakerNotes, Olympus, Camera] 527 - Olympus Focus Info 0x020f: 0 0 0 0 0 +[MakerNotes, Olympus, Camera] 528 - Scene Detect: 0 +[MakerNotes, Olympus, Camera] 529 - Scene Area: 0 0 0 0 0 0 0 0 +[MakerNotes, Olympus, Camera] 530 - Scene Detect Data: (Binary data 1439 bytes) +[MakerNotes, Olympus, Camera] 531 - Olympus Focus Info 0x0213: 0 +[MakerNotes, Olympus, Camera] 532 - Olympus Focus Info 0x0214: 0 +[MakerNotes, Olympus, Camera] 533 - Olympus Focus Info 0x0215: 27641 +[MakerNotes, Olympus, Camera] 768 - Zoom Step Count: 3 +[MakerNotes, Olympus, Camera] 769 - Focus Step Count: 332 +[MakerNotes, Olympus, Camera] 770 - Olympus Focus Info 0x0302: 0 +[MakerNotes, Olympus, Camera] 773 - Focus Distance: 0.705 m +[MakerNotes, Olympus, Camera] 775 - Olympus Focus Info 0x0307: 0 +[MakerNotes, Olympus, Camera] 776 - AF Point: Center (horizontal) +[MakerNotes, Olympus, Camera] 777 - Olympus Focus Info 0x0309: 0 +[MakerNotes, Olympus, Camera] 778 - Olympus Focus Info 0x030a: -62 +[MakerNotes, Olympus, Camera] 779 - Olympus Focus Info 0x030b: 57 +[MakerNotes, Olympus, Camera] 780 - Olympus Focus Info 0x030c: 1 674 +[MakerNotes, Olympus, Camera] 781 - Olympus Focus Info 0x030d: 1747 2847 4883 16470 +[MakerNotes, Olympus, Camera] 782 - Olympus Focus Info 0x030e: 0 +[MakerNotes, Olympus, Camera] 783 - Olympus Focus Info 0x030f: 288 +[MakerNotes, Olympus, Camera] 792 - Olympus Focus Info 0x0318: 3 +[MakerNotes, Olympus, Camera] 793 - Olympus Focus Info 0x0319: 4 +[MakerNotes, Olympus, Camera] 794 - Olympus Focus Info 0x031a: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Olympus, Camera] 795 - AF Point Details: 0 +[MakerNotes, Olympus, Camera] 1026 - Olympus Focus Info 0x0402: 0 +[MakerNotes, Olympus, Camera] 4608 - Olympus Focus Info 0x1200: 0 +[MakerNotes, Olympus, Camera] 4609 - External Flash: Off +[MakerNotes, Olympus, Camera] 4610 - Olympus Focus Info 0x1202: 0 +[MakerNotes, Olympus, Camera] 4611 - External Flash Guide Number: 0 +[MakerNotes, Olympus, Camera] 4612 - External Flash Bounce: Bounce or Off +[MakerNotes, Olympus, Camera] 4613 - External Flash Zoom: 0 +[MakerNotes, Olympus, Camera] 4614 - Olympus Focus Info 0x1206: 0 +[MakerNotes, Olympus, Camera] 4615 - Olympus Focus Info 0x1207: 0 +[MakerNotes, Olympus, Camera] 4616 - Internal Flash: Off +[MakerNotes, Olympus, Camera] 5376 - Sensor Temperature: 20 C +[MakerNotes, Olympus, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00140014 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x00000001 +[PrintIM, PrintIM, Printing] 3 - Print IM 0x0003: 0x00000088 +[PrintIM, PrintIM, Printing] 7 - Print IM 0x0007: 0x00000000 +[PrintIM, PrintIM, Printing] 8 - Print IM 0x0008: 0x00000000 +[PrintIM, PrintIM, Printing] 9 - Print IM 0x0009: 0x00000000 +[PrintIM, PrintIM, Printing] 10 - Print IM 0x000a: 0x00000000 +[PrintIM, PrintIM, Printing] 11 - Print IM 0x000b: 0x000000d0 +[PrintIM, PrintIM, Printing] 12 - Print IM 0x000c: 0x00000000 +[PrintIM, PrintIM, Printing] 13 - Print IM 0x000d: 0x00000000 +[PrintIM, PrintIM, Printing] 14 - Print IM 0x000e: 0x000000e8 +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x00000001 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x000000ff +[PrintIM, PrintIM, Printing] 258 - Print IM 0x0102: 0x00000083 +[PrintIM, PrintIM, Printing] 259 - Print IM 0x0103: 0x00000083 +[PrintIM, PrintIM, Printing] 260 - Print IM 0x0104: 0x00000083 +[PrintIM, PrintIM, Printing] 261 - Print IM 0x0105: 0x00000083 +[PrintIM, PrintIM, Printing] 262 - Print IM 0x0106: 0x00000083 +[PrintIM, PrintIM, Printing] 263 - Print IM 0x0107: 0x00808080 +[PrintIM, PrintIM, Printing] 272 - Print IM 0x0110: 0x00000082 +[Composite, Composite, Image] Exif-Aperture - Aperture: 4.5 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/100 +[Composite, Composite, Camera] Olympus-ExtenderStatus - Extender Status: Not attached +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.132813 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Olympus Zuiko Digital 14-54mm F2.8-3.5 +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.0 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.609375 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 2.0 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.015 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.17 m (0.63 - 0.80 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 47.2 deg (0.62 m) +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 20.0 mm (35 mm equivalent: 40.1 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 5.93 m diff --git a/ExifTool/t/Olympus_6.out b/ExifTool/t/Olympus_6.out new file mode 100644 index 0000000..1a0a2f0 --- /dev/null +++ b/ExifTool/t/Olympus_6.out @@ -0,0 +1,153 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Olympus2.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 7.7 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2007:02:27 20:01:26-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: OLYMPUS DIGITAL CAMERA +[EXIF, IFD0, Camera] 271 - Make: OLYMPUS IMAGING CORP. +[EXIF, IFD0, Camera] 272 - Camera Model Name: u760,S760 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 314 +[EXIF, IFD0, Image] 283 - Y Resolution: 314 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Version 1.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2007:02:16 15:09:13 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/250 +[EXIF, ExifIFD, Image] 33437 - F Number: 16.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 80 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2007:02:16 15:09:13 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2007:02:16 15:09:13 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: -0.7 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.4 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 19.5 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3072 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2304 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 1 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 7424 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Olympus, Camera] 512 - Special Mode: Normal, Sequence: 0, Panorama: (none) +[MakerNotes, Olympus, Camera] 513 - Quality: SHQ (Fine) +[MakerNotes, Olympus, Camera] 514 - Macro: Off +[MakerNotes, Olympus, Camera] 515 - Black And White Mode: Off +[MakerNotes, Olympus, Camera] 516 - Digital Zoom: 1.0 +[MakerNotes, Olympus, Camera] 517 - Focal Plane Diagonal: 7.58 mm +[MakerNotes, Olympus, Camera] 518 - Lens Distortion Params: 0 0 0 0 0 0 +[MakerNotes, Olympus, Camera] 519 - Camera Type: u760,S760 +[MakerNotes, Olympus, Camera] 521 - Camera ID: OLYMPUS DIGITAL CAMERA +[MakerNotes, Olympus, Camera] 0 - Equipment Version: 0100 +[MakerNotes, Olympus, Camera] 256 - Camera Type 2: u760,S760 +[MakerNotes, Olympus, Camera] 257 - Serial Number: C90500055 +[MakerNotes, Olympus, Camera] 258 - Internal Serial Number: 0171612001128001 +[MakerNotes, Olympus, Camera] 259 - Focal Plane Diagonal: 7.58 mm +[MakerNotes, Olympus, Camera] 260 - Body Firmware Version: 1.001 +[MakerNotes, Olympus, Camera] 0 - Camera Settings Version: 0100 +[MakerNotes, Olympus, Camera] 256 - Preview Image Valid: No +[MakerNotes, Olympus, Camera] 257 - Preview Image Start: 4294966614 +[MakerNotes, Olympus, Camera] 258 - Preview Image Length: 0 +[MakerNotes, Olympus, Camera] 512 - Exposure Mode: Program +[MakerNotes, Olympus, Camera] 513 - AE Lock: Off +[MakerNotes, Olympus, Camera] 514 - Metering Mode: ESP +[MakerNotes, Olympus, Camera] 768 - Macro Mode: Off +[MakerNotes, Olympus, Camera] 769 - Focus Mode: Single AF +[MakerNotes, Olympus, Camera] 770 - Focus Process: AF Not Used +[MakerNotes, Olympus, Camera] 771 - AF Search: Not Ready +[MakerNotes, Olympus, Camera] 772 - AF Areas: none +[MakerNotes, Olympus, Camera] 1024 - Flash Mode: Off +[MakerNotes, Olympus, Camera] 1025 - Flash Exposure Comp: 0 +[MakerNotes, Olympus, Camera] 1280 - White Balance 2: Auto +[MakerNotes, Olympus, Camera] 1281 - White Balance Temperature: Auto +[MakerNotes, Olympus, Camera] 1282 - White Balance Bracket: 0 0 +[MakerNotes, Olympus, Camera] 1283 - Custom Saturation: 0 (min -2, max 2) +[MakerNotes, Olympus, Camera] 1284 - Modified Saturation: Off +[MakerNotes, Olympus, Camera] 1285 - Contrast Setting: 0 (min -2, max 2) +[MakerNotes, Olympus, Camera] 1286 - Sharpness Setting: 0 (min -2, max 2) +[MakerNotes, Olympus, Camera] 1287 - Color Space: sRGB +[MakerNotes, Olympus, Camera] 1289 - Scene Mode: Standard +[MakerNotes, Olympus, Camera] 1290 - Noise Reduction: (none) +[MakerNotes, Olympus, Camera] 1291 - Distortion Correction: Off +[MakerNotes, Olympus, Camera] 1292 - Shading Compensation: Off +[MakerNotes, Olympus, Camera] 1293 - Compression Factor: 4 +[MakerNotes, Olympus, Camera] 1295 - Gradation: Normal +[MakerNotes, Olympus, Camera] 1536 - Drive Mode: Single Shot +[MakerNotes, Olympus, Camera] 1537 - Panorama Mode: Off +[MakerNotes, Olympus, Camera] 1539 - Image Quality 2: SHQ +[MakerNotes, Olympus, Camera] 2304 - Manometer Pressure: 0 kPa +[MakerNotes, Olympus, Camera] 2305 - Manometer Reading: 0 m, 0 ft +[MakerNotes, Olympus, Camera] 0 - Raw Dev Version: 0100 +[MakerNotes, Olympus, Camera] 267 - Raw Dev Edit Status: Original +[MakerNotes, Olympus, Camera] 0 - Image Processing Version: 0111 +[MakerNotes, Olympus, Camera] 256 - WB RB Levels: 536 390 +[MakerNotes, Olympus, Camera] 287 - WB G Level: 0 +[MakerNotes, Olympus, Camera] 512 - Color Matrix: 348 -54 -42 -56 422 -114 -2 -132 388 +[MakerNotes, Olympus, Camera] 768 - Enhancer: 38 +[MakerNotes, Olympus, Camera] 784 - Coring Filter: 15 +[MakerNotes, Olympus, Camera] 1536 - Black Level 2: 63 63 63 63 +[MakerNotes, Olympus, Camera] 4112 - Noise Reduction 2: (none) +[MakerNotes, Olympus, Camera] 4113 - Distortion Correction 2: Off +[MakerNotes, Olympus, Camera] 4114 - Shading Compensation 2: On +[MakerNotes, Olympus, Camera] 0 - Focus Info Version: 0100 +[MakerNotes, Olympus, Camera] 528 - Scene Detect: 0 +[MakerNotes, Olympus, Camera] 768 - Zoom Step Count: 13 +[MakerNotes, Olympus, Camera] 769 - Focus Step Count: 321 +[MakerNotes, Olympus, Camera] 771 - Focus Step Infinity: 320 +[MakerNotes, Olympus, Camera] 772 - Focus Step Near: 375 +[MakerNotes, Olympus, Camera] 773 - Focus Distance: 385.8 m +[MakerNotes, Olympus, Camera] 776 - AF Point: Left (or n/a) +[MakerNotes, Olympus, Camera] 4609 - External Flash: Off +[MakerNotes, Olympus, Camera] 4612 - External Flash Bounce: Bounce or Off +[MakerNotes, Olympus, Camera] 4613 - External Flash Zoom: 0 +[MakerNotes, Olympus, Camera] 4616 - Internal Flash: Off +[MakerNotes, Olympus, Camera] 5632 - Image Stabilization: On, Mode 2 +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0300 +[Composite, Composite, Image] Exif-Aperture - Aperture: 16.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/250 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.523438 +[Composite, Composite, Image] Exif-LightValue - Light Value: 16.3 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.09375 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 5.7 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.005 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: inf (4.46 m - inf) +[Composite, Composite, Image] Exif-FOV - Field Of View: 18.4 deg (124.77 m) +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 19.5 mm (35 mm equivalent: 111.3 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 4.51 m diff --git a/ExifTool/t/Olympus_7.out b/ExifTool/t/Olympus_7.out new file mode 100644 index 0000000..55d794a --- /dev/null +++ b/ExifTool/t/Olympus_7.out @@ -0,0 +1,484 @@ + ExifToolVersion = 11.70 + FileName = Olympus_7_failed.jpg + Directory = t + FileSize = 7335 + FileModifyDate = 1570452448 + FileAccessDate = 1570452448 + FileInodeChangeDate = 1570452448 + FilePermissions = 33188 + FileType = JPEG + FileTypeExtension = JPG + MIMEType = image/jpeg +JPEG APP1 (7080 bytes): + ExifByteOrder = II + + [IFD0 directory with 12 entries] + | 0) ImageDescription = OLYMPUS DIGITAL CAMERA + | - Tag 0x010e (32 bytes, string[32]) + | 1) Make = OLYMPUS IMAGING CORP. + | - Tag 0x010f (24 bytes, string[24]) + | 2) Model = u760,S760 + | - Tag 0x0110 (17 bytes, string[17]) + | 3) Orientation = 1 + | - Tag 0x0112 (2 bytes, int16u[1]) + | 4) XResolution = 314 (314/1) + | - Tag 0x011a (8 bytes, rational64u[1]) + | 5) YResolution = 314 (314/1) + | - Tag 0x011b (8 bytes, rational64u[1]) + | 6) ResolutionUnit = 2 + | - Tag 0x0128 (2 bytes, int16u[1]) + | 7) Software = Version 1.0 + | - Tag 0x0131 (32 bytes, string[32]) + | 8) ModifyDate = 2007:02:16 15:09:13 + | - Tag 0x0132 (20 bytes, string[20]) + | 9) YCbCrPositioning = 2 + | - Tag 0x0213 (2 bytes, int16u[1]) + | 10) ExifOffset (SubDirectory) --> + | - Tag 0x8769 (4 bytes, int32u[1]) + | + [ExifIFD directory with 31 entries] + | | 0) ExposureTime = 0.004 (1/250) + | | - Tag 0x829a (8 bytes, rational64u[1]) + | | 1) FNumber = 16 (160/10) + | | - Tag 0x829d (8 bytes, rational64u[1]) + | | 2) ExposureProgram = 2 + | | - Tag 0x8822 (2 bytes, int16u[1]) + | | 3) ISO = 80 + | | - Tag 0x8827 (2 bytes, int16u[1]) + | | 4) ExifVersion = 0221 + | | - Tag 0x9000 (4 bytes, undef[4]) + | | 5) DateTimeOriginal = 2007:02:16 15:09:13 + | | - Tag 0x9003 (20 bytes, string[20]) + | | 6) CreateDate = 2007:02:16 15:09:13 + | | - Tag 0x9004 (20 bytes, string[20]) + | | 7) ComponentsConfiguration = 1 2 3 0 + | | - Tag 0x9101 (4 bytes, undef[4] read as int8u[4]) + | | 8) ExposureCompensation = -0.7 (-7/10) + | | - Tag 0x9204 (8 bytes, rational64s[1]) + | | 9) MaxApertureValue = 3.53 (353/100) + | | - Tag 0x9205 (8 bytes, rational64u[1]) + | | 10) MeteringMode = 5 + | | - Tag 0x9207 (2 bytes, int16u[1]) + | | 11) LightSource = 0 + | | - Tag 0x9208 (2 bytes, int16u[1]) + | | 12) Flash = 16 + | | - Tag 0x9209 (2 bytes, int16u[1]) + | | 13) FocalLength = 19.5 (1950/100) + | | - Tag 0x920a (8 bytes, rational64u[1]) + | | 14) MakerNoteOlympus2 (SubDirectory) --> + | | - Tag 0x927c (5502 bytes, undef[5502]) + | | + [MakerNoteOlympus2 directory with 14 entries] + | | | 0) SpecialMode = 0 0 0 + | | | - Tag 0x0200 (12 bytes, int32u[3]) + | | | 1) Quality = 3 + | | | - Tag 0x0201 (2 bytes, int16u[1]) + | | | 2) Macro = 1 + | | | - Tag 0x0202 (2 bytes, int16u[1]) + | | | 3) BWMode = 0 + | | | - Tag 0x0203 (2 bytes, int16u[1]) + | | | 4) DigitalZoom = 1 (100/100) + | | | - Tag 0x0204 (8 bytes, rational64u[1]) + | | | 5) FocalPlaneDiagonal = 7.58 (758/100) + | | | - Tag 0x0205 (8 bytes, rational64u[1]) + | | | 6) LensDistortionParams = 0 0 0 0 0 0 + | | | - Tag 0x0206 (12 bytes, int16s[6]) + | | | 7) CameraType = D4328 + | | | - Tag 0x0207 (6 bytes, string[6]) + | | | 8) CameraID = OLYMPUS DIGITAL CAMERA + | | | - Tag 0x0209 (32 bytes, undef[32] read as string[32]) + | | | 9) EquipmentIFD (SubDirectory) --> + | | | - Tag 0x2010 (4 bytes, ifd[1]) + | | | + [MakerNotes directory with 6 entries] + | | | | 0) EquipmentVersion = 0100 + | | | | - Tag 0x0000 (4 bytes, undef[4]) + | | | | 1) CameraType2 = D4328 + | | | | - Tag 0x0100 (6 bytes, string[6]) + | | | | 2) SerialNumber = C90500055 + | | | | - Tag 0x0101 (32 bytes, string[32]) + | | | | 3) InternalSerialNumber = 0171612001128001 + | | | | - Tag 0x0102 (32 bytes, string[32]) + | | | | 4) FocalPlaneDiagonal = 7.58 (758/100) + | | | | - Tag 0x0103 (8 bytes, rational64u[1]) + | | | | 5) BodyFirmwareVersion = 4097 + | | | | - Tag 0x0104 (4 bytes, int32u[1]) + | | | 10) CameraSettingsIFD (SubDirectory) --> + | | | - Tag 0x2020 (4 bytes, ifd[1]) + | | | + [MakerNotes directory with 36 entries] + | | | | 0) CameraSettingsVersion = 0100 + | | | | - Tag 0x0000 (4 bytes, undef[4]) + | | | | 1) PreviewImageValid = 0 + | | | | - Tag 0x0100 (4 bytes, int32u[1]) + | | | | 2) PreviewImageStart = 6037 + | | | | - Tag 0x0101 (4 bytes, int32u[1]) + | | | | 3) PreviewImageLength = 0 + | | | | - Tag 0x0102 (4 bytes, int32u[1]) + | | | | 4) ExposureMode = 2 + | | | | - Tag 0x0200 (2 bytes, int16u[1]) + | | | | 5) AELock = 0 + | | | | - Tag 0x0201 (2 bytes, int16u[1]) + | | | | 6) MeteringMode = 5 + | | | | - Tag 0x0202 (2 bytes, int16u[1]) + | | | | 7) MacroMode = 0 + | | | | - Tag 0x0300 (2 bytes, int16u[1]) + | | | | 8) FocusMode = 0 + | | | | - Tag 0x0301 (2 bytes, int16u[1]) + | | | | 9) FocusProcess = 0 + | | | | - Tag 0x0302 (2 bytes, int16u[1]) + | | | | 10) AFSearch = 0 + | | | | - Tag 0x0303 (2 bytes, int16u[1]) + | | | | 11) AFAreas = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0[snip] + | | | | - Tag 0x0304 (256 bytes, int32u[64]) + | | | | 12) FlashMode = 0 + | | | | - Tag 0x0400 (2 bytes, int16u[1]) + | | | | 13) FlashExposureComp = 0 (0/1) + | | | | - Tag 0x0401 (8 bytes, rational64s[1]) + | | | | 14) Olympus_CameraSettings_0x0402 = 0 + | | | | - Tag 0x0402 (2 bytes, int16u[1]) + | | | | 15) WhiteBalance2 = 0 + | | | | - Tag 0x0500 (2 bytes, int16u[1]) + | | | | 16) WhiteBalanceTemperature = 0 + | | | | - Tag 0x0501 (2 bytes, int16u[1]) + | | | | 17) WhiteBalanceBracket = 0 0 + | | | | - Tag 0x0502 (4 bytes, int16s[2]) + | | | | 18) CustomSaturation = 0 -2 2 + | | | | - Tag 0x0503 (6 bytes, int16s[3]) + | | | | 19) ModifiedSaturation = 0 + | | | | - Tag 0x0504 (2 bytes, int16u[1]) + | | | | 20) ContrastSetting = 0 -2 2 + | | | | - Tag 0x0505 (6 bytes, int16s[3]) + | | | | 21) SharpnessSetting = 0 -2 2 + | | | | - Tag 0x0506 (6 bytes, int16s[3]) + | | | | 22) ColorSpace = 0 + | | | | - Tag 0x0507 (2 bytes, int16u[1]) + | | | | 23) SceneMode = 0 + | | | | - Tag 0x0509 (2 bytes, int16u[1]) + | | | | 24) NoiseReduction = 0 + | | | | - Tag 0x050a (2 bytes, int16u[1]) + | | | | 25) DistortionCorrection = 0 + | | | | - Tag 0x050b (2 bytes, int16u[1]) + | | | | 26) ShadingCompensation = 0 + | | | | - Tag 0x050c (2 bytes, int16u[1]) + | | | | 27) CompressionFactor = 4 (4/1) + | | | | - Tag 0x050d (8 bytes, rational64u[1]) + | | | | 28) Olympus_CameraSettings_0x050e = 0 + | | | | - Tag 0x050e (2 bytes, int16u[1]) + | | | | 29) Gradation = 0 -1 1 + | | | | - Tag 0x050f (6 bytes, int16s[3]) + | | | | 30) DriveMode = 0 0 0 + | | | | - Tag 0x0600 (6 bytes, int16u[3]) + | | | | 31) PanoramaMode = 0 0 + | | | | - Tag 0x0601 (4 bytes, int16u[2]) + | | | | 32) ImageQuality2 = 3 + | | | | - Tag 0x0603 (2 bytes, int16u[1]) + | | | | 33) Olympus_CameraSettings_0x0800 = 1 + | | | | - Tag 0x0800 (2 bytes, int16u[1]) + | | | | 34) ManometerPressure = 0 + | | | | - Tag 0x0900 (2 bytes, int16u[1]) + | | | | 35) ManometerReading = 0 0 + | | | | - Tag 0x0901 (8 bytes, int32s[2]) + | | | 11) RawDevelopmentIFD (SubDirectory) --> + | | | - Tag 0x2030 (4 bytes, ifd[1]) + | | | + [MakerNotes directory with 2 entries] + | | | | 0) RawDevVersion = 0100 + | | | | - Tag 0x0000 (4 bytes, undef[4]) + | | | | 1) RawDevEditStatus = 0 + | | | | - Tag 0x010b (2 bytes, int16u[1]) + | | | 12) ImageProcessingIFD (SubDirectory) --> + | | | - Tag 0x2040 (4 bytes, ifd[1]) + | | | + [MakerNotes directory with 24 entries] + | | | | 0) ImageProcessingVersion = 0111 + | | | | - Tag 0x0000 (4 bytes, undef[4]) + | | | | 1) WB_RBLevels = 536 390 + | | | | - Tag 0x0100 (4 bytes, int16u[2]) + | | | | 2) WB_GLevel = 0 + | | | | - Tag 0x011f (2 bytes, int16u[1]) + | | | | 3) ColorMatrix = 348 -54 -42 -56 422 -114 -2 -132 388 + | | | | - Tag 0x0200 (18 bytes, int16u[9] read as int16s[9]) + | | | | 4) Enhancer = 38 + | | | | - Tag 0x0300 (2 bytes, int16u[1]) + | | | | 5) CoringFilter = 15 + | | | | - Tag 0x0310 (2 bytes, int16u[1]) + | | | | 6) BlackLevel2 = 63 63 63 63 + | | | | - Tag 0x0600 (8 bytes, int16u[4]) + | | | | 7) Olympus_ImageProcessing_0x0800 = -0.00417476473376155 -0.0002546840696595[snip] + | | | | - Tag 0x0800 (36 bytes, float[9]) + | | | | 8) Olympus_ImageProcessing_0x0801 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + | | | | - Tag 0x0801 (32 bytes, int16u[16]) + | | | | 9) Olympus_ImageProcessing_0x0802 = undef (0/0) + | | | | - Tag 0x0802 (8 bytes, rational64u[1]) + | | | | 10) Olympus_ImageProcessing_0x1000 = 0 0 + | | | | - Tag 0x1000 (4 bytes, int16u[2]) + | | | | 11) Olympus_ImageProcessing_0x1001 = 0 0 + | | | | - Tag 0x1001 (4 bytes, int16u[2]) + | | | | 12) Olympus_ImageProcessing_0x1002 = 0 0 + | | | | - Tag 0x1002 (4 bytes, int16u[2]) + | | | | 13) Olympus_ImageProcessing_0x1003 = 8 + | | | | - Tag 0x1003 (2 bytes, int16u[1]) + | | | | 14) Olympus_ImageProcessing_0x1004 = 1809 + | | | | - Tag 0x1004 (2 bytes, int16u[1]) + | | | | 15) Olympus_ImageProcessing_0x1005 = 7 7 + | | | | - Tag 0x1005 (4 bytes, int16u[2]) + | | | | 16) Olympus_ImageProcessing_0x1006 = 101 197 101 197 101 196 102 196 + | | | | - Tag 0x1006 (16 bytes, int16u[8]) + | | | | 17) Olympus_ImageProcessing_0x1007 = 909 3 0 0 0 + | | | | - Tag 0x1007 (10 bytes, int16u[5]) + | | | | 18) Olympus_ImageProcessing_0x1008 = 1 + | | | | - Tag 0x1008 (2 bytes, int16u[1]) + | | | | 19) Olympus_ImageProcessing_0x1009 = 0 0 0 + | | | | - Tag 0x1009 (6 bytes, int16u[3]) + | | | | 20) Olympus_ImageProcessing_0x100a = 0 0 439 + | | | | - Tag 0x100a (6 bytes, int16u[3]) + | | | | 21) NoiseReduction2 = 0 + | | | | - Tag 0x1010 (2 bytes, int16u[1]) + | | | | 22) DistortionCorrection2 = 0 + | | | | - Tag 0x1011 (2 bytes, int16u[1]) + | | | | 23) ShadingCompensation2 = 1 + | | | | - Tag 0x1012 (2 bytes, int16u[1]) + | | | 13) FocusInfoIFD (SubDirectory) --> + | | | - Tag 0x2050 (4 bytes, ifd[1]) + | | | + [MakerNotes directory with 54 entries] + | | | | 0) FocusInfoVersion = 0100 + | | | | - Tag 0x0000 (4 bytes, undef[4]) + | | | | 1) Olympus_FocusInfo_0x0200 = 2041 0 + | | | | - Tag 0x0200 (4 bytes, int16s[2]) + | | | | 2) Olympus_FocusInfo_0x0201 = 1109 0 + | | | | - Tag 0x0201 (4 bytes, int16s[2]) + | | | | 3) Olympus_FocusInfo_0x0202 = 2065 0 + | | | | - Tag 0x0202 (4 bytes, int16s[2]) + | | | | 4) Olympus_FocusInfo_0x0203 = 2826 + | | | | - Tag 0x0203 (2 bytes, int16s[1]) + | | | | 5) Olympus_FocusInfo_0x0204 = 3086 3072 2897 2928 2957 2987 3011 3034 3051 3[snip] + | | | | - Tag 0x0204 (166 bytes, int16s[83]) + | | | | 6) AutoFocus = 255 + | | | | - Tag 0x0209 (2 bytes, int16u[1]) + | | | | 7) Olympus_FocusInfo_0x020a = 0 + | | | | - Tag 0x020a (2 bytes, int16u[1]) + | | | | 8) Olympus_FocusInfo_0x020b = 0 + | | | | - Tag 0x020b (2 bytes, int16u[1]) + | | | | 9) Olympus_FocusInfo_0x020d = 0 + | | | | - Tag 0x020d (4 bytes, int32u[1]) + | | | | 10) Olympus_FocusInfo_0x020e = 0 0 0 0 0 + | | | | - Tag 0x020e (20 bytes, int32u[5]) + | | | | 11) Olympus_FocusInfo_0x020f = 0 0 0 0 0 + | | | | - Tag 0x020f (20 bytes, int32u[5]) + | | | | 12) SceneDetect = 0 + | | | | - Tag 0x0210 (2 bytes, int16u[1]) + | | | | 13) SceneArea = 0 0 0 0 0 0 0 0 + | | | | - Tag 0x0211 (32 bytes, int32u[8]) + | | | | 14) SceneDetectData = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0[snip] + | | | | - Tag 0x0212 (2880 bytes, int32u[720]) + | | | | 15) Olympus_FocusInfo_0x0213 = 0 + | | | | - Tag 0x0213 (2 bytes, int16u[1]) + | | | | 16) Olympus_FocusInfo_0x0214 = 0 + | | | | - Tag 0x0214 (2 bytes, int16u[1]) + | | | | 17) Olympus_FocusInfo_0x0216 = 3951 + | | | | - Tag 0x0216 (4 bytes, int32u[1]) + | | | | 18) Olympus_FocusInfo_0x0217 = 288 + | | | | - Tag 0x0217 (2 bytes, int16u[1]) + | | | | 19) Olympus_FocusInfo_0x0218 = 167 + | | | | - Tag 0x0218 (2 bytes, int16u[1]) + | | | | 20) ZoomStepCount = 13 + | | | | - Tag 0x0300 (2 bytes, int16u[1]) + | | | | 21) FocusStepCount = 321 + | | | | - Tag 0x0301 (2 bytes, int16u[1]) + | | | | 22) Olympus_FocusInfo_0x0302 = 0 + | | | | - Tag 0x0302 (2 bytes, int16u[1]) + | | | | 23) FocusStepInfinity = 320 + | | | | - Tag 0x0303 (2 bytes, int16u[1]) + | | | | 24) FocusStepNear = 375 + | | | | - Tag 0x0304 (2 bytes, int16u[1]) + | | | | 25) FocusDistance = 100000 1 + | | | | - Tag 0x0305 (8 bytes, rational64u[1] read as int32u[2]) + | | | | 26) Olympus_FocusInfo_0x0307 = 0 + | | | | - Tag 0x0307 (2 bytes, int16u[1]) + | | | | 27) AFPoint = 0 + | | | | - Tag 0x0308 (2 bytes, int16u[1]) + | | | | 28) Olympus_FocusInfo_0x030c = 0 0 + | | | | - Tag 0x030c (4 bytes, int16u[2]) + | | | | 29) Olympus_FocusInfo_0x030f = 400 + | | | | - Tag 0x030f (2 bytes, int16s[1]) + | | | | 30) Olympus_FocusInfo_0x0310 = 0 + | | | | - Tag 0x0310 (2 bytes, int16u[1]) + | | | | 31) Olympus_FocusInfo_0x0311 = 0 + | | | | - Tag 0x0311 (2 bytes, int16u[1]) + | | | | 32) Olympus_FocusInfo_0x0312 = 76 906 + | | | | - Tag 0x0312 (4 bytes, int16u[2]) + | | | | 33) Olympus_FocusInfo_0x0313 = 302 + | | | | - Tag 0x0313 (2 bytes, int16s[1]) + | | | | 34) Olympus_FocusInfo_0x0314 = 10 + | | | | - Tag 0x0314 (2 bytes, int16u[1]) + | | | | 35) Olympus_FocusInfo_0x0315 = 0 + | | | | - Tag 0x0315 (2 bytes, int16s[1]) + | | | | 36) Olympus_FocusInfo_0x0316 = 3086 0 0 0 0 0 0 0 0 0 + | | | | - Tag 0x0316 (20 bytes, int16s[10]) + | | | | 37) Olympus_FocusInfo_0x031c = 0 + | | | | - Tag 0x031c (2 bytes, int16s[1]) + | | | | 38) Olympus_FocusInfo_0x031d = 319 + | | | | - Tag 0x031d (2 bytes, int16s[1]) + | | | | 39) Olympus_FocusInfo_0x031e = 321 + | | | | - Tag 0x031e (2 bytes, int16s[1]) + | | | | 40) Olympus_FocusInfo_0x0321 = 321 + | | | | - Tag 0x0321 (2 bytes, int16s[1]) + | | | | 41) Olympus_FocusInfo_0x0322 = 0 + | | | | - Tag 0x0322 (2 bytes, int16s[1]) + | | | | 42) Olympus_FocusInfo_0x0323 = 0 + | | | | - Tag 0x0323 (2 bytes, int16s[1]) + | | | | 43) Olympus_FocusInfo_0x0324 = 366 + | | | | - Tag 0x0324 (2 bytes, int16s[1]) + | | | | 44) Olympus_FocusInfo_0x1200 = 0 + | | | | - Tag 0x1200 (2 bytes, int16u[1]) + | | | | 45) ExternalFlash = 0 0 + | | | | - Tag 0x1201 (4 bytes, int16u[2]) + | | | | 46) Olympus_FocusInfo_0x1202 = 0 + | | | | - Tag 0x1202 (2 bytes, int16u[1]) + | | | | 47) ExternalFlashGuideNumber = 0 (0/1) + | | | | - Tag 0x1203 (8 bytes, rational64s[1]) + | | | | 48) ExternalFlashBounce = 0 + | | | | - Tag 0x1204 (2 bytes, int16u[1]) + | | | | 49) ExternalFlashZoom = 0 (0/1) + | | | | - Tag 0x1205 (8 bytes, rational64u[1]) + | | | | 50) Olympus_FocusInfo_0x1206 = 0 + | | | | - Tag 0x1206 (2 bytes, int16u[1]) + | | | | 51) Olympus_FocusInfo_0x1207 = 0 + | | | | - Tag 0x1207 (2 bytes, int16u[1]) + | | | | 52) InternalFlash = 0 + | | | | - Tag 0x1208 (2 bytes, int16u[1]) + | | | | 53) ImageStabilization = .00d.x.K.......T......T...O...... + | | | | - Tag 0x1600 (53 bytes, undef[53]) + | | 15) UserComment = [snip] + | | - Tag 0x9286 (125 bytes, undef[125]) + | | 16) FlashpixVersion = 0100 + | | - Tag 0xa000 (4 bytes, undef[4]) + | | 17) ColorSpace = 1 + | | - Tag 0xa001 (2 bytes, int16u[1]) + | | 18) ExifImageWidth = 3072 + | | - Tag 0xa002 (4 bytes, int32u[1]) + | | 19) ExifImageHeight = 2304 + | | - Tag 0xa003 (4 bytes, int32u[1]) + | | 20) InteropOffset (SubDirectory) --> + | | - Tag 0xa005 (4 bytes, int32u[1]) + | | + [InteropIFD directory with 2 entries] + | | | 0) InteropIndex = R98 + | | | - Tag 0x0001 (4 bytes, string[4]) + | | | 1) InteropVersion = 0100 + | | | - Tag 0x0002 (4 bytes, undef[4]) + | | 21) FileSource = 3 + | | - Tag 0xa300 (1 bytes, undef[1]) + | | 22) CustomRendered = 0 + | | - Tag 0xa401 (2 bytes, int16u[1]) + | | 23) ExposureMode = 1 + | | - Tag 0xa402 (2 bytes, int16u[1]) + | | 24) WhiteBalance = 0 + | | - Tag 0xa403 (2 bytes, int16u[1]) + | | 25) DigitalZoomRatio = 1 (100/100) + | | - Tag 0xa404 (8 bytes, rational64u[1]) + | | 26) SceneCaptureType = 0 + | | - Tag 0xa406 (2 bytes, int16u[1]) + | | 27) GainControl = 0 + | | - Tag 0xa407 (2 bytes, int16u[1]) + | | 28) Contrast = 0 + | | - Tag 0xa408 (2 bytes, int16u[1]) + | | 29) Saturation = 0 + | | - Tag 0xa409 (2 bytes, int16u[1]) + | | 30) Sharpness = 0 + | | - Tag 0xa40a (2 bytes, int16u[1]) + | 11) PrintIM (SubDirectory) --> + | - Tag 0xc4a5 (528 bytes, undef[528]) + | + [PrintIM directory with 37 entries] + | | PrintIMVersion = 0300 + | | - Tag 'PrintIMVersion' (4 bytes) + | | 0) PrintIM_0x0001 = 1310740 + | | - Tag 0x0001 (4 bytes) + | | 1) PrintIM_0x0002 = 1 + | | - Tag 0x0002 (4 bytes) + | | 2) PrintIM_0x0003 = 240 + | | - Tag 0x0003 (4 bytes) + | | 3) PrintIM_0x0007 = 0 + | | - Tag 0x0007 (4 bytes) + | | 4) PrintIM_0x0008 = 0 + | | - Tag 0x0008 (4 bytes) + | | 5) PrintIM_0x0009 = 0 + | | - Tag 0x0009 (4 bytes) + | | 6) PrintIM_0x000a = 0 + | | - Tag 0x000a (4 bytes) + | | 7) PrintIM_0x000b = 312 + | | - Tag 0x000b (4 bytes) + | | 8) PrintIM_0x000c = 0 + | | - Tag 0x000c (4 bytes) + | | 9) PrintIM_0x000d = 0 + | | - Tag 0x000d (4 bytes) + | | 10) PrintIM_0x000e = 336 + | | - Tag 0x000e (4 bytes) + | | 11) PrintIM_0x0010 = 352 + | | - Tag 0x0010 (4 bytes) + | | 12) PrintIM_0x0020 = 436 + | | - Tag 0x0020 (4 bytes) + | | 13) PrintIM_0x0100 = 3 + | | - Tag 0x0100 (4 bytes) + | | 14) PrintIM_0x0101 = 255 + | | - Tag 0x0101 (4 bytes) + | | 15) PrintIM_0x0102 = 131 + | | - Tag 0x0102 (4 bytes) + | | 16) PrintIM_0x0103 = 128 + | | - Tag 0x0103 (4 bytes) + | | 17) PrintIM_0x0104 = 131 + | | - Tag 0x0104 (4 bytes) + | | 18) PrintIM_0x0105 = 131 + | | - Tag 0x0105 (4 bytes) + | | 19) PrintIM_0x0106 = 131 + | | - Tag 0x0106 (4 bytes) + | | 20) PrintIM_0x0107 = 8421504 + | | - Tag 0x0107 (4 bytes) + | | 21) PrintIM_0x0110 = 128 + | | - Tag 0x0110 (4 bytes) + | | 22) PrintIM_0x0200 = 0 + | | - Tag 0x0200 (4 bytes) + | | 23) PrintIM_0x0207 = 0 + | | - Tag 0x0207 (4 bytes) + | | 24) PrintIM_0x0208 = 0 + | | - Tag 0x0208 (4 bytes) + | | 25) PrintIM_0x0209 = 0 + | | - Tag 0x0209 (4 bytes) + | | 26) PrintIM_0x020a = 0 + | | - Tag 0x020a (4 bytes) + | | 27) PrintIM_0x020b = 504 + | | - Tag 0x020b (4 bytes) + | | 28) PrintIM_0x020d = 0 + | | - Tag 0x020d (4 bytes) + | | 29) PrintIM_0x0220 = 470 + | | - Tag 0x0220 (4 bytes) + | | 30) PrintIM_0x0300 = 3 + | | - Tag 0x0300 (4 bytes) + | | 31) PrintIM_0x0301 = 255 + | | - Tag 0x0301 (4 bytes) + | | 32) PrintIM_0x0302 = 131 + | | - Tag 0x0302 (4 bytes) + | | 33) PrintIM_0x0303 = 131 + | | - Tag 0x0303 (4 bytes) + | | 34) PrintIM_0x0306 = 131 + | | - Tag 0x0306 (4 bytes) + | | 35) PrintIM_0x0310 = 128 + | | - Tag 0x0310 (4 bytes) + | | 36) PrintIM_0x0400 = 0 + | | - Tag 0x0400 (4 bytes) + + [IFD1 directory with 6 entries] + | 0) Compression = 6 + | - Tag 0x0103 (2 bytes, int16u[1]) + | 1) XResolution = 72 (72/1) + | - Tag 0x011a (8 bytes, rational64u[1]) + | 2) YResolution = 72 (72/1) + | - Tag 0x011b (8 bytes, rational64u[1]) + | 3) ResolutionUnit = 2 + | - Tag 0x0128 (2 bytes, int16u[1]) + | 4) ThumbnailOffset = 7046 + | - Tag 0x0201 (4 bytes, int32u[1]) + | 5) ThumbnailLength = 28 + | - Tag 0x0202 (4 bytes, int32u[1]) +JPEG DQT (130 bytes): +JPEG SOF0 (15 bytes): + ImageWidth = 8 + ImageHeight = 8 + EncodingProcess = 0 + BitsPerSample = 8 + ColorComponents = 3 + YCbCrSubSampling = 2 2 +JPEG DHT (73 bytes): +JPEG SOS diff --git a/ExifTool/t/Olympus_8.out b/ExifTool/t/Olympus_8.out new file mode 100644 index 0000000..1159ce6 --- /dev/null +++ b/ExifTool/t/Olympus_8.out @@ -0,0 +1,15 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Olympus.dss +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 80 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2015:06:24 09:57:14-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: DSS +[File, File, Other] FileTypeExtension - File Type Extension: dss +[File, File, Other] MIMEType - MIME Type: audio/x-dss +[MakerNotes, Olympus, Audio] 12 - Model: DS2300 +[MakerNotes, Olympus, Time] 38 - Start Time: 2005:11:16 13:52:42 +[MakerNotes, Olympus, Time] 50 - End Time: 2005:11:16 13:52:53 +[MakerNotes, Olympus, Audio] 62 - Duration: 10.00 s diff --git a/ExifTool/t/OpenEXR.t b/ExifTool/t/OpenEXR.t new file mode 100644 index 0000000..e0876c3 --- /dev/null +++ b/ExifTool/t/OpenEXR.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/OpenEXR.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::OpenEXR; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'OpenEXR'; +my $testnum = 1; + +# test 2: Extract information from OpenEXR file +{ + my $exifTool = Image::ExifTool->new; + ++$testnum; + my $info = $exifTool->ImageInfo('t/images/OpenEXR.exr'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/OpenEXR_2.out b/ExifTool/t/OpenEXR_2.out new file mode 100644 index 0000000..5b59f5d --- /dev/null +++ b/ExifTool/t/OpenEXR_2.out @@ -0,0 +1,25 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.56 +[File, System, Other] FileName - File Name: OpenEXR.exr +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 395 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2011:12:10 14:34:35-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:12:01 13:54:43-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:18 10:49:22-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: EXR +[File, File, Other] FileTypeExtension - File Type Extension: exr +[File, File, Other] MIMEType - MIME Type: image/x-exr +[File, File, Image] ImageWidth - Image Width: 3 +[File, File, Image] ImageHeight - Image Height: 2 +[OpenEXR, OpenEXR, Image] _ver - EXR Version: 2 +[OpenEXR, OpenEXR, Image] _flags - Flags: (none) +[OpenEXR, OpenEXR, Image] channels - Channels: A half 1 1, B half 1 1, G half 1 1, R half 1 1 +[OpenEXR, OpenEXR, Image] compression - Compression: PIZ +[OpenEXR, OpenEXR, Image] dataWindow - Data Window: 0 0 2 1 +[OpenEXR, OpenEXR, Image] displayWindow - Display Window: 0 0 2 1 +[OpenEXR, OpenEXR, Image] lineOrder - Line Order: Increasing Y +[OpenEXR, OpenEXR, Image] pixelAspectRatio - Pixel Aspect Ratio: 1 +[OpenEXR, OpenEXR, Image] screenWindowCenter - Screen Window Center: 0 0 +[OpenEXR, OpenEXR, Image] screenWindowWidth - Screen Window Width: 1 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3x2 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000006 diff --git a/ExifTool/t/Opus.t b/ExifTool/t/Opus.t new file mode 100644 index 0000000..b0fb569 --- /dev/null +++ b/ExifTool/t/Opus.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Opus.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Opus; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Opus'; +my $testnum = 1; + +# test 2: Extract information from test file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Opus.opus'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Opus_2.out b/ExifTool/t/Opus_2.out new file mode 100644 index 0000000..8ff5fac --- /dev/null +++ b/ExifTool/t/Opus_2.out @@ -0,0 +1,26 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Opus.opus +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1128 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2016:07:14 08:20:19-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:16-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: OPUS +[File, File, Other] FileTypeExtension - File Type Extension: opus +[File, File, Other] MIMEType - MIME Type: audio/ogg +[Opus, Opus, Audio] 0 - Opus Version: 1 +[Opus, Opus, Audio] 1 - Audio Channels: 1 +[Opus, Opus, Audio] 4 - Sample Rate: 7872 +[Opus, Opus, Audio] 8 - Output Gain: 1 +[Vorbis, Vorbis, Audio] vendor - Vendor: libopus 1.1 +[Vorbis, Vorbis, Audio] ENCODER - Encoder: opusenc from opus-tools 0.1.9 +[FLAC, FLAC, Image] 0 - Picture Type: Front Cover +[FLAC, FLAC, Image] 1 - Picture MIME Type: image/png +[FLAC, FLAC, Image] 2 - Picture Description: cover pic +[FLAC, FLAC, Image] 3 - Picture Width: 16 +[FLAC, FLAC, Image] 4 - Picture Height: 16 +[FLAC, FLAC, Image] 5 - Picture Bits Per Pixel: 1 +[FLAC, FLAC, Image] 6 - Picture Indexed Colors: 0 +[FLAC, FLAC, Image] 7 - Picture Length: 85 +[FLAC, FLAC, Preview] 8 - Picture: (Binary data 85 bytes) diff --git a/ExifTool/t/PCX.t b/ExifTool/t/PCX.t new file mode 100644 index 0000000..eea6e0c --- /dev/null +++ b/ExifTool/t/PCX.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PCX.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PCX; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PCX'; +my $testnum = 1; + +# test 2: Extract information from PCX file +{ + my $exifTool = Image::ExifTool->new; + ++$testnum; + my $info = $exifTool->ImageInfo('t/images/PCX.pcx'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PCX_2.out b/ExifTool/t/PCX_2.out new file mode 100644 index 0000000..ae05590 --- /dev/null +++ b/ExifTool/t/PCX_2.out @@ -0,0 +1,26 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: PCX.pcx +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 160 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2018:12:12 09:01:43-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:14-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2018:12:12 13:16:32-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PCX +[File, File, Other] FileTypeExtension - File Type Extension: pcx +[File, File, Other] MIMEType - MIME Type: image/pcx +[File, File, Image] 0 - Manufacturer: ZSoft +[File, File, Image] 1 - Software: PC Paintbrush 3.0+ +[File, File, Image] 2 - Encoding: RLE +[File, File, Image] 3 - Bits Per Pixel: 8 +[File, File, Image] 4 - Left Margin: 0 +[File, File, Image] 6 - Top Margin: 0 +[File, File, Image] 8 - Image Width: 8 +[File, File, Image] 10 - Image Height: 8 +[File, File, Image] 12 - X Resolution: 72 +[File, File, Image] 14 - Y Resolution: 72 +[File, File, Image] 65 - Color Planes: 3 +[File, File, Image] 66 - Bytes Per Line: 8 +[File, File, Image] 68 - Color Mode: Color Palette +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/PDF.t b/ExifTool/t/PDF.t new file mode 100644 index 0000000..a55f90b --- /dev/null +++ b/ExifTool/t/PDF.t @@ -0,0 +1,303 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PDF.t". + +BEGIN { + $| = 1; print "1..26\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PDF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PDF'; +my $testnum = 1; + +#------------------------------------------------------------------------------ +# PDF decryption test +# Inputs: 0) Encrypt object reference, plus additional entries (see below), +# 1) Test number, 2) encrypt flag (false for decryption) +# Returns: nothing, but prints test result +# Additional encrypt hash entries used by this routine: +# _id - PDF file ID +# _ref - PDF object reference string +# _req - other module required for this test +# _ciphertext - encrypted data +# _plaintext - expected decryption result +# _password - password for decryption (if used) +sub CryptTest($$;$) +{ + my ($cryptInfo, $testNum, $encrypt) = @_; + my $skip = ''; + if (eval "require $$cryptInfo{_req}") { + my $exifTool = Image::ExifTool->new; + $exifTool->Options('Password', $$cryptInfo{_password}); + my $err = Image::ExifTool::PDF::DecryptInit($exifTool, $cryptInfo, $$cryptInfo{_id}); + unless ($err) { + my $data = $$cryptInfo{$encrypt ? '_plaintext' : '_ciphertext'}; + Image::ExifTool::PDF::Crypt(\$data, $$cryptInfo{_ref} || '1 0 R', $encrypt); + $err = $$cryptInfo{_error}; + if (not $err and $data ne $$cryptInfo{$encrypt ? '_ciphertext' : '_plaintext'}) { + $err = "Test $testnum (decryption) returned wrong value:\n " . unpack('H*',$data); + } + } + if ($err) { + warn "\n $err\n"; + notOK(); + } + } else { + $skip = " # skip Requires $$cryptInfo{_req}"; + } + print "ok $testnum$skip\n"; +} + +# test 2: Extract information from PDF.pdf +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/PDF.pdf'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Test Standard PDF decryption +{ + ++$testnum; + my %cryptInfo = ( + Filter => '/Standard', + P => -60, + V => 1, + R => 0, + O => '<2055c756c72e1ad702608e8196acad447ad32d17cff583235f6dd15fed7dab67>', + U => '<7150bd1da9d292af3627fca6a8dde1d696e25312041aed09059f9daee04353ae>', + _id => pack('H*','12116a1a124ae4cd8179e8978f6ac88b'), + _req => 'Digest::MD5', + _ref => '4 0 R', + _ciphertext => pack('N', 0x34a290d3), + _plaintext => pack('N', 0x5924d335), + ); + CryptTest(\%cryptInfo, $testNum); +} + +# tests 4-21: Test writing, deleting and reverting two different files +{ + # do a bunch of edits + my @edits = ([ # (on file containing both PDF Info and XMP) + [ # test 4: write PDF and XMP information + [ 'PDF:Creator' => 'me'], + [ 'XMP:Creator' => 'you' ], + [ 'AllDates' => '2:30', Shift => -1 ], + ],[ # test 5: delete all PDF + [ 'PDF:all' ], + ],[ # test 6: write some XMP + [ 'XMP:Author' => 'them' ], + ],[ # test 7: create new PDF + [ 'PDF:Keywords' => 'one' ], + [ 'PDF:Keywords' => 'two' ], + [ 'AppleKeywords' => 'three' ], + [ 'AppleKeywords' => 'four' ], + ],[ # test 8: delete all XMP + [ 'XMP:all' ], + ],[ # test 9: write some PDF + [ 'PDF:Keywords' => 'another one', AddValue => 1 ], + [ 'AppleKeywords' => 'three', DelValue => 1 ], + ],[ # test 10: create new XMP + [ 'XMP:Author' => 'us' ], + ],[ # test 11: write some PDF + [ 'PDF:Keywords' => 'two', DelValue => 1 ], + [ 'AppleKeywords' => 'five', AddValue => 1 ], + ],[ # test 12: delete re-added XMP + [ 'XMP:all' ], + ], + ],[ # (on file without PDF Info or XMP) + [ # test 14: create new XMP + [ 'XMP:Author' => 'him' ], + ],[ # test 15: create new PDF + [ 'PDF:Author' => 'her' ], + ],[ # test 16: delete XMP and PDF + [ 'XMP:all' ], + [ 'PDF:all' ], + ],[ # test 17: delete XMP and PDF again + [ 'XMP:all' ], + [ 'PDF:all' ], + ],[ # test 18: create new PDF + [ 'PDF:Author' => 'it' ], + ],[ # test 19: create new XMP + [ 'XMP:Author' => 'thing' ], + ],[ # test 20: delete all + [ 'all' ], + ], + ]); + my $testSet; + foreach $testSet (0,1) { + my ($edit, $testfile2, $lastOK); + my $testfile = 't/images/' . ($testSet ? 'PDF2.pdf' : 'PDF.pdf'); + my $testfile1 = $testfile; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(PrintConv => 0); + foreach $edit (@{$edits[$testSet]}) { + ++$testnum; + $exifTool->SetNewValue(); + $exifTool->SetNewValue(@$_) foreach @$edit; + $testfile2 = "t/${testname}_${testnum}_failed.pdf"; + unlink $testfile2; + $exifTool->WriteInfo($testfile1, $testfile2); + my $info = $exifTool->ImageInfo($testfile2, + qw{Filesize PDF:all XMP:Creator XMP:Author AllDates}); + my $ok = check($exifTool, $info, $testname, $testnum); + notOK() unless $ok; + print "ok $testnum\n"; + # erase source file if previous test was OK + unlink $testfile1 if $lastOK; + $lastOK = $ok; + $testfile1 = $testfile2; # use this file for the next test + } + # revert all edits and compare with original file + ++$testnum; + $exifTool->SetNewValue('PDF-update:all'); + $testfile2 = "t/${testname}_${testnum}_failed.pdf"; + unlink $testfile2; + $exifTool->WriteInfo($testfile1, $testfile2); + if (binaryCompare($testfile2, $testfile)) { + unlink $testfile2; + } else { + notOK(); + } + print "ok $testnum\n"; + unlink $testfile1 if $lastOK; + } +} + +# test 22: Delete all tags +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.pdf"; + unlink $testfile; + $exifTool->Options(IgnoreMinorErrors => 1); + $exifTool->SetNewValue(all => undef); + my $ok = writeInfo($exifTool, 't/images/PDF.pdf', $testfile); + $exifTool->Options(IgnoreMinorErrors => 0); + my $info = $exifTool->ImageInfo($testfile,'pdf:all','xmp:all',{Duplicates=>1,Unknown=>1}); + if ($ok and check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 23: Test AES decryption alone (tests 24-26 require Digest::MD5 or Digest::SHA) +{ + ++$testnum; + require Image::ExifTool::AES; + my $data = pack('H*','6fdc3ca684348bc8f31379aa46455d7b60c0989e027c1d82e746f136d6e95b7485735793ff64310e5b9e367dcc26f564'); + my $err = Image::ExifTool::AES::Crypt(\$data, '11223344556677889900112233445566'); + if ($err) { + warn "\n $err\n"; + notOK(); + } elsif ($data ne 'ExifTool AES Test') { + my $hex = unpack 'H*', $data; + warn "\n Incorrect result from AES decryption:\n"; + warn " $hex\n"; + notOK(); + } + print "ok $testnum\n"; +} + +# test 24-26: Test AESV2 and AESV3 decryption +{ + my @encrypt = ( + # AESV2 without password + { + Filter => '/Standard', + V => 4, + R => 4, + P => -1340, + Length => 128, + StmF => '/StdCF', + StrF => '/StdCF', + CF => { + StdCF => { + AuthEvent => '/DocOpen', + CFM => '/AESV2', + Length => 16, + }, + }, + EncryptMetadata => 'false', + O => '<181ee8e93a99fa1c2a534dd68a5ab07c54268cfe8fbf28c468316b6f732674c1>', + U => '<a3525aef4143f4419c78b109317f0e5200000000000000000000000000000000>', + _req => 'Digest::MD5', + _ref => '4 0 R', + _id => pack('H*','d0a736f05faf64c6b52dea82a2ad53e0'), + _plaintext => 'This was a test', + _ciphertext => pack('H*', 'a86b5e00d9c7e4455cf5d8cedf195c2060e1467ea6d698876a77e9a66cb7867c'), + }, + # AESV3 without password + { + Filter => '/Standard', + V => 5, + R => 5, + P => -1028, + Length => 256, + StmF => '/StdCF', + StrF => '/StdCF', + CF => { + StdCF => { + AuthEvent => '/DocOpen', + CFM => '/AESV3', + Length => 32, + }, + }, + Perms => '<014ee28fe2b91e2198a593b7c3b22f50>', + O => '<83e5edfcdecbe2ebe6d519dbafe80fd453028dda119eb76d0216e1344392320d60e1467ea6d698876a77e9a66cb7867c>', + U => '<e5e7ade8aebdc9413a0fd176efc4081bdbad3b16a67ece7a01fadb24010a003ea86b5e00d9c7e4455cf5d8cedf195c20>', + OE => '<a29f37f1f085b575d9016daad05ca466dd073ba5d067cc7ffa8ef7d1605e460e>', + UE => '<47ea891b25af77aaceccf8f2fdeff0c09e9d0f67275f059dbfabbb18fcbf848d>', + _req => 'Digest::SHA', + _ref => 'dummy', + _id => pack('H*','618cb5be1d82fceea9a501b62d408296'), + _plaintext => 'This was a test', + _ciphertext => pack('H*', 'e90756e8fd60fb7390c34d931e3e3d61898cd133e613e8cf86cd40f7b207a62d'), + }, + # AESV3 with a password + { + Filter => '/Standard', + V => 5, + R => 5, + P => -1028, + Length => 256, + StmF => '/StdCF', + StrF => '/StdCF', + CF => { + StdCF => { + AuthEvent => '/DocOpen', + CFM => '/AESV3', + Length => 32, + }, + }, + Perms => '<014ee28fe2b91e2198a593b7c3b22f50>', + O => '<31eefe924a298d8bf19eafc9be6abdfa65a97478f94e907dccff5ac000b83fa521167b86cf70bf77d4a054bc9a59573d>', + U => '<6525a788c2ebf27baa45f526bcdb9d2f96c3c67ae1c62324135cac0b823451ba9ad8edb68d167d2d8370d799c41d17d7>', + OE => '<ea58e3c731999cdc0f8a395c7391836c2b2db0b4ac86439b3fe5692ddc71671a>', + UE => '<f6818f43e176dfe8912f62717032169cf48854f540f7b7641be363ef50371f07>', + _req => 'Digest::SHA', + _ref => 'dummy', + _id => pack('H*','b5f9d17b07152a45bc0a939727c389ad'), + _plaintext => 'This was a test', + _password => 'ExifTool', + _ciphertext => pack('H*', '8bb3565d8c4b9df8cc350954d9f91a46aa47e40eeb5a0cff559acd5ec3e94244'), + }); + my $exifTool = Image::ExifTool->new; + my $cryptInfo; + foreach $cryptInfo (@encrypt) { + ++$testnum; + my $encrypt = 0; # (set to 1 to generate ciphertext strings) + CryptTest($cryptInfo, $testNum, $encrypt); + } +} + +done(); # end diff --git a/ExifTool/t/PDF_10.out b/ExifTool/t/PDF_10.out new file mode 100644 index 0000000..da2471f --- /dev/null +++ b/ExifTool/t/PDF_10.out @@ -0,0 +1,10 @@ +[File, System, Other] FileSize - File Size: 12163 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[PDF, PDF, Document] AAPL:Keywords - Apple Keywords: four +[PDF, PDF, Document] Keywords - Keywords: [one,two,another one] +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 14:30:45 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[XMP, XMP-pdf, Author] Author - Author: us diff --git a/ExifTool/t/PDF_11.out b/ExifTool/t/PDF_11.out new file mode 100644 index 0000000..1428428 --- /dev/null +++ b/ExifTool/t/PDF_11.out @@ -0,0 +1,10 @@ +[File, System, Other] FileSize - File Size: 12165 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[PDF, PDF, Document] AAPL:Keywords - Apple Keywords: [four,five] +[PDF, PDF, Document] Keywords - Keywords: [one,another one] +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 14:30:45 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[XMP, XMP-pdf, Author] Author - Author: us diff --git a/ExifTool/t/PDF_12.out b/ExifTool/t/PDF_12.out new file mode 100644 index 0000000..c36164d --- /dev/null +++ b/ExifTool/t/PDF_12.out @@ -0,0 +1,9 @@ +[File, System, Other] FileSize - File Size: 9256 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[PDF, PDF, Document] AAPL:Keywords - Apple Keywords: [four,five] +[PDF, PDF, Document] Keywords - Keywords: [one,another one] +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 14:30:45 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 diff --git a/ExifTool/t/PDF_14.out b/ExifTool/t/PDF_14.out new file mode 100644 index 0000000..7045662 --- /dev/null +++ b/ExifTool/t/PDF_14.out @@ -0,0 +1,5 @@ +[File, System, Other] FileSize - File Size: 5412 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[XMP, XMP-pdf, Author] Author - Author: him diff --git a/ExifTool/t/PDF_15.out b/ExifTool/t/PDF_15.out new file mode 100644 index 0000000..63b8613 --- /dev/null +++ b/ExifTool/t/PDF_15.out @@ -0,0 +1,6 @@ +[File, System, Other] FileSize - File Size: 5481 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[PDF, PDF, Author] Author - Author: her +[XMP, XMP-pdf, Author] Author - Author: him diff --git a/ExifTool/t/PDF_16.out b/ExifTool/t/PDF_16.out new file mode 100644 index 0000000..7e11953 --- /dev/null +++ b/ExifTool/t/PDF_16.out @@ -0,0 +1,4 @@ +[File, System, Other] FileSize - File Size: 2508 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 diff --git a/ExifTool/t/PDF_17.out b/ExifTool/t/PDF_17.out new file mode 100644 index 0000000..7e11953 --- /dev/null +++ b/ExifTool/t/PDF_17.out @@ -0,0 +1,4 @@ +[File, System, Other] FileSize - File Size: 2508 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 diff --git a/ExifTool/t/PDF_18.out b/ExifTool/t/PDF_18.out new file mode 100644 index 0000000..d69918f --- /dev/null +++ b/ExifTool/t/PDF_18.out @@ -0,0 +1,5 @@ +[File, System, Other] FileSize - File Size: 2576 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[PDF, PDF, Author] Author - Author: it diff --git a/ExifTool/t/PDF_19.out b/ExifTool/t/PDF_19.out new file mode 100644 index 0000000..5abed26 --- /dev/null +++ b/ExifTool/t/PDF_19.out @@ -0,0 +1,6 @@ +[File, System, Other] FileSize - File Size: 5482 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[PDF, PDF, Author] Author - Author: it +[XMP, XMP-pdf, Author] Author - Author: thing diff --git a/ExifTool/t/PDF_2.out b/ExifTool/t/PDF_2.out new file mode 100644 index 0000000..5926dae --- /dev/null +++ b/ExifTool/t/PDF_2.out @@ -0,0 +1,127 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: PDF.pdf +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 8.9 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: PDF +[File, File, Other] FileTypeExtension - File Type Extension: pdf +[File, File, Other] MIMEType - MIME Type: application/pdf +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: No +[PDF, PDF, Document] Creator - Creator: Adobe Photoshop 7.0 +[PDF, PDF, Time] CreationDate - Create Date: 2005:07:18 14:30:45-04:00 +[PDF, PDF, Time] ModDate - Modify Date: 2005:07:18 14:30:45-04:00 +[PDF, PDF, Document] Producer - Producer: Adobe Photoshop for Macintosh +[PDF, PDF, Document] Count - Page Count: 1 +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other] 105 - Headline: No headline +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My Position +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Author] 115 - Source: I'm the source +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Other] 25 - Keywords: ExifTool, Test, XMP +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: 9e02efbd9bdc1dc192279e30a0e27323 +[Photoshop, Photoshop, Image] 0 - X Resolution: 72 +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: 72 +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Image] 0 - Print Style: Centered +[Photoshop, Photoshop, Image] 2 - Print Position: 0 0 +[Photoshop, Photoshop, Image] 10 - Print Scale: 1 +[Photoshop, Photoshop, Image] 1037 - Global Angle: 30 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 30 +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image] 1054 - URL List: +[Photoshop, Photoshop, Other] 20 - Slices Group Name: a +[Photoshop, Photoshop, Other] 24 - Num Slices: 1 +[Photoshop, Photoshop, Image] 4 - Has Real Merged Data: Yes +[Photoshop, Photoshop, Image] 5 - Writer Name: Adobe Photoshop +[Photoshop, Photoshop, Image] 9 - Reader Name: Adobe Photoshop 7.0 +[EXIF, IFD0, Image] 270 - Image Description: A witty caption +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 14:30:45 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: Copyright 2004 Phil Harvey +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.230769231 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Image] 37389 - Noise: 6 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 8 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 8 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 842 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 0 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: XMP toolkit 2.8.2-33, framework 1.5 +[XMP, XMP-rdf, Document] about - About: uuid:b691db33-f92a-11d9-99a4-8c8e8269c120 +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Image] Category - Category: 1 +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image] Headline - Headline: No headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Author] Source - Source: I'm the source +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:cbcc2a62-f127-11d9-ac4d-e8be6f73552e +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: A witty caption +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.6 diff --git a/ExifTool/t/PDF_20.out b/ExifTool/t/PDF_20.out new file mode 100644 index 0000000..7e11953 --- /dev/null +++ b/ExifTool/t/PDF_20.out @@ -0,0 +1,4 @@ +[File, System, Other] FileSize - File Size: 2508 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 diff --git a/ExifTool/t/PDF_22.out b/ExifTool/t/PDF_22.out new file mode 100644 index 0000000..00270ce --- /dev/null +++ b/ExifTool/t/PDF_22.out @@ -0,0 +1,3 @@ +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: No +[PDF, PDF, Document] Count - Page Count: 1 diff --git a/ExifTool/t/PDF_4.out b/ExifTool/t/PDF_4.out new file mode 100644 index 0000000..cc8eb7a --- /dev/null +++ b/ExifTool/t/PDF_4.out @@ -0,0 +1,12 @@ +[File, System, Other] FileSize - File Size: 14618 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Creator - Creator: me +[PDF, PDF, Time] CreationDate - Create Date: 2005:07:18 12:00:45-04:00 +[PDF, PDF, Time] ModDate - Modify Date: 2005:07:18 12:00:45-04:00 +[PDF, PDF, Document] Producer - Producer: Adobe Photoshop for Macintosh +[PDF, PDF, Document] Count - Page Count: 1 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 14:30:45 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[XMP, XMP-dc, Author] creator - Creator: you diff --git a/ExifTool/t/PDF_5.out b/ExifTool/t/PDF_5.out new file mode 100644 index 0000000..e575295 --- /dev/null +++ b/ExifTool/t/PDF_5.out @@ -0,0 +1,8 @@ +[File, System, Other] FileSize - File Size: 14454 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 14:30:45 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[XMP, XMP-dc, Author] creator - Creator: you diff --git a/ExifTool/t/PDF_6.out b/ExifTool/t/PDF_6.out new file mode 100644 index 0000000..60dae46 --- /dev/null +++ b/ExifTool/t/PDF_6.out @@ -0,0 +1,9 @@ +[File, System, Other] FileSize - File Size: 14623 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 14:30:45 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[XMP, XMP-dc, Author] creator - Creator: you +[XMP, XMP-pdf, Author] Author - Author: them diff --git a/ExifTool/t/PDF_7.out b/ExifTool/t/PDF_7.out new file mode 100644 index 0000000..b7320b5 --- /dev/null +++ b/ExifTool/t/PDF_7.out @@ -0,0 +1,11 @@ +[File, System, Other] FileSize - File Size: 14711 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[PDF, PDF, Document] AAPL:Keywords - Apple Keywords: [three,four] +[PDF, PDF, Document] Keywords - Keywords: [one,two] +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 14:30:45 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[XMP, XMP-dc, Author] creator - Creator: you +[XMP, XMP-pdf, Author] Author - Author: them diff --git a/ExifTool/t/PDF_8.out b/ExifTool/t/PDF_8.out new file mode 100644 index 0000000..0470bdf --- /dev/null +++ b/ExifTool/t/PDF_8.out @@ -0,0 +1,9 @@ +[File, System, Other] FileSize - File Size: 9249 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[PDF, PDF, Document] AAPL:Keywords - Apple Keywords: [three,four] +[PDF, PDF, Document] Keywords - Keywords: [one,two] +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 14:30:45 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 diff --git a/ExifTool/t/PDF_9.out b/ExifTool/t/PDF_9.out new file mode 100644 index 0000000..f4ad000 --- /dev/null +++ b/ExifTool/t/PDF_9.out @@ -0,0 +1,9 @@ +[File, System, Other] FileSize - File Size: 9254 +[PDF, PDF, Document] Version - PDF Version: 1.3 +[PDF, PDF, Document] _linearized - Linearized: false +[PDF, PDF, Document] Count - Page Count: 1 +[PDF, PDF, Document] AAPL:Keywords - Apple Keywords: four +[PDF, PDF, Document] Keywords - Keywords: [one,two,another one] +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 14:30:45 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 diff --git a/ExifTool/t/PFM.t b/ExifTool/t/PFM.t new file mode 100644 index 0000000..6ae6e08 --- /dev/null +++ b/ExifTool/t/PFM.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PFM.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Other; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PFM'; +my $testnum = 1; + +# test 2: Extract information from PFM.pfm +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/PFM.pfm'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PFM_2.out b/ExifTool/t/PFM_2.out new file mode 100644 index 0000000..02abb99 --- /dev/null +++ b/ExifTool/t/PFM_2.out @@ -0,0 +1,17 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.29 +[File, System, Other] FileName - File Name: PFM.pfm +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 39 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2021:07:16 08:36:07-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2021:07:16 08:36:08-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:07:16 08:36:07-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: PFM +[File, File, Other] FileTypeExtension - File Type Extension: pfm +[File, File, Other] MIMEType - MIME Type: image/x-pfm +[File, File, Image] ColorSpace - Color Space: RGB +[File, File, Image] ImageWidth - Image Width: 512 +[File, File, Image] ImageHeight - Image Height: 768 +[File, File, Image] ByteOrder - Byte Order: Little-endian +[Composite, Composite, Image] Exif-ImageSize - Image Size: 512x768 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.393 diff --git a/ExifTool/t/PGF.t b/ExifTool/t/PGF.t new file mode 100644 index 0000000..3802a2f --- /dev/null +++ b/ExifTool/t/PGF.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PGF.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PGF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PGF'; +my $testnum = 1; + +# test 2: Extract information from PGF.pgf +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/PGF.pgf'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PGF_2.out b/ExifTool/t/PGF_2.out new file mode 100644 index 0000000..6e16654 --- /dev/null +++ b/ExifTool/t/PGF_2.out @@ -0,0 +1,35 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.29 +[ExifTool, ExifTool, ExifTool] Warning - Warning: [minor] Text/EXIF chunk(s) found after PGF IDAT (may be ignored by some readers) +[File, System, Other] FileName - File Name: PGF.pgf +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 286 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2011:01:26 07:32:42-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2021:07:08 07:09:24-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: PGF +[File, File, Other] FileTypeExtension - File Type Extension: pgf +[File, File, Other] MIMEType - MIME Type: image/pgf +[File, File, Image] 3 - PGF Version: 0x36 +[File, File, Image] 8 - Image Width: 8 +[File, File, Image] 12 - Image Height: 8 +[File, File, Image] 16 - Pyramid Levels: 0 +[File, File, Image] 17 - Quality: 0 +[File, File, Image] 18 - Bits Per Pixel: 24 +[File, File, Image] 19 - Color Components: 3 +[File, File, Image] 20 - Color Mode: RGB +[File, File, Image] 21 - Background Color: 0 0 0 +[PNG, PNG, Image] 0 - Image Width: 1 +[PNG, PNG, Image] 4 - Image Height: 1 +[PNG, PNG, Image] 8 - Bit Depth: 8 +[PNG, PNG, Image] 9 - Color Type: RGB +[PNG, PNG, Image] 10 - Compression: Deflate/Inflate +[PNG, PNG, Image] 11 - Filter: Adaptive +[PNG, PNG, Image] 12 - Interlace: Noninterlaced +[PNG, PNG, Image] sRGB - SRGB Rendering: Perceptual +[PNG, PNG-pHYs, Image] 0 - Pixels Per Unit X: 2835 +[PNG, PNG-pHYs, Image] 4 - Pixels Per Unit Y: 2835 +[PNG, PNG-pHYs, Image] 8 - Pixel Units: meters +[PNG, PNG, Image] Comment - Comment: ExifTool PGF Test +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/PICT.t b/ExifTool/t/PICT.t new file mode 100644 index 0000000..9e50d0b --- /dev/null +++ b/ExifTool/t/PICT.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PICT.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PICT; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PICT'; +my $testnum = 1; + +# test 2: Extract information from PICT.pict +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/PICT.pict'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PICT_2.out b/ExifTool/t/PICT_2.out new file mode 100644 index 0000000..c4ab929 --- /dev/null +++ b/ExifTool/t/PICT_2.out @@ -0,0 +1,17 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: PICT.pict +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 150 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2005:11:14 14:47:21-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:17-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PICT +[File, File, Other] FileTypeExtension - File Type Extension: pict +[File, File, Other] MIMEType - MIME Type: image/pict +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] XResolution - X Resolution: 72 +[File, File, Image] YResolution - Y Resolution: 72 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/PLIST.t b/ExifTool/t/PLIST.t new file mode 100644 index 0000000..86682e1 --- /dev/null +++ b/ExifTool/t/PLIST.t @@ -0,0 +1,31 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PLIST.t". + +BEGIN { + $| = 1; print "1..4\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PLIST; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PLIST'; +my $testnum = 1; + +# tests 2-4: Extract information from PLIST files +{ + my $file; + my $exifTool = Image::ExifTool->new; + foreach $file ('PLIST-xml.plist', 'PLIST-bin.plist', 'PLIST.aae') { + ++$testnum; + my $info = $exifTool->ImageInfo("t/images/$file"); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +done(); # end diff --git a/ExifTool/t/PLIST_2.out b/ExifTool/t/PLIST_2.out new file mode 100644 index 0000000..9b24006 --- /dev/null +++ b/ExifTool/t/PLIST_2.out @@ -0,0 +1,21 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: PLIST-xml.plist +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 795 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2013:02:22 08:21:12-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PLIST +[File, File, Other] FileTypeExtension - File Type Extension: plist +[File, File, Other] MIMEType - MIME Type: application/xml +[PLIST, XML, Document] TestArray - Test Array: one, two, three +[PLIST, XML, Document] TestBoolean - Test Boolean: True +[PLIST, XML, Document] TestData - Test Data: (Binary data 12 bytes) +[PLIST, XML, Time] TestDate - Test Date: 2013:02:22 12:49:10Z +[PLIST, XML, Document] TestDict/Author - Test Dict Author: Phil +[PLIST, XML, Time] TestDict/When - Test Dict When: 2000:01:02 08:04:05Z +[PLIST, XML, Document] TestInteger - Test Integer: 256 +[PLIST, XML, Document] TestReal - Test Real: 1.4 +[PLIST, XML, Document] TestString - Test String: ExifTool PLIST test +[PLIST, XML, Document] TestUnicode - Test Unicode: ExîfTöøl PLIST tést diff --git a/ExifTool/t/PLIST_3.out b/ExifTool/t/PLIST_3.out new file mode 100644 index 0000000..c2a0a93 --- /dev/null +++ b/ExifTool/t/PLIST_3.out @@ -0,0 +1,21 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: PLIST-bin.plist +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 351 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2013:02:22 08:20:16-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PLIST +[File, File, Other] FileTypeExtension - File Type Extension: plist +[File, File, Other] MIMEType - MIME Type: application/x-plist +[PLIST, PLIST, Document] TestReal - Test Real: 1.4 +[PLIST, PLIST, Time] TestDict/When - Test Dict When: 2000:01:02 03:04:05-05:00 +[PLIST, PLIST, Document] TestDict/Author - Test Dict Author: Phil +[PLIST, PLIST, Document] TestString - Test String: ExifTool PLIST test +[PLIST, PLIST, Document] TestArray - Test Array: one, two, three +[PLIST, PLIST, Document] TestInteger - Test Integer: 256 +[PLIST, PLIST, Document] TestUnicode - Test Unicode: ExîfTöøl PLIST tést +[PLIST, PLIST, Time] TestDate - Test Date: 2013:02:22 07:49:10-05:00 +[PLIST, PLIST, Document] TestData - Test Data: (Binary data 12 bytes) +[PLIST, PLIST, Document] TestBoolean - Test Boolean: False diff --git a/ExifTool/t/PLIST_4.out b/ExifTool/t/PLIST_4.out new file mode 100644 index 0000000..9e09871 --- /dev/null +++ b/ExifTool/t/PLIST_4.out @@ -0,0 +1,17 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: PLIST.aae +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 827 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2018:09:07 08:00:15-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: AAE +[File, File, Other] FileTypeExtension - File Type Extension: aae +[File, File, Other] MIMEType - MIME Type: application/vnd.apple.photos +[PLIST, XML, Document] adjustmentBaseVersion - Adjustment Base Version: 0 +[PLIST, XML, Document] adjustmentData - Adjustment Data: (Binary data 208 bytes) +[PLIST, XML, Document] adjustmentEditorBundleID - Adjustment Editor Bundle ID: +[PLIST, XML, Document] adjustmentFormatIdentifier - Adjustment Format Identifier: com.apple.video.slomo +[PLIST, XML, Document] adjustmentFormatVersion - Adjustment Format Version: 1.1 +[PLIST, XML, Document] adjustmentRenderTypes - Adjustment Render Types: 0 diff --git a/ExifTool/t/PLUS.t b/ExifTool/t/PLUS.t new file mode 100644 index 0000000..9e56731 --- /dev/null +++ b/ExifTool/t/PLUS.t @@ -0,0 +1,45 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PLUS.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PLUS; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PLUS'; +my $testnum = 1; + +# test 2: Extract information from PLUS.xmp +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/PLUS.xmp', 'xmp:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Copy PLUS information to a new file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/PLUS.xmp','all:all'); + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + my $ok = writeInfo($exifTool,undef,$testfile); + my $info = $exifTool->ImageInfo($testfile, 'xmp:all'); + if (check($exifTool, $info, $testname, $testnum, 2) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PLUS_2.out b/ExifTool/t/PLUS_2.out new file mode 100644 index 0000000..71ab96e --- /dev/null +++ b/ExifTool/t/PLUS_2.out @@ -0,0 +1,31 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 10.15 +[XMP, XMP-iptcExt, Image] DigitalSourceType - Digital Source Type: https://exiftool.org +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] format - Format: image/tiff +[XMP, XMP-dc, Author] rights - Rights: Copyright © 2016 Phil Harvey +[XMP, XMP-photoshop, Image] ColorMode - Color Mode: RGB +[XMP, XMP-photoshop, Image] ICCProfile - ICC Profile Name: Adobe RGB (1998) +[XMP, XMP-plus, Author] CopyrightOwnerCopyrightOwnerName - Copyright Owner Name: Phil Harvey +[XMP, XMP-plus, Author] CopyrightStatus - Copyright Status: Protected +[XMP, XMP-plus, Author] CreditLineRequired - Credit Line Required: Credit Adjacent To Image +[XMP, XMP-plus, Author] ImageAlterationConstraints - Image Alteration Constraints: No Cropping, No Flipping, No Retouching, No Colorization, No De-Colorization, No Merging +[XMP, XMP-plus, Author] ImageCreatorImageCreatorName - Image Creator Name: Phil Harvey +[XMP, XMP-plus, Author] ImageDuplicationConstraints - Image Duplication Constraints: No Duplication +[XMP, XMP-plus, Author] ImageFileConstraints - Image File Constraints: Maintain File Name, Maintain ID in File Name, Maintain Metadata, Maintain File Type +[XMP, XMP-plus, Author] ImageFileFormatAsDelivered - Image File Format As Delivered: JPEG Interchange Formats (JPG, JIF, JFIF) +[XMP, XMP-plus, Author] ImageFileSizeAsDelivered - Image File Size As Delivered: Up to 50 MB +[XMP, XMP-plus, Author] ImageSupplierImageSupplierName - Image Supplier Name: Phil Harvey +[XMP, XMP-plus, Author] LicensorLicensorName - Licensor Name: Phil Harvey +[XMP, XMP-plus, Author] MediaSummaryCode - Media Summary Code: PLUS V0121 (LDF Version 1.21) U004 (4 Media Usages:); 1IBB (28 Usage Items:) 1UNA (Usage Number A) 2EMA (Advertising|Email|All Email Types|Internet Email) 3PTZ (Multiple Placements on Any Pages) 4SBG (Any Size Image|Up To Full Screen Ad) 5VUP (Single Version) 6QUL (Any Quantity) 7DWM (In Perpetuity) 8RAU (Oceania|Australia) 8IAD (Advertising and Marketing) 8IAG (Agriculture, Farming and Horticulture) 8IAR (Architecture and Engineering) 8IAE (Arts and Entertainment) 8IBR (Broadcast Media) 8IEC (Ecology, Environmental and Conservation) 8IEN (Energy, Utilities and Fuel) 8IEV (Events and Conventions) 8IFO (Forestry and Wood Products) 8IGL (Gardening and Landscaping) 8IGR (Graphic Design) 8IHH (Hotels and Hospitality) 8IIM (Industry and Manufacturing) 8INP (Not For Profit, Social, Charitable) 8IPR (Public Relations) 8IPM (Publishing Media) 8ISM (Retail Sales and Marketing) 8ITR (Travel and Tourism) 8LEN (English) 9EXC (All Exclusive); 1IBB (28 Usage Items:) 1UNB (Usage Number B) 2BFT (Personal Use|Website|Web Page, All Types|All Electronic Distribution Formats) 3PTZ (Multiple Placements on Any Pages) 4SKG (Any Size Image|Any Size Screen) 5VUP (Single Version) 6QUL (Any Quantity) 7DWM (In Perpetuity) 8RAU (Oceania|Australia) 8IAD (Advertising and Marketing) 8IAG (Agriculture, Farming and Horticulture) 8IAR (Architecture and Engineering) 8IAE (Arts and Entertainment) 8IBR (Broadcast Media) 8IEC (Ecology, Environmental and Conservation) 8IEN (Energy, Utilities and Fuel) 8IEV (Events and Conventions) 8IFO (Forestry and Wood Products) 8IGL (Gardening and Landscaping) 8IGR (Graphic Design) 8IHH (Hotels and Hospitality) 8IIM (Industry and Manufacturing) 8INP (Not For Profit, Social, Charitable) 8IPO (Personal Use Only) 8IPR (Public Relations) 8IPM (Publishing Media) 8ITR (Travel and Tourism) 8LEN (English) 9EXC (All Exclusive); 1IBA (27 Usage Items:) 1UNC (Usage Number C) 2BOS (Advertising|Art|Art Display, All Art Types|Electronic Display) 3PSD (Multiple Placements on Screen) 4SDL (Up To Full Screen Image|Any Size Screen) 5VUP (Single Version) 6QCX (One|Display) 7DWM (In Perpetuity) 8RAU (Oceania|Australia) 8IAD (Advertising and Marketing) 8IAG (Agriculture, Farming and Horticulture) 8IAR (Architecture and Engineering) 8IAE (Arts and Entertainment) 8IBR (Broadcast Media) 8IEC (Ecology, Environmental and Conservation) 8IEN (Energy, Utilities and Fuel) 8IEV (Events and Conventions) 8IFO (Forestry and Wood Products) 8IGL (Gardening and Landscaping) 8IGR (Graphic Design) 8IHH (Hotels and Hospitality) 8IIM (Industry and Manufacturing) 8INP (Not For Profit, Social, Charitable) 8IPR (Public Relations) 8IPM (Publishing Media) 8ITR (Travel and Tourism) 8LEN (English) 9EXC (All Exclusive); 1IBA (27 Usage Items:) 1UND (Usage Number D) 2FET (Advertising|Marketing Materials|Promotional E-card|Internet Email) 3PRV (Multiple Placements on Both Sides) 4SLA (Any Size Image|Any Size Pages) 5VUP (Single Version) 6QCH (One|Copy) 7DWM (In Perpetuity) 8RAU (Oceania|Australia) 8IAD (Advertising and Marketing) 8IAG (Agriculture, Farming and Horticulture) 8IAR (Architecture and Engineering) 8IAE (Arts and Entertainment) 8IBR (Broadcast Media) 8IEC (Ecology, Environmental and Conservation) 8IEN (Energy, Utilities and Fuel) 8IEV (Events and Conventions) 8IFO (Forestry and Wood Products) 8IGL (Gardening and Landscaping) 8IGR (Graphic Design) 8IHH (Hotels and Hospitality) 8IIM (Industry and Manufacturing) 8INP (Not For Profit, Social, Charitable) 8IPR (Public Relations) 8IPM (Publishing Media) 8ITR (Travel and Tourism) 8LEN (English) 9EXC (All Exclusive) +[XMP, XMP-plus, Author] Reuse - Reuse: Not Applicable +[XMP, XMP-plus, Author] Version - PLUS Version: 1.2.0 +[XMP, XMP-tiff, Image] BitsPerSample - Bits Per Sample: 8, 8, 8 +[XMP, XMP-tiff, Image] ImageLength - Image Height: 1 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 1 +[XMP, XMP-tiff, Image] PhotometricInterpretation - Photometric Interpretation: RGB +[XMP, XMP-tiff, Image] SamplesPerPixel - Samples Per Pixel: 3 +[XMP, XMP-xmp, Image] CreatorTool - Creator Tool: Adobe Photoshop CS5 Windows +[XMP, XMP-xmp, Time] MetadataDate - Metadata Date: 2016:05:18 12:54:01-05:00 +[XMP, XMP-xmp, Image] Rating - Rating: 0 +[XMP, XMP-xmpRights, Author] Marked - Marked: True +[XMP, XMP-xmpRights, Author] UsageTerms - Usage Terms: Phil Harvey diff --git a/ExifTool/t/PNG.t b/ExifTool/t/PNG.t new file mode 100644 index 0000000..582fcad --- /dev/null +++ b/ExifTool/t/PNG.t @@ -0,0 +1,141 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PNG.t". + +BEGIN { + $| = 1; print "1..7\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PNG; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PNG'; +my $testnum = 1; + +# test 2: Extract information from PNG.png +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/PNG.png'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write a bunch of new information to a PNG in memory +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/IPTC.jpg'); + $exifTool->SetNewValuesFromFile('t/images/XMP.jpg'); + $exifTool->SetNewValue('PNG:Comment'); # and delete a tag + $exifTool->SetNewValue('PixelsPerUnitX', 1234); + my $image; + my $rtnVal = $exifTool->WriteInfo('t/images/PNG.png', \$image); + # must ignore FileSize because size is variable (depends on Zlib availability) + my $info = $exifTool->ImageInfo(\$image, '-filesize'); + my $testfile = "t/${testname}_${testnum}_failed.png"; + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; # erase results of any bad test + } else { + # save the bad image + open(TESTFILE,">$testfile"); + binmode(TESTFILE); + print TESTFILE $image; + close(TESTFILE); + notOK(); + } + print "ok $testnum\n"; +} + +# test 4: Test group delete, alternate languages and special characters +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Charset => 'Latin'); + $exifTool->SetNewValue('PNG:*'); + $exifTool->SetNewValue('XMP:*'); + $exifTool->SetNewValue('PNG:Comment-fr', "Commentaire fran\xe7aise"); + $exifTool->SetNewValue('PNG:Copyright', "\xa9 2010 Phil Harvey"); + $exifTool->SetNewValue('XMP:Description-bar' => "A Br\xfcn is a Gst\xf6"); + my $testfile = "t/${testname}_${testnum}_failed.png"; + unlink $testfile; + my $rtnVal = $exifTool->WriteInfo('t/images/PNG.png', $testfile); + $exifTool->Options(Charset => 'UTF8'); + my $info = $exifTool->ImageInfo($testfile, 'PNG:*', 'XMP:*'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; # erase results of any bad test + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 5: Try moving XMP from after IDAT to before +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $image; + # delete all XMP then copy back again (should move to before IDAT) + $exifTool->SetNewValue(); + my $txtfile = "t/${testname}_${testnum}.failed"; + open PNG_TEST_5, ">$txtfile" or warn "Error opening $txtfile\n"; + $exifTool->Options(Verbose => 2); + $exifTool->Options(TextOut => \*PNG_TEST_5); + $exifTool->SetNewValue('xmp:all'); + $exifTool->SetNewValuesFromFile('t/images/PNG.png', 'all:all<xmp:all'); + my $rtnVal = $exifTool->WriteInfo('t/images/PNG.png', \$image); + close PNG_TEST_5; + if (testCompare('t/PNG_5.out', $txtfile, $testnum)) { + unlink $txtfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 6: Write EXIF +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('EXIF:Artist' => 'me'); + my $testfile = "t/${testname}_${testnum}_failed.png"; + unlink $testfile; + my $rtnVal = $exifTool->WriteInfo('t/images/PNG.png', $testfile); + $exifTool->Options(Charset => 'UTF8'); + my $info = $exifTool->ImageInfo($testfile, 'EXIF:*'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 7: Write ICC_Profile with a name +{ + ++$testnum; + my $skip = ''; + if (eval 'require Compress::Zlib') { + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/ICC_Profile.icc', 'ICC_Profile'); + $exifTool->SetNewValue('PNG:ProfileName' => 'Adobe RGB (1998)'); + my $testfile = "t/${testname}_${testnum}_failed.png"; + unlink $testfile; + my $rtnVal = $exifTool->WriteInfo('t/images/PNG.png', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'ProfileName', 'ProfileCMMType'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + } else { + $skip = ' # skip Requires Compress::Zlib'; + } + print "ok $testnum$skip\n"; +} + +done(); # end diff --git a/ExifTool/t/PNG_2.out b/ExifTool/t/PNG_2.out new file mode 100644 index 0000000..ba04cf2 --- /dev/null +++ b/ExifTool/t/PNG_2.out @@ -0,0 +1,25 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.29 +[ExifTool, ExifTool, ExifTool] Warning - Warning: [minor] Text/EXIF chunk(s) found after PNG IDAT (may be ignored by some readers) +[File, System, Other] FileName - File Name: PNG.png +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 572 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2014:07:23 09:04:15-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2021:07:08 07:19:03-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: PNG +[File, File, Other] FileTypeExtension - File Type Extension: png +[File, File, Other] MIMEType - MIME Type: image/png +[PNG, PNG, Image] 0 - Image Width: 16 +[PNG, PNG, Image] 4 - Image Height: 16 +[PNG, PNG, Image] 8 - Bit Depth: 1 +[PNG, PNG, Image] 9 - Color Type: Grayscale +[PNG, PNG, Image] 10 - Compression: Deflate/Inflate +[PNG, PNG, Image] 11 - Filter: Adaptive +[PNG, PNG, Image] 12 - Interlace: Noninterlaced +[PNG, PNG, Image] bKGD - Background Color: 0 +[PNG, PNG, Image] Comment - Comment: test comment +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 6.51 +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[Composite, Composite, Image] Exif-ImageSize - Image Size: 16x16 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000256 diff --git a/ExifTool/t/PNG_3.out b/ExifTool/t/PNG_3.out new file mode 100644 index 0000000..709eb70 --- /dev/null +++ b/ExifTool/t/PNG_3.out @@ -0,0 +1,123 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, File, Other] FileType - File Type: PNG +[File, File, Other] FileTypeExtension - File Type Extension: png +[File, File, Other] MIMEType - MIME Type: image/png +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[PNG, PNG, Image] 0 - Image Width: 16 +[PNG, PNG, Image] 4 - Image Height: 16 +[PNG, PNG, Image] 8 - Bit Depth: 1 +[PNG, PNG, Image] 9 - Color Type: Grayscale +[PNG, PNG, Image] 10 - Compression: Deflate/Inflate +[PNG, PNG, Image] 11 - Filter: Adaptive +[PNG, PNG, Image] 12 - Interlace: Noninterlaced +[PNG, PNG, Image] bKGD - Background Color: 0 +[PNG, PNG, Author] Artist - Artist: Phil Harvey +[PNG, PNG, Author] Copyright - Copyright: Copyright 2004 Phil Harvey +[PNG, PNG, Image] Description - Description: UTF-16 (big-endian) encoded XMP +[PNG, PNG, Camera] Make - Make: FUJIFILM +[PNG, PNG, Camera] Model - Model: FinePix2400Zoom +[PNG, PNG, Image] Software - Software: Adobe Photoshop 7.0 +[PNG, PNG, Image] Source - Source: I'm the source +[PNG, PNG, Image] Title - Title: Test IPTC picture +[PNG, PNG, Image] URL - URL: https://exiftool.org/ +[PNG, PNG, Time] create-date - Create Date: 2001:05:19 18:36:41 +[PNG, PNG, Time] tIME - Modify Date: 2004:02:26 09:36:46 +[PNG, PNG-pHYs, Image] 0 - Pixels Per Unit X: 1234 +[PNG, PNG-pHYs, Image] 4 - Pixels Per Unit Y: 2834 +[PNG, PNG-pHYs, Image] 8 - Pixel Units: meters +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.03 +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-exif, Image] ComponentsConfiguration - Components Configuration: Y, Cb, Cr, - +[XMP, XMP-exif, Image] CompressedBitsPerPixel - Compressed Bits Per Pixel: 1.6 +[XMP, XMP-exif, Camera] FlashFired - Flash Fired: True +[XMP, XMP-exif, Camera] FlashFunction - Flash Function: False +[XMP, XMP-exif, Camera] FlashMode - Flash Mode: Unknown +[XMP, XMP-exif, Camera] FlashRedEyeMode - Flash Red Eye Mode: False +[XMP, XMP-exif, Camera] FlashReturn - Flash Return: No return detection +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference? +[XMP, XMP-tiff, Image] BitsPerSample - Bits Per Sample: 8 +[XMP, XMP-tiff, Image] Compression - Compression: JPEG (old-style) +[XMP, XMP-tiff, Image] ImageLength - Image Height: 8 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 8 +[XMP, XMP-tiff, Image] YCbCrPositioning - Y Cb Cr Positioning: Co-sited +[XMP, XMP-tiff, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Other] 25 - Keywords: ExifTool, Test, IPTC +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My Position +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 105 - Headline: No headline +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Author] 115 - Source: I'm the source +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image] 1037 - Global Angle: 30 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 30 +[EXIF, IFD0, Image] 270 - Image Description: A witty caption +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2004:02:26 09:36:46 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, IFD0, Author] 33432 - Copyright: Copyright 2004 Phil Harvey +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 100 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 80 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Camera] XMP-Flash - Flash: Fired +[Composite, Composite, Image] Exif-ImageSize - Image Size: 16x16 +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.6 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000256 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.0 mm diff --git a/ExifTool/t/PNG_4.out b/ExifTool/t/PNG_4.out new file mode 100644 index 0000000..859df5d --- /dev/null +++ b/ExifTool/t/PNG_4.out @@ -0,0 +1,12 @@ +[PNG, PNG, Image] 0 - Image Width: 16 +[PNG, PNG, Image] 4 - Image Height: 16 +[PNG, PNG, Image] 8 - Bit Depth: 1 +[PNG, PNG, Image] 9 - Color Type: Grayscale +[PNG, PNG, Image] 10 - Compression: Deflate/Inflate +[PNG, PNG, Image] 11 - Filter: Adaptive +[PNG, PNG, Image] 12 - Interlace: Noninterlaced +[PNG, PNG, Image] bKGD - Background Color: 0 +[PNG, PNG, Image] Comment-fr - Comment (fr): Commentaire française +[PNG, PNG, Author] Copyright - Copyright: © 2010 Phil Harvey +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 8.44 +[XMP, XMP-dc, Image] description-bar - Description (bar): A Brün is a Gstö diff --git a/ExifTool/t/PNG_5.out b/ExifTool/t/PNG_5.out new file mode 100644 index 0000000..69bd342 --- /dev/null +++ b/ExifTool/t/PNG_5.out @@ -0,0 +1,24 @@ + Deleting tags in: XMP XMP-* +Sorry, XMP-x:XMPToolkit is unsafe for writing + Writing new tags after deleting groups: XMP XMP-* +Writing XMP-dc:Creator +Rewriting t/images/PNG.png... + Editing tags in: PNG XMP + Creating tags in: PNG XMP + FileType = PNG + FileTypeExtension = PNG + MIMEType = image/png +PNG IHDR (13 bytes): +PNG bKGD (2 bytes): + Moving tEXt from after IDAT (20 bytes) +PNG tEXt (20 bytes): + Moving iTXt from after IDAT (443 bytes) +PNG iTXt (443 bytes): + Deleting XMP + Rewriting XMP + + XMP-dc:Creator = 'Phil Harvey' +PNG IDAT (1 chunk, total 14 bytes) + Warning = [minor] Text/EXIF chunk(s) found after PNG IDAT (fixed) + Deleting tEXt that was moved (20 bytes) + Deleting iTXt that was moved (443 bytes) +PNG IEND (end of image) diff --git a/ExifTool/t/PNG_6.out b/ExifTool/t/PNG_6.out new file mode 100644 index 0000000..48c53de --- /dev/null +++ b/ExifTool/t/PNG_6.out @@ -0,0 +1,5 @@ +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: me +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered diff --git a/ExifTool/t/PNG_7.out b/ExifTool/t/PNG_7.out new file mode 100644 index 0000000..0e8bba3 --- /dev/null +++ b/ExifTool/t/PNG_7.out @@ -0,0 +1,2 @@ +[PNG, PNG, Image] iCCP-name - Profile Name: Adobe RGB (1998) +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Nikon Corporation diff --git a/ExifTool/t/PPM.t b/ExifTool/t/PPM.t new file mode 100644 index 0000000..14ca24f --- /dev/null +++ b/ExifTool/t/PPM.t @@ -0,0 +1,41 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PPM.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PPM; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PPM'; +my $testnum = 1; + +# test 2: Extract information from PPM.bmp +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/PPM.ppm'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write new comments to PPM in memory +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('Comment', 'A new comment'); + my $image; + $exifTool->WriteInfo('t/images/PPM.ppm', \$image); + $exifTool->Options(Unknown => 1, Binary => 0, ListJoin => ', '); + my $info = $exifTool->ImageInfo(\$image); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PPM_2.out b/ExifTool/t/PPM_2.out new file mode 100644 index 0000000..988d42c --- /dev/null +++ b/ExifTool/t/PPM_2.out @@ -0,0 +1,17 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: PPM.ppm +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 223 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2005:11:14 14:47:21-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:18-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PPM +[File, File, Other] FileTypeExtension - File Type Extension: ppm +[File, File, Other] MIMEType - MIME Type: image/x-portable-pixmap +[File, File, Image] Comment - Comment: ExifTool PPM test +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] MaxVal - Max Val: 255 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/PPM_3.out b/ExifTool/t/PPM_3.out new file mode 100644 index 0000000..34b1a84 --- /dev/null +++ b/ExifTool/t/PPM_3.out @@ -0,0 +1,11 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileSize - File Size: 219 bytes +[File, File, Other] FileType - File Type: PPM +[File, File, Other] FileTypeExtension - File Type Extension: ppm +[File, File, Other] MIMEType - MIME Type: image/x-portable-pixmap +[File, File, Image] Comment - Comment: A new comment +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] MaxVal - Max Val: 255 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/PSP.t b/ExifTool/t/PSP.t new file mode 100644 index 0000000..6a2b2c9 --- /dev/null +++ b/ExifTool/t/PSP.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PSP.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PSP; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PSP'; +my $testnum = 1; + +# test 2: Extract information from PSP file +{ + my $exifTool = Image::ExifTool->new; + ++$testnum; + my $info = $exifTool->ImageInfo('t/images/PSP.psp'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PSP_2.out b/ExifTool/t/PSP_2.out new file mode 100644 index 0000000..cb9a3c9 --- /dev/null +++ b/ExifTool/t/PSP_2.out @@ -0,0 +1,34 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: PSP.psp +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1703 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:01:28 11:15:22-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:19-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PSP +[File, File, Other] FileTypeExtension - File Type Extension: psp +[File, File, Other] MIMEType - MIME Type: image/x-paintshoppro +[PSP, PSP, Image] FileVersion - File Version: 10.0 +[PSP, PSP, Image] 0 - Image Width: 8 +[PSP, PSP, Image] 4 - Image Height: 8 +[PSP, PSP, Image] 8 - Image Resolution: 200 +[PSP, PSP, Image] 16 - Resolution Unit: inches +[PSP, PSP, Image] 17 - Compression: LZ77 +[PSP, PSP, Image] 19 - Bits Per Sample: 24 +[PSP, PSP, Image] 21 - Planes: 1 +[PSP, PSP, Image] 23 - Num Colors: 16777216 +[PSP, PSP, Image] 0 - Title: Test Image +[PSP, PSP, Time] 1 - Create Date: 2010:01:28 09:23:21-05:00 +[PSP, PSP, Time] 2 - Modify Date: 2010:01:28 09:30:26-05:00 +[PSP, PSP, Author] 3 - Artist: Phil Harvey +[PSP, PSP, Author] 4 - Copyright: © 2010 Phil Harvey +[PSP, PSP, Image] 5 - Description: A description +[PSP, PSP, Image] 6 - Creator App ID: Paint Shop Pro +[PSP, PSP, Image] 7 - Creator App Version: 13.0.0.0 +[EXIF, IFD0, Author] 33432 - Copyright: © 2010 Phil Harvey +[EXIF, IFD0, Image] 282 - X Resolution: 200 +[EXIF, IFD0, Image] 283 - Y Resolution: 200 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Palm.t b/ExifTool/t/Palm.t new file mode 100644 index 0000000..6b910df --- /dev/null +++ b/ExifTool/t/Palm.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Palm.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Palm; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Palm'; +my $testnum = 1; + +# test 2: Extract information from MOBI book +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Palm.mobi'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Palm_2.out b/ExifTool/t/Palm_2.out new file mode 100644 index 0000000..56931a2 --- /dev/null +++ b/ExifTool/t/Palm_2.out @@ -0,0 +1,32 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Palm.mobi +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1382 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2014:05:29 12:42:41-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: MOBI +[File, File, Other] FileTypeExtension - File Type Extension: mobi +[File, File, Other] MIMEType - MIME Type: application/x-mobipocket-ebook +[Palm, Palm, Document] 0 - Database Name: El_Diezmo_Continua_Vigente +[Palm, Palm, Time] 9 - Create Date: 2014:05:27 20:00:51-04:00 +[Palm, Palm, Time] 10 - Modify Date: 2014:05:27 20:00:51-04:00 +[Palm, Palm, Time] 11 - Last Backup Date: 0000:00:00 00:00:00 +[Palm, Palm, Document] 12 - Modification Number: 0 +[Palm, Palm, Document] 15 - Palm File Type: Mobipocket +[Palm, MOBI, Document] 0 - Compression: PalmDOC +[Palm, MOBI, Document] 1 - Uncompressed Text Length: 172 kB +[Palm, MOBI, Document] 3 - Encryption: None +[Palm, MOBI, Document] 6 - Mobi Type: Mobipocket Book +[Palm, MOBI, Document] 7 - Code Page: Windows Latin 1 (Western European) +[Palm, MOBI, Document] 9 - Mobi Version: 6 +[Palm, MOBI, Document] 21 - Book Name: El Diezmo Continua Vigente +[Palm, MOBI, Document] 26 - Minimum Version: 6 +[Palm, MOBI, Author] 100 - Author: Mike Peralta +[Palm, MOBI, Document] 108 - Contributor: Smashwords, Inc. +[Palm, MOBI, Document] 103 - Description: Hebreos 7:8 “Y aquí ciertamente reciben los diezmos hombres mortales; pero allí, uno [Jesucristo] de quien se da testimonio de que [Jesucristo] vive.†Basado en Hebreos 7:8, Jesús ahora recibe nuestros diezmos y esta en el Nuevo Testamento. Aquellos que dicen que el diezmo ya no se aplica están hablando falsamente y directamente opuestos al evangelio del Nuevo Testamento de Jesucristo. +[Palm, MOBI, Document] 204 - Creator Software: Kindlegen (Linux) +[Palm, MOBI, Document] 205 - Creator Major Version: 1 +[Palm, MOBI, Document] 206 - Creator Minor Version: 1 +[Palm, MOBI, Document] 207 - Creator Build Number: 98 diff --git a/ExifTool/t/Panasonic.t b/ExifTool/t/Panasonic.t new file mode 100644 index 0000000..8e7c3ef --- /dev/null +++ b/ExifTool/t/Panasonic.t @@ -0,0 +1,60 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Panasonic.t". + +BEGIN { + $| = 1; print "1..5\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Panasonic; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Panasonic'; +my $testnum = 1; + +# test 2: Extract information from Panasonic.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Panasonic.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + ['Keywords' => 'cool'], + ['ShootingMode' => 'Panning'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: Extract information from RW2 image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Panasonic.rw2'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 5: Write to RW2 image +{ + ++$testnum; + my @writeInfo = ( + ['XMP:Title' => 'new title'], + ['IPTC:Keywords' => 'a keyword'], + ['ModifyDate' => '2009:03:25 12:11:46'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, "t/images/$testname.rw2"); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Panasonic_2.out b/ExifTool/t/Panasonic_2.out new file mode 100644 index 0000000..979990b --- /dev/null +++ b/ExifTool/t/Panasonic_2.out @@ -0,0 +1,102 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Panasonic.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 7.1 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: Panasonic +[EXIF, IFD0, Camera] 272 - Camera Model Name: DMC-FZ3 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2004:08:28 15:55:46 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/400 +[EXIF, ExifIFD, Image] 33437 - F Number: 4.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 80 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2004:08:28 15:55:46 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2004:08:28 15:55:46 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 4 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 4.6 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2016 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1512 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 35 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 6822 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Panasonic, Camera] 1 - Image Quality: High +[MakerNotes, Panasonic, Camera] 2 - Firmware Version: 0.1.0.8 +[MakerNotes, Panasonic, Camera] 3 - White Balance: Auto +[MakerNotes, Panasonic, Camera] 7 - Focus Mode: Auto +[MakerNotes, Panasonic, Camera] 15 - AF Area Mode: 9-area +[MakerNotes, Panasonic, Camera] 26 - Image Stabilization: On, Mode 2 +[MakerNotes, Panasonic, Camera] 28 - Macro Mode: Off +[MakerNotes, Panasonic, Camera] 31 - Shooting Mode: Program +[MakerNotes, Panasonic, Camera] 32 - Audio: No +[MakerNotes, Panasonic, Camera] 33 - Data Dump: (Binary data 5428 bytes) +[MakerNotes, Panasonic, Camera] 35 - White Balance Bias: 0 +[MakerNotes, Panasonic, Camera] 36 - Flash Bias: 0 +[MakerNotes, Panasonic, Camera] 37 - Internal Serial Number: (S00) 2004:07:19 no. 0102 +[MakerNotes, Panasonic, Camera] 38 - Panasonic Exif Version: 0100 +[MakerNotes, Panasonic, Camera] 39 - Video Frame Rate: n/a +[MakerNotes, Panasonic, Camera] 40 - Color Effect: Off +[MakerNotes, Panasonic, Camera] 41 - Time Since Power On: 00:00:06.96 +[MakerNotes, Panasonic, Camera] 42 - Burst Mode: Off +[MakerNotes, Panasonic, Camera] 43 - Sequence Number: 0 +[MakerNotes, Panasonic, Camera] 44 - Contrast Mode: Normal +[MakerNotes, Panasonic, Camera] 45 - Noise Reduction: Standard +[MakerNotes, Panasonic, Camera] 46 - Self Timer: Off +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[Composite, Composite, Image] Exif-Aperture - Aperture: 4.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 7.6 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/400 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.004 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 54.4 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 4.6 mm (35 mm equivalent: 35.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 1.34 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 13.0 diff --git a/ExifTool/t/Panasonic_3.out b/ExifTool/t/Panasonic_3.out new file mode 100644 index 0000000..af1edd9 --- /dev/null +++ b/ExifTool/t/Panasonic_3.out @@ -0,0 +1,121 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Panasonic_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 7.1 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: ff6b1fa510bbe0d2c2323752af38a86b +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: Panasonic +[EXIF, IFD0, Camera] 272 - Camera Model Name: DMC-FZ3 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2004:08:28 15:55:46 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/400 +[EXIF, ExifIFD, Image] 33437 - F Number: 4.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 80 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2004:08:28 15:55:46 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2004:08:28 15:55:46 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 4 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 4.6 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2016 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1512 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 35 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 6812 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Panasonic, Camera] 1 - Image Quality: High +[MakerNotes, Panasonic, Camera] 2 - Firmware Version: 0.1.0.8 +[MakerNotes, Panasonic, Camera] 3 - White Balance: Auto +[MakerNotes, Panasonic, Camera] 7 - Focus Mode: Auto +[MakerNotes, Panasonic, Camera] 15 - AF Area Mode: 9-area +[MakerNotes, Panasonic, Camera] 26 - Image Stabilization: On, Mode 2 +[MakerNotes, Panasonic, Camera] 28 - Macro Mode: Off +[MakerNotes, Panasonic, Camera] 31 - Shooting Mode: Panning +[MakerNotes, Panasonic, Camera] 32 - Audio: No +[MakerNotes, Panasonic, Camera] 33 - Data Dump: (Binary data 5428 bytes) +[MakerNotes, Panasonic, Camera] 34 - Panasonic 0x0022: 0 +[MakerNotes, Panasonic, Camera] 35 - White Balance Bias: 0 +[MakerNotes, Panasonic, Camera] 36 - Flash Bias: 0 +[MakerNotes, Panasonic, Camera] 37 - Internal Serial Number: (S00) 2004:07:19 no. 0102 +[MakerNotes, Panasonic, Camera] 38 - Panasonic Exif Version: 0100 +[MakerNotes, Panasonic, Camera] 39 - Video Frame Rate: n/a +[MakerNotes, Panasonic, Camera] 40 - Color Effect: Off +[MakerNotes, Panasonic, Camera] 41 - Time Since Power On: 00:00:06.96 +[MakerNotes, Panasonic, Camera] 42 - Burst Mode: Off +[MakerNotes, Panasonic, Camera] 43 - Sequence Number: 0 +[MakerNotes, Panasonic, Camera] 44 - Contrast Mode: Normal +[MakerNotes, Panasonic, Camera] 45 - Noise Reduction: Standard +[MakerNotes, Panasonic, Camera] 46 - Self Timer: Off +[MakerNotes, Panasonic, Camera] 47 - Panasonic 0x002f: 4 +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00160016 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x00000000 +[PrintIM, PrintIM, Printing] 3 - Print IM 0x0003: 0x00000064 +[PrintIM, PrintIM, Printing] 7 - Print IM 0x0007: 0x00000000 +[PrintIM, PrintIM, Printing] 8 - Print IM 0x0008: 0x00000000 +[PrintIM, PrintIM, Printing] 9 - Print IM 0x0009: 0x00000000 +[PrintIM, PrintIM, Printing] 10 - Print IM 0x000a: 0x00000000 +[PrintIM, PrintIM, Printing] 11 - Print IM 0x000b: 0x000000ac +[PrintIM, PrintIM, Printing] 12 - Print IM 0x000c: 0x00000000 +[PrintIM, PrintIM, Printing] 13 - Print IM 0x000d: 0x00000000 +[PrintIM, PrintIM, Printing] 14 - Print IM 0x000e: 0x000000c4 +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x00000005 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000001 +[PrintIM, PrintIM, Printing] 272 - Print IM 0x0110: 0x00000080 +[IPTC, IPTC, Other] 25 - Keywords: cool +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[Composite, Composite, Image] Exif-Aperture - Aperture: 4.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/400 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-LightValue - Light Value: 13.0 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 7.6 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.004 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 54.4 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 4.6 mm (35 mm equivalent: 35.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 1.34 m diff --git a/ExifTool/t/Panasonic_4.out b/ExifTool/t/Panasonic_4.out new file mode 100644 index 0000000..e32b3c0 --- /dev/null +++ b/ExifTool/t/Panasonic_4.out @@ -0,0 +1,213 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Panasonic.rw2 +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 12 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2009:03:25 12:10:29-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: RW2 +[File, File, Other] FileTypeExtension - File Type Extension: rw2 +[File, File, Other] MIMEType - MIME Type: image/x-panasonic-rw2 +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 1 - Panasonic Raw Version: 0300 +[EXIF, IFD0, Image] 2 - Sensor Width: 3724 +[EXIF, IFD0, Image] 3 - Sensor Height: 2754 +[EXIF, IFD0, Image] 4 - Sensor Top Border: 6 +[EXIF, IFD0, Image] 5 - Sensor Left Border: 8 +[EXIF, IFD0, Image] 6 - Sensor Bottom Border: 2742 +[EXIF, IFD0, Image] 7 - Sensor Right Border: 3656 +[EXIF, IFD0, Image] 8 - Samples Per Pixel: 1 +[EXIF, IFD0, Image] 9 - CFA Pattern: [Blue,Green][Green,Red] +[EXIF, IFD0, Image] 10 - Bits Per Sample: 12 +[EXIF, IFD0, Image] 11 - Compression: Panasonic RAW 1 +[EXIF, IFD0, Image] 14 - Linearity Limit Red: 4095 +[EXIF, IFD0, Image] 15 - Linearity Limit Green: 4095 +[EXIF, IFD0, Image] 16 - Linearity Limit Blue: 4095 +[EXIF, IFD0, Image] 23 - ISO: 80 +[EXIF, IFD0, Image] 24 - High ISO Multiplier Red: 0 +[EXIF, IFD0, Image] 25 - High ISO Multiplier Green: 0 +[EXIF, IFD0, Image] 26 - High ISO Multiplier Blue: 0 +[EXIF, IFD0, Image] 27 - Noise Reduction Params: 3 100 1 1 1 200 2 2 2 400 4 4 4 +[EXIF, IFD0, Image] 28 - Black Level Red: 0 +[EXIF, IFD0, Image] 29 - Black Level Green: 0 +[EXIF, IFD0, Image] 30 - Black Level Blue: 0 +[EXIF, IFD0, Image] 36 - WB Red Level: 570 +[EXIF, IFD0, Image] 37 - WB Green Level: 263 +[EXIF, IFD0, Image] 38 - WB Blue Level: 438 +[EXIF, IFD0, Image] 45 - Raw Format: 4 +[EXIF, IFD0, Camera] 271 - Make: Panasonic +[EXIF, IFD0, Camera] 272 - Camera Model Name: DMC-LX3 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Ver.1.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2008:08:06 15:21:56 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/250 +[EXIF, ExifIFD, Image] 33437 - F Number: 4.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 80 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2008:08:06 15:21:56 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2008:08:06 15:21:56 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 5.1 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1920 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1440 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 24 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 180 +[EXIF, IFD1, Image] 283 - Y Resolution: 180 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 11976 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Preview] 46 - Jpg From Raw: (Binary data 10785 bytes) +[EXIF, IFD0, Camera] 271 - Make: Panasonic +[EXIF, IFD0, Camera] 272 - Camera Model Name: DMC-LX3 +[EXIF, IFD0, Image] 273 - Strip Offsets: 4294967295 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 278 - Rows Per Strip: 2754 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 20511792 +[EXIF, IFD0, Image] 280 - Raw Data Offset: 12322 +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/250 +[EXIF, ExifIFD, Image] 33437 - F Number: 4.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2008:08:06 15:21:56 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2008:08:06 15:21:56 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 5.1 mm +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[PanasonicRaw, PanasonicRaw, Other] 0 - Num WB Entries: 7 +[PanasonicRaw, PanasonicRaw, Other] 1 - WB Type 1: Fine Weather +[PanasonicRaw, PanasonicRaw, Other] 2 - WB RGB Levels 1: 573 256 416 +[PanasonicRaw, PanasonicRaw, Other] 5 - WB Type 2: Cloudy +[PanasonicRaw, PanasonicRaw, Other] 6 - WB RGB Levels 2: 630 256 387 +[PanasonicRaw, PanasonicRaw, Other] 9 - WB Type 3: Shade +[PanasonicRaw, PanasonicRaw, Other] 10 - WB RGB Levels 3: 702 256 355 +[PanasonicRaw, PanasonicRaw, Other] 13 - WB Type 4: Tungsten (Incandescent) +[PanasonicRaw, PanasonicRaw, Other] 14 - WB RGB Levels 4: 378 256 592 +[PanasonicRaw, PanasonicRaw, Other] 17 - WB Type 5: Flash +[PanasonicRaw, PanasonicRaw, Other] 18 - WB RGB Levels 5: 651 256 377 +[PanasonicRaw, PanasonicRaw, Other] 21 - WB Type 6: D55 +[PanasonicRaw, PanasonicRaw, Other] 22 - WB RGB Levels 6: 590 256 421 +[PanasonicRaw, PanasonicRaw, Other] 25 - WB Type 7: ISO Studio Tungsten +[PanasonicRaw, PanasonicRaw, Other] 26 - WB RGB Levels 7: 378 256 592 +[PanasonicRaw, PanasonicRaw, Image] 2 - Distortion Param 02: 0.010772705078125 +[PanasonicRaw, PanasonicRaw, Image] 4 - Distortion Param 04: 0.02056884765625 +[PanasonicRaw, PanasonicRaw, Image] 5 - Distortion Scale: 1 +[PanasonicRaw, PanasonicRaw, Image] 7.1 - Distortion Correction: On +[PanasonicRaw, PanasonicRaw, Image] 8 - Distortion Param 08: 0.12359619140625 +[PanasonicRaw, PanasonicRaw, Image] 9 - Distortion Param 09: 0.00579833984375 +[PanasonicRaw, PanasonicRaw, Image] 11 - Distortion Param 11: -0.02374267578125 +[MakerNotes, Panasonic, Camera] 1 - Image Quality: RAW +[MakerNotes, Panasonic, Camera] 2 - Firmware Version: 0.1.0.0 +[MakerNotes, Panasonic, Camera] 3 - White Balance: Auto +[MakerNotes, Panasonic, Camera] 7 - Focus Mode: Auto +[MakerNotes, Panasonic, Camera] 15 - AF Area Mode: 1-area +[MakerNotes, Panasonic, Camera] 26 - Image Stabilization: On, Mode 2 +[MakerNotes, Panasonic, Camera] 28 - Macro Mode: Off +[MakerNotes, Panasonic, Camera] 31 - Shooting Mode: Aperture Priority +[MakerNotes, Panasonic, Camera] 32 - Audio: No +[MakerNotes, Panasonic, Camera] 33 - Data Dump: (Binary data 8200 bytes) +[MakerNotes, Panasonic, Camera] 36 - Flash Bias: 0 +[MakerNotes, Panasonic, Camera] 37 - Internal Serial Number: (F35) 2008:07:01 no. 0058 +[MakerNotes, Panasonic, Camera] 38 - Panasonic Exif Version: 0270 +[MakerNotes, Panasonic, Camera] 39 - Video Frame Rate: n/a +[MakerNotes, Panasonic, Camera] 40 - Color Effect: Off +[MakerNotes, Panasonic, Camera] 41 - Time Since Power On: 00:10:33.08 +[MakerNotes, Panasonic, Camera] 42 - Burst Mode: Off +[MakerNotes, Panasonic, Camera] 43 - Sequence Number: 0 +[MakerNotes, Panasonic, Camera] 44 - Contrast Mode: Normal +[MakerNotes, Panasonic, Camera] 45 - Noise Reduction: Standard +[MakerNotes, Panasonic, Camera] 46 - Self Timer: Off +[MakerNotes, Panasonic, Camera] 48 - Rotation: Horizontal (normal) +[MakerNotes, Panasonic, Camera] 49 - AF Assist Lamp: Enabled but Not Used +[MakerNotes, Panasonic, Camera] 50 - Color Mode: Normal +[MakerNotes, Panasonic, Camera] 51 - Baby Age: (not set) +[MakerNotes, Panasonic, Camera] 52 - Optical Zoom Mode: Standard +[MakerNotes, Panasonic, Camera] 53 - Conversion Lens: Off +[MakerNotes, Panasonic, Camera] 54 - Travel Day: n/a +[MakerNotes, Panasonic, Camera] 56 - Battery Level: Full +[MakerNotes, Panasonic, Camera] 57 - Contrast: Normal +[MakerNotes, Panasonic, Camera] 58 - World Time Location: Home +[MakerNotes, Panasonic, Camera] 59 - Text Stamp: Off +[MakerNotes, Panasonic, Camera] 60 - Program ISO: 80 +[MakerNotes, Panasonic, Camera] 61 - Advanced Scene Type: 1 +[MakerNotes, Panasonic, Camera] 62 - Text Stamp: Off +[MakerNotes, Panasonic, Camera] 63 - Faces Detected: 0 +[MakerNotes, Panasonic, Camera] 64 - Saturation: Normal +[MakerNotes, Panasonic, Camera] 65 - Sharpness: Normal +[MakerNotes, Panasonic, Camera] 66 - Film Mode: Standard (color) +[MakerNotes, Panasonic, Camera] 67 - JPEG Quality: High +[MakerNotes, Panasonic, Camera] 68 - Color Temp Kelvin: 0 +[MakerNotes, Panasonic, Camera] 70 - WB Shift AB: 0 +[MakerNotes, Panasonic, Camera] 71 - WB Shift GM: 0 +[MakerNotes, Panasonic, Camera] 72 - Flash Curtain: n/a +[MakerNotes, Panasonic, Camera] 75 - Panasonic Image Width: 3648 +[MakerNotes, Panasonic, Camera] 76 - Panasonic Image Height: 2736 +[MakerNotes, Panasonic, Camera] 77 - AF Point Position: 0.5 0.5 +[MakerNotes, Panasonic, Image] 0 - Num Face Positions: 0 +[MakerNotes, Panasonic, Camera] 32768 - Maker Note Version: 0130 +[MakerNotes, Panasonic, Camera] 32769 - Scene Mode: Off +[MakerNotes, Panasonic, Camera] 32770 - Highlight Warning: Yes +[MakerNotes, Panasonic, Camera] 32771 - Dark Focus Environment: No +[MakerNotes, Panasonic, Camera] 32772 - WB Red Level: 2283 +[MakerNotes, Panasonic, Camera] 32773 - WB Green Level: 1054 +[MakerNotes, Panasonic, Camera] 32774 - WB Blue Level: 1752 +[MakerNotes, Panasonic, Camera] 32776 - Text Stamp: Off +[MakerNotes, Panasonic, Camera] 32777 - Text Stamp: Off +[MakerNotes, Panasonic, Camera] 32784 - Baby Age: (not set) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[Composite, Composite, Image] Exif-Aperture - Aperture: 4.0 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.665399 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.1673 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/250 +[Composite, Composite, Camera] Panasonic-AdvancedSceneMode - Advanced Scene Mode: Off +[Composite, Composite, Other] PanasonicRaw-ImageHeight - Image Height: 2736 +[Composite, Composite, Other] PanasonicRaw-ImageWidth - Image Width: 3648 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3648x2736 +[Composite, Composite, Image] Exif-LightValue - Light Value: 12.3 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 10.0 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 4.7 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.006 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 73.7 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 5.1 mm (35 mm equivalent: 24.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 1.02 m diff --git a/ExifTool/t/Panasonic_5.out b/ExifTool/t/Panasonic_5.out new file mode 100644 index 0000000..962a7a7 --- /dev/null +++ b/ExifTool/t/Panasonic_5.out @@ -0,0 +1,244 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Panasonic_5_failed.rw2 +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 15 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:45-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: RW2 +[File, File, Other] FileTypeExtension - File Type Extension: rw2 +[File, File, Other] MIMEType - MIME Type: image/x-panasonic-rw2 +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 85c7e966b2683711ba91f562579e3d76 +[EXIF, IFD0, Image] 1 - Panasonic Raw Version: 0300 +[EXIF, IFD0, Image] 2 - Sensor Width: 3724 +[EXIF, IFD0, Image] 3 - Sensor Height: 2754 +[EXIF, IFD0, Image] 4 - Sensor Top Border: 6 +[EXIF, IFD0, Image] 5 - Sensor Left Border: 8 +[EXIF, IFD0, Image] 6 - Sensor Bottom Border: 2742 +[EXIF, IFD0, Image] 7 - Sensor Right Border: 3656 +[EXIF, IFD0, Image] 8 - Samples Per Pixel: 1 +[EXIF, IFD0, Image] 9 - CFA Pattern: [Blue,Green][Green,Red] +[EXIF, IFD0, Image] 10 - Bits Per Sample: 12 +[EXIF, IFD0, Image] 11 - Compression: Panasonic RAW 1 +[EXIF, IFD0, Image] 13 - Panasonic Raw 0x000d: 1 +[EXIF, IFD0, Image] 14 - Linearity Limit Red: 4095 +[EXIF, IFD0, Image] 15 - Linearity Limit Green: 4095 +[EXIF, IFD0, Image] 16 - Linearity Limit Blue: 4095 +[EXIF, IFD0, Image] 23 - ISO: 80 +[EXIF, IFD0, Image] 24 - High ISO Multiplier Red: 0 +[EXIF, IFD0, Image] 25 - High ISO Multiplier Green: 0 +[EXIF, IFD0, Image] 26 - High ISO Multiplier Blue: 0 +[EXIF, IFD0, Image] 27 - Noise Reduction Params: 3 100 1 1 1 200 2 2 2 400 4 4 4 +[EXIF, IFD0, Image] 28 - Black Level Red: 0 +[EXIF, IFD0, Image] 29 - Black Level Green: 0 +[EXIF, IFD0, Image] 30 - Black Level Blue: 0 +[EXIF, IFD0, Image] 36 - WB Red Level: 570 +[EXIF, IFD0, Image] 37 - WB Green Level: 263 +[EXIF, IFD0, Image] 38 - WB Blue Level: 438 +[EXIF, IFD0, Image] 45 - Raw Format: 4 +[EXIF, IFD0, Camera] 271 - Make: Panasonic +[EXIF, IFD0, Camera] 272 - Camera Model Name: DMC-LX3 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Ver.1.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2009:03:25 12:11:46 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/250 +[EXIF, ExifIFD, Image] 33437 - F Number: 4.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 80 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2008:08:06 15:21:56 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2008:08:06 15:21:56 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 5.1 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1920 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1440 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 24 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: None +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 180 +[EXIF, IFD1, Image] 283 - Y Resolution: 180 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 11042 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Preview] 46 - Jpg From Raw: (Binary data 10781 bytes) +[EXIF, IFD0, Camera] 271 - Make: Panasonic +[EXIF, IFD0, Camera] 272 - Camera Model Name: DMC-LX3 +[EXIF, IFD0, Image] 273 - Strip Offsets: 4294967295 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 278 - Rows Per Strip: 2754 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 20511792 +[EXIF, IFD0, Image] 280 - Raw Data Offset: 14554 +[EXIF, IFD0, Image] 282 - Panasonic Raw 0x011a: 1 +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/250 +[EXIF, ExifIFD, Image] 33437 - F Number: 4.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2008:08:06 15:21:56 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2008:08:06 15:21:56 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 5.1 mm +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[PanasonicRaw, PanasonicRaw, Other] 0 - Num WB Entries: 7 +[PanasonicRaw, PanasonicRaw, Other] 1 - WB Type 1: Fine Weather +[PanasonicRaw, PanasonicRaw, Other] 2 - WB RGB Levels 1: 573 256 416 +[PanasonicRaw, PanasonicRaw, Other] 5 - WB Type 2: Cloudy +[PanasonicRaw, PanasonicRaw, Other] 6 - WB RGB Levels 2: 630 256 387 +[PanasonicRaw, PanasonicRaw, Other] 9 - WB Type 3: Shade +[PanasonicRaw, PanasonicRaw, Other] 10 - WB RGB Levels 3: 702 256 355 +[PanasonicRaw, PanasonicRaw, Other] 13 - WB Type 4: Tungsten (Incandescent) +[PanasonicRaw, PanasonicRaw, Other] 14 - WB RGB Levels 4: 378 256 592 +[PanasonicRaw, PanasonicRaw, Other] 17 - WB Type 5: Flash +[PanasonicRaw, PanasonicRaw, Other] 18 - WB RGB Levels 5: 651 256 377 +[PanasonicRaw, PanasonicRaw, Other] 21 - WB Type 6: D55 +[PanasonicRaw, PanasonicRaw, Other] 22 - WB RGB Levels 6: 590 256 421 +[PanasonicRaw, PanasonicRaw, Other] 25 - WB Type 7: ISO Studio Tungsten +[PanasonicRaw, PanasonicRaw, Other] 26 - WB RGB Levels 7: 378 256 592 +[PanasonicRaw, PanasonicRaw, Image] 2 - Distortion Param 02: 0.010772705078125 +[PanasonicRaw, PanasonicRaw, Image] 4 - Distortion Param 04: 0.02056884765625 +[PanasonicRaw, PanasonicRaw, Image] 5 - Distortion Scale: 1 +[PanasonicRaw, PanasonicRaw, Image] 7.1 - Distortion Correction: On +[PanasonicRaw, PanasonicRaw, Image] 8 - Distortion Param 08: 0.12359619140625 +[PanasonicRaw, PanasonicRaw, Image] 9 - Distortion Param 09: 0.00579833984375 +[PanasonicRaw, PanasonicRaw, Image] 11 - Distortion Param 11: -0.02374267578125 +[PanasonicRaw, PanasonicRaw, Image] 12 - Distortion N: 2280 +[MakerNotes, Panasonic, Camera] 1 - Image Quality: RAW +[MakerNotes, Panasonic, Camera] 2 - Firmware Version: 0.1.0.0 +[MakerNotes, Panasonic, Camera] 3 - White Balance: Auto +[MakerNotes, Panasonic, Camera] 7 - Focus Mode: Auto +[MakerNotes, Panasonic, Camera] 15 - AF Area Mode: 1-area +[MakerNotes, Panasonic, Camera] 26 - Image Stabilization: On, Mode 2 +[MakerNotes, Panasonic, Camera] 28 - Macro Mode: Off +[MakerNotes, Panasonic, Camera] 31 - Shooting Mode: Aperture Priority +[MakerNotes, Panasonic, Camera] 32 - Audio: No +[MakerNotes, Panasonic, Camera] 33 - Data Dump: (Binary data 8200 bytes) +[MakerNotes, Panasonic, Camera] 34 - Panasonic 0x0022: 0 +[MakerNotes, Panasonic, Camera] 36 - Flash Bias: 0 +[MakerNotes, Panasonic, Camera] 37 - Internal Serial Number: (F35) 2008:07:01 no. 0058 +[MakerNotes, Panasonic, Camera] 38 - Panasonic Exif Version: 0270 +[MakerNotes, Panasonic, Camera] 39 - Video Frame Rate: n/a +[MakerNotes, Panasonic, Camera] 40 - Color Effect: Off +[MakerNotes, Panasonic, Camera] 41 - Time Since Power On: 00:10:33.08 +[MakerNotes, Panasonic, Camera] 42 - Burst Mode: Off +[MakerNotes, Panasonic, Camera] 43 - Sequence Number: 0 +[MakerNotes, Panasonic, Camera] 44 - Contrast Mode: Normal +[MakerNotes, Panasonic, Camera] 45 - Noise Reduction: Standard +[MakerNotes, Panasonic, Camera] 46 - Self Timer: Off +[MakerNotes, Panasonic, Camera] 47 - Panasonic 0x002f: 1 +[MakerNotes, Panasonic, Camera] 48 - Rotation: Horizontal (normal) +[MakerNotes, Panasonic, Camera] 49 - AF Assist Lamp: Enabled but Not Used +[MakerNotes, Panasonic, Camera] 50 - Color Mode: Normal +[MakerNotes, Panasonic, Camera] 51 - Baby Age: (not set) +[MakerNotes, Panasonic, Camera] 52 - Optical Zoom Mode: Standard +[MakerNotes, Panasonic, Camera] 53 - Conversion Lens: Off +[MakerNotes, Panasonic, Camera] 54 - Travel Day: n/a +[MakerNotes, Panasonic, Camera] 55 - Panasonic 0x0037: 257 +[MakerNotes, Panasonic, Camera] 56 - Battery Level: Full +[MakerNotes, Panasonic, Camera] 57 - Contrast: Normal +[MakerNotes, Panasonic, Camera] 58 - World Time Location: Home +[MakerNotes, Panasonic, Camera] 59 - Text Stamp: Off +[MakerNotes, Panasonic, Camera] 60 - Program ISO: 80 +[MakerNotes, Panasonic, Camera] 61 - Advanced Scene Type: 1 +[MakerNotes, Panasonic, Camera] 62 - Text Stamp: Off +[MakerNotes, Panasonic, Camera] 63 - Faces Detected: 0 +[MakerNotes, Panasonic, Camera] 64 - Saturation: Normal +[MakerNotes, Panasonic, Camera] 65 - Sharpness: Normal +[MakerNotes, Panasonic, Camera] 66 - Film Mode: Standard (color) +[MakerNotes, Panasonic, Camera] 67 - JPEG Quality: High +[MakerNotes, Panasonic, Camera] 68 - Color Temp Kelvin: 0 +[MakerNotes, Panasonic, Camera] 70 - WB Shift AB: 0 +[MakerNotes, Panasonic, Camera] 71 - WB Shift GM: 0 +[MakerNotes, Panasonic, Camera] 72 - Flash Curtain: n/a +[MakerNotes, Panasonic, Camera] 74 - Panasonic 0x004a: 0 +[MakerNotes, Panasonic, Camera] 75 - Panasonic Image Width: 3648 +[MakerNotes, Panasonic, Camera] 76 - Panasonic Image Height: 2736 +[MakerNotes, Panasonic, Camera] 77 - AF Point Position: 0.5 0.5 +[MakerNotes, Panasonic, Image] 0 - Num Face Positions: 0 +[MakerNotes, Panasonic, Camera] 79 - Panasonic 0x004f: 0 +[MakerNotes, Panasonic, Camera] 85 - Panasonic 0x0055: 1 +[MakerNotes, Panasonic, Camera] 87 - Panasonic 0x0057: 0 +[MakerNotes, Panasonic, Camera] 94 - Panasonic 0x005e: . +[MakerNotes, Panasonic, Camera] 32768 - Maker Note Version: 0130 +[MakerNotes, Panasonic, Camera] 32769 - Scene Mode: Off +[MakerNotes, Panasonic, Camera] 32770 - Highlight Warning: Yes +[MakerNotes, Panasonic, Camera] 32771 - Dark Focus Environment: No +[MakerNotes, Panasonic, Camera] 32772 - WB Red Level: 2283 +[MakerNotes, Panasonic, Camera] 32773 - WB Green Level: 1054 +[MakerNotes, Panasonic, Camera] 32774 - WB Blue Level: 1752 +[MakerNotes, Panasonic, Camera] 32775 - Panasonic 0x8007: 1 +[MakerNotes, Panasonic, Camera] 32776 - Text Stamp: Off +[MakerNotes, Panasonic, Camera] 32777 - Text Stamp: Off +[MakerNotes, Panasonic, Camera] 32784 - Baby Age: (not set) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00160016 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x00000000 +[PrintIM, PrintIM, Printing] 3 - Print IM 0x0003: 0x00000064 +[PrintIM, PrintIM, Printing] 7 - Print IM 0x0007: 0x00000000 +[PrintIM, PrintIM, Printing] 8 - Print IM 0x0008: 0x00000000 +[PrintIM, PrintIM, Printing] 9 - Print IM 0x0009: 0x00000000 +[PrintIM, PrintIM, Printing] 10 - Print IM 0x000a: 0x00000000 +[PrintIM, PrintIM, Printing] 11 - Print IM 0x000b: 0x000000ac +[PrintIM, PrintIM, Printing] 12 - Print IM 0x000c: 0x00000000 +[PrintIM, PrintIM, Printing] 13 - Print IM 0x000d: 0x00000000 +[PrintIM, PrintIM, Printing] 14 - Print IM 0x000e: 0x000000c4 +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x00000005 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000001 +[PrintIM, PrintIM, Printing] 272 - Print IM 0x0110: 0x00000080 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-dc, Image] title - Title: new title +[IPTC, IPTC, Other] 25 - Keywords: a keyword +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[Composite, Composite, Image] Exif-Aperture - Aperture: 4.0 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.665399 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.1673 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/250 +[Composite, Composite, Camera] Panasonic-AdvancedSceneMode - Advanced Scene Mode: Off +[Composite, Composite, Other] PanasonicRaw-ImageHeight - Image Height: 2736 +[Composite, Composite, Other] PanasonicRaw-ImageWidth - Image Width: 3648 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3648x2736 +[Composite, Composite, Image] Exif-LightValue - Light Value: 12.3 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 10.0 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 4.7 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.006 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 73.7 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 5.1 mm (35 mm equivalent: 24.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 1.02 m diff --git a/ExifTool/t/Pentax.t b/ExifTool/t/Pentax.t new file mode 100644 index 0000000..786788b --- /dev/null +++ b/ExifTool/t/Pentax.t @@ -0,0 +1,50 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Pentax.t". + +BEGIN { + $| = 1; print "1..4\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Pentax; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Pentax'; +my $testnum = 1; + +# test 2: Extract information from Pentax.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Pentax.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + ['ThumbnailImage'], # delete thumbnail image + ['WhiteBalance' => 'Tungsten', 'Group' => 'MakerNotes'], + ['FocalLength' => 22 ], + ['MaxAperture' => 2.0 ], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: Extract information from a Pentax AVI video +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Pentax.avi'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Pentax_2.out b/ExifTool/t/Pentax_2.out new file mode 100644 index 0000000..7f6d251 --- /dev/null +++ b/ExifTool/t/Pentax_2.out @@ -0,0 +1,225 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Pentax.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.7 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2008:03:28 13:40:33-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[JFIF, JFIF, Image] 0 - JFIF Version: 1.01 +[JFIF, JFIF, Image] 2 - Resolution Unit: None +[JFIF, JFIF, Image] 3 - X Resolution: 1 +[JFIF, JFIF, Image] 5 - Y Resolution: 1 +[EXIF, IFD0, Camera] 271 - Make: PENTAX Corporation +[EXIF, IFD0, Camera] 272 - Camera Model Name: PENTAX K10D +[EXIF, IFD0, Image] 274 - Orientation: Rotate 270 CW +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: K10D Ver 1.30 +[EXIF, IFD0, Time] 306 - Modify Date: 2008:03:02 12:01:23 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/100 +[EXIF, ExifIFD, Image] 33437 - F Number: 13.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2008:03:02 12:01:23 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2008:03:02 12:01:23 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: +0.7 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 10.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3008 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2000 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 15 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Distant +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 2418 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Pentax, Camera] 0 - Pentax Version: 3.0.0.0 +[MakerNotes, Pentax, Camera] 1 - Pentax Model Type: 0 +[MakerNotes, Pentax, Image] 2 - Preview Image Size: 640x480 +[MakerNotes, Pentax, Image] 3 - Preview Image Length: 26 +[MakerNotes, Pentax, Image] 4 - Preview Image Start: 2446 +[MakerNotes, Pentax, Camera] 5 - Pentax Model ID: K10D +[MakerNotes, Pentax, Time] 6 - Date: 2008:03:02 +[MakerNotes, Pentax, Time] 7 - Time: 12:01:23 +[MakerNotes, Pentax, Camera] 8 - Quality: Better +[MakerNotes, Pentax, Camera] 12 - Flash Mode: Off, Did not fire; Internal +[MakerNotes, Pentax, Camera] 13 - Focus Mode: AF-S (Focus-priority) +[MakerNotes, Pentax, Camera] 14 - AF Point Selected: Center +[MakerNotes, Pentax, Camera] 18 - Exposure Time: 1/100 +[MakerNotes, Pentax, Camera] 19 - F Number: 13.0 +[MakerNotes, Pentax, Camera] 20 - ISO: 100 +[MakerNotes, Pentax, Camera] 22 - Exposure Compensation: +0.7 +[MakerNotes, Pentax, Camera] 23 - Metering Mode: Multi-segment +[MakerNotes, Pentax, Camera] 24 - Auto Bracketing: 0 EV, No Extended Bracket +[MakerNotes, Pentax, Camera] 25 - White Balance: Auto +[MakerNotes, Pentax, Camera] 26 - White Balance Mode: Auto (Daylight) +[MakerNotes, Pentax, Camera] 29 - Focal Length: 10.0 mm +[MakerNotes, Pentax, Camera] 31 - Saturation: 0 (normal) +[MakerNotes, Pentax, Camera] 32 - Contrast: 0 (normal) +[MakerNotes, Pentax, Camera] 33 - Sharpness: 0 (normal) +[MakerNotes, Pentax, Time] 34 - World Time Location: Hometown +[MakerNotes, Pentax, Time] 35 - Hometown City: Toronto +[MakerNotes, Pentax, Time] 36 - Destination City: New York +[MakerNotes, Pentax, Time] 37 - Hometown DST: No +[MakerNotes, Pentax, Time] 38 - Destination DST: No +[MakerNotes, Pentax, Camera] 39 - DSP Firmware Version: 1.30.00.18 +[MakerNotes, Pentax, Camera] 40 - CPU Firmware Version: 1.30.00.18 +[MakerNotes, Pentax, Camera] 45 - Effective LV: 14.8 +[MakerNotes, Pentax, Camera] 50 - Image Editing: None +[MakerNotes, Pentax, Camera] 51 - Picture Mode: Aperture Priority; 1/3 EV steps +[MakerNotes, Pentax, Camera] 52 - Drive Mode: Single-frame; No Timer; Shutter Button; Single Exposure +[MakerNotes, Pentax, Camera] 55 - Color Space: sRGB +[MakerNotes, Pentax, Camera] 61 - Data Scaling: 8192 +[MakerNotes, Pentax, Camera] 62 - Preview Image Borders: 28 28 0 0 +[MakerNotes, Pentax, Camera] 0 - Lens Type: Sigma or Tamron Lens (3 44) +[MakerNotes, Pentax, Camera] 64 - Sensitivity Adjust: 0 +[MakerNotes, Pentax, Camera] 65 - Image Edit Count: 0 +[MakerNotes, Pentax, Camera] 71 - Camera Temperature: 12 C +[MakerNotes, Pentax, Camera] 72 - AE Lock: Off +[MakerNotes, Pentax, Camera] 73 - Noise Reduction: Off +[MakerNotes, Pentax, Camera] 77 - Flash Exposure Comp: 0 +[MakerNotes, Pentax, Camera] 79 - Image Tone: Natural +[MakerNotes, Pentax, Camera] 0 - SR Result: Stabilized +[MakerNotes, Pentax, Camera] 1 - Shake Reduction: On +[MakerNotes, Pentax, Camera] 2 - SR Half Press Time: 1.53 s +[MakerNotes, Pentax, Camera] 3 - SR Focal Length: 10 mm +[MakerNotes, Pentax, Camera] 93 - Shutter Count: 1648 +[MakerNotes, Pentax, Camera] 98 - Raw Development Process: 1 (K10D,K200D,K2000,K-m) +[MakerNotes, Pentax, Camera] 512 - Black Point: 0 0 0 0 +[MakerNotes, Pentax, Camera] 513 - White Point: 13568 8192 8192 9088 +[MakerNotes, Pentax, Camera] 0 - Picture Mode 2: Aperture Priority +[MakerNotes, Pentax, Camera] 1.1 - Program Line: Normal +[MakerNotes, Pentax, Camera] 1.2 - EV Steps: 1/3 EV Steps +[MakerNotes, Pentax, Camera] 1.3 - E-Dial In Program: Tv or Av +[MakerNotes, Pentax, Camera] 1.4 - Aperture Ring Use: Permitted +[MakerNotes, Pentax, Camera] 2 - Flash Options: Wireless (Master) +[MakerNotes, Pentax, Camera] 2.1 - Metering Mode 2: Multi-segment +[MakerNotes, Pentax, Camera] 3 - AF Point Mode: Select +[MakerNotes, Pentax, Camera] 3.1 - Focus Mode 2: AF-S +[MakerNotes, Pentax, Camera] 4 - AF Point Selected 2: Center +[MakerNotes, Pentax, Camera] 6 - ISO Floor: 100 +[MakerNotes, Pentax, Camera] 7 - Drive Mode 2: Single-frame +[MakerNotes, Pentax, Camera] 8 - Exposure Bracket Step Size: 0.3 +[MakerNotes, Pentax, Camera] 9 - Bracket Shot Number: n/a +[MakerNotes, Pentax, Camera] 10 - White Balance Set: Auto +[MakerNotes, Pentax, Camera] 10.1 - Multiple Exposure Set: Off +[MakerNotes, Pentax, Camera] 13 - Raw And Jpg Recording: RAW+JPEG (PEF, Better) +[MakerNotes, Pentax, Camera] 14.1 - Jpg Recorded Pixels: 6 MP +[MakerNotes, Pentax, Camera] 16 - Flash Options 2: Wireless (Master) +[MakerNotes, Pentax, Camera] 16.1 - Metering Mode 3: Multi-segment +[MakerNotes, Pentax, Camera] 17.1 - SR Active: Yes +[MakerNotes, Pentax, Camera] 17.2 - Rotation: Rotate 270 CW +[MakerNotes, Pentax, Camera] 17.3 - ISO Setting: Manual +[MakerNotes, Pentax, Camera] 17.4 - Sensitivity Steps: 1 EV Steps +[MakerNotes, Pentax, Camera] 18 - Tv Exposure Time Setting: 1/203 +[MakerNotes, Pentax, Camera] 19 - Av Aperture Setting: 12.7 +[MakerNotes, Pentax, Camera] 20 - Sv ISO Setting: 100 +[MakerNotes, Pentax, Camera] 21 - Base Exposure Compensation: +0.7 +[MakerNotes, Pentax, Camera] 0 - AE Exposure Time: 1/101 +[MakerNotes, Pentax, Camera] 1 - AE Aperture: 12.9 +[MakerNotes, Pentax, Camera] 2 - AE ISO: 100 +[MakerNotes, Pentax, Camera] 3 - AE Xv: -0.625 +[MakerNotes, Pentax, Camera] 4 - AEB Xv: 0 +[MakerNotes, Pentax, Camera] 5 - AE Min Exposure Time: 1/3862 +[MakerNotes, Pentax, Camera] 6 - AE Program Mode: Av, B or X +[MakerNotes, Pentax, Camera] 8 - AE Aperture Steps: 28 +[MakerNotes, Pentax, Camera] 9 - AE Max Aperture: 4.0 +[MakerNotes, Pentax, Camera] 10 - AE Max Aperture 2: 4.0 +[MakerNotes, Pentax, Camera] 11 - AE Min Aperture: 23 +[MakerNotes, Pentax, Camera] 12 - AE Metering Mode: Multi-segment +[MakerNotes, Pentax, Camera] 14 - Flash Exposure Comp. Setting: 0 +[MakerNotes, Pentax, Camera] 0 - Lens Type: Sigma or Tamron Lens (3 44) +[MakerNotes, Pentax, Camera] 0.1 - Auto Aperture: On +[MakerNotes, Pentax, Camera] 0.2 - Min Aperture: 22 +[MakerNotes, Pentax, Camera] 0.3 - Lens F Stops: 8.5 +[MakerNotes, Pentax, Camera] 3 - Min Focus Distance: 0.49-0.50 m +[MakerNotes, Pentax, Camera] 3.1 - Focus Range Index: 7 (very far) +[MakerNotes, Pentax, Camera] 9 - Lens Focal Length: 10.0 mm +[MakerNotes, Pentax, Camera] 10 - Nominal Max Aperture: 4.0 +[MakerNotes, Pentax, Camera] 10.1 - Nominal Min Aperture: 23 +[MakerNotes, Pentax, Camera] 14.1 - Max Aperture: 3.9 +[MakerNotes, Pentax, Camera] 0 - Flash Status: Off +[MakerNotes, Pentax, Camera] 1 - Internal Flash Mode: Did not fire, Wireless (Master) +[MakerNotes, Pentax, Camera] 2 - External Flash Mode: Off +[MakerNotes, Pentax, Camera] 3 - Internal Flash Strength: 18 +[MakerNotes, Pentax, Camera] 4 - TTL DA A Up: 0 +[MakerNotes, Pentax, Camera] 5 - TTL DA A Down: 0 +[MakerNotes, Pentax, Camera] 6 - TTL DA B Up: 0 +[MakerNotes, Pentax, Camera] 7 - TTL DA B Down: 0 +[MakerNotes, Pentax, Camera] 24.1 - External Flash Guide Number: n/a +[MakerNotes, Pentax, Camera] 25 - External Flash Exposure Comp: n/a +[MakerNotes, Pentax, Camera] 26 - External Flash Bounce: n/a +[MakerNotes, Pentax, Camera] 521 - AE Metering Segments: 13.5 13.6 13.5 13.2 13.6 15.0 13.8 12.9 13.8 16.4 14.4 13.9 14.4 15.8 14.0 16.0 +[MakerNotes, Pentax, Camera] 522 - Flash Metering Segments: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Pentax, Camera] 523 - Slave Flash Metering Segments: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Pentax, Camera] 525 - WB RGGB Levels Daylight: 13600 8192 8192 8765 +[MakerNotes, Pentax, Camera] 526 - WB RGGB Levels Shade: 16128 8192 8192 6635 +[MakerNotes, Pentax, Camera] 527 - WB RGGB Levels Cloudy: 14560 8192 8192 7782 +[MakerNotes, Pentax, Camera] 528 - WB RGGB Levels Tungsten: 8192 8192 8192 20971 +[MakerNotes, Pentax, Camera] 529 - WB RGGB Levels Fluorescent D: 17376 8192 8192 8847 +[MakerNotes, Pentax, Camera] 530 - WB RGGB Levels Fluorescent N: 14528 8192 8192 10076 +[MakerNotes, Pentax, Camera] 531 - WB RGGB Levels Fluorescent W: 13088 8192 8192 12206 +[MakerNotes, Pentax, Camera] 532 - WB RGGB Levels Flash: 13632 8192 8192 8601 +[MakerNotes, Pentax, Camera] 0 - Pentax Model ID: K10D +[MakerNotes, Pentax, Time] 1 - Manufacture Date: 2007:09:13 +[MakerNotes, Pentax, Camera] 2 - Production Code: 2.1 +[MakerNotes, Pentax, Camera] 4 - Internal Serial Number: 132352 +[MakerNotes, Pentax, Camera] 0.1 - Power Source: Body Battery +[MakerNotes, Pentax, Camera] 1.1 - Body Battery State: Full +[MakerNotes, Pentax, Camera] 1.2 - Grip Battery State: Empty or Missing +[MakerNotes, Pentax, Camera] 2 - Body Battery A/D No Load: 173 (7.6V, 51%) +[MakerNotes, Pentax, Camera] 3 - Body Battery A/D Load: 168 (7.4V, 47%) +[MakerNotes, Pentax, Camera] 4 - Grip Battery A/D No Load: 5 +[MakerNotes, Pentax, Camera] 5 - Grip Battery A/D Load: 1 +[MakerNotes, Pentax, Camera] 4 - AF Predictor: 4 +[MakerNotes, Pentax, Camera] 6 - AF Defocus: 2 +[MakerNotes, Pentax, Camera] 7 - AF Integration Time: 0 ms +[MakerNotes, Pentax, Camera] 11 - AF Points In Focus: Center (horizontal) +[MakerNotes, Pentax, Image] 16 - WB Shift AB: 0 +[MakerNotes, Pentax, Image] 17 - WB Shift GM: 0 +[MakerNotes, Pentax, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0300 +[Composite, Composite, Image] Exif-Aperture - Aperture: 13.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Sigma AF 10-20mm F4-5.6 EX DC +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/100 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.020 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 100.4 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 10.0 mm (35 mm equivalent: 15.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 0.38 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.0 diff --git a/ExifTool/t/Pentax_3.out b/ExifTool/t/Pentax_3.out new file mode 100644 index 0000000..34f0ab9 --- /dev/null +++ b/ExifTool/t/Pentax_3.out @@ -0,0 +1,266 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Pentax_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 2.6 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:46-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:46-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[JFIF, JFIF, Image] 0 - JFIF Version: 1.01 +[JFIF, JFIF, Image] 2 - Resolution Unit: None +[JFIF, JFIF, Image] 3 - X Resolution: 1 +[JFIF, JFIF, Image] 5 - Y Resolution: 1 +[EXIF, IFD0, Camera] 271 - Make: PENTAX Corporation +[EXIF, IFD0, Camera] 272 - Camera Model Name: PENTAX K10D +[EXIF, IFD0, Image] 274 - Orientation: Rotate 270 CW +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: K10D Ver 1.30 +[EXIF, IFD0, Time] 306 - Modify Date: 2008:03:02 12:01:23 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/100 +[EXIF, ExifIFD, Image] 33437 - F Number: 13.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2008:03:02 12:01:23 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2008:03:02 12:01:23 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: +0.7 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 22.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3008 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2000 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 15 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Distant +[MakerNotes, Pentax, Camera] 0 - Pentax Version: 3.0.0.0 +[MakerNotes, Pentax, Camera] 1 - Pentax Model Type: 0 +[MakerNotes, Pentax, Image] 2 - Preview Image Size: 640x480 +[MakerNotes, Pentax, Image] 3 - Preview Image Length: 26 +[MakerNotes, Pentax, Image] 4 - Preview Image Start: 2324 +[MakerNotes, Pentax, Camera] 5 - Pentax Model ID: K10D +[MakerNotes, Pentax, Time] 6 - Date: 2008:03:02 +[MakerNotes, Pentax, Time] 7 - Time: 12:01:23 +[MakerNotes, Pentax, Camera] 8 - Quality: Better +[MakerNotes, Pentax, Camera] 12 - Flash Mode: Off, Did not fire; Internal +[MakerNotes, Pentax, Camera] 13 - Focus Mode: AF-S (Focus-priority) +[MakerNotes, Pentax, Camera] 14 - AF Point Selected: Center +[MakerNotes, Pentax, Camera] 18 - Exposure Time: 1/100 +[MakerNotes, Pentax, Camera] 19 - F Number: 13.0 +[MakerNotes, Pentax, Camera] 20 - ISO: 100 +[MakerNotes, Pentax, Camera] 22 - Exposure Compensation: +0.7 +[MakerNotes, Pentax, Camera] 23 - Metering Mode: Multi-segment +[MakerNotes, Pentax, Camera] 24 - Auto Bracketing: 0 EV, No Extended Bracket +[MakerNotes, Pentax, Camera] 25 - White Balance: Tungsten +[MakerNotes, Pentax, Camera] 26 - White Balance Mode: Auto (Daylight) +[MakerNotes, Pentax, Camera] 29 - Focal Length: 22.0 mm +[MakerNotes, Pentax, Camera] 31 - Saturation: 0 (normal) +[MakerNotes, Pentax, Camera] 32 - Contrast: 0 (normal) +[MakerNotes, Pentax, Camera] 33 - Sharpness: 0 (normal) +[MakerNotes, Pentax, Time] 34 - World Time Location: Hometown +[MakerNotes, Pentax, Time] 35 - Hometown City: Toronto +[MakerNotes, Pentax, Time] 36 - Destination City: New York +[MakerNotes, Pentax, Time] 37 - Hometown DST: No +[MakerNotes, Pentax, Time] 38 - Destination DST: No +[MakerNotes, Pentax, Camera] 39 - DSP Firmware Version: 1.30.00.18 +[MakerNotes, Pentax, Camera] 40 - CPU Firmware Version: 1.30.00.18 +[MakerNotes, Pentax, Camera] 45 - Effective LV: 14.8 +[MakerNotes, Pentax, Camera] 50 - Image Editing: None +[MakerNotes, Pentax, Camera] 51 - Picture Mode: Aperture Priority; 1/3 EV steps +[MakerNotes, Pentax, Camera] 52 - Drive Mode: Single-frame; No Timer; Shutter Button; Single Exposure +[MakerNotes, Pentax, Camera] 55 - Color Space: sRGB +[MakerNotes, Pentax, Camera] 61 - Data Scaling: 8192 +[MakerNotes, Pentax, Camera] 62 - Preview Image Borders: 28 28 0 0 +[MakerNotes, Pentax, Camera] 0 - Lens Type: Sigma or Tamron Lens (3 44) +[MakerNotes, Pentax, Camera] 64 - Sensitivity Adjust: 0 +[MakerNotes, Pentax, Camera] 65 - Image Edit Count: 0 +[MakerNotes, Pentax, Camera] 71 - Camera Temperature: 12 C +[MakerNotes, Pentax, Camera] 72 - AE Lock: Off +[MakerNotes, Pentax, Camera] 73 - Noise Reduction: Off +[MakerNotes, Pentax, Camera] 77 - Flash Exposure Comp: 0 +[MakerNotes, Pentax, Camera] 79 - Image Tone: Natural +[MakerNotes, Pentax, Camera] 0 - SR Result: Stabilized +[MakerNotes, Pentax, Camera] 1 - Shake Reduction: On +[MakerNotes, Pentax, Camera] 2 - SR Half Press Time: 1.53 s +[MakerNotes, Pentax, Camera] 3 - SR Focal Length: 10 mm +[MakerNotes, Pentax, Camera] 93 - Shutter Count: 1648 +[MakerNotes, Pentax, Camera] 98 - Raw Development Process: 1 (K10D,K200D,K2000,K-m) +[MakerNotes, Pentax, Camera] 512 - Black Point: 0 0 0 0 +[MakerNotes, Pentax, Camera] 513 - White Point: 13568 8192 8192 9088 +[MakerNotes, Pentax, Camera] 0 - Picture Mode 2: Aperture Priority +[MakerNotes, Pentax, Camera] 1.1 - Program Line: Normal +[MakerNotes, Pentax, Camera] 1.2 - EV Steps: 1/3 EV Steps +[MakerNotes, Pentax, Camera] 1.3 - E-Dial In Program: Tv or Av +[MakerNotes, Pentax, Camera] 1.4 - Aperture Ring Use: Permitted +[MakerNotes, Pentax, Camera] 2 - Flash Options: Wireless (Master) +[MakerNotes, Pentax, Camera] 2.1 - Metering Mode 2: Multi-segment +[MakerNotes, Pentax, Camera] 3 - AF Point Mode: Select +[MakerNotes, Pentax, Camera] 3.1 - Focus Mode 2: AF-S +[MakerNotes, Pentax, Camera] 4 - AF Point Selected 2: Center +[MakerNotes, Pentax, Camera] 6 - ISO Floor: 100 +[MakerNotes, Pentax, Camera] 7 - Drive Mode 2: Single-frame +[MakerNotes, Pentax, Camera] 8 - Exposure Bracket Step Size: 0.3 +[MakerNotes, Pentax, Camera] 9 - Bracket Shot Number: n/a +[MakerNotes, Pentax, Camera] 10 - White Balance Set: Auto +[MakerNotes, Pentax, Camera] 10.1 - Multiple Exposure Set: Off +[MakerNotes, Pentax, Camera] 13 - Raw And Jpg Recording: RAW+JPEG (PEF, Better) +[MakerNotes, Pentax, Camera] 14.1 - Jpg Recorded Pixels: 6 MP +[MakerNotes, Pentax, Camera] 16 - Flash Options 2: Wireless (Master) +[MakerNotes, Pentax, Camera] 16.1 - Metering Mode 3: Multi-segment +[MakerNotes, Pentax, Camera] 17.1 - SR Active: Yes +[MakerNotes, Pentax, Camera] 17.2 - Rotation: Rotate 270 CW +[MakerNotes, Pentax, Camera] 17.3 - ISO Setting: Manual +[MakerNotes, Pentax, Camera] 17.4 - Sensitivity Steps: 1 EV Steps +[MakerNotes, Pentax, Camera] 18 - Tv Exposure Time Setting: 1/203 +[MakerNotes, Pentax, Camera] 19 - Av Aperture Setting: 12.7 +[MakerNotes, Pentax, Camera] 20 - Sv ISO Setting: 100 +[MakerNotes, Pentax, Camera] 21 - Base Exposure Compensation: +0.7 +[MakerNotes, Pentax, Camera] 0 - AE Exposure Time: 1/101 +[MakerNotes, Pentax, Camera] 1 - AE Aperture: 12.9 +[MakerNotes, Pentax, Camera] 2 - AE ISO: 100 +[MakerNotes, Pentax, Camera] 3 - AE Xv: -0.625 +[MakerNotes, Pentax, Camera] 4 - AEB Xv: 0 +[MakerNotes, Pentax, Camera] 5 - AE Min Exposure Time: 1/3862 +[MakerNotes, Pentax, Camera] 6 - AE Program Mode: Av, B or X +[MakerNotes, Pentax, Camera] 7 - AE Flags: (none) +[MakerNotes, Pentax, Camera] 8 - AE Aperture Steps: 28 +[MakerNotes, Pentax, Camera] 9 - AE Max Aperture: 4.0 +[MakerNotes, Pentax, Camera] 10 - AE Max Aperture 2: 4.0 +[MakerNotes, Pentax, Camera] 11 - AE Min Aperture: 23 +[MakerNotes, Pentax, Camera] 12 - AE Metering Mode: Multi-segment +[MakerNotes, Pentax, Camera] 14 - Flash Exposure Comp. Setting: 0 +[MakerNotes, Pentax, Camera] 0 - Lens Type: Sigma or Tamron Lens (3 44) +[MakerNotes, Pentax, Camera] 0.1 - Auto Aperture: On +[MakerNotes, Pentax, Camera] 0.2 - Min Aperture: 22 +[MakerNotes, Pentax, Camera] 0.3 - Lens F Stops: 8.5 +[MakerNotes, Pentax, Camera] 1 - Lens Kind: 0x28 +[MakerNotes, Pentax, Camera] 2 - LC1: 0x94 +[MakerNotes, Pentax, Camera] 3 - Min Focus Distance: 0.49-0.50 m +[MakerNotes, Pentax, Camera] 3.1 - Focus Range Index: 7 (very far) +[MakerNotes, Pentax, Camera] 4 - LC3: 0x5b +[MakerNotes, Pentax, Camera] 5 - LC4: 0x53 +[MakerNotes, Pentax, Camera] 6 - LC5: 0x86 +[MakerNotes, Pentax, Camera] 7 - LC6: 0xea +[MakerNotes, Pentax, Camera] 8 - LC7: 0x41 +[MakerNotes, Pentax, Camera] 9 - Lens Focal Length: 10.0 mm +[MakerNotes, Pentax, Camera] 10 - Nominal Max Aperture: 4.0 +[MakerNotes, Pentax, Camera] 10.1 - Nominal Min Aperture: 23 +[MakerNotes, Pentax, Camera] 11 - LC10: 0x50 +[MakerNotes, Pentax, Camera] 12 - LC11: 0x38 +[MakerNotes, Pentax, Camera] 13 - LC12: 0x01 +[MakerNotes, Pentax, Camera] 14.1 - Max Aperture: 2.0 +[MakerNotes, Pentax, Camera] 15 - LC14: 0x6c +[MakerNotes, Pentax, Camera] 16 - LC15: 0x03 +[MakerNotes, Pentax, Camera] 0 - Flash Status: Off +[MakerNotes, Pentax, Camera] 1 - Internal Flash Mode: Did not fire, Wireless (Master) +[MakerNotes, Pentax, Camera] 2 - External Flash Mode: Off +[MakerNotes, Pentax, Camera] 3 - Internal Flash Strength: 18 +[MakerNotes, Pentax, Camera] 4 - TTL DA A Up: 0 +[MakerNotes, Pentax, Camera] 5 - TTL DA A Down: 0 +[MakerNotes, Pentax, Camera] 6 - TTL DA B Up: 0 +[MakerNotes, Pentax, Camera] 7 - TTL DA B Down: 0 +[MakerNotes, Pentax, Camera] 24.1 - External Flash Guide Number: n/a +[MakerNotes, Pentax, Camera] 25 - External Flash Exposure Comp: n/a +[MakerNotes, Pentax, Camera] 26 - External Flash Bounce: n/a +[MakerNotes, Pentax, Camera] 521 - AE Metering Segments: 13.5 13.6 13.5 13.2 13.6 15.0 13.8 12.9 13.8 16.4 14.4 13.9 14.4 15.8 14.0 16.0 +[MakerNotes, Pentax, Camera] 522 - Flash Metering Segments: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Pentax, Camera] 523 - Slave Flash Metering Segments: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Pentax, Camera] 525 - WB RGGB Levels Daylight: 13600 8192 8192 8765 +[MakerNotes, Pentax, Camera] 526 - WB RGGB Levels Shade: 16128 8192 8192 6635 +[MakerNotes, Pentax, Camera] 527 - WB RGGB Levels Cloudy: 14560 8192 8192 7782 +[MakerNotes, Pentax, Camera] 528 - WB RGGB Levels Tungsten: 8192 8192 8192 20971 +[MakerNotes, Pentax, Camera] 529 - WB RGGB Levels Fluorescent D: 17376 8192 8192 8847 +[MakerNotes, Pentax, Camera] 530 - WB RGGB Levels Fluorescent N: 14528 8192 8192 10076 +[MakerNotes, Pentax, Camera] 531 - WB RGGB Levels Fluorescent W: 13088 8192 8192 12206 +[MakerNotes, Pentax, Camera] 532 - WB RGGB Levels Flash: 13632 8192 8192 8601 +[MakerNotes, Pentax, Camera] 0 - Pentax Model ID: K10D +[MakerNotes, Pentax, Time] 1 - Manufacture Date: 2007:09:13 +[MakerNotes, Pentax, Camera] 2 - Production Code: 2.1 +[MakerNotes, Pentax, Camera] 4 - Internal Serial Number: 132352 +[MakerNotes, Pentax, Camera] 0.1 - Power Source: Body Battery +[MakerNotes, Pentax, Camera] 1.1 - Body Battery State: Full +[MakerNotes, Pentax, Camera] 1.2 - Grip Battery State: Empty or Missing +[MakerNotes, Pentax, Camera] 2 - Body Battery A/D No Load: 173 (7.6V, 51%) +[MakerNotes, Pentax, Camera] 3 - Body Battery A/D Load: 168 (7.4V, 47%) +[MakerNotes, Pentax, Camera] 4 - Grip Battery A/D No Load: 5 +[MakerNotes, Pentax, Camera] 5 - Grip Battery A/D Load: 1 +[MakerNotes, Pentax, Camera] 0 - AF Points Unknown 1: Center +[MakerNotes, Pentax, Camera] 2 - AF Points Unknown 2: Center, [13], [14] +[MakerNotes, Pentax, Camera] 4 - AF Predictor: 4 +[MakerNotes, Pentax, Camera] 6 - AF Defocus: 2 +[MakerNotes, Pentax, Camera] 7 - AF Integration Time: 0 ms +[MakerNotes, Pentax, Camera] 11 - AF Points In Focus: Center (horizontal) +[MakerNotes, Pentax, Image] 16 - WB Shift AB: 0 +[MakerNotes, Pentax, Image] 17 - WB Shift GM: 0 +[MakerNotes, Pentax, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0300 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00160016 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x01000000 +[PrintIM, PrintIM, Printing] 3 - Print IM 0x0003: 0x000000d6 +[PrintIM, PrintIM, Printing] 7 - Print IM 0x0007: 0x00000000 +[PrintIM, PrintIM, Printing] 8 - Print IM 0x0008: 0x00000000 +[PrintIM, PrintIM, Printing] 9 - Print IM 0x0009: 0x00000000 +[PrintIM, PrintIM, Printing] 10 - Print IM 0x000a: 0x00000000 +[PrintIM, PrintIM, Printing] 11 - Print IM 0x000b: 0x0000011e +[PrintIM, PrintIM, Printing] 12 - Print IM 0x000c: 0x00000000 +[PrintIM, PrintIM, Printing] 13 - Print IM 0x000d: 0x00000000 +[PrintIM, PrintIM, Printing] 14 - Print IM 0x000e: 0x00000134 +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x05000000 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0xff000000 +[PrintIM, PrintIM, Printing] 258 - Print IM 0x0102: 0x83000000 +[PrintIM, PrintIM, Printing] 259 - Print IM 0x0103: 0x83000000 +[PrintIM, PrintIM, Printing] 260 - Print IM 0x0104: 0x83000000 +[PrintIM, PrintIM, Printing] 261 - Print IM 0x0105: 0x83000000 +[PrintIM, PrintIM, Printing] 262 - Print IM 0x0106: 0x83000000 +[PrintIM, PrintIM, Printing] 263 - Print IM 0x0107: 0x80808000 +[PrintIM, PrintIM, Printing] 272 - Print IM 0x0110: 0x80000000 +[PrintIM, PrintIM, Printing] 512 - Print IM 0x0200: 0x00000000 +[PrintIM, PrintIM, Printing] 519 - Print IM 0x0207: 0x00000000 +[PrintIM, PrintIM, Printing] 520 - Print IM 0x0208: 0x00000000 +[PrintIM, PrintIM, Printing] 521 - Print IM 0x0209: 0x00000000 +[PrintIM, PrintIM, Printing] 522 - Print IM 0x020a: 0x00000000 +[PrintIM, PrintIM, Printing] 523 - Print IM 0x020b: 0x00000144 +[PrintIM, PrintIM, Printing] 525 - Print IM 0x020d: 0x00000000 +[PrintIM, PrintIM, Printing] 768 - Print IM 0x0300: 0x05000000 +[PrintIM, PrintIM, Printing] 769 - Print IM 0x0301: 0xff000000 +[PrintIM, PrintIM, Printing] 770 - Print IM 0x0302: 0x83000000 +[PrintIM, PrintIM, Printing] 771 - Print IM 0x0303: 0x83000000 +[PrintIM, PrintIM, Printing] 774 - Print IM 0x0306: 0x83000000 +[PrintIM, PrintIM, Printing] 784 - Print IM 0x0310: 0x80000000 +[Composite, Composite, Image] Exif-Aperture - Aperture: 13.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/100 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Sigma or Tamron Lens (3 44) + 2.2x converter +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.0 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 0.7 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.044 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 100.4 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 22.0 mm (35 mm equivalent: 15.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 0.84 m diff --git a/ExifTool/t/Pentax_4.out b/ExifTool/t/Pentax_4.out new file mode 100644 index 0000000..767b875 --- /dev/null +++ b/ExifTool/t/Pentax_4.out @@ -0,0 +1,132 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Pentax.avi +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1672 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2009:10:30 08:10:17-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:15-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:01:05 09:05:38-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: AVI +[File, File, Other] FileTypeExtension - File Type Extension: avi +[File, File, Other] MIMEType - MIME Type: video/x-msvideo +[File, File, Image] 0 - BMP Version: Windows V3 +[File, File, Image] 4 - Image Width: 1280 +[File, File, Image] 8 - Image Height: 720 +[File, File, Image] 12 - Planes: 1 +[File, File, Image] 14 - Bit Depth: 24 +[File, File, Image] 16 - Compression: MJPG +[File, File, Image] 20 - Image Length: 2764800 +[File, File, Image] 24 - Pixels Per Meter X: 0 +[File, File, Image] 28 - Pixels Per Meter Y: 0 +[File, File, Image] 32 - Num Colors: Use BitDepth +[File, File, Image] 36 - Num Important Colors: All +[RIFF, RIFF, Video] 0 - Frame Rate: 24 +[RIFF, RIFF, Video] 1 - Max Data Rate: 0 kB/s +[RIFF, RIFF, Video] 4 - Frame Count: 600 +[RIFF, RIFF, Video] 6 - Stream Count: 2 +[RIFF, RIFF, Video] 8 - Image Width: 1280 +[RIFF, RIFF, Video] 9 - Image Height: 720 +[RIFF, RIFF, Video] 0 - Stream Type: Video +[RIFF, RIFF, Video] 1 - Video Codec: mjpg +[RIFF, RIFF, Video] 5 - Video Frame Rate: 24 +[RIFF, RIFF, Video] 8 - Video Frame Count: 600 +[RIFF, RIFF, Video] 10 - Quality: 10000 +[RIFF, RIFF, Video] 11 - Sample Size: Variable +[RIFF, RIFF, Video] 0 - Stream Type: Audio +[RIFF, RIFF, Video] 1 - Audio Codec: +[RIFF, RIFF, Video] 5 - Audio Sample Rate: 32000 +[RIFF, RIFF, Video] 8 - Audio Sample Count: 800000 +[RIFF, RIFF, Video] 10 - Quality: 10000 +[RIFF, RIFF, Video] 11 - Sample Size: 2 bytes +[RIFF, RIFF, Audio] 0 - Encoding: Microsoft PCM +[RIFF, RIFF, Audio] 1 - Num Channels: 1 +[RIFF, RIFF, Audio] 2 - Sample Rate: 32000 +[RIFF, RIFF, Audio] 4 - Avg Bytes Per Sec: 64000 +[RIFF, RIFF, Audio] 7 - Bits Per Sample: 16 +[RIFF, RIFF, Video] 0 - Total Frame Count: 600 +[RIFF, RIFF, Time] IDIT - Date/Time Original: 2009:10:27 12:14:00 +[RIFF, RIFF, Audio] ISFT - Software: PENTAX K-x +[MakerNotes, Pentax, Camera] 0 - Pentax Version: 5.1.0.0 +[MakerNotes, Pentax, Camera] 1 - Pentax Model Type: 0 +[MakerNotes, Pentax, Camera] 5 - Pentax Model ID: K-x +[MakerNotes, Pentax, Time] 6 - Date: 2009:10:27 +[MakerNotes, Pentax, Time] 7 - Time: 12:14:34 +[MakerNotes, Pentax, Camera] 8 - Quality: Best +[MakerNotes, Pentax, Camera] 19 - F Number: 0.0 +[MakerNotes, Pentax, Camera] 22 - Exposure Compensation: 0 +[MakerNotes, Pentax, Camera] 25 - White Balance: Flash +[MakerNotes, Pentax, Camera] 31 - Saturation: 0 (normal) +[MakerNotes, Pentax, Camera] 32 - Contrast: 0 (normal) +[MakerNotes, Pentax, Camera] 33 - Sharpness: -1 (medium soft) +[MakerNotes, Pentax, Camera] 39 - DSP Firmware Version: 1.00.00.01 +[MakerNotes, Pentax, Camera] 40 - CPU Firmware Version: 1.00.00.01 +[MakerNotes, Pentax, Camera] 51 - Picture Mode: Video (4); 1/3 EV steps +[MakerNotes, Pentax, Camera] 52 - Drive Mode: Video; n/a; Shutter Button; Video +[MakerNotes, Pentax, Camera] 0 - Lens Type: smc PENTAX-DA L 18-55mm F3.5-5.6 +[MakerNotes, Pentax, Camera] 3 - Extender Status: Not attached +[MakerNotes, Pentax, Camera] 79 - Image Tone: Natural +[MakerNotes, Pentax, Camera] 0 - SR Result: Stabilized, [1], [2], [3], [4] +[MakerNotes, Pentax, Camera] 1 - Shake Reduction: On (7) +[MakerNotes, Pentax, Camera] 2 - SR Half Press Time: 0.00 s +[MakerNotes, Pentax, Camera] 3 - SR Focal Length: 23 mm +[MakerNotes, Pentax, Camera] 103 - Hue: Normal +[MakerNotes, Pentax, Camera] 108 - High/Low Key Adj: 0 +[MakerNotes, Pentax, Camera] 115 - Monochrome Filter Effect: None +[MakerNotes, Pentax, Camera] 116 - Monochrome Toning: None +[MakerNotes, Pentax, Camera] 123 - Cross Process: Off +[MakerNotes, Pentax, Camera] 0 - Picture Mode 2: Scene Mode +[MakerNotes, Pentax, Camera] 1.1 - Program Line: Normal +[MakerNotes, Pentax, Camera] 1.2 - EV Steps: 1/3 EV Steps +[MakerNotes, Pentax, Camera] 1.3 - E-Dial In Program: P Shift +[MakerNotes, Pentax, Camera] 1.4 - Aperture Ring Use: Prohibited +[MakerNotes, Pentax, Camera] 2 - Flash Options: Normal +[MakerNotes, Pentax, Camera] 2.1 - Metering Mode 2: Multi-segment +[MakerNotes, Pentax, Camera] 3 - AF Point Mode: Fixed Center +[MakerNotes, Pentax, Camera] 3.1 - Focus Mode 2: AF-S +[MakerNotes, Pentax, Camera] 4 - AF Point Selected 2: Center +[MakerNotes, Pentax, Camera] 6 - ISO Floor: 100 +[MakerNotes, Pentax, Camera] 7 - Drive Mode 2: Single-frame +[MakerNotes, Pentax, Camera] 8 - Exposure Bracket Step Size: 0.3 +[MakerNotes, Pentax, Camera] 9 - Bracket Shot Number: n/a +[MakerNotes, Pentax, Camera] 10 - White Balance Set: Auto +[MakerNotes, Pentax, Camera] 10.1 - Multiple Exposure Set: Off +[MakerNotes, Pentax, Camera] 0 - AE Exposure Time: 1/55 +[MakerNotes, Pentax, Camera] 1 - AE Aperture: 3.8 +[MakerNotes, Pentax, Camera] 2 - AE ISO: 519 +[MakerNotes, Pentax, Camera] 3 - AE Xv: 0 +[MakerNotes, Pentax, Camera] 4 - AEB Xv: 0 +[MakerNotes, Pentax, Camera] 5 - AE Min Exposure Time: 1/5461 +[MakerNotes, Pentax, Camera] 6 - AE Program Mode: Standard +[MakerNotes, Pentax, Camera] 8 - AE Aperture Steps: 0 +[MakerNotes, Pentax, Camera] 9 - AE Max Aperture: 4.0 +[MakerNotes, Pentax, Camera] 10 - AE Max Aperture 2: 3.8 +[MakerNotes, Pentax, Camera] 11 - AE Min Aperture: 27 +[MakerNotes, Pentax, Camera] 12 - AE Metering Mode: Multi-segment +[MakerNotes, Pentax, Camera] 13 - AE White Balance: Daylight Fluorescent +[MakerNotes, Pentax, Camera] 13.1 - AE Metering Mode 2: Multi-segment +[MakerNotes, Pentax, Camera] 14 - Flash Exposure Comp. Setting: -1.0 +[MakerNotes, Pentax, Camera] 21 - Level Indicator: n/a +[MakerNotes, Pentax, Camera] 0 - Lens Type: smc PENTAX-DA L 18-55mm F3.5-5.6 +[MakerNotes, Pentax, Camera] 0.1 - Auto Aperture: On +[MakerNotes, Pentax, Camera] 0.2 - Min Aperture: 32 +[MakerNotes, Pentax, Camera] 0.3 - Lens F Stops: 6 +[MakerNotes, Pentax, Camera] 3 - Min Focus Distance: 0.25-0.28 m +[MakerNotes, Pentax, Camera] 3.1 - Focus Range Index: 6 (far) +[MakerNotes, Pentax, Camera] 9 - Lens Focal Length: 23.1 mm +[MakerNotes, Pentax, Camera] 10 - Nominal Max Aperture: 4.0 +[MakerNotes, Pentax, Camera] 10.1 - Nominal Min Aperture: 27 +[MakerNotes, Pentax, Camera] 14.1 - Max Aperture: 3.7 +[MakerNotes, Pentax, Camera] 0 - Pentax Model ID: K-x +[MakerNotes, Pentax, Time] 1 - Manufacture Date: 2009:09:04 +[MakerNotes, Pentax, Camera] 2 - Production Code: 2.3 +[MakerNotes, Pentax, Camera] 4 - Internal Serial Number: 8007725 +[MakerNotes, Pentax, Image] 16 - WB Shift AB: 0 +[MakerNotes, Pentax, Image] 17 - WB Shift GM: 0 +[MakerNotes, Pentax, Camera] 553 - Serial Number: 3430417 +[MakerNotes, Pentax, Author] 558 - Artist: +[MakerNotes, Pentax, Author] 559 - Copyright: +[MakerNotes, Pentax, Camera] 560 - Firmware Version: K-x Ver 1.00 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 1280x720 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.922 +[Composite, Composite, Other] RIFF-Duration - Duration: 25.00 s +[Composite, Composite, Camera] Exif-LensID - Lens ID: smc PENTAX-DA L 18-55mm F3.5-5.6 diff --git a/ExifTool/t/PhaseOne.t b/ExifTool/t/PhaseOne.t new file mode 100644 index 0000000..2a2ba18 --- /dev/null +++ b/ExifTool/t/PhaseOne.t @@ -0,0 +1,38 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PhaseOne.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PhaseOne; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PhaseOne'; +my $testnum = 1; + +# test 2: Extract information from PhaseOne.iiq +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/PhaseOne.iiq'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + ['SerialNumber' => '1234'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/PhaseOne.iiq'); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PhaseOne_2.out b/ExifTool/t/PhaseOne_2.out new file mode 100644 index 0000000..9b2d61c --- /dev/null +++ b/ExifTool/t/PhaseOne_2.out @@ -0,0 +1,100 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: PhaseOne.iiq +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 3.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2015:12:17 14:25:00-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:47-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: IIQ +[File, File, Other] FileTypeExtension - File Type Extension: iiq +[File, File, Other] MIMEType - MIME Type: image/x-raw +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[EXIF, IFD0, Image] 254 - Subfile Type: Full-resolution image +[EXIF, IFD0, Image] 256 - Image Width: 1 +[EXIF, IFD0, Image] 257 - Image Height: 1 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 259 - Compression: Uncompressed +[EXIF, IFD0, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD0, Camera] 271 - Make: Leaf +[EXIF, IFD0, Camera] 272 - Camera Model Name: Credo 40 +[EXIF, IFD0, Image] 273 - Strip Offsets: 3360 +[EXIF, IFD0, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD0, Image] 278 - Rows Per Strip: 1 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 3 +[EXIF, IFD0, Image] 282 - X Resolution: 341.3333333 +[EXIF, IFD0, Image] 283 - Y Resolution: 341.3333333 +[EXIF, IFD0, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, ExifIFD, Image] 18246 - Rating: 0 +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/1250 +[EXIF, ExifIFD, Image] 33437 - F Number: 5.6 +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2013:06:19 15:44:21 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2013:06:19 15:44:21 +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/1250 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 5.6 +[EXIF, ExifIFD, Camera] 37384 - Light Source: Daylight +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 80.0 mm +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 7320 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 5484 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Camera] 41987 - White Balance: Manual +[EXIF, ExifIFD, Image] 42016 - Image Unique ID: 00004C20007000000400E05800000508 +[EXIF, IFD1, Image] 254 - Subfile Type: Reduced-resolution image +[EXIF, IFD1, Image] 256 - Image Width: 1 +[EXIF, IFD1, Image] 257 - Image Height: 1 +[EXIF, IFD1, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD1, Image] 259 - Compression: Uncompressed +[EXIF, IFD1, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD1, Image] 273 - Strip Offsets: 3356 +[EXIF, IFD1, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD1, Image] 278 - Rows Per Strip: 1 +[EXIF, IFD1, Image] 279 - Strip Byte Counts: 3 +[EXIF, IFD1, Image] 282 - X Resolution: 341.3333333 +[EXIF, IFD1, Image] 283 - Y Resolution: 341.3333333 +[EXIF, IFD1, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Preview] Exif-ThumbnailTIFF - Thumbnail TIFF: (Binary data 219 bytes) +[MakerNotes, PhaseOne, Camera] 271 - Raw Data: (Binary data 15 bytes) +[MakerNotes, PhaseOne, Camera] 256 - Camera Orientation: Horizontal (normal) +[MakerNotes, PhaseOne, Camera] 264 - Sensor Width: 7372 +[MakerNotes, PhaseOne, Camera] 265 - Sensor Height: 5536 +[MakerNotes, PhaseOne, Camera] 266 - Sensor Left Margin: 36 +[MakerNotes, PhaseOne, Camera] 267 - Sensor Top Margin: 6 +[MakerNotes, PhaseOne, Camera] 268 - Image Width: 7320 +[MakerNotes, PhaseOne, Camera] 269 - Image Height: 5484 +[MakerNotes, PhaseOne, Camera] 270 - Raw Format: IIQ L +[MakerNotes, PhaseOne, Camera] 275 - Image Number: 1288 +[MakerNotes, PhaseOne, Camera] 261 - ISO: 100 +[MakerNotes, PhaseOne, Camera] 528 - Sensor Temperature: 37.00 C +[MakerNotes, PhaseOne, Camera] 529 - Sensor Temperature 2: 37.00 C +[MakerNotes, PhaseOne, Time] 274 - Date/Time Original: 2013:06:19 15:44:21 +[MakerNotes, PhaseOne, Camera] 541 - Black Level: 1024 +[MakerNotes, PhaseOne, Camera] 263 - WB RGB Levels: 1.30548012256622 1 1.41912007331848 +[MakerNotes, PhaseOne, Camera] 262 - Color Matrix 1: 1.280 -0.280 0.000 -0.050 1.180 -0.130 0.000 -0.250 1.250 +[MakerNotes, PhaseOne, Camera] 550 - Color Matrix 2: 1.950 -0.950 0.000 -0.250 1.750 -0.500 0.000 -0.950 1.950 +[MakerNotes, PhaseOne, Camera] 1042 - Lens Model: Mamiya LS 80mm f/2.8 D +[MakerNotes, PhaseOne, Camera] 540 - Strip Offsets: (Binary data 54 bytes) +[MakerNotes, PhaseOne, Camera] 547 - Black Level Data: (Binary data 65 bytes) +[MakerNotes, PhaseOne, Camera] 546 - Split Column: 3696 +[MakerNotes, PhaseOne, Camera] 769 - Firmware Versions: Credo 40, Factory Firmware: 1.01.4 +[MakerNotes, PhaseOne, Camera] 515 - Software: Camera back, Firmware: 1.01.4 +[MakerNotes, PhaseOne, Camera] 516 - System: Digital Camera Back +[MakerNotes, PhaseOne, Camera] 258 - Serial Number: LD001055 +[MakerNotes, PhaseOne, Camera] 1024 - Shutter Speed Value: 1/1250 +[MakerNotes, PhaseOne, Camera] 1025 - Aperture Value: 5.6 +[MakerNotes, PhaseOne, Camera] 1027 - Focal Length: 80.0 mm +[MakerNotes, PhaseOne, Camera] 1031 - Serial Number: LD001055 +[MakerNotes, PhaseOne, Camera] 1024 - Sensor Defects: (Binary data 12 bytes) +[Composite, Composite, Image] Exif-Aperture - Aperture: 5.6 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.41912 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 7320x5484 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 40.1 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.30548 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/1250 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 80.0 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 15.3 +[Composite, Composite, Camera] Exif-LensID-2 - Lens ID: Mamiya LS 80mm f/2.8 D diff --git a/ExifTool/t/PhaseOne_3.out b/ExifTool/t/PhaseOne_3.out new file mode 100644 index 0000000..159123a --- /dev/null +++ b/ExifTool/t/PhaseOne_3.out @@ -0,0 +1,171 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: PhaseOne_3_failed.iiq +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:47-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:47-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:47-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: IIQ +[File, File, Other] FileTypeExtension - File Type Extension: iiq +[File, File, Other] MIMEType - MIME Type: image/x-raw +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[EXIF, IFD0, Image] 254 - Subfile Type: Full-resolution image +[EXIF, IFD0, Image] 256 - Image Width: 1 +[EXIF, IFD0, Image] 257 - Image Height: 1 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 259 - Compression: Uncompressed +[EXIF, IFD0, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD0, Camera] 271 - Make: Leaf +[EXIF, IFD0, Camera] 272 - Camera Model Name: Credo 40 +[EXIF, IFD0, Image] 273 - Strip Offsets: 3370 +[EXIF, IFD0, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD0, Image] 278 - Rows Per Strip: 1 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 3 +[EXIF, IFD0, Image] 282 - X Resolution: 341.3333333 +[EXIF, IFD0, Image] 283 - Y Resolution: 341.3333333 +[EXIF, IFD0, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, ExifIFD, Image] 18246 - Rating: 0 +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/1250 +[EXIF, ExifIFD, Image] 33437 - F Number: 5.6 +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2013:06:19 15:44:21 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2013:06:19 15:44:21 +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/1250 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 5.6 +[EXIF, ExifIFD, Camera] 37384 - Light Source: Daylight +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 80.0 mm +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 7320 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 5484 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Camera] 41987 - White Balance: Manual +[EXIF, ExifIFD, Image] 42016 - Image Unique ID: 00004C20007000000400E05800000508 +[EXIF, ExifIFD, Image] 42033 - Serial Number: 1234 +[EXIF, IFD1, Image] 254 - Subfile Type: Reduced-resolution image +[EXIF, IFD1, Image] 256 - Image Width: 1 +[EXIF, IFD1, Image] 257 - Image Height: 1 +[EXIF, IFD1, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD1, Image] 259 - Compression: Uncompressed +[EXIF, IFD1, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD1, Image] 273 - Strip Offsets: 3366 +[EXIF, IFD1, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD1, Image] 278 - Rows Per Strip: 1 +[EXIF, IFD1, Image] 279 - Strip Byte Counts: 3 +[EXIF, IFD1, Image] 282 - X Resolution: 341.3333333 +[EXIF, IFD1, Image] 283 - Y Resolution: 341.3333333 +[EXIF, IFD1, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Preview] Exif-ThumbnailTIFF - Thumbnail TIFF: (Binary data 219 bytes) +[MakerNotes, PhaseOne, Camera] 271 - Raw Data: (Binary data 15 bytes) +[MakerNotes, PhaseOne, Camera] 256 - Camera Orientation: Horizontal (normal) +[MakerNotes, PhaseOne, Camera] 533 - Phase One 0x0215: 2 +[MakerNotes, PhaseOne, Camera] 264 - Sensor Width: 7372 +[MakerNotes, PhaseOne, Camera] 265 - Sensor Height: 5536 +[MakerNotes, PhaseOne, Camera] 266 - Sensor Left Margin: 36 +[MakerNotes, PhaseOne, Camera] 267 - Sensor Top Margin: 6 +[MakerNotes, PhaseOne, Camera] 268 - Image Width: 7320 +[MakerNotes, PhaseOne, Camera] 269 - Image Height: 5484 +[MakerNotes, PhaseOne, Camera] 270 - Raw Format: IIQ L +[MakerNotes, PhaseOne, Camera] 259 - Phase One 0x0103: 19 +[MakerNotes, PhaseOne, Camera] 275 - Image Number: 1288 +[MakerNotes, PhaseOne, Camera] 257 - Phase One 0x0101: 32 +[MakerNotes, PhaseOne, Camera] 260 - Phase One 0x0104: 50 +[MakerNotes, PhaseOne, Camera] 261 - ISO: 100 +[MakerNotes, PhaseOne, Camera] 523 - Phase One 0x020b: 0 +[MakerNotes, PhaseOne, Camera] 524 - Phase One 0x020c: 2 +[MakerNotes, PhaseOne, Camera] 581 - Phase One 0x0245: 1.20000004768372 +[MakerNotes, PhaseOne, Camera] 526 - Phase One 0x020e: 3 +[MakerNotes, PhaseOne, Camera] 528 - Sensor Temperature: 37.00 C +[MakerNotes, PhaseOne, Camera] 529 - Sensor Temperature 2: 37.00 C +[MakerNotes, PhaseOne, Time] 530 - Unknown Date: 2013:06:19 15:44:22 +[MakerNotes, PhaseOne, Camera] 531 - Phase One 0x0213: 32 +[MakerNotes, PhaseOne, Time] 274 - Date/Time Original: 2013:06:19 15:44:21 +[MakerNotes, PhaseOne, Camera] 542 - Phase One 0x021e: 1 +[MakerNotes, PhaseOne, Camera] 544 - Phase One 0x0220: 32 +[MakerNotes, PhaseOne, Camera] 545 - Phase One 0x0221: 0 +[MakerNotes, PhaseOne, Camera] 580 - Phase One 0x0244: 0 +[MakerNotes, PhaseOne, Camera] 554 - Phase One 0x022a: 0 +[MakerNotes, PhaseOne, Camera] 555 - Phase One 0x022b: 0 +[MakerNotes, PhaseOne, Camera] 556 - Phase One 0x022c: 0 +[MakerNotes, PhaseOne, Camera] 551 - Phase One 0x0227: 0 +[MakerNotes, PhaseOne, Camera] 553 - Phase One 0x0229: 0 +[MakerNotes, PhaseOne, Camera] 768 - Phase One 0x0300: 2097264 +[MakerNotes, PhaseOne, Camera] 772 - Phase One 0x0304: 4 +[MakerNotes, PhaseOne, Camera] 541 - Black Level: 1024 +[MakerNotes, PhaseOne, Camera] 263 - WB RGB Levels: 1.30548012256622 1 1.41912007331848 +[MakerNotes, PhaseOne, Camera] 262 - Color Matrix 1: 1.280 -0.280 0.000 -0.050 1.180 -0.130 0.000 -0.250 1.250 +[MakerNotes, PhaseOne, Camera] 550 - Color Matrix 2: 1.950 -0.950 0.000 -0.250 1.750 -0.500 0.000 -0.950 1.950 +[MakerNotes, PhaseOne, Camera] 1042 - Lens Model: Mamiya LS 80mm f/2.8 D +[MakerNotes, PhaseOne, Camera] 540 - Strip Offsets: (Binary data 54 bytes) +[MakerNotes, PhaseOne, Camera] 547 - Black Level Data: (Binary data 65 bytes) +[MakerNotes, PhaseOne, Camera] 546 - Split Column: 3696 +[MakerNotes, PhaseOne, Camera] 549 - Phase One 0x0225: 25660 28021 31085 25632 29793 15969 +[MakerNotes, PhaseOne, Camera] 548 - Phase One 0x0224: 2748 +[MakerNotes, PhaseOne, Camera] 769 - Firmware Versions: Credo 40, Factory Firmware: 1.01.4 +[MakerNotes, PhaseOne, Camera] 515 - Software: Camera back, Firmware: 1.01.4 +[MakerNotes, PhaseOne, Camera] 516 - System: Digital Camera Back +[MakerNotes, PhaseOne, Camera] 258 - Serial Number: 1234 +[MakerNotes, PhaseOne, Camera] 1024 - Shutter Speed Value: 1/1250 +[MakerNotes, PhaseOne, Camera] 1025 - Aperture Value: 5.6 +[MakerNotes, PhaseOne, Camera] 1027 - Focal Length: 80.0 mm +[MakerNotes, PhaseOne, Camera] 578 - Phase One 0x0242: 1 +[MakerNotes, PhaseOne, Camera] 579 - Phase One 0x0243: 27 +[MakerNotes, PhaseOne, Camera] 1041 - Phase One 0x0411: 33556736 +[MakerNotes, PhaseOne, Camera] 1043 - Phase One 0x0413: 16909056 +[MakerNotes, PhaseOne, Camera] 1028 - Sensor Calibration 0x0404: IGNORE +[MakerNotes, PhaseOne, Camera] 1029 - Sensor Calibration 0x0405: IGNORE +[MakerNotes, PhaseOne, Camera] 1030 - Sensor Calibration 0x0406: IGNORE +[MakerNotes, PhaseOne, Camera] 1031 - Serial Number: 1234 +[MakerNotes, PhaseOne, Camera] 1026 - Sensor Calibration 0x0402: +[MakerNotes, PhaseOne, Camera] 1032 - Sensor Calibration 0x0408: -71.6800003051758 1.93874990940094 +[MakerNotes, PhaseOne, Camera] 1043 - Sensor Calibration 0x0413: 10 +[MakerNotes, PhaseOne, Camera] 1065 - Sensor Calibration 0x0429: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1066 - Sensor Calibration 0x042a: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1067 - Sensor Calibration 0x042b: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1068 - Sensor Calibration 0x042c: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1073 - Sensor Calibration 0x0431: 2834 5641 11238 18190 25062 31884 37951 10032 10042 100[...] +[MakerNotes, PhaseOne, Camera] 1074 - Sensor Calibration 0x0432: 4914 10023 20046 40093 10026 10030 10032 10031 10000 10[...] +[MakerNotes, PhaseOne, Camera] 1069 - Sensor Calibration 0x042d: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1070 - Sensor Calibration 0x042e: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1071 - Sensor Calibration 0x042f: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1072 - Sensor Calibration 0x0430: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1076 - Sensor Calibration 0x0434: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1077 - Sensor Calibration 0x0435: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1078 - Sensor Calibration 0x0436: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1079 - Sensor Calibration 0x0437: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1052 - Sensor Calibration 0x041c: 0.989000022411346 0.972000002861023 +[MakerNotes, PhaseOne, Camera] 1044 - Sensor Calibration 0x0414: 4 1.06456995010376 -0.060589998960495 -0.00398000003769994 0.00316999992355704 1.00963997840881 -0.012810000218451 0.00692000007256866 0.0190500002354383 0.974020004272461 0.939149975776672 0.0562799982726574 0.00457999994978309 -0.00303000002168119 0.990019977092743 0.0130099998787045 -0.00662000011652708 -0.0197700001299381 1.02637994289398 0.988960027694702 0.971849977970123 0.89521998167038 +[MakerNotes, PhaseOne, Camera] 1075 - Sensor Calibration 0x0433: 1836409916 1679849837 1046574177 +[MakerNotes, PhaseOne, Camera] 1024 - Sensor Defects: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1040 - All Color Flat Field 2: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1035 - Red Blue Flat Field: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1035 - Red Blue Flat Field: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1035 - Red Blue Flat Field: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1035 - Red Blue Flat Field: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1035 - Red Blue Flat Field: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1035 - Red Blue Flat Field: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1035 - Red Blue Flat Field: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 1035 - Red Blue Flat Field: (Binary data 12 bytes) +[MakerNotes, PhaseOne, Camera] 586 - Phase One 0x024a: 1363265759 +[Composite, Composite, Image] Exif-Aperture - Aperture: 5.6 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 7320x5484 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 40.1 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/1250 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.41912 +[Composite, Composite, Image] Exif-LightValue - Light Value: 15.3 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 1.30548 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 80.0 mm +[Composite, Composite, Camera] Exif-LensID-2 - Lens ID: Mamiya LS 80mm f/2.8 D diff --git a/ExifTool/t/PhotoCD.t b/ExifTool/t/PhotoCD.t new file mode 100644 index 0000000..86f1e0c --- /dev/null +++ b/ExifTool/t/PhotoCD.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PhotoCD.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PhotoCD; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PhotoCD'; +my $testnum = 1; + +# test 2: Extract information from PhotoCD file +{ + my $exifTool = Image::ExifTool->new; + ++$testnum; + my $info = $exifTool->ImageInfo('t/images/PhotoCD.pcd'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PhotoCD_2.out b/ExifTool/t/PhotoCD_2.out new file mode 100644 index 0000000..c21709d --- /dev/null +++ b/ExifTool/t/PhotoCD_2.out @@ -0,0 +1,37 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: PhotoCD.pcd +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 4.1 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2012:05:07 13:11:08-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:47-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: PCD +[File, File, Other] FileTypeExtension - File Type Extension: pcd +[File, File, Other] MIMEType - MIME Type: image/x-photo-cd +[PhotoCD, PhotoCD, Image] 7 - Specification Version: 0.6 +[PhotoCD, PhotoCD, Image] 9 - Authoring Software Release: 7.61 +[PhotoCD, PhotoCD, Image] 11 - Image Magnification Descriptor: 1.0 +[PhotoCD, PhotoCD, Time] 13 - Create Date: 2001:06:27 01:29:36-04:00 +[PhotoCD, PhotoCD, Time] 17 - Modify Date: 2001:06:27 01:29:36-04:00 +[PhotoCD, PhotoCD, Image] 21 - Image Medium: Color negative +[PhotoCD, PhotoCD, Image] 22 - Product Type: 080/11 SPD 0100 +[PhotoCD, PhotoCD, Image] 42 - Scanner Vendor ID: KODAK /4220 +[PhotoCD, PhotoCD, Image] 62 - Scanner Product ID: FilmScanner 2000 +[PhotoCD, PhotoCD, Image] 78 - Scanner Firmware Version: 4.17 +[PhotoCD, PhotoCD, Image] 82 - Scanner Firmware Date: +[PhotoCD, PhotoCD, Image] 90 - Scanner Serial Number: 436 +[PhotoCD, PhotoCD, Image] 110 - Scanner Pixel Size: 11.48 micrometers +[PhotoCD, PhotoCD, Image] 112 - Image Workstation Make: Eastman Kodak +[PhotoCD, PhotoCD, Image] 132 - Character Set: 95 characters ISO 646 +[PhotoCD, PhotoCD, Image] 165 - Photo Finisher Name: Prolab, Inc. 206-547-5447 +[PhotoCD, PhotoCD, Image] 228 - Scene Balance Algorithm Revision: 6.7 +[PhotoCD, PhotoCD, Image] 230 - Scene Balance Algorithm Command: Neutral SBA On, Color SBA On +[PhotoCD, PhotoCD, Image] 325 - Scene Balance Algorithm Film ID: Kodak Gold 100 Gen 2 +[PhotoCD, PhotoCD, Image] 331 - Copyright Status: Not specified +[PhotoCD, PhotoCD, Image] 1538 - Orientation: Rotate 270 CW +[PhotoCD, PhotoCD, Image] 1538.1 - Image Width: 2048 +[PhotoCD, PhotoCD, Image] 1538.2 - Image Height: 3072 +[PhotoCD, PhotoCD, Image] 1538.3 - Compression Class: Class 1 - 35mm film; Pictoral hard copy +[Composite, Composite, Image] Exif-ImageSize - Image Size: 2048x3072 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 6.3 diff --git a/ExifTool/t/PhotoMechanic.t b/ExifTool/t/PhotoMechanic.t new file mode 100644 index 0000000..c2220e5 --- /dev/null +++ b/ExifTool/t/PhotoMechanic.t @@ -0,0 +1,39 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PhotoMechanic.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PhotoMechanic; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PhotoMechanic'; +my $testnum = 1; + +# test 2: Extract information from PhotoMechanic.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/PhotoMechanic.jpg', {Duplicates => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Test writing some information +{ + ++$testnum; + my @writeInfo = ( + ['Rotation' => 90 ], + ['Keywords' => 'PhotoMechanic' ], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, undef, 1); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PhotoMechanic_2.out b/ExifTool/t/PhotoMechanic_2.out new file mode 100644 index 0000000..b7f555c --- /dev/null +++ b/ExifTool/t/PhotoMechanic_2.out @@ -0,0 +1,83 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: PhotoMechanic.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 3.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:10:31 12:53:49-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:47-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 2c19015ea7e2d138576e147a3f093c1a +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[IPTC, IPTC, Other] 0 - Application Record Version: 3 +[IPTC, IPTC, Time] 55 - Date Created: 2006:10:29 +[IPTC, IPTC, Time] 60 - Time Created: 06:27:51-05:00 +[IPTC, IPTC, Location] 90 - City: kingston +[IPTC, IPTC, Location] 95 - Province-State: State +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Country +[IPTC, IPTC, Location] 100 - Country-Primary Location Code: COD +[IPTC, IPTC, Other] 5 - Object Name: Object Name +[IPTC, IPTC, Other] 15 - Category: Category +[IPTC, IPTC, Other] 20 - Supplemental Categories: Supp Cat 1, Supp Cat 2 +[IPTC, IPTC, Other] 25 - Keywords: Keywords, one, three, two +[IPTC, IPTC, Other] 7 - Edit Status: Edit Status +[IPTC, IPTC, Author] 80 - By-line: Photographer +[IPTC, IPTC, Author] 85 - By-line Title: Title +[IPTC, IPTC, Author] 110 - Credit: Credit +[IPTC, IPTC, Author] 115 - Source: Source +[IPTC, IPTC, Author] 122 - Writer-Editor: Writers +[IPTC, IPTC, Other] 120 - Caption-Abstract: Caption +[IPTC, IPTC, Other] 105 - Headline: Headline +[IPTC, IPTC, Other] 40 - Special Instructions: Special ins +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright +[IPTC, IPTC, Image] 221 - Prefs: Tagged:0, ColorClass:0, Rating:0, FrameNum:-00001 +[Photoshop, Photoshop, Image] 0 - X Resolution: 200 +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: 200 +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: True +[XMP, XMP-x, Document] xmptk - XMP Toolkit: XMP toolkit 2.9-9, framework 1.6 +[XMP, XMP-iptcCore, Location] CountryCode - Country Code: COD +[XMP, XMP-photoshop, Location] City - City: kingston +[XMP, XMP-photoshop, Location] State - State: State +[XMP, XMP-photoshop, Location] Country - Country: Country +[XMP, XMP-photoshop, Image] Category - Category: Category +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: Title +[XMP, XMP-photoshop, Author] Credit - Credit: Credit +[XMP, XMP-photoshop, Author] Source - Source: Source +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: Writers +[XMP, XMP-photoshop, Image] Headline - Headline: Headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: Special ins +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: Trans ref +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: Supp Cat 1, Supp Cat 2 +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2006:10:29 +[XMP, XMP-xmpRights, Author] Marked - Marked: True +[XMP, XMP-photomech, Image] Prefs - Prefs: Tagged:0, ColorClass:0, Rating:0, FrameNum:-00001 +[XMP, XMP-photomech, Image] EditStatus - Edit Status: Edit Status +[XMP, XMP-photomech, Location] CountryCode - Country Code: COD +[XMP, XMP-photomech, Time] TimeCreated - Time Created: 06:27:51-05:00 +[XMP, XMP-dc, Image] subject - Subject: Keywords, one, three, two +[XMP, XMP-dc, Image] description - Description: Caption +[XMP, XMP-dc, Author] creator - Creator: Photographer +[XMP, XMP-dc, Image] title - Title: Object Name +[XMP, XMP-dc, Author] rights - Rights: Copyright +[PhotoMechanic, PhotoMechanic, Image] 221 - Tagged: Yes +[PhotoMechanic, PhotoMechanic, Image] 222 - Color Class: 6 (Typical alt) +[PhotoMechanic, PhotoMechanic, Image] 216 - Rotation: 180 +[PhotoMechanic, PhotoMechanic, Image] 217 - Crop Left: 438 +[PhotoMechanic, PhotoMechanic, Image] 218 - Crop Top: 618 +[PhotoMechanic, PhotoMechanic, Image] 219 - Crop Right: 890 +[PhotoMechanic, PhotoMechanic, Image] 220 - Crop Bottom: 1072 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Time] IPTC-DateTimeCreated - Date/Time Created: 2006:10:29 06:27:51-05:00 +[Composite, Composite, Time] Exif-DateTimeOriginal - Date/Time Original: 2006:10:29 06:27:51-05:00 diff --git a/ExifTool/t/PhotoMechanic_3.out b/ExifTool/t/PhotoMechanic_3.out new file mode 100644 index 0000000..cd7ec2f --- /dev/null +++ b/ExifTool/t/PhotoMechanic_3.out @@ -0,0 +1,2 @@ +[IPTC, IPTC, Other] 25 - Keywords: PhotoMechanic +[PhotoMechanic, PhotoMechanic, Image] 216 - Rotation: 90 diff --git a/ExifTool/t/Photoshop.t b/ExifTool/t/Photoshop.t new file mode 100644 index 0000000..78fa814 --- /dev/null +++ b/ExifTool/t/Photoshop.t @@ -0,0 +1,42 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Photoshop.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Photoshop; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Photoshop'; +my $testnum = 1; + +# test 2: Extract information from Photoshop.psd +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Photoshop.psd'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + [XResolution => '120'], + [YResolution => '120'], + [Creator => 'Phil Harvey'], + ['By-Line' => 'Phil Again'], + ['PhotoMechanic:Tagged' => No], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/Photoshop.psd'); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Photoshop_2.out b/ExifTool/t/Photoshop_2.out new file mode 100644 index 0000000..6d0a077 --- /dev/null +++ b/ExifTool/t/Photoshop_2.out @@ -0,0 +1,109 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Photoshop.psd +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 17 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:48-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: PSD +[File, File, Other] FileTypeExtension - File Type Extension: psd +[File, File, Other] MIMEType - MIME Type: application/vnd.adobe.photoshop +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: b765e54396f2885e9a76ff4ad665b190 +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[Photoshop, Photoshop, Image] 6 - Num Channels: 3 +[Photoshop, Photoshop, Image] 7 - Image Height: 8 +[Photoshop, Photoshop, Image] 9 - Image Width: 8 +[Photoshop, Photoshop, Image] 11 - Bit Depth: 8 +[Photoshop, Photoshop, Image] 12 - Color Mode: RGB +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: b765e54396f2885e9a76ff4ad665b190 +[Photoshop, Photoshop, Image] 0 - X Resolution: 72 +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: 72 +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Image] 0 - Print Style: Centered +[Photoshop, Photoshop, Image] 2 - Print Position: 0 0 +[Photoshop, Photoshop, Image] 10 - Print Scale: 1 +[Photoshop, Photoshop, Image] 1037 - Global Angle: 120 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 30 +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image] 1054 - URL List: +[Photoshop, Photoshop, Other] 20 - Slices Group Name: b +[Photoshop, Photoshop, Other] 24 - Num Slices: 1 +[Photoshop, Photoshop, Image] 4 - Has Real Merged Data: Yes +[Photoshop, Photoshop, Image] 5 - Writer Name: Adobe Photoshop +[Photoshop, Photoshop, Image] 9 - Reader Name: Adobe Photoshop 7.0 +[Photoshop, Photoshop, Image] _xcnt - Layer Count: 0 +[Photoshop, Photoshop, Image] 0 - Compression: RLE +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: This is a teeny weeny white square +[IPTC, IPTC, Author] 122 - Writer-Editor: Me too +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: Supreme leader +[IPTC, IPTC, Other] 5 - Object Name: Test Picture +[IPTC, IPTC, Author] 116 - Copyright Notice: Free for all +[XMP, XMP-x, Document] xmptk - XMP Toolkit: XMP toolkit 2.8.2-33, framework 1.5 +[XMP, XMP-rdf, Document] about - About: uuid:c197d41e-f8f7-11d9-b03e-c023c3939af5 +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: Me too +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: Supreme leader +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: This isn't a job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:c197d41a-f8f7-11d9-b03e-c023c3939af5 +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[XMP, XMP-dc, Image] description - Description: This is a teeny weeny white square +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] title - Title: Test Picture +[XMP, XMP-dc, Author] rights - Rights: Free for all +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:06:03 00:00:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple Computer Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Media-Relative Colorimetric +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright 1999 Adobe Systems Incorporated +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Apple RGB +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.47554 0.25516 0.01845 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.33972 0.67259 0.11333 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14896 0.07225 0.69312 +[EXIF, IFD0, Image] 270 - Image Description: This is a teeny weeny white square +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 08:27:14 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Author] 33432 - Copyright: Free for all +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 8 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 8 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 390 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 0 +[PhotoMechanic, PhotoMechanic, Image] 221 - Tagged: Yes +[PhotoMechanic, PhotoMechanic, Image] 222 - Color Class: 6 (Typical alt) +[PhotoMechanic, PhotoMechanic, Image] 216 - Rotation: 180 +[PhotoMechanic, PhotoMechanic, Image] 217 - Crop Left: 438 +[PhotoMechanic, PhotoMechanic, Image] 218 - Crop Top: 618 +[PhotoMechanic, PhotoMechanic, Image] 219 - Crop Right: 890 +[PhotoMechanic, PhotoMechanic, Image] 220 - Crop Bottom: 1072 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Photoshop_3.out b/ExifTool/t/Photoshop_3.out new file mode 100644 index 0000000..d00d03a --- /dev/null +++ b/ExifTool/t/Photoshop_3.out @@ -0,0 +1,118 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.49 +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest is not current. XMP may be out of sync +[File, System, Other] FileName - File Name: Photoshop_3_failed.psd +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 17 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:10:19 14:17:57-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:10:19 14:17:57-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:10:19 14:17:57-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: PSD +[File, File, Other] FileTypeExtension - File Type Extension: psd +[File, File, Other] MIMEType - MIME Type: application/vnd.adobe.photoshop +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 01c26dbee9b75ddbe23315cf1b5e1486 +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[Photoshop, Photoshop, Image] 6 - Num Channels: 3 +[Photoshop, Photoshop, Image] 7 - Image Height: 8 +[Photoshop, Photoshop, Image] 9 - Image Width: 8 +[Photoshop, Photoshop, Image] 11 - Bit Depth: 8 +[Photoshop, Photoshop, Image] 12 - Color Mode: RGB +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: b765e54396f2885e9a76ff4ad665b190 +[Photoshop, Photoshop, Image] 1002 - XML Data: (Binary data 7600 bytes) +[Photoshop, Photoshop, Image] 1001 - Macintosh Print Info: .HH.Þ.@ÿîÿî...R.g.(.ü.HH.Ø.(.d......ÿ..h... +[Photoshop, Photoshop, Image] 0 - X Resolution: 120 +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: 120 +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Image] 0 - Print Style: Centered +[Photoshop, Photoshop, Image] 2 - Print Position: 0 0 +[Photoshop, Photoshop, Image] 10 - Print Scale: 1 +[Photoshop, Photoshop, Image] 1037 - Global Angle: 120 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 30 +[Photoshop, Photoshop, Image] 1011 - Print Flags: Print flags +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image] 10000 - Print Flags Info: .. +[Photoshop, Photoshop, Image] 1013 - Color Halftoning Info: /ff.lff../ff.¡™š..2.Z..5.-.. +[Photoshop, Photoshop, Image] 1016 - Color Transfer Funcs: ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ.èÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ.èÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ.èÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ.è +[Photoshop, Photoshop, Image] 1032 - Grid Guides Info: ..@.@ +[Photoshop, Photoshop, Image] 1054 - URL List: +[Photoshop, Photoshop, Other] 20 - Slices Group Name: b +[Photoshop, Photoshop, Other] 24 - Num Slices: 1 +[Photoshop, Photoshop, Image] 1044 - IDs Base Value: 1 +[Photoshop, Photoshop, Image] 4 - Has Real Merged Data: Yes +[Photoshop, Photoshop, Image] 5 - Writer Name: Adobe Photoshop +[Photoshop, Photoshop, Image] 9 - Reader Name: Adobe Photoshop 7.0 +[Photoshop, Photoshop, Image] _xcnt - Layer Count: 0 +[Photoshop, Photoshop, Image] 0 - Compression: RLE +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: This is a teeny weeny white square +[IPTC, IPTC, Author] 122 - Writer-Editor: Me too +[IPTC, IPTC, Author] 80 - By-line: Phil Again +[IPTC, IPTC, Author] 85 - By-line Title: Supreme leader +[IPTC, IPTC, Other] 5 - Object Name: Test Picture +[IPTC, IPTC, Author] 116 - Copyright Notice: Free for all +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.49 +[XMP, XMP-rdf, Document] about - About: uuid:c197d41e-f8f7-11d9-b03e-c023c3939af5 +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: This is a teeny weeny white square +[XMP, XMP-dc, Author] rights - Rights: Free for all +[XMP, XMP-dc, Image] title - Title: Test Picture +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: Supreme leader +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: Me too +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: This isn't a job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:c197d41a-f8f7-11d9-b03e-c023c3939af5 +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:06:03 00:00:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple Computer Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Media-Relative Colorimetric +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright 1999 Adobe Systems Incorporated +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Apple RGB +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.47554 0.25516 0.01845 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.33972 0.67259 0.11333 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14896 0.07225 0.69312 +[EXIF, IFD0, Image] 270 - Image Description: This is a teeny weeny white square +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 120 +[EXIF, IFD0, Image] 283 - Y Resolution: 120 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:07:18 08:27:14 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Author] 33432 - Copyright: Free for all +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 8 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 8 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 388 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 0 +[PhotoMechanic, PhotoMechanic, Image] 221 - Tagged: No +[PhotoMechanic, PhotoMechanic, Image] 222 - Color Class: 6 (Typical alt) +[PhotoMechanic, PhotoMechanic, Image] 216 - Rotation: 180 +[PhotoMechanic, PhotoMechanic, Image] 217 - Crop Left: 438 +[PhotoMechanic, PhotoMechanic, Image] 218 - Crop Top: 618 +[PhotoMechanic, PhotoMechanic, Image] 219 - Crop Right: 890 +[PhotoMechanic, PhotoMechanic, Image] 220 - Crop Bottom: 1072 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/PostScript.t b/ExifTool/t/PostScript.t new file mode 100644 index 0000000..cc33c0d --- /dev/null +++ b/ExifTool/t/PostScript.t @@ -0,0 +1,49 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/PostScript.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::PostScript; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'PostScript'; +my $testnum = 1; + +# test 2: Extract information from PostScript.eps +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/PostScript.eps'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write EPS information (and test ExtractEmbedded option) +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/IPTC.jpg','*:*'); + $exifTool->SetNewValuesFromFile('t/images/XMP.jpg','*:*'); + $exifTool->SetNewValue(Title => 'new title'); + $exifTool->SetNewValue(Copyright => 'my copyright'); + $exifTool->SetNewValue(Creator => 'phil made it', Replace => 1); + my $testfile = "t/${testname}_${testnum}_failed.eps"; + unlink $testfile; + $exifTool->WriteInfo('t/images/PostScript.eps', $testfile); + my $info = $exifTool->ImageInfo($testfile, {ExtractEmbedded => 1}); + if (check($exifTool, $info, $testname, $testnum, $testnum, 3)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/PostScript_2.out b/ExifTool/t/PostScript_2.out new file mode 100644 index 0000000..5711c42 --- /dev/null +++ b/ExifTool/t/PostScript_2.out @@ -0,0 +1,66 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: PostScript.eps +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 11 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:49-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:01:12 18:02:53-05:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: EPS +[File, File, Other] FileTypeExtension - File Type Extension: eps +[File, File, Other] MIMEType - MIME Type: application/postscript +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 9e02efbd9bdc1dc192279e30a0e27323 +[PostScript, PostScript, Image] Creator - Creator: Adobe Photoshop Version 7.0.1 +[PostScript, PostScript, Image] Title - Title: c.eps +[PostScript, PostScript, Time] CreationDate - Create Date: 7/18/05 2:33 PM +[PostScript, PostScript, Image] BoundingBox - Bounding Box: 0 0 8 8 +[PostScript, PostScript, Image] ImageData - Image Data: 8 8 8 3 1 8 2 "beginimage" +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other] 105 - Headline: No headline +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My Position +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Author] 115 - Source: I'm the source +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Other] 25 - Keywords: ExifTool, Test, XMP +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: 9e02efbd9bdc1dc192279e30a0e27323 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: XMP toolkit 2.8.2-33, framework 1.5 +[XMP, XMP-rdf, Document] about - About: uuid:b691db36-f92a-11d9-99a4-8c8e8269c120 +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Image] Category - Category: 1 +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image] Headline - Headline: No headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Author] Source - Source: I'm the source +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:cbcc2a62-f127-11d9-ac4d-e8be6f73552e +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: A witty caption +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[Composite, Composite, Image] PostScript-ImageHeight - Image Height: 8 +[Composite, Composite, Image] PostScript-ImageWidth - Image Width: 8 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/PostScript_3.out b/ExifTool/t/PostScript_3.out new file mode 100644 index 0000000..73031ef --- /dev/null +++ b/ExifTool/t/PostScript_3.out @@ -0,0 +1,135 @@ +[ExifTool, ExifTool, ExifTool, Main] ExifToolVersion - ExifTool Version Number: 12.42 +[ExifTool, ExifTool, ExifTool, Main] Warning - Warning: IPTCDigest is not current. XMP may be out of sync +[File, System, Other, Main] FileName - File Name: PostScript_3_failed.eps +[File, System, Other, Main] Directory - Directory: t +[File, System, Other, Main] FileSize - File Size: 14 kB +[File, System, Time, Main] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:49-04:00 +[File, System, Time, Main] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:49-04:00 +[File, System, Time, Main] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:49-04:00 +[File, System, Other, Main] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other, Main] FileType - File Type: EPS +[File, File, Other, Main] FileTypeExtension - File Type Extension: eps +[File, File, Other, Main] MIMEType - MIME Type: application/postscript +[File, File, Image, Main] CurrentIPTCDigest - Current IPTC Digest: 04d104a94abbcadb3742516c01f6568b +[File, File, Image, Main] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[PostScript, PostScript, Image, Main] Creator - Creator: phil made it +[PostScript, PostScript, Image, Main] Title - Title: new title +[PostScript, PostScript, Time, Main] CreationDate - Create Date: 7/18/05 2:33 PM +[PostScript, PostScript, Image, Main] BoundingBox - Bounding Box: 0 0 8 8 +[PostScript, PostScript, Image, Main] Copyright - Copyright: my copyright +[PostScript, PostScript, Image, Doc1] EmbeddedFileName - Embedded File Name: Test-Doc1.eps +[PostScript, PostScript, Image, Doc1] Creator - Creator: Adobe Photoshop Version 9.0x211 +[PostScript, PostScript, Image, Doc1] Title - Title: 12a.eps +[PostScript, PostScript, Image, Doc1] BoundingBox - Bounding Box: 0 0 435 283 +[PostScript, PostScript, Image, Doc2] EmbeddedFileName - Embedded File Name: Test-Doc2.eps +[PostScript, PostScript, Image, Doc2] Creator - Creator: Adobe Photoshop Version 9.0x211 +[PostScript, PostScript, Image, Doc2] Title - Title: 12b.eps +[PostScript, PostScript, Image, Doc2] BoundingBox - Bounding Box: 0 0 435 283 +[PostScript, PostScript, Image, Doc2-1] EmbeddedFileName - Embedded File Name: Test-Doc2-1.eps +[PostScript, PostScript, Image, Doc2-1] Creator - Creator: Adobe Photoshop Version 9.0x211 +[PostScript, PostScript, Image, Doc2-1] Title - Title: 12c.eps +[PostScript, PostScript, Image, Doc2-1] BoundingBox - Bounding Box: 0 0 435 283 +[PostScript, PostScript, Image, Doc3] EmbeddedFileName - Embedded File Name: Test-Doc3.eps +[PostScript, PostScript, Image, Doc3] Creator - Creator: Adobe Photoshop Version 9.0x211 +[PostScript, PostScript, Image, Doc3] Title - Title: 12d.eps +[PostScript, PostScript, Image, Doc3] BoundingBox - Bounding Box: 0 0 435 283 +[PostScript, PostScript, Image, Main] ImageData - Image Data: 8 8 8 3 1 8 2 "beginimage" +[IPTC, IPTC, Other, Main] 0 - Application Record Version: 2 +[IPTC, IPTC, Other, Main] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author, Main] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other, Main] 105 - Headline: No headline +[IPTC, IPTC, Other, Main] 40 - Special Instructions: What instructions +[IPTC, IPTC, Author, Main] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author, Main] 85 - By-line Title: My Position +[IPTC, IPTC, Author, Main] 110 - Credit: My Credit +[IPTC, IPTC, Author, Main] 115 - Source: I'm the source +[IPTC, IPTC, Other, Main] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Time, Main] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location, Main] 90 - City: Kingston +[IPTC, IPTC, Location, Main] 95 - Province-State: Ont +[IPTC, IPTC, Location, Main] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other, Main] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other, Main] 15 - Category: 1 +[IPTC, IPTC, Other, Main] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Other, Main] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Other, Main] 25 - Keywords: ExifTool, Test, IPTC +[IPTC, IPTC, Author, Main] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[Photoshop, Photoshop, Image, Main] 1061 - IPTC Digest: 9e02efbd9bdc1dc192279e30a0e27323 +[Photoshop, Photoshop, Author, Main] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author, Main] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image, Main] 1037 - Global Angle: 30 +[Photoshop, Photoshop, Image, Main] 1049 - Global Altitude: 30 +[EXIF, IFD0, Image, Main] 270 - Image Description: A witty caption +[EXIF, IFD0, Camera, Main] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera, Main] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image, Main] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image, Main] 282 - X Resolution: 72 +[EXIF, IFD0, Image, Main] 283 - Y Resolution: 72 +[EXIF, IFD0, Image, Main] 296 - Resolution Unit: inches +[EXIF, IFD0, Image, Main] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time, Main] 306 - Modify Date: 2004:02:26 09:36:46 +[EXIF, IFD0, Author, Main] 315 - Artist: Phil Harvey +[EXIF, IFD0, Image, Main] 531 - Y Cb Cr Positioning: Centered +[EXIF, IFD0, Author, Main] 33432 - Copyright: my copyright +[EXIF, ExifIFD, Image, Main] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera, Main] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image, Main] 34855 - ISO: 100 +[EXIF, ExifIFD, Image, Main] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time, Main] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time, Main] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image, Main] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image, Main] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image, Main] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image, Main] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image, Main] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera, Main] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera, Main] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera, Main] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera, Main] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image, Main] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image, Main] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image, Main] 40962 - Exif Image Width: 100 +[EXIF, ExifIFD, Image, Main] 40963 - Exif Image Height: 80 +[EXIF, ExifIFD, Camera, Main] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera, Main] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera, Main] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera, Main] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image, Main] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image, Main] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image, Main] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image, Main] 282 - X Resolution: 72 +[EXIF, IFD1, Image, Main] 283 - Y Resolution: 72 +[EXIF, IFD1, Image, Main] 296 - Resolution Unit: inches +[XMP, XMP-x, Document, Main] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-rdf, Document, Main] about - About: uuid:b691db36-f92a-11d9-99a4-8c8e8269c120 +[XMP, XMP-dc, Author, Main] creator - Creator: phil made it +[XMP, XMP-dc, Image, Main] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author, Main] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image, Main] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image, Main] title - Title: new title +[XMP, XMP-photoshop, Author, Main] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author, Main] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Image, Main] Category - Category: 1 +[XMP, XMP-photoshop, Location, Main] City - City: Kingston +[XMP, XMP-photoshop, Location, Main] Country - Country: Canada +[XMP, XMP-photoshop, Author, Main] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time, Main] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image, Main] Headline - Headline: No headline +[XMP, XMP-photoshop, Image, Main] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Author, Main] Source - Source: I'm the source +[XMP, XMP-photoshop, Location, Main] State - State: Ont +[XMP, XMP-photoshop, Image, Main] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-photoshop, Image, Main] TransmissionReference - Transmission Reference: What is a transmission reference? +[XMP, XMP-photoshop, Image, Main] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-xmpBJ, Other, Main] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other, Main] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a +[XMP, XMP-xmpRights, Author, Main] Marked - Marked: False +[XMP, XMP-xmpRights, Author, Main] WebStatement - Web Statement: https://exiftool.org/ +[Composite, Composite, Image, Main] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image, Main] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Image, Main] PostScript-ImageHeight - Image Height: 8 +[Composite, Composite, Image, Main] PostScript-ImageWidth - Image Width: 8 +[Composite, Composite, Image, Main] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image, Main] Exif-LightValue - Light Value: 9.6 +[Composite, Composite, Image, Main] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera, Main] Exif-FocalLength35efl - Focal Length: 6.0 mm diff --git a/ExifTool/t/QuickTime.t b/ExifTool/t/QuickTime.t new file mode 100644 index 0000000..e0856ae --- /dev/null +++ b/ExifTool/t/QuickTime.t @@ -0,0 +1,240 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/QuickTime.t". + +BEGIN { + $| = 1; print "1..17\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::QuickTime; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'QuickTime'; +my $testnum = 1; + +# tests 2-3: Extract information from QuickTime.mov and QuickTime.m4a +{ + my $ext; + foreach $ext (qw(mov m4a)) { + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo("t/images/QuickTime.$ext"); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +# tests 4-5: Try writing XMP to the different file formats +{ + my $ext; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(SavePath => 1); # to save group 5 names + $exifTool->SetNewValue('XMP:Title' => 'x'); + $exifTool->SetNewValue('TrackCreateDate' => '2000:01:02 03:04:05'); + $exifTool->SetNewValue('Track1:TrackModifyDate' => '2013:11:04 10:32:15'); + foreach $ext (qw(mov m4a)) { + ++$testnum; + unless (eval { require Time::Local }) { + print "ok $testnum # skip Requires Time::Local\n"; + next; + } + my $testfile = "t/${testname}_${testnum}_failed.$ext"; + unlink $testfile; + my $rtnVal = $exifTool->WriteInfo("t/images/QuickTime.$ext", $testfile); + my $info = $exifTool->ImageInfo($testfile, 'title', 'time:all'); + if (check($exifTool, $info, $testname, $testnum, undef, 5)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + } +} + +# test 6: Write video rotation +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('Rotation' => '270', Protected => 1); + my $testfile = "t/${testname}_${testnum}_failed.mov"; + unlink $testfile; + my $rtnVal = $exifTool->WriteInfo('t/images/QuickTime.mov', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'Rotation', 'MatrixStructure'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 7: Add a bunch of new tags and delete one +{ + ++$testnum; + my @writeInfo = ( + ['QuickTime:Artist' => 'me'], + ['QuickTime:Model' => 'model'], + ['UserData:Genre' => 'rock'], + ['UserData:Album' => 'albumA'], + ['ItemList:Album' => 'albumB'], + ['ItemList:ID-albm:Album' => 'albumC'], + ['QuickTime:Comment-fra-FR' => 'fr comment'], + ['Keys:Director' => 'director'], + ['Keys:CameraDirection' => '90'], + ['Keys:Album' => undef ], + ); + my @extract = ('ItemList:all', 'UserData:all', 'Keys:all'); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/QuickTime.mov', \@extract); + print "ok $testnum\n"; +} + +# test 8-9: Delete everything then add back some tags in one step +{ + my $ext; + my $exifTool = Image::ExifTool->new; + my @writeInfo = ( + ['all' => undef], + ['artist' => 'me'], + ['keys:director' => 'dir'], + ['userdata:arranger' => 'arr'], + ); + my @extract = ('QuickTime:all', 'XMP:all'); + foreach $ext (qw(mov m4a)) { + ++$testnum; + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, "t/images/QuickTime.$ext", \@extract); + print "ok $testnum\n"; + } +} + +# test 10: Delete everything then add back some tags in two steps +{ + ++$testnum; + my $testfile = "t/${testname}_${testnum}a_failed.mov"; + unlink $testfile; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(QuickTimeHandler => 1); + $exifTool->SetNewValue('all' => undef); + writeInfo($exifTool, 't/images/QuickTime.mov', $testfile); + my @writeInfo = ( + ['quicktime:artist' => 'me'], + ['keys:director' => 'dir'], + ['userdata:arranger' => 'arr'], + ); + my @extract = ('QuickTime:all', 'XMP:all', '-Track1:all', '-Track2:all'); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, $testfile, \@extract); + print "ok $testnum\n"; +} + +# tests 11-13: HEIC write tests +{ + ++$testnum; + my $testfile = "t/${testname}_${testnum}_failed.heic"; + unlink $testfile; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Composite => 0); + $exifTool->SetNewValue('XMP-dc:Title' => 'a title'); + writeInfo($exifTool, 't/images/QuickTime.heic', $testfile); + my $info = $exifTool->ImageInfo($testfile, '-file:all'); + unless (check($exifTool, $info, $testname, $testnum)) { + notOK(); + } + print "ok $testnum\n"; + + ++$testnum; + my $testfile2 = "t/${testname}_${testnum}_failed.heic"; + unlink $testfile2; + $exifTool->SetNewValue(); + $exifTool->SetNewValue('XMP:all' => undef); + $exifTool->SetNewValue('EXIF:Artist' => 'an artist'); + writeInfo($exifTool, $testfile, $testfile2); + $info = $exifTool->ImageInfo($testfile2, '-file:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + + ++$testnum; + $testfile = "t/${testname}_${testnum}_failed.heic"; + unlink $testfile; + $exifTool->SetNewValue(); + $exifTool->SetNewValue('EXIF:all' => undef); + $exifTool->SetNewValue('EXIF:UserComment' => 'a comment'); + $exifTool->SetNewValue('XMP:Subject' => 'a subject'); + $exifTool->SetNewValue('XMP:Subject' => 'another subject'); + writeInfo($exifTool, $testfile2, $testfile); + $info = $exifTool->ImageInfo($testfile, '-file:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + unlink $testfile2; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 14: Delete everything then add back a tag without specifying the group +{ + ++$testnum; + my $testfile = "t/${testname}_10a_failed.mov"; # use source file from test 10 + my @writeInfo = ( ['publisher' => 'pub'] ); + my @extract = ('QuickTime:all', 'XMP:all', '-Track1:all', '-Track2:all'); + if (writeCheck(\@writeInfo, $testname, $testnum, $testfile, \@extract)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 15: Test WriteMode option with QuickTime tags +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(WriteMode => 'c'); + $exifTool->SetNewValue('ItemList:Composer' => 'WRONG'); + $exifTool->SetNewValue('ItemList:Author' => 'aut'); + $exifTool->SetNewValue('Keys:Artist' => 'WRONG'); + $exifTool->SetNewValue('UserData:Artist' => 'art'); + my $testfile = "t/${testname}_${testnum}_failed.m4a"; + unlink $testfile; + my $rtnVal = $exifTool->WriteInfo('t/images/QuickTime.m4a', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'ItemList:all', 'Keys:all', 'UserData:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 16: Write some Microsoft Xtra tags +{ + ++$testnum; + my @writeInfo = ( + ['Microsoft:Director' => 'dir1'], + ['Microsoft:Director' => 'dir2'], + ['Microsoft:SharedUserRating' => 75], + ); + my @extract = ('Microsoft:all'); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/QuickTime.mov', \@extract); + print "ok $testnum\n"; +} + +# test 17: Write some 3gp tags +{ + ++$testnum; + my @writeInfo = ( + ['UserData:LocationInformation' => 'test comment role=Shooting lat=1.2 lon=-2.3 alt=100 body=earth notes=a note'], + ['UserData:Rating' => 'entity=ABCD criteria=1234 a rating'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/QuickTime.mov', 1); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/QuickTime_10.out b/ExifTool/t/QuickTime_10.out new file mode 100644 index 0000000..ae5af48 --- /dev/null +++ b/ExifTool/t/QuickTime_10.out @@ -0,0 +1,23 @@ +[QuickTime, QuickTime, Video] 0 - Movie Header Version: 0 +[QuickTime, QuickTime, Time] 1 - Create Date: 2005:08:11 14:03:54 +[QuickTime, QuickTime, Time] 2 - Modify Date: 2010:07:30 15:43:59 +[QuickTime, QuickTime, Video] 3 - Time Scale: 600 +[QuickTime, QuickTime, Video] 4 - Duration: 4.97 s +[QuickTime, QuickTime, Video] 5 - Preferred Rate: 1 +[QuickTime, QuickTime, Video] 6 - Preferred Volume: 99.61% +[QuickTime, QuickTime, Video] 9 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, QuickTime, Video] 18 - Preview Time: 0 s +[QuickTime, QuickTime, Video] 19 - Preview Duration: 0 s +[QuickTime, QuickTime, Video] 20 - Poster Time: 0 s +[QuickTime, QuickTime, Video] 21 - Selection Time: 0 s +[QuickTime, QuickTime, Video] 22 - Selection Duration: 0 s +[QuickTime, QuickTime, Video] 23 - Current Time: 0 s +[QuickTime, QuickTime, Video] 24 - Next Track ID: 3 +[QuickTime, QuickTime, Video] 8 - Handler Type: Metadata +[QuickTime, ItemList, Audio] ©ART - Artist: me +[QuickTime, UserData, Video] ©arg - Arranger: arr +[QuickTime, QuickTime, Video] 8 - Handler Type: Metadata Tags +[QuickTime, Keys, Other] director - Director: dir +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 0 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 2034 +[QuickTime, QuickTime, Video] mdat - Media Data: (Binary data 0 bytes) diff --git a/ExifTool/t/QuickTime_11.out b/ExifTool/t/QuickTime_11.out new file mode 100644 index 0000000..2db8b6b --- /dev/null +++ b/ExifTool/t/QuickTime_11.out @@ -0,0 +1,46 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.90 +[QuickTime, QuickTime, Video] 0 - Major Brand: High Efficiency Image Format still image (.HEIF) +[QuickTime, QuickTime, Video] 1 - Minor Version: 0.0.0 +[QuickTime, QuickTime, Video] 2 - Compatible Brands: mif1, heic, hevc +[QuickTime, QuickTime, Video] 8 - Handler Type: Picture +[QuickTime, Meta, Video] pitm - Primary Item Reference: 20002 +[QuickTime, QuickTime, Video] 0 - HEVC Configuration Version: 1 +[QuickTime, QuickTime, Video] 1 - General Profile Space: Conforming +[QuickTime, QuickTime, Video] 1.1 - General Tier Flag: Main Tier +[QuickTime, QuickTime, Video] 1.2 - General Profile IDC: Main +[QuickTime, QuickTime, Video] 2 - Gen Profile Compatibility Flags: Main 10, Main +[QuickTime, QuickTime, Video] 6 - Constraint Indicator Flags: 144 0 0 0 0 0 +[QuickTime, QuickTime, Video] 12 - General Level IDC: 120 (level 4.0) +[QuickTime, QuickTime, Video] 13 - Min Spatial Segmentation IDC: 0 +[QuickTime, QuickTime, Video] 15 - Parallelism Type: 0 +[QuickTime, QuickTime, Video] 16 - Chroma Format: 4:2:0 +[QuickTime, QuickTime, Video] 17 - Bit Depth Luma: 8 +[QuickTime, QuickTime, Video] 18 - Bit Depth Chroma: 8 +[QuickTime, QuickTime, Video] 19 - Average Frame Rate: 0 +[QuickTime, QuickTime, Video] 21 - Constant Frame Rate: Unknown +[QuickTime, QuickTime, Video] 21.1 - Num Temporal Layers: 1 +[QuickTime, QuickTime, Video] 21.2 - Temporal ID Nested: Yes +[QuickTime, QuickTime, Image] ispe - Image Spatial Extent: 1596x1064 +[QuickTime, QuickTime, Video] 0 - HEVC Configuration Version: 1 +[QuickTime, QuickTime, Video] 1 - General Profile Space: Conforming +[QuickTime, QuickTime, Video] 1.1 - General Tier Flag: Main Tier +[QuickTime, QuickTime, Video] 1.2 - General Profile IDC: Main +[QuickTime, QuickTime, Video] 2 - Gen Profile Compatibility Flags: Main 10, Main +[QuickTime, QuickTime, Video] 6 - Constraint Indicator Flags: 144 0 0 0 0 0 +[QuickTime, QuickTime, Video] 12 - General Level IDC: 60 (level 2.0) +[QuickTime, QuickTime, Video] 13 - Min Spatial Segmentation IDC: 0 +[QuickTime, QuickTime, Video] 15 - Parallelism Type: 0 +[QuickTime, QuickTime, Video] 16 - Chroma Format: 4:2:0 +[QuickTime, QuickTime, Video] 17 - Bit Depth Luma: 8 +[QuickTime, QuickTime, Video] 18 - Bit Depth Chroma: 8 +[QuickTime, QuickTime, Video] 19 - Average Frame Rate: 0 +[QuickTime, QuickTime, Video] 21 - Constant Frame Rate: Unknown +[QuickTime, QuickTime, Video] 21.1 - Num Temporal Layers: 1 +[QuickTime, QuickTime, Video] 21.2 - Temporal ID Nested: Yes +[QuickTime, QuickTime, Image] ispe - Image Spatial Extent: 320x240 +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 2877 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 652 +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 18 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 3537 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 11.90 +[XMP, XMP-dc, Image] title - Title: a title diff --git a/ExifTool/t/QuickTime_12.out b/ExifTool/t/QuickTime_12.out new file mode 100644 index 0000000..c63db69 --- /dev/null +++ b/ExifTool/t/QuickTime_12.out @@ -0,0 +1,49 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.90 +[QuickTime, QuickTime, Video] 0 - Major Brand: High Efficiency Image Format still image (.HEIF) +[QuickTime, QuickTime, Video] 1 - Minor Version: 0.0.0 +[QuickTime, QuickTime, Video] 2 - Compatible Brands: mif1, heic, hevc +[QuickTime, QuickTime, Video] 8 - Handler Type: Picture +[QuickTime, Meta, Video] pitm - Primary Item Reference: 20002 +[QuickTime, QuickTime, Video] 0 - HEVC Configuration Version: 1 +[QuickTime, QuickTime, Video] 1 - General Profile Space: Conforming +[QuickTime, QuickTime, Video] 1.1 - General Tier Flag: Main Tier +[QuickTime, QuickTime, Video] 1.2 - General Profile IDC: Main +[QuickTime, QuickTime, Video] 2 - Gen Profile Compatibility Flags: Main 10, Main +[QuickTime, QuickTime, Video] 6 - Constraint Indicator Flags: 144 0 0 0 0 0 +[QuickTime, QuickTime, Video] 12 - General Level IDC: 120 (level 4.0) +[QuickTime, QuickTime, Video] 13 - Min Spatial Segmentation IDC: 0 +[QuickTime, QuickTime, Video] 15 - Parallelism Type: 0 +[QuickTime, QuickTime, Video] 16 - Chroma Format: 4:2:0 +[QuickTime, QuickTime, Video] 17 - Bit Depth Luma: 8 +[QuickTime, QuickTime, Video] 18 - Bit Depth Chroma: 8 +[QuickTime, QuickTime, Video] 19 - Average Frame Rate: 0 +[QuickTime, QuickTime, Video] 21 - Constant Frame Rate: Unknown +[QuickTime, QuickTime, Video] 21.1 - Num Temporal Layers: 1 +[QuickTime, QuickTime, Video] 21.2 - Temporal ID Nested: Yes +[QuickTime, QuickTime, Image] ispe - Image Spatial Extent: 1596x1064 +[QuickTime, QuickTime, Video] 0 - HEVC Configuration Version: 1 +[QuickTime, QuickTime, Video] 1 - General Profile Space: Conforming +[QuickTime, QuickTime, Video] 1.1 - General Tier Flag: Main Tier +[QuickTime, QuickTime, Video] 1.2 - General Profile IDC: Main +[QuickTime, QuickTime, Video] 2 - Gen Profile Compatibility Flags: Main 10, Main +[QuickTime, QuickTime, Video] 6 - Constraint Indicator Flags: 144 0 0 0 0 0 +[QuickTime, QuickTime, Video] 12 - General Level IDC: 60 (level 2.0) +[QuickTime, QuickTime, Video] 13 - Min Spatial Segmentation IDC: 0 +[QuickTime, QuickTime, Video] 15 - Parallelism Type: 0 +[QuickTime, QuickTime, Video] 16 - Chroma Format: 4:2:0 +[QuickTime, QuickTime, Video] 17 - Bit Depth Luma: 8 +[QuickTime, QuickTime, Video] 18 - Bit Depth Chroma: 8 +[QuickTime, QuickTime, Video] 19 - Average Frame Rate: 0 +[QuickTime, QuickTime, Video] 21 - Constant Frame Rate: Unknown +[QuickTime, QuickTime, Video] 21.1 - Num Temporal Layers: 1 +[QuickTime, QuickTime, Video] 21.2 - Temporal ID Nested: Yes +[QuickTime, QuickTime, Image] ispe - Image Spatial Extent: 320x240 +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 128 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 705 +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 18 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 841 +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: an artist +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered diff --git a/ExifTool/t/QuickTime_13.out b/ExifTool/t/QuickTime_13.out new file mode 100644 index 0000000..fd9fdaf --- /dev/null +++ b/ExifTool/t/QuickTime_13.out @@ -0,0 +1,55 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.90 +[QuickTime, QuickTime, Video] 0 - Major Brand: High Efficiency Image Format still image (.HEIF) +[QuickTime, QuickTime, Video] 1 - Minor Version: 0.0.0 +[QuickTime, QuickTime, Video] 2 - Compatible Brands: mif1, heic, hevc +[QuickTime, QuickTime, Video] 8 - Handler Type: Picture +[QuickTime, Meta, Video] pitm - Primary Item Reference: 20002 +[QuickTime, QuickTime, Video] 0 - HEVC Configuration Version: 1 +[QuickTime, QuickTime, Video] 1 - General Profile Space: Conforming +[QuickTime, QuickTime, Video] 1.1 - General Tier Flag: Main Tier +[QuickTime, QuickTime, Video] 1.2 - General Profile IDC: Main +[QuickTime, QuickTime, Video] 2 - Gen Profile Compatibility Flags: Main 10, Main +[QuickTime, QuickTime, Video] 6 - Constraint Indicator Flags: 144 0 0 0 0 0 +[QuickTime, QuickTime, Video] 12 - General Level IDC: 120 (level 4.0) +[QuickTime, QuickTime, Video] 13 - Min Spatial Segmentation IDC: 0 +[QuickTime, QuickTime, Video] 15 - Parallelism Type: 0 +[QuickTime, QuickTime, Video] 16 - Chroma Format: 4:2:0 +[QuickTime, QuickTime, Video] 17 - Bit Depth Luma: 8 +[QuickTime, QuickTime, Video] 18 - Bit Depth Chroma: 8 +[QuickTime, QuickTime, Video] 19 - Average Frame Rate: 0 +[QuickTime, QuickTime, Video] 21 - Constant Frame Rate: Unknown +[QuickTime, QuickTime, Video] 21.1 - Num Temporal Layers: 1 +[QuickTime, QuickTime, Video] 21.2 - Temporal ID Nested: Yes +[QuickTime, QuickTime, Image] ispe - Image Spatial Extent: 1596x1064 +[QuickTime, QuickTime, Video] 0 - HEVC Configuration Version: 1 +[QuickTime, QuickTime, Video] 1 - General Profile Space: Conforming +[QuickTime, QuickTime, Video] 1.1 - General Tier Flag: Main Tier +[QuickTime, QuickTime, Video] 1.2 - General Profile IDC: Main +[QuickTime, QuickTime, Video] 2 - Gen Profile Compatibility Flags: Main 10, Main +[QuickTime, QuickTime, Video] 6 - Constraint Indicator Flags: 144 0 0 0 0 0 +[QuickTime, QuickTime, Video] 12 - General Level IDC: 60 (level 2.0) +[QuickTime, QuickTime, Video] 13 - Min Spatial Segmentation IDC: 0 +[QuickTime, QuickTime, Video] 15 - Parallelism Type: 0 +[QuickTime, QuickTime, Video] 16 - Chroma Format: 4:2:0 +[QuickTime, QuickTime, Video] 17 - Bit Depth Luma: 8 +[QuickTime, QuickTime, Video] 18 - Bit Depth Chroma: 8 +[QuickTime, QuickTime, Video] 19 - Average Frame Rate: 0 +[QuickTime, QuickTime, Video] 21 - Constant Frame Rate: Unknown +[QuickTime, QuickTime, Video] 21.1 - Num Temporal Layers: 1 +[QuickTime, QuickTime, Video] 21.2 - Temporal ID Nested: Yes +[QuickTime, QuickTime, Image] ispe - Image Spatial Extent: 320x240 +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 3083 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 705 +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 18 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 3796 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 11.90 +[XMP, XMP-dc, Image] subject - Subject: a subject, another subject +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0232 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37510 - User Comment: a comment +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated diff --git a/ExifTool/t/QuickTime_14.out b/ExifTool/t/QuickTime_14.out new file mode 100644 index 0000000..ee5de54 --- /dev/null +++ b/ExifTool/t/QuickTime_14.out @@ -0,0 +1,22 @@ +[QuickTime, QuickTime, Video] 0 - Movie Header Version: 0 +[QuickTime, QuickTime, Time] 1 - Create Date: 2005:08:11 14:03:54 +[QuickTime, QuickTime, Time] 2 - Modify Date: 2010:07:30 15:43:59 +[QuickTime, QuickTime, Video] 3 - Time Scale: 600 +[QuickTime, QuickTime, Video] 4 - Duration: 4.97 s +[QuickTime, QuickTime, Video] 5 - Preferred Rate: 1 +[QuickTime, QuickTime, Video] 6 - Preferred Volume: 99.61% +[QuickTime, QuickTime, Video] 9 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, QuickTime, Video] 18 - Preview Time: 0 s +[QuickTime, QuickTime, Video] 19 - Preview Duration: 0 s +[QuickTime, QuickTime, Video] 20 - Poster Time: 0 s +[QuickTime, QuickTime, Video] 21 - Selection Time: 0 s +[QuickTime, QuickTime, Video] 22 - Selection Duration: 0 s +[QuickTime, QuickTime, Video] 23 - Current Time: 0 s +[QuickTime, QuickTime, Video] 24 - Next Track ID: 3 +[QuickTime, QuickTime, Video] 8 - Handler Type: Metadata +[QuickTime, ItemList, Audio] ©pub - Publisher: pub +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 0 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 4743 +[QuickTime, QuickTime, Video] mdat - Media Data: (Binary data 0 bytes) +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.19 +[XMP, XMP-dc, Author] publisher - Publisher: pub diff --git a/ExifTool/t/QuickTime_15.out b/ExifTool/t/QuickTime_15.out new file mode 100644 index 0000000..a940f7c --- /dev/null +++ b/ExifTool/t/QuickTime_15.out @@ -0,0 +1,20 @@ +[QuickTime, ItemList, Audio] ©nam - Title: Sample +[QuickTime, ItemList, Audio] cpil - Compilation: No +[QuickTime, ItemList, Audio] tmpo - Beats Per Minute: 120 +[QuickTime, ItemList, Audio] ©too - Encoder: iTunes v4.9, QuickTime 7.0.1 +[QuickTime, ItemList, Audio] ©lyr - Lyrics: these are lyrics +[QuickTime, ItemList, Audio] ©ART - Artist: Phil Harvey +[QuickTime, ItemList, Author] aART - Album Artist: album artist +[QuickTime, ItemList, Audio] ©wrt - Composer: Composer +[QuickTime, ItemList, Audio] ©alb - Album: album +[QuickTime, ItemList, Audio] ©grp - Grouping: grouping +[QuickTime, ItemList, Audio] ©gen - Genre: Children’s Music +[QuickTime, ItemList, Audio] trkn - Track Number: 1 of 2 +[QuickTime, ItemList, Audio] disk - Disk Number: 1 of 3 +[QuickTime, ItemList, Time] ©day - Content Create Date: 2006 +[QuickTime, ItemList, Audio] ©cmt - Comment: comments +[QuickTime, ItemList, Audio] pgap - Play Gap: No Gap +[QuickTime, ItemList, Preview] covr - Cover Art: (Binary data 19 bytes) +[QuickTime, ItemList, Preview] covr - Cover Art: (Binary data 19 bytes) +[QuickTime, ItemList, Author] auth - Author: aut +[QuickTime, UserData, Video] ©ART - Artist: art diff --git a/ExifTool/t/QuickTime_16.out b/ExifTool/t/QuickTime_16.out new file mode 100644 index 0000000..4db7d6a --- /dev/null +++ b/ExifTool/t/QuickTime_16.out @@ -0,0 +1,2 @@ +[QuickTime, Microsoft, Video] WM/Director - Director: dir1, dir2 +[QuickTime, Microsoft, Video] WM/SharedUserRating - Shared User Rating: 75 diff --git a/ExifTool/t/QuickTime_17.out b/ExifTool/t/QuickTime_17.out new file mode 100644 index 0000000..064587c --- /dev/null +++ b/ExifTool/t/QuickTime_17.out @@ -0,0 +1,2 @@ +[QuickTime, UserData, Location] loci - Location Information: test comment Role=shooting Lat=1.20000 Lon=-2.30000 Alt=100.00 Body=earth Notes=a note +[QuickTime, UserData, Video] rtng - Rating: Entity=ABCD Criteria=1234 a rating diff --git a/ExifTool/t/QuickTime_2.out b/ExifTool/t/QuickTime_2.out new file mode 100644 index 0000000..81db4bc --- /dev/null +++ b/ExifTool/t/QuickTime_2.out @@ -0,0 +1,133 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: QuickTime.mov +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 3.9 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:03:22 19:58:22-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:50-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: MOV +[File, File, Other] FileTypeExtension - File Type Extension: mov +[File, File, Other] MIMEType - MIME Type: video/quicktime +[QuickTime, QuickTime, Video] 0 - Movie Header Version: 0 +[QuickTime, QuickTime, Time] 1 - Create Date: 2005:08:11 14:03:54 +[QuickTime, QuickTime, Time] 2 - Modify Date: 2010:07:30 15:43:59 +[QuickTime, QuickTime, Video] 3 - Time Scale: 600 +[QuickTime, QuickTime, Video] 4 - Duration: 4.97 s +[QuickTime, QuickTime, Video] 5 - Preferred Rate: 1 +[QuickTime, QuickTime, Video] 6 - Preferred Volume: 99.61% +[QuickTime, QuickTime, Video] 9 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, QuickTime, Video] 18 - Preview Time: 0 s +[QuickTime, QuickTime, Video] 19 - Preview Duration: 0 s +[QuickTime, QuickTime, Video] 20 - Poster Time: 0 s +[QuickTime, QuickTime, Video] 21 - Selection Time: 0 s +[QuickTime, QuickTime, Video] 22 - Selection Duration: 0 s +[QuickTime, QuickTime, Video] 23 - Current Time: 0 s +[QuickTime, QuickTime, Video] 24 - Next Track ID: 3 +[QuickTime, Track1, Video] 0 - Track Header Version: 0 +[QuickTime, Track1, Time] 1 - Track Create Date: 2005:08:11 14:03:54 +[QuickTime, Track1, Time] 2 - Track Modify Date: 2005:08:11 14:03:54 +[QuickTime, Track1, Video] 3 - Track ID: 1 +[QuickTime, Track1, Video] 5 - Track Duration: 4.97 s +[QuickTime, Track1, Video] 8 - Track Layer: 0 +[QuickTime, Track1, Video] 9 - Track Volume: 0.00% +[QuickTime, Track1, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track1, Video] 19 - Image Width: 320 +[QuickTime, Track1, Video] 20 - Image Height: 240 +[QuickTime, Track1, Video] clef - Clean Aperture Dimensions: 320x240 +[QuickTime, Track1, Video] prof - Production Aperture Dimensions: 320x240 +[QuickTime, Track1, Video] enof - Encoded Pixels Dimensions: 320x240 +[QuickTime, Track1, Video] 0 - Media Header Version: 0 +[QuickTime, Track1, Time] 1 - Media Create Date: 2005:08:11 14:03:54 +[QuickTime, Track1, Time] 2 - Media Modify Date: 2005:08:11 14:03:54 +[QuickTime, Track1, Video] 3 - Media Time Scale: 600 +[QuickTime, Track1, Video] 4 - Media Duration: 4.97 s +[QuickTime, Track1, Video] 4 - Handler Class: Media Handler +[QuickTime, Track1, Video] 8 - Handler Type: Video Track +[QuickTime, Track1, Video] 12 - Handler Vendor ID: Pentax +[QuickTime, Track1, Video] 2 - Graphics Mode: ditherCopy +[QuickTime, Track1, Video] 3 - Op Color: 32768 32768 32768 +[QuickTime, Track1, Video] 4 - Handler Class: Data Handler +[QuickTime, Track1, Video] 8 - Handler Type: Alias Data +[QuickTime, Track1, Video] 12 - Handler Vendor ID: Pentax +[QuickTime, Track1, Image] 2 - Compressor ID: jpeg +[QuickTime, Track1, Image] 10 - Vendor ID: Pentax +[QuickTime, Track1, Image] 16 - Source Image Width: 320 +[QuickTime, Track1, Image] 17 - Source Image Height: 240 +[QuickTime, Track1, Image] 18 - X Resolution: 72 +[QuickTime, Track1, Image] 20 - Y Resolution: 72 +[QuickTime, Track1, Image] 25 - Compressor Name: Photo - JPEG +[QuickTime, Track1, Image] 41 - Bit Depth: 24 +[QuickTime, Track1, Video] stts - Video Frame Rate: 30 +[QuickTime, Track2, Video] 0 - Track Header Version: 0 +[QuickTime, Track2, Time] 1 - Track Create Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Time] 2 - Track Modify Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Video] 3 - Track ID: 2 +[QuickTime, Track2, Video] 5 - Track Duration: 4.96 s +[QuickTime, Track2, Video] 8 - Track Layer: 0 +[QuickTime, Track2, Video] 9 - Track Volume: 100.00% +[QuickTime, Track2, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track2, Video] 0 - Media Header Version: 0 +[QuickTime, Track2, Time] 1 - Media Create Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Time] 2 - Media Modify Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Video] 3 - Media Time Scale: 7875 +[QuickTime, Track2, Video] 4 - Media Duration: 4.97 s +[QuickTime, Track2, Video] 4 - Handler Class: Media Handler +[QuickTime, Track2, Video] 8 - Handler Type: Audio Track +[QuickTime, Track2, Video] 12 - Handler Vendor ID: Pentax +[QuickTime, Track2, Audio] 2 - Balance: 0 +[QuickTime, Track2, Video] 4 - Handler Class: Data Handler +[QuickTime, Track2, Video] 8 - Handler Type: Alias Data +[QuickTime, Track2, Video] 12 - Handler Vendor ID: Pentax +[QuickTime, Track2, Audio] 4 - Audio Format: raw +[QuickTime, Track2, Audio] 24 - Audio Channels: 1 +[QuickTime, Track2, Audio] 26 - Audio Bits Per Sample: 8 +[QuickTime, Track2, Audio] 32 - Audio Sample Rate: 7875 +[QuickTime, QuickTime, Video] 8 - Handler Type: Metadata Tags +[QuickTime, Keys, Other] album - Album: Ã¥lbum +[QuickTime, Keys, Other] artist - Artist: Ã¥rtist +[QuickTime, Keys, Other] comment - Comment: çømménts +[QuickTime, UserData, Video] ©fmt - Format: Digital Camera +[QuickTime, UserData, Video] ©inf - Information: PENTAX DIGITAL CAMERA +[QuickTime, UserData, Video] ©alb - Album: Ã¥lbum +[QuickTime, UserData, Video] ©ART - Artist: Ã¥rtist +[QuickTime, UserData, Video] ©cmt - Comment: çømménts +[QuickTime, UserData, Video] ©com - Composer: cømpøsér +[QuickTime, UserData, Video] ©gen - Genre: Genré +[QuickTime, QuickTime, Video] 8 - Handler Type: Metadata +[QuickTime, QuickTime, Video] 12 - Handler Vendor ID: Apple +[QuickTime, ItemList, Audio] ©lyr - Lyrics: These are lyrics +[QuickTime, ItemList, Preview] covr - Cover Art: (Binary data 251 bytes) +[QuickTime, ItemList, Audio] ©ART - Artist: Ã¥rtist +[QuickTime, ItemList, Author] aART - Album Artist: Ã¥lbüm Ã¥rtîst +[QuickTime, ItemList, Audio] ©wrt - Composer: cømpøsér +[QuickTime, ItemList, Audio] ©alb - Album: Ã¥lbum +[QuickTime, ItemList, Audio] ©grp - Grouping: grøuping +[QuickTime, ItemList, Audio] ©gen - Genre: Genré +[QuickTime, ItemList, Audio] trkn - Track Number: 1 of 2 +[QuickTime, ItemList, Audio] disk - Disk Number: 3 of 4 +[QuickTime, ItemList, Time] ©day - Content Create Date: 2010 +[QuickTime, ItemList, Audio] ©cmt - Comment: çømménts +[QuickTime, ItemList, Audio] tmpo - Beats Per Minute: 128 +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 0 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 3871 +[MakerNotes, Pentax, Camera] 0 - Make: PENTAX DIGITAL CAMERA +[MakerNotes, Pentax, Camera] 38 - Exposure Time: 1/38 +[MakerNotes, Pentax, Camera] 42 - F Number: 4.0 +[MakerNotes, Pentax, Camera] 50 - Exposure Compensation: 0 +[MakerNotes, Pentax, Camera] 68 - White Balance: Auto +[MakerNotes, Pentax, Camera] 72 - Focal Length: 18.9 mm +[MakerNotes, Pentax, Camera] 175 - ISO: 50 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Adobe XMP Core 4.1-c036 46.277092, Fri Feb 23 2007 14:16:18 +[XMP, XMP-xmpDM, Image] altTapeName - Alt Tape Name: An Alternate Scene Name +[XMP, XMP-xmpDM, Image] album - Album: No Album +[XMP, XMP-xmp, Time] MetadataDate - Metadata Date: 2008:09:12 11:17:39-04:00 +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[Composite, Composite, Image] Exif-Aperture - Aperture: 4.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 320x240 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.077 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/38 +[Composite, Composite, Video] QuickTime-AvgBitrate - Avg Bitrate: 0 bps +[Composite, Composite, Video] QuickTime-Rotation - Rotation: 0 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 18.9 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 10.3 diff --git a/ExifTool/t/QuickTime_3.out b/ExifTool/t/QuickTime_3.out new file mode 100644 index 0000000..db0a2e1 --- /dev/null +++ b/ExifTool/t/QuickTime_3.out @@ -0,0 +1,73 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: QuickTime.m4a +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 5.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:03:22 19:58:22-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:50-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: M4A +[File, File, Other] FileTypeExtension - File Type Extension: m4a +[File, File, Other] MIMEType - MIME Type: audio/mp4 +[QuickTime, QuickTime, Video] 0 - Major Brand: Apple iTunes AAC-LC (.M4A) Audio +[QuickTime, QuickTime, Video] 1 - Minor Version: 0.0.0 +[QuickTime, QuickTime, Video] 2 - Compatible Brands: M4A , mp42, isom +[QuickTime, QuickTime, Video] 0 - Movie Header Version: 0 +[QuickTime, QuickTime, Time] 1 - Create Date: 2005:06:29 19:02:19 +[QuickTime, QuickTime, Time] 2 - Modify Date: 2006:09:22 11:06:45 +[QuickTime, QuickTime, Video] 3 - Time Scale: 600 +[QuickTime, QuickTime, Video] 4 - Duration: 29.05 s +[QuickTime, QuickTime, Video] 5 - Preferred Rate: 1 +[QuickTime, QuickTime, Video] 6 - Preferred Volume: 100.00% +[QuickTime, QuickTime, Video] 9 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, QuickTime, Video] 18 - Preview Time: 0 s +[QuickTime, QuickTime, Video] 19 - Preview Duration: 0 s +[QuickTime, QuickTime, Video] 20 - Poster Time: 0 s +[QuickTime, QuickTime, Video] 21 - Selection Time: 0 s +[QuickTime, QuickTime, Video] 22 - Selection Duration: 0 s +[QuickTime, QuickTime, Video] 23 - Current Time: 0 s +[QuickTime, QuickTime, Video] 24 - Next Track ID: 2 +[QuickTime, Track1, Video] 0 - Track Header Version: 0 +[QuickTime, Track1, Time] 1 - Track Create Date: 2005:06:29 19:02:19 +[QuickTime, Track1, Time] 2 - Track Modify Date: 2006:09:22 11:06:45 +[QuickTime, Track1, Video] 3 - Track ID: 1 +[QuickTime, Track1, Video] 5 - Track Duration: 29.05 s +[QuickTime, Track1, Video] 8 - Track Layer: 0 +[QuickTime, Track1, Video] 9 - Track Volume: 100.00% +[QuickTime, Track1, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track1, Video] 0 - Media Header Version: 0 +[QuickTime, Track1, Time] 1 - Media Create Date: 2005:06:29 19:02:19 +[QuickTime, Track1, Time] 2 - Media Modify Date: 2006:09:22 11:06:45 +[QuickTime, Track1, Video] 3 - Media Time Scale: 32000 +[QuickTime, Track1, Video] 4 - Media Duration: 29.06 s +[QuickTime, Track1, Video] 5 - Media Language Code: und +[QuickTime, Track1, Video] 8 - Handler Type: Audio Track +[QuickTime, Track1, Audio] 2 - Balance: 0 +[QuickTime, Track1, Audio] 4 - Audio Format: mp4a +[QuickTime, Track1, Audio] 24 - Audio Channels: 2 +[QuickTime, Track1, Audio] 26 - Audio Bits Per Sample: 16 +[QuickTime, Track1, Audio] 32 - Audio Sample Rate: 44100 +[QuickTime, QuickTime, Video] 8 - Handler Type: Metadata +[QuickTime, QuickTime, Video] 12 - Handler Vendor ID: Apple +[QuickTime, ItemList, Audio] ©nam - Title: Sample +[QuickTime, ItemList, Audio] cpil - Compilation: No +[QuickTime, ItemList, Audio] tmpo - Beats Per Minute: 120 +[QuickTime, ItemList, Audio] ©too - Encoder: iTunes v4.9, QuickTime 7.0.1 +[QuickTime, iTunes, Audio] iTunNORM - Volume Normalization: 80 0 610 0 289C 0 0 0 0 0 +[QuickTime, ItemList, Audio] ©lyr - Lyrics: these are lyrics +[QuickTime, ItemList, Audio] ©ART - Artist: Phil Harvey +[QuickTime, ItemList, Author] aART - Album Artist: album artist +[QuickTime, ItemList, Audio] ©wrt - Composer: Composer +[QuickTime, ItemList, Audio] ©alb - Album: album +[QuickTime, ItemList, Audio] ©grp - Grouping: grouping +[QuickTime, ItemList, Audio] ©gen - Genre: Children’s Music +[QuickTime, ItemList, Audio] trkn - Track Number: 1 of 2 +[QuickTime, ItemList, Audio] disk - Disk Number: 1 of 3 +[QuickTime, ItemList, Time] ©day - Content Create Date: 2006 +[QuickTime, ItemList, Audio] ©cmt - Comment: comments +[QuickTime, ItemList, Audio] pgap - Play Gap: No Gap +[QuickTime, ItemList, Preview] covr - Cover Art: (Binary data 19 bytes) +[QuickTime, ItemList, Preview] covr - Cover Art: (Binary data 19 bytes) +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 0 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 5237 +[Composite, Composite, Video] QuickTime-AvgBitrate - Avg Bitrate: 0 bps diff --git a/ExifTool/t/QuickTime_4.out b/ExifTool/t/QuickTime_4.out new file mode 100644 index 0000000..3ff263c --- /dev/null +++ b/ExifTool/t/QuickTime_4.out @@ -0,0 +1,16 @@ +[File, System, Time, Main, , System] FileModifyDate - File Modification Date/Time: 2019:05:03 08:54:44-04:00 +[File, System, Time, Main, , System] FileAccessDate - File Access Date/Time: 2019:05:03 08:54:44-04:00 +[File, System, Time, Main, , System] FileInodeChangeDate - File Inode Change Date/Time: 2019:05:03 08:54:44-04:00 +[QuickTime, QuickTime, Time, Main, , MOV-Movie-MovieHeader] 1 - Create Date: 2005:08:11 14:03:54 +[QuickTime, QuickTime, Time, Main, , MOV-Movie-MovieHeader] 2 - Modify Date: 2010:07:30 15:43:59 +[QuickTime, Track1, Time, Main, , MOV-Movie-Track-TrackHeader] 1 - Track Create Date: 2000:01:02 03:04:05 +[QuickTime, Track1, Time, Main, , MOV-Movie-Track-TrackHeader] 2 - Track Modify Date: 2013:11:04 10:32:15 +[QuickTime, Track1, Time, Main, Copy1, MOV-Movie-Track-Media-MediaHeader] 1 - Media Create Date: 2005:08:11 14:03:54 +[QuickTime, Track1, Time, Main, Copy1, MOV-Movie-Track-Media-MediaHeader] 2 - Media Modify Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Time, Main, Copy1, MOV-Movie-Track-TrackHeader] 1 - Track Create Date: 2000:01:02 03:04:05 +[QuickTime, Track2, Time, Main, Copy1, MOV-Movie-Track-TrackHeader] 2 - Track Modify Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Time, Main, , MOV-Movie-Track-Media-MediaHeader] 1 - Media Create Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Time, Main, , MOV-Movie-Track-Media-MediaHeader] 2 - Media Modify Date: 2005:08:11 14:03:54 +[QuickTime, ItemList, Time, Main, , MOV-Movie-UserData-Meta-ItemList] ©day - Content Create Date: 2010 +[XMP, XMP-dc, Image, Main, , MOV-Movie-UserData-XMP] title - Title: x +[XMP, XMP-xmp, Time, Main, , MOV-Movie-UserData-XMP] MetadataDate - Metadata Date: 2008:09:12 11:17:39-04:00 diff --git a/ExifTool/t/QuickTime_5.out b/ExifTool/t/QuickTime_5.out new file mode 100644 index 0000000..7e5c967 --- /dev/null +++ b/ExifTool/t/QuickTime_5.out @@ -0,0 +1,12 @@ +[File, System, Time, Main, , System] FileModifyDate - File Modification Date/Time: 2019:05:03 08:54:44-04:00 +[File, System, Time, Main, , System] FileAccessDate - File Access Date/Time: 2019:05:03 08:54:44-04:00 +[File, System, Time, Main, , System] FileInodeChangeDate - File Inode Change Date/Time: 2019:05:03 08:54:44-04:00 +[QuickTime, QuickTime, Time, Main, , MOV-Movie-MovieHeader] 1 - Create Date: 2005:06:29 19:02:19 +[QuickTime, QuickTime, Time, Main, , MOV-Movie-MovieHeader] 2 - Modify Date: 2006:09:22 11:06:45 +[QuickTime, Track1, Time, Main, , MOV-Movie-Track-TrackHeader] 1 - Track Create Date: 2000:01:02 03:04:05 +[QuickTime, Track1, Time, Main, , MOV-Movie-Track-TrackHeader] 2 - Track Modify Date: 2013:11:04 10:32:15 +[QuickTime, Track1, Time, Main, , MOV-Movie-Track-Media-MediaHeader] 1 - Media Create Date: 2005:06:29 19:02:19 +[QuickTime, Track1, Time, Main, , MOV-Movie-Track-Media-MediaHeader] 2 - Media Modify Date: 2006:09:22 11:06:45 +[QuickTime, ItemList, Audio, Main, Copy1, MOV-Movie-UserData-Meta-ItemList] ©nam - Title: Sample +[QuickTime, ItemList, Time, Main, , MOV-Movie-UserData-Meta-ItemList] ©day - Content Create Date: 2006 +[XMP, XMP-dc, Image, Main, , MOV-XMP] title - Title: x diff --git a/ExifTool/t/QuickTime_6.out b/ExifTool/t/QuickTime_6.out new file mode 100644 index 0000000..fabc93c --- /dev/null +++ b/ExifTool/t/QuickTime_6.out @@ -0,0 +1,4 @@ +[QuickTime, QuickTime, Video] 9 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track1, Video] 10 - Matrix Structure: 0 -1 0 1 0 0 0 320 1 +[QuickTime, Track2, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[Composite, Composite, Video] QuickTime-Rotation - Rotation: 270 diff --git a/ExifTool/t/QuickTime_7.out b/ExifTool/t/QuickTime_7.out new file mode 100644 index 0000000..1880bdb --- /dev/null +++ b/ExifTool/t/QuickTime_7.out @@ -0,0 +1,27 @@ +[QuickTime, Keys, Other] artist - Artist: me +[QuickTime, Keys, Other] comment - Comment: çømménts +[QuickTime, Keys, Location] direction.facing - Camera Direction: 90 +[QuickTime, Keys, Other] director - Director: director +[QuickTime, UserData, Video] ©fmt - Format: Digital Camera +[QuickTime, UserData, Video] ©inf - Information: PENTAX DIGITAL CAMERA +[QuickTime, UserData, Video] ©alb - Album: albumA +[QuickTime, UserData, Video] ©ART - Artist: me +[QuickTime, UserData, Video] ©cmt - Comment: çømménts +[QuickTime, UserData, Video] ©com - Composer: cømpøsér +[QuickTime, UserData, Video] ©gen - Genre: rock +[QuickTime, ItemList, Audio] ©lyr - Lyrics: These are lyrics +[QuickTime, ItemList, Preview] covr - Cover Art: (Binary data 251 bytes) +[QuickTime, ItemList, Audio] ©ART - Artist: me +[QuickTime, ItemList, Author] aART - Album Artist: Ã¥lbüm Ã¥rtîst +[QuickTime, ItemList, Audio] ©wrt - Composer: cømpøsér +[QuickTime, ItemList, Audio] ©alb - Album: albumB +[QuickTime, ItemList, Audio] ©grp - Grouping: grøuping +[QuickTime, ItemList, Audio] ©gen - Genre: Genré +[QuickTime, ItemList, Audio] trkn - Track Number: 1 of 2 +[QuickTime, ItemList, Audio] disk - Disk Number: 3 of 4 +[QuickTime, ItemList, Time] ©day - Content Create Date: 2010 +[QuickTime, ItemList, Audio] ©cmt - Comment: çømménts +[QuickTime, ItemList, Audio] ©cmt-fra-FR - Comment (fra-FR): fr comment +[QuickTime, ItemList, Audio] tmpo - Beats Per Minute: 128 +[QuickTime, ItemList, Audio] albm - Album: albumC +[QuickTime, UserData, Video] ©mod - Model: model diff --git a/ExifTool/t/QuickTime_8.out b/ExifTool/t/QuickTime_8.out new file mode 100644 index 0000000..f75f13c --- /dev/null +++ b/ExifTool/t/QuickTime_8.out @@ -0,0 +1,96 @@ +[QuickTime, QuickTime, Video] 0 - Movie Header Version: 0 +[QuickTime, QuickTime, Time] 1 - Create Date: 2005:08:11 14:03:54 +[QuickTime, QuickTime, Time] 2 - Modify Date: 2010:07:30 15:43:59 +[QuickTime, QuickTime, Video] 3 - Time Scale: 600 +[QuickTime, QuickTime, Video] 4 - Duration: 4.97 s +[QuickTime, QuickTime, Video] 5 - Preferred Rate: 1 +[QuickTime, QuickTime, Video] 6 - Preferred Volume: 99.61% +[QuickTime, QuickTime, Video] 9 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, QuickTime, Video] 18 - Preview Time: 0 s +[QuickTime, QuickTime, Video] 19 - Preview Duration: 0 s +[QuickTime, QuickTime, Video] 20 - Poster Time: 0 s +[QuickTime, QuickTime, Video] 21 - Selection Time: 0 s +[QuickTime, QuickTime, Video] 22 - Selection Duration: 0 s +[QuickTime, QuickTime, Video] 23 - Current Time: 0 s +[QuickTime, QuickTime, Video] 24 - Next Track ID: 3 +[QuickTime, Track1, Video] 0 - Track Header Version: 0 +[QuickTime, Track1, Time] 1 - Track Create Date: 2005:08:11 14:03:54 +[QuickTime, Track1, Time] 2 - Track Modify Date: 2005:08:11 14:03:54 +[QuickTime, Track1, Video] 3 - Track ID: 1 +[QuickTime, Track1, Video] 5 - Track Duration: 4.97 s +[QuickTime, Track1, Video] 8 - Track Layer: 0 +[QuickTime, Track1, Video] 9 - Track Volume: 0.00% +[QuickTime, Track1, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track1, Video] 19 - Image Width: 320 +[QuickTime, Track1, Video] 20 - Image Height: 240 +[QuickTime, Track1, Video] clef - Clean Aperture Dimensions: 320x240 +[QuickTime, Track1, Video] prof - Production Aperture Dimensions: 320x240 +[QuickTime, Track1, Video] enof - Encoded Pixels Dimensions: 320x240 +[QuickTime, Track1, Video] edts - Unknown edts: (Binary data 28 bytes) +[QuickTime, Track1, Video] 0 - Media Header Version: 0 +[QuickTime, Track1, Time] 1 - Media Create Date: 2005:08:11 14:03:54 +[QuickTime, Track1, Time] 2 - Media Modify Date: 2005:08:11 14:03:54 +[QuickTime, Track1, Video] 3 - Media Time Scale: 600 +[QuickTime, Track1, Video] 4 - Media Duration: 4.97 s +[QuickTime, Track1, Video] 4 - Handler Class: Media Handler +[QuickTime, Track1, Video] 8 - Handler Type: Video Track +[QuickTime, Track1, Video] 12 - Handler Vendor ID: Pentax +[QuickTime, Track1, Video] 2 - Graphics Mode: ditherCopy +[QuickTime, Track1, Video] 3 - Op Color: 32768 32768 32768 +[QuickTime, Track1, Video] 4 - Handler Class: Data Handler +[QuickTime, Track1, Video] 8 - Handler Type: Alias Data +[QuickTime, Track1, Video] 12 - Handler Vendor ID: Pentax +[QuickTime, Track1, Other] alis - Unknown alis: (Binary data 4 bytes) +[QuickTime, Track1, Image] 2 - Compressor ID: jpeg +[QuickTime, Track1, Image] 10 - Vendor ID: Pentax +[QuickTime, Track1, Image] 16 - Source Image Width: 320 +[QuickTime, Track1, Image] 17 - Source Image Height: 240 +[QuickTime, Track1, Image] 18 - X Resolution: 72 +[QuickTime, Track1, Image] 20 - Y Resolution: 72 +[QuickTime, Track1, Image] 25 - Compressor Name: Photo - JPEG +[QuickTime, Track1, Image] 41 - Bit Depth: 24 +[QuickTime, Track1, Video] stts - Video Frame Rate: 30 +[QuickTime, Track1, Video] stsc - Sample To Chunk: (Binary data 32 bytes) +[QuickTime, Track1, Video] stsz - Sample Sizes: (Binary data 608 bytes) +[QuickTime, Track1, Video] stco - Chunk Offset: (Binary data 28 bytes) +[QuickTime, Track2, Video] 0 - Track Header Version: 0 +[QuickTime, Track2, Time] 1 - Track Create Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Time] 2 - Track Modify Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Video] 3 - Track ID: 2 +[QuickTime, Track2, Video] 5 - Track Duration: 4.96 s +[QuickTime, Track2, Video] 8 - Track Layer: 0 +[QuickTime, Track2, Video] 9 - Track Volume: 100.00% +[QuickTime, Track2, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track2, Video] edts - Unknown edts: (Binary data 28 bytes) +[QuickTime, Track2, Video] 0 - Media Header Version: 0 +[QuickTime, Track2, Time] 1 - Media Create Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Time] 2 - Media Modify Date: 2005:08:11 14:03:54 +[QuickTime, Track2, Video] 3 - Media Time Scale: 7875 +[QuickTime, Track2, Video] 4 - Media Duration: 4.97 s +[QuickTime, Track2, Video] 4 - Handler Class: Media Handler +[QuickTime, Track2, Video] 8 - Handler Type: Audio Track +[QuickTime, Track2, Video] 12 - Handler Vendor ID: Pentax +[QuickTime, Track2, Audio] 2 - Balance: 0 +[QuickTime, Track2, Video] 4 - Handler Class: Data Handler +[QuickTime, Track2, Video] 8 - Handler Type: Alias Data +[QuickTime, Track2, Video] 12 - Handler Vendor ID: Pentax +[QuickTime, Track2, Other] alis - Unknown alis: (Binary data 4 bytes) +[QuickTime, Track2, Audio] 4 - Audio Format: raw +[QuickTime, Track2, Audio] 24 - Audio Channels: 1 +[QuickTime, Track2, Audio] 26 - Audio Bits Per Sample: 8 +[QuickTime, Track2, Audio] 32 - Audio Sample Rate: 7875 +[QuickTime, Track2, Video] stts - Time To Sample Table: (Binary data 16 bytes) +[QuickTime, Track2, Video] stsc - Sample To Chunk: (Binary data 32 bytes) +[QuickTime, Track2, Video] stsz - Sample Sizes: (Binary data 12 bytes) +[QuickTime, Track2, Video] stco - Chunk Offset: (Binary data 28 bytes) +[QuickTime, QuickTime, Video] 8 - Handler Type: Metadata Tags +[QuickTime, Keys, Other] director - Director: dir +[QuickTime, QuickTime, Video] 8 - Handler Type: Metadata +[QuickTime, QuickTime, Video] 12 - Handler Vendor ID: Apple +[QuickTime, ItemList, Audio] ©ART - Artist: me +[QuickTime, UserData, Video] ©arg - Arranger: arr +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 0 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 4830 +[QuickTime, QuickTime, Video] mdat - Media Data: (Binary data 0 bytes) +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.27 +[XMP, XMP-tiff, Author] Artist - Artist: me diff --git a/ExifTool/t/QuickTime_9.out b/ExifTool/t/QuickTime_9.out new file mode 100644 index 0000000..0fc529b --- /dev/null +++ b/ExifTool/t/QuickTime_9.out @@ -0,0 +1,54 @@ +[QuickTime, QuickTime, Video] 0 - Major Brand: Apple iTunes AAC-LC (.M4A) Audio +[QuickTime, QuickTime, Video] 1 - Minor Version: 0.0.0 +[QuickTime, QuickTime, Video] 2 - Compatible Brands: M4A , mp42, isom +[QuickTime, QuickTime, Video] 0 - Movie Header Version: 0 +[QuickTime, QuickTime, Time] 1 - Create Date: 2005:06:29 19:02:19 +[QuickTime, QuickTime, Time] 2 - Modify Date: 2006:09:22 11:06:45 +[QuickTime, QuickTime, Video] 3 - Time Scale: 600 +[QuickTime, QuickTime, Video] 4 - Duration: 29.05 s +[QuickTime, QuickTime, Video] 5 - Preferred Rate: 1 +[QuickTime, QuickTime, Video] 6 - Preferred Volume: 100.00% +[QuickTime, QuickTime, Video] 9 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, QuickTime, Video] 18 - Preview Time: 0 s +[QuickTime, QuickTime, Video] 19 - Preview Duration: 0 s +[QuickTime, QuickTime, Video] 20 - Poster Time: 0 s +[QuickTime, QuickTime, Video] 21 - Selection Time: 0 s +[QuickTime, QuickTime, Video] 22 - Selection Duration: 0 s +[QuickTime, QuickTime, Video] 23 - Current Time: 0 s +[QuickTime, QuickTime, Video] 24 - Next Track ID: 2 +[QuickTime, Track1, Video] 0 - Track Header Version: 0 +[QuickTime, Track1, Time] 1 - Track Create Date: 2005:06:29 19:02:19 +[QuickTime, Track1, Time] 2 - Track Modify Date: 2006:09:22 11:06:45 +[QuickTime, Track1, Video] 3 - Track ID: 1 +[QuickTime, Track1, Video] 5 - Track Duration: 29.05 s +[QuickTime, Track1, Video] 8 - Track Layer: 0 +[QuickTime, Track1, Video] 9 - Track Volume: 100.00% +[QuickTime, Track1, Video] 10 - Matrix Structure: 1 0 0 0 1 0 0 0 1 +[QuickTime, Track1, Video] 0 - Media Header Version: 0 +[QuickTime, Track1, Time] 1 - Media Create Date: 2005:06:29 19:02:19 +[QuickTime, Track1, Time] 2 - Media Modify Date: 2006:09:22 11:06:45 +[QuickTime, Track1, Video] 3 - Media Time Scale: 32000 +[QuickTime, Track1, Video] 4 - Media Duration: 29.06 s +[QuickTime, Track1, Video] 5 - Media Language Code: und +[QuickTime, Track1, Video] 8 - Handler Type: Audio Track +[QuickTime, Track1, Audio] 2 - Balance: 0 +[QuickTime, Track1, Audio] 4 - Audio Format: mp4a +[QuickTime, Track1, Audio] 24 - Audio Channels: 2 +[QuickTime, Track1, Audio] 26 - Audio Bits Per Sample: 16 +[QuickTime, Track1, Audio] 32 - Audio Sample Rate: 44100 +[QuickTime, Track1, Audio] esds - Unknown esds: (Binary data 43 bytes) +[QuickTime, Track1, Video] stts - Time To Sample Table: (Binary data 16 bytes) +[QuickTime, Track1, Video] stsc - Sample To Chunk: (Binary data 32 bytes) +[QuickTime, Track1, Video] stsz - Sample Sizes: (Binary data 3644 bytes) +[QuickTime, Track1, Video] stco - Chunk Offset: (Binary data 184 bytes) +[QuickTime, QuickTime, Video] 8 - Handler Type: Metadata +[QuickTime, QuickTime, Video] 12 - Handler Vendor ID: Apple +[QuickTime, ItemList, Audio] ©ART - Artist: me +[QuickTime, UserData, Video] ©arg - Arranger: arr +[QuickTime, QuickTime, Video] 8 - Handler Type: Metadata Tags +[QuickTime, Keys, Other] director - Director: dir +[QuickTime, QuickTime, Video] mdat-size - Media Data Size: 0 +[QuickTime, QuickTime, Video] mdat-offset - Media Data Offset: 7449 +[QuickTime, QuickTime, Video] mdat - Media Data: (Binary data 0 bytes) +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.27 +[XMP, XMP-tiff, Author] Artist - Artist: me diff --git a/ExifTool/t/RIFF.t b/ExifTool/t/RIFF.t new file mode 100644 index 0000000..14742c9 --- /dev/null +++ b/ExifTool/t/RIFF.t @@ -0,0 +1,81 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/RIFF.t". + +BEGIN { + $| = 1; print "1..7\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::RIFF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'RIFF'; +my $testnum = 1; + +# tests 2-4: Extract information from RIFF.wav, RIFF.avi and RIFF.webp +{ + my $ext; + foreach $ext (qw(wav avi webp)) { + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo("t/images/RIFF.$ext"); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +# test 5: Edit EXIF and XMP +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('exif:usercomment' => 'test comment'); + $exifTool->SetNewValue('xmp:description' => 'test description'); + $testfile = "t/${testname}_${testnum}_failed.webp"; + unlink $testfile; + writeInfo($exifTool, 't/images/RIFF.webp', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 6: Delete all metadata from a WebP file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('all'); + $testfile = "t/${testname}_${testnum}_failed.webp"; + unlink $testfile; + writeInfo($exifTool, 't/images/RIFF.webp', $testfile); + my $info = $exifTool->ImageInfo($testfile); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 7: Add back WebP information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('exif:usercomment' => 'test comment 2'); + $exifTool->SetNewValue('xmp:description' => 'test description 2'); + my $testfile2 = "t/${testname}_${testnum}_failed.webp"; + unlink $testfile2; + writeInfo($exifTool, $testfile, $testfile2); + my $info = $exifTool->ImageInfo($testfile2); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + unlink $testfile2; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/RIFF_2.out b/ExifTool/t/RIFF_2.out new file mode 100644 index 0000000..3691cd9 --- /dev/null +++ b/ExifTool/t/RIFF_2.out @@ -0,0 +1,25 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: RIFF.wav +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 224 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:01 09:26:57-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:20-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rwxr-xr-x +[File, File, Other] FileType - File Type: WAV +[File, File, Other] FileTypeExtension - File Type Extension: wav +[File, File, Other] MIMEType - MIME Type: audio/x-wav +[RIFF, RIFF, Audio] 0 - Encoding: Microsoft PCM +[RIFF, RIFF, Audio] 1 - Num Channels: 1 +[RIFF, RIFF, Audio] 2 - Sample Rate: 7872 +[RIFF, RIFF, Audio] 4 - Avg Bytes Per Sec: 7872 +[RIFF, RIFF, Audio] 7 - Bits Per Sample: 8 +[RIFF, RIFF, Time] ICRD - Date Created: 2005:08:08 +[RIFF, RIFF, Audio] ever - Exif Version: 0220 +[RIFF, RIFF, Audio] erel - Related Image File: IMGP1149.JPG +[RIFF, RIFF, Time] etim - Time Created: 16:23:35 +[RIFF, RIFF, Camera] ecor - Make: PENTAX Corporation +[RIFF, RIFF, Camera] emdl - Camera Model Name: PENTAX Optio WP +[RIFF, RIFF, Audio] emnt - Maker Notes: (Binary data 16 bytes) +[Composite, Composite, Time] Exif-DateTimeOriginal - Date/Time Original: 2005:08:08 16:23:35 +[Composite, Composite, Other] RIFF-Duration2 - Duration: 0.03 s diff --git a/ExifTool/t/RIFF_3.out b/ExifTool/t/RIFF_3.out new file mode 100644 index 0000000..339489f --- /dev/null +++ b/ExifTool/t/RIFF_3.out @@ -0,0 +1,55 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: RIFF.avi +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1262 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2008:09:12 12:04:10-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:20-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:01:05 09:06:26-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: AVI +[File, File, Other] FileTypeExtension - File Type Extension: avi +[File, File, Other] MIMEType - MIME Type: video/x-msvideo +[File, File, Image] 0 - BMP Version: Windows V3 +[File, File, Image] 4 - Image Width: 320 +[File, File, Image] 8 - Image Height: 240 +[File, File, Image] 12 - Planes: 1 +[File, File, Image] 14 - Bit Depth: 24 +[File, File, Image] 16 - Compression: MJPG +[File, File, Image] 20 - Image Length: 230400 +[File, File, Image] 24 - Pixels Per Meter X: 0 +[File, File, Image] 28 - Pixels Per Meter Y: 0 +[File, File, Image] 32 - Num Colors: Use BitDepth +[File, File, Image] 36 - Num Important Colors: All +[RIFF, RIFF, Video] 0 - Frame Rate: 15 +[RIFF, RIFF, Video] 1 - Max Data Rate: 201.2 kB/s +[RIFF, RIFF, Video] 4 - Frame Count: 233 +[RIFF, RIFF, Video] 6 - Stream Count: 2 +[RIFF, RIFF, Video] 8 - Image Width: 320 +[RIFF, RIFF, Video] 9 - Image Height: 240 +[RIFF, RIFF, Video] 0 - Stream Type: Video +[RIFF, RIFF, Video] 1 - Video Codec: mjpg +[RIFF, RIFF, Video] 5 - Video Frame Rate: 15 +[RIFF, RIFF, Video] 8 - Video Frame Count: 233 +[RIFF, RIFF, Video] 10 - Quality: 10000 +[RIFF, RIFF, Video] 11 - Sample Size: Variable +[RIFF, RIFF, Video] 0 - Stream Type: Audio +[RIFF, RIFF, Video] 1 - Audio Codec: +[RIFF, RIFF, Video] 5 - Audio Sample Rate: 11024 +[RIFF, RIFF, Video] 8 - Audio Sample Count: 171240 +[RIFF, RIFF, Video] 10 - Quality: 10000 +[RIFF, RIFF, Video] 11 - Sample Size: 1 byte +[RIFF, RIFF, Audio] 0 - Encoding: Microsoft PCM +[RIFF, RIFF, Audio] 1 - Num Channels: 1 +[RIFF, RIFF, Audio] 2 - Sample Rate: 11024 +[RIFF, RIFF, Audio] 4 - Avg Bytes Per Sec: 11024 +[RIFF, RIFF, Audio] 7 - Bits Per Sample: 8 +[RIFF, RIFF, Time] IDIT - Date/Time Original: 2003:03:10 15:04:43 +[RIFF, RIFF, Audio] ISFT - Software: CanonMVI01 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Adobe XMP Core 4.1-c036 46.277092, Fri Feb 23 2007 14:16:18 +[XMP, XMP-xmpDM, Image] altTapeName - Alt Tape Name: An Alternate Scene Name +[XMP, XMP-xmpDM, Image] album - Album: No Album +[XMP, XMP-xmp, Time] MetadataDate - Metadata Date: 2008:09:12 11:17:39-04:00 +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[Composite, Composite, Image] Exif-ImageSize - Image Size: 320x240 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.077 +[Composite, Composite, Other] RIFF-Duration - Duration: 15.53 s diff --git a/ExifTool/t/RIFF_4.out b/ExifTool/t/RIFF_4.out new file mode 100644 index 0000000..badad92 --- /dev/null +++ b/ExifTool/t/RIFF_4.out @@ -0,0 +1,32 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.46 +[File, System, Other] FileName - File Name: RIFF.webp +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 586 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:09:26 15:48:22-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:09:28 21:44:09-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:26 21:32:38-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: Extended WEBP +[File, File, Other] FileTypeExtension - File Type Extension: webp +[File, File, Other] MIMEType - MIME Type: image/webp +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[RIFF, RIFF, Image] 0 - WebP Flags: XMP, EXIF, Alpha +[RIFF, RIFF, Image] 4 - Image Width: 1 +[RIFF, RIFF, Image] 6 - Image Height: 1 +[RIFF, RIFF, Image] 0 - Alpha Preprocessing: Level Reduction +[RIFF, RIFF, Image] 0.1 - Alpha Filtering: Horizontal +[RIFF, RIFF, Image] 0.2 - Alpha Compression: Lossless +[RIFF, RIFF, Image] 0 - VP8 Version: 2 (bilinear reconstruction, no loop) +[RIFF, RIFF, Image] 6 - Image Width: 1 +[RIFF, RIFF, Image] 6.1 - Horizontal Scale: 0 +[RIFF, RIFF, Image] 8 - Image Height: 1 +[RIFF, RIFF, Image] 8.1 - Vertical Scale: 0 +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: me +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.46 +[XMP, XMP-dc, Image] subject - Subject: test +[Composite, Composite, Image] Exif-ImageSize - Image Size: 1x1 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000001 diff --git a/ExifTool/t/RIFF_5.out b/ExifTool/t/RIFF_5.out new file mode 100644 index 0000000..a0856f9 --- /dev/null +++ b/ExifTool/t/RIFF_5.out @@ -0,0 +1,38 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.46 +[File, System, Other] FileName - File Name: RIFF_5_failed.webp +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.3 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:09:29 07:38:50-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:09:29 07:38:50-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:29 07:38:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: Extended WEBP +[File, File, Other] FileTypeExtension - File Type Extension: webp +[File, File, Other] MIMEType - MIME Type: image/webp +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[RIFF, RIFF, Image] 0 - WebP Flags: XMP, EXIF, Alpha +[RIFF, RIFF, Image] 4 - Image Width: 1 +[RIFF, RIFF, Image] 6 - Image Height: 1 +[RIFF, RIFF, Image] 0 - Alpha Preprocessing: Level Reduction +[RIFF, RIFF, Image] 0.1 - Alpha Filtering: Horizontal +[RIFF, RIFF, Image] 0.2 - Alpha Compression: Lossless +[RIFF, RIFF, Image] 0 - VP8 Version: 2 (bilinear reconstruction, no loop) +[RIFF, RIFF, Image] 6 - Image Width: 1 +[RIFF, RIFF, Image] 6.1 - Horizontal Scale: 0 +[RIFF, RIFF, Image] 8 - Image Height: 1 +[RIFF, RIFF, Image] 8.1 - Vertical Scale: 0 +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: me +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0232 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37510 - User Comment: test comment +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.46 +[XMP, XMP-dc, Image] description - Description: test description +[XMP, XMP-dc, Image] subject - Subject: test +[Composite, Composite, Image] Exif-ImageSize - Image Size: 1x1 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000001 diff --git a/ExifTool/t/RIFF_6.out b/ExifTool/t/RIFF_6.out new file mode 100644 index 0000000..7a6a52e --- /dev/null +++ b/ExifTool/t/RIFF_6.out @@ -0,0 +1,24 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.46 +[File, System, Other] FileName - File Name: RIFF_6_failed.webp +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 82 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:09:29 07:38:50-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:09:29 07:38:50-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:29 07:38:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: Extended WEBP +[File, File, Other] FileTypeExtension - File Type Extension: webp +[File, File, Other] MIMEType - MIME Type: image/webp +[RIFF, RIFF, Image] 0 - WebP Flags: Alpha +[RIFF, RIFF, Image] 4 - Image Width: 1 +[RIFF, RIFF, Image] 6 - Image Height: 1 +[RIFF, RIFF, Image] 0 - Alpha Preprocessing: Level Reduction +[RIFF, RIFF, Image] 0.1 - Alpha Filtering: Horizontal +[RIFF, RIFF, Image] 0.2 - Alpha Compression: Lossless +[RIFF, RIFF, Image] 0 - VP8 Version: 2 (bilinear reconstruction, no loop) +[RIFF, RIFF, Image] 6 - Image Width: 1 +[RIFF, RIFF, Image] 6.1 - Horizontal Scale: 0 +[RIFF, RIFF, Image] 8 - Image Height: 1 +[RIFF, RIFF, Image] 8.1 - Vertical Scale: 0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 1x1 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000001 diff --git a/ExifTool/t/RIFF_7.out b/ExifTool/t/RIFF_7.out new file mode 100644 index 0000000..eeb3a38 --- /dev/null +++ b/ExifTool/t/RIFF_7.out @@ -0,0 +1,36 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.46 +[File, System, Other] FileName - File Name: RIFF_7_failed.webp +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:09:29 07:38:50-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:09:29 07:38:50-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:29 07:38:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: Extended WEBP +[File, File, Other] FileTypeExtension - File Type Extension: webp +[File, File, Other] MIMEType - MIME Type: image/webp +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[RIFF, RIFF, Image] 0 - WebP Flags: XMP, EXIF, Alpha +[RIFF, RIFF, Image] 4 - Image Width: 1 +[RIFF, RIFF, Image] 6 - Image Height: 1 +[RIFF, RIFF, Image] 0 - Alpha Preprocessing: Level Reduction +[RIFF, RIFF, Image] 0.1 - Alpha Filtering: Horizontal +[RIFF, RIFF, Image] 0.2 - Alpha Compression: Lossless +[RIFF, RIFF, Image] 0 - VP8 Version: 2 (bilinear reconstruction, no loop) +[RIFF, RIFF, Image] 6 - Image Width: 1 +[RIFF, RIFF, Image] 6.1 - Horizontal Scale: 0 +[RIFF, RIFF, Image] 8 - Image Height: 1 +[RIFF, RIFF, Image] 8.1 - Vertical Scale: 0 +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0232 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37510 - User Comment: test comment 2 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.46 +[XMP, XMP-dc, Image] description - Description: test description 2 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 1x1 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000001 diff --git a/ExifTool/t/RTF.t b/ExifTool/t/RTF.t new file mode 100644 index 0000000..c3b8d90 --- /dev/null +++ b/ExifTool/t/RTF.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/RTF.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::RTF; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'RTF'; +my $testnum = 1; + +# test 2: Extract information from an RTF file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/RTF.rtf'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/RTF_2.out b/ExifTool/t/RTF_2.out new file mode 100644 index 0000000..fdf9544 --- /dev/null +++ b/ExifTool/t/RTF_2.out @@ -0,0 +1,21 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: RTF.rtf +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 990 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:06:20 06:28:27-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: RTF +[File, File, Other] FileTypeExtension - File Type Extension: rtf +[File, File, Other] MIMEType - MIME Type: text/rtf +[RTF, RTF, Document] title - Title: ExifTool RTF test (with spécîål çhars) +[RTF, RTF, Time] revtim - Modify Date: 2010:06:19 20:00:00 +[RTF, RTF, Document] subject - Subject: a “subject†in funny quotes +[RTF, RTF, Document] doccomm - Comments: ストロボ.フラッシュ未発光ã€å¼·åˆ¶ç™ºå…‰ãƒ¢ãƒ¼ãƒ‰(end);another line +[RTF, RTF, Author] author - Author: an author {with braces} +[RTF, RTF, Document] company - Company: a company \with backslashes and leading space\ +[RTF, RTF, Author] copyright - Copyright: © 2010 Phil Harvey +[RTF, RTF, Document] keywords - Keywords: keyword1, keyword2 +[RTF, RTF, Document] CustomString - Custom String: ABCDEF +[RTF, RTF, Document] CustomNumber - Custom Number: 123456 diff --git a/ExifTool/t/Radiance.t b/ExifTool/t/Radiance.t new file mode 100644 index 0000000..26a6d85 --- /dev/null +++ b/ExifTool/t/Radiance.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Radiance.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Radiance; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Radiance'; +my $testnum = 1; + +# test 2: Extract information from RGBE file +{ + my $exifTool = Image::ExifTool->new; + ++$testnum; + my $info = $exifTool->ImageInfo('t/images/Radiance.hdr'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Radiance_2.out b/ExifTool/t/Radiance_2.out new file mode 100644 index 0000000..01d61ea --- /dev/null +++ b/ExifTool/t/Radiance_2.out @@ -0,0 +1,24 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Radiance.hdr +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 587 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2011:12:10 14:16:13-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:19-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: HDR +[File, File, Other] FileTypeExtension - File Type Extension: hdr +[File, File, Other] MIMEType - MIME Type: image/vnd.radiance +[File, File, Image] ImageHeight - Image Height: 1 +[File, File, Image] ImageWidth - Image Width: 1 +[Radiance, Radiance, Image] _command - Command: oconv mat.rad sky.rad surfaces.rad +[Radiance, Radiance, Image] _command - Command: oconv -f -i test4.oct ila01728 +[Radiance, Radiance, Image] _command - Command: rpict -t 30 -vf test4.vf -x 1536 -y 1536 -ps 3 -pt .04 -dp 4096 -ar 68 -ms 0.029 -ds .2 -dt .05 -dc .75 -dr 3 -sj 1 -st .01 -ab 3 -af test4.amb -aa .08 -ad 1024 -as 512 -av 0.5 0.5 0.5 -lr 12 -lw .0005 -st .001 -dj .02 +[Radiance, Radiance, Image] software - Software: RADIANCE 3.1.8 lastmod Thu Sep 17 20:49:56 PDT 1998 by droberts on escher +[Radiance, Radiance, Image] view - View: -vtv -vp 0.832108 2.26053 1.8 -vd 0.984193 0.177099 0 -vu 0 0 1 -vh 100 -vv 100 -vo 0 -va 0 -vs 0 -vl 0 +[Radiance, Radiance, Image] format - Format: 32-bit_rle_rgbe +[Radiance, Radiance, Image] _command - Command: pfilt -1 -e 1 -m .25 -2 -x /3 -y /3 +[Radiance, Radiance, Image] exposure - Exposure: 3.512179e-001 +[Radiance, Radiance, Image] _orient - Orientation: Horizontal (normal) +[Composite, Composite, Image] Exif-ImageSize - Image Size: 1x1 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000001 diff --git a/ExifTool/t/Real.t b/ExifTool/t/Real.t new file mode 100644 index 0000000..4e50d9b --- /dev/null +++ b/ExifTool/t/Real.t @@ -0,0 +1,46 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Real.t". + +BEGIN { + $| = 1; print "1..4\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Real; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Real'; +my $testnum = 1; + +# test 2: Extract information from a RealMedia file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Real.rm'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Extract information from a RealAudio file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Real.ra'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: Extract information from a RealAudio Metafile +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Real.ram'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Real_2.out b/ExifTool/t/Real_2.out new file mode 100644 index 0000000..c0124c0 --- /dev/null +++ b/ExifTool/t/Real_2.out @@ -0,0 +1,69 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Real.rm +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1915 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:05:24 08:37:37-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:19-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: RM +[File, File, Other] FileTypeExtension - File Type Extension: rm +[File, File, Other] MIMEType - MIME Type: audio/x-pn-realaudio +[Real, Real-PROP, Video] 0 - Max Bitrate: 32 kbps +[Real, Real-PROP, Video] 1 - Avg Bitrate: 32 kbps +[Real, Real-PROP, Video] 2 - Max Packet Size: 465 +[Real, Real-PROP, Video] 3 - Avg Packet Size: 465 +[Real, Real-PROP, Video] 4 - Num Packets: 16 +[Real, Real-PROP, Video] 5 - Duration: 0.10 s +[Real, Real-PROP, Video] 6 - Preroll: 1.86 s +[Real, Real-PROP, Video] 9 - Num Streams: 2 +[Real, Real-PROP, Video] 10 - Flags: Allow Recording, Allow Download +[Real, Real-MDPR, Video] 0 - Stream Number: 0 +[Real, Real-MDPR, Video] 1 - Stream Max Bitrate: 32 kbps +[Real, Real-MDPR, Video] 2 - Stream Avg Bitrate: 32 kbps +[Real, Real-MDPR, Video] 3 - Stream Max Packet Size: 465 +[Real, Real-MDPR, Video] 4 - Stream Avg Packet Size: 465 +[Real, Real-MDPR, Video] 5 - Stream Start Time: 0 +[Real, Real-MDPR, Video] 6 - Stream Preroll: 1.86 s +[Real, Real-MDPR, Video] 7 - Stream Duration: 1.86 s +[Real, Real-MDPR, Video] 9 - Stream Name: Audio Stream +[Real, Real-MDPR, Video] 11 - Stream Mime Type: audio/x-pn-realaudio +[Real, Real-MDPR2, Video] 0 - Stream Number: 1 +[Real, Real-MDPR2, Video] 1 - Stream Max Bitrate: 0 bps +[Real, Real-MDPR2, Video] 2 - Stream Avg Bitrate: 0 bps +[Real, Real-MDPR2, Video] 3 - Stream Max Packet Size: 0 +[Real, Real-MDPR2, Video] 4 - Stream Avg Packet Size: 0 +[Real, Real-MDPR2, Video] 5 - Stream Start Time: 0 +[Real, Real-MDPR2, Video] 6 - Stream Preroll: 0 s +[Real, Real-MDPR2, Video] 7 - Stream Duration: 0 s +[Real, Real-MDPR2, Video] 11 - Stream Mime Type: logical-fileinfo +[Real, Real-MDPR2, Video] 14 - File Info Version: 0 +[Real, Real-MDPR2, Video] Content Rating - Content Rating: All Ages +[Real, Real-MDPR2, Video] Audiences - Audiences: 56k Dial-up; +[Real, Real-MDPR2, Video] audioMode - Audio Mode: music +[Real, Real-MDPR2, Time] Creation Date - Create Date: 2004:12:05 09:54:03 +[Real, Real-MDPR2, Video] Generated By - Software: Helix Producer SDK from RealNetworks.9.1.0.24 Windows +[Real, Real-MDPR2, Time] Modification Date - Modify Date: 2004:12:05 09:54:03 +[Real, Real-MDPR2, Video] videoMode - Video Mode: normal +[Real, Real-MDPR2, Video] Description - Description: Test RM file +[Real, Real-MDPR2, Video] Keywords - Keywords: these are keywords +[Real, Real-CONT, Video] 1 - Title: A title +[Real, Real-CONT, Author] 3 - Author: Phil Harvey +[Real, Real-CONT, Author] 5 - Copyright: Copyright 2006 Phil Harvey +[Real, Real-CONT, Video] 7 - Comment: A comment +[Real, Real-RJMD, Video] Album/Name - Album Name: The Real album name +[Real, Real-RJMD, Video] Statistics/CDInfo Source - Statistics CD Info Source: +[Real, Real-RJMD, Video] Track/Category - Track Category: The Real track category +[Real, Real-RJMD, Video] Track/Comments - Track Comments: The Real track comments +[Real, Real-RJMD, Video] Track/Comments/DataSize - Track Comments Data Size: 4294967295 +[Real, Real-RJMD, Video] Track/Comments/MimeType - Track Comments Mime Type: text/plain +[Real, Real-RJMD, Video] Track/Lyrics/DataSize - Track Lyrics Data Size: 1224736768 +[Real, Real-RJMD, Video] Track/Lyrics/Extension - Track Lyrics Extension: .txt +[Real, Real-RJMD, Video] Track/Lyrics/MimeType - Track Lyrics Mime Type: text/plain +[ID3, ID3v1, Audio] 3 - Title: This is a title +[ID3, ID3v1, Author] 33 - Artist: An artist +[ID3, ID3v1, Audio] 63 - Album: The album +[ID3, ID3v1, Time] 93 - Year: 2003 +[ID3, ID3v1, Audio] 97 - Comment: This is a comment +[ID3, ID3v1, Audio] 127 - Genre: Rock & Roll +[Composite, Composite, Time] ID3-DateTimeOriginal - Date/Time Original: 2003 diff --git a/ExifTool/t/Real_3.out b/ExifTool/t/Real_3.out new file mode 100644 index 0000000..51c7156 --- /dev/null +++ b/ExifTool/t/Real_3.out @@ -0,0 +1,19 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Real.ra +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 130 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:05:17 14:28:16-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: RA +[File, File, Other] FileTypeExtension - File Type Extension: ra +[File, File, Other] MIMEType - MIME Type: audio/x-pn-realaudio +[Real, Real-RA4, Audio] 6 - Audio Bytes: 704352 +[Real, Real-RA4, Audio] 7 - Bytes Per Minute: 299743 +[Real, Real-RA4, Audio] 10 - Audio Frame Size: 348 +[Real, Real-RA4, Audio] 13 - Sample Rate: 22050 +[Real, Real-RA4, Audio] 15 - Bits Per Sample: 16 +[Real, Real-RA4, Audio] 16 - Channels: 1 +[Real, Real-RA4, Audio] 24 - Title: The Sewing Girls +[Real, Real-RA4, Author] 28 - Copyright: ** institut für universelle zusammenhänge diff --git a/ExifTool/t/Real_4.out b/ExifTool/t/Real_4.out new file mode 100644 index 0000000..0807660 --- /dev/null +++ b/ExifTool/t/Real_4.out @@ -0,0 +1,12 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Real.ram +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 69 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:05:19 12:55:34-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: RAM +[File, File, Other] FileTypeExtension - File Type Extension: ram +[File, File, Other] MIMEType - MIME Type: audio/x-pn-realaudio +[Real, Real, Video] url - URL: rtsp://media.real.com/showcase/service/samples/rob_h_realvideo9_28.rm diff --git a/ExifTool/t/Red.t b/ExifTool/t/Red.t new file mode 100644 index 0000000..c4067c9 --- /dev/null +++ b/ExifTool/t/Red.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Red.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Red; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Red'; +my $testnum = 1; + +# test 2: Extract information from Red.r3d +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Red.r3d'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Red_2.out b/ExifTool/t/Red_2.out new file mode 100644 index 0000000..ec3e072 --- /dev/null +++ b/ExifTool/t/Red_2.out @@ -0,0 +1,45 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Red.r3d +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1160 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2018:01:26 14:40:32-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:19-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2018:01:26 14:40:32-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: R3D +[File, File, Other] FileTypeExtension - File Type Extension: r3d +[File, File, Other] MIMEType - MIME Type: video/x-red-r3d +[Red, Red, Video] 7 - Redcode Version: 2 +[Red, Red, Video] 76 - Image Width: 5120 +[Red, Red, Video] 80 - Image Height: 2560 +[Red, Red, Video] 86 - Frame Rate: 23.976 +[Red, Red, Camera] 4096 - Start Edge Code: 01:49:54:11 +[Red, Red, Time] 4097 - Start Timecode: 21:36:16:18 +[Red, Red, Camera] 4102 - Serial Number: 130-246-CE5 +[Red, Red, Camera] 8205 - Color Temperature: 4800 +[Red, Red, Camera] 4121 - Camera Type: A +[Red, Red, Video] 4122 - Reel Number: 106 +[Red, Red, Video] 4123 - Take: 037 +[Red, Red, Time] 4131 - Date Created: 2016:01:18 +[Red, Red, Time] 4132 - Time Created: 21:35:55 +[Red, Red, Camera] 4133 - Firmware Version: 6.2.34 +[Red, Red, Camera] 4138 - Storage Type: RED 512GB V4 +[Red, Red, Camera] 4146 - Storage Serial Number: 15240FC80DE7 +[Red, Red, Time] 4144 - Storage Format Date: 2016:01:18 +[Red, Red, Time] 4145 - Storage Format Time: 21:35:55 +[Red, Red, Camera] 16439 - Crop Area: 0 0 5120 2560 +[Red, Red, Camera] 16443 - ISO: 1280 +[Red, Red, Video] 4230 - Video Format: 5K 2:1 +[Red, Red, Camera] 8267 - RGB Curves: 0 0 0.25 0.25 0.5 0.5 0.75 0.75 1 1 0 0 0.25 0.25 0.5 0.5 0.75 0.75 1 1 0 0 0.25 0.25 0.5 0.5 0.75 0.75 1 1 +[Red, Red, Camera] 4182 - Original File Name: A106_C037_0118G5_002.R3D +[Red, Red, Video] 8294 - Original Frame Rate: 23.976 +[Red, Red, Camera] 16490 - F Number: 4.9 +[Red, Red, Camera] 16491 - Focal Length: 24 +[Red, Red, Camera] 24684 - Focus Distance: -0.001 m +[Red, Red, Camera] 4209 - Camera Model Name: S-WEAPON +[Red, Red, Camera] 4246 - Filter: STANDARD +[Composite, Composite, Image] Exif-Aperture - Aperture: 4.9 +[Composite, Composite, Time] Exif-DateTimeOriginal - Date/Time Original: 2016:01:18 21:35:55 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 5120x2560 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 13.1 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 24.0 mm diff --git a/ExifTool/t/Ricoh.t b/ExifTool/t/Ricoh.t new file mode 100644 index 0000000..d866cbf --- /dev/null +++ b/ExifTool/t/Ricoh.t @@ -0,0 +1,47 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Ricoh.t". + +BEGIN { + $| = 1; print "1..4\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Ricoh; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Ricoh'; +my $testnum = 1; + +# test 2: Extract information from Ricoh.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Ricoh.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + ['Ricoh:Sharpness' => 'Soft'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: Test extracting Ricoh APP5 RMETA information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Ricoh2.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Ricoh_2.out b/ExifTool/t/Ricoh_2.out new file mode 100644 index 0000000..40d64ab --- /dev/null +++ b/ExifTool/t/Ricoh_2.out @@ -0,0 +1,72 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Ricoh.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1903 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:20-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: RICOH +[EXIF, IFD0, Camera] 272 - Camera Model Name: Caplio RR1 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2001:11:07 13:08:41 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: (C) by Caplio RR1 User +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 150 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:11:07 13:08:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:11:07 13:08:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/274 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 2.6 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 5.3 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Other +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 7.5 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2272 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1704 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1626 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Ricoh, Camera] 1 - Maker Note Type: RDC +[MakerNotes, Ricoh, Camera] 2 - Firmware Version: 1.04 +[MakerNotes, Ricoh, Image] 0 - Ricoh Image Width: 2272 +[MakerNotes, Ricoh, Image] 2 - Ricoh Image Height: 1704 +[MakerNotes, Ricoh, Camera] 4099 - Sharpness: Sharp +[MakerNotes, Ricoh, Time] 4 - Manufacture Date 1: 2001:10:23 00:00:00 +[MakerNotes, Ricoh, Time] 5 - Manufacture Date 2: 0000:00:00 00:00:00 +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0100 +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.6 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/274 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 7.5 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 10.3 diff --git a/ExifTool/t/Ricoh_3.out b/ExifTool/t/Ricoh_3.out new file mode 100644 index 0000000..3a19246 --- /dev/null +++ b/ExifTool/t/Ricoh_3.out @@ -0,0 +1,100 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Ricoh_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 1907 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:20-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:20-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:20-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: RICOH +[EXIF, IFD0, Camera] 272 - Camera Model Name: Caplio RR1 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2001:11:07 13:08:41 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: (C) by Caplio RR1 User +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 150 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:11:07 13:08:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:11:07 13:08:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/274 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 2.6 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 5.3 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Other +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 7.5 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2272 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1704 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1630 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Ricoh, Camera] 1 - Maker Note Type: RDC +[MakerNotes, Ricoh, Camera] 2 - Firmware Version: 1.04 +[MakerNotes, Ricoh, Camera] 3 - Ricoh 0x0003: 3 +[MakerNotes, Ricoh, Camera] 4 - Ricoh 0x0004: 13104 12853 +[MakerNotes, Ricoh, Image] 0 - Ricoh Image Width: 2272 +[MakerNotes, Ricoh, Image] 2 - Ricoh Image Height: 1704 +[MakerNotes, Ricoh, Camera] 4098 - Ricoh 0x1002: 0 +[MakerNotes, Ricoh, Camera] 4099 - Sharpness: Soft +[MakerNotes, Ricoh, Camera] 4100 - Ricoh 0x1004: 0 +[MakerNotes, Ricoh, Camera] 4101 - Ricoh 0x1005: 1 +[MakerNotes, Ricoh, Camera] 1 - Ricoh Subdir 0x0001: 532 314 +[MakerNotes, Ricoh, Camera] 2 - Ricoh Subdir 0x0002: 42 +[MakerNotes, Ricoh, Camera] 3 - Ricoh Subdir 0x0003: 314572848 +[MakerNotes, Ricoh, Time] 4 - Manufacture Date 1: 2001:10:23 00:00:00 +[MakerNotes, Ricoh, Time] 5 - Manufacture Date 2: 0000:00:00 00:00:00 +[MakerNotes, Ricoh, Camera] 6 - Ricoh Subdir 0x0006: 12 0 +[MakerNotes, Ricoh, Camera] 7 - Ricoh Subdir 0x0007: 0 +[MakerNotes, Ricoh, Camera] 8 - Ricoh Subdir 0x0008: 32 +[MakerNotes, Ricoh, Camera] 9 - Ricoh Subdir 0x0009: 65535 1024 +[MakerNotes, Ricoh, Camera] 10 - Ricoh Subdir 0x000a: 0.07485380117 +[MakerNotes, Ricoh, Camera] 11 - Ricoh Subdir 0x000b: 0.3939393939 +[MakerNotes, Ricoh, Camera] 12 - Ricoh Subdir 0x000c: 136 12 +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0100 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00160016 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x00000000 +[PrintIM, PrintIM, Printing] 3 - Print IM 0x0003: 0x0000005e +[PrintIM, PrintIM, Printing] 7 - Print IM 0x0007: 0x00000000 +[PrintIM, PrintIM, Printing] 8 - Print IM 0x0008: 0x00000000 +[PrintIM, PrintIM, Printing] 9 - Print IM 0x0009: 0x00000000 +[PrintIM, PrintIM, Printing] 10 - Print IM 0x000a: 0x00000000 +[PrintIM, PrintIM, Printing] 11 - Print IM 0x000b: 0x000000a6 +[PrintIM, PrintIM, Printing] 12 - Print IM 0x000c: 0x00000000 +[PrintIM, PrintIM, Printing] 13 - Print IM 0x000d: 0x00000000 +[PrintIM, PrintIM, Printing] 14 - Print IM 0x000e: 0x000000be +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x05000000 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000000 +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.6 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/274 +[Composite, Composite, Image] Exif-LightValue - Light Value: 10.3 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 7.5 mm diff --git a/ExifTool/t/Ricoh_4.out b/ExifTool/t/Ricoh_4.out new file mode 100644 index 0000000..6a74536 --- /dev/null +++ b/ExifTool/t/Ricoh_4.out @@ -0,0 +1,114 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Ricoh2.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 3.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:09:29 19:47:55-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:51-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: RICOH +[EXIF, IFD0, Camera] 272 - Camera Model Name: Caplio Pro G3 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2006:05:26 11:48:48 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: (C) by Caplio Pro G3 User +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/1740 +[EXIF, ExifIFD, Image] 33437 - F Number: 5.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 125 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2006:05:26 11:48:48 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2006:05:26 11:48:48 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3.2 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 5.7 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 9.6 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: -1 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Spot +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Auto, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 5.7 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: GCM_TAG +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2048 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1536 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Hard +[EXIF, GPS, Location] 0 - GPS Version ID: 2.2.0.0 +[EXIF, GPS, Location] 1 - GPS Latitude Ref: North +[EXIF, GPS, Location] 2 - GPS Latitude: 42 deg 2' 4.47" +[EXIF, GPS, Location] 3 - GPS Longitude Ref: West +[EXIF, GPS, Location] 4 - GPS Longitude: 0 deg 30' 27.01" +[EXIF, GPS, Location] 5 - GPS Altitude Ref: Above Sea Level +[EXIF, GPS, Location] 6 - GPS Altitude: 117 m +[EXIF, GPS, Time] 7 - GPS Time Stamp: 10:47:24 +[EXIF, GPS, Location] 8 - GPS Satellites: 24,23,20,17,13,11,04 +[EXIF, GPS, Location] 9 - GPS Status: Measurement Active +[EXIF, GPS, Location] 10 - GPS Measure Mode: 3-Dimensional Measurement +[EXIF, GPS, Location] 11 - GPS Dilution Of Precision: 2.6 +[EXIF, GPS, Location] 12 - GPS Speed Ref: knots +[EXIF, GPS, Location] 13 - GPS Speed: 0 +[EXIF, GPS, Location] 14 - GPS Track Ref: True North +[EXIF, GPS, Location] 15 - GPS Track: 20 +[EXIF, GPS, Location] 16 - GPS Img Direction Ref: True North +[EXIF, GPS, Location] 17 - GPS Img Direction: 20 +[EXIF, GPS, Location] 18 - GPS Map Datum: WGS-84 +[EXIF, GPS, Location] 23 - GPS Dest Bearing Ref: Unknown () +[EXIF, GPS, Location] 24 - GPS Dest Bearing: undef +[EXIF, GPS, Location] 25 - GPS Dest Distance Ref: Unknown () +[EXIF, GPS, Location] 26 - GPS Dest Distance: undef +[EXIF, GPS, Time] 29 - GPS Date Stamp: 2006:05:26 +[EXIF, GPS, Location] 30 - GPS Differential: No Correction +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 2492 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Ricoh, Camera] 1 - Maker Note Type: Rdc +[MakerNotes, Ricoh, Camera] 2 - Firmware Version: 22.19 +[MakerNotes, Ricoh, Camera] 5 - Internal Serial Number: 00000000000000012207000003100957 +[MakerNotes, Ricoh, Image] 0 - Ricoh Image Width: 2048 +[MakerNotes, Ricoh, Image] 2 - Ricoh Image Height: 1536 +[MakerNotes, Ricoh, Time] 6 - Ricoh Date: 2006:05:26 11:48:48 +[MakerNotes, Ricoh, Camera] 4099 - Sharpness: Sharp +[MakerNotes, Ricoh, Time] 4 - Manufacture Date 1: 2005:06:06 +[MakerNotes, Ricoh, Time] 5 - Manufacture Date 2: 2003:08:19 +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[APP5, RMETA, Image] Sign type - Sign Type: Information +[APP5, RMETA, Image] Location - Location: Roundabout +[APP5, RMETA, Image] Lit - Lit: No +[APP5, RMETA, Image] Condition - Condition: Good +[APP5, RMETA, Image] Azimuth - Azimuth: E +[Composite, Composite, Image] Exif-Aperture - Aperture: 5.5 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/1740 +[Composite, Composite, Location] GPS-GPSAltitude - GPS Altitude: 117 m Above Sea Level +[Composite, Composite, Time] GPS-GPSDateTime - GPS Date/Time: 2006:05:26 10:47:24Z +[Composite, Composite, Location] GPS-GPSLatitude - GPS Latitude: 42 deg 2' 4.47" N +[Composite, Composite, Location] GPS-GPSLongitude - GPS Longitude: 0 deg 30' 27.01" W +[Composite, Composite, Location] Exif-GPSPosition - GPS Position: 42 deg 2' 4.47" N, 0 deg 30' 27.01" W +[Composite, Composite, Image] Exif-LightValue - Light Value: 15.4 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 5.7 mm diff --git a/ExifTool/t/Sanyo.t b/ExifTool/t/Sanyo.t new file mode 100644 index 0000000..0bfb706 --- /dev/null +++ b/ExifTool/t/Sanyo.t @@ -0,0 +1,39 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Sanyo.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Sanyo; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Sanyo'; +my $testnum = 1; + +# test 2: Extract information from Sanyo.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Sanyo.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + [SceneCaptureType => 'night'], + [FlashMode => 'force', Group => 'MakerNotes'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Sanyo_2.out b/ExifTool/t/Sanyo_2.out new file mode 100644 index 0000000..3ce1081 --- /dev/null +++ b/ExifTool/t/Sanyo_2.out @@ -0,0 +1,103 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Sanyo.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:51-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: SANYO DIGITAL CAMERA +[EXIF, IFD0, Camera] 271 - Make: SANYO Electric Co.,Ltd. +[EXIF, IFD0, Camera] 272 - Camera Model Name: VPC-MZ3 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: V612P-75 +[EXIF, IFD0, Time] 306 - Modify Date: 2003:10:11 12:43:05 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/185 +[EXIF, ExifIFD, Image] 33437 - F Number: 6.9 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 400 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:10:11 12:43:05 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:10:11 12:43:05 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 2.9 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-spot +[EXIF, ExifIFD, Camera] 37384 - Light Source: Fine Weather +[EXIF, ExifIFD, Camera] 37385 - Flash: Auto, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 23.1 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2000 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1496 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Manual +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 11113 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: High gain up +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Close +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1888 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Sanyo, Camera] 255 - Maker Note Offset: 1076 +[MakerNotes, Sanyo, Camera] 512 - Special Mode: 0 0 0 +[MakerNotes, Sanyo, Camera] 513 - Sanyo Quality: Fine/Very High +[MakerNotes, Sanyo, Camera] 514 - Macro: Normal +[MakerNotes, Sanyo, Camera] 516 - Digital Zoom: 0 +[MakerNotes, Sanyo, Camera] 526 - Sequential Shot: None +[MakerNotes, Sanyo, Camera] 527 - Wide Range: Off +[MakerNotes, Sanyo, Camera] 528 - Color Adjustment Mode: Off +[MakerNotes, Sanyo, Camera] 531 - Quick Shot: Off +[MakerNotes, Sanyo, Camera] 532 - Self Timer: Off +[MakerNotes, Sanyo, Camera] 534 - Voice Memo: Off +[MakerNotes, Sanyo, Camera] 535 - Record Shutter Release: Record while down +[MakerNotes, Sanyo, Camera] 536 - Flicker Reduce: Off +[MakerNotes, Sanyo, Camera] 537 - Optical Zoom On: On +[MakerNotes, Sanyo, Camera] 539 - Digital Zoom On: Off +[MakerNotes, Sanyo, Camera] 541 - Light Source Special: Off +[MakerNotes, Sanyo, Camera] 542 - Resaved: No +[MakerNotes, Sanyo, Camera] 543 - Scene Select: Off +[MakerNotes, Sanyo, Camera] 547 - Manual Focus Distance: 1 +[MakerNotes, Sanyo, Camera] 548 - Sequence Shot Interval: 5 frames/s +[MakerNotes, Sanyo, Camera] 549 - Flash Mode: Red eye +[MakerNotes, Sanyo, Camera] 3840 - Data Dump: (Binary data 260 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0100 +[Composite, Composite, Image] Exif-Aperture - Aperture: 6.9 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 481.1 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/185 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.000 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 0.2 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 23.1 mm (35 mm equivalent: 11113.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 1238.24 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 11.1 diff --git a/ExifTool/t/Sanyo_3.out b/ExifTool/t/Sanyo_3.out new file mode 100644 index 0000000..29ab61c --- /dev/null +++ b/ExifTool/t/Sanyo_3.out @@ -0,0 +1,113 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Sanyo_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 2.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:51-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:51-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:51-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: SANYO DIGITAL CAMERA +[EXIF, IFD0, Camera] 271 - Make: SANYO Electric Co.,Ltd. +[EXIF, IFD0, Camera] 272 - Camera Model Name: VPC-MZ3 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: V612P-75 +[EXIF, IFD0, Time] 306 - Modify Date: 2003:10:11 12:43:05 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/185 +[EXIF, ExifIFD, Image] 33437 - F Number: 6.9 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 400 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:10:11 12:43:05 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:10:11 12:43:05 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 2.9 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-spot +[EXIF, ExifIFD, Camera] 37384 - Light Source: Fine Weather +[EXIF, ExifIFD, Camera] 37385 - Flash: Auto, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 23.1 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2000 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1496 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Manual +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 0 +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 11113 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Night +[EXIF, ExifIFD, Camera] 41991 - Gain Control: High gain up +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Close +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1882 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Sanyo, Camera] 255 - Maker Note Offset: 1076 +[MakerNotes, Sanyo, Camera] 512 - Special Mode: 0 0 0 +[MakerNotes, Sanyo, Camera] 513 - Sanyo Quality: Fine/Very High +[MakerNotes, Sanyo, Camera] 514 - Macro: Normal +[MakerNotes, Sanyo, Camera] 515 - Sanyo 0x0203: 0 +[MakerNotes, Sanyo, Camera] 516 - Digital Zoom: 0 +[MakerNotes, Sanyo, Camera] 526 - Sequential Shot: None +[MakerNotes, Sanyo, Camera] 527 - Wide Range: Off +[MakerNotes, Sanyo, Camera] 528 - Color Adjustment Mode: Off +[MakerNotes, Sanyo, Camera] 529 - Sanyo 0x0211: 0 +[MakerNotes, Sanyo, Camera] 530 - Sanyo 0x0212: 0 +[MakerNotes, Sanyo, Camera] 531 - Quick Shot: Off +[MakerNotes, Sanyo, Camera] 532 - Self Timer: Off +[MakerNotes, Sanyo, Camera] 533 - Sanyo 0x0215: 0 +[MakerNotes, Sanyo, Camera] 534 - Voice Memo: Off +[MakerNotes, Sanyo, Camera] 535 - Record Shutter Release: Record while down +[MakerNotes, Sanyo, Camera] 536 - Flicker Reduce: Off +[MakerNotes, Sanyo, Camera] 537 - Optical Zoom On: On +[MakerNotes, Sanyo, Camera] 538 - Sanyo 0x021a: 3 +[MakerNotes, Sanyo, Camera] 539 - Digital Zoom On: Off +[MakerNotes, Sanyo, Camera] 540 - Sanyo 0x021c: 0 +[MakerNotes, Sanyo, Camera] 541 - Light Source Special: Off +[MakerNotes, Sanyo, Camera] 542 - Resaved: No +[MakerNotes, Sanyo, Camera] 543 - Scene Select: Off +[MakerNotes, Sanyo, Camera] 544 - Sanyo 0x0220: 9 +[MakerNotes, Sanyo, Camera] 545 - Sanyo 0x0221: 0 +[MakerNotes, Sanyo, Camera] 546 - Sanyo 0x0222: 3 1820 800 103 8963 800 211 13726 800 187 19210 800 153[...] +[MakerNotes, Sanyo, Camera] 547 - Manual Focus Distance: 1 +[MakerNotes, Sanyo, Camera] 548 - Sequence Shot Interval: 5 frames/s +[MakerNotes, Sanyo, Camera] 549 - Flash Mode: Force +[MakerNotes, Sanyo, Camera] 3840 - Data Dump: (Binary data 260 bytes) +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0100 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000001 +[Composite, Composite, Image] Exif-Aperture - Aperture: 6.9 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 481.1 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/185 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.000 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 0.2 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 23.1 mm (35 mm equivalent: 11113.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 1238.24 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 11.1 diff --git a/ExifTool/t/Sigma.t b/ExifTool/t/Sigma.t new file mode 100644 index 0000000..28fe4f6 --- /dev/null +++ b/ExifTool/t/Sigma.t @@ -0,0 +1,60 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Sigma.t". + +BEGIN { + $| = 1; print "1..5\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Sigma; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Sigma'; +my $testnum = 1; + +# test 2: Extract information from Sigma.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Sigma.jpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + ['IPTCPixelWidth' => 200], + ['Sharpness' => 2, 'Group' => 'MakerNotes'], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: Test reading X3F image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Sigma.x3f'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 5: Test writing X3F image +{ + ++$testnum; + my @writeInfo = ( + ['Artist' => 'Phil Harvey'], + ['XMP:Title' => 'A title'], + ['Keywords' => ['one','two']], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/SigmaDP2.x3f'); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Sigma_2.out b/ExifTool/t/Sigma_2.out new file mode 100644 index 0000000..0c05987 --- /dev/null +++ b/ExifTool/t/Sigma_2.out @@ -0,0 +1,93 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Sigma.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1531 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:20-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: SIGMA +[EXIF, IFD0, Camera] 272 - Camera Model Name: SIGMA SD10 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: SIGMA PhotoPro 2.0.0.1586 +[EXIF, IFD0, Time] 306 - Modify Date: 2004:03:19 17:48:51 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/320 +[EXIF, ExifIFD, Image] 33437 - F Number: 8.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:11:27 10:31:34 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2004:03:19 17:48:51 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 60.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2268 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1512 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Sigma Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Custom +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Manual +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 102 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Image] 42016 - Image Unique ID: 303230303030313986D2C53F8ABDA2A6 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 180 +[EXIF, IFD1, Image] 283 - Y Resolution: 180 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1254 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Sigma, Camera] 2 - Serial Number: 02000019 +[MakerNotes, Sigma, Camera] 3 - Drive Mode: SINGLE +[MakerNotes, Sigma, Camera] 4 - Resolution Mode: HI +[MakerNotes, Sigma, Camera] 5 - AF Mode: AF-S +[MakerNotes, Sigma, Camera] 6 - Focus Setting: AF +[MakerNotes, Sigma, Camera] 7 - White Balance: Sunlight +[MakerNotes, Sigma, Camera] 8 - Exposure Mode: Program AE +[MakerNotes, Sigma, Camera] 9 - Metering Mode: Multi-segment +[MakerNotes, Sigma, Camera] 10 - Lens Focal Range: 24 to 70 +[MakerNotes, Sigma, Camera] 11 - Color Space: sRGB +[MakerNotes, Sigma, Camera] 12 - Exposure Compensation: +0.8 +[MakerNotes, Sigma, Camera] 13 - Contrast: +0.0 +[MakerNotes, Sigma, Camera] 14 - Shadow: +0.0 +[MakerNotes, Sigma, Camera] 15 - Highlight: +0.0 +[MakerNotes, Sigma, Camera] 16 - Saturation: +0.4 +[MakerNotes, Sigma, Camera] 17 - Sharpness: +1.0 +[MakerNotes, Sigma, Camera] 18 - X3 Fill Light: +0.0 +[MakerNotes, Sigma, Camera] 20 - Color Adjustment: 0 +[MakerNotes, Sigma, Camera] 21 - Adjustment Mode: X3F Setting Mode +[MakerNotes, Sigma, Camera] 22 - Quality: 12 +[MakerNotes, Sigma, Camera] 23 - Firmware: 2.0.4.1642 Release +[MakerNotes, Sigma, Camera] 24 - Software: SIGMA PhotoPro 2.0.0.1586 +[MakerNotes, Sigma, Camera] 25 - Auto Bracket: +[Composite, Composite, Image] Exif-Aperture - Aperture: 8.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.7 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/320 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.018 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 20.0 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 60.0 mm (35 mm equivalent: 102.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 25.46 m +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.3 diff --git a/ExifTool/t/Sigma_3.out b/ExifTool/t/Sigma_3.out new file mode 100644 index 0000000..21e57c3 --- /dev/null +++ b/ExifTool/t/Sigma_3.out @@ -0,0 +1,96 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Sigma_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 1575 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:21-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:21-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:21-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: d29df24ca5b6b8d1ac85670c7041b071 +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: SIGMA +[EXIF, IFD0, Camera] 272 - Camera Model Name: SIGMA SD10 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: SIGMA PhotoPro 2.0.0.1586 +[EXIF, IFD0, Time] 306 - Modify Date: 2004:03:19 17:48:51 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/320 +[EXIF, ExifIFD, Image] 33437 - F Number: 8.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:11:27 10:31:34 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2004:03:19 17:48:51 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 60.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2268 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1512 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Sigma Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Custom +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Manual +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 102 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Image] 42016 - Image Unique ID: 303230303030313986D2C53F8ABDA2A6 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 180 +[EXIF, IFD1, Image] 283 - Y Resolution: 180 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 1254 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Sigma, Camera] 2 - Serial Number: 02000019 +[MakerNotes, Sigma, Camera] 3 - Drive Mode: SINGLE +[MakerNotes, Sigma, Camera] 4 - Resolution Mode: HI +[MakerNotes, Sigma, Camera] 5 - AF Mode: AF-S +[MakerNotes, Sigma, Camera] 6 - Focus Setting: AF +[MakerNotes, Sigma, Camera] 7 - White Balance: Sunlight +[MakerNotes, Sigma, Camera] 8 - Exposure Mode: Program AE +[MakerNotes, Sigma, Camera] 9 - Metering Mode: Multi-segment +[MakerNotes, Sigma, Camera] 10 - Lens Focal Range: 24 to 70 +[MakerNotes, Sigma, Camera] 11 - Color Space: sRGB +[MakerNotes, Sigma, Camera] 12 - Exposure Compensation: +0.8 +[MakerNotes, Sigma, Camera] 13 - Contrast: +0.0 +[MakerNotes, Sigma, Camera] 14 - Shadow: +0.0 +[MakerNotes, Sigma, Camera] 15 - Highlight: +0.0 +[MakerNotes, Sigma, Camera] 16 - Saturation: +0.4 +[MakerNotes, Sigma, Camera] 17 - Sharpness: +2.0 +[MakerNotes, Sigma, Camera] 18 - X3 Fill Light: +0.0 +[MakerNotes, Sigma, Camera] 20 - Color Adjustment: 0 +[MakerNotes, Sigma, Camera] 21 - Adjustment Mode: X3F Setting Mode +[MakerNotes, Sigma, Camera] 22 - Quality: 12 +[MakerNotes, Sigma, Camera] 23 - Firmware: 2.0.4.1642 Release +[MakerNotes, Sigma, Camera] 24 - Software: SIGMA PhotoPro 2.0.0.1586 +[MakerNotes, Sigma, Camera] 25 - Auto Bracket: +[IPTC, IPTC, Image] 40 - IPTC Pixel Width: 200 +[IPTC, IPTC, Image] 0 - News Photo Version: 4 +[Composite, Composite, Image] Exif-Aperture - Aperture: 8.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/320 +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.3 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.7 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.018 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 20.0 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 60.0 mm (35 mm equivalent: 102.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 25.46 m diff --git a/ExifTool/t/Sigma_4.out b/ExifTool/t/Sigma_4.out new file mode 100644 index 0000000..fe9f60e --- /dev/null +++ b/ExifTool/t/Sigma_4.out @@ -0,0 +1,65 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Sigma.x3f +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1464 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2005:11:14 14:47:22-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:21-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: X3F +[File, File, Other] FileTypeExtension - File Type Extension: x3f +[File, File, Other] MIMEType - MIME Type: image/x-sigma-x3f +[SigmaRaw, SigmaRaw, Other] 1 - File Version: 2.2 +[SigmaRaw, SigmaRaw, Other] 2 - Image Unique ID: 303230303132333413d14f3a3b2aa914 +[SigmaRaw, SigmaRaw, Other] 6 - Mark Bits: (none) +[SigmaRaw, SigmaRaw, Other] 7 - Image Width: 2268 +[SigmaRaw, SigmaRaw, Other] 8 - Image Height: 1512 +[SigmaRaw, SigmaRaw, Other] 9 - Rotation: 90 +[SigmaRaw, SigmaRaw, Other] 10 - White Balance: Sunlight +[SigmaRaw, SigmaRaw, Camera] 1 - Exposure Adjust: 0.5 +[SigmaRaw, SigmaRaw, Camera] 2 - Contrast: -1.1 +[SigmaRaw, SigmaRaw, Camera] 3 - Shadow: -0.1 +[SigmaRaw, SigmaRaw, Camera] 4 - Highlight: -2.0 +[SigmaRaw, SigmaRaw, Camera] 5 - Saturation: -0.1 +[SigmaRaw, SigmaRaw, Camera] 6 - Sharpness: -1.9 +[SigmaRaw, SigmaRaw, Camera] 10 - X3 Fill Light: -1.7 +[SigmaRaw, SigmaRaw, Camera] 7 - Red Adjust: 1.0 +[SigmaRaw, SigmaRaw, Camera] 8 - Green Adjust: 0.9 +[SigmaRaw, SigmaRaw, Camera] 9 - Blue Adjust: 0.9 +[SigmaRaw, SigmaRaw, Camera] ISO - ISO: 100 +[SigmaRaw, SigmaRaw, Camera] RESOLUTION - Quality: High +[SigmaRaw, SigmaRaw, Camera] WB_DESC - White Balance: Sunlight +[SigmaRaw, SigmaRaw, Camera] CAMMANUF - Make: SIGMA +[SigmaRaw, SigmaRaw, Camera] CAMMODEL - Model: SIGMA SD10 +[SigmaRaw, SigmaRaw, Camera] CAMSERIAL - Serial Number: 02001234 +[SigmaRaw, SigmaRaw, Camera] FIRMVERS - Firmware Version: 2.0.4.1586 Release +[SigmaRaw, SigmaRaw, Time] TIME - Date/Time Original: 2001:01:01 00:36:35 +[SigmaRaw, SigmaRaw, Image] EXPTIME - Integration Time: 1/41 +[SigmaRaw, SigmaRaw, Camera] FOCUS - Focus: Auto-focus Locked +[SigmaRaw, SigmaRaw, Camera] AFMODE - Focus Mode: AF-S +[SigmaRaw, SigmaRaw, Image] SHUTTER - Exposure Time: 1/108 +[SigmaRaw, SigmaRaw, Camera] SH_DESC - Shutter Speed Displayed: 1/100 +[SigmaRaw, SigmaRaw, Camera] FLASH - Flash Mode: Off +[SigmaRaw, SigmaRaw, Camera] PMODE - Exposure Program: Program +[SigmaRaw, SigmaRaw, Image] EXPCOMP - Exposure Compensation: 0 +[SigmaRaw, SigmaRaw, Image] EXPNET - Net Exposure Compensation: 0 +[SigmaRaw, SigmaRaw, Camera] AEMODE - Metering Mode: 8-segment +[SigmaRaw, SigmaRaw, Camera] DRIVE - Drive Mode: Single Shot +[SigmaRaw, SigmaRaw, Camera] LENSMODEL - Lens Type: Sigma Lens (0x145) +[SigmaRaw, SigmaRaw, Image] APERTURE - F Number: 8.4 +[SigmaRaw, SigmaRaw, Camera] AP_DESC - Aperture Displayed: 8 +[SigmaRaw, SigmaRaw, Camera] LENSARANGE - Lens Aperture Range: 29 to 4.5 +[SigmaRaw, SigmaRaw, Camera] LENSFRANGE - Lens Focal Range: 15 to 30 +[SigmaRaw, SigmaRaw, Camera] FLENGTH - Focal Length: 30.0 mm +[SigmaRaw, SigmaRaw, Camera] FLEQ35MM - Focal Length In 35mm Format: 51.0 mm +[Composite, Composite, Image] Exif-Aperture - Aperture: 8.4 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 2268x1512 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 3.4 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/108 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Sigma 15-30mm F3.5-4.5 EX DG Aspherical +[Composite, Composite, Image] Exif-LightValue - Light Value: 12.9 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.7 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.018 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 38.9 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 30.0 mm (35 mm equivalent: 51.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 6.10 m diff --git a/ExifTool/t/Sigma_5.out b/ExifTool/t/Sigma_5.out new file mode 100644 index 0000000..5663eeb --- /dev/null +++ b/ExifTool/t/Sigma_5.out @@ -0,0 +1,194 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.48 +[File, System, Other] FileName - File Name: Sigma_5_failed.x3f +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 7.3 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:10:13 10:00:31-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:10:13 10:00:31-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:10:13 10:00:31-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: X3F +[File, File, Other] FileTypeExtension - File Type Extension: x3f +[File, File, Other] MIMEType - MIME Type: image/x-sigma-x3f +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[SigmaRaw, SigmaRaw, Other] 1 - File Version: 2.3 +[SigmaRaw, SigmaRaw, Other] 2 - Image Unique ID: 30313030383338321020114b31646638 +[SigmaRaw, SigmaRaw, Other] 6 - Mark Bits: (none) +[SigmaRaw, SigmaRaw, Other] 7 - Image Width: 2640 +[SigmaRaw, SigmaRaw, Other] 8 - Image Height: 1760 +[SigmaRaw, SigmaRaw, Other] 9 - Rotation: 0 +[SigmaRaw, SigmaRaw, Other] 10 - White Balance: Auto +[SigmaRaw, SigmaRaw, Other] 18 - Scene Capture Type: Standard +[SigmaRaw, SigmaRaw, Camera] 1 - Exposure Adjust: 0.0 +[SigmaRaw, SigmaRaw, Camera] 2 - Contrast: 0.0 +[SigmaRaw, SigmaRaw, Camera] 3 - Shadow: 0.0 +[SigmaRaw, SigmaRaw, Camera] 4 - Highlight: 0.0 +[SigmaRaw, SigmaRaw, Camera] 5 - Saturation: 0.0 +[SigmaRaw, SigmaRaw, Camera] 6 - Sharpness: 0.0 +[SigmaRaw, SigmaRaw, Camera] 10 - X3 Fill Light: 0.0 +[SigmaRaw, SigmaRaw, Camera] 7 - Red Adjust: 0.0 +[SigmaRaw, SigmaRaw, Camera] 8 - Green Adjust: 0.0 +[SigmaRaw, SigmaRaw, Camera] 9 - Blue Adjust: 0.0 +[SigmaRaw, SigmaRaw, Preview] IMA2 - Jpg From Raw: (Binary data 5640 bytes) +[SigmaRaw, SigmaRaw, Camera] AEMODE - Metering Mode: 8-segment +[SigmaRaw, SigmaRaw, Camera] AFMODE - Focus Mode: AF-S +[SigmaRaw, SigmaRaw, Camera] AP_DESC - Aperture Displayed: 2.8 +[SigmaRaw, SigmaRaw, Image] APERTURE - F Number: 2.8 +[SigmaRaw, SigmaRaw, Camera] BRACKET - Bracket Shot: +[SigmaRaw, SigmaRaw, Camera] BURST - Burst Shot: 0 +[SigmaRaw, SigmaRaw, Camera] CAMMANUF - Make: SIGMA +[SigmaRaw, SigmaRaw, Camera] CAMMODEL - Model: SIGMA DP2 +[SigmaRaw, SigmaRaw, Camera] CAMNAME - Camera Name: +[SigmaRaw, SigmaRaw, Camera] CAMSERIAL - Serial Number: 1008382 +[SigmaRaw, SigmaRaw, Camera] DRIVE - Drive Mode: Single Shot +[SigmaRaw, SigmaRaw, Image] EXPCOMP - Exposure Compensation: 0 +[SigmaRaw, SigmaRaw, Image] EXPNET - Net Exposure Compensation: 0 +[SigmaRaw, SigmaRaw, Image] EXPTIME - Integration Time: 1/10 +[SigmaRaw, SigmaRaw, Camera] FIRMVERS - Firmware Version: 1.03.0.000 +[SigmaRaw, SigmaRaw, Camera] FLASH - Flash Mode: Off +[SigmaRaw, SigmaRaw, Camera] FLENGTH - Focal Length: 24.2 mm +[SigmaRaw, SigmaRaw, Camera] FLEQ35MM - Focal Length In 35mm Format: 41.0 mm +[SigmaRaw, SigmaRaw, Camera] FOCUS - Focus: Auto-focus Locked +[SigmaRaw, SigmaRaw, Camera] IMAGEBOARDID - Image Board ID: +[SigmaRaw, SigmaRaw, Camera] IMAGERTEMP - Sensor Temperature: 20 C +[SigmaRaw, SigmaRaw, Camera] ISO - ISO: 100 +[SigmaRaw, SigmaRaw, Camera] LENSARANGE - Lens Aperture Range: 2.8 to 14 +[SigmaRaw, SigmaRaw, Camera] LENSFRANGE - Lens Focal Range: 24.2 +[SigmaRaw, SigmaRaw, Camera] LENSMODEL - Lens Type: Unknown ( ) +[SigmaRaw, SigmaRaw, Camera] PMODE - Exposure Program: Program +[SigmaRaw, SigmaRaw, Camera] RESOLUTION - Quality: High +[SigmaRaw, SigmaRaw, Camera] SENSORID - Sensor ID: +[SigmaRaw, SigmaRaw, Camera] SH_DESC - Shutter Speed Displayed: 1/10 +[SigmaRaw, SigmaRaw, Image] SHUTTER - Exposure Time: 1/10 +[SigmaRaw, SigmaRaw, Time] TIME - Date/Time Original: 2009:11:28 13:05:20 +[SigmaRaw, SigmaRaw, Camera] WB_DESC - White Balance: Auto +[SigmaRaw, SigmaRaw, Camera] CM_DESC - Scene Capture Type: Standard +[SigmaRaw, SigmaRaw, Preview] IMA2 - Preview Image: (Binary data 20 bytes) +[EXIF, IFD0, Camera] 271 - Make: SIGMA +[EXIF, IFD0, Camera] 272 - Camera Model Name: SIGMA DP2 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: 1.03.0.000000000 +[EXIF, IFD0, Time] 306 - Modify Date: 2009:11:28 13:05:20 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/10 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.8 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2009:11:28 13:05:20 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2009:11:28 13:05:20 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 24.2 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2640 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1760 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 41 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Image] 42016 - Image Unique ID: 30313030383338321020110031646638 +[EXIF, ExifIFD, Image] 42240 - Gamma: undef +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 180 +[EXIF, IFD1, Image] 283 - Y Resolution: 180 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 2680 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Sigma, Camera] 2 - Serial Number: 1008382 +[MakerNotes, Sigma, Camera] 3 - Drive Mode: SINGLE +[MakerNotes, Sigma, Camera] 4 - Resolution Mode: HI +[MakerNotes, Sigma, Camera] 5 - AF Mode: AF-S +[MakerNotes, Sigma, Camera] 6 - Focus Setting: AF +[MakerNotes, Sigma, Camera] 7 - White Balance: Auto +[MakerNotes, Sigma, Camera] 8 - Exposure Mode: Program AE +[MakerNotes, Sigma, Camera] 9 - Metering Mode: Multi-segment +[MakerNotes, Sigma, Camera] 10 - Lens Focal Range: 24.2 +[MakerNotes, Sigma, Camera] 11 - Color Space: sRGB +[MakerNotes, Sigma, Camera] 12 - Exposure Adjust: 0 +[MakerNotes, Sigma, Camera] 13 - Contrast: 0 +[MakerNotes, Sigma, Camera] 14 - Shadow: 0 +[MakerNotes, Sigma, Camera] 15 - Highlight: 0 +[MakerNotes, Sigma, Camera] 16 - Saturation: 0 +[MakerNotes, Sigma, Camera] 17 - Sharpness: 0 +[MakerNotes, Sigma, Camera] 18 - X3 Fill Light: 0 +[MakerNotes, Sigma, Camera] 20 - Color Adjustment: 0 0 0 +[MakerNotes, Sigma, Camera] 21 - Adjustment Mode: +[MakerNotes, Sigma, Camera] 22 - Quality: BASIC +[MakerNotes, Sigma, Camera] 23 - Firmware: 1.03.0.000 +[MakerNotes, Sigma, Camera] 24 - Software: +[MakerNotes, Sigma, Camera] 25 - Auto Bracket: +[MakerNotes, Sigma, Camera] 26 - Preview Image Start: 2708 +[MakerNotes, Sigma, Camera] 27 - Preview Image Length: 26 +[MakerNotes, Sigma, Camera] 28 - Preview Image Size: 640x480 +[MakerNotes, Sigma, Camera] 29 - Maker Note Version: 0100 +[MakerNotes, Sigma, Camera] 30 - Sigma 0x001e: 4 +[MakerNotes, Sigma, Camera] 31 - AF Point: Center +[MakerNotes, Sigma, Camera] 32 - Sigma 0x0020: +[MakerNotes, Sigma, Camera] 33 - Sigma 0x0021: +[MakerNotes, Sigma, Camera] 34 - File Format: X3F-S +[MakerNotes, Sigma, Camera] 35 - Sigma 0x0023: +[MakerNotes, Sigma, Camera] 36 - Calibration: C72_CalibrationSoft Version 1.00;RunID:C72Cal-001;Runtime:2009-05-29-14:15:38 +[MakerNotes, Sigma, Camera] 37 - Sigma 0x0025: 0.90 +[MakerNotes, Sigma, Camera] 38 - Sigma 0x0026: 0 +[MakerNotes, Sigma, Camera] 39 - Sigma 0x0027: 0 +[MakerNotes, Sigma, Camera] 40 - Sigma 0x0028: 0 +[MakerNotes, Sigma, Camera] 41 - Sigma 0x0029: 0 +[MakerNotes, Sigma, Camera] 42 - Sigma 0x002a: 0 +[MakerNotes, Sigma, Camera] 43 - Sigma 0x002b: 0 +[MakerNotes, Sigma, Camera] 44 - Color Mode: Standard +[MakerNotes, Sigma, Camera] 45 - Sigma 0x002d: 0 +[MakerNotes, Sigma, Camera] 46 - Sigma 0x002e: 0 +[MakerNotes, Sigma, Camera] 47 - Sigma 0x002f: 0 +[MakerNotes, Sigma, Camera] 48 - Lens Aperture Range: 2.8 to 14 +[MakerNotes, Sigma, Camera] 49 - F Number: 2.8 +[MakerNotes, Sigma, Camera] 50 - Exposure Time: 1/10 +[MakerNotes, Sigma, Camera] 51 - Exposure Time 2: 1/10 +[MakerNotes, Sigma, Camera] 52 - Burst Shot: 0 +[MakerNotes, Sigma, Camera] 53 - Exposure Compensation: 0 +[MakerNotes, Sigma, Camera] 54 - Sigma 0x0036: +[MakerNotes, Sigma, Camera] 55 - Sigma 0x0037: +[MakerNotes, Sigma, Camera] 56 - Sigma 0x0038: +[MakerNotes, Sigma, Camera] 57 - Sensor Temperature: 20 C +[MakerNotes, Sigma, Camera] 58 - Flash Exposure Comp: 0 +[MakerNotes, Sigma, Camera] 59 - Firmware: 1.03.0.000 +[MakerNotes, Sigma, Camera] 60 - White Balance: Auto +[MakerNotes, Sigma, Preview] Exif-PreviewImage - Preview Image: (Binary data 26 bytes) +[IPTC, IPTC, Other] 25 - Keywords: one, two +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.48 +[XMP, XMP-dc, Image] title - Title: A title +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.8 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/10 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown ( ) +[Composite, Composite, Image] Exif-LightValue - Light Value: 6.3 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.7 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.018 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 47.4 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 24.2 mm (35 mm equivalent: 41.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 11.79 m diff --git a/ExifTool/t/Sony.t b/ExifTool/t/Sony.t new file mode 100644 index 0000000..91317f6 --- /dev/null +++ b/ExifTool/t/Sony.t @@ -0,0 +1,63 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Sony.t". + +BEGIN { + $| = 1; print "1..5\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Sony; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Sony'; +my $testnum = 1; + +# test 2: Extract information from Sony.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Sony.jpg',{Unknown=>1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( + [FlashFired => 'true'], + [ISO => undef], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 4: Test Sony decryption +{ + ++$testnum; + my $data = pack('N', 0x34a290d3); + Image::ExifTool::Sony::Decrypt(\$data, 0, 4, 0x12345678); + my $expected = 0x20677968; + my $got = unpack('N', $data); + unless ($got == $expected) { + warn "\n Test $testnum (decryption) returned wrong value:\n"; + warn sprintf(" Expected 0x%x but got 0x%x\n", $expected, $got); + notOK(); + } + print "ok $testnum\n"; +} + +# test 5: Extract information from a PMP image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Sony.pmp'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Sony_2.out b/ExifTool/t/Sony_2.out new file mode 100644 index 0000000..a1da5be --- /dev/null +++ b/ExifTool/t/Sony_2.out @@ -0,0 +1,86 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Sony.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.8 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:01:04 14:02:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:52-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: SONY +[EXIF, IFD0, Camera] 272 - Camera Model Name: DSC-F828 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:18 13:51:33 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/250 +[EXIF, ExifIFD, Image] 33437 - F Number: 7.1 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 64 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:18 13:51:33 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:18 13:51:33 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 8 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 12.5 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3264 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2448 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto bracket +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Camera] 271 - Make: SONY +[EXIF, IFD1, Camera] 272 - Camera Model Name: DSC-F828 +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Time] 306 - Modify Date: 2003:12:18 13:51:33 +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 2502 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Sony, Camera] 8192 - Sony 0x2000: 0 +[MakerNotes, Sony, Camera] 36865 - Sony 0x9001: ..­mQV„ûV„û.³9°9ŠJuÿ.Ŭ]è^ޏ.[...] +[MakerNotes, Sony, Camera] 36866 - Sony 0x9002: .JÚ`.p.V!.!¾ê¾ê0ÿ0ÿV!ˆPp,×.0c[...] +[MakerNotes, Sony, Camera] 36867 - Sony 0x9003: .ÜsF®&.¡ÒB.¯.....È..ûÙ;›Õ.utN½½½½½½(¶^w’[...] +[MakerNotes, Sony, Camera] 36868 - Sony 0x9004: ..,.,.,.,.ô.ô.ô.ô.ô.ô.ô.ôlêoÄ@.o”.,‚.,Ï.o‰3úM[...] +[MakerNotes, Sony, Camera] 36869 - Sony 0x9005: ..@o........}[...] +[MakerNotes, Sony, Camera] 36870 - Sony 0x9006: .ð™ârï‘.{.•ân‡öGeG‘š‘`zâ[...] +[MakerNotes, Sony, Camera] 36871 - Sony 0x9007: .ç;.|.cçf.VØÌ£.üêbêØ.ßç¶^O[...] +[MakerNotes, Sony, Camera] 36872 - Sony 0x9008: ..ªø..¼·’.³ôi.Þ.Ð@÷.]:þïó•±ö;Žî;ä‚.ÕH[...] +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x00000001 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000000 +[Composite, Composite, Image] Exif-Aperture - Aperture: 7.1 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/250 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 12.5 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.3 diff --git a/ExifTool/t/Sony_3.out b/ExifTool/t/Sony_3.out new file mode 100644 index 0000000..d3b76ce --- /dev/null +++ b/ExifTool/t/Sony_3.out @@ -0,0 +1,87 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Sony_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 5.6 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:52-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:52-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:52-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: SONY +[EXIF, IFD0, Camera] 272 - Camera Model Name: DSC-F828 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:18 13:51:33 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/250 +[EXIF, ExifIFD, Image] 33437 - F Number: 7.1 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:18 13:51:33 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:18 13:51:33 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 8 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 12.5 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3264 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2448 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto bracket +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Camera] 271 - Make: SONY +[EXIF, IFD1, Camera] 272 - Camera Model Name: DSC-F828 +[EXIF, IFD1, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Time] 306 - Modify Date: 2003:12:18 13:51:33 +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 2486 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Sony, Camera] 8192 - Sony 0x2000: 0 +[MakerNotes, Sony, Camera] 36865 - Sony 0x9001: ..­mQV„ûV„û.³9°9ŠJuÿ.Ŭ]è^ޏ.[...] +[MakerNotes, Sony, Camera] 36866 - Sony 0x9002: .JÚ`.p.V!.!¾ê¾ê0ÿ0ÿV!ˆPp,×.0c[...] +[MakerNotes, Sony, Camera] 36867 - Sony 0x9003: .ÜsF®&.¡ÒB.¯.....È..ûÙ;›Õ.utN½½½½½½(¶^w’[...] +[MakerNotes, Sony, Camera] 36868 - Sony 0x9004: ..,.,.,.,.ô.ô.ô.ô.ô.ô.ô.ôlêoÄ@.o”.,‚.,Ï.o‰3úM[...] +[MakerNotes, Sony, Camera] 36869 - Sony 0x9005: ..@o........}[...] +[MakerNotes, Sony, Camera] 36870 - Sony 0x9006: .ð™ârï‘.{.•ân‡öGeG‘š‘`zâ[...] +[MakerNotes, Sony, Camera] 36871 - Sony 0x9007: .ç;.|.cçf.VØÌ£.üêbêØ.ßç¶^O[...] +[MakerNotes, Sony, Camera] 36872 - Sony 0x9008: ..ªø..¼·’.³ôi.Þ.Ð@÷.]:þïó•±ö;Žî;ä‚.ÕH[...] +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x00000001 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000000 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-exif, Camera] FlashFired - Flash Fired: True +[Composite, Composite, Image] Exif-Aperture - Aperture: 7.1 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/250 +[Composite, Composite, Camera] XMP-Flash - Flash: Fired +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 12.5 mm diff --git a/ExifTool/t/Sony_5.out b/ExifTool/t/Sony_5.out new file mode 100644 index 0000000..8cbe9d6 --- /dev/null +++ b/ExifTool/t/Sony_5.out @@ -0,0 +1,34 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[ExifTool, ExifTool, ExifTool] - Make: Sony +[ExifTool, ExifTool, ExifTool] - Model: DSC-F1 +[File, System, Other] FileName - File Name: Sony.pmp +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 375 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:04:13 18:42:12-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:21-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: PMP +[File, File, Other] FileTypeExtension - File Type Extension: pmp +[File, File, Other] MIMEType - MIME Type: image/x-sony-pmp +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[MakerNotes, Sony, Image] 8 - Jpg From Raw Start: 124 +[MakerNotes, Sony, Image] 12 - Jpg From Raw Length: 251 +[MakerNotes, Sony, Image] 22 - Sony Image Width: 640 +[MakerNotes, Sony, Image] 24 - Sony Image Height: 480 +[MakerNotes, Sony, Image] 27 - Orientation: Horizontal (normal) +[MakerNotes, Sony, Image] 29 - Image Quality: Standard +[MakerNotes, Sony, Image] 52 - Comment: +[MakerNotes, Sony, Time] 76 - Date/Time Original: 1998:09:01 20:19:57 +[MakerNotes, Sony, Time] 84 - Modify Date: 1998:09:01 20:19:57 +[MakerNotes, Sony, Image] 102 - Exposure Time: 1/100 +[MakerNotes, Sony, Camera] 118 - Flash: No Flash +[MakerNotes, Sony, Preview] Exif-JpgFromRaw - Jpg From Raw: (Binary data 251 bytes) +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/100 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/TestLib.pm b/ExifTool/t/TestLib.pm new file mode 100644 index 0000000..6caac91 --- /dev/null +++ b/ExifTool/t/TestLib.pm @@ -0,0 +1,469 @@ +#------------------------------------------------------------------------------ +# File: TestLib.pm +# +# Description: Utility routines for testing ExifTool modules +# +# Revisions: Feb. 19/04 - P. Harvey Created +# Feb. 26/04 - P. Harvey Name temporary file ".failed" and erase +# it if the test passes +# Feb. 27/04 - P. Harvey Change print format and allow ExifTool +# object to be passed instead of tags hash ref. +# Oct. 30/04 - P. Harvey Split testCompare() into separate sub. +# May 18/05 - P. Harvey Tolerate round-off errors in floats. +# Feb. 02/08 - P. Harvey Allow different timezones in time values +# Sep. 16/08 - P. Harvey Improve timezone testing +# Jul. 14/10 - P. Harvey Added writeInfo() +# Jan. 06/12 - P. Harvey Patched MirBSD leap second "feature" +# Jun. 08/21 - PH Patched float compare to fix quadmath test failure +#------------------------------------------------------------------------------ + +package t::TestLib; + +use strict; +require 5.002; +require Exporter; +use Image::ExifTool qw(ImageInfo); + +use vars qw($VERSION @ISA @EXPORT); +$VERSION = '1.23'; +@ISA = qw(Exporter); +@EXPORT = qw(check writeCheck writeInfo testCompare binaryCompare testVerbose notOK done); + +my $noTimeLocal; +my $rtnCode = 0; + +sub nearEnough($$); +sub nearTime($$$$); +sub formatValue($); +sub writeInfo($$;$$$); +sub notOK(); + +#------------------------------------------------------------------------------ +# Compare 2 binary files +# Inputs: 0) file name 1, 1) file name 2 +# Returns: 1 if files are identical +sub binaryCompare($$) +{ + my ($file1, $file2) = @_; + my $success = 1; + open(TESTFILE1, $file1) or return 0; + unless (open(TESTFILE2, $file2)) { + close(TESTFILE1); + return 0; + } + binmode(TESTFILE1); + binmode(TESTFILE2); + my ($buf1, $buf2); + while (read(TESTFILE1, $buf1, 65536)) { + read(TESTFILE2, $buf2, 65536) or $success = 0, last; + $buf1 eq $buf2 or $success = 0, last; + } + read(TESTFILE2, $buf2, 65536) and $success = 0; + close(TESTFILE1); + close(TESTFILE2); + return $success +} + +#------------------------------------------------------------------------------ +# Compare 2 files and return true and erase the 2nd file if they are the same +# Inputs: 0) file1, 1) file2, 2) test number, 3) flag to not erase test file +# Returns: true if files are the same +sub testCompare($$$;$) +{ + my ($stdfile, $testfile, $testnum, $keep) = @_; + my $success = 0; + my $linenum; + + my $oldSep = $/; + $/ = "\x0a"; # set input line separator + if (open(FILE1, $stdfile)) { + if (open(FILE2, $testfile)) { + $success = 1; + my ($line1, $line2); + my $linenum = 0; + my $skip = 0; + for (;;) { + $line1 = <FILE1> unless $skip == 1; + last unless defined $line1; + ++$linenum; + $line2 = <FILE2> unless $skip == 2; + $skip = 0; + if (defined $line2) { + next if $line1 eq $line2; + next if nearEnough($line1, $line2); + # ignore IPTCDigest warning if Digest::MD5 isn't available + if ($line1 =~ /Warning: IPTCDigest is not current/ and + not eval 'require Digest::MD5') + { + $skip = 2; + next; + } elsif ($line2 =~ /Warning: IPTCDigest is not current/ and + not eval 'require Digest::MD5') + { + $skip = 1; + next; + } + } + $success = 0; + last; + } + if ($success) { + # make sure there is nothing left in file2 + $line2 = <FILE2>; + if ($line2) { + ++$linenum; + $success = 0; + } + } + unless ($success) { + warn "\n Test $testnum differs beginning at line $linenum:\n"; + defined $line1 or $line1 = '(null)'; + defined $line2 or $line2 = '(null)'; + chomp($line1,$line2); + warn qq{ Test gave: "$line2"\n}; + warn qq{ Should be: "$line1"\n}; + } + close(FILE2); + } + close(FILE1); + } + $/ = $oldSep; # restore input line separator + + # erase .failed file if test was successful + $success and not $keep and unlink $testfile; + + return $success +} + +#------------------------------------------------------------------------------ +# Return true if two test lines are close enough +# Inputs: 0) line1, 1) line2 +# Returns: true if lines are similar enough to pass test +sub nearEnough($$) +{ + my ($line1, $line2) = @_; + + # of course, the version number will change... + return 1 if $line1 =~ /^(.*ExifTool.*)\b\d{1,2}\.\d{2}\b(.*)/s and + ($line2 eq "$1$Image::ExifTool::VERSION$Image::ExifTool::RELEASE$2" or + $line2 eq "$1$Image::ExifTool::VERSION$2"); + + # allow different FileModifyDate, FileAccessDate, FileCreateDate/FileInodeChangeDate and FilePermissions + return 1 if $line1 =~ /(File\s?(Modif.*Date|Access\s?Date|Inode\s?Change\s?Date|Permissions))/ and + ($line2 =~ /$1/ or $line2 =~ /File\s?Creat.*Date/); + + # allow CurrentIPTCDigest to be zero if Digest::MD5 isn't installed + return 1 if $line1 =~ /Current IPTC Digest/ and + $line2 =~ /Current IPTC Digest: (0|#){32}/ and + not eval 'require Digest::MD5'; + + # analyze every token in the line, and allow rounding + # or format differences in floating point numbers + my @toks1 = split /\s+/, $line1; + my @toks2 = split /\s+/, $line2; + my $lenChanged = 0; + my $i; + for ($i=0; ; ++$i) { + return 1 if $i >= @toks1 and $i >= @toks2; # all tokens were OK + my $tok1 = $toks1[$i]; + my $tok2 = $toks2[$i]; + last unless defined $tok1 and defined $tok2; + next if $tok1 eq $tok2; + # can't compare any more if either line was truncated (ie. ends with '[...]' or '[snip]') + if ($tok1 =~ /\[(\.{3}|snip)\]$/ or $tok2 =~ /\[(\.{3}|snip)\]$/) { + return 1 if $tok1=~ /^[-+]?\d+\./ or $tok2=~/^[-+]?\d+\./; # check for float + return $lenChanged + } + if ($tok1 =~ /^(\d{2}|\d{4}):\d{2}:\d{2}/ and $tok2 =~ /^(\d{2}|\d{4}):\d{2}:\d{2}/ and + not eval { require Time::Local }) + { + unless ($noTimeLocal) { + warn "Ignored time difference(s) because Time::Local is not installed\n"; + $noTimeLocal = 1; + } + next; # ignore times if Time::Local not available + # account for different timezones + } elsif ($tok1 =~ /^(\d{2}:\d{2}:\d{2})(Z|[-+]\d{2}:\d{2})$/i) { + my $time = $1; # remove timezone + # timezone may be wrong if writing date/time value in a different timezone + next if $tok2 =~ /^(\d{2}:\d{2}:\d{2})(Z|[-+]\d{2}:\d{2})$/i and $time eq $1; + # date/time may be wrong to if converting GMT value to local time + last unless $i and $toks1[$i-1] =~ /^\d{4}:\d{2}:\d{2}$/ and + $toks2[$i-1] =~ /^\d{4}:\d{2}:\d{2}$/; + $tok1 = $toks1[$i-1] . ' ' . $tok1; # add date to give date/time value + $tok2 = $toks2[$i-1] . ' ' . $tok2; + last unless nearTime($tok1, $tok2, $line1, $line2); + # date may be different if timezone shifted into next day + } elsif ($tok1 =~ /^\d{4}:\d{2}:\d{2}$/ and $tok2 =~ /^\d{4}:\d{2}:\d{2}$/ and + defined $toks1[$i+1] and defined $toks2[$i+1] and + $toks1[$i+1] =~ /^(\d{2}:\d{2}:\d{2})(Z|[-+]\d{2}:\d{2})$/i and + $toks2[$i+1] =~ /^(\d{2}:\d{2}:\d{2})(Z|[-+]\d{2}:\d{2})$/i) + { + ++$i; + $tok1 .= ' ' . $toks1[$i]; # add time to give date/time value + $tok2 .= ' ' . $toks2[$i]; + last unless nearTime($tok1, $tok2, $line1, $line2); + # handle floating point numbers filtered by ExifTool test 29 + } elsif ($tok1 =~ s/(\.#)#*(e[-+]\#+)?/$1/g or $tok2 =~ s/(\.#)#*(e[-+]\#+)?/$1/g) { + $tok2 =~ s/(\.#)#*(e[-+]\#+)?/$1/g; + last if $tok1 ne $tok2; + } else { + # check to see if both tokens are floating point numbers (with decimal points!) + if ($tok1 =~ s/([^\d.]+)$//) { # remove trailing units + my $a = $1; + last unless $tok2 =~ s/\Q$a\E$//; + } + if ($tok1 =~ s/^(\d+:\d+:)//) { # remove leading HH:MM: + my $a = $1; + last unless $tok2 =~ s/^\Q$a//; + } + if ($tok1 =~ s/^'//) { # remove leading quote + last unless $tok2 =~ s/^'//; + } + last unless Image::ExifTool::IsFloat($tok1) and + Image::ExifTool::IsFloat($tok2); + last if $tok1 == 0 or $tok2 == 0; + # numbers are bad if not the same to 5 significant figures + if (abs(($tok1-$tok2)/($tok1+$tok2)) > 1e-5) { + # (but allow last digit to be different due to round-off errors) + my ($int1, $int2); + ($int1 = $tok1) =~ tr/0-9//dc; + ($int2 = $tok2) =~ tr/0-9//dc; + my $dlen = length($int1) - length($int2); + if ($dlen > 0) { + $int2 .= '0' x $dlen; + } elsif ($dlen < 0) { + $int1 .= '0' x (-$dlen); + } + last if abs($int1-$int2) > 1.00001; + } + } + # set flag if length changed + $lenChanged = 1 if length($tok1) ne length($tok2); + } + return 0; +} + +#------------------------------------------------------------------------------ +# Check two time strings to see if they are the same +# Inputs: 0) time1, 1) time2, 2) line1, 3) line2 +# Returns: true on success +sub nearTime($$$$) +{ + my ($tok1, $tok2, $line1, $line2) = @_; + my $t1 = Image::ExifTool::GetUnixTime($tok1, 'local') or return 0; + my $t2 = Image::ExifTool::GetUnixTime($tok2, 'local') or return 0; + my $td = $t2 - $t1; + if ($td) { + # patch for the MirBSD leap-second unconformity + # (120 leap seconds should cover us until _well_ into the future) + return 0 unless $^O eq 'mirbsd' and $td < 0 and $td > -120; + warn "\n Ignoring $td second error due to MirBSD leap-second \"feature\":\n"; + chomp($line1,$line2); + warn qq{ Test gave: "$line2"\n}; + warn qq{ Should be: "$line1"\n}; + } + return 1; +} + +#------------------------------------------------------------------------------ +# Format value for printing +# Inputs: 0) value +# Returns: string for printing +sub formatValue($) +{ + local $_; + my $val = shift; + my ($str, @a); + if (ref $val eq 'SCALAR') { + if ($$val =~ /^Binary data/) { + $str = "($$val)"; + } else { + $str = '(Binary data ' . length($$val) . ' bytes)'; + } + } elsif (ref $val eq 'ARRAY') { + foreach (@$val) { + push @a, formatValue($_); + } + $str = '[' . join(',', @a) . ']'; + } elsif (ref $val eq 'HASH') { + my $key; + foreach $key (sort keys %$val) { + push @a, $key . '=' . formatValue($$val{$key}); + } + $str = '{' . join(',', @a) . '}'; + } elsif (defined $val) { + # make sure there are no linefeeds in output + ($str = $val) =~ tr/\x0a\x0d/;/; + # translate unknown characters + # $str =~ tr/\x01-\x1f\x80-\xff/\./; + $str =~ tr/\x01-\x1f\x7f/./; + # remove NULL chars + $str =~ s/\x00//g; + } else { + $str = ''; + } + return $str; +} + +#------------------------------------------------------------------------------ +# Compare extracted information against a standard output file +# Inputs: 0) [optional] ExifTool object reference +# 1) tag hash reference, 2) test name, 3) test number +# 4) test number for comparison file (if different than this test) +# 5) top group number to test (2 by default) +# Returns: 1 if check passed +sub check($$$;$$$) +{ + my $exifTool = shift if ref $_[0] ne 'HASH'; + my ($info, $testname, $testnum, $stdnum, $topGroup) = @_; + return 0 unless $info; + $stdnum = $testnum unless defined $stdnum; + my $testfile = "t/${testname}_$testnum.failed"; + my $stdfile = "t/${testname}_$stdnum.out"; + open(FILE, ">$testfile") or return 0; + + # use one type of linefeed so this test works across platforms + my $oldSep = $\; + $\ = "\x0a"; # set output line separator + + # get a list of found tags + my @tags; + if ($exifTool) { + if ($$exifTool{NO_SORT}) { + @tags = $exifTool->GetFoundTags(); + } else { + # sort tags by group to make it a bit prettier + @tags = $exifTool->GetTagList($info, 'Group0'); + } + } else { + @tags = sort keys %$info; + } +# +# Write information to file (with filename "TESTNAME_#.failed") +# + foreach (@tags) { + my $val = formatValue($$info{$_}); + # (no "\n" needed since we set the output line separator above) + if ($exifTool) { + my @groups = $exifTool->GetGroup($_); + my $groups = join ', ', @groups[0..($topGroup||2)]; + my $tagID = $exifTool->GetTagID($_); + my $desc = $exifTool->GetDescription($_); + print FILE "[$groups] $tagID - $desc: $val"; + } else { + print FILE "$_: $val"; + } + } + close(FILE); + + $\ = $oldSep; # restore output line separator +# +# Compare the output file to the output from the standard test (TESTNAME_#.out) +# + return testCompare($stdfile, $testfile, $testnum); +} + +#------------------------------------------------------------------------------ +# Test writing feature by writing specified information to JPEG file +# Inputs: 0) list reference to lists of SetNewValue arguments +# 1) test name, 2) test number, 3) optional source file name, +# 4) true to only check tags which were written (or list ref for tags to check) +# 5) flag set if nothing is expected to change in the output file +# 6) true to ignore warnings +# Returns: 1 if check passed +sub writeCheck($$$;$$$$) +{ + my ($writeInfo, $testname, $testnum, $srcfile, $onlyWritten, $same, $ignore) = @_; + $srcfile or $srcfile = "t/images/$testname.jpg"; + my ($ext) = ($srcfile =~ /\.(.+?)$/); + my $testfile = "t/${testname}_${testnum}_failed.$ext"; + my $exifTool = new Image::ExifTool; + my @tags; + if (ref $onlyWritten eq 'ARRAY') { + @tags = @$onlyWritten; + undef $onlyWritten; + } + foreach (@$writeInfo) { + $exifTool->SetNewValue(@$_); + push @tags, $$_[0] if $onlyWritten; + } + unlink $testfile; + my $ok = writeInfo($exifTool, $srcfile, $testfile, $same, $ignore); + my $info = $exifTool->ImageInfo($testfile,{Duplicates=>1,Unknown=>1},@tags); + my $rtnVal = check($exifTool, $info, $testname, $testnum); + return 0 unless $ok and $rtnVal; + unlink $testfile; + return 1; +} + +#------------------------------------------------------------------------------ +# Call Image::ExifTool::WriteInfo with error checking +# Inputs: 0) ExifTool ref, 1) src file, 2) dst file, 3) true if nothing should change +# 4) true to ignore warnings +# Return: true on success +sub writeInfo($$;$$$) +{ + my ($exifTool, $src, $dst, $same, $ignore) = @_; + # erase temporary file created by WriteInfo() if no destination file is given + # (may be left over from previous crashed tests) + unlink "${src}_exiftool_tmp" if not defined $dst and not ref $src; + my $result = $exifTool->WriteInfo($src, $dst); + my $err = ''; + $err .= " Error: WriteInfo() returned $result\n" if $result != ($same ? 2 : 1); + my $info = $exifTool->GetInfo('Warning', 'Error'); + foreach (sort keys %$info) { + next if $ignore and $_ =~ /^Warning/; + my $tag = Image::ExifTool::GetTagName($_); + $err .= " $tag: $$info{$_}\n"; + } + return 1 unless $err; + warn "\n$err"; + return 0; +} + +#------------------------------------------------------------------------------ +# Test verbose output +# Inputs: 0) test name, 1) test number, 2) Input file, 3) verbose level +# Returns: true if test passed +sub testVerbose($$$$) +{ + my ($testname, $testnum, $infile, $verbose) = @_; + my $testfile = "t/${testname}_$testnum"; + # capture verbose output by redirecting STDOUT + return 0 unless open(TMPFILE,">$testfile.tmp"); + ImageInfo($infile, { Verbose => $verbose, TextOut => \*TMPFILE }); + close(TMPFILE); + # re-write output file to change newlines to be same as standard test file + # (if I was a Perl guru, maybe I would know a better way to do this) + open(TMPFILE,"$testfile.tmp"); + open(TESTFILE,">$testfile.failed"); + my $oldSep = $\; + $\ = "\x0a"; # set output line separator + while (<TMPFILE>) { + chomp; # remove existing newline + print TESTFILE $_; # re-write line using \x0a for newlines + } + $\ = $oldSep; # restore output line separator + close(TESTFILE); + close(TMPFILE); + unlink("$testfile.tmp"); + return testCompare("$testfile.out","$testfile.failed",$testnum); +} + +#------------------------------------------------------------------------------ +# One of the tests failed +sub notOK() +{ + print 'not '; + $rtnCode = 1; +} + +#------------------------------------------------------------------------------ +# Done tests and exit +sub done() +{ + exit $rtnCode; +} + +1; #end diff --git a/ExifTool/t/Text.t b/ExifTool/t/Text.t new file mode 100644 index 0000000..e2b2ad7 --- /dev/null +++ b/ExifTool/t/Text.t @@ -0,0 +1,32 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Text.t". + +BEGIN { + $| = 1; print "1..7\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Text; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Text'; +my $testnum = 1; + +# tests 2-7: Test various types of text files +{ + my $exifTool = Image::ExifTool->new; + my $i; + for (my $i=1; $i<=6; ++$i) { + ++$testnum; + my $fname = 't/images/' . ($i < 6 ? "Text$i.txt" : 'Text.csv'); + my $info = $exifTool->ImageInfo($fname, '-system:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +done(); # end diff --git a/ExifTool/t/Text_2.out b/ExifTool/t/Text_2.out new file mode 100644 index 0000000..4f749d3 --- /dev/null +++ b/ExifTool/t/Text_2.out @@ -0,0 +1,8 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: TXT +[File, File, Other] FileTypeExtension - File Type Extension: txt +[File, File, Other] MIMEType - MIME Type: text/plain +[File, File, Other] MIMEEncoding - MIME Encoding: us-ascii +[File, File, Document] Newlines - Newlines: Unix LF +[File, File, Document] LineCount - Line Count: 1 +[File, File, Document] WordCount - Word Count: 4 diff --git a/ExifTool/t/Text_3.out b/ExifTool/t/Text_3.out new file mode 100644 index 0000000..0570fb2 --- /dev/null +++ b/ExifTool/t/Text_3.out @@ -0,0 +1,8 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: TXT +[File, File, Other] FileTypeExtension - File Type Extension: txt +[File, File, Other] MIMEType - MIME Type: text/plain +[File, File, Other] MIMEEncoding - MIME Encoding: iso-8859-1 +[File, File, Document] Newlines - Newlines: Windows CRLF +[File, File, Document] LineCount - Line Count: 1 +[File, File, Document] WordCount - Word Count: 4 diff --git a/ExifTool/t/Text_4.out b/ExifTool/t/Text_4.out new file mode 100644 index 0000000..d15d22e --- /dev/null +++ b/ExifTool/t/Text_4.out @@ -0,0 +1,9 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: TXT +[File, File, Other] FileTypeExtension - File Type Extension: txt +[File, File, Other] MIMEType - MIME Type: text/plain +[File, File, Other] MIMEEncoding - MIME Encoding: utf-8 +[File, File, Document] ByteOrderMark - Byte Order Mark: Yes +[File, File, Document] Newlines - Newlines: Unix LF +[File, File, Document] LineCount - Line Count: 1 +[File, File, Document] WordCount - Word Count: 4 diff --git a/ExifTool/t/Text_5.out b/ExifTool/t/Text_5.out new file mode 100644 index 0000000..37a579f --- /dev/null +++ b/ExifTool/t/Text_5.out @@ -0,0 +1,8 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: TXT +[File, File, Other] FileTypeExtension - File Type Extension: txt +[File, File, Other] MIMEType - MIME Type: text/plain +[File, File, Other] MIMEEncoding - MIME Encoding: unknown-8bit +[File, File, Document] Newlines - Newlines: Macintosh CR +[File, File, Document] LineCount - Line Count: 1 +[File, File, Document] WordCount - Word Count: 4 diff --git a/ExifTool/t/Text_6.out b/ExifTool/t/Text_6.out new file mode 100644 index 0000000..bf427e7 --- /dev/null +++ b/ExifTool/t/Text_6.out @@ -0,0 +1,7 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: TXT +[File, File, Other] FileTypeExtension - File Type Extension: txt +[File, File, Other] MIMEType - MIME Type: text/plain +[File, File, Other] MIMEEncoding - MIME Encoding: utf-16be +[File, File, Document] ByteOrderMark - Byte Order Mark: Yes +[File, File, Document] Newlines - Newlines: Unix LF diff --git a/ExifTool/t/Text_7.out b/ExifTool/t/Text_7.out new file mode 100644 index 0000000..cee5e4a --- /dev/null +++ b/ExifTool/t/Text_7.out @@ -0,0 +1,10 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.88 +[File, File, Other] FileType - File Type: CSV +[File, File, Other] FileTypeExtension - File Type Extension: csv +[File, File, Other] MIMEType - MIME Type: text/csv +[File, File, Other] MIMEEncoding - MIME Encoding: us-ascii +[File, File, Document] Newlines - Newlines: Unix LF +[File, File, Document] Delimiter - Delimiter: Comma +[File, File, Document] Quoting - Quoting: (none) +[File, File, Document] ColumnCount - Column Count: 6 +[File, File, Document] RowCount - Row Count: 3 diff --git a/ExifTool/t/Torrent.t b/ExifTool/t/Torrent.t new file mode 100644 index 0000000..7bbec9f --- /dev/null +++ b/ExifTool/t/Torrent.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Torrent.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Torrent; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Torrent'; +my $testnum = 1; + +# test 2: Extract information from a BitTorrent file +{ + my $exifTool = Image::ExifTool->new; + ++$testnum; + my $info = $exifTool->ImageInfo('t/images/Torrent.torrent'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Torrent_2.out b/ExifTool/t/Torrent_2.out new file mode 100644 index 0000000..0205df6 --- /dev/null +++ b/ExifTool/t/Torrent_2.out @@ -0,0 +1,32 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Torrent.torrent +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 837 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2013:09:06 11:36:26-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:52-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: Torrent +[File, File, Other] FileTypeExtension - File Type Extension: torrent +[File, File, Other] MIMEType - MIME Type: application/x-bittorrent +[Torrent, Torrent, Document] announce - Announce: udp://tracker.bogus.com:80/announce +[Torrent, Torrent, Document] announce-list1 - Announce List 1: udp://tracker.bogus.com:80/announce +[Torrent, Torrent, Document] announce-list2 - Announce List 2: udp://tracker.bogus2.com:80/announce +[Torrent, Torrent, Document] announce-list3 - Announce List 3: udp://tracker.bogus3.com:80/announce +[Torrent, Torrent, Document] comment - Comment: Test BitTorrent description file +[Torrent, Torrent, Document] created by - Creator: uTorrent/1840 +[Torrent, Torrent, Time] creation date - Create Date: 2013:09:06 11:33:59-04:00 +[Torrent, Torrent, Document] encoding - Encoding: UTF-8 +[Torrent, Torrent, Document] length1 - File 1 Length: 3.7 MB +[Torrent, Torrent, Document] path1 - File 1 Path: exiftool-9.35.zip +[Torrent, Torrent, Document] length2 - File 2 Length: 3.7 MB +[Torrent, Torrent, Document] path2 - File 2 Path: Image-ExifTool-9.35.tar.gz +[Torrent, Torrent, Document] length3 - File 3 Length: 2.4 MB +[Torrent, Torrent, Document] path3 - File 3 Path: ExifTool-9.35.dmg +[Torrent, Torrent, Document] length4 - File 4 Length: 11 kB +[Torrent, Torrent, Document] path4 - File 4 Path: docs/README +[Torrent, Torrent, Document] name - Name: Image-ExifTool-9.35 +[Torrent, Torrent, Document] piece length - Piece Length: 1048576 +[Torrent, Torrent, Document] pieces - Pieces: (Binary data 200 bytes) +[Torrent, Torrent, Document] url-list1 - URL List 1: http://seed.bogus.com/ +[Torrent, Torrent, Document] url-list2 - URL List 2: http://seed.bogus2.com/ diff --git a/ExifTool/t/Unknown.t b/ExifTool/t/Unknown.t new file mode 100644 index 0000000..fb2d0ce --- /dev/null +++ b/ExifTool/t/Unknown.t @@ -0,0 +1,36 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Unknown.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Unknown; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Unknown'; +my $testnum = 1; + +# test 2: Extract information from Unknown.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Unknown.jpg', {Unknown => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Write some new information +{ + ++$testnum; + my @writeInfo = ( ['FocalLength',200] ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Unknown_2.out b/ExifTool/t/Unknown_2.out new file mode 100644 index 0000000..533e6b7 --- /dev/null +++ b/ExifTool/t/Unknown_2.out @@ -0,0 +1,92 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Unknown.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 7.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:06:16 10:57:05-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:53-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: Rollei +[EXIF, IFD0, Camera] 272 - Camera Model Name: dk4010 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: RO-dk4010 Ver 1.00 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:08:10 06:24:38 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/180 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.8 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 125 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:08:10 06:24:38 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:08:10 06:24:38 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 4 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/208 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.1 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 57.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Time] 37520 - Sub Sec Time: 331 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 331 +[EXIF, ExifIFD, Time] 37522 - Sub Sec Time Digitized: 331 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2272 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1704 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 1.3 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: Low gain up +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 6922 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, MakerUnknown, Camera] 1 - Unknown 0x0001: ADM0000037036 +[MakerNotes, MakerUnknown, Camera] 4 - Unknown 0x0004: AE.. ^;k...MULTIoo/..................;[...] +[MakerNotes, MakerUnknown, Camera] 6 - Unknown 0x0006: AFÿÿ...Û.ÿÿgò.rá.ýHî;ŸÍ.~4.". †.ˆ.Û.Û.Û.÷[...] +[MakerNotes, MakerUnknown, Camera] 8 - Unknown 0x0008: WB.|ãÿÿÿ<.dCÿÿÿÄ.­..„.(.„.;n[...] +[MakerNotes, MakerUnknown, Camera] 10 - Unknown 0x000a: ..;..SDSD512€...[...] +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00160016 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x00000001 +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x00000005 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x000000ff +[PrintIM, PrintIM, Printing] 258 - Print IM 0x0102: 0x00000083 +[PrintIM, PrintIM, Printing] 261 - Print IM 0x0105: 0x00000083 +[PrintIM, PrintIM, Printing] 262 - Print IM 0x0106: 0x00000083 +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.8 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/180 +[Composite, Composite, Time] Exif-SubSecCreateDate - Create Date: 2005:08:10 06:24:38.331 +[Composite, Composite, Time] Exif-SubSecDateTimeOriginal - Date/Time Original: 2005:08:10 06:24:38.331 +[Composite, Composite, Time] Exif-SubSecModifyDate - Modify Date: 2005:08:10 06:24:38.331 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 57.0 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 10.1 diff --git a/ExifTool/t/Unknown_3.out b/ExifTool/t/Unknown_3.out new file mode 100644 index 0000000..a8188f3 --- /dev/null +++ b/ExifTool/t/Unknown_3.out @@ -0,0 +1,92 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Unknown_3_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 7.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:53-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:53-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:53-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: Rollei +[EXIF, IFD0, Camera] 272 - Camera Model Name: dk4010 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: RO-dk4010 Ver 1.00 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:08:10 06:24:38 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/180 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.8 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 125 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:08:10 06:24:38 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:08:10 06:24:38 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 4 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/208 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.1 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 200.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Time] 37520 - Sub Sec Time: 331 +[EXIF, ExifIFD, Time] 37521 - Sub Sec Time Original: 331 +[EXIF, ExifIFD, Time] 37522 - Sub Sec Time Digitized: 331 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 2272 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 1704 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 1.3 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41991 - Gain Control: Low gain up +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 6922 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, MakerUnknown, Camera] 1 - Unknown 0x0001: ADM0000037036 +[MakerNotes, MakerUnknown, Camera] 4 - Unknown 0x0004: AE.. ^;k...MULTIoo/..................;[...] +[MakerNotes, MakerUnknown, Camera] 6 - Unknown 0x0006: AFÿÿ...Û.ÿÿgò.rá.ýHî;ŸÍ.~4.". †.ˆ.Û.Û.Û.÷[...] +[MakerNotes, MakerUnknown, Camera] 8 - Unknown 0x0008: WB.|ãÿÿÿ<.dCÿÿÿÄ.­..„.(.„.;n[...] +[MakerNotes, MakerUnknown, Camera] 10 - Unknown 0x000a: ..;..SDSD512€...[...] +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 1 - Print IM 0x0001: 0x00160016 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x00000001 +[PrintIM, PrintIM, Printing] 256 - Print IM 0x0100: 0x00000005 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x000000ff +[PrintIM, PrintIM, Printing] 258 - Print IM 0x0102: 0x00000083 +[PrintIM, PrintIM, Printing] 261 - Print IM 0x0105: 0x00000083 +[PrintIM, PrintIM, Printing] 262 - Print IM 0x0106: 0x00000083 +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.8 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/180 +[Composite, Composite, Time] Exif-SubSecCreateDate - Create Date: 2005:08:10 06:24:38.331 +[Composite, Composite, Time] Exif-SubSecDateTimeOriginal - Date/Time Original: 2005:08:10 06:24:38.331 +[Composite, Composite, Time] Exif-SubSecModifyDate - Modify Date: 2005:08:10 06:24:38.331 +[Composite, Composite, Image] Exif-LightValue - Light Value: 10.1 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 200.0 mm diff --git a/ExifTool/t/VCard.t b/ExifTool/t/VCard.t new file mode 100644 index 0000000..738d8d3 --- /dev/null +++ b/ExifTool/t/VCard.t @@ -0,0 +1,31 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/VCard.t". + +BEGIN { + $| = 1; print "1..3\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::VCard; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'VCard'; +my $testnum = 1; + +# tests 2-3: Extract information from test files +{ + my $exifTool = Image::ExifTool->new; + my $ext; + foreach $ext (qw(vcf ics)) { + ++$testnum; + my $info = $exifTool->ImageInfo("t/images/VCard.$ext"); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +done(); # end diff --git a/ExifTool/t/VCard_2.out b/ExifTool/t/VCard_2.out new file mode 100644 index 0000000..dfca1f8 --- /dev/null +++ b/ExifTool/t/VCard_2.out @@ -0,0 +1,54 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.78 +[File, System, Other] FileName - File Name: VCard.vcf +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1691 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 11:06:35-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:12:04 11:06:39-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:12:04 11:06:35-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: VCard +[File, File, Other] FileTypeExtension - File Type Extension: vcf +[File, File, Other] MIMEType - MIME Type: text/vcard +[VCard, VCard, Document] Version - VCard Version: 3.0 +[VCard, VCard, Document] Prodid - Software: -//Apple Inc.//Mac OS X 10.10.2//EN +[VCard, VCard, Author] N - Name: Harvey;Phil;;; +[VCard, VCard, Author] Fn - Formatted Name: Phil Harvey +[VCard, VCard, Document] Org - Organization: ExifTool; +[VCard, VCard, Document] EmailInternetHomePref - Email Internet Home Pref: someone@home.email +[VCard, VCard, Document] EmailInternetWork - Email Internet Work: someone@work.email +[VCard, Item1, Document] EmailInternet - Email Internet: someone@other.email +[VCard, Item1, Document] X-ablabel - AB Label: Other +[VCard, VCard, Document] TelCellVoicePref - Telephone Cell Voice Pref: 555-0000 +[VCard, VCard, Document] TelIphoneCellVoice - Telephone Iphone Cell Voice: 555-1111 +[VCard, VCard, Document] TelHomeVoice - Telephone Home Voice: 555-2222 +[VCard, VCard, Document] TelWorkVoice - Telephone Work Voice: 555-3333 +[VCard, VCard, Document] TelMain - Telephone Main: 555-4444 +[VCard, VCard, Document] TelWorkFax - Telephone Work Fax: 555-5555 +[VCard, VCard, Document] TelPager - Telephone Pager: 555-6666 +[VCard, VCard, Document] TelOtherVoice - Telephone Other Voice: 555-7777 +[VCard, VCard, Document] TelOtherVoice - Telephone Other Voice: 555-8888 +[VCard, VCard, Location] AdrHomePref - Address Home Pref: ;;Home St.;City;ON;K0K0K0;Canada +[VCard, VCard, Location] AdrWork - Address Work: ;;Work St.;City;ON;K0K0K0;Canada +[VCard, VCard, Location] AdrOther - Address Other: ;;Other Rd.;City;ON;K0K0K0;Canada +[VCard, VCard, Location] AdrOtherGeo - Address Other Geolocation: 12.3457, 78.910 +[VCard, VCard, Document] AdrOtherLabel - Address Other Label: Test;Label +[VCard, VCard, Document] Note - Note: This is "a", comma, \backslash;newline;note! +[VCard, VCard, Document] Note-fr - Note (fr): Oui! +[VCard, Item2, Document] UrlPref - URL Pref: https://exiftool.org/ +[VCard, Item2, Document] X-ablabel - AB Label: HomePage +[VCard, VCard, Time] Bday - Birthday: 1900:01:02 +[VCard, VCard, Document] X-aimHomePref - AIM Home Pref: AIMName +[VCard, Item3, Document] X-icqPref - ICQ Pref: ICQName +[VCard, Item3, Document] X-ablabel - AB Label: Other +[VCard, VCard, Document] ImppHomePref - IMPP Home Pref: aim:AIMName +[VCard, VCard, Document] ImppWork - IMPP Work: xmpp:FacebookName +[VCard, Item4, Document] Impp - IMPP: aim:ICQName +[VCard, Item4, Document] X-ablabel - AB Label: Other +[VCard, VCard, Preview] PhotoJpeg - Photo Jpeg: (Binary data 18 bytes) +[VCard, VCard, Document] X-abuid - AB UID: EBAC8BE8-E695-457F-AE4D-E076ECEEA002:ABPerson +[VCard, VCard, Document] Version - VCard Version: 3.0 +[VCard, VCard, Author] Fn - Formatted Name: VCard Test +[VCard, VCard, Document] LabelWork - Label Work: 12 Cucumber St.;;SaladVille ON;;Canada +[VCard, VCard, Document] LogoImageJpeg - Logo Image Jpeg: (Binary data 18 bytes) +[VCard, VCard, Document] SoundOgg - Sound Ogg: http://example.com/sound.ogg +[VCard, VCard, Document] Note - Note: This sample mixes formats from various vCard versions diff --git a/ExifTool/t/VCard_3.out b/ExifTool/t/VCard_3.out new file mode 100644 index 0000000..40ab18a --- /dev/null +++ b/ExifTool/t/VCard_3.out @@ -0,0 +1,88 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: VCard.ics +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.3 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2015:05:03 12:36:08-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:53-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: ICS +[File, File, Other] FileTypeExtension - File Type Extension: ics +[File, File, Other] MIMEType - MIME Type: text/calendar +[VCard, VCalendar, Document] Calscale - Calendar Scale: GREGORIAN +[VCard, VCalendar, Document] Version - VCalendar Version: 2.0 +[VCard, VCalendar, Document] Method - Method: PUBLISH +[VCard, VCalendar, Document] X-wr-calname - Calendar Name: Calendar +[VCard, VCalendar, Time] X-wr-timezone - Time Zone 2: America/Toronto +[VCard, VCalendar, Document] X-apple-calendar-color - Calendar Color: #CC73E1 +[VCard, Timezone1, Time] Tzid - Timezone ID: Canada/Eastern +[VCard, Timezone1, Time] DaylightTzoffsetfrom - Daylight Timezone Offset From: -0500 +[VCard, Timezone1, Time] DaylightRrule - Daylight Recurrence Rule: FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +[VCard, Timezone1, Time] DaylightDtstart - Daylight Date Time Start: 2007:03:11 02:00:00 +[VCard, Timezone1, Time] DaylightTzname - Daylight Timezone Name: EDT +[VCard, Timezone1, Time] DaylightTzoffsetto - Daylight Timezone Offset To: -0400 +[VCard, Timezone1, Time] StandardTzoffsetfrom - Standard Timezone Offset From: -0400 +[VCard, Timezone1, Time] StandardRrule - Standard Recurrence Rule: FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +[VCard, Timezone1, Time] StandardDtstart - Standard Date Time Start: 2007:11:04 02:00:00 +[VCard, Timezone1, Time] StandardTzname - Standard Timezone Name: EST +[VCard, Timezone1, Time] StandardTzoffsetto - Standard Timezone Offset To: -0500 +[VCard, Timezone2, Time] Tzid - Timezone ID: America/Toronto +[VCard, Timezone2, Time] DaylightTzoffsetfrom - Daylight Timezone Offset From: -0500 +[VCard, Timezone2, Time] DaylightRrule - Daylight Recurrence Rule: FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +[VCard, Timezone2, Time] DaylightDtstart - Daylight Date Time Start: 2007:03:11 02:00:00 +[VCard, Timezone2, Time] DaylightTzname - Daylight Timezone Name: EDT +[VCard, Timezone2, Time] DaylightTzoffsetto - Daylight Timezone Offset To: -0400 +[VCard, Timezone2, Time] StandardTzoffsetfrom - Standard Timezone Offset From: -0400 +[VCard, Timezone2, Time] StandardRrule - Standard Recurrence Rule: FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +[VCard, Timezone2, Time] StandardDtstart - Standard Date Time Start: 2007:11:04 02:00:00 +[VCard, Timezone2, Time] StandardTzname - Standard Timezone Name: EST +[VCard, Timezone2, Time] StandardTzoffsetto - Standard Timezone Offset To: -0500 +[VCard, Event1, Time] Created - Date Created: 2012:08:07 15:27:46Z +[VCard, Event1, Document] Uid - UID: 5E9B5BEF-C3D9-452C-AB1A-613510B5BA52 +[VCard, Event1, Time] Rrule - Recurrence Rule: FREQ=WEEKLY;INTERVAL=1;BYDAY=MO;WKST=SU +[VCard, Event1, Time] Dtend - Date Time End: 2013:09:16 13:30:00 +[VCard, Event1, Time] DtendTzid - Date Time End Timezone ID: Canada/Eastern +[VCard, Event1, Time] Exdate - Exception Date Times: 2014:12:01 12:30:00 +[VCard, Event1, Time] ExdateTzid - Exception Date Times Timezone ID: Canada/Eastern +[VCard, Event1, Time] Exdate - Exception Date Times: 2015:04:27 12:30:00 +[VCard, Event1, Time] ExdateTzid - Exception Date Times Timezone ID: Canada/Eastern +[VCard, Event1, Document] Transp - Time Transparency: OPAQUE +[VCard, Event1, Document] Summary - Summary: Group meeting +[VCard, Event1, Time] Dtstart - Date Time Start: 2013:09:16 12:30:00 +[VCard, Event1, Time] DtstartTzid - Date Time Start Timezone ID: Canada/Eastern +[VCard, Event1, Time] Dtstamp - Date Time Stamp: 2015:04:20 15:55:18Z +[VCard, Event1, Location] Location - Location: 261 +[VCard, Event1, Document] Sequence - Sequence Number: 0 +[VCard, Event1, Document] Alarm1X-wr-alarmuid - Alarm 1 Alarm UID: C41775A2-C98F-456C-BB8F-8E6FB15A554F +[VCard, Event1, Document] Alarm1Uid - Alarm 1 UID: C41775A2-C98F-456C-BB8F-8E6FB15A554F +[VCard, Event1, Document] Alarm1Trigger - Alarm 1 Trigger: -PT5M +[VCard, Event1, Document] Alarm1Attach - Alarm 1 Attachment: Basso +[VCard, Event1, Document] Alarm1X-apple-local-default-alarm - Alarm 1 Local Default Alarm: TRUE +[VCard, Event1, Document] Alarm1Action - Alarm 1 Action: AUDIO +[VCard, Event1, Document] Alarm1X-apple-default-alarm - Alarm 1 Default Alarm: TRUE +[VCard, Event1, Document] Alarm2X-wr-alarmuid - Alarm 2 Alarm UID: 70894F68-E6F8-4C00-A737-73D4CDCF0525 +[VCard, Event1, Document] Alarm2Uid - Alarm 2 UID: 70894F68-E6F8-4C00-A737-73D4CDCF0525 +[VCard, Event1, Document] Alarm2Trigger - Alarm 2 Trigger: -PT5M +[VCard, Event1, Document] Alarm2X-apple-default-alarm - Alarm 2 Default Alarm: TRUE +[VCard, Event1, Document] Alarm2Attach - Alarm 2 Attachment: Basso +[VCard, Event1, Document] Alarm2Action - Alarm 2 Action: AUDIO +[VCard, Event2, Time] Created - Date Created: 2012:08:07 15:27:46Z +[VCard, Event2, Document] Uid - UID: 5E9B5BEF-C3D9-452C-AB1A-613510B5BA52 +[VCard, Event2, Time] Dtend - Date Time End: 2013:11:25 13:30:00 +[VCard, Event2, Time] DtendTzid - Date Time End Timezone ID: Canada/Eastern +[VCard, Event2, Document] Transp - Time Transparency: OPAQUE +[VCard, Event2, Document] Summary - Summary: Group meeting +[VCard, Event2, Time] Dtstart - Date Time Start: 2013:11:25 12:30:00 +[VCard, Event2, Time] DtstartTzid - Date Time Start Timezone ID: Canada/Eastern +[VCard, Event2, Time] Dtstamp - Date Time Stamp: 2015:04:08 14:29:41Z +[VCard, Event2, Location] Location - Location: 501 +[VCard, Event2, Document] Sequence - Sequence Number: 0 +[VCard, Event2, Document] Recurrence-id - Recurrence ID: 20131125T123000 +[VCard, Event2, Time] Recurrence-idTzid - Recurrence ID Timezone ID: Canada/Eastern +[VCard, Event2, Document] Alarm1X-wr-alarmuid - Alarm 1 Alarm UID: C514D9BB-BDB3-4A5F-B580-9287459DA35C +[VCard, Event2, Document] Alarm1Uid - Alarm 1 UID: C514D9BB-BDB3-4A5F-B580-9287459DA35C +[VCard, Event2, Document] Alarm1Trigger - Alarm 1 Trigger: -PT5M +[VCard, Event2, Document] Alarm1Attach - Alarm 1 Attachment: Basso +[VCard, Event2, Document] Alarm1X-apple-local-default-alarm - Alarm 1 Local Default Alarm: TRUE +[VCard, Event2, Document] Alarm1Action - Alarm 1 Action: AUDIO +[VCard, Event2, Document] Alarm1X-apple-default-alarm - Alarm 1 Default Alarm: TRUE diff --git a/ExifTool/t/Vorbis.t b/ExifTool/t/Vorbis.t new file mode 100644 index 0000000..e8ec443 --- /dev/null +++ b/ExifTool/t/Vorbis.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Vorbis.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::Vorbis; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'Vorbis'; +my $testnum = 1; + +# test 2: Extract information from test file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/Vorbis.ogg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Vorbis_2.out b/ExifTool/t/Vorbis_2.out new file mode 100644 index 0000000..22b3a8e --- /dev/null +++ b/ExifTool/t/Vorbis_2.out @@ -0,0 +1,29 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Vorbis.ogg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 5.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:11:10 21:57:27-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:53-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: OGG +[File, File, Other] FileTypeExtension - File Type Extension: ogg +[File, File, Other] MIMEType - MIME Type: audio/ogg +[Vorbis, Vorbis, Audio] 0 - Vorbis Version: 0 +[Vorbis, Vorbis, Audio] 4 - Audio Channels: 2 +[Vorbis, Vorbis, Audio] 5 - Sample Rate: 22050 +[Vorbis, Vorbis, Audio] 13 - Nominal Bitrate: 32 kbps +[Vorbis, Vorbis, Audio] vendor - Vendor: AO; aoTuV b4b [20051117] (based on Xiph.Org's libVorbis) +[Vorbis, Vorbis, Audio] COVERARTMIME - Cover Art MIME Type: image/jpeg +[Vorbis, Vorbis, Preview] COVERART - Cover Art: (Binary data 1351 bytes) +[Vorbis, Vorbis, Audio] MEDIAJUKEBOX:TOOL NAME - Mediajukebox Tool Name: Media Center +[Vorbis, Vorbis, Audio] MEDIAJUKEBOX:TOOL VERSION - Mediajukebox Tool Version: 11.1.197 +[Vorbis, Vorbis, Audio] GENRE - Genre: Funk +[Vorbis, Vorbis, Audio] MEDIAJUKEBOX:DATE - Mediajukebox Date: 38718 +[Vorbis, Vorbis, Time] DATE - Date: 2006 +[Vorbis, Vorbis, Audio] TITLE - Title: A 4s sample for testing embedded cover art +[Vorbis, Vorbis, Audio] TRACKNUMBER - Track Number: 1 +[Vorbis, Vorbis, Audio] COMMENT - Comment: a nice comment +[Vorbis, Vorbis, Author] ARTIST - Artist: Who Knows +[Vorbis, Vorbis, Audio] ALBUM - Album: The Test Album +[Composite, Composite, Other] Vorbis-Duration - Duration: 1.34 s (approx) diff --git a/ExifTool/t/WPG.t b/ExifTool/t/WPG.t new file mode 100644 index 0000000..a671e5f --- /dev/null +++ b/ExifTool/t/WPG.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/WPG.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::WPG; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'WPG'; +my $testnum = 1; + +# test 2: Extract information from WPG.wpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/WPG.wpg'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/WPG_2.out b/ExifTool/t/WPG_2.out new file mode 100644 index 0000000..6fe4c42 --- /dev/null +++ b/ExifTool/t/WPG_2.out @@ -0,0 +1,15 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.62 +[File, System, Other] FileName - File Name: WPG.wpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 159 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2023:05:01 10:27:55-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2023:05:02 15:28:14-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2023:05:02 11:49:56-04:00 +[File, System, Other] FilePermissions - File Permissions: -rwxr-xr-x +[File, File, Other] FileType - File Type: WPG +[File, File, Other] FileTypeExtension - File Type Extension: wpg +[File, File, Other] MIMEType - MIME Type: image/x-wpg +[File, File, Image] WPGVersion - WPG Version: 1.0 +[File, File, Image] ImageWidthInches - Image Width Inches: 8.85 +[File, File, Image] ImageHeightInches - Image Height Inches: 3.85 +[File, File, Image] Records - Records: Start WPG (Type 1), Start WPG (Type 2), Fill Attributes, Line Attributes, Polygon x 5, Fill Attributes, Line Attributes, End WPG diff --git a/ExifTool/t/WTV.t b/ExifTool/t/WTV.t new file mode 100644 index 0000000..ef3b9ea --- /dev/null +++ b/ExifTool/t/WTV.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/WTV.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::WTV; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'WTV'; +my $testnum = 1; + +# test 2: Extract information from WTV.wtv +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/WTV.wtv'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/WTV_2.out b/ExifTool/t/WTV_2.out new file mode 100644 index 0000000..6b5c544 --- /dev/null +++ b/ExifTool/t/WTV_2.out @@ -0,0 +1,80 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: WTV.wtv +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 7.7 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2018:06:04 16:11:36-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:58-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: WTV +[File, File, Other] FileTypeExtension - File Type Extension: wtv +[File, File, Other] MIMEType - MIME Type: video/x-ms-wtv +[WTV, WTV, Video] WM/MediaClassPrimaryID - Media Class Primary ID: bd3098dbb33aab4f8a371a995f7ff74b +[WTV, WTV, Video] WM/MediaClassSecondaryID - Media Class Secondary ID: 8a257fbaf762a947b21f4651c42a000e +[WTV, WTV, Video] Title - Title: Stargate SG-1 +[WTV, WTV, Video] WM/SubTitle - Subtitle: +[WTV, WTV, Video] WM/SubTitleDescription - Subtitle Description: Company Of Thieves: A power struggle in the deadly Lucian Alliance's ranks spells danger for SG-1. With Carter, Vala and Daniel held captive, Mitchell may be their only hope... (S10, ep 9) [S] +[WTV, WTV, Video] WM/Genre - Genre: Shows;Other Shows +[WTV, WTV, Time] WM/OriginalReleaseTime - Original Release Time: 0 +[WTV, WTV, Video] WM/Language - Language: +[WTV, WTV, Video] WM/MediaCredits - Media Credits: ;;; +[WTV, WTV, Video] WM/MediaStationCallSign - Media Station Call Sign: Pick +[WTV, WTV, Video] WM/MediaStationName - Media Station Name: Pick +[WTV, WTV, Video] WM/MediaNetworkAffiliation - Media Network Affiliation: Pick +[WTV, WTV, Video] WM/MediaOriginalChannel - Media Original Channel: 11 +[WTV, WTV, Video] WM/MediaOriginalChannelSubNumber - Media Original Channel Sub Number: 0 +[WTV, WTV, Time] WM/MediaOriginalBroadcastDateTime - Media Original Broadcast Date Time: 0001:01:01 00:00:00Z +[WTV, WTV, Video] WM/MediaOriginalRunTime - Media Original Run Time: 0:18:16 +[WTV, WTV, Audio] WM/MediaIsStereo - Media Is Stereo: No +[WTV, WTV, Video] WM/MediaIsRepeat - Media Is Repeat: No +[WTV, WTV, Video] WM/MediaIsLive - Media Is Live: No +[WTV, WTV, Video] WM/MediaIsTape - Media Is Tape: No +[WTV, WTV, Video] WM/MediaIsDelay - Media Is Delay: No +[WTV, WTV, Video] WM/MediaIsSubtitled - Media Is Subtitled: No +[WTV, WTV, Video] WM/MediaIsMovie - Media Is Movie: No +[WTV, WTV, Video] WM/MediaIsPremiere - Media Is Premiere: No +[WTV, WTV, Video] WM/MediaIsFinale - Media Is Finale: No +[WTV, WTV, Video] WM/MediaIsSAP - Media Is SAP: No +[WTV, WTV, Video] WM/MediaIsSport - Media Is Sport: No +[WTV, WTV, Video] WM/ParentalRating - Parental Rating: +[WTV, WTV, Video] WM/ParentalRatingReason - Parental Rating Reason: +[WTV, WTV, Video] WM/Provider - Provider: MediaCenterDefault +[WTV, WTV, Video] WM/ProviderCopyright - Provider Copyright: +[WTV, WTV, Video] WM/ProviderRating - Provider Rating: +[WTV, WTV, Video] WM/VideoClosedCaptioning - Video Closed Captioning: No +[WTV, WTV, Time] WM/WMRVEncodeTime - Encode Time: 2018:05:25 18:44:43Z +[WTV, WTV, Video] WM/WMRVSeriesUID - Series UID: !GenericSeries!Stargate SG-1 +[WTV, WTV, Video] WM/WMRVServiceID - Service ID: !Generated!428e25b3f1244190851ef9311cd5aea0 +[WTV, WTV, Video] WM/WMRVProgramID - Program ID: !Loaders!Glid!Programs!9018:20544:22208!6518 +[WTV, WTV, Video] WM/WMRVRequestID - Request ID: 0 +[WTV, WTV, Video] WM/WMRVScheduleItemID - Schedule Item ID: 0 +[WTV, WTV, Video] WM/WMRVQuality - Quality: 3 +[WTV, WTV, Video] WM/WMRVOriginalSoftPrePadding - Original Soft Pre Padding: 2794 +[WTV, WTV, Video] WM/WMRVOriginalSoftPostPadding - Original Soft Post Padding: 180 +[WTV, WTV, Video] WM/WMRVHardPrePadding - Hard Pre Padding: -2674 +[WTV, WTV, Video] WM/WMRVHardPostPadding - Hard Post Padding: 0 +[WTV, WTV, Video] WM/WMRVBrandingName - Branding Name: +[WTV, WTV, Video] WM/WMRVBrandingImageID - Branding Image ID: +[WTV, WTV, Video] WM/WMRVATSCContent - ATSC Content: No +[WTV, WTV, Video] WM/WMRVDTVContent - DTV Content: Yes +[WTV, WTV, Video] WM/WMRVHDContent - HD Content: No +[WTV, WTV, Video] Duration - Duration: 27.38 s +[WTV, WTV, Time] WM/WMRVEndTime - End Time: 2018:05:25 18:45:11Z +[WTV, WTV, Video] WM/WMRVKeepUntil - Keep Until: -1 +[WTV, WTV, Video] WM/WMRVActualSoftPrePadding - Actual Soft Pre Padding: -8 +[WTV, WTV, Video] WM/WMRVActualSoftPostPadding - Actual Soft Post Padding: -888 +[WTV, WTV, Video] WM/WMRVContentProtected - Content Protected: No +[WTV, WTV, Video] WM/WMRVContentProtectedPercent - Content Protected Percent: 0 +[WTV, WTV, Video] WM/WMRVInBandRatingSystem - In Band Rating System: 255 +[WTV, WTV, Video] WM/WMRVInBandRatingLevel - In Band Rating Level: 255 +[WTV, WTV, Video] WM/WMRVInBandRatingAttributes - In Band Rating Attributes: 0 +[WTV, WTV, Video] WM/WMRVWatched - Watched: No +[WTV, WTV, Video] WM/MediaThumbWidth - Media Thumb Width: 352 +[WTV, WTV, Video] WM/MediaThumbHeight - Media Thumb Height: 372 +[WTV, WTV, Video] WM/MediaThumbStride - Media Thumb Stride: 1056 +[WTV, WTV, Video] WM/MediaThumbRet - Media Thumb Ret: 0 +[WTV, WTV, Video] WM/MediaThumbRatingSystem - Media Thumb Rating System: 255 +[WTV, WTV, Video] WM/MediaThumbRatingLevel - Media Thumb Rating Level: 255 +[WTV, WTV, Video] WM/MediaThumbRatingAttributes - Media Thumb Rating Attributes: 0 +[WTV, WTV, Video] WM/MediaThumbAspectRatioX - Media Thumb Aspect Ratio X: 16 +[WTV, WTV, Video] WM/MediaThumbAspectRatioY - Media Thumb Aspect Ratio Y: 9 diff --git a/ExifTool/t/Writer.t b/ExifTool/t/Writer.t new file mode 100644 index 0000000..f2f66cc --- /dev/null +++ b/ExifTool/t/Writer.t @@ -0,0 +1,1114 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/Writer.t". + +BEGIN { + $| = 1; print "1..60\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +$loaded = 1; +print "ok 1\n"; + +######################### End of black magic. + +my $testname = 'Writer'; +my $testnum = 1; +my $testfile; + +# tests 2/3: Test writing new comment to JPEG file and removing it again +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile1 = "t/${testname}_${testnum}_failed.jpg"; + -e $testfile1 and unlink $testfile1; + $exifTool->SetNewValue('Comment','New comment in JPG file'); + writeInfo($exifTool, 't/images/Canon.jpg', $testfile1); + my $info = ImageInfo($testfile1); + notOK() unless check($info, $testname, $testnum); + print "ok $testnum\n"; + + ++$testnum; + my $testfile2 = "t/${testname}_${testnum}_failed.jpg"; + -e $testfile2 and unlink $testfile2; + $exifTool->SetNewValue('Comment'); + writeInfo($exifTool, $testfile1, $testfile2); + if (binaryCompare($testfile2, 't/images/Canon.jpg')) { + unlink $testfile1; + unlink $testfile2; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 4/5: Test editing a TIFF in memory then changing it back again +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 1, Unknown => 1); + my $newtiff; + $exifTool->SetNewValue(Headline => 'A different headline'); + $exifTool->SetNewValue(ImageDescription => 'Modified TIFF'); + $exifTool->SetNewValue(Keywords => 'another keyword', AddValue => 1); + $exifTool->SetNewValue('xmp:SupplementalCategories' => 'new XMP info'); + writeInfo($exifTool, 't/images/ExifTool.tif', \$newtiff); + my $info = $exifTool->ImageInfo(\$newtiff); + unless (check($exifTool, $info, $testname, $testnum)) { + $testfile = "t/${testname}_${testnum}_failed.tif"; + open(TESTFILE,">$testfile"); + binmode(TESTFILE); + print TESTFILE $newtiff; + close(TESTFILE); + notOK(); + } + print "ok $testnum\n"; + + ++$testnum; + my $newtiff2; + $exifTool->SetNewValue(); # clear all the changes + $exifTool->SetNewValue(Headline => 'headline'); + $exifTool->SetNewValue(ImageDescription => 'The picture caption'); + $exifTool->SetNewValue(Keywords => 'another keyword', DelValue => 1); + $exifTool->SetNewValue(SupplementalCategories); + writeInfo($exifTool, \$newtiff, \$newtiff2); + $testfile = "t/${testname}_${testnum}_failed.tif"; + open(TESTFILE,">$testfile"); + binmode(TESTFILE); + print TESTFILE $newtiff2; + close(TESTFILE); + if (binaryCompare($testfile,'t/images/ExifTool.tif')) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 6/7: Test rewriting a JPEG file then changing it back again +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 1, Unknown => 1); + my $testfile1 = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile1; + $exifTool->SetNewValue(DateTimeOriginal => '2005:01:01 00:00:00', Group => 'IFD0'); + $exifTool->SetNewValue(Contrast => '+2', Group => 'XMP'); + $exifTool->SetNewValue(ExposureCompensation => 999, Group => 'EXIF'); + $exifTool->SetNewValue(LightSource => 'cloud'); + $exifTool->SetNewValue('EXIF:Flash' => '0x1', Type => 'ValueConv'); + $exifTool->SetNewValue('Orientation#' => 3); + $exifTool->SetNewValue(FocalPlaneResolutionUnit => 'mm'); + $exifTool->SetNewValue(Category => 'IPTC test'); + $exifTool->SetNewValue(Description => 'New description'); + $exifTool->SetNewValue(TimeCodes => '02:53:49:07 2009-11-19T12:38:35:21-03:00'); + writeInfo($exifTool, 't/images/Canon.jpg', $testfile1, undef, 1); + my $info = $exifTool->ImageInfo($testfile1); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + + ++$testnum; + $exifTool->SetNewValue(); + $exifTool->SetNewValue(DateTimeOriginal => '2003:12:04 06:46:52'); + $exifTool->SetNewValue(Contrast => undef, Group => 'XMP'); + $exifTool->SetNewValue(ExposureCompensation => 0, Group => 'EXIF'); + $exifTool->SetNewValue('LightSource'); + $exifTool->SetNewValue('EXIF:Flash' => '0x0', Type => 'ValueConv'); + $exifTool->SetNewValue('Orientation#' => 1); + $exifTool->SetNewValue(FocalPlaneResolutionUnit => 'in'); + $exifTool->SetNewValue('Category'); + $exifTool->SetNewValue('Description'); + $exifTool->SetNewValue('TimeCodes'); + my $image; + writeInfo($exifTool, $testfile1, \$image); + $exifTool->Options(Composite => 0); + $info = $exifTool->ImageInfo(\$image, '-filesize'); + my $testfile2 = "t/${testname}_${testnum}_failed.jpg"; + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile1; + unlink $testfile2; + } else { + # save bad file + open(TESTFILE,">$testfile2"); + binmode(TESTFILE); + print TESTFILE $image; + close(TESTFILE); + notOK(); + } + print "ok $testnum\n"; +} + +# test 8: Test rewriting everything in a JPEG file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 1, Binary => 1, ListJoin => undef); + my $info = $exifTool->ImageInfo('t/images/Canon.jpg'); + my $tag; + foreach $tag (keys %$info) { + my $group = $exifTool->GetGroup($tag); + # eat return values so warnings don't get printed + my @rtns = $exifTool->SetNewValue($tag,$info->{$tag},Group=>$group); + } + undef $info; + my $image; + writeInfo($exifTool, 't/images/Canon.jpg', \$image); + # (must drop Composite tags because their order may change) + $exifTool->Options(Unknown => 1, Binary => 0, ListJoin => ', ', Composite => 0); + # (must ignore filesize because it changes as null padding is discarded) + $info = $exifTool->ImageInfo(\$image, '-filesize'); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + if (check($exifTool, $info, $testname, $testnum, 7)) { + unlink $testfile; + } else { + # save bad file + open(TESTFILE,">$testfile"); + binmode(TESTFILE); + print TESTFILE $image; + close(TESTFILE); + notOK(); + } + print "ok $testnum\n"; +} + +# test 9: Test copying over information with SetNewValuesFromFile() +# (including a transfer of the ICC_Profile record) +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/Canon.jpg'); + $exifTool->SetNewValuesFromFile('t/images/ExifTool.tif', 'ICC_Profile'); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Nikon.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 10: Another SetNewValuesFromFile() test +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(IgnoreMinorErrors => 1); + $exifTool->SetNewValuesFromFile('t/images/Pentax.jpg'); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Canon.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 11/12: Try creating something from nothing and removing it again +# (also test ListSplit and ListJoin options) +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(ListSplit => ';\\s*'); + $exifTool->Options(ListJoin => ' <<separator>> '); + $exifTool->SetNewValue(DateTimeOriginal => '2005:01:19 13:37:22', Group => 'EXIF'); + $exifTool->SetNewValue(FileVersion => 12, Group => 'IPTC'); + $exifTool->SetNewValue(Contributor => 'Guess who', Group => 'XMP'); + $exifTool->SetNewValue(GPSLatitude => q{44 deg 14' 12.25"}, Group => 'GPS'); + $exifTool->SetNewValue('Ducky:Quality' => 50); + $exifTool->SetNewValue(Keywords => 'this; that'); + my $testfile1 = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile1; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile1); + my $info = $exifTool->ImageInfo($testfile1); + my $success = check($exifTool, $info, $testname, $testnum); + notOK() unless $success; + print "ok $testnum\n"; + + ++$testnum; + $exifTool->SetNewValue('DateTimeOriginal'); + $exifTool->SetNewValue('FileVersion'); + $exifTool->SetNewValue('Contributor'); + $exifTool->SetNewValue('GPSLatitude'); + $exifTool->SetNewValue('Ducky:Quality'); + $exifTool->SetNewValue('Keywords'); + my $testfile2 = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile2; + writeInfo($exifTool, $testfile1, $testfile2); + if (binaryCompare('t/images/Writer.jpg', $testfile2)) { + unlink $testfile1 if $success; + unlink $testfile2; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 13: Copy tags from CRW file to JPG +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/CanonRaw.crw'); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 14: Delete all information in a group +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('All' => undef, Group => 'MakerNotes'); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Canon.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 15: Copy a specific set of tags +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my @copyTags = qw(exififd:all -lightSource ifd0:software); + # also test new regular expression feature (ExifTool 9.15) + push @copyTags, 'comment<${ make ; tr{ ,.}{_}; s{__}{_} } {cool, huh?}'; + $exifTool->SetNewValuesFromFile('t/images/Olympus.jpg', @copyTags); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Canon.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 16-18: Test SetNewValuesFromFile() order of operations +{ + my @argsList = ( + [ 'ifd0:xresolution>xmp:*', 'ifd1:xresolution>xmp:*' ], + [ 'ifd1:xresolution>xmp:*', 'ifd0:xresolution>xmp:*' ], + [ '*:xresolution', '-ifd0:xresolution', 'xresolution>xmp:*' ], + ); + my $args; + foreach $args (@argsList) { + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/GPS.jpg', @$args); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'xresolution'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + } +} + +# test 19: Test SaveNewValues()/RestoreNewValues() +my $testOK; +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue(ISO => 25); + $exifTool->SetNewValue(Sharpness => '+1'); + $exifTool->SetNewValue(Artist => 'Phil', Group => 'IFD0'); + $exifTool->SetNewValue(Artist => 'Harvey', Group => 'ExifIFD'); + $exifTool->SetNewValue(DateTimeOriginal => '2006:03:27 16:25:00'); + $exifTool->SetNewValue(Keywords => ['one','two']); + $exifTool->SaveNewValues(); + $exifTool->SetNewValue(Artist => 'nobody'); + $exifTool->SetNewValue(Keywords => 'three'); + $exifTool->SetNewValuesFromFile('t/images/FujiFilm.jpg'); + $exifTool->RestoreNewValues(); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + $testOK = 1; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 20/21: Test edit in place (using the file from the last test) +{ + my ($skip, $size); + ++$testnum; + $skip = ''; + if ($testOK) { + my $exifTool = Image::ExifTool->new; + my $newComment = 'This is a new test comment'; + $exifTool->SetNewValue(Comment => $newComment); + my $ok = writeInfo($exifTool, $testfile); + my $info = $exifTool->ImageInfo($testfile, 'Comment'); + if ($$info{Comment} and $$info{Comment} eq $newComment and $ok) { + $size = -s $testfile; + } else { + $testOK = 0; + notOK(); + } + } else { + $skip = ' # skip Relies on previous test'; + } + print "ok $testnum$skip\n"; + + # test in-place edit of file passed by handle + ++$testnum; + $skip = ''; + if ($testOK) { + my $exifTool = Image::ExifTool->new; + my $shortComment = 'short comment'; + $exifTool->SetNewValue(Comment => $shortComment); + open FILE, "+<$testfile"; # open test file for update + writeInfo($exifTool, \*FILE); + close FILE; + my $info = $exifTool->ImageInfo($testfile, 'Comment'); + if ($$info{Comment} and $$info{Comment} eq $shortComment) { + my $newSize = -s $testfile; + unless ($newSize < $size) { + # test to see if the file got shorter as it should have + $testOK = 0; + $skip = ' # skip truncate() not supported on this system'; + } + } else { + $testOK = 0; + notOK(); + } + } else { + $skip = ' # skip Relies on previous test'; + } + print "ok $testnum$skip\n"; +} + +# test 22: Test time shift feature +{ + ++$testnum; + my @writeInfo = ( + ['DateTimeOriginal' => '1:2', 'Shift' => 1], + ['ModifyDate' => '2:1: 3:4', 'Shift' => 1], + ['CreateDate' => '200 0', 'Shift' => -1], + ['DateCreated' => '20:', 'Shift' => -1], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/XMP.jpg', 1); + print "ok $testnum\n"; +} + +# test 23: Test renaming a file from the value of DateTimeOriginal +{ + ++$testnum; + my $skip = ''; + if (not eval { require POSIX }) { + $skip = ' # skip Requires POSIX'; + } elsif ($testOK) { + my $newfile = "t/${testname}_${testnum}_20060327_failed.jpg"; + unlink $newfile; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(DateFormat => "${testname}_${testnum}_%Y%m%d_failed.jpg"); + $exifTool->SetNewValuesFromFile($testfile, 'FileName<DateTimeOriginal'); + writeInfo($exifTool, $testfile); + if (-e $newfile and not -e $testfile) { + $testfile = $newfile; + } else { + $testOK = 0; + notOK(); + } + } else { + $skip = ' # skip Relies on test 21'; + } + print "ok $testnum$skip\n"; + + $testOK and unlink $testfile; # erase test file if all tests passed +} + +# test 24: Test redirection with expressions +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/FujiFilm.jpg', + 'Comment<ISO=$ISO Aperture=${EXIF:fnumber} Exposure=${shutterspeed}' + ); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'Comment'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 25/26: Test order of delete operations +{ + my $i; + for ($i=0; $i<2; ++$i) { + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/Nikon.jpg', 'all:all', '-makernotes:all'); + $exifTool->SetNewValue(fnumber => 26) if $i == 1; + $exifTool->SetNewValue('exififd:all'); # delete all exifIFD + $exifTool->SetNewValue(fnumber => 25) if $i == 0; + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Canon.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + } +} + +# test 27: Check that mandatory EXIF resolution tags get taken from JFIF +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('exif:all'); # delete all EXIF + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/ExifTool.jpg', $testfile); + $exifTool->SetNewValue(); + $exifTool->SetNewValue('exif:datetimeoriginal', '2000:01:02 03:04:05'); + my $ok = writeInfo($exifTool, $testfile); + $info = $exifTool->ImageInfo($testfile, 'XResolution', 'YResolution', 'DateTimeOriginal'); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 28-30: Check cross delete behaviour when deleting tags +{ + my $group; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('IFD0:ISO',100); + $exifTool->SetNewValue('ExifIFD:ISO',200); + writeInfo($exifTool, 't/images/Writer.jpg', 't/tmp.jpg'); + foreach $group ('EXIF', 'IFD0', 'ExifIFD') { + ++$testnum; + $exifTool->SetNewValue(); # reset values + $exifTool->SetNewValue("$group:ISO"); # delete ISO from specific group + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/tmp.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'FileName', 'ISO'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + } + unlink 't/tmp.jpg'; +} + +# test 31: Delete all but EXIF (excluding IFD1) and IPTC information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('*'); + $exifTool->SetNewValue('EXIF:*', undef, Replace => 2); + $exifTool->SetNewValue('ifd1:all'); + $exifTool->SetNewValue('IPTC:*', undef, Replace => 2); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/ExifTool.jpg', $testfile, undef, 1); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 32-33: Read/Write ICC Profile tags +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(IgnoreMinorErrors => 1); + my $hdr = "\0\0\0\x18ADBE\x02\x10\0\0mntrRGB XYZ "; + $exifTool->SetNewValue(AsShotICCProfile => $hdr . '<dummy>', Protected => 1); + $exifTool->SetNewValue(CurrentICCProfile => $hdr . '<dummy 2>', Protected => 1); + $testfile = "t/${testname}_${testnum}_failed.tif"; + unlink $testfile; + my @tags = qw(ICC_Profile AsShotICCProfile CurrentICCProfile); + writeInfo($exifTool, 't/images/ExifTool.tif', $testfile); + my $info = $exifTool->ImageInfo($testfile, @tags); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + + ++$testnum; + my $srcfile = $testfile; + $exifTool->SetNewValue(); + $exifTool->SetNewValue(ICC_Profile => $hdr . '<another dummy>', Protected => 1); + $testfile = "t/${testname}_${testnum}_failed.tif"; + unlink $testfile; + writeInfo($exifTool, $srcfile, $testfile); + $info = $exifTool->ImageInfo($testfile, @tags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $srcfile; + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 34: copy list tag to list and non-list tags with different options +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(ListJoin => undef); + $exifTool->SetNewValuesFromFile('t/images/IPTC.jpg', + { Replace => 1 }, + 'xmp:subject<filename', + 'xmp:subject<iptc:keywords', + 'comment<iptc:keywords', + { Replace => 0 }, + 'xmp:HierarchicalSubject<filename', + 'xmp:HierarchicalSubject<iptc:keywords', + ); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + $info = $exifTool->ImageInfo($testfile, 'xmp:subject', 'comment', 'HierarchicalSubject'); + my $err; + if (check($exifTool, $info, $testname, $testnum)) { + # make sure it was an array reference + my $val = $$info{Subject} || ''; + my $err; + if (ref $val ne 'ARRAY') { + $err = "Subject is not an ARRAY: '$val'"; + } elsif (@$val != 3) { + $err = "Subject does not contain 3 values: '" . join(', ', @$val) . "'"; + } + if ($err) { + warn "\n $err\n"; + } else { + unlink $testfile; + } + } else { + $err = 1; + } + notOK() if $err; + print "ok $testnum\n"; +} + +# test 35: Add back all information after deleting everything +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('*'); + $exifTool->SetNewValuesFromFile('t/images/ExifTool.jpg', 'all:all', + 'icc_profile', 'canonvrd'); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/ExifTool.jpg', $testfile, undef, 1); + $exifTool->Options(Composite => 0); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 36: Test adding and deleting from the same list +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('IPTC:Keywords', 'out', DelValue => 1); + $exifTool->SetNewValue('IPTC:Keywords', 'in', AddValue => 1); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'IPTC:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 37-38: Create EXIF file from EXIF block and individual tags +{ + my $i; + for ($i=0; $i<2; ++$i) { + ++$testnum; + my $exifTool = Image::ExifTool->new; + my @tags; + if ($i == 0) { + $exifTool->SetNewValuesFromFile('t/images/Sony.jpg', 'EXIF'); + $exifTool->Options(PrintConv => 0); + @tags = qw(FileSize Compression); + } else { + $exifTool->SetNewValuesFromFile('t/images/Sony.jpg'); + $exifTool->Options(PrintConv => 1, Unknown => 1); + } + $testfile = "t/${testname}_${testnum}_failed.exif"; + unlink $testfile; + writeInfo($exifTool, undef, $testfile); + my $info = $exifTool->ImageInfo($testfile, @tags); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + } +} + +# tests 39-40: Test writing only if the tag didn't already exist +{ + ++$testnum; + my @writeInfo = ( + [DateTimeOriginal => '', DelValue => 1], + ['DateTimeOriginal#' => '1999:99:99 99:99:99'], + [XResolution => '', DelValue => 1], + [XResolution => '123'], + [ResolutionUnit => '', DelValue => 1], + [ResolutionUnit => 'cm'], + ); + my @check = qw(FileName DateTimeOriginal XResolution ResolutionUnit); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, + 't/images/Writer.jpg', \@check); + print "ok $testnum\n"; + + ++$testnum; + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, + 't/images/Canon.jpg', \@check, 1); + print "ok $testnum\n"; +} + +# test 41: Test writing Kodak APP3 and Canon CIFF Meta information +{ + ++$testnum; + my @writeInfo = ( + ['Meta:SerialNumber' => '12345'], + ['CIFF:OwnerName' => 'CIFF Write Test'], + ); + my @check = qw(SerialNumber OwnerName); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, + 't/images/ExifTool.jpg', \@check); + print "ok $testnum\n"; +} + +# test 42: Test SetNewValuesFromFile with wildcards +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/ExifTool.jpg', 'ifd0:*<jfif:?resolution'); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + $exifTool->Options(Composite => 0); + my $info = $exifTool->ImageInfo($testfile, '-file:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 43: Test increment feature EXIF +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + my @writeInfo = ( + [ExposureTime => '1.5', Shift => 1], + [SerialNumber => '-9', Shift => -1], # (two negatives make a positive) + [MeteringMode => '1', Shift => 0, AddValue => 1], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/Canon.jpg', 1); + print "ok $testnum\n"; +} + +# test 44: Test increment feature with XMP +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my @writeInfo = ( + ['XMP:ApertureValue' => '-0.1', Shift => 1], # increment + ['XMP:FNumber' => '28/10', DelValue => 1], # conditional delete + ['XMP:DateTimeOriginal' => '3', Shift => 0, AddValue => 1], # shift + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/XMP.xmp', 1); + print "ok $testnum\n"; +} + +# test 45: Test writing different EXIF string encoding +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(CharsetEXIF => 'Latin'); + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValue(Artist => "P\xc3\xaaro", Group => 'EXIF'); + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + $exifTool->Options(CharsetEXIF => undef); + my $info = $exifTool->ImageInfo($testfile, 'artist'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 46: Test writing with wildcards +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValue('A*' => '7'); + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 47: Test various WriteMode settings +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; # Should the tag be written? + $exifTool->Options(WriteMode => 'w'); # --- write existing tags only: + $exifTool->SetNewValue(ISO => 150); # yes (already exists) + $exifTool->SetNewValue(ImageDescription => 'N');# no (doesn't already exist) + $exifTool->Options(WriteMode => 'c'); # --- create new tags only: + $exifTool->SetNewValue(ApertureValue => 8.0); # no (already exists) + $exifTool->SetNewValue(UserComment => 'No'); # no (exists, albeit empty) + $exifTool->SetNewValue(Artist => 'Phil'); # yes (doesn't already exist) + $exifTool->SetNewValue('XMP:Subject' => 'No'); # no (shouldn't create new group) + $exifTool->Options(WriteMode => 'cg'); # --- also create new groups: + $exifTool->SetNewValue('IPTC:Keywords' => 'Y'); # yes (should create new group) + $exifTool->Options(Composite => 0, FastScan => 2); + writeInfo($exifTool, 't/images/Canon.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, '-time:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 48-50: More WriteMode 'cg' tests, and test AddUserDefinedTags() +{ + ++$testnum; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(WriteMode => 'cg'); + $exifTool->SetNewValue('XMP-dc:Title' => 'A'); + $exifTool->SetNewValue('XMP:Subject' => 'A'); + $exifTool->SetNewValue('XMP:LocationCreated' => '{city=A}'); + $exifTool->SetNewValue('XMP:Flash' => '{fired=true}'); + $exifTool->SetNewValue('IPTC:Keywords' => 'A'); + $exifTool->SetNewValue('IPTC:City' => 'A'); + $exifTool->SetNewValue('EXIF:Artist' => 'A'); + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, '-time:all', '-filename'); + unless (check($exifTool, $info, $testname, $testnum)) { + notOK(); + } + print "ok $testnum\n"; + + ++$testnum; + my $testfile2 = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile2; + $exifTool->SetNewValue(); + $exifTool->SetNewValue('XMP-dc:Title' => 'B'); + $exifTool->SetNewValue('XMP:Subject' => 'B'); + $exifTool->SetNewValue('XMP:LocationCreated' => '{city=B}'); + $exifTool->SetNewValue('XMP:Flash' => '{fired=false}'); + $exifTool->SetNewValue('IPTC:Keywords' => 'B'); + $exifTool->SetNewValue('IPTC:City' => 'B'); + $exifTool->SetNewValue('EXIF:Artist' => 'B'); + if (writeInfo($exifTool, $testfile, $testfile2, 1)) { + unlink $testfile; + unlink $testfile2; + } else { + $info = $exifTool->ImageInfo($testfile2, '-time:all', '-filename'); + check($exifTool, $info, $testname, $testnum, $testnum-1); + notOK(); + } + print "ok $testnum\n"; + + ++$testnum; + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + Image::ExifTool::AddUserDefinedTags('Image::ExifTool::XMP::dc', test => {} ); + $exifTool->SetNewValue(); + $exifTool->SetNewValue('XMP-dc:Title' => 'A'); + $exifTool->SetNewValue('XMP-dc:Test' => 'B'); + writeInfo($exifTool, undef, $testfile); + $info = $exifTool->ImageInfo($testfile, 'xmp:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 51: Delete a unknown JPEG APP segment +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('APP6:*' => undef); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/ExifTool.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 52: Delete groups by family 2 group name +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue('Image:*'); + $exifTool->SetNewValue('Camera:*'); + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + writeInfo($exifTool, 't/images/XMP.xmp', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 53: Exclude groups when copying +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/Canon.jpg', '-Exif:All', '-Canon:All'); + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 54: Specify multiple groups when copying, excluding a single tag +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/Canon.jpg', 'Exif:Time:All', '-createdate'); + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'exif:*', '-image:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 55-56: Create and edit EXV file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue(Artist => 'me'); + $exifTool->SetNewValue(Keywords => ['one','two']); + $testfile = "t/${testname}_${testnum}_failed.exv"; + unlink $testfile; + writeInfo($exifTool, undef, $testfile); + my $info = $exifTool->ImageInfo($testfile, 'exif:*', 'iptc:*', 'xmp:*'); + unless (check($exifTool, $info, $testname, $testnum)) { + notOK(); + } + print "ok $testnum\n"; + + ++$testnum; + $exifTool->SetNewValue(); + $exifTool->SetNewValue(Artist); + $exifTool->SetNewValue(Title => 'Test'); + my $testfile2 = "t/${testname}_${testnum}_failed.exv"; + unlink $testfile2; + writeInfo($exifTool, $testfile, $testfile2); + $info = $exifTool->ImageInfo($testfile2, 'exif:*', 'iptc:*', 'xmp:*'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + unlink $testfile2; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 57: Test setting Composite PreviewImage using various groups +{ + ++$testnum; + my $ok = 1; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(IgnoreMinorErrors => 1); + my %try = ( + 'previewimage' => 1, + 'composite:previewimage' => 0, # (shouldn't be in the Composite group) + 'composite:thumbnailimage' => 0, + 'composite:jpgfromraw' => 0, + 'composite:otherimage' => 0, + 'ifd0:previewimage' => 1, + '0ifd0:previewimage' => 0, + '1ifd0:previewimage' => 1, + '2ifd0:previewimage' => 0, + 'exif:previewimage' => 1, + '0exif:previewimage' => 1, + '1exif:previewimage' => 0, + '2exif:previewimage' => 0, + 'preview:previewimage' => 1, + '0preview:previewimage' => 0, + '1preview:previewimage' => 0, + '2preview:previewimage' => 1, + 'makernotes:previewimage' => 1, + '0makernotes:previewimage' => 1, + '1makernotes:previewimage' => 0, + '2makernotes:previewimage' => 0, + 'xxx:previewimage' => 0, + '0xxx:previewimage' => 0, + '1xxx:previewimage' => 0, + '2xxx:previewimage' => 0, + 'ifd0:exif:preview:previewimage' => 1, + '0ifd0:exif:preview:previewimage' => 0, + '1ifd0:exif:preview:previewimage' => 1, + '2ifd0:exif:preview:previewimage' => 0, + 'ifd0:0exif:preview:previewimage' => 1, + 'ifd0:1exif:preview:previewimage' => 0, + 'ifd0:2exif:preview:previewimage' => 0, + 'ifd0:exif:0preview:previewimage' => 0, + 'ifd0:exif:1preview:previewimage' => 0, + 'ifd0:exif:2preview:previewimage' => 1, + '1ifd0:0exif:2preview:previewimage' => 1, + 'exif:makernotes:previewimage' => 1, # (but wouldn't write anything) + ); + my $tag; + foreach $tag (sort keys %try) { + my ($num, $err) = $exifTool->SetNewValue($tag => 'test'); + next unless $num xor $try{$tag}; + warn "\nError setting $tag\n"; + $ok = 0; + } + notOK() unless $ok; + print "ok $testnum\n"; +} + +# test 58: Set ICC_Profile from an external file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + open IN, 't/images/ICC_Profile.icc' or die; + binmode IN; + my $buff; + read IN, $buff, 1000; + close IN; + my @writeInfo = ( + [ICC_Profile => \$buff, Protected => 1], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 59: Test writing empty list elements +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(ListSplit => ','); + $exifTool->Options(ListJoin => ';'); + $exifTool->SetNewValue('XMP-dc:Subject' => ',a,,'); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'xmp:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 60: Test SetAlternateFile() feature with SetNewValuesFromFile() +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetAlternateFile(File1 => 't/images/Nikon.jpg'); + $exifTool->SetAlternateFile(File2 => 't/images/FujiFilm.jpg'); + $exifTool->SetNewValuesFromFile('t/images/Canon.jpg', + 'subject<file1:make', '+subject<make', '+subject<$file2:make', + 'contributor<file1:make', 'contributor<make', 'contributor<$file2:make', + '+xmp-dc:type<file1:me*', + ); + $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + writeInfo($exifTool, 't/images/Writer.jpg', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'subject', 'contributor', 'type'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/Writer_10.out b/ExifTool/t/Writer_10.out new file mode 100644 index 0000000..95f02ce --- /dev/null +++ b/ExifTool/t/Writer_10.out @@ -0,0 +1,246 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_10_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 7.1 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: PENTAX Corporation +[EXIF, IFD0, Camera] 272 - Camera Model Name: PENTAX K10D +[EXIF, IFD0, Image] 274 - Orientation: Rotate 270 CW +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: K10D Ver 1.30 +[EXIF, IFD0, Time] 306 - Modify Date: 2008:03:02 12:01:23 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/100 +[EXIF, ExifIFD, Image] 33437 - F Number: 13.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Aperture-priority AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2008:03:02 12:01:23 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2008:03:02 12:01:23 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 9 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 14.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: +0.7 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Off, Did not fire +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 10.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3008 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2000 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: THM - DCF thumbnail file +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, InteropIFD, Image] 4097 - Related Image Width: 3072 +[EXIF, InteropIFD, Image] 4098 - Related Image Height: 2048 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3443.946188 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3442.016807 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41989 - Focal Length In 35mm Format: 15 mm +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[EXIF, ExifIFD, Camera] 41996 - Subject Distance Range: Distant +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 2814 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Pentax, Camera] 0 - Pentax Version: 3.0.0.0 +[MakerNotes, Pentax, Camera] 1 - Pentax Model Type: 0 +[MakerNotes, Pentax, Image] 2 - Preview Image Size: 640x480 +[MakerNotes, Pentax, Image] 3 - Preview Image Length: 0 +[MakerNotes, Pentax, Image] 4 - Preview Image Start: 2386 +[MakerNotes, Pentax, Camera] 5 - Pentax Model ID: K10D +[MakerNotes, Pentax, Time] 6 - Date: 2008:03:02 +[MakerNotes, Pentax, Time] 7 - Time: 12:01:23 +[MakerNotes, Pentax, Camera] 8 - Quality: Better +[MakerNotes, Pentax, Camera] 12 - Flash Mode: Off, Did not fire; Internal +[MakerNotes, Pentax, Camera] 13 - Focus Mode: AF-S (Focus-priority) +[MakerNotes, Pentax, Camera] 14 - AF Point Selected: Center +[MakerNotes, Pentax, Camera] 18 - Exposure Time: 1/100 +[MakerNotes, Pentax, Camera] 19 - F Number: 13.0 +[MakerNotes, Pentax, Camera] 20 - ISO: 100 +[MakerNotes, Pentax, Camera] 22 - Exposure Compensation: +0.7 +[MakerNotes, Pentax, Camera] 23 - Metering Mode: Multi-segment +[MakerNotes, Pentax, Camera] 24 - Auto Bracketing: 0 EV, No Extended Bracket +[MakerNotes, Pentax, Camera] 25 - White Balance: Auto +[MakerNotes, Pentax, Camera] 26 - White Balance Mode: Auto (Daylight) +[MakerNotes, Pentax, Camera] 29 - Focal Length: 10.0 mm +[MakerNotes, Pentax, Camera] 31 - Saturation: 0 (normal) +[MakerNotes, Pentax, Camera] 32 - Contrast: 0 (normal) +[MakerNotes, Pentax, Camera] 33 - Sharpness: 0 (normal) +[MakerNotes, Pentax, Time] 34 - World Time Location: Hometown +[MakerNotes, Pentax, Time] 35 - Hometown City: Toronto +[MakerNotes, Pentax, Time] 36 - Destination City: New York +[MakerNotes, Pentax, Time] 37 - Hometown DST: No +[MakerNotes, Pentax, Time] 38 - Destination DST: No +[MakerNotes, Pentax, Camera] 39 - DSP Firmware Version: 1.30.00.18 +[MakerNotes, Pentax, Camera] 40 - CPU Firmware Version: 1.30.00.18 +[MakerNotes, Pentax, Camera] 45 - Effective LV: 14.8 +[MakerNotes, Pentax, Camera] 50 - Image Editing: None +[MakerNotes, Pentax, Camera] 51 - Picture Mode: Aperture Priority; 1/3 EV steps +[MakerNotes, Pentax, Camera] 52 - Drive Mode: Single-frame; No Timer; Shutter Button; Single Exposure +[MakerNotes, Pentax, Camera] 55 - Color Space: sRGB +[MakerNotes, Pentax, Camera] 61 - Data Scaling: 8192 +[MakerNotes, Pentax, Camera] 62 - Preview Image Borders: 28 28 0 0 +[MakerNotes, Pentax, Camera] 0 - Lens Type: Sigma or Tamron Lens (3 44) +[MakerNotes, Pentax, Camera] 64 - Sensitivity Adjust: 0 +[MakerNotes, Pentax, Camera] 65 - Image Edit Count: 0 +[MakerNotes, Pentax, Camera] 71 - Camera Temperature: 12 C +[MakerNotes, Pentax, Camera] 72 - AE Lock: Off +[MakerNotes, Pentax, Camera] 73 - Noise Reduction: Off +[MakerNotes, Pentax, Camera] 77 - Flash Exposure Comp: 0 +[MakerNotes, Pentax, Camera] 79 - Image Tone: Natural +[MakerNotes, Pentax, Camera] 0 - SR Result: Stabilized +[MakerNotes, Pentax, Camera] 1 - Shake Reduction: On +[MakerNotes, Pentax, Camera] 2 - SR Half Press Time: 1.53 s +[MakerNotes, Pentax, Camera] 3 - SR Focal Length: 10 mm +[MakerNotes, Pentax, Camera] 93 - Shutter Count: 1648 +[MakerNotes, Pentax, Camera] 98 - Raw Development Process: 1 (K10D,K200D,K2000,K-m) +[MakerNotes, Pentax, Camera] 512 - Black Point: 0 0 0 0 +[MakerNotes, Pentax, Camera] 513 - White Point: 13568 8192 8192 9088 +[MakerNotes, Pentax, Camera] 0 - Picture Mode 2: Aperture Priority +[MakerNotes, Pentax, Camera] 1.1 - Program Line: Normal +[MakerNotes, Pentax, Camera] 1.2 - EV Steps: 1/3 EV Steps +[MakerNotes, Pentax, Camera] 1.3 - E-Dial In Program: Tv or Av +[MakerNotes, Pentax, Camera] 1.4 - Aperture Ring Use: Permitted +[MakerNotes, Pentax, Camera] 2 - Flash Options: Wireless (Master) +[MakerNotes, Pentax, Camera] 2.1 - Metering Mode 2: Multi-segment +[MakerNotes, Pentax, Camera] 3 - AF Point Mode: Select +[MakerNotes, Pentax, Camera] 3.1 - Focus Mode 2: AF-S +[MakerNotes, Pentax, Camera] 4 - AF Point Selected 2: Center +[MakerNotes, Pentax, Camera] 6 - ISO Floor: 100 +[MakerNotes, Pentax, Camera] 7 - Drive Mode 2: Single-frame +[MakerNotes, Pentax, Camera] 8 - Exposure Bracket Step Size: 0.3 +[MakerNotes, Pentax, Camera] 9 - Bracket Shot Number: n/a +[MakerNotes, Pentax, Camera] 10 - White Balance Set: Auto +[MakerNotes, Pentax, Camera] 10.1 - Multiple Exposure Set: Off +[MakerNotes, Pentax, Camera] 13 - Raw And Jpg Recording: RAW+JPEG (PEF, Better) +[MakerNotes, Pentax, Camera] 14.1 - Jpg Recorded Pixels: 6 MP +[MakerNotes, Pentax, Camera] 16 - Flash Options 2: Wireless (Master) +[MakerNotes, Pentax, Camera] 16.1 - Metering Mode 3: Multi-segment +[MakerNotes, Pentax, Camera] 17.1 - SR Active: Yes +[MakerNotes, Pentax, Camera] 17.2 - Rotation: Rotate 270 CW +[MakerNotes, Pentax, Camera] 17.3 - ISO Setting: Manual +[MakerNotes, Pentax, Camera] 17.4 - Sensitivity Steps: 1 EV Steps +[MakerNotes, Pentax, Camera] 18 - Tv Exposure Time Setting: 1/203 +[MakerNotes, Pentax, Camera] 19 - Av Aperture Setting: 12.7 +[MakerNotes, Pentax, Camera] 20 - Sv ISO Setting: 100 +[MakerNotes, Pentax, Camera] 21 - Base Exposure Compensation: +0.7 +[MakerNotes, Pentax, Camera] 0 - AE Exposure Time: 1/101 +[MakerNotes, Pentax, Camera] 1 - AE Aperture: 12.9 +[MakerNotes, Pentax, Camera] 2 - AE ISO: 100 +[MakerNotes, Pentax, Camera] 3 - AE Xv: -0.625 +[MakerNotes, Pentax, Camera] 4 - AEB Xv: 0 +[MakerNotes, Pentax, Camera] 5 - AE Min Exposure Time: 1/3862 +[MakerNotes, Pentax, Camera] 6 - AE Program Mode: Av, B or X +[MakerNotes, Pentax, Camera] 8 - AE Aperture Steps: 28 +[MakerNotes, Pentax, Camera] 9 - AE Max Aperture: 4.0 +[MakerNotes, Pentax, Camera] 10 - AE Max Aperture 2: 4.0 +[MakerNotes, Pentax, Camera] 11 - AE Min Aperture: 23 +[MakerNotes, Pentax, Camera] 12 - AE Metering Mode: Multi-segment +[MakerNotes, Pentax, Camera] 14 - Flash Exposure Comp. Setting: 0 +[MakerNotes, Pentax, Camera] 0 - Lens Type: Sigma or Tamron Lens (3 44) +[MakerNotes, Pentax, Camera] 0.1 - Auto Aperture: On +[MakerNotes, Pentax, Camera] 0.2 - Min Aperture: 22 +[MakerNotes, Pentax, Camera] 0.3 - Lens F Stops: 8.5 +[MakerNotes, Pentax, Camera] 3 - Min Focus Distance: 0.49-0.50 m +[MakerNotes, Pentax, Camera] 3.1 - Focus Range Index: 7 (very far) +[MakerNotes, Pentax, Camera] 9 - Lens Focal Length: 10.0 mm +[MakerNotes, Pentax, Camera] 10 - Nominal Max Aperture: 4.0 +[MakerNotes, Pentax, Camera] 10.1 - Nominal Min Aperture: 23 +[MakerNotes, Pentax, Camera] 14.1 - Max Aperture: 3.9 +[MakerNotes, Pentax, Camera] 0 - Flash Status: Off +[MakerNotes, Pentax, Camera] 1 - Internal Flash Mode: Did not fire, Wireless (Master) +[MakerNotes, Pentax, Camera] 2 - External Flash Mode: Off +[MakerNotes, Pentax, Camera] 3 - Internal Flash Strength: 18 +[MakerNotes, Pentax, Camera] 4 - TTL DA A Up: 0 +[MakerNotes, Pentax, Camera] 5 - TTL DA A Down: 0 +[MakerNotes, Pentax, Camera] 6 - TTL DA B Up: 0 +[MakerNotes, Pentax, Camera] 7 - TTL DA B Down: 0 +[MakerNotes, Pentax, Camera] 24.1 - External Flash Guide Number: n/a +[MakerNotes, Pentax, Camera] 25 - External Flash Exposure Comp: n/a +[MakerNotes, Pentax, Camera] 26 - External Flash Bounce: n/a +[MakerNotes, Pentax, Camera] 521 - AE Metering Segments: 13.5 13.6 13.5 13.2 13.6 15.0 13.8 12.9 13.8 16.4 14.4 13.9 14.4 15.8 14.0 16.0 +[MakerNotes, Pentax, Camera] 522 - Flash Metering Segments: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Pentax, Camera] 523 - Slave Flash Metering Segments: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Pentax, Camera] 525 - WB RGGB Levels Daylight: 13600 8192 8192 8765 +[MakerNotes, Pentax, Camera] 526 - WB RGGB Levels Shade: 16128 8192 8192 6635 +[MakerNotes, Pentax, Camera] 527 - WB RGGB Levels Cloudy: 14560 8192 8192 7782 +[MakerNotes, Pentax, Camera] 528 - WB RGGB Levels Tungsten: 8192 8192 8192 20971 +[MakerNotes, Pentax, Camera] 529 - WB RGGB Levels Fluorescent D: 17376 8192 8192 8847 +[MakerNotes, Pentax, Camera] 530 - WB RGGB Levels Fluorescent N: 14528 8192 8192 10076 +[MakerNotes, Pentax, Camera] 531 - WB RGGB Levels Fluorescent W: 13088 8192 8192 12206 +[MakerNotes, Pentax, Camera] 532 - WB RGGB Levels Flash: 13632 8192 8192 8601 +[MakerNotes, Pentax, Camera] 0 - Pentax Model ID: K10D +[MakerNotes, Pentax, Time] 1 - Manufacture Date: 2007:09:13 +[MakerNotes, Pentax, Camera] 2 - Production Code: 2.1 +[MakerNotes, Pentax, Camera] 4 - Internal Serial Number: 132352 +[MakerNotes, Pentax, Camera] 0.1 - Power Source: Body Battery +[MakerNotes, Pentax, Camera] 1.1 - Body Battery State: Full +[MakerNotes, Pentax, Camera] 1.2 - Grip Battery State: Empty or Missing +[MakerNotes, Pentax, Camera] 2 - Body Battery A/D No Load: 173 (7.6V, 51%) +[MakerNotes, Pentax, Camera] 3 - Body Battery A/D Load: 168 (7.4V, 47%) +[MakerNotes, Pentax, Camera] 4 - Grip Battery A/D No Load: 5 +[MakerNotes, Pentax, Camera] 5 - Grip Battery A/D Load: 1 +[MakerNotes, Pentax, Camera] 4 - AF Predictor: 4 +[MakerNotes, Pentax, Camera] 6 - AF Defocus: 2 +[MakerNotes, Pentax, Camera] 7 - AF Integration Time: 0 ms +[MakerNotes, Pentax, Camera] 11 - AF Points In Focus: Center (horizontal) +[MakerNotes, Pentax, Image] 16 - WB Shift AB: 0 +[MakerNotes, Pentax, Image] 17 - WB Shift GM: 0 +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0300 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-dc, Time] date - Date: 2008:03:02 +[XMP, XMP-exif, Image] ComponentsConfiguration - Components Configuration: Y, Cb, Cr, - +[XMP, XMP-exif, Camera] FlashFired - Flash Fired: False +[XMP, XMP-exif, Camera] FlashFunction - Flash Function: False +[XMP, XMP-exif, Camera] FlashMode - Flash Mode: Off +[XMP, XMP-exif, Camera] FlashRedEyeMode - Flash Red Eye Mode: False +[XMP, XMP-exif, Camera] FlashReturn - Flash Return: No return detection +[XMP, XMP-exifEX, Image] InteroperabilityIndex - Interoperability Index: R98 - DCF basic file (sRGB) +[XMP, XMP-tiff, Image] BitsPerSample - Bits Per Sample: 8 +[XMP, XMP-tiff, Image] Compression - Compression: JPEG (old-style) +[XMP, XMP-tiff, Image] ImageLength - Image Height: 8 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 8 +[XMP, XMP-tiff, Image] YCbCrPositioning - Y Cb Cr Positioning: Co-sited +[XMP, XMP-tiff, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[Composite, Composite, Image] Exif-Aperture - Aperture: 13.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/100 +[Composite, Composite, Camera] XMP-Flash - Flash: Off, Did not fire +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Sigma AF 10-20mm F4-5.6 EX DC +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.0 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.5 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.020 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 100.4 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 10.0 mm (35 mm equivalent: 15.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 0.38 m diff --git a/ExifTool/t/Writer_11.out b/ExifTool/t/Writer_11.out new file mode 100644 index 0000000..f376c97 --- /dev/null +++ b/ExifTool/t/Writer_11.out @@ -0,0 +1,39 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_11_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.5 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: a22376bee9f94185aa8b4c238763315f +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0232 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:01:19 13:37:22 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[EXIF, GPS, Location] 0 - GPS Version ID: 2.3.0.0 +[EXIF, GPS, Location] 2 - GPS Latitude: 44 deg 14' 12.25" +[IPTC, IPTC, Image] 22 - File Version: 12 +[IPTC, IPTC, Other] 0 - Envelope Record Version: 4 +[IPTC, IPTC, Other] 25 - Keywords: this <<separator>> that +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-dc, Author] contributor - Contributor: Guess who +[Ducky, Ducky, Image] 1 - Quality: 50% +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Writer_13.out b/ExifTool/t/Writer_13.out new file mode 100644 index 0000000..3bb394a --- /dev/null +++ b/ExifTool/t/Writer_13.out @@ -0,0 +1,166 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_13_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 5.3 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/64 +[EXIF, ExifIFD, Image] 33437 - F Number: 3.6 +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0232 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:11:10 17:39:26 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 24.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41992 - Contrast: High +[EXIF, ExifIFD, Camera] 41993 - Saturation: High +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Hard +[EXIF, ExifIFD, Image] 42032 - Owner Name: Phil Harvey +[EXIF, ExifIFD, Image] 42033 - Serial Number: 0560018150 +[MakerNotes, Canon, Camera] 1 - Macro Mode: Unknown (0) +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: On +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Continuous +[MakerNotes, Canon, Camera] 7 - Focus Mode: AI Focus AF +[MakerNotes, Canon, Camera] 9 - Record Mode: CRW+THM +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: Unknown (-1) +[MakerNotes, Canon, Camera] 13 - Contrast: +1 +[MakerNotes, Canon, Camera] 14 - Saturation: +1 +[MakerNotes, Canon, Camera] 15 - Sharpness: +1 +[MakerNotes, Canon, Camera] 16 - Camera ISO: n/a +[MakerNotes, Canon, Camera] 17 - Metering Mode: Evaluative +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 19 - AF Point: Manual AF point selection +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Program AE +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 3.6 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 22 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: E-TTL, Built-in +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 3072 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 3072 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Image] 1 - Focal Length: 24 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.22 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.49 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 100 +[MakerNotes, Canon, Image] 3 - Measured EV: 0.00 +[MakerNotes, Canon, Image] 4 - Target Aperture: 3.6 +[MakerNotes, Canon, Image] 5 - Target Exposure Time: 1/60 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: +2/3 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 19 - Focus Distance Upper: 1.42 m +[MakerNotes, Canon, Image] 20 - Focus Distance Lower: 1.19 m +[MakerNotes, Canon, Image] 21 - F Number: 3.6 +[MakerNotes, Canon, Image] 22 - Exposure Time: 1/64 +[MakerNotes, Canon, Image] 23 - Measured EV 2: -0.125 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 0 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: CRW:EOS DIGITAL REBEL CMOS RAW +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware Version 1.1.1 +[MakerNotes, Canon, Image] 8 - File Number: 116-1602 +[MakerNotes, Canon, Camera] 9 - Owner Name: Phil Harvey +[MakerNotes, Canon, Camera] 12 - Serial Number: 0560018150 +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS Digital Rebel / 300D / Kiss Digital +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3072 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2048 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3072 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2048 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 151 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 151 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 1014 608 0 0 0 -608 -1014 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: 0 0 -506 0 506 0 0 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: 3 +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 1 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Camera] 1 - WB RGGB Levels Auto: 1740 832 831 931 +[MakerNotes, Canon, Camera] 5 - WB RGGB Levels Daylight: 1722 832 831 989 +[MakerNotes, Canon, Camera] 9 - WB RGGB Levels Shade: 2035 832 831 839 +[MakerNotes, Canon, Camera] 13 - WB RGGB Levels Cloudy: 1878 832 831 903 +[MakerNotes, Canon, Camera] 17 - WB RGGB Levels Tungsten: 1228 913 912 1668 +[MakerNotes, Canon, Camera] 21 - WB RGGB Levels Fluorescent: 1506 842 841 1381 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels Flash: 1964 832 831 877 +[MakerNotes, Canon, Camera] 29 - WB RGGB Levels Custom: 1722 832 831 989 +[MakerNotes, Canon, Camera] 33 - WB RGGB Levels Kelvin: 1722 832 831 988 +[MakerNotes, Canon, Camera] 37 - WB RGGB Black Levels: 125 124 125 124 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 554 1022 1026 754 +[MakerNotes, Canon, Camera] 174 - Color Temperature: 5200 +[MakerNotes, Canon, Camera] 180 - Color Space: sRGB +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-getty, Image] OriginalFilename - Original File Name: CRW_1602.CRW +[XMP, XMP-aux, Camera] Lens - Lens: 18.0 - 55.0 mm +[XMP, XMP-dc, Image] description - Description: CRW with embedded XMP +[XMP, XMP-pmi, Image] sequenceNumber - Sequence Number: 0 +[XMP, XMP-tiff, Image] ImageLength - Image Height: 2048 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 3072 +[XMP, XMP-crs, Image] Temperature - Color Temperature: 5200 +[Composite, Composite, Camera] Canon-ConditionalFEC - Flash Exposure Compensation: +2/3 +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Continuous Shooting +[Composite, Composite, Camera] Canon-FlashType - Flash Type: Built-In Flash +[Composite, Composite, Camera] Canon-ISO - ISO: 100 +[Composite, Composite, Camera] Canon-Lens - Lens: 18.0 - 55.0 mm +[Composite, Composite, Camera] Canon-RedEyeReduction - Red Eye Reduction: Off +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Program AE +[Composite, Composite, Camera] Canon-ShutterCurtainHack - Shutter Curtain Sync: 1st-curtain sync +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 1740 832 831 931 +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.6 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.119663 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown 18-55mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.7 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.092604 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 18.0 - 55.0 mm (35 mm equivalent: 27.9 - 85.3 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: 0.42 m (1.13 - 1.54 m) +[Composite, Composite, Image] Exif-FOV - Field Of View: 51.6 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 24.0 mm (35 mm equivalent: 37.2 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 8.25 m diff --git a/ExifTool/t/Writer_14.out b/ExifTool/t/Writer_14.out new file mode 100644 index 0000000..b064808 --- /dev/null +++ b/ExifTool/t/Writer_14.out @@ -0,0 +1,69 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Writer_14_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 1253 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:23-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:23-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:23-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:04 06:46:52 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 4 +[EXIF, ExifIFD, Image] 33437 - F Number: 14.0 +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 9 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 14.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 34.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 160 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 120 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: THM - DCF thumbnail file +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, InteropIFD, Image] 4097 - Related Image Width: 3072 +[EXIF, InteropIFD, Image] 4098 - Related Image Height: 2048 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3443.946188 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3442.016807 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[Composite, Composite, Image] Exif-Aperture - Aperture: 14.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 4 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-LightValue - Light Value: 5.6 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 36.9 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 34.0 mm (35 mm equivalent: 54.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 4.37 m diff --git a/ExifTool/t/Writer_15.out b/ExifTool/t/Writer_15.out new file mode 100644 index 0000000..00c9dcf --- /dev/null +++ b/ExifTool/t/Writer_15.out @@ -0,0 +1,175 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_15_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 2.5 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] Comment - Comment: OLYMPUS_OPTICAL_CO_LTD {cool, huh?} +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: v951p-80 +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:04 06:46:52 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/30 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 1999:12:06 16:38:40 +[EXIF, ExifIFD, Time] 36868 - Create Date: 1999:12:06 16:38:40 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 9 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 14.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 7.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 640 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 480 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: THM - DCF thumbnail file +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, InteropIFD, Image] 4097 - Related Image Width: 3072 +[EXIF, InteropIFD, Image] 4098 - Related Image Height: 2048 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3443.946188 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3442.016807 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[MakerNotes, Canon, Camera] 1 - Macro Mode: Unknown (0) +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Continuous +[MakerNotes, Canon, Camera] 7 - Focus Mode: Manual Focus (3) +[MakerNotes, Canon, Camera] 9 - Record Mode: CRW+THM +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: Unknown (-1) +[MakerNotes, Canon, Camera] 13 - Contrast: +1 +[MakerNotes, Canon, Camera] 14 - Saturation: +1 +[MakerNotes, Canon, Camera] 15 - Sharpness: +1 +[MakerNotes, Canon, Camera] 16 - Camera ISO: n/a +[MakerNotes, Canon, Camera] 17 - Metering Mode: Center-weighted average +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Manual +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 4 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 27 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 3072 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 3072 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Image] 1 - Focal Length: 34 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.22 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.49 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 100 +[MakerNotes, Canon, Image] 3 - Measured EV: -1.25 +[MakerNotes, Canon, Image] 4 - Target Aperture: 14 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 19 - Focus Distance Upper: inf +[MakerNotes, Canon, Image] 20 - Focus Distance Lower: 5.46 m +[MakerNotes, Canon, Image] 21 - F Number: 14 +[MakerNotes, Canon, Image] 22 - Exposure Time: 128 +[MakerNotes, Canon, Image] 23 - Measured EV 2: -1.25 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 4 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: CRW:EOS DIGITAL REBEL CMOS RAW +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware Version 1.1.1 +[MakerNotes, Canon, Camera] 12 - Serial Number: 0560018150 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 1 +[MakerNotes, Canon, Image] 8 - File Number: 118-1861 +[MakerNotes, Canon, Camera] 9 - Owner Name: Phil Harvey +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS Digital Rebel / 300D / Kiss Digital +[MakerNotes, Canon, Image] 14 - Canon File Length: 4480822 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 998 1022 1026 808 +[MakerNotes, Canon, Camera] 1 - WB RGGB Levels Auto: 1719 832 831 990 +[MakerNotes, Canon, Camera] 5 - WB RGGB Levels Daylight: 1722 832 831 989 +[MakerNotes, Canon, Camera] 9 - WB RGGB Levels Shade: 2035 832 831 839 +[MakerNotes, Canon, Camera] 13 - WB RGGB Levels Cloudy: 1878 832 831 903 +[MakerNotes, Canon, Camera] 17 - WB RGGB Levels Tungsten: 1228 913 912 1668 +[MakerNotes, Canon, Camera] 21 - WB RGGB Levels Fluorescent: 1506 842 841 1381 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels Flash: 1933 832 831 895 +[MakerNotes, Canon, Camera] 29 - WB RGGB Levels Custom: 1722 832 831 989 +[MakerNotes, Canon, Camera] 33 - WB RGGB Levels Kelvin: 1722 832 831 988 +[MakerNotes, Canon, Camera] 37 - WB RGGB Black Levels: 124 123 124 123 +[MakerNotes, Canon, Camera] 174 - Color Temperature: 5200 +[MakerNotes, Canon, Camera] 180 - Color Space: sRGB +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3072 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2048 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3072 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2048 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 151 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 151 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 1014 608 0 0 0 -608 -1014 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: 0 0 -506 0 506 0 0 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: (none) +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Continuous Shooting +[Composite, Composite, Camera] Canon-ISO - ISO: 100 +[Composite, Composite, Camera] Canon-Lens - Lens: 18.0 - 55.0 mm +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Bulb +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 1719 832 831 990 +[Composite, Composite, Image] Exif-Aperture - Aperture: 2.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 4 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.190619 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown 18-55mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 0.0 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.067348 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 18.0 - 55.0 mm (35 mm equivalent: 28.6 - 87.4 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: inf (1.29 m - inf) +[Composite, Composite, Image] Exif-FOV - Field Of View: 116.6 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 7.0 mm (35 mm equivalent: 11.1 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 1.30 m diff --git a/ExifTool/t/Writer_16.out b/ExifTool/t/Writer_16.out new file mode 100644 index 0000000..fd4c8a8 --- /dev/null +++ b/ExifTool/t/Writer_16.out @@ -0,0 +1 @@ +[XMP, XMP-tiff, Image] XResolution - X Resolution: 72 diff --git a/ExifTool/t/Writer_17.out b/ExifTool/t/Writer_17.out new file mode 100644 index 0000000..8543981 --- /dev/null +++ b/ExifTool/t/Writer_17.out @@ -0,0 +1 @@ +[XMP, XMP-tiff, Image] XResolution - X Resolution: 300 diff --git a/ExifTool/t/Writer_18.out b/ExifTool/t/Writer_18.out new file mode 100644 index 0000000..09a0094 --- /dev/null +++ b/ExifTool/t/Writer_18.out @@ -0,0 +1,3 @@ +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[XMP, XMP-tiff, Image] XResolution - X Resolution: 300 diff --git a/ExifTool/t/Writer_19.out b/ExifTool/t/Writer_19.out new file mode 100644 index 0000000..edfe80d --- /dev/null +++ b/ExifTool/t/Writer_19.out @@ -0,0 +1,36 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Writer_19_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 553 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:23-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:23-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:23-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 780195eb135f878e7864b2564f1dfc9a +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: Phil +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Author] 315 - Artist: Harvey +[EXIF, ExifIFD, Image] 34855 - ISO: 25 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0232 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2006:03:27 16:25:00 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Hard +[IPTC, IPTC, Other] 25 - Keywords: one, two +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Writer_2.out b/ExifTool/t/Writer_2.out new file mode 100644 index 0000000..72b795d --- /dev/null +++ b/ExifTool/t/Writer_2.out @@ -0,0 +1,172 @@ +AEBBracketValue: 0 +AFAreaHeight: 151 +AFAreaWidth: 151 +AFAreaXPositions: 1014 608 0 0 0 -608 -1014 +AFAreaYPositions: 0 0 -506 0 506 0 0 +AFImageHeight: 2048 +AFImageWidth: 3072 +AFPointsInFocus: (none) +Aperture: 14.0 +ApertureValue: 14.0 +AutoExposureBracketing: Off +AutoISO: 100 +AutoRotate: None +BaseISO: 100 +BitsPerSample: 8 +BlueBalance: 1.190619 +BracketMode: Off +BracketShotNumber: 0 +BracketValue: 0 +BulbDuration: 4 +CameraISO: n/a +CameraType: EOS Mid-range +CanonExposureMode: Manual +CanonFileLength: 4480822 +CanonFirmwareVersion: Firmware Version 1.1.1 +CanonFlashMode: Off +CanonImageHeight: 2048 +CanonImageSize: Large +CanonImageType: CRW:EOS DIGITAL REBEL CMOS RAW +CanonImageWidth: 3072 +CanonModelID: EOS Digital Rebel / 300D / Kiss Digital +CircleOfConfusion: 0.019 mm +ColorComponents: 3 +ColorSpace: sRGB +ColorSpace (1): sRGB +ColorTemperature: 5200 +ColorTone: Normal +Comment: New comment in JPG file +ComponentsConfiguration: Y, Cb, Cr, - +CompressedBitsPerPixel: 9 +ContinuousDrive: Continuous +Contrast: +1 +ControlMode: Camera Local Control +CreateDate: 2003:12:04 06:46:52 +CustomRendered: Normal +DOF: inf (4.31 m - inf) +DateTimeOriginal: 2003:12:04 06:46:52 +DigitalZoom: Unknown (-1) +Directory: t +DriveMode: Continuous Shooting +EasyMode: Manual +EncodingProcess: Baseline DCT, Huffman coding +ExifByteOrder: Little-endian (Intel, II) +ExifImageHeight: 120 +ExifImageWidth: 160 +ExifToolVersion: 12.42 +ExifVersion: 0221 +ExposureCompensation: 0 +ExposureCompensation (1): 0 +ExposureMode: Manual +ExposureTime: 4 +ExposureTime (1): 128 +FNumber: 14.0 +FNumber (1): 14 +FOV: 36.9 deg +FileAccessDate: 2022:06:01 14:16:53-04:00 +FileInodeChangeDate: 2022:06:01 14:16:53-04:00 +FileModifyDate: 2022:06:01 14:16:53-04:00 +FileName: Writer_2_failed.jpg +FileNumber: 118-1861 +FilePermissions: -rw-r--r-- +FileSize: 2.7 kB +FileSource: Digital Camera +FileType: JPEG +FileTypeExtension: jpg +Flash: No Flash +FlashActivity: 0 +FlashBits: (none) +FlashExposureComp: 0 +FlashGuideNumber: 0 +FlashpixVersion: 0100 +FocalLength: 34.0 mm +FocalLength (1): 34 mm +FocalLength35efl: 34.0 mm (35 mm equivalent: 54.0 mm) +FocalPlaneResolutionUnit: inches +FocalPlaneXResolution: 3443.946188 +FocalPlaneXSize: 23.22 mm +FocalPlaneYResolution: 3442.016807 +FocalPlaneYSize: 15.49 mm +FocalUnits: 1/mm +FocusDistanceLower: 5.46 m +FocusDistanceUpper: inf +FocusMode: Manual Focus (3) +FocusRange: Not Known +HyperfocalDistance: 4.37 m +ISO: 100 +ISO (1): 100 +ImageHeight: 8 +ImageSize: 8x8 +ImageWidth: 8 +InteropIndex: THM - DCF thumbnail file +InteropVersion: 0100 +Lens: 18.0 - 55.0 mm +Lens35efl: 18.0 - 55.0 mm (35 mm equivalent: 28.6 - 87.4 mm) +LensID: Unknown 18-55mm +LensType: n/a +LightValue: 5.6 +MIMEType: image/jpeg +MacroMode: Unknown (0) +Make: Canon +ManualFlashOutput: n/a +MaxAperture: 4 +MaxApertureValue: 4.5 +MaxFocalLength: 55 mm +MeasuredEV: -1.25 +MeasuredEV2: -1.25 +MeasuredRGGB: 998 1022 1026 808 +Megapixels: 0.000064 +MeteringMode: Center-weighted average +MeteringMode (1): Average +MinAperture: 27 +MinFocalLength: 18 mm +Model: Canon EOS DIGITAL REBEL +ModifyDate: 2003:12:04 06:46:52 +NDFilter: n/a +NumAFPoints: 7 +OpticalZoomCode: n/a +Orientation: Horizontal (normal) +OwnerName: Phil Harvey +Quality: RAW +RecordMode: CRW+THM +RedBalance: 2.067348 +RelatedImageHeight: 2048 +RelatedImageWidth: 3072 +ResolutionUnit: inches +Saturation: +1 +ScaleFactor35efl: 1.6 +SceneCaptureType: Standard +SelfTimer: Off +SelfTimer2: 0 +SensingMethod: One-chip color area +SequenceNumber: 0 +SerialNumber: 0560018150 +SerialNumberFormat: Format 1 +Sharpness: +1 +ShootingMode: Bulb +ShutterSpeed: 4 +ShutterSpeedValue: 0 +SlowShutter: None +TargetAperture: 14 +ThumbnailImageValidArea: 0 159 7 112 +UserComment: +ValidAFPoints: 7 +WB_RGGBBlackLevels: 124 123 124 123 +WB_RGGBLevels: 1719 832 831 990 +WB_RGGBLevelsAuto: 1719 832 831 990 +WB_RGGBLevelsCloudy: 1878 832 831 903 +WB_RGGBLevelsCustom: 1722 832 831 989 +WB_RGGBLevelsDaylight: 1722 832 831 989 +WB_RGGBLevelsFlash: 1933 832 831 895 +WB_RGGBLevelsFluorescent: 1506 842 841 1381 +WB_RGGBLevelsKelvin: 1722 832 831 988 +WB_RGGBLevelsShade: 2035 832 831 839 +WB_RGGBLevelsTungsten: 1228 913 912 1668 +WhiteBalance: Auto +WhiteBalance (1): Auto +XResolution: 180 +YCbCrPositioning: Centered +YCbCrSubSampling: YCbCr4:2:0 (2 2) +YResolution: 180 +ZoomSourceWidth: 3072 +ZoomTargetWidth: 3072 diff --git a/ExifTool/t/Writer_22.out b/ExifTool/t/Writer_22.out new file mode 100644 index 0000000..4febb8e --- /dev/null +++ b/ExifTool/t/Writer_22.out @@ -0,0 +1,4 @@ +[EXIF, IFD0, Time] 306 - Modify Date: 2006:03:26 12:40:46 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 19:38:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2000:10:31 18:36:41 +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2002:06:26 diff --git a/ExifTool/t/Writer_24.out b/ExifTool/t/Writer_24.out new file mode 100644 index 0000000..74fc7dd --- /dev/null +++ b/ExifTool/t/Writer_24.out @@ -0,0 +1 @@ +[File, File, Image] Comment - Comment: ISO=100 Aperture=3.5 Exposure=1/64 diff --git a/ExifTool/t/Writer_25.out b/ExifTool/t/Writer_25.out new file mode 100644 index 0000000..8ee8a06 --- /dev/null +++ b/ExifTool/t/Writer_25.out @@ -0,0 +1,40 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Writer_25_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 621 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:24-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:23-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:24-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: NIKON +[EXIF, IFD0, Camera] 272 - Camera Model Name: E775 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: E775v1.3u +[EXIF, IFD0, Time] 306 - Modify Date: 2001:08:01 12:57:23 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33437 - F Number: 25.0 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0232 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 300 +[EXIF, IFD1, Image] 283 - Y Resolution: 300 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[Composite, Composite, Image] Exif-Aperture - Aperture: 25.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Writer_26.out b/ExifTool/t/Writer_26.out new file mode 100644 index 0000000..ce94374 --- /dev/null +++ b/ExifTool/t/Writer_26.out @@ -0,0 +1,34 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Writer_26_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 535 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:24-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:24-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:24-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: NIKON +[EXIF, IFD0, Camera] 272 - Camera Model Name: E775 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: E775v1.3u +[EXIF, IFD0, Time] 306 - Modify Date: 2001:08:01 12:57:23 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 300 +[EXIF, IFD1, Image] 283 - Y Resolution: 300 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Writer_27.out b/ExifTool/t/Writer_27.out new file mode 100644 index 0000000..ce7237d --- /dev/null +++ b/ExifTool/t/Writer_27.out @@ -0,0 +1,14 @@ +[JFIF, JFIF, Image] 3 - X Resolution: 144 +[JFIF, JFIF, Image] 5 - Y Resolution: 144 +[EXIF, IFD0, Image] 282 - X Resolution: 144 +[EXIF, IFD0, Image] 283 - Y Resolution: 144 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2000:01:02 03:04:05 +[EXIF, IFD0, Image] 282 - X Resolution: 144 +[EXIF, IFD0, Image] 283 - Y Resolution: 144 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2000:01:02 03:04:05 +[APP8, SPIFF, Image] 18 - Y Resolution: 300 +[APP8, SPIFF, Image] 22 - X Resolution: 300 +[MakerNotes, CIFF, Time] 0 - Date/Time Original: 1998:05:01 21:33:18 +[APP12, PictureInfo, Time] TimeDate - Date/Time Original: 1998:12:31 15:17:20 +[Photoshop, Photoshop, Image] 0 - X Resolution: 72 +[Photoshop, Photoshop, Image] 4 - Y Resolution: 72 diff --git a/ExifTool/t/Writer_28.out b/ExifTool/t/Writer_28.out new file mode 100644 index 0000000..0b13f28 --- /dev/null +++ b/ExifTool/t/Writer_28.out @@ -0,0 +1 @@ +[File, System, Other] FileName - File Name: Writer_28_failed.jpg diff --git a/ExifTool/t/Writer_29.out b/ExifTool/t/Writer_29.out new file mode 100644 index 0000000..b5cf7d5 --- /dev/null +++ b/ExifTool/t/Writer_29.out @@ -0,0 +1,2 @@ +[File, System, Other] FileName - File Name: Writer_29_failed.jpg +[EXIF, ExifIFD, Image] 34855 - ISO: 200 diff --git a/ExifTool/t/Writer_30.out b/ExifTool/t/Writer_30.out new file mode 100644 index 0000000..7493b72 --- /dev/null +++ b/ExifTool/t/Writer_30.out @@ -0,0 +1,2 @@ +[File, System, Other] FileName - File Name: Writer_30_failed.jpg +[EXIF, IFD0, Image] 34855 - ISO: 100 diff --git a/ExifTool/t/Writer_31.out b/ExifTool/t/Writer_31.out new file mode 100644 index 0000000..a1035e3 --- /dev/null +++ b/ExifTool/t/Writer_31.out @@ -0,0 +1,108 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.46 +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest is not current. XMP may be out of sync +[File, System, Other] FileName - File Name: Writer_31_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.0 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:09:19 14:15:53-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:09:19 14:15:53-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:19 14:15:53-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 6895be53ef9a287520f400aa17242c09 +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: A witty caption +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2004:02:26 09:36:46 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: Copyright 2004 Phil Harvey +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 100 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 80 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other] 105 - Headline: No headline +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My Position +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Author] 115 - Source: I'm the source +[IPTC, IPTC, Other] 25 - Keywords: jambalaya +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: 05ad1770b1a95f1f9788ac995fa647da +[Photoshop, Photoshop, Image] 0 - X Resolution: 72 +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: 72 +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Image] 0 - Print Style: Centered +[Photoshop, Photoshop, Image] 2 - Print Position: 0 0 +[Photoshop, Photoshop, Image] 10 - Print Scale: 1 +[Photoshop, Photoshop, Image] 1037 - Global Angle: 30 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 30 +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image] 1054 - URL List: +[Photoshop, Photoshop, Other] 20 - Slices Group Name: IPTC +[Photoshop, Photoshop, Other] 24 - Num Slices: 1 +[Photoshop, Photoshop, Image] 4 - Has Real Merged Data: Yes +[Photoshop, Photoshop, Image] 5 - Writer Name: Adobe Photoshop +[Photoshop, Photoshop, Image] 9 - Reader Name: Adobe Photoshop 7.0 +[Photoshop, Photoshop, Image] 0 - Photoshop Quality: 7 +[Photoshop, Photoshop, Image] 1 - Photoshop Format: Standard +[APP14, Adobe, Image] 0 - DCT Encode Version: 100 +[APP14, Adobe, Image] 1 - APP14 Flags 0: (none) +[APP14, Adobe, Image] 2 - APP14 Flags 1: (none) +[APP14, Adobe, Image] 3 - Color Transform: YCbCr +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.6 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.0 mm diff --git a/ExifTool/t/Writer_32.out b/ExifTool/t/Writer_32.out new file mode 100644 index 0000000..3178224 --- /dev/null +++ b/ExifTool/t/Writer_32.out @@ -0,0 +1,3 @@ +[ICC_Profile, ICC_Profile, Image] ICC_Profile - ICC Profile: (Binary data 3144 bytes) +[EXIF, IFD0, Image] 50831 - As Shot ICC Profile: (Binary data 31 bytes) +[EXIF, IFD0, Image] 50833 - Current ICC Profile: (Binary data 33 bytes) diff --git a/ExifTool/t/Writer_33.out b/ExifTool/t/Writer_33.out new file mode 100644 index 0000000..89a0d12 --- /dev/null +++ b/ExifTool/t/Writer_33.out @@ -0,0 +1,3 @@ +[ICC_Profile, ICC_Profile, Image] ICC_Profile - ICC Profile: (Binary data 39 bytes) +[EXIF, IFD0, Image] 50831 - As Shot ICC Profile: (Binary data 31 bytes) +[EXIF, IFD0, Image] 50833 - Current ICC Profile: (Binary data 33 bytes) diff --git a/ExifTool/t/Writer_34.out b/ExifTool/t/Writer_34.out new file mode 100644 index 0000000..c3c9b56 --- /dev/null +++ b/ExifTool/t/Writer_34.out @@ -0,0 +1,3 @@ +[XMP, XMP-dc, Image] subject - Subject: [ExifTool,Test,IPTC] +[XMP, XMP-lr, Image] hierarchicalSubject - Hierarchical Subject: [IPTC.jpg,ExifTool,Test,IPTC] +[File, File, Image] Comment - Comment: ExifTool, Test, IPTC diff --git a/ExifTool/t/Writer_35.out b/ExifTool/t/Writer_35.out new file mode 100644 index 0000000..e45e1d6 --- /dev/null +++ b/ExifTool/t/Writer_35.out @@ -0,0 +1,184 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_35_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 9.7 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:55-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:55-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:55-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: d624fa41e4169333a86729e3c275f95a +[File, File, Image] Comment - Comment: © PhotoStudio Unicode comment;; +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: A witty caption +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2004:02:26 09:36:46 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, IFD0, Author] 33432 - Copyright: Copyright 2004 Phil Harvey +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 100 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 80 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Other] 25 - Keywords: jambalaya +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My Position +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 105 - Headline: No headline +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Author] 115 - Source: I'm the source +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image] 1037 - Global Angle: 30 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 30 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Image] Category - Category: 1 +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image] Headline - Headline: No headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Author] Source - Source: I'm the source +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference? +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:06:03 00:00:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple Computer Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright 1999 Adobe Systems Incorporated +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Adobe RGB (1998) +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.60974 0.31111 0.01947 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.20528 0.62567 0.06087 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14919 0.06322 0.74457 +[Ducky, Ducky, Author] 3 - Copyright: Copyright 2004 Phil Harvey +[Ducky, Ducky, Image] 1 - Quality: 84% +[APP14, Adobe, Image] 0 - DCT Encode Version: 100 +[APP14, Adobe, Image] 1 - APP14 Flags 0: (none) +[APP14, Adobe, Image] 2 - APP14 Flags 1: (none) +[APP14, Adobe, Image] 3 - Color Transform: YCbCr +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 1.0.0 +[CanonVRD, CanonVRD, Image] 6 - WB Adj RGGB Levels: 0 0 0 0 +[CanonVRD, CanonVRD, Image] 24 - White Balance Adj: Shot Settings +[CanonVRD, CanonVRD, Image] 26 - WB Adj Color Temp: 5600 +[CanonVRD, CanonVRD, Image] 36 - WB Fine Tune Active: No +[CanonVRD, CanonVRD, Image] 40 - WB Fine Tune Saturation: 0 +[CanonVRD, CanonVRD, Image] 44 - WB Fine Tune Tone: 0 +[CanonVRD, CanonVRD, Image] 46 - Raw Color Adj: Shot Settings +[CanonVRD, CanonVRD, Image] 48 - Raw Custom Saturation: 0 +[CanonVRD, CanonVRD, Image] 52 - Raw Custom Tone: 0 +[CanonVRD, CanonVRD, Image] 56 - Raw Brightness Adj: 0.00 +[CanonVRD, CanonVRD, Image] 60 - Tone Curve Property: Shot Settings +[CanonVRD, CanonVRD, Image] 122 - Dynamic Range Min: 0 +[CanonVRD, CanonVRD, Image] 124 - Dynamic Range Max: 4095 +[CanonVRD, CanonVRD, Image] 272 - Tone Curve Active: No +[CanonVRD, CanonVRD, Image] 275 - Tone Curve Mode: RGB +[CanonVRD, CanonVRD, Image] 276 - Brightness Adj: 0 +[CanonVRD, CanonVRD, Image] 277 - Contrast Adj: 0 +[CanonVRD, CanonVRD, Image] 278 - Saturation Adj: 100 +[CanonVRD, CanonVRD, Image] 286 - Color Tone Adj: 0 +[CanonVRD, CanonVRD, Image] 294 - Luminance Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 336 - Luminance Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 345 - Tone Curve Interpolation: Curve +[CanonVRD, CanonVRD, Image] 352 - Red Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 394 - Red Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 410 - Green Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 452 - Green Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 468 - Blue Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 510 - Blue Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 526 - RGB Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 568 - RGB Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 580 - Crop Active: No +[CanonVRD, CanonVRD, Image] 582 - Crop Left: 0 +[CanonVRD, CanonVRD, Image] 584 - Crop Top: 0 +[CanonVRD, CanonVRD, Image] 586 - Crop Width: 0 +[CanonVRD, CanonVRD, Image] 588 - Crop Height: 0 +[CanonVRD, CanonVRD, Image] 602 - Sharpness Adj: 0 +[CanonVRD, CanonVRD, Image] 608 - Crop Aspect Ratio: Free +[CanonVRD, CanonVRD, Image] 610 - Constrained Crop Width: 0 +[CanonVRD, CanonVRD, Image] 614 - Constrained Crop Height: 0 +[CanonVRD, CanonVRD, Image] 618 - Check Mark: Clear +[CanonVRD, CanonVRD, Image] 622 - Rotation: 90 +[CanonVRD, CanonVRD, Image] 624 - Work Color Space: sRGB diff --git a/ExifTool/t/Writer_36.out b/ExifTool/t/Writer_36.out new file mode 100644 index 0000000..beaf14f --- /dev/null +++ b/ExifTool/t/Writer_36.out @@ -0,0 +1,2 @@ +[IPTC, IPTC, Other] 25 - Keywords: in +[IPTC, IPTC, Other] 0 - Application Record Version: 4 diff --git a/ExifTool/t/Writer_37.out b/ExifTool/t/Writer_37.out new file mode 100644 index 0000000..b2f1039 --- /dev/null +++ b/ExifTool/t/Writer_37.out @@ -0,0 +1,2 @@ +[File, System, Other] FileSize - File Size: 2518 +[EXIF, IFD1, Image] 259 - Compression: 6 diff --git a/ExifTool/t/Writer_38.out b/ExifTool/t/Writer_38.out new file mode 100644 index 0000000..9535e5f --- /dev/null +++ b/ExifTool/t/Writer_38.out @@ -0,0 +1,63 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_38_failed.exif +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 2.2 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:55-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:55-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:55-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: EXIF +[File, File, Other] FileTypeExtension - File Type Extension: exif +[File, File, Other] MIMEType - MIME Type: application/unknown +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: SONY +[EXIF, IFD0, Camera] 272 - Camera Model Name: DSC-F828 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:18 13:51:33 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/250 +[EXIF, ExifIFD, Image] 33437 - F Number: 7.1 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 64 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:18 13:51:33 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:18 13:51:33 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.0 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 12.5 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 3264 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 2448 +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Auto bracket +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41992 - Contrast: Normal +[EXIF, ExifIFD, Camera] 41993 - Saturation: Normal +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Normal +[MakerNotes, Sony, Camera] 8192 - Sony 0x2000: 0 +[MakerNotes, Sony, Camera] 36865 - Sony 0x9001: ..­mQV„ûV„û.³9°9ŠJuÿ.Ŭ]è^ޏ.[...] +[MakerNotes, Sony, Camera] 36866 - Sony 0x9002: .JÚ`.p.V!.!¾ê¾ê0ÿ0ÿV!ˆPp,×.0c[...] +[MakerNotes, Sony, Camera] 36867 - Sony 0x9003: .ÜsF®&.¡ÒB.¯.....È..ûÙ;›Õ.utN½½½½½½(¶^w’[...] +[MakerNotes, Sony, Camera] 36868 - Sony 0x9004: ..,.,.,.,.ô.ô.ô.ô.ô.ô.ô.ôlêoÄ@.o”.,‚.,Ï.o‰3úM[...] +[MakerNotes, Sony, Camera] 36869 - Sony 0x9005: ..@o........}[...] +[MakerNotes, Sony, Camera] 36870 - Sony 0x9006: .ð™ârï‘.{.•ân‡öGeG‘š‘`zâ[...] +[MakerNotes, Sony, Camera] 36871 - Sony 0x9007: .ç;.|.cçf.VØÌ£.üêbêØ.ßç¶^O[...] +[MakerNotes, Sony, Camera] 36872 - Sony 0x9008: ..ªø..¼·’.³ôi.Þ.Ð@÷.]:þïó•±ö;Žî;ä‚.ÕH[...] +[PrintIM, PrintIM, Printing] PrintIMVersion - PrintIM Version: 0250 +[PrintIM, PrintIM, Printing] 2 - Print IM 0x0002: 0x00000001 +[PrintIM, PrintIM, Printing] 257 - Print IM 0x0101: 0x00000000 +[Composite, Composite, Image] Exif-Aperture - Aperture: 7.1 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/250 +[Composite, Composite, Image] Exif-LightValue - Light Value: 14.3 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 12.5 mm diff --git a/ExifTool/t/Writer_39.out b/ExifTool/t/Writer_39.out new file mode 100644 index 0000000..d5eda01 --- /dev/null +++ b/ExifTool/t/Writer_39.out @@ -0,0 +1,4 @@ +[File, System, Other] FileName - File Name: Writer_39_failed.jpg +[EXIF, IFD0, Image] 282 - X Resolution: 123 +[EXIF, IFD0, Image] 296 - Resolution Unit: cm +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 1999:99:99 99:99:99 diff --git a/ExifTool/t/Writer_4.out b/ExifTool/t/Writer_4.out new file mode 100644 index 0000000..a63990f --- /dev/null +++ b/ExifTool/t/Writer_4.out @@ -0,0 +1,87 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileSize - File Size: 7.8 kB +[File, File, Other] FileType - File Type: TIFF +[File, File, Other] FileTypeExtension - File Type Extension: tif +[File, File, Other] MIMEType - MIME Type: image/tiff +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: e9bf6d2340af45aa812e91745756c524 +[EXIF, IFD0, Image] 254 - Subfile Type: Full-resolution image +[EXIF, IFD0, Image] 256 - Image Width: 160 +[EXIF, IFD0, Image] 257 - Image Height: 120 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 259 - Compression: LZW +[EXIF, IFD0, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD0, Image] 270 - Image Description: Modified TIFF +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 273 - Strip Offsets: 6754 +[EXIF, IFD0, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD0, Image] 278 - Rows Per Strip: 120 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 1048 +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: GraphicConverter +[EXIF, IFD0, Time] 306 - Modify Date: 2004:02:20 08:07:49 +[EXIF, IFD0, Image] 317 - Predictor: None +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: new XMP info +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: The picture caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other] 40 - Special Instructions: no instructions +[IPTC, IPTC, Author] 80 - By-line: I'm the author +[IPTC, IPTC, Author] 85 - By-line Title: On top +[IPTC, IPTC, Author] 110 - Credit: Phil Harvey +[IPTC, IPTC, Author] 115 - Source: My camera +[IPTC, IPTC, Other] 5 - Object Name: This is the title +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:20 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ontario +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: no reference +[IPTC, IPTC, Other] 25 - Keywords: exiftool, test, picture, another keyword +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright notice +[IPTC, IPTC, Other] 105 - Headline: A different headline +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Linotronic +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1998:02:09 06:49:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Microsoft Corporation +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: Hewlett-Packard +[ICC_Profile, ICC-header, Image] 52 - Device Model: sRGB +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Hewlett-Packard +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright (c) 1998 Hewlett-Packard Company +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: sRGB IEC61966-2.1 +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.43607 0.22249 0.01392 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.38515 0.71687 0.09708 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14307 0.06061 0.7141 +[ICC_Profile, ICC_Profile, Camera] dmnd - Device Mfg Desc: IEC http://www.iec.ch +[ICC_Profile, ICC_Profile, Camera] dmdd - Device Model Desc: IEC 61966-2.1 Default RGB colour space - sRGB +[ICC_Profile, ICC_Profile, Image] vued - Viewing Cond Desc: Reference Viewing Condition in IEC61966-2.1 +[ICC_Profile, ICC-view, Image] 8 - Viewing Cond Illuminant: 19.6445 20.3718 16.8089 +[ICC_Profile, ICC-view, Image] 20 - Viewing Cond Surround: 3.92889 4.07439 3.36179 +[ICC_Profile, ICC-view, Image] 32 - Viewing Cond Illuminant Type: D50 +[ICC_Profile, ICC_Profile, Image] lumi - Luminance: 76.03647 80 87.12462 +[ICC_Profile, ICC-meas, Image] 8 - Measurement Observer: CIE 1931 +[ICC_Profile, ICC-meas, Image] 12 - Measurement Backing: 0 0 0 +[ICC_Profile, ICC-meas, Image] 24 - Measurement Geometry: Unknown +[ICC_Profile, ICC-meas, Image] 28 - Measurement Flare: 0.999% +[ICC_Profile, ICC-meas, Image] 32 - Measurement Illuminant: D65 +[ICC_Profile, ICC_Profile, Image] tech - Technology: Cathode Ray Tube Display +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 2060 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 2060 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 2060 bytes) +[Composite, Composite, Image] Exif-ImageSize - Image Size: 160x120 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.019 diff --git a/ExifTool/t/Writer_40.out b/ExifTool/t/Writer_40.out new file mode 100644 index 0000000..26ed400 --- /dev/null +++ b/ExifTool/t/Writer_40.out @@ -0,0 +1,4 @@ +[File, System, Other] FileName - File Name: Writer_40_failed.jpg +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:04 06:46:52 diff --git a/ExifTool/t/Writer_41.out b/ExifTool/t/Writer_41.out new file mode 100644 index 0000000..b372eee --- /dev/null +++ b/ExifTool/t/Writer_41.out @@ -0,0 +1,3 @@ +[MakerNotes, CIFF, Camera] 2064 - Owner Name: CIFF Write Test +[Meta, MetaIFD, Camera] 50004 - Serial Number: 12345 +[APP12, PictureInfo, Camera] Serial# - Serial Number: #00000001 diff --git a/ExifTool/t/Writer_42.out b/ExifTool/t/Writer_42.out new file mode 100644 index 0000000..04f8e6e --- /dev/null +++ b/ExifTool/t/Writer_42.out @@ -0,0 +1,5 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 8.32 +[EXIF, IFD0, Image] 282 - X Resolution: 144 +[EXIF, IFD0, Image] 283 - Y Resolution: 144 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered diff --git a/ExifTool/t/Writer_43.out b/ExifTool/t/Writer_43.out new file mode 100644 index 0000000..67429be --- /dev/null +++ b/ExifTool/t/Writer_43.out @@ -0,0 +1,5 @@ +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 5.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Center-weighted average +[MakerNotes, Canon, Camera] 17 - Metering Mode: Unknown (6) +[MakerNotes, Canon, Image] 22 - Exposure Time: 130.8 +[MakerNotes, Canon, Camera] 12 - Serial Number: 0560018159 diff --git a/ExifTool/t/Writer_44.out b/ExifTool/t/Writer_44.out new file mode 100644 index 0000000..35d41ef --- /dev/null +++ b/ExifTool/t/Writer_44.out @@ -0,0 +1,2 @@ +[XMP, XMP-exif, Image] ApertureValue - Aperture Value: 2.7 +[XMP, XMP-exif, Time] DateTimeOriginal - Date/Time Original: 2005:06:09 23:09:27+02:00 diff --git a/ExifTool/t/Writer_45.out b/ExifTool/t/Writer_45.out new file mode 100644 index 0000000..4d29f80 --- /dev/null +++ b/ExifTool/t/Writer_45.out @@ -0,0 +1 @@ +[EXIF, IFD0, Author] 315 - Artist: Pêro diff --git a/ExifTool/t/Writer_46.out b/ExifTool/t/Writer_46.out new file mode 100644 index 0000000..3a32986 --- /dev/null +++ b/ExifTool/t/Writer_46.out @@ -0,0 +1,121 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_46_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 11 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:55-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:55-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:55-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: ec5278e5f2548e4ce57e1814f3413714 +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: 7 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0232 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 7.0 +[EXIF, ExifIFD, Image] 37888 - Ambient Temperature: 7 C +[EXIF, ExifIFD, Image] 37892 - Acceleration: 7 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[IPTC, IPTC, Other] 120 - ARM Identifier: 7 +[IPTC, IPTC, Other] 122 - ARM Version: 7 +[IPTC, IPTC, Other] 0 - Envelope Record Version: 4 +[IPTC, IPTC, Other] 0 - Application Record Version: 7 +[IPTC, IPTC, Other] 151 - Audio Sampling Rate: 000007 +[IPTC, IPTC, Other] 152 - Audio Sampling Resolution: 07 +[IPTC, IPTC, Other] 153 - Audio Duration: 000007 +[IPTC, IPTC, Other] 154 - Audio Outcue: 7 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-Device, Camera] AppInfoApplication - App Info Application: 7 +[XMP, XMP-Device, Camera] AppInfoItemURI - App Info Item URI: 7 +[XMP, XMP-Device, Camera] AppInfoVersion - App Info Version: 7 +[XMP, XMP-GAudio, Audio] Data - Audio Data: (Binary data 1 bytes) +[XMP, XMP-GAudio, Audio] Mime - Audio Mime Type: 7 +[XMP, XMP-getty, Image] AssetID - Asset ID: 7 +[XMP, XMP-iptcCore, Other] AltTextAccessibility - Alt Text Accessibility: 7 +[XMP, XMP-iptcExt, Author] AboutCvTermCvId - About Cv Term Cv Id: 7 +[XMP, XMP-iptcExt, Author] AboutCvTermCvTermId - About Cv Term Id: 7 +[XMP, XMP-iptcExt, Author] AboutCvTermCvTermName - About Cv Term Name: 7 +[XMP, XMP-iptcExt, Author] AboutCvTermCvTermRefinedAbout - About Cv Term Refined About: 7 +[XMP, XMP-iptcExt, Author] AddlModelInfo - Additional Model Information: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOContentDescription - Artwork Content Description: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOContributionDescription - Artwork Contribution Description: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOCopyrightNotice - Artwork Copyright Notice: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOCreator - Artwork Creator: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOCreatorId - Artwork Creator ID: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOCurrentCopyrightOwnerId - Artwork Copyright Owner ID: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOCurrentCopyrightOwnerName - Artwork Copyright Owner Name: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOCurrentLicensorId - Artwork Licensor ID: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOCurrentLicensorName - Artwork Licensor Name: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOPhysicalDescription - Artwork Physical Description: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOSource - Artwork Source: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOSourceInvNo - Artwork Source Inventory No: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOSourceInvURL - Artwork Source Inv URL: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOStylePeriod - Artwork Style Period: 7 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOTitle - Artwork Title: 7 +[XMP, XMP-iptcExt, Audio] audioBitRate - Audio Bitrate: 7 +[XMP, XMP-iptcExt, Audio] audioBitsPerSample - Audio Bits Per Sample: 7 +[XMP, XMP-iptcExt, Audio] audioChannelCount - Audio Channel Count: 7 +[XMP, XMP-aas, Image] AffineA - Affine A: 7 +[XMP, XMP-aas, Image] AffineB - Affine B: 7 +[XMP, XMP-aas, Image] AffineC - Affine C: 7 +[XMP, XMP-aas, Image] AffineD - Affine D: 7 +[XMP, XMP-aas, Image] AffineX - Affine X: 7 +[XMP, XMP-aas, Image] AffineY - Affine Y: 7 +[XMP, XMP-apple-fi, Image] AngleInfoRoll - Angle Info Roll: 7 +[XMP, XMP-apple-fi, Image] AngleInfoYaw - Angle Info Yaw: 7 +[XMP, XMP-aux, Camera] ApproximateFocusDistance - Approximate Focus Distance: 7 +[XMP, XMP-cc, Author] attributionName - Attribution Name: 7 +[XMP, XMP-cc, Author] attributionURL - Attribution URL: 7 +[XMP, XMP-creatorAtom, Image] aeProjectLinkCompositionID - Ae Project Link Composition ID: 7 +[XMP, XMP-creatorAtom, Image] aeProjectLinkFullPath - Ae Project Link Full Path: 7 +[XMP, XMP-creatorAtom, Image] aeProjectLinkRenderOutputModuleIndex - Ae Project Link Render Output Module Index: 7 +[XMP, XMP-creatorAtom, Image] aeProjectLinkRenderQueueItemID - Ae Project Link Render Queue Item ID: 7 +[XMP, XMP-creatorAtom, Image] aeProjectLinkRenderTimeStamp - Ae Project Link Render Time Stamp: 7 +[XMP, XMP-drone-dji, Location] AbsoluteAltitude - Absolute Altitude: 7 +[XMP, XMP-extensis, Image] Approved - Approved: True +[XMP, XMP-extensis, Image] ApprovedBy - Approved By: 7 +[XMP, XMP-ics, Image] AppVersion - App Version: 7 +[XMP, XMP-pdf, Author] Author - Author: 7 +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: 7 +[XMP, XMP-prism, Document] academicField - Academic Field: 7 +[XMP, XMP-prism, Document] aggregateIssueNumber - Aggregate Issue Number: 7 +[XMP, XMP-prism, Document] aggregationType - Aggregation Type: 7 +[XMP, XMP-prism, Document] alternateTitleA-lang - Alternate Title A-lang: 7 +[XMP, XMP-prism, Document] alternateTitleA-platform - Alternate Title A-platform: 7 +[XMP, XMP-prism, Document] alternateTitleText - Alternate Title Text: 7 +[XMP, XMP-pur, Document] agreement - Agreement: 7 +[XMP, XMP-xmp, Image] Advisory - Advisory: 7 +[XMP, XMP-xmpDM, Image] absPeakAudioFilePath - Abs Peak Audio File Path: 7 +[XMP, XMP-xmpDM, Image] album - Album: 7 +[XMP, XMP-xmpDM, Image] altTapeName - Alt Tape Name: 7 +[XMP, XMP-xmpDM, Image] altTimecodeTimeValue - Alt Timecode Time Value: 7 +[XMP, XMP-xmpDM, Image] altTimecodeValue - Alt Timecode Value: 7 +[XMP, XMP-xmpDM, Image] audioChannelType - Audio Channel Type: 7.1 +[XMP, XMP-xmpDM, Image] audioCompressor - Audio Compressor: 7 +[XMP, XMP-xmpDM, Image] audioSampleRate - Audio Sample Rate: 7 +[XMP, XMP-crs, Image] AlreadyApplied - Already Applied: True +[XMP, XMP-crs, Image] AutoBrightness - Auto Brightness: True +[XMP, XMP-crs, Image] AutoContrast - Auto Contrast: True +[XMP, XMP-crs, Image] AutoExposure - Auto Exposure: True +[XMP, XMP-crs, Image] AutoLateralCA - Auto Lateral CA: 7 +[XMP, XMP-crs, Image] AutoShadows - Auto Shadows: True +[XMP, XMP-crs, Image] AutoTone - Auto Tone: True +[XMP, XMP-crs, Image] AutoToneDigest - Auto Tone Digest: 7 +[XMP, XMP-crs, Image] AutoToneDigestNoSat - Auto Tone Digest No Sat: 7 +[XMP, XMP-crs, Image] AutoWhiteVersion - Auto White Version: 7 +[Composite, Composite, Image] Exif-Aperture - Aperture: 7.0 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Writer_47.out b/ExifTool/t/Writer_47.out new file mode 100644 index 0000000..636c9d0 --- /dev/null +++ b/ExifTool/t/Writer_47.out @@ -0,0 +1,57 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_47_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 2.8 kB +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 836932ef3a5ae22968bb3bb73ab7e497 +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: Phil +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 4 +[EXIF, ExifIFD, Image] 33437 - F Number: 14.0 +[EXIF, ExifIFD, Image] 34855 - ISO: 150 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 9 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 14.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 34.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 160 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 120 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: THM - DCF thumbnail file +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, InteropIFD, Image] 4097 - Related Image Width: 3072 +[EXIF, InteropIFD, Image] 4098 - Related Image Height: 2048 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3443.946188 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3442.016807 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[IPTC, IPTC, Other] 25 - Keywords: Y +[IPTC, IPTC, Other] 0 - Application Record Version: 4 diff --git a/ExifTool/t/Writer_48.out b/ExifTool/t/Writer_48.out new file mode 100644 index 0000000..740f10a --- /dev/null +++ b/ExifTool/t/Writer_48.out @@ -0,0 +1,31 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.9 kB +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Big-endian (Motorola, MM) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: f61c7c28aa8bb54215cff2f36f256b2a +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: A +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[IPTC, IPTC, Other] 25 - Keywords: A +[IPTC, IPTC, Location] 90 - City: A +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-iptcExt, Location] LocationCreatedCity - Location Created City: A +[XMP, XMP-dc, Image] subject - Subject: A +[XMP, XMP-dc, Image] title - Title: A +[XMP, XMP-exif, Camera] FlashFired - Flash Fired: True +[Composite, Composite, Camera] XMP-Flash - Flash: Fired +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Writer_50.out b/ExifTool/t/Writer_50.out new file mode 100644 index 0000000..45482bb --- /dev/null +++ b/ExifTool/t/Writer_50.out @@ -0,0 +1,3 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 9.15 +[XMP, XMP-dc, Other] test - Test: B +[XMP, XMP-dc, Image] title - Title: A diff --git a/ExifTool/t/Writer_51.out b/ExifTool/t/Writer_51.out new file mode 100644 index 0000000..9e099d5 --- /dev/null +++ b/ExifTool/t/Writer_51.out @@ -0,0 +1,429 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.46 +[ExifTool, ExifTool, ExifTool] Warning - Warning: IPTCDigest is not current. XMP may be out of sync +[File, System, Other] FileName - File Name: Writer_51_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 26 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:09:19 14:15:53-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:09:19 14:15:53-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:09:19 14:15:53-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] Comment - Comment: © PhotoStudio Unicode comment;; +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 6895be53ef9a287520f400aa17242c09 +[File, File, Image] Comment - Comment: a comment +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: A witty caption +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2004:02:26 09:36:46 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: Copyright 2004 Phil Harvey +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 100 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 80 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 852 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 0 +[JFIF, JFIF, Image] 0 - JFIF Version: 1.01 +[JFIF, JFIF, Image] 2 - Resolution Unit: inches +[JFIF, JFIF, Image] 3 - X Resolution: 144 +[JFIF, JFIF, Image] 5 - Y Resolution: 144 +[JFIF, JFXX, Preview] 16 - Thumbnail Image: (Binary data 1558 bytes) +[APP8, SPIFF, Image] 0 - SPIFF Version: 1.2 +[APP8, SPIFF, Image] 2 - Profile ID: Continuous-tone Base +[APP8, SPIFF, Image] 3 - Color Components: 3 +[APP8, SPIFF, Image] 6 - Image Height: 4500 +[APP8, SPIFF, Image] 10 - Image Width: 3000 +[APP8, SPIFF, Image] 14 - Color Space: YCbCr, ITU-R BT 709, video +[APP8, SPIFF, Image] 15 - Bits Per Sample: 8 +[APP8, SPIFF, Image] 16 - Compression: JPEG +[APP8, SPIFF, Image] 17 - Resolution Unit: inches +[APP8, SPIFF, Image] 18 - Y Resolution: 300 +[APP8, SPIFF, Image] 22 - X Resolution: 300 +[MakerNotes, CIFF, Image] 0 - File Format: JPEG (lossy) +[MakerNotes, CIFF, Image] 1 - Target Compression Ratio: 2 +[MakerNotes, CIFF, Image] 0 - Image Width: 512 +[MakerNotes, CIFF, Image] 1 - Image Height: 384 +[MakerNotes, CIFF, Image] 2 - Pixel Aspect Ratio: 1 +[MakerNotes, CIFF, Image] 3 - Rotation: 0 +[MakerNotes, CIFF, Image] 4 - Component Bit Depth: 8 +[MakerNotes, CIFF, Image] 5 - Color Bit Depth: 24 +[MakerNotes, CIFF, Image] 6 - Color BW: 1 +[MakerNotes, CIFF, Camera] 4106 - Target Image Type: Real-world Subject +[MakerNotes, CIFF, Camera] 6148 - Record ID: 0 +[MakerNotes, CIFF, Image] 6167 - File Number: 45 +[MakerNotes, CIFF, Time] 0 - Date/Time Original: 1998:05:01 21:33:18 +[MakerNotes, CIFF, Time] 1 - Time Zone Code: 0 +[MakerNotes, CIFF, Time] 2 - Time Zone Info: 0 +[MakerNotes, CIFF, Camera] 2070 - Original File Name: C:\DC97\CTG_0000\AUT_0045.JPG +[MakerNotes, CIFF, Camera] 2071 - Thumbnail File Name: C:\DC97\CTG_0000\THM_0045.JPG +[MakerNotes, CIFF, Camera] 4112 - Shutter Release Method: Single Shot +[MakerNotes, CIFF, Camera] 4113 - Shutter Release Timing: Priority on focus +[MakerNotes, CIFF, Image] 0 - Flash Guide Number: 0 +[MakerNotes, CIFF, Image] 1 - Flash Threshold: 0 +[MakerNotes, CIFF, Image] 0 - Exposure Compensation: 1 +[MakerNotes, CIFF, Image] 1 - Shutter Speed Value: 1/83 +[MakerNotes, CIFF, Image] 2 - Aperture Value: 6.2 +[MakerNotes, CIFF, Camera] 6151 - Target Distance Setting: 476 mm +[MakerNotes, CIFF, Camera] 6164 - Measured EV: 16.25 +[MakerNotes, CIFF, Camera] 2053 - Canon File Description: +[MakerNotes, CIFF, Camera] 2069 - Canon Image Type: AUT:Full automatic mode +[MakerNotes, CIFF, Camera] 2064 - Owner Name: +[MakerNotes, CIFF, Camera] 0 - Make: Canon +[MakerNotes, CIFF, Camera] 6 - Camera Model Name: Canon PowerShot A5 +[MakerNotes, CIFF, Camera] 4124 - Base ISO: 100 +[MakerNotes, CIFF, Camera] 2061 - ROM Operation Mode: USA +[MakerNotes, CIFF, Camera] 2059 - Canon Firmware Version: Firmware Version 01.00 +[MakerNotes, CIFF, Camera] 1 - Free Bytes: (Binary data 12 bytes) +[MakerNotes, CIFF, Image] 0 - Focal Type: Fixed +[MakerNotes, CIFF, Image] 1 - Focal Length: 5 mm +[MakerNotes, CIFF, Image] 2 - Focal Plane X Size: 5.05 mm +[MakerNotes, CIFF, Image] 3 - Focal Plane Y Size: 3.71 mm +[MakerNotes, Qualcomm, Camera] aec_current_sensor_luma - AEC Current Sensor Luma: 22 +[MakerNotes, Qualcomm, Camera] af_position - AF Position: 26 +[MakerNotes, Qualcomm, Camera] aec_current_exp_index - AEC Current Exp Index: 308 +[MakerNotes, Qualcomm, Camera] awb_sample_decision - AWB Sample Decision: 7 +[MakerNotes, Qualcomm, Camera] asf5_enable - ASF5 Enable: 1 +[MakerNotes, Qualcomm, Camera] asf5_filter_mode - ASF5 Filter Mode: 0 +[MakerNotes, Qualcomm, Camera] asf5_exposure_index_1 - ASF5 Exposure Index 1: 180 +[MakerNotes, Qualcomm, Camera] asf5_exposure_index_2 - ASF5 Exposure Index 2: 270 +[MakerNotes, Qualcomm, Camera] asf5_max_exposure_index - ASF5 Max Exposure Index: 290 +[MakerNotes, Samsung, Other] 0x0100-name - Embedded Audio File Name: SoundShot_000 +[MakerNotes, Samsung, Audio] 0x0100 - Embedded Audio File: (Binary data 16 bytes) +[APP0, AVI1, Image] 0 - Interleaved Field: Not Interleaved +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 4.73 +[XMP, XMP-rdf, Document] about - About: uuid:80056b68-1045-fa97-3401-6f4ed84cd53d +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Image] Category - Category: 1 +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image] Headline - Headline: No headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Author] Source - Source: I'm the source +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference? +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:06:03 00:00:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple Computer Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Adobe Systems Inc. +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright 1999 Adobe Systems Incorporated +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Adobe RGB (1998) +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.60974 0.31111 0.01947 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.20528 0.62567 0.06087 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14919 0.06322 0.74457 +[FlashPix, FlashPix, Other] 1 - Code Page: Unicode UTF-16, little endian +[FlashPix, FlashPix, Other] 268435456 - Used Extension Numbers: 1 +[FlashPix, FlashPix, Other] 1 - Extension Name: Screen nail +[FlashPix, FlashPix, Other] 2 - Extension Class ID: 10000230-6FC0-11D0-BD01-00609719A180 +[FlashPix, FlashPix, Other] 3 - Extension Persistence: Invalidated By Modification +[FlashPix, FlashPix, Time] 4 - Extension Create Date: 1999:05:14 21:47:25 +[FlashPix, FlashPix, Time] 5 - Extension Modify Date: 1999:05:14 21:47:25 +[FlashPix, FlashPix, Other] 6 - Creating Application: Digita +[FlashPix, FlashPix, Other] 7 - Extension Description: Presized image for LCD display +[FlashPix, FlashPix, Other] 4096 - Storage-Stream Pathname: /.Screen Nail_bd0100609719a180 +[FlashPix, FlashPix, Other] Screen Nail - Screen Nail: (Binary data 5917 bytes) +[FlashPix, FlashPix, Preview] FlashPix-PreviewImage - Preview Image: (Binary data 5777 bytes) +[MPF, MPF0, Image] 45056 - MPF Version: 0100 +[MPF, MPF0, Image] 45057 - Number Of Images: 2 +[MPF, MPImage1, Image] 0.1 - MP Image Flags: Dependent parent image +[MPF, MPImage1, Image] 0.2 - MP Image Format: JPEG +[MPF, MPImage1, Image] 0.3 - MP Image Type: Baseline MP Primary Image +[MPF, MPImage1, Image] 4 - MP Image Length: 5959981 +[MPF, MPImage1, Image] 8 - MP Image Start: 0 +[MPF, MPImage1, Image] 12 - Dependent Image 1 Entry Number: 2 +[MPF, MPImage1, Image] 14 - Dependent Image 2 Entry Number: 0 +[MPF, MPImage2, Image] 0.1 - MP Image Flags: Dependent child image +[MPF, MPImage2, Image] 0.2 - MP Image Format: JPEG +[MPF, MPImage2, Image] 0.3 - MP Image Type: Large Thumbnail (full HD equivalent) +[MPF, MPImage2, Image] 4 - MP Image Length: 1039273 +[MPF, MPImage2, Image] 8 - MP Image Start: 5945405 +[MPF, MPImage2, Image] 12 - Dependent Image 1 Entry Number: 0 +[MPF, MPImage2, Image] 14 - Dependent Image 2 Entry Number: 0 +[MPF, MPImage2, Preview] PreviewImage - Preview Image: (Binary data 1039273 bytes) +[Meta, MetaIFD, Image] 50000 - Film Product Code: 37 +[Meta, MetaIFD, Image] 50001 - Image Source EK: 1 +[Meta, MetaIFD, Image] 50002 - Capture Conditions PAR: 1 +[Meta, MetaIFD, Image] 50009 - Frame Number: 0 +[Meta, MetaIFD, Image] 50010 - Film Category: 2 +[Meta, MetaIFD, Image] 50011 - Film Gencode: 2 +[Meta, MetaIFD, Image] 50012 - Model And Version: Version 9 +[Meta, MetaIFD, Image] 50013 - Film Size: 1 +[Meta, MetaIFD, Image] 50014 - SBA RGB Shifts: 0 0 0 +[Meta, MetaIFD, Image] 50015 - SBA Input Image Colorspace: 3 +[Meta, MetaIFD, Image] 50016 - SBA Input Image Bit Depth: 12 12 12 +[Meta, MetaIFD, Image] 50017 - SBA Exposure Record: (Binary data 368 bytes) +[Meta, MetaIFD, Image] 50018 - User Adj SBA RGB Shifts: (Binary data 5 bytes) +[Meta, MetaIFD, Image] 50019 - Image Rotation Status: 0 +[Meta, MetaIFD, Image] 50020 - Roll Guid Elements: 00000000000000000000000000000000 +[Meta, MetaIFD, Image] 50021 - Metadata Number: 0100 +[APP5, RMETA, Image] Sign type - Sign Type: Information +[APP5, RMETA, Image] Location - Location: Roundabout +[APP5, RMETA, Image] Lit - Lit: No +[APP5, RMETA, Image] Condition - Condition: Good +[APP5, RMETA, Image] Azimuth - Azimuth: E +[XML, MediaJukebox, Image] Tool_Name - Tool Name: Media Center +[XML, MediaJukebox, Image] Tool_Version - Tool Version: 19.0.67 +[XML, MediaJukebox, Image] People - People: Santa +[XML, MediaJukebox, Image] Places - Places: Jamaica +[XML, MediaJukebox, Time] Date - Date: 2013:09:01 20:12:19 +[XML, MediaJukebox, Image] Album - Album: 2013-09-01 +[XML, MediaJukebox, Image] Name - Name: Glass home at night +[APP11, JPEG-HDR, Image] ver - JPEG-HDR Version: 11 +[APP11, JPEG-HDR, Image] ln0 - Ln0: 0.122262 +[APP11, JPEG-HDR, Image] ln1 - Ln1: 2.634655 +[APP11, JPEG-HDR, Image] s2n - S2n: 2.269635e+03 +[APP11, JPEG-HDR, Image] alp - Alpha: 1.000000 +[APP11, JPEG-HDR, Image] bet - Beta: 1.000000 +[APP11, JPEG-HDR, Image] cor - Correction Method: 0 +[APP11, JPEG-HDR, Preview] RatioImage - Ratio Image: (Binary data 19 bytes) +[JUMBF, JUMBF, Image] type - JUMD Type: (cacb)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: cai +[JUMBF, JUMBF, Image] type - JUMD Type: (cast)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: cb.reuters_1 +[JUMBF, JUMBF, Image] type - JUMD Type: (caas)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: cai.assertions +[JUMBF, JUMBF, Image] type - JUMD Type: (json)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: adobe.asset.info +[JUMBF, JSON, Other] title - Title: HEALTHCORONAVIRUSUSAOREGON_SALEM_08.jpg +[JUMBF, JUMBF, Image] type - JUMD Type: (json)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: cai.location.broad +[JUMBF, JSON, Other] location - Location: Salem, Oregon +[JUMBF, JUMBF, Image] type - JUMD Type: (json)-0011-0010-800000aa00389b71 +[JUMBF, JUMBF, Image] label - JUMD Label: cai.rights +[JUMBF, JSON, Other] copyright - Copyright: Alisha Jucevic +[APP12, PictureInfo, Time] TimeDate - Date/Time Original: 1998:12:31 15:17:20 +[APP12, PictureInfo, Image] Shutter - Exposure Time: 1/155 +[APP12, PictureInfo, Image] Flash - Flash: Off +[APP12, PictureInfo, Image] Resolution - Resolution: 5 +[APP12, PictureInfo, Image] Protect - Protect: 0 +[APP12, PictureInfo, Image] ContTake - Cont Take: 0 +[APP12, PictureInfo, Image] ImageSize - Image Size: 1280x960 +[APP12, PictureInfo, Image] ColorMode - Color Mode: 1 +[APP12, PictureInfo, Image] FNumber - F Number: 11.0 +[APP12, PictureInfo, Image] Zoom - Zoom: 2.1 +[APP12, PictureInfo, Image] Macro - Macro: Off +[APP12, PictureInfo, Image] LightS - Light S: 0 +[APP12, PictureInfo, Image] ExpBias - Exposure Compensation: +2.0 +[APP12, PictureInfo, Camera] Type - Camera Type: SR84 +[APP12, PictureInfo, Camera] Serial# - Serial Number: #00000001 +[APP12, PictureInfo, Camera] Version - Version: v84-71 +[APP12, PictureInfo, Camera] ID - ID: AGFA DIGITAL CAMERA +[APP12, PictureInfo, Image] PicLen - Pic Len: 561039 +[APP12, PictureInfo, Image] ThmLen - Thm Len: 3802 +[APP12, PictureInfo, Image] Q - Tag Q: 96 +[APP12, PictureInfo, Image] R - Tag R: 293 +[APP12, PictureInfo, Image] B - Tag B: 332 +[APP12, PictureInfo, Image] s0 - S0: 1e8,0,11b0,6f72,15cf,4225,4225,1050000,a1e0004,0,2f0030d,2f102c5,2880090,0,0 +[APP12, PictureInfo, Image] T0 - T0: 11b15600,1290000,e00c0f,2,0,0 +[Ducky, Ducky, Image] 1 - Quality: 84% +[Ducky, Ducky, Author] 3 - Copyright: Copyright 2004 Phil Harvey +[IPTC, IPTC, Other] 0 - Application Record Version: 2 +[IPTC, IPTC, Other] 120 - Caption-Abstract: A witty caption +[IPTC, IPTC, Author] 122 - Writer-Editor: I wrote it +[IPTC, IPTC, Other] 105 - Headline: No headline +[IPTC, IPTC, Other] 40 - Special Instructions: What instructions +[IPTC, IPTC, Author] 80 - By-line: Phil Harvey +[IPTC, IPTC, Author] 85 - By-line Title: My Position +[IPTC, IPTC, Author] 110 - Credit: My Credit +[IPTC, IPTC, Other] 5 - Object Name: Test IPTC picture +[IPTC, IPTC, Time] 55 - Date Created: 2004:02:26 +[IPTC, IPTC, Location] 90 - City: Kingston +[IPTC, IPTC, Location] 95 - Province-State: Ont +[IPTC, IPTC, Location] 101 - Country-Primary Location Name: Canada +[IPTC, IPTC, Other] 103 - Original Transmission Reference: What is a transmission reference +[IPTC, IPTC, Other] 15 - Category: 1 +[IPTC, IPTC, Other] 20 - Supplemental Categories: amazing, image, utilities +[IPTC, IPTC, Author] 116 - Copyright Notice: Copyright 2004 Phil Harvey +[IPTC, IPTC, Other] 10 - Urgency: 8 (least urgent) +[IPTC, IPTC, Author] 115 - Source: I'm the source +[IPTC, IPTC, Other] 25 - Keywords: jambalaya +[IPTC, IPTC2, Other] 0 - Application Record Version: 2 +[IPTC, IPTC3, Other] 0 - Application Record Version: 2 +[IPTC, IPTC3, Other] 15 - Category: p +[IPTC, IPTC3, Time] 55 - Date Created: 2005:12:23 +[IPTC, IPTC3, Other] 5 - Object Name: object name +[IPTC, IPTC3, Other] 10 - Urgency: 2 +[IPTC, IPTC3, Other] 20 - Supplemental Categories: supp cat +[IPTC, IPTC3, Other] 40 - Special Instructions: special instructions +[IPTC, IPTC3, Author] 80 - By-line: byline +[IPTC, IPTC3, Author] 85 - By-line Title: byline title +[IPTC, IPTC3, Location] 90 - City: city +[IPTC, IPTC3, Location] 101 - Country-Primary Location Name: country name +[IPTC, IPTC3, Other] 103 - Original Transmission Reference: otr +[IPTC, IPTC3, Other] 105 - Headline: headline +[IPTC, IPTC3, Author] 110 - Credit: credit +[IPTC, IPTC3, Author] 115 - Source: source +[IPTC, IPTC3, Author] 116 - Copyright Notice: copy freely +[IPTC, IPTC3, Other] 120 - Caption-Abstract: ExifTool AFCP test +[IPTC, IPTC3, Author] 122 - Writer-Editor: caption writer +[IPTC, IPTC3, Location] 95 - Province-State: state +[IPTC, IPTC3, Other] 25 - Keywords: jambalaya +[Photoshop, Photoshop, Image] 1061 - IPTC Digest: 05ad1770b1a95f1f9788ac995fa647da +[Photoshop, Photoshop, Image] 0 - X Resolution: 72 +[Photoshop, Photoshop, Image] 2 - Displayed Units X: inches +[Photoshop, Photoshop, Image] 4 - Y Resolution: 72 +[Photoshop, Photoshop, Image] 6 - Displayed Units Y: inches +[Photoshop, Photoshop, Image] 0 - Print Style: Centered +[Photoshop, Photoshop, Image] 2 - Print Position: 0 0 +[Photoshop, Photoshop, Image] 10 - Print Scale: 1 +[Photoshop, Photoshop, Image] 1037 - Global Angle: 30 +[Photoshop, Photoshop, Image] 1049 - Global Altitude: 30 +[Photoshop, Photoshop, Author] 1034 - Copyright Flag: False +[Photoshop, Photoshop, Author] 1035 - URL: https://exiftool.org/ +[Photoshop, Photoshop, Image] 1054 - URL List: +[Photoshop, Photoshop, Other] 20 - Slices Group Name: IPTC +[Photoshop, Photoshop, Other] 24 - Num Slices: 1 +[Photoshop, Photoshop, Image] 4 - Has Real Merged Data: Yes +[Photoshop, Photoshop, Image] 5 - Writer Name: Adobe Photoshop +[Photoshop, Photoshop, Image] 9 - Reader Name: Adobe Photoshop 7.0 +[Photoshop, Photoshop, Image] 0 - Photoshop Quality: 7 +[Photoshop, Photoshop, Image] 1 - Photoshop Format: Standard +[APP13, AdobeCM, Image] 0 - Adobe CM Type: 3 +[APP14, Adobe, Image] 0 - DCT Encode Version: 100 +[APP14, Adobe, Image] 1 - APP14 Flags 0: (none) +[APP14, Adobe, Image] 2 - APP14 Flags 1: (none) +[APP14, Adobe, Image] 3 - Color Transform: YCbCr +[APP15, GraphConv, Image] Q - Quality: 70 +[MIE, MIE-Doc, Author] Copyright - Copyright: © 2006 Phil Harvey +[MIE, MIE-Main, Other] zmie - Trailer Signature: +[PhotoMechanic, PhotoMechanic, Image] 221 - Tagged: Yes +[PhotoMechanic, PhotoMechanic, Image] 222 - Color Class: 6 (Typical alt) +[PhotoMechanic, PhotoMechanic, Image] 216 - Rotation: 180 +[PhotoMechanic, PhotoMechanic, Image] 217 - Crop Left: 438 +[PhotoMechanic, PhotoMechanic, Image] 218 - Crop Top: 618 +[PhotoMechanic, PhotoMechanic, Image] 219 - Crop Right: 890 +[PhotoMechanic, PhotoMechanic, Image] 220 - Crop Bottom: 1072 +[CanonVRD, CanonVRD, Image] 2 - VRD Version: 1.0.0 +[CanonVRD, CanonVRD, Image] 6 - WB Adj RGGB Levels: 0 0 0 0 +[CanonVRD, CanonVRD, Image] 24 - White Balance Adj: Shot Settings +[CanonVRD, CanonVRD, Image] 26 - WB Adj Color Temp: 5600 +[CanonVRD, CanonVRD, Image] 36 - WB Fine Tune Active: No +[CanonVRD, CanonVRD, Image] 40 - WB Fine Tune Saturation: 0 +[CanonVRD, CanonVRD, Image] 44 - WB Fine Tune Tone: 0 +[CanonVRD, CanonVRD, Image] 46 - Raw Color Adj: Shot Settings +[CanonVRD, CanonVRD, Image] 48 - Raw Custom Saturation: 0 +[CanonVRD, CanonVRD, Image] 52 - Raw Custom Tone: 0 +[CanonVRD, CanonVRD, Image] 56 - Raw Brightness Adj: 0.00 +[CanonVRD, CanonVRD, Image] 60 - Tone Curve Property: Shot Settings +[CanonVRD, CanonVRD, Image] 122 - Dynamic Range Min: 0 +[CanonVRD, CanonVRD, Image] 124 - Dynamic Range Max: 4095 +[CanonVRD, CanonVRD, Image] 272 - Tone Curve Active: No +[CanonVRD, CanonVRD, Image] 275 - Tone Curve Mode: RGB +[CanonVRD, CanonVRD, Image] 276 - Brightness Adj: 0 +[CanonVRD, CanonVRD, Image] 277 - Contrast Adj: 0 +[CanonVRD, CanonVRD, Image] 278 - Saturation Adj: 100 +[CanonVRD, CanonVRD, Image] 286 - Color Tone Adj: 0 +[CanonVRD, CanonVRD, Image] 294 - Luminance Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 336 - Luminance Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 345 - Tone Curve Interpolation: Curve +[CanonVRD, CanonVRD, Image] 352 - Red Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 394 - Red Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 410 - Green Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 452 - Green Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 468 - Blue Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 510 - Blue Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 526 - RGB Curve Points: (0,0) (255,255) +[CanonVRD, CanonVRD, Image] 568 - RGB Curve Limits: 255 0 255 0 +[CanonVRD, CanonVRD, Image] 580 - Crop Active: No +[CanonVRD, CanonVRD, Image] 582 - Crop Left: 0 +[CanonVRD, CanonVRD, Image] 584 - Crop Top: 0 +[CanonVRD, CanonVRD, Image] 586 - Crop Width: 0 +[CanonVRD, CanonVRD, Image] 588 - Crop Height: 0 +[CanonVRD, CanonVRD, Image] 602 - Sharpness Adj: 0 +[CanonVRD, CanonVRD, Image] 608 - Crop Aspect Ratio: Free +[CanonVRD, CanonVRD, Image] 610 - Constrained Crop Width: 0 +[CanonVRD, CanonVRD, Image] 614 - Constrained Crop Height: 0 +[CanonVRD, CanonVRD, Image] 618 - Check Mark: Clear +[CanonVRD, CanonVRD, Image] 622 - Rotation: 90 +[CanonVRD, CanonVRD, Image] 624 - Work Color Space: sRGB +[FotoStation, FotoStation, Image] 0 - Original Image Width: 16 +[FotoStation, FotoStation, Image] 1 - Original Image Height: 16 +[FotoStation, FotoStation, Image] 2 - Color Planes: 3 +[FotoStation, FotoStation, Image] 3 - XY Resolution: 9 +[FotoStation, FotoStation, Image] 4 - Rotation: 0 +[FotoStation, FotoStation, Image] 6 - Crop Left: 24.557% +[FotoStation, FotoStation, Image] 7 - Crop Top: 21.25% +[FotoStation, FotoStation, Image] 8 - Crop Right: 30.676% +[FotoStation, FotoStation, Image] 9 - Crop Bottom: 86.25% +[FotoStation, FotoStation, Image] 11 - Crop Rotation: 0 +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/155 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-LightValue - Light Value: 10.9 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 6.9 +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.004 mm +[Composite, Composite, Image] Exif-FOV - Field Of View: 47.0 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.0 mm (35 mm equivalent: 41.4 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 2.36 m diff --git a/ExifTool/t/Writer_52.out b/ExifTool/t/Writer_52.out new file mode 100644 index 0000000..71d8a00 --- /dev/null +++ b/ExifTool/t/Writer_52.out @@ -0,0 +1,23 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: Writer_52_failed.xmp +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 1638 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:11:01 10:15:50-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:11:01 10:15:50-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:11:01 10:15:50-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 11.75 +[XMP, XMP-dc, Author] rights - Rights: © Copyright Phil Harvey +[XMP, XMP-dc, Author] rights-fr - Rights (fr): © Droit d'auteur Phil Harvey +[XMP, XMP-exif, Time] DateTimeDigitized - Date/Time Digitized: 2005:06:09 20:09:27+02:00 +[XMP, XMP-exif, Time] DateTimeOriginal - Date/Time Original: 2005:06:09 20:09:27+02:00 +[XMP, XMP-xmp, Time] CreateDate - Create Date: 2005:11:21 17:07:14+01:00 +[XMP, XMP-xmp, Time] MetadataDate - Metadata Date: 2005:11:21 17:11:31+01:00 +[XMP, XMP-xmp, Time] ModifyDate - Modify Date: 2005:11:21 17:07:14+01:00 +[XMP, XMP-xmpMM, Other] DerivedFromDocumentID - Derived From Document ID: adobe:docid:photoshop:f481cc67-d8f5-11d9-a31e-a1606d941d83 +[XMP, XMP-xmpMM, Other] DerivedFromInstanceID - Derived From Instance ID: adobe:docid:photoshop:f481cc67-d8f5-11d9-a31e-a1606d941d83 +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: uuid:7A9636BAA85ADA11B611C7FA524F1F71 +[XMP, XMP-xmpMM, Other] InstanceID - Instance ID: uuid:7B9636BAA85ADA11B611C7FA524F1F71 diff --git a/ExifTool/t/Writer_53.out b/ExifTool/t/Writer_53.out new file mode 100644 index 0000000..689c6c9 --- /dev/null +++ b/ExifTool/t/Writer_53.out @@ -0,0 +1,36 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_53_failed.xmp +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.6 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:58-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:58-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:58-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0232 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: Uncalibrated +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-aux, Camera] Lens - Lens: 18.0 - 55.0 mm +[XMP, XMP-tiff, Image] BitsPerSample - Bits Per Sample: 8 +[XMP, XMP-tiff, Image] ImageLength - Image Height: 8 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 8 +[XMP, XMP-tiff, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-LensID-2 - Lens ID: 18.0-55.0mm diff --git a/ExifTool/t/Writer_54.out b/ExifTool/t/Writer_54.out new file mode 100644 index 0000000..ba593ee --- /dev/null +++ b/ExifTool/t/Writer_54.out @@ -0,0 +1,2 @@ +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:04 06:46:52 diff --git a/ExifTool/t/Writer_55.out b/ExifTool/t/Writer_55.out new file mode 100644 index 0000000..6ec55d4 --- /dev/null +++ b/ExifTool/t/Writer_55.out @@ -0,0 +1,7 @@ +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Author] 315 - Artist: me +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[IPTC, IPTC, Other] 25 - Keywords: one, two +[IPTC, IPTC, Other] 0 - Application Record Version: 4 diff --git a/ExifTool/t/Writer_56.out b/ExifTool/t/Writer_56.out new file mode 100644 index 0000000..2bf4a7a --- /dev/null +++ b/ExifTool/t/Writer_56.out @@ -0,0 +1,4 @@ +[IPTC, IPTC, Other] 25 - Keywords: one, two +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 9.50 +[XMP, XMP-dc, Image] title - Title: Test diff --git a/ExifTool/t/Writer_58.out b/ExifTool/t/Writer_58.out new file mode 100644 index 0000000..4c5017b --- /dev/null +++ b/ExifTool/t/Writer_58.out @@ -0,0 +1,44 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[File, System, Other] FileName - File Name: Writer_58_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 761 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:07:29 09:04:26-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2020:07:29 09:04:26-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:07:29 09:04:26-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Nikon Corporation +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.2.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1999:12:07 18:59:22 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Apple Computer Inc. +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: none +[ICC_Profile, ICC-header, Image] 52 - Device Model: +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: Nikon Adobe RGB 4.0.0.3000 +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.60976 0.31113 0.01947 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.20525 0.62566 0.06087 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.1492 0.06322 0.74463 +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 14 bytes) +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.9505 1 1.0891 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Nikon Inc. & Nikon Corporation 2001 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/Writer_59.out b/ExifTool/t/Writer_59.out new file mode 100644 index 0000000..0a8ef98 --- /dev/null +++ b/ExifTool/t/Writer_59.out @@ -0,0 +1,2 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 11.71 +[XMP, XMP-dc, Image] subject - Subject: ;a;; diff --git a/ExifTool/t/Writer_6.out b/ExifTool/t/Writer_6.out new file mode 100644 index 0000000..b621dc7 --- /dev/null +++ b/ExifTool/t/Writer_6.out @@ -0,0 +1,186 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_6_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 5.8 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:53-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:53-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:53-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 27e2cf0c32f8cb445eccf7006bfc9af5 +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Rotate 180 +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:04 06:46:52 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 4 +[EXIF, ExifIFD, Image] 33437 - F Number: 14.0 +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 9 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 14.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: +999 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37384 - Light Source: Cloudy +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 34.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 160 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 120 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: THM - DCF thumbnail file +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, InteropIFD, Image] 4097 - Related Image Width: 3072 +[EXIF, InteropIFD, Image] 4098 - Related Image Height: 2048 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3443.946188 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3442.016807 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: mm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, IFD0, Time] 36867 - Date/Time Original: 2005:01:01 00:00:00 +[EXIF, IFD0, Image] 51043 - Time Codes: 02:53:49.07 2009-11-19T12:38:35.21-03:00 +[MakerNotes, Canon, Camera] 1 - Macro Mode: Unknown (0) +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Continuous +[MakerNotes, Canon, Camera] 7 - Focus Mode: Manual Focus (3) +[MakerNotes, Canon, Camera] 9 - Record Mode: CRW+THM +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: Unknown (-1) +[MakerNotes, Canon, Camera] 13 - Contrast: +1 +[MakerNotes, Canon, Camera] 14 - Saturation: +1 +[MakerNotes, Canon, Camera] 15 - Sharpness: +1 +[MakerNotes, Canon, Camera] 16 - Camera ISO: n/a +[MakerNotes, Canon, Camera] 17 - Metering Mode: Center-weighted average +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Manual +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 4 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 27 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 3072 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 3072 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Image] 1 - Focal Length: 34 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.22 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.49 mm +[MakerNotes, Canon, Camera] 3 - Canon Flash Info: 100 0 0 0 +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 100 +[MakerNotes, Canon, Image] 3 - Measured EV: -1.25 +[MakerNotes, Canon, Image] 4 - Target Aperture: 14 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 19 - Focus Distance Upper: inf +[MakerNotes, Canon, Image] 20 - Focus Distance Lower: 5.46 m +[MakerNotes, Canon, Image] 21 - F Number: 14 +[MakerNotes, Canon, Image] 22 - Exposure Time: 128 +[MakerNotes, Canon, Image] 23 - Measured EV 2: -1.25 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 4 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: CRW:EOS DIGITAL REBEL CMOS RAW +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware Version 1.1.1 +[MakerNotes, Canon, Camera] 12 - Serial Number: 0560018150 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 1 +[MakerNotes, Canon, Image] 8 - File Number: 118-1861 +[MakerNotes, Canon, Camera] 9 - Owner Name: Phil Harvey +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS Digital Rebel / 300D / Kiss Digital +[MakerNotes, Canon, Image] 14 - Canon File Length: 4480822 +[MakerNotes, Canon, Camera] 0 - Canon 0x0000: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Canon, Camera] 192 - Canon 0x00c0: 26 331 372 372 177 240 428 429 277 186 510 511 442 +[MakerNotes, Canon, Camera] 193 - Canon 0x00c1: 26 299 375 375 170 202 394 395 240 153 453 454 375 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 998 1022 1026 808 +[MakerNotes, Canon, Camera] 168 - Canon 0x00a8: 20 5190 5190 7000 5987 3214 3897 6203 5190 5200 +[MakerNotes, Canon, Camera] 1 - WB RGGB Levels Auto: 1719 832 831 990 +[MakerNotes, Canon, Camera] 5 - WB RGGB Levels Daylight: 1722 832 831 989 +[MakerNotes, Canon, Camera] 9 - WB RGGB Levels Shade: 2035 832 831 839 +[MakerNotes, Canon, Camera] 13 - WB RGGB Levels Cloudy: 1878 832 831 903 +[MakerNotes, Canon, Camera] 17 - WB RGGB Levels Tungsten: 1228 913 912 1668 +[MakerNotes, Canon, Camera] 21 - WB RGGB Levels Fluorescent: 1506 842 841 1381 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels Flash: 1933 832 831 895 +[MakerNotes, Canon, Camera] 29 - WB RGGB Levels Custom: 1722 832 831 989 +[MakerNotes, Canon, Camera] 33 - WB RGGB Levels Kelvin: 1722 832 831 988 +[MakerNotes, Canon, Camera] 37 - WB RGGB Black Levels: 124 123 124 123 +[MakerNotes, Canon, Camera] 174 - Color Temperature: 5200 +[MakerNotes, Canon, Camera] 180 - Color Space: sRGB +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3072 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2048 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3072 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2048 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 151 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 151 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 1014 608 0 0 0 -608 -1014 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: 0 0 -506 0 506 0 0 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: (none) +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 +[MakerNotes, Canon, Camera] 181 - Canon 0x00b5: 10 3 1 2048 1360 +[MakerNotes, Canon, Camera] 0 - Canon 0x0000: 0 0 0 0 0 0 3072000 892 2048000 595 65540 262146 +[IPTC, IPTC, Other] 15 - Category: IPT +[IPTC, IPTC, Other] 0 - Application Record Version: 4 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-dc, Image] description - Description: New description +[XMP, XMP-exif, Camera] Contrast - Contrast: High +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Continuous Shooting +[Composite, Composite, Camera] Canon-ISO - ISO: 100 +[Composite, Composite, Camera] Canon-Lens - Lens: 18.0 - 55.0 mm +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Bulb +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 1719 832 831 990 +[Composite, Composite, Image] Exif-Aperture - Aperture: 14.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 4 +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.190619 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown 18-55mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 5.6 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.067348 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 18.0 - 55.0 mm (35 mm equivalent: 28.6 - 87.4 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: inf (4.31 m - inf) +[Composite, Composite, Image] Exif-FOV - Field Of View: 36.9 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 34.0 mm (35 mm equivalent: 54.0 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 4.37 m diff --git a/ExifTool/t/Writer_60.out b/ExifTool/t/Writer_60.out new file mode 100644 index 0000000..24ff7b6 --- /dev/null +++ b/ExifTool/t/Writer_60.out @@ -0,0 +1,3 @@ +[XMP, XMP-dc, Author] contributor - Contributor: FUJIFILM +[XMP, XMP-dc, Image] subject - Subject: NIKON, Canon, FUJIFILM +[XMP, XMP-dc, Image] type - Type: Multi-segment, 0.000064 diff --git a/ExifTool/t/Writer_7.out b/ExifTool/t/Writer_7.out new file mode 100644 index 0000000..f59713b --- /dev/null +++ b/ExifTool/t/Writer_7.out @@ -0,0 +1,151 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:04 06:46:52 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 4 +[EXIF, ExifIFD, Image] 33437 - F Number: 14.0 +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 9 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 14.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Average +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 34.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 160 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 120 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: THM - DCF thumbnail file +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, InteropIFD, Image] 4097 - Related Image Width: 3072 +[EXIF, InteropIFD, Image] 4098 - Related Image Height: 2048 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3443.946188 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3442.016807 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[MakerNotes, Canon, Camera] 1 - Macro Mode: Unknown (0) +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Continuous +[MakerNotes, Canon, Camera] 7 - Focus Mode: Manual Focus (3) +[MakerNotes, Canon, Camera] 9 - Record Mode: CRW+THM +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: Unknown (-1) +[MakerNotes, Canon, Camera] 13 - Contrast: +1 +[MakerNotes, Canon, Camera] 14 - Saturation: +1 +[MakerNotes, Canon, Camera] 15 - Sharpness: +1 +[MakerNotes, Canon, Camera] 16 - Camera ISO: n/a +[MakerNotes, Canon, Camera] 17 - Metering Mode: Center-weighted average +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Manual +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 4 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 27 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 3072 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 3072 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Image] 1 - Focal Length: 34 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.22 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.49 mm +[MakerNotes, Canon, Camera] 3 - Canon Flash Info: 100 0 0 0 +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 100 +[MakerNotes, Canon, Image] 3 - Measured EV: -1.25 +[MakerNotes, Canon, Image] 4 - Target Aperture: 14 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 19 - Focus Distance Upper: inf +[MakerNotes, Canon, Image] 20 - Focus Distance Lower: 5.46 m +[MakerNotes, Canon, Image] 21 - F Number: 14 +[MakerNotes, Canon, Image] 22 - Exposure Time: 128 +[MakerNotes, Canon, Image] 23 - Measured EV 2: -1.25 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 4 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: CRW:EOS DIGITAL REBEL CMOS RAW +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware Version 1.1.1 +[MakerNotes, Canon, Camera] 12 - Serial Number: 0560018150 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 1 +[MakerNotes, Canon, Image] 8 - File Number: 118-1861 +[MakerNotes, Canon, Camera] 9 - Owner Name: Phil Harvey +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS Digital Rebel / 300D / Kiss Digital +[MakerNotes, Canon, Image] 14 - Canon File Length: 4480822 +[MakerNotes, Canon, Camera] 0 - Canon 0x0000: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +[MakerNotes, Canon, Camera] 192 - Canon 0x00c0: 26 331 372 372 177 240 428 429 277 186 510 511 442 +[MakerNotes, Canon, Camera] 193 - Canon 0x00c1: 26 299 375 375 170 202 394 395 240 153 453 454 375 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 998 1022 1026 808 +[MakerNotes, Canon, Camera] 168 - Canon 0x00a8: 20 5190 5190 7000 5987 3214 3897 6203 5190 5200 +[MakerNotes, Canon, Camera] 1 - WB RGGB Levels Auto: 1719 832 831 990 +[MakerNotes, Canon, Camera] 5 - WB RGGB Levels Daylight: 1722 832 831 989 +[MakerNotes, Canon, Camera] 9 - WB RGGB Levels Shade: 2035 832 831 839 +[MakerNotes, Canon, Camera] 13 - WB RGGB Levels Cloudy: 1878 832 831 903 +[MakerNotes, Canon, Camera] 17 - WB RGGB Levels Tungsten: 1228 913 912 1668 +[MakerNotes, Canon, Camera] 21 - WB RGGB Levels Fluorescent: 1506 842 841 1381 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels Flash: 1933 832 831 895 +[MakerNotes, Canon, Camera] 29 - WB RGGB Levels Custom: 1722 832 831 989 +[MakerNotes, Canon, Camera] 33 - WB RGGB Levels Kelvin: 1722 832 831 988 +[MakerNotes, Canon, Camera] 37 - WB RGGB Black Levels: 124 123 124 123 +[MakerNotes, Canon, Camera] 174 - Color Temperature: 5200 +[MakerNotes, Canon, Camera] 180 - Color Space: sRGB +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3072 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2048 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3072 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2048 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 151 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 151 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 1014 608 0 0 0 -608 -1014 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: 0 0 -506 0 506 0 0 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: (none) +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 +[MakerNotes, Canon, Camera] 181 - Canon 0x00b5: 10 3 1 2048 1360 +[MakerNotes, Canon, Camera] 0 - Canon 0x0000: 0 0 0 0 0 0 3072000 892 2048000 595 65540 262146 diff --git a/ExifTool/t/Writer_9.out b/ExifTool/t/Writer_9.out new file mode 100644 index 0000000..a805112 --- /dev/null +++ b/ExifTool/t/Writer_9.out @@ -0,0 +1,243 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: Writer_9_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 10 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:54-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Image] 270 - Image Description: +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon EOS DIGITAL REBEL +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: E775v1.3u +[EXIF, IFD0, Time] 306 - Modify Date: 2003:12:04 06:46:52 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 4 +[EXIF, ExifIFD, Image] 33437 - F Number: 14.0 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0221 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2003:12:04 06:46:52 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 14.0 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Center-weighted average +[EXIF, ExifIFD, Camera] 37384 - Light Source: Unknown +[EXIF, ExifIFD, Camera] 37385 - Flash: No Flash +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 34.0 mm +[EXIF, ExifIFD, Image] 37510 - User Comment: +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 160 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 120 +[EXIF, InteropIFD, Image] 1 - Interoperability Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Image] 2 - Interoperability Version: 0100 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3443.946154 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3442.016807 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard +[EXIF, ExifIFD, Camera] 41992 - Contrast: High +[EXIF, ExifIFD, Camera] 41993 - Saturation: High +[EXIF, ExifIFD, Camera] 41994 - Sharpness: Hard +[EXIF, ExifIFD, Image] 42032 - Owner Name: Phil Harvey +[EXIF, ExifIFD, Image] 42033 - Serial Number: 0560018150 +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 300 +[EXIF, IFD1, Image] 283 - Y Resolution: 300 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 2436 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 28 +[EXIF, IFD1, Preview] Exif-ThumbnailImage - Thumbnail Image: (Binary data 28 bytes) +[MakerNotes, Canon, Camera] 1 - Macro Mode: Unknown (0) +[MakerNotes, Canon, Camera] 2 - Self Timer: Off +[MakerNotes, Canon, Camera] 3 - Quality: RAW +[MakerNotes, Canon, Camera] 4 - Canon Flash Mode: Off +[MakerNotes, Canon, Camera] 5 - Continuous Drive: Continuous +[MakerNotes, Canon, Camera] 7 - Focus Mode: Manual Focus (3) +[MakerNotes, Canon, Camera] 9 - Record Mode: CRW+THM +[MakerNotes, Canon, Camera] 10 - Canon Image Size: Large +[MakerNotes, Canon, Camera] 11 - Easy Mode: Manual +[MakerNotes, Canon, Camera] 12 - Digital Zoom: Unknown (-1) +[MakerNotes, Canon, Camera] 13 - Contrast: +1 +[MakerNotes, Canon, Camera] 14 - Saturation: +1 +[MakerNotes, Canon, Camera] 15 - Sharpness: +1 +[MakerNotes, Canon, Camera] 16 - Camera ISO: n/a +[MakerNotes, Canon, Camera] 17 - Metering Mode: Center-weighted average +[MakerNotes, Canon, Camera] 18 - Focus Range: Not Known +[MakerNotes, Canon, Camera] 20 - Canon Exposure Mode: Manual +[MakerNotes, Canon, Camera] 22 - Lens Type: n/a +[MakerNotes, Canon, Camera] 23 - Max Focal Length: 55 mm +[MakerNotes, Canon, Camera] 24 - Min Focal Length: 18 mm +[MakerNotes, Canon, Camera] 25 - Focal Units: 1/mm +[MakerNotes, Canon, Camera] 26 - Max Aperture: 4 +[MakerNotes, Canon, Camera] 27 - Min Aperture: 27 +[MakerNotes, Canon, Camera] 28 - Flash Activity: 0 +[MakerNotes, Canon, Camera] 29 - Flash Bits: (none) +[MakerNotes, Canon, Camera] 36 - Zoom Source Width: 3072 +[MakerNotes, Canon, Camera] 37 - Zoom Target Width: 3072 +[MakerNotes, Canon, Camera] 41 - Manual Flash Output: n/a +[MakerNotes, Canon, Camera] 42 - Color Tone: Normal +[MakerNotes, Canon, Image] 1 - Focal Length: 34 mm +[MakerNotes, Canon, Image] 2 - Focal Plane X Size: 23.22 mm +[MakerNotes, Canon, Image] 3 - Focal Plane Y Size: 15.49 mm +[MakerNotes, Canon, Image] 1 - Auto ISO: 100 +[MakerNotes, Canon, Image] 2 - Base ISO: 100 +[MakerNotes, Canon, Image] 3 - Measured EV: -1.25 +[MakerNotes, Canon, Image] 4 - Target Aperture: 14 +[MakerNotes, Canon, Image] 6 - Exposure Compensation: 0 +[MakerNotes, Canon, Image] 7 - White Balance: Auto +[MakerNotes, Canon, Image] 8 - Slow Shutter: None +[MakerNotes, Canon, Image] 9 - Shot Number In Continuous Burst: 0 +[MakerNotes, Canon, Camera] 10 - Optical Zoom Code: n/a +[MakerNotes, Canon, Image] 13 - Flash Guide Number: 0 +[MakerNotes, Canon, Image] 15 - Flash Exposure Compensation: 0 +[MakerNotes, Canon, Image] 16 - Auto Exposure Bracketing: Off +[MakerNotes, Canon, Image] 17 - AEB Bracket Value: 0 +[MakerNotes, Canon, Image] 18 - Control Mode: Camera Local Control +[MakerNotes, Canon, Image] 19 - Focus Distance Upper: inf +[MakerNotes, Canon, Image] 20 - Focus Distance Lower: 5.46 m +[MakerNotes, Canon, Image] 21 - F Number: 14 +[MakerNotes, Canon, Image] 22 - Exposure Time: 128 +[MakerNotes, Canon, Image] 23 - Measured EV 2: -1.25 +[MakerNotes, Canon, Image] 24 - Bulb Duration: 4 +[MakerNotes, Canon, Camera] 26 - Camera Type: EOS Mid-range +[MakerNotes, Canon, Image] 27 - Auto Rotate: None +[MakerNotes, Canon, Image] 28 - ND Filter: n/a +[MakerNotes, Canon, Image] 29 - Self Timer 2: 0 +[MakerNotes, Canon, Image] 3 - Bracket Mode: Off +[MakerNotes, Canon, Image] 4 - Bracket Value: 0 +[MakerNotes, Canon, Image] 5 - Bracket Shot Number: 0 +[MakerNotes, Canon, Image] 6 - Canon Image Type: CRW:EOS DIGITAL REBEL CMOS RAW +[MakerNotes, Canon, Camera] 7 - Canon Firmware Version: Firmware Version 1.1.1 +[MakerNotes, Canon, Camera] 12 - Serial Number: 0560018150 +[MakerNotes, Canon, Camera] 21 - Serial Number Format: Format 1 +[MakerNotes, Canon, Image] 8 - File Number: 118-1861 +[MakerNotes, Canon, Camera] 9 - Owner Name: Phil Harvey +[MakerNotes, Canon, Camera] 16 - Canon Model ID: EOS Digital Rebel / 300D / Kiss Digital +[MakerNotes, Canon, Image] 14 - Canon File Length: 4480822 +[MakerNotes, Canon, Camera] 1 - Measured RGGB: 998 1022 1026 808 +[MakerNotes, Canon, Camera] 1 - WB RGGB Levels Auto: 1719 832 831 990 +[MakerNotes, Canon, Camera] 5 - WB RGGB Levels Daylight: 1722 832 831 989 +[MakerNotes, Canon, Camera] 9 - WB RGGB Levels Shade: 2035 832 831 839 +[MakerNotes, Canon, Camera] 13 - WB RGGB Levels Cloudy: 1878 832 831 903 +[MakerNotes, Canon, Camera] 17 - WB RGGB Levels Tungsten: 1228 913 912 1668 +[MakerNotes, Canon, Camera] 21 - WB RGGB Levels Fluorescent: 1506 842 841 1381 +[MakerNotes, Canon, Camera] 25 - WB RGGB Levels Flash: 1933 832 831 895 +[MakerNotes, Canon, Camera] 29 - WB RGGB Levels Custom: 1722 832 831 989 +[MakerNotes, Canon, Camera] 33 - WB RGGB Levels Kelvin: 1722 832 831 988 +[MakerNotes, Canon, Camera] 37 - WB RGGB Black Levels: 124 123 124 123 +[MakerNotes, Canon, Camera] 174 - Color Temperature: 5200 +[MakerNotes, Canon, Camera] 180 - Color Space: sRGB +[MakerNotes, Canon, Camera] 0 - Num AF Points: 7 +[MakerNotes, Canon, Camera] 1 - Valid AF Points: 7 +[MakerNotes, Canon, Image] 2 - Canon Image Width: 3072 +[MakerNotes, Canon, Image] 3 - Canon Image Height: 2048 +[MakerNotes, Canon, Camera] 4 - AF Image Width: 3072 +[MakerNotes, Canon, Camera] 5 - AF Image Height: 2048 +[MakerNotes, Canon, Camera] 6 - AF Area Width: 151 +[MakerNotes, Canon, Camera] 7 - AF Area Height: 151 +[MakerNotes, Canon, Camera] 8 - AF Area X Positions: 1014 608 0 0 0 -608 -1014 +[MakerNotes, Canon, Camera] 9 - AF Area Y Positions: 0 0 -506 0 506 0 0 +[MakerNotes, Canon, Camera] 10 - AF Points In Focus: (none) +[MakerNotes, Canon, Camera] 19 - Thumbnail Image Valid Area: 0 159 7 112 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-aux, Camera] Lens - Lens: 18.0 - 55.0 mm +[XMP, XMP-exif, Image] ComponentsConfiguration - Components Configuration: Y, Cb, Cr, - +[XMP, XMP-exif, Image] CompressedBitsPerPixel - Compressed Bits Per Pixel: 9 +[XMP, XMP-exif, Camera] FlashFired - Flash Fired: False +[XMP, XMP-exif, Camera] FlashFunction - Flash Function: False +[XMP, XMP-exif, Camera] FlashMode - Flash Mode: Unknown +[XMP, XMP-exif, Camera] FlashRedEyeMode - Flash Red Eye Mode: False +[XMP, XMP-exif, Camera] FlashReturn - Flash Return: No return detection +[XMP, XMP-exifEX, Image] InteroperabilityIndex - Interoperability Index: THM - DCF thumbnail file +[XMP, XMP-pmi, Image] sequenceNumber - Sequence Number: 0 +[XMP, XMP-tiff, Image] BitsPerSample - Bits Per Sample: 8 +[XMP, XMP-tiff, Image] ImageLength - Image Height: 8 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 8 +[XMP, XMP-tiff, Image] YCbCrPositioning - Y Cb Cr Positioning: Centered +[XMP, XMP-tiff, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[XMP, XMP-crs, Image] Temperature - Color Temperature: 5200 +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Linotronic +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1998:02:09 06:49:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Microsoft Corporation +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: Hewlett-Packard +[ICC_Profile, ICC-header, Image] 52 - Device Model: sRGB +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Hewlett-Packard +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright (c) 1998 Hewlett-Packard Company +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: sRGB IEC61966-2.1 +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.43607 0.22249 0.01392 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.38515 0.71687 0.09708 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14307 0.06061 0.7141 +[ICC_Profile, ICC_Profile, Camera] dmnd - Device Mfg Desc: IEC http://www.iec.ch +[ICC_Profile, ICC_Profile, Camera] dmdd - Device Model Desc: IEC 61966-2.1 Default RGB colour space - sRGB +[ICC_Profile, ICC_Profile, Image] vued - Viewing Cond Desc: Reference Viewing Condition in IEC61966-2.1 +[ICC_Profile, ICC-view, Image] 8 - Viewing Cond Illuminant: 19.6445 20.3718 16.8089 +[ICC_Profile, ICC-view, Image] 20 - Viewing Cond Surround: 3.92889 4.07439 3.36179 +[ICC_Profile, ICC-view, Image] 32 - Viewing Cond Illuminant Type: D50 +[ICC_Profile, ICC_Profile, Image] lumi - Luminance: 76.03647 80 87.12462 +[ICC_Profile, ICC-meas, Image] 8 - Measurement Observer: CIE 1931 +[ICC_Profile, ICC-meas, Image] 12 - Measurement Backing: 0 0 0 +[ICC_Profile, ICC-meas, Image] 24 - Measurement Geometry: Unknown +[ICC_Profile, ICC-meas, Image] 28 - Measurement Flare: 0.999% +[ICC_Profile, ICC-meas, Image] 32 - Measurement Illuminant: D65 +[ICC_Profile, ICC_Profile, Image] tech - Technology: Cathode Ray Tube Display +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 2060 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 2060 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 2060 bytes) +[Composite, Composite, Camera] Canon-DriveMode - Drive Mode: Continuous Shooting +[Composite, Composite, Camera] Canon-ISO - ISO: 100 +[Composite, Composite, Camera] Canon-Lens - Lens: 18.0 - 55.0 mm +[Composite, Composite, Camera] Canon-ShootingMode - Shooting Mode: Bulb +[Composite, Composite, Camera] Canon-WB_RGGBLevels - WB RGGB Levels: 1719 832 831 990 +[Composite, Composite, Image] Exif-Aperture - Aperture: 14.0 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 4 +[Composite, Composite, Camera] XMP-Flash - Flash: No Flash +[Composite, Composite, Camera] Exif-BlueBalance - Blue Balance: 1.190619 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Camera] Exif-LensID - Lens ID: Unknown 18-55mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 5.6 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-RedBalance - Red Balance: 2.067348 +[Composite, Composite, Camera] Exif-ScaleFactor35efl - Scale Factor To 35 mm Equivalent: 1.6 +[Composite, Composite, Camera] Canon-Lens35efl - Lens: 18.0 - 55.0 mm (35 mm equivalent: 27.9 - 85.3 mm) +[Composite, Composite, Camera] Exif-CircleOfConfusion - Circle Of Confusion: 0.019 mm +[Composite, Composite, Image] Exif-DOF - Depth Of Field: inf (4.21 m - inf) +[Composite, Composite, Image] Exif-FOV - Field Of View: 37.7 deg +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 34.0 mm (35 mm equivalent: 52.7 mm) +[Composite, Composite, Camera] Exif-HyperfocalDistance - Hyperfocal Distance: 4.26 m diff --git a/ExifTool/t/XMP.t b/ExifTool/t/XMP.t new file mode 100644 index 0000000..7167c18 --- /dev/null +++ b/ExifTool/t/XMP.t @@ -0,0 +1,702 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/XMP.t". + +BEGIN { + $| = 1; print "1..54\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# definitions for user-defined tag test (#26) +%Image::ExifTool::UserDefined = ( + 'Image::ExifTool::XMP::Main' => { + myXMPns => { + SubDirectory => { + TagTable => 'Image::ExifTool::UserDefined::myXMPns', + # (see the definition of this table below) + }, + }, + }, +); +use vars %Image::ExifTool::UserDefined::myXMPns; # avoid "typo" warning +%Image::ExifTool::UserDefined::myXMPns = ( + GROUPS => { 0 => 'XMP', 1 => 'XMP-myXMPns'}, + NAMESPACE => { 'myXMPns' => 'http://ns.exiftool.org/t/XMP.t' }, + WRITABLE => 'string', + ATestTag => { List => 'Bag', Resource => 1 }, + BTestTag => { + Struct => { + TYPE => 'myXMPns:SomeFunnyType', + Field1 => { Writable => 'lang-alt', List => 'Bag' }, + } + }, + BTestTagField1 => { Name => 'Renamed', Flat => 1 }, +); + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::XMP; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'XMP'; +my $testnum = 1; + +# test 2: Extract information from XMP.jpg +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/XMP.jpg', {Duplicates => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 3: Test rewriting everything with slightly different values +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Duplicates => 1, Binary => 1, ListJoin => undef); + my $info = $exifTool->ImageInfo('t/images/XMP.jpg'); + my $tag; + foreach $tag (keys %$info) { + my $group = $exifTool->GetGroup($tag); + my $val = $$info{$tag}; + if (ref $val eq 'ARRAY') { + push @$val, 'v2'; + } elsif (ref $val eq 'SCALAR') { + $val = 'v2'; + } elsif ($val =~ /^\d+(\.\d*)?$/) { + # (add extra .001 to avoid problem with aperture of 4.85 + # getting rounded to 4.8 or 4.9 and causing failed tests) + $val += ($val / 10) + 1.001; + $1 or $val = int($val); + } else { + $val .= '-v2'; + } + # eat return values so warning don't get printed + my @x = $exifTool->SetNewValue($tag, $val, Group=>$group, Replace=>1); + } + # also try writing a few specific tags + $exifTool->SetNewValue(CreatorCountry => 'Canada'); + $exifTool->SetNewValue(CodedCharacterSet => 'UTF8', Protected => 1); + undef $info; + my $image; + my $ok = writeInfo($exifTool,'t/images/XMP.jpg',\$image); + # this is effectively what the RHEL 3 UTF8 LANG problem does: + # $image = pack("U*", unpack("C*", $image)); + + my $exifTool2 = Image::ExifTool->new; + $exifTool2->Options(Duplicates => 1); + $info = $exifTool2->ImageInfo(\$image); + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + if (check($exifTool2, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + # save bad file + open(TESTFILE,">$testfile"); + binmode(TESTFILE); + print TESTFILE $image; + close(TESTFILE); + notOK(); + } + print "ok $testnum\n"; +} + +# tests 4/5: Test extracting then reading XMP data as a block +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/XMP.jpg','XMP'); + notOK() unless $$info{XMP}; + print "ok $testnum\n"; + + ++$testnum; + my $pass; + if ($$info{XMP}) { + $info = $exifTool->ImageInfo($$info{XMP}); + $pass = check($exifTool, $info, $testname, $testnum); + } + notOK() unless $pass; + print "ok $testnum\n"; +} + +# test 6: Test copying information to a new XMP data file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/XMP.jpg'); + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + my $ok = writeInfo($exifTool,undef,$testfile); + my $info = $exifTool->ImageInfo($testfile); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 7: Test rewriting CS2 XMP information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->SetNewValue(Label => 'Blue'); + $exifTool->SetNewValue(Rating => 3); + $exifTool->SetNewValue(Subject => q{char test: & > < ' "}, AddValue => 1); + $exifTool->SetNewValue('Rights' => "\xc2\xa9 Copyright Someone Else"); + my $ok = writeInfo($exifTool,'t/images/XMP.xmp',$testfile); + notOK() unless testCompare("t/XMP_$testnum.out",$testfile,$testnum) and $ok; + print "ok $testnum\n"; +} + +# test 8-11: Test reading/writing XMP with blank nodes and some problems that need correcting +{ + my $file; + foreach $file ('XMP2.xmp', 'XMP3.xmp') { + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo("t/images/$file", {Duplicates => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + + ++$testnum; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->SetNewValue('XMP:Creator' => 'Phil', AddValue => 1); + $exifTool->SetNewValue('manifestplacedXResolution' => 1); + $exifTool->SetNewValue('attributionname' => 'something else'); + $exifTool->WriteInfo("t/images/$file", $testfile); + my $err = $exifTool->GetValue('Error'); + warn "\n $err\n" if $err; + notOK() unless testCompare("t/XMP_$testnum.out",$testfile,$testnum); + print "ok $testnum\n"; + } +} + +# tests 12-17: Test writing/deleting XMP alternate languages +{ + my @writeList = ( + [ ['Rights-x-default' => "\xc2\xa9 Copyright Another One"] ], # should overwrite x-default only + [ ['Rights-de-DE' => "\xc2\xa9 Urheberrecht Phil Harvey"] ], # should create de-DE only + [ ['Rights-x-default' => undef] ], # should delete x-default only + [ ['Rights-fr' => undef] ], # should delete fr only + [ ['Title-fr' => 'Test fr title'] ],# should not create x-default + [ ['Title-fr' => 'Test fr title'], + ['Title-x-default' => 'dTitle'] ],# should create x-default before fr + ); + my $writeListRef; + foreach $writeListRef (@writeList) { + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + notOK() unless writeCheck($writeListRef, $testname, $testnum, + 't/images/XMP.xmp', ['XMP-dc:*']); + print "ok $testnum\n"; + } +} + +# test 18: Delete some family 1 XMP groups +{ + ++$testnum; + my @writeInfo = ( + [ 'xmp-xmpmm:all' => undef ], + [ 'XMP-PHOTOSHOP:all' => undef ], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, + 't/images/XMP.jpg', ['XMP:all']); + print "ok $testnum\n"; +} + +# test 19-20: Copy from XMP to EXIF with and without PrintConv enabled +{ + my $exifTool = Image::ExifTool->new; + while ($testnum < 20) { + ++$testnum; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValue(); + $exifTool->SetNewValuesFromFile('t/images/XMP.xmp', 'XMP:all>EXIF:all'); + my $ok = writeInfo($exifTool, "t/images/Writer.jpg", $testfile); + my $info = $exifTool->ImageInfo($testfile, 'EXIF:all'); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + $exifTool->Options(PrintConv => 0); + } +} + +# test 21-22: Copy from EXIF to XMP with and without PrintConv enabled +{ + my $exifTool = Image::ExifTool->new; + while ($testnum < 22) { + ++$testnum; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->SetNewValue(); + $exifTool->SetNewValuesFromFile('t/images/Canon.jpg', 'EXIF:* > XMP:*'); + my $ok = writeInfo($exifTool, undef, $testfile); + my $info = $exifTool->ImageInfo($testfile, 'XMP:*'); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + $exifTool->Options(PrintConv => 0); + } +} + +# test 23: Delete all tags except two specific XMP family 1 groups +{ + ++$testnum; + my @writeInfo = ( + [ 'all' => undef ], + [ 'xmp-dc:all' => undef, Replace => 2 ], + [ 'xmp-xmprights:all' => undef, Replace => 2 ], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, + 't/images/XMP.jpg', ['XMP:all'], undef, 1); + print "ok $testnum\n"; +} + +# test 24: Delete all tags except XMP +{ + ++$testnum; + my @writeInfo = ( + [ 'all' => undef ], + [ 'xmp:all' => undef, Replace => 2 ], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, + 't/images/XMP.jpg', ['-file:all'], undef, 1); + print "ok $testnum\n"; +} + +# test 25: Extract information from SVG image +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/XMP.svg', {Duplicates => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 26: Test creating a variety of XMP information +# (including x:xmptk, rdf:about and rdf:resource attributes) +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + my @writeInfo = ( + [ 'XMP-x:XMPToolkit' => "What's this?", Protected => 1 ], + [ 'XMP-rdf:About' => "http://www.exiftool.org/t/$testname.t#$testnum", Protected => 1 ], + [ 'XMP:ImageType' => 'Video' ], + [ 'LicenseeImageNotes-en' => 'english notes' ], + [ 'LicenseeImageNotes-de' => 'deutsche anmerkungen' ], + [ 'LicenseeImageNotes' => 'default notes' ], + [ 'LicenseeName' => 'Phil' ], + [ 'CopyrightStatus' => 'public' ], + [ 'Custom1-en' => 'a' ], + [ 'Custom1-en' => 'b' ], + [ 'ATestTag' => "http://www.exiftool.org/t/$testname.t#$testnum-one" ], + [ 'ATestTag' => "http://www.exiftool.org/t/$testname.t#$testnum-two" ], + ); + $exifTool->SetNewValue(@$_) foreach @writeInfo; + my $ok = writeInfo($exifTool, undef, $testfile); + notOK() unless testCompare("t/XMP_$testnum.out",$testfile,$testnum) and $ok; + print "ok $testnum\n"; +} + +# test 27: Extract information from exiftool RDF/XML output file +# (file created by application with this command line: +# "exiftool -X -D t/images/Nikon.jpg > t/image/XMP.xml") +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/XMP.xml', {Duplicates => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 28: Write information to exiftool RDF/XML output file +{ + ++$testnum; + my @writeInfo = ( + [ 'all' => undef ], + [ 'ifd0:all' => undef, Replace => 2 ], + [ 'XML-file:all' => undef, Replace => 2 ], + [ 'author' => 'Phil' ], + ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/XMP.xml'); + print "ok $testnum\n"; +} + +# test 29: Rewrite extended XMP segment +{ + ++$testnum; + my @writeInfo = ( [ 'author' => 'Test' ] ); + notOK() unless writeCheck(\@writeInfo, $testname, $testnum, 't/images/ExtendedXMP.jpg'); + print "ok $testnum\n"; +} + +# test 30: Test mass copy with deletion of specific XMP family 1 groups in shorthand format +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(XMPShorthand => 1); + my $testfile = "t/${testname}_${testnum}_failed.out"; + unlink $testfile; + $exifTool->SetNewValuesFromFile('t/images/XMP.jpg'); + $exifTool->SetNewValue('xmp-exif:all'); + $exifTool->SetNewValue('XMP-TIFF:*'); + $exifTool->WriteInfo(undef,$testfile,'XMP'); #(also test output file type option) + notOK() unless testCompare("t/XMP_$testnum.out",$testfile,$testnum); + print "ok $testnum\n"; +} + +# test 31: Extract structured information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/XMP4.xmp', {Struct => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# tests 32-34: Conditionally add XMP lang-alt tag +{ + # write title only if it doesn't exist + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile; + $exifTool->SetNewValue('XMP-dc:Title-de' => '', DelValue => 1); + $exifTool->SetNewValue('XMP-dc:Title-de' => 'A'); + my $ok = writeInfo($exifTool,'t/images/Writer.jpg',$testfile); + my $info = $exifTool->ImageInfo($testfile,'XMP:*'); + notOK() unless check($exifTool, $info, $testname, $testnum) and $ok; + print "ok $testnum\n"; + + # try again when title already exists + ++$testnum; + my $testfile2 = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile2; + $exifTool->SetNewValue('XMP-dc:Title-de' => 'B'); + $exifTool->WriteInfo($testfile,$testfile2); + $info = $exifTool->ImageInfo($testfile2,'XMP:*'); + if (check($exifTool, $info, $testname, $testnum, 32)) { + unlink $testfile2 + } else { + notOK(); + } + print "ok $testnum\n"; + + # one final time replacing an existing title + ++$testnum; + $testfile2 = "t/${testname}_${testnum}_failed.jpg"; + unlink $testfile2; + $exifTool->SetNewValue('XMP-dc:Title-de' => 'A', DelValue => 1); + $exifTool->SetNewValue('XMP-dc:Title-de' => 'C'); + $ok = writeInfo($exifTool,$testfile,$testfile2); + $info = $exifTool->ImageInfo($testfile2,'XMP:*'); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + unlink $testfile2 + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 35: Test various features of writing structured information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + my @writeInfo = ( + # write as flattened string + [ HierarchicalKeywords => '{keyWORD=A-1,childREN={keyword=A-2}}' ], + # write as HASH reference + [ HierarchicalKeywords => [{kEyWoRd=>'B-1', cHiLdReN=>{keyword=>'B-2'}},{keyword=>'C-1'}] ], + # write a type'd structure + [ licensee => {licenseename=>'Phil'} ], + # write a region, including a 'seeAlso' resource + [ 'RegionList', { + Area => {X=>0,Y=>0,W=>8,H=>8}, + Name => 'Region 1', + type => 'Face', + seeAlso => 'plus:Licensee', + }], + # write alternate language structure elements + [ ArtworkOrObject => "{AOTitle=test,aotitle-de=pr\xc3\xbcfung,AOTitle_FR=\xc3\xa9preuve}" ], + # disable print conversion for a single structure element + [ 'XMP:Flash' => '{Return=no,mode#=2}' ], + # write a complex user-defined lang-alt structure + [ BTestTag => "{Field1-en-CA=[eh?],Field1-en-US=[huh?,groovy],Field1-fr=[,ing\xc3\xa9nieux]}" ], + # write some dynamic structure elements + [ RegionList => { Extensions => { + # may mix-and-match flattened and structured tags when writing!... + 'XMP-exif:FlashReturn' => 'not', # flattened tag with group name + Flash => { 'Mode#' => 1 }, # structured tag with disabled conversion + 'UsageTerms-fr' => 'libre', # lang-alt tag + 'ArtworkTitle-de' => "verf\xc3\xa4nglich", # renamed lang-alt tag in a list + Renamed => 'this is wild', # user-defined renamed flattened tag with TYPE + }}], + ); + $exifTool->SetNewValue(@$_) foreach @writeInfo; + my $ok = writeInfo($exifTool,undef,$testfile); + notOK() unless testCompare("t/images/XMP5.xmp",$testfile,$testnum) and $ok; + print "ok $testnum\n"; +} + +# tests 36-37: Test reading structures with and without the Struct option +{ + my $i; + for ($i=0; $i<2; ++$i) { + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(Struct => 1 - $i); + $exifTool->Options(Escape => 'HTML'); # test escaping of structure fields too + my $info = $exifTool->ImageInfo("t/images/XMP5.xmp"); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +# test 38: Copy complex structured information +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->SetNewValuesFromFile('t/images/XMP5.xmp', 'xmp:all'); + my $ok = writeInfo($exifTool,undef,$testfile); + notOK() unless testCompare("t/images/XMP5.xmp",$testfile,$testnum) and $ok; + print "ok $testnum\n"; +} + +# test 39: Extract information from an INX file +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/XMP.inx', {Duplicates => 1}); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 40: Copy by flattened tag name and structure at the same time +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->SetNewValuesFromFile('t/images/XMP5.xmp', 'HierarchicalKeywords1', 'Licensee'); + my $ok = writeInfo($exifTool,undef,$testfile); + my $info = $exifTool->ImageInfo($testfile, 'XMP:*'); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 41: Rest writing/reading all DarwinCore tags +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->SetNewValue('xmp-dwc:*' => 2013); + my $ok = writeInfo($exifTool, undef, $testfile); + my $info = $exifTool->ImageInfo($testfile, {Duplicates => 1}); + if (check($exifTool, $info, $testname, $testnum) and $ok) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 42: Read extended XMP +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ExtendedXMP.jpg', 'xmp:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 43: Read XMP with unusual namespace prefixes +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/XMP6.xmp', 'xmp:all'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +# test 44: Write XMP with unusual namespace prefixes +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->SetNewValue('xmp-dc:subject' => 'changed'); + $exifTool->WriteInfo("t/images/XMP6.xmp", $testfile); + my $err = $exifTool->GetValue('Error'); + warn "\n $err\n" if $err; + notOK() unless testCompare("t/XMP_$testnum.out",$testfile,$testnum); + print "ok $testnum\n"; +} + +# test 45: Write empty structures +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->SetNewValue('regioninfo' => '{RegionList=[,]}'); + $exifTool->SetNewValue('xmp:flash' => '{}'); + $exifTool->WriteInfo(undef, $testfile); + notOK() unless testCompare("t/XMP_$testnum.out",$testfile,$testnum); + print "ok $testnum\n"; +} + +# test 46: Test the advanced-formatting '@' feature on an XMP:Subject list +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(ListSplit => ', '); + my $cpy = 'subject<${subject@;/^Test/ ? $_=undef : s/Tool$//}'; + $exifTool->SetNewValuesFromFile('t/images/XMP.jpg', $cpy); + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + writeInfo($exifTool, undef, $testfile); + $exifTool->Options(ListSep => ' // '); + my $info = $exifTool->ImageInfo($testfile, 'Subject'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# tests 47-49: Test replacing specific elements in list of structures +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->Options(ListSplit => ','); + $exifTool->Options(Struct => 1); + $exifTool->SetNewValue('LocationShownCity' => 'Manchester,Lyon,Frankfurt'); + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + writeInfo($exifTool, 't/images/XMP7.xmp', $testfile); + my $info = $exifTool->ImageInfo($testfile, 'XMP:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + + ++$testnum; + $exifTool->SetNewValue(); + $exifTool->SetNewValue('LocationShownCity' => 'London,Berlin', DelValue => 1); + $exifTool->SetNewValue('LocationShownCity' => 'Oxford,Munich'); + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + writeInfo($exifTool, 't/images/XMP7.xmp', $testfile); + $info = $exifTool->ImageInfo($testfile, 'XMP:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; + + ++$testnum; + $exifTool->SetNewValue(); + $exifTool->SetNewValue('AboutCvTermName' => 'one,three', DelValue => 1); + $exifTool->SetNewValue('AboutCvTermName' => 'a,b,c'); + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + writeInfo($exifTool, 't/images/XMP8.xmp', $testfile); + $info = $exifTool->ImageInfo($testfile, 'XMP:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +# test 50-53: Test replacing/creating elements in nested lang-alt list +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValuesFromFile('t/images/XMP9.xmp', '*:*'); + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->WriteInfo('t/images/XMP9.xmp', $testfile); + notOK() unless testCompare("t/XMP_$testnum.out",$testfile,$testnum); + print "ok $testnum\n"; + + ++$testnum; + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->WriteInfo(undef, $testfile); + notOK() unless testCompare('t/XMP_50.out',$testfile,$testnum); + print "ok $testnum\n"; + + ++$testnum; + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->SetNewValue(); + $exifTool->SetNewValue(Custom1 => 'test', DelValue => 1); + $exifTool->SetNewValue(Custom1 => 'new'); + $exifTool->WriteInfo('t/images/XMP9.xmp', $testfile); + notOK() unless testCompare("t/XMP_$testnum.out",$testfile,$testnum); + print "ok $testnum\n"; + + ++$testnum; + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + $exifTool->Options(ListSplit => ','); + $exifTool->SetNewValue(); + $exifTool->SetNewValue(Custom1 => 'a,b,c', AddValue => 1); + $exifTool->SetNewValue('Custom1-fr' => 'a-fr,,c-fr,d-fr,,f-fr', AddValue => 1); + $exifTool->WriteInfo('t/images/XMP9.xmp', $testfile); + notOK() unless testCompare("t/XMP_$testnum.out",$testfile,$testnum); + print "ok $testnum\n"; +} + +# test 54: Write flattened tag and extension in a variable-namespace structure +{ + ++$testnum; + $testfile = "t/${testname}_${testnum}_failed.xmp"; + unlink $testfile; + my $exifTool = Image::ExifTool->new; + $exifTool->SetNewValue(ImageRegionCtypeIdentifier => 'x'); + $exifTool->SetNewValue(ImageRegion => '{Flash={Fired=True,Return#=3}}'); + $exifTool->WriteInfo(undef, $testfile); + $exifTool->Options(Struct => 1); + my $info = $exifTool->ImageInfo($testfile, 'XMP-iptcExt:all'); + if (check($exifTool, $info, $testname, $testnum)) { + unlink $testfile; + } else { + notOK(); + } + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/XMP_10.out b/ExifTool/t/XMP_10.out new file mode 100644 index 0000000..9d77afa --- /dev/null +++ b/ExifTool/t/XMP_10.out @@ -0,0 +1,37 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: XMP3.xmp +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:58-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 6.32 +[XMP, XMP-rdf, Document] about - About: http://www.w3.org/TR/rdf-syntax-grammar +[XMP, XMP-dc, Image] title - Title: ExifTool common node, nonstandard namespace prefix, and bad list type tests +[XMP, XMP-dc, Author] creator - Creator: bad_list_type_A, bad_list_type_B +[XMP, XMP-iptcCore, Location] CountryCode - Country Code: CA +[XMP, XMP-iptcCore, Other] Scene - Scene: scene1 +[XMP, XMP-pdfx, Document] Customↂ0020Propertyↂ00201 - Custom 0020 Property 00201: a custom property value +[XMP, XMP-pdfx, Document] Customↂ0020Propertyↂ00202 - Custom 0020 Property 00202: some other value +[XMP, XMP-xmpMM, Other] ManifestLinkForm - Manifest Link Form: EmbedByReference +[XMP, XMP-xmpMM, Other] ManifestReferenceFilePath - Manifest Reference File Path: C:\some path\file.ext +[XMP, XMP-cc, Author] attributionName - Attribution Name: some attr +[XMP, XMP-ph, Unknown] ph:programmerExclamation - Programmer Exclamation: doh! +[XMP, XMP-ph, Unknown] ph:programmerFood - Programmer Food: cookies +[XMP, XMP-ph, Unknown] ph:programmerFullName - Programmer Full Name: Phil Harvey +[XMP, XMP-ph, Unknown] ph:programmerHomePage - Programmer Home Page: https://exiftool.org/ +[XMP, XMP-ph, Unknown] ph:programmerState - Programmer State: confusion +[XMP, XMP-ph, Unknown] ph:supervisorExclamation - Supervisor Exclamation: doh! +[XMP, XMP-ph, Unknown] ph:supervisorFood - Supervisor Food: cookies +[XMP, XMP-ph, Unknown] ph:supervisorFullName - Supervisor Full Name: Phil Harvey +[XMP, XMP-ph, Unknown] ph:supervisorHomePage - Supervisor Home Page: https://exiftool.org/ +[XMP, XMP-ph, Unknown] ph:supervisorState - Supervisor State: confusion +[XMP, XMP-ph, Unknown] ph:testerExclamation - Tester Exclamation: doh! +[XMP, XMP-ph, Unknown] ph:testerFood - Tester Food: cookies +[XMP, XMP-ph, Unknown] ph:testerFullName - Tester Full Name: Phil Harvey +[XMP, XMP-ph, Unknown] ph:testerHomePage - Tester Home Page: https://exiftool.org/ +[XMP, XMP-ph, Unknown] ph:testerState - Tester State: confusion diff --git a/ExifTool/t/XMP_11.out b/ExifTool/t/XMP_11.out new file mode 100644 index 0000000..49e03ac --- /dev/null +++ b/ExifTool/t/XMP_11.out @@ -0,0 +1,81 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 9.75'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='http://www.w3.org/TR/rdf-syntax-grammar' + xmlns:Iptc4xmpCore='http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/'> + <Iptc4xmpCore:CountryCode>CA</Iptc4xmpCore:CountryCode> + <Iptc4xmpCore:Scene> + <rdf:Bag> + <rdf:li>scene1</rdf:li> + </rdf:Bag> + </Iptc4xmpCore:Scene> + </rdf:Description> + + <rdf:Description rdf:about='http://www.w3.org/TR/rdf-syntax-grammar' + xmlns:cc='http://creativecommons.org/ns#'> + <cc:attributionName>something else</cc:attributionName> + </rdf:Description> + + <rdf:Description rdf:about='http://www.w3.org/TR/rdf-syntax-grammar' + xmlns:dc='http://purl.org/dc/elements/1.1/'> + <dc:creator> + <rdf:Seq> + <rdf:li>bad_list_type_A</rdf:li> + <rdf:li>bad_list_type_B</rdf:li> + <rdf:li>Phil</rdf:li> + </rdf:Seq> + </dc:creator> + <dc:title>ExifTool common node, nonstandard namespace prefix, and bad list type tests</dc:title> + </rdf:Description> + + <rdf:Description rdf:about='http://www.w3.org/TR/rdf-syntax-grammar' + xmlns:pdfx='http://ns.adobe.com/pdfx/1.3/'> + <pdfx:Customↂ0020Propertyↂ00201>a custom property value</pdfx:Customↂ0020Propertyↂ00201> + <pdfx:Customↂ0020Propertyↂ00202>some other value</pdfx:Customↂ0020Propertyↂ00202> + </rdf:Description> + + <rdf:Description rdf:about='http://www.w3.org/TR/rdf-syntax-grammar' + xmlns:ph='https://exiftool.org/ph/1.0/'> + <ph:programmer rdf:parseType='Resource'> + <ph:exclamation>doh!</ph:exclamation> + <ph:food>cookies</ph:food> + <ph:fullName>Phil Harvey</ph:fullName> + <ph:homePage rdf:resource='https://exiftool.org/'/> + <ph:state>confusion</ph:state> + </ph:programmer> + <ph:supervisor rdf:parseType='Resource'> + <ph:exclamation>doh!</ph:exclamation> + <ph:food>cookies</ph:food> + <ph:fullName>Phil Harvey</ph:fullName> + <ph:homePage rdf:resource='https://exiftool.org/'/> + <ph:state>confusion</ph:state> + </ph:supervisor> + <ph:tester rdf:parseType='Resource'> + <ph:exclamation>doh!</ph:exclamation> + <ph:food>cookies</ph:food> + <ph:fullName>Phil Harvey</ph:fullName> + <ph:homePage rdf:resource='https://exiftool.org/'/> + <ph:state>confusion</ph:state> + </ph:tester> + </rdf:Description> + + <rdf:Description rdf:about='http://www.w3.org/TR/rdf-syntax-grammar' + xmlns:stMfs='http://ns.adobe.com/xap/1.0/sType/ManifestItem#' + xmlns:stRef='http://ns.adobe.com/xap/1.0/sType/ResourceRef#' + xmlns:xmpMM='http://ns.adobe.com/xap/1.0/mm/'> + <xmpMM:Manifest> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <stMfs:linkForm>EmbedByReference</stMfs:linkForm> + <stMfs:reference rdf:parseType='Resource'> + <stRef:filePath>C:\some path\file.ext</stRef:filePath> + </stMfs:reference> + <xmpMM:placedXResolution>1</xmpMM:placedXResolution> + </rdf:li> + </rdf:Bag> + </xmpMM:Manifest> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/XMP_12.out b/ExifTool/t/XMP_12.out new file mode 100644 index 0000000..32f37a6 --- /dev/null +++ b/ExifTool/t/XMP_12.out @@ -0,0 +1,4 @@ +[XMP, XMP-dc, Image] format - Format: image/jpeg +[XMP, XMP-dc, Author] rights - Rights: © Copyright Another One +[XMP, XMP-dc, Author] rights-fr - Rights (fr): © Droit d'auteur Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: test1, &-&amp;-& diff --git a/ExifTool/t/XMP_13.out b/ExifTool/t/XMP_13.out new file mode 100644 index 0000000..58660c6 --- /dev/null +++ b/ExifTool/t/XMP_13.out @@ -0,0 +1,5 @@ +[XMP, XMP-dc, Image] format - Format: image/jpeg +[XMP, XMP-dc, Author] rights - Rights: © Copyright Phil Harvey +[XMP, XMP-dc, Author] rights-fr - Rights (fr): © Droit d'auteur Phil Harvey +[XMP, XMP-dc, Author] rights-de-DE - Rights (de-DE): © Urheberrecht Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: test1, &-&amp;-& diff --git a/ExifTool/t/XMP_14.out b/ExifTool/t/XMP_14.out new file mode 100644 index 0000000..5856e34 --- /dev/null +++ b/ExifTool/t/XMP_14.out @@ -0,0 +1,3 @@ +[XMP, XMP-dc, Image] format - Format: image/jpeg +[XMP, XMP-dc, Author] rights-fr - Rights (fr): © Droit d'auteur Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: test1, &-&amp;-& diff --git a/ExifTool/t/XMP_15.out b/ExifTool/t/XMP_15.out new file mode 100644 index 0000000..ab0244b --- /dev/null +++ b/ExifTool/t/XMP_15.out @@ -0,0 +1,3 @@ +[XMP, XMP-dc, Image] format - Format: image/jpeg +[XMP, XMP-dc, Author] rights - Rights: © Copyright Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: test1, &-&amp;-& diff --git a/ExifTool/t/XMP_16.out b/ExifTool/t/XMP_16.out new file mode 100644 index 0000000..b383720 --- /dev/null +++ b/ExifTool/t/XMP_16.out @@ -0,0 +1,5 @@ +[XMP, XMP-dc, Image] format - Format: image/jpeg +[XMP, XMP-dc, Author] rights - Rights: © Copyright Phil Harvey +[XMP, XMP-dc, Author] rights-fr - Rights (fr): © Droit d'auteur Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: test1, &-&amp;-& +[XMP, XMP-dc, Image] title-fr - Title (fr): Test fr title diff --git a/ExifTool/t/XMP_17.out b/ExifTool/t/XMP_17.out new file mode 100644 index 0000000..2df4d18 --- /dev/null +++ b/ExifTool/t/XMP_17.out @@ -0,0 +1,6 @@ +[XMP, XMP-dc, Image] format - Format: image/jpeg +[XMP, XMP-dc, Author] rights - Rights: © Copyright Phil Harvey +[XMP, XMP-dc, Author] rights-fr - Rights (fr): © Droit d'auteur Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: test1, &-&amp;-& +[XMP, XMP-dc, Image] title - Title: dTitle +[XMP, XMP-dc, Image] title-fr - Title (fr): Test fr title diff --git a/ExifTool/t/XMP_18.out b/ExifTool/t/XMP_18.out new file mode 100644 index 0000000..5f79b77 --- /dev/null +++ b/ExifTool/t/XMP_18.out @@ -0,0 +1,10 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 11.78 +[XMP, XMP-rdf, Document] about - About: uuid:80056b68-1045-fa97-3401-6f4ed84cd53d +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ diff --git a/ExifTool/t/XMP_19.out b/ExifTool/t/XMP_19.out new file mode 100644 index 0000000..bc46370 --- /dev/null +++ b/ExifTool/t/XMP_19.out @@ -0,0 +1,35 @@ +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon DIGITAL IXUS 40 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Time] 306 - Modify Date: 2005:11:21 17:07:14 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Centered +[EXIF, IFD0, Image] 18246 - Rating: 1 +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 0.4 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.8 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:06:09 20:09:27 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:11:21 17:07:14 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0.4 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 2.8 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: -1 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.8 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 5.8 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1136 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 852 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 10142.85714 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 10142.85714 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: inches +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: Normal +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: Manual +[EXIF, ExifIFD, Camera] 41987 - White Balance: Auto +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 1 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: Standard diff --git a/ExifTool/t/XMP_2.out b/ExifTool/t/XMP_2.out new file mode 100644 index 0000000..49acd76 --- /dev/null +++ b/ExifTool/t/XMP_2.out @@ -0,0 +1,140 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: XMP.jpg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 10 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2019:12:04 21:22:58-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:58-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[JFIF, JFIF, Image] 0 - JFIF Version: 1.02 +[JFIF, JFIF, Image] 2 - Resolution Unit: inches +[JFIF, JFIF, Image] 3 - X Resolution: 72 +[JFIF, JFIF, Image] 5 - Y Resolution: 72 +[EXIF, IFD0, Image] 270 - Image Description: A witty caption +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 72 +[EXIF, IFD0, Image] 283 - Y Resolution: 72 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0 +[EXIF, IFD0, Time] 306 - Modify Date: 2004:02:26 09:36:46 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: Copyright 2004 Phil Harvey +[EXIF, ExifIFD, Image] 33437 - F Number: 3.5 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 100 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0210 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 3.5 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 2 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: 0 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 3.5 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 100 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 80 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3053 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3053 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 872 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 0 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 4.73 +[XMP, XMP-rdf, Document] about - About: uuid:80056b68-1045-fa97-3401-6f4ed84cd53d +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Image] Category - Category: 1 +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image] Headline - Headline: No headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Author] Source - Source: I'm the source +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference? +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Linotronic +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1998:02:09 06:49:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Microsoft Corporation +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: Hewlett-Packard +[ICC_Profile, ICC-header, Image] 52 - Device Model: sRGB +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Hewlett-Packard +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright (c) 1998 Hewlett-Packard Company +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: sRGB IEC61966-2.1 +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.43607 0.22249 0.01392 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.38515 0.71687 0.09708 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14307 0.06061 0.7141 +[ICC_Profile, ICC_Profile, Camera] dmnd - Device Mfg Desc: IEC http://www.iec.ch +[ICC_Profile, ICC_Profile, Camera] dmdd - Device Model Desc: IEC 61966-2.1 Default RGB colour space - sRGB +[ICC_Profile, ICC_Profile, Image] vued - Viewing Cond Desc: Reference Viewing Condition in IEC61966-2.1 +[ICC_Profile, ICC-view, Image] 8 - Viewing Cond Illuminant: 19.6445 20.3718 16.8089 +[ICC_Profile, ICC-view, Image] 20 - Viewing Cond Surround: 3.92889 4.07439 3.36179 +[ICC_Profile, ICC-view, Image] 32 - Viewing Cond Illuminant Type: D50 +[ICC_Profile, ICC_Profile, Image] lumi - Luminance: 76.03647 80 87.12462 +[ICC_Profile, ICC-meas, Image] 8 - Measurement Observer: CIE 1931 +[ICC_Profile, ICC-meas, Image] 12 - Measurement Backing: 0 0 0 +[ICC_Profile, ICC-meas, Image] 24 - Measurement Geometry: Unknown +[ICC_Profile, ICC-meas, Image] 28 - Measurement Flare: 0.999% +[ICC_Profile, ICC-meas, Image] 32 - Measurement Illuminant: D65 +[ICC_Profile, ICC_Profile, Image] tech - Technology: Cathode Ray Tube Display +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 2060 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 2060 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 2060 bytes) +[APP14, Adobe, Image] 0 - DCT Encode Version: 100 +[APP14, Adobe, Image] 1 - APP14 Flags 0: (none) +[APP14, Adobe, Image] 2 - APP14 Flags 1: (none) +[APP14, Adobe, Image] 3 - Color Transform: YCbCr +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.0 mm +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.6 diff --git a/ExifTool/t/XMP_20.out b/ExifTool/t/XMP_20.out new file mode 100644 index 0000000..ca61009 --- /dev/null +++ b/ExifTool/t/XMP_20.out @@ -0,0 +1,35 @@ +[EXIF, IFD0, Camera] 271 - Make: Canon +[EXIF, IFD0, Camera] 272 - Camera Model Name: Canon DIGITAL IXUS 40 +[EXIF, IFD0, Image] 274 - Orientation: 1 +[EXIF, IFD0, Image] 282 - X Resolution: 180 +[EXIF, IFD0, Image] 283 - Y Resolution: 180 +[EXIF, IFD0, Image] 296 - Resolution Unit: 2 +[EXIF, IFD0, Time] 306 - Modify Date: 2005:11:21 17:07:14+01:00 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: 1 +[EXIF, IFD0, Image] 18246 - Rating: 1 +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 0.4 +[EXIF, ExifIFD, Image] 33437 - F Number: 2.8 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0.2.2.0 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2005:06:09 20:09:27+02:00 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2005:11:21 17:07:14+01:00 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: 1 2 3 0 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 0.402622582987314 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 2.79795934507662 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: -1 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 2.79795934507662 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: 5 +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 5.8 +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0.1.0.0 +[EXIF, ExifIFD, Image] 40961 - Color Space: 1 +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 1136 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 852 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 10142.85714 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 10142.85714 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: 2 +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: 2 +[EXIF, ExifIFD, Image] 41728 - File Source: 3 +[EXIF, ExifIFD, Image] 41985 - Custom Rendered: 0 +[EXIF, ExifIFD, Camera] 41986 - Exposure Mode: 1 +[EXIF, ExifIFD, Camera] 41987 - White Balance: 0 +[EXIF, ExifIFD, Camera] 41988 - Digital Zoom Ratio: 1 +[EXIF, ExifIFD, Camera] 41990 - Scene Capture Type: 0 diff --git a/ExifTool/t/XMP_21.out b/ExifTool/t/XMP_21.out new file mode 100644 index 0000000..b359535 --- /dev/null +++ b/ExifTool/t/XMP_21.out @@ -0,0 +1,38 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 9.32 +[XMP, XMP-exif, Image] ApertureValue - Aperture Value: 14.0 +[XMP, XMP-exif, Image] ColorSpace - Color Space: sRGB +[XMP, XMP-exif, Image] ComponentsConfiguration - Components Configuration: Y, Cb, Cr, - +[XMP, XMP-exif, Image] CompressedBitsPerPixel - Compressed Bits Per Pixel: 9 +[XMP, XMP-exif, Image] CustomRendered - Custom Rendered: Normal +[XMP, XMP-exif, Time] DateTimeOriginal - Date/Time Original: 2003:12:04 06:46:52 +[XMP, XMP-exif, Image] ExifVersion - Exif Version: 0221 +[XMP, XMP-exif, Image] ExposureBiasValue - Exposure Compensation: 0 +[XMP, XMP-exif, Camera] ExposureMode - Exposure Mode: Manual +[XMP, XMP-exif, Image] ExposureTime - Exposure Time: 4 +[XMP, XMP-exif, Image] FNumber - F Number: 14.0 +[XMP, XMP-exif, Image] FileSource - File Source: Digital Camera +[XMP, XMP-exif, Image] FlashpixVersion - Flashpix Version: 0100 +[XMP, XMP-exif, Camera] FocalLength - Focal Length: 34.0 mm +[XMP, XMP-exif, Camera] FocalPlaneResolutionUnit - Focal Plane Resolution Unit: inches +[XMP, XMP-exif, Camera] FocalPlaneXResolution - Focal Plane X Resolution: 3443.94615384615 +[XMP, XMP-exif, Camera] FocalPlaneYResolution - Focal Plane Y Resolution: 3442.01680672269 +[XMP, XMP-exif, Image] ISOSpeedRatings - ISO: 100 +[XMP, XMP-exif, Camera] MaxApertureValue - Max Aperture Value: 4.5 +[XMP, XMP-exif, Camera] MeteringMode - Metering Mode: Average +[XMP, XMP-exif, Image] PixelXDimension - Exif Image Width: 160 +[XMP, XMP-exif, Image] PixelYDimension - Exif Image Height: 120 +[XMP, XMP-exif, Camera] SceneCaptureType - Scene Capture Type: Standard +[XMP, XMP-exif, Camera] SensingMethod - Sensing Method: One-chip color area +[XMP, XMP-exif, Image] ShutterSpeedValue - Shutter Speed Value: 1 +[XMP, XMP-exif, Image] UserComment - User Comment: +[XMP, XMP-exif, Camera] WhiteBalance - White Balance: Auto +[XMP, XMP-exifEX, Image] InteroperabilityIndex - Interoperability Index: THM - DCF thumbnail file +[XMP, XMP-tiff, Camera] Make - Make: Canon +[XMP, XMP-tiff, Camera] Model - Camera Model Name: Canon EOS DIGITAL REBEL +[XMP, XMP-tiff, Image] Orientation - Orientation: Horizontal (normal) +[XMP, XMP-tiff, Image] ResolutionUnit - Resolution Unit: inches +[XMP, XMP-tiff, Image] XResolution - X Resolution: 180 +[XMP, XMP-tiff, Image] YCbCrPositioning - Y Cb Cr Positioning: Centered +[XMP, XMP-tiff, Image] YResolution - Y Resolution: 180 +[XMP, XMP-xmp, Time] CreateDate - Create Date: 2003:12:04 06:46:52 +[XMP, XMP-xmp, Time] ModifyDate - Modify Date: 2003:12:04 06:46:52 diff --git a/ExifTool/t/XMP_22.out b/ExifTool/t/XMP_22.out new file mode 100644 index 0000000..3704dec --- /dev/null +++ b/ExifTool/t/XMP_22.out @@ -0,0 +1,38 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 9.32 +[XMP, XMP-exif, Image] ApertureValue - Aperture Value: 14.0000277425244 +[XMP, XMP-exif, Image] ColorSpace - Color Space: 1 +[XMP, XMP-exif, Image] ComponentsConfiguration - Components Configuration: [1,2,3,0] +[XMP, XMP-exif, Image] CompressedBitsPerPixel - Compressed Bits Per Pixel: 9 +[XMP, XMP-exif, Image] CustomRendered - Custom Rendered: 0 +[XMP, XMP-exif, Time] DateTimeOriginal - Date/Time Original: 2003:12:04 06:46:52 +[XMP, XMP-exif, Image] ExifVersion - Exif Version: 0221 +[XMP, XMP-exif, Image] ExposureBiasValue - Exposure Compensation: 0 +[XMP, XMP-exif, Camera] ExposureMode - Exposure Mode: 1 +[XMP, XMP-exif, Image] ExposureTime - Exposure Time: 4 +[XMP, XMP-exif, Image] FNumber - F Number: 14 +[XMP, XMP-exif, Image] FileSource - File Source: 3 +[XMP, XMP-exif, Image] FlashpixVersion - Flashpix Version: 0100 +[XMP, XMP-exif, Camera] FocalLength - Focal Length: 34 +[XMP, XMP-exif, Camera] FocalPlaneResolutionUnit - Focal Plane Resolution Unit: 2 +[XMP, XMP-exif, Camera] FocalPlaneXResolution - Focal Plane X Resolution: 3443.94615384615 +[XMP, XMP-exif, Camera] FocalPlaneYResolution - Focal Plane Y Resolution: 3442.01680672269 +[XMP, XMP-exif, Image] ISOSpeedRatings - ISO: 100 +[XMP, XMP-exif, Camera] MaxApertureValue - Max Aperture Value: 4.49999024812954 +[XMP, XMP-exif, Camera] MeteringMode - Metering Mode: 1 +[XMP, XMP-exif, Image] PixelXDimension - Exif Image Width: 160 +[XMP, XMP-exif, Image] PixelYDimension - Exif Image Height: 120 +[XMP, XMP-exif, Camera] SceneCaptureType - Scene Capture Type: 0 +[XMP, XMP-exif, Camera] SensingMethod - Sensing Method: 2 +[XMP, XMP-exif, Image] ShutterSpeedValue - Shutter Speed Value: 1 +[XMP, XMP-exif, Image] UserComment - User Comment: +[XMP, XMP-exif, Camera] WhiteBalance - White Balance: 0 +[XMP, XMP-exifEX, Image] InteroperabilityIndex - Interoperability Index: THM +[XMP, XMP-tiff, Camera] Make - Make: Canon +[XMP, XMP-tiff, Camera] Model - Camera Model Name: Canon EOS DIGITAL REBEL +[XMP, XMP-tiff, Image] Orientation - Orientation: 1 +[XMP, XMP-tiff, Image] ResolutionUnit - Resolution Unit: 2 +[XMP, XMP-tiff, Image] XResolution - X Resolution: 180 +[XMP, XMP-tiff, Image] YCbCrPositioning - Y Cb Cr Positioning: 1 +[XMP, XMP-tiff, Image] YResolution - Y Resolution: 180 +[XMP, XMP-xmp, Time] CreateDate - Create Date: 2003:12:04 06:46:52 +[XMP, XMP-xmp, Time] ModifyDate - Modify Date: 2003:12:04 06:46:52 diff --git a/ExifTool/t/XMP_23.out b/ExifTool/t/XMP_23.out new file mode 100644 index 0000000..d21958f --- /dev/null +++ b/ExifTool/t/XMP_23.out @@ -0,0 +1,9 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 11.78 +[XMP, XMP-rdf, Document] about - About: uuid:80056b68-1045-fa97-3401-6f4ed84cd53d +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ diff --git a/ExifTool/t/XMP_24.out b/ExifTool/t/XMP_24.out new file mode 100644 index 0000000..d94d26a --- /dev/null +++ b/ExifTool/t/XMP_24.out @@ -0,0 +1,32 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.03 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 4.73 +[XMP, XMP-rdf, Document] about - About: uuid:80056b68-1045-fa97-3401-6f4ed84cd53d +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Image] Category - Category: 1 +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image] Headline - Headline: No headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Author] Source - Source: I'm the source +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference? +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[APP14, Adobe, Image] 0 - DCT Encode Version: 100 +[APP14, Adobe, Image] 1 - APP14 Flags 0: (none) +[APP14, Adobe, Image] 2 - APP14 Flags 1: (none) +[APP14, Adobe, Image] 3 - Color Transform: YCbCr +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/XMP_25.out b/ExifTool/t/XMP_25.out new file mode 100644 index 0000000..84dc880 --- /dev/null +++ b/ExifTool/t/XMP_25.out @@ -0,0 +1,28 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: XMP.svg +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1402 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2008:04:07 14:11:53-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: SVG +[File, File, Other] FileTypeExtension - File Type Extension: svg +[File, File, Other] MIMEType - MIME Type: image/svg+xml +[SVG, SVG, Image] width - Image Width: 4in +[SVG, SVG, Image] height - Image Height: 3in +[SVG, SVG, Image] version - SVG Version: 1.1 +[SVG, SVG, Image] xmlns - Xmlns: http://www.w3.org/2000/svg +[SVG, SVG-myfoo, Unknown] myfoo:descTitle - Desc Title: This is a financial report +[SVG, SVG-myfoo, Unknown] myfoo:descDescrEmph - Desc Descr Emph: myfoo +[SVG, SVG-myfoo, Unknown] myfoo:descSceneWhat - Desc Scene What: widget $growth +[SVG, SVG-myfoo, Unknown] myfoo:descSceneContains - Desc Scene Contains: $three $graph-bar +[SVG, SVG-myfoo, Unknown] myfoo:descSceneWhen - Desc Scene When: 1998 $through 2000 +[XMP, XMP-rdf, Document] about - About: http://example.org/myfoo +[XMP, XMP-dc, Image] title - Title: MyFoo Financial Report +[XMP, XMP-dc, Image] description - Description: $three $bar $thousands $dollars $from 1998 $through 2000 +[XMP, XMP-dc, Author] publisher - Publisher: Example Organization +[XMP, XMP-dc, Time] date - Date: 2000:04:11 +[XMP, XMP-dc, Image] format - Format: image/svg+xml +[XMP, XMP-dc, Other] language - Language: en +[XMP, XMP-dc, Author] creator - Creator: Irving Bird, Mary Lambert diff --git a/ExifTool/t/XMP_26.out b/ExifTool/t/XMP_26.out new file mode 100644 index 0000000..0c81628 --- /dev/null +++ b/ExifTool/t/XMP_26.out @@ -0,0 +1,50 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='What&#39;s this?'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='http://www.exiftool.org/t/XMP.t#26' + xmlns:myXMPns='http://ns.exiftool.org/t/XMP.t'> + <myXMPns:ATestTag> + <rdf:Bag> + <rdf:li rdf:resource='http://www.exiftool.org/t/XMP.t#26-one'/> + <rdf:li rdf:resource='http://www.exiftool.org/t/XMP.t#26-two'/> + </rdf:Bag> + </myXMPns:ATestTag> + </rdf:Description> + + <rdf:Description rdf:about='http://www.exiftool.org/t/XMP.t#26' + xmlns:plus='http://ns.useplus.org/ldf/xmp/1.0/'> + <plus:CopyrightStatus>http://ns.useplus.org/ldf/vocab/CS-PUB</plus:CopyrightStatus> + <plus:Custom1> + <rdf:Bag> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='en'>a</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='en'>b</rdf:li> + </rdf:Alt> + </rdf:li> + </rdf:Bag> + </plus:Custom1> + <plus:ImageType>http://ns.useplus.org/ldf/vocab/TY-VID</plus:ImageType> + <plus:Licensee> + <rdf:Seq> + <rdf:li rdf:parseType='Resource'> + <plus:LicenseeName>Phil</plus:LicenseeName> + </rdf:li> + </rdf:Seq> + </plus:Licensee> + <plus:LicenseeImageNotes> + <rdf:Alt> + <rdf:li xml:lang='x-default'>default notes</rdf:li> + <rdf:li xml:lang='de'>deutsche anmerkungen</rdf:li> + <rdf:li xml:lang='en'>english notes</rdf:li> + </rdf:Alt> + </plus:LicenseeImageNotes> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/XMP_27.out b/ExifTool/t/XMP_27.out new file mode 100644 index 0000000..8eb8a1c --- /dev/null +++ b/ExifTool/t/XMP_27.out @@ -0,0 +1,101 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.61 +[File, System, Other] FileName - File Name: XMP.xml +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 6.6 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2023:04:22 14:07:47-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2023:04:22 14:07:51-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2023:04:22 14:07:47-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-rdf, Document] about - About: t/images/Nikon.jpg +[XML, XML-ExifTool, Unknown] ExifTool:ExifToolVersion - Exif Tool Version: 12.61 +[XML, XML-System, Unknown] System:FileName - File Name: Nikon.jpg +[XML, XML-System, Unknown] System:Directory - Directory: t/images +[XML, XML-System, Unknown] System:FileSize - File Size: 1703 bytes +[XML, XML-System, Unknown] System:FileModifyDate - File Modify Date: 2006:01:04 14:02:27-05:00 +[XML, XML-System, Unknown] System:FileAccessDate - File Access Date: 2023:04:22 14:07:35-04:00 +[XML, XML-System, Unknown] System:FileInodeChangeDate - File Inode Change Date: 2023:02:21 15:57:49-05:00 +[XML, XML-System, Unknown] System:FilePermissions - File Permissions: -rw-r--r-- +[XML, XML-File, Unknown] File:FileType - File Type: JPEG +[XML, XML-File, Unknown] File:FileTypeExtension - File Type Extension: jpg +[XML, XML-File, Unknown] File:MIMEType - MIME Type: image/jpeg +[XML, XML-File, Unknown] File:ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[XML, XML-File, Unknown] File:ImageWidth - Image Width: 8 +[XML, XML-File, Unknown] File:ImageHeight - Image Height: 8 +[XML, XML-File, Unknown] File:EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[XML, XML-File, Unknown] File:BitsPerSample - Bits Per Sample: 8 +[XML, XML-File, Unknown] File:ColorComponents - Color Components: 3 +[XML, XML-File, Unknown] File:YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[XML, XML-Composite, Unknown] Composite:Aperture - Aperture: 9.4 +[XML, XML-Composite, Unknown] Composite:ImageSize - Image Size: 8x8 +[XML, XML-Composite, Unknown] Composite:Megapixels - Megapixels: 0.000064 +[XML, XML-Composite, Unknown] Composite:ShutterSpeed - Shutter Speed: 0.00469483568075117 +[XML, XML-Composite, Unknown] Composite:FocalLength35efl - Focal Length 35efl: 8.6 mm +[XML, XML-Composite, Unknown] Composite:LightValue - Light Value: 14.2 +[EXIF, IFD0, Unknown] IFD0:ImageDescription - Image Description: +[EXIF, IFD0, Unknown] IFD0:Make - Make: NIKON +[EXIF, IFD0, Unknown] IFD0:Model - Model: E775 +[EXIF, IFD0, Unknown] IFD0:Orientation - Orientation: Horizontal (normal) +[EXIF, IFD0, Unknown] IFD0:XResolution - X Resolution: 300 +[EXIF, IFD0, Unknown] IFD0:YResolution - Y Resolution: 300 +[EXIF, IFD0, Unknown] IFD0:ResolutionUnit - Resolution Unit: inches +[EXIF, IFD0, Unknown] IFD0:Software - Software: E775v1.3u +[EXIF, IFD0, Unknown] IFD0:ModifyDate - Modify Date: 2001:08:01 12:57:23 +[EXIF, IFD0, Unknown] IFD0:YCbCrPositioning - Y Cb Cr Positioning: Co-sited +[EXIF, ExifIFD, Unknown] ExifIFD:ExposureTime - Exposure Time: 0.00469483568075117 +[EXIF, ExifIFD, Unknown] ExifIFD:FNumber - F Number: 9.4 +[EXIF, ExifIFD, Unknown] ExifIFD:ExposureProgram - Exposure Program: Program AE +[EXIF, ExifIFD, Unknown] ExifIFD:iso - Iso: 100 +[EXIF, ExifIFD, Unknown] ExifIFD:ExifVersion - Exif Version: 0210 +[EXIF, ExifIFD, Unknown] ExifIFD:DateTimeOriginal - Date Time Original: 2001:08:01 12:57:23 +[EXIF, ExifIFD, Unknown] ExifIFD:CreateDate - Create Date: 2001:08:01 12:57:23 +[EXIF, ExifIFD, Unknown] ExifIFD:ComponentsConfiguration - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Unknown] ExifIFD:CompressedBitsPerPixel - Compressed Bits Per Pixel: 3 +[EXIF, ExifIFD, Unknown] ExifIFD:ExposureCompensation - Exposure Compensation: 0 +[EXIF, ExifIFD, Unknown] ExifIFD:MaxApertureValue - Max Aperture Value: 3.4 +[EXIF, ExifIFD, Unknown] ExifIFD:MeteringMode - Metering Mode: Multi-segment +[EXIF, ExifIFD, Unknown] ExifIFD:LightSource - Light Source: Unknown +[EXIF, ExifIFD, Unknown] ExifIFD:Flash - Flash: No Flash +[EXIF, ExifIFD, Unknown] ExifIFD:FocalLength - Focal Length: 8.6 mm +[EXIF, ExifIFD, Unknown] ExifIFD:UserComment - User Comment: +[EXIF, ExifIFD, Unknown] ExifIFD:FlashpixVersion - Flashpix Version: 0100 +[EXIF, ExifIFD, Unknown] ExifIFD:ColorSpace - Color Space: sRGB +[EXIF, ExifIFD, Unknown] ExifIFD:ExifImageWidth - Exif Image Width: 1600 +[EXIF, ExifIFD, Unknown] ExifIFD:ExifImageHeight - Exif Image Height: 1200 +[EXIF, ExifIFD, Unknown] ExifIFD:FileSource - File Source: Digital Camera +[EXIF, ExifIFD, Unknown] ExifIFD:SceneType - Scene Type: Directly photographed +[EXIF, InteropIFD, Unknown] InteropIFD:InteropIndex - Interop Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Unknown] InteropIFD:InteropVersion - Interop Version: 0100 +[EXIF, IFD1, Unknown] IFD1:Compression - Compression: JPEG (old-style) +[EXIF, IFD1, Unknown] IFD1:XResolution - X Resolution: 300 +[EXIF, IFD1, Unknown] IFD1:YResolution - Y Resolution: 300 +[EXIF, IFD1, Unknown] IFD1:ResolutionUnit - Resolution Unit: inches +[EXIF, IFD1, Unknown] IFD1:ThumbnailOffset - Thumbnail Offset: 1426 +[EXIF, IFD1, Unknown] IFD1:ThumbnailLength - Thumbnail Length: 28 +[EXIF, IFD1, Unknown] IFD1:ThumbnailImage - Thumbnail Image: (Binary data 28 bytes, use -b option to extract) +[MakerNotes, Nikon, Unknown] Nikon:MakerNoteVersion - Maker Note Version: 1.00 +[MakerNotes, Nikon, Unknown] Nikon:iso - Iso: 0 +[MakerNotes, Nikon, Unknown] Nikon:ColorMode - Color Mode: Color +[MakerNotes, Nikon, Unknown] Nikon:Quality - Quality: Fine +[MakerNotes, Nikon, Unknown] Nikon:WhiteBalance - White Balance: Auto +[MakerNotes, Nikon, Unknown] Nikon:Sharpness - Sharpness: Auto +[MakerNotes, Nikon, Unknown] Nikon:FocusMode - Focus Mode: AF-C +[MakerNotes, Nikon, Unknown] Nikon:FlashSetting - Flash Setting: +[MakerNotes, Nikon, Unknown] Nikon:ISOSelection - ISO Selection: Auto +[MakerNotes, Nikon, Unknown] Nikon:ImageAdjustment - Image Adjustment: Normal +[MakerNotes, Nikon, Unknown] Nikon:AuxiliaryLens - Auxiliary Lens: Off +[MakerNotes, Nikon, Unknown] Nikon:ManualFocusDistance - Manual Focus Distance: undef +[MakerNotes, Nikon, Unknown] Nikon:DigitalZoom - Digital Zoom: 1 +[MakerNotes, Nikon, Unknown] Nikon:AFAreaMode - AF Area Mode: Single Area +[MakerNotes, Nikon, Unknown] Nikon:AFPoint - AF Point: Center +[MakerNotes, Nikon, Unknown] Nikon:AFPointsInFocus - AF Points In Focus: (none) +[MakerNotes, Nikon, Unknown] Nikon:SceneMode - Scene Mode: +[MakerNotes, Nikon, Unknown] Nikon:DataDump - Data Dump: (Binary data 122 bytes, use -b option to extract) +[PrintIM, PrintIM, Unknown] PrintIM:PrintIMVersion - Print IM Version: 0100 +[Composite, Composite, Image] Exif-Aperture - Aperture: 9.4 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/213 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 8.6 mm diff --git a/ExifTool/t/XMP_28.out b/ExifTool/t/XMP_28.out new file mode 100644 index 0000000..a018d0b --- /dev/null +++ b/ExifTool/t/XMP_28.out @@ -0,0 +1,44 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.61 +[File, System, Other] FileName - File Name: XMP_28_failed.xml +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.1 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2023:04:22 14:09:01-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2023:04:22 14:09:01-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2023:04:22 14:09:01-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-rdf, Document] about - About: t/images/Nikon.jpg +[XMP, XMP-pdf, Author] Author - Author: Phil +[XML, XML-File, Unknown] File:BitsPerSample - Bits Per Sample: 8 +[XML, XML-File, Unknown] File:ColorComponents - Color Components: 3 +[XML, XML-File, Unknown] File:EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[XML, XML-File, Unknown] File:ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[XML, XML-File, Unknown] File:FileType - File Type: JPEG +[XML, XML-File, Unknown] File:FileTypeExtension - File Type Extension: jpg +[XML, XML-File, Unknown] File:ImageHeight - Image Height: 8 +[XML, XML-File, Unknown] File:ImageWidth - Image Width: 8 +[XML, XML-File, Unknown] File:MIMEType - MIME Type: image/jpeg +[XML, XML-File, Unknown] File:YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[EXIF, IFD0, Unknown] IFD0:ImageDescription - Image Description: +[EXIF, IFD0, Unknown] IFD0:Make - Make: NIKON +[EXIF, IFD0, Unknown] IFD0:Model - Model: E775 +[EXIF, IFD0, Unknown] IFD0:ModifyDate - Modify Date: 2001:08:01 12:57:23 +[EXIF, IFD0, Unknown] IFD0:Orientation - Orientation: Horizontal (normal) +[EXIF, IFD0, Unknown] IFD0:ResolutionUnit - Resolution Unit: inches +[EXIF, IFD0, Unknown] IFD0:Software - Software: E775v1.3u +[EXIF, IFD0, Unknown] IFD0:XResolution - X Resolution: 300 +[EXIF, IFD0, Unknown] IFD0:YCbCrPositioning - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Unknown] IFD0:YResolution - Y Resolution: 300 +[EXIF, IFD1, Unknown] IFD1:Compression - Compression: JPEG (old-style) +[EXIF, IFD1, Unknown] IFD1:ResolutionUnit - Resolution Unit: inches +[EXIF, IFD1, Unknown] IFD1:ThumbnailImage - Thumbnail Image: (Binary data 28 bytes, use -b option to extract) +[EXIF, IFD1, Unknown] IFD1:ThumbnailLength - Thumbnail Length: 28 +[EXIF, IFD1, Unknown] IFD1:ThumbnailOffset - Thumbnail Offset: 1426 +[EXIF, IFD1, Unknown] IFD1:XResolution - X Resolution: 300 +[EXIF, IFD1, Unknown] IFD1:YResolution - Y Resolution: 300 +[EXIF, InteropIFD, Unknown] InteropIFD:InteropIndex - Interop Index: R98 - DCF basic file (sRGB) +[EXIF, InteropIFD, Unknown] InteropIFD:InteropVersion - Interop Version: 0100 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/XMP_29.out b/ExifTool/t/XMP_29.out new file mode 100644 index 0000000..64f887d --- /dev/null +++ b/ExifTool/t/XMP_29.out @@ -0,0 +1,26 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: XMP_29_failed.jpg +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 3.3 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:59-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:59-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:59-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-pdf, Author] Author - Author: Test +[XMP, XMP-pdf, Time] CreationDate - Creation Date: 2008:10:20 19:54:15 +[XMP, XMP-pdf, Author] Creator - Creator: Guess Who +[XMP, XMP-pdf, Time] ModDate - Mod Date: 2008:10:20 19:54:15 +[XMP, XMP-pdf, Author] Producer - Producer: Just ExifTool again +[XMP, XMP-pdf, Image] Title - Title: PDF Title +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 diff --git a/ExifTool/t/XMP_3.out b/ExifTool/t/XMP_3.out new file mode 100644 index 0000000..2e89c0d --- /dev/null +++ b/ExifTool/t/XMP_3.out @@ -0,0 +1,138 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileSize - File Size: 10 kB +[File, File, Other] FileType - File Type: JPEG +[File, File, Other] FileTypeExtension - File Type Extension: jpg +[File, File, Other] MIMEType - MIME Type: image/jpeg +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[File, File, Image] CurrentIPTCDigest - Current IPTC Digest: 2b3df19b0c67788262a0d0dced3b6d58 +[File, File, Image] ImageWidth - Image Width: 8 +[File, File, Image] ImageHeight - Image Height: 8 +[File, File, Image] EncodingProcess - Encoding Process: Baseline DCT, Huffman coding +[File, File, Image] BitsPerSample - Bits Per Sample: 8 +[File, File, Image] ColorComponents - Color Components: 3 +[File, File, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[JFIF, JFIF, Image] 0 - JFIF Version: 1.02 +[JFIF, JFIF, Image] 2 - Resolution Unit: inches +[JFIF, JFIF, Image] 3 - X Resolution: 80 +[JFIF, JFIF, Image] 5 - Y Resolution: 80 +[EXIF, IFD0, Image] 270 - Image Description: A witty caption-v2 +[EXIF, IFD0, Camera] 271 - Make: FUJIFILM-v2 +[EXIF, IFD0, Camera] 272 - Camera Model Name: FinePix2400Zoom-v2 +[EXIF, IFD0, Image] 274 - Orientation: Horizontal (normal) +[EXIF, IFD0, Image] 282 - X Resolution: 80 +[EXIF, IFD0, Image] 283 - Y Resolution: 80 +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, IFD0, Image] 305 - Software: Adobe Photoshop 7.0-v2 +[EXIF, IFD0, Time] 306 - Modify Date: 2004:02:26 09:36:46 +[EXIF, IFD0, Author] 315 - Artist: Phil Harvey-v2 +[EXIF, IFD0, Image] 531 - Y Cb Cr Positioning: Co-sited +[EXIF, IFD0, Author] 33432 - Copyright: Copyright 2004 Phil Harvey-v2 +[EXIF, ExifIFD, Image] 33437 - F Number: 4.9 +[EXIF, ExifIFD, Camera] 34850 - Exposure Program: Program AE +[EXIF, ExifIFD, Image] 34855 - ISO: 111 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0232 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2001:05:19 18:36:41 +[EXIF, ExifIFD, Image] 37121 - Components Configuration: Y, Cb, Cr, - +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 1.6 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/64 +[EXIF, ExifIFD, Image] 37378 - Aperture Value: 4.9 +[EXIF, ExifIFD, Image] 37379 - Brightness Value: 3 +[EXIF, ExifIFD, Image] 37380 - Exposure Compensation: +1 +[EXIF, ExifIFD, Camera] 37381 - Max Aperture Value: 4.9 +[EXIF, ExifIFD, Camera] 37383 - Metering Mode: Multi-segment +[EXIF, ExifIFD, Camera] 37385 - Flash: Fired +[EXIF, ExifIFD, Camera] 37386 - Focal Length: 6.0 mm +[EXIF, ExifIFD, Image] 40960 - Flashpix Version: 0100 +[EXIF, ExifIFD, Image] 40961 - Color Space: sRGB +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 111 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 89 +[EXIF, ExifIFD, Camera] 41486 - Focal Plane X Resolution: 3359 +[EXIF, ExifIFD, Camera] 41487 - Focal Plane Y Resolution: 3359 +[EXIF, ExifIFD, Camera] 41488 - Focal Plane Resolution Unit: cm +[EXIF, ExifIFD, Camera] 41495 - Sensing Method: One-chip color area +[EXIF, ExifIFD, Image] 41728 - File Source: Digital Camera +[EXIF, ExifIFD, Image] 41729 - Scene Type: Directly photographed +[EXIF, IFD1, Image] 259 - Compression: JPEG (old-style) +[EXIF, IFD1, Image] 282 - X Resolution: 72 +[EXIF, IFD1, Image] 283 - Y Resolution: 72 +[EXIF, IFD1, Image] 296 - Resolution Unit: inches +[EXIF, IFD1, Image] 513 - Thumbnail Offset: 890 +[EXIF, IFD1, Image] 514 - Thumbnail Length: 0 +[IPTC, IPTC, Other] 90 - Coded Character Set: UTF8 +[IPTC, IPTC, Other] 0 - Envelope Record Version: 4 +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-rdf, Document] about - About: uuid:80056b68-1045-fa97-3401-6f4ed84cd53d +[XMP, XMP-iptcCore, Author] CreatorContactInfoCiAdrCtry - Creator Country: Canada +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey-v2 +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP-v2 +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey-v2 +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP, v2 +[XMP, XMP-dc, Image] title - Title: Test IPTC picture-v2 +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position-v2 +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it-v2 +[XMP, XMP-photoshop, Image] Category - Category: 2 +[XMP, XMP-photoshop, Location] City - City: Kingston-v2 +[XMP, XMP-photoshop, Location] Country - Country: Canada-v2 +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit-v2 +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 02:00 +[XMP, XMP-photoshop, Image] Headline - Headline: No headline-v2 +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions-v2 +[XMP, XMP-photoshop, Author] Source - Source: I'm the source-v2 +[XMP, XMP-photoshop, Location] State - State: Ont-v2 +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities, v2 +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference?-v2 +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job-v2 +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a-v2 +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/-v2 +[ICC_Profile, ICC-header, Image] 4 - Profile CMM Type: Linotronic +[ICC_Profile, ICC-header, Image] 8 - Profile Version: 2.1.0 +[ICC_Profile, ICC-header, Image] 12 - Profile Class: Display Device Profile +[ICC_Profile, ICC-header, Image] 16 - Color Space Data: RGB +[ICC_Profile, ICC-header, Image] 20 - Profile Connection Space: XYZ +[ICC_Profile, ICC-header, Time] 24 - Profile Date Time: 1998:02:09 06:49:00 +[ICC_Profile, ICC-header, Image] 36 - Profile File Signature: acsp +[ICC_Profile, ICC-header, Image] 40 - Primary Platform: Microsoft Corporation +[ICC_Profile, ICC-header, Image] 44 - CMM Flags: Not Embedded, Independent +[ICC_Profile, ICC-header, Image] 48 - Device Manufacturer: Hewlett-Packard +[ICC_Profile, ICC-header, Image] 52 - Device Model: sRGB +[ICC_Profile, ICC-header, Image] 56 - Device Attributes: Reflective, Glossy, Positive, Color +[ICC_Profile, ICC-header, Image] 64 - Rendering Intent: Perceptual +[ICC_Profile, ICC-header, Image] 68 - Connection Space Illuminant: 0.9642 1 0.82491 +[ICC_Profile, ICC-header, Image] 80 - Profile Creator: Hewlett-Packard +[ICC_Profile, ICC-header, Image] 84 - Profile ID: 0 +[ICC_Profile, ICC_Profile, Image] cprt - Profile Copyright: Copyright (c) 1998 Hewlett-Packard Company +[ICC_Profile, ICC_Profile, Image] desc - Profile Description: sRGB IEC61966-2.1 +[ICC_Profile, ICC_Profile, Image] wtpt - Media White Point: 0.95045 1 1.08905 +[ICC_Profile, ICC_Profile, Image] bkpt - Media Black Point: 0 0 0 +[ICC_Profile, ICC_Profile, Image] rXYZ - Red Matrix Column: 0.43607 0.22249 0.01392 +[ICC_Profile, ICC_Profile, Image] gXYZ - Green Matrix Column: 0.38515 0.71687 0.09708 +[ICC_Profile, ICC_Profile, Image] bXYZ - Blue Matrix Column: 0.14307 0.06061 0.7141 +[ICC_Profile, ICC_Profile, Camera] dmnd - Device Mfg Desc: IEC http://www.iec.ch +[ICC_Profile, ICC_Profile, Camera] dmdd - Device Model Desc: IEC 61966-2.1 Default RGB colour space - sRGB +[ICC_Profile, ICC_Profile, Image] vued - Viewing Cond Desc: Reference Viewing Condition in IEC61966-2.1 +[ICC_Profile, ICC-view, Image] 8 - Viewing Cond Illuminant: 19.6445 20.3718 16.8089 +[ICC_Profile, ICC-view, Image] 20 - Viewing Cond Surround: 3.92889 4.07439 3.36179 +[ICC_Profile, ICC-view, Image] 32 - Viewing Cond Illuminant Type: D50 +[ICC_Profile, ICC_Profile, Image] lumi - Luminance: 76.03647 80 87.12462 +[ICC_Profile, ICC-meas, Image] 8 - Measurement Observer: CIE 1931 +[ICC_Profile, ICC-meas, Image] 12 - Measurement Backing: 0 0 0 +[ICC_Profile, ICC-meas, Image] 24 - Measurement Geometry: Unknown +[ICC_Profile, ICC-meas, Image] 28 - Measurement Flare: 0.999% +[ICC_Profile, ICC-meas, Image] 32 - Measurement Illuminant: D65 +[ICC_Profile, ICC_Profile, Image] tech - Technology: Cathode Ray Tube Display +[ICC_Profile, ICC_Profile, Image] rTRC - Red Tone Reproduction Curve: (Binary data 2060 bytes) +[ICC_Profile, ICC_Profile, Image] gTRC - Green Tone Reproduction Curve: (Binary data 2060 bytes) +[ICC_Profile, ICC_Profile, Image] bTRC - Blue Tone Reproduction Curve: (Binary data 2060 bytes) +[APP14, Adobe, Image] 0 - DCT Encode Version: 100 +[APP14, Adobe, Image] 1 - APP14 Flags 0: (none) +[APP14, Adobe, Image] 2 - APP14 Flags 1: (none) +[APP14, Adobe, Image] 3 - Color Transform: YCbCr +[Composite, Composite, Image] Exif-Aperture - Aperture: 4.9 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-LightValue - Light Value: 10.4 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.0 mm diff --git a/ExifTool/t/XMP_30.out b/ExifTool/t/XMP_30.out new file mode 100644 index 0000000..a7e0912 --- /dev/null +++ b/ExifTool/t/XMP_30.out @@ -0,0 +1,86 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 11.78'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:dc='http://purl.org/dc/elements/1.1/'> + <dc:creator> + <rdf:Seq> + <rdf:li>Phil Harvey</rdf:li> + </rdf:Seq> + </dc:creator> + <dc:description> + <rdf:Alt> + <rdf:li xml:lang='x-default'>UTF-16 (big-endian) encoded XMP</rdf:li> + </rdf:Alt> + </dc:description> + <dc:rights> + <rdf:Alt> + <rdf:li xml:lang='x-default'>Copyright 2004 Phil Harvey</rdf:li> + </rdf:Alt> + </dc:rights> + <dc:subject> + <rdf:Bag> + <rdf:li>ExifTool</rdf:li> + <rdf:li>Test</rdf:li> + <rdf:li>XMP</rdf:li> + </rdf:Bag> + </dc:subject> + <dc:title> + <rdf:Alt> + <rdf:li xml:lang='x-default'>Test IPTC picture</rdf:li> + </rdf:Alt> + </dc:title> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:photoshop='http://ns.adobe.com/photoshop/1.0/' + photoshop:AuthorsPosition='My Position' + photoshop:CaptionWriter='I wrote it' + photoshop:Category='1' + photoshop:City='Kingston' + photoshop:Country='Canada' + photoshop:Credit='My Credit' + photoshop:DateCreated='2004-02-26' + photoshop:Headline='No headline' + photoshop:Instructions='What instructions' + photoshop:Source='I&#39;m the source' + photoshop:State='Ont' + photoshop:TransmissionReference='What is a transmission reference?' + photoshop:Urgency='8'> + <photoshop:SupplementalCategories> + <rdf:Bag> + <rdf:li>amazing</rdf:li> + <rdf:li>image</rdf:li> + <rdf:li>utilities</rdf:li> + </rdf:Bag> + </photoshop:SupplementalCategories> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:xmp='http://ns.adobe.com/xap/1.0/' + xmp:CreateDate='2001-05-19T18:36:41' + xmp:ModifyDate='2004-02-26T09:36:46'/> + + <rdf:Description rdf:about='' + xmlns:stJob='http://ns.adobe.com/xap/1.0/sType/Job#' + xmlns:xmpBJ='http://ns.adobe.com/xap/1.0/bj/'> + <xmpBJ:JobRef> + <rdf:Bag> + <rdf:li + stJob:name='My Job'/> + </rdf:Bag> + </xmpBJ:JobRef> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:xmpMM='http://ns.adobe.com/xap/1.0/mm/' + xmpMM:DocumentID='adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a'/> + + <rdf:Description rdf:about='' + xmlns:xmpRights='http://ns.adobe.com/xap/1.0/rights/' + xmpRights:Marked='False' + xmpRights:WebStatement='https://exiftool.org/'/> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/XMP_31.out b/ExifTool/t/XMP_31.out new file mode 100644 index 0000000..4a3b791 --- /dev/null +++ b/ExifTool/t/XMP_31.out @@ -0,0 +1,20 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: XMP4.xmp +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 2.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2009:05:24 11:19:39-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:59-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 7.77 +[XMP, XMP-iptcExt, Location] LocationCreated - Location Created: [{City=one,CountryCode=1},{City=two,CountryCode=2},{City=three},{CountryCode=4}] +[XMP, XMP-dc, Author] contributor - Contributor: me +[XMP, XMP-dc, Image] subject - Subject: one, two +[XMP, XMP-test, Unknown] test:StructList2 - Struct List 2: [{Item1=c1-1,Item2=c2-1},{Item1=c1-2,Item2=c2-2,TestList1=[x1],TestList2=[y1,y2]}] +[XMP, XMP-test, Unknown] test:StructList1 - Struct List 1: [{Item1=b1,Item2=b2}] +[XMP, XMP-test, Unknown] test:BareStruct - Bare Struct: {Item1=a1,Item2=a2} +[XMP, XMP-test, Unknown] test:BareList2 - Bare List 2: b1, b2 +[XMP, XMP-test, Unknown] test:BareList1 - Bare List 1: a1 diff --git a/ExifTool/t/XMP_32.out b/ExifTool/t/XMP_32.out new file mode 100644 index 0000000..1189295 --- /dev/null +++ b/ExifTool/t/XMP_32.out @@ -0,0 +1,2 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 8.44 +[XMP, XMP-dc, Image] title-de - Title (de): A diff --git a/ExifTool/t/XMP_34.out b/ExifTool/t/XMP_34.out new file mode 100644 index 0000000..2c72595 --- /dev/null +++ b/ExifTool/t/XMP_34.out @@ -0,0 +1,2 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 8.44 +[XMP, XMP-dc, Image] title-de - Title (de): C diff --git a/ExifTool/t/XMP_36.out b/ExifTool/t/XMP_36.out new file mode 100644 index 0000000..da69c3a --- /dev/null +++ b/ExifTool/t/XMP_36.out @@ -0,0 +1,19 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: XMP5.xmp +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 4.7 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2021:04:28 17:04:35-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:59-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 8.82 +[XMP, XMP-iptcExt, Author] ArtworkOrObject - Artwork Or Object: [{AOTitle=test,AOTitle-de=pr&uuml;fung,AOTitle-fr=&eacute;preuve}] +[XMP, XMP-exif, Camera] Flash - Flash: {Mode=Off,Return=No return detection} +[XMP, XMP-mwg-kw, Image] Keywords - Keyword Info: {Hierarchy=[{Children=[{Keyword=A-2}],Keyword=A-1},{Children=[{Keyword=B-2}],Keyword=B-1},{Keyword=C-1}]} +[XMP, XMP-mwg-rs, Image] Regions - Region Info: {RegionList=[{Area={H=8,W=8,X=0,Y=0},Name=Region 1,SeeAlso=plus:Licensee,Type=Face},{Extensions={XMP-exif:Flash={Mode=On,Return=Return not detected},XMP-iptcExt:ArtworkOrObject=[{AOTitle-de=verf&auml;nglich}],XMP-myXMPns:BTestTag={Field1=[this is wild]},XMP-xmpRights:UsageTerms-fr=libre}}]} +[XMP, XMP-myXMPns, Other] BTestTag - B Test Tag: {Field1-en-CA=[eh?],Field1-en-US=[huh?,groovy],Field1-fr=[,ing&eacute;nieux]} +[XMP, XMP-plus, Author] Licensee - Licensee: [{LicenseeName=Phil}] +[Composite, Composite, Camera] XMP-Flash - Flash: Off, Did not fire diff --git a/ExifTool/t/XMP_37.out b/ExifTool/t/XMP_37.out new file mode 100644 index 0000000..b511779 --- /dev/null +++ b/ExifTool/t/XMP_37.out @@ -0,0 +1,36 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: XMP5.xmp +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 4.7 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2021:04:28 17:04:35-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:59-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 8.82 +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOTitle - Artwork Title: test +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOTitle-de - Artwork Title (de): pr&uuml;fung +[XMP, XMP-iptcExt, Author] ArtworkOrObjectAOTitle-fr - Artwork Title (fr): &eacute;preuve +[XMP, XMP-exif, Camera] FlashMode - Flash Mode: Off +[XMP, XMP-exif, Camera] FlashReturn - Flash Return: No return detection +[XMP, XMP-mwg-kw, Image] KeywordsHierarchyChildrenKeyword - Hierarchical Keywords 2: A-2, B-2 +[XMP, XMP-mwg-kw, Image] KeywordsHierarchyKeyword - Hierarchical Keywords 1: A-1, B-1, C-1 +[XMP, XMP-mwg-rs, Image] RegionsRegionListAreaH - Region Area H: 8 +[XMP, XMP-mwg-rs, Image] RegionsRegionListAreaW - Region Area W: 8 +[XMP, XMP-mwg-rs, Image] RegionsRegionListAreaX - Region Area X: 0 +[XMP, XMP-mwg-rs, Image] RegionsRegionListAreaY - Region Area Y: 0 +[XMP, XMP-mwg-rs, Image] RegionsRegionListName - Region Name: Region 1 +[XMP, XMP-mwg-rs, Image] RegionsRegionListType - Region Type: Face +[XMP, XMP-mwg-rs, Image] RegionsRegionListSeeAlso - Region See Also: plus:Licensee +[XMP, XMP-mwg-rs, Author] Iptc4xmpExt:RegionsRegionListExtensionsArtworkOrObjectAOTitle-de - Region Extensions Artwork Title (de): verf&auml;nglich +[XMP, XMP-mwg-rs, Camera] exif:RegionsRegionListExtensionsFlashMode - Region Extensions Flash Mode: On +[XMP, XMP-mwg-rs, Camera] exif:RegionsRegionListExtensionsFlashReturn - Region Extensions Flash Return: Return not detected +[XMP, XMP-mwg-rs, Other] myXMPns:RegionsRegionListExtensionsBTestTagField1 - Region Extensions Renamed: this is wild +[XMP, XMP-mwg-rs, Author] xmpRights:RegionsRegionListExtensionsUsageTerms-fr - Region Extensions Usage Terms (fr): libre +[XMP, XMP-myXMPns, Other] BTestTagField1-en-CA - Renamed (en-CA): eh? +[XMP, XMP-myXMPns, Other] BTestTagField1-en-US - Renamed (en-US): huh?, groovy +[XMP, XMP-myXMPns, Other] BTestTagField1-fr - Renamed (fr): ing&eacute;nieux +[XMP, XMP-plus, Author] LicenseeLicenseeName - Licensee Name: Phil +[Composite, Composite, Camera] XMP-Flash - Flash: Off, Did not fire diff --git a/ExifTool/t/XMP_39.out b/ExifTool/t/XMP_39.out new file mode 100644 index 0000000..a2b88e4 --- /dev/null +++ b/ExifTool/t/XMP_39.out @@ -0,0 +1,45 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: XMP.inx +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 6.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2011:11:25 11:38:08-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:59-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: INX +[File, File, Other] FileTypeExtension - File Type Extension: inx +[File, File, Other] MIMEType - MIME Type: application/x-indesign-interchange +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Adobe XMP Core 4.0-c006 1.236519, Wed Jun 14 2006 08:31:24 +[XMP, XMP-xmpMM, Other] InstanceID - Instance ID: beb2441b-dd64-11df-b20e-e65882d9428e +[XMP, XMP-xmpMM, Other] DerivedFromInstanceID - Derived From Instance ID: f0d208df-dc56-11df-95ac-e273561c7691 +[XMP, XMP-xmpMM, Other] DerivedFromDocumentID - Derived From Document ID: adobe:docid:indd:0419e6d7-167b-11df-8afc-dcb7ca988d92 +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:indd:f0d208e0-dc56-11df-95ac-e273561c7691 +[XMP, XMP-xmpMM, Other] RenditionClass - Rendition Class: default +[XMP, XMP-xmpMM, Other] ManifestLinkForm - Manifest Link Form: ReferenceStream +[XMP, XMP-xmpMM, Other] ManifestReferenceInstanceID - Manifest Reference Instance ID: uuid:9CC7E7EDDC5811DF8C4BC09357E49DC9 +[XMP, XMP-xmpMM, Other] ManifestReferenceDocumentID - Manifest Reference Document ID: uuid:9CC7E7ECDC5811DF8C4BC09357E49DC9 +[XMP, XMP-xmpMM, Other] ManifestPlacedXResolution - Manifest Placed X Resolution: 72.00 +[XMP, XMP-xmpMM, Other] ManifestPlacedYResolution - Manifest Placed Y Resolution: 72.00 +[XMP, XMP-xmpMM, Other] ManifestPlacedResolutionUnit - Manifest Placed Resolution Unit: Inches +[XMP, XMP-xmp, Time] CreateDate - Create Date: 2010:10:18 14:59:13Z +[XMP, XMP-xmp, Time] ModifyDate - Modify Date: 2010:10:19 22:53:33Z +[XMP, XMP-xmp, Time] MetadataDate - Metadata Date: 2010:10:19 22:53:33Z +[XMP, XMP-xmp, Image] CreatorTool - Creator Tool: Adobe InDesign 5.0 +[XMP, XMP-xmp, Image] ThumbnailsFormat - Thumbnail Format: JPEG +[XMP, XMP-xmp, Image] ThumbnailsWidth - Thumbnail Width: 1024 +[XMP, XMP-xmp, Image] ThumbnailsHeight - Thumbnail Height: 1024 +[XMP, XMP-xmpTPg, Image] ColorantsSwatchName - Colorant Swatch Name: Black +[XMP, XMP-xmpTPg, Image] ColorantsMode - Colorant Mode: CMYK +[XMP, XMP-xmpTPg, Image] ColorantsType - Colorant Type: Process +[XMP, XMP-xmpTPg, Image] ColorantsCyan - Colorant Cyan: 0 +[XMP, XMP-xmpTPg, Image] ColorantsMagenta - Colorant Magenta: 0 +[XMP, XMP-xmpTPg, Image] ColorantsYellow - Colorant Yellow: 0 +[XMP, XMP-xmpTPg, Image] ColorantsBlack - Colorant Black: 100 +[XMP, XMP-xmpTPg, Image] FontsFontName - Font Name: Times-Roman +[XMP, XMP-xmpTPg, Image] FontsFontFamily - Font Family: Times +[XMP, XMP-xmpTPg, Image] FontsFontFace - Font Face: Regular +[XMP, XMP-xmpTPg, Image] FontsFontType - Font Type: TrueType +[XMP, XMP-xmpTPg, Image] FontsVersionString - Font Version: Times-Roman6.0d6e5 +[XMP, XMP-xmpTPg, Image] FontsComposite - Font Composite: false +[XMP, XMP-xmpTPg, Image] FontsFontFileName - Font File Name: Times.dfont +[XMP, XMP-dc, Image] format - Format: application/x-indesign diff --git a/ExifTool/t/XMP_40.out b/ExifTool/t/XMP_40.out new file mode 100644 index 0000000..0e2fe33 --- /dev/null +++ b/ExifTool/t/XMP_40.out @@ -0,0 +1,3 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 8.82 +[XMP, XMP-mwg-kw, Image] KeywordsHierarchyKeyword - Hierarchical Keywords 1: A-1, B-1, C-1 +[XMP, XMP-plus, Author] LicenseeLicenseeName - Licensee Name: Phil diff --git a/ExifTool/t/XMP_41.out b/ExifTool/t/XMP_41.out new file mode 100644 index 0000000..820e50d --- /dev/null +++ b/ExifTool/t/XMP_41.out @@ -0,0 +1,247 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.44 +[File, System, Other] FileName - File Name: XMP_41_failed.xmp +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 14 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:07:12 14:20:45-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:07:12 14:20:45-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:07:12 14:20:45-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.44 +[XMP, XMP-dwc, Time] EventDay - Event Day: 2013 +[XMP, XMP-dwc, Time] EventEarliestDate - Event Earliest Date: 2013 +[XMP, XMP-dwc, Time] EventEndDayOfYear - Event End Day Of Year: 2013 +[XMP, XMP-dwc, Time] EventEventDate - Event Date: 2013 +[XMP, XMP-dwc, Other] EventEventID - Event ID: 2013 +[XMP, XMP-dwc, Other] EventEventRemarks - Event Remarks: 2013 +[XMP, XMP-dwc, Time] EventEventTime - Event Time: 2013 +[XMP, XMP-dwc, Other] EventFieldNotes - Event Field Notes: 2013 +[XMP, XMP-dwc, Other] EventFieldNumber - Event Field Number: 2013 +[XMP, XMP-dwc, Other] EventHabitat - Event Habitat: 2013 +[XMP, XMP-dwc, Time] EventLatestDate - Event Latest Date: 2013 +[XMP, XMP-dwc, Time] EventMonth - Event Month: 2013 +[XMP, XMP-dwc, Other] EventParentEventID - Event Parent Event ID: 2013 +[XMP, XMP-dwc, Other] EventSampleSizeUnit - Event Sample Size Unit: 2013 +[XMP, XMP-dwc, Other] EventSampleSizeValue - Event Sample Size Value: 2013 +[XMP, XMP-dwc, Other] EventSamplingEffort - Event Sampling Effort: 2013 +[XMP, XMP-dwc, Other] EventSamplingProtocol - Event Sampling Protocol: 2013 +[XMP, XMP-dwc, Time] EventStartDayOfYear - Event Start Day Of Year: 2013 +[XMP, XMP-dwc, Time] EventVerbatimEventDate - Event Verbatim Event Date: 2013 +[XMP, XMP-dwc, Time] EventYear - Event Year: 2013 +[XMP, XMP-dwc, Other] FossilSpecimenMaterialSampleID - Fossil Specimen Material Sample ID: 2013 +[XMP, XMP-dwc, Other] GeologicalContextBed - Geological Context Bed: 2013 +[XMP, XMP-dwc, Other] GeologicalContextEarliestAgeOrLowestStage - Earliest Age Or Lowest Stage: 2013 +[XMP, XMP-dwc, Other] GeologicalContextEarliestEonOrLowestEonothem - Earliest Eon Or Lowest Eonothem: 2013 +[XMP, XMP-dwc, Other] GeologicalContextEarliestEpochOrLowestSeries - Earliest Epoch Or Lowest Series: 2013 +[XMP, XMP-dwc, Other] GeologicalContextEarliestEraOrLowestErathem - Earliest Era Or Lowest Erathem: 2013 +[XMP, XMP-dwc, Other] GeologicalContextEarliestPeriodOrLowestSystem - Earliest Period Or Lowest System: 2013 +[XMP, XMP-dwc, Other] GeologicalContextFormation - Geological Context Formation: 2013 +[XMP, XMP-dwc, Other] GeologicalContextGeologicalContextID - Geological Context ID: 2013 +[XMP, XMP-dwc, Other] GeologicalContextGroup - Geological Context Group: 2013 +[XMP, XMP-dwc, Other] GeologicalContextHighestBiostratigraphicZone - Highest Biostratigraphic Zone: 2013 +[XMP, XMP-dwc, Other] GeologicalContextLatestAgeOrHighestStage - Latest Age Or Highest Stage: 2013 +[XMP, XMP-dwc, Other] GeologicalContextLatestEonOrHighestEonothem - Latest Eon Or Highest Eonothem: 2013 +[XMP, XMP-dwc, Other] GeologicalContextLatestEpochOrHighestSeries - Latest Epoch Or Highest Series: 2013 +[XMP, XMP-dwc, Other] GeologicalContextLatestEraOrHighestErathem - Latest Era Or Highest Erathem: 2013 +[XMP, XMP-dwc, Other] GeologicalContextLatestPeriodOrHighestSystem - Latest Period Or Highest System: 2013 +[XMP, XMP-dwc, Other] GeologicalContextLithostratigraphicTerms - Lithostratigraphic Terms: 2013 +[XMP, XMP-dwc, Other] GeologicalContextLowestBiostratigraphicZone - Lowest Biostratigraphic Zone: 2013 +[XMP, XMP-dwc, Other] GeologicalContextMember - Geological Context Member: 2013 +[XMP, XMP-dwc, Time] HumanObservationDay - Human Observation Day: 2013 +[XMP, XMP-dwc, Time] HumanObservationEarliestDate - Human Observation Earliest Date: 2013 +[XMP, XMP-dwc, Time] HumanObservationEndDayOfYear - Human Observation End Day Of Year: 2013 +[XMP, XMP-dwc, Time] HumanObservationEventDate - Human Observation Event Date: 2013 +[XMP, XMP-dwc, Other] HumanObservationEventID - Human Observation Event ID: 2013 +[XMP, XMP-dwc, Other] HumanObservationEventRemarks - Human Observation Event Remarks: 2013 +[XMP, XMP-dwc, Time] HumanObservationEventTime - Human Observation Event Time: 2013 +[XMP, XMP-dwc, Other] HumanObservationFieldNotes - Human Observation Field Notes: 2013 +[XMP, XMP-dwc, Other] HumanObservationFieldNumber - Human Observation Field Number: 2013 +[XMP, XMP-dwc, Other] HumanObservationHabitat - Human Observation Habitat: 2013 +[XMP, XMP-dwc, Time] HumanObservationLatestDate - Human Observation Latest Date: 2013 +[XMP, XMP-dwc, Time] HumanObservationMonth - Human Observation Month: 2013 +[XMP, XMP-dwc, Other] HumanObservationParentEventID - Human Observation Parent Event ID: 2013 +[XMP, XMP-dwc, Other] HumanObservationSampleSizeUnit - Human Observation Sample Size Unit: 2013 +[XMP, XMP-dwc, Other] HumanObservationSampleSizeValue - Human Observation Sample Size Value: 2013 +[XMP, XMP-dwc, Other] HumanObservationSamplingEffort - Human Observation Sampling Effort: 2013 +[XMP, XMP-dwc, Other] HumanObservationSamplingProtocol - Human Observation Sampling Protocol: 2013 +[XMP, XMP-dwc, Time] HumanObservationStartDayOfYear - Human Observation Start Day Of Year: 2013 +[XMP, XMP-dwc, Time] HumanObservationVerbatimEventDate - Human Observation Verbatim Event Date: 2013 +[XMP, XMP-dwc, Time] HumanObservationYear - Human Observation Year: 2013 +[XMP, XMP-dwc, Time] IdentificationDateIdentified - Date Identified: 2013 +[XMP, XMP-dwc, Other] IdentificationIdentificationID - Identification ID: 2013 +[XMP, XMP-dwc, Other] IdentificationIdentificationQualifier - Identification Qualifier: 2013 +[XMP, XMP-dwc, Other] IdentificationIdentificationReferences - Identification References: 2013 +[XMP, XMP-dwc, Other] IdentificationIdentificationRemarks - Identification Remarks: 2013 +[XMP, XMP-dwc, Other] IdentificationIdentificationVerificationStatus - Identification Verification Status: 2013 +[XMP, XMP-dwc, Other] IdentificationIdentifiedBy - Identified By: 2013 +[XMP, XMP-dwc, Other] IdentificationIdentifiedByID - Identified By ID: 2013 +[XMP, XMP-dwc, Other] IdentificationTypeStatus - Type Status: 2013 +[XMP, XMP-dwc, Other] IdentificationVerbatimIdentification - Verbatim Identification: 2013 +[XMP, XMP-dwc, Other] LivingSpecimenMaterialSampleID - Living Specimen Material Sample ID: 2013 +[XMP, XMP-dwc, Time] MachineObservationDay - Machine Observation Day: 2013 +[XMP, XMP-dwc, Time] MachineObservationEarliestDate - Machine Observation Earliest Date: 2013 +[XMP, XMP-dwc, Time] MachineObservationEndDayOfYear - Machine Observation End Day Of Year: 2013 +[XMP, XMP-dwc, Time] MachineObservationEventDate - Machine Observation Event Date: 2013 +[XMP, XMP-dwc, Other] MachineObservationEventID - Machine Observation Event ID: 2013 +[XMP, XMP-dwc, Other] MachineObservationEventRemarks - Machine Observation Event Remarks: 2013 +[XMP, XMP-dwc, Time] MachineObservationEventTime - Machine Observation Event Time: 2013 +[XMP, XMP-dwc, Other] MachineObservationFieldNotes - Machine Observation Field Notes: 2013 +[XMP, XMP-dwc, Other] MachineObservationFieldNumber - Machine Observation Field Number: 2013 +[XMP, XMP-dwc, Other] MachineObservationHabitat - Machine Observation Habitat: 2013 +[XMP, XMP-dwc, Time] MachineObservationLatestDate - Machine Observation Latest Date: 2013 +[XMP, XMP-dwc, Time] MachineObservationMonth - Machine Observation Month: 2013 +[XMP, XMP-dwc, Other] MachineObservationParentEventID - Machine Observation Parent Event ID: 2013 +[XMP, XMP-dwc, Other] MachineObservationSampleSizeUnit - Machine Observation Sample Size Unit: 2013 +[XMP, XMP-dwc, Other] MachineObservationSampleSizeValue - Machine Observation Sample Size Value: 2013 +[XMP, XMP-dwc, Other] MachineObservationSamplingEffort - Machine Observation Sampling Effort: 2013 +[XMP, XMP-dwc, Other] MachineObservationSamplingProtocol - Machine Observation Sampling Protocol: 2013 +[XMP, XMP-dwc, Time] MachineObservationStartDayOfYear - Machine Observation Start Day Of Year: 2013 +[XMP, XMP-dwc, Time] MachineObservationVerbatimEventDate - Machine Observation Verbatim Event Date: 2013 +[XMP, XMP-dwc, Time] MachineObservationYear - Machine Observation Year: 2013 +[XMP, XMP-dwc, Other] MaterialSampleMaterialSampleID - Material Sample ID: 2013 +[XMP, XMP-dwc, Other] MeasurementOrFactMeasurementAccuracy - Measurement Accuracy: 2013 +[XMP, XMP-dwc, Other] MeasurementOrFactMeasurementDeterminedBy - Measurement Determined By: 2013 +[XMP, XMP-dwc, Time] MeasurementOrFactMeasurementDeterminedDate - Measurement Determined Date: 2013 +[XMP, XMP-dwc, Other] MeasurementOrFactMeasurementID - Measurement ID: 2013 +[XMP, XMP-dwc, Other] MeasurementOrFactMeasurementMethod - Measurement Method: 2013 +[XMP, XMP-dwc, Other] MeasurementOrFactMeasurementRemarks - Measurement Remarks: 2013 +[XMP, XMP-dwc, Other] MeasurementOrFactMeasurementType - Measurement Type: 2013 +[XMP, XMP-dwc, Other] MeasurementOrFactMeasurementUnit - Measurement Unit: 2013 +[XMP, XMP-dwc, Other] MeasurementOrFactMeasurementValue - Measurement Value: 2013 +[XMP, XMP-dwc, Other] OccurrenceAssociatedMedia - Occurrence Associated Media: 2013 +[XMP, XMP-dwc, Other] OccurrenceAssociatedOccurrences - Occurrence Associated Occurrences: 2013 +[XMP, XMP-dwc, Other] OccurrenceAssociatedReferences - Occurrence Associated References: 2013 +[XMP, XMP-dwc, Other] OccurrenceAssociatedSequences - Occurrence Associated Sequences: 2013 +[XMP, XMP-dwc, Other] OccurrenceAssociatedTaxa - Occurrence Associated Taxa: 2013 +[XMP, XMP-dwc, Other] OccurrenceBehavior - Occurrence Behavior: 2013 +[XMP, XMP-dwc, Other] OccurrenceCatalogNumber - Occurrence Catalog Number: 2013 +[XMP, XMP-dwc, Other] OccurrenceDegreeOfEstablishment - Occurrence Degree Of Establishment: 2013 +[XMP, XMP-dwc, Other] OccurrenceDisposition - Occurrence Disposition: 2013 +[XMP, XMP-dwc, Other] OccurrenceEstablishmentMeans - Occurrence Establishment Means: 2013 +[XMP, XMP-dwc, Other] OccurrenceGeoreferenceVerificationStatus - Occurrence Georeference Verification Status: 2013 +[XMP, XMP-dwc, Other] OccurrenceIndividualCount - Occurrence Individual Count: 2013 +[XMP, XMP-dwc, Other] OccurrenceIndividualID - Occurrence Individual ID: 2013 +[XMP, XMP-dwc, Other] OccurrenceLifeStage - Occurrence Life Stage: 2013 +[XMP, XMP-dwc, Other] OccurrenceOccurrenceDetails - Occurrence Details: 2013 +[XMP, XMP-dwc, Other] OccurrenceOccurrenceID - Occurrence ID: 2013 +[XMP, XMP-dwc, Other] OccurrenceOccurrenceRemarks - Occurrence Remarks: 2013 +[XMP, XMP-dwc, Other] OccurrenceOccurrenceStatus - Occurrence Status: 2013 +[XMP, XMP-dwc, Other] OccurrenceOrganismQuantity - Occurrence Organism Quantity: 2013 +[XMP, XMP-dwc, Other] OccurrenceOrganismQuantityType - Occurrence Organism Quantity Type: 2013 +[XMP, XMP-dwc, Other] OccurrenceOtherCatalogNumbers - Occurrence Other Catalog Numbers: 2013 +[XMP, XMP-dwc, Other] OccurrencePathway - Occurrence Pathway: 2013 +[XMP, XMP-dwc, Other] OccurrencePreparations - Occurrence Preparations: 2013 +[XMP, XMP-dwc, Other] OccurrencePreviousIdentifications - Occurrence Previous Identifications: 2013 +[XMP, XMP-dwc, Other] OccurrenceRecordNumber - Occurrence Record Number: 2013 +[XMP, XMP-dwc, Other] OccurrenceRecordedBy - Occurrence Recorded By: 2013 +[XMP, XMP-dwc, Other] OccurrenceRecordedByID - Occurrence Recorded By ID: 2013 +[XMP, XMP-dwc, Other] OccurrenceReproductiveCondition - Occurrence Reproductive Condition: 2013 +[XMP, XMP-dwc, Other] OccurrenceSex - Occurrence Sex: 2013 +[XMP, XMP-dwc, Other] OrganismAssociatedOccurrences - Organism Associated Occurrences: 2013 +[XMP, XMP-dwc, Other] OrganismAssociatedOrganisms - Organism Associated Organisms: 2013 +[XMP, XMP-dwc, Other] OrganismOrganismID - Organism ID: 2013 +[XMP, XMP-dwc, Other] OrganismOrganismName - Organism Name: 2013 +[XMP, XMP-dwc, Other] OrganismOrganismRemarks - Organism Remarks: 2013 +[XMP, XMP-dwc, Other] OrganismOrganismScope - Organism Scope: 2013 +[XMP, XMP-dwc, Other] OrganismPreviousIdentifications - Organism Previous Identifications: 2013 +[XMP, XMP-dwc, Other] PreservedSpecimenMaterialSampleID - Preserved Specimen Material Sample ID: 2013 +[XMP, XMP-dwc, Other] RecordBasisOfRecord - Record Basis Of Record: 2013 +[XMP, XMP-dwc, Other] RecordCollectionCode - Record Collection Code: 2013 +[XMP, XMP-dwc, Other] RecordCollectionID - Record Collection ID: 2013 +[XMP, XMP-dwc, Other] RecordDataGeneralizations - Record Data Generalizations: 2013 +[XMP, XMP-dwc, Other] RecordDatasetID - Record Dataset ID: 2013 +[XMP, XMP-dwc, Other] RecordDatasetName - Record Dataset Name: 2013 +[XMP, XMP-dwc, Other] RecordDynamicProperties - Record Dynamic Properties: 2013 +[XMP, XMP-dwc, Other] RecordInformationWithheld - Record Information Withheld: 2013 +[XMP, XMP-dwc, Other] RecordInstitutionCode - Record Institution Code: 2013 +[XMP, XMP-dwc, Other] RecordInstitutionID - Record Institution ID: 2013 +[XMP, XMP-dwc, Other] RecordOwnerInstitutionCode - Record Owner Institution Code: 2013 +[XMP, XMP-dwc, Other] ResourceRelationshipRelatedResourceID - Related Resource ID: 2013 +[XMP, XMP-dwc, Other] ResourceRelationshipRelationshipAccordingTo - Relationship According To: 2013 +[XMP, XMP-dwc, Time] ResourceRelationshipRelationshipEstablishedDate - Relationship Established Date: 2013 +[XMP, XMP-dwc, Other] ResourceRelationshipRelationshipOfResource - Relationship Of Resource: 2013 +[XMP, XMP-dwc, Other] ResourceRelationshipRelationshipOfResourceID - Relationship Of Resource ID: 2013 +[XMP, XMP-dwc, Other] ResourceRelationshipRelationshipRemarks - Relationship Remarks: 2013 +[XMP, XMP-dwc, Other] ResourceRelationshipResourceID - Resource ID: 2013 +[XMP, XMP-dwc, Other] ResourceRelationshipResourceRelationshipID - Resource Relationship ID: 2013 +[XMP, XMP-dwc, Other] TaxonAcceptedNameUsage - Taxon Accepted Name Usage: 2013 +[XMP, XMP-dwc, Other] TaxonAcceptedNameUsageID - Taxon Accepted Name Usage ID: 2013 +[XMP, XMP-dwc, Other] TaxonClass - Taxon Class: 2013 +[XMP, XMP-dwc, Other] TaxonCultivarEpithet - Taxon Cultivar Epithet: 2013 +[XMP, XMP-dwc, Other] TaxonFamily - Taxon Family: 2013 +[XMP, XMP-dwc, Other] TaxonGenus - Taxon Genus: 2013 +[XMP, XMP-dwc, Other] TaxonHigherClassification - Taxon Higher Classification: 2013 +[XMP, XMP-dwc, Other] TaxonInfraspecificEpithet - Taxon Infraspecific Epithet: 2013 +[XMP, XMP-dwc, Other] TaxonKingdom - Taxon Kingdom: 2013 +[XMP, XMP-dwc, Other] TaxonNameAccordingTo - Taxon Name According To: 2013 +[XMP, XMP-dwc, Other] TaxonNameAccordingToID - Taxon Name According To ID: 2013 +[XMP, XMP-dwc, Other] TaxonNamePublishedIn - Taxon Name Published In: 2013 +[XMP, XMP-dwc, Other] TaxonNamePublishedInID - Taxon Name Published In ID: 2013 +[XMP, XMP-dwc, Other] TaxonNamePublishedInYear - Taxon Name Published In Year: 2013 +[XMP, XMP-dwc, Other] TaxonNomenclaturalCode - Taxon Nomenclatural Code: 2013 +[XMP, XMP-dwc, Other] TaxonNomenclaturalStatus - Taxon Nomenclatural Status: 2013 +[XMP, XMP-dwc, Other] TaxonOrder - Taxon Order: 2013 +[XMP, XMP-dwc, Other] TaxonOriginalNameUsage - Taxon Original Name Usage: 2013 +[XMP, XMP-dwc, Other] TaxonOriginalNameUsageID - Taxon Original Name Usage ID: 2013 +[XMP, XMP-dwc, Other] TaxonParentNameUsage - Taxon Parent Name Usage: 2013 +[XMP, XMP-dwc, Other] TaxonParentNameUsageID - Taxon Parent Name Usage ID: 2013 +[XMP, XMP-dwc, Other] TaxonPhylum - Taxon Phylum: 2013 +[XMP, XMP-dwc, Other] TaxonScientificName - Taxon Scientific Name: 2013 +[XMP, XMP-dwc, Other] TaxonScientificNameAuthorship - Taxon Scientific Name Authorship: 2013 +[XMP, XMP-dwc, Other] TaxonScientificNameID - Taxon Scientific Name ID: 2013 +[XMP, XMP-dwc, Other] TaxonSpecificEpithet - Taxon Specific Epithet: 2013 +[XMP, XMP-dwc, Other] TaxonSubgenus - Taxon Subgenus: 2013 +[XMP, XMP-dwc, Other] TaxonTaxonConceptID - Taxon Concept ID: 2013 +[XMP, XMP-dwc, Other] TaxonTaxonID - Taxon ID: 2013 +[XMP, XMP-dwc, Other] TaxonTaxonRank - Taxon Rank: 2013 +[XMP, XMP-dwc, Other] TaxonTaxonRemarks - Taxon Remarks: 2013 +[XMP, XMP-dwc, Other] TaxonTaxonomicStatus - Taxon Taxonomic Status: 2013 +[XMP, XMP-dwc, Other] TaxonVerbatimTaxonRank - Taxon Verbatim Taxon Rank: 2013 +[XMP, XMP-dwc, Other] TaxonVernacularName - Taxon Vernacular Name: 2013 +[XMP, XMP-dwc, Location] dctermsLocationContinent - DC Continent: 2013 +[XMP, XMP-dwc, Location] dctermsLocationCoordinatePrecision - DC Coordinate Precision: 2013 +[XMP, XMP-dwc, Location] dctermsLocationCoordinateUncertaintyInMeters - DC Coordinate Uncertainty In Meters: 2013 +[XMP, XMP-dwc, Location] dctermsLocationCountry - DC Country: 2013 +[XMP, XMP-dwc, Location] dctermsLocationCountryCode - DC Country Code: 2013 +[XMP, XMP-dwc, Location] dctermsLocationCounty - DC County: 2013 +[XMP, XMP-dwc, Location] dctermsLocationDecimalLatitude - DC Decimal Latitude: 2013 +[XMP, XMP-dwc, Location] dctermsLocationDecimalLongitude - DC Decimal Longitude: 2013 +[XMP, XMP-dwc, Location] dctermsLocationFootprintSRS - DC Footprint SRS: 2013 +[XMP, XMP-dwc, Location] dctermsLocationFootprintSpatialFit - DC Footprint Spatial Fit: 2013 +[XMP, XMP-dwc, Location] dctermsLocationFootprintWKT - DC Footprint WKT: 2013 +[XMP, XMP-dwc, Location] dctermsLocationGeodeticDatum - DC Geodetic Datum: 2013 +[XMP, XMP-dwc, Location] dctermsLocationGeoreferenceProtocol - DC Georeference Protocol: 2013 +[XMP, XMP-dwc, Location] dctermsLocationGeoreferenceRemarks - DC Georeference Remarks: 2013 +[XMP, XMP-dwc, Location] dctermsLocationGeoreferenceSources - DC Georeference Sources: 2013 +[XMP, XMP-dwc, Location] dctermsLocationGeoreferenceVerificationStatus - DC Georeference Verification Status: 2013 +[XMP, XMP-dwc, Location] dctermsLocationGeoreferencedBy - DC Georeferenced By: 2013 +[XMP, XMP-dwc, Location] dctermsLocationGeoreferencedDate - DC Georeferenced Date: 2013 +[XMP, XMP-dwc, Location] dctermsLocationHigherGeography - DC Higher Geography: 2013 +[XMP, XMP-dwc, Location] dctermsLocationHigherGeographyID - DC Higher Geography ID: 2013 +[XMP, XMP-dwc, Location] dctermsLocationIsland - DC Island: 2013 +[XMP, XMP-dwc, Location] dctermsLocationIslandGroup - DC Island Group: 2013 +[XMP, XMP-dwc, Location] dctermsLocationLocality - DC Locality: 2013 +[XMP, XMP-dwc, Location] dctermsLocationLocationAccordingTo - DC Location According To: 2013 +[XMP, XMP-dwc, Location] dctermsLocationLocationID - DC Location ID: 2013 +[XMP, XMP-dwc, Location] dctermsLocationLocationRemarks - DC Location Remarks: 2013 +[XMP, XMP-dwc, Location] dctermsLocationMaximumDepthInMeters - DC Maximum Depth In Meters: 2013 +[XMP, XMP-dwc, Location] dctermsLocationMaximumDistanceAboveSurfaceInMeters - DC Maximum Distance Above Surface In Meters: 2013 +[XMP, XMP-dwc, Location] dctermsLocationMaximumElevationInMeters - DC Maximum Elevation In Meters: 2013 +[XMP, XMP-dwc, Location] dctermsLocationMinimumDepthInMeters - DC Minimum Depth In Meters: 2013 +[XMP, XMP-dwc, Location] dctermsLocationMinimumDistanceAboveSurfaceInMeters - DC Minimum Distance Above Surface In Meters: 2013 +[XMP, XMP-dwc, Location] dctermsLocationMinimumElevationInMeters - DC Minimum Elevation In Meters: 2013 +[XMP, XMP-dwc, Location] dctermsLocationMunicipality - DC Municipality: 2013 +[XMP, XMP-dwc, Location] dctermsLocationPointRadiusSpatialFit - DC Point Radius Spatial Fit: 2013 +[XMP, XMP-dwc, Location] dctermsLocationStateProvince - DC State Province: 2013 +[XMP, XMP-dwc, Location] dctermsLocationVerbatimCoordinateSystem - DC Verbatim Coordinate System: 2013 +[XMP, XMP-dwc, Location] dctermsLocationVerbatimCoordinates - DC Verbatim Coordinates: 2013 +[XMP, XMP-dwc, Location] dctermsLocationVerbatimDepth - DC Verbatim Depth: 2013 +[XMP, XMP-dwc, Location] dctermsLocationVerbatimElevation - DC Verbatim Elevation: 2013 +[XMP, XMP-dwc, Location] dctermsLocationVerbatimLatitude - DC Verbatim Latitude: 2013 +[XMP, XMP-dwc, Location] dctermsLocationVerbatimLocality - DC Verbatim Locality: 2013 +[XMP, XMP-dwc, Location] dctermsLocationVerbatimLongitude - DC Verbatim Longitude: 2013 +[XMP, XMP-dwc, Location] dctermsLocationVerbatimSRS - DC Verbatim SRS: 2013 +[XMP, XMP-dwc, Location] dctermsLocationVerticalDatum - DC Vertical Datum: 2013 +[XMP, XMP-dwc, Location] dctermsLocationWaterBody - DC Water Body: 2013 diff --git a/ExifTool/t/XMP_42.out b/ExifTool/t/XMP_42.out new file mode 100644 index 0000000..9706047 --- /dev/null +++ b/ExifTool/t/XMP_42.out @@ -0,0 +1,8 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 7.50 +[XMP, XMP-xmpNote, Other] HasExtendedXMP - Has Extended XMP: 04B9E48040A30A6308713BD1E4223B41 +[XMP, XMP-pdf, Author] Author - Author: PhilToo +[XMP, XMP-pdf, Time] CreationDate - Creation Date: 2008:10:20 19:54:15 +[XMP, XMP-pdf, Author] Creator - Creator: Guess Who +[XMP, XMP-pdf, Time] ModDate - Mod Date: 2008:10:20 19:54:15 +[XMP, XMP-pdf, Author] Producer - Producer: Just ExifTool again +[XMP, XMP-pdf, Image] Title - Title: PDF Title diff --git a/ExifTool/t/XMP_43.out b/ExifTool/t/XMP_43.out new file mode 100644 index 0000000..8b029ac --- /dev/null +++ b/ExifTool/t/XMP_43.out @@ -0,0 +1,6 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 9.97 +[XMP, XMP-xxxx, Unknown] xxxx:Test - Test: trout +[XMP, XMP-tmp0, Unknown] tmp0:Test - Test: tabby +[XMP, XMP-xmp, Image] Rating - Rating: 2 +[XMP, XMP-dc, Image] subject - Subject: x +[XMP, XMP-dc, Image] title - Title: a title diff --git a/ExifTool/t/XMP_44.out b/ExifTool/t/XMP_44.out new file mode 100644 index 0000000..fbaa762 --- /dev/null +++ b/ExifTool/t/XMP_44.out @@ -0,0 +1,35 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 9.97'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:dc='http://purl.org/dc/elements/1.1/'> + <dc:subject> + <rdf:Bag> + <rdf:li>changed</rdf:li> + </rdf:Bag> + </dc:subject> + <dc:title> + <rdf:Alt> + <rdf:li xml:lang='x-default'>a title</rdf:li> + </rdf:Alt> + </dc:title> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:tmp0='http://testtag.com/feline/1.0/'> + <tmp0:Test>tabby</tmp0:Test> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:xmp='http://ns.adobe.com/xap/1.0/'> + <xmp:Rating>2</xmp:Rating> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:xxxx='http://testtag.com/fish/1.0/'> + <xxxx:Test>trout</xxxx:Test> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/XMP_45.out b/ExifTool/t/XMP_45.out new file mode 100644 index 0000000..5e45c66 --- /dev/null +++ b/ExifTool/t/XMP_45.out @@ -0,0 +1,23 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 10.08'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:exif='http://ns.adobe.com/exif/1.0/'> + <exif:Flash rdf:parseType='Resource'/> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:mwg-rs='http://www.metadataworkinggroup.com/schemas/regions/'> + <mwg-rs:Regions rdf:parseType='Resource'> + <mwg-rs:RegionList> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'/> + <rdf:li rdf:parseType='Resource'/> + </rdf:Bag> + </mwg-rs:RegionList> + </mwg-rs:Regions> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/XMP_46.out b/ExifTool/t/XMP_46.out new file mode 100644 index 0000000..fb5ffed --- /dev/null +++ b/ExifTool/t/XMP_46.out @@ -0,0 +1 @@ +[XMP, XMP-dc, Image] subject - Subject: Exif // XMP diff --git a/ExifTool/t/XMP_47.out b/ExifTool/t/XMP_47.out new file mode 100644 index 0000000..1923ad1 --- /dev/null +++ b/ExifTool/t/XMP_47.out @@ -0,0 +1,2 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 11.57 +[XMP, XMP-iptcExt, Location] LocationShown - Location Shown: [{City=Manchester,CountryCode=GB},{City=Lyon,CountryCode=FR},{City=Frankfurt,CountryCode=DE}] diff --git a/ExifTool/t/XMP_48.out b/ExifTool/t/XMP_48.out new file mode 100644 index 0000000..cebd5e2 --- /dev/null +++ b/ExifTool/t/XMP_48.out @@ -0,0 +1,2 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 11.57 +[XMP, XMP-iptcExt, Location] LocationShown - Location Shown: [{City=Oxford,CountryCode=GB},{City=Paris,CountryCode=FR},{City=Munich,CountryCode=DE}] diff --git a/ExifTool/t/XMP_49.out b/ExifTool/t/XMP_49.out new file mode 100644 index 0000000..4b85104 --- /dev/null +++ b/ExifTool/t/XMP_49.out @@ -0,0 +1,2 @@ +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 11.58 +[XMP, XMP-iptcExt, Author] AboutCvTerm - About Cv Term: [{CvId=1,CvTermName=a},{CvId=2,CvTermName=two},{CvId=3,CvTermName=b},{CvTermName=c}] diff --git a/ExifTool/t/XMP_5.out b/ExifTool/t/XMP_5.out new file mode 100644 index 0000000..5e13863 --- /dev/null +++ b/ExifTool/t/XMP_5.out @@ -0,0 +1,30 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileSize - File Size: 6.0 kB +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 4.73 +[XMP, XMP-rdf, Document] about - About: uuid:80056b68-1045-fa97-3401-6f4ed84cd53d +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Image] Category - Category: 1 +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image] Headline - Headline: No headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Author] Source - Source: I'm the source +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference? +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ diff --git a/ExifTool/t/XMP_50.out b/ExifTool/t/XMP_50.out new file mode 100644 index 0000000..d524ca3 --- /dev/null +++ b/ExifTool/t/XMP_50.out @@ -0,0 +1,35 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 11.64'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:plus='http://ns.useplus.org/ldf/xmp/1.0/'> + <plus:Custom1> + <rdf:Bag> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu1</rdf:li> + <rdf:li xml:lang='de'>de1</rdf:li> + <rdf:li xml:lang='fr'>fr1</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu2</rdf:li> + <rdf:li xml:lang='de'/> + <rdf:li xml:lang='fr'/> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu3</rdf:li> + <rdf:li xml:lang='de'>de3</rdf:li> + <rdf:li xml:lang='fr'>fr3</rdf:li> + </rdf:Alt> + </rdf:li> + </rdf:Bag> + </plus:Custom1> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/XMP_52.out b/ExifTool/t/XMP_52.out new file mode 100644 index 0000000..1d0d47a --- /dev/null +++ b/ExifTool/t/XMP_52.out @@ -0,0 +1,39 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 11.64'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:plus='http://ns.useplus.org/ldf/xmp/1.0/'> + <plus:Custom1> + <rdf:Bag> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu1</rdf:li> + <rdf:li xml:lang='de'>de1</rdf:li> + <rdf:li xml:lang='fr'>fr1</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu2</rdf:li> + <rdf:li xml:lang='fr'/> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu3</rdf:li> + <rdf:li xml:lang='de'>de3</rdf:li> + <rdf:li xml:lang='fr'>fr3</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>new</rdf:li> + </rdf:Alt> + </rdf:li> + </rdf:Bag> + </plus:Custom1> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/XMP_53.out b/ExifTool/t/XMP_53.out new file mode 100644 index 0000000..188a818 --- /dev/null +++ b/ExifTool/t/XMP_53.out @@ -0,0 +1,67 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 11.64'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:plus='http://ns.useplus.org/ldf/xmp/1.0/'> + <plus:Custom1> + <rdf:Bag> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu1</rdf:li> + <rdf:li xml:lang='de'>de1</rdf:li> + <rdf:li xml:lang='fr'>fr1</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu2</rdf:li> + <rdf:li xml:lang='fr'/> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu3</rdf:li> + <rdf:li xml:lang='de'>de3</rdf:li> + <rdf:li xml:lang='fr'>fr3</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>a</rdf:li> + <rdf:li xml:lang='fr'>a-fr</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>b</rdf:li> + <rdf:li xml:lang='fr'/> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>c</rdf:li> + <rdf:li xml:lang='fr'>c-fr</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='fr'>d-fr</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='fr'/> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='fr'>f-fr</rdf:li> + </rdf:Alt> + </rdf:li> + </rdf:Bag> + </plus:Custom1> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/XMP_54.out b/ExifTool/t/XMP_54.out new file mode 100644 index 0000000..8969d4d --- /dev/null +++ b/ExifTool/t/XMP_54.out @@ -0,0 +1 @@ +[XMP, XMP-iptcExt, Image] ImageRegion - Image Region: [{RCtype=[{Identifier=[x]}],XMP-exif:Flash={Fired=True,Return=Return detected}}] diff --git a/ExifTool/t/XMP_6.out b/ExifTool/t/XMP_6.out new file mode 100644 index 0000000..d3c09dd --- /dev/null +++ b/ExifTool/t/XMP_6.out @@ -0,0 +1,89 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: XMP_6_failed.xmp +[File, System, Other] Directory - Directory: t +[File, System, Other] FileSize - File Size: 5.8 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2022:06:01 14:16:58-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:58-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2022:06:01 14:16:58-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-x, Document] xmptk - XMP Toolkit: Image::ExifTool 12.42 +[XMP, XMP-dc, Author] creator - Creator: Phil Harvey +[XMP, XMP-dc, Image] description - Description: UTF-16 (big-endian) encoded XMP +[XMP, XMP-dc, Author] rights - Rights: Copyright 2004 Phil Harvey +[XMP, XMP-dc, Image] subject - Subject: ExifTool, Test, XMP +[XMP, XMP-dc, Image] title - Title: Test IPTC picture +[XMP, XMP-exif, Image] ApertureValue - Aperture Value: 3.5 +[XMP, XMP-exif, Image] BrightnessValue - Brightness Value: 2 +[XMP, XMP-exif, Image] ColorSpace - Color Space: sRGB +[XMP, XMP-exif, Image] ComponentsConfiguration - Components Configuration: Y, Cb, Cr, - +[XMP, XMP-exif, Image] CompressedBitsPerPixel - Compressed Bits Per Pixel: 1.6 +[XMP, XMP-exif, Time] DateTimeOriginal - Date/Time Original: 2001:05:19 18:36:41 +[XMP, XMP-exif, Image] ExifVersion - Exif Version: 0210 +[XMP, XMP-exif, Image] ExposureBiasValue - Exposure Compensation: 0 +[XMP, XMP-exif, Camera] ExposureProgram - Exposure Program: Program AE +[XMP, XMP-exif, Image] FNumber - F Number: 3.5 +[XMP, XMP-exif, Image] FileSource - File Source: Digital Camera +[XMP, XMP-exif, Camera] FlashFired - Flash Fired: True +[XMP, XMP-exif, Camera] FlashFunction - Flash Function: False +[XMP, XMP-exif, Camera] FlashMode - Flash Mode: Unknown +[XMP, XMP-exif, Camera] FlashRedEyeMode - Flash Red Eye Mode: False +[XMP, XMP-exif, Camera] FlashReturn - Flash Return: No return detection +[XMP, XMP-exif, Image] FlashpixVersion - Flashpix Version: 0100 +[XMP, XMP-exif, Camera] FocalLength - Focal Length: 6.0 mm +[XMP, XMP-exif, Camera] FocalPlaneResolutionUnit - Focal Plane Resolution Unit: cm +[XMP, XMP-exif, Camera] FocalPlaneXResolution - Focal Plane X Resolution: 3053 +[XMP, XMP-exif, Camera] FocalPlaneYResolution - Focal Plane Y Resolution: 3053 +[XMP, XMP-exif, Image] ISOSpeedRatings - ISO: 100 +[XMP, XMP-exif, Camera] MaxApertureValue - Max Aperture Value: 3.5 +[XMP, XMP-exif, Camera] MeteringMode - Metering Mode: Multi-segment +[XMP, XMP-exif, Image] PixelXDimension - Exif Image Width: 100 +[XMP, XMP-exif, Image] PixelYDimension - Exif Image Height: 80 +[XMP, XMP-exif, Image] SceneType - Scene Type: Directly photographed +[XMP, XMP-exif, Camera] SensingMethod - Sensing Method: One-chip color area +[XMP, XMP-exif, Image] ShutterSpeedValue - Shutter Speed Value: 1/64 +[XMP, XMP-photoshop, Author] AuthorsPosition - Authors Position: My Position +[XMP, XMP-photoshop, Author] CaptionWriter - Caption Writer: I wrote it +[XMP, XMP-photoshop, Image] Category - Category: 1 +[XMP, XMP-photoshop, Location] City - City: Kingston +[XMP, XMP-photoshop, Location] Country - Country: Canada +[XMP, XMP-photoshop, Author] Credit - Credit: My Credit +[XMP, XMP-photoshop, Time] DateCreated - Date Created: 2004:02:26 +[XMP, XMP-photoshop, Image] Headline - Headline: No headline +[XMP, XMP-photoshop, Image] Instructions - Instructions: What instructions +[XMP, XMP-photoshop, Author] Source - Source: I'm the source +[XMP, XMP-photoshop, Location] State - State: Ont +[XMP, XMP-photoshop, Image] SupplementalCategories - Supplemental Categories: amazing, image, utilities +[XMP, XMP-photoshop, Image] TransmissionReference - Transmission Reference: What is a transmission reference? +[XMP, XMP-photoshop, Image] Urgency - Urgency: 8 (least urgent) +[XMP, XMP-tiff, Author] Artist - Artist: Phil Harvey +[XMP, XMP-tiff, Image] BitsPerSample - Bits Per Sample: 8 +[XMP, XMP-tiff, Image] Compression - Compression: JPEG (old-style) +[XMP, XMP-tiff, Author] Copyright - Copyright: Copyright 2004 Phil Harvey +[XMP, XMP-tiff, Image] ImageDescription - Image Description: A witty caption +[XMP, XMP-tiff, Image] ImageLength - Image Height: 8 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 8 +[XMP, XMP-tiff, Camera] Make - Make: FUJIFILM +[XMP, XMP-tiff, Camera] Model - Camera Model Name: FinePix2400Zoom +[XMP, XMP-tiff, Image] Orientation - Orientation: Horizontal (normal) +[XMP, XMP-tiff, Image] ResolutionUnit - Resolution Unit: inches +[XMP, XMP-tiff, Image] Software - Software: Adobe Photoshop 7.0 +[XMP, XMP-tiff, Image] XResolution - X Resolution: 72 +[XMP, XMP-tiff, Image] YCbCrPositioning - Y Cb Cr Positioning: Co-sited +[XMP, XMP-tiff, Image] YCbCrSubSampling - Y Cb Cr Sub Sampling: YCbCr4:2:0 (2 2) +[XMP, XMP-tiff, Image] YResolution - Y Resolution: 72 +[XMP, XMP-xmp, Time] CreateDate - Create Date: 2001:05:19 18:36:41 +[XMP, XMP-xmp, Time] ModifyDate - Modify Date: 2004:02:26 09:36:46 +[XMP, XMP-xmpBJ, Other] JobRefName - Job Ref Name: My Job +[XMP, XMP-xmpMM, Other] DocumentID - Document ID: adobe:docid:photoshop:4cc7b857-69d0-11d8-8ac4-bb59c92f0d9a +[XMP, XMP-xmpRights, Author] Marked - Marked: False +[XMP, XMP-xmpRights, Author] WebStatement - Web Statement: https://exiftool.org/ +[Composite, Composite, Image] Exif-Aperture - Aperture: 3.5 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/64 +[Composite, Composite, Camera] XMP-Flash - Flash: Fired +[Composite, Composite, Image] Exif-ImageSize - Image Size: 8x8 +[Composite, Composite, Image] Exif-LightValue - Light Value: 9.6 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 0.000064 +[Composite, Composite, Camera] Exif-FocalLength35efl - Focal Length: 6.0 mm diff --git a/ExifTool/t/XMP_7.out b/ExifTool/t/XMP_7.out new file mode 100644 index 0000000..7ed546f --- /dev/null +++ b/ExifTool/t/XMP_7.out @@ -0,0 +1,118 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 9.75'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:dc='http://purl.org/dc/elements/1.1/'> + <dc:format>image/jpeg</dc:format> + <dc:rights> + <rdf:Alt> + <rdf:li xml:lang='x-default'>© Copyright Someone Else</rdf:li> + </rdf:Alt> + </dc:rights> + <dc:subject> + <rdf:Bag> + <rdf:li>test1</rdf:li> + <rdf:li>&amp;-<![CDATA[&amp;]]>-&amp;</rdf:li> + <rdf:li>char test: &amp; &gt; &lt; &#39; &quot;</rdf:li> + </rdf:Bag> + </dc:subject> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:exif='http://ns.adobe.com/exif/1.0/'> + <exif:ApertureValue>95/32</exif:ApertureValue> + <exif:ColorSpace>1</exif:ColorSpace> + <exif:ComponentsConfiguration> + <rdf:Seq> + <rdf:li>1</rdf:li> + <rdf:li>2</rdf:li> + <rdf:li>3</rdf:li> + <rdf:li>0</rdf:li> + </rdf:Seq> + </exif:ComponentsConfiguration> + <exif:CompressedBitsPerPixel>3/1</exif:CompressedBitsPerPixel> + <exif:CustomRendered>0</exif:CustomRendered> + <exif:DateTimeDigitized>2005-06-09T20:09:27+02:00</exif:DateTimeDigitized> + <exif:DateTimeOriginal>2005-06-09T20:09:27+02:00</exif:DateTimeOriginal> + <exif:DigitalZoomRatio>2272/2272</exif:DigitalZoomRatio> + <exif:ExifVersion>0.2.2.0</exif:ExifVersion> + <exif:ExposureBiasValue>-3/3</exif:ExposureBiasValue> + <exif:ExposureMode>1</exif:ExposureMode> + <exif:ExposureTime>4/10</exif:ExposureTime> + <exif:FNumber>28/10</exif:FNumber> + <exif:FileSource>3</exif:FileSource> + <exif:Flash rdf:parseType='Resource'> + <exif:Fired>False</exif:Fired> + <exif:Function>False</exif:Function> + <exif:Mode>2</exif:Mode> + <exif:RedEyeMode>False</exif:RedEyeMode> + <exif:Return>0</exif:Return> + </exif:Flash> + <exif:FlashpixVersion>0.1.0.0</exif:FlashpixVersion> + <exif:FocalLength>5800/1000</exif:FocalLength> + <exif:FocalPlaneResolutionUnit>2</exif:FocalPlaneResolutionUnit> + <exif:FocalPlaneXResolution>2272000/224</exif:FocalPlaneXResolution> + <exif:FocalPlaneYResolution>1704000/168</exif:FocalPlaneYResolution> + <exif:MaxApertureValue>95/32</exif:MaxApertureValue> + <exif:MeteringMode>5</exif:MeteringMode> + <exif:NativeDigest>36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;CE459FE772DF591DDF5F63CA3F2A087B</exif:NativeDigest> + <exif:PixelXDimension>1136</exif:PixelXDimension> + <exif:PixelYDimension>852</exif:PixelYDimension> + <exif:SceneCaptureType>0</exif:SceneCaptureType> + <exif:SensingMethod>2</exif:SensingMethod> + <exif:ShutterSpeedValue>42/32</exif:ShutterSpeedValue> + <exif:WhiteBalance>0</exif:WhiteBalance> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:photoshop='http://ns.adobe.com/photoshop/1.0/'> + <photoshop:ColorMode>3</photoshop:ColorMode> + <photoshop:History/> + <photoshop:ICCProfile>sRGB IEC61966-2.1</photoshop:ICCProfile> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:tiff='http://ns.adobe.com/tiff/1.0/'> + <tiff:BitsPerSample> + <rdf:Seq> + <rdf:li>8</rdf:li> + <rdf:li>8</rdf:li> + <rdf:li>8</rdf:li> + </rdf:Seq> + </tiff:BitsPerSample> + <tiff:ImageLength>852</tiff:ImageLength> + <tiff:ImageWidth>1136</tiff:ImageWidth> + <tiff:Make>Canon</tiff:Make> + <tiff:Model>Canon DIGITAL IXUS 40</tiff:Model> + <tiff:NativeDigest>256,257,258,259,262,274,277,284,530,531,282,283,296,301,318,319,529,532,306,270,271,272,305,315,33432;D11E7FC551E751BE9E616260834B2A1D</tiff:NativeDigest> + <tiff:Orientation>1</tiff:Orientation> + <tiff:ResolutionUnit>2</tiff:ResolutionUnit> + <tiff:XResolution>1800000/10000</tiff:XResolution> + <tiff:YCbCrPositioning>1</tiff:YCbCrPositioning> + <tiff:YResolution>1800000/10000</tiff:YResolution> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:xmp='http://ns.adobe.com/xap/1.0/'> + <xmp:CreateDate>2005-11-21T17:07:14+01:00</xmp:CreateDate> + <xmp:CreatorTool>Adobe Photoshop CS2 Windows</xmp:CreatorTool> + <xmp:Label>Blue</xmp:Label> + <xmp:MetadataDate>2005-11-21T17:11:31+01:00</xmp:MetadataDate> + <xmp:ModifyDate>2005-11-21T17:07:14+01:00</xmp:ModifyDate> + <xmp:Rating>3</xmp:Rating> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:stRef='http://ns.adobe.com/xap/1.0/sType/ResourceRef#' + xmlns:xmpMM='http://ns.adobe.com/xap/1.0/mm/'> + <xmpMM:DerivedFrom rdf:parseType='Resource'> + <stRef:documentID>adobe:docid:photoshop:f481cc67-d8f5-11d9-a31e-a1606d941d83</stRef:documentID> + <stRef:instanceID>adobe:docid:photoshop:f481cc67-d8f5-11d9-a31e-a1606d941d83</stRef:instanceID> + </xmpMM:DerivedFrom> + <xmpMM:DocumentID>uuid:7A9636BAA85ADA11B611C7FA524F1F71</xmpMM:DocumentID> + <xmpMM:InstanceID>uuid:7B9636BAA85ADA11B611C7FA524F1F71</xmpMM:InstanceID> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/XMP_8.out b/ExifTool/t/XMP_8.out new file mode 100644 index 0000000..7861796 --- /dev/null +++ b/ExifTool/t/XMP_8.out @@ -0,0 +1,20 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.17 +[File, System, Other] FileName - File Name: XMP2.xmp +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 890 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2006:09:01 09:59:28-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2021:02:02 11:30:54-05:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2017:12:27 12:00:59-05:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: XMP +[File, File, Other] FileTypeExtension - File Type Extension: xmp +[File, File, Other] MIMEType - MIME Type: application/rdf+xml +[XMP, XMP-dc, Image] title - Title: XMP nodeID test (XMP written by f-spot) +[XMP, XMP-aux, Camera] Lens - Lens: 18.0 - 55.0mm +[XMP, XMP-aux, Camera] SerialNumber - Serial Number: 0123456789 +[XMP, XMP-tiff, Image] ImageLength - Image Height: 2048 +[XMP, XMP-tiff, Image] ImageWidth - Image Width: 3072 +[XMP, XMP-dc, Image] subject - Subject: ExifTool, XMP, nodeID, test +[Composite, Composite, Image] Exif-ImageSize - Image Size: 3072x2048 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 6.3 +[Composite, Composite, Camera] Exif-LensID-2 - Lens ID: 18.0-55.0mm diff --git a/ExifTool/t/XMP_9.out b/ExifTool/t/XMP_9.out new file mode 100644 index 0000000..b96a226 --- /dev/null +++ b/ExifTool/t/XMP_9.out @@ -0,0 +1,52 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 9.70'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:aux='http://ns.adobe.com/exif/1.0/aux/'> + <aux:Lens>18.0 - 55.0mm</aux:Lens> + <aux:SerialNumber>0123456789</aux:SerialNumber> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:cc='http://creativecommons.org/ns#'> + <cc:attributionName>something else</cc:attributionName> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:dc='http://purl.org/dc/elements/1.1/'> + <dc:creator> + <rdf:Seq> + <rdf:li>Phil</rdf:li> + </rdf:Seq> + </dc:creator> + <dc:subject> + <rdf:Bag> + <rdf:li>ExifTool</rdf:li> + <rdf:li>XMP</rdf:li> + <rdf:li>nodeID</rdf:li> + <rdf:li>test</rdf:li> + </rdf:Bag> + </dc:subject> + <dc:title>XMP nodeID test (XMP written by f-spot)</dc:title> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:tiff='http://ns.adobe.com/tiff/1.0/'> + <tiff:ImageLength>2048</tiff:ImageLength> + <tiff:ImageWidth>3072</tiff:ImageWidth> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:xmpMM='http://ns.adobe.com/xap/1.0/mm/'> + <xmpMM:Manifest> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <xmpMM:placedXResolution>1</xmpMM:placedXResolution> + </rdf:li> + </rdf:Bag> + </xmpMM:Manifest> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/ZIP.t b/ExifTool/t/ZIP.t new file mode 100644 index 0000000..985b12d --- /dev/null +++ b/ExifTool/t/ZIP.t @@ -0,0 +1,79 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/ZIP.t". + +BEGIN { + $| = 1; print "1..8\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::ZIP; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'ZIP'; +my $testnum = 1; +my $failed; + +# tests 2-3: Extract information from test ZIP and GZIP files +{ + my $exifTool = Image::ExifTool->new; + my $type; + foreach $type (qw(zip gz)) { + ++$testnum; + my $info = $exifTool->ImageInfo("t/images/ZIP.$type"); + notOK() and $failed = 1 unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; + } +} + +# tests 4-7: Extract information from other ZIP-based files (requires Archive::Zip) +{ + my $exifTool = Image::ExifTool->new; + my $file; + foreach $file ('OOXML.docx', 'CaptureOne.eip', 'iWork.numbers', 'OpenDoc.ods') { + ++$testnum; + my $skip = ''; + if (eval 'require Archive::Zip') { + my $info = $exifTool->ImageInfo("t/images/$file"); + notOK() and $failed = 1 unless check($exifTool, $info, $testname, $testnum); + } else { + $skip = ' # skip Requires Archive::Zip'; + } + print "ok $testnum$skip\n"; + } +} + +# pass on any Archive::Zip warning +if ($Image::ExifTool::ZIP::warnString) { + warn $Image::ExifTool::ZIP::warnString; +} + +# print module versions if anything failed +if ($failed) { + my $mod; + warn "\n"; + foreach $mod ('Archive::Zip', 'Compress::Raw::Zlib', 'IO::String') { + my $v; + if (eval "require $mod") { + my $var = $mod . '::VERSION'; + no strict 'refs'; + $v = $$var; + } + my $w = $v ? "version is $v" : 'is not installed'; + warn " ($mod $w)\n"; + } +} + +# test 8: Extract information from test RAR version 5.0 file +{ + my $exifTool = Image::ExifTool->new; + ++$testnum; + my $info = $exifTool->ImageInfo("t/images/ZIP.rar"); + notOK() and $failed = 1 unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/ZIP_2.out b/ExifTool/t/ZIP_2.out new file mode 100644 index 0000000..9f80dfb --- /dev/null +++ b/ExifTool/t/ZIP_2.out @@ -0,0 +1,19 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: ZIP.zip +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 167 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2008:09:04 10:47:13-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: ZIP +[File, File, Other] FileTypeExtension - File Type Extension: zip +[File, File, Other] MIMEType - MIME Type: application/zip +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2008:08:28 09:54:46 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x6e17461a +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 19 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 19 +[ZIP, ZIP, Other] 15 - Zip File Name: test.txt diff --git a/ExifTool/t/ZIP_3.out b/ExifTool/t/ZIP_3.out new file mode 100644 index 0000000..2741f58 --- /dev/null +++ b/ExifTool/t/ZIP_3.out @@ -0,0 +1,18 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: ZIP.gz +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 71 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2009:11:05 11:15:47-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: GZIP +[File, File, Other] FileTypeExtension - File Type Extension: gz +[File, File, Other] MIMEType - MIME Type: application/x-gzip +[ZIP, ZIP, Other] 2 - Compression: Deflated +[ZIP, ZIP, Other] 3 - Flags: FileName, Comment +[ZIP, ZIP, Time] 4 - Modify Date: 2009:11:05 11:09:24-05:00 +[ZIP, ZIP, Other] 8 - Extra Flags: Maximum Compression +[ZIP, ZIP, Other] 9 - Operating System: Unix +[ZIP, ZIP, Other] 10 - Archived File Name: test.txt +[ZIP, ZIP, Other] 11 - Comment: This is a test comment diff --git a/ExifTool/t/ZIP_4.out b/ExifTool/t/ZIP_4.out new file mode 100644 index 0000000..ec1c2af --- /dev/null +++ b/ExifTool/t/ZIP_4.out @@ -0,0 +1,214 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: OOXML.docx +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 7.5 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2009:11:04 13:25:05-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:59-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: DOCX +[File, File, Other] FileTypeExtension - File Type Extension: docx +[File, File, Other] MIMEType - MIME Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document +[File, File, Preview] PreviewImage - Preview Image: (Binary data 251 bytes) +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 1980:01:01 00:00:00 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x815b4431 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 362 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 1489 +[ZIP, ZIP, Other] 15 - Zip File Name: [Content_Types].xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 06:40:40 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x00000000 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 0 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 0 +[ZIP, ZIP, Other] 15 - Zip File Name: _rels/ +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 1980:01:01 00:00:00 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x71b69487 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 270 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 882 +[ZIP, ZIP, Other] 15 - Zip File Name: _rels/.rels +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 07:04:16 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x00000000 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 0 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 0 +[ZIP, ZIP, Other] 15 - Zip File Name: docProps/ +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 07:04:16 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xb1ed5172 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 522 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 1112 +[ZIP, ZIP, Other] 15 - Zip File Name: docProps/app.xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 06:55:46 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xe4eea26b +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 424 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 868 +[ZIP, ZIP, Other] 15 - Zip File Name: docProps/core.xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 06:54:54 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xf1594222 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 661 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 3775 +[ZIP, ZIP, Other] 15 - Zip File Name: docProps/custom.xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 06:45:46 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x20b43d22 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 125 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 251 +[ZIP, ZIP, Other] 15 - Zip File Name: docProps/thumbnail.jpeg +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 06:50:22 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x00000000 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 0 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 0 +[ZIP, ZIP, Other] 15 - Zip File Name: word/ +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 06:40:40 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x00000000 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 0 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 0 +[ZIP, ZIP, Other] 15 - Zip File Name: word/_rels/ +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 1980:01:01 00:00:00 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xf13b554b +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 240 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 817 +[ZIP, ZIP, Other] 15 - Zip File Name: word/_rels/document.xml.rels +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 1980:01:01 00:00:00 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x00c8b869 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 506 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 1559 +[ZIP, ZIP, Other] 15 - Zip File Name: word/document.xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 1980:01:01 00:00:00 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x6caf2e0d +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 332 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 1291 +[ZIP, ZIP, Other] 15 - Zip File Name: word/fontTable.xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 1980:01:01 00:00:00 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x87432dcd +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 716 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 1659 +[ZIP, ZIP, Other] 15 - Zip File Name: word/settings.xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 06:43:48 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xf1e58e0b +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 466 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 1214 +[ZIP, ZIP, Other] 15 - Zip File Name: word/styles.xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 06:40:40 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x00000000 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 0 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 0 +[ZIP, ZIP, Other] 15 - Zip File Name: word/theme/ +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:01 06:44:54 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x6430416a +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 137 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 160 +[ZIP, ZIP, Other] 15 - Zip File Name: word/theme/theme1.xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 1980:01:01 00:00:00 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x69d759c3 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 192 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 276 +[ZIP, ZIP, Other] 15 - Zip File Name: word/webSettings.xml +[XML, XML, Document] Template - Template: Normal +[XML, XML, Document] TotalTime - Total Edit Time: 7 minutes +[XML, XML, Document] Pages - Pages: 1 +[XML, XML, Document] Words - Words: 7 +[XML, XML, Document] Characters - Characters: 42 +[XML, XML, Document] Application - Application: Microsoft Macintosh Word +[XML, XML, Document] DocSecurity - Doc Security: None +[XML, XML, Document] Lines - Lines: 7 +[XML, XML, Document] Paragraphs - Paragraphs: 4 +[XML, XML, Document] ScaleCrop - Scale Crop: No +[XML, XML, Document] HeadingPairs - Heading Pairs: Title, 1 +[XML, XML, Document] TitlesOfParts - Titles Of Parts: The document title +[XML, XML, Document] Manager - Manager: Manager: Self +[XML, XML, Document] Company - Company: Company - ExifTool +[XML, XML, Document] LinksUpToDate - Links Up To Date: No +[XML, XML, Document] CharactersWithSpaces - Characters With Spaces: 45 +[XML, XML, Document] SharedDoc - Shared Doc: No +[XML, XML, Document] HyperlinkBase - Hyperlink Base: the hyperlink base goes here! +[XML, XML, Document] HyperlinksChanged - Hyperlinks Changed: No +[XML, XML, Document] AppVersion - App Version: 12.0000 +[XML, XML, Document] keywords - Keywords: keyword 1, keyword 2, keyword 3 +[XML, XML, Author] lastModifiedBy - Last Modified By: Jeff +[XML, XML, Document] revision - Revision Number: 3 +[XML, XML, Time] created - Create Date: 2009:10:24 01:41:00Z +[XML, XML, Time] modified - Modify Date: 2009:10:24 01:48:00Z +[XML, XML, Document] category - Category: category goes here +[XML, XML, Document] CheckedBy - Checked By: Checked by +[XML, XML, Document] Client - Client: Client +[XML, XML, Time] DateCompleted - Date Completed: 2009:10:23 07:00:00Z +[XML, XML, Document] Department - Department: Department +[XML, XML, Document] Destination - Destination: Destination +[XML, XML, Document] Disposition - Disposition: Disposition +[XML, XML, Document] Division - Division: Division +[XML, XML, Document] DocumentNumber - Document Number: 35 +[XML, XML, Author] Editor - Editor: Editor +[XML, XML, Document] ForwardTo - Forward To: Forward to +[XML, XML, Document] Group - Group: Group +[XML, XML, Document] Language - Language: Language +[XML, XML, Document] Mailstop - Mailstop: Mailstop +[XML, XML, Document] Matter - Matter: Matter +[XML, XML, Document] Office - Office: Office +[XML, XML, Author] Owner - Owner: Owner +[XML, XML, Document] Project - Project: Project +[XML, XML, Document] Publisher - Publisher: Publisher +[XML, XML, Document] Purpose - Purpose: Purpose +[XML, XML, Document] ReceivedFrom - Received From: Received from +[XML, XML, Document] RecordedBy - Recorded By: Recorded by +[XML, XML, Time] RecordedDate - Recorded Date: 2009:10:20 07:00:00Z +[XML, XML, Document] Reference - Reference: Reference +[XML, XML, Document] Source - Source: Source +[XML, XML, Document] Status - Status: Status +[XML, XML, Document] TelephoneNumber - Telephone Number: 415-399-9921 +[XML, XML, Document] Typist - Typist: Typist +[XML, XML, Document] ACustomField - A Custom Field: A Custom field contents +[XMP, XMP-dc, Image] title - Title: The document title +[XMP, XMP-dc, Image] subject - Subject: the subject +[XMP, XMP-dc, Author] creator - Creator: Author: Jeff +[XMP, XMP-dc, Image] description - Description: here are my comments diff --git a/ExifTool/t/ZIP_5.out b/ExifTool/t/ZIP_5.out new file mode 100644 index 0000000..f216ed2 --- /dev/null +++ b/ExifTool/t/ZIP_5.out @@ -0,0 +1,136 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: CaptureOne.eip +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 5.4 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2009:11:04 08:41:15-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:16:59-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: EIP +[File, File, Other] FileTypeExtension - File Type Extension: eip +[File, File, Other] MIMEType - MIME Type: application/x-captureone +[File, File, Image] ExifByteOrder - Exif Byte Order: Little-endian (Intel, II) +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:04:03 10:14:36 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x03b54756 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 576 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 576 +[ZIP, ZIP, Other] 15 - Zip File Name: 0.IIQ +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:04:03 10:14:36 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xff76e911 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 244 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 244 +[ZIP, ZIP, Other] 15 - Zip File Name: manifest.xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:03 19:14:12 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xb7f04729 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 130 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 130 +[ZIP, ZIP, Other] 15 - Zip File Name: manifest50.xml +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:04:03 10:14:44 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x1ad000e6 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 460 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 460 +[ZIP, ZIP, Other] 15 - Zip File Name: CaptureOne/Settings45/0.IIQ.cos +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:03 19:55:32 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xc3481f07 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 3280 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 3280 +[ZIP, ZIP, Other] 15 - Zip File Name: CaptureOne/Settings50/0.IIQ.cos +[EXIF, IFD0, Image] 254 - Subfile Type: Full-resolution image +[EXIF, IFD0, Image] 256 - Image Width: 561 +[EXIF, IFD0, Image] 257 - Image Height: 420 +[EXIF, IFD0, Image] 258 - Bits Per Sample: 8 8 8 +[EXIF, IFD0, Image] 259 - Compression: Uncompressed +[EXIF, IFD0, Image] 262 - Photometric Interpretation: RGB +[EXIF, IFD0, Camera] 271 - Make: Phase One A/S +[EXIF, IFD0, Camera] 272 - Camera Model Name: P65+ +[EXIF, IFD0, Image] 273 - Strip Offsets: 32 +[EXIF, IFD0, Image] 277 - Samples Per Pixel: 3 +[EXIF, IFD0, Image] 278 - Rows Per Strip: 420 +[EXIF, IFD0, Image] 279 - Strip Byte Counts: 20 +[EXIF, IFD0, Image] 282 - X Resolution: 300 +[EXIF, IFD0, Image] 283 - Y Resolution: 300 +[EXIF, IFD0, Image] 284 - Planar Configuration: Chunky +[EXIF, IFD0, Image] 296 - Resolution Unit: inches +[EXIF, ExifIFD, Image] 33434 - Exposure Time: 1/15 +[EXIF, ExifIFD, Image] 34855 - ISO: 200 +[EXIF, ExifIFD, Image] 36864 - Exif Version: 0220 +[EXIF, ExifIFD, Time] 36867 - Date/Time Original: 2009:04:03 15:15:20 +[EXIF, ExifIFD, Time] 36868 - Create Date: 2009:04:03 15:15:20 +[EXIF, ExifIFD, Image] 37122 - Compressed Bits Per Pixel: 3 +[EXIF, ExifIFD, Image] 37377 - Shutter Speed Value: 1/15 +[EXIF, ExifIFD, Camera] 37384 - Light Source: Other +[EXIF, ExifIFD, Image] 40962 - Exif Image Width: 4490 +[EXIF, ExifIFD, Image] 40963 - Exif Image Height: 3364 +[EXIF, ExifIFD, Camera] 41987 - White Balance: Unknown (5) +[EXIF, ExifIFD, Image] 42016 - Image Unique ID: 00E058000066000C01010E13F5000951 +[XML, XML, Image] CompatibleVersion - Compatible Version: 13 +[XML, XML, Image] UUID - UUID: 133ACDE8-EF48-4978-9AED-5C6B4DDE9CBF +[XML, XML, Image] Version - Version: 13 +[XML, XML, Image] UUID - UUID: 43C0DD50-C6FE-4880-A275-7EA545CE1347 +[XML, XML, Image] Basic_Rating - Basic Rating: 3 +[XML, XML, Image] Color_tag_index - Color tag index: 4 +[XML, XML, Image] USMAmount - USM Amount: 140 +[XML, XML, Image] LensGeoAbr - Lens Geo Abr: 0.0,0.0000;26929.9,-2103.9001;53859.8,-16831.2012|0.0|0 +[XML, XML, Image] Rotation - Rotation: 0 +[XML, XML, Image] LensId - Lens Id: 1 +[XML, XML, Image] Shadow - Shadow: 0.000000;0.000000;0.000000;0.000000 +[XML, XML, Image] GCurve - G Curve: +[XML, XML, Image] WhiteBalance - White Balance: 1.391000;1.000000;1.474000 +[XML, XML, Image] Exposure - Exposure: 0 +[XML, XML, Image] LensOpticCenter - Lens Optic Center: 0.000000|0.000000 +[XML, XML, Image] Vignetting - Vignetting: 0.000000|0|0|1 +[XML, XML, Image] USMRadius - USM Radius: 1 +[XML, XML, Image] ShadowRecovery - Shadow Recovery: 0 +[XML, XML, Image] GCurveB - G Curve B: +[XML, XML, Image] LensTilt - Lens Tilt: 0 +[XML, XML, Image] USMThreshold - USM Threshold: 1 +[XML, XML, Image] FilmCurve - Film Curve: PhaseOneP65+-Film Standard.fcrv +[XML, XML, Image] CNRAmount - CNR Amount: 34 +[XML, XML, Image] LensShift - Lens Shift: 0 +[XML, XML, Image] GCurveG - G Curve G: +[XML, XML, Image] ICCProfile - ICC Profile: PhaseOneP65+-Flash.icm +[XML, XML, Image] Contrast - Contrast: 0 +[XML, XML, Image] BRAmount - BR Amount: 0 +[XML, XML, Image] LensTiltDirection - Lens Tilt Direction: 0 +[XML, XML, Image] Crop - Crop: 0.000000;0.000000;0.000000;0.000000 +[XML, XML, Image] NRAmount - NR Amount: 25 +[XML, XML, Image] ColorBalance - Color Balance: 1.000000;1.000000;1.000000;1.000000 +[XML, XML, Image] Highlight - Highlight: 1.000000;1.000000;1.000000;1.000000 +[XML, XML, Image] HighlightRecovery - Highlight Recovery: 0 +[XML, XML, Image] LensShiftDirection - Lens Shift Direction: 0 +[XML, XML, Image] Midtone - Midtone: 0.000000;0.000000;0.000000;0.000000 +[XML, XML, Image] LensOpticCenterEnabled - Lens Optic Center Enabled: 0 +[XML, XML, Image] TargetShadow - Target Shadow: 0.000000;0.000000;0.000000;0.000000 +[XML, XML, Image] CleanLongExposureAmount - Clean Long Exposure Amount: 0 +[XML, XML, Image] Clarity - Clarity: 0.000000 +[XML, XML, Image] Brightness - Brightness: 0 +[XML, XML, Image] LensRadialBlur - Lens Radial Blur: 33662.4,9.4255|33662.4,7.4057|0.0|0 +[XML, XML, Image] TargetEnable - Target Enable: 1 +[XML, XML, Image] Moire - Moire: 0.000000;8 +[XML, XML, Image] Saturation - Saturation: 0 +[XML, XML, Image] GCurveR - G Curve R: +[XML, XML, Image] ColorCorrections - Color Corrections: (Binary data 887 bytes) +[XML, XML, Image] PurpleFringing - Purple Fringing: 0 +[XML, XML, Image] TargetHighlight - Target Highlight: 1.000000;1.000000;1.000000;1.000000 +[XML, XML, Image] Rotation - Rotation: 90 +[XML, XML, Image] Crop - Crop: 2087.088867;1690.806274;3868.893555;3158.520264 +[XML, XML, Image] NRAmount - NR Amount: 18.59756112098694 +[XML, XML, Image] Rating - Rating: 3 +[Composite, Composite, Image] Exif-ImageSize - Image Size: 4490x3364 +[Composite, Composite, Image] Exif-Megapixels - Megapixels: 15.1 +[Composite, Composite, Image] Exif-ShutterSpeed - Shutter Speed: 1/15 diff --git a/ExifTool/t/ZIP_6.out b/ExifTool/t/ZIP_6.out new file mode 100644 index 0000000..e38d8ac --- /dev/null +++ b/ExifTool/t/ZIP_6.out @@ -0,0 +1,50 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 11.75 +[File, System, Other] FileName - File Name: iWork.numbers +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1359 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2009:11:11 14:40:00-05:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2019:10:31 14:56:46-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2019:10:29 08:21:01-04:00 +[File, System, Other] FilePermissions - File Permissions: rw-r--r-- +[File, File, Other] FileType - File Type: NUMBERS +[File, File, Other] FileTypeExtension - File Type Extension: numbers +[File, File, Other] MIMEType - MIME Type: application/x-iwork-numbers-sffnumbers +[File, File, Preview] PreviewImage - Preview Image: (Binary data 27 bytes) +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:11 14:38:06 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xfa4361e7 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 27 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 27 +[ZIP, ZIP, Other] 15 - Zip File Name: QuickLook/Thumbnail.jpg +[ZIP, ZIP, Other] 2 - Zip Required Version: 10 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: None +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:11 14:37:36 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xb6fc7f5e +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 25 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 25 +[ZIP, ZIP, Other] 15 - Zip File Name: QuickLook/Preview.pdf +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:11 14:35:40 +[ZIP, ZIP, Other] 7 - Zip CRC: 0xe267fa27 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 238 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 643 +[ZIP, ZIP, Other] 15 - Zip File Name: buildVersionHistory.plist +[ZIP, ZIP, Other] 2 - Zip Required Version: 20 +[ZIP, ZIP, Other] 3 - Zip Bit Flag: 0 +[ZIP, ZIP, Other] 4 - Zip Compression: Deflated +[ZIP, ZIP, Time] 5 - Zip Modify Date: 2009:11:11 11:57:26 +[ZIP, ZIP, Other] 7 - Zip CRC: 0x02b8a153 +[ZIP, ZIP, Other] 9 - Zip Compressed Size: 451 +[ZIP, ZIP, Other] 11 - Zip Uncompressed Size: 1026 +[ZIP, ZIP, Other] 15 - Zip File Name: index.xml +[XML, XML, Document] projects - Projects: Project 1, Project 2 +[XML, XML, Author] authors - Author: Author Name +[XML, XML, Author] copyright - Copyright: (c) 2009 +[XML, XML, Document] title - Title: Doc Title, yo +[XML, XML, Document] keywords - Keywords: keyword1,keyword2,keyword3 +[XML, XML, Document] comment - Comment: Comments are a joy to behold. diff --git a/ExifTool/t/ZIP_7.out b/ExifTool/t/ZIP_7.out new file mode 100644 index 0000000..ccdca71 --- /dev/null +++ b/ExifTool/t/ZIP_7.out @@ -0,0 +1,29 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.42 +[File, System, Other] FileName - File Name: OpenDoc.ods +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 7.0 kB +[File, System, Time] FileModifyDate - File Modification Date/Time: 2010:05:13 10:39:54-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2022:06:01 14:17:00-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2021:10:31 20:34:50-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: ODS +[File, File, Other] FileTypeExtension - File Type Extension: ods +[File, File, Other] MIMEType - MIME Type: application/vnd.oasis.opendocument.spreadsheet +[File, File, Preview] PreviewPNG - Preview PNG: (Binary data 697 bytes) +[XMP, XMP-grddl, Unknown] grddl:transformation - Transformation: http://docs.oasis-open.org/office/1.2/xslt/odf2rdf.xsl +[XMP, XMP-meta, Unknown] meta:initial-creator - Initial-creator: IDHW IDHW Informatica +[XMP, XMP-meta, Unknown] meta:creation-date - Creation-date: 2010:04:19 11:16:49.13 +[XMP, XMP-dc, Image] title - Title: test +[XMP, XMP-dc, Image] subject - Subject: test +[XMP, XMP-dc, Image] description - Description: test +[XMP, XMP-meta, Unknown] meta:keyword - Keyword: test +[XMP, XMP-dc, Time] date - Date: 2010:04:19 11:18:04.30 +[XMP, XMP-dc, Author] creator - Creator: IDHW IDHW Informatica +[XMP, XMP-meta, Unknown] meta:editing-duration - Editing-duration: PT00H01M17S +[XMP, XMP-meta, Unknown] meta:editing-cycles - Editing-cycles: 1 +[XMP, XMP-meta, Unknown] meta:document-statisticTable-count - Document-statistic Table-count: 3 +[XMP, XMP-meta, Unknown] meta:document-statisticCell-count - Document-statistic Cell-count: 0 +[XMP, XMP-meta, Unknown] meta:document-statisticObject-count - Document-statistic Object-count: 0 +[XMP, XMP-meta, Unknown] meta:generator - Generator: OpenOffice.org/3.2$Win32 OpenOffice.org_project/320m12$Build-9483 +[XMP, XMP-meta, Unknown] meta:user-definedName - User-defined Name: License +[XMP, XMP-meta, Unknown] meta:user-defined - User-defined: test diff --git a/ExifTool/t/ZIP_8.out b/ExifTool/t/ZIP_8.out new file mode 100644 index 0000000..a519e20 --- /dev/null +++ b/ExifTool/t/ZIP_8.out @@ -0,0 +1,16 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.63 +[File, System, Other] FileName - File Name: ZIP.rar +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 74 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2023:04:29 10:28:23-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2023:06:06 11:50:56-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2023:04:29 10:33:33-04:00 +[File, System, Other] FilePermissions - File Permissions: -rwxr-xr-x +[File, File, Other] FileType - File Type: RAR +[File, File, Other] FileTypeExtension - File Type Extension: rar +[File, File, Other] MIMEType - MIME Type: application/x-rar-compressed +[ZIP, ZIP, Other] FileVersion - File Version: RAR v5 +[ZIP, ZIP, Other] CompressedSize - Compressed Size: 5 +[ZIP, ZIP, Other] UncompressedSize - Uncompressed Size: 5 +[ZIP, ZIP, Other] OperatingSystem - Operating System: Win32 +[ZIP, ZIP, Other] ArchivedFileName - Archived File Name: 1.txt diff --git a/ExifTool/t/ZISRAW.t b/ExifTool/t/ZISRAW.t new file mode 100644 index 0000000..a307c23 --- /dev/null +++ b/ExifTool/t/ZISRAW.t @@ -0,0 +1,28 @@ +# Before "make install", this script should be runnable with "make test". +# After "make install" it should work as "perl t/ZISRAW.t". + +BEGIN { + $| = 1; print "1..2\n"; $Image::ExifTool::configFile = ''; + require './t/TestLib.pm'; t::TestLib->import(); +} +END {print "not ok 1\n" unless $loaded;} + +# test 1: Load the module(s) +use Image::ExifTool 'ImageInfo'; +use Image::ExifTool::ZISRAW; +$loaded = 1; +print "ok 1\n"; + +my $testname = 'ZISRAW'; +my $testnum = 1; + +# test 2: Extract information from ZISRAW.czi +{ + ++$testnum; + my $exifTool = Image::ExifTool->new; + my $info = $exifTool->ImageInfo('t/images/ZISRAW.czi'); + notOK() unless check($exifTool, $info, $testname, $testnum); + print "ok $testnum\n"; +} + +done(); # end diff --git a/ExifTool/t/ZISRAW_2.out b/ExifTool/t/ZISRAW_2.out new file mode 100644 index 0000000..d66b1f2 --- /dev/null +++ b/ExifTool/t/ZISRAW_2.out @@ -0,0 +1,42 @@ +[ExifTool, ExifTool, ExifTool] ExifToolVersion - ExifTool Version Number: 12.28 +[File, System, Other] FileName - File Name: ZISRAW.czi +[File, System, Other] Directory - Directory: t/images +[File, System, Other] FileSize - File Size: 1856 bytes +[File, System, Time] FileModifyDate - File Modification Date/Time: 2020:08:07 09:52:14-04:00 +[File, System, Time] FileAccessDate - File Access Date/Time: 2021:04:30 09:16:33-04:00 +[File, System, Time] FileInodeChangeDate - File Inode Change Date/Time: 2020:08:10 09:43:20-04:00 +[File, System, Other] FilePermissions - File Permissions: -rw-r--r-- +[File, File, Other] FileType - File Type: CZI +[File, File, Other] FileTypeExtension - File Type Extension: czi +[File, File, Other] MIMEType - MIME Type: image/x-zeiss-czi +[File, File, Image] 32 - ZISRAW Version: 1.0 +[File, File, Image] 48 - Primary File GUID: 8fae1a521bc8714e97e12b82ec8fa652 +[File, File, Image] 64 - File GUID: 8fae1a521bc8714e97e12b82ec8fa652 +[XML, XML, Image] XML - XML: (Binary data 1003 bytes) +[XML, XML, Unknown] HardwareSettingMicroscopeId - Microscope Id: Microscope +[XML, XML, Unknown] HardwareSettingMicroscopeName - Microscope Name: Axio Observer.Z1 +[XML, XML, Unknown] HardwareSettingMicroscopeUniqueName - Microscope Unique Name: AquilaZ.Stand +[XML, XML, Unknown] HardwareSettingMicroscopeModel - Microscope Model: +[XML, XML, Unknown] HardwareSettingMicroscopeIsAvailable - Microscope Is Available: true +[XML, XML, Unknown] HardwareSettingMicroscopeIsBroken - Microscope Is Broken: false +[XML, XML, Unknown] HardwareSettingMicroscopeIsBrokenReason - Microscope Is Broken Reason: +[XML, XML, Unknown] HardwareSettingMicroscopeMotorization - Microscope Motorization: Motorized +[XML, XML, Unknown] HardwareSettingMicroscopeIsLightSource - Microscope Is Light Source: false +[XML, XML, Unknown] HardwareSettingMicroscopeIsLightSink - Microscope Is Light Sink: false +[XML, XML, Unknown] HardwareSettingMicroscopeStandSpecification - Microscope Stand Specification: Inverted +[XML, XML, Unknown] HardwareSettingMicroscopeDevicesDeviceRefId - Microscope Device Ref Id: MTBCameraAdapter_MTBSideportChanger_Left +[XML, XML, Unknown] HardwareSettingEyePieceId - Eye Piece Id: MTBEyePiece +[XML, XML, Unknown] HardwareSettingEyePieceName - Eye Piece Name: 10x Eyepiece SF23 +[XML, XML, Unknown] HardwareSettingEyePieceUniqueName - Eye Piece Unique Name: Eyepiece.10x_23 +[XML, XML, Unknown] HardwareSettingEyePieceModel - Eye Piece Model: +[XML, XML, Unknown] HardwareSettingEyePieceIsAvailable - Eye Piece Is Available: true +[XML, XML, Unknown] HardwareSettingEyePieceIsBroken - Eye Piece Is Broken: false +[XML, XML, Unknown] HardwareSettingEyePieceIsBrokenReason - Eye Piece Is Broken Reason: +[XML, XML, Unknown] HardwareSettingEyePieceMotorization - Eye Piece Motorization: None +[XML, XML, Unknown] HardwareSettingEyePieceIsLightSource - Eye Piece Is Light Source: false +[XML, XML, Unknown] HardwareSettingEyePieceIsLightSink - Eye Piece Is Light Sink: false +[XML, XML, Unknown] HardwareSettingEyePieceMagnification - Eye Piece Mag: 10 +[XML, XML, Unknown] HardwareSettingEyePieceTotalMagnification - Eye Piece Total Mag: 400 +[XML, XML, Unknown] HardwareSettingEyePieceDepthOfField - Eye Piece Depth Of Field: 0.487202380952381 +[XML, XML, Unknown] HardwareSettingEyePieceFieldOfView - Eye Piece Field Of View: 23 +[XML, XML, Unknown] HardwareSettingEyePieceTotalFieldOfView - Eye Piece Total Field Of View: 0.575 diff --git a/ExifTool/t/images/AFCP.jpg b/ExifTool/t/images/AFCP.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0014a669fb4d9a7d1ff3e768410d9e19de6b56fe GIT binary patch literal 1110 zcmeHG-AWuW6#kN_>r$7xBP%)(*tF=ZAc&6a_D)%MD-~}Rv{1Z}F&k~_I2mTL=y=^X z>I?M3hv?6v^kSNEE7lc!0O=Q!oP6h;?<5COd@p|C3mWyhH|Mn$7FvPd^n*3uVr{Ek zVyoZ1MgJ*~dC%jF`-_Wy$LD^h-&tMl*o2+-&QodkzTz9)D(rB`8&H=xE-7xX4?^$| zl}8NflQD1~F~v{R9AcFn7u)!XX7Qu@@!&NqRqF2z4toHZwzIi>(6csOveReQOF<c( z1xXlvi)ygG-rfj;O-83Mqfb6dVR#%~NJd|R1dKLN%|=PWMQ9lHFnuobOPvlGT|pyD zq)=hRRh*f0B#hE=#^?Z*lRQ$fmH2?#*yKi;$P!0*A(Y7(m8c0licOmTt#$@So0L(7 z>L(cv$N4cTB9%jBOJ}HLdXx$)VlX3glJnD4%E)S6VeZ|PIz7}n;@#cefE$_FjBC6S yVNza-U#7}PD}Icr%!EeLrLr9pc>nLYRlhrpt#X~@?jrv0&#?oy-wF6#zU>d9<amJq literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/AIFF.aif b/ExifTool/t/images/AIFF.aif new file mode 100644 index 0000000000000000000000000000000000000000..97229105ce27b027abe6a05e8fd18ba6b3f00c64 GIT binary patch literal 290 zcmXAku};G<7=$k%P*tEv9ht07FaQlmh!HDo)QW~gl{+woV$w))ToSjCck0T<!*QWr z?)=|ppB={Y2=FfCGz^0{QULn!em{SDbe;FS%x=EYEfZ300$dN!c{<yTxaWG2(U{U7 zf9D^{+Cn&8JJH@I7iz-fBw6K!n55NKH<*s@1!p_z0Tte0F=OR9beX|4qG+moSwjVE z>msmbZEIbD^+4|*T3Tat>BuBLa0<85BCT9rJ19A0-F{NMZ48tm(nVI;&6*#9nm+@F f@Of4ONAPcHU-|rF+zp2htc_cL=|5i4Cwmru#yCF} literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/APE.ape b/ExifTool/t/images/APE.ape new file mode 100644 index 0000000000000000000000000000000000000000..faaf72806c0872ff2068e7753e74770262277ef1 GIT binary patch literal 2166 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 C2T;2J literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/APE.mpc b/ExifTool/t/images/APE.mpc new file mode 100644 index 0000000000000000000000000000000000000000..54c26cba8e74e90d25be52caed35ee8e74de202c GIT binary patch literal 2513 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ASF.wmv b/ExifTool/t/images/ASF.wmv new file mode 100644 index 0000000000000000000000000000000000000000..044c4aa899e274344b90647207f54a69e1129f60 GIT binary patch literal 12379 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 b`U%6Ig8Q$!GfICS6y!0JRzq&P8YK81>H%#W literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Apple.jpg b/ExifTool/t/images/Apple.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fdf12ecb3c9c3616da8f375e1f1f1c2e6c4fd81d GIT binary patch literal 2355 zcmaJ?du)?c6#t#CT_1a}wHtdvC=<E?GTO40ZM+1As5n5_5C}2pw!$hcEN(-lNDLa? zA0&>(7{nPD7>N>;i1?Vn2ufl?3^Afc;15DD3I_ELqsB+9zkA#7>kNeZ_4fR}bI&>V zeCKg*>f6*+S9tlZ*cPC*6*B?2kSB#Oh#Z_rDbm<Yd_cUgkqRG3C2}+d4oQ`Tk4kMu zh885Kb)A$uAt8-Rh(9!nvv!`3!Je^mesH>p9eV)h$t68K@hD<zx_4}k`sPszxGy8C zX)sV1s0#t1Krq}KXlf2M`htzk`~?~S-`<0!S4P|3S1r7GusZAEU`+<s0z=%A*6o2% zC^%#@y*0Xq>=xd&U(zyS*cck*roA!tB@N5|i%FZsA5PjWJeqW7_F~+U?hGy>&b4%p zB}+7)5iw3CcdNZ@PY|C<?$<up8-sI^OlI+j>2oj;7?HHiR0<d|%t#iV9<iA??~6la znSGg^!|87kCKIDZhOe+~9L~w$N#r}m@k82Y`soOd&ei^~S{&;~>ou+>-b_<!DDx`o zs)BNEm#N$F_*plp`ZddO2UGGV?e#JaEwrJAs=pUVyPQ{YesH~!6VjF9i>+SA1=jHd zN~rzR-YR>HqftkEieI2|+|BnNxnjx)zsAIGv+hkiMrjiZJ45B09bj9Fe2p1J+oLG3 zWJ<WRDGwA{*g%nmh0!o&R0pA>Cm!qV3j|hs3~Z9M@*G;Q3eE7d*cTv#0nEm3RLLM3 z@R1xt3~h*F80)bNjo5(4QIAVlCNqqg^0LIS5G&A%FL53{)Silscn<^8EhqRqg#hh_ zsJ96hu}c2JH@J!l>5>Ozk326^QH5&C)S?~Tl8|R)rR<h^X_YZPE7<fE@M1bX#~=6& z^JJ@RmnS7GemN;|IVxW2d*Q*mD3<S$FDGyVDJ+lyIWEIuGYYBiVURj7B=^cNu49AD zl859aX_ITRPKxzvJ}ObjT`yq_moZIBq(W|@MrtJwc?`UpA>W23>RiV!aLXt3<tEPH zQ@!S<OaV$UQSOoNuvF^AMlTO@-6?J+QKdHz#Vm#nImtM^iH-6r-p3=1UM>^v;LM|s zv++89rris;LJzKChE&qO0<O7qDNJCDUc)~)B^#(;FD`jdN|{`bPPQtRLgwfvR5EU# z$xfLs3mLyMlvBpb$IT*Au{?saQYsf@udGouriY;_R}GV<-(cjM)$$dOAbXC(>1y8+ zk8JI2-x-PbN86)4J36~J_Qev>-p)uoT11C2?d}=(?|Z8V-uVa5!R0BNeAnzbjm>F; zy7?7U6*+z^PNn`{#x7h<<~HDiS5m3p`+@v1zAxu|n)7(ty>urM>y9K}*;19Q>Hp7g zUq_^Ko3AsHh&~qa&8=%{N+f(+<^<~+Rkw&fJB(FSB~ljHYtBagv(I2WtoU||zlKOf zCmkV&<`X-+nLbmuhauLwE0u16B{RqrX?~mHdo2DC)05?wTl@o<toeDgKY}S1|Cmze z;B}+@C{5GSGo)P^p7O6MjopgJ+3+vv$DE!t-=w8C((C+J_KdP~Bs}@kG<#0!^&?uw zA?9h#^EZ1ZyNv9e?8=pKcQPx>t~KHE8rCfVp6`0-dzDDj1ugxF#DK4_yFbyfJraxi zVu{GssIM#17g?0LjAv0&R8--us3|L}3Hhh^Lv?<Rx<w6YENWSzN6VdtOsuHzP4fB6 z%l%=$&mV3H&zcpc!qSGNcT&r)rB1`6y3u8mY8W1|c_eifD`C}j=Ck0qg@dAW<(Lwr Xo?AGnOUSn|6CRrfi}4Poq%QskgE}mP literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Audible.aa b/ExifTool/t/images/Audible.aa new file mode 100644 index 0000000000000000000000000000000000000000..3891f12d13bc2647a513f56826e29b3a7c7a561f GIT binary patch literal 1322 zcmZWo|4-9E6fepYL5Gl-puyyfi2(wpU$$#2#$<z;3B)WiP=9K!?a^K6dd>BU_P_8? z@Xzqi^WKe-3v2e)*ZaJ$pZCtPT6^FB)Y!7Dr%H^cwWmIol>Ak)rR0H<f0niUL%km= znLo>OMZH^w{>acD8~Tc&KQZ)GLtj&&o~!RyP{D0|2CUU>_y?yKrQm6u$cUx=)2Mgc zKZ_jK>vS=Rrlse(U<ZlXB@wh(Gy@z8??@(dI;NHUfW{M6A@!3Jiu5t!SNbDn8Hzc- zMmrY;i3LkDn(jgbXm_2|a>B)E!g5xk6Eq~^hBkmg(;KSJ8<tWr_|2}yoaYE~p)(fG zEj9}x1d&{P<uOkim5@mE1zi{X7nneitx~XrhQa%=3r9fgT3N>_k<<<jdZTX#uI*?@ zacFI)v`PdkCF6zJlJ3C3AQbTZCg(*K8Gz{o*sOQM06c+QKjCE~ST>RG-a0rugevyY zJthv(&S)19bVtFcj&oK`sDMCF^2<swC?ie!IjCq1V`&x$Z!6<fRz)Ps1kDJvnD9KO z31|=vIA7Os7B>TMO=rSWn%u1ktkdd{5;dfAO4HdI`9Wxd{W|yzibOP}q-j6r$#tdn z%yj?+4)$iAr?i<lhQ8w?Cv@$u?_meozT@I<U^@Zo1S+U!cWoUQZr)0+##&S~Qf$b1 zUI+{mUOF0GjGL0`OwZt8Zk_PDkop*c?D#Zx!+<1?=LWbFV08=ZLC^1FKeX}Z0d~UP z5f1$UwvQbTd-lM`x`IvRym@;)4E|w1-38(y)Bdforq^5&eK5Z)Goyt7FuArdwuSL1 z+yW!m><Csnsw5He-hyu^i`TPijfFIe(I7HF2n;iB*A*2J$r-86PER_+<DY@=4$m)7 X6sEXnZGBAZ>9j#?N-~O61?1CzUeGiH literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/BMP.bmp b/ExifTool/t/images/BMP.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cf101e49d5ea8cfe4ab2c9644a66510008db267e GIT binary patch literal 1142 zcmb`_yKNg$5CzZ?pg@2lLLD$tK7eDxVCs-9ObOl=*uu2H7TCgTfi19w^%=?~P>UmS zFW=|x=l7q#@A3Tm>GR9CkN@7D-y(iF|NQYS-kh-Sdn}qF5yrmcG(}=y>@BA$5|d*u zIZcsRF!q$w6p2k@jzv=>Hn#P$&9dY&eHpsMMlLx`kqGpW(-et8`p9XD#6*4MG(}=D zJIftSk=Q^V2~CmMR3AA_ktpIPIZcr`(ML{GBnIdsrzsMX^pP`1kyuC{?P!X`;`+#G zio}Nc$Z3j12~*@WMdFJ-a+)IXqdxLy6S*TXK_7RDrbsNJkDR7REUb^5rbuk2kDR7R z6tI+>rbztLNB+Dka-VbfK_A;NujRag^ST+VkDPfu=2e)-Zh?7}^VsGQ&EuF`??mqM z<`&NFnp-osVJ>+tZ!TqOPsOP-ThBJL<V>HT6GuF_bK}Z|jWZ{X99}&v9*;-d@AtUf eZgIU{<8rygwrz1fpW}2o#qoHI!@<5U`1=Qc>|(G0 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/BPG.bpg b/ExifTool/t/images/BPG.bpg new file mode 100644 index 0000000000000000000000000000000000000000..3a31fba336d5de5a104b43935d383686a1695703 GIT binary patch literal 1863 zcmb_dONbm*6n#}anNK5`HV)!%@lZ@9!Rh++1nr_bnf^JpOw2evpb=d3r>3Xq>8eUq zjomXVjq$TlVuK(u;t&+v#f3YA5^*Vr7{sMYR&E3pLCnH)>s6+EpfxTmczw@3b>6+V z?)!9cW$CZQ=~Rln`LJ}c#;Vm5Y??7P!)7I!O#<y;q=4^%eG%h`lwcApjr*iSk$ngF z{uu88AB#0Iuz|UAupeREB@gG?uT4aAuY#wiRCioQ{PN1#8ZTFus%wR1zFH|(mf0sz zP{v!<(6o%HYi360`bl$P!PK*;@ZAfdo}VCnFUCIuE3b>ZElPB0icP?1hM^@ciQ0aJ zW+KLuC|Y_;Xe6q4kQCnvULJ_}4+2lb{0ZQhfrvkYxXD;!fPZx`6?M}DW_xj7UDKq! zVISlelQ3}$-@(}7%OY11b7y1xByj4om`fq04+I8e@(50h;K>n8I_hE$;{l9A7~?uQ zj_W?p*f+C`W#4CPnqwj!iuGu03$;?UO61}s>%shZeUe>(O}uh%?sVU)H#=6y8&=zP z^2#rtey(u4l~<lQc}6?qm8=c>i5=gn?L4*C-03v4E#>s7WUg=ayI$7{>%8CHbON)V zSL!XdVVS^0s|t@KVJELtyY;qZnw7r2zUI1{T-VZup&*FgS~pkAkH;b4<&}*v^h{Ol z^?K=EChhudRnKO#s%EH$F%QLju<eBP{=5@Bga}C<#VA`r)3?3Qb{$SJ>kW4+%qvQg z$Du8x-jD4B>8R9n)9tGCM%B}rifEkY$TdrD*K-}q34@aBtlRA^zfPe^AoA6$^N}OG zP5YFdQz<VHY4&MjJe?U&YX@lI#F=tYo&QppP>=NCzxO7z*Z-k6^k$?t<eKM7jgr6O z1~zrXcG~pm<6mHHqNW@uN>EsHtiHOxhv4k*Ld(I{>tT<{7d0Fltxzsj<P>9FC-hgB ziu~Dg&+)0Rar-4`KQmTu23}!hWtlK8DU0mE=a28Av%tQcr&tHy-PTJsJwJr%CWg_% zKEuI6U98g!J@C>l@LZ!4;g1RXYpW&jUjiR)3w)h$L*N^PeS8EozmvWlVVUmrxrV@W z`sYHcPkC9%^|pPxy%BOc2)satjMuh<(CP-f>NH*7bFn`yx;{mHG>hFSgw(NPnE2T7 zi2WabXdOhj-=2>8E8YFokQ*jyZuu|7o+M`&yPZFbv5SATvmfFzzd7U<-oRb`2rk{X mhul#e7q80LXS<2P(qdV<cK)R!=<ZSJ$`3cb|KiV=ul@}~D{?#l literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/BigTIFF.btf b/ExifTool/t/images/BigTIFF.btf new file mode 100644 index 0000000000000000000000000000000000000000..bd98ef49897b36802e3014ebd7b4ce5f549f510b GIT binary patch literal 384 zcmdUn(FuS+3`5f^NI&&M9l}X&D@Sv)UV5HVM=+!i9!YCGU;t)bC~}u5WJH4_EFX5m mLZ)xeV((Mxm9bqCyVyVJH8Jf3*b?Kfs9{g++44n~|6C2w5Y18m literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Canon.jpg b/ExifTool/t/images/Canon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b1be198892203d1c9f22636ba65e18b32f506258 GIT binary patch literal 2697 zcmeH}+iP4!9LIliW^Y-x$?kSTE){Z`hfOrFY>v&bT_Me7v(5Gz($I)VHMS8KG%c(2 zVnZ9Gv{JPsE7&T%EUiLY+JaaSLLXXaeGvZuFHzKoJ{KB%keKoNn={D<6AM1u-(hAx z^E<!GoZmS!yXt%OTh#}BhYGI|<@3)_h={7FMpzU8y@M77*JGSOiwj8tBezE3*W+36 z8pCC9tFf`b4tx_Bub{2AD!zr2KJ9xOdY0aiv3+A+Z};ukX=n4B@<ZKQ?ZLjDzO8f% z7MyWmbkgZ(Gwn_$z0OX%nRRZa!=Wbn^j&W~E)t(N+zD=Z&&#`)6HNxmhav4a?Q+sv zTMjlp!!LrxlxHKbUTeyWImY@@Sg+y$*eZGYqwH(+C%}HAKMf9+D$iSGn3oVXHXp-g zycDFfexeKD6W|aY)8$uCHtTFpr$xCE$1+@oi8Oo&>@R!u++IageaTx}C%DFN4jjGY z#X%~nTf%fXsGmzhFFx~ZfMglw%F8ACz!GLVt|!}6n{lPLvyu)=(ta2w*nju=;G%}t zLc?C<W}|I{wWJhgGq`Pm`Tlxz(r_2p2VM((KlmdsXE_B9z{3Wg0|yP?21nq**&jF~ zYikW-IvFzjc_@w;p6?9oqej19&_uOi_vI|D;wUKX9Bg<+arAT8LU+Kcz&0N4uHg*$ z8W=C0hQL?AVeoG7Rq)f`BKRhl*EWDHbjvW$<6z#A_h7Rc%qeHVUx7L19GEK+K_B3+ z3G$Ne*^(o0efc{Oq?AC<I=|NVNF7t^Ipn|tQkrjQQCffz4$Bi(vn^9ImE^0|UTsO# zgRxBO)hr<dgtLY8$-*;FICHZ#OSVc<D^zo6UQE8v)msqGpj7YRaAw)bVE1mrVKLY} zOq44W-#j={9JP0i7T+rD8?)O}xE5ldhOZaiu=_`f2SyK9luzhM1^z<?8flAo8`l@~ zkgkgxqE<}Pyij6VG*X*5i0c}COQ*#9;vs!P_r*8Rgz15tmzJ+pCMcK4C0<ywr|xuQ zL(TK`xx_$XkiHId$&K<CIjzT^<)a|aH_2UkJSo5PP02}lJn*}Jv&_p;|A$(CY|-BH zn~~#mgt!H9VTXpeouM=IhkS)MI;8wpm^T>(=866^`<i#$MR@GT`J>O8&;TDGE6cAb z8t1Lt)kHfHlg~{{pFJy^|L?T_TA!i*ZK%|eU-YQ?mBmSFEwDMK)lYPs647WwyrHG8 zuEj|<C7o0flIrYW>CAR($v#d*Z9{|ol%1@vPr6Av>1N%wHWv;(9X*e8tK3s}D8?st xNQ!2%VnW7*x<^|`EdCytw;Ncd!T-g7zWLZPjrx%y5}!{@#%LpbNlog9zX7_#`-lJl literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Canon1DmkIII.jpg b/ExifTool/t/images/Canon1DmkIII.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6d9b5253b30c5fdc7230287fa4bdaa0f668d46a6 GIT binary patch literal 8337 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/CanonRaw.cr2 b/ExifTool/t/images/CanonRaw.cr2 new file mode 100644 index 0000000000000000000000000000000000000000..50b2964f71998bc234cf8b50bb663726b89c626a GIT binary patch literal 8724 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 JK70`^{Wtffx#|D_ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/CanonRaw.cr3 b/ExifTool/t/images/CanonRaw.cr3 new file mode 100644 index 0000000000000000000000000000000000000000..22f39e7f5ff326811c73966dddc6d2a1cc0d0a32 GIT binary patch literal 52502 zcmeHw2S8NE)A+u3#~n>TL?lS$04XBnC@LcADOMDNLX_BP0+A+yVvE>8<0lHS#0JC? zOJa>;i5fNb7JtN&STHuw*bpPA@cuKez;Oyf^#3K_&$q|Eot>SX-JNao_U$f&5VH|U z;}XIWQxt>{3f<_@@v($B-inQnA47<>XL53M_>2QPW=%D0STLnw$(e2~KkoswcD{Xl z`}>B(#mD*f^BAU7y16QqF3PqpN+k;btOTmTzP>>L;G&_xO2C@=`gRFI1Q;XEVZ+q% zNQ;IBtBz-p@IXF;eS<oG0<IJ+Pw)5&VDus$<AQPO#kr&(xEK$N({j$u9tSSQ*EG5# z5+foINu~Mv2f6j^=}5G}*8mU_HnD^z0P=9QAtVMcGG7l?!sBHC8&WhucQaTcfDORi z0oItO!Hk<zS}e~C05|2~YXCd&G*}RKh-(7w9kA^H76i0-Y84DYkW2ir^};<BT|Ws_ z__xB!&@@<$$VYJ9m2O_%N_TH}H-($Kx6;eIRcp;C|CnFu0QKN5)Pp_wdMYQgKcE21 zdH5c{)@fY*U|rbrFmWeD$-}HH)1;jI08cXHYy(ibd$>t5I2jiMA1M#ZAc7IVav;@) zmeP=i4FNXhVN-yq`5YgLhV&Qcvi!{<F1>)ur!~M*9`6XSd;ynFE#Rxe!vGRpp57g3 zW-gEuZODr^z!?i@wtcy~5i(y5UZ4h(Y`!dRN+p%e@rUSy=3#8J5+24jYmm$3tuqMA zkca&MHp=DVLb8Nf#le_2S`C({!7?>ixvHu?x&aJ5h9m+{_f=Y~oS~@16Yw7cek;JO z0M-Ne6~JgPzc^snfYk;2rZ)K|92t(i3}Qq1I4;~1ipkYAxS}B>b?zXt1VyCNp7g^C zev4`C=Aradf@#&d)tg8K`8q=&)*Tvkw`(_Nf45f7o~>eI6(d|dTv|D|cJWa33{(UL z`E%5DePSj!Lr6=d(i`TN>JH1u#0$m=4`G~eg}g`zbQ#ha%8ES*2YYC80CDg}fTaLi z1AaTeGN8gSx)fkJz-9oOfxL78RsgI7*a%`^8I6HiBc4wH;6DNw@o4P;9u7Q$0q+19 zoaoG83h{urm<I=dTk)_Lz+RAAZ@~8exIN(Y0nP%rBf!Y7A=pgd*%{o!fd2~e;|FjL z9YPiYjQke>ya@PU0=WP$=JS>dop1>tZGdJyz+VH5<4$WRXg1Io1MCOzdVsMP#R0q> zV4*DM_&h)mDF8g?WjxSmtGGFD8NfO`oC~m#ij&p105??02~Nt#05<0FhoR9lQc2l< ze+yt!fU&O|K_SclhAa{<fIot`%>a%8SOIt_Hkl1@bAYiit_Ro_U??j20y1R}Fy=oO zU?+e<{A4%4t$>dccn$*G24Jkyhm8nntK#ZK1{13f&_h)bQ=DYMgtD=*rdR`_z+9n3 z&@ht?fg1Ts2*b2BawdilmP8alfVl(;*03P&W3iJFjS!|~NWKsIxJNj8!E^zeO64(0 z<P_^i3Y^Q=us-g=rJqC&LmNR;3YJZX<w@Q;<9CZrj2#=27@_DNkvKX!K2G800*V^T z>Mf6zAlXsmfK*22K}i#uWE-OGD+uH*u|Le<mZEfOsrWQeI<-h=S4Ay>AAHd3P3LvY z>q26fc9SDlj(qm(jUD>ATOaq$Zjmzi;*e3QDnCpptl=jCc1G?U;M~;>-xS_r-j)L# zW|8J?beIg%Oh-V?aPtO-z%k#HVi*c2&4?)=Ij%_45q_5NBQPJm$wZZr=A==9E@@hz z3r{c<DSd{|8wBUO%5?M$$@|mZe7h^%K!x<G(`56eY>PNi0-^-|>m=|clnC``9KRGi zECCNUo`D9e2IJ^77%XbW4dFFMpfLfJTY&WekD59fQ1Mw>9OPi>gY*R39W3h3xc-0! z$XOcXjRx09aBV~|%>fX?5XUe9CbRfJnjbIq$x3{wlb)t1c#q65yJU#tvI(Um)6CAQ z@#3P*BKQ-{yGp=GjRDp$&WVU(r!)=M0RzAYtpr`cs^dM;^9G|CPaqNSg8y|_p@D)- zPDYfFdOpC|2GNgN2aAUXx>zFY8<lwQ2zcxRm3Z(7cx<a)V9}-EA>e5q1~ieV!BRCC z*90ngf=7sp@6?re@CbO!cO@P?0=_xm-)8U~oJf!}@x;fubLZem^KZ|DIGfBjYGAZl z?^~^A^ldVQgp-xzp!IfBr9pRDC;BuQM%oZ3(vGZB^f4~dJs|O>#*((6*jSO)#9p81 zO7zpbkAMCwGx)@*lH_9~AV}gbZ7B<ixtrbKvet{QUOs&}VzHm}u>6qHHhSwCmwQ`Y zz%s=vy`#6Ozo_x_$I`zf6C}ZsZIY8xXPFM&pB_QSNo*xg>C^O8`k3U3q?B@^Z6re^ zttB5z3g{)YqogfJl3_f_x{3uA`zsz*m@|J?;3`f5V?x$fOsp7R@m0mI6}c5|fZNC9 zRLCp-EWcOYxMC$UkD0`rkc4XulocAZ(<{)MX;7eRDjz~jqzTe&Em!^c23riS7=EGG zM*BV~mh_i4G&o{-#qf?{q2ViPHno#ts9}^XwVHfK=96Ud1(`+?Bn-8Sx=bA)E65Cz zNG6i0<Pp$qr}mSjB#lfU@g#|wMEOwV@Ow$EqIyxqK)Zs>1p4R1gTkLa^$V;mEd-9c zNNcJ!WkB5`yJ3ZCH@V6FHq&277fN#E{(6B1p$1`K^Ypx+|22}jYWLRfWAK^5K(H6| z?vgI_Az6Pt4}&(9zpp8CVy>%8N*zHQAh~xjsq}jIBErohCGeG{1;988Xlnad8`ws% z?coZcx{;f~x}m4AZirMAn=M$*7mX+ZQ39d_L<xuz`0ym)8PvnSGh6Izk^<k+QgI6P z>5?29J5G@ll^h!y7ZM$#h>i`3j8KG!B!zSU+L+ieQQ;Wp!nTH&&;rO!sqjRg(p2*R zR>QBVBzz-)I_j;Y#E_8?Iyh-0o2FbNO^m3)^$i#e3KftvNq{dj*#HnKH8wmt1OP>B z_?yes_$bm5tEljpL>7X_nCS2b0p?w*O>A`B2=JJUiDen7C)+%n3$qAMj6fPR7_Oh3 z7^C3f=cAKCV*pMaoiv&iFpp30?bm4lz-ZKYvWO-LP>db`qDAu>1&3C9n!&3Sr}*gm z1$6Dshg5EgA!ehKl1B4b<R(}P$XD1K@EC3aVdMEQeEUY6p|H({!<&W0w`Si4_+W6H zz5~6kM=f=XiXR$eSKVl0H2f-{mIZ_I>4nj#Tkormm%dvaQ-k<AzNJye`L)%tEEtMW z|2oEX3h2$g{sC_A?yTe_BnX9b4LYHC2y_Mz5)i0cKzaIh>4|SNXdHTW>cYZoZjc#o zWkJ$5NFW=)y6>wUHI-x4VH$O4M<<{i1=rG!1U(<wRX3U#4ZjMg%`zYlILZ;K<bAcH z4e!{FM)K{bM57(us;wQFWk50BQ#%rbq7VEGFBzbm2;qbTBGhO{UDVo<m(Y&b+)%J8 zfb=FrX*SL9EHa_wh8HPq5c@o018#G(d4?AO{hrzt^}wG^1Riy3S5#gt?MisJ)o3+X z3QR@KX!uoXK|mbzciL5KL{dmKHo!=aZ&&(p+#H1Ab(wqq!?&lw8ttjKZ;*c%J`pSe zECjJdGXobpl@VAJ0{U@<$pI|T$aaGZb(q;7T-d>(l@IPa&MVoyfd}THj`j8l?rq&S z{_M#`>pB-M8?_{B{>@DreLAKjgoKTZNK%AGL`KK8w|}%}zr7+lyuJN^R{l!=1mB3L z=pN$}BLc_w2?`rOGOTU5ea8+yDc&iu2`FwwN^DHrXz!Hv)*<2Xp%LByBihxvgHK}k z2=9Jfy73g?Yi}Kul$7A@>N<ApSeLOLF7b(xu5NAHwslpyySlqO1BLVGadAl@Db8`D zZFmM<B1VTLMkgfU?hRxZ5*nYJ)ZW@!NH;d2l55;(7d8(rVezr9DIp22u-gOk0Xg;V z7m^em7uiAS<ErNKaYc@5DRkg4kZFJ_i`YT8N=6ZJAegZr0g!q>6z9E?mP0KT_e$IK za_1FQoIie>L_-m+@5%%E_2;w{3T6Z^wx-N^RWczlVoY?zSPeyziUpODV~{<Lr}RsR z?3NfG+b^UVF}5zY1F?@k<aA?wZLh{$>tly@z~g!<(y?6<zMkTq9mJu*R*9{X0Nw^x zk0{)Q2w#1X9fo5Ny1QY?QjUWc(Ba;s=vmxa60~HXRo@Aj0hALLRE5eQoZE}m4`?g* zoB=HC6)Oc;*yEK4naKuvW5D?UCt;7&aDe~+?@hw~{H}YE#Jxfw#5x;suTX8G688$# zCLM9FP;Js-CxmJQuVbnOvmS<-u7RxF7Xx^l3H~?s-!zA`*x8ED%bUJF<IIIdC>6Sb zRmb<wM16~!x%>cDpPOK>uS!BJKrM5DC|W8xx$*@eBVf;N16Egqb)UI7myTt>b3dn6 z;-_!vJvxAO^QB*~cp1Hh^_S7Xtowk=I=*B=h7g7JeVG6@&2m}3e(lfzmhOe@u098q z$nL)-#+&#Zgze%LlFM>Ky(*}I45iRqE5HPV1keO&aMPJA?zA+a3CCrCJXOC59(|DB zmqi*>#mHgl4oXeU^ms1aOc+a~V=O;wSUXhz7)NolyVOd%Q1?yHF*cNFGkoYPxd2s< zv5%$m);_8`o0Z0NZExM<6%_3t&2qd_(SSCT8rkbH_Vjs4jG`0MlU^VRZaRrcqCb@+ zH2IoIr`t*j8t-OS(;rI$Odc|)>HG9xV<%EUZ=<7(qDd=BDt*Zym&8jxqc=AAgY1yF z(5d>3s7De5I!~`F<sp4aJ=Wbu-IV@J4c955474q@DeZE3xtvDj>mM~-f5RQ~TF}Pj zup_T2@gaev0~|U#E7=5lrzL=G4L!&dG`glFg80IYd>NHTUXpzV19%110O+)UQU`X% zHvmd!(wNwiJM=lgX#;LJQIM9fBi@=s!ydo+<Q#n2%^?8>$*kh~j|xCcJhi$Aw2X9! zD;Z?;((pNRo>>dKfSQ*qpwnX<X`BCPNl%X?)VgNaC*j8%==0c&V4z$kv7zVF#u5v9 zC)+YO0!yg#VPrDwzh6l<k^ST>xlSID7X)8TEQtzQ#}twQ?+jbW0caToYGJj-`+H$l z(0)T<Pr(9`OSY1O<UH&heaz;Cg53gKF<=(rd*^+3+N*^<R|=d1ge6-JBR>DF5|EN^ z)D0MO20|m%CMTh7nE*_|_!LTQW$_3%r842<lTT%>*R?dda31MAsw%Jl;QhOKEn&6A zt3520?ID}_9>TR)%&#ttvr}M1&4F{eT<;OWf*_apYYx`7T2{|O4?GLGL@R^cNFl&s z5&4-sP!qs#G6s6dA#y|w$ARioR$8NPKtmYJ_jPCX%o9hoPr8K^Q0>Sz>1uL=>O>T{ zZ@YQvvzD-bv7^+8-YoHF?Md=m#VK-`wXY>r2ZW#W1=Wd?Fr6rxDTV<>LcV3^dTTly zVt0djpQDvYW>UDbM!PH+PSkx&+8Qxr?!ay#KdX&Vbx4XQX+&DUKFq!_6OJQa!ict= z93{VzKiSbtK{~P06h@a{U^IISEyYq8SYAt}W^UI=A^C&klRZ$kbD=iENPpM|s)9Xo zN|;|ApiXU|C0l~bEnr?Yhn8#xdqqv5#EoF4ZU{5B5uDR9fcdxqoXOLNSy&HRiw^tK zhB+O7GIrk9g1MVj^q{xkxj61@90QcFyT19{fx#hF*+$tBJ)w3JxN_lA&ugY~dZ-Ku zhrMv>Kf?MT7`Hd^M!O7aM)1K&H%Jd_5Diyn+GSj*dgawDMWn?Rfv1?uioOEh$i5!i zq$Cw;Wj6`n@{dFxu(|DZ-}8j`vDR_!$Gs(_ttk!`$G!2_OorQ-t*PH=N_s~+M(d$g zFIk4{i0m(!mAtDwR6b5VN4`|PUVcb^QGQciBrldfm%or#$Q4upO+%ZK!$M>MT|nWB z0u4GJ1+%Y>G?hw)6M6Hf9nzmFzJ`%!8T?)_S};;qFzsNEo?3H&T7fq+FgWJce+=X+ z^#wQjM3pswHhon3j7ot0mvfo%%pP`il#u7laGrEC?{W^lOTrm%MKz@85hWl>K$L(e z0Z{^?1Vjml5)dUIN<fr=C;?Fdq69<<h!PMbAWA@#fG7b`0-^*&35XI9B_K*blz=D! zQ39d_L<xuz5G5c=K$L(e0Z{^?1Vjml5)dUIN<fr=C;?Fdq69<<h!PMbAWA@#fG7b` z0-^*&35XI9B_K*blz=D!Q39d_L<#(>B~bfaPk-;uroVefRLyri#f)1)3O=!7CAkj% zw{|MrF|i3;MSvA?7>74<tKInkVT$M#B-eMOY6ILei1aK0;1&Zt8o^~f?4$M@Nvh~H zA-s^H5wR!Uqz~Lvl}ys%Zj8<FU$ZZg`|J%fn)K>^jB7$1*}K~!;9p+g?KIoeZl_VF z)r9$rFj(~ejXIohdP<j;F%LTmCE09$b7!yi>ni<-pW~n`(4-?%zgHFT^(N48JRWzP z0s!23!ru9j4Oe@kA1`@2w`tt5@bp7Vbx-;93>&{Uxr45aWT;E=2@{7`_idb$jhFuM zs~uUXQ1tEFw(Xre!AtLr?{y_N+b(a5qDOwWv!2`fL`{s2%pZ|aP|~bHl+~(nvdc#L z;l>twf{f10J$_}~(ZkX4&VA;o78rl4{Ko0tqsu0X<WVCJ|MEgB$12G0=iWNM&5kkO zaA4<6w^QLw&Mlq$L%~nFE~{5RyI1n?$FLL8pWP;&(a$F7_wxLYjxzaZT7NtDg4>=+ zyS5uTYCEoakYXC_`Lx?Sy=n4!GbYeeuAG|I#CNyVFCJG{6lHHXojHwJH2#OOmWiG( z?yEjF3)H(X<IeQ$xl`gig75Dv-qG7=P3Lw-tG_DpjqFh7zds|7ShyAEu3FyI;P;Kg zPOP<8oPV@7XYucD85Z=^8HPi4em3-v%V)Z+FE1QpX*cNH$bkn>+d2mBeQC0H$%EdH zvx){cEOz=f;nafx_9u+<vOIO??#-Ez7@8i_`Ox(RPh<(d^*KJ|<R?={zL+$K*3DVb zz2xkHRkIR0*`F>sd$xC8aPaaY`mJxyKjiY#t%=ls`^K(k3$EKHJNqr_=zKf!@`yP@ zThA<O>05Bvs&v4d#zyBdC(y%MwD>8(+&t8BT0q*9Wv(ytH%?93HrC9kxqea8PVJqi z#M<>Rd_4B-hgioKv!}$JT4&ND=EvSfn|il1(wkM}n|@Wc^~jDq5_!47kW-^>J!z7h z-KzN7b>~mhau(;^i@%wiH2(3L=ruVxXU=phKTtTvdc`VNqX@2!XFu>AF>TwEtiJ{| zEWRH)&m(%0{fQGRvply>-<xwdYx$V;%{~fk?Xr385{?g%FPxOMD`^n@`T7;z+g&}d z%6rYeOMjUumAiHY2OmAk)v-%q;menSfxCBKyKZam=(p&G<L$_C%ad{<qGb7-oZ4M$ z5;v!DvQ^CV-o0D2ShVQ&?I^ct0UeGkb9HYSILRkD?e5fX^@}bS9z5UX%H^Fx9drF0 z`!r|RtAzkmze`G>`EPWwoeM^QJ~SENEvGn7y2GzZ1`h09WI{XOZbZY&NxQ)vYR!O- zyQ`Q6s%CIwAYL)23!z%@M3=w~d>lW#bq;R~-d6*^E08ks+sD(r0M5v-f}MboA7>bz z-wU2jO5x3EujkL2G6atBA0OGG&_qQw1CA27PYWYf@e883{Hb`ls2ccjssCSp(r~XG z%;Xw%>{COXg!6Um!xozcy@{)1TBDBF^ZdT#>5kRF?+)C>hjo06r;~#0xjM!>@v7QP zO?CI`gRVPPXmxm?el`{8a7;THHtv{()F(Xl^FupqzU(~PV4IC3=|%C0ZI-X@&p0*b z!L%v$)6XW1c=U8sip8FGN8A!iOV%A_(lgGd7O57P?NWY|@yf{V5mP_?EPvNR-ARGH zJWI`$$$gLYx3g?`+tYt?ug5Pp=MVD9C|cX^tNQ6@u}`POc}L#gX*Yb^qXAxrAN_c# z$TuZZwlym|k3>Zk=eCKc-`qWF;@&049iC*hPJh@y+ispmXte!_#<^LZe`@;<-C!S@ zes4+r=5DvxCQ@&C;O;f0*KPYn`7JsceJe8U-khN$FP65n-hSBX=fm}%yFWA?{KM1F z1Wlwi{p>tZus8N@XGaK{+<`GUfSnhsV%K5t_?ME1`Wfm@sCU&>KMT;%&r<lgo6~%% z=GuMy+?~R=o0|10eYibWU7s?88<ZuaRGgFFW1p#C9oM9T%b}I1>r>nLIu>;Bn(J89 zncrh&*1!t&vuO~9W7_)P#vMzE@Cgs;cW8%6zVGbrPBxN@*~KSjKaiMQxHJ1fd}00c zv&>^g`elh0dzLJBOZ?uhzt__i8RuX2QY}c$RDNUI$;ghHTR;6w$36O)Z@XTe5hvY} z4QBPXOPhAvGq|GH<2fUK9khC1(b~Z`yVq;|Z2y;2;wDZv>5*o(?a|6^?Tkten(oaC zl5KUqlSlNV#kt?5H#Mlw+RCJI-x0?zJ;~~M>0txq-h@*j?Ju>umXPHcm%leB#56RW zd|yBP?9j8qmpyt0?!NocHQUK9ev4Z7x*ch{Z_dztH_KYKzj4^=LE`LsudRss*?;@` zS(S!WwQj<7eqmjLz<(4F^|KlV#Qzd>{npRws-G>@(9ddKKQjbM_4Tu6?7G<BUq5@Z zzE}1^>wDb#SzUGTff_ovuzscm|9L`PKdbxtSu>6GGaq*StenA>+bW&DruDNAd+oEX z!wl+Y(}5WEvkz_V&Pz#+yY_=gkB0Dl^Ri1jqxmmP_ujoE+Zs40kEpDQb9KLJYOp<L z*oiNWUw3|%nX|aFUWUcQN#h@9?*D9Px1Y{*>wm3q%)!7x=N1m*zI`3F+`DAi&L>%s z{Tdbrcc15xIqOoZ`_Us$nojT?dVYLpdP%QC*TZkf67~;2K4j;zNm*8rgXpGXLh`yR zIu80w)yZB)l*&&#tzA3xJooMEN}DFqj$1Z%eRA`<?I<I^Ma<~ikva}@hURW7YuRY# zVXL`A=QRHOS>}YhYcE{bz5e#?dnYGu3Tz*C?&pe)Q>9^PcPFgTFS0K`c>avN_<i%k z`MxRYXX5%9uAjY+@0(ief8nW`YwPoU)25n!<^bPNC8S(KpL*Bto0|MSwC|hsStISG zp`XR^Yos!bIu>;BI97$A;rr#Azd6_S`(|tYo3rq3`oH^|b6xeWy3(P3HiOsCz9}4c z?9O<faGB1b9gZcwv;Df-NWKp#K5=Hc#KcsQY`o^huXf*LDHLCP-nQ+r7r{$cCHJ~A za+6(NI@Kfpy#2}EkJG0uJY}*Z-p}?%G44N8dcpTi6N^0s+GpmDe!1>wIqW|(N>?pd z(D&?MJ=(}l8d<vNzLSeqe7|cO#$M~K^K@E_`J=*}H_MHe7N)J7`-6qovtO2dqT86M z;P#*89$cvFcW(R1m(7*Q`*Zu-J-&I{bLjHzhMS!nS1DE(t(6AvO0WqiP3Rj&Pnlpl zv9R&LYc3uaRup-rpU%A0Hfo09!<LCZHZa+LbSSs~EPwlyxN+$wJsietdsN|e_)&{Z zMZSe+Wm~6h$s>)=H5jtL+pQ-Ldu6w3lz-hh@KVm=UrjSCmTj70xOe4eL+=~A?Yeid za7>i@pmWsn_U^d<%-?$Nl27(L$;zDAuz35xc^*HEvp<osCChX6WZ$8sexd1;bq`&i z1^QY1sN+LggiRfpJAYOA;M^76@50`sN1OLu`cDI;a)xZ}TE8irezxIZ;miHsZS4A` z?KRsW9sCw;J7m82_gzUj4YSHx-Z_5Q>Zh-g?(KV?IiVeC(PCInbMqPJCvF<?(=u0? zRp6xO)5e<lje2St)Tq7lu!5?+NgtXHE`Hy{xtcTL`q_uG2k-spXL9(@p6Y8MqJGAA zrGJG}zweth>1UccczyIU2hhRQ_n+Y&iE;+l?W=TpQ9pZ^#)A4;8Zf|j&b<C9$A*Ra zg!jMb({uS8-`P(*Z6wWHi%%RpBQXi`Og3)s>UeokhC(rI^^qf+nyd|PJ!`v7+nCGU zw|Mr*FH)ZDeLZnvwBIiyGUk+W=L0TUKYDuD(qhlgcia-^{J!p}JUrgnF?4^df9JD< z1D`&+{9Rn>qV8>6w5G!OfNSS^>$FJ8ynWZUhsBos@Frsx&i&!{^SUmdCF?eR_=3~V z=AT)pJFjf}$v~Mh*>z)oyR#<dCpV~iJ%07)uY>ePm<Dfex+~$<$kK!#hR{>Kw4PX) zJLH;6l3j@D$?r~Qb}Csk-iywkdqm4*e~u2PpKXKl0q$z&14b?8&Ijbf-tna08w|no z0p%OG^8v?ma~A8D#oxs90R!QDz+%`to_4TsOvxvM&V8<SKHyLOe88NU38$vTa_0lI z;C#SLI3ExT=L6=$`GDWye84IGd_XHWA8@~h^8uy&`G8aW`2hb8w<EtB&7BYE4Ce#x z!ubI67BSOxHea}K0L}-5!TEqW{P}<ha6Z5p&Ig!_=L0^l^8xry?L_I3#?YgMcUBc; z!tSRoWw<X)_^sn32oGR~4jOi#a_bv9up^v-H4b;u3;sJd1vF@ASo;WP_or3B|2IZ+ z2<Y+q)q2)P!}n6|+lGX^WK;NOzn?<6AzwAifXy_!Cmv<s&X+-ufiAo2nm`FT88qX= z1sM>Qd(AQ+u**u&h`E&hi6_nOCe(t`sh<zftzj)h_(sO<+oxe=rTUr)o)5sVLp(o0 zCgNJiKT)?rnp_=!q*2H28g<8=0>LvNTw4&@`#=pk;mpPjo^BwU3{Av37Uca;xZ05D zOt2RvG|>xY7|T@*)2A=PEP;X8AFAx81B`TF_xKai?gGOkB@wa#{4+N*%<~@^=FlC6 z`NNHnQ?`Vd-es6or8vG5A|J~zP1jN6`gMkRagdOqFl4$%GEAGn4D%40NZ-zcc->@} z-?JFT_8!CRhV=UW0(!R<A=XZWoPN$Q=Mxy_FC*keNMQ`a6uL5uGK?k-R}*pp(tgP> zOzc)dte!ATMp*?jAJTSS!7wg280N-oLJS%a5^74wppy(!yqjSt6(Py^v7I#vq9Vgd zD#<1XNC7CQ3Q+q8f`1wLft(;$$s-oGetnv8fi7%rZAA2<%1WPcaq$hICR1bBny)LP zm{K%l3}=$eh$-m^KTG)4WDL~;YUHg(Qx_2V2)y>}sY{Id(5Q1)kC&?M;o)%J6(m7e z{Q!~GWx~w}Jn=g?>)*Y!SFhsqTeTR)=Z1Z4Y{Gg1aK|lg@G}!yG#E#S2mOJc=rN?K zIuM@4`)d*;($vMgF`s-y3`2uyBtW*apq(w&CuAaw2z>$B671XXMKC~2fx>Nu!YH6% z=YVMeGUScm7hFxez&{w`&4zdfj0s7FJP3H?l>w2Wz;yx(f7Oh-Uw|R|l?#UC08`eH zxy!s-$vmB0ah9#2H{y7%snxhw<wFBqnLkZD`teAe9&G8ENd*a6dJ|claJn=UnF2Pn zzzRJz7_|@vO)1b-0tYb?g|XGhG#K$A@WReDUjSH1!ZJ-NlA<3*>Gx#PU~o3$d10SW z&)27)27L-B3D!B9!<aN@Uc+ghTEWH*l0tWqmZ11aLGKkjIU#N?N*5*Z@N`frTM*{~ z1=6t>iT_X;>rU*$)|b!~D&wdD`A}jVtOJT+jsZMz0U<qmIudMV64F>DCn*r!1$-U= zmqLcupdVmsfLCD0P^Pfehbtgh9@K+S`#mL11qfD03=*1Ts}J%l_WeB!;a$v-FqT&> z`&wEy+hy^@T>qnmrc#sChFXRf^tbAoXqUgCR(~WDC@=aMy;YJRb=JD4wN@4(x7NO{ zy<Eo}3sBvt%v5fw09BM~ifX=UiRx?BY}I&GsHzj-ja1c1!Kcy&teHvyar{&xRZCS@ zRd$`yJ3Z|*rt^W$uThiw<z#Eetd3zFtva6Tklewf!<O~|?Mr-eeTMt^__XH^u2xH+ zUc9Tt{5N<gs8{SbTrautZ^)9EhksrIMmX}Q8yH?yRelOi(d$<{=}4gUn39i)6X{7Z z$$|6ggFq9KT5jun5Js{YaI20MHJfr|+?~JTTN=mf=J_urJ=qCiten>S-oV!M&#Tgk zB3AnsQky5ycb-VMAj`VID*_lJSNzd1t>Da-%Ko^_G9B^9$D=pHFus^%N$p51ffo-j Q?-C9vP>dYYq1gQXKa7XsJ^%m! literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/CanonRaw.crw b/ExifTool/t/images/CanonRaw.crw new file mode 100644 index 0000000000000000000000000000000000000000..6e9ef063ecc9ad70e29157d9db90a8d91c29225a GIT binary patch literal 6816 zcmeHMeQ;FO6+d_1?q=Cd$Vxs*f<BOTqc+)1Kx)=}5b{A{N!TVa?5M45vP-f<Hrd%M zCS=rR9BLV*6kA7xGBuq}JCzaqsAWQmEzOKp8K_f+POB&)V`;4n#yF*lvai2$-)@iv zF;@TR(A=H--tTwLz31I~&i#1rZewE}5jE6RdunQGTMQ(?^i*EEE)-g?MtlQmFy!kB zsD5A6R~eVy67H&x^o3e3R1&44u7&FlSMil$F0z2x=0i_P8Ov*0R^3^$#8p}hz&trk z>DwBbl65m2{-jJV(f;4=JALRB`=f4Ut6JN*ys@o%h1ycLtZs!`(*#jVb=<q~r~7hc z3}mvXamQv!#B3?Dr#slAHuxfU1=fRIoN9c%eZ3^F>P1hWSMRJ3MnVI=NI-25MEZk$ zy=qA@e&_8N$>u#->YM+-y?@Ov@c5y4WBY*>X$`lwR@0{Ww;#1lwM&IgA^Q}!PPNnM zYKCqp@@C{^$Ro5`i_&vIT!+UYk3>x)%48Azx0wbgO8dzv`Nz1jR!?&h0C!EMJEX)& z`us3}aTd@DTzSqYkeF0RzN2Yhr#X;CX&w}Mb9E~ymdZ1P8L%v?X>)~v?wf5y4Q{D! zjkV{ok?dffyz08dvlDepG)yH*0lB3WUCLMP{io~bOVQcj5)+y6H1@?=_@xSVXW_|a zng>}PxI$^@g^5(~bZ{eCCBF_l3*3yW8GT&{ULbiIrJxL#&xieMP&eU~1GE8{XjtUJ zMnbxUFfAn0W2E5ug3vaZRopeiiG4lOUiSC@mw%0NbQvfc@#kIde|<byFn{teYpz9m z9T&8h=&Rs2AiI#=$ThSM*F}bJ%9Z5~dQ1EObC75g{X^{5cs4vk-xYt?c#Szk*T5pD z6UMjcD{?I(nlL36KwURu?~D0wkR5EeM>_R(nkDv1w2A&G#$`NyMX!rDWjv11*Tkem zCyb|M#JOs6JU*Yj_oTh2jPJl6M}Nj1`|p&vTSlRs{w5xl^W%Q97+#j~*hB5&m_*0P zCEk+$J8b+Hb}D^7Tq+*Zw|83F8#DeJZzcbfSWCHZzJ0#-T^aVaix;F5TImL{Cq6$a z4bRIc?5A$=szmS5Lh(x6e;;8D*VCoqF=KBNeG&g1qSey5b$GA(v5d#}>09D2a;6-h z&EgL-9`DmmIu(z{IVz$L_3@mvfADyovG?EmZ-@3%?M;!U4|Za2o)M44myB;?$JiIg z+vsM@4t@hBdYNt&Zv*p+AEU>`QDENj&ls;3?YMIimt_ypm-{2U!JF~k?#GiCE8MON zxk%zl5;r7q?i=@1l|Q}LEYta?T~UC4GU?x0I@Y~&rQ?I2krt3AHPgahiVgVsTArG2 z$))2|NJ~=^o?(x3GQfw<W0i;rRH^v5Lf%SS>5Q^E?%k1dR{oeKbByS$tY*RYZXSI@ zMDcr?Cg=&VOJs>LiV01Oi9D?Q1NiNv1N4A+P)yJ^dS1K$NmO|-+m1)@JFJiP(H<J7 zQQJ1`4s!&*C7l%Ww9bz=W+Y3z`N@(ix0Y&nS9_1r#h6xaXw$BSH<BgZ{A9_f%}kS; z9jo@nj7g0VkvBhqMr~GTEK>WS#)!2EG-|sFoTOF)T)g-+*VCwNF2=<NsI@VJN5}_Q zeZ2VzG-_K&vuQ$VXG_G|1iHNCN2s4duxO@dF(RhVevVU~FutNvP;VAZpY6qZ+mEkf z3wAmyO`p9+Lo|%OS+R<y()8I$x|{CDo2HF^g4)w(BeI=*%EIlOJ{z?)W7ZbX#ti-L z&DZZ%y50~|48}=ACOw)}pXtr-%-Nh;mR>fuKGTy~Nsl$F^S$}uoa0%Z%;=nM`*`Xv zOiRvn#71IAV;{v9Xsy}~t(!&+{o2#9t+9J!eKB8bRjfI-ELN;}GRfFN2hB^BD&=R& zn7qHPyr05+xzaB0-%}16A5yj`8_Xw6w<wKD!1RQaho;Kw^<Go%fyX!t^jpD|EFK&V zJ%&h!c`$Ctp+XPIU5Ds8pj`T)(qXvXzAQdge`M!VK4wLBKHY0WUV?KtqNjisd&7c; zUs`c^dx?DkcxgJ(jk$I%2XvF&1>^_XZ|8G_hk<@C(Qcq|dlXyli$FW=R%pvJh<<73 zbH{q1UG_?#+kkf4dC7bf=t+sT13hkUfouoRvr={%=-1N91W=isXX9C*n{)Zh3qc|? zavCXKA-6ZR+MZhg4JRvlee9KS@1GluUgo>rc<E0$-uw1mMKA6gdTw?uAJuc&Y<Wak z2$UmH1CWW2&8QnladEG^!Hk8s0SyAB6g~rV7|7<D#Pc_xBX{NSUTRXaq2ZLG@~Iy) zt1AZGVxZDooKsXcuWVLVdfFOSHsi3mwz{o4zKM$?I2Anua<IH=FzoAG8;GhMfv#Y0 zh2!1d{LY~U{S}T?H#WJN!Zm^JV8c)(&^pxI);YAcv&`?Ps<f65x(7qyP$2442SYu* z{qDgEhtJ>F5pV;u*y&JZk?7hAM<f5E;da*z2G_Lp_4TMX6)!4rz(&Ns#@$j|AGZKj z;pmP=!)~W@U|^tlU{P^jq{~@SR#xV8l{!mHi%_Dde|>M%H(1o$e=RInRcWI((BBye zhNFDq%{F}<ee0qX4u@5ZoA!53Sq`s@^hmq@PG_Je5DN50`<*x>cfy>imis&1{`1yY zq(7p%Cn|}~dV*}&-Q(-+s&EVz`2%Zw>w2P&N}T+w1Hovw8VGd+{QiJn^)`9Ro!q9L ypl(c(xV3WUMUT@9f1*QhldJ+iF`RNc$W-70y@=KT!g9%YlQ@Fm@R^Cn%YOi9vis8j literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/CanonVRD.dr4 b/ExifTool/t/images/CanonVRD.dr4 new file mode 100644 index 0000000000000000000000000000000000000000..0ba820b4b0bba6b688eb1003077fc0f1f676a46e GIT binary patch literal 5472 zcmeI0O=w(I6vxkd^O4DiW+qK-7XhabZEUj$G>a?(^MnW!O)*Fpp@cGuNEcnGL{O+U zieN<1phZ*^M7!vsf{2R>#TiQxgB2I;!h(x|meN8OsanK^#{YTue=;vGH>3q8ZoJ8T z=biJr=iYPAefQ3rnVg)Q%t}`BBJz4D^BKr2Y7TWj>iBk%Py#97K1kF>7_?mm*1Kqj zh}#?o)Al<(ejm|Kg7tlVLKb+~3G)jB=}e5L@t!ol1hZIA8N)0rPGety;UA%dYx-1v zntmZz+V2egatQ{Pc<$%O@*Z}cOhV15Yj1uRIbM)iAIr~VA=v#XzhA({&{+n|;<P^d zyWIS;9yUOh^RPj(JXkW<8uKuiu6u`vjgS>QeU7rvP7fO=>+-M(vNAFja_*M9DX=nZ z-5$Rq^y~4kIWozHvWtB}#A|XC>@Wrj63oWC(&KVn1WV_7;BJ@el7|hFNp5XEb<ANf z&1V2+ecE^<^b0*~jI4<H*&3N&#6C-&xlhnfI$Psu9O^qo7I@eJ8xJh~49<e>gn!Ay z=E(Xy>`k(59(IDP-^0$3ZTGN?WcPU34YC~`mhE+Oko@|*41i&qN#J3F#yVPlwhqH& zVS=HXxQvozJZzjS3ub+poB)f@XOCZ%et9w-Nd2@-lXay0l+TbAJgi10U7@U?t;M18 zFxZ0HJZzaP^swiP&M)I(QgSToVGCrr1jF!gOvOIumrpQ@^?kAq4=ZnTegzLZN!ICM zBmK@#g5K4<SS+W(s!;aax_5PND$~y@!RM*2-(C;PlJ$C6fvlM8mA~PybJL=I_JEZk zgCMhJJZ&>fzYr|FZ%4qe6p{hkrT&Q;f#JB5K8%+>m!`mUKS-hVe5$Jc2RL52cm3z| z4B0gHk)2uUr*oJkQ~iP2x6j@h+7s!$=1NPPJ9isj^|K%@(_cmIroZ82A=Y1Y3a-Pk zZtDEXPfl;V<F}*X5F3Bnb@aWJV^G|yN<2SBPuDUpsD1H~xyMxQXZvdmQuCn2?2U(A zyMsf=;{H}pl*jUMo#TD@)AuZAGaj&=NLhA+-F$bi2-+L(|KsXwI)0OOG?C1~9(nT0 zZkex%)F|Kjc%LGfr9ML$nS98_cjWWMlEp5vTz@JqS1TWWpykg>+{x<2<r!-9<9RIu z(JI>D#`W`c-Qi|M$I==<OOU^!K8mWzvGrE{In-0A%9U+Z(YSO?biZjle^IJU^_NiR zP?am&s-kge-gI1z#o|(%>Mx?cf%;#>rFqdc(tT^|s5aGgPd-EYR<0w4M7h&*U@(f^ z(wD{)&OU$K$;PuzHb0~Oij&h{Qs+6d_zm?>ogDj)I@?|Qf%;h|t30Q^Uy5}*x6)GD zULJJu$Y?{KEq%t4C9R7LHex<M6S;QFb$348sJs1t-OiQzzPMbjyLL_TOSE60zC>x~ zh6vA*ijzwf*Uts&mn!U!_RE#E=h*cM<6*mH`rTyv-^hQby%f1|a-5Gn7tBj^C~D=P z$~Keqv6!26JF{JEn_cZ}EwVq8W>5Ry{<Hmm9If2xUb?e<NNPQ7UCiF<+ugNmU2NQq z<i8!qM(+Zv&*Qpx0Pa!S+uUpI%7@K2oe#68WxL|Ae5B(r`&Pw~9%nPXUHRB-+%&)a zIP}b2+JSe9K73EX3sE<Iuw3XCIj28dqS$wc8opQP?;);KBfB%H`(Mk;FsN*u<*tR= Wtd9hX99`yf-Fk<WW-Vri*!UldU;XL; literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/CanonVRD.vrd b/ExifTool/t/images/CanonVRD.vrd new file mode 100644 index 0000000000000000000000000000000000000000..03c931899511fe70c405376e1626ba38b1649399 GIT binary patch literal 1768 zcmb`ITS!z<6o&tG_Bex=a>x;p!lVZid&tC>%22a1Ek+>;yAVYxlHDvzp@%LQQ3+{T zqy<SqkZB|Yg+0`RT8IxmMUjS7c!4OPi#~*>f1mNpoJcF8eOPn${`OjHul?^mGdbD* z0)JvbVNqUzKYL^1n(U%%K)~j%GfjNqyB1K%1kWL!ETx|o7wiWKu|qZu#4)@~#|N5e zeyFp05eSE&d(mdJ8JL^>Q+tHGejY*GeXJo<-Lh+N0SkSFHk~~yXZ~Xq);iUO&wvbA z{a|jGeE!t5hwY^!!>xcYcoB&NtUxLDpqWn(dNGFg_!i9iYov$A5s5g)F2YKbpd6QQ z8IRG0*BHboCNXYhpM-}!%EjhfGlru?EN5IfuH!16@vO)24imGkX`N(n)=D_bhqGF7 z)fqdDbR3(>H|su-+0@hw1vlpdRa!UE;Vt%EFuzaJAYnwHDi3|!*6sNx!dow_ffd+m z{Z-~*kt&1Vmkj$h2H&05ogMUSw%%tvpRISUt4}qkdUtKuTa~Z!!b&}D7T+gJq*|)c zie5P<=Xq*P(k(q?8>C9A(1CGjlXi(kr%Z_{ohnuOjVR-QY*Bu7K}L*K#uYiDjvDPo zjC89ssZcx#*R_DKTHJ_-{EqP?;!9jmS*)4nY?4dg-9SoXh)N;ZdSJ2F!Ai*@fyBKI zc9QvefceD^)<Q-echJFj=4v|-JLF*9WNCnpYuQbP0b>f7M_;=cKC-8Px7;Qiuce&^ z=Du{W!(@J7PMd?Zlf?j0Cmiezf4RN_k&i-5#gj2t#JGdCk~IL~j{SyaO>=4~t)D)- zs0)AlQW`m4P2;|#f~->O$MJsJU9wYT=~_RIZ__T4-J}Mew0;~9(Q3%L$nI$UI5ug_ zB`?TowSELiWIM<{l5N%ck!>T}6xgdu()y9zAj>CPM;p=lkqwjOaeSOcoh<#5$+Vsi zXosftt0B{I6SQU1`rRWd=9;Nw-f8_t$rz)mSP!++`bi~O2lu*weeQ2&{ivkZ7;=}n e%fp`3Cm^~0Z0~?pH)p2*DgH-=l>c4NeDwoVSuC&s literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/CaptureOne.eip b/ExifTool/t/images/CaptureOne.eip new file mode 100644 index 0000000000000000000000000000000000000000..97b0f3d596cefe0e0e507be3b123c85e1fa8481b GIT binary patch literal 5448 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 MEx5V(;y8Kx8^+wW8UO$Q literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Casio.jpg b/ExifTool/t/images/Casio.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2ab4e7c94cde18d3619aa9357f5b033d764ae62f GIT binary patch literal 1203 zcmaKrPiWI{6vw~6{L-$gn@eVO?HnxZkcz{Sq<_#N)y)nR88U^T$I3P>SP!y+!cIk0 z#6y>Z2SF(aUOfmN#lsFB#naT2ch6qq`z60$61K5dntby9@qX{UU$7Va;h&ctcQ=6L zWt;_&L!M1QBg)eM88br86Tf1U;Phtn1Di%BWZ*SB9rHVOM*2_&=7zCF^83KNkPF0L zv=d35=UwPB&kumtt}NWTb^|M`=c<n5EZrs(u1b6OUR{6WR>&j_Y4J$cbgIp&Yq?Id zR%^O-pwNG#Dr4XJZ)5)cVlJt20E|#3-SJ#!z*UuOZ=lEgcZWqjOyk(mR2~h!4$&Fv zhnDj*vEJ}UWPa+AKPs6V{e0|G^tZ#D|0vcPsv2C!88YclS%;eHP7Xt^IE!IMF+cS_ zie+Lnk(3RX8`H$1+xf#fbC?bv$vf$*NEgNf8IO34zEYJ@Z=zigPo833vY2W{@;Gr; zRXn;&=Z<3OgtTu7t4;}hUiu3@CVdDomg4agA5ZZ_ij5STGUqn#K<%<YIn_Q2E=d2& z#6mPWwYUxX%?sznSF|fMr_h?Ewnz2TZAtC~?<aSHzf9i=jj29xUyG-UnK0YFUBB4g z>h+#joA<YRcOQ1T53Fvlb8p>R>uh#f!EZc8!7xgbrSjBN*|TSDuVRx_T6H0<_ChG_ zqlBF-m8==dE*9;EZP|@>V}8Ct0gLs;qrwtf@D*l`MwsJl4w}ih$%0*62NP3EOZI>U Z#(p1|2Q<MijB)C}$xU3sC(H&v{{!t4aQgrN literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Casio2.jpg b/ExifTool/t/images/Casio2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c9c2b97243c17319b7ecf25646d2edd630003f5c GIT binary patch literal 1699 zcmaJ?-%C?b9RHsCW3E-S&8gcIJSj}c8fSNI=?F`m(66PLl?8zdEl15lv<!L@p`Jqb zAQ2RURzy!>Z_z`<=qY*(D+q#!9(?dGXnoJQ_v~KN{9X?Cv+wzy&;9;9tE^Uj=+6h{ za#sK{8Q2E^ZrDlofPyH9t3bR0yAi)6eo*mE@BtDAU0{F}(pbfBNV{c&3?wGb)}!5f zVh9{SysFgNJoj>-nLH#=eg-v&+i|S{=$`)GGsgyL|6pe5?8v|{g6RH}BWVf)qX#a= zDMd3liVM9l-?1oT^j`skv3QClQ?V`@OQsT?srW${n$Atl9LoSR5)4mk$PvIt;Z(#7 zhHwnc^N(a~%7gjGRlIz^#g20kgx?x747RAsIF%=hS{1J>5z~e!Dl92go(}~LJmRCE zd@ATw{pUiB#gADoxGju^g&h|5BKB16zZdRue)0!E`Bjwu#270Q%O1liDl*q@D5?^> zpr}i%tvxl*5?<(%Jj=0S`%qGC&XOj5(0-P5OMe{gT@HI03+HjfQj)Q7y~no4pImqW zhH$mv@@!Sbt#lB22*7J<3J;?R7v@+i<9qz{xqiOWik0De3ONGnh^!ujFd_@<3)IX- z7<+Mn5>ZP(@+?zDVC-e>1yw~|aXz%S)Ms+SdALO7tn3D$vfptg=w4xi-_XjmMSe^D zO<RRt%odFaPCkP`4GWJWjyr5REsQo8E{{cc$Yjkhdra00cbTji$7Y>4KVdEr3(mUs zkvZzbe6|nF@4&SV9I(D?AZMM!pLz#waNwW=hb+wf3_J9VHb(z|#Tl|2U920RGr=|6 z$}2JkbFhAD;x@f-WBS^EZ51<Hpbd9-VHb#)G=}pcPPnlB&5XJ*Mb(>5AGL2feav~& z;l&}lW>Tq6ndm%DxZ##Ml%CD!@6ega+5F|HZ0<VE<+E2O=y-M}+hgtBoSwLq!w?)h zm2a>B0k1dY54ANkv>B1+h!KsTM0*lkdeXf@(%T8C3x#MCjRb>{WQ0bN>10Po5)bqx z`nEe}?^WJ`&y9Z(Zk4ov;v=e$RLXDy%*)b|ZPz9Zc=<eU5Z@+E<tyw`v5$SK4-Ug? IXs&$u3+n8r$p8QV literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/CasioQVCI.jpg b/ExifTool/t/images/CasioQVCI.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4f246f30f06fd5bed005ee34d8d21b7b168a55b1 GIT binary patch literal 407 zcmaKo&q@O^5XQgR)TL^RNm1Gbp^JwUtPR=V{_{|=E`{2Y7ZH3N559>H;0t*0A*x>V zNz!EP;-S#rK)#R3OvsE~*&pz``C<+V0CEBQi<kR{+XXy?0331cn}cxGSg)>$QK!{y zb?GQPCvr~!d%=Xi;)x&Pf+b~Hs6;DssS=}fq0$`T@^O;q1=<_kFAH6mQYScYPJ8cu z@{MO4a~}1gQNKSjawv_G4mC|Z)9R+C+6A3NQG6Q9!9Zp*mRXgJ$C+!GrL$da|9AEs uq74^}&JYM<g6s#D_#4y@yCK}lfY-}t3cQM~jQv7~xjhReaE&(%?dKZcg*`3+ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/DICOM.dcm b/ExifTool/t/images/DICOM.dcm new file mode 100644 index 0000000000000000000000000000000000000000..20a2b10d3d1ab289566d90b1725cf3b93bc8e84f GIT binary patch literal 1860 zcmc&!U5ndB6g}1&SCn+C*h>&053@-q?nA`P=)*RY5Us6Ui^`H>*(3{o=+bN;kcGge z&^$GzrA^;bX#Yna`h)sYdS<+Kn{7yFUn+m-+_`7&%sqGR0RQhZ$;UH?TUZut{LZ(7 z7ODgD=F@WcUgp=K0v!w^87L{`Fo**cMEvoe{v9&<kiq(+g5gjZx|aM(B_kCo6%S%g z&{3!zc;xS0ifAS%#_H1NZer2yp_yJmSuL768|R04HgRwbY4+xr3m4;N57j(B%FDD^ z)$@Fo&W~5KdhU|{?hIV`|0)9)0+ZB*%`Q}li~URX%}e&Jzu7uuept*pP*F%RU8s!} zsfbbkvSJtE#*$jQsHp~+>le({!Hl^*Ls4~cl!@8tqbFaUJQdB0^XI2$=Pq8-a|7ZX zkBir+%eLjX@VsW)X3R!$e_l~_a4=8vQn<KF%fn%liFzW^hk06v>Y(6myt?dMtiYYn za*&M~)y{76qcRm9t6i777p%=@JSu2bP&$m_#G@XQ6lC7Bc^#TrUfQ%F_3)TpZ_>C( zctpZj()Zhbu>fl(EW62k?61-1K5nj2?c<h3dg9|YRcqt%`Kj=!potX+JjPPeeravZ zLU4%ow`*Hl0`2dtt+xE{t(}CyFi|o6KOko%6w!|t%=jYmt97L9%%8|?L&piZKigSD zqF*g5WNw^CTTnX(A(5%UF2+S(=3^>ui{ayyXVx>!*IV?<7U8PNPP6mY+0|W>`7}$Z zg4bKpXL_0+_~`b=+|Rsvz*u{|38Rjrk84C9n*Iw_7|IcSxNmsV!$t^v#6G#b=}vPa z*iL^-le0Fv<bJZ{Qn1af@XsrOY|qxna0ISebu`!O5_J%&K@!sHt;eMx-|UHPJ{9Kd zG}m!QDH%}0)tynoYbOa;@hFgyjyX@iml2bpc_W$9$(2>_t?$uDjuJyqVWi?AV-NoG z%=sJoMDNkZu{Bm)@t!>)Y%Qd=F9wv>RJ+gHa>**T&_|p4T*0g=tMPPR%`#0dZ(VxP z>QmnhGTL=nqhUM>2Ql+n1G3RF9FMVDTjW^uF~@c+GAngjjfqP1fkoa<A7VRJbBE@% z_HloU=3CTQr088RryH<0Mj`TYk(EupI2Qe7nJ<d0X+9NyeB+^d0G<fov&mOyXD>wk P<m=O?;?c?Tle>QcDsdmD literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/DNG.dng b/ExifTool/t/images/DNG.dng new file mode 100644 index 0000000000000000000000000000000000000000..c5e7ee122004d08bfd241eb237ac127b9347ef8f GIT binary patch literal 13662 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/DPX.dpx b/ExifTool/t/images/DPX.dpx new file mode 100644 index 0000000000000000000000000000000000000000..6a9c7017f68f3addb900218d3fd165127ce491f8 GIT binary patch literal 2080 zcmWG`35Z}|WMBw0)H7fJfx`_T5-7q3WH18pw<VsriRq~dX_+~xd5O8H3NQdtM>Sw% zU}#`vU}0ro3?hv!tPIV-W;++9CYIzE!R&<5RI?XkP(V?BR%&vI0*dbx6sT$kJq&Zs zFQ_caOwTA$$jnR2hXvKp^T&TW0${uV(>e!`ViSSI5y*N524=7;j2IcYKq|l(s=ztG z1*Q}$O-JXCS~W~V0BgdclVEUwXOL%zu8FsTi(`nRv!|b{0*cMRqT0{`MFf>YN|d1L zWMD8bFmY2bP%tzzM3;f{!Nvo5pj^oS;=pAFkU@%{k<~IV2#CV`fh>-X%?S22J{82v Vg=FOC7U$<FIE4hmDh!lR1OTB2$cO*{ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/DV.dv b/ExifTool/t/images/DV.dv new file mode 100644 index 0000000000000000000000000000000000000000..dfa7b15c5699945e17646a34ae2d2eb501380d9e GIT binary patch literal 4400 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 Ot1*{`&aj9*1OE%HiPF&k literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/DjVu.djvu b/ExifTool/t/images/DjVu.djvu new file mode 100644 index 0000000000000000000000000000000000000000..d558c9b75d8708bd8011eadb91256767e7ed1518 GIT binary patch literal 930 zcmV;T16}+<R3=nLPf|?)00WjpN>)uoNm5M!001w600IC207w7;0IvW4-oKe3A;^`; z-*9yLfX&`x#qB)qAEI=h==CsO_`0qY^qc@jPf|?)003S@N>)`#PDW1v000UA2mlBe z0Av6b07*_mOaK4?4|8Z?a%E&+VQy}3E@@_F076GJGynhq5dca70ssgA2#V(6u{YU& zDv1C>M>I45000C55BZ-$M>I45000643Pw*-O#lD_>_kddNkL9jdH?_d;Q#nh`h~hA ziG)#HZldk0Q?kk&rv?vH&XIV4jY82MnO)TjjU(MtI6F&Z4Q?rw{w-vK-#k81_<6&h zvGpr{A3tjj+exNJuxnabg)KvRON&<Hi;dmh^K)N_@#`iKzA66nZVzUT^GrdY-O#rx z<-0Nd`p@hEqH*p-(rlyxt}$8j<<K;Zk*J)W`1EZhZ(kv=Z6|tOwFlr(unD<-<WrPB zZ{N47uUXl=eO$I#Z;2)lFW_Ro@J9CiS_EF{T(xsMpnoJma@t}Y85Z^lX#1dBVyM%A zAFlUegm)~P=Du}Wx$<Xl(7s%s%eSd9q<?$d_v$eIq7z~)Ts>}9n8U#fUGhN)c#gj? z)L!Hc#k}yU^3f{`|A8U%)WFTvysMWv@saf5pP9lkYxIOld^}Li<zx&!>e>xy_I8=P z!jL3>L|OOOuUg&TQne7o?ab&$;V;bR(-1QRD-g=i8FA$gs~i0I==b-=Gc~ue8S)1> z>a%uT==H;3`SQ@dm{YtJlwDSJv1w`isg=sm=Z|?0fH4x#AJE^xaE{(EeRa{w7N~@{ zT}7C+`y~H+clYSW-OP|7%9ta@ctJOLXw8SPn;dQpJuVRGlpo!=5O>CjB2Bo#!;^U; zaV2QWS({}QEZ2Xi5HKl1*k`|wGZVj>Z<TYNvVYRdKdPI7jN$2n*mx01HGZzVcqkxz zONDgQ03zNiW1B_JQ*CMNe4&8v>+tcyV&kqrcAQv}shoMls_u3B%qxE58K|W*ytj=f z?;!Y5lNSUOXF+uy4<$eL1-G;98c>mA0Bex6S*RJur1AlCuG9%+=|I6}r$J*_*irrp zU9*`{CJkv){}mjYB}bD-Z$kTi2vx5#q)J6~$Gajr!9c&_(i@1q4zhjBtkLWM=KusA zMS-&X({GLXseM}+*D?DKL|S!0sJcAo`EfBo3oJm6!X;yi9!l#P$8@zvtp;;ODvD#Y Eg}@87cK`qY literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/EXE.a b/ExifTool/t/images/EXE.a new file mode 100644 index 0000000000000000000000000000000000000000..cf0fcea87950633320c9c5269ae466217526f7bd GIT binary patch literal 784 zcmY$iNi0gvu;WrT)HgCvKmbD%GXql-Gh<Ur1qD+BL!bmi+R(tj%)~@N0fZqSfh#^< zFF4ZI#nnwA*gq)5)rA2FI6wpt6aX=Z9bZyZ3E~3nF*Jf{1=3J^j10}O+hYb-oxoL+ zT3n)+4>Sb`&i{J*kDY;mkr~Kl1Y#B--T-1K05K3q0<i^%j{{^t#V<hB!1zEj89*3h zE(pZOm!wvdK-ds|h-*X$f)8V{K^Y+PKyHBp7KR2m8_0-{PtMORNK7t?FU>2@%u9)n z_W_3&ObsmD6rch(faZbRV!;IQCy)tbAwYb5YDRooQDSZ?L@YiY)qNUJr4CT~2h=<$ zmw}ss0p@kEkP46#0I|5Zz<iKp5<u(#wLbtzgWL$z#}EL-$N&_SAWRMj_2_&Wdl;b; z8lD)&i9nSwR|PRJRtPW(@UU}C0ICAHTLy@C{D%Nod@wlj3A8ae^TPQ+H9+NTAcOgl IO@}5>0Gqr{hX4Qo literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/EXE.dylib b/ExifTool/t/images/EXE.dylib new file mode 100644 index 0000000000000000000000000000000000000000..6cc4f058301230b47ac8c058f67c060d29c47571 GIT binary patch literal 4144 zcmeHKJxc>Y5S=s8gc!*c77+zSM52XQScrv{2MK<lAwmjeP?BIkFAyRosnS^39axxu z;t#ME|ALKRp`De5IPZ4PTtdWNc3^hC_TJ3CtLEwR{VPUfMTlrjbX>GMLUc{Gctlg8 z>lP7n!6_E}RR5u_^>R8EOd1shE`+&JZZ?#!;(k0YrD~V~f;g&Nbw;TZ4w*0AtEyY# zs#~c^VQ{{##>?uD5X-ZE73T68SL!DhWp!l`?^)xeH3H(`ZNosp8+tSE<o33mtz5ue zz}J9X)ga(a=>Cxj@iL<58_qjLXQk9Sub%3g^F*BhL#Kut>5JCj0+?wDfSVB;7mNSD zFB%rq!@a|`zx-*rfIjV-+kQLRc)Y#eJjn09ek@21b5J7T>v9kyC$cDtx~=D0(lO>W zVi+(C7zPXjh5^HXVZbn87%&VN2L1yBhuNE#;|I^fzBQvC6t>vgup6(5h2H7C6h8LR zq-<(-Z(Fk_B?pPL^JFz`%TC7PKDbKDV#keoi{cUTW|2~-+7DAQM6j37k$;cA0bGYb AU;qFB literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/EXE.elf b/ExifTool/t/images/EXE.elf new file mode 100755 index 0000000000000000000000000000000000000000..2bfae40d2d69dc262c05f923b2ce6a5d17f84658 GIT binary patch literal 2860 zcmcIm;ZIvt6u*VX=oAf`sJM{$Ov)^oytTC`(;0EBQX)|pu(1%Rg?6-tw%ESTEoKpX z@}X&(5S>xu2S@yZG5X0UiJCCGz!tv{{{d%oNf&YHhiUqO?D_k>*K1&67Gpfg@11kc z`Q3BQeZBX-z7gnq-r;ZvDK(;2a5Vd-;`Bqe)Y}oiP({6Pi+$oDu?sH7{IlQyS78%% zfCAJ4O-Rx-r#OlGFeVsdZo(4G6~Ivh2+kLccHHuvM+~UYk4db{cn#DD_#=oBmoX+7 zXAQy)z8kd(6=Ooi2CnH54F4u_{=w<^#KGxE`*b{+J)>n(n$Mn#(pK+1FAiIC*$3zB z2558LT${BH)hcQEK6~Nk6}Yzvc~VpuZPmd?2<(Nu59oYLakA$<;$9&_p{YbF8OrG4 zv>plxv^dGuo)Dqlm*9=Wlc8)T8Uc@?DlZX^Cq++R?~(41PxII%YOEd|cyc`ir7<4A zhpTwn9t!nc#kmW;c%bW{{nS|GaPFG1;Vi{LE0y{96u~P+VRpE|Fk2tPlPSzr&WzE# ze$|AuGDD29>+2XYK=J-Ip8Lu8rQAPsEH-0+F^k8*@z@YlX${1i3k#?@Xly7Y@<->2 zI^IwIolV5%f6(hnZ^IZFy;5ATq{^Lrri+bapIz?925IlZ<6@<zFk&tqhW}9U^uvWO z_A{Tc;`o>@vuU2BYWTI%Y8?|ej7!SsSh1k*7lm&gCEs5E;t;s=EBfAL`e5u{Q(W|! z>u99V=TfjF^VQ8|ZVOBqI5}1bZ?Z)UZqIk(_+V)qyt(2Bd(hBEOX<B^rdcplEG!o& z+eTZUeDPelCO=%pq7Qh@+-l)V*3JhSjWtK^sLPoTG(ai&fHSAK%m<8B<<Plu_DADV z%jkG$te9_cRqGFa%|nG3x@hT&sW%wED&~5=)~J}-oA#2c?@_*U>_JhwfVNKJP22mN zOS1)iTEC~%^kP?7d#eH3gS!NqOEhCY|9;C%1#u>MMwXy?eqzvfK?i`l0q)8B0Sarm z@Jp~7{Y?OR5f1_?^3raG=9%H<;ypu+R#O$^MNcXAQii^PG0cB0ykq2~anwoz*0`s; z`zh6p>!3~bYhKN(`aGT|Jzh_T>OK~YsKKyq``bHwIN$$??Xz-wy*~f;+`e}I4!QqN z_8nHKWnvjUt%oOs7EkKY^o-DwDLtw^cciyn4^IhgDw)+{;Y>_uk+aFn*@Uh2v>iDe zO=se%<hDTwv2=8reA}3r)|owyryf0n=P3*jN=3qYSZL8$=u|qKhze~|Po*;`V5?Uq zkqAk`iTET6q%aW=&B|uaoXCJZnMx$0NnQTpch=mq++*CM);A9W?n5bzQRw66lstZy z_>EFwOW|Elc?9?a`;|PNm0*=8<J80ffVIiv8H$0|1)FCJrp4izc^u$5B5w)2B?Nia zq>xMD-Ouwv9?!}R1h~;<UHXY2!1G0$XRHFA3VUl^48;jRej9m4n=xT)KIY~4<t;p( zX%#&7%{bR5dHm?GSKb-CQ#|1D9*`pYjKCpzLGXg$c_ay%JR${19{=ymg7-chRbda5 zEFgKj!<sR<#fsPu=Ayg~kcTwF51t<Z-hWag&wxYny1?r~K*q?GcA?7SZ^x^e|B3j% zq)6U*IGB$-e#H9zf%oAS9zRw?;K>{a{bgT(&3DXv)_*LJF2m;UOW_>$@d+U7^8bPg z!tMDu{(K9Mcex8Z-s4ipA-)1+zE1EucgV*%-vX@9{`l=a4Bk%f<vYZgPYQYbUn)hK QvZnO+WAWe10kq`(3HCPd7ytkO literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/EXE.exe b/ExifTool/t/images/EXE.exe new file mode 100755 index 0000000000000000000000000000000000000000..6860e2772adead26e73ade4cb8ea18b4802a893e GIT binary patch literal 5120 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 Rg0QUYl?G{!+xx#d{tg<%c=G@N literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/EXE.macho b/ExifTool/t/images/EXE.macho new file mode 100755 index 0000000000000000000000000000000000000000..33a0ad62cf64da15572fc340258a5bbe96ee6f51 GIT binary patch literal 9484 zcmeHNUu;`f8UOB$ojGlw-U!gGDZOqhk!e{o&>oyM8A;Wssak~&A}ydwCpXt8$(8MU z&GmI_4_mR*4z08gkvbv7x-2SHv<EbGf(OK=E&?%lVUGyV9)dQBAv$SNL8?$i_<h&c z_Qi2B!~;+FNGIp#@BjI}@7m5e{@2I<e2<78!Q`Q0?jedYei2i_ObT&s{M5v^C%*A@ zCO1aB0ovCH2+&im2{An}GaY2JPD9UI9|D1$c@BkeWzlWPZ`XfxgD7&8s0XVTt0!C~ zc3B@n<V+)9%Ffvtk#`Go+ie{D5V2`QpTQcmK{34Maa4$a0T8y^IOIFr$1CT|8&&db zu${g@92~}1cCax^)NUif`aCrujJ)fZ)&gGe=wKrS@(tMFwHZen_G@u+e0m(5=For| z^=Q_j3q-#bDc#kZ!tmWmpJ2q(4f<{>36aqosm!@fCmYbGLD=9mXoLG=FD7&>6tZ5= z9c-M17=noN8nn@k*I&SdjZ`Va8S+Pmy_^TDA2xUm+Gwc2hGA#3w$&hXuu%nX#OHHg zGzRkD9E|ZiojpBuX5!@O=Ckn>$ec$FGun$ky#IzC+xjdf;-lW7Lf#q5m~%s|EwTL5 znEOwS{PjsXr9@e?DF@kamh!I59y@UiI_4ZC>wDZ^_eTk$!w5iqA9T+7ITi>EesAEp zFF<R9iQj_oSR0XFa{jTg#5Js}&%JdUllL}2#Lsg$!VH8N2s037;D5<L`I)`NWMb>V zbb_8v!oUjNSzk_UZB$>t2hEQta@-*0`R~S`Dc|~2-{>rno_>rfX4m>uHKHwTQmh<P z*WamaeFD80^wv(iM$x5@)b(=tK(YMlkz)DI6U9kV-W(A`*O#Mrz4d1F6L0-x^zXb@ zPjrvfmhqW`clq%<)LXgY*-!Yqkxslxiu{iGwdHQA;uCDB`k%U!ME|JsqYpaqN0zWZ zBRZX|(HP>BRh`C$E_pU$u>Bn8!uOwC!6$lcl&Es}OmXN6WL7CPJVrE(e7w{{RHlbq z&;KetRaR%-@Lvy&-Jw|J%p-lnX;N6X+|%UI*y`3FhQ9CF>P_w;1@XqJ2kSmp)ad=C zckdOeU-I|0O7Y9x6k#9GyT5elgW}SyzZI8$eXp2&mm-s-eruAtDqM@}f8Kh~UW@u( zyEf~4rBV%hZsfSC&|)${v*lRV`X%U2RY~JI*V5Yg!K;YxdlX|@a*d{v@IQO`(Cy?b zsmM>{BtG<kj{_@=xgSO$Q|;BxmwUR_8SB^L#J%F%oNe@u*EjmsU5=G|g?p#`%8?@M zDeyCZJzas_fpX;lXv&i-$j$XvDRR9+sE>M@_rFr1IP)5NcnkJ;AK*uVeaSZ1{_zde z9P;HGN6?dMbQm=s#5dLztmDfxU3&tt_fVuvAJN!N8U&7&X_K)A9Irfkrns^fxp^F4 zI(ukf{3i9IcM8lKHYq+xsL%4&+g>eUvz)5O&N73jC43yMty1qG`W3oyuEEl;?iG=r z1h0^bHur*Oiub~~57w2RFYM(uKV$Io=IGZr9`!@S6hlnxBff?`44@w4H5%mFR}J-b z$m*~?<Ml#<&VhETQFHJ#=AjSHaSt^7a-AFg*>7c0T^~)O-w{{ss_OO7a9SPX`f`jN zWHjC*WVBsm$d~c@q_Z7mvo51-?rcZd@MSi3ky-O))^?Gp`ZCpBWM+Mt*_~uwysGrE z%+7qe|0(?U8+^2t|38Sx+9~{#>tw}z!7}mzx}KIIB{O<SrdX(uk;1f0*VHrS3(^sW zp2@H{O)p9<Egef{w0t3#vmIBPw;jy|IaRXstZ8T&(}IL%<~7T9wVWgK(sDJ|)|`R` zLC|xKz2NBCV|&<Qa2!gZl`az<#M=KuOgzH<`2POi&Y-QGFxv`8n1L_@VFtnsgc%4k z5N06EK$w9r17QZj41^g7Gw^@TKxAJx-_dF8w%5lI;65MYw2KTJvq8rD*L`W<9#8%y z(0}0LYrsGC@e1%Cef%=;T_58-LFxAK4d8^2*MOh(@h#vPAHNP<^6^iAU-9v6;CFod zGuYSsIAb_6BcJ|r&>vSw;d@35^<m&TBs$x|>fwOQL;$A)xD>#z1@LcLm@c-k`d$kw zo4~$b<u5I|`ui4EJ`Tv>L|mE0`~WfYesDsg?#IO4s)`fHt}kQa4!R4uiToJz7r0XG z$93@+;e_v6Kaa`y5GHi!QB3H2cgN7D1DJf58^oWchm%|C!_+W8he={;X53jY{PhCP zwgjIj;q*z8kk$p$!96*MX<5oHI3jgXcjmt;9JyfP9@-IkX}G3s<@rwCU38?L@{YQM zV;5Z0f(emb6tXCdf-A)$*b+Y-(DBm{(<K~)xdw9$nPVfu(ak(mI+F1N5UB0S6lMyC zW2RezRK_;aLeJ<popWR^15(NsSsm_hj%Lo6a6X4n>$_~@ST=TLAy90pjm1ZFe6lB9 zncBu=C1G0zf`nRLe#f%&ySVHi(B`>=#I^uB$OPh$j$_#<qnB-O_k0f7Dvl!!X<m>b zuV-@^$wh3`c_CxZ=@}6CrwOfc^GT<`8KuxuD0Eg|;0o^`;>fHG+YKqtbG?h8<G0g- rbcK$zdF4Xi88V;0h%$=$&qI8vyf0O-I?L&Es0+7727D6pj+FFoO%jcx literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/EXE.so b/ExifTool/t/images/EXE.so new file mode 100644 index 0000000000000000000000000000000000000000..c4c08ebc9c8711418f867a927556f436f4ac0188 GIT binary patch literal 5608 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ExifTool.jpg b/ExifTool/t/images/ExifTool.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a4821d9ba00c278b9b725178e6a55a1d4ac6cdad GIT binary patch literal 25766 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 TaWNsSL<k3q*X20eGyMMmn<<?| literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ExifTool.jps b/ExifTool/t/images/ExifTool.jps new file mode 100644 index 0000000000000000000000000000000000000000..dea7cd4c4df71b384f59c1af01cf49ab15ec5eef GIT binary patch literal 283 zcmex=<Nsp@xp=RDU?7TTU}0ckW@O+gNi8l>NY2mAP0cI$f19C&K}0}6Qb<x=TwL8q zO<v7NPYnq4>`Xzx&czW7T<`%#QAtS!83i>72{kh{1vN7lGi_}%pav&XCw$rva{mu7 z2y!q0Av2>A1Ct;lvmoRDBMjaQjEoGZfDJ>0K>%G1gCK^;|62?^K%X-SG7B=;Gi+v% H|9=wzdrvcY literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ExifTool.tif b/ExifTool/t/images/ExifTool.tif new file mode 100644 index 0000000000000000000000000000000000000000..0d1982224e40ec5f9838dcd2c5a582604c8133f4 GIT binary patch literal 4864 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 S*M~*q0g7~HY}7h{ApZqSUxP~k literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ExtendedXMP.jpg b/ExifTool/t/images/ExtendedXMP.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3800189101b621094672e083df3a12c0315d95bb GIT binary patch literal 1380 zcmdT@O>fgc5M4JCLJ@}sf!auwx&q02sN!8aA8oAIRZ^!EP^t(bm8%<X6Dx^r*&D_5 z%%9=Hg(HV@;79Or>M!8N)r|91wT+74#^kb|ee-7W-cIs0xkMlOLIgUcUZmKLf6A4v zKcev{pgb1bi(KEMh?N@EW@(R=SiQ*Vl_J~QRf`QvZI(2x*eIz8tz{9C-n(9n2F{t@ z4t;0b<)N9yxRxe5t{8H&Z5`t2X@;65v$#e>fLJ{0hHfCBp_!efURj6fG1g(65f`?Z zO)jO2mGjyGRTV~2Z9fj|?tlw?%6qP7ke{C}2zDKVoRnLv6&&!sdvqT1*XPHb?)jiw zb;v&SShm3NXy`?HY>=rKI-Jwal;9~u3=HyoWcRqPH)Hp#<NHIrtCSf5j`UB!#B&Dt zGRS<?$z-BTii#ihs9LR7DbuK?6#!9)-g?503tsdT7(j^|Lg4h<KJ~}KAOy6W4>t;q zeZiOXx196fmI5J&56Y!j{m71*sh<va-n820p9}@jR~P=3TUTa4V}ndUYB2EEN(2Ap z!6Ed283&uN+BCR;=Ube6mM%<vEc$+Ew)^f79@%VP=DEdA9faIYx5csrH#Nrg3MwmT ztfN--a!FUqv!ZLLYo$Q{Y|NtwpY-PqrdUWXU@hOdn|uLS6fF1t#W)gpxgmDXPJW<w z=+XLm{$ZYMY!FS}mNi9&P|R`~rd6MYbyrA_^Lf05<;_jGB4fE?Rd#kNP|zqh?kc;w zOFpCAeFVRZ^aQQtq)bjqzMz*#lF;?IcPoR|Z(>mHRwnt59%Lk7&g9TO`iQoZZ@&S{ CTav*5 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/FITS.fits b/ExifTool/t/images/FITS.fits new file mode 100644 index 0000000..f688cd2 --- /dev/null +++ b/ExifTool/t/images/FITS.fits @@ -0,0 +1 @@ +SIMPLE = T / file does conform to FITS standard BITPIX = 8 / number of bits per data pixel NAXIS = 0 / number of data axes EXTEND = T / FITS dataset may contain extensions COMMENT +------------------+ COMMENT | HEXTE INDEX | COMMENT | XTE - FITS File | COMMENT | Generated by XFF | COMMENT +------------------+ ORIGIN = 'XTE-SOC ' / CREATOR = 'XFF - Revision: 5.3.2' / DATE = '28/01/97' / FITS file creation date (dd/mm/yy) DATE-OBS= '09/12/96' / Start date for data TIME-OBS= '11:56:26' / Start time for data DATE-END= '09/12/96' / End date for data TIME-END= '12:01:40' / End time for data TIMESYS = 'TT ' / XTE time will be TT (Terrestrial Time) MJDREFI = 49353 / 1994.0(UTC) expressed in TT (integer part) MJDREFF = 6.965740740000E-04 / 1994.0(UTC) expressed in TT (fractional part) COMMENT MJD = JD - 2400000.5 TIMEZERO= 3.37843167E+00 / Clock correction TIMEUNIT= 's ' / TIMEREF = 'LOCAL ' / No path length corrections TASSIGN = 'SATELLITE' / Spacecraft clock TIERRELA= 1.000E-06 / Short-term clock stability TIERABSO= 1.000E+00 / Absolute precision of clock correction CLOCKAPP= T / Clock correction applied? TIMVERSN= 'XFF/95-004' / XFF design document OBJECT = '47_TUCANAE_Slew' / RA_OBJ = 6.02170000E+00 / Nominal pointing DEC_OBJ = -7.20808030E+01 / Nominal pointing EQUINOX = 2000.0 / J2000.0 RADECSYS= 'FK5 ' / Julian coordinate reference frame OBSERVER= 'FAN LEI' / OBS_ID = '10080-01-02-00A' / <proposal>-<target>-<viewing>-<seq no><type> CHECKSUM= 'mB6cn96ZmA6bm96Z' / encoded HDU checksum updated on 28/01/97 DATASUM = ' 0' / data unit checksum updated on 28/01/97 END \ No newline at end of file diff --git a/ExifTool/t/images/FLAC.flac b/ExifTool/t/images/FLAC.flac new file mode 100644 index 0000000000000000000000000000000000000000..120b5dbe24234857a5e312a6855f21cbac46c89f GIT binary patch literal 282 zcmYfENpxmlU{Dfb5MucM|38q)_=TweNHJWI?YY^{u!-em1Lur4Kb-1X7#Q9t0L6<^ z(^894^O92)ax#<Ld>ow>4D}54j1-Iv3``A-3{2U8YC!6OTmyU@Bi$W6{o+G{9G$)6 z16&=wZ4LAcpg;+m8jx~ZT_Y1S14E!fg%l?=lN^1VLVXFC1W|)wk}%M|5YG@FS6kPL n%(RgF{2Yan)Z!9Rps=%lKxB}oyGMv^Kt^Vcf=6OeS!yK!XirJu literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/FLAC.ogg b/ExifTool/t/images/FLAC.ogg new file mode 100644 index 0000000000000000000000000000000000000000..817f87796bce26107df0eda9312eae4cc1987d1a GIT binary patch literal 151 zcmeZIPY-5bVt|5(>KiT~=C3Qyb}$;(yZJaeGcqtRrTHW}14WgD7=)k-8NV<UK$$3% zKgc*Z02v3=&oIGp(hWu(76t|l1t6~|H7&I$H7_|;Aty5lWQBsEo}r$Rf{}rNsezG! HDM&j2$BG(@ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/FLIF.flif b/ExifTool/t/images/FLIF.flif new file mode 100755 index 0000000000000000000000000000000000000000..1692a5890eab37d9fb05f7a3868980a91cee6114 GIT binary patch literal 674 zcmV;T0$u$^Oi4yFF%J)ASZ#2E8C8+7Zo)7ShIgLAQU_1u2o;rLB`}m$bfBsVQfFfy zV3F9aV;aM&b?77Y73zct(e1v=uTSxR`7Bs24(Q1ScS;xZ_xpzuB@6m7S&o+W5%+3w zcDOpfuEqHvGD&CCK^|DBY=xeaP?fr7p`cuv4YD}5YoMfy_(MToD!xO;o`Tx0jVZ}P zGD&H4IJsr-^XJ|Wsi1rBEdywpCTZ>y<93i{Sq7sqjK{;67}h84c^K;YE*b`;bIh?8 zPFb&vCN1-gIr@Uq0qNCJT+R0A%FZhVV2KrKUqhOt5Osv)QZV6=d*fQX=vVmJ!G%gS zecP*&EV%o^Qx5&EOT$e(hkvfix0^qW?}xsCcD(i(;d&9&@eD5Z{Rd@OX=YJ9i!lm- zFbqXsD%}M)y?}THXThZ)j&2Hy1xE*uZxf4w{QSJ+4^KQG1UjJ9E$sJ-ml~glq@Jbu z{UR-ed6%SDwcF;Z%=PQ5F=uQ}XT0DAX+uL$f)JIEy-EW?5Xb))gdm8DNI-11N~6X# zNoQkBA);PFxI*o6n<EBs_t>l-g>Rs(U5ZpzK7;rMDFmOwLJ+JhoN)uERmgu?<~PGI zUqJ949yG$704}t%t-W1wd~z!CBg`R>C2XV6R{gZrY#xH7jZVk}_@}QoA9>xt^qTD? zeMR<z{*k5TRDOqex$8M%jy<0-+o)A1z9pXTdcJ3lJr9_#UFwLRIA5if`Gt6W+>5mu z_RftKwT_7s!FyLrl3qnh`DeNbS$r#>I9LH1kHE}#7OUO?!!=;xA&V8uz~UzGJXDwV zYTPBrRe(u;R^v1Jy?0O;x#Wg+1N@ZxT9rgmc&Bb`YHZu}h|5xL;RgUhPXJ-I{h45} I{sI@Ve==uDYXATM literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/FLIR.fpf b/ExifTool/t/images/FLIR.fpf new file mode 100644 index 0000000000000000000000000000000000000000..9e0f1e2ebf1aff55c90b5c31fd561667e58613c0 GIT binary patch literal 914 zcmZ<@a8n2<P0GnkR`ASCOixvC%P-1JEMb5FCLp7R8N_2^Sin#L7G?yKlo4(|o<Rz( zCI+}nF|jl-Fw`{y64+G$<&8lAyDS4knp&;1@#G`UZvH-o7Pu6R0C8jEVtWRL27BYl zlYrqd$r<Qnd;a1&pcn%KLaYJIrzAM;vV&3x1A`b4a|5w35Q_kDH8|}685Dq}3tOFi r`nsGq{%>?%e6iaZ#;3r@L7_GNaFer5N@;Fxr9vh+@1-P`B-#N0&i^zW literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/FLIR.jpg b/ExifTool/t/images/FLIR.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5d14d97fff6ffbf646d8a62b3eda7b5278294a6e GIT binary patch literal 7338 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Flash.flv b/ExifTool/t/images/Flash.flv new file mode 100644 index 0000000000000000000000000000000000000000..a502043d6ccc453dd0bd63209e25fb2f5c964346 GIT binary patch literal 1358 zcmb7EZD?Cn7=H8FeA3c()!9{pinTLM(zNI{KXS7!Rb2N`TFPAU<C1%_Tz7kKntPHg zUDvHtm}G(-xH^y_>vS7BI%c&_6bwvdDwbF`OXyq%Mb_qrbg&^~+Uq&@CT>6c<Auw8 zdCqyC=REIu-`mmIt1|(BmAp_5hC}pA9zED3aSOuiCKj_60yKal%F3HjIud95k*w2! zHJm`(C0@j`jV91d1~7_bRS(AY1Zn{z=KB$WOOFw!V`R;Ql1Rivl4cn1&G45p0++se zefakLBVdcMR1KrJOgHPntO6}0NU{Qz&;WlK@KYxx9h=A|`CTmLC6S^|Ho@@{N9@E| zjDXo5-|*1f*bG>RtzMo(60u~Ki4=(d9AQ@MMre1B6cRaVR<*5Vww3%;s*Ez*9}|$a z^81js<VaG&u*9J-&jC|aO0ZCMnIA+DH2;!QL8rjVn3hS#ga;VQw!+ig&IY)bTg_m6 zB@(NqN?DlvWL8C~haj~_PHNM9B7sdq;KZLZ`MQHofK@$^CT&P-Ne)t2mY#^b$i8vF zU(vVo&Ad_tmF0zS9I;r6>w)1#0by4^;&|58(b?N0NrK;%Z1MO!Pr902QH*07yxvqQ z<xw{_s~bHW@<xOtrhCbq5Qn5MaFmx}6*axP)Jp$h-W)~zt|$hET%6`F!V9QWxTPH` zWG9wse^LYVe#&&R0Zb|-ZA#<a3IygzUDuqkK;UoTQ2UV)^W9oIDSJ$kdD<#!GaG>! zzoJPqg~bDE6x0o8wZQBiQv=de4z<|-UbF@SIMs5~qiKCHxHi)@ZM^{&E<pg=kc?$I zbo__xs5(_-ShPVRMocN5!%<+!fRshRFkTH10BEF$I+^8+|37qu;YE_gYvk~lvf#=> z&HCy#qusWn?wRn!zD(D}&cey=eP6Dd-*od~?(r;>t^fG!>>FtBR-dKjzOF=JkvY1C zsX93{VYptgy6H5Gtbcy!+A(G2(><?E9Q$shK}p>lE&l4$_sxr;Ef0<zIdyB9BYK$p zWM)gcwdOq?Hiyj-5NBoGlIW?MdH1LBZ*_OxQk3O`-uAH4THmPT=9Ir)NPqoQ?zz_Q zKNyXy{wV+G!qvi^#F@djC*Rq+v1!#e-m0m}wKKU3#T%*V<y+k+o}Ik@YUBGE_s_MF z{jH(fgRPd@Q?r@FAAZquE+`l39#~j8ebv-<(SGzw@mg`S+nN1bSKM1M5HfcL11~S- z2ewT{&(~kdHC(>@+2VvewQhg2^PGF-?C|~GAMGvTCl@j%tKR>Ld+NOdf1DTGOjFHE Oy4Z2S*QZVRZ~p)+f~J@N literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Flash.swf b/ExifTool/t/images/Flash.swf new file mode 100644 index 0000000000000000000000000000000000000000..176fce979d97162332a7fc064b0cd7ca382ddca5 GIT binary patch literal 384 zcmYk2&q~8U5XLtb55b!+u;5`&$)+tuvLr2HEqbVkE!1oNY?GzU?viZDS`d9N4?dC? zAHYedrMLOM8GbYK&DYC|PY0n_^p2cggf1QQaaDj3O|4F{EjKvjg^)4%`T8bU<S|)~ z7r{c`@lre-YQ8)?t+K;53v)7^xRGVH(iJxew$)BH%*G_iRmvIEolObuN6a=Rk5y7| z#_p}ytd!c}8-E-SVAT1Bz0BrE0;rgj#%M-q+qQl?@>N~XK^TTK7}DX;g9)#BlP0mA zY_5Ue;+`?%O;!tSgp#;pCaKz+m=G5q(fSN68^1r)&s0UBK?nYb0t@2^6uaG<Qq_~^ iQtYCx{cpf3+Of}e!3VtsFLdQ85fG<Lxr88FNbC<S9eUCL literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/FlashPix.ppt b/ExifTool/t/images/FlashPix.ppt new file mode 100644 index 0000000000000000000000000000000000000000..539dba73fc5f3bbca292956d81ae3d956c7c615b GIT binary patch literal 9728 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 OE&oxH9W~mq%>N(#{&W}s literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Font.afm b/ExifTool/t/images/Font.afm new file mode 100644 index 0000000..255ad9b --- /dev/null +++ b/ExifTool/t/images/Font.afm @@ -0,0 +1,28 @@ +StartFontMetrics 2.0 +Comment Generated by pfaedit +Comment Creation Date: Sat Sep 4 16:12:41 2004 +FontName NimbusSanL-ReguCondItal +FullName Nimbus Sans L Condensed Regular Italic +FamilyName Nimbus Sans L Condensed +Weight Regular +Notice (Copyright (URW)++,Copyright 1999 by (URW)++ Design & Development; Cyrillic glyphs added by Valek Filippov (C) 2001-2004) +ItalicAngle -9.9 +IsFixedPitch false +UnderlinePosition -100 +UnderlineThickness 50 +Version 1.06 +EncodingScheme AdobeStandardEncoding +FontBBox -139 -376 1021 1130 +CapHeight 718 +XHeight 523 +Ascender 718 +Descender -207 +StartCharMetrics 1 +C 32 ; WX 228 ; N space ; B 0 0 0 0 ; +EndCharMetrics +StartKernData +StartKernPairs 1 +KPX quoteright y -4 +EndKernPairs +EndKernData +EndFontMetrics diff --git a/ExifTool/t/images/Font.dfont b/ExifTool/t/images/Font.dfont new file mode 100644 index 0000000000000000000000000000000000000000..0612d07d9726d6d3a10874e5b02067589b87a537 GIT binary patch literal 1744 zcmd^A&u<$=7=3FyNkdS8iXSaPXbw=MDq63trjBcVC~1<?3vuhH2ZWH;yR){H-5qOo z64ydVkvPGfD+g}<D>x$l2L2DO<<0Db7zqxXv9&XAzW2Uw-h8`W0|9{bAAmJpIOg?# z``o#)amh2U?S)a;yM6S0mYph<3%3-9$0uK$ic?t`D!y{f3h=sZQ5hHVqi;U_p71?# zpK!hv+;Iv^pYXD+wc{37HEy7VFFkJJEqv{9i#02c*YGjE^LQPv;1`cK@C3hkyopzX zcRk+1?Z5Wj#s|S?K7P~f+eFaXq>G<|1T_xub|dh(f$hdS9yf8f@u9~pyxBPMcn!}R zSGnse@eTak_}yzZajW^Q$6M$&fAn}8pErN?_zm1>z4k(M`g^?{j{V4u_jW`#iaKuW ziv3iLr5G(MBa2ErE)%8aO2<Z~q1exJp=Xn+sf3o5)E5knj4YE`E=DFjP<h%7qwelt z|M1{w;29Dt8ev7xq^@SF6t#Fb5>Z4LMT4m_i7GEdhY!MTG$`UTsZ18;voZOwbHCq< zuB#4n8CQ}{GO2}8!c3)jsOC%OP*gH8c8^SvB$KJq2&3av7O_4Pu`zl!K6jE*nOP#M z@R8JdDTZnx^L%Q|{6V|DSS-TWb|g$x(KgGu{8yr#%SoJvQ&Z%40f7$s*h3FH9`_Ng z;Cl{t8H=d2ni_YB_K`wi3`sn~l8>b!E;tK3Mu~*1#@vmu<5Gk!FGtQ<V}=Q)WNe0J zZpBOEf}>TAT#l_Xb9%c@@s#WY=ZwVEcBzXv?qWbchd96y(cd%cynl*4bNf}SujtpF z?Yi2KRfIct9km@8(5<2id$N)~ZTD<O$o(x_(9;=fT201i&dOt7HKg`Fd)=dQNJdg? z&s(27b4~>F6eEWFSmq&Db9VN+Q^MsWR9tz^sL?Aqc3qOURW$cVS;_j+bS5|(7q`C7 ziLR=Y%(AP5>Wa=EIX{|v3%XTUaBc6Jy<3K!9-z(d!hJ)xOZ_B-t~Uia!`wNu{(DB- zb!Wn=Ii1-2g6M!Qa-ODsQLg?IjZU7Pw*J5+->%09s;uN27&kBY<K_H5E?>UneAnh7 IaM-~=0aOVXU;qFB literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Font.pfa b/ExifTool/t/images/Font.pfa new file mode 100644 index 0000000..7b07475 --- /dev/null +++ b/ExifTool/t/images/Font.pfa @@ -0,0 +1,38 @@ +%!PS-AdobeFont-1.0: YehudaCLM-Bold 0.100 +%%Title: YehudaCLM-Bold +%%CreationDate: Sat Jun 12 19:16:03 2004 +%%Creator: Maxim Iorsh +%%DocumentSuppliedResources: font YehudaCLM-Bold +% % 2004-2-16: Created. +% Generated by FontForge 20040531 (http://fontforge.sf.net/) +%%EndComments +FontDirectory/YehudaCLM-Bold known{/YehudaCLM-Bold findfont dup/UniqueID known{dup +/UniqueID get 4195924 eq exch/FontType get 1 eq and}{pop false}ifelse +{save true}{false}ifelse}{false}ifelse +12 dict begin +/FontType 1 def +/FontMatrix [0.000917431 0 0 0.000917431 0 0 ]readonly def +/FontName /YehudaCLM-Bold def +/FontBBox {19 -360 750 840 }readonly def +/UniqueID 4195924 def +/XUID [1021 181 2054546049 7395086] def +/PaintType 0 def +/FontInfo 10 dict dup begin + /version (0.100) readonly def + /Notice (Copyright \.51 2004 by Maxim Iorsh \050iorsh@math.technion.ac.il\051. Distributed under the terms of GNU General Public License version 2\050http://www.gnu.org/licenses/gpl.html\051. \012All rights reserved.) readonly def +% Copyright © 2004 by Maxim Iorsh (iorsh@math.technion.ac.il). Distributed under the terms of GNU General Public License version 2(http://www.gnu.org/licenses/gpl.html). +% All rights reserved. + /FullName (Yehuda CLM Bold) readonly def + /FamilyName (Yehuda CLM) readonly def + /Weight (Bold) readonly def + /FSType 0 def + /ItalicAngle 0 def + /isFixedPitch false def + /UnderlinePosition -100 def + /UnderlineThickness 50 def +end readonly def +currentdict end +currentfile eexec +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +{restore}if \ No newline at end of file diff --git a/ExifTool/t/images/Font.pfb b/ExifTool/t/images/Font.pfb new file mode 100644 index 0000000000000000000000000000000000000000..295f4482758341cbd9a423a4637e608e83c5e1a6 GIT binary patch literal 1423 zcmbtUL2ueH6z(+bl3($5V4~_mVoKY=P92n~QdgnUu7_<06TbutW2d&mKnSTneRe`& zh|$a7kobM?eg6FXy*$5(-W?s)-VcW1B~v5pD``X0h&yo0r=!9Qsk{mAFe|!BvTI8P zIPg5E)$X|!creQ)bTv}TmF!XL@`lvH5OaVQkn;{{c3K3Qaol=IRJsGHRwK9Wsic^q zw8J9L1xI#=Min|i<1(DOL&Aa@bXC64JR4i6KioYYpPYQ$Mo7EehSA~$fgT#3N%-Ji zW++tdcl-=pSMP|vqD1X<Ghg9b(pNgeQgHmK*@F7m+Po7*UQg<whDjR|ZFKDPUrE+g zQ_p7tU)$pv6RY$hI@HpMRKMl&Ae?f^%9FDqj~*m{D)71oRgnt?+gOGcTIB2^z94{4 zfb(P=c?tK69KnNSm`b)<<|>Dj3WF=2qI(2OLuUw97r0vf4eYK0LV)qa!U!`igDtxY zV_L1hQLFhJe#DJ9CJ_7o=Ixhr1yfQiw&}NYig57o8`dvh)Et%}Ic-CDdT|aUZW18m zG=|kK=cYq1ohT^=l&|{`Z@0ddsd7ZsiCin!r-EpP+Bok*-Mjr5b~bQYmE{RSee15D z@mcIF=N{yruG_(1@tzPoflMs&u>s0hWx(%L-~{?y@H|&D@LXNLllLPD{S@sf>=#0m zeXUoXbMq$++_(vm45e|rex?Q0;AfgA`vi2##o|?py_!d?W^YZshxdo24@tAU0Enus zFJ&eW!giy*4s+Azb7TW<lW}FyHSplHYr!QBl;Qr<0wIa3+XOcQYw&)|lZixQ;H<(> zvfT@eu4JMZml+JLn+2-bCg81-6k5CCEnV(vU)CviG7;yP1o40Vf<z$IR!ym%1WS#^ LeI5K9KEM3~A#2Q< literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Font.pfm b/ExifTool/t/images/Font.pfm new file mode 100644 index 0000000000000000000000000000000000000000..fc1abf3249eb30e5514dcd487f4b31e034efbad0 GIT binary patch literal 240 zcmZQz{J_A#;GADjS(KTcQKAqU6s{1QpH@<ySd^-xfDkvdv@}vksszac)gyyO1}+92 zMjggIOi<AX2!qjy$%RGXzXDM7I1pcg%7Q34hRHzkHB@9ZRO2lupMe47BppU(h8N5) zm}fIx1{!@8D8lpa7sD6EFN~QCr3{7)$qf2H_=Pcr;VL5oLqL9UNpNyeW<d!9(1q?r Y`5{ocB(*p@MAs=lCxzh`gDc})02bRd1ONa4 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Font.ttf b/ExifTool/t/images/Font.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5525c9affb6c80ce79d461ebfaa51cbc9c817ab4 GIT binary patch literal 1252 zcmajeT}TvB6ae6J_s3m-g0QgEf}0g)ne2)#EE>3IVvr&xMr6Tl_PR4Nvop-j>WYQf z>O&|J1kr;*Wl%xTLqQq_y;MX|7W5+ZlxzhF_VZxiL&v*Udq`-P+54R{=l;wY*0GZu z3JHJ*sA4c{JbdFN1`7bBPw8`L&9|xw0M|_b_{IC|1dinZ?khNYdZG!Xq4+=u<4KGI zJ)AisJH6*IekEcnX0YJIDU4SzwqyE0&-LDsdpOkssGn3hi#{47p8+BTct50KqQL9f zk8u;mdsM?oeVJ=lF~-YX-<wQ0WO4W?K-21ZQo5<IpIxpJfWtfRyw?ntvizUOcYx+u zyiX0&P7RfxTg3PSz}~xYX7G~bj#8|x#Py~vGvU0uoN)tK4FLHimd&lq@9<lI!ET)2 zZP}((u~dv}4^HEpPRx~)dBA3g4}~IW0zL>yyU>TE-LM;)q&=9ANqf<8=^S)Y+7Dad zY}UBRcDN`_pa5=5yC4@HNW0OGr9F@b&!oM0vN`D-bVk~TUXk`gE-94G1s^Gu&V$XQ zOgbNmNVRkU&c!B(%LRVYBJ2Ve<dAk@H`-6Sggxjya$4An_7PP$N7S3bK2ei>cE89E ziF~f`sPHDVpNydi<cs=CC>LxIHCNGY*oO9yDYO@R08mD5Nn?h*kX}8*YiSSW-=m9R zmzX=>qD47v;b&~#Pn$HR*qWm$i90l6SvseUreP%=ZdcMHaixj|!{OjT{Hg6=ERNp} z9!u)XmQfw8tqMe|d9aQi=g~^q$_8k4sFGF(0yVNk7RYF$JtDeJI|nQ?W;09G6xxjc ztfb8otNAsznY2-5@fcsn-;*Cx&HjI%XXB^bIJ>^+2`w7c5~>`qJK{`<jd$pnz*cD7 zZZmCz>DU+(8^dwry1^VRmd!iaA)*^vJj(U8bQg=Sndw@3Jl&ceO<x#3IXn08`O~jY a-pqX5r&Rpsl`Q{QT3V1VMEL&HOy&=R&mepN literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/FotoStation.jpg b/ExifTool/t/images/FotoStation.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f745efbd67997db0f89a193f9c56f8ad9e388421 GIT binary patch literal 4320 zcmeH~yH6BR6o-HJ3_*PEj)A}?H{oWB!R#got2;gl4D6^e7&T&NK{m^1Ccq3~?<V{K zRQ>_>wv<@dnrPwsiOQq1u(mR>8qZp2I7BMTd#0Li?m2gU-%NA!&3p%|=r0ya$4euF zgCn(IIH-*VT%!~9ZcRj!zbo3W)Ih0Zv<ZfWf-o>a7=_hp$PH8Vsr}miKFc4&?*m_+ zDuaCG`6_>kX()x=)w_qFDA&N>L-K7L@f1(?d|bdo4Ch<W1Q1Bm0UW+Kmsm4no3_)| zbO>r(QvqJ1X*I2&+GsA>Buh=DQZZMvR&&AplW01D{+Xq?mGG)7Y1=M$THRhAJB@>r zcWk_zW?8D~F1+ivZfbgjp2m&Ynx<zcG&*gYEt*S7yfA0R_#K{0#_L{4c1qJr$xdr} zC0Sk5Yso@QZzMaT>8)gEHNBJUoTm4JjgM*iAQ|WQC>iJ2kc@MDl8keFmW*?Jk&JU} zO2#?1B;y?0l5vh5!D^i28;(U;tFsuV)?CYM%wO>P(fQqfqq}nkoB?OR8E^)i0cXG& za0Z+KXTTZw9|rgoUs!vv{$vB_<#|2bnI3TeE0C=C<+2Z4{sGM1gNklLo&5P{Q$E?_ He7Ex#rS{x% literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/FujiFilm.jpg b/ExifTool/t/images/FujiFilm.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b876aed591f4261589453bf1878c0691e4e0265d GIT binary patch literal 1373 zcmaKsK}Zx~6vyB9&8({?IirSd2~HknBSU6Z-E<L4+oUb65Q^{+jkxK6s~{Q%og^qa z>7FDC#4ZsXqB=!~h>%Vlqw*AV?cQzrfAf9Q47vE=?fcD}_r34EH{Z9xx8SFK>-?I3 zo2XPeN%cf@h?*Iv22d4hfDPCu&=#f<gB5y|r9%4>d`$6M@Cg;e!7kQjVc$b@;5OJ_ z&{i(<h?uQ)jva~qK>Y`Lm{Z3FyBg8>wTq?k(!?alUo2ns*K+-iGrhD_p$mu>RYbPv z&-p9ff_2WTl$Sm0zi{h%dD-nf?Vt+BaSP5s!5y;P!9riY(C?D<Pl!6{>ALJ|8pU-# zjO)xQp2e=TktQ9+$+?cUA^Vy^ysr2Tm~F}!hF<xm%=t+8tBqmhRWPqg|6{PG{5xP> z@e6QD#k>ZmtFoRH_HIxtrqHNj-XZ2*HBDdP`v|6ORj2DXY&-sTd|S@>Y#sJh{0a@F z6hFu85AVp@w!qDbk%EjJnFmPiip5s+EFJbG{7kW^O~_4vm|Lys*Yb(;)(E|U#A4mN zTK^d=o`od+QCxIhn()3E8g<|2kio;Vy$dfh;tsfCxXDQPO+$XAFYsH5nBxg{66_|J zOqpNgq?QD?#>^7T6D*cozA`mAI$;S6r|=usLrgNV98Wwve^Wz)pr}g+=bO-_rfwiU zZp|BeN{?@BKR9_~8J8zx7=Es}6M2$1R_qepyT}d~S1Xmf*2?^9WoFUy7c9Tx&6TZL zZ^au4_UJyf8AhfhlWlL$=Ikyz*K0$1M+St96h}iT9wh8YCS!G4c1MSuw=Fwg%=h%< zv0!Xq?4V`wyWj(v^_Wh*#=41{Ofy;Vi7pX~4ykBMNZmdU{sZN!_}AR$34TzMh7V(E MCY_--)D?XH3+=6!8~^|S literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/FujiFilm.raf b/ExifTool/t/images/FujiFilm.raf new file mode 100644 index 0000000000000000000000000000000000000000..c51ef1ea4b597a7f57ca4c2709c2ef9de82e6209 GIT binary patch literal 38452 zcmeHw2OyPi+xT;wV`mj*?@`I#MTAI-ic$%Qk}|R>A~Zy1D2XC%NwQ~I8Bs>E_slv( z=6U}2bIuX<^M3F5eed{w|M%xU-On}d>)!LZUDuPw;k{ZKTKo2^si}(`+GW5kE-5ad zp&_Lpi9k!6(+4bFx%FfY*xPb*L%%z5331uqc|`g)1c_aTAaQmGlAMO12nxX8h!4yE zKol`sPYmPyJq-Q|cZ%NJ?P_@jg0!@@LP%s9hz>;pQ3C8a@bmzu2k<TM>rrS31t8*C zQKWcS1aLMYUJ3BcL>Ur*OMtjS0M-F732;UL7l7YDLP!UZx#JC<;2%9m)bct8pC&yT zf&^|5a2zO7LJ>K14#?7jLR|!2gV>S)`>oyf04bz&OHkd?!qU;yid)Up`n0_%_iy6d zM^4*Ih$1a3DK0LzU0i0nq$Ib5)OH#9?NZ_p_g~xn<?Fv0it;9m@o5YM-6!HWfb-s1 zo(Doi+#HNLD3}}q!H~G5qy##cFy6^XAqYdnSpkj;A;_S>93TlHq%Vr_L#gm1R~FzT zp#=U;fTM|g4S-{axDLQci84n3P991q4?rObB90V?l87Vomx?HF420Z6!F&R9&Y28? zt^pj$_zV*H0kQlN;^L?W%XK7qK$wdUfDD?57l1iRO2j+B9HosTl=%!ula7en0GvLK zkPZ-sHW6`T-b3-^`1&Gz6cI;eF6t3M9uXlS;s_tTf=^1sEdifw1)pLCPPqc7T7j=y zfg^cP0bc~x6mC!uqz_kH30C7FODPg}Dc(^~DI~rYF&%*7>209!2>&P>0U~_F{Z$ti zXV6Yae=fnLwR|1mA$!Hr2$T}12it-$;PfHl^d$JbX+07fY!CPm0oWu&9H0<-1&&#P zlY?yokw+ZG3Y>BUP6cpeRI-8xH8HLipy`P?62?GJn4!FY;8~GAFA+!b;0HJ&ivoM5 zz)u_niLa2CSi-?bktE{y@-ZxreAEgYTEEOkZ&=15#^rWE5pj?;M6v?MFcN0vR!}HX zA}$GVG9tbm;N(PH72x#5xB!KC7*P;6fO`Q!!MM~f;^LGma4MoazWlpZlx+{{XPbj0 zAdLe@CB<drC1n-lxn&e2)uklBPg+V=Tuxk4LP1_l961b$i|>+`h9Di?L;H8_Lvmhr z$P5|>({1HUVg{Ka^b!O)qP!R&Qb<gEIpPY81eAybzdj#QkO$aujSwNE1}J<e2~v6B z4%l1TIBM+&tGNtF5b0M$1`Y9oCkkI-N(RtkV9-+25nv=(9})UBSPv110%tM=8iN!O z=nrBdumbcv0(-%lfItceO*}P$1#{I?RtH$2mQGm%0AG2^LXi+w=W)O%A>yI@zn-!{ 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 aZSBTwZ|cHrX>Dq8n%mse(RAnP-2VrBt;3uE literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/GE.jpg b/ExifTool/t/images/GE.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27463d457b9032ab10b1ab79534d1a6f6ecc0895 GIT binary patch literal 1467 zcmaJ>O=uHQ5T5sTo5r@-q*mH!v5z9Dw6N@M+%#FFHfhqZ+JLkoT0At;SOZCw{y+r} zrHJ-e7Y|Ytg61SW2zu}!;z2xm6SR7;H&0%?itD`n4F>y`WWG0VW_G@rd24;PzC+~P zlraT_5SYgYtbK7ezh1cxP%Od-0ED5F90rQ02Y)8%ws9}w28q}>g!nb-0Rbh!2gosK zvoXM1a@@xs$SKzbqY=gS?L@neMD=6t(IbJ){R)U~oSy*VNFmR1M!~q4nP!ujVtytA zh4NB)xm0CFt+ZHKT4a+qCCDpsLPIyshG*l*f>FG{R4FMX0908{>++Z`YfMe(T1wYc zfD@ZLUZ(Ww<`W+;Yz+k&Z4JA40$>}U9ixk~sw&%p$ADJ1MIUcGAdU^80+tRsoQ&v0 zllt)=o1(98m~Fm(*KBv+g&fSVi!p$C$i@ALJAC_B<{kT8-vH<v^PY{X0K%iXm!~4j zbrQs^(;#MDXu@q?)y1x7jxXV@ZFs&MH~BW2d>f*dk0>@mLCl871$~{R{(|o81UBe5 z?ia8lDIJ@lj4}DNu3$&Tc$Zpq$Zdt&qu(b-;y>{E`TKpoFXXwJH@|6$MTb`zM->N< zPZp;Iw22G0iR-u{P@fCzP`@wmlsdfl)B|m|d(H84bma5E@b?18-;aZb-W21J-D}ME z<R3Ko;mm65Tyw+!t<3DCVc_A6&X|%&vjR$0(^!`8Q)zZtPoz_t%C6v^F`3P}a&Co# zB%8aXGeyzZ6mGSI!VLQyWo_|Nd0CfJlB`H_T2f@jQ{Xe(fh>v5RGdktkhU|`GENjX zfe;Re!xza0w_jWjZojzEeETW!Y9el7{k{4O*wLK&&m-o2m_L(StJQ9^)f;QIYfGg{ zl~wSvm)SyTwKQ&hg}cxjiNtzh!+m|jYJ4!RN^xA$xW?CbE@Q9UL5m!X#n=Fg_xHz> zaTZVJlB1(ZbjWJigWQ5M>m5YH_@N97WC&=K2vK6ahx7PRb@_NA)S`jNzcvtU(O7%X RC1CxdLKM!zOBl4i`~|mpt-SyM literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/GIF.gif b/ExifTool/t/images/GIF.gif new file mode 100644 index 0000000000000000000000000000000000000000..a4298d30324ae3684ad4d74952c8ec35c2c3e0e3 GIT binary patch literal 2321 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 G+5Z4(dl@kR literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/GIMP.xcf b/ExifTool/t/images/GIMP.xcf new file mode 100644 index 0000000000000000000000000000000000000000..875ad3c5c23f2f328f4fbb5c8361d049efd2088a GIT binary patch literal 2405 zcmb7G&2QXP5Pwcunxr8uB}9T=o>b9I0k6HARBfCu@<EykHd!SrHE^lC_FL~$yte!N zw(CTkIKTl8a6sbFLrxqJCypQie*rxp^$*~TXeA_0mBP%ky&p6XI=f^4W;}1^eawvQ z@m53=r$HOsX9Q>@W5h23YZ_c$1|C`coDgK7N#HZUL%>rRM!Q$Q>~*+Lc;HrVF=@ND zv_V1O>F^h~Zda;z_kc?a%SmKA`%Kb0^LVgWc>Kdp1<KvU!kz0Cvl6YbCf__1Z1-@h z<{a)jb8caIX>1{}l2(LnY0~n8*h&@)wj0)&1sK&v0gQy(uy)orG82#%3r#5_%P=~f zPO&pn425Tu=H})Mvuu>hI#}rOK_Kl!58^A}VT>vt>nwHzk0cL+%$MtQT}5r-D@U$l zFrT$pAY-FcEa40&U2q-C5zLmMKr!{V%kC*5m3+QbZE~M(+TuPtSTJzbpi|aL$jldv zL0{<F?ai#`e#Msq?s3kRZwKCDA<<pdu-m>YEWMNPMlB3|y0^ALC5z<=kuy&!@;^dh zbiuu*jGs5~hG2{~LP2fcKP6@_0EUxX`s{h5)nH|`${pDj?38G^|J1DS(ElS5APaZY zE_CxDh>I!{i%!@w5<7xiFpYE}SxR)rSa(=`SK3k~4aIX8EDqbkVX3zmGL^wGNjN5z zVg+g}K@vSfkf$nB2IrjWpa7s0jBdez&@DL@K%EIJC@9P^45jy#skV+eLP(`T-hk&L zWK5e-g?<I3kF`r=Sd}vrwDuY~t1uzoXqUVAJ8e3%f%c%4)L3j7-)rSA{zaQtcpUV< zee{*WF9QDK6RsKyHj}5oq}Jpyg})8S%7O#hNv};V8IA9?ZK0YL*#uJ@qwXu%5@@v< z$}nz*5xr3~N!c{7TjmX`bd#DltulaF(pP_^KRv<ES?F`;;C{v|m&~IamrV0WOV@UM zOvuqtcdjEx!#xb+kjA;==#dEX&WbzsJMz}M+gn4FkX9hY&aG9t_x=a;+(R-B5igN> zGG#k)v{J3Uhfabpz!l=!`slAmspeO`Px*i0DAqI>KLM@?2oCPw20ZDdxQ4i%;wEBI z+gSsf4*^ejDgF#`J;e_YcVr~N=3BIPB7yj4z;9)-0FujFd>>kAdOEo7*`f)*8PhcJ zaYm<D;a;Vd4_+l?`)fF&r#;>5r*JB75^~|op6+Uykc$Q(KOQ-4aX*Wq4b2d8n%wj8 zXHD(5HuX@l1lC4Kx|Z94Q>0gEDy)Sf3WcrS7m#ixG`px?guG>YO!owLQHOazrek^F zLkfpJcdw02H#{fwp*c>gn=ZV%O<{8sodBS@D7pqi>n9A&pVNN_I5dgjCCmelmhxDj zJUB_2>GXEMCFOzBWHF8p8;uy=;r*kZRi&E2p|}qjjbJY2)c^HyeignDJQ217wQcyV zf%F0}etSRxs*e@KSRSLmh>@_d<@kIb9`D20ZgQIofH8DVs`N8q67Fc1#|Jn%pFPW` RI}Ez*LEY$Gl$Ve3_cxZ;6b1kQ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/GPS.jpg b/ExifTool/t/images/GPS.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5d13ec19d43b04cb3df99fbd0865dbe4a4891685 GIT binary patch literal 2133 zcmd5+Yitx%6h8OfnVsF)?Y6VqrKQY|p-`zrn|<`fYLpfzEP^T2TIGkb?JnIh?k;S% zQks}ZMXCIv^@S!TW$}UiCB8Mr2;%dX;H#iNtWP8{DfmKS9M7GZwp*I``sK}@Io~~d z&OP6pbI%wP#-tElGoWt+h{cwJ697uVODH}8j`1C07wVHJWr#~qe~#iK3<zp2RYWxP zS;UKMd>(PF?L!fVFt!}^MU=3Oy%>BIVh`G?5&NliCigF=A~;ajq5Z%Rg++*OL9Fi; z%v^(rYlbcCWB|{gtjea-{TaO{p4YRPK7h8)^|7|thK-2yOtM`c=m@pvvH(|LEdFM6 zOCsBy)Y?<oe6}x@-L5s&1tA;^hNHozXlR8NibTWB(NGX#@r>4*)ARXk2BWPr3|rsz zH+y|HR=ZMb%O#U-n69oTn+B+a$A+v5j^V3U4_j3b@Wf>{MhLK90&WW;HMZZX=3(>~ zZ2THxGGh4<Udd6}e9I5ePDictClE8+{soV<@fpO@sFhd594;I4w6YyLg+4H5a|i{4 zWX$sCm84@<C${2MVK!cg?ei9_ycaOG)W(3FDl1sA{K$$T42L9>Gn#=EVZH}&ti(rR zrZW*O)DUfG#^%g+tH_4F8&LR_-im^kOsGPgFbv~$u2J|x`X&4V&S=Tp@Je%b1fN`A zZ|+4-9=h?tY<m~(1+eUcXqRk^1q2`Z@H;%tgK#yX@M?uC=A<`m+@221wR>&<sI4n) z-L<x(xgJku_I&Xi(=+%7=9->C2fjq{3=(Q}$%}SsV-;ihT8F|pOzW#x_ovglw0x>R z-JOZ+w`+Pj-ka1C@qGLm;|EeVJ1r5Mmgd&jMuC?Mh)>0GVa-%J0N%M1snS7k7Y&h0 z_4zLCm$-C-$IR86i*HXQb1KbT;?M`4;&D>(!!a*!6FB*`2{wj<D%}D^Q|YbdGYQ_# zjGj*>wCnUtZz7vkX(_PQ&JLB9&p!qSszId-=l_w1s=pX;<1^R;j51~l6BrXjNfZS^ zbU7qZl3i}M?2=`7NtxH}DfP&**Y7PYQ+z(3yTl*xD*?m`VdyW2PEm9!9@(S(>t!4# z1?-PV%*A`f7k()O(IGiqvYX(AnqKq%ECWiAzrZ+e47-zmVgyC>Ee^Ff{F~xZ^^WrJ z?vaDeC9Ow}1~#9VS{muj4Y}k66_pFCYA#z=d-?MEhQ_Am7133z*R-u&7hB(PL+6cM zH{HCYCz0INo6>LV%lF^0b70rt-Fxo2ckl3h`$q47;K7F;KJdt+g+q@&@#Is7pB{hq zx#wSa@uinvdG*+v$KQJUop;}R|Kx`sef-I%6Q@p3e)-kc-+cSsneWe@JOATPKmYRU zh2JhJNSO)(6C_TV(4G8c6+v7aa`;*{N%0;2>hNx7dF#l*BS&3JBAcfI-MJI;f~ED7 zHRm{Mlk`91JoKMP&v2fhY`g)A6WU>bk!qmWbA=eE!Nc$?q%Z}ph2O^u`)c8TEX+9d ECrbabfdBvi literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/GeoTiff.tif b/ExifTool/t/images/GeoTiff.tif new file mode 100644 index 0000000000000000000000000000000000000000..1a7374bc5f8567141b80a350267494c54e397ed8 GIT binary patch literal 2660 zcmebEWzb?^V02^P`qRL`pe_KT9UQjSrSKOEI5@uMc_F!QF9Q@fs>(hKxzgDO6F&e| z2bXvC>95;UtOnzgO51;c=x~e(Io<$dI!4%^oBF#y!Xv*lJwqWh#8;szKQC3m(A=+v zG@}_A7=aqNSU|dfY!(m?NOOSs_5V2-+cz=@Gca&4fy9A)&h`x;9UNR>nvn;pjt8oa z2dvI+A_E5)3ILUYj0Tx1$;b$2OE5AqFoVT`7BMnI*+5l{Y*02(mQfI<o`Z)`48~@V zU=)Y3nIaga7+9g^b1+K7*#e9jP;-FpVRQhoLDn(xwBBF<IavURCEC}61$BVz7eL>D z#6hl~0rWA5b^ziPK+N#}|KHiaEq+}6R`iAc<C^!LZ=StsexdMm@8hTk-|tPiV|er2 z_3W!0mzG{|I(z?g^+}0iTaN@Ce80bEujZ~}JCe6CY@WN(YTflUC94FNuUqQ9=*5E8 zc}lbQ&y1e-W6IP?M*Zh|bGkV@mbE!IKWMD2m#W!b6;l4Ow69pZ;6z?(4pZj*beq%} z$zPKK6V}CZ#imCej#7xI3A-6;8ayfRU4XaWa$gqj1kb%5l5S-#mz?z-yX{}tx!5eU z`fC|wzRgV7B;V+ap@x2|?n50rtyvo1)q_<xsPHIdDjbzplC6`zEoCk-MeKv9ukcDC zHi0C*{k+oL6`WT%4B2{FUNO5dE(V1FX~6zJX|VnuX>k8v(%}C;q(KG-l}Vl|x!Wdr zY`b+k=RXkqtN!!-ck$2nKeE3)|C;>y!KauHH{OT5z3|58)rps`&ksJceX{eh>BEf= zbnmUYt9E<QEx8-Bu8UorbcO#?_eJ*e&1e6gsXq1nWXbUl$8wImIGl3u!GV~4H}{6_ zzPQVG$I0!kTMurr+q7$=+4@cE^wz9et+sOU3i)NTmx?c*yoi56_k4~yjWd5wFP-v! zQbzxy-k7dy9RaN;o1Gi>)|pptsMIQ3QX*G4BVRD5JBu~FKIKPJQT&_Ow5SIWk)fA^ zeFKjBIe72#G;v$!qT#sEUfO1=6~B3hDU(r+{x{tMt(O|fs&|#c6)wnmNgt846WbwT zD7czmm1jPe1p6db9;P-#+#_NY5nqUyK!h=-4pP<I|A*-y&U}RW`hPGT_}qg{{r<l& z9hmX|xA`~wkM3{$pC^A5e>eM${L979)t{_+WN?4WU5i_LZa7>$a@pg;nR5ZBubhlL zcK1l)!KeE(_q^Rzxc%$a%1wVaG_7M>-L;Z$*`y_63un!jo3m(^+VoXZ^(SqfVBWjC z+rHy)n|t%=M*q6YHQ|+a$`eYS6lLVU$t}qGl2MWRC%GY!HNGnLLrhxKoyfrO<6*WT zn}an1=LLxPb@}}FF7<rvk?3~K)yMgelZC@Ndu7|1HvCqt7Qf63OrM#=7+o}U*WasW ztg}K}PIHO|ms-8bSLJNQhYI0xXJnnEcSz|;EEbm(?H6GcsuK9XpT>KKCy?tnr!D(t vHVu|}%p#0k3=E*q0)-AaEb?{Q|0mZ@kbB8<EySmo@eeElM!{$ZP&otuQH}D{ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Geotag.gpx b/ExifTool/t/images/Geotag.gpx new file mode 100644 index 0000000..f4df945 --- /dev/null +++ b/ExifTool/t/images/Geotag.gpx @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?> +<gpx + xmlns="http://www.topografix.com/GPX/1/0" + version="1.0" creator="Wissenbach Map3D 2.21" + xmlns:wissenbach="http://www.cableone.net/cdwissenbach" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd + http://www.cableone.net/cdwissenbach http://www.cableone.net/cdwissenbach/wissenbach.xsd"> +<trk><name>TESTTRK</name> +<cmt>Test Track</cmt> +<trkseg> +<trkpt lat="43.641000" lon="-116.062231"><ele>1472.396484</ele><time>2003-05-24T17:10:05Z</time></trkpt> +<trkpt lat="43.641086" lon="-116.062059"><ele>1474.319092</ele><time>2003-05-24T17:09:55Z</time></trkpt> +<trkpt lat="43.641450" lon="-116.061802"><ele>1479.606201</ele><time>2003-05-24T17:09:46Z</time></trkpt> +<trkpt lat="43.641751" lon="-116.062102"><ele>1481.528809</ele><time>2003-05-24T17:09:35Z</time></trkpt> +<trkpt lat="43.641922" lon="-116.062338"><ele>1488.739014</ele><time>2003-05-24T17:09:28Z</time></trkpt> +<trkpt lat="43.642030" lon="-116.061995"><ele>1491.622803</ele><time>2003-05-24T17:09:22Z</time></trkpt> +<trkpt lat="43.642201" lon="-116.061738"><ele>1496.429443</ele><time>2003-05-24T17:09:17Z</time></trkpt> +<trkpt lat="43.642545" lon="-116.061587"><ele>1502.677979</ele><time>2003-05-24T17:09:10Z</time></trkpt> +<trkpt lat="43.642824" lon="-116.061502"><ele>1507.003906</ele><time>2003-05-24T17:09:04Z</time></trkpt> +</trkseg> +</trk> +</gpx> diff --git a/ExifTool/t/images/Geotag.igc b/ExifTool/t/images/Geotag.igc new file mode 100644 index 0000000..140b89f --- /dev/null +++ b/ExifTool/t/images/Geotag.igc @@ -0,0 +1,24 @@ +AXMP eTrex Venture HC Software Version 3.10 - 695 +HFDTE050110 +HOPLTPILOT: Josef Klein +HOGTYGLIDERTYPE: Swing Powerplay +HOGIDGLIDERID: 001 +HOCIDCOMPETITIONID: DHV-OLC-2005 +HODTM100GPSDATUM: WGS-84 +HOCCLCOMPETITION CLASS: Paraglider Tandem +HOSITSite: Felsberg +B0723154916430N00643910EA0000000443 +B0731584915416N00645637EA0000000620 +B0742154913984N00648753EA0000000623 +B0753514911464N00650599EA0000000774 +B0805384909414N00653521EA0000000994 +B0813484908365N00653982EA0000001152 +B0824344910245N00649184EA0000000990 +B0836044913533N00645664EA0000000936 +B0846224917082N00642530EA0000000493 +B0852564917363N00642421EA0000000259 +LXMP MaxPunkte_Comp_6.2.2 +LXMP Thursday 07.01.2010 21:23:09 +LXMP Val2.2 +GMaxPunkte 1887F85651F8CAD288F2DE8A2BD3D06C44FB1A83 +GMaxPunkte 5FFF1DF9967C1F0B622473365EC1320B69AFEA3B diff --git a/ExifTool/t/images/Geotag.kml b/ExifTool/t/images/Geotag.kml new file mode 100644 index 0000000..ad456a8 --- /dev/null +++ b/ExifTool/t/images/Geotag.kml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom"> +<Document> +<name>Location history from 11/13/2013 to 11/13/2013</name> +<open>1</open> +<description/> +<StyleMap id="multiTrack"> +<Pair> +<key>normal</key> +<styleUrl>#multiTrack_n</styleUrl> +</Pair> +<Pair> +<key>highlight</key> +<styleUrl>#multiTrack_h</styleUrl> +</Pair> +</StyleMap> +<Style id="multiTrack_n"> +<IconStyle> +<Icon> +<href>http://earth.google.com/images/kml-icons/track-directional/track-0.png</href> +</Icon> +</IconStyle> +<LineStyle> +<color>99ffac59</color> +<width>6</width> +</LineStyle> +</Style> +<Style id="multiTrack_h"> +<IconStyle> +<scale>1.2</scale> +<Icon> +<href>http://earth.google.com/images/kml-icons/track-directional/track-0.png</href> +</Icon> +</IconStyle> +<LineStyle> +<color>99ffac59</color> +<width>8</width> +</LineStyle> +</Style> +<Placemark> +<name>Latitude User</name> +<description>Location history for Latitude User from 11/13/2013 to 11/13/2013</description> +<styleUrl>#multiTrack</styleUrl> +<gx:Track> +<altitudeMode>clampToGround</altitudeMode> +<when>2013-11-13T01:03:34.245-08:00</when> +<gx:coord>-106.026069 34.166232 0</gx:coord> +<when>2013-11-13T01:04:35.259-08:00</when> +<gx:coord>-106.027012 34.165476 0</gx:coord> +<when>2013-11-13T01:05:35.277-08:00</when> +<gx:coord>-106.027854 34.164765 0</gx:coord> +</gx:Track> +</Placemark> +</Document> +</kml> diff --git a/ExifTool/t/images/Geotag.log b/ExifTool/t/images/Geotag.log new file mode 100644 index 0000000..0d4c27a --- /dev/null +++ b/ExifTool/t/images/Geotag.log @@ -0,0 +1,13 @@ +$PMGNTRK,4415.163,N,07631.126,W,00095,M,110833.53,A,,030409*68 +$PMGNTRK,4414.765,N,07631.087,W,00100,M,110905.52,A,,030409*6B +$PMGNTRK,4414.711,N,07631.117,W,00099,M,110920.53,A,,030409*67 +$PMGNTRK,4414.662,N,07631.160,W,00100,M,111021.54,A,,030409*6D +$PMGNTRK,4414.492,N,07631.159,W,00101,M,111045.54,A,,030409*69 +$PMGNTRK,4414.372,N,07631.145,W,00101,M,111103.53,A,,030409*69 +$PMGNTRK,4414.145,N,07630.915,W,00104,M,111133.53,A,,030409*65 +$PMGNTRK,4413.791,N,07630.854,W,00099,M,111210.54,A,,030409*69 +$PMGNTRK,4413.425,N,07630.765,W,00095,M,111326.54,A,,030409*60 +$PMGNTRK,4413.633,N,07630.004,W,00092,M,111441.53,A,,030409*63 +$PMGNTRK,4413.584,N,07629.988,W,00089,M,111454.53,A,,030409*67 +$PMGNTRK,4413.488,N,07629.969,W,00085,M,111515.54,A,,030409*6A +$PMGNCMD,END*3D diff --git a/ExifTool/t/images/Geotag.xml b/ExifTool/t/images/Geotag.xml new file mode 100644 index 0000000..2b18bee --- /dev/null +++ b/ExifTool/t/images/Geotag.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" ?> +<History xmlns="http://www.garmin.com/xmlschemas/ForerunnerLogbook" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.garmin.com/xmlschemas/ForerunnerLogbook http://www.garmin.com/xmlschemas/ForerunnerLogbookv1.xsd" version="1"> +<Run> +<Track> +<Trackpoint> +<Position> +<Latitude>43.64986</Latitude> +<Longitude>-79.58321</Longitude> +<Altitude>142.893</Altitude> +</Position> +<Time>2004-08-28T13:45:00Z</Time> +</Trackpoint> +<Trackpoint> +<Position> +<Latitude>43.64987</Latitude> +<Longitude>-79.58320</Longitude> +<Altitude>150.374</Altitude> +</Position> +<Time>2004-08-28T14:15:00Z</Time> +</Trackpoint> +</Track> +</Run> +</History> diff --git a/ExifTool/t/images/Geotag2.log b/ExifTool/t/images/Geotag2.log new file mode 100644 index 0000000..3f2e61d --- /dev/null +++ b/ExifTool/t/images/Geotag2.log @@ -0,0 +1,7 @@ +$PTNTHPR,254.7,N,-1.7,N,-12.1,N,A*6E +$GPRMC,112723.000,A,5040.3182,N,00505.9469,E,28.66,138.74,240410,,,A*51 +$PTNTHPR,253.9,N,-2.2,N,-10.7,N,A*6C +$GPRMC,112742.000,A,5040.2023,N,00506.1107,E,27.89,139.01,240410,,,A*56 +$PTNTHPR,260.5,N,-7.5,N,-9.3,N,A*73 +$GPRMC,112802.000,A,5040.0932,N,00506.2621,E,25.54,138.58,240410,,,A*59 +$PTNTHPR,254.3,N,-3.7,N,-5.5,N,A*7E diff --git a/ExifTool/t/images/Geotag3.log b/ExifTool/t/images/Geotag3.log new file mode 100644 index 0000000..5102b97 --- /dev/null +++ b/ExifTool/t/images/Geotag3.log @@ -0,0 +1,7 @@ + 1 0001 42.721544 61.671879 249 25.21 21/04/2014 07:06:41 25.21 5.90 3.27 ypr + + 1 0002 42.721664 61.672028 250 21.94 21/04/2014 07:06:42 21.94 7.33 1.26 ypr + + 1 0003 42.721786 61.672173 249 28.36 21/04/2014 07:06:43 28.36 6.42 0.34 ypr + + 1 0004 42.721910 61.672337 248 22.98 21/04/2014 07:06:44 22.98 5.50 -0.80 ypr \ No newline at end of file diff --git a/ExifTool/t/images/Geotag_DJI_2020-12-02_[07-50-31].csv b/ExifTool/t/images/Geotag_DJI_2020-12-02_[07-50-31].csv new file mode 100644 index 0000000..a8f5cb7 --- /dev/null +++ b/ExifTool/t/images/Geotag_DJI_2020-12-02_[07-50-31].csv @@ -0,0 +1,94 @@ +Id,Time(seconds),Time(text),Latitude,Longitude +1,0.3,0m 0.3s,-44.69009395,170.42456545 +2,0.4,0m 0.4s,-44.69009389,170.42456538 +3,0.5,0m 0.5s,-44.69009368,170.42456536 +4,0.6,0m 0.6s,-44.69009359,170.42456529 +5,0.7,0m 0.7s,-44.69009355,170.42456542 +6,0.8,0m 0.8s,-44.69009345,170.42456534 +7,0.9,0m 0.9s,-44.69009337,170.42456518 +8,1,0m 1s,-44.69009331,170.42456511 +9,1.1,0m 1.1s,-44.69009328,170.424565 +10,1.2,0m 1.2s,-44.69009326,170.42456496 +11,1.3,0m 1.3s,-44.69009337,170.42456508 +12,1.4,0m 1.4s,-44.69009337,170.42456506 +13,1.5,0m 1.5s,-44.69009351,170.42456512 +14,1.6,0m 1.6s,-44.69009345,170.42456509 +15,1.7,0m 1.7s,-44.69009359,170.4245652 +16,1.9,0m 1.9s,-44.69009328,170.42456512 +17,2,0m 2s,-44.69009316,170.42456514 +18,2.1,0m 2.1s,-44.69009334,170.42456511 +19,2.2,0m 2.2s,-44.69009329,170.42456518 +20,2.3,0m 2.3s,-44.69009325,170.4245656 +21,2.4,0m 2.4s,-44.69009319,170.4245658 +22,2.5,0m 2.5s,-44.69009329,170.42456598 +23,2.7,0m 2.7s,-44.69009283,170.4245669 +24,2.8,0m 2.8s,-44.69009242,170.42456753 +25,2.9,0m 2.9s,-44.69009213,170.42456775 +26,3,0m 3s,-44.69009167,170.4245683 +27,3.1,0m 3.1s,-44.69009135,170.42456885 +28,3.2,0m 3.2s,-44.69009102,170.42456924 +29,3.3,0m 3.3s,-44.69009102,170.42456972 +30,3.4,0m 3.4s,-44.69009081,170.42456996 +31,3.5,0m 3.5s,-44.69009059,170.42457032 +32,3.6,0m 3.6s,-44.69009042,170.42457045 +33,3.7,0m 3.7s,-44.69009023,170.42457053 +34,3.8,0m 3.8s,-44.6900901,170.42457054 +35,3.9,0m 3.9s,-44.69009008,170.42457032 +36,4,0m 4s,-44.69009,170.42457026 +37,4.1,0m 4.1s,-44.69009002,170.42457028 +38,4.2,0m 4.2s,-44.69008998,170.42457021 +39,4.3,0m 4.3s,-44.69009004,170.42456997 +40,4.5,0m 4.5s,-44.69009009,170.42456979 +41,4.6,0m 4.6s,-44.69009007,170.42456965 +42,4.7,0m 4.7s,-44.69009011,170.42456949 +43,4.8,0m 4.8s,-44.6900901,170.42456935 +44,4.9,0m 4.9s,-44.69009027,170.42456917 +45,5,0m 5s,-44.69009027,170.42456904 +46,5.1,0m 5.1s,-44.69009024,170.42456892 +47,5.3,0m 5.3s,-44.69009024,170.42456862 +48,5.4,0m 5.4s,-44.69009023,170.4245685 +49,5.5,0m 5.5s,-44.69009038,170.4245683 +50,5.6,0m 5.6s,-44.69009037,170.42456818 +51,5.7,0m 5.7s,-44.69009036,170.42456817 +52,5.8,0m 5.8s,-44.69009035,170.42456808 +53,5.9,0m 5.9s,-44.69009035,170.42456806 +54,6,0m 6s,-44.69009035,170.42456801 +55,6.1,0m 6.1s,-44.69009021,170.42456752 +56,6.2,0m 6.2s,-44.69009018,170.42456746 +57,6.3,0m 6.3s,-44.69009019,170.42456757 +58,6.4,0m 6.4s,-44.69009014,170.42456755 +59,6.5,0m 6.5s,-44.69009001,170.42456755 +60,6.6,0m 6.6s,-44.69008996,170.42456756 +61,6.7,0m 6.7s,-44.6900899,170.42456763 +62,6.8,0m 6.8s,-44.69008985,170.42456767 +63,6.9,0m 6.9s,-44.69008982,170.4245677 +64,7,0m 7s,-44.69008977,170.42456775 +65,7.1,0m 7.1s,-44.69008971,170.42456773 +66,7.2,0m 7.2s,-44.69008964,170.42456778 +67,7.3,0m 7.3s,-44.69008958,170.42456798 +68,7.4,0m 7.4s,-44.69008952,170.424568 +69,7.5,0m 7.5s,-44.69008948,170.42456825 +70,7.6,0m 7.6s,-44.69008944,170.42456828 +71,7.7,0m 7.7s,-44.69008943,170.42456831 +72,7.8,0m 7.8s,-44.69008937,170.42456834 +73,7.9,0m 7.9s,-44.6900893,170.42456855 +74,8,0m 8s,-44.69008922,170.42456853 +75,8.2,0m 8.2s,-44.69008899,170.42456858 +76,8.4,0m 8.4s,-44.69008861,170.42456849 +77,8.5,0m 8.5s,-44.69008836,170.42456849 +78,8.6,0m 8.6s,-44.6900882,170.42456853 +79,8.7,0m 8.7s,-44.69008796,170.42456851 +80,8.8,0m 8.8s,-44.69008784,170.42456857 +81,8.9,0m 8.9s,-44.69008797,170.42456858 +82,9,0m 9s,-44.69008791,170.4245686 +83,9.1,0m 9.1s,-44.69008788,170.42456842 +84,9.2,0m 9.2s,-44.6900878,170.42456834 +85,9.3,0m 9.3s,-44.69008788,170.42456845 +86,9.4,0m 9.4s,-44.69008781,170.42456838 +87,9.5,0m 9.5s,-44.69008777,170.42456858 +88,9.6,0m 9.6s,-44.69008774,170.42456857 +89,9.7,0m 9.7s,-44.69008734,170.4245683 +90,9.8,0m 9.8s,-44.69008731,170.42456832 +91,9.9,0m 9.9s,-44.69008766,170.42456823 +92,10,0m 10s,-44.69008766,170.4245683 + diff --git a/ExifTool/t/images/GoPro.jpg b/ExifTool/t/images/GoPro.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8a88d9e50aeb5750c5cf9a4e691358811039a22b GIT binary patch literal 4601 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/HTML.html b/ExifTool/t/images/HTML.html new file mode 100644 index 0000000..2fa94ee --- /dev/null +++ b/ExifTool/t/images/HTML.html @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <title>ExifTool HTML Test</title> + <meta http-equiv="Content-type" content='text/html; charset="iso-8859-1"' /> + <!-- Note: The HTML standard uses a '.' namespace separator (ie. "dc.title"), + but a ':' is used in the Daisy specification, as below --> + <meta name="dc:title" content="ExifTool HTML Test" /> + <meta name="dc:creator" content="Phil Harvey" /> + <meta name="dc:creator" content="Another Creator" /> + <meta name="dc:date" content="2007-30-01" scheme="yyyy-mm-dd" /> + <meta name="dc:format" content="Daisy 2.02" /> + <meta name="dc:identifier" content="DTB00345" /> + <meta name="dc:language" content="EN" scheme="ISO 639" /> + <meta name="dc:publisher" content="TPB" /> + <meta name="dc:source" content="0-065-01022-1" scheme="ISBN" /> + <meta name="dc:subject" content="Greek: &alpha; &beta; &gamma;" /> + <meta name="ncc:sourceDate" content="1993" scheme="yyyy" /> + <meta name="ncc:sourceEdition" content="1" /> + <meta name="ncc:sourcePublisher" content="Phil's Desktop" /> + <meta name="ncc:charset" content="iso-8859-1" /> + <meta name="ncc:generator" content="ExifTool 6.73" /> + <meta name="ncc:narrator" content="a narrator" /> + <meta name="ncc:tocItems" content="1024" /> + <meta name="ncc:totalTime" content="91:27:21" scheme="hh:mm:ss" /> + <meta name="ncc:pageNormal" content="881" /> + <meta name="ncc:maxPageNormal" content="881" /> + <meta name="ncc:pageFront" content="27" /> + <meta name="ncc:pageSpecial" content="45" /> + <meta name="ncc:prodNotes" content="0" /> + <meta name="ncc:footnotes" content="0" /> + <meta name="ncc:sidebars" content="0" /> + <meta name="ncc:setInfo" content="1 of 3" /> + <meta name="ncc:depth" content="4" /> + <meta name="ncc:kByteSize" content="1530000" /> + <meta name="ncc:multimediaType" content="audioNCC" /> + <meta name="ncc:files" content="97" /> + <meta name="prod:recLocation" content="Studio 2" /> + <meta name="prod:recEngineer" content="P Harvey" /> +<!--[if gte mso 9]><xml> + <o:DocumentProperties> + <o:Subject>a subject</o:Subject> + <o:Author>an author</o:Author> + <o:Keywords>keyword1, keyword2</o:Keywords> + <o:Description>a comments&#13;a new line</o:Description> + <o:Template>Normal.dotm</o:Template> + <o:LastAuthor>Phil Harvey</o:LastAuthor> + <o:Revision>2</o:Revision> + <o:TotalTime>1</o:TotalTime> + <o:Created>2010-06-28T23:52:00Z</o:Created> + <o:LastSaved>2010-06-28T23:52:00Z</o:LastSaved> + <o:Pages>1</o:Pages> + <o:Words>84</o:Words> + <o:Characters>324</o:Characters> + <o:Category>a catégory</o:Category> + <o:Manager>a manager</o:Manager> + <o:Company>a company</o:Company> + <o:Lines>26</o:Lines> + <o:Paragraphs>26</o:Paragraphs> + <o:CharactersWithSpaces>382</o:CharactersWithSpaces> + <o:Version>12.0</o:Version> + </o:DocumentProperties> + <o:CustomDocumentProperties> + <o:Checked_x0020_by dt:dt="string">Phil</o:Checked_x0020_by> + <o:test1 dt:dt="boolean">1</o:test1> + <o:test2 dt:dt="float">15</o:test2> + <o:test3 dt:dt="date">2010-02-05T05:00:00Z</o:test3> + <o:test4 dt:dt="string">text</o:test4> + </o:CustomDocumentProperties> + <o:OfficeDocumentSettings> + <o:AllowPNG/> + </o:OfficeDocumentSettings> +</xml><![endif]--> +</head> +</html> diff --git a/ExifTool/t/images/ICC_Profile.icc b/ExifTool/t/images/ICC_Profile.icc new file mode 100644 index 0000000000000000000000000000000000000000..045801f54ca93f921a65403461447faaf1ee8544 GIT binary patch literal 492 zcmZQzV0`1}?eE8=z`&53S5g$@?xYYA8KuB}o`Hvfok57fnn5fvxwybFAi#$K2=emt zQh^jBh>ZZ>u0cdD>mtO6W^tyZ7AFH$e*j|NBB13!;~ju(k#q<<1jJ5)uromHqL3hG zpqd&Wn=c*0?g6orAnZ9Hc6mub2~f=%kox3;A`trkkgWr?3&>=U_RGx9&r@(r$xljE z0QuWQ&j1LG4GauG(ojG^6)3DAD(6TtF!(QHU|{)!5Hsm!U|68dz`#EhA*N=;z#yQ{ zz_4#Ya%oW+)D}i2V+IC7e1sXFL%`w9D0aRiwF2lj1_p><JoA$E6x0+T9OwL^g8ZVy Nl1!i{jSLJ7835JbQnCO5 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ICO.ico b/ExifTool/t/images/ICO.ico new file mode 100644 index 0000000000000000000000000000000000000000..2298f46673756e5ca86a73a0d60006e138a3e823 GIT binary patch literal 78 xcmZQzU<5%%CNOQmz`!5|#2P@%2*gZa8pL8?U;$zUJqQMggYf_V|A8a|003V81SJ3f literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/IPTC.jpg b/ExifTool/t/images/IPTC.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5bdefb2e8f1e4397b3c5651cf21ac2878f176331 GIT binary patch literal 9851 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 IYWrXR1F`#5#sB~S literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ISO.iso b/ExifTool/t/images/ISO.iso new file mode 100644 index 0000000000000000000000000000000000000000..f2067b451984dae83b4eb345f759d204c0ca97d4 GIT binary patch literal 40960 zcmeI*UuauZ90%}U(vii^ZINgZAM{eNFS>YdE2W~W|B}*RUDG7h`3JYO&28>TlW=o0 zQc|?D57vQYsC#fvb>JV$9tM`xvY`dp%9P5K?MZxysFYS6o6_l8t-qU_rfbut6(rs8 zd&2#5&iUPQPCf~_IrrtbyjCJQc8qsxrmA0V4qZ7%ee(;l^sOvilznoW<+-8nhR%CG zrM}O|GES!7-k*8eLgXE-KRePcT@>g3oU*HBN9Nl{61IV~N!uY$TCQ0}8)omDr5D;p ztyA`abnT06yU$#x^V~@Lyq>evH@=PJ_fBu0cGB+G`bn<qpEQ4eT|Tn6u5W%K8B5%C z^W4v=8D}5S^Z+UGXjq9Pl8z1~BJ?QnxDx3WqN>mtRn<gHbL=dY56aRva*5@Slx#0s zNXcH=WAPm_9kRXglYFCkIyLcwWiUN>{;j#{UsHn@tm*nsOy;9y*HcsWH@j?d;PdGR zsWlc<1Df7)yf>}|dxUT_&?N*_HL7w4FP|FSb#!mXwvV?@*`6v*T${T8_u0SJ1pJQ+ z7j_yAKGJ=~Pt;E)qS}L6rtG5~M1L<XUgfQ27uT6L?z(-%)#MgD^<HkvL@)6Lp_32o z*RSJtGimWdZk1DAW>54*HvjzT<F)Bm-*J!GM?R<+AG>;Yw*C?y`*}OQ_+)vu2nPs2 z00I!W4Fx>UWCo?*EA*dybA2op4i*?^rv5-IplXVyL?Z`!0<pEC_4Z2Esz4b8AOHaf zKmY<;FR*<7FL|3sGtBeBf`c;d|IJOlf>AVFM`SM?f?5bb00Izz00g#5;N<Y7*&oNN zoUc!W&Wu&OJDGc~dyny4cWGRc+2_95=fXBO`KXnesh2uPC55_{0{`W4-TrMx|N4_` zJ$-32j_da?83EWs00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00fF4U}|)V zqGTdrgXBsovOgLP3;tkS6P}HR9fEZ0CAp!_@Y28LnOt_g6JE#1q;F)k(p;GyRW|ok zc5xpr{#q|7r+f7S@gcMNkBu~wwUizp?!vf!<*0l??baa`;oQ?KcoH39B_2{D-9_kc zbr+tH5*C^R>fvDT>Z+pTLtRQj;~_|*LlO^HJyBDXUY9%RjK+FZr8}fKwB-7qeV`+* zse#UdK}w`6m@L=|_gX1R)uJTUNTMiKR*NF#6>!ChL@RQf;V<s;@NxEk6fl0%u!q3y zAYk18T`hvY)$8`R3O<jk(cR>3toOTH_icKBuas;$6oC+c00bZa0SIihfH^z=Ct-t^ zlY+d|uR;NeaAce`UiLT0`%Vhc9&+a1M|S@I@MErA(OM_Q{N0%UpQz9yDo(_6e<Dhf zdPzB5<}jY?UnZ-5wC2tKHyHCq@9EC3-_MIMd--K7$GnFma!?g-)hts<BIh6HoS5C$ z@VP`aoHt&Na#Ran+mOA!f!evPef)VYx3fB?i5GreuD=rmH}xgiR8Zb@Kd&Hfx}R5I i(!a?_5P$##wq9Uy>Hfd<L%|3j009U<00IywlE6QXLH`B- literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ITC.itc b/ExifTool/t/images/ITC.itc new file mode 100644 index 0000000000000000000000000000000000000000..28c11a104f7c22c031c3002f91f054e0f4da6d53 GIT binary patch literal 672 zcmZQzV3f%$NzPzkU|>SR42eZ0<v{)j2aGM5C8@c<K)3+Jj7XSaqF3$%DfMZBlHmn{ zao&rz<>V*l1o*k90o5=AF$W0$M*=|pfP$37lEhA+19`cmxPT7f_4IHF0@7f2a)4sq zJ6H1#kYY)9^mSxlxXJ8v&3_?~&spFRS<Jw|Eeyhp4727)00oswTq8<?^V3So6N^$A z0!uTKvqLg-Qx(khj1@F|6O$GEgB2n)8I*Gm@&Q$|mw5WRvfp6jWi-+|d;J?5P)O9% s#WAE}PV%4s|LvL04zz@pT<~IG2)n|}YyRfUVxR&BPgg&ebxsLQ0HPjCtpET3 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/InDesign.indd b/ExifTool/t/images/InDesign.indd new file mode 100644 index 0000000000000000000000000000000000000000..f8d061cedde9d4f53bbecb6cde2ec5eae06dde2e GIT binary patch literal 12288 zcmeI1y>HV%6u?tHB2fm!j4D}TFawV5q+hm@R8>MnRk?*yM8$UTU0N&g2hK%FJ0K=t zK=4m=L8@377!WEO8w(2(0<j<_s2C6|e7ti$nuMsd5=EuFlkR-ydw1{M&(AM@N<9AI z^UI;BcTe=q_uu)Wq5NF#vUP5D;ZQ7gB8bIehoOox5IcElz0rxr2dNQfJlb<0IDb?I zpzW;3VLW@kF&Br+#$2O+3@$(Vl-52CDQy2&0)1R#SGR<(VL<{&00|%gB!C3|Z35z( zQ+!v7??chQ2UV0?jSIF&00|(0JtgqTzP=vcVg4UG;Q9ZN9@4G6y)*Y0ea!!RK;dvl z1a_6|_YubYe@B>62niqoB!C2vz`iDc`TxFtJZKyeIPe5E?X^2^3}fZu?V0jj`{UOK zZ(ol?j(Y<R{*=KV;vx-!f5u6Aq8<>t#JE&oOOBV3Hy>}vl0!4{l@Uv`f*f0R&aZ~- z((3Gjy;`yjDo<q7b+hgUF5^V1yJfFp)-y7peu0_L7q?YeibQxRBQIJPB<}m=lEbA` zLQ|5)S*aKjm(~2RB<YD!IXjpRY0;d|Pc^rLQAS?oJTO(YR;wjyse~Ucsk&hps+Lrf zNd+X7%8JKHUGXZXL0e>ov)EqdRT7a!3EOv7XHnG?nhJ^rrASqtRqW6SxZ``0P)!Pc zm1kr+LN$X97nJ09N>_B9+Df5Fk_sI+7-3@tQWzhW2c@RApu@^8bua<?QbM1UR>l4u z`u?m9+$nCM>M}yiv`)R(R@6z*A3RL7ZIIG1GIJp#oaLcrlbSZBXroF>ThKI9H%u*g zHLdmt0qw1Iez*XRl${i=CQW-l)mid{Q*|tB%Cy#5;Ej}U(qE%K*3{S~)?VvVr?~RF zm0Lo4{<MLOWo=K|btAWnpjnog_wB07JU*SzHeOKb+YU7yk5aSg?PNP|kHB{C)1(78 z=b1+x@ebw6q*BRJRwUIjZ!PJW-E%n9Zj%YBVL6Hz)K*!U2>=!FL%oTH+Gf!YUBa^@ z2+EF4#2M7O0#qW8N~>EE{mpK?)ex{CbgAuN2!nPs1vDAC2C?$n^H;%_n=^B3FP@!w gIC(ApYY;y0zeRXJB!C2v01`j~NB{{SfxkoGC$&4HHvj+t literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/InfiRay.jpg b/ExifTool/t/images/InfiRay.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d762abd643b97fcb0437cf48c9d139befc57f3f4 GIT binary patch literal 2207 zcmeHJ%}Z2K6#t!hW<wgyh^0e@FQX@<JnmebrwuXcpvG8&7`13K2!1e&86t~pQM}49 zqD4dm1wxA!?fVBTS_CPy3Cb3If4FMXF7S5lE8jewk`gL{4xD$-Iq!V_?tOPgJy9>| zbI(L>6zK2AE&ySK2`FQUm(U}MAjI+p<^s`@-I$?GzI|dUw`9jPs)FIgax_WLr<(P% z7k2tSw^_%4%pN?D&SnOYDU9aEM+)O3kV3X3g-nWeGcJ>|HQAEjBv-<Iqz(isIO{rI zxeiw-_henIk2}`AY6dp?_Caj*Y<ecJm3=)1Dg*vTKwj_XKg_k4Y8}Nk&!>XJFM7HD zz@x0V0rap4Iz9eA(DYpQ^t0fqR~Y1xDAPC4vzIX`(=h7o3zDnx@`zjt13%W;DGc=* zTlNee&*x8?x%|*rt}tc}4;>pysW({Au)A(EHIwNj3_j*}0D0cJMHRG`%IE7`xTR*_ zLQKB$dj)-<Ds>4u!^Z@;5t@o!k4=+$i)P?K7Z01db>vhBzhc&Ufc!SqJKSTttKB*S z4*!y<-lKsoGwz?5HU2Ax)w8G%XaH`eneP_Y<x{E!Bb4*+17Mm{NjR|424nkIU&*qK z7GHOJuTD(_(i9K>n&Z>iL(&#mDcVI`2oc|%jwhUi6K`$jI-TbhKc3*#{kCFKoYwlt zIL34v19C;kFUE7_He1APA=cLa-0Y=}x!L`lx|g-kfBgS0{^|uHeZ84p?>i!WWvXYG zM0F$*tBN(&)HF(~-jcS(WTz6EQr%r1b+1OWITkbPOslrmaxBwwx}BybhXc}y^lD+n zcWMSv{&Rq^K@A8+$%vA=k9}U7%g$MO=>;6&DV9^T#G>X=Y49(2)QIwRc?b3C=@+wN B{c!*Q literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/JSON.json b/ExifTool/t/images/JSON.json new file mode 100644 index 0000000..dbdaf10 --- /dev/null +++ b/ExifTool/t/images/JSON.json @@ -0,0 +1,19 @@ +{ + "tags": [], + "description": "test description", + "title": "2013-02-02", + "people": ["one","two","three"], + "comments": [], + "url": "https://(removed)", + "geoInfo": null, + "geoInfoExif": null, + "imageViews": 0, + "license": null, + "test": [{ + "this": [1,2], + "thing": [3,4], + },{ + "this": [5,6], + "thing": [7,8], + }] +} \ No newline at end of file diff --git a/ExifTool/t/images/JVC.jpg b/ExifTool/t/images/JVC.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae86da113acbd693270cd349de59d31b9095ac41 GIT binary patch literal 1253 zcmai!O=wd=5XWcVrupb+UX8Y~g+4sI7K>%`Y#ug9)i!Et5lYb>3lXgbOb?QRf+rC? zc&WJvg3v?tEb6HgDUy?d$KnSF9t82&qnGD@b~hy0QYY-rZ)ay`-_GnN`W*d`zs}z+ zFB8qq9;YM`bx^l(C<CgY^;muhPFvmt_X<e@n%hy)ru8TAfaM+Vi1l&6fnEC@e8@3t z*<OAW(`&a7=tVt2A7I->Tb4xLUZG2i)BjrO;=+mIVqU4137SC+{<ek=lo~Inun>e^ zE-0J~3*#Zp-zl$DXD^cq@`|Vn{zvfRuOgDPgg&8}vqF^8eY@^Ei1X2@xl8xm^o})7 zkB&E{l#7fFBcDA+KgAS}Um+Z8F=+G>HstYRtYQ5TCujY0z-W&3KgrOu2c`zE5$(C< zO+Lz6f?`lA2$Wnib$t(e%<>LcJTX23b&1v5b>4o%o~SkJl(fu8OIc2X+iPauUHIRx zCFlZdf55bE_suCK)|+?5SvU12WJ6ls#);c3&w#~-@kbPjZJ79HVehndpcej?ELsN| z^L{nn&jwfGI$p7w?-cLYJgtMu_hWVhTS;rzMTKp8{%Xz(#*`w(9C&JcA_TLrkAvRP zIbWDK1#0GjJ>n$otB7ONcsXyePvhlmfm>gWa7@PPusfD<b+j~YcSD~pu2w4dyy~sh z%C(hJ`L<WCly2Veu9vE%$><wBq)a-U?adDN^$iF9kROcskg>@;%VhDKmf}Go4rjC8 zpy&7Z`=RgoVKE#X4Y6P<KXuTu=3KN*ZW8}9Nhywz;|l4Ds7`bC&DdkHiknx|V!{7U Y>xkLiV~M^}r$hl<>Czc`LqpM*zcsChHUIzs literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/JVC2.jpg b/ExifTool/t/images/JVC2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f4d2dc119d7cf69b97a929bf4dc687a10730fc7f GIT binary patch literal 947 zcmaKrOKTHR6vxlKH?KyknZBdNUb>i+mX?{ZiFS~PZK9@v*iwovQ~C-RS1nX<V<}o( z)xnh@Wa&;pD6YhXxbg#NUHJvNbm{oNbMKHqOAj2*@18lYxsUK?_=g`>cN*(Nwc0pk zh$u@J7^iN~3+P4gIoLPQ95@Zzg7z>$3|8qSmW=Fs@MXgv!DA+dgFWQ-!G4DJa-FOE zjhBfi_Xl_yjyrRc58bM)HR~H|MDxpYQcL`W=q@5v*VuZF<N3}t-<_78=ND)Et1k7@ z+g&}=Awu67{sJD|Z}%=3et>FpkfsET<GGGF(6fF=e8TV%n7!694D%;~Y&69&{K-J~ z`U_46x_%P1x(uuGSrhXQG0%cD<={)H4QQFZuH&#)drx^w?~58r#4OOu@PNvNpd4L+ z+R!ZYwB0z(htMegISQ<TKbZA$b<uY)%fiwl|90(um7@b!L(t;Pbx?a+xF|)!oN)@P z_#W(Hw)h^r0(ag6#`S*%@N>h%suUzX(b(RoY&V-v<<{nQbLC0B@mMz0t&pqrt$I29 zO?%Xr%jJ9Wg~7psXOGz4lnt3G7nPJNw<4*WC9FT6m&4K?8nR2av`dxJ#6$@Rv&Gr7 z(%Rp`uViKLYBGY261SMJSon?Z5mU#~DX9}4yr#|o#nuT=ctqy~4!{x?-K0-668`!R DAyIRZ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/JXL.jxl b/ExifTool/t/images/JXL.jxl new file mode 100644 index 0000000..26b9f41 --- /dev/null +++ b/ExifTool/t/images/JXL.jxl @@ -0,0 +1,2 @@ +ÿ +Ž<dummy jxl data> \ No newline at end of file diff --git a/ExifTool/t/images/JXL2.jxl b/ExifTool/t/images/JXL2.jxl new file mode 100755 index 0000000000000000000000000000000000000000..69aa0f333eb943d11edf3fc5b178d93929d1ab95 GIT binary patch literal 395 zcmZQzVBqnJ@KNC9YUg5LU=T?wsVvB<$WZ{Y8KAUdQc-@AYei<7bS%S<_7z*t-usxa zJyRoTMVrzgac6TWvsPx2XC+C7n<P2ry<uxIZ+8>fDe~+8icn>y2W~)P7&$-&ROIF; zNH1q}kYO(~4(|}t&WREGsafeDBDU?OCTBd;{uSviCRaY$-25wR!+xgw(F*rw84aoW zuPhtv&%5Uoa0Sk4KF*P5+JEUpy~v~Y|J!71A6?)1VR}XCjdS~ad&8@1v)3-?Et%mP z%>P<}uP0__d{&76iyabd5dwt`VXKProBm!a*s)T!Sp8X$z|D0$mzV6atXa3+#i7pp z;@XXuJN@^Z=v12gN^P=f;<_t7GX&$dz1Eu}&>UxH#+dRb?7EMr_{#j?*vBrV2RYXq zIJjkbwsW8p%R7ZAt*|2#ex92mz9OXR%)`KLU3Pbwy~cMkW@lO`9^ety^2mvrBlv3D mnglaeU_dAV1GE4Xu>ZL@So#`mQc81kD-}SYoRV0QXa@jdiJ##B literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Jpeg2000.j2c b/ExifTool/t/images/Jpeg2000.j2c new file mode 100644 index 0000000000000000000000000000000000000000..4e1113c82a4ddd153f93510e8d09ad3df3b16cce GIT binary patch literal 618 zcmezG|38pHp8*6opcu?yU|<2#APfb}?2L>M^goD!hk=QKhmn<qg@N&Z41=&jKtx0W zGWwswAjrVzotT}NQmR{KqGzOM_&<eF1SkaL`y^JT7U_ECrR7^G<m9Kv8&$ic=9DDr zx@h<YyK3r38^sx#X+ea1G?FSyQj0ZXYjqT0Y%T=_T?0J>9R&rTxvAO)M#uuj=mMtb z0_NxfmZ$=zdIsnMXttQ@nV_pMLl>|>6)=Mus%vBlWCERT26g)X2@G7|0DcJ!$;k}} QaQqe%1A;g7S^sYW0BQ%nEdT%j literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Jpeg2000.jp2 b/ExifTool/t/images/Jpeg2000.jp2 new file mode 100644 index 0000000000000000000000000000000000000000..708b8f059ba3b101c2804f1073b1ea74f44fa378 GIT binary patch literal 2028 zcmds2%WvaE7#}AoeW+PL>=p#58hIrID<$K+N-2)3$niFA6DOOvv|S-Z#+fGWwsw?E zn-p>2sUAQGi90tqf_g!F;a}i@gb-Yi_OK@od*l|rnK&iY7Ojvt;mOSRnBUBNugM^U z77uC|#~02P5JK1Y*=hHno5sMSkS2h3KwR=Vtsc;4M32tSO2D9}-Qch%I6fg2kB`09 z`PQF5%;mTAufP55m!H4+F>0E(&>UbF0T4wTEcz5533-l+PU)YOVgd_lbYn{Y{FMIX zDgCvN7Eu(iUqS!y<3;oo(C?vtcNRr20DTDk{49c&fG&-#29AG^)-Thsbk+FBo%_pq zdcAOe`ID`Mhqb~4BMD1q5Q=gCpoKINJ3qU@LEL@xiWd{~wVPI<xSGKPZ`&38Zs1d# zTD9KexZfb)?x;Gtg9sPHfD85tIXWvz<KL*xk3cbLCI&~3-bOd&!SVnN(8+@U1;~HU zE<<b8qR@<;pP)IT7P^L{E$5!oI!<Jg>CqR6mjOa|ku>`9_eXQFPv8G<zR|DX{RhLN zL#(dz@r3sJUf}2DR8o;K_1&Q5`R%;CZQo9;%IjKOH7kV;qp8=8f^AkU42%4JUhXi~ zT_a>L7$gt6w4DrkZF0~Z64-_SL>}YThJEi6c90Rsw4$WQZl&CCJM_p&cz&NbzDr?M z-&^YoWjSyiCM<ixQSqzhk0^6m4s%E<m(6Aq>Ey8AlC}7}x?xyGy->dJr)gTZt1vOI zPCRVdX2sCBg$ZRfp``3oZY`^<DN0gNl!USZoJuZ(VPEnO8>{4U{Iqx+uGZ)OCINcY zvJJ~_7LA5pH*5cgEa(N{hPLCiC@!A*&XMQhy5qMgZn)H^5a192p)FT+QF<avg<`R8 zG#c36s~LIOrhPVEfhYCCvkckfxTapM)Iwx~^(bYs_PT=0Uc19O!EqlBV^}2@(5qMK z9y^saz2o!_1IEC#C*T{*VN}*EI>38D@7`nhyJk(V77Z<xT}h=?G7*K<dUe~f>wDUE z10oReyf(#|;5JyOlBsy8cFD9huvyH@xojpSd{?ak)NjRc-=VC7U9g*}MCkZVwQ9rU z6*Ts>Zs@k)=G9#-w*uzuhT@)<UR_biUMTLEMZ2Ws-cZSy<JQirCBxh(*;)qFAuGHf zlWkQpb(~6sv9O_Pyan~lcOG+1o=i=5y6m_I<v>q7mtv9HR63JYiGYtme9hE7&}zNl zfVvC=WdXdIke=yj9<J~(9?-|`k+_Yh#~;hhZrk?u_l=>qpU^gI=}HCNf<NFnG$Xx; p;58Ai%BJ+v1z(q56>pq`A~5+TA5rv(A{2Qp)c^gkmBRIc{sr~U@k;;z literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Kodak.jpg b/ExifTool/t/images/Kodak.jpg new file mode 100644 index 0000000000000000000000000000000000000000..99db6229add2a3e611638a8eb57de881801302dc GIT binary patch literal 3375 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/KyoceraRaw.raw b/ExifTool/t/images/KyoceraRaw.raw new file mode 100644 index 0000000000000000000000000000000000000000..ea0472b442f8d8104b887bfec69cad9746f13961 GIT binary patch literal 166 zcmZQDH_$Uw&?`y}V_@)c4DodLbW!kQ00PG#S7-l7Zv_KmD-%O23qu7nLo0IwD^mjl zBcLoV5L*Co2oO6!G0+4sV1&{jc@8KWM5{sh=0KX!DM&#fEZE3G*TO*GSkDxy43n}c J&dV&Y0{|Lm5<LI_ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/LNK.lnk b/ExifTool/t/images/LNK.lnk new file mode 100755 index 0000000000000000000000000000000000000000..5c76a6e4d45f502d9b7051f193ee21756d0685ce GIT binary patch literal 876 zcmah|O=uHA6#mvm6a{HZ1Pg*eNNA7*8!FhQ2Wyj}pfof=QrUwc*`(_xyU|QEfgpQO z6c6IXTfkTlJctLulcIZ+_;YBFQcs>dsE76(znNs|O=tFd@6G$2_jdOZ04=zW6z~+q z@&|I42yI_IKN;_9nTPL=bm#gS#OAM0iN3bEy`VfbMfb=R<dA^0o=SQ-7wP=P<=o1h zL_D2O17|UY0Div@C)ZYIllzYa8EBvd6J-cE+*M2?1`AQdmBpaPaiTopvYtH02)U$v zf*2zrTYo-|lVfRKPdUYA&9FryRIp2-tRaMHR~mX|-K^><qhSfBt{W}0EF7mg9kZfw zqs3HFtTLmG0&3(TRyBDeDedWQQzx)aok_0(D#~7>B$QO3Z;4i+>;@*;iB8MEyNqB7 z^Ylw`21PVgeVaKN%;aaxGK)}oZB}j2JHva(8_GC?8jDs3b<A=;@(y9F5jCPjO&Ax5 z%S4uU3UkZOPSWD{>36w6|6xCV7Y=T+QiYH{RYrvvcMI1zZqB}!mviT8H!Z7OFAfi+ ziJdRrkJo{7Z#Nbn-92%m*E{;)`n~7hCtmL2@VD;D)Bo8&GY7xS-T~~g+Z`=~Dcq;z zuR{ppG!Kt8`E{Ma410IjSB*OP=4Pk^o^{3}U$rN58=ZqZEw+K~&y@D+&5MqRY%vn3 slTS%uw4_oZK?Fv+A#;a6hU=>TLsE+HJdd0L2I|3JASHrZswCn13oV|zb^rhX literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Lytro.lfp b/ExifTool/t/images/Lytro.lfp new file mode 100644 index 0000000000000000000000000000000000000000..cc5826b3c6fac639b8b4f957c9fa57f2bbd3af1d GIT binary patch literal 4400 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 Z2Rwc+>ic-_)cdlRj4(<GK>r<o{{uti*l7R& literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/M2TS.mts b/ExifTool/t/images/M2TS.mts new file mode 100644 index 0000000000000000000000000000000000000000..7e416d215e723a9a0bd793825bce27ce7c36c25d GIT binary patch literal 1344 zcmZQzU|?`}U=Uzn*dWNja1h92d<Y~S$iGZ_p8S9C0|o|PR(D6B`<XUaGB5(&|AFxX z4=am@i*Hy53;+K=f22PMe&Ax|015n;cD8T)$N-Xebk;R)WMMI2VYvS9tJwg0lYt@h zs=I@{07Lx;$^ZXB?&nJ8II#W%*hvhG4GinHSeXu>`v_Uhz!108U4T(xJ1_+?X#CS9 zq<*l;GcYthbuSPEx}Na?kbQv5kb!ymQ9%ag;8ajv-~@S#Q9Xr0V=coQ_6C>Q2TBYK z%nVE*BK4vJ;|mW4TTZ+0Y+VfePC%aL_Z=c24H~Z;go_S<SZoZO*Pk#jgafeytA+Tx zo`fI1f;W4vaWE+G%u4fh3Gii?U{c@^k~CBnQz<z0<^TUeP!QEKFr>CT5D+wAc*q9i zGO~j#1R^$(29T+$9j(kl>gVQxT-UYL-9S(<{^9ihpTEwH>$YE5+3b>6?9P|I;mX6` zi5Xi;cA345EVpKVrBe~Pq4tVZ;>zGjxjed-XP73>^7vTP>>v`>66<9hS{Zii&6JYI z`gdNP{AEzKIArTx&dw0?S+#a|DmU?ez0&CTI&cy1l>;&hVp^wMmT$kZRbhviyLO%_ z%iku4)@-lRV>9~i<$YM;{j*;9n*A5OcCEitCf+``r!_g{XtC|1#W$jQu55bb7m&oQ zTvGbsC!>w{>B1WU@(i5Gt?q@O@`v#+3wL89hav-Wek*@tA$PghTnPo|e~LT{ek4o` zEeMVMyz13uwUD2@<);#zTfJs2-!lK=x}V-Z{4@8}{QUg<!b1DXbyhR-=i0XY{1txT z(siGwe_r^-+gEe#zWsT6B<HW?=dava_U%xV|D|i~se67*WcdH6znP6sZ_o57<@nbZ z)>;^Oa(k@U(APH6dZYF+{wZ%4Y-Z_RyQbxwsaiOj%ft52ZC4hkZ?0B&9oVRqa?<)# KTj{c`7aIT~f{VWZ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/MIE.mie b/ExifTool/t/images/MIE.mie new file mode 100644 index 0000000000000000000000000000000000000000..f30b391c3e52787a975fc1a5310a887ecc955d29 GIT binary patch literal 2055 zcma)7-A~(A6n7wnm=7&g+iBXDPNhzTjRiv~gJ`M>0YYd4SsW&{Xo@cJP3+e8jqGb6 zeYm2f+f=FQL-(?WJ?w3o)~RA$wRZiOw!S;zFWB?`h)p}!5TFm0YRUHT@%f#P-?{f( zk&OSR<#V$NUILE9wNj%_7Yegk{OHs^r=CBO8bMhdStJfM0>^2juGDL&=^4%E^0Ssh zYIOKYoz_G$uH-2vBH5*6(AFbjIvLL}n|USb)bX1K9#xd%1u>t9Wthu7;`5kT=}XLR z)R~VzGt8;WMDU6U=<90h^UR=m;s(SP`hj)Ha?qU;kF1kNlsTLD=BwTH#<GEB(8a)6 zrJg1bB|z<)W0k-^@DI_qnCk4v=}U?sYr05XU+CS5xm_gUy7BH1dT%06Hm0z!-MaP? zk!s65q<$yLFA5h&#8o=CypWsAUCbkgZ^+2@oaNGjwJ|!TX_pyuM6y>|@+|6d!Y$^C zl|-V*c4hQ(`M{mpyR`07Pd27tS~koRZ;dv@d{0c5Tt{fhV~<Wfi`aXWpB>k<k<l|_ zXT`i4(@o;lUF!Q0-@3^dx7^wR9}!K-gffFPIhc`v?$g*MDdwO;T%d$Q4T<-GxCU4p z3`TwTGzT;AI5;#V2K(RILwsCu%a`X@k8b{3dVjIkf5Nvm`xg6`PTl!wN!L0A^bfWT z4O*U=%~edeN?aGR=pQgg>J9-jT?&rn!8$eJS^zw7Q(~ZhrH!O^Ia>_BYi@>rG_N=B zK=>@&hUQn{z2+@wZlQX+xfMPQe~$FwgXWjvcK9TG9NvTG*Ueka>*4+8jXlt2crSb; zVK-4J{oM?=Q<&vP`?1_|X6p557y@ca?D!#Ub~*<>X$-;Zxd3Jxu*w?J%|21oEidac zfk96~K5#20HgB&T29sS_4SeiE11ct~`;w>+_7m{Svw9(*27?O2-3n`#;aO}5T!w7} zlT;`e<i$3Vs55O;)ho{x7bw(eumKKnYX~#~Sfidthv3Bz1Y~<CR0%GHI=0QgPh!he zV77gjRynt*rqp@0Ljm=KdQOe0Z=>|4dJdlxs5uo$lj^h@Q-)BNQm54S)GSJ>IuY4O z`Gk5wP2l@O^}KpALJX*rC`+%C`0tc@UU`Wui<fhy0`q;W5L35^M*+9Z8s#1XiiNwl zvM_a-akOwp+bLfm<%L4yPmO8!w12e6+TCye)*9L^?Y1VgOInBa1xgJ6-!1&68QN#4 zUD3Y5B|XreX}j=(a&ftJA6ida&sz_6wxIQJ=laf#o!cN^t%t4OTTfcghXDURYdx1{ z&sq;*_wmclCK~+OdbD!`yP@KKsy95VX7ay&gnbzf&j9E-mho_J(iyx*al@yzv+0pZ z7@0_)8BdRm2rY5+0&T1_&+unAtZIodTPabWD|i+NMB@mss*OGLSXvuRkB);j8KK8< zrX0fwRkK`TzbXLqq>HplJ?d7dZ*pEw4-c=eucs=^#tCeZ6doqSSE=V%d^q6MiImA5 z8wU310-t-hmi<8iiDNpe@^x(1l)+XhmdQBtaXrH)DGg`n(UQe&DszWip_GBTRgnW( zK<A0&;zb$R$Iuju<XB{id9~r)sn)4vDH9M+WGvpeAfs`dw$R;`VF4aajSO*#K`=t6 zj?<Se;q;SJ98a2@JoJ#I-P294<6iNL*=f`q!TjsY#qsE0%B*HQF{Q>MYu&m=6+<Zq z{f%Ui`bdJD?E}LVXAqsP$)VnU??zFTl8HpQ#Ij#@^1ZSll=!;_;pEfkJO<u21WP7{ e+m@4akr$b-5<h;()(=*zmVIE;r6`H+Wc+_B3Z#Gl literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/MIFF.miff b/ExifTool/t/images/MIFF.miff new file mode 100644 index 0000000000000000000000000000000000000000..ddfd1f39155b17eda009500c65c102cf15295491 GIT binary patch literal 3750 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 F=WlBmflmMc literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/MOI.moi b/ExifTool/t/images/MOI.moi new file mode 100644 index 0000000000000000000000000000000000000000..6c28db1fcca5b7b334409803fa8cae779a7118b2 GIT binary patch literal 320 zcmWGDV_;x(V86}EFKD&9fq_B(0Rtlg{QyfK$hw2vG_WyZ8qiSg-Y}5SNNh$1K`fR* y{A`iPr<BO6n8>4$$fJ_TtD49slgKNb$Rm@;4P?lG7`$?cJaUP=vWYzMU<LqQOA?;| literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/MP3.mp3 b/ExifTool/t/images/MP3.mp3 new file mode 100755 index 0000000000000000000000000000000000000000..9c0d91b122ac27b3c4b859d00bedffbb1147973c GIT binary patch literal 395 zcmZ{fF;BxV6od~DAW#u7z{KE<snDikLr7LqrG-|Fss|EdTA9XDTt~J8WZ*yWe;L3o zjO3<gz4P7o#>rqCURxhDe*kvTzrF!w{CA8nfAZtu&dHTm@I6ilID=TPkm&APUNKvU z?jrZ^Y#aj@xX)57wNhg3aGTUhHLQ|LR~vCh6amK=zRMTVx@07*^JRVD2w5RZHp<Pb zXh884=zz0Wt4dp8Kzst6Z%mh#rAFk=|79cTEQowS2UJMQRz}wq=y?e2A?o$~E%6LE zK&X-)GM@S9(+&T$3HgE3;`{5fB%YFVr3h#KD{h?hz%AnCqE;<C1eU>HTZ?D^2bDNZ AD*ylh literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/MRC.mrc b/ExifTool/t/images/MRC.mrc new file mode 100644 index 0000000000000000000000000000000000000000..1795ee2fb7a8d47f91db139fbef6504058a08f9c GIT binary patch literal 2560 zcmZP&U|?VXVkRKP2E-@;NpADgJU0-A$Zv6CV1Qsopa4iOGf3jV7Z-*P8(bAkTwOt2 zWCPt?Jq_3RfkkOb_&NqCxVV6<XxL9vi$)C}4T0eh0^p3x!0;akHa-#W-qiinA;i(g zKUmkr)X>r#>_amXLrY|@hUDkwrxzs_WK=>#P4o;6^bGXOOpz63aX8M{`gDy9kFiU5 zo?(RsL%;!;v_-x31an#O{TB}2(OK2_cb`&iN%OZZCVQ;_-9jye*C_e+(6XPlAblWO zYT1S4S)McY>K;|);#&3-PXB<2gVn+80MX%31PY6_cJ71GAy7W7%<}L>R?WZ=?1;(- zse^!s+Ya&{{`0`N>_EnhyMYR;L+;tbXqX&T=mYWF5<tP=pk3c&&!vBGCBMpLdysQL z*vVJdEiorKKM&n1XmO1$4COmC)ZvhU7>iSqTnSjnz-V&Kp{+io^j{>%Hi4SyzcBTT z=e2NSml-{x?iCFF2Vfp4SXQ~~GBEu&91pwwo`&iFOnKhrNmGySgV94M{r9Ogfzp42 OmvQW9`iG?(TF?OKDdp_| literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/MWG.jpg b/ExifTool/t/images/MWG.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2b2ce4bf8de229b63b8057ea5c6085274a15c1bf GIT binary patch literal 2255 zcmbVOTW=dh6h6C7TPq-l(?DuisVk7YsAlg<T5oMfjomg@afy;hjSv!ScGvb2d)L@& z+0HY9H<adq=l%yCd4LDRZ{YIOAAtA?+cPukYeTt=XT5W7-#KUI%(3HN<LBJo{gK}Q zsMp~Z0Hh(!y#YL~Ht`9#bx7fxgRA)U4qTyYfUmiCr|WOI4-y@;;_gyg74^R3uP1yC z;2R%S65C(!4F|P{dq?v9<Jtqc>h+zT-;Mk*AQJc-AGBa^s;7qygXva!o*jj~yCUTq z%Y3v-p95UO_ljm1+Ifmbvw?sgKIIsn<5O^PPWdakX~M<RWUcFPagnSI1D;KAbWMB4 zu}tC^k#QKBrdxE1_^p!(@t@rBek+W^ek<&XrlP@}-CBKxWR1Hb;NI}S3;b<J>xF`$ z>6$L^MR-j!bR2o4HGwZf8lesH5%?V<BqNVB3bU~J<ly7S59w~y_Kzy^BX2wmdv1Rw zqdT%B+lRN8?2=s+_=#v{HWhsWZ&E9nqE-B8EbyPgb#`u)_fhEB<j@5EGgzDBA4YcM z3A_jEbIeLO2%;WhM6fZB8+OM%zuAfeew2jC9L^U-{QQd2ilVNiszG0|-SEUyoUo&g z?5?USnhM38QP*}(y+}Oqnto7{{`%oZN%Y;4^kl29)w>n1<?o&Kyu-7{N6y))Q*fo7 z@@jEpjXK?q7un*d(+>L9s3cKv3)h6LN+MN>PD@g)V>dm^B3I=o4BO&urEo_=jGo)D z4yq3l1f)t*66$a`REDM!_L?dlctO<+)i7k#ko)5xvPW{ze;*N6MT#+(uShWMlW-7~ zBxzMlpj~H*+#U4V6xVfBukCfbAnM~r=qkdAV$pRhhg}mwGWbL9OG+@R?U!d2aa`Y* zlT$CM#5GEZ-J&Y3kiS!9c8QOox)=b7$DLjASxooZ(SjqAsMWTEW=R^!uGg>!?MPyQ z%Ea^B7A*wIhy;y#UvftxphedIug+Nk)3hgr`VX#Weu&rp;N%O>VQJE1-nE+xIhUyi zdZUS#$%S5UGCc^YJ3WX?b(vw}h3>^H1SXr?#eZLX-AP}Y*5JZLoQmmnWK}rjS)_Nc zJn1nKvZyYJN!`v^Rcs)%Bt5r`cgK`99p{l|vR12@v|(DBHjguJgqMnz^8ZQ}o5JO3 zPWlZ?5Y?=D==zN@@m$6hjd7Am;xx%rOw-IGLE>D}CixQ90d~-!SvHwMLR}g?O9)#H zHXU`^xsaIlJ}<zAH^j~s{{dgY#@bqTJu7W)N=EKx&QNkVl=2oC<?1~;s;?T{TiL8A zh`Ad#a@#pEw_V-N=eN;dcWd`mW;0p*6J+qu1f)~kd%$P7RECRxhDX4W-}d=&^(79h aE$cw$B~JW1yq@9^IhBDO_#SS?zr6scR#*@K literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/MXF.mxf b/ExifTool/t/images/MXF.mxf new file mode 100644 index 0000000000000000000000000000000000000000..e848a8fe79b5f7ff6505d0c10f709044624a2b8d GIT binary patch literal 7510 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 Hc)R}qra<9* literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/MacOS.macos b/ExifTool/t/images/MacOS.macos new file mode 100755 index 0000000000000000000000000000000000000000..9bd0f42ae611360a10768b29d5cff8c02f508d73 GIT binary patch literal 4096 zcmeHKPe>F|82@%R$wXySDo|SnbHgHb*A=Z?qeKxTCEZ<7FKusk-tJC2GjE)Eo9VHm z9fDGVpde7_Vp!0rP92g!5d!HV9U?DXih{7Bw(qrnEHS)xn2(wF=bP{Md*Ana@9+l7 z4(<gfzyL`|7b4QAR50O#efS0esu!`xdG%WWhX==bt0)e(_g&FwICSS_u7KD?)cm%X za`d4e4&akunvWQUN`2#5ViKJzI>k@UDs0>Ubz9$6V)3j+Wtnh#WH6HFbRNdO4dB>T zoJKjp7}Aj$=;`BB>tPeR%7{$m9z+W6FgA$y{u?urrY7w*nP!Q2k^dOL$(`f-Nt~)N z#+10x9#^v&E1MYSaymmMjHzr!nM5#h{KTdPz{AXxk6u-hoH5l`N;OI?9wjNTcmTkK zbwejW^UmvuntByore-(OG^%sK@9M{92%u>POspWputiN=#Ddm9;8ZZwmLzhJ1ZgNj zlEhR(zT*oqLACy4?)T=#4OPK>0{0!QUC$pbzCQHWRbJS2heHISiF5L&)oy`e?xr;7 zhSlNsm+Hmh{Txg6TSOCEe%VOvO4;(Ma@o0m=-J@Sv$=C$PhUB|@^QLxwzBSxcWrI@ zdtmX==h=Dh(u{ZheYJ0OGxDzU>5F@}o-}+b+XyW*u1qai*N$GC@qV~|clpMvjWg>T zmu~mfbbo0cd=Tbo#ggzLlE_KoBvs5l=5}{Ud5t9$REUgcR#>U>q5^q0Kz$KA=Wx3A z42P*)(LU0hwK&sSM#reCvWbeef+JX7Ra@VbN0tc7icE$6#J0ITwH2@xuobWsuobWs zuobWsuobWs*j)i}Q7>KAJyCJzH>t(4W`asdW@aQs=Xk$Ym`+r6Qc`J>OL3Lx8T=RC So&Eoh_`Cj6z762X<$eLKiUZ~V literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Matroska.mkv b/ExifTool/t/images/Matroska.mkv new file mode 100644 index 0000000000000000000000000000000000000000..1fcf6432f9a45ff522962de269287032234df389 GIT binary patch literal 507 zcmb1gy}x*}Q&UH7Vo6bcadx6pdn2P$Ya^pXa6>vH0|Nu2z(0LK-`OPxe0TQ*uWk|b zT$vWUrb&Pq!W9cymkr{s@ZH@9lm#gaUem%T@UsmfAQQYBC=V3){uQLtVZPS&jm`W{ z4!#YGb25`slX7zu$_(_(^(+*$6@UUTM}Pz^^bErtPvvHp<)#*;rz(^g>6z#mC}^m= zmF6bpWagzSgyfee7Nry`xF#2uBo<eyYbqp_X6BSA<mV~4q$VpU7%LbV7+5G6npqhc zS(zBRbhda1DloVtb};{rDOb$D!BD(pLRd}OyCS>IzlA*nO?+1_o(S@f!}E0x6RtNh z79VX{n5oI%+{n1Ik+GwZF?3ZU!<<Hj+~<vq%7*?e_6-aSq3;?Q+WN!deFI$GP4qp3 z{q-HgoL4n6CNEman8G0U{~?29I*?vt@`UMt=RyVsW(Hu8GBX(Qi|Gn6GO%X6@6_p5 ze%#!`loZ9Fv~^B%X<o{MsT-P@4(w`Ta+uY~5VE0(nIQzkS$FArBNNa?4<)R>G&eFK pyND6!A_f!}bvlAwr0-)4bPLFQ#ym5a`42m{wzyk2FwAdc0sw+4x{?3@ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Minolta.jpg b/ExifTool/t/images/Minolta.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1dbc6f32c98926e6482e1c58f0546bc152746f61 GIT binary patch literal 13501 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Minolta.mrw b/ExifTool/t/images/Minolta.mrw new file mode 100644 index 0000000000000000000000000000000000000000..0f554f8083d0439e534294b5e40d0216ae6249a8 GIT binary patch literal 2596 zcmd^BO>7%Q6#iy+{Tny6-H=8hB%2^LP@>3Ql3+z4jd4oqI&RY>P>?ueX<cfG{3vxB zRf>=m{=&^z91s#x52nWemrC4v;1ET<wF);52q9E)03?1?Xw7@GJDU{>Js@tpXm-B& z=FOY8-@F|M#n~bdjbUbX5<rLbjFlowM?Q>gM{2Q~vDnK*LtuXXED(OHSj15phtVPc zL(U*}QwW74HK9Y7JUNI~XoS0|)x|-Hy9j?K;u1#*-x5ime_HZw#P15r!*|~;tN5bw z<tGTy{1s{ssG)HBR6dtAi}_Qhr{}ZgWPU0?pPe>y+2ZlpEDFm@<#V=KEH5p;Ua`%b zy;xec2h9A^xj{^pi`l8;X4cw6=8L7(!SrC-GC?{Fji%G1>5OSTJ(?aG9nN58wY*fx z7m>1tQ?zw}bZMtQjb0V<Ed~Wq^xl$S@;|Z1Ha?=g_!OY+ZhY+FuQ&VlwA<`=@dQwV z)W}F#R=TDu9kuUjhKKLfb+G;vQ*W&s9&WD(RD8X=9&%%nV?FG{5f{gak9z(`>TQyz zy3hyf>zqR%NO+|Fj?9NL3y)nB?poTpp)kd7Xg*%m5c7tLA6{<+Tpy~UM$pCV<IA(* z%d?^KWS^+dKjvbtRLA(lSYxiw=zeFvA4k4ii>xCX^<sef^F$A^w7~^qTTF;5V#Kni zQJkJn(cXjw?WEsAT;c>xeUA=_Nz=eRm<orYm~gBAmuIWkR9`~45AP|APD<bX8Y!~( zH6kK3AMc%VxRY{G=4eor!#zEmS$>-wwH{|EpLC*JHPXiDNnd!#cE<?aX-VR6>nM%= zYsg<5p?1SrD(=%ftL;{`T+MNL%sTmvIb|b2aDiTRn+WB{xv0Dut1<T~cfL{Bm}67D zGjfxnlqZNF{ee%(XbZY!nm_WGjMwmOrh3L?#^coJU1j5&?qSAtaKP6d;>&TR`E&jK zZwub1eUDOWCkei#p6+tPrgqu&<GolX*3i{}=cpxZ1Ig-LBsX`EsQiJ%3n!3x;{(Lk zuOnXid2G$TWLm=`sg(KZ)k^8oIQloVGvX^ZuGf9O5;f5FE7zW5d;#jBhG0C#x1j!{ zet{;s#M{J=WBc$5g2#S3b{l9Fh+v>eCKrbAgogv@_ppXO!t$mkziy8Q9`&$5rw?~{ z7<k0PIt~$b;%G=VAGlaY!t*iE>S28kQ-%kcO{qwRej8muwzjseMVg0@Kns=MpOHA5 zqk(!lCl{w*OJJc#<e9%Xcj3%J;iX)zM+`7`kxecTY!hr<CKKkth+E@A)3EkDQ31R{ z&nU-}vEL-%*zyU5{-LKY$1x`<#``02gO2dNoEyOqM*2*Wy$bYZdNaIAnbc#*vNEbb zoQdez<mJW1tLDmT=}Nh@W|kN2*GlGjyJC-L=W=<efeP{OdV#agP0`YrMkceRd@-A6 zfyOxRDxQL9*S-<IiIO%fPKqzIUadvUF~&3s^#4eqd8fIHs%$&WADTNdm6`0MUlqyy O4<GIcuG(wPx%~w#&k+s) literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Motorola.jpg b/ExifTool/t/images/Motorola.jpg new file mode 100644 index 0000000000000000000000000000000000000000..be03ece5c5d9fcb48b15be03a6842f2a266ba6b9 GIT binary patch literal 2489 zcmaKuO>7%Q6oB9Cu9Iz=5<5*vLaWBDgo7xtcGo{mEEMwNCMj)xXtE_2XiU5|rb%o$ zc0~{eR8YwY8H+<y31lD=6$ez63x7hP6oHUHh#n|Eha&NNiF)Et3Cx?n^+KzTyt|*@ zzW3(Mo0&ITzgGWQ|LDlmxeUPMB-{@GI<$~BP>^=ub&mKMHzQZbJ>bJ9!E1x;b-70F z1C`0qikM74ivKSVi*X0?Ps(nggVrzRKx6s<H`ec!tfQWun9P^*#r#qVV1CYw9gd+M zz=Y6d&&8!w@${*pQ!20al~<i&Fcvn$X7KpTgcS*!X1ver58|)rEIFxFr|-10whESE z#uCOrg1#~nRwNNQ4A67-yc~n_)Y(^EF0LPJjAy-9u!RW#pn1>?%Q7me9Gmh?MRWP3 zmkHM)iV9T%Ej)7NjjH1Mf4i!>@=I0C<qxVpSLbrIxsk8x4#9qaYKyD$W%XIwmv1kC z@@-AVW*SC~G;C{%%x}l_nku>NLd_?+`L8#5%!E$trnIH32tSln4{I`p=IzZUk6ib# zj2WG7wg@@xfVT*cb2U(R4bkWAX6JS?O#LXDr=?vT8~&GGF>>w?MOUfb<n$a6Y>=`s zMJU;Y9?si@@lNEoY=v2q1$=3%ocANoUFS?)+kOu}$94sZ6B~9#a@nwJl6^J%qGYY# z-jWRa?QdAWxT~~Fb2k9{2Qu>h&6YHeO`9GY13MsRMSU&Jcb+;mjYBcGKH5Nf+QSY1 zO8OIN3nYEn!=Jhg=_@iGWjy_vl&clTkaDu^n37?qW4R1Y#KTd+zIQ%%Vv;YOEb!T2 zGg2T}OYr|c2LK8R=f9sv7Ar~p7+K>hL%WIWYpfo-UB-s_4{}>N3%0>>xEpi$p*_Us z<rH>e&v3?h277_&V5QQ&whJy_bwq45xXiF;PJie9ezAqNe{hZtA^*zxy&CdwoRbS^ z>ltH)H2h@5T4Mf1Y=A8z#>n__7Ede2b}iH7#`a65o%<(Wp}we;s)^je<&5`mR@zWL zBJ{sQ9u+!Rp)yI2{t=;n7s{L6zW&VIE#$ADyi@2$kh=s^Ki!<k8RRjc|2p!x(1{{X zc=)JCe~RaXmcGn1m%|Y9jE5ib@Cgsk38uEThfjL=F;C8C*|^l<;{aK{j{=z6?JVEp zbg$L33fGC?xU#%ztFySLvb<`m<G5(DKBj{q`}8cI7qs=YXL;V#_yFpN>PFW?R#Z2- z9<rjkF?nla-8#WWoX!DP9>$@8_ppMgd1E*-YZ!Rgt(DJ2En~F+i}{sOZl#>ZGp>+Z zER~B6q{^i{zl{u!9(&;E<h&IzAZ8c_MCc7K)&`8|qP3W^U?6IkQ3FppBl2*0g%(tB z-_anj@Zd#PEv=2(iGhBaK|E3Lgu_z=pFE><L2C-tX=j?Kp<^xq9vz?U>&KM<O+=m! zO}IJSuLOO)@s*%gyk7|lk@?U!(6js+*hjr$`_Fm*V6wctycR4ySzdl(C6!wW=9W`g zCzwu^QiJs$;RWdM`@7q_dpkROtx!+M3Wu<T2V=AiCWl!|-l>s2-QB@`!BAIMC>{!i z;>q}-Lvb`1iH+RJtnseC1pyt4l&+G4pah5-AoVM749^}u9QWRIiv;}t*MYz-lKKzO Ss^TmK)BrpL??O-g`+osDO(fv} literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Nikon.jpg b/ExifTool/t/images/Nikon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b55f35f9c9d948f20162bab509f769998504b204 GIT binary patch literal 1703 zcmc(g&rcIk5Xa~3_7^Rb7Ep@Tup%i$l<XE;pfM;=nlwO*sDzUdf=Z1LV}PJhLx>^= z6QS{<Q9~l}z{#j555`3P2>fv|UQGNObp6im+d=~I>NIcXv-4(WX5YMhHMh*~?v1|r z!eydt_9*#?C_q7>;7U})s04eUUtom5Zs>K4Dxs2q=5au{ExiFgDESSzS^6ko1JC-Q z-($qVHPD+F)ruX5eF_g;R-7$xGZJYfA<AX@^Ev8kZ%+^%LsHJ`XtvH7t!=Xu*L5SM zccgSfGvcX4dn(>W5qi34*Y^hxJ(ui3!iMGT+)a`v5SLcG<bu-UhOVyIwFcqulH33m ztG16o{mQBxKgm8tzg1fo*j=>k8L+o#=i>GAN#;~#JJ>J%bMU`i^b*G#!B_&{f$u{u zYE9N==(<?j?t!vquia@hOO^Z@{%*;BPW;kq)usZ4WL`%W?#&3=e?jtIE4i1=ip2pS z9OH=8BT`o(qg^qlRtT$w85SnqStk&}1@?kdU^ln|+ynMV`w-YGc@*pe^Ip9O_TveD zi%D>$^tldR00&{82QPv-<`Ve6^nVOqlKc$3?9>UrNAM3pufraK{TBEgn1Wtwg<822 zkly@2ewZ@ZT%V>9b)P?%cebo^2c0?I%d>KZ78;0tB9`rqot+;Z>>hAt^O=kj0?#-y z0pL-ZvVlY=Er>LGnY;v~T#c|I#lTwVMByLM_>?OHg6hftjdDEedE)-$DynnUztvxS zAB;q~btMT@;`^hDMiS!&B|}sjjU0|PnrFjjLZfjKhsb5p(2c_MOm>iT>_fuZ3fhmK zVs%2b)`<Jbonk4ou6B|Dy~16&W#OJ6KYd_@lLHrFy$Ij`H{rKXuqTOzJ3BzzUmTa@ zAyuAhynCIqZw|#fXWw1$&a+P__P4nNyCONtp&-r~=IE2@*{P|U+RWta)cEvR;hI*M z8oP2yn;4rJ>oUL6J*o+X>Z<CRYHOR~vHDoNH3n(zO0aaLyDdroOT@mqIxVWjB9T}! zrp1!!WJ^mD5A-B@{`J`QocWQ$KJ?Y6iUv}`LJbRZllsv)`;AIh&n^r8K}xd{!n-Wy T7b;iLfUp{-Q}mYV&Ch=Uf8?a* literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Nikon.nef b/ExifTool/t/images/Nikon.nef new file mode 100644 index 0000000000000000000000000000000000000000..1cf06614c811d393511ac56adda6cb11d60d5088 GIT binary patch literal 6188 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 ZcNW^s&u}`SE$n`halm|=b^FcL{{Ss@#0dZZ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/NikonD2Hs.jpg b/ExifTool/t/images/NikonD2Hs.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b46e75405e27eb7d4814939ccadf513f1ed06aa8 GIT binary patch literal 3505 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 QzKM7uGzoo+MrynN4FJf%<^TWy literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/NikonD70.jpg b/ExifTool/t/images/NikonD70.jpg new file mode 100644 index 0000000000000000000000000000000000000000..52e471b1a21cb76495b3ddaf665c2a0eb1ba0690 GIT binary patch literal 3661 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 IW47!60md($$N&HU literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/OOXML.docx b/ExifTool/t/images/OOXML.docx new file mode 100644 index 0000000000000000000000000000000000000000..715638f5d14d3118c78119398f3dffd9abd8eede GIT binary patch literal 7521 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Olympus.dss b/ExifTool/t/images/Olympus.dss new file mode 100644 index 0000000000000000000000000000000000000000..35c6c77847ce9dd342fe66d3b2c567bf0a8db932 GIT binary patch literal 80 zcmZQdDK2JYU}RuoU|?_wHZnFaP=EuF*uVe({(t{(U}|V+Xl7_^YGh)BV4DI}8yFZG J{Ad3U2LSuS8oU4i literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Olympus.jpg b/ExifTool/t/images/Olympus.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c7e642af98d8102384202771bc0b6b35c8f46eac GIT binary patch literal 1573 zcmc(f&ref95Xa~3>n{pQix8+1c_F5NVDnm`X{*#gi&%w}lzI>%rb^K!w2;~oNzfQ$ zl!FK8(Zqy=#6JN40T0FlH%&|wFY4Kg2}jfCci-D*i8NfDWZ!(cGqXE8J6-Lw_TAb$ z_8_-Flt`Q<2N5;Wafy)vs$evOJD_(kTwpu&E=G$alLXDjNy)0~kKj{+_rSfvhk^Zw zZGirP5dgPB|HL@KjCHtA?zu%@hkssMh>{aGCa11XEAhm5B0Vyp#6~8^QX`5ff9=Vs zbRx#>N#DRkI!>{Gs;V<YSFk3}&(tg7A^-BZkcw==;c&zsh^RruAB>z2MFv%z^5qlb z99|^zO0W;yy<W?)NANt>r42hFO;Q7XRo*bp6okJ;@Kvz1Y4}JuC)+gEpW{9ir=Hsa z*jh2>3t)T2*vpD_9fJ8*4TAZ3oWg$v{%aLGy};xn@H6m3Zs$pDiQN3EDs9#7iftJ+ zehhzE@HfO;1+VkiZM&YE43q@(eYrR>2JnO5ALn|NtT)Hw53X@q#9tO#ziW6j5-|($ zon83ws+o~YFia#LU<)&<*9m{C;4@$wbC3<b17JI2#48#CJHUhBD7b;yNdXtZ)NCQ1 z6$)Kf_>DKg#&w^zR-LPkYrEC=)#()4ie{H{cS_}AHc?nux~ZhH_m>vSrQA|s*ssjS zGWl#VqZp?2>T-72h^gP-bvM^bxB3e<WT5LJ*B5pukGVMdX~wyYdhJX6FKkNLo#E)z zqs>WEAKUCB=dJk+`_$6v`p`z&XmUExG^LsL|D_pjoaz6oR5%2x^)b)LMzVNS_}!W> z&px`=e0hF@557DS`iU37f(BrMJNZ%&^JOsg#dtZNUsX!?%K6)cOm0!h<uiA)%6z7j ziE3ZzDYd#>o)%9}dwWm7+vN@Tybxb>h)Xm+qD%ZJk=i^SrBm^Cba;ba#T$$V`}>0k z7#$itDy(MH-jdsaciJIK-Nf9I?3T25bd98c@7Q)|2Jg3V>e|<RXhz#Zlj8#1vYRf^ J8|u=&`~fx;wqO7N literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Olympus2.jpg b/ExifTool/t/images/Olympus2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f817dca4416ece89507955c512cb025d379a5630 GIT binary patch literal 7701 zcmeI1e`p*<6vyA}?%m$qZj!r8+FrG_Yt^f$#J#IY(@@&Prqs~J#{Lil6(gw{u&ro| z6%~Tk-#@59EQm_PY72^p7JplcMyXoRe=J%>6n`LEL`0=l>H2-MZ|_FiYW+w5xk+~B zvv1$LnR)Z;b~gGj`h0q6(Z-R>i2D0aqYM%G)T}HDK#$;R0hf^O#FYmZknY7*R5mH3 zd>yA!I{gNGyzmd;Q$@!DSH$K4a24E!w1mv9R&0lLMs9Pooq0CV;}b6BAv^;*4@@Ik zw)BF5!Bs1QzW&AiD|?p)3ws9^E$<DIP<!4#(7U+*+{MAdWy=TKgMc>7=?u?Ufk-ro zx?vIjwe<YeS6?%-Zf#I$PjnKaI;CnD&gl-T-IdOu($O8x?XJwG!K+8suJ0e9aCR6H zodSDp__MTKo4D;RF4F!a%4*K(*rP5K?rUz+aWf4<lBY`$2ebn*!EcCEgxFFyBevBj zVn=gI$7vnAYIJlI{lZUCJc6s^jE=K9&gs~*4#0Ag=D^y^rP#U@TbE+%Qgocwao!$f za}Gyn0lGaui^yw<(=DkueFg)D<qAE->BX?m>0QXf>ANU`)31(?Q*G=RA}RJ5G>=^+ zl|rSdr)P9@*RBYSKAiqp<7lPLpa*Y?=fLx*`oqFI!832KkMCK+gKTEAtDPHGs}*~5 zJRjbIz9YO3tnQ0-6y}ICmeZr_d&pB`aaq5B-Lcp{w=E;g=Ci_ia8C3OLH~v^m-bI4 zN+ZLq;2ZJ$NAwPjOMNO~sK#TPJV>nZcuw+=wG+mGP6=;B&(mYBjC|C2Yn&HSvV`?u zGvv<Jxc*lmZ`zQ%24@V;8k{qj%V66FagF2R8Fr-EdL-GeQ53QM5DW|>eZ-AZS!b{s z+SH6DIK;DLdm1IlO2zk<FpzA69V>3xtI$sgPk_@_hLF%6M6P9N{t%qCTnZrn6r2y5 zbZcJ5eIT4c?Ly(5m|H!<pMVF2QAHite!UM-U0sTgb}3nVi(pH*3btjfUI}(o+|P;! z48qB{OlvX<AXTE-cOZ%^Va`F~2VP<lUum((d4%$s!#&198EhGB8|>H)^+0z!YDkWe z*NJDtQ&@j2Lry6}$6za!iI+#1vBBJjJCWBhbW$RJ3-Yw^9<XcZWDU+4>>2DE+=MlU z+xrRZG#m03vHvyXl(uz!_Jb9e{l~!ub6&11Wn$L8%NlLWX0`tBYzKk!)_cv9{Z~Ii z#QS*iv{K<+hV@I<t!jp;iT>gptBoV+i!v}Tj4eRdnTOqJtdsGyeh*loo;-kt!B!@2 z`zF@O*W0BGwhXqx+&*rKBV0hgr$om^-P2%x{se5gU>;M?gEOMf`Lkg5+3S$!gm;7U zqJu|NFt{kJeT!$ExDqd6cgM_SFa3&|DB)FTx+Ppdb!;%_&UvUgcm?=dl$e!~&pT{R z7)YMMO_;t($<1JH(L-36T7;hg=MDV=<m`dnkQc%4qjKMXliyKnzs=B}3>~(=4&63I z<N*XF;cc8jdX|5WB(t&?I!Bt7J&-%1e=Rs=$GzNxJ;fC{o6H#OgV~21TI|?Yn~^se zQ7kVQ`bE)SN_X0=qTdevHqk+IX_Ck}?_@B`E=3)t8S?4E3y}8&VQ$xn2G2C~Pcryq z;kD4GqA}yZNn0#KZWrSf>3-C~G32RY?4NPS(}vtNIAbtPcBunJJ_~&gyc_T?z^gy6 z`smKXf?gpY9hU1C=rNn2@2uI?uG+8Uqy37m=LqpTAkMRJZrBlqVMj-B#6V$XPNlPg zJ+@M*b|L<Y7EC7fN%od{DMu$R-+anG*E@(QY;bMuNL+C!VowppX>u1o<)OpFnL`vM z#CP@a&3Ccy(VrFG8;CAc??ii}-O&^3-sn(tL$pbisf8BEALpotYxms#$_cmK^3vF$ z@{40HJo`%OgSMAXx#G<~ab5f0QicjH?*pspO}QrMGMb=!urkEKY}E_;CYErUcn#;Z zo7e1|*X)yjo_CdZkN2eahPTiA&Lh9oAMx(+wtBm~59Ge=U++EW?eN~y_a(pVclq0( z|2A^`3>$pk?{9(R2QP>G)BV~0B7c?tC?ubGzj@RBdHw+IFY=%C_IO{RjFNvQG*<f? z{T(QUbDgMdT;PxRuW75T+UkJM65cx+q2Yms2O1t|c%b2dh6frRXn3IEfrbbEe-H4P zN}#q;6Y;4+kK*%?%oizr7E!MqpL7uMSw_3!QwqFy95pOZY>6)-eezJ#)FQbv8E<s_ zqCAYZzKv>$dfL9zxtqplrE0VI{D#wIYLazOzeoHzW6kzo-q`<}+uCUbe>Jo8w~*y? zyFkX-8)}%wDRumI%IDk3m&Pyg<skXexF2kOX;gCJ^%LZlFi?kc5VI4KbY9<vHEXU5 z)?cw<&82IHMy?D-)(l;~Iv5^WKQur3m^M*cK0mcMb=H(Av#RA8<!XBwp?!V_hxvWI zI`sWrP?M%k4W<R<QmNco4$7T<ou{APi413Voc(v%>d&HG{JM%iTg@b^pzMN*CTIyx l;o}z7;`Ct>d>5=e6(}4giT06iqXP<d0q^tA(TwP$KLGcqoRI(k literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/OlympusE1.jpg b/ExifTool/t/images/OlympusE1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b48f233efda6d9f6f67d0a83f762a4927953a05b GIT binary patch literal 7365 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 D>Z(jq literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/OpenDoc.ods b/ExifTool/t/images/OpenDoc.ods new file mode 100644 index 0000000000000000000000000000000000000000..e3a0ad2b91a898aaf6e8fbae9ae4897afa16ba63 GIT binary patch literal 7037 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/OpenEXR.exr b/ExifTool/t/images/OpenEXR.exr new file mode 100644 index 0000000000000000000000000000000000000000..52b33a385b08715cdf900e11c63163cc656628cc GIT binary patch literal 395 zcmaiu!3x4K42Cn=!J`i%s5=WXJ%}e^9(Jy6#sak+Ynk(=Z*H?8>V}xmBz*b%x6NV| zO#nca!Wh&p44RyWLtrUHp>lBNvmKHBh-4!I(q5_>og-_$X10u#@FFLp>=n+oO$cUz zzJkBTDn+i+;`Udk$>6b8Si^hb`%qDfI&~FFp9xPE3T=gln0IjBI9Vf_-hPM12ZJUl eVA`$w@+rwl`Ep<Zr|zL%yyUj#e9VXWi$4MG<4^7Y literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Opus.opus b/ExifTool/t/images/Opus.opus new file mode 100644 index 0000000000000000000000000000000000000000..4d256e0ecc49ff969a94aed01a20c0ebd7d83b42 GIT binary patch literal 1128 zcmeZIPY-5bVt|6<H#9v#Osl!)jTnXf3rdSUQWH}c8TFVB$bpskgOtMoNI4^r#~aJ@ znC1Wf|NlTbLK4%9xq*T?nMwITu7aVSA;^4HAa?b0_IGg&vIU8!<|Qkn73JqDfH=A( z`T04;3I=+HdX}ew3VmHe99<kk9OIpQ{GGky13aBWLW5jw9YLTRMC24Cg}dcN1zVOU z<tBp!ydsS(jiMqv)05qEvOof^Ai@bmKuHkGE!->A)zrf!Fh9{b9VB4r73>-m3}Qjm zLv*;LdX^W1Epm>`F7XHg$`|GvWch;in0f`dx`n3t6=Z1#Xs4wI1-d#082Wo?d!$x+ zghznPa&*mc3&{>H4a{|R2Kr_cjE2Cl4*_t=z@i5&N<bw8h$&@IxRa52#{Yi+Zv1N_ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PCX.pcx b/ExifTool/t/images/PCX.pcx new file mode 100644 index 0000000000000000000000000000000000000000..07ad09f2d423e1049649bd07bb110df241ea19cc GIT binary patch literal 160 zcmbV@%N2ki3<L=w<U;~&S-M9%(qI7|oO%0pFKYx+5>AhFtu^P22zMW2n3<}I2s8KI YiKw-Ph_zM#*hiG|_TE1@@2h;}0fbr>;{X5v literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PDF.pdf b/ExifTool/t/images/PDF.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bec9652220f6cd459e95432a2ada15dec82824f2 GIT binary patch literal 8907 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PDF2.pdf b/ExifTool/t/images/PDF2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..76877ca4033370021e71ab04dfa871337fbd9c6c GIT binary patch literal 2213 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PFM.pfm b/ExifTool/t/images/PFM.pfm new file mode 100644 index 0000000..4501366 --- /dev/null +++ b/ExifTool/t/images/PFM.pfm @@ -0,0 +1,4 @@ +PF +512 768 +-1.000000 +<dummy image data> \ No newline at end of file diff --git a/ExifTool/t/images/PGF.pgf b/ExifTool/t/images/PGF.pgf new file mode 100644 index 0000000000000000000000000000000000000000..06d52e77c3f760e79f891a2101a6e78580e1d68a GIT binary patch literal 286 zcmWG=cQc#Az`(!(#S9D*%*;S`XMmqOFP9V-kjd-m;SvO-89|tX2}n*T555QD6bHFG zF|0c$^AgD6EbxddW?<kJ24O~qS#u<Sf;^rsjv*Y^lYjjGZ_mK`kLll?Gm+1M;*uq< z5hc#~xw)x%B@C_=nQ0;U`8f&!?rsVpsl_FK3Xfa>Dq`?-^>bP0l+Yx{uv|mIWfdx5 VVraH!FlJzI`3F?_9}1|=2LP?+ZCd~U literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PICT.pict b/ExifTool/t/images/PICT.pict new file mode 100644 index 0000000000000000000000000000000000000000..511b83b169364fcb3fc912f15c0d9312cbfcaa9d GIT binary patch literal 150 zcmZRW#sC5wKq$!cpNHZ9KOoNoieWM!T8@E{fvbVBfwBI7{eOm8K#~6q3LpuvUIqph nFdL>*fI$H$!T}UhVBr3?fPwpu16Uqp4+wy1ricHrB8L9}VhtYI literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PLIST-bin.plist b/ExifTool/t/images/PLIST-bin.plist new file mode 100644 index 0000000000000000000000000000000000000000..42eaf170f37a24d5c50849208a5e0ee0929275a4 GIT binary patch literal 351 zcmYc)$jK}&F)+Bv$i&RT%Er#Y$;B-oC@Ll{AsG>pT3iy8nwSG-xMU`mM1e$tONuh{ z(j!4k$D*Rd%4iVFGp{5yJ+%nT4$aF<&QD2&=t?YsGZMkFPWkycsfl^Y_TSRL;1Uln zAAd-AMrvM|V`)i7ev$FPIv#}$3=9k*0U4P&@dCoG6`5%vKwSy}KAyoL3MD}677GcB z1n1|a2A7oQhn8d%rKSopGUN*gGq^HTFuY?(V+djR#_)q7he3fMfWe2slOY%=qQFqX z@RFgJp~Tp6+w}#<fHwHpq?G37Rw|?<mL%G7GH@_RGpI1=GFUJ;G59e=F(d;Gs9<Ph wXk+MRn8YxfVFAN7hFuK%7!EO<U^vb2h2bwF7tjzOU}A*O3{p@UN-=T+0KxcU1poj5 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PLIST-xml.plist b/ExifTool/t/images/PLIST-xml.plist new file mode 100644 index 0000000..eafab40 --- /dev/null +++ b/ExifTool/t/images/PLIST-xml.plist @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>TestArray</key> + <array> + <string>one</string> + <string>two</string> + <string>three</string> + </array> + <key>TestBoolean</key> + <true/> + <key>TestData</key> + <data> + PGR1bW15IGRhdGE+ + </data> + <key>TestDate</key> + <date>2013-02-22T12:49:10Z</date> + <key>TestDict</key> + <dict> + <key>Author</key> + <string>Phil</string> + <key>When</key> + <date>2000-01-02T08:04:05Z</date> + </dict> + <key>TestInteger</key> + <integer>256</integer> + <key>TestReal</key> + <real>1.4</real> + <key>TestString</key> + <string>ExifTool PLIST test</string> + <key>TestUnicode</key> + <string>ExîfTöøl PLIST tést</string> +</dict> +</plist> diff --git a/ExifTool/t/images/PLIST.aae b/ExifTool/t/images/PLIST.aae new file mode 100644 index 0000000..96ef1b1 --- /dev/null +++ b/ExifTool/t/images/PLIST.aae @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>adjustmentBaseVersion</key> + <integer>0</integer> + <key>adjustmentData</key> + <data> + YnBsaXN0MDDRAQJac2xvd01vdGlvbtIDBAUWV3JlZ2lvbnNUcmF0ZaEG0QcIWXRpbWVS + YW5nZdIJCgsUVXN0YXJ0WGR1cmF0aW9u1AwNDg8QERITVWZsYWdzVXZhbHVlWXRpbWVz + Y2FsZVVlcG9jaBABEwAAAAF3SSaQEjuaygAQANQMDQ4PEBUSExMAAAAG11VeoCI+AAAA + CAsWGyMoKi03PEJLVFpganBye4CCi5QAAAAAAAABAQAAAAAAAAAXAAAAAAAAAAAAAAAA + AAAAmQ== + </data> + <key>adjustmentEditorBundleID</key> + <string></string> + <key>adjustmentFormatIdentifier</key> + <string>com.apple.video.slomo</string> + <key>adjustmentFormatVersion</key> + <string>1.1</string> + <key>adjustmentRenderTypes</key> + <integer>0</integer> +</dict> +</plist> diff --git a/ExifTool/t/images/PLUS.xmp b/ExifTool/t/images/PLUS.xmp new file mode 100644 index 0000000..42c725d --- /dev/null +++ b/ExifTool/t/images/PLUS.xmp @@ -0,0 +1,127 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 10.15'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'> + <Iptc4xmpExt:DigitalSourceType>https://exiftool.org</Iptc4xmpExt:DigitalSourceType> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:dc='http://purl.org/dc/elements/1.1/'> + <dc:creator> + <rdf:Seq> + <rdf:li>Phil Harvey</rdf:li> + </rdf:Seq> + </dc:creator> + <dc:format>image/tiff</dc:format> + <dc:rights> + <rdf:Alt> + <rdf:li xml:lang='x-default'>Copyright © 2016 Phil Harvey</rdf:li> + </rdf:Alt> + </dc:rights> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:photoshop='http://ns.adobe.com/photoshop/1.0/'> + <photoshop:ColorMode>3</photoshop:ColorMode> + <photoshop:ICCProfile>Adobe RGB (1998)</photoshop:ICCProfile> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:plus='http://ns.useplus.org/ldf/xmp/1.0/'> + <plus:CopyrightOwner> + <rdf:Seq> + <rdf:li rdf:parseType='Resource'> + <plus:CopyrightOwnerName>Phil Harvey</plus:CopyrightOwnerName> + </rdf:li> + </rdf:Seq> + </plus:CopyrightOwner> + <plus:CopyrightStatus>http://ns.useplus.org/ldf/vocab/CS-PRO</plus:CopyrightStatus> + <plus:CreditLineRequired>http://ns.useplus.org/ldf/vocab/CR-CAI</plus:CreditLineRequired> + <plus:ImageAlterationConstraints> + <rdf:Bag> + <rdf:li>http://ns.useplus.org/ldf/vocab/AL-CRP</rdf:li> + <rdf:li>http://ns.useplus.org/ldf/vocab/AL-FLP</rdf:li> + <rdf:li>http://ns.useplus.org/ldf/vocab/AL-RET</rdf:li> + <rdf:li>http://ns.useplus.org/ldf/vocab/AL-CLR</rdf:li> + <rdf:li>http://ns.useplus.org/ldf/vocab/AL-DCL</rdf:li> + <rdf:li>http://ns.useplus.org/ldf/vocab/AL-MRG</rdf:li> + </rdf:Bag> + </plus:ImageAlterationConstraints> + <plus:ImageCreator> + <rdf:Seq> + <rdf:li rdf:parseType='Resource'> + <plus:ImageCreatorName>Phil Harvey</plus:ImageCreatorName> + </rdf:li> + </rdf:Seq> + </plus:ImageCreator> + <plus:ImageDuplicationConstraints>http://ns.useplus.org/ldf/vocab/DP-NOD</plus:ImageDuplicationConstraints> + <plus:ImageFileConstraints> + <rdf:Bag> + <rdf:li>http://ns.useplus.org/ldf/vocab/IF-MFN</rdf:li> + <rdf:li>http://ns.useplus.org/ldf/vocab/IF-MID</rdf:li> + <rdf:li>http://ns.useplus.org/ldf/vocab/IF-MMD</rdf:li> + <rdf:li>http://ns.useplus.org/ldf/vocab/IF-MFT</rdf:li> + </rdf:Bag> + </plus:ImageFileConstraints> + <plus:ImageFileFormatAsDelivered>http://ns.useplus.org/ldf/vocab/FF-JPG</plus:ImageFileFormatAsDelivered> + <plus:ImageFileSizeAsDelivered>http://ns.useplus.org/ldf/vocab/SZ-U50</plus:ImageFileSizeAsDelivered> + <plus:ImageSupplier> + <rdf:Seq> + <rdf:li rdf:parseType='Resource'> + <plus:ImageSupplierName>Phil Harvey</plus:ImageSupplierName> + </rdf:li> + </rdf:Seq> + </plus:ImageSupplier> + <plus:Licensor> + <rdf:Seq> + <rdf:li rdf:parseType='Resource'> + <plus:LicensorName>Phil Harvey</plus:LicensorName> + </rdf:li> + </rdf:Seq> + </plus:Licensor> + <plus:MediaSummaryCode>|PLUS|V0121|U004|1IBB1UNA2EMA3PTZ4SBG5VUP6QUL7DWM8RAU8IAD8IAG8IAR8IAE8IBR8IEC8IEN8I +EV8IFO8IGL8IGR8IHH8IIM8INP8IPR8IPM8ISM8ITR8LEN9EXC|1IBB1UNB2BFT3PTZ4SKG5VUP6QUL7DW +M8RAU8IAD8IAG8IAR8IAE8IBR8IEC8IEN8IEV8IFO8IGL8IGR8IHH8IIM8INP8IPO8IPR8IPM8ITR8LEN9EXC|1 +IBA1UNC2BOS3PSD4SDL5VUP6QCX7DWM8RAU8IAD8IAG8IAR8IAE8IBR8IEC8IEN8IEV8IFO8IGL8IGR8IH +H8IIM8INP8IPR8IPM8ITR8LEN9EXC|1IBA1UND2FET3PRV4SLA5VUP6QCH7DWM8RAU8IAD8IAG8IAR8IAE +8IBR8IEC8IEN8IEV8IFO8IGL8IGR8IHH8IIM8INP8IPR8IPM8ITR8LEN9EXC|</plus:MediaSummaryCode> + <plus:Reuse>http://ns.useplus.org/ldf/vocab/RE-NAP</plus:Reuse> + <plus:Version>1.2.0</plus:Version> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:tiff='http://ns.adobe.com/tiff/1.0/'> + <tiff:BitsPerSample> + <rdf:Seq> + <rdf:li>8</rdf:li> + <rdf:li>8</rdf:li> + <rdf:li>8</rdf:li> + </rdf:Seq> + </tiff:BitsPerSample> + <tiff:ImageLength>1</tiff:ImageLength> + <tiff:ImageWidth>1</tiff:ImageWidth> + <tiff:PhotometricInterpretation>2</tiff:PhotometricInterpretation> + <tiff:SamplesPerPixel>3</tiff:SamplesPerPixel> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:xmp='http://ns.adobe.com/xap/1.0/'> + <xmp:CreatorTool>Adobe Photoshop CS5 Windows</xmp:CreatorTool> + <xmp:MetadataDate>2016-05-18T12:54:01-05:00</xmp:MetadataDate> + <xmp:Rating>0</xmp:Rating> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:xmpRights='http://ns.adobe.com/xap/1.0/rights/'> + <xmpRights:Marked>True</xmpRights:Marked> + <xmpRights:UsageTerms> + <rdf:Alt> + <rdf:li xml:lang='x-default'>Phil Harvey</rdf:li> + </rdf:Alt> + </xmpRights:UsageTerms> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/images/PNG.png b/ExifTool/t/images/PNG.png new file mode 100644 index 0000000000000000000000000000000000000000..55aa11f8447e5f20fa0802de7b48bfcccbf8d4cf GIT binary patch literal 572 zcmY+BzfT)66vs`4gme;!fr+J-7(C@nf&j(315qxJs3bxVsTsNWOk(AJ)#s2S8weyi zbY`i<-l6{h0}>Mhe*ps$e?iv{MeyztMeShweV@Pn*naQZeq(pmSTqnqv$d*o0PB1< zreH$rSND&wd}x2(brAabeW?U$uI6kt(<lDt?dEJ@8of3?zC1tluVH6FS6Z4!K_Ehn zv`91_i|e_+CPGuUvf0wD`W}O{>3UIHm}wBhL4>T$G<JDUXxtVZ8QSFc?H$3=v&om$ z`f@$q7F}5#Dseb$H2JW{%O2U>FszgTAQ0NcY2b$mOKp;K8N^wd5}aFf&nC6N?Fhyy zsXT5*k&oBR)gl3g@{ZYovoj`uvPoC#m{B?y49vlb8L18}mdj<jT%x5?0UU+oG}LZd z2$Lls7&vD*BH>ELT1Fwxm~K1jYnu=QkI^2VkmJ7cbFRm!@I_Xzgci*r1sukf#~D|` z)sf2N2!ANP=3=CLxv}4sKCZgzM4VbQyAX1@I~QZSMgL=sjQ_k8{-4{S09iPxCx}T$ YL<ntAQ16)^7nfPHYL$lb>(kNsJLj3RnE(I) literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PPM.ppm b/ExifTool/t/images/PPM.ppm new file mode 100644 index 0000000..01b59a9 --- /dev/null +++ b/ExifTool/t/images/PPM.ppm @@ -0,0 +1,5 @@ +P6 +# ExifTool PPM test +8 8 +255 +ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/ExifTool/t/images/PSP.psp b/ExifTool/t/images/PSP.psp new file mode 100644 index 0000000000000000000000000000000000000000..7c4db225da798b4acbf0c0812f16f390b38a4724 GIT binary patch literal 1703 zcmeHHNl+6}5bZyImSjS#44_7=vI2-A2L)7IE=rV0zypH2rvL%s0urL4RazQxy{V{Z zX#wtd!(C6{t~cD#i~Dv0cR4V2&m@*r?w;+W=Xdx2-M?RV*Dvq|DnqWKimJt~f?$=) zyU17WcX<K}{b~XbsmQC``S=|kVGFLrCIu!k0i3`{W6)+AQ9^@8ekz<r+bYjgwDdu# zsj10`NpVd(hxsVw)CS4`yxue;wg?Sj0b<dfg`B`7iU+yK10!+)%BhSSb^&Fg7FT9^ zM!Kt@BCybv>kBURFJE0Z2pmHJqAQ)ETtrAliDHs(h-A9#`v3%iK|St~n^}(O{+f_k zC=-^5_RhV&e9{CA4G?ZLH4Zk^zRAbD`zKeSy99Q0$aa<bYf6HF#i2k|CAr8(R7#gL z#n4c(Q+5tGj2NgP#y(m!><1VeZIu(uLIGn&Gh?9!eW)FNT-$J!K)<Iy1V^67>j5S( zn1?^m+d#8mYzt=^2pZ!W(;q+`hKZ8Em>`G(ktB*-lqE%$C0SN1szp&$RID*pRka~0 z7P~#hW+$1P$OHd2MOGBr{wvZivV90xGSk5P4PpWpCA`|I+8iJ-&IOT6m=>N~iF+Vw zQfy*Iwj4LjrzBO!XRg`OY)N*vw)ZXU=o&n-I2g97{rWoxB%}-(nmR0P)aa}+W5<o3 zG&#pJCD)r*G=0X*S+nQNEh+Vvl~)Aj*MycVT~@n%#oD@c>o;uNw0Y~c`t3V*HtgEH zZ~uXVhYlZUIeP5)iIb;JpE-N({Dq5`E?>EN?fQ+Iw{G9Ld$05UqsLF4K70P+<*V1- zZ{EIp|Ka1O&pixyw5rreq;K$G`I$fkRtOIhmeHmrN{JbAZ1yz8R~?s>xyBOj-qPIK zZcQFp*wwc<*rE0tJnB(OH$gHG{TZO}KOy=G=qn!mKE!Z_W5YF=0KL7bT<@fT<uC$= zjwBIMP}+z{RdD-C7L*68mQ<ETz?(Bo6C9=JH0#EsN3FkJbseYp_pIv^jf@gO!Rv|t wQ{+wIMqwmK!iqSM>G5hqruU|HPmkW1NwThUa5hY6r?&p+tne3S`47&10U7H;IRF3v literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Palm.mobi b/ExifTool/t/images/Palm.mobi new file mode 100644 index 0000000000000000000000000000000000000000..c3bdb6b9ff2e0b49fb9f1ab4ec27306342d1e018 GIT binary patch literal 1382 zcma)+O=whC6vzK}5`si6QY8c}=1|mX7-RjcMq6THN=y?IHE8Nra%bk6dHUwP<h^;3 z*cOW7MyZ>K3$-ZdN^~KLpo`#6W9!Gpb|(l5E{veK5Eb-)CvWT&v+%;1-?{gmd+zyo zbDQ0sb&lDe^Lv_nFYkDf?%Cq>882_}acn;H%~C5L7h<KcqvNOcj>h$z?ky~>021-u z)GQTFrk9hH>OEuyP}4+C0_M#lD}jX<$SJ_`Ipi0>n#<%gplKKRCD1yPoB?b+OjZHi z@5ySQ_bAD|zIyU2z`IV)2BNE^0Ke6cUjqkUlJtGV-pf0O&yn+i6L-m4;H=Fm37kJp z)&ZBElivc@ZjnoX8{OnG;N}C8`Q7`4TnYTUfaKi|XOU}wCn5O*FvgtF0K7U$HUe+C zhILTtBKafKq!(lh)U@x(R;a3f$Tq0iTgeSjbKjAjP)VELCa9$c$S$Z=H_0uejjJ1~ z{tCGbYV{d%2YH{|g+%3RvKM7c9Y~?9)z&pbwvfBYU&$;g{$Om_LrGXUJ%U;mog?n& zQ@hH>Jyx5xZl_-H{xb({S<SLGlHDn{)hG^jAB&2U5_Xp86RO3lC)`w6%ymVvowQ`v z3M2}H-GeIeVPi@*cXzQOi7x7sDH+O)c4x1VP7`Q1uQ_)q)thrV%<l7pOqi7QUYcJB z`snUxi%A8>59Pc1l_JAGiynx^B<+|WuXFa_BY{afDdUMtqfFr+7E0F7@l+@|pJuKJ zS4+PMe9^8umXyfzWqYd$qjcbed4H#5j0{8$U4%w5TJk2$J2~HT{P7+0f9pYK(9|6h zY1E<4_`+i)Atr^6--M#GexT#f#Ea-LC<$*!xS;J(<I0A}4EoY#S8PIlova;*jBC>& znNpf|R-^5BelnzmIWSt??>cEZ3iFRV$?BA=J%+SfyJ1|MAs%6Yina3lZ7B4tT`_~& z>oaa4%TJ@QssC3ac0=)NLwy;4O2+T@1t=ZQMqZ7nH)>S9P>&jh8%{O+$=$yxQz)a9 oJF#xRpSvacy;x6?*3Q~no4;b6!0lL1G`lkXmP+w0mG~|F0M1=Pc>n+a literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Panasonic.jpg b/ExifTool/t/images/Panasonic.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae04fdaba2c8ab32abc9867872fdb99401a293b1 GIT binary patch literal 7099 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 LGT<F(=l}FSkQ56J literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Panasonic.rw2 b/ExifTool/t/images/Panasonic.rw2 new file mode 100644 index 0000000000000000000000000000000000000000..54dea70e8b8e90f0394ce93b41096f680eb1c8de GIT binary patch literal 12344 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 JbY-MX{|A9mHZ}kN literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Pentax.avi b/ExifTool/t/images/Pentax.avi new file mode 100644 index 0000000000000000000000000000000000000000..d10eea5beb461e6e953c12d63e8fca590c293cec GIT binary patch literal 1672 zcmd5+&1(}u6n{I}G+K&HTa}21Y%16&w%H^r)fO~W8@v4sZ7fARv}U!5CXFUBrb<Cj z5J9j9p&+^Fsegb+55<4L9;6^h^ypEjNL9%C-pr;96};GkLuU4uH}C!4$Gn+IGagU$ z*P8~WO>NYiN{+e!(pDimSI(qQ0l?c=xH!WL3d}I^hd!(QsO$z<cVrDc`V!YS{1!`v zEKbf<&RE6Vja&1w9`VP{wQ>=06Q{RJ(r~Xcw7(ui5zU4_1$;0zoVW=3aY<Rg%z{;^ zAXITGICb4hi5;<~yXsAZKDWbmKJxy*V#@s^lE}Hw-LZ;0d3o7>I?|IZ0Ej7fxtx@{ z7gZF>WC;Q4Fz~`3Ifn0EmHCJIM2=rbNZh+TC`T1T%3HZC{?c2>-{_59cJ<%WnHS9D zevC4r>!(9uJrwEH!jZnP(HAxNJuq<39FI?!Q}HDAaAI&gIWVJ*bXT;$4Q{)J&5&NS zO6kSi!fV8iEsTvpCsI*+t8Q8qz{|-0(;X45uXNX;2|T3Z`OthA5n)%$J~Z9q=p!d| zNVW%Yhj=2m9&+S+5&IoXzBCC398BK3gs-}7*YUD)zZ^$-TrQg$#^6}BZLs02z@8e~ zPB=UWAtB)_LdWlc9+;Q>W_g?xJzg{+M^1o}gzs}(iWr!i$J2qStuj#)AL2Jm)Z{$k z_v`?#8@_1F$2z$_z7XsLJI1dh9x;ot9*zTulPt82D@^AYIa!tbfdv83M4{l(?VG{a zIuujU7_1(FmtduqA-0uIwY4egW#zFBwaMT{`(Wwlv%vEwvxahJ_LptjwLm%e)cb<Y z!wa9j)*e7r<cnwcZlzUQ+1#wD>RRiM7A!uVwGvrI|2z&C+|#?Yhknbyq*Vh$+IN4{ s-%Mdeji_#fdkOCPpoX5lR?xydx-K;~3||@_p*L*j;BN>DbNO=SH)aQ^wEzGB literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Pentax.jpg b/ExifTool/t/images/Pentax.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b6eb71455b2768f5c4ce8b3e09bc766494e8d280 GIT binary patch literal 2721 zcmbW3U2IfE6vxlZ-R<s{LU(KF2h{Ggz%F0n?JljQO5246i&$9|4T)$dX^~ARG)+M? zeym`SM2xs05=o02i-Z>m2`|PM2;m76V|*Z}FU3S-B*Yiw!3Wp>nVGx23gN-Ixii0e z?wK<)XU?2^%Qwn16n$#rri~;7k-$aen~~y%gZW)VgM+l3h{7~S)RF|%<1UJ5h0lsQ zav&4XA+dmhnu)#^3oCp@EVVkYAUbs4Cg@!gsfwMS<wLVN@8>CC?8vQGzn~C$y@Kbv zn8@AShG({BwyE{|#>e)J4^8Cv?NwxDn>*aBvIXiiU~HIHzkG3ITy?BUyPlR(lycqf z9yi_Nrc_6&$L;7zr71U_-#f8skleKE!bTfb*MLjHHKb>Go#%vtHKRmyU9Iamm}@K5 zXr8O<d629Y#u=gvk&uVEc61%kbx_w;x(*cz1rpH$blBmj^;lAmCG}WRk0p7mtkk18 z+P<UhJKDaZ?K|40qpcCe&^s9=szpz3gC|WJzF^uGERf|A-sYA*Fs<{AX&3%t<$7NK zyJ|gj%8L!>%Au(WUpv}vW56LgnzTGjbey!m18yqSaoq7@#`*qupu)d?ER2qj4iy8$ z9>QvzzltGi2a#EfSjO6l)!w)PS$vcG2EN5zUGm22aNUxy`mj`zUQEupTyi`PeOC&4 z9KP{+qt(a8O&dD`OQC5qH^K39#<R&RXYHSHEVDdlxfWdIlZSomt%dcgeexR1Jg_M| zEWAjEa97_i-tQ-aF2^1G==_FuIJ0FvX9Mp{1+;nJTCfu=lCmIXK=%C70+8j%7(8xR ztZbz!vtOYbjs)>oPK{{fGsSjv9>O`&Hu2O)bFG}?a-QWXaGhr#^Yn5Eue9E$(_ncG z<lY_usY%PtF>AIuEMH)mJ@>HBe%vQlK5p}|7sG64k(J}T(PGP-8<tpRorI5*#wUN@ zEZ?T3K7PdLJkI(lBWI*E$2d7bZdPk%{}Mgsv$@u=VtKF8X=wrXnZC|SaKF`0fiu3o zPhyv_7do8{_~e^>JgD;@&x2@-PoDGfb~COJ+w19NGygk@+aJ<StApOy1m0`RKXd?z z*5kKjQ0pTwLPw;D8AL$nG_oSc%m(mz<VNO&;5*2W%+Lu(ktaP?$d(?v$eGrDxL=xB z!nol9SzD3kWVFKDWURtFr1{>l{%-rtVjhzw-k1-{`8sBhal!@JpvQ$D!zW~1Gu#<I zCEGREfzLW+%?kV#=3XhCq;M@N1WUOXDHCwCL`>#p-hD?Q7~bF-38iIt_!#zZ>M+qt zT6!Y=((^}t&-5S7b&LKzPPvSa4g{XnOH4-^yvO^rfA(QkM4#0EanH-ismW7Q=T4tI zeR2A;>GP!v_bB{F(1}?qYG2!jT2b?@?ip@dcX6%g*<0)NbNff#m-#ulJ*~KX^n7PO zw{4#iGKEO36EMrCR1m^JX1+WZ6r^@23P_5_=LXf`_;CC!HQ6?x?#9z`ll!?3(XvbP zY1s={a&mHUCVpKl-_M6A3~R6j3Ou)_n{9J!1YG2Nv_w@%+Z-W+<A3(ACX$`~tBE}A z|Eod7V&|bxk*%`wr64|MT)eEQ+b6R7M@L^*6TA11?%X?+-=p%QL$8jg;h~A4Ui)rr zeB?lW<Td}B@=x?0)kmYvb<N4f#$+lHPo!2QP*(MJa_P-xbjd!bi22RUszoK5ni5?J zmFUWLtys~84(mGCJ*eC4yYgj<h2g%iBic#EgcB3xtF&4FI+~^0x=MfS{w0c<9r4Lx W_ejb=QjLS1i8(POsEZUY|L_l+Q4YBP literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PhaseOne.iiq b/ExifTool/t/images/PhaseOne.iiq new file mode 100644 index 0000000000000000000000000000000000000000..63870b13f3116f79ffd4c274d99a51a2926ef3e4 GIT binary patch literal 3364 zcmdUxYiv_x7{{MpcI(F0T^QgdIlyH1?X>Gg?FMISw~?ATWMF~`A2_lOD{ZKyu+6Z9 zix)nSI2VF!8F30m{bmRzMurF<42k%`h?i)hlPI5vF;OGL1pPm!Z{bN5gW;2h{ds@? z_kG@LpT4Kza0BJLh{AY6qvp_-T%xYwJ@NRsHe`<WnkjP~-CXvjLA2j^|BZJI*Ow=a z`mHa0KzKg6JaBHU>lNd@<vEr|49YoUX8NYl*0%dR$E52P(4Q>N_1ylb8IO*e+Q25w zugBxsNUOKSul3%Zds8YIO>7t$*&RvkUZ>U*jG5ba4n+1wV(DdN);Y|J3?(DOyELEP zr1hKIQ@fJmT7NVdA2pMafaYn@JuN=^BV@)SNmF|g!yqiXnW#6qBbqW}nu-e|juIg= zHqfi<9=*LiQ}V9(ylg1l4)FTH4MTl@FJ(N=?BZ{AH~1Xz)O4#oS!=%+E-NG|s3f}9 zNL0KYrAc&TACYmCNUtRdKptof)CKiGeUSTKR98o&LCc^@r~+E5IDvjUpeVEldKNkW z9fEk@-g2T^@ZccPiCLlsa0|q1X@#=qbz)pQlnXKUBZk+<Wf<Zy@8ulsC#I@(!nk#1 z%m{~_bR~4y@Hda0d;95wXFoc>q4R3z0i)pijq{Bcu6CX;p6%N9#JI6CmN33~eq`-u z<;OaIpXxvR%QF+kkF}3?9lQS3+0(ziHotS*A!A?Dgi-(Tn6aukV%*Q#{_lO8IIZzG zokV#ki=Z6E72sUO)!;mE0m>T44$eIJ;CvVzDp%|W*MoVyZm2==0Ju@{7I2f|VXy~0 zi829s!JAQXf;Fp{&EOWr+rX`gV_;qJ7}%%yIdHq;NpOeaS@0UgZ-J}9y#6!b3NWv0 z9^9s4D(t9GtbyH%n*{p>_X>VY@K(W32~G*#FZhVy<AP6t3)LFl5j-#WlHe<X7X)7y zd_%AkcVhrs=BEVbfF>$|__O5C15;aKyBLib&vE&p{W6lKK=`@EL@wcL5ML-fUSU)u z{BiVOBzz40i-pHB$ss(hHYyiBKL^VdUPHT6crSd8@Wb$hVn47{Bs}l0PxzVXX`@(p zUXNRNWK*y-%a;lN#itiGmJ6?;f0<a%=V)Ip+R;Ruf0^^<_!Yv#66ao~oqa==Z_M(X z!!o`$%hzXl!X`8E3DSsjCF8k#P{yMoPwj_uhwqSF<!iurozQ9(&z|pwGW6knE`|6W zs8;QK7afLH!Q+qcN~Kw7rD{hvN)!`4q}qADGf<6apU3Yd8`jI?^L!sd4z#n^pdTQo z@SH1lh}*4uj{y_$T&%q_H9a=reWkB<JA}t?s$fve&)q}9SHYLc_yV%?{fR$ISe(tR z=C4C8>)=O3|L-pKY%HZ)h~<&32is$|B-jq-?ED%h;1Kn>iZLj@>2|^-w15~ln0I^) zT#7C7c-kU-+f_V|RjKN^ZHZ#8f54Vw#s7@HRf=zbtHr!^V&3SBYW?`7v;`kqzV&Y) zVvgXn)UQJf>sI%VD$A&UeR{8GqnU&0IlsjiClp@;*B(yC40F(Ao^`6a*Q+}w?Zg@N z6Jnf-?U>vCa(bLfux-j^?PEFDPo?Wu^ZKUloUu#Qx1fIC6yBfMC%;MTbLkkq!5oVD z_FD3Kx}MM5aq7-E8m{yesO!Ku{Af7H?qCV&2;{YUbx&JB?+ADv);#S2pD*C`{9_%T zKYt!4PnXc#3pQ%Lh=aqgG#(F2BOQ+~<i-DM7-4k1PjB~I+^*xLU3@#AcpUz^aA`?! nCZBSV6L-s}Ox{%g$Gj;*d?s%yz<0|VUS2v^vUB54w6OjH;`*|J literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PhotoCD.pcd b/ExifTool/t/images/PhotoCD.pcd new file mode 100644 index 0000000000000000000000000000000000000000..f72b4365ba4c8dbd84feba1d48d18b6be398381f GIT binary patch literal 4116 zcmezWA3vx80tO&p1cF6CzzPH)J_v9D0ffcCzzGCQK=2p{*nwa!5WwU>dSUEQdNc$^ zLtr!nMnhmU1V%$(;6ot5*(KgHz>|TE-IkHT+A`k(gc%Gh4D<~R6@mj?6buXv3=}}X z+uy~}8_d!-F*1UPxn<_$1}7)x<)s!W7#SECnCKargOvaY6Js+N6HW_yxF!~t<R<1R zc;}}iW<$l9@i#&K{|_k2&q++uQSi)5)&tsOrfX_q4#Xzr1j5e|C?D+P$i~jVD8O)( z!TSHH|F0OPGid%f_2=Q=$-h1^-e!Ew%*Ooc|1AdVzZ?GX{M`W5U}$U#B%2s6F|t66 zXMVxrgMf^J496I37#UK403^={Y65v^sg^MOhkFyq07;F4(GVC7fzc44We9XhpL!ax z=}zMAiuBZqAfpxmmE=V?E#G!$`7bc|WoxM)$DVmKI?L$G`;^JMWWIMxK2Vh5w|(L= y&(Mzb$Uei^W?;{a=`lO7t2Ww0qh&M=TC;63a}(236;cvQ5*1QXb5cuEQ|thW#I(o& literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PhotoMechanic.jpg b/ExifTool/t/images/PhotoMechanic.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7ee90448bfd77001beb8532cd1cb8e6b949d4e4c GIT binary patch literal 3417 zcmbtX-EP}96sF}Q&71;Dh7N520TnjD8qnI3-NlU!TR~%QXwjt&niMT|GsqHStCl5= zqG2b%?)C(Gl>xii#dfiaJwkuB0egUAkKi3rKPH{D>re`?$aB8)LyG6e^XK!|g&z)w zAr0eU7-_evCfeP<cd)X8U=+Sm6@(O2RF&Iky>6O~nptnEN*isMjr#4KTE*NkO;!0C zt&hB55YsSFm2c4M5w$5+6^t%*!%0BNsj9fB*nQYjl~uIyu>TA@RC{2L0rMeR?*e!b zGSC%NI+{e01{AF(FYBtZjn?nu(`iWDm@SH70P@>tl@1B!vm%{_s&WZ!eBpXjV@^x} zd5A9a{SAm64KY!b$7uD~qdtfPXst^yfK??%Ye(UPI6yr^m!1%hVgkZ5*n%BVk8R9D z>vys3`X00U8QM6Cu;baj<^=#Op~9}JQ1n4Jj82I+7=q3}(Pgv6o{9;h#?H0)nw=}! zM8C7+g(rRUGcR#op@8Opm%bZP8np}~h^w|6_Hoq-$HvT#j9S$+P<w9{+0F=4t&ay@ z(9!?-`4?UD+>ZWa=fFIOx_Ic_JtO$&?7^{fHgcM-zGs!%v({`JjWM;g+1L-_)~us* z<t>;qvY~4nLPs6_>A|5!!_XhWQP!)?O7o_6Lg16Z`H!?(wV{JP;-0h~^}bBC!K$OD zZcnGv>hxAMBm<+?Y&H$EZq(})Kvd$>fZDT45Pu5BxEJ2jTuj=n_mpoI43#u)s)u8T zc>HT<%(UGPC$yvMB`wu-FQU%pu*GgjaIO;s9PUU=T|-20?c4<eY-n0rWVX_q+YMc8 z!Od+;q7oOpGnhMqd++XYl!adIuXF}EJ39(+GpDt>9zC_P$ZH!*47Q~OiN~bHe???M zmZ9m^t@JIzGuDzo8o^c;%92RNlx6Nr=rAPlVHkTXyeuA)qTDh-7Ej{VN?xK^%8=1X zXj{okM5hcHoko0;1gta{h*B~@7S2+Hl}$v!jK;6q;@jK{VoD|sJBQd3(uyc5VPuoX z#17)I7snv~2%lhr0|#52su6r5YPlFHt9wiapy1Tn6$LY*3}xBc$72j3?Wf<~!!g@k z60!UCfG?7j?^%oVp2Q5082CKDF7cB^s!eq3%?=1xzXvxEt_A$M7As<vnpvqgMKvPg zy}}tX^dpx1<BP*NOW?df#A({eWm*R|8G(<-WP;l)*(a(p@_RYPFWVS9Lp$&s(K!cl zI<fm6jd_WYaLY+2lW!fuwTdmVxTUALgvf78PG_Y#smzv8rI)1z)Oi)>GPC6KQff|9 zU~d1|8|QaH=wX%}5Ml3i?(X8SqY3eOA#xo9Cs7l_3D?qV;5w~%!ds)a#81`$Nzsu< z4|GHqGkA;i2G%r`7|9|JHQB2+EZs#FUO0Nkr&1uZg52`$V9?QL6&Ih_6QAl(9(eJ- zz+NifHqIMhHXRndAz9cv!rw~vtV32u(-!Xn{VAU77nCvGl=XrO&F{7op@+S|nz)-> zJ+qa>{r@$Vx0L^3D%YOi<bEWfLAM5dn~^tyu*h3U96<j#g+_7y3Vn~RY;JCUw5?yg zs@Kcc%k^p*M%CJ3!|L7PqxZH^_;`C;Q?>H7Yvo2+D>r(L8#fvtu)nkaHn9b7{wvyA zNAMI2pCDzcP~0lae?#{X^vCj8J4ZpA%NVqEjxv9VE*A@+xwwV)(2wZ)`~_>-*&m;R ws{F+#YpU{`PZU*o!6t>DROKa`D9=>o6`K^#ROJsgSusI!&*^)S)9?HL0#VE);s5{u literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Photoshop.psd b/ExifTool/t/images/Photoshop.psd new file mode 100644 index 0000000000000000000000000000000000000000..199bb80da3475f086344de8bb404493331b54ee3 GIT binary patch literal 17369 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 TH1!SwC(ws3NlnF&noadT+zk#z literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/PostScript.eps b/ExifTool/t/images/PostScript.eps new file mode 100644 index 0000000..4fe6102 --- /dev/null +++ b/ExifTool/t/images/PostScript.eps @@ -0,0 +1,108 @@ +%!PS-Adobe-3.0 EPSF-3.0 %%Creator: Adobe Photoshop Version 7.0.1 %%Title: c.eps %%CreationDate: 7/18/05 2:33 PM %%BoundingBox: 0 0 8 8 %%HiResBoundingBox: 0 0 8 8 %%SuppressDotGainCompensation %%EndComments %%BeginProlog %%EndProlog %%BeginSetup %%EndSetup %%BeginDocument: (Test-Doc1.eps) %!PS-Adobe-3.0 EPSF-3.0 %%Creator: Adobe Photoshop Version 9.0x211 %%Title: 12a.eps %%BoundingBox: 0 0 435 283 %%HiResBoundingBox: 0 0 434.88 283.32 %%SuppressDotGainCompensation %%DocumentProcessColors: Cyan Magenta Yellow Black %%EndDocument %%BeginDocument: (Test-Doc2.eps) %!PS-Adobe-3.0 EPSF-3.0 %%Creator: Adobe Photoshop Version 9.0x211 %%Title: 12b.eps %%BoundingBox: 0 0 435 283 %%HiResBoundingBox: 0 0 434.88 283.32 %%SuppressDotGainCompensation %%DocumentProcessColors: Cyan Magenta Yellow Black %%BeginDocument: (Test-Doc2-1.eps) %!PS-Adobe-3.0 EPSF-3.0 %%Creator: Adobe Photoshop Version 9.0x211 %%Title: 12c.eps %%BoundingBox: 0 0 435 283 %%HiResBoundingBox: 0 0 434.88 283.32 %%SuppressDotGainCompensation %%DocumentProcessColors: Cyan Magenta Yellow Black %%EndDocument %%EndDocument %%BeginDocument: (Test-Doc3.eps) %!PS-Adobe-3.0 EPSF-3.0 %%Creator: Adobe Photoshop Version 9.0x211 %%Title: 12d.eps %%BoundingBox: 0 0 435 283 %%HiResBoundingBox: 0 0 434.88 283.32 %%SuppressDotGainCompensation %%DocumentProcessColors: Cyan Magenta Yellow Black %%EndDocument %ImageData: 8 8 8 3 1 8 2 "beginimage" %BeginPhotoshop: 398 % 3842494d04040000000001661c0200000200021c0278000f4120776974747920 % 63617074696f6e1c027a000a492077726f74652069741c0269000b4e6f206865 % 61646c696e651c022800115768617420696e737472756374696f6e731c025000 % 0b5068696c204861727665791c0255000b4d7920506f736974696f6e1c026e00 % 094d79204372656469741c0273000e49276d2074686520736f757263651c0205 % 001154657374204950544320706963747572651c023700083230303430323236 % 1c025a00084b696e6773746f6e1c025f00034f6e741c0265000643616e616461 % 1c02670020576861742069732061207472616e736d697373696f6e2072656665 % 72656e63651c020f0001311c02140007616d617a696e671c02140005696d6167 % 651c021400097574696c69746965731c020a0001381c0219000845786966546f % 6f6c1c02190004546573741c02190003584d501c0274001a436f707972696768 % 742032303034205068696c204861727665793842494d04250000000000109e02 % efbd9bdc1dc192279e30a0e27323 %EndPhotoshop %begin_xml_code /pdfmark where {pop true} {false} ifelse /currentdistillerparams where {pop currentdistillerparams /CoreDistVersion get 5000 ge } {false} ifelse and not {userdict /pdfmark /cleartomark load put} if [/NamespacePush pdfmark [/_objdef {photoshop_metadata_stream} /type /stream /OBJ pdfmark /MetadataString 3771 string def % exact length of metadata /TempString 100 string def /ConsumeMetadata { currentfile TempString readline pop pop currentfile MetadataString readstring pop pop } bind def ConsumeMetadata %begin_xml_packet: 3771 <?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<?adobe-xap-filters esc="CR"?> +<x:xapmeta xmlns:x='adobe:ns:meta/' x:xaptk='XMP toolkit 2.8.2-33, framework 1.5'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:iX='http://ns.adobe.com/iX/1.0/'> + + <rdf:Description about='uuid:b691db36-f92a-11d9-99a4-8c8e8269c120' + xmlns:pdf='http://ns.adobe.com/pdf/1.3/'> + <!-- pdf:Author is aliased --> + <!-- pdf:Subject is aliased --> + <!-- pdf:Title is aliased --> + </rdf:Description> + + <rdf:Description about='uuid:b691db36-f92a-11d9-99a4-8c8e8269c120' + xmlns:photoshop='http://ns.adobe.com/photoshop/1.0/'> + <photoshop:AuthorsPosition>My Position</photoshop:AuthorsPosition> + <photoshop:CaptionWriter>I wrote it</photoshop:CaptionWriter> + <photoshop:Category>1</photoshop:Category> + <photoshop:City>Kingston</photoshop:City> + <photoshop:Country>Canada</photoshop:Country> + <photoshop:Credit>My Credit</photoshop:Credit> + <photoshop:DateCreated>2004-02-26</photoshop:DateCreated> + <photoshop:Headline>No headline</photoshop:Headline> + <photoshop:Instructions>What instructions</photoshop:Instructions> + <photoshop:Source>I&apos;m the source</photoshop:Source> + <photoshop:State>Ont</photoshop:State> + <photoshop:TransmissionReference>What is a transmission reference</photoshop:TransmissionReference> + <photoshop:Urgency>8</photoshop:Urgency> + <!-- photoshop:WebStatement is aliased --> + <!-- photoshop:Author is aliased --> + <!-- photoshop:Caption is aliased --> + <!-- photoshop:Copyright is aliased --> + <!-- photoshop:Title is aliased --> + <photoshop:SupplementalCategories> + <rdf:Bag> + <rdf:li>amazing</rdf:li> + <rdf:li>image</rdf:li> + <rdf:li>utilities</rdf:li> + </rdf:Bag> + </photoshop:SupplementalCategories> + <!-- photoshop:Keywords is aliased --> + </rdf:Description> + + <rdf:Description about='uuid:b691db36-f92a-11d9-99a4-8c8e8269c120' + xmlns:xap='http://ns.adobe.com/xap/1.0/'> + <!-- xap:Authors is aliased --> + <!-- xap:Author is aliased --> + <!-- xap:Description is aliased --> + <!-- xap:Title is aliased --> + <!-- xap:Keywords is aliased --> + </rdf:Description> + + <rdf:Description about='uuid:b691db36-f92a-11d9-99a4-8c8e8269c120' + xmlns:stJob='http://ns.adobe.com/xap/1.0/sType/Job#' + xmlns:xapBJ='http://ns.adobe.com/xap/1.0/bj/'> + <xapBJ:JobRef> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <stJob:name>My Job</stJob:name> + </rdf:li> + </rdf:Bag> + </xapBJ:JobRef> + </rdf:Description> + + <rdf:Description about='uuid:b691db36-f92a-11d9-99a4-8c8e8269c120' + xmlns:xapMM='http://ns.adobe.com/xap/1.0/mm/'> + <xapMM:DocumentID>adobe:docid:photoshop:cbcc2a62-f127-11d9-ac4d-e8be6f73552e</xapMM:DocumentID> + </rdf:Description> + + <rdf:Description about='uuid:b691db36-f92a-11d9-99a4-8c8e8269c120' + xmlns:xapRights='http://ns.adobe.com/xap/1.0/rights/'> + <xapRights:WebStatement>https://exiftool.org/</xapRights:WebStatement> + <!-- xapRights:Copyright is aliased --> + </rdf:Description> + + <rdf:Description about='uuid:b691db36-f92a-11d9-99a4-8c8e8269c120' + xmlns:dc='http://purl.org/dc/elements/1.1/'> + <dc:creator> + <rdf:Seq> + <rdf:li>Phil Harvey</rdf:li> + </rdf:Seq> + </dc:creator> + <dc:description> + <rdf:Alt> + <rdf:li xml:lang='x-default'>A witty caption</rdf:li> + </rdf:Alt> + </dc:description> + <dc:rights> + <rdf:Alt> + <rdf:li xml:lang='x-default'>Copyright 2004 Phil Harvey</rdf:li> + </rdf:Alt> + </dc:rights> + <dc:title> + <rdf:Alt> + <rdf:li xml:lang='x-default'>Test IPTC picture</rdf:li> + </rdf:Alt> + </dc:title> + <dc:subject> + <rdf:Bag> + <rdf:li>ExifTool</rdf:li> + <rdf:li>Test</rdf:li> + <rdf:li>XMP</rdf:li> + </rdf:Bag> + </dc:subject> + </rdf:Description> + +</rdf:RDF> +</x:xapmeta> +<?xpacket end='w'?> %end_xml_packet [{photoshop_metadata_stream} 2 dict begin /Type /Metadata def /Subtype /XML def currentdict end /PUT pdfmark [{photoshop_metadata_stream} MetadataString /PUT pdfmark [/_objdef {nextImage} /NI pdfmark %end_xml_code gsave % EPS gsave /hascolor /deviceinfo where {pop deviceinfo /Colors known {deviceinfo /Colors get exec 1 gt} {false} ifelse} {/statusdict where {pop statusdict /processcolors known {statusdict /processcolors get exec 1 gt} {false} ifelse} {false} ifelse} ifelse def 40 dict begin /_image systemdict /image get def /_setgray systemdict /setgray get def /_currentgray systemdict /currentgray get def /_settransfer systemdict /settransfer get def /_currenttransfer systemdict /currenttransfer get def /blank 0 _currenttransfer exec 1 _currenttransfer exec eq def /negative blank {0 _currenttransfer exec 0.5 lt} {0 _currenttransfer exec 1 _currenttransfer exec gt} ifelse def /inverted? negative def /level2 systemdict /languagelevel known {languagelevel 2 ge} {false} ifelse def /level3 systemdict /languagelevel known {languagelevel 3 ge} {false} ifelse def /foureq {4 index eq 8 1 roll 4 index eq 8 1 roll 4 index eq 8 1 roll 4 index eq 8 1 roll pop pop pop pop and and and} def hascolor {/band 0 def} {/band 5 def} ifelse /setcmykcolor where {pop 1 0 0 0 setcmykcolor _currentgray 1 exch sub 0 1 0 0 setcmykcolor _currentgray 1 exch sub 0 0 1 0 setcmykcolor _currentgray 1 exch sub 0 0 0 1 setcmykcolor _currentgray 1 exch sub 4 {4 copy} repeat 1 0 0 0 foureq {/band 1 store} if 0 1 0 0 foureq {/band 2 store} if 0 0 1 0 foureq {/band 3 store} if 0 0 0 1 foureq {/band 4 store} if 0 0 0 0 foureq {/band 6 store} if} if blank {/band 6 store} if gsave % Image Header gsave /rows 8 def /cols 8 def 8 8 scale level2 { band 0 eq { /DeviceRGB } {/DeviceGray} ifelse setcolorspace currentdict /PhotoshopDuotoneColorSpace undef currentdict /PhotoshopDuotoneAltColorSpace undef } if /picstr1 8 string def /picstr2 8 string def /picstr3 8 string def /picstr4 8 string def /_rowpadstr 8 string def /rawreaddata {currentfile exch readhexstring pop} def /padreaddata { _topPad 0 gt { /_topPad _topPad 1 sub def pop _rowpadstr } { _subImageRows 0 gt { /_subImageRows _subImageRows 1 sub def dup _leftPad _picsubstr rawreaddata putinterval } { pop _rowpadstr } ifelse } ifelse } def /image2 level2 {/image load def} {{begin Width Height BitsPerComponent ImageMatrix Decode length 2 eq {/DataSource load image} if Decode length 6 eq {DataSource 0 get DataSource 1 get DataSource 2 get true 3 colorimage} if Decode length 8 eq {DataSource 0 get DataSource 1 get DataSource 2 get DataSource 3 get true 4 colorimage} if end} def} ifelse /_image2 level2 {/_image load def} {{begin Width Height BitsPerComponent ImageMatrix /DataSource load _image end} def} ifelse /beginimage { band 0 eq band 4 eq or band 5 eq or {image2} {negative {{pop 0}} {{pop 1}} ifelse _settransfer _image2} ifelse } def /readdata /rawreaddata load bind def 12 dict begin /ImageType 1 def /Width cols def /Height rows def /ImageMatrix [cols 0 0 rows neg 0 rows] def /BitsPerComponent 8 def band 0 eq {/Decode [0 1 0 1 0 1] def /MultipleDataSources true def /DataSource [ {picstr1 readdata} {picstr2 readdata} {picstr3 readdata picstr4 readdata pop} ] def} {/Decode [0 1] def /DataSource { picstr1 readdata pop picstr2 readdata pop picstr3 readdata pop picstr4 readdata } def} ifelse currentdict end %%BeginBinary: 531 beginimage 0202020202020202010101010101010100000001000000000101010101010101 0202020202020202010101010101010100000000000001010101010101010101 0202020202020202010101010100000001000000000001010101010101010101 0202020202020202010101010100010101000000000000000101010101010101 0101020202020202010101010101010101010000000000000101010101010101 0101020202020202010101010101000101010000000001000101010101010101 0102020202020202000001010101000101020100000001000001010101010101 0101020202020202000001010101010102030100000000000101010101010101 %%EndBinary grestore end % Image Trailer grestore grestore % EPS grestore [{nextImage} 1 dict begin /Metadata {photoshop_metadata_stream} def currentdict end /PUT pdfmark [/NamespacePop pdfmark \ No newline at end of file diff --git a/ExifTool/t/images/QuickTime.heic b/ExifTool/t/images/QuickTime.heic new file mode 100644 index 0000000000000000000000000000000000000000..78469063a59bce36dc58723255586d6e55a62e53 GIT binary patch literal 623 zcma)3&r1SP5T5r|$|5u(0wr+Ohz=159hX?rKrefYIs~ojZad)1$F_+^MA5BNm+s!8 zQ@8H@M;<*CHlv7<1P*WB{P<?(eZvEQ%^>b-g+U2O*oHoOq2KlZWKQ|9%XyM~*l0#$ z7eJ#IPyC5ZX^a&IPJym{Y_@0wK*a_*%783`;0(!{Q)DC0rWpzy#LGZ3KoDt;LZ2P( z58Ga;dURYiYs$Uwxnv5{uvs{Zksoj!Nh%Iio!%5YNmNlsn(Q`euSMt$HJ61rC-<2Y zpwox1@4)~dZJ5L`V~Z6r9CCrd6$YQq%agS8gzg@SQSUFPXPEb^uXm>>Z(Fr@K}#FF zrvaH{ft<yP_>4a4Ha)bCS9-Jyz=8#k*rD9*UpCepY^({Jy%{!EZ#kLX7;_oI5+vv2 qBbZ;GG>clfWLfAZ<%4vJ@-abCRyEw%YFsPTHIaYCq}V<9ANc`U+H&at literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/QuickTime.m4a b/ExifTool/t/images/QuickTime.m4a new file mode 100644 index 0000000000000000000000000000000000000000..a6f4a905bd88d8317ebd6536112998e300a76da6 GIT binary patch literal 5237 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 MxAd^(-auyl0jO-5CIA2c literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/QuickTime.mov b/ExifTool/t/images/QuickTime.mov new file mode 100644 index 0000000000000000000000000000000000000000..c553122b6e28cb667e93e06b0935f376d2cbdbe1 GIT binary patch literal 3871 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 VZrJ$a?@Zu+7I=7k0J{wk{Rd$!snY-e literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/RIFF.avi b/ExifTool/t/images/RIFF.avi new file mode 100644 index 0000000000000000000000000000000000000000..d18f3861c94f5d4dfaab1f65ae5016e9d51b438c GIT binary patch literal 1262 zcmZ`&OK;Oa5Z;s?P=!Qz9LnRcGDsjG@gr%|n7D0dWNL)dsEJAsNYTdLHg4^;u{V$Q z%#Yx}??6Z#;Mku6;y`}_vv!gu609}y>^I-c?99&YTc-Kt<ug=2u<%=}-7#|r_1)Mf zL$AMq5IR}Tp^L}YKtm{xa_Cp8A+!Yl3k{)d;9pL#`Jafz0Z4f4gQT&Bo|^<G(eXS_ z#NU2(GlfKK5i|efim{!$6fusT0wiRbFNao<{igLAeay~w<$;jM{T_h%++^S;^BkeW z(NX?+hW`A#l<Fe4z^6;(&ch}LpWq6~q3g(MSe^TT+abd?iLt6+bxl{wdbxx(MJcVM zr&tAs)in34w%NG>`VL_svkxpq%}%)tSP%}qZp%LW0WGa<jU(cmQjUA{*kgwD=i7G) zd#)k9TeB5A+M#`K_dKTU^Jd36KXt0Ew6(cX8|&jB3MeOd9QZ8J$A(1Qut#;!1uRQA zrSMZjstW@?v|D&5j43V`)q<mx)^T~gs8uRTReOQW*uy66VJ$7Cg4MFFuIuUso-Ao| z1=nKtNZ)UmlZ>!4q(0}7F3Y3Qs5mMW!}wTMtJSKkXtJgifKf=!7$@TbOCE_3S&Rlv zoY;%F2aOalNiQ65Lz2Lbr{x4u!=8m_Nii+1=!Ag`b<1i|ksD`>1fFA4*Mn;3d|hYe z7!6`SJ(cUo)TaSuJOO8Qfj=hE%waJT$ugzEA#KCfiO)MEqD>M|=qtl@pVOETPI23z zjACI}p!WuWAvHrFf|glGq}y;qF5#pBo`wX&vr$m01y$>)s;*Y_QndhMr6_63aLsje zC#Hmlv1s9>iEa8J+hslVz0FqN^YJc;hxDu_i@pMu@zdSZp(ZcJ%eZrQ0!|qQcMX#! z&qfV4vtgwSCUgX&C|=$tnCTAG!=3KlZP3NK3-S;|yqE_FEcJg|V%FUguUxD}AB64! bb_Yaw-U1PC3;1=x|4;ML{Am3Cx`h4$olh;J literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/RIFF.wav b/ExifTool/t/images/RIFF.wav new file mode 100755 index 0000000000000000000000000000000000000000..535d9e9633407eae1dfd2693f98959d67fce1678 GIT binary patch literal 224 zcmWIYbaOkwz`zjh80MOmTcRKUWHSQc0XYURW@O-C@bL@|kpPN&`nmagItRIM1KCCf z2Bx|O7C^)RmhS<IrB-C7rIw`@u>jcyMn(pyMX5QwAbq~>0fvSqmU><R?m$%~nYkcQ zLo+KQV=H4*AQK2ulk<y2fOLSXUx;Ibf^&XRL4HwUNoIbY0z+zUN)D2oe*sWfAv^%0 cFE_6Q<mQ6Zypqh63KO7n!5}5EBoV{_07E@5d;kCd literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/RIFF.webp b/ExifTool/t/images/RIFF.webp new file mode 100644 index 0000000000000000000000000000000000000000..e0d227c5b1beabf0d2997cdf4612b59faacf9ee4 GIT binary patch literal 586 zcmYjNO-sW-5S>_xP!ttCh<XTkSP|N_^#dE5inT2*7%5e>c+1DOjm;Oymc-uu8U74U z9`x6E^%q#3B(=D(JMX=ndCW|=-E7tt0PY*L&RyrUmji$uyy>~P>a>;+t}U*veXrE( zKN$eb5;sxX5a31F7eGKf1z7%o>E|orMz7t}Fe(b{V_0oOW?x}kg{-gzSybYJuCP7F z55gXxFY4Hba2CIiu^GU^`b>{2($m5avn2MEj`)nMavpiAK4M(d*uZpT^7ZykMAMMT z{jqYOc$aKwwkAHinOxuMlaXFF$aytaiKNK(Z0t;oY|9CxNG7V`YD_{L%an*o3LnX& zZL0$&N%1VVu4{?KQsL-?powqvrEa}Bvp^=3A?Kb%X&8ou@UY<e16nMX%k-c`OQk$! z<bx-Nt5MzwcK;meEYN+^<EHC~v7@TFV=fbtOFJ|4xvMw!t;EyNDYKZ(93IeOp-4%! zV(3yZ)*hM8t4Uuqb&vwfta29c3XQ#yq#*gG|BI6Uf4$5SaXy@s&i936zH#Q@nnT<t F`~rnyo16du literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/RTF.rtf b/ExifTool/t/images/RTF.rtf new file mode 100644 index 0000000..118e6b9 --- /dev/null +++ b/ExifTool/t/images/RTF.rtf @@ -0,0 +1,25 @@ +{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +{\info +{\title ExifTool RTF test (with sp\'e9c\'ee\'e5l \'e7hars)} +{\revtim\yr2010\mo6\dy19\hr20} +{\subject a \ldblquote subject\rdblquote in funny quotes} +{\doccomm \uc0\u12473 \u12488 \u12525 \u12508 \u12501 \u12521 \u12483 \u12471 \u12517 \u26410 +\u30330 \u20809 \u12289 \u24375 \u21046 \u30330 \u20809 \u12514 \u12540 \uc4\u12489j\'fcnk(end)\ +another line} +{\author +an author \{with braces\}} +{\*\company + a company \\with backslashes and leading space\\} +{\*\copyright \'a9 2010 Phil Harvey} +{\keywords +keyword1, keyword2} +}{\* +\userprops +{{\propname CustomString}\proptype30{\staticval ABCDEF}} +{{\propname CustomNumber}\proptype3{\staticval 123456}} +}\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural + +\f0\fs24 \cf0 This is some text} \ No newline at end of file diff --git a/ExifTool/t/images/Radiance.hdr b/ExifTool/t/images/Radiance.hdr new file mode 100644 index 0000000000000000000000000000000000000000..877ea1b232d1ef77414eddff2bad0d559bc025ad GIT binary patch literal 587 zcmX|8%Wm5+5bT*>F+h(6S(36WF@RARjRWM+I<Vb5P5~v+wNfb(NLr2mK1<iB0PT=F z%USO5^S90N`gZyA`-%<q@H(NlQFz-R{5k!@qu<-wg|xYp@I82o=t7){p^j)fD^;OO z=Es({JR+0e)BaLI3;qU`XLIn=1;>C4e2frs2HuRANkg>tn3v#vfD}?2VgiIFNQF6J z4N)ZY>3Dd7qL?^BTcw=+=xY0lc9u92q<NIo)X4_qiUjRM9rFJ_?>thv`%CpQbvmmi z+2(~(nr+uNyNBgwwZMm8$b=FlIvZj?G}s-E*t!v_fR<M?bCu6=cfEr$rbKl@<A=)m z7%;rTh5Fz;yT4sMEXb%hQ5fkmWu_HfR|`Ftd4?p<r-leq&Xmb$p^8EpgJkcKqD%%% z2O2JArV9>d-ku{lNlXXHZq}P0%iSW=ylUgK@7%NhT{$-H+m7ChbX*ea$!{8;ym-S` j^7BNItsd{z+g~(%CdfTi7{{eljPWNr^5qeVeWLFjB59U+ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Real.ra b/ExifTool/t/images/Real.ra new file mode 100644 index 0000000000000000000000000000000000000000..f5832b2dfd27677ca30161ba96c99f4b8953a9bf GIT binary patch literal 130 zcmdNZO8m>f!oZ+clxV`hwf_o;%}@j)8DoI_1O}E#_raJE$OnQjB_JWdz{uj6S7N}D zl9yV-z|0^Jl98$qoLZilm#*NRS(H=EpsA&$keOFpl37w(qLB8dNTD<@vn;i!I5j6H URiUc1I59UjH80~yUV1760OA8Aj{pDw literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Real.ram b/ExifTool/t/images/Real.ram new file mode 100644 index 0000000..da59c1f --- /dev/null +++ b/ExifTool/t/images/Real.ram @@ -0,0 +1 @@ +rtsp://media.real.com/showcase/service/samples/rob_h_realvideo9_28.rm \ No newline at end of file diff --git a/ExifTool/t/images/Real.rm b/ExifTool/t/images/Real.rm new file mode 100644 index 0000000000000000000000000000000000000000..7325b46521f2a197c59b1c89d99f5745ec2925b5 GIT binary patch literal 1915 zcmbVMPj4GV6o0mpxNh3YEg&HTV%n-LRE_PXNu-7YGImMRV#l&AacZk&J+VjL-LYoJ zjg5qmxWHH7z!yN35C`DY577(#0C4NETv7SG+1)q-wIY4mw{PB`dGr3wRJyHCiRc{V z@Ml=*boV-->&$%hVS>3QM2u7hJ|HUAiNx<j`M)SnrB<`kh5Qi^@Si=9v3;?Asy-bk zEqc~SFMQKuraD`jL~Gy!$HMe}#dv?yG7;z|_%g4lO>d<U5EXv>&0!f4-Gh!EH4}tw zekdd3Ydy{tSwm%{RXBlQ9hax2B-Y$x@KC)1%7Gp#-wW3ERUlQgud(6l1l_I@{_hi= zY3Rtx$cnCKRWx*}CH&d>MWPa?EF)jW;PZ5Pb89S`3SOs^kJ$EIn7GVadLW(2N;r*` z&$??wryD4ZRXP$)&nEhFo7L;<)%99!L)_fn+}N(&VB0;SQ#&$}24(~C$sD3QZQYiE znu(6lgQ+i#=r!+(eWOFsMZMe79%wU;D>p0GE49k{hWK1X1AV}OQjO>=B2xRP!2db^ zJ*4qa4WvFyqZnxudI3ATM6}wJv2WCbeL;dk^ks~iwFFm}jXoecaaYa}(jY0-8+IgP zDLf;^IFmQ_+I`sW0E=~DmBrrG&PWB~wr3v7Ila@+letmDkrk-WmN=>_)rGIa5WVWQ zT9}xnZ@YJxa;pl@Kr=uxbOBfZx*2i5?UZ|(&|Jp<0G?-ofp=X2@`jaq@WnL5BJoj8 z7o5KH2YMdJJi~I^3tb_0`y=TZBs_K?BFM$-0DI**%7Wv-FjDUH)t*IPW2=1EZWqu- zs>>qf^sfS$XE@Vn?&2DVo}L=th05FVKjW7jk>$@@SNg{D$5<rkew;3TO=#}>lt0J} zKraEAXDBr=$wO^;lBs=I6U$BohSKR?rz}FS$3K8BYfwJc0Q107BN-7_mPISlV^iI+ zkA-9U2FNG=l+ra&ekd3?0_GV?xanR`ed#K^^a223)*CipW9Au3EfvcCeBz8(t(@8F zB=D3QvbUi>>8#vKL=&vJF~!etLz2G#JHfzdj-6go((mA#6Z|Emf2K4`l#_jVqPx%n zvf*Rn%PG(DlecDgW#i=epyi64Sr#9=F5T9hTiAy<3(x!Yo&HG00-u*YChT(o^$5=o eUz3IC$cksuUC|@5#S+iYjfMC0;aRA$YX1Y-XgE9o literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Red.r3d b/ExifTool/t/images/Red.r3d new file mode 100644 index 0000000000000000000000000000000000000000..d2bd0b37132482500fb48a9cd29d762cbc8cc3d3 GIT binary patch literal 1160 zcmb_cT}TvB6h7nbY?Dnol^a$YTqwi!XPKX!UE9`~-Ps@e)pfNra8dft3d)kAg0_Dn zH7b*`H+$%zmp~#R*EO++a4X0M!Cne72r3FnilRC*?Mn61OP4v{cg~$N=id9B%K(_$ zqM@Kjb5W6{SjZkdW#4`;Jau~glW&vn4<5<7-8E{H0^Z)D4$rfvsmnmY9|J?GWy{X= zY#zSLhB;ZL*8s60a}nfnzzZJda*yov2m*$bD0n27N5H2HLyI8srJ~bS8VJe25GLF^ zd<*DE+j9(Nhq|#GLvoA-8IqgkIcc6}Fh{Iv93W$upKr|5+yXz$;1&wJ3uzHAxq;=D zh`iw91zb9tTPg~YEX(K>xE!KGastgSqk!XzP!A#tVueOFI58FlS#<K@Kp7tlxiP8O z3`|oAE@Hu;6c8MMwfWy_3+dV=L^d{qpu~TfioRh!WZiIHH(-5kM94z{!Qxv%>v)Pf zkLA56DtAU+Lqv}NT->LpO%FN}uwZ5oiV)A52&xx0p$Zj72Gg_>*^2RMBR)#jWWN08 zhSG`uEw7NQ&8)3+=HP!b3Q;+o$NH+KeVVHd028TI?Q#LCvVJDmxD9^nZ({(v+o-p- zLT==F(Git`Sf$QPnN4OeV}~O36wt5{fsg6oCcIZoTY-vyMP;`6laF7ac+DUn?t*-L zxBCYU<O6ALHJFa<p|I2YYD()u{zz3NSRyBIdltTBCX2X#77H9XRz7rcu5Rjqfo6>3 z`T_29?5_sUa2$I~^c(Q!U`S{B^0yb_4m$w)$$7QQT5Ra^tjVhOyd{P|FndJ3{%)z^ z_kI7Qr0PE^Q%g3NPvu<nHjIST&O5e2Q|scySm)EEGNVF$<84>X!-dL)38JhE<SI}V zQ}@2SlQ=t4mpJ#}UNUR`sQU2cfSU99xzZudSM2@eip|}w9$Gw+_!9e&2s{}Ze08@^ zO{TVMUs8{>NlDW3Z;Wd@@{VY&<JnqugI#M|`$)TZ_=R@0PeKhZfE|^-%p!Clz$#s% cEBKFKx$c@;e`U}g4dNNEnGkxgDUnO?6V)Q^A^-pY literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Ricoh.jpg b/ExifTool/t/images/Ricoh.jpg new file mode 100644 index 0000000000000000000000000000000000000000..67993e250ed79b2dfb8c81e80eed7b5cf5632e1f GIT binary patch literal 1903 zcmeHH%}*0S6n`_@F6BdL5#=*s<xmg|nJph;V^E<Q(4g4Fm}n#^0#ZZ4R4`&Z8KWd# zU=QlCC&JO_&4VGv6DA%w7(Wt%iF)$jf3W^$W_L@(@E7>D)A!rA@4b0nGwvt%o0&g- zH#<c%GD7V{6rplfNgSvK%VAXtR{*cFsKQa;m#mr$aE6Ci1BDePddnIEyusQ$9e87j zz~?64Uz9(1uH+4{|M@seA?*iFDg4{oO_UxUyfCEYG?=+Hlby5DX<G?kLqpnL^Mnv~ z(zcW0sAYF0MPIVprnbSu*7%~e!}QYa$vo_dYL{AyaV@oa_$kpcQ3?EpM8X!!hMxy- zSPliabA@Rg#tmBGehv75!>CNh#LqfKdD<}o`4uM=;BhA$*xqzXyckejC*oljxYWaN z?34xidFQ^2fsbO{a~?^|A_#k<;Ky$ZQDB}eN?hQ6y(8NN!^h@YA?)MuCbpn|)$w04 z>~{p}6Id6qWZq34@9{X8lFNR}@8={}Jq4h0Op~0vP&ze;BosylMg?N_CPN`PdAv&` zdMb5h8_=nV>JFH|TTxD>(2-+AQDX-V1%4<RRNDA%Y1=K>{*pH7$veUp-87od&dv{y z5MBht`{3~)9vLH4vBp%_<*AV*_tbgGTySDYo*QW=3Bl4ErrV_=BZ|SI73Zl6+LDK# zX&!l@c@y-coR4WvPc%2)Yu@<7ldBE(`ipFvYwEk~&cxMp_WIm3PC;%mpRtB#r{=B^ zV%4+3c@0Y<N5A6je)X%@NkHn~4Xa4S$FIp$_24Swl48YC8Q+fusp_2TLKMs`eL$`J z`{N_*l@2XYg`P2yOmta7B-M`uNW-EG8J$EsE`!TTAHw=ivH~0(l^qJDkM+qneT(T? zCNi0!YR%BQ9e23dU%bQ3Gdu5a&ir1Q=Xr5H<QbPVgeB)%@#Cq5TyD{tpI*p~&t|eS zRyLQpF=<U?<}>~7S9(Y_(P({jeQRxPYa-qpPjtpXo&7zM`ctPArS>9rpuXN}vf_1h z@!q%<?@je~bo9btpl4t&v*I^*onjG;XT)GF#AD2eG4~xVB<7t>nUW%NmxOTF{6(={ U688&L7^r{Dh|vjpMa}N#KiLzlJ^%m! literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Ricoh2.jpg b/ExifTool/t/images/Ricoh2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c17732aa28184a75b1273160662825b6fe31c18d GIT binary patch literal 3187 zcmds3U1%It6h3!mcC(YV$!?8p(#CWMy6I0^XD7{?1c|%N)+E@}vW*lfYB$}qUFhzV zxG6>|#27RnO1cP2L0Ke<lu(NLTzx2gv8afMRuC%mrTFAS9(?HdojX6fZDL>gAbNM_ z-tV4!e(pWz+%x0+?A+9U-2Y191ku<S?IfZIZ4gbQ0HyF;6wMN^2fi($5=Vho#70sv z3j9cHp^(HR!eVQHo5XgH2ik-sWm|wYDG$}?b00|h7SMOTRUpmPqhuBy^KnDw>L=8I zbzaI4=~?5!8f8a^o*i*@&`|Eo>4I$>nzD`I9>ahQ8ev2JXqjem!0a8cl13siu(uC? z+CH?yn3yr{7JcsJ$tgNCRVbB5$H=sLO=xI>o)-KRRwvJ#=aZmCWUtG<QTFT5FBAKj zXoKvV&|ekOJwnLSM`z_i402tFLNjD|488A;k<0Gb4rZn+FS;YW?T+d<?zr`qN0-_j zxC;x_i)WbH=Pvm1S1-(c7~qc=I%_ds=<@JuM2jRnqM4RuE~<Wxs23Nt0AD#TTpppQ zbY2N`{!ppm8x@t<k2tyddqoTI?Mf)%|FhEI*+u47A|8fNMfY$kaAUy#SLGbrRoaO( zrSc|Yfw+Y`s{Wo6rdbu9EG$<QKW8IURec<$YRJb8@g=2Zcggn~_OKMLqc?h(Z=u_u zJ$xN|iDQ~yD<{{YVZSNn$7A7P{~Y7dahw%*F8IFjdOy#zh2!?A?|^jpk!@?){BanS z7$?`xVP7nPN1KPAIG2%U+ItiZ@KV%`7JKc>Xy6fbDH?c$$dEkZAb7rq4$4lRwX8({ z8U=_~Np!-J8r6@;SzQ-_VUaSzp?GX$1l8v{9F*_)gM?7%d3hY%(&FS)&KND7uwQVf z87D*ZaiR%2ik?ryPS_2^Cq{h=eT0+w_;VO=`N4-X`j~I9l!eS0_{+sM#vp$}*JvFy zu^ffI#m#0O=negZ`z`e-v(8P_2wx8_w5R_P9fDukyD9MhhLI58-T^K}$y)IPt!KdO z8%XrmXhhB(Z0<^J6v%2?gkgg^XB7kc*33B$+YUPVD9xwzW3(XX4US}Hj%F$Py0%j& zDVnXOS`<n%XI&c}8hdFxJxuJ+sba2NuuGJ+r%U<VggsrRgEkG@cAoaviTuByf_QRv zXxaWgjweS+Gf@fTG2751-Y6K%<8mrUbLQk+K-?o|(etVz(^(7X?*g$bKe+CG?nFL? z4PW*W@{VO;EpiQcC-tP6ztP1#jDx(3tRU|skKDtCK<6FgourR5f3r{5!w7ec9t-GA zv{BLx6o*Z;+2vtTY7Kaj)K*6iz6ta}dPLIkw$h)b4w>Wb8KnYw>3Pm}F^aD+=G`Uf ziw>9G_>M=PcIUF5vE1X{oby~*d%LZkZp-XWJkg!#=}siN&AqkTu?n#W-@kf$r~%Th z9FGhi-re`l8o+vYtJPu9>)(uZUrBnk_LZc6ChESD@S(uF!Kw7i$RH0BYN>2@eVII& znJyM*jPj}J;zTJ|IBgV)xs#JdK3C2SI=4hMJGOs3jjuX(5gO`J`tZ=`C}Tm(hYKf5 zMtSDUBptAiyVv+Yp-e+|DPNG6I{j*)I9)!)?Ei98X!$=>ZB*Pie$#QC2;nwuTHAZh zuQX3B(P-Powyw>ayR7)OxV0yawr8-H+h8UwTjqXEJlxi1v>Wl()_5{*#FLriu3bq8 z?CafkKe5`H^EJgHC@B$DbdnMiYD_p+=^4B~@7Z;D6@~Dgzo*4kQJfpJUPTp*sWEzr KzMyT+FMk8n!68Th literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Sanyo.jpg b/ExifTool/t/images/Sanyo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..25a106b5a4ba6227769a336be47051ef85e0a705 GIT binary patch literal 2165 zcmd6pU1(fI6vxlpxx06F4asgynrxdk)5LD1+r+z@?y5^jyUhl(Hi;pv1?vZHn|>^G ztCp=)*nl7P<H3h?MKDF%VDnJ=Abx-#5h<vZY70eQ>MBU8X!S`1MT@fj=ia$wpiRLy zC!9UMd*;lUGiUCdN&Rwt(K<4Gq_~$TpI=V_BD#ednL(|fDx?YA4t*LD1_z<fLYf&T z1}$YJ^Evu+@G8Y$f^Szo1~{R{*1$<{3-k|=TMajk^eMh*I&uC4|KTUvmK}fKK9S4c zli!{l6+_vv;mNFL9X_yUcll6pw-|b?Z^LMLs*iR|4E2uf>=#5M7%%hk^p12QInjG( z3Nzbwe<opP5($w=W;XU`>=bp=@n_xq-$W8`C_V%3dTA-Q9>p^<@Qg(!l$}i4e8yeN zyYM#^p98aFt`EcdjAL&68tl9pa&o&O{T2TL_ElY<-@sPYomb8gP%P^nR4n%+r2MbJ zzf!emf)imXdI3C*{z=r2=2V^u+h%i1`(@0zb-rXFAH|n_$iZVd_LSx1iIBwR-LXJ2 zH0FwBNz8dS|4SI_Q}$w<$glZZijgt}G<#6vkj4#~znojn^8sWS5`x@lH*aKHt^A>! zah`V8h6g{2I|c2$Z<s|;o!k{~lv`k!c*3TG<!;I>{P-~x<B_<=rr}oNb@(@G_9l(P z8b>s4*4X<pWc(J*-m38mjoUPC*Ep(J=Dkv}T+b@ar$gh_8n4l~Q)8i6zLje=dl&e5 z*ygeJZdHf3@Mhhn`K(hcbD&0xRzSTEA8ImDd7=y8GfpQSv^E9i)L{v}k9fD>U(tz_ z;0qMmD)=lF9ud5S4O9gGlzsf1;B$<h5_|(Y_m1G3S?~+N_p)cc7yL6C_*?J{)o})G z3O;B)PE|2({zNsqVtz!kBZ8l%-*<j!u4fl3v-~RCe{|A3&n}$YW!}%OzCCW1*vl8! zn6I&Ke{3+nqlf=F&o?6$TQ=4c4_!eP|1Flb=w(q>L(0UV;=yu$jBI@4@(W?eWjSl| z=R^n5AdcS9UqrpH(4ut$&OToAJqb;LU*&JBFTQZctB57{<WXr^SBWo3?X+o{W+!RH zx@K&%X7JY#ewx;*z7aY~i|k(X|4*8%kb$Zg&Urm~_g$c+Gf^h#C$4TOdFXjJSq>+o zcax2R^_z_0Jdics4!fZkWp$c&<9K=?ceqq~N|g5>F6}y4C>{{SQeod7F;yrR2J7FT zS}oylv^m<-*4C4Zb;go?F|@wHlx%~!tkZHgYpgXI6&)hh-X2TGL@b?4$Kz=X*pk|E zvtdhp^|KTS;NA~#)<s5y@d&GbLR;}3sU>;Jf1L&Y4|tuPj$CJ{*QkMG10p;^o9I32 HtbhG4Dbf1u literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Sigma.jpg b/ExifTool/t/images/Sigma.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ac6ad9c5f4e7f2c76e7602414c161842028aa78a GIT binary patch literal 1531 zcmaKsO-vI(6vyB0mI4KZ@*xyR7>OxHVzaybplk?QD1}BKNIiJ)Qqu}e`axiuVB*Ob z_22<H84?qs9E?{vc`}BR2M-<$eq8iqjAxJ5|LacECMeUq&Trqm_h$A#J5xETd~^Mo z-OAk}N+vH*3lVv!Ls7{Mx)19Fx1oOo>rymQ(6bFFP1XJhcu?{l_`Hl!!3J^%(7%A0 z;2!jEVcn`zhhuUNT-7=s!Rs!fRB~=H=KPCP+|Y>*Fv2s|`f7w&UMtz9<&Bas13G?2 zI2s{S*F%;bw2TR1j9a0o6*e&c?p<f4J(%vP<RS3L!}=;lC1)@XZ8sAZLb_?{+P1Um zbBK3Ij)IlPPK<&#aqW1j>sx@oX~&uC8n}7Kso#WJt&+LCN5<Sh%$=QP`iYmzfN4+8 zYv{VNS65oya}@fD&Cw+PK+(Qu_4U(pCs#umWxpX;GS90u_?skS+sWM!BlcA~3*Qn8 zfX2FC#@{k}4-5)JUc)lSg`rzP!wQD}sX9W5Uuh7=d6O`%<?JqVAI4Q`g%8q`25tsJ zCTOqXnP@%<J;AKsE*x&@k0qcjm~$Z$zFDs1tg%&c6we+R^Aa&_V6JljZU^(4UV~3b zJ_MhZ{2tsP`3T%8`8(Jv`4_lL@^5etnCtV0(+l<j-QY7|K2!Z*e&cSi0QZCWWQ4#z zFt1<0xakmC*Nq8QYiVv_mME6En8H)Jn9r@Q+1Q1UuuEbbXJd0^ZklLzYrSNR>47NC zl!`W!PN}tY#-Vv6NnhG7Glh6`!x}RXm&oPw&gjgHg^JgM36aX$cCNT87E75d(baM~ zZy6@>K??<pNXQf`*?cy=iLw8m#h@NEVb0G8PDCSdb0!>2L}SzOm>G-k9%}Ng;Jc=F zpS*)2wfp1)xbZ$wROcjohF_IDgxtpagcYpa6t#XbUM>`F3wy0xxLHi+@*-DAuVzIi zZKtOyU+5n7bawi>e4~ARqo#k@ZwCBmfvGUJsd%i~;wLqw*XI*M!r$NTkNAZ@5|50H zMUXHZo<1q9K2~`{-WGf*Et)bys#npxO68EQkiv(ivAK_Va6>l+Fnf=ADxaxcQ&3s+ M(j|ID!<A2e0sb1l$^ZZW literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Sigma.x3f b/ExifTool/t/images/Sigma.x3f new file mode 100644 index 0000000000000000000000000000000000000000..32d1cbf23c808d3840d98b7cca09c46755a35b43 GIT binary patch literal 1464 zcmbu9PiRv?5QiseQmcZ8Rzwse;6YT{CNI^dDu&nByd=^zF|VzEKqWP`jWkiP0qa3L zdJyy^C<Sk#c=RYzK`%m!h^L}AE%YFQh<FgeZ#GGrLW>7q_%{1yW@mphyNR0}9rKG{ z!d5sOv7$X4@3Tja9(cN4cs{j>EC)r-GWV*LsnW#cwA$BueEya|uyxC(w$1B#jV-<~ ze`juCe*PtIuk<?_{W16O!Q!0At$6pBGx6a5Vtm8Z|LO1&PD!Y<g*>gA3!T5IO`l`M zYPmcUDwPWp<Dp_<x^SF&>+yd4TcMZ94WCB;J6`wDB5y*BbsOZrsCqQNKCY7(6Y<IH z_q*-&c*oAT4LPdks>biCg`2Tb*Aw84qmmBT0efH{gyAq~Q%}G!oP$d+1FG*f+=Kft z3y<J2JcH-(0$#&gcn2R~5kA6aSc0$c4SvEe_yYm7ZG(2$4SV4rbV3aJAp@gu9tv<7 z(&9;$^%2yo{=6iNWl%=YpT;+YHN{Nil=RC5888tJ@19hpB2!Y537HhVs*c!_;bXIE zL|hY-mZY()db1|S;2Lj;Yy(C`BhF)=Jp0<Bkzuin!U*3{>A~XS%aeK3#CcdO=^~~J zZx@*kNsQA75i^d)aVf|(Mm?1@s}jumq?ec|Yx)@bSRvR?ORg!#B3lHsH!j_9s7v>< zO?5hEuxd|gwoc>q=$Q6z*l1Jrs!aE>Ks%RO)5(Z*ubhBOy|#%EGP`D<T+=Q&*4I=z z_%o=j-MpG@2%p|N)oU|fRTslDU^=QDQw?cODamg7rsEK{M!fc1>(-vw?4f5dOig+} zxKY#-#izZ|dNiW>X6Rmxj5cfZJ|xXbtv|$Fxx%RDr1=|>?wUwF!A7K>k@mk9nM6s7 z_h#!!qet(aur9(#g$bvqzu_SBWzZbyjGf%+TbWy_)p_!IeXf55Pnf@H5y|DVxhWF- E1(oc)i2wiq literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/SigmaDP2.x3f b/ExifTool/t/images/SigmaDP2.x3f new file mode 100644 index 0000000000000000000000000000000000000000..cd1e031b23ffd8d6a03e6ec94f8891d8c7ce4b81 GIT binary patch literal 4364 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 bd_<AF4SwE>$QJtY&xI{H>rA=$P4K?~yd#@9 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Sony.jpg b/ExifTool/t/images/Sony.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b419c83ccb911579b4976767643964b2a1da1f58 GIT binary patch literal 2779 zcmb_e4NR3)7=F(E;a(6gKiU+o%L}$krOC&=lOdU24N*{0b7Pfy6HM!-z@5U>l8NR5 zokJnO)N~|e>C7w&oIlJIo3kv}bRe0LwQgNYSvht7-JbK^?*<uj&h?(X-}{{RocBHN z`T5>+0_}kx!_SXj>X{E@WjzivfIhGY9c)C^<S1ez<tOB5;&94+<XEAHpp+ahjH*0B zY}N1y;zzYUI${TH`%`{P?xzddO1-DtpxWx_=)G1G22uA5+021#_a@Akn)4ixG2`hc zGSeJsz(m@~ctX;lD9-d0M|w({EybBWA|>7F#I!}8!s4tcP#hx^K*yraLVoZtHm?fK z#LLjOYIrGeVnuhvNg8%ZlMN;qC=`c7(QgROu#Ei;8m=K0Rlz=jv({Aw{eK|)R7a>| z?<F=?2le}iP1V7%<f@xBEQbo$a5Qm**8ed3FRwNsF$_3PjCw7e6h#sB-8-bK53cgz zNI<XQ4P0TPhW&Em8%%0EdP1RLb#2}a`k@*+4a=eQJ@jEcu(<~g?|~zFVCgG@+{zdx zXt=lC>P;s@RkusCOQ4i%QmWPEsj%@-*jSf`C^#!tAh=sKl$o5Oj`%on2eDqm+qsnn z;$rGw;qn`a%ZSethY{Z*c5|goTL0%w+#wAo%CJakWC9<e0Mn7X`lia3M%gryyjPWL zvr7V2sXDfM_OEbP?lYEp-e%P;bt|E4^I~O^GSdql(f@%&cE=nUm^TW@O^8fr@wfPu zfD*_}sF>y*78;<<qRb892bZPl93IrZURbz~ZBv1E;gU}L>DfSAThO04w>T4fVCO2o z!$dNU@*}~C$zndv@6EG)x13o~Qk-*0C1#)|_mb-OEeB4v7!Ue$*6vjOIP2D|&$)D> zEcf>&Bkmv{t1#Vo4Q=QL1U~n6Vh!cRz|^Lrlna>BOvWzNU;OD~S-P3aPL&t<i-hyK zuuXoQCFSzi_<`e>bf$Ht#h(_4KdpOHcvqT(A@ukPixt*$=MKIQ%}9r7<%;-M!9sr# zTH#`xqNs-5R*}CUUgNwmrB7Dhn#OKfJbu^qF?XCdf)YclhhV~FDNnjj*8U@9dJ>o{ zLca`D??92d7V;xotZM3)3mGJdh)aFOrY29tm-{<w4Q651&bT`eKtT4(Bwyx#SHs%L zU2>$m$;HuJRBFR_Z2~SVqEQw9z}LvNtQP|1SD6Rqk=FxNo5?hp^D5h7fCDptBTnGl zQlRb&pz=@JB>}bUyS4);`2$!%-k-;t=viQTE6~ENGj{PWev5u=z$111YZHOw6y7)m zJXUc)^C6(~H|ozaZPGT96Y(5l8)v!f6vy65e`jdZhiw~~<43anVtsw`tXZ>?lapt~ zH^&pjI~`8SW=FQ8S*vMnT2DE7_#zC;4Rd|-ExGMqp}}y=pYLxs{NQVH^Q7M&xjmMz zE%Vy@xEfq*(coU>Yw+c}zb_4QExtc{z!%kB(4fp!ZnRXkWVgh%S?<>Z4+PA+uCEXW zo2$)@S3KtPai`$ACa-m1cKRJ${;L)rUu9yiyrk-${CSl)GjV}=Pv%o_ea37_DqU9y z>n00)vE@v7+#ga-)G+M6@hM_PWkL+u!BNVZCRFg8SdE^Ywnoaa-p4$*d339k;sY)^ z+2ZY5*~~0))wsZxKtGX|qedO;$u5(xh5avxNu^QD=HZr&6|uBSx32M(QFe%*N^i?M zU#H&N&x}kX>b^5&FcHve@0KvuLhqk9c$I|SKkJBld;io?AA0|6Bkt|}vx9n}3*N^2 ziFF#5?;c4H@}?P;QBqK_%vStzNkM*Lo@b%WQ;_%4Jll(T#d)Ix-(fZSM@PrS#w86H zkmRrrwmXK~NyA5vkTg1DtV$X8BJogMoNbWJK5(Et)o!z=W~2@sno5IlBgWm!th+66 z7%^t17_(j^LKh?SF(S~4Nujh5oVxsm^~h72mg({?iusQw(1A!jH#J5dgE45t;K13x E0d3#tz5oCK literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Sony.pmp b/ExifTool/t/images/Sony.pmp new file mode 100644 index 0000000000000000000000000000000000000000..653178d3c5e95d571282121260e0dd9051b904de GIT binary patch literal 375 zcmWd-V&D{DU}XS;8X*1+Vkk&4H84H^GQ=4afFu||MHqy{U?Qlrq7=jTBu+*VVM_)E z7!6aQBE~eMfdK;lg8@Y5|Be5*8Cn=b1Oz07B-O>m)s58T)r|DifI!dA6a?&C9KpZ^ zA7B)flvI#WP?L~QGgDJgGjlQ1);0rba58nmrwt+Z{{Vv^2Lli?Gb%AK2{JMZGX6ip r;LX6u2=f7yX2TF+5I|SMAc!II{}uxeGb7MsW<dsfhRqD}|8D{Snyxye literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Text.csv b/ExifTool/t/images/Text.csv new file mode 100644 index 0000000..c638a99 --- /dev/null +++ b/ExifTool/t/images/Text.csv @@ -0,0 +1,3 @@ +SourceFile,Make,Model,ShutterSpeed,Aperture,ISO +t/images/Canon.jpg,Canon,Canon EOS DIGITAL REBEL,4,14.0,100 +t/images/Nikon.jpg,NIKON,E775,1/213,9.4,100 diff --git a/ExifTool/t/images/Text1.txt b/ExifTool/t/images/Text1.txt new file mode 100644 index 0000000..a2f7f17 --- /dev/null +++ b/ExifTool/t/images/Text1.txt @@ -0,0 +1 @@ +this is plain ASCII diff --git a/ExifTool/t/images/Text2.txt b/ExifTool/t/images/Text2.txt new file mode 100644 index 0000000..5347e13 --- /dev/null +++ b/ExifTool/t/images/Text2.txt @@ -0,0 +1 @@ +this é is Latin1 diff --git a/ExifTool/t/images/Text3.txt b/ExifTool/t/images/Text3.txt new file mode 100644 index 0000000..2a3da08 --- /dev/null +++ b/ExifTool/t/images/Text3.txt @@ -0,0 +1 @@ +this é is UTF-8 diff --git a/ExifTool/t/images/Text4.txt b/ExifTool/t/images/Text4.txt new file mode 100644 index 0000000..4a5626e --- /dev/null +++ b/ExifTool/t/images/Text4.txt @@ -0,0 +1 @@ +this Ž is MacRoman \ No newline at end of file diff --git a/ExifTool/t/images/Text5.txt b/ExifTool/t/images/Text5.txt new file mode 100644 index 0000000000000000000000000000000000000000..c9d2c810d48dcdf44f98a038fd5cc09b5ce0bc56 GIT binary patch literal 36 mcmezOpP_^ygCUclm_Y$bhBAaOxH0H57&4eKI5D^~Z~*|Q4F=`_ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Torrent.torrent b/ExifTool/t/images/Torrent.torrent new file mode 100644 index 0000000..b992395 --- /dev/null +++ b/ExifTool/t/images/Torrent.torrent @@ -0,0 +1 @@ +d8:announce35:udp://tracker.bogus.com:80/announce13:announce-listll35:udp://tracker.bogus.com:80/announce36:udp://tracker.bogus2.com:80/announce36:udp://tracker.bogus3.com:80/announceee7:comment32:Test BitTorrent description file10:created by13:uTorrent/184013:creation datei1378481639e8:encoding5:UTF-84:infod5:filesld6:lengthi3700748e4:pathl17:exiftool-9.35.zipeed6:lengthi3662939e4:pathl26:Image-ExifTool-9.35.tar.gzeed6:lengthi2394706e4:pathl17:ExifTool-9.35.dmgeed6:lengthi10848e4:pathl4:docs6:READMEeee4:name19:Image-ExifTool-9.3512:piece lengthi1048576e6:pieces200:¦æ‡[ûš'§OïÕãtš=ÙÖà£aPœÿ"÷á5!“S®:f¹8—Îɯg²æO¹Žké KÝ|á[^åÒâY•Sæ ׌öhò¬/î×èáˆÙ$e 1›4¨á›€§HXg(縵«aL72‰UâðD©â”§ôdUZ&±³Q:ƒ¹XµÞ|y³²x¼ØiCKîÏŽz–#ñö‘»=W¬8Bƒ;šòÅz¡Š]ù#âˆzêçœödº­Âú$2ÆrVþ@l¢De8:url-listl22:http://seed.bogus.com/23:http://seed.bogus2.com/ee \ No newline at end of file diff --git a/ExifTool/t/images/Unknown.jpg b/ExifTool/t/images/Unknown.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b07f77d4adc6e0dce5fa01ee72eac62c61f5d9b0 GIT binary patch literal 7199 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/VCard.ics b/ExifTool/t/images/VCard.ics new file mode 100644 index 0000000..60a7486 --- /dev/null +++ b/ExifTool/t/images/VCard.ics @@ -0,0 +1,94 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +METHOD:PUBLISH +X-WR-CALNAME:Calendar +X-WR-TIMEZONE:America/Toronto +X-APPLE-CALENDAR-COLOR:#CC73E1 +BEGIN:VTIMEZONE +TZID:Canada/Eastern +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +DTSTART:20070311T020000 +TZNAME:EDT +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +DTSTART:20071104T020000 +TZNAME:EST +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VTIMEZONE +TZID:America/Toronto +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +DTSTART:20070311T020000 +TZNAME:EDT +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +DTSTART:20071104T020000 +TZNAME:EST +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20120807T152746Z +UID:5E9B5BEF-C3D9-452C-AB1A-613510B5BA52 +RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO;WKST=SU +DTEND;TZID=Canada/Eastern:20130916T133000 +EXDATE;TZID=Canada/Eastern:20141201T123000 +EXDATE;TZID=Canada/Eastern:20150427T123000 +TRANSP:OPAQUE +SUMMARY:Group meeting +DTSTART;TZID=Canada/Eastern:20130916T123000 +DTSTAMP:20150420T155518Z +LOCATION:261 +SEQUENCE:0 +BEGIN:VALARM +X-WR-ALARMUID:C41775A2-C98F-456C-BB8F-8E6FB15A554F +UID:C41775A2-C98F-456C-BB8F-8E6FB15A554F +TRIGGER:-PT5M +ATTACH;VALUE=URI:Basso +X-APPLE-LOCAL-DEFAULT-ALARM:TRUE +ACTION:AUDIO +X-APPLE-DEFAULT-ALARM:TRUE +END:VALARM +BEGIN:VALARM +X-WR-ALARMUID:70894F68-E6F8-4C00-A737-73D4CDCF0525 +UID:70894F68-E6F8-4C00-A737-73D4CDCF0525 +TRIGGER:-PT5M +X-APPLE-DEFAULT-ALARM:TRUE +ATTACH;VALUE=URI:Basso +ACTION:AUDIO +END:VALARM +END:VEVENT +BEGIN:VEVENT +CREATED:20120807T152746Z +UID:5E9B5BEF-C3D9-452C-AB1A-613510B5BA52 +DTEND;TZID=Canada/Eastern:20131125T133000 +TRANSP:OPAQUE +SUMMARY:Group meeting +DTSTART;TZID=Canada/Eastern:20131125T123000 +DTSTAMP:20150408T142941Z +LOCATION:501 +SEQUENCE:0 +RECURRENCE-ID;TZID=Canada/Eastern:20131125T123000 +BEGIN:VALARM +X-WR-ALARMUID:C514D9BB-BDB3-4A5F-B580-9287459DA35C +UID:C514D9BB-BDB3-4A5F-B580-9287459DA35C +TRIGGER:-PT5M +ATTACH;VALUE=URI:Basso +X-APPLE-LOCAL-DEFAULT-ALARM:TRUE +ACTION:AUDIO +X-APPLE-DEFAULT-ALARM:TRUE +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/ExifTool/t/images/VCard.vcf b/ExifTool/t/images/VCard.vcf new file mode 100644 index 0000000..7248373 --- /dev/null +++ b/ExifTool/t/images/VCard.vcf @@ -0,0 +1,45 @@ +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Apple Inc.//Mac OS X 10.10.2//EN +N:Harvey;Phil;;; +FN:Phil Harvey +ORG:ExifTool; +EMAIL;type=INTERNET;type=HOME;type=pref:someone@home.email +EMAIL;type=INTERNET;type=WORK:someone@work.email +item1.EMAIL;type=INTERNET:someone@other.email +item1.X-ABLabel:_$!<Other>!$_ +TEL;type=CELL;type=VOICE;type=pref:555-0000 +TEL;type=IPHONE;type=CELL;type=VOICE:555-1111 +TEL;type=HOME;type=VOICE:555-2222 +TEL;type=WORK;type=VOICE:555-3333 +TEL;type=MAIN:555-4444 +TEL;type=WORK;type=FAX:555-5555 +TEL;type=PAGER:555-6666 +TEL;type=OTHER;type=VOICE:555-7777 +TEL;type=OTHER;type=VOICE:555-8888 +ADR;type=HOME;type=pref:;;Home St.;City;ON;K0K0K0;Canada +ADR;type=WORK:;;Work St.;City;ON;K0K0K0;Canada +ADR;type=OTHER;GEO="geo:12.3457,78.910";LABEL=Test\nLabel:;;Other Rd.;City;ON;K0K0K0;Canada +NOTE:This is "a"\, comma\, \\backslash\nnewline\nnote! +NOTE;LANGUAGE=fr:Oui! +item2.URL;type=pref:https://exiftool.org/ +item2.X-ABLabel:_$!<HomePage>!$_ +BDAY:1900-01-02 +X-AIM;type=HOME;type=pref:AIMName +item3.X-ICQ;type=pref:ICQName +item3.X-ABLabel:_$!<Other>!$_ +IMPP;X-SERVICE-TYPE=AIM;type=HOME;type=pref:aim:AIMName +IMPP;X-SERVICE-TYPE=Facebook;type=WORK:xmpp:FacebookName +item4.IMPP;X-SERVICE-TYPE=ICQ:aim:ICQName +item4.X-ABLabel:_$!<Other>!$_ +PHOTO;ENCODING=b;TYPE=JPEG:PGR1bW15IGltYWdlIGRhdGE+ +X-ABUID:EBAC8BE8-E695-457F-AE4D-E076ECEEA002:ABPerson +END:VCARD +BEGIN:VCARD +VERSION:3.0 +fn:VCard Test +LABEL;WORK;ENCODING=QUOTED-PRINTABLE:12 Cucumber St.=0D=0ASaladVille ON=0D=0ACanada +LOGO:data:image/jpeg;base64,PGR1bW15IGltYWdlIGRhdGE+ +SOUND;OGG:http://example.com/sound.ogg +note:This sample mixes formats from various vCard versions +END:VCARD diff --git a/ExifTool/t/images/Vorbis.ogg b/ExifTool/t/images/Vorbis.ogg new file mode 100644 index 0000000000000000000000000000000000000000..20d5e6f90c9475996b4e4904afd1355aa135cb51 GIT binary patch literal 5371 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 Nj&<ONKjPok{0E=0?bHAO literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/WPG.wpg b/ExifTool/t/images/WPG.wpg new file mode 100755 index 0000000000000000000000000000000000000000..c6e6d5aafaff87c12e665105627ee572471f4918 GIT binary patch literal 159 zcmew#9^fp%z`(#L#t0-C82H&387ei|ge18cm>BpOn3;hD10xe70}~4)10Ms25DUXE zxmvCWwpy-T?2f$YvW~nUF-Pg^yf4MB^D2v;6{wUvD*zIkByyeCO5i$gG2dB%lR^-& nJG|F<+c>ZD?q@$MAjSg`Q&+TLguo}94jNB5VYV?c2rvKuFN+-^ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/WTV.wtv b/ExifTool/t/images/WTV.wtv new file mode 100644 index 0000000000000000000000000000000000000000..c8577879951ced425bf5fc523dbd41c1799efa77 GIT binary patch literal 7680 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 literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/Writer.jpg b/ExifTool/t/images/Writer.jpg new file mode 100644 index 0000000000000000000000000000000000000000..964260d4ae1ee14ab05d637a78f07ea0576fc46a GIT binary patch literal 251 zcmex=<Ns}j76uUk0ZAcAb#ZZZBQ<$7BRw@B(6ciI0Xr8*FmS;K7)2!|6=W3DBqY?# z)D+arT+Fn!&43!5Or7v)L&*I<z#z!M0EEnpN(@YbjLd?J|Bo<uGcYnTpaM1w5e5Nt cH4K6nBL8nO@Gvt1O=cEkuxHrJApid+0AQ*q_5c6? literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/XMP.inx b/ExifTool/t/images/XMP.inx new file mode 100644 index 0000000..19d9b93 --- /dev/null +++ b/ExifTool/t/images/XMP.inx @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<?aid style="33" type="document" DOMVersion="5.0" readerVersion="4.0" featureSet="257" product="5.0(458)" ?> +<docu InLi="x_0" zero="x_2_U_0_U_0" pacl="o_uaa" DCcp="c_U.S. Web Coated (SWOP) v2" DCrp="c_sRGB IEC61966-2.1" DCci="e_RIcs" DCbi="e_RIcs" DCii="e_RIcs" CSrp="e_CPpp" CScp="e_CPpd" DCsa="b_f" Self="rc_d"> + <lang pnam="rk_English: USA" lsqu="k_‘’" ldqu="k_“â€" pril="rk_English" subl="rk_USA" ID="rl_10d" lhyv="c_Proximity" lspv="c_Proximity" Self="rc_u40"/> + <colr clmd="e_prss" clsp="e_CMYK" clvl="x_4_D_0_D_0_D_0_D_100" ovrd="e_eOvB" clbs="o_n" atcs="e_nasp" atvl="x_0" pnam="c_Black" edbl="b_f" rmbl="b_f" pvis="b_t" swID="l_1f01" Self="rc_ub"/> + <ctbb pvrs="l_0" bbtp="e_bbtc" spcl="y_0" bben="e_eta6" bbmx="x_6_D_1_D_0_D_0_D_1_D_0_D_0" pnam="k_" edbl="b_t" rmbl="b_t" pvis="b_f" swID="l_1f01" Self="rc_u7e"> + <pcnt><![CDATA[AAAAAT/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==]]></pcnt> + </ctbb> + <swch pnam="c_None" edbl="b_f" rmbl="b_f" pvis="b_t" swID="l_1f01" Self="rc_ue"/> + <grad grdt="e_axlg" pnam="k_" edbl="b_t" rmbl="b_t" pvis="b_f" swID="l_1f01" Self="rc_u7c"> + <gstp gcls="o_u7d" loca="D_0" Self="rc_u7cgstp0"/> + </grad> + <StSt strT="rk_" pnam="k_Triple~sep~Stroke" Self="rc_dib01a"/> + <fFam pnam="c_Times" Self="rc_di3e"> + <FonT fFam="c_Times" pnam="c_Times Regular" fPSN="k_Times-Roman" stts="e_fsIn" fSty="c_Regular" fnTp="e_ftTT" WSCR="l_0" fnam="c_Times Roman" fFUN="c_Times Roman" fStN="k_Regular" fPLT="k_" vers="c_6.0d6e5" Self="rc_di3eFonTnTimes Regular"/> + </fFam> + <Cset pnam="k_Styles for Word/RTF Imported Lists" Self="rc_u236"/> + <csty pnam="k_[No character style]" smpt="b_f" Self="rc_u68"/> + <cMep Self="rc_dcMep1"> + <pcnt><![CDATA[<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> +<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.0-c006 1.236519, Wed Jun 14 2006 08:31:24"> + <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <rdf:Description rdf:about="" + xmlns:xapMM="http://ns.adobe.com/xap/1.0/mm/" + xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" + xmlns:stMfs="http://ns.adobe.com/xap/1.0/sType/ManifestItem#"> + <xapMM:InstanceID>beb2441b-dd64-11df-b20e-e65882d9428e</xapMM:InstanceID> + <xapMM:DerivedFrom rdf:parseType="Resource"> + <stRef:instanceID>f0d208df-dc56-11df-95ac-e273561c7691</stRef:instanceID> + <stRef:documentID>adobe:docid:indd:0419e6d7-167b-11df-8afc-dcb7ca988d92</stRef:documentID> + </xapMM:DerivedFrom> + <xapMM:DocumentID>adobe:docid:indd:f0d208e0-dc56-11df-95ac-e273561c7691</xapMM:DocumentID> + <xapMM:RenditionClass>default</xapMM:RenditionClass> + <xapMM:Manifest> + <rdf:Bag> + <rdf:li rdf:parseType="Resource"> + <stMfs:linkForm>ReferenceStream</stMfs:linkForm> + <stMfs:reference rdf:parseType="Resource"> + <stRef:instanceID>uuid:9CC7E7EDDC5811DF8C4BC09357E49DC9</stRef:instanceID> + <stRef:documentID>uuid:9CC7E7ECDC5811DF8C4BC09357E49DC9</stRef:documentID> + </stMfs:reference> + <xapMM:placedXResolution>72.00</xapMM:placedXResolution> + <xapMM:placedYResolution>72.00</xapMM:placedYResolution> + <xapMM:placedResolutionUnit>Inches</xapMM:placedResolutionUnit> + </rdf:li> + </rdf:Bag> + </xapMM:Manifest> + </rdf:Description> + <rdf:Description rdf:about="" + xmlns:xap="http://ns.adobe.com/xap/1.0/" + xmlns:xapGImg="http://ns.adobe.com/xap/1.0/g/img/"> + <xap:CreateDate>2010-10-18T14:59:13Z</xap:CreateDate> + <xap:ModifyDate>2010-10-19T22:53:33Z</xap:ModifyDate> + <xap:MetadataDate>2010-10-19T22:53:33Z</xap:MetadataDate> + <xap:CreatorTool>Adobe InDesign 5.0</xap:CreatorTool> + <xap:Thumbnails> + <rdf:Alt> + <rdf:li rdf:parseType="Resource"> + <xapGImg:format>JPEG</xapGImg:format> + <xapGImg:width>1024</xapGImg:width> + <xapGImg:height>1024</xapGImg:height></xapGImg:image> + </rdf:li> + </rdf:Alt> + </xap:Thumbnails> + </rdf:Description> + <rdf:Description rdf:about="" + xmlns:xapTPg="http://ns.adobe.com/xap/1.0/t/pg/" + xmlns:xapG="http://ns.adobe.com/xap/1.0/g/" + xmlns:stFnt="http://ns.adobe.com/xap/1.0/sType/Font#"> + <xapTPg:Colorants> + <rdf:Seq> + <rdf:li rdf:parseType="Resource"> + <xapG:swatchName>Black</xapG:swatchName> + <xapG:mode>CMYK</xapG:mode> + <xapG:type>Process</xapG:type> + <xapG:cyan>0</xapG:cyan> + <xapG:magenta>0</xapG:magenta> + <xapG:yellow>0</xapG:yellow> + <xapG:black>100</xapG:black> + </rdf:li> + </rdf:Seq> + </xapTPg:Colorants> + <xapTPg:Fonts> + <rdf:Bag> + <rdf:li rdf:parseType="Resource"> + <stFnt:fontName>Times-Roman</stFnt:fontName> + <stFnt:fontFamily>Times</stFnt:fontFamily> + <stFnt:fontFace>Regular</stFnt:fontFace> + <stFnt:fontType>TrueType</stFnt:fontType> + <stFnt:versionString>Times-Roman6.0d6e5</stFnt:versionString> + <stFnt:composite>false</stFnt:composite> + <stFnt:fontFileName>Times.dfont</stFnt:fontFileName> + </rdf:li> + </rdf:li> + </rdf:Bag> + </xapTPg:Fonts> + </rdf:Description> + <rdf:Description rdf:about="" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <dc:format>application/x-indesign</dc:format> + </rdf:Description> + </rdf:RDF> +</x:xmpmeta> +<?xpacket end="r"?>]]></pcnt> + </cMep> + <cOpt tlnm="k_Index" tlsl="o_u67" ReIx="b_t" iBkD="b_f" iHEs="b_f" IdxF="e_NtIf" iSHs="b_t" iEiS="b_f" lOst="o_u67" lTws="o_u67" lThs="o_u67" lFst="o_u67" sHst="o_u67" pnSt="o_u68" crSt="o_u68" crTS="o_u68" ftSp="c_ " beSp="c_; " prSp="c_^=" pnSp="c_, " crSp="c_. " eeSp="c_" Self="rc_dcOpt1"/> + <layr pnam="c_Layer 2" pvis="b_f" plck="b_t" lcol="e_iRed" iwrp="b_f" sogd="b_t" lkgd="b_f" uilr="b_t" exlr="b_t" prta="b_t" Self="rc_u146"/> +</docu> diff --git a/ExifTool/t/images/XMP.jpg b/ExifTool/t/images/XMP.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ec2f5a1c7b6fb72d949e6f4bcfaea510d8ef8057 GIT binary patch literal 10314 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 a;K8v&OWP%6!y<q)%Mo6$wj%GgZ~hNgjcw%s literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/XMP.svg b/ExifTool/t/images/XMP.svg new file mode 100644 index 0000000..3c3d094 --- /dev/null +++ b/ExifTool/t/images/XMP.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" standalone="yes"?> +<svg width="4in" height="3in" version="1.1" + xmlns = 'http://www.w3.org/2000/svg'> + <desc xmlns:myfoo="http://example.org/myfoo"> + <myfoo:title>This is a financial report</myfoo:title> + <myfoo:descr>The global description uses markup from the + <myfoo:emph>myfoo</myfoo:emph> namespace.</myfoo:descr> + <myfoo:scene><myfoo:what>widget $growth</myfoo:what> + <myfoo:contains>$three $graph-bar</myfoo:contains> + <myfoo:when>1998 $through 2000</myfoo:when> </myfoo:scene> + </desc> + <metadata> + <rdf:RDF + xmlns:rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:rdfs = "http://www.w3.org/2000/01/rdf-schema#" + xmlns:dc = "http://purl.org/dc/elements/1.1/" > + <rdf:Description about="http://example.org/myfoo" + dc:title="MyFoo Financial Report" + dc:description="$three $bar $thousands $dollars $from 1998 $through 2000" + dc:publisher="Example Organization" + dc:date="2000-04-11" + dc:format="image/svg+xml" + dc:language="en" > + <dc:creator> + <rdf:Bag> + <rdf:li>Irving Bird</rdf:li> + <rdf:li>Mary Lambert</rdf:li> + </rdf:Bag> + </dc:creator> + </rdf:Description> + </rdf:RDF> + </metadata> +</svg> diff --git a/ExifTool/t/images/XMP.xml b/ExifTool/t/images/XMP.xml new file mode 100644 index 0000000..35f00c6 --- /dev/null +++ b/ExifTool/t/images/XMP.xml @@ -0,0 +1,101 @@ +<?xml version='1.0' encoding='UTF-8'?> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + +<rdf:Description rdf:about='t/images/Nikon.jpg' + xmlns:et='http://ns.exiftool.org/1.0/' et:toolkit='Image::ExifTool 12.61' + xmlns:ExifTool='http://ns.exiftool.org/ExifTool/1.0/' + xmlns:System='http://ns.exiftool.org/File/System/1.0/' + xmlns:File='http://ns.exiftool.org/File/1.0/' + xmlns:IFD0='http://ns.exiftool.org/EXIF/IFD0/1.0/' + xmlns:ExifIFD='http://ns.exiftool.org/EXIF/ExifIFD/1.0/' + xmlns:Nikon='http://ns.exiftool.org/MakerNotes/Nikon/1.0/' + xmlns:PrintIM='http://ns.exiftool.org/PrintIM/PrintIM/1.0/' + xmlns:InteropIFD='http://ns.exiftool.org/EXIF/InteropIFD/1.0/' + xmlns:IFD1='http://ns.exiftool.org/EXIF/IFD1/1.0/' + xmlns:Composite='http://ns.exiftool.org/Composite/1.0/'> + <ExifTool:ExifToolVersion et:id='ExifToolVersion'>12.61</ExifTool:ExifToolVersion> + <System:FileName et:id='FileName'>Nikon.jpg</System:FileName> + <System:Directory et:id='Directory'>t/images</System:Directory> + <System:FileSize et:id='FileSize'>1703 bytes</System:FileSize> + <System:FileModifyDate et:id='FileModifyDate'>2006:01:04 14:02:27-05:00</System:FileModifyDate> + <System:FileAccessDate et:id='FileAccessDate'>2023:04:22 14:07:35-04:00</System:FileAccessDate> + <System:FileInodeChangeDate et:id='FileInodeChangeDate'>2023:02:21 15:57:49-05:00</System:FileInodeChangeDate> + <System:FilePermissions et:id='FilePermissions'>-rw-r--r--</System:FilePermissions> + <File:FileType et:id='FileType'>JPEG</File:FileType> + <File:FileTypeExtension et:id='FileTypeExtension'>jpg</File:FileTypeExtension> + <File:MIMEType et:id='MIMEType'>image/jpeg</File:MIMEType> + <File:ExifByteOrder et:id='ExifByteOrder'>Little-endian (Intel, II)</File:ExifByteOrder> + <File:ImageWidth et:id='ImageWidth'>8</File:ImageWidth> + <File:ImageHeight et:id='ImageHeight'>8</File:ImageHeight> + <File:EncodingProcess et:id='EncodingProcess'>Baseline DCT, Huffman coding</File:EncodingProcess> + <File:BitsPerSample et:id='BitsPerSample'>8</File:BitsPerSample> + <File:ColorComponents et:id='ColorComponents'>3</File:ColorComponents> + <File:YCbCrSubSampling et:id='YCbCrSubSampling'>YCbCr4:2:0 (2 2)</File:YCbCrSubSampling> + <IFD0:ImageDescription et:id='270'> </IFD0:ImageDescription> + <IFD0:Make et:id='271'>NIKON</IFD0:Make> + <IFD0:Model et:id='272'>E775</IFD0:Model> + <IFD0:Orientation et:id='274'>Horizontal (normal)</IFD0:Orientation> + <IFD0:XResolution et:id='282'>300</IFD0:XResolution> + <IFD0:YResolution et:id='283'>300</IFD0:YResolution> + <IFD0:ResolutionUnit et:id='296'>inches</IFD0:ResolutionUnit> + <IFD0:Software et:id='305'>E775v1.3u</IFD0:Software> + <IFD0:ModifyDate et:id='306'>2001:08:01 12:57:23</IFD0:ModifyDate> + <IFD0:YCbCrPositioning et:id='531'>Co-sited</IFD0:YCbCrPositioning> + <ExifIFD:ExposureTime et:id='33434'>1/213</ExifIFD:ExposureTime> + <ExifIFD:FNumber et:id='33437'>9.4</ExifIFD:FNumber> + <ExifIFD:ExposureProgram et:id='34850'>Program AE</ExifIFD:ExposureProgram> + <ExifIFD:ISO et:id='34855'>100</ExifIFD:ISO> + <ExifIFD:ExifVersion et:id='36864'>0210</ExifIFD:ExifVersion> + <ExifIFD:DateTimeOriginal et:id='36867'>2001:08:01 12:57:23</ExifIFD:DateTimeOriginal> + <ExifIFD:CreateDate et:id='36868'>2001:08:01 12:57:23</ExifIFD:CreateDate> + <ExifIFD:ComponentsConfiguration et:id='37121'>Y, Cb, Cr, -</ExifIFD:ComponentsConfiguration> + <ExifIFD:CompressedBitsPerPixel et:id='37122'>3</ExifIFD:CompressedBitsPerPixel> + <ExifIFD:ExposureCompensation et:id='37380'>0</ExifIFD:ExposureCompensation> + <ExifIFD:MaxApertureValue et:id='37381'>3.4</ExifIFD:MaxApertureValue> + <ExifIFD:MeteringMode et:id='37383'>Multi-segment</ExifIFD:MeteringMode> + <ExifIFD:LightSource et:id='37384'>Unknown</ExifIFD:LightSource> + <ExifIFD:Flash et:id='37385'>No Flash</ExifIFD:Flash> + <ExifIFD:FocalLength et:id='37386'>8.6 mm</ExifIFD:FocalLength> + <ExifIFD:UserComment et:id='37510'></ExifIFD:UserComment> + <ExifIFD:FlashpixVersion et:id='40960'>0100</ExifIFD:FlashpixVersion> + <ExifIFD:ColorSpace et:id='40961'>sRGB</ExifIFD:ColorSpace> + <ExifIFD:ExifImageWidth et:id='40962'>1600</ExifIFD:ExifImageWidth> + <ExifIFD:ExifImageHeight et:id='40963'>1200</ExifIFD:ExifImageHeight> + <ExifIFD:FileSource et:id='41728'>Digital Camera</ExifIFD:FileSource> + <ExifIFD:SceneType et:id='41729'>Directly photographed</ExifIFD:SceneType> + <Nikon:MakerNoteVersion et:id='1'>1.00</Nikon:MakerNoteVersion> + <Nikon:ISO et:id='2'>0</Nikon:ISO> + <Nikon:ColorMode et:id='3'>Color</Nikon:ColorMode> + <Nikon:Quality et:id='4'>Fine</Nikon:Quality> + <Nikon:WhiteBalance et:id='5'>Auto</Nikon:WhiteBalance> + <Nikon:Sharpness et:id='6'>Auto</Nikon:Sharpness> + <Nikon:FocusMode et:id='7'>AF-C</Nikon:FocusMode> + <Nikon:FlashSetting et:id='8'></Nikon:FlashSetting> + <Nikon:ISOSelection et:id='15'>Auto</Nikon:ISOSelection> + <Nikon:ImageAdjustment et:id='128'>Normal</Nikon:ImageAdjustment> + <Nikon:AuxiliaryLens et:id='130'>Off</Nikon:AuxiliaryLens> + <Nikon:ManualFocusDistance et:id='133'>undef</Nikon:ManualFocusDistance> + <Nikon:DigitalZoom et:id='134'>1</Nikon:DigitalZoom> + <Nikon:AFAreaMode et:id='0'>Single Area</Nikon:AFAreaMode> + <Nikon:AFPoint et:id='1'>Center</Nikon:AFPoint> + <Nikon:AFPointsInFocus et:id='2'>(none)</Nikon:AFPointsInFocus> + <Nikon:SceneMode et:id='143'></Nikon:SceneMode> + <Nikon:DataDump et:id='16'>(Binary data 122 bytes, use -b option to extract)</Nikon:DataDump> + <PrintIM:PrintIMVersion et:id='PrintIMVersion'>0100</PrintIM:PrintIMVersion> + <InteropIFD:InteropIndex et:id='1'>R98 - DCF basic file (sRGB)</InteropIFD:InteropIndex> + <InteropIFD:InteropVersion et:id='2'>0100</InteropIFD:InteropVersion> + <IFD1:Compression et:id='259'>JPEG (old-style)</IFD1:Compression> + <IFD1:XResolution et:id='282'>300</IFD1:XResolution> + <IFD1:YResolution et:id='283'>300</IFD1:YResolution> + <IFD1:ResolutionUnit et:id='296'>inches</IFD1:ResolutionUnit> + <IFD1:ThumbnailOffset et:id='513'>1426</IFD1:ThumbnailOffset> + <IFD1:ThumbnailLength et:id='514'>28</IFD1:ThumbnailLength> + <IFD1:ThumbnailImage et:id='Exif-ThumbnailImage'>(Binary data 28 bytes, use -b option to extract)</IFD1:ThumbnailImage> + <Composite:Aperture et:id='Exif-Aperture'>9.4</Composite:Aperture> + <Composite:ImageSize et:id='Exif-ImageSize'>8x8</Composite:ImageSize> + <Composite:Megapixels et:id='Exif-Megapixels'>0.000064</Composite:Megapixels> + <Composite:ShutterSpeed et:id='Exif-ShutterSpeed'>1/213</Composite:ShutterSpeed> + <Composite:FocalLength35efl et:id='Exif-FocalLength35efl'>8.6 mm</Composite:FocalLength35efl> + <Composite:LightValue et:id='Exif-LightValue'>14.2</Composite:LightValue> +</rdf:Description> +</rdf:RDF> diff --git a/ExifTool/t/images/XMP.xmp b/ExifTool/t/images/XMP.xmp new file mode 100644 index 0000000..288ba55 --- /dev/null +++ b/ExifTool/t/images/XMP.xmp @@ -0,0 +1,99 @@ +<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> +<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="3.1.1-111"> + <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <rdf:Description rdf:about="" + xmlns:exif="http://ns.adobe.com/exif/1.0/" + xmlns:tiff="http://ns.adobe.com/tiff/1.0/" + xmlns:xap="http://ns.adobe.com/xap/1.0/" + xmlns:xapMM="http://ns.adobe.com/xap/1.0/mm/" + xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" + exif:ExifVersion="0.2.2.0" + exif:FlashpixVersion="0.1.0.0" + exif:ColorSpace="1" + exif:CompressedBitsPerPixel="3/1" + exif:PixelXDimension="1136" + exif:PixelYDimension="852" + exif:DateTimeOriginal="2005-06-09T20:09:27+02:00" + exif:DateTimeDigitized="2005-06-09T20:09:27+02:00" + exif:ExposureTime="4/10" + exif:FNumber="28/10" + exif:ShutterSpeedValue="42/32" + exif:ApertureValue="95/32" + exif:ExposureBiasValue="-3/3" + exif:MaxApertureValue="95/32" + exif:MeteringMode="5" + exif:FocalLength="5800/1000" + exif:FocalPlaneXResolution="2272000/224" + exif:FocalPlaneYResolution="1704000/168" + exif:FocalPlaneResolutionUnit="2" + exif:SensingMethod="2" + exif:FileSource="3" + exif:CustomRendered="0" + exif:ExposureMode="1" + exif:WhiteBalance="0" + exif:DigitalZoomRatio="2272/2272" + exif:SceneCaptureType="0" + exif:NativeDigest="36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;CE459FE772DF591DDF5F63CA3F2A087B" + tiff:Make="Canon" + tiff:Model="Canon DIGITAL IXUS 40" + tiff:Orientation="1" + tiff:XResolution="1800000/10000" + tiff:YResolution="1800000/10000" + tiff:ResolutionUnit="2" + tiff:YCbCrPositioning="1" + tiff:ImageWidth="1136" + tiff:ImageLength="852" + tiff:NativeDigest="256,257,258,259,262,274,277,284,530,531,282,283,296,301,318,319,529,532,306,270,271,272,305,315,33432;D11E7FC551E751BE9E616260834B2A1D" + xap:CreateDate="2005-11-21T17:07:14+01:00" + xap:ModifyDate="2005-11-21T17:07:14+01:00" + xap:MetadataDate="2005-11-21T17:11:31+01:00" + xap:CreatorTool="Adobe Photoshop CS2 Windows" + xap:Rating="1" + xapMM:DocumentID="uuid:7A9636BAA85ADA11B611C7FA524F1F71" + xapMM:InstanceID="uuid:7B9636BAA85ADA11B611C7FA524F1F71" + dc:format="image/jpeg" + photoshop:ColorMode="3" + photoshop:ICCProfile="sRGB IEC61966-2.1" + photoshop:History=""> + <dc:rights> + <rdf:Alt> + <rdf:li xml:lang='x-default'>© Copyright Phil Harvey</rdf:li> + <rdf:li xml:lang='fr'>© Droit d'auteur Phil Harvey</rdf:li> + </rdf:Alt> + </dc:rights> + <dc:subject> + <rdf:Bag> + <rdf:li>test1</rdf:li> + <rdf:li>&amp;-<![CDATA[&amp;]]>-&amp;</rdf:li> + </rdf:Bag> + </dc:subject> + <exif:ComponentsConfiguration> + <rdf:Seq> + <rdf:li>1</rdf:li> + <rdf:li>2</rdf:li> + <rdf:li>3</rdf:li> + <rdf:li>0</rdf:li> + </rdf:Seq> + </exif:ComponentsConfiguration> + <exif:Flash + exif:Fired="False" + exif:Return="0" + exif:Mode="2" + exif:Function="False" + exif:RedEyeMode="False"/> + <tiff:BitsPerSample> + <rdf:Seq> + <rdf:li>8</rdf:li> + <rdf:li>8</rdf:li> + <rdf:li>8</rdf:li> + </rdf:Seq> + </tiff:BitsPerSample> + <xapMM:DerivedFrom + stRef:instanceID="adobe:docid:photoshop:f481cc67-d8f5-11d9-a31e-a1606d941d83" + stRef:documentID="adobe:docid:photoshop:f481cc67-d8f5-11d9-a31e-a1606d941d83"/> + </rdf:Description> + </rdf:RDF> +</x:xmpmeta> +<?xpacket end="w"?> \ No newline at end of file diff --git a/ExifTool/t/images/XMP2.xmp b/ExifTool/t/images/XMP2.xmp new file mode 100644 index 0000000..92ca23c --- /dev/null +++ b/ExifTool/t/images/XMP2.xmp @@ -0,0 +1,25 @@ +<?xpacket begin="" id="testing"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF +xmlns:dc="http://purl.org/dc/elements/1.1/" +xmlns:tiff="http://ns.adobe.com/tiff/1.0/" +xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" +xmlns:aux="http://ns.adobe.com/exif/1.0/aux/"> +<rdf:Description rdf:nodeID="anon0"> + <aux:Lens>18.0 - 55.0mm</aux:Lens> + <aux:SerialNumber>0123456789</aux:SerialNumber> +</rdf:Description> +<rdf:Description rdf:nodeID="anon1"> + <tiff:ImageLength>2048</tiff:ImageLength> + <tiff:ImageWidth>3072</tiff:ImageWidth> +</rdf:Description> +<rdf:Bag rdf:nodeID="anon2"> + <rdf:li>ExifTool</rdf:li> + <rdf:li>XMP</rdf:li> + <rdf:li>nodeID</rdf:li> + <rdf:li>test</rdf:li> +</rdf:Bag> +<rdf:Description rdf:about="" + dc:title="XMP nodeID test (XMP written by f-spot)"> + <dc:subject rdf:nodeID="anon2" /> +</rdf:Description> +</rdf:RDF> +</x:xmpmeta><?xpacket end="r"?> \ No newline at end of file diff --git a/ExifTool/t/images/XMP3.xmp b/ExifTool/t/images/XMP3.xmp new file mode 100644 index 0000000..5737dd8 --- /dev/null +++ b/ExifTool/t/images/XMP3.xmp @@ -0,0 +1,69 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 6.32'> +<!-- +** Note that rdf:NodeID is not allowed in XMP, so this not strictly +** an XMP sample, but a more general RDF/XML sample! +--> +<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:dd="http://purl.org/dc/elements/1.1/" + xmlns:ph="https://exiftool.org/ph/1.0/" + xmlns:xxx="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/" + xmlns:pdfx="http://ns.adobe.com/pdfx/1.3/" + xmlns:stMfs='http://ns.adobe.com/xap/1.0/sType/ManifestItem#' + xmlns:stRef='http://ns.adobe.com/xap/1.0/sType/ResourceRef#' + xmlns:xmpMM='http://ns.adobe.com/xap/1.0/mm/' + xmlns:cc='http://creativecommons.org/ns#'> + <rdf:Description rdf:about="http://www.w3.org/TR/rdf-syntax-grammar" + dd:title="ExifTool common node, nonstandard namespace prefix, and bad list type tests"> + <dd:creator> + <rdf:Bag> + <rdf:li>bad_list_type_A</rdf:li> + <rdf:li>bad_list_type_B</rdf:li> + </rdf:Bag> + </dd:creator> + <ph:supervisor> + <rdf:Description rdf:nodeID="abc" ph:food="cookies"> + <ph:exclamation>doh!</ph:exclamation> + </rdf:Description> + </ph:supervisor> + <ph:programmer> + <rdf:Description rdf:nodeID="abc"> + <ph:state>confusion</ph:state> + </rdf:Description> + </ph:programmer> + <ph:tester rdf:nodeID="abc"/> + <xxx:CountryCode>CA</xxx:CountryCode> + <xxx:Scene> + <rdf:Bag> + <rdf:li>scene1</rdf:li> + </rdf:Bag> + </xxx:Scene> + <pdfx:Customↂ0020Propertyↂ00201>a custom property value</pdfx:Customↂ0020Propertyↂ00201> + <pdfx:Customↂ0020Propertyↂ00202>some other value</pdfx:Customↂ0020Propertyↂ00202> + </rdf:Description> + + <rdf:Description rdf:nodeID="abc" + ph:fullName="Phil Harvey"> + <ph:homePage rdf:resource="https://exiftool.org/"/> + </rdf:Description> + + <rdf:Description> + <xmpMM:Manifest> + <rdf:Seq> + <rdf:li rdf:parseType='Resource'> + <stMfs:linkForm>EmbedByReference</stMfs:linkForm> + <stMfs:reference rdf:parseType='Resource'> + <stRef:filePath>C:\some path\file.ext</stRef:filePath> + </stMfs:reference> + </rdf:li> + </rdf:Seq> + </xmpMM:Manifest> + <cc:attributionName> + <rdf:Alt> + <rdf:li xml:lang='x-default'>some attr</rdf:li> + </rdf:Alt> + </cc:attributionName> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/images/XMP4.xmp b/ExifTool/t/images/XMP4.xmp new file mode 100644 index 0000000..7a925dc --- /dev/null +++ b/ExifTool/t/images/XMP4.xmp @@ -0,0 +1,93 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.77'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'> + <Iptc4xmpExt:LocationCreated> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:City>one</Iptc4xmpExt:City> + <Iptc4xmpExt:CountryCode>1</Iptc4xmpExt:CountryCode> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:City>two</Iptc4xmpExt:City> + <Iptc4xmpExt:CountryCode>2</Iptc4xmpExt:CountryCode> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:City>three</Iptc4xmpExt:City> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:CountryCode>4</Iptc4xmpExt:CountryCode> + </rdf:li> + </rdf:Bag> + </Iptc4xmpExt:LocationCreated> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:dc='http://purl.org/dc/elements/1.1/'> + <dc:contributor> + <rdf:Bag> + <rdf:li>me</rdf:li> + </rdf:Bag> + </dc:contributor> + <dc:subject> + <rdf:Bag> + <rdf:li>one</rdf:li> + <rdf:li>two</rdf:li> + </rdf:Bag> + </dc:subject> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:test='http://ns.test.com/'> + <test:StructList2> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <test:Item1>c1-1</test:Item1> + <test:Item2>c2-1</test:Item2> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <test:Item1>c1-2</test:Item1> + <test:Item2>c2-2</test:Item2> + <test:TestList1> + <rdf:Bag> + <rdf:li>x1</rdf:li> + </rdf:Bag> + </test:TestList1> + <test:TestList2> + <rdf:Bag> + <rdf:li>y1</rdf:li> + <rdf:li>y2</rdf:li> + </rdf:Bag> + </test:TestList2> + </rdf:li> + </rdf:Bag> + </test:StructList2> + <test:StructList1> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <test:Item1>b1</test:Item1> + <test:Item2>b2</test:Item2> + </rdf:li> + </rdf:Bag> + </test:StructList1> + <test:BareStruct rdf:parseType='Resource'> + <test:Item1>a1</test:Item1> + <test:Item2>a2</test:Item2> + </test:BareStruct> + <test:BareList2> + <rdf:Bag> + <rdf:li>b1</rdf:li> + <rdf:li>b2</rdf:li> + </rdf:Bag> + </test:BareList2> + <test:BareList1> + <rdf:Bag> + <rdf:li>a1</rdf:li> + </rdf:Bag> + </test:BareList1> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/images/XMP5.xmp b/ExifTool/t/images/XMP5.xmp new file mode 100644 index 0000000..9566701 --- /dev/null +++ b/ExifTool/t/images/XMP5.xmp @@ -0,0 +1,157 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 8.82'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'> + <Iptc4xmpExt:ArtworkOrObject> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:AOTitle> + <rdf:Alt> + <rdf:li xml:lang='x-default'>test</rdf:li> + <rdf:li xml:lang='de'>prüfung</rdf:li> + <rdf:li xml:lang='fr'>épreuve</rdf:li> + </rdf:Alt> + </Iptc4xmpExt:AOTitle> + </rdf:li> + </rdf:Bag> + </Iptc4xmpExt:ArtworkOrObject> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:exif='http://ns.adobe.com/exif/1.0/'> + <exif:Flash rdf:parseType='Resource'> + <exif:Mode>2</exif:Mode> + <exif:Return>0</exif:Return> + </exif:Flash> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:mwg-kw='http://www.metadataworkinggroup.com/schemas/keywords/'> + <mwg-kw:Keywords rdf:parseType='Resource'> + <mwg-kw:Hierarchy> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <mwg-kw:Children> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <mwg-kw:Keyword>A-2</mwg-kw:Keyword> + </rdf:li> + </rdf:Bag> + </mwg-kw:Children> + <mwg-kw:Keyword>A-1</mwg-kw:Keyword> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <mwg-kw:Children> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <mwg-kw:Keyword>B-2</mwg-kw:Keyword> + </rdf:li> + </rdf:Bag> + </mwg-kw:Children> + <mwg-kw:Keyword>B-1</mwg-kw:Keyword> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <mwg-kw:Keyword>C-1</mwg-kw:Keyword> + </rdf:li> + </rdf:Bag> + </mwg-kw:Hierarchy> + </mwg-kw:Keywords> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/' + xmlns:exif='http://ns.adobe.com/exif/1.0/' + xmlns:mwg-rs='http://www.metadataworkinggroup.com/schemas/regions/' + xmlns:myXMPns='http://ns.exiftool.org/t/XMP.t' + xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#' + xmlns:stArea='http://ns.adobe.com/xmp/sType/Area#' + xmlns:xmpRights='http://ns.adobe.com/xap/1.0/rights/'> + <mwg-rs:Regions rdf:parseType='Resource'> + <mwg-rs:RegionList> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <mwg-rs:Area rdf:parseType='Resource'> + <stArea:h>8</stArea:h> + <stArea:w>8</stArea:w> + <stArea:x>0</stArea:x> + <stArea:y>0</stArea:y> + </mwg-rs:Area> + <mwg-rs:Name>Region 1</mwg-rs:Name> + <mwg-rs:Type>Face</mwg-rs:Type> + <rdfs:seeAlso rdf:resource='plus:Licensee'/> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <mwg-rs:Extensions rdf:parseType='Resource'> + <Iptc4xmpExt:ArtworkOrObject> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:AOTitle> + <rdf:Alt> + <rdf:li xml:lang='de'>verfänglich</rdf:li> + </rdf:Alt> + </Iptc4xmpExt:AOTitle> + </rdf:li> + </rdf:Bag> + </Iptc4xmpExt:ArtworkOrObject> + <exif:Flash rdf:parseType='Resource'> + <exif:Mode>1</exif:Mode> + <exif:Return>2</exif:Return> + </exif:Flash> + <myXMPns:BTestTag rdf:parseType='Resource'> + <rdf:type rdf:resource='myXMPns:SomeFunnyType'/> + <myXMPns:Field1> + <rdf:Bag> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>this is wild</rdf:li> + </rdf:Alt> + </rdf:li> + </rdf:Bag> + </myXMPns:Field1> + </myXMPns:BTestTag> + <xmpRights:UsageTerms> + <rdf:Alt> + <rdf:li xml:lang='fr'>libre</rdf:li> + </rdf:Alt> + </xmpRights:UsageTerms> + </mwg-rs:Extensions> + </rdf:li> + </rdf:Bag> + </mwg-rs:RegionList> + </mwg-rs:Regions> + <myXMPns:BTestTag rdf:parseType='Resource'> + <rdf:type rdf:resource='myXMPns:SomeFunnyType'/> + <myXMPns:Field1> + <rdf:Bag> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='en-CA'>eh?</rdf:li> + <rdf:li xml:lang='en-US'>huh?</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='en-US'>groovy</rdf:li> + <rdf:li xml:lang='fr'>ingénieux</rdf:li> + </rdf:Alt> + </rdf:li> + </rdf:Bag> + </myXMPns:Field1> + </myXMPns:BTestTag> + </rdf:Description> + + <rdf:Description rdf:about='' + xmlns:plus='http://ns.useplus.org/ldf/xmp/1.0/'> + <plus:Licensee> + <rdf:Seq> + <rdf:li rdf:parseType='Resource'> + <plus:LicenseeName>Phil</plus:LicenseeName> + </rdf:li> + </rdf:Seq> + </plus:Licensee> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/images/XMP6.xmp b/ExifTool/t/images/XMP6.xmp new file mode 100644 index 0000000..9fd0c20 --- /dev/null +++ b/ExifTool/t/images/XMP6.xmp @@ -0,0 +1,48 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<!-- using "xmp:xmpmeta" instead of the usual "x:xmpmeta" --> +<xmp:xmpmeta xmlns:xmp='adobe:ns:meta/' xmp:xmptk='Image::ExifTool 9.97'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <!-- a non-standard namespace --> + <rdf:Description rdf:about='' + xmlns:xxxx='http://testtag.com/fish/1.0/'> + <xxxx:Test>trout</xxxx:Test> + </rdf:Description> + + <!-- a non-standard namespace using an already defined non-standard prefix --> + <rdf:Description rdf:about='' + xmlns:xxxx='http://testtag.com/feline/1.0/'> + <xxxx:Test>tabby</xxxx:Test> + </rdf:Description> + + <!-- a standard namespace using the proper (but already defined) standard prefix --> + <rdf:Description rdf:about='' + xmlns:xmp='http://ns.adobe.com/xap/1.0/'> + <xmp:Rating>2</xmp:Rating> + </rdf:Description> + + <!-- another standard namespace using the same prefix --> + <rdf:Description rdf:about='' + xmlns:xmp='http://purl.org/dc/elements/1.1/'> + <xmp:subject> + <rdf:Bag> + <rdf:li>x</rdf:li> + </rdf:Bag> + </xmp:subject> + </rdf:Description> + + <!-- an already-used standard namespace with a different prefix and a non-standard rdf prefix --> + <!-- (note: ExifTool currently processes attributes in order, even though the scope of the xmlns + should be from the beginning of the element, which is why "z:about" won't yet work below) --> + <z:Description rdf:about='' + xmlns:y='http://purl.org/dc/elements/1.1/' + xmlns:z='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + <y:title> + <z:Alt> + <z:li xml:lang='x-default'>a title</z:li> + </z:Alt> + </y:title> + </z:Description> +</rdf:RDF> +</xmp:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/images/XMP7.xmp b/ExifTool/t/images/XMP7.xmp new file mode 100644 index 0000000..328f500 --- /dev/null +++ b/ExifTool/t/images/XMP7.xmp @@ -0,0 +1,26 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 11.57'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'> + <Iptc4xmpExt:LocationShown> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:City>London</Iptc4xmpExt:City> + <Iptc4xmpExt:CountryCode>GB</Iptc4xmpExt:CountryCode> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:City>Paris</Iptc4xmpExt:City> + <Iptc4xmpExt:CountryCode>FR</Iptc4xmpExt:CountryCode> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:City>Berlin</Iptc4xmpExt:City> + <Iptc4xmpExt:CountryCode>DE</Iptc4xmpExt:CountryCode> + </rdf:li> + </rdf:Bag> + </Iptc4xmpExt:LocationShown> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/images/XMP8.xmp b/ExifTool/t/images/XMP8.xmp new file mode 100644 index 0000000..69c7780 --- /dev/null +++ b/ExifTool/t/images/XMP8.xmp @@ -0,0 +1,38 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 11.58'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'> + <Iptc4xmpExt:AboutCvTerm> + <rdf:Bag> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:CvId>1</Iptc4xmpExt:CvId> + <Iptc4xmpExt:CvTermName> + <rdf:Alt> + <rdf:li xml:lang='x-default'>one</rdf:li> + </rdf:Alt> + </Iptc4xmpExt:CvTermName> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:CvId>2</Iptc4xmpExt:CvId> + <Iptc4xmpExt:CvTermName> + <rdf:Alt> + <rdf:li xml:lang='x-default'>two</rdf:li> + </rdf:Alt> + </Iptc4xmpExt:CvTermName> + </rdf:li> + <rdf:li rdf:parseType='Resource'> + <Iptc4xmpExt:CvId>3</Iptc4xmpExt:CvId> + <Iptc4xmpExt:CvTermName> + <rdf:Alt> + <rdf:li xml:lang='x-default'>three</rdf:li> + </rdf:Alt> + </Iptc4xmpExt:CvTermName> + </rdf:li> + </rdf:Bag> + </Iptc4xmpExt:AboutCvTerm> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/images/XMP9.xmp b/ExifTool/t/images/XMP9.xmp new file mode 100644 index 0000000..4f840f1 --- /dev/null +++ b/ExifTool/t/images/XMP9.xmp @@ -0,0 +1,34 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 11.64'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> + + <rdf:Description rdf:about='' + xmlns:plus='http://ns.useplus.org/ldf/xmp/1.0/'> + <plus:Custom1> + <rdf:Bag> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu1</rdf:li> + <rdf:li xml:lang='de'>de1</rdf:li> + <rdf:li xml:lang='fr'>fr1</rdf:li> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu2</rdf:li> + <rdf:li xml:lang='fr'/> + </rdf:Alt> + </rdf:li> + <rdf:li> + <rdf:Alt> + <rdf:li xml:lang='x-default'>cu3</rdf:li> + <rdf:li xml:lang='de'>de3</rdf:li> + <rdf:li xml:lang='fr'>fr3</rdf:li> + </rdf:Alt> + </rdf:li> + </rdf:Bag> + </plus:Custom1> + </rdf:Description> +</rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> \ No newline at end of file diff --git a/ExifTool/t/images/ZIP.gz b/ExifTool/t/images/ZIP.gz new file mode 100644 index 0000000000000000000000000000000000000000..bb8a5eb05b2b77446de519463e0948a9a8e8d9d8 GIT binary patch literal 71 zcmb2|=8)L(<C7N?b4hA(iC#%X2}4LmX0ZYgB`Sb~6q56Eb5rw57>d`P@Zmmr{(PXv Y`cpaq8lJj(51AO2xlFqw%)r0^0LO(IU;qFB literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ZIP.rar b/ExifTool/t/images/ZIP.rar new file mode 100755 index 0000000000000000000000000000000000000000..fc5d77f5aae365d88a5210e9b533a9e3ef88b04f GIT binary patch literal 74 zcmWGaEK-zWXJjy*wDl<$BP$yND<fk=14GJ-!vTs+%-pREEUgR*O`R9NH!v`;8tRo) elyEUKwUvENOAxrp=#iR}lP_By7Rb!X!T<p56cnQX literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ZIP.zip b/ExifTool/t/images/ZIP.zip new file mode 100644 index 0000000000000000000000000000000000000000..68549bc29b69d0c7e6c836bf6ed9a3cf5e14cdef GIT binary patch literal 167 zcmWIWW@h1H0D<d%GL}+q;(5YAHVAVth%%I<7MJLiRFs5<a56Bft=r~d55%DrEDT>6 zzcRX3WTu7W=jSMZR4Jro=A?23cr!AIGvhLk7i0|sBM>iX1hEjNvqDS{tzcjX@MdKL ONiqVVGm!QJaToyNn;`iB literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/ZISRAW.czi b/ExifTool/t/images/ZISRAW.czi new file mode 100644 index 0000000000000000000000000000000000000000..941d9707ecbb597964ec419bad5aa601a70c6a54 GIT binary patch literal 1856 zcmeHH&2G~`5a#cM#0$V><ttL`B#m06O(dsDgrzt^wgD-ZYHN>^RUNOhUZ;g)rJj4_ z4Y<KGaD&(23=hHjFIl6UP%oA6A*=73na?xxWxcRm$9zArrsD_Y^Qd_~tYCfIetqt| z__3LN`uXzu_w8rrZ=C0S`*Ks1p;}9F=zGc4I{(l6v4KVL|FFlNIWj#{_WY#m``6>< zc|WGP`!2DyCQBGGKU+gA2G5=%gl!NcAc7P~;@5-+mxO~0LeO|w(Rm{EIZJ((0J4I< zwuc&;ku~%+^NKPwKTm;gfwvZI4ZV%&CW9)v$!J6tEmx2@(2&gni29m_tkm2R899%n zgy0$E)R40ai2GVdqEsOqASsh{UM?7?ABmtjt`JCFE1lBiO1LcJz7*UElq`)eYHl}} z9IfcdO~9ui^$X2`70V$61Xw>1%okTacm!MOL#n7ry^?ewEH~v1PvwF*F$0na;Agfs zbZGzy<KlEh;-#df5DIO8^<AYRShM?=V12*+T_PTTfHxHU${uf6<JzCLafKw86glps z(`_15hgvf4EDzqPMKcx`M*f34Hd)4dW7>wCzQHJNcubI}89g#&!2w>W8bKmf^Ke2T z3I;~&=;XL#bh;<TtDgLA=j&JEN?lPf58qL^9CW%^SFw^-yj+bLt=@63YD(qn8m!mW WRpw&-(5;kv*xy~OH}CerkH;TD{#DKZ literal 0 HcmV?d00001 diff --git a/ExifTool/t/images/iWork.numbers b/ExifTool/t/images/iWork.numbers new file mode 100644 index 0000000000000000000000000000000000000000..e27f651b6b9d547dcfec74006ca1327731dbdeb5 GIT binary patch literal 1359 zcmWIWW@h1H0D;3L+1Ae!oqtIK*&r;=Aj%L}nwgyKlb@fhACgg;o0ONBnWL9gkRBSs z$-q2U=(iULhgPsKd}aK~Xya0vn_H<+P?TDhnOd%pnVXoNs*sXcl4!>j0Jmfl(2}_N zKiedM7J#rQiX{OMy?O;HX<#cFfX0I`4l4p&+>k5~0UF1_Py)0-{a5;<cR*e<6UY)# zhNRNWoRqNCqT<Z_Jde!clKi4dy@DJd18fNo(1N#u2upe|Ui3O_Aj0+_`?rYU_m`fF zZX2DnDp_lw5q<F8q16}Hri$G*Su(RUKmQq<Qpb~TAD*6{FJt*XNBT?P%zf{!@NAxt z6tc25{Msvn!?XX)<gn>ys+3+Y{xf@;!rd!-U2i_qjMWoRW9W}E7j>9(T*G_1zGaal z!$~gXKWS_07$n*hb<SzsDSs>ZZNf~y@MH6W#NF;RZrGw)c0J}&{nf(Dhh{tyVKv{h z{n*#8D;w`xI<2u>RenfQw8ZJ>G|wGP|26l2Usir*I+xzIJN<q$%dcxnR|eaCThdci zKW(w_`E<im|MU#Jj{Hd1dAQ4$^Zx0+CVf!UKtfJ8KHEBY;SQ$5j0_A+EDQ{sz>v$# zOG&NJtH{j(ht$_MzfeOe*f)QUfxzD9;SqLQgASN&;9M}F<E&QDQk7L(EWf;(b<&AT z;^w(ce}2z8pQp6&*ojL$&-a?koi8XZU*%Q8vXnbgea23a`*-8+OtCt*q+<O7vlC2N zvaiGEJ-9PNO--d)dCDfnr=ON-9u(cAWV+tLJf=ZStV{gbbx-5xOS)UHY+HN#*SZk) zM}enKtY3b7?}>$v`~tHNXEPeFz7l^nB&#Q<*8A+lP(I18OkReY%KPiz2>sb{-}kHi zNgl6uJ{diq!WNdx4}K*+XV>&C*Qx!mZOXm;#cPkh*uqv2ePw2ju*{DRR}sV6Y^`PA z<u{+4a8_~=<EnnY=oGc>yc~ajs<c0vFq@@1$5>`=&b@;#(hhGsA$Vw#*~JIktbU7A zn#|)Kv?^}vy|TWLwJrV5hl`s(nhUt~%Y}E9dtW^Nd&~E^t7oq2)p~VUK>jStf0>^h ziJ#(o%o9$`N#A6D_rcvKK`%qsmUJvmzrDXc>$!~LY`?GiTeUv=*0_KBxVZIyXA)QN zA+6iz7^_|iPG2#(`-tRo{*tvJH>)^>8)E(1%%%S+eGfd$mt$YKy6JmzjPg2$!Y^vp zDJs8scj_2_Uvnz5wPp@e(YNyZaer6?ycwCqnQ;{WyueJvzyM4S3`-h8ES$vwE2KCG ztzck4HW4$oBb%6tVj@c32bu@T_b~HtWp0FdZyS#R&BK$`fo6iTI>L>(l0CAS48S4; bDH8zA0A&J*8LVueFkt~gD_{`)X9n>A&3z2y literal 0 HcmV?d00001 diff --git a/SQLITE/INSTALL b/SQLITE/INSTALL new file mode 100644 index 0000000..a1e89e1 --- /dev/null +++ b/SQLITE/INSTALL @@ -0,0 +1,370 @@ +Installation Instructions +************************* + +Copyright (C) 1994-1996, 1999-2002, 2004-2011 Free Software Foundation, +Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `<wchar.h>' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/SQLITE/Makefile.am b/SQLITE/Makefile.am new file mode 100644 index 0000000..1eaa560 --- /dev/null +++ b/SQLITE/Makefile.am @@ -0,0 +1,20 @@ + +AM_CFLAGS = @BUILD_CFLAGS@ +lib_LTLIBRARIES = libsqlite3.la +libsqlite3_la_SOURCES = sqlite3.c +libsqlite3_la_LDFLAGS = -no-undefined -version-info 8:6:8 + +bin_PROGRAMS = sqlite3 +sqlite3_SOURCES = shell.c sqlite3.h +EXTRA_sqlite3_SOURCES = sqlite3.c +sqlite3_LDADD = @EXTRA_SHELL_OBJ@ @READLINE_LIBS@ +sqlite3_DEPENDENCIES = @EXTRA_SHELL_OBJ@ +sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_DQS=0 -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS) + +include_HEADERS = sqlite3.h sqlite3ext.h + +EXTRA_DIST = sqlite3.1 tea Makefile.msc sqlite3.rc sqlite3rc.h README.txt Replace.cs Makefile.fallback +pkgconfigdir = ${libdir}/pkgconfig +pkgconfig_DATA = sqlite3.pc + +man_MANS = sqlite3.1 diff --git a/SQLITE/Makefile.fallback b/SQLITE/Makefile.fallback new file mode 100644 index 0000000..9355b14 --- /dev/null +++ b/SQLITE/Makefile.fallback @@ -0,0 +1,19 @@ +#!/usr/bin/make +# +# If the configure script does not work, then this Makefile is available +# as a backup. Manually configure the variables below. +# +# Note: This makefile works out-of-the-box on MacOS 10.2 (Jaguar) +# +CC = gcc +CFLAGS = -O0 -I. +LIBS = -lz +COPTS += -D_BSD_SOURCE +COPTS += -DSQLITE_ENABLE_LOCKING_STYLE=0 +COPTS += -DSQLITE_THREADSAFE=0 +COPTS += -DSQLITE_OMIT_LOAD_EXTENSION +COPTS += -DSQLITE_WITHOUT_ZONEMALLOC +COPTS += -DSQLITE_ENABLE_RTREE + +sqlite3: shell.c sqlite3.c + $(CC) $(CFLAGS) $(COPTS) -o sqlite3 shell.c sqlite3.c $(LIBS) diff --git a/SQLITE/Makefile.in b/SQLITE/Makefile.in new file mode 100644 index 0000000..b2d020f --- /dev/null +++ b/SQLITE/Makefile.in @@ -0,0 +1,1028 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = sqlite3$(EXEEXT) +subdir = . +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \ + $(am__configure_deps) $(include_HEADERS) $(am__DIST_COMMON) +am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ + configure.lineno config.status.lineno +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = sqlite3.pc +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" \ + "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(pkgconfigdir)" \ + "$(DESTDIR)$(includedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +libsqlite3_la_LIBADD = +am_libsqlite3_la_OBJECTS = sqlite3.lo +libsqlite3_la_OBJECTS = $(am_libsqlite3_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libsqlite3_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(libsqlite3_la_LDFLAGS) $(LDFLAGS) -o $@ +PROGRAMS = $(bin_PROGRAMS) +am_sqlite3_OBJECTS = sqlite3-shell.$(OBJEXT) +sqlite3_OBJECTS = $(am_sqlite3_OBJECTS) +sqlite3_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(sqlite3_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libsqlite3_la_SOURCES) $(sqlite3_SOURCES) \ + $(EXTRA_sqlite3_SOURCES) +DIST_SOURCES = $(libsqlite3_la_SOURCES) $(sqlite3_SOURCES) \ + $(EXTRA_sqlite3_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +man1dir = $(mandir)/man1 +NROFF = nroff +MANS = $(man_MANS) +DATA = $(pkgconfig_DATA) +HEADERS = $(include_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +CSCOPE = cscope +AM_RECURSIVE_TARGETS = cscope +am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/sqlite3.pc.in \ + INSTALL compile config.guess config.sub depcomp install-sh \ + ltmain.sh missing +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +distdir = $(PACKAGE)-$(VERSION) +top_distdir = $(distdir) +am__remove_distdir = \ + if test -d "$(distdir)"; then \ + find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -rf "$(distdir)" \ + || { sleep 5 && rm -rf "$(distdir)"; }; \ + else :; fi +am__post_remove_distdir = $(am__remove_distdir) +DIST_ARCHIVES = $(distdir).tar.gz +GZIP_ENV = --best +DIST_TARGETS = dist-gzip +distuninstallcheck_listfiles = find . -type f -print +am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ + | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' +distcleancheck_listfiles = find . -type f -print +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BUILD_CFLAGS = @BUILD_CFLAGS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +EXTRA_SHELL_OBJ = @EXTRA_SHELL_OBJ@ +FGREP = @FGREP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHELL_CFLAGS = @SHELL_CFLAGS@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CFLAGS = @BUILD_CFLAGS@ +lib_LTLIBRARIES = libsqlite3.la +libsqlite3_la_SOURCES = sqlite3.c +libsqlite3_la_LDFLAGS = -no-undefined -version-info 8:6:8 +sqlite3_SOURCES = shell.c sqlite3.h +EXTRA_sqlite3_SOURCES = sqlite3.c +sqlite3_LDADD = @EXTRA_SHELL_OBJ@ @READLINE_LIBS@ +sqlite3_DEPENDENCIES = @EXTRA_SHELL_OBJ@ +sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_DQS=0 -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS) +include_HEADERS = sqlite3.h sqlite3ext.h +EXTRA_DIST = sqlite3.1 tea Makefile.msc sqlite3.rc sqlite3rc.h README.txt Replace.cs Makefile.fallback +pkgconfigdir = ${libdir}/pkgconfig +pkgconfig_DATA = sqlite3.pc +man_MANS = sqlite3.1 +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +am--refresh: Makefile + @: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ + $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + echo ' $(SHELL) ./config.status'; \ + $(SHELL) ./config.status;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + $(SHELL) ./config.status --recheck + +$(top_srcdir)/configure: $(am__configure_deps) + $(am__cd) $(srcdir) && $(AUTOCONF) +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) +$(am__aclocal_m4_deps): +sqlite3.pc: $(top_builddir)/config.status $(srcdir)/sqlite3.pc.in + cd $(top_builddir) && $(SHELL) ./config.status $@ + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libsqlite3.la: $(libsqlite3_la_OBJECTS) $(libsqlite3_la_DEPENDENCIES) $(EXTRA_libsqlite3_la_DEPENDENCIES) + $(AM_V_CCLD)$(libsqlite3_la_LINK) -rpath $(libdir) $(libsqlite3_la_OBJECTS) $(libsqlite3_la_LIBADD) $(LIBS) +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +sqlite3$(EXEEXT): $(sqlite3_OBJECTS) $(sqlite3_DEPENDENCIES) $(EXTRA_sqlite3_DEPENDENCIES) + @rm -f sqlite3$(EXEEXT) + $(AM_V_CCLD)$(sqlite3_LINK) $(sqlite3_OBJECTS) $(sqlite3_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sqlite3-shell.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sqlite3-sqlite3.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sqlite3.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +sqlite3-shell.o: shell.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sqlite3_CFLAGS) $(CFLAGS) -MT sqlite3-shell.o -MD -MP -MF $(DEPDIR)/sqlite3-shell.Tpo -c -o sqlite3-shell.o `test -f 'shell.c' || echo '$(srcdir)/'`shell.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/sqlite3-shell.Tpo $(DEPDIR)/sqlite3-shell.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='shell.c' object='sqlite3-shell.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sqlite3_CFLAGS) $(CFLAGS) -c -o sqlite3-shell.o `test -f 'shell.c' || echo '$(srcdir)/'`shell.c + +sqlite3-shell.obj: shell.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sqlite3_CFLAGS) $(CFLAGS) -MT sqlite3-shell.obj -MD -MP -MF $(DEPDIR)/sqlite3-shell.Tpo -c -o sqlite3-shell.obj `if test -f 'shell.c'; then $(CYGPATH_W) 'shell.c'; else $(CYGPATH_W) '$(srcdir)/shell.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/sqlite3-shell.Tpo $(DEPDIR)/sqlite3-shell.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='shell.c' object='sqlite3-shell.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sqlite3_CFLAGS) $(CFLAGS) -c -o sqlite3-shell.obj `if test -f 'shell.c'; then $(CYGPATH_W) 'shell.c'; else $(CYGPATH_W) '$(srcdir)/shell.c'; fi` + +sqlite3-sqlite3.o: sqlite3.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sqlite3_CFLAGS) $(CFLAGS) -MT sqlite3-sqlite3.o -MD -MP -MF $(DEPDIR)/sqlite3-sqlite3.Tpo -c -o sqlite3-sqlite3.o `test -f 'sqlite3.c' || echo '$(srcdir)/'`sqlite3.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/sqlite3-sqlite3.Tpo $(DEPDIR)/sqlite3-sqlite3.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sqlite3.c' object='sqlite3-sqlite3.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sqlite3_CFLAGS) $(CFLAGS) -c -o sqlite3-sqlite3.o `test -f 'sqlite3.c' || echo '$(srcdir)/'`sqlite3.c + +sqlite3-sqlite3.obj: sqlite3.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sqlite3_CFLAGS) $(CFLAGS) -MT sqlite3-sqlite3.obj -MD -MP -MF $(DEPDIR)/sqlite3-sqlite3.Tpo -c -o sqlite3-sqlite3.obj `if test -f 'sqlite3.c'; then $(CYGPATH_W) 'sqlite3.c'; else $(CYGPATH_W) '$(srcdir)/sqlite3.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/sqlite3-sqlite3.Tpo $(DEPDIR)/sqlite3-sqlite3.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sqlite3.c' object='sqlite3-sqlite3.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sqlite3_CFLAGS) $(CFLAGS) -c -o sqlite3-sqlite3.obj `if test -f 'sqlite3.c'; then $(CYGPATH_W) 'sqlite3.c'; else $(CYGPATH_W) '$(srcdir)/sqlite3.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool config.lt +install-man1: $(man_MANS) + @$(NORMAL_INSTALL) + @list1=''; \ + list2='$(man_MANS)'; \ + test -n "$(man1dir)" \ + && test -n "`echo $$list1$$list2`" \ + || exit 0; \ + echo " $(MKDIR_P) '$(DESTDIR)$(man1dir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(man1dir)" || exit 1; \ + { for i in $$list1; do echo "$$i"; done; \ + if test -n "$$list2"; then \ + for i in $$list2; do echo "$$i"; done \ + | sed -n '/\.1[a-z]*$$/p'; \ + fi; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \ + done; } + +uninstall-man1: + @$(NORMAL_UNINSTALL) + @list=''; test -n "$(man1dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.1[a-z]*$$/p'; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + dir='$(DESTDIR)$(man1dir)'; $(am__uninstall_files_from_dir) +install-pkgconfigDATA: $(pkgconfig_DATA) + @$(NORMAL_INSTALL) + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkgconfigdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \ + done + +uninstall-pkgconfigDATA: + @$(NORMAL_UNINSTALL) + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkgconfigdir)'; $(am__uninstall_files_from_dir) +install-includeHEADERS: $(include_HEADERS) + @$(NORMAL_INSTALL) + @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(includedir)" || exit $$?; \ + done + +uninstall-includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscope: cscope.files + test ! -s cscope.files \ + || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS) +clean-cscope: + -rm -f cscope.files +cscope.files: clean-cscope cscopelist +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + -rm -f cscope.out cscope.in.out cscope.po.out cscope.files + +distdir: $(DISTFILES) + $(am__remove_distdir) + test -d "$(distdir)" || mkdir "$(distdir)" + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + -test -n "$(am__skip_mode_fix)" \ + || find "$(distdir)" -type d ! -perm -755 \ + -exec chmod u+rwx,go+rx {} \; -o \ + ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ + || chmod -R a+r "$(distdir)" +dist-gzip: distdir + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + $(am__post_remove_distdir) + +dist-bzip2: distdir + tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 + $(am__post_remove_distdir) + +dist-lzip: distdir + tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz + $(am__post_remove_distdir) + +dist-xz: distdir + tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz + $(am__post_remove_distdir) + +dist-tarZ: distdir + @echo WARNING: "Support for distribution archives compressed with" \ + "legacy program 'compress' is deprecated." >&2 + @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 + tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z + $(am__post_remove_distdir) + +dist-shar: distdir + @echo WARNING: "Support for shar distribution archives is" \ + "deprecated." >&2 + @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 + shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz + $(am__post_remove_distdir) + +dist-zip: distdir + -rm -f $(distdir).zip + zip -rq $(distdir).zip $(distdir) + $(am__post_remove_distdir) + +dist dist-all: + $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' + $(am__post_remove_distdir) + +# This target untars the dist file and tries a VPATH configuration. Then +# it guarantees that the distribution is self-contained by making another +# tarfile. +distcheck: dist + case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + esac + chmod -R a-w $(distdir) + chmod u+w $(distdir) + mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst + chmod a-w $(distdir) + test -d $(distdir)/_build || exit 0; \ + dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ + && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ + && am__cwd=`pwd` \ + && $(am__cd) $(distdir)/_build/sub \ + && ../../configure \ + $(AM_DISTCHECK_CONFIGURE_FLAGS) \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + --srcdir=../.. --prefix="$$dc_install_base" \ + && $(MAKE) $(AM_MAKEFLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) dvi \ + && $(MAKE) $(AM_MAKEFLAGS) check \ + && $(MAKE) $(AM_MAKEFLAGS) install \ + && $(MAKE) $(AM_MAKEFLAGS) installcheck \ + && $(MAKE) $(AM_MAKEFLAGS) uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ + distuninstallcheck \ + && chmod -R a-w "$$dc_install_base" \ + && ({ \ + (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ + distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ + } || { rm -rf "$$dc_destdir"; exit 1; }) \ + && rm -rf "$$dc_destdir" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && rm -rf $(DIST_ARCHIVES) \ + && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ + && cd "$$am__cwd" \ + || exit 1 + $(am__post_remove_distdir) + @(echo "$(distdir) archives ready for distribution: "; \ + list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ + sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' +distuninstallcheck: + @test -n '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: trying to run $@ with an empty' \ + '$$(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + $(am__cd) '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left after uninstall:" ; \ + if test -n "$(DESTDIR)"; then \ + echo " (check DESTDIR support)"; \ + fi ; \ + $(distuninstallcheck_listfiles) ; \ + exit 1; } >&2 +distcleancheck: distclean + @if test '$(srcdir)' = . ; then \ + echo "ERROR: distcleancheck can only run from a VPATH build" ; \ + exit 1 ; \ + fi + @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left in build directory after distclean:" ; \ + $(distcleancheck_listfiles) ; \ + exit 1; } >&2 +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(PROGRAMS) $(MANS) $(DATA) $(HEADERS) +install-binPROGRAMS: install-libLTLIBRARIES + +installdirs: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(pkgconfigdir)" "$(DESTDIR)$(includedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \ + clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-libtool distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-includeHEADERS install-man \ + install-pkgconfigDATA + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS install-libLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: install-man1 + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf $(top_srcdir)/autom4te.cache + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-includeHEADERS \ + uninstall-libLTLIBRARIES uninstall-man uninstall-pkgconfigDATA + +uninstall-man: uninstall-man1 + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--refresh check check-am clean \ + clean-binPROGRAMS clean-cscope clean-generic \ + clean-libLTLIBRARIES clean-libtool cscope cscopelist-am ctags \ + ctags-am dist dist-all dist-bzip2 dist-gzip dist-lzip \ + dist-shar dist-tarZ dist-xz dist-zip distcheck distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distcleancheck distdir distuninstallcheck dvi \ + dvi-am html html-am info info-am install install-am \ + install-binPROGRAMS install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-includeHEADERS install-info \ + install-info-am install-libLTLIBRARIES install-man \ + install-man1 install-pdf install-pdf-am install-pkgconfigDATA \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \ + uninstall-includeHEADERS uninstall-libLTLIBRARIES \ + uninstall-man uninstall-man1 uninstall-pkgconfigDATA + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/SQLITE/Makefile.msc b/SQLITE/Makefile.msc new file mode 100644 index 0000000..13663d8 --- /dev/null +++ b/SQLITE/Makefile.msc @@ -0,0 +1,1064 @@ +#### DO NOT EDIT #### +# This makefile is automatically generated from the Makefile.msc at +# the root of the canonical SQLite source tree (not the +# amalgamation tarball) using the tool/mkmsvcmin.tcl +# script. +# + +# +# nmake Makefile for SQLite +# +############################################################################### +############################## START OF OPTIONS ############################### +############################################################################### + +# The toplevel directory of the source tree. This is the directory +# that contains this "Makefile.msc". +# +TOP = . + + +# Set this non-0 to enable full warnings (-W4, etc) when compiling. +# +!IFNDEF USE_FULLWARN +USE_FULLWARN = 1 +!ENDIF + +# Set this non-0 to enable treating warnings as errors (-WX, etc) when +# compiling. +# +!IFNDEF USE_FATAL_WARN +USE_FATAL_WARN = 0 +!ENDIF + +# Set this non-0 to enable full runtime error checks (-RTC1, etc). This +# has no effect if (any) optimizations are enabled. +# +!IFNDEF USE_RUNTIME_CHECKS +USE_RUNTIME_CHECKS = 0 +!ENDIF + +# Set this non-0 to create a SQLite amalgamation file that excludes the +# various built-in extensions. +# +!IFNDEF MINIMAL_AMALGAMATION +MINIMAL_AMALGAMATION = 0 +!ENDIF + +# Set this non-0 to use "stdcall" calling convention for the core library +# and shell executable. +# +!IFNDEF USE_STDCALL +USE_STDCALL = 0 +!ENDIF + +# Set this non-0 to use structured exception handling (SEH) for WAL mode +# in the core library. +# +!IFNDEF USE_SEH +USE_SEH = 1 +!ENDIF + +# Set this non-0 to have the shell executable link against the core dynamic +# link library. +# +!IFNDEF DYNAMIC_SHELL +DYNAMIC_SHELL = 0 +!ENDIF + +# Set this non-0 to enable extra code that attempts to detect misuse of the +# SQLite API. +# +!IFNDEF API_ARMOR +API_ARMOR = 0 +!ENDIF + +# If necessary, create a list of harmless compiler warnings to disable when +# compiling the various tools. For the SQLite source code itself, warnings, +# if any, will be disabled from within it. +# +!IFNDEF NO_WARN +!IF $(USE_FULLWARN)!=0 +NO_WARN = -wd4054 -wd4055 -wd4100 -wd4127 -wd4130 -wd4152 -wd4189 -wd4206 +NO_WARN = $(NO_WARN) -wd4210 -wd4232 -wd4244 -wd4305 -wd4306 -wd4702 -wd4706 +!ENDIF +!ENDIF + +# Set this non-0 to use the library paths and other options necessary for +# Windows Phone 8.1. +# +!IFNDEF USE_WP81_OPTS +USE_WP81_OPTS = 0 +!ENDIF + +# Set this non-0 to split the SQLite amalgamation file into chunks to +# be used for debugging with Visual Studio. +# +!IFNDEF SPLIT_AMALGAMATION +SPLIT_AMALGAMATION = 0 +!ENDIF + + +# Set this non-0 to dynamically link to the MSVC runtime library. +# +!IFNDEF USE_CRT_DLL +USE_CRT_DLL = 0 +!ENDIF + +# Set this non-0 to link to the RPCRT4 library. +# +!IFNDEF USE_RPCRT4_LIB +USE_RPCRT4_LIB = 0 +!ENDIF + +# Set this non-0 to generate assembly code listings for the source code +# files. +# +!IFNDEF USE_LISTINGS +USE_LISTINGS = 0 +!ENDIF + +# Set this non-0 to attempt setting the native compiler automatically +# for cross-compiling the command line tools needed during the compilation +# process. +# +!IFNDEF XCOMPILE +XCOMPILE = 0 +!ENDIF + +# Set this non-0 to use the native libraries paths for cross-compiling +# the command line tools needed during the compilation process. +# +!IFNDEF USE_NATIVE_LIBPATHS +USE_NATIVE_LIBPATHS = 0 +!ENDIF + +# Set this 0 to skip the compiling and embedding of version resources. +# +!IFNDEF USE_RC +USE_RC = 1 +!ENDIF + +# Set this non-0 to compile binaries suitable for the WinRT environment. +# This setting does not apply to any binaries that require Tcl to operate +# properly (i.e. the text fixture, etc). +# +!IFNDEF FOR_WINRT +FOR_WINRT = 0 +!ENDIF + +# Set this non-0 to compile binaries suitable for the UWP environment. +# This setting does not apply to any binaries that require Tcl to operate +# properly (i.e. the text fixture, etc). +# +!IFNDEF FOR_UWP +FOR_UWP = 0 +!ENDIF + +# Set this non-0 to compile binaries suitable for the Windows 10 platform. +# +!IFNDEF FOR_WIN10 +FOR_WIN10 = 0 +!ENDIF + + +# Set this to non-0 to create and use PDBs. +# +!IFNDEF SYMBOLS +SYMBOLS = 1 +!ENDIF + +# Set this to non-0 to use the SQLite debugging heap subsystem. +# +!IFNDEF MEMDEBUG +MEMDEBUG = 0 +!ENDIF + +# Set this to non-0 to use the Win32 native heap subsystem. +# +!IFNDEF WIN32HEAP +WIN32HEAP = 0 +!ENDIF + +# Set this to non-0 to enable OSTRACE() macros, which can be useful when +# debugging. +# +!IFNDEF OSTRACE +OSTRACE = 0 +!ENDIF + +# enable address sanitizer using ASAN=1 on the command-line. +# +!IFNDEF ASAN +ASAN = 0 +!ENDIF + +# Set this to one of the following values to enable various debugging +# features. Each level includes the debugging options from the previous +# levels. Currently, the recognized values for DEBUG are: +# +# 0 == NDEBUG: Disables assert() and other runtime diagnostics. +# 1 == SQLITE_ENABLE_API_ARMOR: extra attempts to detect misuse of the API. +# 2 == Disables NDEBUG and all optimizations and then enables PDBs. +# 3 == SQLITE_DEBUG: Enables various diagnostics messages and code. +# 4 == SQLITE_WIN32_MALLOC_VALIDATE: Validate the Win32 native heap per call. +# 5 == SQLITE_DEBUG_OS_TRACE: Enables output from the OSTRACE() macros. +# 6 == SQLITE_ENABLE_IOTRACE: Enables output from the IOTRACE() macros. +# +!IFNDEF DEBUG +DEBUG = 0 +!ENDIF + + +# Enable use of available compiler optimizations? Normally, this should be +# non-zero. Setting this to zero, thus disabling all compiler optimizations, +# can be useful for testing. +# +!IFNDEF OPTIMIZATIONS +OPTIMIZATIONS = 2 +!ENDIF + +# Set this to non-0 to enable support for the session extension. +# +!IFNDEF SESSION +SESSION = 0 +!ENDIF + +# Set this to non-0 to enable support for the rbu extension. +# +!IFNDEF RBU +RBU = 0 +!ENDIF + +# Set the source code file to be used by executables and libraries when +# they need the amalgamation. +# +!IFNDEF SQLITE3C +!IF $(SPLIT_AMALGAMATION)!=0 +SQLITE3C = sqlite3-all.c +!ELSE +SQLITE3C = sqlite3.c +!ENDIF +!ENDIF + +# Set the include code file to be used by executables and libraries when +# they need SQLite. +# +!IFNDEF SQLITE3H +SQLITE3H = sqlite3.h +!ENDIF + +# This is the name to use for the SQLite dynamic link library (DLL). +# +!IFNDEF SQLITE3DLL +!IF $(FOR_WIN10)!=0 +SQLITE3DLL = winsqlite3.dll +!ELSE +SQLITE3DLL = sqlite3.dll +!ENDIF +!ENDIF + +# This is the name to use for the SQLite import library (LIB). +# +!IFNDEF SQLITE3LIB +!IF $(FOR_WIN10)!=0 +SQLITE3LIB = winsqlite3.lib +!ELSE +SQLITE3LIB = sqlite3.lib +!ENDIF +!ENDIF + +# This is the name to use for the SQLite shell executable (EXE). +# +!IFNDEF SQLITE3EXE +!IF $(FOR_WIN10)!=0 +SQLITE3EXE = winsqlite3shell.exe +!ELSE +SQLITE3EXE = sqlite3.exe +!ENDIF +!ENDIF + +# This is the argument used to set the program database (PDB) file for the +# SQLite shell executable (EXE). +# +!IFNDEF SQLITE3EXEPDB +!IF $(FOR_WIN10)!=0 +SQLITE3EXEPDB = +!ELSE +SQLITE3EXEPDB = /pdb:sqlite3sh.pdb +!ENDIF +!ENDIF + + +# These are the "standard" SQLite compilation options used when compiling for +# the Windows platform. +# +!IFNDEF OPT_FEATURE_FLAGS +!IF $(MINIMAL_AMALGAMATION)==0 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_GEOPOLY=1 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_DBPAGE_VTAB=1 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_DBSTAT_VTAB=1 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1 +!ENDIF +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1 +!ENDIF + +# Should the session extension be enabled? If so, add compilation options +# to enable it. +# +!IF $(SESSION)!=0 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_SESSION=1 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_PREUPDATE_HOOK=1 +!ENDIF + +# Always enable math functions on Windows +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_MATH_FUNCTIONS + +# Should the rbu extension be enabled? If so, add compilation options +# to enable it. +# +!IF $(RBU)!=0 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1 +!ENDIF + +# Should structured exception handling (SEH) be enabled for WAL mode in +# the core library? +# +!IF $(USE_SEH)!=0 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1 +!ENDIF + +# These are the "extended" SQLite compilation options used when compiling for +# the Windows 10 platform. +# +!IFNDEF EXT_FEATURE_FLAGS +!IF $(FOR_WIN10)!=0 +EXT_FEATURE_FLAGS = $(EXT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS4=1 +EXT_FEATURE_FLAGS = $(EXT_FEATURE_FLAGS) -DSQLITE_SYSTEM_MALLOC=1 +EXT_FEATURE_FLAGS = $(EXT_FEATURE_FLAGS) -DSQLITE_OMIT_LOCALTIME=1 +!ELSE +EXT_FEATURE_FLAGS = +!ENDIF +!ENDIF + +############################################################################### +############################### END OF OPTIONS ################################ +############################################################################### + +# When compiling for the Windows 10 platform, the PLATFORM macro must be set +# to an appropriate value (e.g. x86, x64, arm, arm64, etc). +# +!IF $(FOR_WIN10)!=0 +!IFNDEF PLATFORM +!ERROR Using the FOR_WIN10 option requires a value for PLATFORM. +!ENDIF +!ENDIF + +# This assumes that MSVC is always installed in 32-bit Program Files directory +# and sets the variable for use in locating other 32-bit installs accordingly. +# +PROGRAMFILES_X86 = $(VCINSTALLDIR)\..\.. +PROGRAMFILES_X86 = $(PROGRAMFILES_X86:\\=\) + +# Check for the predefined command macro CC. This should point to the compiler +# binary for the target platform. If it is not defined, simply define it to +# the legacy default value 'cl.exe'. +# +!IFNDEF CC +CC = cl.exe +!ENDIF + +# Check for the predefined command macro CSC. This should point to a working +# C Sharp compiler binary. If it is not defined, simply define it to the +# legacy default value 'csc.exe'. +# +!IFNDEF CSC +CSC = csc.exe +!ENDIF + +# Check for the command macro LD. This should point to the linker binary for +# the target platform. If it is not defined, simply define it to the legacy +# default value 'link.exe'. +# +!IFNDEF LD +LD = link.exe +!ENDIF + +# Check for the predefined command macro RC. This should point to the resource +# compiler binary for the target platform. If it is not defined, simply define +# it to the legacy default value 'rc.exe'. +# +!IFNDEF RC +RC = rc.exe +!ENDIF + +# Check for the MSVC runtime library path macro. Otherwise, this value will +# default to the 'lib' directory underneath the MSVC installation directory. +# +!IFNDEF CRTLIBPATH +CRTLIBPATH = $(VCINSTALLDIR)\lib +!ENDIF + +CRTLIBPATH = $(CRTLIBPATH:\\=\) + +# Check for the command macro NCC. This should point to the compiler binary +# for the platform the compilation process is taking place on. If it is not +# defined, simply define it to have the same value as the CC macro. When +# cross-compiling, it is suggested that this macro be modified via the command +# line (since nmake itself does not provide a built-in method to guess it). +# For example, to use the x86 compiler when cross-compiling for x64, a command +# line similar to the following could be used (all on one line): +# +# nmake /f Makefile.msc sqlite3.dll +# XCOMPILE=1 USE_NATIVE_LIBPATHS=1 +# +# Alternatively, the full path and file name to the compiler binary for the +# platform the compilation process is taking place may be specified (all on +# one line): +# +# nmake /f Makefile.msc sqlite3.dll +# "NCC=""%VCINSTALLDIR%\bin\cl.exe""" +# USE_NATIVE_LIBPATHS=1 +# +!IFDEF NCC +NCC = $(NCC:\\=\) +!ELSEIF $(XCOMPILE)!=0 +NCC = "$(VCINSTALLDIR)\bin\$(CC)" +NCC = $(NCC:\\=\) +!ELSE +NCC = $(CC) +!ENDIF + +# Check for the MSVC native runtime library path macro. Otherwise, +# this value will default to the 'lib' directory underneath the MSVC +# installation directory. +# +!IFNDEF NCRTLIBPATH +NCRTLIBPATH = $(VCINSTALLDIR)\lib +!ENDIF + +NCRTLIBPATH = $(NCRTLIBPATH:\\=\) + +# Check for the Platform SDK library path macro. Otherwise, this +# value will default to the 'lib' directory underneath the Windows +# SDK installation directory (the environment variable used appears +# to be available when using Visual C++ 2008 or later via the +# command line). +# +!IFNDEF NSDKLIBPATH +NSDKLIBPATH = $(WINDOWSSDKDIR)\lib +!ENDIF + +NSDKLIBPATH = $(NSDKLIBPATH:\\=\) + +# Check for the UCRT library path macro. Otherwise, this value will +# default to the version-specific, platform-specific 'lib' directory +# underneath the Windows SDK installation directory. +# +!IFNDEF UCRTLIBPATH +UCRTLIBPATH = $(WINDOWSSDKDIR)\lib\$(WINDOWSSDKLIBVERSION)\ucrt\$(PLATFORM) +!ENDIF + +UCRTLIBPATH = $(UCRTLIBPATH:\\=\) + +# C compiler and options for use in building executables that +# will run on the platform that is doing the build. +# +!IF $(USE_FULLWARN)!=0 +BCC = $(NCC) -nologo -W4 -Fd$*.pdb $(CCOPTS) $(BCCOPTS) +!ELSE +BCC = $(NCC) -nologo -W3 -Fd$*.pdb $(CCOPTS) $(BCCOPTS) +!ENDIF + +# Check if assembly code listings should be generated for the source +# code files to be compiled. +# +!IF $(USE_LISTINGS)!=0 +BCC = $(BCC) -FAcs +!ENDIF + +# Check if the native library paths should be used when compiling +# the command line tools used during the compilation process. If +# so, set the necessary macro now. +# +!IF $(USE_NATIVE_LIBPATHS)!=0 +NLTLIBPATHS = "/LIBPATH:$(NCRTLIBPATH)" "/LIBPATH:$(NSDKLIBPATH)" + +!IFDEF NUCRTLIBPATH +NUCRTLIBPATH = $(NUCRTLIBPATH:\\=\) +NLTLIBPATHS = $(NLTLIBPATHS) "/LIBPATH:$(NUCRTLIBPATH)" +!ENDIF +!ENDIF + +# C compiler and options for use in building executables that +# will run on the target platform. (BCC and TCC are usually the +# same unless your are cross-compiling.) +# +!IF $(USE_FULLWARN)!=0 +TCC = $(CC) -nologo -W4 -DINCLUDE_MSVC_H=1 $(CCOPTS) $(TCCOPTS) +!ELSE +TCC = $(CC) -nologo -W3 $(CCOPTS) $(TCCOPTS) +!ENDIF + +# Check if warnings should be treated as errors when compiling. +# +!IF $(USE_FATAL_WARN)!=0 +TCC = $(TCC) -WX +!ENDIF + +TCC = $(TCC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) -fp:precise +RCC = $(RC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) $(RCOPTS) $(RCCOPTS) + +# Check if we want to use the "stdcall" calling convention when compiling. +# This is not supported by the compilers for non-x86 platforms. It should +# also be noted here that building any target with these "stdcall" options +# will most likely fail if the Tcl library is also required. This is due +# to how the Tcl library functions are declared and exported (i.e. without +# an explicit calling convention, which results in "cdecl"). +# +!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 +!IF "$(PLATFORM)"=="x86" +CORE_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall +SHELL_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall +!ELSE +!IFNDEF PLATFORM +CORE_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall +SHELL_CCONV_OPTS = -Gz -guard:cf -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall +!ELSE +CORE_CCONV_OPTS = +SHELL_CCONV_OPTS = +!ENDIF +!ENDIF +!ELSE +CORE_CCONV_OPTS = +SHELL_CCONV_OPTS = +!ENDIF + +# These are additional compiler options used for the core library. +# +!IFNDEF CORE_COMPILE_OPTS +!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0 +CORE_COMPILE_OPTS = $(CORE_CCONV_OPTS) -DSQLITE_API=__declspec(dllexport) +!ELSE +CORE_COMPILE_OPTS = $(CORE_CCONV_OPTS) +!ENDIF +!ENDIF + +# These are the additional targets that the core library should depend on +# when linking. +# +!IFNDEF CORE_LINK_DEP +!IF $(DYNAMIC_SHELL)!=0 +CORE_LINK_DEP = +!ELSEIF $(FOR_WIN10)==0 || "$(PLATFORM)"=="x86" +CORE_LINK_DEP = sqlite3.def +!ELSE +CORE_LINK_DEP = +!ENDIF +!ENDIF + +# These are additional linker options used for the core library. +# +!IFNDEF CORE_LINK_OPTS +!IF $(DYNAMIC_SHELL)!=0 +CORE_LINK_OPTS = +!ELSEIF $(FOR_WIN10)==0 || "$(PLATFORM)"=="x86" +CORE_LINK_OPTS = /DEF:sqlite3.def +!ELSE +CORE_LINK_OPTS = +!ENDIF +!ENDIF + +# These are additional compiler options used for the shell executable. +# +!IFNDEF SHELL_COMPILE_OPTS +!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0 +SHELL_COMPILE_OPTS = $(SHELL_CCONV_OPTS) -DSQLITE_API=__declspec(dllimport) +!ELSE +SHELL_COMPILE_OPTS = $(SHELL_CCONV_OPTS) +!ENDIF +!ENDIF + +# This is the source code that the shell executable should be compiled +# with. +# +!IFNDEF SHELL_CORE_SRC +!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0 +SHELL_CORE_SRC = +!ELSE +SHELL_CORE_SRC = $(SQLITE3C) +!ENDIF +!ENDIF + +# This is the core library that the shell executable should depend on. +# +!IFNDEF SHELL_CORE_DEP +!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0 +SHELL_CORE_DEP = $(SQLITE3DLL) +!ELSE +SHELL_CORE_DEP = +!ENDIF +!ENDIF + + +# This is the core library that the shell executable should link with. +# +!IFNDEF SHELL_CORE_LIB +!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0 +SHELL_CORE_LIB = $(SQLITE3LIB) +!ELSE +SHELL_CORE_LIB = +!ENDIF +!ENDIF + +# These are additional linker options used for the shell executable. +# +!IFNDEF SHELL_LINK_OPTS +SHELL_LINK_OPTS = $(SHELL_CORE_LIB) +!ENDIF + +# Check if assembly code listings should be generated for the source +# code files to be compiled. +# +!IF $(USE_LISTINGS)!=0 +TCC = $(TCC) -FAcs +!ENDIF + +# When compiling the library for use in the WinRT environment, +# the following compile-time options must be used as well to +# disable use of Win32 APIs that are not available and to enable +# use of Win32 APIs that are specific to Windows 8 and/or WinRT. +# +!IF $(FOR_WINRT)!=0 +TCC = $(TCC) -DSQLITE_OS_WINRT=1 +RCC = $(RCC) -DSQLITE_OS_WINRT=1 +TCC = $(TCC) -DWINAPI_FAMILY=WINAPI_FAMILY_APP +RCC = $(RCC) -DWINAPI_FAMILY=WINAPI_FAMILY_APP +!ENDIF + +# C compiler options for the Windows 10 platform (needs MSVC 2015). +# +!IF $(FOR_WIN10)!=0 +TCC = $(TCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE +BCC = $(BCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE +!ENDIF + +# Also, we need to dynamically link to the correct MSVC runtime +# when compiling for WinRT (e.g. debug or release) OR if the +# USE_CRT_DLL option is set to force dynamically linking to the +# MSVC runtime library. +# +!IF $(FOR_WINRT)!=0 || $(USE_CRT_DLL)!=0 +!IF $(DEBUG)>1 +TCC = $(TCC) -MDd +BCC = $(BCC) -MDd +!ELSE +TCC = $(TCC) -MD +BCC = $(BCC) -MD +!ENDIF +!ELSE +!IF $(DEBUG)>1 +TCC = $(TCC) -MTd +BCC = $(BCC) -MTd +!ELSE +TCC = $(TCC) -MT +BCC = $(BCC) -MT +!ENDIF +!ENDIF + + +# Define -DNDEBUG to compile without debugging (i.e., for production usage) +# Omitting the define will cause extra debugging code to be inserted and +# includes extra comments when "EXPLAIN stmt" is used. +# +!IF $(DEBUG)==0 +TCC = $(TCC) -DNDEBUG +BCC = $(BCC) -DNDEBUG +RCC = $(RCC) -DNDEBUG +!ENDIF + +!IF $(DEBUG)>0 || $(API_ARMOR)!=0 || $(FOR_WIN10)!=0 +TCC = $(TCC) -DSQLITE_ENABLE_API_ARMOR=1 +RCC = $(RCC) -DSQLITE_ENABLE_API_ARMOR=1 +!ENDIF + +!IF $(DEBUG)>2 +TCC = $(TCC) -DSQLITE_DEBUG=1 +RCC = $(RCC) -DSQLITE_DEBUG=1 +!IF $(DYNAMIC_SHELL)==0 +TCC = $(TCC) -DSQLITE_ENABLE_WHERETRACE -DSQLITE_ENABLE_SELECTTRACE +RCC = $(RCC) -DSQLITE_ENABLE_WHERETRACE -DSQLITE_ENABLE_SELECTTRACE +!ENDIF +!ENDIF + +!IF $(DEBUG)>4 || $(OSTRACE)!=0 +TCC = $(TCC) -DSQLITE_FORCE_OS_TRACE=1 -DSQLITE_DEBUG_OS_TRACE=1 +RCC = $(RCC) -DSQLITE_FORCE_OS_TRACE=1 -DSQLITE_DEBUG_OS_TRACE=1 +!ENDIF + +!IF $(DEBUG)>5 +TCC = $(TCC) -DSQLITE_ENABLE_IOTRACE=1 +RCC = $(RCC) -DSQLITE_ENABLE_IOTRACE=1 +!ENDIF + +# Prevent warnings about "insecure" MSVC runtime library functions +# being used. +# +TCC = $(TCC) -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS +BCC = $(BCC) -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS +RCC = $(RCC) -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS + +# Prevent warnings about "deprecated" POSIX functions being used. +# +TCC = $(TCC) -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS +BCC = $(BCC) -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS +RCC = $(RCC) -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS + +# Use the SQLite debugging heap subsystem? +# +!IF $(MEMDEBUG)!=0 +TCC = $(TCC) -DSQLITE_MEMDEBUG=1 +RCC = $(RCC) -DSQLITE_MEMDEBUG=1 + +# Use native Win32 heap subsystem instead of malloc/free? +# +!ELSEIF $(WIN32HEAP)!=0 +TCC = $(TCC) -DSQLITE_WIN32_MALLOC=1 +RCC = $(RCC) -DSQLITE_WIN32_MALLOC=1 + +# Validate the heap on every call into the native Win32 heap subsystem? +# +!IF $(DEBUG)>3 +TCC = $(TCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1 +RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1 +!ENDIF +!ENDIF + + +# Address sanitizer if ASAN=1 +# +!IF $(ASAN)>0 +TCC = $(TCC) /fsanitize=address +!ENDIF + + +# Compiler options needed for programs that use the readline() library. +# +!IFNDEF READLINE_FLAGS +READLINE_FLAGS = -DHAVE_READLINE=0 +!ENDIF + +# The library that programs using readline() must link against. +# +!IFNDEF LIBREADLINE +LIBREADLINE = +!ENDIF + +# Should the database engine be compiled threadsafe +# +TCC = $(TCC) -DSQLITE_THREADSAFE=1 +RCC = $(RCC) -DSQLITE_THREADSAFE=1 + +# Do threads override each others locks by default (1), or do we test (-1) +# +TCC = $(TCC) -DSQLITE_THREAD_OVERRIDE_LOCK=-1 +RCC = $(RCC) -DSQLITE_THREAD_OVERRIDE_LOCK=-1 + +# Any target libraries which libsqlite must be linked against +# +!IFNDEF TLIBS +TLIBS = +!ENDIF + +# Flags controlling use of the in memory btree implementation +# +# SQLITE_TEMP_STORE is 0 to force temporary tables to be in a file, 1 to +# default to file, 2 to default to memory, and 3 to force temporary +# tables to always be in memory. +# +TCC = $(TCC) -DSQLITE_TEMP_STORE=1 +RCC = $(RCC) -DSQLITE_TEMP_STORE=1 + +# Enable/disable loadable extensions, and other optional features +# based on configuration. (-DSQLITE_OMIT*, -DSQLITE_ENABLE*). +# The same set of OMIT and ENABLE flags should be passed to the +# LEMON parser generator and the mkkeywordhash tool as well. + +# These are the required SQLite compilation options used when compiling for +# the Windows platform. +# +REQ_FEATURE_FLAGS = $(REQ_FEATURE_FLAGS) -DSQLITE_MAX_TRIGGER_DEPTH=100 + +# If we are linking to the RPCRT4 library, enable features that need it. +# +!IF $(USE_RPCRT4_LIB)!=0 +REQ_FEATURE_FLAGS = $(REQ_FEATURE_FLAGS) -DSQLITE_WIN32_USE_UUID=1 +!ENDIF + +# Add the required and optional SQLite compilation options into the command +# lines used to invoke the MSVC code and resource compilers. +# +TCC = $(TCC) $(REQ_FEATURE_FLAGS) $(OPT_FEATURE_FLAGS) $(EXT_FEATURE_FLAGS) +RCC = $(RCC) $(REQ_FEATURE_FLAGS) $(OPT_FEATURE_FLAGS) $(EXT_FEATURE_FLAGS) + +# Add in any optional parameters specified on the commane line, e.g. +# nmake /f Makefile.msc all "OPTS=-DSQLITE_ENABLE_FOO=1 -DSQLITE_OMIT_FOO=1" +# +TCC = $(TCC) $(OPTS) +RCC = $(RCC) $(OPTS) + +# If compiling for debugging, add some defines. +# +!IF $(DEBUG)>1 +TCC = $(TCC) -D_DEBUG +BCC = $(BCC) -D_DEBUG +RCC = $(RCC) -D_DEBUG +!ENDIF + +# If optimizations are enabled or disabled (either implicitly or +# explicitly), add the necessary flags. +# +!IF $(DEBUG)>1 || $(OPTIMIZATIONS)==0 +TCC = $(TCC) -Od +BCC = $(BCC) -Od +!IF $(USE_RUNTIME_CHECKS)!=0 +TCC = $(TCC) -RTC1 +BCC = $(BCC) -RTC1 +!ENDIF +!ELSEIF $(OPTIMIZATIONS)>=3 +TCC = $(TCC) -Ox +BCC = $(BCC) -Ox +!ELSEIF $(OPTIMIZATIONS)==2 +TCC = $(TCC) -O2 +BCC = $(BCC) -O2 +!ELSEIF $(OPTIMIZATIONS)==1 +TCC = $(TCC) -O1 +BCC = $(BCC) -O1 +!ENDIF + +# If symbols are enabled (or compiling for debugging), enable PDBs. +# +!IF $(DEBUG)>1 || $(SYMBOLS)!=0 +TCC = $(TCC) -Zi +BCC = $(BCC) -Zi +!ENDIF + + +# Command line prefixes for compiling code, compiling resources, +# linking, etc. +# +LTCOMPILE = $(TCC) -Fo$@ -Fd$*.pdb +LTRCOMPILE = $(RCC) -r +LTLIB = lib.exe +LTLINK = $(TCC) -Fe$@ + +# If requested, link to the RPCRT4 library. +# +!IF $(USE_RPCRT4_LIB)!=0 +LTLIBS = $(LTLIBS) rpcrt4.lib +!ENDIF + +# If a platform was set, force the linker to target that. +# Note that the vcvars*.bat family of batch files typically +# set this for you. Otherwise, the linker will attempt +# to deduce the binary type based on the object files. +!IFDEF PLATFORM +LTLINKOPTS = /NOLOGO /MACHINE:$(PLATFORM) +LTLIBOPTS = /NOLOGO /MACHINE:$(PLATFORM) +!ELSEIF "$(VISUALSTUDIOVERSION)"=="12.0" || \ + "$(VISUALSTUDIOVERSION)"=="14.0" || \ + "$(VISUALSTUDIOVERSION)"=="15.0" +LTLINKOPTS = /NOLOGO /MACHINE:x86 +LTLIBOPTS = /NOLOGO /MACHINE:x86 +!ELSE +LTLINKOPTS = /NOLOGO +LTLIBOPTS = /NOLOGO +!ENDIF + +# When compiling for use in the WinRT environment, the following +# linker option must be used to mark the executable as runnable +# only in the context of an application container. +# +!IF $(FOR_WINRT)!=0 +LTLINKOPTS = $(LTLINKOPTS) /APPCONTAINER +!IF "$(VISUALSTUDIOVERSION)"=="12.0" || "$(VISUALSTUDIOVERSION)"=="14.0" +!IFNDEF STORELIBPATH +!IF "$(PLATFORM)"=="x86" +STORELIBPATH = $(CRTLIBPATH)\store +!ELSEIF "$(PLATFORM)"=="x64" +STORELIBPATH = $(CRTLIBPATH)\store\amd64 +!ELSEIF "$(PLATFORM)"=="ARM" +STORELIBPATH = $(CRTLIBPATH)\store\arm +!ELSE +STORELIBPATH = $(CRTLIBPATH)\store +!ENDIF +!ENDIF +STORELIBPATH = $(STORELIBPATH:\\=\) +LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(STORELIBPATH)" +!ENDIF +!ENDIF + +# When compiling for Windows Phone 8.1, an extra library path is +# required. +# +!IF $(USE_WP81_OPTS)!=0 +!IFNDEF WP81LIBPATH +!IF "$(PLATFORM)"=="x86" +WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\x86 +!ELSEIF "$(PLATFORM)"=="ARM" +WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\ARM +!ELSE +WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\x86 +!ENDIF +!ENDIF +!ENDIF + +# When compiling for Windows Phone 8.1, some extra linker options +# are also required. +# +!IF $(USE_WP81_OPTS)!=0 +!IFDEF WP81LIBPATH +LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(WP81LIBPATH)" +!ENDIF +LTLINKOPTS = $(LTLINKOPTS) /DYNAMICBASE +LTLINKOPTS = $(LTLINKOPTS) WindowsPhoneCore.lib RuntimeObject.lib PhoneAppModelHost.lib +LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:kernel32.lib /NODEFAULTLIB:ole32.lib +!ENDIF + +# When compiling for UWP or the Windows 10 platform, some extra linker +# options are also required. +# +!IF $(FOR_UWP)!=0 || $(FOR_WIN10)!=0 +LTLINKOPTS = $(LTLINKOPTS) /DYNAMICBASE /NODEFAULTLIB:kernel32.lib +LTLINKOPTS = $(LTLINKOPTS) mincore.lib +!IFDEF PSDKLIBPATH +LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(PSDKLIBPATH)" +!ENDIF +!ENDIF + +!IF $(FOR_WIN10)!=0 +LTLINKOPTS = $(LTLINKOPTS) /guard:cf "/LIBPATH:$(UCRTLIBPATH)" +!IF $(DEBUG)>1 +LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:libucrtd.lib /DEFAULTLIB:ucrtd.lib +!ELSE +LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib +!ENDIF +!ENDIF + +# If either debugging or symbols are enabled, enable PDBs. +# +!IF $(DEBUG)>1 || $(SYMBOLS)!=0 +LDFLAGS = /DEBUG $(LDOPTS) +!ELSE +LDFLAGS = $(LDOPTS) +!ENDIF + + +# You should not have to change anything below this line +############################################################################### + + +# Object files for the amalgamation. +# +LIBOBJS1 = sqlite3.lo + +# Determine the real value of LIBOBJ based on the 'configure' script +# +LIBOBJ = $(LIBOBJS1) + +# Determine if embedded resource compilation and usage are enabled. +# +!IF $(USE_RC)!=0 +LIBRESOBJS = sqlite3res.lo +!ELSE +LIBRESOBJS = +!ENDIF + + +# Additional compiler options for the shell. These are only effective +# when the shell is not being dynamically linked. +# +!IF $(DYNAMIC_SHELL)==0 && $(FOR_WIN10)==0 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_DQS=0 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1 +!ENDIF + + +# This is the default Makefile target. The objects listed here +# are what get build when you type just "make" with no arguments. +# +core: dll shell + +# Targets that require the Tcl library. +# +tcl: $(ALL_TCL_TARGETS) + +# This Makefile target builds all of the standard binaries. +# +all: core tcl + +# Dynamic link library section. +# +dll: $(SQLITE3DLL) + +# Shell executable. +# +shell: $(SQLITE3EXE) + + +$(SQLITE3DLL): $(LIBOBJ) $(LIBRESOBJS) $(CORE_LINK_DEP) + $(LD) $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) /DLL $(CORE_LINK_OPTS) /OUT:$@ $(LIBOBJ) $(LIBRESOBJS) $(LTLIBS) $(TLIBS) + +Replace.exe: + $(CSC) /target:exe $(TOP)\Replace.cs + +sqlite3.def: Replace.exe $(LIBOBJ) + echo EXPORTS > sqlite3.def + dumpbin /all $(LIBOBJ) \ + | .\Replace.exe "^\s+/EXPORT:_?(sqlite3(?:session|changeset|changegroup|rebaser|rbu)?_[^@,]*)(?:@\d+|,DATA)?$$" $$1 true \ + | sort >> sqlite3.def + +$(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLITE3H) + $(LTLINK) $(SHELL_COMPILE_OPTS) $(READLINE_FLAGS) shell.c $(SHELL_CORE_SRC) \ + /link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS) + + +# Rule to build the amalgamation +# +sqlite3.lo: $(SQLITE3C) + $(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(SQLITE3C) + + +# Rule to build the Win32 resources object file. +# +!IF $(USE_RC)!=0 +_HASHCHAR=^# +!IF ![echo !IFNDEF VERSION > rcver.vc] && \ + ![for /F "delims=" %V in ('type "$(SQLITE3H)" ^| "%SystemRoot%\System32\find.exe" "$(_HASHCHAR)define SQLITE_VERSION "') do (echo VERSION = ^^%V >> rcver.vc)] && \ + ![echo !ENDIF >> rcver.vc] +!INCLUDE rcver.vc +!ENDIF + +RESOURCE_VERSION = $(VERSION:^#=) +RESOURCE_VERSION = $(RESOURCE_VERSION:define=) +RESOURCE_VERSION = $(RESOURCE_VERSION:SQLITE_VERSION=) +RESOURCE_VERSION = $(RESOURCE_VERSION:"=) +RESOURCE_VERSION = $(RESOURCE_VERSION:.=,) + +$(LIBRESOBJS): $(TOP)\sqlite3.rc rcver.vc $(SQLITE3H) + echo #ifndef SQLITE_RESOURCE_VERSION > sqlite3rc.h + echo #define SQLITE_RESOURCE_VERSION $(RESOURCE_VERSION) >> sqlite3rc.h + echo #endif >> sqlite3rc.h + $(LTRCOMPILE) -fo $(LIBRESOBJS) -DRC_VERONLY $(TOP)\sqlite3.rc +!ENDIF + + +clean: + del /Q *.exp *.lo *.ilk *.lib *.obj *.ncb *.pdb *.sdf *.suo 2>NUL + del /Q *.bsc *.def *.cod *.da *.bb *.bbg *.vc gmon.out 2>NUL + del /Q $(SQLITE3EXE) $(SQLITE3DLL) Replace.exe 2>NUL diff --git a/SQLITE/README.txt b/SQLITE/README.txt new file mode 100644 index 0000000..ccf5e23 --- /dev/null +++ b/SQLITE/README.txt @@ -0,0 +1,113 @@ +This package contains: + + * the SQLite library amalgamation source code file: sqlite3.c + * the sqlite3.h and sqlite3ext.h header files that define the C-language + interface to the sqlite3.c library file + * the shell.c file used to build the sqlite3 command-line shell program + * autoconf/automake installation infrastucture for building on POSIX + compliant systems + * a Makefile.msc, sqlite3.rc, and Replace.cs for building with Microsoft + Visual C++ on Windows + +SUMMARY OF HOW TO BUILD +======================= + + Unix: ./configure; make + Windows: nmake /f Makefile.msc + +BUILDING ON POSIX +================= + +The generic installation instructions for autoconf/automake are found +in the INSTALL file. + +The following SQLite specific boolean options are supported: + + --enable-readline use readline in shell tool [default=yes] + --enable-threadsafe build a thread-safe library [default=yes] + --enable-dynamic-extensions support loadable extensions [default=yes] + +The default value for the CFLAGS variable (options passed to the C +compiler) includes debugging symbols in the build, resulting in larger +binaries than are necessary. Override it on the configure command +line like this: + + $ CFLAGS="-Os" ./configure + +to produce a smaller installation footprint. + +Other SQLite compilation parameters can also be set using CFLAGS. For +example: + + $ CFLAGS="-Os -DSQLITE_THREADSAFE=0" ./configure + + +BUILDING WITH MICROSOFT VISUAL C++ +================================== + +To compile for Windows using Microsoft Visual C++: + + $ nmake /f Makefile.msc + +Using Microsoft Visual C++ 2005 (or later) is recommended. Several Windows +platform variants may be built by adding additional macros to the NMAKE +command line. + +Building for WinRT 8.0 +---------------------- + + FOR_WINRT=1 + +Using Microsoft Visual C++ 2012 (or later) is required. When using the +above, something like the following macro will need to be added to the +NMAKE command line as well: + + "NSDKLIBPATH=%WindowsSdkDir%\..\8.0\lib\win8\um\x86" + +Building for WinRT 8.1 +---------------------- + + FOR_WINRT=1 + +Using Microsoft Visual C++ 2013 (or later) is required. When using the +above, something like the following macro will need to be added to the +NMAKE command line as well: + + "NSDKLIBPATH=%WindowsSdkDir%\..\8.1\lib\winv6.3\um\x86" + +Building for UWP 10.0 +--------------------- + + FOR_WINRT=1 FOR_UWP=1 + +Using Microsoft Visual C++ 2015 (or later) is required. When using the +above, something like the following macros will need to be added to the +NMAKE command line as well: + + "NSDKLIBPATH=%WindowsSdkDir%\..\10\lib\10.0.10586.0\um\x86" + "PSDKLIBPATH=%WindowsSdkDir%\..\10\lib\10.0.10586.0\um\x86" + "NUCRTLIBPATH=%UniversalCRTSdkDir%\..\10\lib\10.0.10586.0\ucrt\x86" + +Building for the Windows 10 SDK +------------------------------- + + FOR_WIN10=1 + +Using Microsoft Visual C++ 2015 (or later) is required. When using the +above, no other macros should be needed on the NMAKE command line. + +Other preprocessor defines +-------------------------- + +Additionally, preprocessor defines may be specified by using the OPTS macro +on the NMAKE command line. However, not all possible preprocessor defines +may be specified in this manner as some require the amalgamation to be built +with them enabled (see http://www.sqlite.org/compile.html). For example, the +following will work: + + "OPTS=-DSQLITE_ENABLE_STAT4=1 -DSQLITE_OMIT_JSON=1" + +However, the following will not compile unless the amalgamation was built +with it enabled: + + "OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1" diff --git a/SQLITE/Replace.cs b/SQLITE/Replace.cs new file mode 100644 index 0000000..3475a47 --- /dev/null +++ b/SQLITE/Replace.cs @@ -0,0 +1,223 @@ +/* +** 2016 February 26 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C# code to perform regular expression replacements +** using the standard input and output channels. +*/ + +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +/////////////////////////////////////////////////////////////////////////////// + +#region Assembly Metadata +[assembly: AssemblyTitle("Replace Tool")] +[assembly: AssemblyDescription("Replace text using standard input/output.")] +[assembly: AssemblyCompany("SQLite Development Team")] +[assembly: AssemblyProduct("SQLite")] +[assembly: AssemblyCopyright("Public Domain")] +[assembly: ComVisible(false)] +[assembly: Guid("95a0513f-8863-48cd-a76f-cb80868cb578")] +[assembly: AssemblyVersion("1.0.*")] + +#if DEBUG +[assembly: AssemblyConfiguration("Debug")] +#else +[assembly: AssemblyConfiguration("Release")] +#endif +#endregion + +/////////////////////////////////////////////////////////////////////////////// + +namespace Replace +{ + /// <summary> + /// This enumeration is used to represent all the possible exit codes from + /// this tool. + /// </summary> + internal enum ExitCode + { + /// <summary> + /// The file download was a success. + /// </summary> + Success = 0, + + /// <summary> + /// The command line arguments are missing (i.e. null). Generally, + /// this should not happen. + /// </summary> + MissingArgs = 1, + + /// <summary> + /// The wrong number of command line arguments was supplied. + /// </summary> + WrongNumArgs = 2, + + /// <summary> + /// The "matchingOnly" flag could not be converted to a value of the + /// <see cref="Boolean"/> type. + /// </summary> + BadMatchingOnlyFlag = 3, + + /// <summary> + /// An exception was caught in <see cref="Main" />. Generally, this + /// should not happen. + /// </summary> + Exception = 4 + } + + /////////////////////////////////////////////////////////////////////////// + + internal static class Replace + { + #region Private Support Methods + /// <summary> + /// This method displays an error message to the console and/or + /// displays the command line usage information for this tool. + /// </summary> + /// <param name="message"> + /// The error message to display, if any. + /// </param> + /// <param name="usage"> + /// Non-zero to display the command line usage information. + /// </param> + private static void Error( + string message, + bool usage + ) + { + if (message != null) + Console.WriteLine(message); + + string fileName = Path.GetFileName( + Process.GetCurrentProcess().MainModule.FileName); + + Console.WriteLine(String.Format( + "usage: {0} <regExPattern> <regExSubSpec> <matchingOnly>", + fileName)); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Program Entry Point + /// <summary> + /// This is the entry-point for this tool. It handles processing the + /// command line arguments, reading from the standard input channel, + /// replacing any matching lines of text, and writing to the standard + /// output channel. + /// </summary> + /// <param name="args"> + /// The command line arguments. + /// </param> + /// <returns> + /// Zero upon success; non-zero on failure. This will be one of the + /// values from the <see cref="ExitCode" /> enumeration. + /// </returns> + private static int Main( + string[] args + ) + { + // + // NOTE: Sanity check the command line arguments. + // + if (args == null) + { + Error(null, true); + return (int)ExitCode.MissingArgs; + } + + if (args.Length != 3) + { + Error(null, true); + return (int)ExitCode.WrongNumArgs; + } + + try + { + // + // NOTE: Create a regular expression from the first command + // line argument. Then, grab the replacement string, + // which is the second argument. + // + Regex regEx = new Regex(args[0]); + string replacement = args[1]; + + // + // NOTE: Attempt to convert the third argument to a boolean. + // + bool matchingOnly; + + if (!bool.TryParse(args[2], out matchingOnly)) + { + Error(null, true); + return (int)ExitCode.BadMatchingOnlyFlag; + } + + // + // NOTE: Grab the standard input and output channels from the + // console. + // + TextReader inputTextReader = Console.In; + TextWriter outputTextWriter = Console.Out; + + // + // NOTE: Loop until end-of-file is hit on the standard input + // stream. + // + while (true) + { + // + // NOTE: Read a line from the standard input channel. If + // null is returned here, there is no more input and + // we are done. + // + string inputLine = inputTextReader.ReadLine(); + + if (inputLine == null) + break; + + // + // NOTE: Perform regular expression replacements on this + // line, if any. Then, write the modified line to + // the standard output channel. + // + string outputLine = regEx.Replace(inputLine, replacement); + + if (!matchingOnly || !String.Equals( + inputLine, outputLine, StringComparison.Ordinal)) + { + outputTextWriter.WriteLine(outputLine); + } + } + + // + // NOTE: At this point, everything has succeeded. + // + return (int)ExitCode.Success; + } + catch (Exception e) + { + // + // NOTE: An exception was caught. Report it via the console + // and return failure. + // + Error(e.ToString(), false); + return (int)ExitCode.Exception; + } + } + #endregion + } +} diff --git a/SQLITE/aclocal.m4 b/SQLITE/aclocal.m4 new file mode 100644 index 0000000..f7ad092 --- /dev/null +++ b/SQLITE/aclocal.m4 @@ -0,0 +1,10200 @@ +# generated automatically by aclocal 1.15 -*- Autoconf -*- + +# Copyright (C) 1996-2014 Free Software Foundation, Inc. + +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],, +[m4_warning([this file was generated for autoconf 2.69. +You have another version of autoconf. It may work, but is not guaranteed to. +If you have problems, you may need to regenerate the build system entirely. +To do so, use the procedure documented by the package, typically 'autoreconf'.])]) + +# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- +# +# Copyright (C) 1996-2001, 2003-2015 Free Software Foundation, Inc. +# Written by Gordon Matzigkeit, 1996 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +m4_define([_LT_COPYING], [dnl +# Copyright (C) 2014 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program or library that is built +# using GNU Libtool, you may include this file under the same +# distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +]) + +# serial 58 LT_INIT + + +# LT_PREREQ(VERSION) +# ------------------ +# Complain and exit if this libtool version is less that VERSION. +m4_defun([LT_PREREQ], +[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1, + [m4_default([$3], + [m4_fatal([Libtool version $1 or higher is required], + 63)])], + [$2])]) + + +# _LT_CHECK_BUILDDIR +# ------------------ +# Complain if the absolute build directory name contains unusual characters +m4_defun([_LT_CHECK_BUILDDIR], +[case `pwd` in + *\ * | *\ *) + AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;; +esac +]) + + +# LT_INIT([OPTIONS]) +# ------------------ +AC_DEFUN([LT_INIT], +[AC_PREREQ([2.62])dnl We use AC_PATH_PROGS_FEATURE_CHECK +AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +AC_BEFORE([$0], [LT_LANG])dnl +AC_BEFORE([$0], [LT_OUTPUT])dnl +AC_BEFORE([$0], [LTDL_INIT])dnl +m4_require([_LT_CHECK_BUILDDIR])dnl + +dnl Autoconf doesn't catch unexpanded LT_ macros by default: +m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl +m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl +dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4 +dnl unless we require an AC_DEFUNed macro: +AC_REQUIRE([LTOPTIONS_VERSION])dnl +AC_REQUIRE([LTSUGAR_VERSION])dnl +AC_REQUIRE([LTVERSION_VERSION])dnl +AC_REQUIRE([LTOBSOLETE_VERSION])dnl +m4_require([_LT_PROG_LTMAIN])dnl + +_LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}]) + +dnl Parse OPTIONS +_LT_SET_OPTIONS([$0], [$1]) + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS=$ltmain + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' +AC_SUBST(LIBTOOL)dnl + +_LT_SETUP + +# Only expand once: +m4_define([LT_INIT]) +])# LT_INIT + +# Old names: +AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT]) +AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PROG_LIBTOOL], []) +dnl AC_DEFUN([AM_PROG_LIBTOOL], []) + + +# _LT_PREPARE_CC_BASENAME +# ----------------------- +m4_defun([_LT_PREPARE_CC_BASENAME], [ +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in @S|@*""; do + case $cc_temp in + compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;; + distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} +])# _LT_PREPARE_CC_BASENAME + + +# _LT_CC_BASENAME(CC) +# ------------------- +# It would be clearer to call AC_REQUIREs from _LT_PREPARE_CC_BASENAME, +# but that macro is also expanded into generated libtool script, which +# arranges for $SED and $ECHO to be set by different means. +m4_defun([_LT_CC_BASENAME], +[m4_require([_LT_PREPARE_CC_BASENAME])dnl +AC_REQUIRE([_LT_DECL_SED])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl +func_cc_basename $1 +cc_basename=$func_cc_basename_result +]) + + +# _LT_FILEUTILS_DEFAULTS +# ---------------------- +# It is okay to use these file commands and assume they have been set +# sensibly after 'm4_require([_LT_FILEUTILS_DEFAULTS])'. +m4_defun([_LT_FILEUTILS_DEFAULTS], +[: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} +])# _LT_FILEUTILS_DEFAULTS + + +# _LT_SETUP +# --------- +m4_defun([_LT_SETUP], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl + +_LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl +dnl +_LT_DECL([], [host_alias], [0], [The host system])dnl +_LT_DECL([], [host], [0])dnl +_LT_DECL([], [host_os], [0])dnl +dnl +_LT_DECL([], [build_alias], [0], [The build system])dnl +_LT_DECL([], [build], [0])dnl +_LT_DECL([], [build_os], [0])dnl +dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +dnl +AC_REQUIRE([AC_PROG_LN_S])dnl +test -z "$LN_S" && LN_S="ln -s" +_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl +dnl +AC_REQUIRE([LT_CMD_MAX_LEN])dnl +_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl +_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl +dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl +m4_require([_LT_CMD_RELOAD])dnl +m4_require([_LT_CHECK_MAGIC_METHOD])dnl +m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl +m4_require([_LT_CMD_OLD_ARCHIVE])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_WITH_SYSROOT])dnl +m4_require([_LT_CMD_TRUNCATE])dnl + +_LT_CONFIG_LIBTOOL_INIT([ +# See if we are running on zsh, and set the options that allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi +]) +if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + +_LT_CHECK_OBJDIR + +m4_require([_LT_TAG_COMPILER])dnl + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld=$lt_cv_prog_gnu_ld + +old_CC=$CC +old_CFLAGS=$CFLAGS + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +_LT_CC_BASENAME([$compiler]) + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + _LT_PATH_MAGIC + fi + ;; +esac + +# Use C for the default configuration in the libtool script +LT_SUPPORTED_TAG([CC]) +_LT_LANG_C_CONFIG +_LT_LANG_DEFAULT_CONFIG +_LT_CONFIG_COMMANDS +])# _LT_SETUP + + +# _LT_PREPARE_SED_QUOTE_VARS +# -------------------------- +# Define a few sed substitution that help us do robust quoting. +m4_defun([_LT_PREPARE_SED_QUOTE_VARS], +[# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\([["`$\\]]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\([["`\\]]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' +]) + +# _LT_PROG_LTMAIN +# --------------- +# Note that this code is called both from 'configure', and 'config.status' +# now that we use AC_CONFIG_COMMANDS to generate libtool. Notably, +# 'config.status' has no value for ac_aux_dir unless we are using Automake, +# so we pass a copy along to make sure it has a sensible value anyway. +m4_defun([_LT_PROG_LTMAIN], +[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl +_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir']) +ltmain=$ac_aux_dir/ltmain.sh +])# _LT_PROG_LTMAIN + + + +# So that we can recreate a full libtool script including additional +# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS +# in macros and then make a single call at the end using the 'libtool' +# label. + + +# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS]) +# ---------------------------------------- +# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL_INIT], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_INIT], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_INIT]) + + +# _LT_CONFIG_LIBTOOL([COMMANDS]) +# ------------------------------ +# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS]) + + +# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS]) +# ----------------------------------------------------- +m4_defun([_LT_CONFIG_SAVE_COMMANDS], +[_LT_CONFIG_LIBTOOL([$1]) +_LT_CONFIG_LIBTOOL_INIT([$2]) +]) + + +# _LT_FORMAT_COMMENT([COMMENT]) +# ----------------------------- +# Add leading comment marks to the start of each line, and a trailing +# full-stop to the whole comment if one is not present already. +m4_define([_LT_FORMAT_COMMENT], +[m4_ifval([$1], [ +m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])], + [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.]) +)]) + + + + + +# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?]) +# ------------------------------------------------------------------- +# CONFIGNAME is the name given to the value in the libtool script. +# VARNAME is the (base) name used in the configure script. +# VALUE may be 0, 1 or 2 for a computed quote escaped value based on +# VARNAME. Any other value will be used directly. +m4_define([_LT_DECL], +[lt_if_append_uniq([lt_decl_varnames], [$2], [, ], + [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name], + [m4_ifval([$1], [$1], [$2])]) + lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3]) + m4_ifval([$4], + [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])]) + lt_dict_add_subkey([lt_decl_dict], [$2], + [tagged?], [m4_ifval([$5], [yes], [no])])]) +]) + + +# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION]) +# -------------------------------------------------------- +m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])]) + + +# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_tag_varnames], +[_lt_decl_filter([tagged?], [yes], $@)]) + + +# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..]) +# --------------------------------------------------------- +m4_define([_lt_decl_filter], +[m4_case([$#], + [0], [m4_fatal([$0: too few arguments: $#])], + [1], [m4_fatal([$0: too few arguments: $#: $1])], + [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)], + [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)], + [lt_dict_filter([lt_decl_dict], $@)])[]dnl +]) + + +# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...]) +# -------------------------------------------------- +m4_define([lt_decl_quote_varnames], +[_lt_decl_filter([value], [1], $@)]) + + +# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_dquote_varnames], +[_lt_decl_filter([value], [2], $@)]) + + +# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_varnames_tagged], +[m4_assert([$# <= 2])dnl +_$0(m4_quote(m4_default([$1], [[, ]])), + m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]), + m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))]) +m4_define([_lt_decl_varnames_tagged], +[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])]) + + +# lt_decl_all_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_all_varnames], +[_$0(m4_quote(m4_default([$1], [[, ]])), + m4_if([$2], [], + m4_quote(lt_decl_varnames), + m4_quote(m4_shift($@))))[]dnl +]) +m4_define([_lt_decl_all_varnames], +[lt_join($@, lt_decl_varnames_tagged([$1], + lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl +]) + + +# _LT_CONFIG_STATUS_DECLARE([VARNAME]) +# ------------------------------------ +# Quote a variable value, and forward it to 'config.status' so that its +# declaration there will have the same value as in 'configure'. VARNAME +# must have a single quote delimited value for this to work. +m4_define([_LT_CONFIG_STATUS_DECLARE], +[$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`']) + + +# _LT_CONFIG_STATUS_DECLARATIONS +# ------------------------------ +# We delimit libtool config variables with single quotes, so when +# we write them to config.status, we have to be sure to quote all +# embedded single quotes properly. In configure, this macro expands +# each variable declared with _LT_DECL (and _LT_TAGDECL) into: +# +# <var>='`$ECHO "$<var>" | $SED "$delay_single_quote_subst"`' +m4_defun([_LT_CONFIG_STATUS_DECLARATIONS], +[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames), + [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAGS +# ---------------- +# Output comment and list of tags supported by the script +m4_defun([_LT_LIBTOOL_TAGS], +[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl +available_tags='_LT_TAGS'dnl +]) + + +# _LT_LIBTOOL_DECLARE(VARNAME, [TAG]) +# ----------------------------------- +# Extract the dictionary values for VARNAME (optionally with TAG) and +# expand to a commented shell variable setting: +# +# # Some comment about what VAR is for. +# visible_name=$lt_internal_name +m4_define([_LT_LIBTOOL_DECLARE], +[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], + [description])))[]dnl +m4_pushdef([_libtool_name], + m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl +m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])), + [0], [_libtool_name=[$]$1], + [1], [_libtool_name=$lt_[]$1], + [2], [_libtool_name=$lt_[]$1], + [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl +m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl +]) + + +# _LT_LIBTOOL_CONFIG_VARS +# ----------------------- +# Produce commented declarations of non-tagged libtool config variables +# suitable for insertion in the LIBTOOL CONFIG section of the 'libtool' +# script. Tagged libtool config variables (even for the LIBTOOL CONFIG +# section) are produced by _LT_LIBTOOL_TAG_VARS. +m4_defun([_LT_LIBTOOL_CONFIG_VARS], +[m4_foreach([_lt_var], + m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAG_VARS(TAG) +# ------------------------- +m4_define([_LT_LIBTOOL_TAG_VARS], +[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])]) + + +# _LT_TAGVAR(VARNAME, [TAGNAME]) +# ------------------------------ +m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])]) + + +# _LT_CONFIG_COMMANDS +# ------------------- +# Send accumulated output to $CONFIG_STATUS. Thanks to the lists of +# variables for single and double quote escaping we saved from calls +# to _LT_DECL, we can put quote escaped variables declarations +# into 'config.status', and then the shell code to quote escape them in +# for loops in 'config.status'. Finally, any additional code accumulated +# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded. +m4_defun([_LT_CONFIG_COMMANDS], +[AC_PROVIDE_IFELSE([LT_OUTPUT], + dnl If the libtool generation code has been placed in $CONFIG_LT, + dnl instead of duplicating it all over again into config.status, + dnl then we will have config.status run $CONFIG_LT later, so it + dnl needs to know what name is stored there: + [AC_CONFIG_COMMANDS([libtool], + [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])], + dnl If the libtool generation code is destined for config.status, + dnl expand the accumulated commands and init code now: + [AC_CONFIG_COMMANDS([libtool], + [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])]) +])#_LT_CONFIG_COMMANDS + + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT], +[ + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +_LT_CONFIG_STATUS_DECLARATIONS +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$[]1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_quote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_dquote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +_LT_OUTPUT_LIBTOOL_INIT +]) + +# _LT_GENERATED_FILE_INIT(FILE, [COMMENT]) +# ------------------------------------ +# Generate a child script FILE with all initialization necessary to +# reuse the environment learned by the parent script, and make the +# file executable. If COMMENT is supplied, it is inserted after the +# '#!' sequence but before initialization text begins. After this +# macro, additional text can be appended to FILE to form the body of +# the child script. The macro ends with non-zero status if the +# file could not be fully written (such as if the disk is full). +m4_ifdef([AS_INIT_GENERATED], +[m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])], +[m4_defun([_LT_GENERATED_FILE_INIT], +[m4_require([AS_PREPARE])]dnl +[m4_pushdef([AS_MESSAGE_LOG_FD])]dnl +[lt_write_fail=0 +cat >$1 <<_ASEOF || lt_write_fail=1 +#! $SHELL +# Generated by $as_me. +$2 +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$1 <<\_ASEOF || lt_write_fail=1 +AS_SHELL_SANITIZE +_AS_PREPARE +exec AS_MESSAGE_FD>&1 +_ASEOF +test 0 = "$lt_write_fail" && chmod +x $1[]dnl +m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT + +# LT_OUTPUT +# --------- +# This macro allows early generation of the libtool script (before +# AC_OUTPUT is called), incase it is used in configure for compilation +# tests. +AC_DEFUN([LT_OUTPUT], +[: ${CONFIG_LT=./config.lt} +AC_MSG_NOTICE([creating $CONFIG_LT]) +_LT_GENERATED_FILE_INIT(["$CONFIG_LT"], +[# Run this file to recreate a libtool stub with the current configuration.]) + +cat >>"$CONFIG_LT" <<\_LTEOF +lt_cl_silent=false +exec AS_MESSAGE_LOG_FD>>config.log +{ + echo + AS_BOX([Running $as_me.]) +} >&AS_MESSAGE_LOG_FD + +lt_cl_help="\ +'$as_me' creates a local libtool stub from the current configuration, +for use in further configure time tests before the real libtool is +generated. + +Usage: $[0] [[OPTIONS]] + + -h, --help print this help, then exit + -V, --version print version number, then exit + -q, --quiet do not print progress messages + -d, --debug don't remove temporary files + +Report bugs to <bug-libtool@gnu.org>." + +lt_cl_version="\ +m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl +m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION]) +configured by $[0], generated by m4_PACKAGE_STRING. + +Copyright (C) 2011 Free Software Foundation, Inc. +This config.lt script is free software; the Free Software Foundation +gives unlimited permision to copy, distribute and modify it." + +while test 0 != $[#] +do + case $[1] in + --version | --v* | -V ) + echo "$lt_cl_version"; exit 0 ;; + --help | --h* | -h ) + echo "$lt_cl_help"; exit 0 ;; + --debug | --d* | -d ) + debug=: ;; + --quiet | --q* | --silent | --s* | -q ) + lt_cl_silent=: ;; + + -*) AC_MSG_ERROR([unrecognized option: $[1] +Try '$[0] --help' for more information.]) ;; + + *) AC_MSG_ERROR([unrecognized argument: $[1] +Try '$[0] --help' for more information.]) ;; + esac + shift +done + +if $lt_cl_silent; then + exec AS_MESSAGE_FD>/dev/null +fi +_LTEOF + +cat >>"$CONFIG_LT" <<_LTEOF +_LT_OUTPUT_LIBTOOL_COMMANDS_INIT +_LTEOF + +cat >>"$CONFIG_LT" <<\_LTEOF +AC_MSG_NOTICE([creating $ofile]) +_LT_OUTPUT_LIBTOOL_COMMANDS +AS_EXIT(0) +_LTEOF +chmod +x "$CONFIG_LT" + +# configure is writing to config.log, but config.lt does its own redirection, +# appending to config.log, which fails on DOS, as config.log is still kept +# open by configure. Here we exec the FD to /dev/null, effectively closing +# config.log, so it can be properly (re)opened and appended to by config.lt. +lt_cl_success=: +test yes = "$silent" && + lt_config_lt_args="$lt_config_lt_args --quiet" +exec AS_MESSAGE_LOG_FD>/dev/null +$SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false +exec AS_MESSAGE_LOG_FD>>config.log +$lt_cl_success || AS_EXIT(1) +])# LT_OUTPUT + + +# _LT_CONFIG(TAG) +# --------------- +# If TAG is the built-in tag, create an initial libtool script with a +# default configuration from the untagged config vars. Otherwise add code +# to config.status for appending the configuration named by TAG from the +# matching tagged config vars. +m4_defun([_LT_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_CONFIG_SAVE_COMMANDS([ + m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl + m4_if(_LT_TAG, [C], [ + # See if we are running on zsh, and set the options that allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST + fi + + cfgfile=${ofile}T + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL +# Generated automatically by $as_me ($PACKAGE) $VERSION +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: +# NOTE: Changes made to this file will be lost: look at ltmain.sh. + +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit, 1996 + +_LT_COPYING +_LT_LIBTOOL_TAGS + +# Configured defaults for sys_lib_dlsearch_path munging. +: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} + +# ### BEGIN LIBTOOL CONFIG +_LT_LIBTOOL_CONFIG_VARS +_LT_LIBTOOL_TAG_VARS +# ### END LIBTOOL CONFIG + +_LT_EOF + + cat <<'_LT_EOF' >> "$cfgfile" + +# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE + +_LT_PREPARE_MUNGE_PATH_LIST +_LT_PREPARE_CC_BASENAME + +# ### END FUNCTIONS SHARED WITH CONFIGURE + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + _LT_PROG_LTMAIN + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" +], +[cat <<_LT_EOF >> "$ofile" + +dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded +dnl in a comment (ie after a #). +# ### BEGIN LIBTOOL TAG CONFIG: $1 +_LT_LIBTOOL_TAG_VARS(_LT_TAG) +# ### END LIBTOOL TAG CONFIG: $1 +_LT_EOF +])dnl /m4_if +], +[m4_if([$1], [], [ + PACKAGE='$PACKAGE' + VERSION='$VERSION' + RM='$RM' + ofile='$ofile'], []) +])dnl /_LT_CONFIG_SAVE_COMMANDS +])# _LT_CONFIG + + +# LT_SUPPORTED_TAG(TAG) +# --------------------- +# Trace this macro to discover what tags are supported by the libtool +# --tag option, using: +# autoconf --trace 'LT_SUPPORTED_TAG:$1' +AC_DEFUN([LT_SUPPORTED_TAG], []) + + +# C support is built-in for now +m4_define([_LT_LANG_C_enabled], []) +m4_define([_LT_TAGS], []) + + +# LT_LANG(LANG) +# ------------- +# Enable libtool support for the given language if not already enabled. +AC_DEFUN([LT_LANG], +[AC_BEFORE([$0], [LT_OUTPUT])dnl +m4_case([$1], + [C], [_LT_LANG(C)], + [C++], [_LT_LANG(CXX)], + [Go], [_LT_LANG(GO)], + [Java], [_LT_LANG(GCJ)], + [Fortran 77], [_LT_LANG(F77)], + [Fortran], [_LT_LANG(FC)], + [Windows Resource], [_LT_LANG(RC)], + [m4_ifdef([_LT_LANG_]$1[_CONFIG], + [_LT_LANG($1)], + [m4_fatal([$0: unsupported language: "$1"])])])dnl +])# LT_LANG + + +# _LT_LANG(LANGNAME) +# ------------------ +m4_defun([_LT_LANG], +[m4_ifdef([_LT_LANG_]$1[_enabled], [], + [LT_SUPPORTED_TAG([$1])dnl + m4_append([_LT_TAGS], [$1 ])dnl + m4_define([_LT_LANG_]$1[_enabled], [])dnl + _LT_LANG_$1_CONFIG($1)])dnl +])# _LT_LANG + + +m4_ifndef([AC_PROG_GO], [ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_GO. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +m4_defun([AC_PROG_GO], +[AC_LANG_PUSH(Go)dnl +AC_ARG_VAR([GOC], [Go compiler command])dnl +AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl +_AC_ARG_VAR_LDFLAGS()dnl +AC_CHECK_TOOL(GOC, gccgo) +if test -z "$GOC"; then + if test -n "$ac_tool_prefix"; then + AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo]) + fi +fi +if test -z "$GOC"; then + AC_CHECK_PROG(GOC, gccgo, gccgo, false) +fi +])#m4_defun +])#m4_ifndef + + +# _LT_LANG_DEFAULT_CONFIG +# ----------------------- +m4_defun([_LT_LANG_DEFAULT_CONFIG], +[AC_PROVIDE_IFELSE([AC_PROG_CXX], + [LT_LANG(CXX)], + [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])]) + +AC_PROVIDE_IFELSE([AC_PROG_F77], + [LT_LANG(F77)], + [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])]) + +AC_PROVIDE_IFELSE([AC_PROG_FC], + [LT_LANG(FC)], + [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])]) + +dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal +dnl pulling things in needlessly. +AC_PROVIDE_IFELSE([AC_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([LT_PROG_GCJ], + [LT_LANG(GCJ)], + [m4_ifdef([AC_PROG_GCJ], + [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([A][M_PROG_GCJ], + [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([LT_PROG_GCJ], + [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])]) + +AC_PROVIDE_IFELSE([AC_PROG_GO], + [LT_LANG(GO)], + [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])]) + +AC_PROVIDE_IFELSE([LT_PROG_RC], + [LT_LANG(RC)], + [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])]) +])# _LT_LANG_DEFAULT_CONFIG + +# Obsolete macros: +AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)]) +AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)]) +AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)]) +AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)]) +AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_CXX], []) +dnl AC_DEFUN([AC_LIBTOOL_F77], []) +dnl AC_DEFUN([AC_LIBTOOL_FC], []) +dnl AC_DEFUN([AC_LIBTOOL_GCJ], []) +dnl AC_DEFUN([AC_LIBTOOL_RC], []) + + +# _LT_TAG_COMPILER +# ---------------- +m4_defun([_LT_TAG_COMPILER], +[AC_REQUIRE([AC_PROG_CC])dnl + +_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl +_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl +_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl +_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC +])# _LT_TAG_COMPILER + + +# _LT_COMPILER_BOILERPLATE +# ------------------------ +# Check for compiler boilerplate output or warnings with +# the simple compiler test code. +m4_defun([_LT_COMPILER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* +])# _LT_COMPILER_BOILERPLATE + + +# _LT_LINKER_BOILERPLATE +# ---------------------- +# Check for linker boilerplate output or warnings with +# the simple link test code. +m4_defun([_LT_LINKER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* +])# _LT_LINKER_BOILERPLATE + +# _LT_REQUIRED_DARWIN_CHECKS +# ------------------------- +m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[ + case $host_os in + rhapsody* | darwin*) + AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:]) + AC_CHECK_TOOL([NMEDIT], [nmedit], [:]) + AC_CHECK_TOOL([LIPO], [lipo], [:]) + AC_CHECK_TOOL([OTOOL], [otool], [:]) + AC_CHECK_TOOL([OTOOL64], [otool64], [:]) + _LT_DECL([], [DSYMUTIL], [1], + [Tool to manipulate archived DWARF debug symbol files on Mac OS X]) + _LT_DECL([], [NMEDIT], [1], + [Tool to change global to local symbols on Mac OS X]) + _LT_DECL([], [LIPO], [1], + [Tool to manipulate fat objects and archives on Mac OS X]) + _LT_DECL([], [OTOOL], [1], + [ldd/readelf like tool for Mach-O binaries on Mac OS X]) + _LT_DECL([], [OTOOL64], [1], + [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4]) + + AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod], + [lt_cv_apple_cc_single_mod=no + if test -z "$LT_MULTI_MODULE"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test 0 = "$_lt_result"; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi]) + + AC_CACHE_CHECK([for -exported_symbols_list linker flag], + [lt_cv_ld_exported_symbols_list], + [lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [lt_cv_ld_exported_symbols_list=yes], + [lt_cv_ld_exported_symbols_list=no]) + LDFLAGS=$save_LDFLAGS + ]) + + AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load], + [lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD + echo "$AR cru libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD + $AR cru libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD + echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD + $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + ]) + case $host_os in + rhapsody* | darwin1.[[012]]) + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[[91]]*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + 10.[[012]][[,.]]*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test yes = "$lt_cv_apple_cc_single_mod"; then + _lt_dar_single_mod='$single_module' + fi + if test yes = "$lt_cv_ld_exported_symbols_list"; then + _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' + fi + if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac +]) + + +# _LT_DARWIN_LINKER_FEATURES([TAG]) +# --------------------------------- +# Checks for linker and compiler features on darwin +m4_defun([_LT_DARWIN_LINKER_FEATURES], +[ + m4_require([_LT_REQUIRED_DARWIN_CHECKS]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_automatic, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + if test yes = "$lt_cv_ld_force_load"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes], + [FC], [_LT_TAGVAR(compiler_needs_object, $1)=yes]) + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='' + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + m4_if([$1], [CXX], +[ if test yes != "$lt_cv_apple_cc_single_mod"; then + _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" + fi +],[]) + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi +]) + +# _LT_SYS_MODULE_PATH_AIX([TAGNAME]) +# ---------------------------------- +# Links a minimal program and checks the executable +# for the system default hardcoded library path. In most cases, +# this is /usr/lib:/lib, but when the MPI compilers are used +# the location of the communication and MPI libs are included too. +# If we don't find anything, use the default library path according +# to the aix ld manual. +# Store the results from the different compilers for each TAGNAME. +# Allow to override them for all tags through lt_cv_aix_libpath. +m4_defun([_LT_SYS_MODULE_PATH_AIX], +[m4_require([_LT_DECL_SED])dnl +if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])], + [AC_LINK_IFELSE([AC_LANG_PROGRAM],[ + lt_aix_libpath_sed='[ + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }]' + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi],[]) + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=/usr/lib:/lib + fi + ]) + aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1]) +fi +])# _LT_SYS_MODULE_PATH_AIX + + +# _LT_SHELL_INIT(ARG) +# ------------------- +m4_define([_LT_SHELL_INIT], +[m4_divert_text([M4SH-INIT], [$1 +])])# _LT_SHELL_INIT + + + +# _LT_PROG_ECHO_BACKSLASH +# ----------------------- +# Find how we can fake an echo command that does not interpret backslash. +# In particular, with Autoconf 2.60 or later we add some code to the start +# of the generated configure script that will find a shell with a builtin +# printf (that we can use as an echo command). +m4_defun([_LT_PROG_ECHO_BACKSLASH], +[ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +AC_MSG_CHECKING([how to print strings]) +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$[]1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "$*" +} + +case $ECHO in + printf*) AC_MSG_RESULT([printf]) ;; + print*) AC_MSG_RESULT([print -r]) ;; + *) AC_MSG_RESULT([cat]) ;; +esac + +m4_ifdef([_AS_DETECT_SUGGESTED], +[_AS_DETECT_SUGGESTED([ + test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test "X`printf %s $ECHO`" = "X$ECHO" \ + || test "X`print -r -- $ECHO`" = "X$ECHO" )])]) + +_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts]) +_LT_DECL([], [ECHO], [1], [An echo program that protects backslashes]) +])# _LT_PROG_ECHO_BACKSLASH + + +# _LT_WITH_SYSROOT +# ---------------- +AC_DEFUN([_LT_WITH_SYSROOT], +[AC_MSG_CHECKING([for sysroot]) +AC_ARG_WITH([sysroot], +[AS_HELP_STRING([--with-sysroot@<:@=DIR@:>@], + [Search for dependent libraries within DIR (or the compiler's sysroot + if not specified).])], +[], [with_sysroot=no]) + +dnl lt_sysroot will always be passed unquoted. We quote it here +dnl in case the user passed a directory name. +lt_sysroot= +case $with_sysroot in #( + yes) + if test yes = "$GCC"; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + AC_MSG_RESULT([$with_sysroot]) + AC_MSG_ERROR([The sysroot must be an absolute path.]) + ;; +esac + + AC_MSG_RESULT([${lt_sysroot:-no}]) +_LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl +[dependent libraries, and where our libraries should be installed.])]) + +# _LT_ENABLE_LOCK +# --------------- +m4_defun([_LT_ENABLE_LOCK], +[AC_ARG_ENABLE([libtool-lock], + [AS_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test no = "$enable_libtool_lock" || enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out what ABI is being produced by ac_compile, and set mode + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE=32 + ;; + *ELF-64*) + HPUX_IA64_MODE=64 + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + if test yes = "$lt_cv_prog_gnu_ld"; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +mips64*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + emul=elf + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + emul="${emul}32" + ;; + *64-bit*) + emul="${emul}64" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *MSB*) + emul="${emul}btsmip" + ;; + *LSB*) + emul="${emul}ltsmip" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *N32*) + emul="${emul}n32" + ;; + esac + LD="${LD-ld} -m $emul" + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. Note that the listed cases only cover the + # situations where additional linker options are needed (such as when + # doing 32-bit compilation for a host where ld defaults to 64-bit, or + # vice versa); the common cases where no linker options are needed do + # not appear in the list. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + case `/usr/bin/file conftest.o` in + *x86-64*) + LD="${LD-ld} -m elf32_x86_64" + ;; + *) + LD="${LD-ld} -m elf_i386" + ;; + esac + ;; + powerpc64le-*linux*) + LD="${LD-ld} -m elf32lppclinux" + ;; + powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + powerpcle-*linux*) + LD="${LD-ld} -m elf64lppc" + ;; + powerpc-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -belf" + AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, + [AC_LANG_PUSH(C) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) + AC_LANG_POP]) + if test yes != "$lt_cv_cc_needs_belf"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS=$SAVE_CFLAGS + fi + ;; +*-*solaris*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*|x86_64-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD=${LD-ld}_sol2 + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks=$enable_libtool_lock +])# _LT_ENABLE_LOCK + + +# _LT_PROG_AR +# ----------- +m4_defun([_LT_PROG_AR], +[AC_CHECK_TOOLS(AR, [ar], false) +: ${AR=ar} +: ${AR_FLAGS=cru} +_LT_DECL([], [AR], [1], [The archiver]) +_LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive]) + +AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file], + [lt_cv_ar_at_file=no + AC_COMPILE_IFELSE([AC_LANG_PROGRAM], + [echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD' + AC_TRY_EVAL([lt_ar_try]) + if test 0 -eq "$ac_status"; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + AC_TRY_EVAL([lt_ar_try]) + if test 0 -ne "$ac_status"; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + ]) + ]) + +if test no = "$lt_cv_ar_at_file"; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi +_LT_DECL([], [archiver_list_spec], [1], + [How to feed a file listing to the archiver]) +])# _LT_PROG_AR + + +# _LT_CMD_OLD_ARCHIVE +# ------------------- +m4_defun([_LT_CMD_OLD_ARCHIVE], +[_LT_PROG_AR + +AC_CHECK_TOOL(STRIP, strip, :) +test -z "$STRIP" && STRIP=: +_LT_DECL([], [STRIP], [1], [A symbol stripping program]) + +AC_CHECK_TOOL(RANLIB, ranlib, :) +test -z "$RANLIB" && RANLIB=: +_LT_DECL([], [RANLIB], [1], + [Commands used to install an old-style archive]) + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + bitrig* | openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac +_LT_DECL([], [old_postinstall_cmds], [2]) +_LT_DECL([], [old_postuninstall_cmds], [2]) +_LT_TAGDECL([], [old_archive_cmds], [2], + [Commands used to build an old-style archive]) +_LT_DECL([], [lock_old_archive_extraction], [0], + [Whether to use a lock for old archive extraction]) +])# _LT_CMD_OLD_ARCHIVE + + +# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------------------- +# Check whether the given compiler option works +AC_DEFUN([_LT_COMPILER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$3" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + fi + $RM conftest* +]) + +if test yes = "[$]$2"; then + m4_if([$5], , :, [$5]) +else + m4_if([$6], , :, [$6]) +fi +])# _LT_COMPILER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], []) + + +# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------- +# Check whether the given linker option works +AC_DEFUN([_LT_LINKER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $3" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&AS_MESSAGE_LOG_FD + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + else + $2=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS +]) + +if test yes = "[$]$2"; then + m4_if([$4], , :, [$4]) +else + m4_if([$5], , :, [$5]) +fi +])# _LT_LINKER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], []) + + +# LT_CMD_MAX_LEN +#--------------- +AC_DEFUN([LT_CMD_MAX_LEN], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +# find the maximum length of command line arguments +AC_MSG_CHECKING([the maximum length of command line arguments]) +AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl + i=0 + teststring=ABCD + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[ ]]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len" && \ + test undefined != "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test X`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test 17 != "$i" # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac +]) +if test -n "$lt_cv_sys_max_cmd_len"; then + AC_MSG_RESULT($lt_cv_sys_max_cmd_len) +else + AC_MSG_RESULT(none) +fi +max_cmd_len=$lt_cv_sys_max_cmd_len +_LT_DECL([], [max_cmd_len], [0], + [What is the maximum length of a command?]) +])# LT_CMD_MAX_LEN + +# Old name: +AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], []) + + +# _LT_HEADER_DLFCN +# ---------------- +m4_defun([_LT_HEADER_DLFCN], +[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl +])# _LT_HEADER_DLFCN + + +# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, +# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) +# ---------------------------------------------------------------- +m4_defun([_LT_TRY_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes = "$cross_compiling"; then : + [$4] +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +[#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include <dlfcn.h> +#endif + +#include <stdio.h> + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +}] +_LT_EOF + if AC_TRY_EVAL(ac_link) && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) $1 ;; + x$lt_dlneed_uscore) $2 ;; + x$lt_dlunknown|x*) $3 ;; + esac + else : + # compilation failed + $3 + fi +fi +rm -fr conftest* +])# _LT_TRY_DLOPEN_SELF + + +# LT_SYS_DLOPEN_SELF +# ------------------ +AC_DEFUN([LT_SYS_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes != "$enable_dlopen"; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen=load_add_on + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen=LoadLibrary + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl],[ + lt_cv_dlopen=dyld + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ]) + ;; + + tpf*) + # Don't try to run any link tests for TPF. We know it's impossible + # because TPF is a cross-compiler, and we know how we open DSOs. + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + lt_cv_dlopen_self=no + ;; + + *) + AC_CHECK_FUNC([shl_load], + [lt_cv_dlopen=shl_load], + [AC_CHECK_LIB([dld], [shl_load], + [lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld], + [AC_CHECK_FUNC([dlopen], + [lt_cv_dlopen=dlopen], + [AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl], + [AC_CHECK_LIB([svld], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld], + [AC_CHECK_LIB([dld], [dld_link], + [lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld]) + ]) + ]) + ]) + ]) + ]) + ;; + esac + + if test no = "$lt_cv_dlopen"; then + enable_dlopen=no + else + enable_dlopen=yes + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS=$CPPFLAGS + test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS=$LDFLAGS + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS=$LIBS + LIBS="$lt_cv_dlopen_libs $LIBS" + + AC_CACHE_CHECK([whether a program can dlopen itself], + lt_cv_dlopen_self, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, + lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) + ]) + + if test yes = "$lt_cv_dlopen_self"; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + AC_CACHE_CHECK([whether a statically linked program can dlopen itself], + lt_cv_dlopen_self_static, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, + lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) + ]) + fi + + CPPFLAGS=$save_CPPFLAGS + LDFLAGS=$save_LDFLAGS + LIBS=$save_LIBS + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi +_LT_DECL([dlopen_support], [enable_dlopen], [0], + [Whether dlopen is supported]) +_LT_DECL([dlopen_self], [enable_dlopen_self], [0], + [Whether dlopen of programs is supported]) +_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0], + [Whether dlopen of statically linked programs is supported]) +])# LT_SYS_DLOPEN_SELF + +# Old name: +AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], []) + + +# _LT_COMPILER_C_O([TAGNAME]) +# --------------------------- +# Check to see if options -c and -o are simultaneously supported by compiler. +# This macro does not hard code the compiler like AC_PROG_CC_C_O. +m4_defun([_LT_COMPILER_C_O], +[m4_require([_LT_DECL_SED])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + fi + fi + chmod u+w . 2>&AS_MESSAGE_LOG_FD + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* +]) +_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1], + [Does compiler simultaneously support -c and -o options?]) +])# _LT_COMPILER_C_O + + +# _LT_COMPILER_FILE_LOCKS([TAGNAME]) +# ---------------------------------- +# Check to see if we can do hard links to lock some files if needed +m4_defun([_LT_COMPILER_FILE_LOCKS], +[m4_require([_LT_ENABLE_LOCK])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_COMPILER_C_O([$1]) + +hard_links=nottested +if test no = "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + AC_MSG_CHECKING([if we can lock with hard links]) + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + AC_MSG_RESULT([$hard_links]) + if test no = "$hard_links"; then + AC_MSG_WARN(['$CC' does not support '-c -o', so 'make -j' may be unsafe]) + need_locks=warn + fi +else + need_locks=no +fi +_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?]) +])# _LT_COMPILER_FILE_LOCKS + + +# _LT_CHECK_OBJDIR +# ---------------- +m4_defun([_LT_CHECK_OBJDIR], +[AC_CACHE_CHECK([for objdir], [lt_cv_objdir], +[rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null]) +objdir=$lt_cv_objdir +_LT_DECL([], [objdir], [0], + [The name of the directory that contains temporary libtool files])dnl +m4_pattern_allow([LT_OBJDIR])dnl +AC_DEFINE_UNQUOTED([LT_OBJDIR], "$lt_cv_objdir/", + [Define to the sub-directory where libtool stores uninstalled libraries.]) +])# _LT_CHECK_OBJDIR + + +# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME]) +# -------------------------------------- +# Check hardcoding attributes. +m4_defun([_LT_LINKER_HARDCODE_LIBPATH], +[AC_MSG_CHECKING([how to hardcode library paths into programs]) +_LT_TAGVAR(hardcode_action, $1)= +if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" || + test -n "$_LT_TAGVAR(runpath_var, $1)" || + test yes = "$_LT_TAGVAR(hardcode_automatic, $1)"; then + + # We can hardcode non-existent directories. + if test no != "$_LT_TAGVAR(hardcode_direct, $1)" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" && + test no != "$_LT_TAGVAR(hardcode_minus_L, $1)"; then + # Linking always hardcodes the temporary library directory. + _LT_TAGVAR(hardcode_action, $1)=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + _LT_TAGVAR(hardcode_action, $1)=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + _LT_TAGVAR(hardcode_action, $1)=unsupported +fi +AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)]) + +if test relink = "$_LT_TAGVAR(hardcode_action, $1)" || + test yes = "$_LT_TAGVAR(inherit_rpath, $1)"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi +_LT_TAGDECL([], [hardcode_action], [0], + [How to hardcode a shared library path into an executable]) +])# _LT_LINKER_HARDCODE_LIBPATH + + +# _LT_CMD_STRIPLIB +# ---------------- +m4_defun([_LT_CMD_STRIPLIB], +[m4_require([_LT_DECL_EGREP]) +striplib= +old_striplib= +AC_MSG_CHECKING([whether stripping libraries is possible]) +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + AC_MSG_RESULT([yes]) +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP"; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac +fi +_LT_DECL([], [old_striplib], [1], [Commands to strip libraries]) +_LT_DECL([], [striplib], [1]) +])# _LT_CMD_STRIPLIB + + +# _LT_PREPARE_MUNGE_PATH_LIST +# --------------------------- +# Make sure func_munge_path_list() is defined correctly. +m4_defun([_LT_PREPARE_MUNGE_PATH_LIST], +[[# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x@S|@2 in + x) + ;; + *:) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'` \@S|@@S|@1\" + ;; + x:*) + eval @S|@1=\"\@S|@@S|@1 `$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval @S|@1=\"\@S|@@S|@1\ `$ECHO @S|@2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval @S|@1=\"`$ECHO @S|@2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \@S|@@S|@1\" + ;; + *) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + esac +} +]])# _LT_PREPARE_PATH_LIST + + +# _LT_SYS_DYNAMIC_LINKER([TAG]) +# ----------------------------- +# PORTME Fill in your ld.so characteristics +m4_defun([_LT_SYS_DYNAMIC_LINKER], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_OBJDUMP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PREPARE_MUNGE_PATH_LIST])dnl +AC_MSG_CHECKING([dynamic linker characteristics]) +m4_if([$1], + [], [ +if test yes = "$GCC"; then + case $host_os in + darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; + *) lt_awk_arg='/^libraries:/' ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq='s|=\([[A-Za-z]]:\)|\1|g' ;; + *) lt_sed_strip_eq='s|=/|/|g' ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary... + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + # ...but if some path component already ends with the multilib dir we assume + # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). + case "$lt_multi_os_dir; $lt_search_path_spec " in + "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) + lt_multi_os_dir= + ;; + esac + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" + elif test -n "$lt_multi_os_dir"; then + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS = " "; FS = "/|\n";} { + lt_foo = ""; + lt_count = 0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo = "/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[[lt_foo]]++; } + if (lt_freq[[lt_foo]] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's|/\([[A-Za-z]]:\)|\1|g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi]) +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +AC_ARG_VAR([LT_SYS_LIBRARY_PATH], +[User-defined run-time library search path.]) + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[[4-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[[01]] | aix4.[[01]].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib<name>.so + # instead of lib<name>.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a[(]lib.so.V[)]' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)], lib.a[(]lib.so.V[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a[(]lib.so.V[)], lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[[45]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"]) + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"]) + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[[23]].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[[01]]* | freebsdelf3.[[01]]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \ + freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[[3-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath], + [lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \ + LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\"" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null], + [lt_cv_shlibpath_overrides_runpath=yes])]) + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + ]) + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsdelf*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='NetBSD ld.elf_so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +AC_MSG_RESULT([$dynamic_linker]) +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + +_LT_DECL([], [variables_saved_for_relink], [1], + [Variables whose values should be saved in libtool wrapper scripts and + restored at link time]) +_LT_DECL([], [need_lib_prefix], [0], + [Do we need the "lib" prefix for modules?]) +_LT_DECL([], [need_version], [0], [Do we need a version for libraries?]) +_LT_DECL([], [version_type], [0], [Library versioning type]) +_LT_DECL([], [runpath_var], [0], [Shared library runtime path variable]) +_LT_DECL([], [shlibpath_var], [0],[Shared library path variable]) +_LT_DECL([], [shlibpath_overrides_runpath], [0], + [Is shlibpath searched before the hard-coded library search path?]) +_LT_DECL([], [libname_spec], [1], [Format of library name prefix]) +_LT_DECL([], [library_names_spec], [1], + [[List of archive names. First name is the real one, the rest are links. + The last name is the one that the linker finds with -lNAME]]) +_LT_DECL([], [soname_spec], [1], + [[The coded name of the library, if different from the real name]]) +_LT_DECL([], [install_override_mode], [1], + [Permission mode override for installation of shared libraries]) +_LT_DECL([], [postinstall_cmds], [2], + [Command to use after installation of a shared archive]) +_LT_DECL([], [postuninstall_cmds], [2], + [Command to use after uninstallation of a shared archive]) +_LT_DECL([], [finish_cmds], [2], + [Commands used to finish a libtool library installation in a directory]) +_LT_DECL([], [finish_eval], [1], + [[As "finish_cmds", except a single script fragment to be evaled but + not shown]]) +_LT_DECL([], [hardcode_into_libs], [0], + [Whether we should hardcode library paths into libraries]) +_LT_DECL([], [sys_lib_search_path_spec], [2], + [Compile-time system search path for libraries]) +_LT_DECL([sys_lib_dlsearch_path_spec], [configure_time_dlsearch_path], [2], + [Detected run-time system search path for libraries]) +_LT_DECL([], [configure_time_lt_sys_library_path], [2], + [Explicit LT_SYS_LIBRARY_PATH set during ./configure time]) +])# _LT_SYS_DYNAMIC_LINKER + + +# _LT_PATH_TOOL_PREFIX(TOOL) +# -------------------------- +# find a file program that can recognize shared library +AC_DEFUN([_LT_PATH_TOOL_PREFIX], +[m4_require([_LT_DECL_EGREP])dnl +AC_MSG_CHECKING([for $1]) +AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, +[case $MAGIC_CMD in +[[\\/*] | ?:[\\/]*]) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR +dnl $ac_dummy forces splitting on constant user-supplied paths. +dnl POSIX.2 word splitting is done only on the output of word expansions, +dnl not every word. This closes a longstanding sh security hole. + ac_dummy="m4_if([$2], , $PATH, [$2])" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$1"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"$1" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac]) +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + AC_MSG_RESULT($MAGIC_CMD) +else + AC_MSG_RESULT(no) +fi +_LT_DECL([], [MAGIC_CMD], [0], + [Used to examine libraries when file_magic_cmd begins with "file"])dnl +])# _LT_PATH_TOOL_PREFIX + +# Old name: +AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], []) + + +# _LT_PATH_MAGIC +# -------------- +# find a file program that can recognize a shared library +m4_defun([_LT_PATH_MAGIC], +[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) + else + MAGIC_CMD=: + fi +fi +])# _LT_PATH_MAGIC + + +# LT_PATH_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([LT_PATH_LD], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PROG_ECHO_BACKSLASH])dnl + +AC_ARG_WITH([gnu-ld], + [AS_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test no = "$withval" || with_gnu_ld=yes], + [with_gnu_ld=no])dnl + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in + *GNU* | *'with BFD'*) + test no != "$with_gnu_ld" && break + ;; + *) + test yes != "$with_gnu_ld" && break + ;; + esac + fi + done + IFS=$lt_save_ifs +else + lt_cv_path_LD=$LD # Let the user override the test with a path. +fi]) +LD=$lt_cv_path_LD +if test -n "$LD"; then + AC_MSG_RESULT($LD) +else + AC_MSG_RESULT(no) +fi +test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH]) +_LT_PATH_LD_GNU +AC_SUBST([LD]) + +_LT_TAGDECL([], [LD], [1], [The linker used to build libraries]) +])# LT_PATH_LD + +# Old names: +AU_ALIAS([AM_PROG_LD], [LT_PATH_LD]) +AU_ALIAS([AC_PROG_LD], [LT_PATH_LD]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_PROG_LD], []) +dnl AC_DEFUN([AC_PROG_LD], []) + + +# _LT_PATH_LD_GNU +#- -------------- +m4_defun([_LT_PATH_LD_GNU], +[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], lt_cv_prog_gnu_ld, +[# I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 </dev/null` in +*GNU* | *'with BFD'*) + lt_cv_prog_gnu_ld=yes + ;; +*) + lt_cv_prog_gnu_ld=no + ;; +esac]) +with_gnu_ld=$lt_cv_prog_gnu_ld +])# _LT_PATH_LD_GNU + + +# _LT_CMD_RELOAD +# -------------- +# find reload flag for linker +# -- PORTME Some linkers may need a different reload flag. +m4_defun([_LT_CMD_RELOAD], +[AC_CACHE_CHECK([for $LD option to reload object files], + lt_cv_ld_reload_flag, + [lt_cv_ld_reload_flag='-r']) +reload_flag=$lt_cv_ld_reload_flag +case $reload_flag in +"" | " "*) ;; +*) reload_flag=" $reload_flag" ;; +esac +reload_cmds='$LD$reload_flag -o $output$reload_objs' +case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + if test yes != "$GCC"; then + reload_cmds=false + fi + ;; + darwin*) + if test yes = "$GCC"; then + reload_cmds='$LTCC $LTCFLAGS -nostdlib $wl-r -o $output$reload_objs' + else + reload_cmds='$LD$reload_flag -o $output$reload_objs' + fi + ;; +esac +_LT_TAGDECL([], [reload_flag], [1], [How to create reloadable object files])dnl +_LT_TAGDECL([], [reload_cmds], [2])dnl +])# _LT_CMD_RELOAD + + +# _LT_PATH_DD +# ----------- +# find a working dd +m4_defun([_LT_PATH_DD], +[AC_CACHE_CHECK([for a working dd], [ac_cv_path_lt_DD], +[printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +: ${lt_DD:=$DD} +AC_PATH_PROGS_FEATURE_CHECK([lt_DD], [dd], +[if "$ac_path_lt_DD" bs=32 count=1 <conftest2.i >conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: +fi]) +rm -f conftest.i conftest2.i conftest.out]) +])# _LT_PATH_DD + + +# _LT_CMD_TRUNCATE +# ---------------- +# find command to truncate a binary pipe +m4_defun([_LT_CMD_TRUNCATE], +[m4_require([_LT_PATH_DD]) +AC_CACHE_CHECK([how to truncate binary pipes], [lt_cv_truncate_bin], +[printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +lt_cv_truncate_bin= +if "$ac_cv_path_lt_DD" bs=32 count=1 <conftest2.i >conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" +fi +rm -f conftest.i conftest2.i conftest.out +test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q"]) +_LT_DECL([lt_truncate_bin], [lt_cv_truncate_bin], [1], + [Command to truncate a binary pipe]) +])# _LT_CMD_TRUNCATE + + +# _LT_CHECK_MAGIC_METHOD +# ---------------------- +# how to check for library dependencies +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_MAGIC_METHOD], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +AC_CACHE_CHECK([how to recognize dependent libraries], +lt_cv_deplibs_check_method, +[lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# 'unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# that responds to the $file_magic_cmd with a given extended regex. +# If you have 'file' or equivalent on your system and you're not sure +# whether 'pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[[4-9]]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[[45]]*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'] + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[[3-9]]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd* | bitrig*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +os2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac +]) + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + +_LT_DECL([], [deplibs_check_method], [1], + [Method to check whether dependent libraries are shared objects]) +_LT_DECL([], [file_magic_cmd], [1], + [Command to use when deplibs_check_method = "file_magic"]) +_LT_DECL([], [file_magic_glob], [1], + [How to find potential files when deplibs_check_method = "file_magic"]) +_LT_DECL([], [want_nocaseglob], [1], + [Find potential files using nocaseglob when deplibs_check_method = "file_magic"]) +])# _LT_CHECK_MAGIC_METHOD + + +# LT_PATH_NM +# ---------- +# find the pathname to a BSD- or MS-compatible name lister +AC_DEFUN([LT_PATH_NM], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM, +[if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM=$NM +else + lt_nm_to_check=${ac_tool_prefix}nm + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + tmp_nm=$ac_dir/$lt_tmp_nm + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the 'sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty + case $build_os in + mingw*) lt_bad_file=conftest.nm/nofile ;; + *) lt_bad_file=/dev/null ;; + esac + case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in + *$lt_bad_file* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break 2 + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break 2 + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS=$lt_save_ifs + done + : ${lt_cv_path_NM=no} +fi]) +if test no != "$lt_cv_path_NM"; then + NM=$lt_cv_path_NM +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :) + case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols -headers" + ;; + *) + DUMPBIN=: + ;; + esac + fi + AC_SUBST([DUMPBIN]) + if test : != "$DUMPBIN"; then + NM=$DUMPBIN + fi +fi +test -z "$NM" && NM=nm +AC_SUBST([NM]) +_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl + +AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface], + [lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD) + cat conftest.out >&AS_MESSAGE_LOG_FD + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest*]) +])# LT_PATH_NM + +# Old names: +AU_ALIAS([AM_PROG_NM], [LT_PATH_NM]) +AU_ALIAS([AC_PROG_NM], [LT_PATH_NM]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_PROG_NM], []) +dnl AC_DEFUN([AC_PROG_NM], []) + +# _LT_CHECK_SHAREDLIB_FROM_LINKLIB +# -------------------------------- +# how to determine the name of the shared library +# associated with a specific link library. +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +m4_require([_LT_DECL_DLLTOOL]) +AC_CACHE_CHECK([how to associate runtime and link libraries], +lt_cv_sharedlib_from_linklib_cmd, +[lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh; + # decide which one to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd=$ECHO + ;; +esac +]) +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + +_LT_DECL([], [sharedlib_from_linklib_cmd], [1], + [Command to associate shared and link libraries]) +])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB + + +# _LT_PATH_MANIFEST_TOOL +# ---------------------- +# locate the manifest tool +m4_defun([_LT_PATH_MANIFEST_TOOL], +[AC_CHECK_TOOL(MANIFEST_TOOL, mt, :) +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool], + [lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&AS_MESSAGE_LOG_FD + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest*]) +if test yes != "$lt_cv_path_mainfest_tool"; then + MANIFEST_TOOL=: +fi +_LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl +])# _LT_PATH_MANIFEST_TOOL + + +# _LT_DLL_DEF_P([FILE]) +# --------------------- +# True iff FILE is a Windows DLL '.def' file. +# Keep in sync with func_dll_def_p in the libtool script +AC_DEFUN([_LT_DLL_DEF_P], +[dnl + test DEF = "`$SED -n dnl + -e '\''s/^[[ ]]*//'\'' dnl Strip leading whitespace + -e '\''/^\(;.*\)*$/d'\'' dnl Delete empty lines and comments + -e '\''s/^\(EXPORTS\|LIBRARY\)\([[ ]].*\)*$/DEF/p'\'' dnl + -e q dnl Only consider the first "real" line + $1`" dnl +])# _LT_DLL_DEF_P + + +# LT_LIB_M +# -------- +# check for math library +AC_DEFUN([LT_LIB_M], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM=-lmw) + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM=-lm) + ;; +esac +AC_SUBST([LIBM]) +])# LT_LIB_M + +# Old name: +AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_CHECK_LIBM], []) + + +# _LT_COMPILER_NO_RTTI([TAGNAME]) +# ------------------------------- +m4_defun([_LT_COMPILER_NO_RTTI], +[m4_require([_LT_TAG_COMPILER])dnl + +_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + +if test yes = "$GCC"; then + case $cc_basename in + nvcc*) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;; + *) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;; + esac + + _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], + lt_cv_prog_compiler_rtti_exceptions, + [-fno-rtti -fno-exceptions], [], + [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) +fi +_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1], + [Compiler flag to turn off builtin functions]) +])# _LT_COMPILER_NO_RTTI + + +# _LT_CMD_GLOBAL_SYMBOLS +# ---------------------- +m4_defun([_LT_CMD_GLOBAL_SYMBOLS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([LT_PATH_NM])dnl +AC_REQUIRE([LT_PATH_LD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_TAG_COMPILER])dnl + +# Check for command to grab the raw symbol name followed by C symbol from nm. +AC_MSG_CHECKING([command to parse $NM output from $compiler object]) +AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], +[ +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[[BCDEGRST]]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[[BCDT]]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[[ABCDGISTW]]' + ;; +hpux*) + if test ia64 = "$host_cpu"; then + symcode='[[ABCDEGRST]]' + fi + ;; +irix* | nonstopux*) + symcode='[[BCDEGRST]]' + ;; +osf*) + symcode='[[BCDEGQRST]]' + ;; +solaris*) + symcode='[[BDRT]]' + ;; +sco3.2v5*) + symcode='[[DT]]' + ;; +sysv4.2uw2*) + symcode='[[DT]]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[[ABDT]]' + ;; +sysv4) + symcode='[[DFNSTU]]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[[ABCDGIRSTW]]' ;; +esac + +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Gets list of data symbols to import. + lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" + # Adjust the below global symbol transforms to fixup imported variables. + lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" + lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" + lt_c_name_lib_hook="\ + -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ + -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" +else + # Disable hooks by default. + lt_cv_sys_global_symbol_to_import= + lt_cdecl_hook= + lt_c_name_hook= + lt_c_name_lib_hook= +fi + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n"\ +$lt_cdecl_hook\ +" -e 's/^T .* \(.*\)$/extern int \1();/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ +$lt_c_name_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" + +# Transform an extracted symbol line into symbol name with lib prefix and +# symbol address. +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ +$lt_c_name_lib_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function, + # D for any global variable and I for any imported variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK ['"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ +" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ +" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ +" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ +" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx]" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if AC_TRY_EVAL(ac_compile); then + # Now try to grab the symbols. + nlist=conftest.nm + if AC_TRY_EVAL(NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT@&t@_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT@&t@_DLSYM_CONST +#else +# define LT@&t@_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT@&t@_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[[]] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS=conftstm.$ac_objext + CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" + if AC_TRY_EVAL(ac_link) && test -s conftest$ac_exeext; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD + fi + else + echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test yes = "$pipe_works"; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done +]) +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + AC_MSG_RESULT(failed) +else + AC_MSG_RESULT(ok) +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + +_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1], + [Take the output of nm and produce a listing of raw symbols and C names]) +_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1], + [Transform the output of nm in a proper C declaration]) +_LT_DECL([global_symbol_to_import], [lt_cv_sys_global_symbol_to_import], [1], + [Transform the output of nm into a list of symbols to manually relocate]) +_LT_DECL([global_symbol_to_c_name_address], + [lt_cv_sys_global_symbol_to_c_name_address], [1], + [Transform the output of nm in a C name address pair]) +_LT_DECL([global_symbol_to_c_name_address_lib_prefix], + [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1], + [Transform the output of nm in a C name address pair when lib prefix is needed]) +_LT_DECL([nm_interface], [lt_cv_nm_interface], [1], + [The name lister interface]) +_LT_DECL([], [nm_file_list_spec], [1], + [Specify filename containing input files for $NM]) +]) # _LT_CMD_GLOBAL_SYMBOLS + + +# _LT_COMPILER_PIC([TAGNAME]) +# --------------------------- +m4_defun([_LT_COMPILER_PIC], +[m4_require([_LT_TAG_COMPILER])dnl +_LT_TAGVAR(lt_prog_compiler_wl, $1)= +_LT_TAGVAR(lt_prog_compiler_pic, $1)= +_LT_TAGVAR(lt_prog_compiler_static, $1)= + +m4_if([$1], [CXX], [ + # C++ specific cases for pic, static, wl, etc. + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + case $host_os in + aix[[4-9]]*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + dgux*) + case $cc_basename in + ec++*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + fi + ;; + aCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # KAI C++ Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + ecpc* ) + # old Intel C++ for x86_64, which still supported -KPIC. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + icpc* ) + # Intel C++, used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*) + # IBM XL 8.0, 9.0 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd* | netbsdelf*-gnu) + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + cxx*) + # Digital/Compaq C++ + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + lcc*) + # Lucid + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + *) + ;; + esac + ;; + vxworks*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +], +[ + if test yes = "$GCC"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker ' + if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + case $cc_basename in + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + hpux9* | hpux10* | hpux11*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC (with -KPIC) is the default. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + # old Intel for x86_64, which still supported -KPIC. + ecc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared' + _LT_TAGVAR(lt_prog_compiler_static, $1)='--static' + ;; + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + ccc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All Alpha code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='' + ;; + *Sun\ F* | *Sun*Fortran*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + ;; + *Intel*\ [[CF]]*Compiler*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + *Portland\ Group*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All OSF/1 code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + rdos*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + solaris*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';; + *) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';; + esac + ;; + + sunos4*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + unicos*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + + uts4*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +]) +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])" + ;; +esac + +AC_CACHE_CHECK([for $compiler option to produce PIC], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)]) +_LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1) + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works], + [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)], + [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [], + [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;; + esac], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) +fi +_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1], + [Additional compiler flags for building library objects]) + +_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1], + [How to pass a linker flag through the compiler]) +# +# Check to make sure the static flag actually works. +# +wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\" +_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works], + _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1), + $lt_tmp_static_flag, + [], + [_LT_TAGVAR(lt_prog_compiler_static, $1)=]) +_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1], + [Compiler flag to prevent dynamic linking]) +])# _LT_COMPILER_PIC + + +# _LT_LINKER_SHLIBS([TAGNAME]) +# ---------------------------- +# See if the linker supports building shared libraries. +m4_defun([_LT_LINKER_SHLIBS], +[AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +m4_if([$1], [CXX], [ + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + case $host_os in + aix[[4-9]]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + _LT_TAGVAR(export_symbols_cmds, $1)=$ltdll_cmds + ;; + cygwin* | mingw* | cegcc*) + case $cc_basename in + cl*) + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + ;; + esac + ;; + linux* | k*bsd*-gnu | gnu*) + _LT_TAGVAR(link_all_deplibs, $1)=no + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac +], [ + runpath_var= + _LT_TAGVAR(allow_undefined_flag, $1)= + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(archive_cmds, $1)= + _LT_TAGVAR(archive_expsym_cmds, $1)= + _LT_TAGVAR(compiler_needs_object, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(hardcode_automatic, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(hardcode_libdir_separator, $1)= + _LT_TAGVAR(hardcode_minus_L, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_TAGVAR(inherit_rpath, $1)=no + _LT_TAGVAR(link_all_deplibs, $1)=unknown + _LT_TAGVAR(module_cmds, $1)= + _LT_TAGVAR(module_expsym_cmds, $1)= + _LT_TAGVAR(old_archive_from_new_cmds, $1)= + _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)= + _LT_TAGVAR(thread_safe_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + _LT_TAGVAR(include_expsyms, $1)= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ' (' and ')$', so one must not match beginning or + # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', + # as well as any symbol that contains 'd'. + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. +dnl Note also adjust exclude_expsyms for C++ above. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test yes != "$GCC"; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd* | bitrig*) + with_gnu_ld=no + ;; + linux* | k*bsd*-gnu | gnu*) + _LT_TAGVAR(link_all_deplibs, $1)=no + ;; + esac + + _LT_TAGVAR(ld_shlibs, $1)=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test yes = "$with_gnu_ld"; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;; + *\ \(GNU\ Binutils\)\ [[3-9]]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test yes = "$lt_use_gnu_ld_interface"; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='$wl' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + supports_anon_versioning=no + case `$LD -v | $SED -e 's/([^)]\+)\s\+//' 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[[3-9]]*) + # On AIX/PPC, the GNU linker is very broken + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach <jrb3@best.com> says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test linux-dietlibc = "$host_os"; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test no = "$tmp_diet" + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + _LT_TAGVAR(whole_archive_flag_spec, $1)= + tmp_sharedflag='--shared' ;; + nagfor*) # NAGFOR 5.3 + tmp_sharedflag='-Wl,-shared' ;; + xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + tcc*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-rdynamic' + ;; + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*) + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + sunos4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + + if test no = "$_LT_TAGVAR(ld_shlibs, $1)"; then + runpath_var= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + _LT_TAGVAR(hardcode_direct, $1)=unsupported + fi + ;; + + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then + aix_use_runtimelinking=yes + break + fi + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # traditional, no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GCC"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + ;; + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag="$shared_flag "'$wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared libraries. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + bsdi[[45]]*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + # FIXME: Should let the user specify the lib program. + _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + esac + ;; + + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + dgux*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + hpux9*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + + hpux10*) + if test yes,no = "$GCC,$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + fi + ;; + + hpux11*) + if test yes,no = "$GCC,$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + m4_if($1, [], [ + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + _LT_LINKER_OPTION([if $CC understands -b], + _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'], + [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags']) + ;; + esac + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol], + [lt_cv_irix_exported_symbol], + [save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" + AC_LINK_IFELSE( + [AC_LANG_SOURCE( + [AC_LANG_CASE([C], [[int foo (void) { return 0; }]], + [C++], [[int foo (void) { return 0; }]], + [Fortran 77], [[ + subroutine foo + end]], + [Fortran], [[ + subroutine foo + end]])])], + [lt_cv_irix_exported_symbol=yes], + [lt_cv_irix_exported_symbol=no]) + LDFLAGS=$save_LDFLAGS]) + if test yes = "$lt_cv_irix_exported_symbol"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' + fi + _LT_TAGVAR(link_all_deplibs, $1)=no + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + linux*) + case $cc_basename in + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(ld_shlibs, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + newsos6) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *nto* | *qnx*) + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + fi + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + osf3*) + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + solaris*) + _LT_TAGVAR(no_undefined_flag, $1)=' -z defs' + if test yes = "$GCC"; then + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + _LT_TAGVAR(archive_cmds, $1)='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. GCC discards it without '$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test yes = "$GCC"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + fi + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + sunos4*) + if test sequent = "$host_vendor"; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4) + case $host_vendor in + sni) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' + _LT_TAGVAR(hardcode_direct, $1)=no + ;; + motorola) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4.3*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + _LT_TAGVAR(ld_shlibs, $1)=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + if test sni = "$host_vendor"; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Blargedynsym' + ;; + esac + fi + fi +]) +AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) +test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + +_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld + +_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl +_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl +_LT_DECL([], [extract_expsyms_cmds], [2], + [The commands to extract the exported symbol list from a shared archive]) + +# +# Do we need to explicitly link libc? +# +case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in +x|xyes) + # Assume -lc should be added + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $_LT_TAGVAR(archive_cmds, $1) in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + AC_CACHE_CHECK([whether -lc should be explicitly linked in], + [lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1), + [$RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if AC_TRY_EVAL(ac_compile) 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) + pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1) + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1) + _LT_TAGVAR(allow_undefined_flag, $1)= + if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) + then + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no + else + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes + fi + _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + ]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1) + ;; + esac + fi + ;; +esac + +_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0], + [Whether or not to add -lc for building shared libraries]) +_LT_TAGDECL([allow_libtool_libs_with_static_runtimes], + [enable_shared_with_static_runtimes], [0], + [Whether or not to disallow shared libs when runtime libs are static]) +_LT_TAGDECL([], [export_dynamic_flag_spec], [1], + [Compiler flag to allow reflexive dlopens]) +_LT_TAGDECL([], [whole_archive_flag_spec], [1], + [Compiler flag to generate shared objects directly from archives]) +_LT_TAGDECL([], [compiler_needs_object], [1], + [Whether the compiler copes with passing no objects directly]) +_LT_TAGDECL([], [old_archive_from_new_cmds], [2], + [Create an old-style archive from a shared archive]) +_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2], + [Create a temporary old-style archive to link instead of a shared archive]) +_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive]) +_LT_TAGDECL([], [archive_expsym_cmds], [2]) +_LT_TAGDECL([], [module_cmds], [2], + [Commands used to build a loadable module if different from building + a shared archive.]) +_LT_TAGDECL([], [module_expsym_cmds], [2]) +_LT_TAGDECL([], [with_gnu_ld], [1], + [Whether we are building with GNU ld or not]) +_LT_TAGDECL([], [allow_undefined_flag], [1], + [Flag that allows shared libraries with undefined symbols to be built]) +_LT_TAGDECL([], [no_undefined_flag], [1], + [Flag that enforces no undefined symbols]) +_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1], + [Flag to hardcode $libdir into a binary during linking. + This must work even if $libdir does not exist]) +_LT_TAGDECL([], [hardcode_libdir_separator], [1], + [Whether we need a single "-rpath" flag with a separated argument]) +_LT_TAGDECL([], [hardcode_direct], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary]) +_LT_TAGDECL([], [hardcode_direct_absolute], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary and the resulting library dependency is + "absolute", i.e impossible to change by setting $shlibpath_var if the + library is relocated]) +_LT_TAGDECL([], [hardcode_minus_L], [0], + [Set to "yes" if using the -LDIR flag during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_shlibpath_var], [0], + [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_automatic], [0], + [Set to "yes" if building a shared library automatically hardcodes DIR + into the library and all subsequent libraries and executables linked + against it]) +_LT_TAGDECL([], [inherit_rpath], [0], + [Set to yes if linker adds runtime paths of dependent libraries + to runtime path list]) +_LT_TAGDECL([], [link_all_deplibs], [0], + [Whether libtool must link a program against all its dependency libraries]) +_LT_TAGDECL([], [always_export_symbols], [0], + [Set to "yes" if exported symbols are required]) +_LT_TAGDECL([], [export_symbols_cmds], [2], + [The commands to list exported symbols]) +_LT_TAGDECL([], [exclude_expsyms], [1], + [Symbols that should not be listed in the preloaded symbols]) +_LT_TAGDECL([], [include_expsyms], [1], + [Symbols that must always be exported]) +_LT_TAGDECL([], [prelink_cmds], [2], + [Commands necessary for linking programs (against libraries) with templates]) +_LT_TAGDECL([], [postlink_cmds], [2], + [Commands necessary for finishing linking programs]) +_LT_TAGDECL([], [file_list_spec], [1], + [Specify filename containing input files]) +dnl FIXME: Not yet implemented +dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1], +dnl [Compiler flag to generate thread safe objects]) +])# _LT_LINKER_SHLIBS + + +# _LT_LANG_C_CONFIG([TAG]) +# ------------------------ +# Ensure that the configuration variables for a C compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_C_CONFIG], +[m4_require([_LT_DECL_EGREP])dnl +lt_save_CC=$CC +AC_LANG_PUSH(C) + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + +_LT_TAG_COMPILER +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + LT_SYS_DLOPEN_SELF + _LT_CMD_STRIPLIB + + # Report what library types will actually be built + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_CONFIG($1) +fi +AC_LANG_POP +CC=$lt_save_CC +])# _LT_LANG_C_CONFIG + + +# _LT_LANG_CXX_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a C++ compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_CXX_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +if test -n "$CXX" && ( test no != "$CXX" && + ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || + (test g++ != "$CXX"))); then + AC_PROG_CXXCPP +else + _lt_caught_CXX_error=yes +fi + +AC_LANG_PUSH(C++) +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(compiler_needs_object, $1)=no +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the CXX compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_caught_CXX_error"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="int some_variable = 0;" + + # Code to be used in simple link tests + lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }' + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_CFLAGS=$CFLAGS + lt_save_LD=$LD + lt_save_GCC=$GCC + GCC=$GXX + lt_save_with_gnu_ld=$with_gnu_ld + lt_save_path_LD=$lt_cv_path_LD + if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx + else + $as_unset lt_cv_prog_gnu_ld + fi + if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX + else + $as_unset lt_cv_path_LD + fi + test -z "${LDCXX+set}" || LD=$LDCXX + CC=${CXX-"c++"} + CFLAGS=$CXXFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + # We don't want -fno-exception when compiling C++ code, so set the + # no_builtin_flag separately + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + else + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + fi + + if test yes = "$GXX"; then + # Set up default GNU C++ configuration + + LT_PATH_LD + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test yes = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='$wl' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | + $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + GXX=no + with_gnu_ld=no + wlarc= + fi + + # PORTME: fill in a description of your system's C++ link characteristics + AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) + _LT_TAGVAR(ld_shlibs, $1)=yes + case $host_os in + aix3*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GXX"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag=$shared_flag' $wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to + # export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + # The "-G" linker flag allows undefined symbols. + _LT_TAGVAR(no_undefined_flag, $1)='-bernotok' + # Determine the default libpath from the value encoded in an empty + # executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared + # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach <jrb3@best.com> says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + cygwin* | mingw* | pw32* | cegcc*) + case $GXX,$cc_basename in + ,cl* | no,cl*) + # Native MSVC + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + func_to_tool_file "$lt_outputfile"~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # g++ + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + freebsd2.*) + # C++ shared libraries reported to be fairly broken before + # switch to ELF + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + freebsd-elf*) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + hpux9*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + hpux10*|hpux11*) + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' + fi + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + esac + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc* | ecpc* ) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + case `$CC -V` in + *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*) + _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ + compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' + _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ + $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ + $RANLIB $oldlib' + _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 6 and above use weak symbols + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl--rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + ;; + cxx*) + # Compaq C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' + ;; + xl* | mpixl* | bgxl*) + # IBM XL 8.0 on PPC, with GNU ld + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + + lynxos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + m88k*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + + *nto* | *qnx*) + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + fi + output_verbose_link_cmd=func_echo_all + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + case $host in + osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; + *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;; + esac + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + cxx*) + case $host in + osf3*) + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + ;; + *) + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ + $RM $lib.exp' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + case $host in + osf3*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + psos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(archive_cmds_need_lc,$1)=yes + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-z ${wl}defs' + if $CC --version | $GREP -v '^2\.7' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + else + # g++ 2.7 appears to require '-G' NOT '-shared' on this + # platform. + _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + fi + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $wl$libdir' + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~ + '"$_LT_TAGVAR(old_archive_cmds, $1)" + _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~ + '"$_LT_TAGVAR(reload_cmds, $1)" + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + vxworks*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) + test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + + _LT_TAGVAR(GCC, $1)=$GXX + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS + LDCXX=$LD + LD=$lt_save_LD + GCC=$lt_save_GCC + with_gnu_ld=$lt_save_with_gnu_ld + lt_cv_path_LDCXX=$lt_cv_path_LD + lt_cv_path_LD=$lt_save_path_LD + lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld + lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +fi # test yes != "$_lt_caught_CXX_error" + +AC_LANG_POP +])# _LT_LANG_CXX_CONFIG + + +# _LT_FUNC_STRIPNAME_CNF +# ---------------------- +# func_stripname_cnf prefix suffix name +# strip PREFIX and SUFFIX off of NAME. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +# +# This function is identical to the (non-XSI) version of func_stripname, +# except this one can be used by m4 code that may be executed by configure, +# rather than the libtool script. +m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl +AC_REQUIRE([_LT_DECL_SED]) +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH]) +func_stripname_cnf () +{ + case @S|@2 in + .*) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%\\\\@S|@2\$%%"`;; + *) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%@S|@2\$%%"`;; + esac +} # func_stripname_cnf +])# _LT_FUNC_STRIPNAME_CNF + + +# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME]) +# --------------------------------- +# Figure out "hidden" library dependencies from verbose +# compiler output when linking a shared library. +# Parse the compiler output and extract the necessary +# objects, libraries and library flags. +m4_defun([_LT_SYS_HIDDEN_LIBDEPS], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl +# Dependencies to place before and after the object being linked: +_LT_TAGVAR(predep_objects, $1)= +_LT_TAGVAR(postdep_objects, $1)= +_LT_TAGVAR(predeps, $1)= +_LT_TAGVAR(postdeps, $1)= +_LT_TAGVAR(compiler_lib_search_path, $1)= + +dnl we can't use the lt_simple_compile_test_code here, +dnl because it contains code intended for an executable, +dnl not a library. It's possible we should let each +dnl tag define a new lt_????_link_test_code variable, +dnl but it's only used here... +m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF +int a; +void foo (void) { a = 0; } +_LT_EOF +], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF +class Foo +{ +public: + Foo (void) { a = 0; } +private: + int a; +}; +_LT_EOF +], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer*4 a + a=0 + return + end +_LT_EOF +], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer a + a=0 + return + end +_LT_EOF +], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF +public class foo { + private int a; + public void bar (void) { + a = 0; + } +}; +_LT_EOF +], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF +package foo +func foo() { +} +_LT_EOF +]) + +_lt_libdeps_save_CFLAGS=$CFLAGS +case "$CC $CFLAGS " in #( +*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; +*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; +*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; +esac + +dnl Parse the compiler output and extract the necessary +dnl objects, libraries and library flags. +if AC_TRY_EVAL(ac_compile); then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + for p in `eval "$output_verbose_link_cmd"`; do + case $prev$p in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test x-L = "$p" || + test x-R = "$p"; then + prev=$p + continue + fi + + # Expand the sysroot to ease extracting the directories later. + if test -z "$prev"; then + case $p in + -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; + -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; + -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; + esac + fi + case $p in + =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; + esac + if test no = "$pre_test_object_deps_done"; then + case $prev in + -L | -R) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then + _LT_TAGVAR(compiler_lib_search_path, $1)=$prev$p + else + _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} $prev$p" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$_LT_TAGVAR(postdeps, $1)"; then + _LT_TAGVAR(postdeps, $1)=$prev$p + else + _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} $prev$p" + fi + fi + prev= + ;; + + *.lto.$objext) ;; # Ignore GCC LTO objects + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test no = "$pre_test_object_deps_done"; then + if test -z "$_LT_TAGVAR(predep_objects, $1)"; then + _LT_TAGVAR(predep_objects, $1)=$p + else + _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p" + fi + else + if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then + _LT_TAGVAR(postdep_objects, $1)=$p + else + _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling $1 test program" +fi + +$RM -f confest.$objext +CFLAGS=$_lt_libdeps_save_CFLAGS + +# PORTME: override above test on systems where it is broken +m4_if([$1], [CXX], +[case $host_os in +interix[[3-9]]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + _LT_TAGVAR(predep_objects,$1)= + _LT_TAGVAR(postdep_objects,$1)= + _LT_TAGVAR(postdeps,$1)= + ;; +esac +]) + +case " $_LT_TAGVAR(postdeps, $1) " in +*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; +esac + _LT_TAGVAR(compiler_lib_search_dirs, $1)= +if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then + _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | $SED -e 's! -L! !g' -e 's!^ !!'` +fi +_LT_TAGDECL([], [compiler_lib_search_dirs], [1], + [The directories searched by this compiler when creating a shared library]) +_LT_TAGDECL([], [predep_objects], [1], + [Dependencies to place before and after the objects being linked to + create a shared library]) +_LT_TAGDECL([], [postdep_objects], [1]) +_LT_TAGDECL([], [predeps], [1]) +_LT_TAGDECL([], [postdeps], [1]) +_LT_TAGDECL([], [compiler_lib_search_path], [1], + [The library search path used internally by the compiler when linking + a shared library]) +])# _LT_SYS_HIDDEN_LIBDEPS + + +# _LT_LANG_F77_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a Fortran 77 compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_F77_CONFIG], +[AC_LANG_PUSH(Fortran 77) +if test -z "$F77" || test no = "$F77"; then + _lt_disable_F77=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for f77 test sources. +ac_ext=f + +# Object file extension for compiled f77 test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the F77 compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_F77"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${F77-"f77"} + CFLAGS=$FFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + GCC=$G77 + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$G77 + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_F77" + +AC_LANG_POP +])# _LT_LANG_F77_CONFIG + + +# _LT_LANG_FC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for a Fortran compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_FC_CONFIG], +[AC_LANG_PUSH(Fortran) + +if test -z "$FC" || test no = "$FC"; then + _lt_disable_FC=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for fc test sources. +ac_ext=${ac_fc_srcext-f} + +# Object file extension for compiled fc test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the FC compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_FC"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${FC-"f95"} + CFLAGS=$FCFLAGS + compiler=$CC + GCC=$ac_cv_fc_compiler_gnu + + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$ac_cv_fc_compiler_gnu + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_FC" + +AC_LANG_POP +])# _LT_LANG_FC_CONFIG + + +# _LT_LANG_GCJ_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Java Compiler compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GCJ_CONFIG], +[AC_REQUIRE([LT_PROG_GCJ])dnl +AC_LANG_SAVE + +# Source file extension for Java test sources. +ac_ext=java + +# Object file extension for compiled Java test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="class foo {}" + +# Code to be used in simple link tests +lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GCJ-"gcj"} +CFLAGS=$GCJFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# GCJ did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GCJ_CONFIG + + +# _LT_LANG_GO_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Go compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GO_CONFIG], +[AC_REQUIRE([LT_PROG_GO])dnl +AC_LANG_SAVE + +# Source file extension for Go test sources. +ac_ext=go + +# Object file extension for compiled Go test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="package main; func main() { }" + +# Code to be used in simple link tests +lt_simple_link_test_code='package main; func main() { }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GOC-"gccgo"} +CFLAGS=$GOFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# Go did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GO_CONFIG + + +# _LT_LANG_RC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for the Windows resource compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_RC_CONFIG], +[AC_REQUIRE([LT_PROG_RC])dnl +AC_LANG_SAVE + +# Source file extension for RC test sources. +ac_ext=rc + +# Object file extension for compiled RC test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' + +# Code to be used in simple link tests +lt_simple_link_test_code=$lt_simple_compile_test_code + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC= +CC=${RC-"windres"} +CFLAGS= +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) +_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + +if test -n "$compiler"; then + : + _LT_CONFIG($1) +fi + +GCC=$lt_save_GCC +AC_LANG_RESTORE +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_RC_CONFIG + + +# LT_PROG_GCJ +# ----------- +AC_DEFUN([LT_PROG_GCJ], +[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ], + [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ], + [AC_CHECK_TOOL(GCJ, gcj,) + test set = "${GCJFLAGS+set}" || GCJFLAGS="-g -O2" + AC_SUBST(GCJFLAGS)])])[]dnl +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_GCJ], []) + + +# LT_PROG_GO +# ---------- +AC_DEFUN([LT_PROG_GO], +[AC_CHECK_TOOL(GOC, gccgo,) +]) + + +# LT_PROG_RC +# ---------- +AC_DEFUN([LT_PROG_RC], +[AC_CHECK_TOOL(RC, windres,) +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_RC], []) + + +# _LT_DECL_EGREP +# -------------- +# If we don't have a new enough Autoconf to choose the best grep +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_EGREP], +[AC_REQUIRE([AC_PROG_EGREP])dnl +AC_REQUIRE([AC_PROG_FGREP])dnl +test -z "$GREP" && GREP=grep +_LT_DECL([], [GREP], [1], [A grep program that handles long lines]) +_LT_DECL([], [EGREP], [1], [An ERE matcher]) +_LT_DECL([], [FGREP], [1], [A literal string matcher]) +dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too +AC_SUBST([GREP]) +]) + + +# _LT_DECL_OBJDUMP +# -------------- +# If we don't have a new enough Autoconf to choose the best objdump +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_OBJDUMP], +[AC_CHECK_TOOL(OBJDUMP, objdump, false) +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper]) +AC_SUBST([OBJDUMP]) +]) + +# _LT_DECL_DLLTOOL +# ---------------- +# Ensure DLLTOOL variable is set. +m4_defun([_LT_DECL_DLLTOOL], +[AC_CHECK_TOOL(DLLTOOL, dlltool, false) +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program]) +AC_SUBST([DLLTOOL]) +]) + +# _LT_DECL_SED +# ------------ +# Check for a fully-functional sed program, that truncates +# as few characters as possible. Prefer GNU sed if found. +m4_defun([_LT_DECL_SED], +[AC_PROG_SED +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" +_LT_DECL([], [SED], [1], [A sed program that does not truncate output]) +_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"], + [Sed that helps us avoid accidentally triggering echo(1) options like -n]) +])# _LT_DECL_SED + +m4_ifndef([AC_PROG_SED], [ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_SED. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # + +m4_defun([AC_PROG_SED], +[AC_MSG_CHECKING([for a sed that does not truncate output]) +AC_CACHE_VAL(lt_cv_path_SED, +[# Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +IFS=$as_save_IFS +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f "$lt_ac_sed" && continue + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test 10 -lt "$lt_ac_count" && break + lt_ac_count=`expr $lt_ac_count + 1` + if test "$lt_ac_count" -gt "$lt_ac_max"; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done +]) +SED=$lt_cv_path_SED +AC_SUBST([SED]) +AC_MSG_RESULT([$SED]) +])#AC_PROG_SED +])#m4_ifndef + +# Old name: +AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_SED], []) + + +# _LT_CHECK_SHELL_FEATURES +# ------------------------ +# Find out whether the shell is Bourne or XSI compatible, +# or has some other useful features. +m4_defun([_LT_CHECK_SHELL_FEATURES], +[if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi +_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac +_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl +_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl +])# _LT_CHECK_SHELL_FEATURES + + +# _LT_PATH_CONVERSION_FUNCTIONS +# ----------------------------- +# Determine what file name conversion functions should be used by +# func_to_host_file (and, implicitly, by func_to_host_path). These are needed +# for certain cross-compile configurations and native mingw. +m4_defun([_LT_PATH_CONVERSION_FUNCTIONS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_MSG_CHECKING([how to convert $build file names to $host format]) +AC_CACHE_VAL(lt_cv_to_host_file_cmd, +[case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac +]) +to_host_file_cmd=$lt_cv_to_host_file_cmd +AC_MSG_RESULT([$lt_cv_to_host_file_cmd]) +_LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd], + [0], [convert $build file names to $host format])dnl + +AC_MSG_CHECKING([how to convert $build file names to toolchain format]) +AC_CACHE_VAL(lt_cv_to_tool_file_cmd, +[#assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac +]) +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +AC_MSG_RESULT([$lt_cv_to_tool_file_cmd]) +_LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd], + [0], [convert $build files to toolchain format])dnl +])# _LT_PATH_CONVERSION_FUNCTIONS + +# Helper functions for option handling. -*- Autoconf -*- +# +# Copyright (C) 2004-2005, 2007-2009, 2011-2015 Free Software +# Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 8 ltoptions.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])]) + + +# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME) +# ------------------------------------------ +m4_define([_LT_MANGLE_OPTION], +[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])]) + + +# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME) +# --------------------------------------- +# Set option OPTION-NAME for macro MACRO-NAME, and if there is a +# matching handler defined, dispatch to it. Other OPTION-NAMEs are +# saved as a flag. +m4_define([_LT_SET_OPTION], +[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl +m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]), + _LT_MANGLE_DEFUN([$1], [$2]), + [m4_warning([Unknown $1 option '$2'])])[]dnl +]) + + +# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET]) +# ------------------------------------------------------------ +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +m4_define([_LT_IF_OPTION], +[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])]) + + +# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET) +# ------------------------------------------------------- +# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME +# are set. +m4_define([_LT_UNLESS_OPTIONS], +[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option), + [m4_define([$0_found])])])[]dnl +m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3 +])[]dnl +]) + + +# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST) +# ---------------------------------------- +# OPTION-LIST is a space-separated list of Libtool options associated +# with MACRO-NAME. If any OPTION has a matching handler declared with +# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about +# the unknown option and exit. +m4_defun([_LT_SET_OPTIONS], +[# Set options +m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [_LT_SET_OPTION([$1], _LT_Option)]) + +m4_if([$1],[LT_INIT],[ + dnl + dnl Simply set some default values (i.e off) if boolean options were not + dnl specified: + _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no + ]) + _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no + ]) + dnl + dnl If no reference was made to various pairs of opposing options, then + dnl we run the default mode handler for the pair. For example, if neither + dnl 'shared' nor 'disable-shared' was passed, we enable building of shared + dnl archives by default: + _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED]) + _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install], + [_LT_ENABLE_FAST_INSTALL]) + _LT_UNLESS_OPTIONS([LT_INIT], [aix-soname=aix aix-soname=both aix-soname=svr4], + [_LT_WITH_AIX_SONAME([aix])]) + ]) +])# _LT_SET_OPTIONS + + + +# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME) +# ----------------------------------------- +m4_define([_LT_MANGLE_DEFUN], +[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])]) + + +# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE) +# ----------------------------------------------- +m4_define([LT_OPTION_DEFINE], +[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl +])# LT_OPTION_DEFINE + + +# dlopen +# ------ +LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes +]) + +AU_DEFUN([AC_LIBTOOL_DLOPEN], +[_LT_SET_OPTION([LT_INIT], [dlopen]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'dlopen' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], []) + + +# win32-dll +# --------- +# Declare package support for building win32 dll's. +LT_OPTION_DEFINE([LT_INIT], [win32-dll], +[enable_win32_dll=yes + +case $host in +*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*) + AC_CHECK_TOOL(AS, as, false) + AC_CHECK_TOOL(DLLTOOL, dlltool, false) + AC_CHECK_TOOL(OBJDUMP, objdump, false) + ;; +esac + +test -z "$AS" && AS=as +_LT_DECL([], [AS], [1], [Assembler program])dnl + +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl + +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl +])# win32-dll + +AU_DEFUN([AC_LIBTOOL_WIN32_DLL], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +_LT_SET_OPTION([LT_INIT], [win32-dll]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'win32-dll' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], []) + + +# _LT_ENABLE_SHARED([DEFAULT]) +# ---------------------------- +# implement the --enable-shared flag, and supports the 'shared' and +# 'disable-shared' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_SHARED], +[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([shared], + [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@], + [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_shared=]_LT_ENABLE_SHARED_DEFAULT) + + _LT_DECL([build_libtool_libs], [enable_shared], [0], + [Whether or not to build shared libraries]) +])# _LT_ENABLE_SHARED + +LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared]) +]) + +AC_DEFUN([AC_DISABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], [disable-shared]) +]) + +AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) +AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_SHARED], []) +dnl AC_DEFUN([AM_DISABLE_SHARED], []) + + + +# _LT_ENABLE_STATIC([DEFAULT]) +# ---------------------------- +# implement the --enable-static flag, and support the 'static' and +# 'disable-static' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_STATIC], +[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([static], + [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@], + [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_static=]_LT_ENABLE_STATIC_DEFAULT) + + _LT_DECL([build_old_libs], [enable_static], [0], + [Whether or not to build static libraries]) +])# _LT_ENABLE_STATIC + +LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static]) +]) + +AC_DEFUN([AC_DISABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], [disable-static]) +]) + +AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) +AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_STATIC], []) +dnl AC_DEFUN([AM_DISABLE_STATIC], []) + + + +# _LT_ENABLE_FAST_INSTALL([DEFAULT]) +# ---------------------------------- +# implement the --enable-fast-install flag, and support the 'fast-install' +# and 'disable-fast-install' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_FAST_INSTALL], +[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([fast-install], + [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], + [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT) + +_LT_DECL([fast_install], [enable_fast_install], [0], + [Whether or not to optimize for fast installation])dnl +])# _LT_ENABLE_FAST_INSTALL + +LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])]) + +# Old names: +AU_DEFUN([AC_ENABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the 'fast-install' option into LT_INIT's first parameter.]) +]) + +AU_DEFUN([AC_DISABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], [disable-fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the 'disable-fast-install' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], []) +dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], []) + + +# _LT_WITH_AIX_SONAME([DEFAULT]) +# ---------------------------------- +# implement the --with-aix-soname flag, and support the `aix-soname=aix' +# and `aix-soname=both' and `aix-soname=svr4' LT_INIT options. DEFAULT +# is either `aix', `both' or `svr4'. If omitted, it defaults to `aix'. +m4_define([_LT_WITH_AIX_SONAME], +[m4_define([_LT_WITH_AIX_SONAME_DEFAULT], [m4_if($1, svr4, svr4, m4_if($1, both, both, aix))])dnl +shared_archive_member_spec= +case $host,$enable_shared in +power*-*-aix[[5-9]]*,yes) + AC_MSG_CHECKING([which variant of shared library versioning to provide]) + AC_ARG_WITH([aix-soname], + [AS_HELP_STRING([--with-aix-soname=aix|svr4|both], + [shared library versioning (aka "SONAME") variant to provide on AIX, @<:@default=]_LT_WITH_AIX_SONAME_DEFAULT[@:>@.])], + [case $withval in + aix|svr4|both) + ;; + *) + AC_MSG_ERROR([Unknown argument to --with-aix-soname]) + ;; + esac + lt_cv_with_aix_soname=$with_aix_soname], + [AC_CACHE_VAL([lt_cv_with_aix_soname], + [lt_cv_with_aix_soname=]_LT_WITH_AIX_SONAME_DEFAULT) + with_aix_soname=$lt_cv_with_aix_soname]) + AC_MSG_RESULT([$with_aix_soname]) + if test aix != "$with_aix_soname"; then + # For the AIX way of multilib, we name the shared archive member + # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', + # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. + # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, + # the AIX toolchain works better with OBJECT_MODE set (default 32). + if test 64 = "${OBJECT_MODE-32}"; then + shared_archive_member_spec=shr_64 + else + shared_archive_member_spec=shr + fi + fi + ;; +*) + with_aix_soname=aix + ;; +esac + +_LT_DECL([], [shared_archive_member_spec], [0], + [Shared archive member basename, for filename based shared library versioning on AIX])dnl +])# _LT_WITH_AIX_SONAME + +LT_OPTION_DEFINE([LT_INIT], [aix-soname=aix], [_LT_WITH_AIX_SONAME([aix])]) +LT_OPTION_DEFINE([LT_INIT], [aix-soname=both], [_LT_WITH_AIX_SONAME([both])]) +LT_OPTION_DEFINE([LT_INIT], [aix-soname=svr4], [_LT_WITH_AIX_SONAME([svr4])]) + + +# _LT_WITH_PIC([MODE]) +# -------------------- +# implement the --with-pic flag, and support the 'pic-only' and 'no-pic' +# LT_INIT options. +# MODE is either 'yes' or 'no'. If omitted, it defaults to 'both'. +m4_define([_LT_WITH_PIC], +[AC_ARG_WITH([pic], + [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@], + [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], + [lt_p=${PACKAGE-default} + case $withval in + yes|no) pic_mode=$withval ;; + *) + pic_mode=default + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for lt_pkg in $withval; do + IFS=$lt_save_ifs + if test "X$lt_pkg" = "X$lt_p"; then + pic_mode=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [pic_mode=m4_default([$1], [default])]) + +_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl +])# _LT_WITH_PIC + +LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])]) + +# Old name: +AU_DEFUN([AC_LIBTOOL_PICMODE], +[_LT_SET_OPTION([LT_INIT], [pic-only]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'pic-only' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_PICMODE], []) + + +m4_define([_LTDL_MODE], []) +LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive], + [m4_define([_LTDL_MODE], [nonrecursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [recursive], + [m4_define([_LTDL_MODE], [recursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [subproject], + [m4_define([_LTDL_MODE], [subproject])]) + +m4_define([_LTDL_TYPE], []) +LT_OPTION_DEFINE([LTDL_INIT], [installable], + [m4_define([_LTDL_TYPE], [installable])]) +LT_OPTION_DEFINE([LTDL_INIT], [convenience], + [m4_define([_LTDL_TYPE], [convenience])]) + +# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*- +# +# Copyright (C) 2004-2005, 2007-2008, 2011-2015 Free Software +# Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 6 ltsugar.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])]) + + +# lt_join(SEP, ARG1, [ARG2...]) +# ----------------------------- +# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their +# associated separator. +# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier +# versions in m4sugar had bugs. +m4_define([lt_join], +[m4_if([$#], [1], [], + [$#], [2], [[$2]], + [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])]) +m4_define([_lt_join], +[m4_if([$#$2], [2], [], + [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])]) + + +# lt_car(LIST) +# lt_cdr(LIST) +# ------------ +# Manipulate m4 lists. +# These macros are necessary as long as will still need to support +# Autoconf-2.59, which quotes differently. +m4_define([lt_car], [[$1]]) +m4_define([lt_cdr], +[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])], + [$#], 1, [], + [m4_dquote(m4_shift($@))])]) +m4_define([lt_unquote], $1) + + +# lt_append(MACRO-NAME, STRING, [SEPARATOR]) +# ------------------------------------------ +# Redefine MACRO-NAME to hold its former content plus 'SEPARATOR''STRING'. +# Note that neither SEPARATOR nor STRING are expanded; they are appended +# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked). +# No SEPARATOR is output if MACRO-NAME was previously undefined (different +# than defined and empty). +# +# This macro is needed until we can rely on Autoconf 2.62, since earlier +# versions of m4sugar mistakenly expanded SEPARATOR but not STRING. +m4_define([lt_append], +[m4_define([$1], + m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])]) + + + +# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...]) +# ---------------------------------------------------------- +# Produce a SEP delimited list of all paired combinations of elements of +# PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list +# has the form PREFIXmINFIXSUFFIXn. +# Needed until we can rely on m4_combine added in Autoconf 2.62. +m4_define([lt_combine], +[m4_if(m4_eval([$# > 3]), [1], + [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl +[[m4_foreach([_Lt_prefix], [$2], + [m4_foreach([_Lt_suffix], + ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[, + [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])]) + + +# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ]) +# ----------------------------------------------------------------------- +# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited +# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ. +m4_define([lt_if_append_uniq], +[m4_ifdef([$1], + [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1], + [lt_append([$1], [$2], [$3])$4], + [$5])], + [lt_append([$1], [$2], [$3])$4])]) + + +# lt_dict_add(DICT, KEY, VALUE) +# ----------------------------- +m4_define([lt_dict_add], +[m4_define([$1($2)], [$3])]) + + +# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE) +# -------------------------------------------- +m4_define([lt_dict_add_subkey], +[m4_define([$1($2:$3)], [$4])]) + + +# lt_dict_fetch(DICT, KEY, [SUBKEY]) +# ---------------------------------- +m4_define([lt_dict_fetch], +[m4_ifval([$3], + m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]), + m4_ifdef([$1($2)], [m4_defn([$1($2)])]))]) + + +# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE]) +# ----------------------------------------------------------------- +m4_define([lt_if_dict_fetch], +[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4], + [$5], + [$6])]) + + +# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...]) +# -------------------------------------------------------------- +m4_define([lt_dict_filter], +[m4_if([$5], [], [], + [lt_join(m4_quote(m4_default([$4], [[, ]])), + lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]), + [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl +]) + +# ltversion.m4 -- version numbers -*- Autoconf -*- +# +# Copyright (C) 2004, 2011-2015 Free Software Foundation, Inc. +# Written by Scott James Remnant, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# @configure_input@ + +# serial 4179 ltversion.m4 +# This file is part of GNU Libtool + +m4_define([LT_PACKAGE_VERSION], [2.4.6]) +m4_define([LT_PACKAGE_REVISION], [2.4.6]) + +AC_DEFUN([LTVERSION_VERSION], +[macro_version='2.4.6' +macro_revision='2.4.6' +_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?]) +_LT_DECL(, macro_revision, 0) +]) + +# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*- +# +# Copyright (C) 2004-2005, 2007, 2009, 2011-2015 Free Software +# Foundation, Inc. +# Written by Scott James Remnant, 2004. +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 5 lt~obsolete.m4 + +# These exist entirely to fool aclocal when bootstrapping libtool. +# +# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN), +# which have later been changed to m4_define as they aren't part of the +# exported API, or moved to Autoconf or Automake where they belong. +# +# The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN +# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us +# using a macro with the same name in our local m4/libtool.m4 it'll +# pull the old libtool.m4 in (it doesn't see our shiny new m4_define +# and doesn't know about Autoconf macros at all.) +# +# So we provide this file, which has a silly filename so it's always +# included after everything else. This provides aclocal with the +# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything +# because those macros already exist, or will be overwritten later. +# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. +# +# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here. +# Yes, that means every name once taken will need to remain here until +# we give up compatibility with versions before 1.7, at which point +# we need to keep only those names which we still refer to. + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])]) + +m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])]) +m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])]) +m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])]) +m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])]) +m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])]) +m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])]) +m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])]) +m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])]) +m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])]) +m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])]) +m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])]) +m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])]) +m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])]) +m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])]) +m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])]) +m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])]) +m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])]) +m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])]) +m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) +m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])]) +m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])]) +m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])]) +m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])]) +m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])]) +m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])]) +m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])]) +m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])]) +m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])]) +m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])]) +m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])]) +m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])]) +m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])]) +m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])]) +m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])]) +m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])]) +m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])]) +m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])]) +m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])]) +m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])]) +m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])]) +m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])]) +m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])]) +m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])]) +m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])]) +m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])]) +m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])]) +m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])]) +m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])]) +m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])]) +m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])]) +m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])]) + +# Copyright (C) 2002-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_AUTOMAKE_VERSION(VERSION) +# ---------------------------- +# Automake X.Y traces this macro to ensure aclocal.m4 has been +# generated from the m4 files accompanying Automake X.Y. +# (This private macro should not be called outside this file.) +AC_DEFUN([AM_AUTOMAKE_VERSION], +[am__api_version='1.15' +dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to +dnl require some minimum version. Point them to the right macro. +m4_if([$1], [1.15], [], + [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl +]) + +# _AM_AUTOCONF_VERSION(VERSION) +# ----------------------------- +# aclocal traces this macro to find the Autoconf version. +# This is a private macro too. Using m4_define simplifies +# the logic in aclocal, which can simply ignore this definition. +m4_define([_AM_AUTOCONF_VERSION], []) + +# AM_SET_CURRENT_AUTOMAKE_VERSION +# ------------------------------- +# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. +# This function is AC_REQUIREd by AM_INIT_AUTOMAKE. +AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], +[AM_AUTOMAKE_VERSION([1.15])dnl +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) + +# AM_AUX_DIR_EXPAND -*- Autoconf -*- + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets +# $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to +# '$srcdir', '$srcdir/..', or '$srcdir/../..'. +# +# Of course, Automake must honor this variable whenever it calls a +# tool from the auxiliary directory. The problem is that $srcdir (and +# therefore $ac_aux_dir as well) can be either absolute or relative, +# depending on how configure is run. This is pretty annoying, since +# it makes $ac_aux_dir quite unusable in subdirectories: in the top +# source directory, any form will work fine, but in subdirectories a +# relative path needs to be adjusted first. +# +# $ac_aux_dir/missing +# fails when called from a subdirectory if $ac_aux_dir is relative +# $top_srcdir/$ac_aux_dir/missing +# fails if $ac_aux_dir is absolute, +# fails when called from a subdirectory in a VPATH build with +# a relative $ac_aux_dir +# +# The reason of the latter failure is that $top_srcdir and $ac_aux_dir +# are both prefixed by $srcdir. In an in-source build this is usually +# harmless because $srcdir is '.', but things will broke when you +# start a VPATH build or use an absolute $srcdir. +# +# So we could use something similar to $top_srcdir/$ac_aux_dir/missing, +# iff we strip the leading $srcdir from $ac_aux_dir. That would be: +# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` +# and then we would define $MISSING as +# MISSING="\${SHELL} $am_aux_dir/missing" +# This will work as long as MISSING is not called from configure, because +# unfortunately $(top_srcdir) has no meaning in configure. +# However there are other variables, like CC, which are often used in +# configure, and could therefore not use this "fixed" $ac_aux_dir. +# +# Another solution, used here, is to always expand $ac_aux_dir to an +# absolute PATH. The drawback is that using absolute paths prevent a +# configured tree to be moved without reconfiguration. + +AC_DEFUN([AM_AUX_DIR_EXPAND], +[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +# Expand $ac_aux_dir to an absolute path. +am_aux_dir=`cd "$ac_aux_dir" && pwd` +]) + +# AM_CONDITIONAL -*- Autoconf -*- + +# Copyright (C) 1997-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_CONDITIONAL(NAME, SHELL-CONDITION) +# ------------------------------------- +# Define a conditional. +AC_DEFUN([AM_CONDITIONAL], +[AC_PREREQ([2.52])dnl + m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], + [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl +AC_SUBST([$1_TRUE])dnl +AC_SUBST([$1_FALSE])dnl +_AM_SUBST_NOTMAKE([$1_TRUE])dnl +_AM_SUBST_NOTMAKE([$1_FALSE])dnl +m4_define([_AM_COND_VALUE_$1], [$2])dnl +if $2; then + $1_TRUE= + $1_FALSE='#' +else + $1_TRUE='#' + $1_FALSE= +fi +AC_CONFIG_COMMANDS_PRE( +[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then + AC_MSG_ERROR([[conditional "$1" was never defined. +Usually this means the macro was only invoked conditionally.]]) +fi])]) + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + + +# There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be +# written in clear, in which case automake, when reading aclocal.m4, +# will think it sees a *use*, and therefore will trigger all it's +# C support machinery. Also note that it means that autoscan, seeing +# CC etc. in the Makefile, will ask for an AC_PROG_CC use... + + +# _AM_DEPENDENCIES(NAME) +# ---------------------- +# See how the compiler implements dependency checking. +# NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC". +# We try a few techniques and use that to set a single cache variable. +# +# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was +# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular +# dependency, and given that the user is not expected to run this macro, +# just rely on AC_PROG_CC. +AC_DEFUN([_AM_DEPENDENCIES], +[AC_REQUIRE([AM_SET_DEPDIR])dnl +AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl +AC_REQUIRE([AM_MAKE_INCLUDE])dnl +AC_REQUIRE([AM_DEP_TRACK])dnl + +m4_if([$1], [CC], [depcc="$CC" am_compiler_list=], + [$1], [CXX], [depcc="$CXX" am_compiler_list=], + [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'], + [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'], + [$1], [UPC], [depcc="$UPC" am_compiler_list=], + [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'], + [depcc="$$1" am_compiler_list=]) + +AC_CACHE_CHECK([dependency style of $depcc], + [am_cv_$1_dependencies_compiler_type], +[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_$1_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` + fi + am__universal=false + m4_case([$1], [CC], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac], + [CXX], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac]) + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_$1_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_$1_dependencies_compiler_type=none +fi +]) +AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) +AM_CONDITIONAL([am__fastdep$1], [ + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) +]) + + +# AM_SET_DEPDIR +# ------------- +# Choose a directory name for dependency files. +# This macro is AC_REQUIREd in _AM_DEPENDENCIES. +AC_DEFUN([AM_SET_DEPDIR], +[AC_REQUIRE([AM_SET_LEADING_DOT])dnl +AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl +]) + + +# AM_DEP_TRACK +# ------------ +AC_DEFUN([AM_DEP_TRACK], +[AC_ARG_ENABLE([dependency-tracking], [dnl +AS_HELP_STRING( + [--enable-dependency-tracking], + [do not reject slow dependency extractors]) +AS_HELP_STRING( + [--disable-dependency-tracking], + [speeds up one-time build])]) +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' + am__nodep='_no' +fi +AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) +AC_SUBST([AMDEPBACKSLASH])dnl +_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl +AC_SUBST([am__nodep])dnl +_AM_SUBST_NOTMAKE([am__nodep])dnl +]) + +# Generate code to set up dependency tracking. -*- Autoconf -*- + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + + +# _AM_OUTPUT_DEPENDENCY_COMMANDS +# ------------------------------ +AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], +[{ + # Older Autoconf quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + case $CONFIG_FILES in + *\'*) eval set x "$CONFIG_FILES" ;; + *) set x $CONFIG_FILES ;; + esac + shift + for mf + do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named 'Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`AS_DIRNAME("$mf")` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running 'make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "$am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`AS_DIRNAME(["$file"])` + AS_MKDIR_P([$dirpart/$fdir]) + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done + done +} +])# _AM_OUTPUT_DEPENDENCY_COMMANDS + + +# AM_OUTPUT_DEPENDENCY_COMMANDS +# ----------------------------- +# This macro should only be invoked once -- use via AC_REQUIRE. +# +# This code is only required when automatic dependency tracking +# is enabled. FIXME. This creates each '.P' file that we will +# need in order to bootstrap the dependency handling code. +AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], +[AC_CONFIG_COMMANDS([depfiles], + [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], + [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) +]) + +# Do all the work for Automake. -*- Autoconf -*- + +# Copyright (C) 1996-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This macro actually does too much. Some checks are only needed if +# your package does certain things. But this isn't really a big deal. + +dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O. +m4_define([AC_PROG_CC], +m4_defn([AC_PROG_CC]) +[_AM_PROG_CC_C_O +]) + +# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) +# AM_INIT_AUTOMAKE([OPTIONS]) +# ----------------------------------------------- +# The call with PACKAGE and VERSION arguments is the old style +# call (pre autoconf-2.50), which is being phased out. PACKAGE +# and VERSION should now be passed to AC_INIT and removed from +# the call to AM_INIT_AUTOMAKE. +# We support both call styles for the transition. After +# the next Automake release, Autoconf can make the AC_INIT +# arguments mandatory, and then we can depend on a new Autoconf +# release and drop the old call support. +AC_DEFUN([AM_INIT_AUTOMAKE], +[AC_PREREQ([2.65])dnl +dnl Autoconf wants to disallow AM_ names. We explicitly allow +dnl the ones we care about. +m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl +AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl +AC_REQUIRE([AC_PROG_INSTALL])dnl +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi +AC_SUBST([CYGPATH_W]) + +# Define the identity of the package. +dnl Distinguish between old-style and new-style calls. +m4_ifval([$2], +[AC_DIAGNOSE([obsolete], + [$0: two- and three-arguments forms are deprecated.]) +m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl + AC_SUBST([PACKAGE], [$1])dnl + AC_SUBST([VERSION], [$2])], +[_AM_SET_OPTIONS([$1])dnl +dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. +m4_if( + m4_ifdef([AC_PACKAGE_NAME], [ok]):m4_ifdef([AC_PACKAGE_VERSION], [ok]), + [ok:ok],, + [m4_fatal([AC_INIT should be called with package and version arguments])])dnl + AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl + AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl + +_AM_IF_OPTION([no-define],, +[AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) + AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl + +# Some tools Automake needs. +AC_REQUIRE([AM_SANITY_CHECK])dnl +AC_REQUIRE([AC_ARG_PROGRAM])dnl +AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}]) +AM_MISSING_PROG([AUTOCONF], [autoconf]) +AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}]) +AM_MISSING_PROG([AUTOHEADER], [autoheader]) +AM_MISSING_PROG([MAKEINFO], [makeinfo]) +AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl +AC_REQUIRE([AC_PROG_MKDIR_P])dnl +# For better backward compatibility. To be removed once Automake 1.9.x +# dies out for good. For more background, see: +# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html> +# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html> +AC_SUBST([mkdir_p], ['$(MKDIR_P)']) +# We need awk for the "check" target (and possibly the TAP driver). The +# system "awk" is bad on some platforms. +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([AC_PROG_MAKE_SET])dnl +AC_REQUIRE([AM_SET_LEADING_DOT])dnl +_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], + [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], + [_AM_PROG_TAR([v7])])]) +_AM_IF_OPTION([no-dependencies],, +[AC_PROVIDE_IFELSE([AC_PROG_CC], + [_AM_DEPENDENCIES([CC])], + [m4_define([AC_PROG_CC], + m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_CXX], + [_AM_DEPENDENCIES([CXX])], + [m4_define([AC_PROG_CXX], + m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJC], + [_AM_DEPENDENCIES([OBJC])], + [m4_define([AC_PROG_OBJC], + m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJCXX], + [_AM_DEPENDENCIES([OBJCXX])], + [m4_define([AC_PROG_OBJCXX], + m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl +]) +AC_REQUIRE([AM_SILENT_RULES])dnl +dnl The testsuite driver may need to know about EXEEXT, so add the +dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This +dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below. +AC_CONFIG_COMMANDS_PRE(dnl +[m4_provide_if([_AM_COMPILER_EXEEXT], + [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl + +# POSIX will say in a future version that running "rm -f" with no argument +# is OK; and we want to be able to make that assumption in our Makefile +# recipes. So use an aggressive probe to check that the usage we want is +# actually supported "in the wild" to an acceptable degree. +# See automake bug#10828. +# To make any issue more visible, cause the running configure to be aborted +# by default if the 'rm' program in use doesn't match our expectations; the +# user can still override this though. +if rm -f && rm -fr && rm -rf; then : OK; else + cat >&2 <<'END' +Oops! + +Your 'rm' program seems unable to run without file operands specified +on the command line, even when the '-f' option is present. This is contrary +to the behaviour of most rm programs out there, and not conforming with +the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542> + +Please tell bug-automake@gnu.org about your system, including the value +of your $PATH and any error possibly output before this message. This +can help us improve future automake versions. + +END + if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then + echo 'Configuration will proceed anyway, since you have set the' >&2 + echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 + echo >&2 + else + cat >&2 <<'END' +Aborting the configuration process, to ensure you take notice of the issue. + +You can download and install GNU coreutils to get an 'rm' implementation +that behaves properly: <http://www.gnu.org/software/coreutils/>. + +If you want to complete the configuration process using your problematic +'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM +to "yes", and re-run configure. + +END + AC_MSG_ERROR([Your 'rm' program is bad, sorry.]) + fi +fi +dnl The trailing newline in this macro's definition is deliberate, for +dnl backward compatibility and to allow trailing 'dnl'-style comments +dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841. +]) + +dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not +dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further +dnl mangled by Autoconf and run in a shell conditional statement. +m4_define([_AC_COMPILER_EXEEXT], +m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) + +# When config.status generates a header, we must update the stamp-h file. +# This file resides in the same directory as the config header +# that is generated. The stamp files are numbered to have different names. + +# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the +# loop where config.status creates the headers, so we can generate +# our stamp files there. +AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], +[# Compute $1's index in $config_headers. +_am_arg=$1 +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_SH +# ------------------ +# Define $install_sh. +AC_DEFUN([AM_PROG_INSTALL_SH], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +if test x"${install_sh+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi +AC_SUBST([install_sh])]) + +# Copyright (C) 2003-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# Check whether the underlying file-system supports filenames +# with a leading dot. For instance MS-DOS doesn't. +AC_DEFUN([AM_SET_LEADING_DOT], +[rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null +AC_SUBST([am__leading_dot])]) + +# Check to see how 'make' treats includes. -*- Autoconf -*- + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_MAKE_INCLUDE() +# ----------------- +# Check to see how make treats includes. +AC_DEFUN([AM_MAKE_INCLUDE], +[am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo this is the am__doit target +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +AC_MSG_CHECKING([for style of include used by $am_make]) +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# Ignore all kinds of additional output from 'make'. +case `$am_make -s -f confmf 2> /dev/null` in #( +*the\ am__doit\ target*) + am__include=include + am__quote= + _am_result=GNU + ;; +esac +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + case `$am_make -s -f confmf 2> /dev/null` in #( + *the\ am__doit\ target*) + am__include=.include + am__quote="\"" + _am_result=BSD + ;; + esac +fi +AC_SUBST([am__include]) +AC_SUBST([am__quote]) +AC_MSG_RESULT([$_am_result]) +rm -f confinc confmf +]) + +# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- + +# Copyright (C) 1997-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_MISSING_PROG(NAME, PROGRAM) +# ------------------------------ +AC_DEFUN([AM_MISSING_PROG], +[AC_REQUIRE([AM_MISSING_HAS_RUN]) +$1=${$1-"${am_missing_run}$2"} +AC_SUBST($1)]) + +# AM_MISSING_HAS_RUN +# ------------------ +# Define MISSING if not defined so far and test if it is modern enough. +# If it is, set am_missing_run to use it, otherwise, to nothing. +AC_DEFUN([AM_MISSING_HAS_RUN], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([missing])dnl +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --is-lightweight"; then + am_missing_run="$MISSING " +else + am_missing_run= + AC_MSG_WARN(['missing' script is too old or missing]) +fi +]) + +# Helper functions for option handling. -*- Autoconf -*- + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_MANGLE_OPTION(NAME) +# ----------------------- +AC_DEFUN([_AM_MANGLE_OPTION], +[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) + +# _AM_SET_OPTION(NAME) +# -------------------- +# Set option NAME. Presently that only means defining a flag for this option. +AC_DEFUN([_AM_SET_OPTION], +[m4_define(_AM_MANGLE_OPTION([$1]), [1])]) + +# _AM_SET_OPTIONS(OPTIONS) +# ------------------------ +# OPTIONS is a space-separated list of Automake options. +AC_DEFUN([_AM_SET_OPTIONS], +[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) + +# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) +# ------------------------------------------- +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +AC_DEFUN([_AM_IF_OPTION], +[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_PROG_CC_C_O +# --------------- +# Like AC_PROG_CC_C_O, but changed for automake. We rewrite AC_PROG_CC +# to automatically call this. +AC_DEFUN([_AM_PROG_CC_C_O], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([compile])dnl +AC_LANG_PUSH([C])dnl +AC_CACHE_CHECK( + [whether $CC understands -c and -o together], + [am_cv_prog_cc_c_o], + [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])]) + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i]) +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +AC_LANG_POP([C])]) + +# For backward compatibility. +AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])]) + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_RUN_LOG(COMMAND) +# ------------------- +# Run COMMAND, save the exit status in ac_status, and log it. +# (This has been adapted from Autoconf's _AC_RUN_LOG macro.) +AC_DEFUN([AM_RUN_LOG], +[{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD + ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + (exit $ac_status); }]) + +# Check to make sure that the build environment is sane. -*- Autoconf -*- + +# Copyright (C) 1996-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_SANITY_CHECK +# --------------- +AC_DEFUN([AM_SANITY_CHECK], +[AC_MSG_CHECKING([whether build environment is sane]) +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[[\\\"\#\$\&\'\`$am_lf]]*) + AC_MSG_ERROR([unsafe absolute working directory name]);; +esac +case $srcdir in + *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*) + AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);; +esac + +# Do 'set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + am_has_slept=no + for am_try in 1 2; do + echo "timestamp, slept: $am_has_slept" > conftest.file + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$[*]" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + if test "$[*]" != "X $srcdir/configure conftest.file" \ + && test "$[*]" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken + alias in your environment]) + fi + if test "$[2]" = conftest.file || test $am_try -eq 2; then + break + fi + # Just in case. + sleep 1 + am_has_slept=yes + done + test "$[2]" = conftest.file + ) +then + # Ok. + : +else + AC_MSG_ERROR([newly created file is older than distributed files! +Check your system clock]) +fi +AC_MSG_RESULT([yes]) +# If we didn't sleep, we still need to ensure time stamps of config.status and +# generated files are strictly newer. +am_sleep_pid= +if grep 'slept: no' conftest.file >/dev/null 2>&1; then + ( sleep 1 ) & + am_sleep_pid=$! +fi +AC_CONFIG_COMMANDS_PRE( + [AC_MSG_CHECKING([that generated files are newer than configure]) + if test -n "$am_sleep_pid"; then + # Hide warnings about reused PIDs. + wait $am_sleep_pid 2>/dev/null + fi + AC_MSG_RESULT([done])]) +rm -f conftest.file +]) + +# Copyright (C) 2009-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_SILENT_RULES([DEFAULT]) +# -------------------------- +# Enable less verbose build rules; with the default set to DEFAULT +# ("yes" being less verbose, "no" or empty being verbose). +AC_DEFUN([AM_SILENT_RULES], +[AC_ARG_ENABLE([silent-rules], [dnl +AS_HELP_STRING( + [--enable-silent-rules], + [less verbose build output (undo: "make V=1")]) +AS_HELP_STRING( + [--disable-silent-rules], + [verbose build output (undo: "make V=0")])dnl +]) +case $enable_silent_rules in @%:@ ((( + yes) AM_DEFAULT_VERBOSITY=0;; + no) AM_DEFAULT_VERBOSITY=1;; + *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);; +esac +dnl +dnl A few 'make' implementations (e.g., NonStop OS and NextStep) +dnl do not support nested variable expansions. +dnl See automake bug#9928 and bug#10237. +am_make=${MAKE-make} +AC_CACHE_CHECK([whether $am_make supports nested variables], + [am_cv_make_support_nested_variables], + [if AS_ECHO([['TRUE=$(BAR$(V)) +BAR0=false +BAR1=true +V=1 +am__doit: + @$(TRUE) +.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then + am_cv_make_support_nested_variables=yes +else + am_cv_make_support_nested_variables=no +fi]) +if test $am_cv_make_support_nested_variables = yes; then + dnl Using '$V' instead of '$(V)' breaks IRIX make. + AM_V='$(V)' + AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' +else + AM_V=$AM_DEFAULT_VERBOSITY + AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY +fi +AC_SUBST([AM_V])dnl +AM_SUBST_NOTMAKE([AM_V])dnl +AC_SUBST([AM_DEFAULT_V])dnl +AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl +AC_SUBST([AM_DEFAULT_VERBOSITY])dnl +AM_BACKSLASH='\' +AC_SUBST([AM_BACKSLASH])dnl +_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl +]) + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_STRIP +# --------------------- +# One issue with vendor 'install' (even GNU) is that you can't +# specify the program used to strip binaries. This is especially +# annoying in cross-compiling environments, where the build's strip +# is unlikely to handle the host's binaries. +# Fortunately install-sh will honor a STRIPPROG variable, so we +# always use install-sh in "make install-strip", and initialize +# STRIPPROG with the value of the STRIP variable (set by the user). +AC_DEFUN([AM_PROG_INSTALL_STRIP], +[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +# Installed binaries are usually stripped using 'strip' when the user +# run "make install-strip". However 'strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the 'STRIP' environment variable to overrule this program. +dnl Don't test for $cross_compiling = yes, because it might be 'maybe'. +if test "$cross_compiling" != no; then + AC_CHECK_TOOL([STRIP], [strip], :) +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" +AC_SUBST([INSTALL_STRIP_PROGRAM])]) + +# Copyright (C) 2006-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. +# This macro is traced by Automake. +AC_DEFUN([_AM_SUBST_NOTMAKE]) + +# AM_SUBST_NOTMAKE(VARIABLE) +# -------------------------- +# Public sister of _AM_SUBST_NOTMAKE. +AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) + +# Check how to create a tarball. -*- Autoconf -*- + +# Copyright (C) 2004-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_PROG_TAR(FORMAT) +# -------------------- +# Check how to create a tarball in format FORMAT. +# FORMAT should be one of 'v7', 'ustar', or 'pax'. +# +# Substitute a variable $(am__tar) that is a command +# writing to stdout a FORMAT-tarball containing the directory +# $tardir. +# tardir=directory && $(am__tar) > result.tar +# +# Substitute a variable $(am__untar) that extract such +# a tarball read from stdin. +# $(am__untar) < result.tar +# +AC_DEFUN([_AM_PROG_TAR], +[# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AC_SUBST([AMTAR], ['$${TAR-tar}']) + +# We'll loop over all known methods to create a tar archive until one works. +_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' + +m4_if([$1], [v7], + [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], + + [m4_case([$1], + [ustar], + [# The POSIX 1988 'ustar' format is defined with fixed-size fields. + # There is notably a 21 bits limit for the UID and the GID. In fact, + # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 + # and bug#13588). + am_max_uid=2097151 # 2^21 - 1 + am_max_gid=$am_max_uid + # The $UID and $GID variables are not portable, so we need to resort + # to the POSIX-mandated id(1) utility. Errors in the 'id' calls + # below are definitely unexpected, so allow the users to see them + # (that is, avoid stderr redirection). + am_uid=`id -u || echo unknown` + am_gid=`id -g || echo unknown` + AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format]) + if test $am_uid -le $am_max_uid; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + _am_tools=none + fi + AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format]) + if test $am_gid -le $am_max_gid; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + _am_tools=none + fi], + + [pax], + [], + + [m4_fatal([Unknown tar format])]) + + AC_MSG_CHECKING([how to create a $1 tar archive]) + + # Go ahead even if we have the value already cached. We do so because we + # need to set the values for the 'am__tar' and 'am__untar' variables. + _am_tools=${am_cv_prog_tar_$1-$_am_tools} + + for _am_tool in $_am_tools; do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; do + AM_RUN_LOG([$_am_tar --version]) && break + done + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x $1 -w "$$tardir"' + am__tar_='pax -L -x $1 -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H $1 -L' + am__tar_='find "$tardir" -print | cpio -o -H $1 -L' + am__untar='cpio -i -H $1 -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_$1}" && break + + # tar/untar a dummy directory, and stop if the command works. + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) + rm -rf conftest.dir + if test -s conftest.tar; then + AM_RUN_LOG([$am__untar <conftest.tar]) + AM_RUN_LOG([cat conftest.dir/file]) + grep GrepMe conftest.dir/file >/dev/null 2>&1 && break + fi + done + rm -rf conftest.dir + + AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) + AC_MSG_RESULT([$am_cv_prog_tar_$1])]) + +AC_SUBST([am__tar]) +AC_SUBST([am__untar]) +]) # _AM_PROG_TAR + diff --git a/SQLITE/compile b/SQLITE/compile new file mode 100755 index 0000000..a85b723 --- /dev/null +++ b/SQLITE/compile @@ -0,0 +1,347 @@ +#! /bin/sh +# Wrapper for compilers which do not understand '-c -o'. + +scriptversion=2012-10-14.11; # UTC + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. +# Written by Tom Tromey <tromey@cygnus.com>. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to <bug-automake@gnu.org> or send patches to +# <automake-patches@gnu.org>. + +nl=' +' + +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent tools from complaining about whitespace usage. +IFS=" "" $nl" + +file_conv= + +# func_file_conv build_file lazy +# Convert a $build file to $host form and store it in $file +# Currently only supports Windows hosts. If the determined conversion +# type is listed in (the comma separated) LAZY, no conversion will +# take place. +func_file_conv () +{ + file=$1 + case $file in + / | /[!/]*) # absolute file, and not a UNC file + if test -z "$file_conv"; then + # lazily determine how to convert abs files + case `uname -s` in + MINGW*) + file_conv=mingw + ;; + CYGWIN*) + file_conv=cygwin + ;; + *) + file_conv=wine + ;; + esac + fi + case $file_conv/,$2, in + *,$file_conv,*) + ;; + mingw/*) + file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` + ;; + cygwin/*) + file=`cygpath -m "$file" || echo "$file"` + ;; + wine/*) + file=`winepath -w "$file" || echo "$file"` + ;; + esac + ;; + esac +} + +# func_cl_dashL linkdir +# Make cl look for libraries in LINKDIR +func_cl_dashL () +{ + func_file_conv "$1" + if test -z "$lib_path"; then + lib_path=$file + else + lib_path="$lib_path;$file" + fi + linker_opts="$linker_opts -LIBPATH:$file" +} + +# func_cl_dashl library +# Do a library search-path lookup for cl +func_cl_dashl () +{ + lib=$1 + found=no + save_IFS=$IFS + IFS=';' + for dir in $lib_path $LIB + do + IFS=$save_IFS + if $shared && test -f "$dir/$lib.dll.lib"; then + found=yes + lib=$dir/$lib.dll.lib + break + fi + if test -f "$dir/$lib.lib"; then + found=yes + lib=$dir/$lib.lib + break + fi + if test -f "$dir/lib$lib.a"; then + found=yes + lib=$dir/lib$lib.a + break + fi + done + IFS=$save_IFS + + if test "$found" != yes; then + lib=$lib.lib + fi +} + +# func_cl_wrapper cl arg... +# Adjust compile command to suit cl +func_cl_wrapper () +{ + # Assume a capable shell + lib_path= + shared=: + linker_opts= + for arg + do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + eat=1 + case $2 in + *.o | *.[oO][bB][jJ]) + func_file_conv "$2" + set x "$@" -Fo"$file" + shift + ;; + *) + func_file_conv "$2" + set x "$@" -Fe"$file" + shift + ;; + esac + ;; + -I) + eat=1 + func_file_conv "$2" mingw + set x "$@" -I"$file" + shift + ;; + -I*) + func_file_conv "${1#-I}" mingw + set x "$@" -I"$file" + shift + ;; + -l) + eat=1 + func_cl_dashl "$2" + set x "$@" "$lib" + shift + ;; + -l*) + func_cl_dashl "${1#-l}" + set x "$@" "$lib" + shift + ;; + -L) + eat=1 + func_cl_dashL "$2" + ;; + -L*) + func_cl_dashL "${1#-L}" + ;; + -static) + shared=false + ;; + -Wl,*) + arg=${1#-Wl,} + save_ifs="$IFS"; IFS=',' + for flag in $arg; do + IFS="$save_ifs" + linker_opts="$linker_opts $flag" + done + IFS="$save_ifs" + ;; + -Xlinker) + eat=1 + linker_opts="$linker_opts $2" + ;; + -*) + set x "$@" "$1" + shift + ;; + *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) + func_file_conv "$1" + set x "$@" -Tp"$file" + shift + ;; + *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) + func_file_conv "$1" mingw + set x "$@" "$file" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift + done + if test -n "$linker_opts"; then + linker_opts="-link$linker_opts" + fi + exec "$@" $linker_opts + exit 1 +} + +eat= + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: compile [--help] [--version] PROGRAM [ARGS] + +Wrapper for compilers which do not understand '-c -o'. +Remove '-o dest.o' from ARGS, run PROGRAM with the remaining +arguments, and rename the output as expected. + +If you are trying to build a whole package this is not the +right script to run: please start by reading the file 'INSTALL'. + +Report bugs to <bug-automake@gnu.org>. +EOF + exit $? + ;; + -v | --v*) + echo "compile $scriptversion" + exit $? + ;; + cl | *[/\\]cl | cl.exe | *[/\\]cl.exe ) + func_cl_wrapper "$@" # Doesn't return... + ;; +esac + +ofile= +cfile= + +for arg +do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + # So we strip '-o arg' only if arg is an object. + eat=1 + case $2 in + *.o | *.obj) + ofile=$2 + ;; + *) + set x "$@" -o "$2" + shift + ;; + esac + ;; + *.c) + cfile=$1 + set x "$@" "$1" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift +done + +if test -z "$ofile" || test -z "$cfile"; then + # If no '-o' option was seen then we might have been invoked from a + # pattern rule where we don't need one. That is ok -- this is a + # normal compilation that the losing compiler can handle. If no + # '.c' file was seen then we are probably linking. That is also + # ok. + exec "$@" +fi + +# Name of file we expect compiler to create. +cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` + +# Create the lock directory. +# Note: use '[/\\:.-]' here to ensure that we don't use the same name +# that we are using for the .o file. Also, base the name on the expected +# object file name, since that is what matters with a parallel build. +lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d +while true; do + if mkdir "$lockdir" >/dev/null 2>&1; then + break + fi + sleep 1 +done +# FIXME: race condition here if user kills between mkdir and trap. +trap "rmdir '$lockdir'; exit 1" 1 2 15 + +# Run the compile. +"$@" +ret=$? + +if test -f "$cofile"; then + test "$cofile" = "$ofile" || mv "$cofile" "$ofile" +elif test -f "${cofile}bj"; then + test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" +fi + +rmdir "$lockdir" +exit $ret + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/SQLITE/config.guess b/SQLITE/config.guess new file mode 100755 index 0000000..1659250 --- /dev/null +++ b/SQLITE/config.guess @@ -0,0 +1,1441 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright 1992-2015 Free Software Foundation, Inc. + +timestamp='2015-08-20' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). +# +# Originally written by Per Bothner; maintained since 2000 by Ben Elliston. +# +# You can get the latest version of this script from: +# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD +# +# Please send patches to <config-patches@gnu.org>. + + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to <config-patches@gnu.org>." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright 1992-2015 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case "${UNAME_SYSTEM}" in +Linux|GNU|GNU/*) + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + LIBC=gnu + + eval $set_cc_for_build + cat <<-EOF > $dummy.c + #include <features.h> + #if defined(__UCLIBC__) + LIBC=uclibc + #elif defined(__dietlibc__) + LIBC=dietlibc + #else + LIBC=gnu + #endif + EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` + ;; +esac + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ + /sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || \ + echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + earmv*) + arch=`echo ${UNAME_MACHINE_ARCH} | sed -e 's,^e\(armv[0-9]\).*$,\1,'` + endian=`echo ${UNAME_MACHINE_ARCH} | sed -ne 's,^.*\(eb\)$,\1,p'` + machine=${arch}${endian}-unknown + ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|earm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ELF__ + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # Determine ABI tags. + case "${UNAME_MACHINE_ARCH}" in + earm*) + expr='s/^earmv[0-9]/-eabi/;s/eb$//' + abi=`echo ${UNAME_MACHINE_ARCH} | sed -e "$expr"` + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE} | sed -e 's/[-_].*//' | cut -d. -f1,2` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}${abi}" + exit ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-bitrig${UNAME_RELEASE} + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} + exit ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit ;; + *:SolidBSD:*:*) + echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE} + exit ;; + macppc:MirBSD:*:*) + echo powerpc-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:Sortix:*:*) + echo ${UNAME_MACHINE}-unknown-sortix + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + exitcode=$? + trap '' 0 + exit $exitcode ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit ;; + arm*:riscos:*:*|arm*:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + s390x:SunOS:*:*) + echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + echo i386-pc-auroraux${UNAME_RELEASE} + exit ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + eval $set_cc_for_build + SUN_ARCH="i386" + # If there is a compiler, see if it is configured for 64-bit objects. + # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. + # This test works for both compilers. + if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then + if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH="x86_64" + fi + fi + echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include <stdio.h> /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && + dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`$dummy $dummyarg` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos${UNAME_RELEASE} + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include <sys/systemcfg.h> + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[4567]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/lslpp ] ; then + IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | + awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include <stdlib.h> + #include <unistd.h> + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + eval $set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | + grep -q __LP64__ + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include <unistd.h> + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:FreeBSD:*:*) + UNAME_PROCESSOR=`/usr/bin/uname -p` + case ${UNAME_PROCESSOR} in + amd64) + echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + *) + echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + esac + exit ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit ;; + *:MINGW64*:*) + echo ${UNAME_MACHINE}-pc-mingw64 + exit ;; + *:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit ;; + *:MSYS*:*) + echo ${UNAME_MACHINE}-pc-msys + exit ;; + i*:windows32*:*) + # uname -m includes "-pc" on this system. + echo ${UNAME_MACHINE}-mingw32 + exit ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit ;; + *:Interix*:*) + case ${UNAME_MACHINE} in + x86) + echo i586-pc-interix${UNAME_RELEASE} + exit ;; + authenticamd | genuineintel | EM64T) + echo x86_64-unknown-interix${UNAME_RELEASE} + exit ;; + IA64) + echo ia64-unknown-interix${UNAME_RELEASE} + exit ;; + esac ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit ;; + 8664:Windows_NT:*) + echo x86_64-pc-mks + exit ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-${LIBC}`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC} + exit ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit ;; + aarch64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep -q ld.so.1 + if test "$?" = 0 ; then LIBC="gnulibc1" ; fi + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + arc:Linux:*:* | arceb:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + arm*:Linux:*:*) + eval $set_cc_for_build + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabi + else + echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabihf + fi + fi + exit ;; + avr32*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + cris:Linux:*:*) + echo ${UNAME_MACHINE}-axis-linux-${LIBC} + exit ;; + crisv32:Linux:*:*) + echo ${UNAME_MACHINE}-axis-linux-${LIBC} + exit ;; + e2k:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + frv:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + hexagon:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + i*86:Linux:*:*) + echo ${UNAME_MACHINE}-pc-linux-${LIBC} + exit ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + m32r*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + mips:Linux:*:* | mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef ${UNAME_MACHINE} + #undef ${UNAME_MACHINE}el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=${UNAME_MACHINE}el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=${UNAME_MACHINE} + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; } + ;; + openrisc*:Linux:*:*) + echo or1k-unknown-linux-${LIBC} + exit ;; + or32:Linux:*:* | or1k*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + padre:Linux:*:*) + echo sparc-unknown-linux-${LIBC} + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-${LIBC} + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-${LIBC} ;; + PA8*) echo hppa2.0-unknown-linux-${LIBC} ;; + *) echo hppa-unknown-linux-${LIBC} ;; + esac + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-${LIBC} + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-${LIBC} + exit ;; + ppc64le:Linux:*:*) + echo powerpc64le-unknown-linux-${LIBC} + exit ;; + ppcle:Linux:*:*) + echo powerpcle-unknown-linux-${LIBC} + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux-${LIBC} + exit ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + tile*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + vax:Linux:*:*) + echo ${UNAME_MACHINE}-dec-linux-${LIBC} + exit ;; + x86_64:Linux:*:*) + echo ${UNAME_MACHINE}-pc-linux-${LIBC} + exit ;; + xtensa*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name` + echo ${UNAME_MACHINE}-pc-isc$UNAME_REL + elif /bin/uname -X 2>/dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. + # Note: whatever this is, it MUST be the same as what config.sub + # prints for the "djgpp" host, or else GDB configury will decide that + # this is a cross-build. + echo i586-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + NCR*:*:4.2:* | MPRAS*:*:4.2:*) + OS_REL='.3' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says <Richard.M.Bartel@ccMail.Census.GOV> + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes <hewes@openmarket.com>. + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo ${UNAME_MACHINE}-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + echo i586-pc-haiku + exit ;; + x86_64:Haiku:*:*) + echo x86_64-unknown-haiku + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit ;; + SX-7:SUPER-UX:*:*) + echo sx7-nec-superux${UNAME_RELEASE} + exit ;; + SX-8:SUPER-UX:*:*) + echo sx8-nec-superux${UNAME_RELEASE} + exit ;; + SX-8R:SUPER-UX:*:*) + echo sx8r-nec-superux${UNAME_RELEASE} + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + eval $set_cc_for_build + if test "$UNAME_PROCESSOR" = unknown ; then + UNAME_PROCESSOR=powerpc + fi + if test `echo "$UNAME_RELEASE" | sed -e 's/\..*//'` -le 10 ; then + if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + case $UNAME_PROCESSOR in + i386) UNAME_PROCESSOR=x86_64 ;; + powerpc) UNAME_PROCESSOR=powerpc64 ;; + esac + fi + fi + elif test "$UNAME_PROCESSOR" = i386 ; then + # Avoid executing cc on OS X 10.9, as it ships with a stub + # that puts up a graphical alert prompting to install + # developer tools. Any system running Mac OS X 10.7 or + # later (Darwin 11 and later) is required to have a 64-bit + # processor. This is not true of the ARM version of Darwin + # that Apple uses in portable devices. + UNAME_PROCESSOR=x86_64 + fi + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NEO-?:NONSTOP_KERNEL:*:*) + echo neo-tandem-nsk${UNAME_RELEASE} + exit ;; + NSE-*:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk${UNAME_RELEASE} + exit ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "${UNAME_MACHINE}" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' + exit ;; + i*86:rdos:*:*) + echo ${UNAME_MACHINE}-pc-rdos + exit ;; + i*86:AROS:*:*) + echo ${UNAME_MACHINE}-pc-aros + exit ;; + x86_64:VMkernel:*:*) + echo ${UNAME_MACHINE}-unknown-esx + exit ;; +esac + +cat >&2 <<EOF +$0: unable to guess system type + +This script, last modified $timestamp, has failed to recognize +the operating system you are using. It is advised that you +download the most up to date version of the config scripts from + + http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD +and + http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD + +If the version you run ($0) is already up to date, please +send the following data and any information you think might be +pertinent to <config-patches@gnu.org> in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/SQLITE/config.sub b/SQLITE/config.sub new file mode 100755 index 0000000..1acc966 --- /dev/null +++ b/SQLITE/config.sub @@ -0,0 +1,1813 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright 1992-2015 Free Software Foundation, Inc. + +timestamp='2015-08-20' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). + + +# Please send patches to <config-patches@gnu.org>. +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# You can get the latest version of this script from: +# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to <config-patches@gnu.org>." + +version="\ +GNU config.sub ($timestamp) + +Copyright 1992-2015 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \ + linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ + knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \ + kopensolaris*-gnu* | \ + storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + android-linux) + os=-linux-android + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis | -knuth | -cray | -microblaze*) + os= + basic_machine=$1 + ;; + -bluegene*) + os=-cnk + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco6) + os=-sco5v6 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*178) + os=-lynxos178 + ;; + -lynx*5) + os=-lynxos5 + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | aarch64 | aarch64_be \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arceb \ + | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ + | avr | avr32 \ + | ba \ + | be32 | be64 \ + | bfin \ + | c4x | c8051 | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | e2k | epiphany \ + | fido | fr30 | frv | ft32 \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | hexagon \ + | i370 | i860 | i960 | ia64 \ + | ip2k | iq2000 \ + | k1om \ + | le32 | le64 \ + | lm32 \ + | m32c | m32r | m32rle | m68000 | m68k | m88k \ + | maxq | mb | microblaze | microblazeel | mcore | mep | metag \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64octeon | mips64octeonel \ + | mips64orion | mips64orionel \ + | mips64r5900 | mips64r5900el \ + | mips64vr | mips64vrel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mips64vr5900 | mips64vr5900el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa32r6 | mipsisa32r6el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64r6 | mipsisa64r6el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipsr5900 | mipsr5900el \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | moxie \ + | mt \ + | msp430 \ + | nds32 | nds32le | nds32be \ + | nios | nios2 | nios2eb | nios2el \ + | ns16k | ns32k \ + | open8 | or1k | or1knd | or32 \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle \ + | pyramid \ + | riscv32 | riscv64 \ + | rl78 | rx \ + | score \ + | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[234]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ + | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ + | spu \ + | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ + | ubicom32 \ + | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \ + | visium \ + | we32k \ + | x86 | xc16x | xstormy16 | xtensa \ + | z8k | z80) + basic_machine=$basic_machine-unknown + ;; + c54x) + basic_machine=tic54x-unknown + ;; + c55x) + basic_machine=tic55x-unknown + ;; + c6x) + basic_machine=tic6x-unknown + ;; + leon|leon[3-9]) + basic_machine=sparc-$basic_machine + ;; + m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip) + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + ms1) + basic_machine=mt-unknown + ;; + + strongarm | thumb | xscale) + basic_machine=arm-unknown + ;; + xgate) + basic_machine=$basic_machine-unknown + os=-none + ;; + xscaleeb) + basic_machine=armeb-unknown + ;; + + xscaleel) + basic_machine=armel-unknown + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | aarch64-* | aarch64_be-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* | avr32-* \ + | ba-* \ + | be32-* | be64-* \ + | bfin-* | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* \ + | c8051-* | clipper-* | craynv-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | e2k-* | elxsi-* \ + | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | hexagon-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | ip2k-* | iq2000-* \ + | k1om-* \ + | le32-* | le64-* \ + | lm32-* \ + | m32c-* | m32r-* | m32rle-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \ + | microblaze-* | microblazeel-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64octeon-* | mips64octeonel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64r5900-* | mips64r5900el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mips64vr5900-* | mips64vr5900el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa32r6-* | mipsisa32r6el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64r6-* | mipsisa64r6el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipsr5900-* | mipsr5900el-* \ + | mipstx39-* | mipstx39el-* \ + | mmix-* \ + | mt-* \ + | msp430-* \ + | nds32-* | nds32le-* | nds32be-* \ + | nios-* | nios2-* | nios2eb-* | nios2el-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | open8-* \ + | or1k*-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ + | pyramid-* \ + | riscv32-* | riscv64-* \ + | rl78-* | romp-* | rs6000-* | rx-* \ + | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ + | sparclite-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx*-* \ + | tahoe-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tile*-* \ + | tron-* \ + | ubicom32-* \ + | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \ + | vax-* \ + | visium-* \ + | we32k-* \ + | x86-* | x86_64-* | xc16x-* | xps100-* \ + | xstormy16-* | xtensa*-* \ + | ymp-* \ + | z8k-* | z80-*) + ;; + # Recognize the basic CPU types without company name, with glob match. + xtensa*) + basic_machine=$basic_machine-unknown + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aros) + basic_machine=i386-pc + os=-aros + ;; + asmjs) + basic_machine=asmjs-unknown + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + blackfin) + basic_machine=bfin-unknown + os=-linux + ;; + blackfin-*) + basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + bluegene*) + basic_machine=powerpc-ibm + os=-cnk + ;; + c54x-*) + basic_machine=tic54x-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + c55x-*) + basic_machine=tic55x-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + c6x-*) + basic_machine=tic6x-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + cegcc) + basic_machine=arm-unknown + os=-cegcc + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + craynv) + basic_machine=craynv-cray + os=-unicosmp + ;; + cr16 | cr16-*) + basic_machine=cr16-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + crisv32 | crisv32-* | etraxfs*) + basic_machine=crisv32-axis + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dicos) + basic_machine=i686-pc + os=-dicos + ;; + djgpp) + basic_machine=i586-pc + os=-msdosdjgpp + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + leon-*|leon[3-9]-*) + basic_machine=sparc-`echo $basic_machine | sed 's/-.*//'` + ;; + m68knommu) + basic_machine=m68k-unknown + os=-linux + ;; + m68knommu-*) + basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + microblaze*) + basic_machine=microblaze-xilinx + ;; + mingw64) + basic_machine=x86_64-pc + os=-mingw64 + ;; + mingw32) + basic_machine=i686-pc + os=-mingw32 + ;; + mingw32ce) + basic_machine=arm-unknown + os=-mingw32ce + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + moxiebox) + basic_machine=moxie-unknown + os=-moxiebox + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + ms1-*) + basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'` + ;; + msys) + basic_machine=i686-pc + os=-msys + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + nacl) + basic_machine=le32-unknown + os=-nacl + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + neo-tandem) + basic_machine=neo-tandem + ;; + nse-tandem) + basic_machine=nse-tandem + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + openrisc | openrisc-*) + basic_machine=or32-unknown + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + parisc) + basic_machine=hppa-unknown + os=-linux + ;; + parisc-*) + basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pc98) + basic_machine=i386-pc + ;; + pc98-*) + basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc | ppcbe) basic_machine=powerpc-unknown + ;; + ppc-* | ppcbe-*) + basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rdos | rdos64) + basic_machine=x86_64-pc + os=-rdos + ;; + rdos32) + basic_machine=i386-pc + os=-rdos + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sde) + basic_machine=mipsisa32-sde + os=-elf + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sh5el) + basic_machine=sh5le-unknown + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparclite-wrs | simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + strongarm-* | thumb-*) + basic_machine=arm-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tile*) + basic_machine=$basic_machine-unknown + os=-linux-gnu + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xbox) + basic_machine=i686-pc + os=-mingw32 + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + xscale-* | xscalee[bl]-*) + basic_machine=`echo $basic_machine | sed 's/^xscale/arm/'` + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + z80-*-coff) + basic_machine=z80-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + mmix) + basic_machine=mmix-knuth + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -auroraux) + os=-auroraux + ;; + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \ + | -sym* | -kopensolaris* | -plan9* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* | -aros* | -cloudabi* | -sortix* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \ + | -bitrig* | -openbsd* | -solidbsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* | -cegcc* \ + | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \ + | -linux-newlib* | -linux-musl* | -linux-uclibc* \ + | -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ + | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* | -tirtos*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -aros*) + os=-aros + ;; + -zvmoe) + os=-zvmoe + ;; + -dicos*) + os=-dicos + ;; + -nacl*) + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + score-*) + os=-elf + ;; + spu-*) + os=-elf + ;; + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + c8051-*) + os=-elf + ;; + hexagon-*) + os=-elf + ;; + tic54x-*) + os=-coff + ;; + tic55x-*) + os=-coff + ;; + tic6x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + ;; + m68*-cisco) + os=-aout + ;; + mep-*) + os=-elf + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-haiku) + os=-haiku + ;; + *-ibm) + os=-aix + ;; + *-knuth) + os=-mmixware + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -cnk*|-aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/SQLITE/configure b/SQLITE/configure new file mode 100755 index 0000000..ff123e3 --- /dev/null +++ b/SQLITE/configure @@ -0,0 +1,16112 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69 for sqlite 3.44.0. +# +# Report bugs to <http://www.sqlite.org>. +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 + + test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO + ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\ + || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org and +$0: http://www.sqlite.org about your system, including any +$0: error possibly output before this message. Then install +$0: a modern shell, or manually run the script under such a +$0: shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + +SHELL=${CONFIG_SHELL-/bin/sh} + + +test -n "$DJDIR" || exec 7<&0 </dev/null +exec 6>&1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='sqlite' +PACKAGE_TARNAME='sqlite' +PACKAGE_VERSION='3.44.0' +PACKAGE_STRING='sqlite 3.44.0' +PACKAGE_BUGREPORT='http://www.sqlite.org' +PACKAGE_URL='' + +ac_unique_file="sqlite3.c" +# Factoring default headers for most tests. +ac_includes_default="\ +#include <stdio.h> +#ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif +#ifdef HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif +#ifdef STDC_HEADERS +# include <stdlib.h> +# include <stddef.h> +#else +# ifdef HAVE_STDLIB_H +# include <stdlib.h> +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include <memory.h> +# endif +# include <string.h> +#endif +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif +#ifdef HAVE_INTTYPES_H +# include <inttypes.h> +#endif +#ifdef HAVE_STDINT_H +# include <stdint.h> +#endif +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif" + +ac_subst_vars='am__EXEEXT_FALSE +am__EXEEXT_TRUE +LTLIBOBJS +LIBOBJS +SHELL_CFLAGS +EXTRA_SHELL_OBJ +READLINE_LIBS +BUILD_CFLAGS +CPP +LT_SYS_LIBRARY_PATH +OTOOL64 +OTOOL +LIPO +NMEDIT +DSYMUTIL +MANIFEST_TOOL +RANLIB +ac_ct_AR +AR +DLLTOOL +OBJDUMP +LN_S +NM +ac_ct_DUMPBIN +DUMPBIN +LD +FGREP +EGREP +GREP +SED +host_os +host_vendor +host_cpu +host +build_os +build_vendor +build_cpu +build +LIBTOOL +am__fastdepCC_FALSE +am__fastdepCC_TRUE +CCDEPMODE +am__nodep +AMDEPBACKSLASH +AMDEP_FALSE +AMDEP_TRUE +am__quote +am__include +DEPDIR +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +AM_BACKSLASH +AM_DEFAULT_VERBOSITY +AM_DEFAULT_V +AM_V +am__untar +am__tar +AMTAR +am__leading_dot +SET_MAKE +AWK +mkdir_p +MKDIR_P +INSTALL_STRIP_PROGRAM +STRIP +install_sh +MAKEINFO +AUTOHEADER +AUTOMAKE +AUTOCONF +ACLOCAL +VERSION +PACKAGE +CYGPATH_W +am__isrc +INSTALL_DATA +INSTALL_SCRIPT +INSTALL_PROGRAM +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +runstatedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_silent_rules +enable_largefile +enable_dependency_tracking +enable_shared +enable_static +with_pic +enable_fast_install +with_aix_soname +with_gnu_ld +with_sysroot +enable_libtool_lock +enable_editline +enable_readline +enable_threadsafe +enable_dynamic_extensions +enable_math +enable_fts4 +enable_fts3 +enable_fts5 +enable_rtree +enable_session +enable_debug +enable_static_shell +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +LT_SYS_LIBRARY_PATH +CPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir runstatedir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures sqlite 3.44.0 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/sqlite] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF + +Program names: + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM run sed PROGRAM on installed program names + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST cross-compile to build programs to run on HOST [BUILD] +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of sqlite 3.44.0:";; + esac + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-silent-rules less verbose build output (undo: "make V=1") + --disable-silent-rules verbose build output (undo: "make V=0") + --disable-largefile omit support for large files + --enable-dependency-tracking + do not reject slow dependency extractors + --disable-dependency-tracking + speeds up one-time build + --enable-shared[=PKGS] build shared libraries [default=yes] + --enable-static[=PKGS] build static libraries [default=yes] + --enable-fast-install[=PKGS] + optimize for fast installation [default=yes] + --disable-libtool-lock avoid locking (might break parallel builds) + --enable-editline use BSD libedit + --enable-readline use readline + --enable-threadsafe build a thread-safe library [default=yes] + --enable-dynamic-extensions + support loadable extensions [default=yes] + --enable-math SQL math functions [default=yes] + --enable-fts4 include fts4 support [default=yes] + --enable-fts3 include fts3 support [default=no] + --enable-fts5 include fts5 support [default=yes] + --enable-rtree include rtree support [default=yes] + --enable-session enable the session extension [default=no] + --enable-debug build with debugging features enabled [default=no] + --enable-static-shell statically link libsqlite3 into shell tool + [default=yes] + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use + both] + --with-aix-soname=aix|svr4|both + shared library versioning (aka "SONAME") variant to + provide on AIX, [default=aix]. + --with-gnu-ld assume the C compiler uses GNU ld [default=no] + --with-sysroot[=DIR] Search for dependent libraries within DIR (or the + compiler's sysroot if not specified). + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a + nonstandard directory <lib dir> + LIBS libraries to pass to the linker, e.g. -l<library> + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if + you have headers in a nonstandard directory <include dir> + LT_SYS_LIBRARY_PATH + User-defined run-time library search path. + CPP C preprocessor + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to <http://www.sqlite.org>. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +sqlite configure 3.44.0 +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case <limits.h> declares $2. + For example, HP-UX 11i <limits.h> declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. + Prefer <limits.h> to <assert.h> if __STDC__ is defined, since + <limits.h> exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include <limits.h> +#else +# include <assert.h> +#endif + +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main () +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func + +# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES +# --------------------------------------------- +# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR +# accordingly. +ac_fn_c_check_decl () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + as_decl_name=`echo $2|sed 's/ *(.*//'` + as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 +$as_echo_n "checking whether $as_decl_name is declared... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main () +{ +#ifndef $as_decl_name +#ifdef __cplusplus + (void) $as_decl_use; +#else + (void) $as_decl_name; +#endif +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_decl + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} +( $as_echo "## ------------------------------------ ## +## Report this to http://www.sqlite.org ## +## ------------------------------------ ##" + ) | sed "s/^/$as_me: WARNING: /" >&2 + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_mongrel +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by sqlite $as_me 3.44.0, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +ac_aux_dir= +for ac_dir in . "$srcdir"/.; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in . \"$srcdir\"/." "$LINENO" 5 +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + + +# Use automake. +am__api_version='1.15' + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if ${ac_cv_path_install+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + + done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5 +$as_echo_n "checking whether build environment is sane... " >&6; } +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[\\\"\#\$\&\'\`$am_lf]*) + as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;; +esac +case $srcdir in + *[\\\"\#\$\&\'\`$am_lf\ \ ]*) + as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;; +esac + +# Do 'set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + am_has_slept=no + for am_try in 1 2; do + echo "timestamp, slept: $am_has_slept" > conftest.file + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$*" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + if test "$*" != "X $srcdir/configure conftest.file" \ + && test "$*" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + as_fn_error $? "ls -t appears to fail. Make sure there is not a broken + alias in your environment" "$LINENO" 5 + fi + if test "$2" = conftest.file || test $am_try -eq 2; then + break + fi + # Just in case. + sleep 1 + am_has_slept=yes + done + test "$2" = conftest.file + ) +then + # Ok. + : +else + as_fn_error $? "newly created file is older than distributed files! +Check your system clock" "$LINENO" 5 +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +# If we didn't sleep, we still need to ensure time stamps of config.status and +# generated files are strictly newer. +am_sleep_pid= +if grep 'slept: no' conftest.file >/dev/null 2>&1; then + ( sleep 1 ) & + am_sleep_pid=$! +fi + +rm -f conftest.file + +test "$program_prefix" != NONE && + program_transform_name="s&^&$program_prefix&;$program_transform_name" +# Use a double $ so make ignores it. +test "$program_suffix" != NONE && + program_transform_name="s&\$&$program_suffix&;$program_transform_name" +# Double any \ or $. +# By default was `s,x,x', remove it if useless. +ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' +program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"` + +# Expand $ac_aux_dir to an absolute path. +am_aux_dir=`cd "$ac_aux_dir" && pwd` + +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --is-lightweight"; then + am_missing_run="$MISSING " +else + am_missing_run= + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5 +$as_echo "$as_me: WARNING: 'missing' script is too old or missing" >&2;} +fi + +if test x"${install_sh+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi + +# Installed binaries are usually stripped using 'strip' when the user +# run "make install-strip". However 'strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the 'STRIP' environment variable to overrule this program. +if test "$cross_compiling" != no; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5 +$as_echo_n "checking for a thread-safe mkdir -p... " >&6; } +if test -z "$MKDIR_P"; then + if ${ac_cv_path_mkdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in mkdir gmkdir; do + for ac_exec_ext in '' $ac_executable_extensions; do + as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue + case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #( + 'mkdir (GNU coreutils) '* | \ + 'mkdir (coreutils) '* | \ + 'mkdir (fileutils) '4.1*) + ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext + break 3;; + esac + done + done + done +IFS=$as_save_IFS + +fi + + test -d ./--version && rmdir ./--version + if test "${ac_cv_path_mkdir+set}" = set; then + MKDIR_P="$ac_cv_path_mkdir -p" + else + # As a last resort, use the slow shell script. Don't cache a + # value for MKDIR_P within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + MKDIR_P="$ac_install_sh -d" + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 +$as_echo "$MKDIR_P" >&6; } + +for ac_prog in gawk mawk nawk awk +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AWK+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AWK"; then + ac_cv_prog_AWK="$AWK" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AWK="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AWK=$ac_cv_prog_AWK +if test -n "$AWK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 +$as_echo "$AWK" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AWK" && break +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + +rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null + +# Check whether --enable-silent-rules was given. +if test "${enable_silent_rules+set}" = set; then : + enableval=$enable_silent_rules; +fi + +case $enable_silent_rules in # ((( + yes) AM_DEFAULT_VERBOSITY=0;; + no) AM_DEFAULT_VERBOSITY=1;; + *) AM_DEFAULT_VERBOSITY=1;; +esac +am_make=${MAKE-make} +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 +$as_echo_n "checking whether $am_make supports nested variables... " >&6; } +if ${am_cv_make_support_nested_variables+:} false; then : + $as_echo_n "(cached) " >&6 +else + if $as_echo 'TRUE=$(BAR$(V)) +BAR0=false +BAR1=true +V=1 +am__doit: + @$(TRUE) +.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then + am_cv_make_support_nested_variables=yes +else + am_cv_make_support_nested_variables=no +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 +$as_echo "$am_cv_make_support_nested_variables" >&6; } +if test $am_cv_make_support_nested_variables = yes; then + AM_V='$(V)' + AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' +else + AM_V=$AM_DEFAULT_VERBOSITY + AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY +fi +AM_BACKSLASH='\' + +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + am__isrc=' -I$(srcdir)' + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi + + +# Define the identity of the package. + PACKAGE='sqlite' + VERSION='3.44.0' + + +cat >>confdefs.h <<_ACEOF +#define PACKAGE "$PACKAGE" +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define VERSION "$VERSION" +_ACEOF + +# Some tools Automake needs. + +ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} + + +AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} + + +AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} + + +AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} + + +MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} + +# For better backward compatibility. To be removed once Automake 1.9.x +# dies out for good. For more background, see: +# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html> +# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html> +mkdir_p='$(MKDIR_P)' + +# We need awk for the "check" target (and possibly the TAP driver). The +# system "awk" is bad on some platforms. +# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AMTAR='$${TAR-tar}' + + +# We'll loop over all known methods to create a tar archive until one works. +_am_tools='gnutar pax cpio none' + +am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -' + + + + + + +# POSIX will say in a future version that running "rm -f" with no argument +# is OK; and we want to be able to make that assumption in our Makefile +# recipes. So use an aggressive probe to check that the usage we want is +# actually supported "in the wild" to an acceptable degree. +# See automake bug#10828. +# To make any issue more visible, cause the running configure to be aborted +# by default if the 'rm' program in use doesn't match our expectations; the +# user can still override this though. +if rm -f && rm -fr && rm -rf; then : OK; else + cat >&2 <<'END' +Oops! + +Your 'rm' program seems unable to run without file operands specified +on the command line, even when the '-f' option is present. This is contrary +to the behaviour of most rm programs out there, and not conforming with +the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542> + +Please tell bug-automake@gnu.org about your system, including the value +of your $PATH and any error possibly output before this message. This +can help us improve future automake versions. + +END + if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then + echo 'Configuration will proceed anyway, since you have set the' >&2 + echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 + echo >&2 + else + cat >&2 <<'END' +Aborting the configuration process, to ensure you take notice of the issue. + +You can download and install GNU coreutils to get an 'rm' implementation +that behaves properly: <http://www.gnu.org/software/coreutils/>. + +If you want to complete the configuration process using your problematic +'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM +to "yes", and re-run configure. + +END + as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5 + fi +fi + + +DEPDIR="${am__leading_dot}deps" + +ac_config_commands="$ac_config_commands depfiles" + + +am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo this is the am__doit target +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5 +$as_echo_n "checking for style of include used by $am_make... " >&6; } +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# Ignore all kinds of additional output from 'make'. +case `$am_make -s -f confmf 2> /dev/null` in #( +*the\ am__doit\ target*) + am__include=include + am__quote= + _am_result=GNU + ;; +esac +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + case `$am_make -s -f confmf 2> /dev/null` in #( + *the\ am__doit\ target*) + am__include=.include + am__quote="\"" + _am_result=BSD + ;; + esac +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5 +$as_echo "$_am_result" >&6; } +rm -f confinc confmf + +# Check whether --enable-dependency-tracking was given. +if test "${enable_dependency_tracking+set}" = set; then : + enableval=$enable_dependency_tracking; +fi + +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' + am__nodep='_no' +fi + if test "x$enable_dependency_tracking" != xno; then + AMDEP_TRUE= + AMDEP_FALSE='#' +else + AMDEP_TRUE='#' + AMDEP_FALSE= +fi + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdio.h> +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdarg.h> +#include <stdio.h> +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 +$as_echo_n "checking whether $CC understands -c and -o together... " >&6; } +if ${am_cv_prog_cc_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 + ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 +$as_echo "$am_cv_prog_cc_c_o" >&6; } +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +depcc="$CC" am_compiler_list= + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if ${am_cv_CC_dependencies_compiler_type+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CC_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + am__universal=false + case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CC_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CC_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } +CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then + am__fastdepCC_TRUE= + am__fastdepCC_FALSE='#' +else + am__fastdepCC_TRUE='#' + am__fastdepCC_FALSE= +fi + + + +# Check whether --enable-largefile was given. +if test "${enable_largefile+set}" = set; then : + enableval=$enable_largefile; +fi + +if test "$enable_largefile" != no; then + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for special C compiler options needed for large files" >&5 +$as_echo_n "checking for special C compiler options needed for large files... " >&6; } +if ${ac_cv_sys_largefile_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_sys_largefile_CC=no + if test "$GCC" != yes; then + ac_save_CC=$CC + while :; do + # IRIX 6.2 and later do not support large files by default, + # so use the C compiler's -n32 option if that helps. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/types.h> + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF + if ac_fn_c_try_compile "$LINENO"; then : + break +fi +rm -f core conftest.err conftest.$ac_objext + CC="$CC -n32" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_largefile_CC=' -n32'; break +fi +rm -f core conftest.err conftest.$ac_objext + break + done + CC=$ac_save_CC + rm -f conftest.$ac_ext + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_largefile_CC" >&5 +$as_echo "$ac_cv_sys_largefile_CC" >&6; } + if test "$ac_cv_sys_largefile_CC" != no; then + CC=$CC$ac_cv_sys_largefile_CC + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _FILE_OFFSET_BITS value needed for large files" >&5 +$as_echo_n "checking for _FILE_OFFSET_BITS value needed for large files... " >&6; } +if ${ac_cv_sys_file_offset_bits+:} false; then : + $as_echo_n "(cached) " >&6 +else + while :; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/types.h> + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_file_offset_bits=no; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _FILE_OFFSET_BITS 64 +#include <sys/types.h> + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_file_offset_bits=64; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cv_sys_file_offset_bits=unknown + break +done +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_file_offset_bits" >&5 +$as_echo "$ac_cv_sys_file_offset_bits" >&6; } +case $ac_cv_sys_file_offset_bits in #( + no | unknown) ;; + *) +cat >>confdefs.h <<_ACEOF +#define _FILE_OFFSET_BITS $ac_cv_sys_file_offset_bits +_ACEOF +;; +esac +rm -rf conftest* + if test $ac_cv_sys_file_offset_bits = unknown; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _LARGE_FILES value needed for large files" >&5 +$as_echo_n "checking for _LARGE_FILES value needed for large files... " >&6; } +if ${ac_cv_sys_large_files+:} false; then : + $as_echo_n "(cached) " >&6 +else + while :; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/types.h> + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_large_files=no; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _LARGE_FILES 1 +#include <sys/types.h> + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_large_files=1; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cv_sys_large_files=unknown + break +done +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_large_files" >&5 +$as_echo "$ac_cv_sys_large_files" >&6; } +case $ac_cv_sys_large_files in #( + no | unknown) ;; + *) +cat >>confdefs.h <<_ACEOF +#define _LARGE_FILES $ac_cv_sys_large_files +_ACEOF +;; +esac +rm -rf conftest* + fi + + +fi + + +# Check for required programs. +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdarg.h> +#include <stdio.h> +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 +$as_echo_n "checking whether $CC understands -c and -o together... " >&6; } +if ${am_cv_prog_cc_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 + ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 +$as_echo "$am_cv_prog_cc_c_o" >&6; } +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +depcc="$CC" am_compiler_list= + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if ${am_cv_CC_dependencies_compiler_type+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CC_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + am__universal=false + case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CC_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CC_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } +CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then + am__fastdepCC_TRUE= + am__fastdepCC_FALSE='#' +else + am__fastdepCC_TRUE='#' + am__fastdepCC_FALSE= +fi + + +case `pwd` in + *\ * | *\ *) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5 +$as_echo "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;; +esac + + + +macro_version='2.4.6' +macro_revision='2.4.6' + + + + + + + + + + + + + +ltmain=$ac_aux_dir/ltmain.sh + +# Make sure we can run config.sub. +$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || + as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 +$as_echo_n "checking build system type... " >&6; } +if ${ac_cv_build+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_build_alias=$build_alias +test "x$ac_build_alias" = x && + ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` +test "x$ac_build_alias" = x && + as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 +ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 +$as_echo "$ac_cv_build" >&6; } +case $ac_cv_build in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; +esac +build=$ac_cv_build +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_build +shift +build_cpu=$1 +build_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +build_os=$* +IFS=$ac_save_IFS +case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 +$as_echo_n "checking host system type... " >&6; } +if ${ac_cv_host+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$host_alias" = x; then + ac_cv_host=$ac_cv_build +else + ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 +$as_echo "$ac_cv_host" >&6; } +case $ac_cv_host in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; +esac +host=$ac_cv_host +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_host +shift +host_cpu=$1 +host_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +host_os=$* +IFS=$ac_save_IFS +case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac + + +# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\(["`$\\]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\(["`\\]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' + +ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5 +$as_echo_n "checking how to print strings... " >&6; } +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "" +} + +case $ECHO in + printf*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: printf" >&5 +$as_echo "printf" >&6; } ;; + print*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: print -r" >&5 +$as_echo "print -r" >&6; } ;; + *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: cat" >&5 +$as_echo "cat" >&6; } ;; +esac + + + + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 +$as_echo_n "checking for a sed that does not truncate output... " >&6; } +if ${ac_cv_path_SED+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for ac_i in 1 2 3 4 5 6 7; do + ac_script="$ac_script$as_nl$ac_script" + done + echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed + { ac_script=; unset ac_script;} + if test -z "$SED"; then + ac_path_SED_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_SED="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_SED" || continue +# Check for GNU ac_path_SED and select it if it is found. + # Check for GNU $ac_path_SED +case `"$ac_path_SED" --version 2>&1` in +*GNU*) + ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo '' >> "conftest.nl" + "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_SED_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_SED="$ac_path_SED" + ac_path_SED_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_SED_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_SED"; then + as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 + fi +else + ac_cv_path_SED=$SED +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 +$as_echo "$ac_cv_path_SED" >&6; } + SED="$ac_cv_path_SED" + rm -f conftest.sed + +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5 +$as_echo_n "checking for fgrep... " >&6; } +if ${ac_cv_path_FGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1 + then ac_cv_path_FGREP="$GREP -F" + else + if test -z "$FGREP"; then + ac_path_FGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in fgrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_FGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_FGREP" || continue +# Check for GNU ac_path_FGREP and select it if it is found. + # Check for GNU $ac_path_FGREP +case `"$ac_path_FGREP" --version 2>&1` in +*GNU*) + ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'FGREP' >> "conftest.nl" + "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_FGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_FGREP="$ac_path_FGREP" + ac_path_FGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_FGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_FGREP"; then + as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_FGREP=$FGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5 +$as_echo "$ac_cv_path_FGREP" >&6; } + FGREP="$ac_cv_path_FGREP" + + +test -z "$GREP" && GREP=grep + + + + + + + + + + + + + + + + + + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then : + withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if ${lt_cv_path_LD+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in + *GNU* | *'with BFD'*) + test no != "$with_gnu_ld" && break + ;; + *) + test yes != "$with_gnu_ld" && break + ;; + esac + fi + done + IFS=$lt_save_ifs +else + lt_cv_path_LD=$LD # Let the user override the test with a path. +fi +fi + +LD=$lt_cv_path_LD +if test -n "$LD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LD" >&5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if ${lt_cv_prog_gnu_ld+:} false; then : + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 </dev/null` in +*GNU* | *'with BFD'*) + lt_cv_prog_gnu_ld=yes + ;; +*) + lt_cv_prog_gnu_ld=no + ;; +esac +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_gnu_ld" >&5 +$as_echo "$lt_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$lt_cv_prog_gnu_ld + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5 +$as_echo_n "checking for BSD- or MS-compatible name lister (nm)... " >&6; } +if ${lt_cv_path_NM+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM=$NM +else + lt_nm_to_check=${ac_tool_prefix}nm + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + tmp_nm=$ac_dir/$lt_tmp_nm + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the 'sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty + case $build_os in + mingw*) lt_bad_file=conftest.nm/nofile ;; + *) lt_bad_file=/dev/null ;; + esac + case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in + *$lt_bad_file* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break 2 + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break 2 + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS=$lt_save_ifs + done + : ${lt_cv_path_NM=no} +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5 +$as_echo "$lt_cv_path_NM" >&6; } +if test no != "$lt_cv_path_NM"; then + NM=$lt_cv_path_NM +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + if test -n "$ac_tool_prefix"; then + for ac_prog in dumpbin "link -dump" + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DUMPBIN+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DUMPBIN"; then + ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DUMPBIN=$ac_cv_prog_DUMPBIN +if test -n "$DUMPBIN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5 +$as_echo "$DUMPBIN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$DUMPBIN" && break + done +fi +if test -z "$DUMPBIN"; then + ac_ct_DUMPBIN=$DUMPBIN + for ac_prog in dumpbin "link -dump" +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DUMPBIN+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DUMPBIN"; then + ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DUMPBIN="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN +if test -n "$ac_ct_DUMPBIN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5 +$as_echo "$ac_ct_DUMPBIN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_DUMPBIN" && break +done + + if test "x$ac_ct_DUMPBIN" = x; then + DUMPBIN=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DUMPBIN=$ac_ct_DUMPBIN + fi +fi + + case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols -headers" + ;; + *) + DUMPBIN=: + ;; + esac + fi + + if test : != "$DUMPBIN"; then + NM=$DUMPBIN + fi +fi +test -z "$NM" && NM=nm + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5 +$as_echo_n "checking the name lister ($NM) interface... " >&6; } +if ${lt_cv_nm_interface+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&5 + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&5 + (eval echo "\"\$as_me:$LINENO: output\"" >&5) + cat conftest.out >&5 + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5 +$as_echo "$lt_cv_nm_interface" >&6; } + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 +$as_echo_n "checking whether ln -s works... " >&6; } +LN_S=$as_ln_s +if test "$LN_S" = "ln -s"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 +$as_echo "no, using $LN_S" >&6; } +fi + +# find the maximum length of command line arguments +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5 +$as_echo_n "checking the maximum length of command line arguments... " >&6; } +if ${lt_cv_sys_max_cmd_len+:} false; then : + $as_echo_n "(cached) " >&6 +else + i=0 + teststring=ABCD + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[ ]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len" && \ + test undefined != "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test X`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test 17 != "$i" # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac + +fi + +if test -n "$lt_cv_sys_max_cmd_len"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5 +$as_echo "$lt_cv_sys_max_cmd_len" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 +$as_echo "none" >&6; } +fi +max_cmd_len=$lt_cv_sys_max_cmd_len + + + + + + +: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} + +if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi + + + + + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5 +$as_echo_n "checking how to convert $build file names to $host format... " >&6; } +if ${lt_cv_to_host_file_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac + +fi + +to_host_file_cmd=$lt_cv_to_host_file_cmd +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5 +$as_echo "$lt_cv_to_host_file_cmd" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5 +$as_echo_n "checking how to convert $build file names to toolchain format... " >&6; } +if ${lt_cv_to_tool_file_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + #assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac + +fi + +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5 +$as_echo "$lt_cv_to_tool_file_cmd" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5 +$as_echo_n "checking for $LD option to reload object files... " >&6; } +if ${lt_cv_ld_reload_flag+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_reload_flag='-r' +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5 +$as_echo "$lt_cv_ld_reload_flag" >&6; } +reload_flag=$lt_cv_ld_reload_flag +case $reload_flag in +"" | " "*) ;; +*) reload_flag=" $reload_flag" ;; +esac +reload_cmds='$LD$reload_flag -o $output$reload_objs' +case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + if test yes != "$GCC"; then + reload_cmds=false + fi + ;; + darwin*) + if test yes = "$GCC"; then + reload_cmds='$LTCC $LTCFLAGS -nostdlib $wl-r -o $output$reload_objs' + else + reload_cmds='$LD$reload_flag -o $output$reload_objs' + fi + ;; +esac + + + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args. +set dummy ${ac_tool_prefix}objdump; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OBJDUMP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OBJDUMP"; then + ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OBJDUMP=$ac_cv_prog_OBJDUMP +if test -n "$OBJDUMP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5 +$as_echo "$OBJDUMP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OBJDUMP"; then + ac_ct_OBJDUMP=$OBJDUMP + # Extract the first word of "objdump", so it can be a program name with args. +set dummy objdump; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OBJDUMP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OBJDUMP"; then + ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OBJDUMP="objdump" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP +if test -n "$ac_ct_OBJDUMP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5 +$as_echo "$ac_ct_OBJDUMP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OBJDUMP" = x; then + OBJDUMP="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OBJDUMP=$ac_ct_OBJDUMP + fi +else + OBJDUMP="$ac_cv_prog_OBJDUMP" +fi + +test -z "$OBJDUMP" && OBJDUMP=objdump + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5 +$as_echo_n "checking how to recognize dependent libraries... " >&6; } +if ${lt_cv_deplibs_check_method+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# 'unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# that responds to the $file_magic_cmd with a given extended regex. +# If you have 'file' or equivalent on your system and you're not sure +# whether 'pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[4-9]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[45]*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]' + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[3-9]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd* | bitrig*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +os2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5 +$as_echo "$lt_cv_deplibs_check_method" >&6; } + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + + + + + + + + + + + + + + + + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args. +set dummy ${ac_tool_prefix}dlltool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DLLTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DLLTOOL"; then + ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DLLTOOL=$ac_cv_prog_DLLTOOL +if test -n "$DLLTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5 +$as_echo "$DLLTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DLLTOOL"; then + ac_ct_DLLTOOL=$DLLTOOL + # Extract the first word of "dlltool", so it can be a program name with args. +set dummy dlltool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DLLTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DLLTOOL"; then + ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DLLTOOL="dlltool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL +if test -n "$ac_ct_DLLTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5 +$as_echo "$ac_ct_DLLTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DLLTOOL" = x; then + DLLTOOL="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DLLTOOL=$ac_ct_DLLTOOL + fi +else + DLLTOOL="$ac_cv_prog_DLLTOOL" +fi + +test -z "$DLLTOOL" && DLLTOOL=dlltool + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5 +$as_echo_n "checking how to associate runtime and link libraries... " >&6; } +if ${lt_cv_sharedlib_from_linklib_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh; + # decide which one to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd=$ECHO + ;; +esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5 +$as_echo "$lt_cv_sharedlib_from_linklib_cmd" >&6; } +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + + + + + + + +if test -n "$ac_tool_prefix"; then + for ac_prog in ar + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AR"; then + ac_cv_prog_AR="$AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AR="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AR=$ac_cv_prog_AR +if test -n "$AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 +$as_echo "$AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AR" && break + done +fi +if test -z "$AR"; then + ac_ct_AR=$AR + for ac_prog in ar +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_AR"; then + ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_AR="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_AR=$ac_cv_prog_ac_ct_AR +if test -n "$ac_ct_AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 +$as_echo "$ac_ct_AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_AR" && break +done + + if test "x$ac_ct_AR" = x; then + AR="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + AR=$ac_ct_AR + fi +fi + +: ${AR=ar} +: ${AR_FLAGS=cru} + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5 +$as_echo_n "checking for archiver @FILE support... " >&6; } +if ${lt_cv_ar_at_file+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ar_at_file=no + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 + (eval $lt_ar_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if test 0 -eq "$ac_status"; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 + (eval $lt_ar_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if test 0 -ne "$ac_status"; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5 +$as_echo "$lt_cv_ar_at_file" >&6; } + +if test no = "$lt_cv_ar_at_file"; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +test -z "$STRIP" && STRIP=: + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + +test -z "$RANLIB" && RANLIB=: + + + + + + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + bitrig* | openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + +# Check for command to grab the raw symbol name followed by C symbol from nm. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5 +$as_echo_n "checking command to parse $NM output from $compiler object... " >&6; } +if ${lt_cv_sys_global_symbol_pipe+:} false; then : + $as_echo_n "(cached) " >&6 +else + +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[BCDEGRST]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([_A-Za-z][_A-Za-z0-9]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[BCDT]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[ABCDGISTW]' + ;; +hpux*) + if test ia64 = "$host_cpu"; then + symcode='[ABCDEGRST]' + fi + ;; +irix* | nonstopux*) + symcode='[BCDEGRST]' + ;; +osf*) + symcode='[BCDEGQRST]' + ;; +solaris*) + symcode='[BDRT]' + ;; +sco3.2v5*) + symcode='[DT]' + ;; +sysv4.2uw2*) + symcode='[DT]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[ABDT]' + ;; +sysv4) + symcode='[DFNSTU]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[ABCDGIRSTW]' ;; +esac + +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Gets list of data symbols to import. + lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" + # Adjust the below global symbol transforms to fixup imported variables. + lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" + lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" + lt_c_name_lib_hook="\ + -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ + -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" +else + # Disable hooks by default. + lt_cv_sys_global_symbol_to_import= + lt_cdecl_hook= + lt_c_name_hook= + lt_c_name_lib_hook= +fi + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n"\ +$lt_cdecl_hook\ +" -e 's/^T .* \(.*\)$/extern int \1();/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ +$lt_c_name_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" + +# Transform an extracted symbol line into symbol name with lib prefix and +# symbol address. +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ +$lt_c_name_lib_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function, + # D for any global variable and I for any imported variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK '"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ +" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ +" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ +" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ +" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + # Now try to grab the symbols. + nlist=conftest.nm + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist\""; } >&5 + (eval $NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT_DLSYM_CONST +#else +# define LT_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS=conftstm.$ac_objext + CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag" + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s conftest$ac_exeext; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&5 + fi + else + echo "cannot find nm_test_var in $nlist" >&5 + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5 + fi + else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test yes = "$pipe_works"; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done + +fi + +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5 +$as_echo "failed" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok" >&5 +$as_echo "ok" >&6; } +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5 +$as_echo_n "checking for sysroot... " >&6; } + +# Check whether --with-sysroot was given. +if test "${with_sysroot+set}" = set; then : + withval=$with_sysroot; +else + with_sysroot=no +fi + + +lt_sysroot= +case $with_sysroot in #( + yes) + if test yes = "$GCC"; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_sysroot" >&5 +$as_echo "$with_sysroot" >&6; } + as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5 + ;; +esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5 +$as_echo "${lt_sysroot:-no}" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a working dd" >&5 +$as_echo_n "checking for a working dd... " >&6; } +if ${ac_cv_path_lt_DD+:} false; then : + $as_echo_n "(cached) " >&6 +else + printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +: ${lt_DD:=$DD} +if test -z "$lt_DD"; then + ac_path_lt_DD_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in dd; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_lt_DD="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_lt_DD" || continue +if "$ac_path_lt_DD" bs=32 count=1 <conftest2.i >conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: +fi + $ac_path_lt_DD_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_lt_DD"; then + : + fi +else + ac_cv_path_lt_DD=$lt_DD +fi + +rm -f conftest.i conftest2.i conftest.out +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_lt_DD" >&5 +$as_echo "$ac_cv_path_lt_DD" >&6; } + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to truncate binary pipes" >&5 +$as_echo_n "checking how to truncate binary pipes... " >&6; } +if ${lt_cv_truncate_bin+:} false; then : + $as_echo_n "(cached) " >&6 +else + printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +lt_cv_truncate_bin= +if "$ac_cv_path_lt_DD" bs=32 count=1 <conftest2.i >conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" +fi +rm -f conftest.i conftest2.i conftest.out +test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q" +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_truncate_bin" >&5 +$as_echo "$lt_cv_truncate_bin" >&6; } + + + + + + + +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in $*""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} + +# Check whether --enable-libtool-lock was given. +if test "${enable_libtool_lock+set}" = set; then : + enableval=$enable_libtool_lock; +fi + +test no = "$enable_libtool_lock" || enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out what ABI is being produced by ac_compile, and set mode + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE=32 + ;; + *ELF-64*) + HPUX_IA64_MODE=64 + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '#line '$LINENO' "configure"' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + if test yes = "$lt_cv_prog_gnu_ld"; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +mips64*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '#line '$LINENO' "configure"' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + emul=elf + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + emul="${emul}32" + ;; + *64-bit*) + emul="${emul}64" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *MSB*) + emul="${emul}btsmip" + ;; + *LSB*) + emul="${emul}ltsmip" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *N32*) + emul="${emul}n32" + ;; + esac + LD="${LD-ld} -m $emul" + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. Note that the listed cases only cover the + # situations where additional linker options are needed (such as when + # doing 32-bit compilation for a host where ld defaults to 64-bit, or + # vice versa); the common cases where no linker options are needed do + # not appear in the list. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + case `/usr/bin/file conftest.o` in + *x86-64*) + LD="${LD-ld} -m elf32_x86_64" + ;; + *) + LD="${LD-ld} -m elf_i386" + ;; + esac + ;; + powerpc64le-*linux*) + LD="${LD-ld} -m elf32lppclinux" + ;; + powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + powerpcle-*linux*) + LD="${LD-ld} -m elf64lppc" + ;; + powerpc-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -belf" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5 +$as_echo_n "checking whether the C compiler needs -belf... " >&6; } +if ${lt_cv_cc_needs_belf+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_cc_needs_belf=yes +else + lt_cv_cc_needs_belf=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5 +$as_echo "$lt_cv_cc_needs_belf" >&6; } + if test yes != "$lt_cv_cc_needs_belf"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS=$SAVE_CFLAGS + fi + ;; +*-*solaris*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*|x86_64-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD=${LD-ld}_sol2 + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks=$enable_libtool_lock + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args. +set dummy ${ac_tool_prefix}mt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_MANIFEST_TOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$MANIFEST_TOOL"; then + ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL +if test -n "$MANIFEST_TOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5 +$as_echo "$MANIFEST_TOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_MANIFEST_TOOL"; then + ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL + # Extract the first word of "mt", so it can be a program name with args. +set dummy mt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_MANIFEST_TOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_MANIFEST_TOOL"; then + ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_MANIFEST_TOOL="mt" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL +if test -n "$ac_ct_MANIFEST_TOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5 +$as_echo "$ac_ct_MANIFEST_TOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_MANIFEST_TOOL" = x; then + MANIFEST_TOOL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL + fi +else + MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL" +fi + +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5 +$as_echo_n "checking if $MANIFEST_TOOL is a manifest tool... " >&6; } +if ${lt_cv_path_mainfest_tool+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5 + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&5 + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_mainfest_tool" >&5 +$as_echo "$lt_cv_path_mainfest_tool" >&6; } +if test yes != "$lt_cv_path_mainfest_tool"; then + MANIFEST_TOOL=: +fi + + + + + + + case $host_os in + rhapsody* | darwin*) + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args. +set dummy ${ac_tool_prefix}dsymutil; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DSYMUTIL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DSYMUTIL"; then + ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DSYMUTIL=$ac_cv_prog_DSYMUTIL +if test -n "$DSYMUTIL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5 +$as_echo "$DSYMUTIL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DSYMUTIL"; then + ac_ct_DSYMUTIL=$DSYMUTIL + # Extract the first word of "dsymutil", so it can be a program name with args. +set dummy dsymutil; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DSYMUTIL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DSYMUTIL"; then + ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DSYMUTIL="dsymutil" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL +if test -n "$ac_ct_DSYMUTIL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5 +$as_echo "$ac_ct_DSYMUTIL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DSYMUTIL" = x; then + DSYMUTIL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DSYMUTIL=$ac_ct_DSYMUTIL + fi +else + DSYMUTIL="$ac_cv_prog_DSYMUTIL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args. +set dummy ${ac_tool_prefix}nmedit; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_NMEDIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$NMEDIT"; then + ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +NMEDIT=$ac_cv_prog_NMEDIT +if test -n "$NMEDIT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5 +$as_echo "$NMEDIT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_NMEDIT"; then + ac_ct_NMEDIT=$NMEDIT + # Extract the first word of "nmedit", so it can be a program name with args. +set dummy nmedit; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_NMEDIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_NMEDIT"; then + ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_NMEDIT="nmedit" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT +if test -n "$ac_ct_NMEDIT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5 +$as_echo "$ac_ct_NMEDIT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_NMEDIT" = x; then + NMEDIT=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + NMEDIT=$ac_ct_NMEDIT + fi +else + NMEDIT="$ac_cv_prog_NMEDIT" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args. +set dummy ${ac_tool_prefix}lipo; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_LIPO+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$LIPO"; then + ac_cv_prog_LIPO="$LIPO" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_LIPO="${ac_tool_prefix}lipo" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +LIPO=$ac_cv_prog_LIPO +if test -n "$LIPO"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5 +$as_echo "$LIPO" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_LIPO"; then + ac_ct_LIPO=$LIPO + # Extract the first word of "lipo", so it can be a program name with args. +set dummy lipo; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_LIPO+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_LIPO"; then + ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_LIPO="lipo" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO +if test -n "$ac_ct_LIPO"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5 +$as_echo "$ac_ct_LIPO" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_LIPO" = x; then + LIPO=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + LIPO=$ac_ct_LIPO + fi +else + LIPO="$ac_cv_prog_LIPO" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args. +set dummy ${ac_tool_prefix}otool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OTOOL"; then + ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OTOOL="${ac_tool_prefix}otool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OTOOL=$ac_cv_prog_OTOOL +if test -n "$OTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5 +$as_echo "$OTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OTOOL"; then + ac_ct_OTOOL=$OTOOL + # Extract the first word of "otool", so it can be a program name with args. +set dummy otool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OTOOL"; then + ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OTOOL="otool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL +if test -n "$ac_ct_OTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5 +$as_echo "$ac_ct_OTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OTOOL" = x; then + OTOOL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OTOOL=$ac_ct_OTOOL + fi +else + OTOOL="$ac_cv_prog_OTOOL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args. +set dummy ${ac_tool_prefix}otool64; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OTOOL64+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OTOOL64"; then + ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OTOOL64=$ac_cv_prog_OTOOL64 +if test -n "$OTOOL64"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5 +$as_echo "$OTOOL64" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OTOOL64"; then + ac_ct_OTOOL64=$OTOOL64 + # Extract the first word of "otool64", so it can be a program name with args. +set dummy otool64; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OTOOL64+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OTOOL64"; then + ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OTOOL64="otool64" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64 +if test -n "$ac_ct_OTOOL64"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5 +$as_echo "$ac_ct_OTOOL64" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OTOOL64" = x; then + OTOOL64=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OTOOL64=$ac_ct_OTOOL64 + fi +else + OTOOL64="$ac_cv_prog_OTOOL64" +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5 +$as_echo_n "checking for -single_module linker flag... " >&6; } +if ${lt_cv_apple_cc_single_mod+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_apple_cc_single_mod=no + if test -z "$LT_MULTI_MODULE"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&5 + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&5 + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test 0 = "$_lt_result"; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&5 + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5 +$as_echo "$lt_cv_apple_cc_single_mod" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5 +$as_echo_n "checking for -exported_symbols_list linker flag... " >&6; } +if ${lt_cv_ld_exported_symbols_list+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_ld_exported_symbols_list=yes +else + lt_cv_ld_exported_symbols_list=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5 +$as_echo "$lt_cv_ld_exported_symbols_list" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5 +$as_echo_n "checking for -force_load linker flag... " >&6; } +if ${lt_cv_ld_force_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5 + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5 + echo "$AR cru libconftest.a conftest.o" >&5 + $AR cru libconftest.a conftest.o 2>&5 + echo "$RANLIB libconftest.a" >&5 + $RANLIB libconftest.a 2>&5 + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5 + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&5 + elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&5 + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5 +$as_echo "$lt_cv_ld_force_load" >&6; } + case $host_os in + rhapsody* | darwin1.[012]) + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[91]*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + 10.[012][,.]*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test yes = "$lt_cv_apple_cc_single_mod"; then + _lt_dar_single_mod='$single_module' + fi + if test yes = "$lt_cv_ld_exported_symbols_list"; then + _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' + fi + if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac + +# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x$2 in + x) + ;; + *:) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" + ;; + x:*) + eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" + ;; + *) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" + ;; + esac +} + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since + # <limits.h> exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include <limits.h> +#else +# include <assert.h> +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <ac_nonexistent.h> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since + # <limits.h> exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include <limits.h> +#else +# include <assert.h> +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <ac_nonexistent.h> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <float.h> + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <string.h> + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdlib.h> + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <ctype.h> +#include <stdlib.h> +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +for ac_header in dlfcn.h +do : + ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default +" +if test "x$ac_cv_header_dlfcn_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_DLFCN_H 1 +_ACEOF + +fi + +done + + + + + +# Set options + + + + enable_dlopen=no + + + enable_win32_dll=no + + + # Check whether --enable-shared was given. +if test "${enable_shared+set}" = set; then : + enableval=$enable_shared; p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_shared=yes +fi + + + + + + + + + + # Check whether --enable-static was given. +if test "${enable_static+set}" = set; then : + enableval=$enable_static; p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_static=yes +fi + + + + + + + + + + +# Check whether --with-pic was given. +if test "${with_pic+set}" = set; then : + withval=$with_pic; lt_p=${PACKAGE-default} + case $withval in + yes|no) pic_mode=$withval ;; + *) + pic_mode=default + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for lt_pkg in $withval; do + IFS=$lt_save_ifs + if test "X$lt_pkg" = "X$lt_p"; then + pic_mode=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + pic_mode=default +fi + + + + + + + + + # Check whether --enable-fast-install was given. +if test "${enable_fast_install+set}" = set; then : + enableval=$enable_fast_install; p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_fast_install=yes +fi + + + + + + + + + shared_archive_member_spec= +case $host,$enable_shared in +power*-*-aix[5-9]*,yes) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking which variant of shared library versioning to provide" >&5 +$as_echo_n "checking which variant of shared library versioning to provide... " >&6; } + +# Check whether --with-aix-soname was given. +if test "${with_aix_soname+set}" = set; then : + withval=$with_aix_soname; case $withval in + aix|svr4|both) + ;; + *) + as_fn_error $? "Unknown argument to --with-aix-soname" "$LINENO" 5 + ;; + esac + lt_cv_with_aix_soname=$with_aix_soname +else + if ${lt_cv_with_aix_soname+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_with_aix_soname=aix +fi + + with_aix_soname=$lt_cv_with_aix_soname +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_aix_soname" >&5 +$as_echo "$with_aix_soname" >&6; } + if test aix != "$with_aix_soname"; then + # For the AIX way of multilib, we name the shared archive member + # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', + # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. + # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, + # the AIX toolchain works better with OBJECT_MODE set (default 32). + if test 64 = "${OBJECT_MODE-32}"; then + shared_archive_member_spec=shr_64 + else + shared_archive_member_spec=shr + fi + fi + ;; +*) + with_aix_soname=aix + ;; +esac + + + + + + + + + + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS=$ltmain + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +test -z "$LN_S" && LN_S="ln -s" + + + + + + + + + + + + + + +if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5 +$as_echo_n "checking for objdir... " >&6; } +if ${lt_cv_objdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5 +$as_echo "$lt_cv_objdir" >&6; } +objdir=$lt_cv_objdir + + + + + +cat >>confdefs.h <<_ACEOF +#define LT_OBJDIR "$lt_cv_objdir/" +_ACEOF + + + + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld=$lt_cv_prog_gnu_ld + +old_CC=$CC +old_CFLAGS=$CFLAGS + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +func_cc_basename $compiler +cc_basename=$func_cc_basename_result + + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5 +$as_echo_n "checking for ${ac_tool_prefix}file... " >&6; } +if ${lt_cv_path_MAGIC_CMD+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/${ac_tool_prefix}file"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"${ac_tool_prefix}file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac +fi + +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + + +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for file" >&5 +$as_echo_n "checking for file... " >&6; } +if ${lt_cv_path_MAGIC_CMD+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/file"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac +fi + +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + else + MAGIC_CMD=: + fi +fi + + fi + ;; +esac + +# Use C for the default configuration in the libtool script + +lt_save_CC=$CC +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +objext=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* + +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* + + +if test -n "$compiler"; then + +lt_prog_compiler_no_builtin_flag= + +if test yes = "$GCC"; then + case $cc_basename in + nvcc*) + lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;; + *) + lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;; + esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 +$as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; } +if ${lt_cv_prog_compiler_rtti_exceptions+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_rtti_exceptions=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="-fno-rtti -fno-exceptions" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_rtti_exceptions=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5 +$as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; } + +if test yes = "$lt_cv_prog_compiler_rtti_exceptions"; then + lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions" +else + : +fi + +fi + + + + + + + lt_prog_compiler_wl= +lt_prog_compiler_pic= +lt_prog_compiler_static= + + + if test yes = "$GCC"; then + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_static='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + fi + lt_prog_compiler_pic='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + lt_prog_compiler_pic='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic='-DDLL_EXPORT' + case $host_os in + os2*) + lt_prog_compiler_static='$wl-static' + ;; + esac + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + lt_prog_compiler_static= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + ;; + + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + lt_prog_compiler_can_build_shared=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic=-Kconform_pic + fi + ;; + + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + lt_prog_compiler_wl='-Xlinker ' + if test -n "$lt_prog_compiler_pic"; then + lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + lt_prog_compiler_wl='-Wl,' + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + else + lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic='-fno-common' + case $cc_basename in + nagfor*) + # NAG Fortran compiler + lt_prog_compiler_wl='-Wl,-Wl,,' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + lt_prog_compiler_pic='-DDLL_EXPORT' + case $host_os in + os2*) + lt_prog_compiler_static='$wl-static' + ;; + esac + ;; + + hpux9* | hpux10* | hpux11*) + lt_prog_compiler_wl='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + lt_prog_compiler_static='$wl-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + lt_prog_compiler_wl='-Wl,' + # PIC (with -KPIC) is the default. + lt_prog_compiler_static='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + # old Intel for x86_64, which still supported -KPIC. + ecc*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='--shared' + lt_prog_compiler_static='--static' + ;; + nagfor*) + # NAG Fortran compiler + lt_prog_compiler_wl='-Wl,-Wl,,' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + ccc*) + lt_prog_compiler_wl='-Wl,' + # All Alpha code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-qpic' + lt_prog_compiler_static='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='' + ;; + *Sun\ F* | *Sun*Fortran*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Wl,' + ;; + *Intel*\ [CF]*Compiler*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + *Portland\ Group*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + lt_prog_compiler_wl='-Wl,' + # All OSF/1 code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + + rdos*) + lt_prog_compiler_static='-non_shared' + ;; + + solaris*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + lt_prog_compiler_wl='-Qoption ld ';; + *) + lt_prog_compiler_wl='-Wl,';; + esac + ;; + + sunos4*) + lt_prog_compiler_wl='-Qoption ld ' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic='-Kconform_pic' + lt_prog_compiler_static='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + unicos*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_can_build_shared=no + ;; + + uts4*) + lt_prog_compiler_pic='-pic' + lt_prog_compiler_static='-Bstatic' + ;; + + *) + lt_prog_compiler_can_build_shared=no + ;; + esac + fi + +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic= + ;; + *) + lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } +if ${lt_cv_prog_compiler_pic+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic=$lt_prog_compiler_pic +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5 +$as_echo "$lt_cv_prog_compiler_pic" >&6; } +lt_prog_compiler_pic=$lt_cv_prog_compiler_pic + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } +if ${lt_cv_prog_compiler_pic_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic -DPIC" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works" >&6; } + +if test yes = "$lt_cv_prog_compiler_pic_works"; then + case $lt_prog_compiler_pic in + "" | " "*) ;; + *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; + esac +else + lt_prog_compiler_pic= + lt_prog_compiler_can_build_shared=no +fi + +fi + + + + + + + + + + + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if ${lt_cv_prog_compiler_static_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works=yes + fi + else + lt_cv_prog_compiler_static_works=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5 +$as_echo "$lt_cv_prog_compiler_static_works" >&6; } + +if test yes = "$lt_cv_prog_compiler_static_works"; then + : +else + lt_prog_compiler_static= +fi + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + + + +hard_links=nottested +if test no = "$lt_cv_prog_compiler_c_o" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test no = "$hard_links"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + runpath_var= + allow_undefined_flag= + always_export_symbols=no + archive_cmds= + archive_expsym_cmds= + compiler_needs_object=no + enable_shared_with_static_runtimes=no + export_dynamic_flag_spec= + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + hardcode_automatic=no + hardcode_direct=no + hardcode_direct_absolute=no + hardcode_libdir_flag_spec= + hardcode_libdir_separator= + hardcode_minus_L=no + hardcode_shlibpath_var=unsupported + inherit_rpath=no + link_all_deplibs=unknown + module_cmds= + module_expsym_cmds= + old_archive_from_new_cmds= + old_archive_from_expsyms_cmds= + thread_safe_flag_spec= + whole_archive_flag_spec= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + include_expsyms= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ' (' and ')$', so one must not match beginning or + # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', + # as well as any symbol that contains 'd'. + exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test yes != "$GCC"; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd* | bitrig*) + with_gnu_ld=no + ;; + linux* | k*bsd*-gnu | gnu*) + link_all_deplibs=no + ;; + esac + + ld_shlibs=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test yes = "$with_gnu_ld"; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;; + *\ \(GNU\ Binutils\)\ [3-9]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test yes = "$lt_use_gnu_ld_interface"; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='$wl' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + export_dynamic_flag_spec='$wl--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + whole_archive_flag_spec= + fi + supports_anon_versioning=no + case `$LD -v | $SED -e 's/(^)\+)\s\+//' 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test ia64 != "$host_cpu"; then + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='' + ;; + m68k) + archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag=unsupported + # Joseph Beckenbach <jrb3@best.com> says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + ld_shlibs=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + export_dynamic_flag_spec='$wl--export-all-symbols' + allow_undefined_flag=unsupported + always_export_symbols=no + enable_shared_with_static_runtimes=yes + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs=no + fi + ;; + + haiku*) + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + link_all_deplibs=yes + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + shrext_cmds=.dll + archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + enable_shared_with_static_runtimes=yes + ;; + + interix[3-9]*) + hardcode_direct=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + export_dynamic_flag_spec='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test linux-dietlibc = "$host_os"; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test no = "$tmp_diet" + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + whole_archive_flag_spec= + tmp_sharedflag='--shared' ;; + nagfor*) # NAGFOR 5.3 + tmp_sharedflag='-Wl,-shared' ;; + xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + compiler_needs_object=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + whole_archive_flag_spec='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + compiler_needs_object=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + + if test yes = "$supports_anon_versioning"; then + archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + tcc*) + export_dynamic_flag_spec='-rdynamic' + ;; + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test yes = "$supports_anon_versioning"; then + archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + ld_shlibs=no + fi + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + ;; + + sunos4*) + archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + + if test no = "$ld_shlibs"; then + runpath_var= + hardcode_libdir_flag_spec= + export_dynamic_flag_spec= + whole_archive_flag_spec= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + allow_undefined_flag=unsupported + always_export_symbols=yes + archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + + aix[4-9]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then + aix_use_runtimelinking=yes + break + fi + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds='' + hardcode_direct=yes + hardcode_direct_absolute=yes + hardcode_libdir_separator=':' + link_all_deplibs=yes + file_list_spec='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # traditional, no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + hardcode_direct=no + hardcode_direct_absolute=no + ;; + esac + + if test yes = "$GCC"; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + ;; + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag="$shared_flag "'$wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + export_dynamic_flag_spec='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + always_export_symbols=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + allow_undefined_flag='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath_+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath_ +fi + + hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" + archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + hardcode_libdir_flag_spec='$wl-R $libdir:/usr/lib:/lib' + allow_undefined_flag="-z nodefs" + archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath_+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath_ +fi + + hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag=' $wl-bernotok' + allow_undefined_flag=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + whole_archive_flag_spec='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec='$convenience' + fi + archive_cmds_need_lc=yes + archive_expsym_cmds='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared libraries. + archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + archive_expsym_cmds="$archive_expsym_cmds"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + archive_expsym_cmds="$archive_expsym_cmds"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='' + ;; + m68k) + archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + + bsdi[45]*) + export_dynamic_flag_spec=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + always_export_symbols=yes + file_list_spec='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, )='true' + enable_shared_with_static_runtimes=yes + exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + old_postinstall_cmds='chmod 644 $oldlib' + postlink_cmds='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + old_archive_from_new_cmds='true' + # FIXME: Should let the user specify the lib program. + old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs' + enable_shared_with_static_runtimes=yes + ;; + esac + ;; + + darwin* | rhapsody*) + + + archive_cmds_need_lc=no + hardcode_direct=no + hardcode_automatic=yes + hardcode_shlibpath_var=unsupported + if test yes = "$lt_cv_ld_force_load"; then + whole_archive_flag_spec='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + + else + whole_archive_flag_spec='' + fi + link_all_deplibs=yes + allow_undefined_flag=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + archive_expsym_cmds="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + module_expsym_cmds="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + + else + ld_shlibs=no + fi + + ;; + + dgux*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + hpux9*) + if test yes = "$GCC"; then + archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + fi + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + export_dynamic_flag_spec='$wl-E' + ;; + + hpux10*) + if test yes,no = "$GCC,$with_gnu_ld"; then + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test no = "$with_gnu_ld"; then + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + hardcode_direct_absolute=yes + export_dynamic_flag_spec='$wl-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + fi + ;; + + hpux11*) + if test yes,no = "$GCC,$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + archive_cmds='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + archive_cmds='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5 +$as_echo_n "checking if $CC understands -b... " >&6; } +if ${lt_cv_prog_compiler__b+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler__b=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -b" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler__b=yes + fi + else + lt_cv_prog_compiler__b=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5 +$as_echo "$lt_cv_prog_compiler__b" >&6; } + +if test yes = "$lt_cv_prog_compiler__b"; then + archive_cmds='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' +else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' +fi + + ;; + esac + fi + if test no = "$with_gnu_ld"; then + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct=no + hardcode_shlibpath_var=no + ;; + *) + hardcode_direct=yes + hardcode_direct_absolute=yes + export_dynamic_flag_spec='$wl-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test yes = "$GCC"; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5 +$as_echo_n "checking whether the $host_os linker accepts -exported_symbol... " >&6; } +if ${lt_cv_irix_exported_symbol+:} false; then : + $as_echo_n "(cached) " >&6 +else + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int foo (void) { return 0; } +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_irix_exported_symbol=yes +else + lt_cv_irix_exported_symbol=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5 +$as_echo "$lt_cv_irix_exported_symbol" >&6; } + if test yes = "$lt_cv_irix_exported_symbol"; then + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' + fi + link_all_deplibs=no + else + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' + fi + archive_cmds_need_lc='no' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + inherit_rpath=yes + link_all_deplibs=yes + ;; + + linux*) + case $cc_basename in + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + ld_shlibs=yes + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + newsos6) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + hardcode_shlibpath_var=no + ;; + + *nto* | *qnx*) + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + hardcode_direct=yes + hardcode_shlibpath_var=no + hardcode_direct_absolute=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + export_dynamic_flag_spec='$wl-E' + else + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + fi + else + ld_shlibs=no + fi + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + shrext_cmds=.dll + archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + enable_shared_with_static_runtimes=yes + ;; + + osf3*) + if test yes = "$GCC"; then + allow_undefined_flag=' $wl-expect_unresolved $wl\*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + fi + archive_cmds_need_lc='no' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test yes = "$GCC"; then + allow_undefined_flag=' $wl-expect_unresolved $wl\*' + archive_cmds='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + hardcode_libdir_flag_spec='-rpath $libdir' + fi + archive_cmds_need_lc='no' + hardcode_libdir_separator=: + ;; + + solaris*) + no_undefined_flag=' -z defs' + if test yes = "$GCC"; then + wlarc='$wl' + archive_cmds='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + archive_cmds='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='$wl' + archive_cmds='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_shlibpath_var=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. GCC discards it without '$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test yes = "$GCC"; then + whole_archive_flag_spec='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + else + whole_archive_flag_spec='-z allextract$convenience -z defaultextract' + fi + ;; + esac + link_all_deplibs=yes + ;; + + sunos4*) + if test sequent = "$host_vendor"; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + archive_cmds='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + sysv4) + case $host_vendor in + sni) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags' + reload_cmds='$CC -r -o $output$reload_objs' + hardcode_direct=no + ;; + motorola) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + hardcode_shlibpath_var=no + ;; + + sysv4.3*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + export_dynamic_flag_spec='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ld_shlibs=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag='$wl-z,text' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag='$wl-z,text' + allow_undefined_flag='$wl-z,nodefs' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='$wl-R,$libdir' + hardcode_libdir_separator=':' + link_all_deplibs=yes + export_dynamic_flag_spec='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + *) + ld_shlibs=no + ;; + esac + + if test sni = "$host_vendor"; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + export_dynamic_flag_spec='$wl-Blargedynsym' + ;; + esac + fi + fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5 +$as_echo "$ld_shlibs" >&6; } +test no = "$ld_shlibs" && can_build_shared=no + +with_gnu_ld=$with_gnu_ld + + + + + + + + + + + + + + + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $archive_cmds in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } +if ${lt_cv_archive_cmds_need_lc+:} false; then : + $as_echo_n "(cached) " >&6 +else + $RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl + pic_flag=$lt_prog_compiler_pic + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag + allow_undefined_flag= + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 + (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + then + lt_cv_archive_cmds_need_lc=no + else + lt_cv_archive_cmds_need_lc=yes + fi + allow_undefined_flag=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5 +$as_echo "$lt_cv_archive_cmds_need_lc" >&6; } + archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc + ;; + esac + fi + ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } + +if test yes = "$GCC"; then + case $host_os in + darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; + *) lt_awk_arg='/^libraries:/' ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq='s|=\([A-Za-z]:\)|\1|g' ;; + *) lt_sed_strip_eq='s|=/|/|g' ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary... + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + # ...but if some path component already ends with the multilib dir we assume + # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). + case "$lt_multi_os_dir; $lt_search_path_spec " in + "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) + lt_multi_os_dir= + ;; + esac + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" + elif test -n "$lt_multi_os_dir"; then + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS = " "; FS = "/|\n";} { + lt_foo = ""; + lt_count = 0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo = "/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[lt_foo]++; } + if (lt_freq[lt_foo] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's|/\([A-Za-z]:\)|\1|g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + + + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[4-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib<name>.so + # instead of lib<name>.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a(lib.so.V)' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api" + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib" + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[23].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[3-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + hardcode_libdir_flag_spec='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + if ${lt_cv_shlibpath_overrides_runpath+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \ + LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\"" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then : + lt_cv_shlibpath_overrides_runpath=yes +fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + +fi + + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsdelf*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='NetBSD ld.elf_so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action= +if test -n "$hardcode_libdir_flag_spec" || + test -n "$runpath_var" || + test yes = "$hardcode_automatic"; then + + # We can hardcode non-existent directories. + if test no != "$hardcode_direct" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, )" && + test no != "$hardcode_minus_L"; then + # Linking always hardcodes the temporary library directory. + hardcode_action=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action=unsupported +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5 +$as_echo "$hardcode_action" >&6; } + +if test relink = "$hardcode_action" || + test yes = "$inherit_rpath"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + + + + + + if test yes != "$enable_dlopen"; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen=load_add_on + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen=LoadLibrary + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl +else + + lt_cv_dlopen=dyld + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + +fi + + ;; + + tpf*) + # Don't try to run any link tests for TPF. We know it's impossible + # because TPF is a cross-compiler, and we know how we open DSOs. + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + lt_cv_dlopen_self=no + ;; + + *) + ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load" +if test "x$ac_cv_func_shl_load" = xyes; then : + lt_cv_dlopen=shl_load +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +$as_echo_n "checking for shl_load in -ldld... " >&6; } +if ${ac_cv_lib_dld_shl_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_shl_load=yes +else + ac_cv_lib_dld_shl_load=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +$as_echo "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes; then : + lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld +else + ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen" +if test "x$ac_cv_func_dlopen" = xyes; then : + lt_cv_dlopen=dlopen +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5 +$as_echo_n "checking for dlopen in -lsvld... " >&6; } +if ${ac_cv_lib_svld_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsvld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_svld_dlopen=yes +else + ac_cv_lib_svld_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5 +$as_echo "$ac_cv_lib_svld_dlopen" >&6; } +if test "x$ac_cv_lib_svld_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5 +$as_echo_n "checking for dld_link in -ldld... " >&6; } +if ${ac_cv_lib_dld_dld_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dld_link (); +int +main () +{ +return dld_link (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_dld_link=yes +else + ac_cv_lib_dld_dld_link=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5 +$as_echo "$ac_cv_lib_dld_dld_link" >&6; } +if test "x$ac_cv_lib_dld_dld_link" = xyes; then : + lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld +fi + + +fi + + +fi + + +fi + + +fi + + +fi + + ;; + esac + + if test no = "$lt_cv_dlopen"; then + enable_dlopen=no + else + enable_dlopen=yes + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS=$CPPFLAGS + test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS=$LDFLAGS + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS=$LIBS + LIBS="$lt_cv_dlopen_libs $LIBS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5 +$as_echo_n "checking whether a program can dlopen itself... " >&6; } +if ${lt_cv_dlopen_self+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test yes = "$cross_compiling"; then : + lt_cv_dlopen_self=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include <dlfcn.h> +#endif + +#include <stdio.h> + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +} +_LT_EOF + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5 +$as_echo "$lt_cv_dlopen_self" >&6; } + + if test yes = "$lt_cv_dlopen_self"; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5 +$as_echo_n "checking whether a statically linked program can dlopen itself... " >&6; } +if ${lt_cv_dlopen_self_static+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test yes = "$cross_compiling"; then : + lt_cv_dlopen_self_static=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include <dlfcn.h> +#endif + +#include <stdio.h> + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +} +_LT_EOF + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self_static=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5 +$as_echo "$lt_cv_dlopen_self_static" >&6; } + fi + + CPPFLAGS=$save_CPPFLAGS + LDFLAGS=$save_LDFLAGS + LIBS=$save_LIBS + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi + + + + + + + + + + + + + + + + + +striplib= +old_striplib= +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5 +$as_echo_n "checking whether stripping libraries is possible... " >&6; } +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP"; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + ;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + ;; + esac +fi + + + + + + + + + + + + + # Report what library types will actually be built + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5 +$as_echo_n "checking if libtool supports shared libraries... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5 +$as_echo "$can_build_shared" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5 +$as_echo_n "checking whether to build shared libraries... " >&6; } + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[4-9]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5 +$as_echo "$enable_shared" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5 +$as_echo_n "checking whether to build static libraries... " >&6; } + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5 +$as_echo "$enable_static" >&6; } + + + + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +CC=$lt_save_CC + + + + + + + + + + + + + + + + ac_config_commands="$ac_config_commands libtool" + + + + +# Only expand once: + + + + +# Check for library functions that SQLite can optionally use. +for ac_func in fdatasync usleep fullfsync localtime_r gmtime_r +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + +ac_fn_c_check_decl "$LINENO" "strerror_r" "ac_cv_have_decl_strerror_r" "$ac_includes_default" +if test "x$ac_cv_have_decl_strerror_r" = xyes; then : + ac_have_decl=1 +else + ac_have_decl=0 +fi + +cat >>confdefs.h <<_ACEOF +#define HAVE_DECL_STRERROR_R $ac_have_decl +_ACEOF + +for ac_func in strerror_r +do : + ac_fn_c_check_func "$LINENO" "strerror_r" "ac_cv_func_strerror_r" +if test "x$ac_cv_func_strerror_r" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_STRERROR_R 1 +_ACEOF + +fi +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether strerror_r returns char *" >&5 +$as_echo_n "checking whether strerror_r returns char *... " >&6; } +if ${ac_cv_func_strerror_r_char_p+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ac_cv_func_strerror_r_char_p=no + if test $ac_cv_have_decl_strerror_r = yes; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ + + char buf[100]; + char x = *strerror_r (0, buf, sizeof buf); + char *p = strerror_r (0, buf, sizeof buf); + return !p || x; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_func_strerror_r_char_p=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + else + # strerror_r is not declared. Choose between + # systems that have relatively inaccessible declarations for the + # function. BeOS and DEC UNIX 4.0 fall in this category, but the + # former has a strerror_r that returns char*, while the latter + # has a strerror_r that returns `int'. + # This test should segfault on the DEC system. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_includes_default + extern char *strerror_r (); +int +main () +{ +char buf[100]; + char x = *strerror_r (0, buf, sizeof buf); + return ! isalpha (x); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + ac_cv_func_strerror_r_char_p=yes +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_strerror_r_char_p" >&5 +$as_echo "$ac_cv_func_strerror_r_char_p" >&6; } +if test $ac_cv_func_strerror_r_char_p = yes; then + +$as_echo "#define STRERROR_R_CHAR_P 1" >>confdefs.h + +fi + + +ac_config_files="$ac_config_files Makefile sqlite3.pc" + +BUILD_CFLAGS= + + +#------------------------------------------------------------------------- +# Two options to enable readline compatible libraries: +# +# --enable-editline +# --enable-readline +# +# Both are enabled by default. If, after command line processing both are +# still enabled, the script searches for editline first and automatically +# disables readline if it is found. So, to use readline explicitly, the +# user must pass "--disable-editline". To disable command line editing +# support altogether, "--disable-editline --disable-readline". +# +# When searching for either library, check for headers before libraries +# as some distros supply packages that contain libraries but not header +# files, which come as a separate development package. +# +# Check whether --enable-editline was given. +if test "${enable_editline+set}" = set; then : + enableval=$enable_editline; +fi + +# Check whether --enable-readline was given. +if test "${enable_readline+set}" = set; then : + enableval=$enable_readline; +fi + + +if test x"$enable_editline" != xno ; then : + + for ac_header in editline/readline.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "editline/readline.h" "ac_cv_header_editline_readline_h" "$ac_includes_default" +if test "x$ac_cv_header_editline_readline_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_EDITLINE_READLINE_H 1 +_ACEOF + + sLIBS=$LIBS + LIBS="" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing readline" >&5 +$as_echo_n "checking for library containing readline... " >&6; } +if ${ac_cv_search_readline+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char readline (); +int +main () +{ +return readline (); + ; + return 0; +} +_ACEOF +for ac_lib in '' edit; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib -ltinfo $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_readline=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_readline+:} false; then : + break +fi +done +if ${ac_cv_search_readline+:} false; then : + +else + ac_cv_search_readline=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_readline" >&5 +$as_echo "$ac_cv_search_readline" >&6; } +ac_res=$ac_cv_search_readline +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + + +$as_echo "#define HAVE_EDITLINE 1" >>confdefs.h + + READLINE_LIBS="$LIBS -ltinfo" + enable_readline=no + +fi + + { ac_cv_search_readline=; unset ac_cv_search_readline;} + LIBS=$sLIBS + +fi + +done + + +fi + +if test x"$enable_readline" != xno ; then : + + for ac_header in readline/readline.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "readline/readline.h" "ac_cv_header_readline_readline_h" "$ac_includes_default" +if test "x$ac_cv_header_readline_readline_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_READLINE_READLINE_H 1 +_ACEOF + + sLIBS=$LIBS + LIBS="" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing tgetent" >&5 +$as_echo_n "checking for library containing tgetent... " >&6; } +if ${ac_cv_search_tgetent+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char tgetent (); +int +main () +{ +return tgetent (); + ; + return 0; +} +_ACEOF +for ac_lib in '' termcap curses ncurses ncursesw; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_tgetent=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_tgetent+:} false; then : + break +fi +done +if ${ac_cv_search_tgetent+:} false; then : + +else + ac_cv_search_tgetent=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_tgetent" >&5 +$as_echo "$ac_cv_search_tgetent" >&6; } +ac_res=$ac_cv_search_tgetent +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing readline" >&5 +$as_echo_n "checking for library containing readline... " >&6; } +if ${ac_cv_search_readline+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char readline (); +int +main () +{ +return readline (); + ; + return 0; +} +_ACEOF +for ac_lib in '' readline edit; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_readline=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_readline+:} false; then : + break +fi +done +if ${ac_cv_search_readline+:} false; then : + +else + ac_cv_search_readline=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_readline" >&5 +$as_echo "$ac_cv_search_readline" >&6; } +ac_res=$ac_cv_search_readline +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + + +$as_echo "#define HAVE_READLINE 1" >>confdefs.h + + READLINE_LIBS=$LIBS + +fi + + LIBS=$sLIBS + +fi + +done + + +fi + + +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-threadsafe +# +# Check whether --enable-threadsafe was given. +if test "${enable_threadsafe+set}" = set; then : + enableval=$enable_threadsafe; +else + enable_threadsafe=yes +fi + +if test x"$enable_threadsafe" == "xno"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_THREADSAFE=0" +else + BUILD_CFLAGS="$BUILD_CFLAGS -D_REENTRANT=1 -DSQLITE_THREADSAFE=1" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing pthread_create" >&5 +$as_echo_n "checking for library containing pthread_create... " >&6; } +if ${ac_cv_search_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +for ac_lib in '' pthread; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_pthread_create=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_pthread_create+:} false; then : + break +fi +done +if ${ac_cv_search_pthread_create+:} false; then : + +else + ac_cv_search_pthread_create=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_pthread_create" >&5 +$as_echo "$ac_cv_search_pthread_create" >&6; } +ac_res=$ac_cv_search_pthread_create +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing pthread_mutexattr_init" >&5 +$as_echo_n "checking for library containing pthread_mutexattr_init... " >&6; } +if ${ac_cv_search_pthread_mutexattr_init+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_mutexattr_init (); +int +main () +{ +return pthread_mutexattr_init (); + ; + return 0; +} +_ACEOF +for ac_lib in '' pthread; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_pthread_mutexattr_init=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_pthread_mutexattr_init+:} false; then : + break +fi +done +if ${ac_cv_search_pthread_mutexattr_init+:} false; then : + +else + ac_cv_search_pthread_mutexattr_init=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_pthread_mutexattr_init" >&5 +$as_echo "$ac_cv_search_pthread_mutexattr_init" >&6; } +ac_res=$ac_cv_search_pthread_mutexattr_init +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +fi + +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-dynamic-extensions +# +# Check whether --enable-dynamic-extensions was given. +if test "${enable_dynamic_extensions+set}" = set; then : + enableval=$enable_dynamic_extensions; +else + enable_dynamic_extensions=yes +fi + +if test x"$enable_dynamic_extensions" != "xno"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing dlopen" >&5 +$as_echo_n "checking for library containing dlopen... " >&6; } +if ${ac_cv_search_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +for ac_lib in '' dl; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_dlopen=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_dlopen+:} false; then : + break +fi +done +if ${ac_cv_search_dlopen+:} false; then : + +else + ac_cv_search_dlopen=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_dlopen" >&5 +$as_echo "$ac_cv_search_dlopen" >&6; } +ac_res=$ac_cv_search_dlopen +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +fi + +else + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_OMIT_LOAD_EXTENSION=1" +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for whether to support dynamic extensions" >&5 +$as_echo_n "checking for whether to support dynamic extensions... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_dynamic_extensions" >&5 +$as_echo "$enable_dynamic_extensions" >&6; } +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-math +# +# Check whether --enable-math was given. +if test "${enable_math+set}" = set; then : + enableval=$enable_math; +else + enable_math=yes +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking SQL math functions" >&5 +$as_echo_n "checking SQL math functions... " >&6; } +if test x"$enable_math" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_MATH_FUNCTIONS" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: enabled" >&5 +$as_echo "enabled" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing ceil" >&5 +$as_echo_n "checking for library containing ceil... " >&6; } +if ${ac_cv_search_ceil+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char ceil (); +int +main () +{ +return ceil (); + ; + return 0; +} +_ACEOF +for ac_lib in '' m; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_ceil=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_ceil+:} false; then : + break +fi +done +if ${ac_cv_search_ceil+:} false; then : + +else + ac_cv_search_ceil=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_ceil" >&5 +$as_echo "$ac_cv_search_ceil" >&6; } +ac_res=$ac_cv_search_ceil +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +fi + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: disabled" >&5 +$as_echo "disabled" >&6; } +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-fts4 +# +# Check whether --enable-fts4 was given. +if test "${enable_fts4+set}" = set; then : + enableval=$enable_fts4; +else + enable_fts4=yes +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking FTS4 extension" >&5 +$as_echo_n "checking FTS4 extension... " >&6; } +if test x"$enable_fts4" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_FTS4" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: enabled" >&5 +$as_echo "enabled" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: disabled" >&5 +$as_echo "disabled" >&6; } +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-fts3 +# +# Check whether --enable-fts3 was given. +if test "${enable_fts3+set}" = set; then : + enableval=$enable_fts3; +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking FTS3 extension" >&5 +$as_echo_n "checking FTS3 extension... " >&6; } +if test x"$enable_fts3" = "xyes" -a x"$enable_fts4" = "xno"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_FTS3" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: enabled" >&5 +$as_echo "enabled" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: disabled" >&5 +$as_echo "disabled" >&6; } +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-fts5 +# +# Check whether --enable-fts5 was given. +if test "${enable_fts5+set}" = set; then : + enableval=$enable_fts5; +else + enable_fts5=yes +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking FTS5 extension" >&5 +$as_echo_n "checking FTS5 extension... " >&6; } +if test x"$enable_fts5" = "xyes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: enabled" >&5 +$as_echo "enabled" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing log" >&5 +$as_echo_n "checking for library containing log... " >&6; } +if ${ac_cv_search_log+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char log (); +int +main () +{ +return log (); + ; + return 0; +} +_ACEOF +for ac_lib in '' m; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_log=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_log+:} false; then : + break +fi +done +if ${ac_cv_search_log+:} false; then : + +else + ac_cv_search_log=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_log" >&5 +$as_echo "$ac_cv_search_log" >&6; } +ac_res=$ac_cv_search_log +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +fi + + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_FTS5" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: disabled" >&5 +$as_echo "disabled" >&6; } +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-rtree +# +# Check whether --enable-rtree was given. +if test "${enable_rtree+set}" = set; then : + enableval=$enable_rtree; +else + enable_rtree=yes +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking RTREE extension" >&5 +$as_echo_n "checking RTREE extension... " >&6; } +if test x"$enable_rtree" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_GEOPOLY" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: enabled" >&5 +$as_echo "enabled" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: disabled" >&5 +$as_echo "disabled" >&6; } +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-session +# +# Check whether --enable-session was given. +if test "${enable_session+set}" = set; then : + enableval=$enable_session; +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Session extension" >&5 +$as_echo_n "checking Session extension... " >&6; } +if test x"$enable_session" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: enabled" >&5 +$as_echo "enabled" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: disabled" >&5 +$as_echo "disabled" >&6; } +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-debug +# +# Check whether --enable-debug was given. +if test "${enable_debug+set}" = set; then : + enableval=$enable_debug; +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Build type" >&5 +$as_echo_n "checking Build type... " >&6; } +if test x"$enable_debug" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_DEBUG -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE" + CFLAGS="-g -O0" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: debug" >&5 +$as_echo "debug" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: release" >&5 +$as_echo "release" >&6; } +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-static-shell +# +# Check whether --enable-static-shell was given. +if test "${enable_static_shell+set}" = set; then : + enableval=$enable_static_shell; +else + enable_static_shell=yes +fi + +if test x"$enable_static_shell" = "xyes"; then + EXTRA_SHELL_OBJ=sqlite3-sqlite3.$OBJEXT +else + EXTRA_SHELL_OBJ=libsqlite3.la +fi + +#----------------------------------------------------------------------- + +for ac_func in posix_fallocate +do : + ac_fn_c_check_func "$LINENO" "posix_fallocate" "ac_cv_func_posix_fallocate" +if test "x$ac_cv_func_posix_fallocate" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_POSIX_FALLOCATE 1 +_ACEOF + +fi +done + +for ac_header in zlib.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" +if test "x$ac_cv_header_zlib_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_ZLIB_H 1 +_ACEOF + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing deflate" >&5 +$as_echo_n "checking for library containing deflate... " >&6; } +if ${ac_cv_search_deflate+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char deflate (); +int +main () +{ +return deflate (); + ; + return 0; +} +_ACEOF +for ac_lib in '' z; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_deflate=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_deflate+:} false; then : + break +fi +done +if ${ac_cv_search_deflate+:} false; then : + +else + ac_cv_search_deflate=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_deflate" >&5 +$as_echo "$ac_cv_search_deflate" >&6; } +ac_res=$ac_cv_search_deflate +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_HAVE_ZLIB" +fi + + +fi + +done + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing system" >&5 +$as_echo_n "checking for library containing system... " >&6; } +if ${ac_cv_search_system+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char system (); +int +main () +{ +return system (); + ; + return 0; +} +_ACEOF +for ac_lib in '' ; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_system=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_system+:} false; then : + break +fi +done +if ${ac_cv_search_system+:} false; then : + +else + ac_cv_search_system=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_system" >&5 +$as_echo "$ac_cv_search_system" >&6; } +ac_res=$ac_cv_search_system +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +else + SHELL_CFLAGS="-DSQLITE_NOHAVE_SYSTEM" +fi + + + +#----------------------------------------------------------------------- +# UPDATE: Maybe it's better if users just set CFLAGS before invoking +# configure. This option doesn't really add much... +# +# --enable-tempstore +# +# AC_ARG_ENABLE(tempstore, [AS_HELP_STRING( +# [--enable-tempstore], +# [in-memory temporary tables (never, no, yes, always) [default=no]])], +# [], [enable_tempstore=no]) +# AC_MSG_CHECKING([for whether or not to store temp tables in-memory]) +# case "$enable_tempstore" in +# never ) TEMP_STORE=0 ;; +# no ) TEMP_STORE=1 ;; +# always ) TEMP_STORE=3 ;; +# yes ) TEMP_STORE=3 ;; +# * ) +# TEMP_STORE=1 +# enable_tempstore=yes +# ;; +# esac +# AC_MSG_RESULT($enable_tempstore) +# AC_SUBST(TEMP_STORE) +#----------------------------------------------------------------------- + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5 +$as_echo_n "checking that generated files are newer than configure... " >&6; } + if test -n "$am_sleep_pid"; then + # Hide warnings about reused PIDs. + wait $am_sleep_pid 2>/dev/null + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: done" >&5 +$as_echo "done" >&6; } + if test -n "$EXEEXT"; then + am__EXEEXT_TRUE= + am__EXEEXT_FALSE='#' +else + am__EXEEXT_TRUE='#' + am__EXEEXT_FALSE= +fi + +if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then + as_fn_error $? "conditional \"AMDEP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then + as_fn_error $? "conditional \"am__fastdepCC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then + as_fn_error $? "conditional \"am__fastdepCC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by sqlite $as_me 3.44.0, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_commands="$ac_config_commands" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Configuration commands: +$config_commands + +Report bugs to <http://www.sqlite.org>." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +sqlite config.status 3.44.0 +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +INSTALL='$INSTALL' +MKDIR_P='$MKDIR_P' +AWK='$AWK' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# +# INIT-COMMANDS +# +AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir" + + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`' +macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`' +enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`' +enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`' +pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`' +enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`' +shared_archive_member_spec='`$ECHO "$shared_archive_member_spec" | $SED "$delay_single_quote_subst"`' +SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`' +ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`' +PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`' +host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`' +host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`' +host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`' +build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`' +build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`' +build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`' +SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`' +Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`' +GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`' +EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`' +FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`' +LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`' +NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`' +LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`' +max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`' +ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`' +exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`' +lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`' +lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`' +lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`' +lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`' +lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`' +reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`' +reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`' +OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`' +deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`' +file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`' +file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`' +want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`' +DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`' +sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`' +AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`' +AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`' +archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`' +STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`' +RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`' +old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`' +old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`' +old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`' +lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`' +CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`' +CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`' +compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`' +GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_import='`$ECHO "$lt_cv_sys_global_symbol_to_import" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`' +lt_cv_nm_interface='`$ECHO "$lt_cv_nm_interface" | $SED "$delay_single_quote_subst"`' +nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`' +lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`' +lt_cv_truncate_bin='`$ECHO "$lt_cv_truncate_bin" | $SED "$delay_single_quote_subst"`' +objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`' +MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`' +lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`' +need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`' +MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`' +DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`' +NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`' +LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`' +OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`' +OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`' +libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`' +shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`' +extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`' +archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`' +enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`' +export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`' +whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`' +compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`' +old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`' +old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`' +archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`' +archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`' +module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`' +module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`' +with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`' +allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`' +no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`' +hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`' +hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`' +hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`' +hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`' +hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`' +inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`' +link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`' +always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`' +export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`' +exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`' +include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`' +prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`' +postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`' +file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`' +variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`' +need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`' +need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`' +version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`' +runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`' +shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`' +shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`' +libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`' +library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`' +soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`' +install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`' +postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`' +postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`' +finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`' +finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`' +hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`' +sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`' +configure_time_dlsearch_path='`$ECHO "$configure_time_dlsearch_path" | $SED "$delay_single_quote_subst"`' +configure_time_lt_sys_library_path='`$ECHO "$configure_time_lt_sys_library_path" | $SED "$delay_single_quote_subst"`' +hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`' +enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`' +enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`' +enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`' +old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`' +striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`' + +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in SHELL \ +ECHO \ +PATH_SEPARATOR \ +SED \ +GREP \ +EGREP \ +FGREP \ +LD \ +NM \ +LN_S \ +lt_SP2NL \ +lt_NL2SP \ +reload_flag \ +OBJDUMP \ +deplibs_check_method \ +file_magic_cmd \ +file_magic_glob \ +want_nocaseglob \ +DLLTOOL \ +sharedlib_from_linklib_cmd \ +AR \ +AR_FLAGS \ +archiver_list_spec \ +STRIP \ +RANLIB \ +CC \ +CFLAGS \ +compiler \ +lt_cv_sys_global_symbol_pipe \ +lt_cv_sys_global_symbol_to_cdecl \ +lt_cv_sys_global_symbol_to_import \ +lt_cv_sys_global_symbol_to_c_name_address \ +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \ +lt_cv_nm_interface \ +nm_file_list_spec \ +lt_cv_truncate_bin \ +lt_prog_compiler_no_builtin_flag \ +lt_prog_compiler_pic \ +lt_prog_compiler_wl \ +lt_prog_compiler_static \ +lt_cv_prog_compiler_c_o \ +need_locks \ +MANIFEST_TOOL \ +DSYMUTIL \ +NMEDIT \ +LIPO \ +OTOOL \ +OTOOL64 \ +shrext_cmds \ +export_dynamic_flag_spec \ +whole_archive_flag_spec \ +compiler_needs_object \ +with_gnu_ld \ +allow_undefined_flag \ +no_undefined_flag \ +hardcode_libdir_flag_spec \ +hardcode_libdir_separator \ +exclude_expsyms \ +include_expsyms \ +file_list_spec \ +variables_saved_for_relink \ +libname_spec \ +library_names_spec \ +soname_spec \ +install_override_mode \ +finish_eval \ +old_striplib \ +striplib; do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[\\\\\\\`\\"\\\$]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in reload_cmds \ +old_postinstall_cmds \ +old_postuninstall_cmds \ +old_archive_cmds \ +extract_expsyms_cmds \ +old_archive_from_new_cmds \ +old_archive_from_expsyms_cmds \ +archive_cmds \ +archive_expsym_cmds \ +module_cmds \ +module_expsym_cmds \ +export_symbols_cmds \ +prelink_cmds \ +postlink_cmds \ +postinstall_cmds \ +postuninstall_cmds \ +finish_cmds \ +sys_lib_search_path_spec \ +configure_time_dlsearch_path \ +configure_time_lt_sys_library_path; do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[\\\\\\\`\\"\\\$]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +ac_aux_dir='$ac_aux_dir' + +# See if we are running on zsh, and set the options that allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + + + PACKAGE='$PACKAGE' + VERSION='$VERSION' + RM='$RM' + ofile='$ofile' + + + + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; + "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "sqlite3.pc") CONFIG_FILES="$CONFIG_FILES sqlite3.pc" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' <conf$$subs.awk | sed ' +/^[^""]/{ + N + s/\n// +} +' >>$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES :C $CONFIG_COMMANDS" +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + + case $INSTALL in + [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; + *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; + esac + ac_MKDIR_P=$MKDIR_P + case $MKDIR_P in + [\\/$]* | ?:[\\/]* ) ;; + */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; + esac +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +s&@INSTALL@&$ac_INSTALL&;t t +s&@MKDIR_P@&$ac_MKDIR_P&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 +$as_echo "$as_me: executing $ac_file commands" >&6;} + ;; + esac + + + case $ac_file$ac_mode in + "depfiles":C) test x"$AMDEP_TRUE" != x"" || { + # Older Autoconf quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + case $CONFIG_FILES in + *\'*) eval set x "$CONFIG_FILES" ;; + *) set x $CONFIG_FILES ;; + esac + shift + for mf + do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named 'Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`$as_dirname -- "$mf" || +$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$mf" : 'X\(//\)[^/]' \| \ + X"$mf" : 'X\(//\)$' \| \ + X"$mf" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$mf" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running 'make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "$am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`$as_dirname -- "$file" || +$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$file" : 'X\(//\)[^/]' \| \ + X"$file" : 'X\(//\)$' \| \ + X"$file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir=$dirpart/$fdir; as_fn_mkdir_p + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done + done +} + ;; + "libtool":C) + + # See if we are running on zsh, and set the options that allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST + fi + + cfgfile=${ofile}T + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL +# Generated automatically by $as_me ($PACKAGE) $VERSION +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: +# NOTE: Changes made to this file will be lost: look at ltmain.sh. + +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit, 1996 + +# Copyright (C) 2014 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program or library that is built +# using GNU Libtool, you may include this file under the same +# distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +# The names of the tagged configurations supported by this script. +available_tags='' + +# Configured defaults for sys_lib_dlsearch_path munging. +: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} + +# ### BEGIN LIBTOOL CONFIG + +# Which release of libtool.m4 was used? +macro_version=$macro_version +macro_revision=$macro_revision + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# What type of objects to build. +pic_mode=$pic_mode + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# Shared archive member basename,for filename based shared library versioning on AIX. +shared_archive_member_spec=$shared_archive_member_spec + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# An echo program that protects backslashes. +ECHO=$lt_ECHO + +# The PATH separator for the build system. +PATH_SEPARATOR=$lt_PATH_SEPARATOR + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# A sed program that does not truncate output. +SED=$lt_SED + +# Sed that helps us avoid accidentally triggering echo(1) options like -n. +Xsed="\$SED -e 1s/^X//" + +# A grep program that handles long lines. +GREP=$lt_GREP + +# An ERE matcher. +EGREP=$lt_EGREP + +# A literal string matcher. +FGREP=$lt_FGREP + +# A BSD- or MS-compatible name lister. +NM=$lt_NM + +# Whether we need soft or hard links. +LN_S=$lt_LN_S + +# What is the maximum length of a command? +max_cmd_len=$max_cmd_len + +# Object file suffix (normally "o"). +objext=$ac_objext + +# Executable file suffix (normally ""). +exeext=$exeext + +# whether the shell understands "unset". +lt_unset=$lt_unset + +# turn spaces into newlines. +SP2NL=$lt_lt_SP2NL + +# turn newlines into spaces. +NL2SP=$lt_lt_NL2SP + +# convert \$build file names to \$host format. +to_host_file_cmd=$lt_cv_to_host_file_cmd + +# convert \$build files to toolchain format. +to_tool_file_cmd=$lt_cv_to_tool_file_cmd + +# An object symbol dumper. +OBJDUMP=$lt_OBJDUMP + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method = "file_magic". +file_magic_cmd=$lt_file_magic_cmd + +# How to find potential files when deplibs_check_method = "file_magic". +file_magic_glob=$lt_file_magic_glob + +# Find potential files using nocaseglob when deplibs_check_method = "file_magic". +want_nocaseglob=$lt_want_nocaseglob + +# DLL creation program. +DLLTOOL=$lt_DLLTOOL + +# Command to associate shared and link libraries. +sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd + +# The archiver. +AR=$lt_AR + +# Flags to create an archive. +AR_FLAGS=$lt_AR_FLAGS + +# How to feed a file listing to the archiver. +archiver_list_spec=$lt_archiver_list_spec + +# A symbol stripping program. +STRIP=$lt_STRIP + +# Commands used to install an old-style archive. +RANLIB=$lt_RANLIB +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Whether to use a lock for old archive extraction. +lock_old_archive_extraction=$lock_old_archive_extraction + +# A C compiler. +LTCC=$lt_CC + +# LTCC compiler flags. +LTCFLAGS=$lt_CFLAGS + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration. +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm into a list of symbols to manually relocate. +global_symbol_to_import=$lt_lt_cv_sys_global_symbol_to_import + +# Transform the output of nm in a C name address pair. +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# Transform the output of nm in a C name address pair when lib prefix is needed. +global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix + +# The name lister interface. +nm_interface=$lt_lt_cv_nm_interface + +# Specify filename containing input files for \$NM. +nm_file_list_spec=$lt_nm_file_list_spec + +# The root where to search for dependent libraries,and where our libraries should be installed. +lt_sysroot=$lt_sysroot + +# Command to truncate a binary pipe. +lt_truncate_bin=$lt_lt_cv_truncate_bin + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# Used to examine libraries when file_magic_cmd begins with "file". +MAGIC_CMD=$MAGIC_CMD + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Manifest tool. +MANIFEST_TOOL=$lt_MANIFEST_TOOL + +# Tool to manipulate archived DWARF debug symbol files on Mac OS X. +DSYMUTIL=$lt_DSYMUTIL + +# Tool to change global to local symbols on Mac OS X. +NMEDIT=$lt_NMEDIT + +# Tool to manipulate fat objects and archives on Mac OS X. +LIPO=$lt_LIPO + +# ldd/readelf like tool for Mach-O binaries on Mac OS X. +OTOOL=$lt_OTOOL + +# ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4. +OTOOL64=$lt_OTOOL64 + +# Old archive suffix (normally "a"). +libext=$libext + +# Shared library suffix (normally ".so"). +shrext_cmds=$lt_shrext_cmds + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at link time. +variables_saved_for_relink=$lt_variables_saved_for_relink + +# Do we need the "lib" prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Library versioning type. +version_type=$version_type + +# Shared library runtime path variable. +runpath_var=$runpath_var + +# Shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Permission mode override for installation of shared libraries. +install_override_mode=$lt_install_override_mode + +# Command to use after installation of a shared archive. +postinstall_cmds=$lt_postinstall_cmds + +# Command to use after uninstallation of a shared archive. +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# As "finish_cmds", except a single script fragment to be evaled but +# not shown. +finish_eval=$lt_finish_eval + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Compile-time system search path for libraries. +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Detected run-time system search path for libraries. +sys_lib_dlsearch_path_spec=$lt_configure_time_dlsearch_path + +# Explicit LT_SYS_LIBRARY_PATH set during ./configure time. +configure_time_lt_sys_library_path=$lt_configure_time_lt_sys_library_path + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + + +# The linker used to build libraries. +LD=$lt_LD + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# Commands used to build an old-style archive. +old_archive_cmds=$lt_old_archive_cmds + +# A language specific compiler. +CC=$lt_compiler + +# Is the compiler the GNU compiler? +with_gcc=$GCC + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc + +# Whether or not to disallow shared libs when runtime libs are static. +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec + +# Whether the compiler copes with passing no objects directly. +compiler_needs_object=$lt_compiler_needs_object + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds + +# Commands used to build a shared archive. +archive_cmds=$lt_archive_cmds +archive_expsym_cmds=$lt_archive_expsym_cmds + +# Commands used to build a loadable module if different from building +# a shared archive. +module_cmds=$lt_module_cmds +module_expsym_cmds=$lt_module_expsym_cmds + +# Whether we are building with GNU ld or not. +with_gnu_ld=$lt_with_gnu_ld + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag + +# Flag that enforces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec + +# Whether we need a single "-rpath" flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary. +hardcode_direct=$hardcode_direct + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary and the resulting library dependency is +# "absolute",i.e impossible to change by setting \$shlibpath_var if the +# library is relocated. +hardcode_direct_absolute=$hardcode_direct_absolute + +# Set to "yes" if using the -LDIR flag during linking hardcodes DIR +# into the resulting binary. +hardcode_minus_L=$hardcode_minus_L + +# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR +# into the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var + +# Set to "yes" if building a shared library automatically hardcodes DIR +# into the library and all subsequent libraries and executables linked +# against it. +hardcode_automatic=$hardcode_automatic + +# Set to yes if linker adds runtime paths of dependent libraries +# to runtime path list. +inherit_rpath=$inherit_rpath + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs + +# Set to "yes" if exported symbols are required. +always_export_symbols=$always_export_symbols + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms + +# Commands necessary for linking programs (against libraries) with templates. +prelink_cmds=$lt_prelink_cmds + +# Commands necessary for finishing linking programs. +postlink_cmds=$lt_postlink_cmds + +# Specify filename containing input files. +file_list_spec=$lt_file_list_spec + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action + +# ### END LIBTOOL CONFIG + +_LT_EOF + + cat <<'_LT_EOF' >> "$cfgfile" + +# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE + +# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x$2 in + x) + ;; + *:) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" + ;; + x:*) + eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" + ;; + *) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" + ;; + esac +} + + +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in $*""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} + + +# ### END FUNCTIONS SHARED WITH CONFIGURE + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + +ltmain=$ac_aux_dir/ltmain.sh + + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" + + ;; + + esac +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + diff --git a/SQLITE/configure.ac b/SQLITE/configure.ac new file mode 100644 index 0000000..cf5fae5 --- /dev/null +++ b/SQLITE/configure.ac @@ -0,0 +1,270 @@ + +#----------------------------------------------------------------------- +# Supports the following non-standard switches. +# +# --enable-threadsafe +# --enable-readline +# --enable-editline +# --enable-static-shell +# --enable-dynamic-extensions +# + +AC_PREREQ(2.61) +AC_INIT(sqlite, 3.44.0, http://www.sqlite.org) +AC_CONFIG_SRCDIR([sqlite3.c]) +AC_CONFIG_AUX_DIR([.]) + +# Use automake. +AM_INIT_AUTOMAKE([foreign]) + +AC_SYS_LARGEFILE + +# Check for required programs. +AC_PROG_CC +AC_PROG_LIBTOOL +AC_PROG_MKDIR_P + +# Check for library functions that SQLite can optionally use. +AC_CHECK_FUNCS([fdatasync usleep fullfsync localtime_r gmtime_r]) +AC_FUNC_STRERROR_R + +AC_CONFIG_FILES([Makefile sqlite3.pc]) +BUILD_CFLAGS= +AC_SUBST(BUILD_CFLAGS) + +#------------------------------------------------------------------------- +# Two options to enable readline compatible libraries: +# +# --enable-editline +# --enable-readline +# +# Both are enabled by default. If, after command line processing both are +# still enabled, the script searches for editline first and automatically +# disables readline if it is found. So, to use readline explicitly, the +# user must pass "--disable-editline". To disable command line editing +# support altogether, "--disable-editline --disable-readline". +# +# When searching for either library, check for headers before libraries +# as some distros supply packages that contain libraries but not header +# files, which come as a separate development package. +# +AC_ARG_ENABLE(editline, [AS_HELP_STRING([--enable-editline],[use BSD libedit])]) +AC_ARG_ENABLE(readline, [AS_HELP_STRING([--enable-readline],[use readline])]) + +AS_IF([ test x"$enable_editline" != xno ],[ + AC_CHECK_HEADERS([editline/readline.h],[ + sLIBS=$LIBS + LIBS="" + AC_SEARCH_LIBS([readline],[edit],[ + AC_DEFINE([HAVE_EDITLINE],1,Define to use BSD editline) + READLINE_LIBS="$LIBS -ltinfo" + enable_readline=no + ],[],[-ltinfo]) + AS_UNSET(ac_cv_search_readline) + LIBS=$sLIBS + ]) +]) + +AS_IF([ test x"$enable_readline" != xno ],[ + AC_CHECK_HEADERS([readline/readline.h],[ + sLIBS=$LIBS + LIBS="" + AC_SEARCH_LIBS(tgetent, termcap curses ncurses ncursesw, [], []) + AC_SEARCH_LIBS(readline,[readline edit], [ + AC_DEFINE([HAVE_READLINE],1,Define to use readline or wrapper) + READLINE_LIBS=$LIBS + ]) + LIBS=$sLIBS + ]) +]) + +AC_SUBST(READLINE_LIBS) +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-threadsafe +# +AC_ARG_ENABLE(threadsafe, [AS_HELP_STRING( + [--enable-threadsafe], [build a thread-safe library [default=yes]])], + [], [enable_threadsafe=yes]) +if test x"$enable_threadsafe" == "xno"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_THREADSAFE=0" +else + BUILD_CFLAGS="$BUILD_CFLAGS -D_REENTRANT=1 -DSQLITE_THREADSAFE=1" + AC_SEARCH_LIBS(pthread_create, pthread) + AC_SEARCH_LIBS(pthread_mutexattr_init, pthread) +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-dynamic-extensions +# +AC_ARG_ENABLE(dynamic-extensions, [AS_HELP_STRING( + [--enable-dynamic-extensions], [support loadable extensions [default=yes]])], + [], [enable_dynamic_extensions=yes]) +if test x"$enable_dynamic_extensions" != "xno"; then + AC_SEARCH_LIBS(dlopen, dl) +else + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_OMIT_LOAD_EXTENSION=1" +fi +AC_MSG_CHECKING([for whether to support dynamic extensions]) +AC_MSG_RESULT($enable_dynamic_extensions) +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-math +# +AC_ARG_ENABLE(math, [AS_HELP_STRING( + [--enable-math], [SQL math functions [default=yes]])], + [], [enable_math=yes]) +AC_MSG_CHECKING([SQL math functions]) +if test x"$enable_math" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_MATH_FUNCTIONS" + AC_MSG_RESULT([enabled]) + AC_SEARCH_LIBS(ceil, m) +else + AC_MSG_RESULT([disabled]) +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-fts4 +# +AC_ARG_ENABLE(fts4, [AS_HELP_STRING( + [--enable-fts4], [include fts4 support [default=yes]])], + [], [enable_fts4=yes]) +AC_MSG_CHECKING([FTS4 extension]) +if test x"$enable_fts4" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_FTS4" + AC_MSG_RESULT([enabled]) +else + AC_MSG_RESULT([disabled]) +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-fts3 +# +AC_ARG_ENABLE(fts3, [AS_HELP_STRING( + [--enable-fts3], [include fts3 support [default=no]])], + [], []) +AC_MSG_CHECKING([FTS3 extension]) +if test x"$enable_fts3" = "xyes" -a x"$enable_fts4" = "xno"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_FTS3" + AC_MSG_RESULT([enabled]) +else + AC_MSG_RESULT([disabled]) +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-fts5 +# +AC_ARG_ENABLE(fts5, [AS_HELP_STRING( + [--enable-fts5], [include fts5 support [default=yes]])], + [], [enable_fts5=yes]) +AC_MSG_CHECKING([FTS5 extension]) +if test x"$enable_fts5" = "xyes"; then + AC_MSG_RESULT([enabled]) + AC_SEARCH_LIBS(log, m) + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_FTS5" +else + AC_MSG_RESULT([disabled]) +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-rtree +# +AC_ARG_ENABLE(rtree, [AS_HELP_STRING( + [--enable-rtree], [include rtree support [default=yes]])], + [], [enable_rtree=yes]) +AC_MSG_CHECKING([RTREE extension]) +if test x"$enable_rtree" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_GEOPOLY" + AC_MSG_RESULT([enabled]) +else + AC_MSG_RESULT([disabled]) +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-session +# +AC_ARG_ENABLE(session, [AS_HELP_STRING( + [--enable-session], [enable the session extension [default=no]])], + [], []) +AC_MSG_CHECKING([Session extension]) +if test x"$enable_session" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK" + AC_MSG_RESULT([enabled]) +else + AC_MSG_RESULT([disabled]) +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-debug +# +AC_ARG_ENABLE(debug, [AS_HELP_STRING( + [--enable-debug], [build with debugging features enabled [default=no]])], + [], []) +AC_MSG_CHECKING([Build type]) +if test x"$enable_debug" = "xyes"; then + BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_DEBUG -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE" + CFLAGS="-g -O0" + AC_MSG_RESULT([debug]) +else + AC_MSG_RESULT([release]) +fi +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# --enable-static-shell +# +AC_ARG_ENABLE(static-shell, [AS_HELP_STRING( + [--enable-static-shell], + [statically link libsqlite3 into shell tool [default=yes]])], + [], [enable_static_shell=yes]) +if test x"$enable_static_shell" = "xyes"; then + EXTRA_SHELL_OBJ=sqlite3-sqlite3.$OBJEXT +else + EXTRA_SHELL_OBJ=libsqlite3.la +fi +AC_SUBST(EXTRA_SHELL_OBJ) +#----------------------------------------------------------------------- + +AC_CHECK_FUNCS(posix_fallocate) +AC_CHECK_HEADERS(zlib.h,[ + AC_SEARCH_LIBS(deflate,z,[BUILD_CFLAGS="$BUILD_CFLAGS -DSQLITE_HAVE_ZLIB"]) +]) + +AC_SEARCH_LIBS(system,,,[SHELL_CFLAGS="-DSQLITE_NOHAVE_SYSTEM"]) +AC_SUBST(SHELL_CFLAGS) + +#----------------------------------------------------------------------- +# UPDATE: Maybe it's better if users just set CFLAGS before invoking +# configure. This option doesn't really add much... +# +# --enable-tempstore +# +# AC_ARG_ENABLE(tempstore, [AS_HELP_STRING( +# [--enable-tempstore], +# [in-memory temporary tables (never, no, yes, always) [default=no]])], +# [], [enable_tempstore=no]) +# AC_MSG_CHECKING([for whether or not to store temp tables in-memory]) +# case "$enable_tempstore" in +# never ) TEMP_STORE=0 ;; +# no ) TEMP_STORE=1 ;; +# always ) TEMP_STORE=3 ;; +# yes ) TEMP_STORE=3 ;; +# * ) +# TEMP_STORE=1 +# enable_tempstore=yes +# ;; +# esac +# AC_MSG_RESULT($enable_tempstore) +# AC_SUBST(TEMP_STORE) +#----------------------------------------------------------------------- + +AC_OUTPUT diff --git a/SQLITE/depcomp b/SQLITE/depcomp new file mode 100755 index 0000000..fc98710 --- /dev/null +++ b/SQLITE/depcomp @@ -0,0 +1,791 @@ +#! /bin/sh +# depcomp - compile a program generating dependencies as side-effects + +scriptversion=2013-05-30.07; # UTC + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>. + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: depcomp [--help] [--version] PROGRAM [ARGS] + +Run PROGRAMS ARGS to compile a file, generating dependencies +as side-effects. + +Environment variables: + depmode Dependency tracking mode. + source Source file read by 'PROGRAMS ARGS'. + object Object file output by 'PROGRAMS ARGS'. + DEPDIR directory where to store dependencies. + depfile Dependency file to output. + tmpdepfile Temporary file to use when outputting dependencies. + libtool Whether libtool is used (yes/no). + +Report bugs to <bug-automake@gnu.org>. +EOF + exit $? + ;; + -v | --v*) + echo "depcomp $scriptversion" + exit $? + ;; +esac + +# Get the directory component of the given path, and save it in the +# global variables '$dir'. Note that this directory component will +# be either empty or ending with a '/' character. This is deliberate. +set_dir_from () +{ + case $1 in + */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; + *) dir=;; + esac +} + +# Get the suffix-stripped basename of the given path, and save it the +# global variable '$base'. +set_base_from () +{ + base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` +} + +# If no dependency file was actually created by the compiler invocation, +# we still have to create a dummy depfile, to avoid errors with the +# Makefile "include basename.Plo" scheme. +make_dummy_depfile () +{ + echo "#dummy" > "$depfile" +} + +# Factor out some common post-processing of the generated depfile. +# Requires the auxiliary global variable '$tmpdepfile' to be set. +aix_post_process_depfile () +{ + # If the compiler actually managed to produce a dependency file, + # post-process it. + if test -f "$tmpdepfile"; then + # Each line is of the form 'foo.o: dependency.h'. + # Do two passes, one to just change these to + # $object: dependency.h + # and one to simply output + # dependency.h: + # which is needed to avoid the deleted-header problem. + { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" + sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" + } > "$depfile" + rm -f "$tmpdepfile" + else + make_dummy_depfile + fi +} + +# A tabulation character. +tab=' ' +# A newline character. +nl=' +' +# Character ranges might be problematic outside the C locale. +# These definitions help. +upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ +lower=abcdefghijklmnopqrstuvwxyz +digits=0123456789 +alpha=${upper}${lower} + +if test -z "$depmode" || test -z "$source" || test -z "$object"; then + echo "depcomp: Variables source, object and depmode must be set" 1>&2 + exit 1 +fi + +# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. +depfile=${depfile-`echo "$object" | + sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} +tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} + +rm -f "$tmpdepfile" + +# Avoid interferences from the environment. +gccflag= dashmflag= + +# Some modes work just like other modes, but use different flags. We +# parameterize here, but still list the modes in the big case below, +# to make depend.m4 easier to write. Note that we *cannot* use a case +# here, because this file can only contain one case statement. +if test "$depmode" = hp; then + # HP compiler uses -M and no extra arg. + gccflag=-M + depmode=gcc +fi + +if test "$depmode" = dashXmstdout; then + # This is just like dashmstdout with a different argument. + dashmflag=-xM + depmode=dashmstdout +fi + +cygpath_u="cygpath -u -f -" +if test "$depmode" = msvcmsys; then + # This is just like msvisualcpp but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvisualcpp +fi + +if test "$depmode" = msvc7msys; then + # This is just like msvc7 but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvc7 +fi + +if test "$depmode" = xlc; then + # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. + gccflag=-qmakedep=gcc,-MF + depmode=gcc +fi + +case "$depmode" in +gcc3) +## gcc 3 implements dependency tracking that does exactly what +## we want. Yay! Note: for some reason libtool 1.4 doesn't like +## it if -MD -MP comes after the -MF stuff. Hmm. +## Unfortunately, FreeBSD c89 acceptance of flags depends upon +## the command line argument order; so add the flags where they +## appear in depend2.am. Note that the slowdown incurred here +## affects only configure: in makefiles, %FASTDEP% shortcuts this. + for arg + do + case $arg in + -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; + *) set fnord "$@" "$arg" ;; + esac + shift # fnord + shift # $arg + done + "$@" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + mv "$tmpdepfile" "$depfile" + ;; + +gcc) +## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. +## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. +## (see the conditional assignment to $gccflag above). +## There are various ways to get dependency output from gcc. Here's +## why we pick this rather obscure method: +## - Don't want to use -MD because we'd like the dependencies to end +## up in a subdir. Having to rename by hand is ugly. +## (We might end up doing this anyway to support other compilers.) +## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like +## -MM, not -M (despite what the docs say). Also, it might not be +## supported by the other compilers which use the 'gcc' depmode. +## - Using -M directly means running the compiler twice (even worse +## than renaming). + if test -z "$gccflag"; then + gccflag=-MD, + fi + "$@" -Wp,"$gccflag$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The second -e expression handles DOS-style file names with drive + # letters. + sed -e 's/^[^:]*: / /' \ + -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" +## This next piece of magic avoids the "deleted header file" problem. +## The problem is that when a header file which appears in a .P file +## is deleted, the dependency causes make to die (because there is +## typically no way to rebuild the header). We avoid this by adding +## dummy dependencies for each header file. Too bad gcc doesn't do +## this for us directly. +## Some versions of gcc put a space before the ':'. On the theory +## that the space means something, we add a space to the output as +## well. hp depmode also adds that space, but also prefixes the VPATH +## to the object. Take care to not repeat it in the output. +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +sgi) + if test "$libtool" = yes; then + "$@" "-Wp,-MDupdate,$tmpdepfile" + else + "$@" -MDupdate "$tmpdepfile" + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + + if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files + echo "$object : \\" > "$depfile" + # Clip off the initial element (the dependent). Don't try to be + # clever and replace this with sed code, as IRIX sed won't handle + # lines with more than a fixed number of characters (4096 in + # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; + # the IRIX cc adds comments like '#:fec' to the end of the + # dependency line. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ + | tr "$nl" ' ' >> "$depfile" + echo >> "$depfile" + # The second pass generates a dummy entry for each header file. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ + >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" + ;; + +xlc) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +aix) + # The C for AIX Compiler uses -M and outputs the dependencies + # in a .u file. In older versions, this file always lives in the + # current directory. Also, the AIX compiler puts '$object:' at the + # start of each line; $object doesn't have directory information. + # Version 6 uses the directory in both cases. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.u + tmpdepfile2=$base.u + tmpdepfile3=$dir.libs/$base.u + "$@" -Wc,-M + else + tmpdepfile1=$dir$base.u + tmpdepfile2=$dir$base.u + tmpdepfile3=$dir$base.u + "$@" -M + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + aix_post_process_depfile + ;; + +tcc) + # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 + # FIXME: That version still under development at the moment of writing. + # Make that this statement remains true also for stable, released + # versions. + # It will wrap lines (doesn't matter whether long or short) with a + # trailing '\', as in: + # + # foo.o : \ + # foo.c \ + # foo.h \ + # + # It will put a trailing '\' even on the last line, and will use leading + # spaces rather than leading tabs (at least since its commit 0394caf7 + # "Emit spaces for -MD"). + "$@" -MD -MF "$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. + # We have to change lines of the first kind to '$object: \'. + sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" + # And for each line of the second kind, we have to emit a 'dep.h:' + # dummy dependency, to avoid the deleted-header problem. + sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" + rm -f "$tmpdepfile" + ;; + +## The order of this option in the case statement is important, since the +## shell code in configure will try each of these formats in the order +## listed in this file. A plain '-MD' option would be understood by many +## compilers, so we must ensure this comes after the gcc and icc options. +pgcc) + # Portland's C compiler understands '-MD'. + # Will always output deps to 'file.d' where file is the root name of the + # source file under compilation, even if file resides in a subdirectory. + # The object file name does not affect the name of the '.d' file. + # pgcc 10.2 will output + # foo.o: sub/foo.c sub/foo.h + # and will wrap long lines using '\' : + # foo.o: sub/foo.c ... \ + # sub/foo.h ... \ + # ... + set_dir_from "$object" + # Use the source, not the object, to determine the base name, since + # that's sadly what pgcc will do too. + set_base_from "$source" + tmpdepfile=$base.d + + # For projects that build the same source file twice into different object + # files, the pgcc approach of using the *source* file root name can cause + # problems in parallel builds. Use a locking strategy to avoid stomping on + # the same $tmpdepfile. + lockdir=$base.d-lock + trap " + echo '$0: caught signal, cleaning up...' >&2 + rmdir '$lockdir' + exit 1 + " 1 2 13 15 + numtries=100 + i=$numtries + while test $i -gt 0; do + # mkdir is a portable test-and-set. + if mkdir "$lockdir" 2>/dev/null; then + # This process acquired the lock. + "$@" -MD + stat=$? + # Release the lock. + rmdir "$lockdir" + break + else + # If the lock is being held by a different process, wait + # until the winning process is done or we timeout. + while test -d "$lockdir" && test $i -gt 0; do + sleep 1 + i=`expr $i - 1` + done + fi + i=`expr $i - 1` + done + trap - 1 2 13 15 + if test $i -le 0; then + echo "$0: failed to acquire lock after $numtries attempts" >&2 + echo "$0: check lockdir '$lockdir'" >&2 + exit 1 + fi + + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each line is of the form `foo.o: dependent.h', + # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. + # Do two passes, one to just change these to + # `$object: dependent.h' and one to simply `dependent.h:'. + sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp2) + # The "hp" stanza above does not work with aCC (C++) and HP's ia64 + # compilers, which have integrated preprocessors. The correct option + # to use with these is +Maked; it writes dependencies to a file named + # 'foo.d', which lands next to the object file, wherever that + # happens to be. + # Much of this is similar to the tru64 case; see comments there. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir.libs/$base.d + "$@" -Wc,+Maked + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + "$@" +Maked + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" + do + test -f "$tmpdepfile" && break + done + if test -f "$tmpdepfile"; then + sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" + # Add 'dependent.h:' lines. + sed -ne '2,${ + s/^ *// + s/ \\*$// + s/$/:/ + p + }' "$tmpdepfile" >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" "$tmpdepfile2" + ;; + +tru64) + # The Tru64 compiler uses -MD to generate dependencies as a side + # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. + # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put + # dependencies in 'foo.d' instead, so we check for that too. + # Subdirectories are respected. + set_dir_from "$object" + set_base_from "$object" + + if test "$libtool" = yes; then + # Libtool generates 2 separate objects for the 2 libraries. These + # two compilations output dependencies in $dir.libs/$base.o.d and + # in $dir$base.o.d. We have to check for both files, because + # one of the two compilations can be disabled. We should prefer + # $dir$base.o.d over $dir.libs/$base.o.d because the latter is + # automatically cleaned when .libs/ is deleted, while ignoring + # the former would cause a distcleancheck panic. + tmpdepfile1=$dir$base.o.d # libtool 1.5 + tmpdepfile2=$dir.libs/$base.o.d # Likewise. + tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 + "$@" -Wc,-MD + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + tmpdepfile3=$dir$base.d + "$@" -MD + fi + + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + # Same post-processing that is required for AIX mode. + aix_post_process_depfile + ;; + +msvc7) + if test "$libtool" = yes; then + showIncludes=-Wc,-showIncludes + else + showIncludes=-showIncludes + fi + "$@" $showIncludes > "$tmpdepfile" + stat=$? + grep -v '^Note: including file: ' "$tmpdepfile" + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The first sed program below extracts the file names and escapes + # backslashes for cygpath. The second sed program outputs the file + # name when reading, but also accumulates all include files in the + # hold buffer in order to output them again at the end. This only + # works with sed implementations that can handle large buffers. + sed < "$tmpdepfile" -n ' +/^Note: including file: *\(.*\)/ { + s//\1/ + s/\\/\\\\/g + p +}' | $cygpath_u | sort -u | sed -n ' +s/ /\\ /g +s/\(.*\)/'"$tab"'\1 \\/p +s/.\(.*\) \\/\1:/ +H +$ { + s/.*/'"$tab"'/ + G + p +}' >> "$depfile" + echo >> "$depfile" # make sure the fragment doesn't end with a backslash + rm -f "$tmpdepfile" + ;; + +msvc7msys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +#nosideeffect) + # This comment above is used by automake to tell side-effect + # dependency tracking mechanisms from slower ones. + +dashmstdout) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout, regardless of -o. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + test -z "$dashmflag" && dashmflag=-M + # Require at least two characters before searching for ':' + # in the target name. This is to cope with DOS-style filenames: + # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. + "$@" $dashmflag | + sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this sed invocation + # correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +dashXmstdout) + # This case only exists to satisfy depend.m4. It is never actually + # run, as this mode is specially recognized in the preamble. + exit 1 + ;; + +makedepend) + "$@" || exit $? + # Remove any Libtool call + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + # X makedepend + shift + cleared=no eat=no + for arg + do + case $cleared in + no) + set ""; shift + cleared=yes ;; + esac + if test $eat = yes; then + eat=no + continue + fi + case "$arg" in + -D*|-I*) + set fnord "$@" "$arg"; shift ;; + # Strip any option that makedepend may not understand. Remove + # the object too, otherwise makedepend will parse it as a source file. + -arch) + eat=yes ;; + -*|$object) + ;; + *) + set fnord "$@" "$arg"; shift ;; + esac + done + obj_suffix=`echo "$object" | sed 's/^.*\././'` + touch "$tmpdepfile" + ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" + rm -f "$depfile" + # makedepend may prepend the VPATH from the source file name to the object. + # No need to regex-escape $object, excess matching of '.' is harmless. + sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process the last invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed '1,2d' "$tmpdepfile" \ + | tr ' ' "$nl" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" "$tmpdepfile".bak + ;; + +cpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + "$@" -E \ + | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + | sed '$ s: \\$::' > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + cat < "$tmpdepfile" >> "$depfile" + sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvisualcpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + IFS=" " + for arg + do + case "$arg" in + -o) + shift + ;; + $object) + shift + ;; + "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") + set fnord "$@" + shift + shift + ;; + *) + set fnord "$@" "$arg" + shift + shift + ;; + esac + done + "$@" -E 2>/dev/null | + sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" + echo "$tab" >> "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvcmsys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +none) + exec "$@" + ;; + +*) + echo "Unknown depmode $depmode" 1>&2 + exit 1 + ;; +esac + +exit 0 + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/SQLITE/install-sh b/SQLITE/install-sh new file mode 100755 index 0000000..59990a1 --- /dev/null +++ b/SQLITE/install-sh @@ -0,0 +1,508 @@ +#!/bin/sh +# install - install a program, script, or datafile + +scriptversion=2014-09-12.12; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# 'make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. + +tab=' ' +nl=' +' +IFS=" $tab$nl" + +# Set DOITPROG to "echo" to test this script. + +doit=${DOITPROG-} +doit_exec=${doit:-exec} + +# Put in absolute file names if you don't have them in your path; +# or use environment vars. + +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} + +posix_mkdir= + +# Desired mode of installed file. +mode=0755 + +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog +rmcmd="$rmprog -f" +stripcmd= + +src= +dst= +dir_arg= +dst_arg= + +copy_on_change=false +is_target_a_directory=possibly + +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... + +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. + +Options: + --help display this help and exit. + --version display version info and exit. + + -c (ignored) + -C install only if different (preserve the last data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -s $stripprog installed files. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. + +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG +" + +while test $# -ne 0; do + case $1 in + -c) ;; + + -C) copy_on_change=true;; + + -d) dir_arg=true;; + + -g) chgrpcmd="$chgrpprog $2" + shift;; + + --help) echo "$usage"; exit $?;; + + -m) mode=$2 + case $mode in + *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; + + -o) chowncmd="$chownprog $2" + shift;; + + -s) stripcmd=$stripprog;; + + -t) + is_target_a_directory=always + dst_arg=$2 + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + shift;; + + -T) is_target_a_directory=never;; + + --version) echo "$0 $scriptversion"; exit $?;; + + --) shift + break;; + + -*) echo "$0: invalid option: $1" >&2 + exit 1;; + + *) break;; + esac + shift +done + +# We allow the use of options -d and -T together, by making -d +# take the precedence; this is for compatibility with GNU install. + +if test -n "$dir_arg"; then + if test -n "$dst_arg"; then + echo "$0: target directory not allowed when installing a directory." >&2 + exit 1 + fi +fi + +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + done +fi + +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call 'install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + if test $# -gt 1 || test "$is_target_a_directory" = always; then + if test ! -d "$dst_arg"; then + echo "$0: $dst_arg: Is not a directory." >&2 + exit 1 + fi + fi +fi + +if test -z "$dir_arg"; then + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi + +for src +do + # Protect names problematic for 'test' and other utilities. + case $src in + -* | [=\(\)!]) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + dst=$dst_arg + + # If destination is a directory, append the input filename; won't work + # if double slashes aren't ignored. + if test -d "$dst"; then + if test "$is_target_a_directory" = never; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dst=$dstdir/`basename "$src"` + dstdir_status=0 + else + dstdir=`dirname "$dst"` + test -d "$dstdir" + dstdir_status=$? + fi + fi + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # Create intermediate dirs using mode 755 as modified by the umask. + # This is like FreeBSD 'install' as of 1997-10-28. + umask=`umask` + case $stripcmd.$umask in + # Optimize common cases. + *[2367][2367]) mkdir_umask=$umask;; + .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; + + *[0-7]) + mkdir_umask=`expr $umask + 22 \ + - $umask % 100 % 40 + $umask % 20 \ + - $umask % 10 % 4 + $umask % 2 + `;; + *) mkdir_umask=$umask,go-w;; + esac + + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + case $umask in + *[123567][0-7][0-7]) + # POSIX mkdir -p sets u+wx bits regardless of umask, which + # is incompatible with FreeBSD 'install' when (umask & 300) != 0. + ;; + *) + # $RANDOM is not portable (e.g. dash); use it when possible to + # lower collision chance + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0 + + # As "mkdir -p" follows symlinks and we work in /tmp possibly; so + # create the $tmpdir first (and fail if unsuccessful) to make sure + # that nobody tries to guess the $tmpdir name. + if (umask $mkdir_umask && + $mkdirprog $mkdir_mode "$tmpdir" && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + test_tmpdir="$tmpdir/a" + ls_ld_tmpdir=`ls -ld "$test_tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null + fi + trap '' 0;; + esac;; + esac + + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # The umask is ridiculous, or mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + [-=\(\)!]*) prefix='./';; + *) prefix='';; + esac + + oIFS=$IFS + IFS=/ + set -f + set fnord $dstdir + shift + set +f + IFS=$oIFS + + prefixes= + + for d + do + test X"$d" = X && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask=$mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=$dstdir/_inst.$$_ + rmtmp=$dstdir/_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + set +f && + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd -f "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/SQLITE/ltmain.sh b/SQLITE/ltmain.sh new file mode 100644 index 0000000..147d758 --- /dev/null +++ b/SQLITE/ltmain.sh @@ -0,0 +1,11156 @@ +#! /bin/sh +## DO NOT EDIT - This file generated from ./build-aux/ltmain.in +## by inline-source v2014-01-03.01 + +# libtool (GNU libtool) 2.4.6 +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996 + +# Copyright (C) 1996-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, +# if you distribute this file as part of a program or library that +# is built using GNU Libtool, you may include this file under the +# same distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +PROGRAM=libtool +PACKAGE=libtool +VERSION="2.4.6 Debian-2.4.6-0.1" +package_revision=2.4.6 + + +## ------ ## +## Usage. ## +## ------ ## + +# Run './libtool --help' for help with using this script from the +# command line. + + +## ------------------------------- ## +## User overridable command paths. ## +## ------------------------------- ## + +# After configure completes, it has a better idea of some of the +# shell tools we need than the defaults used by the functions shared +# with bootstrap, so set those here where they can still be over- +# ridden by the user, but otherwise take precedence. + +: ${AUTOCONF="autoconf"} +: ${AUTOMAKE="automake"} + + +## -------------------------- ## +## Source external libraries. ## +## -------------------------- ## + +# Much of our low-level functionality needs to be sourced from external +# libraries, which are installed to $pkgauxdir. + +# Set a version string for this script. +scriptversion=2015-01-20.17; # UTC + +# General shell script boiler plate, and helper functions. +# Written by Gary V. Vaughan, 2004 + +# Copyright (C) 2004-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +# As a special exception to the GNU General Public License, if you distribute +# this file as part of a program or library that is built using GNU Libtool, +# you may include this file under the same distribution terms that you use +# for the rest of that program. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNES FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Please report bugs or propose patches to gary@gnu.org. + + +## ------ ## +## Usage. ## +## ------ ## + +# Evaluate this file near the top of your script to gain access to +# the functions and variables defined here: +# +# . `echo "$0" | ${SED-sed} 's|[^/]*$||'`/build-aux/funclib.sh +# +# If you need to override any of the default environment variable +# settings, do that before evaluating this file. + + +## -------------------- ## +## Shell normalisation. ## +## -------------------- ## + +# Some shells need a little help to be as Bourne compatible as possible. +# Before doing anything else, make sure all that help has been provided! + +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac +fi + +# NLS nuisances: We save the old values in case they are required later. +_G_user_locale= +_G_safe_locale= +for _G_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES +do + eval "if test set = \"\${$_G_var+set}\"; then + save_$_G_var=\$$_G_var + $_G_var=C + export $_G_var + _G_user_locale=\"$_G_var=\\\$save_\$_G_var; \$_G_user_locale\" + _G_safe_locale=\"$_G_var=C; \$_G_safe_locale\" + fi" +done + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Make sure IFS has a sensible default +sp=' ' +nl=' +' +IFS="$sp $nl" + +# There are apparently some retarded systems that use ';' as a PATH separator! +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + + +## ------------------------- ## +## Locate command utilities. ## +## ------------------------- ## + + +# func_executable_p FILE +# ---------------------- +# Check that FILE is an executable regular file. +func_executable_p () +{ + test -f "$1" && test -x "$1" +} + + +# func_path_progs PROGS_LIST CHECK_FUNC [PATH] +# -------------------------------------------- +# Search for either a program that responds to --version with output +# containing "GNU", or else returned by CHECK_FUNC otherwise, by +# trying all the directories in PATH with each of the elements of +# PROGS_LIST. +# +# CHECK_FUNC should accept the path to a candidate program, and +# set $func_check_prog_result if it truncates its output less than +# $_G_path_prog_max characters. +func_path_progs () +{ + _G_progs_list=$1 + _G_check_func=$2 + _G_PATH=${3-"$PATH"} + + _G_path_prog_max=0 + _G_path_prog_found=false + _G_save_IFS=$IFS; IFS=${PATH_SEPARATOR-:} + for _G_dir in $_G_PATH; do + IFS=$_G_save_IFS + test -z "$_G_dir" && _G_dir=. + for _G_prog_name in $_G_progs_list; do + for _exeext in '' .EXE; do + _G_path_prog=$_G_dir/$_G_prog_name$_exeext + func_executable_p "$_G_path_prog" || continue + case `"$_G_path_prog" --version 2>&1` in + *GNU*) func_path_progs_result=$_G_path_prog _G_path_prog_found=: ;; + *) $_G_check_func $_G_path_prog + func_path_progs_result=$func_check_prog_result + ;; + esac + $_G_path_prog_found && break 3 + done + done + done + IFS=$_G_save_IFS + test -z "$func_path_progs_result" && { + echo "no acceptable sed could be found in \$PATH" >&2 + exit 1 + } +} + + +# We want to be able to use the functions in this file before configure +# has figured out where the best binaries are kept, which means we have +# to search for them ourselves - except when the results are already set +# where we skip the searches. + +# Unless the user overrides by setting SED, search the path for either GNU +# sed, or the sed that truncates its output the least. +test -z "$SED" && { + _G_sed_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for _G_i in 1 2 3 4 5 6 7; do + _G_sed_script=$_G_sed_script$nl$_G_sed_script + done + echo "$_G_sed_script" 2>/dev/null | sed 99q >conftest.sed + _G_sed_script= + + func_check_prog_sed () + { + _G_path_prog=$1 + + _G_count=0 + printf 0123456789 >conftest.in + while : + do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo '' >> conftest.nl + "$_G_path_prog" -f conftest.sed <conftest.nl >conftest.out 2>/dev/null || break + diff conftest.out conftest.nl >/dev/null 2>&1 || break + _G_count=`expr $_G_count + 1` + if test "$_G_count" -gt "$_G_path_prog_max"; then + # Best one so far, save it but keep looking for a better one + func_check_prog_result=$_G_path_prog + _G_path_prog_max=$_G_count + fi + # 10*(2^10) chars as input seems more than enough + test 10 -lt "$_G_count" && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out + } + + func_path_progs "sed gsed" func_check_prog_sed $PATH:/usr/xpg4/bin + rm -f conftest.sed + SED=$func_path_progs_result +} + + +# Unless the user overrides by setting GREP, search the path for either GNU +# grep, or the grep that truncates its output the least. +test -z "$GREP" && { + func_check_prog_grep () + { + _G_path_prog=$1 + + _G_count=0 + _G_path_prog_max=0 + printf 0123456789 >conftest.in + while : + do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo 'GREP' >> conftest.nl + "$_G_path_prog" -e 'GREP$' -e '-(cannot match)-' <conftest.nl >conftest.out 2>/dev/null || break + diff conftest.out conftest.nl >/dev/null 2>&1 || break + _G_count=`expr $_G_count + 1` + if test "$_G_count" -gt "$_G_path_prog_max"; then + # Best one so far, save it but keep looking for a better one + func_check_prog_result=$_G_path_prog + _G_path_prog_max=$_G_count + fi + # 10*(2^10) chars as input seems more than enough + test 10 -lt "$_G_count" && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out + } + + func_path_progs "grep ggrep" func_check_prog_grep $PATH:/usr/xpg4/bin + GREP=$func_path_progs_result +} + + +## ------------------------------- ## +## User overridable command paths. ## +## ------------------------------- ## + +# All uppercase variable names are used for environment variables. These +# variables can be overridden by the user before calling a script that +# uses them if a suitable command of that name is not already available +# in the command search PATH. + +: ${CP="cp -f"} +: ${ECHO="printf %s\n"} +: ${EGREP="$GREP -E"} +: ${FGREP="$GREP -F"} +: ${LN_S="ln -s"} +: ${MAKE="make"} +: ${MKDIR="mkdir"} +: ${MV="mv -f"} +: ${RM="rm -f"} +: ${SHELL="${CONFIG_SHELL-/bin/sh}"} + + +## -------------------- ## +## Useful sed snippets. ## +## -------------------- ## + +sed_dirname='s|/[^/]*$||' +sed_basename='s|^.*/||' + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='s|\([`"$\\]\)|\\\1|g' + +# Same as above, but do not quote variable references. +sed_double_quote_subst='s/\(["`\\]\)/\\\1/g' + +# Sed substitution that turns a string into a regex matching for the +# string literally. +sed_make_literal_regex='s|[].[^$\\*\/]|\\&|g' + +# Sed substitution that converts a w32 file name or path +# that contains forward slashes, into one that contains +# (escaped) backslashes. A very naive implementation. +sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g' + +# Re-'\' parameter expansions in output of sed_double_quote_subst that +# were '\'-ed in input to the same. If an odd number of '\' preceded a +# '$' in input to sed_double_quote_subst, that '$' was protected from +# expansion. Since each input '\' is now two '\'s, look for any number +# of runs of four '\'s followed by two '\'s and then a '$'. '\' that '$'. +_G_bs='\\' +_G_bs2='\\\\' +_G_bs4='\\\\\\\\' +_G_dollar='\$' +sed_double_backslash="\ + s/$_G_bs4/&\\ +/g + s/^$_G_bs2$_G_dollar/$_G_bs&/ + s/\\([^$_G_bs]\\)$_G_bs2$_G_dollar/\\1$_G_bs2$_G_bs$_G_dollar/g + s/\n//g" + + +## ----------------- ## +## Global variables. ## +## ----------------- ## + +# Except for the global variables explicitly listed below, the following +# functions in the '^func_' namespace, and the '^require_' namespace +# variables initialised in the 'Resource management' section, sourcing +# this file will not pollute your global namespace with anything +# else. There's no portable way to scope variables in Bourne shell +# though, so actually running these functions will sometimes place +# results into a variable named after the function, and often use +# temporary variables in the '^_G_' namespace. If you are careful to +# avoid using those namespaces casually in your sourcing script, things +# should continue to work as you expect. And, of course, you can freely +# overwrite any of the functions or variables defined here before +# calling anything to customize them. + +EXIT_SUCCESS=0 +EXIT_FAILURE=1 +EXIT_MISMATCH=63 # $? = 63 is used to indicate version mismatch to missing. +EXIT_SKIP=77 # $? = 77 is used to indicate a skipped test to automake. + +# Allow overriding, eg assuming that you follow the convention of +# putting '$debug_cmd' at the start of all your functions, you can get +# bash to show function call trace with: +# +# debug_cmd='eval echo "${FUNCNAME[0]} $*" >&2' bash your-script-name +debug_cmd=${debug_cmd-":"} +exit_cmd=: + +# By convention, finish your script with: +# +# exit $exit_status +# +# so that you can set exit_status to non-zero if you want to indicate +# something went wrong during execution without actually bailing out at +# the point of failure. +exit_status=$EXIT_SUCCESS + +# Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh +# is ksh but when the shell is invoked as "sh" and the current value of +# the _XPG environment variable is not equal to 1 (one), the special +# positional parameter $0, within a function call, is the name of the +# function. +progpath=$0 + +# The name of this program. +progname=`$ECHO "$progpath" |$SED "$sed_basename"` + +# Make sure we have an absolute progpath for reexecution: +case $progpath in + [\\/]*|[A-Za-z]:\\*) ;; + *[\\/]*) + progdir=`$ECHO "$progpath" |$SED "$sed_dirname"` + progdir=`cd "$progdir" && pwd` + progpath=$progdir/$progname + ;; + *) + _G_IFS=$IFS + IFS=${PATH_SEPARATOR-:} + for progdir in $PATH; do + IFS=$_G_IFS + test -x "$progdir/$progname" && break + done + IFS=$_G_IFS + test -n "$progdir" || progdir=`pwd` + progpath=$progdir/$progname + ;; +esac + + +## ----------------- ## +## Standard options. ## +## ----------------- ## + +# The following options affect the operation of the functions defined +# below, and should be set appropriately depending on run-time para- +# meters passed on the command line. + +opt_dry_run=false +opt_quiet=false +opt_verbose=false + +# Categories 'all' and 'none' are always available. Append any others +# you will pass as the first argument to func_warning from your own +# code. +warning_categories= + +# By default, display warnings according to 'opt_warning_types'. Set +# 'warning_func' to ':' to elide all warnings, or func_fatal_error to +# treat the next displayed warning as a fatal error. +warning_func=func_warn_and_continue + +# Set to 'all' to display all warnings, 'none' to suppress all +# warnings, or a space delimited list of some subset of +# 'warning_categories' to display only the listed warnings. +opt_warning_types=all + + +## -------------------- ## +## Resource management. ## +## -------------------- ## + +# This section contains definitions for functions that each ensure a +# particular resource (a file, or a non-empty configuration variable for +# example) is available, and if appropriate to extract default values +# from pertinent package files. Call them using their associated +# 'require_*' variable to ensure that they are executed, at most, once. +# +# It's entirely deliberate that calling these functions can set +# variables that don't obey the namespace limitations obeyed by the rest +# of this file, in order that that they be as useful as possible to +# callers. + + +# require_term_colors +# ------------------- +# Allow display of bold text on terminals that support it. +require_term_colors=func_require_term_colors +func_require_term_colors () +{ + $debug_cmd + + test -t 1 && { + # COLORTERM and USE_ANSI_COLORS environment variables take + # precedence, because most terminfo databases neglect to describe + # whether color sequences are supported. + test -n "${COLORTERM+set}" && : ${USE_ANSI_COLORS="1"} + + if test 1 = "$USE_ANSI_COLORS"; then + # Standard ANSI escape sequences + tc_reset='' + tc_bold=''; tc_standout='' + tc_red=''; tc_green='' + tc_blue=''; tc_cyan='' + else + # Otherwise trust the terminfo database after all. + test -n "`tput sgr0 2>/dev/null`" && { + tc_reset=`tput sgr0` + test -n "`tput bold 2>/dev/null`" && tc_bold=`tput bold` + tc_standout=$tc_bold + test -n "`tput smso 2>/dev/null`" && tc_standout=`tput smso` + test -n "`tput setaf 1 2>/dev/null`" && tc_red=`tput setaf 1` + test -n "`tput setaf 2 2>/dev/null`" && tc_green=`tput setaf 2` + test -n "`tput setaf 4 2>/dev/null`" && tc_blue=`tput setaf 4` + test -n "`tput setaf 5 2>/dev/null`" && tc_cyan=`tput setaf 5` + } + fi + } + + require_term_colors=: +} + + +## ----------------- ## +## Function library. ## +## ----------------- ## + +# This section contains a variety of useful functions to call in your +# scripts. Take note of the portable wrappers for features provided by +# some modern shells, which will fall back to slower equivalents on +# less featureful shells. + + +# func_append VAR VALUE +# --------------------- +# Append VALUE onto the existing contents of VAR. + + # We should try to minimise forks, especially on Windows where they are + # unreasonably slow, so skip the feature probes when bash or zsh are + # being used: + if test set = "${BASH_VERSION+set}${ZSH_VERSION+set}"; then + : ${_G_HAVE_ARITH_OP="yes"} + : ${_G_HAVE_XSI_OPS="yes"} + # The += operator was introduced in bash 3.1 + case $BASH_VERSION in + [12].* | 3.0 | 3.0*) ;; + *) + : ${_G_HAVE_PLUSEQ_OP="yes"} + ;; + esac + fi + + # _G_HAVE_PLUSEQ_OP + # Can be empty, in which case the shell is probed, "yes" if += is + # useable or anything else if it does not work. + test -z "$_G_HAVE_PLUSEQ_OP" \ + && (eval 'x=a; x+=" b"; test "a b" = "$x"') 2>/dev/null \ + && _G_HAVE_PLUSEQ_OP=yes + +if test yes = "$_G_HAVE_PLUSEQ_OP" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_append () + { + $debug_cmd + + eval "$1+=\$2" + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_append () + { + $debug_cmd + + eval "$1=\$$1\$2" + } +fi + + +# func_append_quoted VAR VALUE +# ---------------------------- +# Quote VALUE and append to the end of shell variable VAR, separated +# by a space. +if test yes = "$_G_HAVE_PLUSEQ_OP"; then + eval 'func_append_quoted () + { + $debug_cmd + + func_quote_for_eval "$2" + eval "$1+=\\ \$func_quote_for_eval_result" + }' +else + func_append_quoted () + { + $debug_cmd + + func_quote_for_eval "$2" + eval "$1=\$$1\\ \$func_quote_for_eval_result" + } +fi + + +# func_append_uniq VAR VALUE +# -------------------------- +# Append unique VALUE onto the existing contents of VAR, assuming +# entries are delimited by the first character of VALUE. For example: +# +# func_append_uniq options " --another-option option-argument" +# +# will only append to $options if " --another-option option-argument " +# is not already present somewhere in $options already (note spaces at +# each end implied by leading space in second argument). +func_append_uniq () +{ + $debug_cmd + + eval _G_current_value='`$ECHO $'$1'`' + _G_delim=`expr "$2" : '\(.\)'` + + case $_G_delim$_G_current_value$_G_delim in + *"$2$_G_delim"*) ;; + *) func_append "$@" ;; + esac +} + + +# func_arith TERM... +# ------------------ +# Set func_arith_result to the result of evaluating TERMs. + test -z "$_G_HAVE_ARITH_OP" \ + && (eval 'test 2 = $(( 1 + 1 ))') 2>/dev/null \ + && _G_HAVE_ARITH_OP=yes + +if test yes = "$_G_HAVE_ARITH_OP"; then + eval 'func_arith () + { + $debug_cmd + + func_arith_result=$(( $* )) + }' +else + func_arith () + { + $debug_cmd + + func_arith_result=`expr "$@"` + } +fi + + +# func_basename FILE +# ------------------ +# Set func_basename_result to FILE with everything up to and including +# the last / stripped. +if test yes = "$_G_HAVE_XSI_OPS"; then + # If this shell supports suffix pattern removal, then use it to avoid + # forking. Hide the definitions single quotes in case the shell chokes + # on unsupported syntax... + _b='func_basename_result=${1##*/}' + _d='case $1 in + */*) func_dirname_result=${1%/*}$2 ;; + * ) func_dirname_result=$3 ;; + esac' + +else + # ...otherwise fall back to using sed. + _b='func_basename_result=`$ECHO "$1" |$SED "$sed_basename"`' + _d='func_dirname_result=`$ECHO "$1" |$SED "$sed_dirname"` + if test "X$func_dirname_result" = "X$1"; then + func_dirname_result=$3 + else + func_append func_dirname_result "$2" + fi' +fi + +eval 'func_basename () +{ + $debug_cmd + + '"$_b"' +}' + + +# func_dirname FILE APPEND NONDIR_REPLACEMENT +# ------------------------------------------- +# Compute the dirname of FILE. If nonempty, add APPEND to the result, +# otherwise set result to NONDIR_REPLACEMENT. +eval 'func_dirname () +{ + $debug_cmd + + '"$_d"' +}' + + +# func_dirname_and_basename FILE APPEND NONDIR_REPLACEMENT +# -------------------------------------------------------- +# Perform func_basename and func_dirname in a single function +# call: +# dirname: Compute the dirname of FILE. If nonempty, +# add APPEND to the result, otherwise set result +# to NONDIR_REPLACEMENT. +# value returned in "$func_dirname_result" +# basename: Compute filename of FILE. +# value retuned in "$func_basename_result" +# For efficiency, we do not delegate to the functions above but instead +# duplicate the functionality here. +eval 'func_dirname_and_basename () +{ + $debug_cmd + + '"$_b"' + '"$_d"' +}' + + +# func_echo ARG... +# ---------------- +# Echo program name prefixed message. +func_echo () +{ + $debug_cmd + + _G_message=$* + + func_echo_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_IFS + $ECHO "$progname: $_G_line" + done + IFS=$func_echo_IFS +} + + +# func_echo_all ARG... +# -------------------- +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "$*" +} + + +# func_echo_infix_1 INFIX ARG... +# ------------------------------ +# Echo program name, followed by INFIX on the first line, with any +# additional lines not showing INFIX. +func_echo_infix_1 () +{ + $debug_cmd + + $require_term_colors + + _G_infix=$1; shift + _G_indent=$_G_infix + _G_prefix="$progname: $_G_infix: " + _G_message=$* + + # Strip color escape sequences before counting printable length + for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan" + do + test -n "$_G_tc" && { + _G_esc_tc=`$ECHO "$_G_tc" | $SED "$sed_make_literal_regex"` + _G_indent=`$ECHO "$_G_indent" | $SED "s|$_G_esc_tc||g"` + } + done + _G_indent="$progname: "`echo "$_G_indent" | $SED 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes + + func_echo_infix_1_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_infix_1_IFS + $ECHO "$_G_prefix$tc_bold$_G_line$tc_reset" >&2 + _G_prefix=$_G_indent + done + IFS=$func_echo_infix_1_IFS +} + + +# func_error ARG... +# ----------------- +# Echo program name prefixed message to standard error. +func_error () +{ + $debug_cmd + + $require_term_colors + + func_echo_infix_1 " $tc_standout${tc_red}error$tc_reset" "$*" >&2 +} + + +# func_fatal_error ARG... +# ----------------------- +# Echo program name prefixed message to standard error, and exit. +func_fatal_error () +{ + $debug_cmd + + func_error "$*" + exit $EXIT_FAILURE +} + + +# func_grep EXPRESSION FILENAME +# ----------------------------- +# Check whether EXPRESSION matches any line of FILENAME, without output. +func_grep () +{ + $debug_cmd + + $GREP "$1" "$2" >/dev/null 2>&1 +} + + +# func_len STRING +# --------------- +# Set func_len_result to the length of STRING. STRING may not +# start with a hyphen. + test -z "$_G_HAVE_XSI_OPS" \ + && (eval 'x=a/b/c; + test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ + && _G_HAVE_XSI_OPS=yes + +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_len () + { + $debug_cmd + + func_len_result=${#1} + }' +else + func_len () + { + $debug_cmd + + func_len_result=`expr "$1" : ".*" 2>/dev/null || echo $max_cmd_len` + } +fi + + +# func_mkdir_p DIRECTORY-PATH +# --------------------------- +# Make sure the entire path to DIRECTORY-PATH is available. +func_mkdir_p () +{ + $debug_cmd + + _G_directory_path=$1 + _G_dir_list= + + if test -n "$_G_directory_path" && test : != "$opt_dry_run"; then + + # Protect directory names starting with '-' + case $_G_directory_path in + -*) _G_directory_path=./$_G_directory_path ;; + esac + + # While some portion of DIR does not yet exist... + while test ! -d "$_G_directory_path"; do + # ...make a list in topmost first order. Use a colon delimited + # list incase some portion of path contains whitespace. + _G_dir_list=$_G_directory_path:$_G_dir_list + + # If the last portion added has no slash in it, the list is done + case $_G_directory_path in */*) ;; *) break ;; esac + + # ...otherwise throw away the child directory and loop + _G_directory_path=`$ECHO "$_G_directory_path" | $SED -e "$sed_dirname"` + done + _G_dir_list=`$ECHO "$_G_dir_list" | $SED 's|:*$||'` + + func_mkdir_p_IFS=$IFS; IFS=: + for _G_dir in $_G_dir_list; do + IFS=$func_mkdir_p_IFS + # mkdir can fail with a 'File exist' error if two processes + # try to create one of the directories concurrently. Don't + # stop in that case! + $MKDIR "$_G_dir" 2>/dev/null || : + done + IFS=$func_mkdir_p_IFS + + # Bail out if we (or some other process) failed to create a directory. + test -d "$_G_directory_path" || \ + func_fatal_error "Failed to create '$1'" + fi +} + + +# func_mktempdir [BASENAME] +# ------------------------- +# Make a temporary directory that won't clash with other running +# libtool processes, and avoids race conditions if possible. If +# given, BASENAME is the basename for that directory. +func_mktempdir () +{ + $debug_cmd + + _G_template=${TMPDIR-/tmp}/${1-$progname} + + if test : = "$opt_dry_run"; then + # Return a directory name, but don't create it in dry-run mode + _G_tmpdir=$_G_template-$$ + else + + # If mktemp works, use that first and foremost + _G_tmpdir=`mktemp -d "$_G_template-XXXXXXXX" 2>/dev/null` + + if test ! -d "$_G_tmpdir"; then + # Failing that, at least try and use $RANDOM to avoid a race + _G_tmpdir=$_G_template-${RANDOM-0}$$ + + func_mktempdir_umask=`umask` + umask 0077 + $MKDIR "$_G_tmpdir" + umask $func_mktempdir_umask + fi + + # If we're not in dry-run mode, bomb out on failure + test -d "$_G_tmpdir" || \ + func_fatal_error "cannot create temporary directory '$_G_tmpdir'" + fi + + $ECHO "$_G_tmpdir" +} + + +# func_normal_abspath PATH +# ------------------------ +# Remove doubled-up and trailing slashes, "." path components, +# and cancel out any ".." path components in PATH after making +# it an absolute path. +func_normal_abspath () +{ + $debug_cmd + + # These SED scripts presuppose an absolute path with a trailing slash. + _G_pathcar='s|^/\([^/]*\).*$|\1|' + _G_pathcdr='s|^/[^/]*||' + _G_removedotparts=':dotsl + s|/\./|/|g + t dotsl + s|/\.$|/|' + _G_collapseslashes='s|/\{1,\}|/|g' + _G_finalslash='s|/*$|/|' + + # Start from root dir and reassemble the path. + func_normal_abspath_result= + func_normal_abspath_tpath=$1 + func_normal_abspath_altnamespace= + case $func_normal_abspath_tpath in + "") + # Empty path, that just means $cwd. + func_stripname '' '/' "`pwd`" + func_normal_abspath_result=$func_stripname_result + return + ;; + # The next three entries are used to spot a run of precisely + # two leading slashes without using negated character classes; + # we take advantage of case's first-match behaviour. + ///*) + # Unusual form of absolute path, do nothing. + ;; + //*) + # Not necessarily an ordinary path; POSIX reserves leading '//' + # and for example Cygwin uses it to access remote file shares + # over CIFS/SMB, so we conserve a leading double slash if found. + func_normal_abspath_altnamespace=/ + ;; + /*) + # Absolute path, do nothing. + ;; + *) + # Relative path, prepend $cwd. + func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath + ;; + esac + + # Cancel out all the simple stuff to save iterations. We also want + # the path to end with a slash for ease of parsing, so make sure + # there is one (and only one) here. + func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_removedotparts" -e "$_G_collapseslashes" -e "$_G_finalslash"` + while :; do + # Processed it all yet? + if test / = "$func_normal_abspath_tpath"; then + # If we ascended to the root using ".." the result may be empty now. + if test -z "$func_normal_abspath_result"; then + func_normal_abspath_result=/ + fi + break + fi + func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_pathcar"` + func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_pathcdr"` + # Figure out what to do with it + case $func_normal_abspath_tcomponent in + "") + # Trailing empty path component, ignore it. + ;; + ..) + # Parent dir; strip last assembled component from result. + func_dirname "$func_normal_abspath_result" + func_normal_abspath_result=$func_dirname_result + ;; + *) + # Actual path component, append it. + func_append func_normal_abspath_result "/$func_normal_abspath_tcomponent" + ;; + esac + done + # Restore leading double-slash if one was found on entry. + func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result +} + + +# func_notquiet ARG... +# -------------------- +# Echo program name prefixed message only when not in quiet mode. +func_notquiet () +{ + $debug_cmd + + $opt_quiet || func_echo ${1+"$@"} + + # A bug in bash halts the script if the last line of a function + # fails when set -e is in force, so we need another command to + # work around that: + : +} + + +# func_relative_path SRCDIR DSTDIR +# -------------------------------- +# Set func_relative_path_result to the relative path from SRCDIR to DSTDIR. +func_relative_path () +{ + $debug_cmd + + func_relative_path_result= + func_normal_abspath "$1" + func_relative_path_tlibdir=$func_normal_abspath_result + func_normal_abspath "$2" + func_relative_path_tbindir=$func_normal_abspath_result + + # Ascend the tree starting from libdir + while :; do + # check if we have found a prefix of bindir + case $func_relative_path_tbindir in + $func_relative_path_tlibdir) + # found an exact match + func_relative_path_tcancelled= + break + ;; + $func_relative_path_tlibdir*) + # found a matching prefix + func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir" + func_relative_path_tcancelled=$func_stripname_result + if test -z "$func_relative_path_result"; then + func_relative_path_result=. + fi + break + ;; + *) + func_dirname $func_relative_path_tlibdir + func_relative_path_tlibdir=$func_dirname_result + if test -z "$func_relative_path_tlibdir"; then + # Have to descend all the way to the root! + func_relative_path_result=../$func_relative_path_result + func_relative_path_tcancelled=$func_relative_path_tbindir + break + fi + func_relative_path_result=../$func_relative_path_result + ;; + esac + done + + # Now calculate path; take care to avoid doubling-up slashes. + func_stripname '' '/' "$func_relative_path_result" + func_relative_path_result=$func_stripname_result + func_stripname '/' '/' "$func_relative_path_tcancelled" + if test -n "$func_stripname_result"; then + func_append func_relative_path_result "/$func_stripname_result" + fi + + # Normalisation. If bindir is libdir, return '.' else relative path. + if test -n "$func_relative_path_result"; then + func_stripname './' '' "$func_relative_path_result" + func_relative_path_result=$func_stripname_result + fi + + test -n "$func_relative_path_result" || func_relative_path_result=. + + : +} + + +# func_quote_for_eval ARG... +# -------------------------- +# Aesthetically quote ARGs to be evaled later. +# This function returns two values: +# i) func_quote_for_eval_result +# double-quoted, suitable for a subsequent eval +# ii) func_quote_for_eval_unquoted_result +# has all characters that are still active within double +# quotes backslashified. +func_quote_for_eval () +{ + $debug_cmd + + func_quote_for_eval_unquoted_result= + func_quote_for_eval_result= + while test 0 -lt $#; do + case $1 in + *[\\\`\"\$]*) + _G_unquoted_arg=`printf '%s\n' "$1" |$SED "$sed_quote_subst"` ;; + *) + _G_unquoted_arg=$1 ;; + esac + if test -n "$func_quote_for_eval_unquoted_result"; then + func_append func_quote_for_eval_unquoted_result " $_G_unquoted_arg" + else + func_append func_quote_for_eval_unquoted_result "$_G_unquoted_arg" + fi + + case $_G_unquoted_arg in + # Double-quote args containing shell metacharacters to delay + # word splitting, command substitution and variable expansion + # for a subsequent eval. + # Many Bourne shells cannot handle close brackets correctly + # in scan sets, so we specify it separately. + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + _G_quoted_arg=\"$_G_unquoted_arg\" + ;; + *) + _G_quoted_arg=$_G_unquoted_arg + ;; + esac + + if test -n "$func_quote_for_eval_result"; then + func_append func_quote_for_eval_result " $_G_quoted_arg" + else + func_append func_quote_for_eval_result "$_G_quoted_arg" + fi + shift + done +} + + +# func_quote_for_expand ARG +# ------------------------- +# Aesthetically quote ARG to be evaled later; same as above, +# but do not quote variable references. +func_quote_for_expand () +{ + $debug_cmd + + case $1 in + *[\\\`\"]*) + _G_arg=`$ECHO "$1" | $SED \ + -e "$sed_double_quote_subst" -e "$sed_double_backslash"` ;; + *) + _G_arg=$1 ;; + esac + + case $_G_arg in + # Double-quote args containing shell metacharacters to delay + # word splitting and command substitution for a subsequent eval. + # Many Bourne shells cannot handle close brackets correctly + # in scan sets, so we specify it separately. + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + _G_arg=\"$_G_arg\" + ;; + esac + + func_quote_for_expand_result=$_G_arg +} + + +# func_stripname PREFIX SUFFIX NAME +# --------------------------------- +# strip PREFIX and SUFFIX from NAME, and store in func_stripname_result. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_stripname () + { + $debug_cmd + + # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are + # positional parameters, so assign one to ordinary variable first. + func_stripname_result=$3 + func_stripname_result=${func_stripname_result#"$1"} + func_stripname_result=${func_stripname_result%"$2"} + }' +else + func_stripname () + { + $debug_cmd + + case $2 in + .*) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%\\\\$2\$%%"`;; + *) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%$2\$%%"`;; + esac + } +fi + + +# func_show_eval CMD [FAIL_EXP] +# ----------------------------- +# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is +# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP +# is given, then evaluate it. +func_show_eval () +{ + $debug_cmd + + _G_cmd=$1 + _G_fail_exp=${2-':'} + + func_quote_for_expand "$_G_cmd" + eval "func_notquiet $func_quote_for_expand_result" + + $opt_dry_run || { + eval "$_G_cmd" + _G_status=$? + if test 0 -ne "$_G_status"; then + eval "(exit $_G_status); $_G_fail_exp" + fi + } +} + + +# func_show_eval_locale CMD [FAIL_EXP] +# ------------------------------------ +# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is +# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP +# is given, then evaluate it. Use the saved locale for evaluation. +func_show_eval_locale () +{ + $debug_cmd + + _G_cmd=$1 + _G_fail_exp=${2-':'} + + $opt_quiet || { + func_quote_for_expand "$_G_cmd" + eval "func_echo $func_quote_for_expand_result" + } + + $opt_dry_run || { + eval "$_G_user_locale + $_G_cmd" + _G_status=$? + eval "$_G_safe_locale" + if test 0 -ne "$_G_status"; then + eval "(exit $_G_status); $_G_fail_exp" + fi + } +} + + +# func_tr_sh +# ---------- +# Turn $1 into a string suitable for a shell variable name. +# Result is stored in $func_tr_sh_result. All characters +# not in the set a-zA-Z0-9_ are replaced with '_'. Further, +# if $1 begins with a digit, a '_' is prepended as well. +func_tr_sh () +{ + $debug_cmd + + case $1 in + [0-9]* | *[!a-zA-Z0-9_]*) + func_tr_sh_result=`$ECHO "$1" | $SED -e 's/^\([0-9]\)/_\1/' -e 's/[^a-zA-Z0-9_]/_/g'` + ;; + * ) + func_tr_sh_result=$1 + ;; + esac +} + + +# func_verbose ARG... +# ------------------- +# Echo program name prefixed message in verbose mode only. +func_verbose () +{ + $debug_cmd + + $opt_verbose && func_echo "$*" + + : +} + + +# func_warn_and_continue ARG... +# ----------------------------- +# Echo program name prefixed warning message to standard error. +func_warn_and_continue () +{ + $debug_cmd + + $require_term_colors + + func_echo_infix_1 "${tc_red}warning$tc_reset" "$*" >&2 +} + + +# func_warning CATEGORY ARG... +# ---------------------------- +# Echo program name prefixed warning message to standard error. Warning +# messages can be filtered according to CATEGORY, where this function +# elides messages where CATEGORY is not listed in the global variable +# 'opt_warning_types'. +func_warning () +{ + $debug_cmd + + # CATEGORY must be in the warning_categories list! + case " $warning_categories " in + *" $1 "*) ;; + *) func_internal_error "invalid warning category '$1'" ;; + esac + + _G_category=$1 + shift + + case " $opt_warning_types " in + *" $_G_category "*) $warning_func ${1+"$@"} ;; + esac +} + + +# func_sort_ver VER1 VER2 +# ----------------------- +# 'sort -V' is not generally available. +# Note this deviates from the version comparison in automake +# in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a +# but this should suffice as we won't be specifying old +# version formats or redundant trailing .0 in bootstrap.conf. +# If we did want full compatibility then we should probably +# use m4_version_compare from autoconf. +func_sort_ver () +{ + $debug_cmd + + printf '%s\n%s\n' "$1" "$2" \ + | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n +} + +# func_lt_ver PREV CURR +# --------------------- +# Return true if PREV and CURR are in the correct order according to +# func_sort_ver, otherwise false. Use it like this: +# +# func_lt_ver "$prev_ver" "$proposed_ver" || func_fatal_error "..." +func_lt_ver () +{ + $debug_cmd + + test "x$1" = x`func_sort_ver "$1" "$2" | $SED 1q` +} + + +# Local variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" +# time-stamp-time-zone: "UTC" +# End: +#! /bin/sh + +# Set a version string for this script. +scriptversion=2014-01-07.03; # UTC + +# A portable, pluggable option parser for Bourne shell. +# Written by Gary V. Vaughan, 2010 + +# Copyright (C) 2010-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Please report bugs or propose patches to gary@gnu.org. + + +## ------ ## +## Usage. ## +## ------ ## + +# This file is a library for parsing options in your shell scripts along +# with assorted other useful supporting features that you can make use +# of too. +# +# For the simplest scripts you might need only: +# +# #!/bin/sh +# . relative/path/to/funclib.sh +# . relative/path/to/options-parser +# scriptversion=1.0 +# func_options ${1+"$@"} +# eval set dummy "$func_options_result"; shift +# ...rest of your script... +# +# In order for the '--version' option to work, you will need to have a +# suitably formatted comment like the one at the top of this file +# starting with '# Written by ' and ending with '# warranty; '. +# +# For '-h' and '--help' to work, you will also need a one line +# description of your script's purpose in a comment directly above the +# '# Written by ' line, like the one at the top of this file. +# +# The default options also support '--debug', which will turn on shell +# execution tracing (see the comment above debug_cmd below for another +# use), and '--verbose' and the func_verbose function to allow your script +# to display verbose messages only when your user has specified +# '--verbose'. +# +# After sourcing this file, you can plug processing for additional +# options by amending the variables from the 'Configuration' section +# below, and following the instructions in the 'Option parsing' +# section further down. + +## -------------- ## +## Configuration. ## +## -------------- ## + +# You should override these variables in your script after sourcing this +# file so that they reflect the customisations you have added to the +# option parser. + +# The usage line for option parsing errors and the start of '-h' and +# '--help' output messages. You can embed shell variables for delayed +# expansion at the time the message is displayed, but you will need to +# quote other shell meta-characters carefully to prevent them being +# expanded when the contents are evaled. +usage='$progpath [OPTION]...' + +# Short help message in response to '-h' and '--help'. Add to this or +# override it after sourcing this library to reflect the full set of +# options your script accepts. +usage_message="\ + --debug enable verbose shell tracing + -W, --warnings=CATEGORY + report the warnings falling in CATEGORY [all] + -v, --verbose verbosely report processing + --version print version information and exit + -h, --help print short or long help message and exit +" + +# Additional text appended to 'usage_message' in response to '--help'. +long_help_message=" +Warning categories include: + 'all' show all warnings + 'none' turn off all the warnings + 'error' warnings are treated as fatal errors" + +# Help message printed before fatal option parsing errors. +fatal_help="Try '\$progname --help' for more information." + + + +## ------------------------- ## +## Hook function management. ## +## ------------------------- ## + +# This section contains functions for adding, removing, and running hooks +# to the main code. A hook is just a named list of of function, that can +# be run in order later on. + +# func_hookable FUNC_NAME +# ----------------------- +# Declare that FUNC_NAME will run hooks added with +# 'func_add_hook FUNC_NAME ...'. +func_hookable () +{ + $debug_cmd + + func_append hookable_fns " $1" +} + + +# func_add_hook FUNC_NAME HOOK_FUNC +# --------------------------------- +# Request that FUNC_NAME call HOOK_FUNC before it returns. FUNC_NAME must +# first have been declared "hookable" by a call to 'func_hookable'. +func_add_hook () +{ + $debug_cmd + + case " $hookable_fns " in + *" $1 "*) ;; + *) func_fatal_error "'$1' does not accept hook functions." ;; + esac + + eval func_append ${1}_hooks '" $2"' +} + + +# func_remove_hook FUNC_NAME HOOK_FUNC +# ------------------------------------ +# Remove HOOK_FUNC from the list of functions called by FUNC_NAME. +func_remove_hook () +{ + $debug_cmd + + eval ${1}_hooks='`$ECHO "\$'$1'_hooks" |$SED "s| '$2'||"`' +} + + +# func_run_hooks FUNC_NAME [ARG]... +# --------------------------------- +# Run all hook functions registered to FUNC_NAME. +# It is assumed that the list of hook functions contains nothing more +# than a whitespace-delimited list of legal shell function names, and +# no effort is wasted trying to catch shell meta-characters or preserve +# whitespace. +func_run_hooks () +{ + $debug_cmd + + case " $hookable_fns " in + *" $1 "*) ;; + *) func_fatal_error "'$1' does not support hook funcions.n" ;; + esac + + eval _G_hook_fns=\$$1_hooks; shift + + for _G_hook in $_G_hook_fns; do + eval $_G_hook '"$@"' + + # store returned options list back into positional + # parameters for next 'cmd' execution. + eval _G_hook_result=\$${_G_hook}_result + eval set dummy "$_G_hook_result"; shift + done + + func_quote_for_eval ${1+"$@"} + func_run_hooks_result=$func_quote_for_eval_result +} + + + +## --------------- ## +## Option parsing. ## +## --------------- ## + +# In order to add your own option parsing hooks, you must accept the +# full positional parameter list in your hook function, remove any +# options that you action, and then pass back the remaining unprocessed +# options in '<hooked_function_name>_result', escaped suitably for +# 'eval'. Like this: +# +# my_options_prep () +# { +# $debug_cmd +# +# # Extend the existing usage message. +# usage_message=$usage_message' +# -s, --silent don'\''t print informational messages +# ' +# +# func_quote_for_eval ${1+"$@"} +# my_options_prep_result=$func_quote_for_eval_result +# } +# func_add_hook func_options_prep my_options_prep +# +# +# my_silent_option () +# { +# $debug_cmd +# +# # Note that for efficiency, we parse as many options as we can +# # recognise in a loop before passing the remainder back to the +# # caller on the first unrecognised argument we encounter. +# while test $# -gt 0; do +# opt=$1; shift +# case $opt in +# --silent|-s) opt_silent=: ;; +# # Separate non-argument short options: +# -s*) func_split_short_opt "$_G_opt" +# set dummy "$func_split_short_opt_name" \ +# "-$func_split_short_opt_arg" ${1+"$@"} +# shift +# ;; +# *) set dummy "$_G_opt" "$*"; shift; break ;; +# esac +# done +# +# func_quote_for_eval ${1+"$@"} +# my_silent_option_result=$func_quote_for_eval_result +# } +# func_add_hook func_parse_options my_silent_option +# +# +# my_option_validation () +# { +# $debug_cmd +# +# $opt_silent && $opt_verbose && func_fatal_help "\ +# '--silent' and '--verbose' options are mutually exclusive." +# +# func_quote_for_eval ${1+"$@"} +# my_option_validation_result=$func_quote_for_eval_result +# } +# func_add_hook func_validate_options my_option_validation +# +# You'll alse need to manually amend $usage_message to reflect the extra +# options you parse. It's preferable to append if you can, so that +# multiple option parsing hooks can be added safely. + + +# func_options [ARG]... +# --------------------- +# All the functions called inside func_options are hookable. See the +# individual implementations for details. +func_hookable func_options +func_options () +{ + $debug_cmd + + func_options_prep ${1+"$@"} + eval func_parse_options \ + ${func_options_prep_result+"$func_options_prep_result"} + eval func_validate_options \ + ${func_parse_options_result+"$func_parse_options_result"} + + eval func_run_hooks func_options \ + ${func_validate_options_result+"$func_validate_options_result"} + + # save modified positional parameters for caller + func_options_result=$func_run_hooks_result +} + + +# func_options_prep [ARG]... +# -------------------------- +# All initialisations required before starting the option parse loop. +# Note that when calling hook functions, we pass through the list of +# positional parameters. If a hook function modifies that list, and +# needs to propogate that back to rest of this script, then the complete +# modified list must be put in 'func_run_hooks_result' before +# returning. +func_hookable func_options_prep +func_options_prep () +{ + $debug_cmd + + # Option defaults: + opt_verbose=false + opt_warning_types= + + func_run_hooks func_options_prep ${1+"$@"} + + # save modified positional parameters for caller + func_options_prep_result=$func_run_hooks_result +} + + +# func_parse_options [ARG]... +# --------------------------- +# The main option parsing loop. +func_hookable func_parse_options +func_parse_options () +{ + $debug_cmd + + func_parse_options_result= + + # this just eases exit handling + while test $# -gt 0; do + # Defer to hook functions for initial option parsing, so they + # get priority in the event of reusing an option name. + func_run_hooks func_parse_options ${1+"$@"} + + # Adjust func_parse_options positional parameters to match + eval set dummy "$func_run_hooks_result"; shift + + # Break out of the loop if we already parsed every option. + test $# -gt 0 || break + + _G_opt=$1 + shift + case $_G_opt in + --debug|-x) debug_cmd='set -x' + func_echo "enabling shell trace mode" + $debug_cmd + ;; + + --no-warnings|--no-warning|--no-warn) + set dummy --warnings none ${1+"$@"} + shift + ;; + + --warnings|--warning|-W) + test $# = 0 && func_missing_arg $_G_opt && break + case " $warning_categories $1" in + *" $1 "*) + # trailing space prevents matching last $1 above + func_append_uniq opt_warning_types " $1" + ;; + *all) + opt_warning_types=$warning_categories + ;; + *none) + opt_warning_types=none + warning_func=: + ;; + *error) + opt_warning_types=$warning_categories + warning_func=func_fatal_error + ;; + *) + func_fatal_error \ + "unsupported warning category: '$1'" + ;; + esac + shift + ;; + + --verbose|-v) opt_verbose=: ;; + --version) func_version ;; + -\?|-h) func_usage ;; + --help) func_help ;; + + # Separate optargs to long options (plugins may need this): + --*=*) func_split_equals "$_G_opt" + set dummy "$func_split_equals_lhs" \ + "$func_split_equals_rhs" ${1+"$@"} + shift + ;; + + # Separate optargs to short options: + -W*) + func_split_short_opt "$_G_opt" + set dummy "$func_split_short_opt_name" \ + "$func_split_short_opt_arg" ${1+"$@"} + shift + ;; + + # Separate non-argument short options: + -\?*|-h*|-v*|-x*) + func_split_short_opt "$_G_opt" + set dummy "$func_split_short_opt_name" \ + "-$func_split_short_opt_arg" ${1+"$@"} + shift + ;; + + --) break ;; + -*) func_fatal_help "unrecognised option: '$_G_opt'" ;; + *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; + esac + done + + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + func_parse_options_result=$func_quote_for_eval_result +} + + +# func_validate_options [ARG]... +# ------------------------------ +# Perform any sanity checks on option settings and/or unconsumed +# arguments. +func_hookable func_validate_options +func_validate_options () +{ + $debug_cmd + + # Display all warnings if -W was not given. + test -n "$opt_warning_types" || opt_warning_types=" $warning_categories" + + func_run_hooks func_validate_options ${1+"$@"} + + # Bail if the options were screwed! + $exit_cmd $EXIT_FAILURE + + # save modified positional parameters for caller + func_validate_options_result=$func_run_hooks_result +} + + + +## ----------------- ## +## Helper functions. ## +## ----------------- ## + +# This section contains the helper functions used by the rest of the +# hookable option parser framework in ascii-betical order. + + +# func_fatal_help ARG... +# ---------------------- +# Echo program name prefixed message to standard error, followed by +# a help hint, and exit. +func_fatal_help () +{ + $debug_cmd + + eval \$ECHO \""Usage: $usage"\" + eval \$ECHO \""$fatal_help"\" + func_error ${1+"$@"} + exit $EXIT_FAILURE +} + + +# func_help +# --------- +# Echo long help message to standard output and exit. +func_help () +{ + $debug_cmd + + func_usage_message + $ECHO "$long_help_message" + exit 0 +} + + +# func_missing_arg ARGNAME +# ------------------------ +# Echo program name prefixed message to standard error and set global +# exit_cmd. +func_missing_arg () +{ + $debug_cmd + + func_error "Missing argument for '$1'." + exit_cmd=exit +} + + +# func_split_equals STRING +# ------------------------ +# Set func_split_equals_lhs and func_split_equals_rhs shell variables after +# splitting STRING at the '=' sign. +test -z "$_G_HAVE_XSI_OPS" \ + && (eval 'x=a/b/c; + test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ + && _G_HAVE_XSI_OPS=yes + +if test yes = "$_G_HAVE_XSI_OPS" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_split_equals () + { + $debug_cmd + + func_split_equals_lhs=${1%%=*} + func_split_equals_rhs=${1#*=} + test "x$func_split_equals_lhs" = "x$1" \ + && func_split_equals_rhs= + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_split_equals () + { + $debug_cmd + + func_split_equals_lhs=`expr "x$1" : 'x\([^=]*\)'` + func_split_equals_rhs= + test "x$func_split_equals_lhs" = "x$1" \ + || func_split_equals_rhs=`expr "x$1" : 'x[^=]*=\(.*\)$'` + } +fi #func_split_equals + + +# func_split_short_opt SHORTOPT +# ----------------------------- +# Set func_split_short_opt_name and func_split_short_opt_arg shell +# variables after splitting SHORTOPT after the 2nd character. +if test yes = "$_G_HAVE_XSI_OPS" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_split_short_opt () + { + $debug_cmd + + func_split_short_opt_arg=${1#??} + func_split_short_opt_name=${1%"$func_split_short_opt_arg"} + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_split_short_opt () + { + $debug_cmd + + func_split_short_opt_name=`expr "x$1" : 'x-\(.\)'` + func_split_short_opt_arg=`expr "x$1" : 'x-.\(.*\)$'` + } +fi #func_split_short_opt + + +# func_usage +# ---------- +# Echo short help message to standard output and exit. +func_usage () +{ + $debug_cmd + + func_usage_message + $ECHO "Run '$progname --help |${PAGER-more}' for full usage" + exit 0 +} + + +# func_usage_message +# ------------------ +# Echo short help message to standard output. +func_usage_message () +{ + $debug_cmd + + eval \$ECHO \""Usage: $usage"\" + echo + $SED -n 's|^# || + /^Written by/{ + x;p;x + } + h + /^Written by/q' < "$progpath" + echo + eval \$ECHO \""$usage_message"\" +} + + +# func_version +# ------------ +# Echo version message to standard output and exit. +func_version () +{ + $debug_cmd + + printf '%s\n' "$progname $scriptversion" + $SED -n ' + /(C)/!b go + :more + /\./!{ + N + s|\n# | | + b more + } + :go + /^# Written by /,/# warranty; / { + s|^# || + s|^# *$|| + s|\((C)\)[ 0-9,-]*[ ,-]\([1-9][0-9]* \)|\1 \2| + p + } + /^# Written by / { + s|^# || + p + } + /^warranty; /q' < "$progpath" + + exit $? +} + + +# Local variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" +# time-stamp-time-zone: "UTC" +# End: + +# Set a version string. +scriptversion='(GNU libtool) 2.4.6' + + +# func_echo ARG... +# ---------------- +# Libtool also displays the current mode in messages, so override +# funclib.sh func_echo with this custom definition. +func_echo () +{ + $debug_cmd + + _G_message=$* + + func_echo_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_IFS + $ECHO "$progname${opt_mode+: $opt_mode}: $_G_line" + done + IFS=$func_echo_IFS +} + + +# func_warning ARG... +# ------------------- +# Libtool warnings are not categorized, so override funclib.sh +# func_warning with this simpler definition. +func_warning () +{ + $debug_cmd + + $warning_func ${1+"$@"} +} + + +## ---------------- ## +## Options parsing. ## +## ---------------- ## + +# Hook in the functions to make sure our own options are parsed during +# the option parsing loop. + +usage='$progpath [OPTION]... [MODE-ARG]...' + +# Short help message in response to '-h'. +usage_message="Options: + --config show all configuration variables + --debug enable verbose shell tracing + -n, --dry-run display commands without modifying any files + --features display basic configuration information and exit + --mode=MODE use operation mode MODE + --no-warnings equivalent to '-Wnone' + --preserve-dup-deps don't remove duplicate dependency libraries + --quiet, --silent don't print informational messages + --tag=TAG use configuration variables from tag TAG + -v, --verbose print more informational messages than default + --version print version information + -W, --warnings=CATEGORY report the warnings falling in CATEGORY [all] + -h, --help, --help-all print short, long, or detailed help message +" + +# Additional text appended to 'usage_message' in response to '--help'. +func_help () +{ + $debug_cmd + + func_usage_message + $ECHO "$long_help_message + +MODE must be one of the following: + + clean remove files from the build directory + compile compile a source file into a libtool object + execute automatically set library path, then run a program + finish complete the installation of libtool libraries + install install libraries or executables + link create a library or an executable + uninstall remove libraries from an installed directory + +MODE-ARGS vary depending on the MODE. When passed as first option, +'--mode=MODE' may be abbreviated as 'MODE' or a unique abbreviation of that. +Try '$progname --help --mode=MODE' for a more detailed description of MODE. + +When reporting a bug, please describe a test case to reproduce it and +include the following information: + + host-triplet: $host + shell: $SHELL + compiler: $LTCC + compiler flags: $LTCFLAGS + linker: $LD (gnu? $with_gnu_ld) + version: $progname (GNU libtool) 2.4.6 + automake: `($AUTOMAKE --version) 2>/dev/null |$SED 1q` + autoconf: `($AUTOCONF --version) 2>/dev/null |$SED 1q` + +Report bugs to <bug-libtool@gnu.org>. +GNU libtool home page: <http://www.gnu.org/s/libtool/>. +General help using GNU software: <http://www.gnu.org/gethelp/>." + exit 0 +} + + +# func_lo2o OBJECT-NAME +# --------------------- +# Transform OBJECT-NAME from a '.lo' suffix to the platform specific +# object suffix. + +lo2o=s/\\.lo\$/.$objext/ +o2lo=s/\\.$objext\$/.lo/ + +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_lo2o () + { + case $1 in + *.lo) func_lo2o_result=${1%.lo}.$objext ;; + * ) func_lo2o_result=$1 ;; + esac + }' + + # func_xform LIBOBJ-OR-SOURCE + # --------------------------- + # Transform LIBOBJ-OR-SOURCE from a '.o' or '.c' (or otherwise) + # suffix to a '.lo' libtool-object suffix. + eval 'func_xform () + { + func_xform_result=${1%.*}.lo + }' +else + # ...otherwise fall back to using sed. + func_lo2o () + { + func_lo2o_result=`$ECHO "$1" | $SED "$lo2o"` + } + + func_xform () + { + func_xform_result=`$ECHO "$1" | $SED 's|\.[^.]*$|.lo|'` + } +fi + + +# func_fatal_configuration ARG... +# ------------------------------- +# Echo program name prefixed message to standard error, followed by +# a configuration failure hint, and exit. +func_fatal_configuration () +{ + func__fatal_error ${1+"$@"} \ + "See the $PACKAGE documentation for more information." \ + "Fatal configuration error." +} + + +# func_config +# ----------- +# Display the configuration for all the tags in this script. +func_config () +{ + re_begincf='^# ### BEGIN LIBTOOL' + re_endcf='^# ### END LIBTOOL' + + # Default configuration. + $SED "1,/$re_begincf CONFIG/d;/$re_endcf CONFIG/,\$d" < "$progpath" + + # Now print the configurations for the tags. + for tagname in $taglist; do + $SED -n "/$re_begincf TAG CONFIG: $tagname\$/,/$re_endcf TAG CONFIG: $tagname\$/p" < "$progpath" + done + + exit $? +} + + +# func_features +# ------------- +# Display the features supported by this script. +func_features () +{ + echo "host: $host" + if test yes = "$build_libtool_libs"; then + echo "enable shared libraries" + else + echo "disable shared libraries" + fi + if test yes = "$build_old_libs"; then + echo "enable static libraries" + else + echo "disable static libraries" + fi + + exit $? +} + + +# func_enable_tag TAGNAME +# ----------------------- +# Verify that TAGNAME is valid, and either flag an error and exit, or +# enable the TAGNAME tag. We also add TAGNAME to the global $taglist +# variable here. +func_enable_tag () +{ + # Global variable: + tagname=$1 + + re_begincf="^# ### BEGIN LIBTOOL TAG CONFIG: $tagname\$" + re_endcf="^# ### END LIBTOOL TAG CONFIG: $tagname\$" + sed_extractcf=/$re_begincf/,/$re_endcf/p + + # Validate tagname. + case $tagname in + *[!-_A-Za-z0-9,/]*) + func_fatal_error "invalid tag name: $tagname" + ;; + esac + + # Don't test for the "default" C tag, as we know it's + # there but not specially marked. + case $tagname in + CC) ;; + *) + if $GREP "$re_begincf" "$progpath" >/dev/null 2>&1; then + taglist="$taglist $tagname" + + # Evaluate the configuration. Be careful to quote the path + # and the sed script, to avoid splitting on whitespace, but + # also don't use non-portable quotes within backquotes within + # quotes we have to do it in 2 steps: + extractedcf=`$SED -n -e "$sed_extractcf" < "$progpath"` + eval "$extractedcf" + else + func_error "ignoring unknown tag $tagname" + fi + ;; + esac +} + + +# func_check_version_match +# ------------------------ +# Ensure that we are using m4 macros, and libtool script from the same +# release of libtool. +func_check_version_match () +{ + if test "$package_revision" != "$macro_revision"; then + if test "$VERSION" != "$macro_version"; then + if test -z "$macro_version"; then + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, but the +$progname: definition of this LT_INIT comes from an older release. +$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION +$progname: and run autoconf again. +_LT_EOF + else + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, but the +$progname: definition of this LT_INIT comes from $PACKAGE $macro_version. +$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION +$progname: and run autoconf again. +_LT_EOF + fi + else + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, revision $package_revision, +$progname: but the definition of this LT_INIT comes from revision $macro_revision. +$progname: You should recreate aclocal.m4 with macros from revision $package_revision +$progname: of $PACKAGE $VERSION and run autoconf again. +_LT_EOF + fi + + exit $EXIT_MISMATCH + fi +} + + +# libtool_options_prep [ARG]... +# ----------------------------- +# Preparation for options parsed by libtool. +libtool_options_prep () +{ + $debug_mode + + # Option defaults: + opt_config=false + opt_dlopen= + opt_dry_run=false + opt_help=false + opt_mode= + opt_preserve_dup_deps=false + opt_quiet=false + + nonopt= + preserve_args= + + # Shorthand for --mode=foo, only valid as the first argument + case $1 in + clean|clea|cle|cl) + shift; set dummy --mode clean ${1+"$@"}; shift + ;; + compile|compil|compi|comp|com|co|c) + shift; set dummy --mode compile ${1+"$@"}; shift + ;; + execute|execut|execu|exec|exe|ex|e) + shift; set dummy --mode execute ${1+"$@"}; shift + ;; + finish|finis|fini|fin|fi|f) + shift; set dummy --mode finish ${1+"$@"}; shift + ;; + install|instal|insta|inst|ins|in|i) + shift; set dummy --mode install ${1+"$@"}; shift + ;; + link|lin|li|l) + shift; set dummy --mode link ${1+"$@"}; shift + ;; + uninstall|uninstal|uninsta|uninst|unins|unin|uni|un|u) + shift; set dummy --mode uninstall ${1+"$@"}; shift + ;; + esac + + # Pass back the list of options. + func_quote_for_eval ${1+"$@"} + libtool_options_prep_result=$func_quote_for_eval_result +} +func_add_hook func_options_prep libtool_options_prep + + +# libtool_parse_options [ARG]... +# --------------------------------- +# Provide handling for libtool specific options. +libtool_parse_options () +{ + $debug_cmd + + # Perform our own loop to consume as many options as possible in + # each iteration. + while test $# -gt 0; do + _G_opt=$1 + shift + case $_G_opt in + --dry-run|--dryrun|-n) + opt_dry_run=: + ;; + + --config) func_config ;; + + --dlopen|-dlopen) + opt_dlopen="${opt_dlopen+$opt_dlopen +}$1" + shift + ;; + + --preserve-dup-deps) + opt_preserve_dup_deps=: ;; + + --features) func_features ;; + + --finish) set dummy --mode finish ${1+"$@"}; shift ;; + + --help) opt_help=: ;; + + --help-all) opt_help=': help-all' ;; + + --mode) test $# = 0 && func_missing_arg $_G_opt && break + opt_mode=$1 + case $1 in + # Valid mode arguments: + clean|compile|execute|finish|install|link|relink|uninstall) ;; + + # Catch anything else as an error + *) func_error "invalid argument for $_G_opt" + exit_cmd=exit + break + ;; + esac + shift + ;; + + --no-silent|--no-quiet) + opt_quiet=false + func_append preserve_args " $_G_opt" + ;; + + --no-warnings|--no-warning|--no-warn) + opt_warning=false + func_append preserve_args " $_G_opt" + ;; + + --no-verbose) + opt_verbose=false + func_append preserve_args " $_G_opt" + ;; + + --silent|--quiet) + opt_quiet=: + opt_verbose=false + func_append preserve_args " $_G_opt" + ;; + + --tag) test $# = 0 && func_missing_arg $_G_opt && break + opt_tag=$1 + func_append preserve_args " $_G_opt $1" + func_enable_tag "$1" + shift + ;; + + --verbose|-v) opt_quiet=false + opt_verbose=: + func_append preserve_args " $_G_opt" + ;; + + # An option not handled by this hook function: + *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; + esac + done + + + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + libtool_parse_options_result=$func_quote_for_eval_result +} +func_add_hook func_parse_options libtool_parse_options + + + +# libtool_validate_options [ARG]... +# --------------------------------- +# Perform any sanity checks on option settings and/or unconsumed +# arguments. +libtool_validate_options () +{ + # save first non-option argument + if test 0 -lt $#; then + nonopt=$1 + shift + fi + + # preserve --debug + test : = "$debug_cmd" || func_append preserve_args " --debug" + + case $host in + # Solaris2 added to fix http://debbugs.gnu.org/cgi/bugreport.cgi?bug=16452 + # see also: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59788 + *cygwin* | *mingw* | *pw32* | *cegcc* | *solaris2* | *os2*) + # don't eliminate duplications in $postdeps and $predeps + opt_duplicate_compiler_generated_deps=: + ;; + *) + opt_duplicate_compiler_generated_deps=$opt_preserve_dup_deps + ;; + esac + + $opt_help || { + # Sanity checks first: + func_check_version_match + + test yes != "$build_libtool_libs" \ + && test yes != "$build_old_libs" \ + && func_fatal_configuration "not configured to build any kind of library" + + # Darwin sucks + eval std_shrext=\"$shrext_cmds\" + + # Only execute mode is allowed to have -dlopen flags. + if test -n "$opt_dlopen" && test execute != "$opt_mode"; then + func_error "unrecognized option '-dlopen'" + $ECHO "$help" 1>&2 + exit $EXIT_FAILURE + fi + + # Change the help message to a mode-specific one. + generic_help=$help + help="Try '$progname --help --mode=$opt_mode' for more information." + } + + # Pass back the unparsed argument list + func_quote_for_eval ${1+"$@"} + libtool_validate_options_result=$func_quote_for_eval_result +} +func_add_hook func_validate_options libtool_validate_options + + +# Process options as early as possible so that --help and --version +# can return quickly. +func_options ${1+"$@"} +eval set dummy "$func_options_result"; shift + + + +## ----------- ## +## Main. ## +## ----------- ## + +magic='%%%MAGIC variable%%%' +magic_exe='%%%MAGIC EXE variable%%%' + +# Global variables. +extracted_archives= +extracted_serial=0 + +# If this variable is set in any of the actions, the command in it +# will be execed at the end. This prevents here-documents from being +# left over by shells. +exec_cmd= + + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' +} + +# func_generated_by_libtool +# True iff stdin has been generated by Libtool. This function is only +# a basic sanity check; it will hardly flush out determined imposters. +func_generated_by_libtool_p () +{ + $GREP "^# Generated by .*$PACKAGE" > /dev/null 2>&1 +} + +# func_lalib_p file +# True iff FILE is a libtool '.la' library or '.lo' object file. +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_lalib_p () +{ + test -f "$1" && + $SED -e 4q "$1" 2>/dev/null | func_generated_by_libtool_p +} + +# func_lalib_unsafe_p file +# True iff FILE is a libtool '.la' library or '.lo' object file. +# This function implements the same check as func_lalib_p without +# resorting to external programs. To this end, it redirects stdin and +# closes it afterwards, without saving the original file descriptor. +# As a safety measure, use it only where a negative result would be +# fatal anyway. Works if 'file' does not exist. +func_lalib_unsafe_p () +{ + lalib_p=no + if test -f "$1" && test -r "$1" && exec 5<&0 <"$1"; then + for lalib_p_l in 1 2 3 4 + do + read lalib_p_line + case $lalib_p_line in + \#\ Generated\ by\ *$PACKAGE* ) lalib_p=yes; break;; + esac + done + exec 0<&5 5<&- + fi + test yes = "$lalib_p" +} + +# func_ltwrapper_script_p file +# True iff FILE is a libtool wrapper script +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_script_p () +{ + test -f "$1" && + $lt_truncate_bin < "$1" 2>/dev/null | func_generated_by_libtool_p +} + +# func_ltwrapper_executable_p file +# True iff FILE is a libtool wrapper executable +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_executable_p () +{ + func_ltwrapper_exec_suffix= + case $1 in + *.exe) ;; + *) func_ltwrapper_exec_suffix=.exe ;; + esac + $GREP "$magic_exe" "$1$func_ltwrapper_exec_suffix" >/dev/null 2>&1 +} + +# func_ltwrapper_scriptname file +# Assumes file is an ltwrapper_executable +# uses $file to determine the appropriate filename for a +# temporary ltwrapper_script. +func_ltwrapper_scriptname () +{ + func_dirname_and_basename "$1" "" "." + func_stripname '' '.exe' "$func_basename_result" + func_ltwrapper_scriptname_result=$func_dirname_result/$objdir/${func_stripname_result}_ltshwrapper +} + +# func_ltwrapper_p file +# True iff FILE is a libtool wrapper script or wrapper executable +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_p () +{ + func_ltwrapper_script_p "$1" || func_ltwrapper_executable_p "$1" +} + + +# func_execute_cmds commands fail_cmd +# Execute tilde-delimited COMMANDS. +# If FAIL_CMD is given, eval that upon failure. +# FAIL_CMD may read-access the current command in variable CMD! +func_execute_cmds () +{ + $debug_cmd + + save_ifs=$IFS; IFS='~' + for cmd in $1; do + IFS=$sp$nl + eval cmd=\"$cmd\" + IFS=$save_ifs + func_show_eval "$cmd" "${2-:}" + done + IFS=$save_ifs +} + + +# func_source file +# Source FILE, adding directory component if necessary. +# Note that it is not necessary on cygwin/mingw to append a dot to +# FILE even if both FILE and FILE.exe exist: automatic-append-.exe +# behavior happens only for exec(3), not for open(2)! Also, sourcing +# 'FILE.' does not work on cygwin managed mounts. +func_source () +{ + $debug_cmd + + case $1 in + */* | *\\*) . "$1" ;; + *) . "./$1" ;; + esac +} + + +# func_resolve_sysroot PATH +# Replace a leading = in PATH with a sysroot. Store the result into +# func_resolve_sysroot_result +func_resolve_sysroot () +{ + func_resolve_sysroot_result=$1 + case $func_resolve_sysroot_result in + =*) + func_stripname '=' '' "$func_resolve_sysroot_result" + func_resolve_sysroot_result=$lt_sysroot$func_stripname_result + ;; + esac +} + +# func_replace_sysroot PATH +# If PATH begins with the sysroot, replace it with = and +# store the result into func_replace_sysroot_result. +func_replace_sysroot () +{ + case $lt_sysroot:$1 in + ?*:"$lt_sysroot"*) + func_stripname "$lt_sysroot" '' "$1" + func_replace_sysroot_result='='$func_stripname_result + ;; + *) + # Including no sysroot. + func_replace_sysroot_result=$1 + ;; + esac +} + +# func_infer_tag arg +# Infer tagged configuration to use if any are available and +# if one wasn't chosen via the "--tag" command line option. +# Only attempt this if the compiler in the base compile +# command doesn't match the default compiler. +# arg is usually of the form 'gcc ...' +func_infer_tag () +{ + $debug_cmd + + if test -n "$available_tags" && test -z "$tagname"; then + CC_quoted= + for arg in $CC; do + func_append_quoted CC_quoted "$arg" + done + CC_expanded=`func_echo_all $CC` + CC_quoted_expanded=`func_echo_all $CC_quoted` + case $@ in + # Blanks in the command may have been stripped by the calling shell, + # but not from the CC environment variable when configure was run. + " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ + " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) ;; + # Blanks at the start of $base_compile will cause this to fail + # if we don't check for them as well. + *) + for z in $available_tags; do + if $GREP "^# ### BEGIN LIBTOOL TAG CONFIG: $z$" < "$progpath" > /dev/null; then + # Evaluate the configuration. + eval "`$SED -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$z'$/,/^# ### END LIBTOOL TAG CONFIG: '$z'$/p' < $progpath`" + CC_quoted= + for arg in $CC; do + # Double-quote args containing other shell metacharacters. + func_append_quoted CC_quoted "$arg" + done + CC_expanded=`func_echo_all $CC` + CC_quoted_expanded=`func_echo_all $CC_quoted` + case "$@ " in + " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ + " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) + # The compiler in the base compile command matches + # the one in the tagged configuration. + # Assume this is the tagged configuration we want. + tagname=$z + break + ;; + esac + fi + done + # If $tagname still isn't set, then no tagged configuration + # was found and let the user know that the "--tag" command + # line option must be used. + if test -z "$tagname"; then + func_echo "unable to infer tagged configuration" + func_fatal_error "specify a tag with '--tag'" +# else +# func_verbose "using $tagname tagged configuration" + fi + ;; + esac + fi +} + + + +# func_write_libtool_object output_name pic_name nonpic_name +# Create a libtool object file (analogous to a ".la" file), +# but don't create it if we're doing a dry run. +func_write_libtool_object () +{ + write_libobj=$1 + if test yes = "$build_libtool_libs"; then + write_lobj=\'$2\' + else + write_lobj=none + fi + + if test yes = "$build_old_libs"; then + write_oldobj=\'$3\' + else + write_oldobj=none + fi + + $opt_dry_run || { + cat >${write_libobj}T <<EOF +# $write_libobj - a libtool object file +# Generated by $PROGRAM (GNU $PACKAGE) $VERSION +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# Name of the PIC object. +pic_object=$write_lobj + +# Name of the non-PIC object +non_pic_object=$write_oldobj + +EOF + $MV "${write_libobj}T" "$write_libobj" + } +} + + +################################################## +# FILE NAME AND PATH CONVERSION HELPER FUNCTIONS # +################################################## + +# func_convert_core_file_wine_to_w32 ARG +# Helper function used by file name conversion functions when $build is *nix, +# and $host is mingw, cygwin, or some other w32 environment. Relies on a +# correctly configured wine environment available, with the winepath program +# in $build's $PATH. +# +# ARG is the $build file name to be converted to w32 format. +# Result is available in $func_convert_core_file_wine_to_w32_result, and will +# be empty on error (or when ARG is empty) +func_convert_core_file_wine_to_w32 () +{ + $debug_cmd + + func_convert_core_file_wine_to_w32_result=$1 + if test -n "$1"; then + # Unfortunately, winepath does not exit with a non-zero error code, so we + # are forced to check the contents of stdout. On the other hand, if the + # command is not found, the shell will set an exit code of 127 and print + # *an error message* to stdout. So we must check for both error code of + # zero AND non-empty stdout, which explains the odd construction: + func_convert_core_file_wine_to_w32_tmp=`winepath -w "$1" 2>/dev/null` + if test "$?" -eq 0 && test -n "$func_convert_core_file_wine_to_w32_tmp"; then + func_convert_core_file_wine_to_w32_result=`$ECHO "$func_convert_core_file_wine_to_w32_tmp" | + $SED -e "$sed_naive_backslashify"` + else + func_convert_core_file_wine_to_w32_result= + fi + fi +} +# end: func_convert_core_file_wine_to_w32 + + +# func_convert_core_path_wine_to_w32 ARG +# Helper function used by path conversion functions when $build is *nix, and +# $host is mingw, cygwin, or some other w32 environment. Relies on a correctly +# configured wine environment available, with the winepath program in $build's +# $PATH. Assumes ARG has no leading or trailing path separator characters. +# +# ARG is path to be converted from $build format to win32. +# Result is available in $func_convert_core_path_wine_to_w32_result. +# Unconvertible file (directory) names in ARG are skipped; if no directory names +# are convertible, then the result may be empty. +func_convert_core_path_wine_to_w32 () +{ + $debug_cmd + + # unfortunately, winepath doesn't convert paths, only file names + func_convert_core_path_wine_to_w32_result= + if test -n "$1"; then + oldIFS=$IFS + IFS=: + for func_convert_core_path_wine_to_w32_f in $1; do + IFS=$oldIFS + func_convert_core_file_wine_to_w32 "$func_convert_core_path_wine_to_w32_f" + if test -n "$func_convert_core_file_wine_to_w32_result"; then + if test -z "$func_convert_core_path_wine_to_w32_result"; then + func_convert_core_path_wine_to_w32_result=$func_convert_core_file_wine_to_w32_result + else + func_append func_convert_core_path_wine_to_w32_result ";$func_convert_core_file_wine_to_w32_result" + fi + fi + done + IFS=$oldIFS + fi +} +# end: func_convert_core_path_wine_to_w32 + + +# func_cygpath ARGS... +# Wrapper around calling the cygpath program via LT_CYGPATH. This is used when +# when (1) $build is *nix and Cygwin is hosted via a wine environment; or (2) +# $build is MSYS and $host is Cygwin, or (3) $build is Cygwin. In case (1) or +# (2), returns the Cygwin file name or path in func_cygpath_result (input +# file name or path is assumed to be in w32 format, as previously converted +# from $build's *nix or MSYS format). In case (3), returns the w32 file name +# or path in func_cygpath_result (input file name or path is assumed to be in +# Cygwin format). Returns an empty string on error. +# +# ARGS are passed to cygpath, with the last one being the file name or path to +# be converted. +# +# Specify the absolute *nix (or w32) name to cygpath in the LT_CYGPATH +# environment variable; do not put it in $PATH. +func_cygpath () +{ + $debug_cmd + + if test -n "$LT_CYGPATH" && test -f "$LT_CYGPATH"; then + func_cygpath_result=`$LT_CYGPATH "$@" 2>/dev/null` + if test "$?" -ne 0; then + # on failure, ensure result is empty + func_cygpath_result= + fi + else + func_cygpath_result= + func_error "LT_CYGPATH is empty or specifies non-existent file: '$LT_CYGPATH'" + fi +} +#end: func_cygpath + + +# func_convert_core_msys_to_w32 ARG +# Convert file name or path ARG from MSYS format to w32 format. Return +# result in func_convert_core_msys_to_w32_result. +func_convert_core_msys_to_w32 () +{ + $debug_cmd + + # awkward: cmd appends spaces to result + func_convert_core_msys_to_w32_result=`( cmd //c echo "$1" ) 2>/dev/null | + $SED -e 's/[ ]*$//' -e "$sed_naive_backslashify"` +} +#end: func_convert_core_msys_to_w32 + + +# func_convert_file_check ARG1 ARG2 +# Verify that ARG1 (a file name in $build format) was converted to $host +# format in ARG2. Otherwise, emit an error message, but continue (resetting +# func_to_host_file_result to ARG1). +func_convert_file_check () +{ + $debug_cmd + + if test -z "$2" && test -n "$1"; then + func_error "Could not determine host file name corresponding to" + func_error " '$1'" + func_error "Continuing, but uninstalled executables may not work." + # Fallback: + func_to_host_file_result=$1 + fi +} +# end func_convert_file_check + + +# func_convert_path_check FROM_PATHSEP TO_PATHSEP FROM_PATH TO_PATH +# Verify that FROM_PATH (a path in $build format) was converted to $host +# format in TO_PATH. Otherwise, emit an error message, but continue, resetting +# func_to_host_file_result to a simplistic fallback value (see below). +func_convert_path_check () +{ + $debug_cmd + + if test -z "$4" && test -n "$3"; then + func_error "Could not determine the host path corresponding to" + func_error " '$3'" + func_error "Continuing, but uninstalled executables may not work." + # Fallback. This is a deliberately simplistic "conversion" and + # should not be "improved". See libtool.info. + if test "x$1" != "x$2"; then + lt_replace_pathsep_chars="s|$1|$2|g" + func_to_host_path_result=`echo "$3" | + $SED -e "$lt_replace_pathsep_chars"` + else + func_to_host_path_result=$3 + fi + fi +} +# end func_convert_path_check + + +# func_convert_path_front_back_pathsep FRONTPAT BACKPAT REPL ORIG +# Modifies func_to_host_path_result by prepending REPL if ORIG matches FRONTPAT +# and appending REPL if ORIG matches BACKPAT. +func_convert_path_front_back_pathsep () +{ + $debug_cmd + + case $4 in + $1 ) func_to_host_path_result=$3$func_to_host_path_result + ;; + esac + case $4 in + $2 ) func_append func_to_host_path_result "$3" + ;; + esac +} +# end func_convert_path_front_back_pathsep + + +################################################## +# $build to $host FILE NAME CONVERSION FUNCTIONS # +################################################## +# invoked via '$to_host_file_cmd ARG' +# +# In each case, ARG is the path to be converted from $build to $host format. +# Result will be available in $func_to_host_file_result. + + +# func_to_host_file ARG +# Converts the file name ARG from $build format to $host format. Return result +# in func_to_host_file_result. +func_to_host_file () +{ + $debug_cmd + + $to_host_file_cmd "$1" +} +# end func_to_host_file + + +# func_to_tool_file ARG LAZY +# converts the file name ARG from $build format to toolchain format. Return +# result in func_to_tool_file_result. If the conversion in use is listed +# in (the comma separated) LAZY, no conversion takes place. +func_to_tool_file () +{ + $debug_cmd + + case ,$2, in + *,"$to_tool_file_cmd",*) + func_to_tool_file_result=$1 + ;; + *) + $to_tool_file_cmd "$1" + func_to_tool_file_result=$func_to_host_file_result + ;; + esac +} +# end func_to_tool_file + + +# func_convert_file_noop ARG +# Copy ARG to func_to_host_file_result. +func_convert_file_noop () +{ + func_to_host_file_result=$1 +} +# end func_convert_file_noop + + +# func_convert_file_msys_to_w32 ARG +# Convert file name ARG from (mingw) MSYS to (mingw) w32 format; automatic +# conversion to w32 is not available inside the cwrapper. Returns result in +# func_to_host_file_result. +func_convert_file_msys_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_msys_to_w32 "$1" + func_to_host_file_result=$func_convert_core_msys_to_w32_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_msys_to_w32 + + +# func_convert_file_cygwin_to_w32 ARG +# Convert file name ARG from Cygwin to w32 format. Returns result in +# func_to_host_file_result. +func_convert_file_cygwin_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + # because $build is cygwin, we call "the" cygpath in $PATH; no need to use + # LT_CYGPATH in this case. + func_to_host_file_result=`cygpath -m "$1"` + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_cygwin_to_w32 + + +# func_convert_file_nix_to_w32 ARG +# Convert file name ARG from *nix to w32 format. Requires a wine environment +# and a working winepath. Returns result in func_to_host_file_result. +func_convert_file_nix_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_file_wine_to_w32 "$1" + func_to_host_file_result=$func_convert_core_file_wine_to_w32_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_nix_to_w32 + + +# func_convert_file_msys_to_cygwin ARG +# Convert file name ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. +# Returns result in func_to_host_file_result. +func_convert_file_msys_to_cygwin () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_msys_to_w32 "$1" + func_cygpath -u "$func_convert_core_msys_to_w32_result" + func_to_host_file_result=$func_cygpath_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_msys_to_cygwin + + +# func_convert_file_nix_to_cygwin ARG +# Convert file name ARG from *nix to Cygwin format. Requires Cygwin installed +# in a wine environment, working winepath, and LT_CYGPATH set. Returns result +# in func_to_host_file_result. +func_convert_file_nix_to_cygwin () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + # convert from *nix to w32, then use cygpath to convert from w32 to cygwin. + func_convert_core_file_wine_to_w32 "$1" + func_cygpath -u "$func_convert_core_file_wine_to_w32_result" + func_to_host_file_result=$func_cygpath_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_nix_to_cygwin + + +############################################# +# $build to $host PATH CONVERSION FUNCTIONS # +############################################# +# invoked via '$to_host_path_cmd ARG' +# +# In each case, ARG is the path to be converted from $build to $host format. +# The result will be available in $func_to_host_path_result. +# +# Path separators are also converted from $build format to $host format. If +# ARG begins or ends with a path separator character, it is preserved (but +# converted to $host format) on output. +# +# All path conversion functions are named using the following convention: +# file name conversion function : func_convert_file_X_to_Y () +# path conversion function : func_convert_path_X_to_Y () +# where, for any given $build/$host combination the 'X_to_Y' value is the +# same. If conversion functions are added for new $build/$host combinations, +# the two new functions must follow this pattern, or func_init_to_host_path_cmd +# will break. + + +# func_init_to_host_path_cmd +# Ensures that function "pointer" variable $to_host_path_cmd is set to the +# appropriate value, based on the value of $to_host_file_cmd. +to_host_path_cmd= +func_init_to_host_path_cmd () +{ + $debug_cmd + + if test -z "$to_host_path_cmd"; then + func_stripname 'func_convert_file_' '' "$to_host_file_cmd" + to_host_path_cmd=func_convert_path_$func_stripname_result + fi +} + + +# func_to_host_path ARG +# Converts the path ARG from $build format to $host format. Return result +# in func_to_host_path_result. +func_to_host_path () +{ + $debug_cmd + + func_init_to_host_path_cmd + $to_host_path_cmd "$1" +} +# end func_to_host_path + + +# func_convert_path_noop ARG +# Copy ARG to func_to_host_path_result. +func_convert_path_noop () +{ + func_to_host_path_result=$1 +} +# end func_convert_path_noop + + +# func_convert_path_msys_to_w32 ARG +# Convert path ARG from (mingw) MSYS to (mingw) w32 format; automatic +# conversion to w32 is not available inside the cwrapper. Returns result in +# func_to_host_path_result. +func_convert_path_msys_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # Remove leading and trailing path separator characters from ARG. MSYS + # behavior is inconsistent here; cygpath turns them into '.;' and ';.'; + # and winepath ignores them completely. + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" + func_to_host_path_result=$func_convert_core_msys_to_w32_result + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_msys_to_w32 + + +# func_convert_path_cygwin_to_w32 ARG +# Convert path ARG from Cygwin to w32 format. Returns result in +# func_to_host_file_result. +func_convert_path_cygwin_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_to_host_path_result=`cygpath -m -p "$func_to_host_path_tmp1"` + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_cygwin_to_w32 + + +# func_convert_path_nix_to_w32 ARG +# Convert path ARG from *nix to w32 format. Requires a wine environment and +# a working winepath. Returns result in func_to_host_file_result. +func_convert_path_nix_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" + func_to_host_path_result=$func_convert_core_path_wine_to_w32_result + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_nix_to_w32 + + +# func_convert_path_msys_to_cygwin ARG +# Convert path ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. +# Returns result in func_to_host_file_result. +func_convert_path_msys_to_cygwin () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" + func_cygpath -u -p "$func_convert_core_msys_to_w32_result" + func_to_host_path_result=$func_cygpath_result + func_convert_path_check : : \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" : "$1" + fi +} +# end func_convert_path_msys_to_cygwin + + +# func_convert_path_nix_to_cygwin ARG +# Convert path ARG from *nix to Cygwin format. Requires Cygwin installed in a +# a wine environment, working winepath, and LT_CYGPATH set. Returns result in +# func_to_host_file_result. +func_convert_path_nix_to_cygwin () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # Remove leading and trailing path separator characters from + # ARG. msys behavior is inconsistent here, cygpath turns them + # into '.;' and ';.', and winepath ignores them completely. + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" + func_cygpath -u -p "$func_convert_core_path_wine_to_w32_result" + func_to_host_path_result=$func_cygpath_result + func_convert_path_check : : \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" : "$1" + fi +} +# end func_convert_path_nix_to_cygwin + + +# func_dll_def_p FILE +# True iff FILE is a Windows DLL '.def' file. +# Keep in sync with _LT_DLL_DEF_P in libtool.m4 +func_dll_def_p () +{ + $debug_cmd + + func_dll_def_p_tmp=`$SED -n \ + -e 's/^[ ]*//' \ + -e '/^\(;.*\)*$/d' \ + -e 's/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p' \ + -e q \ + "$1"` + test DEF = "$func_dll_def_p_tmp" +} + + +# func_mode_compile arg... +func_mode_compile () +{ + $debug_cmd + + # Get the compilation command and the source file. + base_compile= + srcfile=$nonopt # always keep a non-empty value in "srcfile" + suppress_opt=yes + suppress_output= + arg_mode=normal + libobj= + later= + pie_flag= + + for arg + do + case $arg_mode in + arg ) + # do not "continue". Instead, add this to base_compile + lastarg=$arg + arg_mode=normal + ;; + + target ) + libobj=$arg + arg_mode=normal + continue + ;; + + normal ) + # Accept any command-line options. + case $arg in + -o) + test -n "$libobj" && \ + func_fatal_error "you cannot specify '-o' more than once" + arg_mode=target + continue + ;; + + -pie | -fpie | -fPIE) + func_append pie_flag " $arg" + continue + ;; + + -shared | -static | -prefer-pic | -prefer-non-pic) + func_append later " $arg" + continue + ;; + + -no-suppress) + suppress_opt=no + continue + ;; + + -Xcompiler) + arg_mode=arg # the next one goes into the "base_compile" arg list + continue # The current "srcfile" will either be retained or + ;; # replaced later. I would guess that would be a bug. + + -Wc,*) + func_stripname '-Wc,' '' "$arg" + args=$func_stripname_result + lastarg= + save_ifs=$IFS; IFS=, + for arg in $args; do + IFS=$save_ifs + func_append_quoted lastarg "$arg" + done + IFS=$save_ifs + func_stripname ' ' '' "$lastarg" + lastarg=$func_stripname_result + + # Add the arguments to base_compile. + func_append base_compile " $lastarg" + continue + ;; + + *) + # Accept the current argument as the source file. + # The previous "srcfile" becomes the current argument. + # + lastarg=$srcfile + srcfile=$arg + ;; + esac # case $arg + ;; + esac # case $arg_mode + + # Aesthetically quote the previous argument. + func_append_quoted base_compile "$lastarg" + done # for arg + + case $arg_mode in + arg) + func_fatal_error "you must specify an argument for -Xcompile" + ;; + target) + func_fatal_error "you must specify a target with '-o'" + ;; + *) + # Get the name of the library object. + test -z "$libobj" && { + func_basename "$srcfile" + libobj=$func_basename_result + } + ;; + esac + + # Recognize several different file suffixes. + # If the user specifies -o file.o, it is replaced with file.lo + case $libobj in + *.[cCFSifmso] | \ + *.ada | *.adb | *.ads | *.asm | \ + *.c++ | *.cc | *.ii | *.class | *.cpp | *.cxx | \ + *.[fF][09]? | *.for | *.java | *.go | *.obj | *.sx | *.cu | *.cup) + func_xform "$libobj" + libobj=$func_xform_result + ;; + esac + + case $libobj in + *.lo) func_lo2o "$libobj"; obj=$func_lo2o_result ;; + *) + func_fatal_error "cannot determine name of library object from '$libobj'" + ;; + esac + + func_infer_tag $base_compile + + for arg in $later; do + case $arg in + -shared) + test yes = "$build_libtool_libs" \ + || func_fatal_configuration "cannot build a shared library" + build_old_libs=no + continue + ;; + + -static) + build_libtool_libs=no + build_old_libs=yes + continue + ;; + + -prefer-pic) + pic_mode=yes + continue + ;; + + -prefer-non-pic) + pic_mode=no + continue + ;; + esac + done + + func_quote_for_eval "$libobj" + test "X$libobj" != "X$func_quote_for_eval_result" \ + && $ECHO "X$libobj" | $GREP '[]~#^*{};<>?"'"'"' &()|`$[]' \ + && func_warning "libobj name '$libobj' may not contain shell special characters." + func_dirname_and_basename "$obj" "/" "" + objname=$func_basename_result + xdir=$func_dirname_result + lobj=$xdir$objdir/$objname + + test -z "$base_compile" && \ + func_fatal_help "you must specify a compilation command" + + # Delete any leftover library objects. + if test yes = "$build_old_libs"; then + removelist="$obj $lobj $libobj ${libobj}T" + else + removelist="$lobj $libobj ${libobj}T" + fi + + # On Cygwin there's no "real" PIC flag so we must build both object types + case $host_os in + cygwin* | mingw* | pw32* | os2* | cegcc*) + pic_mode=default + ;; + esac + if test no = "$pic_mode" && test pass_all != "$deplibs_check_method"; then + # non-PIC code in shared libraries is not supported + pic_mode=default + fi + + # Calculate the filename of the output object if compiler does + # not support -o with -c + if test no = "$compiler_c_o"; then + output_obj=`$ECHO "$srcfile" | $SED 's%^.*/%%; s%\.[^.]*$%%'`.$objext + lockfile=$output_obj.lock + else + output_obj= + need_locks=no + lockfile= + fi + + # Lock this critical section if it is needed + # We use this script file to make the link, it avoids creating a new file + if test yes = "$need_locks"; then + until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do + func_echo "Waiting for $lockfile to be removed" + sleep 2 + done + elif test warn = "$need_locks"; then + if test -f "$lockfile"; then + $ECHO "\ +*** ERROR, $lockfile exists and contains: +`cat $lockfile 2>/dev/null` + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + func_append removelist " $output_obj" + $ECHO "$srcfile" > "$lockfile" + fi + + $opt_dry_run || $RM $removelist + func_append removelist " $lockfile" + trap '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' 1 2 15 + + func_to_tool_file "$srcfile" func_convert_file_msys_to_w32 + srcfile=$func_to_tool_file_result + func_quote_for_eval "$srcfile" + qsrcfile=$func_quote_for_eval_result + + # Only build a PIC object if we are building libtool libraries. + if test yes = "$build_libtool_libs"; then + # Without this assignment, base_compile gets emptied. + fbsd_hideous_sh_bug=$base_compile + + if test no != "$pic_mode"; then + command="$base_compile $qsrcfile $pic_flag" + else + # Don't build PIC code + command="$base_compile $qsrcfile" + fi + + func_mkdir_p "$xdir$objdir" + + if test -z "$output_obj"; then + # Place PIC objects in $objdir + func_append command " -o $lobj" + fi + + func_show_eval_locale "$command" \ + 'test -n "$output_obj" && $RM $removelist; exit $EXIT_FAILURE' + + if test warn = "$need_locks" && + test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then + $ECHO "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + + # Just move the object if needed, then go on to compile the next one + if test -n "$output_obj" && test "X$output_obj" != "X$lobj"; then + func_show_eval '$MV "$output_obj" "$lobj"' \ + 'error=$?; $opt_dry_run || $RM $removelist; exit $error' + fi + + # Allow error messages only from the first compilation. + if test yes = "$suppress_opt"; then + suppress_output=' >/dev/null 2>&1' + fi + fi + + # Only build a position-dependent object if we build old libraries. + if test yes = "$build_old_libs"; then + if test yes != "$pic_mode"; then + # Don't build PIC code + command="$base_compile $qsrcfile$pie_flag" + else + command="$base_compile $qsrcfile $pic_flag" + fi + if test yes = "$compiler_c_o"; then + func_append command " -o $obj" + fi + + # Suppress compiler output if we already did a PIC compilation. + func_append command "$suppress_output" + func_show_eval_locale "$command" \ + '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' + + if test warn = "$need_locks" && + test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then + $ECHO "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + + # Just move the object if needed + if test -n "$output_obj" && test "X$output_obj" != "X$obj"; then + func_show_eval '$MV "$output_obj" "$obj"' \ + 'error=$?; $opt_dry_run || $RM $removelist; exit $error' + fi + fi + + $opt_dry_run || { + func_write_libtool_object "$libobj" "$objdir/$objname" "$objname" + + # Unlock the critical section if it was locked + if test no != "$need_locks"; then + removelist=$lockfile + $RM "$lockfile" + fi + } + + exit $EXIT_SUCCESS +} + +$opt_help || { + test compile = "$opt_mode" && func_mode_compile ${1+"$@"} +} + +func_mode_help () +{ + # We need to display help for each of the modes. + case $opt_mode in + "") + # Generic help is extracted from the usage comments + # at the start of this file. + func_help + ;; + + clean) + $ECHO \ +"Usage: $progname [OPTION]... --mode=clean RM [RM-OPTION]... FILE... + +Remove files from the build directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed +to RM. + +If FILE is a libtool library, object or program, all the files associated +with it are deleted. Otherwise, only FILE itself is deleted using RM." + ;; + + compile) + $ECHO \ +"Usage: $progname [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE + +Compile a source file into a libtool library object. + +This mode accepts the following additional options: + + -o OUTPUT-FILE set the output file name to OUTPUT-FILE + -no-suppress do not suppress compiler output for multiple passes + -prefer-pic try to build PIC objects only + -prefer-non-pic try to build non-PIC objects only + -shared do not build a '.o' file suitable for static linking + -static only build a '.o' file suitable for static linking + -Wc,FLAG pass FLAG directly to the compiler + +COMPILE-COMMAND is a command to be used in creating a 'standard' object file +from the given SOURCEFILE. + +The output file name is determined by removing the directory component from +SOURCEFILE, then substituting the C source code suffix '.c' with the +library object suffix, '.lo'." + ;; + + execute) + $ECHO \ +"Usage: $progname [OPTION]... --mode=execute COMMAND [ARGS]... + +Automatically set library path, then run a program. + +This mode accepts the following additional options: + + -dlopen FILE add the directory containing FILE to the library path + +This mode sets the library path environment variable according to '-dlopen' +flags. + +If any of the ARGS are libtool executable wrappers, then they are translated +into their corresponding uninstalled binary, and any of their required library +directories are added to the library path. + +Then, COMMAND is executed, with ARGS as arguments." + ;; + + finish) + $ECHO \ +"Usage: $progname [OPTION]... --mode=finish [LIBDIR]... + +Complete the installation of libtool libraries. + +Each LIBDIR is a directory that contains libtool libraries. + +The commands that this mode executes may require superuser privileges. Use +the '--dry-run' option if you just want to see what would be executed." + ;; + + install) + $ECHO \ +"Usage: $progname [OPTION]... --mode=install INSTALL-COMMAND... + +Install executables or libraries. + +INSTALL-COMMAND is the installation command. The first component should be +either the 'install' or 'cp' program. + +The following components of INSTALL-COMMAND are treated specially: + + -inst-prefix-dir PREFIX-DIR Use PREFIX-DIR as a staging area for installation + +The rest of the components are interpreted as arguments to that command (only +BSD-compatible install options are recognized)." + ;; + + link) + $ECHO \ +"Usage: $progname [OPTION]... --mode=link LINK-COMMAND... + +Link object files or libraries together to form another library, or to +create an executable program. + +LINK-COMMAND is a command using the C compiler that you would use to create +a program from several object files. + +The following components of LINK-COMMAND are treated specially: + + -all-static do not do any dynamic linking at all + -avoid-version do not add a version suffix if possible + -bindir BINDIR specify path to binaries directory (for systems where + libraries must be found in the PATH setting at runtime) + -dlopen FILE '-dlpreopen' FILE if it cannot be dlopened at runtime + -dlpreopen FILE link in FILE and add its symbols to lt_preloaded_symbols + -export-dynamic allow symbols from OUTPUT-FILE to be resolved with dlsym(3) + -export-symbols SYMFILE + try to export only the symbols listed in SYMFILE + -export-symbols-regex REGEX + try to export only the symbols matching REGEX + -LLIBDIR search LIBDIR for required installed libraries + -lNAME OUTPUT-FILE requires the installed library libNAME + -module build a library that can dlopened + -no-fast-install disable the fast-install mode + -no-install link a not-installable executable + -no-undefined declare that a library does not refer to external symbols + -o OUTPUT-FILE create OUTPUT-FILE from the specified objects + -objectlist FILE use a list of object files found in FILE to specify objects + -os2dllname NAME force a short DLL name on OS/2 (no effect on other OSes) + -precious-files-regex REGEX + don't remove output files matching REGEX + -release RELEASE specify package release information + -rpath LIBDIR the created library will eventually be installed in LIBDIR + -R[ ]LIBDIR add LIBDIR to the runtime path of programs and libraries + -shared only do dynamic linking of libtool libraries + -shrext SUFFIX override the standard shared library file extension + -static do not do any dynamic linking of uninstalled libtool libraries + -static-libtool-libs + do not do any dynamic linking of libtool libraries + -version-info CURRENT[:REVISION[:AGE]] + specify library version info [each variable defaults to 0] + -weak LIBNAME declare that the target provides the LIBNAME interface + -Wc,FLAG + -Xcompiler FLAG pass linker-specific FLAG directly to the compiler + -Wl,FLAG + -Xlinker FLAG pass linker-specific FLAG directly to the linker + -XCClinker FLAG pass link-specific FLAG to the compiler driver (CC) + +All other options (arguments beginning with '-') are ignored. + +Every other argument is treated as a filename. Files ending in '.la' are +treated as uninstalled libtool libraries, other files are standard or library +object files. + +If the OUTPUT-FILE ends in '.la', then a libtool library is created, +only library objects ('.lo' files) may be specified, and '-rpath' is +required, except when creating a convenience library. + +If OUTPUT-FILE ends in '.a' or '.lib', then a standard library is created +using 'ar' and 'ranlib', or on Windows using 'lib'. + +If OUTPUT-FILE ends in '.lo' or '.$objext', then a reloadable object file +is created, otherwise an executable program is created." + ;; + + uninstall) + $ECHO \ +"Usage: $progname [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE... + +Remove libraries from an installation directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed +to RM. + +If FILE is a libtool library, all the files associated with it are deleted. +Otherwise, only FILE itself is deleted using RM." + ;; + + *) + func_fatal_help "invalid operation mode '$opt_mode'" + ;; + esac + + echo + $ECHO "Try '$progname --help' for more information about other modes." +} + +# Now that we've collected a possible --mode arg, show help if necessary +if $opt_help; then + if test : = "$opt_help"; then + func_mode_help + else + { + func_help noexit + for opt_mode in compile link execute install finish uninstall clean; do + func_mode_help + done + } | $SED -n '1p; 2,$s/^Usage:/ or: /p' + { + func_help noexit + for opt_mode in compile link execute install finish uninstall clean; do + echo + func_mode_help + done + } | + $SED '1d + /^When reporting/,/^Report/{ + H + d + } + $x + /information about other modes/d + /more detailed .*MODE/d + s/^Usage:.*--mode=\([^ ]*\) .*/Description of \1 mode:/' + fi + exit $? +fi + + +# func_mode_execute arg... +func_mode_execute () +{ + $debug_cmd + + # The first argument is the command name. + cmd=$nonopt + test -z "$cmd" && \ + func_fatal_help "you must specify a COMMAND" + + # Handle -dlopen flags immediately. + for file in $opt_dlopen; do + test -f "$file" \ + || func_fatal_help "'$file' is not a file" + + dir= + case $file in + *.la) + func_resolve_sysroot "$file" + file=$func_resolve_sysroot_result + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$file" \ + || func_fatal_help "'$lib' is not a valid libtool archive" + + # Read the libtool library. + dlname= + library_names= + func_source "$file" + + # Skip this library if it cannot be dlopened. + if test -z "$dlname"; then + # Warn if it was a shared library. + test -n "$library_names" && \ + func_warning "'$file' was not linked with '-export-dynamic'" + continue + fi + + func_dirname "$file" "" "." + dir=$func_dirname_result + + if test -f "$dir/$objdir/$dlname"; then + func_append dir "/$objdir" + else + if test ! -f "$dir/$dlname"; then + func_fatal_error "cannot find '$dlname' in '$dir' or '$dir/$objdir'" + fi + fi + ;; + + *.lo) + # Just add the directory containing the .lo file. + func_dirname "$file" "" "." + dir=$func_dirname_result + ;; + + *) + func_warning "'-dlopen' is ignored for non-libtool libraries and objects" + continue + ;; + esac + + # Get the absolute pathname. + absdir=`cd "$dir" && pwd` + test -n "$absdir" && dir=$absdir + + # Now add the directory to shlibpath_var. + if eval "test -z \"\$$shlibpath_var\""; then + eval "$shlibpath_var=\"\$dir\"" + else + eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\"" + fi + done + + # This variable tells wrapper scripts just to set shlibpath_var + # rather than running their programs. + libtool_execute_magic=$magic + + # Check if any of the arguments is a wrapper script. + args= + for file + do + case $file in + -* | *.la | *.lo ) ;; + *) + # Do a test to see if this is really a libtool program. + if func_ltwrapper_script_p "$file"; then + func_source "$file" + # Transform arg to wrapped name. + file=$progdir/$program + elif func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + func_source "$func_ltwrapper_scriptname_result" + # Transform arg to wrapped name. + file=$progdir/$program + fi + ;; + esac + # Quote arguments (to preserve shell metacharacters). + func_append_quoted args "$file" + done + + if $opt_dry_run; then + # Display what would be done. + if test -n "$shlibpath_var"; then + eval "\$ECHO \"\$shlibpath_var=\$$shlibpath_var\"" + echo "export $shlibpath_var" + fi + $ECHO "$cmd$args" + exit $EXIT_SUCCESS + else + if test -n "$shlibpath_var"; then + # Export the shlibpath_var. + eval "export $shlibpath_var" + fi + + # Restore saved environment variables + for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES + do + eval "if test \"\${save_$lt_var+set}\" = set; then + $lt_var=\$save_$lt_var; export $lt_var + else + $lt_unset $lt_var + fi" + done + + # Now prepare to actually exec the command. + exec_cmd=\$cmd$args + fi +} + +test execute = "$opt_mode" && func_mode_execute ${1+"$@"} + + +# func_mode_finish arg... +func_mode_finish () +{ + $debug_cmd + + libs= + libdirs= + admincmds= + + for opt in "$nonopt" ${1+"$@"} + do + if test -d "$opt"; then + func_append libdirs " $opt" + + elif test -f "$opt"; then + if func_lalib_unsafe_p "$opt"; then + func_append libs " $opt" + else + func_warning "'$opt' is not a valid libtool archive" + fi + + else + func_fatal_error "invalid argument '$opt'" + fi + done + + if test -n "$libs"; then + if test -n "$lt_sysroot"; then + sysroot_regex=`$ECHO "$lt_sysroot" | $SED "$sed_make_literal_regex"` + sysroot_cmd="s/\([ ']\)$sysroot_regex/\1/g;" + else + sysroot_cmd= + fi + + # Remove sysroot references + if $opt_dry_run; then + for lib in $libs; do + echo "removing references to $lt_sysroot and '=' prefixes from $lib" + done + else + tmpdir=`func_mktempdir` + for lib in $libs; do + $SED -e "$sysroot_cmd s/\([ ']-[LR]\)=/\1/g; s/\([ ']\)=/\1/g" $lib \ + > $tmpdir/tmp-la + mv -f $tmpdir/tmp-la $lib + done + ${RM}r "$tmpdir" + fi + fi + + if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then + for libdir in $libdirs; do + if test -n "$finish_cmds"; then + # Do each command in the finish commands. + func_execute_cmds "$finish_cmds" 'admincmds="$admincmds +'"$cmd"'"' + fi + if test -n "$finish_eval"; then + # Do the single finish_eval. + eval cmds=\"$finish_eval\" + $opt_dry_run || eval "$cmds" || func_append admincmds " + $cmds" + fi + done + fi + + # Exit here if they wanted silent mode. + $opt_quiet && exit $EXIT_SUCCESS + + if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then + echo "----------------------------------------------------------------------" + echo "Libraries have been installed in:" + for libdir in $libdirs; do + $ECHO " $libdir" + done + echo + echo "If you ever happen to want to link against installed libraries" + echo "in a given directory, LIBDIR, you must either use libtool, and" + echo "specify the full pathname of the library, or use the '-LLIBDIR'" + echo "flag during linking and do at least one of the following:" + if test -n "$shlibpath_var"; then + echo " - add LIBDIR to the '$shlibpath_var' environment variable" + echo " during execution" + fi + if test -n "$runpath_var"; then + echo " - add LIBDIR to the '$runpath_var' environment variable" + echo " during linking" + fi + if test -n "$hardcode_libdir_flag_spec"; then + libdir=LIBDIR + eval flag=\"$hardcode_libdir_flag_spec\" + + $ECHO " - use the '$flag' linker flag" + fi + if test -n "$admincmds"; then + $ECHO " - have your system administrator run these commands:$admincmds" + fi + if test -f /etc/ld.so.conf; then + echo " - have your system administrator add LIBDIR to '/etc/ld.so.conf'" + fi + echo + + echo "See any operating system documentation about shared libraries for" + case $host in + solaris2.[6789]|solaris2.1[0-9]) + echo "more information, such as the ld(1), crle(1) and ld.so(8) manual" + echo "pages." + ;; + *) + echo "more information, such as the ld(1) and ld.so(8) manual pages." + ;; + esac + echo "----------------------------------------------------------------------" + fi + exit $EXIT_SUCCESS +} + +test finish = "$opt_mode" && func_mode_finish ${1+"$@"} + + +# func_mode_install arg... +func_mode_install () +{ + $debug_cmd + + # There may be an optional sh(1) argument at the beginning of + # install_prog (especially on Windows NT). + if test "$SHELL" = "$nonopt" || test /bin/sh = "$nonopt" || + # Allow the use of GNU shtool's install command. + case $nonopt in *shtool*) :;; *) false;; esac + then + # Aesthetically quote it. + func_quote_for_eval "$nonopt" + install_prog="$func_quote_for_eval_result " + arg=$1 + shift + else + install_prog= + arg=$nonopt + fi + + # The real first argument should be the name of the installation program. + # Aesthetically quote it. + func_quote_for_eval "$arg" + func_append install_prog "$func_quote_for_eval_result" + install_shared_prog=$install_prog + case " $install_prog " in + *[\\\ /]cp\ *) install_cp=: ;; + *) install_cp=false ;; + esac + + # We need to accept at least all the BSD install flags. + dest= + files= + opts= + prev= + install_type= + isdir=false + stripme= + no_mode=: + for arg + do + arg2= + if test -n "$dest"; then + func_append files " $dest" + dest=$arg + continue + fi + + case $arg in + -d) isdir=: ;; + -f) + if $install_cp; then :; else + prev=$arg + fi + ;; + -g | -m | -o) + prev=$arg + ;; + -s) + stripme=" -s" + continue + ;; + -*) + ;; + *) + # If the previous option needed an argument, then skip it. + if test -n "$prev"; then + if test X-m = "X$prev" && test -n "$install_override_mode"; then + arg2=$install_override_mode + no_mode=false + fi + prev= + else + dest=$arg + continue + fi + ;; + esac + + # Aesthetically quote the argument. + func_quote_for_eval "$arg" + func_append install_prog " $func_quote_for_eval_result" + if test -n "$arg2"; then + func_quote_for_eval "$arg2" + fi + func_append install_shared_prog " $func_quote_for_eval_result" + done + + test -z "$install_prog" && \ + func_fatal_help "you must specify an install program" + + test -n "$prev" && \ + func_fatal_help "the '$prev' option requires an argument" + + if test -n "$install_override_mode" && $no_mode; then + if $install_cp; then :; else + func_quote_for_eval "$install_override_mode" + func_append install_shared_prog " -m $func_quote_for_eval_result" + fi + fi + + if test -z "$files"; then + if test -z "$dest"; then + func_fatal_help "no file or destination specified" + else + func_fatal_help "you must specify a destination" + fi + fi + + # Strip any trailing slash from the destination. + func_stripname '' '/' "$dest" + dest=$func_stripname_result + + # Check to see that the destination is a directory. + test -d "$dest" && isdir=: + if $isdir; then + destdir=$dest + destname= + else + func_dirname_and_basename "$dest" "" "." + destdir=$func_dirname_result + destname=$func_basename_result + + # Not a directory, so check to see that there is only one file specified. + set dummy $files; shift + test "$#" -gt 1 && \ + func_fatal_help "'$dest' is not a directory" + fi + case $destdir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + for file in $files; do + case $file in + *.lo) ;; + *) + func_fatal_help "'$destdir' must be an absolute directory name" + ;; + esac + done + ;; + esac + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic=$magic + + staticlibs= + future_libdirs= + current_libdirs= + for file in $files; do + + # Do each installation. + case $file in + *.$libext) + # Do the static libraries later. + func_append staticlibs " $file" + ;; + + *.la) + func_resolve_sysroot "$file" + file=$func_resolve_sysroot_result + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$file" \ + || func_fatal_help "'$file' is not a valid libtool archive" + + library_names= + old_library= + relink_command= + func_source "$file" + + # Add the libdir to current_libdirs if it is the destination. + if test "X$destdir" = "X$libdir"; then + case "$current_libdirs " in + *" $libdir "*) ;; + *) func_append current_libdirs " $libdir" ;; + esac + else + # Note the libdir as a future libdir. + case "$future_libdirs " in + *" $libdir "*) ;; + *) func_append future_libdirs " $libdir" ;; + esac + fi + + func_dirname "$file" "/" "" + dir=$func_dirname_result + func_append dir "$objdir" + + if test -n "$relink_command"; then + # Determine the prefix the user has applied to our future dir. + inst_prefix_dir=`$ECHO "$destdir" | $SED -e "s%$libdir\$%%"` + + # Don't allow the user to place us outside of our expected + # location b/c this prevents finding dependent libraries that + # are installed to the same prefix. + # At present, this check doesn't affect windows .dll's that + # are installed into $libdir/../bin (currently, that works fine) + # but it's something to keep an eye on. + test "$inst_prefix_dir" = "$destdir" && \ + func_fatal_error "error: cannot install '$file' to a directory not ending in $libdir" + + if test -n "$inst_prefix_dir"; then + # Stick the inst_prefix_dir data into the link command. + relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%-inst-prefix-dir $inst_prefix_dir%"` + else + relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%%"` + fi + + func_warning "relinking '$file'" + func_show_eval "$relink_command" \ + 'func_fatal_error "error: relink '\''$file'\'' with the above command before installing it"' + fi + + # See the names of the shared library. + set dummy $library_names; shift + if test -n "$1"; then + realname=$1 + shift + + srcname=$realname + test -n "$relink_command" && srcname=${realname}T + + # Install the shared library and build the symlinks. + func_show_eval "$install_shared_prog $dir/$srcname $destdir/$realname" \ + 'exit $?' + tstripme=$stripme + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + case $realname in + *.dll.a) + tstripme= + ;; + esac + ;; + os2*) + case $realname in + *_dll.a) + tstripme= + ;; + esac + ;; + esac + if test -n "$tstripme" && test -n "$striplib"; then + func_show_eval "$striplib $destdir/$realname" 'exit $?' + fi + + if test "$#" -gt 0; then + # Delete the old symlinks, and create new ones. + # Try 'ln -sf' first, because the 'ln' binary might depend on + # the symlink we replace! Solaris /bin/ln does not understand -f, + # so we also need to try rm && ln -s. + for linkname + do + test "$linkname" != "$realname" \ + && func_show_eval "(cd $destdir && { $LN_S -f $realname $linkname || { $RM $linkname && $LN_S $realname $linkname; }; })" + done + fi + + # Do each command in the postinstall commands. + lib=$destdir/$realname + func_execute_cmds "$postinstall_cmds" 'exit $?' + fi + + # Install the pseudo-library for information purposes. + func_basename "$file" + name=$func_basename_result + instname=$dir/${name}i + func_show_eval "$install_prog $instname $destdir/$name" 'exit $?' + + # Maybe install the static library, too. + test -n "$old_library" && func_append staticlibs " $dir/$old_library" + ;; + + *.lo) + # Install (i.e. copy) a libtool object. + + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile=$destdir/$destname + else + func_basename "$file" + destfile=$func_basename_result + destfile=$destdir/$destfile + fi + + # Deduce the name of the destination old-style object file. + case $destfile in + *.lo) + func_lo2o "$destfile" + staticdest=$func_lo2o_result + ;; + *.$objext) + staticdest=$destfile + destfile= + ;; + *) + func_fatal_help "cannot copy a libtool object to '$destfile'" + ;; + esac + + # Install the libtool object if requested. + test -n "$destfile" && \ + func_show_eval "$install_prog $file $destfile" 'exit $?' + + # Install the old object if enabled. + if test yes = "$build_old_libs"; then + # Deduce the name of the old-style object file. + func_lo2o "$file" + staticobj=$func_lo2o_result + func_show_eval "$install_prog \$staticobj \$staticdest" 'exit $?' + fi + exit $EXIT_SUCCESS + ;; + + *) + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile=$destdir/$destname + else + func_basename "$file" + destfile=$func_basename_result + destfile=$destdir/$destfile + fi + + # If the file is missing, and there is a .exe on the end, strip it + # because it is most likely a libtool script we actually want to + # install + stripped_ext= + case $file in + *.exe) + if test ! -f "$file"; then + func_stripname '' '.exe' "$file" + file=$func_stripname_result + stripped_ext=.exe + fi + ;; + esac + + # Do a test to see if this is really a libtool program. + case $host in + *cygwin* | *mingw*) + if func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + wrapper=$func_ltwrapper_scriptname_result + else + func_stripname '' '.exe' "$file" + wrapper=$func_stripname_result + fi + ;; + *) + wrapper=$file + ;; + esac + if func_ltwrapper_script_p "$wrapper"; then + notinst_deplibs= + relink_command= + + func_source "$wrapper" + + # Check the variables that should have been set. + test -z "$generated_by_libtool_version" && \ + func_fatal_error "invalid libtool wrapper script '$wrapper'" + + finalize=: + for lib in $notinst_deplibs; do + # Check to see that each library is installed. + libdir= + if test -f "$lib"; then + func_source "$lib" + fi + libfile=$libdir/`$ECHO "$lib" | $SED 's%^.*/%%g'` + if test -n "$libdir" && test ! -f "$libfile"; then + func_warning "'$lib' has not been installed in '$libdir'" + finalize=false + fi + done + + relink_command= + func_source "$wrapper" + + outputname= + if test no = "$fast_install" && test -n "$relink_command"; then + $opt_dry_run || { + if $finalize; then + tmpdir=`func_mktempdir` + func_basename "$file$stripped_ext" + file=$func_basename_result + outputname=$tmpdir/$file + # Replace the output file specification. + relink_command=`$ECHO "$relink_command" | $SED 's%@OUTPUT@%'"$outputname"'%g'` + + $opt_quiet || { + func_quote_for_expand "$relink_command" + eval "func_echo $func_quote_for_expand_result" + } + if eval "$relink_command"; then : + else + func_error "error: relink '$file' with the above command before installing it" + $opt_dry_run || ${RM}r "$tmpdir" + continue + fi + file=$outputname + else + func_warning "cannot relink '$file'" + fi + } + else + # Install the binary that we compiled earlier. + file=`$ECHO "$file$stripped_ext" | $SED "s%\([^/]*\)$%$objdir/\1%"` + fi + fi + + # remove .exe since cygwin /usr/bin/install will append another + # one anyway + case $install_prog,$host in + */usr/bin/install*,*cygwin*) + case $file:$destfile in + *.exe:*.exe) + # this is ok + ;; + *.exe:*) + destfile=$destfile.exe + ;; + *:*.exe) + func_stripname '' '.exe' "$destfile" + destfile=$func_stripname_result + ;; + esac + ;; + esac + func_show_eval "$install_prog\$stripme \$file \$destfile" 'exit $?' + $opt_dry_run || if test -n "$outputname"; then + ${RM}r "$tmpdir" + fi + ;; + esac + done + + for file in $staticlibs; do + func_basename "$file" + name=$func_basename_result + + # Set up the ranlib parameters. + oldlib=$destdir/$name + func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 + tool_oldlib=$func_to_tool_file_result + + func_show_eval "$install_prog \$file \$oldlib" 'exit $?' + + if test -n "$stripme" && test -n "$old_striplib"; then + func_show_eval "$old_striplib $tool_oldlib" 'exit $?' + fi + + # Do each command in the postinstall commands. + func_execute_cmds "$old_postinstall_cmds" 'exit $?' + done + + test -n "$future_libdirs" && \ + func_warning "remember to run '$progname --finish$future_libdirs'" + + if test -n "$current_libdirs"; then + # Maybe just do a dry run. + $opt_dry_run && current_libdirs=" -n$current_libdirs" + exec_cmd='$SHELL "$progpath" $preserve_args --finish$current_libdirs' + else + exit $EXIT_SUCCESS + fi +} + +test install = "$opt_mode" && func_mode_install ${1+"$@"} + + +# func_generate_dlsyms outputname originator pic_p +# Extract symbols from dlprefiles and create ${outputname}S.o with +# a dlpreopen symbol table. +func_generate_dlsyms () +{ + $debug_cmd + + my_outputname=$1 + my_originator=$2 + my_pic_p=${3-false} + my_prefix=`$ECHO "$my_originator" | $SED 's%[^a-zA-Z0-9]%_%g'` + my_dlsyms= + + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + if test -n "$NM" && test -n "$global_symbol_pipe"; then + my_dlsyms=${my_outputname}S.c + else + func_error "not configured to extract global symbols from dlpreopened files" + fi + fi + + if test -n "$my_dlsyms"; then + case $my_dlsyms in + "") ;; + *.c) + # Discover the nlist of each of the dlfiles. + nlist=$output_objdir/$my_outputname.nm + + func_show_eval "$RM $nlist ${nlist}S ${nlist}T" + + # Parse the name list into a source file. + func_verbose "creating $output_objdir/$my_dlsyms" + + $opt_dry_run || $ECHO > "$output_objdir/$my_dlsyms" "\ +/* $my_dlsyms - symbol resolution table for '$my_outputname' dlsym emulation. */ +/* Generated by $PROGRAM (GNU $PACKAGE) $VERSION */ + +#ifdef __cplusplus +extern \"C\" { +#endif + +#if defined __GNUC__ && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4)) || (__GNUC__ > 4)) +#pragma GCC diagnostic ignored \"-Wstrict-prototypes\" +#endif + +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT_DLSYM_CONST +#else +# define LT_DLSYM_CONST const +#endif + +#define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) + +/* External symbol declarations for the compiler. */\ +" + + if test yes = "$dlself"; then + func_verbose "generating symbol list for '$output'" + + $opt_dry_run || echo ': @PROGRAM@ ' > "$nlist" + + # Add our own program objects to the symbol list. + progfiles=`$ECHO "$objs$old_deplibs" | $SP2NL | $SED "$lo2o" | $NL2SP` + for progfile in $progfiles; do + func_to_tool_file "$progfile" func_convert_file_msys_to_w32 + func_verbose "extracting global C symbols from '$func_to_tool_file_result'" + $opt_dry_run || eval "$NM $func_to_tool_file_result | $global_symbol_pipe >> '$nlist'" + done + + if test -n "$exclude_expsyms"; then + $opt_dry_run || { + eval '$EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + } + fi + + if test -n "$export_symbols_regex"; then + $opt_dry_run || { + eval '$EGREP -e "$export_symbols_regex" "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + } + fi + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + export_symbols=$output_objdir/$outputname.exp + $opt_dry_run || { + $RM $export_symbols + eval "$SED -n -e '/^: @PROGRAM@ $/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"' + case $host in + *cygwin* | *mingw* | *cegcc* ) + eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' + eval 'cat "$export_symbols" >> "$output_objdir/$outputname.def"' + ;; + esac + } + else + $opt_dry_run || { + eval "$SED -e 's/\([].[*^$]\)/\\\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$outputname.exp"' + eval '$GREP -f "$output_objdir/$outputname.exp" < "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + case $host in + *cygwin* | *mingw* | *cegcc* ) + eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' + eval 'cat "$nlist" >> "$output_objdir/$outputname.def"' + ;; + esac + } + fi + fi + + for dlprefile in $dlprefiles; do + func_verbose "extracting global C symbols from '$dlprefile'" + func_basename "$dlprefile" + name=$func_basename_result + case $host in + *cygwin* | *mingw* | *cegcc* ) + # if an import library, we need to obtain dlname + if func_win32_import_lib_p "$dlprefile"; then + func_tr_sh "$dlprefile" + eval "curr_lafile=\$libfile_$func_tr_sh_result" + dlprefile_dlbasename= + if test -n "$curr_lafile" && func_lalib_p "$curr_lafile"; then + # Use subshell, to avoid clobbering current variable values + dlprefile_dlname=`source "$curr_lafile" && echo "$dlname"` + if test -n "$dlprefile_dlname"; then + func_basename "$dlprefile_dlname" + dlprefile_dlbasename=$func_basename_result + else + # no lafile. user explicitly requested -dlpreopen <import library>. + $sharedlib_from_linklib_cmd "$dlprefile" + dlprefile_dlbasename=$sharedlib_from_linklib_result + fi + fi + $opt_dry_run || { + if test -n "$dlprefile_dlbasename"; then + eval '$ECHO ": $dlprefile_dlbasename" >> "$nlist"' + else + func_warning "Could not compute DLL name from $name" + eval '$ECHO ": $name " >> "$nlist"' + fi + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe | + $SED -e '/I __imp/d' -e 's/I __nm_/D /;s/_nm__//' >> '$nlist'" + } + else # not an import lib + $opt_dry_run || { + eval '$ECHO ": $name " >> "$nlist"' + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" + } + fi + ;; + *) + $opt_dry_run || { + eval '$ECHO ": $name " >> "$nlist"' + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" + } + ;; + esac + done + + $opt_dry_run || { + # Make sure we have at least an empty file. + test -f "$nlist" || : > "$nlist" + + if test -n "$exclude_expsyms"; then + $EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T + $MV "$nlist"T "$nlist" + fi + + # Try sorting and uniquifying the output. + if $GREP -v "^: " < "$nlist" | + if sort -k 3 </dev/null >/dev/null 2>&1; then + sort -k 3 + else + sort +2 + fi | + uniq > "$nlist"S; then + : + else + $GREP -v "^: " < "$nlist" > "$nlist"S + fi + + if test -f "$nlist"S; then + eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$my_dlsyms"' + else + echo '/* NONE */' >> "$output_objdir/$my_dlsyms" + fi + + func_show_eval '$RM "${nlist}I"' + if test -n "$global_symbol_to_import"; then + eval "$global_symbol_to_import"' < "$nlist"S > "$nlist"I' + fi + + echo >> "$output_objdir/$my_dlsyms" "\ + +/* The mapping between symbol names and symbols. */ +typedef struct { + const char *name; + void *address; +} lt_dlsymlist; +extern LT_DLSYM_CONST lt_dlsymlist +lt_${my_prefix}_LTX_preloaded_symbols[];\ +" + + if test -s "$nlist"I; then + echo >> "$output_objdir/$my_dlsyms" "\ +static void lt_syminit(void) +{ + LT_DLSYM_CONST lt_dlsymlist *symbol = lt_${my_prefix}_LTX_preloaded_symbols; + for (; symbol->name; ++symbol) + {" + $SED 's/.*/ if (STREQ (symbol->name, \"&\")) symbol->address = (void *) \&&;/' < "$nlist"I >> "$output_objdir/$my_dlsyms" + echo >> "$output_objdir/$my_dlsyms" "\ + } +}" + fi + echo >> "$output_objdir/$my_dlsyms" "\ +LT_DLSYM_CONST lt_dlsymlist +lt_${my_prefix}_LTX_preloaded_symbols[] = +{ {\"$my_originator\", (void *) 0}," + + if test -s "$nlist"I; then + echo >> "$output_objdir/$my_dlsyms" "\ + {\"@INIT@\", (void *) &lt_syminit}," + fi + + case $need_lib_prefix in + no) + eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$my_dlsyms" + ;; + *) + eval "$global_symbol_to_c_name_address_lib_prefix" < "$nlist" >> "$output_objdir/$my_dlsyms" + ;; + esac + echo >> "$output_objdir/$my_dlsyms" "\ + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt_${my_prefix}_LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif\ +" + } # !$opt_dry_run + + pic_flag_for_symtable= + case "$compile_command " in + *" -static "*) ;; + *) + case $host in + # compiling the symbol table file with pic_flag works around + # a FreeBSD bug that causes programs to crash when -lm is + # linked before any other PIC object. But we must not use + # pic_flag when linking with -static. The problem exists in + # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1. + *-*-freebsd2.*|*-*-freebsd3.0*|*-*-freebsdelf3.0*) + pic_flag_for_symtable=" $pic_flag -DFREEBSD_WORKAROUND" ;; + *-*-hpux*) + pic_flag_for_symtable=" $pic_flag" ;; + *) + $my_pic_p && pic_flag_for_symtable=" $pic_flag" + ;; + esac + ;; + esac + symtab_cflags= + for arg in $LTCFLAGS; do + case $arg in + -pie | -fpie | -fPIE) ;; + *) func_append symtab_cflags " $arg" ;; + esac + done + + # Now compile the dynamic symbol file. + func_show_eval '(cd $output_objdir && $LTCC$symtab_cflags -c$no_builtin_flag$pic_flag_for_symtable "$my_dlsyms")' 'exit $?' + + # Clean up the generated files. + func_show_eval '$RM "$output_objdir/$my_dlsyms" "$nlist" "${nlist}S" "${nlist}T" "${nlist}I"' + + # Transform the symbol file into the correct name. + symfileobj=$output_objdir/${my_outputname}S.$objext + case $host in + *cygwin* | *mingw* | *cegcc* ) + if test -f "$output_objdir/$my_outputname.def"; then + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` + else + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` + fi + ;; + *) + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` + ;; + esac + ;; + *) + func_fatal_error "unknown suffix for '$my_dlsyms'" + ;; + esac + else + # We keep going just in case the user didn't refer to + # lt_preloaded_symbols. The linker will fail if global_symbol_pipe + # really was required. + + # Nullify the symbol file. + compile_command=`$ECHO "$compile_command" | $SED "s% @SYMFILE@%%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s% @SYMFILE@%%"` + fi +} + +# func_cygming_gnu_implib_p ARG +# This predicate returns with zero status (TRUE) if +# ARG is a GNU/binutils-style import library. Returns +# with nonzero status (FALSE) otherwise. +func_cygming_gnu_implib_p () +{ + $debug_cmd + + func_to_tool_file "$1" func_convert_file_msys_to_w32 + func_cygming_gnu_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $EGREP ' (_head_[A-Za-z0-9_]+_[ad]l*|[A-Za-z0-9_]+_[ad]l*_iname)$'` + test -n "$func_cygming_gnu_implib_tmp" +} + +# func_cygming_ms_implib_p ARG +# This predicate returns with zero status (TRUE) if +# ARG is an MS-style import library. Returns +# with nonzero status (FALSE) otherwise. +func_cygming_ms_implib_p () +{ + $debug_cmd + + func_to_tool_file "$1" func_convert_file_msys_to_w32 + func_cygming_ms_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $GREP '_NULL_IMPORT_DESCRIPTOR'` + test -n "$func_cygming_ms_implib_tmp" +} + +# func_win32_libid arg +# return the library type of file 'arg' +# +# Need a lot of goo to handle *both* DLLs and import libs +# Has to be a shell function in order to 'eat' the argument +# that is supplied when $file_magic_command is called. +# Despite the name, also deal with 64 bit binaries. +func_win32_libid () +{ + $debug_cmd + + win32_libid_type=unknown + win32_fileres=`file -L $1 2>/dev/null` + case $win32_fileres in + *ar\ archive\ import\ library*) # definitely import + win32_libid_type="x86 archive import" + ;; + *ar\ archive*) # could be an import, or static + # Keep the egrep pattern in sync with the one in _LT_CHECK_MAGIC_METHOD. + if eval $OBJDUMP -f $1 | $SED -e '10q' 2>/dev/null | + $EGREP 'file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' >/dev/null; then + case $nm_interface in + "MS dumpbin") + if func_cygming_ms_implib_p "$1" || + func_cygming_gnu_implib_p "$1" + then + win32_nmres=import + else + win32_nmres= + fi + ;; + *) + func_to_tool_file "$1" func_convert_file_msys_to_w32 + win32_nmres=`eval $NM -f posix -A \"$func_to_tool_file_result\" | + $SED -n -e ' + 1,100{ + / I /{ + s|.*|import| + p + q + } + }'` + ;; + esac + case $win32_nmres in + import*) win32_libid_type="x86 archive import";; + *) win32_libid_type="x86 archive static";; + esac + fi + ;; + *DLL*) + win32_libid_type="x86 DLL" + ;; + *executable*) # but shell scripts are "executable" too... + case $win32_fileres in + *MS\ Windows\ PE\ Intel*) + win32_libid_type="x86 DLL" + ;; + esac + ;; + esac + $ECHO "$win32_libid_type" +} + +# func_cygming_dll_for_implib ARG +# +# Platform-specific function to extract the +# name of the DLL associated with the specified +# import library ARG. +# Invoked by eval'ing the libtool variable +# $sharedlib_from_linklib_cmd +# Result is available in the variable +# $sharedlib_from_linklib_result +func_cygming_dll_for_implib () +{ + $debug_cmd + + sharedlib_from_linklib_result=`$DLLTOOL --identify-strict --identify "$1"` +} + +# func_cygming_dll_for_implib_fallback_core SECTION_NAME LIBNAMEs +# +# The is the core of a fallback implementation of a +# platform-specific function to extract the name of the +# DLL associated with the specified import library LIBNAME. +# +# SECTION_NAME is either .idata$6 or .idata$7, depending +# on the platform and compiler that created the implib. +# +# Echos the name of the DLL associated with the +# specified import library. +func_cygming_dll_for_implib_fallback_core () +{ + $debug_cmd + + match_literal=`$ECHO "$1" | $SED "$sed_make_literal_regex"` + $OBJDUMP -s --section "$1" "$2" 2>/dev/null | + $SED '/^Contents of section '"$match_literal"':/{ + # Place marker at beginning of archive member dllname section + s/.*/====MARK====/ + p + d + } + # These lines can sometimes be longer than 43 characters, but + # are always uninteresting + /:[ ]*file format pe[i]\{,1\}-/d + /^In archive [^:]*:/d + # Ensure marker is printed + /^====MARK====/p + # Remove all lines with less than 43 characters + /^.\{43\}/!d + # From remaining lines, remove first 43 characters + s/^.\{43\}//' | + $SED -n ' + # Join marker and all lines until next marker into a single line + /^====MARK====/ b para + H + $ b para + b + :para + x + s/\n//g + # Remove the marker + s/^====MARK====// + # Remove trailing dots and whitespace + s/[\. \t]*$// + # Print + /./p' | + # we now have a list, one entry per line, of the stringified + # contents of the appropriate section of all members of the + # archive that possess that section. Heuristic: eliminate + # all those that have a first or second character that is + # a '.' (that is, objdump's representation of an unprintable + # character.) This should work for all archives with less than + # 0x302f exports -- but will fail for DLLs whose name actually + # begins with a literal '.' or a single character followed by + # a '.'. + # + # Of those that remain, print the first one. + $SED -e '/^\./d;/^.\./d;q' +} + +# func_cygming_dll_for_implib_fallback ARG +# Platform-specific function to extract the +# name of the DLL associated with the specified +# import library ARG. +# +# This fallback implementation is for use when $DLLTOOL +# does not support the --identify-strict option. +# Invoked by eval'ing the libtool variable +# $sharedlib_from_linklib_cmd +# Result is available in the variable +# $sharedlib_from_linklib_result +func_cygming_dll_for_implib_fallback () +{ + $debug_cmd + + if func_cygming_gnu_implib_p "$1"; then + # binutils import library + sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$7' "$1"` + elif func_cygming_ms_implib_p "$1"; then + # ms-generated import library + sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$6' "$1"` + else + # unknown + sharedlib_from_linklib_result= + fi +} + + +# func_extract_an_archive dir oldlib +func_extract_an_archive () +{ + $debug_cmd + + f_ex_an_ar_dir=$1; shift + f_ex_an_ar_oldlib=$1 + if test yes = "$lock_old_archive_extraction"; then + lockfile=$f_ex_an_ar_oldlib.lock + until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do + func_echo "Waiting for $lockfile to be removed" + sleep 2 + done + fi + func_show_eval "(cd \$f_ex_an_ar_dir && $AR x \"\$f_ex_an_ar_oldlib\")" \ + 'stat=$?; rm -f "$lockfile"; exit $stat' + if test yes = "$lock_old_archive_extraction"; then + $opt_dry_run || rm -f "$lockfile" + fi + if ($AR t "$f_ex_an_ar_oldlib" | sort | sort -uc >/dev/null 2>&1); then + : + else + func_fatal_error "object name conflicts in archive: $f_ex_an_ar_dir/$f_ex_an_ar_oldlib" + fi +} + + +# func_extract_archives gentop oldlib ... +func_extract_archives () +{ + $debug_cmd + + my_gentop=$1; shift + my_oldlibs=${1+"$@"} + my_oldobjs= + my_xlib= + my_xabs= + my_xdir= + + for my_xlib in $my_oldlibs; do + # Extract the objects. + case $my_xlib in + [\\/]* | [A-Za-z]:[\\/]*) my_xabs=$my_xlib ;; + *) my_xabs=`pwd`"/$my_xlib" ;; + esac + func_basename "$my_xlib" + my_xlib=$func_basename_result + my_xlib_u=$my_xlib + while :; do + case " $extracted_archives " in + *" $my_xlib_u "*) + func_arith $extracted_serial + 1 + extracted_serial=$func_arith_result + my_xlib_u=lt$extracted_serial-$my_xlib ;; + *) break ;; + esac + done + extracted_archives="$extracted_archives $my_xlib_u" + my_xdir=$my_gentop/$my_xlib_u + + func_mkdir_p "$my_xdir" + + case $host in + *-darwin*) + func_verbose "Extracting $my_xabs" + # Do not bother doing anything if just a dry run + $opt_dry_run || { + darwin_orig_dir=`pwd` + cd $my_xdir || exit $? + darwin_archive=$my_xabs + darwin_curdir=`pwd` + func_basename "$darwin_archive" + darwin_base_archive=$func_basename_result + darwin_arches=`$LIPO -info "$darwin_archive" 2>/dev/null | $GREP Architectures 2>/dev/null || true` + if test -n "$darwin_arches"; then + darwin_arches=`$ECHO "$darwin_arches" | $SED -e 's/.*are://'` + darwin_arch= + func_verbose "$darwin_base_archive has multiple architectures $darwin_arches" + for darwin_arch in $darwin_arches; do + func_mkdir_p "unfat-$$/$darwin_base_archive-$darwin_arch" + $LIPO -thin $darwin_arch -output "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" "$darwin_archive" + cd "unfat-$$/$darwin_base_archive-$darwin_arch" + func_extract_an_archive "`pwd`" "$darwin_base_archive" + cd "$darwin_curdir" + $RM "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" + done # $darwin_arches + ## Okay now we've a bunch of thin objects, gotta fatten them up :) + darwin_filelist=`find unfat-$$ -type f -name \*.o -print -o -name \*.lo -print | $SED -e "$sed_basename" | sort -u` + darwin_file= + darwin_files= + for darwin_file in $darwin_filelist; do + darwin_files=`find unfat-$$ -name $darwin_file -print | sort | $NL2SP` + $LIPO -create -output "$darwin_file" $darwin_files + done # $darwin_filelist + $RM -rf unfat-$$ + cd "$darwin_orig_dir" + else + cd $darwin_orig_dir + func_extract_an_archive "$my_xdir" "$my_xabs" + fi # $darwin_arches + } # !$opt_dry_run + ;; + *) + func_extract_an_archive "$my_xdir" "$my_xabs" + ;; + esac + my_oldobjs="$my_oldobjs "`find $my_xdir -name \*.$objext -print -o -name \*.lo -print | sort | $NL2SP` + done + + func_extract_archives_result=$my_oldobjs +} + + +# func_emit_wrapper [arg=no] +# +# Emit a libtool wrapper script on stdout. +# Don't directly open a file because we may want to +# incorporate the script contents within a cygwin/mingw +# wrapper executable. Must ONLY be called from within +# func_mode_link because it depends on a number of variables +# set therein. +# +# ARG is the value that the WRAPPER_SCRIPT_BELONGS_IN_OBJDIR +# variable will take. If 'yes', then the emitted script +# will assume that the directory where it is stored is +# the $objdir directory. This is a cygwin/mingw-specific +# behavior. +func_emit_wrapper () +{ + func_emit_wrapper_arg1=${1-no} + + $ECHO "\ +#! $SHELL + +# $output - temporary wrapper script for $objdir/$outputname +# Generated by $PROGRAM (GNU $PACKAGE) $VERSION +# +# The $output program cannot be directly executed until all the libtool +# libraries that it depends on are installed. +# +# This wrapper script should never be moved out of the build directory. +# If it is, it will not operate correctly. + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='$sed_quote_subst' + +# Be Bourne compatible +if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Zsh 3.x and 4.x performs word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in *posix*) set -o posix;; esac +fi +BIN_SH=xpg4; export BIN_SH # for Tru64 +DUALCASE=1; export DUALCASE # for MKS sh + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +relink_command=\"$relink_command\" + +# This environment variable determines our operation mode. +if test \"\$libtool_install_magic\" = \"$magic\"; then + # install mode needs the following variables: + generated_by_libtool_version='$macro_version' + notinst_deplibs='$notinst_deplibs' +else + # When we are sourced in execute mode, \$file and \$ECHO are already set. + if test \"\$libtool_execute_magic\" != \"$magic\"; then + file=\"\$0\"" + + qECHO=`$ECHO "$ECHO" | $SED "$sed_quote_subst"` + $ECHO "\ + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$1 +_LTECHO_EOF' +} + ECHO=\"$qECHO\" + fi + +# Very basic option parsing. These options are (a) specific to +# the libtool wrapper, (b) are identical between the wrapper +# /script/ and the wrapper /executable/ that is used only on +# windows platforms, and (c) all begin with the string "--lt-" +# (application programs are unlikely to have options that match +# this pattern). +# +# There are only two supported options: --lt-debug and +# --lt-dump-script. There is, deliberately, no --lt-help. +# +# The first argument to this parsing function should be the +# script's $0 value, followed by "$@". +lt_option_debug= +func_parse_lt_options () +{ + lt_script_arg0=\$0 + shift + for lt_opt + do + case \"\$lt_opt\" in + --lt-debug) lt_option_debug=1 ;; + --lt-dump-script) + lt_dump_D=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%/[^/]*$%%'\` + test \"X\$lt_dump_D\" = \"X\$lt_script_arg0\" && lt_dump_D=. + lt_dump_F=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%^.*/%%'\` + cat \"\$lt_dump_D/\$lt_dump_F\" + exit 0 + ;; + --lt-*) + \$ECHO \"Unrecognized --lt- option: '\$lt_opt'\" 1>&2 + exit 1 + ;; + esac + done + + # Print the debug banner immediately: + if test -n \"\$lt_option_debug\"; then + echo \"$outputname:$output:\$LINENO: libtool wrapper (GNU $PACKAGE) $VERSION\" 1>&2 + fi +} + +# Used when --lt-debug. Prints its arguments to stdout +# (redirection is the responsibility of the caller) +func_lt_dump_args () +{ + lt_dump_args_N=1; + for lt_arg + do + \$ECHO \"$outputname:$output:\$LINENO: newargv[\$lt_dump_args_N]: \$lt_arg\" + lt_dump_args_N=\`expr \$lt_dump_args_N + 1\` + done +} + +# Core function for launching the target application +func_exec_program_core () +{ +" + case $host in + # Backslashes separate directories on plain windows + *-*-mingw | *-*-os2* | *-cegcc*) + $ECHO "\ + if test -n \"\$lt_option_debug\"; then + \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir\\\\\$program\" 1>&2 + func_lt_dump_args \${1+\"\$@\"} 1>&2 + fi + exec \"\$progdir\\\\\$program\" \${1+\"\$@\"} +" + ;; + + *) + $ECHO "\ + if test -n \"\$lt_option_debug\"; then + \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir/\$program\" 1>&2 + func_lt_dump_args \${1+\"\$@\"} 1>&2 + fi + exec \"\$progdir/\$program\" \${1+\"\$@\"} +" + ;; + esac + $ECHO "\ + \$ECHO \"\$0: cannot exec \$program \$*\" 1>&2 + exit 1 +} + +# A function to encapsulate launching the target application +# Strips options in the --lt-* namespace from \$@ and +# launches target application with the remaining arguments. +func_exec_program () +{ + case \" \$* \" in + *\\ --lt-*) + for lt_wr_arg + do + case \$lt_wr_arg in + --lt-*) ;; + *) set x \"\$@\" \"\$lt_wr_arg\"; shift;; + esac + shift + done ;; + esac + func_exec_program_core \${1+\"\$@\"} +} + + # Parse options + func_parse_lt_options \"\$0\" \${1+\"\$@\"} + + # Find the directory that this script lives in. + thisdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*$%%'\` + test \"x\$thisdir\" = \"x\$file\" && thisdir=. + + # Follow symbolic links until we get to the real thisdir. + file=\`ls -ld \"\$file\" | $SED -n 's/.*-> //p'\` + while test -n \"\$file\"; do + destdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*\$%%'\` + + # If there was a directory component, then change thisdir. + if test \"x\$destdir\" != \"x\$file\"; then + case \"\$destdir\" in + [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;; + *) thisdir=\"\$thisdir/\$destdir\" ;; + esac + fi + + file=\`\$ECHO \"\$file\" | $SED 's%^.*/%%'\` + file=\`ls -ld \"\$thisdir/\$file\" | $SED -n 's/.*-> //p'\` + done + + # Usually 'no', except on cygwin/mingw when embedded into + # the cwrapper. + WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=$func_emit_wrapper_arg1 + if test \"\$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR\" = \"yes\"; then + # special case for '.' + if test \"\$thisdir\" = \".\"; then + thisdir=\`pwd\` + fi + # remove .libs from thisdir + case \"\$thisdir\" in + *[\\\\/]$objdir ) thisdir=\`\$ECHO \"\$thisdir\" | $SED 's%[\\\\/][^\\\\/]*$%%'\` ;; + $objdir ) thisdir=. ;; + esac + fi + + # Try to get the absolute directory name. + absdir=\`cd \"\$thisdir\" && pwd\` + test -n \"\$absdir\" && thisdir=\"\$absdir\" +" + + if test yes = "$fast_install"; then + $ECHO "\ + program=lt-'$outputname'$exeext + progdir=\"\$thisdir/$objdir\" + + if test ! -f \"\$progdir/\$program\" || + { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | $SED 1q\`; \\ + test \"X\$file\" != \"X\$progdir/\$program\"; }; then + + file=\"\$\$-\$program\" + + if test ! -d \"\$progdir\"; then + $MKDIR \"\$progdir\" + else + $RM \"\$progdir/\$file\" + fi" + + $ECHO "\ + + # relink executable if necessary + if test -n \"\$relink_command\"; then + if relink_command_output=\`eval \$relink_command 2>&1\`; then : + else + \$ECHO \"\$relink_command_output\" >&2 + $RM \"\$progdir/\$file\" + exit 1 + fi + fi + + $MV \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null || + { $RM \"\$progdir/\$program\"; + $MV \"\$progdir/\$file\" \"\$progdir/\$program\"; } + $RM \"\$progdir/\$file\" + fi" + else + $ECHO "\ + program='$outputname' + progdir=\"\$thisdir/$objdir\" +" + fi + + $ECHO "\ + + if test -f \"\$progdir/\$program\"; then" + + # fixup the dll searchpath if we need to. + # + # Fix the DLL searchpath if we need to. Do this before prepending + # to shlibpath, because on Windows, both are PATH and uninstalled + # libraries must come first. + if test -n "$dllsearchpath"; then + $ECHO "\ + # Add the dll search path components to the executable PATH + PATH=$dllsearchpath:\$PATH +" + fi + + # Export our shlibpath_var if we have one. + if test yes = "$shlibpath_overrides_runpath" && test -n "$shlibpath_var" && test -n "$temp_rpath"; then + $ECHO "\ + # Add our own library path to $shlibpath_var + $shlibpath_var=\"$temp_rpath\$$shlibpath_var\" + + # Some systems cannot cope with colon-terminated $shlibpath_var + # The second colon is a workaround for a bug in BeOS R4 sed + $shlibpath_var=\`\$ECHO \"\$$shlibpath_var\" | $SED 's/::*\$//'\` + + export $shlibpath_var +" + fi + + $ECHO "\ + if test \"\$libtool_execute_magic\" != \"$magic\"; then + # Run the actual program with our arguments. + func_exec_program \${1+\"\$@\"} + fi + else + # The program doesn't exist. + \$ECHO \"\$0: error: '\$progdir/\$program' does not exist\" 1>&2 + \$ECHO \"This script is just a wrapper for \$program.\" 1>&2 + \$ECHO \"See the $PACKAGE documentation for more information.\" 1>&2 + exit 1 + fi +fi\ +" +} + + +# func_emit_cwrapperexe_src +# emit the source code for a wrapper executable on stdout +# Must ONLY be called from within func_mode_link because +# it depends on a number of variable set therein. +func_emit_cwrapperexe_src () +{ + cat <<EOF + +/* $cwrappersource - temporary wrapper executable for $objdir/$outputname + Generated by $PROGRAM (GNU $PACKAGE) $VERSION + + The $output program cannot be directly executed until all the libtool + libraries that it depends on are installed. + + This wrapper executable should never be moved out of the build directory. + If it is, it will not operate correctly. +*/ +EOF + cat <<"EOF" +#ifdef _MSC_VER +# define _CRT_SECURE_NO_DEPRECATE 1 +#endif +#include <stdio.h> +#include <stdlib.h> +#ifdef _MSC_VER +# include <direct.h> +# include <process.h> +# include <io.h> +#else +# include <unistd.h> +# include <stdint.h> +# ifdef __CYGWIN__ +# include <io.h> +# endif +#endif +#include <malloc.h> +#include <stdarg.h> +#include <assert.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> + +#define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) + +/* declarations of non-ANSI functions */ +#if defined __MINGW32__ +# ifdef __STRICT_ANSI__ +int _putenv (const char *); +# endif +#elif defined __CYGWIN__ +# ifdef __STRICT_ANSI__ +char *realpath (const char *, char *); +int putenv (char *); +int setenv (const char *, const char *, int); +# endif +/* #elif defined other_platform || defined ... */ +#endif + +/* portability defines, excluding path handling macros */ +#if defined _MSC_VER +# define setmode _setmode +# define stat _stat +# define chmod _chmod +# define getcwd _getcwd +# define putenv _putenv +# define S_IXUSR _S_IEXEC +#elif defined __MINGW32__ +# define setmode _setmode +# define stat _stat +# define chmod _chmod +# define getcwd _getcwd +# define putenv _putenv +#elif defined __CYGWIN__ +# define HAVE_SETENV +# define FOPEN_WB "wb" +/* #elif defined other platforms ... */ +#endif + +#if defined PATH_MAX +# define LT_PATHMAX PATH_MAX +#elif defined MAXPATHLEN +# define LT_PATHMAX MAXPATHLEN +#else +# define LT_PATHMAX 1024 +#endif + +#ifndef S_IXOTH +# define S_IXOTH 0 +#endif +#ifndef S_IXGRP +# define S_IXGRP 0 +#endif + +/* path handling portability macros */ +#ifndef DIR_SEPARATOR +# define DIR_SEPARATOR '/' +# define PATH_SEPARATOR ':' +#endif + +#if defined _WIN32 || defined __MSDOS__ || defined __DJGPP__ || \ + defined __OS2__ +# define HAVE_DOS_BASED_FILE_SYSTEM +# define FOPEN_WB "wb" +# ifndef DIR_SEPARATOR_2 +# define DIR_SEPARATOR_2 '\\' +# endif +# ifndef PATH_SEPARATOR_2 +# define PATH_SEPARATOR_2 ';' +# endif +#endif + +#ifndef DIR_SEPARATOR_2 +# define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR) +#else /* DIR_SEPARATOR_2 */ +# define IS_DIR_SEPARATOR(ch) \ + (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2)) +#endif /* DIR_SEPARATOR_2 */ + +#ifndef PATH_SEPARATOR_2 +# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR) +#else /* PATH_SEPARATOR_2 */ +# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2) +#endif /* PATH_SEPARATOR_2 */ + +#ifndef FOPEN_WB +# define FOPEN_WB "w" +#endif +#ifndef _O_BINARY +# define _O_BINARY 0 +#endif + +#define XMALLOC(type, num) ((type *) xmalloc ((num) * sizeof(type))) +#define XFREE(stale) do { \ + if (stale) { free (stale); stale = 0; } \ +} while (0) + +#if defined LT_DEBUGWRAPPER +static int lt_debug = 1; +#else +static int lt_debug = 0; +#endif + +const char *program_name = "libtool-wrapper"; /* in case xstrdup fails */ + +void *xmalloc (size_t num); +char *xstrdup (const char *string); +const char *base_name (const char *name); +char *find_executable (const char *wrapper); +char *chase_symlinks (const char *pathspec); +int make_executable (const char *path); +int check_executable (const char *path); +char *strendzap (char *str, const char *pat); +void lt_debugprintf (const char *file, int line, const char *fmt, ...); +void lt_fatal (const char *file, int line, const char *message, ...); +static const char *nonnull (const char *s); +static const char *nonempty (const char *s); +void lt_setenv (const char *name, const char *value); +char *lt_extend_str (const char *orig_value, const char *add, int to_end); +void lt_update_exe_path (const char *name, const char *value); +void lt_update_lib_path (const char *name, const char *value); +char **prepare_spawn (char **argv); +void lt_dump_script (FILE *f); +EOF + + cat <<EOF +#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 5) +# define externally_visible volatile +#else +# define externally_visible __attribute__((externally_visible)) volatile +#endif +externally_visible const char * MAGIC_EXE = "$magic_exe"; +const char * LIB_PATH_VARNAME = "$shlibpath_var"; +EOF + + if test yes = "$shlibpath_overrides_runpath" && test -n "$shlibpath_var" && test -n "$temp_rpath"; then + func_to_host_path "$temp_rpath" + cat <<EOF +const char * LIB_PATH_VALUE = "$func_to_host_path_result"; +EOF + else + cat <<"EOF" +const char * LIB_PATH_VALUE = ""; +EOF + fi + + if test -n "$dllsearchpath"; then + func_to_host_path "$dllsearchpath:" + cat <<EOF +const char * EXE_PATH_VARNAME = "PATH"; +const char * EXE_PATH_VALUE = "$func_to_host_path_result"; +EOF + else + cat <<"EOF" +const char * EXE_PATH_VARNAME = ""; +const char * EXE_PATH_VALUE = ""; +EOF + fi + + if test yes = "$fast_install"; then + cat <<EOF +const char * TARGET_PROGRAM_NAME = "lt-$outputname"; /* hopefully, no .exe */ +EOF + else + cat <<EOF +const char * TARGET_PROGRAM_NAME = "$outputname"; /* hopefully, no .exe */ +EOF + fi + + + cat <<"EOF" + +#define LTWRAPPER_OPTION_PREFIX "--lt-" + +static const char *ltwrapper_option_prefix = LTWRAPPER_OPTION_PREFIX; +static const char *dumpscript_opt = LTWRAPPER_OPTION_PREFIX "dump-script"; +static const char *debug_opt = LTWRAPPER_OPTION_PREFIX "debug"; + +int +main (int argc, char *argv[]) +{ + char **newargz; + int newargc; + char *tmp_pathspec; + char *actual_cwrapper_path; + char *actual_cwrapper_name; + char *target_name; + char *lt_argv_zero; + int rval = 127; + + int i; + + program_name = (char *) xstrdup (base_name (argv[0])); + newargz = XMALLOC (char *, (size_t) argc + 1); + + /* very simple arg parsing; don't want to rely on getopt + * also, copy all non cwrapper options to newargz, except + * argz[0], which is handled differently + */ + newargc=0; + for (i = 1; i < argc; i++) + { + if (STREQ (argv[i], dumpscript_opt)) + { +EOF + case $host in + *mingw* | *cygwin* ) + # make stdout use "unix" line endings + echo " setmode(1,_O_BINARY);" + ;; + esac + + cat <<"EOF" + lt_dump_script (stdout); + return 0; + } + if (STREQ (argv[i], debug_opt)) + { + lt_debug = 1; + continue; + } + if (STREQ (argv[i], ltwrapper_option_prefix)) + { + /* however, if there is an option in the LTWRAPPER_OPTION_PREFIX + namespace, but it is not one of the ones we know about and + have already dealt with, above (inluding dump-script), then + report an error. Otherwise, targets might begin to believe + they are allowed to use options in the LTWRAPPER_OPTION_PREFIX + namespace. The first time any user complains about this, we'll + need to make LTWRAPPER_OPTION_PREFIX a configure-time option + or a configure.ac-settable value. + */ + lt_fatal (__FILE__, __LINE__, + "unrecognized %s option: '%s'", + ltwrapper_option_prefix, argv[i]); + } + /* otherwise ... */ + newargz[++newargc] = xstrdup (argv[i]); + } + newargz[++newargc] = NULL; + +EOF + cat <<EOF + /* The GNU banner must be the first non-error debug message */ + lt_debugprintf (__FILE__, __LINE__, "libtool wrapper (GNU $PACKAGE) $VERSION\n"); +EOF + cat <<"EOF" + lt_debugprintf (__FILE__, __LINE__, "(main) argv[0]: %s\n", argv[0]); + lt_debugprintf (__FILE__, __LINE__, "(main) program_name: %s\n", program_name); + + tmp_pathspec = find_executable (argv[0]); + if (tmp_pathspec == NULL) + lt_fatal (__FILE__, __LINE__, "couldn't find %s", argv[0]); + lt_debugprintf (__FILE__, __LINE__, + "(main) found exe (before symlink chase) at: %s\n", + tmp_pathspec); + + actual_cwrapper_path = chase_symlinks (tmp_pathspec); + lt_debugprintf (__FILE__, __LINE__, + "(main) found exe (after symlink chase) at: %s\n", + actual_cwrapper_path); + XFREE (tmp_pathspec); + + actual_cwrapper_name = xstrdup (base_name (actual_cwrapper_path)); + strendzap (actual_cwrapper_path, actual_cwrapper_name); + + /* wrapper name transforms */ + strendzap (actual_cwrapper_name, ".exe"); + tmp_pathspec = lt_extend_str (actual_cwrapper_name, ".exe", 1); + XFREE (actual_cwrapper_name); + actual_cwrapper_name = tmp_pathspec; + tmp_pathspec = 0; + + /* target_name transforms -- use actual target program name; might have lt- prefix */ + target_name = xstrdup (base_name (TARGET_PROGRAM_NAME)); + strendzap (target_name, ".exe"); + tmp_pathspec = lt_extend_str (target_name, ".exe", 1); + XFREE (target_name); + target_name = tmp_pathspec; + tmp_pathspec = 0; + + lt_debugprintf (__FILE__, __LINE__, + "(main) libtool target name: %s\n", + target_name); +EOF + + cat <<EOF + newargz[0] = + XMALLOC (char, (strlen (actual_cwrapper_path) + + strlen ("$objdir") + 1 + strlen (actual_cwrapper_name) + 1)); + strcpy (newargz[0], actual_cwrapper_path); + strcat (newargz[0], "$objdir"); + strcat (newargz[0], "/"); +EOF + + cat <<"EOF" + /* stop here, and copy so we don't have to do this twice */ + tmp_pathspec = xstrdup (newargz[0]); + + /* do NOT want the lt- prefix here, so use actual_cwrapper_name */ + strcat (newargz[0], actual_cwrapper_name); + + /* DO want the lt- prefix here if it exists, so use target_name */ + lt_argv_zero = lt_extend_str (tmp_pathspec, target_name, 1); + XFREE (tmp_pathspec); + tmp_pathspec = NULL; +EOF + + case $host_os in + mingw*) + cat <<"EOF" + { + char* p; + while ((p = strchr (newargz[0], '\\')) != NULL) + { + *p = '/'; + } + while ((p = strchr (lt_argv_zero, '\\')) != NULL) + { + *p = '/'; + } + } +EOF + ;; + esac + + cat <<"EOF" + XFREE (target_name); + XFREE (actual_cwrapper_path); + XFREE (actual_cwrapper_name); + + lt_setenv ("BIN_SH", "xpg4"); /* for Tru64 */ + lt_setenv ("DUALCASE", "1"); /* for MSK sh */ + /* Update the DLL searchpath. EXE_PATH_VALUE ($dllsearchpath) must + be prepended before (that is, appear after) LIB_PATH_VALUE ($temp_rpath) + because on Windows, both *_VARNAMEs are PATH but uninstalled + libraries must come first. */ + lt_update_exe_path (EXE_PATH_VARNAME, EXE_PATH_VALUE); + lt_update_lib_path (LIB_PATH_VARNAME, LIB_PATH_VALUE); + + lt_debugprintf (__FILE__, __LINE__, "(main) lt_argv_zero: %s\n", + nonnull (lt_argv_zero)); + for (i = 0; i < newargc; i++) + { + lt_debugprintf (__FILE__, __LINE__, "(main) newargz[%d]: %s\n", + i, nonnull (newargz[i])); + } + +EOF + + case $host_os in + mingw*) + cat <<"EOF" + /* execv doesn't actually work on mingw as expected on unix */ + newargz = prepare_spawn (newargz); + rval = (int) _spawnv (_P_WAIT, lt_argv_zero, (const char * const *) newargz); + if (rval == -1) + { + /* failed to start process */ + lt_debugprintf (__FILE__, __LINE__, + "(main) failed to launch target \"%s\": %s\n", + lt_argv_zero, nonnull (strerror (errno))); + return 127; + } + return rval; +EOF + ;; + *) + cat <<"EOF" + execv (lt_argv_zero, newargz); + return rval; /* =127, but avoids unused variable warning */ +EOF + ;; + esac + + cat <<"EOF" +} + +void * +xmalloc (size_t num) +{ + void *p = (void *) malloc (num); + if (!p) + lt_fatal (__FILE__, __LINE__, "memory exhausted"); + + return p; +} + +char * +xstrdup (const char *string) +{ + return string ? strcpy ((char *) xmalloc (strlen (string) + 1), + string) : NULL; +} + +const char * +base_name (const char *name) +{ + const char *base; + +#if defined HAVE_DOS_BASED_FILE_SYSTEM + /* Skip over the disk name in MSDOS pathnames. */ + if (isalpha ((unsigned char) name[0]) && name[1] == ':') + name += 2; +#endif + + for (base = name; *name; name++) + if (IS_DIR_SEPARATOR (*name)) + base = name + 1; + return base; +} + +int +check_executable (const char *path) +{ + struct stat st; + + lt_debugprintf (__FILE__, __LINE__, "(check_executable): %s\n", + nonempty (path)); + if ((!path) || (!*path)) + return 0; + + if ((stat (path, &st) >= 0) + && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + return 1; + else + return 0; +} + +int +make_executable (const char *path) +{ + int rval = 0; + struct stat st; + + lt_debugprintf (__FILE__, __LINE__, "(make_executable): %s\n", + nonempty (path)); + if ((!path) || (!*path)) + return 0; + + if (stat (path, &st) >= 0) + { + rval = chmod (path, st.st_mode | S_IXOTH | S_IXGRP | S_IXUSR); + } + return rval; +} + +/* Searches for the full path of the wrapper. Returns + newly allocated full path name if found, NULL otherwise + Does not chase symlinks, even on platforms that support them. +*/ +char * +find_executable (const char *wrapper) +{ + int has_slash = 0; + const char *p; + const char *p_next; + /* static buffer for getcwd */ + char tmp[LT_PATHMAX + 1]; + size_t tmp_len; + char *concat_name; + + lt_debugprintf (__FILE__, __LINE__, "(find_executable): %s\n", + nonempty (wrapper)); + + if ((wrapper == NULL) || (*wrapper == '\0')) + return NULL; + + /* Absolute path? */ +#if defined HAVE_DOS_BASED_FILE_SYSTEM + if (isalpha ((unsigned char) wrapper[0]) && wrapper[1] == ':') + { + concat_name = xstrdup (wrapper); + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } + else + { +#endif + if (IS_DIR_SEPARATOR (wrapper[0])) + { + concat_name = xstrdup (wrapper); + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } +#if defined HAVE_DOS_BASED_FILE_SYSTEM + } +#endif + + for (p = wrapper; *p; p++) + if (*p == '/') + { + has_slash = 1; + break; + } + if (!has_slash) + { + /* no slashes; search PATH */ + const char *path = getenv ("PATH"); + if (path != NULL) + { + for (p = path; *p; p = p_next) + { + const char *q; + size_t p_len; + for (q = p; *q; q++) + if (IS_PATH_SEPARATOR (*q)) + break; + p_len = (size_t) (q - p); + p_next = (*q == '\0' ? q : q + 1); + if (p_len == 0) + { + /* empty path: current directory */ + if (getcwd (tmp, LT_PATHMAX) == NULL) + lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", + nonnull (strerror (errno))); + tmp_len = strlen (tmp); + concat_name = + XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, tmp, tmp_len); + concat_name[tmp_len] = '/'; + strcpy (concat_name + tmp_len + 1, wrapper); + } + else + { + concat_name = + XMALLOC (char, p_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, p, p_len); + concat_name[p_len] = '/'; + strcpy (concat_name + p_len + 1, wrapper); + } + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } + } + /* not found in PATH; assume curdir */ + } + /* Relative path | not found in path: prepend cwd */ + if (getcwd (tmp, LT_PATHMAX) == NULL) + lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", + nonnull (strerror (errno))); + tmp_len = strlen (tmp); + concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, tmp, tmp_len); + concat_name[tmp_len] = '/'; + strcpy (concat_name + tmp_len + 1, wrapper); + + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + return NULL; +} + +char * +chase_symlinks (const char *pathspec) +{ +#ifndef S_ISLNK + return xstrdup (pathspec); +#else + char buf[LT_PATHMAX]; + struct stat s; + char *tmp_pathspec = xstrdup (pathspec); + char *p; + int has_symlinks = 0; + while (strlen (tmp_pathspec) && !has_symlinks) + { + lt_debugprintf (__FILE__, __LINE__, + "checking path component for symlinks: %s\n", + tmp_pathspec); + if (lstat (tmp_pathspec, &s) == 0) + { + if (S_ISLNK (s.st_mode) != 0) + { + has_symlinks = 1; + break; + } + + /* search backwards for last DIR_SEPARATOR */ + p = tmp_pathspec + strlen (tmp_pathspec) - 1; + while ((p > tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) + p--; + if ((p == tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) + { + /* no more DIR_SEPARATORS left */ + break; + } + *p = '\0'; + } + else + { + lt_fatal (__FILE__, __LINE__, + "error accessing file \"%s\": %s", + tmp_pathspec, nonnull (strerror (errno))); + } + } + XFREE (tmp_pathspec); + + if (!has_symlinks) + { + return xstrdup (pathspec); + } + + tmp_pathspec = realpath (pathspec, buf); + if (tmp_pathspec == 0) + { + lt_fatal (__FILE__, __LINE__, + "could not follow symlinks for %s", pathspec); + } + return xstrdup (tmp_pathspec); +#endif +} + +char * +strendzap (char *str, const char *pat) +{ + size_t len, patlen; + + assert (str != NULL); + assert (pat != NULL); + + len = strlen (str); + patlen = strlen (pat); + + if (patlen <= len) + { + str += len - patlen; + if (STREQ (str, pat)) + *str = '\0'; + } + return str; +} + +void +lt_debugprintf (const char *file, int line, const char *fmt, ...) +{ + va_list args; + if (lt_debug) + { + (void) fprintf (stderr, "%s:%s:%d: ", program_name, file, line); + va_start (args, fmt); + (void) vfprintf (stderr, fmt, args); + va_end (args); + } +} + +static void +lt_error_core (int exit_status, const char *file, + int line, const char *mode, + const char *message, va_list ap) +{ + fprintf (stderr, "%s:%s:%d: %s: ", program_name, file, line, mode); + vfprintf (stderr, message, ap); + fprintf (stderr, ".\n"); + + if (exit_status >= 0) + exit (exit_status); +} + +void +lt_fatal (const char *file, int line, const char *message, ...) +{ + va_list ap; + va_start (ap, message); + lt_error_core (EXIT_FAILURE, file, line, "FATAL", message, ap); + va_end (ap); +} + +static const char * +nonnull (const char *s) +{ + return s ? s : "(null)"; +} + +static const char * +nonempty (const char *s) +{ + return (s && !*s) ? "(empty)" : nonnull (s); +} + +void +lt_setenv (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_setenv) setting '%s' to '%s'\n", + nonnull (name), nonnull (value)); + { +#ifdef HAVE_SETENV + /* always make a copy, for consistency with !HAVE_SETENV */ + char *str = xstrdup (value); + setenv (name, str, 1); +#else + size_t len = strlen (name) + 1 + strlen (value) + 1; + char *str = XMALLOC (char, len); + sprintf (str, "%s=%s", name, value); + if (putenv (str) != EXIT_SUCCESS) + { + XFREE (str); + } +#endif + } +} + +char * +lt_extend_str (const char *orig_value, const char *add, int to_end) +{ + char *new_value; + if (orig_value && *orig_value) + { + size_t orig_value_len = strlen (orig_value); + size_t add_len = strlen (add); + new_value = XMALLOC (char, add_len + orig_value_len + 1); + if (to_end) + { + strcpy (new_value, orig_value); + strcpy (new_value + orig_value_len, add); + } + else + { + strcpy (new_value, add); + strcpy (new_value + add_len, orig_value); + } + } + else + { + new_value = xstrdup (add); + } + return new_value; +} + +void +lt_update_exe_path (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_update_exe_path) modifying '%s' by prepending '%s'\n", + nonnull (name), nonnull (value)); + + if (name && *name && value && *value) + { + char *new_value = lt_extend_str (getenv (name), value, 0); + /* some systems can't cope with a ':'-terminated path #' */ + size_t len = strlen (new_value); + while ((len > 0) && IS_PATH_SEPARATOR (new_value[len-1])) + { + new_value[--len] = '\0'; + } + lt_setenv (name, new_value); + XFREE (new_value); + } +} + +void +lt_update_lib_path (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_update_lib_path) modifying '%s' by prepending '%s'\n", + nonnull (name), nonnull (value)); + + if (name && *name && value && *value) + { + char *new_value = lt_extend_str (getenv (name), value, 0); + lt_setenv (name, new_value); + XFREE (new_value); + } +} + +EOF + case $host_os in + mingw*) + cat <<"EOF" + +/* Prepares an argument vector before calling spawn(). + Note that spawn() does not by itself call the command interpreter + (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") : + ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&v); + v.dwPlatformId == VER_PLATFORM_WIN32_NT; + }) ? "cmd.exe" : "command.com"). + Instead it simply concatenates the arguments, separated by ' ', and calls + CreateProcess(). We must quote the arguments since Win32 CreateProcess() + interprets characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a + special way: + - Space and tab are interpreted as delimiters. They are not treated as + delimiters if they are surrounded by double quotes: "...". + - Unescaped double quotes are removed from the input. Their only effect is + that within double quotes, space and tab are treated like normal + characters. + - Backslashes not followed by double quotes are not special. + - But 2*n+1 backslashes followed by a double quote become + n backslashes followed by a double quote (n >= 0): + \" -> " + \\\" -> \" + \\\\\" -> \\" + */ +#define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" +#define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" +char ** +prepare_spawn (char **argv) +{ + size_t argc; + char **new_argv; + size_t i; + + /* Count number of arguments. */ + for (argc = 0; argv[argc] != NULL; argc++) + ; + + /* Allocate new argument vector. */ + new_argv = XMALLOC (char *, argc + 1); + + /* Put quoted arguments into the new argument vector. */ + for (i = 0; i < argc; i++) + { + const char *string = argv[i]; + + if (string[0] == '\0') + new_argv[i] = xstrdup ("\"\""); + else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL) + { + int quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL); + size_t length; + unsigned int backslashes; + const char *s; + char *quoted_string; + char *p; + + length = 0; + backslashes = 0; + if (quote_around) + length++; + for (s = string; *s != '\0'; s++) + { + char c = *s; + if (c == '"') + length += backslashes + 1; + length++; + if (c == '\\') + backslashes++; + else + backslashes = 0; + } + if (quote_around) + length += backslashes + 1; + + quoted_string = XMALLOC (char, length + 1); + + p = quoted_string; + backslashes = 0; + if (quote_around) + *p++ = '"'; + for (s = string; *s != '\0'; s++) + { + char c = *s; + if (c == '"') + { + unsigned int j; + for (j = backslashes + 1; j > 0; j--) + *p++ = '\\'; + } + *p++ = c; + if (c == '\\') + backslashes++; + else + backslashes = 0; + } + if (quote_around) + { + unsigned int j; + for (j = backslashes; j > 0; j--) + *p++ = '\\'; + *p++ = '"'; + } + *p = '\0'; + + new_argv[i] = quoted_string; + } + else + new_argv[i] = (char *) string; + } + new_argv[argc] = NULL; + + return new_argv; +} +EOF + ;; + esac + + cat <<"EOF" +void lt_dump_script (FILE* f) +{ +EOF + func_emit_wrapper yes | + $SED -n -e ' +s/^\(.\{79\}\)\(..*\)/\1\ +\2/ +h +s/\([\\"]\)/\\\1/g +s/$/\\n/ +s/\([^\n]*\).*/ fputs ("\1", f);/p +g +D' + cat <<"EOF" +} +EOF +} +# end: func_emit_cwrapperexe_src + +# func_win32_import_lib_p ARG +# True if ARG is an import lib, as indicated by $file_magic_cmd +func_win32_import_lib_p () +{ + $debug_cmd + + case `eval $file_magic_cmd \"\$1\" 2>/dev/null | $SED -e 10q` in + *import*) : ;; + *) false ;; + esac +} + +# func_suncc_cstd_abi +# !!ONLY CALL THIS FOR SUN CC AFTER $compile_command IS FULLY EXPANDED!! +# Several compiler flags select an ABI that is incompatible with the +# Cstd library. Avoid specifying it if any are in CXXFLAGS. +func_suncc_cstd_abi () +{ + $debug_cmd + + case " $compile_command " in + *" -compat=g "*|*\ -std=c++[0-9][0-9]\ *|*" -library=stdcxx4 "*|*" -library=stlport4 "*) + suncc_use_cstd_abi=no + ;; + *) + suncc_use_cstd_abi=yes + ;; + esac +} + +# func_mode_link arg... +func_mode_link () +{ + $debug_cmd + + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + # It is impossible to link a dll without this setting, and + # we shouldn't force the makefile maintainer to figure out + # what system we are compiling for in order to pass an extra + # flag for every libtool invocation. + # allow_undefined=no + + # FIXME: Unfortunately, there are problems with the above when trying + # to make a dll that has undefined symbols, in which case not + # even a static library is built. For now, we need to specify + # -no-undefined on the libtool link line when we can be certain + # that all symbols are satisfied, otherwise we get a static library. + allow_undefined=yes + ;; + *) + allow_undefined=yes + ;; + esac + libtool_args=$nonopt + base_compile="$nonopt $@" + compile_command=$nonopt + finalize_command=$nonopt + + compile_rpath= + finalize_rpath= + compile_shlibpath= + finalize_shlibpath= + convenience= + old_convenience= + deplibs= + old_deplibs= + compiler_flags= + linker_flags= + dllsearchpath= + lib_search_path=`pwd` + inst_prefix_dir= + new_inherited_linker_flags= + + avoid_version=no + bindir= + dlfiles= + dlprefiles= + dlself=no + export_dynamic=no + export_symbols= + export_symbols_regex= + generated= + libobjs= + ltlibs= + module=no + no_install=no + objs= + os2dllname= + non_pic_objects= + precious_files_regex= + prefer_static_libs=no + preload=false + prev= + prevarg= + release= + rpath= + xrpath= + perm_rpath= + temp_rpath= + thread_safe=no + vinfo= + vinfo_number=no + weak_libs= + single_module=$wl-single_module + func_infer_tag $base_compile + + # We need to know -static, to get the right output filenames. + for arg + do + case $arg in + -shared) + test yes != "$build_libtool_libs" \ + && func_fatal_configuration "cannot build a shared library" + build_old_libs=no + break + ;; + -all-static | -static | -static-libtool-libs) + case $arg in + -all-static) + if test yes = "$build_libtool_libs" && test -z "$link_static_flag"; then + func_warning "complete static linking is impossible in this configuration" + fi + if test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=yes + ;; + -static) + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=built + ;; + -static-libtool-libs) + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=yes + ;; + esac + build_libtool_libs=no + build_old_libs=yes + break + ;; + esac + done + + # See if our shared archives depend on static archives. + test -n "$old_archive_from_new_cmds" && build_old_libs=yes + + # Go through the arguments, transforming them on the way. + while test "$#" -gt 0; do + arg=$1 + shift + func_quote_for_eval "$arg" + qarg=$func_quote_for_eval_unquoted_result + func_append libtool_args " $func_quote_for_eval_result" + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + case $prev in + output) + func_append compile_command " @OUTPUT@" + func_append finalize_command " @OUTPUT@" + ;; + esac + + case $prev in + bindir) + bindir=$arg + prev= + continue + ;; + dlfiles|dlprefiles) + $preload || { + # Add the symbol object into the linking commands. + func_append compile_command " @SYMFILE@" + func_append finalize_command " @SYMFILE@" + preload=: + } + case $arg in + *.la | *.lo) ;; # We handle these cases below. + force) + if test no = "$dlself"; then + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + self) + if test dlprefiles = "$prev"; then + dlself=yes + elif test dlfiles = "$prev" && test yes != "$dlopen_self"; then + dlself=yes + else + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + *) + if test dlfiles = "$prev"; then + func_append dlfiles " $arg" + else + func_append dlprefiles " $arg" + fi + prev= + continue + ;; + esac + ;; + expsyms) + export_symbols=$arg + test -f "$arg" \ + || func_fatal_error "symbol file '$arg' does not exist" + prev= + continue + ;; + expsyms_regex) + export_symbols_regex=$arg + prev= + continue + ;; + framework) + case $host in + *-*-darwin*) + case "$deplibs " in + *" $qarg.ltframework "*) ;; + *) func_append deplibs " $qarg.ltframework" # this is fixed later + ;; + esac + ;; + esac + prev= + continue + ;; + inst_prefix) + inst_prefix_dir=$arg + prev= + continue + ;; + mllvm) + # Clang does not use LLVM to link, so we can simply discard any + # '-mllvm $arg' options when doing the link step. + prev= + continue + ;; + objectlist) + if test -f "$arg"; then + save_arg=$arg + moreargs= + for fil in `cat "$save_arg"` + do +# func_append moreargs " $fil" + arg=$fil + # A libtool-controlled object. + + # Check to see that this really is a libtool object. + if func_lalib_unsafe_p "$arg"; then + pic_object= + non_pic_object= + + # Read the .lo file + func_source "$arg" + + if test -z "$pic_object" || + test -z "$non_pic_object" || + test none = "$pic_object" && + test none = "$non_pic_object"; then + func_fatal_error "cannot find name of object for '$arg'" + fi + + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + if test none != "$pic_object"; then + # Prepend the subdirectory the object is found in. + pic_object=$xdir$pic_object + + if test dlfiles = "$prev"; then + if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then + func_append dlfiles " $pic_object" + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + # CHECK ME: I think I busted this. -Ossama + if test dlprefiles = "$prev"; then + # Preload the old-style object. + func_append dlprefiles " $pic_object" + prev= + fi + + # A PIC object. + func_append libobjs " $pic_object" + arg=$pic_object + fi + + # Non-PIC object. + if test none != "$non_pic_object"; then + # Prepend the subdirectory the object is found in. + non_pic_object=$xdir$non_pic_object + + # A standard non-PIC object + func_append non_pic_objects " $non_pic_object" + if test -z "$pic_object" || test none = "$pic_object"; then + arg=$non_pic_object + fi + else + # If the PIC object exists, use it instead. + # $xdir was prepended to $pic_object above. + non_pic_object=$pic_object + func_append non_pic_objects " $non_pic_object" + fi + else + # Only an error if not doing a dry-run. + if $opt_dry_run; then + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + func_lo2o "$arg" + pic_object=$xdir$objdir/$func_lo2o_result + non_pic_object=$xdir$func_lo2o_result + func_append libobjs " $pic_object" + func_append non_pic_objects " $non_pic_object" + else + func_fatal_error "'$arg' is not a valid libtool object" + fi + fi + done + else + func_fatal_error "link input file '$arg' does not exist" + fi + arg=$save_arg + prev= + continue + ;; + os2dllname) + os2dllname=$arg + prev= + continue + ;; + precious_regex) + precious_files_regex=$arg + prev= + continue + ;; + release) + release=-$arg + prev= + continue + ;; + rpath | xrpath) + # We need an absolute path. + case $arg in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + func_fatal_error "only absolute run-paths are allowed" + ;; + esac + if test rpath = "$prev"; then + case "$rpath " in + *" $arg "*) ;; + *) func_append rpath " $arg" ;; + esac + else + case "$xrpath " in + *" $arg "*) ;; + *) func_append xrpath " $arg" ;; + esac + fi + prev= + continue + ;; + shrext) + shrext_cmds=$arg + prev= + continue + ;; + weak) + func_append weak_libs " $arg" + prev= + continue + ;; + xcclinker) + func_append linker_flags " $qarg" + func_append compiler_flags " $qarg" + prev= + func_append compile_command " $qarg" + func_append finalize_command " $qarg" + continue + ;; + xcompiler) + func_append compiler_flags " $qarg" + prev= + func_append compile_command " $qarg" + func_append finalize_command " $qarg" + continue + ;; + xlinker) + func_append linker_flags " $qarg" + func_append compiler_flags " $wl$qarg" + prev= + func_append compile_command " $wl$qarg" + func_append finalize_command " $wl$qarg" + continue + ;; + *) + eval "$prev=\"\$arg\"" + prev= + continue + ;; + esac + fi # test -n "$prev" + + prevarg=$arg + + case $arg in + -all-static) + if test -n "$link_static_flag"; then + # See comment for -static flag below, for more details. + func_append compile_command " $link_static_flag" + func_append finalize_command " $link_static_flag" + fi + continue + ;; + + -allow-undefined) + # FIXME: remove this flag sometime in the future. + func_fatal_error "'-allow-undefined' must not be used because it is the default" + ;; + + -avoid-version) + avoid_version=yes + continue + ;; + + -bindir) + prev=bindir + continue + ;; + + -dlopen) + prev=dlfiles + continue + ;; + + -dlpreopen) + prev=dlprefiles + continue + ;; + + -export-dynamic) + export_dynamic=yes + continue + ;; + + -export-symbols | -export-symbols-regex) + if test -n "$export_symbols" || test -n "$export_symbols_regex"; then + func_fatal_error "more than one -exported-symbols argument is not allowed" + fi + if test X-export-symbols = "X$arg"; then + prev=expsyms + else + prev=expsyms_regex + fi + continue + ;; + + -framework) + prev=framework + continue + ;; + + -inst-prefix-dir) + prev=inst_prefix + continue + ;; + + # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:* + # so, if we see these flags be careful not to treat them like -L + -L[A-Z][A-Z]*:*) + case $with_gcc/$host in + no/*-*-irix* | /*-*-irix*) + func_append compile_command " $arg" + func_append finalize_command " $arg" + ;; + esac + continue + ;; + + -L*) + func_stripname "-L" '' "$arg" + if test -z "$func_stripname_result"; then + if test "$#" -gt 0; then + func_fatal_error "require no space between '-L' and '$1'" + else + func_fatal_error "need path for '-L' option" + fi + fi + func_resolve_sysroot "$func_stripname_result" + dir=$func_resolve_sysroot_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + absdir=`cd "$dir" && pwd` + test -z "$absdir" && \ + func_fatal_error "cannot determine absolute directory name of '$dir'" + dir=$absdir + ;; + esac + case "$deplibs " in + *" -L$dir "* | *" $arg "*) + # Will only happen for absolute or sysroot arguments + ;; + *) + # Preserve sysroot, but never include relative directories + case $dir in + [\\/]* | [A-Za-z]:[\\/]* | =*) func_append deplibs " $arg" ;; + *) func_append deplibs " -L$dir" ;; + esac + func_append lib_search_path " $dir" + ;; + esac + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + testbindir=`$ECHO "$dir" | $SED 's*/lib$*/bin*'` + case :$dllsearchpath: in + *":$dir:"*) ;; + ::) dllsearchpath=$dir;; + *) func_append dllsearchpath ":$dir";; + esac + case :$dllsearchpath: in + *":$testbindir:"*) ;; + ::) dllsearchpath=$testbindir;; + *) func_append dllsearchpath ":$testbindir";; + esac + ;; + esac + continue + ;; + + -l*) + if test X-lc = "X$arg" || test X-lm = "X$arg"; then + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-beos* | *-cegcc* | *-*-haiku*) + # These systems don't actually have a C or math library (as such) + continue + ;; + *-*-os2*) + # These systems don't actually have a C library (as such) + test X-lc = "X$arg" && continue + ;; + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig*) + # Do not include libc due to us having libc/libc_r. + test X-lc = "X$arg" && continue + ;; + *-*-rhapsody* | *-*-darwin1.[012]) + # Rhapsody C and math libraries are in the System framework + func_append deplibs " System.ltframework" + continue + ;; + *-*-sco3.2v5* | *-*-sco5v6*) + # Causes problems with __ctype + test X-lc = "X$arg" && continue + ;; + *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) + # Compiler inserts libc in the correct place for threads to work + test X-lc = "X$arg" && continue + ;; + esac + elif test X-lc_r = "X$arg"; then + case $host in + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig*) + # Do not include libc_r directly, use -pthread flag. + continue + ;; + esac + fi + func_append deplibs " $arg" + continue + ;; + + -mllvm) + prev=mllvm + continue + ;; + + -module) + module=yes + continue + ;; + + # Tru64 UNIX uses -model [arg] to determine the layout of C++ + # classes, name mangling, and exception handling. + # Darwin uses the -arch flag to determine output architecture. + -model|-arch|-isysroot|--sysroot) + func_append compiler_flags " $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + prev=xcompiler + continue + ;; + + -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \ + |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*) + func_append compiler_flags " $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + case "$new_inherited_linker_flags " in + *" $arg "*) ;; + * ) func_append new_inherited_linker_flags " $arg" ;; + esac + continue + ;; + + -multi_module) + single_module=$wl-multi_module + continue + ;; + + -no-fast-install) + fast_install=no + continue + ;; + + -no-install) + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-darwin* | *-cegcc*) + # The PATH hackery in wrapper scripts is required on Windows + # and Darwin in order for the loader to find any dlls it needs. + func_warning "'-no-install' is ignored for $host" + func_warning "assuming '-no-fast-install' instead" + fast_install=no + ;; + *) no_install=yes ;; + esac + continue + ;; + + -no-undefined) + allow_undefined=no + continue + ;; + + -objectlist) + prev=objectlist + continue + ;; + + -os2dllname) + prev=os2dllname + continue + ;; + + -o) prev=output ;; + + -precious-files-regex) + prev=precious_regex + continue + ;; + + -release) + prev=release + continue + ;; + + -rpath) + prev=rpath + continue + ;; + + -R) + prev=xrpath + continue + ;; + + -R*) + func_stripname '-R' '' "$arg" + dir=$func_stripname_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + =*) + func_stripname '=' '' "$dir" + dir=$lt_sysroot$func_stripname_result + ;; + *) + func_fatal_error "only absolute run-paths are allowed" + ;; + esac + case "$xrpath " in + *" $dir "*) ;; + *) func_append xrpath " $dir" ;; + esac + continue + ;; + + -shared) + # The effects of -shared are defined in a previous loop. + continue + ;; + + -shrext) + prev=shrext + continue + ;; + + -static | -static-libtool-libs) + # The effects of -static are defined in a previous loop. + # We used to do the same as -all-static on platforms that + # didn't have a PIC flag, but the assumption that the effects + # would be equivalent was wrong. It would break on at least + # Digital Unix and AIX. + continue + ;; + + -thread-safe) + thread_safe=yes + continue + ;; + + -version-info) + prev=vinfo + continue + ;; + + -version-number) + prev=vinfo + vinfo_number=yes + continue + ;; + + -weak) + prev=weak + continue + ;; + + -Wc,*) + func_stripname '-Wc,' '' "$arg" + args=$func_stripname_result + arg= + save_ifs=$IFS; IFS=, + for flag in $args; do + IFS=$save_ifs + func_quote_for_eval "$flag" + func_append arg " $func_quote_for_eval_result" + func_append compiler_flags " $func_quote_for_eval_result" + done + IFS=$save_ifs + func_stripname ' ' '' "$arg" + arg=$func_stripname_result + ;; + + -Wl,*) + func_stripname '-Wl,' '' "$arg" + args=$func_stripname_result + arg= + save_ifs=$IFS; IFS=, + for flag in $args; do + IFS=$save_ifs + func_quote_for_eval "$flag" + func_append arg " $wl$func_quote_for_eval_result" + func_append compiler_flags " $wl$func_quote_for_eval_result" + func_append linker_flags " $func_quote_for_eval_result" + done + IFS=$save_ifs + func_stripname ' ' '' "$arg" + arg=$func_stripname_result + ;; + + -Xcompiler) + prev=xcompiler + continue + ;; + + -Xlinker) + prev=xlinker + continue + ;; + + -XCClinker) + prev=xcclinker + continue + ;; + + # -msg_* for osf cc + -msg_*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + + # Flags to be passed through unchanged, with rationale: + # -64, -mips[0-9] enable 64-bit mode for the SGI compiler + # -r[0-9][0-9]* specify processor for the SGI compiler + # -xarch=*, -xtarget=* enable 64-bit mode for the Sun compiler + # +DA*, +DD* enable 64-bit mode for the HP compiler + # -q* compiler args for the IBM compiler + # -m*, -t[45]*, -txscale* architecture-specific flags for GCC + # -F/path path to uninstalled frameworks, gcc on darwin + # -p, -pg, --coverage, -fprofile-* profiling flags for GCC + # -fstack-protector* stack protector flags for GCC + # @file GCC response files + # -tp=* Portland pgcc target processor selection + # --sysroot=* for sysroot support + # -O*, -g*, -flto*, -fwhopr*, -fuse-linker-plugin GCC link-time optimization + # -specs=* GCC specs files + # -stdlib=* select c++ std lib with clang + # -fsanitize=* Clang/GCC memory and address sanitizer + -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \ + -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \ + -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*| \ + -specs=*|-fsanitize=*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + func_append compile_command " $arg" + func_append finalize_command " $arg" + func_append compiler_flags " $arg" + continue + ;; + + -Z*) + if test os2 = "`expr $host : '.*\(os2\)'`"; then + # OS/2 uses -Zxxx to specify OS/2-specific options + compiler_flags="$compiler_flags $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + case $arg in + -Zlinker | -Zstack) + prev=xcompiler + ;; + esac + continue + else + # Otherwise treat like 'Some other compiler flag' below + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + fi + ;; + + # Some other compiler flag. + -* | +*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + + *.$objext) + # A standard object. + func_append objs " $arg" + ;; + + *.lo) + # A libtool-controlled object. + + # Check to see that this really is a libtool object. + if func_lalib_unsafe_p "$arg"; then + pic_object= + non_pic_object= + + # Read the .lo file + func_source "$arg" + + if test -z "$pic_object" || + test -z "$non_pic_object" || + test none = "$pic_object" && + test none = "$non_pic_object"; then + func_fatal_error "cannot find name of object for '$arg'" + fi + + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + test none = "$pic_object" || { + # Prepend the subdirectory the object is found in. + pic_object=$xdir$pic_object + + if test dlfiles = "$prev"; then + if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then + func_append dlfiles " $pic_object" + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + # CHECK ME: I think I busted this. -Ossama + if test dlprefiles = "$prev"; then + # Preload the old-style object. + func_append dlprefiles " $pic_object" + prev= + fi + + # A PIC object. + func_append libobjs " $pic_object" + arg=$pic_object + } + + # Non-PIC object. + if test none != "$non_pic_object"; then + # Prepend the subdirectory the object is found in. + non_pic_object=$xdir$non_pic_object + + # A standard non-PIC object + func_append non_pic_objects " $non_pic_object" + if test -z "$pic_object" || test none = "$pic_object"; then + arg=$non_pic_object + fi + else + # If the PIC object exists, use it instead. + # $xdir was prepended to $pic_object above. + non_pic_object=$pic_object + func_append non_pic_objects " $non_pic_object" + fi + else + # Only an error if not doing a dry-run. + if $opt_dry_run; then + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + func_lo2o "$arg" + pic_object=$xdir$objdir/$func_lo2o_result + non_pic_object=$xdir$func_lo2o_result + func_append libobjs " $pic_object" + func_append non_pic_objects " $non_pic_object" + else + func_fatal_error "'$arg' is not a valid libtool object" + fi + fi + ;; + + *.$libext) + # An archive. + func_append deplibs " $arg" + func_append old_deplibs " $arg" + continue + ;; + + *.la) + # A libtool-controlled library. + + func_resolve_sysroot "$arg" + if test dlfiles = "$prev"; then + # This library was specified with -dlopen. + func_append dlfiles " $func_resolve_sysroot_result" + prev= + elif test dlprefiles = "$prev"; then + # The library was specified with -dlpreopen. + func_append dlprefiles " $func_resolve_sysroot_result" + prev= + else + func_append deplibs " $func_resolve_sysroot_result" + fi + continue + ;; + + # Some other compiler argument. + *) + # Unknown arguments in both finalize_command and compile_command need + # to be aesthetically quoted because they are evaled later. + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + esac # arg + + # Now actually substitute the argument into the commands. + if test -n "$arg"; then + func_append compile_command " $arg" + func_append finalize_command " $arg" + fi + done # argument parsing loop + + test -n "$prev" && \ + func_fatal_help "the '$prevarg' option requires an argument" + + if test yes = "$export_dynamic" && test -n "$export_dynamic_flag_spec"; then + eval arg=\"$export_dynamic_flag_spec\" + func_append compile_command " $arg" + func_append finalize_command " $arg" + fi + + oldlibs= + # calculate the name of the file, without its directory + func_basename "$output" + outputname=$func_basename_result + libobjs_save=$libobjs + + if test -n "$shlibpath_var"; then + # get the directories listed in $shlibpath_var + eval shlib_search_path=\`\$ECHO \"\$$shlibpath_var\" \| \$SED \'s/:/ /g\'\` + else + shlib_search_path= + fi + eval sys_lib_search_path=\"$sys_lib_search_path_spec\" + eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\" + + # Definition is injected by LT_CONFIG during libtool generation. + func_munge_path_list sys_lib_dlsearch_path "$LT_SYS_LIBRARY_PATH" + + func_dirname "$output" "/" "" + output_objdir=$func_dirname_result$objdir + func_to_tool_file "$output_objdir/" + tool_output_objdir=$func_to_tool_file_result + # Create the object directory. + func_mkdir_p "$output_objdir" + + # Determine the type of output + case $output in + "") + func_fatal_help "you must specify an output file" + ;; + *.$libext) linkmode=oldlib ;; + *.lo | *.$objext) linkmode=obj ;; + *.la) linkmode=lib ;; + *) linkmode=prog ;; # Anything else should be a program. + esac + + specialdeplibs= + + libs= + # Find all interdependent deplibs by searching for libraries + # that are linked more than once (e.g. -la -lb -la) + for deplib in $deplibs; do + if $opt_preserve_dup_deps; then + case "$libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append libs " $deplib" + done + + if test lib = "$linkmode"; then + libs="$predeps $libs $compiler_lib_search_path $postdeps" + + # Compute libraries that are listed more than once in $predeps + # $postdeps and mark them as special (i.e., whose duplicates are + # not to be eliminated). + pre_post_deps= + if $opt_duplicate_compiler_generated_deps; then + for pre_post_dep in $predeps $postdeps; do + case "$pre_post_deps " in + *" $pre_post_dep "*) func_append specialdeplibs " $pre_post_deps" ;; + esac + func_append pre_post_deps " $pre_post_dep" + done + fi + pre_post_deps= + fi + + deplibs= + newdependency_libs= + newlib_search_path= + need_relink=no # whether we're linking any uninstalled libtool libraries + notinst_deplibs= # not-installed libtool libraries + notinst_path= # paths that contain not-installed libtool libraries + + case $linkmode in + lib) + passes="conv dlpreopen link" + for file in $dlfiles $dlprefiles; do + case $file in + *.la) ;; + *) + func_fatal_help "libraries can '-dlopen' only libtool libraries: $file" + ;; + esac + done + ;; + prog) + compile_deplibs= + finalize_deplibs= + alldeplibs=false + newdlfiles= + newdlprefiles= + passes="conv scan dlopen dlpreopen link" + ;; + *) passes="conv" + ;; + esac + + for pass in $passes; do + # The preopen pass in lib mode reverses $deplibs; put it back here + # so that -L comes before libs that need it for instance... + if test lib,link = "$linkmode,$pass"; then + ## FIXME: Find the place where the list is rebuilt in the wrong + ## order, and fix it there properly + tmp_deplibs= + for deplib in $deplibs; do + tmp_deplibs="$deplib $tmp_deplibs" + done + deplibs=$tmp_deplibs + fi + + if test lib,link = "$linkmode,$pass" || + test prog,scan = "$linkmode,$pass"; then + libs=$deplibs + deplibs= + fi + if test prog = "$linkmode"; then + case $pass in + dlopen) libs=$dlfiles ;; + dlpreopen) libs=$dlprefiles ;; + link) + libs="$deplibs %DEPLIBS%" + test "X$link_all_deplibs" != Xno && libs="$libs $dependency_libs" + ;; + esac + fi + if test lib,dlpreopen = "$linkmode,$pass"; then + # Collect and forward deplibs of preopened libtool libs + for lib in $dlprefiles; do + # Ignore non-libtool-libs + dependency_libs= + func_resolve_sysroot "$lib" + case $lib in + *.la) func_source "$func_resolve_sysroot_result" ;; + esac + + # Collect preopened libtool deplibs, except any this library + # has declared as weak libs + for deplib in $dependency_libs; do + func_basename "$deplib" + deplib_base=$func_basename_result + case " $weak_libs " in + *" $deplib_base "*) ;; + *) func_append deplibs " $deplib" ;; + esac + done + done + libs=$dlprefiles + fi + if test dlopen = "$pass"; then + # Collect dlpreopened libraries + save_deplibs=$deplibs + deplibs= + fi + + for deplib in $libs; do + lib= + found=false + case $deplib in + -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \ + |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*) + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + func_append compiler_flags " $deplib" + if test lib = "$linkmode"; then + case "$new_inherited_linker_flags " in + *" $deplib "*) ;; + * ) func_append new_inherited_linker_flags " $deplib" ;; + esac + fi + fi + continue + ;; + -l*) + if test lib != "$linkmode" && test prog != "$linkmode"; then + func_warning "'-l' is ignored for archives/objects" + continue + fi + func_stripname '-l' '' "$deplib" + name=$func_stripname_result + if test lib = "$linkmode"; then + searchdirs="$newlib_search_path $lib_search_path $compiler_lib_search_dirs $sys_lib_search_path $shlib_search_path" + else + searchdirs="$newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path" + fi + for searchdir in $searchdirs; do + for search_ext in .la $std_shrext .so .a; do + # Search the libtool library + lib=$searchdir/lib$name$search_ext + if test -f "$lib"; then + if test .la = "$search_ext"; then + found=: + else + found=false + fi + break 2 + fi + done + done + if $found; then + # deplib is a libtool library + # If $allow_libtool_libs_with_static_runtimes && $deplib is a stdlib, + # We need to do some special things here, and not later. + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $deplib "*) + if func_lalib_p "$lib"; then + library_names= + old_library= + func_source "$lib" + for l in $old_library $library_names; do + ll=$l + done + if test "X$ll" = "X$old_library"; then # only static version available + found=false + func_dirname "$lib" "" "." + ladir=$func_dirname_result + lib=$ladir/$old_library + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs" + fi + continue + fi + fi + ;; + *) ;; + esac + fi + else + # deplib doesn't seem to be a libtool library + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs" + fi + continue + fi + ;; # -l + *.ltframework) + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + if test lib = "$linkmode"; then + case "$new_inherited_linker_flags " in + *" $deplib "*) ;; + * ) func_append new_inherited_linker_flags " $deplib" ;; + esac + fi + fi + continue + ;; + -L*) + case $linkmode in + lib) + deplibs="$deplib $deplibs" + test conv = "$pass" && continue + newdependency_libs="$deplib $newdependency_libs" + func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + prog) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + continue + fi + if test scan = "$pass"; then + deplibs="$deplib $deplibs" + else + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + fi + func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + *) + func_warning "'-L' is ignored for archives/objects" + ;; + esac # linkmode + continue + ;; # -L + -R*) + if test link = "$pass"; then + func_stripname '-R' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + dir=$func_resolve_sysroot_result + # Make sure the xrpath contains only unique directories. + case "$xrpath " in + *" $dir "*) ;; + *) func_append xrpath " $dir" ;; + esac + fi + deplibs="$deplib $deplibs" + continue + ;; + *.la) + func_resolve_sysroot "$deplib" + lib=$func_resolve_sysroot_result + ;; + *.$libext) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + continue + fi + case $linkmode in + lib) + # Linking convenience modules into shared libraries is allowed, + # but linking other static libraries is non-portable. + case " $dlpreconveniencelibs " in + *" $deplib "*) ;; + *) + valid_a_lib=false + case $deplibs_check_method in + match_pattern*) + set dummy $deplibs_check_method; shift + match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` + if eval "\$ECHO \"$deplib\"" 2>/dev/null | $SED 10q \ + | $EGREP "$match_pattern_regex" > /dev/null; then + valid_a_lib=: + fi + ;; + pass_all) + valid_a_lib=: + ;; + esac + if $valid_a_lib; then + echo + $ECHO "*** Warning: Linking the shared library $output against the" + $ECHO "*** static library $deplib is not portable!" + deplibs="$deplib $deplibs" + else + echo + $ECHO "*** Warning: Trying to link with static lib archive $deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because the file extensions .$libext of this argument makes me believe" + echo "*** that it is just a static archive that I should not use here." + fi + ;; + esac + continue + ;; + prog) + if test link != "$pass"; then + deplibs="$deplib $deplibs" + else + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + fi + continue + ;; + esac # linkmode + ;; # *.$libext + *.lo | *.$objext) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + elif test prog = "$linkmode"; then + if test dlpreopen = "$pass" || test yes != "$dlopen_support" || test no = "$build_libtool_libs"; then + # If there is no dlopen support or we're linking statically, + # we need to preload. + func_append newdlprefiles " $deplib" + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + func_append newdlfiles " $deplib" + fi + fi + continue + ;; + %DEPLIBS%) + alldeplibs=: + continue + ;; + esac # case $deplib + + $found || test -f "$lib" \ + || func_fatal_error "cannot find the library '$lib' or unhandled argument '$deplib'" + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$lib" \ + || func_fatal_error "'$lib' is not a valid libtool archive" + + func_dirname "$lib" "" "." + ladir=$func_dirname_result + + dlname= + dlopen= + dlpreopen= + libdir= + library_names= + old_library= + inherited_linker_flags= + # If the library was installed with an old release of libtool, + # it will not redefine variables installed, or shouldnotlink + installed=yes + shouldnotlink=no + avoidtemprpath= + + + # Read the .la file + func_source "$lib" + + # Convert "-framework foo" to "foo.ltframework" + if test -n "$inherited_linker_flags"; then + tmp_inherited_linker_flags=`$ECHO "$inherited_linker_flags" | $SED 's/-framework \([^ $]*\)/\1.ltframework/g'` + for tmp_inherited_linker_flag in $tmp_inherited_linker_flags; do + case " $new_inherited_linker_flags " in + *" $tmp_inherited_linker_flag "*) ;; + *) func_append new_inherited_linker_flags " $tmp_inherited_linker_flag";; + esac + done + fi + dependency_libs=`$ECHO " $dependency_libs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + if test lib,link = "$linkmode,$pass" || + test prog,scan = "$linkmode,$pass" || + { test prog != "$linkmode" && test lib != "$linkmode"; }; then + test -n "$dlopen" && func_append dlfiles " $dlopen" + test -n "$dlpreopen" && func_append dlprefiles " $dlpreopen" + fi + + if test conv = "$pass"; then + # Only check for convenience libraries + deplibs="$lib $deplibs" + if test -z "$libdir"; then + if test -z "$old_library"; then + func_fatal_error "cannot find name of link library for '$lib'" + fi + # It is a libtool convenience library, so add in its objects. + func_append convenience " $ladir/$objdir/$old_library" + func_append old_convenience " $ladir/$objdir/$old_library" + tmp_libs= + for deplib in $dependency_libs; do + deplibs="$deplib $deplibs" + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append tmp_libs " $deplib" + done + elif test prog != "$linkmode" && test lib != "$linkmode"; then + func_fatal_error "'$lib' is not a convenience library" + fi + continue + fi # $pass = conv + + + # Get the name of the library we link against. + linklib= + if test -n "$old_library" && + { test yes = "$prefer_static_libs" || + test built,no = "$prefer_static_libs,$installed"; }; then + linklib=$old_library + else + for l in $old_library $library_names; do + linklib=$l + done + fi + if test -z "$linklib"; then + func_fatal_error "cannot find name of link library for '$lib'" + fi + + # This library was specified with -dlopen. + if test dlopen = "$pass"; then + test -z "$libdir" \ + && func_fatal_error "cannot -dlopen a convenience library: '$lib'" + if test -z "$dlname" || + test yes != "$dlopen_support" || + test no = "$build_libtool_libs" + then + # If there is no dlname, no dlopen support or we're linking + # statically, we need to preload. We also need to preload any + # dependent libraries so libltdl's deplib preloader doesn't + # bomb out in the load deplibs phase. + func_append dlprefiles " $lib $dependency_libs" + else + func_append newdlfiles " $lib" + fi + continue + fi # $pass = dlopen + + # We need an absolute path. + case $ladir in + [\\/]* | [A-Za-z]:[\\/]*) abs_ladir=$ladir ;; + *) + abs_ladir=`cd "$ladir" && pwd` + if test -z "$abs_ladir"; then + func_warning "cannot determine absolute directory name of '$ladir'" + func_warning "passing it literally to the linker, although it might fail" + abs_ladir=$ladir + fi + ;; + esac + func_basename "$lib" + laname=$func_basename_result + + # Find the relevant object directory and library name. + if test yes = "$installed"; then + if test ! -f "$lt_sysroot$libdir/$linklib" && test -f "$abs_ladir/$linklib"; then + func_warning "library '$lib' was moved." + dir=$ladir + absdir=$abs_ladir + libdir=$abs_ladir + else + dir=$lt_sysroot$libdir + absdir=$lt_sysroot$libdir + fi + test yes = "$hardcode_automatic" && avoidtemprpath=yes + else + if test ! -f "$ladir/$objdir/$linklib" && test -f "$abs_ladir/$linklib"; then + dir=$ladir + absdir=$abs_ladir + # Remove this search path later + func_append notinst_path " $abs_ladir" + else + dir=$ladir/$objdir + absdir=$abs_ladir/$objdir + # Remove this search path later + func_append notinst_path " $abs_ladir" + fi + fi # $installed = yes + func_stripname 'lib' '.la' "$laname" + name=$func_stripname_result + + # This library was specified with -dlpreopen. + if test dlpreopen = "$pass"; then + if test -z "$libdir" && test prog = "$linkmode"; then + func_fatal_error "only libraries may -dlpreopen a convenience library: '$lib'" + fi + case $host in + # special handling for platforms with PE-DLLs. + *cygwin* | *mingw* | *cegcc* ) + # Linker will automatically link against shared library if both + # static and shared are present. Therefore, ensure we extract + # symbols from the import library if a shared library is present + # (otherwise, the dlopen module name will be incorrect). We do + # this by putting the import library name into $newdlprefiles. + # We recover the dlopen module name by 'saving' the la file + # name in a special purpose variable, and (later) extracting the + # dlname from the la file. + if test -n "$dlname"; then + func_tr_sh "$dir/$linklib" + eval "libfile_$func_tr_sh_result=\$abs_ladir/\$laname" + func_append newdlprefiles " $dir/$linklib" + else + func_append newdlprefiles " $dir/$old_library" + # Keep a list of preopened convenience libraries to check + # that they are being used correctly in the link pass. + test -z "$libdir" && \ + func_append dlpreconveniencelibs " $dir/$old_library" + fi + ;; + * ) + # Prefer using a static library (so that no silly _DYNAMIC symbols + # are required to link). + if test -n "$old_library"; then + func_append newdlprefiles " $dir/$old_library" + # Keep a list of preopened convenience libraries to check + # that they are being used correctly in the link pass. + test -z "$libdir" && \ + func_append dlpreconveniencelibs " $dir/$old_library" + # Otherwise, use the dlname, so that lt_dlopen finds it. + elif test -n "$dlname"; then + func_append newdlprefiles " $dir/$dlname" + else + func_append newdlprefiles " $dir/$linklib" + fi + ;; + esac + fi # $pass = dlpreopen + + if test -z "$libdir"; then + # Link the convenience library + if test lib = "$linkmode"; then + deplibs="$dir/$old_library $deplibs" + elif test prog,link = "$linkmode,$pass"; then + compile_deplibs="$dir/$old_library $compile_deplibs" + finalize_deplibs="$dir/$old_library $finalize_deplibs" + else + deplibs="$lib $deplibs" # used for prog,scan pass + fi + continue + fi + + + if test prog = "$linkmode" && test link != "$pass"; then + func_append newlib_search_path " $ladir" + deplibs="$lib $deplibs" + + linkalldeplibs=false + if test no != "$link_all_deplibs" || test -z "$library_names" || + test no = "$build_libtool_libs"; then + linkalldeplibs=: + fi + + tmp_libs= + for deplib in $dependency_libs; do + case $deplib in + -L*) func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + esac + # Need to link against all dependency_libs? + if $linkalldeplibs; then + deplibs="$deplib $deplibs" + else + # Need to hardcode shared library paths + # or/and link against static libraries + newdependency_libs="$deplib $newdependency_libs" + fi + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append tmp_libs " $deplib" + done # for deplib + continue + fi # $linkmode = prog... + + if test prog,link = "$linkmode,$pass"; then + if test -n "$library_names" && + { { test no = "$prefer_static_libs" || + test built,yes = "$prefer_static_libs,$installed"; } || + test -z "$old_library"; }; then + # We need to hardcode the library path + if test -n "$shlibpath_var" && test -z "$avoidtemprpath"; then + # Make sure the rpath contains only unique directories. + case $temp_rpath: in + *"$absdir:"*) ;; + *) func_append temp_rpath "$absdir:" ;; + esac + fi + + # Hardcode the library path. + # Skip directories that are in the system default run-time + # search path. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) func_append compile_rpath " $absdir" ;; + esac + ;; + esac + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + ;; + esac + fi # $linkmode,$pass = prog,link... + + if $alldeplibs && + { test pass_all = "$deplibs_check_method" || + { test yes = "$build_libtool_libs" && + test -n "$library_names"; }; }; then + # We only need to search for static libraries + continue + fi + fi + + link_static=no # Whether the deplib will be linked statically + use_static_libs=$prefer_static_libs + if test built = "$use_static_libs" && test yes = "$installed"; then + use_static_libs=no + fi + if test -n "$library_names" && + { test no = "$use_static_libs" || test -z "$old_library"; }; then + case $host in + *cygwin* | *mingw* | *cegcc* | *os2*) + # No point in relinking DLLs because paths are not encoded + func_append notinst_deplibs " $lib" + need_relink=no + ;; + *) + if test no = "$installed"; then + func_append notinst_deplibs " $lib" + need_relink=yes + fi + ;; + esac + # This is a shared library + + # Warn about portability, can't link against -module's on some + # systems (darwin). Don't bleat about dlopened modules though! + dlopenmodule= + for dlpremoduletest in $dlprefiles; do + if test "X$dlpremoduletest" = "X$lib"; then + dlopenmodule=$dlpremoduletest + break + fi + done + if test -z "$dlopenmodule" && test yes = "$shouldnotlink" && test link = "$pass"; then + echo + if test prog = "$linkmode"; then + $ECHO "*** Warning: Linking the executable $output against the loadable module" + else + $ECHO "*** Warning: Linking the shared library $output against the loadable module" + fi + $ECHO "*** $linklib is not portable!" + fi + if test lib = "$linkmode" && + test yes = "$hardcode_into_libs"; then + # Hardcode the library path. + # Skip directories that are in the system default run-time + # search path. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) func_append compile_rpath " $absdir" ;; + esac + ;; + esac + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + ;; + esac + fi + + if test -n "$old_archive_from_expsyms_cmds"; then + # figure out the soname + set dummy $library_names + shift + realname=$1 + shift + libname=`eval "\\$ECHO \"$libname_spec\""` + # use dlname if we got it. it's perfectly good, no? + if test -n "$dlname"; then + soname=$dlname + elif test -n "$soname_spec"; then + # bleh windows + case $host in + *cygwin* | mingw* | *cegcc* | *os2*) + func_arith $current - $age + major=$func_arith_result + versuffix=-$major + ;; + esac + eval soname=\"$soname_spec\" + else + soname=$realname + fi + + # Make a new name for the extract_expsyms_cmds to use + soroot=$soname + func_basename "$soroot" + soname=$func_basename_result + func_stripname 'lib' '.dll' "$soname" + newlib=libimp-$func_stripname_result.a + + # If the library has no export list, then create one now + if test -f "$output_objdir/$soname-def"; then : + else + func_verbose "extracting exported symbol list from '$soname'" + func_execute_cmds "$extract_expsyms_cmds" 'exit $?' + fi + + # Create $newlib + if test -f "$output_objdir/$newlib"; then :; else + func_verbose "generating import library for '$soname'" + func_execute_cmds "$old_archive_from_expsyms_cmds" 'exit $?' + fi + # make sure the library variables are pointing to the new library + dir=$output_objdir + linklib=$newlib + fi # test -n "$old_archive_from_expsyms_cmds" + + if test prog = "$linkmode" || test relink != "$opt_mode"; then + add_shlibpath= + add_dir= + add= + lib_linked=yes + case $hardcode_action in + immediate | unsupported) + if test no = "$hardcode_direct"; then + add=$dir/$linklib + case $host in + *-*-sco3.2v5.0.[024]*) add_dir=-L$dir ;; + *-*-sysv4*uw2*) add_dir=-L$dir ;; + *-*-sysv5OpenUNIX* | *-*-sysv5UnixWare7.[01].[10]* | \ + *-*-unixware7*) add_dir=-L$dir ;; + *-*-darwin* ) + # if the lib is a (non-dlopened) module then we cannot + # link against it, someone is ignoring the earlier warnings + if /usr/bin/file -L $add 2> /dev/null | + $GREP ": [^:]* bundle" >/dev/null; then + if test "X$dlopenmodule" != "X$lib"; then + $ECHO "*** Warning: lib $linklib is a module, not a shared library" + if test -z "$old_library"; then + echo + echo "*** And there doesn't seem to be a static archive available" + echo "*** The link will probably fail, sorry" + else + add=$dir/$old_library + fi + elif test -n "$old_library"; then + add=$dir/$old_library + fi + fi + esac + elif test no = "$hardcode_minus_L"; then + case $host in + *-*-sunos*) add_shlibpath=$dir ;; + esac + add_dir=-L$dir + add=-l$name + elif test no = "$hardcode_shlibpath_var"; then + add_shlibpath=$dir + add=-l$name + else + lib_linked=no + fi + ;; + relink) + if test yes = "$hardcode_direct" && + test no = "$hardcode_direct_absolute"; then + add=$dir/$linklib + elif test yes = "$hardcode_minus_L"; then + add_dir=-L$absdir + # Try looking first in the location we're being installed to. + if test -n "$inst_prefix_dir"; then + case $libdir in + [\\/]*) + func_append add_dir " -L$inst_prefix_dir$libdir" + ;; + esac + fi + add=-l$name + elif test yes = "$hardcode_shlibpath_var"; then + add_shlibpath=$dir + add=-l$name + else + lib_linked=no + fi + ;; + *) lib_linked=no ;; + esac + + if test yes != "$lib_linked"; then + func_fatal_configuration "unsupported hardcode properties" + fi + + if test -n "$add_shlibpath"; then + case :$compile_shlibpath: in + *":$add_shlibpath:"*) ;; + *) func_append compile_shlibpath "$add_shlibpath:" ;; + esac + fi + if test prog = "$linkmode"; then + test -n "$add_dir" && compile_deplibs="$add_dir $compile_deplibs" + test -n "$add" && compile_deplibs="$add $compile_deplibs" + else + test -n "$add_dir" && deplibs="$add_dir $deplibs" + test -n "$add" && deplibs="$add $deplibs" + if test yes != "$hardcode_direct" && + test yes != "$hardcode_minus_L" && + test yes = "$hardcode_shlibpath_var"; then + case :$finalize_shlibpath: in + *":$libdir:"*) ;; + *) func_append finalize_shlibpath "$libdir:" ;; + esac + fi + fi + fi + + if test prog = "$linkmode" || test relink = "$opt_mode"; then + add_shlibpath= + add_dir= + add= + # Finalize command for both is simple: just hardcode it. + if test yes = "$hardcode_direct" && + test no = "$hardcode_direct_absolute"; then + add=$libdir/$linklib + elif test yes = "$hardcode_minus_L"; then + add_dir=-L$libdir + add=-l$name + elif test yes = "$hardcode_shlibpath_var"; then + case :$finalize_shlibpath: in + *":$libdir:"*) ;; + *) func_append finalize_shlibpath "$libdir:" ;; + esac + add=-l$name + elif test yes = "$hardcode_automatic"; then + if test -n "$inst_prefix_dir" && + test -f "$inst_prefix_dir$libdir/$linklib"; then + add=$inst_prefix_dir$libdir/$linklib + else + add=$libdir/$linklib + fi + else + # We cannot seem to hardcode it, guess we'll fake it. + add_dir=-L$libdir + # Try looking first in the location we're being installed to. + if test -n "$inst_prefix_dir"; then + case $libdir in + [\\/]*) + func_append add_dir " -L$inst_prefix_dir$libdir" + ;; + esac + fi + add=-l$name + fi + + if test prog = "$linkmode"; then + test -n "$add_dir" && finalize_deplibs="$add_dir $finalize_deplibs" + test -n "$add" && finalize_deplibs="$add $finalize_deplibs" + else + test -n "$add_dir" && deplibs="$add_dir $deplibs" + test -n "$add" && deplibs="$add $deplibs" + fi + fi + elif test prog = "$linkmode"; then + # Here we assume that one of hardcode_direct or hardcode_minus_L + # is not unsupported. This is valid on all known static and + # shared platforms. + if test unsupported != "$hardcode_direct"; then + test -n "$old_library" && linklib=$old_library + compile_deplibs="$dir/$linklib $compile_deplibs" + finalize_deplibs="$dir/$linklib $finalize_deplibs" + else + compile_deplibs="-l$name -L$dir $compile_deplibs" + finalize_deplibs="-l$name -L$dir $finalize_deplibs" + fi + elif test yes = "$build_libtool_libs"; then + # Not a shared library + if test pass_all != "$deplibs_check_method"; then + # We're trying link a shared library against a static one + # but the system doesn't support it. + + # Just print a warning and add the library to dependency_libs so + # that the program can be linked against the static library. + echo + $ECHO "*** Warning: This system cannot link to static lib archive $lib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have." + if test yes = "$module"; then + echo "*** But as you try to build a module library, libtool will still create " + echo "*** a static module, that should work as long as the dlopening application" + echo "*** is linked with the -dlopen flag to resolve symbols at runtime." + if test -z "$global_symbol_pipe"; then + echo + echo "*** However, this would only work if libtool was able to extract symbol" + echo "*** lists from a program, using 'nm' or equivalent, but libtool could" + echo "*** not find such a program. So, this module is probably useless." + echo "*** 'nm' from GNU binutils and a full rebuild may help." + fi + if test no = "$build_old_libs"; then + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + fi + else + deplibs="$dir/$old_library $deplibs" + link_static=yes + fi + fi # link shared/static library? + + if test lib = "$linkmode"; then + if test -n "$dependency_libs" && + { test yes != "$hardcode_into_libs" || + test yes = "$build_old_libs" || + test yes = "$link_static"; }; then + # Extract -R from dependency_libs + temp_deplibs= + for libdir in $dependency_libs; do + case $libdir in + -R*) func_stripname '-R' '' "$libdir" + temp_xrpath=$func_stripname_result + case " $xrpath " in + *" $temp_xrpath "*) ;; + *) func_append xrpath " $temp_xrpath";; + esac;; + *) func_append temp_deplibs " $libdir";; + esac + done + dependency_libs=$temp_deplibs + fi + + func_append newlib_search_path " $absdir" + # Link against this library + test no = "$link_static" && newdependency_libs="$abs_ladir/$laname $newdependency_libs" + # ... and its dependency_libs + tmp_libs= + for deplib in $dependency_libs; do + newdependency_libs="$deplib $newdependency_libs" + case $deplib in + -L*) func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result";; + *) func_resolve_sysroot "$deplib" ;; + esac + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $func_resolve_sysroot_result "*) + func_append specialdeplibs " $func_resolve_sysroot_result" ;; + esac + fi + func_append tmp_libs " $func_resolve_sysroot_result" + done + + if test no != "$link_all_deplibs"; then + # Add the search paths of all dependency libraries + for deplib in $dependency_libs; do + path= + case $deplib in + -L*) path=$deplib ;; + *.la) + func_resolve_sysroot "$deplib" + deplib=$func_resolve_sysroot_result + func_dirname "$deplib" "" "." + dir=$func_dirname_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) absdir=$dir ;; + *) + absdir=`cd "$dir" && pwd` + if test -z "$absdir"; then + func_warning "cannot determine absolute directory name of '$dir'" + absdir=$dir + fi + ;; + esac + if $GREP "^installed=no" $deplib > /dev/null; then + case $host in + *-*-darwin*) + depdepl= + eval deplibrary_names=`$SED -n -e 's/^library_names=\(.*\)$/\1/p' $deplib` + if test -n "$deplibrary_names"; then + for tmp in $deplibrary_names; do + depdepl=$tmp + done + if test -f "$absdir/$objdir/$depdepl"; then + depdepl=$absdir/$objdir/$depdepl + darwin_install_name=`$OTOOL -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'` + if test -z "$darwin_install_name"; then + darwin_install_name=`$OTOOL64 -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'` + fi + func_append compiler_flags " $wl-dylib_file $wl$darwin_install_name:$depdepl" + func_append linker_flags " -dylib_file $darwin_install_name:$depdepl" + path= + fi + fi + ;; + *) + path=-L$absdir/$objdir + ;; + esac + else + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $deplib` + test -z "$libdir" && \ + func_fatal_error "'$deplib' is not a valid libtool archive" + test "$absdir" != "$libdir" && \ + func_warning "'$deplib' seems to be moved" + + path=-L$absdir + fi + ;; + esac + case " $deplibs " in + *" $path "*) ;; + *) deplibs="$path $deplibs" ;; + esac + done + fi # link_all_deplibs != no + fi # linkmode = lib + done # for deplib in $libs + if test link = "$pass"; then + if test prog = "$linkmode"; then + compile_deplibs="$new_inherited_linker_flags $compile_deplibs" + finalize_deplibs="$new_inherited_linker_flags $finalize_deplibs" + else + compiler_flags="$compiler_flags "`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + fi + fi + dependency_libs=$newdependency_libs + if test dlpreopen = "$pass"; then + # Link the dlpreopened libraries before other libraries + for deplib in $save_deplibs; do + deplibs="$deplib $deplibs" + done + fi + if test dlopen != "$pass"; then + test conv = "$pass" || { + # Make sure lib_search_path contains only unique directories. + lib_search_path= + for dir in $newlib_search_path; do + case "$lib_search_path " in + *" $dir "*) ;; + *) func_append lib_search_path " $dir" ;; + esac + done + newlib_search_path= + } + + if test prog,link = "$linkmode,$pass"; then + vars="compile_deplibs finalize_deplibs" + else + vars=deplibs + fi + for var in $vars dependency_libs; do + # Add libraries to $var in reverse order + eval tmp_libs=\"\$$var\" + new_libs= + for deplib in $tmp_libs; do + # FIXME: Pedantically, this is the right thing to do, so + # that some nasty dependency loop isn't accidentally + # broken: + #new_libs="$deplib $new_libs" + # Pragmatically, this seems to cause very few problems in + # practice: + case $deplib in + -L*) new_libs="$deplib $new_libs" ;; + -R*) ;; + *) + # And here is the reason: when a library appears more + # than once as an explicit dependence of a library, or + # is implicitly linked in more than once by the + # compiler, it is considered special, and multiple + # occurrences thereof are not removed. Compare this + # with having the same library being listed as a + # dependency of multiple other libraries: in this case, + # we know (pedantically, we assume) the library does not + # need to be listed more than once, so we keep only the + # last copy. This is not always right, but it is rare + # enough that we require users that really mean to play + # such unportable linking tricks to link the library + # using -Wl,-lname, so that libtool does not consider it + # for duplicate removal. + case " $specialdeplibs " in + *" $deplib "*) new_libs="$deplib $new_libs" ;; + *) + case " $new_libs " in + *" $deplib "*) ;; + *) new_libs="$deplib $new_libs" ;; + esac + ;; + esac + ;; + esac + done + tmp_libs= + for deplib in $new_libs; do + case $deplib in + -L*) + case " $tmp_libs " in + *" $deplib "*) ;; + *) func_append tmp_libs " $deplib" ;; + esac + ;; + *) func_append tmp_libs " $deplib" ;; + esac + done + eval $var=\"$tmp_libs\" + done # for var + fi + + # Add Sun CC postdeps if required: + test CXX = "$tagname" && { + case $host_os in + linux*) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C++ 5.9 + func_suncc_cstd_abi + + if test no != "$suncc_use_cstd_abi"; then + func_append postdeps ' -library=Cstd -library=Crun' + fi + ;; + esac + ;; + + solaris*) + func_cc_basename "$CC" + case $func_cc_basename_result in + CC* | sunCC*) + func_suncc_cstd_abi + + if test no != "$suncc_use_cstd_abi"; then + func_append postdeps ' -library=Cstd -library=Crun' + fi + ;; + esac + ;; + esac + } + + # Last step: remove runtime libs from dependency_libs + # (they stay in deplibs) + tmp_libs= + for i in $dependency_libs; do + case " $predeps $postdeps $compiler_lib_search_path " in + *" $i "*) + i= + ;; + esac + if test -n "$i"; then + func_append tmp_libs " $i" + fi + done + dependency_libs=$tmp_libs + done # for pass + if test prog = "$linkmode"; then + dlfiles=$newdlfiles + fi + if test prog = "$linkmode" || test lib = "$linkmode"; then + dlprefiles=$newdlprefiles + fi + + case $linkmode in + oldlib) + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + func_warning "'-dlopen' is ignored for archives" + fi + + case " $deplibs" in + *\ -l* | *\ -L*) + func_warning "'-l' and '-L' are ignored for archives" ;; + esac + + test -n "$rpath" && \ + func_warning "'-rpath' is ignored for archives" + + test -n "$xrpath" && \ + func_warning "'-R' is ignored for archives" + + test -n "$vinfo" && \ + func_warning "'-version-info/-version-number' is ignored for archives" + + test -n "$release" && \ + func_warning "'-release' is ignored for archives" + + test -n "$export_symbols$export_symbols_regex" && \ + func_warning "'-export-symbols' is ignored for archives" + + # Now set the variables for building old libraries. + build_libtool_libs=no + oldlibs=$output + func_append objs "$old_deplibs" + ;; + + lib) + # Make sure we only generate libraries of the form 'libNAME.la'. + case $outputname in + lib*) + func_stripname 'lib' '.la' "$outputname" + name=$func_stripname_result + eval shared_ext=\"$shrext_cmds\" + eval libname=\"$libname_spec\" + ;; + *) + test no = "$module" \ + && func_fatal_help "libtool library '$output' must begin with 'lib'" + + if test no != "$need_lib_prefix"; then + # Add the "lib" prefix for modules if required + func_stripname '' '.la' "$outputname" + name=$func_stripname_result + eval shared_ext=\"$shrext_cmds\" + eval libname=\"$libname_spec\" + else + func_stripname '' '.la' "$outputname" + libname=$func_stripname_result + fi + ;; + esac + + if test -n "$objs"; then + if test pass_all != "$deplibs_check_method"; then + func_fatal_error "cannot build libtool library '$output' from non-libtool objects on this host:$objs" + else + echo + $ECHO "*** Warning: Linking the shared library $output against the non-libtool" + $ECHO "*** objects $objs is not portable!" + func_append libobjs " $objs" + fi + fi + + test no = "$dlself" \ + || func_warning "'-dlopen self' is ignored for libtool libraries" + + set dummy $rpath + shift + test 1 -lt "$#" \ + && func_warning "ignoring multiple '-rpath's for a libtool library" + + install_libdir=$1 + + oldlibs= + if test -z "$rpath"; then + if test yes = "$build_libtool_libs"; then + # Building a libtool convenience library. + # Some compilers have problems with a '.al' extension so + # convenience libraries should have the same extension an + # archive normally would. + oldlibs="$output_objdir/$libname.$libext $oldlibs" + build_libtool_libs=convenience + build_old_libs=yes + fi + + test -n "$vinfo" && \ + func_warning "'-version-info/-version-number' is ignored for convenience libraries" + + test -n "$release" && \ + func_warning "'-release' is ignored for convenience libraries" + else + + # Parse the version information argument. + save_ifs=$IFS; IFS=: + set dummy $vinfo 0 0 0 + shift + IFS=$save_ifs + + test -n "$7" && \ + func_fatal_help "too many parameters to '-version-info'" + + # convert absolute version numbers to libtool ages + # this retains compatibility with .la files and attempts + # to make the code below a bit more comprehensible + + case $vinfo_number in + yes) + number_major=$1 + number_minor=$2 + number_revision=$3 + # + # There are really only two kinds -- those that + # use the current revision as the major version + # and those that subtract age and use age as + # a minor version. But, then there is irix + # that has an extra 1 added just for fun + # + case $version_type in + # correct linux to gnu/linux during the next big refactor + darwin|freebsd-elf|linux|osf|windows|none) + func_arith $number_major + $number_minor + current=$func_arith_result + age=$number_minor + revision=$number_revision + ;; + freebsd-aout|qnx|sunos) + current=$number_major + revision=$number_minor + age=0 + ;; + irix|nonstopux) + func_arith $number_major + $number_minor + current=$func_arith_result + age=$number_minor + revision=$number_minor + lt_irix_increment=no + ;; + *) + func_fatal_configuration "$modename: unknown library version type '$version_type'" + ;; + esac + ;; + no) + current=$1 + revision=$2 + age=$3 + ;; + esac + + # Check that each of the things are valid numbers. + case $current in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "CURRENT '$current' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + case $revision in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "REVISION '$revision' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + case $age in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "AGE '$age' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + if test "$age" -gt "$current"; then + func_error "AGE '$age' is greater than the current interface number '$current'" + func_fatal_error "'$vinfo' is not valid version information" + fi + + # Calculate the version variables. + major= + versuffix= + verstring= + case $version_type in + none) ;; + + darwin) + # Like Linux, but with the current version available in + # verstring for coding it into the library header + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + # Darwin ld doesn't like 0 for these options... + func_arith $current + 1 + minor_current=$func_arith_result + xlcverstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision" + verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" + # On Darwin other compilers + case $CC in + nagfor*) + verstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision" + ;; + *) + verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" + ;; + esac + ;; + + freebsd-aout) + major=.$current + versuffix=.$current.$revision + ;; + + freebsd-elf) + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + ;; + + irix | nonstopux) + if test no = "$lt_irix_increment"; then + func_arith $current - $age + else + func_arith $current - $age + 1 + fi + major=$func_arith_result + + case $version_type in + nonstopux) verstring_prefix=nonstopux ;; + *) verstring_prefix=sgi ;; + esac + verstring=$verstring_prefix$major.$revision + + # Add in all the interfaces that we are compatible with. + loop=$revision + while test 0 -ne "$loop"; do + func_arith $revision - $loop + iface=$func_arith_result + func_arith $loop - 1 + loop=$func_arith_result + verstring=$verstring_prefix$major.$iface:$verstring + done + + # Before this point, $major must not contain '.'. + major=.$major + versuffix=$major.$revision + ;; + + linux) # correct to gnu/linux during the next big refactor + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + ;; + + osf) + func_arith $current - $age + major=.$func_arith_result + versuffix=.$current.$age.$revision + verstring=$current.$age.$revision + + # Add in all the interfaces that we are compatible with. + loop=$age + while test 0 -ne "$loop"; do + func_arith $current - $loop + iface=$func_arith_result + func_arith $loop - 1 + loop=$func_arith_result + verstring=$verstring:$iface.0 + done + + # Make executables depend on our current version. + func_append verstring ":$current.0" + ;; + + qnx) + major=.$current + versuffix=.$current + ;; + + sco) + major=.$current + versuffix=.$current + ;; + + sunos) + major=.$current + versuffix=.$current.$revision + ;; + + windows) + # Use '-' rather than '.', since we only want one + # extension on DOS 8.3 file systems. + func_arith $current - $age + major=$func_arith_result + versuffix=-$major + ;; + + *) + func_fatal_configuration "unknown library version type '$version_type'" + ;; + esac + + # Clear the version info if we defaulted, and they specified a release. + if test -z "$vinfo" && test -n "$release"; then + major= + case $version_type in + darwin) + # we can't check for "0.0" in archive_cmds due to quoting + # problems, so we reset it completely + verstring= + ;; + *) + verstring=0.0 + ;; + esac + if test no = "$need_version"; then + versuffix= + else + versuffix=.0.0 + fi + fi + + # Remove version info from name if versioning should be avoided + if test yes,no = "$avoid_version,$need_version"; then + major= + versuffix= + verstring= + fi + + # Check to see if the archive will have undefined symbols. + if test yes = "$allow_undefined"; then + if test unsupported = "$allow_undefined_flag"; then + if test yes = "$build_old_libs"; then + func_warning "undefined symbols not allowed in $host shared libraries; building static only" + build_libtool_libs=no + else + func_fatal_error "can't build $host shared library unless -no-undefined is specified" + fi + fi + else + # Don't allow undefined symbols. + allow_undefined_flag=$no_undefined_flag + fi + + fi + + func_generate_dlsyms "$libname" "$libname" : + func_append libobjs " $symfileobj" + test " " = "$libobjs" && libobjs= + + if test relink != "$opt_mode"; then + # Remove our outputs, but don't remove object files since they + # may have been created when compiling PIC objects. + removelist= + tempremovelist=`$ECHO "$output_objdir/*"` + for p in $tempremovelist; do + case $p in + *.$objext | *.gcno) + ;; + $output_objdir/$outputname | $output_objdir/$libname.* | $output_objdir/$libname$release.*) + if test -n "$precious_files_regex"; then + if $ECHO "$p" | $EGREP -e "$precious_files_regex" >/dev/null 2>&1 + then + continue + fi + fi + func_append removelist " $p" + ;; + *) ;; + esac + done + test -n "$removelist" && \ + func_show_eval "${RM}r \$removelist" + fi + + # Now set the variables for building old libraries. + if test yes = "$build_old_libs" && test convenience != "$build_libtool_libs"; then + func_append oldlibs " $output_objdir/$libname.$libext" + + # Transform .lo files to .o files. + oldobjs="$objs "`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; $lo2o" | $NL2SP` + fi + + # Eliminate all temporary directories. + #for path in $notinst_path; do + # lib_search_path=`$ECHO "$lib_search_path " | $SED "s% $path % %g"` + # deplibs=`$ECHO "$deplibs " | $SED "s% -L$path % %g"` + # dependency_libs=`$ECHO "$dependency_libs " | $SED "s% -L$path % %g"` + #done + + if test -n "$xrpath"; then + # If the user specified any rpath flags, then add them. + temp_xrpath= + for libdir in $xrpath; do + func_replace_sysroot "$libdir" + func_append temp_xrpath " -R$func_replace_sysroot_result" + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + done + if test yes != "$hardcode_into_libs" || test yes = "$build_old_libs"; then + dependency_libs="$temp_xrpath $dependency_libs" + fi + fi + + # Make sure dlfiles contains only unique files that won't be dlpreopened + old_dlfiles=$dlfiles + dlfiles= + for lib in $old_dlfiles; do + case " $dlprefiles $dlfiles " in + *" $lib "*) ;; + *) func_append dlfiles " $lib" ;; + esac + done + + # Make sure dlprefiles contains only unique files + old_dlprefiles=$dlprefiles + dlprefiles= + for lib in $old_dlprefiles; do + case "$dlprefiles " in + *" $lib "*) ;; + *) func_append dlprefiles " $lib" ;; + esac + done + + if test yes = "$build_libtool_libs"; then + if test -n "$rpath"; then + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-beos* | *-cegcc* | *-*-haiku*) + # these systems don't actually have a c library (as such)! + ;; + *-*-rhapsody* | *-*-darwin1.[012]) + # Rhapsody C library is in the System framework + func_append deplibs " System.ltframework" + ;; + *-*-netbsd*) + # Don't link with libc until the a.out ld.so is fixed. + ;; + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*) + # Do not include libc due to us having libc/libc_r. + ;; + *-*-sco3.2v5* | *-*-sco5v6*) + # Causes problems with __ctype + ;; + *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) + # Compiler inserts libc in the correct place for threads to work + ;; + *) + # Add libc to deplibs on all other systems if necessary. + if test yes = "$build_libtool_need_lc"; then + func_append deplibs " -lc" + fi + ;; + esac + fi + + # Transform deplibs into only deplibs that can be linked in shared. + name_save=$name + libname_save=$libname + release_save=$release + versuffix_save=$versuffix + major_save=$major + # I'm not sure if I'm treating the release correctly. I think + # release should show up in the -l (ie -lgmp5) so we don't want to + # add it in twice. Is that correct? + release= + versuffix= + major= + newdeplibs= + droppeddeps=no + case $deplibs_check_method in + pass_all) + # Don't check for shared/static. Everything works. + # This might be a little naive. We might want to check + # whether the library exists or not. But this is on + # osf3 & osf4 and I'm not really sure... Just + # implementing what was already the behavior. + newdeplibs=$deplibs + ;; + test_compile) + # This code stresses the "libraries are programs" paradigm to its + # limits. Maybe even breaks it. We compile a program, linking it + # against the deplibs as a proxy for the library. Then we can check + # whether they linked in statically or dynamically with ldd. + $opt_dry_run || $RM conftest.c + cat > conftest.c <<EOF + int main() { return 0; } +EOF + $opt_dry_run || $RM conftest + if $LTCC $LTCFLAGS -o conftest conftest.c $deplibs; then + ldd_output=`ldd conftest` + for i in $deplibs; do + case $i in + -l*) + func_stripname -l '' "$i" + name=$func_stripname_result + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $i "*) + func_append newdeplibs " $i" + i= + ;; + esac + fi + if test -n "$i"; then + libname=`eval "\\$ECHO \"$libname_spec\""` + deplib_matches=`eval "\\$ECHO \"$library_names_spec\""` + set dummy $deplib_matches; shift + deplib_match=$1 + if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0; then + func_append newdeplibs " $i" + else + droppeddeps=yes + echo + $ECHO "*** Warning: dynamic linker does not accept needed library $i." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which I believe you do not have" + echo "*** because a test_compile did reveal that the linker did not use it for" + echo "*** its dynamic dependency list that programs get resolved with at runtime." + fi + fi + ;; + *) + func_append newdeplibs " $i" + ;; + esac + done + else + # Error occurred in the first compile. Let's try to salvage + # the situation: Compile a separate program for each library. + for i in $deplibs; do + case $i in + -l*) + func_stripname -l '' "$i" + name=$func_stripname_result + $opt_dry_run || $RM conftest + if $LTCC $LTCFLAGS -o conftest conftest.c $i; then + ldd_output=`ldd conftest` + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $i "*) + func_append newdeplibs " $i" + i= + ;; + esac + fi + if test -n "$i"; then + libname=`eval "\\$ECHO \"$libname_spec\""` + deplib_matches=`eval "\\$ECHO \"$library_names_spec\""` + set dummy $deplib_matches; shift + deplib_match=$1 + if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0; then + func_append newdeplibs " $i" + else + droppeddeps=yes + echo + $ECHO "*** Warning: dynamic linker does not accept needed library $i." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because a test_compile did reveal that the linker did not use this one" + echo "*** as a dynamic dependency that programs can get resolved with at runtime." + fi + fi + else + droppeddeps=yes + echo + $ECHO "*** Warning! Library $i is needed by this library but I was not able to" + echo "*** make it link in! You will probably need to install it or some" + echo "*** library that it depends on before this library will be fully" + echo "*** functional. Installing it before continuing would be even better." + fi + ;; + *) + func_append newdeplibs " $i" + ;; + esac + done + fi + ;; + file_magic*) + set dummy $deplibs_check_method; shift + file_magic_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` + for a_deplib in $deplibs; do + case $a_deplib in + -l*) + func_stripname -l '' "$a_deplib" + name=$func_stripname_result + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $a_deplib "*) + func_append newdeplibs " $a_deplib" + a_deplib= + ;; + esac + fi + if test -n "$a_deplib"; then + libname=`eval "\\$ECHO \"$libname_spec\""` + if test -n "$file_magic_glob"; then + libnameglob=`func_echo_all "$libname" | $SED -e $file_magic_glob` + else + libnameglob=$libname + fi + test yes = "$want_nocaseglob" && nocaseglob=`shopt -p nocaseglob` + for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do + if test yes = "$want_nocaseglob"; then + shopt -s nocaseglob + potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null` + $nocaseglob + else + potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null` + fi + for potent_lib in $potential_libs; do + # Follow soft links. + if ls -lLd "$potent_lib" 2>/dev/null | + $GREP " -> " >/dev/null; then + continue + fi + # The statement above tries to avoid entering an + # endless loop below, in case of cyclic links. + # We might still enter an endless loop, since a link + # loop can be closed while we follow links, + # but so what? + potlib=$potent_lib + while test -h "$potlib" 2>/dev/null; do + potliblink=`ls -ld $potlib | $SED 's/.* -> //'` + case $potliblink in + [\\/]* | [A-Za-z]:[\\/]*) potlib=$potliblink;; + *) potlib=`$ECHO "$potlib" | $SED 's|[^/]*$||'`"$potliblink";; + esac + done + if eval $file_magic_cmd \"\$potlib\" 2>/dev/null | + $SED -e 10q | + $EGREP "$file_magic_regex" > /dev/null; then + func_append newdeplibs " $a_deplib" + a_deplib= + break 2 + fi + done + done + fi + if test -n "$a_deplib"; then + droppeddeps=yes + echo + $ECHO "*** Warning: linker path does not have real file for library $a_deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because I did check the linker path looking for a file starting" + if test -z "$potlib"; then + $ECHO "*** with $libname but no candidates were found. (...for file magic test)" + else + $ECHO "*** with $libname and none of the candidates passed a file format test" + $ECHO "*** using a file magic. Last file checked: $potlib" + fi + fi + ;; + *) + # Add a -L argument. + func_append newdeplibs " $a_deplib" + ;; + esac + done # Gone through all deplibs. + ;; + match_pattern*) + set dummy $deplibs_check_method; shift + match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` + for a_deplib in $deplibs; do + case $a_deplib in + -l*) + func_stripname -l '' "$a_deplib" + name=$func_stripname_result + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $a_deplib "*) + func_append newdeplibs " $a_deplib" + a_deplib= + ;; + esac + fi + if test -n "$a_deplib"; then + libname=`eval "\\$ECHO \"$libname_spec\""` + for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do + potential_libs=`ls $i/$libname[.-]* 2>/dev/null` + for potent_lib in $potential_libs; do + potlib=$potent_lib # see symlink-check above in file_magic test + if eval "\$ECHO \"$potent_lib\"" 2>/dev/null | $SED 10q | \ + $EGREP "$match_pattern_regex" > /dev/null; then + func_append newdeplibs " $a_deplib" + a_deplib= + break 2 + fi + done + done + fi + if test -n "$a_deplib"; then + droppeddeps=yes + echo + $ECHO "*** Warning: linker path does not have real file for library $a_deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because I did check the linker path looking for a file starting" + if test -z "$potlib"; then + $ECHO "*** with $libname but no candidates were found. (...for regex pattern test)" + else + $ECHO "*** with $libname and none of the candidates passed a file format test" + $ECHO "*** using a regex pattern. Last file checked: $potlib" + fi + fi + ;; + *) + # Add a -L argument. + func_append newdeplibs " $a_deplib" + ;; + esac + done # Gone through all deplibs. + ;; + none | unknown | *) + newdeplibs= + tmp_deplibs=`$ECHO " $deplibs" | $SED 's/ -lc$//; s/ -[LR][^ ]*//g'` + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + for i in $predeps $postdeps; do + # can't use Xsed below, because $i might contain '/' + tmp_deplibs=`$ECHO " $tmp_deplibs" | $SED "s|$i||"` + done + fi + case $tmp_deplibs in + *[!\ \ ]*) + echo + if test none = "$deplibs_check_method"; then + echo "*** Warning: inter-library dependencies are not supported in this platform." + else + echo "*** Warning: inter-library dependencies are not known to be supported." + fi + echo "*** All declared inter-library dependencies are being dropped." + droppeddeps=yes + ;; + esac + ;; + esac + versuffix=$versuffix_save + major=$major_save + release=$release_save + libname=$libname_save + name=$name_save + + case $host in + *-*-rhapsody* | *-*-darwin1.[012]) + # On Rhapsody replace the C library with the System framework + newdeplibs=`$ECHO " $newdeplibs" | $SED 's/ -lc / System.ltframework /'` + ;; + esac + + if test yes = "$droppeddeps"; then + if test yes = "$module"; then + echo + echo "*** Warning: libtool could not satisfy all declared inter-library" + $ECHO "*** dependencies of module $libname. Therefore, libtool will create" + echo "*** a static module, that should work as long as the dlopening" + echo "*** application is linked with the -dlopen flag." + if test -z "$global_symbol_pipe"; then + echo + echo "*** However, this would only work if libtool was able to extract symbol" + echo "*** lists from a program, using 'nm' or equivalent, but libtool could" + echo "*** not find such a program. So, this module is probably useless." + echo "*** 'nm' from GNU binutils and a full rebuild may help." + fi + if test no = "$build_old_libs"; then + oldlibs=$output_objdir/$libname.$libext + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + else + echo "*** The inter-library dependencies that have been dropped here will be" + echo "*** automatically added whenever a program is linked with this library" + echo "*** or is declared to -dlopen it." + + if test no = "$allow_undefined"; then + echo + echo "*** Since this library must not contain undefined symbols," + echo "*** because either the platform does not support them or" + echo "*** it was explicitly requested with -no-undefined," + echo "*** libtool will only create a static version of it." + if test no = "$build_old_libs"; then + oldlibs=$output_objdir/$libname.$libext + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + fi + fi + fi + # Done checking deplibs! + deplibs=$newdeplibs + fi + # Time to change all our "foo.ltframework" stuff back to "-framework foo" + case $host in + *-*-darwin*) + newdeplibs=`$ECHO " $newdeplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + new_inherited_linker_flags=`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + deplibs=`$ECHO " $deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + ;; + esac + + # move library search paths that coincide with paths to not yet + # installed libraries to the beginning of the library search list + new_libs= + for path in $notinst_path; do + case " $new_libs " in + *" -L$path/$objdir "*) ;; + *) + case " $deplibs " in + *" -L$path/$objdir "*) + func_append new_libs " -L$path/$objdir" ;; + esac + ;; + esac + done + for deplib in $deplibs; do + case $deplib in + -L*) + case " $new_libs " in + *" $deplib "*) ;; + *) func_append new_libs " $deplib" ;; + esac + ;; + *) func_append new_libs " $deplib" ;; + esac + done + deplibs=$new_libs + + # All the library-specific variables (install_libdir is set above). + library_names= + old_library= + dlname= + + # Test again, we may have decided not to build it any more + if test yes = "$build_libtool_libs"; then + # Remove $wl instances when linking with ld. + # FIXME: should test the right _cmds variable. + case $archive_cmds in + *\$LD\ *) wl= ;; + esac + if test yes = "$hardcode_into_libs"; then + # Hardcode the library paths + hardcode_libdirs= + dep_rpath= + rpath=$finalize_rpath + test relink = "$opt_mode" || rpath=$compile_rpath$rpath + for libdir in $rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + func_replace_sysroot "$libdir" + libdir=$func_replace_sysroot_result + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append dep_rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) func_append perm_rpath " $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval "dep_rpath=\"$hardcode_libdir_flag_spec\"" + fi + if test -n "$runpath_var" && test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + func_append rpath "$dir:" + done + eval "$runpath_var='$rpath\$$runpath_var'; export $runpath_var" + fi + test -n "$dep_rpath" && deplibs="$dep_rpath $deplibs" + fi + + shlibpath=$finalize_shlibpath + test relink = "$opt_mode" || shlibpath=$compile_shlibpath$shlibpath + if test -n "$shlibpath"; then + eval "$shlibpath_var='$shlibpath\$$shlibpath_var'; export $shlibpath_var" + fi + + # Get the real and link names of the library. + eval shared_ext=\"$shrext_cmds\" + eval library_names=\"$library_names_spec\" + set dummy $library_names + shift + realname=$1 + shift + + if test -n "$soname_spec"; then + eval soname=\"$soname_spec\" + else + soname=$realname + fi + if test -z "$dlname"; then + dlname=$soname + fi + + lib=$output_objdir/$realname + linknames= + for link + do + func_append linknames " $link" + done + + # Use standard objects if they are pic + test -z "$pic_flag" && libobjs=`$ECHO "$libobjs" | $SP2NL | $SED "$lo2o" | $NL2SP` + test "X$libobjs" = "X " && libobjs= + + delfiles= + if test -n "$export_symbols" && test -n "$include_expsyms"; then + $opt_dry_run || cp "$export_symbols" "$output_objdir/$libname.uexp" + export_symbols=$output_objdir/$libname.uexp + func_append delfiles " $export_symbols" + fi + + orig_export_symbols= + case $host_os in + cygwin* | mingw* | cegcc*) + if test -n "$export_symbols" && test -z "$export_symbols_regex"; then + # exporting using user supplied symfile + func_dll_def_p "$export_symbols" || { + # and it's NOT already a .def file. Must figure out + # which of the given symbols are data symbols and tag + # them as such. So, trigger use of export_symbols_cmds. + # export_symbols gets reassigned inside the "prepare + # the list of exported symbols" if statement, so the + # include_expsyms logic still works. + orig_export_symbols=$export_symbols + export_symbols= + always_export_symbols=yes + } + fi + ;; + esac + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + if test yes = "$always_export_symbols" || test -n "$export_symbols_regex"; then + func_verbose "generating symbol list for '$libname.la'" + export_symbols=$output_objdir/$libname.exp + $opt_dry_run || $RM $export_symbols + cmds=$export_symbols_cmds + save_ifs=$IFS; IFS='~' + for cmd1 in $cmds; do + IFS=$save_ifs + # Take the normal branch if the nm_file_list_spec branch + # doesn't work or if tool conversion is not needed. + case $nm_file_list_spec~$to_tool_file_cmd in + *~func_convert_file_noop | *~func_convert_file_msys_to_w32 | ~*) + try_normal_branch=yes + eval cmd=\"$cmd1\" + func_len " $cmd" + len=$func_len_result + ;; + *) + try_normal_branch=no + ;; + esac + if test yes = "$try_normal_branch" \ + && { test "$len" -lt "$max_cmd_len" \ + || test "$max_cmd_len" -le -1; } + then + func_show_eval "$cmd" 'exit $?' + skipped_export=false + elif test -n "$nm_file_list_spec"; then + func_basename "$output" + output_la=$func_basename_result + save_libobjs=$libobjs + save_output=$output + output=$output_objdir/$output_la.nm + func_to_tool_file "$output" + libobjs=$nm_file_list_spec$func_to_tool_file_result + func_append delfiles " $output" + func_verbose "creating $NM input file list: $output" + for obj in $save_libobjs; do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" + done > "$output" + eval cmd=\"$cmd1\" + func_show_eval "$cmd" 'exit $?' + output=$save_output + libobjs=$save_libobjs + skipped_export=false + else + # The command line is too long to execute in one step. + func_verbose "using reloadable object file for export list..." + skipped_export=: + # Break out early, otherwise skipped_export may be + # set to false by a later but shorter cmd. + break + fi + done + IFS=$save_ifs + if test -n "$export_symbols_regex" && test : != "$skipped_export"; then + func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' + func_show_eval '$MV "${export_symbols}T" "$export_symbols"' + fi + fi + fi + + if test -n "$export_symbols" && test -n "$include_expsyms"; then + tmp_export_symbols=$export_symbols + test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols + $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"' + fi + + if test : != "$skipped_export" && test -n "$orig_export_symbols"; then + # The given exports_symbols file has to be filtered, so filter it. + func_verbose "filter symbol list for '$libname.la' to tag DATA exports" + # FIXME: $output_objdir/$libname.filter potentially contains lots of + # 's' commands, which not all seds can handle. GNU sed should be fine + # though. Also, the filter scales superlinearly with the number of + # global variables. join(1) would be nice here, but unfortunately + # isn't a blessed tool. + $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter + func_append delfiles " $export_symbols $output_objdir/$libname.filter" + export_symbols=$output_objdir/$libname.def + $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols + fi + + tmp_deplibs= + for test_deplib in $deplibs; do + case " $convenience " in + *" $test_deplib "*) ;; + *) + func_append tmp_deplibs " $test_deplib" + ;; + esac + done + deplibs=$tmp_deplibs + + if test -n "$convenience"; then + if test -n "$whole_archive_flag_spec" && + test yes = "$compiler_needs_object" && + test -z "$libobjs"; then + # extract the archives, so we have objects to list. + # TODO: could optimize this to just extract one archive. + whole_archive_flag_spec= + fi + if test -n "$whole_archive_flag_spec"; then + save_libobjs=$libobjs + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + test "X$libobjs" = "X " && libobjs= + else + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $convenience + func_append libobjs " $func_extract_archives_result" + test "X$libobjs" = "X " && libobjs= + fi + fi + + if test yes = "$thread_safe" && test -n "$thread_safe_flag_spec"; then + eval flag=\"$thread_safe_flag_spec\" + func_append linker_flags " $flag" + fi + + # Make a backup of the uninstalled library when relinking + if test relink = "$opt_mode"; then + $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}U && $MV $realname ${realname}U)' || exit $? + fi + + # Do each of the archive commands. + if test yes = "$module" && test -n "$module_cmds"; then + if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then + eval test_cmds=\"$module_expsym_cmds\" + cmds=$module_expsym_cmds + else + eval test_cmds=\"$module_cmds\" + cmds=$module_cmds + fi + else + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + eval test_cmds=\"$archive_expsym_cmds\" + cmds=$archive_expsym_cmds + else + eval test_cmds=\"$archive_cmds\" + cmds=$archive_cmds + fi + fi + + if test : != "$skipped_export" && + func_len " $test_cmds" && + len=$func_len_result && + test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then + : + else + # The command line is too long to link in one step, link piecewise + # or, if using GNU ld and skipped_export is not :, use a linker + # script. + + # Save the value of $output and $libobjs because we want to + # use them later. If we have whole_archive_flag_spec, we + # want to use save_libobjs as it was before + # whole_archive_flag_spec was expanded, because we can't + # assume the linker understands whole_archive_flag_spec. + # This may have to be revisited, in case too many + # convenience libraries get linked in and end up exceeding + # the spec. + if test -z "$convenience" || test -z "$whole_archive_flag_spec"; then + save_libobjs=$libobjs + fi + save_output=$output + func_basename "$output" + output_la=$func_basename_result + + # Clear the reloadable object creation command queue and + # initialize k to one. + test_cmds= + concat_cmds= + objlist= + last_robj= + k=1 + + if test -n "$save_libobjs" && test : != "$skipped_export" && test yes = "$with_gnu_ld"; then + output=$output_objdir/$output_la.lnkscript + func_verbose "creating GNU ld script: $output" + echo 'INPUT (' > $output + for obj in $save_libobjs + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" >> $output + done + echo ')' >> $output + func_append delfiles " $output" + func_to_tool_file "$output" + output=$func_to_tool_file_result + elif test -n "$save_libobjs" && test : != "$skipped_export" && test -n "$file_list_spec"; then + output=$output_objdir/$output_la.lnk + func_verbose "creating linker input file list: $output" + : > $output + set x $save_libobjs + shift + firstobj= + if test yes = "$compiler_needs_object"; then + firstobj="$1 " + shift + fi + for obj + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" >> $output + done + func_append delfiles " $output" + func_to_tool_file "$output" + output=$firstobj\"$file_list_spec$func_to_tool_file_result\" + else + if test -n "$save_libobjs"; then + func_verbose "creating reloadable object files..." + output=$output_objdir/$output_la-$k.$objext + eval test_cmds=\"$reload_cmds\" + func_len " $test_cmds" + len0=$func_len_result + len=$len0 + + # Loop over the list of objects to be linked. + for obj in $save_libobjs + do + func_len " $obj" + func_arith $len + $func_len_result + len=$func_arith_result + if test -z "$objlist" || + test "$len" -lt "$max_cmd_len"; then + func_append objlist " $obj" + else + # The command $test_cmds is almost too long, add a + # command to the queue. + if test 1 -eq "$k"; then + # The first file doesn't have a previous command to add. + reload_objs=$objlist + eval concat_cmds=\"$reload_cmds\" + else + # All subsequent reloadable object files will link in + # the last one created. + reload_objs="$objlist $last_robj" + eval concat_cmds=\"\$concat_cmds~$reload_cmds~\$RM $last_robj\" + fi + last_robj=$output_objdir/$output_la-$k.$objext + func_arith $k + 1 + k=$func_arith_result + output=$output_objdir/$output_la-$k.$objext + objlist=" $obj" + func_len " $last_robj" + func_arith $len0 + $func_len_result + len=$func_arith_result + fi + done + # Handle the remaining objects by creating one last + # reloadable object file. All subsequent reloadable object + # files will link in the last one created. + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + reload_objs="$objlist $last_robj" + eval concat_cmds=\"\$concat_cmds$reload_cmds\" + if test -n "$last_robj"; then + eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\" + fi + func_append delfiles " $output" + + else + output= + fi + + ${skipped_export-false} && { + func_verbose "generating symbol list for '$libname.la'" + export_symbols=$output_objdir/$libname.exp + $opt_dry_run || $RM $export_symbols + libobjs=$output + # Append the command to create the export file. + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + eval concat_cmds=\"\$concat_cmds$export_symbols_cmds\" + if test -n "$last_robj"; then + eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\" + fi + } + + test -n "$save_libobjs" && + func_verbose "creating a temporary reloadable object file: $output" + + # Loop through the commands generated above and execute them. + save_ifs=$IFS; IFS='~' + for cmd in $concat_cmds; do + IFS=$save_ifs + $opt_quiet || { + func_quote_for_expand "$cmd" + eval "func_echo $func_quote_for_expand_result" + } + $opt_dry_run || eval "$cmd" || { + lt_exit=$? + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + ( cd "$output_objdir" && \ + $RM "${realname}T" && \ + $MV "${realname}U" "$realname" ) + fi + + exit $lt_exit + } + done + IFS=$save_ifs + + if test -n "$export_symbols_regex" && ${skipped_export-false}; then + func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' + func_show_eval '$MV "${export_symbols}T" "$export_symbols"' + fi + fi + + ${skipped_export-false} && { + if test -n "$export_symbols" && test -n "$include_expsyms"; then + tmp_export_symbols=$export_symbols + test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols + $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"' + fi + + if test -n "$orig_export_symbols"; then + # The given exports_symbols file has to be filtered, so filter it. + func_verbose "filter symbol list for '$libname.la' to tag DATA exports" + # FIXME: $output_objdir/$libname.filter potentially contains lots of + # 's' commands, which not all seds can handle. GNU sed should be fine + # though. Also, the filter scales superlinearly with the number of + # global variables. join(1) would be nice here, but unfortunately + # isn't a blessed tool. + $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter + func_append delfiles " $export_symbols $output_objdir/$libname.filter" + export_symbols=$output_objdir/$libname.def + $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols + fi + } + + libobjs=$output + # Restore the value of output. + output=$save_output + + if test -n "$convenience" && test -n "$whole_archive_flag_spec"; then + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + test "X$libobjs" = "X " && libobjs= + fi + # Expand the library linking commands again to reset the + # value of $libobjs for piecewise linking. + + # Do each of the archive commands. + if test yes = "$module" && test -n "$module_cmds"; then + if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then + cmds=$module_expsym_cmds + else + cmds=$module_cmds + fi + else + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + cmds=$archive_expsym_cmds + else + cmds=$archive_cmds + fi + fi + fi + + if test -n "$delfiles"; then + # Append the command to remove temporary files to $cmds. + eval cmds=\"\$cmds~\$RM $delfiles\" + fi + + # Add any objects from preloaded convenience libraries + if test -n "$dlprefiles"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $dlprefiles + func_append libobjs " $func_extract_archives_result" + test "X$libobjs" = "X " && libobjs= + fi + + save_ifs=$IFS; IFS='~' + for cmd in $cmds; do + IFS=$sp$nl + eval cmd=\"$cmd\" + IFS=$save_ifs + $opt_quiet || { + func_quote_for_expand "$cmd" + eval "func_echo $func_quote_for_expand_result" + } + $opt_dry_run || eval "$cmd" || { + lt_exit=$? + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + ( cd "$output_objdir" && \ + $RM "${realname}T" && \ + $MV "${realname}U" "$realname" ) + fi + + exit $lt_exit + } + done + IFS=$save_ifs + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}T && $MV $realname ${realname}T && $MV ${realname}U $realname)' || exit $? + + if test -n "$convenience"; then + if test -z "$whole_archive_flag_spec"; then + func_show_eval '${RM}r "$gentop"' + fi + fi + + exit $EXIT_SUCCESS + fi + + # Create links to the real library. + for linkname in $linknames; do + if test "$realname" != "$linkname"; then + func_show_eval '(cd "$output_objdir" && $RM "$linkname" && $LN_S "$realname" "$linkname")' 'exit $?' + fi + done + + # If -module or -export-dynamic was specified, set the dlname. + if test yes = "$module" || test yes = "$export_dynamic"; then + # On all known operating systems, these are identical. + dlname=$soname + fi + fi + ;; + + obj) + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + func_warning "'-dlopen' is ignored for objects" + fi + + case " $deplibs" in + *\ -l* | *\ -L*) + func_warning "'-l' and '-L' are ignored for objects" ;; + esac + + test -n "$rpath" && \ + func_warning "'-rpath' is ignored for objects" + + test -n "$xrpath" && \ + func_warning "'-R' is ignored for objects" + + test -n "$vinfo" && \ + func_warning "'-version-info' is ignored for objects" + + test -n "$release" && \ + func_warning "'-release' is ignored for objects" + + case $output in + *.lo) + test -n "$objs$old_deplibs" && \ + func_fatal_error "cannot build library object '$output' from non-libtool objects" + + libobj=$output + func_lo2o "$libobj" + obj=$func_lo2o_result + ;; + *) + libobj= + obj=$output + ;; + esac + + # Delete the old objects. + $opt_dry_run || $RM $obj $libobj + + # Objects from convenience libraries. This assumes + # single-version convenience libraries. Whenever we create + # different ones for PIC/non-PIC, this we'll have to duplicate + # the extraction. + reload_conv_objs= + gentop= + # if reload_cmds runs $LD directly, get rid of -Wl from + # whole_archive_flag_spec and hope we can get by with turning comma + # into space. + case $reload_cmds in + *\$LD[\ \$]*) wl= ;; + esac + if test -n "$convenience"; then + if test -n "$whole_archive_flag_spec"; then + eval tmp_whole_archive_flags=\"$whole_archive_flag_spec\" + test -n "$wl" || tmp_whole_archive_flags=`$ECHO "$tmp_whole_archive_flags" | $SED 's|,| |g'` + reload_conv_objs=$reload_objs\ $tmp_whole_archive_flags + else + gentop=$output_objdir/${obj}x + func_append generated " $gentop" + + func_extract_archives $gentop $convenience + reload_conv_objs="$reload_objs $func_extract_archives_result" + fi + fi + + # If we're not building shared, we need to use non_pic_objs + test yes = "$build_libtool_libs" || libobjs=$non_pic_objects + + # Create the old-style object. + reload_objs=$objs$old_deplibs' '`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; /\.lib$/d; $lo2o" | $NL2SP`' '$reload_conv_objs + + output=$obj + func_execute_cmds "$reload_cmds" 'exit $?' + + # Exit if we aren't doing a library object file. + if test -z "$libobj"; then + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + exit $EXIT_SUCCESS + fi + + test yes = "$build_libtool_libs" || { + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + # Create an invalid libtool object if no PIC, so that we don't + # accidentally link it into a program. + # $show "echo timestamp > $libobj" + # $opt_dry_run || eval "echo timestamp > $libobj" || exit $? + exit $EXIT_SUCCESS + } + + if test -n "$pic_flag" || test default != "$pic_mode"; then + # Only do commands if we really have different PIC objects. + reload_objs="$libobjs $reload_conv_objs" + output=$libobj + func_execute_cmds "$reload_cmds" 'exit $?' + fi + + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + exit $EXIT_SUCCESS + ;; + + prog) + case $host in + *cygwin*) func_stripname '' '.exe' "$output" + output=$func_stripname_result.exe;; + esac + test -n "$vinfo" && \ + func_warning "'-version-info' is ignored for programs" + + test -n "$release" && \ + func_warning "'-release' is ignored for programs" + + $preload \ + && test unknown,unknown,unknown = "$dlopen_support,$dlopen_self,$dlopen_self_static" \ + && func_warning "'LT_INIT([dlopen])' not used. Assuming no dlopen support." + + case $host in + *-*-rhapsody* | *-*-darwin1.[012]) + # On Rhapsody replace the C library is the System framework + compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's/ -lc / System.ltframework /'` + finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's/ -lc / System.ltframework /'` + ;; + esac + + case $host in + *-*-darwin*) + # Don't allow lazy linking, it breaks C++ global constructors + # But is supposedly fixed on 10.4 or later (yay!). + if test CXX = "$tagname"; then + case ${MACOSX_DEPLOYMENT_TARGET-10.0} in + 10.[0123]) + func_append compile_command " $wl-bind_at_load" + func_append finalize_command " $wl-bind_at_load" + ;; + esac + fi + # Time to change all our "foo.ltframework" stuff back to "-framework foo" + compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + ;; + esac + + + # move library search paths that coincide with paths to not yet + # installed libraries to the beginning of the library search list + new_libs= + for path in $notinst_path; do + case " $new_libs " in + *" -L$path/$objdir "*) ;; + *) + case " $compile_deplibs " in + *" -L$path/$objdir "*) + func_append new_libs " -L$path/$objdir" ;; + esac + ;; + esac + done + for deplib in $compile_deplibs; do + case $deplib in + -L*) + case " $new_libs " in + *" $deplib "*) ;; + *) func_append new_libs " $deplib" ;; + esac + ;; + *) func_append new_libs " $deplib" ;; + esac + done + compile_deplibs=$new_libs + + + func_append compile_command " $compile_deplibs" + func_append finalize_command " $finalize_deplibs" + + if test -n "$rpath$xrpath"; then + # If the user specified any rpath flags, then add them. + for libdir in $rpath $xrpath; do + # This is the magic to use -rpath. + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + done + fi + + # Now hardcode the library paths + rpath= + hardcode_libdirs= + for libdir in $compile_rpath $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) func_append perm_rpath " $libdir" ;; + esac + fi + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + testbindir=`$ECHO "$libdir" | $SED -e 's*/lib$*/bin*'` + case :$dllsearchpath: in + *":$libdir:"*) ;; + ::) dllsearchpath=$libdir;; + *) func_append dllsearchpath ":$libdir";; + esac + case :$dllsearchpath: in + *":$testbindir:"*) ;; + ::) dllsearchpath=$testbindir;; + *) func_append dllsearchpath ":$testbindir";; + esac + ;; + esac + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + compile_rpath=$rpath + + rpath= + hardcode_libdirs= + for libdir in $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$finalize_perm_rpath " in + *" $libdir "*) ;; + *) func_append finalize_perm_rpath " $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + finalize_rpath=$rpath + + if test -n "$libobjs" && test yes = "$build_old_libs"; then + # Transform all the library objects into standard objects. + compile_command=`$ECHO "$compile_command" | $SP2NL | $SED "$lo2o" | $NL2SP` + finalize_command=`$ECHO "$finalize_command" | $SP2NL | $SED "$lo2o" | $NL2SP` + fi + + func_generate_dlsyms "$outputname" "@PROGRAM@" false + + # template prelinking step + if test -n "$prelink_cmds"; then + func_execute_cmds "$prelink_cmds" 'exit $?' + fi + + wrappers_required=: + case $host in + *cegcc* | *mingw32ce*) + # Disable wrappers for cegcc and mingw32ce hosts, we are cross compiling anyway. + wrappers_required=false + ;; + *cygwin* | *mingw* ) + test yes = "$build_libtool_libs" || wrappers_required=false + ;; + *) + if test no = "$need_relink" || test yes != "$build_libtool_libs"; then + wrappers_required=false + fi + ;; + esac + $wrappers_required || { + # Replace the output file specification. + compile_command=`$ECHO "$compile_command" | $SED 's%@OUTPUT@%'"$output"'%g'` + link_command=$compile_command$compile_rpath + + # We have no uninstalled library dependencies, so finalize right now. + exit_status=0 + func_show_eval "$link_command" 'exit_status=$?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + # Delete the generated files. + if test -f "$output_objdir/${outputname}S.$objext"; then + func_show_eval '$RM "$output_objdir/${outputname}S.$objext"' + fi + + exit $exit_status + } + + if test -n "$compile_shlibpath$finalize_shlibpath"; then + compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command" + fi + if test -n "$finalize_shlibpath"; then + finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command" + fi + + compile_var= + finalize_var= + if test -n "$runpath_var"; then + if test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + func_append rpath "$dir:" + done + compile_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + if test -n "$finalize_perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $finalize_perm_rpath; do + func_append rpath "$dir:" + done + finalize_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + fi + + if test yes = "$no_install"; then + # We don't need to create a wrapper script. + link_command=$compile_var$compile_command$compile_rpath + # Replace the output file specification. + link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output"'%g'` + # Delete the old output file. + $opt_dry_run || $RM $output + # Link the executable and exit + func_show_eval "$link_command" 'exit $?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + exit $EXIT_SUCCESS + fi + + case $hardcode_action,$fast_install in + relink,*) + # Fast installation is not supported + link_command=$compile_var$compile_command$compile_rpath + relink_command=$finalize_var$finalize_command$finalize_rpath + + func_warning "this platform does not like uninstalled shared libraries" + func_warning "'$output' will be relinked during installation" + ;; + *,yes) + link_command=$finalize_var$compile_command$finalize_rpath + relink_command=`$ECHO "$compile_var$compile_command$compile_rpath" | $SED 's%@OUTPUT@%\$progdir/\$file%g'` + ;; + *,no) + link_command=$compile_var$compile_command$compile_rpath + relink_command=$finalize_var$finalize_command$finalize_rpath + ;; + *,needless) + link_command=$finalize_var$compile_command$finalize_rpath + relink_command= + ;; + esac + + # Replace the output file specification. + link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'` + + # Delete the old output files. + $opt_dry_run || $RM $output $output_objdir/$outputname $output_objdir/lt-$outputname + + func_show_eval "$link_command" 'exit $?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output_objdir/$outputname" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + # Now create the wrapper script. + func_verbose "creating $output" + + # Quote the relink command for shipping. + if test -n "$relink_command"; then + # Preserve any variables that may affect compiler behavior + for var in $variables_saved_for_relink; do + if eval test -z \"\${$var+set}\"; then + relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command" + elif eval var_value=\$$var; test -z "$var_value"; then + relink_command="$var=; export $var; $relink_command" + else + func_quote_for_eval "$var_value" + relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command" + fi + done + relink_command="(cd `pwd`; $relink_command)" + relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"` + fi + + # Only actually do things if not in dry run mode. + $opt_dry_run || { + # win32 will think the script is a binary if it has + # a .exe suffix, so we strip it off here. + case $output in + *.exe) func_stripname '' '.exe' "$output" + output=$func_stripname_result ;; + esac + # test for cygwin because mv fails w/o .exe extensions + case $host in + *cygwin*) + exeext=.exe + func_stripname '' '.exe' "$outputname" + outputname=$func_stripname_result ;; + *) exeext= ;; + esac + case $host in + *cygwin* | *mingw* ) + func_dirname_and_basename "$output" "" "." + output_name=$func_basename_result + output_path=$func_dirname_result + cwrappersource=$output_path/$objdir/lt-$output_name.c + cwrapper=$output_path/$output_name.exe + $RM $cwrappersource $cwrapper + trap "$RM $cwrappersource $cwrapper; exit $EXIT_FAILURE" 1 2 15 + + func_emit_cwrapperexe_src > $cwrappersource + + # The wrapper executable is built using the $host compiler, + # because it contains $host paths and files. If cross- + # compiling, it, like the target executable, must be + # executed on the $host or under an emulation environment. + $opt_dry_run || { + $LTCC $LTCFLAGS -o $cwrapper $cwrappersource + $STRIP $cwrapper + } + + # Now, create the wrapper script for func_source use: + func_ltwrapper_scriptname $cwrapper + $RM $func_ltwrapper_scriptname_result + trap "$RM $func_ltwrapper_scriptname_result; exit $EXIT_FAILURE" 1 2 15 + $opt_dry_run || { + # note: this script will not be executed, so do not chmod. + if test "x$build" = "x$host"; then + $cwrapper --lt-dump-script > $func_ltwrapper_scriptname_result + else + func_emit_wrapper no > $func_ltwrapper_scriptname_result + fi + } + ;; + * ) + $RM $output + trap "$RM $output; exit $EXIT_FAILURE" 1 2 15 + + func_emit_wrapper no > $output + chmod +x $output + ;; + esac + } + exit $EXIT_SUCCESS + ;; + esac + + # See if we need to build an old-fashioned archive. + for oldlib in $oldlibs; do + + case $build_libtool_libs in + convenience) + oldobjs="$libobjs_save $symfileobj" + addlibs=$convenience + build_libtool_libs=no + ;; + module) + oldobjs=$libobjs_save + addlibs=$old_convenience + build_libtool_libs=no + ;; + *) + oldobjs="$old_deplibs $non_pic_objects" + $preload && test -f "$symfileobj" \ + && func_append oldobjs " $symfileobj" + addlibs=$old_convenience + ;; + esac + + if test -n "$addlibs"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $addlibs + func_append oldobjs " $func_extract_archives_result" + fi + + # Do each command in the archive commands. + if test -n "$old_archive_from_new_cmds" && test yes = "$build_libtool_libs"; then + cmds=$old_archive_from_new_cmds + else + + # Add any objects from preloaded convenience libraries + if test -n "$dlprefiles"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $dlprefiles + func_append oldobjs " $func_extract_archives_result" + fi + + # POSIX demands no paths to be encoded in archives. We have + # to avoid creating archives with duplicate basenames if we + # might have to extract them afterwards, e.g., when creating a + # static archive out of a convenience library, or when linking + # the entirety of a libtool archive into another (currently + # not supported by libtool). + if (for obj in $oldobjs + do + func_basename "$obj" + $ECHO "$func_basename_result" + done | sort | sort -uc >/dev/null 2>&1); then + : + else + echo "copying selected object files to avoid basename conflicts..." + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + func_mkdir_p "$gentop" + save_oldobjs=$oldobjs + oldobjs= + counter=1 + for obj in $save_oldobjs + do + func_basename "$obj" + objbase=$func_basename_result + case " $oldobjs " in + " ") oldobjs=$obj ;; + *[\ /]"$objbase "*) + while :; do + # Make sure we don't pick an alternate name that also + # overlaps. + newobj=lt$counter-$objbase + func_arith $counter + 1 + counter=$func_arith_result + case " $oldobjs " in + *[\ /]"$newobj "*) ;; + *) if test ! -f "$gentop/$newobj"; then break; fi ;; + esac + done + func_show_eval "ln $obj $gentop/$newobj || cp $obj $gentop/$newobj" + func_append oldobjs " $gentop/$newobj" + ;; + *) func_append oldobjs " $obj" ;; + esac + done + fi + func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 + tool_oldlib=$func_to_tool_file_result + eval cmds=\"$old_archive_cmds\" + + func_len " $cmds" + len=$func_len_result + if test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then + cmds=$old_archive_cmds + elif test -n "$archiver_list_spec"; then + func_verbose "using command file archive linking..." + for obj in $oldobjs + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" + done > $output_objdir/$libname.libcmd + func_to_tool_file "$output_objdir/$libname.libcmd" + oldobjs=" $archiver_list_spec$func_to_tool_file_result" + cmds=$old_archive_cmds + else + # the command line is too long to link in one step, link in parts + func_verbose "using piecewise archive linking..." + save_RANLIB=$RANLIB + RANLIB=: + objlist= + concat_cmds= + save_oldobjs=$oldobjs + oldobjs= + # Is there a better way of finding the last object in the list? + for obj in $save_oldobjs + do + last_oldobj=$obj + done + eval test_cmds=\"$old_archive_cmds\" + func_len " $test_cmds" + len0=$func_len_result + len=$len0 + for obj in $save_oldobjs + do + func_len " $obj" + func_arith $len + $func_len_result + len=$func_arith_result + func_append objlist " $obj" + if test "$len" -lt "$max_cmd_len"; then + : + else + # the above command should be used before it gets too long + oldobjs=$objlist + if test "$obj" = "$last_oldobj"; then + RANLIB=$save_RANLIB + fi + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + eval concat_cmds=\"\$concat_cmds$old_archive_cmds\" + objlist= + len=$len0 + fi + done + RANLIB=$save_RANLIB + oldobjs=$objlist + if test -z "$oldobjs"; then + eval cmds=\"\$concat_cmds\" + else + eval cmds=\"\$concat_cmds~\$old_archive_cmds\" + fi + fi + fi + func_execute_cmds "$cmds" 'exit $?' + done + + test -n "$generated" && \ + func_show_eval "${RM}r$generated" + + # Now create the libtool archive. + case $output in + *.la) + old_library= + test yes = "$build_old_libs" && old_library=$libname.$libext + func_verbose "creating $output" + + # Preserve any variables that may affect compiler behavior + for var in $variables_saved_for_relink; do + if eval test -z \"\${$var+set}\"; then + relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command" + elif eval var_value=\$$var; test -z "$var_value"; then + relink_command="$var=; export $var; $relink_command" + else + func_quote_for_eval "$var_value" + relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command" + fi + done + # Quote the link command for shipping. + relink_command="(cd `pwd`; $SHELL \"$progpath\" $preserve_args --mode=relink $libtool_args @inst_prefix_dir@)" + relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"` + if test yes = "$hardcode_automatic"; then + relink_command= + fi + + # Only create the output if not a dry run. + $opt_dry_run || { + for installed in no yes; do + if test yes = "$installed"; then + if test -z "$install_libdir"; then + break + fi + output=$output_objdir/${outputname}i + # Replace all uninstalled libtool libraries with the installed ones + newdependency_libs= + for deplib in $dependency_libs; do + case $deplib in + *.la) + func_basename "$deplib" + name=$func_basename_result + func_resolve_sysroot "$deplib" + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $func_resolve_sysroot_result` + test -z "$libdir" && \ + func_fatal_error "'$deplib' is not a valid libtool archive" + func_append newdependency_libs " ${lt_sysroot:+=}$libdir/$name" + ;; + -L*) + func_stripname -L '' "$deplib" + func_replace_sysroot "$func_stripname_result" + func_append newdependency_libs " -L$func_replace_sysroot_result" + ;; + -R*) + func_stripname -R '' "$deplib" + func_replace_sysroot "$func_stripname_result" + func_append newdependency_libs " -R$func_replace_sysroot_result" + ;; + *) func_append newdependency_libs " $deplib" ;; + esac + done + dependency_libs=$newdependency_libs + newdlfiles= + + for lib in $dlfiles; do + case $lib in + *.la) + func_basename "$lib" + name=$func_basename_result + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib` + test -z "$libdir" && \ + func_fatal_error "'$lib' is not a valid libtool archive" + func_append newdlfiles " ${lt_sysroot:+=}$libdir/$name" + ;; + *) func_append newdlfiles " $lib" ;; + esac + done + dlfiles=$newdlfiles + newdlprefiles= + for lib in $dlprefiles; do + case $lib in + *.la) + # Only pass preopened files to the pseudo-archive (for + # eventual linking with the app. that links it) if we + # didn't already link the preopened objects directly into + # the library: + func_basename "$lib" + name=$func_basename_result + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib` + test -z "$libdir" && \ + func_fatal_error "'$lib' is not a valid libtool archive" + func_append newdlprefiles " ${lt_sysroot:+=}$libdir/$name" + ;; + esac + done + dlprefiles=$newdlprefiles + else + newdlfiles= + for lib in $dlfiles; do + case $lib in + [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;; + *) abs=`pwd`"/$lib" ;; + esac + func_append newdlfiles " $abs" + done + dlfiles=$newdlfiles + newdlprefiles= + for lib in $dlprefiles; do + case $lib in + [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;; + *) abs=`pwd`"/$lib" ;; + esac + func_append newdlprefiles " $abs" + done + dlprefiles=$newdlprefiles + fi + $RM $output + # place dlname in correct position for cygwin + # In fact, it would be nice if we could use this code for all target + # systems that can't hard-code library paths into their executables + # and that have no shared library path variable independent of PATH, + # but it turns out we can't easily determine that from inspecting + # libtool variables, so we have to hard-code the OSs to which it + # applies here; at the moment, that means platforms that use the PE + # object format with DLL files. See the long comment at the top of + # tests/bindir.at for full details. + tdlname=$dlname + case $host,$output,$installed,$module,$dlname in + *cygwin*,*lai,yes,no,*.dll | *mingw*,*lai,yes,no,*.dll | *cegcc*,*lai,yes,no,*.dll) + # If a -bindir argument was supplied, place the dll there. + if test -n "$bindir"; then + func_relative_path "$install_libdir" "$bindir" + tdlname=$func_relative_path_result/$dlname + else + # Otherwise fall back on heuristic. + tdlname=../bin/$dlname + fi + ;; + esac + $ECHO > $output "\ +# $outputname - a libtool library file +# Generated by $PROGRAM (GNU $PACKAGE) $VERSION +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='$tdlname' + +# Names of this library. +library_names='$library_names' + +# The name of the static archive. +old_library='$old_library' + +# Linker flags that cannot go in dependency_libs. +inherited_linker_flags='$new_inherited_linker_flags' + +# Libraries that this one depends upon. +dependency_libs='$dependency_libs' + +# Names of additional weak libraries provided by this library +weak_library_names='$weak_libs' + +# Version information for $libname. +current=$current +age=$age +revision=$revision + +# Is this an already installed library? +installed=$installed + +# Should we warn about portability when linking against -modules? +shouldnotlink=$module + +# Files to dlopen/dlpreopen +dlopen='$dlfiles' +dlpreopen='$dlprefiles' + +# Directory that this library needs to be installed in: +libdir='$install_libdir'" + if test no,yes = "$installed,$need_relink"; then + $ECHO >> $output "\ +relink_command=\"$relink_command\"" + fi + done + } + + # Do a symbolic link so that the libtool archive can be found in + # LD_LIBRARY_PATH before the program is installed. + func_show_eval '( cd "$output_objdir" && $RM "$outputname" && $LN_S "../$outputname" "$outputname" )' 'exit $?' + ;; + esac + exit $EXIT_SUCCESS +} + +if test link = "$opt_mode" || test relink = "$opt_mode"; then + func_mode_link ${1+"$@"} +fi + + +# func_mode_uninstall arg... +func_mode_uninstall () +{ + $debug_cmd + + RM=$nonopt + files= + rmforce=false + exit_status=0 + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic=$magic + + for arg + do + case $arg in + -f) func_append RM " $arg"; rmforce=: ;; + -*) func_append RM " $arg" ;; + *) func_append files " $arg" ;; + esac + done + + test -z "$RM" && \ + func_fatal_help "you must specify an RM program" + + rmdirs= + + for file in $files; do + func_dirname "$file" "" "." + dir=$func_dirname_result + if test . = "$dir"; then + odir=$objdir + else + odir=$dir/$objdir + fi + func_basename "$file" + name=$func_basename_result + test uninstall = "$opt_mode" && odir=$dir + + # Remember odir for removal later, being careful to avoid duplicates + if test clean = "$opt_mode"; then + case " $rmdirs " in + *" $odir "*) ;; + *) func_append rmdirs " $odir" ;; + esac + fi + + # Don't error if the file doesn't exist and rm -f was used. + if { test -L "$file"; } >/dev/null 2>&1 || + { test -h "$file"; } >/dev/null 2>&1 || + test -f "$file"; then + : + elif test -d "$file"; then + exit_status=1 + continue + elif $rmforce; then + continue + fi + + rmfiles=$file + + case $name in + *.la) + # Possibly a libtool archive, so verify it. + if func_lalib_p "$file"; then + func_source $dir/$name + + # Delete the libtool libraries and symlinks. + for n in $library_names; do + func_append rmfiles " $odir/$n" + done + test -n "$old_library" && func_append rmfiles " $odir/$old_library" + + case $opt_mode in + clean) + case " $library_names " in + *" $dlname "*) ;; + *) test -n "$dlname" && func_append rmfiles " $odir/$dlname" ;; + esac + test -n "$libdir" && func_append rmfiles " $odir/$name $odir/${name}i" + ;; + uninstall) + if test -n "$library_names"; then + # Do each command in the postuninstall commands. + func_execute_cmds "$postuninstall_cmds" '$rmforce || exit_status=1' + fi + + if test -n "$old_library"; then + # Do each command in the old_postuninstall commands. + func_execute_cmds "$old_postuninstall_cmds" '$rmforce || exit_status=1' + fi + # FIXME: should reinstall the best remaining shared library. + ;; + esac + fi + ;; + + *.lo) + # Possibly a libtool object, so verify it. + if func_lalib_p "$file"; then + + # Read the .lo file + func_source $dir/$name + + # Add PIC object to the list of files to remove. + if test -n "$pic_object" && test none != "$pic_object"; then + func_append rmfiles " $dir/$pic_object" + fi + + # Add non-PIC object to the list of files to remove. + if test -n "$non_pic_object" && test none != "$non_pic_object"; then + func_append rmfiles " $dir/$non_pic_object" + fi + fi + ;; + + *) + if test clean = "$opt_mode"; then + noexename=$name + case $file in + *.exe) + func_stripname '' '.exe' "$file" + file=$func_stripname_result + func_stripname '' '.exe' "$name" + noexename=$func_stripname_result + # $file with .exe has already been added to rmfiles, + # add $file without .exe + func_append rmfiles " $file" + ;; + esac + # Do a test to see if this is a libtool program. + if func_ltwrapper_p "$file"; then + if func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + relink_command= + func_source $func_ltwrapper_scriptname_result + func_append rmfiles " $func_ltwrapper_scriptname_result" + else + relink_command= + func_source $dir/$noexename + fi + + # note $name still contains .exe if it was in $file originally + # as does the version of $file that was added into $rmfiles + func_append rmfiles " $odir/$name $odir/${name}S.$objext" + if test yes = "$fast_install" && test -n "$relink_command"; then + func_append rmfiles " $odir/lt-$name" + fi + if test "X$noexename" != "X$name"; then + func_append rmfiles " $odir/lt-$noexename.c" + fi + fi + fi + ;; + esac + func_show_eval "$RM $rmfiles" 'exit_status=1' + done + + # Try to remove the $objdir's in the directories where we deleted files + for dir in $rmdirs; do + if test -d "$dir"; then + func_show_eval "rmdir $dir >/dev/null 2>&1" + fi + done + + exit $exit_status +} + +if test uninstall = "$opt_mode" || test clean = "$opt_mode"; then + func_mode_uninstall ${1+"$@"} +fi + +test -z "$opt_mode" && { + help=$generic_help + func_fatal_help "you must specify a MODE" +} + +test -z "$exec_cmd" && \ + func_fatal_help "invalid operation mode '$opt_mode'" + +if test -n "$exec_cmd"; then + eval exec "$exec_cmd" + exit $EXIT_FAILURE +fi + +exit $exit_status + + +# The TAGs below are defined such that we never get into a situation +# where we disable both kinds of libraries. Given conflicting +# choices, we go for a static library, that is the most portable, +# since we can't tell whether shared libraries were disabled because +# the user asked for that or because the platform doesn't support +# them. This is particularly important on AIX, because we don't +# support having both static and shared libraries enabled at the same +# time on that platform, so we default to a shared-only configuration. +# If a disable-shared tag is given, we'll fallback to a static-only +# configuration. But we'll never go from static-only to shared-only. + +# ### BEGIN LIBTOOL TAG CONFIG: disable-shared +build_libtool_libs=no +build_old_libs=yes +# ### END LIBTOOL TAG CONFIG: disable-shared + +# ### BEGIN LIBTOOL TAG CONFIG: disable-static +build_old_libs=`case $build_libtool_libs in yes) echo no;; *) echo yes;; esac` +# ### END LIBTOOL TAG CONFIG: disable-static + +# Local Variables: +# mode:shell-script +# sh-indentation:2 +# End: diff --git a/SQLITE/missing b/SQLITE/missing new file mode 100755 index 0000000..f62bbae --- /dev/null +++ b/SQLITE/missing @@ -0,0 +1,215 @@ +#! /bin/sh +# Common wrapper for a few potentially missing GNU programs. + +scriptversion=2013-10-28.13; # UTC + +# Copyright (C) 1996-2014 Free Software Foundation, Inc. +# Originally written by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +if test $# -eq 0; then + echo 1>&2 "Try '$0 --help' for more information" + exit 1 +fi + +case $1 in + + --is-lightweight) + # Used by our autoconf macros to check whether the available missing + # script is modern enough. + exit 0 + ;; + + --run) + # Back-compat with the calling convention used by older automake. + shift + ;; + + -h|--h|--he|--hel|--help) + echo "\ +$0 [OPTION]... PROGRAM [ARGUMENT]... + +Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due +to PROGRAM being missing or too old. + +Options: + -h, --help display this help and exit + -v, --version output version information and exit + +Supported PROGRAM values: + aclocal autoconf autoheader autom4te automake makeinfo + bison yacc flex lex help2man + +Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and +'g' are ignored when checking the name. + +Send bug reports to <bug-automake@gnu.org>." + exit $? + ;; + + -v|--v|--ve|--ver|--vers|--versi|--versio|--version) + echo "missing $scriptversion (GNU Automake)" + exit $? + ;; + + -*) + echo 1>&2 "$0: unknown '$1' option" + echo 1>&2 "Try '$0 --help' for more information" + exit 1 + ;; + +esac + +# Run the given program, remember its exit status. +"$@"; st=$? + +# If it succeeded, we are done. +test $st -eq 0 && exit 0 + +# Also exit now if we it failed (or wasn't found), and '--version' was +# passed; such an option is passed most likely to detect whether the +# program is present and works. +case $2 in --version|--help) exit $st;; esac + +# Exit code 63 means version mismatch. This often happens when the user +# tries to use an ancient version of a tool on a file that requires a +# minimum version. +if test $st -eq 63; then + msg="probably too old" +elif test $st -eq 127; then + # Program was missing. + msg="missing on your system" +else + # Program was found and executed, but failed. Give up. + exit $st +fi + +perl_URL=http://www.perl.org/ +flex_URL=http://flex.sourceforge.net/ +gnu_software_URL=http://www.gnu.org/software + +program_details () +{ + case $1 in + aclocal|automake) + echo "The '$1' program is part of the GNU Automake package:" + echo "<$gnu_software_URL/automake>" + echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" + echo "<$gnu_software_URL/autoconf>" + echo "<$gnu_software_URL/m4/>" + echo "<$perl_URL>" + ;; + autoconf|autom4te|autoheader) + echo "The '$1' program is part of the GNU Autoconf package:" + echo "<$gnu_software_URL/autoconf/>" + echo "It also requires GNU m4 and Perl in order to run:" + echo "<$gnu_software_URL/m4/>" + echo "<$perl_URL>" + ;; + esac +} + +give_advice () +{ + # Normalize program name to check for. + normalized_program=`echo "$1" | sed ' + s/^gnu-//; t + s/^gnu//; t + s/^g//; t'` + + printf '%s\n' "'$1' is $msg." + + configure_deps="'configure.ac' or m4 files included by 'configure.ac'" + case $normalized_program in + autoconf*) + echo "You should only need it if you modified 'configure.ac'," + echo "or m4 files included by it." + program_details 'autoconf' + ;; + autoheader*) + echo "You should only need it if you modified 'acconfig.h' or" + echo "$configure_deps." + program_details 'autoheader' + ;; + automake*) + echo "You should only need it if you modified 'Makefile.am' or" + echo "$configure_deps." + program_details 'automake' + ;; + aclocal*) + echo "You should only need it if you modified 'acinclude.m4' or" + echo "$configure_deps." + program_details 'aclocal' + ;; + autom4te*) + echo "You might have modified some maintainer files that require" + echo "the 'autom4te' program to be rebuilt." + program_details 'autom4te' + ;; + bison*|yacc*) + echo "You should only need it if you modified a '.y' file." + echo "You may want to install the GNU Bison package:" + echo "<$gnu_software_URL/bison/>" + ;; + lex*|flex*) + echo "You should only need it if you modified a '.l' file." + echo "You may want to install the Fast Lexical Analyzer package:" + echo "<$flex_URL>" + ;; + help2man*) + echo "You should only need it if you modified a dependency" \ + "of a man page." + echo "You may want to install the GNU Help2man package:" + echo "<$gnu_software_URL/help2man/>" + ;; + makeinfo*) + echo "You should only need it if you modified a '.texi' file, or" + echo "any other file indirectly affecting the aspect of the manual." + echo "You might want to install the Texinfo package:" + echo "<$gnu_software_URL/texinfo/>" + echo "The spurious makeinfo call might also be the consequence of" + echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" + echo "want to install GNU make:" + echo "<$gnu_software_URL/make/>" + ;; + *) + echo "You might have modified some files without having the proper" + echo "tools for further handling them. Check the 'README' file, it" + echo "often tells you about the needed prerequisites for installing" + echo "this package. You may also peek at any GNU archive site, in" + echo "case some other package contains this missing '$1' program." + ;; + esac +} + +give_advice "$1" | sed -e '1s/^/WARNING: /' \ + -e '2,$s/^/ /' >&2 + +# Propagate the correct exit status (expected to be 127 for a program +# not found, 63 for a program that failed due to version mismatch). +exit $st + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/SQLITE/osint.sqlite b/SQLITE/osint.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..b483624632e5630a19f7c36568f8b11c9b85f993 GIT binary patch literal 20480 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 QE#l%o4T$SEo4VZn5B(5Z?*IS* literal 0 HcmV?d00001 diff --git a/SQLITE/shell.c b/SQLITE/shell.c new file mode 100644 index 0000000..6b2cdca --- /dev/null +++ b/SQLITE/shell.c @@ -0,0 +1,28615 @@ +/* DO NOT EDIT! +** This file is automatically generated by the script in the canonical +** SQLite source tree at tool/mkshellc.tcl. That script combines source +** code from various constituent source files of SQLite into this single +** "shell.c" file used to implement the SQLite command-line shell. +** +** Most of the code found below comes from the "src/shell.c.in" file in +** the canonical SQLite source tree. That main file contains "INCLUDE" +** lines that specify other files in the canonical source tree that are +** inserted to getnerate this complete program source file. +** +** The code from multiple files is combined into this single "shell.c" +** source file to help make the command-line program easier to compile. +** +** To modify this program, get a copy of the canonical SQLite source tree, +** edit the src/shell.c.in" and/or some of the other files that are included +** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script. +*/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement the "sqlite" command line +** utility for accessing SQLite databases. +*/ +#if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) +/* This needs to come before any includes for MSVC compiler */ +#define _CRT_SECURE_NO_WARNINGS +#endif +typedef unsigned int u32; +typedef unsigned short int u16; + +/* +** Optionally #include a user-defined header, whereby compilation options +** may be set prior to where they take effect, but after platform setup. +** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include +** file. Note that this macro has a like effect on sqlite3.c compilation. +*/ +# define SHELL_STRINGIFY_(f) #f +# define SHELL_STRINGIFY(f) SHELL_STRINGIFY_(f) +#ifdef SQLITE_CUSTOM_INCLUDE +# include SHELL_STRINGIFY(SQLITE_CUSTOM_INCLUDE) +#endif + +/* +** Determine if we are dealing with WinRT, which provides only a subset of +** the full Win32 API. +*/ +#if !defined(SQLITE_OS_WINRT) +# define SQLITE_OS_WINRT 0 +#endif + +/* +** If SQLITE_SHELL_FIDDLE is defined then the shell is modified +** somewhat for use as a WASM module in a web browser. This flag +** should only be used when building the "fiddle" web application, as +** the browser-mode build has much different user input requirements +** and this build mode rewires the user input subsystem to account for +** that. +*/ + +/* +** Warning pragmas copied from msvc.h in the core. +*/ +#if defined(_MSC_VER) +#pragma warning(disable : 4054) +#pragma warning(disable : 4055) +#pragma warning(disable : 4100) +#pragma warning(disable : 4127) +#pragma warning(disable : 4130) +#pragma warning(disable : 4152) +#pragma warning(disable : 4189) +#pragma warning(disable : 4206) +#pragma warning(disable : 4210) +#pragma warning(disable : 4232) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) +#pragma warning(disable : 4306) +#pragma warning(disable : 4702) +#pragma warning(disable : 4706) +#endif /* defined(_MSC_VER) */ + +/* +** No support for loadable extensions in VxWorks. +*/ +#if (defined(__RTP__) || defined(_WRS_KERNEL)) && !SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION 1 +#endif + +/* +** Enable large-file support for fopen() and friends on unix. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + +#if defined(SQLITE_SHELL_FIDDLE) && !defined(_POSIX_SOURCE) +/* +** emcc requires _POSIX_SOURCE (or one of several similar defines) +** to expose strdup(). +*/ +# define _POSIX_SOURCE +#endif + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include <math.h> +#include "sqlite3.h" +typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; +typedef unsigned char u8; +#if SQLITE_USER_AUTHENTICATION +# include "sqlite3userauth.h" +#endif +#include <ctype.h> +#include <stdarg.h> + +#if !defined(_WIN32) && !defined(WIN32) +# include <signal.h> +# if !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI) +# include <pwd.h> +# endif +#endif +#if (!defined(_WIN32) && !defined(WIN32)) || defined(__MINGW32__) +# include <unistd.h> +# include <dirent.h> +# define GETPID getpid +# if defined(__MINGW32__) +# define DIRENT dirent +# ifndef S_ISLNK +# define S_ISLNK(mode) (0) +# endif +# endif +#else +# define GETPID (int)GetCurrentProcessId +#endif +#include <sys/types.h> +#include <sys/stat.h> + +#if HAVE_READLINE +# include <readline/readline.h> +# include <readline/history.h> +#endif + +#if HAVE_EDITLINE +# include <editline/readline.h> +#endif + +#if HAVE_EDITLINE || HAVE_READLINE + +# define shell_add_history(X) add_history(X) +# define shell_read_history(X) read_history(X) +# define shell_write_history(X) write_history(X) +# define shell_stifle_history(X) stifle_history(X) +# define shell_readline(X) readline(X) + +#elif HAVE_LINENOISE + +# include "linenoise.h" +# define shell_add_history(X) linenoiseHistoryAdd(X) +# define shell_read_history(X) linenoiseHistoryLoad(X) +# define shell_write_history(X) linenoiseHistorySave(X) +# define shell_stifle_history(X) linenoiseHistorySetMaxLen(X) +# define shell_readline(X) linenoise(X) + +#else + +# define shell_read_history(X) +# define shell_write_history(X) +# define shell_stifle_history(X) + +# define SHELL_USE_LOCAL_GETLINE 1 +#endif + +#ifndef deliberate_fall_through +/* Quiet some compilers about some of our intentional code. */ +# if defined(GCC_VERSION) && GCC_VERSION>=7000000 +# define deliberate_fall_through __attribute__((fallthrough)); +# else +# define deliberate_fall_through +# endif +#endif + +#if defined(_WIN32) || defined(WIN32) +# if SQLITE_OS_WINRT +# define SQLITE_OMIT_POPEN 1 +# else +# include <io.h> +# include <fcntl.h> +# define isatty(h) _isatty(h) +# ifndef access +# define access(f,m) _access((f),(m)) +# endif +# ifndef unlink +# define unlink _unlink +# endif +# ifndef strdup +# define strdup _strdup +# endif +# undef popen +# define popen _popen +# undef pclose +# define pclose _pclose +# endif +#else + /* Make sure isatty() has a prototype. */ + extern int isatty(int); + +# if !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI) + /* popen and pclose are not C89 functions and so are + ** sometimes omitted from the <stdio.h> header */ + extern FILE *popen(const char*,const char*); + extern int pclose(FILE*); +# else +# define SQLITE_OMIT_POPEN 1 +# endif +#endif + +#if defined(_WIN32_WCE) +/* Windows CE (arm-wince-mingw32ce-gcc) does not provide isatty() + * thus we always assume that we have a console. That can be + * overridden with the -batch command line option. + */ +#define isatty(x) 1 +#endif + +/* ctype macros that work with signed characters */ +#define IsSpace(X) isspace((unsigned char)X) +#define IsDigit(X) isdigit((unsigned char)X) +#define ToLower(X) (char)tolower((unsigned char)X) + +#if defined(_WIN32) || defined(WIN32) +#if SQLITE_OS_WINRT +#include <intrin.h> +#endif +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +/* string conversion routines only needed on Win32 */ +extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); +extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int); +extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); +extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); +#endif + +/* On Windows, we normally run with output mode of TEXT so that \n characters +** are automatically translated into \r\n. However, this behavior needs +** to be disabled in some cases (ex: when generating CSV output and when +** rendering quoted strings that contain \n characters). The following +** routines take care of that. +*/ +#if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT +static void setBinaryMode(FILE *file, int isOutput){ + if( isOutput ) fflush(file); + _setmode(_fileno(file), _O_BINARY); +} +static void setTextMode(FILE *file, int isOutput){ + if( isOutput ) fflush(file); + _setmode(_fileno(file), _O_TEXT); +} +#else +# define setBinaryMode(X,Y) +# define setTextMode(X,Y) +#endif + +/* True if the timer is enabled */ +static int enableTimer = 0; + +/* A version of strcmp() that works with NULL values */ +static int cli_strcmp(const char *a, const char *b){ + if( a==0 ) a = ""; + if( b==0 ) b = ""; + return strcmp(a,b); +} +static int cli_strncmp(const char *a, const char *b, size_t n){ + if( a==0 ) a = ""; + if( b==0 ) b = ""; + return strncmp(a,b,n); +} + +/* Return the current wall-clock time */ +static sqlite3_int64 timeOfDay(void){ + static sqlite3_vfs *clockVfs = 0; + sqlite3_int64 t; + if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); + if( clockVfs==0 ) return 0; /* Never actually happens */ + if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ + clockVfs->xCurrentTimeInt64(clockVfs, &t); + }else{ + double r; + clockVfs->xCurrentTime(clockVfs, &r); + t = (sqlite3_int64)(r*86400000.0); + } + return t; +} + +#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) +#include <sys/time.h> +#include <sys/resource.h> + +/* VxWorks does not support getrusage() as far as we can determine */ +#if defined(_WRS_KERNEL) || defined(__RTP__) +struct rusage { + struct timeval ru_utime; /* user CPU time used */ + struct timeval ru_stime; /* system CPU time used */ +}; +#define getrusage(A,B) memset(B,0,sizeof(*B)) +#endif + +/* Saved resource information for the beginning of an operation */ +static struct rusage sBegin; /* CPU time at start */ +static sqlite3_int64 iBegin; /* Wall-clock time at start */ + +/* +** Begin timing an operation +*/ +static void beginTimer(void){ + if( enableTimer ){ + getrusage(RUSAGE_SELF, &sBegin); + iBegin = timeOfDay(); + } +} + +/* Return the difference of two time_structs in seconds */ +static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ + return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + + (double)(pEnd->tv_sec - pStart->tv_sec); +} + +/* +** Print the timing results. +*/ +static void endTimer(void){ + if( enableTimer ){ + sqlite3_int64 iEnd = timeOfDay(); + struct rusage sEnd; + getrusage(RUSAGE_SELF, &sEnd); + printf("Run Time: real %.3f user %f sys %f\n", + (iEnd - iBegin)*0.001, + timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); + } +} + +#define BEGIN_TIMER beginTimer() +#define END_TIMER endTimer() +#define HAS_TIMER 1 + +#elif (defined(_WIN32) || defined(WIN32)) + +/* Saved resource information for the beginning of an operation */ +static HANDLE hProcess; +static FILETIME ftKernelBegin; +static FILETIME ftUserBegin; +static sqlite3_int64 ftWallBegin; +typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, + LPFILETIME, LPFILETIME); +static GETPROCTIMES getProcessTimesAddr = NULL; + +/* +** Check to see if we have timer support. Return 1 if necessary +** support found (or found previously). +*/ +static int hasTimer(void){ + if( getProcessTimesAddr ){ + return 1; + } else { +#if !SQLITE_OS_WINRT + /* GetProcessTimes() isn't supported in WIN95 and some other Windows + ** versions. See if the version we are running on has it, and if it + ** does, save off a pointer to it and the current process handle. + */ + hProcess = GetCurrentProcess(); + if( hProcess ){ + HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); + if( NULL != hinstLib ){ + getProcessTimesAddr = + (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); + if( NULL != getProcessTimesAddr ){ + return 1; + } + FreeLibrary(hinstLib); + } + } +#endif + } + return 0; +} + +/* +** Begin timing an operation +*/ +static void beginTimer(void){ + if( enableTimer && getProcessTimesAddr ){ + FILETIME ftCreation, ftExit; + getProcessTimesAddr(hProcess,&ftCreation,&ftExit, + &ftKernelBegin,&ftUserBegin); + ftWallBegin = timeOfDay(); + } +} + +/* Return the difference of two FILETIME structs in seconds */ +static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ + sqlite_int64 i64Start = *((sqlite_int64 *) pStart); + sqlite_int64 i64End = *((sqlite_int64 *) pEnd); + return (double) ((i64End - i64Start) / 10000000.0); +} + +/* +** Print the timing results. +*/ +static void endTimer(void){ + if( enableTimer && getProcessTimesAddr){ + FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; + sqlite3_int64 ftWallEnd = timeOfDay(); + getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); + printf("Run Time: real %.3f user %f sys %f\n", + (ftWallEnd - ftWallBegin)*0.001, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); + } +} + +#define BEGIN_TIMER beginTimer() +#define END_TIMER endTimer() +#define HAS_TIMER hasTimer() + +#else +#define BEGIN_TIMER +#define END_TIMER +#define HAS_TIMER 0 +#endif + +/* +** Used to prevent warnings about unused parameters +*/ +#define UNUSED_PARAMETER(x) (void)(x) + +/* +** Number of elements in an array +*/ +#define ArraySize(X) (int)(sizeof(X)/sizeof(X[0])) + +/* +** If the following flag is set, then command execution stops +** at an error if we are not interactive. +*/ +static int bail_on_error = 0; + +/* +** Treat stdin as an interactive input if the following variable +** is true. Otherwise, assume stdin is connected to a file or pipe. +*/ +static int stdin_is_interactive = 1; + +#if (defined(_WIN32) || defined(WIN32)) && SHELL_USE_LOCAL_GETLINE \ + && !defined(SHELL_OMIT_WIN_UTF8) +# define SHELL_WIN_UTF8_OPT 1 +#else +# define SHELL_WIN_UTF8_OPT 0 +#endif + +#if SHELL_WIN_UTF8_OPT +/* +** Setup console for UTF-8 input/output when following variable true. +*/ +static int console_utf8 = 0; +#endif + +/* +** On Windows systems we have to know if standard output is a console +** in order to translate UTF-8 into MBCS. The following variable is +** true if translation is required. +*/ +static int stdout_is_console = 1; + +/* +** The following is the open SQLite database. We make a pointer +** to this database a static variable so that it can be accessed +** by the SIGINT handler to interrupt database processing. +*/ +static sqlite3 *globalDb = 0; + +/* +** True if an interrupt (Control-C) has been received. +*/ +static volatile int seenInterrupt = 0; + +/* +** This is the name of our program. It is set in main(), used +** in a number of other places, mostly for error messages. +*/ +static char *Argv0; + +/* +** Prompt strings. Initialized in main. Settable with +** .prompt main continue +*/ +#define PROMPT_LEN_MAX 20 +/* First line prompt. default: "sqlite> " */ +static char mainPrompt[PROMPT_LEN_MAX]; +/* Continuation prompt. default: " ...> " */ +static char continuePrompt[PROMPT_LEN_MAX]; + +/* This is variant of the standard-library strncpy() routine with the +** one change that the destination string is always zero-terminated, even +** if there is no zero-terminator in the first n-1 characters of the source +** string. +*/ +static char *shell_strncpy(char *dest, const char *src, size_t n){ + size_t i; + for(i=0; i<n-1 && src[i]!=0; i++) dest[i] = src[i]; + dest[i] = 0; + return dest; +} + +/* +** Optionally disable dynamic continuation prompt. +** Unless disabled, the continuation prompt shows open SQL lexemes if any, +** or open parentheses level if non-zero, or continuation prompt as set. +** This facility interacts with the scanner and process_input() where the +** below 5 macros are used. +*/ +#ifdef SQLITE_OMIT_DYNAPROMPT +# define CONTINUATION_PROMPT continuePrompt +# define CONTINUE_PROMPT_RESET +# define CONTINUE_PROMPT_AWAITS(p,s) +# define CONTINUE_PROMPT_AWAITC(p,c) +# define CONTINUE_PAREN_INCR(p,n) +# define CONTINUE_PROMPT_PSTATE 0 +typedef void *t_NoDynaPrompt; +# define SCAN_TRACKER_REFTYPE t_NoDynaPrompt +#else +# define CONTINUATION_PROMPT dynamicContinuePrompt() +# define CONTINUE_PROMPT_RESET \ + do {setLexemeOpen(&dynPrompt,0,0); trackParenLevel(&dynPrompt,0);} while(0) +# define CONTINUE_PROMPT_AWAITS(p,s) \ + if(p && stdin_is_interactive) setLexemeOpen(p, s, 0) +# define CONTINUE_PROMPT_AWAITC(p,c) \ + if(p && stdin_is_interactive) setLexemeOpen(p, 0, c) +# define CONTINUE_PAREN_INCR(p,n) \ + if(p && stdin_is_interactive) (trackParenLevel(p,n)) +# define CONTINUE_PROMPT_PSTATE (&dynPrompt) +typedef struct DynaPrompt *t_DynaPromptRef; +# define SCAN_TRACKER_REFTYPE t_DynaPromptRef + +static struct DynaPrompt { + char dynamicPrompt[PROMPT_LEN_MAX]; + char acAwait[2]; + int inParenLevel; + char *zScannerAwaits; +} dynPrompt = { {0}, {0}, 0, 0 }; + +/* Record parenthesis nesting level change, or force level to 0. */ +static void trackParenLevel(struct DynaPrompt *p, int ni){ + p->inParenLevel += ni; + if( ni==0 ) p->inParenLevel = 0; + p->zScannerAwaits = 0; +} + +/* Record that a lexeme is opened, or closed with args==0. */ +static void setLexemeOpen(struct DynaPrompt *p, char *s, char c){ + if( s!=0 || c==0 ){ + p->zScannerAwaits = s; + p->acAwait[0] = 0; + }else{ + p->acAwait[0] = c; + p->zScannerAwaits = p->acAwait; + } +} + +/* Upon demand, derive the continuation prompt to display. */ +static char *dynamicContinuePrompt(void){ + if( continuePrompt[0]==0 + || (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){ + return continuePrompt; + }else{ + if( dynPrompt.zScannerAwaits ){ + size_t ncp = strlen(continuePrompt); + size_t ndp = strlen(dynPrompt.zScannerAwaits); + if( ndp > ncp-3 ) return continuePrompt; + strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits); + while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' '; + shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, + PROMPT_LEN_MAX-4); + }else{ + if( dynPrompt.inParenLevel>9 ){ + shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4); + }else if( dynPrompt.inParenLevel<0 ){ + shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4); + }else{ + shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4); + dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel); + } + shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, PROMPT_LEN_MAX-4); + } + } + return dynPrompt.dynamicPrompt; +} +#endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ + +#if SHELL_WIN_UTF8_OPT +/* Following struct is used for -utf8 operation. */ +static struct ConsoleState { + int stdinEof; /* EOF has been seen on console input */ + int infsMode; /* Input file stream mode upon shell start */ + UINT inCodePage; /* Input code page upon shell start */ + UINT outCodePage; /* Output code page upon shell start */ + HANDLE hConsoleIn; /* Console input handle */ + DWORD consoleMode; /* Console mode upon shell start */ +} conState = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 }; + +#ifndef _O_U16TEXT /* For build environments lacking this constant: */ +# define _O_U16TEXT 0x20000 +#endif + +/* +** Prepare console, (if known to be a WIN32 console), for UTF-8 +** input (from either typing or suitable paste operations) and for +** UTF-8 rendering. This may "fail" with a message to stderr, where +** the preparation is not done and common "code page" issues occur. +*/ +static void console_prepare(void){ + HANDLE hCI = GetStdHandle(STD_INPUT_HANDLE); + DWORD consoleMode = 0; + if( isatty(0) && GetFileType(hCI)==FILE_TYPE_CHAR + && GetConsoleMode( hCI, &consoleMode) ){ + if( !IsValidCodePage(CP_UTF8) ){ + fprintf(stderr, "Cannot use UTF-8 code page.\n"); + console_utf8 = 0; + return; + } + conState.hConsoleIn = hCI; + conState.consoleMode = consoleMode; + conState.inCodePage = GetConsoleCP(); + conState.outCodePage = GetConsoleOutputCP(); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + consoleMode |= ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; + SetConsoleMode(conState.hConsoleIn, consoleMode); + conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT); + console_utf8 = 1; + }else{ + console_utf8 = 0; + } +} + +/* +** Undo the effects of console_prepare(), if any. +*/ +static void SQLITE_CDECL console_restore(void){ + if( console_utf8 && conState.inCodePage!=0 + && conState.hConsoleIn!=INVALID_HANDLE_VALUE ){ + _setmode(_fileno(stdin), conState.infsMode); + SetConsoleCP(conState.inCodePage); + SetConsoleOutputCP(conState.outCodePage); + SetConsoleMode(conState.hConsoleIn, conState.consoleMode); + /* Avoid multiple calls. */ + conState.hConsoleIn = INVALID_HANDLE_VALUE; + conState.consoleMode = 0; + console_utf8 = 0; + } +} + +/* +** Collect input like fgets(...) with special provisions for input +** from the Windows console to get around its strange coding issues. +** Defers to plain fgets() when input is not interactive or when the +** startup option, -utf8, has not been provided or taken effect. +*/ +static char* utf8_fgets(char *buf, int ncmax, FILE *fin){ + if( fin==0 ) fin = stdin; + if( fin==stdin && stdin_is_interactive && console_utf8 ){ +# define SQLITE_IALIM 150 + wchar_t wbuf[SQLITE_IALIM]; + int lend = 0; + int noc = 0; + if( ncmax==0 || conState.stdinEof ) return 0; + buf[0] = 0; + while( noc<ncmax-7-1 && !lend ){ + /* There is room for at least 2 more characters and a 0-terminator. */ + int na = (ncmax > SQLITE_IALIM*4+1 + noc) + ? SQLITE_IALIM : (ncmax-1 - noc)/4; +# undef SQLITE_IALIM + DWORD nbr = 0; + BOOL bRC = ReadConsoleW(conState.hConsoleIn, wbuf, na, &nbr, 0); + if( !bRC || (noc==0 && nbr==0) ) return 0; + if( nbr > 0 ){ + int nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, + wbuf,nbr,0,0,0,0); + if( nmb !=0 && noc+nmb <= ncmax ){ + int iseg = noc; + nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, + wbuf,nbr,buf+noc,nmb,0,0); + noc += nmb; + /* Fixup line-ends as coded by Windows for CR (or "Enter".)*/ + if( noc > 0 ){ + if( buf[noc-1]=='\n' ){ + lend = 1; + if( noc > 1 && buf[noc-2]=='\r' ){ + buf[noc-2] = '\n'; + --noc; + } + } + } + /* Check for ^Z (anywhere in line) too. */ + while( iseg < noc ){ + if( buf[iseg]==0x1a ){ + conState.stdinEof = 1; + noc = iseg; /* Chop ^Z and anything following. */ + break; + } + ++iseg; + } + }else break; /* Drop apparent garbage in. (Could assert.) */ + }else break; + } + /* If got nothing, (after ^Z chop), must be at end-of-file. */ + if( noc == 0 ) return 0; + buf[noc] = 0; + return buf; + }else{ + return fgets(buf, ncmax, fin); + } +} + +# define fgets(b,n,f) utf8_fgets(b,n,f) +#endif /* SHELL_WIN_UTF8_OPT */ + +/* +** Render output like fprintf(). Except, if the output is going to the +** console and if this is running on a Windows machine, and if the -utf8 +** option is unavailable or (available and inactive), translate the +** output from UTF-8 into MBCS for output through 8-bit stdout stream. +** (With -utf8 active, no translation is needed and must not be done.) +*/ +#if defined(_WIN32) || defined(WIN32) +void utf8_printf(FILE *out, const char *zFormat, ...){ + va_list ap; + va_start(ap, zFormat); + if( stdout_is_console && (out==stdout || out==stderr) +# if SHELL_WIN_UTF8_OPT + && !console_utf8 +# endif + ){ + char *z1 = sqlite3_vmprintf(zFormat, ap); + char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); + sqlite3_free(z1); + fputs(z2, out); + sqlite3_free(z2); + }else{ + vfprintf(out, zFormat, ap); + } + va_end(ap); +} +#elif !defined(utf8_printf) +# define utf8_printf fprintf +#endif + +/* +** Render output like fprintf(). This should not be used on anything that +** includes string formatting (e.g. "%s"). +*/ +#if !defined(raw_printf) +# define raw_printf fprintf +#endif + +/* Indicate out-of-memory and exit. */ +static void shell_out_of_memory(void){ + raw_printf(stderr,"Error: out of memory\n"); + exit(1); +} + +/* Check a pointer to see if it is NULL. If it is NULL, exit with an +** out-of-memory error. +*/ +static void shell_check_oom(const void *p){ + if( p==0 ) shell_out_of_memory(); +} + +/* +** Write I/O traces to the following stream. +*/ +#ifdef SQLITE_ENABLE_IOTRACE +static FILE *iotrace = 0; +#endif + +/* +** This routine works like printf in that its first argument is a +** format string and subsequent arguments are values to be substituted +** in place of % fields. The result of formatting this string +** is written to iotrace. +*/ +#ifdef SQLITE_ENABLE_IOTRACE +static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ + va_list ap; + char *z; + if( iotrace==0 ) return; + va_start(ap, zFormat); + z = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + utf8_printf(iotrace, "%s", z); + sqlite3_free(z); +} +#endif + +/* +** Output string zUtf to stream pOut as w characters. If w is negative, +** then right-justify the text. W is the width in UTF-8 characters, not +** in bytes. This is different from the %*.*s specification in printf +** since with %*.*s the width is measured in bytes, not characters. +*/ +static void utf8_width_print(FILE *pOut, int w, const char *zUtf){ + int i; + int n; + int aw = w<0 ? -w : w; + if( zUtf==0 ) zUtf = ""; + for(i=n=0; zUtf[i]; i++){ + if( (zUtf[i]&0xc0)!=0x80 ){ + n++; + if( n==aw ){ + do{ i++; }while( (zUtf[i]&0xc0)==0x80 ); + break; + } + } + } + if( n>=aw ){ + utf8_printf(pOut, "%.*s", i, zUtf); + }else if( w<0 ){ + utf8_printf(pOut, "%*s%s", aw-n, "", zUtf); + }else{ + utf8_printf(pOut, "%s%*s", zUtf, aw-n, ""); + } +} + + +/* +** Determines if a string is a number of not. +*/ +static int isNumber(const char *z, int *realnum){ + if( *z=='-' || *z=='+' ) z++; + if( !IsDigit(*z) ){ + return 0; + } + z++; + if( realnum ) *realnum = 0; + while( IsDigit(*z) ){ z++; } + if( *z=='.' ){ + z++; + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } + if( realnum ) *realnum = 1; + } + if( *z=='e' || *z=='E' ){ + z++; + if( *z=='+' || *z=='-' ) z++; + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } + if( realnum ) *realnum = 1; + } + return *z==0; +} + +/* +** Compute a string length that is limited to what can be stored in +** lower 30 bits of a 32-bit signed integer. +*/ +static int strlen30(const char *z){ + const char *z2 = z; + while( *z2 ){ z2++; } + return 0x3fffffff & (int)(z2 - z); +} + +/* +** Return the length of a string in characters. Multibyte UTF8 characters +** count as a single character. +*/ +static int strlenChar(const char *z){ + int n = 0; + while( *z ){ + if( (0xc0&*(z++))!=0x80 ) n++; + } + return n; +} + +/* +** Return open FILE * if zFile exists, can be opened for read +** and is an ordinary file or a character stream source. +** Otherwise return 0. +*/ +static FILE * openChrSource(const char *zFile){ +#ifdef _WIN32 + struct _stat x = {0}; +# define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) + /* On Windows, open first, then check the stream nature. This order + ** is necessary because _stat() and sibs, when checking a named pipe, + ** effectively break the pipe as its supplier sees it. */ + FILE *rv = fopen(zFile, "rb"); + if( rv==0 ) return 0; + if( _fstat(_fileno(rv), &x) != 0 + || !STAT_CHR_SRC(x.st_mode)){ + fclose(rv); + rv = 0; + } + return rv; +#else + struct stat x = {0}; + int rc = stat(zFile, &x); +# define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode)) + if( rc!=0 ) return 0; + if( STAT_CHR_SRC(x.st_mode) ){ + return fopen(zFile, "rb"); + }else{ + return 0; + } +#endif +#undef STAT_CHR_SRC +} + +/* +** This routine reads a line of text from FILE in, stores +** the text in memory obtained from malloc() and returns a pointer +** to the text. NULL is returned at end of file, or if malloc() +** fails. +** +** If zLine is not NULL then it is a malloced buffer returned from +** a previous call to this routine that may be reused. +*/ +static char *local_getline(char *zLine, FILE *in){ + int nLine = zLine==0 ? 0 : 100; + int n = 0; + + while( 1 ){ + if( n+100>nLine ){ + nLine = nLine*2 + 100; + zLine = realloc(zLine, nLine); + shell_check_oom(zLine); + } + if( fgets(&zLine[n], nLine - n, in)==0 ){ + if( n==0 ){ + free(zLine); + return 0; + } + zLine[n] = 0; + break; + } + while( zLine[n] ) n++; + if( n>0 && zLine[n-1]=='\n' ){ + n--; + if( n>0 && zLine[n-1]=='\r' ) n--; + zLine[n] = 0; + break; + } + } +#if defined(_WIN32) || defined(WIN32) + /* For interactive input on Windows systems, without -utf8, + ** translate the multi-byte characterset characters into UTF-8. + ** This is the translation that predates the -utf8 option. */ + if( stdin_is_interactive && in==stdin +# if SHELL_WIN_UTF8_OPT + && !console_utf8 +# endif /* SHELL_WIN_UTF8_OPT */ + ){ + char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); + if( zTrans ){ + i64 nTrans = strlen(zTrans)+1; + if( nTrans>nLine ){ + zLine = realloc(zLine, nTrans); + shell_check_oom(zLine); + } + memcpy(zLine, zTrans, nTrans); + sqlite3_free(zTrans); + } + } +#endif /* defined(_WIN32) || defined(WIN32) */ + return zLine; +} + +/* +** Retrieve a single line of input text. +** +** If in==0 then read from standard input and prompt before each line. +** If isContinuation is true, then a continuation prompt is appropriate. +** If isContinuation is zero, then the main prompt should be used. +** +** If zPrior is not NULL then it is a buffer from a prior call to this +** routine that can be reused. +** +** The result is stored in space obtained from malloc() and must either +** be freed by the caller or else passed back into this routine via the +** zPrior argument for reuse. +*/ +#ifndef SQLITE_SHELL_FIDDLE +static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ + char *zPrompt; + char *zResult; + if( in!=0 ){ + zResult = local_getline(zPrior, in); + }else{ + zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt; +#if SHELL_USE_LOCAL_GETLINE + printf("%s", zPrompt); + fflush(stdout); + do{ + zResult = local_getline(zPrior, stdin); + zPrior = 0; + /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */ + if( zResult==0 ) sqlite3_sleep(50); + }while( zResult==0 && seenInterrupt>0 ); +#else + free(zPrior); + zResult = shell_readline(zPrompt); + while( zResult==0 ){ + /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */ + sqlite3_sleep(50); + if( seenInterrupt==0 ) break; + zResult = shell_readline(""); + } + if( zResult && *zResult ) shell_add_history(zResult); +#endif + } + return zResult; +} +#endif /* !SQLITE_SHELL_FIDDLE */ + +/* +** Return the value of a hexadecimal digit. Return -1 if the input +** is not a hex digit. +*/ +static int hexDigitValue(char c){ + if( c>='0' && c<='9' ) return c - '0'; + if( c>='a' && c<='f' ) return c - 'a' + 10; + if( c>='A' && c<='F' ) return c - 'A' + 10; + return -1; +} + +/* +** Interpret zArg as an integer value, possibly with suffixes. +*/ +static sqlite3_int64 integerValue(const char *zArg){ + sqlite3_int64 v = 0; + static const struct { char *zSuffix; int iMult; } aMult[] = { + { "KiB", 1024 }, + { "MiB", 1024*1024 }, + { "GiB", 1024*1024*1024 }, + { "KB", 1000 }, + { "MB", 1000000 }, + { "GB", 1000000000 }, + { "K", 1000 }, + { "M", 1000000 }, + { "G", 1000000000 }, + }; + int i; + int isNeg = 0; + if( zArg[0]=='-' ){ + isNeg = 1; + zArg++; + }else if( zArg[0]=='+' ){ + zArg++; + } + if( zArg[0]=='0' && zArg[1]=='x' ){ + int x; + zArg += 2; + while( (x = hexDigitValue(zArg[0]))>=0 ){ + v = (v<<4) + x; + zArg++; + } + }else{ + while( IsDigit(zArg[0]) ){ + v = v*10 + zArg[0] - '0'; + zArg++; + } + } + for(i=0; i<ArraySize(aMult); i++){ + if( sqlite3_stricmp(aMult[i].zSuffix, zArg)==0 ){ + v *= aMult[i].iMult; + break; + } + } + return isNeg? -v : v; +} + +/* +** A variable length string to which one can append text. +*/ +typedef struct ShellText ShellText; +struct ShellText { + char *z; + int n; + int nAlloc; +}; + +/* +** Initialize and destroy a ShellText object +*/ +static void initText(ShellText *p){ + memset(p, 0, sizeof(*p)); +} +static void freeText(ShellText *p){ + free(p->z); + initText(p); +} + +/* zIn is either a pointer to a NULL-terminated string in memory obtained +** from malloc(), or a NULL pointer. The string pointed to by zAppend is +** added to zIn, and the result returned in memory obtained from malloc(). +** zIn, if it was not NULL, is freed. +** +** If the third argument, quote, is not '\0', then it is used as a +** quote character for zAppend. +*/ +static void appendText(ShellText *p, const char *zAppend, char quote){ + i64 len; + i64 i; + i64 nAppend = strlen30(zAppend); + + len = nAppend+p->n+1; + if( quote ){ + len += 2; + for(i=0; i<nAppend; i++){ + if( zAppend[i]==quote ) len++; + } + } + + if( p->z==0 || p->n+len>=p->nAlloc ){ + p->nAlloc = p->nAlloc*2 + len + 20; + p->z = realloc(p->z, p->nAlloc); + shell_check_oom(p->z); + } + + if( quote ){ + char *zCsr = p->z+p->n; + *zCsr++ = quote; + for(i=0; i<nAppend; i++){ + *zCsr++ = zAppend[i]; + if( zAppend[i]==quote ) *zCsr++ = quote; + } + *zCsr++ = quote; + p->n = (int)(zCsr - p->z); + *zCsr = '\0'; + }else{ + memcpy(p->z+p->n, zAppend, nAppend); + p->n += nAppend; + p->z[p->n] = '\0'; + } +} + +/* +** Attempt to determine if identifier zName needs to be quoted, either +** because it contains non-alphanumeric characters, or because it is an +** SQLite keyword. Be conservative in this estimate: When in doubt assume +** that quoting is required. +** +** Return '"' if quoting is required. Return 0 if no quoting is required. +*/ +static char quoteChar(const char *zName){ + int i; + if( zName==0 ) return '"'; + if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"'; + for(i=0; zName[i]; i++){ + if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"'; + } + return sqlite3_keyword_check(zName, i) ? '"' : 0; +} + +/* +** Construct a fake object name and column list to describe the structure +** of the view, virtual table, or table valued function zSchema.zName. +*/ +static char *shellFakeSchema( + sqlite3 *db, /* The database connection containing the vtab */ + const char *zSchema, /* Schema of the database holding the vtab */ + const char *zName /* The name of the virtual table */ +){ + sqlite3_stmt *pStmt = 0; + char *zSql; + ShellText s; + char cQuote; + char *zDiv = "("; + int nRow = 0; + + zSql = sqlite3_mprintf("PRAGMA \"%w\".table_info=%Q;", + zSchema ? zSchema : "main", zName); + shell_check_oom(zSql); + sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + initText(&s); + if( zSchema ){ + cQuote = quoteChar(zSchema); + if( cQuote && sqlite3_stricmp(zSchema,"temp")==0 ) cQuote = 0; + appendText(&s, zSchema, cQuote); + appendText(&s, ".", 0); + } + cQuote = quoteChar(zName); + appendText(&s, zName, cQuote); + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zCol = (const char*)sqlite3_column_text(pStmt, 1); + nRow++; + appendText(&s, zDiv, 0); + zDiv = ","; + if( zCol==0 ) zCol = ""; + cQuote = quoteChar(zCol); + appendText(&s, zCol, cQuote); + } + appendText(&s, ")", 0); + sqlite3_finalize(pStmt); + if( nRow==0 ){ + freeText(&s); + s.z = 0; + } + return s.z; +} + +/* +** SQL function: strtod(X) +** +** Use the C-library strtod() function to convert string X into a double. +** Used for comparing the accuracy of SQLite's internal text-to-float conversion +** routines against the C-library. +*/ +static void shellStrtod( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + char *z = (char*)sqlite3_value_text(apVal[0]); + UNUSED_PARAMETER(nVal); + if( z==0 ) return; + sqlite3_result_double(pCtx, strtod(z,0)); +} + +/* +** SQL function: dtostr(X) +** +** Use the C-library printf() function to convert real value X into a string. +** Used for comparing the accuracy of SQLite's internal float-to-text conversion +** routines against the C-library. +*/ +static void shellDtostr( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + double r = sqlite3_value_double(apVal[0]); + int n = nVal>=2 ? sqlite3_value_int(apVal[1]) : 26; + char z[400]; + if( n<1 ) n = 1; + if( n>350 ) n = 350; + sprintf(z, "%#+.*e", n, r); + sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); +} + + +/* +** SQL function: shell_module_schema(X) +** +** Return a fake schema for the table-valued function or eponymous virtual +** table X. +*/ +static void shellModuleSchema( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + const char *zName; + char *zFake; + UNUSED_PARAMETER(nVal); + zName = (const char*)sqlite3_value_text(apVal[0]); + zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; + if( zFake ){ + sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), + -1, sqlite3_free); + free(zFake); + } +} + +/* +** SQL function: shell_add_schema(S,X) +** +** Add the schema name X to the CREATE statement in S and return the result. +** Examples: +** +** CREATE TABLE t1(x) -> CREATE TABLE xyz.t1(x); +** +** Also works on +** +** CREATE INDEX +** CREATE UNIQUE INDEX +** CREATE VIEW +** CREATE TRIGGER +** CREATE VIRTUAL TABLE +** +** This UDF is used by the .schema command to insert the schema name of +** attached databases into the middle of the sqlite_schema.sql field. +*/ +static void shellAddSchemaName( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + static const char *aPrefix[] = { + "TABLE", + "INDEX", + "UNIQUE INDEX", + "VIEW", + "TRIGGER", + "VIRTUAL TABLE" + }; + int i = 0; + const char *zIn = (const char*)sqlite3_value_text(apVal[0]); + const char *zSchema = (const char*)sqlite3_value_text(apVal[1]); + const char *zName = (const char*)sqlite3_value_text(apVal[2]); + sqlite3 *db = sqlite3_context_db_handle(pCtx); + UNUSED_PARAMETER(nVal); + if( zIn!=0 && cli_strncmp(zIn, "CREATE ", 7)==0 ){ + for(i=0; i<ArraySize(aPrefix); i++){ + int n = strlen30(aPrefix[i]); + if( cli_strncmp(zIn+7, aPrefix[i], n)==0 && zIn[n+7]==' ' ){ + char *z = 0; + char *zFake = 0; + if( zSchema ){ + char cQuote = quoteChar(zSchema); + if( cQuote && sqlite3_stricmp(zSchema,"temp")!=0 ){ + z = sqlite3_mprintf("%.*s \"%w\".%s", n+7, zIn, zSchema, zIn+n+8); + }else{ + z = sqlite3_mprintf("%.*s %s.%s", n+7, zIn, zSchema, zIn+n+8); + } + } + if( zName + && aPrefix[i][0]=='V' + && (zFake = shellFakeSchema(db, zSchema, zName))!=0 + ){ + if( z==0 ){ + z = sqlite3_mprintf("%s\n/* %s */", zIn, zFake); + }else{ + z = sqlite3_mprintf("%z\n/* %s */", z, zFake); + } + free(zFake); + } + if( z ){ + sqlite3_result_text(pCtx, z, -1, sqlite3_free); + return; + } + } + } + } + sqlite3_result_value(pCtx, apVal[0]); +} + +/* +** The source code for several run-time loadable extensions is inserted +** below by the ../tool/mkshellc.tcl script. Before processing that included +** code, we need to override some macros to make the included program code +** work here in the middle of this regular program. +*/ +#define SQLITE_EXTENSION_INIT1 +#define SQLITE_EXTENSION_INIT2(X) (void)(X) + +#if defined(_WIN32) && defined(_MSC_VER) +/************************* Begin test_windirent.h ******************/ +/* +** 2015 November 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains declarations for most of the opendir() family of +** POSIX functions on Win32 using the MSVCRT. +*/ + +#if defined(_WIN32) && defined(_MSC_VER) && !defined(SQLITE_WINDIRENT_H) +#define SQLITE_WINDIRENT_H + +/* +** We need several data types from the Windows SDK header. +*/ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include "windows.h" + +/* +** We need several support functions from the SQLite core. +*/ + +/* #include "sqlite3.h" */ + +/* +** We need several things from the ANSI and MSVCRT headers. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <io.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> + +/* +** We may need several defines that should have been in "sys/stat.h". +*/ + +#ifndef S_ISREG +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif + +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +#ifndef S_ISLNK +#define S_ISLNK(mode) (0) +#endif + +/* +** We may need to provide the "mode_t" type. +*/ + +#ifndef MODE_T_DEFINED + #define MODE_T_DEFINED + typedef unsigned short mode_t; +#endif + +/* +** We may need to provide the "ino_t" type. +*/ + +#ifndef INO_T_DEFINED + #define INO_T_DEFINED + typedef unsigned short ino_t; +#endif + +/* +** We need to define "NAME_MAX" if it was not present in "limits.h". +*/ + +#ifndef NAME_MAX +# ifdef FILENAME_MAX +# define NAME_MAX (FILENAME_MAX) +# else +# define NAME_MAX (260) +# endif +#endif + +/* +** We need to define "NULL_INTPTR_T" and "BAD_INTPTR_T". +*/ + +#ifndef NULL_INTPTR_T +# define NULL_INTPTR_T ((intptr_t)(0)) +#endif + +#ifndef BAD_INTPTR_T +# define BAD_INTPTR_T ((intptr_t)(-1)) +#endif + +/* +** We need to provide the necessary structures and related types. +*/ + +#ifndef DIRENT_DEFINED +#define DIRENT_DEFINED +typedef struct DIRENT DIRENT; +typedef DIRENT *LPDIRENT; +struct DIRENT { + ino_t d_ino; /* Sequence number, do not use. */ + unsigned d_attributes; /* Win32 file attributes. */ + char d_name[NAME_MAX + 1]; /* Name within the directory. */ +}; +#endif + +#ifndef DIR_DEFINED +#define DIR_DEFINED +typedef struct DIR DIR; +typedef DIR *LPDIR; +struct DIR { + intptr_t d_handle; /* Value returned by "_findfirst". */ + DIRENT d_first; /* DIRENT constructed based on "_findfirst". */ + DIRENT d_next; /* DIRENT constructed based on "_findnext". */ +}; +#endif + +/* +** Provide a macro, for use by the implementation, to determine if a +** particular directory entry should be skipped over when searching for +** the next directory entry that should be returned by the readdir() or +** readdir_r() functions. +*/ + +#ifndef is_filtered +# define is_filtered(a) ((((a).attrib)&_A_HIDDEN) || (((a).attrib)&_A_SYSTEM)) +#endif + +/* +** Provide the function prototype for the POSIX compatible getenv() +** function. This function is not thread-safe. +*/ + +extern const char *windirent_getenv(const char *name); + +/* +** Finally, we can provide the function prototypes for the opendir(), +** readdir(), readdir_r(), and closedir() POSIX functions. +*/ + +extern LPDIR opendir(const char *dirname); +extern LPDIRENT readdir(LPDIR dirp); +extern INT readdir_r(LPDIR dirp, LPDIRENT entry, LPDIRENT *result); +extern INT closedir(LPDIR dirp); + +#endif /* defined(WIN32) && defined(_MSC_VER) */ + +/************************* End test_windirent.h ********************/ +/************************* Begin test_windirent.c ******************/ +/* +** 2015 November 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement most of the opendir() family of +** POSIX functions on Win32 using the MSVCRT. +*/ + +#if defined(_WIN32) && defined(_MSC_VER) +/* #include "test_windirent.h" */ + +/* +** Implementation of the POSIX getenv() function using the Win32 API. +** This function is not thread-safe. +*/ +const char *windirent_getenv( + const char *name +){ + static char value[32768]; /* Maximum length, per MSDN */ + DWORD dwSize = sizeof(value) / sizeof(char); /* Size in chars */ + DWORD dwRet; /* Value returned by GetEnvironmentVariableA() */ + + memset(value, 0, sizeof(value)); + dwRet = GetEnvironmentVariableA(name, value, dwSize); + if( dwRet==0 || dwRet>dwSize ){ + /* + ** The function call to GetEnvironmentVariableA() failed -OR- + ** the buffer is not large enough. Either way, return NULL. + */ + return 0; + }else{ + /* + ** The function call to GetEnvironmentVariableA() succeeded + ** -AND- the buffer contains the entire value. + */ + return value; + } +} + +/* +** Implementation of the POSIX opendir() function using the MSVCRT. +*/ +LPDIR opendir( + const char *dirname +){ + struct _finddata_t data; + LPDIR dirp = (LPDIR)sqlite3_malloc(sizeof(DIR)); + SIZE_T namesize = sizeof(data.name) / sizeof(data.name[0]); + + if( dirp==NULL ) return NULL; + memset(dirp, 0, sizeof(DIR)); + + /* TODO: Remove this if Unix-style root paths are not used. */ + if( sqlite3_stricmp(dirname, "/")==0 ){ + dirname = windirent_getenv("SystemDrive"); + } + + memset(&data, 0, sizeof(struct _finddata_t)); + _snprintf(data.name, namesize, "%s\\*", dirname); + dirp->d_handle = _findfirst(data.name, &data); + + if( dirp->d_handle==BAD_INTPTR_T ){ + closedir(dirp); + return NULL; + } + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ){ +next: + + memset(&data, 0, sizeof(struct _finddata_t)); + if( _findnext(dirp->d_handle, &data)==-1 ){ + closedir(dirp); + return NULL; + } + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ) goto next; + } + + dirp->d_first.d_attributes = data.attrib; + strncpy(dirp->d_first.d_name, data.name, NAME_MAX); + dirp->d_first.d_name[NAME_MAX] = '\0'; + + return dirp; +} + +/* +** Implementation of the POSIX readdir() function using the MSVCRT. +*/ +LPDIRENT readdir( + LPDIR dirp +){ + struct _finddata_t data; + + if( dirp==NULL ) return NULL; + + if( dirp->d_first.d_ino==0 ){ + dirp->d_first.d_ino++; + dirp->d_next.d_ino++; + + return &dirp->d_first; + } + +next: + + memset(&data, 0, sizeof(struct _finddata_t)); + if( _findnext(dirp->d_handle, &data)==-1 ) return NULL; + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ) goto next; + + dirp->d_next.d_ino++; + dirp->d_next.d_attributes = data.attrib; + strncpy(dirp->d_next.d_name, data.name, NAME_MAX); + dirp->d_next.d_name[NAME_MAX] = '\0'; + + return &dirp->d_next; +} + +/* +** Implementation of the POSIX readdir_r() function using the MSVCRT. +*/ +INT readdir_r( + LPDIR dirp, + LPDIRENT entry, + LPDIRENT *result +){ + struct _finddata_t data; + + if( dirp==NULL ) return EBADF; + + if( dirp->d_first.d_ino==0 ){ + dirp->d_first.d_ino++; + dirp->d_next.d_ino++; + + entry->d_ino = dirp->d_first.d_ino; + entry->d_attributes = dirp->d_first.d_attributes; + strncpy(entry->d_name, dirp->d_first.d_name, NAME_MAX); + entry->d_name[NAME_MAX] = '\0'; + + *result = entry; + return 0; + } + +next: + + memset(&data, 0, sizeof(struct _finddata_t)); + if( _findnext(dirp->d_handle, &data)==-1 ){ + *result = NULL; + return ENOENT; + } + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ) goto next; + + entry->d_ino = (ino_t)-1; /* not available */ + entry->d_attributes = data.attrib; + strncpy(entry->d_name, data.name, NAME_MAX); + entry->d_name[NAME_MAX] = '\0'; + + *result = entry; + return 0; +} + +/* +** Implementation of the POSIX closedir() function using the MSVCRT. +*/ +INT closedir( + LPDIR dirp +){ + INT result = 0; + + if( dirp==NULL ) return EINVAL; + + if( dirp->d_handle!=NULL_INTPTR_T && dirp->d_handle!=BAD_INTPTR_T ){ + result = _findclose(dirp->d_handle); + } + + sqlite3_free(dirp); + return result; +} + +#endif /* defined(WIN32) && defined(_MSC_VER) */ + +/************************* End test_windirent.c ********************/ +#define dirent DIRENT +#endif +/************************* Begin ../ext/misc/memtrace.c ******************/ +/* +** 2019-01-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements an extension that uses the SQLITE_CONFIG_MALLOC +** mechanism to add a tracing layer on top of SQLite. If this extension +** is registered prior to sqlite3_initialize(), it will cause all memory +** allocation activities to be logged on standard output, or to some other +** FILE specified by the initializer. +** +** This file needs to be compiled into the application that uses it. +** +** This extension is used to implement the --memtrace option of the +** command-line shell. +*/ +#include <assert.h> +#include <string.h> +#include <stdio.h> + +/* The original memory allocation routines */ +static sqlite3_mem_methods memtraceBase; +static FILE *memtraceOut; + +/* Methods that trace memory allocations */ +static void *memtraceMalloc(int n){ + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: allocate %d bytes\n", + memtraceBase.xRoundup(n)); + } + return memtraceBase.xMalloc(n); +} +static void memtraceFree(void *p){ + if( p==0 ) return; + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: free %d bytes\n", memtraceBase.xSize(p)); + } + memtraceBase.xFree(p); +} +static void *memtraceRealloc(void *p, int n){ + if( p==0 ) return memtraceMalloc(n); + if( n==0 ){ + memtraceFree(p); + return 0; + } + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: resize %d -> %d bytes\n", + memtraceBase.xSize(p), memtraceBase.xRoundup(n)); + } + return memtraceBase.xRealloc(p, n); +} +static int memtraceSize(void *p){ + return memtraceBase.xSize(p); +} +static int memtraceRoundup(int n){ + return memtraceBase.xRoundup(n); +} +static int memtraceInit(void *p){ + return memtraceBase.xInit(p); +} +static void memtraceShutdown(void *p){ + memtraceBase.xShutdown(p); +} + +/* The substitute memory allocator */ +static sqlite3_mem_methods ersaztMethods = { + memtraceMalloc, + memtraceFree, + memtraceRealloc, + memtraceSize, + memtraceRoundup, + memtraceInit, + memtraceShutdown, + 0 +}; + +/* Begin tracing memory allocations to out. */ +int sqlite3MemTraceActivate(FILE *out){ + int rc = SQLITE_OK; + if( memtraceBase.xMalloc==0 ){ + rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &memtraceBase); + if( rc==SQLITE_OK ){ + rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &ersaztMethods); + } + } + memtraceOut = out; + return rc; +} + +/* Deactivate memory tracing */ +int sqlite3MemTraceDeactivate(void){ + int rc = SQLITE_OK; + if( memtraceBase.xMalloc!=0 ){ + rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &memtraceBase); + if( rc==SQLITE_OK ){ + memset(&memtraceBase, 0, sizeof(memtraceBase)); + } + } + memtraceOut = 0; + return rc; +} + +/************************* End ../ext/misc/memtrace.c ********************/ +/************************* Begin ../ext/misc/pcachetrace.c ******************/ +/* +** 2023-06-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements an extension that uses the SQLITE_CONFIG_PCACHE2 +** mechanism to add a tracing layer on top of pluggable page cache of +** SQLite. If this extension is registered prior to sqlite3_initialize(), +** it will cause all page cache activities to be logged on standard output, +** or to some other FILE specified by the initializer. +** +** This file needs to be compiled into the application that uses it. +** +** This extension is used to implement the --pcachetrace option of the +** command-line shell. +*/ +#include <assert.h> +#include <string.h> +#include <stdio.h> + +/* The original page cache routines */ +static sqlite3_pcache_methods2 pcacheBase; +static FILE *pcachetraceOut; + +/* Methods that trace pcache activity */ +static int pcachetraceInit(void *pArg){ + int nRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p)\n", pArg); + } + nRes = pcacheBase.xInit(pArg); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p) -> %d\n", pArg, nRes); + } + return nRes; +} +static void pcachetraceShutdown(void *pArg){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xShutdown(%p)\n", pArg); + } + pcacheBase.xShutdown(pArg); +} +static sqlite3_pcache *pcachetraceCreate(int szPage, int szExtra, int bPurge){ + sqlite3_pcache *pRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d)\n", + szPage, szExtra, bPurge); + } + pRes = pcacheBase.xCreate(szPage, szExtra, bPurge); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d) -> %p\n", + szPage, szExtra, bPurge, pRes); + } + return pRes; +} +static void pcachetraceCachesize(sqlite3_pcache *p, int nCachesize){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xCachesize(%p, %d)\n", p, nCachesize); + } + pcacheBase.xCachesize(p, nCachesize); +} +static int pcachetracePagecount(sqlite3_pcache *p){ + int nRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p)\n", p); + } + nRes = pcacheBase.xPagecount(p); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p) -> %d\n", p, nRes); + } + return nRes; +} +static sqlite3_pcache_page *pcachetraceFetch( + sqlite3_pcache *p, + unsigned key, + int crFg +){ + sqlite3_pcache_page *pRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d)\n", p, key, crFg); + } + pRes = pcacheBase.xFetch(p, key, crFg); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d) -> %p\n", + p, key, crFg, pRes); + } + return pRes; +} +static void pcachetraceUnpin( + sqlite3_pcache *p, + sqlite3_pcache_page *pPg, + int bDiscard +){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xUnpin(%p, %p, %d)\n", + p, pPg, bDiscard); + } + pcacheBase.xUnpin(p, pPg, bDiscard); +} +static void pcachetraceRekey( + sqlite3_pcache *p, + sqlite3_pcache_page *pPg, + unsigned oldKey, + unsigned newKey +){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xRekey(%p, %p, %u, %u)\n", + p, pPg, oldKey, newKey); + } + pcacheBase.xRekey(p, pPg, oldKey, newKey); +} +static void pcachetraceTruncate(sqlite3_pcache *p, unsigned n){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xTruncate(%p, %u)\n", p, n); + } + pcacheBase.xTruncate(p, n); +} +static void pcachetraceDestroy(sqlite3_pcache *p){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xDestroy(%p)\n", p); + } + pcacheBase.xDestroy(p); +} +static void pcachetraceShrink(sqlite3_pcache *p){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xShrink(%p)\n", p); + } + pcacheBase.xShrink(p); +} + +/* The substitute pcache methods */ +static sqlite3_pcache_methods2 ersaztPcacheMethods = { + 0, + 0, + pcachetraceInit, + pcachetraceShutdown, + pcachetraceCreate, + pcachetraceCachesize, + pcachetracePagecount, + pcachetraceFetch, + pcachetraceUnpin, + pcachetraceRekey, + pcachetraceTruncate, + pcachetraceDestroy, + pcachetraceShrink +}; + +/* Begin tracing memory allocations to out. */ +int sqlite3PcacheTraceActivate(FILE *out){ + int rc = SQLITE_OK; + if( pcacheBase.xFetch==0 ){ + rc = sqlite3_config(SQLITE_CONFIG_GETPCACHE2, &pcacheBase); + if( rc==SQLITE_OK ){ + rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &ersaztPcacheMethods); + } + } + pcachetraceOut = out; + return rc; +} + +/* Deactivate memory tracing */ +int sqlite3PcacheTraceDeactivate(void){ + int rc = SQLITE_OK; + if( pcacheBase.xFetch!=0 ){ + rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &pcacheBase); + if( rc==SQLITE_OK ){ + memset(&pcacheBase, 0, sizeof(pcacheBase)); + } + } + pcachetraceOut = 0; + return rc; +} + +/************************* End ../ext/misc/pcachetrace.c ********************/ +/************************* Begin ../ext/misc/shathree.c ******************/ +/* +** 2017-03-08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements functions that compute SHA3 hashes +** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard. +** Two SQL functions are implemented: +** +** sha3(X,SIZE) +** sha3_query(Y,SIZE) +** +** The sha3(X) function computes the SHA3 hash of the input X, or NULL if +** X is NULL. +** +** The sha3_query(Y) function evaluates all queries in the SQL statements of Y +** and returns a hash of their results. +** +** The SIZE argument is optional. If omitted, the SHA3-256 hash algorithm +** is used. If SIZE is included it must be one of the integers 224, 256, +** 384, or 512, to determine SHA3 hash variant that is computed. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include <assert.h> +#include <string.h> +#include <stdarg.h> + +#ifndef SQLITE_AMALGAMATION +/* typedef sqlite3_uint64 u64; */ +#endif /* SQLITE_AMALGAMATION */ + +/****************************************************************************** +** The Hash Engine +*/ +/* +** Macros to determine whether the machine is big or little endian, +** and whether or not that determination is run-time or compile-time. +** +** For best performance, an attempt is made to guess at the byte-order +** using C-preprocessor macros. If that is unsuccessful, or if +** -DSHA3_BYTEORDER=0 is set, then byte-order is determined +** at run-time. +*/ +#ifndef SHA3_BYTEORDER +# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ + defined(__arm__) +# define SHA3_BYTEORDER 1234 +# elif defined(sparc) || defined(__ppc__) +# define SHA3_BYTEORDER 4321 +# else +# define SHA3_BYTEORDER 0 +# endif +#endif + + +/* +** State structure for a SHA3 hash in progress +*/ +typedef struct SHA3Context SHA3Context; +struct SHA3Context { + union { + u64 s[25]; /* Keccak state. 5x5 lines of 64 bits each */ + unsigned char x[1600]; /* ... or 1600 bytes */ + } u; + unsigned nRate; /* Bytes of input accepted per Keccak iteration */ + unsigned nLoaded; /* Input bytes loaded into u.x[] so far this cycle */ + unsigned ixMask; /* Insert next input into u.x[nLoaded^ixMask]. */ +}; + +/* +** A single step of the Keccak mixing function for a 1600-bit state +*/ +static void KeccakF1600Step(SHA3Context *p){ + int i; + u64 b0, b1, b2, b3, b4; + u64 c0, c1, c2, c3, c4; + u64 d0, d1, d2, d3, d4; + static const u64 RC[] = { + 0x0000000000000001ULL, 0x0000000000008082ULL, + 0x800000000000808aULL, 0x8000000080008000ULL, + 0x000000000000808bULL, 0x0000000080000001ULL, + 0x8000000080008081ULL, 0x8000000000008009ULL, + 0x000000000000008aULL, 0x0000000000000088ULL, + 0x0000000080008009ULL, 0x000000008000000aULL, + 0x000000008000808bULL, 0x800000000000008bULL, + 0x8000000000008089ULL, 0x8000000000008003ULL, + 0x8000000000008002ULL, 0x8000000000000080ULL, + 0x000000000000800aULL, 0x800000008000000aULL, + 0x8000000080008081ULL, 0x8000000000008080ULL, + 0x0000000080000001ULL, 0x8000000080008008ULL + }; +# define a00 (p->u.s[0]) +# define a01 (p->u.s[1]) +# define a02 (p->u.s[2]) +# define a03 (p->u.s[3]) +# define a04 (p->u.s[4]) +# define a10 (p->u.s[5]) +# define a11 (p->u.s[6]) +# define a12 (p->u.s[7]) +# define a13 (p->u.s[8]) +# define a14 (p->u.s[9]) +# define a20 (p->u.s[10]) +# define a21 (p->u.s[11]) +# define a22 (p->u.s[12]) +# define a23 (p->u.s[13]) +# define a24 (p->u.s[14]) +# define a30 (p->u.s[15]) +# define a31 (p->u.s[16]) +# define a32 (p->u.s[17]) +# define a33 (p->u.s[18]) +# define a34 (p->u.s[19]) +# define a40 (p->u.s[20]) +# define a41 (p->u.s[21]) +# define a42 (p->u.s[22]) +# define a43 (p->u.s[23]) +# define a44 (p->u.s[24]) +# define ROL64(a,x) ((a<<x)|(a>>(64-x))) + + for(i=0; i<24; i+=4){ + c0 = a00^a10^a20^a30^a40; + c1 = a01^a11^a21^a31^a41; + c2 = a02^a12^a22^a32^a42; + c3 = a03^a13^a23^a33^a43; + c4 = a04^a14^a24^a34^a44; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a11^d1), 44); + b2 = ROL64((a22^d2), 43); + b3 = ROL64((a33^d3), 21); + b4 = ROL64((a44^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i]; + a11 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a20^d0), 3); + b3 = ROL64((a31^d1), 45); + b4 = ROL64((a42^d2), 61); + b0 = ROL64((a03^d3), 28); + b1 = ROL64((a14^d4), 20); + a20 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a40^d0), 18); + b0 = ROL64((a01^d1), 1); + b1 = ROL64((a12^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a34^d4), 8); + a40 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a10^d0), 36); + b2 = ROL64((a21^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a43^d3), 56); + b0 = ROL64((a04^d4), 27); + a10 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a30^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a02^d2), 62); + b1 = ROL64((a13^d3), 55); + b2 = ROL64((a24^d4), 39); + a30 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + c0 = a00^a20^a40^a10^a30; + c1 = a11^a31^a01^a21^a41; + c2 = a22^a42^a12^a32^a02; + c3 = a33^a03^a23^a43^a13; + c4 = a44^a14^a34^a04^a24; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a31^d1), 44); + b2 = ROL64((a12^d2), 43); + b3 = ROL64((a43^d3), 21); + b4 = ROL64((a24^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+1]; + a31 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a40^d0), 3); + b3 = ROL64((a21^d1), 45); + b4 = ROL64((a02^d2), 61); + b0 = ROL64((a33^d3), 28); + b1 = ROL64((a14^d4), 20); + a40 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a30^d0), 18); + b0 = ROL64((a11^d1), 1); + b1 = ROL64((a42^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a04^d4), 8); + a30 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a20^d0), 36); + b2 = ROL64((a01^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a13^d3), 56); + b0 = ROL64((a44^d4), 27); + a20 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a10^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a22^d2), 62); + b1 = ROL64((a03^d3), 55); + b2 = ROL64((a34^d4), 39); + a10 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + c0 = a00^a40^a30^a20^a10; + c1 = a31^a21^a11^a01^a41; + c2 = a12^a02^a42^a32^a22; + c3 = a43^a33^a23^a13^a03; + c4 = a24^a14^a04^a44^a34; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a21^d1), 44); + b2 = ROL64((a42^d2), 43); + b3 = ROL64((a13^d3), 21); + b4 = ROL64((a34^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+2]; + a21 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a30^d0), 3); + b3 = ROL64((a01^d1), 45); + b4 = ROL64((a22^d2), 61); + b0 = ROL64((a43^d3), 28); + b1 = ROL64((a14^d4), 20); + a30 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a10^d0), 18); + b0 = ROL64((a31^d1), 1); + b1 = ROL64((a02^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a44^d4), 8); + a10 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a40^d0), 36); + b2 = ROL64((a11^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a03^d3), 56); + b0 = ROL64((a24^d4), 27); + a40 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a20^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a12^d2), 62); + b1 = ROL64((a33^d3), 55); + b2 = ROL64((a04^d4), 39); + a20 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + c0 = a00^a30^a10^a40^a20; + c1 = a21^a01^a31^a11^a41; + c2 = a42^a22^a02^a32^a12; + c3 = a13^a43^a23^a03^a33; + c4 = a34^a14^a44^a24^a04; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a01^d1), 44); + b2 = ROL64((a02^d2), 43); + b3 = ROL64((a03^d3), 21); + b4 = ROL64((a04^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+3]; + a01 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a10^d0), 3); + b3 = ROL64((a11^d1), 45); + b4 = ROL64((a12^d2), 61); + b0 = ROL64((a13^d3), 28); + b1 = ROL64((a14^d4), 20); + a10 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a20^d0), 18); + b0 = ROL64((a21^d1), 1); + b1 = ROL64((a22^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a24^d4), 8); + a20 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a30^d0), 36); + b2 = ROL64((a31^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a33^d3), 56); + b0 = ROL64((a34^d4), 27); + a30 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a40^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a42^d2), 62); + b1 = ROL64((a43^d3), 55); + b2 = ROL64((a44^d4), 39); + a40 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + } +} + +/* +** Initialize a new hash. iSize determines the size of the hash +** in bits and should be one of 224, 256, 384, or 512. Or iSize +** can be zero to use the default hash size of 256 bits. +*/ +static void SHA3Init(SHA3Context *p, int iSize){ + memset(p, 0, sizeof(*p)); + if( iSize>=128 && iSize<=512 ){ + p->nRate = (1600 - ((iSize + 31)&~31)*2)/8; + }else{ + p->nRate = (1600 - 2*256)/8; + } +#if SHA3_BYTEORDER==1234 + /* Known to be little-endian at compile-time. No-op */ +#elif SHA3_BYTEORDER==4321 + p->ixMask = 7; /* Big-endian */ +#else + { + static unsigned int one = 1; + if( 1==*(unsigned char*)&one ){ + /* Little endian. No byte swapping. */ + p->ixMask = 0; + }else{ + /* Big endian. Byte swap. */ + p->ixMask = 7; + } + } +#endif +} + +/* +** Make consecutive calls to the SHA3Update function to add new content +** to the hash +*/ +static void SHA3Update( + SHA3Context *p, + const unsigned char *aData, + unsigned int nData +){ + unsigned int i = 0; + if( aData==0 ) return; +#if SHA3_BYTEORDER==1234 + if( (p->nLoaded % 8)==0 && ((aData - (const unsigned char*)0)&7)==0 ){ + for(; i+7<nData; i+=8){ + p->u.s[p->nLoaded/8] ^= *(u64*)&aData[i]; + p->nLoaded += 8; + if( p->nLoaded>=p->nRate ){ + KeccakF1600Step(p); + p->nLoaded = 0; + } + } + } +#endif + for(; i<nData; i++){ +#if SHA3_BYTEORDER==1234 + p->u.x[p->nLoaded] ^= aData[i]; +#elif SHA3_BYTEORDER==4321 + p->u.x[p->nLoaded^0x07] ^= aData[i]; +#else + p->u.x[p->nLoaded^p->ixMask] ^= aData[i]; +#endif + p->nLoaded++; + if( p->nLoaded==p->nRate ){ + KeccakF1600Step(p); + p->nLoaded = 0; + } + } +} + +/* +** After all content has been added, invoke SHA3Final() to compute +** the final hash. The function returns a pointer to the binary +** hash value. +*/ +static unsigned char *SHA3Final(SHA3Context *p){ + unsigned int i; + if( p->nLoaded==p->nRate-1 ){ + const unsigned char c1 = 0x86; + SHA3Update(p, &c1, 1); + }else{ + const unsigned char c2 = 0x06; + const unsigned char c3 = 0x80; + SHA3Update(p, &c2, 1); + p->nLoaded = p->nRate - 1; + SHA3Update(p, &c3, 1); + } + for(i=0; i<p->nRate; i++){ + p->u.x[i+p->nRate] = p->u.x[i^p->ixMask]; + } + return &p->u.x[p->nRate]; +} +/* End of the hashing logic +*****************************************************************************/ + +/* +** Implementation of the sha3(X,SIZE) function. +** +** Return a BLOB which is the SIZE-bit SHA3 hash of X. The default +** size is 256. If X is a BLOB, it is hashed as is. +** For all other non-NULL types of input, X is converted into a UTF-8 string +** and the string is hashed without the trailing 0x00 terminator. The hash +** of a NULL value is NULL. +*/ +static void sha3Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + SHA3Context cx; + int eType = sqlite3_value_type(argv[0]); + int nByte = sqlite3_value_bytes(argv[0]); + int iSize; + if( argc==1 ){ + iSize = 256; + }else{ + iSize = sqlite3_value_int(argv[1]); + if( iSize!=224 && iSize!=256 && iSize!=384 && iSize!=512 ){ + sqlite3_result_error(context, "SHA3 size should be one of: 224 256 " + "384 512", -1); + return; + } + } + if( eType==SQLITE_NULL ) return; + SHA3Init(&cx, iSize); + if( eType==SQLITE_BLOB ){ + SHA3Update(&cx, sqlite3_value_blob(argv[0]), nByte); + }else{ + SHA3Update(&cx, sqlite3_value_text(argv[0]), nByte); + } + sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); +} + +/* Compute a string using sqlite3_vsnprintf() with a maximum length +** of 50 bytes and add it to the hash. +*/ +static void sha3_step_vformat( + SHA3Context *p, /* Add content to this context */ + const char *zFormat, + ... +){ + va_list ap; + int n; + char zBuf[50]; + va_start(ap, zFormat); + sqlite3_vsnprintf(sizeof(zBuf),zBuf,zFormat,ap); + va_end(ap); + n = (int)strlen(zBuf); + SHA3Update(p, (unsigned char*)zBuf, n); +} + +/* +** Implementation of the sha3_query(SQL,SIZE) function. +** +** This function compiles and runs the SQL statement(s) given in the +** argument. The results are hashed using a SIZE-bit SHA3. The default +** size is 256. +** +** The format of the byte stream that is hashed is summarized as follows: +** +** S<n>:<sql> +** R +** N +** I<int> +** F<ieee-float> +** B<size>:<bytes> +** T<size>:<text> +** +** <sql> is the original SQL text for each statement run and <n> is +** the size of that text. The SQL text is UTF-8. A single R character +** occurs before the start of each row. N means a NULL value. +** I mean an 8-byte little-endian integer <int>. F is a floating point +** number with an 8-byte little-endian IEEE floating point value <ieee-float>. +** B means blobs of <size> bytes. T means text rendered as <size> +** bytes of UTF-8. The <n> and <size> values are expressed as an ASCII +** text integers. +** +** For each SQL statement in the X input, there is one S segment. Each +** S segment is followed by zero or more R segments, one for each row in the +** result set. After each R, there are one or more N, I, F, B, or T segments, +** one for each column in the result set. Segments are concatentated directly +** with no delimiters of any kind. +*/ +static void sha3QueryFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + const char *zSql = (const char*)sqlite3_value_text(argv[0]); + sqlite3_stmt *pStmt = 0; + int nCol; /* Number of columns in the result set */ + int i; /* Loop counter */ + int rc; + int n; + const char *z; + SHA3Context cx; + int iSize; + + if( argc==1 ){ + iSize = 256; + }else{ + iSize = sqlite3_value_int(argv[1]); + if( iSize!=224 && iSize!=256 && iSize!=384 && iSize!=512 ){ + sqlite3_result_error(context, "SHA3 size should be one of: 224 256 " + "384 512", -1); + return; + } + } + if( zSql==0 ) return; + SHA3Init(&cx, iSize); + while( zSql[0] ){ + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zSql); + if( rc ){ + char *zMsg = sqlite3_mprintf("error SQL statement [%s]: %s", + zSql, sqlite3_errmsg(db)); + sqlite3_finalize(pStmt); + sqlite3_result_error(context, zMsg, -1); + sqlite3_free(zMsg); + return; + } + if( !sqlite3_stmt_readonly(pStmt) ){ + char *zMsg = sqlite3_mprintf("non-query: [%s]", sqlite3_sql(pStmt)); + sqlite3_finalize(pStmt); + sqlite3_result_error(context, zMsg, -1); + sqlite3_free(zMsg); + return; + } + nCol = sqlite3_column_count(pStmt); + z = sqlite3_sql(pStmt); + if( z ){ + n = (int)strlen(z); + sha3_step_vformat(&cx,"S%d:",n); + SHA3Update(&cx,(unsigned char*)z,n); + } + + /* Compute a hash over the result of the query */ + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + SHA3Update(&cx,(const unsigned char*)"R",1); + for(i=0; i<nCol; i++){ + switch( sqlite3_column_type(pStmt,i) ){ + case SQLITE_NULL: { + SHA3Update(&cx, (const unsigned char*)"N",1); + break; + } + case SQLITE_INTEGER: { + sqlite3_uint64 u; + int j; + unsigned char x[9]; + sqlite3_int64 v = sqlite3_column_int64(pStmt,i); + memcpy(&u, &v, 8); + for(j=8; j>=1; j--){ + x[j] = u & 0xff; + u >>= 8; + } + x[0] = 'I'; + SHA3Update(&cx, x, 9); + break; + } + case SQLITE_FLOAT: { + sqlite3_uint64 u; + int j; + unsigned char x[9]; + double r = sqlite3_column_double(pStmt,i); + memcpy(&u, &r, 8); + for(j=8; j>=1; j--){ + x[j] = u & 0xff; + u >>= 8; + } + x[0] = 'F'; + SHA3Update(&cx,x,9); + break; + } + case SQLITE_TEXT: { + int n2 = sqlite3_column_bytes(pStmt, i); + const unsigned char *z2 = sqlite3_column_text(pStmt, i); + sha3_step_vformat(&cx,"T%d:",n2); + SHA3Update(&cx, z2, n2); + break; + } + case SQLITE_BLOB: { + int n2 = sqlite3_column_bytes(pStmt, i); + const unsigned char *z2 = sqlite3_column_blob(pStmt, i); + sha3_step_vformat(&cx,"B%d:",n2); + SHA3Update(&cx, z2, n2); + break; + } + } + } + } + sqlite3_finalize(pStmt); + } + sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); +} + + +#ifdef _WIN32 + +#endif +int sqlite3_shathree_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + rc = sqlite3_create_function(db, "sha3", 1, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, sha3Func, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3", 2, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, sha3Func, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3_query", 1, + SQLITE_UTF8 | SQLITE_DIRECTONLY, + 0, sha3QueryFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3_query", 2, + SQLITE_UTF8 | SQLITE_DIRECTONLY, + 0, sha3QueryFunc, 0, 0); + } + return rc; +} + +/************************* End ../ext/misc/shathree.c ********************/ +/************************* Begin ../ext/misc/uint.c ******************/ +/* +** 2020-04-14 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements the UINT collating sequence. +** +** UINT works like BINARY for text, except that embedded strings +** of digits compare in numeric order. +** +** * Leading zeros are handled properly, in the sense that +** they do not mess of the maginitude comparison of embedded +** strings of digits. "x00123y" is equal to "x123y". +** +** * Only unsigned integers are recognized. Plus and minus +** signs are ignored. Decimal points and exponential notation +** are ignored. +** +** * Embedded integers can be of arbitrary length. Comparison +** is *not* limited integers that can be expressed as a +** 64-bit machine integer. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include <assert.h> +#include <string.h> +#include <ctype.h> + +/* +** Compare text in lexicographic order, except strings of digits +** compare in numeric order. +*/ +static int uintCollFunc( + void *notUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + const unsigned char *zA = (const unsigned char*)pKey1; + const unsigned char *zB = (const unsigned char*)pKey2; + int i=0, j=0, x; + (void)notUsed; + while( i<nKey1 && j<nKey2 ){ + x = zA[i] - zB[j]; + if( isdigit(zA[i]) ){ + int k; + if( !isdigit(zB[j]) ) return x; + while( i<nKey1 && zA[i]=='0' ){ i++; } + while( j<nKey2 && zB[j]=='0' ){ j++; } + k = 0; + while( i+k<nKey1 && isdigit(zA[i+k]) + && j+k<nKey2 && isdigit(zB[j+k]) ){ + k++; + } + if( i+k<nKey1 && isdigit(zA[i+k]) ){ + return +1; + }else if( j+k<nKey2 && isdigit(zB[j+k]) ){ + return -1; + }else{ + x = memcmp(zA+i, zB+j, k); + if( x ) return x; + i += k; + j += k; + } + }else if( x ){ + return x; + }else{ + i++; + j++; + } + } + return (nKey1 - i) - (nKey2 - j); +} + +#ifdef _WIN32 + +#endif +int sqlite3_uint_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + return sqlite3_create_collation(db, "uint", SQLITE_UTF8, 0, uintCollFunc); +} + +/************************* End ../ext/misc/uint.c ********************/ +/************************* Begin ../ext/misc/decimal.c ******************/ +/* +** 2020-06-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** Routines to implement arbitrary-precision decimal math. +** +** The focus here is on simplicity and correctness, not performance. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include <assert.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> + +/* Mark a function parameter as unused, to suppress nuisance compiler +** warnings. */ +#ifndef UNUSED_PARAMETER +# define UNUSED_PARAMETER(X) (void)(X) +#endif + + +/* A decimal object */ +typedef struct Decimal Decimal; +struct Decimal { + char sign; /* 0 for positive, 1 for negative */ + char oom; /* True if an OOM is encountered */ + char isNull; /* True if holds a NULL rather than a number */ + char isInit; /* True upon initialization */ + int nDigit; /* Total number of digits */ + int nFrac; /* Number of digits to the right of the decimal point */ + signed char *a; /* Array of digits. Most significant first. */ +}; + +/* +** Release memory held by a Decimal, but do not free the object itself. +*/ +static void decimal_clear(Decimal *p){ + sqlite3_free(p->a); +} + +/* +** Destroy a Decimal object +*/ +static void decimal_free(Decimal *p){ + if( p ){ + decimal_clear(p); + sqlite3_free(p); + } +} + +/* +** Allocate a new Decimal object initialized to the text in zIn[]. +** Return NULL if any kind of error occurs. +*/ +static Decimal *decimalNewFromText(const char *zIn, int n){ + Decimal *p = 0; + int i; + int iExp = 0; + + p = sqlite3_malloc( sizeof(*p) ); + if( p==0 ) goto new_from_text_failed; + p->sign = 0; + p->oom = 0; + p->isInit = 1; + p->isNull = 0; + p->nDigit = 0; + p->nFrac = 0; + p->a = sqlite3_malloc64( n+1 ); + if( p->a==0 ) goto new_from_text_failed; + for(i=0; isspace(zIn[i]); i++){} + if( zIn[i]=='-' ){ + p->sign = 1; + i++; + }else if( zIn[i]=='+' ){ + i++; + } + while( i<n && zIn[i]=='0' ) i++; + while( i<n ){ + char c = zIn[i]; + if( c>='0' && c<='9' ){ + p->a[p->nDigit++] = c - '0'; + }else if( c=='.' ){ + p->nFrac = p->nDigit + 1; + }else if( c=='e' || c=='E' ){ + int j = i+1; + int neg = 0; + if( j>=n ) break; + if( zIn[j]=='-' ){ + neg = 1; + j++; + }else if( zIn[j]=='+' ){ + j++; + } + while( j<n && iExp<1000000 ){ + if( zIn[j]>='0' && zIn[j]<='9' ){ + iExp = iExp*10 + zIn[j] - '0'; + } + j++; + } + if( neg ) iExp = -iExp; + break; + } + i++; + } + if( p->nFrac ){ + p->nFrac = p->nDigit - (p->nFrac - 1); + } + if( iExp>0 ){ + if( p->nFrac>0 ){ + if( iExp<=p->nFrac ){ + p->nFrac -= iExp; + iExp = 0; + }else{ + iExp -= p->nFrac; + p->nFrac = 0; + } + } + if( iExp>0 ){ + p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); + if( p->a==0 ) goto new_from_text_failed; + memset(p->a+p->nDigit, 0, iExp); + p->nDigit += iExp; + } + }else if( iExp<0 ){ + int nExtra; + iExp = -iExp; + nExtra = p->nDigit - p->nFrac - 1; + if( nExtra ){ + if( nExtra>=iExp ){ + p->nFrac += iExp; + iExp = 0; + }else{ + iExp -= nExtra; + p->nFrac = p->nDigit - 1; + } + } + if( iExp>0 ){ + p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); + if( p->a==0 ) goto new_from_text_failed; + memmove(p->a+iExp, p->a, p->nDigit); + memset(p->a, 0, iExp); + p->nDigit += iExp; + p->nFrac += iExp; + } + } + return p; + +new_from_text_failed: + if( p ){ + if( p->a ) sqlite3_free(p->a); + sqlite3_free(p); + } + return 0; +} + +/* Forward reference */ +static Decimal *decimalFromDouble(double); + +/* +** Allocate a new Decimal object from an sqlite3_value. Return a pointer +** to the new object, or NULL if there is an error. If the pCtx argument +** is not NULL, then errors are reported on it as well. +** +** If the pIn argument is SQLITE_TEXT or SQLITE_INTEGER, it is converted +** directly into a Decimal. For SQLITE_FLOAT or for SQLITE_BLOB of length +** 8 bytes, the resulting double value is expanded into its decimal equivalent. +** If pIn is NULL or if it is a BLOB that is not exactly 8 bytes in length, +** then NULL is returned. +*/ +static Decimal *decimal_new( + sqlite3_context *pCtx, /* Report error here, if not null */ + sqlite3_value *pIn, /* Construct the decimal object from this */ + int bTextOnly /* Always interpret pIn as text if true */ +){ + Decimal *p = 0; + int eType = sqlite3_value_type(pIn); + if( bTextOnly && (eType==SQLITE_FLOAT || eType==SQLITE_BLOB) ){ + eType = SQLITE_TEXT; + } + switch( eType ){ + case SQLITE_TEXT: + case SQLITE_INTEGER: { + const char *zIn = (const char*)sqlite3_value_text(pIn); + int n = sqlite3_value_bytes(pIn); + p = decimalNewFromText(zIn, n); + if( p==0 ) goto new_failed; + break; + } + + case SQLITE_FLOAT: { + p = decimalFromDouble(sqlite3_value_double(pIn)); + break; + } + + case SQLITE_BLOB: { + const unsigned char *x; + unsigned int i; + sqlite3_uint64 v = 0; + double r; + + if( sqlite3_value_bytes(pIn)!=sizeof(r) ) break; + x = sqlite3_value_blob(pIn); + for(i=0; i<sizeof(r); i++){ + v = (v<<8) | x[i]; + } + memcpy(&r, &v, sizeof(r)); + p = decimalFromDouble(r); + break; + } + + case SQLITE_NULL: { + break; + } + } + return p; + +new_failed: + if( pCtx ) sqlite3_result_error_nomem(pCtx); + sqlite3_free(p); + return 0; +} + +/* +** Make the given Decimal the result. +*/ +static void decimal_result(sqlite3_context *pCtx, Decimal *p){ + char *z; + int i, j; + int n; + if( p==0 || p->oom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( p->isNull ){ + sqlite3_result_null(pCtx); + return; + } + z = sqlite3_malloc( p->nDigit+4 ); + if( z==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + i = 0; + if( p->nDigit==0 || (p->nDigit==1 && p->a[0]==0) ){ + p->sign = 0; + } + if( p->sign ){ + z[0] = '-'; + i = 1; + } + n = p->nDigit - p->nFrac; + if( n<=0 ){ + z[i++] = '0'; + } + j = 0; + while( n>1 && p->a[j]==0 ){ + j++; + n--; + } + while( n>0 ){ + z[i++] = p->a[j] + '0'; + j++; + n--; + } + if( p->nFrac ){ + z[i++] = '.'; + do{ + z[i++] = p->a[j] + '0'; + j++; + }while( j<p->nDigit ); + } + z[i] = 0; + sqlite3_result_text(pCtx, z, i, sqlite3_free); +} + +/* +** Make the given Decimal the result in an format similar to '%+#e'. +** In other words, show exponential notation with leading and trailing +** zeros omitted. +*/ +static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p){ + char *z; /* The output buffer */ + int i; /* Loop counter */ + int nZero; /* Number of leading zeros */ + int nDigit; /* Number of digits not counting trailing zeros */ + int nFrac; /* Digits to the right of the decimal point */ + int exp; /* Exponent value */ + signed char zero; /* Zero value */ + signed char *a; /* Array of digits */ + + if( p==0 || p->oom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( p->isNull ){ + sqlite3_result_null(pCtx); + return; + } + for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){} + for(nZero=0; nZero<nDigit && p->a[nZero]==0; nZero++){} + nFrac = p->nFrac + (nDigit - p->nDigit); + nDigit -= nZero; + z = sqlite3_malloc( nDigit+20 ); + if( z==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( nDigit==0 ){ + zero = 0; + a = &zero; + nDigit = 1; + nFrac = 0; + }else{ + a = &p->a[nZero]; + } + if( p->sign && nDigit>0 ){ + z[0] = '-'; + }else{ + z[0] = '+'; + } + z[1] = a[0]+'0'; + z[2] = '.'; + if( nDigit==1 ){ + z[3] = '0'; + i = 4; + }else{ + for(i=1; i<nDigit; i++){ + z[2+i] = a[i]+'0'; + } + i = nDigit+2; + } + exp = nDigit - nFrac - 1; + sqlite3_snprintf(nDigit+20-i, &z[i], "e%+03d", exp); + sqlite3_result_text(pCtx, z, -1, sqlite3_free); +} + +/* +** Compare to Decimal objects. Return negative, 0, or positive if the +** first object is less than, equal to, or greater than the second. +** +** Preconditions for this routine: +** +** pA!=0 +** pA->isNull==0 +** pB!=0 +** pB->isNull==0 +*/ +static int decimal_cmp(const Decimal *pA, const Decimal *pB){ + int nASig, nBSig, rc, n; + if( pA->sign!=pB->sign ){ + return pA->sign ? -1 : +1; + } + if( pA->sign ){ + const Decimal *pTemp = pA; + pA = pB; + pB = pTemp; + } + nASig = pA->nDigit - pA->nFrac; + nBSig = pB->nDigit - pB->nFrac; + if( nASig!=nBSig ){ + return nASig - nBSig; + } + n = pA->nDigit; + if( n>pB->nDigit ) n = pB->nDigit; + rc = memcmp(pA->a, pB->a, n); + if( rc==0 ){ + rc = pA->nDigit - pB->nDigit; + } + return rc; +} + +/* +** SQL Function: decimal_cmp(X, Y) +** +** Return negative, zero, or positive if X is less then, equal to, or +** greater than Y. +*/ +static void decimalCmpFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = 0, *pB = 0; + int rc; + + UNUSED_PARAMETER(argc); + pA = decimal_new(context, argv[0], 1); + if( pA==0 || pA->isNull ) goto cmp_done; + pB = decimal_new(context, argv[1], 1); + if( pB==0 || pB->isNull ) goto cmp_done; + rc = decimal_cmp(pA, pB); + if( rc<0 ) rc = -1; + else if( rc>0 ) rc = +1; + sqlite3_result_int(context, rc); +cmp_done: + decimal_free(pA); + decimal_free(pB); +} + +/* +** Expand the Decimal so that it has a least nDigit digits and nFrac +** digits to the right of the decimal point. +*/ +static void decimal_expand(Decimal *p, int nDigit, int nFrac){ + int nAddSig; + int nAddFrac; + if( p==0 ) return; + nAddFrac = nFrac - p->nFrac; + nAddSig = (nDigit - p->nDigit) - nAddFrac; + if( nAddFrac==0 && nAddSig==0 ) return; + p->a = sqlite3_realloc64(p->a, nDigit+1); + if( p->a==0 ){ + p->oom = 1; + return; + } + if( nAddSig ){ + memmove(p->a+nAddSig, p->a, p->nDigit); + memset(p->a, 0, nAddSig); + p->nDigit += nAddSig; + } + if( nAddFrac ){ + memset(p->a+p->nDigit, 0, nAddFrac); + p->nDigit += nAddFrac; + p->nFrac += nAddFrac; + } +} + +/* +** Add the value pB into pA. A := A + B. +** +** Both pA and pB might become denormalized by this routine. +*/ +static void decimal_add(Decimal *pA, Decimal *pB){ + int nSig, nFrac, nDigit; + int i, rc; + if( pA==0 ){ + return; + } + if( pA->oom || pB==0 || pB->oom ){ + pA->oom = 1; + return; + } + if( pA->isNull || pB->isNull ){ + pA->isNull = 1; + return; + } + nSig = pA->nDigit - pA->nFrac; + if( nSig && pA->a[0]==0 ) nSig--; + if( nSig<pB->nDigit-pB->nFrac ){ + nSig = pB->nDigit - pB->nFrac; + } + nFrac = pA->nFrac; + if( nFrac<pB->nFrac ) nFrac = pB->nFrac; + nDigit = nSig + nFrac + 1; + decimal_expand(pA, nDigit, nFrac); + decimal_expand(pB, nDigit, nFrac); + if( pA->oom || pB->oom ){ + pA->oom = 1; + }else{ + if( pA->sign==pB->sign ){ + int carry = 0; + for(i=nDigit-1; i>=0; i--){ + int x = pA->a[i] + pB->a[i] + carry; + if( x>=10 ){ + carry = 1; + pA->a[i] = x - 10; + }else{ + carry = 0; + pA->a[i] = x; + } + } + }else{ + signed char *aA, *aB; + int borrow = 0; + rc = memcmp(pA->a, pB->a, nDigit); + if( rc<0 ){ + aA = pB->a; + aB = pA->a; + pA->sign = !pA->sign; + }else{ + aA = pA->a; + aB = pB->a; + } + for(i=nDigit-1; i>=0; i--){ + int x = aA[i] - aB[i] - borrow; + if( x<0 ){ + pA->a[i] = x+10; + borrow = 1; + }else{ + pA->a[i] = x; + borrow = 0; + } + } + } + } +} + +/* +** Multiply A by B. A := A * B +** +** All significant digits after the decimal point are retained. +** Trailing zeros after the decimal point are omitted as long as +** the number of digits after the decimal point is no less than +** either the number of digits in either input. +*/ +static void decimalMul(Decimal *pA, Decimal *pB){ + signed char *acc = 0; + int i, j, k; + int minFrac; + + if( pA==0 || pA->oom || pA->isNull + || pB==0 || pB->oom || pB->isNull + ){ + goto mul_end; + } + acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 ); + if( acc==0 ){ + pA->oom = 1; + goto mul_end; + } + memset(acc, 0, pA->nDigit + pB->nDigit + 2); + minFrac = pA->nFrac; + if( pB->nFrac<minFrac ) minFrac = pB->nFrac; + for(i=pA->nDigit-1; i>=0; i--){ + signed char f = pA->a[i]; + int carry = 0, x; + for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){ + x = acc[k] + f*pB->a[j] + carry; + acc[k] = x%10; + carry = x/10; + } + x = acc[k] + carry; + acc[k] = x%10; + acc[k-1] += x/10; + } + sqlite3_free(pA->a); + pA->a = acc; + acc = 0; + pA->nDigit += pB->nDigit + 2; + pA->nFrac += pB->nFrac; + pA->sign ^= pB->sign; + while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){ + pA->nFrac--; + pA->nDigit--; + } + +mul_end: + sqlite3_free(acc); +} + +/* +** Create a new Decimal object that contains an integer power of 2. +*/ +static Decimal *decimalPow2(int N){ + Decimal *pA = 0; /* The result to be returned */ + Decimal *pX = 0; /* Multiplier */ + if( N<-20000 || N>20000 ) goto pow2_fault; + pA = decimalNewFromText("1.0", 3); + if( pA==0 || pA->oom ) goto pow2_fault; + if( N==0 ) return pA; + if( N>0 ){ + pX = decimalNewFromText("2.0", 3); + }else{ + N = -N; + pX = decimalNewFromText("0.5", 3); + } + if( pX==0 || pX->oom ) goto pow2_fault; + while( 1 /* Exit by break */ ){ + if( N & 1 ){ + decimalMul(pA, pX); + if( pA->oom ) goto pow2_fault; + } + N >>= 1; + if( N==0 ) break; + decimalMul(pX, pX); + } + decimal_free(pX); + return pA; + +pow2_fault: + decimal_free(pA); + decimal_free(pX); + return 0; +} + +/* +** Use an IEEE754 binary64 ("double") to generate a new Decimal object. +*/ +static Decimal *decimalFromDouble(double r){ + sqlite3_int64 m, a; + int e; + int isNeg; + Decimal *pA; + Decimal *pX; + char zNum[100]; + if( r<0.0 ){ + isNeg = 1; + r = -r; + }else{ + isNeg = 0; + } + memcpy(&a,&r,sizeof(a)); + if( a==0 ){ + e = 0; + m = 0; + }else{ + e = a>>52; + m = a & ((((sqlite3_int64)1)<<52)-1); + if( e==0 ){ + m <<= 1; + }else{ + m |= ((sqlite3_int64)1)<<52; + } + while( e<1075 && m>0 && (m&1)==0 ){ + m >>= 1; + e++; + } + if( isNeg ) m = -m; + e = e - 1075; + if( e>971 ){ + return 0; /* A NaN or an Infinity */ + } + } + + /* At this point m is the integer significand and e is the exponent */ + sqlite3_snprintf(sizeof(zNum), zNum, "%lld", m); + pA = decimalNewFromText(zNum, (int)strlen(zNum)); + pX = decimalPow2(e); + decimalMul(pA, pX); + decimal_free(pX); + return pA; +} + +/* +** SQL Function: decimal(X) +** OR: decimal_exp(X) +** +** Convert input X into decimal and then back into text. +** +** If X is originally a float, then a full decimal expansion of that floating +** point value is done. Or if X is an 8-byte blob, it is interpreted +** as a float and similarly expanded. +** +** The decimal_exp(X) function returns the result in exponential notation. +** decimal(X) returns a complete decimal, without the e+NNN at the end. +*/ +static void decimalFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p = decimal_new(context, argv[0], 0); + UNUSED_PARAMETER(argc); + if( p ){ + if( sqlite3_user_data(context)!=0 ){ + decimal_result_sci(context, p); + }else{ + decimal_result(context, p); + } + decimal_free(p); + } +} + +/* +** Compare text in decimal order. +*/ +static int decimalCollFunc( + void *notUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + const unsigned char *zA = (const unsigned char*)pKey1; + const unsigned char *zB = (const unsigned char*)pKey2; + Decimal *pA = decimalNewFromText((const char*)zA, nKey1); + Decimal *pB = decimalNewFromText((const char*)zB, nKey2); + int rc; + UNUSED_PARAMETER(notUsed); + if( pA==0 || pB==0 ){ + rc = 0; + }else{ + rc = decimal_cmp(pA, pB); + } + decimal_free(pA); + decimal_free(pB); + return rc; +} + + +/* +** SQL Function: decimal_add(X, Y) +** decimal_sub(X, Y) +** +** Return the sum or difference of X and Y. +*/ +static void decimalAddFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 1); + Decimal *pB = decimal_new(context, argv[1], 1); + UNUSED_PARAMETER(argc); + decimal_add(pA, pB); + decimal_result(context, pA); + decimal_free(pA); + decimal_free(pB); +} +static void decimalSubFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 1); + Decimal *pB = decimal_new(context, argv[1], 1); + UNUSED_PARAMETER(argc); + if( pB ){ + pB->sign = !pB->sign; + decimal_add(pA, pB); + decimal_result(context, pA); + } + decimal_free(pA); + decimal_free(pB); +} + +/* Aggregate funcion: decimal_sum(X) +** +** Works like sum() except that it uses decimal arithmetic for unlimited +** precision. +*/ +static void decimalSumStep( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p; + Decimal *pArg; + UNUSED_PARAMETER(argc); + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( !p->isInit ){ + p->isInit = 1; + p->a = sqlite3_malloc(2); + if( p->a==0 ){ + p->oom = 1; + }else{ + p->a[0] = 0; + } + p->nDigit = 1; + p->nFrac = 0; + } + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pArg = decimal_new(context, argv[0], 1); + decimal_add(p, pArg); + decimal_free(pArg); +} +static void decimalSumInverse( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p; + Decimal *pArg; + UNUSED_PARAMETER(argc); + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pArg = decimal_new(context, argv[0], 1); + if( pArg ) pArg->sign = !pArg->sign; + decimal_add(p, pArg); + decimal_free(pArg); +} +static void decimalSumValue(sqlite3_context *context){ + Decimal *p = sqlite3_aggregate_context(context, 0); + if( p==0 ) return; + decimal_result(context, p); +} +static void decimalSumFinalize(sqlite3_context *context){ + Decimal *p = sqlite3_aggregate_context(context, 0); + if( p==0 ) return; + decimal_result(context, p); + decimal_clear(p); +} + +/* +** SQL Function: decimal_mul(X, Y) +** +** Return the product of X and Y. +*/ +static void decimalMulFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 1); + Decimal *pB = decimal_new(context, argv[1], 1); + UNUSED_PARAMETER(argc); + if( pA==0 || pA->oom || pA->isNull + || pB==0 || pB->oom || pB->isNull + ){ + goto mul_end; + } + decimalMul(pA, pB); + if( pA->oom ){ + goto mul_end; + } + decimal_result(context, pA); + +mul_end: + decimal_free(pA); + decimal_free(pB); +} + +/* +** SQL Function: decimal_pow2(N) +** +** Return the N-th power of 2. N must be an integer. +*/ +static void decimalPow2Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){ + Decimal *pA = decimalPow2(sqlite3_value_int(argv[0])); + decimal_result_sci(context, pA); + decimal_free(pA); + } +} + +#ifdef _WIN32 + +#endif +int sqlite3_decimal_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + static const struct { + const char *zFuncName; + int nArg; + int iArg; + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } aFunc[] = { + { "decimal", 1, 0, decimalFunc }, + { "decimal_exp", 1, 1, decimalFunc }, + { "decimal_cmp", 2, 0, decimalCmpFunc }, + { "decimal_add", 2, 0, decimalAddFunc }, + { "decimal_sub", 2, 0, decimalSubFunc }, + { "decimal_mul", 2, 0, decimalMulFunc }, + { "decimal_pow2", 1, 0, decimalPow2Func }, + }; + unsigned int i; + (void)pzErrMsg; /* Unused parameter */ + + SQLITE_EXTENSION_INIT2(pApi); + + for(i=0; i<(int)(sizeof(aFunc)/sizeof(aFunc[0])) && rc==SQLITE_OK; i++){ + rc = sqlite3_create_function(db, aFunc[i].zFuncName, aFunc[i].nArg, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, + aFunc[i].iArg ? db : 0, aFunc[i].xFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_window_function(db, "decimal_sum", 1, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, 0, + decimalSumStep, decimalSumFinalize, + decimalSumValue, decimalSumInverse, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_collation(db, "decimal", SQLITE_UTF8, + 0, decimalCollFunc); + } + return rc; +} + +/************************* End ../ext/misc/decimal.c ********************/ +#undef sqlite3_base_init +#define sqlite3_base_init sqlite3_base64_init +/************************* Begin ../ext/misc/base64.c ******************/ +/* +** 2022-11-18 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This is a SQLite extension for converting in either direction +** between a (binary) blob and base64 text. Base64 can transit a +** sane USASCII channel unmolested. It also plays nicely in CSV or +** written as TCL brace-enclosed literals or SQL string literals, +** and can be used unmodified in XML-like documents. +** +** This is an independent implementation of conversions specified in +** RFC 4648, done on the above date by the author (Larry Brasfield) +** who thereby has the right to put this into the public domain. +** +** The conversions meet RFC 4648 requirements, provided that this +** C source specifies that line-feeds are included in the encoded +** data to limit visible line lengths to 72 characters and to +** terminate any encoded blob having non-zero length. +** +** Length limitations are not imposed except that the runtime +** SQLite string or blob length limits are respected. Otherwise, +** any length binary sequence can be represented and recovered. +** Generated base64 sequences, with their line-feeds included, +** can be concatenated; the result converted back to binary will +** be the concatenation of the represented binary sequences. +** +** This SQLite3 extension creates a function, base64(x), which +** either: converts text x containing base64 to a returned blob; +** or converts a blob x to returned text containing base64. An +** error will be thrown for other input argument types. +** +** This code relies on UTF-8 encoding only with respect to the +** meaning of the first 128 (7-bit) codes matching that of USASCII. +** It will fail miserably if somehow made to try to convert EBCDIC. +** Because it is table-driven, it could be enhanced to handle that, +** but the world and SQLite have moved on from that anachronism. +** +** To build the extension: +** Set shell variable SQDIR=<your favorite SQLite checkout directory> +** *Nix: gcc -O2 -shared -I$SQDIR -fPIC -o base64.so base64.c +** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR -o base64.dylib base64.c +** Win32: gcc -O2 -shared -I%SQDIR% -o base64.dll base64.c +** Win32: cl /Os -I%SQDIR% base64.c -link -dll -out:base64.dll +*/ + +#include <assert.h> + +/* #include "sqlite3ext.h" */ + +#ifndef deliberate_fall_through +/* Quiet some compilers about some of our intentional code. */ +# if GCC_VERSION>=7000000 +# define deliberate_fall_through __attribute__((fallthrough)); +# else +# define deliberate_fall_through +# endif +#endif + +SQLITE_EXTENSION_INIT1; + +#define PC 0x80 /* pad character */ +#define WS 0x81 /* whitespace */ +#define ND 0x82 /* Not above or digit-value */ +#define PAD_CHAR '=' + +#ifndef U8_TYPEDEF +/* typedef unsigned char u8; */ +#define U8_TYPEDEF +#endif + +/* Decoding table, ASCII (7-bit) value to base 64 digit value or other */ +static const u8 b64DigitValues[128] = { + /* HT LF VT FF CR */ + ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND, + /* US */ + ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, + /*sp + / */ + WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63, + /* 0 1 5 9 = */ + 52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND, + /* A O */ + ND, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + /* P Z */ + 15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND, + /* a o */ + ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + /* p z */ + 41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND +}; + +static const char b64Numerals[64+1] += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define BX_DV_PROTO(c) \ + ((((u8)(c))<0x80)? (u8)(b64DigitValues[(u8)(c)]) : 0x80) +#define IS_BX_DIGIT(bdp) (((u8)(bdp))<0x80) +#define IS_BX_WS(bdp) ((bdp)==WS) +#define IS_BX_PAD(bdp) ((bdp)==PC) +#define BX_NUMERAL(dv) (b64Numerals[(u8)(dv)]) +/* Width of base64 lines. Should be an integer multiple of 4. */ +#define B64_DARK_MAX 72 + +/* Encode a byte buffer into base64 text with linefeeds appended to limit +** encoded group lengths to B64_DARK_MAX or to terminate the last group. +*/ +static char* toBase64( u8 *pIn, int nbIn, char *pOut ){ + int nCol = 0; + while( nbIn >= 3 ){ + /* Do the bit-shuffle, exploiting unsigned input to avoid masking. */ + pOut[0] = BX_NUMERAL(pIn[0]>>2); + pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f); + pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6)); + pOut[3] = BX_NUMERAL(pIn[2]&0x3f); + pOut += 4; + nbIn -= 3; + pIn += 3; + if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){ + *pOut++ = '\n'; + nCol = 0; + } + } + if( nbIn > 0 ){ + signed char nco = nbIn+1; + int nbe; + unsigned long qv = *pIn++; + for( nbe=1; nbe<3; ++nbe ){ + qv <<= 8; + if( nbe<nbIn ) qv |= *pIn++; + } + for( nbe=3; nbe>=0; --nbe ){ + char ce = (nbe<nco)? BX_NUMERAL((u8)(qv & 0x3f)) : PAD_CHAR; + qv >>= 6; + pOut[nbe] = ce; + } + pOut += 4; + *pOut++ = '\n'; + } + *pOut = 0; + return pOut; +} + +/* Skip over text which is not base64 numeral(s). */ +static char * skipNonB64( char *s, int nc ){ + char c; + while( nc-- > 0 && (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s; + return s; +} + +/* Decode base64 text into a byte buffer. */ +static u8* fromBase64( char *pIn, int ncIn, u8 *pOut ){ + if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; + while( ncIn>0 && *pIn!=PAD_CHAR ){ + static signed char nboi[] = { 0, 0, 1, 2, 3 }; + char *pUse = skipNonB64(pIn, ncIn); + unsigned long qv = 0L; + int nti, nbo, nac; + ncIn -= (pUse - pIn); + pIn = pUse; + nti = (ncIn>4)? 4 : ncIn; + ncIn -= nti; + nbo = nboi[nti]; + if( nbo==0 ) break; + for( nac=0; nac<4; ++nac ){ + char c = (nac<nti)? *pIn++ : b64Numerals[0]; + u8 bdp = BX_DV_PROTO(c); + switch( bdp ){ + case ND: + /* Treat dark non-digits as pad, but they terminate decode too. */ + ncIn = 0; + deliberate_fall_through; + case WS: + /* Treat whitespace as pad and terminate this group.*/ + nti = nac; + deliberate_fall_through; + case PC: + bdp = 0; + --nbo; + deliberate_fall_through; + default: /* bdp is the digit value. */ + qv = qv<<6 | bdp; + break; + } + } + switch( nbo ){ + case 3: + pOut[2] = (qv) & 0xff; + case 2: + pOut[1] = (qv>>8) & 0xff; + case 1: + pOut[0] = (qv>>16) & 0xff; + } + pOut += nbo; + } + return pOut; +} + +/* This function does the work for the SQLite base64(x) UDF. */ +static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){ + int nb, nc, nv = sqlite3_value_bytes(av[0]); + int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), + SQLITE_LIMIT_LENGTH, -1); + char *cBuf; + u8 *bBuf; + assert(na==1); + switch( sqlite3_value_type(av[0]) ){ + case SQLITE_BLOB: + nb = nv; + nc = 4*(nv+2/3); /* quads needed */ + nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */ + if( nvMax < nc ){ + sqlite3_result_error(context, "blob expanded to base64 too big", -1); + return; + } + bBuf = (u8*)sqlite3_value_blob(av[0]); + if( !bBuf ){ + if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ + goto memFail; + } + sqlite3_result_text(context,"",-1,SQLITE_STATIC); + break; + } + cBuf = sqlite3_malloc(nc); + if( !cBuf ) goto memFail; + nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf); + sqlite3_result_text(context, cBuf, nc, sqlite3_free); + break; + case SQLITE_TEXT: + nc = nv; + nb = 3*((nv+3)/4); /* may overestimate due to LF and padding */ + if( nvMax < nb ){ + sqlite3_result_error(context, "blob from base64 may be too big", -1); + return; + }else if( nb<1 ){ + nb = 1; + } + cBuf = (char *)sqlite3_value_text(av[0]); + if( !cBuf ){ + if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ + goto memFail; + } + sqlite3_result_zeroblob(context, 0); + break; + } + bBuf = sqlite3_malloc(nb); + if( !bBuf ) goto memFail; + nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf); + sqlite3_result_blob(context, bBuf, nb, sqlite3_free); + break; + default: + sqlite3_result_error(context, "base64 accepts only blob or text", -1); + return; + } + return; + memFail: + sqlite3_result_error(context, "base64 OOM", -1); +} + +/* +** Establish linkage to running SQLite library. +*/ +#ifndef SQLITE_SHELL_EXTFUNCS +#ifdef _WIN32 + +#endif +int sqlite3_base_init +#else +static int sqlite3_base64_init +#endif +(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErr; + return sqlite3_create_function + (db, "base64", 1, + SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8, + 0, base64, 0, 0); +} + +/* +** Define some macros to allow this extension to be built into the shell +** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This +** allows shell.c, as distributed, to have this extension built in. +*/ +#define BASE64_INIT(db) sqlite3_base64_init(db, 0, 0) +#define BASE64_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */ + +/************************* End ../ext/misc/base64.c ********************/ +#undef sqlite3_base_init +#define sqlite3_base_init sqlite3_base85_init +#define OMIT_BASE85_CHECKER +/************************* Begin ../ext/misc/base85.c ******************/ +/* +** 2022-11-16 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This is a utility for converting binary to base85 or vice-versa. +** It can be built as a standalone program or an SQLite3 extension. +** +** Much like base64 representations, base85 can be sent through a +** sane USASCII channel unmolested. It also plays nicely in CSV or +** written as TCL brace-enclosed literals or SQL string literals. +** It is not suited for unmodified use in XML-like documents. +** +** The encoding used resembles Ascii85, but was devised by the author +** (Larry Brasfield) before Mozilla, Adobe, ZMODEM or other Ascii85 +** variant sources existed, in the 1984 timeframe on a VAX mainframe. +** Further, this is an independent implementation of a base85 system. +** Hence, the author has rightfully put this into the public domain. +** +** Base85 numerals are taken from the set of 7-bit USASCII codes, +** excluding control characters and Space ! " ' ( ) { | } ~ Del +** in code order representing digit values 0 to 84 (base 10.) +** +** Groups of 4 bytes, interpreted as big-endian 32-bit values, +** are represented as 5-digit base85 numbers with MS to LS digit +** order. Groups of 1-3 bytes are represented with 2-4 digits, +** still big-endian but 8-24 bit values. (Using big-endian yields +** the simplest transition to byte groups smaller than 4 bytes. +** These byte groups can also be considered base-256 numbers.) +** Groups of 0 bytes are represented with 0 digits and vice-versa. +** No pad characters are used; Encoded base85 numeral sequence +** (aka "group") length maps 1-to-1 to the decoded binary length. +** +** Any character not in the base85 numeral set delimits groups. +** When base85 is streamed or stored in containers of indefinite +** size, newline is used to separate it into sub-sequences of no +** more than 80 digits so that fgets() can be used to read it. +** +** Length limitations are not imposed except that the runtime +** SQLite string or blob length limits are respected. Otherwise, +** any length binary sequence can be represented and recovered. +** Base85 sequences can be concatenated by separating them with +** a non-base85 character; the conversion to binary will then +** be the concatenation of the represented binary sequences. + +** The standalone program either converts base85 on stdin to create +** a binary file or converts a binary file to base85 on stdout. +** Read or make it blurt its help for invocation details. +** +** The SQLite3 extension creates a function, base85(x), which will +** either convert text base85 to a blob or a blob to text base85 +** and return the result (or throw an error for other types.) +** Unless built with OMIT_BASE85_CHECKER defined, it also creates a +** function, is_base85(t), which returns 1 iff the text t contains +** nothing other than base85 numerals and whitespace, or 0 otherwise. +** +** To build the extension: +** Set shell variable SQDIR=<your favorite SQLite checkout directory> +** and variable OPTS to -DOMIT_BASE85_CHECKER if is_base85() unwanted. +** *Nix: gcc -O2 -shared -I$SQDIR $OPTS -fPIC -o base85.so base85.c +** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR $OPTS -o base85.dylib base85.c +** Win32: gcc -O2 -shared -I%SQDIR% %OPTS% -o base85.dll base85.c +** Win32: cl /Os -I%SQDIR% %OPTS% base85.c -link -dll -out:base85.dll +** +** To build the standalone program, define PP symbol BASE85_STANDALONE. Eg. +** *Nix or OSX: gcc -O2 -DBASE85_STANDALONE base85.c -o base85 +** Win32: gcc -O2 -DBASE85_STANDALONE -o base85.exe base85.c +** Win32: cl /Os /MD -DBASE85_STANDALONE base85.c +*/ + +#include <stdio.h> +#include <memory.h> +#include <string.h> +#include <assert.h> +#ifndef OMIT_BASE85_CHECKER +# include <ctype.h> +#endif + +#ifndef BASE85_STANDALONE + +/* # include "sqlite3ext.h" */ + +SQLITE_EXTENSION_INIT1; + +#else + +# ifdef _WIN32 +# include <io.h> +# include <fcntl.h> +# else +# define setmode(fd,m) +# endif + +static char *zHelp = + "Usage: base85 <dirFlag> <binFile>\n" + " <dirFlag> is either -r to read or -w to write <binFile>,\n" + " content to be converted to/from base85 on stdout/stdin.\n" + " <binFile> names a binary file to be rendered or created.\n" + " Or, the name '-' refers to the stdin or stdout stream.\n" + ; + +static void sayHelp(){ + printf("%s", zHelp); +} +#endif + +#ifndef U8_TYPEDEF +/* typedef unsigned char u8; */ +#define U8_TYPEDEF +#endif + +/* Classify c according to interval within USASCII set w.r.t. base85 + * Values of 1 and 3 are base85 numerals. Values of 0, 2, or 4 are not. + */ +#define B85_CLASS( c ) (((c)>='#')+((c)>'&')+((c)>='*')+((c)>'z')) + +/* Provide digitValue to b85Numeral offset as a function of above class. */ +static u8 b85_cOffset[] = { 0, '#', 0, '*'-4, 0 }; +#define B85_DNOS( c ) b85_cOffset[B85_CLASS(c)] + +/* Say whether c is a base85 numeral. */ +#define IS_B85( c ) (B85_CLASS(c) & 1) + +#if 0 /* Not used, */ +static u8 base85DigitValue( char c ){ + u8 dv = (u8)(c - '#'); + if( dv>87 ) return 0xff; + return (dv > 3)? dv-3 : dv; +} +#endif + +/* Width of base64 lines. Should be an integer multiple of 5. */ +#define B85_DARK_MAX 80 + + +static char * skipNonB85( char *s, int nc ){ + char c; + while( nc-- > 0 && (c = *s) && !IS_B85(c) ) ++s; + return s; +} + +/* Convert small integer, known to be in 0..84 inclusive, to base85 numeral. + * Do not use the macro form with argument expression having a side-effect.*/ +#if 0 +static char base85Numeral( u8 b ){ + return (b < 4)? (char)(b + '#') : (char)(b - 4 + '*'); +} +#else +# define base85Numeral( dn )\ + ((char)(((dn) < 4)? (char)((dn) + '#') : (char)((dn) - 4 + '*'))) +#endif + +static char *putcs(char *pc, char *s){ + char c; + while( (c = *s++)!=0 ) *pc++ = c; + return pc; +} + +/* Encode a byte buffer into base85 text. If pSep!=0, it's a C string +** to be appended to encoded groups to limit their length to B85_DARK_MAX +** or to terminate the last group (to aid concatenation.) +*/ +static char* toBase85( u8 *pIn, int nbIn, char *pOut, char *pSep ){ + int nCol = 0; + while( nbIn >= 4 ){ + int nco = 5; + unsigned long qbv = (((unsigned long)pIn[0])<<24) | + (pIn[1]<<16) | (pIn[2]<<8) | pIn[3]; + while( nco > 0 ){ + unsigned nqv = (unsigned)(qbv/85UL); + unsigned char dv = qbv - 85UL*nqv; + qbv = nqv; + pOut[--nco] = base85Numeral(dv); + } + nbIn -= 4; + pIn += 4; + pOut += 5; + if( pSep && (nCol += 5)>=B85_DARK_MAX ){ + pOut = putcs(pOut, pSep); + nCol = 0; + } + } + if( nbIn > 0 ){ + int nco = nbIn + 1; + unsigned long qv = *pIn++; + int nbe = 1; + while( nbe++ < nbIn ){ + qv = (qv<<8) | *pIn++; + } + nCol += nco; + while( nco > 0 ){ + u8 dv = (u8)(qv % 85); + qv /= 85; + pOut[--nco] = base85Numeral(dv); + } + pOut += (nbIn+1); + } + if( pSep && nCol>0 ) pOut = putcs(pOut, pSep); + *pOut = 0; + return pOut; +} + +/* Decode base85 text into a byte buffer. */ +static u8* fromBase85( char *pIn, int ncIn, u8 *pOut ){ + if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; + while( ncIn>0 ){ + static signed char nboi[] = { 0, 0, 1, 2, 3, 4 }; + char *pUse = skipNonB85(pIn, ncIn); + unsigned long qv = 0L; + int nti, nbo; + ncIn -= (pUse - pIn); + pIn = pUse; + nti = (ncIn>5)? 5 : ncIn; + nbo = nboi[nti]; + if( nbo==0 ) break; + while( nti>0 ){ + char c = *pIn++; + u8 cdo = B85_DNOS(c); + --ncIn; + if( cdo==0 ) break; + qv = 85 * qv + (c - cdo); + --nti; + } + nbo -= nti; /* Adjust for early (non-digit) end of group. */ + switch( nbo ){ + case 4: + *pOut++ = (qv >> 24)&0xff; + case 3: + *pOut++ = (qv >> 16)&0xff; + case 2: + *pOut++ = (qv >> 8)&0xff; + case 1: + *pOut++ = qv&0xff; + case 0: + break; + } + } + return pOut; +} + +#ifndef OMIT_BASE85_CHECKER +/* Say whether input char sequence is all (base85 and/or whitespace).*/ +static int allBase85( char *p, int len ){ + char c; + while( len-- > 0 && (c = *p++) != 0 ){ + if( !IS_B85(c) && !isspace(c) ) return 0; + } + return 1; +} +#endif + +#ifndef BASE85_STANDALONE + +# ifndef OMIT_BASE85_CHECKER +/* This function does the work for the SQLite is_base85(t) UDF. */ +static void is_base85(sqlite3_context *context, int na, sqlite3_value *av[]){ + assert(na==1); + switch( sqlite3_value_type(av[0]) ){ + case SQLITE_TEXT: + { + int rv = allBase85( (char *)sqlite3_value_text(av[0]), + sqlite3_value_bytes(av[0]) ); + sqlite3_result_int(context, rv); + } + break; + case SQLITE_NULL: + sqlite3_result_null(context); + break; + default: + sqlite3_result_error(context, "is_base85 accepts only text or NULL", -1); + return; + } +} +# endif + +/* This function does the work for the SQLite base85(x) UDF. */ +static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){ + int nb, nc, nv = sqlite3_value_bytes(av[0]); + int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), + SQLITE_LIMIT_LENGTH, -1); + char *cBuf; + u8 *bBuf; + assert(na==1); + switch( sqlite3_value_type(av[0]) ){ + case SQLITE_BLOB: + nb = nv; + /* ulongs tail newlines tailenc+nul*/ + nc = 5*(nv/4) + nv%4 + nv/64+1 + 2; + if( nvMax < nc ){ + sqlite3_result_error(context, "blob expanded to base85 too big", -1); + return; + } + bBuf = (u8*)sqlite3_value_blob(av[0]); + if( !bBuf ){ + if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ + goto memFail; + } + sqlite3_result_text(context,"",-1,SQLITE_STATIC); + break; + } + cBuf = sqlite3_malloc(nc); + if( !cBuf ) goto memFail; + nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf); + sqlite3_result_text(context, cBuf, nc, sqlite3_free); + break; + case SQLITE_TEXT: + nc = nv; + nb = 4*(nv/5) + nv%5; /* may overestimate */ + if( nvMax < nb ){ + sqlite3_result_error(context, "blob from base85 may be too big", -1); + return; + }else if( nb<1 ){ + nb = 1; + } + cBuf = (char *)sqlite3_value_text(av[0]); + if( !cBuf ){ + if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ + goto memFail; + } + sqlite3_result_zeroblob(context, 0); + break; + } + bBuf = sqlite3_malloc(nb); + if( !bBuf ) goto memFail; + nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf); + sqlite3_result_blob(context, bBuf, nb, sqlite3_free); + break; + default: + sqlite3_result_error(context, "base85 accepts only blob or text.", -1); + return; + } + return; + memFail: + sqlite3_result_error(context, "base85 OOM", -1); +} + +/* +** Establish linkage to running SQLite library. +*/ +#ifndef SQLITE_SHELL_EXTFUNCS +#ifdef _WIN32 + +#endif +int sqlite3_base_init +#else +static int sqlite3_base85_init +#endif +(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErr; +# ifndef OMIT_BASE85_CHECKER + { + int rc = sqlite3_create_function + (db, "is_base85", 1, + SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_UTF8, + 0, is_base85, 0, 0); + if( rc!=SQLITE_OK ) return rc; + } +# endif + return sqlite3_create_function + (db, "base85", 1, + SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8, + 0, base85, 0, 0); +} + +/* +** Define some macros to allow this extension to be built into the shell +** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This +** allows shell.c, as distributed, to have this extension built in. +*/ +# define BASE85_INIT(db) sqlite3_base85_init(db, 0, 0) +# define BASE85_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */ + +#else /* standalone program */ + +int main(int na, char *av[]){ + int cin; + int rc = 0; + u8 bBuf[4*(B85_DARK_MAX/5)]; + char cBuf[5*(sizeof(bBuf)/4)+2]; + size_t nio; +# ifndef OMIT_BASE85_CHECKER + int b85Clean = 1; +# endif + char rw; + FILE *fb = 0, *foc = 0; + char fmode[3] = "xb"; + if( na < 3 || av[1][0]!='-' || (rw = av[1][1])==0 || (rw!='r' && rw!='w') ){ + sayHelp(); + return 0; + } + fmode[0] = rw; + if( av[2][0]=='-' && av[2][1]==0 ){ + switch( rw ){ + case 'r': + fb = stdin; + setmode(fileno(stdin), O_BINARY); + break; + case 'w': + fb = stdout; + setmode(fileno(stdout), O_BINARY); + break; + } + }else{ + fb = fopen(av[2], fmode); + foc = fb; + } + if( !fb ){ + fprintf(stderr, "Cannot open %s for %c\n", av[2], rw); + rc = 1; + }else{ + switch( rw ){ + case 'r': + while( (nio = fread( bBuf, 1, sizeof(bBuf), fb))>0 ){ + toBase85( bBuf, (int)nio, cBuf, 0 ); + fprintf(stdout, "%s\n", cBuf); + } + break; + case 'w': + while( 0 != fgets(cBuf, sizeof(cBuf), stdin) ){ + int nc = strlen(cBuf); + size_t nbo = fromBase85( cBuf, nc, bBuf ) - bBuf; + if( 1 != fwrite(bBuf, nbo, 1, fb) ) rc = 1; +# ifndef OMIT_BASE85_CHECKER + b85Clean &= allBase85( cBuf, nc ); +# endif + } + break; + default: + sayHelp(); + rc = 1; + } + if( foc ) fclose(foc); + } +# ifndef OMIT_BASE85_CHECKER + if( !b85Clean ){ + fprintf(stderr, "Base85 input had non-base85 dark or control content.\n"); + } +# endif + return rc; +} + +#endif + +/************************* End ../ext/misc/base85.c ********************/ +/************************* Begin ../ext/misc/ieee754.c ******************/ +/* +** 2013-04-17 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements functions for the exact display +** and input of IEEE754 Binary64 floating-point numbers. +** +** ieee754(X) +** ieee754(Y,Z) +** +** In the first form, the value X should be a floating-point number. +** The function will return a string of the form 'ieee754(Y,Z)' where +** Y and Z are integers such that X==Y*pow(2,Z). +** +** In the second form, Y and Z are integers which are the mantissa and +** base-2 exponent of a new floating point number. The function returns +** a floating-point value equal to Y*pow(2,Z). +** +** Examples: +** +** ieee754(2.0) -> 'ieee754(2,0)' +** ieee754(45.25) -> 'ieee754(181,-2)' +** ieee754(2, 0) -> 2.0 +** ieee754(181, -2) -> 45.25 +** +** Two additional functions break apart the one-argument ieee754() +** result into separate integer values: +** +** ieee754_mantissa(45.25) -> 181 +** ieee754_exponent(45.25) -> -2 +** +** These functions convert binary64 numbers into blobs and back again. +** +** ieee754_from_blob(x'3ff0000000000000') -> 1.0 +** ieee754_to_blob(1.0) -> x'3ff0000000000000' +** +** In all single-argument functions, if the argument is an 8-byte blob +** then that blob is interpreted as a big-endian binary64 value. +** +** +** EXACT DECIMAL REPRESENTATION OF BINARY64 VALUES +** ----------------------------------------------- +** +** This extension in combination with the separate 'decimal' extension +** can be used to compute the exact decimal representation of binary64 +** values. To begin, first compute a table of exponent values: +** +** CREATE TABLE pow2(x INTEGER PRIMARY KEY, v TEXT); +** WITH RECURSIVE c(x,v) AS ( +** VALUES(0,'1') +** UNION ALL +** SELECT x+1, decimal_mul(v,'2') FROM c WHERE x+1<=971 +** ) INSERT INTO pow2(x,v) SELECT x, v FROM c; +** WITH RECURSIVE c(x,v) AS ( +** VALUES(-1,'0.5') +** UNION ALL +** SELECT x-1, decimal_mul(v,'0.5') FROM c WHERE x-1>=-1075 +** ) INSERT INTO pow2(x,v) SELECT x, v FROM c; +** +** Then, to compute the exact decimal representation of a floating +** point value (the value 47.49 is used in the example) do: +** +** WITH c(n) AS (VALUES(47.49)) +** ---------------^^^^^---- Replace with whatever you want +** SELECT decimal_mul(ieee754_mantissa(c.n),pow2.v) +** FROM pow2, c WHERE pow2.x=ieee754_exponent(c.n); +** +** Here is a query to show various boundry values for the binary64 +** number format: +** +** WITH c(name,bin) AS (VALUES +** ('minimum positive value', x'0000000000000001'), +** ('maximum subnormal value', x'000fffffffffffff'), +** ('mininum positive nornal value', x'0010000000000000'), +** ('maximum value', x'7fefffffffffffff')) +** SELECT c.name, decimal_mul(ieee754_mantissa(c.bin),pow2.v) +** FROM pow2, c WHERE pow2.x=ieee754_exponent(c.bin); +** +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include <assert.h> +#include <string.h> + +/* Mark a function parameter as unused, to suppress nuisance compiler +** warnings. */ +#ifndef UNUSED_PARAMETER +# define UNUSED_PARAMETER(X) (void)(X) +#endif + +/* +** Implementation of the ieee754() function +*/ +static void ieee754func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + if( argc==1 ){ + sqlite3_int64 m, a; + double r; + int e; + int isNeg; + char zResult[100]; + assert( sizeof(m)==sizeof(r) ); + if( sqlite3_value_type(argv[0])==SQLITE_BLOB + && sqlite3_value_bytes(argv[0])==sizeof(r) + ){ + const unsigned char *x = sqlite3_value_blob(argv[0]); + unsigned int i; + sqlite3_uint64 v = 0; + for(i=0; i<sizeof(r); i++){ + v = (v<<8) | x[i]; + } + memcpy(&r, &v, sizeof(r)); + }else{ + r = sqlite3_value_double(argv[0]); + } + if( r<0.0 ){ + isNeg = 1; + r = -r; + }else{ + isNeg = 0; + } + memcpy(&a,&r,sizeof(a)); + if( a==0 ){ + e = 0; + m = 0; + }else{ + e = a>>52; + m = a & ((((sqlite3_int64)1)<<52)-1); + if( e==0 ){ + m <<= 1; + }else{ + m |= ((sqlite3_int64)1)<<52; + } + while( e<1075 && m>0 && (m&1)==0 ){ + m >>= 1; + e++; + } + if( isNeg ) m = -m; + } + switch( *(int*)sqlite3_user_data(context) ){ + case 0: + sqlite3_snprintf(sizeof(zResult), zResult, "ieee754(%lld,%d)", + m, e-1075); + sqlite3_result_text(context, zResult, -1, SQLITE_TRANSIENT); + break; + case 1: + sqlite3_result_int64(context, m); + break; + case 2: + sqlite3_result_int(context, e-1075); + break; + } + }else{ + sqlite3_int64 m, e, a; + double r; + int isNeg = 0; + m = sqlite3_value_int64(argv[0]); + e = sqlite3_value_int64(argv[1]); + + /* Limit the range of e. Ticket 22dea1cfdb9151e4 2021-03-02 */ + if( e>10000 ){ + e = 10000; + }else if( e<-10000 ){ + e = -10000; + } + + if( m<0 ){ + isNeg = 1; + m = -m; + if( m<0 ) return; + }else if( m==0 && e>-1000 && e<1000 ){ + sqlite3_result_double(context, 0.0); + return; + } + while( (m>>32)&0xffe00000 ){ + m >>= 1; + e++; + } + while( m!=0 && ((m>>32)&0xfff00000)==0 ){ + m <<= 1; + e--; + } + e += 1075; + if( e<=0 ){ + /* Subnormal */ + if( 1-e >= 64 ){ + m = 0; + }else{ + m >>= 1-e; + } + e = 0; + }else if( e>0x7ff ){ + e = 0x7ff; + } + a = m & ((((sqlite3_int64)1)<<52)-1); + a |= e<<52; + if( isNeg ) a |= ((sqlite3_uint64)1)<<63; + memcpy(&r, &a, sizeof(r)); + sqlite3_result_double(context, r); + } +} + +/* +** Functions to convert between blobs and floats. +*/ +static void ieee754func_from_blob( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_BLOB + && sqlite3_value_bytes(argv[0])==sizeof(double) + ){ + double r; + const unsigned char *x = sqlite3_value_blob(argv[0]); + unsigned int i; + sqlite3_uint64 v = 0; + for(i=0; i<sizeof(r); i++){ + v = (v<<8) | x[i]; + } + memcpy(&r, &v, sizeof(r)); + sqlite3_result_double(context, r); + } +} +static void ieee754func_to_blob( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_FLOAT + || sqlite3_value_type(argv[0])==SQLITE_INTEGER + ){ + double r = sqlite3_value_double(argv[0]); + sqlite3_uint64 v; + unsigned char a[sizeof(r)]; + unsigned int i; + memcpy(&v, &r, sizeof(r)); + for(i=1; i<=sizeof(r); i++){ + a[sizeof(r)-i] = v&0xff; + v >>= 8; + } + sqlite3_result_blob(context, a, sizeof(r), SQLITE_TRANSIENT); + } +} + +/* +** SQL Function: ieee754_inc(r,N) +** +** Move the floating point value r by N quantums and return the new +** values. +** +** Behind the scenes: this routine merely casts r into a 64-bit unsigned +** integer, adds N, then casts the value back into float. +** +** Example: To find the smallest positive number: +** +** SELECT ieee754_inc(0.0,+1); +*/ +static void ieee754inc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + double r; + sqlite3_int64 N; + sqlite3_uint64 m1, m2; + double r2; + UNUSED_PARAMETER(argc); + r = sqlite3_value_double(argv[0]); + N = sqlite3_value_int64(argv[1]); + memcpy(&m1, &r, 8); + m2 = m1 + N; + memcpy(&r2, &m2, 8); + sqlite3_result_double(context, r2); +} + + +#ifdef _WIN32 + +#endif +int sqlite3_ieee_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + static const struct { + char *zFName; + int nArg; + int iAux; + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } aFunc[] = { + { "ieee754", 1, 0, ieee754func }, + { "ieee754", 2, 0, ieee754func }, + { "ieee754_mantissa", 1, 1, ieee754func }, + { "ieee754_exponent", 1, 2, ieee754func }, + { "ieee754_to_blob", 1, 0, ieee754func_to_blob }, + { "ieee754_from_blob", 1, 0, ieee754func_from_blob }, + { "ieee754_inc", 2, 0, ieee754inc }, + }; + unsigned int i; + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){ + rc = sqlite3_create_function(db, aFunc[i].zFName, aFunc[i].nArg, + SQLITE_UTF8|SQLITE_INNOCUOUS, + (void*)&aFunc[i].iAux, + aFunc[i].xFunc, 0, 0); + } + return rc; +} + +/************************* End ../ext/misc/ieee754.c ********************/ +/************************* Begin ../ext/misc/series.c ******************/ +/* +** 2015-08-18, 2023-04-28 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file demonstrates how to create a table-valued-function using +** a virtual table. This demo implements the generate_series() function +** which gives the same results as the eponymous function in PostgreSQL, +** within the limitation that its arguments are signed 64-bit integers. +** +** Considering its equivalents to generate_series(start,stop,step): A +** value V[n] sequence is produced for integer n ascending from 0 where +** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 ) +** for each produced value (independent of production time ordering.) +** +** All parameters must be either integer or convertable to integer. +** The start parameter is required. +** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff) +** The step parameter defaults to 1 and 0 is treated as 1. +** +** Examples: +** +** SELECT * FROM generate_series(0,100,5); +** +** The query above returns integers from 0 through 100 counting by steps +** of 5. +** +** SELECT * FROM generate_series(0,100); +** +** Integers from 0 through 100 with a step size of 1. +** +** SELECT * FROM generate_series(20) LIMIT 10; +** +** Integers 20 through 29. +** +** SELECT * FROM generate_series(0,-100,-5); +** +** Integers 0 -5 -10 ... -100. +** +** SELECT * FROM generate_series(0,-1); +** +** Empty sequence. +** +** HOW IT WORKS +** +** The generate_series "function" is really a virtual table with the +** following schema: +** +** CREATE TABLE generate_series( +** value, +** start HIDDEN, +** stop HIDDEN, +** step HIDDEN +** ); +** +** The virtual table also has a rowid, logically equivalent to n+1 where +** "n" is the ascending integer in the aforesaid production definition. +** +** Function arguments in queries against this virtual table are translated +** into equality constraints against successive hidden columns. In other +** words, the following pairs of queries are equivalent to each other: +** +** SELECT * FROM generate_series(0,100,5); +** SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5; +** +** SELECT * FROM generate_series(0,100); +** SELECT * FROM generate_series WHERE start=0 AND stop=100; +** +** SELECT * FROM generate_series(20) LIMIT 10; +** SELECT * FROM generate_series WHERE start=20 LIMIT 10; +** +** The generate_series virtual table implementation leaves the xCreate method +** set to NULL. This means that it is not possible to do a CREATE VIRTUAL +** TABLE command with "generate_series" as the USING argument. Instead, there +** is a single generate_series virtual table that is always available without +** having to be created first. +** +** The xBestIndex method looks for equality constraints against the hidden +** start, stop, and step columns, and if present, it uses those constraints +** to bound the sequence of generated values. If the equality constraints +** are missing, it uses 0 for start, 4294967295 for stop, and 1 for step. +** xBestIndex returns a small cost when both start and stop are available, +** and a very large cost if either start or stop are unavailable. This +** encourages the query planner to order joins such that the bounds of the +** series are well-defined. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include <assert.h> +#include <string.h> +#include <limits.h> + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Return that member of a generate_series(...) sequence whose 0-based +** index is ix. The 0th member is given by smBase. The sequence members +** progress per ix increment by smStep. +*/ +static sqlite3_int64 genSeqMember(sqlite3_int64 smBase, + sqlite3_int64 smStep, + sqlite3_uint64 ix){ + if( ix>=(sqlite3_uint64)LLONG_MAX ){ + /* Get ix into signed i64 range. */ + ix -= (sqlite3_uint64)LLONG_MAX; + /* With 2's complement ALU, this next can be 1 step, but is split into + * 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */ + smBase += (LLONG_MAX/2) * smStep; + smBase += (LLONG_MAX - LLONG_MAX/2) * smStep; + } + /* Under UBSAN (or on 1's complement machines), must do this last term + * in steps to avoid the dreaded (and harmless) signed multiply overlow. */ + if( ix>=2 ){ + sqlite3_int64 ix2 = (sqlite3_int64)ix/2; + smBase += ix2*smStep; + ix -= ix2; + } + return smBase + ((sqlite3_int64)ix)*smStep; +} + +/* typedef unsigned char u8; */ + +typedef struct SequenceSpec { + sqlite3_int64 iBase; /* Starting value ("start") */ + sqlite3_int64 iTerm; /* Given terminal value ("stop") */ + sqlite3_int64 iStep; /* Increment ("step") */ + sqlite3_uint64 uSeqIndexMax; /* maximum sequence index (aka "n") */ + sqlite3_uint64 uSeqIndexNow; /* Current index during generation */ + sqlite3_int64 iValueNow; /* Current value during generation */ + u8 isNotEOF; /* Sequence generation not exhausted */ + u8 isReversing; /* Sequence is being reverse generated */ +} SequenceSpec; + +/* +** Prepare a SequenceSpec for use in generating an integer series +** given initialized iBase, iTerm and iStep values. Sequence is +** initialized per given isReversing. Other members are computed. +*/ +static void setupSequence( SequenceSpec *pss ){ + int bSameSigns; + pss->uSeqIndexMax = 0; + pss->isNotEOF = 0; + bSameSigns = (pss->iBase < 0)==(pss->iTerm < 0); + if( pss->iTerm < pss->iBase ){ + sqlite3_uint64 nuspan = 0; + if( bSameSigns ){ + nuspan = (sqlite3_uint64)(pss->iBase - pss->iTerm); + }else{ + /* Under UBSAN (or on 1's complement machines), must do this in steps. + * In this clause, iBase>=0 and iTerm<0 . */ + nuspan = 1; + nuspan += pss->iBase; + nuspan += -(pss->iTerm+1); + } + if( pss->iStep<0 ){ + pss->isNotEOF = 1; + if( nuspan==ULONG_MAX ){ + pss->uSeqIndexMax = ( pss->iStep>LLONG_MIN )? nuspan/-pss->iStep : 1; + }else if( pss->iStep>LLONG_MIN ){ + pss->uSeqIndexMax = nuspan/-pss->iStep; + } + } + }else if( pss->iTerm > pss->iBase ){ + sqlite3_uint64 puspan = 0; + if( bSameSigns ){ + puspan = (sqlite3_uint64)(pss->iTerm - pss->iBase); + }else{ + /* Under UBSAN (or on 1's complement machines), must do this in steps. + * In this clause, iTerm>=0 and iBase<0 . */ + puspan = 1; + puspan += pss->iTerm; + puspan += -(pss->iBase+1); + } + if( pss->iStep>0 ){ + pss->isNotEOF = 1; + pss->uSeqIndexMax = puspan/pss->iStep; + } + }else if( pss->iTerm == pss->iBase ){ + pss->isNotEOF = 1; + pss->uSeqIndexMax = 0; + } + pss->uSeqIndexNow = (pss->isReversing)? pss->uSeqIndexMax : 0; + pss->iValueNow = (pss->isReversing) + ? genSeqMember(pss->iBase, pss->iStep, pss->uSeqIndexMax) + : pss->iBase; +} + +/* +** Progress sequence generator to yield next value, if any. +** Leave its state to either yield next value or be at EOF. +** Return whether there is a next value, or 0 at EOF. +*/ +static int progressSequence( SequenceSpec *pss ){ + if( !pss->isNotEOF ) return 0; + if( pss->isReversing ){ + if( pss->uSeqIndexNow > 0 ){ + pss->uSeqIndexNow--; + pss->iValueNow -= pss->iStep; + }else{ + pss->isNotEOF = 0; + } + }else{ + if( pss->uSeqIndexNow < pss->uSeqIndexMax ){ + pss->uSeqIndexNow++; + pss->iValueNow += pss->iStep; + }else{ + pss->isNotEOF = 0; + } + } + return pss->isNotEOF; +} + +/* series_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct series_cursor series_cursor; +struct series_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + SequenceSpec ss; /* (this) Derived class data */ +}; + +/* +** The seriesConnect() method is invoked to create a new +** series_vtab that describes the generate_series virtual table. +** +** Think of this routine as the constructor for series_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the series_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against generate_series will look like. +*/ +static int seriesConnect( + sqlite3 *db, + void *pUnused, + int argcUnused, const char *const*argvUnused, + sqlite3_vtab **ppVtab, + char **pzErrUnused +){ + sqlite3_vtab *pNew; + int rc; + +/* Column numbers */ +#define SERIES_COLUMN_VALUE 0 +#define SERIES_COLUMN_START 1 +#define SERIES_COLUMN_STOP 2 +#define SERIES_COLUMN_STEP 3 + + (void)pUnused; + (void)argcUnused; + (void)argvUnused; + (void)pzErrUnused; + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(value,start hidden,stop hidden,step hidden)"); + if( rc==SQLITE_OK ){ + pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + } + return rc; +} + +/* +** This method is the destructor for series_cursor objects. +*/ +static int seriesDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new series_cursor object. +*/ +static int seriesOpen(sqlite3_vtab *pUnused, sqlite3_vtab_cursor **ppCursor){ + series_cursor *pCur; + (void)pUnused; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Destructor for a series_cursor. +*/ +static int seriesClose(sqlite3_vtab_cursor *cur){ + sqlite3_free(cur); + return SQLITE_OK; +} + + +/* +** Advance a series_cursor to its next row of output. +*/ +static int seriesNext(sqlite3_vtab_cursor *cur){ + series_cursor *pCur = (series_cursor*)cur; + progressSequence( & pCur->ss ); + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the series_cursor +** is currently pointing. +*/ +static int seriesColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + series_cursor *pCur = (series_cursor*)cur; + sqlite3_int64 x = 0; + switch( i ){ + case SERIES_COLUMN_START: x = pCur->ss.iBase; break; + case SERIES_COLUMN_STOP: x = pCur->ss.iTerm; break; + case SERIES_COLUMN_STEP: x = pCur->ss.iStep; break; + default: x = pCur->ss.iValueNow; break; + } + sqlite3_result_int64(ctx, x); + return SQLITE_OK; +} + +#ifndef LARGEST_UINT64 +#define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32)) +#endif + +/* +** Return the rowid for the current row, logically equivalent to n+1 where +** "n" is the ascending integer in the aforesaid production definition. +*/ +static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + series_cursor *pCur = (series_cursor*)cur; + sqlite3_uint64 n = pCur->ss.uSeqIndexNow; + *pRowid = (sqlite3_int64)((n<LARGEST_UINT64)? n+1 : 0); + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int seriesEof(sqlite3_vtab_cursor *cur){ + series_cursor *pCur = (series_cursor*)cur; + return !pCur->ss.isNotEOF; +} + +/* True to cause run-time checking of the start=, stop=, and/or step= +** parameters. The only reason to do this is for testing the +** constraint checking logic for virtual tables in the SQLite core. +*/ +#ifndef SQLITE_SERIES_CONSTRAINT_VERIFY +# define SQLITE_SERIES_CONSTRAINT_VERIFY 0 +#endif + +/* +** This method is called to "rewind" the series_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to seriesColumn() or seriesRowid() or +** seriesEof(). +** +** The query plan selected by seriesBestIndex is passed in the idxNum +** parameter. (idxStr is not used in this implementation.) idxNum +** is a bitmask showing which constraints are available: +** +** 1: start=VALUE +** 2: stop=VALUE +** 4: step=VALUE +** +** Also, if bit 8 is set, that means that the series should be output +** in descending order rather than in ascending order. If bit 16 is +** set, then output must appear in ascending order. +** +** This routine should initialize the cursor and position it so that it +** is pointing at the first row, or pointing off the end of the table +** (so that seriesEof() will return true) if the table is empty. +*/ +static int seriesFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStrUnused, + int argc, sqlite3_value **argv +){ + series_cursor *pCur = (series_cursor *)pVtabCursor; + int i = 0; + (void)idxStrUnused; + if( idxNum & 1 ){ + pCur->ss.iBase = sqlite3_value_int64(argv[i++]); + }else{ + pCur->ss.iBase = 0; + } + if( idxNum & 2 ){ + pCur->ss.iTerm = sqlite3_value_int64(argv[i++]); + }else{ + pCur->ss.iTerm = 0xffffffff; + } + if( idxNum & 4 ){ + pCur->ss.iStep = sqlite3_value_int64(argv[i++]); + if( pCur->ss.iStep==0 ){ + pCur->ss.iStep = 1; + }else if( pCur->ss.iStep<0 ){ + if( (idxNum & 16)==0 ) idxNum |= 8; + } + }else{ + pCur->ss.iStep = 1; + } + for(i=0; i<argc; i++){ + if( sqlite3_value_type(argv[i])==SQLITE_NULL ){ + /* If any of the constraints have a NULL value, then return no rows. + ** See ticket https://www.sqlite.org/src/info/fac496b61722daf2 */ + pCur->ss.iBase = 1; + pCur->ss.iTerm = 0; + pCur->ss.iStep = 1; + break; + } + } + if( idxNum & 8 ){ + pCur->ss.isReversing = pCur->ss.iStep > 0; + }else{ + pCur->ss.isReversing = pCur->ss.iStep < 0; + } + setupSequence( &pCur->ss ); + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the generate_series virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** In this implementation idxNum is used to represent the +** query plan. idxStr is unused. +** +** The query plan is represented by bits in idxNum: +** +** (1) start = $value -- constraint exists +** (2) stop = $value -- constraint exists +** (4) step = $value -- constraint exists +** (8) output in descending order +*/ +static int seriesBestIndex( + sqlite3_vtab *pVTab, + sqlite3_index_info *pIdxInfo +){ + int i, j; /* Loop over constraints */ + int idxNum = 0; /* The query plan bitmask */ + int bStartSeen = 0; /* EQ constraint seen on the START column */ + int unusableMask = 0; /* Mask of unusable constraints */ + int nArg = 0; /* Number of arguments that seriesFilter() expects */ + int aIdx[3]; /* Constraints on start, stop, and step */ + const struct sqlite3_index_constraint *pConstraint; + + /* This implementation assumes that the start, stop, and step columns + ** are the last three columns in the virtual table. */ + assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 ); + assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 ); + + aIdx[0] = aIdx[1] = aIdx[2] = -1; + pConstraint = pIdxInfo->aConstraint; + for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ + int iCol; /* 0 for start, 1 for stop, 2 for step */ + int iMask; /* bitmask for those column */ + if( pConstraint->iColumn<SERIES_COLUMN_START ) continue; + iCol = pConstraint->iColumn - SERIES_COLUMN_START; + assert( iCol>=0 && iCol<=2 ); + iMask = 1 << iCol; + if( iCol==0 ) bStartSeen = 1; + if( pConstraint->usable==0 ){ + unusableMask |= iMask; + continue; + }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + idxNum |= iMask; + aIdx[iCol] = i; + } + } + for(i=0; i<3; i++){ + if( (j = aIdx[i])>=0 ){ + pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[j].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; + } + } + /* The current generate_column() implementation requires at least one + ** argument (the START value). Legacy versions assumed START=0 if the + ** first argument was omitted. Compile with -DZERO_ARGUMENT_GENERATE_SERIES + ** to obtain the legacy behavior */ +#ifndef ZERO_ARGUMENT_GENERATE_SERIES + if( !bStartSeen ){ + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = sqlite3_mprintf( + "first argument to \"generate_series()\" missing or unusable"); + return SQLITE_ERROR; + } +#endif + if( (unusableMask & ~idxNum)!=0 ){ + /* The start, stop, and step columns are inputs. Therefore if there + ** are unusable constraints on any of start, stop, or step then + ** this plan is unusable */ + return SQLITE_CONSTRAINT; + } + if( (idxNum & 3)==3 ){ + /* Both start= and stop= boundaries are available. This is the + ** the preferred case */ + pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0)); + pIdxInfo->estimatedRows = 1000; + if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){ + if( pIdxInfo->aOrderBy[0].desc ){ + idxNum |= 8; + }else{ + idxNum |= 16; + } + pIdxInfo->orderByConsumed = 1; + } + }else{ + /* If either boundary is missing, we have to generate a huge span + ** of numbers. Make this case very expensive so that the query + ** planner will work hard to avoid it. */ + pIdxInfo->estimatedRows = 2147483647; + } + pIdxInfo->idxNum = idxNum; + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** generate_series virtual table. +*/ +static sqlite3_module seriesModule = { + 0, /* iVersion */ + 0, /* xCreate */ + seriesConnect, /* xConnect */ + seriesBestIndex, /* xBestIndex */ + seriesDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + seriesOpen, /* xOpen - open a cursor */ + seriesClose, /* xClose - close a cursor */ + seriesFilter, /* xFilter - configure scan constraints */ + seriesNext, /* xNext - advance a cursor */ + seriesEof, /* xEof - check for end of scan */ + seriesColumn, /* xColumn - read data */ + seriesRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ +}; + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifdef _WIN32 + +#endif +int sqlite3_series_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( sqlite3_libversion_number()<3008012 && pzErrMsg!=0 ){ + *pzErrMsg = sqlite3_mprintf( + "generate_series() requires SQLite 3.8.12 or later"); + return SQLITE_ERROR; + } + rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0); +#endif + return rc; +} + +/************************* End ../ext/misc/series.c ********************/ +/************************* Begin ../ext/misc/regexp.c ******************/ +/* +** 2012-11-13 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** The code in this file implements a compact but reasonably +** efficient regular-expression matcher for posix extended regular +** expressions against UTF8 text. +** +** This file is an SQLite extension. It registers a single function +** named "regexp(A,B)" where A is the regular expression and B is the +** string to be matched. By registering this function, SQLite will also +** then implement the "B regexp A" operator. Note that with the function +** the regular expression comes first, but with the operator it comes +** second. +** +** The following regular expression syntax is supported: +** +** X* zero or more occurrences of X +** X+ one or more occurrences of X +** X? zero or one occurrences of X +** X{p,q} between p and q occurrences of X +** (X) match X +** X|Y X or Y +** ^X X occurring at the beginning of the string +** X$ X occurring at the end of the string +** . Match any single character +** \c Character c where c is one of \{}()[]|*+?. +** \c C-language escapes for c in afnrtv. ex: \t or \n +** \uXXXX Where XXXX is exactly 4 hex digits, unicode value XXXX +** \xXX Where XX is exactly 2 hex digits, unicode value XX +** [abc] Any single character from the set abc +** [^abc] Any single character not in the set abc +** [a-z] Any single character in the range a-z +** [^a-z] Any single character not in the range a-z +** \b Word boundary +** \w Word character. [A-Za-z0-9_] +** \W Non-word character +** \d Digit +** \D Non-digit +** \s Whitespace character +** \S Non-whitespace character +** +** A nondeterministic finite automaton (NFA) is used for matching, so the +** performance is bounded by O(N*M) where N is the size of the regular +** expression and M is the size of the input string. The matcher never +** exhibits exponential behavior. Note that the X{p,q} operator expands +** to p copies of X following by q-p copies of X? and that the size of the +** regular expression in the O(N*M) performance bound is computed after +** this expansion. +*/ +#include <string.h> +#include <stdlib.h> +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 + +/* +** The following #defines change the names of some functions implemented in +** this file to prevent name collisions with C-library functions of the +** same name. +*/ +#define re_match sqlite3re_match +#define re_compile sqlite3re_compile +#define re_free sqlite3re_free + +/* The end-of-input character */ +#define RE_EOF 0 /* End of input */ +#define RE_START 0xfffffff /* Start of input - larger than an UTF-8 */ + +/* The NFA is implemented as sequence of opcodes taken from the following +** set. Each opcode has a single integer argument. +*/ +#define RE_OP_MATCH 1 /* Match the one character in the argument */ +#define RE_OP_ANY 2 /* Match any one character. (Implements ".") */ +#define RE_OP_ANYSTAR 3 /* Special optimized version of .* */ +#define RE_OP_FORK 4 /* Continue to both next and opcode at iArg */ +#define RE_OP_GOTO 5 /* Jump to opcode at iArg */ +#define RE_OP_ACCEPT 6 /* Halt and indicate a successful match */ +#define RE_OP_CC_INC 7 /* Beginning of a [...] character class */ +#define RE_OP_CC_EXC 8 /* Beginning of a [^...] character class */ +#define RE_OP_CC_VALUE 9 /* Single value in a character class */ +#define RE_OP_CC_RANGE 10 /* Range of values in a character class */ +#define RE_OP_WORD 11 /* Perl word character [A-Za-z0-9_] */ +#define RE_OP_NOTWORD 12 /* Not a perl word character */ +#define RE_OP_DIGIT 13 /* digit: [0-9] */ +#define RE_OP_NOTDIGIT 14 /* Not a digit */ +#define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ +#define RE_OP_NOTSPACE 16 /* Not a digit */ +#define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ +#define RE_OP_ATSTART 18 /* Currently at the start of the string */ + +#if defined(SQLITE_DEBUG) +/* Opcode names used for symbolic debugging */ +static const char *ReOpName[] = { + "EOF", + "MATCH", + "ANY", + "ANYSTAR", + "FORK", + "GOTO", + "ACCEPT", + "CC_INC", + "CC_EXC", + "CC_VALUE", + "CC_RANGE", + "WORD", + "NOTWORD", + "DIGIT", + "NOTDIGIT", + "SPACE", + "NOTSPACE", + "BOUNDARY", + "ATSTART", +}; +#endif /* SQLITE_DEBUG */ + + +/* Each opcode is a "state" in the NFA */ +typedef unsigned short ReStateNumber; + +/* Because this is an NFA and not a DFA, multiple states can be active at +** once. An instance of the following object records all active states in +** the NFA. The implementation is optimized for the common case where the +** number of actives states is small. +*/ +typedef struct ReStateSet { + unsigned nState; /* Number of current states */ + ReStateNumber *aState; /* Current states */ +} ReStateSet; + +/* An input string read one character at a time. +*/ +typedef struct ReInput ReInput; +struct ReInput { + const unsigned char *z; /* All text */ + int i; /* Next byte to read */ + int mx; /* EOF when i>=mx */ +}; + +/* A compiled NFA (or an NFA that is in the process of being compiled) is +** an instance of the following object. +*/ +typedef struct ReCompiled ReCompiled; +struct ReCompiled { + ReInput sIn; /* Regular expression text */ + const char *zErr; /* Error message to return */ + char *aOp; /* Operators for the virtual machine */ + int *aArg; /* Arguments to each operator */ + unsigned (*xNextChar)(ReInput*); /* Next character function */ + unsigned char zInit[12]; /* Initial text to match */ + int nInit; /* Number of bytes in zInit */ + unsigned nState; /* Number of entries in aOp[] and aArg[] */ + unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ +}; + +/* Add a state to the given state set if it is not already there */ +static void re_add_state(ReStateSet *pSet, int newState){ + unsigned i; + for(i=0; i<pSet->nState; i++) if( pSet->aState[i]==newState ) return; + pSet->aState[pSet->nState++] = (ReStateNumber)newState; +} + +/* Extract the next unicode character from *pzIn and return it. Advance +** *pzIn to the first byte past the end of the character returned. To +** be clear: this routine converts utf8 to unicode. This routine is +** optimized for the common case where the next character is a single byte. +*/ +static unsigned re_next_char(ReInput *p){ + unsigned c; + if( p->i>=p->mx ) return 0; + c = p->z[p->i++]; + if( c>=0x80 ){ + if( (c&0xe0)==0xc0 && p->i<p->mx && (p->z[p->i]&0xc0)==0x80 ){ + c = (c&0x1f)<<6 | (p->z[p->i++]&0x3f); + if( c<0x80 ) c = 0xfffd; + }else if( (c&0xf0)==0xe0 && p->i+1<p->mx && (p->z[p->i]&0xc0)==0x80 + && (p->z[p->i+1]&0xc0)==0x80 ){ + c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f); + p->i += 2; + if( c<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd; + }else if( (c&0xf8)==0xf0 && p->i+2<p->mx && (p->z[p->i]&0xc0)==0x80 + && (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){ + c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6) + | (p->z[p->i+2]&0x3f); + p->i += 3; + if( c<=0xffff || c>0x10ffff ) c = 0xfffd; + }else{ + c = 0xfffd; + } + } + return c; +} +static unsigned re_next_char_nocase(ReInput *p){ + unsigned c = re_next_char(p); + if( c>='A' && c<='Z' ) c += 'a' - 'A'; + return c; +} + +/* Return true if c is a perl "word" character: [A-Za-z0-9_] */ +static int re_word_char(int c){ + return (c>='0' && c<='9') || (c>='a' && c<='z') + || (c>='A' && c<='Z') || c=='_'; +} + +/* Return true if c is a "digit" character: [0-9] */ +static int re_digit_char(int c){ + return (c>='0' && c<='9'); +} + +/* Return true if c is a perl "space" character: [ \t\r\n\v\f] */ +static int re_space_char(int c){ + return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f'; +} + +/* Run a compiled regular expression on the zero-terminated input +** string zIn[]. Return true on a match and false if there is no match. +*/ +static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ + ReStateSet aStateSet[2], *pThis, *pNext; + ReStateNumber aSpace[100]; + ReStateNumber *pToFree; + unsigned int i = 0; + unsigned int iSwap = 0; + int c = RE_START; + int cPrev = 0; + int rc = 0; + ReInput in; + + in.z = zIn; + in.i = 0; + in.mx = nIn>=0 ? nIn : (int)strlen((char const*)zIn); + + /* Look for the initial prefix match, if there is one. */ + if( pRe->nInit ){ + unsigned char x = pRe->zInit[0]; + while( in.i+pRe->nInit<=in.mx + && (zIn[in.i]!=x || + strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0) + ){ + in.i++; + } + if( in.i+pRe->nInit>in.mx ) return 0; + c = RE_START-1; + } + + if( pRe->nState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){ + pToFree = 0; + aStateSet[0].aState = aSpace; + }else{ + pToFree = sqlite3_malloc64( sizeof(ReStateNumber)*2*pRe->nState ); + if( pToFree==0 ) return -1; + aStateSet[0].aState = pToFree; + } + aStateSet[1].aState = &aStateSet[0].aState[pRe->nState]; + pNext = &aStateSet[1]; + pNext->nState = 0; + re_add_state(pNext, 0); + while( c!=RE_EOF && pNext->nState>0 ){ + cPrev = c; + c = pRe->xNextChar(&in); + pThis = pNext; + pNext = &aStateSet[iSwap]; + iSwap = 1 - iSwap; + pNext->nState = 0; + for(i=0; i<pThis->nState; i++){ + int x = pThis->aState[i]; + switch( pRe->aOp[x] ){ + case RE_OP_MATCH: { + if( pRe->aArg[x]==c ) re_add_state(pNext, x+1); + break; + } + case RE_OP_ATSTART: { + if( cPrev==RE_START ) re_add_state(pThis, x+1); + break; + } + case RE_OP_ANY: { + if( c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_WORD: { + if( re_word_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTWORD: { + if( !re_word_char(c) && c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_DIGIT: { + if( re_digit_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTDIGIT: { + if( !re_digit_char(c) && c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_SPACE: { + if( re_space_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTSPACE: { + if( !re_space_char(c) && c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_BOUNDARY: { + if( re_word_char(c)!=re_word_char(cPrev) ) re_add_state(pThis, x+1); + break; + } + case RE_OP_ANYSTAR: { + re_add_state(pNext, x); + re_add_state(pThis, x+1); + break; + } + case RE_OP_FORK: { + re_add_state(pThis, x+pRe->aArg[x]); + re_add_state(pThis, x+1); + break; + } + case RE_OP_GOTO: { + re_add_state(pThis, x+pRe->aArg[x]); + break; + } + case RE_OP_ACCEPT: { + rc = 1; + goto re_match_end; + } + case RE_OP_CC_EXC: { + if( c==0 ) break; + /* fall-through */ goto re_op_cc_inc; + } + case RE_OP_CC_INC: re_op_cc_inc: { + int j = 1; + int n = pRe->aArg[x]; + int hit = 0; + for(j=1; j>0 && j<n; j++){ + if( pRe->aOp[x+j]==RE_OP_CC_VALUE ){ + if( pRe->aArg[x+j]==c ){ + hit = 1; + j = -1; + } + }else{ + if( pRe->aArg[x+j]<=c && pRe->aArg[x+j+1]>=c ){ + hit = 1; + j = -1; + }else{ + j++; + } + } + } + if( pRe->aOp[x]==RE_OP_CC_EXC ) hit = !hit; + if( hit ) re_add_state(pNext, x+n); + break; + } + } + } + } + for(i=0; i<pNext->nState; i++){ + int x = pNext->aState[i]; + while( pRe->aOp[x]==RE_OP_GOTO ) x += pRe->aArg[x]; + if( pRe->aOp[x]==RE_OP_ACCEPT ){ rc = 1; break; } + } +re_match_end: + sqlite3_free(pToFree); + return rc; +} + +/* Resize the opcode and argument arrays for an RE under construction. +*/ +static int re_resize(ReCompiled *p, int N){ + char *aOp; + int *aArg; + aOp = sqlite3_realloc64(p->aOp, N*sizeof(p->aOp[0])); + if( aOp==0 ) return 1; + p->aOp = aOp; + aArg = sqlite3_realloc64(p->aArg, N*sizeof(p->aArg[0])); + if( aArg==0 ) return 1; + p->aArg = aArg; + p->nAlloc = N; + return 0; +} + +/* Insert a new opcode and argument into an RE under construction. The +** insertion point is just prior to existing opcode iBefore. +*/ +static int re_insert(ReCompiled *p, int iBefore, int op, int arg){ + int i; + if( p->nAlloc<=p->nState && re_resize(p, p->nAlloc*2) ) return 0; + for(i=p->nState; i>iBefore; i--){ + p->aOp[i] = p->aOp[i-1]; + p->aArg[i] = p->aArg[i-1]; + } + p->nState++; + p->aOp[iBefore] = (char)op; + p->aArg[iBefore] = arg; + return iBefore; +} + +/* Append a new opcode and argument to the end of the RE under construction. +*/ +static int re_append(ReCompiled *p, int op, int arg){ + return re_insert(p, p->nState, op, arg); +} + +/* Make a copy of N opcodes starting at iStart onto the end of the RE +** under construction. +*/ +static void re_copy(ReCompiled *p, int iStart, int N){ + if( p->nState+N>=p->nAlloc && re_resize(p, p->nAlloc*2+N) ) return; + memcpy(&p->aOp[p->nState], &p->aOp[iStart], N*sizeof(p->aOp[0])); + memcpy(&p->aArg[p->nState], &p->aArg[iStart], N*sizeof(p->aArg[0])); + p->nState += N; +} + +/* Return true if c is a hexadecimal digit character: [0-9a-fA-F] +** If c is a hex digit, also set *pV = (*pV)*16 + valueof(c). If +** c is not a hex digit *pV is unchanged. +*/ +static int re_hex(int c, int *pV){ + if( c>='0' && c<='9' ){ + c -= '0'; + }else if( c>='a' && c<='f' ){ + c -= 'a' - 10; + }else if( c>='A' && c<='F' ){ + c -= 'A' - 10; + }else{ + return 0; + } + *pV = (*pV)*16 + (c & 0xff); + return 1; +} + +/* A backslash character has been seen, read the next character and +** return its interpretation. +*/ +static unsigned re_esc_char(ReCompiled *p){ + static const char zEsc[] = "afnrtv\\()*.+?[$^{|}]"; + static const char zTrans[] = "\a\f\n\r\t\v"; + int i, v = 0; + char c; + if( p->sIn.i>=p->sIn.mx ) return 0; + c = p->sIn.z[p->sIn.i]; + if( c=='u' && p->sIn.i+4<p->sIn.mx ){ + const unsigned char *zIn = p->sIn.z + p->sIn.i; + if( re_hex(zIn[1],&v) + && re_hex(zIn[2],&v) + && re_hex(zIn[3],&v) + && re_hex(zIn[4],&v) + ){ + p->sIn.i += 5; + return v; + } + } + if( c=='x' && p->sIn.i+2<p->sIn.mx ){ + const unsigned char *zIn = p->sIn.z + p->sIn.i; + if( re_hex(zIn[1],&v) + && re_hex(zIn[2],&v) + ){ + p->sIn.i += 3; + return v; + } + } + for(i=0; zEsc[i] && zEsc[i]!=c; i++){} + if( zEsc[i] ){ + if( i<6 ) c = zTrans[i]; + p->sIn.i++; + }else{ + p->zErr = "unknown \\ escape"; + } + return c; +} + +/* Forward declaration */ +static const char *re_subcompile_string(ReCompiled*); + +/* Peek at the next byte of input */ +static unsigned char rePeek(ReCompiled *p){ + return p->sIn.i<p->sIn.mx ? p->sIn.z[p->sIn.i] : 0; +} + +/* Compile RE text into a sequence of opcodes. Continue up to the +** first unmatched ")" character, then return. If an error is found, +** return a pointer to the error message string. +*/ +static const char *re_subcompile_re(ReCompiled *p){ + const char *zErr; + int iStart, iEnd, iGoto; + iStart = p->nState; + zErr = re_subcompile_string(p); + if( zErr ) return zErr; + while( rePeek(p)=='|' ){ + iEnd = p->nState; + re_insert(p, iStart, RE_OP_FORK, iEnd + 2 - iStart); + iGoto = re_append(p, RE_OP_GOTO, 0); + p->sIn.i++; + zErr = re_subcompile_string(p); + if( zErr ) return zErr; + p->aArg[iGoto] = p->nState - iGoto; + } + return 0; +} + +/* Compile an element of regular expression text (anything that can be +** an operand to the "|" operator). Return NULL on success or a pointer +** to the error message if there is a problem. +*/ +static const char *re_subcompile_string(ReCompiled *p){ + int iPrev = -1; + int iStart; + unsigned c; + const char *zErr; + while( (c = p->xNextChar(&p->sIn))!=0 ){ + iStart = p->nState; + switch( c ){ + case '|': + case ')': { + p->sIn.i--; + return 0; + } + case '(': { + zErr = re_subcompile_re(p); + if( zErr ) return zErr; + if( rePeek(p)!=')' ) return "unmatched '('"; + p->sIn.i++; + break; + } + case '.': { + if( rePeek(p)=='*' ){ + re_append(p, RE_OP_ANYSTAR, 0); + p->sIn.i++; + }else{ + re_append(p, RE_OP_ANY, 0); + } + break; + } + case '*': { + if( iPrev<0 ) return "'*' without operand"; + re_insert(p, iPrev, RE_OP_GOTO, p->nState - iPrev + 1); + re_append(p, RE_OP_FORK, iPrev - p->nState + 1); + break; + } + case '+': { + if( iPrev<0 ) return "'+' without operand"; + re_append(p, RE_OP_FORK, iPrev - p->nState); + break; + } + case '?': { + if( iPrev<0 ) return "'?' without operand"; + re_insert(p, iPrev, RE_OP_FORK, p->nState - iPrev+1); + break; + } + case '$': { + re_append(p, RE_OP_MATCH, RE_EOF); + break; + } + case '^': { + re_append(p, RE_OP_ATSTART, 0); + break; + } + case '{': { + int m = 0, n = 0; + int sz, j; + if( iPrev<0 ) return "'{m,n}' without operand"; + while( (c=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; } + n = m; + if( c==',' ){ + p->sIn.i++; + n = 0; + while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; } + } + if( c!='}' ) return "unmatched '{'"; + if( n>0 && n<m ) return "n less than m in '{m,n}'"; + p->sIn.i++; + sz = p->nState - iPrev; + if( m==0 ){ + if( n==0 ) return "both m and n are zero in '{m,n}'"; + re_insert(p, iPrev, RE_OP_FORK, sz+1); + iPrev++; + n--; + }else{ + for(j=1; j<m; j++) re_copy(p, iPrev, sz); + } + for(j=m; j<n; j++){ + re_append(p, RE_OP_FORK, sz+1); + re_copy(p, iPrev, sz); + } + if( n==0 && m>0 ){ + re_append(p, RE_OP_FORK, -sz); + } + break; + } + case '[': { + unsigned int iFirst = p->nState; + if( rePeek(p)=='^' ){ + re_append(p, RE_OP_CC_EXC, 0); + p->sIn.i++; + }else{ + re_append(p, RE_OP_CC_INC, 0); + } + while( (c = p->xNextChar(&p->sIn))!=0 ){ + if( c=='[' && rePeek(p)==':' ){ + return "POSIX character classes not supported"; + } + if( c=='\\' ) c = re_esc_char(p); + if( rePeek(p)=='-' ){ + re_append(p, RE_OP_CC_RANGE, c); + p->sIn.i++; + c = p->xNextChar(&p->sIn); + if( c=='\\' ) c = re_esc_char(p); + re_append(p, RE_OP_CC_RANGE, c); + }else{ + re_append(p, RE_OP_CC_VALUE, c); + } + if( rePeek(p)==']' ){ p->sIn.i++; break; } + } + if( c==0 ) return "unclosed '['"; + if( p->nState>iFirst ) p->aArg[iFirst] = p->nState - iFirst; + break; + } + case '\\': { + int specialOp = 0; + switch( rePeek(p) ){ + case 'b': specialOp = RE_OP_BOUNDARY; break; + case 'd': specialOp = RE_OP_DIGIT; break; + case 'D': specialOp = RE_OP_NOTDIGIT; break; + case 's': specialOp = RE_OP_SPACE; break; + case 'S': specialOp = RE_OP_NOTSPACE; break; + case 'w': specialOp = RE_OP_WORD; break; + case 'W': specialOp = RE_OP_NOTWORD; break; + } + if( specialOp ){ + p->sIn.i++; + re_append(p, specialOp, 0); + }else{ + c = re_esc_char(p); + re_append(p, RE_OP_MATCH, c); + } + break; + } + default: { + re_append(p, RE_OP_MATCH, c); + break; + } + } + iPrev = iStart; + } + return 0; +} + +/* Free and reclaim all the memory used by a previously compiled +** regular expression. Applications should invoke this routine once +** for every call to re_compile() to avoid memory leaks. +*/ +static void re_free(ReCompiled *pRe){ + if( pRe ){ + sqlite3_free(pRe->aOp); + sqlite3_free(pRe->aArg); + sqlite3_free(pRe); + } +} + +/* +** Compile a textual regular expression in zIn[] into a compiled regular +** expression suitable for us by re_match() and return a pointer to the +** compiled regular expression in *ppRe. Return NULL on success or an +** error message if something goes wrong. +*/ +static const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ + ReCompiled *pRe; + const char *zErr; + int i, j; + + *ppRe = 0; + pRe = sqlite3_malloc( sizeof(*pRe) ); + if( pRe==0 ){ + return "out of memory"; + } + memset(pRe, 0, sizeof(*pRe)); + pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char; + if( re_resize(pRe, 30) ){ + re_free(pRe); + return "out of memory"; + } + if( zIn[0]=='^' ){ + zIn++; + }else{ + re_append(pRe, RE_OP_ANYSTAR, 0); + } + pRe->sIn.z = (unsigned char*)zIn; + pRe->sIn.i = 0; + pRe->sIn.mx = (int)strlen(zIn); + zErr = re_subcompile_re(pRe); + if( zErr ){ + re_free(pRe); + return zErr; + } + if( pRe->sIn.i>=pRe->sIn.mx ){ + re_append(pRe, RE_OP_ACCEPT, 0); + *ppRe = pRe; + }else{ + re_free(pRe); + return "unrecognized character"; + } + + /* The following is a performance optimization. If the regex begins with + ** ".*" (if the input regex lacks an initial "^") and afterwards there are + ** one or more matching characters, enter those matching characters into + ** zInit[]. The re_match() routine can then search ahead in the input + ** string looking for the initial match without having to run the whole + ** regex engine over the string. Do not worry about trying to match + ** unicode characters beyond plane 0 - those are very rare and this is + ** just an optimization. */ + if( pRe->aOp[0]==RE_OP_ANYSTAR && !noCase ){ + for(j=0, i=1; j<(int)sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ + unsigned x = pRe->aArg[i]; + if( x<=0x7f ){ + pRe->zInit[j++] = (unsigned char)x; + }else if( x<=0x7ff ){ + pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6)); + pRe->zInit[j++] = 0x80 | (x&0x3f); + }else if( x<=0xffff ){ + pRe->zInit[j++] = (unsigned char)(0xe0 | (x>>12)); + pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f); + pRe->zInit[j++] = 0x80 | (x&0x3f); + }else{ + break; + } + } + if( j>0 && pRe->zInit[j-1]==0 ) j--; + pRe->nInit = j; + } + return pRe->zErr; +} + +/* +** Implementation of the regexp() SQL function. This function implements +** the build-in REGEXP operator. The first argument to the function is the +** pattern and the second argument is the string. So, the SQL statements: +** +** A REGEXP B +** +** is implemented as regexp(B,A). +*/ +static void re_sql_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + ReCompiled *pRe; /* Compiled regular expression */ + const char *zPattern; /* The regular expression */ + const unsigned char *zStr;/* String being searched */ + const char *zErr; /* Compile error message */ + int setAux = 0; /* True to invoke sqlite3_set_auxdata() */ + + (void)argc; /* Unused */ + pRe = sqlite3_get_auxdata(context, 0); + if( pRe==0 ){ + zPattern = (const char*)sqlite3_value_text(argv[0]); + if( zPattern==0 ) return; + zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); + if( zErr ){ + re_free(pRe); + sqlite3_result_error(context, zErr, -1); + return; + } + if( pRe==0 ){ + sqlite3_result_error_nomem(context); + return; + } + setAux = 1; + } + zStr = (const unsigned char*)sqlite3_value_text(argv[1]); + if( zStr!=0 ){ + sqlite3_result_int(context, re_match(pRe, zStr, -1)); + } + if( setAux ){ + sqlite3_set_auxdata(context, 0, pRe, (void(*)(void*))re_free); + } +} + +#if defined(SQLITE_DEBUG) +/* +** This function is used for testing and debugging only. It is only available +** if the SQLITE_DEBUG compile-time option is used. +** +** Compile a regular expression and then convert the compiled expression into +** text and return that text. +*/ +static void re_bytecode_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zPattern; + const char *zErr; + ReCompiled *pRe; + sqlite3_str *pStr; + int i; + int n; + char *z; + (void)argc; + + zPattern = (const char*)sqlite3_value_text(argv[0]); + if( zPattern==0 ) return; + zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); + if( zErr ){ + re_free(pRe); + sqlite3_result_error(context, zErr, -1); + return; + } + if( pRe==0 ){ + sqlite3_result_error_nomem(context); + return; + } + pStr = sqlite3_str_new(0); + if( pStr==0 ) goto re_bytecode_func_err; + if( pRe->nInit>0 ){ + sqlite3_str_appendf(pStr, "INIT "); + for(i=0; i<pRe->nInit; i++){ + sqlite3_str_appendf(pStr, "%02x", pRe->zInit[i]); + } + sqlite3_str_appendf(pStr, "\n"); + } + for(i=0; (unsigned)i<pRe->nState; i++){ + sqlite3_str_appendf(pStr, "%-8s %4d\n", + ReOpName[(unsigned char)pRe->aOp[i]], pRe->aArg[i]); + } + n = sqlite3_str_length(pStr); + z = sqlite3_str_finish(pStr); + if( n==0 ){ + sqlite3_free(z); + }else{ + sqlite3_result_text(context, z, n-1, sqlite3_free); + } + +re_bytecode_func_err: + re_free(pRe); +} + +#endif /* SQLITE_DEBUG */ + + +/* +** Invoke this routine to register the regexp() function with the +** SQLite database connection. +*/ +#ifdef _WIN32 + +#endif +int sqlite3_regexp_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused */ + rc = sqlite3_create_function(db, "regexp", 2, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, + 0, re_sql_func, 0, 0); + if( rc==SQLITE_OK ){ + /* The regexpi(PATTERN,STRING) function is a case-insensitive version + ** of regexp(PATTERN,STRING). */ + rc = sqlite3_create_function(db, "regexpi", 2, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, + (void*)db, re_sql_func, 0, 0); +#if defined(SQLITE_DEBUG) + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "regexp_bytecode", 1, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, + 0, re_bytecode_func, 0, 0); + } +#endif /* SQLITE_DEBUG */ + } + return rc; +} + +/************************* End ../ext/misc/regexp.c ********************/ +#ifndef SQLITE_SHELL_FIDDLE +/************************* Begin ../ext/misc/fileio.c ******************/ +/* +** 2014-06-13 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements SQL functions readfile() and +** writefile(), and eponymous virtual type "fsdir". +** +** WRITEFILE(FILE, DATA [, MODE [, MTIME]]): +** +** If neither of the optional arguments is present, then this UDF +** function writes blob DATA to file FILE. If successful, the number +** of bytes written is returned. If an error occurs, NULL is returned. +** +** If the first option argument - MODE - is present, then it must +** be passed an integer value that corresponds to a POSIX mode +** value (file type + permissions, as returned in the stat.st_mode +** field by the stat() system call). Three types of files may +** be written/created: +** +** regular files: (mode & 0170000)==0100000 +** symbolic links: (mode & 0170000)==0120000 +** directories: (mode & 0170000)==0040000 +** +** For a directory, the DATA is ignored. For a symbolic link, it is +** interpreted as text and used as the target of the link. For a +** regular file, it is interpreted as a blob and written into the +** named file. Regardless of the type of file, its permissions are +** set to (mode & 0777) before returning. +** +** If the optional MTIME argument is present, then it is interpreted +** as an integer - the number of seconds since the unix epoch. The +** modification-time of the target file is set to this value before +** returning. +** +** If three or more arguments are passed to this function and an +** error is encountered, an exception is raised. +** +** READFILE(FILE): +** +** Read and return the contents of file FILE (type blob) from disk. +** +** FSDIR: +** +** Used as follows: +** +** SELECT * FROM fsdir($path [, $dir]); +** +** Parameter $path is an absolute or relative pathname. If the file that it +** refers to does not exist, it is an error. If the path refers to a regular +** file or symbolic link, it returns a single row. Or, if the path refers +** to a directory, it returns one row for the directory, and one row for each +** file within the hierarchy rooted at $path. +** +** Each row has the following columns: +** +** name: Path to file or directory (text value). +** mode: Value of stat.st_mode for directory entry (an integer). +** mtime: Value of stat.st_mtime for directory entry (an integer). +** data: For a regular file, a blob containing the file data. For a +** symlink, a text value containing the text of the link. For a +** directory, NULL. +** +** If a non-NULL value is specified for the optional $dir parameter and +** $path is a relative path, then $path is interpreted relative to $dir. +** And the paths returned in the "name" column of the table are also +** relative to directory $dir. +** +** Notes on building this extension for Windows: +** Unless linked statically with the SQLite library, a preprocessor +** symbol, FILEIO_WIN32_DLL, must be #define'd to create a stand-alone +** DLL form of this extension for WIN32. See its use below for details. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#if !defined(_WIN32) && !defined(WIN32) +# include <unistd.h> +# include <dirent.h> +# include <utime.h> +# include <sys/time.h> +#else +# include "windows.h" +# include <io.h> +# include <direct.h> +/* # include "test_windirent.h" */ +# define dirent DIRENT +# ifndef chmod +# define chmod _chmod +# endif +# ifndef stat +# define stat _stat +# endif +# define mkdir(path,mode) _mkdir(path) +# define lstat(path,buf) stat(path,buf) +#endif +#include <time.h> +#include <errno.h> + + +/* +** Structure of the fsdir() table-valued function +*/ + /* 0 1 2 3 4 5 */ +#define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)" +#define FSDIR_COLUMN_NAME 0 /* Name of the file */ +#define FSDIR_COLUMN_MODE 1 /* Access mode */ +#define FSDIR_COLUMN_MTIME 2 /* Last modification time */ +#define FSDIR_COLUMN_DATA 3 /* File content */ +#define FSDIR_COLUMN_PATH 4 /* Path to top of search */ +#define FSDIR_COLUMN_DIR 5 /* Path is relative to this directory */ + + +/* +** Set the result stored by context ctx to a blob containing the +** contents of file zName. Or, leave the result unchanged (NULL) +** if the file does not exist or is unreadable. +** +** If the file exceeds the SQLite blob size limit, through an +** SQLITE_TOOBIG error. +** +** Throw an SQLITE_IOERR if there are difficulties pulling the file +** off of disk. +*/ +static void readFileContents(sqlite3_context *ctx, const char *zName){ + FILE *in; + sqlite3_int64 nIn; + void *pBuf; + sqlite3 *db; + int mxBlob; + + in = fopen(zName, "rb"); + if( in==0 ){ + /* File does not exist or is unreadable. Leave the result set to NULL. */ + return; + } + fseek(in, 0, SEEK_END); + nIn = ftell(in); + rewind(in); + db = sqlite3_context_db_handle(ctx); + mxBlob = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1); + if( nIn>mxBlob ){ + sqlite3_result_error_code(ctx, SQLITE_TOOBIG); + fclose(in); + return; + } + pBuf = sqlite3_malloc64( nIn ? nIn : 1 ); + if( pBuf==0 ){ + sqlite3_result_error_nomem(ctx); + fclose(in); + return; + } + if( nIn==(sqlite3_int64)fread(pBuf, 1, (size_t)nIn, in) ){ + sqlite3_result_blob64(ctx, pBuf, nIn, sqlite3_free); + }else{ + sqlite3_result_error_code(ctx, SQLITE_IOERR); + sqlite3_free(pBuf); + } + fclose(in); +} + +/* +** Implementation of the "readfile(X)" SQL function. The entire content +** of the file named X is read and returned as a BLOB. NULL is returned +** if the file does not exist or is unreadable. +*/ +static void readfileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName; + (void)(argc); /* Unused parameter */ + zName = (const char*)sqlite3_value_text(argv[0]); + if( zName==0 ) return; + readFileContents(context, zName); +} + +/* +** Set the error message contained in context ctx to the results of +** vprintf(zFmt, ...). +*/ +static void ctxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){ + char *zMsg = 0; + va_list ap; + va_start(ap, zFmt); + zMsg = sqlite3_vmprintf(zFmt, ap); + sqlite3_result_error(ctx, zMsg, -1); + sqlite3_free(zMsg); + va_end(ap); +} + +#if defined(_WIN32) +/* +** This function is designed to convert a Win32 FILETIME structure into the +** number of seconds since the Unix Epoch (1970-01-01 00:00:00 UTC). +*/ +static sqlite3_uint64 fileTimeToUnixTime( + LPFILETIME pFileTime +){ + SYSTEMTIME epochSystemTime; + ULARGE_INTEGER epochIntervals; + FILETIME epochFileTime; + ULARGE_INTEGER fileIntervals; + + memset(&epochSystemTime, 0, sizeof(SYSTEMTIME)); + epochSystemTime.wYear = 1970; + epochSystemTime.wMonth = 1; + epochSystemTime.wDay = 1; + SystemTimeToFileTime(&epochSystemTime, &epochFileTime); + epochIntervals.LowPart = epochFileTime.dwLowDateTime; + epochIntervals.HighPart = epochFileTime.dwHighDateTime; + + fileIntervals.LowPart = pFileTime->dwLowDateTime; + fileIntervals.HighPart = pFileTime->dwHighDateTime; + + return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000; +} + + +#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32)) +# /* To allow a standalone DLL, use this next replacement function: */ +# undef sqlite3_win32_utf8_to_unicode +# define sqlite3_win32_utf8_to_unicode utf8_to_utf16 +# +LPWSTR utf8_to_utf16(const char *z){ + int nAllot = MultiByteToWideChar(CP_UTF8, 0, z, -1, NULL, 0); + LPWSTR rv = sqlite3_malloc(nAllot * sizeof(WCHAR)); + if( rv!=0 && 0 < MultiByteToWideChar(CP_UTF8, 0, z, -1, rv, nAllot) ) + return rv; + sqlite3_free(rv); + return 0; +} +#endif + +/* +** This function attempts to normalize the time values found in the stat() +** buffer to UTC. This is necessary on Win32, where the runtime library +** appears to return these values as local times. +*/ +static void statTimesToUtc( + const char *zPath, + struct stat *pStatBuf +){ + HANDLE hFindFile; + WIN32_FIND_DATAW fd; + LPWSTR zUnicodeName; + extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); + zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath); + if( zUnicodeName ){ + memset(&fd, 0, sizeof(WIN32_FIND_DATAW)); + hFindFile = FindFirstFileW(zUnicodeName, &fd); + if( hFindFile!=NULL ){ + pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime); + pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime); + pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime); + FindClose(hFindFile); + } + sqlite3_free(zUnicodeName); + } +} +#endif + +/* +** This function is used in place of stat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls stat(). +*/ +static int fileStat( + const char *zPath, + struct stat *pStatBuf +){ +#if defined(_WIN32) + int rc = stat(zPath, pStatBuf); + if( rc==0 ) statTimesToUtc(zPath, pStatBuf); + return rc; +#else + return stat(zPath, pStatBuf); +#endif +} + +/* +** This function is used in place of lstat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls lstat(). +*/ +static int fileLinkStat( + const char *zPath, + struct stat *pStatBuf +){ +#if defined(_WIN32) + int rc = lstat(zPath, pStatBuf); + if( rc==0 ) statTimesToUtc(zPath, pStatBuf); + return rc; +#else + return lstat(zPath, pStatBuf); +#endif +} + +/* +** Argument zFile is the name of a file that will be created and/or written +** by SQL function writefile(). This function ensures that the directory +** zFile will be written to exists, creating it if required. The permissions +** for any path components created by this function are set in accordance +** with the current umask. +** +** If an OOM condition is encountered, SQLITE_NOMEM is returned. Otherwise, +** SQLITE_OK is returned if the directory is successfully created, or +** SQLITE_ERROR otherwise. +*/ +static int makeDirectory( + const char *zFile +){ + char *zCopy = sqlite3_mprintf("%s", zFile); + int rc = SQLITE_OK; + + if( zCopy==0 ){ + rc = SQLITE_NOMEM; + }else{ + int nCopy = (int)strlen(zCopy); + int i = 1; + + while( rc==SQLITE_OK ){ + struct stat sStat; + int rc2; + + for(; zCopy[i]!='/' && i<nCopy; i++); + if( i==nCopy ) break; + zCopy[i] = '\0'; + + rc2 = fileStat(zCopy, &sStat); + if( rc2!=0 ){ + if( mkdir(zCopy, 0777) ) rc = SQLITE_ERROR; + }else{ + if( !S_ISDIR(sStat.st_mode) ) rc = SQLITE_ERROR; + } + zCopy[i] = '/'; + i++; + } + + sqlite3_free(zCopy); + } + + return rc; +} + +/* +** This function does the work for the writefile() UDF. Refer to +** header comments at the top of this file for details. +*/ +static int writeFile( + sqlite3_context *pCtx, /* Context to return bytes written in */ + const char *zFile, /* File to write */ + sqlite3_value *pData, /* Data to write */ + mode_t mode, /* MODE parameter passed to writefile() */ + sqlite3_int64 mtime /* MTIME parameter (or -1 to not set time) */ +){ + if( zFile==0 ) return 1; +#if !defined(_WIN32) && !defined(WIN32) + if( S_ISLNK(mode) ){ + const char *zTo = (const char*)sqlite3_value_text(pData); + if( zTo==0 || symlink(zTo, zFile)<0 ) return 1; + }else +#endif + { + if( S_ISDIR(mode) ){ + if( mkdir(zFile, mode) ){ + /* The mkdir() call to create the directory failed. This might not + ** be an error though - if there is already a directory at the same + ** path and either the permissions already match or can be changed + ** to do so using chmod(), it is not an error. */ + struct stat sStat; + if( errno!=EEXIST + || 0!=fileStat(zFile, &sStat) + || !S_ISDIR(sStat.st_mode) + || ((sStat.st_mode&0777)!=(mode&0777) && 0!=chmod(zFile, mode&0777)) + ){ + return 1; + } + } + }else{ + sqlite3_int64 nWrite = 0; + const char *z; + int rc = 0; + FILE *out = fopen(zFile, "wb"); + if( out==0 ) return 1; + z = (const char*)sqlite3_value_blob(pData); + if( z ){ + sqlite3_int64 n = fwrite(z, 1, sqlite3_value_bytes(pData), out); + nWrite = sqlite3_value_bytes(pData); + if( nWrite!=n ){ + rc = 1; + } + } + fclose(out); + if( rc==0 && mode && chmod(zFile, mode & 0777) ){ + rc = 1; + } + if( rc ) return 2; + sqlite3_result_int64(pCtx, nWrite); + } + } + + if( mtime>=0 ){ +#if defined(_WIN32) +#if !SQLITE_OS_WINRT + /* Windows */ + FILETIME lastAccess; + FILETIME lastWrite; + SYSTEMTIME currentTime; + LONGLONG intervals; + HANDLE hFile; + LPWSTR zUnicodeName; + extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); + + GetSystemTime(&currentTime); + SystemTimeToFileTime(&currentTime, &lastAccess); + intervals = Int32x32To64(mtime, 10000000) + 116444736000000000; + lastWrite.dwLowDateTime = (DWORD)intervals; + lastWrite.dwHighDateTime = intervals >> 32; + zUnicodeName = sqlite3_win32_utf8_to_unicode(zFile); + if( zUnicodeName==0 ){ + return 1; + } + hFile = CreateFileW( + zUnicodeName, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL + ); + sqlite3_free(zUnicodeName); + if( hFile!=INVALID_HANDLE_VALUE ){ + BOOL bResult = SetFileTime(hFile, NULL, &lastAccess, &lastWrite); + CloseHandle(hFile); + return !bResult; + }else{ + return 1; + } +#endif +#elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */ + /* Recent unix */ + struct timespec times[2]; + times[0].tv_nsec = times[1].tv_nsec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){ + return 1; + } +#else + /* Legacy unix */ + struct timeval times[2]; + times[0].tv_usec = times[1].tv_usec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimes(zFile, times) ){ + return 1; + } +#endif + } + + return 0; +} + +/* +** Implementation of the "writefile(W,X[,Y[,Z]]])" SQL function. +** Refer to header comments at the top of this file for details. +*/ +static void writefileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zFile; + mode_t mode = 0; + int res; + sqlite3_int64 mtime = -1; + + if( argc<2 || argc>4 ){ + sqlite3_result_error(context, + "wrong number of arguments to function writefile()", -1 + ); + return; + } + + zFile = (const char*)sqlite3_value_text(argv[0]); + if( zFile==0 ) return; + if( argc>=3 ){ + mode = (mode_t)sqlite3_value_int(argv[2]); + } + if( argc==4 ){ + mtime = sqlite3_value_int64(argv[3]); + } + + res = writeFile(context, zFile, argv[1], mode, mtime); + if( res==1 && errno==ENOENT ){ + if( makeDirectory(zFile)==SQLITE_OK ){ + res = writeFile(context, zFile, argv[1], mode, mtime); + } + } + + if( argc>2 && res!=0 ){ + if( S_ISLNK(mode) ){ + ctxErrorMsg(context, "failed to create symlink: %s", zFile); + }else if( S_ISDIR(mode) ){ + ctxErrorMsg(context, "failed to create directory: %s", zFile); + }else{ + ctxErrorMsg(context, "failed to write file: %s", zFile); + } + } +} + +/* +** SQL function: lsmode(MODE) +** +** Given a numberic st_mode from stat(), convert it into a human-readable +** text string in the style of "ls -l". +*/ +static void lsModeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int i; + int iMode = sqlite3_value_int(argv[0]); + char z[16]; + (void)argc; + if( S_ISLNK(iMode) ){ + z[0] = 'l'; + }else if( S_ISREG(iMode) ){ + z[0] = '-'; + }else if( S_ISDIR(iMode) ){ + z[0] = 'd'; + }else{ + z[0] = '?'; + } + for(i=0; i<3; i++){ + int m = (iMode >> ((2-i)*3)); + char *a = &z[1 + i*3]; + a[0] = (m & 0x4) ? 'r' : '-'; + a[1] = (m & 0x2) ? 'w' : '-'; + a[2] = (m & 0x1) ? 'x' : '-'; + } + z[10] = '\0'; + sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT); +} + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** Cursor type for recursively iterating through a directory structure. +*/ +typedef struct fsdir_cursor fsdir_cursor; +typedef struct FsdirLevel FsdirLevel; + +struct FsdirLevel { + DIR *pDir; /* From opendir() */ + char *zDir; /* Name of directory (nul-terminated) */ +}; + +struct fsdir_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + + int nLvl; /* Number of entries in aLvl[] array */ + int iLvl; /* Index of current entry */ + FsdirLevel *aLvl; /* Hierarchy of directories being traversed */ + + const char *zBase; + int nBase; + + struct stat sStat; /* Current lstat() results */ + char *zPath; /* Path to current entry */ + sqlite3_int64 iRowid; /* Current rowid */ +}; + +typedef struct fsdir_tab fsdir_tab; +struct fsdir_tab { + sqlite3_vtab base; /* Base class - must be first */ +}; + +/* +** Construct a new fsdir virtual table object. +*/ +static int fsdirConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + fsdir_tab *pNew = 0; + int rc; + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; + rc = sqlite3_declare_vtab(db, "CREATE TABLE x" FSDIR_SCHEMA); + if( rc==SQLITE_OK ){ + pNew = (fsdir_tab*)sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + } + *ppVtab = (sqlite3_vtab*)pNew; + return rc; +} + +/* +** This method is the destructor for fsdir vtab objects. +*/ +static int fsdirDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new fsdir_cursor object. +*/ +static int fsdirOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + fsdir_cursor *pCur; + (void)p; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->iLvl = -1; + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Reset a cursor back to the state it was in when first returned +** by fsdirOpen(). +*/ +static void fsdirResetCursor(fsdir_cursor *pCur){ + int i; + for(i=0; i<=pCur->iLvl; i++){ + FsdirLevel *pLvl = &pCur->aLvl[i]; + if( pLvl->pDir ) closedir(pLvl->pDir); + sqlite3_free(pLvl->zDir); + } + sqlite3_free(pCur->zPath); + sqlite3_free(pCur->aLvl); + pCur->aLvl = 0; + pCur->zPath = 0; + pCur->zBase = 0; + pCur->nBase = 0; + pCur->nLvl = 0; + pCur->iLvl = -1; + pCur->iRowid = 1; +} + +/* +** Destructor for an fsdir_cursor. +*/ +static int fsdirClose(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + + fsdirResetCursor(pCur); + sqlite3_free(pCur); + return SQLITE_OK; +} + +/* +** Set the error message for the virtual table associated with cursor +** pCur to the results of vprintf(zFmt, ...). +*/ +static void fsdirSetErrmsg(fsdir_cursor *pCur, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + pCur->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} + + +/* +** Advance an fsdir_cursor to its next row of output. +*/ +static int fsdirNext(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + mode_t m = pCur->sStat.st_mode; + + pCur->iRowid++; + if( S_ISDIR(m) ){ + /* Descend into this directory */ + int iNew = pCur->iLvl + 1; + FsdirLevel *pLvl; + if( iNew>=pCur->nLvl ){ + int nNew = iNew+1; + sqlite3_int64 nByte = nNew*sizeof(FsdirLevel); + FsdirLevel *aNew = (FsdirLevel*)sqlite3_realloc64(pCur->aLvl, nByte); + if( aNew==0 ) return SQLITE_NOMEM; + memset(&aNew[pCur->nLvl], 0, sizeof(FsdirLevel)*(nNew-pCur->nLvl)); + pCur->aLvl = aNew; + pCur->nLvl = nNew; + } + pCur->iLvl = iNew; + pLvl = &pCur->aLvl[iNew]; + + pLvl->zDir = pCur->zPath; + pCur->zPath = 0; + pLvl->pDir = opendir(pLvl->zDir); + if( pLvl->pDir==0 ){ + fsdirSetErrmsg(pCur, "cannot read directory: %s", pCur->zPath); + return SQLITE_ERROR; + } + } + + while( pCur->iLvl>=0 ){ + FsdirLevel *pLvl = &pCur->aLvl[pCur->iLvl]; + struct dirent *pEntry = readdir(pLvl->pDir); + if( pEntry ){ + if( pEntry->d_name[0]=='.' ){ + if( pEntry->d_name[1]=='.' && pEntry->d_name[2]=='\0' ) continue; + if( pEntry->d_name[1]=='\0' ) continue; + } + sqlite3_free(pCur->zPath); + pCur->zPath = sqlite3_mprintf("%s/%s", pLvl->zDir, pEntry->d_name); + if( pCur->zPath==0 ) return SQLITE_NOMEM; + if( fileLinkStat(pCur->zPath, &pCur->sStat) ){ + fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); + return SQLITE_ERROR; + } + return SQLITE_OK; + } + closedir(pLvl->pDir); + sqlite3_free(pLvl->zDir); + pLvl->pDir = 0; + pLvl->zDir = 0; + pCur->iLvl--; + } + + /* EOF */ + sqlite3_free(pCur->zPath); + pCur->zPath = 0; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the series_cursor +** is currently pointing. +*/ +static int fsdirColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + switch( i ){ + case FSDIR_COLUMN_NAME: { + sqlite3_result_text(ctx, &pCur->zPath[pCur->nBase], -1, SQLITE_TRANSIENT); + break; + } + + case FSDIR_COLUMN_MODE: + sqlite3_result_int64(ctx, pCur->sStat.st_mode); + break; + + case FSDIR_COLUMN_MTIME: + sqlite3_result_int64(ctx, pCur->sStat.st_mtime); + break; + + case FSDIR_COLUMN_DATA: { + mode_t m = pCur->sStat.st_mode; + if( S_ISDIR(m) ){ + sqlite3_result_null(ctx); +#if !defined(_WIN32) && !defined(WIN32) + }else if( S_ISLNK(m) ){ + char aStatic[64]; + char *aBuf = aStatic; + sqlite3_int64 nBuf = 64; + int n; + + while( 1 ){ + n = readlink(pCur->zPath, aBuf, nBuf); + if( n<nBuf ) break; + if( aBuf!=aStatic ) sqlite3_free(aBuf); + nBuf = nBuf*2; + aBuf = sqlite3_malloc64(nBuf); + if( aBuf==0 ){ + sqlite3_result_error_nomem(ctx); + return SQLITE_NOMEM; + } + } + + sqlite3_result_text(ctx, aBuf, n, SQLITE_TRANSIENT); + if( aBuf!=aStatic ) sqlite3_free(aBuf); +#endif + }else{ + readFileContents(ctx, pCur->zPath); + } + } + case FSDIR_COLUMN_PATH: + default: { + /* The FSDIR_COLUMN_PATH and FSDIR_COLUMN_DIR are input parameters. + ** always return their values as NULL */ + break; + } + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** first row returned is assigned rowid value 1, and each subsequent +** row a value 1 more than that of the previous. +*/ +static int fsdirRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int fsdirEof(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + return (pCur->zPath==0); +} + +/* +** xFilter callback. +** +** idxNum==1 PATH parameter only +** idxNum==2 Both PATH and DIR supplied +*/ +static int fsdirFilter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + const char *zDir = 0; + fsdir_cursor *pCur = (fsdir_cursor*)cur; + (void)idxStr; + fsdirResetCursor(pCur); + + if( idxNum==0 ){ + fsdirSetErrmsg(pCur, "table function fsdir requires an argument"); + return SQLITE_ERROR; + } + + assert( argc==idxNum && (argc==1 || argc==2) ); + zDir = (const char*)sqlite3_value_text(argv[0]); + if( zDir==0 ){ + fsdirSetErrmsg(pCur, "table function fsdir requires a non-NULL argument"); + return SQLITE_ERROR; + } + if( argc==2 ){ + pCur->zBase = (const char*)sqlite3_value_text(argv[1]); + } + if( pCur->zBase ){ + pCur->nBase = (int)strlen(pCur->zBase)+1; + pCur->zPath = sqlite3_mprintf("%s/%s", pCur->zBase, zDir); + }else{ + pCur->zPath = sqlite3_mprintf("%s", zDir); + } + + if( pCur->zPath==0 ){ + return SQLITE_NOMEM; + } + if( fileLinkStat(pCur->zPath, &pCur->sStat) ){ + fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); + return SQLITE_ERROR; + } + + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the generate_series virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** In this implementation idxNum is used to represent the +** query plan. idxStr is unused. +** +** The query plan is represented by values of idxNum: +** +** (1) The path value is supplied by argv[0] +** (2) Path is in argv[0] and dir is in argv[1] +*/ +static int fsdirBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int idxPath = -1; /* Index in pIdxInfo->aConstraint of PATH= */ + int idxDir = -1; /* Index in pIdxInfo->aConstraint of DIR= */ + int seenPath = 0; /* True if an unusable PATH= constraint is seen */ + int seenDir = 0; /* True if an unusable DIR= constraint is seen */ + const struct sqlite3_index_constraint *pConstraint; + + (void)tab; + pConstraint = pIdxInfo->aConstraint; + for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case FSDIR_COLUMN_PATH: { + if( pConstraint->usable ){ + idxPath = i; + seenPath = 0; + }else if( idxPath<0 ){ + seenPath = 1; + } + break; + } + case FSDIR_COLUMN_DIR: { + if( pConstraint->usable ){ + idxDir = i; + seenDir = 0; + }else if( idxDir<0 ){ + seenDir = 1; + } + break; + } + } + } + if( seenPath || seenDir ){ + /* If input parameters are unusable, disallow this plan */ + return SQLITE_CONSTRAINT; + } + + if( idxPath<0 ){ + pIdxInfo->idxNum = 0; + /* The pIdxInfo->estimatedCost should have been initialized to a huge + ** number. Leave it unchanged. */ + pIdxInfo->estimatedRows = 0x7fffffff; + }else{ + pIdxInfo->aConstraintUsage[idxPath].omit = 1; + pIdxInfo->aConstraintUsage[idxPath].argvIndex = 1; + if( idxDir>=0 ){ + pIdxInfo->aConstraintUsage[idxDir].omit = 1; + pIdxInfo->aConstraintUsage[idxDir].argvIndex = 2; + pIdxInfo->idxNum = 2; + pIdxInfo->estimatedCost = 10.0; + }else{ + pIdxInfo->idxNum = 1; + pIdxInfo->estimatedCost = 100.0; + } + } + + return SQLITE_OK; +} + +/* +** Register the "fsdir" virtual table. +*/ +static int fsdirRegister(sqlite3 *db){ + static sqlite3_module fsdirModule = { + 0, /* iVersion */ + 0, /* xCreate */ + fsdirConnect, /* xConnect */ + fsdirBestIndex, /* xBestIndex */ + fsdirDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + fsdirOpen, /* xOpen - open a cursor */ + fsdirClose, /* xClose - close a cursor */ + fsdirFilter, /* xFilter - configure scan constraints */ + fsdirNext, /* xNext - advance a cursor */ + fsdirEof, /* xEof - check for end of scan */ + fsdirColumn, /* xColumn - read data */ + fsdirRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + }; + + int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0); + return rc; +} +#else /* SQLITE_OMIT_VIRTUALTABLE */ +# define fsdirRegister(x) SQLITE_OK +#endif + +#ifdef _WIN32 + +#endif +int sqlite3_fileio_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + rc = sqlite3_create_function(db, "readfile", 1, + SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + readfileFunc, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "writefile", -1, + SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + writefileFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "lsmode", 1, SQLITE_UTF8, 0, + lsModeFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = fsdirRegister(db); + } + return rc; +} + +#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32)) +/* To allow a standalone DLL, make test_windirent.c use the same + * redefined SQLite API calls as the above extension code does. + * Just pull in this .c to accomplish this. As a beneficial side + * effect, this extension becomes a single translation unit. */ +# include "test_windirent.c" +#endif + +/************************* End ../ext/misc/fileio.c ********************/ +/************************* Begin ../ext/misc/completion.c ******************/ +/* +** 2017-07-10 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements an eponymous virtual table that returns suggested +** completions for a partial SQL input. +** +** Suggested usage: +** +** SELECT DISTINCT candidate COLLATE nocase +** FROM completion($prefix,$wholeline) +** ORDER BY 1; +** +** The two query parameters are optional. $prefix is the text of the +** current word being typed and that is to be completed. $wholeline is +** the complete input line, used for context. +** +** The raw completion() table might return the same candidate multiple +** times, for example if the same column name is used to two or more +** tables. And the candidates are returned in an arbitrary order. Hence, +** the DISTINCT and ORDER BY are recommended. +** +** This virtual table operates at the speed of human typing, and so there +** is no attempt to make it fast. Even a slow implementation will be much +** faster than any human can type. +** +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include <assert.h> +#include <string.h> +#include <ctype.h> + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* completion_vtab is a subclass of sqlite3_vtab which will +** serve as the underlying representation of a completion virtual table +*/ +typedef struct completion_vtab completion_vtab; +struct completion_vtab { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this completion vtab */ +}; + +/* completion_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct completion_cursor completion_cursor; +struct completion_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this cursor */ + int nPrefix, nLine; /* Number of bytes in zPrefix and zLine */ + char *zPrefix; /* The prefix for the word we want to complete */ + char *zLine; /* The whole that we want to complete */ + const char *zCurrentRow; /* Current output row */ + int szRow; /* Length of the zCurrentRow string */ + sqlite3_stmt *pStmt; /* Current statement */ + sqlite3_int64 iRowid; /* The rowid */ + int ePhase; /* Current phase */ + int j; /* inter-phase counter */ +}; + +/* Values for ePhase: +*/ +#define COMPLETION_FIRST_PHASE 1 +#define COMPLETION_KEYWORDS 1 +#define COMPLETION_PRAGMAS 2 +#define COMPLETION_FUNCTIONS 3 +#define COMPLETION_COLLATIONS 4 +#define COMPLETION_INDEXES 5 +#define COMPLETION_TRIGGERS 6 +#define COMPLETION_DATABASES 7 +#define COMPLETION_TABLES 8 /* Also VIEWs and TRIGGERs */ +#define COMPLETION_COLUMNS 9 +#define COMPLETION_MODULES 10 +#define COMPLETION_EOF 11 + +/* +** The completionConnect() method is invoked to create a new +** completion_vtab that describes the completion virtual table. +** +** Think of this routine as the constructor for completion_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the completion_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against completion will look like. +*/ +static int completionConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + completion_vtab *pNew; + int rc; + + (void)(pAux); /* Unused parameter */ + (void)(argc); /* Unused parameter */ + (void)(argv); /* Unused parameter */ + (void)(pzErr); /* Unused parameter */ + +/* Column numbers */ +#define COMPLETION_COLUMN_CANDIDATE 0 /* Suggested completion of the input */ +#define COMPLETION_COLUMN_PREFIX 1 /* Prefix of the word to be completed */ +#define COMPLETION_COLUMN_WHOLELINE 2 /* Entire line seen so far */ +#define COMPLETION_COLUMN_PHASE 3 /* ePhase - used for debugging only */ + + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(" + " candidate TEXT," + " prefix TEXT HIDDEN," + " wholeline TEXT HIDDEN," + " phase INT HIDDEN" /* Used for debugging only */ + ")"); + if( rc==SQLITE_OK ){ + pNew = sqlite3_malloc( sizeof(*pNew) ); + *ppVtab = (sqlite3_vtab*)pNew; + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + pNew->db = db; + } + return rc; +} + +/* +** This method is the destructor for completion_cursor objects. +*/ +static int completionDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new completion_cursor object. +*/ +static int completionOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + completion_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->db = ((completion_vtab*)p)->db; + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Reset the completion_cursor. +*/ +static void completionCursorReset(completion_cursor *pCur){ + sqlite3_free(pCur->zPrefix); pCur->zPrefix = 0; pCur->nPrefix = 0; + sqlite3_free(pCur->zLine); pCur->zLine = 0; pCur->nLine = 0; + sqlite3_finalize(pCur->pStmt); pCur->pStmt = 0; + pCur->j = 0; +} + +/* +** Destructor for a completion_cursor. +*/ +static int completionClose(sqlite3_vtab_cursor *cur){ + completionCursorReset((completion_cursor*)cur); + sqlite3_free(cur); + return SQLITE_OK; +} + +/* +** Advance a completion_cursor to its next row of output. +** +** The ->ePhase, ->j, and ->pStmt fields of the completion_cursor object +** record the current state of the scan. This routine sets ->zCurrentRow +** to the current row of output and then returns. If no more rows remain, +** then ->ePhase is set to COMPLETION_EOF which will signal the virtual +** table that has reached the end of its scan. +** +** The current implementation just lists potential identifiers and +** keywords and filters them by zPrefix. Future enhancements should +** take zLine into account to try to restrict the set of identifiers and +** keywords based on what would be legal at the current point of input. +*/ +static int completionNext(sqlite3_vtab_cursor *cur){ + completion_cursor *pCur = (completion_cursor*)cur; + int eNextPhase = 0; /* Next phase to try if current phase reaches end */ + int iCol = -1; /* If >=0, step pCur->pStmt and use the i-th column */ + pCur->iRowid++; + while( pCur->ePhase!=COMPLETION_EOF ){ + switch( pCur->ePhase ){ + case COMPLETION_KEYWORDS: { + if( pCur->j >= sqlite3_keyword_count() ){ + pCur->zCurrentRow = 0; + pCur->ePhase = COMPLETION_DATABASES; + }else{ + sqlite3_keyword_name(pCur->j++, &pCur->zCurrentRow, &pCur->szRow); + } + iCol = -1; + break; + } + case COMPLETION_DATABASES: { + if( pCur->pStmt==0 ){ + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, + &pCur->pStmt, 0); + } + iCol = 1; + eNextPhase = COMPLETION_TABLES; + break; + } + case COMPLETION_TABLES: { + if( pCur->pStmt==0 ){ + sqlite3_stmt *pS2; + char *zSql = 0; + const char *zSep = ""; + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0); + while( sqlite3_step(pS2)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pS2, 1); + zSql = sqlite3_mprintf( + "%z%s" + "SELECT name FROM \"%w\".sqlite_schema", + zSql, zSep, zDb + ); + if( zSql==0 ) return SQLITE_NOMEM; + zSep = " UNION "; + } + sqlite3_finalize(pS2); + sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0); + sqlite3_free(zSql); + } + iCol = 0; + eNextPhase = COMPLETION_COLUMNS; + break; + } + case COMPLETION_COLUMNS: { + if( pCur->pStmt==0 ){ + sqlite3_stmt *pS2; + char *zSql = 0; + const char *zSep = ""; + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0); + while( sqlite3_step(pS2)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pS2, 1); + zSql = sqlite3_mprintf( + "%z%s" + "SELECT pti.name FROM \"%w\".sqlite_schema AS sm" + " JOIN pragma_table_info(sm.name,%Q) AS pti" + " WHERE sm.type='table'", + zSql, zSep, zDb, zDb + ); + if( zSql==0 ) return SQLITE_NOMEM; + zSep = " UNION "; + } + sqlite3_finalize(pS2); + sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0); + sqlite3_free(zSql); + } + iCol = 0; + eNextPhase = COMPLETION_EOF; + break; + } + } + if( iCol<0 ){ + /* This case is when the phase presets zCurrentRow */ + if( pCur->zCurrentRow==0 ) continue; + }else{ + if( sqlite3_step(pCur->pStmt)==SQLITE_ROW ){ + /* Extract the next row of content */ + pCur->zCurrentRow = (const char*)sqlite3_column_text(pCur->pStmt, iCol); + pCur->szRow = sqlite3_column_bytes(pCur->pStmt, iCol); + }else{ + /* When all rows are finished, advance to the next phase */ + sqlite3_finalize(pCur->pStmt); + pCur->pStmt = 0; + pCur->ePhase = eNextPhase; + continue; + } + } + if( pCur->nPrefix==0 ) break; + if( pCur->nPrefix<=pCur->szRow + && sqlite3_strnicmp(pCur->zPrefix, pCur->zCurrentRow, pCur->nPrefix)==0 + ){ + break; + } + } + + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the completion_cursor +** is currently pointing. +*/ +static int completionColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + completion_cursor *pCur = (completion_cursor*)cur; + switch( i ){ + case COMPLETION_COLUMN_CANDIDATE: { + sqlite3_result_text(ctx, pCur->zCurrentRow, pCur->szRow,SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_PREFIX: { + sqlite3_result_text(ctx, pCur->zPrefix, -1, SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_WHOLELINE: { + sqlite3_result_text(ctx, pCur->zLine, -1, SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_PHASE: { + sqlite3_result_int(ctx, pCur->ePhase); + break; + } + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int completionRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + completion_cursor *pCur = (completion_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int completionEof(sqlite3_vtab_cursor *cur){ + completion_cursor *pCur = (completion_cursor*)cur; + return pCur->ePhase >= COMPLETION_EOF; +} + +/* +** This method is called to "rewind" the completion_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to completionColumn() or completionRowid() or +** completionEof(). +*/ +static int completionFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + completion_cursor *pCur = (completion_cursor *)pVtabCursor; + int iArg = 0; + (void)(idxStr); /* Unused parameter */ + (void)(argc); /* Unused parameter */ + completionCursorReset(pCur); + if( idxNum & 1 ){ + pCur->nPrefix = sqlite3_value_bytes(argv[iArg]); + if( pCur->nPrefix>0 ){ + pCur->zPrefix = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); + if( pCur->zPrefix==0 ) return SQLITE_NOMEM; + } + iArg = 1; + } + if( idxNum & 2 ){ + pCur->nLine = sqlite3_value_bytes(argv[iArg]); + if( pCur->nLine>0 ){ + pCur->zLine = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); + if( pCur->zLine==0 ) return SQLITE_NOMEM; + } + } + if( pCur->zLine!=0 && pCur->zPrefix==0 ){ + int i = pCur->nLine; + while( i>0 && (isalnum(pCur->zLine[i-1]) || pCur->zLine[i-1]=='_') ){ + i--; + } + pCur->nPrefix = pCur->nLine - i; + if( pCur->nPrefix>0 ){ + pCur->zPrefix = sqlite3_mprintf("%.*s", pCur->nPrefix, pCur->zLine + i); + if( pCur->zPrefix==0 ) return SQLITE_NOMEM; + } + } + pCur->iRowid = 0; + pCur->ePhase = COMPLETION_FIRST_PHASE; + return completionNext(pVtabCursor); +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the completion virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** There are two hidden parameters that act as arguments to the table-valued +** function: "prefix" and "wholeline". Bit 0 of idxNum is set if "prefix" +** is available and bit 1 is set if "wholeline" is available. +*/ +static int completionBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int idxNum = 0; /* The query plan bitmask */ + int prefixIdx = -1; /* Index of the start= constraint, or -1 if none */ + int wholelineIdx = -1; /* Index of the stop= constraint, or -1 if none */ + int nArg = 0; /* Number of arguments that completeFilter() expects */ + const struct sqlite3_index_constraint *pConstraint; + + (void)(tab); /* Unused parameter */ + pConstraint = pIdxInfo->aConstraint; + for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case COMPLETION_COLUMN_PREFIX: + prefixIdx = i; + idxNum |= 1; + break; + case COMPLETION_COLUMN_WHOLELINE: + wholelineIdx = i; + idxNum |= 2; + break; + } + } + if( prefixIdx>=0 ){ + pIdxInfo->aConstraintUsage[prefixIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[prefixIdx].omit = 1; + } + if( wholelineIdx>=0 ){ + pIdxInfo->aConstraintUsage[wholelineIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[wholelineIdx].omit = 1; + } + pIdxInfo->idxNum = idxNum; + pIdxInfo->estimatedCost = (double)5000 - 1000*nArg; + pIdxInfo->estimatedRows = 500 - 100*nArg; + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** completion virtual table. +*/ +static sqlite3_module completionModule = { + 0, /* iVersion */ + 0, /* xCreate */ + completionConnect, /* xConnect */ + completionBestIndex, /* xBestIndex */ + completionDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + completionOpen, /* xOpen - open a cursor */ + completionClose, /* xClose - close a cursor */ + completionFilter, /* xFilter - configure scan constraints */ + completionNext, /* xNext - advance a cursor */ + completionEof, /* xEof - check for end of scan */ + completionColumn, /* xColumn - read data */ + completionRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ +}; + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +int sqlite3CompletionVtabInit(sqlite3 *db){ + int rc = SQLITE_OK; +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3_create_module(db, "completion", &completionModule, 0); +#endif + return rc; +} + +#ifdef _WIN32 + +#endif +int sqlite3_completion_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)(pzErrMsg); /* Unused parameter */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3CompletionVtabInit(db); +#endif + return rc; +} + +/************************* End ../ext/misc/completion.c ********************/ +/************************* Begin ../ext/misc/appendvfs.c ******************/ +/* +** 2017-10-20 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file implements a VFS shim that allows an SQLite database to be +** appended onto the end of some other file, such as an executable. +** +** A special record must appear at the end of the file that identifies the +** file as an appended database and provides the offset to the first page +** of the exposed content. (Or, it is the length of the content prefix.) +** For best performance page 1 should be located at a disk page boundary, +** though that is not required. +** +** When opening a database using this VFS, the connection might treat +** the file as an ordinary SQLite database, or it might treat it as a +** database appended onto some other file. The decision is made by +** applying the following rules in order: +** +** (1) An empty file is an ordinary database. +** +** (2) If the file ends with the appendvfs trailer string +** "Start-Of-SQLite3-NNNNNNNN" that file is an appended database. +** +** (3) If the file begins with the standard SQLite prefix string +** "SQLite format 3", that file is an ordinary database. +** +** (4) If none of the above apply and the SQLITE_OPEN_CREATE flag is +** set, then a new database is appended to the already existing file. +** +** (5) Otherwise, SQLITE_CANTOPEN is returned. +** +** To avoid unnecessary complications with the PENDING_BYTE, the size of +** the file containing the database is limited to 1GiB. (1073741824 bytes) +** This VFS will not read or write past the 1GiB mark. This restriction +** might be lifted in future versions. For now, if you need a larger +** database, then keep it in a separate file. +** +** If the file being opened is a plain database (not an appended one), then +** this shim is a pass-through into the default underlying VFS. (rule 3) +**/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include <string.h> +#include <assert.h> + +/* The append mark at the end of the database is: +** +** Start-Of-SQLite3-NNNNNNNN +** 123456789 123456789 12345 +** +** The NNNNNNNN represents a 64-bit big-endian unsigned integer which is +** the offset to page 1, and also the length of the prefix content. +*/ +#define APND_MARK_PREFIX "Start-Of-SQLite3-" +#define APND_MARK_PREFIX_SZ 17 +#define APND_MARK_FOS_SZ 8 +#define APND_MARK_SIZE (APND_MARK_PREFIX_SZ+APND_MARK_FOS_SZ) + +/* +** Maximum size of the combined prefix + database + append-mark. This +** must be less than 0x40000000 to avoid locking issues on Windows. +*/ +#define APND_MAX_SIZE (0x40000000) + +/* +** Try to align the database to an even multiple of APND_ROUNDUP bytes. +*/ +#ifndef APND_ROUNDUP +#define APND_ROUNDUP 4096 +#endif +#define APND_ALIGN_MASK ((sqlite3_int64)(APND_ROUNDUP-1)) +#define APND_START_ROUNDUP(fsz) (((fsz)+APND_ALIGN_MASK) & ~APND_ALIGN_MASK) + +/* +** Forward declaration of objects used by this utility +*/ +typedef struct sqlite3_vfs ApndVfs; +typedef struct ApndFile ApndFile; + +/* Access to a lower-level VFS that (might) implement dynamic loading, +** access to randomness, etc. +*/ +#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) +#define ORIGFILE(p) ((sqlite3_file*)(((ApndFile*)(p))+1)) + +/* An open appendvfs file +** +** An instance of this structure describes the appended database file. +** A separate sqlite3_file object is always appended. The appended +** sqlite3_file object (which can be accessed using ORIGFILE()) describes +** the entire file, including the prefix, the database, and the +** append-mark. +** +** The structure of an AppendVFS database is like this: +** +** +-------------+---------+----------+-------------+ +** | prefix-file | padding | database | append-mark | +** +-------------+---------+----------+-------------+ +** ^ ^ +** | | +** iPgOne iMark +** +** +** "prefix file" - file onto which the database has been appended. +** "padding" - zero or more bytes inserted so that "database" +** starts on an APND_ROUNDUP boundary +** "database" - The SQLite database file +** "append-mark" - The 25-byte "Start-Of-SQLite3-NNNNNNNN" that indicates +** the offset from the start of prefix-file to the start +** of "database". +** +** The size of the database is iMark - iPgOne. +** +** The NNNNNNNN in the "Start-Of-SQLite3-NNNNNNNN" suffix is the value +** of iPgOne stored as a big-ending 64-bit integer. +** +** iMark will be the size of the underlying file minus 25 (APND_MARKSIZE). +** Or, iMark is -1 to indicate that it has not yet been written. +*/ +struct ApndFile { + sqlite3_file base; /* Subclass. MUST BE FIRST! */ + sqlite3_int64 iPgOne; /* Offset to the start of the database */ + sqlite3_int64 iMark; /* Offset of the append mark. -1 if unwritten */ + /* Always followed by another sqlite3_file that describes the whole file */ +}; + +/* +** Methods for ApndFile +*/ +static int apndClose(sqlite3_file*); +static int apndRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int apndWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst); +static int apndTruncate(sqlite3_file*, sqlite3_int64 size); +static int apndSync(sqlite3_file*, int flags); +static int apndFileSize(sqlite3_file*, sqlite3_int64 *pSize); +static int apndLock(sqlite3_file*, int); +static int apndUnlock(sqlite3_file*, int); +static int apndCheckReservedLock(sqlite3_file*, int *pResOut); +static int apndFileControl(sqlite3_file*, int op, void *pArg); +static int apndSectorSize(sqlite3_file*); +static int apndDeviceCharacteristics(sqlite3_file*); +static int apndShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**); +static int apndShmLock(sqlite3_file*, int offset, int n, int flags); +static void apndShmBarrier(sqlite3_file*); +static int apndShmUnmap(sqlite3_file*, int deleteFlag); +static int apndFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); +static int apndUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p); + +/* +** Methods for ApndVfs +*/ +static int apndOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); +static int apndDelete(sqlite3_vfs*, const char *zName, int syncDir); +static int apndAccess(sqlite3_vfs*, const char *zName, int flags, int *); +static int apndFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); +static void *apndDlOpen(sqlite3_vfs*, const char *zFilename); +static void apndDlError(sqlite3_vfs*, int nByte, char *zErrMsg); +static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void); +static void apndDlClose(sqlite3_vfs*, void*); +static int apndRandomness(sqlite3_vfs*, int nByte, char *zOut); +static int apndSleep(sqlite3_vfs*, int microseconds); +static int apndCurrentTime(sqlite3_vfs*, double*); +static int apndGetLastError(sqlite3_vfs*, int, char *); +static int apndCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); +static int apndSetSystemCall(sqlite3_vfs*, const char*,sqlite3_syscall_ptr); +static sqlite3_syscall_ptr apndGetSystemCall(sqlite3_vfs*, const char *z); +static const char *apndNextSystemCall(sqlite3_vfs*, const char *zName); + +static sqlite3_vfs apnd_vfs = { + 3, /* iVersion (set when registered) */ + 0, /* szOsFile (set when registered) */ + 1024, /* mxPathname */ + 0, /* pNext */ + "apndvfs", /* zName */ + 0, /* pAppData (set when registered) */ + apndOpen, /* xOpen */ + apndDelete, /* xDelete */ + apndAccess, /* xAccess */ + apndFullPathname, /* xFullPathname */ + apndDlOpen, /* xDlOpen */ + apndDlError, /* xDlError */ + apndDlSym, /* xDlSym */ + apndDlClose, /* xDlClose */ + apndRandomness, /* xRandomness */ + apndSleep, /* xSleep */ + apndCurrentTime, /* xCurrentTime */ + apndGetLastError, /* xGetLastError */ + apndCurrentTimeInt64, /* xCurrentTimeInt64 */ + apndSetSystemCall, /* xSetSystemCall */ + apndGetSystemCall, /* xGetSystemCall */ + apndNextSystemCall /* xNextSystemCall */ +}; + +static const sqlite3_io_methods apnd_io_methods = { + 3, /* iVersion */ + apndClose, /* xClose */ + apndRead, /* xRead */ + apndWrite, /* xWrite */ + apndTruncate, /* xTruncate */ + apndSync, /* xSync */ + apndFileSize, /* xFileSize */ + apndLock, /* xLock */ + apndUnlock, /* xUnlock */ + apndCheckReservedLock, /* xCheckReservedLock */ + apndFileControl, /* xFileControl */ + apndSectorSize, /* xSectorSize */ + apndDeviceCharacteristics, /* xDeviceCharacteristics */ + apndShmMap, /* xShmMap */ + apndShmLock, /* xShmLock */ + apndShmBarrier, /* xShmBarrier */ + apndShmUnmap, /* xShmUnmap */ + apndFetch, /* xFetch */ + apndUnfetch /* xUnfetch */ +}; + +/* +** Close an apnd-file. +*/ +static int apndClose(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xClose(pFile); +} + +/* +** Read data from an apnd-file. +*/ +static int apndRead( + sqlite3_file *pFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + ApndFile *paf = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + return pFile->pMethods->xRead(pFile, zBuf, iAmt, paf->iPgOne+iOfst); +} + +/* +** Add the append-mark onto what should become the end of the file. +* If and only if this succeeds, internal ApndFile.iMark is updated. +* Parameter iWriteEnd is the appendvfs-relative offset of the new mark. +*/ +static int apndWriteMark( + ApndFile *paf, + sqlite3_file *pFile, + sqlite_int64 iWriteEnd +){ + sqlite_int64 iPgOne = paf->iPgOne; + unsigned char a[APND_MARK_SIZE]; + int i = APND_MARK_FOS_SZ; + int rc; + assert(pFile == ORIGFILE(paf)); + memcpy(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ); + while( --i >= 0 ){ + a[APND_MARK_PREFIX_SZ+i] = (unsigned char)(iPgOne & 0xff); + iPgOne >>= 8; + } + iWriteEnd += paf->iPgOne; + if( SQLITE_OK==(rc = pFile->pMethods->xWrite + (pFile, a, APND_MARK_SIZE, iWriteEnd)) ){ + paf->iMark = iWriteEnd; + } + return rc; +} + +/* +** Write data to an apnd-file. +*/ +static int apndWrite( + sqlite3_file *pFile, + const void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + ApndFile *paf = (ApndFile *)pFile; + sqlite_int64 iWriteEnd = iOfst + iAmt; + if( iWriteEnd>=APND_MAX_SIZE ) return SQLITE_FULL; + pFile = ORIGFILE(pFile); + /* If append-mark is absent or will be overwritten, write it. */ + if( paf->iMark < 0 || paf->iPgOne + iWriteEnd > paf->iMark ){ + int rc = apndWriteMark(paf, pFile, iWriteEnd); + if( SQLITE_OK!=rc ) return rc; + } + return pFile->pMethods->xWrite(pFile, zBuf, iAmt, paf->iPgOne+iOfst); +} + +/* +** Truncate an apnd-file. +*/ +static int apndTruncate(sqlite3_file *pFile, sqlite_int64 size){ + ApndFile *paf = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + /* The append mark goes out first so truncate failure does not lose it. */ + if( SQLITE_OK!=apndWriteMark(paf, pFile, size) ) return SQLITE_IOERR; + /* Truncate underlying file just past append mark */ + return pFile->pMethods->xTruncate(pFile, paf->iMark+APND_MARK_SIZE); +} + +/* +** Sync an apnd-file. +*/ +static int apndSync(sqlite3_file *pFile, int flags){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xSync(pFile, flags); +} + +/* +** Return the current file-size of an apnd-file. +** If the append mark is not yet there, the file-size is 0. +*/ +static int apndFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ + ApndFile *paf = (ApndFile *)pFile; + *pSize = ( paf->iMark >= 0 )? (paf->iMark - paf->iPgOne) : 0; + return SQLITE_OK; +} + +/* +** Lock an apnd-file. +*/ +static int apndLock(sqlite3_file *pFile, int eLock){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xLock(pFile, eLock); +} + +/* +** Unlock an apnd-file. +*/ +static int apndUnlock(sqlite3_file *pFile, int eLock){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xUnlock(pFile, eLock); +} + +/* +** Check if another file-handle holds a RESERVED lock on an apnd-file. +*/ +static int apndCheckReservedLock(sqlite3_file *pFile, int *pResOut){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xCheckReservedLock(pFile, pResOut); +} + +/* +** File control method. For custom operations on an apnd-file. +*/ +static int apndFileControl(sqlite3_file *pFile, int op, void *pArg){ + ApndFile *paf = (ApndFile *)pFile; + int rc; + pFile = ORIGFILE(pFile); + if( op==SQLITE_FCNTL_SIZE_HINT ) *(sqlite3_int64*)pArg += paf->iPgOne; + rc = pFile->pMethods->xFileControl(pFile, op, pArg); + if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){ + *(char**)pArg = sqlite3_mprintf("apnd(%lld)/%z", paf->iPgOne,*(char**)pArg); + } + return rc; +} + +/* +** Return the sector-size in bytes for an apnd-file. +*/ +static int apndSectorSize(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xSectorSize(pFile); +} + +/* +** Return the device characteristic flags supported by an apnd-file. +*/ +static int apndDeviceCharacteristics(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xDeviceCharacteristics(pFile); +} + +/* Create a shared memory file mapping */ +static int apndShmMap( + sqlite3_file *pFile, + int iPg, + int pgsz, + int bExtend, + void volatile **pp +){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmMap(pFile,iPg,pgsz,bExtend,pp); +} + +/* Perform locking on a shared-memory segment */ +static int apndShmLock(sqlite3_file *pFile, int offset, int n, int flags){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmLock(pFile,offset,n,flags); +} + +/* Memory barrier operation on shared memory */ +static void apndShmBarrier(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + pFile->pMethods->xShmBarrier(pFile); +} + +/* Unmap a shared memory segment */ +static int apndShmUnmap(sqlite3_file *pFile, int deleteFlag){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmUnmap(pFile,deleteFlag); +} + +/* Fetch a page of a memory-mapped file */ +static int apndFetch( + sqlite3_file *pFile, + sqlite3_int64 iOfst, + int iAmt, + void **pp +){ + ApndFile *p = (ApndFile *)pFile; + if( p->iMark < 0 || iOfst+iAmt > p->iMark ){ + return SQLITE_IOERR; /* Cannot read what is not yet there. */ + } + pFile = ORIGFILE(pFile); + return pFile->pMethods->xFetch(pFile, iOfst+p->iPgOne, iAmt, pp); +} + +/* Release a memory-mapped page */ +static int apndUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ + ApndFile *p = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + return pFile->pMethods->xUnfetch(pFile, iOfst+p->iPgOne, pPage); +} + +/* +** Try to read the append-mark off the end of a file. Return the +** start of the appended database if the append-mark is present. +** If there is no valid append-mark, return -1; +** +** An append-mark is only valid if the NNNNNNNN start-of-database offset +** indicates that the appended database contains at least one page. The +** start-of-database value must be a multiple of 512. +*/ +static sqlite3_int64 apndReadMark(sqlite3_int64 sz, sqlite3_file *pFile){ + int rc, i; + sqlite3_int64 iMark; + int msbs = 8 * (APND_MARK_FOS_SZ-1); + unsigned char a[APND_MARK_SIZE]; + + if( APND_MARK_SIZE!=(sz & 0x1ff) ) return -1; + rc = pFile->pMethods->xRead(pFile, a, APND_MARK_SIZE, sz-APND_MARK_SIZE); + if( rc ) return -1; + if( memcmp(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ)!=0 ) return -1; + iMark = ((sqlite3_int64)(a[APND_MARK_PREFIX_SZ] & 0x7f)) << msbs; + for(i=1; i<8; i++){ + msbs -= 8; + iMark |= (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]<<msbs; + } + if( iMark > (sz - APND_MARK_SIZE - 512) ) return -1; + if( iMark & 0x1ff ) return -1; + return iMark; +} + +static const char apvfsSqliteHdr[] = "SQLite format 3"; +/* +** Check to see if the file is an appendvfs SQLite database file. +** Return true iff it is such. Parameter sz is the file's size. +*/ +static int apndIsAppendvfsDatabase(sqlite3_int64 sz, sqlite3_file *pFile){ + int rc; + char zHdr[16]; + sqlite3_int64 iMark = apndReadMark(sz, pFile); + if( iMark>=0 ){ + /* If file has the correct end-marker, the expected odd size, and the + ** SQLite DB type marker where the end-marker puts it, then it + ** is an appendvfs database. + */ + rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), iMark); + if( SQLITE_OK==rc + && memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))==0 + && (sz & 0x1ff) == APND_MARK_SIZE + && sz>=512+APND_MARK_SIZE + ){ + return 1; /* It's an appendvfs database */ + } + } + return 0; +} + +/* +** Check to see if the file is an ordinary SQLite database file. +** Return true iff so. Parameter sz is the file's size. +*/ +static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){ + char zHdr[16]; + if( apndIsAppendvfsDatabase(sz, pFile) /* rule 2 */ + || (sz & 0x1ff) != 0 + || SQLITE_OK!=pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0) + || memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))!=0 + ){ + return 0; + }else{ + return 1; + } +} + +/* +** Open an apnd file handle. +*/ +static int apndOpen( + sqlite3_vfs *pApndVfs, + const char *zName, + sqlite3_file *pFile, + int flags, + int *pOutFlags +){ + ApndFile *pApndFile = (ApndFile*)pFile; + sqlite3_file *pBaseFile = ORIGFILE(pFile); + sqlite3_vfs *pBaseVfs = ORIGVFS(pApndVfs); + int rc; + sqlite3_int64 sz = 0; + if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){ + /* The appendvfs is not to be used for transient or temporary databases. + ** Just use the base VFS open to initialize the given file object and + ** open the underlying file. (Appendvfs is then unused for this file.) + */ + return pBaseVfs->xOpen(pBaseVfs, zName, pFile, flags, pOutFlags); + } + memset(pApndFile, 0, sizeof(ApndFile)); + pFile->pMethods = &apnd_io_methods; + pApndFile->iMark = -1; /* Append mark not yet written */ + + rc = pBaseVfs->xOpen(pBaseVfs, zName, pBaseFile, flags, pOutFlags); + if( rc==SQLITE_OK ){ + rc = pBaseFile->pMethods->xFileSize(pBaseFile, &sz); + if( rc ){ + pBaseFile->pMethods->xClose(pBaseFile); + } + } + if( rc ){ + pFile->pMethods = 0; + return rc; + } + if( apndIsOrdinaryDatabaseFile(sz, pBaseFile) ){ + /* The file being opened appears to be just an ordinary DB. Copy + ** the base dispatch-table so this instance mimics the base VFS. + */ + memmove(pApndFile, pBaseFile, pBaseVfs->szOsFile); + return SQLITE_OK; + } + pApndFile->iPgOne = apndReadMark(sz, pFile); + if( pApndFile->iPgOne>=0 ){ + pApndFile->iMark = sz - APND_MARK_SIZE; /* Append mark found */ + return SQLITE_OK; + } + if( (flags & SQLITE_OPEN_CREATE)==0 ){ + pBaseFile->pMethods->xClose(pBaseFile); + rc = SQLITE_CANTOPEN; + pFile->pMethods = 0; + }else{ + /* Round newly added appendvfs location to #define'd page boundary. + ** Note that nothing has yet been written to the underlying file. + ** The append mark will be written along with first content write. + ** Until then, paf->iMark value indicates it is not yet written. + */ + pApndFile->iPgOne = APND_START_ROUNDUP(sz); + } + return rc; +} + +/* +** Delete an apnd file. +** For an appendvfs, this could mean delete the appendvfs portion, +** leaving the appendee as it was before it gained an appendvfs. +** For now, this code deletes the underlying file too. +*/ +static int apndDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + return ORIGVFS(pVfs)->xDelete(ORIGVFS(pVfs), zPath, dirSync); +} + +/* +** All other VFS methods are pass-thrus. +*/ +static int apndAccess( + sqlite3_vfs *pVfs, + const char *zPath, + int flags, + int *pResOut +){ + return ORIGVFS(pVfs)->xAccess(ORIGVFS(pVfs), zPath, flags, pResOut); +} +static int apndFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, + char *zOut +){ + return ORIGVFS(pVfs)->xFullPathname(ORIGVFS(pVfs),zPath,nOut,zOut); +} +static void *apndDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath); +} +static void apndDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ + ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg); +} +static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){ + return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym); +} +static void apndDlClose(sqlite3_vfs *pVfs, void *pHandle){ + ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle); +} +static int apndRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ + return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut); +} +static int apndSleep(sqlite3_vfs *pVfs, int nMicro){ + return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro); +} +static int apndCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ + return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut); +} +static int apndGetLastError(sqlite3_vfs *pVfs, int a, char *b){ + return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b); +} +static int apndCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ + return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p); +} +static int apndSetSystemCall( + sqlite3_vfs *pVfs, + const char *zName, + sqlite3_syscall_ptr pCall +){ + return ORIGVFS(pVfs)->xSetSystemCall(ORIGVFS(pVfs),zName,pCall); +} +static sqlite3_syscall_ptr apndGetSystemCall( + sqlite3_vfs *pVfs, + const char *zName +){ + return ORIGVFS(pVfs)->xGetSystemCall(ORIGVFS(pVfs),zName); +} +static const char *apndNextSystemCall(sqlite3_vfs *pVfs, const char *zName){ + return ORIGVFS(pVfs)->xNextSystemCall(ORIGVFS(pVfs), zName); +} + + +#ifdef _WIN32 + +#endif +/* +** This routine is called when the extension is loaded. +** Register the new VFS. +*/ +int sqlite3_appendvfs_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + sqlite3_vfs *pOrig; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; + (void)db; + pOrig = sqlite3_vfs_find(0); + if( pOrig==0 ) return SQLITE_ERROR; + apnd_vfs.iVersion = pOrig->iVersion; + apnd_vfs.pAppData = pOrig; + apnd_vfs.szOsFile = pOrig->szOsFile + sizeof(ApndFile); + rc = sqlite3_vfs_register(&apnd_vfs, 0); +#ifdef APPENDVFS_TEST + if( rc==SQLITE_OK ){ + rc = sqlite3_auto_extension((void(*)(void))apndvfsRegister); + } +#endif + if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY; + return rc; +} + +/************************* End ../ext/misc/appendvfs.c ********************/ +#endif +#ifdef SQLITE_HAVE_ZLIB +/************************* Begin ../ext/misc/zipfile.c ******************/ +/* +** 2017-12-26 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file implements a virtual table for reading and writing ZIP archive +** files. +** +** Usage example: +** +** SELECT name, sz, datetime(mtime,'unixepoch') FROM zipfile($filename); +** +** Current limitations: +** +** * No support for encryption +** * No support for ZIP archives spanning multiple files +** * No support for zip64 extensions +** * Only the "inflate/deflate" (zlib) compression method is supported +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <zlib.h> + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +#ifndef SQLITE_AMALGAMATION + +#ifndef UINT32_TYPE +# ifdef HAVE_UINT32_T +# define UINT32_TYPE uint32_t +# else +# define UINT32_TYPE unsigned int +# endif +#endif +#ifndef UINT16_TYPE +# ifdef HAVE_UINT16_T +# define UINT16_TYPE uint16_t +# else +# define UINT16_TYPE unsigned short int +# endif +#endif +/* typedef sqlite3_int64 i64; */ +/* typedef unsigned char u8; */ +/* typedef UINT32_TYPE u32; // 4-byte unsigned integer // */ +/* typedef UINT16_TYPE u16; // 2-byte unsigned integer // */ +#define MIN(a,b) ((a)<(b) ? (a) : (b)) + +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif + +#endif /* SQLITE_AMALGAMATION */ + +/* +** Definitions for mode bitmasks S_IFDIR, S_IFREG and S_IFLNK. +** +** In some ways it would be better to obtain these values from system +** header files. But, the dependency is undesirable and (a) these +** have been stable for decades, (b) the values are part of POSIX and +** are also made explicit in [man stat], and (c) are part of the +** file format for zip archives. +*/ +#ifndef S_IFDIR +# define S_IFDIR 0040000 +#endif +#ifndef S_IFREG +# define S_IFREG 0100000 +#endif +#ifndef S_IFLNK +# define S_IFLNK 0120000 +#endif + +static const char ZIPFILE_SCHEMA[] = + "CREATE TABLE y(" + "name PRIMARY KEY," /* 0: Name of file in zip archive */ + "mode," /* 1: POSIX mode for file */ + "mtime," /* 2: Last modification time (secs since 1970)*/ + "sz," /* 3: Size of object */ + "rawdata," /* 4: Raw data */ + "data," /* 5: Uncompressed data */ + "method," /* 6: Compression method (integer) */ + "z HIDDEN" /* 7: Name of zip file */ + ") WITHOUT ROWID;"; + +#define ZIPFILE_F_COLUMN_IDX 7 /* Index of column "file" in the above */ +#define ZIPFILE_BUFFER_SIZE (64*1024) + + +/* +** Magic numbers used to read and write zip files. +** +** ZIPFILE_NEWENTRY_MADEBY: +** Use this value for the "version-made-by" field in new zip file +** entries. The upper byte indicates "unix", and the lower byte +** indicates that the zip file matches pkzip specification 3.0. +** This is what info-zip seems to do. +** +** ZIPFILE_NEWENTRY_REQUIRED: +** Value for "version-required-to-extract" field of new entries. +** Version 2.0 is required to support folders and deflate compression. +** +** ZIPFILE_NEWENTRY_FLAGS: +** Value for "general-purpose-bit-flags" field of new entries. Bit +** 11 means "utf-8 filename and comment". +** +** ZIPFILE_SIGNATURE_CDS: +** First 4 bytes of a valid CDS record. +** +** ZIPFILE_SIGNATURE_LFH: +** First 4 bytes of a valid LFH record. +** +** ZIPFILE_SIGNATURE_EOCD +** First 4 bytes of a valid EOCD record. +*/ +#define ZIPFILE_EXTRA_TIMESTAMP 0x5455 +#define ZIPFILE_NEWENTRY_MADEBY ((3<<8) + 30) +#define ZIPFILE_NEWENTRY_REQUIRED 20 +#define ZIPFILE_NEWENTRY_FLAGS 0x800 +#define ZIPFILE_SIGNATURE_CDS 0x02014b50 +#define ZIPFILE_SIGNATURE_LFH 0x04034b50 +#define ZIPFILE_SIGNATURE_EOCD 0x06054b50 + +/* +** The sizes of the fixed-size part of each of the three main data +** structures in a zip archive. +*/ +#define ZIPFILE_LFH_FIXED_SZ 30 +#define ZIPFILE_EOCD_FIXED_SZ 22 +#define ZIPFILE_CDS_FIXED_SZ 46 + +/* +*** 4.3.16 End of central directory record: +*** +*** end of central dir signature 4 bytes (0x06054b50) +*** number of this disk 2 bytes +*** number of the disk with the +*** start of the central directory 2 bytes +*** total number of entries in the +*** central directory on this disk 2 bytes +*** total number of entries in +*** the central directory 2 bytes +*** size of the central directory 4 bytes +*** offset of start of central +*** directory with respect to +*** the starting disk number 4 bytes +*** .ZIP file comment length 2 bytes +*** .ZIP file comment (variable size) +*/ +typedef struct ZipfileEOCD ZipfileEOCD; +struct ZipfileEOCD { + u16 iDisk; + u16 iFirstDisk; + u16 nEntry; + u16 nEntryTotal; + u32 nSize; + u32 iOffset; +}; + +/* +*** 4.3.12 Central directory structure: +*** +*** ... +*** +*** central file header signature 4 bytes (0x02014b50) +*** version made by 2 bytes +*** version needed to extract 2 bytes +*** general purpose bit flag 2 bytes +*** compression method 2 bytes +*** last mod file time 2 bytes +*** last mod file date 2 bytes +*** crc-32 4 bytes +*** compressed size 4 bytes +*** uncompressed size 4 bytes +*** file name length 2 bytes +*** extra field length 2 bytes +*** file comment length 2 bytes +*** disk number start 2 bytes +*** internal file attributes 2 bytes +*** external file attributes 4 bytes +*** relative offset of local header 4 bytes +*/ +typedef struct ZipfileCDS ZipfileCDS; +struct ZipfileCDS { + u16 iVersionMadeBy; + u16 iVersionExtract; + u16 flags; + u16 iCompression; + u16 mTime; + u16 mDate; + u32 crc32; + u32 szCompressed; + u32 szUncompressed; + u16 nFile; + u16 nExtra; + u16 nComment; + u16 iDiskStart; + u16 iInternalAttr; + u32 iExternalAttr; + u32 iOffset; + char *zFile; /* Filename (sqlite3_malloc()) */ +}; + +/* +*** 4.3.7 Local file header: +*** +*** local file header signature 4 bytes (0x04034b50) +*** version needed to extract 2 bytes +*** general purpose bit flag 2 bytes +*** compression method 2 bytes +*** last mod file time 2 bytes +*** last mod file date 2 bytes +*** crc-32 4 bytes +*** compressed size 4 bytes +*** uncompressed size 4 bytes +*** file name length 2 bytes +*** extra field length 2 bytes +*** +*/ +typedef struct ZipfileLFH ZipfileLFH; +struct ZipfileLFH { + u16 iVersionExtract; + u16 flags; + u16 iCompression; + u16 mTime; + u16 mDate; + u32 crc32; + u32 szCompressed; + u32 szUncompressed; + u16 nFile; + u16 nExtra; +}; + +typedef struct ZipfileEntry ZipfileEntry; +struct ZipfileEntry { + ZipfileCDS cds; /* Parsed CDS record */ + u32 mUnixTime; /* Modification time, in UNIX format */ + u8 *aExtra; /* cds.nExtra+cds.nComment bytes of extra data */ + i64 iDataOff; /* Offset to data in file (if aData==0) */ + u8 *aData; /* cds.szCompressed bytes of compressed data */ + ZipfileEntry *pNext; /* Next element in in-memory CDS */ +}; + +/* +** Cursor type for zipfile tables. +*/ +typedef struct ZipfileCsr ZipfileCsr; +struct ZipfileCsr { + sqlite3_vtab_cursor base; /* Base class - must be first */ + i64 iId; /* Cursor ID */ + u8 bEof; /* True when at EOF */ + u8 bNoop; /* If next xNext() call is no-op */ + + /* Used outside of write transactions */ + FILE *pFile; /* Zip file */ + i64 iNextOff; /* Offset of next record in central directory */ + ZipfileEOCD eocd; /* Parse of central directory record */ + + ZipfileEntry *pFreeEntry; /* Free this list when cursor is closed or reset */ + ZipfileEntry *pCurrent; /* Current entry */ + ZipfileCsr *pCsrNext; /* Next cursor on same virtual table */ +}; + +typedef struct ZipfileTab ZipfileTab; +struct ZipfileTab { + sqlite3_vtab base; /* Base class - must be first */ + char *zFile; /* Zip file this table accesses (may be NULL) */ + sqlite3 *db; /* Host database connection */ + u8 *aBuffer; /* Temporary buffer used for various tasks */ + + ZipfileCsr *pCsrList; /* List of cursors */ + i64 iNextCsrid; + + /* The following are used by write transactions only */ + ZipfileEntry *pFirstEntry; /* Linked list of all files (if pWriteFd!=0) */ + ZipfileEntry *pLastEntry; /* Last element in pFirstEntry list */ + FILE *pWriteFd; /* File handle open on zip archive */ + i64 szCurrent; /* Current size of zip archive */ + i64 szOrig; /* Size of archive at start of transaction */ +}; + +/* +** Set the error message contained in context ctx to the results of +** vprintf(zFmt, ...). +*/ +static void zipfileCtxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){ + char *zMsg = 0; + va_list ap; + va_start(ap, zFmt); + zMsg = sqlite3_vmprintf(zFmt, ap); + sqlite3_result_error(ctx, zMsg, -1); + sqlite3_free(zMsg); + va_end(ap); +} + +/* +** If string zIn is quoted, dequote it in place. Otherwise, if the string +** is not quoted, do nothing. +*/ +static void zipfileDequote(char *zIn){ + char q = zIn[0]; + if( q=='"' || q=='\'' || q=='`' || q=='[' ){ + int iIn = 1; + int iOut = 0; + if( q=='[' ) q = ']'; + while( ALWAYS(zIn[iIn]) ){ + char c = zIn[iIn++]; + if( c==q && zIn[iIn++]!=q ) break; + zIn[iOut++] = c; + } + zIn[iOut] = '\0'; + } +} + +/* +** Construct a new ZipfileTab virtual table object. +** +** argv[0] -> module name ("zipfile") +** argv[1] -> database name +** argv[2] -> table name +** argv[...] -> "column name" and other module argument fields. +*/ +static int zipfileConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + int nByte = sizeof(ZipfileTab) + ZIPFILE_BUFFER_SIZE; + int nFile = 0; + const char *zFile = 0; + ZipfileTab *pNew = 0; + int rc; + (void)pAux; + + /* If the table name is not "zipfile", require that the argument be + ** specified. This stops zipfile tables from being created as: + ** + ** CREATE VIRTUAL TABLE zzz USING zipfile(); + ** + ** It does not prevent: + ** + ** CREATE VIRTUAL TABLE zipfile USING zipfile(); + */ + assert( 0==sqlite3_stricmp(argv[0], "zipfile") ); + if( (0!=sqlite3_stricmp(argv[2], "zipfile") && argc<4) || argc>4 ){ + *pzErr = sqlite3_mprintf("zipfile constructor requires one argument"); + return SQLITE_ERROR; + } + + if( argc>3 ){ + zFile = argv[3]; + nFile = (int)strlen(zFile)+1; + } + + rc = sqlite3_declare_vtab(db, ZIPFILE_SCHEMA); + if( rc==SQLITE_OK ){ + pNew = (ZipfileTab*)sqlite3_malloc64((sqlite3_int64)nByte+nFile); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, nByte+nFile); + pNew->db = db; + pNew->aBuffer = (u8*)&pNew[1]; + if( zFile ){ + pNew->zFile = (char*)&pNew->aBuffer[ZIPFILE_BUFFER_SIZE]; + memcpy(pNew->zFile, zFile, nFile); + zipfileDequote(pNew->zFile); + } + } + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + *ppVtab = (sqlite3_vtab*)pNew; + return rc; +} + +/* +** Free the ZipfileEntry structure indicated by the only argument. +*/ +static void zipfileEntryFree(ZipfileEntry *p){ + if( p ){ + sqlite3_free(p->cds.zFile); + sqlite3_free(p); + } +} + +/* +** Release resources that should be freed at the end of a write +** transaction. +*/ +static void zipfileCleanupTransaction(ZipfileTab *pTab){ + ZipfileEntry *pEntry; + ZipfileEntry *pNext; + + if( pTab->pWriteFd ){ + fclose(pTab->pWriteFd); + pTab->pWriteFd = 0; + } + for(pEntry=pTab->pFirstEntry; pEntry; pEntry=pNext){ + pNext = pEntry->pNext; + zipfileEntryFree(pEntry); + } + pTab->pFirstEntry = 0; + pTab->pLastEntry = 0; + pTab->szCurrent = 0; + pTab->szOrig = 0; +} + +/* +** This method is the destructor for zipfile vtab objects. +*/ +static int zipfileDisconnect(sqlite3_vtab *pVtab){ + zipfileCleanupTransaction((ZipfileTab*)pVtab); + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new ZipfileCsr object. +*/ +static int zipfileOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){ + ZipfileTab *pTab = (ZipfileTab*)p; + ZipfileCsr *pCsr; + pCsr = sqlite3_malloc(sizeof(*pCsr)); + *ppCsr = (sqlite3_vtab_cursor*)pCsr; + if( pCsr==0 ){ + return SQLITE_NOMEM; + } + memset(pCsr, 0, sizeof(*pCsr)); + pCsr->iId = ++pTab->iNextCsrid; + pCsr->pCsrNext = pTab->pCsrList; + pTab->pCsrList = pCsr; + return SQLITE_OK; +} + +/* +** Reset a cursor back to the state it was in when first returned +** by zipfileOpen(). +*/ +static void zipfileResetCursor(ZipfileCsr *pCsr){ + ZipfileEntry *p; + ZipfileEntry *pNext; + + pCsr->bEof = 0; + if( pCsr->pFile ){ + fclose(pCsr->pFile); + pCsr->pFile = 0; + zipfileEntryFree(pCsr->pCurrent); + pCsr->pCurrent = 0; + } + + for(p=pCsr->pFreeEntry; p; p=pNext){ + pNext = p->pNext; + zipfileEntryFree(p); + } +} + +/* +** Destructor for an ZipfileCsr. +*/ +static int zipfileClose(sqlite3_vtab_cursor *cur){ + ZipfileCsr *pCsr = (ZipfileCsr*)cur; + ZipfileTab *pTab = (ZipfileTab*)(pCsr->base.pVtab); + ZipfileCsr **pp; + zipfileResetCursor(pCsr); + + /* Remove this cursor from the ZipfileTab.pCsrList list. */ + for(pp=&pTab->pCsrList; *pp!=pCsr; pp=&((*pp)->pCsrNext)); + *pp = pCsr->pCsrNext; + + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** Set the error message for the virtual table associated with cursor +** pCsr to the results of vprintf(zFmt, ...). +*/ +static void zipfileTableErr(ZipfileTab *pTab, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + sqlite3_free(pTab->base.zErrMsg); + pTab->base.zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} +static void zipfileCursorErr(ZipfileCsr *pCsr, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + sqlite3_free(pCsr->base.pVtab->zErrMsg); + pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} + +/* +** Read nRead bytes of data from offset iOff of file pFile into buffer +** aRead[]. Return SQLITE_OK if successful, or an SQLite error code +** otherwise. +** +** If an error does occur, output variable (*pzErrmsg) may be set to point +** to an English language error message. It is the responsibility of the +** caller to eventually free this buffer using +** sqlite3_free(). +*/ +static int zipfileReadData( + FILE *pFile, /* Read from this file */ + u8 *aRead, /* Read into this buffer */ + int nRead, /* Number of bytes to read */ + i64 iOff, /* Offset to read from */ + char **pzErrmsg /* OUT: Error message (from sqlite3_malloc) */ +){ + size_t n; + fseek(pFile, (long)iOff, SEEK_SET); + n = fread(aRead, 1, nRead, pFile); + if( (int)n!=nRead ){ + *pzErrmsg = sqlite3_mprintf("error in fread()"); + return SQLITE_ERROR; + } + return SQLITE_OK; +} + +static int zipfileAppendData( + ZipfileTab *pTab, + const u8 *aWrite, + int nWrite +){ + if( nWrite>0 ){ + size_t n = nWrite; + fseek(pTab->pWriteFd, (long)pTab->szCurrent, SEEK_SET); + n = fwrite(aWrite, 1, nWrite, pTab->pWriteFd); + if( (int)n!=nWrite ){ + pTab->base.zErrMsg = sqlite3_mprintf("error in fwrite()"); + return SQLITE_ERROR; + } + pTab->szCurrent += nWrite; + } + return SQLITE_OK; +} + +/* +** Read and return a 16-bit little-endian unsigned integer from buffer aBuf. +*/ +static u16 zipfileGetU16(const u8 *aBuf){ + return (aBuf[1] << 8) + aBuf[0]; +} + +/* +** Read and return a 32-bit little-endian unsigned integer from buffer aBuf. +*/ +static u32 zipfileGetU32(const u8 *aBuf){ + if( aBuf==0 ) return 0; + return ((u32)(aBuf[3]) << 24) + + ((u32)(aBuf[2]) << 16) + + ((u32)(aBuf[1]) << 8) + + ((u32)(aBuf[0]) << 0); +} + +/* +** Write a 16-bit little endiate integer into buffer aBuf. +*/ +static void zipfilePutU16(u8 *aBuf, u16 val){ + aBuf[0] = val & 0xFF; + aBuf[1] = (val>>8) & 0xFF; +} + +/* +** Write a 32-bit little endiate integer into buffer aBuf. +*/ +static void zipfilePutU32(u8 *aBuf, u32 val){ + aBuf[0] = val & 0xFF; + aBuf[1] = (val>>8) & 0xFF; + aBuf[2] = (val>>16) & 0xFF; + aBuf[3] = (val>>24) & 0xFF; +} + +#define zipfileRead32(aBuf) ( aBuf+=4, zipfileGetU32(aBuf-4) ) +#define zipfileRead16(aBuf) ( aBuf+=2, zipfileGetU16(aBuf-2) ) + +#define zipfileWrite32(aBuf,val) { zipfilePutU32(aBuf,val); aBuf+=4; } +#define zipfileWrite16(aBuf,val) { zipfilePutU16(aBuf,val); aBuf+=2; } + +/* +** Magic numbers used to read CDS records. +*/ +#define ZIPFILE_CDS_NFILE_OFF 28 +#define ZIPFILE_CDS_SZCOMPRESSED_OFF 20 + +/* +** Decode the CDS record in buffer aBuf into (*pCDS). Return SQLITE_ERROR +** if the record is not well-formed, or SQLITE_OK otherwise. +*/ +static int zipfileReadCDS(u8 *aBuf, ZipfileCDS *pCDS){ + u8 *aRead = aBuf; + u32 sig = zipfileRead32(aRead); + int rc = SQLITE_OK; + if( sig!=ZIPFILE_SIGNATURE_CDS ){ + rc = SQLITE_ERROR; + }else{ + pCDS->iVersionMadeBy = zipfileRead16(aRead); + pCDS->iVersionExtract = zipfileRead16(aRead); + pCDS->flags = zipfileRead16(aRead); + pCDS->iCompression = zipfileRead16(aRead); + pCDS->mTime = zipfileRead16(aRead); + pCDS->mDate = zipfileRead16(aRead); + pCDS->crc32 = zipfileRead32(aRead); + pCDS->szCompressed = zipfileRead32(aRead); + pCDS->szUncompressed = zipfileRead32(aRead); + assert( aRead==&aBuf[ZIPFILE_CDS_NFILE_OFF] ); + pCDS->nFile = zipfileRead16(aRead); + pCDS->nExtra = zipfileRead16(aRead); + pCDS->nComment = zipfileRead16(aRead); + pCDS->iDiskStart = zipfileRead16(aRead); + pCDS->iInternalAttr = zipfileRead16(aRead); + pCDS->iExternalAttr = zipfileRead32(aRead); + pCDS->iOffset = zipfileRead32(aRead); + assert( aRead==&aBuf[ZIPFILE_CDS_FIXED_SZ] ); + } + + return rc; +} + +/* +** Decode the LFH record in buffer aBuf into (*pLFH). Return SQLITE_ERROR +** if the record is not well-formed, or SQLITE_OK otherwise. +*/ +static int zipfileReadLFH( + u8 *aBuffer, + ZipfileLFH *pLFH +){ + u8 *aRead = aBuffer; + int rc = SQLITE_OK; + + u32 sig = zipfileRead32(aRead); + if( sig!=ZIPFILE_SIGNATURE_LFH ){ + rc = SQLITE_ERROR; + }else{ + pLFH->iVersionExtract = zipfileRead16(aRead); + pLFH->flags = zipfileRead16(aRead); + pLFH->iCompression = zipfileRead16(aRead); + pLFH->mTime = zipfileRead16(aRead); + pLFH->mDate = zipfileRead16(aRead); + pLFH->crc32 = zipfileRead32(aRead); + pLFH->szCompressed = zipfileRead32(aRead); + pLFH->szUncompressed = zipfileRead32(aRead); + pLFH->nFile = zipfileRead16(aRead); + pLFH->nExtra = zipfileRead16(aRead); + } + return rc; +} + + +/* +** Buffer aExtra (size nExtra bytes) contains zip archive "extra" fields. +** Scan through this buffer to find an "extra-timestamp" field. If one +** exists, extract the 32-bit modification-timestamp from it and store +** the value in output parameter *pmTime. +** +** Zero is returned if no extra-timestamp record could be found (and so +** *pmTime is left unchanged), or non-zero otherwise. +** +** The general format of an extra field is: +** +** Header ID 2 bytes +** Data Size 2 bytes +** Data N bytes +*/ +static int zipfileScanExtra(u8 *aExtra, int nExtra, u32 *pmTime){ + int ret = 0; + u8 *p = aExtra; + u8 *pEnd = &aExtra[nExtra]; + + while( p<pEnd ){ + u16 id = zipfileRead16(p); + u16 nByte = zipfileRead16(p); + + switch( id ){ + case ZIPFILE_EXTRA_TIMESTAMP: { + u8 b = p[0]; + if( b & 0x01 ){ /* 0x01 -> modtime is present */ + *pmTime = zipfileGetU32(&p[1]); + ret = 1; + } + break; + } + } + + p += nByte; + } + return ret; +} + +/* +** Convert the standard MS-DOS timestamp stored in the mTime and mDate +** fields of the CDS structure passed as the only argument to a 32-bit +** UNIX seconds-since-the-epoch timestamp. Return the result. +** +** "Standard" MS-DOS time format: +** +** File modification time: +** Bits 00-04: seconds divided by 2 +** Bits 05-10: minute +** Bits 11-15: hour +** File modification date: +** Bits 00-04: day +** Bits 05-08: month (1-12) +** Bits 09-15: years from 1980 +** +** https://msdn.microsoft.com/en-us/library/9kkf9tah.aspx +*/ +static u32 zipfileMtime(ZipfileCDS *pCDS){ + int Y,M,D,X1,X2,A,B,sec,min,hr; + i64 JDsec; + Y = (1980 + ((pCDS->mDate >> 9) & 0x7F)); + M = ((pCDS->mDate >> 5) & 0x0F); + D = (pCDS->mDate & 0x1F); + sec = (pCDS->mTime & 0x1F)*2; + min = (pCDS->mTime >> 5) & 0x3F; + hr = (pCDS->mTime >> 11) & 0x1F; + if( M<=2 ){ + Y--; + M += 12; + } + X1 = 36525*(Y+4716)/100; + X2 = 306001*(M+1)/10000; + A = Y/100; + B = 2 - A + (A/4); + JDsec = (i64)((X1 + X2 + D + B - 1524.5)*86400) + hr*3600 + min*60 + sec; + return (u32)(JDsec - (i64)24405875*(i64)8640); +} + +/* +** The opposite of zipfileMtime(). This function populates the mTime and +** mDate fields of the CDS structure passed as the first argument according +** to the UNIX timestamp value passed as the second. +*/ +static void zipfileMtimeToDos(ZipfileCDS *pCds, u32 mUnixTime){ + /* Convert unix timestamp to JD (2440588 is noon on 1/1/1970) */ + i64 JD = (i64)2440588 + mUnixTime / (24*60*60); + + int A, B, C, D, E; + int yr, mon, day; + int hr, min, sec; + + A = (int)((JD - 1867216.25)/36524.25); + A = (int)(JD + 1 + A - (A/4)); + B = A + 1524; + C = (int)((B - 122.1)/365.25); + D = (36525*(C&32767))/100; + E = (int)((B-D)/30.6001); + + day = B - D - (int)(30.6001*E); + mon = (E<14 ? E-1 : E-13); + yr = mon>2 ? C-4716 : C-4715; + + hr = (mUnixTime % (24*60*60)) / (60*60); + min = (mUnixTime % (60*60)) / 60; + sec = (mUnixTime % 60); + + if( yr>=1980 ){ + pCds->mDate = (u16)(day + (mon << 5) + ((yr-1980) << 9)); + pCds->mTime = (u16)(sec/2 + (min<<5) + (hr<<11)); + }else{ + pCds->mDate = pCds->mTime = 0; + } + + assert( mUnixTime<315507600 + || mUnixTime==zipfileMtime(pCds) + || ((mUnixTime % 2) && mUnixTime-1==zipfileMtime(pCds)) + /* || (mUnixTime % 2) */ + ); +} + +/* +** If aBlob is not NULL, then it is a pointer to a buffer (nBlob bytes in +** size) containing an entire zip archive image. Or, if aBlob is NULL, +** then pFile is a file-handle open on a zip file. In either case, this +** function creates a ZipfileEntry object based on the zip archive entry +** for which the CDS record is at offset iOff. +** +** If successful, SQLITE_OK is returned and (*ppEntry) set to point to +** the new object. Otherwise, an SQLite error code is returned and the +** final value of (*ppEntry) undefined. +*/ +static int zipfileGetEntry( + ZipfileTab *pTab, /* Store any error message here */ + const u8 *aBlob, /* Pointer to in-memory file image */ + int nBlob, /* Size of aBlob[] in bytes */ + FILE *pFile, /* If aBlob==0, read from this file */ + i64 iOff, /* Offset of CDS record */ + ZipfileEntry **ppEntry /* OUT: Pointer to new object */ +){ + u8 *aRead; + char **pzErr = &pTab->base.zErrMsg; + int rc = SQLITE_OK; + (void)nBlob; + + if( aBlob==0 ){ + aRead = pTab->aBuffer; + rc = zipfileReadData(pFile, aRead, ZIPFILE_CDS_FIXED_SZ, iOff, pzErr); + }else{ + aRead = (u8*)&aBlob[iOff]; + } + + if( rc==SQLITE_OK ){ + sqlite3_int64 nAlloc; + ZipfileEntry *pNew; + + int nFile = zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF]); + int nExtra = zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF+2]); + nExtra += zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF+4]); + + nAlloc = sizeof(ZipfileEntry) + nExtra; + if( aBlob ){ + nAlloc += zipfileGetU32(&aRead[ZIPFILE_CDS_SZCOMPRESSED_OFF]); + } + + pNew = (ZipfileEntry*)sqlite3_malloc64(nAlloc); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pNew, 0, sizeof(ZipfileEntry)); + rc = zipfileReadCDS(aRead, &pNew->cds); + if( rc!=SQLITE_OK ){ + *pzErr = sqlite3_mprintf("failed to read CDS at offset %lld", iOff); + }else if( aBlob==0 ){ + rc = zipfileReadData( + pFile, aRead, nExtra+nFile, iOff+ZIPFILE_CDS_FIXED_SZ, pzErr + ); + }else{ + aRead = (u8*)&aBlob[iOff + ZIPFILE_CDS_FIXED_SZ]; + } + } + + if( rc==SQLITE_OK ){ + u32 *pt = &pNew->mUnixTime; + pNew->cds.zFile = sqlite3_mprintf("%.*s", nFile, aRead); + pNew->aExtra = (u8*)&pNew[1]; + memcpy(pNew->aExtra, &aRead[nFile], nExtra); + if( pNew->cds.zFile==0 ){ + rc = SQLITE_NOMEM; + }else if( 0==zipfileScanExtra(&aRead[nFile], pNew->cds.nExtra, pt) ){ + pNew->mUnixTime = zipfileMtime(&pNew->cds); + } + } + + if( rc==SQLITE_OK ){ + static const int szFix = ZIPFILE_LFH_FIXED_SZ; + ZipfileLFH lfh; + if( pFile ){ + rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr); + }else{ + aRead = (u8*)&aBlob[pNew->cds.iOffset]; + } + + if( rc==SQLITE_OK ) rc = zipfileReadLFH(aRead, &lfh); + if( rc==SQLITE_OK ){ + pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ; + pNew->iDataOff += lfh.nFile + lfh.nExtra; + if( aBlob && pNew->cds.szCompressed ){ + pNew->aData = &pNew->aExtra[nExtra]; + memcpy(pNew->aData, &aBlob[pNew->iDataOff], pNew->cds.szCompressed); + } + }else{ + *pzErr = sqlite3_mprintf("failed to read LFH at offset %d", + (int)pNew->cds.iOffset + ); + } + } + + if( rc!=SQLITE_OK ){ + zipfileEntryFree(pNew); + }else{ + *ppEntry = pNew; + } + } + + return rc; +} + +/* +** Advance an ZipfileCsr to its next row of output. +*/ +static int zipfileNext(sqlite3_vtab_cursor *cur){ + ZipfileCsr *pCsr = (ZipfileCsr*)cur; + int rc = SQLITE_OK; + + if( pCsr->pFile ){ + i64 iEof = pCsr->eocd.iOffset + pCsr->eocd.nSize; + zipfileEntryFree(pCsr->pCurrent); + pCsr->pCurrent = 0; + if( pCsr->iNextOff>=iEof ){ + pCsr->bEof = 1; + }else{ + ZipfileEntry *p = 0; + ZipfileTab *pTab = (ZipfileTab*)(cur->pVtab); + rc = zipfileGetEntry(pTab, 0, 0, pCsr->pFile, pCsr->iNextOff, &p); + if( rc==SQLITE_OK ){ + pCsr->iNextOff += ZIPFILE_CDS_FIXED_SZ; + pCsr->iNextOff += (int)p->cds.nExtra + p->cds.nFile + p->cds.nComment; + } + pCsr->pCurrent = p; + } + }else{ + if( !pCsr->bNoop ){ + pCsr->pCurrent = pCsr->pCurrent->pNext; + } + if( pCsr->pCurrent==0 ){ + pCsr->bEof = 1; + } + } + + pCsr->bNoop = 0; + return rc; +} + +static void zipfileFree(void *p) { + sqlite3_free(p); +} + +/* +** Buffer aIn (size nIn bytes) contains compressed data. Uncompressed, the +** size is nOut bytes. This function uncompresses the data and sets the +** return value in context pCtx to the result (a blob). +** +** If an error occurs, an error code is left in pCtx instead. +*/ +static void zipfileInflate( + sqlite3_context *pCtx, /* Store result here */ + const u8 *aIn, /* Compressed data */ + int nIn, /* Size of buffer aIn[] in bytes */ + int nOut /* Expected output size */ +){ + u8 *aRes = sqlite3_malloc(nOut); + if( aRes==0 ){ + sqlite3_result_error_nomem(pCtx); + }else{ + int err; + z_stream str; + memset(&str, 0, sizeof(str)); + + str.next_in = (Byte*)aIn; + str.avail_in = nIn; + str.next_out = (Byte*)aRes; + str.avail_out = nOut; + + err = inflateInit2(&str, -15); + if( err!=Z_OK ){ + zipfileCtxErrorMsg(pCtx, "inflateInit2() failed (%d)", err); + }else{ + err = inflate(&str, Z_NO_FLUSH); + if( err!=Z_STREAM_END ){ + zipfileCtxErrorMsg(pCtx, "inflate() failed (%d)", err); + }else{ + sqlite3_result_blob(pCtx, aRes, nOut, zipfileFree); + aRes = 0; + } + } + sqlite3_free(aRes); + inflateEnd(&str); + } +} + +/* +** Buffer aIn (size nIn bytes) contains uncompressed data. This function +** compresses it and sets (*ppOut) to point to a buffer containing the +** compressed data. The caller is responsible for eventually calling +** sqlite3_free() to release buffer (*ppOut). Before returning, (*pnOut) +** is set to the size of buffer (*ppOut) in bytes. +** +** If no error occurs, SQLITE_OK is returned. Otherwise, an SQLite error +** code is returned and an error message left in virtual-table handle +** pTab. The values of (*ppOut) and (*pnOut) are left unchanged in this +** case. +*/ +static int zipfileDeflate( + const u8 *aIn, int nIn, /* Input */ + u8 **ppOut, int *pnOut, /* Output */ + char **pzErr /* OUT: Error message */ +){ + int rc = SQLITE_OK; + sqlite3_int64 nAlloc; + z_stream str; + u8 *aOut; + + memset(&str, 0, sizeof(str)); + str.next_in = (Bytef*)aIn; + str.avail_in = nIn; + deflateInit2(&str, 9, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + + nAlloc = deflateBound(&str, nIn); + aOut = (u8*)sqlite3_malloc64(nAlloc); + if( aOut==0 ){ + rc = SQLITE_NOMEM; + }else{ + int res; + str.next_out = aOut; + str.avail_out = nAlloc; + res = deflate(&str, Z_FINISH); + if( res==Z_STREAM_END ){ + *ppOut = aOut; + *pnOut = (int)str.total_out; + }else{ + sqlite3_free(aOut); + *pzErr = sqlite3_mprintf("zipfile: deflate() error"); + rc = SQLITE_ERROR; + } + deflateEnd(&str); + } + + return rc; +} + + +/* +** Return values of columns for the row at which the series_cursor +** is currently pointing. +*/ +static int zipfileColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + ZipfileCsr *pCsr = (ZipfileCsr*)cur; + ZipfileCDS *pCDS = &pCsr->pCurrent->cds; + int rc = SQLITE_OK; + switch( i ){ + case 0: /* name */ + sqlite3_result_text(ctx, pCDS->zFile, -1, SQLITE_TRANSIENT); + break; + case 1: /* mode */ + /* TODO: Whether or not the following is correct surely depends on + ** the platform on which the archive was created. */ + sqlite3_result_int(ctx, pCDS->iExternalAttr >> 16); + break; + case 2: { /* mtime */ + sqlite3_result_int64(ctx, pCsr->pCurrent->mUnixTime); + break; + } + case 3: { /* sz */ + if( sqlite3_vtab_nochange(ctx)==0 ){ + sqlite3_result_int64(ctx, pCDS->szUncompressed); + } + break; + } + case 4: /* rawdata */ + if( sqlite3_vtab_nochange(ctx) ) break; + case 5: { /* data */ + if( i==4 || pCDS->iCompression==0 || pCDS->iCompression==8 ){ + int sz = pCDS->szCompressed; + int szFinal = pCDS->szUncompressed; + if( szFinal>0 ){ + u8 *aBuf; + u8 *aFree = 0; + if( pCsr->pCurrent->aData ){ + aBuf = pCsr->pCurrent->aData; + }else{ + aBuf = aFree = sqlite3_malloc64(sz); + if( aBuf==0 ){ + rc = SQLITE_NOMEM; + }else{ + FILE *pFile = pCsr->pFile; + if( pFile==0 ){ + pFile = ((ZipfileTab*)(pCsr->base.pVtab))->pWriteFd; + } + rc = zipfileReadData(pFile, aBuf, sz, pCsr->pCurrent->iDataOff, + &pCsr->base.pVtab->zErrMsg + ); + } + } + if( rc==SQLITE_OK ){ + if( i==5 && pCDS->iCompression ){ + zipfileInflate(ctx, aBuf, sz, szFinal); + }else{ + sqlite3_result_blob(ctx, aBuf, sz, SQLITE_TRANSIENT); + } + } + sqlite3_free(aFree); + }else{ + /* Figure out if this is a directory or a zero-sized file. Consider + ** it to be a directory either if the mode suggests so, or if + ** the final character in the name is '/'. */ + u32 mode = pCDS->iExternalAttr >> 16; + if( !(mode & S_IFDIR) + && pCDS->nFile>=1 + && pCDS->zFile[pCDS->nFile-1]!='/' + ){ + sqlite3_result_blob(ctx, "", 0, SQLITE_STATIC); + } + } + } + break; + } + case 6: /* method */ + sqlite3_result_int(ctx, pCDS->iCompression); + break; + default: /* z */ + assert( i==7 ); + sqlite3_result_int64(ctx, pCsr->iId); + break; + } + + return rc; +} + +/* +** Return TRUE if the cursor is at EOF. +*/ +static int zipfileEof(sqlite3_vtab_cursor *cur){ + ZipfileCsr *pCsr = (ZipfileCsr*)cur; + return pCsr->bEof; +} + +/* +** If aBlob is not NULL, then it points to a buffer nBlob bytes in size +** containing an entire zip archive image. Or, if aBlob is NULL, then pFile +** is guaranteed to be a file-handle open on a zip file. +** +** This function attempts to locate the EOCD record within the zip archive +** and populate *pEOCD with the results of decoding it. SQLITE_OK is +** returned if successful. Otherwise, an SQLite error code is returned and +** an English language error message may be left in virtual-table pTab. +*/ +static int zipfileReadEOCD( + ZipfileTab *pTab, /* Return errors here */ + const u8 *aBlob, /* Pointer to in-memory file image */ + int nBlob, /* Size of aBlob[] in bytes */ + FILE *pFile, /* Read from this file if aBlob==0 */ + ZipfileEOCD *pEOCD /* Object to populate */ +){ + u8 *aRead = pTab->aBuffer; /* Temporary buffer */ + int nRead; /* Bytes to read from file */ + int rc = SQLITE_OK; + + memset(pEOCD, 0, sizeof(ZipfileEOCD)); + if( aBlob==0 ){ + i64 iOff; /* Offset to read from */ + i64 szFile; /* Total size of file in bytes */ + fseek(pFile, 0, SEEK_END); + szFile = (i64)ftell(pFile); + if( szFile==0 ){ + return SQLITE_OK; + } + nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE)); + iOff = szFile - nRead; + rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg); + }else{ + nRead = (int)(MIN(nBlob, ZIPFILE_BUFFER_SIZE)); + aRead = (u8*)&aBlob[nBlob-nRead]; + } + + if( rc==SQLITE_OK ){ + int i; + + /* Scan backwards looking for the signature bytes */ + for(i=nRead-20; i>=0; i--){ + if( aRead[i]==0x50 && aRead[i+1]==0x4b + && aRead[i+2]==0x05 && aRead[i+3]==0x06 + ){ + break; + } + } + if( i<0 ){ + pTab->base.zErrMsg = sqlite3_mprintf( + "cannot find end of central directory record" + ); + return SQLITE_ERROR; + } + + aRead += i+4; + pEOCD->iDisk = zipfileRead16(aRead); + pEOCD->iFirstDisk = zipfileRead16(aRead); + pEOCD->nEntry = zipfileRead16(aRead); + pEOCD->nEntryTotal = zipfileRead16(aRead); + pEOCD->nSize = zipfileRead32(aRead); + pEOCD->iOffset = zipfileRead32(aRead); + } + + return rc; +} + +/* +** Add object pNew to the linked list that begins at ZipfileTab.pFirstEntry +** and ends with pLastEntry. If argument pBefore is NULL, then pNew is added +** to the end of the list. Otherwise, it is added to the list immediately +** before pBefore (which is guaranteed to be a part of said list). +*/ +static void zipfileAddEntry( + ZipfileTab *pTab, + ZipfileEntry *pBefore, + ZipfileEntry *pNew +){ + assert( (pTab->pFirstEntry==0)==(pTab->pLastEntry==0) ); + assert( pNew->pNext==0 ); + if( pBefore==0 ){ + if( pTab->pFirstEntry==0 ){ + pTab->pFirstEntry = pTab->pLastEntry = pNew; + }else{ + assert( pTab->pLastEntry->pNext==0 ); + pTab->pLastEntry->pNext = pNew; + pTab->pLastEntry = pNew; + } + }else{ + ZipfileEntry **pp; + for(pp=&pTab->pFirstEntry; *pp!=pBefore; pp=&((*pp)->pNext)); + pNew->pNext = pBefore; + *pp = pNew; + } +} + +static int zipfileLoadDirectory(ZipfileTab *pTab, const u8 *aBlob, int nBlob){ + ZipfileEOCD eocd; + int rc; + int i; + i64 iOff; + + rc = zipfileReadEOCD(pTab, aBlob, nBlob, pTab->pWriteFd, &eocd); + iOff = eocd.iOffset; + for(i=0; rc==SQLITE_OK && i<eocd.nEntry; i++){ + ZipfileEntry *pNew = 0; + rc = zipfileGetEntry(pTab, aBlob, nBlob, pTab->pWriteFd, iOff, &pNew); + + if( rc==SQLITE_OK ){ + zipfileAddEntry(pTab, 0, pNew); + iOff += ZIPFILE_CDS_FIXED_SZ; + iOff += (int)pNew->cds.nExtra + pNew->cds.nFile + pNew->cds.nComment; + } + } + return rc; +} + +/* +** xFilter callback. +*/ +static int zipfileFilter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + ZipfileTab *pTab = (ZipfileTab*)cur->pVtab; + ZipfileCsr *pCsr = (ZipfileCsr*)cur; + const char *zFile = 0; /* Zip file to scan */ + int rc = SQLITE_OK; /* Return Code */ + int bInMemory = 0; /* True for an in-memory zipfile */ + + (void)idxStr; + (void)argc; + + zipfileResetCursor(pCsr); + + if( pTab->zFile ){ + zFile = pTab->zFile; + }else if( idxNum==0 ){ + zipfileCursorErr(pCsr, "zipfile() function requires an argument"); + return SQLITE_ERROR; + }else if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){ + static const u8 aEmptyBlob = 0; + const u8 *aBlob = (const u8*)sqlite3_value_blob(argv[0]); + int nBlob = sqlite3_value_bytes(argv[0]); + assert( pTab->pFirstEntry==0 ); + if( aBlob==0 ){ + aBlob = &aEmptyBlob; + nBlob = 0; + } + rc = zipfileLoadDirectory(pTab, aBlob, nBlob); + pCsr->pFreeEntry = pTab->pFirstEntry; + pTab->pFirstEntry = pTab->pLastEntry = 0; + if( rc!=SQLITE_OK ) return rc; + bInMemory = 1; + }else{ + zFile = (const char*)sqlite3_value_text(argv[0]); + } + + if( 0==pTab->pWriteFd && 0==bInMemory ){ + pCsr->pFile = zFile ? fopen(zFile, "rb") : 0; + if( pCsr->pFile==0 ){ + zipfileCursorErr(pCsr, "cannot open file: %s", zFile); + rc = SQLITE_ERROR; + }else{ + rc = zipfileReadEOCD(pTab, 0, 0, pCsr->pFile, &pCsr->eocd); + if( rc==SQLITE_OK ){ + if( pCsr->eocd.nEntry==0 ){ + pCsr->bEof = 1; + }else{ + pCsr->iNextOff = pCsr->eocd.iOffset; + rc = zipfileNext(cur); + } + } + } + }else{ + pCsr->bNoop = 1; + pCsr->pCurrent = pCsr->pFreeEntry ? pCsr->pFreeEntry : pTab->pFirstEntry; + rc = zipfileNext(cur); + } + + return rc; +} + +/* +** xBestIndex callback. +*/ +static int zipfileBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; + int idx = -1; + int unusable = 0; + (void)tab; + + for(i=0; i<pIdxInfo->nConstraint; i++){ + const struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i]; + if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue; + if( pCons->usable==0 ){ + unusable = 1; + }else if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + idx = i; + } + } + pIdxInfo->estimatedCost = 1000.0; + if( idx>=0 ){ + pIdxInfo->aConstraintUsage[idx].argvIndex = 1; + pIdxInfo->aConstraintUsage[idx].omit = 1; + pIdxInfo->idxNum = 1; + }else if( unusable ){ + return SQLITE_CONSTRAINT; + } + return SQLITE_OK; +} + +static ZipfileEntry *zipfileNewEntry(const char *zPath){ + ZipfileEntry *pNew; + pNew = sqlite3_malloc(sizeof(ZipfileEntry)); + if( pNew ){ + memset(pNew, 0, sizeof(ZipfileEntry)); + pNew->cds.zFile = sqlite3_mprintf("%s", zPath); + if( pNew->cds.zFile==0 ){ + sqlite3_free(pNew); + pNew = 0; + } + } + return pNew; +} + +static int zipfileSerializeLFH(ZipfileEntry *pEntry, u8 *aBuf){ + ZipfileCDS *pCds = &pEntry->cds; + u8 *a = aBuf; + + pCds->nExtra = 9; + + /* Write the LFH itself */ + zipfileWrite32(a, ZIPFILE_SIGNATURE_LFH); + zipfileWrite16(a, pCds->iVersionExtract); + zipfileWrite16(a, pCds->flags); + zipfileWrite16(a, pCds->iCompression); + zipfileWrite16(a, pCds->mTime); + zipfileWrite16(a, pCds->mDate); + zipfileWrite32(a, pCds->crc32); + zipfileWrite32(a, pCds->szCompressed); + zipfileWrite32(a, pCds->szUncompressed); + zipfileWrite16(a, (u16)pCds->nFile); + zipfileWrite16(a, pCds->nExtra); + assert( a==&aBuf[ZIPFILE_LFH_FIXED_SZ] ); + + /* Add the file name */ + memcpy(a, pCds->zFile, (int)pCds->nFile); + a += (int)pCds->nFile; + + /* The "extra" data */ + zipfileWrite16(a, ZIPFILE_EXTRA_TIMESTAMP); + zipfileWrite16(a, 5); + *a++ = 0x01; + zipfileWrite32(a, pEntry->mUnixTime); + + return a-aBuf; +} + +static int zipfileAppendEntry( + ZipfileTab *pTab, + ZipfileEntry *pEntry, + const u8 *pData, + int nData +){ + u8 *aBuf = pTab->aBuffer; + int nBuf; + int rc; + + nBuf = zipfileSerializeLFH(pEntry, aBuf); + rc = zipfileAppendData(pTab, aBuf, nBuf); + if( rc==SQLITE_OK ){ + pEntry->iDataOff = pTab->szCurrent; + rc = zipfileAppendData(pTab, pData, nData); + } + + return rc; +} + +static int zipfileGetMode( + sqlite3_value *pVal, + int bIsDir, /* If true, default to directory */ + u32 *pMode, /* OUT: Mode value */ + char **pzErr /* OUT: Error message */ +){ + const char *z = (const char*)sqlite3_value_text(pVal); + u32 mode = 0; + if( z==0 ){ + mode = (bIsDir ? (S_IFDIR + 0755) : (S_IFREG + 0644)); + }else if( z[0]>='0' && z[0]<='9' ){ + mode = (unsigned int)sqlite3_value_int(pVal); + }else{ + const char zTemplate[11] = "-rwxrwxrwx"; + int i; + if( strlen(z)!=10 ) goto parse_error; + switch( z[0] ){ + case '-': mode |= S_IFREG; break; + case 'd': mode |= S_IFDIR; break; + case 'l': mode |= S_IFLNK; break; + default: goto parse_error; + } + for(i=1; i<10; i++){ + if( z[i]==zTemplate[i] ) mode |= 1 << (9-i); + else if( z[i]!='-' ) goto parse_error; + } + } + if( ((mode & S_IFDIR)==0)==bIsDir ){ + /* The "mode" attribute is a directory, but data has been specified. + ** Or vice-versa - no data but "mode" is a file or symlink. */ + *pzErr = sqlite3_mprintf("zipfile: mode does not match data"); + return SQLITE_CONSTRAINT; + } + *pMode = mode; + return SQLITE_OK; + + parse_error: + *pzErr = sqlite3_mprintf("zipfile: parse error in mode: %s", z); + return SQLITE_ERROR; +} + +/* +** Both (const char*) arguments point to nul-terminated strings. Argument +** nB is the value of strlen(zB). This function returns 0 if the strings are +** identical, ignoring any trailing '/' character in either path. */ +static int zipfileComparePath(const char *zA, const char *zB, int nB){ + int nA = (int)strlen(zA); + if( nA>0 && zA[nA-1]=='/' ) nA--; + if( nB>0 && zB[nB-1]=='/' ) nB--; + if( nA==nB && memcmp(zA, zB, nA)==0 ) return 0; + return 1; +} + +static int zipfileBegin(sqlite3_vtab *pVtab){ + ZipfileTab *pTab = (ZipfileTab*)pVtab; + int rc = SQLITE_OK; + + assert( pTab->pWriteFd==0 ); + if( pTab->zFile==0 || pTab->zFile[0]==0 ){ + pTab->base.zErrMsg = sqlite3_mprintf("zipfile: missing filename"); + return SQLITE_ERROR; + } + + /* Open a write fd on the file. Also load the entire central directory + ** structure into memory. During the transaction any new file data is + ** appended to the archive file, but the central directory is accumulated + ** in main-memory until the transaction is committed. */ + pTab->pWriteFd = fopen(pTab->zFile, "ab+"); + if( pTab->pWriteFd==0 ){ + pTab->base.zErrMsg = sqlite3_mprintf( + "zipfile: failed to open file %s for writing", pTab->zFile + ); + rc = SQLITE_ERROR; + }else{ + fseek(pTab->pWriteFd, 0, SEEK_END); + pTab->szCurrent = pTab->szOrig = (i64)ftell(pTab->pWriteFd); + rc = zipfileLoadDirectory(pTab, 0, 0); + } + + if( rc!=SQLITE_OK ){ + zipfileCleanupTransaction(pTab); + } + + return rc; +} + +/* +** Return the current time as a 32-bit timestamp in UNIX epoch format (like +** time(2)). +*/ +static u32 zipfileTime(void){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + u32 ret; + if( pVfs==0 ) return 0; + if( pVfs->iVersion>=2 && pVfs->xCurrentTimeInt64 ){ + i64 ms; + pVfs->xCurrentTimeInt64(pVfs, &ms); + ret = (u32)((ms/1000) - ((i64)24405875 * 8640)); + }else{ + double day; + pVfs->xCurrentTime(pVfs, &day); + ret = (u32)((day - 2440587.5) * 86400); + } + return ret; +} + +/* +** Return a 32-bit timestamp in UNIX epoch format. +** +** If the value passed as the only argument is either NULL or an SQL NULL, +** return the current time. Otherwise, return the value stored in (*pVal) +** cast to a 32-bit unsigned integer. +*/ +static u32 zipfileGetTime(sqlite3_value *pVal){ + if( pVal==0 || sqlite3_value_type(pVal)==SQLITE_NULL ){ + return zipfileTime(); + } + return (u32)sqlite3_value_int64(pVal); +} + +/* +** Unless it is NULL, entry pOld is currently part of the pTab->pFirstEntry +** linked list. Remove it from the list and free the object. +*/ +static void zipfileRemoveEntryFromList(ZipfileTab *pTab, ZipfileEntry *pOld){ + if( pOld ){ + if( pTab->pFirstEntry==pOld ){ + pTab->pFirstEntry = pOld->pNext; + if( pTab->pLastEntry==pOld ) pTab->pLastEntry = 0; + }else{ + ZipfileEntry *p; + for(p=pTab->pFirstEntry; p; p=p->pNext){ + if( p->pNext==pOld ){ + p->pNext = pOld->pNext; + if( pTab->pLastEntry==pOld ) pTab->pLastEntry = p; + break; + } + } + } + zipfileEntryFree(pOld); + } +} + +/* +** xUpdate method. +*/ +static int zipfileUpdate( + sqlite3_vtab *pVtab, + int nVal, + sqlite3_value **apVal, + sqlite_int64 *pRowid +){ + ZipfileTab *pTab = (ZipfileTab*)pVtab; + int rc = SQLITE_OK; /* Return Code */ + ZipfileEntry *pNew = 0; /* New in-memory CDS entry */ + + u32 mode = 0; /* Mode for new entry */ + u32 mTime = 0; /* Modification time for new entry */ + i64 sz = 0; /* Uncompressed size */ + const char *zPath = 0; /* Path for new entry */ + int nPath = 0; /* strlen(zPath) */ + const u8 *pData = 0; /* Pointer to buffer containing content */ + int nData = 0; /* Size of pData buffer in bytes */ + int iMethod = 0; /* Compression method for new entry */ + u8 *pFree = 0; /* Free this */ + char *zFree = 0; /* Also free this */ + ZipfileEntry *pOld = 0; + ZipfileEntry *pOld2 = 0; + int bUpdate = 0; /* True for an update that modifies "name" */ + int bIsDir = 0; + u32 iCrc32 = 0; + + (void)pRowid; + + if( pTab->pWriteFd==0 ){ + rc = zipfileBegin(pVtab); + if( rc!=SQLITE_OK ) return rc; + } + + /* If this is a DELETE or UPDATE, find the archive entry to delete. */ + if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ + const char *zDelete = (const char*)sqlite3_value_text(apVal[0]); + int nDelete = (int)strlen(zDelete); + if( nVal>1 ){ + const char *zUpdate = (const char*)sqlite3_value_text(apVal[1]); + if( zUpdate && zipfileComparePath(zUpdate, zDelete, nDelete)!=0 ){ + bUpdate = 1; + } + } + for(pOld=pTab->pFirstEntry; 1; pOld=pOld->pNext){ + if( zipfileComparePath(pOld->cds.zFile, zDelete, nDelete)==0 ){ + break; + } + assert( pOld->pNext ); + } + } + + if( nVal>1 ){ + /* Check that "sz" and "rawdata" are both NULL: */ + if( sqlite3_value_type(apVal[5])!=SQLITE_NULL ){ + zipfileTableErr(pTab, "sz must be NULL"); + rc = SQLITE_CONSTRAINT; + } + if( sqlite3_value_type(apVal[6])!=SQLITE_NULL ){ + zipfileTableErr(pTab, "rawdata must be NULL"); + rc = SQLITE_CONSTRAINT; + } + + if( rc==SQLITE_OK ){ + if( sqlite3_value_type(apVal[7])==SQLITE_NULL ){ + /* data=NULL. A directory */ + bIsDir = 1; + }else{ + /* Value specified for "data", and possibly "method". This must be + ** a regular file or a symlink. */ + const u8 *aIn = sqlite3_value_blob(apVal[7]); + int nIn = sqlite3_value_bytes(apVal[7]); + int bAuto = sqlite3_value_type(apVal[8])==SQLITE_NULL; + + iMethod = sqlite3_value_int(apVal[8]); + sz = nIn; + pData = aIn; + nData = nIn; + if( iMethod!=0 && iMethod!=8 ){ + zipfileTableErr(pTab, "unknown compression method: %d", iMethod); + rc = SQLITE_CONSTRAINT; + }else{ + if( bAuto || iMethod ){ + int nCmp; + rc = zipfileDeflate(aIn, nIn, &pFree, &nCmp, &pTab->base.zErrMsg); + if( rc==SQLITE_OK ){ + if( iMethod || nCmp<nIn ){ + iMethod = 8; + pData = pFree; + nData = nCmp; + } + } + } + iCrc32 = crc32(0, aIn, nIn); + } + } + } + + if( rc==SQLITE_OK ){ + rc = zipfileGetMode(apVal[3], bIsDir, &mode, &pTab->base.zErrMsg); + } + + if( rc==SQLITE_OK ){ + zPath = (const char*)sqlite3_value_text(apVal[2]); + if( zPath==0 ) zPath = ""; + nPath = (int)strlen(zPath); + mTime = zipfileGetTime(apVal[4]); + } + + if( rc==SQLITE_OK && bIsDir ){ + /* For a directory, check that the last character in the path is a + ** '/'. This appears to be required for compatibility with info-zip + ** (the unzip command on unix). It does not create directories + ** otherwise. */ + if( nPath<=0 || zPath[nPath-1]!='/' ){ + zFree = sqlite3_mprintf("%s/", zPath); + zPath = (const char*)zFree; + if( zFree==0 ){ + rc = SQLITE_NOMEM; + nPath = 0; + }else{ + nPath = (int)strlen(zPath); + } + } + } + + /* Check that we're not inserting a duplicate entry -OR- updating an + ** entry with a path, thereby making it into a duplicate. */ + if( (pOld==0 || bUpdate) && rc==SQLITE_OK ){ + ZipfileEntry *p; + for(p=pTab->pFirstEntry; p; p=p->pNext){ + if( zipfileComparePath(p->cds.zFile, zPath, nPath)==0 ){ + switch( sqlite3_vtab_on_conflict(pTab->db) ){ + case SQLITE_IGNORE: { + goto zipfile_update_done; + } + case SQLITE_REPLACE: { + pOld2 = p; + break; + } + default: { + zipfileTableErr(pTab, "duplicate name: \"%s\"", zPath); + rc = SQLITE_CONSTRAINT; + break; + } + } + break; + } + } + } + + if( rc==SQLITE_OK ){ + /* Create the new CDS record. */ + pNew = zipfileNewEntry(zPath); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + pNew->cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY; + pNew->cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED; + pNew->cds.flags = ZIPFILE_NEWENTRY_FLAGS; + pNew->cds.iCompression = (u16)iMethod; + zipfileMtimeToDos(&pNew->cds, mTime); + pNew->cds.crc32 = iCrc32; + pNew->cds.szCompressed = nData; + pNew->cds.szUncompressed = (u32)sz; + pNew->cds.iExternalAttr = (mode<<16); + pNew->cds.iOffset = (u32)pTab->szCurrent; + pNew->cds.nFile = (u16)nPath; + pNew->mUnixTime = (u32)mTime; + rc = zipfileAppendEntry(pTab, pNew, pData, nData); + zipfileAddEntry(pTab, pOld, pNew); + } + } + } + + if( rc==SQLITE_OK && (pOld || pOld2) ){ + ZipfileCsr *pCsr; + for(pCsr=pTab->pCsrList; pCsr; pCsr=pCsr->pCsrNext){ + if( pCsr->pCurrent && (pCsr->pCurrent==pOld || pCsr->pCurrent==pOld2) ){ + pCsr->pCurrent = pCsr->pCurrent->pNext; + pCsr->bNoop = 1; + } + } + + zipfileRemoveEntryFromList(pTab, pOld); + zipfileRemoveEntryFromList(pTab, pOld2); + } + +zipfile_update_done: + sqlite3_free(pFree); + sqlite3_free(zFree); + return rc; +} + +static int zipfileSerializeEOCD(ZipfileEOCD *p, u8 *aBuf){ + u8 *a = aBuf; + zipfileWrite32(a, ZIPFILE_SIGNATURE_EOCD); + zipfileWrite16(a, p->iDisk); + zipfileWrite16(a, p->iFirstDisk); + zipfileWrite16(a, p->nEntry); + zipfileWrite16(a, p->nEntryTotal); + zipfileWrite32(a, p->nSize); + zipfileWrite32(a, p->iOffset); + zipfileWrite16(a, 0); /* Size of trailing comment in bytes*/ + + return a-aBuf; +} + +static int zipfileAppendEOCD(ZipfileTab *pTab, ZipfileEOCD *p){ + int nBuf = zipfileSerializeEOCD(p, pTab->aBuffer); + assert( nBuf==ZIPFILE_EOCD_FIXED_SZ ); + return zipfileAppendData(pTab, pTab->aBuffer, nBuf); +} + +/* +** Serialize the CDS structure into buffer aBuf[]. Return the number +** of bytes written. +*/ +static int zipfileSerializeCDS(ZipfileEntry *pEntry, u8 *aBuf){ + u8 *a = aBuf; + ZipfileCDS *pCDS = &pEntry->cds; + + if( pEntry->aExtra==0 ){ + pCDS->nExtra = 9; + } + + zipfileWrite32(a, ZIPFILE_SIGNATURE_CDS); + zipfileWrite16(a, pCDS->iVersionMadeBy); + zipfileWrite16(a, pCDS->iVersionExtract); + zipfileWrite16(a, pCDS->flags); + zipfileWrite16(a, pCDS->iCompression); + zipfileWrite16(a, pCDS->mTime); + zipfileWrite16(a, pCDS->mDate); + zipfileWrite32(a, pCDS->crc32); + zipfileWrite32(a, pCDS->szCompressed); + zipfileWrite32(a, pCDS->szUncompressed); + assert( a==&aBuf[ZIPFILE_CDS_NFILE_OFF] ); + zipfileWrite16(a, pCDS->nFile); + zipfileWrite16(a, pCDS->nExtra); + zipfileWrite16(a, pCDS->nComment); + zipfileWrite16(a, pCDS->iDiskStart); + zipfileWrite16(a, pCDS->iInternalAttr); + zipfileWrite32(a, pCDS->iExternalAttr); + zipfileWrite32(a, pCDS->iOffset); + + memcpy(a, pCDS->zFile, pCDS->nFile); + a += pCDS->nFile; + + if( pEntry->aExtra ){ + int n = (int)pCDS->nExtra + (int)pCDS->nComment; + memcpy(a, pEntry->aExtra, n); + a += n; + }else{ + assert( pCDS->nExtra==9 ); + zipfileWrite16(a, ZIPFILE_EXTRA_TIMESTAMP); + zipfileWrite16(a, 5); + *a++ = 0x01; + zipfileWrite32(a, pEntry->mUnixTime); + } + + return a-aBuf; +} + +static int zipfileCommit(sqlite3_vtab *pVtab){ + ZipfileTab *pTab = (ZipfileTab*)pVtab; + int rc = SQLITE_OK; + if( pTab->pWriteFd ){ + i64 iOffset = pTab->szCurrent; + ZipfileEntry *p; + ZipfileEOCD eocd; + int nEntry = 0; + + /* Write out all entries */ + for(p=pTab->pFirstEntry; rc==SQLITE_OK && p; p=p->pNext){ + int n = zipfileSerializeCDS(p, pTab->aBuffer); + rc = zipfileAppendData(pTab, pTab->aBuffer, n); + nEntry++; + } + + /* Write out the EOCD record */ + eocd.iDisk = 0; + eocd.iFirstDisk = 0; + eocd.nEntry = (u16)nEntry; + eocd.nEntryTotal = (u16)nEntry; + eocd.nSize = (u32)(pTab->szCurrent - iOffset); + eocd.iOffset = (u32)iOffset; + rc = zipfileAppendEOCD(pTab, &eocd); + + zipfileCleanupTransaction(pTab); + } + return rc; +} + +static int zipfileRollback(sqlite3_vtab *pVtab){ + return zipfileCommit(pVtab); +} + +static ZipfileCsr *zipfileFindCursor(ZipfileTab *pTab, i64 iId){ + ZipfileCsr *pCsr; + for(pCsr=pTab->pCsrList; pCsr; pCsr=pCsr->pCsrNext){ + if( iId==pCsr->iId ) break; + } + return pCsr; +} + +static void zipfileFunctionCds( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + ZipfileCsr *pCsr; + ZipfileTab *pTab = (ZipfileTab*)sqlite3_user_data(context); + assert( argc>0 ); + + pCsr = zipfileFindCursor(pTab, sqlite3_value_int64(argv[0])); + if( pCsr ){ + ZipfileCDS *p = &pCsr->pCurrent->cds; + char *zRes = sqlite3_mprintf("{" + "\"version-made-by\" : %u, " + "\"version-to-extract\" : %u, " + "\"flags\" : %u, " + "\"compression\" : %u, " + "\"time\" : %u, " + "\"date\" : %u, " + "\"crc32\" : %u, " + "\"compressed-size\" : %u, " + "\"uncompressed-size\" : %u, " + "\"file-name-length\" : %u, " + "\"extra-field-length\" : %u, " + "\"file-comment-length\" : %u, " + "\"disk-number-start\" : %u, " + "\"internal-attr\" : %u, " + "\"external-attr\" : %u, " + "\"offset\" : %u }", + (u32)p->iVersionMadeBy, (u32)p->iVersionExtract, + (u32)p->flags, (u32)p->iCompression, + (u32)p->mTime, (u32)p->mDate, + (u32)p->crc32, (u32)p->szCompressed, + (u32)p->szUncompressed, (u32)p->nFile, + (u32)p->nExtra, (u32)p->nComment, + (u32)p->iDiskStart, (u32)p->iInternalAttr, + (u32)p->iExternalAttr, (u32)p->iOffset + ); + + if( zRes==0 ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_result_text(context, zRes, -1, SQLITE_TRANSIENT); + sqlite3_free(zRes); + } + } +} + +/* +** xFindFunction method. +*/ +static int zipfileFindFunction( + sqlite3_vtab *pVtab, /* Virtual table handle */ + int nArg, /* Number of SQL function arguments */ + const char *zName, /* Name of SQL function */ + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */ + void **ppArg /* OUT: User data for *pxFunc */ +){ + (void)nArg; + if( sqlite3_stricmp("zipfile_cds", zName)==0 ){ + *pxFunc = zipfileFunctionCds; + *ppArg = (void*)pVtab; + return 1; + } + return 0; +} + +typedef struct ZipfileBuffer ZipfileBuffer; +struct ZipfileBuffer { + u8 *a; /* Pointer to buffer */ + int n; /* Size of buffer in bytes */ + int nAlloc; /* Byte allocated at a[] */ +}; + +typedef struct ZipfileCtx ZipfileCtx; +struct ZipfileCtx { + int nEntry; + ZipfileBuffer body; + ZipfileBuffer cds; +}; + +static int zipfileBufferGrow(ZipfileBuffer *pBuf, int nByte){ + if( pBuf->n+nByte>pBuf->nAlloc ){ + u8 *aNew; + sqlite3_int64 nNew = pBuf->n ? pBuf->n*2 : 512; + int nReq = pBuf->n + nByte; + + while( nNew<nReq ) nNew = nNew*2; + aNew = sqlite3_realloc64(pBuf->a, nNew); + if( aNew==0 ) return SQLITE_NOMEM; + pBuf->a = aNew; + pBuf->nAlloc = (int)nNew; + } + return SQLITE_OK; +} + +/* +** xStep() callback for the zipfile() aggregate. This can be called in +** any of the following ways: +** +** SELECT zipfile(name,data) ... +** SELECT zipfile(name,mode,mtime,data) ... +** SELECT zipfile(name,mode,mtime,data,method) ... +*/ +static void zipfileStep(sqlite3_context *pCtx, int nVal, sqlite3_value **apVal){ + ZipfileCtx *p; /* Aggregate function context */ + ZipfileEntry e; /* New entry to add to zip archive */ + + sqlite3_value *pName = 0; + sqlite3_value *pMode = 0; + sqlite3_value *pMtime = 0; + sqlite3_value *pData = 0; + sqlite3_value *pMethod = 0; + + int bIsDir = 0; + u32 mode; + int rc = SQLITE_OK; + char *zErr = 0; + + int iMethod = -1; /* Compression method to use (0 or 8) */ + + const u8 *aData = 0; /* Possibly compressed data for new entry */ + int nData = 0; /* Size of aData[] in bytes */ + int szUncompressed = 0; /* Size of data before compression */ + u8 *aFree = 0; /* Free this before returning */ + u32 iCrc32 = 0; /* crc32 of uncompressed data */ + + char *zName = 0; /* Path (name) of new entry */ + int nName = 0; /* Size of zName in bytes */ + char *zFree = 0; /* Free this before returning */ + int nByte; + + memset(&e, 0, sizeof(e)); + p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx)); + if( p==0 ) return; + + /* Martial the arguments into stack variables */ + if( nVal!=2 && nVal!=4 && nVal!=5 ){ + zErr = sqlite3_mprintf("wrong number of arguments to function zipfile()"); + rc = SQLITE_ERROR; + goto zipfile_step_out; + } + pName = apVal[0]; + if( nVal==2 ){ + pData = apVal[1]; + }else{ + pMode = apVal[1]; + pMtime = apVal[2]; + pData = apVal[3]; + if( nVal==5 ){ + pMethod = apVal[4]; + } + } + + /* Check that the 'name' parameter looks ok. */ + zName = (char*)sqlite3_value_text(pName); + nName = sqlite3_value_bytes(pName); + if( zName==0 ){ + zErr = sqlite3_mprintf("first argument to zipfile() must be non-NULL"); + rc = SQLITE_ERROR; + goto zipfile_step_out; + } + + /* Inspect the 'method' parameter. This must be either 0 (store), 8 (use + ** deflate compression) or NULL (choose automatically). */ + if( pMethod && SQLITE_NULL!=sqlite3_value_type(pMethod) ){ + iMethod = (int)sqlite3_value_int64(pMethod); + if( iMethod!=0 && iMethod!=8 ){ + zErr = sqlite3_mprintf("illegal method value: %d", iMethod); + rc = SQLITE_ERROR; + goto zipfile_step_out; + } + } + + /* Now inspect the data. If this is NULL, then the new entry must be a + ** directory. Otherwise, figure out whether or not the data should + ** be deflated or simply stored in the zip archive. */ + if( sqlite3_value_type(pData)==SQLITE_NULL ){ + bIsDir = 1; + iMethod = 0; + }else{ + aData = sqlite3_value_blob(pData); + szUncompressed = nData = sqlite3_value_bytes(pData); + iCrc32 = crc32(0, aData, nData); + if( iMethod<0 || iMethod==8 ){ + int nOut = 0; + rc = zipfileDeflate(aData, nData, &aFree, &nOut, &zErr); + if( rc!=SQLITE_OK ){ + goto zipfile_step_out; + } + if( iMethod==8 || nOut<nData ){ + aData = aFree; + nData = nOut; + iMethod = 8; + }else{ + iMethod = 0; + } + } + } + + /* Decode the "mode" argument. */ + rc = zipfileGetMode(pMode, bIsDir, &mode, &zErr); + if( rc ) goto zipfile_step_out; + + /* Decode the "mtime" argument. */ + e.mUnixTime = zipfileGetTime(pMtime); + + /* If this is a directory entry, ensure that there is exactly one '/' + ** at the end of the path. Or, if this is not a directory and the path + ** ends in '/' it is an error. */ + if( bIsDir==0 ){ + if( nName>0 && zName[nName-1]=='/' ){ + zErr = sqlite3_mprintf("non-directory name must not end with /"); + rc = SQLITE_ERROR; + goto zipfile_step_out; + } + }else{ + if( nName==0 || zName[nName-1]!='/' ){ + zName = zFree = sqlite3_mprintf("%s/", zName); + if( zName==0 ){ + rc = SQLITE_NOMEM; + goto zipfile_step_out; + } + nName = (int)strlen(zName); + }else{ + while( nName>1 && zName[nName-2]=='/' ) nName--; + } + } + + /* Assemble the ZipfileEntry object for the new zip archive entry */ + e.cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY; + e.cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED; + e.cds.flags = ZIPFILE_NEWENTRY_FLAGS; + e.cds.iCompression = (u16)iMethod; + zipfileMtimeToDos(&e.cds, (u32)e.mUnixTime); + e.cds.crc32 = iCrc32; + e.cds.szCompressed = nData; + e.cds.szUncompressed = szUncompressed; + e.cds.iExternalAttr = (mode<<16); + e.cds.iOffset = p->body.n; + e.cds.nFile = (u16)nName; + e.cds.zFile = zName; + + /* Append the LFH to the body of the new archive */ + nByte = ZIPFILE_LFH_FIXED_SZ + e.cds.nFile + 9; + if( (rc = zipfileBufferGrow(&p->body, nByte)) ) goto zipfile_step_out; + p->body.n += zipfileSerializeLFH(&e, &p->body.a[p->body.n]); + + /* Append the data to the body of the new archive */ + if( nData>0 ){ + if( (rc = zipfileBufferGrow(&p->body, nData)) ) goto zipfile_step_out; + memcpy(&p->body.a[p->body.n], aData, nData); + p->body.n += nData; + } + + /* Append the CDS record to the directory of the new archive */ + nByte = ZIPFILE_CDS_FIXED_SZ + e.cds.nFile + 9; + if( (rc = zipfileBufferGrow(&p->cds, nByte)) ) goto zipfile_step_out; + p->cds.n += zipfileSerializeCDS(&e, &p->cds.a[p->cds.n]); + + /* Increment the count of entries in the archive */ + p->nEntry++; + + zipfile_step_out: + sqlite3_free(aFree); + sqlite3_free(zFree); + if( rc ){ + if( zErr ){ + sqlite3_result_error(pCtx, zErr, -1); + }else{ + sqlite3_result_error_code(pCtx, rc); + } + } + sqlite3_free(zErr); +} + +/* +** xFinalize() callback for zipfile aggregate function. +*/ +static void zipfileFinal(sqlite3_context *pCtx){ + ZipfileCtx *p; + ZipfileEOCD eocd; + sqlite3_int64 nZip; + u8 *aZip; + + p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx)); + if( p==0 ) return; + if( p->nEntry>0 ){ + memset(&eocd, 0, sizeof(eocd)); + eocd.nEntry = (u16)p->nEntry; + eocd.nEntryTotal = (u16)p->nEntry; + eocd.nSize = p->cds.n; + eocd.iOffset = p->body.n; + + nZip = p->body.n + p->cds.n + ZIPFILE_EOCD_FIXED_SZ; + aZip = (u8*)sqlite3_malloc64(nZip); + if( aZip==0 ){ + sqlite3_result_error_nomem(pCtx); + }else{ + memcpy(aZip, p->body.a, p->body.n); + memcpy(&aZip[p->body.n], p->cds.a, p->cds.n); + zipfileSerializeEOCD(&eocd, &aZip[p->body.n + p->cds.n]); + sqlite3_result_blob(pCtx, aZip, (int)nZip, zipfileFree); + } + } + + sqlite3_free(p->body.a); + sqlite3_free(p->cds.a); +} + + +/* +** Register the "zipfile" virtual table. +*/ +static int zipfileRegister(sqlite3 *db){ + static sqlite3_module zipfileModule = { + 1, /* iVersion */ + zipfileConnect, /* xCreate */ + zipfileConnect, /* xConnect */ + zipfileBestIndex, /* xBestIndex */ + zipfileDisconnect, /* xDisconnect */ + zipfileDisconnect, /* xDestroy */ + zipfileOpen, /* xOpen - open a cursor */ + zipfileClose, /* xClose - close a cursor */ + zipfileFilter, /* xFilter - configure scan constraints */ + zipfileNext, /* xNext - advance a cursor */ + zipfileEof, /* xEof - check for end of scan */ + zipfileColumn, /* xColumn - read data */ + 0, /* xRowid - read data */ + zipfileUpdate, /* xUpdate */ + zipfileBegin, /* xBegin */ + 0, /* xSync */ + zipfileCommit, /* xCommit */ + zipfileRollback, /* xRollback */ + zipfileFindFunction, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollback */ + 0 /* xShadowName */ + }; + + int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0); + if( rc==SQLITE_OK ) rc = sqlite3_overload_function(db, "zipfile_cds", -1); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "zipfile", -1, SQLITE_UTF8, 0, 0, + zipfileStep, zipfileFinal + ); + } + assert( sizeof(i64)==8 ); + assert( sizeof(u32)==4 ); + assert( sizeof(u16)==2 ); + assert( sizeof(u8)==1 ); + return rc; +} +#else /* SQLITE_OMIT_VIRTUALTABLE */ +# define zipfileRegister(x) SQLITE_OK +#endif + +#ifdef _WIN32 + +#endif +int sqlite3_zipfile_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + return zipfileRegister(db); +} + +/************************* End ../ext/misc/zipfile.c ********************/ +/************************* Begin ../ext/misc/sqlar.c ******************/ +/* +** 2017-12-17 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** Utility functions sqlar_compress() and sqlar_uncompress(). Useful +** for working with sqlar archives and used by the shell tool's built-in +** sqlar support. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include <zlib.h> +#include <assert.h> + +/* +** Implementation of the "sqlar_compress(X)" SQL function. +** +** If the type of X is SQLITE_BLOB, and compressing that blob using +** zlib utility function compress() yields a smaller blob, return the +** compressed blob. Otherwise, return a copy of X. +** +** SQLar uses the "zlib format" for compressed content. The zlib format +** contains a two-byte identification header and a four-byte checksum at +** the end. This is different from ZIP which uses the raw deflate format. +** +** Future enhancements to SQLar might add support for new compression formats. +** If so, those new formats will be identified by alternative headers in the +** compressed data. +*/ +static void sqlarCompressFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){ + const Bytef *pData = sqlite3_value_blob(argv[0]); + uLong nData = sqlite3_value_bytes(argv[0]); + uLongf nOut = compressBound(nData); + Bytef *pOut; + + pOut = (Bytef*)sqlite3_malloc(nOut); + if( pOut==0 ){ + sqlite3_result_error_nomem(context); + return; + }else{ + if( Z_OK!=compress(pOut, &nOut, pData, nData) ){ + sqlite3_result_error(context, "error in compress()", -1); + }else if( nOut<nData ){ + sqlite3_result_blob(context, pOut, nOut, SQLITE_TRANSIENT); + }else{ + sqlite3_result_value(context, argv[0]); + } + sqlite3_free(pOut); + } + }else{ + sqlite3_result_value(context, argv[0]); + } +} + +/* +** Implementation of the "sqlar_uncompress(X,SZ)" SQL function +** +** Parameter SZ is interpreted as an integer. If it is less than or +** equal to zero, then this function returns a copy of X. Or, if +** SZ is equal to the size of X when interpreted as a blob, also +** return a copy of X. Otherwise, decompress blob X using zlib +** utility function uncompress() and return the results (another +** blob). +*/ +static void sqlarUncompressFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + uLong nData; + uLongf sz; + + assert( argc==2 ); + sz = sqlite3_value_int(argv[1]); + + if( sz<=0 || sz==(nData = sqlite3_value_bytes(argv[0])) ){ + sqlite3_result_value(context, argv[0]); + }else{ + const Bytef *pData= sqlite3_value_blob(argv[0]); + Bytef *pOut = sqlite3_malloc(sz); + if( pOut==0 ){ + sqlite3_result_error_nomem(context); + }else if( Z_OK!=uncompress(pOut, &sz, pData, nData) ){ + sqlite3_result_error(context, "error in uncompress()", -1); + }else{ + sqlite3_result_blob(context, pOut, sz, SQLITE_TRANSIENT); + } + sqlite3_free(pOut); + } +} + +#ifdef _WIN32 + +#endif +int sqlite3_sqlar_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + rc = sqlite3_create_function(db, "sqlar_compress", 1, + SQLITE_UTF8|SQLITE_INNOCUOUS, 0, + sqlarCompressFunc, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sqlar_uncompress", 2, + SQLITE_UTF8|SQLITE_INNOCUOUS, 0, + sqlarUncompressFunc, 0, 0); + } + return rc; +} + +/************************* End ../ext/misc/sqlar.c ********************/ +#endif +/************************* Begin ../ext/expert/sqlite3expert.h ******************/ +/* +** 2017 April 07 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +*/ +#if !defined(SQLITEEXPERT_H) +#define SQLITEEXPERT_H 1 +/* #include "sqlite3.h" */ + +typedef struct sqlite3expert sqlite3expert; + +/* +** Create a new sqlite3expert object. +** +** If successful, a pointer to the new object is returned and (*pzErr) set +** to NULL. Or, if an error occurs, NULL is returned and (*pzErr) set to +** an English-language error message. In this case it is the responsibility +** of the caller to eventually free the error message buffer using +** sqlite3_free(). +*/ +sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErr); + +/* +** Configure an sqlite3expert object. +** +** EXPERT_CONFIG_SAMPLE: +** By default, sqlite3_expert_analyze() generates sqlite_stat1 data for +** each candidate index. This involves scanning and sorting the entire +** contents of each user database table once for each candidate index +** associated with the table. For large databases, this can be +** prohibitively slow. This option allows the sqlite3expert object to +** be configured so that sqlite_stat1 data is instead generated based on a +** subset of each table, or so that no sqlite_stat1 data is used at all. +** +** A single integer argument is passed to this option. If the value is less +** than or equal to zero, then no sqlite_stat1 data is generated or used by +** the analysis - indexes are recommended based on the database schema only. +** Or, if the value is 100 or greater, complete sqlite_stat1 data is +** generated for each candidate index (this is the default). Finally, if the +** value falls between 0 and 100, then it represents the percentage of user +** table rows that should be considered when generating sqlite_stat1 data. +** +** Examples: +** +** // Do not generate any sqlite_stat1 data +** sqlite3_expert_config(pExpert, EXPERT_CONFIG_SAMPLE, 0); +** +** // Generate sqlite_stat1 data based on 10% of the rows in each table. +** sqlite3_expert_config(pExpert, EXPERT_CONFIG_SAMPLE, 10); +*/ +int sqlite3_expert_config(sqlite3expert *p, int op, ...); + +#define EXPERT_CONFIG_SAMPLE 1 /* int */ + +/* +** Specify zero or more SQL statements to be included in the analysis. +** +** Buffer zSql must contain zero or more complete SQL statements. This +** function parses all statements contained in the buffer and adds them +** to the internal list of statements to analyze. If successful, SQLITE_OK +** is returned and (*pzErr) set to NULL. Or, if an error occurs - for example +** due to a error in the SQL - an SQLite error code is returned and (*pzErr) +** may be set to point to an English language error message. In this case +** the caller is responsible for eventually freeing the error message buffer +** using sqlite3_free(). +** +** If an error does occur while processing one of the statements in the +** buffer passed as the second argument, none of the statements in the +** buffer are added to the analysis. +** +** This function must be called before sqlite3_expert_analyze(). If a call +** to this function is made on an sqlite3expert object that has already +** been passed to sqlite3_expert_analyze() SQLITE_MISUSE is returned +** immediately and no statements are added to the analysis. +*/ +int sqlite3_expert_sql( + sqlite3expert *p, /* From a successful sqlite3_expert_new() */ + const char *zSql, /* SQL statement(s) to add */ + char **pzErr /* OUT: Error message (if any) */ +); + + +/* +** This function is called after the sqlite3expert object has been configured +** with all SQL statements using sqlite3_expert_sql() to actually perform +** the analysis. Once this function has been called, it is not possible to +** add further SQL statements to the analysis. +** +** If successful, SQLITE_OK is returned and (*pzErr) is set to NULL. Or, if +** an error occurs, an SQLite error code is returned and (*pzErr) set to +** point to a buffer containing an English language error message. In this +** case it is the responsibility of the caller to eventually free the buffer +** using sqlite3_free(). +** +** If an error does occur within this function, the sqlite3expert object +** is no longer useful for any purpose. At that point it is no longer +** possible to add further SQL statements to the object or to re-attempt +** the analysis. The sqlite3expert object must still be freed using a call +** sqlite3_expert_destroy(). +*/ +int sqlite3_expert_analyze(sqlite3expert *p, char **pzErr); + +/* +** Return the total number of statements loaded using sqlite3_expert_sql(). +** The total number of SQL statements may be different from the total number +** to calls to sqlite3_expert_sql(). +*/ +int sqlite3_expert_count(sqlite3expert*); + +/* +** Return a component of the report. +** +** This function is called after sqlite3_expert_analyze() to extract the +** results of the analysis. Each call to this function returns either a +** NULL pointer or a pointer to a buffer containing a nul-terminated string. +** The value passed as the third argument must be one of the EXPERT_REPORT_* +** #define constants defined below. +** +** For some EXPERT_REPORT_* parameters, the buffer returned contains +** information relating to a specific SQL statement. In these cases that +** SQL statement is identified by the value passed as the second argument. +** SQL statements are numbered from 0 in the order in which they are parsed. +** If an out-of-range value (less than zero or equal to or greater than the +** value returned by sqlite3_expert_count()) is passed as the second argument +** along with such an EXPERT_REPORT_* parameter, NULL is always returned. +** +** EXPERT_REPORT_SQL: +** Return the text of SQL statement iStmt. +** +** EXPERT_REPORT_INDEXES: +** Return a buffer containing the CREATE INDEX statements for all recommended +** indexes for statement iStmt. If there are no new recommeded indexes, NULL +** is returned. +** +** EXPERT_REPORT_PLAN: +** Return a buffer containing the EXPLAIN QUERY PLAN output for SQL query +** iStmt after the proposed indexes have been added to the database schema. +** +** EXPERT_REPORT_CANDIDATES: +** Return a pointer to a buffer containing the CREATE INDEX statements +** for all indexes that were tested (for all SQL statements). The iStmt +** parameter is ignored for EXPERT_REPORT_CANDIDATES calls. +*/ +const char *sqlite3_expert_report(sqlite3expert*, int iStmt, int eReport); + +/* +** Values for the third argument passed to sqlite3_expert_report(). +*/ +#define EXPERT_REPORT_SQL 1 +#define EXPERT_REPORT_INDEXES 2 +#define EXPERT_REPORT_PLAN 3 +#define EXPERT_REPORT_CANDIDATES 4 + +/* +** Free an (sqlite3expert*) handle and all associated resources. There +** should be one call to this function for each successful call to +** sqlite3-expert_new(). +*/ +void sqlite3_expert_destroy(sqlite3expert*); + +#endif /* !defined(SQLITEEXPERT_H) */ + +/************************* End ../ext/expert/sqlite3expert.h ********************/ +/************************* Begin ../ext/expert/sqlite3expert.c ******************/ +/* +** 2017 April 09 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +*/ +/* #include "sqlite3expert.h" */ +#include <assert.h> +#include <string.h> +#include <stdio.h> + +#if !defined(SQLITE_AMALGAMATION) +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif +#endif /* !defined(SQLITE_AMALGAMATION) */ + + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* typedef sqlite3_int64 i64; */ +/* typedef sqlite3_uint64 u64; */ + +typedef struct IdxColumn IdxColumn; +typedef struct IdxConstraint IdxConstraint; +typedef struct IdxScan IdxScan; +typedef struct IdxStatement IdxStatement; +typedef struct IdxTable IdxTable; +typedef struct IdxWrite IdxWrite; + +#define STRLEN (int)strlen + +/* +** A temp table name that we assume no user database will actually use. +** If this assumption proves incorrect triggers on the table with the +** conflicting name will be ignored. +*/ +#define UNIQUE_TABLE_NAME "t592690916721053953805701627921227776" + +/* +** A single constraint. Equivalent to either "col = ?" or "col < ?" (or +** any other type of single-ended range constraint on a column). +** +** pLink: +** Used to temporarily link IdxConstraint objects into lists while +** creating candidate indexes. +*/ +struct IdxConstraint { + char *zColl; /* Collation sequence */ + int bRange; /* True for range, false for eq */ + int iCol; /* Constrained table column */ + int bFlag; /* Used by idxFindCompatible() */ + int bDesc; /* True if ORDER BY <expr> DESC */ + IdxConstraint *pNext; /* Next constraint in pEq or pRange list */ + IdxConstraint *pLink; /* See above */ +}; + +/* +** A single scan of a single table. +*/ +struct IdxScan { + IdxTable *pTab; /* Associated table object */ + int iDb; /* Database containing table zTable */ + i64 covering; /* Mask of columns required for cov. index */ + IdxConstraint *pOrder; /* ORDER BY columns */ + IdxConstraint *pEq; /* List of == constraints */ + IdxConstraint *pRange; /* List of < constraints */ + IdxScan *pNextScan; /* Next IdxScan object for same analysis */ +}; + +/* +** Information regarding a single database table. Extracted from +** "PRAGMA table_info" by function idxGetTableInfo(). +*/ +struct IdxColumn { + char *zName; + char *zColl; + int iPk; +}; +struct IdxTable { + int nCol; + char *zName; /* Table name */ + IdxColumn *aCol; + IdxTable *pNext; /* Next table in linked list of all tables */ +}; + +/* +** An object of the following type is created for each unique table/write-op +** seen. The objects are stored in a singly-linked list beginning at +** sqlite3expert.pWrite. +*/ +struct IdxWrite { + IdxTable *pTab; + int eOp; /* SQLITE_UPDATE, DELETE or INSERT */ + IdxWrite *pNext; +}; + +/* +** Each statement being analyzed is represented by an instance of this +** structure. +*/ +struct IdxStatement { + int iId; /* Statement number */ + char *zSql; /* SQL statement */ + char *zIdx; /* Indexes */ + char *zEQP; /* Plan */ + IdxStatement *pNext; +}; + + +/* +** A hash table for storing strings. With space for a payload string +** with each entry. Methods are: +** +** idxHashInit() +** idxHashClear() +** idxHashAdd() +** idxHashSearch() +*/ +#define IDX_HASH_SIZE 1023 +typedef struct IdxHashEntry IdxHashEntry; +typedef struct IdxHash IdxHash; +struct IdxHashEntry { + char *zKey; /* nul-terminated key */ + char *zVal; /* nul-terminated value string */ + char *zVal2; /* nul-terminated value string 2 */ + IdxHashEntry *pHashNext; /* Next entry in same hash bucket */ + IdxHashEntry *pNext; /* Next entry in hash */ +}; +struct IdxHash { + IdxHashEntry *pFirst; + IdxHashEntry *aHash[IDX_HASH_SIZE]; +}; + +/* +** sqlite3expert object. +*/ +struct sqlite3expert { + int iSample; /* Percentage of tables to sample for stat1 */ + sqlite3 *db; /* User database */ + sqlite3 *dbm; /* In-memory db for this analysis */ + sqlite3 *dbv; /* Vtab schema for this analysis */ + IdxTable *pTable; /* List of all IdxTable objects */ + IdxScan *pScan; /* List of scan objects */ + IdxWrite *pWrite; /* List of write objects */ + IdxStatement *pStatement; /* List of IdxStatement objects */ + int bRun; /* True once analysis has run */ + char **pzErrmsg; + int rc; /* Error code from whereinfo hook */ + IdxHash hIdx; /* Hash containing all candidate indexes */ + char *zCandidates; /* For EXPERT_REPORT_CANDIDATES */ +}; + + +/* +** Allocate and return nByte bytes of zeroed memory using sqlite3_malloc(). +** If the allocation fails, set *pRc to SQLITE_NOMEM and return NULL. +*/ +static void *idxMalloc(int *pRc, int nByte){ + void *pRet; + assert( *pRc==SQLITE_OK ); + assert( nByte>0 ); + pRet = sqlite3_malloc(nByte); + if( pRet ){ + memset(pRet, 0, nByte); + }else{ + *pRc = SQLITE_NOMEM; + } + return pRet; +} + +/* +** Initialize an IdxHash hash table. +*/ +static void idxHashInit(IdxHash *pHash){ + memset(pHash, 0, sizeof(IdxHash)); +} + +/* +** Reset an IdxHash hash table. +*/ +static void idxHashClear(IdxHash *pHash){ + int i; + for(i=0; i<IDX_HASH_SIZE; i++){ + IdxHashEntry *pEntry; + IdxHashEntry *pNext; + for(pEntry=pHash->aHash[i]; pEntry; pEntry=pNext){ + pNext = pEntry->pHashNext; + sqlite3_free(pEntry->zVal2); + sqlite3_free(pEntry); + } + } + memset(pHash, 0, sizeof(IdxHash)); +} + +/* +** Return the index of the hash bucket that the string specified by the +** arguments to this function belongs. +*/ +static int idxHashString(const char *z, int n){ + unsigned int ret = 0; + int i; + for(i=0; i<n; i++){ + ret += (ret<<3) + (unsigned char)(z[i]); + } + return (int)(ret % IDX_HASH_SIZE); +} + +/* +** If zKey is already present in the hash table, return non-zero and do +** nothing. Otherwise, add an entry with key zKey and payload string zVal to +** the hash table passed as the second argument. +*/ +static int idxHashAdd( + int *pRc, + IdxHash *pHash, + const char *zKey, + const char *zVal +){ + int nKey = STRLEN(zKey); + int iHash = idxHashString(zKey, nKey); + int nVal = (zVal ? STRLEN(zVal) : 0); + IdxHashEntry *pEntry; + assert( iHash>=0 ); + for(pEntry=pHash->aHash[iHash]; pEntry; pEntry=pEntry->pHashNext){ + if( STRLEN(pEntry->zKey)==nKey && 0==memcmp(pEntry->zKey, zKey, nKey) ){ + return 1; + } + } + pEntry = idxMalloc(pRc, sizeof(IdxHashEntry) + nKey+1 + nVal+1); + if( pEntry ){ + pEntry->zKey = (char*)&pEntry[1]; + memcpy(pEntry->zKey, zKey, nKey); + if( zVal ){ + pEntry->zVal = &pEntry->zKey[nKey+1]; + memcpy(pEntry->zVal, zVal, nVal); + } + pEntry->pHashNext = pHash->aHash[iHash]; + pHash->aHash[iHash] = pEntry; + + pEntry->pNext = pHash->pFirst; + pHash->pFirst = pEntry; + } + return 0; +} + +/* +** If zKey/nKey is present in the hash table, return a pointer to the +** hash-entry object. +*/ +static IdxHashEntry *idxHashFind(IdxHash *pHash, const char *zKey, int nKey){ + int iHash; + IdxHashEntry *pEntry; + if( nKey<0 ) nKey = STRLEN(zKey); + iHash = idxHashString(zKey, nKey); + assert( iHash>=0 ); + for(pEntry=pHash->aHash[iHash]; pEntry; pEntry=pEntry->pHashNext){ + if( STRLEN(pEntry->zKey)==nKey && 0==memcmp(pEntry->zKey, zKey, nKey) ){ + return pEntry; + } + } + return 0; +} + +/* +** If the hash table contains an entry with a key equal to the string +** passed as the final two arguments to this function, return a pointer +** to the payload string. Otherwise, if zKey/nKey is not present in the +** hash table, return NULL. +*/ +static const char *idxHashSearch(IdxHash *pHash, const char *zKey, int nKey){ + IdxHashEntry *pEntry = idxHashFind(pHash, zKey, nKey); + if( pEntry ) return pEntry->zVal; + return 0; +} + +/* +** Allocate and return a new IdxConstraint object. Set the IdxConstraint.zColl +** variable to point to a copy of nul-terminated string zColl. +*/ +static IdxConstraint *idxNewConstraint(int *pRc, const char *zColl){ + IdxConstraint *pNew; + int nColl = STRLEN(zColl); + + assert( *pRc==SQLITE_OK ); + pNew = (IdxConstraint*)idxMalloc(pRc, sizeof(IdxConstraint) * nColl + 1); + if( pNew ){ + pNew->zColl = (char*)&pNew[1]; + memcpy(pNew->zColl, zColl, nColl+1); + } + return pNew; +} + +/* +** An error associated with database handle db has just occurred. Pass +** the error message to callback function xOut. +*/ +static void idxDatabaseError( + sqlite3 *db, /* Database handle */ + char **pzErrmsg /* Write error here */ +){ + *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); +} + +/* +** Prepare an SQL statement. +*/ +static int idxPrepareStmt( + sqlite3 *db, /* Database handle to compile against */ + sqlite3_stmt **ppStmt, /* OUT: Compiled SQL statement */ + char **pzErrmsg, /* OUT: sqlite3_malloc()ed error message */ + const char *zSql /* SQL statement to compile */ +){ + int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); + if( rc!=SQLITE_OK ){ + *ppStmt = 0; + idxDatabaseError(db, pzErrmsg); + } + return rc; +} + +/* +** Prepare an SQL statement using the results of a printf() formatting. +*/ +static int idxPrintfPrepareStmt( + sqlite3 *db, /* Database handle to compile against */ + sqlite3_stmt **ppStmt, /* OUT: Compiled SQL statement */ + char **pzErrmsg, /* OUT: sqlite3_malloc()ed error message */ + const char *zFmt, /* printf() format of SQL statement */ + ... /* Trailing printf() arguments */ +){ + va_list ap; + int rc; + char *zSql; + va_start(ap, zFmt); + zSql = sqlite3_vmprintf(zFmt, ap); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = idxPrepareStmt(db, ppStmt, pzErrmsg, zSql); + sqlite3_free(zSql); + } + va_end(ap); + return rc; +} + + +/************************************************************************* +** Beginning of virtual table implementation. +*/ +typedef struct ExpertVtab ExpertVtab; +struct ExpertVtab { + sqlite3_vtab base; + IdxTable *pTab; + sqlite3expert *pExpert; +}; + +typedef struct ExpertCsr ExpertCsr; +struct ExpertCsr { + sqlite3_vtab_cursor base; + sqlite3_stmt *pData; +}; + +static char *expertDequote(const char *zIn){ + int n = STRLEN(zIn); + char *zRet = sqlite3_malloc(n); + + assert( zIn[0]=='\'' ); + assert( zIn[n-1]=='\'' ); + + if( zRet ){ + int iOut = 0; + int iIn = 0; + for(iIn=1; iIn<(n-1); iIn++){ + if( zIn[iIn]=='\'' ){ + assert( zIn[iIn+1]=='\'' ); + iIn++; + } + zRet[iOut++] = zIn[iIn]; + } + zRet[iOut] = '\0'; + } + + return zRet; +} + +/* +** This function is the implementation of both the xConnect and xCreate +** methods of the r-tree virtual table. +** +** argv[0] -> module name +** argv[1] -> database name +** argv[2] -> table name +** argv[...] -> column names... +*/ +static int expertConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + sqlite3expert *pExpert = (sqlite3expert*)pAux; + ExpertVtab *p = 0; + int rc; + + if( argc!=4 ){ + *pzErr = sqlite3_mprintf("internal error!"); + rc = SQLITE_ERROR; + }else{ + char *zCreateTable = expertDequote(argv[3]); + if( zCreateTable ){ + rc = sqlite3_declare_vtab(db, zCreateTable); + if( rc==SQLITE_OK ){ + p = idxMalloc(&rc, sizeof(ExpertVtab)); + } + if( rc==SQLITE_OK ){ + p->pExpert = pExpert; + p->pTab = pExpert->pTable; + assert( sqlite3_stricmp(p->pTab->zName, argv[2])==0 ); + } + sqlite3_free(zCreateTable); + }else{ + rc = SQLITE_NOMEM; + } + } + + *ppVtab = (sqlite3_vtab*)p; + return rc; +} + +static int expertDisconnect(sqlite3_vtab *pVtab){ + ExpertVtab *p = (ExpertVtab*)pVtab; + sqlite3_free(p); + return SQLITE_OK; +} + +static int expertBestIndex(sqlite3_vtab *pVtab, sqlite3_index_info *pIdxInfo){ + ExpertVtab *p = (ExpertVtab*)pVtab; + int rc = SQLITE_OK; + int n = 0; + IdxScan *pScan; + const int opmask = + SQLITE_INDEX_CONSTRAINT_EQ | SQLITE_INDEX_CONSTRAINT_GT | + SQLITE_INDEX_CONSTRAINT_LT | SQLITE_INDEX_CONSTRAINT_GE | + SQLITE_INDEX_CONSTRAINT_LE; + + pScan = idxMalloc(&rc, sizeof(IdxScan)); + if( pScan ){ + int i; + + /* Link the new scan object into the list */ + pScan->pTab = p->pTab; + pScan->pNextScan = p->pExpert->pScan; + p->pExpert->pScan = pScan; + + /* Add the constraints to the IdxScan object */ + for(i=0; i<pIdxInfo->nConstraint; i++){ + struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i]; + if( pCons->usable + && pCons->iColumn>=0 + && p->pTab->aCol[pCons->iColumn].iPk==0 + && (pCons->op & opmask) + ){ + IdxConstraint *pNew; + const char *zColl = sqlite3_vtab_collation(pIdxInfo, i); + pNew = idxNewConstraint(&rc, zColl); + if( pNew ){ + pNew->iCol = pCons->iColumn; + if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + pNew->pNext = pScan->pEq; + pScan->pEq = pNew; + }else{ + pNew->bRange = 1; + pNew->pNext = pScan->pRange; + pScan->pRange = pNew; + } + } + n++; + pIdxInfo->aConstraintUsage[i].argvIndex = n; + } + } + + /* Add the ORDER BY to the IdxScan object */ + for(i=pIdxInfo->nOrderBy-1; i>=0; i--){ + int iCol = pIdxInfo->aOrderBy[i].iColumn; + if( iCol>=0 ){ + IdxConstraint *pNew = idxNewConstraint(&rc, p->pTab->aCol[iCol].zColl); + if( pNew ){ + pNew->iCol = iCol; + pNew->bDesc = pIdxInfo->aOrderBy[i].desc; + pNew->pNext = pScan->pOrder; + pNew->pLink = pScan->pOrder; + pScan->pOrder = pNew; + n++; + } + } + } + } + + pIdxInfo->estimatedCost = 1000000.0 / (n+1); + return rc; +} + +static int expertUpdate( + sqlite3_vtab *pVtab, + int nData, + sqlite3_value **azData, + sqlite_int64 *pRowid +){ + (void)pVtab; + (void)nData; + (void)azData; + (void)pRowid; + return SQLITE_OK; +} + +/* +** Virtual table module xOpen method. +*/ +static int expertOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + int rc = SQLITE_OK; + ExpertCsr *pCsr; + (void)pVTab; + pCsr = idxMalloc(&rc, sizeof(ExpertCsr)); + *ppCursor = (sqlite3_vtab_cursor*)pCsr; + return rc; +} + +/* +** Virtual table module xClose method. +*/ +static int expertClose(sqlite3_vtab_cursor *cur){ + ExpertCsr *pCsr = (ExpertCsr*)cur; + sqlite3_finalize(pCsr->pData); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** Virtual table module xEof method. +** +** Return non-zero if the cursor does not currently point to a valid +** record (i.e if the scan has finished), or zero otherwise. +*/ +static int expertEof(sqlite3_vtab_cursor *cur){ + ExpertCsr *pCsr = (ExpertCsr*)cur; + return pCsr->pData==0; +} + +/* +** Virtual table module xNext method. +*/ +static int expertNext(sqlite3_vtab_cursor *cur){ + ExpertCsr *pCsr = (ExpertCsr*)cur; + int rc = SQLITE_OK; + + assert( pCsr->pData ); + rc = sqlite3_step(pCsr->pData); + if( rc!=SQLITE_ROW ){ + rc = sqlite3_finalize(pCsr->pData); + pCsr->pData = 0; + }else{ + rc = SQLITE_OK; + } + + return rc; +} + +/* +** Virtual table module xRowid method. +*/ +static int expertRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + (void)cur; + *pRowid = 0; + return SQLITE_OK; +} + +/* +** Virtual table module xColumn method. +*/ +static int expertColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ + ExpertCsr *pCsr = (ExpertCsr*)cur; + sqlite3_value *pVal; + pVal = sqlite3_column_value(pCsr->pData, i); + if( pVal ){ + sqlite3_result_value(ctx, pVal); + } + return SQLITE_OK; +} + +/* +** Virtual table module xFilter method. +*/ +static int expertFilter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + ExpertCsr *pCsr = (ExpertCsr*)cur; + ExpertVtab *pVtab = (ExpertVtab*)(cur->pVtab); + sqlite3expert *pExpert = pVtab->pExpert; + int rc; + + (void)idxNum; + (void)idxStr; + (void)argc; + (void)argv; + rc = sqlite3_finalize(pCsr->pData); + pCsr->pData = 0; + if( rc==SQLITE_OK ){ + rc = idxPrintfPrepareStmt(pExpert->db, &pCsr->pData, &pVtab->base.zErrMsg, + "SELECT * FROM main.%Q WHERE sample()", pVtab->pTab->zName + ); + } + + if( rc==SQLITE_OK ){ + rc = expertNext(cur); + } + return rc; +} + +static int idxRegisterVtab(sqlite3expert *p){ + static sqlite3_module expertModule = { + 2, /* iVersion */ + expertConnect, /* xCreate - create a table */ + expertConnect, /* xConnect - connect to an existing table */ + expertBestIndex, /* xBestIndex - Determine search strategy */ + expertDisconnect, /* xDisconnect - Disconnect from a table */ + expertDisconnect, /* xDestroy - Drop a table */ + expertOpen, /* xOpen - open a cursor */ + expertClose, /* xClose - close a cursor */ + expertFilter, /* xFilter - configure scan constraints */ + expertNext, /* xNext - advance a cursor */ + expertEof, /* xEof */ + expertColumn, /* xColumn - read data */ + expertRowid, /* xRowid - read data */ + expertUpdate, /* xUpdate - write data */ + 0, /* xBegin - begin transaction */ + 0, /* xSync - sync transaction */ + 0, /* xCommit - commit transaction */ + 0, /* xRollback - rollback transaction */ + 0, /* xFindFunction - function overloading */ + 0, /* xRename - rename the table */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + }; + + return sqlite3_create_module(p->dbv, "expert", &expertModule, (void*)p); +} +/* +** End of virtual table implementation. +*************************************************************************/ +/* +** Finalize SQL statement pStmt. If (*pRc) is SQLITE_OK when this function +** is called, set it to the return value of sqlite3_finalize() before +** returning. Otherwise, discard the sqlite3_finalize() return value. +*/ +static void idxFinalize(int *pRc, sqlite3_stmt *pStmt){ + int rc = sqlite3_finalize(pStmt); + if( *pRc==SQLITE_OK ) *pRc = rc; +} + +/* +** Attempt to allocate an IdxTable structure corresponding to table zTab +** in the main database of connection db. If successful, set (*ppOut) to +** point to the new object and return SQLITE_OK. Otherwise, return an +** SQLite error code and set (*ppOut) to NULL. In this case *pzErrmsg may be +** set to point to an error string. +** +** It is the responsibility of the caller to eventually free either the +** IdxTable object or error message using sqlite3_free(). +*/ +static int idxGetTableInfo( + sqlite3 *db, /* Database connection to read details from */ + const char *zTab, /* Table name */ + IdxTable **ppOut, /* OUT: New object (if successful) */ + char **pzErrmsg /* OUT: Error message (if not) */ +){ + sqlite3_stmt *p1 = 0; + int nCol = 0; + int nTab; + int nByte; + IdxTable *pNew = 0; + int rc, rc2; + char *pCsr = 0; + int nPk = 0; + + *ppOut = 0; + if( zTab==0 ) return SQLITE_ERROR; + nTab = STRLEN(zTab); + nByte = sizeof(IdxTable) + nTab + 1; + rc = idxPrintfPrepareStmt(db, &p1, pzErrmsg, "PRAGMA table_xinfo=%Q", zTab); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(p1) ){ + const char *zCol = (const char*)sqlite3_column_text(p1, 1); + const char *zColSeq = 0; + if( zCol==0 ){ + rc = SQLITE_ERROR; + break; + } + nByte += 1 + STRLEN(zCol); + rc = sqlite3_table_column_metadata( + db, "main", zTab, zCol, 0, &zColSeq, 0, 0, 0 + ); + if( zColSeq==0 ) zColSeq = "binary"; + nByte += 1 + STRLEN(zColSeq); + nCol++; + nPk += (sqlite3_column_int(p1, 5)>0); + } + rc2 = sqlite3_reset(p1); + if( rc==SQLITE_OK ) rc = rc2; + + nByte += sizeof(IdxColumn) * nCol; + if( rc==SQLITE_OK ){ + pNew = idxMalloc(&rc, nByte); + } + if( rc==SQLITE_OK ){ + pNew->aCol = (IdxColumn*)&pNew[1]; + pNew->nCol = nCol; + pCsr = (char*)&pNew->aCol[nCol]; + } + + nCol = 0; + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(p1) ){ + const char *zCol = (const char*)sqlite3_column_text(p1, 1); + const char *zColSeq = 0; + int nCopy; + if( zCol==0 ) continue; + nCopy = STRLEN(zCol) + 1; + pNew->aCol[nCol].zName = pCsr; + pNew->aCol[nCol].iPk = (sqlite3_column_int(p1, 5)==1 && nPk==1); + memcpy(pCsr, zCol, nCopy); + pCsr += nCopy; + + rc = sqlite3_table_column_metadata( + db, "main", zTab, zCol, 0, &zColSeq, 0, 0, 0 + ); + if( rc==SQLITE_OK ){ + if( zColSeq==0 ) zColSeq = "binary"; + nCopy = STRLEN(zColSeq) + 1; + pNew->aCol[nCol].zColl = pCsr; + memcpy(pCsr, zColSeq, nCopy); + pCsr += nCopy; + } + + nCol++; + } + idxFinalize(&rc, p1); + + if( rc!=SQLITE_OK ){ + sqlite3_free(pNew); + pNew = 0; + }else if( ALWAYS(pNew!=0) ){ + pNew->zName = pCsr; + if( ALWAYS(pNew->zName!=0) ) memcpy(pNew->zName, zTab, nTab+1); + } + + *ppOut = pNew; + return rc; +} + +/* +** This function is a no-op if *pRc is set to anything other than +** SQLITE_OK when it is called. +** +** If *pRc is initially set to SQLITE_OK, then the text specified by +** the printf() style arguments is appended to zIn and the result returned +** in a buffer allocated by sqlite3_malloc(). sqlite3_free() is called on +** zIn before returning. +*/ +static char *idxAppendText(int *pRc, char *zIn, const char *zFmt, ...){ + va_list ap; + char *zAppend = 0; + char *zRet = 0; + int nIn = zIn ? STRLEN(zIn) : 0; + int nAppend = 0; + va_start(ap, zFmt); + if( *pRc==SQLITE_OK ){ + zAppend = sqlite3_vmprintf(zFmt, ap); + if( zAppend ){ + nAppend = STRLEN(zAppend); + zRet = (char*)sqlite3_malloc(nIn + nAppend + 1); + } + if( zAppend && zRet ){ + if( nIn ) memcpy(zRet, zIn, nIn); + memcpy(&zRet[nIn], zAppend, nAppend+1); + }else{ + sqlite3_free(zRet); + zRet = 0; + *pRc = SQLITE_NOMEM; + } + sqlite3_free(zAppend); + sqlite3_free(zIn); + } + va_end(ap); + return zRet; +} + +/* +** Return true if zId must be quoted in order to use it as an SQL +** identifier, or false otherwise. +*/ +static int idxIdentifierRequiresQuotes(const char *zId){ + int i; + int nId = STRLEN(zId); + + if( sqlite3_keyword_check(zId, nId) ) return 1; + + for(i=0; zId[i]; i++){ + if( !(zId[i]=='_') + && !(zId[i]>='0' && zId[i]<='9') + && !(zId[i]>='a' && zId[i]<='z') + && !(zId[i]>='A' && zId[i]<='Z') + ){ + return 1; + } + } + return 0; +} + +/* +** This function appends an index column definition suitable for constraint +** pCons to the string passed as zIn and returns the result. +*/ +static char *idxAppendColDefn( + int *pRc, /* IN/OUT: Error code */ + char *zIn, /* Column defn accumulated so far */ + IdxTable *pTab, /* Table index will be created on */ + IdxConstraint *pCons +){ + char *zRet = zIn; + IdxColumn *p = &pTab->aCol[pCons->iCol]; + if( zRet ) zRet = idxAppendText(pRc, zRet, ", "); + + if( idxIdentifierRequiresQuotes(p->zName) ){ + zRet = idxAppendText(pRc, zRet, "%Q", p->zName); + }else{ + zRet = idxAppendText(pRc, zRet, "%s", p->zName); + } + + if( sqlite3_stricmp(p->zColl, pCons->zColl) ){ + if( idxIdentifierRequiresQuotes(pCons->zColl) ){ + zRet = idxAppendText(pRc, zRet, " COLLATE %Q", pCons->zColl); + }else{ + zRet = idxAppendText(pRc, zRet, " COLLATE %s", pCons->zColl); + } + } + + if( pCons->bDesc ){ + zRet = idxAppendText(pRc, zRet, " DESC"); + } + return zRet; +} + +/* +** Search database dbm for an index compatible with the one idxCreateFromCons() +** would create from arguments pScan, pEq and pTail. If no error occurs and +** such an index is found, return non-zero. Or, if no such index is found, +** return zero. +** +** If an error occurs, set *pRc to an SQLite error code and return zero. +*/ +static int idxFindCompatible( + int *pRc, /* OUT: Error code */ + sqlite3* dbm, /* Database to search */ + IdxScan *pScan, /* Scan for table to search for index on */ + IdxConstraint *pEq, /* List of == constraints */ + IdxConstraint *pTail /* List of range constraints */ +){ + const char *zTbl = pScan->pTab->zName; + sqlite3_stmt *pIdxList = 0; + IdxConstraint *pIter; + int nEq = 0; /* Number of elements in pEq */ + int rc; + + /* Count the elements in list pEq */ + for(pIter=pEq; pIter; pIter=pIter->pLink) nEq++; + + rc = idxPrintfPrepareStmt(dbm, &pIdxList, 0, "PRAGMA index_list=%Q", zTbl); + while( rc==SQLITE_OK && sqlite3_step(pIdxList)==SQLITE_ROW ){ + int bMatch = 1; + IdxConstraint *pT = pTail; + sqlite3_stmt *pInfo = 0; + const char *zIdx = (const char*)sqlite3_column_text(pIdxList, 1); + if( zIdx==0 ) continue; + + /* Zero the IdxConstraint.bFlag values in the pEq list */ + for(pIter=pEq; pIter; pIter=pIter->pLink) pIter->bFlag = 0; + + rc = idxPrintfPrepareStmt(dbm, &pInfo, 0, "PRAGMA index_xInfo=%Q", zIdx); + while( rc==SQLITE_OK && sqlite3_step(pInfo)==SQLITE_ROW ){ + int iIdx = sqlite3_column_int(pInfo, 0); + int iCol = sqlite3_column_int(pInfo, 1); + const char *zColl = (const char*)sqlite3_column_text(pInfo, 4); + + if( iIdx<nEq ){ + for(pIter=pEq; pIter; pIter=pIter->pLink){ + if( pIter->bFlag ) continue; + if( pIter->iCol!=iCol ) continue; + if( sqlite3_stricmp(pIter->zColl, zColl) ) continue; + pIter->bFlag = 1; + break; + } + if( pIter==0 ){ + bMatch = 0; + break; + } + }else{ + if( pT ){ + if( pT->iCol!=iCol || sqlite3_stricmp(pT->zColl, zColl) ){ + bMatch = 0; + break; + } + pT = pT->pLink; + } + } + } + idxFinalize(&rc, pInfo); + + if( rc==SQLITE_OK && bMatch ){ + sqlite3_finalize(pIdxList); + return 1; + } + } + idxFinalize(&rc, pIdxList); + + *pRc = rc; + return 0; +} + +/* Callback for sqlite3_exec() with query with leading count(*) column. + * The first argument is expected to be an int*, referent to be incremented + * if that leading column is not exactly '0'. + */ +static int countNonzeros(void* pCount, int nc, + char* azResults[], char* azColumns[]){ + (void)azColumns; /* Suppress unused parameter warning */ + if( nc>0 && (azResults[0][0]!='0' || azResults[0][1]!=0) ){ + *((int *)pCount) += 1; + } + return 0; +} + +static int idxCreateFromCons( + sqlite3expert *p, + IdxScan *pScan, + IdxConstraint *pEq, + IdxConstraint *pTail +){ + sqlite3 *dbm = p->dbm; + int rc = SQLITE_OK; + if( (pEq || pTail) && 0==idxFindCompatible(&rc, dbm, pScan, pEq, pTail) ){ + IdxTable *pTab = pScan->pTab; + char *zCols = 0; + char *zIdx = 0; + IdxConstraint *pCons; + unsigned int h = 0; + const char *zFmt; + + for(pCons=pEq; pCons; pCons=pCons->pLink){ + zCols = idxAppendColDefn(&rc, zCols, pTab, pCons); + } + for(pCons=pTail; pCons; pCons=pCons->pLink){ + zCols = idxAppendColDefn(&rc, zCols, pTab, pCons); + } + + if( rc==SQLITE_OK ){ + /* Hash the list of columns to come up with a name for the index */ + const char *zTable = pScan->pTab->zName; + int quoteTable = idxIdentifierRequiresQuotes(zTable); + char *zName = 0; /* Index name */ + int collisions = 0; + do{ + int i; + char *zFind; + for(i=0; zCols[i]; i++){ + h += ((h<<3) + zCols[i]); + } + sqlite3_free(zName); + zName = sqlite3_mprintf("%s_idx_%08x", zTable, h); + if( zName==0 ) break; + /* Is is unique among table, view and index names? */ + zFmt = "SELECT count(*) FROM sqlite_schema WHERE name=%Q" + " AND type in ('index','table','view')"; + zFind = sqlite3_mprintf(zFmt, zName); + i = 0; + rc = sqlite3_exec(dbm, zFind, countNonzeros, &i, 0); + assert(rc==SQLITE_OK); + sqlite3_free(zFind); + if( i==0 ){ + collisions = 0; + break; + } + ++collisions; + }while( collisions<50 && zName!=0 ); + if( collisions ){ + /* This return means "Gave up trying to find a unique index name." */ + rc = SQLITE_BUSY_TIMEOUT; + }else if( zName==0 ){ + rc = SQLITE_NOMEM; + }else{ + if( quoteTable ){ + zFmt = "CREATE INDEX \"%w\" ON \"%w\"(%s)"; + }else{ + zFmt = "CREATE INDEX %s ON %s(%s)"; + } + zIdx = sqlite3_mprintf(zFmt, zName, zTable, zCols); + if( !zIdx ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_exec(dbm, zIdx, 0, 0, p->pzErrmsg); + if( rc!=SQLITE_OK ){ + rc = SQLITE_BUSY_TIMEOUT; + }else{ + idxHashAdd(&rc, &p->hIdx, zName, zIdx); + } + } + sqlite3_free(zName); + sqlite3_free(zIdx); + } + } + + sqlite3_free(zCols); + } + return rc; +} + +/* +** Return true if list pList (linked by IdxConstraint.pLink) contains +** a constraint compatible with *p. Otherwise return false. +*/ +static int idxFindConstraint(IdxConstraint *pList, IdxConstraint *p){ + IdxConstraint *pCmp; + for(pCmp=pList; pCmp; pCmp=pCmp->pLink){ + if( p->iCol==pCmp->iCol ) return 1; + } + return 0; +} + +static int idxCreateFromWhere( + sqlite3expert *p, + IdxScan *pScan, /* Create indexes for this scan */ + IdxConstraint *pTail /* range/ORDER BY constraints for inclusion */ +){ + IdxConstraint *p1 = 0; + IdxConstraint *pCon; + int rc; + + /* Gather up all the == constraints. */ + for(pCon=pScan->pEq; pCon; pCon=pCon->pNext){ + if( !idxFindConstraint(p1, pCon) && !idxFindConstraint(pTail, pCon) ){ + pCon->pLink = p1; + p1 = pCon; + } + } + + /* Create an index using the == constraints collected above. And the + ** range constraint/ORDER BY terms passed in by the caller, if any. */ + rc = idxCreateFromCons(p, pScan, p1, pTail); + + /* If no range/ORDER BY passed by the caller, create a version of the + ** index for each range constraint. */ + if( pTail==0 ){ + for(pCon=pScan->pRange; rc==SQLITE_OK && pCon; pCon=pCon->pNext){ + assert( pCon->pLink==0 ); + if( !idxFindConstraint(p1, pCon) && !idxFindConstraint(pTail, pCon) ){ + rc = idxCreateFromCons(p, pScan, p1, pCon); + } + } + } + + return rc; +} + +/* +** Create candidate indexes in database [dbm] based on the data in +** linked-list pScan. +*/ +static int idxCreateCandidates(sqlite3expert *p){ + int rc = SQLITE_OK; + IdxScan *pIter; + + for(pIter=p->pScan; pIter && rc==SQLITE_OK; pIter=pIter->pNextScan){ + rc = idxCreateFromWhere(p, pIter, 0); + if( rc==SQLITE_OK && pIter->pOrder ){ + rc = idxCreateFromWhere(p, pIter, pIter->pOrder); + } + } + + return rc; +} + +/* +** Free all elements of the linked list starting at pConstraint. +*/ +static void idxConstraintFree(IdxConstraint *pConstraint){ + IdxConstraint *pNext; + IdxConstraint *p; + + for(p=pConstraint; p; p=pNext){ + pNext = p->pNext; + sqlite3_free(p); + } +} + +/* +** Free all elements of the linked list starting from pScan up until pLast +** (pLast is not freed). +*/ +static void idxScanFree(IdxScan *pScan, IdxScan *pLast){ + IdxScan *p; + IdxScan *pNext; + for(p=pScan; p!=pLast; p=pNext){ + pNext = p->pNextScan; + idxConstraintFree(p->pOrder); + idxConstraintFree(p->pEq); + idxConstraintFree(p->pRange); + sqlite3_free(p); + } +} + +/* +** Free all elements of the linked list starting from pStatement up +** until pLast (pLast is not freed). +*/ +static void idxStatementFree(IdxStatement *pStatement, IdxStatement *pLast){ + IdxStatement *p; + IdxStatement *pNext; + for(p=pStatement; p!=pLast; p=pNext){ + pNext = p->pNext; + sqlite3_free(p->zEQP); + sqlite3_free(p->zIdx); + sqlite3_free(p); + } +} + +/* +** Free the linked list of IdxTable objects starting at pTab. +*/ +static void idxTableFree(IdxTable *pTab){ + IdxTable *pIter; + IdxTable *pNext; + for(pIter=pTab; pIter; pIter=pNext){ + pNext = pIter->pNext; + sqlite3_free(pIter); + } +} + +/* +** Free the linked list of IdxWrite objects starting at pTab. +*/ +static void idxWriteFree(IdxWrite *pTab){ + IdxWrite *pIter; + IdxWrite *pNext; + for(pIter=pTab; pIter; pIter=pNext){ + pNext = pIter->pNext; + sqlite3_free(pIter); + } +} + + + +/* +** This function is called after candidate indexes have been created. It +** runs all the queries to see which indexes they prefer, and populates +** IdxStatement.zIdx and IdxStatement.zEQP with the results. +*/ +static int idxFindIndexes( + sqlite3expert *p, + char **pzErr /* OUT: Error message (sqlite3_malloc) */ +){ + IdxStatement *pStmt; + sqlite3 *dbm = p->dbm; + int rc = SQLITE_OK; + + IdxHash hIdx; + idxHashInit(&hIdx); + + for(pStmt=p->pStatement; rc==SQLITE_OK && pStmt; pStmt=pStmt->pNext){ + IdxHashEntry *pEntry; + sqlite3_stmt *pExplain = 0; + idxHashClear(&hIdx); + rc = idxPrintfPrepareStmt(dbm, &pExplain, pzErr, + "EXPLAIN QUERY PLAN %s", pStmt->zSql + ); + while( rc==SQLITE_OK && sqlite3_step(pExplain)==SQLITE_ROW ){ + /* int iId = sqlite3_column_int(pExplain, 0); */ + /* int iParent = sqlite3_column_int(pExplain, 1); */ + /* int iNotUsed = sqlite3_column_int(pExplain, 2); */ + const char *zDetail = (const char*)sqlite3_column_text(pExplain, 3); + int nDetail; + int i; + + if( !zDetail ) continue; + nDetail = STRLEN(zDetail); + + for(i=0; i<nDetail; i++){ + const char *zIdx = 0; + if( i+13<nDetail && memcmp(&zDetail[i], " USING INDEX ", 13)==0 ){ + zIdx = &zDetail[i+13]; + }else if( i+22<nDetail + && memcmp(&zDetail[i], " USING COVERING INDEX ", 22)==0 + ){ + zIdx = &zDetail[i+22]; + } + if( zIdx ){ + const char *zSql; + int nIdx = 0; + while( zIdx[nIdx]!='\0' && (zIdx[nIdx]!=' ' || zIdx[nIdx+1]!='(') ){ + nIdx++; + } + zSql = idxHashSearch(&p->hIdx, zIdx, nIdx); + if( zSql ){ + idxHashAdd(&rc, &hIdx, zSql, 0); + if( rc ) goto find_indexes_out; + } + break; + } + } + + if( zDetail[0]!='-' ){ + pStmt->zEQP = idxAppendText(&rc, pStmt->zEQP, "%s\n", zDetail); + } + } + + for(pEntry=hIdx.pFirst; pEntry; pEntry=pEntry->pNext){ + pStmt->zIdx = idxAppendText(&rc, pStmt->zIdx, "%s;\n", pEntry->zKey); + } + + idxFinalize(&rc, pExplain); + } + + find_indexes_out: + idxHashClear(&hIdx); + return rc; +} + +static int idxAuthCallback( + void *pCtx, + int eOp, + const char *z3, + const char *z4, + const char *zDb, + const char *zTrigger +){ + int rc = SQLITE_OK; + (void)z4; + (void)zTrigger; + if( eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE || eOp==SQLITE_DELETE ){ + if( sqlite3_stricmp(zDb, "main")==0 ){ + sqlite3expert *p = (sqlite3expert*)pCtx; + IdxTable *pTab; + for(pTab=p->pTable; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_stricmp(z3, pTab->zName) ) break; + } + if( pTab ){ + IdxWrite *pWrite; + for(pWrite=p->pWrite; pWrite; pWrite=pWrite->pNext){ + if( pWrite->pTab==pTab && pWrite->eOp==eOp ) break; + } + if( pWrite==0 ){ + pWrite = idxMalloc(&rc, sizeof(IdxWrite)); + if( rc==SQLITE_OK ){ + pWrite->pTab = pTab; + pWrite->eOp = eOp; + pWrite->pNext = p->pWrite; + p->pWrite = pWrite; + } + } + } + } + } + return rc; +} + +static int idxProcessOneTrigger( + sqlite3expert *p, + IdxWrite *pWrite, + char **pzErr +){ + static const char *zInt = UNIQUE_TABLE_NAME; + static const char *zDrop = "DROP TABLE " UNIQUE_TABLE_NAME; + IdxTable *pTab = pWrite->pTab; + const char *zTab = pTab->zName; + const char *zSql = + "SELECT 'CREATE TEMP' || substr(sql, 7) FROM sqlite_schema " + "WHERE tbl_name = %Q AND type IN ('table', 'trigger') " + "ORDER BY type;"; + sqlite3_stmt *pSelect = 0; + int rc = SQLITE_OK; + char *zWrite = 0; + + /* Create the table and its triggers in the temp schema */ + rc = idxPrintfPrepareStmt(p->db, &pSelect, pzErr, zSql, zTab, zTab); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSelect) ){ + const char *zCreate = (const char*)sqlite3_column_text(pSelect, 0); + if( zCreate==0 ) continue; + rc = sqlite3_exec(p->dbv, zCreate, 0, 0, pzErr); + } + idxFinalize(&rc, pSelect); + + /* Rename the table in the temp schema to zInt */ + if( rc==SQLITE_OK ){ + char *z = sqlite3_mprintf("ALTER TABLE temp.%Q RENAME TO %Q", zTab, zInt); + if( z==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_exec(p->dbv, z, 0, 0, pzErr); + sqlite3_free(z); + } + } + + switch( pWrite->eOp ){ + case SQLITE_INSERT: { + int i; + zWrite = idxAppendText(&rc, zWrite, "INSERT INTO %Q VALUES(", zInt); + for(i=0; i<pTab->nCol; i++){ + zWrite = idxAppendText(&rc, zWrite, "%s?", i==0 ? "" : ", "); + } + zWrite = idxAppendText(&rc, zWrite, ")"); + break; + } + case SQLITE_UPDATE: { + int i; + zWrite = idxAppendText(&rc, zWrite, "UPDATE %Q SET ", zInt); + for(i=0; i<pTab->nCol; i++){ + zWrite = idxAppendText(&rc, zWrite, "%s%Q=?", i==0 ? "" : ", ", + pTab->aCol[i].zName + ); + } + break; + } + default: { + assert( pWrite->eOp==SQLITE_DELETE ); + if( rc==SQLITE_OK ){ + zWrite = sqlite3_mprintf("DELETE FROM %Q", zInt); + if( zWrite==0 ) rc = SQLITE_NOMEM; + } + } + } + + if( rc==SQLITE_OK ){ + sqlite3_stmt *pX = 0; + rc = sqlite3_prepare_v2(p->dbv, zWrite, -1, &pX, 0); + idxFinalize(&rc, pX); + if( rc!=SQLITE_OK ){ + idxDatabaseError(p->dbv, pzErr); + } + } + sqlite3_free(zWrite); + + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(p->dbv, zDrop, 0, 0, pzErr); + } + + return rc; +} + +static int idxProcessTriggers(sqlite3expert *p, char **pzErr){ + int rc = SQLITE_OK; + IdxWrite *pEnd = 0; + IdxWrite *pFirst = p->pWrite; + + while( rc==SQLITE_OK && pFirst!=pEnd ){ + IdxWrite *pIter; + for(pIter=pFirst; rc==SQLITE_OK && pIter!=pEnd; pIter=pIter->pNext){ + rc = idxProcessOneTrigger(p, pIter, pzErr); + } + pEnd = pFirst; + pFirst = p->pWrite; + } + + return rc; +} + + +static int idxCreateVtabSchema(sqlite3expert *p, char **pzErrmsg){ + int rc = idxRegisterVtab(p); + sqlite3_stmt *pSchema = 0; + + /* For each table in the main db schema: + ** + ** 1) Add an entry to the p->pTable list, and + ** 2) Create the equivalent virtual table in dbv. + */ + rc = idxPrepareStmt(p->db, &pSchema, pzErrmsg, + "SELECT type, name, sql, 1 FROM sqlite_schema " + "WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%%' " + " UNION ALL " + "SELECT type, name, sql, 2 FROM sqlite_schema " + "WHERE type = 'trigger'" + " AND tbl_name IN(SELECT name FROM sqlite_schema WHERE type = 'view') " + "ORDER BY 4, 1" + ); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSchema) ){ + const char *zType = (const char*)sqlite3_column_text(pSchema, 0); + const char *zName = (const char*)sqlite3_column_text(pSchema, 1); + const char *zSql = (const char*)sqlite3_column_text(pSchema, 2); + + if( zType==0 || zName==0 ) continue; + if( zType[0]=='v' || zType[1]=='r' ){ + if( zSql ) rc = sqlite3_exec(p->dbv, zSql, 0, 0, pzErrmsg); + }else{ + IdxTable *pTab; + rc = idxGetTableInfo(p->db, zName, &pTab, pzErrmsg); + if( rc==SQLITE_OK ){ + int i; + char *zInner = 0; + char *zOuter = 0; + pTab->pNext = p->pTable; + p->pTable = pTab; + + /* The statement the vtab will pass to sqlite3_declare_vtab() */ + zInner = idxAppendText(&rc, 0, "CREATE TABLE x("); + for(i=0; i<pTab->nCol; i++){ + zInner = idxAppendText(&rc, zInner, "%s%Q COLLATE %s", + (i==0 ? "" : ", "), pTab->aCol[i].zName, pTab->aCol[i].zColl + ); + } + zInner = idxAppendText(&rc, zInner, ")"); + + /* The CVT statement to create the vtab */ + zOuter = idxAppendText(&rc, 0, + "CREATE VIRTUAL TABLE %Q USING expert(%Q)", zName, zInner + ); + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(p->dbv, zOuter, 0, 0, pzErrmsg); + } + sqlite3_free(zInner); + sqlite3_free(zOuter); + } + } + } + idxFinalize(&rc, pSchema); + return rc; +} + +struct IdxSampleCtx { + int iTarget; + double target; /* Target nRet/nRow value */ + double nRow; /* Number of rows seen */ + double nRet; /* Number of rows returned */ +}; + +static void idxSampleFunc( + sqlite3_context *pCtx, + int argc, + sqlite3_value **argv +){ + struct IdxSampleCtx *p = (struct IdxSampleCtx*)sqlite3_user_data(pCtx); + int bRet; + + (void)argv; + assert( argc==0 ); + if( p->nRow==0.0 ){ + bRet = 1; + }else{ + bRet = (p->nRet / p->nRow) <= p->target; + if( bRet==0 ){ + unsigned short rnd; + sqlite3_randomness(2, (void*)&rnd); + bRet = ((int)rnd % 100) <= p->iTarget; + } + } + + sqlite3_result_int(pCtx, bRet); + p->nRow += 1.0; + p->nRet += (double)bRet; +} + +struct IdxRemCtx { + int nSlot; + struct IdxRemSlot { + int eType; /* SQLITE_NULL, INTEGER, REAL, TEXT, BLOB */ + i64 iVal; /* SQLITE_INTEGER value */ + double rVal; /* SQLITE_FLOAT value */ + int nByte; /* Bytes of space allocated at z */ + int n; /* Size of buffer z */ + char *z; /* SQLITE_TEXT/BLOB value */ + } aSlot[1]; +}; + +/* +** Implementation of scalar function rem(). +*/ +static void idxRemFunc( + sqlite3_context *pCtx, + int argc, + sqlite3_value **argv +){ + struct IdxRemCtx *p = (struct IdxRemCtx*)sqlite3_user_data(pCtx); + struct IdxRemSlot *pSlot; + int iSlot; + assert( argc==2 ); + + iSlot = sqlite3_value_int(argv[0]); + assert( iSlot<=p->nSlot ); + pSlot = &p->aSlot[iSlot]; + + switch( pSlot->eType ){ + case SQLITE_NULL: + /* no-op */ + break; + + case SQLITE_INTEGER: + sqlite3_result_int64(pCtx, pSlot->iVal); + break; + + case SQLITE_FLOAT: + sqlite3_result_double(pCtx, pSlot->rVal); + break; + + case SQLITE_BLOB: + sqlite3_result_blob(pCtx, pSlot->z, pSlot->n, SQLITE_TRANSIENT); + break; + + case SQLITE_TEXT: + sqlite3_result_text(pCtx, pSlot->z, pSlot->n, SQLITE_TRANSIENT); + break; + } + + pSlot->eType = sqlite3_value_type(argv[1]); + switch( pSlot->eType ){ + case SQLITE_NULL: + /* no-op */ + break; + + case SQLITE_INTEGER: + pSlot->iVal = sqlite3_value_int64(argv[1]); + break; + + case SQLITE_FLOAT: + pSlot->rVal = sqlite3_value_double(argv[1]); + break; + + case SQLITE_BLOB: + case SQLITE_TEXT: { + int nByte = sqlite3_value_bytes(argv[1]); + const void *pData = 0; + if( nByte>pSlot->nByte ){ + char *zNew = (char*)sqlite3_realloc(pSlot->z, nByte*2); + if( zNew==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + pSlot->nByte = nByte*2; + pSlot->z = zNew; + } + pSlot->n = nByte; + if( pSlot->eType==SQLITE_BLOB ){ + pData = sqlite3_value_blob(argv[1]); + if( pData ) memcpy(pSlot->z, pData, nByte); + }else{ + pData = sqlite3_value_text(argv[1]); + memcpy(pSlot->z, pData, nByte); + } + break; + } + } +} + +static int idxLargestIndex(sqlite3 *db, int *pnMax, char **pzErr){ + int rc = SQLITE_OK; + const char *zMax = + "SELECT max(i.seqno) FROM " + " sqlite_schema AS s, " + " pragma_index_list(s.name) AS l, " + " pragma_index_info(l.name) AS i " + "WHERE s.type = 'table'"; + sqlite3_stmt *pMax = 0; + + *pnMax = 0; + rc = idxPrepareStmt(db, &pMax, pzErr, zMax); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pMax) ){ + *pnMax = sqlite3_column_int(pMax, 0) + 1; + } + idxFinalize(&rc, pMax); + + return rc; +} + +static int idxPopulateOneStat1( + sqlite3expert *p, + sqlite3_stmt *pIndexXInfo, + sqlite3_stmt *pWriteStat, + const char *zTab, + const char *zIdx, + char **pzErr +){ + char *zCols = 0; + char *zOrder = 0; + char *zQuery = 0; + int nCol = 0; + int i; + sqlite3_stmt *pQuery = 0; + int *aStat = 0; + int rc = SQLITE_OK; + + assert( p->iSample>0 ); + + /* Formulate the query text */ + sqlite3_bind_text(pIndexXInfo, 1, zIdx, -1, SQLITE_STATIC); + while( SQLITE_OK==rc && SQLITE_ROW==sqlite3_step(pIndexXInfo) ){ + const char *zComma = zCols==0 ? "" : ", "; + const char *zName = (const char*)sqlite3_column_text(pIndexXInfo, 0); + const char *zColl = (const char*)sqlite3_column_text(pIndexXInfo, 1); + zCols = idxAppendText(&rc, zCols, + "%sx.%Q IS rem(%d, x.%Q) COLLATE %s", zComma, zName, nCol, zName, zColl + ); + zOrder = idxAppendText(&rc, zOrder, "%s%d", zComma, ++nCol); + } + sqlite3_reset(pIndexXInfo); + if( rc==SQLITE_OK ){ + if( p->iSample==100 ){ + zQuery = sqlite3_mprintf( + "SELECT %s FROM %Q x ORDER BY %s", zCols, zTab, zOrder + ); + }else{ + zQuery = sqlite3_mprintf( + "SELECT %s FROM temp."UNIQUE_TABLE_NAME" x ORDER BY %s", zCols, zOrder + ); + } + } + sqlite3_free(zCols); + sqlite3_free(zOrder); + + /* Formulate the query text */ + if( rc==SQLITE_OK ){ + sqlite3 *dbrem = (p->iSample==100 ? p->db : p->dbv); + rc = idxPrepareStmt(dbrem, &pQuery, pzErr, zQuery); + } + sqlite3_free(zQuery); + + if( rc==SQLITE_OK ){ + aStat = (int*)idxMalloc(&rc, sizeof(int)*(nCol+1)); + } + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pQuery) ){ + IdxHashEntry *pEntry; + char *zStat = 0; + for(i=0; i<=nCol; i++) aStat[i] = 1; + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pQuery) ){ + aStat[0]++; + for(i=0; i<nCol; i++){ + if( sqlite3_column_int(pQuery, i)==0 ) break; + } + for(/*no-op*/; i<nCol; i++){ + aStat[i+1]++; + } + } + + if( rc==SQLITE_OK ){ + int s0 = aStat[0]; + zStat = sqlite3_mprintf("%d", s0); + if( zStat==0 ) rc = SQLITE_NOMEM; + for(i=1; rc==SQLITE_OK && i<=nCol; i++){ + zStat = idxAppendText(&rc, zStat, " %d", (s0+aStat[i]/2) / aStat[i]); + } + } + + if( rc==SQLITE_OK ){ + sqlite3_bind_text(pWriteStat, 1, zTab, -1, SQLITE_STATIC); + sqlite3_bind_text(pWriteStat, 2, zIdx, -1, SQLITE_STATIC); + sqlite3_bind_text(pWriteStat, 3, zStat, -1, SQLITE_STATIC); + sqlite3_step(pWriteStat); + rc = sqlite3_reset(pWriteStat); + } + + pEntry = idxHashFind(&p->hIdx, zIdx, STRLEN(zIdx)); + if( pEntry ){ + assert( pEntry->zVal2==0 ); + pEntry->zVal2 = zStat; + }else{ + sqlite3_free(zStat); + } + } + sqlite3_free(aStat); + idxFinalize(&rc, pQuery); + + return rc; +} + +static int idxBuildSampleTable(sqlite3expert *p, const char *zTab){ + int rc; + char *zSql; + + rc = sqlite3_exec(p->dbv,"DROP TABLE IF EXISTS temp."UNIQUE_TABLE_NAME,0,0,0); + if( rc!=SQLITE_OK ) return rc; + + zSql = sqlite3_mprintf( + "CREATE TABLE temp." UNIQUE_TABLE_NAME " AS SELECT * FROM %Q", zTab + ); + if( zSql==0 ) return SQLITE_NOMEM; + rc = sqlite3_exec(p->dbv, zSql, 0, 0, 0); + sqlite3_free(zSql); + + return rc; +} + +/* +** This function is called as part of sqlite3_expert_analyze(). Candidate +** indexes have already been created in database sqlite3expert.dbm, this +** function populates sqlite_stat1 table in the same database. +** +** The stat1 data is generated by querying the +*/ +static int idxPopulateStat1(sqlite3expert *p, char **pzErr){ + int rc = SQLITE_OK; + int nMax =0; + struct IdxRemCtx *pCtx = 0; + struct IdxSampleCtx samplectx; + int i; + i64 iPrev = -100000; + sqlite3_stmt *pAllIndex = 0; + sqlite3_stmt *pIndexXInfo = 0; + sqlite3_stmt *pWrite = 0; + + const char *zAllIndex = + "SELECT s.rowid, s.name, l.name FROM " + " sqlite_schema AS s, " + " pragma_index_list(s.name) AS l " + "WHERE s.type = 'table'"; + const char *zIndexXInfo = + "SELECT name, coll FROM pragma_index_xinfo(?) WHERE key"; + const char *zWrite = "INSERT INTO sqlite_stat1 VALUES(?, ?, ?)"; + + /* If iSample==0, no sqlite_stat1 data is required. */ + if( p->iSample==0 ) return SQLITE_OK; + + rc = idxLargestIndex(p->dbm, &nMax, pzErr); + if( nMax<=0 || rc!=SQLITE_OK ) return rc; + + rc = sqlite3_exec(p->dbm, "ANALYZE; PRAGMA writable_schema=1", 0, 0, 0); + + if( rc==SQLITE_OK ){ + int nByte = sizeof(struct IdxRemCtx) + (sizeof(struct IdxRemSlot) * nMax); + pCtx = (struct IdxRemCtx*)idxMalloc(&rc, nByte); + } + + if( rc==SQLITE_OK ){ + sqlite3 *dbrem = (p->iSample==100 ? p->db : p->dbv); + rc = sqlite3_create_function( + dbrem, "rem", 2, SQLITE_UTF8, (void*)pCtx, idxRemFunc, 0, 0 + ); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function( + p->db, "sample", 0, SQLITE_UTF8, (void*)&samplectx, idxSampleFunc, 0, 0 + ); + } + + if( rc==SQLITE_OK ){ + pCtx->nSlot = nMax+1; + rc = idxPrepareStmt(p->dbm, &pAllIndex, pzErr, zAllIndex); + } + if( rc==SQLITE_OK ){ + rc = idxPrepareStmt(p->dbm, &pIndexXInfo, pzErr, zIndexXInfo); + } + if( rc==SQLITE_OK ){ + rc = idxPrepareStmt(p->dbm, &pWrite, pzErr, zWrite); + } + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pAllIndex) ){ + i64 iRowid = sqlite3_column_int64(pAllIndex, 0); + const char *zTab = (const char*)sqlite3_column_text(pAllIndex, 1); + const char *zIdx = (const char*)sqlite3_column_text(pAllIndex, 2); + if( zTab==0 || zIdx==0 ) continue; + if( p->iSample<100 && iPrev!=iRowid ){ + samplectx.target = (double)p->iSample / 100.0; + samplectx.iTarget = p->iSample; + samplectx.nRow = 0.0; + samplectx.nRet = 0.0; + rc = idxBuildSampleTable(p, zTab); + if( rc!=SQLITE_OK ) break; + } + rc = idxPopulateOneStat1(p, pIndexXInfo, pWrite, zTab, zIdx, pzErr); + iPrev = iRowid; + } + if( rc==SQLITE_OK && p->iSample<100 ){ + rc = sqlite3_exec(p->dbv, + "DROP TABLE IF EXISTS temp." UNIQUE_TABLE_NAME, 0,0,0 + ); + } + + idxFinalize(&rc, pAllIndex); + idxFinalize(&rc, pIndexXInfo); + idxFinalize(&rc, pWrite); + + if( pCtx ){ + for(i=0; i<pCtx->nSlot; i++){ + sqlite3_free(pCtx->aSlot[i].z); + } + sqlite3_free(pCtx); + } + + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(p->dbm, "ANALYZE sqlite_schema", 0, 0, 0); + } + + sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp."UNIQUE_TABLE_NAME,0,0,0); + return rc; +} + +/* +** Allocate a new sqlite3expert object. +*/ +sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){ + int rc = SQLITE_OK; + sqlite3expert *pNew; + + pNew = (sqlite3expert*)idxMalloc(&rc, sizeof(sqlite3expert)); + + /* Open two in-memory databases to work with. The "vtab database" (dbv) + ** will contain a virtual table corresponding to each real table in + ** the user database schema, and a copy of each view. It is used to + ** collect information regarding the WHERE, ORDER BY and other clauses + ** of the user's query. + */ + if( rc==SQLITE_OK ){ + pNew->db = db; + pNew->iSample = 100; + rc = sqlite3_open(":memory:", &pNew->dbv); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_open(":memory:", &pNew->dbm); + if( rc==SQLITE_OK ){ + sqlite3_db_config(pNew->dbm, SQLITE_DBCONFIG_TRIGGER_EQP, 1, (int*)0); + } + } + + + /* Copy the entire schema of database [db] into [dbm]. */ + if( rc==SQLITE_OK ){ + sqlite3_stmt *pSql = 0; + rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg, + "SELECT sql FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%%'" + " AND sql NOT LIKE 'CREATE VIRTUAL %%'" + ); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + const char *zSql = (const char*)sqlite3_column_text(pSql, 0); + if( zSql ) rc = sqlite3_exec(pNew->dbm, zSql, 0, 0, pzErrmsg); + } + idxFinalize(&rc, pSql); + } + + /* Create the vtab schema */ + if( rc==SQLITE_OK ){ + rc = idxCreateVtabSchema(pNew, pzErrmsg); + } + + /* Register the auth callback with dbv */ + if( rc==SQLITE_OK ){ + sqlite3_set_authorizer(pNew->dbv, idxAuthCallback, (void*)pNew); + } + + /* If an error has occurred, free the new object and reutrn NULL. Otherwise, + ** return the new sqlite3expert handle. */ + if( rc!=SQLITE_OK ){ + sqlite3_expert_destroy(pNew); + pNew = 0; + } + return pNew; +} + +/* +** Configure an sqlite3expert object. +*/ +int sqlite3_expert_config(sqlite3expert *p, int op, ...){ + int rc = SQLITE_OK; + va_list ap; + va_start(ap, op); + switch( op ){ + case EXPERT_CONFIG_SAMPLE: { + int iVal = va_arg(ap, int); + if( iVal<0 ) iVal = 0; + if( iVal>100 ) iVal = 100; + p->iSample = iVal; + break; + } + default: + rc = SQLITE_NOTFOUND; + break; + } + + va_end(ap); + return rc; +} + +/* +** Add an SQL statement to the analysis. +*/ +int sqlite3_expert_sql( + sqlite3expert *p, /* From sqlite3_expert_new() */ + const char *zSql, /* SQL statement to add */ + char **pzErr /* OUT: Error message (if any) */ +){ + IdxScan *pScanOrig = p->pScan; + IdxStatement *pStmtOrig = p->pStatement; + int rc = SQLITE_OK; + const char *zStmt = zSql; + + if( p->bRun ) return SQLITE_MISUSE; + + while( rc==SQLITE_OK && zStmt && zStmt[0] ){ + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(p->dbv, zStmt, -1, &pStmt, &zStmt); + if( rc==SQLITE_OK ){ + if( pStmt ){ + IdxStatement *pNew; + const char *z = sqlite3_sql(pStmt); + int n = STRLEN(z); + pNew = (IdxStatement*)idxMalloc(&rc, sizeof(IdxStatement) + n+1); + if( rc==SQLITE_OK ){ + pNew->zSql = (char*)&pNew[1]; + memcpy(pNew->zSql, z, n+1); + pNew->pNext = p->pStatement; + if( p->pStatement ) pNew->iId = p->pStatement->iId+1; + p->pStatement = pNew; + } + sqlite3_finalize(pStmt); + } + }else{ + idxDatabaseError(p->dbv, pzErr); + } + } + + if( rc!=SQLITE_OK ){ + idxScanFree(p->pScan, pScanOrig); + idxStatementFree(p->pStatement, pStmtOrig); + p->pScan = pScanOrig; + p->pStatement = pStmtOrig; + } + + return rc; +} + +int sqlite3_expert_analyze(sqlite3expert *p, char **pzErr){ + int rc; + IdxHashEntry *pEntry; + + /* Do trigger processing to collect any extra IdxScan structures */ + rc = idxProcessTriggers(p, pzErr); + + /* Create candidate indexes within the in-memory database file */ + if( rc==SQLITE_OK ){ + rc = idxCreateCandidates(p); + }else if ( rc==SQLITE_BUSY_TIMEOUT ){ + if( pzErr ) + *pzErr = sqlite3_mprintf("Cannot find a unique index name to propose."); + return rc; + } + + /* Generate the stat1 data */ + if( rc==SQLITE_OK ){ + rc = idxPopulateStat1(p, pzErr); + } + + /* Formulate the EXPERT_REPORT_CANDIDATES text */ + for(pEntry=p->hIdx.pFirst; pEntry; pEntry=pEntry->pNext){ + p->zCandidates = idxAppendText(&rc, p->zCandidates, + "%s;%s%s\n", pEntry->zVal, + pEntry->zVal2 ? " -- stat1: " : "", pEntry->zVal2 + ); + } + + /* Figure out which of the candidate indexes are preferred by the query + ** planner and report the results to the user. */ + if( rc==SQLITE_OK ){ + rc = idxFindIndexes(p, pzErr); + } + + if( rc==SQLITE_OK ){ + p->bRun = 1; + } + return rc; +} + +/* +** Return the total number of statements that have been added to this +** sqlite3expert using sqlite3_expert_sql(). +*/ +int sqlite3_expert_count(sqlite3expert *p){ + int nRet = 0; + if( p->pStatement ) nRet = p->pStatement->iId+1; + return nRet; +} + +/* +** Return a component of the report. +*/ +const char *sqlite3_expert_report(sqlite3expert *p, int iStmt, int eReport){ + const char *zRet = 0; + IdxStatement *pStmt; + + if( p->bRun==0 ) return 0; + for(pStmt=p->pStatement; pStmt && pStmt->iId!=iStmt; pStmt=pStmt->pNext); + switch( eReport ){ + case EXPERT_REPORT_SQL: + if( pStmt ) zRet = pStmt->zSql; + break; + case EXPERT_REPORT_INDEXES: + if( pStmt ) zRet = pStmt->zIdx; + break; + case EXPERT_REPORT_PLAN: + if( pStmt ) zRet = pStmt->zEQP; + break; + case EXPERT_REPORT_CANDIDATES: + zRet = p->zCandidates; + break; + } + return zRet; +} + +/* +** Free an sqlite3expert object. +*/ +void sqlite3_expert_destroy(sqlite3expert *p){ + if( p ){ + sqlite3_close(p->dbm); + sqlite3_close(p->dbv); + idxScanFree(p->pScan, 0); + idxStatementFree(p->pStatement, 0); + idxTableFree(p->pTable); + idxWriteFree(p->pWrite); + idxHashClear(&p->hIdx); + sqlite3_free(p->zCandidates); + sqlite3_free(p); + } +} + +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + +/************************* End ../ext/expert/sqlite3expert.c ********************/ + +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) +#define SQLITE_SHELL_HAVE_RECOVER 1 +#else +#define SQLITE_SHELL_HAVE_RECOVER 0 +#endif +#if SQLITE_SHELL_HAVE_RECOVER +/************************* Begin ../ext/recover/sqlite3recover.h ******************/ +/* +** 2022-08-27 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the public interface to the "recover" extension - +** an SQLite extension designed to recover data from corrupted database +** files. +*/ + +/* +** OVERVIEW: +** +** To use the API to recover data from a corrupted database, an +** application: +** +** 1) Creates an sqlite3_recover handle by calling either +** sqlite3_recover_init() or sqlite3_recover_init_sql(). +** +** 2) Configures the new handle using one or more calls to +** sqlite3_recover_config(). +** +** 3) Executes the recovery by repeatedly calling sqlite3_recover_step() on +** the handle until it returns something other than SQLITE_OK. If it +** returns SQLITE_DONE, then the recovery operation completed without +** error. If it returns some other non-SQLITE_OK value, then an error +** has occurred. +** +** 4) Retrieves any error code and English language error message using the +** sqlite3_recover_errcode() and sqlite3_recover_errmsg() APIs, +** respectively. +** +** 5) Destroys the sqlite3_recover handle and frees all resources +** using sqlite3_recover_finish(). +** +** The application may abandon the recovery operation at any point +** before it is finished by passing the sqlite3_recover handle to +** sqlite3_recover_finish(). This is not an error, but the final state +** of the output database, or the results of running the partial script +** delivered to the SQL callback, are undefined. +*/ + +#ifndef _SQLITE_RECOVER_H +#define _SQLITE_RECOVER_H + +/* #include "sqlite3.h" */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** An instance of the sqlite3_recover object represents a recovery +** operation in progress. +** +** Constructors: +** +** sqlite3_recover_init() +** sqlite3_recover_init_sql() +** +** Destructor: +** +** sqlite3_recover_finish() +** +** Methods: +** +** sqlite3_recover_config() +** sqlite3_recover_errcode() +** sqlite3_recover_errmsg() +** sqlite3_recover_run() +** sqlite3_recover_step() +*/ +typedef struct sqlite3_recover sqlite3_recover; + +/* +** These two APIs attempt to create and return a new sqlite3_recover object. +** In both cases the first two arguments identify the (possibly +** corrupt) database to recover data from. The first argument is an open +** database handle and the second the name of a database attached to that +** handle (i.e. "main", "temp" or the name of an attached database). +** +** If sqlite3_recover_init() is used to create the new sqlite3_recover +** handle, then data is recovered into a new database, identified by +** string parameter zUri. zUri may be an absolute or relative file path, +** or may be an SQLite URI. If the identified database file already exists, +** it is overwritten. +** +** If sqlite3_recover_init_sql() is invoked, then any recovered data will +** be returned to the user as a series of SQL statements. Executing these +** SQL statements results in the same database as would have been created +** had sqlite3_recover_init() been used. For each SQL statement in the +** output, the callback function passed as the third argument (xSql) is +** invoked once. The first parameter is a passed a copy of the fourth argument +** to this function (pCtx) as its first parameter, and a pointer to a +** nul-terminated buffer containing the SQL statement formated as UTF-8 as +** the second. If the xSql callback returns any value other than SQLITE_OK, +** then processing is immediately abandoned and the value returned used as +** the recover handle error code (see below). +** +** If an out-of-memory error occurs, NULL may be returned instead of +** a valid handle. In all other cases, it is the responsibility of the +** application to avoid resource leaks by ensuring that +** sqlite3_recover_finish() is called on all allocated handles. +*/ +sqlite3_recover *sqlite3_recover_init( + sqlite3* db, + const char *zDb, + const char *zUri +); +sqlite3_recover *sqlite3_recover_init_sql( + sqlite3* db, + const char *zDb, + int (*xSql)(void*, const char*), + void *pCtx +); + +/* +** Configure an sqlite3_recover object that has just been created using +** sqlite3_recover_init() or sqlite3_recover_init_sql(). This function +** may only be called before the first call to sqlite3_recover_step() +** or sqlite3_recover_run() on the object. +** +** The second argument passed to this function must be one of the +** SQLITE_RECOVER_* symbols defined below. Valid values for the third argument +** depend on the specific SQLITE_RECOVER_* symbol in use. +** +** SQLITE_OK is returned if the configuration operation was successful, +** or an SQLite error code otherwise. +*/ +int sqlite3_recover_config(sqlite3_recover*, int op, void *pArg); + +/* +** SQLITE_RECOVER_LOST_AND_FOUND: +** The pArg argument points to a string buffer containing the name +** of a "lost-and-found" table in the output database, or NULL. If +** the argument is non-NULL and the database contains seemingly +** valid pages that cannot be associated with any table in the +** recovered part of the schema, data is extracted from these +** pages to add to the lost-and-found table. +** +** SQLITE_RECOVER_FREELIST_CORRUPT: +** The pArg value must actually be a pointer to a value of type +** int containing value 0 or 1 cast as a (void*). If this option is set +** (argument is 1) and a lost-and-found table has been configured using +** SQLITE_RECOVER_LOST_AND_FOUND, then is assumed that the freelist is +** corrupt and an attempt is made to recover records from pages that +** appear to be linked into the freelist. Otherwise, pages on the freelist +** are ignored. Setting this option can recover more data from the +** database, but often ends up "recovering" deleted records. The default +** value is 0 (clear). +** +** SQLITE_RECOVER_ROWIDS: +** The pArg value must actually be a pointer to a value of type +** int containing value 0 or 1 cast as a (void*). If this option is set +** (argument is 1), then an attempt is made to recover rowid values +** that are not also INTEGER PRIMARY KEY values. If this option is +** clear, then new rowids are assigned to all recovered rows. The +** default value is 1 (set). +** +** SQLITE_RECOVER_SLOWINDEXES: +** The pArg value must actually be a pointer to a value of type +** int containing value 0 or 1 cast as a (void*). If this option is clear +** (argument is 0), then when creating an output database, the recover +** module creates and populates non-UNIQUE indexes right at the end of the +** recovery operation - after all recoverable data has been inserted +** into the new database. This is faster overall, but means that the +** final call to sqlite3_recover_step() for a recovery operation may +** be need to create a large number of indexes, which may be very slow. +** +** Or, if this option is set (argument is 1), then non-UNIQUE indexes +** are created in the output database before it is populated with +** recovered data. This is slower overall, but avoids the slow call +** to sqlite3_recover_step() at the end of the recovery operation. +** +** The default option value is 0. +*/ +#define SQLITE_RECOVER_LOST_AND_FOUND 1 +#define SQLITE_RECOVER_FREELIST_CORRUPT 2 +#define SQLITE_RECOVER_ROWIDS 3 +#define SQLITE_RECOVER_SLOWINDEXES 4 + +/* +** Perform a unit of work towards the recovery operation. This function +** must normally be called multiple times to complete database recovery. +** +** If no error occurs but the recovery operation is not completed, this +** function returns SQLITE_OK. If recovery has been completed successfully +** then SQLITE_DONE is returned. If an error has occurred, then an SQLite +** error code (e.g. SQLITE_IOERR or SQLITE_NOMEM) is returned. It is not +** considered an error if some or all of the data cannot be recovered +** due to database corruption. +** +** Once sqlite3_recover_step() has returned a value other than SQLITE_OK, +** all further such calls on the same recover handle are no-ops that return +** the same non-SQLITE_OK value. +*/ +int sqlite3_recover_step(sqlite3_recover*); + +/* +** Run the recovery operation to completion. Return SQLITE_OK if successful, +** or an SQLite error code otherwise. Calling this function is the same +** as executing: +** +** while( SQLITE_OK==sqlite3_recover_step(p) ); +** return sqlite3_recover_errcode(p); +*/ +int sqlite3_recover_run(sqlite3_recover*); + +/* +** If an error has been encountered during a prior call to +** sqlite3_recover_step(), then this function attempts to return a +** pointer to a buffer containing an English language explanation of +** the error. If no error message is available, or if an out-of memory +** error occurs while attempting to allocate a buffer in which to format +** the error message, NULL is returned. +** +** The returned buffer remains valid until the sqlite3_recover handle is +** destroyed using sqlite3_recover_finish(). +*/ +const char *sqlite3_recover_errmsg(sqlite3_recover*); + +/* +** If this function is called on an sqlite3_recover handle after +** an error occurs, an SQLite error code is returned. Otherwise, SQLITE_OK. +*/ +int sqlite3_recover_errcode(sqlite3_recover*); + +/* +** Clean up a recovery object created by a call to sqlite3_recover_init(). +** The results of using a recovery object with any API after it has been +** passed to this function are undefined. +** +** This function returns the same value as sqlite3_recover_errcode(). +*/ +int sqlite3_recover_finish(sqlite3_recover*); + + +#ifdef __cplusplus +} /* end of the 'extern "C"' block */ +#endif + +#endif /* ifndef _SQLITE_RECOVER_H */ + +/************************* End ../ext/recover/sqlite3recover.h ********************/ +# ifndef SQLITE_HAVE_SQLITE3R +/************************* Begin ../ext/recover/dbdata.c ******************/ +/* +** 2019-04-17 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains an implementation of two eponymous virtual tables, +** "sqlite_dbdata" and "sqlite_dbptr". Both modules require that the +** "sqlite_dbpage" eponymous virtual table be available. +** +** SQLITE_DBDATA: +** sqlite_dbdata is used to extract data directly from a database b-tree +** page and its associated overflow pages, bypassing the b-tree layer. +** The table schema is equivalent to: +** +** CREATE TABLE sqlite_dbdata( +** pgno INTEGER, +** cell INTEGER, +** field INTEGER, +** value ANY, +** schema TEXT HIDDEN +** ); +** +** IMPORTANT: THE VIRTUAL TABLE SCHEMA ABOVE IS SUBJECT TO CHANGE. IN THE +** FUTURE NEW NON-HIDDEN COLUMNS MAY BE ADDED BETWEEN "value" AND +** "schema". +** +** Each page of the database is inspected. If it cannot be interpreted as +** a b-tree page, or if it is a b-tree page containing 0 entries, the +** sqlite_dbdata table contains no rows for that page. Otherwise, the +** table contains one row for each field in the record associated with +** each cell on the page. For intkey b-trees, the key value is stored in +** field -1. +** +** For example, for the database: +** +** CREATE TABLE t1(a, b); -- root page is page 2 +** INSERT INTO t1(rowid, a, b) VALUES(5, 'v', 'five'); +** INSERT INTO t1(rowid, a, b) VALUES(10, 'x', 'ten'); +** +** the sqlite_dbdata table contains, as well as from entries related to +** page 1, content equivalent to: +** +** INSERT INTO sqlite_dbdata(pgno, cell, field, value) VALUES +** (2, 0, -1, 5 ), +** (2, 0, 0, 'v' ), +** (2, 0, 1, 'five'), +** (2, 1, -1, 10 ), +** (2, 1, 0, 'x' ), +** (2, 1, 1, 'ten' ); +** +** If database corruption is encountered, this module does not report an +** error. Instead, it attempts to extract as much data as possible and +** ignores the corruption. +** +** SQLITE_DBPTR: +** The sqlite_dbptr table has the following schema: +** +** CREATE TABLE sqlite_dbptr( +** pgno INTEGER, +** child INTEGER, +** schema TEXT HIDDEN +** ); +** +** It contains one entry for each b-tree pointer between a parent and +** child page in the database. +*/ + +#if !defined(SQLITEINT_H) +/* #include "sqlite3.h" */ + +/* typedef unsigned char u8; */ +/* typedef unsigned int u32; */ + +#endif +#include <string.h> +#include <assert.h> + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +#define DBDATA_PADDING_BYTES 100 + +typedef struct DbdataTable DbdataTable; +typedef struct DbdataCursor DbdataCursor; + +/* Cursor object */ +struct DbdataCursor { + sqlite3_vtab_cursor base; /* Base class. Must be first */ + sqlite3_stmt *pStmt; /* For fetching database pages */ + + int iPgno; /* Current page number */ + u8 *aPage; /* Buffer containing page */ + int nPage; /* Size of aPage[] in bytes */ + int nCell; /* Number of cells on aPage[] */ + int iCell; /* Current cell number */ + int bOnePage; /* True to stop after one page */ + int szDb; + sqlite3_int64 iRowid; + + /* Only for the sqlite_dbdata table */ + u8 *pRec; /* Buffer containing current record */ + sqlite3_int64 nRec; /* Size of pRec[] in bytes */ + sqlite3_int64 nHdr; /* Size of header in bytes */ + int iField; /* Current field number */ + u8 *pHdrPtr; + u8 *pPtr; + u32 enc; /* Text encoding */ + + sqlite3_int64 iIntkey; /* Integer key value */ +}; + +/* Table object */ +struct DbdataTable { + sqlite3_vtab base; /* Base class. Must be first */ + sqlite3 *db; /* The database connection */ + sqlite3_stmt *pStmt; /* For fetching database pages */ + int bPtr; /* True for sqlite3_dbptr table */ +}; + +/* Column and schema definitions for sqlite_dbdata */ +#define DBDATA_COLUMN_PGNO 0 +#define DBDATA_COLUMN_CELL 1 +#define DBDATA_COLUMN_FIELD 2 +#define DBDATA_COLUMN_VALUE 3 +#define DBDATA_COLUMN_SCHEMA 4 +#define DBDATA_SCHEMA \ + "CREATE TABLE x(" \ + " pgno INTEGER," \ + " cell INTEGER," \ + " field INTEGER," \ + " value ANY," \ + " schema TEXT HIDDEN" \ + ")" + +/* Column and schema definitions for sqlite_dbptr */ +#define DBPTR_COLUMN_PGNO 0 +#define DBPTR_COLUMN_CHILD 1 +#define DBPTR_COLUMN_SCHEMA 2 +#define DBPTR_SCHEMA \ + "CREATE TABLE x(" \ + " pgno INTEGER," \ + " child INTEGER," \ + " schema TEXT HIDDEN" \ + ")" + +/* +** Connect to an sqlite_dbdata (pAux==0) or sqlite_dbptr (pAux!=0) virtual +** table. +*/ +static int dbdataConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + DbdataTable *pTab = 0; + int rc = sqlite3_declare_vtab(db, pAux ? DBPTR_SCHEMA : DBDATA_SCHEMA); + + (void)argc; + (void)argv; + (void)pzErr; + sqlite3_vtab_config(db, SQLITE_VTAB_USES_ALL_SCHEMAS); + if( rc==SQLITE_OK ){ + pTab = (DbdataTable*)sqlite3_malloc64(sizeof(DbdataTable)); + if( pTab==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pTab, 0, sizeof(DbdataTable)); + pTab->db = db; + pTab->bPtr = (pAux!=0); + } + } + + *ppVtab = (sqlite3_vtab*)pTab; + return rc; +} + +/* +** Disconnect from or destroy a sqlite_dbdata or sqlite_dbptr virtual table. +*/ +static int dbdataDisconnect(sqlite3_vtab *pVtab){ + DbdataTable *pTab = (DbdataTable*)pVtab; + if( pTab ){ + sqlite3_finalize(pTab->pStmt); + sqlite3_free(pVtab); + } + return SQLITE_OK; +} + +/* +** This function interprets two types of constraints: +** +** schema=? +** pgno=? +** +** If neither are present, idxNum is set to 0. If schema=? is present, +** the 0x01 bit in idxNum is set. If pgno=? is present, the 0x02 bit +** in idxNum is set. +** +** If both parameters are present, schema is in position 0 and pgno in +** position 1. +*/ +static int dbdataBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdx){ + DbdataTable *pTab = (DbdataTable*)tab; + int i; + int iSchema = -1; + int iPgno = -1; + int colSchema = (pTab->bPtr ? DBPTR_COLUMN_SCHEMA : DBDATA_COLUMN_SCHEMA); + + for(i=0; i<pIdx->nConstraint; i++){ + struct sqlite3_index_constraint *p = &pIdx->aConstraint[i]; + if( p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + if( p->iColumn==colSchema ){ + if( p->usable==0 ) return SQLITE_CONSTRAINT; + iSchema = i; + } + if( p->iColumn==DBDATA_COLUMN_PGNO && p->usable ){ + iPgno = i; + } + } + } + + if( iSchema>=0 ){ + pIdx->aConstraintUsage[iSchema].argvIndex = 1; + pIdx->aConstraintUsage[iSchema].omit = 1; + } + if( iPgno>=0 ){ + pIdx->aConstraintUsage[iPgno].argvIndex = 1 + (iSchema>=0); + pIdx->aConstraintUsage[iPgno].omit = 1; + pIdx->estimatedCost = 100; + pIdx->estimatedRows = 50; + + if( pTab->bPtr==0 && pIdx->nOrderBy && pIdx->aOrderBy[0].desc==0 ){ + int iCol = pIdx->aOrderBy[0].iColumn; + if( pIdx->nOrderBy==1 ){ + pIdx->orderByConsumed = (iCol==0 || iCol==1); + }else if( pIdx->nOrderBy==2 && pIdx->aOrderBy[1].desc==0 && iCol==0 ){ + pIdx->orderByConsumed = (pIdx->aOrderBy[1].iColumn==1); + } + } + + }else{ + pIdx->estimatedCost = 100000000; + pIdx->estimatedRows = 1000000000; + } + pIdx->idxNum = (iSchema>=0 ? 0x01 : 0x00) | (iPgno>=0 ? 0x02 : 0x00); + return SQLITE_OK; +} + +/* +** Open a new sqlite_dbdata or sqlite_dbptr cursor. +*/ +static int dbdataOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + DbdataCursor *pCsr; + + pCsr = (DbdataCursor*)sqlite3_malloc64(sizeof(DbdataCursor)); + if( pCsr==0 ){ + return SQLITE_NOMEM; + }else{ + memset(pCsr, 0, sizeof(DbdataCursor)); + pCsr->base.pVtab = pVTab; + } + + *ppCursor = (sqlite3_vtab_cursor *)pCsr; + return SQLITE_OK; +} + +/* +** Restore a cursor object to the state it was in when first allocated +** by dbdataOpen(). +*/ +static void dbdataResetCursor(DbdataCursor *pCsr){ + DbdataTable *pTab = (DbdataTable*)(pCsr->base.pVtab); + if( pTab->pStmt==0 ){ + pTab->pStmt = pCsr->pStmt; + }else{ + sqlite3_finalize(pCsr->pStmt); + } + pCsr->pStmt = 0; + pCsr->iPgno = 1; + pCsr->iCell = 0; + pCsr->iField = 0; + pCsr->bOnePage = 0; + sqlite3_free(pCsr->aPage); + sqlite3_free(pCsr->pRec); + pCsr->pRec = 0; + pCsr->aPage = 0; +} + +/* +** Close an sqlite_dbdata or sqlite_dbptr cursor. +*/ +static int dbdataClose(sqlite3_vtab_cursor *pCursor){ + DbdataCursor *pCsr = (DbdataCursor*)pCursor; + dbdataResetCursor(pCsr); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** Utility methods to decode 16 and 32-bit big-endian unsigned integers. +*/ +static u32 get_uint16(unsigned char *a){ + return (a[0]<<8)|a[1]; +} +static u32 get_uint32(unsigned char *a){ + return ((u32)a[0]<<24) + | ((u32)a[1]<<16) + | ((u32)a[2]<<8) + | ((u32)a[3]); +} + +/* +** Load page pgno from the database via the sqlite_dbpage virtual table. +** If successful, set (*ppPage) to point to a buffer containing the page +** data, (*pnPage) to the size of that buffer in bytes and return +** SQLITE_OK. In this case it is the responsibility of the caller to +** eventually free the buffer using sqlite3_free(). +** +** Or, if an error occurs, set both (*ppPage) and (*pnPage) to 0 and +** return an SQLite error code. +*/ +static int dbdataLoadPage( + DbdataCursor *pCsr, /* Cursor object */ + u32 pgno, /* Page number of page to load */ + u8 **ppPage, /* OUT: pointer to page buffer */ + int *pnPage /* OUT: Size of (*ppPage) in bytes */ +){ + int rc2; + int rc = SQLITE_OK; + sqlite3_stmt *pStmt = pCsr->pStmt; + + *ppPage = 0; + *pnPage = 0; + if( pgno>0 ){ + sqlite3_bind_int64(pStmt, 2, pgno); + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + int nCopy = sqlite3_column_bytes(pStmt, 0); + if( nCopy>0 ){ + u8 *pPage; + pPage = (u8*)sqlite3_malloc64(nCopy + DBDATA_PADDING_BYTES); + if( pPage==0 ){ + rc = SQLITE_NOMEM; + }else{ + const u8 *pCopy = sqlite3_column_blob(pStmt, 0); + memcpy(pPage, pCopy, nCopy); + memset(&pPage[nCopy], 0, DBDATA_PADDING_BYTES); + } + *ppPage = pPage; + *pnPage = nCopy; + } + } + rc2 = sqlite3_reset(pStmt); + if( rc==SQLITE_OK ) rc = rc2; + } + + return rc; +} + +/* +** Read a varint. Put the value in *pVal and return the number of bytes. +*/ +static int dbdataGetVarint(const u8 *z, sqlite3_int64 *pVal){ + sqlite3_uint64 u = 0; + int i; + for(i=0; i<8; i++){ + u = (u<<7) + (z[i]&0x7f); + if( (z[i]&0x80)==0 ){ *pVal = (sqlite3_int64)u; return i+1; } + } + u = (u<<8) + (z[i]&0xff); + *pVal = (sqlite3_int64)u; + return 9; +} + +/* +** Like dbdataGetVarint(), but set the output to 0 if it is less than 0 +** or greater than 0xFFFFFFFF. This can be used for all varints in an +** SQLite database except for key values in intkey tables. +*/ +static int dbdataGetVarintU32(const u8 *z, sqlite3_int64 *pVal){ + sqlite3_int64 val; + int nRet = dbdataGetVarint(z, &val); + if( val<0 || val>0xFFFFFFFF ) val = 0; + *pVal = val; + return nRet; +} + +/* +** Return the number of bytes of space used by an SQLite value of type +** eType. +*/ +static int dbdataValueBytes(int eType){ + switch( eType ){ + case 0: case 8: case 9: + case 10: case 11: + return 0; + case 1: + return 1; + case 2: + return 2; + case 3: + return 3; + case 4: + return 4; + case 5: + return 6; + case 6: + case 7: + return 8; + default: + if( eType>0 ){ + return ((eType-12) / 2); + } + return 0; + } +} + +/* +** Load a value of type eType from buffer pData and use it to set the +** result of context object pCtx. +*/ +static void dbdataValue( + sqlite3_context *pCtx, + u32 enc, + int eType, + u8 *pData, + sqlite3_int64 nData +){ + if( eType>=0 && dbdataValueBytes(eType)<=nData ){ + switch( eType ){ + case 0: + case 10: + case 11: + sqlite3_result_null(pCtx); + break; + + case 8: + sqlite3_result_int(pCtx, 0); + break; + case 9: + sqlite3_result_int(pCtx, 1); + break; + + case 1: case 2: case 3: case 4: case 5: case 6: case 7: { + sqlite3_uint64 v = (signed char)pData[0]; + pData++; + switch( eType ){ + case 7: + case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; + case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; + case 4: v = (v<<8) + pData[0]; pData++; + case 3: v = (v<<8) + pData[0]; pData++; + case 2: v = (v<<8) + pData[0]; pData++; + } + + if( eType==7 ){ + double r; + memcpy(&r, &v, sizeof(r)); + sqlite3_result_double(pCtx, r); + }else{ + sqlite3_result_int64(pCtx, (sqlite3_int64)v); + } + break; + } + + default: { + int n = ((eType-12) / 2); + if( eType % 2 ){ + switch( enc ){ +#ifndef SQLITE_OMIT_UTF16 + case SQLITE_UTF16BE: + sqlite3_result_text16be(pCtx, (void*)pData, n, SQLITE_TRANSIENT); + break; + case SQLITE_UTF16LE: + sqlite3_result_text16le(pCtx, (void*)pData, n, SQLITE_TRANSIENT); + break; +#endif + default: + sqlite3_result_text(pCtx, (char*)pData, n, SQLITE_TRANSIENT); + break; + } + }else{ + sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT); + } + } + } + } +} + +/* +** Move an sqlite_dbdata or sqlite_dbptr cursor to the next entry. +*/ +static int dbdataNext(sqlite3_vtab_cursor *pCursor){ + DbdataCursor *pCsr = (DbdataCursor*)pCursor; + DbdataTable *pTab = (DbdataTable*)pCursor->pVtab; + + pCsr->iRowid++; + while( 1 ){ + int rc; + int iOff = (pCsr->iPgno==1 ? 100 : 0); + int bNextPage = 0; + + if( pCsr->aPage==0 ){ + while( 1 ){ + if( pCsr->bOnePage==0 && pCsr->iPgno>pCsr->szDb ) return SQLITE_OK; + rc = dbdataLoadPage(pCsr, pCsr->iPgno, &pCsr->aPage, &pCsr->nPage); + if( rc!=SQLITE_OK ) return rc; + if( pCsr->aPage && pCsr->nPage>=256 ) break; + sqlite3_free(pCsr->aPage); + pCsr->aPage = 0; + if( pCsr->bOnePage ) return SQLITE_OK; + pCsr->iPgno++; + } + + assert( iOff+3+2<=pCsr->nPage ); + pCsr->iCell = pTab->bPtr ? -2 : 0; + pCsr->nCell = get_uint16(&pCsr->aPage[iOff+3]); + } + + if( pTab->bPtr ){ + if( pCsr->aPage[iOff]!=0x02 && pCsr->aPage[iOff]!=0x05 ){ + pCsr->iCell = pCsr->nCell; + } + pCsr->iCell++; + if( pCsr->iCell>=pCsr->nCell ){ + sqlite3_free(pCsr->aPage); + pCsr->aPage = 0; + if( pCsr->bOnePage ) return SQLITE_OK; + pCsr->iPgno++; + }else{ + return SQLITE_OK; + } + }else{ + /* If there is no record loaded, load it now. */ + if( pCsr->pRec==0 ){ + int bHasRowid = 0; + int nPointer = 0; + sqlite3_int64 nPayload = 0; + sqlite3_int64 nHdr = 0; + int iHdr; + int U, X; + int nLocal; + + switch( pCsr->aPage[iOff] ){ + case 0x02: + nPointer = 4; + break; + case 0x0a: + break; + case 0x0d: + bHasRowid = 1; + break; + default: + /* This is not a b-tree page with records on it. Continue. */ + pCsr->iCell = pCsr->nCell; + break; + } + + if( pCsr->iCell>=pCsr->nCell ){ + bNextPage = 1; + }else{ + + iOff += 8 + nPointer + pCsr->iCell*2; + if( iOff>pCsr->nPage ){ + bNextPage = 1; + }else{ + iOff = get_uint16(&pCsr->aPage[iOff]); + } + + /* For an interior node cell, skip past the child-page number */ + iOff += nPointer; + + /* Load the "byte of payload including overflow" field */ + if( bNextPage || iOff>pCsr->nPage ){ + bNextPage = 1; + }else{ + iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload); + } + + /* If this is a leaf intkey cell, load the rowid */ + if( bHasRowid && !bNextPage && iOff<pCsr->nPage ){ + iOff += dbdataGetVarint(&pCsr->aPage[iOff], &pCsr->iIntkey); + } + + /* Figure out how much data to read from the local page */ + U = pCsr->nPage; + if( bHasRowid ){ + X = U-35; + }else{ + X = ((U-12)*64/255)-23; + } + if( nPayload<=X ){ + nLocal = nPayload; + }else{ + int M, K; + M = ((U-12)*32/255)-23; + K = M+((nPayload-M)%(U-4)); + if( K<=X ){ + nLocal = K; + }else{ + nLocal = M; + } + } + + if( bNextPage || nLocal+iOff>pCsr->nPage ){ + bNextPage = 1; + }else{ + + /* Allocate space for payload. And a bit more to catch small buffer + ** overruns caused by attempting to read a varint or similar from + ** near the end of a corrupt record. */ + pCsr->pRec = (u8*)sqlite3_malloc64(nPayload+DBDATA_PADDING_BYTES); + if( pCsr->pRec==0 ) return SQLITE_NOMEM; + memset(pCsr->pRec, 0, nPayload+DBDATA_PADDING_BYTES); + pCsr->nRec = nPayload; + + /* Load the nLocal bytes of payload */ + memcpy(pCsr->pRec, &pCsr->aPage[iOff], nLocal); + iOff += nLocal; + + /* Load content from overflow pages */ + if( nPayload>nLocal ){ + sqlite3_int64 nRem = nPayload - nLocal; + u32 pgnoOvfl = get_uint32(&pCsr->aPage[iOff]); + while( nRem>0 ){ + u8 *aOvfl = 0; + int nOvfl = 0; + int nCopy; + rc = dbdataLoadPage(pCsr, pgnoOvfl, &aOvfl, &nOvfl); + assert( rc!=SQLITE_OK || aOvfl==0 || nOvfl==pCsr->nPage ); + if( rc!=SQLITE_OK ) return rc; + if( aOvfl==0 ) break; + + nCopy = U-4; + if( nCopy>nRem ) nCopy = nRem; + memcpy(&pCsr->pRec[nPayload-nRem], &aOvfl[4], nCopy); + nRem -= nCopy; + + pgnoOvfl = get_uint32(aOvfl); + sqlite3_free(aOvfl); + } + } + + iHdr = dbdataGetVarintU32(pCsr->pRec, &nHdr); + if( nHdr>nPayload ) nHdr = 0; + pCsr->nHdr = nHdr; + pCsr->pHdrPtr = &pCsr->pRec[iHdr]; + pCsr->pPtr = &pCsr->pRec[pCsr->nHdr]; + pCsr->iField = (bHasRowid ? -1 : 0); + } + } + }else{ + pCsr->iField++; + if( pCsr->iField>0 ){ + sqlite3_int64 iType; + if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){ + bNextPage = 1; + }else{ + int szField = 0; + pCsr->pHdrPtr += dbdataGetVarintU32(pCsr->pHdrPtr, &iType); + szField = dbdataValueBytes(iType); + if( (pCsr->nRec - (pCsr->pPtr - pCsr->pRec))<szField ){ + pCsr->pPtr = &pCsr->pRec[pCsr->nRec]; + }else{ + pCsr->pPtr += szField; + } + } + } + } + + if( bNextPage ){ + sqlite3_free(pCsr->aPage); + sqlite3_free(pCsr->pRec); + pCsr->aPage = 0; + pCsr->pRec = 0; + if( pCsr->bOnePage ) return SQLITE_OK; + pCsr->iPgno++; + }else{ + if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->pRec[pCsr->nHdr] ){ + return SQLITE_OK; + } + + /* Advance to the next cell. The next iteration of the loop will load + ** the record and so on. */ + sqlite3_free(pCsr->pRec); + pCsr->pRec = 0; + pCsr->iCell++; + } + } + } + + assert( !"can't get here" ); + return SQLITE_OK; +} + +/* +** Return true if the cursor is at EOF. +*/ +static int dbdataEof(sqlite3_vtab_cursor *pCursor){ + DbdataCursor *pCsr = (DbdataCursor*)pCursor; + return pCsr->aPage==0; +} + +/* +** Return true if nul-terminated string zSchema ends in "()". Or false +** otherwise. +*/ +static int dbdataIsFunction(const char *zSchema){ + size_t n = strlen(zSchema); + if( n>2 && zSchema[n-2]=='(' && zSchema[n-1]==')' ){ + return (int)n-2; + } + return 0; +} + +/* +** Determine the size in pages of database zSchema (where zSchema is +** "main", "temp" or the name of an attached database) and set +** pCsr->szDb accordingly. If successful, return SQLITE_OK. Otherwise, +** an SQLite error code. +*/ +static int dbdataDbsize(DbdataCursor *pCsr, const char *zSchema){ + DbdataTable *pTab = (DbdataTable*)pCsr->base.pVtab; + char *zSql = 0; + int rc, rc2; + int nFunc = 0; + sqlite3_stmt *pStmt = 0; + + if( (nFunc = dbdataIsFunction(zSchema))>0 ){ + zSql = sqlite3_mprintf("SELECT %.*s(0)", nFunc, zSchema); + }else{ + zSql = sqlite3_mprintf("PRAGMA %Q.page_count", zSchema); + } + if( zSql==0 ) return SQLITE_NOMEM; + + rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + pCsr->szDb = sqlite3_column_int(pStmt, 0); + } + rc2 = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ) rc = rc2; + return rc; +} + +/* +** Attempt to figure out the encoding of the database by retrieving page 1 +** and inspecting the header field. If successful, set the pCsr->enc variable +** and return SQLITE_OK. Otherwise, return an SQLite error code. +*/ +static int dbdataGetEncoding(DbdataCursor *pCsr){ + int rc = SQLITE_OK; + int nPg1 = 0; + u8 *aPg1 = 0; + rc = dbdataLoadPage(pCsr, 1, &aPg1, &nPg1); + if( rc==SQLITE_OK && nPg1>=(56+4) ){ + pCsr->enc = get_uint32(&aPg1[56]); + } + sqlite3_free(aPg1); + return rc; +} + + +/* +** xFilter method for sqlite_dbdata and sqlite_dbptr. +*/ +static int dbdataFilter( + sqlite3_vtab_cursor *pCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + DbdataCursor *pCsr = (DbdataCursor*)pCursor; + DbdataTable *pTab = (DbdataTable*)pCursor->pVtab; + int rc = SQLITE_OK; + const char *zSchema = "main"; + (void)idxStr; + (void)argc; + + dbdataResetCursor(pCsr); + assert( pCsr->iPgno==1 ); + if( idxNum & 0x01 ){ + zSchema = (const char*)sqlite3_value_text(argv[0]); + if( zSchema==0 ) zSchema = ""; + } + if( idxNum & 0x02 ){ + pCsr->iPgno = sqlite3_value_int(argv[(idxNum & 0x01)]); + pCsr->bOnePage = 1; + }else{ + rc = dbdataDbsize(pCsr, zSchema); + } + + if( rc==SQLITE_OK ){ + int nFunc = 0; + if( pTab->pStmt ){ + pCsr->pStmt = pTab->pStmt; + pTab->pStmt = 0; + }else if( (nFunc = dbdataIsFunction(zSchema))>0 ){ + char *zSql = sqlite3_mprintf("SELECT %.*s(?2)", nFunc, zSchema); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0); + sqlite3_free(zSql); + } + }else{ + rc = sqlite3_prepare_v2(pTab->db, + "SELECT data FROM sqlite_dbpage(?) WHERE pgno=?", -1, + &pCsr->pStmt, 0 + ); + } + } + if( rc==SQLITE_OK ){ + rc = sqlite3_bind_text(pCsr->pStmt, 1, zSchema, -1, SQLITE_TRANSIENT); + } + + /* Try to determine the encoding of the db by inspecting the header + ** field on page 1. */ + if( rc==SQLITE_OK ){ + rc = dbdataGetEncoding(pCsr); + } + + if( rc!=SQLITE_OK ){ + pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); + } + + if( rc==SQLITE_OK ){ + rc = dbdataNext(pCursor); + } + return rc; +} + +/* +** Return a column for the sqlite_dbdata or sqlite_dbptr table. +*/ +static int dbdataColumn( + sqlite3_vtab_cursor *pCursor, + sqlite3_context *ctx, + int i +){ + DbdataCursor *pCsr = (DbdataCursor*)pCursor; + DbdataTable *pTab = (DbdataTable*)pCursor->pVtab; + if( pTab->bPtr ){ + switch( i ){ + case DBPTR_COLUMN_PGNO: + sqlite3_result_int64(ctx, pCsr->iPgno); + break; + case DBPTR_COLUMN_CHILD: { + int iOff = pCsr->iPgno==1 ? 100 : 0; + if( pCsr->iCell<0 ){ + iOff += 8; + }else{ + iOff += 12 + pCsr->iCell*2; + if( iOff>pCsr->nPage ) return SQLITE_OK; + iOff = get_uint16(&pCsr->aPage[iOff]); + } + if( iOff<=pCsr->nPage ){ + sqlite3_result_int64(ctx, get_uint32(&pCsr->aPage[iOff])); + } + break; + } + } + }else{ + switch( i ){ + case DBDATA_COLUMN_PGNO: + sqlite3_result_int64(ctx, pCsr->iPgno); + break; + case DBDATA_COLUMN_CELL: + sqlite3_result_int(ctx, pCsr->iCell); + break; + case DBDATA_COLUMN_FIELD: + sqlite3_result_int(ctx, pCsr->iField); + break; + case DBDATA_COLUMN_VALUE: { + if( pCsr->iField<0 ){ + sqlite3_result_int64(ctx, pCsr->iIntkey); + }else if( &pCsr->pRec[pCsr->nRec] >= pCsr->pPtr ){ + sqlite3_int64 iType; + dbdataGetVarintU32(pCsr->pHdrPtr, &iType); + dbdataValue( + ctx, pCsr->enc, iType, pCsr->pPtr, + &pCsr->pRec[pCsr->nRec] - pCsr->pPtr + ); + } + break; + } + } + } + return SQLITE_OK; +} + +/* +** Return the rowid for an sqlite_dbdata or sqlite_dptr table. +*/ +static int dbdataRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ + DbdataCursor *pCsr = (DbdataCursor*)pCursor; + *pRowid = pCsr->iRowid; + return SQLITE_OK; +} + + +/* +** Invoke this routine to register the "sqlite_dbdata" virtual table module +*/ +static int sqlite3DbdataRegister(sqlite3 *db){ + static sqlite3_module dbdata_module = { + 0, /* iVersion */ + 0, /* xCreate */ + dbdataConnect, /* xConnect */ + dbdataBestIndex, /* xBestIndex */ + dbdataDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + dbdataOpen, /* xOpen - open a cursor */ + dbdataClose, /* xClose - close a cursor */ + dbdataFilter, /* xFilter - configure scan constraints */ + dbdataNext, /* xNext - advance a cursor */ + dbdataEof, /* xEof - check for end of scan */ + dbdataColumn, /* xColumn - read data */ + dbdataRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ + }; + + int rc = sqlite3_create_module(db, "sqlite_dbdata", &dbdata_module, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_module(db, "sqlite_dbptr", &dbdata_module, (void*)1); + } + return rc; +} + +int sqlite3_dbdata_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + (void)pzErrMsg; + return sqlite3DbdataRegister(db); +} + +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + +/************************* End ../ext/recover/dbdata.c ********************/ +/************************* Begin ../ext/recover/sqlite3recover.c ******************/ +/* +** 2022-08-27 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +*/ + + +/* #include "sqlite3recover.h" */ +#include <assert.h> +#include <string.h> + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** Declaration for public API function in file dbdata.c. This may be called +** with NULL as the final two arguments to register the sqlite_dbptr and +** sqlite_dbdata virtual tables with a database handle. +*/ +#ifdef _WIN32 + +#endif +int sqlite3_dbdata_init(sqlite3*, char**, const sqlite3_api_routines*); + +/* typedef unsigned int u32; */ +/* typedef unsigned char u8; */ +/* typedef sqlite3_int64 i64; */ + +typedef struct RecoverTable RecoverTable; +typedef struct RecoverColumn RecoverColumn; + +/* +** When recovering rows of data that can be associated with table +** definitions recovered from the sqlite_schema table, each table is +** represented by an instance of the following object. +** +** iRoot: +** The root page in the original database. Not necessarily (and usually +** not) the same in the recovered database. +** +** zTab: +** Name of the table. +** +** nCol/aCol[]: +** aCol[] is an array of nCol columns. In the order in which they appear +** in the table. +** +** bIntkey: +** Set to true for intkey tables, false for WITHOUT ROWID. +** +** iRowidBind: +** Each column in the aCol[] array has associated with it the index of +** the bind parameter its values will be bound to in the INSERT statement +** used to construct the output database. If the table does has a rowid +** but not an INTEGER PRIMARY KEY column, then iRowidBind contains the +** index of the bind paramater to which the rowid value should be bound. +** Otherwise, it contains -1. If the table does contain an INTEGER PRIMARY +** KEY column, then the rowid value should be bound to the index associated +** with the column. +** +** pNext: +** All RecoverTable objects used by the recovery operation are allocated +** and populated as part of creating the recovered database schema in +** the output database, before any non-schema data are recovered. They +** are then stored in a singly-linked list linked by this variable beginning +** at sqlite3_recover.pTblList. +*/ +struct RecoverTable { + u32 iRoot; /* Root page in original database */ + char *zTab; /* Name of table */ + int nCol; /* Number of columns in table */ + RecoverColumn *aCol; /* Array of columns */ + int bIntkey; /* True for intkey, false for without rowid */ + int iRowidBind; /* If >0, bind rowid to INSERT here */ + RecoverTable *pNext; +}; + +/* +** Each database column is represented by an instance of the following object +** stored in the RecoverTable.aCol[] array of the associated table. +** +** iField: +** The index of the associated field within database records. Or -1 if +** there is no associated field (e.g. for virtual generated columns). +** +** iBind: +** The bind index of the INSERT statement to bind this columns values +** to. Or 0 if there is no such index (iff (iField<0)). +** +** bIPK: +** True if this is the INTEGER PRIMARY KEY column. +** +** zCol: +** Name of column. +** +** eHidden: +** A RECOVER_EHIDDEN_* constant value (see below for interpretation of each). +*/ +struct RecoverColumn { + int iField; /* Field in record on disk */ + int iBind; /* Binding to use in INSERT */ + int bIPK; /* True for IPK column */ + char *zCol; + int eHidden; +}; + +#define RECOVER_EHIDDEN_NONE 0 /* Normal database column */ +#define RECOVER_EHIDDEN_HIDDEN 1 /* Column is __HIDDEN__ */ +#define RECOVER_EHIDDEN_VIRTUAL 2 /* Virtual generated column */ +#define RECOVER_EHIDDEN_STORED 3 /* Stored generated column */ + +/* +** Bitmap object used to track pages in the input database. Allocated +** and manipulated only by the following functions: +** +** recoverBitmapAlloc() +** recoverBitmapFree() +** recoverBitmapSet() +** recoverBitmapQuery() +** +** nPg: +** Largest page number that may be stored in the bitmap. The range +** of valid keys is 1 to nPg, inclusive. +** +** aElem[]: +** Array large enough to contain a bit for each key. For key value +** iKey, the associated bit is the bit (iKey%32) of aElem[iKey/32]. +** In other words, the following is true if bit iKey is set, or +** false if it is clear: +** +** (aElem[iKey/32] & (1 << (iKey%32))) ? 1 : 0 +*/ +typedef struct RecoverBitmap RecoverBitmap; +struct RecoverBitmap { + i64 nPg; /* Size of bitmap */ + u32 aElem[1]; /* Array of 32-bit bitmasks */ +}; + +/* +** State variables (part of the sqlite3_recover structure) used while +** recovering data for tables identified in the recovered schema (state +** RECOVER_STATE_WRITING). +*/ +typedef struct RecoverStateW1 RecoverStateW1; +struct RecoverStateW1 { + sqlite3_stmt *pTbls; + sqlite3_stmt *pSel; + sqlite3_stmt *pInsert; + int nInsert; + + RecoverTable *pTab; /* Table currently being written */ + int nMax; /* Max column count in any schema table */ + sqlite3_value **apVal; /* Array of nMax values */ + int nVal; /* Number of valid entries in apVal[] */ + int bHaveRowid; + i64 iRowid; + i64 iPrevPage; + int iPrevCell; +}; + +/* +** State variables (part of the sqlite3_recover structure) used while +** recovering data destined for the lost and found table (states +** RECOVER_STATE_LOSTANDFOUND[123]). +*/ +typedef struct RecoverStateLAF RecoverStateLAF; +struct RecoverStateLAF { + RecoverBitmap *pUsed; + i64 nPg; /* Size of db in pages */ + sqlite3_stmt *pAllAndParent; + sqlite3_stmt *pMapInsert; + sqlite3_stmt *pMaxField; + sqlite3_stmt *pUsedPages; + sqlite3_stmt *pFindRoot; + sqlite3_stmt *pInsert; /* INSERT INTO lost_and_found ... */ + sqlite3_stmt *pAllPage; + sqlite3_stmt *pPageData; + sqlite3_value **apVal; + int nMaxField; +}; + +/* +** Main recover handle structure. +*/ +struct sqlite3_recover { + /* Copies of sqlite3_recover_init[_sql]() parameters */ + sqlite3 *dbIn; /* Input database */ + char *zDb; /* Name of input db ("main" etc.) */ + char *zUri; /* URI for output database */ + void *pSqlCtx; /* SQL callback context */ + int (*xSql)(void*,const char*); /* Pointer to SQL callback function */ + + /* Values configured by sqlite3_recover_config() */ + char *zStateDb; /* State database to use (or NULL) */ + char *zLostAndFound; /* Name of lost-and-found table (or NULL) */ + int bFreelistCorrupt; /* SQLITE_RECOVER_FREELIST_CORRUPT setting */ + int bRecoverRowid; /* SQLITE_RECOVER_ROWIDS setting */ + int bSlowIndexes; /* SQLITE_RECOVER_SLOWINDEXES setting */ + + int pgsz; + int detected_pgsz; + int nReserve; + u8 *pPage1Disk; + u8 *pPage1Cache; + + /* Error code and error message */ + int errCode; /* For sqlite3_recover_errcode() */ + char *zErrMsg; /* For sqlite3_recover_errmsg() */ + + int eState; + int bCloseTransaction; + + /* Variables used with eState==RECOVER_STATE_WRITING */ + RecoverStateW1 w1; + + /* Variables used with states RECOVER_STATE_LOSTANDFOUND[123] */ + RecoverStateLAF laf; + + /* Fields used within sqlite3_recover_run() */ + sqlite3 *dbOut; /* Output database */ + sqlite3_stmt *pGetPage; /* SELECT against input db sqlite_dbdata */ + RecoverTable *pTblList; /* List of tables recovered from schema */ +}; + +/* +** The various states in which an sqlite3_recover object may exist: +** +** RECOVER_STATE_INIT: +** The object is initially created in this state. sqlite3_recover_step() +** has yet to be called. This is the only state in which it is permitted +** to call sqlite3_recover_config(). +** +** RECOVER_STATE_WRITING: +** +** RECOVER_STATE_LOSTANDFOUND1: +** State to populate the bitmap of pages used by other tables or the +** database freelist. +** +** RECOVER_STATE_LOSTANDFOUND2: +** Populate the recovery.map table - used to figure out a "root" page +** for each lost page from in the database from which records are +** extracted. +** +** RECOVER_STATE_LOSTANDFOUND3: +** Populate the lost-and-found table itself. +*/ +#define RECOVER_STATE_INIT 0 +#define RECOVER_STATE_WRITING 1 +#define RECOVER_STATE_LOSTANDFOUND1 2 +#define RECOVER_STATE_LOSTANDFOUND2 3 +#define RECOVER_STATE_LOSTANDFOUND3 4 +#define RECOVER_STATE_SCHEMA2 5 +#define RECOVER_STATE_DONE 6 + + +/* +** Global variables used by this extension. +*/ +typedef struct RecoverGlobal RecoverGlobal; +struct RecoverGlobal { + const sqlite3_io_methods *pMethods; + sqlite3_recover *p; +}; +static RecoverGlobal recover_g; + +/* +** Use this static SQLite mutex to protect the globals during the +** first call to sqlite3_recover_step(). +*/ +#define RECOVER_MUTEX_ID SQLITE_MUTEX_STATIC_APP2 + + +/* +** Default value for SQLITE_RECOVER_ROWIDS (sqlite3_recover.bRecoverRowid). +*/ +#define RECOVER_ROWID_DEFAULT 1 + +/* +** Mutex handling: +** +** recoverEnterMutex() - Enter the recovery mutex +** recoverLeaveMutex() - Leave the recovery mutex +** recoverAssertMutexHeld() - Assert that the recovery mutex is held +*/ +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 +# define recoverEnterMutex() +# define recoverLeaveMutex() +#else +static void recoverEnterMutex(void){ + sqlite3_mutex_enter(sqlite3_mutex_alloc(RECOVER_MUTEX_ID)); +} +static void recoverLeaveMutex(void){ + sqlite3_mutex_leave(sqlite3_mutex_alloc(RECOVER_MUTEX_ID)); +} +#endif +#if SQLITE_THREADSAFE+0>=1 && defined(SQLITE_DEBUG) +static void recoverAssertMutexHeld(void){ + assert( sqlite3_mutex_held(sqlite3_mutex_alloc(RECOVER_MUTEX_ID)) ); +} +#else +# define recoverAssertMutexHeld() +#endif + + +/* +** Like strlen(). But handles NULL pointer arguments. +*/ +static int recoverStrlen(const char *zStr){ + if( zStr==0 ) return 0; + return (int)(strlen(zStr)&0x7fffffff); +} + +/* +** This function is a no-op if the recover handle passed as the first +** argument already contains an error (if p->errCode!=SQLITE_OK). +** +** Otherwise, an attempt is made to allocate, zero and return a buffer nByte +** bytes in size. If successful, a pointer to the new buffer is returned. Or, +** if an OOM error occurs, NULL is returned and the handle error code +** (p->errCode) set to SQLITE_NOMEM. +*/ +static void *recoverMalloc(sqlite3_recover *p, i64 nByte){ + void *pRet = 0; + assert( nByte>0 ); + if( p->errCode==SQLITE_OK ){ + pRet = sqlite3_malloc64(nByte); + if( pRet ){ + memset(pRet, 0, nByte); + }else{ + p->errCode = SQLITE_NOMEM; + } + } + return pRet; +} + +/* +** Set the error code and error message for the recover handle passed as +** the first argument. The error code is set to the value of parameter +** errCode. +** +** Parameter zFmt must be a printf() style formatting string. The handle +** error message is set to the result of using any trailing arguments for +** parameter substitutions in the formatting string. +** +** For example: +** +** recoverError(p, SQLITE_ERROR, "no such table: %s", zTablename); +*/ +static int recoverError( + sqlite3_recover *p, + int errCode, + const char *zFmt, ... +){ + char *z = 0; + va_list ap; + va_start(ap, zFmt); + if( zFmt ){ + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + } + sqlite3_free(p->zErrMsg); + p->zErrMsg = z; + p->errCode = errCode; + return errCode; +} + + +/* +** This function is a no-op if p->errCode is initially other than SQLITE_OK. +** In this case it returns NULL. +** +** Otherwise, an attempt is made to allocate and return a bitmap object +** large enough to store a bit for all page numbers between 1 and nPg, +** inclusive. The bitmap is initially zeroed. +*/ +static RecoverBitmap *recoverBitmapAlloc(sqlite3_recover *p, i64 nPg){ + int nElem = (nPg+1+31) / 32; + int nByte = sizeof(RecoverBitmap) + nElem*sizeof(u32); + RecoverBitmap *pRet = (RecoverBitmap*)recoverMalloc(p, nByte); + + if( pRet ){ + pRet->nPg = nPg; + } + return pRet; +} + +/* +** Free a bitmap object allocated by recoverBitmapAlloc(). +*/ +static void recoverBitmapFree(RecoverBitmap *pMap){ + sqlite3_free(pMap); +} + +/* +** Set the bit associated with page iPg in bitvec pMap. +*/ +static void recoverBitmapSet(RecoverBitmap *pMap, i64 iPg){ + if( iPg<=pMap->nPg ){ + int iElem = (iPg / 32); + int iBit = (iPg % 32); + pMap->aElem[iElem] |= (((u32)1) << iBit); + } +} + +/* +** Query bitmap object pMap for the state of the bit associated with page +** iPg. Return 1 if it is set, or 0 otherwise. +*/ +static int recoverBitmapQuery(RecoverBitmap *pMap, i64 iPg){ + int ret = 1; + if( iPg<=pMap->nPg && iPg>0 ){ + int iElem = (iPg / 32); + int iBit = (iPg % 32); + ret = (pMap->aElem[iElem] & (((u32)1) << iBit)) ? 1 : 0; + } + return ret; +} + +/* +** Set the recover handle error to the error code and message returned by +** calling sqlite3_errcode() and sqlite3_errmsg(), respectively, on database +** handle db. +*/ +static int recoverDbError(sqlite3_recover *p, sqlite3 *db){ + return recoverError(p, sqlite3_errcode(db), "%s", sqlite3_errmsg(db)); +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). +** +** Otherwise, it attempts to prepare the SQL statement in zSql against +** database handle db. If successful, the statement handle is returned. +** Or, if an error occurs, NULL is returned and an error left in the +** recover handle. +*/ +static sqlite3_stmt *recoverPrepare( + sqlite3_recover *p, + sqlite3 *db, + const char *zSql +){ + sqlite3_stmt *pStmt = 0; + if( p->errCode==SQLITE_OK ){ + if( sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0) ){ + recoverDbError(p, db); + } + } + return pStmt; +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). +** +** Otherwise, argument zFmt is used as a printf() style format string, +** along with any trailing arguments, to create an SQL statement. This +** SQL statement is prepared against database handle db and, if successful, +** the statment handle returned. Or, if an error occurs - either during +** the printf() formatting or when preparing the resulting SQL - an +** error code and message are left in the recover handle. +*/ +static sqlite3_stmt *recoverPreparePrintf( + sqlite3_recover *p, + sqlite3 *db, + const char *zFmt, ... +){ + sqlite3_stmt *pStmt = 0; + if( p->errCode==SQLITE_OK ){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( z==0 ){ + p->errCode = SQLITE_NOMEM; + }else{ + pStmt = recoverPrepare(p, db, z); + sqlite3_free(z); + } + } + return pStmt; +} + +/* +** Reset SQLite statement handle pStmt. If the call to sqlite3_reset() +** indicates that an error occurred, and there is not already an error +** in the recover handle passed as the first argument, set the error +** code and error message appropriately. +** +** This function returns a copy of the statement handle pointer passed +** as the second argument. +*/ +static sqlite3_stmt *recoverReset(sqlite3_recover *p, sqlite3_stmt *pStmt){ + int rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT && p->errCode==SQLITE_OK ){ + recoverDbError(p, sqlite3_db_handle(pStmt)); + } + return pStmt; +} + +/* +** Finalize SQLite statement handle pStmt. If the call to sqlite3_reset() +** indicates that an error occurred, and there is not already an error +** in the recover handle passed as the first argument, set the error +** code and error message appropriately. +*/ +static void recoverFinalize(sqlite3_recover *p, sqlite3_stmt *pStmt){ + sqlite3 *db = sqlite3_db_handle(pStmt); + int rc = sqlite3_finalize(pStmt); + if( rc!=SQLITE_OK && p->errCode==SQLITE_OK ){ + recoverDbError(p, db); + } +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). A copy of p->errCode is returned in this +** case. +** +** Otherwise, execute SQL script zSql. If successful, return SQLITE_OK. +** Or, if an error occurs, leave an error code and message in the recover +** handle and return a copy of the error code. +*/ +static int recoverExec(sqlite3_recover *p, sqlite3 *db, const char *zSql){ + if( p->errCode==SQLITE_OK ){ + int rc = sqlite3_exec(db, zSql, 0, 0, 0); + if( rc ){ + recoverDbError(p, db); + } + } + return p->errCode; +} + +/* +** Bind the value pVal to parameter iBind of statement pStmt. Leave an +** error in the recover handle passed as the first argument if an error +** (e.g. an OOM) occurs. +*/ +static void recoverBindValue( + sqlite3_recover *p, + sqlite3_stmt *pStmt, + int iBind, + sqlite3_value *pVal +){ + if( p->errCode==SQLITE_OK ){ + int rc = sqlite3_bind_value(pStmt, iBind, pVal); + if( rc ) recoverError(p, rc, 0); + } +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). NULL is returned in this case. +** +** Otherwise, an attempt is made to interpret zFmt as a printf() style +** formatting string and the result of using the trailing arguments for +** parameter substitution with it written into a buffer obtained from +** sqlite3_malloc(). If successful, a pointer to the buffer is returned. +** It is the responsibility of the caller to eventually free the buffer +** using sqlite3_free(). +** +** Or, if an error occurs, an error code and message is left in the recover +** handle and NULL returned. +*/ +static char *recoverMPrintf(sqlite3_recover *p, const char *zFmt, ...){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( p->errCode==SQLITE_OK ){ + if( z==0 ) p->errCode = SQLITE_NOMEM; + }else{ + sqlite3_free(z); + z = 0; + } + return z; +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). Zero is returned in this case. +** +** Otherwise, execute "PRAGMA page_count" against the input database. If +** successful, return the integer result. Or, if an error occurs, leave an +** error code and error message in the sqlite3_recover handle and return +** zero. +*/ +static i64 recoverPageCount(sqlite3_recover *p){ + i64 nPg = 0; + if( p->errCode==SQLITE_OK ){ + sqlite3_stmt *pStmt = 0; + pStmt = recoverPreparePrintf(p, p->dbIn, "PRAGMA %Q.page_count", p->zDb); + if( pStmt ){ + sqlite3_step(pStmt); + nPg = sqlite3_column_int64(pStmt, 0); + } + recoverFinalize(p, pStmt); + } + return nPg; +} + +/* +** Implementation of SQL scalar function "read_i32". The first argument to +** this function must be a blob. The second a non-negative integer. This +** function reads and returns a 32-bit big-endian integer from byte +** offset (4*<arg2>) of the blob. +** +** SELECT read_i32(<blob>, <idx>) +*/ +static void recoverReadI32( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *pBlob; + int nBlob; + int iInt; + + assert( argc==2 ); + nBlob = sqlite3_value_bytes(argv[0]); + pBlob = (const unsigned char*)sqlite3_value_blob(argv[0]); + iInt = sqlite3_value_int(argv[1]) & 0xFFFF; + + if( (iInt+1)*4<=nBlob ){ + const unsigned char *a = &pBlob[iInt*4]; + i64 iVal = ((i64)a[0]<<24) + + ((i64)a[1]<<16) + + ((i64)a[2]<< 8) + + ((i64)a[3]<< 0); + sqlite3_result_int64(context, iVal); + } +} + +/* +** Implementation of SQL scalar function "page_is_used". This function +** is used as part of the procedure for locating orphan rows for the +** lost-and-found table, and it depends on those routines having populated +** the sqlite3_recover.laf.pUsed variable. +** +** The only argument to this function is a page-number. It returns true +** if the page has already been used somehow during data recovery, or false +** otherwise. +** +** SELECT page_is_used(<pgno>); +*/ +static void recoverPageIsUsed( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + sqlite3_recover *p = (sqlite3_recover*)sqlite3_user_data(pCtx); + i64 pgno = sqlite3_value_int64(apArg[0]); + assert( nArg==1 ); + sqlite3_result_int(pCtx, recoverBitmapQuery(p->laf.pUsed, pgno)); +} + +/* +** The implementation of a user-defined SQL function invoked by the +** sqlite_dbdata and sqlite_dbptr virtual table modules to access pages +** of the database being recovered. +** +** This function always takes a single integer argument. If the argument +** is zero, then the value returned is the number of pages in the db being +** recovered. If the argument is greater than zero, it is a page number. +** The value returned in this case is an SQL blob containing the data for +** the identified page of the db being recovered. e.g. +** +** SELECT getpage(0); -- return number of pages in db +** SELECT getpage(4); -- return page 4 of db as a blob of data +*/ +static void recoverGetPage( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + sqlite3_recover *p = (sqlite3_recover*)sqlite3_user_data(pCtx); + i64 pgno = sqlite3_value_int64(apArg[0]); + sqlite3_stmt *pStmt = 0; + + assert( nArg==1 ); + if( pgno==0 ){ + i64 nPg = recoverPageCount(p); + sqlite3_result_int64(pCtx, nPg); + return; + }else{ + if( p->pGetPage==0 ){ + pStmt = p->pGetPage = recoverPreparePrintf( + p, p->dbIn, "SELECT data FROM sqlite_dbpage(%Q) WHERE pgno=?", p->zDb + ); + }else if( p->errCode==SQLITE_OK ){ + pStmt = p->pGetPage; + } + + if( pStmt ){ + sqlite3_bind_int64(pStmt, 1, pgno); + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + const u8 *aPg; + int nPg; + assert( p->errCode==SQLITE_OK ); + aPg = sqlite3_column_blob(pStmt, 0); + nPg = sqlite3_column_bytes(pStmt, 0); + if( pgno==1 && nPg==p->pgsz && 0==memcmp(p->pPage1Cache, aPg, nPg) ){ + aPg = p->pPage1Disk; + } + sqlite3_result_blob(pCtx, aPg, nPg-p->nReserve, SQLITE_TRANSIENT); + } + recoverReset(p, pStmt); + } + } + + if( p->errCode ){ + if( p->zErrMsg ) sqlite3_result_error(pCtx, p->zErrMsg, -1); + sqlite3_result_error_code(pCtx, p->errCode); + } +} + +/* +** Find a string that is not found anywhere in z[]. Return a pointer +** to that string. +** +** Try to use zA and zB first. If both of those are already found in z[] +** then make up some string and store it in the buffer zBuf. +*/ +static const char *recoverUnusedString( + const char *z, /* Result must not appear anywhere in z */ + const char *zA, const char *zB, /* Try these first */ + char *zBuf /* Space to store a generated string */ +){ + unsigned i = 0; + if( strstr(z, zA)==0 ) return zA; + if( strstr(z, zB)==0 ) return zB; + do{ + sqlite3_snprintf(20,zBuf,"(%s%u)", zA, i++); + }while( strstr(z,zBuf)!=0 ); + return zBuf; +} + +/* +** Implementation of scalar SQL function "escape_crnl". The argument passed to +** this function is the output of built-in function quote(). If the first +** character of the input is "'", indicating that the value passed to quote() +** was a text value, then this function searches the input for "\n" and "\r" +** characters and adds a wrapper similar to the following: +** +** replace(replace(<input>, '\n', char(10), '\r', char(13)); +** +** Or, if the first character of the input is not "'", then a copy of the input +** is returned. +*/ +static void recoverEscapeCrnl( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zText = (const char*)sqlite3_value_text(argv[0]); + (void)argc; + if( zText && zText[0]=='\'' ){ + int nText = sqlite3_value_bytes(argv[0]); + int i; + char zBuf1[20]; + char zBuf2[20]; + const char *zNL = 0; + const char *zCR = 0; + int nCR = 0; + int nNL = 0; + + for(i=0; zText[i]; i++){ + if( zNL==0 && zText[i]=='\n' ){ + zNL = recoverUnusedString(zText, "\\n", "\\012", zBuf1); + nNL = (int)strlen(zNL); + } + if( zCR==0 && zText[i]=='\r' ){ + zCR = recoverUnusedString(zText, "\\r", "\\015", zBuf2); + nCR = (int)strlen(zCR); + } + } + + if( zNL || zCR ){ + int iOut = 0; + i64 nMax = (nNL > nCR) ? nNL : nCR; + i64 nAlloc = nMax * nText + (nMax+64)*2; + char *zOut = (char*)sqlite3_malloc64(nAlloc); + if( zOut==0 ){ + sqlite3_result_error_nomem(context); + return; + } + + if( zNL && zCR ){ + memcpy(&zOut[iOut], "replace(replace(", 16); + iOut += 16; + }else{ + memcpy(&zOut[iOut], "replace(", 8); + iOut += 8; + } + for(i=0; zText[i]; i++){ + if( zText[i]=='\n' ){ + memcpy(&zOut[iOut], zNL, nNL); + iOut += nNL; + }else if( zText[i]=='\r' ){ + memcpy(&zOut[iOut], zCR, nCR); + iOut += nCR; + }else{ + zOut[iOut] = zText[i]; + iOut++; + } + } + + if( zNL ){ + memcpy(&zOut[iOut], ",'", 2); iOut += 2; + memcpy(&zOut[iOut], zNL, nNL); iOut += nNL; + memcpy(&zOut[iOut], "', char(10))", 12); iOut += 12; + } + if( zCR ){ + memcpy(&zOut[iOut], ",'", 2); iOut += 2; + memcpy(&zOut[iOut], zCR, nCR); iOut += nCR; + memcpy(&zOut[iOut], "', char(13))", 12); iOut += 12; + } + + sqlite3_result_text(context, zOut, iOut, SQLITE_TRANSIENT); + sqlite3_free(zOut); + return; + } + } + + sqlite3_result_value(context, argv[0]); +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). A copy of the error code is returned in +** this case. +** +** Otherwise, attempt to populate temporary table "recovery.schema" with the +** parts of the database schema that can be extracted from the input database. +** +** If no error occurs, SQLITE_OK is returned. Otherwise, an error code +** and error message are left in the recover handle and a copy of the +** error code returned. It is not considered an error if part of all of +** the database schema cannot be recovered due to corruption. +*/ +static int recoverCacheSchema(sqlite3_recover *p){ + return recoverExec(p, p->dbOut, + "WITH RECURSIVE pages(p) AS (" + " SELECT 1" + " UNION" + " SELECT child FROM sqlite_dbptr('getpage()'), pages WHERE pgno=p" + ")" + "INSERT INTO recovery.schema SELECT" + " max(CASE WHEN field=0 THEN value ELSE NULL END)," + " max(CASE WHEN field=1 THEN value ELSE NULL END)," + " max(CASE WHEN field=2 THEN value ELSE NULL END)," + " max(CASE WHEN field=3 THEN value ELSE NULL END)," + " max(CASE WHEN field=4 THEN value ELSE NULL END)" + "FROM sqlite_dbdata('getpage()') WHERE pgno IN (" + " SELECT p FROM pages" + ") GROUP BY pgno, cell" + ); +} + +/* +** If this recover handle is not in SQL callback mode (i.e. was not created +** using sqlite3_recover_init_sql()) of if an error has already occurred, +** this function is a no-op. Otherwise, issue a callback with SQL statement +** zSql as the parameter. +** +** If the callback returns non-zero, set the recover handle error code to +** the value returned (so that the caller will abandon processing). +*/ +static void recoverSqlCallback(sqlite3_recover *p, const char *zSql){ + if( p->errCode==SQLITE_OK && p->xSql ){ + int res = p->xSql(p->pSqlCtx, zSql); + if( res ){ + recoverError(p, SQLITE_ERROR, "callback returned an error - %d", res); + } + } +} + +/* +** Transfer the following settings from the input database to the output +** database: +** +** + page-size, +** + auto-vacuum settings, +** + database encoding, +** + user-version (PRAGMA user_version), and +** + application-id (PRAGMA application_id), and +*/ +static void recoverTransferSettings(sqlite3_recover *p){ + const char *aPragma[] = { + "encoding", + "page_size", + "auto_vacuum", + "user_version", + "application_id" + }; + int ii; + + /* Truncate the output database to 0 pages in size. This is done by + ** opening a new, empty, temp db, then using the backup API to clobber + ** any existing output db with a copy of it. */ + if( p->errCode==SQLITE_OK ){ + sqlite3 *db2 = 0; + int rc = sqlite3_open("", &db2); + if( rc!=SQLITE_OK ){ + recoverDbError(p, db2); + return; + } + + for(ii=0; ii<(int)(sizeof(aPragma)/sizeof(aPragma[0])); ii++){ + const char *zPrag = aPragma[ii]; + sqlite3_stmt *p1 = 0; + p1 = recoverPreparePrintf(p, p->dbIn, "PRAGMA %Q.%s", p->zDb, zPrag); + if( p->errCode==SQLITE_OK && sqlite3_step(p1)==SQLITE_ROW ){ + const char *zArg = (const char*)sqlite3_column_text(p1, 0); + char *z2 = recoverMPrintf(p, "PRAGMA %s = %Q", zPrag, zArg); + recoverSqlCallback(p, z2); + recoverExec(p, db2, z2); + sqlite3_free(z2); + if( zArg==0 ){ + recoverError(p, SQLITE_NOMEM, 0); + } + } + recoverFinalize(p, p1); + } + recoverExec(p, db2, "CREATE TABLE t1(a); DROP TABLE t1;"); + + if( p->errCode==SQLITE_OK ){ + sqlite3 *db = p->dbOut; + sqlite3_backup *pBackup = sqlite3_backup_init(db, "main", db2, "main"); + if( pBackup ){ + sqlite3_backup_step(pBackup, -1); + p->errCode = sqlite3_backup_finish(pBackup); + }else{ + recoverDbError(p, db); + } + } + + sqlite3_close(db2); + } +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). A copy of the error code is returned in +** this case. +** +** Otherwise, an attempt is made to open the output database, attach +** and create the schema of the temporary database used to store +** intermediate data, and to register all required user functions and +** virtual table modules with the output handle. +** +** If no error occurs, SQLITE_OK is returned. Otherwise, an error code +** and error message are left in the recover handle and a copy of the +** error code returned. +*/ +static int recoverOpenOutput(sqlite3_recover *p){ + struct Func { + const char *zName; + int nArg; + void (*xFunc)(sqlite3_context*,int,sqlite3_value **); + } aFunc[] = { + { "getpage", 1, recoverGetPage }, + { "page_is_used", 1, recoverPageIsUsed }, + { "read_i32", 2, recoverReadI32 }, + { "escape_crnl", 1, recoverEscapeCrnl }, + }; + + const int flags = SQLITE_OPEN_URI|SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE; + sqlite3 *db = 0; /* New database handle */ + int ii; /* For iterating through aFunc[] */ + + assert( p->dbOut==0 ); + + if( sqlite3_open_v2(p->zUri, &db, flags, 0) ){ + recoverDbError(p, db); + } + + /* Register the sqlite_dbdata and sqlite_dbptr virtual table modules. + ** These two are registered with the output database handle - this + ** module depends on the input handle supporting the sqlite_dbpage + ** virtual table only. */ + if( p->errCode==SQLITE_OK ){ + p->errCode = sqlite3_dbdata_init(db, 0, 0); + } + + /* Register the custom user-functions with the output handle. */ + for(ii=0; + p->errCode==SQLITE_OK && ii<(int)(sizeof(aFunc)/sizeof(aFunc[0])); + ii++){ + p->errCode = sqlite3_create_function(db, aFunc[ii].zName, + aFunc[ii].nArg, SQLITE_UTF8, (void*)p, aFunc[ii].xFunc, 0, 0 + ); + } + + p->dbOut = db; + return p->errCode; +} + +/* +** Attach the auxiliary database 'recovery' to the output database handle. +** This temporary database is used during the recovery process and then +** discarded. +*/ +static void recoverOpenRecovery(sqlite3_recover *p){ + char *zSql = recoverMPrintf(p, "ATTACH %Q AS recovery;", p->zStateDb); + recoverExec(p, p->dbOut, zSql); + recoverExec(p, p->dbOut, + "PRAGMA writable_schema = 1;" + "CREATE TABLE recovery.map(pgno INTEGER PRIMARY KEY, parent INT);" + "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);" + ); + sqlite3_free(zSql); +} + + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). +** +** Otherwise, argument zName must be the name of a table that has just been +** created in the output database. This function queries the output db +** for the schema of said table, and creates a RecoverTable object to +** store the schema in memory. The new RecoverTable object is linked into +** the list at sqlite3_recover.pTblList. +** +** Parameter iRoot must be the root page of table zName in the INPUT +** database. +*/ +static void recoverAddTable( + sqlite3_recover *p, + const char *zName, /* Name of table created in output db */ + i64 iRoot /* Root page of same table in INPUT db */ +){ + sqlite3_stmt *pStmt = recoverPreparePrintf(p, p->dbOut, + "PRAGMA table_xinfo(%Q)", zName + ); + + if( pStmt ){ + int iPk = -1; + int iBind = 1; + RecoverTable *pNew = 0; + int nCol = 0; + int nName = recoverStrlen(zName); + int nByte = 0; + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + nCol++; + nByte += (sqlite3_column_bytes(pStmt, 1)+1); + } + nByte += sizeof(RecoverTable) + nCol*sizeof(RecoverColumn) + nName+1; + recoverReset(p, pStmt); + + pNew = recoverMalloc(p, nByte); + if( pNew ){ + int i = 0; + int iField = 0; + char *csr = 0; + pNew->aCol = (RecoverColumn*)&pNew[1]; + pNew->zTab = csr = (char*)&pNew->aCol[nCol]; + pNew->nCol = nCol; + pNew->iRoot = iRoot; + memcpy(csr, zName, nName); + csr += nName+1; + + for(i=0; sqlite3_step(pStmt)==SQLITE_ROW; i++){ + int iPKF = sqlite3_column_int(pStmt, 5); + int n = sqlite3_column_bytes(pStmt, 1); + const char *z = (const char*)sqlite3_column_text(pStmt, 1); + const char *zType = (const char*)sqlite3_column_text(pStmt, 2); + int eHidden = sqlite3_column_int(pStmt, 6); + + if( iPk==-1 && iPKF==1 && !sqlite3_stricmp("integer", zType) ) iPk = i; + if( iPKF>1 ) iPk = -2; + pNew->aCol[i].zCol = csr; + pNew->aCol[i].eHidden = eHidden; + if( eHidden==RECOVER_EHIDDEN_VIRTUAL ){ + pNew->aCol[i].iField = -1; + }else{ + pNew->aCol[i].iField = iField++; + } + if( eHidden!=RECOVER_EHIDDEN_VIRTUAL + && eHidden!=RECOVER_EHIDDEN_STORED + ){ + pNew->aCol[i].iBind = iBind++; + } + memcpy(csr, z, n); + csr += (n+1); + } + + pNew->pNext = p->pTblList; + p->pTblList = pNew; + pNew->bIntkey = 1; + } + + recoverFinalize(p, pStmt); + + pStmt = recoverPreparePrintf(p, p->dbOut, "PRAGMA index_xinfo(%Q)", zName); + while( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ + int iField = sqlite3_column_int(pStmt, 0); + int iCol = sqlite3_column_int(pStmt, 1); + + assert( iCol<pNew->nCol ); + pNew->aCol[iCol].iField = iField; + + pNew->bIntkey = 0; + iPk = -2; + } + recoverFinalize(p, pStmt); + + if( p->errCode==SQLITE_OK ){ + if( iPk>=0 ){ + pNew->aCol[iPk].bIPK = 1; + }else if( pNew->bIntkey ){ + pNew->iRowidBind = iBind++; + } + } + } +} + +/* +** This function is called after recoverCacheSchema() has cached those parts +** of the input database schema that could be recovered in temporary table +** "recovery.schema". This function creates in the output database copies +** of all parts of that schema that must be created before the tables can +** be populated. Specifically, this means: +** +** * all tables that are not VIRTUAL, and +** * UNIQUE indexes. +** +** If the recovery handle uses SQL callbacks, then callbacks containing +** the associated "CREATE TABLE" and "CREATE INDEX" statements are made. +** +** Additionally, records are added to the sqlite_schema table of the +** output database for any VIRTUAL tables. The CREATE VIRTUAL TABLE +** records are written directly to sqlite_schema, not actually executed. +** If the handle is in SQL callback mode, then callbacks are invoked +** with equivalent SQL statements. +*/ +static int recoverWriteSchema1(sqlite3_recover *p){ + sqlite3_stmt *pSelect = 0; + sqlite3_stmt *pTblname = 0; + + pSelect = recoverPrepare(p, p->dbOut, + "WITH dbschema(rootpage, name, sql, tbl, isVirtual, isIndex) AS (" + " SELECT rootpage, name, sql, " + " type='table', " + " sql LIKE 'create virtual%'," + " (type='index' AND (sql LIKE '%unique%' OR ?1))" + " FROM recovery.schema" + ")" + "SELECT rootpage, tbl, isVirtual, name, sql" + " FROM dbschema " + " WHERE tbl OR isIndex" + " ORDER BY tbl DESC, name=='sqlite_sequence' DESC" + ); + + pTblname = recoverPrepare(p, p->dbOut, + "SELECT name FROM sqlite_schema " + "WHERE type='table' ORDER BY rowid DESC LIMIT 1" + ); + + if( pSelect ){ + sqlite3_bind_int(pSelect, 1, p->bSlowIndexes); + while( sqlite3_step(pSelect)==SQLITE_ROW ){ + i64 iRoot = sqlite3_column_int64(pSelect, 0); + int bTable = sqlite3_column_int(pSelect, 1); + int bVirtual = sqlite3_column_int(pSelect, 2); + const char *zName = (const char*)sqlite3_column_text(pSelect, 3); + const char *zSql = (const char*)sqlite3_column_text(pSelect, 4); + char *zFree = 0; + int rc = SQLITE_OK; + + if( bVirtual ){ + zSql = (const char*)(zFree = recoverMPrintf(p, + "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)", + zName, zName, zSql + )); + } + rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0); + if( rc==SQLITE_OK ){ + recoverSqlCallback(p, zSql); + if( bTable && !bVirtual ){ + if( SQLITE_ROW==sqlite3_step(pTblname) ){ + const char *zTbl = (const char*)sqlite3_column_text(pTblname, 0); + recoverAddTable(p, zTbl, iRoot); + } + recoverReset(p, pTblname); + } + }else if( rc!=SQLITE_ERROR ){ + recoverDbError(p, p->dbOut); + } + sqlite3_free(zFree); + } + } + recoverFinalize(p, pSelect); + recoverFinalize(p, pTblname); + + return p->errCode; +} + +/* +** This function is called after the output database has been populated. It +** adds all recovered schema elements that were not created in the output +** database by recoverWriteSchema1() - everything except for tables and +** UNIQUE indexes. Specifically: +** +** * views, +** * triggers, +** * non-UNIQUE indexes. +** +** If the recover handle is in SQL callback mode, then equivalent callbacks +** are issued to create the schema elements. +*/ +static int recoverWriteSchema2(sqlite3_recover *p){ + sqlite3_stmt *pSelect = 0; + + pSelect = recoverPrepare(p, p->dbOut, + p->bSlowIndexes ? + "SELECT rootpage, sql FROM recovery.schema " + " WHERE type!='table' AND type!='index'" + : + "SELECT rootpage, sql FROM recovery.schema " + " WHERE type!='table' AND (type!='index' OR sql NOT LIKE '%unique%')" + ); + + if( pSelect ){ + while( sqlite3_step(pSelect)==SQLITE_ROW ){ + const char *zSql = (const char*)sqlite3_column_text(pSelect, 1); + int rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0); + if( rc==SQLITE_OK ){ + recoverSqlCallback(p, zSql); + }else if( rc!=SQLITE_ERROR ){ + recoverDbError(p, p->dbOut); + } + } + } + recoverFinalize(p, pSelect); + + return p->errCode; +} + +/* +** This function is a no-op if recover handle p already contains an error +** (if p->errCode!=SQLITE_OK). In this case it returns NULL. +** +** Otherwise, if the recover handle is configured to create an output +** database (was created by sqlite3_recover_init()), then this function +** prepares and returns an SQL statement to INSERT a new record into table +** pTab, assuming the first nField fields of a record extracted from disk +** are valid. +** +** For example, if table pTab is: +** +** CREATE TABLE name(a, b GENERATED ALWAYS AS (a+1) STORED, c, d, e); +** +** And nField is 4, then the SQL statement prepared and returned is: +** +** INSERT INTO (a, c, d) VALUES (?1, ?2, ?3); +** +** In this case even though 4 values were extracted from the input db, +** only 3 are written to the output, as the generated STORED column +** cannot be written. +** +** If the recover handle is in SQL callback mode, then the SQL statement +** prepared is such that evaluating it returns a single row containing +** a single text value - itself an SQL statement similar to the above, +** except with SQL literals in place of the variables. For example: +** +** SELECT 'INSERT INTO (a, c, d) VALUES (' +** || quote(?1) || ', ' +** || quote(?2) || ', ' +** || quote(?3) || ')'; +** +** In either case, it is the responsibility of the caller to eventually +** free the statement handle using sqlite3_finalize(). +*/ +static sqlite3_stmt *recoverInsertStmt( + sqlite3_recover *p, + RecoverTable *pTab, + int nField +){ + sqlite3_stmt *pRet = 0; + const char *zSep = ""; + const char *zSqlSep = ""; + char *zSql = 0; + char *zFinal = 0; + char *zBind = 0; + int ii; + int bSql = p->xSql ? 1 : 0; + + if( nField<=0 ) return 0; + + assert( nField<=pTab->nCol ); + + zSql = recoverMPrintf(p, "INSERT OR IGNORE INTO %Q(", pTab->zTab); + + if( pTab->iRowidBind ){ + assert( pTab->bIntkey ); + zSql = recoverMPrintf(p, "%z_rowid_", zSql); + if( bSql ){ + zBind = recoverMPrintf(p, "%zquote(?%d)", zBind, pTab->iRowidBind); + }else{ + zBind = recoverMPrintf(p, "%z?%d", zBind, pTab->iRowidBind); + } + zSqlSep = "||', '||"; + zSep = ", "; + } + + for(ii=0; ii<nField; ii++){ + int eHidden = pTab->aCol[ii].eHidden; + if( eHidden!=RECOVER_EHIDDEN_VIRTUAL + && eHidden!=RECOVER_EHIDDEN_STORED + ){ + assert( pTab->aCol[ii].iField>=0 && pTab->aCol[ii].iBind>=1 ); + zSql = recoverMPrintf(p, "%z%s%Q", zSql, zSep, pTab->aCol[ii].zCol); + + if( bSql ){ + zBind = recoverMPrintf(p, + "%z%sescape_crnl(quote(?%d))", zBind, zSqlSep, pTab->aCol[ii].iBind + ); + zSqlSep = "||', '||"; + }else{ + zBind = recoverMPrintf(p, "%z%s?%d", zBind, zSep, pTab->aCol[ii].iBind); + } + zSep = ", "; + } + } + + if( bSql ){ + zFinal = recoverMPrintf(p, "SELECT %Q || ') VALUES (' || %s || ')'", + zSql, zBind + ); + }else{ + zFinal = recoverMPrintf(p, "%s) VALUES (%s)", zSql, zBind); + } + + pRet = recoverPrepare(p, p->dbOut, zFinal); + sqlite3_free(zSql); + sqlite3_free(zBind); + sqlite3_free(zFinal); + + return pRet; +} + + +/* +** Search the list of RecoverTable objects at p->pTblList for one that +** has root page iRoot in the input database. If such an object is found, +** return a pointer to it. Otherwise, return NULL. +*/ +static RecoverTable *recoverFindTable(sqlite3_recover *p, u32 iRoot){ + RecoverTable *pRet = 0; + for(pRet=p->pTblList; pRet && pRet->iRoot!=iRoot; pRet=pRet->pNext); + return pRet; +} + +/* +** This function attempts to create a lost and found table within the +** output db. If successful, it returns a pointer to a buffer containing +** the name of the new table. It is the responsibility of the caller to +** eventually free this buffer using sqlite3_free(). +** +** If an error occurs, NULL is returned and an error code and error +** message left in the recover handle. +*/ +static char *recoverLostAndFoundCreate( + sqlite3_recover *p, /* Recover object */ + int nField /* Number of column fields in new table */ +){ + char *zTbl = 0; + sqlite3_stmt *pProbe = 0; + int ii = 0; + + pProbe = recoverPrepare(p, p->dbOut, + "SELECT 1 FROM sqlite_schema WHERE name=?" + ); + for(ii=-1; zTbl==0 && p->errCode==SQLITE_OK && ii<1000; ii++){ + int bFail = 0; + if( ii<0 ){ + zTbl = recoverMPrintf(p, "%s", p->zLostAndFound); + }else{ + zTbl = recoverMPrintf(p, "%s_%d", p->zLostAndFound, ii); + } + + if( p->errCode==SQLITE_OK ){ + sqlite3_bind_text(pProbe, 1, zTbl, -1, SQLITE_STATIC); + if( SQLITE_ROW==sqlite3_step(pProbe) ){ + bFail = 1; + } + recoverReset(p, pProbe); + } + + if( bFail ){ + sqlite3_clear_bindings(pProbe); + sqlite3_free(zTbl); + zTbl = 0; + } + } + recoverFinalize(p, pProbe); + + if( zTbl ){ + const char *zSep = 0; + char *zField = 0; + char *zSql = 0; + + zSep = "rootpgno INTEGER, pgno INTEGER, nfield INTEGER, id INTEGER, "; + for(ii=0; p->errCode==SQLITE_OK && ii<nField; ii++){ + zField = recoverMPrintf(p, "%z%sc%d", zField, zSep, ii); + zSep = ", "; + } + + zSql = recoverMPrintf(p, "CREATE TABLE %s(%s)", zTbl, zField); + sqlite3_free(zField); + + recoverExec(p, p->dbOut, zSql); + recoverSqlCallback(p, zSql); + sqlite3_free(zSql); + }else if( p->errCode==SQLITE_OK ){ + recoverError( + p, SQLITE_ERROR, "failed to create %s output table", p->zLostAndFound + ); + } + + return zTbl; +} + +/* +** Synthesize and prepare an INSERT statement to write to the lost_and_found +** table in the output database. The name of the table is zTab, and it has +** nField c* fields. +*/ +static sqlite3_stmt *recoverLostAndFoundInsert( + sqlite3_recover *p, + const char *zTab, + int nField +){ + int nTotal = nField + 4; + int ii; + char *zBind = 0; + sqlite3_stmt *pRet = 0; + + if( p->xSql==0 ){ + for(ii=0; ii<nTotal; ii++){ + zBind = recoverMPrintf(p, "%z%s?", zBind, zBind?", ":"", ii); + } + pRet = recoverPreparePrintf( + p, p->dbOut, "INSERT INTO %s VALUES(%s)", zTab, zBind + ); + }else{ + const char *zSep = ""; + for(ii=0; ii<nTotal; ii++){ + zBind = recoverMPrintf(p, "%z%squote(?)", zBind, zSep); + zSep = "|| ', ' ||"; + } + pRet = recoverPreparePrintf( + p, p->dbOut, "SELECT 'INSERT INTO %s VALUES(' || %s || ')'", zTab, zBind + ); + } + + sqlite3_free(zBind); + return pRet; +} + +/* +** Input database page iPg contains data that will be written to the +** lost-and-found table of the output database. This function attempts +** to identify the root page of the tree that page iPg belonged to. +** If successful, it sets output variable (*piRoot) to the page number +** of the root page and returns SQLITE_OK. Otherwise, if an error occurs, +** an SQLite error code is returned and the final value of *piRoot +** undefined. +*/ +static int recoverLostAndFoundFindRoot( + sqlite3_recover *p, + i64 iPg, + i64 *piRoot +){ + RecoverStateLAF *pLaf = &p->laf; + + if( pLaf->pFindRoot==0 ){ + pLaf->pFindRoot = recoverPrepare(p, p->dbOut, + "WITH RECURSIVE p(pgno) AS (" + " SELECT ?" + " UNION" + " SELECT parent FROM recovery.map AS m, p WHERE m.pgno=p.pgno" + ") " + "SELECT p.pgno FROM p, recovery.map m WHERE m.pgno=p.pgno " + " AND m.parent IS NULL" + ); + } + if( p->errCode==SQLITE_OK ){ + sqlite3_bind_int64(pLaf->pFindRoot, 1, iPg); + if( sqlite3_step(pLaf->pFindRoot)==SQLITE_ROW ){ + *piRoot = sqlite3_column_int64(pLaf->pFindRoot, 0); + }else{ + *piRoot = iPg; + } + recoverReset(p, pLaf->pFindRoot); + } + return p->errCode; +} + +/* +** Recover data from page iPage of the input database and write it to +** the lost-and-found table in the output database. +*/ +static void recoverLostAndFoundOnePage(sqlite3_recover *p, i64 iPage){ + RecoverStateLAF *pLaf = &p->laf; + sqlite3_value **apVal = pLaf->apVal; + sqlite3_stmt *pPageData = pLaf->pPageData; + sqlite3_stmt *pInsert = pLaf->pInsert; + + int nVal = -1; + int iPrevCell = 0; + i64 iRoot = 0; + int bHaveRowid = 0; + i64 iRowid = 0; + int ii = 0; + + if( recoverLostAndFoundFindRoot(p, iPage, &iRoot) ) return; + sqlite3_bind_int64(pPageData, 1, iPage); + while( p->errCode==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPageData) ){ + int iCell = sqlite3_column_int64(pPageData, 0); + int iField = sqlite3_column_int64(pPageData, 1); + + if( iPrevCell!=iCell && nVal>=0 ){ + /* Insert the new row */ + sqlite3_bind_int64(pInsert, 1, iRoot); /* rootpgno */ + sqlite3_bind_int64(pInsert, 2, iPage); /* pgno */ + sqlite3_bind_int(pInsert, 3, nVal); /* nfield */ + if( bHaveRowid ){ + sqlite3_bind_int64(pInsert, 4, iRowid); /* id */ + } + for(ii=0; ii<nVal; ii++){ + recoverBindValue(p, pInsert, 5+ii, apVal[ii]); + } + if( sqlite3_step(pInsert)==SQLITE_ROW ){ + recoverSqlCallback(p, (const char*)sqlite3_column_text(pInsert, 0)); + } + recoverReset(p, pInsert); + + /* Discard the accumulated row data */ + for(ii=0; ii<nVal; ii++){ + sqlite3_value_free(apVal[ii]); + apVal[ii] = 0; + } + sqlite3_clear_bindings(pInsert); + bHaveRowid = 0; + nVal = -1; + } + + if( iCell<0 ) break; + + if( iField<0 ){ + assert( nVal==-1 ); + iRowid = sqlite3_column_int64(pPageData, 2); + bHaveRowid = 1; + nVal = 0; + }else if( iField<pLaf->nMaxField ){ + sqlite3_value *pVal = sqlite3_column_value(pPageData, 2); + apVal[iField] = sqlite3_value_dup(pVal); + assert( iField==nVal || (nVal==-1 && iField==0) ); + nVal = iField+1; + if( apVal[iField]==0 ){ + recoverError(p, SQLITE_NOMEM, 0); + } + } + + iPrevCell = iCell; + } + recoverReset(p, pPageData); + + for(ii=0; ii<nVal; ii++){ + sqlite3_value_free(apVal[ii]); + apVal[ii] = 0; + } +} + +/* +** Perform one step (sqlite3_recover_step()) of work for the connection +** passed as the only argument, which is guaranteed to be in +** RECOVER_STATE_LOSTANDFOUND3 state - during which the lost-and-found +** table of the output database is populated with recovered data that can +** not be assigned to any recovered schema object. +*/ +static int recoverLostAndFound3Step(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + if( p->errCode==SQLITE_OK ){ + if( pLaf->pInsert==0 ){ + return SQLITE_DONE; + }else{ + if( p->errCode==SQLITE_OK ){ + int res = sqlite3_step(pLaf->pAllPage); + if( res==SQLITE_ROW ){ + i64 iPage = sqlite3_column_int64(pLaf->pAllPage, 0); + if( recoverBitmapQuery(pLaf->pUsed, iPage)==0 ){ + recoverLostAndFoundOnePage(p, iPage); + } + }else{ + recoverReset(p, pLaf->pAllPage); + return SQLITE_DONE; + } + } + } + } + return SQLITE_OK; +} + +/* +** Initialize resources required in RECOVER_STATE_LOSTANDFOUND3 +** state - during which the lost-and-found table of the output database +** is populated with recovered data that can not be assigned to any +** recovered schema object. +*/ +static void recoverLostAndFound3Init(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + + if( pLaf->nMaxField>0 ){ + char *zTab = 0; /* Name of lost_and_found table */ + + zTab = recoverLostAndFoundCreate(p, pLaf->nMaxField); + pLaf->pInsert = recoverLostAndFoundInsert(p, zTab, pLaf->nMaxField); + sqlite3_free(zTab); + + pLaf->pAllPage = recoverPreparePrintf(p, p->dbOut, + "WITH RECURSIVE seq(ii) AS (" + " SELECT 1 UNION ALL SELECT ii+1 FROM seq WHERE ii<%lld" + ")" + "SELECT ii FROM seq" , p->laf.nPg + ); + pLaf->pPageData = recoverPrepare(p, p->dbOut, + "SELECT cell, field, value " + "FROM sqlite_dbdata('getpage()') d WHERE d.pgno=? " + "UNION ALL " + "SELECT -1, -1, -1" + ); + + pLaf->apVal = (sqlite3_value**)recoverMalloc(p, + pLaf->nMaxField*sizeof(sqlite3_value*) + ); + } +} + +/* +** Initialize resources required in RECOVER_STATE_WRITING state - during which +** tables recovered from the schema of the input database are populated with +** recovered data. +*/ +static int recoverWriteDataInit(sqlite3_recover *p){ + RecoverStateW1 *p1 = &p->w1; + RecoverTable *pTbl = 0; + int nByte = 0; + + /* Figure out the maximum number of columns for any table in the schema */ + assert( p1->nMax==0 ); + for(pTbl=p->pTblList; pTbl; pTbl=pTbl->pNext){ + if( pTbl->nCol>p1->nMax ) p1->nMax = pTbl->nCol; + } + + /* Allocate an array of (sqlite3_value*) in which to accumulate the values + ** that will be written to the output database in a single row. */ + nByte = sizeof(sqlite3_value*) * (p1->nMax+1); + p1->apVal = (sqlite3_value**)recoverMalloc(p, nByte); + if( p1->apVal==0 ) return p->errCode; + + /* Prepare the SELECT to loop through schema tables (pTbls) and the SELECT + ** to loop through cells that appear to belong to a single table (pSel). */ + p1->pTbls = recoverPrepare(p, p->dbOut, + "SELECT rootpage FROM recovery.schema " + " WHERE type='table' AND (sql NOT LIKE 'create virtual%')" + " ORDER BY (tbl_name='sqlite_sequence') ASC" + ); + p1->pSel = recoverPrepare(p, p->dbOut, + "WITH RECURSIVE pages(page) AS (" + " SELECT ?1" + " UNION" + " SELECT child FROM sqlite_dbptr('getpage()'), pages " + " WHERE pgno=page" + ") " + "SELECT page, cell, field, value " + "FROM sqlite_dbdata('getpage()') d, pages p WHERE p.page=d.pgno " + "UNION ALL " + "SELECT 0, 0, 0, 0" + ); + + return p->errCode; +} + +/* +** Clean up resources allocated by recoverWriteDataInit() (stuff in +** sqlite3_recover.w1). +*/ +static void recoverWriteDataCleanup(sqlite3_recover *p){ + RecoverStateW1 *p1 = &p->w1; + int ii; + for(ii=0; ii<p1->nVal; ii++){ + sqlite3_value_free(p1->apVal[ii]); + } + sqlite3_free(p1->apVal); + recoverFinalize(p, p1->pInsert); + recoverFinalize(p, p1->pTbls); + recoverFinalize(p, p1->pSel); + memset(p1, 0, sizeof(*p1)); +} + +/* +** Perform one step (sqlite3_recover_step()) of work for the connection +** passed as the only argument, which is guaranteed to be in +** RECOVER_STATE_WRITING state - during which tables recovered from the +** schema of the input database are populated with recovered data. +*/ +static int recoverWriteDataStep(sqlite3_recover *p){ + RecoverStateW1 *p1 = &p->w1; + sqlite3_stmt *pSel = p1->pSel; + sqlite3_value **apVal = p1->apVal; + + if( p->errCode==SQLITE_OK && p1->pTab==0 ){ + if( sqlite3_step(p1->pTbls)==SQLITE_ROW ){ + i64 iRoot = sqlite3_column_int64(p1->pTbls, 0); + p1->pTab = recoverFindTable(p, iRoot); + + recoverFinalize(p, p1->pInsert); + p1->pInsert = 0; + + /* If this table is unknown, return early. The caller will invoke this + ** function again and it will move on to the next table. */ + if( p1->pTab==0 ) return p->errCode; + + /* If this is the sqlite_sequence table, delete any rows added by + ** earlier INSERT statements on tables with AUTOINCREMENT primary + ** keys before recovering its contents. The p1->pTbls SELECT statement + ** is rigged to deliver "sqlite_sequence" last of all, so we don't + ** worry about it being modified after it is recovered. */ + if( sqlite3_stricmp("sqlite_sequence", p1->pTab->zTab)==0 ){ + recoverExec(p, p->dbOut, "DELETE FROM sqlite_sequence"); + recoverSqlCallback(p, "DELETE FROM sqlite_sequence"); + } + + /* Bind the root page of this table within the original database to + ** SELECT statement p1->pSel. The SELECT statement will then iterate + ** through cells that look like they belong to table pTab. */ + sqlite3_bind_int64(pSel, 1, iRoot); + + p1->nVal = 0; + p1->bHaveRowid = 0; + p1->iPrevPage = -1; + p1->iPrevCell = -1; + }else{ + return SQLITE_DONE; + } + } + assert( p->errCode!=SQLITE_OK || p1->pTab ); + + if( p->errCode==SQLITE_OK && sqlite3_step(pSel)==SQLITE_ROW ){ + RecoverTable *pTab = p1->pTab; + + i64 iPage = sqlite3_column_int64(pSel, 0); + int iCell = sqlite3_column_int(pSel, 1); + int iField = sqlite3_column_int(pSel, 2); + sqlite3_value *pVal = sqlite3_column_value(pSel, 3); + int bNewCell = (p1->iPrevPage!=iPage || p1->iPrevCell!=iCell); + + assert( bNewCell==0 || (iField==-1 || iField==0) ); + assert( bNewCell || iField==p1->nVal || p1->nVal==pTab->nCol ); + + if( bNewCell ){ + int ii = 0; + if( p1->nVal>=0 ){ + if( p1->pInsert==0 || p1->nVal!=p1->nInsert ){ + recoverFinalize(p, p1->pInsert); + p1->pInsert = recoverInsertStmt(p, pTab, p1->nVal); + p1->nInsert = p1->nVal; + } + if( p1->nVal>0 ){ + sqlite3_stmt *pInsert = p1->pInsert; + for(ii=0; ii<pTab->nCol; ii++){ + RecoverColumn *pCol = &pTab->aCol[ii]; + int iBind = pCol->iBind; + if( iBind>0 ){ + if( pCol->bIPK ){ + sqlite3_bind_int64(pInsert, iBind, p1->iRowid); + }else if( pCol->iField<p1->nVal ){ + recoverBindValue(p, pInsert, iBind, apVal[pCol->iField]); + } + } + } + if( p->bRecoverRowid && pTab->iRowidBind>0 && p1->bHaveRowid ){ + sqlite3_bind_int64(pInsert, pTab->iRowidBind, p1->iRowid); + } + if( SQLITE_ROW==sqlite3_step(pInsert) ){ + const char *z = (const char*)sqlite3_column_text(pInsert, 0); + recoverSqlCallback(p, z); + } + recoverReset(p, pInsert); + assert( p->errCode || pInsert ); + if( pInsert ) sqlite3_clear_bindings(pInsert); + } + } + + for(ii=0; ii<p1->nVal; ii++){ + sqlite3_value_free(apVal[ii]); + apVal[ii] = 0; + } + p1->nVal = -1; + p1->bHaveRowid = 0; + } + + if( iPage!=0 ){ + if( iField<0 ){ + p1->iRowid = sqlite3_column_int64(pSel, 3); + assert( p1->nVal==-1 ); + p1->nVal = 0; + p1->bHaveRowid = 1; + }else if( iField<pTab->nCol ){ + assert( apVal[iField]==0 ); + apVal[iField] = sqlite3_value_dup( pVal ); + if( apVal[iField]==0 ){ + recoverError(p, SQLITE_NOMEM, 0); + } + p1->nVal = iField+1; + } + p1->iPrevCell = iCell; + p1->iPrevPage = iPage; + } + }else{ + recoverReset(p, pSel); + p1->pTab = 0; + } + + return p->errCode; +} + +/* +** Initialize resources required by sqlite3_recover_step() in +** RECOVER_STATE_LOSTANDFOUND1 state - during which the set of pages not +** already allocated to a recovered schema element is determined. +*/ +static void recoverLostAndFound1Init(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + sqlite3_stmt *pStmt = 0; + + assert( p->laf.pUsed==0 ); + pLaf->nPg = recoverPageCount(p); + pLaf->pUsed = recoverBitmapAlloc(p, pLaf->nPg); + + /* Prepare a statement to iterate through all pages that are part of any tree + ** in the recoverable part of the input database schema to the bitmap. And, + ** if !p->bFreelistCorrupt, add all pages that appear to be part of the + ** freelist. */ + pStmt = recoverPrepare( + p, p->dbOut, + "WITH trunk(pgno) AS (" + " SELECT read_i32(getpage(1), 8) AS x WHERE x>0" + " UNION" + " SELECT read_i32(getpage(trunk.pgno), 0) AS x FROM trunk WHERE x>0" + ")," + "trunkdata(pgno, data) AS (" + " SELECT pgno, getpage(pgno) FROM trunk" + ")," + "freelist(data, n, freepgno) AS (" + " SELECT data, min(16384, read_i32(data, 1)-1), pgno FROM trunkdata" + " UNION ALL" + " SELECT data, n-1, read_i32(data, 2+n) FROM freelist WHERE n>=0" + ")," + "" + "roots(r) AS (" + " SELECT 1 UNION ALL" + " SELECT rootpage FROM recovery.schema WHERE rootpage>0" + ")," + "used(page) AS (" + " SELECT r FROM roots" + " UNION" + " SELECT child FROM sqlite_dbptr('getpage()'), used " + " WHERE pgno=page" + ") " + "SELECT page FROM used" + " UNION ALL " + "SELECT freepgno FROM freelist WHERE NOT ?" + ); + if( pStmt ) sqlite3_bind_int(pStmt, 1, p->bFreelistCorrupt); + pLaf->pUsedPages = pStmt; +} + +/* +** Perform one step (sqlite3_recover_step()) of work for the connection +** passed as the only argument, which is guaranteed to be in +** RECOVER_STATE_LOSTANDFOUND1 state - during which the set of pages not +** already allocated to a recovered schema element is determined. +*/ +static int recoverLostAndFound1Step(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + int rc = p->errCode; + if( rc==SQLITE_OK ){ + rc = sqlite3_step(pLaf->pUsedPages); + if( rc==SQLITE_ROW ){ + i64 iPg = sqlite3_column_int64(pLaf->pUsedPages, 0); + recoverBitmapSet(pLaf->pUsed, iPg); + rc = SQLITE_OK; + }else{ + recoverFinalize(p, pLaf->pUsedPages); + pLaf->pUsedPages = 0; + } + } + return rc; +} + +/* +** Initialize resources required by RECOVER_STATE_LOSTANDFOUND2 +** state - during which the pages identified in RECOVER_STATE_LOSTANDFOUND1 +** are sorted into sets that likely belonged to the same database tree. +*/ +static void recoverLostAndFound2Init(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + + assert( p->laf.pAllAndParent==0 ); + assert( p->laf.pMapInsert==0 ); + assert( p->laf.pMaxField==0 ); + assert( p->laf.nMaxField==0 ); + + pLaf->pMapInsert = recoverPrepare(p, p->dbOut, + "INSERT OR IGNORE INTO recovery.map(pgno, parent) VALUES(?, ?)" + ); + pLaf->pAllAndParent = recoverPreparePrintf(p, p->dbOut, + "WITH RECURSIVE seq(ii) AS (" + " SELECT 1 UNION ALL SELECT ii+1 FROM seq WHERE ii<%lld" + ")" + "SELECT pgno, child FROM sqlite_dbptr('getpage()') " + " UNION ALL " + "SELECT NULL, ii FROM seq", p->laf.nPg + ); + pLaf->pMaxField = recoverPreparePrintf(p, p->dbOut, + "SELECT max(field)+1 FROM sqlite_dbdata('getpage') WHERE pgno = ?" + ); +} + +/* +** Perform one step (sqlite3_recover_step()) of work for the connection +** passed as the only argument, which is guaranteed to be in +** RECOVER_STATE_LOSTANDFOUND2 state - during which the pages identified +** in RECOVER_STATE_LOSTANDFOUND1 are sorted into sets that likely belonged +** to the same database tree. +*/ +static int recoverLostAndFound2Step(sqlite3_recover *p){ + RecoverStateLAF *pLaf = &p->laf; + if( p->errCode==SQLITE_OK ){ + int res = sqlite3_step(pLaf->pAllAndParent); + if( res==SQLITE_ROW ){ + i64 iChild = sqlite3_column_int(pLaf->pAllAndParent, 1); + if( recoverBitmapQuery(pLaf->pUsed, iChild)==0 ){ + sqlite3_bind_int64(pLaf->pMapInsert, 1, iChild); + sqlite3_bind_value(pLaf->pMapInsert, 2, + sqlite3_column_value(pLaf->pAllAndParent, 0) + ); + sqlite3_step(pLaf->pMapInsert); + recoverReset(p, pLaf->pMapInsert); + sqlite3_bind_int64(pLaf->pMaxField, 1, iChild); + if( SQLITE_ROW==sqlite3_step(pLaf->pMaxField) ){ + int nMax = sqlite3_column_int(pLaf->pMaxField, 0); + if( nMax>pLaf->nMaxField ) pLaf->nMaxField = nMax; + } + recoverReset(p, pLaf->pMaxField); + } + }else{ + recoverFinalize(p, pLaf->pAllAndParent); + pLaf->pAllAndParent =0; + return SQLITE_DONE; + } + } + return p->errCode; +} + +/* +** Free all resources allocated as part of sqlite3_recover_step() calls +** in one of the RECOVER_STATE_LOSTANDFOUND[123] states. +*/ +static void recoverLostAndFoundCleanup(sqlite3_recover *p){ + recoverBitmapFree(p->laf.pUsed); + p->laf.pUsed = 0; + sqlite3_finalize(p->laf.pUsedPages); + sqlite3_finalize(p->laf.pAllAndParent); + sqlite3_finalize(p->laf.pMapInsert); + sqlite3_finalize(p->laf.pMaxField); + sqlite3_finalize(p->laf.pFindRoot); + sqlite3_finalize(p->laf.pInsert); + sqlite3_finalize(p->laf.pAllPage); + sqlite3_finalize(p->laf.pPageData); + p->laf.pUsedPages = 0; + p->laf.pAllAndParent = 0; + p->laf.pMapInsert = 0; + p->laf.pMaxField = 0; + p->laf.pFindRoot = 0; + p->laf.pInsert = 0; + p->laf.pAllPage = 0; + p->laf.pPageData = 0; + sqlite3_free(p->laf.apVal); + p->laf.apVal = 0; +} + +/* +** Free all resources allocated as part of sqlite3_recover_step() calls. +*/ +static void recoverFinalCleanup(sqlite3_recover *p){ + RecoverTable *pTab = 0; + RecoverTable *pNext = 0; + + recoverWriteDataCleanup(p); + recoverLostAndFoundCleanup(p); + + for(pTab=p->pTblList; pTab; pTab=pNext){ + pNext = pTab->pNext; + sqlite3_free(pTab); + } + p->pTblList = 0; + sqlite3_finalize(p->pGetPage); + p->pGetPage = 0; + sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_RESET_CACHE, 0); + + { +#ifndef NDEBUG + int res = +#endif + sqlite3_close(p->dbOut); + assert( res==SQLITE_OK ); + } + p->dbOut = 0; +} + +/* +** Decode and return an unsigned 16-bit big-endian integer value from +** buffer a[]. +*/ +static u32 recoverGetU16(const u8 *a){ + return (((u32)a[0])<<8) + ((u32)a[1]); +} + +/* +** Decode and return an unsigned 32-bit big-endian integer value from +** buffer a[]. +*/ +static u32 recoverGetU32(const u8 *a){ + return (((u32)a[0])<<24) + (((u32)a[1])<<16) + (((u32)a[2])<<8) + ((u32)a[3]); +} + +/* +** Decode an SQLite varint from buffer a[]. Write the decoded value to (*pVal) +** and return the number of bytes consumed. +*/ +static int recoverGetVarint(const u8 *a, i64 *pVal){ + sqlite3_uint64 u = 0; + int i; + for(i=0; i<8; i++){ + u = (u<<7) + (a[i]&0x7f); + if( (a[i]&0x80)==0 ){ *pVal = (sqlite3_int64)u; return i+1; } + } + u = (u<<8) + (a[i]&0xff); + *pVal = (sqlite3_int64)u; + return 9; +} + +/* +** The second argument points to a buffer n bytes in size. If this buffer +** or a prefix thereof appears to contain a well-formed SQLite b-tree page, +** return the page-size in bytes. Otherwise, if the buffer does not +** appear to contain a well-formed b-tree page, return 0. +*/ +static int recoverIsValidPage(u8 *aTmp, const u8 *a, int n){ + u8 *aUsed = aTmp; + int nFrag = 0; + int nActual = 0; + int iFree = 0; + int nCell = 0; /* Number of cells on page */ + int iCellOff = 0; /* Offset of cell array in page */ + int iContent = 0; + int eType = 0; + int ii = 0; + + eType = (int)a[0]; + if( eType!=0x02 && eType!=0x05 && eType!=0x0A && eType!=0x0D ) return 0; + + iFree = (int)recoverGetU16(&a[1]); + nCell = (int)recoverGetU16(&a[3]); + iContent = (int)recoverGetU16(&a[5]); + if( iContent==0 ) iContent = 65536; + nFrag = (int)a[7]; + + if( iContent>n ) return 0; + + memset(aUsed, 0, n); + memset(aUsed, 0xFF, iContent); + + /* Follow the free-list. This is the same format for all b-tree pages. */ + if( iFree && iFree<=iContent ) return 0; + while( iFree ){ + int iNext = 0; + int nByte = 0; + if( iFree>(n-4) ) return 0; + iNext = recoverGetU16(&a[iFree]); + nByte = recoverGetU16(&a[iFree+2]); + if( iFree+nByte>n || nByte<4 ) return 0; + if( iNext && iNext<iFree+nByte ) return 0; + memset(&aUsed[iFree], 0xFF, nByte); + iFree = iNext; + } + + /* Run through the cells */ + if( eType==0x02 || eType==0x05 ){ + iCellOff = 12; + }else{ + iCellOff = 8; + } + if( (iCellOff + 2*nCell)>iContent ) return 0; + for(ii=0; ii<nCell; ii++){ + int iByte; + i64 nPayload = 0; + int nByte = 0; + int iOff = recoverGetU16(&a[iCellOff + 2*ii]); + if( iOff<iContent || iOff>n ){ + return 0; + } + if( eType==0x05 || eType==0x02 ) nByte += 4; + nByte += recoverGetVarint(&a[iOff+nByte], &nPayload); + if( eType==0x0D ){ + i64 dummy = 0; + nByte += recoverGetVarint(&a[iOff+nByte], &dummy); + } + if( eType!=0x05 ){ + int X = (eType==0x0D) ? n-35 : (((n-12)*64/255)-23); + int M = ((n-12)*32/255)-23; + int K = M+((nPayload-M)%(n-4)); + + if( nPayload<X ){ + nByte += nPayload; + }else if( K<=X ){ + nByte += K+4; + }else{ + nByte += M+4; + } + } + + if( iOff+nByte>n ){ + return 0; + } + for(iByte=iOff; iByte<(iOff+nByte); iByte++){ + if( aUsed[iByte]!=0 ){ + return 0; + } + aUsed[iByte] = 0xFF; + } + } + + nActual = 0; + for(ii=0; ii<n; ii++){ + if( aUsed[ii]==0 ) nActual++; + } + return (nActual==nFrag); +} + + +static int recoverVfsClose(sqlite3_file*); +static int recoverVfsRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int recoverVfsWrite(sqlite3_file*, const void*, int, sqlite3_int64); +static int recoverVfsTruncate(sqlite3_file*, sqlite3_int64 size); +static int recoverVfsSync(sqlite3_file*, int flags); +static int recoverVfsFileSize(sqlite3_file*, sqlite3_int64 *pSize); +static int recoverVfsLock(sqlite3_file*, int); +static int recoverVfsUnlock(sqlite3_file*, int); +static int recoverVfsCheckReservedLock(sqlite3_file*, int *pResOut); +static int recoverVfsFileControl(sqlite3_file*, int op, void *pArg); +static int recoverVfsSectorSize(sqlite3_file*); +static int recoverVfsDeviceCharacteristics(sqlite3_file*); +static int recoverVfsShmMap(sqlite3_file*, int, int, int, void volatile**); +static int recoverVfsShmLock(sqlite3_file*, int offset, int n, int flags); +static void recoverVfsShmBarrier(sqlite3_file*); +static int recoverVfsShmUnmap(sqlite3_file*, int deleteFlag); +static int recoverVfsFetch(sqlite3_file*, sqlite3_int64, int, void**); +static int recoverVfsUnfetch(sqlite3_file *pFd, sqlite3_int64 iOff, void *p); + +static sqlite3_io_methods recover_methods = { + 2, /* iVersion */ + recoverVfsClose, + recoverVfsRead, + recoverVfsWrite, + recoverVfsTruncate, + recoverVfsSync, + recoverVfsFileSize, + recoverVfsLock, + recoverVfsUnlock, + recoverVfsCheckReservedLock, + recoverVfsFileControl, + recoverVfsSectorSize, + recoverVfsDeviceCharacteristics, + recoverVfsShmMap, + recoverVfsShmLock, + recoverVfsShmBarrier, + recoverVfsShmUnmap, + recoverVfsFetch, + recoverVfsUnfetch +}; + +static int recoverVfsClose(sqlite3_file *pFd){ + assert( pFd->pMethods!=&recover_methods ); + return pFd->pMethods->xClose(pFd); +} + +/* +** Write value v to buffer a[] as a 16-bit big-endian unsigned integer. +*/ +static void recoverPutU16(u8 *a, u32 v){ + a[0] = (v>>8) & 0x00FF; + a[1] = (v>>0) & 0x00FF; +} + +/* +** Write value v to buffer a[] as a 32-bit big-endian unsigned integer. +*/ +static void recoverPutU32(u8 *a, u32 v){ + a[0] = (v>>24) & 0x00FF; + a[1] = (v>>16) & 0x00FF; + a[2] = (v>>8) & 0x00FF; + a[3] = (v>>0) & 0x00FF; +} + +/* +** Detect the page-size of the database opened by file-handle pFd by +** searching the first part of the file for a well-formed SQLite b-tree +** page. If parameter nReserve is non-zero, then as well as searching for +** a b-tree page with zero reserved bytes, this function searches for one +** with nReserve reserved bytes at the end of it. +** +** If successful, set variable p->detected_pgsz to the detected page-size +** in bytes and return SQLITE_OK. Or, if no error occurs but no valid page +** can be found, return SQLITE_OK but leave p->detected_pgsz set to 0. Or, +** if an error occurs (e.g. an IO or OOM error), then an SQLite error code +** is returned. The final value of p->detected_pgsz is undefined in this +** case. +*/ +static int recoverVfsDetectPagesize( + sqlite3_recover *p, /* Recover handle */ + sqlite3_file *pFd, /* File-handle open on input database */ + u32 nReserve, /* Possible nReserve value */ + i64 nSz /* Size of database file in bytes */ +){ + int rc = SQLITE_OK; + const int nMin = 512; + const int nMax = 65536; + const int nMaxBlk = 4; + u32 pgsz = 0; + int iBlk = 0; + u8 *aPg = 0; + u8 *aTmp = 0; + int nBlk = 0; + + aPg = (u8*)sqlite3_malloc(2*nMax); + if( aPg==0 ) return SQLITE_NOMEM; + aTmp = &aPg[nMax]; + + nBlk = (nSz+nMax-1)/nMax; + if( nBlk>nMaxBlk ) nBlk = nMaxBlk; + + do { + for(iBlk=0; rc==SQLITE_OK && iBlk<nBlk; iBlk++){ + int nByte = (nSz>=((iBlk+1)*nMax)) ? nMax : (nSz % nMax); + memset(aPg, 0, nMax); + rc = pFd->pMethods->xRead(pFd, aPg, nByte, iBlk*nMax); + if( rc==SQLITE_OK ){ + int pgsz2; + for(pgsz2=(pgsz ? pgsz*2 : nMin); pgsz2<=nMax; pgsz2=pgsz2*2){ + int iOff; + for(iOff=0; iOff<nMax; iOff+=pgsz2){ + if( recoverIsValidPage(aTmp, &aPg[iOff], pgsz2-nReserve) ){ + pgsz = pgsz2; + break; + } + } + } + } + } + if( pgsz>(u32)p->detected_pgsz ){ + p->detected_pgsz = pgsz; + p->nReserve = nReserve; + } + if( nReserve==0 ) break; + nReserve = 0; + }while( 1 ); + + p->detected_pgsz = pgsz; + sqlite3_free(aPg); + return rc; +} + +/* +** The xRead() method of the wrapper VFS. This is used to intercept calls +** to read page 1 of the input database. +*/ +static int recoverVfsRead(sqlite3_file *pFd, void *aBuf, int nByte, i64 iOff){ + int rc = SQLITE_OK; + if( pFd->pMethods==&recover_methods ){ + pFd->pMethods = recover_g.pMethods; + rc = pFd->pMethods->xRead(pFd, aBuf, nByte, iOff); + if( nByte==16 ){ + sqlite3_randomness(16, aBuf); + }else + if( rc==SQLITE_OK && iOff==0 && nByte>=108 ){ + /* Ensure that the database has a valid header file. The only fields + ** that really matter to recovery are: + ** + ** + Database page size (16-bits at offset 16) + ** + Size of db in pages (32-bits at offset 28) + ** + Database encoding (32-bits at offset 56) + ** + ** Also preserved are: + ** + ** + first freelist page (32-bits at offset 32) + ** + size of freelist (32-bits at offset 36) + ** + the wal-mode flags (16-bits at offset 18) + ** + ** We also try to preserve the auto-vacuum, incr-value, user-version + ** and application-id fields - all 32 bit quantities at offsets + ** 52, 60, 64 and 68. All other fields are set to known good values. + ** + ** Byte offset 105 should also contain the page-size as a 16-bit + ** integer. + */ + const int aPreserve[] = {32, 36, 52, 60, 64, 68}; + u8 aHdr[108] = { + 0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00, + 0xFF, 0xFF, 0x01, 0x01, 0x00, 0x40, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x10, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x2e, 0x5b, 0x30, + + 0x0D, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00 + }; + u8 *a = (u8*)aBuf; + + u32 pgsz = recoverGetU16(&a[16]); + u32 nReserve = a[20]; + u32 enc = recoverGetU32(&a[56]); + u32 dbsz = 0; + i64 dbFileSize = 0; + int ii; + sqlite3_recover *p = recover_g.p; + + if( pgsz==0x01 ) pgsz = 65536; + rc = pFd->pMethods->xFileSize(pFd, &dbFileSize); + + if( rc==SQLITE_OK && p->detected_pgsz==0 ){ + rc = recoverVfsDetectPagesize(p, pFd, nReserve, dbFileSize); + } + if( p->detected_pgsz ){ + pgsz = p->detected_pgsz; + nReserve = p->nReserve; + } + + if( pgsz ){ + dbsz = dbFileSize / pgsz; + } + if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF16BE && enc!=SQLITE_UTF16LE ){ + enc = SQLITE_UTF8; + } + + sqlite3_free(p->pPage1Cache); + p->pPage1Cache = 0; + p->pPage1Disk = 0; + + p->pgsz = nByte; + p->pPage1Cache = (u8*)recoverMalloc(p, nByte*2); + if( p->pPage1Cache ){ + p->pPage1Disk = &p->pPage1Cache[nByte]; + memcpy(p->pPage1Disk, aBuf, nByte); + aHdr[18] = a[18]; + aHdr[19] = a[19]; + recoverPutU32(&aHdr[28], dbsz); + recoverPutU32(&aHdr[56], enc); + recoverPutU16(&aHdr[105], pgsz-nReserve); + if( pgsz==65536 ) pgsz = 1; + recoverPutU16(&aHdr[16], pgsz); + aHdr[20] = nReserve; + for(ii=0; ii<(int)(sizeof(aPreserve)/sizeof(aPreserve[0])); ii++){ + memcpy(&aHdr[aPreserve[ii]], &a[aPreserve[ii]], 4); + } + memcpy(aBuf, aHdr, sizeof(aHdr)); + memset(&((u8*)aBuf)[sizeof(aHdr)], 0, nByte-sizeof(aHdr)); + + memcpy(p->pPage1Cache, aBuf, nByte); + }else{ + rc = p->errCode; + } + + } + pFd->pMethods = &recover_methods; + }else{ + rc = pFd->pMethods->xRead(pFd, aBuf, nByte, iOff); + } + return rc; +} + +/* +** Used to make sqlite3_io_methods wrapper methods less verbose. +*/ +#define RECOVER_VFS_WRAPPER(code) \ + int rc = SQLITE_OK; \ + if( pFd->pMethods==&recover_methods ){ \ + pFd->pMethods = recover_g.pMethods; \ + rc = code; \ + pFd->pMethods = &recover_methods; \ + }else{ \ + rc = code; \ + } \ + return rc; + +/* +** Methods of the wrapper VFS. All methods except for xRead() and xClose() +** simply uninstall the sqlite3_io_methods wrapper, invoke the equivalent +** method on the lower level VFS, then reinstall the wrapper before returning. +** Those that return an integer value use the RECOVER_VFS_WRAPPER macro. +*/ +static int recoverVfsWrite( + sqlite3_file *pFd, const void *aBuf, int nByte, i64 iOff +){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xWrite(pFd, aBuf, nByte, iOff) + ); +} +static int recoverVfsTruncate(sqlite3_file *pFd, sqlite3_int64 size){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xTruncate(pFd, size) + ); +} +static int recoverVfsSync(sqlite3_file *pFd, int flags){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xSync(pFd, flags) + ); +} +static int recoverVfsFileSize(sqlite3_file *pFd, sqlite3_int64 *pSize){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xFileSize(pFd, pSize) + ); +} +static int recoverVfsLock(sqlite3_file *pFd, int eLock){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xLock(pFd, eLock) + ); +} +static int recoverVfsUnlock(sqlite3_file *pFd, int eLock){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xUnlock(pFd, eLock) + ); +} +static int recoverVfsCheckReservedLock(sqlite3_file *pFd, int *pResOut){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xCheckReservedLock(pFd, pResOut) + ); +} +static int recoverVfsFileControl(sqlite3_file *pFd, int op, void *pArg){ + RECOVER_VFS_WRAPPER ( + (pFd->pMethods ? pFd->pMethods->xFileControl(pFd, op, pArg) : SQLITE_NOTFOUND) + ); +} +static int recoverVfsSectorSize(sqlite3_file *pFd){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xSectorSize(pFd) + ); +} +static int recoverVfsDeviceCharacteristics(sqlite3_file *pFd){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xDeviceCharacteristics(pFd) + ); +} +static int recoverVfsShmMap( + sqlite3_file *pFd, int iPg, int pgsz, int bExtend, void volatile **pp +){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xShmMap(pFd, iPg, pgsz, bExtend, pp) + ); +} +static int recoverVfsShmLock(sqlite3_file *pFd, int offset, int n, int flags){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xShmLock(pFd, offset, n, flags) + ); +} +static void recoverVfsShmBarrier(sqlite3_file *pFd){ + if( pFd->pMethods==&recover_methods ){ + pFd->pMethods = recover_g.pMethods; + pFd->pMethods->xShmBarrier(pFd); + pFd->pMethods = &recover_methods; + }else{ + pFd->pMethods->xShmBarrier(pFd); + } +} +static int recoverVfsShmUnmap(sqlite3_file *pFd, int deleteFlag){ + RECOVER_VFS_WRAPPER ( + pFd->pMethods->xShmUnmap(pFd, deleteFlag) + ); +} + +static int recoverVfsFetch( + sqlite3_file *pFd, + sqlite3_int64 iOff, + int iAmt, + void **pp +){ + (void)pFd; + (void)iOff; + (void)iAmt; + *pp = 0; + return SQLITE_OK; +} +static int recoverVfsUnfetch(sqlite3_file *pFd, sqlite3_int64 iOff, void *p){ + (void)pFd; + (void)iOff; + (void)p; + return SQLITE_OK; +} + +/* +** Install the VFS wrapper around the file-descriptor open on the input +** database for recover handle p. Mutex RECOVER_MUTEX_ID must be held +** when this function is called. +*/ +static void recoverInstallWrapper(sqlite3_recover *p){ + sqlite3_file *pFd = 0; + assert( recover_g.pMethods==0 ); + recoverAssertMutexHeld(); + sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_FILE_POINTER, (void*)&pFd); + assert( pFd==0 || pFd->pMethods!=&recover_methods ); + if( pFd && pFd->pMethods ){ + int iVersion = 1 + (pFd->pMethods->iVersion>1 && pFd->pMethods->xShmMap!=0); + recover_g.pMethods = pFd->pMethods; + recover_g.p = p; + recover_methods.iVersion = iVersion; + pFd->pMethods = &recover_methods; + } +} + +/* +** Uninstall the VFS wrapper that was installed around the file-descriptor open +** on the input database for recover handle p. Mutex RECOVER_MUTEX_ID must be +** held when this function is called. +*/ +static void recoverUninstallWrapper(sqlite3_recover *p){ + sqlite3_file *pFd = 0; + recoverAssertMutexHeld(); + sqlite3_file_control(p->dbIn, p->zDb,SQLITE_FCNTL_FILE_POINTER,(void*)&pFd); + if( pFd && pFd->pMethods ){ + pFd->pMethods = recover_g.pMethods; + recover_g.pMethods = 0; + recover_g.p = 0; + } +} + +/* +** This function does the work of a single sqlite3_recover_step() call. It +** is guaranteed that the handle is not in an error state when this +** function is called. +*/ +static void recoverStep(sqlite3_recover *p){ + assert( p && p->errCode==SQLITE_OK ); + switch( p->eState ){ + case RECOVER_STATE_INIT: + /* This is the very first call to sqlite3_recover_step() on this object. + */ + recoverSqlCallback(p, "BEGIN"); + recoverSqlCallback(p, "PRAGMA writable_schema = on"); + + recoverEnterMutex(); + recoverInstallWrapper(p); + + /* Open the output database. And register required virtual tables and + ** user functions with the new handle. */ + recoverOpenOutput(p); + + /* Open transactions on both the input and output databases. */ + sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_RESET_CACHE, 0); + recoverExec(p, p->dbIn, "PRAGMA writable_schema = on"); + recoverExec(p, p->dbIn, "BEGIN"); + if( p->errCode==SQLITE_OK ) p->bCloseTransaction = 1; + recoverExec(p, p->dbIn, "SELECT 1 FROM sqlite_schema"); + recoverTransferSettings(p); + recoverOpenRecovery(p); + recoverCacheSchema(p); + + recoverUninstallWrapper(p); + recoverLeaveMutex(); + + recoverExec(p, p->dbOut, "BEGIN"); + + recoverWriteSchema1(p); + p->eState = RECOVER_STATE_WRITING; + break; + + case RECOVER_STATE_WRITING: { + if( p->w1.pTbls==0 ){ + recoverWriteDataInit(p); + } + if( SQLITE_DONE==recoverWriteDataStep(p) ){ + recoverWriteDataCleanup(p); + if( p->zLostAndFound ){ + p->eState = RECOVER_STATE_LOSTANDFOUND1; + }else{ + p->eState = RECOVER_STATE_SCHEMA2; + } + } + break; + } + + case RECOVER_STATE_LOSTANDFOUND1: { + if( p->laf.pUsed==0 ){ + recoverLostAndFound1Init(p); + } + if( SQLITE_DONE==recoverLostAndFound1Step(p) ){ + p->eState = RECOVER_STATE_LOSTANDFOUND2; + } + break; + } + case RECOVER_STATE_LOSTANDFOUND2: { + if( p->laf.pAllAndParent==0 ){ + recoverLostAndFound2Init(p); + } + if( SQLITE_DONE==recoverLostAndFound2Step(p) ){ + p->eState = RECOVER_STATE_LOSTANDFOUND3; + } + break; + } + + case RECOVER_STATE_LOSTANDFOUND3: { + if( p->laf.pInsert==0 ){ + recoverLostAndFound3Init(p); + } + if( SQLITE_DONE==recoverLostAndFound3Step(p) ){ + p->eState = RECOVER_STATE_SCHEMA2; + } + break; + } + + case RECOVER_STATE_SCHEMA2: { + int rc = SQLITE_OK; + + recoverWriteSchema2(p); + p->eState = RECOVER_STATE_DONE; + + /* If no error has occurred, commit the write transaction on the output + ** database. Regardless of whether or not an error has occurred, make + ** an attempt to end the read transaction on the input database. */ + recoverExec(p, p->dbOut, "COMMIT"); + rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0); + if( p->errCode==SQLITE_OK ) p->errCode = rc; + + recoverSqlCallback(p, "PRAGMA writable_schema = off"); + recoverSqlCallback(p, "COMMIT"); + p->eState = RECOVER_STATE_DONE; + recoverFinalCleanup(p); + break; + }; + + case RECOVER_STATE_DONE: { + /* no-op */ + break; + }; + } +} + + +/* +** This is a worker function that does the heavy lifting for both init +** functions: +** +** sqlite3_recover_init() +** sqlite3_recover_init_sql() +** +** All this function does is allocate space for the recover handle and +** take copies of the input parameters. All the real work is done within +** sqlite3_recover_run(). +*/ +sqlite3_recover *recoverInit( + sqlite3* db, + const char *zDb, + const char *zUri, /* Output URI for _recover_init() */ + int (*xSql)(void*, const char*),/* SQL callback for _recover_init_sql() */ + void *pSqlCtx /* Context arg for _recover_init_sql() */ +){ + sqlite3_recover *pRet = 0; + int nDb = 0; + int nUri = 0; + int nByte = 0; + + if( zDb==0 ){ zDb = "main"; } + + nDb = recoverStrlen(zDb); + nUri = recoverStrlen(zUri); + + nByte = sizeof(sqlite3_recover) + nDb+1 + nUri+1; + pRet = (sqlite3_recover*)sqlite3_malloc(nByte); + if( pRet ){ + memset(pRet, 0, nByte); + pRet->dbIn = db; + pRet->zDb = (char*)&pRet[1]; + pRet->zUri = &pRet->zDb[nDb+1]; + memcpy(pRet->zDb, zDb, nDb); + if( nUri>0 && zUri ) memcpy(pRet->zUri, zUri, nUri); + pRet->xSql = xSql; + pRet->pSqlCtx = pSqlCtx; + pRet->bRecoverRowid = RECOVER_ROWID_DEFAULT; + } + + return pRet; +} + +/* +** Initialize a recovery handle that creates a new database containing +** the recovered data. +*/ +sqlite3_recover *sqlite3_recover_init( + sqlite3* db, + const char *zDb, + const char *zUri +){ + return recoverInit(db, zDb, zUri, 0, 0); +} + +/* +** Initialize a recovery handle that returns recovered data in the +** form of SQL statements via a callback. +*/ +sqlite3_recover *sqlite3_recover_init_sql( + sqlite3* db, + const char *zDb, + int (*xSql)(void*, const char*), + void *pSqlCtx +){ + return recoverInit(db, zDb, 0, xSql, pSqlCtx); +} + +/* +** Return the handle error message, if any. +*/ +const char *sqlite3_recover_errmsg(sqlite3_recover *p){ + return (p && p->errCode!=SQLITE_NOMEM) ? p->zErrMsg : "out of memory"; +} + +/* +** Return the handle error code. +*/ +int sqlite3_recover_errcode(sqlite3_recover *p){ + return p ? p->errCode : SQLITE_NOMEM; +} + +/* +** Configure the handle. +*/ +int sqlite3_recover_config(sqlite3_recover *p, int op, void *pArg){ + int rc = SQLITE_OK; + if( p==0 ){ + rc = SQLITE_NOMEM; + }else if( p->eState!=RECOVER_STATE_INIT ){ + rc = SQLITE_MISUSE; + }else{ + switch( op ){ + case 789: + /* This undocumented magic configuration option is used to set the + ** name of the auxiliary database that is ATTACH-ed to the database + ** connection and used to hold state information during the + ** recovery process. This option is for debugging use only and + ** is subject to change or removal at any time. */ + sqlite3_free(p->zStateDb); + p->zStateDb = recoverMPrintf(p, "%s", (char*)pArg); + break; + + case SQLITE_RECOVER_LOST_AND_FOUND: { + const char *zArg = (const char*)pArg; + sqlite3_free(p->zLostAndFound); + if( zArg ){ + p->zLostAndFound = recoverMPrintf(p, "%s", zArg); + }else{ + p->zLostAndFound = 0; + } + break; + } + + case SQLITE_RECOVER_FREELIST_CORRUPT: + p->bFreelistCorrupt = *(int*)pArg; + break; + + case SQLITE_RECOVER_ROWIDS: + p->bRecoverRowid = *(int*)pArg; + break; + + case SQLITE_RECOVER_SLOWINDEXES: + p->bSlowIndexes = *(int*)pArg; + break; + + default: + rc = SQLITE_NOTFOUND; + break; + } + } + + return rc; +} + +/* +** Do a unit of work towards the recovery job. Return SQLITE_OK if +** no error has occurred but database recovery is not finished, SQLITE_DONE +** if database recovery has been successfully completed, or an SQLite +** error code if an error has occurred. +*/ +int sqlite3_recover_step(sqlite3_recover *p){ + if( p==0 ) return SQLITE_NOMEM; + if( p->errCode==SQLITE_OK ) recoverStep(p); + if( p->eState==RECOVER_STATE_DONE && p->errCode==SQLITE_OK ){ + return SQLITE_DONE; + } + return p->errCode; +} + +/* +** Do the configured recovery operation. Return SQLITE_OK if successful, or +** else an SQLite error code. +*/ +int sqlite3_recover_run(sqlite3_recover *p){ + while( SQLITE_OK==sqlite3_recover_step(p) ); + return sqlite3_recover_errcode(p); +} + + +/* +** Free all resources associated with the recover handle passed as the only +** argument. The results of using a handle with any sqlite3_recover_** +** API function after it has been passed to this function are undefined. +** +** A copy of the value returned by the first call made to sqlite3_recover_run() +** on this handle is returned, or SQLITE_OK if sqlite3_recover_run() has +** not been called on this handle. +*/ +int sqlite3_recover_finish(sqlite3_recover *p){ + int rc; + if( p==0 ){ + rc = SQLITE_NOMEM; + }else{ + recoverFinalCleanup(p); + if( p->bCloseTransaction && sqlite3_get_autocommit(p->dbIn)==0 ){ + rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0); + if( p->errCode==SQLITE_OK ) p->errCode = rc; + } + rc = p->errCode; + sqlite3_free(p->zErrMsg); + sqlite3_free(p->zStateDb); + sqlite3_free(p->zLostAndFound); + sqlite3_free(p->pPage1Cache); + sqlite3_free(p); + } + return rc; +} + +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + +/************************* End ../ext/recover/sqlite3recover.c ********************/ +# endif /* SQLITE_HAVE_SQLITE3R */ +#endif +#ifdef SQLITE_SHELL_EXTSRC +# include SHELL_STRINGIFY(SQLITE_SHELL_EXTSRC) +#endif + +#if defined(SQLITE_ENABLE_SESSION) +/* +** State information for a single open session +*/ +typedef struct OpenSession OpenSession; +struct OpenSession { + char *zName; /* Symbolic name for this session */ + int nFilter; /* Number of xFilter rejection GLOB patterns */ + char **azFilter; /* Array of xFilter rejection GLOB patterns */ + sqlite3_session *p; /* The open session */ +}; +#endif + +typedef struct ExpertInfo ExpertInfo; +struct ExpertInfo { + sqlite3expert *pExpert; + int bVerbose; +}; + +/* A single line in the EQP output */ +typedef struct EQPGraphRow EQPGraphRow; +struct EQPGraphRow { + int iEqpId; /* ID for this row */ + int iParentId; /* ID of the parent row */ + EQPGraphRow *pNext; /* Next row in sequence */ + char zText[1]; /* Text to display for this row */ +}; + +/* All EQP output is collected into an instance of the following */ +typedef struct EQPGraph EQPGraph; +struct EQPGraph { + EQPGraphRow *pRow; /* Linked list of all rows of the EQP output */ + EQPGraphRow *pLast; /* Last element of the pRow list */ + char zPrefix[100]; /* Graph prefix */ +}; + +/* Parameters affecting columnar mode result display (defaulting together) */ +typedef struct ColModeOpts { + int iWrap; /* In columnar modes, wrap lines reaching this limit */ + u8 bQuote; /* Quote results for .mode box and table */ + u8 bWordWrap; /* In columnar modes, wrap at word boundaries */ +} ColModeOpts; +#define ColModeOpts_default { 60, 0, 0 } +#define ColModeOpts_default_qbox { 60, 1, 0 } + +/* +** State information about the database connection is contained in an +** instance of the following structure. +*/ +typedef struct ShellState ShellState; +struct ShellState { + sqlite3 *db; /* The database */ + u8 autoExplain; /* Automatically turn on .explain mode */ + u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */ + u8 autoEQPtest; /* autoEQP is in test mode */ + u8 autoEQPtrace; /* autoEQP is in trace mode */ + u8 scanstatsOn; /* True to display scan stats before each finalize */ + u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */ + u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */ + u8 nEqpLevel; /* Depth of the EQP output graph */ + u8 eTraceType; /* SHELL_TRACE_* value for type of trace */ + u8 bSafeMode; /* True to prohibit unsafe operations */ + u8 bSafeModePersist; /* The long-term value of bSafeMode */ + ColModeOpts cmOpts; /* Option values affecting columnar mode output */ + unsigned statsOn; /* True to display memory stats before each finalize */ + unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */ + int inputNesting; /* Track nesting level of .read and other redirects */ + int outCount; /* Revert to stdout when reaching zero */ + int cnt; /* Number of records displayed so far */ + int lineno; /* Line number of last line read from in */ + int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */ + FILE *in; /* Read commands from this stream */ + FILE *out; /* Write results here */ + FILE *traceOut; /* Output for sqlite3_trace() */ + int nErr; /* Number of errors seen */ + int mode; /* An output mode setting */ + int modePrior; /* Saved mode */ + int cMode; /* temporary output mode for the current query */ + int normalMode; /* Output mode before ".explain on" */ + int writableSchema; /* True if PRAGMA writable_schema=ON */ + int showHeader; /* True to show column names in List or Column mode */ + int nCheck; /* Number of ".check" commands run */ + unsigned nProgress; /* Number of progress callbacks encountered */ + unsigned mxProgress; /* Maximum progress callbacks before failing */ + unsigned flgProgress; /* Flags for the progress callback */ + unsigned shellFlgs; /* Various flags */ + unsigned priorShFlgs; /* Saved copy of flags */ + sqlite3_int64 szMax; /* --maxsize argument to .open */ + char *zDestTable; /* Name of destination table when MODE_Insert */ + char *zTempFile; /* Temporary file that might need deleting */ + char zTestcase[30]; /* Name of current test case */ + char colSeparator[20]; /* Column separator character for several modes */ + char rowSeparator[20]; /* Row separator character for MODE_Ascii */ + char colSepPrior[20]; /* Saved column separator */ + char rowSepPrior[20]; /* Saved row separator */ + int *colWidth; /* Requested width of each column in columnar modes */ + int *actualWidth; /* Actual width of each column */ + int nWidth; /* Number of slots in colWidth[] and actualWidth[] */ + char nullValue[20]; /* The text to print when a NULL comes back from + ** the database */ + char outfile[FILENAME_MAX]; /* Filename for *out */ + sqlite3_stmt *pStmt; /* Current statement if any. */ + FILE *pLog; /* Write log output here */ + struct AuxDb { /* Storage space for auxiliary database connections */ + sqlite3 *db; /* Connection pointer */ + const char *zDbFilename; /* Filename used to open the connection */ + char *zFreeOnClose; /* Free this memory allocation on close */ +#if defined(SQLITE_ENABLE_SESSION) + int nSession; /* Number of active sessions */ + OpenSession aSession[4]; /* Array of sessions. [0] is in focus. */ +#endif + } aAuxDb[5], /* Array of all database connections */ + *pAuxDb; /* Currently active database connection */ + int *aiIndent; /* Array of indents used in MODE_Explain */ + int nIndent; /* Size of array aiIndent[] */ + int iIndent; /* Index of current op in aiIndent[] */ + char *zNonce; /* Nonce for temporary safe-mode escapes */ + EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ + ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ +#ifdef SQLITE_SHELL_FIDDLE + struct { + const char * zInput; /* Input string from wasm/JS proxy */ + const char * zPos; /* Cursor pos into zInput */ + const char * zDefaultDbName; /* Default name for db file */ + } wasm; +#endif +}; + +#ifdef SQLITE_SHELL_FIDDLE +static ShellState shellState; +#endif + + +/* Allowed values for ShellState.autoEQP +*/ +#define AUTOEQP_off 0 /* Automatic EXPLAIN QUERY PLAN is off */ +#define AUTOEQP_on 1 /* Automatic EQP is on */ +#define AUTOEQP_trigger 2 /* On and also show plans for triggers */ +#define AUTOEQP_full 3 /* Show full EXPLAIN */ + +/* Allowed values for ShellState.openMode +*/ +#define SHELL_OPEN_UNSPEC 0 /* No open-mode specified */ +#define SHELL_OPEN_NORMAL 1 /* Normal database file */ +#define SHELL_OPEN_APPENDVFS 2 /* Use appendvfs */ +#define SHELL_OPEN_ZIPFILE 3 /* Use the zipfile virtual table */ +#define SHELL_OPEN_READONLY 4 /* Open a normal database read-only */ +#define SHELL_OPEN_DESERIALIZE 5 /* Open using sqlite3_deserialize() */ +#define SHELL_OPEN_HEXDB 6 /* Use "dbtotxt" output as data source */ + +/* Allowed values for ShellState.eTraceType +*/ +#define SHELL_TRACE_PLAIN 0 /* Show input SQL text */ +#define SHELL_TRACE_EXPANDED 1 /* Show expanded SQL text */ +#define SHELL_TRACE_NORMALIZED 2 /* Show normalized SQL text */ + +/* Bits in the ShellState.flgProgress variable */ +#define SHELL_PROGRESS_QUIET 0x01 /* Omit announcing every progress callback */ +#define SHELL_PROGRESS_RESET 0x02 /* Reset the count when the progress + ** callback limit is reached, and for each + ** top-level SQL statement */ +#define SHELL_PROGRESS_ONCE 0x04 /* Cancel the --limit after firing once */ + +/* +** These are the allowed shellFlgs values +*/ +#define SHFLG_Pagecache 0x00000001 /* The --pagecache option is used */ +#define SHFLG_Lookaside 0x00000002 /* Lookaside memory is used */ +#define SHFLG_Backslash 0x00000004 /* The --backslash option is used */ +#define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */ +#define SHFLG_Newlines 0x00000010 /* .dump --newline flag */ +#define SHFLG_CountChanges 0x00000020 /* .changes setting */ +#define SHFLG_Echo 0x00000040 /* .echo on/off, or --echo setting */ +#define SHFLG_HeaderSet 0x00000080 /* showHeader has been specified */ +#define SHFLG_DumpDataOnly 0x00000100 /* .dump show data only */ +#define SHFLG_DumpNoSys 0x00000200 /* .dump omits system tables */ +#define SHFLG_TestingMode 0x00000400 /* allow unsafe testing features */ + +/* +** Macros for testing and setting shellFlgs +*/ +#define ShellHasFlag(P,X) (((P)->shellFlgs & (X))!=0) +#define ShellSetFlag(P,X) ((P)->shellFlgs|=(X)) +#define ShellClearFlag(P,X) ((P)->shellFlgs&=(~(X))) + +/* +** These are the allowed modes. +*/ +#define MODE_Line 0 /* One column per line. Blank line between records */ +#define MODE_Column 1 /* One record per line in neat columns */ +#define MODE_List 2 /* One record per line with a separator */ +#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */ +#define MODE_Html 4 /* Generate an XHTML table */ +#define MODE_Insert 5 /* Generate SQL "insert" statements */ +#define MODE_Quote 6 /* Quote values as for SQL */ +#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */ +#define MODE_Csv 8 /* Quote strings, numbers are plain */ +#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */ +#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */ +#define MODE_Pretty 11 /* Pretty-print schemas */ +#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */ +#define MODE_Json 13 /* Output JSON */ +#define MODE_Markdown 14 /* Markdown formatting */ +#define MODE_Table 15 /* MySQL-style table formatting */ +#define MODE_Box 16 /* Unicode box-drawing characters */ +#define MODE_Count 17 /* Output only a count of the rows of output */ +#define MODE_Off 18 /* No query output shown */ +#define MODE_ScanExp 19 /* Like MODE_Explain, but for ".scanstats vm" */ + +static const char *modeDescr[] = { + "line", + "column", + "list", + "semi", + "html", + "insert", + "quote", + "tcl", + "csv", + "explain", + "ascii", + "prettyprint", + "eqp", + "json", + "markdown", + "table", + "box", + "count", + "off" +}; + +/* +** These are the column/row/line separators used by the various +** import/export modes. +*/ +#define SEP_Column "|" +#define SEP_Row "\n" +#define SEP_Tab "\t" +#define SEP_Space " " +#define SEP_Comma "," +#define SEP_CrLf "\r\n" +#define SEP_Unit "\x1F" +#define SEP_Record "\x1E" + +/* +** Limit input nesting via .read or any other input redirect. +** It's not too expensive, so a generous allowance can be made. +*/ +#define MAX_INPUT_NESTING 25 + +/* +** A callback for the sqlite3_log() interface. +*/ +static void shellLog(void *pArg, int iErrCode, const char *zMsg){ + ShellState *p = (ShellState*)pArg; + if( p->pLog==0 ) return; + utf8_printf(p->pLog, "(%d) %s\n", iErrCode, zMsg); + fflush(p->pLog); +} + +/* +** SQL function: shell_putsnl(X) +** +** Write the text X to the screen (or whatever output is being directed) +** adding a newline at the end, and then return X. +*/ +static void shellPutsFunc( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + ShellState *p = (ShellState*)sqlite3_user_data(pCtx); + (void)nVal; + utf8_printf(p->out, "%s\n", sqlite3_value_text(apVal[0])); + sqlite3_result_value(pCtx, apVal[0]); +} + +/* +** If in safe mode, print an error message described by the arguments +** and exit immediately. +*/ +static void failIfSafeMode( + ShellState *p, + const char *zErrMsg, + ... +){ + if( p->bSafeMode ){ + va_list ap; + char *zMsg; + va_start(ap, zErrMsg); + zMsg = sqlite3_vmprintf(zErrMsg, ap); + va_end(ap); + raw_printf(stderr, "line %d: ", p->lineno); + utf8_printf(stderr, "%s\n", zMsg); + exit(1); + } +} + +/* +** SQL function: edit(VALUE) +** edit(VALUE,EDITOR) +** +** These steps: +** +** (1) Write VALUE into a temporary file. +** (2) Run program EDITOR on that temporary file. +** (3) Read the temporary file back and return its content as the result. +** (4) Delete the temporary file +** +** If the EDITOR argument is omitted, use the value in the VISUAL +** environment variable. If still there is no EDITOR, through an error. +** +** Also throw an error if the EDITOR program returns a non-zero exit code. +*/ +#ifndef SQLITE_NOHAVE_SYSTEM +static void editFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zEditor; + char *zTempFile = 0; + sqlite3 *db; + char *zCmd = 0; + int bBin; + int rc; + int hasCRNL = 0; + FILE *f = 0; + sqlite3_int64 sz; + sqlite3_int64 x; + unsigned char *p = 0; + + if( argc==2 ){ + zEditor = (const char*)sqlite3_value_text(argv[1]); + }else{ + zEditor = getenv("VISUAL"); + } + if( zEditor==0 ){ + sqlite3_result_error(context, "no editor for edit()", -1); + return; + } + if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ + sqlite3_result_error(context, "NULL input to edit()", -1); + return; + } + db = sqlite3_context_db_handle(context); + zTempFile = 0; + sqlite3_file_control(db, 0, SQLITE_FCNTL_TEMPFILENAME, &zTempFile); + if( zTempFile==0 ){ + sqlite3_uint64 r = 0; + sqlite3_randomness(sizeof(r), &r); + zTempFile = sqlite3_mprintf("temp%llx", r); + if( zTempFile==0 ){ + sqlite3_result_error_nomem(context); + return; + } + } + bBin = sqlite3_value_type(argv[0])==SQLITE_BLOB; + /* When writing the file to be edited, do \n to \r\n conversions on systems + ** that want \r\n line endings */ + f = fopen(zTempFile, bBin ? "wb" : "w"); + if( f==0 ){ + sqlite3_result_error(context, "edit() cannot open temp file", -1); + goto edit_func_end; + } + sz = sqlite3_value_bytes(argv[0]); + if( bBin ){ + x = fwrite(sqlite3_value_blob(argv[0]), 1, (size_t)sz, f); + }else{ + const char *z = (const char*)sqlite3_value_text(argv[0]); + /* Remember whether or not the value originally contained \r\n */ + if( z && strstr(z,"\r\n")!=0 ) hasCRNL = 1; + x = fwrite(sqlite3_value_text(argv[0]), 1, (size_t)sz, f); + } + fclose(f); + f = 0; + if( x!=sz ){ + sqlite3_result_error(context, "edit() could not write the whole file", -1); + goto edit_func_end; + } + zCmd = sqlite3_mprintf("%s \"%s\"", zEditor, zTempFile); + if( zCmd==0 ){ + sqlite3_result_error_nomem(context); + goto edit_func_end; + } + rc = system(zCmd); + sqlite3_free(zCmd); + if( rc ){ + sqlite3_result_error(context, "EDITOR returned non-zero", -1); + goto edit_func_end; + } + f = fopen(zTempFile, "rb"); + if( f==0 ){ + sqlite3_result_error(context, + "edit() cannot reopen temp file after edit", -1); + goto edit_func_end; + } + fseek(f, 0, SEEK_END); + sz = ftell(f); + rewind(f); + p = sqlite3_malloc64( sz+1 ); + if( p==0 ){ + sqlite3_result_error_nomem(context); + goto edit_func_end; + } + x = fread(p, 1, (size_t)sz, f); + fclose(f); + f = 0; + if( x!=sz ){ + sqlite3_result_error(context, "could not read back the whole file", -1); + goto edit_func_end; + } + if( bBin ){ + sqlite3_result_blob64(context, p, sz, sqlite3_free); + }else{ + sqlite3_int64 i, j; + if( hasCRNL ){ + /* If the original contains \r\n then do no conversions back to \n */ + }else{ + /* If the file did not originally contain \r\n then convert any new + ** \r\n back into \n */ + p[sz] = 0; + for(i=j=0; i<sz; i++){ + if( p[i]=='\r' && p[i+1]=='\n' ) i++; + p[j++] = p[i]; + } + sz = j; + p[sz] = 0; + } + sqlite3_result_text64(context, (const char*)p, sz, + sqlite3_free, SQLITE_UTF8); + } + p = 0; + +edit_func_end: + if( f ) fclose(f); + unlink(zTempFile); + sqlite3_free(zTempFile); + sqlite3_free(p); +} +#endif /* SQLITE_NOHAVE_SYSTEM */ + +/* +** Save or restore the current output mode +*/ +static void outputModePush(ShellState *p){ + p->modePrior = p->mode; + p->priorShFlgs = p->shellFlgs; + memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator)); + memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator)); +} +static void outputModePop(ShellState *p){ + p->mode = p->modePrior; + p->shellFlgs = p->priorShFlgs; + memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator)); + memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator)); +} + +/* +** Output the given string as a hex-encoded blob (eg. X'1234' ) +*/ +static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ + int i; + unsigned char *aBlob = (unsigned char*)pBlob; + + char *zStr = sqlite3_malloc(nBlob*2 + 1); + shell_check_oom(zStr); + + for(i=0; i<nBlob; i++){ + static const char aHex[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + zStr[i*2] = aHex[ (aBlob[i] >> 4) ]; + zStr[i*2+1] = aHex[ (aBlob[i] & 0x0F) ]; + } + zStr[i*2] = '\0'; + + raw_printf(out,"X'%s'", zStr); + sqlite3_free(zStr); +} + +/* +** Find a string that is not found anywhere in z[]. Return a pointer +** to that string. +** +** Try to use zA and zB first. If both of those are already found in z[] +** then make up some string and store it in the buffer zBuf. +*/ +static const char *unused_string( + const char *z, /* Result must not appear anywhere in z */ + const char *zA, const char *zB, /* Try these first */ + char *zBuf /* Space to store a generated string */ +){ + unsigned i = 0; + if( strstr(z, zA)==0 ) return zA; + if( strstr(z, zB)==0 ) return zB; + do{ + sqlite3_snprintf(20,zBuf,"(%s%u)", zA, i++); + }while( strstr(z,zBuf)!=0 ); + return zBuf; +} + +/* +** Output the given string as a quoted string using SQL quoting conventions. +** +** See also: output_quoted_escaped_string() +*/ +static void output_quoted_string(FILE *out, const char *z){ + int i; + char c; + setBinaryMode(out, 1); + if( z==0 ) return; + for(i=0; (c = z[i])!=0 && c!='\''; i++){} + if( c==0 ){ + utf8_printf(out,"'%s'",z); + }else{ + raw_printf(out, "'"); + while( *z ){ + for(i=0; (c = z[i])!=0 && c!='\''; i++){} + if( c=='\'' ) i++; + if( i ){ + utf8_printf(out, "%.*s", i, z); + z += i; + } + if( c=='\'' ){ + raw_printf(out, "'"); + continue; + } + if( c==0 ){ + break; + } + z++; + } + raw_printf(out, "'"); + } + setTextMode(out, 1); +} + +/* +** Output the given string as a quoted string using SQL quoting conventions. +** Additionallly , escape the "\n" and "\r" characters so that they do not +** get corrupted by end-of-line translation facilities in some operating +** systems. +** +** This is like output_quoted_string() but with the addition of the \r\n +** escape mechanism. +*/ +static void output_quoted_escaped_string(FILE *out, const char *z){ + int i; + char c; + setBinaryMode(out, 1); + for(i=0; (c = z[i])!=0 && c!='\'' && c!='\n' && c!='\r'; i++){} + if( c==0 ){ + utf8_printf(out,"'%s'",z); + }else{ + const char *zNL = 0; + const char *zCR = 0; + int nNL = 0; + int nCR = 0; + char zBuf1[20], zBuf2[20]; + for(i=0; z[i]; i++){ + if( z[i]=='\n' ) nNL++; + if( z[i]=='\r' ) nCR++; + } + if( nNL ){ + raw_printf(out, "replace("); + zNL = unused_string(z, "\\n", "\\012", zBuf1); + } + if( nCR ){ + raw_printf(out, "replace("); + zCR = unused_string(z, "\\r", "\\015", zBuf2); + } + raw_printf(out, "'"); + while( *z ){ + for(i=0; (c = z[i])!=0 && c!='\n' && c!='\r' && c!='\''; i++){} + if( c=='\'' ) i++; + if( i ){ + utf8_printf(out, "%.*s", i, z); + z += i; + } + if( c=='\'' ){ + raw_printf(out, "'"); + continue; + } + if( c==0 ){ + break; + } + z++; + if( c=='\n' ){ + raw_printf(out, "%s", zNL); + continue; + } + raw_printf(out, "%s", zCR); + } + raw_printf(out, "'"); + if( nCR ){ + raw_printf(out, ",'%s',char(13))", zCR); + } + if( nNL ){ + raw_printf(out, ",'%s',char(10))", zNL); + } + } + setTextMode(out, 1); +} + +/* +** Output the given string as a quoted according to C or TCL quoting rules. +*/ +static void output_c_string(FILE *out, const char *z){ + unsigned int c; + fputc('"', out); + while( (c = *(z++))!=0 ){ + if( c=='\\' ){ + fputc(c, out); + fputc(c, out); + }else if( c=='"' ){ + fputc('\\', out); + fputc('"', out); + }else if( c=='\t' ){ + fputc('\\', out); + fputc('t', out); + }else if( c=='\n' ){ + fputc('\\', out); + fputc('n', out); + }else if( c=='\r' ){ + fputc('\\', out); + fputc('r', out); + }else if( !isprint(c&0xff) ){ + raw_printf(out, "\\%03o", c&0xff); + }else{ + fputc(c, out); + } + } + fputc('"', out); +} + +/* +** Output the given string as a quoted according to JSON quoting rules. +*/ +static void output_json_string(FILE *out, const char *z, i64 n){ + unsigned int c; + if( z==0 ) z = ""; + if( n<0 ) n = strlen(z); + fputc('"', out); + while( n-- ){ + c = *(z++); + if( c=='\\' || c=='"' ){ + fputc('\\', out); + fputc(c, out); + }else if( c<=0x1f ){ + fputc('\\', out); + if( c=='\b' ){ + fputc('b', out); + }else if( c=='\f' ){ + fputc('f', out); + }else if( c=='\n' ){ + fputc('n', out); + }else if( c=='\r' ){ + fputc('r', out); + }else if( c=='\t' ){ + fputc('t', out); + }else{ + raw_printf(out, "u%04x",c); + } + }else{ + fputc(c, out); + } + } + fputc('"', out); +} + +/* +** Output the given string with characters that are special to +** HTML escaped. +*/ +static void output_html_string(FILE *out, const char *z){ + int i; + if( z==0 ) z = ""; + while( *z ){ + for(i=0; z[i] + && z[i]!='<' + && z[i]!='&' + && z[i]!='>' + && z[i]!='\"' + && z[i]!='\''; + i++){} + if( i>0 ){ + utf8_printf(out,"%.*s",i,z); + } + if( z[i]=='<' ){ + raw_printf(out,"&lt;"); + }else if( z[i]=='&' ){ + raw_printf(out,"&amp;"); + }else if( z[i]=='>' ){ + raw_printf(out,"&gt;"); + }else if( z[i]=='\"' ){ + raw_printf(out,"&quot;"); + }else if( z[i]=='\'' ){ + raw_printf(out,"&#39;"); + }else{ + break; + } + z += i + 1; + } +} + +/* +** If a field contains any character identified by a 1 in the following +** array, then the string must be quoted for CSV. +*/ +static const char needCsvQuote[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +}; + +/* +** Output a single term of CSV. Actually, p->colSeparator is used for +** the separator, which may or may not be a comma. p->nullValue is +** the null value. Strings are quoted if necessary. The separator +** is only issued if bSep is true. +*/ +static void output_csv(ShellState *p, const char *z, int bSep){ + FILE *out = p->out; + if( z==0 ){ + utf8_printf(out,"%s",p->nullValue); + }else{ + unsigned i; + for(i=0; z[i]; i++){ + if( needCsvQuote[((unsigned char*)z)[i]] ){ + i = 0; + break; + } + } + if( i==0 || strstr(z, p->colSeparator)!=0 ){ + char *zQuoted = sqlite3_mprintf("\"%w\"", z); + shell_check_oom(zQuoted); + utf8_printf(out, "%s", zQuoted); + sqlite3_free(zQuoted); + }else{ + utf8_printf(out, "%s", z); + } + } + if( bSep ){ + utf8_printf(p->out, "%s", p->colSeparator); + } +} + +/* +** This routine runs when the user presses Ctrl-C +*/ +static void interrupt_handler(int NotUsed){ + UNUSED_PARAMETER(NotUsed); + if( ++seenInterrupt>1 ) exit(1); + if( globalDb ) sqlite3_interrupt(globalDb); +} + +#if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) +/* +** This routine runs for console events (e.g. Ctrl-C) on Win32 +*/ +static BOOL WINAPI ConsoleCtrlHandler( + DWORD dwCtrlType /* One of the CTRL_*_EVENT constants */ +){ + if( dwCtrlType==CTRL_C_EVENT ){ + interrupt_handler(0); + return TRUE; + } + return FALSE; +} +#endif + +#ifndef SQLITE_OMIT_AUTHORIZATION +/* +** This authorizer runs in safe mode. +*/ +static int safeModeAuth( + void *pClientData, + int op, + const char *zA1, + const char *zA2, + const char *zA3, + const char *zA4 +){ + ShellState *p = (ShellState*)pClientData; + static const char *azProhibitedFunctions[] = { + "edit", + "fts3_tokenizer", + "load_extension", + "readfile", + "writefile", + "zipfile", + "zipfile_cds", + }; + UNUSED_PARAMETER(zA1); + UNUSED_PARAMETER(zA3); + UNUSED_PARAMETER(zA4); + switch( op ){ + case SQLITE_ATTACH: { +#ifndef SQLITE_SHELL_FIDDLE + /* In WASM builds the filesystem is a virtual sandbox, so + ** there's no harm in using ATTACH. */ + failIfSafeMode(p, "cannot run ATTACH in safe mode"); +#endif + break; + } + case SQLITE_FUNCTION: { + int i; + for(i=0; i<ArraySize(azProhibitedFunctions); i++){ + if( sqlite3_stricmp(zA2, azProhibitedFunctions[i])==0 ){ + failIfSafeMode(p, "cannot use the %s() function in safe mode", + azProhibitedFunctions[i]); + } + } + break; + } + } + return SQLITE_OK; +} + +/* +** When the ".auth ON" is set, the following authorizer callback is +** invoked. It always returns SQLITE_OK. +*/ +static int shellAuth( + void *pClientData, + int op, + const char *zA1, + const char *zA2, + const char *zA3, + const char *zA4 +){ + ShellState *p = (ShellState*)pClientData; + static const char *azAction[] = { 0, + "CREATE_INDEX", "CREATE_TABLE", "CREATE_TEMP_INDEX", + "CREATE_TEMP_TABLE", "CREATE_TEMP_TRIGGER", "CREATE_TEMP_VIEW", + "CREATE_TRIGGER", "CREATE_VIEW", "DELETE", + "DROP_INDEX", "DROP_TABLE", "DROP_TEMP_INDEX", + "DROP_TEMP_TABLE", "DROP_TEMP_TRIGGER", "DROP_TEMP_VIEW", + "DROP_TRIGGER", "DROP_VIEW", "INSERT", + "PRAGMA", "READ", "SELECT", + "TRANSACTION", "UPDATE", "ATTACH", + "DETACH", "ALTER_TABLE", "REINDEX", + "ANALYZE", "CREATE_VTABLE", "DROP_VTABLE", + "FUNCTION", "SAVEPOINT", "RECURSIVE" + }; + int i; + const char *az[4]; + az[0] = zA1; + az[1] = zA2; + az[2] = zA3; + az[3] = zA4; + utf8_printf(p->out, "authorizer: %s", azAction[op]); + for(i=0; i<4; i++){ + raw_printf(p->out, " "); + if( az[i] ){ + output_c_string(p->out, az[i]); + }else{ + raw_printf(p->out, "NULL"); + } + } + raw_printf(p->out, "\n"); + if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4); + return SQLITE_OK; +} +#endif + +/* +** Print a schema statement. Part of MODE_Semi and MODE_Pretty output. +** +** This routine converts some CREATE TABLE statements for shadow tables +** in FTS3/4/5 into CREATE TABLE IF NOT EXISTS statements. +** +** If the schema statement in z[] contains a start-of-comment and if +** sqlite3_complete() returns false, try to terminate the comment before +** printing the result. https://sqlite.org/forum/forumpost/d7be961c5c +*/ +static void printSchemaLine(FILE *out, const char *z, const char *zTail){ + char *zToFree = 0; + if( z==0 ) return; + if( zTail==0 ) return; + if( zTail[0]==';' && (strstr(z, "/*")!=0 || strstr(z,"--")!=0) ){ + const char *zOrig = z; + static const char *azTerm[] = { "", "*/", "\n" }; + int i; + for(i=0; i<ArraySize(azTerm); i++){ + char *zNew = sqlite3_mprintf("%s%s;", zOrig, azTerm[i]); + shell_check_oom(zNew); + if( sqlite3_complete(zNew) ){ + size_t n = strlen(zNew); + zNew[n-1] = 0; + zToFree = zNew; + z = zNew; + break; + } + sqlite3_free(zNew); + } + } + if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){ + utf8_printf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); + }else{ + utf8_printf(out, "%s%s", z, zTail); + } + sqlite3_free(zToFree); +} +static void printSchemaLineN(FILE *out, char *z, int n, const char *zTail){ + char c = z[n]; + z[n] = 0; + printSchemaLine(out, z, zTail); + z[n] = c; +} + +/* +** Return true if string z[] has nothing but whitespace and comments to the +** end of the first line. +*/ +static int wsToEol(const char *z){ + int i; + for(i=0; z[i]; i++){ + if( z[i]=='\n' ) return 1; + if( IsSpace(z[i]) ) continue; + if( z[i]=='-' && z[i+1]=='-' ) return 1; + return 0; + } + return 1; +} + +/* +** Add a new entry to the EXPLAIN QUERY PLAN data +*/ +static void eqp_append(ShellState *p, int iEqpId, int p2, const char *zText){ + EQPGraphRow *pNew; + i64 nText; + if( zText==0 ) return; + nText = strlen(zText); + if( p->autoEQPtest ){ + utf8_printf(p->out, "%d,%d,%s\n", iEqpId, p2, zText); + } + pNew = sqlite3_malloc64( sizeof(*pNew) + nText ); + shell_check_oom(pNew); + pNew->iEqpId = iEqpId; + pNew->iParentId = p2; + memcpy(pNew->zText, zText, nText+1); + pNew->pNext = 0; + if( p->sGraph.pLast ){ + p->sGraph.pLast->pNext = pNew; + }else{ + p->sGraph.pRow = pNew; + } + p->sGraph.pLast = pNew; +} + +/* +** Free and reset the EXPLAIN QUERY PLAN data that has been collected +** in p->sGraph. +*/ +static void eqp_reset(ShellState *p){ + EQPGraphRow *pRow, *pNext; + for(pRow = p->sGraph.pRow; pRow; pRow = pNext){ + pNext = pRow->pNext; + sqlite3_free(pRow); + } + memset(&p->sGraph, 0, sizeof(p->sGraph)); +} + +/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after +** pOld, or return the first such line if pOld is NULL +*/ +static EQPGraphRow *eqp_next_row(ShellState *p, int iEqpId, EQPGraphRow *pOld){ + EQPGraphRow *pRow = pOld ? pOld->pNext : p->sGraph.pRow; + while( pRow && pRow->iParentId!=iEqpId ) pRow = pRow->pNext; + return pRow; +} + +/* Render a single level of the graph that has iEqpId as its parent. Called +** recursively to render sublevels. +*/ +static void eqp_render_level(ShellState *p, int iEqpId){ + EQPGraphRow *pRow, *pNext; + i64 n = strlen(p->sGraph.zPrefix); + char *z; + for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){ + pNext = eqp_next_row(p, iEqpId, pRow); + z = pRow->zText; + utf8_printf(p->out, "%s%s%s\n", p->sGraph.zPrefix, + pNext ? "|--" : "`--", z); + if( n<(i64)sizeof(p->sGraph.zPrefix)-7 ){ + memcpy(&p->sGraph.zPrefix[n], pNext ? "| " : " ", 4); + eqp_render_level(p, pRow->iEqpId); + p->sGraph.zPrefix[n] = 0; + } + } +} + +/* +** Display and reset the EXPLAIN QUERY PLAN data +*/ +static void eqp_render(ShellState *p, i64 nCycle){ + EQPGraphRow *pRow = p->sGraph.pRow; + if( pRow ){ + if( pRow->zText[0]=='-' ){ + if( pRow->pNext==0 ){ + eqp_reset(p); + return; + } + utf8_printf(p->out, "%s\n", pRow->zText+3); + p->sGraph.pRow = pRow->pNext; + sqlite3_free(pRow); + }else if( nCycle>0 ){ + utf8_printf(p->out, "QUERY PLAN (cycles=%lld [100%%])\n", nCycle); + }else{ + utf8_printf(p->out, "QUERY PLAN\n"); + } + p->sGraph.zPrefix[0] = 0; + eqp_render_level(p, 0); + eqp_reset(p); + } +} + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK +/* +** Progress handler callback. +*/ +static int progress_handler(void *pClientData) { + ShellState *p = (ShellState*)pClientData; + p->nProgress++; + if( p->nProgress>=p->mxProgress && p->mxProgress>0 ){ + raw_printf(p->out, "Progress limit reached (%u)\n", p->nProgress); + if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0; + if( p->flgProgress & SHELL_PROGRESS_ONCE ) p->mxProgress = 0; + return 1; + } + if( (p->flgProgress & SHELL_PROGRESS_QUIET)==0 ){ + raw_printf(p->out, "Progress %u\n", p->nProgress); + } + return 0; +} +#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ + +/* +** Print N dashes +*/ +static void print_dashes(FILE *out, int N){ + const char zDash[] = "--------------------------------------------------"; + const int nDash = sizeof(zDash) - 1; + while( N>nDash ){ + fputs(zDash, out); + N -= nDash; + } + raw_printf(out, "%.*s", N, zDash); +} + +/* +** Print a markdown or table-style row separator using ascii-art +*/ +static void print_row_separator( + ShellState *p, + int nArg, + const char *zSep +){ + int i; + if( nArg>0 ){ + fputs(zSep, p->out); + print_dashes(p->out, p->actualWidth[0]+2); + for(i=1; i<nArg; i++){ + fputs(zSep, p->out); + print_dashes(p->out, p->actualWidth[i]+2); + } + fputs(zSep, p->out); + } + fputs("\n", p->out); +} + +/* +** This is the callback routine that the shell +** invokes for each row of a query result. +*/ +static int shell_callback( + void *pArg, + int nArg, /* Number of result columns */ + char **azArg, /* Text of each result column */ + char **azCol, /* Column names */ + int *aiType /* Column types. Might be NULL */ +){ + int i; + ShellState *p = (ShellState*)pArg; + + if( azArg==0 ) return 0; + switch( p->cMode ){ + case MODE_Count: + case MODE_Off: { + break; + } + case MODE_Line: { + int w = 5; + if( azArg==0 ) break; + for(i=0; i<nArg; i++){ + int len = strlen30(azCol[i] ? azCol[i] : ""); + if( len>w ) w = len; + } + if( p->cnt++>0 ) utf8_printf(p->out, "%s", p->rowSeparator); + for(i=0; i<nArg; i++){ + utf8_printf(p->out,"%*s = %s%s", w, azCol[i], + azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator); + } + break; + } + case MODE_ScanExp: + case MODE_Explain: { + static const int aExplainWidth[] = {4, 13, 4, 4, 4, 13, 2, 13}; + static const int aExplainMap[] = {0, 1, 2, 3, 4, 5, 6, 7 }; + static const int aScanExpWidth[] = {4, 6, 6, 13, 4, 4, 4, 13, 2, 13}; + static const int aScanExpMap[] = {0, 9, 8, 1, 2, 3, 4, 5, 6, 7 }; + + const int *aWidth = aExplainWidth; + const int *aMap = aExplainMap; + int nWidth = ArraySize(aExplainWidth); + int iIndent = 1; + + if( p->cMode==MODE_ScanExp ){ + aWidth = aScanExpWidth; + aMap = aScanExpMap; + nWidth = ArraySize(aScanExpWidth); + iIndent = 3; + } + if( nArg>nWidth ) nArg = nWidth; + + /* If this is the first row seen, print out the headers */ + if( p->cnt++==0 ){ + for(i=0; i<nArg; i++){ + utf8_width_print(p->out, aWidth[i], azCol[ aMap[i] ]); + fputs(i==nArg-1 ? "\n" : " ", p->out); + } + for(i=0; i<nArg; i++){ + print_dashes(p->out, aWidth[i]); + fputs(i==nArg-1 ? "\n" : " ", p->out); + } + } + + /* If there is no data, exit early. */ + if( azArg==0 ) break; + + for(i=0; i<nArg; i++){ + const char *zSep = " "; + int w = aWidth[i]; + const char *zVal = azArg[ aMap[i] ]; + if( i==nArg-1 ) w = 0; + if( zVal && strlenChar(zVal)>w ){ + w = strlenChar(zVal); + zSep = " "; + } + if( i==iIndent && p->aiIndent && p->pStmt ){ + if( p->iIndent<p->nIndent ){ + utf8_printf(p->out, "%*.s", p->aiIndent[p->iIndent], ""); + } + p->iIndent++; + } + utf8_width_print(p->out, w, zVal ? zVal : p->nullValue); + fputs(i==nArg-1 ? "\n" : zSep, p->out); + } + break; + } + case MODE_Semi: { /* .schema and .fullschema output */ + printSchemaLine(p->out, azArg[0], ";\n"); + break; + } + case MODE_Pretty: { /* .schema and .fullschema with --indent */ + char *z; + int j; + int nParen = 0; + char cEnd = 0; + char c; + int nLine = 0; + assert( nArg==1 ); + if( azArg[0]==0 ) break; + if( sqlite3_strlike("CREATE VIEW%", azArg[0], 0)==0 + || sqlite3_strlike("CREATE TRIG%", azArg[0], 0)==0 + ){ + utf8_printf(p->out, "%s;\n", azArg[0]); + break; + } + z = sqlite3_mprintf("%s", azArg[0]); + shell_check_oom(z); + j = 0; + for(i=0; IsSpace(z[i]); i++){} + for(; (c = z[i])!=0; i++){ + if( IsSpace(c) ){ + if( z[j-1]=='\r' ) z[j-1] = '\n'; + if( IsSpace(z[j-1]) || z[j-1]=='(' ) continue; + }else if( (c=='(' || c==')') && j>0 && IsSpace(z[j-1]) ){ + j--; + } + z[j++] = c; + } + while( j>0 && IsSpace(z[j-1]) ){ j--; } + z[j] = 0; + if( strlen30(z)>=79 ){ + for(i=j=0; (c = z[i])!=0; i++){ /* Copy from z[i] back to z[j] */ + if( c==cEnd ){ + cEnd = 0; + }else if( c=='"' || c=='\'' || c=='`' ){ + cEnd = c; + }else if( c=='[' ){ + cEnd = ']'; + }else if( c=='-' && z[i+1]=='-' ){ + cEnd = '\n'; + }else if( c=='(' ){ + nParen++; + }else if( c==')' ){ + nParen--; + if( nLine>0 && nParen==0 && j>0 ){ + printSchemaLineN(p->out, z, j, "\n"); + j = 0; + } + } + z[j++] = c; + if( nParen==1 && cEnd==0 + && (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1))) + ){ + if( c=='\n' ) j--; + printSchemaLineN(p->out, z, j, "\n "); + j = 0; + nLine++; + while( IsSpace(z[i+1]) ){ i++; } + } + } + z[j] = 0; + } + printSchemaLine(p->out, z, ";\n"); + sqlite3_free(z); + break; + } + case MODE_List: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; i<nArg; i++){ + utf8_printf(p->out,"%s%s",azCol[i], + i==nArg-1 ? p->rowSeparator : p->colSeparator); + } + } + if( azArg==0 ) break; + for(i=0; i<nArg; i++){ + char *z = azArg[i]; + if( z==0 ) z = p->nullValue; + utf8_printf(p->out, "%s", z); + if( i<nArg-1 ){ + utf8_printf(p->out, "%s", p->colSeparator); + }else{ + utf8_printf(p->out, "%s", p->rowSeparator); + } + } + break; + } + case MODE_Html: { + if( p->cnt++==0 && p->showHeader ){ + raw_printf(p->out,"<TR>"); + for(i=0; i<nArg; i++){ + raw_printf(p->out,"<TH>"); + output_html_string(p->out, azCol[i]); + raw_printf(p->out,"</TH>\n"); + } + raw_printf(p->out,"</TR>\n"); + } + if( azArg==0 ) break; + raw_printf(p->out,"<TR>"); + for(i=0; i<nArg; i++){ + raw_printf(p->out,"<TD>"); + output_html_string(p->out, azArg[i] ? azArg[i] : p->nullValue); + raw_printf(p->out,"</TD>\n"); + } + raw_printf(p->out,"</TR>\n"); + break; + } + case MODE_Tcl: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; i<nArg; i++){ + output_c_string(p->out,azCol[i] ? azCol[i] : ""); + if(i<nArg-1) utf8_printf(p->out, "%s", p->colSeparator); + } + utf8_printf(p->out, "%s", p->rowSeparator); + } + if( azArg==0 ) break; + for(i=0; i<nArg; i++){ + output_c_string(p->out, azArg[i] ? azArg[i] : p->nullValue); + if(i<nArg-1) utf8_printf(p->out, "%s", p->colSeparator); + } + utf8_printf(p->out, "%s", p->rowSeparator); + break; + } + case MODE_Csv: { + setBinaryMode(p->out, 1); + if( p->cnt++==0 && p->showHeader ){ + for(i=0; i<nArg; i++){ + output_csv(p, azCol[i] ? azCol[i] : "", i<nArg-1); + } + utf8_printf(p->out, "%s", p->rowSeparator); + } + if( nArg>0 ){ + for(i=0; i<nArg; i++){ + output_csv(p, azArg[i], i<nArg-1); + } + utf8_printf(p->out, "%s", p->rowSeparator); + } + setTextMode(p->out, 1); + break; + } + case MODE_Insert: { + if( azArg==0 ) break; + utf8_printf(p->out,"INSERT INTO %s",p->zDestTable); + if( p->showHeader ){ + raw_printf(p->out,"("); + for(i=0; i<nArg; i++){ + if( i>0 ) raw_printf(p->out, ","); + if( quoteChar(azCol[i]) ){ + char *z = sqlite3_mprintf("\"%w\"", azCol[i]); + shell_check_oom(z); + utf8_printf(p->out, "%s", z); + sqlite3_free(z); + }else{ + raw_printf(p->out, "%s", azCol[i]); + } + } + raw_printf(p->out,")"); + } + p->cnt++; + for(i=0; i<nArg; i++){ + raw_printf(p->out, i>0 ? "," : " VALUES("); + if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ + utf8_printf(p->out,"NULL"); + }else if( aiType && aiType[i]==SQLITE_TEXT ){ + if( ShellHasFlag(p, SHFLG_Newlines) ){ + output_quoted_string(p->out, azArg[i]); + }else{ + output_quoted_escaped_string(p->out, azArg[i]); + } + }else if( aiType && aiType[i]==SQLITE_INTEGER ){ + utf8_printf(p->out,"%s", azArg[i]); + }else if( aiType && aiType[i]==SQLITE_FLOAT ){ + char z[50]; + double r = sqlite3_column_double(p->pStmt, i); + sqlite3_uint64 ur; + memcpy(&ur,&r,sizeof(r)); + if( ur==0x7ff0000000000000LL ){ + raw_printf(p->out, "9.0e+999"); + }else if( ur==0xfff0000000000000LL ){ + raw_printf(p->out, "-9.0e+999"); + }else{ + sqlite3_int64 ir = (sqlite3_int64)r; + if( r==(double)ir ){ + sqlite3_snprintf(50,z,"%lld.0", ir); + }else{ + sqlite3_snprintf(50,z,"%!.20g", r); + } + raw_printf(p->out, "%s", z); + } + }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ + const void *pBlob = sqlite3_column_blob(p->pStmt, i); + int nBlob = sqlite3_column_bytes(p->pStmt, i); + output_hex_blob(p->out, pBlob, nBlob); + }else if( isNumber(azArg[i], 0) ){ + utf8_printf(p->out,"%s", azArg[i]); + }else if( ShellHasFlag(p, SHFLG_Newlines) ){ + output_quoted_string(p->out, azArg[i]); + }else{ + output_quoted_escaped_string(p->out, azArg[i]); + } + } + raw_printf(p->out,");\n"); + break; + } + case MODE_Json: { + if( azArg==0 ) break; + if( p->cnt==0 ){ + fputs("[{", p->out); + }else{ + fputs(",\n{", p->out); + } + p->cnt++; + for(i=0; i<nArg; i++){ + output_json_string(p->out, azCol[i], -1); + putc(':', p->out); + if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ + fputs("null",p->out); + }else if( aiType && aiType[i]==SQLITE_FLOAT ){ + char z[50]; + double r = sqlite3_column_double(p->pStmt, i); + sqlite3_uint64 ur; + memcpy(&ur,&r,sizeof(r)); + if( ur==0x7ff0000000000000LL ){ + raw_printf(p->out, "9.0e+999"); + }else if( ur==0xfff0000000000000LL ){ + raw_printf(p->out, "-9.0e+999"); + }else{ + sqlite3_snprintf(50,z,"%!.20g", r); + raw_printf(p->out, "%s", z); + } + }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ + const void *pBlob = sqlite3_column_blob(p->pStmt, i); + int nBlob = sqlite3_column_bytes(p->pStmt, i); + output_json_string(p->out, pBlob, nBlob); + }else if( aiType && aiType[i]==SQLITE_TEXT ){ + output_json_string(p->out, azArg[i], -1); + }else{ + utf8_printf(p->out,"%s", azArg[i]); + } + if( i<nArg-1 ){ + putc(',', p->out); + } + } + putc('}', p->out); + break; + } + case MODE_Quote: { + if( azArg==0 ) break; + if( p->cnt==0 && p->showHeader ){ + for(i=0; i<nArg; i++){ + if( i>0 ) fputs(p->colSeparator, p->out); + output_quoted_string(p->out, azCol[i]); + } + fputs(p->rowSeparator, p->out); + } + p->cnt++; + for(i=0; i<nArg; i++){ + if( i>0 ) fputs(p->colSeparator, p->out); + if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ + utf8_printf(p->out,"NULL"); + }else if( aiType && aiType[i]==SQLITE_TEXT ){ + output_quoted_string(p->out, azArg[i]); + }else if( aiType && aiType[i]==SQLITE_INTEGER ){ + utf8_printf(p->out,"%s", azArg[i]); + }else if( aiType && aiType[i]==SQLITE_FLOAT ){ + char z[50]; + double r = sqlite3_column_double(p->pStmt, i); + sqlite3_snprintf(50,z,"%!.20g", r); + raw_printf(p->out, "%s", z); + }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ + const void *pBlob = sqlite3_column_blob(p->pStmt, i); + int nBlob = sqlite3_column_bytes(p->pStmt, i); + output_hex_blob(p->out, pBlob, nBlob); + }else if( isNumber(azArg[i], 0) ){ + utf8_printf(p->out,"%s", azArg[i]); + }else{ + output_quoted_string(p->out, azArg[i]); + } + } + fputs(p->rowSeparator, p->out); + break; + } + case MODE_Ascii: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; i<nArg; i++){ + if( i>0 ) utf8_printf(p->out, "%s", p->colSeparator); + utf8_printf(p->out,"%s",azCol[i] ? azCol[i] : ""); + } + utf8_printf(p->out, "%s", p->rowSeparator); + } + if( azArg==0 ) break; + for(i=0; i<nArg; i++){ + if( i>0 ) utf8_printf(p->out, "%s", p->colSeparator); + utf8_printf(p->out,"%s",azArg[i] ? azArg[i] : p->nullValue); + } + utf8_printf(p->out, "%s", p->rowSeparator); + break; + } + case MODE_EQP: { + eqp_append(p, atoi(azArg[0]), atoi(azArg[1]), azArg[3]); + break; + } + } + return 0; +} + +/* +** This is the callback routine that the SQLite library +** invokes for each row of a query result. +*/ +static int callback(void *pArg, int nArg, char **azArg, char **azCol){ + /* since we don't have type info, call the shell_callback with a NULL value */ + return shell_callback(pArg, nArg, azArg, azCol, NULL); +} + +/* +** This is the callback routine from sqlite3_exec() that appends all +** output onto the end of a ShellText object. +*/ +static int captureOutputCallback(void *pArg, int nArg, char **azArg, char **az){ + ShellText *p = (ShellText*)pArg; + int i; + UNUSED_PARAMETER(az); + if( azArg==0 ) return 0; + if( p->n ) appendText(p, "|", 0); + for(i=0; i<nArg; i++){ + if( i ) appendText(p, ",", 0); + if( azArg[i] ) appendText(p, azArg[i], 0); + } + return 0; +} + +/* +** Generate an appropriate SELFTEST table in the main database. +*/ +static void createSelftestTable(ShellState *p){ + char *zErrMsg = 0; + sqlite3_exec(p->db, + "SAVEPOINT selftest_init;\n" + "CREATE TABLE IF NOT EXISTS selftest(\n" + " tno INTEGER PRIMARY KEY,\n" /* Test number */ + " op TEXT,\n" /* Operator: memo run */ + " cmd TEXT,\n" /* Command text */ + " ans TEXT\n" /* Desired answer */ + ");" + "CREATE TEMP TABLE [_shell$self](op,cmd,ans);\n" + "INSERT INTO [_shell$self](rowid,op,cmd)\n" + " VALUES(coalesce((SELECT (max(tno)+100)/10 FROM selftest),10),\n" + " 'memo','Tests generated by --init');\n" + "INSERT INTO [_shell$self]\n" + " SELECT 'run',\n" + " 'SELECT hex(sha3_query(''SELECT type,name,tbl_name,sql " + "FROM sqlite_schema ORDER BY 2'',224))',\n" + " hex(sha3_query('SELECT type,name,tbl_name,sql " + "FROM sqlite_schema ORDER BY 2',224));\n" + "INSERT INTO [_shell$self]\n" + " SELECT 'run'," + " 'SELECT hex(sha3_query(''SELECT * FROM \"' ||" + " printf('%w',name) || '\" NOT INDEXED'',224))',\n" + " hex(sha3_query(printf('SELECT * FROM \"%w\" NOT INDEXED',name),224))\n" + " FROM (\n" + " SELECT name FROM sqlite_schema\n" + " WHERE type='table'\n" + " AND name<>'selftest'\n" + " AND coalesce(rootpage,0)>0\n" + " )\n" + " ORDER BY name;\n" + "INSERT INTO [_shell$self]\n" + " VALUES('run','PRAGMA integrity_check','ok');\n" + "INSERT INTO selftest(tno,op,cmd,ans)" + " SELECT rowid*10,op,cmd,ans FROM [_shell$self];\n" + "DROP TABLE [_shell$self];" + ,0,0,&zErrMsg); + if( zErrMsg ){ + utf8_printf(stderr, "SELFTEST initialization failure: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } + sqlite3_exec(p->db, "RELEASE selftest_init",0,0,0); +} + + +/* +** Set the destination table field of the ShellState structure to +** the name of the table given. Escape any quote characters in the +** table name. +*/ +static void set_table_name(ShellState *p, const char *zName){ + int i, n; + char cQuote; + char *z; + + if( p->zDestTable ){ + free(p->zDestTable); + p->zDestTable = 0; + } + if( zName==0 ) return; + cQuote = quoteChar(zName); + n = strlen30(zName); + if( cQuote ) n += n+2; + z = p->zDestTable = malloc( n+1 ); + shell_check_oom(z); + n = 0; + if( cQuote ) z[n++] = cQuote; + for(i=0; zName[i]; i++){ + z[n++] = zName[i]; + if( zName[i]==cQuote ) z[n++] = cQuote; + } + if( cQuote ) z[n++] = cQuote; + z[n] = 0; +} + +/* +** Maybe construct two lines of text that point out the position of a +** syntax error. Return a pointer to the text, in memory obtained from +** sqlite3_malloc(). Or, if the most recent error does not involve a +** specific token that we can point to, return an empty string. +** +** In all cases, the memory returned is obtained from sqlite3_malloc64() +** and should be released by the caller invoking sqlite3_free(). +*/ +static char *shell_error_context(const char *zSql, sqlite3 *db){ + int iOffset; + size_t len; + char *zCode; + char *zMsg; + int i; + if( db==0 + || zSql==0 + || (iOffset = sqlite3_error_offset(db))<0 + || iOffset>=(int)strlen(zSql) + ){ + return sqlite3_mprintf(""); + } + while( iOffset>50 ){ + iOffset--; + zSql++; + while( (zSql[0]&0xc0)==0x80 ){ zSql++; iOffset--; } + } + len = strlen(zSql); + if( len>78 ){ + len = 78; + while( len>0 && (zSql[len]&0xc0)==0x80 ) len--; + } + zCode = sqlite3_mprintf("%.*s", len, zSql); + shell_check_oom(zCode); + for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; } + if( iOffset<25 ){ + zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", zCode,iOffset,""); + }else{ + zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^", zCode,iOffset-14,""); + } + return zMsg; +} + + +/* +** Execute a query statement that will generate SQL output. Print +** the result columns, comma-separated, on a line and then add a +** semicolon terminator to the end of that line. +** +** If the number of columns is 1 and that column contains text "--" +** then write the semicolon on a separate line. That way, if a +** "--" comment occurs at the end of the statement, the comment +** won't consume the semicolon terminator. +*/ +static int run_table_dump_query( + ShellState *p, /* Query context */ + const char *zSelect /* SELECT statement to extract content */ +){ + sqlite3_stmt *pSelect; + int rc; + int nResult; + int i; + const char *z; + rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0); + if( rc!=SQLITE_OK || !pSelect ){ + char *zContext = shell_error_context(zSelect, p->db); + utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n%s", rc, + sqlite3_errmsg(p->db), zContext); + sqlite3_free(zContext); + if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; + return rc; + } + rc = sqlite3_step(pSelect); + nResult = sqlite3_column_count(pSelect); + while( rc==SQLITE_ROW ){ + z = (const char*)sqlite3_column_text(pSelect, 0); + utf8_printf(p->out, "%s", z); + for(i=1; i<nResult; i++){ + utf8_printf(p->out, ",%s", sqlite3_column_text(pSelect, i)); + } + if( z==0 ) z = ""; + while( z[0] && (z[0]!='-' || z[1]!='-') ) z++; + if( z[0] ){ + raw_printf(p->out, "\n;\n"); + }else{ + raw_printf(p->out, ";\n"); + } + rc = sqlite3_step(pSelect); + } + rc = sqlite3_finalize(pSelect); + if( rc!=SQLITE_OK ){ + utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, + sqlite3_errmsg(p->db)); + if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; + } + return rc; +} + +/* +** Allocate space and save off string indicating current error. +*/ +static char *save_err_msg( + sqlite3 *db, /* Database to query */ + const char *zPhase, /* When the error occurs */ + int rc, /* Error code returned from API */ + const char *zSql /* SQL string, or NULL */ +){ + char *zErr; + char *zContext; + sqlite3_str *pStr = sqlite3_str_new(0); + sqlite3_str_appendf(pStr, "%s, %s", zPhase, sqlite3_errmsg(db)); + if( rc>1 ){ + sqlite3_str_appendf(pStr, " (%d)", rc); + } + zContext = shell_error_context(zSql, db); + if( zContext ){ + sqlite3_str_appendall(pStr, zContext); + sqlite3_free(zContext); + } + zErr = sqlite3_str_finish(pStr); + shell_check_oom(zErr); + return zErr; +} + +#ifdef __linux__ +/* +** Attempt to display I/O stats on Linux using /proc/PID/io +*/ +static void displayLinuxIoStats(FILE *out){ + FILE *in; + char z[200]; + sqlite3_snprintf(sizeof(z), z, "/proc/%d/io", getpid()); + in = fopen(z, "rb"); + if( in==0 ) return; + while( fgets(z, sizeof(z), in)!=0 ){ + static const struct { + const char *zPattern; + const char *zDesc; + } aTrans[] = { + { "rchar: ", "Bytes received by read():" }, + { "wchar: ", "Bytes sent to write():" }, + { "syscr: ", "Read() system calls:" }, + { "syscw: ", "Write() system calls:" }, + { "read_bytes: ", "Bytes read from storage:" }, + { "write_bytes: ", "Bytes written to storage:" }, + { "cancelled_write_bytes: ", "Cancelled write bytes:" }, + }; + int i; + for(i=0; i<ArraySize(aTrans); i++){ + int n = strlen30(aTrans[i].zPattern); + if( cli_strncmp(aTrans[i].zPattern, z, n)==0 ){ + utf8_printf(out, "%-36s %s", aTrans[i].zDesc, &z[n]); + break; + } + } + } + fclose(in); +} +#endif + +/* +** Display a single line of status using 64-bit values. +*/ +static void displayStatLine( + ShellState *p, /* The shell context */ + char *zLabel, /* Label for this one line */ + char *zFormat, /* Format for the result */ + int iStatusCtrl, /* Which status to display */ + int bReset /* True to reset the stats */ +){ + sqlite3_int64 iCur = -1; + sqlite3_int64 iHiwtr = -1; + int i, nPercent; + char zLine[200]; + sqlite3_status64(iStatusCtrl, &iCur, &iHiwtr, bReset); + for(i=0, nPercent=0; zFormat[i]; i++){ + if( zFormat[i]=='%' ) nPercent++; + } + if( nPercent>1 ){ + sqlite3_snprintf(sizeof(zLine), zLine, zFormat, iCur, iHiwtr); + }else{ + sqlite3_snprintf(sizeof(zLine), zLine, zFormat, iHiwtr); + } + raw_printf(p->out, "%-36s %s\n", zLabel, zLine); +} + +/* +** Display memory stats. +*/ +static int display_stats( + sqlite3 *db, /* Database to query */ + ShellState *pArg, /* Pointer to ShellState */ + int bReset /* True to reset the stats */ +){ + int iCur; + int iHiwtr; + FILE *out; + if( pArg==0 || pArg->out==0 ) return 0; + out = pArg->out; + + if( pArg->pStmt && pArg->statsOn==2 ){ + int nCol, i, x; + sqlite3_stmt *pStmt = pArg->pStmt; + char z[100]; + nCol = sqlite3_column_count(pStmt); + raw_printf(out, "%-36s %d\n", "Number of output columns:", nCol); + for(i=0; i<nCol; i++){ + sqlite3_snprintf(sizeof(z),z,"Column %d %nname:", i, &x); + utf8_printf(out, "%-36s %s\n", z, sqlite3_column_name(pStmt,i)); +#ifndef SQLITE_OMIT_DECLTYPE + sqlite3_snprintf(30, z+x, "declared type:"); + utf8_printf(out, "%-36s %s\n", z, sqlite3_column_decltype(pStmt, i)); +#endif +#ifdef SQLITE_ENABLE_COLUMN_METADATA + sqlite3_snprintf(30, z+x, "database name:"); + utf8_printf(out, "%-36s %s\n", z, sqlite3_column_database_name(pStmt,i)); + sqlite3_snprintf(30, z+x, "table name:"); + utf8_printf(out, "%-36s %s\n", z, sqlite3_column_table_name(pStmt,i)); + sqlite3_snprintf(30, z+x, "origin name:"); + utf8_printf(out, "%-36s %s\n", z, sqlite3_column_origin_name(pStmt,i)); +#endif + } + } + + if( pArg->statsOn==3 ){ + if( pArg->pStmt ){ + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset); + raw_printf(pArg->out, "VM-steps: %d\n", iCur); + } + return 0; + } + + displayStatLine(pArg, "Memory Used:", + "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset); + displayStatLine(pArg, "Number of Outstanding Allocations:", + "%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset); + if( pArg->shellFlgs & SHFLG_Pagecache ){ + displayStatLine(pArg, "Number of Pcache Pages Used:", + "%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset); + } + displayStatLine(pArg, "Number of Pcache Overflow Bytes:", + "%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset); + displayStatLine(pArg, "Largest Allocation:", + "%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset); + displayStatLine(pArg, "Largest Pcache Allocation:", + "%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset); +#ifdef YYTRACKMAXSTACKDEPTH + displayStatLine(pArg, "Deepest Parser Stack:", + "%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset); +#endif + + if( db ){ + if( pArg->shellFlgs & SHFLG_Lookaside ){ + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, + &iCur, &iHiwtr, bReset); + raw_printf(pArg->out, + "Lookaside Slots Used: %d (max %d)\n", + iCur, iHiwtr); + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, + &iCur, &iHiwtr, bReset); + raw_printf(pArg->out, "Successful lookaside attempts: %d\n", + iHiwtr); + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, + &iCur, &iHiwtr, bReset); + raw_printf(pArg->out, "Lookaside failures due to size: %d\n", + iHiwtr); + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, + &iCur, &iHiwtr, bReset); + raw_printf(pArg->out, "Lookaside failures due to OOM: %d\n", + iHiwtr); + } + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset); + raw_printf(pArg->out, "Pager Heap Usage: %d bytes\n", + iCur); + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1); + raw_printf(pArg->out, "Page cache hits: %d\n", iCur); + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); + raw_printf(pArg->out, "Page cache misses: %d\n", iCur); + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); + raw_printf(pArg->out, "Page cache writes: %d\n", iCur); + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1); + raw_printf(pArg->out, "Page cache spills: %d\n", iCur); + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); + raw_printf(pArg->out, "Schema Heap Usage: %d bytes\n", + iCur); + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset); + raw_printf(pArg->out, "Statement Heap/Lookaside Usage: %d bytes\n", + iCur); + } + + if( pArg->pStmt ){ + int iHit, iMiss; + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, + bReset); + raw_printf(pArg->out, "Fullscan Steps: %d\n", iCur); + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset); + raw_printf(pArg->out, "Sort Operations: %d\n", iCur); + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset); + raw_printf(pArg->out, "Autoindex Inserts: %d\n", iCur); + iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, + bReset); + iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, + bReset); + if( iHit || iMiss ){ + raw_printf(pArg->out, "Bloom filter bypass taken: %d/%d\n", + iHit, iHit+iMiss); + } + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset); + raw_printf(pArg->out, "Virtual Machine Steps: %d\n", iCur); + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset); + raw_printf(pArg->out, "Reprepare operations: %d\n", iCur); + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset); + raw_printf(pArg->out, "Number of times run: %d\n", iCur); + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset); + raw_printf(pArg->out, "Memory used by prepared stmt: %d\n", iCur); + } + +#ifdef __linux__ + displayLinuxIoStats(pArg->out); +#endif + + /* Do not remove this machine readable comment: extra-stats-output-here */ + + return 0; +} + + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +static int scanStatsHeight(sqlite3_stmt *p, int iEntry){ + int iPid = 0; + int ret = 1; + sqlite3_stmt_scanstatus_v2(p, iEntry, + SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid + ); + while( iPid!=0 ){ + int ii; + for(ii=0; 1; ii++){ + int iId; + int res; + res = sqlite3_stmt_scanstatus_v2(p, ii, + SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iId + ); + if( res ) break; + if( iId==iPid ){ + sqlite3_stmt_scanstatus_v2(p, ii, + SQLITE_SCANSTAT_PARENTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid + ); + } + } + ret++; + } + return ret; +} +#endif + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +static void display_explain_scanstats( + sqlite3 *db, /* Database to query */ + ShellState *pArg /* Pointer to ShellState */ +){ + static const int f = SQLITE_SCANSTAT_COMPLEX; + sqlite3_stmt *p = pArg->pStmt; + int ii = 0; + i64 nTotal = 0; + int nWidth = 0; + eqp_reset(pArg); + + for(ii=0; 1; ii++){ + const char *z = 0; + int n = 0; + if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ + break; + } + n = strlen(z) + scanStatsHeight(p, ii)*3; + if( n>nWidth ) nWidth = n; + } + nWidth += 4; + + sqlite3_stmt_scanstatus_v2(p, -1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal); + for(ii=0; 1; ii++){ + i64 nLoop = 0; + i64 nRow = 0; + i64 nCycle = 0; + int iId = 0; + int iPid = 0; + const char *z = 0; + const char *zName = 0; + char *zText = 0; + double rEst = 0.0; + + if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ + break; + } + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_EST,f,(void*)&rEst); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid); + sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName); + + zText = sqlite3_mprintf("%s", z); + if( nCycle>=0 || nLoop>=0 || nRow>=0 ){ + char *z = 0; + if( nCycle>=0 && nTotal>0 ){ + z = sqlite3_mprintf("%zcycles=%lld [%d%%]", z, + nCycle, ((nCycle*100)+nTotal/2) / nTotal + ); + } + if( nLoop>=0 ){ + z = sqlite3_mprintf("%z%sloops=%lld", z, z ? " " : "", nLoop); + } + if( nRow>=0 ){ + z = sqlite3_mprintf("%z%srows=%lld", z, z ? " " : "", nRow); + } + + if( zName && pArg->scanstatsOn>1 ){ + double rpl = (double)nRow / (double)nLoop; + z = sqlite3_mprintf("%z rpl=%.1f est=%.1f", z, rpl, rEst); + } + + zText = sqlite3_mprintf( + "% *z (%z)", -1*(nWidth-scanStatsHeight(p, ii)*3), zText, z + ); + } + + eqp_append(pArg, iId, iPid, zText); + sqlite3_free(zText); + } + + eqp_render(pArg, nTotal); +} +#endif + + +/* +** Parameter azArray points to a zero-terminated array of strings. zStr +** points to a single nul-terminated string. Return non-zero if zStr +** is equal, according to strcmp(), to any of the strings in the array. +** Otherwise, return zero. +*/ +static int str_in_array(const char *zStr, const char **azArray){ + int i; + for(i=0; azArray[i]; i++){ + if( 0==cli_strcmp(zStr, azArray[i]) ) return 1; + } + return 0; +} + +/* +** If compiled statement pSql appears to be an EXPLAIN statement, allocate +** and populate the ShellState.aiIndent[] array with the number of +** spaces each opcode should be indented before it is output. +** +** The indenting rules are: +** +** * For each "Next", "Prev", "VNext" or "VPrev" instruction, indent +** all opcodes that occur between the p2 jump destination and the opcode +** itself by 2 spaces. +** +** * Do the previous for "Return" instructions for when P2 is positive. +** See tag-20220407a in wherecode.c and vdbe.c. +** +** * For each "Goto", if the jump destination is earlier in the program +** and ends on one of: +** Yield SeekGt SeekLt RowSetRead Rewind +** or if the P1 parameter is one instead of zero, +** then indent all opcodes between the earlier instruction +** and "Goto" by 2 spaces. +*/ +static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ + int *abYield = 0; /* True if op is an OP_Yield */ + int nAlloc = 0; /* Allocated size of p->aiIndent[], abYield */ + int iOp; /* Index of operation in p->aiIndent[] */ + + const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext", + "Return", 0 }; + const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead", + "Rewind", 0 }; + const char *azGoto[] = { "Goto", 0 }; + + /* The caller guarantees that the leftmost 4 columns of the statement + ** passed to this function are equivalent to the leftmost 4 columns + ** of EXPLAIN statement output. In practice the statement may be + ** an EXPLAIN, or it may be a query on the bytecode() virtual table. */ + assert( sqlite3_column_count(pSql)>=4 ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 0), "addr" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 1), "opcode" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 2), "p1" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 3), "p2" ) ); + + for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){ + int i; + int iAddr = sqlite3_column_int(pSql, 0); + const char *zOp = (const char*)sqlite3_column_text(pSql, 1); + int p1 = sqlite3_column_int(pSql, 2); + int p2 = sqlite3_column_int(pSql, 3); + + /* Assuming that p2 is an instruction address, set variable p2op to the + ** index of that instruction in the aiIndent[] array. p2 and p2op may be + ** different if the current instruction is part of a sub-program generated + ** by an SQL trigger or foreign key. */ + int p2op = (p2 + (iOp-iAddr)); + + /* Grow the p->aiIndent array as required */ + if( iOp>=nAlloc ){ + nAlloc += 100; + p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int)); + shell_check_oom(p->aiIndent); + abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int)); + shell_check_oom(abYield); + } + + abYield[iOp] = str_in_array(zOp, azYield); + p->aiIndent[iOp] = 0; + p->nIndent = iOp+1; + if( str_in_array(zOp, azNext) && p2op>0 ){ + for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2; + } + if( str_in_array(zOp, azGoto) && p2op<iOp && (abYield[p2op] || p1) ){ + for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2; + } + } + + p->iIndent = 0; + sqlite3_free(abYield); + sqlite3_reset(pSql); +} + +/* +** Free the array allocated by explain_data_prepare(). +*/ +static void explain_data_delete(ShellState *p){ + sqlite3_free(p->aiIndent); + p->aiIndent = 0; + p->nIndent = 0; + p->iIndent = 0; +} + +static void exec_prepared_stmt(ShellState*, sqlite3_stmt*); + +/* +** Display scan stats. +*/ +static void display_scanstats( + sqlite3 *db, /* Database to query */ + ShellState *pArg /* Pointer to ShellState */ +){ +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(pArg); +#else + if( pArg->scanstatsOn==3 ){ + const char *zSql = + " SELECT addr, opcode, p1, p2, p3, p4, p5, comment, nexec," + " round(ncycle*100.0 / (sum(ncycle) OVER ()), 2)||'%' AS cycles" + " FROM bytecode(?)"; + + int rc = SQLITE_OK; + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_stmt *pSave = pArg->pStmt; + pArg->pStmt = pStmt; + sqlite3_bind_pointer(pStmt, 1, pSave, "stmt-pointer", 0); + + pArg->cnt = 0; + pArg->cMode = MODE_ScanExp; + explain_data_prepare(pArg, pStmt); + exec_prepared_stmt(pArg, pStmt); + explain_data_delete(pArg); + + sqlite3_finalize(pStmt); + pArg->pStmt = pSave; + } + }else{ + display_explain_scanstats(db, pArg); + } +#endif +} + +/* +** Disable and restore .wheretrace and .treetrace/.selecttrace settings. +*/ +static unsigned int savedSelectTrace; +static unsigned int savedWhereTrace; +static void disable_debug_trace_modes(void){ + unsigned int zero = 0; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 0, &savedSelectTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &zero); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 2, &savedWhereTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &zero); +} +static void restore_debug_trace_modes(void){ + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &savedSelectTrace); + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace); +} + +/* Create the TEMP table used to store parameter bindings */ +static void bind_table_init(ShellState *p){ + int wrSchema = 0; + int defensiveMode = 0; + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &defensiveMode); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &wrSchema); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0); + sqlite3_exec(p->db, + "CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n" + " key TEXT PRIMARY KEY,\n" + " value\n" + ") WITHOUT ROWID;", + 0, 0, 0); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, defensiveMode, 0); +} + +/* +** Bind parameters on a prepared statement. +** +** Parameter bindings are taken from a TEMP table of the form: +** +** CREATE TEMP TABLE sqlite_parameters(key TEXT PRIMARY KEY, value) +** WITHOUT ROWID; +** +** No bindings occur if this table does not exist. The name of the table +** begins with "sqlite_" so that it will not collide with ordinary application +** tables. The table must be in the TEMP schema. +*/ +static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ + int nVar; + int i; + int rc; + sqlite3_stmt *pQ = 0; + + nVar = sqlite3_bind_parameter_count(pStmt); + if( nVar==0 ) return; /* Nothing to do */ + if( sqlite3_table_column_metadata(pArg->db, "TEMP", "sqlite_parameters", + "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){ + rc = SQLITE_NOTFOUND; + pQ = 0; + }else{ + rc = sqlite3_prepare_v2(pArg->db, + "SELECT value FROM temp.sqlite_parameters" + " WHERE key=?1", -1, &pQ, 0); + } + for(i=1; i<=nVar; i++){ + char zNum[30]; + const char *zVar = sqlite3_bind_parameter_name(pStmt, i); + if( zVar==0 ){ + sqlite3_snprintf(sizeof(zNum),zNum,"?%d",i); + zVar = zNum; + } + sqlite3_bind_text(pQ, 1, zVar, -1, SQLITE_STATIC); + if( rc==SQLITE_OK && pQ && sqlite3_step(pQ)==SQLITE_ROW ){ + sqlite3_bind_value(pStmt, i, sqlite3_column_value(pQ, 0)); +#ifdef NAN + }else if( sqlite3_strlike("_NAN", zVar, 0)==0 ){ + sqlite3_bind_double(pStmt, i, NAN); +#endif +#ifdef INFINITY + }else if( sqlite3_strlike("_INF", zVar, 0)==0 ){ + sqlite3_bind_double(pStmt, i, INFINITY); +#endif + }else{ + sqlite3_bind_null(pStmt, i); + } + sqlite3_reset(pQ); + } + sqlite3_finalize(pQ); +} + +/* +** UTF8 box-drawing characters. Imagine box lines like this: +** +** 1 +** | +** 4 --+-- 2 +** | +** 3 +** +** Each box characters has between 2 and 4 of the lines leading from +** the center. The characters are here identified by the numbers of +** their corresponding lines. +*/ +#define BOX_24 "\342\224\200" /* U+2500 --- */ +#define BOX_13 "\342\224\202" /* U+2502 | */ +#define BOX_23 "\342\224\214" /* U+250c ,- */ +#define BOX_34 "\342\224\220" /* U+2510 -, */ +#define BOX_12 "\342\224\224" /* U+2514 '- */ +#define BOX_14 "\342\224\230" /* U+2518 -' */ +#define BOX_123 "\342\224\234" /* U+251c |- */ +#define BOX_134 "\342\224\244" /* U+2524 -| */ +#define BOX_234 "\342\224\254" /* U+252c -,- */ +#define BOX_124 "\342\224\264" /* U+2534 -'- */ +#define BOX_1234 "\342\224\274" /* U+253c -|- */ + +/* Draw horizontal line N characters long using unicode box +** characters +*/ +static void print_box_line(FILE *out, int N){ + const char zDash[] = + BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 + BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24; + const int nDash = sizeof(zDash) - 1; + N *= 3; + while( N>nDash ){ + utf8_printf(out, zDash); + N -= nDash; + } + utf8_printf(out, "%.*s", N, zDash); +} + +/* +** Draw a horizontal separator for a MODE_Box table. +*/ +static void print_box_row_separator( + ShellState *p, + int nArg, + const char *zSep1, + const char *zSep2, + const char *zSep3 +){ + int i; + if( nArg>0 ){ + utf8_printf(p->out, "%s", zSep1); + print_box_line(p->out, p->actualWidth[0]+2); + for(i=1; i<nArg; i++){ + utf8_printf(p->out, "%s", zSep2); + print_box_line(p->out, p->actualWidth[i]+2); + } + utf8_printf(p->out, "%s", zSep3); + } + fputs("\n", p->out); +} + +/* +** z[] is a line of text that is to be displayed the .mode box or table or +** similar tabular formats. z[] might contain control characters such +** as \n, \t, \f, or \r. +** +** Compute characters to display on the first line of z[]. Stop at the +** first \r, \n, or \f. Expand \t into spaces. Return a copy (obtained +** from malloc()) of that first line, which caller should free sometime. +** Write anything to display on the next line into *pzTail. If this is +** the last line, write a NULL into *pzTail. (*pzTail is not allocated.) +*/ +static char *translateForDisplayAndDup( + const unsigned char *z, /* Input text to be transformed */ + const unsigned char **pzTail, /* OUT: Tail of the input for next line */ + int mxWidth, /* Max width. 0 means no limit */ + u8 bWordWrap /* If true, avoid breaking mid-word */ +){ + int i; /* Input bytes consumed */ + int j; /* Output bytes generated */ + int k; /* Input bytes to be displayed */ + int n; /* Output column number */ + unsigned char *zOut; /* Output text */ + + if( z==0 ){ + *pzTail = 0; + return 0; + } + if( mxWidth<0 ) mxWidth = -mxWidth; + if( mxWidth==0 ) mxWidth = 1000000; + i = j = n = 0; + while( n<mxWidth ){ + if( z[i]>=' ' ){ + n++; + do{ i++; j++; }while( (z[i]&0xc0)==0x80 ); + continue; + } + if( z[i]=='\t' ){ + do{ + n++; + j++; + }while( (n&7)!=0 && n<mxWidth ); + i++; + continue; + } + break; + } + if( n>=mxWidth && bWordWrap ){ + /* Perhaps try to back up to a better place to break the line */ + for(k=i; k>i/2; k--){ + if( isspace(z[k-1]) ) break; + } + if( k<=i/2 ){ + for(k=i; k>i/2; k--){ + if( isalnum(z[k-1])!=isalnum(z[k]) && (z[k]&0xc0)!=0x80 ) break; + } + } + if( k<=i/2 ){ + k = i; + }else{ + i = k; + while( z[i]==' ' ) i++; + } + }else{ + k = i; + } + if( n>=mxWidth && z[i]>=' ' ){ + *pzTail = &z[i]; + }else if( z[i]=='\r' && z[i+1]=='\n' ){ + *pzTail = z[i+2] ? &z[i+2] : 0; + }else if( z[i]==0 || z[i+1]==0 ){ + *pzTail = 0; + }else{ + *pzTail = &z[i+1]; + } + zOut = malloc( j+1 ); + shell_check_oom(zOut); + i = j = n = 0; + while( i<k ){ + if( z[i]>=' ' ){ + n++; + do{ zOut[j++] = z[i++]; }while( (z[i]&0xc0)==0x80 ); + continue; + } + if( z[i]=='\t' ){ + do{ + n++; + zOut[j++] = ' '; + }while( (n&7)!=0 && n<mxWidth ); + i++; + continue; + } + break; + } + zOut[j] = 0; + return (char*)zOut; +} + +/* Extract the value of the i-th current column for pStmt as an SQL literal +** value. Memory is obtained from sqlite3_malloc64() and must be freed by +** the caller. +*/ +static char *quoted_column(sqlite3_stmt *pStmt, int i){ + switch( sqlite3_column_type(pStmt, i) ){ + case SQLITE_NULL: { + return sqlite3_mprintf("NULL"); + } + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + return sqlite3_mprintf("%s",sqlite3_column_text(pStmt,i)); + } + case SQLITE_TEXT: { + return sqlite3_mprintf("%Q",sqlite3_column_text(pStmt,i)); + } + case SQLITE_BLOB: { + int j; + sqlite3_str *pStr = sqlite3_str_new(0); + const unsigned char *a = sqlite3_column_blob(pStmt,i); + int n = sqlite3_column_bytes(pStmt,i); + sqlite3_str_append(pStr, "x'", 2); + for(j=0; j<n; j++){ + sqlite3_str_appendf(pStr, "%02x", a[j]); + } + sqlite3_str_append(pStr, "'", 1); + return sqlite3_str_finish(pStr); + } + } + return 0; /* Not reached */ +} + +/* +** Run a prepared statement and output the result in one of the +** table-oriented formats: MODE_Column, MODE_Markdown, MODE_Table, +** or MODE_Box. +** +** This is different from ordinary exec_prepared_stmt() in that +** it has to run the entire query and gather the results into memory +** first, in order to determine column widths, before providing +** any output. +*/ +static void exec_prepared_stmt_columnar( + ShellState *p, /* Pointer to ShellState */ + sqlite3_stmt *pStmt /* Statement to run */ +){ + sqlite3_int64 nRow = 0; + int nColumn = 0; + char **azData = 0; + sqlite3_int64 nAlloc = 0; + char *abRowDiv = 0; + const unsigned char *uz; + const char *z; + char **azQuoted = 0; + int rc; + sqlite3_int64 i, nData; + int j, nTotal, w, n; + const char *colSep = 0; + const char *rowSep = 0; + const unsigned char **azNextLine = 0; + int bNextLine = 0; + int bMultiLineRowExists = 0; + int bw = p->cmOpts.bWordWrap; + const char *zEmpty = ""; + const char *zShowNull = p->nullValue; + + rc = sqlite3_step(pStmt); + if( rc!=SQLITE_ROW ) return; + nColumn = sqlite3_column_count(pStmt); + nAlloc = nColumn*4; + if( nAlloc<=0 ) nAlloc = 1; + azData = sqlite3_malloc64( nAlloc*sizeof(char*) ); + shell_check_oom(azData); + azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) ); + shell_check_oom(azNextLine); + memset((void*)azNextLine, 0, nColumn*sizeof(char*) ); + if( p->cmOpts.bQuote ){ + azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) ); + shell_check_oom(azQuoted); + memset(azQuoted, 0, nColumn*sizeof(char*) ); + } + abRowDiv = sqlite3_malloc64( nAlloc/nColumn ); + shell_check_oom(abRowDiv); + if( nColumn>p->nWidth ){ + p->colWidth = realloc(p->colWidth, (nColumn+1)*2*sizeof(int)); + shell_check_oom(p->colWidth); + for(i=p->nWidth; i<nColumn; i++) p->colWidth[i] = 0; + p->nWidth = nColumn; + p->actualWidth = &p->colWidth[nColumn]; + } + memset(p->actualWidth, 0, nColumn*sizeof(int)); + for(i=0; i<nColumn; i++){ + w = p->colWidth[i]; + if( w<0 ) w = -w; + p->actualWidth[i] = w; + } + for(i=0; i<nColumn; i++){ + const unsigned char *zNotUsed; + int wx = p->colWidth[i]; + if( wx==0 ){ + wx = p->cmOpts.iWrap; + } + if( wx<0 ) wx = -wx; + uz = (const unsigned char*)sqlite3_column_name(pStmt,i); + if( uz==0 ) uz = (u8*)""; + azData[i] = translateForDisplayAndDup(uz, &zNotUsed, wx, bw); + } + do{ + int useNextLine = bNextLine; + bNextLine = 0; + if( (nRow+2)*nColumn >= nAlloc ){ + nAlloc *= 2; + azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*)); + shell_check_oom(azData); + abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn); + shell_check_oom(abRowDiv); + } + abRowDiv[nRow] = 1; + nRow++; + for(i=0; i<nColumn; i++){ + int wx = p->colWidth[i]; + if( wx==0 ){ + wx = p->cmOpts.iWrap; + } + if( wx<0 ) wx = -wx; + if( useNextLine ){ + uz = azNextLine[i]; + if( uz==0 ) uz = (u8*)zEmpty; + }else if( p->cmOpts.bQuote ){ + sqlite3_free(azQuoted[i]); + azQuoted[i] = quoted_column(pStmt,i); + uz = (const unsigned char*)azQuoted[i]; + }else{ + uz = (const unsigned char*)sqlite3_column_text(pStmt,i); + if( uz==0 ) uz = (u8*)zShowNull; + } + azData[nRow*nColumn + i] + = translateForDisplayAndDup(uz, &azNextLine[i], wx, bw); + if( azNextLine[i] ){ + bNextLine = 1; + abRowDiv[nRow-1] = 0; + bMultiLineRowExists = 1; + } + } + }while( bNextLine || sqlite3_step(pStmt)==SQLITE_ROW ); + nTotal = nColumn*(nRow+1); + for(i=0; i<nTotal; i++){ + z = azData[i]; + if( z==0 ) z = (char*)zEmpty; + n = strlenChar(z); + j = i%nColumn; + if( n>p->actualWidth[j] ) p->actualWidth[j] = n; + } + if( seenInterrupt ) goto columnar_end; + if( nColumn==0 ) goto columnar_end; + switch( p->cMode ){ + case MODE_Column: { + colSep = " "; + rowSep = "\n"; + if( p->showHeader ){ + for(i=0; i<nColumn; i++){ + w = p->actualWidth[i]; + if( p->colWidth[i]<0 ) w = -w; + utf8_width_print(p->out, w, azData[i]); + fputs(i==nColumn-1?"\n":" ", p->out); + } + for(i=0; i<nColumn; i++){ + print_dashes(p->out, p->actualWidth[i]); + fputs(i==nColumn-1?"\n":" ", p->out); + } + } + break; + } + case MODE_Table: { + colSep = " | "; + rowSep = " |\n"; + print_row_separator(p, nColumn, "+"); + fputs("| ", p->out); + for(i=0; i<nColumn; i++){ + w = p->actualWidth[i]; + n = strlenChar(azData[i]); + utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); + fputs(i==nColumn-1?" |\n":" | ", p->out); + } + print_row_separator(p, nColumn, "+"); + break; + } + case MODE_Markdown: { + colSep = " | "; + rowSep = " |\n"; + fputs("| ", p->out); + for(i=0; i<nColumn; i++){ + w = p->actualWidth[i]; + n = strlenChar(azData[i]); + utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); + fputs(i==nColumn-1?" |\n":" | ", p->out); + } + print_row_separator(p, nColumn, "|"); + break; + } + case MODE_Box: { + colSep = " " BOX_13 " "; + rowSep = " " BOX_13 "\n"; + print_box_row_separator(p, nColumn, BOX_23, BOX_234, BOX_34); + utf8_printf(p->out, BOX_13 " "); + for(i=0; i<nColumn; i++){ + w = p->actualWidth[i]; + n = strlenChar(azData[i]); + utf8_printf(p->out, "%*s%s%*s%s", + (w-n)/2, "", azData[i], (w-n+1)/2, "", + i==nColumn-1?" "BOX_13"\n":" "BOX_13" "); + } + print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); + break; + } + } + for(i=nColumn, j=0; i<nTotal; i++, j++){ + if( j==0 && p->cMode!=MODE_Column ){ + utf8_printf(p->out, "%s", p->cMode==MODE_Box?BOX_13" ":"| "); + } + z = azData[i]; + if( z==0 ) z = p->nullValue; + w = p->actualWidth[j]; + if( p->colWidth[j]<0 ) w = -w; + utf8_width_print(p->out, w, z); + if( j==nColumn-1 ){ + utf8_printf(p->out, "%s", rowSep); + if( bMultiLineRowExists && abRowDiv[i/nColumn-1] && i+1<nTotal ){ + if( p->cMode==MODE_Table ){ + print_row_separator(p, nColumn, "+"); + }else if( p->cMode==MODE_Box ){ + print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); + }else if( p->cMode==MODE_Column ){ + raw_printf(p->out, "\n"); + } + } + j = -1; + if( seenInterrupt ) goto columnar_end; + }else{ + utf8_printf(p->out, "%s", colSep); + } + } + if( p->cMode==MODE_Table ){ + print_row_separator(p, nColumn, "+"); + }else if( p->cMode==MODE_Box ){ + print_box_row_separator(p, nColumn, BOX_12, BOX_124, BOX_14); + } +columnar_end: + if( seenInterrupt ){ + utf8_printf(p->out, "Interrupt\n"); + } + nData = (nRow+1)*nColumn; + for(i=0; i<nData; i++){ + z = azData[i]; + if( z!=zEmpty && z!=zShowNull ) free(azData[i]); + } + sqlite3_free(azData); + sqlite3_free((void*)azNextLine); + sqlite3_free(abRowDiv); + if( azQuoted ){ + for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]); + sqlite3_free(azQuoted); + } +} + +/* +** Run a prepared statement +*/ +static void exec_prepared_stmt( + ShellState *pArg, /* Pointer to ShellState */ + sqlite3_stmt *pStmt /* Statement to run */ +){ + int rc; + sqlite3_uint64 nRow = 0; + + if( pArg->cMode==MODE_Column + || pArg->cMode==MODE_Table + || pArg->cMode==MODE_Box + || pArg->cMode==MODE_Markdown + ){ + exec_prepared_stmt_columnar(pArg, pStmt); + return; + } + + /* perform the first step. this will tell us if we + ** have a result set or not and how wide it is. + */ + rc = sqlite3_step(pStmt); + /* if we have a result set... */ + if( SQLITE_ROW == rc ){ + /* allocate space for col name ptr, value ptr, and type */ + int nCol = sqlite3_column_count(pStmt); + void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1); + if( !pData ){ + shell_out_of_memory(); + }else{ + char **azCols = (char **)pData; /* Names of result columns */ + char **azVals = &azCols[nCol]; /* Results */ + int *aiTypes = (int *)&azVals[nCol]; /* Result types */ + int i, x; + assert(sizeof(int) <= sizeof(char *)); + /* save off ptrs to column names */ + for(i=0; i<nCol; i++){ + azCols[i] = (char *)sqlite3_column_name(pStmt, i); + } + do{ + nRow++; + /* extract the data and data types */ + for(i=0; i<nCol; i++){ + aiTypes[i] = x = sqlite3_column_type(pStmt, i); + if( x==SQLITE_BLOB + && pArg + && (pArg->cMode==MODE_Insert || pArg->cMode==MODE_Quote) + ){ + azVals[i] = ""; + }else{ + azVals[i] = (char*)sqlite3_column_text(pStmt, i); + } + if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){ + rc = SQLITE_NOMEM; + break; /* from for */ + } + } /* end for */ + + /* if data and types extracted successfully... */ + if( SQLITE_ROW == rc ){ + /* call the supplied callback with the result row data */ + if( shell_callback(pArg, nCol, azVals, azCols, aiTypes) ){ + rc = SQLITE_ABORT; + }else{ + rc = sqlite3_step(pStmt); + } + } + } while( SQLITE_ROW == rc ); + sqlite3_free(pData); + if( pArg->cMode==MODE_Json ){ + fputs("]\n", pArg->out); + }else if( pArg->cMode==MODE_Count ){ + char zBuf[200]; + sqlite3_snprintf(sizeof(zBuf), zBuf, "%llu row%s\n", + nRow, nRow!=1 ? "s" : ""); + printf("%s", zBuf); + } + } + } +} + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** This function is called to process SQL if the previous shell command +** was ".expert". It passes the SQL in the second argument directly to +** the sqlite3expert object. +** +** If successful, SQLITE_OK is returned. Otherwise, an SQLite error +** code. In this case, (*pzErr) may be set to point to a buffer containing +** an English language error message. It is the responsibility of the +** caller to eventually free this buffer using sqlite3_free(). +*/ +static int expertHandleSQL( + ShellState *pState, + const char *zSql, + char **pzErr +){ + assert( pState->expert.pExpert ); + assert( pzErr==0 || *pzErr==0 ); + return sqlite3_expert_sql(pState->expert.pExpert, zSql, pzErr); +} + +/* +** This function is called either to silently clean up the object +** created by the ".expert" command (if bCancel==1), or to generate a +** report from it and then clean it up (if bCancel==0). +** +** If successful, SQLITE_OK is returned. Otherwise, an SQLite error +** code. In this case, (*pzErr) may be set to point to a buffer containing +** an English language error message. It is the responsibility of the +** caller to eventually free this buffer using sqlite3_free(). +*/ +static int expertFinish( + ShellState *pState, + int bCancel, + char **pzErr +){ + int rc = SQLITE_OK; + sqlite3expert *p = pState->expert.pExpert; + assert( p ); + assert( bCancel || pzErr==0 || *pzErr==0 ); + if( bCancel==0 ){ + FILE *out = pState->out; + int bVerbose = pState->expert.bVerbose; + + rc = sqlite3_expert_analyze(p, pzErr); + if( rc==SQLITE_OK ){ + int nQuery = sqlite3_expert_count(p); + int i; + + if( bVerbose ){ + const char *zCand = sqlite3_expert_report(p,0,EXPERT_REPORT_CANDIDATES); + raw_printf(out, "-- Candidates -----------------------------\n"); + raw_printf(out, "%s\n", zCand); + } + for(i=0; i<nQuery; i++){ + const char *zSql = sqlite3_expert_report(p, i, EXPERT_REPORT_SQL); + const char *zIdx = sqlite3_expert_report(p, i, EXPERT_REPORT_INDEXES); + const char *zEQP = sqlite3_expert_report(p, i, EXPERT_REPORT_PLAN); + if( zIdx==0 ) zIdx = "(no new indexes)\n"; + if( bVerbose ){ + raw_printf(out, "-- Query %d --------------------------------\n",i+1); + raw_printf(out, "%s\n\n", zSql); + } + raw_printf(out, "%s\n", zIdx); + raw_printf(out, "%s\n", zEQP); + } + } + } + sqlite3_expert_destroy(p); + pState->expert.pExpert = 0; + return rc; +} + +/* +** Implementation of ".expert" dot command. +*/ +static int expertDotCommand( + ShellState *pState, /* Current shell tool state */ + char **azArg, /* Array of arguments passed to dot command */ + int nArg /* Number of entries in azArg[] */ +){ + int rc = SQLITE_OK; + char *zErr = 0; + int i; + int iSample = 0; + + assert( pState->expert.pExpert==0 ); + memset(&pState->expert, 0, sizeof(ExpertInfo)); + + for(i=1; rc==SQLITE_OK && i<nArg; i++){ + char *z = azArg[i]; + int n; + if( z[0]=='-' && z[1]=='-' ) z++; + n = strlen30(z); + if( n>=2 && 0==cli_strncmp(z, "-verbose", n) ){ + pState->expert.bVerbose = 1; + } + else if( n>=2 && 0==cli_strncmp(z, "-sample", n) ){ + if( i==(nArg-1) ){ + raw_printf(stderr, "option requires an argument: %s\n", z); + rc = SQLITE_ERROR; + }else{ + iSample = (int)integerValue(azArg[++i]); + if( iSample<0 || iSample>100 ){ + raw_printf(stderr, "value out of range: %s\n", azArg[i]); + rc = SQLITE_ERROR; + } + } + } + else{ + raw_printf(stderr, "unknown option: %s\n", z); + rc = SQLITE_ERROR; + } + } + + if( rc==SQLITE_OK ){ + pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr); + if( pState->expert.pExpert==0 ){ + raw_printf(stderr, "sqlite3_expert_new: %s\n", + zErr ? zErr : "out of memory"); + rc = SQLITE_ERROR; + }else{ + sqlite3_expert_config( + pState->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample + ); + } + } + sqlite3_free(zErr); + + return rc; +} +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + +/* +** Execute a statement or set of statements. Print +** any result rows/columns depending on the current mode +** set via the supplied callback. +** +** This is very similar to SQLite's built-in sqlite3_exec() +** function except it takes a slightly different callback +** and callback data argument. +*/ +static int shell_exec( + ShellState *pArg, /* Pointer to ShellState */ + const char *zSql, /* SQL to be evaluated */ + char **pzErrMsg /* Error msg written here */ +){ + sqlite3_stmt *pStmt = NULL; /* Statement to execute. */ + int rc = SQLITE_OK; /* Return Code */ + int rc2; + const char *zLeftover; /* Tail of unprocessed SQL */ + sqlite3 *db = pArg->db; + + if( pzErrMsg ){ + *pzErrMsg = NULL; + } + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( pArg->expert.pExpert ){ + rc = expertHandleSQL(pArg, zSql, pzErrMsg); + return expertFinish(pArg, (rc!=SQLITE_OK), pzErrMsg); + } +#endif + + while( zSql[0] && (SQLITE_OK == rc) ){ + static const char *zStmtSql; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); + if( SQLITE_OK != rc ){ + if( pzErrMsg ){ + *pzErrMsg = save_err_msg(db, "in prepare", rc, zSql); + } + }else{ + if( !pStmt ){ + /* this happens for a comment or white-space */ + zSql = zLeftover; + while( IsSpace(zSql[0]) ) zSql++; + continue; + } + zStmtSql = sqlite3_sql(pStmt); + if( zStmtSql==0 ) zStmtSql = ""; + while( IsSpace(zStmtSql[0]) ) zStmtSql++; + + /* save off the prepared statement handle and reset row count */ + if( pArg ){ + pArg->pStmt = pStmt; + pArg->cnt = 0; + } + + /* Show the EXPLAIN QUERY PLAN if .eqp is on */ + if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){ + sqlite3_stmt *pExplain; + int triggerEQP = 0; + disable_debug_trace_modes(); + sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP); + if( pArg->autoEQP>=AUTOEQP_trigger ){ + sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0); + } + pExplain = pStmt; + sqlite3_reset(pExplain); + rc = sqlite3_stmt_explain(pExplain, 2); + if( rc==SQLITE_OK ){ + while( sqlite3_step(pExplain)==SQLITE_ROW ){ + const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3); + int iEqpId = sqlite3_column_int(pExplain, 0); + int iParentId = sqlite3_column_int(pExplain, 1); + if( zEQPLine==0 ) zEQPLine = ""; + if( zEQPLine[0]=='-' ) eqp_render(pArg, 0); + eqp_append(pArg, iEqpId, iParentId, zEQPLine); + } + eqp_render(pArg, 0); + } + if( pArg->autoEQP>=AUTOEQP_full ){ + /* Also do an EXPLAIN for ".eqp full" mode */ + sqlite3_reset(pExplain); + rc = sqlite3_stmt_explain(pExplain, 1); + if( rc==SQLITE_OK ){ + pArg->cMode = MODE_Explain; + assert( sqlite3_stmt_isexplain(pExplain)==1 ); + explain_data_prepare(pArg, pExplain); + exec_prepared_stmt(pArg, pExplain); + explain_data_delete(pArg); + } + } + if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){ + sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0); + } + sqlite3_reset(pStmt); + sqlite3_stmt_explain(pStmt, 0); + restore_debug_trace_modes(); + } + + if( pArg ){ + int bIsExplain = (sqlite3_stmt_isexplain(pStmt)==1); + pArg->cMode = pArg->mode; + if( pArg->autoExplain ){ + if( bIsExplain ){ + pArg->cMode = MODE_Explain; + } + if( sqlite3_stmt_isexplain(pStmt)==2 ){ + pArg->cMode = MODE_EQP; + } + } + + /* If the shell is currently in ".explain" mode, gather the extra + ** data required to add indents to the output.*/ + if( pArg->cMode==MODE_Explain && bIsExplain ){ + explain_data_prepare(pArg, pStmt); + } + } + + bind_prepared_stmt(pArg, pStmt); + exec_prepared_stmt(pArg, pStmt); + explain_data_delete(pArg); + eqp_render(pArg, 0); + + /* print usage stats if stats on */ + if( pArg && pArg->statsOn ){ + display_stats(db, pArg, 0); + } + + /* print loop-counters if required */ + if( pArg && pArg->scanstatsOn ){ + display_scanstats(db, pArg); + } + + /* Finalize the statement just executed. If this fails, save a + ** copy of the error message. Otherwise, set zSql to point to the + ** next statement to execute. */ + rc2 = sqlite3_finalize(pStmt); + if( rc!=SQLITE_NOMEM ) rc = rc2; + if( rc==SQLITE_OK ){ + zSql = zLeftover; + while( IsSpace(zSql[0]) ) zSql++; + }else if( pzErrMsg ){ + *pzErrMsg = save_err_msg(db, "stepping", rc, 0); + } + + /* clear saved stmt handle */ + if( pArg ){ + pArg->pStmt = NULL; + } + } + } /* end while */ + + return rc; +} + +/* +** Release memory previously allocated by tableColumnList(). +*/ +static void freeColumnList(char **azCol){ + int i; + for(i=1; azCol[i]; i++){ + sqlite3_free(azCol[i]); + } + /* azCol[0] is a static string */ + sqlite3_free(azCol); +} + +/* +** Return a list of pointers to strings which are the names of all +** columns in table zTab. The memory to hold the names is dynamically +** allocated and must be released by the caller using a subsequent call +** to freeColumnList(). +** +** The azCol[0] entry is usually NULL. However, if zTab contains a rowid +** value that needs to be preserved, then azCol[0] is filled in with the +** name of the rowid column. +** +** The first regular column in the table is azCol[1]. The list is terminated +** by an entry with azCol[i]==0. +*/ +static char **tableColumnList(ShellState *p, const char *zTab){ + char **azCol = 0; + sqlite3_stmt *pStmt; + char *zSql; + int nCol = 0; + int nAlloc = 0; + int nPK = 0; /* Number of PRIMARY KEY columns seen */ + int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */ + int preserveRowid = ShellHasFlag(p, SHFLG_PreserveRowid); + int rc; + + zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab); + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rc ) return 0; + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + if( nCol>=nAlloc-2 ){ + nAlloc = nAlloc*2 + nCol + 10; + azCol = sqlite3_realloc(azCol, nAlloc*sizeof(azCol[0])); + shell_check_oom(azCol); + } + azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1)); + shell_check_oom(azCol[nCol]); + if( sqlite3_column_int(pStmt, 5) ){ + nPK++; + if( nPK==1 + && sqlite3_stricmp((const char*)sqlite3_column_text(pStmt,2), + "INTEGER")==0 + ){ + isIPK = 1; + }else{ + isIPK = 0; + } + } + } + sqlite3_finalize(pStmt); + if( azCol==0 ) return 0; + azCol[0] = 0; + azCol[nCol+1] = 0; + + /* The decision of whether or not a rowid really needs to be preserved + ** is tricky. We never need to preserve a rowid for a WITHOUT ROWID table + ** or a table with an INTEGER PRIMARY KEY. We are unable to preserve + ** rowids on tables where the rowid is inaccessible because there are other + ** columns in the table named "rowid", "_rowid_", and "oid". + */ + if( preserveRowid && isIPK ){ + /* If a single PRIMARY KEY column with type INTEGER was seen, then it + ** might be an alias for the ROWID. But it might also be a WITHOUT ROWID + ** table or a INTEGER PRIMARY KEY DESC column, neither of which are + ** ROWID aliases. To distinguish these cases, check to see if + ** there is a "pk" entry in "PRAGMA index_list". There will be + ** no "pk" index if the PRIMARY KEY really is an alias for the ROWID. + */ + zSql = sqlite3_mprintf("SELECT 1 FROM pragma_index_list(%Q)" + " WHERE origin='pk'", zTab); + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rc ){ + freeColumnList(azCol); + return 0; + } + rc = sqlite3_step(pStmt); + sqlite3_finalize(pStmt); + preserveRowid = rc==SQLITE_ROW; + } + if( preserveRowid ){ + /* Only preserve the rowid if we can find a name to use for the + ** rowid */ + static char *azRowid[] = { "rowid", "_rowid_", "oid" }; + int i, j; + for(j=0; j<3; j++){ + for(i=1; i<=nCol; i++){ + if( sqlite3_stricmp(azRowid[j],azCol[i])==0 ) break; + } + if( i>nCol ){ + /* At this point, we know that azRowid[j] is not the name of any + ** ordinary column in the table. Verify that azRowid[j] is a valid + ** name for the rowid before adding it to azCol[0]. WITHOUT ROWID + ** tables will fail this last check */ + rc = sqlite3_table_column_metadata(p->db,0,zTab,azRowid[j],0,0,0,0,0); + if( rc==SQLITE_OK ) azCol[0] = azRowid[j]; + break; + } + } + } + return azCol; +} + +/* +** Toggle the reverse_unordered_selects setting. +*/ +static void toggleSelectOrder(sqlite3 *db){ + sqlite3_stmt *pStmt = 0; + int iSetting = 0; + char zStmt[100]; + sqlite3_prepare_v2(db, "PRAGMA reverse_unordered_selects", -1, &pStmt, 0); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + iSetting = sqlite3_column_int(pStmt, 0); + } + sqlite3_finalize(pStmt); + sqlite3_snprintf(sizeof(zStmt), zStmt, + "PRAGMA reverse_unordered_selects(%d)", !iSetting); + sqlite3_exec(db, zStmt, 0, 0, 0); +} + +/* +** This is a different callback routine used for dumping the database. +** Each row received by this callback consists of a table name, +** the table type ("index" or "table") and SQL to create the table. +** This routine should print text sufficient to recreate the table. +*/ +static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ + int rc; + const char *zTable; + const char *zType; + const char *zSql; + ShellState *p = (ShellState *)pArg; + int dataOnly; + int noSys; + + UNUSED_PARAMETER(azNotUsed); + if( nArg!=3 || azArg==0 ) return 0; + zTable = azArg[0]; + zType = azArg[1]; + zSql = azArg[2]; + if( zTable==0 ) return 0; + if( zType==0 ) return 0; + dataOnly = (p->shellFlgs & SHFLG_DumpDataOnly)!=0; + noSys = (p->shellFlgs & SHFLG_DumpNoSys)!=0; + + if( cli_strcmp(zTable, "sqlite_sequence")==0 && !noSys ){ + if( !dataOnly ) raw_printf(p->out, "DELETE FROM sqlite_sequence;\n"); + }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 && !noSys ){ + if( !dataOnly ) raw_printf(p->out, "ANALYZE sqlite_schema;\n"); + }else if( cli_strncmp(zTable, "sqlite_", 7)==0 ){ + return 0; + }else if( dataOnly ){ + /* no-op */ + }else if( cli_strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){ + char *zIns; + if( !p->writableSchema ){ + raw_printf(p->out, "PRAGMA writable_schema=ON;\n"); + p->writableSchema = 1; + } + zIns = sqlite3_mprintf( + "INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)" + "VALUES('table','%q','%q',0,'%q');", + zTable, zTable, zSql); + shell_check_oom(zIns); + utf8_printf(p->out, "%s\n", zIns); + sqlite3_free(zIns); + return 0; + }else{ + printSchemaLine(p->out, zSql, ";\n"); + } + + if( cli_strcmp(zType, "table")==0 ){ + ShellText sSelect; + ShellText sTable; + char **azCol; + int i; + char *savedDestTable; + int savedMode; + + azCol = tableColumnList(p, zTable); + if( azCol==0 ){ + p->nErr++; + return 0; + } + + /* Always quote the table name, even if it appears to be pure ascii, + ** in case it is a keyword. Ex: INSERT INTO "table" ... */ + initText(&sTable); + appendText(&sTable, zTable, quoteChar(zTable)); + /* If preserving the rowid, add a column list after the table name. + ** In other words: "INSERT INTO tab(rowid,a,b,c,...) VALUES(...)" + ** instead of the usual "INSERT INTO tab VALUES(...)". + */ + if( azCol[0] ){ + appendText(&sTable, "(", 0); + appendText(&sTable, azCol[0], 0); + for(i=1; azCol[i]; i++){ + appendText(&sTable, ",", 0); + appendText(&sTable, azCol[i], quoteChar(azCol[i])); + } + appendText(&sTable, ")", 0); + } + + /* Build an appropriate SELECT statement */ + initText(&sSelect); + appendText(&sSelect, "SELECT ", 0); + if( azCol[0] ){ + appendText(&sSelect, azCol[0], 0); + appendText(&sSelect, ",", 0); + } + for(i=1; azCol[i]; i++){ + appendText(&sSelect, azCol[i], quoteChar(azCol[i])); + if( azCol[i+1] ){ + appendText(&sSelect, ",", 0); + } + } + freeColumnList(azCol); + appendText(&sSelect, " FROM ", 0); + appendText(&sSelect, zTable, quoteChar(zTable)); + + savedDestTable = p->zDestTable; + savedMode = p->mode; + p->zDestTable = sTable.z; + p->mode = p->cMode = MODE_Insert; + rc = shell_exec(p, sSelect.z, 0); + if( (rc&0xff)==SQLITE_CORRUPT ){ + raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n"); + toggleSelectOrder(p->db); + shell_exec(p, sSelect.z, 0); + toggleSelectOrder(p->db); + } + p->zDestTable = savedDestTable; + p->mode = savedMode; + freeText(&sTable); + freeText(&sSelect); + if( rc ) p->nErr++; + } + return 0; +} + +/* +** Run zQuery. Use dump_callback() as the callback routine so that +** the contents of the query are output as SQL statements. +** +** If we get a SQLITE_CORRUPT error, rerun the query after appending +** "ORDER BY rowid DESC" to the end. +*/ +static int run_schema_dump_query( + ShellState *p, + const char *zQuery +){ + int rc; + char *zErr = 0; + rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr); + if( rc==SQLITE_CORRUPT ){ + char *zQ2; + int len = strlen30(zQuery); + raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n"); + if( zErr ){ + utf8_printf(p->out, "/****** %s ******/\n", zErr); + sqlite3_free(zErr); + zErr = 0; + } + zQ2 = malloc( len+100 ); + if( zQ2==0 ) return rc; + sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery); + rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr); + if( rc ){ + utf8_printf(p->out, "/****** ERROR: %s ******/\n", zErr); + }else{ + rc = SQLITE_CORRUPT; + } + sqlite3_free(zErr); + free(zQ2); + } + return rc; +} + +/* +** Text of help messages. +** +** The help text for each individual command begins with a line that starts +** with ".". Subsequent lines are supplemental information. +** +** There must be two or more spaces between the end of the command and the +** start of the description of what that command does. +*/ +static const char *(azHelp[]) = { +#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \ + && !defined(SQLITE_SHELL_FIDDLE) + ".archive ... Manage SQL archives", + " Each command must have exactly one of the following options:", + " -c, --create Create a new archive", + " -u, --update Add or update files with changed mtime", + " -i, --insert Like -u but always add even if unchanged", + " -r, --remove Remove files from archive", + " -t, --list List contents of archive", + " -x, --extract Extract files from archive", + " Optional arguments:", + " -v, --verbose Print each filename as it is processed", + " -f FILE, --file FILE Use archive FILE (default is current db)", + " -a FILE, --append FILE Open FILE using the apndvfs VFS", + " -C DIR, --directory DIR Read/extract files from directory DIR", + " -g, --glob Use glob matching for names in archive", + " -n, --dryrun Show the SQL that would have occurred", + " Examples:", + " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar", + " .ar -tf ARCHIVE # List members of ARCHIVE", + " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE", + " See also:", + " http://sqlite.org/cli.html#sqlite_archive_support", +#endif +#ifndef SQLITE_OMIT_AUTHORIZATION + ".auth ON|OFF Show authorizer callbacks", +#endif +#ifndef SQLITE_SHELL_FIDDLE + ".backup ?DB? FILE Backup DB (default \"main\") to FILE", + " Options:", + " --append Use the appendvfs", + " --async Write to FILE without journal and fsync()", +#endif + ".bail on|off Stop after hitting an error. Default OFF", +#ifndef SQLITE_SHELL_FIDDLE + ".cd DIRECTORY Change the working directory to DIRECTORY", +#endif + ".changes on|off Show number of rows changed by SQL", +#ifndef SQLITE_SHELL_FIDDLE + ".check GLOB Fail if output since .testcase does not match", + ".clone NEWDB Clone data into NEWDB from the existing database", +#endif + ".connection [close] [#] Open or close an auxiliary database connection", +#if defined(_WIN32) || defined(WIN32) + ".crnl on|off Translate \\n to \\r\\n. Default ON", +#endif + ".databases List names and files of attached databases", + ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", +#if SQLITE_SHELL_HAVE_RECOVER + ".dbinfo ?DB? Show status information about the database", +#endif + ".dump ?OBJECTS? Render database content as SQL", + " Options:", + " --data-only Output only INSERT statements", + " --newlines Allow unescaped newline characters in output", + " --nosys Omit system tables (ex: \"sqlite_stat1\")", + " --preserve-rowids Include ROWID values in the output", + " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump", + " Additional LIKE patterns can be given in subsequent arguments", + ".echo on|off Turn command echo on or off", + ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN", + " Other Modes:", +#ifdef SQLITE_DEBUG + " test Show raw EXPLAIN QUERY PLAN output", + " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"", +#endif + " trigger Like \"full\" but also show trigger bytecode", +#ifndef SQLITE_SHELL_FIDDLE + ".excel Display the output of next command in spreadsheet", + " --bom Put a UTF8 byte-order mark on intermediate file", +#endif +#ifndef SQLITE_SHELL_FIDDLE + ".exit ?CODE? Exit this program with return-code CODE", +#endif + ".expert EXPERIMENTAL. Suggest indexes for queries", + ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", + ".filectrl CMD ... Run various sqlite3_file_control() operations", + " --schema SCHEMA Use SCHEMA instead of \"main\"", + " --help Show CMD details", + ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", + ".headers on|off Turn display of headers on or off", + ".help ?-all? ?PATTERN? Show help text for PATTERN", +#ifndef SQLITE_SHELL_FIDDLE + ".import FILE TABLE Import data from FILE into TABLE", + " Options:", + " --ascii Use \\037 and \\036 as column and row separators", + " --csv Use , and \\n as column and row separators", + " --skip N Skip the first N rows of input", + " --schema S Target table to be S.TABLE", + " -v \"Verbose\" - increase auxiliary output", + " Notes:", + " * If TABLE does not exist, it is created. The first row of input", + " determines the column names.", + " * If neither --csv or --ascii are used, the input mode is derived", + " from the \".mode\" output mode", + " * If FILE begins with \"|\" then it is a command that generates the", + " input text.", +#endif +#ifndef SQLITE_OMIT_TEST_CONTROL + ",imposter INDEX TABLE Create imposter table TABLE on index INDEX", +#endif + ".indexes ?TABLE? Show names of indexes", + " If TABLE is specified, only show indexes for", + " tables matching TABLE using the LIKE operator.", +#ifdef SQLITE_ENABLE_IOTRACE + ",iotrace FILE Enable I/O diagnostic logging to FILE", +#endif + ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT", + ".lint OPTIONS Report potential schema issues.", + " Options:", + " fkey-indexes Find missing foreign key indexes", +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE) + ".load FILE ?ENTRY? Load an extension library", +#endif +#if !defined(SQLITE_SHELL_FIDDLE) + ".log FILE|on|off Turn logging on or off. FILE can be stderr/stdout", +#else + ".log on|off Turn logging on or off.", +#endif + ".mode MODE ?OPTIONS? Set output mode", + " MODE is one of:", + " ascii Columns/rows delimited by 0x1F and 0x1E", + " box Tables using unicode box-drawing characters", + " csv Comma-separated values", + " column Output in columns. (See .width)", + " html HTML <table> code", + " insert SQL insert statements for TABLE", + " json Results in a JSON array", + " line One value per line", + " list Values delimited by \"|\"", + " markdown Markdown table format", + " qbox Shorthand for \"box --wrap 60 --quote\"", + " quote Escape answers as for SQL", + " table ASCII-art table", + " tabs Tab-separated values", + " tcl TCL list elements", + " OPTIONS: (for columnar modes or insert mode):", + " --wrap N Wrap output lines to no longer than N characters", + " --wordwrap B Wrap or not at word boundaries per B (on/off)", + " --ww Shorthand for \"--wordwrap 1\"", + " --quote Quote output text as SQL literals", + " --noquote Do not quote output text", + " TABLE The name of SQL table used for \"insert\" mode", +#ifndef SQLITE_SHELL_FIDDLE + ".nonce STRING Suspend safe mode for one command if nonce matches", +#endif + ".nullvalue STRING Use STRING in place of NULL values", +#ifndef SQLITE_SHELL_FIDDLE + ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE", + " If FILE begins with '|' then open as a pipe", + " --bom Put a UTF8 byte-order mark at the beginning", + " -e Send output to the system text editor", + " -x Send output as CSV to a spreadsheet (same as \".excel\")", + /* Note that .open is (partially) available in WASM builds but is + ** currently only intended to be used by the fiddle tool, not + ** end users, so is "undocumented." */ + ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE", + " Options:", + " --append Use appendvfs to append database to the end of FILE", +#endif +#ifndef SQLITE_OMIT_DESERIALIZE + " --deserialize Load into memory using sqlite3_deserialize()", + " --hexdb Load the output of \"dbtotxt\" as an in-memory db", + " --maxsize N Maximum size for --hexdb or --deserialized database", +#endif + " --new Initialize FILE to an empty database", + " --nofollow Do not follow symbolic links", + " --readonly Open FILE readonly", + " --zip FILE is a ZIP archive", +#ifndef SQLITE_SHELL_FIDDLE + ".output ?FILE? Send output to FILE or stdout if FILE is omitted", + " If FILE begins with '|' then open it as a pipe.", + " Options:", + " --bom Prefix output with a UTF8 byte-order mark", + " -e Send output to the system text editor", + " -x Send output as CSV to a spreadsheet", +#endif + ".parameter CMD ... Manage SQL parameter bindings", + " clear Erase all bindings", + " init Initialize the TEMP table that holds bindings", + " list List the current parameter bindings", + " set PARAMETER VALUE Given SQL parameter PARAMETER a value of VALUE", + " PARAMETER should start with one of: $ : @ ?", + " unset PARAMETER Remove PARAMETER from the binding table", + ".print STRING... Print literal STRING", +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + ".progress N Invoke progress handler after every N opcodes", + " --limit N Interrupt after N progress callbacks", + " --once Do no more than one progress interrupt", + " --quiet|-q No output except at interrupts", + " --reset Reset the count for each input and interrupt", +#endif + ".prompt MAIN CONTINUE Replace the standard prompts", +#ifndef SQLITE_SHELL_FIDDLE + ".quit Stop interpreting input stream, exit if primary.", + ".read FILE Read input from FILE or command output", + " If FILE begins with \"|\", it is a command that generates the input.", +#endif +#if SQLITE_SHELL_HAVE_RECOVER + ".recover Recover as much data as possible from corrupt db.", + " --ignore-freelist Ignore pages that appear to be on db freelist", + " --lost-and-found TABLE Alternative name for the lost-and-found table", + " --no-rowids Do not attempt to recover rowid values", + " that are not also INTEGER PRIMARY KEYs", +#endif +#ifndef SQLITE_SHELL_FIDDLE + ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", + ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)", +#endif + ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off", + ".schema ?PATTERN? Show the CREATE statements matching PATTERN", + " Options:", + " --indent Try to pretty-print the schema", + " --nosys Omit objects whose names start with \"sqlite_\"", + ",selftest ?OPTIONS? Run tests defined in the SELFTEST table", + " Options:", + " --init Create a new SELFTEST table", + " -v Verbose output", + ".separator COL ?ROW? Change the column and row separators", +#if defined(SQLITE_ENABLE_SESSION) + ".session ?NAME? CMD ... Create or control sessions", + " Subcommands:", + " attach TABLE Attach TABLE", + " changeset FILE Write a changeset into FILE", + " close Close one session", + " enable ?BOOLEAN? Set or query the enable bit", + " filter GLOB... Reject tables matching GLOBs", + " indirect ?BOOLEAN? Mark or query the indirect status", + " isempty Query whether the session is empty", + " list List currently open session names", + " open DB NAME Open a new session on DB", + " patchset FILE Write a patchset into FILE", + " If ?NAME? is omitted, the first defined session is used.", +#endif + ".sha3sum ... Compute a SHA3 hash of database content", + " Options:", + " --schema Also hash the sqlite_schema table", + " --sha3-224 Use the sha3-224 algorithm", + " --sha3-256 Use the sha3-256 algorithm (default)", + " --sha3-384 Use the sha3-384 algorithm", + " --sha3-512 Use the sha3-512 algorithm", + " Any other argument is a LIKE pattern for tables to hash", +#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) + ".shell CMD ARGS... Run CMD ARGS... in a system shell", +#endif + ".show Show the current values for various settings", + ".stats ?ARG? Show stats or turn stats on or off", + " off Turn off automatic stat display", + " on Turn on automatic stat display", + " stmt Show statement stats", + " vmstep Show the virtual machine step count only", +#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) + ".system CMD ARGS... Run CMD ARGS... in a system shell", +#endif + ".tables ?TABLE? List names of tables matching LIKE pattern TABLE", +#ifndef SQLITE_SHELL_FIDDLE + ",testcase NAME Begin redirecting output to 'testcase-out.txt'", +#endif + ",testctrl CMD ... Run various sqlite3_test_control() operations", + " Run \".testctrl\" with no arguments for details", + ".timeout MS Try opening locked tables for MS milliseconds", + ".timer on|off Turn SQL timer on or off", +#ifndef SQLITE_OMIT_TRACE + ".trace ?OPTIONS? Output each SQL statement as it is run", + " FILE Send output to FILE", + " stdout Send output to stdout", + " stderr Send output to stderr", + " off Disable tracing", + " --expanded Expand query parameters", +#ifdef SQLITE_ENABLE_NORMALIZE + " --normalized Normal the SQL statements", +#endif + " --plain Show SQL as it is input", + " --stmt Trace statement execution (SQLITE_TRACE_STMT)", + " --profile Profile statements (SQLITE_TRACE_PROFILE)", + " --row Trace each row (SQLITE_TRACE_ROW)", + " --close Trace connection close (SQLITE_TRACE_CLOSE)", +#endif /* SQLITE_OMIT_TRACE */ +#ifdef SQLITE_DEBUG + ".unmodule NAME ... Unregister virtual table modules", + " --allexcept Unregister everything except those named", +#endif + ".version Show source, library and compiler versions", + ".vfsinfo ?AUX? Information about the top-level VFS", + ".vfslist List all available VFSes", + ".vfsname ?AUX? Print the name of the VFS stack", + ".width NUM1 NUM2 ... Set minimum column widths for columnar output", + " Negative values right-justify", +}; + +/* +** Output help text. +** +** zPattern describes the set of commands for which help text is provided. +** If zPattern is NULL, then show all commands, but only give a one-line +** description of each. +** +** Return the number of matches. +*/ +static int showHelp(FILE *out, const char *zPattern){ + int i = 0; + int j = 0; + int n = 0; + char *zPat; + if( zPattern==0 + || zPattern[0]=='0' + || cli_strcmp(zPattern,"-a")==0 + || cli_strcmp(zPattern,"-all")==0 + || cli_strcmp(zPattern,"--all")==0 + ){ + enum HelpWanted { HW_NoCull = 0, HW_SummaryOnly = 1, HW_Undoc = 2 }; + enum HelpHave { HH_Undoc = 2, HH_Summary = 1, HH_More = 0 }; + /* Show all or most commands + ** *zPattern==0 => summary of documented commands only + ** *zPattern=='0' => whole help for undocumented commands + ** Otherwise => whole help for documented commands + */ + enum HelpWanted hw = HW_SummaryOnly; + enum HelpHave hh = HH_More; + if( zPattern!=0 ){ + hw = (*zPattern=='0')? HW_NoCull|HW_Undoc : HW_NoCull; + } + for(i=0; i<ArraySize(azHelp); i++){ + switch( azHelp[i][0] ){ + case ',': + hh = HH_Summary|HH_Undoc; + break; + case '.': + hh = HH_Summary; + break; + default: + hh &= ~HH_Summary; + break; + } + if( ((hw^hh)&HH_Undoc)==0 ){ + if( (hh&HH_Summary)!=0 ){ + utf8_printf(out, ".%s\n", azHelp[i]+1); + ++n; + }else if( (hw&HW_SummaryOnly)==0 ){ + utf8_printf(out, "%s\n", azHelp[i]); + } + } + } + }else{ + /* Seek documented commands for which zPattern is an exact prefix */ + zPat = sqlite3_mprintf(".%s*", zPattern); + shell_check_oom(zPat); + for(i=0; i<ArraySize(azHelp); i++){ + if( sqlite3_strglob(zPat, azHelp[i])==0 ){ + utf8_printf(out, "%s\n", azHelp[i]); + j = i+1; + n++; + } + } + sqlite3_free(zPat); + if( n ){ + if( n==1 ){ + /* when zPattern is a prefix of exactly one command, then include + ** the details of that command, which should begin at offset j */ + while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){ + utf8_printf(out, "%s\n", azHelp[j]); + j++; + } + } + return n; + } + /* Look for documented commands that contain zPattern anywhere. + ** Show complete text of all documented commands that match. */ + zPat = sqlite3_mprintf("%%%s%%", zPattern); + shell_check_oom(zPat); + for(i=0; i<ArraySize(azHelp); i++){ + if( azHelp[i][0]==',' ){ + while( i<ArraySize(azHelp)-1 && azHelp[i+1][0]==' ' ) ++i; + continue; + } + if( azHelp[i][0]=='.' ) j = i; + if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){ + utf8_printf(out, "%s\n", azHelp[j]); + while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){ + j++; + utf8_printf(out, "%s\n", azHelp[j]); + } + i = j; + n++; + } + } + sqlite3_free(zPat); + } + return n; +} + +/* Forward reference */ +static int process_input(ShellState *p); + +/* +** Read the content of file zName into memory obtained from sqlite3_malloc64() +** and return a pointer to the buffer. The caller is responsible for freeing +** the memory. +** +** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes +** read. +** +** For convenience, a nul-terminator byte is always appended to the data read +** from the file before the buffer is returned. This byte is not included in +** the final value of (*pnByte), if applicable. +** +** NULL is returned if any error is encountered. The final value of *pnByte +** is undefined in this case. +*/ +static char *readFile(const char *zName, int *pnByte){ + FILE *in = fopen(zName, "rb"); + long nIn; + size_t nRead; + char *pBuf; + int rc; + if( in==0 ) return 0; + rc = fseek(in, 0, SEEK_END); + if( rc!=0 ){ + raw_printf(stderr, "Error: '%s' not seekable\n", zName); + fclose(in); + return 0; + } + nIn = ftell(in); + rewind(in); + pBuf = sqlite3_malloc64( nIn+1 ); + if( pBuf==0 ){ + raw_printf(stderr, "Error: out of memory\n"); + fclose(in); + return 0; + } + nRead = fread(pBuf, nIn, 1, in); + fclose(in); + if( nRead!=1 ){ + sqlite3_free(pBuf); + raw_printf(stderr, "Error: cannot read '%s'\n", zName); + return 0; + } + pBuf[nIn] = 0; + if( pnByte ) *pnByte = nIn; + return pBuf; +} + +#if defined(SQLITE_ENABLE_SESSION) +/* +** Close a single OpenSession object and release all of its associated +** resources. +*/ +static void session_close(OpenSession *pSession){ + int i; + sqlite3session_delete(pSession->p); + sqlite3_free(pSession->zName); + for(i=0; i<pSession->nFilter; i++){ + sqlite3_free(pSession->azFilter[i]); + } + sqlite3_free(pSession->azFilter); + memset(pSession, 0, sizeof(OpenSession)); +} +#endif + +/* +** Close all OpenSession objects and release all associated resources. +*/ +#if defined(SQLITE_ENABLE_SESSION) +static void session_close_all(ShellState *p, int i){ + int j; + struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i]; + for(j=0; j<pAuxDb->nSession; j++){ + session_close(&pAuxDb->aSession[j]); + } + pAuxDb->nSession = 0; +} +#else +# define session_close_all(X,Y) +#endif + +/* +** Implementation of the xFilter function for an open session. Omit +** any tables named by ".session filter" but let all other table through. +*/ +#if defined(SQLITE_ENABLE_SESSION) +static int session_filter(void *pCtx, const char *zTab){ + OpenSession *pSession = (OpenSession*)pCtx; + int i; + for(i=0; i<pSession->nFilter; i++){ + if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0; + } + return 1; +} +#endif + +/* +** Try to deduce the type of file for zName based on its content. Return +** one of the SHELL_OPEN_* constants. +** +** If the file does not exist or is empty but its name looks like a ZIP +** archive and the dfltZip flag is true, then assume it is a ZIP archive. +** Otherwise, assume an ordinary database regardless of the filename if +** the type cannot be determined from content. +*/ +int deduceDatabaseType(const char *zName, int dfltZip){ + FILE *f = fopen(zName, "rb"); + size_t n; + int rc = SHELL_OPEN_UNSPEC; + char zBuf[100]; + if( f==0 ){ + if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ + return SHELL_OPEN_ZIPFILE; + }else{ + return SHELL_OPEN_NORMAL; + } + } + n = fread(zBuf, 16, 1, f); + if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){ + fclose(f); + return SHELL_OPEN_NORMAL; + } + fseek(f, -25, SEEK_END); + n = fread(zBuf, 25, 1, f); + if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){ + rc = SHELL_OPEN_APPENDVFS; + }else{ + fseek(f, -22, SEEK_END); + n = fread(zBuf, 22, 1, f); + if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05 + && zBuf[3]==0x06 ){ + rc = SHELL_OPEN_ZIPFILE; + }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ + rc = SHELL_OPEN_ZIPFILE; + } + } + fclose(f); + return rc; +} + +#ifndef SQLITE_OMIT_DESERIALIZE +/* +** Reconstruct an in-memory database using the output from the "dbtotxt" +** program. Read content from the file in p->aAuxDb[].zDbFilename. +** If p->aAuxDb[].zDbFilename is 0, then read from standard input. +*/ +static unsigned char *readHexDb(ShellState *p, int *pnData){ + unsigned char *a = 0; + int nLine; + int n = 0; + int pgsz = 0; + int iOffset = 0; + int j, k; + int rc; + FILE *in; + const char *zDbFilename = p->pAuxDb->zDbFilename; + unsigned int x[16]; + char zLine[1000]; + if( zDbFilename ){ + in = fopen(zDbFilename, "r"); + if( in==0 ){ + utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename); + return 0; + } + nLine = 0; + }else{ + in = p->in; + nLine = p->lineno; + if( in==0 ) in = stdin; + } + *pnData = 0; + nLine++; + if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error; + rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz); + if( rc!=2 ) goto readHexDb_error; + if( n<0 ) goto readHexDb_error; + if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error; + n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */ + a = sqlite3_malloc( n ? n : 1 ); + shell_check_oom(a); + memset(a, 0, n); + if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ + utf8_printf(stderr, "invalid pagesize\n"); + goto readHexDb_error; + } + for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){ + rc = sscanf(zLine, "| page %d offset %d", &j, &k); + if( rc==2 ){ + iOffset = k; + continue; + } + if( cli_strncmp(zLine, "| end ", 6)==0 ){ + break; + } + rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], + &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]); + if( rc==17 ){ + k = iOffset+j; + if( k+16<=n && k>=0 ){ + int ii; + for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff; + } + } + } + *pnData = n; + if( in!=p->in ){ + fclose(in); + }else{ + p->lineno = nLine; + } + return a; + +readHexDb_error: + if( in!=p->in ){ + fclose(in); + }else{ + while( fgets(zLine, sizeof(zLine), p->in)!=0 ){ + nLine++; + if(cli_strncmp(zLine, "| end ", 6)==0 ) break; + } + p->lineno = nLine; + } + sqlite3_free(a); + utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine); + return 0; +} +#endif /* SQLITE_OMIT_DESERIALIZE */ + +/* +** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. +*/ +static void shellUSleepFunc( + sqlite3_context *context, + int argcUnused, + sqlite3_value **argv +){ + int sleep = sqlite3_value_int(argv[0]); + (void)argcUnused; + sqlite3_sleep(sleep/1000); + sqlite3_result_int(context, sleep); +} + +/* Flags for open_db(). +** +** The default behavior of open_db() is to exit(1) if the database fails to +** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error +** but still returns without calling exit. +** +** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a +** ZIP archive if the file does not exist or is empty and its name matches +** the *.zip pattern. +*/ +#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */ +#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */ + +/* +** Make sure the database is open. If it is not, then open it. If +** the database fails to open, print an error message and exit. +*/ +static void open_db(ShellState *p, int openFlags){ + if( p->db==0 ){ + const char *zDbFilename = p->pAuxDb->zDbFilename; + if( p->openMode==SHELL_OPEN_UNSPEC ){ + if( zDbFilename==0 || zDbFilename[0]==0 ){ + p->openMode = SHELL_OPEN_NORMAL; + }else{ + p->openMode = (u8)deduceDatabaseType(zDbFilename, + (openFlags & OPEN_DB_ZIPFILE)!=0); + } + } + switch( p->openMode ){ + case SHELL_OPEN_APPENDVFS: { + sqlite3_open_v2(zDbFilename, &p->db, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs"); + break; + } + case SHELL_OPEN_HEXDB: + case SHELL_OPEN_DESERIALIZE: { + sqlite3_open(0, &p->db); + break; + } + case SHELL_OPEN_ZIPFILE: { + sqlite3_open(":memory:", &p->db); + break; + } + case SHELL_OPEN_READONLY: { + sqlite3_open_v2(zDbFilename, &p->db, + SQLITE_OPEN_READONLY|p->openFlags, 0); + break; + } + case SHELL_OPEN_UNSPEC: + case SHELL_OPEN_NORMAL: { + sqlite3_open_v2(zDbFilename, &p->db, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0); + break; + } + } + globalDb = p->db; + if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ + utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", + zDbFilename, sqlite3_errmsg(p->db)); + if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){ + exit(1); + } + sqlite3_close(p->db); + sqlite3_open(":memory:", &p->db); + if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ + utf8_printf(stderr, + "Also: unable to open substitute in-memory database.\n" + ); + exit(1); + }else{ + utf8_printf(stderr, + "Notice: using substitute in-memory database instead of \"%s\"\n", + zDbFilename); + } + } + sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0); + + /* Reflect the use or absence of --unsafe-testing invocation. */ + { + int testmode_on = ShellHasFlag(p,SHFLG_TestingMode); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_TRUSTED_SCHEMA, testmode_on,0); + sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0); + } + +#ifndef SQLITE_OMIT_LOAD_EXTENSION + sqlite3_enable_load_extension(p->db, 1); +#endif + sqlite3_shathree_init(p->db, 0, 0); + sqlite3_uint_init(p->db, 0, 0); + sqlite3_decimal_init(p->db, 0, 0); + sqlite3_base64_init(p->db, 0, 0); + sqlite3_base85_init(p->db, 0, 0); + sqlite3_regexp_init(p->db, 0, 0); + sqlite3_ieee_init(p->db, 0, 0); + sqlite3_series_init(p->db, 0, 0); +#ifndef SQLITE_SHELL_FIDDLE + sqlite3_fileio_init(p->db, 0, 0); + sqlite3_completion_init(p->db, 0, 0); +#endif +#ifdef SQLITE_HAVE_ZLIB + if( !p->bSafeModePersist ){ + sqlite3_zipfile_init(p->db, 0, 0); + sqlite3_sqlar_init(p->db, 0, 0); + } +#endif +#ifdef SQLITE_SHELL_EXTFUNCS + /* Create a preprocessing mechanism for extensions to make + * their own provisions for being built into the shell. + * This is a short-span macro. See further below for usage. + */ +#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant +#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant) + /* Let custom-included extensions get their ..._init() called. + * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause + * the extension's sqlite3_*_init( db, pzErrorMsg, pApi ) + * initialization routine to be called. + */ + { + int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db); + /* Let custom-included extensions expose their functionality. + * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause + * the SQL functions, virtual tables, collating sequences or + * VFS's implemented by the extension to be registered. + */ + if( irc==SQLITE_OK + || irc==SQLITE_OK_LOAD_PERMANENTLY ){ + SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0); + } +#undef SHELL_SUB_MACRO +#undef SHELL_SUBMACRO + } +#endif + + sqlite3_create_function(p->db, "strtod", 1, SQLITE_UTF8, 0, + shellStrtod, 0, 0); + sqlite3_create_function(p->db, "dtostr", 1, SQLITE_UTF8, 0, + shellDtostr, 0, 0); + sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0, + shellDtostr, 0, 0); + sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, + shellAddSchemaName, 0, 0); + sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, + shellModuleSchema, 0, 0); + sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, + shellPutsFunc, 0, 0); + sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, + shellUSleepFunc, 0, 0); +#ifndef SQLITE_NOHAVE_SYSTEM + sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0, + editFunc, 0, 0); + sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, + editFunc, 0, 0); +#endif + + if( p->openMode==SHELL_OPEN_ZIPFILE ){ + char *zSql = sqlite3_mprintf( + "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); + shell_check_oom(zSql); + sqlite3_exec(p->db, zSql, 0, 0, 0); + sqlite3_free(zSql); + } +#ifndef SQLITE_OMIT_DESERIALIZE + else + if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){ + int rc; + int nData = 0; + unsigned char *aData; + if( p->openMode==SHELL_OPEN_DESERIALIZE ){ + aData = (unsigned char*)readFile(zDbFilename, &nData); + }else{ + aData = readHexDb(p, &nData); + } + if( aData==0 ){ + return; + } + rc = sqlite3_deserialize(p->db, "main", aData, nData, nData, + SQLITE_DESERIALIZE_RESIZEABLE | + SQLITE_DESERIALIZE_FREEONCLOSE); + if( rc ){ + utf8_printf(stderr, "Error: sqlite3_deserialize() returns %d\n", rc); + } + if( p->szMax>0 ){ + sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax); + } + } +#endif + } + if( p->db!=0 ){ + if( p->bSafeModePersist ){ + sqlite3_set_authorizer(p->db, safeModeAuth, p); + } + sqlite3_db_config( + p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 + ); + } +} + +/* +** Attempt to close the database connection. Report errors. +*/ +void close_db(sqlite3 *db){ + int rc = sqlite3_close(db); + if( rc ){ + utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", + rc, sqlite3_errmsg(db)); + } +} + +#if HAVE_READLINE || HAVE_EDITLINE +/* +** Readline completion callbacks +*/ +static char *readline_completion_generator(const char *text, int state){ + static sqlite3_stmt *pStmt = 0; + char *zRet; + if( state==0 ){ + char *zSql; + sqlite3_finalize(pStmt); + zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" + " FROM completion(%Q) ORDER BY 1", text); + shell_check_oom(zSql); + sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + } + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *z = (const char*)sqlite3_column_text(pStmt,0); + zRet = z ? strdup(z) : 0; + }else{ + sqlite3_finalize(pStmt); + pStmt = 0; + zRet = 0; + } + return zRet; +} +static char **readline_completion(const char *zText, int iStart, int iEnd){ + (void)iStart; + (void)iEnd; + rl_attempted_completion_over = 1; + return rl_completion_matches(zText, readline_completion_generator); +} + +#elif HAVE_LINENOISE +/* +** Linenoise completion callback +*/ +static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ + i64 nLine = strlen(zLine); + i64 i, iStart; + sqlite3_stmt *pStmt = 0; + char *zSql; + char zBuf[1000]; + + if( nLine>(i64)sizeof(zBuf)-30 ) return; + if( zLine[0]=='.' || zLine[0]=='#') return; + for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} + if( i==nLine-1 ) return; + iStart = i+1; + memcpy(zBuf, zLine, iStart); + zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" + " FROM completion(%Q,%Q) ORDER BY 1", + &zLine[iStart], zLine); + shell_check_oom(zSql); + sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0); + int nCompletion = sqlite3_column_bytes(pStmt, 0); + if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){ + memcpy(zBuf+iStart, zCompletion, nCompletion+1); + linenoiseAddCompletion(lc, zBuf); + } + } + sqlite3_finalize(pStmt); +} +#endif + +/* +** Do C-language style dequoting. +** +** \a -> alarm +** \b -> backspace +** \t -> tab +** \n -> newline +** \v -> vertical tab +** \f -> form feed +** \r -> carriage return +** \s -> space +** \" -> " +** \' -> ' +** \\ -> backslash +** \NNN -> ascii character NNN in octal +** \xHH -> ascii character HH in hexadecimal +*/ +static void resolve_backslashes(char *z){ + int i, j; + char c; + while( *z && *z!='\\' ) z++; + for(i=j=0; (c = z[i])!=0; i++, j++){ + if( c=='\\' && z[i+1]!=0 ){ + c = z[++i]; + if( c=='a' ){ + c = '\a'; + }else if( c=='b' ){ + c = '\b'; + }else if( c=='t' ){ + c = '\t'; + }else if( c=='n' ){ + c = '\n'; + }else if( c=='v' ){ + c = '\v'; + }else if( c=='f' ){ + c = '\f'; + }else if( c=='r' ){ + c = '\r'; + }else if( c=='"' ){ + c = '"'; + }else if( c=='\'' ){ + c = '\''; + }else if( c=='\\' ){ + c = '\\'; + }else if( c=='x' ){ + int nhd = 0, hdv; + u8 hv = 0; + while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){ + hv = (u8)((hv<<4)|hdv); + ++nhd; + } + i += nhd; + c = (u8)hv; + }else if( c>='0' && c<='7' ){ + c -= '0'; + if( z[i+1]>='0' && z[i+1]<='7' ){ + i++; + c = (c<<3) + z[i] - '0'; + if( z[i+1]>='0' && z[i+1]<='7' ){ + i++; + c = (c<<3) + z[i] - '0'; + } + } + } + } + z[j] = c; + } + if( j<i ) z[j] = 0; +} + +/* +** Interpret zArg as either an integer or a boolean value. Return 1 or 0 +** for TRUE and FALSE. Return the integer value if appropriate. +*/ +static int booleanValue(const char *zArg){ + int i; + if( zArg[0]=='0' && zArg[1]=='x' ){ + for(i=2; hexDigitValue(zArg[i])>=0; i++){} + }else{ + for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){} + } + if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff); + if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){ + return 1; + } + if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){ + return 0; + } + utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", + zArg); + return 0; +} + +/* +** Set or clear a shell flag according to a boolean value. +*/ +static void setOrClearFlag(ShellState *p, unsigned mFlag, const char *zArg){ + if( booleanValue(zArg) ){ + ShellSetFlag(p, mFlag); + }else{ + ShellClearFlag(p, mFlag); + } +} + +/* +** Close an output file, assuming it is not stderr or stdout +*/ +static void output_file_close(FILE *f){ + if( f && f!=stdout && f!=stderr ) fclose(f); +} + +/* +** Try to open an output file. The names "stdout" and "stderr" are +** recognized and do the right thing. NULL is returned if the output +** filename is "off". +*/ +static FILE *output_file_open(const char *zFile, int bTextMode){ + FILE *f; + if( cli_strcmp(zFile,"stdout")==0 ){ + f = stdout; + }else if( cli_strcmp(zFile, "stderr")==0 ){ + f = stderr; + }else if( cli_strcmp(zFile, "off")==0 ){ + f = 0; + }else{ + f = fopen(zFile, bTextMode ? "w" : "wb"); + if( f==0 ){ + utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); + } + } + return f; +} + +#ifndef SQLITE_OMIT_TRACE +/* +** A routine for handling output from sqlite3_trace(). +*/ +static int sql_trace_callback( + unsigned mType, /* The trace type */ + void *pArg, /* The ShellState pointer */ + void *pP, /* Usually a pointer to sqlite_stmt */ + void *pX /* Auxiliary output */ +){ + ShellState *p = (ShellState*)pArg; + sqlite3_stmt *pStmt; + const char *zSql; + i64 nSql; + if( p->traceOut==0 ) return 0; + if( mType==SQLITE_TRACE_CLOSE ){ + utf8_printf(p->traceOut, "-- closing database connection\n"); + return 0; + } + if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){ + zSql = (const char*)pX; + }else{ + pStmt = (sqlite3_stmt*)pP; + switch( p->eTraceType ){ + case SHELL_TRACE_EXPANDED: { + zSql = sqlite3_expanded_sql(pStmt); + break; + } +#ifdef SQLITE_ENABLE_NORMALIZE + case SHELL_TRACE_NORMALIZED: { + zSql = sqlite3_normalized_sql(pStmt); + break; + } +#endif + default: { + zSql = sqlite3_sql(pStmt); + break; + } + } + } + if( zSql==0 ) return 0; + nSql = strlen(zSql); + if( nSql>1000000000 ) nSql = 1000000000; + while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; } + switch( mType ){ + case SQLITE_TRACE_ROW: + case SQLITE_TRACE_STMT: { + utf8_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql); + break; + } + case SQLITE_TRACE_PROFILE: { + sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0; + utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec); + break; + } + } + return 0; +} +#endif + +/* +** A no-op routine that runs with the ".breakpoint" doc-command. This is +** a useful spot to set a debugger breakpoint. +** +** This routine does not do anything practical. The code are there simply +** to prevent the compiler from optimizing this routine out. +*/ +static void test_breakpoint(void){ + static unsigned int nCall = 0; + if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n"); +} + +/* +** An object used to read a CSV and other files for import. +*/ +typedef struct ImportCtx ImportCtx; +struct ImportCtx { + const char *zFile; /* Name of the input file */ + FILE *in; /* Read the CSV text from this input stream */ + int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ + char *z; /* Accumulated text for a field */ + int n; /* Number of bytes in z */ + int nAlloc; /* Space allocated for z[] */ + int nLine; /* Current line number */ + int nRow; /* Number of rows imported */ + int nErr; /* Number of errors encountered */ + int bNotFirst; /* True if one or more bytes already read */ + int cTerm; /* Character that terminated the most recent field */ + int cColSep; /* The column separator character. (Usually ",") */ + int cRowSep; /* The row separator character. (Usually "\n") */ +}; + +/* Clean up resourced used by an ImportCtx */ +static void import_cleanup(ImportCtx *p){ + if( p->in!=0 && p->xCloser!=0 ){ + p->xCloser(p->in); + p->in = 0; + } + sqlite3_free(p->z); + p->z = 0; +} + +/* Append a single byte to z[] */ +static void import_append_char(ImportCtx *p, int c){ + if( p->n+1>=p->nAlloc ){ + p->nAlloc += p->nAlloc + 100; + p->z = sqlite3_realloc64(p->z, p->nAlloc); + shell_check_oom(p->z); + } + p->z[p->n++] = (char)c; +} + +/* Read a single field of CSV text. Compatible with rfc4180 and extended +** with the option of having a separator other than ",". +** +** + Input comes from p->in. +** + Store results in p->z of length p->n. Space to hold p->z comes +** from sqlite3_malloc64(). +** + Use p->cSep as the column separator. The default is ",". +** + Use p->rSep as the row separator. The default is "\n". +** + Keep track of the line number in p->nLine. +** + Store the character that terminates the field in p->cTerm. Store +** EOF on end-of-file. +** + Report syntax errors on stderr +*/ +static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ + int c; + int cSep = (u8)p->cColSep; + int rSep = (u8)p->cRowSep; + p->n = 0; + c = fgetc(p->in); + if( c==EOF || seenInterrupt ){ + p->cTerm = EOF; + return 0; + } + if( c=='"' ){ + int pc, ppc; + int startLine = p->nLine; + int cQuote = c; + pc = ppc = 0; + while( 1 ){ + c = fgetc(p->in); + if( c==rSep ) p->nLine++; + if( c==cQuote ){ + if( pc==cQuote ){ + pc = 0; + continue; + } + } + if( (c==cSep && pc==cQuote) + || (c==rSep && pc==cQuote) + || (c==rSep && pc=='\r' && ppc==cQuote) + || (c==EOF && pc==cQuote) + ){ + do{ p->n--; }while( p->z[p->n]!=cQuote ); + p->cTerm = c; + break; + } + if( pc==cQuote && c!='\r' ){ + utf8_printf(stderr, "%s:%d: unescaped %c character\n", + p->zFile, p->nLine, cQuote); + } + if( c==EOF ){ + utf8_printf(stderr, "%s:%d: unterminated %c-quoted field\n", + p->zFile, startLine, cQuote); + p->cTerm = c; + break; + } + import_append_char(p, c); + ppc = pc; + pc = c; + } + }else{ + /* If this is the first field being parsed and it begins with the + ** UTF-8 BOM (0xEF BB BF) then skip the BOM */ + if( (c&0xff)==0xef && p->bNotFirst==0 ){ + import_append_char(p, c); + c = fgetc(p->in); + if( (c&0xff)==0xbb ){ + import_append_char(p, c); + c = fgetc(p->in); + if( (c&0xff)==0xbf ){ + p->bNotFirst = 1; + p->n = 0; + return csv_read_one_field(p); + } + } + } + while( c!=EOF && c!=cSep && c!=rSep ){ + import_append_char(p, c); + c = fgetc(p->in); + } + if( c==rSep ){ + p->nLine++; + if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--; + } + p->cTerm = c; + } + if( p->z ) p->z[p->n] = 0; + p->bNotFirst = 1; + return p->z; +} + +/* Read a single field of ASCII delimited text. +** +** + Input comes from p->in. +** + Store results in p->z of length p->n. Space to hold p->z comes +** from sqlite3_malloc64(). +** + Use p->cSep as the column separator. The default is "\x1F". +** + Use p->rSep as the row separator. The default is "\x1E". +** + Keep track of the row number in p->nLine. +** + Store the character that terminates the field in p->cTerm. Store +** EOF on end-of-file. +** + Report syntax errors on stderr +*/ +static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){ + int c; + int cSep = (u8)p->cColSep; + int rSep = (u8)p->cRowSep; + p->n = 0; + c = fgetc(p->in); + if( c==EOF || seenInterrupt ){ + p->cTerm = EOF; + return 0; + } + while( c!=EOF && c!=cSep && c!=rSep ){ + import_append_char(p, c); + c = fgetc(p->in); + } + if( c==rSep ){ + p->nLine++; + } + p->cTerm = c; + if( p->z ) p->z[p->n] = 0; + return p->z; +} + +/* +** Try to transfer data for table zTable. If an error is seen while +** moving forward, try to go backwards. The backwards movement won't +** work for WITHOUT ROWID tables. +*/ +static void tryToCloneData( + ShellState *p, + sqlite3 *newDb, + const char *zTable +){ + sqlite3_stmt *pQuery = 0; + sqlite3_stmt *pInsert = 0; + char *zQuery = 0; + char *zInsert = 0; + int rc; + int i, j, n; + int nTable = strlen30(zTable); + int k = 0; + int cnt = 0; + const int spinRate = 10000; + + zQuery = sqlite3_mprintf("SELECT * FROM \"%w\"", zTable); + shell_check_oom(zQuery); + rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); + if( rc ){ + utf8_printf(stderr, "Error %d: %s on [%s]\n", + sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), + zQuery); + goto end_data_xfer; + } + n = sqlite3_column_count(pQuery); + zInsert = sqlite3_malloc64(200 + nTable + n*3); + shell_check_oom(zInsert); + sqlite3_snprintf(200+nTable,zInsert, + "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable); + i = strlen30(zInsert); + for(j=1; j<n; j++){ + memcpy(zInsert+i, ",?", 2); + i += 2; + } + memcpy(zInsert+i, ");", 3); + rc = sqlite3_prepare_v2(newDb, zInsert, -1, &pInsert, 0); + if( rc ){ + utf8_printf(stderr, "Error %d: %s on [%s]\n", + sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb), + zInsert); + goto end_data_xfer; + } + for(k=0; k<2; k++){ + while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ + for(i=0; i<n; i++){ + switch( sqlite3_column_type(pQuery, i) ){ + case SQLITE_NULL: { + sqlite3_bind_null(pInsert, i+1); + break; + } + case SQLITE_INTEGER: { + sqlite3_bind_int64(pInsert, i+1, sqlite3_column_int64(pQuery,i)); + break; + } + case SQLITE_FLOAT: { + sqlite3_bind_double(pInsert, i+1, sqlite3_column_double(pQuery,i)); + break; + } + case SQLITE_TEXT: { + sqlite3_bind_text(pInsert, i+1, + (const char*)sqlite3_column_text(pQuery,i), + -1, SQLITE_STATIC); + break; + } + case SQLITE_BLOB: { + sqlite3_bind_blob(pInsert, i+1, sqlite3_column_blob(pQuery,i), + sqlite3_column_bytes(pQuery,i), + SQLITE_STATIC); + break; + } + } + } /* End for */ + rc = sqlite3_step(pInsert); + if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){ + utf8_printf(stderr, "Error %d: %s\n", sqlite3_extended_errcode(newDb), + sqlite3_errmsg(newDb)); + } + sqlite3_reset(pInsert); + cnt++; + if( (cnt%spinRate)==0 ){ + printf("%c\b", "|/-\\"[(cnt/spinRate)%4]); + fflush(stdout); + } + } /* End while */ + if( rc==SQLITE_DONE ) break; + sqlite3_finalize(pQuery); + sqlite3_free(zQuery); + zQuery = sqlite3_mprintf("SELECT * FROM \"%w\" ORDER BY rowid DESC;", + zTable); + shell_check_oom(zQuery); + rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); + if( rc ){ + utf8_printf(stderr, "Warning: cannot step \"%s\" backwards", zTable); + break; + } + } /* End for(k=0...) */ + +end_data_xfer: + sqlite3_finalize(pQuery); + sqlite3_finalize(pInsert); + sqlite3_free(zQuery); + sqlite3_free(zInsert); +} + + +/* +** Try to transfer all rows of the schema that match zWhere. For +** each row, invoke xForEach() on the object defined by that row. +** If an error is encountered while moving forward through the +** sqlite_schema table, try again moving backwards. +*/ +static void tryToCloneSchema( + ShellState *p, + sqlite3 *newDb, + const char *zWhere, + void (*xForEach)(ShellState*,sqlite3*,const char*) +){ + sqlite3_stmt *pQuery = 0; + char *zQuery = 0; + int rc; + const unsigned char *zName; + const unsigned char *zSql; + char *zErrMsg = 0; + + zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema" + " WHERE %s ORDER BY rowid ASC", zWhere); + shell_check_oom(zQuery); + rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); + if( rc ){ + utf8_printf(stderr, "Error: (%d) %s on [%s]\n", + sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), + zQuery); + goto end_schema_xfer; + } + while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ + zName = sqlite3_column_text(pQuery, 0); + zSql = sqlite3_column_text(pQuery, 1); + if( zName==0 || zSql==0 ) continue; + if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){ + printf("%s... ", zName); fflush(stdout); + sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); + if( zErrMsg ){ + utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + sqlite3_free(zErrMsg); + zErrMsg = 0; + } + } + if( xForEach ){ + xForEach(p, newDb, (const char*)zName); + } + printf("done\n"); + } + if( rc!=SQLITE_DONE ){ + sqlite3_finalize(pQuery); + sqlite3_free(zQuery); + zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema" + " WHERE %s ORDER BY rowid DESC", zWhere); + shell_check_oom(zQuery); + rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); + if( rc ){ + utf8_printf(stderr, "Error: (%d) %s on [%s]\n", + sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), + zQuery); + goto end_schema_xfer; + } + while( sqlite3_step(pQuery)==SQLITE_ROW ){ + zName = sqlite3_column_text(pQuery, 0); + zSql = sqlite3_column_text(pQuery, 1); + if( zName==0 || zSql==0 ) continue; + if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue; + printf("%s... ", zName); fflush(stdout); + sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); + if( zErrMsg ){ + utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + sqlite3_free(zErrMsg); + zErrMsg = 0; + } + if( xForEach ){ + xForEach(p, newDb, (const char*)zName); + } + printf("done\n"); + } + } +end_schema_xfer: + sqlite3_finalize(pQuery); + sqlite3_free(zQuery); +} + +/* +** Open a new database file named "zNewDb". Try to recover as much information +** as possible out of the main database (which might be corrupt) and write it +** into zNewDb. +*/ +static void tryToClone(ShellState *p, const char *zNewDb){ + int rc; + sqlite3 *newDb = 0; + if( access(zNewDb,0)==0 ){ + utf8_printf(stderr, "File \"%s\" already exists.\n", zNewDb); + return; + } + rc = sqlite3_open(zNewDb, &newDb); + if( rc ){ + utf8_printf(stderr, "Cannot create output database: %s\n", + sqlite3_errmsg(newDb)); + }else{ + sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0); + sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0); + tryToCloneSchema(p, newDb, "type='table'", tryToCloneData); + tryToCloneSchema(p, newDb, "type!='table'", 0); + sqlite3_exec(newDb, "COMMIT;", 0, 0, 0); + sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); + } + close_db(newDb); +} + +/* +** Change the output file back to stdout. +** +** If the p->doXdgOpen flag is set, that means the output was being +** redirected to a temporary file named by p->zTempFile. In that case, +** launch start/open/xdg-open on that temporary file. +*/ +static void output_reset(ShellState *p){ + if( p->outfile[0]=='|' ){ +#ifndef SQLITE_OMIT_POPEN + pclose(p->out); +#endif + }else{ + output_file_close(p->out); +#ifndef SQLITE_NOHAVE_SYSTEM + if( p->doXdgOpen ){ + const char *zXdgOpenCmd = +#if defined(_WIN32) + "start"; +#elif defined(__APPLE__) + "open"; +#else + "xdg-open"; +#endif + char *zCmd; + zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile); + if( system(zCmd) ){ + utf8_printf(stderr, "Failed: [%s]\n", zCmd); + }else{ + /* Give the start/open/xdg-open command some time to get + ** going before we continue, and potential delete the + ** p->zTempFile data file out from under it */ + sqlite3_sleep(2000); + } + sqlite3_free(zCmd); + outputModePop(p); + p->doXdgOpen = 0; + } +#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */ + } + p->outfile[0] = 0; + p->out = stdout; +} + +/* +** Run an SQL command and return the single integer result. +*/ +static int db_int(sqlite3 *db, const char *zSql){ + sqlite3_stmt *pStmt; + int res = 0; + sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ + res = sqlite3_column_int(pStmt,0); + } + sqlite3_finalize(pStmt); + return res; +} + +#if SQLITE_SHELL_HAVE_RECOVER +/* +** Convert a 2-byte or 4-byte big-endian integer into a native integer +*/ +static unsigned int get2byteInt(unsigned char *a){ + return (a[0]<<8) + a[1]; +} +static unsigned int get4byteInt(unsigned char *a){ + return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; +} + +/* +** Implementation of the ".dbinfo" command. +** +** Return 1 on error, 2 to exit, and 0 otherwise. +*/ +static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ + static const struct { const char *zName; int ofst; } aField[] = { + { "file change counter:", 24 }, + { "database page count:", 28 }, + { "freelist page count:", 36 }, + { "schema cookie:", 40 }, + { "schema format:", 44 }, + { "default cache size:", 48 }, + { "autovacuum top root:", 52 }, + { "incremental vacuum:", 64 }, + { "text encoding:", 56 }, + { "user version:", 60 }, + { "application id:", 68 }, + { "software version:", 96 }, + }; + static const struct { const char *zName; const char *zSql; } aQuery[] = { + { "number of tables:", + "SELECT count(*) FROM %s WHERE type='table'" }, + { "number of indexes:", + "SELECT count(*) FROM %s WHERE type='index'" }, + { "number of triggers:", + "SELECT count(*) FROM %s WHERE type='trigger'" }, + { "number of views:", + "SELECT count(*) FROM %s WHERE type='view'" }, + { "schema size:", + "SELECT total(length(sql)) FROM %s" }, + }; + int i, rc; + unsigned iDataVersion; + char *zSchemaTab; + char *zDb = nArg>=2 ? azArg[1] : "main"; + sqlite3_stmt *pStmt = 0; + unsigned char aHdr[100]; + open_db(p, 0); + if( p->db==0 ) return 1; + rc = sqlite3_prepare_v2(p->db, + "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1", + -1, &pStmt, 0); + if( rc ){ + utf8_printf(stderr, "error: %s\n", sqlite3_errmsg(p->db)); + sqlite3_finalize(pStmt); + return 1; + } + sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC); + if( sqlite3_step(pStmt)==SQLITE_ROW + && sqlite3_column_bytes(pStmt,0)>100 + ){ + const u8 *pb = sqlite3_column_blob(pStmt,0); + shell_check_oom(pb); + memcpy(aHdr, pb, 100); + sqlite3_finalize(pStmt); + }else{ + raw_printf(stderr, "unable to read database header\n"); + sqlite3_finalize(pStmt); + return 1; + } + i = get2byteInt(aHdr+16); + if( i==1 ) i = 65536; + utf8_printf(p->out, "%-20s %d\n", "database page size:", i); + utf8_printf(p->out, "%-20s %d\n", "write format:", aHdr[18]); + utf8_printf(p->out, "%-20s %d\n", "read format:", aHdr[19]); + utf8_printf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]); + for(i=0; i<ArraySize(aField); i++){ + int ofst = aField[i].ofst; + unsigned int val = get4byteInt(aHdr + ofst); + utf8_printf(p->out, "%-20s %u", aField[i].zName, val); + switch( ofst ){ + case 56: { + if( val==1 ) raw_printf(p->out, " (utf8)"); + if( val==2 ) raw_printf(p->out, " (utf16le)"); + if( val==3 ) raw_printf(p->out, " (utf16be)"); + } + } + raw_printf(p->out, "\n"); + } + if( zDb==0 ){ + zSchemaTab = sqlite3_mprintf("main.sqlite_schema"); + }else if( cli_strcmp(zDb,"temp")==0 ){ + zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema"); + }else{ + zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb); + } + for(i=0; i<ArraySize(aQuery); i++){ + char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab); + int val = db_int(p->db, zSql); + sqlite3_free(zSql); + utf8_printf(p->out, "%-20s %d\n", aQuery[i].zName, val); + } + sqlite3_free(zSchemaTab); + sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion); + utf8_printf(p->out, "%-20s %u\n", "data version", iDataVersion); + return 0; +} +#endif /* SQLITE_SHELL_HAVE_RECOVER */ + +/* +** Print the current sqlite3_errmsg() value to stderr and return 1. +*/ +static int shellDatabaseError(sqlite3 *db){ + const char *zErr = sqlite3_errmsg(db); + utf8_printf(stderr, "Error: %s\n", zErr); + return 1; +} + +/* +** Compare the pattern in zGlob[] against the text in z[]. Return TRUE +** if they match and FALSE (0) if they do not match. +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** '#' Matches any sequence of one or more digits with an +** optional + or - sign in front +** +** ' ' Any span of whitespace matches any other span of +** whitespace. +** +** Extra whitespace at the end of z[] is ignored. +*/ +static int testcase_glob(const char *zGlob, const char *z){ + int c, c2; + int invert; + int seen; + + while( (c = (*(zGlob++)))!=0 ){ + if( IsSpace(c) ){ + if( !IsSpace(*z) ) return 0; + while( IsSpace(*zGlob) ) zGlob++; + while( IsSpace(*z) ) z++; + }else if( c=='*' ){ + while( (c=(*(zGlob++))) == '*' || c=='?' ){ + if( c=='?' && (*(z++))==0 ) return 0; + } + if( c==0 ){ + return 1; + }else if( c=='[' ){ + while( *z && testcase_glob(zGlob-1,z)==0 ){ + z++; + } + return (*z)!=0; + } + while( (c2 = (*(z++)))!=0 ){ + while( c2!=c ){ + c2 = *(z++); + if( c2==0 ) return 0; + } + if( testcase_glob(zGlob,z) ) return 1; + } + return 0; + }else if( c=='?' ){ + if( (*(z++))==0 ) return 0; + }else if( c=='[' ){ + int prior_c = 0; + seen = 0; + invert = 0; + c = *(z++); + if( c==0 ) return 0; + c2 = *(zGlob++); + if( c2=='^' ){ + invert = 1; + c2 = *(zGlob++); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = *(zGlob++); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ + c2 = *(zGlob++); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = *(zGlob++); + } + if( c2==0 || (seen ^ invert)==0 ) return 0; + }else if( c=='#' ){ + if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++; + if( !IsDigit(z[0]) ) return 0; + z++; + while( IsDigit(z[0]) ){ z++; } + }else{ + if( c!=(*(z++)) ) return 0; + } + } + while( IsSpace(*z) ){ z++; } + return *z==0; +} + + +/* +** Compare the string as a command-line option with either one or two +** initial "-" characters. +*/ +static int optionMatch(const char *zStr, const char *zOpt){ + if( zStr[0]!='-' ) return 0; + zStr++; + if( zStr[0]=='-' ) zStr++; + return cli_strcmp(zStr, zOpt)==0; +} + +/* +** Delete a file. +*/ +int shellDeleteFile(const char *zFilename){ + int rc; +#ifdef _WIN32 + wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename); + rc = _wunlink(z); + sqlite3_free(z); +#else + rc = unlink(zFilename); +#endif + return rc; +} + +/* +** Try to delete the temporary file (if there is one) and free the +** memory used to hold the name of the temp file. +*/ +static void clearTempFile(ShellState *p){ + if( p->zTempFile==0 ) return; + if( p->doXdgOpen ) return; + if( shellDeleteFile(p->zTempFile) ) return; + sqlite3_free(p->zTempFile); + p->zTempFile = 0; +} + +/* +** Create a new temp file name with the given suffix. +*/ +static void newTempFile(ShellState *p, const char *zSuffix){ + clearTempFile(p); + sqlite3_free(p->zTempFile); + p->zTempFile = 0; + if( p->db ){ + sqlite3_file_control(p->db, 0, SQLITE_FCNTL_TEMPFILENAME, &p->zTempFile); + } + if( p->zTempFile==0 ){ + /* If p->db is an in-memory database then the TEMPFILENAME file-control + ** will not work and we will need to fallback to guessing */ + char *zTemp; + sqlite3_uint64 r; + sqlite3_randomness(sizeof(r), &r); + zTemp = getenv("TEMP"); + if( zTemp==0 ) zTemp = getenv("TMP"); + if( zTemp==0 ){ +#ifdef _WIN32 + zTemp = "\\tmp"; +#else + zTemp = "/tmp"; +#endif + } + p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix); + }else{ + p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix); + } + shell_check_oom(p->zTempFile); +} + + +/* +** The implementation of SQL scalar function fkey_collate_clause(), used +** by the ".lint fkey-indexes" command. This scalar function is always +** called with four arguments - the parent table name, the parent column name, +** the child table name and the child column name. +** +** fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col') +** +** If either of the named tables or columns do not exist, this function +** returns an empty string. An empty string is also returned if both tables +** and columns exist but have the same default collation sequence. Or, +** if both exist but the default collation sequences are different, this +** function returns the string " COLLATE <parent-collation>", where +** <parent-collation> is the default collation sequence of the parent column. +*/ +static void shellFkeyCollateClause( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + sqlite3 *db = sqlite3_context_db_handle(pCtx); + const char *zParent; + const char *zParentCol; + const char *zParentSeq; + const char *zChild; + const char *zChildCol; + const char *zChildSeq = 0; /* Initialize to avoid false-positive warning */ + int rc; + + assert( nVal==4 ); + zParent = (const char*)sqlite3_value_text(apVal[0]); + zParentCol = (const char*)sqlite3_value_text(apVal[1]); + zChild = (const char*)sqlite3_value_text(apVal[2]); + zChildCol = (const char*)sqlite3_value_text(apVal[3]); + + sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC); + rc = sqlite3_table_column_metadata( + db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0 + ); + if( rc==SQLITE_OK ){ + rc = sqlite3_table_column_metadata( + db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0 + ); + } + + if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){ + char *z = sqlite3_mprintf(" COLLATE %s", zParentSeq); + sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); + sqlite3_free(z); + } +} + + +/* +** The implementation of dot-command ".lint fkey-indexes". +*/ +static int lintFkeyIndexes( + ShellState *pState, /* Current shell tool state */ + char **azArg, /* Array of arguments passed to dot command */ + int nArg /* Number of entries in azArg[] */ +){ + sqlite3 *db = pState->db; /* Database handle to query "main" db of */ + FILE *out = pState->out; /* Stream to write non-error output to */ + int bVerbose = 0; /* If -verbose is present */ + int bGroupByParent = 0; /* If -groupbyparent is present */ + int i; /* To iterate through azArg[] */ + const char *zIndent = ""; /* How much to indent CREATE INDEX by */ + int rc; /* Return code */ + sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */ + + /* + ** This SELECT statement returns one row for each foreign key constraint + ** in the schema of the main database. The column values are: + ** + ** 0. The text of an SQL statement similar to: + ** + ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?" + ** + ** This SELECT is similar to the one that the foreign keys implementation + ** needs to run internally on child tables. If there is an index that can + ** be used to optimize this query, then it can also be used by the FK + ** implementation to optimize DELETE or UPDATE statements on the parent + ** table. + ** + ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by + ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema + ** contains an index that can be used to optimize the query. + ** + ** 2. Human readable text that describes the child table and columns. e.g. + ** + ** "child_table(child_key1, child_key2)" + ** + ** 3. Human readable text that describes the parent table and columns. e.g. + ** + ** "parent_table(parent_key1, parent_key2)" + ** + ** 4. A full CREATE INDEX statement for an index that could be used to + ** optimize DELETE or UPDATE statements on the parent table. e.g. + ** + ** "CREATE INDEX child_table_child_key ON child_table(child_key)" + ** + ** 5. The name of the parent table. + ** + ** These six values are used by the C logic below to generate the report. + */ + const char *zSql = + "SELECT " + " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '" + " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' " + " || fkey_collate_clause(" + " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')" + ", " + " 'SEARCH ' || s.name || ' USING COVERING INDEX*('" + " || group_concat('*=?', ' AND ') || ')'" + ", " + " s.name || '(' || group_concat(f.[from], ', ') || ')'" + ", " + " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'" + ", " + " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))" + " || ' ON ' || quote(s.name) || '('" + " || group_concat(quote(f.[from]) ||" + " fkey_collate_clause(" + " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')" + " || ');'" + ", " + " f.[table] " + "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f " + "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) " + "GROUP BY s.name, f.id " + "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)" + ; + const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)"; + + for(i=2; i<nArg; i++){ + int n = strlen30(azArg[i]); + if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){ + bVerbose = 1; + } + else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){ + bGroupByParent = 1; + zIndent = " "; + } + else{ + raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", + azArg[0], azArg[1] + ); + return SQLITE_ERROR; + } + } + + /* Register the fkey_collate_clause() SQL function */ + rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8, + 0, shellFkeyCollateClause, 0, 0 + ); + + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0); + } + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pSql, 1, bGroupByParent); + } + + if( rc==SQLITE_OK ){ + int rc2; + char *zPrev = 0; + while( SQLITE_ROW==sqlite3_step(pSql) ){ + int res = -1; + sqlite3_stmt *pExplain = 0; + const char *zEQP = (const char*)sqlite3_column_text(pSql, 0); + const char *zGlob = (const char*)sqlite3_column_text(pSql, 1); + const char *zFrom = (const char*)sqlite3_column_text(pSql, 2); + const char *zTarget = (const char*)sqlite3_column_text(pSql, 3); + const char *zCI = (const char*)sqlite3_column_text(pSql, 4); + const char *zParent = (const char*)sqlite3_column_text(pSql, 5); + + if( zEQP==0 ) continue; + if( zGlob==0 ) continue; + rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); + if( rc!=SQLITE_OK ) break; + if( SQLITE_ROW==sqlite3_step(pExplain) ){ + const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3); + res = zPlan!=0 && ( 0==sqlite3_strglob(zGlob, zPlan) + || 0==sqlite3_strglob(zGlobIPK, zPlan)); + } + rc = sqlite3_finalize(pExplain); + if( rc!=SQLITE_OK ) break; + + if( res<0 ){ + raw_printf(stderr, "Error: internal error"); + break; + }else{ + if( bGroupByParent + && (bVerbose || res==0) + && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) + ){ + raw_printf(out, "-- Parent table %s\n", zParent); + sqlite3_free(zPrev); + zPrev = sqlite3_mprintf("%s", zParent); + } + + if( res==0 ){ + raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); + }else if( bVerbose ){ + raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n", + zIndent, zFrom, zTarget + ); + } + } + } + sqlite3_free(zPrev); + + if( rc!=SQLITE_OK ){ + raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + } + + rc2 = sqlite3_finalize(pSql); + if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ + rc = rc2; + raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + } + }else{ + raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + } + + return rc; +} + +/* +** Implementation of ".lint" dot command. +*/ +static int lintDotCommand( + ShellState *pState, /* Current shell tool state */ + char **azArg, /* Array of arguments passed to dot command */ + int nArg /* Number of entries in azArg[] */ +){ + int n; + n = (nArg>=2 ? strlen30(azArg[1]) : 0); + if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage; + return lintFkeyIndexes(pState, azArg, nArg); + + usage: + raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]); + raw_printf(stderr, "Where sub-commands are:\n"); + raw_printf(stderr, " fkey-indexes\n"); + return SQLITE_ERROR; +} + +#if !defined SQLITE_OMIT_VIRTUALTABLE +static void shellPrepare( + sqlite3 *db, + int *pRc, + const char *zSql, + sqlite3_stmt **ppStmt +){ + *ppStmt = 0; + if( *pRc==SQLITE_OK ){ + int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); + if( rc!=SQLITE_OK ){ + raw_printf(stderr, "sql error: %s (%d)\n", + sqlite3_errmsg(db), sqlite3_errcode(db) + ); + *pRc = rc; + } + } +} + +/* +** Create a prepared statement using printf-style arguments for the SQL. +** +** This routine is could be marked "static". But it is not always used, +** depending on compile-time options. By omitting the "static", we avoid +** nuisance compiler warnings about "defined but not used". +*/ +void shellPreparePrintf( + sqlite3 *db, + int *pRc, + sqlite3_stmt **ppStmt, + const char *zFmt, + ... +){ + *ppStmt = 0; + if( *pRc==SQLITE_OK ){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( z==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + shellPrepare(db, pRc, z, ppStmt); + sqlite3_free(z); + } + } +} + +/* Finalize the prepared statement created using shellPreparePrintf(). +** +** This routine is could be marked "static". But it is not always used, +** depending on compile-time options. By omitting the "static", we avoid +** nuisance compiler warnings about "defined but not used". +*/ +void shellFinalize( + int *pRc, + sqlite3_stmt *pStmt +){ + if( pStmt ){ + sqlite3 *db = sqlite3_db_handle(pStmt); + int rc = sqlite3_finalize(pStmt); + if( *pRc==SQLITE_OK ){ + if( rc!=SQLITE_OK ){ + raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); + } + *pRc = rc; + } + } +} + +/* Reset the prepared statement created using shellPreparePrintf(). +** +** This routine is could be marked "static". But it is not always used, +** depending on compile-time options. By omitting the "static", we avoid +** nuisance compiler warnings about "defined but not used". +*/ +void shellReset( + int *pRc, + sqlite3_stmt *pStmt +){ + int rc = sqlite3_reset(pStmt); + if( *pRc==SQLITE_OK ){ + if( rc!=SQLITE_OK ){ + sqlite3 *db = sqlite3_db_handle(pStmt); + raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); + } + *pRc = rc; + } +} +#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */ + +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) +/****************************************************************************** +** The ".archive" or ".ar" command. +*/ +/* +** Structure representing a single ".ar" command. +*/ +typedef struct ArCommand ArCommand; +struct ArCommand { + u8 eCmd; /* An AR_CMD_* value */ + u8 bVerbose; /* True if --verbose */ + u8 bZip; /* True if the archive is a ZIP */ + u8 bDryRun; /* True if --dry-run */ + u8 bAppend; /* True if --append */ + u8 bGlob; /* True if --glob */ + u8 fromCmdLine; /* Run from -A instead of .archive */ + int nArg; /* Number of command arguments */ + char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */ + const char *zFile; /* --file argument, or NULL */ + const char *zDir; /* --directory argument, or NULL */ + char **azArg; /* Array of command arguments */ + ShellState *p; /* Shell state */ + sqlite3 *db; /* Database containing the archive */ +}; + +/* +** Print a usage message for the .ar command to stderr and return SQLITE_ERROR. +*/ +static int arUsage(FILE *f){ + showHelp(f,"archive"); + return SQLITE_ERROR; +} + +/* +** Print an error message for the .ar command to stderr and return +** SQLITE_ERROR. +*/ +static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + utf8_printf(stderr, "Error: %s\n", z); + if( pAr->fromCmdLine ){ + utf8_printf(stderr, "Use \"-A\" for more help\n"); + }else{ + utf8_printf(stderr, "Use \".archive --help\" for more help\n"); + } + sqlite3_free(z); + return SQLITE_ERROR; +} + +/* +** Values for ArCommand.eCmd. +*/ +#define AR_CMD_CREATE 1 +#define AR_CMD_UPDATE 2 +#define AR_CMD_INSERT 3 +#define AR_CMD_EXTRACT 4 +#define AR_CMD_LIST 5 +#define AR_CMD_HELP 6 +#define AR_CMD_REMOVE 7 + +/* +** Other (non-command) switches. +*/ +#define AR_SWITCH_VERBOSE 8 +#define AR_SWITCH_FILE 9 +#define AR_SWITCH_DIRECTORY 10 +#define AR_SWITCH_APPEND 11 +#define AR_SWITCH_DRYRUN 12 +#define AR_SWITCH_GLOB 13 + +static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ + switch( eSwitch ){ + case AR_CMD_CREATE: + case AR_CMD_EXTRACT: + case AR_CMD_LIST: + case AR_CMD_REMOVE: + case AR_CMD_UPDATE: + case AR_CMD_INSERT: + case AR_CMD_HELP: + if( pAr->eCmd ){ + return arErrorMsg(pAr, "multiple command options"); + } + pAr->eCmd = eSwitch; + break; + + case AR_SWITCH_DRYRUN: + pAr->bDryRun = 1; + break; + case AR_SWITCH_GLOB: + pAr->bGlob = 1; + break; + case AR_SWITCH_VERBOSE: + pAr->bVerbose = 1; + break; + case AR_SWITCH_APPEND: + pAr->bAppend = 1; + deliberate_fall_through; + case AR_SWITCH_FILE: + pAr->zFile = zArg; + break; + case AR_SWITCH_DIRECTORY: + pAr->zDir = zArg; + break; + } + + return SQLITE_OK; +} + +/* +** Parse the command line for an ".ar" command. The results are written into +** structure (*pAr). SQLITE_OK is returned if the command line is parsed +** successfully, otherwise an error message is written to stderr and +** SQLITE_ERROR returned. +*/ +static int arParseCommand( + char **azArg, /* Array of arguments passed to dot command */ + int nArg, /* Number of entries in azArg[] */ + ArCommand *pAr /* Populate this object */ +){ + struct ArSwitch { + const char *zLong; + char cShort; + u8 eSwitch; + u8 bArg; + } aSwitch[] = { + { "create", 'c', AR_CMD_CREATE, 0 }, + { "extract", 'x', AR_CMD_EXTRACT, 0 }, + { "insert", 'i', AR_CMD_INSERT, 0 }, + { "list", 't', AR_CMD_LIST, 0 }, + { "remove", 'r', AR_CMD_REMOVE, 0 }, + { "update", 'u', AR_CMD_UPDATE, 0 }, + { "help", 'h', AR_CMD_HELP, 0 }, + { "verbose", 'v', AR_SWITCH_VERBOSE, 0 }, + { "file", 'f', AR_SWITCH_FILE, 1 }, + { "append", 'a', AR_SWITCH_APPEND, 1 }, + { "directory", 'C', AR_SWITCH_DIRECTORY, 1 }, + { "dryrun", 'n', AR_SWITCH_DRYRUN, 0 }, + { "glob", 'g', AR_SWITCH_GLOB, 0 }, + }; + int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch); + struct ArSwitch *pEnd = &aSwitch[nSwitch]; + + if( nArg<=1 ){ + utf8_printf(stderr, "Wrong number of arguments. Usage:\n"); + return arUsage(stderr); + }else{ + char *z = azArg[1]; + if( z[0]!='-' ){ + /* Traditional style [tar] invocation */ + int i; + int iArg = 2; + for(i=0; z[i]; i++){ + const char *zArg = 0; + struct ArSwitch *pOpt; + for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){ + if( z[i]==pOpt->cShort ) break; + } + if( pOpt==pEnd ){ + return arErrorMsg(pAr, "unrecognized option: %c", z[i]); + } + if( pOpt->bArg ){ + if( iArg>=nArg ){ + return arErrorMsg(pAr, "option requires an argument: %c",z[i]); + } + zArg = azArg[iArg++]; + } + if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR; + } + pAr->nArg = nArg-iArg; + if( pAr->nArg>0 ){ + pAr->azArg = &azArg[iArg]; + } + }else{ + /* Non-traditional invocation */ + int iArg; + for(iArg=1; iArg<nArg; iArg++){ + int n; + z = azArg[iArg]; + if( z[0]!='-' ){ + /* All remaining command line words are command arguments. */ + pAr->azArg = &azArg[iArg]; + pAr->nArg = nArg-iArg; + break; + } + n = strlen30(z); + + if( z[1]!='-' ){ + int i; + /* One or more short options */ + for(i=1; i<n; i++){ + const char *zArg = 0; + struct ArSwitch *pOpt; + for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){ + if( z[i]==pOpt->cShort ) break; + } + if( pOpt==pEnd ){ + return arErrorMsg(pAr, "unrecognized option: %c", z[i]); + } + if( pOpt->bArg ){ + if( i<(n-1) ){ + zArg = &z[i+1]; + i = n; + }else{ + if( iArg>=(nArg-1) ){ + return arErrorMsg(pAr, "option requires an argument: %c", + z[i]); + } + zArg = azArg[++iArg]; + } + } + if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR; + } + }else if( z[2]=='\0' ){ + /* A -- option, indicating that all remaining command line words + ** are command arguments. */ + pAr->azArg = &azArg[iArg+1]; + pAr->nArg = nArg-iArg-1; + break; + }else{ + /* A long option */ + const char *zArg = 0; /* Argument for option, if any */ + struct ArSwitch *pMatch = 0; /* Matching option */ + struct ArSwitch *pOpt; /* Iterator */ + for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){ + const char *zLong = pOpt->zLong; + if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){ + if( pMatch ){ + return arErrorMsg(pAr, "ambiguous option: %s",z); + }else{ + pMatch = pOpt; + } + } + } + + if( pMatch==0 ){ + return arErrorMsg(pAr, "unrecognized option: %s", z); + } + if( pMatch->bArg ){ + if( iArg>=(nArg-1) ){ + return arErrorMsg(pAr, "option requires an argument: %s", z); + } + zArg = azArg[++iArg]; + } + if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR; + } + } + } + } + if( pAr->eCmd==0 ){ + utf8_printf(stderr, "Required argument missing. Usage:\n"); + return arUsage(stderr); + } + return SQLITE_OK; +} + +/* +** This function assumes that all arguments within the ArCommand.azArg[] +** array refer to archive members, as for the --extract, --list or --remove +** commands. It checks that each of them are "present". If any specified +** file is not present in the archive, an error is printed to stderr and an +** error code returned. Otherwise, if all specified arguments are present +** in the archive, SQLITE_OK is returned. Here, "present" means either an +** exact equality when pAr->bGlob is false or a "name GLOB pattern" match +** when pAr->bGlob is true. +** +** This function strips any trailing '/' characters from each argument. +** This is consistent with the way the [tar] command seems to work on +** Linux. +*/ +static int arCheckEntries(ArCommand *pAr){ + int rc = SQLITE_OK; + if( pAr->nArg ){ + int i, j; + sqlite3_stmt *pTest = 0; + const char *zSel = (pAr->bGlob) + ? "SELECT name FROM %s WHERE glob($name,name)" + : "SELECT name FROM %s WHERE name=$name"; + + shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable); + j = sqlite3_bind_parameter_index(pTest, "$name"); + for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){ + char *z = pAr->azArg[i]; + int n = strlen30(z); + int bOk = 0; + while( n>0 && z[n-1]=='/' ) n--; + z[n] = '\0'; + sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC); + if( SQLITE_ROW==sqlite3_step(pTest) ){ + bOk = 1; + } + shellReset(&rc, pTest); + if( rc==SQLITE_OK && bOk==0 ){ + utf8_printf(stderr, "not found in archive: %s\n", z); + rc = SQLITE_ERROR; + } + } + shellFinalize(&rc, pTest); + } + return rc; +} + +/* +** Format a WHERE clause that can be used against the "sqlar" table to +** identify all archive members that match the command arguments held +** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning. +** The caller is responsible for eventually calling sqlite3_free() on +** any non-NULL (*pzWhere) value. Here, "match" means strict equality +** when pAr->bGlob is false and GLOB match when pAr->bGlob is true. +*/ +static void arWhereClause( + int *pRc, + ArCommand *pAr, + char **pzWhere /* OUT: New WHERE clause */ +){ + char *zWhere = 0; + const char *zSameOp = (pAr->bGlob)? "GLOB" : "="; + if( *pRc==SQLITE_OK ){ + if( pAr->nArg==0 ){ + zWhere = sqlite3_mprintf("1"); + }else{ + int i; + const char *zSep = ""; + for(i=0; i<pAr->nArg; i++){ + const char *z = pAr->azArg[i]; + zWhere = sqlite3_mprintf( + "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", + zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z + ); + if( zWhere==0 ){ + *pRc = SQLITE_NOMEM; + break; + } + zSep = " OR "; + } + } + } + *pzWhere = zWhere; +} + +/* +** Implementation of .ar "lisT" command. +*/ +static int arListCommand(ArCommand *pAr){ + const char *zSql = "SELECT %s FROM %s WHERE %s"; + const char *azCols[] = { + "name", + "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name" + }; + + char *zWhere = 0; + sqlite3_stmt *pSql = 0; + int rc; + + rc = arCheckEntries(pAr); + arWhereClause(&rc, pAr, &zWhere); + + shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose], + pAr->zSrcTable, zWhere); + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + }else{ + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( pAr->bVerbose ){ + utf8_printf(pAr->p->out, "%s % 10d %s %s\n", + sqlite3_column_text(pSql, 0), + sqlite3_column_int(pSql, 1), + sqlite3_column_text(pSql, 2), + sqlite3_column_text(pSql, 3) + ); + }else{ + utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + } + } + } + shellFinalize(&rc, pSql); + sqlite3_free(zWhere); + return rc; +} + + +/* +** Implementation of .ar "Remove" command. +*/ +static int arRemoveCommand(ArCommand *pAr){ + int rc = 0; + char *zSql = 0; + char *zWhere = 0; + + if( pAr->nArg ){ + /* Verify that args actually exist within the archive before proceeding. + ** And formulate a WHERE clause to match them. */ + rc = arCheckEntries(pAr); + arWhereClause(&rc, pAr, &zWhere); + } + if( rc==SQLITE_OK ){ + zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;", + pAr->zSrcTable, zWhere); + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", zSql); + }else{ + char *zErr = 0; + rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); + if( rc!=SQLITE_OK ){ + sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0); + }else{ + rc = sqlite3_exec(pAr->db, "RELEASE ar;", 0, 0, 0); + } + } + if( zErr ){ + utf8_printf(stdout, "ERROR: %s\n", zErr); + sqlite3_free(zErr); + } + } + } + sqlite3_free(zWhere); + sqlite3_free(zSql); + return rc; +} + +/* +** Implementation of .ar "eXtract" command. +*/ +static int arExtractCommand(ArCommand *pAr){ + const char *zSql1 = + "SELECT " + " ($dir || name)," + " writefile(($dir || name), %s, mode, mtime) " + "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)" + " AND name NOT GLOB '*..[/\\]*'"; + + const char *azExtraArg[] = { + "sqlar_uncompress(data, sz)", + "data" + }; + + sqlite3_stmt *pSql = 0; + int rc = SQLITE_OK; + char *zDir = 0; + char *zWhere = 0; + int i, j; + + /* If arguments are specified, check that they actually exist within + ** the archive before proceeding. And formulate a WHERE clause to + ** match them. */ + rc = arCheckEntries(pAr); + arWhereClause(&rc, pAr, &zWhere); + + if( rc==SQLITE_OK ){ + if( pAr->zDir ){ + zDir = sqlite3_mprintf("%s/", pAr->zDir); + }else{ + zDir = sqlite3_mprintf(""); + } + if( zDir==0 ) rc = SQLITE_NOMEM; + } + + shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, + azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere + ); + + if( rc==SQLITE_OK ){ + j = sqlite3_bind_parameter_index(pSql, "$dir"); + sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); + + /* Run the SELECT statement twice. The first time, writefile() is called + ** for all archive members that should be extracted. The second time, + ** only for the directories. This is because the timestamps for + ** extracted directories must be reset after they are populated (as + ** populating them changes the timestamp). */ + for(i=0; i<2; i++){ + j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); + sqlite3_bind_int(pSql, j, i); + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + }else{ + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( i==0 && pAr->bVerbose ){ + utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + } + } + } + shellReset(&rc, pSql); + } + shellFinalize(&rc, pSql); + } + + sqlite3_free(zDir); + sqlite3_free(zWhere); + return rc; +} + +/* +** Run the SQL statement in zSql. Or if doing a --dryrun, merely print it out. +*/ +static int arExecSql(ArCommand *pAr, const char *zSql){ + int rc; + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", zSql); + rc = SQLITE_OK; + }else{ + char *zErr = 0; + rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); + if( zErr ){ + utf8_printf(stdout, "ERROR: %s\n", zErr); + sqlite3_free(zErr); + } + } + return rc; +} + + +/* +** Implementation of .ar "create", "insert", and "update" commands. +** +** create -> Create a new SQL archive +** insert -> Insert or reinsert all files listed +** update -> Insert files that have changed or that were not +** previously in the archive +** +** Create the "sqlar" table in the database if it does not already exist. +** Then add each file in the azFile[] array to the archive. Directories +** are added recursively. If argument bVerbose is non-zero, a message is +** printed on stdout for each file archived. +** +** The create command is the same as update, except that it drops +** any existing "sqlar" table before beginning. The "insert" command +** always overwrites every file named on the command-line, where as +** "update" only overwrites if the size or mtime or mode has changed. +*/ +static int arCreateOrUpdateCommand( + ArCommand *pAr, /* Command arguments and options */ + int bUpdate, /* true for a --create. */ + int bOnlyIfChanged /* Only update if file has changed */ +){ + const char *zCreate = + "CREATE TABLE IF NOT EXISTS sqlar(\n" + " name TEXT PRIMARY KEY, -- name of the file\n" + " mode INT, -- access permissions\n" + " mtime INT, -- last modification time\n" + " sz INT, -- original file size\n" + " data BLOB -- compressed content\n" + ")"; + const char *zDrop = "DROP TABLE IF EXISTS sqlar"; + const char *zInsertFmt[2] = { + "REPLACE INTO %s(name,mode,mtime,sz,data)\n" + " SELECT\n" + " %s,\n" + " mode,\n" + " mtime,\n" + " CASE substr(lsmode(mode),1,1)\n" + " WHEN '-' THEN length(data)\n" + " WHEN 'd' THEN 0\n" + " ELSE -1 END,\n" + " sqlar_compress(data)\n" + " FROM fsdir(%Q,%Q) AS disk\n" + " WHERE lsmode(mode) NOT LIKE '?%%'%s;" + , + "REPLACE INTO %s(name,mode,mtime,data)\n" + " SELECT\n" + " %s,\n" + " mode,\n" + " mtime,\n" + " data\n" + " FROM fsdir(%Q,%Q) AS disk\n" + " WHERE lsmode(mode) NOT LIKE '?%%'%s;" + }; + int i; /* For iterating through azFile[] */ + int rc; /* Return code */ + const char *zTab = 0; /* SQL table into which to insert */ + char *zSql; + char zTemp[50]; + char *zExists = 0; + + arExecSql(pAr, "PRAGMA page_size=512"); + rc = arExecSql(pAr, "SAVEPOINT ar;"); + if( rc!=SQLITE_OK ) return rc; + zTemp[0] = 0; + if( pAr->bZip ){ + /* Initialize the zipfile virtual table, if necessary */ + if( pAr->zFile ){ + sqlite3_uint64 r; + sqlite3_randomness(sizeof(r),&r); + sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r); + zTab = zTemp; + zSql = sqlite3_mprintf( + "CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)", + zTab, pAr->zFile + ); + rc = arExecSql(pAr, zSql); + sqlite3_free(zSql); + }else{ + zTab = "zip"; + } + }else{ + /* Initialize the table for an SQLAR */ + zTab = "sqlar"; + if( bUpdate==0 ){ + rc = arExecSql(pAr, zDrop); + if( rc!=SQLITE_OK ) goto end_ar_transaction; + } + rc = arExecSql(pAr, zCreate); + } + if( bOnlyIfChanged ){ + zExists = sqlite3_mprintf( + " AND NOT EXISTS(" + "SELECT 1 FROM %s AS mem" + " WHERE mem.name=disk.name" + " AND mem.mtime=disk.mtime" + " AND mem.mode=disk.mode)", zTab); + }else{ + zExists = sqlite3_mprintf(""); + } + if( zExists==0 ) rc = SQLITE_NOMEM; + for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){ + char *zSql2 = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab, + pAr->bVerbose ? "shell_putsnl(name)" : "name", + pAr->azArg[i], pAr->zDir, zExists); + rc = arExecSql(pAr, zSql2); + sqlite3_free(zSql2); + } +end_ar_transaction: + if( rc!=SQLITE_OK ){ + sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0); + }else{ + rc = arExecSql(pAr, "RELEASE ar;"); + if( pAr->bZip && pAr->zFile ){ + zSql = sqlite3_mprintf("DROP TABLE %s", zTemp); + arExecSql(pAr, zSql); + sqlite3_free(zSql); + } + } + sqlite3_free(zExists); + return rc; +} + +/* +** Implementation of ".ar" dot command. +*/ +static int arDotCommand( + ShellState *pState, /* Current shell tool state */ + int fromCmdLine, /* True if -A command-line option, not .ar cmd */ + char **azArg, /* Array of arguments passed to dot command */ + int nArg /* Number of entries in azArg[] */ +){ + ArCommand cmd; + int rc; + memset(&cmd, 0, sizeof(cmd)); + cmd.fromCmdLine = fromCmdLine; + rc = arParseCommand(azArg, nArg, &cmd); + if( rc==SQLITE_OK ){ + int eDbType = SHELL_OPEN_UNSPEC; + cmd.p = pState; + cmd.db = pState->db; + if( cmd.zFile ){ + eDbType = deduceDatabaseType(cmd.zFile, 1); + }else{ + eDbType = pState->openMode; + } + if( eDbType==SHELL_OPEN_ZIPFILE ){ + if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){ + if( cmd.zFile==0 ){ + cmd.zSrcTable = sqlite3_mprintf("zip"); + }else{ + cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile); + } + } + cmd.bZip = 1; + }else if( cmd.zFile ){ + int flags; + if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS; + if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT + || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){ + flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; + }else{ + flags = SQLITE_OPEN_READONLY; + } + cmd.db = 0; + if( cmd.bDryRun ){ + utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile, + eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); + } + rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, + eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, "cannot open file: %s (%s)\n", + cmd.zFile, sqlite3_errmsg(cmd.db) + ); + goto end_ar_command; + } + sqlite3_fileio_init(cmd.db, 0, 0); + sqlite3_sqlar_init(cmd.db, 0, 0); + sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p, + shellPutsFunc, 0, 0); + + } + if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){ + if( cmd.eCmd!=AR_CMD_CREATE + && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0) + ){ + utf8_printf(stderr, "database does not contain an 'sqlar' table\n"); + rc = SQLITE_ERROR; + goto end_ar_command; + } + cmd.zSrcTable = sqlite3_mprintf("sqlar"); + } + + switch( cmd.eCmd ){ + case AR_CMD_CREATE: + rc = arCreateOrUpdateCommand(&cmd, 0, 0); + break; + + case AR_CMD_EXTRACT: + rc = arExtractCommand(&cmd); + break; + + case AR_CMD_LIST: + rc = arListCommand(&cmd); + break; + + case AR_CMD_HELP: + arUsage(pState->out); + break; + + case AR_CMD_INSERT: + rc = arCreateOrUpdateCommand(&cmd, 1, 0); + break; + + case AR_CMD_REMOVE: + rc = arRemoveCommand(&cmd); + break; + + default: + assert( cmd.eCmd==AR_CMD_UPDATE ); + rc = arCreateOrUpdateCommand(&cmd, 1, 1); + break; + } + } +end_ar_command: + if( cmd.db!=pState->db ){ + close_db(cmd.db); + } + sqlite3_free(cmd.zSrcTable); + + return rc; +} +/* End of the ".archive" or ".ar" command logic +*******************************************************************************/ +#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */ + +#if SQLITE_SHELL_HAVE_RECOVER + +/* +** This function is used as a callback by the recover extension. Simply +** print the supplied SQL statement to stdout. +*/ +static int recoverSqlCb(void *pCtx, const char *zSql){ + ShellState *pState = (ShellState*)pCtx; + utf8_printf(pState->out, "%s;\n", zSql); + return SQLITE_OK; +} + +/* +** This function is called to recover data from the database. A script +** to construct a new database containing all recovered data is output +** on stream pState->out. +*/ +static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ + int rc = SQLITE_OK; + const char *zRecoveryDb = ""; /* Name of "recovery" database. Debug only */ + const char *zLAF = "lost_and_found"; + int bFreelist = 1; /* 0 if --ignore-freelist is specified */ + int bRowids = 1; /* 0 if --no-rowids */ + sqlite3_recover *p = 0; + int i = 0; + + for(i=1; i<nArg; i++){ + char *z = azArg[i]; + int n; + if( z[0]=='-' && z[1]=='-' ) z++; + n = strlen30(z); + if( n<=17 && memcmp("-ignore-freelist", z, n)==0 ){ + bFreelist = 0; + }else + if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){ + /* This option determines the name of the ATTACH-ed database used + ** internally by the recovery extension. The default is "" which + ** means to use a temporary database that is automatically deleted + ** when closed. This option is undocumented and might disappear at + ** any moment. */ + i++; + zRecoveryDb = azArg[i]; + }else + if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){ + i++; + zLAF = azArg[i]; + }else + if( n<=10 && memcmp("-no-rowids", z, n)==0 ){ + bRowids = 0; + } + else{ + utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); + showHelp(pState->out, azArg[0]); + return 1; + } + } + + p = sqlite3_recover_init_sql( + pState->db, "main", recoverSqlCb, (void*)pState + ); + + sqlite3_recover_config(p, 789, (void*)zRecoveryDb); /* Debug use only */ + sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF); + sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids); + sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist); + + sqlite3_recover_run(p); + if( sqlite3_recover_errcode(p)!=SQLITE_OK ){ + const char *zErr = sqlite3_recover_errmsg(p); + int errCode = sqlite3_recover_errcode(p); + raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode); + } + rc = sqlite3_recover_finish(p); + return rc; +} +#endif /* SQLITE_SHELL_HAVE_RECOVER */ + + +/* + * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it. + * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE, + * close db and set it to 0, and return the columns spec, to later + * be sqlite3_free()'ed by the caller. + * The return is 0 when either: + * (a) The db was not initialized and zCol==0 (There are no columns.) + * (b) zCol!=0 (Column was added, db initialized as needed.) + * The 3rd argument, pRenamed, references an out parameter. If the + * pointer is non-zero, its referent will be set to a summary of renames + * done if renaming was necessary, or set to 0 if none was done. The out + * string (if any) must be sqlite3_free()'ed by the caller. + */ +#ifdef SHELL_DEBUG +#define rc_err_oom_die(rc) \ + if( rc==SQLITE_NOMEM ) shell_check_oom(0); \ + else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \ + fprintf(stderr,"E:%d\n",rc), assert(0) +#else +static void rc_err_oom_die(int rc){ + if( rc==SQLITE_NOMEM ) shell_check_oom(0); + assert(rc==SQLITE_OK||rc==SQLITE_DONE); +} +#endif + +#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */ +static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB); +#else /* Otherwise, memory is faster/better for the transient DB. */ +static const char *zCOL_DB = ":memory:"; +#endif + +/* Define character (as C string) to separate generated column ordinal + * from protected part of incoming column names. This defaults to "_" + * so that incoming column identifiers that did not need not be quoted + * remain usable without being quoted. It must be one character. + */ +#ifndef SHELL_AUTOCOLUMN_SEP +# define AUTOCOLUMN_SEP "_" +#else +# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP) +#endif + +static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){ + /* Queries and D{D,M}L used here */ + static const char * const zTabMake = "\ +CREATE TABLE ColNames(\ + cpos INTEGER PRIMARY KEY,\ + name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\ +CREATE VIEW RepeatedNames AS \ +SELECT DISTINCT t.name FROM ColNames t \ +WHERE t.name COLLATE NOCASE IN (\ + SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\ +);\ +"; + static const char * const zTabFill = "\ +INSERT INTO ColNames(name,nlen,chop,reps,suff)\ + VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\ +"; + static const char * const zHasDupes = "\ +SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\ + <count(name) FROM ColNames\ +"; +#ifdef SHELL_COLUMN_RENAME_CLEAN + static const char * const zDedoctor = "\ +UPDATE ColNames SET chop=iif(\ + (substring(name,nlen,1) BETWEEN '0' AND '9')\ + AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\ + nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\ + 0\ +)\ +"; +#endif + static const char * const zSetReps = "\ +UPDATE ColNames AS t SET reps=\ +(SELECT count(*) FROM ColNames d \ + WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\ + COLLATE NOCASE\ +)\ +"; +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS + static const char * const zColDigits = "\ +SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \ +"; +#else + /* Counting on SQLITE_MAX_COLUMN < 100,000 here. (32767 is the hard limit.) */ + static const char * const zColDigits = "\ +SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \ + WHEN (nc < 1000) THEN 3 WHEN (nc < 10000) THEN 4 \ + ELSE 5 FROM (SELECT count(*) AS nc FROM ColNames) \ +"; +#endif + static const char * const zRenameRank = +#ifdef SHELL_COLUMN_RENAME_CLEAN + "UPDATE ColNames AS t SET suff=" + "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')" +#else /* ...RENAME_MINIMAL_ONE_PASS */ +"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */ +" SELECT 0 AS nlz" +" UNION" +" SELECT nlz+1 AS nlz FROM Lzn" +" WHERE EXISTS(" +" SELECT 1" +" FROM ColNames t, ColNames o" +" WHERE" +" iif(t.name IN (SELECT * FROM RepeatedNames)," +" printf('%s"AUTOCOLUMN_SEP"%s'," +" t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2))," +" t.name" +" )" +" =" +" iif(o.name IN (SELECT * FROM RepeatedNames)," +" printf('%s"AUTOCOLUMN_SEP"%s'," +" o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2))," +" o.name" +" )" +" COLLATE NOCASE" +" AND o.cpos<>t.cpos" +" GROUP BY t.cpos" +" )" +") UPDATE Colnames AS t SET" +" chop = 0," /* No chopping, never touch incoming names. */ +" suff = iif(name IN (SELECT * FROM RepeatedNames)," +" printf('"AUTOCOLUMN_SEP"%s', substring(" +" printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2))," +" ''" +" )" +#endif + ; + static const char * const zCollectVar = "\ +SELECT\ + '('||x'0a'\ + || group_concat(\ + cname||' TEXT',\ + ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\ + ||')' AS ColsSpec \ +FROM (\ + SELECT cpos, printf('\"%w\"',printf('%!.*s%s', nlen-chop,name,suff)) AS cname \ + FROM ColNames ORDER BY cpos\ +)"; + static const char * const zRenamesDone = + "SELECT group_concat(" + " printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff))," + " ','||x'0a')" + "FROM ColNames WHERE suff<>'' OR chop!=0" + ; + int rc; + sqlite3_stmt *pStmt = 0; + assert(pDb!=0); + if( zColNew ){ + /* Add initial or additional column. Init db if necessary. */ + if( *pDb==0 ){ + if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0; +#ifdef SHELL_COLFIX_DB + if(*zCOL_DB!=':') + sqlite3_exec(*pDb,"drop table if exists ColNames;" + "drop view if exists RepeatedNames;",0,0,0); +#endif + rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0); + rc_err_oom_die(rc); + } + assert(*pDb!=0); + rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0); + rc_err_oom_die(rc); + rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0); + rc_err_oom_die(rc); + rc = sqlite3_step(pStmt); + rc_err_oom_die(rc); + sqlite3_finalize(pStmt); + return 0; + }else if( *pDb==0 ){ + return 0; + }else{ + /* Formulate the columns spec, close the DB, zero *pDb. */ + char *zColsSpec = 0; + int hasDupes = db_int(*pDb, zHasDupes); + int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0; + if( hasDupes ){ +#ifdef SHELL_COLUMN_RENAME_CLEAN + rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); + rc_err_oom_die(rc); +#endif + rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0); + rc_err_oom_die(rc); + rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0); + rc_err_oom_die(rc); + sqlite3_bind_int(pStmt, 1, nDigits); + rc = sqlite3_step(pStmt); + sqlite3_finalize(pStmt); + if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM); + } + assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */ + rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); + rc_err_oom_die(rc); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ + zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + }else{ + zColsSpec = 0; + } + if( pzRenamed!=0 ){ + if( !hasDupes ) *pzRenamed = 0; + else{ + sqlite3_finalize(pStmt); + if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0) + && SQLITE_ROW==sqlite3_step(pStmt) ){ + *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + }else + *pzRenamed = 0; + } + } + sqlite3_finalize(pStmt); + sqlite3_close(*pDb); + *pDb = 0; + return zColsSpec; + } +} + +/* +** If an input line begins with "." then invoke this routine to +** process that line. +** +** Return 1 on error, 2 to exit, and 0 otherwise. +*/ +static int do_meta_command(char *zLine, ShellState *p){ + int h = 1; + int nArg = 0; + int n, c; + int rc = 0; + char *azArg[52]; + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( p->expert.pExpert ){ + expertFinish(p, 1, 0); + } +#endif + + /* Parse the input line into tokens. + */ + while( zLine[h] && nArg<ArraySize(azArg)-1 ){ + while( IsSpace(zLine[h]) ){ h++; } + if( zLine[h]==0 ) break; + if( zLine[h]=='\'' || zLine[h]=='"' ){ + int delim = zLine[h++]; + azArg[nArg++] = &zLine[h]; + while( zLine[h] && zLine[h]!=delim ){ + if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++; + h++; + } + if( zLine[h]==delim ){ + zLine[h++] = 0; + } + if( delim=='"' ) resolve_backslashes(azArg[nArg-1]); + }else{ + azArg[nArg++] = &zLine[h]; + while( zLine[h] && !IsSpace(zLine[h]) ){ h++; } + if( zLine[h] ) zLine[h++] = 0; + resolve_backslashes(azArg[nArg-1]); + } + } + azArg[nArg] = 0; + + /* Process the input line. + */ + if( nArg==0 ) return 0; /* no tokens, no error */ + n = strlen30(azArg[0]); + c = azArg[0][0]; + clearTempFile(p); + +#ifndef SQLITE_OMIT_AUTHORIZATION + if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){ + if( nArg!=2 ){ + raw_printf(stderr, "Usage: .auth ON|OFF\n"); + rc = 1; + goto meta_command_exit; + } + open_db(p, 0); + if( booleanValue(azArg[1]) ){ + sqlite3_set_authorizer(p->db, shellAuth, p); + }else if( p->bSafeModePersist ){ + sqlite3_set_authorizer(p->db, safeModeAuth, p); + }else{ + sqlite3_set_authorizer(p->db, 0, 0); + } + }else +#endif + +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \ + && !defined(SQLITE_SHELL_FIDDLE) + if( c=='a' && cli_strncmp(azArg[0], "archive", n)==0 ){ + open_db(p, 0); + failIfSafeMode(p, "cannot run .archive in safe mode"); + rc = arDotCommand(p, 0, azArg, nArg); + }else +#endif + +#ifndef SQLITE_SHELL_FIDDLE + if( (c=='b' && n>=3 && cli_strncmp(azArg[0], "backup", n)==0) + || (c=='s' && n>=3 && cli_strncmp(azArg[0], "save", n)==0) + ){ + const char *zDestFile = 0; + const char *zDb = 0; + sqlite3 *pDest; + sqlite3_backup *pBackup; + int j; + int bAsync = 0; + const char *zVfs = 0; + failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); + for(j=1; j<nArg; j++){ + const char *z = azArg[j]; + if( z[0]=='-' ){ + if( z[1]=='-' ) z++; + if( cli_strcmp(z, "-append")==0 ){ + zVfs = "apndvfs"; + }else + if( cli_strcmp(z, "-async")==0 ){ + bAsync = 1; + }else + { + utf8_printf(stderr, "unknown option: %s\n", azArg[j]); + return 1; + } + }else if( zDestFile==0 ){ + zDestFile = azArg[j]; + }else if( zDb==0 ){ + zDb = zDestFile; + zDestFile = azArg[j]; + }else{ + raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); + return 1; + } + } + if( zDestFile==0 ){ + raw_printf(stderr, "missing FILENAME argument on .backup\n"); + return 1; + } + if( zDb==0 ) zDb = "main"; + rc = sqlite3_open_v2(zDestFile, &pDest, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile); + close_db(pDest); + return 1; + } + if( bAsync ){ + sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;", + 0, 0, 0); + } + open_db(p, 0); + pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb); + if( pBackup==0 ){ + utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); + close_db(pDest); + return 1; + } + while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){} + sqlite3_backup_finish(pBackup); + if( rc==SQLITE_DONE ){ + rc = 0; + }else{ + utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); + rc = 1; + } + close_db(pDest); + }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + + if( c=='b' && n>=3 && cli_strncmp(azArg[0], "bail", n)==0 ){ + if( nArg==2 ){ + bail_on_error = booleanValue(azArg[1]); + }else{ + raw_printf(stderr, "Usage: .bail on|off\n"); + rc = 1; + } + }else + + /* Undocumented. Legacy only. See "crnl" below */ + if( c=='b' && n>=3 && cli_strncmp(azArg[0], "binary", n)==0 ){ + if( nArg==2 ){ + if( booleanValue(azArg[1]) ){ + setBinaryMode(p->out, 1); + }else{ + setTextMode(p->out, 1); + } + }else{ + raw_printf(stderr, "The \".binary\" command is deprecated." + " Use \".crnl\" instead.\n"); + raw_printf(stderr, "Usage: .binary on|off\n"); + rc = 1; + } + }else + + /* The undocumented ".breakpoint" command causes a call to the no-op + ** routine named test_breakpoint(). + */ + if( c=='b' && n>=3 && cli_strncmp(azArg[0], "breakpoint", n)==0 ){ + test_breakpoint(); + }else + +#ifndef SQLITE_SHELL_FIDDLE + if( c=='c' && cli_strcmp(azArg[0],"cd")==0 ){ + failIfSafeMode(p, "cannot run .cd in safe mode"); + if( nArg==2 ){ +#if defined(_WIN32) || defined(WIN32) + wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]); + rc = !SetCurrentDirectoryW(z); + sqlite3_free(z); +#else + rc = chdir(azArg[1]); +#endif + if( rc ){ + utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]); + rc = 1; + } + }else{ + raw_printf(stderr, "Usage: .cd DIRECTORY\n"); + rc = 1; + } + }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + + if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){ + if( nArg==2 ){ + setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); + }else{ + raw_printf(stderr, "Usage: .changes on|off\n"); + rc = 1; + } + }else + +#ifndef SQLITE_SHELL_FIDDLE + /* Cancel output redirection, if it is currently set (by .testcase) + ** Then read the content of the testcase-out.txt file and compare against + ** azArg[1]. If there are differences, report an error and exit. + */ + if( c=='c' && n>=3 && cli_strncmp(azArg[0], "check", n)==0 ){ + char *zRes = 0; + output_reset(p); + if( nArg!=2 ){ + raw_printf(stderr, "Usage: .check GLOB-PATTERN\n"); + rc = 2; + }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ + rc = 2; + }else if( testcase_glob(azArg[1],zRes)==0 ){ + utf8_printf(stderr, + "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", + p->zTestcase, azArg[1], zRes); + rc = 1; + }else{ + utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase); + p->nCheck++; + } + sqlite3_free(zRes); + }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + +#ifndef SQLITE_SHELL_FIDDLE + if( c=='c' && cli_strncmp(azArg[0], "clone", n)==0 ){ + failIfSafeMode(p, "cannot run .clone in safe mode"); + if( nArg==2 ){ + tryToClone(p, azArg[1]); + }else{ + raw_printf(stderr, "Usage: .clone FILENAME\n"); + rc = 1; + } + }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + + if( c=='c' && cli_strncmp(azArg[0], "connection", n)==0 ){ + if( nArg==1 ){ + /* List available connections */ + int i; + for(i=0; i<ArraySize(p->aAuxDb); i++){ + const char *zFile = p->aAuxDb[i].zDbFilename; + if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){ + zFile = "(not open)"; + }else if( zFile==0 ){ + zFile = "(memory)"; + }else if( zFile[0]==0 ){ + zFile = "(temporary-file)"; + } + if( p->pAuxDb == &p->aAuxDb[i] ){ + utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile); + }else if( p->aAuxDb[i].db!=0 ){ + utf8_printf(stdout, " %d: %s\n", i, zFile); + } + } + }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){ + int i = azArg[1][0] - '0'; + if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && i<ArraySize(p->aAuxDb) ){ + p->pAuxDb->db = p->db; + p->pAuxDb = &p->aAuxDb[i]; + globalDb = p->db = p->pAuxDb->db; + p->pAuxDb->db = 0; + } + }else if( nArg==3 && cli_strcmp(azArg[1], "close")==0 + && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){ + int i = azArg[2][0] - '0'; + if( i<0 || i>=ArraySize(p->aAuxDb) ){ + /* No-op */ + }else if( p->pAuxDb == &p->aAuxDb[i] ){ + raw_printf(stderr, "cannot close the active database connection\n"); + rc = 1; + }else if( p->aAuxDb[i].db ){ + session_close_all(p, i); + close_db(p->aAuxDb[i].db); + p->aAuxDb[i].db = 0; + } + }else{ + raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n"); + rc = 1; + } + }else + + if( c=='c' && n==4 && cli_strncmp(azArg[0], "crnl", n)==0 ){ + if( nArg==2 ){ + if( booleanValue(azArg[1]) ){ + setTextMode(p->out, 1); + }else{ + setBinaryMode(p->out, 1); + } + }else{ +#if !defined(_WIN32) && !defined(WIN32) + raw_printf(stderr, "The \".crnl\" is a no-op on non-Windows machines.\n"); +#endif + raw_printf(stderr, "Usage: .crnl on|off\n"); + rc = 1; + } + }else + + if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){ + char **azName = 0; + int nName = 0; + sqlite3_stmt *pStmt; + int i; + open_db(p, 0); + rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); + if( rc ){ + utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + rc = 1; + }else{ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zSchema = (const char *)sqlite3_column_text(pStmt,1); + const char *zFile = (const char*)sqlite3_column_text(pStmt,2); + if( zSchema==0 || zFile==0 ) continue; + azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*)); + shell_check_oom(azName); + azName[nName*2] = strdup(zSchema); + azName[nName*2+1] = strdup(zFile); + nName++; + } + } + sqlite3_finalize(pStmt); + for(i=0; i<nName; i++){ + int eTxn = sqlite3_txn_state(p->db, azName[i*2]); + int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]); + const char *z = azName[i*2+1]; + utf8_printf(p->out, "%s: %s %s%s\n", + azName[i*2], + z && z[0] ? z : "\"\"", + bRdonly ? "r/o" : "r/w", + eTxn==SQLITE_TXN_NONE ? "" : + eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); + free(azName[i*2]); + free(azName[i*2+1]); + } + sqlite3_free(azName); + }else + + if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbconfig", n)==0 ){ + static const struct DbConfigChoices { + const char *zName; + int op; + } aDbConfig[] = { + { "defensive", SQLITE_DBCONFIG_DEFENSIVE }, + { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL }, + { "dqs_dml", SQLITE_DBCONFIG_DQS_DML }, + { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY }, + { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG }, + { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER }, + { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW }, + { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, + { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE }, + { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, + { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, + { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE }, + { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE }, + { "reverse_scanorder", SQLITE_DBCONFIG_REVERSE_SCANORDER }, + { "stmt_scanstatus", SQLITE_DBCONFIG_STMT_SCANSTATUS }, + { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP }, + { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA }, + { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA }, + }; + int ii, v; + open_db(p, 0); + for(ii=0; ii<ArraySize(aDbConfig); ii++){ + if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; + if( nArg>=3 ){ + sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); + } + sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); + utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); + if( nArg>1 ) break; + } + if( nArg>1 && ii==ArraySize(aDbConfig) ){ + utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); + utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); + } + }else + +#if SQLITE_SHELL_HAVE_RECOVER + if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbinfo", n)==0 ){ + rc = shell_dbinfo_command(p, nArg, azArg); + }else + + if( c=='r' && cli_strncmp(azArg[0], "recover", n)==0 ){ + open_db(p, 0); + rc = recoverDatabaseCmd(p, nArg, azArg); + }else +#endif /* SQLITE_SHELL_HAVE_RECOVER */ + + if( c=='d' && cli_strncmp(azArg[0], "dump", n)==0 ){ + char *zLike = 0; + char *zSql; + int i; + int savedShowHeader = p->showHeader; + int savedShellFlags = p->shellFlgs; + ShellClearFlag(p, + SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo + |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); + for(i=1; i<nArg; i++){ + if( azArg[i][0]=='-' ){ + const char *z = azArg[i]+1; + if( z[0]=='-' ) z++; + if( cli_strcmp(z,"preserve-rowids")==0 ){ +#ifdef SQLITE_OMIT_VIRTUALTABLE + raw_printf(stderr, "The --preserve-rowids option is not compatible" + " with SQLITE_OMIT_VIRTUALTABLE\n"); + rc = 1; + sqlite3_free(zLike); + goto meta_command_exit; +#else + ShellSetFlag(p, SHFLG_PreserveRowid); +#endif + }else + if( cli_strcmp(z,"newlines")==0 ){ + ShellSetFlag(p, SHFLG_Newlines); + }else + if( cli_strcmp(z,"data-only")==0 ){ + ShellSetFlag(p, SHFLG_DumpDataOnly); + }else + if( cli_strcmp(z,"nosys")==0 ){ + ShellSetFlag(p, SHFLG_DumpNoSys); + }else + { + raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]); + rc = 1; + sqlite3_free(zLike); + goto meta_command_exit; + } + }else{ + /* azArg[i] contains a LIKE pattern. This ".dump" request should + ** only dump data for tables for which either the table name matches + ** the LIKE pattern, or the table appears to be a shadow table of + ** a virtual table for which the name matches the LIKE pattern. + */ + char *zExpr = sqlite3_mprintf( + "name LIKE %Q ESCAPE '\\' OR EXISTS (" + " SELECT 1 FROM sqlite_schema WHERE " + " name LIKE %Q ESCAPE '\\' AND" + " sql LIKE 'CREATE VIRTUAL TABLE%%' AND" + " substr(o.name, 1, length(name)+1) == (name||'_')" + ")", azArg[i], azArg[i] + ); + + if( zLike ){ + zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr); + }else{ + zLike = zExpr; + } + } + } + + open_db(p, 0); + + if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + /* When playing back a "dump", the content might appear in an order + ** which causes immediate foreign key constraints to be violated. + ** So disable foreign-key constraint enforcement to prevent problems. */ + raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n"); + raw_printf(p->out, "BEGIN TRANSACTION;\n"); + } + p->writableSchema = 0; + p->showHeader = 0; + /* Set writable_schema=ON since doing so forces SQLite to initialize + ** as much of the schema as it can even if the sqlite_schema table is + ** corrupt. */ + sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); + p->nErr = 0; + if( zLike==0 ) zLike = sqlite3_mprintf("true"); + zSql = sqlite3_mprintf( + "SELECT name, type, sql FROM sqlite_schema AS o " + "WHERE (%s) AND type=='table'" + " AND sql NOT NULL" + " ORDER BY tbl_name='sqlite_sequence', rowid", + zLike + ); + run_schema_dump_query(p,zSql); + sqlite3_free(zSql); + if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + zSql = sqlite3_mprintf( + "SELECT sql FROM sqlite_schema AS o " + "WHERE (%s) AND sql NOT NULL" + " AND type IN ('index','trigger','view')", + zLike + ); + run_table_dump_query(p, zSql); + sqlite3_free(zSql); + } + sqlite3_free(zLike); + if( p->writableSchema ){ + raw_printf(p->out, "PRAGMA writable_schema=OFF;\n"); + p->writableSchema = 0; + } + sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); + sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); + if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); + } + p->showHeader = savedShowHeader; + p->shellFlgs = savedShellFlags; + }else + + if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){ + if( nArg==2 ){ + setOrClearFlag(p, SHFLG_Echo, azArg[1]); + }else{ + raw_printf(stderr, "Usage: .echo on|off\n"); + rc = 1; + } + }else + + if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ + if( nArg==2 ){ + p->autoEQPtest = 0; + if( p->autoEQPtrace ){ + if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0); + p->autoEQPtrace = 0; + } + if( cli_strcmp(azArg[1],"full")==0 ){ + p->autoEQP = AUTOEQP_full; + }else if( cli_strcmp(azArg[1],"trigger")==0 ){ + p->autoEQP = AUTOEQP_trigger; +#ifdef SQLITE_DEBUG + }else if( cli_strcmp(azArg[1],"test")==0 ){ + p->autoEQP = AUTOEQP_on; + p->autoEQPtest = 1; + }else if( cli_strcmp(azArg[1],"trace")==0 ){ + p->autoEQP = AUTOEQP_full; + p->autoEQPtrace = 1; + open_db(p, 0); + sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0); + sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0); +#endif + }else{ + p->autoEQP = (u8)booleanValue(azArg[1]); + } + }else{ + raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n"); + rc = 1; + } + }else + +#ifndef SQLITE_SHELL_FIDDLE + if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){ + if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); + rc = 2; + }else +#endif + + /* The ".explain" command is automatic now. It is largely pointless. It + ** retained purely for backwards compatibility */ + if( c=='e' && cli_strncmp(azArg[0], "explain", n)==0 ){ + int val = 1; + if( nArg>=2 ){ + if( cli_strcmp(azArg[1],"auto")==0 ){ + val = 99; + }else{ + val = booleanValue(azArg[1]); + } + } + if( val==1 && p->mode!=MODE_Explain ){ + p->normalMode = p->mode; + p->mode = MODE_Explain; + p->autoExplain = 0; + }else if( val==0 ){ + if( p->mode==MODE_Explain ) p->mode = p->normalMode; + p->autoExplain = 0; + }else if( val==99 ){ + if( p->mode==MODE_Explain ) p->mode = p->normalMode; + p->autoExplain = 1; + } + }else + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ + if( p->bSafeMode ){ + raw_printf(stderr, + "Cannot run experimental commands such as \"%s\" in safe mode\n", + azArg[0]); + rc = 1; + }else{ + open_db(p, 0); + expertDotCommand(p, azArg, nArg); + } + }else +#endif + + if( c=='f' && cli_strncmp(azArg[0], "filectrl", n)==0 ){ + static const struct { + const char *zCtrlName; /* Name of a test-control option */ + int ctrlCode; /* Integer code for that option */ + const char *zUsage; /* Usage notes */ + } aCtrl[] = { + { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" }, + { "data_version", SQLITE_FCNTL_DATA_VERSION, "" }, + { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, + { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" }, + { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" }, + /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/ + { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" }, + { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" }, + { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" }, + { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" }, + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/ + }; + int filectrl = -1; + int iCtrl = -1; + sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */ + int isOk = 0; /* 0: usage 1: %lld 2: no-result */ + int n2, i; + const char *zCmd = 0; + const char *zSchema = 0; + + open_db(p, 0); + zCmd = nArg>=2 ? azArg[1] : "help"; + + if( zCmd[0]=='-' + && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0) + && nArg>=4 + ){ + zSchema = azArg[2]; + for(i=3; i<nArg; i++) azArg[i-2] = azArg[i]; + nArg -= 2; + zCmd = azArg[1]; + } + + /* The argument can optionally begin with "-" or "--" */ + if( zCmd[0]=='-' && zCmd[1] ){ + zCmd++; + if( zCmd[0]=='-' && zCmd[1] ) zCmd++; + } + + /* --help lists all file-controls */ + if( cli_strcmp(zCmd,"help")==0 ){ + utf8_printf(p->out, "Available file-controls:\n"); + for(i=0; i<ArraySize(aCtrl); i++){ + utf8_printf(p->out, " .filectrl %s %s\n", + aCtrl[i].zCtrlName, aCtrl[i].zUsage); + } + rc = 1; + goto meta_command_exit; + } + + /* convert filectrl text option to value. allow any unique prefix + ** of the option name, or a numerical value. */ + n2 = strlen30(zCmd); + for(i=0; i<ArraySize(aCtrl); i++){ + if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){ + if( filectrl<0 ){ + filectrl = aCtrl[i].ctrlCode; + iCtrl = i; + }else{ + utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n" + "Use \".filectrl --help\" for help\n", zCmd); + rc = 1; + goto meta_command_exit; + } + } + } + if( filectrl<0 ){ + utf8_printf(stderr,"Error: unknown file-control: %s\n" + "Use \".filectrl --help\" for help\n", zCmd); + }else{ + switch(filectrl){ + case SQLITE_FCNTL_SIZE_LIMIT: { + if( nArg!=2 && nArg!=3 ) break; + iRes = nArg==3 ? integerValue(azArg[2]) : -1; + sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes); + isOk = 1; + break; + } + case SQLITE_FCNTL_LOCK_TIMEOUT: + case SQLITE_FCNTL_CHUNK_SIZE: { + int x; + if( nArg!=3 ) break; + x = (int)integerValue(azArg[2]); + sqlite3_file_control(p->db, zSchema, filectrl, &x); + isOk = 2; + break; + } + case SQLITE_FCNTL_PERSIST_WAL: + case SQLITE_FCNTL_POWERSAFE_OVERWRITE: { + int x; + if( nArg!=2 && nArg!=3 ) break; + x = nArg==3 ? booleanValue(azArg[2]) : -1; + sqlite3_file_control(p->db, zSchema, filectrl, &x); + iRes = x; + isOk = 1; + break; + } + case SQLITE_FCNTL_DATA_VERSION: + case SQLITE_FCNTL_HAS_MOVED: { + int x; + if( nArg!=2 ) break; + sqlite3_file_control(p->db, zSchema, filectrl, &x); + iRes = x; + isOk = 1; + break; + } + case SQLITE_FCNTL_TEMPFILENAME: { + char *z = 0; + if( nArg!=2 ) break; + sqlite3_file_control(p->db, zSchema, filectrl, &z); + if( z ){ + utf8_printf(p->out, "%s\n", z); + sqlite3_free(z); + } + isOk = 2; + break; + } + case SQLITE_FCNTL_RESERVE_BYTES: { + int x; + if( nArg>=3 ){ + x = atoi(azArg[2]); + sqlite3_file_control(p->db, zSchema, filectrl, &x); + } + x = -1; + sqlite3_file_control(p->db, zSchema, filectrl, &x); + utf8_printf(p->out,"%d\n", x); + isOk = 2; + break; + } + } + } + if( isOk==0 && iCtrl>=0 ){ + utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); + rc = 1; + }else if( isOk==1 ){ + char zBuf[100]; + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes); + raw_printf(p->out, "%s\n", zBuf); + } + }else + + if( c=='f' && cli_strncmp(azArg[0], "fullschema", n)==0 ){ + ShellState data; + int doStats = 0; + memcpy(&data, p, sizeof(data)); + data.showHeader = 0; + data.cMode = data.mode = MODE_Semi; + if( nArg==2 && optionMatch(azArg[1], "indent") ){ + data.cMode = data.mode = MODE_Pretty; + nArg = 1; + } + if( nArg!=1 ){ + raw_printf(stderr, "Usage: .fullschema ?--indent?\n"); + rc = 1; + goto meta_command_exit; + } + open_db(p, 0); + rc = sqlite3_exec(p->db, + "SELECT sql FROM" + " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" + " FROM sqlite_schema UNION ALL" + " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) " + "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " + "ORDER BY x", + callback, &data, 0 + ); + if( rc==SQLITE_OK ){ + sqlite3_stmt *pStmt; + rc = sqlite3_prepare_v2(p->db, + "SELECT rowid FROM sqlite_schema" + " WHERE name GLOB 'sqlite_stat[134]'", + -1, &pStmt, 0); + doStats = sqlite3_step(pStmt)==SQLITE_ROW; + sqlite3_finalize(pStmt); + } + if( doStats==0 ){ + raw_printf(p->out, "/* No STAT tables available */\n"); + }else{ + raw_printf(p->out, "ANALYZE sqlite_schema;\n"); + data.cMode = data.mode = MODE_Insert; + data.zDestTable = "sqlite_stat1"; + shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); + data.zDestTable = "sqlite_stat4"; + shell_exec(&data, "SELECT * FROM sqlite_stat4", 0); + raw_printf(p->out, "ANALYZE sqlite_schema;\n"); + } + }else + + if( c=='h' && cli_strncmp(azArg[0], "headers", n)==0 ){ + if( nArg==2 ){ + p->showHeader = booleanValue(azArg[1]); + p->shellFlgs |= SHFLG_HeaderSet; + }else{ + raw_printf(stderr, "Usage: .headers on|off\n"); + rc = 1; + } + }else + + if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){ + if( nArg>=2 ){ + n = showHelp(p->out, azArg[1]); + if( n==0 ){ + utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]); + } + }else{ + showHelp(p->out, 0); + } + }else + +#ifndef SQLITE_SHELL_FIDDLE + if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){ + char *zTable = 0; /* Insert data into this table */ + char *zSchema = 0; /* within this schema (may default to "main") */ + char *zFile = 0; /* Name of file to extra content from */ + sqlite3_stmt *pStmt = NULL; /* A statement */ + int nCol; /* Number of columns in the table */ + int nByte; /* Number of bytes in an SQL string */ + int i, j; /* Loop counters */ + int needCommit; /* True to COMMIT or ROLLBACK at end */ + int nSep; /* Number of bytes in p->colSeparator[] */ + char *zSql; /* An SQL statement */ + char *zFullTabName; /* Table name with schema if applicable */ + ImportCtx sCtx; /* Reader context */ + char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ + int eVerbose = 0; /* Larger for more console output */ + int nSkip = 0; /* Initial lines to skip */ + int useOutputMode = 1; /* Use output mode to determine separators */ + char *zCreate = 0; /* CREATE TABLE statement text */ + + failIfSafeMode(p, "cannot run .import in safe mode"); + memset(&sCtx, 0, sizeof(sCtx)); + if( p->mode==MODE_Ascii ){ + xRead = ascii_read_one_field; + }else{ + xRead = csv_read_one_field; + } + rc = 1; + for(i=1; i<nArg; i++){ + char *z = azArg[i]; + if( z[0]=='-' && z[1]=='-' ) z++; + if( z[0]!='-' ){ + if( zFile==0 ){ + zFile = z; + }else if( zTable==0 ){ + zTable = z; + }else{ + utf8_printf(p->out, "ERROR: extra argument: \"%s\". Usage:\n", z); + showHelp(p->out, "import"); + goto meta_command_exit; + } + }else if( cli_strcmp(z,"-v")==0 ){ + eVerbose++; + }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){ + zSchema = azArg[++i]; + }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){ + nSkip = integerValue(azArg[++i]); + }else if( cli_strcmp(z,"-ascii")==0 ){ + sCtx.cColSep = SEP_Unit[0]; + sCtx.cRowSep = SEP_Record[0]; + xRead = ascii_read_one_field; + useOutputMode = 0; + }else if( cli_strcmp(z,"-csv")==0 ){ + sCtx.cColSep = ','; + sCtx.cRowSep = '\n'; + xRead = csv_read_one_field; + useOutputMode = 0; + }else{ + utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z); + showHelp(p->out, "import"); + goto meta_command_exit; + } + } + if( zTable==0 ){ + utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n", + zFile==0 ? "FILE" : "TABLE"); + showHelp(p->out, "import"); + goto meta_command_exit; + } + seenInterrupt = 0; + open_db(p, 0); + if( useOutputMode ){ + /* If neither the --csv or --ascii options are specified, then set + ** the column and row separator characters from the output mode. */ + nSep = strlen30(p->colSeparator); + if( nSep==0 ){ + raw_printf(stderr, + "Error: non-null column separator required for import\n"); + goto meta_command_exit; + } + if( nSep>1 ){ + raw_printf(stderr, + "Error: multi-character column separators not allowed" + " for import\n"); + goto meta_command_exit; + } + nSep = strlen30(p->rowSeparator); + if( nSep==0 ){ + raw_printf(stderr, + "Error: non-null row separator required for import\n"); + goto meta_command_exit; + } + if( nSep==2 && p->mode==MODE_Csv + && cli_strcmp(p->rowSeparator,SEP_CrLf)==0 + ){ + /* When importing CSV (only), if the row separator is set to the + ** default output row separator, change it to the default input + ** row separator. This avoids having to maintain different input + ** and output row separators. */ + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + nSep = strlen30(p->rowSeparator); + } + if( nSep>1 ){ + raw_printf(stderr, "Error: multi-character row separators not allowed" + " for import\n"); + goto meta_command_exit; + } + sCtx.cColSep = (u8)p->colSeparator[0]; + sCtx.cRowSep = (u8)p->rowSeparator[0]; + } + sCtx.zFile = zFile; + sCtx.nLine = 1; + if( sCtx.zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + raw_printf(stderr, "Error: pipes are not supported in this OS\n"); + goto meta_command_exit; +#else + sCtx.in = popen(sCtx.zFile+1, "r"); + sCtx.zFile = "<pipe>"; + sCtx.xCloser = pclose; +#endif + }else{ + sCtx.in = fopen(sCtx.zFile, "rb"); + sCtx.xCloser = fclose; + } + if( sCtx.in==0 ){ + utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); + goto meta_command_exit; + } + if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ + char zSep[2]; + zSep[1] = 0; + zSep[0] = sCtx.cColSep; + utf8_printf(p->out, "Column separator "); + output_c_string(p->out, zSep); + utf8_printf(p->out, ", row separator "); + zSep[0] = sCtx.cRowSep; + output_c_string(p->out, zSep); + utf8_printf(p->out, "\n"); + } + sCtx.z = sqlite3_malloc64(120); + if( sCtx.z==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + /* Below, resources must be freed before exit. */ + while( (nSkip--)>0 ){ + while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} + } + if( zSchema!=0 ){ + zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable); + }else{ + zFullTabName = sqlite3_mprintf("\"%w\"", zTable); + } + zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName); + if( zSql==0 || zFullTabName==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + nByte = strlen30(zSql); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ + if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){ + sqlite3 *dbCols = 0; + char *zRenames = 0; + char *zColDefs; + zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName); + while( xRead(&sCtx) ){ + zAutoColumn(sCtx.z, &dbCols, 0); + if( sCtx.cTerm!=sCtx.cColSep ) break; + } + zColDefs = zAutoColumn(0, &dbCols, &zRenames); + if( zRenames!=0 ){ + utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr, + "Columns renamed during .import %s due to duplicates:\n" + "%s\n", sCtx.zFile, zRenames); + sqlite3_free(zRenames); + } + assert(dbCols==0); + if( zColDefs==0 ){ + utf8_printf(stderr,"%s: empty file\n", sCtx.zFile); + import_fail: + sqlite3_free(zCreate); + sqlite3_free(zSql); + sqlite3_free(zFullTabName); + import_cleanup(&sCtx); + rc = 1; + goto meta_command_exit; + } + zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); + if( eVerbose>=1 ){ + utf8_printf(p->out, "%s\n", zCreate); + } + rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); + if( rc ){ + utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); + goto import_fail; + } + sqlite3_free(zCreate); + zCreate = 0; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + } + if( rc ){ + if (pStmt) sqlite3_finalize(pStmt); + utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db)); + goto import_fail; + } + sqlite3_free(zSql); + nCol = sqlite3_column_count(pStmt); + sqlite3_finalize(pStmt); + pStmt = 0; + if( nCol==0 ) return 0; /* no columns, no error */ + zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 ); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName); + j = strlen30(zSql); + for(i=1; i<nCol; i++){ + zSql[j++] = ','; + zSql[j++] = '?'; + } + zSql[j++] = ')'; + zSql[j] = 0; + if( eVerbose>=2 ){ + utf8_printf(p->out, "Insert using: %s\n", zSql); + } + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + if( rc ){ + utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + if (pStmt) sqlite3_finalize(pStmt); + goto import_fail; + } + sqlite3_free(zSql); + sqlite3_free(zFullTabName); + needCommit = sqlite3_get_autocommit(p->db); + if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); + do{ + int startLine = sCtx.nLine; + for(i=0; i<nCol; i++){ + char *z = xRead(&sCtx); + /* + ** Did we reach end-of-file before finding any columns? + ** If so, stop instead of NULL filling the remaining columns. + */ + if( z==0 && i==0 ) break; + /* + ** Did we reach end-of-file OR end-of-line before finding any + ** columns in ASCII mode? If so, stop instead of NULL filling + ** the remaining columns. + */ + if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; + sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); + if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){ + utf8_printf(stderr, "%s:%d: expected %d columns but found %d - " + "filling the rest with NULL\n", + sCtx.zFile, startLine, nCol, i+1); + i += 2; + while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; } + } + } + if( sCtx.cTerm==sCtx.cColSep ){ + do{ + xRead(&sCtx); + i++; + }while( sCtx.cTerm==sCtx.cColSep ); + utf8_printf(stderr, "%s:%d: expected %d columns but found %d - " + "extras ignored\n", + sCtx.zFile, startLine, nCol, i); + } + if( i>=nCol ){ + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile, + startLine, sqlite3_errmsg(p->db)); + sCtx.nErr++; + }else{ + sCtx.nRow++; + } + } + }while( sCtx.cTerm!=EOF ); + + import_cleanup(&sCtx); + sqlite3_finalize(pStmt); + if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); + if( eVerbose>0 ){ + utf8_printf(p->out, + "Added %d rows with %d errors using %d lines of input\n", + sCtx.nRow, sCtx.nErr, sCtx.nLine-1); + } + }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + +#ifndef SQLITE_UNTESTABLE + if( c=='i' && cli_strncmp(azArg[0], "imposter", n)==0 ){ + char *zSql; + char *zCollist = 0; + sqlite3_stmt *pStmt; + int tnum = 0; + int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */ + int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ + int i; + if( !ShellHasFlag(p,SHFLG_TestingMode) ){ + utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n", + "imposter"); + rc = 1; + goto meta_command_exit; + } + if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ + utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n" + " .imposter off\n"); + /* Also allowed, but not documented: + ** + ** .imposter TABLE IMPOSTER + ** + ** where TABLE is a WITHOUT ROWID table. In that case, the + ** imposter is another WITHOUT ROWID table with the columns in + ** storage order. */ + rc = 1; + goto meta_command_exit; + } + open_db(p, 0); + if( nArg==2 ){ + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1); + goto meta_command_exit; + } + zSql = sqlite3_mprintf( + "SELECT rootpage, 0 FROM sqlite_schema" + " WHERE name='%q' AND type='index'" + "UNION ALL " + "SELECT rootpage, 1 FROM sqlite_schema" + " WHERE name='%q' AND type='table'" + " AND sql LIKE '%%without%%rowid%%'", + azArg[1], azArg[1] + ); + sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + tnum = sqlite3_column_int(pStmt, 0); + isWO = sqlite3_column_int(pStmt, 1); + } + sqlite3_finalize(pStmt); + zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + i = 0; + while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + char zLabel[20]; + const char *zCol = (const char*)sqlite3_column_text(pStmt,2); + i++; + if( zCol==0 ){ + if( sqlite3_column_int(pStmt,1)==-1 ){ + zCol = "_ROWID_"; + }else{ + sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i); + zCol = zLabel; + } + } + if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){ + lenPK = (int)strlen(zCollist); + } + if( zCollist==0 ){ + zCollist = sqlite3_mprintf("\"%w\"", zCol); + }else{ + zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol); + } + } + sqlite3_finalize(pStmt); + if( i==0 || tnum==0 ){ + utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]); + rc = 1; + sqlite3_free(zCollist); + goto meta_command_exit; + } + if( lenPK==0 ) lenPK = 100000; + zSql = sqlite3_mprintf( + "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID", + azArg[2], zCollist, lenPK, zCollist); + sqlite3_free(zCollist); + rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum); + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(p->db, zSql, 0, 0, 0); + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); + if( rc ){ + utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); + }else{ + utf8_printf(stdout, "%s;\n", zSql); + raw_printf(stdout, + "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n", + azArg[1], isWO ? "table" : "index" + ); + } + }else{ + raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); + rc = 1; + } + sqlite3_free(zSql); + }else +#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */ + +#ifdef SQLITE_ENABLE_IOTRACE + if( c=='i' && cli_strncmp(azArg[0], "iotrace", n)==0 ){ + SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...); + if( iotrace && iotrace!=stdout ) fclose(iotrace); + iotrace = 0; + if( nArg<2 ){ + sqlite3IoTrace = 0; + }else if( cli_strcmp(azArg[1], "-")==0 ){ + sqlite3IoTrace = iotracePrintf; + iotrace = stdout; + }else{ + iotrace = fopen(azArg[1], "w"); + if( iotrace==0 ){ + utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); + sqlite3IoTrace = 0; + rc = 1; + }else{ + sqlite3IoTrace = iotracePrintf; + } + } + }else +#endif + + if( c=='l' && n>=5 && cli_strncmp(azArg[0], "limits", n)==0 ){ + static const struct { + const char *zLimitName; /* Name of a limit */ + int limitCode; /* Integer code for that limit */ + } aLimit[] = { + { "length", SQLITE_LIMIT_LENGTH }, + { "sql_length", SQLITE_LIMIT_SQL_LENGTH }, + { "column", SQLITE_LIMIT_COLUMN }, + { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH }, + { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT }, + { "vdbe_op", SQLITE_LIMIT_VDBE_OP }, + { "function_arg", SQLITE_LIMIT_FUNCTION_ARG }, + { "attached", SQLITE_LIMIT_ATTACHED }, + { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH }, + { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER }, + { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH }, + { "worker_threads", SQLITE_LIMIT_WORKER_THREADS }, + }; + int i, n2; + open_db(p, 0); + if( nArg==1 ){ + for(i=0; i<ArraySize(aLimit); i++){ + printf("%20s %d\n", aLimit[i].zLimitName, + sqlite3_limit(p->db, aLimit[i].limitCode, -1)); + } + }else if( nArg>3 ){ + raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n"); + rc = 1; + goto meta_command_exit; + }else{ + int iLimit = -1; + n2 = strlen30(azArg[1]); + for(i=0; i<ArraySize(aLimit); i++){ + if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){ + if( iLimit<0 ){ + iLimit = i; + }else{ + utf8_printf(stderr, "ambiguous limit: \"%s\"\n", azArg[1]); + rc = 1; + goto meta_command_exit; + } + } + } + if( iLimit<0 ){ + utf8_printf(stderr, "unknown limit: \"%s\"\n" + "enter \".limits\" with no arguments for a list.\n", + azArg[1]); + rc = 1; + goto meta_command_exit; + } + if( nArg==3 ){ + sqlite3_limit(p->db, aLimit[iLimit].limitCode, + (int)integerValue(azArg[2])); + } + printf("%20s %d\n", aLimit[iLimit].zLimitName, + sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); + } + }else + + if( c=='l' && n>2 && cli_strncmp(azArg[0], "lint", n)==0 ){ + open_db(p, 0); + lintDotCommand(p, azArg, nArg); + }else + +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE) + if( c=='l' && cli_strncmp(azArg[0], "load", n)==0 ){ + const char *zFile, *zProc; + char *zErrMsg = 0; + failIfSafeMode(p, "cannot run .load in safe mode"); + if( nArg<2 || azArg[1][0]==0 ){ + /* Must have a non-empty FILE. (Will not load self.) */ + raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n"); + rc = 1; + goto meta_command_exit; + } + zFile = azArg[1]; + zProc = nArg>=3 ? azArg[2] : 0; + open_db(p, 0); + rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, "Error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + rc = 1; + } + }else +#endif + + if( c=='l' && cli_strncmp(azArg[0], "log", n)==0 ){ + if( nArg!=2 ){ + raw_printf(stderr, "Usage: .log FILENAME\n"); + rc = 1; + }else{ + const char *zFile = azArg[1]; + if( p->bSafeMode + && cli_strcmp(zFile,"on")!=0 + && cli_strcmp(zFile,"off")!=0 + ){ + raw_printf(stdout, "cannot set .log to anything other " + "than \"on\" or \"off\"\n"); + zFile = "off"; + } + output_file_close(p->pLog); + if( cli_strcmp(zFile,"on")==0 ) zFile = "stdout"; + p->pLog = output_file_open(zFile, 0); + } + }else + + if( c=='m' && cli_strncmp(azArg[0], "mode", n)==0 ){ + const char *zMode = 0; + const char *zTabname = 0; + int i, n2; + ColModeOpts cmOpts = ColModeOpts_default; + for(i=1; i<nArg; i++){ + const char *z = azArg[i]; + if( optionMatch(z,"wrap") && i+1<nArg ){ + cmOpts.iWrap = integerValue(azArg[++i]); + }else if( optionMatch(z,"ww") ){ + cmOpts.bWordWrap = 1; + }else if( optionMatch(z,"wordwrap") && i+1<nArg ){ + cmOpts.bWordWrap = (u8)booleanValue(azArg[++i]); + }else if( optionMatch(z,"quote") ){ + cmOpts.bQuote = 1; + }else if( optionMatch(z,"noquote") ){ + cmOpts.bQuote = 0; + }else if( zMode==0 ){ + zMode = z; + /* Apply defaults for qbox pseudo-mode. If that + * overwrites already-set values, user was informed of this. + */ + if( cli_strcmp(z, "qbox")==0 ){ + ColModeOpts cmo = ColModeOpts_default_qbox; + zMode = "box"; + cmOpts = cmo; + } + }else if( zTabname==0 ){ + zTabname = z; + }else if( z[0]=='-' ){ + utf8_printf(stderr, "unknown option: %s\n", z); + utf8_printf(stderr, "options:\n" + " --noquote\n" + " --quote\n" + " --wordwrap on/off\n" + " --wrap N\n" + " --ww\n"); + rc = 1; + goto meta_command_exit; + }else{ + utf8_printf(stderr, "extra argument: \"%s\"\n", z); + rc = 1; + goto meta_command_exit; + } + } + if( zMode==0 ){ + if( p->mode==MODE_Column + || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) + ){ + raw_printf + (p->out, + "current output mode: %s --wrap %d --wordwrap %s --%squote\n", + modeDescr[p->mode], p->cmOpts.iWrap, + p->cmOpts.bWordWrap ? "on" : "off", + p->cmOpts.bQuote ? "" : "no"); + }else{ + raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]); + } + zMode = modeDescr[p->mode]; + } + n2 = strlen30(zMode); + if( cli_strncmp(zMode,"lines",n2)==0 ){ + p->mode = MODE_Line; + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + }else if( cli_strncmp(zMode,"columns",n2)==0 ){ + p->mode = MODE_Column; + if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){ + p->showHeader = 1; + } + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + p->cmOpts = cmOpts; + }else if( cli_strncmp(zMode,"list",n2)==0 ){ + p->mode = MODE_List; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + }else if( cli_strncmp(zMode,"html",n2)==0 ){ + p->mode = MODE_Html; + }else if( cli_strncmp(zMode,"tcl",n2)==0 ){ + p->mode = MODE_Tcl; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + }else if( cli_strncmp(zMode,"csv",n2)==0 ){ + p->mode = MODE_Csv; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); + }else if( cli_strncmp(zMode,"tabs",n2)==0 ){ + p->mode = MODE_List; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab); + }else if( cli_strncmp(zMode,"insert",n2)==0 ){ + p->mode = MODE_Insert; + set_table_name(p, zTabname ? zTabname : "table"); + }else if( cli_strncmp(zMode,"quote",n2)==0 ){ + p->mode = MODE_Quote; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + }else if( cli_strncmp(zMode,"ascii",n2)==0 ){ + p->mode = MODE_Ascii; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); + }else if( cli_strncmp(zMode,"markdown",n2)==0 ){ + p->mode = MODE_Markdown; + p->cmOpts = cmOpts; + }else if( cli_strncmp(zMode,"table",n2)==0 ){ + p->mode = MODE_Table; + p->cmOpts = cmOpts; + }else if( cli_strncmp(zMode,"box",n2)==0 ){ + p->mode = MODE_Box; + p->cmOpts = cmOpts; + }else if( cli_strncmp(zMode,"count",n2)==0 ){ + p->mode = MODE_Count; + }else if( cli_strncmp(zMode,"off",n2)==0 ){ + p->mode = MODE_Off; + }else if( cli_strncmp(zMode,"json",n2)==0 ){ + p->mode = MODE_Json; + }else{ + raw_printf(stderr, "Error: mode should be one of: " + "ascii box column csv html insert json line list markdown " + "qbox quote table tabs tcl\n"); + rc = 1; + } + p->cMode = p->mode; + }else + +#ifndef SQLITE_SHELL_FIDDLE + if( c=='n' && cli_strcmp(azArg[0], "nonce")==0 ){ + if( nArg!=2 ){ + raw_printf(stderr, "Usage: .nonce NONCE\n"); + rc = 1; + }else if( p->zNonce==0 || cli_strcmp(azArg[1],p->zNonce)!=0 ){ + raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", + p->lineno, azArg[1]); + exit(1); + }else{ + p->bSafeMode = 0; + return 0; /* Return immediately to bypass the safe mode reset + ** at the end of this procedure */ + } + }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + + if( c=='n' && cli_strncmp(azArg[0], "nullvalue", n)==0 ){ + if( nArg==2 ){ + sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, + "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); + }else{ + raw_printf(stderr, "Usage: .nullvalue STRING\n"); + rc = 1; + } + }else + + if( c=='o' && cli_strncmp(azArg[0], "open", n)==0 && n>=2 ){ + const char *zFN = 0; /* Pointer to constant filename */ + char *zNewFilename = 0; /* Name of the database file to open */ + int iName = 1; /* Index in azArg[] of the filename */ + int newFlag = 0; /* True to delete file before opening */ + int openMode = SHELL_OPEN_UNSPEC; + + /* Check for command-line arguments */ + for(iName=1; iName<nArg; iName++){ + const char *z = azArg[iName]; +#ifndef SQLITE_SHELL_FIDDLE + if( optionMatch(z,"new") ){ + newFlag = 1; +#ifdef SQLITE_HAVE_ZLIB + }else if( optionMatch(z, "zip") ){ + openMode = SHELL_OPEN_ZIPFILE; +#endif + }else if( optionMatch(z, "append") ){ + openMode = SHELL_OPEN_APPENDVFS; + }else if( optionMatch(z, "readonly") ){ + openMode = SHELL_OPEN_READONLY; + }else if( optionMatch(z, "nofollow") ){ + p->openFlags |= SQLITE_OPEN_NOFOLLOW; +#ifndef SQLITE_OMIT_DESERIALIZE + }else if( optionMatch(z, "deserialize") ){ + openMode = SHELL_OPEN_DESERIALIZE; + }else if( optionMatch(z, "hexdb") ){ + openMode = SHELL_OPEN_HEXDB; + }else if( optionMatch(z, "maxsize") && iName+1<nArg ){ + p->szMax = integerValue(azArg[++iName]); +#endif /* SQLITE_OMIT_DESERIALIZE */ + }else +#endif /* !SQLITE_SHELL_FIDDLE */ + if( z[0]=='-' ){ + utf8_printf(stderr, "unknown option: %s\n", z); + rc = 1; + goto meta_command_exit; + }else if( zFN ){ + utf8_printf(stderr, "extra argument: \"%s\"\n", z); + rc = 1; + goto meta_command_exit; + }else{ + zFN = z; + } + } + + /* Close the existing database */ + session_close_all(p, -1); + close_db(p->db); + p->db = 0; + p->pAuxDb->zDbFilename = 0; + sqlite3_free(p->pAuxDb->zFreeOnClose); + p->pAuxDb->zFreeOnClose = 0; + p->openMode = openMode; + p->openFlags = 0; + p->szMax = 0; + + /* If a filename is specified, try to open it first */ + if( zFN || p->openMode==SHELL_OPEN_HEXDB ){ + if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN); +#ifndef SQLITE_SHELL_FIDDLE + if( p->bSafeMode + && p->openMode!=SHELL_OPEN_HEXDB + && zFN + && cli_strcmp(zFN,":memory:")!=0 + ){ + failIfSafeMode(p, "cannot open disk-based database files in safe mode"); + } +#else + /* WASM mode has its own sandboxed pseudo-filesystem. */ +#endif + if( zFN ){ + zNewFilename = sqlite3_mprintf("%s", zFN); + shell_check_oom(zNewFilename); + }else{ + zNewFilename = 0; + } + p->pAuxDb->zDbFilename = zNewFilename; + open_db(p, OPEN_DB_KEEPALIVE); + if( p->db==0 ){ + utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename); + sqlite3_free(zNewFilename); + }else{ + p->pAuxDb->zFreeOnClose = zNewFilename; + } + } + if( p->db==0 ){ + /* As a fall-back open a TEMP database */ + p->pAuxDb->zDbFilename = 0; + open_db(p, 0); + } + }else + +#ifndef SQLITE_SHELL_FIDDLE + if( (c=='o' + && (cli_strncmp(azArg[0], "output", n)==0 + || cli_strncmp(azArg[0], "once", n)==0)) + || (c=='e' && n==5 && cli_strcmp(azArg[0],"excel")==0) + ){ + char *zFile = 0; + int bTxtMode = 0; + int i; + int eMode = 0; + int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */ + unsigned char zBOM[4]; /* Byte-order mark to using if --bom is present */ + + zBOM[0] = 0; + failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); + if( c=='e' ){ + eMode = 'x'; + bOnce = 2; + }else if( cli_strncmp(azArg[0],"once",n)==0 ){ + bOnce = 1; + } + for(i=1; i<nArg; i++){ + char *z = azArg[i]; + if( z[0]=='-' ){ + if( z[1]=='-' ) z++; + if( cli_strcmp(z,"-bom")==0 ){ + zBOM[0] = 0xef; + zBOM[1] = 0xbb; + zBOM[2] = 0xbf; + zBOM[3] = 0; + }else if( c!='e' && cli_strcmp(z,"-x")==0 ){ + eMode = 'x'; /* spreadsheet */ + }else if( c!='e' && cli_strcmp(z,"-e")==0 ){ + eMode = 'e'; /* text editor */ + }else{ + utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", + azArg[i]); + showHelp(p->out, azArg[0]); + rc = 1; + goto meta_command_exit; + } + }else if( zFile==0 && eMode!='e' && eMode!='x' ){ + zFile = sqlite3_mprintf("%s", z); + if( zFile && zFile[0]=='|' ){ + while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]); + break; + } + }else{ + utf8_printf(p->out,"ERROR: extra parameter: \"%s\". Usage:\n", + azArg[i]); + showHelp(p->out, azArg[0]); + rc = 1; + sqlite3_free(zFile); + goto meta_command_exit; + } + } + if( zFile==0 ){ + zFile = sqlite3_mprintf("stdout"); + } + if( bOnce ){ + p->outCount = 2; + }else{ + p->outCount = 0; + } + output_reset(p); +#ifndef SQLITE_NOHAVE_SYSTEM + if( eMode=='e' || eMode=='x' ){ + p->doXdgOpen = 1; + outputModePush(p); + if( eMode=='x' ){ + /* spreadsheet mode. Output as CSV. */ + newTempFile(p, "csv"); + ShellClearFlag(p, SHFLG_Echo); + p->mode = MODE_Csv; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); + }else{ + /* text editor mode */ + newTempFile(p, "txt"); + bTxtMode = 1; + } + sqlite3_free(zFile); + zFile = sqlite3_mprintf("%s", p->zTempFile); + } +#endif /* SQLITE_NOHAVE_SYSTEM */ + shell_check_oom(zFile); + if( zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + raw_printf(stderr, "Error: pipes are not supported in this OS\n"); + rc = 1; + p->out = stdout; +#else + p->out = popen(zFile + 1, "w"); + if( p->out==0 ){ + utf8_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1); + p->out = stdout; + rc = 1; + }else{ + if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out); + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); + } +#endif + }else{ + p->out = output_file_open(zFile, bTxtMode); + if( p->out==0 ){ + if( cli_strcmp(zFile,"off")!=0 ){ + utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile); + } + p->out = stdout; + rc = 1; + } else { + if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out); + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); + } + } + sqlite3_free(zFile); + }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + + if( c=='p' && n>=3 && cli_strncmp(azArg[0], "parameter", n)==0 ){ + open_db(p,0); + if( nArg<=1 ) goto parameter_syntax_error; + + /* .parameter clear + ** Clear all bind parameters by dropping the TEMP table that holds them. + */ + if( nArg==2 && cli_strcmp(azArg[1],"clear")==0 ){ + sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp.sqlite_parameters;", + 0, 0, 0); + }else + + /* .parameter list + ** List all bind parameters. + */ + if( nArg==2 && cli_strcmp(azArg[1],"list")==0 ){ + sqlite3_stmt *pStmt = 0; + int rx; + int len = 0; + rx = sqlite3_prepare_v2(p->db, + "SELECT max(length(key)) " + "FROM temp.sqlite_parameters;", -1, &pStmt, 0); + if( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + len = sqlite3_column_int(pStmt, 0); + if( len>40 ) len = 40; + } + sqlite3_finalize(pStmt); + pStmt = 0; + if( len ){ + rx = sqlite3_prepare_v2(p->db, + "SELECT key, quote(value) " + "FROM temp.sqlite_parameters;", -1, &pStmt, 0); + while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), + sqlite3_column_text(pStmt,1)); + } + sqlite3_finalize(pStmt); + } + }else + + /* .parameter init + ** Make sure the TEMP table used to hold bind parameters exists. + ** Create it if necessary. + */ + if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){ + bind_table_init(p); + }else + + /* .parameter set NAME VALUE + ** Set or reset a bind parameter. NAME should be the full parameter + ** name exactly as it appears in the query. (ex: $abc, @def). The + ** VALUE can be in either SQL literal notation, or if not it will be + ** understood to be a text string. + */ + if( nArg==4 && cli_strcmp(azArg[1],"set")==0 ){ + int rx; + char *zSql; + sqlite3_stmt *pStmt; + const char *zKey = azArg[2]; + const char *zValue = azArg[3]; + bind_table_init(p); + zSql = sqlite3_mprintf( + "REPLACE INTO temp.sqlite_parameters(key,value)" + "VALUES(%Q,%s);", zKey, zValue); + shell_check_oom(zSql); + pStmt = 0; + rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rx!=SQLITE_OK ){ + sqlite3_finalize(pStmt); + pStmt = 0; + zSql = sqlite3_mprintf( + "REPLACE INTO temp.sqlite_parameters(key,value)" + "VALUES(%Q,%Q);", zKey, zValue); + shell_check_oom(zSql); + rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rx!=SQLITE_OK ){ + utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); + sqlite3_finalize(pStmt); + pStmt = 0; + rc = 1; + } + } + sqlite3_step(pStmt); + sqlite3_finalize(pStmt); + }else + + /* .parameter unset NAME + ** Remove the NAME binding from the parameter binding table, if it + ** exists. + */ + if( nArg==3 && cli_strcmp(azArg[1],"unset")==0 ){ + char *zSql = sqlite3_mprintf( + "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]); + shell_check_oom(zSql); + sqlite3_exec(p->db, zSql, 0, 0, 0); + sqlite3_free(zSql); + }else + /* If no command name matches, show a syntax error */ + parameter_syntax_error: + showHelp(p->out, "parameter"); + }else + + if( c=='p' && n>=3 && cli_strncmp(azArg[0], "print", n)==0 ){ + int i; + for(i=1; i<nArg; i++){ + if( i>1 ) raw_printf(p->out, " "); + utf8_printf(p->out, "%s", azArg[i]); + } + raw_printf(p->out, "\n"); + }else + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( c=='p' && n>=3 && cli_strncmp(azArg[0], "progress", n)==0 ){ + int i; + int nn = 0; + p->flgProgress = 0; + p->mxProgress = 0; + p->nProgress = 0; + for(i=1; i<nArg; i++){ + const char *z = azArg[i]; + if( z[0]=='-' ){ + z++; + if( z[0]=='-' ) z++; + if( cli_strcmp(z,"quiet")==0 || cli_strcmp(z,"q")==0 ){ + p->flgProgress |= SHELL_PROGRESS_QUIET; + continue; + } + if( cli_strcmp(z,"reset")==0 ){ + p->flgProgress |= SHELL_PROGRESS_RESET; + continue; + } + if( cli_strcmp(z,"once")==0 ){ + p->flgProgress |= SHELL_PROGRESS_ONCE; + continue; + } + if( cli_strcmp(z,"limit")==0 ){ + if( i+1>=nArg ){ + utf8_printf(stderr, "Error: missing argument on --limit\n"); + rc = 1; + goto meta_command_exit; + }else{ + p->mxProgress = (int)integerValue(azArg[++i]); + } + continue; + } + utf8_printf(stderr, "Error: unknown option: \"%s\"\n", azArg[i]); + rc = 1; + goto meta_command_exit; + }else{ + nn = (int)integerValue(z); + } + } + open_db(p, 0); + sqlite3_progress_handler(p->db, nn, progress_handler, p); + }else +#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ + + if( c=='p' && cli_strncmp(azArg[0], "prompt", n)==0 ){ + if( nArg >= 2) { + shell_strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); + } + if( nArg >= 3) { + shell_strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); + } + }else + +#ifndef SQLITE_SHELL_FIDDLE + if( c=='q' && cli_strncmp(azArg[0], "quit", n)==0 ){ + rc = 2; + }else +#endif + +#ifndef SQLITE_SHELL_FIDDLE + if( c=='r' && n>=3 && cli_strncmp(azArg[0], "read", n)==0 ){ + FILE *inSaved = p->in; + int savedLineno = p->lineno; + failIfSafeMode(p, "cannot run .read in safe mode"); + if( nArg!=2 ){ + raw_printf(stderr, "Usage: .read FILE\n"); + rc = 1; + goto meta_command_exit; + } + if( azArg[1][0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + raw_printf(stderr, "Error: pipes are not supported in this OS\n"); + rc = 1; + p->out = stdout; +#else + p->in = popen(azArg[1]+1, "r"); + if( p->in==0 ){ + utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); + rc = 1; + }else{ + rc = process_input(p); + pclose(p->in); + } +#endif + }else if( (p->in = openChrSource(azArg[1]))==0 ){ + utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); + rc = 1; + }else{ + rc = process_input(p); + fclose(p->in); + } + p->in = inSaved; + p->lineno = savedLineno; + }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + +#ifndef SQLITE_SHELL_FIDDLE + if( c=='r' && n>=3 && cli_strncmp(azArg[0], "restore", n)==0 ){ + const char *zSrcFile; + const char *zDb; + sqlite3 *pSrc; + sqlite3_backup *pBackup; + int nTimeout = 0; + + failIfSafeMode(p, "cannot run .restore in safe mode"); + if( nArg==2 ){ + zSrcFile = azArg[1]; + zDb = "main"; + }else if( nArg==3 ){ + zSrcFile = azArg[2]; + zDb = azArg[1]; + }else{ + raw_printf(stderr, "Usage: .restore ?DB? FILE\n"); + rc = 1; + goto meta_command_exit; + } + rc = sqlite3_open(zSrcFile, &pSrc); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, "Error: cannot open \"%s\"\n", zSrcFile); + close_db(pSrc); + return 1; + } + open_db(p, 0); + pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main"); + if( pBackup==0 ){ + utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + close_db(pSrc); + return 1; + } + while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK + || rc==SQLITE_BUSY ){ + if( rc==SQLITE_BUSY ){ + if( nTimeout++ >= 3 ) break; + sqlite3_sleep(100); + } + } + sqlite3_backup_finish(pBackup); + if( rc==SQLITE_DONE ){ + rc = 0; + }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){ + raw_printf(stderr, "Error: source database is busy\n"); + rc = 1; + }else{ + utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + rc = 1; + } + close_db(pSrc); + }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + + if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ + if( nArg==2 ){ + if( cli_strcmp(azArg[1], "vm")==0 ){ + p->scanstatsOn = 3; + }else + if( cli_strcmp(azArg[1], "est")==0 ){ + p->scanstatsOn = 2; + }else{ + p->scanstatsOn = (u8)booleanValue(azArg[1]); + } + open_db(p, 0); + sqlite3_db_config( + p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 + ); +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS + raw_printf(stderr, "Warning: .scanstats not available in this build.\n"); +#endif + }else{ + raw_printf(stderr, "Usage: .scanstats on|off|est\n"); + rc = 1; + } + }else + + if( c=='s' && cli_strncmp(azArg[0], "schema", n)==0 ){ + ShellText sSelect; + ShellState data; + char *zErrMsg = 0; + const char *zDiv = "("; + const char *zName = 0; + int iSchema = 0; + int bDebug = 0; + int bNoSystemTabs = 0; + int ii; + + open_db(p, 0); + memcpy(&data, p, sizeof(data)); + data.showHeader = 0; + data.cMode = data.mode = MODE_Semi; + initText(&sSelect); + for(ii=1; ii<nArg; ii++){ + if( optionMatch(azArg[ii],"indent") ){ + data.cMode = data.mode = MODE_Pretty; + }else if( optionMatch(azArg[ii],"debug") ){ + bDebug = 1; + }else if( optionMatch(azArg[ii],"nosys") ){ + bNoSystemTabs = 1; + }else if( azArg[ii][0]=='-' ){ + utf8_printf(stderr, "Unknown option: \"%s\"\n", azArg[ii]); + rc = 1; + goto meta_command_exit; + }else if( zName==0 ){ + zName = azArg[ii]; + }else{ + raw_printf(stderr, + "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n"); + rc = 1; + goto meta_command_exit; + } + } + if( zName!=0 ){ + int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0 + || sqlite3_strlike(zName, "sqlite_schema", '\\')==0 + || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0 + || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0; + if( isSchema ){ + char *new_argv[2], *new_colv[2]; + new_argv[0] = sqlite3_mprintf( + "CREATE TABLE %s (\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " rootpage integer,\n" + " sql text\n" + ")", zName); + shell_check_oom(new_argv[0]); + new_argv[1] = 0; + new_colv[0] = "sql"; + new_colv[1] = 0; + callback(&data, 1, new_argv, new_colv); + sqlite3_free(new_argv[0]); + } + } + if( zDiv ){ + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list", + -1, &pStmt, 0); + if( rc ){ + utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + sqlite3_finalize(pStmt); + rc = 1; + goto meta_command_exit; + } + appendText(&sSelect, "SELECT sql FROM", 0); + iSchema = 0; + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pStmt, 0); + char zScNum[30]; + sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema); + appendText(&sSelect, zDiv, 0); + zDiv = " UNION ALL "; + appendText(&sSelect, "SELECT shell_add_schema(sql,", 0); + if( sqlite3_stricmp(zDb, "main")!=0 ){ + appendText(&sSelect, zDb, '\''); + }else{ + appendText(&sSelect, "NULL", 0); + } + appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0); + appendText(&sSelect, zScNum, 0); + appendText(&sSelect, " AS snum, ", 0); + appendText(&sSelect, zDb, '\''); + appendText(&sSelect, " AS sname FROM ", 0); + appendText(&sSelect, zDb, quoteChar(zDb)); + appendText(&sSelect, ".sqlite_schema", 0); + } + sqlite3_finalize(pStmt); +#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS + if( zName ){ + appendText(&sSelect, + " UNION ALL SELECT shell_module_schema(name)," + " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list", + 0); + } +#endif + appendText(&sSelect, ") WHERE ", 0); + if( zName ){ + char *zQarg = sqlite3_mprintf("%Q", zName); + int bGlob; + shell_check_oom(zQarg); + bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 || + strchr(zName, '[') != 0; + if( strchr(zName, '.') ){ + appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0); + }else{ + appendText(&sSelect, "lower(tbl_name)", 0); + } + appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0); + appendText(&sSelect, zQarg, 0); + if( !bGlob ){ + appendText(&sSelect, " ESCAPE '\\' ", 0); + } + appendText(&sSelect, " AND ", 0); + sqlite3_free(zQarg); + } + if( bNoSystemTabs ){ + appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0); + } + appendText(&sSelect, "sql IS NOT NULL" + " ORDER BY snum, rowid", 0); + if( bDebug ){ + utf8_printf(p->out, "SQL: %s;\n", sSelect.z); + }else{ + rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg); + } + freeText(&sSelect); + } + if( zErrMsg ){ + utf8_printf(stderr,"Error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + rc = 1; + }else if( rc != SQLITE_OK ){ + raw_printf(stderr,"Error: querying schema information\n"); + rc = 1; + }else{ + rc = 0; + } + }else + + if( (c=='s' && n==11 && cli_strncmp(azArg[0], "selecttrace", n)==0) + || (c=='t' && n==9 && cli_strncmp(azArg[0], "treetrace", n)==0) + ){ + unsigned int x = nArg>=2? (unsigned int)integerValue(azArg[1]) : 0xffffffff; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); + }else + +#if defined(SQLITE_ENABLE_SESSION) + if( c=='s' && cli_strncmp(azArg[0],"session",n)==0 && n>=3 ){ + struct AuxDb *pAuxDb = p->pAuxDb; + OpenSession *pSession = &pAuxDb->aSession[0]; + char **azCmd = &azArg[1]; + int iSes = 0; + int nCmd = nArg - 1; + int i; + if( nArg<=1 ) goto session_syntax_error; + open_db(p, 0); + if( nArg>=3 ){ + for(iSes=0; iSes<pAuxDb->nSession; iSes++){ + if( cli_strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break; + } + if( iSes<pAuxDb->nSession ){ + pSession = &pAuxDb->aSession[iSes]; + azCmd++; + nCmd--; + }else{ + pSession = &pAuxDb->aSession[0]; + iSes = 0; + } + } + + /* .session attach TABLE + ** Invoke the sqlite3session_attach() interface to attach a particular + ** table so that it is never filtered. + */ + if( cli_strcmp(azCmd[0],"attach")==0 ){ + if( nCmd!=2 ) goto session_syntax_error; + if( pSession->p==0 ){ + session_not_open: + raw_printf(stderr, "ERROR: No sessions are open\n"); + }else{ + rc = sqlite3session_attach(pSession->p, azCmd[1]); + if( rc ){ + raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc); + rc = 0; + } + } + }else + + /* .session changeset FILE + ** .session patchset FILE + ** Write a changeset or patchset into a file. The file is overwritten. + */ + if( cli_strcmp(azCmd[0],"changeset")==0 + || cli_strcmp(azCmd[0],"patchset")==0 + ){ + FILE *out = 0; + failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]); + if( nCmd!=2 ) goto session_syntax_error; + if( pSession->p==0 ) goto session_not_open; + out = fopen(azCmd[1], "wb"); + if( out==0 ){ + utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n", + azCmd[1]); + }else{ + int szChng; + void *pChng; + if( azCmd[0][0]=='c' ){ + rc = sqlite3session_changeset(pSession->p, &szChng, &pChng); + }else{ + rc = sqlite3session_patchset(pSession->p, &szChng, &pChng); + } + if( rc ){ + printf("Error: error code %d\n", rc); + rc = 0; + } + if( pChng + && fwrite(pChng, szChng, 1, out)!=1 ){ + raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n", + szChng); + } + sqlite3_free(pChng); + fclose(out); + } + }else + + /* .session close + ** Close the identified session + */ + if( cli_strcmp(azCmd[0], "close")==0 ){ + if( nCmd!=1 ) goto session_syntax_error; + if( pAuxDb->nSession ){ + session_close(pSession); + pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession]; + } + }else + + /* .session enable ?BOOLEAN? + ** Query or set the enable flag + */ + if( cli_strcmp(azCmd[0], "enable")==0 ){ + int ii; + if( nCmd>2 ) goto session_syntax_error; + ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); + if( pAuxDb->nSession ){ + ii = sqlite3session_enable(pSession->p, ii); + utf8_printf(p->out, "session %s enable flag = %d\n", + pSession->zName, ii); + } + }else + + /* .session filter GLOB .... + ** Set a list of GLOB patterns of table names to be excluded. + */ + if( cli_strcmp(azCmd[0], "filter")==0 ){ + int ii, nByte; + if( nCmd<2 ) goto session_syntax_error; + if( pAuxDb->nSession ){ + for(ii=0; ii<pSession->nFilter; ii++){ + sqlite3_free(pSession->azFilter[ii]); + } + sqlite3_free(pSession->azFilter); + nByte = sizeof(pSession->azFilter[0])*(nCmd-1); + pSession->azFilter = sqlite3_malloc( nByte ); + if( pSession->azFilter==0 ){ + raw_printf(stderr, "Error: out or memory\n"); + exit(1); + } + for(ii=1; ii<nCmd; ii++){ + char *x = pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); + shell_check_oom(x); + } + pSession->nFilter = ii-1; + } + }else + + /* .session indirect ?BOOLEAN? + ** Query or set the indirect flag + */ + if( cli_strcmp(azCmd[0], "indirect")==0 ){ + int ii; + if( nCmd>2 ) goto session_syntax_error; + ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); + if( pAuxDb->nSession ){ + ii = sqlite3session_indirect(pSession->p, ii); + utf8_printf(p->out, "session %s indirect flag = %d\n", + pSession->zName, ii); + } + }else + + /* .session isempty + ** Determine if the session is empty + */ + if( cli_strcmp(azCmd[0], "isempty")==0 ){ + int ii; + if( nCmd!=1 ) goto session_syntax_error; + if( pAuxDb->nSession ){ + ii = sqlite3session_isempty(pSession->p); + utf8_printf(p->out, "session %s isempty flag = %d\n", + pSession->zName, ii); + } + }else + + /* .session list + ** List all currently open sessions + */ + if( cli_strcmp(azCmd[0],"list")==0 ){ + for(i=0; i<pAuxDb->nSession; i++){ + utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName); + } + }else + + /* .session open DB NAME + ** Open a new session called NAME on the attached database DB. + ** DB is normally "main". + */ + if( cli_strcmp(azCmd[0],"open")==0 ){ + char *zName; + if( nCmd!=3 ) goto session_syntax_error; + zName = azCmd[2]; + if( zName[0]==0 ) goto session_syntax_error; + for(i=0; i<pAuxDb->nSession; i++){ + if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ + utf8_printf(stderr, "Session \"%s\" already exists\n", zName); + goto meta_command_exit; + } + } + if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ + raw_printf(stderr, + "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); + goto meta_command_exit; + } + pSession = &pAuxDb->aSession[pAuxDb->nSession]; + rc = sqlite3session_create(p->db, azCmd[1], &pSession->p); + if( rc ){ + raw_printf(stderr, "Cannot open session: error code=%d\n", rc); + rc = 0; + goto meta_command_exit; + } + pSession->nFilter = 0; + sqlite3session_table_filter(pSession->p, session_filter, pSession); + pAuxDb->nSession++; + pSession->zName = sqlite3_mprintf("%s", zName); + shell_check_oom(pSession->zName); + }else + /* If no command name matches, show a syntax error */ + session_syntax_error: + showHelp(p->out, "session"); + }else +#endif + +#ifdef SQLITE_DEBUG + /* Undocumented commands for internal testing. Subject to change + ** without notice. */ + if( c=='s' && n>=10 && cli_strncmp(azArg[0], "selftest-", 9)==0 ){ + if( cli_strncmp(azArg[0]+9, "boolean", n-9)==0 ){ + int i, v; + for(i=1; i<nArg; i++){ + v = booleanValue(azArg[i]); + utf8_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v); + } + } + if( cli_strncmp(azArg[0]+9, "integer", n-9)==0 ){ + int i; sqlite3_int64 v; + for(i=1; i<nArg; i++){ + char zBuf[200]; + v = integerValue(azArg[i]); + sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v); + utf8_printf(p->out, "%s", zBuf); + } + } + }else +#endif + + if( c=='s' && n>=4 && cli_strncmp(azArg[0],"selftest",n)==0 ){ + int bIsInit = 0; /* True to initialize the SELFTEST table */ + int bVerbose = 0; /* Verbose output */ + int bSelftestExists; /* True if SELFTEST already exists */ + int i, k; /* Loop counters */ + int nTest = 0; /* Number of tests runs */ + int nErr = 0; /* Number of errors seen */ + ShellText str; /* Answer for a query */ + sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */ + + open_db(p,0); + for(i=1; i<nArg; i++){ + const char *z = azArg[i]; + if( z[0]=='-' && z[1]=='-' ) z++; + if( cli_strcmp(z,"-init")==0 ){ + bIsInit = 1; + }else + if( cli_strcmp(z,"-v")==0 ){ + bVerbose++; + }else + { + utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n", + azArg[i], azArg[0]); + raw_printf(stderr, "Should be one of: --init -v\n"); + rc = 1; + goto meta_command_exit; + } + } + if( sqlite3_table_column_metadata(p->db,"main","selftest",0,0,0,0,0,0) + != SQLITE_OK ){ + bSelftestExists = 0; + }else{ + bSelftestExists = 1; + } + if( bIsInit ){ + createSelftestTable(p); + bSelftestExists = 1; + } + initText(&str); + appendText(&str, "x", 0); + for(k=bSelftestExists; k>=0; k--){ + if( k==1 ){ + rc = sqlite3_prepare_v2(p->db, + "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno", + -1, &pStmt, 0); + }else{ + rc = sqlite3_prepare_v2(p->db, + "VALUES(0,'memo','Missing SELFTEST table - default checks only','')," + " (1,'run','PRAGMA integrity_check','ok')", + -1, &pStmt, 0); + } + if( rc ){ + raw_printf(stderr, "Error querying the selftest table\n"); + rc = 1; + sqlite3_finalize(pStmt); + goto meta_command_exit; + } + for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){ + int tno = sqlite3_column_int(pStmt, 0); + const char *zOp = (const char*)sqlite3_column_text(pStmt, 1); + const char *zSql = (const char*)sqlite3_column_text(pStmt, 2); + const char *zAns = (const char*)sqlite3_column_text(pStmt, 3); + + if( zOp==0 ) continue; + if( zSql==0 ) continue; + if( zAns==0 ) continue; + k = 0; + if( bVerbose>0 ){ + printf("%d: %s %s\n", tno, zOp, zSql); + } + if( cli_strcmp(zOp,"memo")==0 ){ + utf8_printf(p->out, "%s\n", zSql); + }else + if( cli_strcmp(zOp,"run")==0 ){ + char *zErrMsg = 0; + str.n = 0; + str.z[0] = 0; + rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg); + nTest++; + if( bVerbose ){ + utf8_printf(p->out, "Result: %s\n", str.z); + } + if( rc || zErrMsg ){ + nErr++; + rc = 1; + utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg); + sqlite3_free(zErrMsg); + }else if( cli_strcmp(zAns,str.z)!=0 ){ + nErr++; + rc = 1; + utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns); + utf8_printf(p->out, "%d: Got: [%s]\n", tno, str.z); + } + }else + { + utf8_printf(stderr, + "Unknown operation \"%s\" on selftest line %d\n", zOp, tno); + rc = 1; + break; + } + } /* End loop over rows of content from SELFTEST */ + sqlite3_finalize(pStmt); + } /* End loop over k */ + freeText(&str); + utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest); + }else + + if( c=='s' && cli_strncmp(azArg[0], "separator", n)==0 ){ + if( nArg<2 || nArg>3 ){ + raw_printf(stderr, "Usage: .separator COL ?ROW?\n"); + rc = 1; + } + if( nArg>=2 ){ + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, + "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]); + } + if( nArg>=3 ){ + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, + "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]); + } + }else + + if( c=='s' && n>=4 && cli_strncmp(azArg[0],"sha3sum",n)==0 ){ + const char *zLike = 0; /* Which table to checksum. 0 means everything */ + int i; /* Loop counter */ + int bSchema = 0; /* Also hash the schema */ + int bSeparate = 0; /* Hash each table separately */ + int iSize = 224; /* Hash algorithm to use */ + int bDebug = 0; /* Only show the query that would have run */ + sqlite3_stmt *pStmt; /* For querying tables names */ + char *zSql; /* SQL to be run */ + char *zSep; /* Separator */ + ShellText sSql; /* Complete SQL for the query to run the hash */ + ShellText sQuery; /* Set of queries used to read all content */ + open_db(p, 0); + for(i=1; i<nArg; i++){ + const char *z = azArg[i]; + if( z[0]=='-' ){ + z++; + if( z[0]=='-' ) z++; + if( cli_strcmp(z,"schema")==0 ){ + bSchema = 1; + }else + if( cli_strcmp(z,"sha3-224")==0 || cli_strcmp(z,"sha3-256")==0 + || cli_strcmp(z,"sha3-384")==0 || cli_strcmp(z,"sha3-512")==0 + ){ + iSize = atoi(&z[5]); + }else + if( cli_strcmp(z,"debug")==0 ){ + bDebug = 1; + }else + { + utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n", + azArg[i], azArg[0]); + showHelp(p->out, azArg[0]); + rc = 1; + goto meta_command_exit; + } + }else if( zLike ){ + raw_printf(stderr, "Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n"); + rc = 1; + goto meta_command_exit; + }else{ + zLike = z; + bSeparate = 1; + if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1; + } + } + if( bSchema ){ + zSql = "SELECT lower(name) as tname FROM sqlite_schema" + " WHERE type='table' AND coalesce(rootpage,0)>1" + " UNION ALL SELECT 'sqlite_schema'" + " ORDER BY 1 collate nocase"; + }else{ + zSql = "SELECT lower(name) as tname FROM sqlite_schema" + " WHERE type='table' AND coalesce(rootpage,0)>1" + " AND name NOT LIKE 'sqlite_%'" + " ORDER BY 1 collate nocase"; + } + sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + initText(&sQuery); + initText(&sSql); + appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0); + zSep = "VALUES("; + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zTab = (const char*)sqlite3_column_text(pStmt,0); + if( zTab==0 ) continue; + if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue; + if( cli_strncmp(zTab, "sqlite_",7)!=0 ){ + appendText(&sQuery,"SELECT * FROM ", 0); + appendText(&sQuery,zTab,'"'); + appendText(&sQuery," NOT INDEXED;", 0); + }else if( cli_strcmp(zTab, "sqlite_schema")==0 ){ + appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema" + " ORDER BY name;", 0); + }else if( cli_strcmp(zTab, "sqlite_sequence")==0 ){ + appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence" + " ORDER BY name;", 0); + }else if( cli_strcmp(zTab, "sqlite_stat1")==0 ){ + appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1" + " ORDER BY tbl,idx;", 0); + }else if( cli_strcmp(zTab, "sqlite_stat4")==0 ){ + appendText(&sQuery, "SELECT * FROM ", 0); + appendText(&sQuery, zTab, 0); + appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0); + } + appendText(&sSql, zSep, 0); + appendText(&sSql, sQuery.z, '\''); + sQuery.n = 0; + appendText(&sSql, ",", 0); + appendText(&sSql, zTab, '\''); + zSep = "),("; + } + sqlite3_finalize(pStmt); + if( bSeparate ){ + zSql = sqlite3_mprintf( + "%s))" + " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label" + " FROM [sha3sum$query]", + sSql.z, iSize); + }else{ + zSql = sqlite3_mprintf( + "%s))" + " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash" + " FROM [sha3sum$query]", + sSql.z, iSize); + } + shell_check_oom(zSql); + freeText(&sQuery); + freeText(&sSql); + if( bDebug ){ + utf8_printf(p->out, "%s\n", zSql); + }else{ + shell_exec(p, zSql, 0); + } +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && !defined(SQLITE_OMIT_VIRTUALTABLE) + { + int lrc; + char *zRevText = /* Query for reversible to-blob-to-text check */ + "SELECT lower(name) as tname FROM sqlite_schema\n" + "WHERE type='table' AND coalesce(rootpage,0)>1\n" + "AND name NOT LIKE 'sqlite_%%'%s\n" + "ORDER BY 1 collate nocase"; + zRevText = sqlite3_mprintf(zRevText, zLike? " AND name LIKE $tspec" : ""); + zRevText = sqlite3_mprintf( + /* lower-case query is first run, producing upper-case query. */ + "with tabcols as materialized(\n" + "select tname, cname\n" + "from (" + " select printf('\"%%w\"',ss.tname) as tname," + " printf('\"%%w\"',ti.name) as cname\n" + " from (%z) ss\n inner join pragma_table_info(tname) ti))\n" + "select 'SELECT total(bad_text_count) AS bad_text_count\n" + "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n" + " from (select 'SELECT COUNT(*) AS bad_text_count\n" + "FROM '||tname||' WHERE '\n" + "||group_concat('CAST(CAST('||cname||' AS BLOB) AS TEXT)<>'||cname\n" + "|| ' AND typeof('||cname||')=''text'' ',\n" + "' OR ') as query, tname from tabcols group by tname)" + , zRevText); + shell_check_oom(zRevText); + if( bDebug ) utf8_printf(p->out, "%s\n", zRevText); + lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0); + if( lrc!=SQLITE_OK ){ + /* assert(lrc==SQLITE_NOMEM); // might also be SQLITE_ERROR if the + ** user does cruel and unnatural things like ".limit expr_depth 0". */ + rc = 1; + }else{ + if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC); + lrc = SQLITE_ROW==sqlite3_step(pStmt); + if( lrc ){ + const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0); + sqlite3_stmt *pCheckStmt; + lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0); + if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery); + if( lrc!=SQLITE_OK ){ + rc = 1; + }else{ + if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){ + double countIrreversible = sqlite3_column_double(pCheckStmt, 0); + if( countIrreversible>0 ){ + int sz = (int)(countIrreversible + 0.5); + utf8_printf(stderr, + "Digest includes %d invalidly encoded text field%s.\n", + sz, (sz>1)? "s": ""); + } + } + sqlite3_finalize(pCheckStmt); + } + sqlite3_finalize(pStmt); + } + } + if( rc ) utf8_printf(stderr, ".sha3sum failed.\n"); + sqlite3_free(zRevText); + } +#endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */ + sqlite3_free(zSql); + }else + +#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) + if( c=='s' + && (cli_strncmp(azArg[0], "shell", n)==0 + || cli_strncmp(azArg[0],"system",n)==0) + ){ + char *zCmd; + int i, x; + failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); + if( nArg<2 ){ + raw_printf(stderr, "Usage: .system COMMAND\n"); + rc = 1; + goto meta_command_exit; + } + zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]); + for(i=2; i<nArg && zCmd!=0; i++){ + zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"", + zCmd, azArg[i]); + } + x = zCmd!=0 ? system(zCmd) : 1; + sqlite3_free(zCmd); + if( x ) raw_printf(stderr, "System command returns %d\n", x); + }else +#endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */ + + if( c=='s' && cli_strncmp(azArg[0], "show", n)==0 ){ + static const char *azBool[] = { "off", "on", "trigger", "full"}; + const char *zOut; + int i; + if( nArg!=1 ){ + raw_printf(stderr, "Usage: .show\n"); + rc = 1; + goto meta_command_exit; + } + utf8_printf(p->out, "%12.12s: %s\n","echo", + azBool[ShellHasFlag(p, SHFLG_Echo)]); + utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]); + utf8_printf(p->out, "%12.12s: %s\n","explain", + p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off"); + utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]); + if( p->mode==MODE_Column + || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) + ){ + utf8_printf + (p->out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode", + modeDescr[p->mode], p->cmOpts.iWrap, + p->cmOpts.bWordWrap ? "on" : "off", + p->cmOpts.bQuote ? "" : "no"); + }else{ + utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]); + } + utf8_printf(p->out, "%12.12s: ", "nullvalue"); + output_c_string(p->out, p->nullValue); + raw_printf(p->out, "\n"); + utf8_printf(p->out,"%12.12s: %s\n","output", + strlen30(p->outfile) ? p->outfile : "stdout"); + utf8_printf(p->out,"%12.12s: ", "colseparator"); + output_c_string(p->out, p->colSeparator); + raw_printf(p->out, "\n"); + utf8_printf(p->out,"%12.12s: ", "rowseparator"); + output_c_string(p->out, p->rowSeparator); + raw_printf(p->out, "\n"); + switch( p->statsOn ){ + case 0: zOut = "off"; break; + default: zOut = "on"; break; + case 2: zOut = "stmt"; break; + case 3: zOut = "vmstep"; break; + } + utf8_printf(p->out, "%12.12s: %s\n","stats", zOut); + utf8_printf(p->out, "%12.12s: ", "width"); + for (i=0;i<p->nWidth;i++) { + raw_printf(p->out, "%d ", p->colWidth[i]); + } + raw_printf(p->out, "\n"); + utf8_printf(p->out, "%12.12s: %s\n", "filename", + p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); + }else + + if( c=='s' && cli_strncmp(azArg[0], "stats", n)==0 ){ + if( nArg==2 ){ + if( cli_strcmp(azArg[1],"stmt")==0 ){ + p->statsOn = 2; + }else if( cli_strcmp(azArg[1],"vmstep")==0 ){ + p->statsOn = 3; + }else{ + p->statsOn = (u8)booleanValue(azArg[1]); + } + }else if( nArg==1 ){ + display_stats(p->db, p, 0); + }else{ + raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n"); + rc = 1; + } + }else + + if( (c=='t' && n>1 && cli_strncmp(azArg[0], "tables", n)==0) + || (c=='i' && (cli_strncmp(azArg[0], "indices", n)==0 + || cli_strncmp(azArg[0], "indexes", n)==0) ) + ){ + sqlite3_stmt *pStmt; + char **azResult; + int nRow, nAlloc; + int ii; + ShellText s; + initText(&s); + open_db(p, 0); + rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); + if( rc ){ + sqlite3_finalize(pStmt); + return shellDatabaseError(p->db); + } + + if( nArg>2 && c=='i' ){ + /* It is an historical accident that the .indexes command shows an error + ** when called with the wrong number of arguments whereas the .tables + ** command does not. */ + raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); + rc = 1; + sqlite3_finalize(pStmt); + goto meta_command_exit; + } + for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){ + const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); + if( zDbName==0 ) continue; + if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0); + if( sqlite3_stricmp(zDbName, "main")==0 ){ + appendText(&s, "SELECT name FROM ", 0); + }else{ + appendText(&s, "SELECT ", 0); + appendText(&s, zDbName, '\''); + appendText(&s, "||'.'||name FROM ", 0); + } + appendText(&s, zDbName, '"'); + appendText(&s, ".sqlite_schema ", 0); + if( c=='t' ){ + appendText(&s," WHERE type IN ('table','view')" + " AND name NOT LIKE 'sqlite_%'" + " AND name LIKE ?1", 0); + }else{ + appendText(&s," WHERE type='index'" + " AND tbl_name LIKE ?1", 0); + } + } + rc = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ){ + appendText(&s, " ORDER BY 1", 0); + rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0); + } + freeText(&s); + if( rc ) return shellDatabaseError(p->db); + + /* Run the SQL statement prepared by the above block. Store the results + ** as an array of nul-terminated strings in azResult[]. */ + nRow = nAlloc = 0; + azResult = 0; + if( nArg>1 ){ + sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT); + }else{ + sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); + } + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + if( nRow>=nAlloc ){ + char **azNew; + int n2 = nAlloc*2 + 10; + azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); + shell_check_oom(azNew); + nAlloc = n2; + azResult = azNew; + } + azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + shell_check_oom(azResult[nRow]); + nRow++; + } + if( sqlite3_finalize(pStmt)!=SQLITE_OK ){ + rc = shellDatabaseError(p->db); + } + + /* Pretty-print the contents of array azResult[] to the output */ + if( rc==0 && nRow>0 ){ + int len, maxlen = 0; + int i, j; + int nPrintCol, nPrintRow; + for(i=0; i<nRow; i++){ + len = strlen30(azResult[i]); + if( len>maxlen ) maxlen = len; + } + nPrintCol = 80/(maxlen+2); + if( nPrintCol<1 ) nPrintCol = 1; + nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; + for(i=0; i<nPrintRow; i++){ + for(j=i; j<nRow; j+=nPrintRow){ + char *zSp = j<nPrintRow ? "" : " "; + utf8_printf(p->out, "%s%-*s", zSp, maxlen, + azResult[j] ? azResult[j]:""); + } + raw_printf(p->out, "\n"); + } + } + + for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]); + sqlite3_free(azResult); + }else + +#ifndef SQLITE_SHELL_FIDDLE + /* Begin redirecting output to the file "testcase-out.txt" */ + if( c=='t' && cli_strcmp(azArg[0],"testcase")==0 ){ + output_reset(p); + p->out = output_file_open("testcase-out.txt", 0); + if( p->out==0 ){ + raw_printf(stderr, "Error: cannot open 'testcase-out.txt'\n"); + } + if( nArg>=2 ){ + sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]); + }else{ + sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?"); + } + }else +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + +#ifndef SQLITE_UNTESTABLE + if( c=='t' && n>=8 && cli_strncmp(azArg[0], "testctrl", n)==0 ){ + static const struct { + const char *zCtrlName; /* Name of a test-control option */ + int ctrlCode; /* Integer code for that option */ + int unSafe; /* Not valid unless --unsafe-testing */ + const char *zUsage; /* Usage notes */ + } aCtrl[] = { + {"always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" }, + {"assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" }, + /*{"benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/ + /*{"bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/ + {"byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" }, + {"extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" }, + /*{"fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/ + {"imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"}, + {"internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" }, + {"localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" }, + {"never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" }, + {"optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" }, +#ifdef YYCOVERAGE + {"parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" }, +#endif + {"pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " }, + {"prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" }, + {"prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" }, + {"prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" }, + {"seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" }, + {"sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, + {"tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, + {"uselongdouble", SQLITE_TESTCTRL_USELONGDOUBLE,0,"?BOOLEAN|\"default\"?"}, + }; + int testctrl = -1; + int iCtrl = -1; + int rc2 = 0; /* 0: usage. 1: %d 2: %x 3: no-output */ + int isOk = 0; + int i, n2; + const char *zCmd = 0; + + open_db(p, 0); + zCmd = nArg>=2 ? azArg[1] : "help"; + + /* The argument can optionally begin with "-" or "--" */ + if( zCmd[0]=='-' && zCmd[1] ){ + zCmd++; + if( zCmd[0]=='-' && zCmd[1] ) zCmd++; + } + + /* --help lists all test-controls */ + if( cli_strcmp(zCmd,"help")==0 ){ + utf8_printf(p->out, "Available test-controls:\n"); + for(i=0; i<ArraySize(aCtrl); i++){ + if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue; + utf8_printf(p->out, " .testctrl %s %s\n", + aCtrl[i].zCtrlName, aCtrl[i].zUsage); + } + rc = 1; + goto meta_command_exit; + } + + /* convert testctrl text option to value. allow any unique prefix + ** of the option name, or a numerical value. */ + n2 = strlen30(zCmd); + for(i=0; i<ArraySize(aCtrl); i++){ + if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue; + if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){ + if( testctrl<0 ){ + testctrl = aCtrl[i].ctrlCode; + iCtrl = i; + }else{ + utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n" + "Use \".testctrl --help\" for help\n", zCmd); + rc = 1; + goto meta_command_exit; + } + } + } + if( testctrl<0 ){ + utf8_printf(stderr,"Error: unknown test-control: %s\n" + "Use \".testctrl --help\" for help\n", zCmd); + }else{ + switch(testctrl){ + + /* sqlite3_test_control(int, db, int) */ + case SQLITE_TESTCTRL_OPTIMIZATIONS: + if( nArg==3 ){ + unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0); + rc2 = sqlite3_test_control(testctrl, p->db, opt); + isOk = 3; + } + break; + + /* sqlite3_test_control(int) */ + case SQLITE_TESTCTRL_PRNG_SAVE: + case SQLITE_TESTCTRL_PRNG_RESTORE: + case SQLITE_TESTCTRL_BYTEORDER: + if( nArg==2 ){ + rc2 = sqlite3_test_control(testctrl); + isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3; + } + break; + + /* sqlite3_test_control(int, uint) */ + case SQLITE_TESTCTRL_PENDING_BYTE: + if( nArg==3 ){ + unsigned int opt = (unsigned int)integerValue(azArg[2]); + rc2 = sqlite3_test_control(testctrl, opt); + isOk = 3; + } + break; + + /* sqlite3_test_control(int, int, sqlite3*) */ + case SQLITE_TESTCTRL_PRNG_SEED: + if( nArg==3 || nArg==4 ){ + int ii = (int)integerValue(azArg[2]); + sqlite3 *db; + if( ii==0 && cli_strcmp(azArg[2],"random")==0 ){ + sqlite3_randomness(sizeof(ii),&ii); + printf("-- random seed: %d\n", ii); + } + if( nArg==3 ){ + db = 0; + }else{ + db = p->db; + /* Make sure the schema has been loaded */ + sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0); + } + rc2 = sqlite3_test_control(testctrl, ii, db); + isOk = 3; + } + break; + + /* sqlite3_test_control(int, int) */ + case SQLITE_TESTCTRL_ASSERT: + case SQLITE_TESTCTRL_ALWAYS: + if( nArg==3 ){ + int opt = booleanValue(azArg[2]); + rc2 = sqlite3_test_control(testctrl, opt); + isOk = 1; + } + break; + + /* sqlite3_test_control(int, int) */ + case SQLITE_TESTCTRL_LOCALTIME_FAULT: + case SQLITE_TESTCTRL_NEVER_CORRUPT: + if( nArg==3 ){ + int opt = booleanValue(azArg[2]); + rc2 = sqlite3_test_control(testctrl, opt); + isOk = 3; + } + break; + + /* sqlite3_test_control(int, int) */ + case SQLITE_TESTCTRL_USELONGDOUBLE: { + int opt = -1; + if( nArg==3 ){ + if( cli_strcmp(azArg[2],"default")==0 ){ + opt = 2; + }else{ + opt = booleanValue(azArg[2]); + } + } + rc2 = sqlite3_test_control(testctrl, opt); + isOk = 1; + break; + } + + /* sqlite3_test_control(sqlite3*) */ + case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: + rc2 = sqlite3_test_control(testctrl, p->db); + isOk = 3; + break; + + case SQLITE_TESTCTRL_IMPOSTER: + if( nArg==5 ){ + rc2 = sqlite3_test_control(testctrl, p->db, + azArg[2], + integerValue(azArg[3]), + integerValue(azArg[4])); + isOk = 3; + } + break; + + case SQLITE_TESTCTRL_SEEK_COUNT: { + u64 x = 0; + rc2 = sqlite3_test_control(testctrl, p->db, &x); + utf8_printf(p->out, "%llu\n", x); + isOk = 3; + break; + } + +#ifdef YYCOVERAGE + case SQLITE_TESTCTRL_PARSER_COVERAGE: { + if( nArg==2 ){ + sqlite3_test_control(testctrl, p->out); + isOk = 3; + } + break; + } +#endif +#ifdef SQLITE_DEBUG + case SQLITE_TESTCTRL_TUNE: { + if( nArg==4 ){ + int id = (int)integerValue(azArg[2]); + int val = (int)integerValue(azArg[3]); + sqlite3_test_control(testctrl, id, &val); + isOk = 3; + }else if( nArg==3 ){ + int id = (int)integerValue(azArg[2]); + sqlite3_test_control(testctrl, -id, &rc2); + isOk = 1; + }else if( nArg==2 ){ + int id = 1; + while(1){ + int val = 0; + rc2 = sqlite3_test_control(testctrl, -id, &val); + if( rc2!=SQLITE_OK ) break; + if( id>1 ) utf8_printf(p->out, " "); + utf8_printf(p->out, "%d: %d", id, val); + id++; + } + if( id>1 ) utf8_printf(p->out, "\n"); + isOk = 3; + } + break; + } +#endif + case SQLITE_TESTCTRL_SORTER_MMAP: + if( nArg==3 ){ + int opt = (unsigned int)integerValue(azArg[2]); + rc2 = sqlite3_test_control(testctrl, p->db, opt); + isOk = 3; + } + break; + } + } + if( isOk==0 && iCtrl>=0 ){ + utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); + rc = 1; + }else if( isOk==1 ){ + raw_printf(p->out, "%d\n", rc2); + }else if( isOk==2 ){ + raw_printf(p->out, "0x%08x\n", rc2); + } + }else +#endif /* !defined(SQLITE_UNTESTABLE) */ + + if( c=='t' && n>4 && cli_strncmp(azArg[0], "timeout", n)==0 ){ + open_db(p, 0); + sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0); + }else + + if( c=='t' && n>=5 && cli_strncmp(azArg[0], "timer", n)==0 ){ + if( nArg==2 ){ + enableTimer = booleanValue(azArg[1]); + if( enableTimer && !HAS_TIMER ){ + raw_printf(stderr, "Error: timer not available on this system.\n"); + enableTimer = 0; + } + }else{ + raw_printf(stderr, "Usage: .timer on|off\n"); + rc = 1; + } + }else + +#ifndef SQLITE_OMIT_TRACE + if( c=='t' && cli_strncmp(azArg[0], "trace", n)==0 ){ + int mType = 0; + int jj; + open_db(p, 0); + for(jj=1; jj<nArg; jj++){ + const char *z = azArg[jj]; + if( z[0]=='-' ){ + if( optionMatch(z, "expanded") ){ + p->eTraceType = SHELL_TRACE_EXPANDED; + } +#ifdef SQLITE_ENABLE_NORMALIZE + else if( optionMatch(z, "normalized") ){ + p->eTraceType = SHELL_TRACE_NORMALIZED; + } +#endif + else if( optionMatch(z, "plain") ){ + p->eTraceType = SHELL_TRACE_PLAIN; + } + else if( optionMatch(z, "profile") ){ + mType |= SQLITE_TRACE_PROFILE; + } + else if( optionMatch(z, "row") ){ + mType |= SQLITE_TRACE_ROW; + } + else if( optionMatch(z, "stmt") ){ + mType |= SQLITE_TRACE_STMT; + } + else if( optionMatch(z, "close") ){ + mType |= SQLITE_TRACE_CLOSE; + } + else { + raw_printf(stderr, "Unknown option \"%s\" on \".trace\"\n", z); + rc = 1; + goto meta_command_exit; + } + }else{ + output_file_close(p->traceOut); + p->traceOut = output_file_open(z, 0); + } + } + if( p->traceOut==0 ){ + sqlite3_trace_v2(p->db, 0, 0, 0); + }else{ + if( mType==0 ) mType = SQLITE_TRACE_STMT; + sqlite3_trace_v2(p->db, mType, sql_trace_callback, p); + } + }else +#endif /* !defined(SQLITE_OMIT_TRACE) */ + +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE) + if( c=='u' && cli_strncmp(azArg[0], "unmodule", n)==0 ){ + int ii; + int lenOpt; + char *zOpt; + if( nArg<2 ){ + raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n"); + rc = 1; + goto meta_command_exit; + } + open_db(p, 0); + zOpt = azArg[1]; + if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++; + lenOpt = (int)strlen(zOpt); + if( lenOpt>=3 && cli_strncmp(zOpt, "-allexcept",lenOpt)==0 ){ + assert( azArg[nArg]==0 ); + sqlite3_drop_modules(p->db, nArg>2 ? (const char**)(azArg+2) : 0); + }else{ + for(ii=1; ii<nArg; ii++){ + sqlite3_create_module(p->db, azArg[ii], 0, 0); + } + } + }else +#endif + +#if SQLITE_USER_AUTHENTICATION + if( c=='u' && cli_strncmp(azArg[0], "user", n)==0 ){ + if( nArg<2 ){ + raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n"); + rc = 1; + goto meta_command_exit; + } + open_db(p, 0); + if( cli_strcmp(azArg[1],"login")==0 ){ + if( nArg!=4 ){ + raw_printf(stderr, "Usage: .user login USER PASSWORD\n"); + rc = 1; + goto meta_command_exit; + } + rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], + strlen30(azArg[3])); + if( rc ){ + utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]); + rc = 1; + } + }else if( cli_strcmp(azArg[1],"add")==0 ){ + if( nArg!=5 ){ + raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n"); + rc = 1; + goto meta_command_exit; + } + rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]), + booleanValue(azArg[4])); + if( rc ){ + raw_printf(stderr, "User-Add failed: %d\n", rc); + rc = 1; + } + }else if( cli_strcmp(azArg[1],"edit")==0 ){ + if( nArg!=5 ){ + raw_printf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n"); + rc = 1; + goto meta_command_exit; + } + rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]), + booleanValue(azArg[4])); + if( rc ){ + raw_printf(stderr, "User-Edit failed: %d\n", rc); + rc = 1; + } + }else if( cli_strcmp(azArg[1],"delete")==0 ){ + if( nArg!=3 ){ + raw_printf(stderr, "Usage: .user delete USER\n"); + rc = 1; + goto meta_command_exit; + } + rc = sqlite3_user_delete(p->db, azArg[2]); + if( rc ){ + raw_printf(stderr, "User-Delete failed: %d\n", rc); + rc = 1; + } + }else{ + raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n"); + rc = 1; + goto meta_command_exit; + } + }else +#endif /* SQLITE_USER_AUTHENTICATION */ + + if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ + char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; + utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, + sqlite3_libversion(), sqlite3_sourceid()); +#if SQLITE_HAVE_ZLIB + utf8_printf(p->out, "zlib version %s\n", zlibVersion()); +#endif +#define CTIMEOPT_VAL_(opt) #opt +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) +#if defined(__clang__) && defined(__clang_major__) + utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." + CTIMEOPT_VAL(__clang_minor__) "." + CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz); +#elif defined(_MSC_VER) + utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); +#elif defined(__GNUC__) && defined(__VERSION__) + utf8_printf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz); +#endif + }else + + if( c=='v' && cli_strncmp(azArg[0], "vfsinfo", n)==0 ){ + const char *zDbName = nArg==2 ? azArg[1] : "main"; + sqlite3_vfs *pVfs = 0; + if( p->db ){ + sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs); + if( pVfs ){ + utf8_printf(p->out, "vfs.zName = \"%s\"\n", pVfs->zName); + raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); + raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); + raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + } + } + }else + + if( c=='v' && cli_strncmp(azArg[0], "vfslist", n)==0 ){ + sqlite3_vfs *pVfs; + sqlite3_vfs *pCurrent = 0; + if( p->db ){ + sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent); + } + for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){ + utf8_printf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName, + pVfs==pCurrent ? " <--- CURRENT" : ""); + raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); + raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); + raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + if( pVfs->pNext ){ + raw_printf(p->out, "-----------------------------------\n"); + } + } + }else + + if( c=='v' && cli_strncmp(azArg[0], "vfsname", n)==0 ){ + const char *zDbName = nArg==2 ? azArg[1] : "main"; + char *zVfsName = 0; + if( p->db ){ + sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName); + if( zVfsName ){ + utf8_printf(p->out, "%s\n", zVfsName); + sqlite3_free(zVfsName); + } + } + }else + + if( c=='w' && cli_strncmp(azArg[0], "wheretrace", n)==0 ){ + unsigned int x = nArg>=2? (unsigned int)integerValue(azArg[1]) : 0xffffffff; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x); + }else + + if( c=='w' && cli_strncmp(azArg[0], "width", n)==0 ){ + int j; + assert( nArg<=ArraySize(azArg) ); + p->nWidth = nArg-1; + p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2); + if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory(); + if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth]; + for(j=1; j<nArg; j++){ + p->colWidth[j-1] = (int)integerValue(azArg[j]); + } + }else + + { + utf8_printf(stderr, "Error: unknown command or invalid arguments: " + " \"%s\". Enter \".help\" for help\n", azArg[0]); + rc = 1; + } + +meta_command_exit: + if( p->outCount ){ + p->outCount--; + if( p->outCount==0 ) output_reset(p); + } + p->bSafeMode = p->bSafeModePersist; + return rc; +} + +/* Line scan result and intermediate states (supporting scan resumption) +*/ +#ifndef CHAR_BIT +# define CHAR_BIT 8 +#endif +typedef enum { + QSS_HasDark = 1<<CHAR_BIT, QSS_EndingSemi = 2<<CHAR_BIT, + QSS_CharMask = (1<<CHAR_BIT)-1, QSS_ScanMask = 3<<CHAR_BIT, + QSS_Start = 0 +} QuickScanState; +#define QSS_SETV(qss, newst) ((newst) | ((qss) & QSS_ScanMask)) +#define QSS_INPLAIN(qss) (((qss)&QSS_CharMask)==QSS_Start) +#define QSS_PLAINWHITE(qss) (((qss)&~QSS_EndingSemi)==QSS_Start) +#define QSS_PLAINDARK(qss) (((qss)&~QSS_EndingSemi)==QSS_HasDark) +#define QSS_SEMITERM(qss) (((qss)&~QSS_HasDark)==QSS_EndingSemi) + +/* +** Scan line for classification to guide shell's handling. +** The scan is resumable for subsequent lines when prior +** return values are passed as the 2nd argument. +*/ +static QuickScanState quickscan(char *zLine, QuickScanState qss, + SCAN_TRACKER_REFTYPE pst){ + char cin; + char cWait = (char)qss; /* intentional narrowing loss */ + if( cWait==0 ){ + PlainScan: + assert( cWait==0 ); + while( (cin = *zLine++)!=0 ){ + if( IsSpace(cin) ) + continue; + switch (cin){ + case '-': + if( *zLine!='-' ) + break; + while((cin = *++zLine)!=0 ) + if( cin=='\n') + goto PlainScan; + return qss; + case ';': + qss |= QSS_EndingSemi; + continue; + case '/': + if( *zLine=='*' ){ + ++zLine; + cWait = '*'; + CONTINUE_PROMPT_AWAITS(pst, "/*"); + qss = QSS_SETV(qss, cWait); + goto TermScan; + } + break; + case '[': + cin = ']'; + deliberate_fall_through; + case '`': case '\'': case '"': + cWait = cin; + qss = QSS_HasDark | cWait; + CONTINUE_PROMPT_AWAITC(pst, cin); + goto TermScan; + case '(': + CONTINUE_PAREN_INCR(pst, 1); + break; + case ')': + CONTINUE_PAREN_INCR(pst, -1); + break; + default: + break; + } + qss = (qss & ~QSS_EndingSemi) | QSS_HasDark; + } + }else{ + TermScan: + while( (cin = *zLine++)!=0 ){ + if( cin==cWait ){ + switch( cWait ){ + case '*': + if( *zLine != '/' ) + continue; + ++zLine; + cWait = 0; + CONTINUE_PROMPT_AWAITC(pst, 0); + qss = QSS_SETV(qss, 0); + goto PlainScan; + case '`': case '\'': case '"': + if(*zLine==cWait){ + /* Swallow doubled end-delimiter.*/ + ++zLine; + continue; + } + deliberate_fall_through; + case ']': + cWait = 0; + CONTINUE_PROMPT_AWAITC(pst, 0); + qss = QSS_SETV(qss, 0); + goto PlainScan; + default: assert(0); + } + } + } + } + return qss; +} + +/* +** Return TRUE if the line typed in is an SQL command terminator other +** than a semi-colon. The SQL Server style "go" command is understood +** as is the Oracle "/". +*/ +static int line_is_command_terminator(char *zLine){ + while( IsSpace(zLine[0]) ){ zLine++; }; + if( zLine[0]=='/' ) + zLine += 1; /* Oracle */ + else if ( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' ) + zLine += 2; /* SQL Server */ + else + return 0; + return quickscan(zLine, QSS_Start, 0)==QSS_Start; +} + +/* +** The CLI needs a working sqlite3_complete() to work properly. So error +** out of the build if compiling with SQLITE_OMIT_COMPLETE. +*/ +#ifdef SQLITE_OMIT_COMPLETE +# error the CLI application is imcompatable with SQLITE_OMIT_COMPLETE. +#endif + +/* +** Return true if zSql is a complete SQL statement. Return false if it +** ends in the middle of a string literal or C-style comment. +*/ +static int line_is_complete(char *zSql, int nSql){ + int rc; + if( zSql==0 ) return 1; + zSql[nSql] = ';'; + zSql[nSql+1] = 0; + rc = sqlite3_complete(zSql); + zSql[nSql] = 0; + return rc; +} + +/* +** Run a single line of SQL. Return the number of errors. +*/ +static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ + int rc; + char *zErrMsg = 0; + + open_db(p, 0); + if( ShellHasFlag(p,SHFLG_Backslash) ) resolve_backslashes(zSql); + if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0; + BEGIN_TIMER; + rc = shell_exec(p, zSql, &zErrMsg); + END_TIMER; + if( rc || zErrMsg ){ + char zPrefix[100]; + const char *zErrorTail; + const char *zErrorType; + if( zErrMsg==0 ){ + zErrorType = "Error"; + zErrorTail = sqlite3_errmsg(p->db); + }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){ + zErrorType = "Parse error"; + zErrorTail = &zErrMsg[12]; + }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){ + zErrorType = "Runtime error"; + zErrorTail = &zErrMsg[10]; + }else{ + zErrorType = "Error"; + zErrorTail = zErrMsg; + } + if( in!=0 || !stdin_is_interactive ){ + sqlite3_snprintf(sizeof(zPrefix), zPrefix, + "%s near line %d:", zErrorType, startline); + }else{ + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); + } + utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail); + sqlite3_free(zErrMsg); + zErrMsg = 0; + return 1; + }else if( ShellHasFlag(p, SHFLG_CountChanges) ){ + char zLineBuf[2000]; + sqlite3_snprintf(sizeof(zLineBuf), zLineBuf, + "changes: %lld total_changes: %lld", + sqlite3_changes64(p->db), sqlite3_total_changes64(p->db)); + raw_printf(p->out, "%s\n", zLineBuf); + } + return 0; +} + +static void echo_group_input(ShellState *p, const char *zDo){ + if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo); +} + +#ifdef SQLITE_SHELL_FIDDLE +/* +** Alternate one_input_line() impl for wasm mode. This is not in the primary +** impl because we need the global shellState and cannot access it from that +** function without moving lots of code around (creating a larger/messier diff). +*/ +static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ + /* Parse the next line from shellState.wasm.zInput. */ + const char *zBegin = shellState.wasm.zPos; + const char *z = zBegin; + char *zLine = 0; + i64 nZ = 0; + + UNUSED_PARAMETER(in); + UNUSED_PARAMETER(isContinuation); + if(!z || !*z){ + return 0; + } + while(*z && isspace(*z)) ++z; + zBegin = z; + for(; *z && '\n'!=*z; ++nZ, ++z){} + if(nZ>0 && '\r'==zBegin[nZ-1]){ + --nZ; + } + shellState.wasm.zPos = z; + zLine = realloc(zPrior, nZ+1); + shell_check_oom(zLine); + memcpy(zLine, zBegin, nZ); + zLine[nZ] = 0; + return zLine; +} +#endif /* SQLITE_SHELL_FIDDLE */ + +/* +** Read input from *in and process it. If *in==0 then input +** is interactive - the user is typing it it. Otherwise, input +** is coming from a file or device. A prompt is issued and history +** is saved only if input is interactive. An interrupt signal will +** cause this routine to exit immediately, unless input is interactive. +** +** Return the number of errors. +*/ +static int process_input(ShellState *p){ + char *zLine = 0; /* A single input line */ + char *zSql = 0; /* Accumulated SQL text */ + i64 nLine; /* Length of current line */ + i64 nSql = 0; /* Bytes of zSql[] used */ + i64 nAlloc = 0; /* Allocated zSql[] space */ + int rc; /* Error code */ + int errCnt = 0; /* Number of errors seen */ + i64 startline = 0; /* Line number for start of current input */ + QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */ + + if( p->inputNesting==MAX_INPUT_NESTING ){ + /* This will be more informative in a later version. */ + utf8_printf(stderr,"Input nesting limit (%d) reached at line %d." + " Check recursion.\n", MAX_INPUT_NESTING, p->lineno); + return 1; + } + ++p->inputNesting; + p->lineno = 0; + CONTINUE_PROMPT_RESET; + while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ + fflush(p->out); + zLine = one_input_line(p->in, zLine, nSql>0); + if( zLine==0 ){ + /* End of input */ + if( p->in==0 && stdin_is_interactive ) printf("\n"); + break; + } + if( seenInterrupt ){ + if( p->in!=0 ) break; + seenInterrupt = 0; + } + p->lineno++; + if( QSS_INPLAIN(qss) + && line_is_command_terminator(zLine) + && line_is_complete(zSql, nSql) ){ + memcpy(zLine,";",2); + } + qss = quickscan(zLine, qss, CONTINUE_PROMPT_PSTATE); + if( QSS_PLAINWHITE(qss) && nSql==0 ){ + /* Just swallow single-line whitespace */ + echo_group_input(p, zLine); + qss = QSS_Start; + continue; + } + if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ + CONTINUE_PROMPT_RESET; + echo_group_input(p, zLine); + if( zLine[0]=='.' ){ + rc = do_meta_command(zLine, p); + if( rc==2 ){ /* exit requested */ + break; + }else if( rc ){ + errCnt++; + } + } + qss = QSS_Start; + continue; + } + /* No single-line dispositions remain; accumulate line(s). */ + nLine = strlen(zLine); + if( nSql+nLine+2>=nAlloc ){ + /* Grow buffer by half-again increments when big. */ + nAlloc = nSql+(nSql>>1)+nLine+100; + zSql = realloc(zSql, nAlloc); + shell_check_oom(zSql); + } + if( nSql==0 ){ + i64 i; + for(i=0; zLine[i] && IsSpace(zLine[i]); i++){} + assert( nAlloc>0 && zSql!=0 ); + memcpy(zSql, zLine+i, nLine+1-i); + startline = p->lineno; + nSql = nLine-i; + }else{ + zSql[nSql++] = '\n'; + memcpy(zSql+nSql, zLine, nLine+1); + nSql += nLine; + } + if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){ + echo_group_input(p, zSql); + errCnt += runOneSqlLine(p, zSql, p->in, startline); + CONTINUE_PROMPT_RESET; + nSql = 0; + if( p->outCount ){ + output_reset(p); + p->outCount = 0; + }else{ + clearTempFile(p); + } + p->bSafeMode = p->bSafeModePersist; + qss = QSS_Start; + }else if( nSql && QSS_PLAINWHITE(qss) ){ + echo_group_input(p, zSql); + nSql = 0; + qss = QSS_Start; + } + } + if( nSql ){ + /* This may be incomplete. Let the SQL parser deal with that. */ + echo_group_input(p, zSql); + errCnt += runOneSqlLine(p, zSql, p->in, startline); + CONTINUE_PROMPT_RESET; + } + free(zSql); + free(zLine); + --p->inputNesting; + return errCnt>0; +} + +/* +** Return a pathname which is the user's home directory. A +** 0 return indicates an error of some kind. +*/ +static char *find_home_dir(int clearFlag){ + static char *home_dir = NULL; + if( clearFlag ){ + free(home_dir); + home_dir = 0; + return 0; + } + if( home_dir ) return home_dir; + +#if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) \ + && !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI) + { + struct passwd *pwent; + uid_t uid = getuid(); + if( (pwent=getpwuid(uid)) != NULL) { + home_dir = pwent->pw_dir; + } + } +#endif + +#if defined(_WIN32_WCE) + /* Windows CE (arm-wince-mingw32ce-gcc) does not provide getenv() + */ + home_dir = "/"; +#else + +#if defined(_WIN32) || defined(WIN32) + if (!home_dir) { + home_dir = getenv("USERPROFILE"); + } +#endif + + if (!home_dir) { + home_dir = getenv("HOME"); + } + +#if defined(_WIN32) || defined(WIN32) + if (!home_dir) { + char *zDrive, *zPath; + int n; + zDrive = getenv("HOMEDRIVE"); + zPath = getenv("HOMEPATH"); + if( zDrive && zPath ){ + n = strlen30(zDrive) + strlen30(zPath) + 1; + home_dir = malloc( n ); + if( home_dir==0 ) return 0; + sqlite3_snprintf(n, home_dir, "%s%s", zDrive, zPath); + return home_dir; + } + home_dir = "c:\\"; + } +#endif + +#endif /* !_WIN32_WCE */ + + if( home_dir ){ + i64 n = strlen(home_dir) + 1; + char *z = malloc( n ); + if( z ) memcpy(z, home_dir, n); + home_dir = z; + } + + return home_dir; +} + +/* +** On non-Windows platforms, look for $XDG_CONFIG_HOME. +** If ${XDG_CONFIG_HOME}/sqlite3/sqliterc is found, return +** the path to it, else return 0. The result is cached for +** subsequent calls. +*/ +static const char *find_xdg_config(void){ +#if defined(_WIN32) || defined(WIN32) || defined(_WIN32_WCE) \ + || defined(__RTP__) || defined(_WRS_KERNEL) + return 0; +#else + static int alreadyTried = 0; + static char *zConfig = 0; + const char *zXdgHome; + + if( alreadyTried!=0 ){ + return zConfig; + } + alreadyTried = 1; + zXdgHome = getenv("XDG_CONFIG_HOME"); + if( zXdgHome==0 ){ + return 0; + } + zConfig = sqlite3_mprintf("%s/sqlite3/sqliterc", zXdgHome); + shell_check_oom(zConfig); + if( access(zConfig,0)!=0 ){ + sqlite3_free(zConfig); + zConfig = 0; + } + return zConfig; +#endif +} + +/* +** Read input from the file given by sqliterc_override. Or if that +** parameter is NULL, take input from the first of find_xdg_config() +** or ~/.sqliterc which is found. +** +** Returns the number of errors. +*/ +static void process_sqliterc( + ShellState *p, /* Configuration data */ + const char *sqliterc_override /* Name of config file. NULL to use default */ +){ + char *home_dir = NULL; + const char *sqliterc = sqliterc_override; + char *zBuf = 0; + FILE *inSaved = p->in; + int savedLineno = p->lineno; + + if( sqliterc == NULL ){ + sqliterc = find_xdg_config(); + } + if( sqliterc == NULL ){ + home_dir = find_home_dir(0); + if( home_dir==0 ){ + raw_printf(stderr, "-- warning: cannot find home directory;" + " cannot read ~/.sqliterc\n"); + return; + } + zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); + shell_check_oom(zBuf); + sqliterc = zBuf; + } + p->in = fopen(sqliterc,"rb"); + if( p->in ){ + if( stdin_is_interactive ){ + utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc); + } + if( process_input(p) && bail_on_error ) exit(1); + fclose(p->in); + }else if( sqliterc_override!=0 ){ + utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc); + if( bail_on_error ) exit(1); + } + p->in = inSaved; + p->lineno = savedLineno; + sqlite3_free(zBuf); +} + +/* +** Show available command line options +*/ +static const char zOptions[] = + " -- treat no subsequent arguments as options\n" +#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) + " -A ARGS... run \".archive ARGS\" and exit\n" +#endif + " -append append the database to the end of the file\n" + " -ascii set output mode to 'ascii'\n" + " -bail stop after hitting an error\n" + " -batch force batch I/O\n" + " -box set output mode to 'box'\n" + " -column set output mode to 'column'\n" + " -cmd COMMAND run \"COMMAND\" before reading stdin\n" + " -csv set output mode to 'csv'\n" +#if !defined(SQLITE_OMIT_DESERIALIZE) + " -deserialize open the database using sqlite3_deserialize()\n" +#endif + " -echo print inputs before execution\n" + " -init FILENAME read/process named file\n" + " -[no]header turn headers on or off\n" +#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) + " -heap SIZE Size of heap for memsys3 or memsys5\n" +#endif + " -help show this message\n" + " -html set output mode to HTML\n" + " -interactive force interactive I/O\n" + " -json set output mode to 'json'\n" + " -line set output mode to 'line'\n" + " -list set output mode to 'list'\n" + " -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n" + " -markdown set output mode to 'markdown'\n" +#if !defined(SQLITE_OMIT_DESERIALIZE) + " -maxsize N maximum size for a --deserialize database\n" +#endif + " -memtrace trace all memory allocations and deallocations\n" + " -mmap N default mmap size set to N\n" +#ifdef SQLITE_ENABLE_MULTIPLEX + " -multiplex enable the multiplexor VFS\n" +#endif + " -newline SEP set output row separator. Default: '\\n'\n" + " -nofollow refuse to open symbolic links to database files\n" + " -nonce STRING set the safe-mode escape nonce\n" + " -nullvalue TEXT set text string for NULL values. Default ''\n" + " -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n" + " -pcachetrace trace all page cache operations\n" + " -quote set output mode to 'quote'\n" + " -readonly open the database read-only\n" + " -safe enable safe-mode\n" + " -separator SEP set output column separator. Default: '|'\n" +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + " -sorterref SIZE sorter references threshold size\n" +#endif + " -stats print memory stats before each finalize\n" + " -table set output mode to 'table'\n" + " -tabs set output mode to 'tabs'\n" + " -unsafe-testing allow unsafe commands and modes for testing\n" +#if SHELL_WIN_UTF8_OPT + " -utf8 setup interactive console code page for UTF-8\n" +#endif + " -version show SQLite version\n" + " -vfs NAME use NAME as the default VFS\n" +#ifdef SQLITE_ENABLE_VFSTRACE + " -vfstrace enable tracing of all VFS calls\n" +#endif +#ifdef SQLITE_HAVE_ZLIB + " -zip open the file as a ZIP Archive\n" +#endif +; +static void usage(int showDetail){ + utf8_printf(stderr, + "Usage: %s [OPTIONS] [FILENAME [SQL]]\n" + "FILENAME is the name of an SQLite database. A new database is created\n" + "if the file does not previously exist. Defaults to :memory:.\n", Argv0); + if( showDetail ){ + utf8_printf(stderr, "OPTIONS include:\n%s", zOptions); + }else{ + raw_printf(stderr, "Use the -help option for additional information\n"); + } + exit(1); +} + +/* +** Internal check: Verify that the SQLite is uninitialized. Print a +** error message if it is initialized. +*/ +static void verify_uninitialized(void){ + if( sqlite3_config(-1)==SQLITE_MISUSE ){ + utf8_printf(stdout, "WARNING: attempt to configure SQLite after" + " initialization.\n"); + } +} + +/* +** Initialize the state information in data +*/ +static void main_init(ShellState *data) { + memset(data, 0, sizeof(*data)); + data->normalMode = data->cMode = data->mode = MODE_List; + data->autoExplain = 1; + data->pAuxDb = &data->aAuxDb[0]; + memcpy(data->colSeparator,SEP_Column, 2); + memcpy(data->rowSeparator,SEP_Row, 2); + data->showHeader = 0; + data->shellFlgs = SHFLG_Lookaside; + sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data); +#if !defined(SQLITE_SHELL_FIDDLE) + verify_uninitialized(); +#endif + sqlite3_config(SQLITE_CONFIG_URI, 1); + sqlite3_config(SQLITE_CONFIG_MULTITHREAD); + sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> "); + sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> "); +} + +/* +** Output text to the console in a font that attracts extra attention. +*/ +#ifdef _WIN32 +static void printBold(const char *zText){ +#if !SQLITE_OS_WINRT + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO defaultScreenInfo; + GetConsoleScreenBufferInfo(out, &defaultScreenInfo); + SetConsoleTextAttribute(out, + FOREGROUND_RED|FOREGROUND_INTENSITY + ); +#endif + printf("%s", zText); +#if !SQLITE_OS_WINRT + SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); +#endif +} +#else +static void printBold(const char *zText){ + printf("\033[1m%s\033[0m", zText); +} +#endif + +/* +** Get the argument to an --option. Throw an error and die if no argument +** is available. +*/ +static char *cmdline_option_value(int argc, char **argv, int i){ + if( i==argc ){ + utf8_printf(stderr, "%s: Error: missing argument to %s\n", + argv[0], argv[argc-1]); + exit(1); + } + return argv[i]; +} + +static void sayAbnormalExit(void){ + if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n"); +} + +#ifndef SQLITE_SHELL_IS_UTF8 +# if (defined(_WIN32) || defined(WIN32)) \ + && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__))) +# define SQLITE_SHELL_IS_UTF8 (0) +# else +# define SQLITE_SHELL_IS_UTF8 (1) +# endif +#endif + +#ifdef SQLITE_SHELL_FIDDLE +# define main fiddle_main +#endif + +#if SQLITE_SHELL_IS_UTF8 +int SQLITE_CDECL main(int argc, char **argv){ +#else +int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ + char **argv; +#endif +#ifdef SQLITE_DEBUG + sqlite3_int64 mem_main_enter = 0; +#endif + char *zErrMsg = 0; +#ifdef SQLITE_SHELL_FIDDLE +# define data shellState +#else + ShellState data; +#endif + const char *zInitFile = 0; + int i; + int rc = 0; + int warnInmemoryDb = 0; + int readStdin = 1; + int nCmd = 0; + int nOptsEnd = argc; + char **azCmd = 0; + const char *zVfs = 0; /* Value of -vfs command-line option */ +#if !SQLITE_SHELL_IS_UTF8 + char **argvToFree = 0; + int argcToFree = 0; +#endif + setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ + +#ifdef SQLITE_SHELL_FIDDLE + stdin_is_interactive = 0; + stdout_is_console = 1; + data.wasm.zDefaultDbName = "/fiddle.sqlite3"; +#else + stdin_is_interactive = isatty(0); + stdout_is_console = isatty(1); +#endif +#if SHELL_WIN_UTF8_OPT + atexit(console_restore); /* Needs revision for CLI as library call */ +#endif + atexit(sayAbnormalExit); +#ifdef SQLITE_DEBUG + mem_main_enter = sqlite3_memory_used(); +#endif +#if !defined(_WIN32_WCE) + if( getenv("SQLITE_DEBUG_BREAK") ){ + if( isatty(0) && isatty(2) ){ + fprintf(stderr, + "attach debugger to process %d and press any key to continue.\n", + GETPID()); + fgetc(stdin); + }else{ +#if defined(_WIN32) || defined(WIN32) +#if SQLITE_OS_WINRT + __debugbreak(); +#else + DebugBreak(); +#endif +#elif defined(SIGTRAP) + raise(SIGTRAP); +#endif + } + } +#endif + /* Register a valid signal handler early, before much else is done. */ +#ifdef SIGINT + signal(SIGINT, interrupt_handler); +#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) + if( !SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE) ){ + fprintf(stderr, "No ^C handler.\n"); + } +#endif + +#if USE_SYSTEM_SQLITE+0!=1 + if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){ + utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", + sqlite3_sourceid(), SQLITE_SOURCE_ID); + exit(1); + } +#endif + main_init(&data); + + /* On Windows, we must translate command-line arguments into UTF-8. + ** The SQLite memory allocator subsystem has to be enabled in order to + ** do this. But we want to run an sqlite3_shutdown() afterwards so that + ** subsequent sqlite3_config() calls will work. So copy all results into + ** memory that does not come from the SQLite memory allocator. + */ +#if !SQLITE_SHELL_IS_UTF8 + sqlite3_initialize(); + argvToFree = malloc(sizeof(argv[0])*argc*2); + shell_check_oom(argvToFree); + argcToFree = argc; + argv = argvToFree + argc; + for(i=0; i<argc; i++){ + char *z = sqlite3_win32_unicode_to_utf8(wargv[i]); + i64 n; + shell_check_oom(z); + n = strlen(z); + argv[i] = malloc( n+1 ); + shell_check_oom(argv[i]); + memcpy(argv[i], z, n+1); + argvToFree[i] = argv[i]; + sqlite3_free(z); + } + sqlite3_shutdown(); +#endif + + assert( argc>=1 && argv && argv[0] ); + Argv0 = argv[0]; + +#ifdef SQLITE_SHELL_DBNAME_PROC + { + /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name + ** of a C-function that will provide the name of the database file. Use + ** this compile-time option to embed this shell program in larger + ** applications. */ + extern void SQLITE_SHELL_DBNAME_PROC(const char**); + SQLITE_SHELL_DBNAME_PROC(&data.pAuxDb->zDbFilename); + warnInmemoryDb = 0; + } +#endif + + /* Do an initial pass through the command-line argument to locate + ** the name of the database file, the name of the initialization file, + ** the size of the alternative malloc heap, + ** and the first command to execute. + */ +#ifndef SQLITE_SHELL_FIDDLE + verify_uninitialized(); +#endif + for(i=1; i<argc; i++){ + char *z; + z = argv[i]; + if( z[0]!='-' || i>nOptsEnd ){ + if( data.aAuxDb->zDbFilename==0 ){ + data.aAuxDb->zDbFilename = z; + }else{ + /* Excess arguments are interpreted as SQL (or dot-commands) and + ** mean that nothing is read from stdin */ + readStdin = 0; + nCmd++; + azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd); + shell_check_oom(azCmd); + azCmd[nCmd-1] = z; + } + continue; + } + if( z[1]=='-' ) z++; + if( cli_strcmp(z, "-")==0 ){ + nOptsEnd = i; + continue; + }else if( cli_strcmp(z,"-separator")==0 + || cli_strcmp(z,"-nullvalue")==0 + || cli_strcmp(z,"-newline")==0 + || cli_strcmp(z,"-cmd")==0 + ){ + (void)cmdline_option_value(argc, argv, ++i); + }else if( cli_strcmp(z,"-init")==0 ){ + zInitFile = cmdline_option_value(argc, argv, ++i); + }else if( cli_strcmp(z,"-batch")==0 ){ + /* Need to check for batch mode here to so we can avoid printing + ** informational messages (like from process_sqliterc) before + ** we do the actual processing of arguments later in a second pass. + */ + stdin_is_interactive = 0; + }else if( cli_strcmp(z,"-heap")==0 ){ +#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) + const char *zSize; + sqlite3_int64 szHeap; + + zSize = cmdline_option_value(argc, argv, ++i); + szHeap = integerValue(zSize); + if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000; + verify_uninitialized(); + sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); +#else + (void)cmdline_option_value(argc, argv, ++i); +#endif + }else if( cli_strcmp(z,"-pagecache")==0 ){ + sqlite3_int64 n, sz; + sz = integerValue(cmdline_option_value(argc,argv,++i)); + if( sz>70000 ) sz = 70000; + if( sz<0 ) sz = 0; + n = integerValue(cmdline_option_value(argc,argv,++i)); + if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){ + n = 0xffffffffffffLL/sz; + } + verify_uninitialized(); + sqlite3_config(SQLITE_CONFIG_PAGECACHE, + (n>0 && sz>0) ? malloc(n*sz) : 0, sz, n); + data.shellFlgs |= SHFLG_Pagecache; + }else if( cli_strcmp(z,"-lookaside")==0 ){ + int n, sz; + sz = (int)integerValue(cmdline_option_value(argc,argv,++i)); + if( sz<0 ) sz = 0; + n = (int)integerValue(cmdline_option_value(argc,argv,++i)); + if( n<0 ) n = 0; + verify_uninitialized(); + sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); + if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; + }else if( cli_strcmp(z,"-threadsafe")==0 ){ + int n; + n = (int)integerValue(cmdline_option_value(argc,argv,++i)); + verify_uninitialized(); + switch( n ){ + case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break; + case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break; + default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break; + } +#ifdef SQLITE_ENABLE_VFSTRACE + }else if( cli_strcmp(z,"-vfstrace")==0 ){ + extern int vfstrace_register( + const char *zTraceName, + const char *zOldVfsName, + int (*xOut)(const char*,void*), + void *pOutArg, + int makeDefault + ); + vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1); +#endif +#ifdef SQLITE_ENABLE_MULTIPLEX + }else if( cli_strcmp(z,"-multiplex")==0 ){ + extern int sqlite3_multiplex_initialize(const char*,int); + sqlite3_multiplex_initialize(0, 1); +#endif + }else if( cli_strcmp(z,"-mmap")==0 ){ + sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); + verify_uninitialized(); + sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz); +#if defined(SQLITE_ENABLE_SORTER_REFERENCES) + }else if( cli_strcmp(z,"-sorterref")==0 ){ + sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); + verify_uninitialized(); + sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz); +#endif + }else if( cli_strcmp(z,"-vfs")==0 ){ + zVfs = cmdline_option_value(argc, argv, ++i); +#ifdef SQLITE_HAVE_ZLIB + }else if( cli_strcmp(z,"-zip")==0 ){ + data.openMode = SHELL_OPEN_ZIPFILE; +#endif + }else if( cli_strcmp(z,"-append")==0 ){ + data.openMode = SHELL_OPEN_APPENDVFS; +#ifndef SQLITE_OMIT_DESERIALIZE + }else if( cli_strcmp(z,"-deserialize")==0 ){ + data.openMode = SHELL_OPEN_DESERIALIZE; + }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){ + data.szMax = integerValue(argv[++i]); +#endif + }else if( cli_strcmp(z,"-readonly")==0 ){ + data.openMode = SHELL_OPEN_READONLY; + }else if( cli_strcmp(z,"-nofollow")==0 ){ + data.openFlags = SQLITE_OPEN_NOFOLLOW; +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) + }else if( cli_strncmp(z, "-A",2)==0 ){ + /* All remaining command-line arguments are passed to the ".archive" + ** command, so ignore them */ + break; +#endif + }else if( cli_strcmp(z, "-memtrace")==0 ){ + sqlite3MemTraceActivate(stderr); + }else if( cli_strcmp(z, "-pcachetrace")==0 ){ + sqlite3PcacheTraceActivate(stderr); + }else if( cli_strcmp(z,"-bail")==0 ){ + bail_on_error = 1; + }else if( cli_strcmp(z,"-nonce")==0 ){ + free(data.zNonce); + data.zNonce = strdup(cmdline_option_value(argc, argv, ++i)); + }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ + ShellSetFlag(&data,SHFLG_TestingMode); + }else if( cli_strcmp(z,"-safe")==0 ){ + /* no-op - catch this on the second pass */ + } + } +#ifndef SQLITE_SHELL_FIDDLE + verify_uninitialized(); +#endif + + +#ifdef SQLITE_SHELL_INIT_PROC + { + /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name + ** of a C-function that will perform initialization actions on SQLite that + ** occur just before or after sqlite3_initialize(). Use this compile-time + ** option to embed this shell program in larger applications. */ + extern void SQLITE_SHELL_INIT_PROC(void); + SQLITE_SHELL_INIT_PROC(); + } +#else + /* All the sqlite3_config() calls have now been made. So it is safe + ** to call sqlite3_initialize() and process any command line -vfs option. */ + sqlite3_initialize(); +#endif + + if( zVfs ){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(zVfs); + if( pVfs ){ + sqlite3_vfs_register(pVfs, 1); + }else{ + utf8_printf(stderr, "no such VFS: \"%s\"\n", zVfs); + exit(1); + } + } + + if( data.pAuxDb->zDbFilename==0 ){ +#ifndef SQLITE_OMIT_MEMORYDB + data.pAuxDb->zDbFilename = ":memory:"; + warnInmemoryDb = argc==1; +#else + utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0); + return 1; +#endif + } + data.out = stdout; +#ifndef SQLITE_SHELL_FIDDLE + sqlite3_appendvfs_init(0,0,0); +#endif + + /* Go ahead and open the database file if it already exists. If the + ** file does not exist, delay opening it. This prevents empty database + ** files from being created if a user mistypes the database name argument + ** to the sqlite command-line tool. + */ + if( access(data.pAuxDb->zDbFilename, 0)==0 ){ + open_db(&data, 0); + } + + /* Process the initialization file if there is one. If no -init option + ** is given on the command line, look for a file named ~/.sqliterc and + ** try to process it. + */ + process_sqliterc(&data,zInitFile); + + /* Make a second pass through the command-line argument and set + ** options. This second pass is delayed until after the initialization + ** file is processed so that the command-line arguments will override + ** settings in the initialization file. + */ + for(i=1; i<argc; i++){ + char *z = argv[i]; + if( z[0]!='-' || i>=nOptsEnd ) continue; + if( z[1]=='-' ){ z++; } + if( cli_strcmp(z,"-init")==0 ){ + i++; + }else if( cli_strcmp(z,"-html")==0 ){ + data.mode = MODE_Html; + }else if( cli_strcmp(z,"-list")==0 ){ + data.mode = MODE_List; + }else if( cli_strcmp(z,"-quote")==0 ){ + data.mode = MODE_Quote; + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row); + }else if( cli_strcmp(z,"-line")==0 ){ + data.mode = MODE_Line; + }else if( cli_strcmp(z,"-column")==0 ){ + data.mode = MODE_Column; + }else if( cli_strcmp(z,"-json")==0 ){ + data.mode = MODE_Json; + }else if( cli_strcmp(z,"-markdown")==0 ){ + data.mode = MODE_Markdown; + }else if( cli_strcmp(z,"-table")==0 ){ + data.mode = MODE_Table; + }else if( cli_strcmp(z,"-box")==0 ){ + data.mode = MODE_Box; + }else if( cli_strcmp(z,"-csv")==0 ){ + data.mode = MODE_Csv; + memcpy(data.colSeparator,",",2); +#ifdef SQLITE_HAVE_ZLIB + }else if( cli_strcmp(z,"-zip")==0 ){ + data.openMode = SHELL_OPEN_ZIPFILE; +#endif + }else if( cli_strcmp(z,"-append")==0 ){ + data.openMode = SHELL_OPEN_APPENDVFS; +#ifndef SQLITE_OMIT_DESERIALIZE + }else if( cli_strcmp(z,"-deserialize")==0 ){ + data.openMode = SHELL_OPEN_DESERIALIZE; + }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){ + data.szMax = integerValue(argv[++i]); +#endif + }else if( cli_strcmp(z,"-readonly")==0 ){ + data.openMode = SHELL_OPEN_READONLY; + }else if( cli_strcmp(z,"-nofollow")==0 ){ + data.openFlags |= SQLITE_OPEN_NOFOLLOW; + }else if( cli_strcmp(z,"-ascii")==0 ){ + data.mode = MODE_Ascii; + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit); + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record); + }else if( cli_strcmp(z,"-tabs")==0 ){ + data.mode = MODE_List; + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Tab); + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Row); + }else if( cli_strcmp(z,"-separator")==0 ){ + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, + "%s",cmdline_option_value(argc,argv,++i)); + }else if( cli_strcmp(z,"-newline")==0 ){ + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, + "%s",cmdline_option_value(argc,argv,++i)); + }else if( cli_strcmp(z,"-nullvalue")==0 ){ + sqlite3_snprintf(sizeof(data.nullValue), data.nullValue, + "%s",cmdline_option_value(argc,argv,++i)); + }else if( cli_strcmp(z,"-header")==0 ){ + data.showHeader = 1; + ShellSetFlag(&data, SHFLG_HeaderSet); + }else if( cli_strcmp(z,"-noheader")==0 ){ + data.showHeader = 0; + ShellSetFlag(&data, SHFLG_HeaderSet); + }else if( cli_strcmp(z,"-echo")==0 ){ + ShellSetFlag(&data, SHFLG_Echo); + }else if( cli_strcmp(z,"-eqp")==0 ){ + data.autoEQP = AUTOEQP_on; + }else if( cli_strcmp(z,"-eqpfull")==0 ){ + data.autoEQP = AUTOEQP_full; + }else if( cli_strcmp(z,"-stats")==0 ){ + data.statsOn = 1; + }else if( cli_strcmp(z,"-scanstats")==0 ){ + data.scanstatsOn = 1; + }else if( cli_strcmp(z,"-backslash")==0 ){ + /* Undocumented command-line option: -backslash + ** Causes C-style backslash escapes to be evaluated in SQL statements + ** prior to sending the SQL into SQLite. Useful for injecting + ** crazy bytes in the middle of SQL statements for testing and debugging. + */ + ShellSetFlag(&data, SHFLG_Backslash); + }else if( cli_strcmp(z,"-bail")==0 ){ + /* No-op. The bail_on_error flag should already be set. */ + }else if( cli_strcmp(z,"-version")==0 ){ + printf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); + return 0; + }else if( cli_strcmp(z,"-interactive")==0 ){ + stdin_is_interactive = 1; + }else if( cli_strcmp(z,"-batch")==0 ){ + stdin_is_interactive = 0; + }else if( cli_strcmp(z,"-utf8")==0 ){ +#if SHELL_WIN_UTF8_OPT + console_utf8 = 1; +#endif /* SHELL_WIN_UTF8_OPT */ + }else if( cli_strcmp(z,"-heap")==0 ){ + i++; + }else if( cli_strcmp(z,"-pagecache")==0 ){ + i+=2; + }else if( cli_strcmp(z,"-lookaside")==0 ){ + i+=2; + }else if( cli_strcmp(z,"-threadsafe")==0 ){ + i+=2; + }else if( cli_strcmp(z,"-nonce")==0 ){ + i += 2; + }else if( cli_strcmp(z,"-mmap")==0 ){ + i++; + }else if( cli_strcmp(z,"-memtrace")==0 ){ + i++; + }else if( cli_strcmp(z,"-pcachetrace")==0 ){ + i++; +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + }else if( cli_strcmp(z,"-sorterref")==0 ){ + i++; +#endif + }else if( cli_strcmp(z,"-vfs")==0 ){ + i++; +#ifdef SQLITE_ENABLE_VFSTRACE + }else if( cli_strcmp(z,"-vfstrace")==0 ){ + i++; +#endif +#ifdef SQLITE_ENABLE_MULTIPLEX + }else if( cli_strcmp(z,"-multiplex")==0 ){ + i++; +#endif + }else if( cli_strcmp(z,"-help")==0 ){ + usage(1); + }else if( cli_strcmp(z,"-cmd")==0 ){ + /* Run commands that follow -cmd first and separately from commands + ** that simply appear on the command-line. This seems goofy. It would + ** be better if all commands ran in the order that they appear. But + ** we retain the goofy behavior for historical compatibility. */ + if( i==argc-1 ) break; + z = cmdline_option_value(argc,argv,++i); + if( z[0]=='.' ){ + rc = do_meta_command(z, &data); + if( rc && bail_on_error ) return rc==2 ? 0 : rc; + }else{ + open_db(&data, 0); + rc = shell_exec(&data, z, &zErrMsg); + if( zErrMsg!=0 ){ + utf8_printf(stderr,"Error: %s\n", zErrMsg); + if( bail_on_error ) return rc!=0 ? rc : 1; + }else if( rc!=0 ){ + utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z); + if( bail_on_error ) return rc; + } + } +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) + }else if( cli_strncmp(z, "-A", 2)==0 ){ + if( nCmd>0 ){ + utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands" + " with \"%s\"\n", z); + return 1; + } + open_db(&data, OPEN_DB_ZIPFILE); + if( z[2] ){ + argv[i] = &z[2]; + arDotCommand(&data, 1, argv+(i-1), argc-(i-1)); + }else{ + arDotCommand(&data, 1, argv+i, argc-i); + } + readStdin = 0; + break; +#endif + }else if( cli_strcmp(z,"-safe")==0 ){ + data.bSafeMode = data.bSafeModePersist = 1; + }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ + /* Acted upon in first pass. */ + }else{ + utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); + raw_printf(stderr,"Use -help for a list of options.\n"); + return 1; + } + data.cMode = data.mode; + } +#if SHELL_WIN_UTF8_OPT + if( console_utf8 && stdin_is_interactive ){ + console_prepare(); + }else{ + setBinaryMode(stdin, 0); + console_utf8 = 0; + } +#endif + + if( !readStdin ){ + /* Run all arguments that do not begin with '-' as if they were separate + ** command-line inputs, except for the argToSkip argument which contains + ** the database filename. + */ + for(i=0; i<nCmd; i++){ + if( azCmd[i][0]=='.' ){ + rc = do_meta_command(azCmd[i], &data); + if( rc ){ + free(azCmd); + return rc==2 ? 0 : rc; + } + }else{ + open_db(&data, 0); + echo_group_input(&data, azCmd[i]); + rc = shell_exec(&data, azCmd[i], &zErrMsg); + if( zErrMsg || rc ){ + if( zErrMsg!=0 ){ + utf8_printf(stderr,"Error: %s\n", zErrMsg); + }else{ + utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]); + } + sqlite3_free(zErrMsg); + free(azCmd); + return rc!=0 ? rc : 1; + } + } + } + }else{ + /* Run commands received from standard input + */ + if( stdin_is_interactive ){ + char *zHome; + char *zHistory; + int nHistory; + printf( + "SQLite version %s %.19s\n" /*extra-version-info*/ + "Enter \".help\" for usage hints.\n", + sqlite3_libversion(), sqlite3_sourceid() + ); + if( warnInmemoryDb ){ + printf("Connected to a "); + printBold("transient in-memory database"); + printf(".\nUse \".open FILENAME\" to reopen on a " + "persistent database.\n"); + } + zHistory = getenv("SQLITE_HISTORY"); + if( zHistory ){ + zHistory = strdup(zHistory); + }else if( (zHome = find_home_dir(0))!=0 ){ + nHistory = strlen30(zHome) + 20; + if( (zHistory = malloc(nHistory))!=0 ){ + sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); + } + } + if( zHistory ){ shell_read_history(zHistory); } +#if HAVE_READLINE || HAVE_EDITLINE + rl_attempted_completion_function = readline_completion; +#elif HAVE_LINENOISE + linenoiseSetCompletionCallback(linenoise_completion); +#endif + data.in = 0; + rc = process_input(&data); + if( zHistory ){ + shell_stifle_history(2000); + shell_write_history(zHistory); + free(zHistory); + } + }else{ + data.in = stdin; + rc = process_input(&data); + } + } +#ifndef SQLITE_SHELL_FIDDLE + /* In WASM mode we have to leave the db state in place so that + ** client code can "push" SQL into it after this call returns. */ + free(azCmd); + set_table_name(&data, 0); + if( data.db ){ + session_close_all(&data, -1); + close_db(data.db); + } + for(i=0; i<ArraySize(data.aAuxDb); i++){ + sqlite3_free(data.aAuxDb[i].zFreeOnClose); + if( data.aAuxDb[i].db ){ + session_close_all(&data, i); + close_db(data.aAuxDb[i].db); + } + } + find_home_dir(1); + output_reset(&data); + data.doXdgOpen = 0; + clearTempFile(&data); +#if !SQLITE_SHELL_IS_UTF8 + for(i=0; i<argcToFree; i++) free(argvToFree[i]); + free(argvToFree); +#endif + free(data.colWidth); + free(data.zNonce); + /* Clear the global data structure so that valgrind will detect memory + ** leaks */ + memset(&data, 0, sizeof(data)); +#ifdef SQLITE_DEBUG + if( sqlite3_memory_used()>mem_main_enter ){ + utf8_printf(stderr, "Memory leaked: %u bytes\n", + (unsigned int)(sqlite3_memory_used()-mem_main_enter)); + } +#endif +#endif /* !SQLITE_SHELL_FIDDLE */ + return rc; +} + + +#ifdef SQLITE_SHELL_FIDDLE +/* Only for emcc experimentation purposes. */ +int fiddle_experiment(int a,int b){ + return a + b; +} + +/* +** Returns a pointer to the current DB handle. +*/ +sqlite3 * fiddle_db_handle(){ + return globalDb; +} + +/* +** Returns a pointer to the given DB name's VFS. If zDbName is 0 then +** "main" is assumed. Returns 0 if no db with the given name is +** open. +*/ +sqlite3_vfs * fiddle_db_vfs(const char *zDbName){ + sqlite3_vfs * pVfs = 0; + if(globalDb){ + sqlite3_file_control(globalDb, zDbName ? zDbName : "main", + SQLITE_FCNTL_VFS_POINTER, &pVfs); + } + return pVfs; +} + +/* Only for emcc experimentation purposes. */ +sqlite3 * fiddle_db_arg(sqlite3 *arg){ + printf("fiddle_db_arg(%p)\n", (const void*)arg); + return arg; +} + +/* +** Intended to be called via a SharedWorker() while a separate +** SharedWorker() (which manages the wasm module) is performing work +** which should be interrupted. Unfortunately, SharedWorker is not +** portable enough to make real use of. +*/ +void fiddle_interrupt(void){ + if( globalDb ) sqlite3_interrupt(globalDb); +} + +/* +** Returns the filename of the given db name, assuming "main" if +** zDbName is NULL. Returns NULL if globalDb is not opened. +*/ +const char * fiddle_db_filename(const char * zDbName){ + return globalDb + ? sqlite3_db_filename(globalDb, zDbName ? zDbName : "main") + : NULL; +} + +/* +** Completely wipes out the contents of the currently-opened database +** but leaves its storage intact for reuse. +*/ +void fiddle_reset_db(void){ + if( globalDb ){ + int rc = sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); + if( 0==rc ) rc = sqlite3_exec(globalDb, "VACUUM", 0, 0, 0); + sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); + } +} + +/* +** Uses the current database's VFS xRead to stream the db file's +** contents out to the given callback. The callback gets a single +** chunk of size n (its 2nd argument) on each call and must return 0 +** on success, non-0 on error. This function returns 0 on success, +** SQLITE_NOTFOUND if no db is open, or propagates any other non-0 +** code from the callback. Note that this is not thread-friendly: it +** expects that it will be the only thread reading the db file and +** takes no measures to ensure that is the case. +*/ +int fiddle_export_db( int (*xCallback)(unsigned const char *zOut, int n) ){ + sqlite3_int64 nSize = 0; + sqlite3_int64 nPos = 0; + sqlite3_file * pFile = 0; + unsigned char buf[1024 * 8]; + int nBuf = (int)sizeof(buf); + int rc = shellState.db + ? sqlite3_file_control(shellState.db, "main", + SQLITE_FCNTL_FILE_POINTER, &pFile) + : SQLITE_NOTFOUND; + if( rc ) return rc; + rc = pFile->pMethods->xFileSize(pFile, &nSize); + if( rc ) return rc; + if(nSize % nBuf){ + /* DB size is not an even multiple of the buffer size. Reduce + ** buffer size so that we do not unduly inflate the db size when + ** exporting. */ + if(0 == nSize % 4096) nBuf = 4096; + else if(0 == nSize % 2048) nBuf = 2048; + else if(0 == nSize % 1024) nBuf = 1024; + else nBuf = 512; + } + for( ; 0==rc && nPos<nSize; nPos += nBuf ){ + rc = pFile->pMethods->xRead(pFile, buf, nBuf, nPos); + if(SQLITE_IOERR_SHORT_READ == rc){ + rc = (nPos + nBuf) < nSize ? rc : 0/*assume EOF*/; + } + if( 0==rc ) rc = xCallback(buf, nBuf); + } + return rc; +} + +/* +** Trivial exportable function for emscripten. It processes zSql as if +** it were input to the sqlite3 shell and redirects all output to the +** wasm binding. fiddle_main() must have been called before this +** is called, or results are undefined. +*/ +void fiddle_exec(const char * zSql){ + if(zSql && *zSql){ + if('.'==*zSql) puts(zSql); + shellState.wasm.zInput = zSql; + shellState.wasm.zPos = zSql; + process_input(&shellState); + shellState.wasm.zInput = shellState.wasm.zPos = 0; + } +} +#endif /* SQLITE_SHELL_FIDDLE */ diff --git a/SQLITE/sqlite3.1 b/SQLITE/sqlite3.1 new file mode 100644 index 0000000..08b1ff2 --- /dev/null +++ b/SQLITE/sqlite3.1 @@ -0,0 +1,161 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH SQLITE3 1 "Fri Aug 11 23:50:12 CET 2023" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp <n> insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +.B sqlite3 +\- A command line interface for SQLite version 3 + +.SH SYNOPSIS +.B sqlite3 +.RI [ options ] +.RI [ databasefile ] +.RI [ SQL ] + +.SH SUMMARY +.PP +.B sqlite3 +is a terminal-based front-end to the SQLite library that can evaluate +queries interactively and display the results in multiple formats. +.B sqlite3 +can also be used within shell scripts and other applications to provide +batch processing features. + +.SH DESCRIPTION +To start a +.B sqlite3 +interactive session, invoke the +.B sqlite3 +command and optionally provide the name of a database file. If the +database file does not exist, it will be created. If the database file +does exist, it will be opened. + +For example, to create a new database file named "mydata.db", create +a table named "memos" and insert a couple of records into that table: +.sp +$ +.B sqlite3 mydata.db +.br +SQLite version 3.43.0 2023-08-11 17:45:23 +.br +Enter ".help" for usage hints. +.br +sqlite> +.B create table memos(text, priority INTEGER); +.br +sqlite> +.B insert into memos values('deliver project description', 10); +.br +sqlite> +.B insert into memos values('lunch with Christine', 100); +.br +sqlite> +.B select * from memos; +.br +deliver project description|10 +.br +lunch with Christine|100 +.br +sqlite> +.sp + +If no database name is supplied, the ATTACH sql command can be used +to attach to existing or create new database files. ATTACH can also +be used to attach to multiple databases within the same interactive +session. This is useful for migrating data between databases, +possibly changing the schema along the way. + +Optionally, a SQL statement or set of SQL statements can be supplied as +a single argument. Multiple statements should be separated by +semi-colons. + +For example: +.sp +$ +.B sqlite3 -line mydata.db 'select * from memos where priority > 20;' +.br + text = lunch with Christine +.br +priority = 100 +.br +.sp + +.SS SQLITE META-COMMANDS +.PP +The interactive interpreter offers a set of meta-commands that can be +used to control the output format, examine the currently attached +database files, or perform administrative operations upon the +attached databases (such as rebuilding indices). Meta-commands are +always prefixed with a dot (.). + +A list of available meta-commands can be viewed at any time by issuing +the '.help' command. For example: +.sp +sqlite> +.B .help +.nf +.tr %. +... +.sp +.fi + +The available commands differ by version and build options, so they +are not listed here. Please refer to your local copy for all available +options. + + +.SH INIT FILE +.B sqlite3 +reads an initialization file to set the configuration of the +interactive environment. Throughout initialization, any previously +specified setting can be overridden. The sequence of initialization is +as follows: + +o The default configuration is established as follows: + +.sp +.nf +.cc | +mode = LIST +separator = "|" +main prompt = "sqlite> " +continue prompt = " ...> " +|cc . +.sp +.fi + +o If the file +.B ${XDG_CONFIG_HOME}/sqlite3/sqliterc +or +.B ~/.sqliterc +exists, the first of those to be found is processed during startup. +It should generally only contain meta-commands. + +o If the -init option is present, the specified file is processed. + +o All other command line options are processed. + +.SH SEE ALSO +https://sqlite.org/cli.html +.br +https://sqlite.org/fiddle (a WebAssembly build of the CLI app) +.br +The sqlite3-doc package. +.SH AUTHOR +This manual page was originally written by Andreas Rottmann +<rotty@debian.org>, for the Debian GNU/Linux system (but may be used +by others). It was subsequently revised by Bill Bumgarner <bbum@mac.com>, +Laszlo Boszormenyi <gcs@debian.hu>, and the sqlite3 developers. diff --git a/SQLITE/sqlite3.c b/SQLITE/sqlite3.c new file mode 100644 index 0000000..208db91 --- /dev/null +++ b/SQLITE/sqlite3.c @@ -0,0 +1,251262 @@ +/****************************************************************************** +** This file is an amalgamation of many separate C source files from SQLite +** version 3.44.0. By combining all the individual C code files into this +** single large file, the entire code can be compiled as a single translation +** unit. This allows many compilers to do optimizations that would not be +** possible if the files were compiled separately. Performance improvements +** of 5% or more are commonly seen when SQLite is compiled as a single +** translation unit. +** +** This file is all you need to compile SQLite. To use SQLite in other +** programs, you need this file and the "sqlite3.h" header file that defines +** the programming interface to the SQLite library. (If you do not have +** the "sqlite3.h" header file at hand, you will find a copy embedded within +** the text of this file. Search for "Begin file sqlite3.h" to find the start +** of the embedded sqlite3.h header file.) Additional code files may be needed +** if you want a wrapper to interface SQLite with your choice of programming +** language. The code for the "sqlite3" command-line shell is also in a +** separate file. This file contains only code for the core SQLite library. +** +** The content in this amalgamation comes from Fossil check-in +** 308fdda4b81c110ba4a66d0b325e7653c2f. +*/ +#define SQLITE_CORE 1 +#define SQLITE_AMALGAMATION 1 +#ifndef SQLITE_PRIVATE +# define SQLITE_PRIVATE static +#endif +/************** Begin file sqliteInt.h ***************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Internal interface definitions for SQLite. +** +*/ +#ifndef SQLITEINT_H +#define SQLITEINT_H + +/* Special Comments: +** +** Some comments have special meaning to the tools that measure test +** coverage: +** +** NO_TEST - The branches on this line are not +** measured by branch coverage. This is +** used on lines of code that actually +** implement parts of coverage testing. +** +** OPTIMIZATION-IF-TRUE - This branch is allowed to always be false +** and the correct answer is still obtained, +** though perhaps more slowly. +** +** OPTIMIZATION-IF-FALSE - This branch is allowed to always be true +** and the correct answer is still obtained, +** though perhaps more slowly. +** +** PREVENTS-HARMLESS-OVERREAD - This branch prevents a buffer overread +** that would be harmless and undetectable +** if it did occur. +** +** In all cases, the special comment must be enclosed in the usual +** slash-asterisk...asterisk-slash comment marks, with no spaces between the +** asterisks and the comment text. +*/ + +/* +** Make sure the Tcl calling convention macro is defined. This macro is +** only used by test code and Tcl integration code. +*/ +#ifndef SQLITE_TCLAPI +# define SQLITE_TCLAPI +#endif + +/* +** Include the header file used to customize the compiler options for MSVC. +** This should be done first so that it can successfully prevent spurious +** compiler warnings due to subsequent content in this file and other files +** that are included by this file. +*/ +/************** Include msvc.h in the middle of sqliteInt.h ******************/ +/************** Begin file msvc.h ********************************************/ +/* +** 2015 January 12 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to MSVC. +*/ +#ifndef SQLITE_MSVC_H +#define SQLITE_MSVC_H + +#if defined(_MSC_VER) +#pragma warning(disable : 4054) +#pragma warning(disable : 4055) +#pragma warning(disable : 4100) +#pragma warning(disable : 4127) +#pragma warning(disable : 4130) +#pragma warning(disable : 4152) +#pragma warning(disable : 4189) +#pragma warning(disable : 4206) +#pragma warning(disable : 4210) +#pragma warning(disable : 4232) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) +#pragma warning(disable : 4306) +#pragma warning(disable : 4702) +#pragma warning(disable : 4706) +#endif /* defined(_MSC_VER) */ + +#if defined(_MSC_VER) && !defined(_WIN64) +#undef SQLITE_4_BYTE_ALIGNED_MALLOC +#define SQLITE_4_BYTE_ALIGNED_MALLOC +#endif /* defined(_MSC_VER) && !defined(_WIN64) */ + +#if !defined(HAVE_LOG2) && defined(_MSC_VER) && _MSC_VER<1800 +#define HAVE_LOG2 0 +#endif /* !defined(HAVE_LOG2) && defined(_MSC_VER) && _MSC_VER<1800 */ + +#endif /* SQLITE_MSVC_H */ + +/************** End of msvc.h ************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + +/* +** Special setup for VxWorks +*/ +/************** Include vxworks.h in the middle of sqliteInt.h ***************/ +/************** Begin file vxworks.h *****************************************/ +/* +** 2015-03-02 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to Wind River's VxWorks +*/ +#if defined(__RTP__) || defined(_WRS_KERNEL) +/* This is VxWorks. Set up things specially for that OS +*/ +#include <vxWorks.h> +#include <pthread.h> /* amalgamator: dontcache */ +#define OS_VXWORKS 1 +#define SQLITE_OS_OTHER 0 +#define SQLITE_HOMEGROWN_RECURSIVE_MUTEX 1 +#define SQLITE_OMIT_LOAD_EXTENSION 1 +#define SQLITE_ENABLE_LOCKING_STYLE 0 +#define HAVE_UTIME 1 +#else +/* This is not VxWorks. */ +#define OS_VXWORKS 0 +#define HAVE_FCHOWN 1 +#define HAVE_READLINK 1 +#define HAVE_LSTAT 1 +#endif /* defined(_WRS_KERNEL) */ + +/************** End of vxworks.h *********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + +/* +** These #defines should enable >2GB file support on POSIX if the +** underlying operating system supports it. If the OS lacks +** large file support, or if the OS is windows, these should be no-ops. +** +** Ticket #2739: The _LARGEFILE_SOURCE macro must appear before any +** system #includes. Hence, this block of code must be the very first +** code in all source files. +** +** Large file support can be disabled using the -DSQLITE_DISABLE_LFS switch +** on the compiler command line. This is necessary if you are compiling +** on a recent machine (ex: Red Hat 7.2) but you want your code to work +** on an older machine (ex: Red Hat 6.0). If you compile on Red Hat 7.2 +** without this option, LFS is enable. But LFS does not exist in the kernel +** in Red Hat 6.0, so the code won't work. Hence, for maximum binary +** portability you should omit LFS. +** +** The previous paragraph was written in 2005. (This paragraph is written +** on 2008-11-28.) These days, all Linux kernels support large files, so +** you should probably leave LFS enabled. But some embedded platforms might +** lack LFS in which case the SQLITE_DISABLE_LFS macro might still be useful. +** +** Similar is true for Mac OS X. LFS is only supported on Mac OS X 9 and later. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + +/* The GCC_VERSION and MSVC_VERSION macros are used to +** conditionally include optimizations for each of these compilers. A +** value of 0 means that compiler is not being used. The +** SQLITE_DISABLE_INTRINSIC macro means do not use any compiler-specific +** optimizations, and hence set all compiler macros to 0 +** +** There was once also a CLANG_VERSION macro. However, we learn that the +** version numbers in clang are for "marketing" only and are inconsistent +** and unreliable. Fortunately, all versions of clang also recognize the +** gcc version numbers and have reasonable settings for gcc version numbers, +** so the GCC_VERSION macro will be set to a correct non-zero value even +** when compiling with clang. +*/ +#if defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC) +# define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__) +#else +# define GCC_VERSION 0 +#endif +#if defined(_MSC_VER) && !defined(SQLITE_DISABLE_INTRINSIC) +# define MSVC_VERSION _MSC_VER +#else +# define MSVC_VERSION 0 +#endif + +/* +** Some C99 functions in "math.h" are only present for MSVC when its version +** is associated with Visual Studio 2013 or higher. +*/ +#ifndef SQLITE_HAVE_C99_MATH_FUNCS +# if MSVC_VERSION==0 || MSVC_VERSION>=1800 +# define SQLITE_HAVE_C99_MATH_FUNCS (1) +# else +# define SQLITE_HAVE_C99_MATH_FUNCS (0) +# endif +#endif + +/* Needed for various definitions... */ +#if defined(__GNUC__) && !defined(_GNU_SOURCE) +# define _GNU_SOURCE +#endif + +#if defined(__OpenBSD__) && !defined(_BSD_SOURCE) +# define _BSD_SOURCE +#endif + +/* +** Macro to disable warnings about missing "break" at the end of a "case". +*/ +#if GCC_VERSION>=7000000 +# define deliberate_fall_through __attribute__((fallthrough)); +#else +# define deliberate_fall_through +#endif + +/* +** For MinGW, check to see if we can include the header file containing its +** version information, among other things. Normally, this internal MinGW +** header file would [only] be included automatically by other MinGW header +** files; however, the contained version information is now required by this +** header file to work around binary compatibility issues (see below) and +** this is the only known way to reliably obtain it. This entire #if block +** would be completely unnecessary if there was any other way of detecting +** MinGW via their preprocessor (e.g. if they customized their GCC to define +** some MinGW-specific macros). When compiling for MinGW, either the +** _HAVE_MINGW_H or _HAVE__MINGW_H (note the extra underscore) macro must be +** defined; otherwise, detection of conditions specific to MinGW will be +** disabled. +*/ +#if defined(_HAVE_MINGW_H) +# include "mingw.h" +#elif defined(_HAVE__MINGW_H) +# include "_mingw.h" +#endif + +/* +** For MinGW version 4.x (and higher), check to see if the _USE_32BIT_TIME_T +** define is required to maintain binary compatibility with the MSVC runtime +** library in use (e.g. for Windows XP). +*/ +#if !defined(_USE_32BIT_TIME_T) && !defined(_USE_64BIT_TIME_T) && \ + defined(_WIN32) && !defined(_WIN64) && \ + defined(__MINGW_MAJOR_VERSION) && __MINGW_MAJOR_VERSION >= 4 && \ + defined(__MSVCRT__) +# define _USE_32BIT_TIME_T +#endif + +/* Optionally #include a user-defined header, whereby compilation options +** may be set prior to where they take effect, but after platform setup. +** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include +** file. +*/ +#ifdef SQLITE_CUSTOM_INCLUDE +# define INC_STRINGIFY_(f) #f +# define INC_STRINGIFY(f) INC_STRINGIFY_(f) +# include INC_STRINGIFY(SQLITE_CUSTOM_INCLUDE) +#endif + +/* The public SQLite interface. The _FILE_OFFSET_BITS macro must appear +** first in QNX. Also, the _USE_32BIT_TIME_T macro must appear first for +** MinGW. +*/ +/************** Include sqlite3.h in the middle of sqliteInt.h ***************/ +/************** Begin file sqlite3.h *****************************************/ +/* +** 2001-09-15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the SQLite library +** presents to client programs. If a C-function, structure, datatype, +** or constant definition does not appear in this file, then it is +** not a published API of SQLite, is subject to change without +** notice, and should not be referenced by programs that use SQLite. +** +** Some of the definitions that are in this file are marked as +** "experimental". Experimental interfaces are normally new +** features recently added to SQLite. We do not anticipate changes +** to experimental interfaces but reserve the right to make minor changes +** if experience from use "in the wild" suggest such changes are prudent. +** +** The official C-language API documentation for SQLite is derived +** from comments in this file. This file is the authoritative source +** on how SQLite interfaces are supposed to operate. +** +** The name of this file under configuration management is "sqlite.h.in". +** The makefile makes some minor changes to this file (such as inserting +** the version number) and changes its name to "sqlite3.h" as +** part of the build process. +*/ +#ifndef SQLITE3_H +#define SQLITE3_H +#include <stdarg.h> /* Needed for the definition of va_list */ + +/* +** Make sure we can call this stuff from C++. +*/ +#if 0 +extern "C" { +#endif + + +/* +** Facilitate override of interface linkage and calling conventions. +** Be aware that these macros may not be used within this particular +** translation of the amalgamation and its associated header file. +** +** The SQLITE_EXTERN and SQLITE_API macros are used to instruct the +** compiler that the target identifier should have external linkage. +** +** The SQLITE_CDECL macro is used to set the calling convention for +** public functions that accept a variable number of arguments. +** +** The SQLITE_APICALL macro is used to set the calling convention for +** public functions that accept a fixed number of arguments. +** +** The SQLITE_STDCALL macro is no longer used and is now deprecated. +** +** The SQLITE_CALLBACK macro is used to set the calling convention for +** function pointers. +** +** The SQLITE_SYSAPI macro is used to set the calling convention for +** functions provided by the operating system. +** +** Currently, the SQLITE_CDECL, SQLITE_APICALL, SQLITE_CALLBACK, and +** SQLITE_SYSAPI macros are used only when building for environments +** that require non-default calling conventions. +*/ +#ifndef SQLITE_EXTERN +# define SQLITE_EXTERN extern +#endif +#ifndef SQLITE_API +# define SQLITE_API +#endif +#ifndef SQLITE_CDECL +# define SQLITE_CDECL +#endif +#ifndef SQLITE_APICALL +# define SQLITE_APICALL +#endif +#ifndef SQLITE_STDCALL +# define SQLITE_STDCALL SQLITE_APICALL +#endif +#ifndef SQLITE_CALLBACK +# define SQLITE_CALLBACK +#endif +#ifndef SQLITE_SYSAPI +# define SQLITE_SYSAPI +#endif + +/* +** These no-op macros are used in front of interfaces to mark those +** interfaces as either deprecated or experimental. New applications +** should not use deprecated interfaces - they are supported for backwards +** compatibility only. Application writers should be aware that +** experimental interfaces are subject to change in point releases. +** +** These macros used to resolve to various kinds of compiler magic that +** would generate warning messages when they were used. But that +** compiler magic ended up generating such a flurry of bug reports +** that we have taken it all out and gone back to using simple +** noop macros. +*/ +#define SQLITE_DEPRECATED +#define SQLITE_EXPERIMENTAL + +/* +** Ensure these symbols were not defined by some previous header file. +*/ +#ifdef SQLITE_VERSION +# undef SQLITE_VERSION +#endif +#ifdef SQLITE_VERSION_NUMBER +# undef SQLITE_VERSION_NUMBER +#endif + +/* +** CAPI3REF: Compile-Time Library Version Numbers +** +** ^(The [SQLITE_VERSION] C preprocessor macro in the sqlite3.h header +** evaluates to a string literal that is the SQLite version in the +** format "X.Y.Z" where X is the major version number (always 3 for +** SQLite3) and Y is the minor version number and Z is the release number.)^ +** ^(The [SQLITE_VERSION_NUMBER] C preprocessor macro resolves to an integer +** with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same +** numbers used in [SQLITE_VERSION].)^ +** The SQLITE_VERSION_NUMBER for any given release of SQLite will also +** be larger than the release from which it is derived. Either Y will +** be held constant and Z will be incremented or else Y will be incremented +** and Z will be reset to zero. +** +** Since [version 3.6.18] ([dateof:3.6.18]), +** SQLite source code has been stored in the +** <a href="http://www.fossil-scm.org/">Fossil configuration management +** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to +** a string which identifies a particular check-in of SQLite +** within its configuration management system. ^The SQLITE_SOURCE_ID +** string contains the date and time of the check-in (UTC) and a SHA1 +** or SHA3-256 hash of the entire source tree. If the source code has +** been edited in any way since it was last checked in, then the last +** four hexadecimal digits of the hash may be modified. +** +** See also: [sqlite3_libversion()], +** [sqlite3_libversion_number()], [sqlite3_sourceid()], +** [sqlite_version()] and [sqlite_source_id()]. +*/ +#define SQLITE_VERSION "3.44.0" +#define SQLITE_VERSION_NUMBER 3044000 +#define SQLITE_SOURCE_ID "2023-09-11 15:27:27 3308fdda4b81c110ba4a66d0b325e7653c2f8155e7864aeb78991ed1da061836" + +/* +** CAPI3REF: Run-Time Library Version Numbers +** KEYWORDS: sqlite3_version sqlite3_sourceid +** +** These interfaces provide the same information as the [SQLITE_VERSION], +** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros +** but are associated with the library instead of the header file. ^(Cautious +** programmers might include assert() statements in their application to +** verify that values returned by these interfaces match the macros in +** the header, and thus ensure that the application is +** compiled with matching library and header files. +** +** <blockquote><pre> +** assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER ); +** assert( strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,80)==0 ); +** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 ); +** </pre></blockquote>)^ +** +** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION] +** macro. ^The sqlite3_libversion() function returns a pointer to the +** to the sqlite3_version[] string constant. The sqlite3_libversion() +** function is provided for use in DLLs since DLL users usually do not have +** direct access to string constants within the DLL. ^The +** sqlite3_libversion_number() function returns an integer equal to +** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns +** a pointer to a string constant whose value is the same as the +** [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built +** using an edited copy of [the amalgamation], then the last four characters +** of the hash might be different from [SQLITE_SOURCE_ID].)^ +** +** See also: [sqlite_version()] and [sqlite_source_id()]. +*/ +SQLITE_API const char sqlite3_version[] = SQLITE_VERSION; +SQLITE_API const char *sqlite3_libversion(void); +SQLITE_API const char *sqlite3_sourceid(void); +SQLITE_API int sqlite3_libversion_number(void); + +/* +** CAPI3REF: Run-Time Library Compilation Options Diagnostics +** +** ^The sqlite3_compileoption_used() function returns 0 or 1 +** indicating whether the specified option was defined at +** compile time. ^The SQLITE_ prefix may be omitted from the +** option name passed to sqlite3_compileoption_used(). +** +** ^The sqlite3_compileoption_get() function allows iterating +** over the list of options that were defined at compile time by +** returning the N-th compile time option string. ^If N is out of range, +** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ +** prefix is omitted from any strings returned by +** sqlite3_compileoption_get(). +** +** ^Support for the diagnostic functions sqlite3_compileoption_used() +** and sqlite3_compileoption_get() may be omitted by specifying the +** [SQLITE_OMIT_COMPILEOPTION_DIAGS] option at compile time. +** +** See also: SQL functions [sqlite_compileoption_used()] and +** [sqlite_compileoption_get()] and the [compile_options pragma]. +*/ +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS +SQLITE_API int sqlite3_compileoption_used(const char *zOptName); +SQLITE_API const char *sqlite3_compileoption_get(int N); +#else +# define sqlite3_compileoption_used(X) 0 +# define sqlite3_compileoption_get(X) ((void*)0) +#endif + +/* +** CAPI3REF: Test To See If The Library Is Threadsafe +** +** ^The sqlite3_threadsafe() function returns zero if and only if +** SQLite was compiled with mutexing code omitted due to the +** [SQLITE_THREADSAFE] compile-time option being set to 0. +** +** SQLite can be compiled with or without mutexes. When +** the [SQLITE_THREADSAFE] C preprocessor macro is 1 or 2, mutexes +** are enabled and SQLite is threadsafe. When the +** [SQLITE_THREADSAFE] macro is 0, +** the mutexes are omitted. Without the mutexes, it is not safe +** to use SQLite concurrently from more than one thread. +** +** Enabling mutexes incurs a measurable performance penalty. +** So if speed is of utmost importance, it makes sense to disable +** the mutexes. But for maximum safety, mutexes should be enabled. +** ^The default behavior is for mutexes to be enabled. +** +** This interface can be used by an application to make sure that the +** version of SQLite that it is linking against was compiled with +** the desired setting of the [SQLITE_THREADSAFE] macro. +** +** This interface only reports on the compile-time mutex setting +** of the [SQLITE_THREADSAFE] flag. If SQLite is compiled with +** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but +** can be fully or partially disabled using a call to [sqlite3_config()] +** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD], +** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the +** sqlite3_threadsafe() function shows only the compile-time setting of +** thread safety, not any run-time changes to that setting made by +** sqlite3_config(). In other words, the return value from sqlite3_threadsafe() +** is unchanged by calls to sqlite3_config().)^ +** +** See the [threading mode] documentation for additional information. +*/ +SQLITE_API int sqlite3_threadsafe(void); + +/* +** CAPI3REF: Database Connection Handle +** KEYWORDS: {database connection} {database connections} +** +** Each open SQLite database is represented by a pointer to an instance of +** the opaque structure named "sqlite3". It is useful to think of an sqlite3 +** pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and +** [sqlite3_open_v2()] interfaces are its constructors, and [sqlite3_close()] +** and [sqlite3_close_v2()] are its destructors. There are many other +** interfaces (such as +** [sqlite3_prepare_v2()], [sqlite3_create_function()], and +** [sqlite3_busy_timeout()] to name but three) that are methods on an +** sqlite3 object. +*/ +typedef struct sqlite3 sqlite3; + +/* +** CAPI3REF: 64-Bit Integer Types +** KEYWORDS: sqlite_int64 sqlite_uint64 +** +** Because there is no cross-platform way to specify 64-bit integer types +** SQLite includes typedefs for 64-bit signed and unsigned integers. +** +** The sqlite3_int64 and sqlite3_uint64 are the preferred type definitions. +** The sqlite_int64 and sqlite_uint64 types are supported for backwards +** compatibility only. +** +** ^The sqlite3_int64 and sqlite_int64 types can store integer values +** between -9223372036854775808 and +9223372036854775807 inclusive. ^The +** sqlite3_uint64 and sqlite_uint64 types can store integer values +** between 0 and +18446744073709551615 inclusive. +*/ +#ifdef SQLITE_INT64_TYPE + typedef SQLITE_INT64_TYPE sqlite_int64; +# ifdef SQLITE_UINT64_TYPE + typedef SQLITE_UINT64_TYPE sqlite_uint64; +# else + typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; +# endif +#elif defined(_MSC_VER) || defined(__BORLANDC__) + typedef __int64 sqlite_int64; + typedef unsigned __int64 sqlite_uint64; +#else + typedef long long int sqlite_int64; + typedef unsigned long long int sqlite_uint64; +#endif +typedef sqlite_int64 sqlite3_int64; +typedef sqlite_uint64 sqlite3_uint64; + +/* +** If compiling for a processor that lacks floating point support, +** substitute integer for floating-point. +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# define double sqlite3_int64 +#endif + +/* +** CAPI3REF: Closing A Database Connection +** DESTRUCTOR: sqlite3 +** +** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors +** for the [sqlite3] object. +** ^Calls to sqlite3_close() and sqlite3_close_v2() return [SQLITE_OK] if +** the [sqlite3] object is successfully destroyed and all associated +** resources are deallocated. +** +** Ideally, applications should [sqlite3_finalize | finalize] all +** [prepared statements], [sqlite3_blob_close | close] all [BLOB handles], and +** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated +** with the [sqlite3] object prior to attempting to close the object. +** ^If the database connection is associated with unfinalized prepared +** statements, BLOB handlers, and/or unfinished sqlite3_backup objects then +** sqlite3_close() will leave the database connection open and return +** [SQLITE_BUSY]. ^If sqlite3_close_v2() is called with unfinalized prepared +** statements, unclosed BLOB handlers, and/or unfinished sqlite3_backups, +** it returns [SQLITE_OK] regardless, but instead of deallocating the database +** connection immediately, it marks the database connection as an unusable +** "zombie" and makes arrangements to automatically deallocate the database +** connection after all prepared statements are finalized, all BLOB handles +** are closed, and all backups have finished. The sqlite3_close_v2() interface +** is intended for use with host languages that are garbage collected, and +** where the order in which destructors are called is arbitrary. +** +** ^If an [sqlite3] object is destroyed while a transaction is open, +** the transaction is automatically rolled back. +** +** The C parameter to [sqlite3_close(C)] and [sqlite3_close_v2(C)] +** must be either a NULL +** pointer or an [sqlite3] object pointer obtained +** from [sqlite3_open()], [sqlite3_open16()], or +** [sqlite3_open_v2()], and not previously closed. +** ^Calling sqlite3_close() or sqlite3_close_v2() with a NULL pointer +** argument is a harmless no-op. +*/ +SQLITE_API int sqlite3_close(sqlite3*); +SQLITE_API int sqlite3_close_v2(sqlite3*); + +/* +** The type for a callback function. +** This is legacy and deprecated. It is included for historical +** compatibility and is not documented. +*/ +typedef int (*sqlite3_callback)(void*,int,char**, char**); + +/* +** CAPI3REF: One-Step Query Execution Interface +** METHOD: sqlite3 +** +** The sqlite3_exec() interface is a convenience wrapper around +** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()], +** that allows an application to run multiple statements of SQL +** without having to use a lot of C code. +** +** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded, +** semicolon-separate SQL statements passed into its 2nd argument, +** in the context of the [database connection] passed in as its 1st +** argument. ^If the callback function of the 3rd argument to +** sqlite3_exec() is not NULL, then it is invoked for each result row +** coming out of the evaluated SQL statements. ^The 4th argument to +** sqlite3_exec() is relayed through to the 1st argument of each +** callback invocation. ^If the callback pointer to sqlite3_exec() +** is NULL, then no callback is ever invoked and result rows are +** ignored. +** +** ^If an error occurs while evaluating the SQL statements passed into +** sqlite3_exec(), then execution of the current statement stops and +** subsequent statements are skipped. ^If the 5th parameter to sqlite3_exec() +** is not NULL then any error message is written into memory obtained +** from [sqlite3_malloc()] and passed back through the 5th parameter. +** To avoid memory leaks, the application should invoke [sqlite3_free()] +** on error message strings returned through the 5th parameter of +** sqlite3_exec() after the error message string is no longer needed. +** ^If the 5th parameter to sqlite3_exec() is not NULL and no errors +** occur, then sqlite3_exec() sets the pointer in its 5th parameter to +** NULL before returning. +** +** ^If an sqlite3_exec() callback returns non-zero, the sqlite3_exec() +** routine returns SQLITE_ABORT without invoking the callback again and +** without running any subsequent SQL statements. +** +** ^The 2nd argument to the sqlite3_exec() callback function is the +** number of columns in the result. ^The 3rd argument to the sqlite3_exec() +** callback is an array of pointers to strings obtained as if from +** [sqlite3_column_text()], one for each column. ^If an element of a +** result row is NULL then the corresponding string pointer for the +** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the +** sqlite3_exec() callback is an array of pointers to strings where each +** entry represents the name of corresponding result column as obtained +** from [sqlite3_column_name()]. +** +** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer +** to an empty string, or a pointer that contains only whitespace and/or +** SQL comments, then no SQL statements are evaluated and the database +** is not changed. +** +** Restrictions: +** +** <ul> +** <li> The application must ensure that the 1st parameter to sqlite3_exec() +** is a valid and open [database connection]. +** <li> The application must not close the [database connection] specified by +** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running. +** <li> The application must not modify the SQL statement text passed into +** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running. +** </ul> +*/ +SQLITE_API int sqlite3_exec( + sqlite3*, /* An open database */ + const char *sql, /* SQL to be evaluated */ + int (*callback)(void*,int,char**,char**), /* Callback function */ + void *, /* 1st argument to callback */ + char **errmsg /* Error msg written here */ +); + +/* +** CAPI3REF: Result Codes +** KEYWORDS: {result code definitions} +** +** Many SQLite functions return an integer result code from the set shown +** here in order to indicate success or failure. +** +** New error codes may be added in future versions of SQLite. +** +** See also: [extended result code definitions] +*/ +#define SQLITE_OK 0 /* Successful result */ +/* beginning-of-error-codes */ +#define SQLITE_ERROR 1 /* Generic error */ +#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ +#define SQLITE_PERM 3 /* Access permission denied */ +#define SQLITE_ABORT 4 /* Callback routine requested an abort */ +#define SQLITE_BUSY 5 /* The database file is locked */ +#define SQLITE_LOCKED 6 /* A table in the database is locked */ +#define SQLITE_NOMEM 7 /* A malloc() failed */ +#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ +#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ +#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ +#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ +#define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */ +#define SQLITE_FULL 13 /* Insertion failed because database is full */ +#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ +#define SQLITE_PROTOCOL 15 /* Database lock protocol error */ +#define SQLITE_EMPTY 16 /* Internal use only */ +#define SQLITE_SCHEMA 17 /* The database schema changed */ +#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ +#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ +#define SQLITE_MISMATCH 20 /* Data type mismatch */ +#define SQLITE_MISUSE 21 /* Library used incorrectly */ +#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ +#define SQLITE_AUTH 23 /* Authorization denied */ +#define SQLITE_FORMAT 24 /* Not used */ +#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ +#define SQLITE_NOTADB 26 /* File opened that is not a database file */ +#define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */ +#define SQLITE_WARNING 28 /* Warnings from sqlite3_log() */ +#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ +#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ +/* end-of-error-codes */ + +/* +** CAPI3REF: Extended Result Codes +** KEYWORDS: {extended result code definitions} +** +** In its default configuration, SQLite API routines return one of 30 integer +** [result codes]. However, experience has shown that many of +** these result codes are too coarse-grained. They do not provide as +** much information about problems as programmers might like. In an effort to +** address this, newer versions of SQLite (version 3.3.8 [dateof:3.3.8] +** and later) include +** support for additional result codes that provide more detailed information +** about errors. These [extended result codes] are enabled or disabled +** on a per database connection basis using the +** [sqlite3_extended_result_codes()] API. Or, the extended code for +** the most recent error can be obtained using +** [sqlite3_extended_errcode()]. +*/ +#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) +#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) +#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) +#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) +#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) +#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) +#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) +#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) +#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6<<8)) +#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7<<8)) +#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8<<8)) +#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9<<8)) +#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10<<8)) +#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11<<8)) +#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12<<8)) +#define SQLITE_IOERR_ACCESS (SQLITE_IOERR | (13<<8)) +#define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14<<8)) +#define SQLITE_IOERR_LOCK (SQLITE_IOERR | (15<<8)) +#define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16<<8)) +#define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17<<8)) +#define SQLITE_IOERR_SHMOPEN (SQLITE_IOERR | (18<<8)) +#define SQLITE_IOERR_SHMSIZE (SQLITE_IOERR | (19<<8)) +#define SQLITE_IOERR_SHMLOCK (SQLITE_IOERR | (20<<8)) +#define SQLITE_IOERR_SHMMAP (SQLITE_IOERR | (21<<8)) +#define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22<<8)) +#define SQLITE_IOERR_DELETE_NOENT (SQLITE_IOERR | (23<<8)) +#define SQLITE_IOERR_MMAP (SQLITE_IOERR | (24<<8)) +#define SQLITE_IOERR_GETTEMPPATH (SQLITE_IOERR | (25<<8)) +#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26<<8)) +#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8)) +#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8)) +#define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29<<8)) +#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) +#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) +#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) +#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) +#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) +#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) +#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) +#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) +#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) +#define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) +#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8)) +#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8)) +#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8)) +#define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8)) +#define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5<<8)) /* Not Used */ +#define SQLITE_CANTOPEN_SYMLINK (SQLITE_CANTOPEN | (6<<8)) +#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) +#define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8)) +#define SQLITE_CORRUPT_INDEX (SQLITE_CORRUPT | (3<<8)) +#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) +#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8)) +#define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8)) +#define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4<<8)) +#define SQLITE_READONLY_CANTINIT (SQLITE_READONLY | (5<<8)) +#define SQLITE_READONLY_DIRECTORY (SQLITE_READONLY | (6<<8)) +#define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2<<8)) +#define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1<<8)) +#define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2<<8)) +#define SQLITE_CONSTRAINT_FOREIGNKEY (SQLITE_CONSTRAINT | (3<<8)) +#define SQLITE_CONSTRAINT_FUNCTION (SQLITE_CONSTRAINT | (4<<8)) +#define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5<<8)) +#define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6<<8)) +#define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7<<8)) +#define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) +#define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) +#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) +#define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT |(11<<8)) +#define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8)) +#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) +#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) +#define SQLITE_NOTICE_RBU (SQLITE_NOTICE | (3<<8)) +#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) +#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) +#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) +#define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8)) /* internal use only */ + +/* +** CAPI3REF: Flags For File Open Operations +** +** These bit values are intended for use in the +** 3rd parameter to the [sqlite3_open_v2()] interface and +** in the 4th parameter to the [sqlite3_vfs.xOpen] method. +** +** Only those flags marked as "Ok for sqlite3_open_v2()" may be +** used as the third argument to the [sqlite3_open_v2()] interface. +** The other flags have historically been ignored by sqlite3_open_v2(), +** though future versions of SQLite might change so that an error is +** raised if any of the disallowed bits are passed into sqlite3_open_v2(). +** Applications should not depend on the historical behavior. +** +** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into +** [sqlite3_open_v2()] does *not* cause the underlying database file +** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into +** [sqlite3_open_v2()] has historically be a no-op and might become an +** error in future versions of SQLite. +*/ +#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_DELETEONCLOSE 0x00000008 /* VFS only */ +#define SQLITE_OPEN_EXCLUSIVE 0x00000010 /* VFS only */ +#define SQLITE_OPEN_AUTOPROXY 0x00000020 /* VFS only */ +#define SQLITE_OPEN_URI 0x00000040 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */ +#define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */ +#define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */ +#define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 /* VFS only */ +#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */ +#define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */ +#define SQLITE_OPEN_SUPER_JOURNAL 0x00004000 /* VFS only */ +#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ +#define SQLITE_OPEN_NOFOLLOW 0x01000000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_EXRESCODE 0x02000000 /* Extended result codes */ + +/* Reserved: 0x00F00000 */ +/* Legacy compatibility: */ +#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */ + + +/* +** CAPI3REF: Device Characteristics +** +** The xDeviceCharacteristics method of the [sqlite3_io_methods] +** object returns an integer which is a vector of these +** bit values expressing I/O characteristics of the mass storage +** device that holds the file that the [sqlite3_io_methods] +** refers to. +** +** The SQLITE_IOCAP_ATOMIC property means that all writes of +** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values +** mean that writes of blocks that are nnn bytes in size and +** are aligned to an address which is an integer multiple of +** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means +** that when data is appended to a file, the data is appended +** first then the size of the file is extended, never the other +** way around. The SQLITE_IOCAP_SEQUENTIAL property means that +** information is written to disk in the same order as calls +** to xWrite(). The SQLITE_IOCAP_POWERSAFE_OVERWRITE property means that +** after reboot following a crash or power loss, the only bytes in a +** file that were written at the application level might have changed +** and that adjacent bytes, even bytes within the same sector are +** guaranteed to be unchanged. The SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN +** flag indicates that a file cannot be deleted when open. The +** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on +** read-only media and cannot be changed even by processes with +** elevated privileges. +** +** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying +** filesystem supports doing multiple write operations atomically when those +** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and +** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. +*/ +#define SQLITE_IOCAP_ATOMIC 0x00000001 +#define SQLITE_IOCAP_ATOMIC512 0x00000002 +#define SQLITE_IOCAP_ATOMIC1K 0x00000004 +#define SQLITE_IOCAP_ATOMIC2K 0x00000008 +#define SQLITE_IOCAP_ATOMIC4K 0x00000010 +#define SQLITE_IOCAP_ATOMIC8K 0x00000020 +#define SQLITE_IOCAP_ATOMIC16K 0x00000040 +#define SQLITE_IOCAP_ATOMIC32K 0x00000080 +#define SQLITE_IOCAP_ATOMIC64K 0x00000100 +#define SQLITE_IOCAP_SAFE_APPEND 0x00000200 +#define SQLITE_IOCAP_SEQUENTIAL 0x00000400 +#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 +#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 +#define SQLITE_IOCAP_IMMUTABLE 0x00002000 +#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 + +/* +** CAPI3REF: File Locking Levels +** +** SQLite uses one of these integer values as the second +** argument to calls it makes to the xLock() and xUnlock() methods +** of an [sqlite3_io_methods] object. These values are ordered from +** lest restrictive to most restrictive. +** +** The argument to xLock() is always SHARED or higher. The argument to +** xUnlock is either SHARED or NONE. +*/ +#define SQLITE_LOCK_NONE 0 /* xUnlock() only */ +#define SQLITE_LOCK_SHARED 1 /* xLock() or xUnlock() */ +#define SQLITE_LOCK_RESERVED 2 /* xLock() only */ +#define SQLITE_LOCK_PENDING 3 /* xLock() only */ +#define SQLITE_LOCK_EXCLUSIVE 4 /* xLock() only */ + +/* +** CAPI3REF: Synchronization Type Flags +** +** When SQLite invokes the xSync() method of an +** [sqlite3_io_methods] object it uses a combination of +** these integer values as the second argument. +** +** When the SQLITE_SYNC_DATAONLY flag is used, it means that the +** sync operation only needs to flush data to mass storage. Inode +** information need not be flushed. If the lower four bits of the flag +** equal SQLITE_SYNC_NORMAL, that means to use normal fsync() semantics. +** If the lower four bits equal SQLITE_SYNC_FULL, that means +** to use Mac OS X style fullsync instead of fsync(). +** +** Do not confuse the SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL flags +** with the [PRAGMA synchronous]=NORMAL and [PRAGMA synchronous]=FULL +** settings. The [synchronous pragma] determines when calls to the +** xSync VFS method occur and applies uniformly across all platforms. +** The SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL flags determine how +** energetic or rigorous or forceful the sync operations are and +** only make a difference on Mac OSX for the default SQLite code. +** (Third-party VFS implementations might also make the distinction +** between SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL, but among the +** operating systems natively supported by SQLite, only Mac OSX +** cares about the difference.) +*/ +#define SQLITE_SYNC_NORMAL 0x00002 +#define SQLITE_SYNC_FULL 0x00003 +#define SQLITE_SYNC_DATAONLY 0x00010 + +/* +** CAPI3REF: OS Interface Open File Handle +** +** An [sqlite3_file] object represents an open file in the +** [sqlite3_vfs | OS interface layer]. Individual OS interface +** implementations will +** want to subclass this object by appending additional fields +** for their own use. The pMethods entry is a pointer to an +** [sqlite3_io_methods] object that defines methods for performing +** I/O operations on the open file. +*/ +typedef struct sqlite3_file sqlite3_file; +struct sqlite3_file { + const struct sqlite3_io_methods *pMethods; /* Methods for an open file */ +}; + +/* +** CAPI3REF: OS Interface File Virtual Methods Object +** +** Every file opened by the [sqlite3_vfs.xOpen] method populates an +** [sqlite3_file] object (or, more commonly, a subclass of the +** [sqlite3_file] object) with a pointer to an instance of this object. +** This object defines the methods used to perform various operations +** against the open file represented by the [sqlite3_file] object. +** +** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element +** to a non-NULL pointer, then the sqlite3_io_methods.xClose method +** may be invoked even if the [sqlite3_vfs.xOpen] reported that it failed. The +** only way to prevent a call to xClose following a failed [sqlite3_vfs.xOpen] +** is for the [sqlite3_vfs.xOpen] to set the sqlite3_file.pMethods element +** to NULL. +** +** The flags argument to xSync may be one of [SQLITE_SYNC_NORMAL] or +** [SQLITE_SYNC_FULL]. The first choice is the normal fsync(). +** The second choice is a Mac OS X style fullsync. The [SQLITE_SYNC_DATAONLY] +** flag may be ORed in to indicate that only the data of the file +** and not its inode needs to be synced. +** +** The integer values to xLock() and xUnlock() are one of +** <ul> +** <li> [SQLITE_LOCK_NONE], +** <li> [SQLITE_LOCK_SHARED], +** <li> [SQLITE_LOCK_RESERVED], +** <li> [SQLITE_LOCK_PENDING], or +** <li> [SQLITE_LOCK_EXCLUSIVE]. +** </ul> +** xLock() upgrades the database file lock. In other words, xLock() moves the +** database file lock in the direction NONE toward EXCLUSIVE. The argument to +** xLock() is always on of SHARED, RESERVED, PENDING, or EXCLUSIVE, never +** SQLITE_LOCK_NONE. If the database file lock is already at or above the +** requested lock, then the call to xLock() is a no-op. +** xUnlock() downgrades the database file lock to either SHARED or NONE. +* If the lock is already at or below the requested lock state, then the call +** to xUnlock() is a no-op. +** The xCheckReservedLock() method checks whether any database connection, +** either in this process or in some other process, is holding a RESERVED, +** PENDING, or EXCLUSIVE lock on the file. It returns true +** if such a lock exists and false otherwise. +** +** The xFileControl() method is a generic interface that allows custom +** VFS implementations to directly control an open file using the +** [sqlite3_file_control()] interface. The second "op" argument is an +** integer opcode. The third argument is a generic pointer intended to +** point to a structure that may contain arguments or space in which to +** write return values. Potential uses for xFileControl() might be +** functions to enable blocking locks with timeouts, to change the +** locking strategy (for example to use dot-file locks), to inquire +** about the status of a lock, or to break stale locks. The SQLite +** core reserves all opcodes less than 100 for its own use. +** A [file control opcodes | list of opcodes] less than 100 is available. +** Applications that define a custom xFileControl method should use opcodes +** greater than 100 to avoid conflicts. VFS implementations should +** return [SQLITE_NOTFOUND] for file control opcodes that they do not +** recognize. +** +** The xSectorSize() method returns the sector size of the +** device that underlies the file. The sector size is the +** minimum write that can be performed without disturbing +** other bytes in the file. The xDeviceCharacteristics() +** method returns a bit vector describing behaviors of the +** underlying device: +** +** <ul> +** <li> [SQLITE_IOCAP_ATOMIC] +** <li> [SQLITE_IOCAP_ATOMIC512] +** <li> [SQLITE_IOCAP_ATOMIC1K] +** <li> [SQLITE_IOCAP_ATOMIC2K] +** <li> [SQLITE_IOCAP_ATOMIC4K] +** <li> [SQLITE_IOCAP_ATOMIC8K] +** <li> [SQLITE_IOCAP_ATOMIC16K] +** <li> [SQLITE_IOCAP_ATOMIC32K] +** <li> [SQLITE_IOCAP_ATOMIC64K] +** <li> [SQLITE_IOCAP_SAFE_APPEND] +** <li> [SQLITE_IOCAP_SEQUENTIAL] +** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] +** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE] +** <li> [SQLITE_IOCAP_IMMUTABLE] +** <li> [SQLITE_IOCAP_BATCH_ATOMIC] +** </ul> +** +** The SQLITE_IOCAP_ATOMIC property means that all writes of +** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values +** mean that writes of blocks that are nnn bytes in size and +** are aligned to an address which is an integer multiple of +** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means +** that when data is appended to a file, the data is appended +** first then the size of the file is extended, never the other +** way around. The SQLITE_IOCAP_SEQUENTIAL property means that +** information is written to disk in the same order as calls +** to xWrite(). +** +** If xRead() returns SQLITE_IOERR_SHORT_READ it must also fill +** in the unread portions of the buffer with zeros. A VFS that +** fails to zero-fill short reads might seem to work. However, +** failure to zero-fill short reads will eventually lead to +** database corruption. +*/ +typedef struct sqlite3_io_methods sqlite3_io_methods; +struct sqlite3_io_methods { + int iVersion; + int (*xClose)(sqlite3_file*); + int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); + int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst); + int (*xTruncate)(sqlite3_file*, sqlite3_int64 size); + int (*xSync)(sqlite3_file*, int flags); + int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize); + int (*xLock)(sqlite3_file*, int); + int (*xUnlock)(sqlite3_file*, int); + int (*xCheckReservedLock)(sqlite3_file*, int *pResOut); + int (*xFileControl)(sqlite3_file*, int op, void *pArg); + int (*xSectorSize)(sqlite3_file*); + int (*xDeviceCharacteristics)(sqlite3_file*); + /* Methods above are valid for version 1 */ + int (*xShmMap)(sqlite3_file*, int iPg, int pgsz, int, void volatile**); + int (*xShmLock)(sqlite3_file*, int offset, int n, int flags); + void (*xShmBarrier)(sqlite3_file*); + int (*xShmUnmap)(sqlite3_file*, int deleteFlag); + /* Methods above are valid for version 2 */ + int (*xFetch)(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); + int (*xUnfetch)(sqlite3_file*, sqlite3_int64 iOfst, void *p); + /* Methods above are valid for version 3 */ + /* Additional methods may be added in future releases */ +}; + +/* +** CAPI3REF: Standard File Control Opcodes +** KEYWORDS: {file control opcodes} {file control opcode} +** +** These integer constants are opcodes for the xFileControl method +** of the [sqlite3_io_methods] object and for the [sqlite3_file_control()] +** interface. +** +** <ul> +** <li>[[SQLITE_FCNTL_LOCKSTATE]] +** The [SQLITE_FCNTL_LOCKSTATE] opcode is used for debugging. This +** opcode causes the xFileControl method to write the current state of +** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED], +** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE]) +** into an integer that the pArg argument points to. +** This capability is only available if SQLite is compiled with [SQLITE_DEBUG]. +** +** <li>[[SQLITE_FCNTL_SIZE_HINT]] +** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS +** layer a hint of how large the database file will grow to be during the +** current transaction. This hint is not guaranteed to be accurate but it +** is often close. The underlying VFS might choose to preallocate database +** file space based on this hint in order to help writes to the database +** file run faster. +** +** <li>[[SQLITE_FCNTL_SIZE_LIMIT]] +** The [SQLITE_FCNTL_SIZE_LIMIT] opcode is used by in-memory VFS that +** implements [sqlite3_deserialize()] to set an upper bound on the size +** of the in-memory database. The argument is a pointer to a [sqlite3_int64]. +** If the integer pointed to is negative, then it is filled in with the +** current limit. Otherwise the limit is set to the larger of the value +** of the integer pointed to and the current database size. The integer +** pointed to is set to the new limit. +** +** <li>[[SQLITE_FCNTL_CHUNK_SIZE]] +** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS +** extends and truncates the database file in chunks of a size specified +** by the user. The fourth argument to [sqlite3_file_control()] should +** point to an integer (type int) containing the new chunk-size to use +** for the nominated database. Allocating database file space in large +** chunks (say 1MB at a time), may reduce file-system fragmentation and +** improve performance on some systems. +** +** <li>[[SQLITE_FCNTL_FILE_POINTER]] +** The [SQLITE_FCNTL_FILE_POINTER] opcode is used to obtain a pointer +** to the [sqlite3_file] object associated with a particular database +** connection. See also [SQLITE_FCNTL_JOURNAL_POINTER]. +** +** <li>[[SQLITE_FCNTL_JOURNAL_POINTER]] +** The [SQLITE_FCNTL_JOURNAL_POINTER] opcode is used to obtain a pointer +** to the [sqlite3_file] object associated with the journal file (either +** the [rollback journal] or the [write-ahead log]) for a particular database +** connection. See also [SQLITE_FCNTL_FILE_POINTER]. +** +** <li>[[SQLITE_FCNTL_SYNC_OMITTED]] +** No longer in use. +** +** <li>[[SQLITE_FCNTL_SYNC]] +** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and +** sent to the VFS immediately before the xSync method is invoked on a +** database file descriptor. Or, if the xSync method is not invoked +** because the user has configured SQLite with +** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place +** of the xSync method. In most cases, the pointer argument passed with +** this file-control is NULL. However, if the database file is being synced +** as part of a multi-database commit, the argument points to a nul-terminated +** string containing the transactions super-journal file name. VFSes that +** do not need this signal should silently ignore this opcode. Applications +** should not call [sqlite3_file_control()] with this opcode as doing so may +** disrupt the operation of the specialized VFSes that do require it. +** +** <li>[[SQLITE_FCNTL_COMMIT_PHASETWO]] +** The [SQLITE_FCNTL_COMMIT_PHASETWO] opcode is generated internally by SQLite +** and sent to the VFS after a transaction has been committed immediately +** but before the database is unlocked. VFSes that do not need this signal +** should silently ignore this opcode. Applications should not call +** [sqlite3_file_control()] with this opcode as doing so may disrupt the +** operation of the specialized VFSes that do require it. +** +** <li>[[SQLITE_FCNTL_WIN32_AV_RETRY]] +** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic +** retry counts and intervals for certain disk I/O operations for the +** windows [VFS] in order to provide robustness in the presence of +** anti-virus programs. By default, the windows VFS will retry file read, +** file write, and file delete operations up to 10 times, with a delay +** of 25 milliseconds before the first retry and with the delay increasing +** by an additional 25 milliseconds with each subsequent retry. This +** opcode allows these two values (10 retries and 25 milliseconds of delay) +** to be adjusted. The values are changed for all database connections +** within the same process. The argument is a pointer to an array of two +** integers where the first integer is the new retry count and the second +** integer is the delay. If either integer is negative, then the setting +** is not changed but instead the prior value of that setting is written +** into the array entry, allowing the current retry settings to be +** interrogated. The zDbName parameter is ignored. +** +** <li>[[SQLITE_FCNTL_PERSIST_WAL]] +** ^The [SQLITE_FCNTL_PERSIST_WAL] opcode is used to set or query the +** persistent [WAL | Write Ahead Log] setting. By default, the auxiliary +** write ahead log ([WAL file]) and shared memory +** files used for transaction control +** are automatically deleted when the latest connection to the database +** closes. Setting persistent WAL mode causes those files to persist after +** close. Persisting the files is useful when other processes that do not +** have write permission on the directory containing the database file want +** to read the database file, as the WAL and shared memory files must exist +** in order for the database to be readable. The fourth parameter to +** [sqlite3_file_control()] for this opcode should be a pointer to an integer. +** That integer is 0 to disable persistent WAL mode or 1 to enable persistent +** WAL mode. If the integer is -1, then it is overwritten with the current +** WAL persistence setting. +** +** <li>[[SQLITE_FCNTL_POWERSAFE_OVERWRITE]] +** ^The [SQLITE_FCNTL_POWERSAFE_OVERWRITE] opcode is used to set or query the +** persistent "powersafe-overwrite" or "PSOW" setting. The PSOW setting +** determines the [SQLITE_IOCAP_POWERSAFE_OVERWRITE] bit of the +** xDeviceCharacteristics methods. The fourth parameter to +** [sqlite3_file_control()] for this opcode should be a pointer to an integer. +** That integer is 0 to disable zero-damage mode or 1 to enable zero-damage +** mode. If the integer is -1, then it is overwritten with the current +** zero-damage mode setting. +** +** <li>[[SQLITE_FCNTL_OVERWRITE]] +** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening +** a write transaction to indicate that, unless it is rolled back for some +** reason, the entire database file will be overwritten by the current +** transaction. This is used by VACUUM operations. +** +** <li>[[SQLITE_FCNTL_VFSNAME]] +** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of +** all [VFSes] in the VFS stack. The names are of all VFS shims and the +** final bottom-level VFS are written into memory obtained from +** [sqlite3_malloc()] and the result is stored in the char* variable +** that the fourth parameter of [sqlite3_file_control()] points to. +** The caller is responsible for freeing the memory when done. As with +** all file-control actions, there is no guarantee that this will actually +** do anything. Callers should initialize the char* variable to a NULL +** pointer in case this file-control is not implemented. This file-control +** is intended for diagnostic use only. +** +** <li>[[SQLITE_FCNTL_VFS_POINTER]] +** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level +** [VFSes] currently in use. ^(The argument X in +** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be +** of type "[sqlite3_vfs] **". This opcodes will set *X +** to a pointer to the top-level VFS.)^ +** ^When there are multiple VFS shims in the stack, this opcode finds the +** upper-most shim only. +** +** <li>[[SQLITE_FCNTL_PRAGMA]] +** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] +** file control is sent to the open [sqlite3_file] object corresponding +** to the database file to which the pragma statement refers. ^The argument +** to the [SQLITE_FCNTL_PRAGMA] file control is an array of +** pointers to strings (char**) in which the second element of the array +** is the name of the pragma and the third element is the argument to the +** pragma or NULL if the pragma has no argument. ^The handler for an +** [SQLITE_FCNTL_PRAGMA] file control can optionally make the first element +** of the char** argument point to a string obtained from [sqlite3_mprintf()] +** or the equivalent and that string will become the result of the pragma or +** the error message if the pragma fails. ^If the +** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal +** [PRAGMA] processing continues. ^If the [SQLITE_FCNTL_PRAGMA] +** file control returns [SQLITE_OK], then the parser assumes that the +** VFS has handled the PRAGMA itself and the parser generates a no-op +** prepared statement if result string is NULL, or that returns a copy +** of the result string if the string is non-NULL. +** ^If the [SQLITE_FCNTL_PRAGMA] file control returns +** any result code other than [SQLITE_OK] or [SQLITE_NOTFOUND], that means +** that the VFS encountered an error while handling the [PRAGMA] and the +** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] +** file control occurs at the beginning of pragma statement analysis and so +** it is able to override built-in [PRAGMA] statements. +** +** <li>[[SQLITE_FCNTL_BUSYHANDLER]] +** ^The [SQLITE_FCNTL_BUSYHANDLER] +** file-control may be invoked by SQLite on the database file handle +** shortly after it is opened in order to provide a custom VFS with access +** to the connection's busy-handler callback. The argument is of type (void**) +** - an array of two (void *) values. The first (void *) actually points +** to a function of type (int (*)(void *)). In order to invoke the connection's +** busy-handler, this function should be invoked with the second (void *) in +** the array as the only argument. If it returns non-zero, then the operation +** should be retried. If it returns zero, the custom VFS should abandon the +** current operation. +** +** <li>[[SQLITE_FCNTL_TEMPFILENAME]] +** ^Applications can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control +** to have SQLite generate a +** temporary filename using the same algorithm that is followed to generate +** temporary filenames for TEMP tables and other internal uses. The +** argument should be a char** which will be filled with the filename +** written into memory obtained from [sqlite3_malloc()]. The caller should +** invoke [sqlite3_free()] on the result to avoid a memory leak. +** +** <li>[[SQLITE_FCNTL_MMAP_SIZE]] +** The [SQLITE_FCNTL_MMAP_SIZE] file control is used to query or set the +** maximum number of bytes that will be used for memory-mapped I/O. +** The argument is a pointer to a value of type sqlite3_int64 that +** is an advisory maximum number of bytes in the file to memory map. The +** pointer is overwritten with the old value. The limit is not changed if +** the value originally pointed to is negative, and so the current limit +** can be queried by passing in a pointer to a negative number. This +** file-control is used internally to implement [PRAGMA mmap_size]. +** +** <li>[[SQLITE_FCNTL_TRACE]] +** The [SQLITE_FCNTL_TRACE] file control provides advisory information +** to the VFS about what the higher layers of the SQLite stack are doing. +** This file control is used by some VFS activity tracing [shims]. +** The argument is a zero-terminated string. Higher layers in the +** SQLite stack may generate instances of this file control if +** the [SQLITE_USE_FCNTL_TRACE] compile-time option is enabled. +** +** <li>[[SQLITE_FCNTL_HAS_MOVED]] +** The [SQLITE_FCNTL_HAS_MOVED] file control interprets its argument as a +** pointer to an integer and it writes a boolean into that integer depending +** on whether or not the file has been renamed, moved, or deleted since it +** was first opened. +** +** <li>[[SQLITE_FCNTL_WIN32_GET_HANDLE]] +** The [SQLITE_FCNTL_WIN32_GET_HANDLE] opcode can be used to obtain the +** underlying native file handle associated with a file handle. This file +** control interprets its argument as a pointer to a native file handle and +** writes the resulting value there. +** +** <li>[[SQLITE_FCNTL_WIN32_SET_HANDLE]] +** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This +** opcode causes the xFileControl method to swap the file handle with the one +** pointed to by the pArg argument. This capability is used during testing +** and only needs to be supported when SQLITE_TEST is defined. +** +** <li>[[SQLITE_FCNTL_WAL_BLOCK]] +** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might +** be advantageous to block on the next WAL lock if the lock is not immediately +** available. The WAL subsystem issues this signal during rare +** circumstances in order to fix a problem with priority inversion. +** Applications should <em>not</em> use this file-control. +** +** <li>[[SQLITE_FCNTL_ZIPVFS]] +** The [SQLITE_FCNTL_ZIPVFS] opcode is implemented by zipvfs only. All other +** VFS should return SQLITE_NOTFOUND for this opcode. +** +** <li>[[SQLITE_FCNTL_RBU]] +** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by +** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for +** this opcode. +** +** <li>[[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]] +** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then +** the file descriptor is placed in "batch write mode", which +** means all subsequent write operations will be deferred and done +** atomically at the next [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. Systems +** that do not support batch atomic writes will return SQLITE_NOTFOUND. +** ^Following a successful SQLITE_FCNTL_BEGIN_ATOMIC_WRITE and prior to +** the closing [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] or +** [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE], SQLite will make +** no VFS interface calls on the same [sqlite3_file] file descriptor +** except for calls to the xWrite method and the xFileControl method +** with [SQLITE_FCNTL_SIZE_HINT]. +** +** <li>[[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]] +** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write +** operations since the previous successful call to +** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically. +** This file control returns [SQLITE_OK] if and only if the writes were +** all performed successfully and have been committed to persistent storage. +** ^Regardless of whether or not it is successful, this file control takes +** the file descriptor out of batch write mode so that all subsequent +** write operations are independent. +** ^SQLite will never invoke SQLITE_FCNTL_COMMIT_ATOMIC_WRITE without +** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]. +** +** <li>[[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]] +** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write +** operations since the previous successful call to +** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back. +** ^This file control takes the file descriptor out of batch write mode +** so that all subsequent write operations are independent. +** ^SQLite will never invoke SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE without +** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]. +** +** <li>[[SQLITE_FCNTL_LOCK_TIMEOUT]] +** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode is used to configure a VFS +** to block for up to M milliseconds before failing when attempting to +** obtain a file lock using the xLock or xShmLock methods of the VFS. +** The parameter is a pointer to a 32-bit signed integer that contains +** the value that M is to be set to. Before returning, the 32-bit signed +** integer is overwritten with the previous value of M. +** +** <li>[[SQLITE_FCNTL_DATA_VERSION]] +** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to +** a database file. The argument is a pointer to a 32-bit unsigned integer. +** The "data version" for the pager is written into the pointer. The +** "data version" changes whenever any change occurs to the corresponding +** database file, either through SQL statements on the same database +** connection or through transactions committed by separate database +** connections possibly in other processes. The [sqlite3_total_changes()] +** interface can be used to find if any database on the connection has changed, +** but that interface responds to changes on TEMP as well as MAIN and does +** not provide a mechanism to detect changes to MAIN only. Also, the +** [sqlite3_total_changes()] interface responds to internal changes only and +** omits changes made by other database connections. The +** [PRAGMA data_version] command provides a mechanism to detect changes to +** a single attached database that occur due to other database connections, +** but omits changes implemented by the database connection on which it is +** called. This file control is the only mechanism to detect changes that +** happen either internally or externally and that are associated with +** a particular attached database. +** +** <li>[[SQLITE_FCNTL_CKPT_START]] +** The [SQLITE_FCNTL_CKPT_START] opcode is invoked from within a checkpoint +** in wal mode before the client starts to copy pages from the wal +** file to the database file. +** +** <li>[[SQLITE_FCNTL_CKPT_DONE]] +** The [SQLITE_FCNTL_CKPT_DONE] opcode is invoked from within a checkpoint +** in wal mode after the client has finished copying pages from the wal +** file to the database file, but before the *-shm file is updated to +** record the fact that the pages have been checkpointed. +** +** <li>[[SQLITE_FCNTL_EXTERNAL_READER]] +** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect +** whether or not there is a database client in another process with a wal-mode +** transaction open on the database or not. It is only available on unix.The +** (void*) argument passed with this file-control should be a pointer to a +** value of type (int). The integer value is set to 1 if the database is a wal +** mode database and there exists at least one client in another process that +** currently has an SQL transaction open on the database. It is set to 0 if +** the database is not a wal-mode db, or if there is no such connection in any +** other process. This opcode cannot be used to detect transactions opened +** by clients within the current process, only within other processes. +** +** <li>[[SQLITE_FCNTL_CKSM_FILE]] +** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use internally by the +** [checksum VFS shim] only. +** +** <li>[[SQLITE_FCNTL_RESET_CACHE]] +** If there is currently no transaction open on the database, and the +** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control +** purges the contents of the in-memory page cache. If there is an open +** transaction, or if the db is a temp-db, this opcode is a no-op, not an error. +** </ul> +*/ +#define SQLITE_FCNTL_LOCKSTATE 1 +#define SQLITE_FCNTL_GET_LOCKPROXYFILE 2 +#define SQLITE_FCNTL_SET_LOCKPROXYFILE 3 +#define SQLITE_FCNTL_LAST_ERRNO 4 +#define SQLITE_FCNTL_SIZE_HINT 5 +#define SQLITE_FCNTL_CHUNK_SIZE 6 +#define SQLITE_FCNTL_FILE_POINTER 7 +#define SQLITE_FCNTL_SYNC_OMITTED 8 +#define SQLITE_FCNTL_WIN32_AV_RETRY 9 +#define SQLITE_FCNTL_PERSIST_WAL 10 +#define SQLITE_FCNTL_OVERWRITE 11 +#define SQLITE_FCNTL_VFSNAME 12 +#define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 +#define SQLITE_FCNTL_PRAGMA 14 +#define SQLITE_FCNTL_BUSYHANDLER 15 +#define SQLITE_FCNTL_TEMPFILENAME 16 +#define SQLITE_FCNTL_MMAP_SIZE 18 +#define SQLITE_FCNTL_TRACE 19 +#define SQLITE_FCNTL_HAS_MOVED 20 +#define SQLITE_FCNTL_SYNC 21 +#define SQLITE_FCNTL_COMMIT_PHASETWO 22 +#define SQLITE_FCNTL_WIN32_SET_HANDLE 23 +#define SQLITE_FCNTL_WAL_BLOCK 24 +#define SQLITE_FCNTL_ZIPVFS 25 +#define SQLITE_FCNTL_RBU 26 +#define SQLITE_FCNTL_VFS_POINTER 27 +#define SQLITE_FCNTL_JOURNAL_POINTER 28 +#define SQLITE_FCNTL_WIN32_GET_HANDLE 29 +#define SQLITE_FCNTL_PDB 30 +#define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31 +#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32 +#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33 +#define SQLITE_FCNTL_LOCK_TIMEOUT 34 +#define SQLITE_FCNTL_DATA_VERSION 35 +#define SQLITE_FCNTL_SIZE_LIMIT 36 +#define SQLITE_FCNTL_CKPT_DONE 37 +#define SQLITE_FCNTL_RESERVE_BYTES 38 +#define SQLITE_FCNTL_CKPT_START 39 +#define SQLITE_FCNTL_EXTERNAL_READER 40 +#define SQLITE_FCNTL_CKSM_FILE 41 +#define SQLITE_FCNTL_RESET_CACHE 42 + +/* deprecated names */ +#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE +#define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE +#define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO + + +/* +** CAPI3REF: Mutex Handle +** +** The mutex module within SQLite defines [sqlite3_mutex] to be an +** abstract type for a mutex object. The SQLite core never looks +** at the internal representation of an [sqlite3_mutex]. It only +** deals with pointers to the [sqlite3_mutex] object. +** +** Mutexes are created using [sqlite3_mutex_alloc()]. +*/ +typedef struct sqlite3_mutex sqlite3_mutex; + +/* +** CAPI3REF: Loadable Extension Thunk +** +** A pointer to the opaque sqlite3_api_routines structure is passed as +** the third parameter to entry points of [loadable extensions]. This +** structure must be typedefed in order to work around compiler warnings +** on some platforms. +*/ +typedef struct sqlite3_api_routines sqlite3_api_routines; + +/* +** CAPI3REF: File Name +** +** Type [sqlite3_filename] is used by SQLite to pass filenames to the +** xOpen method of a [VFS]. It may be cast to (const char*) and treated +** as a normal, nul-terminated, UTF-8 buffer containing the filename, but +** may also be passed to special APIs such as: +** +** <ul> +** <li> sqlite3_filename_database() +** <li> sqlite3_filename_journal() +** <li> sqlite3_filename_wal() +** <li> sqlite3_uri_parameter() +** <li> sqlite3_uri_boolean() +** <li> sqlite3_uri_int64() +** <li> sqlite3_uri_key() +** </ul> +*/ +typedef const char *sqlite3_filename; + +/* +** CAPI3REF: OS Interface Object +** +** An instance of the sqlite3_vfs object defines the interface between +** the SQLite core and the underlying operating system. The "vfs" +** in the name of the object stands for "virtual file system". See +** the [VFS | VFS documentation] for further information. +** +** The VFS interface is sometimes extended by adding new methods onto +** the end. Each time such an extension occurs, the iVersion field +** is incremented. The iVersion value started out as 1 in +** SQLite [version 3.5.0] on [dateof:3.5.0], then increased to 2 +** with SQLite [version 3.7.0] on [dateof:3.7.0], and then increased +** to 3 with SQLite [version 3.7.6] on [dateof:3.7.6]. Additional fields +** may be appended to the sqlite3_vfs object and the iVersion value +** may increase again in future versions of SQLite. +** Note that due to an oversight, the structure +** of the sqlite3_vfs object changed in the transition from +** SQLite [version 3.5.9] to [version 3.6.0] on [dateof:3.6.0] +** and yet the iVersion field was not increased. +** +** The szOsFile field is the size of the subclassed [sqlite3_file] +** structure used by this VFS. mxPathname is the maximum length of +** a pathname in this VFS. +** +** Registered sqlite3_vfs objects are kept on a linked list formed by +** the pNext pointer. The [sqlite3_vfs_register()] +** and [sqlite3_vfs_unregister()] interfaces manage this list +** in a thread-safe way. The [sqlite3_vfs_find()] interface +** searches the list. Neither the application code nor the VFS +** implementation should use the pNext pointer. +** +** The pNext field is the only field in the sqlite3_vfs +** structure that SQLite will ever modify. SQLite will only access +** or modify this field while holding a particular static mutex. +** The application should never modify anything within the sqlite3_vfs +** object once the object has been registered. +** +** The zName field holds the name of the VFS module. The name must +** be unique across all VFS modules. +** +** [[sqlite3_vfs.xOpen]] +** ^SQLite guarantees that the zFilename parameter to xOpen +** is either a NULL pointer or string obtained +** from xFullPathname() with an optional suffix added. +** ^If a suffix is added to the zFilename parameter, it will +** consist of a single "-" character followed by no more than +** 11 alphanumeric and/or "-" characters. +** ^SQLite further guarantees that +** the string will be valid and unchanged until xClose() is +** called. Because of the previous sentence, +** the [sqlite3_file] can safely store a pointer to the +** filename if it needs to remember the filename for some reason. +** If the zFilename parameter to xOpen is a NULL pointer then xOpen +** must invent its own temporary name for the file. ^Whenever the +** xFilename parameter is NULL it will also be the case that the +** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE]. +** +** The flags argument to xOpen() includes all bits set in +** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()] +** or [sqlite3_open16()] is used, then flags includes at least +** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. +** If xOpen() opens a file read-only then it sets *pOutFlags to +** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set. +** +** ^(SQLite will also add one of the following flags to the xOpen() +** call, depending on the object being opened: +** +** <ul> +** <li> [SQLITE_OPEN_MAIN_DB] +** <li> [SQLITE_OPEN_MAIN_JOURNAL] +** <li> [SQLITE_OPEN_TEMP_DB] +** <li> [SQLITE_OPEN_TEMP_JOURNAL] +** <li> [SQLITE_OPEN_TRANSIENT_DB] +** <li> [SQLITE_OPEN_SUBJOURNAL] +** <li> [SQLITE_OPEN_SUPER_JOURNAL] +** <li> [SQLITE_OPEN_WAL] +** </ul>)^ +** +** The file I/O implementation can use the object type flags to +** change the way it deals with files. For example, an application +** that does not care about crash recovery or rollback might make +** the open of a journal file a no-op. Writes to this journal would +** also be no-ops, and any attempt to read the journal would return +** SQLITE_IOERR. Or the implementation might recognize that a database +** file will be doing page-aligned sector reads and writes in a random +** order and set up its I/O subsystem accordingly. +** +** SQLite might also add one of the following flags to the xOpen method: +** +** <ul> +** <li> [SQLITE_OPEN_DELETEONCLOSE] +** <li> [SQLITE_OPEN_EXCLUSIVE] +** </ul> +** +** The [SQLITE_OPEN_DELETEONCLOSE] flag means the file should be +** deleted when it is closed. ^The [SQLITE_OPEN_DELETEONCLOSE] +** will be set for TEMP databases and their journals, transient +** databases, and subjournals. +** +** ^The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction +** with the [SQLITE_OPEN_CREATE] flag, which are both directly +** analogous to the O_EXCL and O_CREAT flags of the POSIX open() +** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the +** SQLITE_OPEN_CREATE, is used to indicate that file should always +** be created, and that it is an error if it already exists. +** It is <i>not</i> used to indicate the file should be opened +** for exclusive access. +** +** ^At least szOsFile bytes of memory are allocated by SQLite +** to hold the [sqlite3_file] structure passed as the third +** argument to xOpen. The xOpen method does not have to +** allocate the structure; it should just fill it in. Note that +** the xOpen method must set the sqlite3_file.pMethods to either +** a valid [sqlite3_io_methods] object or to NULL. xOpen must do +** this even if the open fails. SQLite expects that the sqlite3_file.pMethods +** element will be valid after xOpen returns regardless of the success +** or failure of the xOpen call. +** +** [[sqlite3_vfs.xAccess]] +** ^The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS] +** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to +** test whether a file is readable and writable, or [SQLITE_ACCESS_READ] +** to test whether a file is at least readable. The SQLITE_ACCESS_READ +** flag is never actually used and is not implemented in the built-in +** VFSes of SQLite. The file is named by the second argument and can be a +** directory. The xAccess method returns [SQLITE_OK] on success or some +** non-zero error code if there is an I/O error or if the name of +** the file given in the second argument is illegal. If SQLITE_OK +** is returned, then non-zero or zero is written into *pResOut to indicate +** whether or not the file is accessible. +** +** ^SQLite will always allocate at least mxPathname+1 bytes for the +** output buffer xFullPathname. The exact size of the output buffer +** is also passed as a parameter to both methods. If the output buffer +** is not large enough, [SQLITE_CANTOPEN] should be returned. Since this is +** handled as a fatal error by SQLite, vfs implementations should endeavor +** to prevent this by setting mxPathname to a sufficiently large value. +** +** The xRandomness(), xSleep(), xCurrentTime(), and xCurrentTimeInt64() +** interfaces are not strictly a part of the filesystem, but they are +** included in the VFS structure for completeness. +** The xRandomness() function attempts to return nBytes bytes +** of good-quality randomness into zOut. The return value is +** the actual number of bytes of randomness obtained. +** The xSleep() method causes the calling thread to sleep for at +** least the number of microseconds given. ^The xCurrentTime() +** method returns a Julian Day Number for the current date and time as +** a floating point value. +** ^The xCurrentTimeInt64() method returns, as an integer, the Julian +** Day Number multiplied by 86400000 (the number of milliseconds in +** a 24-hour day). +** ^SQLite will use the xCurrentTimeInt64() method to get the current +** date and time if that method is available (if iVersion is 2 or +** greater and the function pointer is not NULL) and will fall back +** to xCurrentTime() if xCurrentTimeInt64() is unavailable. +** +** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces +** are not used by the SQLite core. These optional interfaces are provided +** by some VFSes to facilitate testing of the VFS code. By overriding +** system calls with functions under its control, a test program can +** simulate faults and error conditions that would otherwise be difficult +** or impossible to induce. The set of system calls that can be overridden +** varies from one VFS to another, and from one version of the same VFS to the +** next. Applications that use these interfaces must be prepared for any +** or all of these interfaces to be NULL or for their behavior to change +** from one release to the next. Applications must not attempt to access +** any of these methods if the iVersion of the VFS is less than 3. +*/ +typedef struct sqlite3_vfs sqlite3_vfs; +typedef void (*sqlite3_syscall_ptr)(void); +struct sqlite3_vfs { + int iVersion; /* Structure version number (currently 3) */ + int szOsFile; /* Size of subclassed sqlite3_file */ + int mxPathname; /* Maximum file pathname length */ + sqlite3_vfs *pNext; /* Next registered VFS */ + const char *zName; /* Name of this virtual file system */ + void *pAppData; /* Pointer to application-specific data */ + int (*xOpen)(sqlite3_vfs*, sqlite3_filename zName, sqlite3_file*, + int flags, int *pOutFlags); + int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir); + int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut); + int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut); + void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename); + void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg); + void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void); + void (*xDlClose)(sqlite3_vfs*, void*); + int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut); + int (*xSleep)(sqlite3_vfs*, int microseconds); + int (*xCurrentTime)(sqlite3_vfs*, double*); + int (*xGetLastError)(sqlite3_vfs*, int, char *); + /* + ** The methods above are in version 1 of the sqlite_vfs object + ** definition. Those that follow are added in version 2 or later + */ + int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*); + /* + ** The methods above are in versions 1 and 2 of the sqlite_vfs object. + ** Those below are for version 3 and greater. + */ + int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr); + sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName); + const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName); + /* + ** The methods above are in versions 1 through 3 of the sqlite_vfs object. + ** New fields may be appended in future versions. The iVersion + ** value will increment whenever this happens. + */ +}; + +/* +** CAPI3REF: Flags for the xAccess VFS method +** +** These integer constants can be used as the third parameter to +** the xAccess method of an [sqlite3_vfs] object. They determine +** what kind of permissions the xAccess method is looking for. +** With SQLITE_ACCESS_EXISTS, the xAccess method +** simply checks whether the file exists. +** With SQLITE_ACCESS_READWRITE, the xAccess method +** checks whether the named directory is both readable and writable +** (in other words, if files can be added, removed, and renamed within +** the directory). +** The SQLITE_ACCESS_READWRITE constant is currently used only by the +** [temp_store_directory pragma], though this could change in a future +** release of SQLite. +** With SQLITE_ACCESS_READ, the xAccess method +** checks whether the file is readable. The SQLITE_ACCESS_READ constant is +** currently unused, though it might be used in a future release of +** SQLite. +*/ +#define SQLITE_ACCESS_EXISTS 0 +#define SQLITE_ACCESS_READWRITE 1 /* Used by PRAGMA temp_store_directory */ +#define SQLITE_ACCESS_READ 2 /* Unused */ + +/* +** CAPI3REF: Flags for the xShmLock VFS method +** +** These integer constants define the various locking operations +** allowed by the xShmLock method of [sqlite3_io_methods]. The +** following are the only legal combinations of flags to the +** xShmLock method: +** +** <ul> +** <li> SQLITE_SHM_LOCK | SQLITE_SHM_SHARED +** <li> SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE +** <li> SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED +** <li> SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE +** </ul> +** +** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as +** was given on the corresponding lock. +** +** The xShmLock method can transition between unlocked and SHARED or +** between unlocked and EXCLUSIVE. It cannot transition between SHARED +** and EXCLUSIVE. +*/ +#define SQLITE_SHM_UNLOCK 1 +#define SQLITE_SHM_LOCK 2 +#define SQLITE_SHM_SHARED 4 +#define SQLITE_SHM_EXCLUSIVE 8 + +/* +** CAPI3REF: Maximum xShmLock index +** +** The xShmLock method on [sqlite3_io_methods] may use values +** between 0 and this upper bound as its "offset" argument. +** The SQLite core will never attempt to acquire or release a +** lock outside of this range +*/ +#define SQLITE_SHM_NLOCK 8 + + +/* +** CAPI3REF: Initialize The SQLite Library +** +** ^The sqlite3_initialize() routine initializes the +** SQLite library. ^The sqlite3_shutdown() routine +** deallocates any resources that were allocated by sqlite3_initialize(). +** These routines are designed to aid in process initialization and +** shutdown on embedded systems. Workstation applications using +** SQLite normally do not need to invoke either of these routines. +** +** A call to sqlite3_initialize() is an "effective" call if it is +** the first time sqlite3_initialize() is invoked during the lifetime of +** the process, or if it is the first time sqlite3_initialize() is invoked +** following a call to sqlite3_shutdown(). ^(Only an effective call +** of sqlite3_initialize() does any initialization. All other calls +** are harmless no-ops.)^ +** +** A call to sqlite3_shutdown() is an "effective" call if it is the first +** call to sqlite3_shutdown() since the last sqlite3_initialize(). ^(Only +** an effective call to sqlite3_shutdown() does any deinitialization. +** All other valid calls to sqlite3_shutdown() are harmless no-ops.)^ +** +** The sqlite3_initialize() interface is threadsafe, but sqlite3_shutdown() +** is not. The sqlite3_shutdown() interface must only be called from a +** single thread. All open [database connections] must be closed and all +** other SQLite resources must be deallocated prior to invoking +** sqlite3_shutdown(). +** +** Among other things, ^sqlite3_initialize() will invoke +** sqlite3_os_init(). Similarly, ^sqlite3_shutdown() +** will invoke sqlite3_os_end(). +** +** ^The sqlite3_initialize() routine returns [SQLITE_OK] on success. +** ^If for some reason, sqlite3_initialize() is unable to initialize +** the library (perhaps it is unable to allocate a needed resource such +** as a mutex) it returns an [error code] other than [SQLITE_OK]. +** +** ^The sqlite3_initialize() routine is called internally by many other +** SQLite interfaces so that an application usually does not need to +** invoke sqlite3_initialize() directly. For example, [sqlite3_open()] +** calls sqlite3_initialize() so the SQLite library will be automatically +** initialized when [sqlite3_open()] is called if it has not be initialized +** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT] +** compile-time option, then the automatic calls to sqlite3_initialize() +** are omitted and the application must call sqlite3_initialize() directly +** prior to using any other SQLite interface. For maximum portability, +** it is recommended that applications always invoke sqlite3_initialize() +** directly prior to using any other SQLite interface. Future releases +** of SQLite may require this. In other words, the behavior exhibited +** when SQLite is compiled with [SQLITE_OMIT_AUTOINIT] might become the +** default behavior in some future release of SQLite. +** +** The sqlite3_os_init() routine does operating-system specific +** initialization of the SQLite library. The sqlite3_os_end() +** routine undoes the effect of sqlite3_os_init(). Typical tasks +** performed by these routines include allocation or deallocation +** of static resources, initialization of global variables, +** setting up a default [sqlite3_vfs] module, or setting up +** a default configuration using [sqlite3_config()]. +** +** The application should never invoke either sqlite3_os_init() +** or sqlite3_os_end() directly. The application should only invoke +** sqlite3_initialize() and sqlite3_shutdown(). The sqlite3_os_init() +** interface is called automatically by sqlite3_initialize() and +** sqlite3_os_end() is called by sqlite3_shutdown(). Appropriate +** implementations for sqlite3_os_init() and sqlite3_os_end() +** are built into SQLite when it is compiled for Unix, Windows, or OS/2. +** When [custom builds | built for other platforms] +** (using the [SQLITE_OS_OTHER=1] compile-time +** option) the application must supply a suitable implementation for +** sqlite3_os_init() and sqlite3_os_end(). An application-supplied +** implementation of sqlite3_os_init() or sqlite3_os_end() +** must return [SQLITE_OK] on success and some other [error code] upon +** failure. +*/ +SQLITE_API int sqlite3_initialize(void); +SQLITE_API int sqlite3_shutdown(void); +SQLITE_API int sqlite3_os_init(void); +SQLITE_API int sqlite3_os_end(void); + +/* +** CAPI3REF: Configuring The SQLite Library +** +** The sqlite3_config() interface is used to make global configuration +** changes to SQLite in order to tune SQLite to the specific needs of +** the application. The default configuration is recommended for most +** applications and so this routine is usually not necessary. It is +** provided to support rare applications with unusual needs. +** +** <b>The sqlite3_config() interface is not threadsafe. The application +** must ensure that no other SQLite interfaces are invoked by other +** threads while sqlite3_config() is running.</b> +** +** The first argument to sqlite3_config() is an integer +** [configuration option] that determines +** what property of SQLite is to be configured. Subsequent arguments +** vary depending on the [configuration option] +** in the first argument. +** +** For most configuration options, the sqlite3_config() interface +** may only be invoked prior to library initialization using +** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. +** The exceptional configuration options that may be invoked at any time +** are called "anytime configuration options". +** ^If sqlite3_config() is called after [sqlite3_initialize()] and before +** [sqlite3_shutdown()] with a first argument that is not an anytime +** configuration option, then the sqlite3_config() call will return SQLITE_MISUSE. +** Note, however, that ^sqlite3_config() can be called as part of the +** implementation of an application-defined [sqlite3_os_init()]. +** +** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. +** ^If the option is unknown or SQLite is unable to set the option +** then this routine returns a non-zero [error code]. +*/ +SQLITE_API int sqlite3_config(int, ...); + +/* +** CAPI3REF: Configure database connections +** METHOD: sqlite3 +** +** The sqlite3_db_config() interface is used to make configuration +** changes to a [database connection]. The interface is similar to +** [sqlite3_config()] except that the changes apply to a single +** [database connection] (specified in the first argument). +** +** The second argument to sqlite3_db_config(D,V,...) is the +** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code +** that indicates what aspect of the [database connection] is being configured. +** Subsequent arguments vary depending on the configuration verb. +** +** ^Calls to sqlite3_db_config() return SQLITE_OK if and only if +** the call is considered successful. +*/ +SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...); + +/* +** CAPI3REF: Memory Allocation Routines +** +** An instance of this object defines the interface between SQLite +** and low-level memory allocation routines. +** +** This object is used in only one place in the SQLite interface. +** A pointer to an instance of this object is the argument to +** [sqlite3_config()] when the configuration option is +** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. +** By creating an instance of this object +** and passing it to [sqlite3_config]([SQLITE_CONFIG_MALLOC]) +** during configuration, an application can specify an alternative +** memory allocation subsystem for SQLite to use for all of its +** dynamic memory needs. +** +** Note that SQLite comes with several [built-in memory allocators] +** that are perfectly adequate for the overwhelming majority of applications +** and that this object is only useful to a tiny minority of applications +** with specialized memory allocation requirements. This object is +** also used during testing of SQLite in order to specify an alternative +** memory allocator that simulates memory out-of-memory conditions in +** order to verify that SQLite recovers gracefully from such +** conditions. +** +** The xMalloc, xRealloc, and xFree methods must work like the +** malloc(), realloc() and free() functions from the standard C library. +** ^SQLite guarantees that the second argument to +** xRealloc is always a value returned by a prior call to xRoundup. +** +** xSize should return the allocated size of a memory allocation +** previously obtained from xMalloc or xRealloc. The allocated size +** is always at least as big as the requested size but may be larger. +** +** The xRoundup method returns what would be the allocated size of +** a memory allocation given a particular requested size. Most memory +** allocators round up memory allocations at least to the next multiple +** of 8. Some allocators round up to a larger multiple or to a power of 2. +** Every memory allocation request coming in through [sqlite3_malloc()] +** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, +** that causes the corresponding memory allocation to fail. +** +** The xInit method initializes the memory allocator. For example, +** it might allocate any required mutexes or initialize internal data +** structures. The xShutdown method is invoked (indirectly) by +** [sqlite3_shutdown()] and should deallocate any resources acquired +** by xInit. The pAppData pointer is used as the only parameter to +** xInit and xShutdown. +** +** SQLite holds the [SQLITE_MUTEX_STATIC_MAIN] mutex when it invokes +** the xInit method, so the xInit method need not be threadsafe. The +** xShutdown method is only called from [sqlite3_shutdown()] so it does +** not need to be threadsafe either. For all other methods, SQLite +** holds the [SQLITE_MUTEX_STATIC_MEM] mutex as long as the +** [SQLITE_CONFIG_MEMSTATUS] configuration option is turned on (which +** it is by default) and so the methods are automatically serialized. +** However, if [SQLITE_CONFIG_MEMSTATUS] is disabled, then the other +** methods must be threadsafe or else make their own arrangements for +** serialization. +** +** SQLite will never invoke xInit() more than once without an intervening +** call to xShutdown(). +*/ +typedef struct sqlite3_mem_methods sqlite3_mem_methods; +struct sqlite3_mem_methods { + void *(*xMalloc)(int); /* Memory allocation function */ + void (*xFree)(void*); /* Free a prior allocation */ + void *(*xRealloc)(void*,int); /* Resize an allocation */ + int (*xSize)(void*); /* Return the size of an allocation */ + int (*xRoundup)(int); /* Round up request size to allocation size */ + int (*xInit)(void*); /* Initialize the memory allocator */ + void (*xShutdown)(void*); /* Deinitialize the memory allocator */ + void *pAppData; /* Argument to xInit() and xShutdown() */ +}; + +/* +** CAPI3REF: Configuration Options +** KEYWORDS: {configuration option} +** +** These constants are the available integer configuration options that +** can be passed as the first argument to the [sqlite3_config()] interface. +** +** Most of the configuration options for sqlite3_config() +** will only work if invoked prior to [sqlite3_initialize()] or after +** [sqlite3_shutdown()]. The few exceptions to this rule are called +** "anytime configuration options". +** ^Calling [sqlite3_config()] with a first argument that is not an +** anytime configuration option in between calls to [sqlite3_initialize()] and +** [sqlite3_shutdown()] is a no-op that returns SQLITE_MISUSE. +** +** The set of anytime configuration options can change (by insertions +** and/or deletions) from one release of SQLite to the next. +** As of SQLite version 3.42.0, the complete set of anytime configuration +** options is: +** <ul> +** <li> SQLITE_CONFIG_LOG +** <li> SQLITE_CONFIG_PCACHE_HDRSZ +** </ul> +** +** New configuration options may be added in future releases of SQLite. +** Existing configuration options might be discontinued. Applications +** should check the return code from [sqlite3_config()] to make sure that +** the call worked. The [sqlite3_config()] interface will return a +** non-zero [error code] if a discontinued or unsupported configuration option +** is invoked. +** +** <dl> +** [[SQLITE_CONFIG_SINGLETHREAD]] <dt>SQLITE_CONFIG_SINGLETHREAD</dt> +** <dd>There are no arguments to this option. ^This option sets the +** [threading mode] to Single-thread. In other words, it disables +** all mutexing and puts SQLite into a mode where it can only be used +** by a single thread. ^If SQLite is compiled with +** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then +** it is not possible to change the [threading mode] from its default +** value of Single-thread and so [sqlite3_config()] will return +** [SQLITE_ERROR] if called with the SQLITE_CONFIG_SINGLETHREAD +** configuration option.</dd> +** +** [[SQLITE_CONFIG_MULTITHREAD]] <dt>SQLITE_CONFIG_MULTITHREAD</dt> +** <dd>There are no arguments to this option. ^This option sets the +** [threading mode] to Multi-thread. In other words, it disables +** mutexing on [database connection] and [prepared statement] objects. +** The application is responsible for serializing access to +** [database connections] and [prepared statements]. But other mutexes +** are enabled so that SQLite will be safe to use in a multi-threaded +** environment as long as no two threads attempt to use the same +** [database connection] at the same time. ^If SQLite is compiled with +** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then +** it is not possible to set the Multi-thread [threading mode] and +** [sqlite3_config()] will return [SQLITE_ERROR] if called with the +** SQLITE_CONFIG_MULTITHREAD configuration option.</dd> +** +** [[SQLITE_CONFIG_SERIALIZED]] <dt>SQLITE_CONFIG_SERIALIZED</dt> +** <dd>There are no arguments to this option. ^This option sets the +** [threading mode] to Serialized. In other words, this option enables +** all mutexes including the recursive +** mutexes on [database connection] and [prepared statement] objects. +** In this mode (which is the default when SQLite is compiled with +** [SQLITE_THREADSAFE=1]) the SQLite library will itself serialize access +** to [database connections] and [prepared statements] so that the +** application is free to use the same [database connection] or the +** same [prepared statement] in different threads at the same time. +** ^If SQLite is compiled with +** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then +** it is not possible to set the Serialized [threading mode] and +** [sqlite3_config()] will return [SQLITE_ERROR] if called with the +** SQLITE_CONFIG_SERIALIZED configuration option.</dd> +** +** [[SQLITE_CONFIG_MALLOC]] <dt>SQLITE_CONFIG_MALLOC</dt> +** <dd> ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is +** a pointer to an instance of the [sqlite3_mem_methods] structure. +** The argument specifies +** alternative low-level memory allocation routines to be used in place of +** the memory allocation routines built into SQLite.)^ ^SQLite makes +** its own private copy of the content of the [sqlite3_mem_methods] structure +** before the [sqlite3_config()] call returns.</dd> +** +** [[SQLITE_CONFIG_GETMALLOC]] <dt>SQLITE_CONFIG_GETMALLOC</dt> +** <dd> ^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which +** is a pointer to an instance of the [sqlite3_mem_methods] structure. +** The [sqlite3_mem_methods] +** structure is filled with the currently defined memory allocation routines.)^ +** This option can be used to overload the default memory allocation +** routines with a wrapper that simulations memory allocation failure or +** tracks memory usage, for example. </dd> +** +** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt> +** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of +** type int, interpreted as a boolean, which if true provides a hint to +** SQLite that it should avoid large memory allocations if possible. +** SQLite will run faster if it is free to make large memory allocations, +** but some application might prefer to run slower in exchange for +** guarantees about memory fragmentation that are possible if large +** allocations are avoided. This hint is normally off. +** </dd> +** +** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt> +** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, +** interpreted as a boolean, which enables or disables the collection of +** memory allocation statistics. ^(When memory allocation statistics are +** disabled, the following SQLite interfaces become non-operational: +** <ul> +** <li> [sqlite3_hard_heap_limit64()] +** <li> [sqlite3_memory_used()] +** <li> [sqlite3_memory_highwater()] +** <li> [sqlite3_soft_heap_limit64()] +** <li> [sqlite3_status64()] +** </ul>)^ +** ^Memory allocation statistics are enabled by default unless SQLite is +** compiled with [SQLITE_DEFAULT_MEMSTATUS]=0 in which case memory +** allocation statistics are disabled by default. +** </dd> +** +** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt> +** <dd> The SQLITE_CONFIG_SCRATCH option is no longer used. +** </dd> +** +** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt> +** <dd> ^The SQLITE_CONFIG_PAGECACHE option specifies a memory pool +** that SQLite can use for the database page cache with the default page +** cache implementation. +** This configuration option is a no-op if an application-defined page +** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2]. +** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to +** 8-byte aligned memory (pMem), the size of each page cache line (sz), +** and the number of cache lines (N). +** The sz argument should be the size of the largest database page +** (a power of two between 512 and 65536) plus some extra bytes for each +** page header. ^The number of extra bytes needed by the page header +** can be determined using [SQLITE_CONFIG_PCACHE_HDRSZ]. +** ^It is harmless, apart from the wasted memory, +** for the sz parameter to be larger than necessary. The pMem +** argument must be either a NULL pointer or a pointer to an 8-byte +** aligned block of memory of at least sz*N bytes, otherwise +** subsequent behavior is undefined. +** ^When pMem is not NULL, SQLite will strive to use the memory provided +** to satisfy page cache needs, falling back to [sqlite3_malloc()] if +** a page cache line is larger than sz bytes or if all of the pMem buffer +** is exhausted. +** ^If pMem is NULL and N is non-zero, then each database connection +** does an initial bulk allocation for page cache memory +** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or +** of -1024*N bytes if N is negative, . ^If additional +** page cache memory is needed beyond what is provided by the initial +** allocation, then SQLite goes to [sqlite3_malloc()] separately for each +** additional cache line. </dd> +** +** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt> +** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer +** that SQLite will use for all of its dynamic memory allocation needs +** beyond those provided for by [SQLITE_CONFIG_PAGECACHE]. +** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled +** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns +** [SQLITE_ERROR] if invoked otherwise. +** ^There are three arguments to SQLITE_CONFIG_HEAP: +** An 8-byte aligned pointer to the memory, +** the number of bytes in the memory buffer, and the minimum allocation size. +** ^If the first pointer (the memory pointer) is NULL, then SQLite reverts +** to using its default memory allocator (the system malloc() implementation), +** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the +** memory pointer is not NULL then the alternative memory +** allocator is engaged to handle all of SQLites memory allocation needs. +** The first pointer (the memory pointer) must be aligned to an 8-byte +** boundary or subsequent behavior of SQLite will be undefined. +** The minimum allocation size is capped at 2**12. Reasonable values +** for the minimum allocation size are 2**5 through 2**8.</dd> +** +** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt> +** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a +** pointer to an instance of the [sqlite3_mutex_methods] structure. +** The argument specifies alternative low-level mutex routines to be used +** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of +** the content of the [sqlite3_mutex_methods] structure before the call to +** [sqlite3_config()] returns. ^If SQLite is compiled with +** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then +** the entire mutexing subsystem is omitted from the build and hence calls to +** [sqlite3_config()] with the SQLITE_CONFIG_MUTEX configuration option will +** return [SQLITE_ERROR].</dd> +** +** [[SQLITE_CONFIG_GETMUTEX]] <dt>SQLITE_CONFIG_GETMUTEX</dt> +** <dd> ^(The SQLITE_CONFIG_GETMUTEX option takes a single argument which +** is a pointer to an instance of the [sqlite3_mutex_methods] structure. The +** [sqlite3_mutex_methods] +** structure is filled with the currently defined mutex routines.)^ +** This option can be used to overload the default mutex allocation +** routines with a wrapper used to track mutex usage for performance +** profiling or testing, for example. ^If SQLite is compiled with +** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then +** the entire mutexing subsystem is omitted from the build and hence calls to +** [sqlite3_config()] with the SQLITE_CONFIG_GETMUTEX configuration option will +** return [SQLITE_ERROR].</dd> +** +** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt> +** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine +** the default size of lookaside memory on each [database connection]. +** The first argument is the +** size of each lookaside buffer slot and the second is the number of +** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE +** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] +** option to [sqlite3_db_config()] can be used to change the lookaside +** configuration on individual connections.)^ </dd> +** +** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> +** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is +** a pointer to an [sqlite3_pcache_methods2] object. This object specifies +** the interface to a custom page cache implementation.)^ +** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.</dd> +** +** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt> +** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which +** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of +** the current page cache implementation into that object.)^ </dd> +** +** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt> +** <dd> The SQLITE_CONFIG_LOG option is used to configure the SQLite +** global [error log]. +** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a +** function with a call signature of void(*)(void*,int,const char*), +** and a pointer to void. ^If the function pointer is not NULL, it is +** invoked by [sqlite3_log()] to process each logging event. ^If the +** function pointer is NULL, the [sqlite3_log()] interface becomes a no-op. +** ^The void pointer that is the second argument to SQLITE_CONFIG_LOG is +** passed through as the first parameter to the application-defined logger +** function whenever that function is invoked. ^The second parameter to +** the logger function is a copy of the first parameter to the corresponding +** [sqlite3_log()] call and is intended to be a [result code] or an +** [extended result code]. ^The third parameter passed to the logger is +** log message after formatting via [sqlite3_snprintf()]. +** The SQLite logging interface is not reentrant; the logger function +** supplied by the application must not invoke any SQLite interface. +** In a multi-threaded application, the application-defined logger +** function must be threadsafe. </dd> +** +** [[SQLITE_CONFIG_URI]] <dt>SQLITE_CONFIG_URI +** <dd>^(The SQLITE_CONFIG_URI option takes a single argument of type int. +** If non-zero, then URI handling is globally enabled. If the parameter is zero, +** then URI handling is globally disabled.)^ ^If URI handling is globally +** enabled, all filenames passed to [sqlite3_open()], [sqlite3_open_v2()], +** [sqlite3_open16()] or +** specified as part of [ATTACH] commands are interpreted as URIs, regardless +** of whether or not the [SQLITE_OPEN_URI] flag is set when the database +** connection is opened. ^If it is globally disabled, filenames are +** only interpreted as URIs if the SQLITE_OPEN_URI flag is set when the +** database connection is opened. ^(By default, URI handling is globally +** disabled. The default value may be changed by compiling with the +** [SQLITE_USE_URI] symbol defined.)^ +** +** [[SQLITE_CONFIG_COVERING_INDEX_SCAN]] <dt>SQLITE_CONFIG_COVERING_INDEX_SCAN +** <dd>^The SQLITE_CONFIG_COVERING_INDEX_SCAN option takes a single integer +** argument which is interpreted as a boolean in order to enable or disable +** the use of covering indices for full table scans in the query optimizer. +** ^The default setting is determined +** by the [SQLITE_ALLOW_COVERING_INDEX_SCAN] compile-time option, or is "on" +** if that compile-time option is omitted. +** The ability to disable the use of covering indices for full table scans +** is because some incorrectly coded legacy applications might malfunction +** when the optimization is enabled. Providing the ability to +** disable the optimization allows the older, buggy application code to work +** without change even with newer versions of SQLite. +** +** [[SQLITE_CONFIG_PCACHE]] [[SQLITE_CONFIG_GETPCACHE]] +** <dt>SQLITE_CONFIG_PCACHE and SQLITE_CONFIG_GETPCACHE +** <dd> These options are obsolete and should not be used by new code. +** They are retained for backwards compatibility but are now no-ops. +** </dd> +** +** [[SQLITE_CONFIG_SQLLOG]] +** <dt>SQLITE_CONFIG_SQLLOG +** <dd>This option is only available if sqlite is compiled with the +** [SQLITE_ENABLE_SQLLOG] pre-processor macro defined. The first argument should +** be a pointer to a function of type void(*)(void*,sqlite3*,const char*, int). +** The second should be of type (void*). The callback is invoked by the library +** in three separate circumstances, identified by the value passed as the +** fourth parameter. If the fourth parameter is 0, then the database connection +** passed as the second argument has just been opened. The third argument +** points to a buffer containing the name of the main database file. If the +** fourth parameter is 1, then the SQL statement that the third parameter +** points to has just been executed. Or, if the fourth parameter is 2, then +** the connection being passed as the second parameter is being closed. The +** third parameter is passed NULL In this case. An example of using this +** configuration option can be seen in the "test_sqllog.c" source file in +** the canonical SQLite source tree.</dd> +** +** [[SQLITE_CONFIG_MMAP_SIZE]] +** <dt>SQLITE_CONFIG_MMAP_SIZE +** <dd>^SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values +** that are the default mmap size limit (the default setting for +** [PRAGMA mmap_size]) and the maximum allowed mmap size limit. +** ^The default setting can be overridden by each database connection using +** either the [PRAGMA mmap_size] command, or by using the +** [SQLITE_FCNTL_MMAP_SIZE] file control. ^(The maximum allowed mmap size +** will be silently truncated if necessary so that it does not exceed the +** compile-time maximum mmap size set by the +** [SQLITE_MAX_MMAP_SIZE] compile-time option.)^ +** ^If either argument to this option is negative, then that argument is +** changed to its compile-time default. +** +** [[SQLITE_CONFIG_WIN32_HEAPSIZE]] +** <dt>SQLITE_CONFIG_WIN32_HEAPSIZE +** <dd>^The SQLITE_CONFIG_WIN32_HEAPSIZE option is only available if SQLite is +** compiled for Windows with the [SQLITE_WIN32_MALLOC] pre-processor macro +** defined. ^SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value +** that specifies the maximum size of the created heap. +** +** [[SQLITE_CONFIG_PCACHE_HDRSZ]] +** <dt>SQLITE_CONFIG_PCACHE_HDRSZ +** <dd>^The SQLITE_CONFIG_PCACHE_HDRSZ option takes a single parameter which +** is a pointer to an integer and writes into that integer the number of extra +** bytes per page required for each page in [SQLITE_CONFIG_PAGECACHE]. +** The amount of extra space required can change depending on the compiler, +** target platform, and SQLite version. +** +** [[SQLITE_CONFIG_PMASZ]] +** <dt>SQLITE_CONFIG_PMASZ +** <dd>^The SQLITE_CONFIG_PMASZ option takes a single parameter which +** is an unsigned integer and sets the "Minimum PMA Size" for the multithreaded +** sorter to that integer. The default minimum PMA Size is set by the +** [SQLITE_SORTER_PMASZ] compile-time option. New threads are launched +** to help with sort operations when multithreaded sorting +** is enabled (using the [PRAGMA threads] command) and the amount of content +** to be sorted exceeds the page size times the minimum of the +** [PRAGMA cache_size] setting and this value. +** +** [[SQLITE_CONFIG_STMTJRNL_SPILL]] +** <dt>SQLITE_CONFIG_STMTJRNL_SPILL +** <dd>^The SQLITE_CONFIG_STMTJRNL_SPILL option takes a single parameter which +** becomes the [statement journal] spill-to-disk threshold. +** [Statement journals] are held in memory until their size (in bytes) +** exceeds this threshold, at which point they are written to disk. +** Or if the threshold is -1, statement journals are always held +** exclusively in memory. +** Since many statement journals never become large, setting the spill +** threshold to a value such as 64KiB can greatly reduce the amount of +** I/O required to support statement rollback. +** The default value for this setting is controlled by the +** [SQLITE_STMTJRNL_SPILL] compile-time option. +** +** [[SQLITE_CONFIG_SORTERREF_SIZE]] +** <dt>SQLITE_CONFIG_SORTERREF_SIZE +** <dd>The SQLITE_CONFIG_SORTERREF_SIZE option accepts a single parameter +** of type (int) - the new value of the sorter-reference size threshold. +** Usually, when SQLite uses an external sort to order records according +** to an ORDER BY clause, all fields required by the caller are present in the +** sorted records. However, if SQLite determines based on the declared type +** of a table column that its values are likely to be very large - larger +** than the configured sorter-reference size threshold - then a reference +** is stored in each sorted record and the required column values loaded +** from the database as records are returned in sorted order. The default +** value for this option is to never use this optimization. Specifying a +** negative value for this option restores the default behaviour. +** This option is only available if SQLite is compiled with the +** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option. +** +** [[SQLITE_CONFIG_MEMDB_MAXSIZE]] +** <dt>SQLITE_CONFIG_MEMDB_MAXSIZE +** <dd>The SQLITE_CONFIG_MEMDB_MAXSIZE option accepts a single parameter +** [sqlite3_int64] parameter which is the default maximum size for an in-memory +** database created using [sqlite3_deserialize()]. This default maximum +** size can be adjusted up or down for individual databases using the +** [SQLITE_FCNTL_SIZE_LIMIT] [sqlite3_file_control|file-control]. If this +** configuration setting is never used, then the default maximum is determined +** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that +** compile-time option is not set, then the default maximum is 1073741824. +** </dl> +*/ +#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ +#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ +#define SQLITE_CONFIG_SERIALIZED 3 /* nil */ +#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */ +#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ +#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ +#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ +#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ +#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ +#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ +#define SQLITE_CONFIG_PCACHE 14 /* no-op */ +#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ +#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ +#define SQLITE_CONFIG_URI 17 /* int */ +#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ +#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ +#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */ +#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ +#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ +#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */ +#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */ +#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ +#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */ +#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ +#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ +#define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ + +/* +** CAPI3REF: Database Connection Configuration Options +** +** These constants are the available integer configuration options that +** can be passed as the second argument to the [sqlite3_db_config()] interface. +** +** New configuration options may be added in future releases of SQLite. +** Existing configuration options might be discontinued. Applications +** should check the return code from [sqlite3_db_config()] to make sure that +** the call worked. ^The [sqlite3_db_config()] interface will return a +** non-zero [error code] if a discontinued or unsupported configuration option +** is invoked. +** +** <dl> +** [[SQLITE_DBCONFIG_LOOKASIDE]] +** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt> +** <dd> ^This option takes three additional arguments that determine the +** [lookaside memory allocator] configuration for the [database connection]. +** ^The first argument (the third parameter to [sqlite3_db_config()] is a +** pointer to a memory buffer to use for lookaside memory. +** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb +** may be NULL in which case SQLite will allocate the +** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the +** size of each lookaside buffer slot. ^The third argument is the number of +** slots. The size of the buffer in the first argument must be greater than +** or equal to the product of the second and third arguments. The buffer +** must be aligned to an 8-byte boundary. ^If the second argument to +** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally +** rounded down to the next smaller multiple of 8. ^(The lookaside memory +** configuration for a database connection can only be changed when that +** connection is not currently using lookaside memory, or in other words +** when the "current value" returned by +** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero. +** Any attempt to change the lookaside memory configuration when lookaside +** memory is in use leaves the configuration unchanged and returns +** [SQLITE_BUSY].)^</dd> +** +** [[SQLITE_DBCONFIG_ENABLE_FKEY]] +** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt> +** <dd> ^This option is used to enable or disable the enforcement of +** [foreign key constraints]. There should be two additional arguments. +** The first argument is an integer which is 0 to disable FK enforcement, +** positive to enable FK enforcement or negative to leave FK enforcement +** unchanged. The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether FK enforcement is off or on +** following this call. The second parameter may be a NULL pointer, in +** which case the FK enforcement setting is not reported back. </dd> +** +** [[SQLITE_DBCONFIG_ENABLE_TRIGGER]] +** <dt>SQLITE_DBCONFIG_ENABLE_TRIGGER</dt> +** <dd> ^This option is used to enable or disable [CREATE TRIGGER | triggers]. +** There should be two additional arguments. +** The first argument is an integer which is 0 to disable triggers, +** positive to enable triggers or negative to leave the setting unchanged. +** The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether triggers are disabled or enabled +** following this call. The second parameter may be a NULL pointer, in +** which case the trigger setting is not reported back. +** +** <p>Originally this option disabled all triggers. ^(However, since +** SQLite version 3.35.0, TEMP triggers are still allowed even if +** this option is off. So, in other words, this option now only disables +** triggers in the main database schema or in the schemas of ATTACH-ed +** databases.)^ </dd> +** +** [[SQLITE_DBCONFIG_ENABLE_VIEW]] +** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt> +** <dd> ^This option is used to enable or disable [CREATE VIEW | views]. +** There should be two additional arguments. +** The first argument is an integer which is 0 to disable views, +** positive to enable views or negative to leave the setting unchanged. +** The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether views are disabled or enabled +** following this call. The second parameter may be a NULL pointer, in +** which case the view setting is not reported back. +** +** <p>Originally this option disabled all views. ^(However, since +** SQLite version 3.35.0, TEMP views are still allowed even if +** this option is off. So, in other words, this option now only disables +** views in the main database schema or in the schemas of ATTACH-ed +** databases.)^ </dd> +** +** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] +** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> +** <dd> ^This option is used to enable or disable the +** [fts3_tokenizer()] function which is part of the +** [FTS3] full-text search engine extension. +** There should be two additional arguments. +** The first argument is an integer which is 0 to disable fts3_tokenizer() or +** positive to enable fts3_tokenizer() or negative to leave the setting +** unchanged. +** The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled +** following this call. The second parameter may be a NULL pointer, in +** which case the new setting is not reported back. </dd> +** +** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] +** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> +** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()] +** interface independently of the [load_extension()] SQL function. +** The [sqlite3_enable_load_extension()] API enables or disables both the +** C-API [sqlite3_load_extension()] and the SQL function [load_extension()]. +** There should be two additional arguments. +** When the first argument to this interface is 1, then only the C-API is +** enabled and the SQL function remains disabled. If the first argument to +** this interface is 0, then both the C-API and the SQL function are disabled. +** If the first argument is -1, then no changes are made to state of either the +** C-API or the SQL function. +** The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface +** is disabled or enabled following this call. The second parameter may +** be a NULL pointer, in which case the new setting is not reported back. +** </dd> +** +** [[SQLITE_DBCONFIG_MAINDBNAME]] <dt>SQLITE_DBCONFIG_MAINDBNAME</dt> +** <dd> ^This option is used to change the name of the "main" database +** schema. ^The sole argument is a pointer to a constant UTF8 string +** which will become the new schema name in place of "main". ^SQLite +** does not make a copy of the new main schema name string, so the application +** must ensure that the argument passed into this DBCONFIG option is unchanged +** until after the database connection closes. +** </dd> +** +** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]] +** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt> +** <dd> Usually, when a database in wal mode is closed or detached from a +** database handle, SQLite checks if this will mean that there are now no +** connections at all to the database. If so, it performs a checkpoint +** operation before closing the connection. This option may be used to +** override this behaviour. The first parameter passed to this operation +** is an integer - positive to disable checkpoints-on-close, or zero (the +** default) to enable them, and negative to leave the setting unchanged. +** The second parameter is a pointer to an integer +** into which is written 0 or 1 to indicate whether checkpoints-on-close +** have been disabled - 0 if they are not disabled, 1 if they are. +** </dd> +** +** [[SQLITE_DBCONFIG_ENABLE_QPSG]] <dt>SQLITE_DBCONFIG_ENABLE_QPSG</dt> +** <dd>^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates +** the [query planner stability guarantee] (QPSG). When the QPSG is active, +** a single SQL query statement will always use the same algorithm regardless +** of values of [bound parameters].)^ The QPSG disables some query optimizations +** that look at the values of bound parameters, which can make some queries +** slower. But the QPSG has the advantage of more predictable behavior. With +** the QPSG active, SQLite will always use the same query plan in the field as +** was used during testing in the lab. +** The first argument to this setting is an integer which is 0 to disable +** the QPSG, positive to enable QPSG, or negative to leave the setting +** unchanged. The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether the QPSG is disabled or enabled +** following this call. +** </dd> +** +** [[SQLITE_DBCONFIG_TRIGGER_EQP]] <dt>SQLITE_DBCONFIG_TRIGGER_EQP</dt> +** <dd> By default, the output of EXPLAIN QUERY PLAN commands does not +** include output for any operations performed by trigger programs. This +** option is used to set or clear (the default) a flag that governs this +** behavior. The first parameter passed to this operation is an integer - +** positive to enable output for trigger programs, or zero to disable it, +** or negative to leave the setting unchanged. +** The second parameter is a pointer to an integer into which is written +** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if +** it is not disabled, 1 if it is. +** </dd> +** +** [[SQLITE_DBCONFIG_RESET_DATABASE]] <dt>SQLITE_DBCONFIG_RESET_DATABASE</dt> +** <dd> Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run +** [VACUUM] in order to reset a database back to an empty database +** with no schema and no content. The following process works even for +** a badly corrupted database file: +** <ol> +** <li> If the database connection is newly opened, make sure it has read the +** database schema by preparing then discarding some query against the +** database, or calling sqlite3_table_column_metadata(), ignoring any +** errors. This step is only necessary if the application desires to keep +** the database in WAL mode after the reset if it was in WAL mode before +** the reset. +** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); +** <li> [sqlite3_exec](db, "[VACUUM]", 0, 0, 0); +** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); +** </ol> +** Because resetting a database is destructive and irreversible, the +** process requires the use of this obscure API and multiple steps to +** help ensure that it does not happen by accident. Because this +** feature must be capable of resetting corrupt databases, and +** shutting down virtual tables may require access to that corrupt +** storage, the library must abandon any installed virtual tables +** without calling their xDestroy() methods. +** +** [[SQLITE_DBCONFIG_DEFENSIVE]] <dt>SQLITE_DBCONFIG_DEFENSIVE</dt> +** <dd>The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the +** "defensive" flag for a database connection. When the defensive +** flag is enabled, language features that allow ordinary SQL to +** deliberately corrupt the database file are disabled. The disabled +** features include but are not limited to the following: +** <ul> +** <li> The [PRAGMA writable_schema=ON] statement. +** <li> The [PRAGMA journal_mode=OFF] statement. +** <li> The [PRAGMA schema_version=N] statement. +** <li> Writes to the [sqlite_dbpage] virtual table. +** <li> Direct writes to [shadow tables]. +** </ul> +** </dd> +** +** [[SQLITE_DBCONFIG_WRITABLE_SCHEMA]] <dt>SQLITE_DBCONFIG_WRITABLE_SCHEMA</dt> +** <dd>The SQLITE_DBCONFIG_WRITABLE_SCHEMA option activates or deactivates the +** "writable_schema" flag. This has the same effect and is logically equivalent +** to setting [PRAGMA writable_schema=ON] or [PRAGMA writable_schema=OFF]. +** The first argument to this setting is an integer which is 0 to disable +** the writable_schema, positive to enable writable_schema, or negative to +** leave the setting unchanged. The second parameter is a pointer to an +** integer into which is written 0 or 1 to indicate whether the writable_schema +** is enabled or disabled following this call. +** </dd> +** +** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]] +** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt> +** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates +** the legacy behavior of the [ALTER TABLE RENAME] command such it +** behaves as it did prior to [version 3.24.0] (2018-06-04). See the +** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for +** additional information. This feature can also be turned on and off +** using the [PRAGMA legacy_alter_table] statement. +** </dd> +** +** [[SQLITE_DBCONFIG_DQS_DML]] +** <dt>SQLITE_DBCONFIG_DQS_DML</dt> +** <dd>The SQLITE_DBCONFIG_DQS_DML option activates or deactivates +** the legacy [double-quoted string literal] misfeature for DML statements +** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The +** default value of this setting is determined by the [-DSQLITE_DQS] +** compile-time option. +** </dd> +** +** [[SQLITE_DBCONFIG_DQS_DDL]] +** <dt>SQLITE_DBCONFIG_DQS_DDL</dt> +** <dd>The SQLITE_DBCONFIG_DQS option activates or deactivates +** the legacy [double-quoted string literal] misfeature for DDL statements, +** such as CREATE TABLE and CREATE INDEX. The +** default value of this setting is determined by the [-DSQLITE_DQS] +** compile-time option. +** </dd> +** +** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]] +** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</dt> +** <dd>The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to +** assume that database schemas are untainted by malicious content. +** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite +** takes additional defensive steps to protect the application from harm +** including: +** <ul> +** <li> Prohibit the use of SQL functions inside triggers, views, +** CHECK constraints, DEFAULT clauses, expression indexes, +** partial indexes, or generated columns +** unless those functions are tagged with [SQLITE_INNOCUOUS]. +** <li> Prohibit the use of virtual tables inside of triggers or views +** unless those virtual tables are tagged with [SQLITE_VTAB_INNOCUOUS]. +** </ul> +** This setting defaults to "on" for legacy compatibility, however +** all applications are advised to turn it off if possible. This setting +** can also be controlled using the [PRAGMA trusted_schema] statement. +** </dd> +** +** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]] +** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt> +** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates +** the legacy file format flag. When activated, this flag causes all newly +** created database file to have a schema format version number (the 4-byte +** integer found at offset 44 into the database header) of 1. This in turn +** means that the resulting database file will be readable and writable by +** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting, +** newly created databases are generally not understandable by SQLite versions +** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there +** is now scarcely any need to generate database files that are compatible +** all the way back to version 3.0.0, and so this setting is of little +** practical use, but is provided so that SQLite can continue to claim the +** ability to generate new database files that are compatible with version +** 3.0.0. +** <p>Note that when the SQLITE_DBCONFIG_LEGACY_FILE_FORMAT setting is on, +** the [VACUUM] command will fail with an obscure error when attempting to +** process a table with generated columns and a descending index. This is +** not considered a bug since SQLite versions 3.3.0 and earlier do not support +** either generated columns or descending indexes. +** </dd> +** +** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] +** <dt>SQLITE_DBCONFIG_STMT_SCANSTATUS</dt> +** <dd>The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in +** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears +** a flag that enables collection of the sqlite3_stmt_scanstatus_v2() +** statistics. For statistics to be collected, the flag must be set on +** the database handle both when the SQL statement is prepared and when it +** is stepped. The flag is set (collection of statistics is enabled) +** by default. This option takes two arguments: an integer and a pointer to +** an integer.. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the statement scanstatus option. If the second argument +** is not NULL, then the value of the statement scanstatus setting after +** processing the first argument is written into the integer that the second +** argument points to. +** </dd> +** +** [[SQLITE_DBCONFIG_REVERSE_SCANORDER]] +** <dt>SQLITE_DBCONFIG_REVERSE_SCANORDER</dt> +** <dd>The SQLITE_DBCONFIG_REVERSE_SCANORDER option changes the default order +** in which tables and indexes are scanned so that the scans start at the end +** and work toward the beginning rather than starting at the beginning and +** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the +** same as setting [PRAGMA reverse_unordered_selects]. This option takes +** two arguments which are an integer and a pointer to an integer. The first +** argument is 1, 0, or -1 to enable, disable, or leave unchanged the +** reverse scan order flag, respectively. If the second argument is not NULL, +** then 0 or 1 is written into the integer that the second argument points to +** depending on if the reverse scan order flag is set after processing the +** first argument. +** </dd> +** +** </dl> +*/ +#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ +#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ +#define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */ +#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */ +#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */ +#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */ +#define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */ +#define SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011 /* int int* */ +#define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012 /* int int* */ +#define SQLITE_DBCONFIG_DQS_DML 1013 /* int int* */ +#define SQLITE_DBCONFIG_DQS_DDL 1014 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */ +#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */ +#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */ +#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */ +#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */ +#define SQLITE_DBCONFIG_MAX 1019 /* Largest DBCONFIG */ + +/* +** CAPI3REF: Enable Or Disable Extended Result Codes +** METHOD: sqlite3 +** +** ^The sqlite3_extended_result_codes() routine enables or disables the +** [extended result codes] feature of SQLite. ^The extended result +** codes are disabled by default for historical compatibility. +*/ +SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); + +/* +** CAPI3REF: Last Insert Rowid +** METHOD: sqlite3 +** +** ^Each entry in most SQLite tables (except for [WITHOUT ROWID] tables) +** has a unique 64-bit signed +** integer key called the [ROWID | "rowid"]. ^The rowid is always available +** as an undeclared column named ROWID, OID, or _ROWID_ as long as those +** names are not also used by explicitly declared columns. ^If +** the table has a column of type [INTEGER PRIMARY KEY] then that column +** is another alias for the rowid. +** +** ^The sqlite3_last_insert_rowid(D) interface usually returns the [rowid] of +** the most recent successful [INSERT] into a rowid table or [virtual table] +** on database connection D. ^Inserts into [WITHOUT ROWID] tables are not +** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred +** on the database connection D, then sqlite3_last_insert_rowid(D) returns +** zero. +** +** As well as being set automatically as rows are inserted into database +** tables, the value returned by this function may be set explicitly by +** [sqlite3_set_last_insert_rowid()] +** +** Some virtual table implementations may INSERT rows into rowid tables as +** part of committing a transaction (e.g. to flush data accumulated in memory +** to disk). In this case subsequent calls to this function return the rowid +** associated with these internal INSERT operations, which leads to +** unintuitive results. Virtual table implementations that do write to rowid +** tables in this way can avoid this problem by restoring the original +** rowid value using [sqlite3_set_last_insert_rowid()] before returning +** control to the user. +** +** ^(If an [INSERT] occurs within a trigger then this routine will +** return the [rowid] of the inserted row as long as the trigger is +** running. Once the trigger program ends, the value returned +** by this routine reverts to what it was before the trigger was fired.)^ +** +** ^An [INSERT] that fails due to a constraint violation is not a +** successful [INSERT] and does not change the value returned by this +** routine. ^Thus INSERT OR FAIL, INSERT OR IGNORE, INSERT OR ROLLBACK, +** and INSERT OR ABORT make no changes to the return value of this +** routine when their insertion fails. ^(When INSERT OR REPLACE +** encounters a constraint violation, it does not fail. The +** INSERT continues to completion after deleting rows that caused +** the constraint problem so INSERT OR REPLACE will always change +** the return value of this interface.)^ +** +** ^For the purposes of this routine, an [INSERT] is considered to +** be successful even if it is subsequently rolled back. +** +** This function is accessible to SQL statements via the +** [last_insert_rowid() SQL function]. +** +** If a separate thread performs a new [INSERT] on the same +** database connection while the [sqlite3_last_insert_rowid()] +** function is running and thus changes the last insert [rowid], +** then the value returned by [sqlite3_last_insert_rowid()] is +** unpredictable and might not equal either the old or the new +** last insert [rowid]. +*/ +SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); + +/* +** CAPI3REF: Set the Last Insert Rowid value. +** METHOD: sqlite3 +** +** The sqlite3_set_last_insert_rowid(D, R) method allows the application to +** set the value returned by calling sqlite3_last_insert_rowid(D) to R +** without inserting a row into the database. +*/ +SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); + +/* +** CAPI3REF: Count The Number Of Rows Modified +** METHOD: sqlite3 +** +** ^These functions return the number of rows modified, inserted or +** deleted by the most recently completed INSERT, UPDATE or DELETE +** statement on the database connection specified by the only parameter. +** The two functions are identical except for the type of the return value +** and that if the number of rows modified by the most recent INSERT, UPDATE +** or DELETE is greater than the maximum value supported by type "int", then +** the return value of sqlite3_changes() is undefined. ^Executing any other +** type of SQL statement does not modify the value returned by these functions. +** +** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are +** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], +** [foreign key actions] or [REPLACE] constraint resolution are not counted. +** +** Changes to a view that are intercepted by +** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value +** returned by sqlite3_changes() immediately after an INSERT, UPDATE or +** DELETE statement run on a view is always zero. Only changes made to real +** tables are counted. +** +** Things are more complicated if the sqlite3_changes() function is +** executed while a trigger program is running. This may happen if the +** program uses the [changes() SQL function], or if some other callback +** function invokes sqlite3_changes() directly. Essentially: +** +** <ul> +** <li> ^(Before entering a trigger program the value returned by +** sqlite3_changes() function is saved. After the trigger program +** has finished, the original value is restored.)^ +** +** <li> ^(Within a trigger program each INSERT, UPDATE and DELETE +** statement sets the value returned by sqlite3_changes() +** upon completion as normal. Of course, this value will not include +** any changes performed by sub-triggers, as the sqlite3_changes() +** value will be saved and restored after each sub-trigger has run.)^ +** </ul> +** +** ^This means that if the changes() SQL function (or similar) is used +** by the first INSERT, UPDATE or DELETE statement within a trigger, it +** returns the value as set when the calling statement began executing. +** ^If it is used by the second or subsequent such statement within a trigger +** program, the value returned reflects the number of rows modified by the +** previous INSERT, UPDATE or DELETE statement within the same trigger. +** +** If a separate thread makes changes on the same database connection +** while [sqlite3_changes()] is running then the value returned +** is unpredictable and not meaningful. +** +** See also: +** <ul> +** <li> the [sqlite3_total_changes()] interface +** <li> the [count_changes pragma] +** <li> the [changes() SQL function] +** <li> the [data_version pragma] +** </ul> +*/ +SQLITE_API int sqlite3_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3*); + +/* +** CAPI3REF: Total Number Of Rows Modified +** METHOD: sqlite3 +** +** ^These functions return the total number of rows inserted, modified or +** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed +** since the database connection was opened, including those executed as +** part of trigger programs. The two functions are identical except for the +** type of the return value and that if the number of rows modified by the +** connection exceeds the maximum value supported by type "int", then +** the return value of sqlite3_total_changes() is undefined. ^Executing +** any other type of SQL statement does not affect the value returned by +** sqlite3_total_changes(). +** +** ^Changes made as part of [foreign key actions] are included in the +** count, but those made as part of REPLACE constraint resolution are +** not. ^Changes to a view that are intercepted by INSTEAD OF triggers +** are not counted. +** +** The [sqlite3_total_changes(D)] interface only reports the number +** of rows that changed due to SQL statement run against database +** connection D. Any changes by other database connections are ignored. +** To detect changes against a database file from other database +** connections use the [PRAGMA data_version] command or the +** [SQLITE_FCNTL_DATA_VERSION] [file control]. +** +** If a separate thread makes changes on the same database connection +** while [sqlite3_total_changes()] is running then the value +** returned is unpredictable and not meaningful. +** +** See also: +** <ul> +** <li> the [sqlite3_changes()] interface +** <li> the [count_changes pragma] +** <li> the [changes() SQL function] +** <li> the [data_version pragma] +** <li> the [SQLITE_FCNTL_DATA_VERSION] [file control] +** </ul> +*/ +SQLITE_API int sqlite3_total_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*); + +/* +** CAPI3REF: Interrupt A Long-Running Query +** METHOD: sqlite3 +** +** ^This function causes any pending database operation to abort and +** return at its earliest opportunity. This routine is typically +** called in response to a user action such as pressing "Cancel" +** or Ctrl-C where the user wants a long query operation to halt +** immediately. +** +** ^It is safe to call this routine from a thread different from the +** thread that is currently running the database operation. But it +** is not safe to call this routine with a [database connection] that +** is closed or might close before sqlite3_interrupt() returns. +** +** ^If an SQL operation is very nearly finished at the time when +** sqlite3_interrupt() is called, then it might not have an opportunity +** to be interrupted and might continue to completion. +** +** ^An SQL operation that is interrupted will return [SQLITE_INTERRUPT]. +** ^If the interrupted SQL operation is an INSERT, UPDATE, or DELETE +** that is inside an explicit transaction, then the entire transaction +** will be rolled back automatically. +** +** ^The sqlite3_interrupt(D) call is in effect until all currently running +** SQL statements on [database connection] D complete. ^Any new SQL statements +** that are started after the sqlite3_interrupt() call and before the +** running statement count reaches zero are interrupted as if they had been +** running prior to the sqlite3_interrupt() call. ^New SQL statements +** that are started after the running statement count reaches zero are +** not effected by the sqlite3_interrupt(). +** ^A call to sqlite3_interrupt(D) that occurs when there are no running +** SQL statements is a no-op and has no effect on SQL statements +** that are started after the sqlite3_interrupt() call returns. +** +** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether +** or not an interrupt is currently in effect for [database connection] D. +** It returns 1 if an interrupt is currently in effect, or 0 otherwise. +*/ +SQLITE_API void sqlite3_interrupt(sqlite3*); +SQLITE_API int sqlite3_is_interrupted(sqlite3*); + +/* +** CAPI3REF: Determine If An SQL Statement Is Complete +** +** These routines are useful during command-line input to determine if the +** currently entered text seems to form a complete SQL statement or +** if additional input is needed before sending the text into +** SQLite for parsing. ^These routines return 1 if the input string +** appears to be a complete SQL statement. ^A statement is judged to be +** complete if it ends with a semicolon token and is not a prefix of a +** well-formed CREATE TRIGGER statement. ^Semicolons that are embedded within +** string literals or quoted identifier names or comments are not +** independent tokens (they are part of the token in which they are +** embedded) and thus do not count as a statement terminator. ^Whitespace +** and comments that follow the final semicolon are ignored. +** +** ^These routines return 0 if the statement is incomplete. ^If a +** memory allocation fails, then SQLITE_NOMEM is returned. +** +** ^These routines do not parse the SQL statements thus +** will not detect syntactically incorrect SQL. +** +** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior +** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked +** automatically by sqlite3_complete16(). If that initialization fails, +** then the return value from sqlite3_complete16() will be non-zero +** regardless of whether or not the input SQL is complete.)^ +** +** The input to [sqlite3_complete()] must be a zero-terminated +** UTF-8 string. +** +** The input to [sqlite3_complete16()] must be a zero-terminated +** UTF-16 string in native byte order. +*/ +SQLITE_API int sqlite3_complete(const char *sql); +SQLITE_API int sqlite3_complete16(const void *sql); + +/* +** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors +** KEYWORDS: {busy-handler callback} {busy handler} +** METHOD: sqlite3 +** +** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X +** that might be invoked with argument P whenever +** an attempt is made to access a database table associated with +** [database connection] D when another thread +** or process has the table locked. +** The sqlite3_busy_handler() interface is used to implement +** [sqlite3_busy_timeout()] and [PRAGMA busy_timeout]. +** +** ^If the busy callback is NULL, then [SQLITE_BUSY] +** is returned immediately upon encountering the lock. ^If the busy callback +** is not NULL, then the callback might be invoked with two arguments. +** +** ^The first argument to the busy handler is a copy of the void* pointer which +** is the third argument to sqlite3_busy_handler(). ^The second argument to +** the busy handler callback is the number of times that the busy handler has +** been invoked previously for the same locking event. ^If the +** busy callback returns 0, then no additional attempts are made to +** access the database and [SQLITE_BUSY] is returned +** to the application. +** ^If the callback returns non-zero, then another attempt +** is made to access the database and the cycle repeats. +** +** The presence of a busy handler does not guarantee that it will be invoked +** when there is lock contention. ^If SQLite determines that invoking the busy +** handler could result in a deadlock, it will go ahead and return [SQLITE_BUSY] +** to the application instead of invoking the +** busy handler. +** Consider a scenario where one process is holding a read lock that +** it is trying to promote to a reserved lock and +** a second process is holding a reserved lock that it is trying +** to promote to an exclusive lock. The first process cannot proceed +** because it is blocked by the second and the second process cannot +** proceed because it is blocked by the first. If both processes +** invoke the busy handlers, neither will make any progress. Therefore, +** SQLite returns [SQLITE_BUSY] for the first process, hoping that this +** will induce the first process to release its read lock and allow +** the second process to proceed. +** +** ^The default busy callback is NULL. +** +** ^(There can only be a single busy handler defined for each +** [database connection]. Setting a new busy handler clears any +** previously set handler.)^ ^Note that calling [sqlite3_busy_timeout()] +** or evaluating [PRAGMA busy_timeout=N] will change the +** busy handler and thus clear any previously set busy handler. +** +** The busy callback should not take any actions which modify the +** database connection that invoked the busy handler. In other words, +** the busy handler is not reentrant. Any such actions +** result in undefined behavior. +** +** A busy handler must not close the database connection +** or [prepared statement] that invoked the busy handler. +*/ +SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*); + +/* +** CAPI3REF: Set A Busy Timeout +** METHOD: sqlite3 +** +** ^This routine sets a [sqlite3_busy_handler | busy handler] that sleeps +** for a specified amount of time when a table is locked. ^The handler +** will sleep multiple times until at least "ms" milliseconds of sleeping +** have accumulated. ^After at least "ms" milliseconds of sleeping, +** the handler returns 0 which causes [sqlite3_step()] to return +** [SQLITE_BUSY]. +** +** ^Calling this routine with an argument less than or equal to zero +** turns off all busy handlers. +** +** ^(There can only be a single busy handler for a particular +** [database connection] at any given moment. If another busy handler +** was defined (using [sqlite3_busy_handler()]) prior to calling +** this routine, that other busy handler is cleared.)^ +** +** See also: [PRAGMA busy_timeout] +*/ +SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); + +/* +** CAPI3REF: Convenience Routines For Running Queries +** METHOD: sqlite3 +** +** This is a legacy interface that is preserved for backwards compatibility. +** Use of this interface is not recommended. +** +** Definition: A <b>result table</b> is memory data structure created by the +** [sqlite3_get_table()] interface. A result table records the +** complete query results from one or more queries. +** +** The table conceptually has a number of rows and columns. But +** these numbers are not part of the result table itself. These +** numbers are obtained separately. Let N be the number of rows +** and M be the number of columns. +** +** A result table is an array of pointers to zero-terminated UTF-8 strings. +** There are (N+1)*M elements in the array. The first M pointers point +** to zero-terminated strings that contain the names of the columns. +** The remaining entries all point to query results. NULL values result +** in NULL pointers. All other values are in their UTF-8 zero-terminated +** string representation as returned by [sqlite3_column_text()]. +** +** A result table might consist of one or more memory allocations. +** It is not safe to pass a result table directly to [sqlite3_free()]. +** A result table should be deallocated using [sqlite3_free_table()]. +** +** ^(As an example of the result table format, suppose a query result +** is as follows: +** +** <blockquote><pre> +** Name | Age +** ----------------------- +** Alice | 43 +** Bob | 28 +** Cindy | 21 +** </pre></blockquote> +** +** There are two columns (M==2) and three rows (N==3). Thus the +** result table has 8 entries. Suppose the result table is stored +** in an array named azResult. Then azResult holds this content: +** +** <blockquote><pre> +** azResult&#91;0] = "Name"; +** azResult&#91;1] = "Age"; +** azResult&#91;2] = "Alice"; +** azResult&#91;3] = "43"; +** azResult&#91;4] = "Bob"; +** azResult&#91;5] = "28"; +** azResult&#91;6] = "Cindy"; +** azResult&#91;7] = "21"; +** </pre></blockquote>)^ +** +** ^The sqlite3_get_table() function evaluates one or more +** semicolon-separated SQL statements in the zero-terminated UTF-8 +** string of its 2nd parameter and returns a result table to the +** pointer given in its 3rd parameter. +** +** After the application has finished with the result from sqlite3_get_table(), +** it must pass the result table pointer to sqlite3_free_table() in order to +** release the memory that was malloced. Because of the way the +** [sqlite3_malloc()] happens within sqlite3_get_table(), the calling +** function must not try to call [sqlite3_free()] directly. Only +** [sqlite3_free_table()] is able to release the memory properly and safely. +** +** The sqlite3_get_table() interface is implemented as a wrapper around +** [sqlite3_exec()]. The sqlite3_get_table() routine does not have access +** to any internal data structures of SQLite. It uses only the public +** interface defined here. As a consequence, errors that occur in the +** wrapper layer outside of the internal [sqlite3_exec()] call are not +** reflected in subsequent calls to [sqlite3_errcode()] or +** [sqlite3_errmsg()]. +*/ +SQLITE_API int sqlite3_get_table( + sqlite3 *db, /* An open database */ + const char *zSql, /* SQL to be evaluated */ + char ***pazResult, /* Results of the query */ + int *pnRow, /* Number of result rows written here */ + int *pnColumn, /* Number of result columns written here */ + char **pzErrmsg /* Error msg written here */ +); +SQLITE_API void sqlite3_free_table(char **result); + +/* +** CAPI3REF: Formatted String Printing Functions +** +** These routines are work-alikes of the "printf()" family of functions +** from the standard C library. +** These routines understand most of the common formatting options from +** the standard library printf() +** plus some additional non-standard formats ([%q], [%Q], [%w], and [%z]). +** See the [built-in printf()] documentation for details. +** +** ^The sqlite3_mprintf() and sqlite3_vmprintf() routines write their +** results into memory obtained from [sqlite3_malloc64()]. +** The strings returned by these two routines should be +** released by [sqlite3_free()]. ^Both routines return a +** NULL pointer if [sqlite3_malloc64()] is unable to allocate enough +** memory to hold the resulting string. +** +** ^(The sqlite3_snprintf() routine is similar to "snprintf()" from +** the standard C library. The result is written into the +** buffer supplied as the second parameter whose size is given by +** the first parameter. Note that the order of the +** first two parameters is reversed from snprintf().)^ This is an +** historical accident that cannot be fixed without breaking +** backwards compatibility. ^(Note also that sqlite3_snprintf() +** returns a pointer to its buffer instead of the number of +** characters actually written into the buffer.)^ We admit that +** the number of characters written would be a more useful return +** value but we cannot change the implementation of sqlite3_snprintf() +** now without breaking compatibility. +** +** ^As long as the buffer size is greater than zero, sqlite3_snprintf() +** guarantees that the buffer is always zero-terminated. ^The first +** parameter "n" is the total size of the buffer, including space for +** the zero terminator. So the longest string that can be completely +** written will be n-1 characters. +** +** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf(). +** +** See also: [built-in printf()], [printf() SQL function] +*/ +SQLITE_API char *sqlite3_mprintf(const char*,...); +SQLITE_API char *sqlite3_vmprintf(const char*, va_list); +SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...); +SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); + +/* +** CAPI3REF: Memory Allocation Subsystem +** +** The SQLite core uses these three routines for all of its own +** internal memory allocation needs. "Core" in the previous sentence +** does not include operating-system specific [VFS] implementation. The +** Windows VFS uses native malloc() and free() for some operations. +** +** ^The sqlite3_malloc() routine returns a pointer to a block +** of memory at least N bytes in length, where N is the parameter. +** ^If sqlite3_malloc() is unable to obtain sufficient free +** memory, it returns a NULL pointer. ^If the parameter N to +** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns +** a NULL pointer. +** +** ^The sqlite3_malloc64(N) routine works just like +** sqlite3_malloc(N) except that N is an unsigned 64-bit integer instead +** of a signed 32-bit integer. +** +** ^Calling sqlite3_free() with a pointer previously returned +** by sqlite3_malloc() or sqlite3_realloc() releases that memory so +** that it might be reused. ^The sqlite3_free() routine is +** a no-op if is called with a NULL pointer. Passing a NULL pointer +** to sqlite3_free() is harmless. After being freed, memory +** should neither be read nor written. Even reading previously freed +** memory might result in a segmentation fault or other severe error. +** Memory corruption, a segmentation fault, or other severe error +** might result if sqlite3_free() is called with a non-NULL pointer that +** was not obtained from sqlite3_malloc() or sqlite3_realloc(). +** +** ^The sqlite3_realloc(X,N) interface attempts to resize a +** prior memory allocation X to be at least N bytes. +** ^If the X parameter to sqlite3_realloc(X,N) +** is a NULL pointer then its behavior is identical to calling +** sqlite3_malloc(N). +** ^If the N parameter to sqlite3_realloc(X,N) is zero or +** negative then the behavior is exactly the same as calling +** sqlite3_free(X). +** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation +** of at least N bytes in size or NULL if insufficient memory is available. +** ^If M is the size of the prior allocation, then min(N,M) bytes +** of the prior allocation are copied into the beginning of buffer returned +** by sqlite3_realloc(X,N) and the prior allocation is freed. +** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the +** prior allocation is not freed. +** +** ^The sqlite3_realloc64(X,N) interfaces works the same as +** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead +** of a 32-bit signed integer. +** +** ^If X is a memory allocation previously obtained from sqlite3_malloc(), +** sqlite3_malloc64(), sqlite3_realloc(), or sqlite3_realloc64(), then +** sqlite3_msize(X) returns the size of that memory allocation in bytes. +** ^The value returned by sqlite3_msize(X) might be larger than the number +** of bytes requested when X was allocated. ^If X is a NULL pointer then +** sqlite3_msize(X) returns zero. If X points to something that is not +** the beginning of memory allocation, or if it points to a formerly +** valid memory allocation that has now been freed, then the behavior +** of sqlite3_msize(X) is undefined and possibly harmful. +** +** ^The memory returned by sqlite3_malloc(), sqlite3_realloc(), +** sqlite3_malloc64(), and sqlite3_realloc64() +** is always aligned to at least an 8 byte boundary, or to a +** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time +** option is used. +** +** The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()] +** must be either NULL or else pointers obtained from a prior +** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that have +** not yet been released. +** +** The application must not read or write any part of +** a block of memory after it has been released using +** [sqlite3_free()] or [sqlite3_realloc()]. +*/ +SQLITE_API void *sqlite3_malloc(int); +SQLITE_API void *sqlite3_malloc64(sqlite3_uint64); +SQLITE_API void *sqlite3_realloc(void*, int); +SQLITE_API void *sqlite3_realloc64(void*, sqlite3_uint64); +SQLITE_API void sqlite3_free(void*); +SQLITE_API sqlite3_uint64 sqlite3_msize(void*); + +/* +** CAPI3REF: Memory Allocator Statistics +** +** SQLite provides these two interfaces for reporting on the status +** of the [sqlite3_malloc()], [sqlite3_free()], and [sqlite3_realloc()] +** routines, which form the built-in memory allocation subsystem. +** +** ^The [sqlite3_memory_used()] routine returns the number of bytes +** of memory currently outstanding (malloced but not freed). +** ^The [sqlite3_memory_highwater()] routine returns the maximum +** value of [sqlite3_memory_used()] since the high-water mark +** was last reset. ^The values returned by [sqlite3_memory_used()] and +** [sqlite3_memory_highwater()] include any overhead +** added by SQLite in its implementation of [sqlite3_malloc()], +** but not overhead added by the any underlying system library +** routines that [sqlite3_malloc()] may call. +** +** ^The memory high-water mark is reset to the current value of +** [sqlite3_memory_used()] if and only if the parameter to +** [sqlite3_memory_highwater()] is true. ^The value returned +** by [sqlite3_memory_highwater(1)] is the high-water mark +** prior to the reset. +*/ +SQLITE_API sqlite3_int64 sqlite3_memory_used(void); +SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag); + +/* +** CAPI3REF: Pseudo-Random Number Generator +** +** SQLite contains a high-quality pseudo-random number generator (PRNG) used to +** select random [ROWID | ROWIDs] when inserting new records into a table that +** already uses the largest possible [ROWID]. The PRNG is also used for +** the built-in random() and randomblob() SQL functions. This interface allows +** applications to access the same PRNG for other purposes. +** +** ^A call to this routine stores N bytes of randomness into buffer P. +** ^The P parameter can be a NULL pointer. +** +** ^If this routine has not been previously called or if the previous +** call had N less than one or a NULL pointer for P, then the PRNG is +** seeded using randomness obtained from the xRandomness method of +** the default [sqlite3_vfs] object. +** ^If the previous call to this routine had an N of 1 or more and a +** non-NULL P then the pseudo-randomness is generated +** internally and without recourse to the [sqlite3_vfs] xRandomness +** method. +*/ +SQLITE_API void sqlite3_randomness(int N, void *P); + +/* +** CAPI3REF: Compile-Time Authorization Callbacks +** METHOD: sqlite3 +** KEYWORDS: {authorizer callback} +** +** ^This routine registers an authorizer callback with a particular +** [database connection], supplied in the first argument. +** ^The authorizer callback is invoked as SQL statements are being compiled +** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()], +** [sqlite3_prepare_v3()], [sqlite3_prepare16()], [sqlite3_prepare16_v2()], +** and [sqlite3_prepare16_v3()]. ^At various +** points during the compilation process, as logic is being created +** to perform various actions, the authorizer callback is invoked to +** see if those actions are allowed. ^The authorizer callback should +** return [SQLITE_OK] to allow the action, [SQLITE_IGNORE] to disallow the +** specific action but allow the SQL statement to continue to be +** compiled, or [SQLITE_DENY] to cause the entire SQL statement to be +** rejected with an error. ^If the authorizer callback returns +** any value other than [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY] +** then the [sqlite3_prepare_v2()] or equivalent call that triggered +** the authorizer will fail with an error message. +** +** When the callback returns [SQLITE_OK], that means the operation +** requested is ok. ^When the callback returns [SQLITE_DENY], the +** [sqlite3_prepare_v2()] or equivalent call that triggered the +** authorizer will fail with an error message explaining that +** access is denied. +** +** ^The first parameter to the authorizer callback is a copy of the third +** parameter to the sqlite3_set_authorizer() interface. ^The second parameter +** to the callback is an integer [SQLITE_COPY | action code] that specifies +** the particular action to be authorized. ^The third through sixth parameters +** to the callback are either NULL pointers or zero-terminated strings +** that contain additional details about the action to be authorized. +** Applications must always be prepared to encounter a NULL pointer in any +** of the third through the sixth parameters of the authorization callback. +** +** ^If the action code is [SQLITE_READ] +** and the callback returns [SQLITE_IGNORE] then the +** [prepared statement] statement is constructed to substitute +** a NULL value in place of the table column that would have +** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE] +** return can be used to deny an untrusted user access to individual +** columns of a table. +** ^When a table is referenced by a [SELECT] but no column values are +** extracted from that table (for example in a query like +** "SELECT count(*) FROM tab") then the [SQLITE_READ] authorizer callback +** is invoked once for that table with a column name that is an empty string. +** ^If the action code is [SQLITE_DELETE] and the callback returns +** [SQLITE_IGNORE] then the [DELETE] operation proceeds but the +** [truncate optimization] is disabled and all rows are deleted individually. +** +** An authorizer is used when [sqlite3_prepare | preparing] +** SQL statements from an untrusted source, to ensure that the SQL statements +** do not try to access data they are not allowed to see, or that they do not +** try to execute malicious statements that damage the database. For +** example, an application may allow a user to enter arbitrary +** SQL queries for evaluation by a database. But the application does +** not want the user to be able to make arbitrary changes to the +** database. An authorizer could then be put in place while the +** user-entered SQL is being [sqlite3_prepare | prepared] that +** disallows everything except [SELECT] statements. +** +** Applications that need to process SQL from untrusted sources +** might also consider lowering resource limits using [sqlite3_limit()] +** and limiting database size using the [max_page_count] [PRAGMA] +** in addition to using an authorizer. +** +** ^(Only a single authorizer can be in place on a database connection +** at a time. Each call to sqlite3_set_authorizer overrides the +** previous call.)^ ^Disable the authorizer by installing a NULL callback. +** The authorizer is disabled by default. +** +** The authorizer callback must not do anything that will modify +** the database connection that invoked the authorizer callback. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** ^When [sqlite3_prepare_v2()] is used to prepare a statement, the +** statement might be re-prepared during [sqlite3_step()] due to a +** schema change. Hence, the application should ensure that the +** correct authorizer callback remains in place during the [sqlite3_step()]. +** +** ^Note that the authorizer callback is invoked only during +** [sqlite3_prepare()] or its variants. Authorization is not +** performed during statement evaluation in [sqlite3_step()], unless +** as stated in the previous paragraph, sqlite3_step() invokes +** sqlite3_prepare_v2() to reprepare a statement after a schema change. +*/ +SQLITE_API int sqlite3_set_authorizer( + sqlite3*, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pUserData +); + +/* +** CAPI3REF: Authorizer Return Codes +** +** The [sqlite3_set_authorizer | authorizer callback function] must +** return either [SQLITE_OK] or one of these two constants in order +** to signal SQLite whether or not the action is permitted. See the +** [sqlite3_set_authorizer | authorizer documentation] for additional +** information. +** +** Note that SQLITE_IGNORE is also used as a [conflict resolution mode] +** returned from the [sqlite3_vtab_on_conflict()] interface. +*/ +#define SQLITE_DENY 1 /* Abort the SQL statement with an error */ +#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ + +/* +** CAPI3REF: Authorizer Action Codes +** +** The [sqlite3_set_authorizer()] interface registers a callback function +** that is invoked to authorize certain SQL statement actions. The +** second parameter to the callback is an integer code that specifies +** what action is being authorized. These are the integer action codes that +** the authorizer callback may be passed. +** +** These action code values signify what kind of operation is to be +** authorized. The 3rd and 4th parameters to the authorization +** callback function will be parameters or NULL depending on which of these +** codes is used as the second parameter. ^(The 5th parameter to the +** authorizer callback is the name of the database ("main", "temp", +** etc.) if applicable.)^ ^The 6th parameter to the authorizer callback +** is the name of the inner-most trigger or view that is responsible for +** the access attempt or NULL if this access attempt is directly from +** top-level SQL code. +*/ +/******************************************* 3rd ************ 4th ***********/ +#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */ +#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */ +#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */ +#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */ +#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */ +#define SQLITE_CREATE_VIEW 8 /* View Name NULL */ +#define SQLITE_DELETE 9 /* Table Name NULL */ +#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */ +#define SQLITE_DROP_TABLE 11 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */ +#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */ +#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */ +#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */ +#define SQLITE_DROP_VIEW 17 /* View Name NULL */ +#define SQLITE_INSERT 18 /* Table Name NULL */ +#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */ +#define SQLITE_READ 20 /* Table Name Column Name */ +#define SQLITE_SELECT 21 /* NULL NULL */ +#define SQLITE_TRANSACTION 22 /* Operation NULL */ +#define SQLITE_UPDATE 23 /* Table Name Column Name */ +#define SQLITE_ATTACH 24 /* Filename NULL */ +#define SQLITE_DETACH 25 /* Database Name NULL */ +#define SQLITE_ALTER_TABLE 26 /* Database Name Table Name */ +#define SQLITE_REINDEX 27 /* Index Name NULL */ +#define SQLITE_ANALYZE 28 /* Table Name NULL */ +#define SQLITE_CREATE_VTABLE 29 /* Table Name Module Name */ +#define SQLITE_DROP_VTABLE 30 /* Table Name Module Name */ +#define SQLITE_FUNCTION 31 /* NULL Function Name */ +#define SQLITE_SAVEPOINT 32 /* Operation Savepoint Name */ +#define SQLITE_COPY 0 /* No longer used */ +#define SQLITE_RECURSIVE 33 /* NULL NULL */ + +/* +** CAPI3REF: Tracing And Profiling Functions +** METHOD: sqlite3 +** +** These routines are deprecated. Use the [sqlite3_trace_v2()] interface +** instead of the routines described here. +** +** These routines register callback functions that can be used for +** tracing and profiling the execution of SQL statements. +** +** ^The callback function registered by sqlite3_trace() is invoked at +** various times when an SQL statement is being run by [sqlite3_step()]. +** ^The sqlite3_trace() callback is invoked with a UTF-8 rendering of the +** SQL statement text as the statement first begins executing. +** ^(Additional sqlite3_trace() callbacks might occur +** as each triggered subprogram is entered. The callbacks for triggers +** contain a UTF-8 SQL comment that identifies the trigger.)^ +** +** The [SQLITE_TRACE_SIZE_LIMIT] compile-time option can be used to limit +** the length of [bound parameter] expansion in the output of sqlite3_trace(). +** +** ^The callback function registered by sqlite3_profile() is invoked +** as each SQL statement finishes. ^The profile callback contains +** the original statement text and an estimate of wall-clock time +** of how long that statement took to run. ^The profile callback +** time is in units of nanoseconds, however the current implementation +** is only capable of millisecond resolution so the six least significant +** digits in the time are meaningless. Future versions of SQLite +** might provide greater resolution on the profiler callback. Invoking +** either [sqlite3_trace()] or [sqlite3_trace_v2()] will cancel the +** profile callback. +*/ +SQLITE_API SQLITE_DEPRECATED void *sqlite3_trace(sqlite3*, + void(*xTrace)(void*,const char*), void*); +SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, + void(*xProfile)(void*,const char*,sqlite3_uint64), void*); + +/* +** CAPI3REF: SQL Trace Event Codes +** KEYWORDS: SQLITE_TRACE +** +** These constants identify classes of events that can be monitored +** using the [sqlite3_trace_v2()] tracing logic. The M argument +** to [sqlite3_trace_v2(D,M,X,P)] is an OR-ed combination of one or more of +** the following constants. ^The first argument to the trace callback +** is one of the following constants. +** +** New tracing constants may be added in future releases. +** +** ^A trace callback has four arguments: xCallback(T,C,P,X). +** ^The T argument is one of the integer type codes above. +** ^The C argument is a copy of the context pointer passed in as the +** fourth argument to [sqlite3_trace_v2()]. +** The P and X arguments are pointers whose meanings depend on T. +** +** <dl> +** [[SQLITE_TRACE_STMT]] <dt>SQLITE_TRACE_STMT</dt> +** <dd>^An SQLITE_TRACE_STMT callback is invoked when a prepared statement +** first begins running and possibly at other times during the +** execution of the prepared statement, such as at the start of each +** trigger subprogram. ^The P argument is a pointer to the +** [prepared statement]. ^The X argument is a pointer to a string which +** is the unexpanded SQL text of the prepared statement or an SQL comment +** that indicates the invocation of a trigger. ^The callback can compute +** the same text that would have been returned by the legacy [sqlite3_trace()] +** interface by using the X argument when X begins with "--" and invoking +** [sqlite3_expanded_sql(P)] otherwise. +** +** [[SQLITE_TRACE_PROFILE]] <dt>SQLITE_TRACE_PROFILE</dt> +** <dd>^An SQLITE_TRACE_PROFILE callback provides approximately the same +** information as is provided by the [sqlite3_profile()] callback. +** ^The P argument is a pointer to the [prepared statement] and the +** X argument points to a 64-bit integer which is approximately +** the number of nanoseconds that the prepared statement took to run. +** ^The SQLITE_TRACE_PROFILE callback is invoked when the statement finishes. +** +** [[SQLITE_TRACE_ROW]] <dt>SQLITE_TRACE_ROW</dt> +** <dd>^An SQLITE_TRACE_ROW callback is invoked whenever a prepared +** statement generates a single row of result. +** ^The P argument is a pointer to the [prepared statement] and the +** X argument is unused. +** +** [[SQLITE_TRACE_CLOSE]] <dt>SQLITE_TRACE_CLOSE</dt> +** <dd>^An SQLITE_TRACE_CLOSE callback is invoked when a database +** connection closes. +** ^The P argument is a pointer to the [database connection] object +** and the X argument is unused. +** </dl> +*/ +#define SQLITE_TRACE_STMT 0x01 +#define SQLITE_TRACE_PROFILE 0x02 +#define SQLITE_TRACE_ROW 0x04 +#define SQLITE_TRACE_CLOSE 0x08 + +/* +** CAPI3REF: SQL Trace Hook +** METHOD: sqlite3 +** +** ^The sqlite3_trace_v2(D,M,X,P) interface registers a trace callback +** function X against [database connection] D, using property mask M +** and context pointer P. ^If the X callback is +** NULL or if the M mask is zero, then tracing is disabled. The +** M argument should be the bitwise OR-ed combination of +** zero or more [SQLITE_TRACE] constants. +** +** ^Each call to either sqlite3_trace(D,X,P) or sqlite3_trace_v2(D,M,X,P) +** overrides (cancels) all prior calls to sqlite3_trace(D,X,P) or +** sqlite3_trace_v2(D,M,X,P) for the [database connection] D. Each +** database connection may have at most one trace callback. +** +** ^The X callback is invoked whenever any of the events identified by +** mask M occur. ^The integer return value from the callback is currently +** ignored, though this may change in future releases. Callback +** implementations should return zero to ensure future compatibility. +** +** ^A trace callback is invoked with four arguments: callback(T,C,P,X). +** ^The T argument is one of the [SQLITE_TRACE] +** constants to indicate why the callback was invoked. +** ^The C argument is a copy of the context pointer. +** The P and X arguments are pointers whose meanings depend on T. +** +** The sqlite3_trace_v2() interface is intended to replace the legacy +** interfaces [sqlite3_trace()] and [sqlite3_profile()], both of which +** are deprecated. +*/ +SQLITE_API int sqlite3_trace_v2( + sqlite3*, + unsigned uMask, + int(*xCallback)(unsigned,void*,void*,void*), + void *pCtx +); + +/* +** CAPI3REF: Query Progress Callbacks +** METHOD: sqlite3 +** +** ^The sqlite3_progress_handler(D,N,X,P) interface causes the callback +** function X to be invoked periodically during long running calls to +** [sqlite3_step()] and [sqlite3_prepare()] and similar for +** database connection D. An example use for this +** interface is to keep a GUI updated during a large query. +** +** ^The parameter P is passed through as the only parameter to the +** callback function X. ^The parameter N is the approximate number of +** [virtual machine instructions] that are evaluated between successive +** invocations of the callback X. ^If N is less than one then the progress +** handler is disabled. +** +** ^Only a single progress handler may be defined at one time per +** [database connection]; setting a new progress handler cancels the +** old one. ^Setting parameter X to NULL disables the progress handler. +** ^The progress handler is also disabled by setting N to a value less +** than 1. +** +** ^If the progress callback returns non-zero, the operation is +** interrupted. This feature can be used to implement a +** "Cancel" button on a GUI progress dialog box. +** +** The progress handler callback must not do anything that will modify +** the database connection that invoked the progress handler. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** The progress handler callback would originally only be invoked from the +** bytecode engine. It still might be invoked during [sqlite3_prepare()] +** and similar because those routines might force a reparse of the schema +** which involves running the bytecode engine. However, beginning with +** SQLite version 3.41.0, the progress handler callback might also be +** invoked directly from [sqlite3_prepare()] while analyzing and generating +** code for complex queries. +*/ +SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); + +/* +** CAPI3REF: Opening A New Database Connection +** CONSTRUCTOR: sqlite3 +** +** ^These routines open an SQLite database file as specified by the +** filename argument. ^The filename argument is interpreted as UTF-8 for +** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte +** order for sqlite3_open16(). ^(A [database connection] handle is usually +** returned in *ppDb, even if an error occurs. The only exception is that +** if SQLite is unable to allocate memory to hold the [sqlite3] object, +** a NULL will be written into *ppDb instead of a pointer to the [sqlite3] +** object.)^ ^(If the database is opened (and/or created) successfully, then +** [SQLITE_OK] is returned. Otherwise an [error code] is returned.)^ ^The +** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain +** an English language description of the error following a failure of any +** of the sqlite3_open() routines. +** +** ^The default encoding will be UTF-8 for databases created using +** sqlite3_open() or sqlite3_open_v2(). ^The default encoding for databases +** created using sqlite3_open16() will be UTF-16 in the native byte order. +** +** Whether or not an error occurs when it is opened, resources +** associated with the [database connection] handle should be released by +** passing it to [sqlite3_close()] when it is no longer required. +** +** The sqlite3_open_v2() interface works like sqlite3_open() +** except that it accepts two additional parameters for additional control +** over the new database connection. ^(The flags parameter to +** sqlite3_open_v2() must include, at a minimum, one of the following +** three flag combinations:)^ +** +** <dl> +** ^(<dt>[SQLITE_OPEN_READONLY]</dt> +** <dd>The database is opened in read-only mode. If the database does +** not already exist, an error is returned.</dd>)^ +** +** ^(<dt>[SQLITE_OPEN_READWRITE]</dt> +** <dd>The database is opened for reading and writing if possible, or +** reading only if the file is write protected by the operating +** system. In either case the database must already exist, otherwise +** an error is returned. For historical reasons, if opening in +** read-write mode fails due to OS-level permissions, an attempt is +** made to open it in read-only mode. [sqlite3_db_readonly()] can be +** used to determine whether the database is actually +** read-write.</dd>)^ +** +** ^(<dt>[SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]</dt> +** <dd>The database is opened for reading and writing, and is created if +** it does not already exist. This is the behavior that is always used for +** sqlite3_open() and sqlite3_open16().</dd>)^ +** </dl> +** +** In addition to the required flags, the following optional flags are +** also supported: +** +** <dl> +** ^(<dt>[SQLITE_OPEN_URI]</dt> +** <dd>The filename can be interpreted as a URI if this flag is set.</dd>)^ +** +** ^(<dt>[SQLITE_OPEN_MEMORY]</dt> +** <dd>The database will be opened as an in-memory database. The database +** is named by the "filename" argument for the purposes of cache-sharing, +** if shared cache mode is enabled, but the "filename" is otherwise ignored. +** </dd>)^ +** +** ^(<dt>[SQLITE_OPEN_NOMUTEX]</dt> +** <dd>The new database connection will use the "multi-thread" +** [threading mode].)^ This means that separate threads are allowed +** to use SQLite at the same time, as long as each thread is using +** a different [database connection]. +** +** ^(<dt>[SQLITE_OPEN_FULLMUTEX]</dt> +** <dd>The new database connection will use the "serialized" +** [threading mode].)^ This means the multiple threads can safely +** attempt to use the same database connection at the same time. +** (Mutexes will block any actual concurrency, but in this mode +** there is no harm in trying.) +** +** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt> +** <dd>The database is opened [shared cache] enabled, overriding +** the default shared cache setting provided by +** [sqlite3_enable_shared_cache()].)^ +** The [use of shared cache mode is discouraged] and hence shared cache +** capabilities may be omitted from many builds of SQLite. In such cases, +** this option is a no-op. +** +** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt> +** <dd>The database is opened [shared cache] disabled, overriding +** the default shared cache setting provided by +** [sqlite3_enable_shared_cache()].)^ +** +** [[OPEN_EXRESCODE]] ^(<dt>[SQLITE_OPEN_EXRESCODE]</dt> +** <dd>The database connection comes up in "extended result code mode". +** In other words, the database behaves has if +** [sqlite3_extended_result_codes(db,1)] where called on the database +** connection as soon as the connection is created. In addition to setting +** the extended result code mode, this flag also causes [sqlite3_open_v2()] +** to return an extended result code.</dd> +** +** [[OPEN_NOFOLLOW]] ^(<dt>[SQLITE_OPEN_NOFOLLOW]</dt> +** <dd>The database filename is not allowed to contain a symbolic link</dd> +** </dl>)^ +** +** If the 3rd parameter to sqlite3_open_v2() is not one of the +** required combinations shown above optionally combined with other +** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits] +** then the behavior is undefined. Historic versions of SQLite +** have silently ignored surplus bits in the flags parameter to +** sqlite3_open_v2(), however that behavior might not be carried through +** into future versions of SQLite and so applications should not rely +** upon it. Note in particular that the SQLITE_OPEN_EXCLUSIVE flag is a no-op +** for sqlite3_open_v2(). The SQLITE_OPEN_EXCLUSIVE does *not* cause +** the open to fail if the database already exists. The SQLITE_OPEN_EXCLUSIVE +** flag is intended for use by the [sqlite3_vfs|VFS interface] only, and not +** by sqlite3_open_v2(). +** +** ^The fourth parameter to sqlite3_open_v2() is the name of the +** [sqlite3_vfs] object that defines the operating system interface that +** the new database connection should use. ^If the fourth parameter is +** a NULL pointer then the default [sqlite3_vfs] object is used. +** +** ^If the filename is ":memory:", then a private, temporary in-memory database +** is created for the connection. ^This in-memory database will vanish when +** the database connection is closed. Future versions of SQLite might +** make use of additional special filenames that begin with the ":" character. +** It is recommended that when a database filename actually does begin with +** a ":" character you should prefix the filename with a pathname such as +** "./" to avoid ambiguity. +** +** ^If the filename is an empty string, then a private, temporary +** on-disk database will be created. ^This private database will be +** automatically deleted as soon as the database connection is closed. +** +** [[URI filenames in sqlite3_open()]] <h3>URI Filenames</h3> +** +** ^If [URI filename] interpretation is enabled, and the filename argument +** begins with "file:", then the filename is interpreted as a URI. ^URI +** filename interpretation is enabled if the [SQLITE_OPEN_URI] flag is +** set in the third argument to sqlite3_open_v2(), or if it has +** been enabled globally using the [SQLITE_CONFIG_URI] option with the +** [sqlite3_config()] method or by the [SQLITE_USE_URI] compile-time option. +** URI filename interpretation is turned off +** by default, but future releases of SQLite might enable URI filename +** interpretation by default. See "[URI filenames]" for additional +** information. +** +** URI filenames are parsed according to RFC 3986. ^If the URI contains an +** authority, then it must be either an empty string or the string +** "localhost". ^If the authority is not an empty string or "localhost", an +** error is returned to the caller. ^The fragment component of a URI, if +** present, is ignored. +** +** ^SQLite uses the path component of the URI as the name of the disk file +** which contains the database. ^If the path begins with a '/' character, +** then it is interpreted as an absolute path. ^If the path does not begin +** with a '/' (meaning that the authority section is omitted from the URI) +** then the path is interpreted as a relative path. +** ^(On windows, the first component of an absolute path +** is a drive specification (e.g. "C:").)^ +** +** [[core URI query parameters]] +** The query component of a URI may contain parameters that are interpreted +** either by SQLite itself, or by a [VFS | custom VFS implementation]. +** SQLite and its built-in [VFSes] interpret the +** following query parameters: +** +** <ul> +** <li> <b>vfs</b>: ^The "vfs" parameter may be used to specify the name of +** a VFS object that provides the operating system interface that should +** be used to access the database file on disk. ^If this option is set to +** an empty string the default VFS object is used. ^Specifying an unknown +** VFS is an error. ^If sqlite3_open_v2() is used and the vfs option is +** present, then the VFS specified by the option takes precedence over +** the value passed as the fourth parameter to sqlite3_open_v2(). +** +** <li> <b>mode</b>: ^(The mode parameter may be set to either "ro", "rw", +** "rwc", or "memory". Attempting to set it to any other value is +** an error)^. +** ^If "ro" is specified, then the database is opened for read-only +** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the +** third argument to sqlite3_open_v2(). ^If the mode option is set to +** "rw", then the database is opened for read-write (but not create) +** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had +** been set. ^Value "rwc" is equivalent to setting both +** SQLITE_OPEN_READWRITE and SQLITE_OPEN_CREATE. ^If the mode option is +** set to "memory" then a pure [in-memory database] that never reads +** or writes from disk is used. ^It is an error to specify a value for +** the mode parameter that is less restrictive than that specified by +** the flags passed in the third parameter to sqlite3_open_v2(). +** +** <li> <b>cache</b>: ^The cache parameter may be set to either "shared" or +** "private". ^Setting it to "shared" is equivalent to setting the +** SQLITE_OPEN_SHAREDCACHE bit in the flags argument passed to +** sqlite3_open_v2(). ^Setting the cache parameter to "private" is +** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit. +** ^If sqlite3_open_v2() is used and the "cache" parameter is present in +** a URI filename, its value overrides any behavior requested by setting +** SQLITE_OPEN_PRIVATECACHE or SQLITE_OPEN_SHAREDCACHE flag. +** +** <li> <b>psow</b>: ^The psow parameter indicates whether or not the +** [powersafe overwrite] property does or does not apply to the +** storage media on which the database file resides. +** +** <li> <b>nolock</b>: ^The nolock parameter is a boolean query parameter +** which if set disables file locking in rollback journal modes. This +** is useful for accessing a database on a filesystem that does not +** support locking. Caution: Database corruption might result if two +** or more processes write to the same database and any one of those +** processes uses nolock=1. +** +** <li> <b>immutable</b>: ^The immutable parameter is a boolean query +** parameter that indicates that the database file is stored on +** read-only media. ^When immutable is set, SQLite assumes that the +** database file cannot be changed, even by a process with higher +** privilege, and so the database is opened read-only and all locking +** and change detection is disabled. Caution: Setting the immutable +** property on a database file that does in fact change can result +** in incorrect query results and/or [SQLITE_CORRUPT] errors. +** See also: [SQLITE_IOCAP_IMMUTABLE]. +** +** </ul> +** +** ^Specifying an unknown parameter in the query component of a URI is not an +** error. Future versions of SQLite might understand additional query +** parameters. See "[query parameters with special meaning to SQLite]" for +** additional information. +** +** [[URI filename examples]] <h3>URI filename examples</h3> +** +** <table border="1" align=center cellpadding=5> +** <tr><th> URI filenames <th> Results +** <tr><td> file:data.db <td> +** Open the file "data.db" in the current directory. +** <tr><td> file:/home/fred/data.db<br> +** file:///home/fred/data.db <br> +** file://localhost/home/fred/data.db <br> <td> +** Open the database file "/home/fred/data.db". +** <tr><td> file://darkstar/home/fred/data.db <td> +** An error. "darkstar" is not a recognized authority. +** <tr><td style="white-space:nowrap"> +** file:///C:/Documents%20and%20Settings/fred/Desktop/data.db +** <td> Windows only: Open the file "data.db" on fred's desktop on drive +** C:. Note that the %20 escaping in this example is not strictly +** necessary - space characters can be used literally +** in URI filenames. +** <tr><td> file:data.db?mode=ro&cache=private <td> +** Open file "data.db" in the current directory for read-only access. +** Regardless of whether or not shared-cache mode is enabled by +** default, use a private cache. +** <tr><td> file:/home/fred/data.db?vfs=unix-dotfile <td> +** Open file "/home/fred/data.db". Use the special VFS "unix-dotfile" +** that uses dot-files in place of posix advisory locking. +** <tr><td> file:data.db?mode=readonly <td> +** An error. "readonly" is not a valid option for the "mode" parameter. +** Use "ro" instead: "file:data.db?mode=ro". +** </table> +** +** ^URI hexadecimal escape sequences (%HH) are supported within the path and +** query components of a URI. A hexadecimal escape sequence consists of a +** percent sign - "%" - followed by exactly two hexadecimal digits +** specifying an octet value. ^Before the path or query components of a +** URI filename are interpreted, they are encoded using UTF-8 and all +** hexadecimal escape sequences replaced by a single byte containing the +** corresponding octet. If this process generates an invalid UTF-8 encoding, +** the results are undefined. +** +** <b>Note to Windows users:</b> The encoding used for the filename argument +** of sqlite3_open() and sqlite3_open_v2() must be UTF-8, not whatever +** codepage is currently defined. Filenames containing international +** characters must be converted to UTF-8 prior to passing them into +** sqlite3_open() or sqlite3_open_v2(). +** +** <b>Note to Windows Runtime users:</b> The temporary directory must be set +** prior to calling sqlite3_open() or sqlite3_open_v2(). Otherwise, various +** features that require the use of temporary files may fail. +** +** See also: [sqlite3_temp_directory] +*/ +SQLITE_API int sqlite3_open( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +SQLITE_API int sqlite3_open16( + const void *filename, /* Database filename (UTF-16) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +SQLITE_API int sqlite3_open_v2( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb, /* OUT: SQLite db handle */ + int flags, /* Flags */ + const char *zVfs /* Name of VFS module to use */ +); + +/* +** CAPI3REF: Obtain Values For URI Parameters +** +** These are utility routines, useful to [VFS|custom VFS implementations], +** that check if a database file was a URI that contained a specific query +** parameter, and if so obtains the value of that query parameter. +** +** The first parameter to these interfaces (hereafter referred to +** as F) must be one of: +** <ul> +** <li> A database filename pointer created by the SQLite core and +** passed into the xOpen() method of a VFS implementation, or +** <li> A filename obtained from [sqlite3_db_filename()], or +** <li> A new filename constructed using [sqlite3_create_filename()]. +** </ul> +** If the F parameter is not one of the above, then the behavior is +** undefined and probably undesirable. Older versions of SQLite were +** more tolerant of invalid F parameters than newer versions. +** +** If F is a suitable filename (as described in the previous paragraph) +** and if P is the name of the query parameter, then +** sqlite3_uri_parameter(F,P) returns the value of the P +** parameter if it exists or a NULL pointer if P does not appear as a +** query parameter on F. If P is a query parameter of F and it +** has no explicit value, then sqlite3_uri_parameter(F,P) returns +** a pointer to an empty string. +** +** The sqlite3_uri_boolean(F,P,B) routine assumes that P is a boolean +** parameter and returns true (1) or false (0) according to the value +** of P. The sqlite3_uri_boolean(F,P,B) routine returns true (1) if the +** value of query parameter P is one of "yes", "true", or "on" in any +** case or if the value begins with a non-zero number. The +** sqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of +** query parameter P is one of "no", "false", or "off" in any case or +** if the value begins with a numeric zero. If P is not a query +** parameter on F or if the value of P does not match any of the +** above, then sqlite3_uri_boolean(F,P,B) returns (B!=0). +** +** The sqlite3_uri_int64(F,P,D) routine converts the value of P into a +** 64-bit signed integer and returns that integer, or D if P does not +** exist. If the value of P is something other than an integer, then +** zero is returned. +** +** The sqlite3_uri_key(F,N) returns a pointer to the name (not +** the value) of the N-th query parameter for filename F, or a NULL +** pointer if N is less than zero or greater than the number of query +** parameters minus 1. The N value is zero-based so N should be 0 to obtain +** the name of the first query parameter, 1 for the second parameter, and +** so forth. +** +** If F is a NULL pointer, then sqlite3_uri_parameter(F,P) returns NULL and +** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and +** is not a database file pathname pointer that the SQLite core passed +** into the xOpen VFS method, then the behavior of this routine is undefined +** and probably undesirable. +** +** Beginning with SQLite [version 3.31.0] ([dateof:3.31.0]) the input F +** parameter can also be the name of a rollback journal file or WAL file +** in addition to the main database file. Prior to version 3.31.0, these +** routines would only work if F was the name of the main database file. +** When the F parameter is the name of the rollback journal or WAL file, +** it has access to all the same query parameters as were found on the +** main database file. +** +** See the [URI filename] documentation for additional information. +*/ +SQLITE_API const char *sqlite3_uri_parameter(sqlite3_filename z, const char *zParam); +SQLITE_API int sqlite3_uri_boolean(sqlite3_filename z, const char *zParam, int bDefault); +SQLITE_API sqlite3_int64 sqlite3_uri_int64(sqlite3_filename, const char*, sqlite3_int64); +SQLITE_API const char *sqlite3_uri_key(sqlite3_filename z, int N); + +/* +** CAPI3REF: Translate filenames +** +** These routines are available to [VFS|custom VFS implementations] for +** translating filenames between the main database file, the journal file, +** and the WAL file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** passed by the SQLite core into the VFS, then sqlite3_filename_database(F) +** returns the name of the corresponding database file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** passed by the SQLite core into the VFS, or if F is a database filename +** obtained from [sqlite3_db_filename()], then sqlite3_filename_journal(F) +** returns the name of the corresponding rollback journal file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** that was passed by the SQLite core into the VFS, or if F is a database +** filename obtained from [sqlite3_db_filename()], then +** sqlite3_filename_wal(F) returns the name of the corresponding +** WAL file. +** +** In all of the above, if F is not the name of a database, journal or WAL +** filename passed into the VFS from the SQLite core and F is not the +** return value from [sqlite3_db_filename()], then the result is +** undefined and is likely a memory access violation. +*/ +SQLITE_API const char *sqlite3_filename_database(sqlite3_filename); +SQLITE_API const char *sqlite3_filename_journal(sqlite3_filename); +SQLITE_API const char *sqlite3_filename_wal(sqlite3_filename); + +/* +** CAPI3REF: Database File Corresponding To A Journal +** +** ^If X is the name of a rollback or WAL-mode journal file that is +** passed into the xOpen method of [sqlite3_vfs], then +** sqlite3_database_file_object(X) returns a pointer to the [sqlite3_file] +** object that represents the main database file. +** +** This routine is intended for use in custom [VFS] implementations +** only. It is not a general-purpose interface. +** The argument sqlite3_file_object(X) must be a filename pointer that +** has been passed into [sqlite3_vfs].xOpen method where the +** flags parameter to xOpen contains one of the bits +** [SQLITE_OPEN_MAIN_JOURNAL] or [SQLITE_OPEN_WAL]. Any other use +** of this routine results in undefined and probably undesirable +** behavior. +*/ +SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*); + +/* +** CAPI3REF: Create and Destroy VFS Filenames +** +** These interfaces are provided for use by [VFS shim] implementations and +** are not useful outside of that context. +** +** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of +** database filename D with corresponding journal file J and WAL file W and +** with N URI parameters key/values pairs in the array P. The result from +** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that +** is safe to pass to routines like: +** <ul> +** <li> [sqlite3_uri_parameter()], +** <li> [sqlite3_uri_boolean()], +** <li> [sqlite3_uri_int64()], +** <li> [sqlite3_uri_key()], +** <li> [sqlite3_filename_database()], +** <li> [sqlite3_filename_journal()], or +** <li> [sqlite3_filename_wal()]. +** </ul> +** If a memory allocation error occurs, sqlite3_create_filename() might +** return a NULL pointer. The memory obtained from sqlite3_create_filename(X) +** must be released by a corresponding call to sqlite3_free_filename(Y). +** +** The P parameter in sqlite3_create_filename(D,J,W,N,P) should be an array +** of 2*N pointers to strings. Each pair of pointers in this array corresponds +** to a key and value for a query parameter. The P parameter may be a NULL +** pointer if N is zero. None of the 2*N pointers in the P array may be +** NULL pointers and key pointers should not be empty strings. +** None of the D, J, or W parameters to sqlite3_create_filename(D,J,W,N,P) may +** be NULL pointers, though they can be empty strings. +** +** The sqlite3_free_filename(Y) routine releases a memory allocation +** previously obtained from sqlite3_create_filename(). Invoking +** sqlite3_free_filename(Y) where Y is a NULL pointer is a harmless no-op. +** +** If the Y parameter to sqlite3_free_filename(Y) is anything other +** than a NULL pointer or a pointer previously acquired from +** sqlite3_create_filename(), then bad things such as heap +** corruption or segfaults may occur. The value Y should not be +** used again after sqlite3_free_filename(Y) has been called. This means +** that if the [sqlite3_vfs.xOpen()] method of a VFS has been called using Y, +** then the corresponding [sqlite3_module.xClose() method should also be +** invoked prior to calling sqlite3_free_filename(Y). +*/ +SQLITE_API sqlite3_filename sqlite3_create_filename( + const char *zDatabase, + const char *zJournal, + const char *zWal, + int nParam, + const char **azParam +); +SQLITE_API void sqlite3_free_filename(sqlite3_filename); + +/* +** CAPI3REF: Error Codes And Messages +** METHOD: sqlite3 +** +** ^If the most recent sqlite3_* API call associated with +** [database connection] D failed, then the sqlite3_errcode(D) interface +** returns the numeric [result code] or [extended result code] for that +** API call. +** ^The sqlite3_extended_errcode() +** interface is the same except that it always returns the +** [extended result code] even when extended result codes are +** disabled. +** +** The values returned by sqlite3_errcode() and/or +** sqlite3_extended_errcode() might change with each API call. +** Except, there are some interfaces that are guaranteed to never +** change the value of the error code. The error-code preserving +** interfaces include the following: +** +** <ul> +** <li> sqlite3_errcode() +** <li> sqlite3_extended_errcode() +** <li> sqlite3_errmsg() +** <li> sqlite3_errmsg16() +** <li> sqlite3_error_offset() +** </ul> +** +** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language +** text that describes the error, as either UTF-8 or UTF-16 respectively. +** ^(Memory to hold the error message string is managed internally. +** The application does not need to worry about freeing the result. +** However, the error string might be overwritten or deallocated by +** subsequent calls to other SQLite interface functions.)^ +** +** ^The sqlite3_errstr() interface returns the English-language text +** that describes the [result code], as UTF-8. +** ^(Memory to hold the error message string is managed internally +** and must not be freed by the application)^. +** +** ^If the most recent error references a specific token in the input +** SQL, the sqlite3_error_offset() interface returns the byte offset +** of the start of that token. ^The byte offset returned by +** sqlite3_error_offset() assumes that the input SQL is UTF8. +** ^If the most recent error does not reference a specific token in the input +** SQL, then the sqlite3_error_offset() function returns -1. +** +** When the serialized [threading mode] is in use, it might be the +** case that a second error occurs on a separate thread in between +** the time of the first error and the call to these interfaces. +** When that happens, the second error will be reported since these +** interfaces always report the most recent result. To avoid +** this, each thread can obtain exclusive use of the [database connection] D +** by invoking [sqlite3_mutex_enter]([sqlite3_db_mutex](D)) before beginning +** to use D and invoking [sqlite3_mutex_leave]([sqlite3_db_mutex](D)) after +** all calls to the interfaces listed here are completed. +** +** If an interface fails with SQLITE_MISUSE, that means the interface +** was invoked incorrectly by the application. In that case, the +** error code and message may or may not be set. +*/ +SQLITE_API int sqlite3_errcode(sqlite3 *db); +SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); +SQLITE_API const char *sqlite3_errmsg(sqlite3*); +SQLITE_API const void *sqlite3_errmsg16(sqlite3*); +SQLITE_API const char *sqlite3_errstr(int); +SQLITE_API int sqlite3_error_offset(sqlite3 *db); + +/* +** CAPI3REF: Prepared Statement Object +** KEYWORDS: {prepared statement} {prepared statements} +** +** An instance of this object represents a single SQL statement that +** has been compiled into binary form and is ready to be evaluated. +** +** Think of each SQL statement as a separate computer program. The +** original SQL text is source code. A prepared statement object +** is the compiled object code. All SQL must be converted into a +** prepared statement before it can be run. +** +** The life-cycle of a prepared statement object usually goes like this: +** +** <ol> +** <li> Create the prepared statement object using [sqlite3_prepare_v2()]. +** <li> Bind values to [parameters] using the sqlite3_bind_*() +** interfaces. +** <li> Run the SQL by calling [sqlite3_step()] one or more times. +** <li> Reset the prepared statement using [sqlite3_reset()] then go back +** to step 2. Do this zero or more times. +** <li> Destroy the object using [sqlite3_finalize()]. +** </ol> +*/ +typedef struct sqlite3_stmt sqlite3_stmt; + +/* +** CAPI3REF: Run-time Limits +** METHOD: sqlite3 +** +** ^(This interface allows the size of various constructs to be limited +** on a connection by connection basis. The first parameter is the +** [database connection] whose limit is to be set or queried. The +** second parameter is one of the [limit categories] that define a +** class of constructs to be size limited. The third parameter is the +** new limit for that construct.)^ +** +** ^If the new limit is a negative number, the limit is unchanged. +** ^(For each limit category SQLITE_LIMIT_<i>NAME</i> there is a +** [limits | hard upper bound] +** set at compile-time by a C preprocessor macro called +** [limits | SQLITE_MAX_<i>NAME</i>]. +** (The "_LIMIT_" in the name is changed to "_MAX_".))^ +** ^Attempts to increase a limit above its hard upper bound are +** silently truncated to the hard upper bound. +** +** ^Regardless of whether or not the limit was changed, the +** [sqlite3_limit()] interface returns the prior value of the limit. +** ^Hence, to find the current value of a limit without changing it, +** simply invoke this interface with the third parameter set to -1. +** +** Run-time limits are intended for use in applications that manage +** both their own internal database and also databases that are controlled +** by untrusted external sources. An example application might be a +** web browser that has its own databases for storing history and +** separate databases controlled by JavaScript applications downloaded +** off the Internet. The internal databases can be given the +** large, default limits. Databases managed by external sources can +** be given much smaller limits designed to prevent a denial of service +** attack. Developers might also want to use the [sqlite3_set_authorizer()] +** interface to further control untrusted SQL. The size of the database +** created by an untrusted script can be contained using the +** [max_page_count] [PRAGMA]. +** +** New run-time limit categories may be added in future releases. +*/ +SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); + +/* +** CAPI3REF: Run-Time Limit Categories +** KEYWORDS: {limit category} {*limit categories} +** +** These constants define various performance limits +** that can be lowered at run-time using [sqlite3_limit()]. +** The synopsis of the meanings of the various limits is shown below. +** Additional information is available at [limits | Limits in SQLite]. +** +** <dl> +** [[SQLITE_LIMIT_LENGTH]] ^(<dt>SQLITE_LIMIT_LENGTH</dt> +** <dd>The maximum size of any string or BLOB or table row, in bytes.<dd>)^ +** +** [[SQLITE_LIMIT_SQL_LENGTH]] ^(<dt>SQLITE_LIMIT_SQL_LENGTH</dt> +** <dd>The maximum length of an SQL statement, in bytes.</dd>)^ +** +** [[SQLITE_LIMIT_COLUMN]] ^(<dt>SQLITE_LIMIT_COLUMN</dt> +** <dd>The maximum number of columns in a table definition or in the +** result set of a [SELECT] or the maximum number of columns in an index +** or in an ORDER BY or GROUP BY clause.</dd>)^ +** +** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(<dt>SQLITE_LIMIT_EXPR_DEPTH</dt> +** <dd>The maximum depth of the parse tree on any expression.</dd>)^ +** +** [[SQLITE_LIMIT_COMPOUND_SELECT]] ^(<dt>SQLITE_LIMIT_COMPOUND_SELECT</dt> +** <dd>The maximum number of terms in a compound SELECT statement.</dd>)^ +** +** [[SQLITE_LIMIT_VDBE_OP]] ^(<dt>SQLITE_LIMIT_VDBE_OP</dt> +** <dd>The maximum number of instructions in a virtual machine program +** used to implement an SQL statement. If [sqlite3_prepare_v2()] or +** the equivalent tries to allocate space for more than this many opcodes +** in a single prepared statement, an SQLITE_NOMEM error is returned.</dd>)^ +** +** [[SQLITE_LIMIT_FUNCTION_ARG]] ^(<dt>SQLITE_LIMIT_FUNCTION_ARG</dt> +** <dd>The maximum number of arguments on a function.</dd>)^ +** +** [[SQLITE_LIMIT_ATTACHED]] ^(<dt>SQLITE_LIMIT_ATTACHED</dt> +** <dd>The maximum number of [ATTACH | attached databases].)^</dd> +** +** [[SQLITE_LIMIT_LIKE_PATTERN_LENGTH]] +** ^(<dt>SQLITE_LIMIT_LIKE_PATTERN_LENGTH</dt> +** <dd>The maximum length of the pattern argument to the [LIKE] or +** [GLOB] operators.</dd>)^ +** +** [[SQLITE_LIMIT_VARIABLE_NUMBER]] +** ^(<dt>SQLITE_LIMIT_VARIABLE_NUMBER</dt> +** <dd>The maximum index number of any [parameter] in an SQL statement.)^ +** +** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(<dt>SQLITE_LIMIT_TRIGGER_DEPTH</dt> +** <dd>The maximum depth of recursion for triggers.</dd>)^ +** +** [[SQLITE_LIMIT_WORKER_THREADS]] ^(<dt>SQLITE_LIMIT_WORKER_THREADS</dt> +** <dd>The maximum number of auxiliary worker threads that a single +** [prepared statement] may start.</dd>)^ +** </dl> +*/ +#define SQLITE_LIMIT_LENGTH 0 +#define SQLITE_LIMIT_SQL_LENGTH 1 +#define SQLITE_LIMIT_COLUMN 2 +#define SQLITE_LIMIT_EXPR_DEPTH 3 +#define SQLITE_LIMIT_COMPOUND_SELECT 4 +#define SQLITE_LIMIT_VDBE_OP 5 +#define SQLITE_LIMIT_FUNCTION_ARG 6 +#define SQLITE_LIMIT_ATTACHED 7 +#define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 +#define SQLITE_LIMIT_VARIABLE_NUMBER 9 +#define SQLITE_LIMIT_TRIGGER_DEPTH 10 +#define SQLITE_LIMIT_WORKER_THREADS 11 + +/* +** CAPI3REF: Prepare Flags +** +** These constants define various flags that can be passed into +** "prepFlags" parameter of the [sqlite3_prepare_v3()] and +** [sqlite3_prepare16_v3()] interfaces. +** +** New flags may be added in future releases of SQLite. +** +** <dl> +** [[SQLITE_PREPARE_PERSISTENT]] ^(<dt>SQLITE_PREPARE_PERSISTENT</dt> +** <dd>The SQLITE_PREPARE_PERSISTENT flag is a hint to the query planner +** that the prepared statement will be retained for a long time and +** probably reused many times.)^ ^Without this flag, [sqlite3_prepare_v3()] +** and [sqlite3_prepare16_v3()] assume that the prepared statement will +** be used just once or at most a few times and then destroyed using +** [sqlite3_finalize()] relatively soon. The current implementation acts +** on this hint by avoiding the use of [lookaside memory] so as not to +** deplete the limited store of lookaside memory. Future versions of +** SQLite may act on this hint differently. +** +** [[SQLITE_PREPARE_NORMALIZE]] <dt>SQLITE_PREPARE_NORMALIZE</dt> +** <dd>The SQLITE_PREPARE_NORMALIZE flag is a no-op. This flag used +** to be required for any prepared statement that wanted to use the +** [sqlite3_normalized_sql()] interface. However, the +** [sqlite3_normalized_sql()] interface is now available to all +** prepared statements, regardless of whether or not they use this +** flag. +** +** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt> +** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler +** to return an error (error code SQLITE_ERROR) if the statement uses +** any virtual tables. +** </dl> +*/ +#define SQLITE_PREPARE_PERSISTENT 0x01 +#define SQLITE_PREPARE_NORMALIZE 0x02 +#define SQLITE_PREPARE_NO_VTAB 0x04 + +/* +** CAPI3REF: Compiling An SQL Statement +** KEYWORDS: {SQL statement compiler} +** METHOD: sqlite3 +** CONSTRUCTOR: sqlite3_stmt +** +** To execute an SQL statement, it must first be compiled into a byte-code +** program using one of these routines. Or, in other words, these routines +** are constructors for the [prepared statement] object. +** +** The preferred routine to use is [sqlite3_prepare_v2()]. The +** [sqlite3_prepare()] interface is legacy and should be avoided. +** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used +** for special purposes. +** +** The use of the UTF-8 interfaces is preferred, as SQLite currently +** does all parsing using UTF-8. The UTF-16 interfaces are provided +** as a convenience. The UTF-16 interfaces work by converting the +** input text into UTF-8, then invoking the corresponding UTF-8 interface. +** +** The first argument, "db", is a [database connection] obtained from a +** prior successful call to [sqlite3_open()], [sqlite3_open_v2()] or +** [sqlite3_open16()]. The database connection must not have been closed. +** +** The second argument, "zSql", is the statement to be compiled, encoded +** as either UTF-8 or UTF-16. The sqlite3_prepare(), sqlite3_prepare_v2(), +** and sqlite3_prepare_v3() +** interfaces use UTF-8, and sqlite3_prepare16(), sqlite3_prepare16_v2(), +** and sqlite3_prepare16_v3() use UTF-16. +** +** ^If the nByte argument is negative, then zSql is read up to the +** first zero terminator. ^If nByte is positive, then it is the +** number of bytes read from zSql. ^If nByte is zero, then no prepared +** statement is generated. +** If the caller knows that the supplied string is nul-terminated, then +** there is a small performance advantage to passing an nByte parameter that +** is the number of bytes in the input string <i>including</i> +** the nul-terminator. +** +** ^If pzTail is not NULL then *pzTail is made to point to the first byte +** past the end of the first SQL statement in zSql. These routines only +** compile the first statement in zSql, so *pzTail is left pointing to +** what remains uncompiled. +** +** ^*ppStmt is left pointing to a compiled [prepared statement] that can be +** executed using [sqlite3_step()]. ^If there is an error, *ppStmt is set +** to NULL. ^If the input text contains no SQL (if the input is an empty +** string or a comment) then *ppStmt is set to NULL. +** The calling procedure is responsible for deleting the compiled +** SQL statement using [sqlite3_finalize()] after it has finished with it. +** ppStmt may not be NULL. +** +** ^On success, the sqlite3_prepare() family of routines return [SQLITE_OK]; +** otherwise an [error code] is returned. +** +** The sqlite3_prepare_v2(), sqlite3_prepare_v3(), sqlite3_prepare16_v2(), +** and sqlite3_prepare16_v3() interfaces are recommended for all new programs. +** The older interfaces (sqlite3_prepare() and sqlite3_prepare16()) +** are retained for backwards compatibility, but their use is discouraged. +** ^In the "vX" interfaces, the prepared statement +** that is returned (the [sqlite3_stmt] object) contains a copy of the +** original SQL text. This causes the [sqlite3_step()] interface to +** behave differently in three ways: +** +** <ol> +** <li> +** ^If the database schema changes, instead of returning [SQLITE_SCHEMA] as it +** always used to do, [sqlite3_step()] will automatically recompile the SQL +** statement and try to run it again. As many as [SQLITE_MAX_SCHEMA_RETRY] +** retries will occur before sqlite3_step() gives up and returns an error. +** </li> +** +** <li> +** ^When an error occurs, [sqlite3_step()] will return one of the detailed +** [error codes] or [extended error codes]. ^The legacy behavior was that +** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code +** and the application would have to make a second call to [sqlite3_reset()] +** in order to find the underlying cause of the problem. With the "v2" prepare +** interfaces, the underlying reason for the error is returned immediately. +** </li> +** +** <li> +** ^If the specific value bound to a [parameter | host parameter] in the +** WHERE clause might influence the choice of query plan for a statement, +** then the statement will be automatically recompiled, as if there had been +** a schema change, on the first [sqlite3_step()] call following any change +** to the [sqlite3_bind_text | bindings] of that [parameter]. +** ^The specific value of a WHERE-clause [parameter] might influence the +** choice of query plan if the parameter is the left-hand side of a [LIKE] +** or [GLOB] operator or if the parameter is compared to an indexed column +** and the [SQLITE_ENABLE_STAT4] compile-time option is enabled. +** </li> +** </ol> +** +** <p>^sqlite3_prepare_v3() differs from sqlite3_prepare_v2() only in having +** the extra prepFlags parameter, which is a bit array consisting of zero or +** more of the [SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_*] flags. ^The +** sqlite3_prepare_v2() interface works exactly the same as +** sqlite3_prepare_v3() with a zero prepFlags parameter. +*/ +SQLITE_API int sqlite3_prepare( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare_v2( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare_v3( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16_v2( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16_v3( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); + +/* +** CAPI3REF: Retrieving Statement SQL +** METHOD: sqlite3_stmt +** +** ^The sqlite3_sql(P) interface returns a pointer to a copy of the UTF-8 +** SQL text used to create [prepared statement] P if P was +** created by [sqlite3_prepare_v2()], [sqlite3_prepare_v3()], +** [sqlite3_prepare16_v2()], or [sqlite3_prepare16_v3()]. +** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8 +** string containing the SQL text of prepared statement P with +** [bound parameters] expanded. +** ^The sqlite3_normalized_sql(P) interface returns a pointer to a UTF-8 +** string containing the normalized SQL text of prepared statement P. The +** semantics used to normalize a SQL statement are unspecified and subject +** to change. At a minimum, literal values will be replaced with suitable +** placeholders. +** +** ^(For example, if a prepared statement is created using the SQL +** text "SELECT $abc,:xyz" and if parameter $abc is bound to integer 2345 +** and parameter :xyz is unbound, then sqlite3_sql() will return +** the original string, "SELECT $abc,:xyz" but sqlite3_expanded_sql() +** will return "SELECT 2345,NULL".)^ +** +** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory +** is available to hold the result, or if the result would exceed the +** the maximum string length determined by the [SQLITE_LIMIT_LENGTH]. +** +** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of +** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time +** option causes sqlite3_expanded_sql() to always return NULL. +** +** ^The strings returned by sqlite3_sql(P) and sqlite3_normalized_sql(P) +** are managed by SQLite and are automatically freed when the prepared +** statement is finalized. +** ^The string returned by sqlite3_expanded_sql(P), on the other hand, +** is obtained from [sqlite3_malloc()] and must be freed by the application +** by passing it to [sqlite3_free()]. +** +** ^The sqlite3_normalized_sql() interface is only available if +** the [SQLITE_ENABLE_NORMALIZE] compile-time option is defined. +*/ +SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); +SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); +#ifdef SQLITE_ENABLE_NORMALIZE +SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); +#endif + +/* +** CAPI3REF: Determine If An SQL Statement Writes The Database +** METHOD: sqlite3_stmt +** +** ^The sqlite3_stmt_readonly(X) interface returns true (non-zero) if +** and only if the [prepared statement] X makes no direct changes to +** the content of the database file. +** +** Note that [application-defined SQL functions] or +** [virtual tables] might change the database indirectly as a side effect. +** ^(For example, if an application defines a function "eval()" that +** calls [sqlite3_exec()], then the following SQL statement would +** change the database file through side-effects: +** +** <blockquote><pre> +** SELECT eval('DELETE FROM t1') FROM t2; +** </pre></blockquote> +** +** But because the [SELECT] statement does not change the database file +** directly, sqlite3_stmt_readonly() would still return true.)^ +** +** ^Transaction control statements such as [BEGIN], [COMMIT], [ROLLBACK], +** [SAVEPOINT], and [RELEASE] cause sqlite3_stmt_readonly() to return true, +** since the statements themselves do not actually modify the database but +** rather they control the timing of when other statements modify the +** database. ^The [ATTACH] and [DETACH] statements also cause +** sqlite3_stmt_readonly() to return true since, while those statements +** change the configuration of a database connection, they do not make +** changes to the content of the database files on disk. +** ^The sqlite3_stmt_readonly() interface returns true for [BEGIN] since +** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and +** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so +** sqlite3_stmt_readonly() returns false for those commands. +** +** ^This routine returns false if there is any possibility that the +** statement might change the database file. ^A false return does +** not guarantee that the statement will change the database file. +** ^For example, an UPDATE statement might have a WHERE clause that +** makes it a no-op, but the sqlite3_stmt_readonly() result would still +** be false. ^Similarly, a CREATE TABLE IF NOT EXISTS statement is a +** read-only no-op if the table already exists, but +** sqlite3_stmt_readonly() still returns false for such a statement. +** +** ^If prepared statement X is an [EXPLAIN] or [EXPLAIN QUERY PLAN] +** statement, then sqlite3_stmt_readonly(X) returns the same value as +** if the EXPLAIN or EXPLAIN QUERY PLAN prefix were omitted. +*/ +SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Query The EXPLAIN Setting For A Prepared Statement +** METHOD: sqlite3_stmt +** +** ^The sqlite3_stmt_isexplain(S) interface returns 1 if the +** prepared statement S is an EXPLAIN statement, or 2 if the +** statement S is an EXPLAIN QUERY PLAN. +** ^The sqlite3_stmt_isexplain(S) interface returns 0 if S is +** an ordinary statement or a NULL pointer. +*/ +SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement +** METHOD: sqlite3_stmt +** +** The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN +** setting for [prepared statement] S. If E is zero, then S becomes +** a normal prepared statement. If E is 1, then S behaves as if +** its SQL text began with "[EXPLAIN]". If E is 2, then S behaves as if +** its SQL text began with "[EXPLAIN QUERY PLAN]". +** +** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared. +** SQLite tries to avoid a reprepare, but a reprepare might be necessary +** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode. +** +** Because of the potential need to reprepare, a call to +** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be +** reprepared because it was created using [sqlite3_prepare()] instead of +** the newer [sqlite3_prepare_v2()] or [sqlite3_prepare_v3()] interfaces and +** hence has no saved SQL text with which to reprepare. +** +** Changing the explain setting for a prepared statement does not change +** the original SQL text for the statement. Hence, if the SQL text originally +** began with EXPLAIN or EXPLAIN QUERY PLAN, but sqlite3_stmt_explain(S,0) +** is called to convert the statement into an ordinary statement, the EXPLAIN +** or EXPLAIN QUERY PLAN keywords will still appear in the sqlite3_sql(S) +** output, even though the statement now acts like a normal SQL statement. +** +** This routine returns SQLITE_OK if the explain mode is successfully +** changed, or an error code if the explain mode could not be changed. +** The explain mode cannot be changed while a statement is active. +** Hence, it is good practice to call [sqlite3_reset(S)] +** immediately prior to calling sqlite3_stmt_explain(S,E). +*/ +SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode); + +/* +** CAPI3REF: Determine If A Prepared Statement Has Been Reset +** METHOD: sqlite3_stmt +** +** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the +** [prepared statement] S has been stepped at least once using +** [sqlite3_step(S)] but has neither run to completion (returned +** [SQLITE_DONE] from [sqlite3_step(S)]) nor +** been reset using [sqlite3_reset(S)]. ^The sqlite3_stmt_busy(S) +** interface returns false if S is a NULL pointer. If S is not a +** NULL pointer and is not a pointer to a valid [prepared statement] +** object, then the behavior is undefined and probably undesirable. +** +** This interface can be used in combination [sqlite3_next_stmt()] +** to locate all prepared statements associated with a database +** connection that are in need of being reset. This can be used, +** for example, in diagnostic routines to search for prepared +** statements that are holding a transaction open. +*/ +SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); + +/* +** CAPI3REF: Dynamically Typed Value Object +** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value} +** +** SQLite uses the sqlite3_value object to represent all values +** that can be stored in a database table. SQLite uses dynamic typing +** for the values it stores. ^Values stored in sqlite3_value objects +** can be integers, floating point values, strings, BLOBs, or NULL. +** +** An sqlite3_value object may be either "protected" or "unprotected". +** Some interfaces require a protected sqlite3_value. Other interfaces +** will accept either a protected or an unprotected sqlite3_value. +** Every interface that accepts sqlite3_value arguments specifies +** whether or not it requires a protected sqlite3_value. The +** [sqlite3_value_dup()] interface can be used to construct a new +** protected sqlite3_value from an unprotected sqlite3_value. +** +** The terms "protected" and "unprotected" refer to whether or not +** a mutex is held. An internal mutex is held for a protected +** sqlite3_value object but no mutex is held for an unprotected +** sqlite3_value object. If SQLite is compiled to be single-threaded +** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0) +** or if SQLite is run in one of reduced mutex modes +** [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD] +** then there is no distinction between protected and unprotected +** sqlite3_value objects and they can be used interchangeably. However, +** for maximum code portability it is recommended that applications +** still make the distinction between protected and unprotected +** sqlite3_value objects even when not strictly required. +** +** ^The sqlite3_value objects that are passed as parameters into the +** implementation of [application-defined SQL functions] are protected. +** ^The sqlite3_value objects returned by [sqlite3_vtab_rhs_value()] +** are protected. +** ^The sqlite3_value object returned by +** [sqlite3_column_value()] is unprotected. +** Unprotected sqlite3_value objects may only be used as arguments +** to [sqlite3_result_value()], [sqlite3_bind_value()], and +** [sqlite3_value_dup()]. +** The [sqlite3_value_blob | sqlite3_value_type()] family of +** interfaces require protected sqlite3_value objects. +*/ +typedef struct sqlite3_value sqlite3_value; + +/* +** CAPI3REF: SQL Function Context Object +** +** The context in which an SQL function executes is stored in an +** sqlite3_context object. ^A pointer to an sqlite3_context object +** is always first parameter to [application-defined SQL functions]. +** The application-defined SQL function implementation will pass this +** pointer through into calls to [sqlite3_result_int | sqlite3_result()], +** [sqlite3_aggregate_context()], [sqlite3_user_data()], +** [sqlite3_context_db_handle()], [sqlite3_get_auxdata()], +** and/or [sqlite3_set_auxdata()]. +*/ +typedef struct sqlite3_context sqlite3_context; + +/* +** CAPI3REF: Binding Values To Prepared Statements +** KEYWORDS: {host parameter} {host parameters} {host parameter name} +** KEYWORDS: {SQL parameter} {SQL parameters} {parameter binding} +** METHOD: sqlite3_stmt +** +** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants, +** literals may be replaced by a [parameter] that matches one of following +** templates: +** +** <ul> +** <li> ? +** <li> ?NNN +** <li> :VVV +** <li> @VVV +** <li> $VVV +** </ul> +** +** In the templates above, NNN represents an integer literal, +** and VVV represents an alphanumeric identifier.)^ ^The values of these +** parameters (also called "host parameter names" or "SQL parameters") +** can be set using the sqlite3_bind_*() routines defined here. +** +** ^The first argument to the sqlite3_bind_*() routines is always +** a pointer to the [sqlite3_stmt] object returned from +** [sqlite3_prepare_v2()] or its variants. +** +** ^The second argument is the index of the SQL parameter to be set. +** ^The leftmost SQL parameter has an index of 1. ^When the same named +** SQL parameter is used more than once, second and subsequent +** occurrences have the same index as the first occurrence. +** ^The index for named parameters can be looked up using the +** [sqlite3_bind_parameter_index()] API if desired. ^The index +** for "?NNN" parameters is the value of NNN. +** ^The NNN value must be between 1 and the [sqlite3_limit()] +** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 32766). +** +** ^The third argument is the value to bind to the parameter. +** ^If the third parameter to sqlite3_bind_text() or sqlite3_bind_text16() +** or sqlite3_bind_blob() is a NULL pointer then the fourth parameter +** is ignored and the end result is the same as sqlite3_bind_null(). +** ^If the third parameter to sqlite3_bind_text() is not NULL, then +** it should be a pointer to well-formed UTF8 text. +** ^If the third parameter to sqlite3_bind_text16() is not NULL, then +** it should be a pointer to well-formed UTF16 text. +** ^If the third parameter to sqlite3_bind_text64() is not NULL, then +** it should be a pointer to a well-formed unicode string that is +** either UTF8 if the sixth parameter is SQLITE_UTF8, or UTF16 +** otherwise. +** +** [[byte-order determination rules]] ^The byte-order of +** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) +** found in first character, which is removed, or in the absence of a BOM +** the byte order is the native byte order of the host +** machine for sqlite3_bind_text16() or the byte order specified in +** the 6th parameter for sqlite3_bind_text64().)^ +** ^If UTF16 input text contains invalid unicode +** characters, then SQLite might change those invalid characters +** into the unicode replacement character: U+FFFD. +** +** ^(In those routines that have a fourth argument, its value is the +** number of bytes in the parameter. To be clear: the value is the +** number of <u>bytes</u> in the value, not the number of characters.)^ +** ^If the fourth parameter to sqlite3_bind_text() or sqlite3_bind_text16() +** is negative, then the length of the string is +** the number of bytes up to the first zero terminator. +** If the fourth parameter to sqlite3_bind_blob() is negative, then +** the behavior is undefined. +** If a non-negative fourth parameter is provided to sqlite3_bind_text() +** or sqlite3_bind_text16() or sqlite3_bind_text64() then +** that parameter must be the byte offset +** where the NUL terminator would occur assuming the string were NUL +** terminated. If any NUL characters occurs at byte offsets less than +** the value of the fourth parameter then the resulting string value will +** contain embedded NULs. The result of expressions involving strings +** with embedded NULs is undefined. +** +** ^The fifth argument to the BLOB and string binding interfaces controls +** or indicates the lifetime of the object referenced by the third parameter. +** These three options exist: +** ^ (1) A destructor to dispose of the BLOB or string after SQLite has finished +** with it may be passed. ^It is called to dispose of the BLOB or string even +** if the call to the bind API fails, except the destructor is not called if +** the third parameter is a NULL pointer or the fourth parameter is negative. +** ^ (2) The special constant, [SQLITE_STATIC], may be passed to indicate that +** the application remains responsible for disposing of the object. ^In this +** case, the object and the provided pointer to it must remain valid until +** either the prepared statement is finalized or the same SQL parameter is +** bound to something else, whichever occurs sooner. +** ^ (3) The constant, [SQLITE_TRANSIENT], may be passed to indicate that the +** object is to be copied prior to the return from sqlite3_bind_*(). ^The +** object and pointer to it must remain valid until then. ^SQLite will then +** manage the lifetime of its private copy. +** +** ^The sixth argument to sqlite3_bind_text64() must be one of +** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] +** to specify the encoding of the text in the third parameter. If +** the sixth argument to sqlite3_bind_text64() is not one of the +** allowed values shown above, or if the text encoding is different +** from the encoding specified by the sixth parameter, then the behavior +** is undefined. +** +** ^The sqlite3_bind_zeroblob() routine binds a BLOB of length N that +** is filled with zeroes. ^A zeroblob uses a fixed amount of memory +** (just an integer to hold its size) while it is being processed. +** Zeroblobs are intended to serve as placeholders for BLOBs whose +** content is later written using +** [sqlite3_blob_open | incremental BLOB I/O] routines. +** ^A negative value for the zeroblob results in a zero-length BLOB. +** +** ^The sqlite3_bind_pointer(S,I,P,T,D) routine causes the I-th parameter in +** [prepared statement] S to have an SQL value of NULL, but to also be +** associated with the pointer P of type T. ^D is either a NULL pointer or +** a pointer to a destructor function for P. ^SQLite will invoke the +** destructor D with a single argument of P when it is finished using +** P. The T parameter should be a static string, preferably a string +** literal. The sqlite3_bind_pointer() routine is part of the +** [pointer passing interface] added for SQLite 3.20.0. +** +** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer +** for the [prepared statement] or with a prepared statement for which +** [sqlite3_step()] has been called more recently than [sqlite3_reset()], +** then the call will return [SQLITE_MISUSE]. If any sqlite3_bind_() +** routine is passed a [prepared statement] that has been finalized, the +** result is undefined and probably harmful. +** +** ^Bindings are not cleared by the [sqlite3_reset()] routine. +** ^Unbound parameters are interpreted as NULL. +** +** ^The sqlite3_bind_* routines return [SQLITE_OK] on success or an +** [error code] if anything goes wrong. +** ^[SQLITE_TOOBIG] might be returned if the size of a string or BLOB +** exceeds limits imposed by [sqlite3_limit]([SQLITE_LIMIT_LENGTH]) or +** [SQLITE_MAX_LENGTH]. +** ^[SQLITE_RANGE] is returned if the parameter +** index is out of range. ^[SQLITE_NOMEM] is returned if malloc() fails. +** +** See also: [sqlite3_bind_parameter_count()], +** [sqlite3_bind_parameter_name()], and [sqlite3_bind_parameter_index()]. +*/ +SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); +SQLITE_API int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64, + void(*)(void*)); +SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double); +SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int); +SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); +SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int); +SQLITE_API int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*)); +SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); +SQLITE_API int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64, + void(*)(void*), unsigned char encoding); +SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); +SQLITE_API int sqlite3_bind_pointer(sqlite3_stmt*, int, void*, const char*,void(*)(void*)); +SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); +SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64); + +/* +** CAPI3REF: Number Of SQL Parameters +** METHOD: sqlite3_stmt +** +** ^This routine can be used to find the number of [SQL parameters] +** in a [prepared statement]. SQL parameters are tokens of the +** form "?", "?NNN", ":AAA", "$AAA", or "@AAA" that serve as +** placeholders for values that are [sqlite3_bind_blob | bound] +** to the parameters at a later time. +** +** ^(This routine actually returns the index of the largest (rightmost) +** parameter. For all forms except ?NNN, this will correspond to the +** number of unique parameters. If parameters of the ?NNN form are used, +** there may be gaps in the list.)^ +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_name()], and +** [sqlite3_bind_parameter_index()]. +*/ +SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); + +/* +** CAPI3REF: Name Of A Host Parameter +** METHOD: sqlite3_stmt +** +** ^The sqlite3_bind_parameter_name(P,N) interface returns +** the name of the N-th [SQL parameter] in the [prepared statement] P. +** ^(SQL parameters of the form "?NNN" or ":AAA" or "@AAA" or "$AAA" +** have a name which is the string "?NNN" or ":AAA" or "@AAA" or "$AAA" +** respectively. +** In other words, the initial ":" or "$" or "@" or "?" +** is included as part of the name.)^ +** ^Parameters of the form "?" without a following integer have no name +** and are referred to as "nameless" or "anonymous parameters". +** +** ^The first host parameter has an index of 1, not 0. +** +** ^If the value N is out of range or if the N-th parameter is +** nameless, then NULL is returned. ^The returned string is +** always in UTF-8 encoding even if the named parameter was +** originally specified as UTF-16 in [sqlite3_prepare16()], +** [sqlite3_prepare16_v2()], or [sqlite3_prepare16_v3()]. +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_count()], and +** [sqlite3_bind_parameter_index()]. +*/ +SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); + +/* +** CAPI3REF: Index Of A Parameter With A Given Name +** METHOD: sqlite3_stmt +** +** ^Return the index of an SQL parameter given its name. ^The +** index value returned is suitable for use as the second +** parameter to [sqlite3_bind_blob|sqlite3_bind()]. ^A zero +** is returned if no matching parameter is found. ^The parameter +** name must be given in UTF-8 even if the original statement +** was prepared from UTF-16 text using [sqlite3_prepare16_v2()] or +** [sqlite3_prepare16_v3()]. +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_count()], and +** [sqlite3_bind_parameter_name()]. +*/ +SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); + +/* +** CAPI3REF: Reset All Bindings On A Prepared Statement +** METHOD: sqlite3_stmt +** +** ^Contrary to the intuition of many, [sqlite3_reset()] does not reset +** the [sqlite3_bind_blob | bindings] on a [prepared statement]. +** ^Use this routine to reset all host parameters to NULL. +*/ +SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); + +/* +** CAPI3REF: Number Of Columns In A Result Set +** METHOD: sqlite3_stmt +** +** ^Return the number of columns in the result set returned by the +** [prepared statement]. ^If this routine returns 0, that means the +** [prepared statement] returns no data (for example an [UPDATE]). +** ^However, just because this routine returns a positive number does not +** mean that one or more rows of data will be returned. ^A SELECT statement +** will always have a positive sqlite3_column_count() but depending on the +** WHERE clause constraints and the table content, it might return no rows. +** +** See also: [sqlite3_data_count()] +*/ +SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Column Names In A Result Set +** METHOD: sqlite3_stmt +** +** ^These routines return the name assigned to a particular column +** in the result set of a [SELECT] statement. ^The sqlite3_column_name() +** interface returns a pointer to a zero-terminated UTF-8 string +** and sqlite3_column_name16() returns a pointer to a zero-terminated +** UTF-16 string. ^The first parameter is the [prepared statement] +** that implements the [SELECT] statement. ^The second parameter is the +** column number. ^The leftmost column is number 0. +** +** ^The returned string pointer is valid until either the [prepared statement] +** is destroyed by [sqlite3_finalize()] or until the statement is automatically +** reprepared by the first call to [sqlite3_step()] for a particular run +** or until the next call to +** sqlite3_column_name() or sqlite3_column_name16() on the same column. +** +** ^If sqlite3_malloc() fails during the processing of either routine +** (for example during a conversion from UTF-8 to UTF-16) then a +** NULL pointer is returned. +** +** ^The name of a result column is the value of the "AS" clause for +** that column, if there is an AS clause. If there is no AS clause +** then the name of the column is unspecified and may change from +** one release of SQLite to the next. +*/ +SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N); +SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); + +/* +** CAPI3REF: Source Of Data In A Query Result +** METHOD: sqlite3_stmt +** +** ^These routines provide a means to determine the database, table, and +** table column that is the origin of a particular result column in +** [SELECT] statement. +** ^The name of the database or table or column can be returned as +** either a UTF-8 or UTF-16 string. ^The _database_ routines return +** the database name, the _table_ routines return the table name, and +** the origin_ routines return the column name. +** ^The returned string is valid until the [prepared statement] is destroyed +** using [sqlite3_finalize()] or until the statement is automatically +** reprepared by the first call to [sqlite3_step()] for a particular run +** or until the same information is requested +** again in a different encoding. +** +** ^The names returned are the original un-aliased names of the +** database, table, and column. +** +** ^The first argument to these interfaces is a [prepared statement]. +** ^These functions return information about the Nth result column returned by +** the statement, where N is the second function argument. +** ^The left-most column is column 0 for these routines. +** +** ^If the Nth column returned by the statement is an expression or +** subquery and is not a column value, then all of these functions return +** NULL. ^These routines might also return NULL if a memory allocation error +** occurs. ^Otherwise, they return the name of the attached database, table, +** or column that query result column was extracted from. +** +** ^As with all other SQLite APIs, those whose names end with "16" return +** UTF-16 encoded strings and the other functions return UTF-8. +** +** ^These APIs are only available if the library was compiled with the +** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol. +** +** If two or more threads call one or more +** [sqlite3_column_database_name | column metadata interfaces] +** for the same [prepared statement] and result column +** at the same time then the results are undefined. +*/ +SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int); +SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int); +SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int); + +/* +** CAPI3REF: Declared Datatype Of A Query Result +** METHOD: sqlite3_stmt +** +** ^(The first parameter is a [prepared statement]. +** If this statement is a [SELECT] statement and the Nth column of the +** returned result set of that [SELECT] is a table column (not an +** expression or subquery) then the declared type of the table +** column is returned.)^ ^If the Nth column of the result set is an +** expression or subquery, then a NULL pointer is returned. +** ^The returned string is always UTF-8 encoded. +** +** ^(For example, given the database schema: +** +** CREATE TABLE t1(c1 VARIANT); +** +** and the following statement to be compiled: +** +** SELECT c1 + 1, c1 FROM t1; +** +** this routine would return the string "VARIANT" for the second result +** column (i==1), and a NULL pointer for the first result column (i==0).)^ +** +** ^SQLite uses dynamic run-time typing. ^So just because a column +** is declared to contain a particular type does not mean that the +** data stored in that column is of the declared type. SQLite is +** strongly typed, but the typing is dynamic not static. ^Type +** is associated with individual values, not with the containers +** used to hold those values. +*/ +SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); + +/* +** CAPI3REF: Evaluate An SQL Statement +** METHOD: sqlite3_stmt +** +** After a [prepared statement] has been prepared using any of +** [sqlite3_prepare_v2()], [sqlite3_prepare_v3()], [sqlite3_prepare16_v2()], +** or [sqlite3_prepare16_v3()] or one of the legacy +** interfaces [sqlite3_prepare()] or [sqlite3_prepare16()], this function +** must be called one or more times to evaluate the statement. +** +** The details of the behavior of the sqlite3_step() interface depend +** on whether the statement was prepared using the newer "vX" interfaces +** [sqlite3_prepare_v3()], [sqlite3_prepare_v2()], [sqlite3_prepare16_v3()], +** [sqlite3_prepare16_v2()] or the older legacy +** interfaces [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the +** new "vX" interface is recommended for new applications but the legacy +** interface will continue to be supported. +** +** ^In the legacy interface, the return value will be either [SQLITE_BUSY], +** [SQLITE_DONE], [SQLITE_ROW], [SQLITE_ERROR], or [SQLITE_MISUSE]. +** ^With the "v2" interface, any of the other [result codes] or +** [extended result codes] might be returned as well. +** +** ^[SQLITE_BUSY] means that the database engine was unable to acquire the +** database locks it needs to do its job. ^If the statement is a [COMMIT] +** or occurs outside of an explicit transaction, then you can retry the +** statement. If the statement is not a [COMMIT] and occurs within an +** explicit transaction then you should rollback the transaction before +** continuing. +** +** ^[SQLITE_DONE] means that the statement has finished executing +** successfully. sqlite3_step() should not be called again on this virtual +** machine without first calling [sqlite3_reset()] to reset the virtual +** machine back to its initial state. +** +** ^If the SQL statement being executed returns any data, then [SQLITE_ROW] +** is returned each time a new row of data is ready for processing by the +** caller. The values may be accessed using the [column access functions]. +** sqlite3_step() is called again to retrieve the next row of data. +** +** ^[SQLITE_ERROR] means that a run-time error (such as a constraint +** violation) has occurred. sqlite3_step() should not be called again on +** the VM. More information may be found by calling [sqlite3_errmsg()]. +** ^With the legacy interface, a more specific error code (for example, +** [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth) +** can be obtained by calling [sqlite3_reset()] on the +** [prepared statement]. ^In the "v2" interface, +** the more specific error code is returned directly by sqlite3_step(). +** +** [SQLITE_MISUSE] means that the this routine was called inappropriately. +** Perhaps it was called on a [prepared statement] that has +** already been [sqlite3_finalize | finalized] or on one that had +** previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could +** be the case that the same database connection is being used by two or +** more threads at the same moment in time. +** +** For all versions of SQLite up to and including 3.6.23.1, a call to +** [sqlite3_reset()] was required after sqlite3_step() returned anything +** other than [SQLITE_ROW] before any subsequent invocation of +** sqlite3_step(). Failure to reset the prepared statement using +** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from +** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], +** sqlite3_step() began +** calling [sqlite3_reset()] automatically in this circumstance rather +** than returning [SQLITE_MISUSE]. This is not considered a compatibility +** break because any application that ever receives an SQLITE_MISUSE error +** is broken by definition. The [SQLITE_OMIT_AUTORESET] compile-time option +** can be used to restore the legacy behavior. +** +** <b>Goofy Interface Alert:</b> In the legacy interface, the sqlite3_step() +** API always returns a generic error code, [SQLITE_ERROR], following any +** error other than [SQLITE_BUSY] and [SQLITE_MISUSE]. You must call +** [sqlite3_reset()] or [sqlite3_finalize()] in order to find one of the +** specific [error codes] that better describes the error. +** We admit that this is a goofy design. The problem has been fixed +** with the "v2" interface. If you prepare all of your SQL statements +** using [sqlite3_prepare_v3()] or [sqlite3_prepare_v2()] +** or [sqlite3_prepare16_v2()] or [sqlite3_prepare16_v3()] instead +** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces, +** then the more specific [error codes] are returned directly +** by sqlite3_step(). The use of the "vX" interfaces is recommended. +*/ +SQLITE_API int sqlite3_step(sqlite3_stmt*); + +/* +** CAPI3REF: Number of columns in a result set +** METHOD: sqlite3_stmt +** +** ^The sqlite3_data_count(P) interface returns the number of columns in the +** current row of the result set of [prepared statement] P. +** ^If prepared statement P does not have results ready to return +** (via calls to the [sqlite3_column_int | sqlite3_column()] family of +** interfaces) then sqlite3_data_count(P) returns 0. +** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. +** ^The sqlite3_data_count(P) routine returns 0 if the previous call to +** [sqlite3_step](P) returned [SQLITE_DONE]. ^The sqlite3_data_count(P) +** will return non-zero if previous call to [sqlite3_step](P) returned +** [SQLITE_ROW], except in the case of the [PRAGMA incremental_vacuum] +** where it always returns zero since each step of that multi-step +** pragma returns 0 columns of data. +** +** See also: [sqlite3_column_count()] +*/ +SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Fundamental Datatypes +** KEYWORDS: SQLITE_TEXT +** +** ^(Every value in SQLite has one of five fundamental datatypes: +** +** <ul> +** <li> 64-bit signed integer +** <li> 64-bit IEEE floating point number +** <li> string +** <li> BLOB +** <li> NULL +** </ul>)^ +** +** These constants are codes for each of those types. +** +** Note that the SQLITE_TEXT constant was also used in SQLite version 2 +** for a completely different meaning. Software that links against both +** SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT, not +** SQLITE_TEXT. +*/ +#define SQLITE_INTEGER 1 +#define SQLITE_FLOAT 2 +#define SQLITE_BLOB 4 +#define SQLITE_NULL 5 +#ifdef SQLITE_TEXT +# undef SQLITE_TEXT +#else +# define SQLITE_TEXT 3 +#endif +#define SQLITE3_TEXT 3 + +/* +** CAPI3REF: Result Values From A Query +** KEYWORDS: {column access functions} +** METHOD: sqlite3_stmt +** +** <b>Summary:</b> +** <blockquote><table border=0 cellpadding=0 cellspacing=0> +** <tr><td><b>sqlite3_column_blob</b><td>&rarr;<td>BLOB result +** <tr><td><b>sqlite3_column_double</b><td>&rarr;<td>REAL result +** <tr><td><b>sqlite3_column_int</b><td>&rarr;<td>32-bit INTEGER result +** <tr><td><b>sqlite3_column_int64</b><td>&rarr;<td>64-bit INTEGER result +** <tr><td><b>sqlite3_column_text</b><td>&rarr;<td>UTF-8 TEXT result +** <tr><td><b>sqlite3_column_text16</b><td>&rarr;<td>UTF-16 TEXT result +** <tr><td><b>sqlite3_column_value</b><td>&rarr;<td>The result as an +** [sqlite3_value|unprotected sqlite3_value] object. +** <tr><td>&nbsp;<td>&nbsp;<td>&nbsp; +** <tr><td><b>sqlite3_column_bytes</b><td>&rarr;<td>Size of a BLOB +** or a UTF-8 TEXT result in bytes +** <tr><td><b>sqlite3_column_bytes16&nbsp;&nbsp;</b> +** <td>&rarr;&nbsp;&nbsp;<td>Size of UTF-16 +** TEXT in bytes +** <tr><td><b>sqlite3_column_type</b><td>&rarr;<td>Default +** datatype of the result +** </table></blockquote> +** +** <b>Details:</b> +** +** ^These routines return information about a single column of the current +** result row of a query. ^In every case the first argument is a pointer +** to the [prepared statement] that is being evaluated (the [sqlite3_stmt*] +** that was returned from [sqlite3_prepare_v2()] or one of its variants) +** and the second argument is the index of the column for which information +** should be returned. ^The leftmost column of the result set has the index 0. +** ^The number of columns in the result can be determined using +** [sqlite3_column_count()]. +** +** If the SQL statement does not currently point to a valid row, or if the +** column index is out of range, the result is undefined. +** These routines may only be called when the most recent call to +** [sqlite3_step()] has returned [SQLITE_ROW] and neither +** [sqlite3_reset()] nor [sqlite3_finalize()] have been called subsequently. +** If any of these routines are called after [sqlite3_reset()] or +** [sqlite3_finalize()] or after [sqlite3_step()] has returned +** something other than [SQLITE_ROW], the results are undefined. +** If [sqlite3_step()] or [sqlite3_reset()] or [sqlite3_finalize()] +** are called from a different thread while any of these routines +** are pending, then the results are undefined. +** +** The first six interfaces (_blob, _double, _int, _int64, _text, and _text16) +** each return the value of a result column in a specific data format. If +** the result column is not initially in the requested format (for example, +** if the query returns an integer but the sqlite3_column_text() interface +** is used to extract the value) then an automatic type conversion is performed. +** +** ^The sqlite3_column_type() routine returns the +** [SQLITE_INTEGER | datatype code] for the initial data type +** of the result column. ^The returned value is one of [SQLITE_INTEGER], +** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. +** The return value of sqlite3_column_type() can be used to decide which +** of the first six interface should be used to extract the column value. +** The value returned by sqlite3_column_type() is only meaningful if no +** automatic type conversions have occurred for the value in question. +** After a type conversion, the result of calling sqlite3_column_type() +** is undefined, though harmless. Future +** versions of SQLite may change the behavior of sqlite3_column_type() +** following a type conversion. +** +** If the result is a BLOB or a TEXT string, then the sqlite3_column_bytes() +** or sqlite3_column_bytes16() interfaces can be used to determine the size +** of that BLOB or string. +** +** ^If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes() +** routine returns the number of bytes in that BLOB or string. +** ^If the result is a UTF-16 string, then sqlite3_column_bytes() converts +** the string to UTF-8 and then returns the number of bytes. +** ^If the result is a numeric value then sqlite3_column_bytes() uses +** [sqlite3_snprintf()] to convert that value to a UTF-8 string and returns +** the number of bytes in that string. +** ^If the result is NULL, then sqlite3_column_bytes() returns zero. +** +** ^If the result is a BLOB or UTF-16 string then the sqlite3_column_bytes16() +** routine returns the number of bytes in that BLOB or string. +** ^If the result is a UTF-8 string, then sqlite3_column_bytes16() converts +** the string to UTF-16 and then returns the number of bytes. +** ^If the result is a numeric value then sqlite3_column_bytes16() uses +** [sqlite3_snprintf()] to convert that value to a UTF-16 string and returns +** the number of bytes in that string. +** ^If the result is NULL, then sqlite3_column_bytes16() returns zero. +** +** ^The values returned by [sqlite3_column_bytes()] and +** [sqlite3_column_bytes16()] do not include the zero terminators at the end +** of the string. ^For clarity: the values returned by +** [sqlite3_column_bytes()] and [sqlite3_column_bytes16()] are the number of +** bytes in the string, not the number of characters. +** +** ^Strings returned by sqlite3_column_text() and sqlite3_column_text16(), +** even empty strings, are always zero-terminated. ^The return +** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer. +** +** ^Strings returned by sqlite3_column_text16() always have the endianness +** which is native to the platform, regardless of the text encoding set +** for the database. +** +** <b>Warning:</b> ^The object returned by [sqlite3_column_value()] is an +** [unprotected sqlite3_value] object. In a multithreaded environment, +** an unprotected sqlite3_value object may only be used safely with +** [sqlite3_bind_value()] and [sqlite3_result_value()]. +** If the [unprotected sqlite3_value] object returned by +** [sqlite3_column_value()] is used in any other way, including calls +** to routines like [sqlite3_value_int()], [sqlite3_value_text()], +** or [sqlite3_value_bytes()], the behavior is not threadsafe. +** Hence, the sqlite3_column_value() interface +** is normally only useful within the implementation of +** [application-defined SQL functions] or [virtual tables], not within +** top-level application code. +** +** These routines may attempt to convert the datatype of the result. +** ^For example, if the internal representation is FLOAT and a text result +** is requested, [sqlite3_snprintf()] is used internally to perform the +** conversion automatically. ^(The following table details the conversions +** that are applied: +** +** <blockquote> +** <table border="1"> +** <tr><th> Internal<br>Type <th> Requested<br>Type <th> Conversion +** +** <tr><td> NULL <td> INTEGER <td> Result is 0 +** <tr><td> NULL <td> FLOAT <td> Result is 0.0 +** <tr><td> NULL <td> TEXT <td> Result is a NULL pointer +** <tr><td> NULL <td> BLOB <td> Result is a NULL pointer +** <tr><td> INTEGER <td> FLOAT <td> Convert from integer to float +** <tr><td> INTEGER <td> TEXT <td> ASCII rendering of the integer +** <tr><td> INTEGER <td> BLOB <td> Same as INTEGER->TEXT +** <tr><td> FLOAT <td> INTEGER <td> [CAST] to INTEGER +** <tr><td> FLOAT <td> TEXT <td> ASCII rendering of the float +** <tr><td> FLOAT <td> BLOB <td> [CAST] to BLOB +** <tr><td> TEXT <td> INTEGER <td> [CAST] to INTEGER +** <tr><td> TEXT <td> FLOAT <td> [CAST] to REAL +** <tr><td> TEXT <td> BLOB <td> No change +** <tr><td> BLOB <td> INTEGER <td> [CAST] to INTEGER +** <tr><td> BLOB <td> FLOAT <td> [CAST] to REAL +** <tr><td> BLOB <td> TEXT <td> [CAST] to TEXT, ensure zero terminator +** </table> +** </blockquote>)^ +** +** Note that when type conversions occur, pointers returned by prior +** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or +** sqlite3_column_text16() may be invalidated. +** Type conversions and pointer invalidations might occur +** in the following cases: +** +** <ul> +** <li> The initial content is a BLOB and sqlite3_column_text() or +** sqlite3_column_text16() is called. A zero-terminator might +** need to be added to the string.</li> +** <li> The initial content is UTF-8 text and sqlite3_column_bytes16() or +** sqlite3_column_text16() is called. The content must be converted +** to UTF-16.</li> +** <li> The initial content is UTF-16 text and sqlite3_column_bytes() or +** sqlite3_column_text() is called. The content must be converted +** to UTF-8.</li> +** </ul> +** +** ^Conversions between UTF-16be and UTF-16le are always done in place and do +** not invalidate a prior pointer, though of course the content of the buffer +** that the prior pointer references will have been modified. Other kinds +** of conversion are done in place when it is possible, but sometimes they +** are not possible and in those cases prior pointers are invalidated. +** +** The safest policy is to invoke these routines +** in one of the following ways: +** +** <ul> +** <li>sqlite3_column_text() followed by sqlite3_column_bytes()</li> +** <li>sqlite3_column_blob() followed by sqlite3_column_bytes()</li> +** <li>sqlite3_column_text16() followed by sqlite3_column_bytes16()</li> +** </ul> +** +** In other words, you should call sqlite3_column_text(), +** sqlite3_column_blob(), or sqlite3_column_text16() first to force the result +** into the desired format, then invoke sqlite3_column_bytes() or +** sqlite3_column_bytes16() to find the size of the result. Do not mix calls +** to sqlite3_column_text() or sqlite3_column_blob() with calls to +** sqlite3_column_bytes16(), and do not mix calls to sqlite3_column_text16() +** with calls to sqlite3_column_bytes(). +** +** ^The pointers returned are valid until a type conversion occurs as +** described above, or until [sqlite3_step()] or [sqlite3_reset()] or +** [sqlite3_finalize()] is called. ^The memory space used to hold strings +** and BLOBs is freed automatically. Do not pass the pointers returned +** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into +** [sqlite3_free()]. +** +** As long as the input parameters are correct, these routines will only +** fail if an out-of-memory error occurs during a format conversion. +** Only the following subset of interfaces are subject to out-of-memory +** errors: +** +** <ul> +** <li> sqlite3_column_blob() +** <li> sqlite3_column_text() +** <li> sqlite3_column_text16() +** <li> sqlite3_column_bytes() +** <li> sqlite3_column_bytes16() +** </ul> +** +** If an out-of-memory error occurs, then the return value from these +** routines is the same as if the column had contained an SQL NULL value. +** Valid SQL NULL returns can be distinguished from out-of-memory errors +** by invoking the [sqlite3_errcode()] immediately after the suspect +** return value is obtained and before any +** other SQLite interface is called on the same [database connection]. +*/ +SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); +SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); +SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); +SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); + +/* +** CAPI3REF: Destroy A Prepared Statement Object +** DESTRUCTOR: sqlite3_stmt +** +** ^The sqlite3_finalize() function is called to delete a [prepared statement]. +** ^If the most recent evaluation of the statement encountered no errors +** or if the statement is never been evaluated, then sqlite3_finalize() returns +** SQLITE_OK. ^If the most recent evaluation of statement S failed, then +** sqlite3_finalize(S) returns the appropriate [error code] or +** [extended error code]. +** +** ^The sqlite3_finalize(S) routine can be called at any point during +** the life cycle of [prepared statement] S: +** before statement S is ever evaluated, after +** one or more calls to [sqlite3_reset()], or after any call +** to [sqlite3_step()] regardless of whether or not the statement has +** completed execution. +** +** ^Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op. +** +** The application must finalize every [prepared statement] in order to avoid +** resource leaks. It is a grievous error for the application to try to use +** a prepared statement after it has been finalized. Any use of a prepared +** statement after it has been finalized can result in undefined and +** undesirable behavior such as segfaults and heap corruption. +*/ +SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Reset A Prepared Statement Object +** METHOD: sqlite3_stmt +** +** The sqlite3_reset() function is called to reset a [prepared statement] +** object back to its initial state, ready to be re-executed. +** ^Any SQL statement variables that had values bound to them using +** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values. +** Use [sqlite3_clear_bindings()] to reset the bindings. +** +** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S +** back to the beginning of its program. +** +** ^The return code from [sqlite3_reset(S)] indicates whether or not +** the previous evaluation of prepared statement S completed successfully. +** ^If [sqlite3_step(S)] has never before been called on S or if +** [sqlite3_step(S)] has not been called since the previous call +** to [sqlite3_reset(S)], then [sqlite3_reset(S)] will return +** [SQLITE_OK]. +** +** ^If the most recent call to [sqlite3_step(S)] for the +** [prepared statement] S indicated an error, then +** [sqlite3_reset(S)] returns an appropriate [error code]. +** ^The [sqlite3_reset(S)] interface might also return an [error code] +** if there were no prior errors but the process of resetting +** the prepared statement caused a new error. ^For example, if an +** [INSERT] statement with a [RETURNING] clause is only stepped one time, +** that one call to [sqlite3_step(S)] might return SQLITE_ROW but +** the overall statement might still fail and the [sqlite3_reset(S)] call +** might return SQLITE_BUSY if locking constraints prevent the +** database change from committing. Therefore, it is important that +** applications check the return code from [sqlite3_reset(S)] even if +** no prior call to [sqlite3_step(S)] indicated a problem. +** +** ^The [sqlite3_reset(S)] interface does not change the values +** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. +*/ +SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); + + +/* +** CAPI3REF: Create Or Redefine SQL Functions +** KEYWORDS: {function creation routines} +** METHOD: sqlite3 +** +** ^These functions (collectively known as "function creation routines") +** are used to add SQL functions or aggregates or to redefine the behavior +** of existing SQL functions or aggregates. The only differences between +** the three "sqlite3_create_function*" routines are the text encoding +** expected for the second parameter (the name of the function being +** created) and the presence or absence of a destructor callback for +** the application data pointer. Function sqlite3_create_window_function() +** is similar, but allows the user to supply the extra callback functions +** needed by [aggregate window functions]. +** +** ^The first parameter is the [database connection] to which the SQL +** function is to be added. ^If an application uses more than one database +** connection then application-defined SQL functions must be added +** to each database connection separately. +** +** ^The second parameter is the name of the SQL function to be created or +** redefined. ^The length of the name is limited to 255 bytes in a UTF-8 +** representation, exclusive of the zero-terminator. ^Note that the name +** length limit is in UTF-8 bytes, not characters nor UTF-16 bytes. +** ^Any attempt to create a function with a longer name +** will result in [SQLITE_MISUSE] being returned. +** +** ^The third parameter (nArg) +** is the number of arguments that the SQL function or +** aggregate takes. ^If this parameter is -1, then the SQL function or +** aggregate may take any number of arguments between 0 and the limit +** set by [sqlite3_limit]([SQLITE_LIMIT_FUNCTION_ARG]). If the third +** parameter is less than -1 or greater than 127 then the behavior is +** undefined. +** +** ^The fourth parameter, eTextRep, specifies what +** [SQLITE_UTF8 | text encoding] this SQL function prefers for +** its parameters. The application should set this parameter to +** [SQLITE_UTF16LE] if the function implementation invokes +** [sqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the +** implementation invokes [sqlite3_value_text16be()] on an input, or +** [SQLITE_UTF16] if [sqlite3_value_text16()] is used, or [SQLITE_UTF8] +** otherwise. ^The same SQL function may be registered multiple times using +** different preferred text encodings, with different implementations for +** each encoding. +** ^When multiple implementations of the same function are available, SQLite +** will pick the one that involves the least amount of data conversion. +** +** ^The fourth parameter may optionally be ORed with [SQLITE_DETERMINISTIC] +** to signal that the function will always return the same result given +** the same inputs within a single SQL statement. Most SQL functions are +** deterministic. The built-in [random()] SQL function is an example of a +** function that is not deterministic. The SQLite query planner is able to +** perform additional optimizations on deterministic functions, so use +** of the [SQLITE_DETERMINISTIC] flag is recommended where possible. +** +** ^The fourth parameter may also optionally include the [SQLITE_DIRECTONLY] +** flag, which if present prevents the function from being invoked from +** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions, +** index expressions, or the WHERE clause of partial indexes. +** +** For best security, the [SQLITE_DIRECTONLY] flag is recommended for +** all application-defined SQL functions that do not need to be +** used inside of triggers, view, CHECK constraints, or other elements of +** the database schema. This flags is especially recommended for SQL +** functions that have side effects or reveal internal application state. +** Without this flag, an attacker might be able to modify the schema of +** a database file to include invocations of the function with parameters +** chosen by the attacker, which the application will then execute when +** the database file is opened and read. +** +** ^(The fifth parameter is an arbitrary pointer. The implementation of the +** function can gain access to this pointer using [sqlite3_user_data()].)^ +** +** ^The sixth, seventh and eighth parameters passed to the three +** "sqlite3_create_function*" functions, xFunc, xStep and xFinal, are +** pointers to C-language functions that implement the SQL function or +** aggregate. ^A scalar SQL function requires an implementation of the xFunc +** callback only; NULL pointers must be passed as the xStep and xFinal +** parameters. ^An aggregate SQL function requires an implementation of xStep +** and xFinal and NULL pointer must be passed for xFunc. ^To delete an existing +** SQL function or aggregate, pass NULL pointers for all three function +** callbacks. +** +** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue +** and xInverse) passed to sqlite3_create_window_function are pointers to +** C-language callbacks that implement the new function. xStep and xFinal +** must both be non-NULL. xValue and xInverse may either both be NULL, in +** which case a regular aggregate function is created, or must both be +** non-NULL, in which case the new function may be used as either an aggregate +** or aggregate window function. More details regarding the implementation +** of aggregate window functions are +** [user-defined window functions|available here]. +** +** ^(If the final parameter to sqlite3_create_function_v2() or +** sqlite3_create_window_function() is not NULL, then it is destructor for +** the application data pointer. The destructor is invoked when the function +** is deleted, either by being overloaded or when the database connection +** closes.)^ ^The destructor is also invoked if the call to +** sqlite3_create_function_v2() fails. ^When the destructor callback is +** invoked, it is passed a single argument which is a copy of the application +** data pointer which was the fifth parameter to sqlite3_create_function_v2(). +** +** ^It is permitted to register multiple implementations of the same +** functions with the same name but with either differing numbers of +** arguments or differing preferred text encodings. ^SQLite will use +** the implementation that most closely matches the way in which the +** SQL function is used. ^A function implementation with a non-negative +** nArg parameter is a better match than a function implementation with +** a negative nArg. ^A function where the preferred text encoding +** matches the database encoding is a better +** match than a function where the encoding is different. +** ^A function where the encoding difference is between UTF16le and UTF16be +** is a closer match than a function where the encoding difference is +** between UTF8 and UTF16. +** +** ^Built-in functions may be overloaded by new application-defined functions. +** +** ^An application-defined function is permitted to call other +** SQLite interfaces. However, such calls must not +** close the database connection nor finalize or reset the prepared +** statement in which the function is running. +*/ +SQLITE_API int sqlite3_create_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); +SQLITE_API int sqlite3_create_function16( + sqlite3 *db, + const void *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); +SQLITE_API int sqlite3_create_function_v2( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void(*xDestroy)(void*) +); +SQLITE_API int sqlite3_create_window_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void (*xValue)(sqlite3_context*), + void (*xInverse)(sqlite3_context*,int,sqlite3_value**), + void(*xDestroy)(void*) +); + +/* +** CAPI3REF: Text Encodings +** +** These constant define integer codes that represent the various +** text encodings supported by SQLite. +*/ +#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ +#define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ +#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */ +#define SQLITE_UTF16 4 /* Use native byte order */ +#define SQLITE_ANY 5 /* Deprecated */ +#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ + +/* +** CAPI3REF: Function Flags +** +** These constants may be ORed together with the +** [SQLITE_UTF8 | preferred text encoding] as the fourth argument +** to [sqlite3_create_function()], [sqlite3_create_function16()], or +** [sqlite3_create_function_v2()]. +** +** <dl> +** [[SQLITE_DETERMINISTIC]] <dt>SQLITE_DETERMINISTIC</dt><dd> +** The SQLITE_DETERMINISTIC flag means that the new function always gives +** the same output when the input parameters are the same. +** The [abs|abs() function] is deterministic, for example, but +** [randomblob|randomblob()] is not. Functions must +** be deterministic in order to be used in certain contexts such as +** with the WHERE clause of [partial indexes] or in [generated columns]. +** SQLite might also optimize deterministic functions by factoring them +** out of inner loops. +** </dd> +** +** [[SQLITE_DIRECTONLY]] <dt>SQLITE_DIRECTONLY</dt><dd> +** The SQLITE_DIRECTONLY flag means that the function may only be invoked +** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in +** schema structures such as [CHECK constraints], [DEFAULT clauses], +** [expression indexes], [partial indexes], or [generated columns]. +** <p> +** The SQLITE_DIRECTONLY flag is recommended for any +** [application-defined SQL function] +** that has side-effects or that could potentially leak sensitive information. +** This will prevent attacks in which an application is tricked +** into using a database file that has had its schema surreptitiously +** modified to invoke the application-defined function in ways that are +** harmful. +** <p> +** Some people say it is good practice to set SQLITE_DIRECTONLY on all +** [application-defined SQL functions], regardless of whether or not they +** are security sensitive, as doing so prevents those functions from being used +** inside of the database schema, and thus ensures that the database +** can be inspected and modified using generic tools (such as the [CLI]) +** that do not have access to the application-defined functions. +** </dd> +** +** [[SQLITE_INNOCUOUS]] <dt>SQLITE_INNOCUOUS</dt><dd> +** The SQLITE_INNOCUOUS flag means that the function is unlikely +** to cause problems even if misused. An innocuous function should have +** no side effects and should not depend on any values other than its +** input parameters. The [abs|abs() function] is an example of an +** innocuous function. +** The [load_extension() SQL function] is not innocuous because of its +** side effects. +** <p> SQLITE_INNOCUOUS is similar to SQLITE_DETERMINISTIC, but is not +** exactly the same. The [random|random() function] is an example of a +** function that is innocuous but not deterministic. +** <p>Some heightened security settings +** ([SQLITE_DBCONFIG_TRUSTED_SCHEMA] and [PRAGMA trusted_schema=OFF]) +** disable the use of SQL functions inside views and triggers and in +** schema structures such as [CHECK constraints], [DEFAULT clauses], +** [expression indexes], [partial indexes], and [generated columns] unless +** the function is tagged with SQLITE_INNOCUOUS. Most built-in functions +** are innocuous. Developers are advised to avoid using the +** SQLITE_INNOCUOUS flag for application-defined functions unless the +** function has been carefully audited and found to be free of potentially +** security-adverse side-effects and information-leaks. +** </dd> +** +** [[SQLITE_SUBTYPE]] <dt>SQLITE_SUBTYPE</dt><dd> +** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call +** [sqlite3_value_subtype()] to inspect the sub-types of its arguments. +** Specifying this flag makes no difference for scalar or aggregate user +** functions. However, if it is not specified for a user-defined window +** function, then any sub-types belonging to arguments passed to the window +** function may be discarded before the window function is called (i.e. +** sqlite3_value_subtype() will always return 0). +** </dd> +** </dl> +*/ +#define SQLITE_DETERMINISTIC 0x000000800 +#define SQLITE_DIRECTONLY 0x000080000 +#define SQLITE_SUBTYPE 0x000100000 +#define SQLITE_INNOCUOUS 0x000200000 + +/* +** CAPI3REF: Deprecated Functions +** DEPRECATED +** +** These functions are [deprecated]. In order to maintain +** backwards compatibility with older code, these functions continue +** to be supported. However, new applications should avoid +** the use of these functions. To encourage programmers to avoid +** these functions, we will not explain what they do. +*/ +#ifndef SQLITE_OMIT_DEPRECATED +SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void); +SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void); +SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), + void*,sqlite3_int64); +#endif + +/* +** CAPI3REF: Obtaining SQL Values +** METHOD: sqlite3_value +** +** <b>Summary:</b> +** <blockquote><table border=0 cellpadding=0 cellspacing=0> +** <tr><td><b>sqlite3_value_blob</b><td>&rarr;<td>BLOB value +** <tr><td><b>sqlite3_value_double</b><td>&rarr;<td>REAL value +** <tr><td><b>sqlite3_value_int</b><td>&rarr;<td>32-bit INTEGER value +** <tr><td><b>sqlite3_value_int64</b><td>&rarr;<td>64-bit INTEGER value +** <tr><td><b>sqlite3_value_pointer</b><td>&rarr;<td>Pointer value +** <tr><td><b>sqlite3_value_text</b><td>&rarr;<td>UTF-8 TEXT value +** <tr><td><b>sqlite3_value_text16</b><td>&rarr;<td>UTF-16 TEXT value in +** the native byteorder +** <tr><td><b>sqlite3_value_text16be</b><td>&rarr;<td>UTF-16be TEXT value +** <tr><td><b>sqlite3_value_text16le</b><td>&rarr;<td>UTF-16le TEXT value +** <tr><td>&nbsp;<td>&nbsp;<td>&nbsp; +** <tr><td><b>sqlite3_value_bytes</b><td>&rarr;<td>Size of a BLOB +** or a UTF-8 TEXT in bytes +** <tr><td><b>sqlite3_value_bytes16&nbsp;&nbsp;</b> +** <td>&rarr;&nbsp;&nbsp;<td>Size of UTF-16 +** TEXT in bytes +** <tr><td><b>sqlite3_value_type</b><td>&rarr;<td>Default +** datatype of the value +** <tr><td><b>sqlite3_value_numeric_type&nbsp;&nbsp;</b> +** <td>&rarr;&nbsp;&nbsp;<td>Best numeric datatype of the value +** <tr><td><b>sqlite3_value_nochange&nbsp;&nbsp;</b> +** <td>&rarr;&nbsp;&nbsp;<td>True if the column is unchanged in an UPDATE +** against a virtual table. +** <tr><td><b>sqlite3_value_frombind&nbsp;&nbsp;</b> +** <td>&rarr;&nbsp;&nbsp;<td>True if value originated from a [bound parameter] +** </table></blockquote> +** +** <b>Details:</b> +** +** These routines extract type, size, and content information from +** [protected sqlite3_value] objects. Protected sqlite3_value objects +** are used to pass parameter information into the functions that +** implement [application-defined SQL functions] and [virtual tables]. +** +** These routines work only with [protected sqlite3_value] objects. +** Any attempt to use these routines on an [unprotected sqlite3_value] +** is not threadsafe. +** +** ^These routines work just like the corresponding [column access functions] +** except that these routines take a single [protected sqlite3_value] object +** pointer instead of a [sqlite3_stmt*] pointer and an integer column number. +** +** ^The sqlite3_value_text16() interface extracts a UTF-16 string +** in the native byte-order of the host machine. ^The +** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces +** extract UTF-16 strings as big-endian and little-endian respectively. +** +** ^If [sqlite3_value] object V was initialized +** using [sqlite3_bind_pointer(S,I,P,X,D)] or [sqlite3_result_pointer(C,P,X,D)] +** and if X and Y are strings that compare equal according to strcmp(X,Y), +** then sqlite3_value_pointer(V,Y) will return the pointer P. ^Otherwise, +** sqlite3_value_pointer(V,Y) returns a NULL. The sqlite3_bind_pointer() +** routine is part of the [pointer passing interface] added for SQLite 3.20.0. +** +** ^(The sqlite3_value_type(V) interface returns the +** [SQLITE_INTEGER | datatype code] for the initial datatype of the +** [sqlite3_value] object V. The returned value is one of [SQLITE_INTEGER], +** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL].)^ +** Other interfaces might change the datatype for an sqlite3_value object. +** For example, if the datatype is initially SQLITE_INTEGER and +** sqlite3_value_text(V) is called to extract a text value for that +** integer, then subsequent calls to sqlite3_value_type(V) might return +** SQLITE_TEXT. Whether or not a persistent internal datatype conversion +** occurs is undefined and may change from one release of SQLite to the next. +** +** ^(The sqlite3_value_numeric_type() interface attempts to apply +** numeric affinity to the value. This means that an attempt is +** made to convert the value to an integer or floating point. If +** such a conversion is possible without loss of information (in other +** words, if the value is a string that looks like a number) +** then the conversion is performed. Otherwise no conversion occurs. +** The [SQLITE_INTEGER | datatype] after conversion is returned.)^ +** +** ^Within the [xUpdate] method of a [virtual table], the +** sqlite3_value_nochange(X) interface returns true if and only if +** the column corresponding to X is unchanged by the UPDATE operation +** that the xUpdate method call was invoked to implement and if +** and the prior [xColumn] method call that was invoked to extracted +** the value for that column returned without setting a result (probably +** because it queried [sqlite3_vtab_nochange()] and found that the column +** was unchanging). ^Within an [xUpdate] method, any value for which +** sqlite3_value_nochange(X) is true will in all other respects appear +** to be a NULL value. If sqlite3_value_nochange(X) is invoked anywhere other +** than within an [xUpdate] method call for an UPDATE statement, then +** the return value is arbitrary and meaningless. +** +** ^The sqlite3_value_frombind(X) interface returns non-zero if the +** value X originated from one of the [sqlite3_bind_int|sqlite3_bind()] +** interfaces. ^If X comes from an SQL literal value, or a table column, +** or an expression, then sqlite3_value_frombind(X) returns zero. +** +** Please pay particular attention to the fact that the pointer returned +** from [sqlite3_value_blob()], [sqlite3_value_text()], or +** [sqlite3_value_text16()] can be invalidated by a subsequent call to +** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()], +** or [sqlite3_value_text16()]. +** +** These routines must be called from the same thread as +** the SQL function that supplied the [sqlite3_value*] parameters. +** +** As long as the input parameter is correct, these routines can only +** fail if an out-of-memory error occurs during a format conversion. +** Only the following subset of interfaces are subject to out-of-memory +** errors: +** +** <ul> +** <li> sqlite3_value_blob() +** <li> sqlite3_value_text() +** <li> sqlite3_value_text16() +** <li> sqlite3_value_text16le() +** <li> sqlite3_value_text16be() +** <li> sqlite3_value_bytes() +** <li> sqlite3_value_bytes16() +** </ul> +** +** If an out-of-memory error occurs, then the return value from these +** routines is the same as if the column had contained an SQL NULL value. +** Valid SQL NULL returns can be distinguished from out-of-memory errors +** by invoking the [sqlite3_errcode()] immediately after the suspect +** return value is obtained and before any +** other SQLite interface is called on the same [database connection]. +*/ +SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); +SQLITE_API double sqlite3_value_double(sqlite3_value*); +SQLITE_API int sqlite3_value_int(sqlite3_value*); +SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); +SQLITE_API void *sqlite3_value_pointer(sqlite3_value*, const char*); +SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); +SQLITE_API int sqlite3_value_type(sqlite3_value*); +SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); +SQLITE_API int sqlite3_value_nochange(sqlite3_value*); +SQLITE_API int sqlite3_value_frombind(sqlite3_value*); + +/* +** CAPI3REF: Report the internal text encoding state of an sqlite3_value object +** METHOD: sqlite3_value +** +** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8], +** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current text encoding +** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X) +** returns something other than SQLITE_TEXT, then the return value from +** sqlite3_value_encoding(X) is meaningless. ^Calls to +** [sqlite3_value_text(X)], [sqlite3_value_text16(X)], [sqlite3_value_text16be(X)], +** [sqlite3_value_text16le(X)], [sqlite3_value_bytes(X)], or +** [sqlite3_value_bytes16(X)] might change the encoding of the value X and +** thus change the return from subsequent calls to sqlite3_value_encoding(X). +** +** This routine is intended for used by applications that test and validate +** the SQLite implementation. This routine is inquiring about the opaque +** internal state of an [sqlite3_value] object. Ordinary applications should +** not need to know what the internal state of an sqlite3_value object is and +** hence should not need to use this interface. +*/ +SQLITE_API int sqlite3_value_encoding(sqlite3_value*); + +/* +** CAPI3REF: Finding The Subtype Of SQL Values +** METHOD: sqlite3_value +** +** The sqlite3_value_subtype(V) function returns the subtype for +** an [application-defined SQL function] argument V. The subtype +** information can be used to pass a limited amount of context from +** one SQL function to another. Use the [sqlite3_result_subtype()] +** routine to set the subtype for the return value of an SQL function. +*/ +SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); + +/* +** CAPI3REF: Copy And Free SQL Values +** METHOD: sqlite3_value +** +** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value] +** object D and returns a pointer to that copy. ^The [sqlite3_value] returned +** is a [protected sqlite3_value] object even if the input is not. +** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a +** memory allocation fails. ^If V is a [pointer value], then the result +** of sqlite3_value_dup(V) is a NULL value. +** +** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object +** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer +** then sqlite3_value_free(V) is a harmless no-op. +*/ +SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value*); +SQLITE_API void sqlite3_value_free(sqlite3_value*); + +/* +** CAPI3REF: Obtain Aggregate Function Context +** METHOD: sqlite3_context +** +** Implementations of aggregate SQL functions use this +** routine to allocate memory for storing their state. +** +** ^The first time the sqlite3_aggregate_context(C,N) routine is called +** for a particular aggregate function, SQLite allocates +** N bytes of memory, zeroes out that memory, and returns a pointer +** to the new memory. ^On second and subsequent calls to +** sqlite3_aggregate_context() for the same aggregate function instance, +** the same buffer is returned. Sqlite3_aggregate_context() is normally +** called once for each invocation of the xStep callback and then one +** last time when the xFinal callback is invoked. ^(When no rows match +** an aggregate query, the xStep() callback of the aggregate function +** implementation is never called and xFinal() is called exactly once. +** In those cases, sqlite3_aggregate_context() might be called for the +** first time from within xFinal().)^ +** +** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer +** when first called if N is less than or equal to zero or if a memory +** allocation error occurs. +** +** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is +** determined by the N parameter on first successful call. Changing the +** value of N in any subsequent call to sqlite3_aggregate_context() within +** the same aggregate function instance will not resize the memory +** allocation.)^ Within the xFinal callback, it is customary to set +** N=0 in calls to sqlite3_aggregate_context(C,N) so that no +** pointless memory allocations occur. +** +** ^SQLite automatically frees the memory allocated by +** sqlite3_aggregate_context() when the aggregate query concludes. +** +** The first parameter must be a copy of the +** [sqlite3_context | SQL function context] that is the first parameter +** to the xStep or xFinal callback routine that implements the aggregate +** function. +** +** This routine must be called from the same thread in which +** the aggregate SQL function is running. +*/ +SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); + +/* +** CAPI3REF: User Data For Functions +** METHOD: sqlite3_context +** +** ^The sqlite3_user_data() interface returns a copy of +** the pointer that was the pUserData parameter (the 5th parameter) +** of the [sqlite3_create_function()] +** and [sqlite3_create_function16()] routines that originally +** registered the application defined function. +** +** This routine must be called from the same thread in which +** the application-defined function is running. +*/ +SQLITE_API void *sqlite3_user_data(sqlite3_context*); + +/* +** CAPI3REF: Database Connection For Functions +** METHOD: sqlite3_context +** +** ^The sqlite3_context_db_handle() interface returns a copy of +** the pointer to the [database connection] (the 1st parameter) +** of the [sqlite3_create_function()] +** and [sqlite3_create_function16()] routines that originally +** registered the application defined function. +*/ +SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); + +/* +** CAPI3REF: Function Auxiliary Data +** METHOD: sqlite3_context +** +** These functions may be used by (non-aggregate) SQL functions to +** associate auxiliary data with argument values. If the same argument +** value is passed to multiple invocations of the same SQL function during +** query execution, under some circumstances the associated auxiliary data +** might be preserved. An example of where this might be useful is in a +** regular-expression matching function. The compiled version of the regular +** expression can be stored as auxiliary data associated with the pattern string. +** Then as long as the pattern string remains the same, +** the compiled regular expression can be reused on multiple +** invocations of the same function. +** +** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the auxiliary data +** associated by the sqlite3_set_auxdata(C,N,P,X) function with the Nth argument +** value to the application-defined function. ^N is zero for the left-most +** function argument. ^If there is no auxiliary data +** associated with the function argument, the sqlite3_get_auxdata(C,N) interface +** returns a NULL pointer. +** +** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as auxiliary data for the +** N-th argument of the application-defined function. ^Subsequent +** calls to sqlite3_get_auxdata(C,N) return P from the most recent +** sqlite3_set_auxdata(C,N,P,X) call if the auxiliary data is still valid or +** NULL if the auxiliary data has been discarded. +** ^After each call to sqlite3_set_auxdata(C,N,P,X) where X is not NULL, +** SQLite will invoke the destructor function X with parameter P exactly +** once, when the auxiliary data is discarded. +** SQLite is free to discard the auxiliary data at any time, including: <ul> +** <li> ^(when the corresponding function parameter changes)^, or +** <li> ^(when [sqlite3_reset()] or [sqlite3_finalize()] is called for the +** SQL statement)^, or +** <li> ^(when sqlite3_set_auxdata() is invoked again on the same +** parameter)^, or +** <li> ^(during the original sqlite3_set_auxdata() call when a memory +** allocation error occurs.)^ </ul> +** +** Note the last bullet in particular. The destructor X in +** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the +** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata() +** should be called near the end of the function implementation and the +** function implementation should not make any use of P after +** sqlite3_set_auxdata() has been called. +** +** ^(In practice, auxiliary data is preserved between function calls for +** function parameters that are compile-time constants, including literal +** values and [parameters] and expressions composed from the same.)^ +** +** The value of the N parameter to these interfaces should be non-negative. +** Future enhancements may make use of negative N values to define new +** kinds of function caching behavior. +** +** These routines must be called from the same thread in which +** the SQL function is running. +** +** See also: [sqlite3_get_clientdata()] and [sqlite3_set_clientdata()]. +*/ +SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N); +SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); + +/* +** CAPI3REF: Database Connection Client Data +** METHOD: sqlite3 +** +** These functions are used to associate one or more named pointers +** with a [database connection]. +** A call to sqlite3_set_clientdata(D,N,P,X) causes the pointer P +** to be attached to [database connection] D using name N. Subsequent +** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P +** or a NULL pointer if there were no prior calls to +** sqlite3_set_clientdata() with the same values of D and N. +** Names are compared using strcmp() and are thus case sensitive. +** +** If P and X are both non-NULL, then the destructor X is invoked with +** argument P on the first of the following occurrences: +** <ul> +** <li> An out-of-memory error occurs during the call to +** sqlite3_set_clientdata() which attempts to register pointer P. +** <li> A subsequent call to sqlite3_set_clientdata(D,N,P,X) is made +** with the same D and N parameters. +** <li> The database connection closes. SQLite does not make any guarantees +** about the order in which destructors are called, only that all +** destructors will be called exactly once at some point during the +** database connection closingi process. +** </ul> +** +** SQLite does not do anything with client data other than invoke +** destructors on the client data at the appropriate time. The intended +** use for client data is to provide a mechanism for wrapper libraries +** to store additional information about an SQLite database connection. +** +** There is no limit (other than available memory) on the number of different +** client data pointers (with different names) that can be attached to a +** single database connection. However, the implementation is optimized +** for the case of having only one or two different client data names. +** Applications and wrapper libraries are discouraged from using more than +** one client data name each. +** +** There is no way to enumerate the client data pointers +** associated with a database connection. The N parameter can be thought +** of as a secret key such that only code that knows the secret key is able +** to access the associated data. +** +** Security Warning: These interfaces should not be exposed in scripting +** languages or in other circumstances where it might be possible for an +** an attacker to invoke them. Any agent that can invoke these interfaces +** can probably also take control of the process. +** +** Database connection client data is only available for SQLite +** version 3.44.0 ([dateof:3.44.0]) and later. +** +** See also: [sqlite3_set_auxdata()] and [sqlite3_get_auxdata()]. +*/ +SQLITE_API void *sqlite3_get_clientdata(sqlite3*,const char*); +SQLITE_API int sqlite3_set_clientdata(sqlite3*, const char*, void*, void(*)(void*)); + +/* +** CAPI3REF: Constants Defining Special Destructor Behavior +** +** These are special values for the destructor that is passed in as the +** final argument to routines like [sqlite3_result_blob()]. ^If the destructor +** argument is SQLITE_STATIC, it means that the content pointer is constant +** and will never change. It does not need to be destroyed. ^The +** SQLITE_TRANSIENT value means that the content will likely change in +** the near future and that SQLite should make its own private copy of +** the content before returning. +** +** The typedef is necessary to work around problems in certain +** C++ compilers. +*/ +typedef void (*sqlite3_destructor_type)(void*); +#define SQLITE_STATIC ((sqlite3_destructor_type)0) +#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) + +/* +** CAPI3REF: Setting The Result Of An SQL Function +** METHOD: sqlite3_context +** +** These routines are used by the xFunc or xFinal callbacks that +** implement SQL functions and aggregates. See +** [sqlite3_create_function()] and [sqlite3_create_function16()] +** for additional information. +** +** These functions work very much like the [parameter binding] family of +** functions used to bind values to host parameters in prepared statements. +** Refer to the [SQL parameter] documentation for additional information. +** +** ^The sqlite3_result_blob() interface sets the result from +** an application-defined function to be the BLOB whose content is pointed +** to by the second parameter and which is N bytes long where N is the +** third parameter. +** +** ^The sqlite3_result_zeroblob(C,N) and sqlite3_result_zeroblob64(C,N) +** interfaces set the result of the application-defined function to be +** a BLOB containing all zero bytes and N bytes in size. +** +** ^The sqlite3_result_double() interface sets the result from +** an application-defined function to be a floating point value specified +** by its 2nd argument. +** +** ^The sqlite3_result_error() and sqlite3_result_error16() functions +** cause the implemented SQL function to throw an exception. +** ^SQLite uses the string pointed to by the +** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() +** as the text of an error message. ^SQLite interprets the error +** message string from sqlite3_result_error() as UTF-8. ^SQLite +** interprets the string from sqlite3_result_error16() as UTF-16 using +** the same [byte-order determination rules] as [sqlite3_bind_text16()]. +** ^If the third parameter to sqlite3_result_error() +** or sqlite3_result_error16() is negative then SQLite takes as the error +** message all text up through the first zero character. +** ^If the third parameter to sqlite3_result_error() or +** sqlite3_result_error16() is non-negative then SQLite takes that many +** bytes (not characters) from the 2nd parameter as the error message. +** ^The sqlite3_result_error() and sqlite3_result_error16() +** routines make a private copy of the error message text before +** they return. Hence, the calling function can deallocate or +** modify the text after they return without harm. +** ^The sqlite3_result_error_code() function changes the error code +** returned by SQLite as a result of an error in a function. ^By default, +** the error code is SQLITE_ERROR. ^A subsequent call to sqlite3_result_error() +** or sqlite3_result_error16() resets the error code to SQLITE_ERROR. +** +** ^The sqlite3_result_error_toobig() interface causes SQLite to throw an +** error indicating that a string or BLOB is too long to represent. +** +** ^The sqlite3_result_error_nomem() interface causes SQLite to throw an +** error indicating that a memory allocation failed. +** +** ^The sqlite3_result_int() interface sets the return value +** of the application-defined function to be the 32-bit signed integer +** value given in the 2nd argument. +** ^The sqlite3_result_int64() interface sets the return value +** of the application-defined function to be the 64-bit signed integer +** value given in the 2nd argument. +** +** ^The sqlite3_result_null() interface sets the return value +** of the application-defined function to be NULL. +** +** ^The sqlite3_result_text(), sqlite3_result_text16(), +** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces +** set the return value of the application-defined function to be +** a text string which is represented as UTF-8, UTF-16 native byte order, +** UTF-16 little endian, or UTF-16 big endian, respectively. +** ^The sqlite3_result_text64() interface sets the return value of an +** application-defined function to be a text string in an encoding +** specified by the fifth (and last) parameter, which must be one +** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. +** ^SQLite takes the text result from the application from +** the 2nd parameter of the sqlite3_result_text* interfaces. +** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces +** other than sqlite3_result_text64() is negative, then SQLite computes +** the string length itself by searching the 2nd parameter for the first +** zero character. +** ^If the 3rd parameter to the sqlite3_result_text* interfaces +** is non-negative, then as many bytes (not characters) of the text +** pointed to by the 2nd parameter are taken as the application-defined +** function result. If the 3rd parameter is non-negative, then it +** must be the byte offset into the string where the NUL terminator would +** appear if the string where NUL terminated. If any NUL characters occur +** in the string at a byte offset that is less than the value of the 3rd +** parameter, then the resulting string will contain embedded NULs and the +** result of expressions operating on strings with embedded NULs is undefined. +** ^If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that +** function as the destructor on the text or BLOB result when it has +** finished using that result. +** ^If the 4th parameter to the sqlite3_result_text* interfaces or to +** sqlite3_result_blob is the special constant SQLITE_STATIC, then SQLite +** assumes that the text or BLOB result is in constant space and does not +** copy the content of the parameter nor call a destructor on the content +** when it has finished using that result. +** ^If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT +** then SQLite makes a copy of the result into space obtained +** from [sqlite3_malloc()] before it returns. +** +** ^For the sqlite3_result_text16(), sqlite3_result_text16le(), and +** sqlite3_result_text16be() routines, and for sqlite3_result_text64() +** when the encoding is not UTF8, if the input UTF16 begins with a +** byte-order mark (BOM, U+FEFF) then the BOM is removed from the +** string and the rest of the string is interpreted according to the +** byte-order specified by the BOM. ^The byte-order specified by +** the BOM at the beginning of the text overrides the byte-order +** specified by the interface procedure. ^So, for example, if +** sqlite3_result_text16le() is invoked with text that begins +** with bytes 0xfe, 0xff (a big-endian byte-order mark) then the +** first two bytes of input are skipped and the remaining input +** is interpreted as UTF16BE text. +** +** ^For UTF16 input text to the sqlite3_result_text16(), +** sqlite3_result_text16be(), sqlite3_result_text16le(), and +** sqlite3_result_text64() routines, if the text contains invalid +** UTF16 characters, the invalid characters might be converted +** into the unicode replacement character, U+FFFD. +** +** ^The sqlite3_result_value() interface sets the result of +** the application-defined function to be a copy of the +** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The +** sqlite3_result_value() interface makes a copy of the [sqlite3_value] +** so that the [sqlite3_value] specified in the parameter may change or +** be deallocated after sqlite3_result_value() returns without harm. +** ^A [protected sqlite3_value] object may always be used where an +** [unprotected sqlite3_value] object is required, so either +** kind of [sqlite3_value] object can be used with this interface. +** +** ^The sqlite3_result_pointer(C,P,T,D) interface sets the result to an +** SQL NULL value, just like [sqlite3_result_null(C)], except that it +** also associates the host-language pointer P or type T with that +** NULL value such that the pointer can be retrieved within an +** [application-defined SQL function] using [sqlite3_value_pointer()]. +** ^If the D parameter is not NULL, then it is a pointer to a destructor +** for the P parameter. ^SQLite invokes D with P as its only argument +** when SQLite is finished with P. The T parameter should be a static +** string and preferably a string literal. The sqlite3_result_pointer() +** routine is part of the [pointer passing interface] added for SQLite 3.20.0. +** +** If these routines are called from within the different thread +** than the one containing the application-defined function that received +** the [sqlite3_context] pointer, the results are undefined. +*/ +SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*, + sqlite3_uint64,void(*)(void*)); +SQLITE_API void sqlite3_result_double(sqlite3_context*, double); +SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); +SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); +SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*); +SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*); +SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); +SQLITE_API void sqlite3_result_null(sqlite3_context*); +SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64, + void(*)(void*), unsigned char encoding); +SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); +SQLITE_API void sqlite3_result_pointer(sqlite3_context*, void*,const char*,void(*)(void*)); +SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); +SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); + + +/* +** CAPI3REF: Setting The Subtype Of An SQL Function +** METHOD: sqlite3_context +** +** The sqlite3_result_subtype(C,T) function causes the subtype of +** the result from the [application-defined SQL function] with +** [sqlite3_context] C to be the value T. Only the lower 8 bits +** of the subtype T are preserved in current versions of SQLite; +** higher order bits are discarded. +** The number of subtype bytes preserved by SQLite might increase +** in future releases of SQLite. +*/ +SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); + +/* +** CAPI3REF: Define New Collating Sequences +** METHOD: sqlite3 +** +** ^These functions add, remove, or modify a [collation] associated +** with the [database connection] specified as the first argument. +** +** ^The name of the collation is a UTF-8 string +** for sqlite3_create_collation() and sqlite3_create_collation_v2() +** and a UTF-16 string in native byte order for sqlite3_create_collation16(). +** ^Collation names that compare equal according to [sqlite3_strnicmp()] are +** considered to be the same name. +** +** ^(The third argument (eTextRep) must be one of the constants: +** <ul> +** <li> [SQLITE_UTF8], +** <li> [SQLITE_UTF16LE], +** <li> [SQLITE_UTF16BE], +** <li> [SQLITE_UTF16], or +** <li> [SQLITE_UTF16_ALIGNED]. +** </ul>)^ +** ^The eTextRep argument determines the encoding of strings passed +** to the collating function callback, xCompare. +** ^The [SQLITE_UTF16] and [SQLITE_UTF16_ALIGNED] values for eTextRep +** force strings to be UTF16 with native byte order. +** ^The [SQLITE_UTF16_ALIGNED] value for eTextRep forces strings to begin +** on an even byte address. +** +** ^The fourth argument, pArg, is an application data pointer that is passed +** through as the first argument to the collating function callback. +** +** ^The fifth argument, xCompare, is a pointer to the collating function. +** ^Multiple collating functions can be registered using the same name but +** with different eTextRep parameters and SQLite will use whichever +** function requires the least amount of data transformation. +** ^If the xCompare argument is NULL then the collating function is +** deleted. ^When all collating functions having the same name are deleted, +** that collation is no longer usable. +** +** ^The collating function callback is invoked with a copy of the pArg +** application data pointer and with two strings in the encoding specified +** by the eTextRep argument. The two integer parameters to the collating +** function callback are the length of the two strings, in bytes. The collating +** function must return an integer that is negative, zero, or positive +** if the first string is less than, equal to, or greater than the second, +** respectively. A collating function must always return the same answer +** given the same inputs. If two or more collating functions are registered +** to the same collation name (using different eTextRep values) then all +** must give an equivalent answer when invoked with equivalent strings. +** The collating function must obey the following properties for all +** strings A, B, and C: +** +** <ol> +** <li> If A==B then B==A. +** <li> If A==B and B==C then A==C. +** <li> If A&lt;B THEN B&gt;A. +** <li> If A&lt;B and B&lt;C then A&lt;C. +** </ol> +** +** If a collating function fails any of the above constraints and that +** collating function is registered and used, then the behavior of SQLite +** is undefined. +** +** ^The sqlite3_create_collation_v2() works like sqlite3_create_collation() +** with the addition that the xDestroy callback is invoked on pArg when +** the collating function is deleted. +** ^Collating functions are deleted when they are overridden by later +** calls to the collation creation functions or when the +** [database connection] is closed using [sqlite3_close()]. +** +** ^The xDestroy callback is <u>not</u> called if the +** sqlite3_create_collation_v2() function fails. Applications that invoke +** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should +** check the return code and dispose of the application data pointer +** themselves rather than expecting SQLite to deal with it for them. +** This is different from every other SQLite interface. The inconsistency +** is unfortunate but cannot be changed without breaking backwards +** compatibility. +** +** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. +*/ +SQLITE_API int sqlite3_create_collation( + sqlite3*, + const char *zName, + int eTextRep, + void *pArg, + int(*xCompare)(void*,int,const void*,int,const void*) +); +SQLITE_API int sqlite3_create_collation_v2( + sqlite3*, + const char *zName, + int eTextRep, + void *pArg, + int(*xCompare)(void*,int,const void*,int,const void*), + void(*xDestroy)(void*) +); +SQLITE_API int sqlite3_create_collation16( + sqlite3*, + const void *zName, + int eTextRep, + void *pArg, + int(*xCompare)(void*,int,const void*,int,const void*) +); + +/* +** CAPI3REF: Collation Needed Callbacks +** METHOD: sqlite3 +** +** ^To avoid having to register all collation sequences before a database +** can be used, a single callback function may be registered with the +** [database connection] to be invoked whenever an undefined collation +** sequence is required. +** +** ^If the function is registered using the sqlite3_collation_needed() API, +** then it is passed the names of undefined collation sequences as strings +** encoded in UTF-8. ^If sqlite3_collation_needed16() is used, +** the names are passed as UTF-16 in machine native byte order. +** ^A call to either function replaces the existing collation-needed callback. +** +** ^(When the callback is invoked, the first argument passed is a copy +** of the second argument to sqlite3_collation_needed() or +** sqlite3_collation_needed16(). The second argument is the database +** connection. The third argument is one of [SQLITE_UTF8], [SQLITE_UTF16BE], +** or [SQLITE_UTF16LE], indicating the most desirable form of the collation +** sequence function required. The fourth parameter is the name of the +** required collation sequence.)^ +** +** The callback function should register the desired collation using +** [sqlite3_create_collation()], [sqlite3_create_collation16()], or +** [sqlite3_create_collation_v2()]. +*/ +SQLITE_API int sqlite3_collation_needed( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const char*) +); +SQLITE_API int sqlite3_collation_needed16( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const void*) +); + +#ifdef SQLITE_ENABLE_CEROD +/* +** Specify the activation key for a CEROD database. Unless +** activated, none of the CEROD routines will work. +*/ +SQLITE_API void sqlite3_activate_cerod( + const char *zPassPhrase /* Activation phrase */ +); +#endif + +/* +** CAPI3REF: Suspend Execution For A Short Time +** +** The sqlite3_sleep() function causes the current thread to suspend execution +** for at least a number of milliseconds specified in its parameter. +** +** If the operating system does not support sleep requests with +** millisecond time resolution, then the time will be rounded up to +** the nearest second. The number of milliseconds of sleep actually +** requested from the operating system is returned. +** +** ^SQLite implements this interface by calling the xSleep() +** method of the default [sqlite3_vfs] object. If the xSleep() method +** of the default VFS is not implemented correctly, or not implemented at +** all, then the behavior of sqlite3_sleep() may deviate from the description +** in the previous paragraphs. +** +** If a negative argument is passed to sqlite3_sleep() the results vary by +** VFS and operating system. Some system treat a negative argument as an +** instruction to sleep forever. Others understand it to mean do not sleep +** at all. ^In SQLite version 3.42.0 and later, a negative +** argument passed into sqlite3_sleep() is changed to zero before it is relayed +** down into the xSleep method of the VFS. +*/ +SQLITE_API int sqlite3_sleep(int); + +/* +** CAPI3REF: Name Of The Folder Holding Temporary Files +** +** ^(If this global variable is made to point to a string which is +** the name of a folder (a.k.a. directory), then all temporary files +** created by SQLite when using a built-in [sqlite3_vfs | VFS] +** will be placed in that directory.)^ ^If this variable +** is a NULL pointer, then SQLite performs a search for an appropriate +** temporary file directory. +** +** Applications are strongly discouraged from using this global variable. +** It is required to set a temporary folder on Windows Runtime (WinRT). +** But for all other platforms, it is highly recommended that applications +** neither read nor write this variable. This global variable is a relic +** that exists for backwards compatibility of legacy applications and should +** be avoided in new projects. +** +** It is not safe to read or modify this variable in more than one +** thread at a time. It is not safe to read or modify this variable +** if a [database connection] is being used at the same time in a separate +** thread. +** It is intended that this variable be set once +** as part of process initialization and before any SQLite interface +** routines have been called and that this variable remain unchanged +** thereafter. +** +** ^The [temp_store_directory pragma] may modify this variable and cause +** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, +** the [temp_store_directory pragma] always assumes that any string +** that this variable points to is held in memory obtained from +** [sqlite3_malloc] and the pragma may attempt to free that memory +** using [sqlite3_free]. +** Hence, if this variable is modified directly, either it should be +** made NULL or made to point to memory obtained from [sqlite3_malloc] +** or else the use of the [temp_store_directory pragma] should be avoided. +** Except when requested by the [temp_store_directory pragma], SQLite +** does not free the memory that sqlite3_temp_directory points to. If +** the application wants that memory to be freed, it must do +** so itself, taking care to only do so after all [database connection] +** objects have been destroyed. +** +** <b>Note to Windows Runtime users:</b> The temporary directory must be set +** prior to calling [sqlite3_open] or [sqlite3_open_v2]. Otherwise, various +** features that require the use of temporary files may fail. Here is an +** example of how to do this using C++ with the Windows Runtime: +** +** <blockquote><pre> +** LPCWSTR zPath = Windows::Storage::ApplicationData::Current-> +** &nbsp; TemporaryFolder->Path->Data(); +** char zPathBuf&#91;MAX_PATH + 1&#93;; +** memset(zPathBuf, 0, sizeof(zPathBuf)); +** WideCharToMultiByte(CP_UTF8, 0, zPath, -1, zPathBuf, sizeof(zPathBuf), +** &nbsp; NULL, NULL); +** sqlite3_temp_directory = sqlite3_mprintf("%s", zPathBuf); +** </pre></blockquote> +*/ +SQLITE_API char *sqlite3_temp_directory; + +/* +** CAPI3REF: Name Of The Folder Holding Database Files +** +** ^(If this global variable is made to point to a string which is +** the name of a folder (a.k.a. directory), then all database files +** specified with a relative pathname and created or accessed by +** SQLite when using a built-in windows [sqlite3_vfs | VFS] will be assumed +** to be relative to that directory.)^ ^If this variable is a NULL +** pointer, then SQLite assumes that all database files specified +** with a relative pathname are relative to the current directory +** for the process. Only the windows VFS makes use of this global +** variable; it is ignored by the unix VFS. +** +** Changing the value of this variable while a database connection is +** open can result in a corrupt database. +** +** It is not safe to read or modify this variable in more than one +** thread at a time. It is not safe to read or modify this variable +** if a [database connection] is being used at the same time in a separate +** thread. +** It is intended that this variable be set once +** as part of process initialization and before any SQLite interface +** routines have been called and that this variable remain unchanged +** thereafter. +** +** ^The [data_store_directory pragma] may modify this variable and cause +** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, +** the [data_store_directory pragma] always assumes that any string +** that this variable points to is held in memory obtained from +** [sqlite3_malloc] and the pragma may attempt to free that memory +** using [sqlite3_free]. +** Hence, if this variable is modified directly, either it should be +** made NULL or made to point to memory obtained from [sqlite3_malloc] +** or else the use of the [data_store_directory pragma] should be avoided. +*/ +SQLITE_API char *sqlite3_data_directory; + +/* +** CAPI3REF: Win32 Specific Interface +** +** These interfaces are available only on Windows. The +** [sqlite3_win32_set_directory] interface is used to set the value associated +** with the [sqlite3_temp_directory] or [sqlite3_data_directory] variable, to +** zValue, depending on the value of the type parameter. The zValue parameter +** should be NULL to cause the previous value to be freed via [sqlite3_free]; +** a non-NULL value will be copied into memory obtained from [sqlite3_malloc] +** prior to being used. The [sqlite3_win32_set_directory] interface returns +** [SQLITE_OK] to indicate success, [SQLITE_ERROR] if the type is unsupported, +** or [SQLITE_NOMEM] if memory could not be allocated. The value of the +** [sqlite3_data_directory] variable is intended to act as a replacement for +** the current directory on the sub-platforms of Win32 where that concept is +** not present, e.g. WinRT and UWP. The [sqlite3_win32_set_directory8] and +** [sqlite3_win32_set_directory16] interfaces behave exactly the same as the +** sqlite3_win32_set_directory interface except the string parameter must be +** UTF-8 or UTF-16, respectively. +*/ +SQLITE_API int sqlite3_win32_set_directory( + unsigned long type, /* Identifier for directory being set or reset */ + void *zValue /* New value for directory being set or reset */ +); +SQLITE_API int sqlite3_win32_set_directory8(unsigned long type, const char *zValue); +SQLITE_API int sqlite3_win32_set_directory16(unsigned long type, const void *zValue); + +/* +** CAPI3REF: Win32 Directory Types +** +** These macros are only available on Windows. They define the allowed values +** for the type argument to the [sqlite3_win32_set_directory] interface. +*/ +#define SQLITE_WIN32_DATA_DIRECTORY_TYPE 1 +#define SQLITE_WIN32_TEMP_DIRECTORY_TYPE 2 + +/* +** CAPI3REF: Test For Auto-Commit Mode +** KEYWORDS: {autocommit mode} +** METHOD: sqlite3 +** +** ^The sqlite3_get_autocommit() interface returns non-zero or +** zero if the given database connection is or is not in autocommit mode, +** respectively. ^Autocommit mode is on by default. +** ^Autocommit mode is disabled by a [BEGIN] statement. +** ^Autocommit mode is re-enabled by a [COMMIT] or [ROLLBACK]. +** +** If certain kinds of errors occur on a statement within a multi-statement +** transaction (errors including [SQLITE_FULL], [SQLITE_IOERR], +** [SQLITE_NOMEM], [SQLITE_BUSY], and [SQLITE_INTERRUPT]) then the +** transaction might be rolled back automatically. The only way to +** find out whether SQLite automatically rolled back the transaction after +** an error is to use this function. +** +** If another thread changes the autocommit status of the database +** connection while this routine is running, then the return value +** is undefined. +*/ +SQLITE_API int sqlite3_get_autocommit(sqlite3*); + +/* +** CAPI3REF: Find The Database Handle Of A Prepared Statement +** METHOD: sqlite3_stmt +** +** ^The sqlite3_db_handle interface returns the [database connection] handle +** to which a [prepared statement] belongs. ^The [database connection] +** returned by sqlite3_db_handle is the same [database connection] +** that was the first argument +** to the [sqlite3_prepare_v2()] call (or its variants) that was used to +** create the statement in the first place. +*/ +SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); + +/* +** CAPI3REF: Return The Schema Name For A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name +** for the N-th database on database connection D, or a NULL pointer of N is +** out of range. An N value of 0 means the main database file. An N of 1 is +** the "temp" schema. Larger values of N correspond to various ATTACH-ed +** databases. +** +** Space to hold the string that is returned by sqlite3_db_name() is managed +** by SQLite itself. The string might be deallocated by any operation that +** changes the schema, including [ATTACH] or [DETACH] or calls to +** [sqlite3_serialize()] or [sqlite3_deserialize()], even operations that +** occur on a different thread. Applications that need to +** remember the string long-term should make their own copy. Applications that +** are accessing the same database connection simultaneously on multiple +** threads should mutex-protect calls to this API and should make their own +** private copy of the result prior to releasing the mutex. +*/ +SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N); + +/* +** CAPI3REF: Return The Filename For A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_filename(D,N) interface returns a pointer to the filename +** associated with database N of connection D. +** ^If there is no attached database N on the database +** connection D, or if database N is a temporary or in-memory database, then +** this function will return either a NULL pointer or an empty string. +** +** ^The string value returned by this routine is owned and managed by +** the database connection. ^The value will be valid until the database N +** is [DETACH]-ed or until the database connection closes. +** +** ^The filename returned by this function is the output of the +** xFullPathname method of the [VFS]. ^In other words, the filename +** will be an absolute pathname, even if the filename used +** to open the database originally was a URI or relative pathname. +** +** If the filename pointer returned by this routine is not NULL, then it +** can be used as the filename input parameter to these routines: +** <ul> +** <li> [sqlite3_uri_parameter()] +** <li> [sqlite3_uri_boolean()] +** <li> [sqlite3_uri_int64()] +** <li> [sqlite3_filename_database()] +** <li> [sqlite3_filename_journal()] +** <li> [sqlite3_filename_wal()] +** </ul> +*/ +SQLITE_API sqlite3_filename sqlite3_db_filename(sqlite3 *db, const char *zDbName); + +/* +** CAPI3REF: Determine if a database is read-only +** METHOD: sqlite3 +** +** ^The sqlite3_db_readonly(D,N) interface returns 1 if the database N +** of connection D is read-only, 0 if it is read/write, or -1 if N is not +** the name of a database on connection D. +*/ +SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName); + +/* +** CAPI3REF: Determine the transaction state of a database +** METHOD: sqlite3 +** +** ^The sqlite3_txn_state(D,S) interface returns the current +** [transaction state] of schema S in database connection D. ^If S is NULL, +** then the highest transaction state of any schema on database connection D +** is returned. Transaction states are (in order of lowest to highest): +** <ol> +** <li value="0"> SQLITE_TXN_NONE +** <li value="1"> SQLITE_TXN_READ +** <li value="2"> SQLITE_TXN_WRITE +** </ol> +** ^If the S argument to sqlite3_txn_state(D,S) is not the name of +** a valid schema, then -1 is returned. +*/ +SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); + +/* +** CAPI3REF: Allowed return values from [sqlite3_txn_state()] +** KEYWORDS: {transaction state} +** +** These constants define the current transaction state of a database file. +** ^The [sqlite3_txn_state(D,S)] interface returns one of these +** constants in order to describe the transaction state of schema S +** in [database connection] D. +** +** <dl> +** [[SQLITE_TXN_NONE]] <dt>SQLITE_TXN_NONE</dt> +** <dd>The SQLITE_TXN_NONE state means that no transaction is currently +** pending.</dd> +** +** [[SQLITE_TXN_READ]] <dt>SQLITE_TXN_READ</dt> +** <dd>The SQLITE_TXN_READ state means that the database is currently +** in a read transaction. Content has been read from the database file +** but nothing in the database file has changed. The transaction state +** will advanced to SQLITE_TXN_WRITE if any changes occur and there are +** no other conflicting concurrent write transactions. The transaction +** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or +** [COMMIT].</dd> +** +** [[SQLITE_TXN_WRITE]] <dt>SQLITE_TXN_WRITE</dt> +** <dd>The SQLITE_TXN_WRITE state means that the database is currently +** in a write transaction. Content has been written to the database file +** but has not yet committed. The transaction state will change to +** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd> +*/ +#define SQLITE_TXN_NONE 0 +#define SQLITE_TXN_READ 1 +#define SQLITE_TXN_WRITE 2 + +/* +** CAPI3REF: Find the next prepared statement +** METHOD: sqlite3 +** +** ^This interface returns a pointer to the next [prepared statement] after +** pStmt associated with the [database connection] pDb. ^If pStmt is NULL +** then this interface returns a pointer to the first prepared statement +** associated with the database connection pDb. ^If no prepared statement +** satisfies the conditions of this routine, it returns NULL. +** +** The [database connection] pointer D in a call to +** [sqlite3_next_stmt(D,S)] must refer to an open database +** connection and in particular must not be a NULL pointer. +*/ +SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Commit And Rollback Notification Callbacks +** METHOD: sqlite3 +** +** ^The sqlite3_commit_hook() interface registers a callback +** function to be invoked whenever a transaction is [COMMIT | committed]. +** ^Any callback set by a previous call to sqlite3_commit_hook() +** for the same database connection is overridden. +** ^The sqlite3_rollback_hook() interface registers a callback +** function to be invoked whenever a transaction is [ROLLBACK | rolled back]. +** ^Any callback set by a previous call to sqlite3_rollback_hook() +** for the same database connection is overridden. +** ^The pArg argument is passed through to the callback. +** ^If the callback on a commit hook function returns non-zero, +** then the commit is converted into a rollback. +** +** ^The sqlite3_commit_hook(D,C,P) and sqlite3_rollback_hook(D,C,P) functions +** return the P argument from the previous call of the same function +** on the same [database connection] D, or NULL for +** the first call for each function on D. +** +** The commit and rollback hook callbacks are not reentrant. +** The callback implementation must not do anything that will modify +** the database connection that invoked the callback. Any actions +** to modify the database connection must be deferred until after the +** completion of the [sqlite3_step()] call that triggered the commit +** or rollback hook in the first place. +** Note that running any other SQL statements, including SELECT statements, +** or merely calling [sqlite3_prepare_v2()] and [sqlite3_step()] will modify +** the database connections for the meaning of "modify" in this paragraph. +** +** ^Registering a NULL function disables the callback. +** +** ^When the commit hook callback routine returns zero, the [COMMIT] +** operation is allowed to continue normally. ^If the commit hook +** returns non-zero, then the [COMMIT] is converted into a [ROLLBACK]. +** ^The rollback hook is invoked on a rollback that results from a commit +** hook returning non-zero, just as it would be with any other rollback. +** +** ^For the purposes of this API, a transaction is said to have been +** rolled back if an explicit "ROLLBACK" statement is executed, or +** an error or constraint causes an implicit rollback to occur. +** ^The rollback callback is not invoked if a transaction is +** automatically rolled back because the database connection is closed. +** +** See also the [sqlite3_update_hook()] interface. +*/ +SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); +SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); + +/* +** CAPI3REF: Autovacuum Compaction Amount Callback +** METHOD: sqlite3 +** +** ^The sqlite3_autovacuum_pages(D,C,P,X) interface registers a callback +** function C that is invoked prior to each autovacuum of the database +** file. ^The callback is passed a copy of the generic data pointer (P), +** the schema-name of the attached database that is being autovacuumed, +** the size of the database file in pages, the number of free pages, +** and the number of bytes per page, respectively. The callback should +** return the number of free pages that should be removed by the +** autovacuum. ^If the callback returns zero, then no autovacuum happens. +** ^If the value returned is greater than or equal to the number of +** free pages, then a complete autovacuum happens. +** +** <p>^If there are multiple ATTACH-ed database files that are being +** modified as part of a transaction commit, then the autovacuum pages +** callback is invoked separately for each file. +** +** <p><b>The callback is not reentrant.</b> The callback function should +** not attempt to invoke any other SQLite interface. If it does, bad +** things may happen, including segmentation faults and corrupt database +** files. The callback function should be a simple function that +** does some arithmetic on its input parameters and returns a result. +** +** ^The X parameter to sqlite3_autovacuum_pages(D,C,P,X) is an optional +** destructor for the P parameter. ^If X is not NULL, then X(P) is +** invoked whenever the database connection closes or when the callback +** is overwritten by another invocation of sqlite3_autovacuum_pages(). +** +** <p>^There is only one autovacuum pages callback per database connection. +** ^Each call to the sqlite3_autovacuum_pages() interface overrides all +** previous invocations for that database connection. ^If the callback +** argument (C) to sqlite3_autovacuum_pages(D,C,P,X) is a NULL pointer, +** then the autovacuum steps callback is cancelled. The return value +** from sqlite3_autovacuum_pages() is normally SQLITE_OK, but might +** be some other error code if something goes wrong. The current +** implementation will only return SQLITE_OK or SQLITE_MISUSE, but other +** return codes might be added in future releases. +** +** <p>If no autovacuum pages callback is specified (the usual case) or +** a NULL pointer is provided for the callback, +** then the default behavior is to vacuum all free pages. So, in other +** words, the default behavior is the same as if the callback function +** were something like this: +** +** <blockquote><pre> +** &nbsp; unsigned int demonstration_autovac_pages_callback( +** &nbsp; void *pClientData, +** &nbsp; const char *zSchema, +** &nbsp; unsigned int nDbPage, +** &nbsp; unsigned int nFreePage, +** &nbsp; unsigned int nBytePerPage +** &nbsp; ){ +** &nbsp; return nFreePage; +** &nbsp; } +** </pre></blockquote> +*/ +SQLITE_API int sqlite3_autovacuum_pages( + sqlite3 *db, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, + void(*)(void*) +); + + +/* +** CAPI3REF: Data Change Notification Callbacks +** METHOD: sqlite3 +** +** ^The sqlite3_update_hook() interface registers a callback function +** with the [database connection] identified by the first argument +** to be invoked whenever a row is updated, inserted or deleted in +** a [rowid table]. +** ^Any callback set by a previous call to this function +** for the same database connection is overridden. +** +** ^The second argument is a pointer to the function to invoke when a +** row is updated, inserted or deleted in a rowid table. +** ^The first argument to the callback is a copy of the third argument +** to sqlite3_update_hook(). +** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], +** or [SQLITE_UPDATE], depending on the operation that caused the callback +** to be invoked. +** ^The third and fourth arguments to the callback contain pointers to the +** database and table name containing the affected row. +** ^The final callback parameter is the [rowid] of the row. +** ^In the case of an update, this is the [rowid] after the update takes place. +** +** ^(The update hook is not invoked when internal system tables are +** modified (i.e. sqlite_sequence).)^ +** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified. +** +** ^In the current implementation, the update hook +** is not invoked when conflicting rows are deleted because of an +** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook +** invoked when rows are deleted using the [truncate optimization]. +** The exceptions defined in this paragraph might change in a future +** release of SQLite. +** +** The update hook implementation must not do anything that will modify +** the database connection that invoked the update hook. Any actions +** to modify the database connection must be deferred until after the +** completion of the [sqlite3_step()] call that triggered the update hook. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** ^The sqlite3_update_hook(D,C,P) function +** returns the P argument from the previous call +** on the same [database connection] D, or NULL for +** the first call on D. +** +** See also the [sqlite3_commit_hook()], [sqlite3_rollback_hook()], +** and [sqlite3_preupdate_hook()] interfaces. +*/ +SQLITE_API void *sqlite3_update_hook( + sqlite3*, + void(*)(void *,int ,char const *,char const *,sqlite3_int64), + void* +); + +/* +** CAPI3REF: Enable Or Disable Shared Pager Cache +** +** ^(This routine enables or disables the sharing of the database cache +** and schema data structures between [database connection | connections] +** to the same database. Sharing is enabled if the argument is true +** and disabled if the argument is false.)^ +** +** This interface is omitted if SQLite is compiled with +** [-DSQLITE_OMIT_SHARED_CACHE]. The [-DSQLITE_OMIT_SHARED_CACHE] +** compile-time option is recommended because the +** [use of shared cache mode is discouraged]. +** +** ^Cache sharing is enabled and disabled for an entire process. +** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). +** In prior versions of SQLite, +** sharing was enabled or disabled for each thread separately. +** +** ^(The cache sharing mode set by this interface effects all subsequent +** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()]. +** Existing database connections continue to use the sharing mode +** that was in effect at the time they were opened.)^ +** +** ^(This routine returns [SQLITE_OK] if shared cache was enabled or disabled +** successfully. An [error code] is returned otherwise.)^ +** +** ^Shared cache is disabled by default. It is recommended that it stay +** that way. In other words, do not use this routine. This interface +** continues to be provided for historical compatibility, but its use is +** discouraged. Any use of shared cache is discouraged. If shared cache +** must be used, it is recommended that shared cache only be enabled for +** individual database connections using the [sqlite3_open_v2()] interface +** with the [SQLITE_OPEN_SHAREDCACHE] flag. +** +** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0 +** and will always return SQLITE_MISUSE. On those systems, +** shared cache mode should be enabled per-database connection via +** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE]. +** +** This interface is threadsafe on processors where writing a +** 32-bit integer is atomic. +** +** See Also: [SQLite Shared-Cache Mode] +*/ +SQLITE_API int sqlite3_enable_shared_cache(int); + +/* +** CAPI3REF: Attempt To Free Heap Memory +** +** ^The sqlite3_release_memory() interface attempts to free N bytes +** of heap memory by deallocating non-essential memory allocations +** held by the database library. Memory used to cache database +** pages to improve performance is an example of non-essential memory. +** ^sqlite3_release_memory() returns the number of bytes actually freed, +** which might be more or less than the amount requested. +** ^The sqlite3_release_memory() routine is a no-op returning zero +** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT]. +** +** See also: [sqlite3_db_release_memory()] +*/ +SQLITE_API int sqlite3_release_memory(int); + +/* +** CAPI3REF: Free Memory Used By A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_release_memory(D) interface attempts to free as much heap +** memory as possible from database connection D. Unlike the +** [sqlite3_release_memory()] interface, this interface is in effect even +** when the [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is +** omitted. +** +** See also: [sqlite3_release_memory()] +*/ +SQLITE_API int sqlite3_db_release_memory(sqlite3*); + +/* +** CAPI3REF: Impose A Limit On Heap Size +** +** These interfaces impose limits on the amount of heap memory that will be +** by all database connections within a single process. +** +** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the +** soft limit on the amount of heap memory that may be allocated by SQLite. +** ^SQLite strives to keep heap memory utilization below the soft heap +** limit by reducing the number of pages held in the page cache +** as heap memory usages approaches the limit. +** ^The soft heap limit is "soft" because even though SQLite strives to stay +** below the limit, it will exceed the limit rather than generate +** an [SQLITE_NOMEM] error. In other words, the soft heap limit +** is advisory only. +** +** ^The sqlite3_hard_heap_limit64(N) interface sets a hard upper bound of +** N bytes on the amount of memory that will be allocated. ^The +** sqlite3_hard_heap_limit64(N) interface is similar to +** sqlite3_soft_heap_limit64(N) except that memory allocations will fail +** when the hard heap limit is reached. +** +** ^The return value from both sqlite3_soft_heap_limit64() and +** sqlite3_hard_heap_limit64() is the size of +** the heap limit prior to the call, or negative in the case of an +** error. ^If the argument N is negative +** then no change is made to the heap limit. Hence, the current +** size of heap limits can be determined by invoking +** sqlite3_soft_heap_limit64(-1) or sqlite3_hard_heap_limit(-1). +** +** ^Setting the heap limits to zero disables the heap limiter mechanism. +** +** ^The soft heap limit may not be greater than the hard heap limit. +** ^If the hard heap limit is enabled and if sqlite3_soft_heap_limit(N) +** is invoked with a value of N that is greater than the hard heap limit, +** the soft heap limit is set to the value of the hard heap limit. +** ^The soft heap limit is automatically enabled whenever the hard heap +** limit is enabled. ^When sqlite3_hard_heap_limit64(N) is invoked and +** the soft heap limit is outside the range of 1..N, then the soft heap +** limit is set to N. ^Invoking sqlite3_soft_heap_limit64(0) when the +** hard heap limit is enabled makes the soft heap limit equal to the +** hard heap limit. +** +** The memory allocation limits can also be adjusted using +** [PRAGMA soft_heap_limit] and [PRAGMA hard_heap_limit]. +** +** ^(The heap limits are not enforced in the current implementation +** if one or more of following conditions are true: +** +** <ul> +** <li> The limit value is set to zero. +** <li> Memory accounting is disabled using a combination of the +** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and +** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option. +** <li> An alternative page cache implementation is specified using +** [sqlite3_config]([SQLITE_CONFIG_PCACHE2],...). +** <li> The page cache allocates from its own memory pool supplied +** by [sqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than +** from the heap. +** </ul>)^ +** +** The circumstances under which SQLite will enforce the heap limits may +** changes in future releases of SQLite. +*/ +SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); +SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N); + +/* +** CAPI3REF: Deprecated Soft Heap Limit Interface +** DEPRECATED +** +** This is a deprecated version of the [sqlite3_soft_heap_limit64()] +** interface. This routine is provided for historical compatibility +** only. All new applications should use the +** [sqlite3_soft_heap_limit64()] interface rather than this one. +*/ +SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); + + +/* +** CAPI3REF: Extract Metadata About A Column Of A Table +** METHOD: sqlite3 +** +** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns +** information about column C of table T in database D +** on [database connection] X.)^ ^The sqlite3_table_column_metadata() +** interface returns SQLITE_OK and fills in the non-NULL pointers in +** the final five arguments with appropriate values if the specified +** column exists. ^The sqlite3_table_column_metadata() interface returns +** SQLITE_ERROR if the specified column does not exist. +** ^If the column-name parameter to sqlite3_table_column_metadata() is a +** NULL pointer, then this routine simply checks for the existence of the +** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it +** does not. If the table name parameter T in a call to +** sqlite3_table_column_metadata(X,D,T,C,...) is NULL then the result is +** undefined behavior. +** +** ^The column is identified by the second, third and fourth parameters to +** this function. ^(The second parameter is either the name of the database +** (i.e. "main", "temp", or an attached database) containing the specified +** table or NULL.)^ ^If it is NULL, then all attached databases are searched +** for the table using the same algorithm used by the database engine to +** resolve unqualified table references. +** +** ^The third and fourth parameters to this function are the table and column +** name of the desired column, respectively. +** +** ^Metadata is returned by writing to the memory locations passed as the 5th +** and subsequent parameters to this function. ^Any of these arguments may be +** NULL, in which case the corresponding element of metadata is omitted. +** +** ^(<blockquote> +** <table border="1"> +** <tr><th> Parameter <th> Output<br>Type <th> Description +** +** <tr><td> 5th <td> const char* <td> Data type +** <tr><td> 6th <td> const char* <td> Name of default collation sequence +** <tr><td> 7th <td> int <td> True if column has a NOT NULL constraint +** <tr><td> 8th <td> int <td> True if column is part of the PRIMARY KEY +** <tr><td> 9th <td> int <td> True if column is [AUTOINCREMENT] +** </table> +** </blockquote>)^ +** +** ^The memory pointed to by the character pointers returned for the +** declaration type and collation sequence is valid until the next +** call to any SQLite API function. +** +** ^If the specified table is actually a view, an [error code] is returned. +** +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** is not a [WITHOUT ROWID] table and an +** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output +** parameters are set for the explicitly declared column. ^(If there is no +** [INTEGER PRIMARY KEY] column, then the outputs +** for the [rowid] are set as follows: +** +** <pre> +** data type: "INTEGER" +** collation sequence: "BINARY" +** not null: 0 +** primary key: 1 +** auto increment: 0 +** </pre>)^ +** +** ^This function causes all database schemas to be read from disk and +** parsed, if that has not already been done, and returns an error if +** any errors are encountered while loading the schema. +*/ +SQLITE_API int sqlite3_table_column_metadata( + sqlite3 *db, /* Connection handle */ + const char *zDbName, /* Database name or NULL */ + const char *zTableName, /* Table name */ + const char *zColumnName, /* Column name */ + char const **pzDataType, /* OUTPUT: Declared data type */ + char const **pzCollSeq, /* OUTPUT: Collation sequence name */ + int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */ + int *pPrimaryKey, /* OUTPUT: True if column part of PK */ + int *pAutoinc /* OUTPUT: True if column is auto-increment */ +); + +/* +** CAPI3REF: Load An Extension +** METHOD: sqlite3 +** +** ^This interface loads an SQLite extension library from the named file. +** +** ^The sqlite3_load_extension() interface attempts to load an +** [SQLite extension] library contained in the file zFile. If +** the file cannot be loaded directly, attempts are made to load +** with various operating-system specific extensions added. +** So for example, if "samplelib" cannot be loaded, then names like +** "samplelib.so" or "samplelib.dylib" or "samplelib.dll" might +** be tried also. +** +** ^The entry point is zProc. +** ^(zProc may be 0, in which case SQLite will try to come up with an +** entry point name on its own. It first tries "sqlite3_extension_init". +** If that does not work, it constructs a name "sqlite3_X_init" where the +** X is consists of the lower-case equivalent of all ASCII alphabetic +** characters in the filename from the last "/" to the first following +** "." and omitting any initial "lib".)^ +** ^The sqlite3_load_extension() interface returns +** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. +** ^If an error occurs and pzErrMsg is not 0, then the +** [sqlite3_load_extension()] interface shall attempt to +** fill *pzErrMsg with error message text stored in memory +** obtained from [sqlite3_malloc()]. The calling function +** should free this memory by calling [sqlite3_free()]. +** +** ^Extension loading must be enabled using +** [sqlite3_enable_load_extension()] or +** [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],1,NULL) +** prior to calling this API, +** otherwise an error will be returned. +** +** <b>Security warning:</b> It is recommended that the +** [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method be used to enable only this +** interface. The use of the [sqlite3_enable_load_extension()] interface +** should be avoided. This will keep the SQL function [load_extension()] +** disabled and prevent SQL injections from giving attackers +** access to extension loading capabilities. +** +** See also the [load_extension() SQL function]. +*/ +SQLITE_API int sqlite3_load_extension( + sqlite3 *db, /* Load the extension into this database connection */ + const char *zFile, /* Name of the shared library containing extension */ + const char *zProc, /* Entry point. Derived from zFile if 0 */ + char **pzErrMsg /* Put error message here if not 0 */ +); + +/* +** CAPI3REF: Enable Or Disable Extension Loading +** METHOD: sqlite3 +** +** ^So as not to open security holes in older applications that are +** unprepared to deal with [extension loading], and as a means of disabling +** [extension loading] while evaluating user-entered SQL, the following API +** is provided to turn the [sqlite3_load_extension()] mechanism on and off. +** +** ^Extension loading is off by default. +** ^Call the sqlite3_enable_load_extension() routine with onoff==1 +** to turn extension loading on and call it with onoff==0 to turn +** it back off again. +** +** ^This interface enables or disables both the C-API +** [sqlite3_load_extension()] and the SQL function [load_extension()]. +** ^(Use [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],..) +** to enable or disable only the C-API.)^ +** +** <b>Security warning:</b> It is recommended that extension loading +** be enabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method +** rather than this interface, so the [load_extension()] SQL function +** remains disabled. This will prevent SQL injections from giving attackers +** access to extension loading capabilities. +*/ +SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); + +/* +** CAPI3REF: Automatically Load Statically Linked Extensions +** +** ^This interface causes the xEntryPoint() function to be invoked for +** each new [database connection] that is created. The idea here is that +** xEntryPoint() is the entry point for a statically linked [SQLite extension] +** that is to be automatically loaded into all new database connections. +** +** ^(Even though the function prototype shows that xEntryPoint() takes +** no arguments and returns void, SQLite invokes xEntryPoint() with three +** arguments and expects an integer result as if the signature of the +** entry point where as follows: +** +** <blockquote><pre> +** &nbsp; int xEntryPoint( +** &nbsp; sqlite3 *db, +** &nbsp; const char **pzErrMsg, +** &nbsp; const struct sqlite3_api_routines *pThunk +** &nbsp; ); +** </pre></blockquote>)^ +** +** If the xEntryPoint routine encounters an error, it should make *pzErrMsg +** point to an appropriate error message (obtained from [sqlite3_mprintf()]) +** and return an appropriate [error code]. ^SQLite ensures that *pzErrMsg +** is NULL before calling the xEntryPoint(). ^SQLite will invoke +** [sqlite3_free()] on *pzErrMsg after xEntryPoint() returns. ^If any +** xEntryPoint() returns an error, the [sqlite3_open()], [sqlite3_open16()], +** or [sqlite3_open_v2()] call that provoked the xEntryPoint() will fail. +** +** ^Calling sqlite3_auto_extension(X) with an entry point X that is already +** on the list of automatic extensions is a harmless no-op. ^No entry point +** will be called more than once for each database connection that is opened. +** +** See also: [sqlite3_reset_auto_extension()] +** and [sqlite3_cancel_auto_extension()] +*/ +SQLITE_API int sqlite3_auto_extension(void(*xEntryPoint)(void)); + +/* +** CAPI3REF: Cancel Automatic Extension Loading +** +** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the +** initialization routine X that was registered using a prior call to +** [sqlite3_auto_extension(X)]. ^The [sqlite3_cancel_auto_extension(X)] +** routine returns 1 if initialization routine X was successfully +** unregistered and it returns 0 if X was not on the list of initialization +** routines. +*/ +SQLITE_API int sqlite3_cancel_auto_extension(void(*xEntryPoint)(void)); + +/* +** CAPI3REF: Reset Automatic Extension Loading +** +** ^This interface disables all automatic extensions previously +** registered using [sqlite3_auto_extension()]. +*/ +SQLITE_API void sqlite3_reset_auto_extension(void); + +/* +** Structures used by the virtual table interface +*/ +typedef struct sqlite3_vtab sqlite3_vtab; +typedef struct sqlite3_index_info sqlite3_index_info; +typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; +typedef struct sqlite3_module sqlite3_module; + +/* +** CAPI3REF: Virtual Table Object +** KEYWORDS: sqlite3_module {virtual table module} +** +** This structure, sometimes called a "virtual table module", +** defines the implementation of a [virtual table]. +** This structure consists mostly of methods for the module. +** +** ^A virtual table module is created by filling in a persistent +** instance of this structure and passing a pointer to that instance +** to [sqlite3_create_module()] or [sqlite3_create_module_v2()]. +** ^The registration remains valid until it is replaced by a different +** module or until the [database connection] closes. The content +** of this structure must not change while it is registered with +** any database connection. +*/ +struct sqlite3_module { + int iVersion; + int (*xCreate)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xConnect)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*); + int (*xDisconnect)(sqlite3_vtab *pVTab); + int (*xDestroy)(sqlite3_vtab *pVTab); + int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor); + int (*xClose)(sqlite3_vtab_cursor*); + int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, + int argc, sqlite3_value **argv); + int (*xNext)(sqlite3_vtab_cursor*); + int (*xEof)(sqlite3_vtab_cursor*); + int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int); + int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid); + int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *); + int (*xBegin)(sqlite3_vtab *pVTab); + int (*xSync)(sqlite3_vtab *pVTab); + int (*xCommit)(sqlite3_vtab *pVTab); + int (*xRollback)(sqlite3_vtab *pVTab); + int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg); + int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); + /* The methods above are in version 1 of the sqlite_module object. Those + ** below are for version 2 and greater. */ + int (*xSavepoint)(sqlite3_vtab *pVTab, int); + int (*xRelease)(sqlite3_vtab *pVTab, int); + int (*xRollbackTo)(sqlite3_vtab *pVTab, int); + /* The methods above are in versions 1 and 2 of the sqlite_module object. + ** Those below are for version 3 and greater. */ + int (*xShadowName)(const char*); + /* The methods above are in versions 1 through 3 of the sqlite_module object. + ** Those below are for version 4 and greater. */ + int (*xIntegrity)(sqlite3_vtab *pVTab, char**); +}; + +/* +** CAPI3REF: Virtual Table Indexing Information +** KEYWORDS: sqlite3_index_info +** +** The sqlite3_index_info structure and its substructures is used as part +** of the [virtual table] interface to +** pass information into and receive the reply from the [xBestIndex] +** method of a [virtual table module]. The fields under **Inputs** are the +** inputs to xBestIndex and are read-only. xBestIndex inserts its +** results into the **Outputs** fields. +** +** ^(The aConstraint[] array records WHERE clause constraints of the form: +** +** <blockquote>column OP expr</blockquote> +** +** where OP is =, &lt;, &lt;=, &gt;, or &gt;=.)^ ^(The particular operator is +** stored in aConstraint[].op using one of the +** [SQLITE_INDEX_CONSTRAINT_EQ | SQLITE_INDEX_CONSTRAINT_ values].)^ +** ^(The index of the column is stored in +** aConstraint[].iColumn.)^ ^(aConstraint[].usable is TRUE if the +** expr on the right-hand side can be evaluated (and thus the constraint +** is usable) and false if it cannot.)^ +** +** ^The optimizer automatically inverts terms of the form "expr OP column" +** and makes other simplifications to the WHERE clause in an attempt to +** get as many WHERE clause terms into the form shown above as possible. +** ^The aConstraint[] array only reports WHERE clause terms that are +** relevant to the particular virtual table being queried. +** +** ^Information about the ORDER BY clause is stored in aOrderBy[]. +** ^Each term of aOrderBy records a column of the ORDER BY clause. +** +** The colUsed field indicates which columns of the virtual table may be +** required by the current scan. Virtual table columns are numbered from +** zero in the order in which they appear within the CREATE TABLE statement +** passed to sqlite3_declare_vtab(). For the first 63 columns (columns 0-62), +** the corresponding bit is set within the colUsed mask if the column may be +** required by SQLite. If the table has at least 64 columns and any column +** to the right of the first 63 is required, then bit 63 of colUsed is also +** set. In other words, column iCol may be required if the expression +** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to +** non-zero. +** +** The [xBestIndex] method must fill aConstraintUsage[] with information +** about what parameters to pass to xFilter. ^If argvIndex>0 then +** the right-hand side of the corresponding aConstraint[] is evaluated +** and becomes the argvIndex-th entry in argv. ^(If aConstraintUsage[].omit +** is true, then the constraint is assumed to be fully handled by the +** virtual table and might not be checked again by the byte code.)^ ^(The +** aConstraintUsage[].omit flag is an optimization hint. When the omit flag +** is left in its default setting of false, the constraint will always be +** checked separately in byte code. If the omit flag is change to true, then +** the constraint may or may not be checked in byte code. In other words, +** when the omit flag is true there is no guarantee that the constraint will +** not be checked again using byte code.)^ +** +** ^The idxNum and idxStr values are recorded and passed into the +** [xFilter] method. +** ^[sqlite3_free()] is used to free idxStr if and only if +** needToFreeIdxStr is true. +** +** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in +** the correct order to satisfy the ORDER BY clause so that no separate +** sorting step is required. +** +** ^The estimatedCost value is an estimate of the cost of a particular +** strategy. A cost of N indicates that the cost of the strategy is similar +** to a linear scan of an SQLite table with N rows. A cost of log(N) +** indicates that the expense of the operation is similar to that of a +** binary search on a unique indexed field of an SQLite table with N rows. +** +** ^The estimatedRows value is an estimate of the number of rows that +** will be returned by the strategy. +** +** The xBestIndex method may optionally populate the idxFlags field with a +** mask of SQLITE_INDEX_SCAN_* flags. Currently there is only one such flag - +** SQLITE_INDEX_SCAN_UNIQUE. If the xBestIndex method sets this flag, SQLite +** assumes that the strategy may visit at most one row. +** +** Additionally, if xBestIndex sets the SQLITE_INDEX_SCAN_UNIQUE flag, then +** SQLite also assumes that if a call to the xUpdate() method is made as +** part of the same statement to delete or update a virtual table row and the +** implementation returns SQLITE_CONSTRAINT, then there is no need to rollback +** any database changes. In other words, if the xUpdate() returns +** SQLITE_CONSTRAINT, the database contents must be exactly as they were +** before xUpdate was called. By contrast, if SQLITE_INDEX_SCAN_UNIQUE is not +** set and xUpdate returns SQLITE_CONSTRAINT, any database changes made by +** the xUpdate method are automatically rolled back by SQLite. +** +** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info +** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). +** If a virtual table extension is +** used with an SQLite version earlier than 3.8.2, the results of attempting +** to read or write the estimatedRows field are undefined (but are likely +** to include crashing the application). The estimatedRows field should +** therefore only be used if [sqlite3_libversion_number()] returns a +** value greater than or equal to 3008002. Similarly, the idxFlags field +** was added for [version 3.9.0] ([dateof:3.9.0]). +** It may therefore only be used if +** sqlite3_libversion_number() returns a value greater than or equal to +** 3009000. +*/ +struct sqlite3_index_info { + /* Inputs */ + int nConstraint; /* Number of entries in aConstraint */ + struct sqlite3_index_constraint { + int iColumn; /* Column constrained. -1 for ROWID */ + unsigned char op; /* Constraint operator */ + unsigned char usable; /* True if this constraint is usable */ + int iTermOffset; /* Used internally - xBestIndex should ignore */ + } *aConstraint; /* Table of WHERE clause constraints */ + int nOrderBy; /* Number of terms in the ORDER BY clause */ + struct sqlite3_index_orderby { + int iColumn; /* Column number */ + unsigned char desc; /* True for DESC. False for ASC. */ + } *aOrderBy; /* The ORDER BY clause */ + /* Outputs */ + struct sqlite3_index_constraint_usage { + int argvIndex; /* if >0, constraint is part of argv to xFilter */ + unsigned char omit; /* Do not code a test for this constraint */ + } *aConstraintUsage; + int idxNum; /* Number used to identify the index */ + char *idxStr; /* String, possibly obtained from sqlite3_malloc */ + int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ + int orderByConsumed; /* True if output is already ordered */ + double estimatedCost; /* Estimated cost of using this index */ + /* Fields below are only available in SQLite 3.8.2 and later */ + sqlite3_int64 estimatedRows; /* Estimated number of rows returned */ + /* Fields below are only available in SQLite 3.9.0 and later */ + int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */ + /* Fields below are only available in SQLite 3.10.0 and later */ + sqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */ +}; + +/* +** CAPI3REF: Virtual Table Scan Flags +** +** Virtual table implementations are allowed to set the +** [sqlite3_index_info].idxFlags field to some combination of +** these bits. +*/ +#define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */ + +/* +** CAPI3REF: Virtual Table Constraint Operator Codes +** +** These macros define the allowed values for the +** [sqlite3_index_info].aConstraint[].op field. Each value represents +** an operator that is part of a constraint term in the WHERE clause of +** a query that uses a [virtual table]. +** +** ^The left-hand operand of the operator is given by the corresponding +** aConstraint[].iColumn field. ^An iColumn of -1 indicates the left-hand +** operand is the rowid. +** The SQLITE_INDEX_CONSTRAINT_LIMIT and SQLITE_INDEX_CONSTRAINT_OFFSET +** operators have no left-hand operand, and so for those operators the +** corresponding aConstraint[].iColumn is meaningless and should not be +** used. +** +** All operator values from SQLITE_INDEX_CONSTRAINT_FUNCTION through +** value 255 are reserved to represent functions that are overloaded +** by the [xFindFunction|xFindFunction method] of the virtual table +** implementation. +** +** The right-hand operands for each constraint might be accessible using +** the [sqlite3_vtab_rhs_value()] interface. Usually the right-hand +** operand is only available if it appears as a single constant literal +** in the input SQL. If the right-hand operand is another column or an +** expression (even a constant expression) or a parameter, then the +** sqlite3_vtab_rhs_value() probably will not be able to extract it. +** ^The SQLITE_INDEX_CONSTRAINT_ISNULL and +** SQLITE_INDEX_CONSTRAINT_ISNOTNULL operators have no right-hand operand +** and hence calls to sqlite3_vtab_rhs_value() for those operators will +** always return SQLITE_NOTFOUND. +** +** The collating sequence to be used for comparison can be found using +** the [sqlite3_vtab_collation()] interface. For most real-world virtual +** tables, the collating sequence of constraints does not matter (for example +** because the constraints are numeric) and so the sqlite3_vtab_collation() +** interface is not commonly needed. +*/ +#define SQLITE_INDEX_CONSTRAINT_EQ 2 +#define SQLITE_INDEX_CONSTRAINT_GT 4 +#define SQLITE_INDEX_CONSTRAINT_LE 8 +#define SQLITE_INDEX_CONSTRAINT_LT 16 +#define SQLITE_INDEX_CONSTRAINT_GE 32 +#define SQLITE_INDEX_CONSTRAINT_MATCH 64 +#define SQLITE_INDEX_CONSTRAINT_LIKE 65 +#define SQLITE_INDEX_CONSTRAINT_GLOB 66 +#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 +#define SQLITE_INDEX_CONSTRAINT_NE 68 +#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 +#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 +#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 +#define SQLITE_INDEX_CONSTRAINT_IS 72 +#define SQLITE_INDEX_CONSTRAINT_LIMIT 73 +#define SQLITE_INDEX_CONSTRAINT_OFFSET 74 +#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 + +/* +** CAPI3REF: Register A Virtual Table Implementation +** METHOD: sqlite3 +** +** ^These routines are used to register a new [virtual table module] name. +** ^Module names must be registered before +** creating a new [virtual table] using the module and before using a +** preexisting [virtual table] for the module. +** +** ^The module name is registered on the [database connection] specified +** by the first parameter. ^The name of the module is given by the +** second parameter. ^The third parameter is a pointer to +** the implementation of the [virtual table module]. ^The fourth +** parameter is an arbitrary client data pointer that is passed through +** into the [xCreate] and [xConnect] methods of the virtual table module +** when a new virtual table is be being created or reinitialized. +** +** ^The sqlite3_create_module_v2() interface has a fifth parameter which +** is a pointer to a destructor for the pClientData. ^SQLite will +** invoke the destructor function (if it is not NULL) when SQLite +** no longer needs the pClientData pointer. ^The destructor will also +** be invoked if the call to sqlite3_create_module_v2() fails. +** ^The sqlite3_create_module() +** interface is equivalent to sqlite3_create_module_v2() with a NULL +** destructor. +** +** ^If the third parameter (the pointer to the sqlite3_module object) is +** NULL then no new module is created and any existing modules with the +** same name are dropped. +** +** See also: [sqlite3_drop_modules()] +*/ +SQLITE_API int sqlite3_create_module( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *p, /* Methods for the module */ + void *pClientData /* Client data for xCreate/xConnect */ +); +SQLITE_API int sqlite3_create_module_v2( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *p, /* Methods for the module */ + void *pClientData, /* Client data for xCreate/xConnect */ + void(*xDestroy)(void*) /* Module destructor function */ +); + +/* +** CAPI3REF: Remove Unnecessary Virtual Table Implementations +** METHOD: sqlite3 +** +** ^The sqlite3_drop_modules(D,L) interface removes all virtual +** table modules from database connection D except those named on list L. +** The L parameter must be either NULL or a pointer to an array of pointers +** to strings where the array is terminated by a single NULL pointer. +** ^If the L parameter is NULL, then all virtual table modules are removed. +** +** See also: [sqlite3_create_module()] +*/ +SQLITE_API int sqlite3_drop_modules( + sqlite3 *db, /* Remove modules from this connection */ + const char **azKeep /* Except, do not remove the ones named here */ +); + +/* +** CAPI3REF: Virtual Table Instance Object +** KEYWORDS: sqlite3_vtab +** +** Every [virtual table module] implementation uses a subclass +** of this object to describe a particular instance +** of the [virtual table]. Each subclass will +** be tailored to the specific needs of the module implementation. +** The purpose of this superclass is to define certain fields that are +** common to all module implementations. +** +** ^Virtual tables methods can set an error message by assigning a +** string obtained from [sqlite3_mprintf()] to zErrMsg. The method should +** take care that any prior string is freed by a call to [sqlite3_free()] +** prior to assigning a new string to zErrMsg. ^After the error message +** is delivered up to the client application, the string will be automatically +** freed by sqlite3_free() and the zErrMsg field will be zeroed. +*/ +struct sqlite3_vtab { + const sqlite3_module *pModule; /* The module for this virtual table */ + int nRef; /* Number of open cursors */ + char *zErrMsg; /* Error message from sqlite3_mprintf() */ + /* Virtual table implementations will typically add additional fields */ +}; + +/* +** CAPI3REF: Virtual Table Cursor Object +** KEYWORDS: sqlite3_vtab_cursor {virtual table cursor} +** +** Every [virtual table module] implementation uses a subclass of the +** following structure to describe cursors that point into the +** [virtual table] and are used +** to loop through the virtual table. Cursors are created using the +** [sqlite3_module.xOpen | xOpen] method of the module and are destroyed +** by the [sqlite3_module.xClose | xClose] method. Cursors are used +** by the [xFilter], [xNext], [xEof], [xColumn], and [xRowid] methods +** of the module. Each module implementation will define +** the content of a cursor structure to suit its own needs. +** +** This superclass exists in order to define fields of the cursor that +** are common to all implementations. +*/ +struct sqlite3_vtab_cursor { + sqlite3_vtab *pVtab; /* Virtual table of this cursor */ + /* Virtual table implementations will typically add additional fields */ +}; + +/* +** CAPI3REF: Declare The Schema Of A Virtual Table +** +** ^The [xCreate] and [xConnect] methods of a +** [virtual table module] call this interface +** to declare the format (the names and datatypes of the columns) of +** the virtual tables they implement. +*/ +SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL); + +/* +** CAPI3REF: Overload A Function For A Virtual Table +** METHOD: sqlite3 +** +** ^(Virtual tables can provide alternative implementations of functions +** using the [xFindFunction] method of the [virtual table module]. +** But global versions of those functions +** must exist in order to be overloaded.)^ +** +** ^(This API makes sure a global version of a function with a particular +** name and number of parameters exists. If no such function exists +** before this API is called, a new function is created.)^ ^The implementation +** of the new function always causes an exception to be thrown. So +** the new function is not good for anything by itself. Its only +** purpose is to be a placeholder function that can be overloaded +** by a [virtual table]. +*/ +SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); + +/* +** CAPI3REF: A Handle To An Open BLOB +** KEYWORDS: {BLOB handle} {BLOB handles} +** +** An instance of this object represents an open BLOB on which +** [sqlite3_blob_open | incremental BLOB I/O] can be performed. +** ^Objects of this type are created by [sqlite3_blob_open()] +** and destroyed by [sqlite3_blob_close()]. +** ^The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces +** can be used to read or write small subsections of the BLOB. +** ^The [sqlite3_blob_bytes()] interface returns the size of the BLOB in bytes. +*/ +typedef struct sqlite3_blob sqlite3_blob; + +/* +** CAPI3REF: Open A BLOB For Incremental I/O +** METHOD: sqlite3 +** CONSTRUCTOR: sqlite3_blob +** +** ^(This interfaces opens a [BLOB handle | handle] to the BLOB located +** in row iRow, column zColumn, table zTable in database zDb; +** in other words, the same BLOB that would be selected by: +** +** <pre> +** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow; +** </pre>)^ +** +** ^(Parameter zDb is not the filename that contains the database, but +** rather the symbolic name of the database. For attached databases, this is +** the name that appears after the AS keyword in the [ATTACH] statement. +** For the main database file, the database name is "main". For TEMP +** tables, the database name is "temp".)^ +** +** ^If the flags parameter is non-zero, then the BLOB is opened for read +** and write access. ^If the flags parameter is zero, the BLOB is opened for +** read-only access. +** +** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored +** in *ppBlob. Otherwise an [error code] is returned and, unless the error +** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided +** the API is not misused, it is always safe to call [sqlite3_blob_close()] +** on *ppBlob after this function it returns. +** +** This function fails with SQLITE_ERROR if any of the following are true: +** <ul> +** <li> ^(Database zDb does not exist)^, +** <li> ^(Table zTable does not exist within database zDb)^, +** <li> ^(Table zTable is a WITHOUT ROWID table)^, +** <li> ^(Column zColumn does not exist)^, +** <li> ^(Row iRow is not present in the table)^, +** <li> ^(The specified column of row iRow contains a value that is not +** a TEXT or BLOB value)^, +** <li> ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE +** constraint and the blob is being opened for read/write access)^, +** <li> ^([foreign key constraints | Foreign key constraints] are enabled, +** column zColumn is part of a [child key] definition and the blob is +** being opened for read/write access)^. +** </ul> +** +** ^Unless it returns SQLITE_MISUSE, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** +** A BLOB referenced by sqlite3_blob_open() may be read using the +** [sqlite3_blob_read()] interface and modified by using +** [sqlite3_blob_write()]. The [BLOB handle] can be moved to a +** different row of the same table using the [sqlite3_blob_reopen()] +** interface. However, the column, table, or database of a [BLOB handle] +** cannot be changed after the [BLOB handle] is opened. +** +** ^(If the row that a BLOB handle points to is modified by an +** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects +** then the BLOB handle is marked as "expired". +** This is true if any column of the row is changed, even a column +** other than the one the BLOB handle is open on.)^ +** ^Calls to [sqlite3_blob_read()] and [sqlite3_blob_write()] for +** an expired BLOB handle fail with a return code of [SQLITE_ABORT]. +** ^(Changes written into a BLOB prior to the BLOB expiring are not +** rolled back by the expiration of the BLOB. Such changes will eventually +** commit if the transaction continues to completion.)^ +** +** ^Use the [sqlite3_blob_bytes()] interface to determine the size of +** the opened blob. ^The size of a blob may not be changed by this +** interface. Use the [UPDATE] SQL command to change the size of a +** blob. +** +** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces +** and the built-in [zeroblob] SQL function may be used to create a +** zero-filled blob to read or write using the incremental-blob interface. +** +** To avoid a resource leak, every open [BLOB handle] should eventually +** be released by a call to [sqlite3_blob_close()]. +** +** See also: [sqlite3_blob_close()], +** [sqlite3_blob_reopen()], [sqlite3_blob_read()], +** [sqlite3_blob_bytes()], [sqlite3_blob_write()]. +*/ +SQLITE_API int sqlite3_blob_open( + sqlite3*, + const char *zDb, + const char *zTable, + const char *zColumn, + sqlite3_int64 iRow, + int flags, + sqlite3_blob **ppBlob +); + +/* +** CAPI3REF: Move a BLOB Handle to a New Row +** METHOD: sqlite3_blob +** +** ^This function is used to move an existing [BLOB handle] so that it points +** to a different row of the same database table. ^The new row is identified +** by the rowid value passed as the second argument. Only the row can be +** changed. ^The database, table and column on which the blob handle is open +** remain the same. Moving an existing [BLOB handle] to a new row is +** faster than closing the existing handle and opening a new one. +** +** ^(The new row must meet the same criteria as for [sqlite3_blob_open()] - +** it must exist and there must be either a blob or text value stored in +** the nominated column.)^ ^If the new row is not present in the table, or if +** it does not contain a blob or text value, or if another error occurs, an +** SQLite error code is returned and the blob handle is considered aborted. +** ^All subsequent calls to [sqlite3_blob_read()], [sqlite3_blob_write()] or +** [sqlite3_blob_reopen()] on an aborted blob handle immediately return +** SQLITE_ABORT. ^Calling [sqlite3_blob_bytes()] on an aborted blob handle +** always returns zero. +** +** ^This function sets the database handle error code and message. +*/ +SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); + +/* +** CAPI3REF: Close A BLOB Handle +** DESTRUCTOR: sqlite3_blob +** +** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed +** unconditionally. Even if this routine returns an error code, the +** handle is still closed.)^ +** +** ^If the blob handle being closed was opened for read-write access, and if +** the database is in auto-commit mode and there are no other open read-write +** blob handles or active write statements, the current transaction is +** committed. ^If an error occurs while committing the transaction, an error +** code is returned and the transaction rolled back. +** +** Calling this function with an argument that is not a NULL pointer or an +** open blob handle results in undefined behaviour. ^Calling this routine +** with a null pointer (such as would be returned by a failed call to +** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function +** is passed a valid open blob handle, the values returned by the +** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning. +*/ +SQLITE_API int sqlite3_blob_close(sqlite3_blob *); + +/* +** CAPI3REF: Return The Size Of An Open BLOB +** METHOD: sqlite3_blob +** +** ^Returns the size in bytes of the BLOB accessible via the +** successfully opened [BLOB handle] in its only argument. ^The +** incremental blob I/O routines can only read or overwriting existing +** blob content; they cannot change the size of a blob. +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +*/ +SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *); + +/* +** CAPI3REF: Read Data From A BLOB Incrementally +** METHOD: sqlite3_blob +** +** ^(This function is used to read data from an open [BLOB handle] into a +** caller-supplied buffer. N bytes of data are copied into buffer Z +** from the open BLOB, starting at offset iOffset.)^ +** +** ^If offset iOffset is less than N bytes from the end of the BLOB, +** [SQLITE_ERROR] is returned and no data is read. ^If N or iOffset is +** less than zero, [SQLITE_ERROR] is returned and no data is read. +** ^The size of the blob (and hence the maximum value of N+iOffset) +** can be determined using the [sqlite3_blob_bytes()] interface. +** +** ^An attempt to read from an expired [BLOB handle] fails with an +** error code of [SQLITE_ABORT]. +** +** ^(On success, sqlite3_blob_read() returns SQLITE_OK. +** Otherwise, an [error code] or an [extended error code] is returned.)^ +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +** +** See also: [sqlite3_blob_write()]. +*/ +SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); + +/* +** CAPI3REF: Write Data Into A BLOB Incrementally +** METHOD: sqlite3_blob +** +** ^(This function is used to write data into an open [BLOB handle] from a +** caller-supplied buffer. N bytes of data are copied from the buffer Z +** into the open BLOB, starting at offset iOffset.)^ +** +** ^(On success, sqlite3_blob_write() returns SQLITE_OK. +** Otherwise, an [error code] or an [extended error code] is returned.)^ +** ^Unless SQLITE_MISUSE is returned, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** +** ^If the [BLOB handle] passed as the first argument was not opened for +** writing (the flags parameter to [sqlite3_blob_open()] was zero), +** this function returns [SQLITE_READONLY]. +** +** This function may only modify the contents of the BLOB; it is +** not possible to increase the size of a BLOB using this API. +** ^If offset iOffset is less than N bytes from the end of the BLOB, +** [SQLITE_ERROR] is returned and no data is written. The size of the +** BLOB (and hence the maximum value of N+iOffset) can be determined +** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less +** than zero [SQLITE_ERROR] is returned and no data is written. +** +** ^An attempt to write to an expired [BLOB handle] fails with an +** error code of [SQLITE_ABORT]. ^Writes to the BLOB that occurred +** before the [BLOB handle] expired are not rolled back by the +** expiration of the handle, though of course those changes might +** have been overwritten by the statement that expired the BLOB handle +** or by other independent statements. +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +** +** See also: [sqlite3_blob_read()]. +*/ +SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); + +/* +** CAPI3REF: Virtual File System Objects +** +** A virtual filesystem (VFS) is an [sqlite3_vfs] object +** that SQLite uses to interact +** with the underlying operating system. Most SQLite builds come with a +** single default VFS that is appropriate for the host computer. +** New VFSes can be registered and existing VFSes can be unregistered. +** The following interfaces are provided. +** +** ^The sqlite3_vfs_find() interface returns a pointer to a VFS given its name. +** ^Names are case sensitive. +** ^Names are zero-terminated UTF-8 strings. +** ^If there is no match, a NULL pointer is returned. +** ^If zVfsName is NULL then the default VFS is returned. +** +** ^New VFSes are registered with sqlite3_vfs_register(). +** ^Each new VFS becomes the default VFS if the makeDflt flag is set. +** ^The same VFS can be registered multiple times without injury. +** ^To make an existing VFS into the default VFS, register it again +** with the makeDflt flag set. If two different VFSes with the +** same name are registered, the behavior is undefined. If a +** VFS is registered with a name that is NULL or an empty string, +** then the behavior is undefined. +** +** ^Unregister a VFS with the sqlite3_vfs_unregister() interface. +** ^(If the default VFS is unregistered, another VFS is chosen as +** the default. The choice for the new VFS is arbitrary.)^ +*/ +SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName); +SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); +SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); + +/* +** CAPI3REF: Mutexes +** +** The SQLite core uses these routines for thread +** synchronization. Though they are intended for internal +** use by SQLite, code that links against SQLite is +** permitted to use any of these routines. +** +** The SQLite source code contains multiple implementations +** of these mutex routines. An appropriate implementation +** is selected automatically at compile-time. The following +** implementations are available in the SQLite core: +** +** <ul> +** <li> SQLITE_MUTEX_PTHREADS +** <li> SQLITE_MUTEX_W32 +** <li> SQLITE_MUTEX_NOOP +** </ul> +** +** The SQLITE_MUTEX_NOOP implementation is a set of routines +** that does no real locking and is appropriate for use in +** a single-threaded application. The SQLITE_MUTEX_PTHREADS and +** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix +** and Windows. +** +** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor +** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex +** implementation is included with the library. In this case the +** application must supply a custom mutex implementation using the +** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function +** before calling sqlite3_initialize() or any other public sqlite3_ +** function that calls sqlite3_initialize(). +** +** ^The sqlite3_mutex_alloc() routine allocates a new +** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() +** routine returns NULL if it is unable to allocate the requested +** mutex. The argument to sqlite3_mutex_alloc() must one of these +** integer constants: +** +** <ul> +** <li> SQLITE_MUTEX_FAST +** <li> SQLITE_MUTEX_RECURSIVE +** <li> SQLITE_MUTEX_STATIC_MAIN +** <li> SQLITE_MUTEX_STATIC_MEM +** <li> SQLITE_MUTEX_STATIC_OPEN +** <li> SQLITE_MUTEX_STATIC_PRNG +** <li> SQLITE_MUTEX_STATIC_LRU +** <li> SQLITE_MUTEX_STATIC_PMEM +** <li> SQLITE_MUTEX_STATIC_APP1 +** <li> SQLITE_MUTEX_STATIC_APP2 +** <li> SQLITE_MUTEX_STATIC_APP3 +** <li> SQLITE_MUTEX_STATIC_VFS1 +** <li> SQLITE_MUTEX_STATIC_VFS2 +** <li> SQLITE_MUTEX_STATIC_VFS3 +** </ul> +** +** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) +** cause sqlite3_mutex_alloc() to create +** a new mutex. ^The new mutex is recursive when SQLITE_MUTEX_RECURSIVE +** is used but not necessarily so when SQLITE_MUTEX_FAST is used. +** The mutex implementation does not need to make a distinction +** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does +** not want to. SQLite will only request a recursive mutex in +** cases where it really needs one. If a faster non-recursive mutex +** implementation is available on the host platform, the mutex subsystem +** might return such a mutex in response to SQLITE_MUTEX_FAST. +** +** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other +** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return +** a pointer to a static preexisting mutex. ^Nine static mutexes are +** used by the current version of SQLite. Future versions of SQLite +** may add additional static mutexes. Static mutexes are for internal +** use by SQLite only. Applications that use SQLite mutexes should +** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or +** SQLITE_MUTEX_RECURSIVE. +** +** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST +** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() +** returns a different mutex on every call. ^For the static +** mutex types, the same mutex is returned on every call that has +** the same type number. +** +** ^The sqlite3_mutex_free() routine deallocates a previously +** allocated dynamic mutex. Attempting to deallocate a static +** mutex results in undefined behavior. +** +** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt +** to enter a mutex. ^If another thread is already within the mutex, +** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return +** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK] +** upon successful entry. ^(Mutexes created using +** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread. +** In such cases, the +** mutex must be exited an equal number of times before another thread +** can enter.)^ If the same thread tries to enter any mutex other +** than an SQLITE_MUTEX_RECURSIVE more than once, the behavior is undefined. +** +** ^(Some systems (for example, Windows 95) do not support the operation +** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() +** will always return SQLITE_BUSY. The SQLite core only ever uses +** sqlite3_mutex_try() as an optimization so this is acceptable +** behavior.)^ +** +** ^The sqlite3_mutex_leave() routine exits a mutex that was +** previously entered by the same thread. The behavior +** is undefined if the mutex is not currently entered by the +** calling thread or is not currently allocated. +** +** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), +** sqlite3_mutex_leave(), or sqlite3_mutex_free() is a NULL pointer, +** then any of the four routines behaves as a no-op. +** +** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. +*/ +SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int); +SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); + +/* +** CAPI3REF: Mutex Methods Object +** +** An instance of this structure defines the low-level routines +** used to allocate and use mutexes. +** +** Usually, the default mutex implementations provided by SQLite are +** sufficient, however the application has the option of substituting a custom +** implementation for specialized deployments or systems for which SQLite +** does not provide a suitable implementation. In this case, the application +** creates and populates an instance of this structure to pass +** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option. +** Additionally, an instance of this structure can be used as an +** output variable when querying the system for the current mutex +** implementation, using the [SQLITE_CONFIG_GETMUTEX] option. +** +** ^The xMutexInit method defined by this structure is invoked as +** part of system initialization by the sqlite3_initialize() function. +** ^The xMutexInit routine is called by SQLite exactly once for each +** effective call to [sqlite3_initialize()]. +** +** ^The xMutexEnd method defined by this structure is invoked as +** part of system shutdown by the sqlite3_shutdown() function. The +** implementation of this method is expected to release all outstanding +** resources obtained by the mutex methods implementation, especially +** those obtained by the xMutexInit method. ^The xMutexEnd() +** interface is invoked exactly once for each call to [sqlite3_shutdown()]. +** +** ^(The remaining seven methods defined by this structure (xMutexAlloc, +** xMutexFree, xMutexEnter, xMutexTry, xMutexLeave, xMutexHeld and +** xMutexNotheld) implement the following interfaces (respectively): +** +** <ul> +** <li> [sqlite3_mutex_alloc()] </li> +** <li> [sqlite3_mutex_free()] </li> +** <li> [sqlite3_mutex_enter()] </li> +** <li> [sqlite3_mutex_try()] </li> +** <li> [sqlite3_mutex_leave()] </li> +** <li> [sqlite3_mutex_held()] </li> +** <li> [sqlite3_mutex_notheld()] </li> +** </ul>)^ +** +** The only difference is that the public sqlite3_XXX functions enumerated +** above silently ignore any invocations that pass a NULL pointer instead +** of a valid mutex handle. The implementations of the methods defined +** by this structure are not required to handle this case. The results +** of passing a NULL pointer instead of a valid mutex handle are undefined +** (i.e. it is acceptable to provide an implementation that segfaults if +** it is passed a NULL pointer). +** +** The xMutexInit() method must be threadsafe. It must be harmless to +** invoke xMutexInit() multiple times within the same process and without +** intervening calls to xMutexEnd(). Second and subsequent calls to +** xMutexInit() must be no-ops. +** +** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] +** and its associates). Similarly, xMutexAlloc() must not use SQLite memory +** allocation for a static mutex. ^However xMutexAlloc() may use SQLite +** memory allocation for a fast or recursive mutex. +** +** ^SQLite will invoke the xMutexEnd() method when [sqlite3_shutdown()] is +** called, but only if the prior call to xMutexInit returned SQLITE_OK. +** If xMutexInit fails in any way, it is expected to clean up after itself +** prior to returning. +*/ +typedef struct sqlite3_mutex_methods sqlite3_mutex_methods; +struct sqlite3_mutex_methods { + int (*xMutexInit)(void); + int (*xMutexEnd)(void); + sqlite3_mutex *(*xMutexAlloc)(int); + void (*xMutexFree)(sqlite3_mutex *); + void (*xMutexEnter)(sqlite3_mutex *); + int (*xMutexTry)(sqlite3_mutex *); + void (*xMutexLeave)(sqlite3_mutex *); + int (*xMutexHeld)(sqlite3_mutex *); + int (*xMutexNotheld)(sqlite3_mutex *); +}; + +/* +** CAPI3REF: Mutex Verification Routines +** +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines +** are intended for use inside assert() statements. The SQLite core +** never uses these routines except inside an assert() and applications +** are advised to follow the lead of the core. The SQLite core only +** provides implementations for these routines when it is compiled +** with the SQLITE_DEBUG flag. External mutex implementations +** are only required to provide these routines if SQLITE_DEBUG is +** defined and if NDEBUG is not defined. +** +** These routines should return true if the mutex in their argument +** is held or not held, respectively, by the calling thread. +** +** The implementation is not required to provide versions of these +** routines that actually work. If the implementation does not provide working +** versions of these routines, it should at least provide stubs that always +** return true so that one does not get spurious assertion failures. +** +** If the argument to sqlite3_mutex_held() is a NULL pointer then +** the routine should return 1. This seems counter-intuitive since +** clearly the mutex cannot be held if it does not exist. But +** the reason the mutex does not exist is because the build is not +** using mutexes. And we do not want the assert() containing the +** call to sqlite3_mutex_held() to fail, so a non-zero return is +** the appropriate thing to do. The sqlite3_mutex_notheld() +** interface should also return 1 when given a NULL pointer. +*/ +#ifndef NDEBUG +SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); +#endif + +/* +** CAPI3REF: Mutex Types +** +** The [sqlite3_mutex_alloc()] interface takes a single argument +** which is one of these integer constants. +** +** The set of static mutexes may change from one SQLite release to the +** next. Applications that override the built-in mutex logic must be +** prepared to accommodate additional static mutexes. +*/ +#define SQLITE_MUTEX_FAST 0 +#define SQLITE_MUTEX_RECURSIVE 1 +#define SQLITE_MUTEX_STATIC_MAIN 2 +#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ +#define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ +#define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ +#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_randomness() */ +#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ +#define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ +#define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ +#define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */ +#define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */ +#define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */ +#define SQLITE_MUTEX_STATIC_VFS1 11 /* For use by built-in VFS */ +#define SQLITE_MUTEX_STATIC_VFS2 12 /* For use by extension VFS */ +#define SQLITE_MUTEX_STATIC_VFS3 13 /* For use by application VFS */ + +/* Legacy compatibility: */ +#define SQLITE_MUTEX_STATIC_MASTER 2 + + +/* +** CAPI3REF: Retrieve the mutex for a database connection +** METHOD: sqlite3 +** +** ^This interface returns a pointer the [sqlite3_mutex] object that +** serializes access to the [database connection] given in the argument +** when the [threading mode] is Serialized. +** ^If the [threading mode] is Single-thread or Multi-thread then this +** routine returns a NULL pointer. +*/ +SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*); + +/* +** CAPI3REF: Low-Level Control Of Database Files +** METHOD: sqlite3 +** KEYWORDS: {file control} +** +** ^The [sqlite3_file_control()] interface makes a direct call to the +** xFileControl method for the [sqlite3_io_methods] object associated +** with a particular database identified by the second argument. ^The +** name of the database is "main" for the main database or "temp" for the +** TEMP database, or the name that appears after the AS keyword for +** databases that are added using the [ATTACH] SQL command. +** ^A NULL pointer can be used in place of "main" to refer to the +** main database file. +** ^The third and fourth parameters to this routine +** are passed directly through to the second and third parameters of +** the xFileControl method. ^The return value of the xFileControl +** method becomes the return value of this routine. +** +** A few opcodes for [sqlite3_file_control()] are handled directly +** by the SQLite core and never invoke the +** sqlite3_io_methods.xFileControl method. +** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes +** a pointer to the underlying [sqlite3_file] object to be written into +** the space pointed to by the 4th parameter. The +** [SQLITE_FCNTL_JOURNAL_POINTER] works similarly except that it returns +** the [sqlite3_file] object associated with the journal file instead of +** the main database. The [SQLITE_FCNTL_VFS_POINTER] opcode returns +** a pointer to the underlying [sqlite3_vfs] object for the file. +** The [SQLITE_FCNTL_DATA_VERSION] returns the data version counter +** from the pager. +** +** ^If the second parameter (zDbName) does not match the name of any +** open database file, then SQLITE_ERROR is returned. ^This error +** code is not remembered and will not be recalled by [sqlite3_errcode()] +** or [sqlite3_errmsg()]. The underlying xFileControl method might +** also return SQLITE_ERROR. There is no way to distinguish between +** an incorrect zDbName and an SQLITE_ERROR return from the underlying +** xFileControl method. +** +** See also: [file control opcodes] +*/ +SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); + +/* +** CAPI3REF: Testing Interface +** +** ^The sqlite3_test_control() interface is used to read out internal +** state of SQLite and to inject faults into SQLite for testing +** purposes. ^The first parameter is an operation code that determines +** the number, meaning, and operation of all subsequent parameters. +** +** This interface is not for use by applications. It exists solely +** for verifying the correct operation of the SQLite library. Depending +** on how the SQLite library is compiled, this interface might not exist. +** +** The details of the operation codes, their meanings, the parameters +** they take, and what they do are all subject to change without notice. +** Unlike most of the SQLite API, this function is not guaranteed to +** operate consistently from one release to the next. +*/ +SQLITE_API int sqlite3_test_control(int op, ...); + +/* +** CAPI3REF: Testing Interface Operation Codes +** +** These constants are the valid operation code parameters used +** as the first argument to [sqlite3_test_control()]. +** +** These parameters and their meanings are subject to change +** without notice. These values are for testing purposes only. +** Applications should not use any of these parameters or the +** [sqlite3_test_control()] interface. +*/ +#define SQLITE_TESTCTRL_FIRST 5 +#define SQLITE_TESTCTRL_PRNG_SAVE 5 +#define SQLITE_TESTCTRL_PRNG_RESTORE 6 +#define SQLITE_TESTCTRL_PRNG_RESET 7 /* NOT USED */ +#define SQLITE_TESTCTRL_BITVEC_TEST 8 +#define SQLITE_TESTCTRL_FAULT_INSTALL 9 +#define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 +#define SQLITE_TESTCTRL_PENDING_BYTE 11 +#define SQLITE_TESTCTRL_ASSERT 12 +#define SQLITE_TESTCTRL_ALWAYS 13 +#define SQLITE_TESTCTRL_RESERVE 14 /* NOT USED */ +#define SQLITE_TESTCTRL_OPTIMIZATIONS 15 +#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */ +#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */ +#define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17 +#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 +#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ +#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19 +#define SQLITE_TESTCTRL_NEVER_CORRUPT 20 +#define SQLITE_TESTCTRL_VDBE_COVERAGE 21 +#define SQLITE_TESTCTRL_BYTEORDER 22 +#define SQLITE_TESTCTRL_ISINIT 23 +#define SQLITE_TESTCTRL_SORTER_MMAP 24 +#define SQLITE_TESTCTRL_IMPOSTER 25 +#define SQLITE_TESTCTRL_PARSER_COVERAGE 26 +#define SQLITE_TESTCTRL_RESULT_INTREAL 27 +#define SQLITE_TESTCTRL_PRNG_SEED 28 +#define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29 +#define SQLITE_TESTCTRL_SEEK_COUNT 30 +#define SQLITE_TESTCTRL_TRACEFLAGS 31 +#define SQLITE_TESTCTRL_TUNE 32 +#define SQLITE_TESTCTRL_LOGEST 33 +#define SQLITE_TESTCTRL_USELONGDOUBLE 34 +#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */ + +/* +** CAPI3REF: SQL Keyword Checking +** +** These routines provide access to the set of SQL language keywords +** recognized by SQLite. Applications can uses these routines to determine +** whether or not a specific identifier needs to be escaped (for example, +** by enclosing in double-quotes) so as not to confuse the parser. +** +** The sqlite3_keyword_count() interface returns the number of distinct +** keywords understood by SQLite. +** +** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and +** makes *Z point to that keyword expressed as UTF8 and writes the number +** of bytes in the keyword into *L. The string that *Z points to is not +** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns +** SQLITE_OK if N is within bounds and SQLITE_ERROR if not. If either Z +** or L are NULL or invalid pointers then calls to +** sqlite3_keyword_name(N,Z,L) result in undefined behavior. +** +** The sqlite3_keyword_check(Z,L) interface checks to see whether or not +** the L-byte UTF8 identifier that Z points to is a keyword, returning non-zero +** if it is and zero if not. +** +** The parser used by SQLite is forgiving. It is often possible to use +** a keyword as an identifier as long as such use does not result in a +** parsing ambiguity. For example, the statement +** "CREATE TABLE BEGIN(REPLACE,PRAGMA,END);" is accepted by SQLite, and +** creates a new table named "BEGIN" with three columns named +** "REPLACE", "PRAGMA", and "END". Nevertheless, best practice is to avoid +** using keywords as identifiers. Common techniques used to avoid keyword +** name collisions include: +** <ul> +** <li> Put all identifier names inside double-quotes. This is the official +** SQL way to escape identifier names. +** <li> Put identifier names inside &#91;...&#93;. This is not standard SQL, +** but it is what SQL Server does and so lots of programmers use this +** technique. +** <li> Begin every identifier with the letter "Z" as no SQL keywords start +** with "Z". +** <li> Include a digit somewhere in every identifier name. +** </ul> +** +** Note that the number of keywords understood by SQLite can depend on +** compile-time options. For example, "VACUUM" is not a keyword if +** SQLite is compiled with the [-DSQLITE_OMIT_VACUUM] option. Also, +** new keywords may be added to future releases of SQLite. +*/ +SQLITE_API int sqlite3_keyword_count(void); +SQLITE_API int sqlite3_keyword_name(int,const char**,int*); +SQLITE_API int sqlite3_keyword_check(const char*,int); + +/* +** CAPI3REF: Dynamic String Object +** KEYWORDS: {dynamic string} +** +** An instance of the sqlite3_str object contains a dynamically-sized +** string under construction. +** +** The lifecycle of an sqlite3_str object is as follows: +** <ol> +** <li> ^The sqlite3_str object is created using [sqlite3_str_new()]. +** <li> ^Text is appended to the sqlite3_str object using various +** methods, such as [sqlite3_str_appendf()]. +** <li> ^The sqlite3_str object is destroyed and the string it created +** is returned using the [sqlite3_str_finish()] interface. +** </ol> +*/ +typedef struct sqlite3_str sqlite3_str; + +/* +** CAPI3REF: Create A New Dynamic String Object +** CONSTRUCTOR: sqlite3_str +** +** ^The [sqlite3_str_new(D)] interface allocates and initializes +** a new [sqlite3_str] object. To avoid memory leaks, the object returned by +** [sqlite3_str_new()] must be freed by a subsequent call to +** [sqlite3_str_finish(X)]. +** +** ^The [sqlite3_str_new(D)] interface always returns a pointer to a +** valid [sqlite3_str] object, though in the event of an out-of-memory +** error the returned object might be a special singleton that will +** silently reject new text, always return SQLITE_NOMEM from +** [sqlite3_str_errcode()], always return 0 for +** [sqlite3_str_length()], and always return NULL from +** [sqlite3_str_finish(X)]. It is always safe to use the value +** returned by [sqlite3_str_new(D)] as the sqlite3_str parameter +** to any of the other [sqlite3_str] methods. +** +** The D parameter to [sqlite3_str_new(D)] may be NULL. If the +** D parameter in [sqlite3_str_new(D)] is not NULL, then the maximum +** length of the string contained in the [sqlite3_str] object will be +** the value set for [sqlite3_limit](D,[SQLITE_LIMIT_LENGTH]) instead +** of [SQLITE_MAX_LENGTH]. +*/ +SQLITE_API sqlite3_str *sqlite3_str_new(sqlite3*); + +/* +** CAPI3REF: Finalize A Dynamic String +** DESTRUCTOR: sqlite3_str +** +** ^The [sqlite3_str_finish(X)] interface destroys the sqlite3_str object X +** and returns a pointer to a memory buffer obtained from [sqlite3_malloc64()] +** that contains the constructed string. The calling application should +** pass the returned value to [sqlite3_free()] to avoid a memory leak. +** ^The [sqlite3_str_finish(X)] interface may return a NULL pointer if any +** errors were encountered during construction of the string. ^The +** [sqlite3_str_finish(X)] interface will also return a NULL pointer if the +** string in [sqlite3_str] object X is zero bytes long. +*/ +SQLITE_API char *sqlite3_str_finish(sqlite3_str*); + +/* +** CAPI3REF: Add Content To A Dynamic String +** METHOD: sqlite3_str +** +** These interfaces add content to an sqlite3_str object previously obtained +** from [sqlite3_str_new()]. +** +** ^The [sqlite3_str_appendf(X,F,...)] and +** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf] +** functionality of SQLite to append formatted text onto the end of +** [sqlite3_str] object X. +** +** ^The [sqlite3_str_append(X,S,N)] method appends exactly N bytes from string S +** onto the end of the [sqlite3_str] object X. N must be non-negative. +** S must contain at least N non-zero bytes of content. To append a +** zero-terminated string in its entirety, use the [sqlite3_str_appendall()] +** method instead. +** +** ^The [sqlite3_str_appendall(X,S)] method appends the complete content of +** zero-terminated string S onto the end of [sqlite3_str] object X. +** +** ^The [sqlite3_str_appendchar(X,N,C)] method appends N copies of the +** single-byte character C onto the end of [sqlite3_str] object X. +** ^This method can be used, for example, to add whitespace indentation. +** +** ^The [sqlite3_str_reset(X)] method resets the string under construction +** inside [sqlite3_str] object X back to zero bytes in length. +** +** These methods do not return a result code. ^If an error occurs, that fact +** is recorded in the [sqlite3_str] object and can be recovered by a +** subsequent call to [sqlite3_str_errcode(X)]. +*/ +SQLITE_API void sqlite3_str_appendf(sqlite3_str*, const char *zFormat, ...); +SQLITE_API void sqlite3_str_vappendf(sqlite3_str*, const char *zFormat, va_list); +SQLITE_API void sqlite3_str_append(sqlite3_str*, const char *zIn, int N); +SQLITE_API void sqlite3_str_appendall(sqlite3_str*, const char *zIn); +SQLITE_API void sqlite3_str_appendchar(sqlite3_str*, int N, char C); +SQLITE_API void sqlite3_str_reset(sqlite3_str*); + +/* +** CAPI3REF: Status Of A Dynamic String +** METHOD: sqlite3_str +** +** These interfaces return the current status of an [sqlite3_str] object. +** +** ^If any prior errors have occurred while constructing the dynamic string +** in sqlite3_str X, then the [sqlite3_str_errcode(X)] method will return +** an appropriate error code. ^The [sqlite3_str_errcode(X)] method returns +** [SQLITE_NOMEM] following any out-of-memory error, or +** [SQLITE_TOOBIG] if the size of the dynamic string exceeds +** [SQLITE_MAX_LENGTH], or [SQLITE_OK] if there have been no errors. +** +** ^The [sqlite3_str_length(X)] method returns the current length, in bytes, +** of the dynamic string under construction in [sqlite3_str] object X. +** ^The length returned by [sqlite3_str_length(X)] does not include the +** zero-termination byte. +** +** ^The [sqlite3_str_value(X)] method returns a pointer to the current +** content of the dynamic string under construction in X. The value +** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X +** and might be freed or altered by any subsequent method on the same +** [sqlite3_str] object. Applications must not used the pointer returned +** [sqlite3_str_value(X)] after any subsequent method call on the same +** object. ^Applications may change the content of the string returned +** by [sqlite3_str_value(X)] as long as they do not write into any bytes +** outside the range of 0 to [sqlite3_str_length(X)] and do not read or +** write any byte after any subsequent sqlite3_str method call. +*/ +SQLITE_API int sqlite3_str_errcode(sqlite3_str*); +SQLITE_API int sqlite3_str_length(sqlite3_str*); +SQLITE_API char *sqlite3_str_value(sqlite3_str*); + +/* +** CAPI3REF: SQLite Runtime Status +** +** ^These interfaces are used to retrieve runtime status information +** about the performance of SQLite, and optionally to reset various +** highwater marks. ^The first argument is an integer code for +** the specific parameter to measure. ^(Recognized integer codes +** are of the form [status parameters | SQLITE_STATUS_...].)^ +** ^The current value of the parameter is returned into *pCurrent. +** ^The highest recorded value is returned in *pHighwater. ^If the +** resetFlag is true, then the highest record value is reset after +** *pHighwater is written. ^(Some parameters do not record the highest +** value. For those parameters +** nothing is written into *pHighwater and the resetFlag is ignored.)^ +** ^(Other parameters record only the highwater mark and not the current +** value. For these latter parameters nothing is written into *pCurrent.)^ +** +** ^The sqlite3_status() and sqlite3_status64() routines return +** SQLITE_OK on success and a non-zero [error code] on failure. +** +** If either the current value or the highwater mark is too large to +** be represented by a 32-bit integer, then the values returned by +** sqlite3_status() are undefined. +** +** See also: [sqlite3_db_status()] +*/ +SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); +SQLITE_API int sqlite3_status64( + int op, + sqlite3_int64 *pCurrent, + sqlite3_int64 *pHighwater, + int resetFlag +); + + +/* +** CAPI3REF: Status Parameters +** KEYWORDS: {status parameters} +** +** These integer constants designate various run-time status parameters +** that can be returned by [sqlite3_status()]. +** +** <dl> +** [[SQLITE_STATUS_MEMORY_USED]] ^(<dt>SQLITE_STATUS_MEMORY_USED</dt> +** <dd>This parameter is the current amount of memory checked out +** using [sqlite3_malloc()], either directly or indirectly. The +** figure includes calls made to [sqlite3_malloc()] by the application +** and internal memory usage by the SQLite library. Auxiliary page-cache +** memory controlled by [SQLITE_CONFIG_PAGECACHE] is not included in +** this parameter. The amount returned is the sum of the allocation +** sizes as reported by the xSize method in [sqlite3_mem_methods].</dd>)^ +** +** [[SQLITE_STATUS_MALLOC_SIZE]] ^(<dt>SQLITE_STATUS_MALLOC_SIZE</dt> +** <dd>This parameter records the largest memory allocation request +** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their +** internal equivalents). Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. +** The value written into the *pCurrent parameter is undefined.</dd>)^ +** +** [[SQLITE_STATUS_MALLOC_COUNT]] ^(<dt>SQLITE_STATUS_MALLOC_COUNT</dt> +** <dd>This parameter records the number of separate memory allocations +** currently checked out.</dd>)^ +** +** [[SQLITE_STATUS_PAGECACHE_USED]] ^(<dt>SQLITE_STATUS_PAGECACHE_USED</dt> +** <dd>This parameter returns the number of pages used out of the +** [pagecache memory allocator] that was configured using +** [SQLITE_CONFIG_PAGECACHE]. The +** value returned is in pages, not in bytes.</dd>)^ +** +** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] +** ^(<dt>SQLITE_STATUS_PAGECACHE_OVERFLOW</dt> +** <dd>This parameter returns the number of bytes of page cache +** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] +** buffer and where forced to overflow to [sqlite3_malloc()]. The +** returned value includes allocations that overflowed because they +** where too large (they were larger than the "sz" parameter to +** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because +** no space was left in the page cache.</dd>)^ +** +** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(<dt>SQLITE_STATUS_PAGECACHE_SIZE</dt> +** <dd>This parameter records the largest memory allocation request +** handed to the [pagecache memory allocator]. Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. +** The value written into the *pCurrent parameter is undefined.</dd>)^ +** +** [[SQLITE_STATUS_SCRATCH_USED]] <dt>SQLITE_STATUS_SCRATCH_USED</dt> +** <dd>No longer used.</dd> +** +** [[SQLITE_STATUS_SCRATCH_OVERFLOW]] ^(<dt>SQLITE_STATUS_SCRATCH_OVERFLOW</dt> +** <dd>No longer used.</dd> +** +** [[SQLITE_STATUS_SCRATCH_SIZE]] <dt>SQLITE_STATUS_SCRATCH_SIZE</dt> +** <dd>No longer used.</dd> +** +** [[SQLITE_STATUS_PARSER_STACK]] ^(<dt>SQLITE_STATUS_PARSER_STACK</dt> +** <dd>The *pHighwater parameter records the deepest parser stack. +** The *pCurrent value is undefined. The *pHighwater value is only +** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].</dd>)^ +** </dl> +** +** New status parameters may be added from time to time. +*/ +#define SQLITE_STATUS_MEMORY_USED 0 +#define SQLITE_STATUS_PAGECACHE_USED 1 +#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2 +#define SQLITE_STATUS_SCRATCH_USED 3 /* NOT USED */ +#define SQLITE_STATUS_SCRATCH_OVERFLOW 4 /* NOT USED */ +#define SQLITE_STATUS_MALLOC_SIZE 5 +#define SQLITE_STATUS_PARSER_STACK 6 +#define SQLITE_STATUS_PAGECACHE_SIZE 7 +#define SQLITE_STATUS_SCRATCH_SIZE 8 /* NOT USED */ +#define SQLITE_STATUS_MALLOC_COUNT 9 + +/* +** CAPI3REF: Database Connection Status +** METHOD: sqlite3 +** +** ^This interface is used to retrieve runtime status information +** about a single [database connection]. ^The first argument is the +** database connection object to be interrogated. ^The second argument +** is an integer constant, taken from the set of +** [SQLITE_DBSTATUS options], that +** determines the parameter to interrogate. The set of +** [SQLITE_DBSTATUS options] is likely +** to grow in future releases of SQLite. +** +** ^The current value of the requested parameter is written into *pCur +** and the highest instantaneous value is written into *pHiwtr. ^If +** the resetFlg is true, then the highest instantaneous value is +** reset back down to the current value. +** +** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a +** non-zero [error code] on failure. +** +** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. +*/ +SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); + +/* +** CAPI3REF: Status Parameters for database connections +** KEYWORDS: {SQLITE_DBSTATUS options} +** +** These constants are the available integer "verbs" that can be passed as +** the second argument to the [sqlite3_db_status()] interface. +** +** New verbs may be added in future releases of SQLite. Existing verbs +** might be discontinued. Applications should check the return code from +** [sqlite3_db_status()] to make sure that the call worked. +** The [sqlite3_db_status()] interface will return a non-zero error code +** if a discontinued or unsupported verb is invoked. +** +** <dl> +** [[SQLITE_DBSTATUS_LOOKASIDE_USED]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_USED</dt> +** <dd>This parameter returns the number of lookaside memory slots currently +** checked out.</dd>)^ +** +** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt> +** <dd>This parameter returns the number of malloc attempts that were +** satisfied using lookaside memory. Only the high-water value is meaningful; +** the current value is always zero.)^ +** +** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]] +** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt> +** <dd>This parameter returns the number malloc attempts that might have +** been satisfied using lookaside memory but failed due to the amount of +** memory requested being larger than the lookaside slot size. +** Only the high-water value is meaningful; +** the current value is always zero.)^ +** +** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]] +** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt> +** <dd>This parameter returns the number malloc attempts that might have +** been satisfied using lookaside memory but failed due to all lookaside +** memory already being in use. +** Only the high-water value is meaningful; +** the current value is always zero.)^ +** +** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt> +** <dd>This parameter returns the approximate number of bytes of heap +** memory used by all pager caches associated with the database connection.)^ +** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. +** +** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] +** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt> +** <dd>This parameter is similar to DBSTATUS_CACHE_USED, except that if a +** pager cache is shared between two or more connections the bytes of heap +** memory used by that pager cache is divided evenly between the attached +** connections.)^ In other words, if none of the pager caches associated +** with the database connection are shared, this request returns the same +** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are +** shared, the value returned by this call will be smaller than that returned +** by DBSTATUS_CACHE_USED. ^The highwater mark associated with +** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0. +** +** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt> +** <dd>This parameter returns the approximate number of bytes of heap +** memory used to store the schema for all databases associated +** with the connection - main, temp, and any [ATTACH]-ed databases.)^ +** ^The full amount of memory used by the schemas is reported, even if the +** schema memory is shared with other database connections due to +** [shared cache mode] being enabled. +** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0. +** +** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt> +** <dd>This parameter returns the approximate number of bytes of heap +** and lookaside memory used by all prepared statements associated with +** the database connection.)^ +** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0. +** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(<dt>SQLITE_DBSTATUS_CACHE_HIT</dt> +** <dd>This parameter returns the number of pager cache hits that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** is always 0. +** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(<dt>SQLITE_DBSTATUS_CACHE_MISS</dt> +** <dd>This parameter returns the number of pager cache misses that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** is always 0. +** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_WRITE]] ^(<dt>SQLITE_DBSTATUS_CACHE_WRITE</dt> +** <dd>This parameter returns the number of dirty cache entries that have +** been written to disk. Specifically, the number of pages written to the +** wal file in wal mode databases, or the number of pages written to the +** database file in rollback mode databases. Any pages written as part of +** transaction rollback or database recovery operations are not included. +** If an IO or other error occurs while writing a page to disk, the effect +** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The +** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. +** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> +** <dd>This parameter returns the number of dirty cache entries that have +** been written to disk in the middle of a transaction due to the page +** cache overflowing. Transactions are more efficient if they are written +** to disk all at once. When pages spill mid-transaction, that introduces +** additional overhead. This parameter can be used help identify +** inefficiencies that can be resolved by increasing the cache size. +** </dd> +** +** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt> +** <dd>This parameter returns zero for the current value if and only if +** all foreign key constraints (deferred or immediate) have been +** resolved.)^ ^The highwater mark is always 0. +** </dd> +** </dl> +*/ +#define SQLITE_DBSTATUS_LOOKASIDE_USED 0 +#define SQLITE_DBSTATUS_CACHE_USED 1 +#define SQLITE_DBSTATUS_SCHEMA_USED 2 +#define SQLITE_DBSTATUS_STMT_USED 3 +#define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 +#define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 +#define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 +#define SQLITE_DBSTATUS_CACHE_HIT 7 +#define SQLITE_DBSTATUS_CACHE_MISS 8 +#define SQLITE_DBSTATUS_CACHE_WRITE 9 +#define SQLITE_DBSTATUS_DEFERRED_FKS 10 +#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 +#define SQLITE_DBSTATUS_CACHE_SPILL 12 +#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */ + + +/* +** CAPI3REF: Prepared Statement Status +** METHOD: sqlite3_stmt +** +** ^(Each prepared statement maintains various +** [SQLITE_STMTSTATUS counters] that measure the number +** of times it has performed specific operations.)^ These counters can +** be used to monitor the performance characteristics of the prepared +** statements. For example, if the number of table steps greatly exceeds +** the number of table searches or result rows, that would tend to indicate +** that the prepared statement is using a full table scan rather than +** an index. +** +** ^(This interface is used to retrieve and reset counter values from +** a [prepared statement]. The first argument is the prepared statement +** object to be interrogated. The second argument +** is an integer code for a specific [SQLITE_STMTSTATUS counter] +** to be interrogated.)^ +** ^The current value of the requested counter is returned. +** ^If the resetFlg is true, then the counter is reset to zero after this +** interface call returns. +** +** See also: [sqlite3_status()] and [sqlite3_db_status()]. +*/ +SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); + +/* +** CAPI3REF: Status Parameters for prepared statements +** KEYWORDS: {SQLITE_STMTSTATUS counter} {SQLITE_STMTSTATUS counters} +** +** These preprocessor macros define integer codes that name counter +** values associated with the [sqlite3_stmt_status()] interface. +** The meanings of the various counters are as follows: +** +** <dl> +** [[SQLITE_STMTSTATUS_FULLSCAN_STEP]] <dt>SQLITE_STMTSTATUS_FULLSCAN_STEP</dt> +** <dd>^This is the number of times that SQLite has stepped forward in +** a table as part of a full table scan. Large numbers for this counter +** may indicate opportunities for performance improvement through +** careful use of indices.</dd> +** +** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt> +** <dd>^This is the number of sort operations that have occurred. +** A non-zero value in this counter may indicate an opportunity to +** improvement performance through careful use of indices.</dd> +** +** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt> +** <dd>^This is the number of rows inserted into transient indices that +** were created automatically in order to help joins run faster. +** A non-zero value in this counter may indicate an opportunity to +** improvement performance by adding permanent indices that do not +** need to be reinitialized each time the statement is run.</dd> +** +** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt> +** <dd>^This is the number of virtual machine operations executed +** by the prepared statement if that number is less than or equal +** to 2147483647. The number of virtual machine operations can be +** used as a proxy for the total work done by the prepared statement. +** If the number of virtual machine operations exceeds 2147483647 +** then the value returned by this statement status code is undefined. +** +** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt> +** <dd>^This is the number of times that the prepare statement has been +** automatically regenerated due to schema changes or changes to +** [bound parameters] that might affect the query plan. +** +** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt> +** <dd>^This is the number of times that the prepared statement has +** been run. A single "run" for the purposes of this counter is one +** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()]. +** The counter is incremented on the first [sqlite3_step()] call of each +** cycle. +** +** [[SQLITE_STMTSTATUS_FILTER_MISS]] +** [[SQLITE_STMTSTATUS_FILTER HIT]] +** <dt>SQLITE_STMTSTATUS_FILTER_HIT<br> +** SQLITE_STMTSTATUS_FILTER_MISS</dt> +** <dd>^SQLITE_STMTSTATUS_FILTER_HIT is the number of times that a join +** step was bypassed because a Bloom filter returned not-found. The +** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of +** times that the Bloom filter returned a find, and thus the join step +** had to be processed as normal. +** +** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt> +** <dd>^This is the approximate number of bytes of heap memory +** used to store the prepared statement. ^This value is not actually +** a counter, and so the resetFlg parameter to sqlite3_stmt_status() +** is ignored when the opcode is SQLITE_STMTSTATUS_MEMUSED. +** </dd> +** </dl> +*/ +#define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 +#define SQLITE_STMTSTATUS_SORT 2 +#define SQLITE_STMTSTATUS_AUTOINDEX 3 +#define SQLITE_STMTSTATUS_VM_STEP 4 +#define SQLITE_STMTSTATUS_REPREPARE 5 +#define SQLITE_STMTSTATUS_RUN 6 +#define SQLITE_STMTSTATUS_FILTER_MISS 7 +#define SQLITE_STMTSTATUS_FILTER_HIT 8 +#define SQLITE_STMTSTATUS_MEMUSED 99 + +/* +** CAPI3REF: Custom Page Cache Object +** +** The sqlite3_pcache type is opaque. It is implemented by +** the pluggable module. The SQLite core has no knowledge of +** its size or internal structure and never deals with the +** sqlite3_pcache object except by holding and passing pointers +** to the object. +** +** See [sqlite3_pcache_methods2] for additional information. +*/ +typedef struct sqlite3_pcache sqlite3_pcache; + +/* +** CAPI3REF: Custom Page Cache Object +** +** The sqlite3_pcache_page object represents a single page in the +** page cache. The page cache will allocate instances of this +** object. Various methods of the page cache use pointers to instances +** of this object as parameters or as their return value. +** +** See [sqlite3_pcache_methods2] for additional information. +*/ +typedef struct sqlite3_pcache_page sqlite3_pcache_page; +struct sqlite3_pcache_page { + void *pBuf; /* The content of the page */ + void *pExtra; /* Extra information associated with the page */ +}; + +/* +** CAPI3REF: Application Defined Page Cache. +** KEYWORDS: {page cache} +** +** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can +** register an alternative page cache implementation by passing in an +** instance of the sqlite3_pcache_methods2 structure.)^ +** In many applications, most of the heap memory allocated by +** SQLite is used for the page cache. +** By implementing a +** custom page cache using this API, an application can better control +** the amount of memory consumed by SQLite, the way in which +** that memory is allocated and released, and the policies used to +** determine exactly which parts of a database file are cached and for +** how long. +** +** The alternative page cache mechanism is an +** extreme measure that is only needed by the most demanding applications. +** The built-in page cache is recommended for most uses. +** +** ^(The contents of the sqlite3_pcache_methods2 structure are copied to an +** internal buffer by SQLite within the call to [sqlite3_config]. Hence +** the application may discard the parameter after the call to +** [sqlite3_config()] returns.)^ +** +** [[the xInit() page cache method]] +** ^(The xInit() method is called once for each effective +** call to [sqlite3_initialize()])^ +** (usually only once during the lifetime of the process). ^(The xInit() +** method is passed a copy of the sqlite3_pcache_methods2.pArg value.)^ +** The intent of the xInit() method is to set up global data structures +** required by the custom page cache implementation. +** ^(If the xInit() method is NULL, then the +** built-in default page cache is used instead of the application defined +** page cache.)^ +** +** [[the xShutdown() page cache method]] +** ^The xShutdown() method is called by [sqlite3_shutdown()]. +** It can be used to clean up +** any outstanding resources before process shutdown, if required. +** ^The xShutdown() method may be NULL. +** +** ^SQLite automatically serializes calls to the xInit method, +** so the xInit method need not be threadsafe. ^The +** xShutdown method is only called from [sqlite3_shutdown()] so it does +** not need to be threadsafe either. All other methods must be threadsafe +** in multithreaded applications. +** +** ^SQLite will never invoke xInit() more than once without an intervening +** call to xShutdown(). +** +** [[the xCreate() page cache methods]] +** ^SQLite invokes the xCreate() method to construct a new cache instance. +** SQLite will typically create one cache instance for each open database file, +** though this is not guaranteed. ^The +** first parameter, szPage, is the size in bytes of the pages that must +** be allocated by the cache. ^szPage will always a power of two. ^The +** second parameter szExtra is a number of bytes of extra storage +** associated with each page cache entry. ^The szExtra parameter will +** a number less than 250. SQLite will use the +** extra szExtra bytes on each page to store metadata about the underlying +** database page on disk. The value passed into szExtra depends +** on the SQLite version, the target platform, and how SQLite was compiled. +** ^The third argument to xCreate(), bPurgeable, is true if the cache being +** created will be used to cache database pages of a file stored on disk, or +** false if it is used for an in-memory database. The cache implementation +** does not have to do anything special based with the value of bPurgeable; +** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will +** never invoke xUnpin() except to deliberately delete a page. +** ^In other words, calls to xUnpin() on a cache with bPurgeable set to +** false will always have the "discard" flag set to true. +** ^Hence, a cache created with bPurgeable false will +** never contain any unpinned pages. +** +** [[the xCachesize() page cache method]] +** ^(The xCachesize() method may be called at any time by SQLite to set the +** suggested maximum cache-size (number of pages stored by) the cache +** instance passed as the first argument. This is the value configured using +** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable +** parameter, the implementation is not required to do anything with this +** value; it is advisory only. +** +** [[the xPagecount() page cache methods]] +** The xPagecount() method must return the number of pages currently +** stored in the cache, both pinned and unpinned. +** +** [[the xFetch() page cache methods]] +** The xFetch() method locates a page in the cache and returns a pointer to +** an sqlite3_pcache_page object associated with that page, or a NULL pointer. +** The pBuf element of the returned sqlite3_pcache_page object will be a +** pointer to a buffer of szPage bytes used to store the content of a +** single database page. The pExtra element of sqlite3_pcache_page will be +** a pointer to the szExtra bytes of extra storage that SQLite has requested +** for each entry in the page cache. +** +** The page to be fetched is determined by the key. ^The minimum key value +** is 1. After it has been retrieved using xFetch, the page is considered +** to be "pinned". +** +** If the requested page is already in the page cache, then the page cache +** implementation must return a pointer to the page buffer with its content +** intact. If the requested page is not already in the cache, then the +** cache implementation should use the value of the createFlag +** parameter to help it determined what action to take: +** +** <table border=1 width=85% align=center> +** <tr><th> createFlag <th> Behavior when page is not already in cache +** <tr><td> 0 <td> Do not allocate a new page. Return NULL. +** <tr><td> 1 <td> Allocate a new page if it easy and convenient to do so. +** Otherwise return NULL. +** <tr><td> 2 <td> Make every effort to allocate a new page. Only return +** NULL if allocating a new page is effectively impossible. +** </table> +** +** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1. SQLite +** will only use a createFlag of 2 after a prior call with a createFlag of 1 +** failed.)^ In between the xFetch() calls, SQLite may +** attempt to unpin one or more cache pages by spilling the content of +** pinned pages to disk and synching the operating system disk cache. +** +** [[the xUnpin() page cache method]] +** ^xUnpin() is called by SQLite with a pointer to a currently pinned page +** as its second argument. If the third parameter, discard, is non-zero, +** then the page must be evicted from the cache. +** ^If the discard parameter is +** zero, then the page may be discarded or retained at the discretion of +** page cache implementation. ^The page cache implementation +** may choose to evict unpinned pages at any time. +** +** The cache must not perform any reference counting. A single +** call to xUnpin() unpins the page regardless of the number of prior calls +** to xFetch(). +** +** [[the xRekey() page cache methods]] +** The xRekey() method is used to change the key value associated with the +** page passed as the second argument. If the cache +** previously contains an entry associated with newKey, it must be +** discarded. ^Any prior cache entry associated with newKey is guaranteed not +** to be pinned. +** +** When SQLite calls the xTruncate() method, the cache must discard all +** existing cache entries with page numbers (keys) greater than or equal +** to the value of the iLimit parameter passed to xTruncate(). If any +** of these pages are pinned, they are implicitly unpinned, meaning that +** they can be safely discarded. +** +** [[the xDestroy() page cache method]] +** ^The xDestroy() method is used to delete a cache allocated by xCreate(). +** All resources associated with the specified cache should be freed. ^After +** calling the xDestroy() method, SQLite considers the [sqlite3_pcache*] +** handle invalid, and will not use it with any other sqlite3_pcache_methods2 +** functions. +** +** [[the xShrink() page cache method]] +** ^SQLite invokes the xShrink() method when it wants the page cache to +** free up as much of heap memory as possible. The page cache implementation +** is not obligated to free any memory, but well-behaved implementations should +** do their best. +*/ +typedef struct sqlite3_pcache_methods2 sqlite3_pcache_methods2; +struct sqlite3_pcache_methods2 { + int iVersion; + void *pArg; + int (*xInit)(void*); + void (*xShutdown)(void*); + sqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable); + void (*xCachesize)(sqlite3_pcache*, int nCachesize); + int (*xPagecount)(sqlite3_pcache*); + sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); + void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard); + void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, + unsigned oldKey, unsigned newKey); + void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); + void (*xDestroy)(sqlite3_pcache*); + void (*xShrink)(sqlite3_pcache*); +}; + +/* +** This is the obsolete pcache_methods object that has now been replaced +** by sqlite3_pcache_methods2. This object is not used by SQLite. It is +** retained in the header file for backwards compatibility only. +*/ +typedef struct sqlite3_pcache_methods sqlite3_pcache_methods; +struct sqlite3_pcache_methods { + void *pArg; + int (*xInit)(void*); + void (*xShutdown)(void*); + sqlite3_pcache *(*xCreate)(int szPage, int bPurgeable); + void (*xCachesize)(sqlite3_pcache*, int nCachesize); + int (*xPagecount)(sqlite3_pcache*); + void *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); + void (*xUnpin)(sqlite3_pcache*, void*, int discard); + void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey); + void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); + void (*xDestroy)(sqlite3_pcache*); +}; + + +/* +** CAPI3REF: Online Backup Object +** +** The sqlite3_backup object records state information about an ongoing +** online backup operation. ^The sqlite3_backup object is created by +** a call to [sqlite3_backup_init()] and is destroyed by a call to +** [sqlite3_backup_finish()]. +** +** See Also: [Using the SQLite Online Backup API] +*/ +typedef struct sqlite3_backup sqlite3_backup; + +/* +** CAPI3REF: Online Backup API. +** +** The backup API copies the content of one database into another. +** It is useful either for creating backups of databases or +** for copying in-memory databases to or from persistent files. +** +** See Also: [Using the SQLite Online Backup API] +** +** ^SQLite holds a write transaction open on the destination database file +** for the duration of the backup operation. +** ^The source database is read-locked only while it is being read; +** it is not locked continuously for the entire backup operation. +** ^Thus, the backup may be performed on a live source database without +** preventing other database connections from +** reading or writing to the source database while the backup is underway. +** +** ^(To perform a backup operation: +** <ol> +** <li><b>sqlite3_backup_init()</b> is called once to initialize the +** backup, +** <li><b>sqlite3_backup_step()</b> is called one or more times to transfer +** the data between the two databases, and finally +** <li><b>sqlite3_backup_finish()</b> is called to release all resources +** associated with the backup operation. +** </ol>)^ +** There should be exactly one call to sqlite3_backup_finish() for each +** successful call to sqlite3_backup_init(). +** +** [[sqlite3_backup_init()]] <b>sqlite3_backup_init()</b> +** +** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the +** [database connection] associated with the destination database +** and the database name, respectively. +** ^The database name is "main" for the main database, "temp" for the +** temporary database, or the name specified after the AS keyword in +** an [ATTACH] statement for an attached database. +** ^The S and M arguments passed to +** sqlite3_backup_init(D,N,S,M) identify the [database connection] +** and database name of the source database, respectively. +** ^The source and destination [database connections] (parameters S and D) +** must be different or else sqlite3_backup_init(D,N,S,M) will fail with +** an error. +** +** ^A call to sqlite3_backup_init() will fail, returning NULL, if +** there is already a read or read-write transaction open on the +** destination database. +** +** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is +** returned and an error code and error message are stored in the +** destination [database connection] D. +** ^The error code and message for the failed call to sqlite3_backup_init() +** can be retrieved using the [sqlite3_errcode()], [sqlite3_errmsg()], and/or +** [sqlite3_errmsg16()] functions. +** ^A successful call to sqlite3_backup_init() returns a pointer to an +** [sqlite3_backup] object. +** ^The [sqlite3_backup] object may be used with the sqlite3_backup_step() and +** sqlite3_backup_finish() functions to perform the specified backup +** operation. +** +** [[sqlite3_backup_step()]] <b>sqlite3_backup_step()</b> +** +** ^Function sqlite3_backup_step(B,N) will copy up to N pages between +** the source and destination databases specified by [sqlite3_backup] object B. +** ^If N is negative, all remaining source pages are copied. +** ^If sqlite3_backup_step(B,N) successfully copies N pages and there +** are still more pages to be copied, then the function returns [SQLITE_OK]. +** ^If sqlite3_backup_step(B,N) successfully finishes copying all pages +** from source to destination, then it returns [SQLITE_DONE]. +** ^If an error occurs while running sqlite3_backup_step(B,N), +** then an [error code] is returned. ^As well as [SQLITE_OK] and +** [SQLITE_DONE], a call to sqlite3_backup_step() may return [SQLITE_READONLY], +** [SQLITE_NOMEM], [SQLITE_BUSY], [SQLITE_LOCKED], or an +** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX] extended error code. +** +** ^(The sqlite3_backup_step() might return [SQLITE_READONLY] if +** <ol> +** <li> the destination database was opened read-only, or +** <li> the destination database is using write-ahead-log journaling +** and the destination and source page sizes differ, or +** <li> the destination database is an in-memory database and the +** destination and source page sizes differ. +** </ol>)^ +** +** ^If sqlite3_backup_step() cannot obtain a required file-system lock, then +** the [sqlite3_busy_handler | busy-handler function] +** is invoked (if one is specified). ^If the +** busy-handler returns non-zero before the lock is available, then +** [SQLITE_BUSY] is returned to the caller. ^In this case the call to +** sqlite3_backup_step() can be retried later. ^If the source +** [database connection] +** is being used to write to the source database when sqlite3_backup_step() +** is called, then [SQLITE_LOCKED] is returned immediately. ^Again, in this +** case the call to sqlite3_backup_step() can be retried later on. ^(If +** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or +** [SQLITE_READONLY] is returned, then +** there is no point in retrying the call to sqlite3_backup_step(). These +** errors are considered fatal.)^ The application must accept +** that the backup operation has failed and pass the backup operation handle +** to the sqlite3_backup_finish() to release associated resources. +** +** ^The first call to sqlite3_backup_step() obtains an exclusive lock +** on the destination file. ^The exclusive lock is not released until either +** sqlite3_backup_finish() is called or the backup operation is complete +** and sqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to +** sqlite3_backup_step() obtains a [shared lock] on the source database that +** lasts for the duration of the sqlite3_backup_step() call. +** ^Because the source database is not locked between calls to +** sqlite3_backup_step(), the source database may be modified mid-way +** through the backup process. ^If the source database is modified by an +** external process or via a database connection other than the one being +** used by the backup operation, then the backup will be automatically +** restarted by the next call to sqlite3_backup_step(). ^If the source +** database is modified by the using the same database connection as is used +** by the backup operation, then the backup database is automatically +** updated at the same time. +** +** [[sqlite3_backup_finish()]] <b>sqlite3_backup_finish()</b> +** +** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the +** application wishes to abandon the backup operation, the application +** should destroy the [sqlite3_backup] by passing it to sqlite3_backup_finish(). +** ^The sqlite3_backup_finish() interfaces releases all +** resources associated with the [sqlite3_backup] object. +** ^If sqlite3_backup_step() has not yet returned [SQLITE_DONE], then any +** active write-transaction on the destination database is rolled back. +** The [sqlite3_backup] object is invalid +** and may not be used following a call to sqlite3_backup_finish(). +** +** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no +** sqlite3_backup_step() errors occurred, regardless or whether or not +** sqlite3_backup_step() completed. +** ^If an out-of-memory condition or IO error occurred during any prior +** sqlite3_backup_step() call on the same [sqlite3_backup] object, then +** sqlite3_backup_finish() returns the corresponding [error code]. +** +** ^A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from sqlite3_backup_step() +** is not a permanent error and does not affect the return value of +** sqlite3_backup_finish(). +** +** [[sqlite3_backup_remaining()]] [[sqlite3_backup_pagecount()]] +** <b>sqlite3_backup_remaining() and sqlite3_backup_pagecount()</b> +** +** ^The sqlite3_backup_remaining() routine returns the number of pages still +** to be backed up at the conclusion of the most recent sqlite3_backup_step(). +** ^The sqlite3_backup_pagecount() routine returns the total number of pages +** in the source database at the conclusion of the most recent +** sqlite3_backup_step(). +** ^(The values returned by these functions are only updated by +** sqlite3_backup_step(). If the source database is modified in a way that +** changes the size of the source database or the number of pages remaining, +** those changes are not reflected in the output of sqlite3_backup_pagecount() +** and sqlite3_backup_remaining() until after the next +** sqlite3_backup_step().)^ +** +** <b>Concurrent Usage of Database Handles</b> +** +** ^The source [database connection] may be used by the application for other +** purposes while a backup operation is underway or being initialized. +** ^If SQLite is compiled and configured to support threadsafe database +** connections, then the source database connection may be used concurrently +** from within other threads. +** +** However, the application must guarantee that the destination +** [database connection] is not passed to any other API (by any thread) after +** sqlite3_backup_init() is called and before the corresponding call to +** sqlite3_backup_finish(). SQLite does not currently check to see +** if the application incorrectly accesses the destination [database connection] +** and so no error code is reported, but the operations may malfunction +** nevertheless. Use of the destination database connection while a +** backup is in progress might also cause a mutex deadlock. +** +** If running in [shared cache mode], the application must +** guarantee that the shared cache used by the destination database +** is not accessed while the backup is running. In practice this means +** that the application must guarantee that the disk file being +** backed up to is not accessed by any connection within the process, +** not just the specific connection that was passed to sqlite3_backup_init(). +** +** The [sqlite3_backup] object itself is partially threadsafe. Multiple +** threads may safely make multiple concurrent calls to sqlite3_backup_step(). +** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() +** APIs are not strictly speaking threadsafe. If they are invoked at the +** same time as another thread is invoking sqlite3_backup_step() it is +** possible that they return invalid values. +*/ +SQLITE_API sqlite3_backup *sqlite3_backup_init( + sqlite3 *pDest, /* Destination database handle */ + const char *zDestName, /* Destination database name */ + sqlite3 *pSource, /* Source database handle */ + const char *zSourceName /* Source database name */ +); +SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage); +SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p); +SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p); +SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); + +/* +** CAPI3REF: Unlock Notification +** METHOD: sqlite3 +** +** ^When running in shared-cache mode, a database operation may fail with +** an [SQLITE_LOCKED] error if the required locks on the shared-cache or +** individual tables within the shared-cache cannot be obtained. See +** [SQLite Shared-Cache Mode] for a description of shared-cache locking. +** ^This API may be used to register a callback that SQLite will invoke +** when the connection currently holding the required lock relinquishes it. +** ^This API is only available if the library was compiled with the +** [SQLITE_ENABLE_UNLOCK_NOTIFY] C-preprocessor symbol defined. +** +** See Also: [Using the SQLite Unlock Notification Feature]. +** +** ^Shared-cache locks are released when a database connection concludes +** its current transaction, either by committing it or rolling it back. +** +** ^When a connection (known as the blocked connection) fails to obtain a +** shared-cache lock and SQLITE_LOCKED is returned to the caller, the +** identity of the database connection (the blocking connection) that +** has locked the required resource is stored internally. ^After an +** application receives an SQLITE_LOCKED error, it may call the +** sqlite3_unlock_notify() method with the blocked connection handle as +** the first argument to register for a callback that will be invoked +** when the blocking connections current transaction is concluded. ^The +** callback is invoked from within the [sqlite3_step] or [sqlite3_close] +** call that concludes the blocking connection's transaction. +** +** ^(If sqlite3_unlock_notify() is called in a multi-threaded application, +** there is a chance that the blocking connection will have already +** concluded its transaction by the time sqlite3_unlock_notify() is invoked. +** If this happens, then the specified callback is invoked immediately, +** from within the call to sqlite3_unlock_notify().)^ +** +** ^If the blocked connection is attempting to obtain a write-lock on a +** shared-cache table, and more than one other connection currently holds +** a read-lock on the same table, then SQLite arbitrarily selects one of +** the other connections to use as the blocking connection. +** +** ^(There may be at most one unlock-notify callback registered by a +** blocked connection. If sqlite3_unlock_notify() is called when the +** blocked connection already has a registered unlock-notify callback, +** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is +** called with a NULL pointer as its second argument, then any existing +** unlock-notify callback is cancelled. ^The blocked connections +** unlock-notify callback may also be cancelled by closing the blocked +** connection using [sqlite3_close()]. +** +** The unlock-notify callback is not reentrant. If an application invokes +** any sqlite3_xxx API functions from within an unlock-notify callback, a +** crash or deadlock may be the result. +** +** ^Unless deadlock is detected (see below), sqlite3_unlock_notify() always +** returns SQLITE_OK. +** +** <b>Callback Invocation Details</b> +** +** When an unlock-notify callback is registered, the application provides a +** single void* pointer that is passed to the callback when it is invoked. +** However, the signature of the callback function allows SQLite to pass +** it an array of void* context pointers. The first argument passed to +** an unlock-notify callback is a pointer to an array of void* pointers, +** and the second is the number of entries in the array. +** +** When a blocking connection's transaction is concluded, there may be +** more than one blocked connection that has registered for an unlock-notify +** callback. ^If two or more such blocked connections have specified the +** same callback function, then instead of invoking the callback function +** multiple times, it is invoked once with the set of void* context pointers +** specified by the blocked connections bundled together into an array. +** This gives the application an opportunity to prioritize any actions +** related to the set of unblocked database connections. +** +** <b>Deadlock Detection</b> +** +** Assuming that after registering for an unlock-notify callback a +** database waits for the callback to be issued before taking any further +** action (a reasonable assumption), then using this API may cause the +** application to deadlock. For example, if connection X is waiting for +** connection Y's transaction to be concluded, and similarly connection +** Y is waiting on connection X's transaction, then neither connection +** will proceed and the system may remain deadlocked indefinitely. +** +** To avoid this scenario, the sqlite3_unlock_notify() performs deadlock +** detection. ^If a given call to sqlite3_unlock_notify() would put the +** system in a deadlocked state, then SQLITE_LOCKED is returned and no +** unlock-notify callback is registered. The system is said to be in +** a deadlocked state if connection A has registered for an unlock-notify +** callback on the conclusion of connection B's transaction, and connection +** B has itself registered for an unlock-notify callback when connection +** A's transaction is concluded. ^Indirect deadlock is also detected, so +** the system is also considered to be deadlocked if connection B has +** registered for an unlock-notify callback on the conclusion of connection +** C's transaction, where connection C is waiting on connection A. ^Any +** number of levels of indirection are allowed. +** +** <b>The "DROP TABLE" Exception</b> +** +** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost +** always appropriate to call sqlite3_unlock_notify(). There is however, +** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement, +** SQLite checks if there are any currently executing SELECT statements +** that belong to the same connection. If there are, SQLITE_LOCKED is +** returned. In this case there is no "blocking connection", so invoking +** sqlite3_unlock_notify() results in the unlock-notify callback being +** invoked immediately. If the application then re-attempts the "DROP TABLE" +** or "DROP INDEX" query, an infinite loop might be the result. +** +** One way around this problem is to check the extended error code returned +** by an sqlite3_step() call. ^(If there is a blocking connection, then the +** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in +** the special "DROP TABLE/INDEX" case, the extended error code is just +** SQLITE_LOCKED.)^ +*/ +SQLITE_API int sqlite3_unlock_notify( + sqlite3 *pBlocked, /* Waiting connection */ + void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */ + void *pNotifyArg /* Argument to pass to xNotify */ +); + + +/* +** CAPI3REF: String Comparison +** +** ^The [sqlite3_stricmp()] and [sqlite3_strnicmp()] APIs allow applications +** and extensions to compare the contents of two buffers containing UTF-8 +** strings in a case-independent fashion, using the same definition of "case +** independence" that SQLite uses internally when comparing identifiers. +*/ +SQLITE_API int sqlite3_stricmp(const char *, const char *); +SQLITE_API int sqlite3_strnicmp(const char *, const char *, int); + +/* +** CAPI3REF: String Globbing +* +** ^The [sqlite3_strglob(P,X)] interface returns zero if and only if +** string X matches the [GLOB] pattern P. +** ^The definition of [GLOB] pattern matching used in +** [sqlite3_strglob(P,X)] is the same as for the "X GLOB P" operator in the +** SQL dialect understood by SQLite. ^The [sqlite3_strglob(P,X)] function +** is case sensitive. +** +** Note that this routine returns zero on a match and non-zero if the strings +** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()]. +** +** See also: [sqlite3_strlike()]. +*/ +SQLITE_API int sqlite3_strglob(const char *zGlob, const char *zStr); + +/* +** CAPI3REF: String LIKE Matching +* +** ^The [sqlite3_strlike(P,X,E)] interface returns zero if and only if +** string X matches the [LIKE] pattern P with escape character E. +** ^The definition of [LIKE] pattern matching used in +** [sqlite3_strlike(P,X,E)] is the same as for the "X LIKE P ESCAPE E" +** operator in the SQL dialect understood by SQLite. ^For "X LIKE P" without +** the ESCAPE clause, set the E parameter of [sqlite3_strlike(P,X,E)] to 0. +** ^As with the LIKE operator, the [sqlite3_strlike(P,X,E)] function is case +** insensitive - equivalent upper and lower case ASCII characters match +** one another. +** +** ^The [sqlite3_strlike(P,X,E)] function matches Unicode characters, though +** only ASCII characters are case folded. +** +** Note that this routine returns zero on a match and non-zero if the strings +** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()]. +** +** See also: [sqlite3_strglob()]. +*/ +SQLITE_API int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc); + +/* +** CAPI3REF: Error Logging Interface +** +** ^The [sqlite3_log()] interface writes a message into the [error log] +** established by the [SQLITE_CONFIG_LOG] option to [sqlite3_config()]. +** ^If logging is enabled, the zFormat string and subsequent arguments are +** used with [sqlite3_snprintf()] to generate the final output string. +** +** The sqlite3_log() interface is intended for use by extensions such as +** virtual tables, collating functions, and SQL functions. While there is +** nothing to prevent an application from calling sqlite3_log(), doing so +** is considered bad form. +** +** The zFormat string must not be NULL. +** +** To avoid deadlocks and other threading problems, the sqlite3_log() routine +** will not use dynamically allocated memory. The log message is stored in +** a fixed-length buffer on the stack. If the log message is longer than +** a few hundred characters, it will be truncated to the length of the +** buffer. +*/ +SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); + +/* +** CAPI3REF: Write-Ahead Log Commit Hook +** METHOD: sqlite3 +** +** ^The [sqlite3_wal_hook()] function is used to register a callback that +** is invoked each time data is committed to a database in wal mode. +** +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation +** may read, write or [checkpoint] the database as required. +** +** ^The first parameter passed to the callback function when it is invoked +** is a copy of the third parameter passed to sqlite3_wal_hook() when +** registering the callback. ^The second is a copy of the database handle. +** ^The third parameter is the name of the database that was written to - +** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter +** is the number of pages currently in the write-ahead log file, +** including those that were just committed. +** +** The callback function should normally return [SQLITE_OK]. ^If an error +** code is returned, that error will propagate back up through the +** SQLite code base to cause the statement that provoked the callback +** to report an error, though the commit will have still occurred. If the +** callback returns [SQLITE_ROW] or [SQLITE_DONE], or if it returns a value +** that does not correspond to any valid SQLite error code, the results +** are undefined. +** +** A single database handle may have at most a single write-ahead log callback +** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any +** previously registered write-ahead log callback. ^The return value is +** a copy of the third parameter from the previous call, if any, or 0. +** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the +** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will +** overwrite any prior [sqlite3_wal_hook()] settings. +*/ +SQLITE_API void *sqlite3_wal_hook( + sqlite3*, + int(*)(void *,sqlite3*,const char*,int), + void* +); + +/* +** CAPI3REF: Configure an auto-checkpoint +** METHOD: sqlite3 +** +** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around +** [sqlite3_wal_hook()] that causes any database on [database connection] D +** to automatically [checkpoint] +** after committing a transaction if there are N or +** more frames in the [write-ahead log] file. ^Passing zero or +** a negative value as the nFrame parameter disables automatic +** checkpoints entirely. +** +** ^The callback registered by this function replaces any existing callback +** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback +** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism +** configured by this function. +** +** ^The [wal_autocheckpoint pragma] can be used to invoke this interface +** from SQL. +** +** ^Checkpoints initiated by this mechanism are +** [sqlite3_wal_checkpoint_v2|PASSIVE]. +** +** ^Every new [database connection] defaults to having the auto-checkpoint +** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] +** pages. The use of this interface +** is only necessary if the default setting is found to be suboptimal +** for a particular application. +*/ +SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); + +/* +** CAPI3REF: Checkpoint a database +** METHOD: sqlite3 +** +** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to +** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^ +** +** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the +** [write-ahead log] for database X on [database connection] D to be +** transferred into the database file and for the write-ahead log to +** be reset. See the [checkpointing] documentation for addition +** information. +** +** This interface used to be the only way to cause a checkpoint to +** occur. But then the newer and more powerful [sqlite3_wal_checkpoint_v2()] +** interface was added. This interface is retained for backwards +** compatibility and as a convenience for applications that need to manually +** start a callback but which do not need the full power (and corresponding +** complication) of [sqlite3_wal_checkpoint_v2()]. +*/ +SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); + +/* +** CAPI3REF: Checkpoint a database +** METHOD: sqlite3 +** +** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint +** operation on database X of [database connection] D in mode M. Status +** information is written back into integers pointed to by L and C.)^ +** ^(The M parameter must be a valid [checkpoint mode]:)^ +** +** <dl> +** <dt>SQLITE_CHECKPOINT_PASSIVE<dd> +** ^Checkpoint as many frames as possible without waiting for any database +** readers or writers to finish, then sync the database file if all frames +** in the log were checkpointed. ^The [busy-handler callback] +** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. +** ^On the other hand, passive mode might leave the checkpoint unfinished +** if there are concurrent readers or writers. +** +** <dt>SQLITE_CHECKPOINT_FULL<dd> +** ^This mode blocks (it invokes the +** [sqlite3_busy_handler|busy-handler callback]) until there is no +** database writer and all readers are reading from the most recent database +** snapshot. ^It then checkpoints all frames in the log file and syncs the +** database file. ^This mode blocks new database writers while it is pending, +** but new database readers are allowed to continue unimpeded. +** +** <dt>SQLITE_CHECKPOINT_RESTART<dd> +** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition +** that after checkpointing the log file it blocks (calls the +** [busy-handler callback]) +** until all readers are reading from the database file only. ^This ensures +** that the next writer will restart the log file from the beginning. +** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new +** database writer attempts while it is pending, but does not impede readers. +** +** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd> +** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the +** addition that it also truncates the log file to zero bytes just prior +** to a successful return. +** </dl> +** +** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in +** the log file or to -1 if the checkpoint could not run because +** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not +** NULL,then *pnCkpt is set to the total number of checkpointed frames in the +** log file (including any that were already checkpointed before the function +** was called) or to -1 if the checkpoint could not run due to an error or +** because the database is not in WAL mode. ^Note that upon successful +** completion of an SQLITE_CHECKPOINT_TRUNCATE, the log file will have been +** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero. +** +** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If +** any other process is running a checkpoint operation at the same time, the +** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a +** busy-handler configured, it will not be invoked in this case. +** +** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the +** exclusive "writer" lock on the database file. ^If the writer lock cannot be +** obtained immediately, and a busy-handler is configured, it is invoked and +** the writer lock retried until either the busy-handler returns 0 or the lock +** is successfully obtained. ^The busy-handler is also invoked while waiting for +** database readers as described above. ^If the busy-handler returns 0 before +** the writer lock is obtained or while waiting for database readers, the +** checkpoint operation proceeds from that point in the same way as +** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible +** without blocking any further. ^SQLITE_BUSY is returned in this case. +** +** ^If parameter zDb is NULL or points to a zero length string, then the +** specified operation is attempted on all WAL databases [attached] to +** [database connection] db. In this case the +** values written to output parameters *pnLog and *pnCkpt are undefined. ^If +** an SQLITE_BUSY error is encountered when processing one or more of the +** attached WAL databases, the operation is still attempted on any remaining +** attached databases and SQLITE_BUSY is returned at the end. ^If any other +** error occurs while processing an attached database, processing is abandoned +** and the error code is returned to the caller immediately. ^If no error +** (SQLITE_BUSY or otherwise) is encountered while processing the attached +** databases, SQLITE_OK is returned. +** +** ^If database zDb is the name of an attached database that is not in WAL +** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. ^If +** zDb is not NULL (or a zero length string) and is not the name of any +** attached database, SQLITE_ERROR is returned to the caller. +** +** ^Unless it returns SQLITE_MISUSE, +** the sqlite3_wal_checkpoint_v2() interface +** sets the error information that is queried by +** [sqlite3_errcode()] and [sqlite3_errmsg()]. +** +** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface +** from SQL. +*/ +SQLITE_API int sqlite3_wal_checkpoint_v2( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of attached database (or NULL) */ + int eMode, /* SQLITE_CHECKPOINT_* value */ + int *pnLog, /* OUT: Size of WAL log in frames */ + int *pnCkpt /* OUT: Total number of frames checkpointed */ +); + +/* +** CAPI3REF: Checkpoint Mode Values +** KEYWORDS: {checkpoint mode} +** +** These constants define all valid values for the "checkpoint mode" passed +** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. +** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the +** meaning of each of these checkpoint modes. +*/ +#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ +#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ +#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ +#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ + +/* +** CAPI3REF: Virtual Table Interface Configuration +** +** This function may be called by either the [xConnect] or [xCreate] method +** of a [virtual table] implementation to configure +** various facets of the virtual table interface. +** +** If this interface is invoked outside the context of an xConnect or +** xCreate virtual table method then the behavior is undefined. +** +** In the call sqlite3_vtab_config(D,C,...) the D parameter is the +** [database connection] in which the virtual table is being created and +** which is passed in as the first argument to the [xConnect] or [xCreate] +** method that is invoking sqlite3_vtab_config(). The C parameter is one +** of the [virtual table configuration options]. The presence and meaning +** of parameters after C depend on which [virtual table configuration option] +** is used. +*/ +SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); + +/* +** CAPI3REF: Virtual Table Configuration Options +** KEYWORDS: {virtual table configuration options} +** KEYWORDS: {virtual table configuration option} +** +** These macros define the various options to the +** [sqlite3_vtab_config()] interface that [virtual table] implementations +** can use to customize and optimize their behavior. +** +** <dl> +** [[SQLITE_VTAB_CONSTRAINT_SUPPORT]] +** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT</dt> +** <dd>Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported, +** where X is an integer. If X is zero, then the [virtual table] whose +** [xCreate] or [xConnect] method invoked [sqlite3_vtab_config()] does not +** support constraints. In this configuration (which is the default) if +** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire +** statement is rolled back as if [ON CONFLICT | OR ABORT] had been +** specified as part of the users SQL statement, regardless of the actual +** ON CONFLICT mode specified. +** +** If X is non-zero, then the virtual table implementation guarantees +** that if [xUpdate] returns [SQLITE_CONSTRAINT], it will do so before +** any modifications to internal or persistent data structures have been made. +** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite +** is able to roll back a statement or database transaction, and abandon +** or continue processing the current SQL statement as appropriate. +** If the ON CONFLICT mode is REPLACE and the [xUpdate] method returns +** [SQLITE_CONSTRAINT], SQLite handles this as if the ON CONFLICT mode +** had been ABORT. +** +** Virtual table implementations that are required to handle OR REPLACE +** must do so within the [xUpdate] method. If a call to the +** [sqlite3_vtab_on_conflict()] function indicates that the current ON +** CONFLICT policy is REPLACE, the virtual table implementation should +** silently replace the appropriate rows within the xUpdate callback and +** return SQLITE_OK. Or, if this is not possible, it may return +** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT +** constraint handling. +** </dd> +** +** [[SQLITE_VTAB_DIRECTONLY]]<dt>SQLITE_VTAB_DIRECTONLY</dt> +** <dd>Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** prohibits that virtual table from being used from within triggers and +** views. +** </dd> +** +** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt> +** <dd>Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** identify that virtual table as being safe to use from within triggers +** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the +** virtual table can do no serious harm even if it is controlled by a +** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS +** flag unless absolutely necessary. +** </dd> +** +** [[SQLITE_VTAB_USES_ALL_SCHEMAS]]<dt>SQLITE_VTAB_USES_ALL_SCHEMAS</dt> +** <dd>Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_USES_ALL_SCHEMA) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** instruct the query planner to begin at least a read transaction on +** all schemas ("main", "temp", and any ATTACH-ed databases) whenever the +** virtual table is used. +** </dd> +** </dl> +*/ +#define SQLITE_VTAB_CONSTRAINT_SUPPORT 1 +#define SQLITE_VTAB_INNOCUOUS 2 +#define SQLITE_VTAB_DIRECTONLY 3 +#define SQLITE_VTAB_USES_ALL_SCHEMAS 4 + +/* +** CAPI3REF: Determine The Virtual Table Conflict Policy +** +** This function may only be called from within a call to the [xUpdate] method +** of a [virtual table] implementation for an INSERT or UPDATE operation. ^The +** value returned is one of [SQLITE_ROLLBACK], [SQLITE_IGNORE], [SQLITE_FAIL], +** [SQLITE_ABORT], or [SQLITE_REPLACE], according to the [ON CONFLICT] mode +** of the SQL statement that triggered the call to the [xUpdate] method of the +** [virtual table]. +*/ +SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); + +/* +** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE +** +** If the sqlite3_vtab_nochange(X) routine is called within the [xColumn] +** method of a [virtual table], then it might return true if the +** column is being fetched as part of an UPDATE operation during which the +** column value will not change. The virtual table implementation can use +** this hint as permission to substitute a return value that is less +** expensive to compute and that the corresponding +** [xUpdate] method understands as a "no-change" value. +** +** If the [xColumn] method calls sqlite3_vtab_nochange() and finds that +** the column is not changed by the UPDATE statement, then the xColumn +** method can optionally return without setting a result, without calling +** any of the [sqlite3_result_int|sqlite3_result_xxxxx() interfaces]. +** In that case, [sqlite3_value_nochange(X)] will return true for the +** same column in the [xUpdate] method. +** +** The sqlite3_vtab_nochange() routine is an optimization. Virtual table +** implementations should continue to give a correct answer even if the +** sqlite3_vtab_nochange() interface were to always return false. In the +** current implementation, the sqlite3_vtab_nochange() interface does always +** returns false for the enhanced [UPDATE FROM] statement. +*/ +SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*); + +/* +** CAPI3REF: Determine The Collation For a Virtual Table Constraint +** METHOD: sqlite3_index_info +** +** This function may only be called from within a call to the [xBestIndex] +** method of a [virtual table]. This function returns a pointer to a string +** that is the name of the appropriate collation sequence to use for text +** comparisons on the constraint identified by its arguments. +** +** The first argument must be the pointer to the [sqlite3_index_info] object +** that is the first parameter to the xBestIndex() method. The second argument +** must be an index into the aConstraint[] array belonging to the +** sqlite3_index_info structure passed to xBestIndex. +** +** Important: +** The first parameter must be the same pointer that is passed into the +** xBestMethod() method. The first parameter may not be a pointer to a +** different [sqlite3_index_info] object, even an exact copy. +** +** The return value is computed as follows: +** +** <ol> +** <li><p> If the constraint comes from a WHERE clause expression that contains +** a [COLLATE operator], then the name of the collation specified by +** that COLLATE operator is returned. +** <li><p> If there is no COLLATE operator, but the column that is the subject +** of the constraint specifies an alternative collating sequence via +** a [COLLATE clause] on the column definition within the CREATE TABLE +** statement that was passed into [sqlite3_declare_vtab()], then the +** name of that alternative collating sequence is returned. +** <li><p> Otherwise, "BINARY" is returned. +** </ol> +*/ +SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int); + +/* +** CAPI3REF: Determine if a virtual table query is DISTINCT +** METHOD: sqlite3_index_info +** +** This API may only be used from within an [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this +** interface from outside of xBestIndex() is undefined and probably harmful. +** +** ^The sqlite3_vtab_distinct() interface returns an integer between 0 and +** 3. The integer returned by sqlite3_vtab_distinct() +** gives the virtual table additional information about how the query +** planner wants the output to be ordered. As long as the virtual table +** can meet the ordering requirements of the query planner, it may set +** the "orderByConsumed" flag. +** +** <ol><li value="0"><p> +** ^If the sqlite3_vtab_distinct() interface returns 0, that means +** that the query planner needs the virtual table to return all rows in the +** sort order defined by the "nOrderBy" and "aOrderBy" fields of the +** [sqlite3_index_info] object. This is the default expectation. If the +** virtual table outputs all rows in sorted order, then it is always safe for +** the xBestIndex method to set the "orderByConsumed" flag, regardless of +** the return value from sqlite3_vtab_distinct(). +** <li value="1"><p> +** ^(If the sqlite3_vtab_distinct() interface returns 1, that means +** that the query planner does not need the rows to be returned in sorted order +** as long as all rows with the same values in all columns identified by the +** "aOrderBy" field are adjacent.)^ This mode is used when the query planner +** is doing a GROUP BY. +** <li value="2"><p> +** ^(If the sqlite3_vtab_distinct() interface returns 2, that means +** that the query planner does not need the rows returned in any particular +** order, as long as rows with the same values in all "aOrderBy" columns +** are adjacent.)^ ^(Furthermore, only a single row for each particular +** combination of values in the columns identified by the "aOrderBy" field +** needs to be returned.)^ ^It is always ok for two or more rows with the same +** values in all "aOrderBy" columns to be returned, as long as all such rows +** are adjacent. ^The virtual table may, if it chooses, omit extra rows +** that have the same value for all columns identified by "aOrderBy". +** ^However omitting the extra rows is optional. +** This mode is used for a DISTINCT query. +** <li value="3"><p> +** ^(If the sqlite3_vtab_distinct() interface returns 3, that means +** that the query planner needs only distinct rows but it does need the +** rows to be sorted.)^ ^The virtual table implementation is free to omit +** rows that are identical in all aOrderBy columns, if it wants to, but +** it is not required to omit any rows. This mode is used for queries +** that have both DISTINCT and ORDER BY clauses. +** </ol> +** +** ^For the purposes of comparing virtual table output values to see if the +** values are same value for sorting purposes, two NULL values are considered +** to be the same. In other words, the comparison operator is "IS" +** (or "IS NOT DISTINCT FROM") and not "==". +** +** If a virtual table implementation is unable to meet the requirements +** specified above, then it must not set the "orderByConsumed" flag in the +** [sqlite3_index_info] object or an incorrect answer may result. +** +** ^A virtual table implementation is always free to return rows in any order +** it wants, as long as the "orderByConsumed" flag is not set. ^When the +** the "orderByConsumed" flag is unset, the query planner will add extra +** [bytecode] to ensure that the final results returned by the SQL query are +** ordered correctly. The use of the "orderByConsumed" flag and the +** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful +** use of the sqlite3_vtab_distinct() interface and the "orderByConsumed" +** flag might help queries against a virtual table to run faster. Being +** overly aggressive and setting the "orderByConsumed" flag when it is not +** valid to do so, on the other hand, might cause SQLite to return incorrect +** results. +*/ +SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*); + +/* +** CAPI3REF: Identify and handle IN constraints in xBestIndex +** +** This interface may only be used from within an +** [xBestIndex|xBestIndex() method] of a [virtual table] implementation. +** The result of invoking this interface from any other context is +** undefined and probably harmful. +** +** ^(A constraint on a virtual table of the form +** "[IN operator|column IN (...)]" is +** communicated to the xBestIndex method as a +** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use +** this constraint, it must set the corresponding +** aConstraintUsage[].argvIndex to a positive integer. ^(Then, under +** the usual mode of handling IN operators, SQLite generates [bytecode] +** that invokes the [xFilter|xFilter() method] once for each value +** on the right-hand side of the IN operator.)^ Thus the virtual table +** only sees a single value from the right-hand side of the IN operator +** at a time. +** +** In some cases, however, it would be advantageous for the virtual +** table to see all values on the right-hand of the IN operator all at +** once. The sqlite3_vtab_in() interfaces facilitates this in two ways: +** +** <ol> +** <li><p> +** ^A call to sqlite3_vtab_in(P,N,-1) will return true (non-zero) +** if and only if the [sqlite3_index_info|P->aConstraint][N] constraint +** is an [IN operator] that can be processed all at once. ^In other words, +** sqlite3_vtab_in() with -1 in the third argument is a mechanism +** by which the virtual table can ask SQLite if all-at-once processing +** of the IN operator is even possible. +** +** <li><p> +** ^A call to sqlite3_vtab_in(P,N,F) with F==1 or F==0 indicates +** to SQLite that the virtual table does or does not want to process +** the IN operator all-at-once, respectively. ^Thus when the third +** parameter (F) is non-negative, this interface is the mechanism by +** which the virtual table tells SQLite how it wants to process the +** IN operator. +** </ol> +** +** ^The sqlite3_vtab_in(P,N,F) interface can be invoked multiple times +** within the same xBestIndex method call. ^For any given P,N pair, +** the return value from sqlite3_vtab_in(P,N,F) will always be the same +** within the same xBestIndex call. ^If the interface returns true +** (non-zero), that means that the constraint is an IN operator +** that can be processed all-at-once. ^If the constraint is not an IN +** operator or cannot be processed all-at-once, then the interface returns +** false. +** +** ^(All-at-once processing of the IN operator is selected if both of the +** following conditions are met: +** +** <ol> +** <li><p> The P->aConstraintUsage[N].argvIndex value is set to a positive +** integer. This is how the virtual table tells SQLite that it wants to +** use the N-th constraint. +** +** <li><p> The last call to sqlite3_vtab_in(P,N,F) for which F was +** non-negative had F>=1. +** </ol>)^ +** +** ^If either or both of the conditions above are false, then SQLite uses +** the traditional one-at-a-time processing strategy for the IN constraint. +** ^If both conditions are true, then the argvIndex-th parameter to the +** xFilter method will be an [sqlite3_value] that appears to be NULL, +** but which can be passed to [sqlite3_vtab_in_first()] and +** [sqlite3_vtab_in_next()] to find all values on the right-hand side +** of the IN constraint. +*/ +SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); + +/* +** CAPI3REF: Find all elements on the right-hand side of an IN constraint. +** +** These interfaces are only useful from within the +** [xFilter|xFilter() method] of a [virtual table] implementation. +** The result of invoking these interfaces from any other context +** is undefined and probably harmful. +** +** The X parameter in a call to sqlite3_vtab_in_first(X,P) or +** sqlite3_vtab_in_next(X,P) should be one of the parameters to the +** xFilter method which invokes these routines, and specifically +** a parameter that was previously selected for all-at-once IN constraint +** processing use the [sqlite3_vtab_in()] interface in the +** [xBestIndex|xBestIndex method]. ^(If the X parameter is not +** an xFilter argument that was selected for all-at-once IN constraint +** processing, then these routines return [SQLITE_ERROR].)^ +** +** ^(Use these routines to access all values on the right-hand side +** of the IN constraint using code like the following: +** +** <blockquote><pre> +** &nbsp; for(rc=sqlite3_vtab_in_first(pList, &pVal); +** &nbsp; rc==SQLITE_OK && pVal; +** &nbsp; rc=sqlite3_vtab_in_next(pList, &pVal) +** &nbsp; ){ +** &nbsp; // do something with pVal +** &nbsp; } +** &nbsp; if( rc!=SQLITE_OK ){ +** &nbsp; // an error has occurred +** &nbsp; } +** </pre></blockquote>)^ +** +** ^On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) +** routines return SQLITE_OK and set *P to point to the first or next value +** on the RHS of the IN constraint. ^If there are no more values on the +** right hand side of the IN constraint, then *P is set to NULL and these +** routines return [SQLITE_DONE]. ^The return value might be +** some other value, such as SQLITE_NOMEM, in the event of a malfunction. +** +** The *ppOut values returned by these routines are only valid until the +** next call to either of these routines or until the end of the xFilter +** method from which these routines were called. If the virtual table +** implementation needs to retain the *ppOut values for longer, it must make +** copies. The *ppOut values are [protected sqlite3_value|protected]. +*/ +SQLITE_API int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); +SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); + +/* +** CAPI3REF: Constraint values in xBestIndex() +** METHOD: sqlite3_index_info +** +** This API may only be used from within the [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this interface +** from outside of an xBestIndex method are undefined and probably harmful. +** +** ^When the sqlite3_vtab_rhs_value(P,J,V) interface is invoked from within +** the [xBestIndex] method of a [virtual table] implementation, with P being +** a copy of the [sqlite3_index_info] object pointer passed into xBestIndex and +** J being a 0-based index into P->aConstraint[], then this routine +** attempts to set *V to the value of the right-hand operand of +** that constraint if the right-hand operand is known. ^If the +** right-hand operand is not known, then *V is set to a NULL pointer. +** ^The sqlite3_vtab_rhs_value(P,J,V) interface returns SQLITE_OK if +** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V) +** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th +** constraint is not available. ^The sqlite3_vtab_rhs_value() interface +** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if +** something goes wrong. +** +** The sqlite3_vtab_rhs_value() interface is usually only successful if +** the right-hand operand of a constraint is a literal value in the original +** SQL statement. If the right-hand operand is an expression or a reference +** to some other column or a [host parameter], then sqlite3_vtab_rhs_value() +** will probably return [SQLITE_NOTFOUND]. +** +** ^(Some constraints, such as [SQLITE_INDEX_CONSTRAINT_ISNULL] and +** [SQLITE_INDEX_CONSTRAINT_ISNOTNULL], have no right-hand operand. For such +** constraints, sqlite3_vtab_rhs_value() always returns SQLITE_NOTFOUND.)^ +** +** ^The [sqlite3_value] object returned in *V is a protected sqlite3_value +** and remains valid for the duration of the xBestIndex method call. +** ^When xBestIndex returns, the sqlite3_value object returned by +** sqlite3_vtab_rhs_value() is automatically deallocated. +** +** The "_rhs_" in the name of this routine is an abbreviation for +** "Right-Hand Side". +*/ +SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); + +/* +** CAPI3REF: Conflict resolution modes +** KEYWORDS: {conflict resolution mode} +** +** These constants are returned by [sqlite3_vtab_on_conflict()] to +** inform a [virtual table] implementation what the [ON CONFLICT] mode +** is for the SQL statement being evaluated. +** +** Note that the [SQLITE_IGNORE] constant is also used as a potential +** return value from the [sqlite3_set_authorizer()] callback and that +** [SQLITE_ABORT] is also a [result code]. +*/ +#define SQLITE_ROLLBACK 1 +/* #define SQLITE_IGNORE 2 // Also used by sqlite3_authorizer() callback */ +#define SQLITE_FAIL 3 +/* #define SQLITE_ABORT 4 // Also an error code */ +#define SQLITE_REPLACE 5 + +/* +** CAPI3REF: Prepared Statement Scan Status Opcodes +** KEYWORDS: {scanstatus options} +** +** The following constants can be used for the T parameter to the +** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a +** different metric for sqlite3_stmt_scanstatus() to return. +** +** When the value returned to V is a string, space to hold that string is +** managed by the prepared statement S and will be automatically freed when +** S is finalized. +** +** Not all values are available for all query elements. When a value is +** not available, the output variable is set to -1 if the value is numeric, +** or to NULL if it is a string (SQLITE_SCANSTAT_NAME). +** +** <dl> +** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt> +** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be +** set to the total number of times that the X-th loop has run.</dd> +** +** [[SQLITE_SCANSTAT_NVISIT]] <dt>SQLITE_SCANSTAT_NVISIT</dt> +** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be set +** to the total number of rows examined by all iterations of the X-th loop.</dd> +** +** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt> +** <dd>^The "double" variable pointed to by the V parameter will be set to the +** query planner's estimate for the average number of rows output from each +** iteration of the X-th loop. If the query planner's estimates was accurate, +** then this value will approximate the quotient NVISIT/NLOOP and the +** product of this value for all prior loops with the same SELECTID will +** be the NLOOP value for the current loop. +** +** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt> +** <dd>^The "const char *" variable pointed to by the V parameter will be set +** to a zero-terminated UTF-8 string containing the name of the index or table +** used for the X-th loop. +** +** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt> +** <dd>^The "const char *" variable pointed to by the V parameter will be set +** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] +** description for the X-th loop. +** +** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt> +** <dd>^The "int" variable pointed to by the V parameter will be set to the +** id for the X-th query plan element. The id value is unique within the +** statement. The select-id is the same value as is output in the first +** column of an [EXPLAIN QUERY PLAN] query. +** +** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt> +** <dd>The "int" variable pointed to by the V parameter will be set to the +** the id of the parent of the current query element, if applicable, or +** to zero if the query element has no parent. This is the same value as +** returned in the second column of an [EXPLAIN QUERY PLAN] query. +** +** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt> +** <dd>The sqlite3_int64 output value is set to the number of cycles, +** according to the processor time-stamp counter, that elapsed while the +** query element was being processed. This value is not available for +** all query elements - if it is unavailable the output variable is +** set to -1. +** </dl> +*/ +#define SQLITE_SCANSTAT_NLOOP 0 +#define SQLITE_SCANSTAT_NVISIT 1 +#define SQLITE_SCANSTAT_EST 2 +#define SQLITE_SCANSTAT_NAME 3 +#define SQLITE_SCANSTAT_EXPLAIN 4 +#define SQLITE_SCANSTAT_SELECTID 5 +#define SQLITE_SCANSTAT_PARENTID 6 +#define SQLITE_SCANSTAT_NCYCLE 7 + +/* +** CAPI3REF: Prepared Statement Scan Status +** METHOD: sqlite3_stmt +** +** These interfaces return information about the predicted and measured +** performance for pStmt. Advanced applications can use this +** interface to compare the predicted and the measured performance and +** issue warnings and/or rerun [ANALYZE] if discrepancies are found. +** +** Since this interface is expected to be rarely used, it is only +** available if SQLite is compiled using the [SQLITE_ENABLE_STMT_SCANSTATUS] +** compile-time option. +** +** The "iScanStatusOp" parameter determines which status information to return. +** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior +** of this interface is undefined. ^The requested measurement is written into +** a variable pointed to by the "pOut" parameter. +** +** The "flags" parameter must be passed a mask of flags. At present only +** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX +** is specified, then status information is available for all elements +** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If +** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements +** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of +** the EXPLAIN QUERY PLAN output) are available. Invoking API +** sqlite3_stmt_scanstatus() is equivalent to calling +** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter. +** +** Parameter "idx" identifies the specific query element to retrieve statistics +** for. Query elements are numbered starting from zero. A value of -1 may be +** to query for statistics regarding the entire query. ^If idx is out of range +** - less than -1 or greater than or equal to the total number of query +** elements used to implement the statement - a non-zero value is returned and +** the variable that pOut points to is unchanged. +** +** See also: [sqlite3_stmt_scanstatus_reset()] +*/ +SQLITE_API int sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + void *pOut /* Result written here */ +); +SQLITE_API int sqlite3_stmt_scanstatus_v2( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + int flags, /* Mask of flags defined below */ + void *pOut /* Result written here */ +); + +/* +** CAPI3REF: Prepared Statement Scan Status +** KEYWORDS: {scan status flags} +*/ +#define SQLITE_SCANSTAT_COMPLEX 0x0001 + +/* +** CAPI3REF: Zero Scan-Status Counters +** METHOD: sqlite3_stmt +** +** ^Zero all [sqlite3_stmt_scanstatus()] related event counters. +** +** This API is only available if the library is built with pre-processor +** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined. +*/ +SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); + +/* +** CAPI3REF: Flush caches to disk mid-transaction +** METHOD: sqlite3 +** +** ^If a write-transaction is open on [database connection] D when the +** [sqlite3_db_cacheflush(D)] interface invoked, any dirty +** pages in the pager-cache that are not currently in use are written out +** to disk. A dirty page may be in use if a database cursor created by an +** active SQL statement is reading from it, or if it is page 1 of a database +** file (page 1 is always "in use"). ^The [sqlite3_db_cacheflush(D)] +** interface flushes caches for all schemas - "main", "temp", and +** any [attached] databases. +** +** ^If this function needs to obtain extra database locks before dirty pages +** can be flushed to disk, it does so. ^If those locks cannot be obtained +** immediately and there is a busy-handler callback configured, it is invoked +** in the usual manner. ^If the required lock still cannot be obtained, then +** the database is skipped and an attempt made to flush any dirty pages +** belonging to the next (if any) database. ^If any databases are skipped +** because locks cannot be obtained, but no other error occurs, this +** function returns SQLITE_BUSY. +** +** ^If any other error occurs while flushing dirty pages to disk (for +** example an IO error or out-of-memory condition), then processing is +** abandoned and an SQLite [error code] is returned to the caller immediately. +** +** ^Otherwise, if no error occurs, [sqlite3_db_cacheflush()] returns SQLITE_OK. +** +** ^This function does not set the database handle error code or message +** returned by the [sqlite3_errcode()] and [sqlite3_errmsg()] functions. +*/ +SQLITE_API int sqlite3_db_cacheflush(sqlite3*); + +/* +** CAPI3REF: The pre-update hook. +** METHOD: sqlite3 +** +** ^These interfaces are only available if SQLite is compiled using the +** [SQLITE_ENABLE_PREUPDATE_HOOK] compile-time option. +** +** ^The [sqlite3_preupdate_hook()] interface registers a callback function +** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation +** on a database table. +** ^At most one preupdate hook may be registered at a time on a single +** [database connection]; each call to [sqlite3_preupdate_hook()] overrides +** the previous setting. +** ^The preupdate hook is disabled by invoking [sqlite3_preupdate_hook()] +** with a NULL pointer as the second parameter. +** ^The third parameter to [sqlite3_preupdate_hook()] is passed through as +** the first parameter to callbacks. +** +** ^The preupdate hook only fires for changes to real database tables; the +** preupdate hook is not invoked for changes to [virtual tables] or to +** system tables like sqlite_sequence or sqlite_stat1. +** +** ^The second parameter to the preupdate callback is a pointer to +** the [database connection] that registered the preupdate hook. +** ^The third parameter to the preupdate callback is one of the constants +** [SQLITE_INSERT], [SQLITE_DELETE], or [SQLITE_UPDATE] to identify the +** kind of update operation that is about to occur. +** ^(The fourth parameter to the preupdate callback is the name of the +** database within the database connection that is being modified. This +** will be "main" for the main database or "temp" for TEMP tables or +** the name given after the AS keyword in the [ATTACH] statement for attached +** databases.)^ +** ^The fifth parameter to the preupdate callback is the name of the +** table that is being modified. +** +** For an UPDATE or DELETE operation on a [rowid table], the sixth +** parameter passed to the preupdate callback is the initial [rowid] of the +** row being modified or deleted. For an INSERT operation on a rowid table, +** or any operation on a WITHOUT ROWID table, the value of the sixth +** parameter is undefined. For an INSERT or UPDATE on a rowid table the +** seventh parameter is the final rowid value of the row being inserted +** or updated. The value of the seventh parameter passed to the callback +** function is not defined for operations on WITHOUT ROWID tables, or for +** DELETE operations on rowid tables. +** +** ^The sqlite3_preupdate_hook(D,C,P) function returns the P argument from +** the previous call on the same [database connection] D, or NULL for +** the first call on D. +** +** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()], +** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces +** provide additional information about a preupdate event. These routines +** may only be called from within a preupdate callback. Invoking any of +** these routines from outside of a preupdate callback or with a +** [database connection] pointer that is different from the one supplied +** to the preupdate callback results in undefined and probably undesirable +** behavior. +** +** ^The [sqlite3_preupdate_count(D)] interface returns the number of columns +** in the row that is being inserted, updated, or deleted. +** +** ^The [sqlite3_preupdate_old(D,N,P)] interface writes into P a pointer to +** a [protected sqlite3_value] that contains the value of the Nth column of +** the table row before it is updated. The N parameter must be between 0 +** and one less than the number of columns or the behavior will be +** undefined. This must only be used within SQLITE_UPDATE and SQLITE_DELETE +** preupdate callbacks; if it is used by an SQLITE_INSERT callback then the +** behavior is undefined. The [sqlite3_value] that P points to +** will be destroyed when the preupdate callback returns. +** +** ^The [sqlite3_preupdate_new(D,N,P)] interface writes into P a pointer to +** a [protected sqlite3_value] that contains the value of the Nth column of +** the table row after it is updated. The N parameter must be between 0 +** and one less than the number of columns or the behavior will be +** undefined. This must only be used within SQLITE_INSERT and SQLITE_UPDATE +** preupdate callbacks; if it is used by an SQLITE_DELETE callback then the +** behavior is undefined. The [sqlite3_value] that P points to +** will be destroyed when the preupdate callback returns. +** +** ^The [sqlite3_preupdate_depth(D)] interface returns 0 if the preupdate +** callback was invoked as a result of a direct insert, update, or delete +** operation; or 1 for inserts, updates, or deletes invoked by top-level +** triggers; or 2 for changes resulting from triggers called by top-level +** triggers; and so forth. +** +** When the [sqlite3_blob_write()] API is used to update a blob column, +** the pre-update hook is invoked with SQLITE_DELETE. This is because the +** in this case the new values are not available. In this case, when a +** callback made with op==SQLITE_DELETE is actually a write using the +** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns +** the index of the column being written. In other cases, where the +** pre-update hook is being invoked for some other reason, including a +** regular DELETE, sqlite3_preupdate_blobwrite() returns -1. +** +** See also: [sqlite3_update_hook()] +*/ +#if defined(SQLITE_ENABLE_PREUPDATE_HOOK) +SQLITE_API void *sqlite3_preupdate_hook( + sqlite3 *db, + void(*xPreUpdate)( + void *pCtx, /* Copy of third arg to preupdate_hook() */ + sqlite3 *db, /* Database handle */ + int op, /* SQLITE_UPDATE, DELETE or INSERT */ + char const *zDb, /* Database name */ + char const *zName, /* Table name */ + sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */ + sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */ + ), + void* +); +SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_count(sqlite3 *); +SQLITE_API int sqlite3_preupdate_depth(sqlite3 *); +SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); +#endif + +/* +** CAPI3REF: Low-level system error code +** METHOD: sqlite3 +** +** ^Attempt to return the underlying operating system error code or error +** number that caused the most recent I/O error or failure to open a file. +** The return value is OS-dependent. For example, on unix systems, after +** [sqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be +** called to get back the underlying "errno" that caused the problem, such +** as ENOSPC, EAUTH, EISDIR, and so forth. +*/ +SQLITE_API int sqlite3_system_errno(sqlite3*); + +/* +** CAPI3REF: Database Snapshot +** KEYWORDS: {snapshot} {sqlite3_snapshot} +** +** An instance of the snapshot object records the state of a [WAL mode] +** database for some specific point in history. +** +** In [WAL mode], multiple [database connections] that are open on the +** same database file can each be reading a different historical version +** of the database file. When a [database connection] begins a read +** transaction, that connection sees an unchanging copy of the database +** as it existed for the point in time when the transaction first started. +** Subsequent changes to the database from other connections are not seen +** by the reader until a new read transaction is started. +** +** The sqlite3_snapshot object records state information about an historical +** version of the database file so that it is possible to later open a new read +** transaction that sees that historical version of the database rather than +** the most recent version. +*/ +typedef struct sqlite3_snapshot { + unsigned char hidden[48]; +} sqlite3_snapshot; + +/* +** CAPI3REF: Record A Database Snapshot +** CONSTRUCTOR: sqlite3_snapshot +** +** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a +** new [sqlite3_snapshot] object that records the current state of +** schema S in database connection D. ^On success, the +** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly +** created [sqlite3_snapshot] object into *P and returns SQLITE_OK. +** If there is not already a read-transaction open on schema S when +** this function is called, one is opened automatically. +** +** The following must be true for this function to succeed. If any of +** the following statements are false when sqlite3_snapshot_get() is +** called, SQLITE_ERROR is returned. The final value of *P is undefined +** in this case. +** +** <ul> +** <li> The database handle must not be in [autocommit mode]. +** +** <li> Schema S of [database connection] D must be a [WAL mode] database. +** +** <li> There must not be a write transaction open on schema S of database +** connection D. +** +** <li> One or more transactions must have been written to the current wal +** file since it was created on disk (by any connection). This means +** that a snapshot cannot be taken on a wal mode database with no wal +** file immediately after it is first opened. At least one transaction +** must be written to it first. +** </ul> +** +** This function may also return SQLITE_NOMEM. If it is called with the +** database handle in autocommit mode but fails for some other reason, +** whether or not a read transaction is opened on schema S is undefined. +** +** The [sqlite3_snapshot] object returned from a successful call to +** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()] +** to avoid a memory leak. +** +** The [sqlite3_snapshot_get()] interface is only available when the +** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( + sqlite3 *db, + const char *zSchema, + sqlite3_snapshot **ppSnapshot +); + +/* +** CAPI3REF: Start a read transaction on an historical snapshot +** METHOD: sqlite3_snapshot +** +** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read +** transaction or upgrades an existing one for schema S of +** [database connection] D such that the read transaction refers to +** historical [snapshot] P, rather than the most recent change to the +** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK +** on success or an appropriate [error code] if it fails. +** +** ^In order to succeed, the database connection must not be in +** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there +** is already a read transaction open on schema S, then the database handle +** must have no active statements (SELECT statements that have been passed +** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). +** SQLITE_ERROR is returned if either of these conditions is violated, or +** if schema S does not exist, or if the snapshot object is invalid. +** +** ^A call to sqlite3_snapshot_open() will fail to open if the specified +** snapshot has been overwritten by a [checkpoint]. In this case +** SQLITE_ERROR_SNAPSHOT is returned. +** +** If there is already a read transaction open when this function is +** invoked, then the same read transaction remains open (on the same +** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_ERROR_SNAPSHOT +** is returned. If another error code - for example SQLITE_PROTOCOL or an +** SQLITE_IOERR error code - is returned, then the final state of the +** read transaction is undefined. If SQLITE_OK is returned, then the +** read transaction is now open on database snapshot P. +** +** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the +** database connection D does not know that the database file for +** schema S is in [WAL mode]. A database connection might not know +** that the database file is in [WAL mode] if there has been no prior +** I/O on that database connection, or if the database entered [WAL mode] +** after the most recent I/O on the database connection.)^ +** (Hint: Run "[PRAGMA application_id]" against a newly opened +** database connection in order to make it ready to use snapshots.) +** +** The [sqlite3_snapshot_open()] interface is only available when the +** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( + sqlite3 *db, + const char *zSchema, + sqlite3_snapshot *pSnapshot +); + +/* +** CAPI3REF: Destroy a snapshot +** DESTRUCTOR: sqlite3_snapshot +** +** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P. +** The application must eventually free every [sqlite3_snapshot] object +** using this routine to avoid a memory leak. +** +** The [sqlite3_snapshot_free()] interface is only available when the +** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. +*/ +SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); + +/* +** CAPI3REF: Compare the ages of two snapshot handles. +** METHOD: sqlite3_snapshot +** +** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages +** of two valid snapshot handles. +** +** If the two snapshot handles are not associated with the same database +** file, the result of the comparison is undefined. +** +** Additionally, the result of the comparison is only valid if both of the +** snapshot handles were obtained by calling sqlite3_snapshot_get() since the +** last time the wal file was deleted. The wal file is deleted when the +** database is changed back to rollback mode or when the number of database +** clients drops to zero. If either snapshot handle was obtained before the +** wal file was last deleted, the value returned by this function +** is undefined. +** +** Otherwise, this API returns a negative value if P1 refers to an older +** snapshot than P2, zero if the two handles refer to the same database +** snapshot, and a positive value if P1 is a newer snapshot than P2. +** +** This interface is only available if SQLite is compiled with the +** [SQLITE_ENABLE_SNAPSHOT] option. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( + sqlite3_snapshot *p1, + sqlite3_snapshot *p2 +); + +/* +** CAPI3REF: Recover snapshots from a wal file +** METHOD: sqlite3_snapshot +** +** If a [WAL file] remains on disk after all database connections close +** (either through the use of the [SQLITE_FCNTL_PERSIST_WAL] [file control] +** or because the last process to have the database opened exited without +** calling [sqlite3_close()]) and a new connection is subsequently opened +** on that database and [WAL file], the [sqlite3_snapshot_open()] interface +** will only be able to open the last transaction added to the WAL file +** even though the WAL file contains other valid transactions. +** +** This function attempts to scan the WAL file associated with database zDb +** of database handle db and make all valid snapshots available to +** sqlite3_snapshot_open(). It is an error if there is already a read +** transaction open on the database, or if the database is not a WAL mode +** database. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +** +** This interface is only available if SQLite is compiled with the +** [SQLITE_ENABLE_SNAPSHOT] option. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); + +/* +** CAPI3REF: Serialize a database +** +** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory +** that is a serialization of the S database on [database connection] D. +** If P is not a NULL pointer, then the size of the database in bytes +** is written into *P. +** +** For an ordinary on-disk database file, the serialization is just a +** copy of the disk file. For an in-memory database or a "TEMP" database, +** the serialization is the same sequence of bytes which would be written +** to disk if that database where backed up to disk. +** +** The usual case is that sqlite3_serialize() copies the serialization of +** the database into memory obtained from [sqlite3_malloc64()] and returns +** a pointer to that memory. The caller is responsible for freeing the +** returned value to avoid a memory leak. However, if the F argument +** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations +** are made, and the sqlite3_serialize() function will return a pointer +** to the contiguous memory representation of the database that SQLite +** is currently using for that database, or NULL if the no such contiguous +** memory representation of the database exists. A contiguous memory +** representation of the database will usually only exist if there has +** been a prior call to [sqlite3_deserialize(D,S,...)] with the same +** values of D and S. +** The size of the database is written into *P even if the +** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy +** of the database exists. +** +** A call to sqlite3_serialize(D,S,P,F) might return NULL even if the +** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory +** allocation error occurs. +** +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. +*/ +SQLITE_API unsigned char *sqlite3_serialize( + sqlite3 *db, /* The database connection */ + const char *zSchema, /* Which DB to serialize. ex: "main", "temp", ... */ + sqlite3_int64 *piSize, /* Write size of the DB here, if not NULL */ + unsigned int mFlags /* Zero or more SQLITE_SERIALIZE_* flags */ +); + +/* +** CAPI3REF: Flags for sqlite3_serialize +** +** Zero or more of the following constants can be OR-ed together for +** the F argument to [sqlite3_serialize(D,S,P,F)]. +** +** SQLITE_SERIALIZE_NOCOPY means that [sqlite3_serialize()] will return +** a pointer to contiguous in-memory database that it is currently using, +** without making a copy of the database. If SQLite is not currently using +** a contiguous in-memory database, then this option causes +** [sqlite3_serialize()] to return a NULL pointer. SQLite will only be +** using a contiguous in-memory database if it has been initialized by a +** prior call to [sqlite3_deserialize()]. +*/ +#define SQLITE_SERIALIZE_NOCOPY 0x001 /* Do no memory allocations */ + +/* +** CAPI3REF: Deserialize a database +** +** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the +** [database connection] D to disconnect from database S and then +** reopen S as an in-memory database based on the serialization contained +** in P. The serialized database P is N bytes in size. M is the size of +** the buffer P, which might be larger than N. If M is larger than N, and +** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is +** permitted to add content to the in-memory database as long as the total +** size does not exceed M bytes. +** +** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will +** invoke sqlite3_free() on the serialization buffer when the database +** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then +** SQLite will try to increase the buffer size using sqlite3_realloc64() +** if writes on the database cause it to grow larger than M bytes. +** +** The sqlite3_deserialize() interface will fail with SQLITE_BUSY if the +** database is currently in a read transaction or is involved in a backup +** operation. +** +** It is not possible to deserialized into the TEMP database. If the +** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the +** function returns SQLITE_ERROR. +** +** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the +** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then +** [sqlite3_free()] is invoked on argument P prior to returning. +** +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. +*/ +SQLITE_API int sqlite3_deserialize( + sqlite3 *db, /* The database connection */ + const char *zSchema, /* Which DB to reopen with the deserialization */ + unsigned char *pData, /* The serialized database content */ + sqlite3_int64 szDb, /* Number bytes in the deserialization */ + sqlite3_int64 szBuf, /* Total size of buffer pData[] */ + unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */ +); + +/* +** CAPI3REF: Flags for sqlite3_deserialize() +** +** The following are allowed values for 6th argument (the F argument) to +** the [sqlite3_deserialize(D,S,P,N,M,F)] interface. +** +** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization +** in the P argument is held in memory obtained from [sqlite3_malloc64()] +** and that SQLite should take ownership of this memory and automatically +** free it when it has finished using it. Without this flag, the caller +** is responsible for freeing any dynamically allocated memory. +** +** The SQLITE_DESERIALIZE_RESIZEABLE flag means that SQLite is allowed to +** grow the size of the database using calls to [sqlite3_realloc64()]. This +** flag should only be used if SQLITE_DESERIALIZE_FREEONCLOSE is also used. +** Without this flag, the deserialized database cannot increase in size beyond +** the number of bytes specified by the M parameter. +** +** The SQLITE_DESERIALIZE_READONLY flag means that the deserialized database +** should be treated as read-only. +*/ +#define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */ +#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ +#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ + +/* +** Undo the hack that converts floating point types to integer for +** builds on processors without floating point support. +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# undef double +#endif + +#if defined(__wasi__) +# undef SQLITE_WASI +# define SQLITE_WASI 1 +# undef SQLITE_OMIT_WAL +# define SQLITE_OMIT_WAL 1/* because it requires shared memory APIs */ +# ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION +# endif +# ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +# endif +#endif + +#if 0 +} /* End of the 'extern "C"' block */ +#endif +#endif /* SQLITE3_H */ + +/******** Begin file sqlite3rtree.h *********/ +/* +** 2010 August 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +*/ + +#ifndef _SQLITE3RTREE_H_ +#define _SQLITE3RTREE_H_ + + +#if 0 +extern "C" { +#endif + +typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry; +typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info; + +/* The double-precision datatype used by RTree depends on the +** SQLITE_RTREE_INT_ONLY compile-time option. +*/ +#ifdef SQLITE_RTREE_INT_ONLY + typedef sqlite3_int64 sqlite3_rtree_dbl; +#else + typedef double sqlite3_rtree_dbl; +#endif + +/* +** Register a geometry callback named zGeom that can be used as part of an +** R-Tree geometry query as follows: +** +** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zGeom(... params ...) +*/ +SQLITE_API int sqlite3_rtree_geometry_callback( + sqlite3 *db, + const char *zGeom, + int (*xGeom)(sqlite3_rtree_geometry*, int, sqlite3_rtree_dbl*,int*), + void *pContext +); + + +/* +** A pointer to a structure of the following type is passed as the first +** argument to callbacks registered using rtree_geometry_callback(). +*/ +struct sqlite3_rtree_geometry { + void *pContext; /* Copy of pContext passed to s_r_g_c() */ + int nParam; /* Size of array aParam[] */ + sqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */ + void *pUser; /* Callback implementation user data */ + void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */ +}; + +/* +** Register a 2nd-generation geometry callback named zScore that can be +** used as part of an R-Tree geometry query as follows: +** +** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zQueryFunc(... params ...) +*/ +SQLITE_API int sqlite3_rtree_query_callback( + sqlite3 *db, + const char *zQueryFunc, + int (*xQueryFunc)(sqlite3_rtree_query_info*), + void *pContext, + void (*xDestructor)(void*) +); + + +/* +** A pointer to a structure of the following type is passed as the +** argument to scored geometry callback registered using +** sqlite3_rtree_query_callback(). +** +** Note that the first 5 fields of this structure are identical to +** sqlite3_rtree_geometry. This structure is a subclass of +** sqlite3_rtree_geometry. +*/ +struct sqlite3_rtree_query_info { + void *pContext; /* pContext from when function registered */ + int nParam; /* Number of function parameters */ + sqlite3_rtree_dbl *aParam; /* value of function parameters */ + void *pUser; /* callback can use this, if desired */ + void (*xDelUser)(void*); /* function to free pUser */ + sqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */ + unsigned int *anQueue; /* Number of pending entries in the queue */ + int nCoord; /* Number of coordinates */ + int iLevel; /* Level of current node or entry */ + int mxLevel; /* The largest iLevel value in the tree */ + sqlite3_int64 iRowid; /* Rowid for current entry */ + sqlite3_rtree_dbl rParentScore; /* Score of parent node */ + int eParentWithin; /* Visibility of parent node */ + int eWithin; /* OUT: Visibility */ + sqlite3_rtree_dbl rScore; /* OUT: Write the score here */ + /* The following fields are only available in 3.8.11 and later */ + sqlite3_value **apSqlParam; /* Original SQL values of parameters */ +}; + +/* +** Allowed values for sqlite3_rtree_query.eWithin and .eParentWithin. +*/ +#define NOT_WITHIN 0 /* Object completely outside of query region */ +#define PARTLY_WITHIN 1 /* Object partially overlaps query region */ +#define FULLY_WITHIN 2 /* Object fully contained within query region */ + + +#if 0 +} /* end of the 'extern "C"' block */ +#endif + +#endif /* ifndef _SQLITE3RTREE_H_ */ + +/******** End of sqlite3rtree.h *********/ +/******** Begin file sqlite3session.h *********/ + +#if !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) +#define __SQLITESESSION_H_ 1 + +/* +** Make sure we can call this stuff from C++. +*/ +#if 0 +extern "C" { +#endif + + +/* +** CAPI3REF: Session Object Handle +** +** An instance of this object is a [session] that can be used to +** record changes to a database. +*/ +typedef struct sqlite3_session sqlite3_session; + +/* +** CAPI3REF: Changeset Iterator Handle +** +** An instance of this object acts as a cursor for iterating +** over the elements of a [changeset] or [patchset]. +*/ +typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; + +/* +** CAPI3REF: Create A New Session Object +** CONSTRUCTOR: sqlite3_session +** +** Create a new session object attached to database handle db. If successful, +** a pointer to the new object is written to *ppSession and SQLITE_OK is +** returned. If an error occurs, *ppSession is set to NULL and an SQLite +** error code (e.g. SQLITE_NOMEM) is returned. +** +** It is possible to create multiple session objects attached to a single +** database handle. +** +** Session objects created using this function should be deleted using the +** [sqlite3session_delete()] function before the database handle that they +** are attached to is itself closed. If the database handle is closed before +** the session object is deleted, then the results of calling any session +** module function, including [sqlite3session_delete()] on the session object +** are undefined. +** +** Because the session module uses the [sqlite3_preupdate_hook()] API, it +** is not possible for an application to register a pre-update hook on a +** database handle that has one or more session objects attached. Nor is +** it possible to create a session object attached to a database handle for +** which a pre-update hook is already defined. The results of attempting +** either of these things are undefined. +** +** The session object will be used to create changesets for tables in +** database zDb, where zDb is either "main", or "temp", or the name of an +** attached database. It is not an error if database zDb is not attached +** to the database when the session object is created. +*/ +SQLITE_API int sqlite3session_create( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of db (e.g. "main") */ + sqlite3_session **ppSession /* OUT: New session object */ +); + +/* +** CAPI3REF: Delete A Session Object +** DESTRUCTOR: sqlite3_session +** +** Delete a session object previously allocated using +** [sqlite3session_create()]. Once a session object has been deleted, the +** results of attempting to use pSession with any other session module +** function are undefined. +** +** Session objects must be deleted before the database handle to which they +** are attached is closed. Refer to the documentation for +** [sqlite3session_create()] for details. +*/ +SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); + +/* +** CAPI3REF: Configure a Session Object +** METHOD: sqlite3_session +** +** This method is used to configure a session object after it has been +** created. At present the only valid values for the second parameter are +** [SQLITE_SESSION_OBJCONFIG_SIZE] and [SQLITE_SESSION_OBJCONFIG_ROWID]. +** +*/ +SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg); + +/* +** CAPI3REF: Options for sqlite3session_object_config +** +** The following values may passed as the the 2nd parameter to +** sqlite3session_object_config(). +** +** <dt>SQLITE_SESSION_OBJCONFIG_SIZE <dd> +** This option is used to set, clear or query the flag that enables +** the [sqlite3session_changeset_size()] API. Because it imposes some +** computational overhead, this API is disabled by default. Argument +** pArg must point to a value of type (int). If the value is initially +** 0, then the sqlite3session_changeset_size() API is disabled. If it +** is greater than 0, then the same API is enabled. Or, if the initial +** value is less than zero, no change is made. In all cases the (int) +** variable is set to 1 if the sqlite3session_changeset_size() API is +** enabled following the current call, or 0 otherwise. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +** +** <dt>SQLITE_SESSION_OBJCONFIG_ROWID <dd> +** This option is used to set, clear or query the flag that enables +** collection of data for tables with no explicit PRIMARY KEY. +** +** Normally, tables with no explicit PRIMARY KEY are simply ignored +** by the sessions module. However, if this flag is set, it behaves +** as if such tables have a column "_rowid_ INTEGER PRIMARY KEY" inserted +** as their leftmost columns. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +*/ +#define SQLITE_SESSION_OBJCONFIG_SIZE 1 +#define SQLITE_SESSION_OBJCONFIG_ROWID 2 + +/* +** CAPI3REF: Enable Or Disable A Session Object +** METHOD: sqlite3_session +** +** Enable or disable the recording of changes by a session object. When +** enabled, a session object records changes made to the database. When +** disabled - it does not. A newly created session object is enabled. +** Refer to the documentation for [sqlite3session_changeset()] for further +** details regarding how enabling and disabling a session object affects +** the eventual changesets. +** +** Passing zero to this function disables the session. Passing a value +** greater than zero enables it. Passing a value less than zero is a +** no-op, and may be used to query the current state of the session. +** +** The return value indicates the final state of the session object: 0 if +** the session is disabled, or 1 if it is enabled. +*/ +SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); + +/* +** CAPI3REF: Set Or Clear the Indirect Change Flag +** METHOD: sqlite3_session +** +** Each change recorded by a session object is marked as either direct or +** indirect. A change is marked as indirect if either: +** +** <ul> +** <li> The session object "indirect" flag is set when the change is +** made, or +** <li> The change is made by an SQL trigger or foreign key action +** instead of directly as a result of a users SQL statement. +** </ul> +** +** If a single row is affected by more than one operation within a session, +** then the change is considered indirect if all operations meet the criteria +** for an indirect change above, or direct otherwise. +** +** This function is used to set, clear or query the session object indirect +** flag. If the second argument passed to this function is zero, then the +** indirect flag is cleared. If it is greater than zero, the indirect flag +** is set. Passing a value less than zero does not modify the current value +** of the indirect flag, and may be used to query the current state of the +** indirect flag for the specified session object. +** +** The return value indicates the final state of the indirect flag: 0 if +** it is clear, or 1 if it is set. +*/ +SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); + +/* +** CAPI3REF: Attach A Table To A Session Object +** METHOD: sqlite3_session +** +** If argument zTab is not NULL, then it is the name of a table to attach +** to the session object passed as the first argument. All subsequent changes +** made to the table while the session object is enabled will be recorded. See +** documentation for [sqlite3session_changeset()] for further details. +** +** Or, if argument zTab is NULL, then changes are recorded for all tables +** in the database. If additional tables are added to the database (by +** executing "CREATE TABLE" statements) after this call is made, changes for +** the new tables are also recorded. +** +** Changes can only be recorded for tables that have a PRIMARY KEY explicitly +** defined as part of their CREATE TABLE statement. It does not matter if the +** PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) or not. The PRIMARY +** KEY may consist of a single column, or may be a composite key. +** +** It is not an error if the named table does not exist in the database. Nor +** is it an error if the named table does not have a PRIMARY KEY. However, +** no changes will be recorded in either of these scenarios. +** +** Changes are not recorded for individual rows that have NULL values stored +** in one or more of their PRIMARY KEY columns. +** +** SQLITE_OK is returned if the call completes without error. Or, if an error +** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. +** +** <h3>Special sqlite_stat1 Handling</h3> +** +** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to +** some of the rules above. In SQLite, the schema of sqlite_stat1 is: +** <pre> +** &nbsp; CREATE TABLE sqlite_stat1(tbl,idx,stat) +** </pre> +** +** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are +** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes +** are recorded for rows for which (idx IS NULL) is true. However, for such +** rows a zero-length blob (SQL value X'') is stored in the changeset or +** patchset instead of a NULL value. This allows such changesets to be +** manipulated by legacy implementations of sqlite3changeset_invert(), +** concat() and similar. +** +** The sqlite3changeset_apply() function automatically converts the +** zero-length blob back to a NULL value when updating the sqlite_stat1 +** table. However, if the application calls sqlite3changeset_new(), +** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset +** iterator directly (including on a changeset iterator passed to a +** conflict-handler callback) then the X'' value is returned. The application +** must translate X'' to NULL itself if required. +** +** Legacy (older than 3.22.0) versions of the sessions module cannot capture +** changes made to the sqlite_stat1 table. Legacy versions of the +** sqlite3changeset_apply() function silently ignore any modifications to the +** sqlite_stat1 table that are part of a changeset or patchset. +*/ +SQLITE_API int sqlite3session_attach( + sqlite3_session *pSession, /* Session object */ + const char *zTab /* Table name */ +); + +/* +** CAPI3REF: Set a table filter on a Session Object. +** METHOD: sqlite3_session +** +** The second argument (xFilter) is the "filter callback". For changes to rows +** in tables that are not attached to the Session object, the filter is called +** to determine whether changes to the table's rows should be tracked or not. +** If xFilter returns 0, changes are not tracked. Note that once a table is +** attached, xFilter will not be called again. +*/ +SQLITE_API void sqlite3session_table_filter( + sqlite3_session *pSession, /* Session object */ + int(*xFilter)( + void *pCtx, /* Copy of third arg to _filter_table() */ + const char *zTab /* Table name */ + ), + void *pCtx /* First argument passed to xFilter */ +); + +/* +** CAPI3REF: Generate A Changeset From A Session Object +** METHOD: sqlite3_session +** +** Obtain a changeset containing changes to the tables attached to the +** session object passed as the first argument. If successful, +** set *ppChangeset to point to a buffer containing the changeset +** and *pnChangeset to the size of the changeset in bytes before returning +** SQLITE_OK. If an error occurs, set both *ppChangeset and *pnChangeset to +** zero and return an SQLite error code. +** +** A changeset consists of zero or more INSERT, UPDATE and/or DELETE changes, +** each representing a change to a single row of an attached table. An INSERT +** change contains the values of each field of a new database row. A DELETE +** contains the original values of each field of a deleted database row. An +** UPDATE change contains the original values of each field of an updated +** database row along with the updated values for each updated non-primary-key +** column. It is not possible for an UPDATE change to represent a change that +** modifies the values of primary key columns. If such a change is made, it +** is represented in a changeset as a DELETE followed by an INSERT. +** +** Changes are not recorded for rows that have NULL values stored in one or +** more of their PRIMARY KEY columns. If such a row is inserted or deleted, +** no corresponding change is present in the changesets returned by this +** function. If an existing row with one or more NULL values stored in +** PRIMARY KEY columns is updated so that all PRIMARY KEY columns are non-NULL, +** only an INSERT is appears in the changeset. Similarly, if an existing row +** with non-NULL PRIMARY KEY values is updated so that one or more of its +** PRIMARY KEY columns are set to NULL, the resulting changeset contains a +** DELETE change only. +** +** The contents of a changeset may be traversed using an iterator created +** using the [sqlite3changeset_start()] API. A changeset may be applied to +** a database with a compatible schema using the [sqlite3changeset_apply()] +** API. +** +** Within a changeset generated by this function, all changes related to a +** single table are grouped together. In other words, when iterating through +** a changeset or when applying a changeset to a database, all changes related +** to a single table are processed before moving on to the next table. Tables +** are sorted in the same order in which they were attached (or auto-attached) +** to the sqlite3_session object. The order in which the changes related to +** a single table are stored is undefined. +** +** Following a successful call to this function, it is the responsibility of +** the caller to eventually free the buffer that *ppChangeset points to using +** [sqlite3_free()]. +** +** <h3>Changeset Generation</h3> +** +** Once a table has been attached to a session object, the session object +** records the primary key values of all new rows inserted into the table. +** It also records the original primary key and other column values of any +** deleted or updated rows. For each unique primary key value, data is only +** recorded once - the first time a row with said primary key is inserted, +** updated or deleted in the lifetime of the session. +** +** There is one exception to the previous paragraph: when a row is inserted, +** updated or deleted, if one or more of its primary key columns contain a +** NULL value, no record of the change is made. +** +** The session object therefore accumulates two types of records - those +** that consist of primary key values only (created when the user inserts +** a new record) and those that consist of the primary key values and the +** original values of other table columns (created when the users deletes +** or updates a record). +** +** When this function is called, the requested changeset is created using +** both the accumulated records and the current contents of the database +** file. Specifically: +** +** <ul> +** <li> For each record generated by an insert, the database is queried +** for a row with a matching primary key. If one is found, an INSERT +** change is added to the changeset. If no such row is found, no change +** is added to the changeset. +** +** <li> For each record generated by an update or delete, the database is +** queried for a row with a matching primary key. If such a row is +** found and one or more of the non-primary key fields have been +** modified from their original values, an UPDATE change is added to +** the changeset. Or, if no such row is found in the table, a DELETE +** change is added to the changeset. If there is a row with a matching +** primary key in the database, but all fields contain their original +** values, no change is added to the changeset. +** </ul> +** +** This means, amongst other things, that if a row is inserted and then later +** deleted while a session object is active, neither the insert nor the delete +** will be present in the changeset. Or if a row is deleted and then later a +** row with the same primary key values inserted while a session object is +** active, the resulting changeset will contain an UPDATE change instead of +** a DELETE and an INSERT. +** +** When a session object is disabled (see the [sqlite3session_enable()] API), +** it does not accumulate records when rows are inserted, updated or deleted. +** This may appear to have some counter-intuitive effects if a single row +** is written to more than once during a session. For example, if a row +** is inserted while a session object is enabled, then later deleted while +** the same session object is disabled, no INSERT record will appear in the +** changeset, even though the delete took place while the session was disabled. +** Or, if one field of a row is updated while a session is disabled, and +** another field of the same row is updated while the session is enabled, the +** resulting changeset will contain an UPDATE change that updates both fields. +*/ +SQLITE_API int sqlite3session_changeset( + sqlite3_session *pSession, /* Session object */ + int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ + void **ppChangeset /* OUT: Buffer containing changeset */ +); + +/* +** CAPI3REF: Return An Upper-limit For The Size Of The Changeset +** METHOD: sqlite3_session +** +** By default, this function always returns 0. For it to return +** a useful result, the sqlite3_session object must have been configured +** to enable this API using sqlite3session_object_config() with the +** SQLITE_SESSION_OBJCONFIG_SIZE verb. +** +** When enabled, this function returns an upper limit, in bytes, for the size +** of the changeset that might be produced if sqlite3session_changeset() were +** called. The final changeset size might be equal to or smaller than the +** size in bytes returned by this function. +*/ +SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession); + +/* +** CAPI3REF: Load The Difference Between Tables Into A Session +** METHOD: sqlite3_session +** +** If it is not already attached to the session object passed as the first +** argument, this function attaches table zTbl in the same manner as the +** [sqlite3session_attach()] function. If zTbl does not exist, or if it +** does not have a primary key, this function is a no-op (but does not return +** an error). +** +** Argument zFromDb must be the name of a database ("main", "temp" etc.) +** attached to the same database handle as the session object that contains +** a table compatible with the table attached to the session by this function. +** A table is considered compatible if it: +** +** <ul> +** <li> Has the same name, +** <li> Has the same set of columns declared in the same order, and +** <li> Has the same PRIMARY KEY definition. +** </ul> +** +** If the tables are not compatible, SQLITE_SCHEMA is returned. If the tables +** are compatible but do not have any PRIMARY KEY columns, it is not an error +** but no changes are added to the session object. As with other session +** APIs, tables without PRIMARY KEYs are simply ignored. +** +** This function adds a set of changes to the session object that could be +** used to update the table in database zFrom (call this the "from-table") +** so that its content is the same as the table attached to the session +** object (call this the "to-table"). Specifically: +** +** <ul> +** <li> For each row (primary key) that exists in the to-table but not in +** the from-table, an INSERT record is added to the session object. +** +** <li> For each row (primary key) that exists in the to-table but not in +** the from-table, a DELETE record is added to the session object. +** +** <li> For each row (primary key) that exists in both tables, but features +** different non-PK values in each, an UPDATE record is added to the +** session. +** </ul> +** +** To clarify, if this function is called and then a changeset constructed +** using [sqlite3session_changeset()], then after applying that changeset to +** database zFrom the contents of the two compatible tables would be +** identical. +** +** It an error if database zFrom does not exist or does not contain the +** required compatible table. +** +** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite +** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg +** may be set to point to a buffer containing an English language error +** message. It is the responsibility of the caller to free this buffer using +** sqlite3_free(). +*/ +SQLITE_API int sqlite3session_diff( + sqlite3_session *pSession, + const char *zFromDb, + const char *zTbl, + char **pzErrMsg +); + + +/* +** CAPI3REF: Generate A Patchset From A Session Object +** METHOD: sqlite3_session +** +** The differences between a patchset and a changeset are that: +** +** <ul> +** <li> DELETE records consist of the primary key fields only. The +** original values of other fields are omitted. +** <li> The original values of any modified fields are omitted from +** UPDATE records. +** </ul> +** +** A patchset blob may be used with up to date versions of all +** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(), +** which returns SQLITE_CORRUPT if it is passed a patchset. Similarly, +** attempting to use a patchset blob with old versions of the +** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error. +** +** Because the non-primary key "old.*" fields are omitted, no +** SQLITE_CHANGESET_DATA conflicts can be detected or reported if a patchset +** is passed to the sqlite3changeset_apply() API. Other conflict types work +** in the same way as for changesets. +** +** Changes within a patchset are ordered in the same way as for changesets +** generated by the sqlite3session_changeset() function (i.e. all changes for +** a single table are grouped together, tables appear in the order in which +** they were attached to the session object). +*/ +SQLITE_API int sqlite3session_patchset( + sqlite3_session *pSession, /* Session object */ + int *pnPatchset, /* OUT: Size of buffer at *ppPatchset */ + void **ppPatchset /* OUT: Buffer containing patchset */ +); + +/* +** CAPI3REF: Test if a changeset has recorded any changes. +** +** Return non-zero if no changes to attached tables have been recorded by +** the session object passed as the first argument. Otherwise, if one or +** more changes have been recorded, return zero. +** +** Even if this function returns zero, it is possible that calling +** [sqlite3session_changeset()] on the session handle may still return a +** changeset that contains no changes. This can happen when a row in +** an attached table is modified and then later on the original values +** are restored. However, if this function returns non-zero, then it is +** guaranteed that a call to sqlite3session_changeset() will return a +** changeset containing zero changes. +*/ +SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); + +/* +** CAPI3REF: Query for the amount of heap memory used by a session object. +** +** This API returns the total amount of heap memory in bytes currently +** used by the session object passed as the only argument. +*/ +SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession); + +/* +** CAPI3REF: Create An Iterator To Traverse A Changeset +** CONSTRUCTOR: sqlite3_changeset_iter +** +** Create an iterator used to iterate through the contents of a changeset. +** If successful, *pp is set to point to the iterator handle and SQLITE_OK +** is returned. Otherwise, if an error occurs, *pp is set to zero and an +** SQLite error code is returned. +** +** The following functions can be used to advance and query a changeset +** iterator created by this function: +** +** <ul> +** <li> [sqlite3changeset_next()] +** <li> [sqlite3changeset_op()] +** <li> [sqlite3changeset_new()] +** <li> [sqlite3changeset_old()] +** </ul> +** +** It is the responsibility of the caller to eventually destroy the iterator +** by passing it to [sqlite3changeset_finalize()]. The buffer containing the +** changeset (pChangeset) must remain valid until after the iterator is +** destroyed. +** +** Assuming the changeset blob was created by one of the +** [sqlite3session_changeset()], [sqlite3changeset_concat()] or +** [sqlite3changeset_invert()] functions, all changes within the changeset +** that apply to a single table are grouped together. This means that when +** an application iterates through a changeset using an iterator created by +** this function, all changes that relate to a single table are visited +** consecutively. There is no chance that the iterator will visit a change +** the applies to table X, then one for table Y, and then later on visit +** another change for table X. +** +** The behavior of sqlite3changeset_start_v2() and its streaming equivalent +** may be modified by passing a combination of +** [SQLITE_CHANGESETSTART_INVERT | supported flags] as the 4th parameter. +** +** Note that the sqlite3changeset_start_v2() API is still <b>experimental</b> +** and therefore subject to change. +*/ +SQLITE_API int sqlite3changeset_start( + sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ + int nChangeset, /* Size of changeset blob in bytes */ + void *pChangeset /* Pointer to blob containing changeset */ +); +SQLITE_API int sqlite3changeset_start_v2( + sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ + int nChangeset, /* Size of changeset blob in bytes */ + void *pChangeset, /* Pointer to blob containing changeset */ + int flags /* SESSION_CHANGESETSTART_* flags */ +); + +/* +** CAPI3REF: Flags for sqlite3changeset_start_v2 +** +** The following flags may passed via the 4th parameter to +** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]: +** +** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd> +** Invert the changeset while iterating through it. This is equivalent to +** inverting a changeset using sqlite3changeset_invert() before applying it. +** It is an error to specify this flag with a patchset. +*/ +#define SQLITE_CHANGESETSTART_INVERT 0x0002 + + +/* +** CAPI3REF: Advance A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** This function may only be used with iterators created by the function +** [sqlite3changeset_start()]. If it is called on an iterator passed to +** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE +** is returned and the call has no effect. +** +** Immediately after an iterator is created by sqlite3changeset_start(), it +** does not point to any change in the changeset. Assuming the changeset +** is not empty, the first call to this function advances the iterator to +** point to the first change in the changeset. Each subsequent call advances +** the iterator to point to the next change in the changeset (if any). If +** no error occurs and the iterator points to a valid change after a call +** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned. +** Otherwise, if all changes in the changeset have already been visited, +** SQLITE_DONE is returned. +** +** If an error occurs, an SQLite error code is returned. Possible error +** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or +** SQLITE_NOMEM. +*/ +SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter); + +/* +** CAPI3REF: Obtain The Current Operation From A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** The pIter argument passed to this function may either be an iterator +** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator +** created by [sqlite3changeset_start()]. In the latter case, the most recent +** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this +** is not the case, this function returns [SQLITE_MISUSE]. +** +** Arguments pOp, pnCol and pzTab may not be NULL. Upon return, three +** outputs are set through these pointers: +** +** *pOp is set to one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], +** depending on the type of change that the iterator currently points to; +** +** *pnCol is set to the number of columns in the table affected by the change; and +** +** *pzTab is set to point to a nul-terminated utf-8 encoded string containing +** the name of the table affected by the current change. The buffer remains +** valid until either sqlite3changeset_next() is called on the iterator +** or until the conflict-handler function returns. +** +** If pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change +** is an indirect change, or false (0) otherwise. See the documentation for +** [sqlite3session_indirect()] for a description of direct and indirect +** changes. +** +** If no error occurs, SQLITE_OK is returned. If an error does occur, an +** SQLite error code is returned. The values of the output variables may not +** be trusted in this case. +*/ +SQLITE_API int sqlite3changeset_op( + sqlite3_changeset_iter *pIter, /* Iterator object */ + const char **pzTab, /* OUT: Pointer to table name */ + int *pnCol, /* OUT: Number of columns in table */ + int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ + int *pbIndirect /* OUT: True for an 'indirect' change */ +); + +/* +** CAPI3REF: Obtain The Primary Key Definition Of A Table +** METHOD: sqlite3_changeset_iter +** +** For each modified table, a changeset includes the following: +** +** <ul> +** <li> The number of columns in the table, and +** <li> Which of those columns make up the tables PRIMARY KEY. +** </ul> +** +** This function is used to find which columns comprise the PRIMARY KEY of +** the table modified by the change that iterator pIter currently points to. +** If successful, *pabPK is set to point to an array of nCol entries, where +** nCol is the number of columns in the table. Elements of *pabPK are set to +** 0x01 if the corresponding column is part of the tables primary key, or +** 0x00 if it is not. +** +** If argument pnCol is not NULL, then *pnCol is set to the number of columns +** in the table. +** +** If this function is called when the iterator does not point to a valid +** entry, SQLITE_MISUSE is returned and the output variables zeroed. Otherwise, +** SQLITE_OK is returned and the output variables populated as described +** above. +*/ +SQLITE_API int sqlite3changeset_pk( + sqlite3_changeset_iter *pIter, /* Iterator object */ + unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */ + int *pnCol /* OUT: Number of entries in output array */ +); + +/* +** CAPI3REF: Obtain old.* Values From A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** The pIter argument passed to this function may either be an iterator +** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator +** created by [sqlite3changeset_start()]. In the latter case, the most recent +** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. +** Furthermore, it may only be called if the type of change that the iterator +** currently points to is either [SQLITE_DELETE] or [SQLITE_UPDATE]. Otherwise, +** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [SQLITE_RANGE] is returned and *ppValue is set to NULL. +** +** If successful, this function sets *ppValue to point to a protected +** sqlite3_value object containing the iVal'th value from the vector of +** original row values stored as part of the UPDATE or DELETE change and +** returns SQLITE_OK. The name of the function comes from the fact that this +** is similar to the "old.*" columns available to update or delete triggers. +** +** If some other error occurs (e.g. an OOM condition), an SQLite error code +** is returned and *ppValue is set to NULL. +*/ +SQLITE_API int sqlite3changeset_old( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */ +); + +/* +** CAPI3REF: Obtain new.* Values From A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** The pIter argument passed to this function may either be an iterator +** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator +** created by [sqlite3changeset_start()]. In the latter case, the most recent +** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. +** Furthermore, it may only be called if the type of change that the iterator +** currently points to is either [SQLITE_UPDATE] or [SQLITE_INSERT]. Otherwise, +** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [SQLITE_RANGE] is returned and *ppValue is set to NULL. +** +** If successful, this function sets *ppValue to point to a protected +** sqlite3_value object containing the iVal'th value from the vector of +** new row values stored as part of the UPDATE or INSERT change and +** returns SQLITE_OK. If the change is an UPDATE and does not include +** a new value for the requested column, *ppValue is set to NULL and +** SQLITE_OK returned. The name of the function comes from the fact that +** this is similar to the "new.*" columns available to update or delete +** triggers. +** +** If some other error occurs (e.g. an OOM condition), an SQLite error code +** is returned and *ppValue is set to NULL. +*/ +SQLITE_API int sqlite3changeset_new( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */ +); + +/* +** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** This function should only be used with iterator objects passed to a +** conflict-handler callback by [sqlite3changeset_apply()] with either +** [SQLITE_CHANGESET_DATA] or [SQLITE_CHANGESET_CONFLICT]. If this function +** is called on any other iterator, [SQLITE_MISUSE] is returned and *ppValue +** is set to NULL. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [SQLITE_RANGE] is returned and *ppValue is set to NULL. +** +** If successful, this function sets *ppValue to point to a protected +** sqlite3_value object containing the iVal'th value from the +** "conflicting row" associated with the current conflict-handler callback +** and returns SQLITE_OK. +** +** If some other error occurs (e.g. an OOM condition), an SQLite error code +** is returned and *ppValue is set to NULL. +*/ +SQLITE_API int sqlite3changeset_conflict( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: Value from conflicting row */ +); + +/* +** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations +** METHOD: sqlite3_changeset_iter +** +** This function may only be called with an iterator passed to an +** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case +** it sets the output variable to the total number of known foreign key +** violations in the destination database and returns SQLITE_OK. +** +** In all other cases this function returns SQLITE_MISUSE. +*/ +SQLITE_API int sqlite3changeset_fk_conflicts( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int *pnOut /* OUT: Number of FK violations */ +); + + +/* +** CAPI3REF: Finalize A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** This function is used to finalize an iterator allocated with +** [sqlite3changeset_start()]. +** +** This function should only be called on iterators created using the +** [sqlite3changeset_start()] function. If an application calls this +** function with an iterator passed to a conflict-handler by +** [sqlite3changeset_apply()], [SQLITE_MISUSE] is immediately returned and the +** call has no effect. +** +** If an error was encountered within a call to an sqlite3changeset_xxx() +** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an +** [SQLITE_NOMEM] in [sqlite3changeset_new()]) then an error code corresponding +** to that error is returned by this function. Otherwise, SQLITE_OK is +** returned. This is to allow the following pattern (pseudo-code): +** +** <pre> +** sqlite3changeset_start(); +** while( SQLITE_ROW==sqlite3changeset_next() ){ +** // Do something with change. +** } +** rc = sqlite3changeset_finalize(); +** if( rc!=SQLITE_OK ){ +** // An error has occurred +** } +** </pre> +*/ +SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); + +/* +** CAPI3REF: Invert A Changeset +** +** This function is used to "invert" a changeset object. Applying an inverted +** changeset to a database reverses the effects of applying the uninverted +** changeset. Specifically: +** +** <ul> +** <li> Each DELETE change is changed to an INSERT, and +** <li> Each INSERT change is changed to a DELETE, and +** <li> For each UPDATE change, the old.* and new.* values are exchanged. +** </ul> +** +** This function does not change the order in which changes appear within +** the changeset. It merely reverses the sense of each individual change. +** +** If successful, a pointer to a buffer containing the inverted changeset +** is stored in *ppOut, the size of the same buffer is stored in *pnOut, and +** SQLITE_OK is returned. If an error occurs, both *pnOut and *ppOut are +** zeroed and an SQLite error code returned. +** +** It is the responsibility of the caller to eventually call sqlite3_free() +** on the *ppOut pointer to free the buffer allocation following a successful +** call to this function. +** +** WARNING/TODO: This function currently assumes that the input is a valid +** changeset. If it is not, the results are undefined. +*/ +SQLITE_API int sqlite3changeset_invert( + int nIn, const void *pIn, /* Input changeset */ + int *pnOut, void **ppOut /* OUT: Inverse of input */ +); + +/* +** CAPI3REF: Concatenate Two Changeset Objects +** +** This function is used to concatenate two changesets, A and B, into a +** single changeset. The result is a changeset equivalent to applying +** changeset A followed by changeset B. +** +** This function combines the two input changesets using an +** sqlite3_changegroup object. Calling it produces similar results as the +** following code fragment: +** +** <pre> +** sqlite3_changegroup *pGrp; +** rc = sqlite3_changegroup_new(&pGrp); +** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nA, pA); +** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nB, pB); +** if( rc==SQLITE_OK ){ +** rc = sqlite3changegroup_output(pGrp, pnOut, ppOut); +** }else{ +** *ppOut = 0; +** *pnOut = 0; +** } +** </pre> +** +** Refer to the sqlite3_changegroup documentation below for details. +*/ +SQLITE_API int sqlite3changeset_concat( + int nA, /* Number of bytes in buffer pA */ + void *pA, /* Pointer to buffer containing changeset A */ + int nB, /* Number of bytes in buffer pB */ + void *pB, /* Pointer to buffer containing changeset B */ + int *pnOut, /* OUT: Number of bytes in output changeset */ + void **ppOut /* OUT: Buffer containing output changeset */ +); + + +/* +** CAPI3REF: Changegroup Handle +** +** A changegroup is an object used to combine two or more +** [changesets] or [patchsets] +*/ +typedef struct sqlite3_changegroup sqlite3_changegroup; + +/* +** CAPI3REF: Create A New Changegroup Object +** CONSTRUCTOR: sqlite3_changegroup +** +** An sqlite3_changegroup object is used to combine two or more changesets +** (or patchsets) into a single changeset (or patchset). A single changegroup +** object may combine changesets or patchsets, but not both. The output is +** always in the same format as the input. +** +** If successful, this function returns SQLITE_OK and populates (*pp) with +** a pointer to a new sqlite3_changegroup object before returning. The caller +** should eventually free the returned object using a call to +** sqlite3changegroup_delete(). If an error occurs, an SQLite error code +** (i.e. SQLITE_NOMEM) is returned and *pp is set to NULL. +** +** The usual usage pattern for an sqlite3_changegroup object is as follows: +** +** <ul> +** <li> It is created using a call to sqlite3changegroup_new(). +** +** <li> Zero or more changesets (or patchsets) are added to the object +** by calling sqlite3changegroup_add(). +** +** <li> The result of combining all input changesets together is obtained +** by the application via a call to sqlite3changegroup_output(). +** +** <li> The object is deleted using a call to sqlite3changegroup_delete(). +** </ul> +** +** Any number of calls to add() and output() may be made between the calls to +** new() and delete(), and in any order. +** +** As well as the regular sqlite3changegroup_add() and +** sqlite3changegroup_output() functions, also available are the streaming +** versions sqlite3changegroup_add_strm() and sqlite3changegroup_output_strm(). +*/ +SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); + +/* +** CAPI3REF: Add A Changeset To A Changegroup +** METHOD: sqlite3_changegroup +** +** Add all changes within the changeset (or patchset) in buffer pData (size +** nData bytes) to the changegroup. +** +** If the buffer contains a patchset, then all prior calls to this function +** on the same changegroup object must also have specified patchsets. Or, if +** the buffer contains a changeset, so must have the earlier calls to this +** function. Otherwise, SQLITE_ERROR is returned and no changes are added +** to the changegroup. +** +** Rows within the changeset and changegroup are identified by the values in +** their PRIMARY KEY columns. A change in the changeset is considered to +** apply to the same row as a change already present in the changegroup if +** the two rows have the same primary key. +** +** Changes to rows that do not already appear in the changegroup are +** simply copied into it. Or, if both the new changeset and the changegroup +** contain changes that apply to a single row, the final contents of the +** changegroup depends on the type of each change, as follows: +** +** <table border=1 style="margin-left:8ex;margin-right:8ex"> +** <tr><th style="white-space:pre">Existing Change </th> +** <th style="white-space:pre">New Change </th> +** <th>Output Change +** <tr><td>INSERT <td>INSERT <td> +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +** <tr><td>INSERT <td>UPDATE <td> +** The INSERT change remains in the changegroup. The values in the +** INSERT change are modified as if the row was inserted by the +** existing change and then updated according to the new change. +** <tr><td>INSERT <td>DELETE <td> +** The existing INSERT is removed from the changegroup. The DELETE is +** not added. +** <tr><td>UPDATE <td>INSERT <td> +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +** <tr><td>UPDATE <td>UPDATE <td> +** The existing UPDATE remains within the changegroup. It is amended +** so that the accompanying values are as if the row was updated once +** by the existing change and then again by the new change. +** <tr><td>UPDATE <td>DELETE <td> +** The existing UPDATE is replaced by the new DELETE within the +** changegroup. +** <tr><td>DELETE <td>INSERT <td> +** If one or more of the column values in the row inserted by the +** new change differ from those in the row deleted by the existing +** change, the existing DELETE is replaced by an UPDATE within the +** changegroup. Otherwise, if the inserted row is exactly the same +** as the deleted row, the existing DELETE is simply discarded. +** <tr><td>DELETE <td>UPDATE <td> +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +** <tr><td>DELETE <td>DELETE <td> +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +** </table> +** +** If the new changeset contains changes to a table that is already present +** in the changegroup, then the number of columns and the position of the +** primary key columns for the table must be consistent. If this is not the +** case, this function fails with SQLITE_SCHEMA. If the input changeset +** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is +** returned. Or, if an out-of-memory condition occurs during processing, this +** function returns SQLITE_NOMEM. In all cases, if an error occurs the state +** of the final contents of the changegroup is undefined. +** +** If no error occurs, SQLITE_OK is returned. +*/ +SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); + +/* +** CAPI3REF: Obtain A Composite Changeset From A Changegroup +** METHOD: sqlite3_changegroup +** +** Obtain a buffer containing a changeset (or patchset) representing the +** current contents of the changegroup. If the inputs to the changegroup +** were themselves changesets, the output is a changeset. Or, if the +** inputs were patchsets, the output is also a patchset. +** +** As with the output of the sqlite3session_changeset() and +** sqlite3session_patchset() functions, all changes related to a single +** table are grouped together in the output of this function. Tables appear +** in the same order as for the very first changeset added to the changegroup. +** If the second or subsequent changesets added to the changegroup contain +** changes for tables that do not appear in the first changeset, they are +** appended onto the end of the output changeset, again in the order in +** which they are first encountered. +** +** If an error occurs, an SQLite error code is returned and the output +** variables (*pnData) and (*ppData) are set to 0. Otherwise, SQLITE_OK +** is returned and the output variables are set to the size of and a +** pointer to the output buffer, respectively. In this case it is the +** responsibility of the caller to eventually free the buffer using a +** call to sqlite3_free(). +*/ +SQLITE_API int sqlite3changegroup_output( + sqlite3_changegroup*, + int *pnData, /* OUT: Size of output buffer in bytes */ + void **ppData /* OUT: Pointer to output buffer */ +); + +/* +** CAPI3REF: Delete A Changegroup Object +** DESTRUCTOR: sqlite3_changegroup +*/ +SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); + +/* +** CAPI3REF: Apply A Changeset To A Database +** +** Apply a changeset or patchset to a database. These functions attempt to +** update the "main" database attached to handle db with the changes found in +** the changeset passed via the second and third arguments. +** +** The fourth argument (xFilter) passed to these functions is the "filter +** callback". If it is not NULL, then for each table affected by at least one +** change in the changeset, the filter callback is invoked with +** the table name as the second argument, and a copy of the context pointer +** passed as the sixth argument as the first. If the "filter callback" +** returns zero, then no attempt is made to apply any changes to the table. +** Otherwise, if the return value is non-zero or the xFilter argument to +** is NULL, all changes related to the table are attempted. +** +** For each table that is not excluded by the filter callback, this function +** tests that the target database contains a compatible table. A table is +** considered compatible if all of the following are true: +** +** <ul> +** <li> The table has the same name as the name recorded in the +** changeset, and +** <li> The table has at least as many columns as recorded in the +** changeset, and +** <li> The table has primary key columns in the same position as +** recorded in the changeset. +** </ul> +** +** If there is no compatible table, it is not an error, but none of the +** changes associated with the table are applied. A warning message is issued +** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most +** one such warning is issued for each table in the changeset. +** +** For each change for which there is a compatible table, an attempt is made +** to modify the table contents according to the UPDATE, INSERT or DELETE +** change. If a change cannot be applied cleanly, the conflict handler +** function passed as the fifth argument to sqlite3changeset_apply() may be +** invoked. A description of exactly when the conflict handler is invoked for +** each type of change is below. +** +** Unlike the xFilter argument, xConflict may not be passed NULL. The results +** of passing anything other than a valid function pointer as the xConflict +** argument are undefined. +** +** Each time the conflict handler function is invoked, it must return one +** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or +** [SQLITE_CHANGESET_REPLACE]. SQLITE_CHANGESET_REPLACE may only be returned +** if the second argument passed to the conflict handler is either +** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler +** returns an illegal value, any changes already made are rolled back and +** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different +** actions are taken by sqlite3changeset_apply() depending on the value +** returned by each invocation of the conflict-handler function. Refer to +** the documentation for the three +** [SQLITE_CHANGESET_OMIT|available return values] for details. +** +** <dl> +** <dt>DELETE Changes<dd> +** For each DELETE change, the function checks if the target database +** contains a row with the same primary key value (or values) as the +** original row values stored in the changeset. If it does, and the values +** stored in all non-primary key columns also match the values stored in +** the changeset the row is deleted from the target database. +** +** If a row with matching primary key values is found, but one or more of +** the non-primary key fields contains a value different from the original +** row value stored in the changeset, the conflict-handler function is +** invoked with [SQLITE_CHANGESET_DATA] as the second argument. If the +** database table has more columns than are recorded in the changeset, +** only the values of those non-primary key fields are compared against +** the current database contents - any trailing database table columns +** are ignored. +** +** If no row with matching primary key values is found in the database, +** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] +** passed as the second argument. +** +** If the DELETE operation is attempted, but SQLite returns SQLITE_CONSTRAINT +** (which can only happen if a foreign key constraint is violated), the +** conflict-handler function is invoked with [SQLITE_CHANGESET_CONSTRAINT] +** passed as the second argument. This includes the case where the DELETE +** operation is attempted because an earlier call to the conflict handler +** function returned [SQLITE_CHANGESET_REPLACE]. +** +** <dt>INSERT Changes<dd> +** For each INSERT change, an attempt is made to insert the new row into +** the database. If the changeset row contains fewer fields than the +** database table, the trailing fields are populated with their default +** values. +** +** If the attempt to insert the row fails because the database already +** contains a row with the same primary key values, the conflict handler +** function is invoked with the second argument set to +** [SQLITE_CHANGESET_CONFLICT]. +** +** If the attempt to insert the row fails because of some other constraint +** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is +** invoked with the second argument set to [SQLITE_CHANGESET_CONSTRAINT]. +** This includes the case where the INSERT operation is re-attempted because +** an earlier call to the conflict handler function returned +** [SQLITE_CHANGESET_REPLACE]. +** +** <dt>UPDATE Changes<dd> +** For each UPDATE change, the function checks if the target database +** contains a row with the same primary key value (or values) as the +** original row values stored in the changeset. If it does, and the values +** stored in all modified non-primary key columns also match the values +** stored in the changeset the row is updated within the target database. +** +** If a row with matching primary key values is found, but one or more of +** the modified non-primary key fields contains a value different from an +** original row value stored in the changeset, the conflict-handler function +** is invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since +** UPDATE changes only contain values for non-primary key fields that are +** to be modified, only those fields need to match the original values to +** avoid the SQLITE_CHANGESET_DATA conflict-handler callback. +** +** If no row with matching primary key values is found in the database, +** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] +** passed as the second argument. +** +** If the UPDATE operation is attempted, but SQLite returns +** SQLITE_CONSTRAINT, the conflict-handler function is invoked with +** [SQLITE_CHANGESET_CONSTRAINT] passed as the second argument. +** This includes the case where the UPDATE operation is attempted after +** an earlier call to the conflict handler function returned +** [SQLITE_CHANGESET_REPLACE]. +** </dl> +** +** It is safe to execute SQL statements, including those that write to the +** table that the callback related to, from within the xConflict callback. +** This can be used to further customize the application's conflict +** resolution strategy. +** +** All changes made by these functions are enclosed in a savepoint transaction. +** If any other error (aside from a constraint failure when attempting to +** write to the target database) occurs, then the savepoint transaction is +** rolled back, restoring the target database to its original state, and an +** SQLite error code returned. +** +** If the output parameters (ppRebase) and (pnRebase) are non-NULL and +** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() +** may set (*ppRebase) to point to a "rebase" that may be used with the +** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) +** is set to the size of the buffer in bytes. It is the responsibility of the +** caller to eventually free any such buffer using sqlite3_free(). The buffer +** is only allocated and populated if one or more conflicts were encountered +** while applying the patchset. See comments surrounding the sqlite3_rebaser +** APIs for further details. +** +** The behavior of sqlite3changeset_apply_v2() and its streaming equivalent +** may be modified by passing a combination of +** [SQLITE_CHANGESETAPPLY_NOSAVEPOINT | supported flags] as the 9th parameter. +** +** Note that the sqlite3changeset_apply_v2() API is still <b>experimental</b> +** and therefore subject to change. +*/ +SQLITE_API int sqlite3changeset_apply( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +); +SQLITE_API int sqlite3changeset_apply_v2( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, /* OUT: Rebase data */ + int flags /* SESSION_CHANGESETAPPLY_* flags */ +); + +/* +** CAPI3REF: Flags for sqlite3changeset_apply_v2 +** +** The following flags may passed via the 9th parameter to +** [sqlite3changeset_apply_v2] and [sqlite3changeset_apply_v2_strm]: +** +** <dl> +** <dt>SQLITE_CHANGESETAPPLY_NOSAVEPOINT <dd> +** Usually, the sessions module encloses all operations performed by +** a single call to apply_v2() or apply_v2_strm() in a [SAVEPOINT]. The +** SAVEPOINT is committed if the changeset or patchset is successfully +** applied, or rolled back if an error occurs. Specifying this flag +** causes the sessions module to omit this savepoint. In this case, if the +** caller has an open transaction or savepoint when apply_v2() is called, +** it may revert the partially applied changeset by rolling it back. +** +** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd> +** Invert the changeset before applying it. This is equivalent to inverting +** a changeset using sqlite3changeset_invert() before applying it. It is +** an error to specify this flag with a patchset. +** +** <dt>SQLITE_CHANGESETAPPLY_IGNORENOOP <dd> +** Do not invoke the conflict handler callback for any changes that +** would not actually modify the database even if they were applied. +** Specifically, this means that the conflict handler is not invoked +** for: +** <ul> +** <li>a delete change if the row being deleted cannot be found, +** <li>an update change if the modified fields are already set to +** their new values in the conflicting row, or +** <li>an insert change if all fields of the conflicting row match +** the row being inserted. +** </ul> +*/ +#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 +#define SQLITE_CHANGESETAPPLY_INVERT 0x0002 +#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 + +/* +** CAPI3REF: Constants Passed To The Conflict Handler +** +** Values that may be passed as the second argument to a conflict-handler. +** +** <dl> +** <dt>SQLITE_CHANGESET_DATA<dd> +** The conflict handler is invoked with CHANGESET_DATA as the second argument +** when processing a DELETE or UPDATE change if a row with the required +** PRIMARY KEY fields is present in the database, but one or more other +** (non primary-key) fields modified by the update do not contain the +** expected "before" values. +** +** The conflicting row, in this case, is the database row with the matching +** primary key. +** +** <dt>SQLITE_CHANGESET_NOTFOUND<dd> +** The conflict handler is invoked with CHANGESET_NOTFOUND as the second +** argument when processing a DELETE or UPDATE change if a row with the +** required PRIMARY KEY fields is not present in the database. +** +** There is no conflicting row in this case. The results of invoking the +** sqlite3changeset_conflict() API are undefined. +** +** <dt>SQLITE_CHANGESET_CONFLICT<dd> +** CHANGESET_CONFLICT is passed as the second argument to the conflict +** handler while processing an INSERT change if the operation would result +** in duplicate primary key values. +** +** The conflicting row in this case is the database row with the matching +** primary key. +** +** <dt>SQLITE_CHANGESET_FOREIGN_KEY<dd> +** If foreign key handling is enabled, and applying a changeset leaves the +** database in a state containing foreign key violations, the conflict +** handler is invoked with CHANGESET_FOREIGN_KEY as the second argument +** exactly once before the changeset is committed. If the conflict handler +** returns CHANGESET_OMIT, the changes, including those that caused the +** foreign key constraint violation, are committed. Or, if it returns +** CHANGESET_ABORT, the changeset is rolled back. +** +** No current or conflicting row information is provided. The only function +** it is possible to call on the supplied sqlite3_changeset_iter handle +** is sqlite3changeset_fk_conflicts(). +** +** <dt>SQLITE_CHANGESET_CONSTRAINT<dd> +** If any other constraint violation occurs while applying a change (i.e. +** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is +** invoked with CHANGESET_CONSTRAINT as the second argument. +** +** There is no conflicting row in this case. The results of invoking the +** sqlite3changeset_conflict() API are undefined. +** +** </dl> +*/ +#define SQLITE_CHANGESET_DATA 1 +#define SQLITE_CHANGESET_NOTFOUND 2 +#define SQLITE_CHANGESET_CONFLICT 3 +#define SQLITE_CHANGESET_CONSTRAINT 4 +#define SQLITE_CHANGESET_FOREIGN_KEY 5 + +/* +** CAPI3REF: Constants Returned By The Conflict Handler +** +** A conflict handler callback must return one of the following three values. +** +** <dl> +** <dt>SQLITE_CHANGESET_OMIT<dd> +** If a conflict handler returns this value no special action is taken. The +** change that caused the conflict is not applied. The session module +** continues to the next change in the changeset. +** +** <dt>SQLITE_CHANGESET_REPLACE<dd> +** This value may only be returned if the second argument to the conflict +** handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this +** is not the case, any changes applied so far are rolled back and the +** call to sqlite3changeset_apply() returns SQLITE_MISUSE. +** +** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict +** handler, then the conflicting row is either updated or deleted, depending +** on the type of change. +** +** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_CONFLICT conflict +** handler, then the conflicting row is removed from the database and a +** second attempt to apply the change is made. If this second attempt fails, +** the original row is restored to the database before continuing. +** +** <dt>SQLITE_CHANGESET_ABORT<dd> +** If this value is returned, any changes applied so far are rolled back +** and the call to sqlite3changeset_apply() returns SQLITE_ABORT. +** </dl> +*/ +#define SQLITE_CHANGESET_OMIT 0 +#define SQLITE_CHANGESET_REPLACE 1 +#define SQLITE_CHANGESET_ABORT 2 + +/* +** CAPI3REF: Rebasing changesets +** EXPERIMENTAL +** +** Suppose there is a site hosting a database in state S0. And that +** modifications are made that move that database to state S1 and a +** changeset recorded (the "local" changeset). Then, a changeset based +** on S0 is received from another site (the "remote" changeset) and +** applied to the database. The database is then in state +** (S1+"remote"), where the exact state depends on any conflict +** resolution decisions (OMIT or REPLACE) made while applying "remote". +** Rebasing a changeset is to update it to take those conflict +** resolution decisions into account, so that the same conflicts +** do not have to be resolved elsewhere in the network. +** +** For example, if both the local and remote changesets contain an +** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)": +** +** local: INSERT INTO t1 VALUES(1, 'v1'); +** remote: INSERT INTO t1 VALUES(1, 'v2'); +** +** and the conflict resolution is REPLACE, then the INSERT change is +** removed from the local changeset (it was overridden). Or, if the +** conflict resolution was "OMIT", then the local changeset is modified +** to instead contain: +** +** UPDATE t1 SET b = 'v2' WHERE a=1; +** +** Changes within the local changeset are rebased as follows: +** +** <dl> +** <dt>Local INSERT<dd> +** This may only conflict with a remote INSERT. If the conflict +** resolution was OMIT, then add an UPDATE change to the rebased +** changeset. Or, if the conflict resolution was REPLACE, add +** nothing to the rebased changeset. +** +** <dt>Local DELETE<dd> +** This may conflict with a remote UPDATE or DELETE. In both cases the +** only possible resolution is OMIT. If the remote operation was a +** DELETE, then add no change to the rebased changeset. If the remote +** operation was an UPDATE, then the old.* fields of change are updated +** to reflect the new.* values in the UPDATE. +** +** <dt>Local UPDATE<dd> +** This may conflict with a remote UPDATE or DELETE. If it conflicts +** with a DELETE, and the conflict resolution was OMIT, then the update +** is changed into an INSERT. Any undefined values in the new.* record +** from the update change are filled in using the old.* values from +** the conflicting DELETE. Or, if the conflict resolution was REPLACE, +** the UPDATE change is simply omitted from the rebased changeset. +** +** If conflict is with a remote UPDATE and the resolution is OMIT, then +** the old.* values are rebased using the new.* values in the remote +** change. Or, if the resolution is REPLACE, then the change is copied +** into the rebased changeset with updates to columns also updated by +** the conflicting remote UPDATE removed. If this means no columns would +** be updated, the change is omitted. +** </dl> +** +** A local change may be rebased against multiple remote changes +** simultaneously. If a single key is modified by multiple remote +** changesets, they are combined as follows before the local changeset +** is rebased: +** +** <ul> +** <li> If there has been one or more REPLACE resolutions on a +** key, it is rebased according to a REPLACE. +** +** <li> If there have been no REPLACE resolutions on a key, then +** the local changeset is rebased according to the most recent +** of the OMIT resolutions. +** </ul> +** +** Note that conflict resolutions from multiple remote changesets are +** combined on a per-field basis, not per-row. This means that in the +** case of multiple remote UPDATE operations, some fields of a single +** local change may be rebased for REPLACE while others are rebased for +** OMIT. +** +** In order to rebase a local changeset, the remote changeset must first +** be applied to the local database using sqlite3changeset_apply_v2() and +** the buffer of rebase information captured. Then: +** +** <ol> +** <li> An sqlite3_rebaser object is created by calling +** sqlite3rebaser_create(). +** <li> The new object is configured with the rebase buffer obtained from +** sqlite3changeset_apply_v2() by calling sqlite3rebaser_configure(). +** If the local changeset is to be rebased against multiple remote +** changesets, then sqlite3rebaser_configure() should be called +** multiple times, in the same order that the multiple +** sqlite3changeset_apply_v2() calls were made. +** <li> Each local changeset is rebased by calling sqlite3rebaser_rebase(). +** <li> The sqlite3_rebaser object is deleted by calling +** sqlite3rebaser_delete(). +** </ol> +*/ +typedef struct sqlite3_rebaser sqlite3_rebaser; + +/* +** CAPI3REF: Create a changeset rebaser object. +** EXPERIMENTAL +** +** Allocate a new changeset rebaser object. If successful, set (*ppNew) to +** point to the new object and return SQLITE_OK. Otherwise, if an error +** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew) +** to NULL. +*/ +SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser **ppNew); + +/* +** CAPI3REF: Configure a changeset rebaser object. +** EXPERIMENTAL +** +** Configure the changeset rebaser object to rebase changesets according +** to the conflict resolutions described by buffer pRebase (size nRebase +** bytes), which must have been obtained from a previous call to +** sqlite3changeset_apply_v2(). +*/ +SQLITE_API int sqlite3rebaser_configure( + sqlite3_rebaser*, + int nRebase, const void *pRebase +); + +/* +** CAPI3REF: Rebase a changeset +** EXPERIMENTAL +** +** Argument pIn must point to a buffer containing a changeset nIn bytes +** in size. This function allocates and populates a buffer with a copy +** of the changeset rebased according to the configuration of the +** rebaser object passed as the first argument. If successful, (*ppOut) +** is set to point to the new buffer containing the rebased changeset and +** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the +** responsibility of the caller to eventually free the new buffer using +** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut) +** are set to zero and an SQLite error code returned. +*/ +SQLITE_API int sqlite3rebaser_rebase( + sqlite3_rebaser*, + int nIn, const void *pIn, + int *pnOut, void **ppOut +); + +/* +** CAPI3REF: Delete a changeset rebaser object. +** EXPERIMENTAL +** +** Delete the changeset rebaser object and all associated resources. There +** should be one call to this function for each successful invocation +** of sqlite3rebaser_create(). +*/ +SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); + +/* +** CAPI3REF: Streaming Versions of API functions. +** +** The six streaming API xxx_strm() functions serve similar purposes to the +** corresponding non-streaming API functions: +** +** <table border=1 style="margin-left:8ex;margin-right:8ex"> +** <tr><th>Streaming function<th>Non-streaming equivalent</th> +** <tr><td>sqlite3changeset_apply_strm<td>[sqlite3changeset_apply] +** <tr><td>sqlite3changeset_apply_strm_v2<td>[sqlite3changeset_apply_v2] +** <tr><td>sqlite3changeset_concat_strm<td>[sqlite3changeset_concat] +** <tr><td>sqlite3changeset_invert_strm<td>[sqlite3changeset_invert] +** <tr><td>sqlite3changeset_start_strm<td>[sqlite3changeset_start] +** <tr><td>sqlite3session_changeset_strm<td>[sqlite3session_changeset] +** <tr><td>sqlite3session_patchset_strm<td>[sqlite3session_patchset] +** </table> +** +** Non-streaming functions that accept changesets (or patchsets) as input +** require that the entire changeset be stored in a single buffer in memory. +** Similarly, those that return a changeset or patchset do so by returning +** a pointer to a single large buffer allocated using sqlite3_malloc(). +** Normally this is convenient. However, if an application running in a +** low-memory environment is required to handle very large changesets, the +** large contiguous memory allocations required can become onerous. +** +** In order to avoid this problem, instead of a single large buffer, input +** is passed to a streaming API functions by way of a callback function that +** the sessions module invokes to incrementally request input data as it is +** required. In all cases, a pair of API function parameters such as +** +** <pre> +** &nbsp; int nChangeset, +** &nbsp; void *pChangeset, +** </pre> +** +** Is replaced by: +** +** <pre> +** &nbsp; int (*xInput)(void *pIn, void *pData, int *pnData), +** &nbsp; void *pIn, +** </pre> +** +** Each time the xInput callback is invoked by the sessions module, the first +** argument passed is a copy of the supplied pIn context pointer. The second +** argument, pData, points to a buffer (*pnData) bytes in size. Assuming no +** error occurs the xInput method should copy up to (*pnData) bytes of data +** into the buffer and set (*pnData) to the actual number of bytes copied +** before returning SQLITE_OK. If the input is completely exhausted, (*pnData) +** should be set to zero to indicate this. Or, if an error occurs, an SQLite +** error code should be returned. In all cases, if an xInput callback returns +** an error, all processing is abandoned and the streaming API function +** returns a copy of the error code to the caller. +** +** In the case of sqlite3changeset_start_strm(), the xInput callback may be +** invoked by the sessions module at any point during the lifetime of the +** iterator. If such an xInput callback returns an error, the iterator enters +** an error state, whereby all subsequent calls to iterator functions +** immediately fail with the same error code as returned by xInput. +** +** Similarly, streaming API functions that return changesets (or patchsets) +** return them in chunks by way of a callback function instead of via a +** pointer to a single large buffer. In this case, a pair of parameters such +** as: +** +** <pre> +** &nbsp; int *pnChangeset, +** &nbsp; void **ppChangeset, +** </pre> +** +** Is replaced by: +** +** <pre> +** &nbsp; int (*xOutput)(void *pOut, const void *pData, int nData), +** &nbsp; void *pOut +** </pre> +** +** The xOutput callback is invoked zero or more times to return data to +** the application. The first parameter passed to each call is a copy of the +** pOut pointer supplied by the application. The second parameter, pData, +** points to a buffer nData bytes in size containing the chunk of output +** data being returned. If the xOutput callback successfully processes the +** supplied data, it should return SQLITE_OK to indicate success. Otherwise, +** it should return some other SQLite error code. In this case processing +** is immediately abandoned and the streaming API function returns a copy +** of the xOutput error code to the application. +** +** The sessions module never invokes an xOutput callback with the third +** parameter set to a value less than or equal to zero. Other than this, +** no guarantees are made as to the size of the chunks of data returned. +*/ +SQLITE_API int sqlite3changeset_apply_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +); +SQLITE_API int sqlite3changeset_apply_v2_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +); +SQLITE_API int sqlite3changeset_concat_strm( + int (*xInputA)(void *pIn, void *pData, int *pnData), + void *pInA, + int (*xInputB)(void *pIn, void *pData, int *pnData), + void *pInB, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3changeset_invert_strm( + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3changeset_start_strm( + sqlite3_changeset_iter **pp, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +); +SQLITE_API int sqlite3changeset_start_v2_strm( + sqlite3_changeset_iter **pp, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int flags +); +SQLITE_API int sqlite3session_changeset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3session_patchset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3changegroup_add_strm(sqlite3_changegroup*, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +); +SQLITE_API int sqlite3changegroup_output_strm(sqlite3_changegroup*, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3rebaser_rebase_strm( + sqlite3_rebaser *pRebaser, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); + +/* +** CAPI3REF: Configure global parameters +** +** The sqlite3session_config() interface is used to make global configuration +** changes to the sessions module in order to tune it to the specific needs +** of the application. +** +** The sqlite3session_config() interface is not threadsafe. If it is invoked +** while any other thread is inside any other sessions method then the +** results are undefined. Furthermore, if it is invoked after any sessions +** related objects have been created, the results are also undefined. +** +** The first argument to the sqlite3session_config() function must be one +** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The +** interpretation of the (void*) value passed as the second parameter and +** the effect of calling this function depends on the value of the first +** parameter. +** +** <dl> +** <dt>SQLITE_SESSION_CONFIG_STRMSIZE<dd> +** By default, the sessions module streaming interfaces attempt to input +** and output data in approximately 1 KiB chunks. This operand may be used +** to set and query the value of this configuration setting. The pointer +** passed as the second argument must point to a value of type (int). +** If this value is greater than 0, it is used as the new streaming data +** chunk size for both input and output. Before returning, the (int) value +** pointed to by pArg is set to the final value of the streaming interface +** chunk size. +** </dl> +** +** This function returns SQLITE_OK if successful, or an SQLite error code +** otherwise. +*/ +SQLITE_API int sqlite3session_config(int op, void *pArg); + +/* +** CAPI3REF: Values for sqlite3session_config(). +*/ +#define SQLITE_SESSION_CONFIG_STRMSIZE 1 + +/* +** Make sure we can call this stuff from C++. +*/ +#if 0 +} +#endif + +#endif /* !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) */ + +/******** End of sqlite3session.h *********/ +/******** Begin file fts5.h *********/ +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** Interfaces to extend FTS5. Using the interfaces defined in this file, +** FTS5 may be extended with: +** +** * custom tokenizers, and +** * custom auxiliary functions. +*/ + + +#ifndef _FTS5_H +#define _FTS5_H + + +#if 0 +extern "C" { +#endif + +/************************************************************************* +** CUSTOM AUXILIARY FUNCTIONS +** +** Virtual table implementations may overload SQL functions by implementing +** the sqlite3_module.xFindFunction() method. +*/ + +typedef struct Fts5ExtensionApi Fts5ExtensionApi; +typedef struct Fts5Context Fts5Context; +typedef struct Fts5PhraseIter Fts5PhraseIter; + +typedef void (*fts5_extension_function)( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + sqlite3_context *pCtx, /* Context for returning result/error */ + int nVal, /* Number of values in apVal[] array */ + sqlite3_value **apVal /* Array of trailing arguments */ +); + +struct Fts5PhraseIter { + const unsigned char *a; + const unsigned char *b; +}; + +/* +** EXTENSION API FUNCTIONS +** +** xUserData(pFts): +** Return a copy of the context pointer the extension function was +** registered with. +** +** xColumnTotalSize(pFts, iCol, pnToken): +** If parameter iCol is less than zero, set output variable *pnToken +** to the total number of tokens in the FTS5 table. Or, if iCol is +** non-negative but less than the number of columns in the table, return +** the total number of tokens in column iCol, considering all rows in +** the FTS5 table. +** +** If parameter iCol is greater than or equal to the number of columns +** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. +** an OOM condition or IO error), an appropriate SQLite error code is +** returned. +** +** xColumnCount(pFts): +** Return the number of columns in the table. +** +** xColumnSize(pFts, iCol, pnToken): +** If parameter iCol is less than zero, set output variable *pnToken +** to the total number of tokens in the current row. Or, if iCol is +** non-negative but less than the number of columns in the table, set +** *pnToken to the number of tokens in column iCol of the current row. +** +** If parameter iCol is greater than or equal to the number of columns +** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. +** an OOM condition or IO error), an appropriate SQLite error code is +** returned. +** +** This function may be quite inefficient if used with an FTS5 table +** created with the "columnsize=0" option. +** +** xColumnText: +** This function attempts to retrieve the text of column iCol of the +** current document. If successful, (*pz) is set to point to a buffer +** containing the text in utf-8 encoding, (*pn) is set to the size in bytes +** (not characters) of the buffer and SQLITE_OK is returned. Otherwise, +** if an error occurs, an SQLite error code is returned and the final values +** of (*pz) and (*pn) are undefined. +** +** xPhraseCount: +** Returns the number of phrases in the current query expression. +** +** xPhraseSize: +** Returns the number of tokens in phrase iPhrase of the query. Phrases +** are numbered starting from zero. +** +** xInstCount: +** Set *pnInst to the total number of occurrences of all phrases within +** the query within the current row. Return SQLITE_OK if successful, or +** an error code (i.e. SQLITE_NOMEM) if an error occurs. +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always returns 0. +** +** xInst: +** Query for the details of phrase match iIdx within the current row. +** Phrase matches are numbered starting from zero, so the iIdx argument +** should be greater than or equal to zero and smaller than the value +** output by xInstCount(). +** +** Usually, output parameter *piPhrase is set to the phrase number, *piCol +** to the column in which it occurs and *piOff the token offset of the +** first token of the phrase. Returns SQLITE_OK if successful, or an error +** code (i.e. SQLITE_NOMEM) if an error occurs. +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. +** +** xRowid: +** Returns the rowid of the current row. +** +** xTokenize: +** Tokenize text using the tokenizer belonging to the FTS5 table. +** +** xQueryPhrase(pFts5, iPhrase, pUserData, xCallback): +** This API function is used to query the FTS table for phrase iPhrase +** of the current query. Specifically, a query equivalent to: +** +** ... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid +** +** with $p set to a phrase equivalent to the phrase iPhrase of the +** current query is executed. Any column filter that applies to +** phrase iPhrase of the current query is included in $p. For each +** row visited, the callback function passed as the fourth argument +** is invoked. The context and API objects passed to the callback +** function may be used to access the properties of each matched row. +** Invoking Api.xUserData() returns a copy of the pointer passed as +** the third argument to pUserData. +** +** If the callback function returns any value other than SQLITE_OK, the +** query is abandoned and the xQueryPhrase function returns immediately. +** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK. +** Otherwise, the error code is propagated upwards. +** +** If the query runs to completion without incident, SQLITE_OK is returned. +** Or, if some error occurs before the query completes or is aborted by +** the callback, an SQLite error code is returned. +** +** +** xSetAuxdata(pFts5, pAux, xDelete) +** +** Save the pointer passed as the second argument as the extension function's +** "auxiliary data". The pointer may then be retrieved by the current or any +** future invocation of the same fts5 extension function made as part of +** the same MATCH query using the xGetAuxdata() API. +** +** Each extension function is allocated a single auxiliary data slot for +** each FTS query (MATCH expression). If the extension function is invoked +** more than once for a single FTS query, then all invocations share a +** single auxiliary data context. +** +** If there is already an auxiliary data pointer when this function is +** invoked, then it is replaced by the new pointer. If an xDelete callback +** was specified along with the original pointer, it is invoked at this +** point. +** +** The xDelete callback, if one is specified, is also invoked on the +** auxiliary data pointer after the FTS5 query has finished. +** +** If an error (e.g. an OOM condition) occurs within this function, +** the auxiliary data is set to NULL and an error code returned. If the +** xDelete parameter was not NULL, it is invoked on the auxiliary data +** pointer before returning. +** +** +** xGetAuxdata(pFts5, bClear) +** +** Returns the current auxiliary data pointer for the fts5 extension +** function. See the xSetAuxdata() method for details. +** +** If the bClear argument is non-zero, then the auxiliary data is cleared +** (set to NULL) before this function returns. In this case the xDelete, +** if any, is not invoked. +** +** +** xRowCount(pFts5, pnRow) +** +** This function is used to retrieve the total number of rows in the table. +** In other words, the same value that would be returned by: +** +** SELECT count(*) FROM ftstable; +** +** xPhraseFirst() +** This function is used, along with type Fts5PhraseIter and the xPhraseNext +** method, to iterate through all instances of a single query phrase within +** the current row. This is the same information as is accessible via the +** xInstCount/xInst APIs. While the xInstCount/xInst APIs are more convenient +** to use, this API may be faster under some circumstances. To iterate +** through instances of phrase iPhrase, use the following code: +** +** Fts5PhraseIter iter; +** int iCol, iOff; +** for(pApi->xPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff); +** iCol>=0; +** pApi->xPhraseNext(pFts, &iter, &iCol, &iOff) +** ){ +** // An instance of phrase iPhrase at offset iOff of column iCol +** } +** +** The Fts5PhraseIter structure is defined above. Applications should not +** modify this structure directly - it should only be used as shown above +** with the xPhraseFirst() and xPhraseNext() API methods (and by +** xPhraseFirstColumn() and xPhraseNextColumn() as illustrated below). +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always iterates +** through an empty set (all calls to xPhraseFirst() set iCol to -1). +** +** xPhraseNext() +** See xPhraseFirst above. +** +** xPhraseFirstColumn() +** This function and xPhraseNextColumn() are similar to the xPhraseFirst() +** and xPhraseNext() APIs described above. The difference is that instead +** of iterating through all instances of a phrase in the current row, these +** APIs are used to iterate through the set of columns in the current row +** that contain one or more instances of a specified phrase. For example: +** +** Fts5PhraseIter iter; +** int iCol; +** for(pApi->xPhraseFirstColumn(pFts, iPhrase, &iter, &iCol); +** iCol>=0; +** pApi->xPhraseNextColumn(pFts, &iter, &iCol) +** ){ +** // Column iCol contains at least one instance of phrase iPhrase +** } +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" option. If the FTS5 table is created with either +** "detail=none" "content=" option (i.e. if it is a contentless table), +** then this API always iterates through an empty set (all calls to +** xPhraseFirstColumn() set iCol to -1). +** +** The information accessed using this API and its companion +** xPhraseFirstColumn() may also be obtained using xPhraseFirst/xPhraseNext +** (or xInst/xInstCount). The chief advantage of this API is that it is +** significantly more efficient than those alternatives when used with +** "detail=column" tables. +** +** xPhraseNextColumn() +** See xPhraseFirstColumn above. +*/ +struct Fts5ExtensionApi { + int iVersion; /* Currently always set to 2 */ + + void *(*xUserData)(Fts5Context*); + + int (*xColumnCount)(Fts5Context*); + int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow); + int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken); + + int (*xTokenize)(Fts5Context*, + const char *pText, int nText, /* Text to tokenize */ + void *pCtx, /* Context passed to xToken() */ + int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ + ); + + int (*xPhraseCount)(Fts5Context*); + int (*xPhraseSize)(Fts5Context*, int iPhrase); + + int (*xInstCount)(Fts5Context*, int *pnInst); + int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff); + + sqlite3_int64 (*xRowid)(Fts5Context*); + int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn); + int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken); + + int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData, + int(*)(const Fts5ExtensionApi*,Fts5Context*,void*) + ); + int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*)); + void *(*xGetAuxdata)(Fts5Context*, int bClear); + + int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); + void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); + + int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); + void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); +}; + +/* +** CUSTOM AUXILIARY FUNCTIONS +*************************************************************************/ + +/************************************************************************* +** CUSTOM TOKENIZERS +** +** Applications may also register custom tokenizer types. A tokenizer +** is registered by providing fts5 with a populated instance of the +** following structure. All structure methods must be defined, setting +** any member of the fts5_tokenizer struct to NULL leads to undefined +** behaviour. The structure methods are expected to function as follows: +** +** xCreate: +** This function is used to allocate and initialize a tokenizer instance. +** A tokenizer instance is required to actually tokenize text. +** +** The first argument passed to this function is a copy of the (void*) +** pointer provided by the application when the fts5_tokenizer object +** was registered with FTS5 (the third argument to xCreateTokenizer()). +** The second and third arguments are an array of nul-terminated strings +** containing the tokenizer arguments, if any, specified following the +** tokenizer name as part of the CREATE VIRTUAL TABLE statement used +** to create the FTS5 table. +** +** The final argument is an output variable. If successful, (*ppOut) +** should be set to point to the new tokenizer handle and SQLITE_OK +** returned. If an error occurs, some value other than SQLITE_OK should +** be returned. In this case, fts5 assumes that the final value of *ppOut +** is undefined. +** +** xDelete: +** This function is invoked to delete a tokenizer handle previously +** allocated using xCreate(). Fts5 guarantees that this function will +** be invoked exactly once for each successful call to xCreate(). +** +** xTokenize: +** This function is expected to tokenize the nText byte string indicated +** by argument pText. pText may or may not be nul-terminated. The first +** argument passed to this function is a pointer to an Fts5Tokenizer object +** returned by an earlier call to xCreate(). +** +** The second argument indicates the reason that FTS5 is requesting +** tokenization of the supplied text. This is always one of the following +** four values: +** +** <ul><li> <b>FTS5_TOKENIZE_DOCUMENT</b> - A document is being inserted into +** or removed from the FTS table. The tokenizer is being invoked to +** determine the set of tokens to add to (or delete from) the +** FTS index. +** +** <li> <b>FTS5_TOKENIZE_QUERY</b> - A MATCH query is being executed +** against the FTS index. The tokenizer is being called to tokenize +** a bareword or quoted string specified as part of the query. +** +** <li> <b>(FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX)</b> - Same as +** FTS5_TOKENIZE_QUERY, except that the bareword or quoted string is +** followed by a "*" character, indicating that the last token +** returned by the tokenizer will be treated as a token prefix. +** +** <li> <b>FTS5_TOKENIZE_AUX</b> - The tokenizer is being invoked to +** satisfy an fts5_api.xTokenize() request made by an auxiliary +** function. Or an fts5_api.xColumnSize() request made by the same +** on a columnsize=0 database. +** </ul> +** +** For each token in the input string, the supplied callback xToken() must +** be invoked. The first argument to it should be a copy of the pointer +** passed as the second argument to xTokenize(). The third and fourth +** arguments are a pointer to a buffer containing the token text, and the +** size of the token in bytes. The 4th and 5th arguments are the byte offsets +** of the first byte of and first byte immediately following the text from +** which the token is derived within the input. +** +** The second argument passed to the xToken() callback ("tflags") should +** normally be set to 0. The exception is if the tokenizer supports +** synonyms. In this case see the discussion below for details. +** +** FTS5 assumes the xToken() callback is invoked for each token in the +** order that they occur within the input text. +** +** If an xToken() callback returns any value other than SQLITE_OK, then +** the tokenization should be abandoned and the xTokenize() method should +** immediately return a copy of the xToken() return value. Or, if the +** input buffer is exhausted, xTokenize() should return SQLITE_OK. Finally, +** if an error occurs with the xTokenize() implementation itself, it +** may abandon the tokenization and return any error code other than +** SQLITE_OK or SQLITE_DONE. +** +** SYNONYM SUPPORT +** +** Custom tokenizers may also support synonyms. Consider a case in which a +** user wishes to query for a phrase such as "first place". Using the +** built-in tokenizers, the FTS5 query 'first + place' will match instances +** of "first place" within the document set, but not alternative forms +** such as "1st place". In some applications, it would be better to match +** all instances of "first place" or "1st place" regardless of which form +** the user specified in the MATCH query text. +** +** There are several ways to approach this in FTS5: +** +** <ol><li> By mapping all synonyms to a single token. In this case, using +** the above example, this means that the tokenizer returns the +** same token for inputs "first" and "1st". Say that token is in +** fact "first", so that when the user inserts the document "I won +** 1st place" entries are added to the index for tokens "i", "won", +** "first" and "place". If the user then queries for '1st + place', +** the tokenizer substitutes "first" for "1st" and the query works +** as expected. +** +** <li> By querying the index for all synonyms of each query term +** separately. In this case, when tokenizing query text, the +** tokenizer may provide multiple synonyms for a single term +** within the document. FTS5 then queries the index for each +** synonym individually. For example, faced with the query: +** +** <codeblock> +** ... MATCH 'first place'</codeblock> +** +** the tokenizer offers both "1st" and "first" as synonyms for the +** first token in the MATCH query and FTS5 effectively runs a query +** similar to: +** +** <codeblock> +** ... MATCH '(first OR 1st) place'</codeblock> +** +** except that, for the purposes of auxiliary functions, the query +** still appears to contain just two phrases - "(first OR 1st)" +** being treated as a single phrase. +** +** <li> By adding multiple synonyms for a single term to the FTS index. +** Using this method, when tokenizing document text, the tokenizer +** provides multiple synonyms for each token. So that when a +** document such as "I won first place" is tokenized, entries are +** added to the FTS index for "i", "won", "first", "1st" and +** "place". +** +** This way, even if the tokenizer does not provide synonyms +** when tokenizing query text (it should not - to do so would be +** inefficient), it doesn't matter if the user queries for +** 'first + place' or '1st + place', as there are entries in the +** FTS index corresponding to both forms of the first token. +** </ol> +** +** Whether it is parsing document or query text, any call to xToken that +** specifies a <i>tflags</i> argument with the FTS5_TOKEN_COLOCATED bit +** is considered to supply a synonym for the previous token. For example, +** when parsing the document "I won first place", a tokenizer that supports +** synonyms would call xToken() 5 times, as follows: +** +** <codeblock> +** xToken(pCtx, 0, "i", 1, 0, 1); +** xToken(pCtx, 0, "won", 3, 2, 5); +** xToken(pCtx, 0, "first", 5, 6, 11); +** xToken(pCtx, FTS5_TOKEN_COLOCATED, "1st", 3, 6, 11); +** xToken(pCtx, 0, "place", 5, 12, 17); +**</codeblock> +** +** It is an error to specify the FTS5_TOKEN_COLOCATED flag the first time +** xToken() is called. Multiple synonyms may be specified for a single token +** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. +** There is no limit to the number of synonyms that may be provided for a +** single token. +** +** In many cases, method (1) above is the best approach. It does not add +** extra data to the FTS index or require FTS5 to query for multiple terms, +** so it is efficient in terms of disk space and query speed. However, it +** does not support prefix queries very well. If, as suggested above, the +** token "first" is substituted for "1st" by the tokenizer, then the query: +** +** <codeblock> +** ... MATCH '1s*'</codeblock> +** +** will not match documents that contain the token "1st" (as the tokenizer +** will probably not map "1s" to any prefix of "first"). +** +** For full prefix support, method (3) may be preferred. In this case, +** because the index contains entries for both "first" and "1st", prefix +** queries such as 'fi*' or '1s*' will match correctly. However, because +** extra entries are added to the FTS index, this method uses more space +** within the database. +** +** Method (2) offers a midpoint between (1) and (3). Using this method, +** a query such as '1s*' will match documents that contain the literal +** token "1st", but not "first" (assuming the tokenizer is not able to +** provide synonyms for prefixes). However, a non-prefix query like '1st' +** will match against "1st" and "first". This method does not require +** extra disk space, as no extra entries are added to the FTS index. +** On the other hand, it may require more CPU cycles to run MATCH queries, +** as separate queries of the FTS index are required for each synonym. +** +** When using methods (2) or (3), it is important that the tokenizer only +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is +** inefficient. +*/ +typedef struct Fts5Tokenizer Fts5Tokenizer; +typedef struct fts5_tokenizer fts5_tokenizer; +struct fts5_tokenizer { + int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + void (*xDelete)(Fts5Tokenizer*); + int (*xTokenize)(Fts5Tokenizer*, + void *pCtx, + int flags, /* Mask of FTS5_TOKENIZE_* flags */ + const char *pText, int nText, + int (*xToken)( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStart, /* Byte offset of token within input text */ + int iEnd /* Byte offset of end of token within input text */ + ) + ); +}; + +/* Flags that may be passed as the third argument to xTokenize() */ +#define FTS5_TOKENIZE_QUERY 0x0001 +#define FTS5_TOKENIZE_PREFIX 0x0002 +#define FTS5_TOKENIZE_DOCUMENT 0x0004 +#define FTS5_TOKENIZE_AUX 0x0008 + +/* Flags that may be passed by the tokenizer implementation back to FTS5 +** as the third argument to the supplied xToken callback. */ +#define FTS5_TOKEN_COLOCATED 0x0001 /* Same position as prev. token */ + +/* +** END OF CUSTOM TOKENIZERS +*************************************************************************/ + +/************************************************************************* +** FTS5 EXTENSION REGISTRATION API +*/ +typedef struct fts5_api fts5_api; +struct fts5_api { + int iVersion; /* Currently always set to 2 */ + + /* Create a new tokenizer */ + int (*xCreateTokenizer)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_tokenizer *pTokenizer, + void (*xDestroy)(void*) + ); + + /* Find an existing tokenizer */ + int (*xFindTokenizer)( + fts5_api *pApi, + const char *zName, + void **ppUserData, + fts5_tokenizer *pTokenizer + ); + + /* Create a new auxiliary function */ + int (*xCreateFunction)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_extension_function xFunction, + void (*xDestroy)(void*) + ); +}; + +/* +** END OF REGISTRATION API +*************************************************************************/ + +#if 0 +} /* end of the 'extern "C"' block */ +#endif + +#endif /* _FTS5_H */ + +/******** End of fts5.h *********/ + +/************** End of sqlite3.h *********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + +/* +** Reuse the STATIC_LRU for mutex access to sqlite3_temp_directory. +*/ +#define SQLITE_MUTEX_STATIC_TEMPDIR SQLITE_MUTEX_STATIC_VFS1 + +/* +** Include the configuration header output by 'configure' if we're using the +** autoconf-based build +*/ +#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) +#include "sqlite_cfg.h" +#define SQLITECONFIG_H 1 +#endif + +/************** Include sqliteLimit.h in the middle of sqliteInt.h ***********/ +/************** Begin file sqliteLimit.h *************************************/ +/* +** 2007 May 7 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file defines various limits of what SQLite can process. +*/ + +/* +** The maximum length of a TEXT or BLOB in bytes. This also +** limits the size of a row in a table or index. +** +** The hard limit is the ability of a 32-bit signed integer +** to count the size: 2^31-1 or 2147483647. +*/ +#ifndef SQLITE_MAX_LENGTH +# define SQLITE_MAX_LENGTH 1000000000 +#endif + +/* +** This is the maximum number of +** +** * Columns in a table +** * Columns in an index +** * Columns in a view +** * Terms in the SET clause of an UPDATE statement +** * Terms in the result set of a SELECT statement +** * Terms in the GROUP BY or ORDER BY clauses of a SELECT statement. +** * Terms in the VALUES clause of an INSERT statement +** +** The hard upper limit here is 32676. Most database people will +** tell you that in a well-normalized database, you usually should +** not have more than a dozen or so columns in any table. And if +** that is the case, there is no point in having more than a few +** dozen values in any of the other situations described above. +*/ +#ifndef SQLITE_MAX_COLUMN +# define SQLITE_MAX_COLUMN 2000 +#endif + +/* +** The maximum length of a single SQL statement in bytes. +** +** It used to be the case that setting this value to zero would +** turn the limit off. That is no longer true. It is not possible +** to turn this limit off. +*/ +#ifndef SQLITE_MAX_SQL_LENGTH +# define SQLITE_MAX_SQL_LENGTH 1000000000 +#endif + +/* +** The maximum depth of an expression tree. This is limited to +** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might +** want to place more severe limits on the complexity of an +** expression. A value of 0 means that there is no limit. +*/ +#ifndef SQLITE_MAX_EXPR_DEPTH +# define SQLITE_MAX_EXPR_DEPTH 1000 +#endif + +/* +** The maximum number of terms in a compound SELECT statement. +** The code generator for compound SELECT statements does one +** level of recursion for each term. A stack overflow can result +** if the number of terms is too large. In practice, most SQL +** never has more than 3 or 4 terms. Use a value of 0 to disable +** any limit on the number of terms in a compound SELECT. +*/ +#ifndef SQLITE_MAX_COMPOUND_SELECT +# define SQLITE_MAX_COMPOUND_SELECT 500 +#endif + +/* +** The maximum number of opcodes in a VDBE program. +** Not currently enforced. +*/ +#ifndef SQLITE_MAX_VDBE_OP +# define SQLITE_MAX_VDBE_OP 250000000 +#endif + +/* +** The maximum number of arguments to an SQL function. +*/ +#ifndef SQLITE_MAX_FUNCTION_ARG +# define SQLITE_MAX_FUNCTION_ARG 127 +#endif + +/* +** The suggested maximum number of in-memory pages to use for +** the main database table and for temporary tables. +** +** IMPLEMENTATION-OF: R-30185-15359 The default suggested cache size is -2000, +** which means the cache size is limited to 2048000 bytes of memory. +** IMPLEMENTATION-OF: R-48205-43578 The default suggested cache size can be +** altered using the SQLITE_DEFAULT_CACHE_SIZE compile-time options. +*/ +#ifndef SQLITE_DEFAULT_CACHE_SIZE +# define SQLITE_DEFAULT_CACHE_SIZE -2000 +#endif + +/* +** The default number of frames to accumulate in the log file before +** checkpointing the database in WAL mode. +*/ +#ifndef SQLITE_DEFAULT_WAL_AUTOCHECKPOINT +# define SQLITE_DEFAULT_WAL_AUTOCHECKPOINT 1000 +#endif + +/* +** The maximum number of attached databases. This must be between 0 +** and 125. The upper bound of 125 is because the attached databases are +** counted using a signed 8-bit integer which has a maximum value of 127 +** and we have to allow 2 extra counts for the "main" and "temp" databases. +*/ +#ifndef SQLITE_MAX_ATTACHED +# define SQLITE_MAX_ATTACHED 10 +#endif + + +/* +** The maximum value of a ?nnn wildcard that the parser will accept. +** If the value exceeds 32767 then extra space is required for the Expr +** structure. But otherwise, we believe that the number can be as large +** as a signed 32-bit integer can hold. +*/ +#ifndef SQLITE_MAX_VARIABLE_NUMBER +# define SQLITE_MAX_VARIABLE_NUMBER 32766 +#endif + +/* Maximum page size. The upper bound on this value is 65536. This a limit +** imposed by the use of 16-bit offsets within each page. +** +** Earlier versions of SQLite allowed the user to change this value at +** compile time. This is no longer permitted, on the grounds that it creates +** a library that is technically incompatible with an SQLite library +** compiled with a different limit. If a process operating on a database +** with a page-size of 65536 bytes crashes, then an instance of SQLite +** compiled with the default page-size limit will not be able to rollback +** the aborted transaction. This could lead to database corruption. +*/ +#ifdef SQLITE_MAX_PAGE_SIZE +# undef SQLITE_MAX_PAGE_SIZE +#endif +#define SQLITE_MAX_PAGE_SIZE 65536 + + +/* +** The default size of a database page. +*/ +#ifndef SQLITE_DEFAULT_PAGE_SIZE +# define SQLITE_DEFAULT_PAGE_SIZE 4096 +#endif +#if SQLITE_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE +# undef SQLITE_DEFAULT_PAGE_SIZE +# define SQLITE_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE +#endif + +/* +** Ordinarily, if no value is explicitly provided, SQLite creates databases +** with page size SQLITE_DEFAULT_PAGE_SIZE. However, based on certain +** device characteristics (sector-size and atomic write() support), +** SQLite may choose a larger value. This constant is the maximum value +** SQLite will choose on its own. +*/ +#ifndef SQLITE_MAX_DEFAULT_PAGE_SIZE +# define SQLITE_MAX_DEFAULT_PAGE_SIZE 8192 +#endif +#if SQLITE_MAX_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE +# undef SQLITE_MAX_DEFAULT_PAGE_SIZE +# define SQLITE_MAX_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE +#endif + + +/* +** Maximum number of pages in one database file. +** +** This is really just the default value for the max_page_count pragma. +** This value can be lowered (or raised) at run-time using that the +** max_page_count macro. +*/ +#ifndef SQLITE_MAX_PAGE_COUNT +# define SQLITE_MAX_PAGE_COUNT 1073741823 +#endif + +/* +** Maximum length (in bytes) of the pattern in a LIKE or GLOB +** operator. +*/ +#ifndef SQLITE_MAX_LIKE_PATTERN_LENGTH +# define SQLITE_MAX_LIKE_PATTERN_LENGTH 50000 +#endif + +/* +** Maximum depth of recursion for triggers. +** +** A value of 1 means that a trigger program will not be able to itself +** fire any triggers. A value of 0 means that no trigger programs at all +** may be executed. +*/ +#ifndef SQLITE_MAX_TRIGGER_DEPTH +# define SQLITE_MAX_TRIGGER_DEPTH 1000 +#endif + +/************** End of sqliteLimit.h *****************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + +/* Disable nuisance warnings on Borland compilers */ +#if defined(__BORLANDC__) +#pragma warn -rch /* unreachable code */ +#pragma warn -ccc /* Condition is always true or false */ +#pragma warn -aus /* Assigned value is never used */ +#pragma warn -csu /* Comparing signed and unsigned */ +#pragma warn -spa /* Suspicious pointer arithmetic */ +#endif + +/* +** A few places in the code require atomic load/store of aligned +** integer values. +*/ +#ifndef __has_extension +# define __has_extension(x) 0 /* compatibility with non-clang compilers */ +#endif +#if GCC_VERSION>=4007000 || __has_extension(c_atomic) +# define SQLITE_ATOMIC_INTRINSICS 1 +# define AtomicLoad(PTR) __atomic_load_n((PTR),__ATOMIC_RELAXED) +# define AtomicStore(PTR,VAL) __atomic_store_n((PTR),(VAL),__ATOMIC_RELAXED) +#else +# define SQLITE_ATOMIC_INTRINSICS 0 +# define AtomicLoad(PTR) (*(PTR)) +# define AtomicStore(PTR,VAL) (*(PTR) = (VAL)) +#endif + +/* +** Include standard header files as necessary +*/ +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#endif + +/* +** The following macros are used to cast pointers to integers and +** integers to pointers. The way you do this varies from one compiler +** to the next, so we have developed the following set of #if statements +** to generate appropriate macros for a wide range of compilers. +** +** The correct "ANSI" way to do this is to use the intptr_t type. +** Unfortunately, that typedef is not available on all compilers, or +** if it is available, it requires an #include of specific headers +** that vary from one machine to the next. +** +** Ticket #3860: The llvm-gcc-4.2 compiler from Apple chokes on +** the ((void*)&((char*)0)[X]) construct. But MSVC chokes on ((void*)(X)). +** So we have to define the macros in different ways depending on the +** compiler. +*/ +#if defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */ +# define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X)) +# define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X)) +#elif defined(__PTRDIFF_TYPE__) /* This case should work for GCC */ +# define SQLITE_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) +# define SQLITE_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X)) +#elif !defined(__GNUC__) /* Works for compilers other than LLVM */ +# define SQLITE_INT_TO_PTR(X) ((void*)&((char*)0)[X]) +# define SQLITE_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) +#else /* Generates a warning - but it always works */ +# define SQLITE_INT_TO_PTR(X) ((void*)(X)) +# define SQLITE_PTR_TO_INT(X) ((int)(X)) +#endif + +/* +** Macros to hint to the compiler that a function should or should not be +** inlined. +*/ +#if defined(__GNUC__) +# define SQLITE_NOINLINE __attribute__((noinline)) +# define SQLITE_INLINE __attribute__((always_inline)) inline +#elif defined(_MSC_VER) && _MSC_VER>=1310 +# define SQLITE_NOINLINE __declspec(noinline) +# define SQLITE_INLINE __forceinline +#else +# define SQLITE_NOINLINE +# define SQLITE_INLINE +#endif +#if defined(SQLITE_COVERAGE_TEST) || defined(__STRICT_ANSI__) +# undef SQLITE_INLINE +# define SQLITE_INLINE +#endif + +/* +** Make sure that the compiler intrinsics we desire are enabled when +** compiling with an appropriate version of MSVC unless prevented by +** the SQLITE_DISABLE_INTRINSIC define. +*/ +#if !defined(SQLITE_DISABLE_INTRINSIC) +# if defined(_MSC_VER) && _MSC_VER>=1400 +# if !defined(_WIN32_WCE) +# include <intrin.h> +# pragma intrinsic(_byteswap_ushort) +# pragma intrinsic(_byteswap_ulong) +# pragma intrinsic(_byteswap_uint64) +# pragma intrinsic(_ReadWriteBarrier) +# else +# include <cmnintrin.h> +# endif +# endif +#endif + +/* +** The SQLITE_THREADSAFE macro must be defined as 0, 1, or 2. +** 0 means mutexes are permanently disable and the library is never +** threadsafe. 1 means the library is serialized which is the highest +** level of threadsafety. 2 means the library is multithreaded - multiple +** threads can use SQLite as long as no two threads try to use the same +** database connection at the same time. +** +** Older versions of SQLite used an optional THREADSAFE macro. +** We support that for legacy. +** +** To ensure that the correct value of "THREADSAFE" is reported when querying +** for compile-time options at runtime (e.g. "PRAGMA compile_options"), this +** logic is partially replicated in ctime.c. If it is updated here, it should +** also be updated there. +*/ +#if !defined(SQLITE_THREADSAFE) +# if defined(THREADSAFE) +# define SQLITE_THREADSAFE THREADSAFE +# else +# define SQLITE_THREADSAFE 1 /* IMP: R-07272-22309 */ +# endif +#endif + +/* +** Powersafe overwrite is on by default. But can be turned off using +** the -DSQLITE_POWERSAFE_OVERWRITE=0 command-line option. +*/ +#ifndef SQLITE_POWERSAFE_OVERWRITE +# define SQLITE_POWERSAFE_OVERWRITE 1 +#endif + +/* +** EVIDENCE-OF: R-25715-37072 Memory allocation statistics are enabled by +** default unless SQLite is compiled with SQLITE_DEFAULT_MEMSTATUS=0 in +** which case memory allocation statistics are disabled by default. +*/ +#if !defined(SQLITE_DEFAULT_MEMSTATUS) +# define SQLITE_DEFAULT_MEMSTATUS 1 +#endif + +/* +** Exactly one of the following macros must be defined in order to +** specify which memory allocation subsystem to use. +** +** SQLITE_SYSTEM_MALLOC // Use normal system malloc() +** SQLITE_WIN32_MALLOC // Use Win32 native heap API +** SQLITE_ZERO_MALLOC // Use a stub allocator that always fails +** SQLITE_MEMDEBUG // Debugging version of system malloc() +** +** On Windows, if the SQLITE_WIN32_MALLOC_VALIDATE macro is defined and the +** assert() macro is enabled, each call into the Win32 native heap subsystem +** will cause HeapValidate to be called. If heap validation should fail, an +** assertion will be triggered. +** +** If none of the above are defined, then set SQLITE_SYSTEM_MALLOC as +** the default. +*/ +#if defined(SQLITE_SYSTEM_MALLOC) \ + + defined(SQLITE_WIN32_MALLOC) \ + + defined(SQLITE_ZERO_MALLOC) \ + + defined(SQLITE_MEMDEBUG)>1 +# error "Two or more of the following compile-time configuration options\ + are defined but at most one is allowed:\ + SQLITE_SYSTEM_MALLOC, SQLITE_WIN32_MALLOC, SQLITE_MEMDEBUG,\ + SQLITE_ZERO_MALLOC" +#endif +#if defined(SQLITE_SYSTEM_MALLOC) \ + + defined(SQLITE_WIN32_MALLOC) \ + + defined(SQLITE_ZERO_MALLOC) \ + + defined(SQLITE_MEMDEBUG)==0 +# define SQLITE_SYSTEM_MALLOC 1 +#endif + +/* +** If SQLITE_MALLOC_SOFT_LIMIT is not zero, then try to keep the +** sizes of memory allocations below this value where possible. +*/ +#if !defined(SQLITE_MALLOC_SOFT_LIMIT) +# define SQLITE_MALLOC_SOFT_LIMIT 1024 +#endif + +/* +** We need to define _XOPEN_SOURCE as follows in order to enable +** recursive mutexes on most Unix systems and fchmod() on OpenBSD. +** But _XOPEN_SOURCE define causes problems for Mac OS X, so omit +** it. +*/ +#if !defined(_XOPEN_SOURCE) && !defined(__DARWIN__) && !defined(__APPLE__) +# define _XOPEN_SOURCE 600 +#endif + +/* +** NDEBUG and SQLITE_DEBUG are opposites. It should always be true that +** defined(NDEBUG)==!defined(SQLITE_DEBUG). If this is not currently true, +** make it true by defining or undefining NDEBUG. +** +** Setting NDEBUG makes the code smaller and faster by disabling the +** assert() statements in the code. So we want the default action +** to be for NDEBUG to be set and NDEBUG to be undefined only if SQLITE_DEBUG +** is set. Thus NDEBUG becomes an opt-in rather than an opt-out +** feature. +*/ +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif +#if defined(NDEBUG) && defined(SQLITE_DEBUG) +# undef NDEBUG +#endif + +/* +** Enable SQLITE_ENABLE_EXPLAIN_COMMENTS if SQLITE_DEBUG is turned on. +*/ +#if !defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) && defined(SQLITE_DEBUG) +# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 +#endif + +/* +** The testcase() macro is used to aid in coverage testing. When +** doing coverage testing, the condition inside the argument to +** testcase() must be evaluated both true and false in order to +** get full branch coverage. The testcase() macro is inserted +** to help ensure adequate test coverage in places where simple +** condition/decision coverage is inadequate. For example, testcase() +** can be used to make sure boundary values are tested. For +** bitmask tests, testcase() can be used to make sure each bit +** is significant and used at least once. On switch statements +** where multiple cases go to the same block of code, testcase() +** can insure that all cases are evaluated. +*/ +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) +# ifndef SQLITE_AMALGAMATION + extern unsigned int sqlite3CoverageCounter; +# endif +# define testcase(X) if( X ){ sqlite3CoverageCounter += (unsigned)__LINE__; } +#else +# define testcase(X) +#endif + +/* +** The TESTONLY macro is used to enclose variable declarations or +** other bits of code that are needed to support the arguments +** within testcase() and assert() macros. +*/ +#if !defined(NDEBUG) || defined(SQLITE_COVERAGE_TEST) +# define TESTONLY(X) X +#else +# define TESTONLY(X) +#endif + +/* +** Sometimes we need a small amount of code such as a variable initialization +** to setup for a later assert() statement. We do not want this code to +** appear when assert() is disabled. The following macro is therefore +** used to contain that setup code. The "VVA" acronym stands for +** "Verification, Validation, and Accreditation". In other words, the +** code within VVA_ONLY() will only run during verification processes. +*/ +#ifndef NDEBUG +# define VVA_ONLY(X) X +#else +# define VVA_ONLY(X) +#endif + +/* +** Disable ALWAYS() and NEVER() (make them pass-throughs) for coverage +** and mutation testing +*/ +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif + +/* +** The ALWAYS and NEVER macros surround boolean expressions which +** are intended to always be true or false, respectively. Such +** expressions could be omitted from the code completely. But they +** are included in a few cases in order to enhance the resilience +** of SQLite to unexpected behavior - to make the code "self-healing" +** or "ductile" rather than being "brittle" and crashing at the first +** hint of unplanned behavior. +** +** In other words, ALWAYS and NEVER are added for defensive code. +** +** When doing coverage testing ALWAYS and NEVER are hard-coded to +** be true and false so that the unreachable code they specify will +** not be counted as untested code. +*/ +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif + +/* +** Some conditionals are optimizations only. In other words, if the +** conditionals are replaced with a constant 1 (true) or 0 (false) then +** the correct answer is still obtained, though perhaps not as quickly. +** +** The following macros mark these optimizations conditionals. +*/ +#if defined(SQLITE_MUTATION_TEST) +# define OK_IF_ALWAYS_TRUE(X) (1) +# define OK_IF_ALWAYS_FALSE(X) (0) +#else +# define OK_IF_ALWAYS_TRUE(X) (X) +# define OK_IF_ALWAYS_FALSE(X) (X) +#endif + +/* +** Some malloc failures are only possible if SQLITE_TEST_REALLOC_STRESS is +** defined. We need to defend against those failures when testing with +** SQLITE_TEST_REALLOC_STRESS, but we don't want the unreachable branches +** during a normal build. The following macro can be used to disable tests +** that are always false except when SQLITE_TEST_REALLOC_STRESS is set. +*/ +#if defined(SQLITE_TEST_REALLOC_STRESS) +# define ONLY_IF_REALLOC_STRESS(X) (X) +#elif !defined(NDEBUG) +# define ONLY_IF_REALLOC_STRESS(X) ((X)?(assert(0),1):0) +#else +# define ONLY_IF_REALLOC_STRESS(X) (0) +#endif + +/* +** Declarations used for tracing the operating system interfaces. +*/ +#if defined(SQLITE_FORCE_OS_TRACE) || defined(SQLITE_TEST) || \ + (defined(SQLITE_DEBUG) && SQLITE_OS_WIN) + extern int sqlite3OSTrace; +# define OSTRACE(X) if( sqlite3OSTrace ) sqlite3DebugPrintf X +# define SQLITE_HAVE_OS_TRACE +#else +# define OSTRACE(X) +# undef SQLITE_HAVE_OS_TRACE +#endif + +/* +** Is the sqlite3ErrName() function needed in the build? Currently, +** it is needed by "mutex_w32.c" (when debugging), "os_win.c" (when +** OSTRACE is enabled), and by several "test*.c" files (which are +** compiled using SQLITE_TEST). +*/ +#if defined(SQLITE_HAVE_OS_TRACE) || defined(SQLITE_TEST) || \ + (defined(SQLITE_DEBUG) && SQLITE_OS_WIN) +# define SQLITE_NEED_ERR_NAME +#else +# undef SQLITE_NEED_ERR_NAME +#endif + +/* +** SQLITE_ENABLE_EXPLAIN_COMMENTS is incompatible with SQLITE_OMIT_EXPLAIN +*/ +#ifdef SQLITE_OMIT_EXPLAIN +# undef SQLITE_ENABLE_EXPLAIN_COMMENTS +#endif + +/* +** SQLITE_OMIT_VIRTUALTABLE implies SQLITE_OMIT_ALTERTABLE +*/ +#if defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_ALTERTABLE) +# define SQLITE_OMIT_ALTERTABLE +#endif + +/* +** Return true (non-zero) if the input is an integer that is too large +** to fit in 32-bits. This macro is used inside of various testcase() +** macros to verify that we have tested SQLite for large-file support. +*/ +#define IS_BIG_INT(X) (((X)&~(i64)0xffffffff)!=0) + +/* +** The macro unlikely() is a hint that surrounds a boolean +** expression that is usually false. Macro likely() surrounds +** a boolean expression that is usually true. These hints could, +** in theory, be used by the compiler to generate better code, but +** currently they are just comments for human readers. +*/ +#define likely(X) (X) +#define unlikely(X) (X) + +/************** Include hash.h in the middle of sqliteInt.h ******************/ +/************** Begin file hash.h ********************************************/ +/* +** 2001 September 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the header file for the generic hash-table implementation +** used in SQLite. +*/ +#ifndef SQLITE_HASH_H +#define SQLITE_HASH_H + +/* Forward declarations of structures. */ +typedef struct Hash Hash; +typedef struct HashElem HashElem; + +/* A complete hash table is an instance of the following structure. +** The internals of this structure are intended to be opaque -- client +** code should not attempt to access or modify the fields of this structure +** directly. Change this structure only by using the routines below. +** However, some of the "procedures" and "functions" for modifying and +** accessing this structure are really macros, so we can't really make +** this structure opaque. +** +** All elements of the hash table are on a single doubly-linked list. +** Hash.first points to the head of this list. +** +** There are Hash.htsize buckets. Each bucket points to a spot in +** the global doubly-linked list. The contents of the bucket are the +** element pointed to plus the next _ht.count-1 elements in the list. +** +** Hash.htsize and Hash.ht may be zero. In that case lookup is done +** by a linear search of the global list. For small tables, the +** Hash.ht table is never allocated because if there are few elements +** in the table, it is faster to do a linear search than to manage +** the hash table. +*/ +struct Hash { + unsigned int htsize; /* Number of buckets in the hash table */ + unsigned int count; /* Number of entries in this table */ + HashElem *first; /* The first element of the array */ + struct _ht { /* the hash table */ + unsigned int count; /* Number of entries with this hash */ + HashElem *chain; /* Pointer to first entry with this hash */ + } *ht; +}; + +/* Each element in the hash table is an instance of the following +** structure. All elements are stored on a single doubly-linked list. +** +** Again, this structure is intended to be opaque, but it can't really +** be opaque because it is used by macros. +*/ +struct HashElem { + HashElem *next, *prev; /* Next and previous elements in the table */ + void *data; /* Data associated with this element */ + const char *pKey; /* Key associated with this element */ +}; + +/* +** Access routines. To delete, insert a NULL pointer. +*/ +SQLITE_PRIVATE void sqlite3HashInit(Hash*); +SQLITE_PRIVATE void *sqlite3HashInsert(Hash*, const char *pKey, void *pData); +SQLITE_PRIVATE void *sqlite3HashFind(const Hash*, const char *pKey); +SQLITE_PRIVATE void sqlite3HashClear(Hash*); + +/* +** Macros for looping over all elements of a hash table. The idiom is +** like this: +** +** Hash h; +** HashElem *p; +** ... +** for(p=sqliteHashFirst(&h); p; p=sqliteHashNext(p)){ +** SomeStructure *pData = sqliteHashData(p); +** // do something with pData +** } +*/ +#define sqliteHashFirst(H) ((H)->first) +#define sqliteHashNext(E) ((E)->next) +#define sqliteHashData(E) ((E)->data) +/* #define sqliteHashKey(E) ((E)->pKey) // NOT USED */ +/* #define sqliteHashKeysize(E) ((E)->nKey) // NOT USED */ + +/* +** Number of entries in a hash table +*/ +#define sqliteHashCount(H) ((H)->count) + +#endif /* SQLITE_HASH_H */ + +/************** End of hash.h ************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include parse.h in the middle of sqliteInt.h *****************/ +/************** Begin file parse.h *******************************************/ +#define TK_SEMI 1 +#define TK_EXPLAIN 2 +#define TK_QUERY 3 +#define TK_PLAN 4 +#define TK_BEGIN 5 +#define TK_TRANSACTION 6 +#define TK_DEFERRED 7 +#define TK_IMMEDIATE 8 +#define TK_EXCLUSIVE 9 +#define TK_COMMIT 10 +#define TK_END 11 +#define TK_ROLLBACK 12 +#define TK_SAVEPOINT 13 +#define TK_RELEASE 14 +#define TK_TO 15 +#define TK_TABLE 16 +#define TK_CREATE 17 +#define TK_IF 18 +#define TK_NOT 19 +#define TK_EXISTS 20 +#define TK_TEMP 21 +#define TK_LP 22 +#define TK_RP 23 +#define TK_AS 24 +#define TK_COMMA 25 +#define TK_WITHOUT 26 +#define TK_ABORT 27 +#define TK_ACTION 28 +#define TK_AFTER 29 +#define TK_ANALYZE 30 +#define TK_ASC 31 +#define TK_ATTACH 32 +#define TK_BEFORE 33 +#define TK_BY 34 +#define TK_CASCADE 35 +#define TK_CAST 36 +#define TK_CONFLICT 37 +#define TK_DATABASE 38 +#define TK_DESC 39 +#define TK_DETACH 40 +#define TK_EACH 41 +#define TK_FAIL 42 +#define TK_OR 43 +#define TK_AND 44 +#define TK_IS 45 +#define TK_MATCH 46 +#define TK_LIKE_KW 47 +#define TK_BETWEEN 48 +#define TK_IN 49 +#define TK_ISNULL 50 +#define TK_NOTNULL 51 +#define TK_NE 52 +#define TK_EQ 53 +#define TK_GT 54 +#define TK_LE 55 +#define TK_LT 56 +#define TK_GE 57 +#define TK_ESCAPE 58 +#define TK_ID 59 +#define TK_COLUMNKW 60 +#define TK_DO 61 +#define TK_FOR 62 +#define TK_IGNORE 63 +#define TK_INITIALLY 64 +#define TK_INSTEAD 65 +#define TK_NO 66 +#define TK_KEY 67 +#define TK_OF 68 +#define TK_OFFSET 69 +#define TK_PRAGMA 70 +#define TK_RAISE 71 +#define TK_RECURSIVE 72 +#define TK_REPLACE 73 +#define TK_RESTRICT 74 +#define TK_ROW 75 +#define TK_ROWS 76 +#define TK_TRIGGER 77 +#define TK_VACUUM 78 +#define TK_VIEW 79 +#define TK_VIRTUAL 80 +#define TK_WITH 81 +#define TK_NULLS 82 +#define TK_FIRST 83 +#define TK_LAST 84 +#define TK_CURRENT 85 +#define TK_FOLLOWING 86 +#define TK_PARTITION 87 +#define TK_PRECEDING 88 +#define TK_RANGE 89 +#define TK_UNBOUNDED 90 +#define TK_EXCLUDE 91 +#define TK_GROUPS 92 +#define TK_OTHERS 93 +#define TK_TIES 94 +#define TK_GENERATED 95 +#define TK_ALWAYS 96 +#define TK_MATERIALIZED 97 +#define TK_REINDEX 98 +#define TK_RENAME 99 +#define TK_CTIME_KW 100 +#define TK_ANY 101 +#define TK_BITAND 102 +#define TK_BITOR 103 +#define TK_LSHIFT 104 +#define TK_RSHIFT 105 +#define TK_PLUS 106 +#define TK_MINUS 107 +#define TK_STAR 108 +#define TK_SLASH 109 +#define TK_REM 110 +#define TK_CONCAT 111 +#define TK_PTR 112 +#define TK_COLLATE 113 +#define TK_BITNOT 114 +#define TK_ON 115 +#define TK_INDEXED 116 +#define TK_STRING 117 +#define TK_JOIN_KW 118 +#define TK_CONSTRAINT 119 +#define TK_DEFAULT 120 +#define TK_NULL 121 +#define TK_PRIMARY 122 +#define TK_UNIQUE 123 +#define TK_CHECK 124 +#define TK_REFERENCES 125 +#define TK_AUTOINCR 126 +#define TK_INSERT 127 +#define TK_DELETE 128 +#define TK_UPDATE 129 +#define TK_SET 130 +#define TK_DEFERRABLE 131 +#define TK_FOREIGN 132 +#define TK_DROP 133 +#define TK_UNION 134 +#define TK_ALL 135 +#define TK_EXCEPT 136 +#define TK_INTERSECT 137 +#define TK_SELECT 138 +#define TK_VALUES 139 +#define TK_DISTINCT 140 +#define TK_DOT 141 +#define TK_FROM 142 +#define TK_JOIN 143 +#define TK_USING 144 +#define TK_ORDER 145 +#define TK_GROUP 146 +#define TK_HAVING 147 +#define TK_LIMIT 148 +#define TK_WHERE 149 +#define TK_RETURNING 150 +#define TK_INTO 151 +#define TK_NOTHING 152 +#define TK_FLOAT 153 +#define TK_BLOB 154 +#define TK_INTEGER 155 +#define TK_VARIABLE 156 +#define TK_CASE 157 +#define TK_WHEN 158 +#define TK_THEN 159 +#define TK_ELSE 160 +#define TK_INDEX 161 +#define TK_ALTER 162 +#define TK_ADD 163 +#define TK_WINDOW 164 +#define TK_OVER 165 +#define TK_FILTER 166 +#define TK_COLUMN 167 +#define TK_AGG_FUNCTION 168 +#define TK_AGG_COLUMN 169 +#define TK_TRUEFALSE 170 +#define TK_ISNOT 171 +#define TK_FUNCTION 172 +#define TK_UMINUS 173 +#define TK_UPLUS 174 +#define TK_TRUTH 175 +#define TK_REGISTER 176 +#define TK_VECTOR 177 +#define TK_SELECT_COLUMN 178 +#define TK_IF_NULL_ROW 179 +#define TK_ASTERISK 180 +#define TK_SPAN 181 +#define TK_ERROR 182 +#define TK_SPACE 183 +#define TK_ILLEGAL 184 + +/************** End of parse.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stddef.h> + +/* +** Use a macro to replace memcpy() if compiled with SQLITE_INLINE_MEMCPY. +** This allows better measurements of where memcpy() is used when running +** cachegrind. But this macro version of memcpy() is very slow so it +** should not be used in production. This is a performance measurement +** hack only. +*/ +#ifdef SQLITE_INLINE_MEMCPY +# define memcpy(D,S,N) {char*xxd=(char*)(D);const char*xxs=(const char*)(S);\ + int xxn=(N);while(xxn-->0)*(xxd++)=*(xxs++);} +#endif + +/* +** If compiling for a processor that lacks floating point support, +** substitute integer for floating-point +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# define double sqlite_int64 +# define float sqlite_int64 +# define LONGDOUBLE_TYPE sqlite_int64 +# ifndef SQLITE_BIG_DBL +# define SQLITE_BIG_DBL (((sqlite3_int64)1)<<50) +# endif +# define SQLITE_OMIT_DATETIME_FUNCS 1 +# define SQLITE_OMIT_TRACE 1 +# undef SQLITE_MIXED_ENDIAN_64BIT_FLOAT +# undef SQLITE_HAVE_ISNAN +#endif +#ifndef SQLITE_BIG_DBL +# define SQLITE_BIG_DBL (1e99) +#endif + +/* +** OMIT_TEMPDB is set to 1 if SQLITE_OMIT_TEMPDB is defined, or 0 +** afterward. Having this macro allows us to cause the C compiler +** to omit code used by TEMP tables without messy #ifndef statements. +*/ +#ifdef SQLITE_OMIT_TEMPDB +#define OMIT_TEMPDB 1 +#else +#define OMIT_TEMPDB 0 +#endif + +/* +** The "file format" number is an integer that is incremented whenever +** the VDBE-level file format changes. The following macros define the +** the default file format for new databases and the maximum file format +** that the library can read. +*/ +#define SQLITE_MAX_FILE_FORMAT 4 +#ifndef SQLITE_DEFAULT_FILE_FORMAT +# define SQLITE_DEFAULT_FILE_FORMAT 4 +#endif + +/* +** Determine whether triggers are recursive by default. This can be +** changed at run-time using a pragma. +*/ +#ifndef SQLITE_DEFAULT_RECURSIVE_TRIGGERS +# define SQLITE_DEFAULT_RECURSIVE_TRIGGERS 0 +#endif + +/* +** Provide a default value for SQLITE_TEMP_STORE in case it is not specified +** on the command-line +*/ +#ifndef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 1 +#endif + +/* +** If no value has been provided for SQLITE_MAX_WORKER_THREADS, or if +** SQLITE_TEMP_STORE is set to 3 (never use temporary files), set it +** to zero. +*/ +#if SQLITE_TEMP_STORE==3 || SQLITE_THREADSAFE==0 +# undef SQLITE_MAX_WORKER_THREADS +# define SQLITE_MAX_WORKER_THREADS 0 +#endif +#ifndef SQLITE_MAX_WORKER_THREADS +# define SQLITE_MAX_WORKER_THREADS 8 +#endif +#ifndef SQLITE_DEFAULT_WORKER_THREADS +# define SQLITE_DEFAULT_WORKER_THREADS 0 +#endif +#if SQLITE_DEFAULT_WORKER_THREADS>SQLITE_MAX_WORKER_THREADS +# undef SQLITE_MAX_WORKER_THREADS +# define SQLITE_MAX_WORKER_THREADS SQLITE_DEFAULT_WORKER_THREADS +#endif + +/* +** The default initial allocation for the pagecache when using separate +** pagecaches for each database connection. A positive number is the +** number of pages. A negative number N translations means that a buffer +** of -1024*N bytes is allocated and used for as many pages as it will hold. +** +** The default value of "20" was chosen to minimize the run-time of the +** speedtest1 test program with options: --shrink-memory --reprepare +*/ +#ifndef SQLITE_DEFAULT_PCACHE_INITSZ +# define SQLITE_DEFAULT_PCACHE_INITSZ 20 +#endif + +/* +** Default value for the SQLITE_CONFIG_SORTERREF_SIZE option. +*/ +#ifndef SQLITE_DEFAULT_SORTERREF_SIZE +# define SQLITE_DEFAULT_SORTERREF_SIZE 0x7fffffff +#endif + +/* +** The compile-time options SQLITE_MMAP_READWRITE and +** SQLITE_ENABLE_BATCH_ATOMIC_WRITE are not compatible with one another. +** You must choose one or the other (or neither) but not both. +*/ +#if defined(SQLITE_MMAP_READWRITE) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) +#error Cannot use both SQLITE_MMAP_READWRITE and SQLITE_ENABLE_BATCH_ATOMIC_WRITE +#endif + +/* +** GCC does not define the offsetof() macro so we'll have to do it +** ourselves. +*/ +#ifndef offsetof +#define offsetof(STRUCTURE,FIELD) ((int)((char*)&((STRUCTURE*)0)->FIELD)) +#endif + +/* +** Macros to compute minimum and maximum of two numbers. +*/ +#ifndef MIN +# define MIN(A,B) ((A)<(B)?(A):(B)) +#endif +#ifndef MAX +# define MAX(A,B) ((A)>(B)?(A):(B)) +#endif + +/* +** Swap two objects of type TYPE. +*/ +#define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;} + +/* +** Check to see if this machine uses EBCDIC. (Yes, believe it or +** not, there are still machines out there that use EBCDIC.) +*/ +#if 'A' == '\301' +# define SQLITE_EBCDIC 1 +#else +# define SQLITE_ASCII 1 +#endif + +/* +** Integers of known sizes. These typedefs might change for architectures +** where the sizes very. Preprocessor macros are available so that the +** types can be conveniently redefined at compile-type. Like this: +** +** cc '-DUINTPTR_TYPE=long long int' ... +*/ +#ifndef UINT32_TYPE +# ifdef HAVE_UINT32_T +# define UINT32_TYPE uint32_t +# else +# define UINT32_TYPE unsigned int +# endif +#endif +#ifndef UINT16_TYPE +# ifdef HAVE_UINT16_T +# define UINT16_TYPE uint16_t +# else +# define UINT16_TYPE unsigned short int +# endif +#endif +#ifndef INT16_TYPE +# ifdef HAVE_INT16_T +# define INT16_TYPE int16_t +# else +# define INT16_TYPE short int +# endif +#endif +#ifndef UINT8_TYPE +# ifdef HAVE_UINT8_T +# define UINT8_TYPE uint8_t +# else +# define UINT8_TYPE unsigned char +# endif +#endif +#ifndef INT8_TYPE +# ifdef HAVE_INT8_T +# define INT8_TYPE int8_t +# else +# define INT8_TYPE signed char +# endif +#endif +#ifndef LONGDOUBLE_TYPE +# define LONGDOUBLE_TYPE long double +#endif +typedef sqlite_int64 i64; /* 8-byte signed integer */ +typedef sqlite_uint64 u64; /* 8-byte unsigned integer */ +typedef UINT32_TYPE u32; /* 4-byte unsigned integer */ +typedef UINT16_TYPE u16; /* 2-byte unsigned integer */ +typedef INT16_TYPE i16; /* 2-byte signed integer */ +typedef UINT8_TYPE u8; /* 1-byte unsigned integer */ +typedef INT8_TYPE i8; /* 1-byte signed integer */ + +/* +** SQLITE_MAX_U32 is a u64 constant that is the maximum u64 value +** that can be stored in a u32 without loss of data. The value +** is 0x00000000ffffffff. But because of quirks of some compilers, we +** have to specify the value in the less intuitive manner shown: +*/ +#define SQLITE_MAX_U32 ((((u64)1)<<32)-1) + +/* +** The datatype used to store estimates of the number of rows in a +** table or index. +*/ +typedef u64 tRowcnt; + +/* +** Estimated quantities used for query planning are stored as 16-bit +** logarithms. For quantity X, the value stored is 10*log2(X). This +** gives a possible range of values of approximately 1.0e986 to 1e-986. +** But the allowed values are "grainy". Not every value is representable. +** For example, quantities 16 and 17 are both represented by a LogEst +** of 40. However, since LogEst quantities are suppose to be estimates, +** not exact values, this imprecision is not a problem. +** +** "LogEst" is short for "Logarithmic Estimate". +** +** Examples: +** 1 -> 0 20 -> 43 10000 -> 132 +** 2 -> 10 25 -> 46 25000 -> 146 +** 3 -> 16 100 -> 66 1000000 -> 199 +** 4 -> 20 1000 -> 99 1048576 -> 200 +** 10 -> 33 1024 -> 100 4294967296 -> 320 +** +** The LogEst can be negative to indicate fractional values. +** Examples: +** +** 0.5 -> -10 0.1 -> -33 0.0625 -> -40 +*/ +typedef INT16_TYPE LogEst; + +/* +** Set the SQLITE_PTRSIZE macro to the number of bytes in a pointer +*/ +#ifndef SQLITE_PTRSIZE +# if defined(__SIZEOF_POINTER__) +# define SQLITE_PTRSIZE __SIZEOF_POINTER__ +# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(_M_ARM) || defined(__arm__) || defined(__x86) || \ + (defined(__APPLE__) && defined(__POWERPC__)) || \ + (defined(__TOS_AIX__) && !defined(__64BIT__)) +# define SQLITE_PTRSIZE 4 +# else +# define SQLITE_PTRSIZE 8 +# endif +#endif + +/* The uptr type is an unsigned integer large enough to hold a pointer +*/ +#if defined(HAVE_STDINT_H) + typedef uintptr_t uptr; +#elif SQLITE_PTRSIZE==4 + typedef u32 uptr; +#else + typedef u64 uptr; +#endif + +/* +** The SQLITE_WITHIN(P,S,E) macro checks to see if pointer P points to +** something between S (inclusive) and E (exclusive). +** +** In other words, S is a buffer and E is a pointer to the first byte after +** the end of buffer S. This macro returns true if P points to something +** contained within the buffer S. +*/ +#define SQLITE_WITHIN(P,S,E) (((uptr)(P)>=(uptr)(S))&&((uptr)(P)<(uptr)(E))) + +/* +** P is one byte past the end of a large buffer. Return true if a span of bytes +** between S..E crosses the end of that buffer. In other words, return true +** if the sub-buffer S..E-1 overflows the buffer whose last byte is P-1. +** +** S is the start of the span. E is one byte past the end of end of span. +** +** P +** |-----------------| FALSE +** |-------| +** S E +** +** P +** |-----------------| +** |-------| TRUE +** S E +** +** P +** |-----------------| +** |-------| FALSE +** S E +*/ +#define SQLITE_OVERFLOW(P,S,E) (((uptr)(S)<(uptr)(P))&&((uptr)(E)>(uptr)(P))) + +/* +** Macros to determine whether the machine is big or little endian, +** and whether or not that determination is run-time or compile-time. +** +** For best performance, an attempt is made to guess at the byte-order +** using C-preprocessor macros. If that is unsuccessful, or if +** -DSQLITE_BYTEORDER=0 is set, then byte-order is determined +** at run-time. +** +** If you are building SQLite on some obscure platform for which the +** following ifdef magic does not work, you can always include either: +** +** -DSQLITE_BYTEORDER=1234 +** +** or +** +** -DSQLITE_BYTEORDER=4321 +** +** to cause the build to work for little-endian or big-endian processors, +** respectively. +*/ +#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */ +# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__ +# define SQLITE_BYTEORDER 4321 +# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ +# define SQLITE_BYTEORDER 1234 +# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1 +# define SQLITE_BYTEORDER 4321 +# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ + defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64) +# define SQLITE_BYTEORDER 1234 +# elif defined(sparc) || defined(__ARMEB__) || defined(__AARCH64EB__) +# define SQLITE_BYTEORDER 4321 +# else +# define SQLITE_BYTEORDER 0 +# endif +#endif +#if SQLITE_BYTEORDER==4321 +# define SQLITE_BIGENDIAN 1 +# define SQLITE_LITTLEENDIAN 0 +# define SQLITE_UTF16NATIVE SQLITE_UTF16BE +#elif SQLITE_BYTEORDER==1234 +# define SQLITE_BIGENDIAN 0 +# define SQLITE_LITTLEENDIAN 1 +# define SQLITE_UTF16NATIVE SQLITE_UTF16LE +#else +# ifdef SQLITE_AMALGAMATION + const int sqlite3one = 1; +# else + extern const int sqlite3one; +# endif +# define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0) +# define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1) +# define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE) +#endif + +/* +** Constants for the largest and smallest possible 64-bit signed integers. +** These macros are designed to work correctly on both 32-bit and 64-bit +** compilers. +*/ +#define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) +#define LARGEST_UINT64 (0xffffffff|(((u64)0xffffffff)<<32)) +#define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) + +/* +** Round up a number to the next larger multiple of 8. This is used +** to force 8-byte alignment on 64-bit architectures. +** +** ROUND8() always does the rounding, for any argument. +** +** ROUND8P() assumes that the argument is already an integer number of +** pointers in size, and so it is a no-op on systems where the pointer +** size is 8. +*/ +#define ROUND8(x) (((x)+7)&~7) +#if SQLITE_PTRSIZE==8 +# define ROUND8P(x) (x) +#else +# define ROUND8P(x) (((x)+7)&~7) +#endif + +/* +** Round down to the nearest multiple of 8 +*/ +#define ROUNDDOWN8(x) ((x)&~7) + +/* +** Assert that the pointer X is aligned to an 8-byte boundary. This +** macro is used only within assert() to verify that the code gets +** all alignment restrictions correct. +** +** Except, if SQLITE_4_BYTE_ALIGNED_MALLOC is defined, then the +** underlying malloc() implementation might return us 4-byte aligned +** pointers. In that case, only verify 4-byte alignment. +*/ +#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC +# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&3)==0) +#else +# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) +#endif + +/* +** Disable MMAP on platforms where it is known to not work +*/ +#if defined(__OpenBSD__) || defined(__QNXNTO__) +# undef SQLITE_MAX_MMAP_SIZE +# define SQLITE_MAX_MMAP_SIZE 0 +#endif + +/* +** Default maximum size of memory used by memory-mapped I/O in the VFS +*/ +#ifdef __APPLE__ +# include <TargetConditionals.h> +#endif +#ifndef SQLITE_MAX_MMAP_SIZE +# if defined(__linux__) \ + || defined(_WIN32) \ + || (defined(__APPLE__) && defined(__MACH__)) \ + || defined(__sun) \ + || defined(__FreeBSD__) \ + || defined(__DragonFly__) +# define SQLITE_MAX_MMAP_SIZE 0x7fff0000 /* 2147418112 */ +# else +# define SQLITE_MAX_MMAP_SIZE 0 +# endif +#endif + +/* +** The default MMAP_SIZE is zero on all platforms. Or, even if a larger +** default MMAP_SIZE is specified at compile-time, make sure that it does +** not exceed the maximum mmap size. +*/ +#ifndef SQLITE_DEFAULT_MMAP_SIZE +# define SQLITE_DEFAULT_MMAP_SIZE 0 +#endif +#if SQLITE_DEFAULT_MMAP_SIZE>SQLITE_MAX_MMAP_SIZE +# undef SQLITE_DEFAULT_MMAP_SIZE +# define SQLITE_DEFAULT_MMAP_SIZE SQLITE_MAX_MMAP_SIZE +#endif + +/* +** TREETRACE_ENABLED will be either 1 or 0 depending on whether or not +** the Abstract Syntax Tree tracing logic is turned on. +*/ +#if !defined(SQLITE_AMALGAMATION) +SQLITE_PRIVATE u32 sqlite3TreeTrace; +#endif +#if defined(SQLITE_DEBUG) \ + && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE) \ + || defined(SQLITE_ENABLE_TREETRACE)) +# define TREETRACE_ENABLED 1 +# define TREETRACE(K,P,S,X) \ + if(sqlite3TreeTrace&(K)) \ + sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\ + sqlite3DebugPrintf X +#else +# define TREETRACE(K,P,S,X) +# define TREETRACE_ENABLED 0 +#endif + +/* TREETRACE flag meanings: +** +** 0x00000001 Beginning and end of SELECT processing +** 0x00000002 WHERE clause processing +** 0x00000004 Query flattener +** 0x00000008 Result-set wildcard expansion +** 0x00000010 Query name resolution +** 0x00000020 Aggregate analysis +** 0x00000040 Window functions +** 0x00000080 Generated column names +** 0x00000100 Move HAVING terms into WHERE +** 0x00000200 Count-of-view optimization +** 0x00000400 Compound SELECT processing +** 0x00000800 Drop superfluous ORDER BY +** 0x00001000 LEFT JOIN simplifies to JOIN +** 0x00002000 Constant propagation +** 0x00004000 Push-down optimization +** 0x00008000 After all FROM-clause analysis +** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing +** 0x00020000 Transform DISTINCT into GROUP BY +** 0x00040000 SELECT tree dump after all code has been generated +*/ + +/* +** Macros for "wheretrace" +*/ +SQLITE_PRIVATE u32 sqlite3WhereTrace; +#if defined(SQLITE_DEBUG) \ + && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHERETRACE)) +# define WHERETRACE(K,X) if(sqlite3WhereTrace&(K)) sqlite3DebugPrintf X +# define WHERETRACE_ENABLED 1 +#else +# define WHERETRACE(K,X) +#endif + +/* +** Bits for the sqlite3WhereTrace mask: +** +** (---any--) Top-level block structure +** 0x-------F High-level debug messages +** 0x----FFF- More detail +** 0xFFFF---- Low-level debug messages +** +** 0x00000001 Code generation +** 0x00000002 Solver +** 0x00000004 Solver costs +** 0x00000008 WhereLoop inserts +** +** 0x00000010 Display sqlite3_index_info xBestIndex calls +** 0x00000020 Range an equality scan metrics +** 0x00000040 IN operator decisions +** 0x00000080 WhereLoop cost adjustements +** 0x00000100 +** 0x00000200 Covering index decisions +** 0x00000400 OR optimization +** 0x00000800 Index scanner +** 0x00001000 More details associated with code generation +** 0x00002000 +** 0x00004000 Show all WHERE terms at key points +** 0x00008000 Show the full SELECT statement at key places +** +** 0x00010000 Show more detail when printing WHERE terms +** 0x00020000 Show WHERE terms returned from whereScanNext() +*/ + + +/* +** An instance of the following structure is used to store the busy-handler +** callback for a given sqlite handle. +** +** The sqlite.busyHandler member of the sqlite struct contains the busy +** callback for the database handle. Each pager opened via the sqlite +** handle is passed a pointer to sqlite.busyHandler. The busy-handler +** callback is currently invoked only from within pager.c. +*/ +typedef struct BusyHandler BusyHandler; +struct BusyHandler { + int (*xBusyHandler)(void *,int); /* The busy callback */ + void *pBusyArg; /* First arg to busy callback */ + int nBusy; /* Incremented with each busy call */ +}; + +/* +** Name of table that holds the database schema. +** +** The PREFERRED names are used wherever possible. But LEGACY is also +** used for backwards compatibility. +** +** 1. Queries can use either the PREFERRED or the LEGACY names +** 2. The sqlite3_set_authorizer() callback uses the LEGACY name +** 3. The PRAGMA table_list statement uses the PREFERRED name +** +** The LEGACY names are stored in the internal symbol hash table +** in support of (2). Names are translated using sqlite3PreferredTableName() +** for (3). The sqlite3FindTable() function takes care of translating +** names for (1). +** +** Note that "sqlite_temp_schema" can also be called "temp.sqlite_schema". +*/ +#define LEGACY_SCHEMA_TABLE "sqlite_master" +#define LEGACY_TEMP_SCHEMA_TABLE "sqlite_temp_master" +#define PREFERRED_SCHEMA_TABLE "sqlite_schema" +#define PREFERRED_TEMP_SCHEMA_TABLE "sqlite_temp_schema" + + +/* +** The root-page of the schema table. +*/ +#define SCHEMA_ROOT 1 + +/* +** The name of the schema table. The name is different for TEMP. +*/ +#define SCHEMA_TABLE(x) \ + ((!OMIT_TEMPDB)&&(x==1)?LEGACY_TEMP_SCHEMA_TABLE:LEGACY_SCHEMA_TABLE) + +/* +** A convenience macro that returns the number of elements in +** an array. +*/ +#define ArraySize(X) ((int)(sizeof(X)/sizeof(X[0]))) + +/* +** Determine if the argument is a power of two +*/ +#define IsPowerOfTwo(X) (((X)&((X)-1))==0) + +/* +** The following value as a destructor means to use sqlite3DbFree(). +** The sqlite3DbFree() routine requires two parameters instead of the +** one parameter that destructors normally want. So we have to introduce +** this magic value that the code knows to handle differently. Any +** pointer will work here as long as it is distinct from SQLITE_STATIC +** and SQLITE_TRANSIENT. +*/ +#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3OomClear) + +/* +** When SQLITE_OMIT_WSD is defined, it means that the target platform does +** not support Writable Static Data (WSD) such as global and static variables. +** All variables must either be on the stack or dynamically allocated from +** the heap. When WSD is unsupported, the variable declarations scattered +** throughout the SQLite code must become constants instead. The SQLITE_WSD +** macro is used for this purpose. And instead of referencing the variable +** directly, we use its constant as a key to lookup the run-time allocated +** buffer that holds real variable. The constant is also the initializer +** for the run-time allocated buffer. +** +** In the usual case where WSD is supported, the SQLITE_WSD and GLOBAL +** macros become no-ops and have zero performance impact. +*/ +#ifdef SQLITE_OMIT_WSD + #define SQLITE_WSD const + #define GLOBAL(t,v) (*(t*)sqlite3_wsd_find((void*)&(v), sizeof(v))) + #define sqlite3GlobalConfig GLOBAL(struct Sqlite3Config, sqlite3Config) +SQLITE_API int sqlite3_wsd_init(int N, int J); +SQLITE_API void *sqlite3_wsd_find(void *K, int L); +#else + #define SQLITE_WSD + #define GLOBAL(t,v) v + #define sqlite3GlobalConfig sqlite3Config +#endif + +/* +** The following macros are used to suppress compiler warnings and to +** make it clear to human readers when a function parameter is deliberately +** left unused within the body of a function. This usually happens when +** a function is called via a function pointer. For example the +** implementation of an SQL aggregate step callback may not use the +** parameter indicating the number of arguments passed to the aggregate, +** if it knows that this is enforced elsewhere. +** +** When a function parameter is not used at all within the body of a function, +** it is generally named "NotUsed" or "NotUsed2" to make things even clearer. +** However, these macros may also be used to suppress warnings related to +** parameters that may or may not be used depending on compilation options. +** For example those parameters only used in assert() statements. In these +** cases the parameters are named as per the usual conventions. +*/ +#define UNUSED_PARAMETER(x) (void)(x) +#define UNUSED_PARAMETER2(x,y) UNUSED_PARAMETER(x),UNUSED_PARAMETER(y) + +/* +** Forward references to structures +*/ +typedef struct AggInfo AggInfo; +typedef struct AuthContext AuthContext; +typedef struct AutoincInfo AutoincInfo; +typedef struct Bitvec Bitvec; +typedef struct CollSeq CollSeq; +typedef struct Column Column; +typedef struct Cte Cte; +typedef struct CteUse CteUse; +typedef struct Db Db; +typedef struct DbClientData DbClientData; +typedef struct DbFixer DbFixer; +typedef struct Schema Schema; +typedef struct Expr Expr; +typedef struct ExprList ExprList; +typedef struct FKey FKey; +typedef struct FpDecode FpDecode; +typedef struct FuncDestructor FuncDestructor; +typedef struct FuncDef FuncDef; +typedef struct FuncDefHash FuncDefHash; +typedef struct IdList IdList; +typedef struct Index Index; +typedef struct IndexedExpr IndexedExpr; +typedef struct IndexSample IndexSample; +typedef struct KeyClass KeyClass; +typedef struct KeyInfo KeyInfo; +typedef struct Lookaside Lookaside; +typedef struct LookasideSlot LookasideSlot; +typedef struct Module Module; +typedef struct NameContext NameContext; +typedef struct OnOrUsing OnOrUsing; +typedef struct Parse Parse; +typedef struct ParseCleanup ParseCleanup; +typedef struct PreUpdate PreUpdate; +typedef struct PrintfArguments PrintfArguments; +typedef struct RCStr RCStr; +typedef struct RenameToken RenameToken; +typedef struct Returning Returning; +typedef struct RowSet RowSet; +typedef struct Savepoint Savepoint; +typedef struct Select Select; +typedef struct SQLiteThread SQLiteThread; +typedef struct SelectDest SelectDest; +typedef struct SrcItem SrcItem; +typedef struct SrcList SrcList; +typedef struct sqlite3_str StrAccum; /* Internal alias for sqlite3_str */ +typedef struct Table Table; +typedef struct TableLock TableLock; +typedef struct Token Token; +typedef struct TreeView TreeView; +typedef struct Trigger Trigger; +typedef struct TriggerPrg TriggerPrg; +typedef struct TriggerStep TriggerStep; +typedef struct UnpackedRecord UnpackedRecord; +typedef struct Upsert Upsert; +typedef struct VTable VTable; +typedef struct VtabCtx VtabCtx; +typedef struct Walker Walker; +typedef struct WhereInfo WhereInfo; +typedef struct Window Window; +typedef struct With With; + + +/* +** The bitmask datatype defined below is used for various optimizations. +** +** Changing this from a 64-bit to a 32-bit type limits the number of +** tables in a join to 32 instead of 64. But it also reduces the size +** of the library by 738 bytes on ix86. +*/ +#ifdef SQLITE_BITMASK_TYPE + typedef SQLITE_BITMASK_TYPE Bitmask; +#else + typedef u64 Bitmask; +#endif + +/* +** The number of bits in a Bitmask. "BMS" means "BitMask Size". +*/ +#define BMS ((int)(sizeof(Bitmask)*8)) + +/* +** A bit in a Bitmask +*/ +#define MASKBIT(n) (((Bitmask)1)<<(n)) +#define MASKBIT64(n) (((u64)1)<<(n)) +#define MASKBIT32(n) (((unsigned int)1)<<(n)) +#define SMASKBIT32(n) ((n)<=31?((unsigned int)1)<<(n):0) +#define ALLBITS ((Bitmask)-1) +#define TOPBIT (((Bitmask)1)<<(BMS-1)) + +/* A VList object records a mapping between parameters/variables/wildcards +** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer +** variable number associated with that parameter. See the format description +** on the sqlite3VListAdd() routine for more information. A VList is really +** just an array of integers. +*/ +typedef int VList; + +/* +** Defer sourcing vdbe.h and btree.h until after the "u8" and +** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque +** pointer types (i.e. FuncDef) defined above. +*/ +/************** Include os.h in the middle of sqliteInt.h ********************/ +/************** Begin file os.h **********************************************/ +/* +** 2001 September 16 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file (together with is companion C source-code file +** "os.c") attempt to abstract the underlying operating system so that +** the SQLite library will work on both POSIX and windows systems. +** +** This header file is #include-ed by sqliteInt.h and thus ends up +** being included by every source file. +*/ +#ifndef _SQLITE_OS_H_ +#define _SQLITE_OS_H_ + +/* +** Attempt to automatically detect the operating system and setup the +** necessary pre-processor macros for it. +*/ +/************** Include os_setup.h in the middle of os.h *********************/ +/************** Begin file os_setup.h ****************************************/ +/* +** 2013 November 25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains pre-processor directives related to operating system +** detection and/or setup. +*/ +#ifndef SQLITE_OS_SETUP_H +#define SQLITE_OS_SETUP_H + +/* +** Figure out if we are dealing with Unix, Windows, or some other operating +** system. +** +** After the following block of preprocess macros, all of +** +** SQLITE_OS_KV +** SQLITE_OS_OTHER +** SQLITE_OS_UNIX +** SQLITE_OS_WIN +** +** will defined to either 1 or 0. One of them will be 1. The others will be 0. +** If none of the macros are initially defined, then select either +** SQLITE_OS_UNIX or SQLITE_OS_WIN depending on the target platform. +** +** If SQLITE_OS_OTHER=1 is specified at compile-time, then the application +** must provide its own VFS implementation together with sqlite3_os_init() +** and sqlite3_os_end() routines. +*/ +#if !defined(SQLITE_OS_KV) && !defined(SQLITE_OS_OTHER) && \ + !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_WIN) +# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \ + defined(__MINGW32__) || defined(__BORLANDC__) +# define SQLITE_OS_WIN 1 +# define SQLITE_OS_UNIX 0 +# else +# define SQLITE_OS_WIN 0 +# define SQLITE_OS_UNIX 1 +# endif +#endif +#if SQLITE_OS_OTHER+1>1 +# undef SQLITE_OS_KV +# define SQLITE_OS_KV 0 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +#endif +#if SQLITE_OS_KV+1>1 +# undef SQLITE_OS_OTHER +# define SQLITE_OS_OTHER 0 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +# define SQLITE_OMIT_LOAD_EXTENSION 1 +# define SQLITE_OMIT_WAL 1 +# define SQLITE_OMIT_DEPRECATED 1 +# undef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 3 /* Always use memory for temporary storage */ +# define SQLITE_DQS 0 +# define SQLITE_OMIT_SHARED_CACHE 1 +# define SQLITE_OMIT_AUTOINIT 1 +#endif +#if SQLITE_OS_UNIX+1>1 +# undef SQLITE_OS_KV +# define SQLITE_OS_KV 0 +# undef SQLITE_OS_OTHER +# define SQLITE_OS_OTHER 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +#endif +#if SQLITE_OS_WIN+1>1 +# undef SQLITE_OS_KV +# define SQLITE_OS_KV 0 +# undef SQLITE_OS_OTHER +# define SQLITE_OS_OTHER 0 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +#endif + + +#endif /* SQLITE_OS_SETUP_H */ + +/************** End of os_setup.h ********************************************/ +/************** Continuing where we left off in os.h *************************/ + +/* If the SET_FULLSYNC macro is not defined above, then make it +** a no-op +*/ +#ifndef SET_FULLSYNC +# define SET_FULLSYNC(x,y) +#endif + +/* Maximum pathname length. Note: FILENAME_MAX defined by stdio.h +*/ +#ifndef SQLITE_MAX_PATHLEN +# define SQLITE_MAX_PATHLEN FILENAME_MAX +#endif + +/* Maximum number of symlinks that will be resolved while trying to +** expand a filename in xFullPathname() in the VFS. +*/ +#ifndef SQLITE_MAX_SYMLINK +# define SQLITE_MAX_SYMLINK 200 +#endif + +/* +** The default size of a disk sector +*/ +#ifndef SQLITE_DEFAULT_SECTOR_SIZE +# define SQLITE_DEFAULT_SECTOR_SIZE 4096 +#endif + +/* +** Temporary files are named starting with this prefix followed by 16 random +** alphanumeric characters, and no file extension. They are stored in the +** OS's standard temporary file directory, and are deleted prior to exit. +** If sqlite is being embedded in another program, you may wish to change the +** prefix to reflect your program's name, so that if your program exits +** prematurely, old temporary files can be easily identified. This can be done +** using -DSQLITE_TEMP_FILE_PREFIX=myprefix_ on the compiler command line. +** +** 2006-10-31: The default prefix used to be "sqlite_". But then +** Mcafee started using SQLite in their anti-virus product and it +** started putting files with the "sqlite" name in the c:/temp folder. +** This annoyed many windows users. Those users would then do a +** Google search for "sqlite", find the telephone numbers of the +** developers and call to wake them up at night and complain. +** For this reason, the default name prefix is changed to be "sqlite" +** spelled backwards. So the temp files are still identified, but +** anybody smart enough to figure out the code is also likely smart +** enough to know that calling the developer will not help get rid +** of the file. +*/ +#ifndef SQLITE_TEMP_FILE_PREFIX +# define SQLITE_TEMP_FILE_PREFIX "etilqs_" +#endif + +/* +** The following values may be passed as the second argument to +** sqlite3OsLock(). The various locks exhibit the following semantics: +** +** SHARED: Any number of processes may hold a SHARED lock simultaneously. +** RESERVED: A single process may hold a RESERVED lock on a file at +** any time. Other processes may hold and obtain new SHARED locks. +** PENDING: A single process may hold a PENDING lock on a file at +** any one time. Existing SHARED locks may persist, but no new +** SHARED locks may be obtained by other processes. +** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. +** +** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a +** process that requests an EXCLUSIVE lock may actually obtain a PENDING +** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to +** sqlite3OsLock(). +*/ +#define NO_LOCK 0 +#define SHARED_LOCK 1 +#define RESERVED_LOCK 2 +#define PENDING_LOCK 3 +#define EXCLUSIVE_LOCK 4 + +/* +** File Locking Notes: (Mostly about windows but also some info for Unix) +** +** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because +** those functions are not available. So we use only LockFile() and +** UnlockFile(). +** +** LockFile() prevents not just writing but also reading by other processes. +** A SHARED_LOCK is obtained by locking a single randomly-chosen +** byte out of a specific range of bytes. The lock byte is obtained at +** random so two separate readers can probably access the file at the +** same time, unless they are unlucky and choose the same lock byte. +** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range. +** There can only be one writer. A RESERVED_LOCK is obtained by locking +** a single byte of the file that is designated as the reserved lock byte. +** A PENDING_LOCK is obtained by locking a designated byte different from +** the RESERVED_LOCK byte. +** +** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available, +** which means we can use reader/writer locks. When reader/writer locks +** are used, the lock is placed on the same range of bytes that is used +** for probabilistic locking in Win95/98/ME. Hence, the locking scheme +** will support two or more Win95 readers or two or more WinNT readers. +** But a single Win95 reader will lock out all WinNT readers and a single +** WinNT reader will lock out all other Win95 readers. +** +** The following #defines specify the range of bytes used for locking. +** SHARED_SIZE is the number of bytes available in the pool from which +** a random byte is selected for a shared lock. The pool of bytes for +** shared locks begins at SHARED_FIRST. +** +** The same locking strategy and +** byte ranges are used for Unix. This leaves open the possibility of having +** clients on win95, winNT, and unix all talking to the same shared file +** and all locking correctly. To do so would require that samba (or whatever +** tool is being used for file sharing) implements locks correctly between +** windows and unix. I'm guessing that isn't likely to happen, but by +** using the same locking range we are at least open to the possibility. +** +** Locking in windows is manditory. For this reason, we cannot store +** actual data in the bytes used for locking. The pager never allocates +** the pages involved in locking therefore. SHARED_SIZE is selected so +** that all locks will fit on a single page even at the minimum page size. +** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE +** is set high so that we don't have to allocate an unused page except +** for very large databases. But one should test the page skipping logic +** by setting PENDING_BYTE low and running the entire regression suite. +** +** Changing the value of PENDING_BYTE results in a subtly incompatible +** file format. Depending on how it is changed, you might not notice +** the incompatibility right away, even running a full regression test. +** The default location of PENDING_BYTE is the first byte past the +** 1GB boundary. +** +*/ +#ifdef SQLITE_OMIT_WSD +# define PENDING_BYTE (0x40000000) +#else +# define PENDING_BYTE sqlite3PendingByte +#endif +#define RESERVED_BYTE (PENDING_BYTE+1) +#define SHARED_FIRST (PENDING_BYTE+2) +#define SHARED_SIZE 510 + +/* +** Wrapper around OS specific sqlite3_os_init() function. +*/ +SQLITE_PRIVATE int sqlite3OsInit(void); + +/* +** Functions for accessing sqlite3_file methods +*/ +SQLITE_PRIVATE void sqlite3OsClose(sqlite3_file*); +SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset); +SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file*, const void*, int amt, i64 offset); +SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file*, i64 size); +SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file*, i64 *pSize); +SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut); +SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file*,int,void*); +SQLITE_PRIVATE void sqlite3OsFileControlHint(sqlite3_file*,int,void*); +#define SQLITE_FCNTL_DB_UNCHANGED 0xca093fa0 +SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id); +SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id); +#ifndef SQLITE_OMIT_WAL +SQLITE_PRIVATE int sqlite3OsShmMap(sqlite3_file *,int,int,int,void volatile **); +SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int, int, int); +SQLITE_PRIVATE void sqlite3OsShmBarrier(sqlite3_file *id); +SQLITE_PRIVATE int sqlite3OsShmUnmap(sqlite3_file *id, int); +#endif /* SQLITE_OMIT_WAL */ +SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64, int, void **); +SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *, i64, void *); + + +/* +** Functions for accessing sqlite3_vfs methods +*/ +SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *); +SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int); +SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *, const char *, int, int *pResOut); +SQLITE_PRIVATE int sqlite3OsFullPathname(sqlite3_vfs *, const char *, int, char *); +#ifndef SQLITE_OMIT_LOAD_EXTENSION +SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *, const char *); +SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *, int, char *); +SQLITE_PRIVATE void (*sqlite3OsDlSym(sqlite3_vfs *, void *, const char *))(void); +SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *, void *); +#endif /* SQLITE_OMIT_LOAD_EXTENSION */ +SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *, int, char *); +SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int); +SQLITE_PRIVATE int sqlite3OsGetLastError(sqlite3_vfs*); +SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *, sqlite3_int64*); + +/* +** Convenience functions for opening and closing files using +** sqlite3_malloc() to obtain space for the file-handle structure. +*/ +SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*); +SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *); + +#endif /* _SQLITE_OS_H_ */ + +/************** End of os.h **************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include pager.h in the middle of sqliteInt.h *****************/ +/************** Begin file pager.h *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite page cache +** subsystem. The page cache subsystem reads and writes a file a page +** at a time and provides a journal for rollback. +*/ + +#ifndef SQLITE_PAGER_H +#define SQLITE_PAGER_H + +/* +** Default maximum size for persistent journal files. A negative +** value means no limit. This value may be overridden using the +** sqlite3PagerJournalSizeLimit() API. See also "PRAGMA journal_size_limit". +*/ +#ifndef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT + #define SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT -1 +#endif + +/* +** The type used to represent a page number. The first page in a file +** is called page 1. 0 is used to represent "not a page". +*/ +typedef u32 Pgno; + +/* +** Each open file is managed by a separate instance of the "Pager" structure. +*/ +typedef struct Pager Pager; + +/* +** Handle type for pages. +*/ +typedef struct PgHdr DbPage; + +/* +** Page number PAGER_SJ_PGNO is never used in an SQLite database (it is +** reserved for working around a windows/posix incompatibility). It is +** used in the journal to signify that the remainder of the journal file +** is devoted to storing a super-journal name - there are no more pages to +** roll back. See comments for function writeSuperJournal() in pager.c +** for details. +*/ +#define PAGER_SJ_PGNO_COMPUTED(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1)) +#define PAGER_SJ_PGNO(x) ((x)->lckPgno) + +/* +** Allowed values for the flags parameter to sqlite3PagerOpen(). +** +** NOTE: These values must match the corresponding BTREE_ values in btree.h. +*/ +#define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */ +#define PAGER_MEMORY 0x0002 /* In-memory database */ + +/* +** Valid values for the second argument to sqlite3PagerLockingMode(). +*/ +#define PAGER_LOCKINGMODE_QUERY -1 +#define PAGER_LOCKINGMODE_NORMAL 0 +#define PAGER_LOCKINGMODE_EXCLUSIVE 1 + +/* +** Numeric constants that encode the journalmode. +** +** The numeric values encoded here (other than PAGER_JOURNALMODE_QUERY) +** are exposed in the API via the "PRAGMA journal_mode" command and +** therefore cannot be changed without a compatibility break. +*/ +#define PAGER_JOURNALMODE_QUERY (-1) /* Query the value of journalmode */ +#define PAGER_JOURNALMODE_DELETE 0 /* Commit by deleting journal file */ +#define PAGER_JOURNALMODE_PERSIST 1 /* Commit by zeroing journal header */ +#define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */ +#define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */ +#define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */ +#define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */ + +/* +** Flags that make up the mask passed to sqlite3PagerGet(). +*/ +#define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */ +#define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */ + +/* +** Flags for sqlite3PagerSetFlags() +** +** Value constraints (enforced via assert()): +** PAGER_FULLFSYNC == SQLITE_FullFSync +** PAGER_CKPT_FULLFSYNC == SQLITE_CkptFullFSync +** PAGER_CACHE_SPILL == SQLITE_CacheSpill +*/ +#define PAGER_SYNCHRONOUS_OFF 0x01 /* PRAGMA synchronous=OFF */ +#define PAGER_SYNCHRONOUS_NORMAL 0x02 /* PRAGMA synchronous=NORMAL */ +#define PAGER_SYNCHRONOUS_FULL 0x03 /* PRAGMA synchronous=FULL */ +#define PAGER_SYNCHRONOUS_EXTRA 0x04 /* PRAGMA synchronous=EXTRA */ +#define PAGER_SYNCHRONOUS_MASK 0x07 /* Mask for four values above */ +#define PAGER_FULLFSYNC 0x08 /* PRAGMA fullfsync=ON */ +#define PAGER_CKPT_FULLFSYNC 0x10 /* PRAGMA checkpoint_fullfsync=ON */ +#define PAGER_CACHESPILL 0x20 /* PRAGMA cache_spill=ON */ +#define PAGER_FLAGS_MASK 0x38 /* All above except SYNCHRONOUS */ + +/* +** The remainder of this file contains the declarations of the functions +** that make up the Pager sub-system API. See source code comments for +** a detailed description of each routine. +*/ + +/* Open and close a Pager connection. */ +SQLITE_PRIVATE int sqlite3PagerOpen( + sqlite3_vfs*, + Pager **ppPager, + const char*, + int, + int, + int, + void(*)(DbPage*) +); +SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3*); +SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); + +/* Functions used to configure a Pager object. */ +SQLITE_PRIVATE void sqlite3PagerSetBusyHandler(Pager*, int(*)(void *), void *); +SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager*, u32*, int); +SQLITE_PRIVATE Pgno sqlite3PagerMaxPageCount(Pager*, Pgno); +SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager*, int); +SQLITE_PRIVATE int sqlite3PagerSetSpillsize(Pager*, int); +SQLITE_PRIVATE void sqlite3PagerSetMmapLimit(Pager *, sqlite3_int64); +SQLITE_PRIVATE void sqlite3PagerShrink(Pager*); +SQLITE_PRIVATE void sqlite3PagerSetFlags(Pager*,unsigned); +SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *, int); +SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *, int); +SQLITE_PRIVATE int sqlite3PagerGetJournalMode(Pager*); +SQLITE_PRIVATE int sqlite3PagerOkToChangeJournalMode(Pager*); +SQLITE_PRIVATE i64 sqlite3PagerJournalSizeLimit(Pager *, i64); +SQLITE_PRIVATE sqlite3_backup **sqlite3PagerBackupPtr(Pager*); +SQLITE_PRIVATE int sqlite3PagerFlush(Pager*); + +/* Functions used to obtain and release page references. */ +SQLITE_PRIVATE int sqlite3PagerGet(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag); +SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno); +SQLITE_PRIVATE void sqlite3PagerRef(DbPage*); +SQLITE_PRIVATE void sqlite3PagerUnref(DbPage*); +SQLITE_PRIVATE void sqlite3PagerUnrefNotNull(DbPage*); +SQLITE_PRIVATE void sqlite3PagerUnrefPageOne(DbPage*); + +/* Operations on page references. */ +SQLITE_PRIVATE int sqlite3PagerWrite(DbPage*); +SQLITE_PRIVATE void sqlite3PagerDontWrite(DbPage*); +SQLITE_PRIVATE int sqlite3PagerMovepage(Pager*,DbPage*,Pgno,int); +SQLITE_PRIVATE int sqlite3PagerPageRefcount(DbPage*); +SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *); +SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *); + +/* Functions used to manage pager transactions and savepoints. */ +SQLITE_PRIVATE void sqlite3PagerPagecount(Pager*, int*); +SQLITE_PRIVATE int sqlite3PagerBegin(Pager*, int exFlag, int); +SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(Pager*,const char *zSuper, int); +SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager*); +SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zSuper); +SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager*); +SQLITE_PRIVATE int sqlite3PagerRollback(Pager*); +SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int n); +SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); +SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager); + +#ifndef SQLITE_OMIT_WAL +SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, sqlite3*, int, int*, int*); +SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerWalCallback(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen); +SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3*); +# ifdef SQLITE_ENABLE_SNAPSHOT +SQLITE_PRIVATE int sqlite3PagerSnapshotGet(Pager*, sqlite3_snapshot **ppSnapshot); +SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager*, sqlite3_snapshot *pSnapshot); +SQLITE_PRIVATE int sqlite3PagerSnapshotRecover(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot); +SQLITE_PRIVATE void sqlite3PagerSnapshotUnlock(Pager *pPager); +# endif +#endif + +#if !defined(SQLITE_OMIT_WAL) && defined(SQLITE_ENABLE_SETLK_TIMEOUT) +SQLITE_PRIVATE int sqlite3PagerWalWriteLock(Pager*, int); +SQLITE_PRIVATE void sqlite3PagerWalDb(Pager*, sqlite3*); +#else +# define sqlite3PagerWalWriteLock(y,z) SQLITE_OK +# define sqlite3PagerWalDb(x,y) +#endif + +#ifdef SQLITE_DIRECT_OVERFLOW_READ +SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno); +#endif + +#ifdef SQLITE_ENABLE_ZIPVFS +SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager); +#endif + +/* Functions used to query pager state and configuration. */ +SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager*); +SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager*); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*); +#endif +SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager*); +SQLITE_PRIVATE const char *sqlite3PagerFilename(const Pager*, int); +SQLITE_PRIVATE sqlite3_vfs *sqlite3PagerVfs(Pager*); +SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*); +SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager*); +SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager*); +SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); +SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); +SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, int *); +SQLITE_PRIVATE void sqlite3PagerClearCache(Pager*); +SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *); + +/* Functions used to truncate the database file. */ +SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); + +SQLITE_PRIVATE void sqlite3PagerRekey(DbPage*, Pgno, u16); + +/* Functions to support testing and debugging. */ +#if !defined(NDEBUG) || defined(SQLITE_TEST) +SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage*); +SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage*); +#endif +#ifdef SQLITE_TEST +SQLITE_PRIVATE int *sqlite3PagerStats(Pager*); +SQLITE_PRIVATE void sqlite3PagerRefdump(Pager*); + void disable_simulated_io_errors(void); + void enable_simulated_io_errors(void); +#else +# define disable_simulated_io_errors() +# define enable_simulated_io_errors() +#endif + +#if defined(SQLITE_USE_SEH) && !defined(SQLITE_OMIT_WAL) +SQLITE_PRIVATE int sqlite3PagerWalSystemErrno(Pager*); +#endif + +#endif /* SQLITE_PAGER_H */ + +/************** End of pager.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include btree.h in the middle of sqliteInt.h *****************/ +/************** Begin file btree.h *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite B-Tree file +** subsystem. See comments in the source code for a detailed description +** of what each interface routine does. +*/ +#ifndef SQLITE_BTREE_H +#define SQLITE_BTREE_H + +/* TODO: This definition is just included so other modules compile. It +** needs to be revisited. +*/ +#define SQLITE_N_BTREE_META 16 + +/* +** If defined as non-zero, auto-vacuum is enabled by default. Otherwise +** it must be turned on for each database using "PRAGMA auto_vacuum = 1". +*/ +#ifndef SQLITE_DEFAULT_AUTOVACUUM + #define SQLITE_DEFAULT_AUTOVACUUM 0 +#endif + +#define BTREE_AUTOVACUUM_NONE 0 /* Do not do auto-vacuum */ +#define BTREE_AUTOVACUUM_FULL 1 /* Do full auto-vacuum */ +#define BTREE_AUTOVACUUM_INCR 2 /* Incremental vacuum */ + +/* +** Forward declarations of structure +*/ +typedef struct Btree Btree; +typedef struct BtCursor BtCursor; +typedef struct BtShared BtShared; +typedef struct BtreePayload BtreePayload; + + +SQLITE_PRIVATE int sqlite3BtreeOpen( + sqlite3_vfs *pVfs, /* VFS to use with this b-tree */ + const char *zFilename, /* Name of database file to open */ + sqlite3 *db, /* Associated database connection */ + Btree **ppBtree, /* Return open Btree* here */ + int flags, /* Flags */ + int vfsFlags /* Flags passed through to VFS open */ +); + +/* The flags parameter to sqlite3BtreeOpen can be the bitwise or of the +** following values. +** +** NOTE: These values must match the corresponding PAGER_ values in +** pager.h. +*/ +#define BTREE_OMIT_JOURNAL 1 /* Do not create or use a rollback journal */ +#define BTREE_MEMORY 2 /* This is an in-memory DB */ +#define BTREE_SINGLE 4 /* The file contains at most 1 b-tree */ +#define BTREE_UNORDERED 8 /* Use of a hash implementation is OK */ + +SQLITE_PRIVATE int sqlite3BtreeClose(Btree*); +SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeSetSpillSize(Btree*,int); +#if SQLITE_MAX_MMAP_SIZE>0 +SQLITE_PRIVATE int sqlite3BtreeSetMmapLimit(Btree*,sqlite3_int64); +#endif +SQLITE_PRIVATE int sqlite3BtreeSetPagerFlags(Btree*,unsigned); +SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix); +SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*); +SQLITE_PRIVATE Pgno sqlite3BtreeMaxPageCount(Btree*,Pgno); +SQLITE_PRIVATE Pgno sqlite3BtreeLastPage(Btree*); +SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeGetRequestedReserve(Btree*); +SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p); +SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int); +SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *); +SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int,int*); +SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char*); +SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*, int); +SQLITE_PRIVATE int sqlite3BtreeCommit(Btree*); +SQLITE_PRIVATE int sqlite3BtreeRollback(Btree*,int,int); +SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree*, Pgno*, int flags); +SQLITE_PRIVATE int sqlite3BtreeTxnState(Btree*); +SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree*); + +SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *, int, void(*)(void *)); +SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *pBtree); +#ifndef SQLITE_OMIT_SHARED_CACHE +SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *pBtree, int iTab, u8 isWriteLock); +#endif + +/* Savepoints are named, nestable SQL transactions mostly implemented */ +/* in vdbe.c and pager.c See https://sqlite.org/lang_savepoint.html */ +SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *, int, int); + +/* "Checkpoint" only refers to WAL. See https://sqlite.org/wal.html#ckpt */ +#ifndef SQLITE_OMIT_WAL +SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *); +#endif + +SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *); +SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *); +SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *, Btree *); + +SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *); + +/* The flags parameter to sqlite3BtreeCreateTable can be the bitwise OR +** of the flags shown below. +** +** Every SQLite table must have either BTREE_INTKEY or BTREE_BLOBKEY set. +** With BTREE_INTKEY, the table key is a 64-bit integer and arbitrary data +** is stored in the leaves. (BTREE_INTKEY is used for SQL tables.) With +** BTREE_BLOBKEY, the key is an arbitrary BLOB and no content is stored +** anywhere - the key is the content. (BTREE_BLOBKEY is used for SQL +** indices.) +*/ +#define BTREE_INTKEY 1 /* Table has only 64-bit signed integer keys */ +#define BTREE_BLOBKEY 2 /* Table has keys only - no data */ + +SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree*, int, int*); +SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int, i64*); +SQLITE_PRIVATE int sqlite3BtreeClearTableOfCursor(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeTripAllCursors(Btree*, int, int); + +SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *pBtree, int idx, u32 *pValue); +SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value); + +SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p); + +/* +** The second parameter to sqlite3BtreeGetMeta or sqlite3BtreeUpdateMeta +** should be one of the following values. The integer values are assigned +** to constants so that the offset of the corresponding field in an +** SQLite database header may be found using the following formula: +** +** offset = 36 + (idx * 4) +** +** For example, the free-page-count field is located at byte offset 36 of +** the database file header. The incr-vacuum-flag field is located at +** byte offset 64 (== 36+4*7). +** +** The BTREE_DATA_VERSION value is not really a value stored in the header. +** It is a read-only number computed by the pager. But we merge it with +** the header value access routines since its access pattern is the same. +** Call it a "virtual meta value". +*/ +#define BTREE_FREE_PAGE_COUNT 0 +#define BTREE_SCHEMA_VERSION 1 +#define BTREE_FILE_FORMAT 2 +#define BTREE_DEFAULT_CACHE_SIZE 3 +#define BTREE_LARGEST_ROOT_PAGE 4 +#define BTREE_TEXT_ENCODING 5 +#define BTREE_USER_VERSION 6 +#define BTREE_INCR_VACUUM 7 +#define BTREE_APPLICATION_ID 8 +#define BTREE_DATA_VERSION 15 /* A virtual meta-value */ + +/* +** Kinds of hints that can be passed into the sqlite3BtreeCursorHint() +** interface. +** +** BTREE_HINT_RANGE (arguments: Expr*, Mem*) +** +** The first argument is an Expr* (which is guaranteed to be constant for +** the lifetime of the cursor) that defines constraints on which rows +** might be fetched with this cursor. The Expr* tree may contain +** TK_REGISTER nodes that refer to values stored in the array of registers +** passed as the second parameter. In other words, if Expr.op==TK_REGISTER +** then the value of the node is the value in Mem[pExpr.iTable]. Any +** TK_COLUMN node in the expression tree refers to the Expr.iColumn-th +** column of the b-tree of the cursor. The Expr tree will not contain +** any function calls nor subqueries nor references to b-trees other than +** the cursor being hinted. +** +** The design of the _RANGE hint is aid b-tree implementations that try +** to prefetch content from remote machines - to provide those +** implementations with limits on what needs to be prefetched and thereby +** reduce network bandwidth. +** +** Note that BTREE_HINT_FLAGS with BTREE_BULKLOAD is the only hint used by +** standard SQLite. The other hints are provided for extensions that use +** the SQLite parser and code generator but substitute their own storage +** engine. +*/ +#define BTREE_HINT_RANGE 0 /* Range constraints on queries */ + +/* +** Values that may be OR'd together to form the argument to the +** BTREE_HINT_FLAGS hint for sqlite3BtreeCursorHint(): +** +** The BTREE_BULKLOAD flag is set on index cursors when the index is going +** to be filled with content that is already in sorted order. +** +** The BTREE_SEEK_EQ flag is set on cursors that will get OP_SeekGE or +** OP_SeekLE opcodes for a range search, but where the range of entries +** selected will all have the same key. In other words, the cursor will +** be used only for equality key searches. +** +*/ +#define BTREE_BULKLOAD 0x00000001 /* Used to full index in sorted order */ +#define BTREE_SEEK_EQ 0x00000002 /* EQ seeks only - no range seeks */ + +/* +** Flags passed as the third argument to sqlite3BtreeCursor(). +** +** For read-only cursors the wrFlag argument is always zero. For read-write +** cursors it may be set to either (BTREE_WRCSR|BTREE_FORDELETE) or just +** (BTREE_WRCSR). If the BTREE_FORDELETE bit is set, then the cursor will +** only be used by SQLite for the following: +** +** * to seek to and then delete specific entries, and/or +** +** * to read values that will be used to create keys that other +** BTREE_FORDELETE cursors will seek to and delete. +** +** The BTREE_FORDELETE flag is an optimization hint. It is not used by +** by this, the native b-tree engine of SQLite, but it is available to +** alternative storage engines that might be substituted in place of this +** b-tree system. For alternative storage engines in which a delete of +** the main table row automatically deletes corresponding index rows, +** the FORDELETE flag hint allows those alternative storage engines to +** skip a lot of work. Namely: FORDELETE cursors may treat all SEEK +** and DELETE operations as no-ops, and any READ operation against a +** FORDELETE cursor may return a null row: 0x01 0x00. +*/ +#define BTREE_WRCSR 0x00000004 /* read-write cursor */ +#define BTREE_FORDELETE 0x00000008 /* Cursor is for seek/delete only */ + +SQLITE_PRIVATE int sqlite3BtreeCursor( + Btree*, /* BTree containing table to open */ + Pgno iTable, /* Index of root page */ + int wrFlag, /* 1 for writing. 0 for read-only */ + struct KeyInfo*, /* First argument to compare function */ + BtCursor *pCursor /* Space to write cursor structure */ +); +SQLITE_PRIVATE BtCursor *sqlite3BtreeFakeValidCursor(void); +SQLITE_PRIVATE int sqlite3BtreeCursorSize(void); +SQLITE_PRIVATE void sqlite3BtreeCursorZero(BtCursor*); +SQLITE_PRIVATE void sqlite3BtreeCursorHintFlags(BtCursor*, unsigned); +#ifdef SQLITE_ENABLE_CURSOR_HINTS +SQLITE_PRIVATE void sqlite3BtreeCursorHint(BtCursor*, int, ...); +#endif + +SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeTableMoveto( + BtCursor*, + i64 intKey, + int bias, + int *pRes +); +SQLITE_PRIVATE int sqlite3BtreeIndexMoveto( + BtCursor*, + UnpackedRecord *pUnKey, + int *pRes +); +SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor*, int*); +SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags); + +/* Allowed flags for sqlite3BtreeDelete() and sqlite3BtreeInsert() */ +#define BTREE_SAVEPOSITION 0x02 /* Leave cursor pointing at NEXT or PREV */ +#define BTREE_AUXDELETE 0x04 /* not the primary delete operation */ +#define BTREE_APPEND 0x08 /* Insert is likely an append */ +#define BTREE_PREFORMAT 0x80 /* Inserted data is a preformated cell */ + +/* An instance of the BtreePayload object describes the content of a single +** entry in either an index or table btree. +** +** Index btrees (used for indexes and also WITHOUT ROWID tables) contain +** an arbitrary key and no data. These btrees have pKey,nKey set to the +** key and the pData,nData,nZero fields are uninitialized. The aMem,nMem +** fields give an array of Mem objects that are a decomposition of the key. +** The nMem field might be zero, indicating that no decomposition is available. +** +** Table btrees (used for rowid tables) contain an integer rowid used as +** the key and passed in the nKey field. The pKey field is zero. +** pData,nData hold the content of the new entry. nZero extra zero bytes +** are appended to the end of the content when constructing the entry. +** The aMem,nMem fields are uninitialized for table btrees. +** +** Field usage summary: +** +** Table BTrees Index Btrees +** +** pKey always NULL encoded key +** nKey the ROWID length of pKey +** pData data not used +** aMem not used decomposed key value +** nMem not used entries in aMem +** nData length of pData not used +** nZero extra zeros after pData not used +** +** This object is used to pass information into sqlite3BtreeInsert(). The +** same information used to be passed as five separate parameters. But placing +** the information into this object helps to keep the interface more +** organized and understandable, and it also helps the resulting code to +** run a little faster by using fewer registers for parameter passing. +*/ +struct BtreePayload { + const void *pKey; /* Key content for indexes. NULL for tables */ + sqlite3_int64 nKey; /* Size of pKey for indexes. PRIMARY KEY for tabs */ + const void *pData; /* Data for tables. */ + sqlite3_value *aMem; /* First of nMem value in the unpacked pKey */ + u16 nMem; /* Number of aMem[] value. Might be zero */ + int nData; /* Size of pData. 0 if none. */ + int nZero; /* Extra zero data appended after pData,nData */ +}; + +SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload, + int flags, int seekResult); +SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int flags); +SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int flags); +SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor*); +SQLITE_PRIVATE void sqlite3BtreeCursorPin(BtCursor*); +SQLITE_PRIVATE void sqlite3BtreeCursorUnpin(BtCursor*); +SQLITE_PRIVATE i64 sqlite3BtreeOffset(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*); +SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); +SQLITE_PRIVATE u32 sqlite3BtreePayloadSize(BtCursor*); +SQLITE_PRIVATE sqlite3_int64 sqlite3BtreeMaxRecordSize(BtCursor*); + +SQLITE_PRIVATE int sqlite3BtreeIntegrityCheck( + sqlite3 *db, /* Database connection that is running the check */ + Btree *p, /* The btree to be checked */ + Pgno *aRoot, /* An array of root pages numbers for individual trees */ + int nRoot, /* Number of entries in aRoot[] */ + int mxErr, /* Stop reporting errors after this many */ + int *pnErr, /* OUT: Write number of errors seen to this variable */ + char **pzOut /* OUT: Write the error message string here */ +); +SQLITE_PRIVATE struct Pager *sqlite3BtreePager(Btree*); +SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor*); + +#ifndef SQLITE_OMIT_INCRBLOB +SQLITE_PRIVATE int sqlite3BtreePayloadChecked(BtCursor*, u32 offset, u32 amt, void*); +SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*); +SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *); +#endif +SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *); +SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBt, int iVersion); +SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask); +SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *pBt); +SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void); + +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE sqlite3_uint64 sqlite3BtreeSeekCount(Btree*); +#else +# define sqlite3BtreeSeekCount(X) 0 +#endif + +#ifndef NDEBUG +SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor*); +#endif +SQLITE_PRIVATE int sqlite3BtreeCursorIsValidNN(BtCursor*); + +SQLITE_PRIVATE int sqlite3BtreeCount(sqlite3*, BtCursor*, i64*); + +#ifdef SQLITE_TEST +SQLITE_PRIVATE int sqlite3BtreeCursorInfo(BtCursor*, int*, int); +SQLITE_PRIVATE void sqlite3BtreeCursorList(Btree*); +#endif + +#ifndef SQLITE_OMIT_WAL +SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *); +#endif + +SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor*, BtCursor*, i64); + +SQLITE_PRIVATE void sqlite3BtreeClearCache(Btree*); + +/* +** If we are not using shared cache, then there is no need to +** use mutexes to access the BtShared structures. So make the +** Enter and Leave procedures no-ops. +*/ +#ifndef SQLITE_OMIT_SHARED_CACHE +SQLITE_PRIVATE void sqlite3BtreeEnter(Btree*); +SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3*); +SQLITE_PRIVATE int sqlite3BtreeSharable(Btree*); +SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree*); +#else +# define sqlite3BtreeEnter(X) +# define sqlite3BtreeEnterAll(X) +# define sqlite3BtreeSharable(X) 0 +# define sqlite3BtreeEnterCursor(X) +# define sqlite3BtreeConnectionCount(X) 1 +#endif + +#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE +SQLITE_PRIVATE void sqlite3BtreeLeave(Btree*); +SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor*); +SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3*); +#ifndef NDEBUG + /* These routines are used inside assert() statements only. */ +SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree*); +SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3*); +SQLITE_PRIVATE int sqlite3SchemaMutexHeld(sqlite3*,int,Schema*); +#endif +#else + +# define sqlite3BtreeLeave(X) +# define sqlite3BtreeLeaveCursor(X) +# define sqlite3BtreeLeaveAll(X) + +# define sqlite3BtreeHoldsMutex(X) 1 +# define sqlite3BtreeHoldsAllMutexes(X) 1 +# define sqlite3SchemaMutexHeld(X,Y,Z) 1 +#endif + + +#endif /* SQLITE_BTREE_H */ + +/************** End of btree.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include vdbe.h in the middle of sqliteInt.h ******************/ +/************** Begin file vdbe.h ********************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Header file for the Virtual DataBase Engine (VDBE) +** +** This header defines the interface to the virtual database engine +** or VDBE. The VDBE implements an abstract machine that runs a +** simple program to access and modify the underlying database. +*/ +#ifndef SQLITE_VDBE_H +#define SQLITE_VDBE_H +/* #include <stdio.h> */ + +/* +** A single VDBE is an opaque structure named "Vdbe". Only routines +** in the source file sqliteVdbe.c are allowed to see the insides +** of this structure. +*/ +typedef struct Vdbe Vdbe; + +/* +** The names of the following types declared in vdbeInt.h are required +** for the VdbeOp definition. +*/ +typedef struct sqlite3_value Mem; +typedef struct SubProgram SubProgram; + +/* +** A single instruction of the virtual machine has an opcode +** and as many as three operands. The instruction is recorded +** as an instance of the following structure: +*/ +struct VdbeOp { + u8 opcode; /* What operation to perform */ + signed char p4type; /* One of the P4_xxx constants for p4 */ + u16 p5; /* Fifth parameter is an unsigned 16-bit integer */ + int p1; /* First operand */ + int p2; /* Second parameter (often the jump destination) */ + int p3; /* The third parameter */ + union p4union { /* fourth parameter */ + int i; /* Integer value if p4type==P4_INT32 */ + void *p; /* Generic pointer */ + char *z; /* Pointer to data for string (char array) types */ + i64 *pI64; /* Used when p4type is P4_INT64 */ + double *pReal; /* Used when p4type is P4_REAL */ + FuncDef *pFunc; /* Used when p4type is P4_FUNCDEF */ + sqlite3_context *pCtx; /* Used when p4type is P4_FUNCCTX */ + CollSeq *pColl; /* Used when p4type is P4_COLLSEQ */ + Mem *pMem; /* Used when p4type is P4_MEM */ + VTable *pVtab; /* Used when p4type is P4_VTAB */ + KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */ + u32 *ai; /* Used when p4type is P4_INTARRAY */ + SubProgram *pProgram; /* Used when p4type is P4_SUBPROGRAM */ + Table *pTab; /* Used when p4type is P4_TABLE */ +#ifdef SQLITE_ENABLE_CURSOR_HINTS + Expr *pExpr; /* Used when p4type is P4_EXPR */ +#endif + } p4; +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + char *zComment; /* Comment to improve readability */ +#endif +#ifdef SQLITE_VDBE_COVERAGE + u32 iSrcLine; /* Source-code line that generated this opcode + ** with flags in the upper 8 bits */ +#endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + u64 nExec; + u64 nCycle; +#endif +}; +typedef struct VdbeOp VdbeOp; + + +/* +** A sub-routine used to implement a trigger program. +*/ +struct SubProgram { + VdbeOp *aOp; /* Array of opcodes for sub-program */ + int nOp; /* Elements in aOp[] */ + int nMem; /* Number of memory cells required */ + int nCsr; /* Number of cursors required */ + u8 *aOnce; /* Array of OP_Once flags */ + void *token; /* id that may be used to recursive triggers */ + SubProgram *pNext; /* Next sub-program already visited */ +}; + +/* +** A smaller version of VdbeOp used for the VdbeAddOpList() function because +** it takes up less space. +*/ +struct VdbeOpList { + u8 opcode; /* What operation to perform */ + signed char p1; /* First operand */ + signed char p2; /* Second parameter (often the jump destination) */ + signed char p3; /* Third parameter */ +}; +typedef struct VdbeOpList VdbeOpList; + +/* +** Allowed values of VdbeOp.p4type +*/ +#define P4_NOTUSED 0 /* The P4 parameter is not used */ +#define P4_TRANSIENT 0 /* P4 is a pointer to a transient string */ +#define P4_STATIC (-1) /* Pointer to a static string */ +#define P4_COLLSEQ (-2) /* P4 is a pointer to a CollSeq structure */ +#define P4_INT32 (-3) /* P4 is a 32-bit signed integer */ +#define P4_SUBPROGRAM (-4) /* P4 is a pointer to a SubProgram structure */ +#define P4_TABLE (-5) /* P4 is a pointer to a Table structure */ +/* Above do not own any resources. Must free those below */ +#define P4_FREE_IF_LE (-6) +#define P4_DYNAMIC (-6) /* Pointer to memory from sqliteMalloc() */ +#define P4_FUNCDEF (-7) /* P4 is a pointer to a FuncDef structure */ +#define P4_KEYINFO (-8) /* P4 is a pointer to a KeyInfo structure */ +#define P4_EXPR (-9) /* P4 is a pointer to an Expr tree */ +#define P4_MEM (-10) /* P4 is a pointer to a Mem* structure */ +#define P4_VTAB (-11) /* P4 is a pointer to an sqlite3_vtab structure */ +#define P4_REAL (-12) /* P4 is a 64-bit floating point value */ +#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ +#define P4_INTARRAY (-14) /* P4 is a vector of 32-bit integers */ +#define P4_FUNCCTX (-15) /* P4 is a pointer to an sqlite3_context object */ + +/* Error message codes for OP_Halt */ +#define P5_ConstraintNotNull 1 +#define P5_ConstraintUnique 2 +#define P5_ConstraintCheck 3 +#define P5_ConstraintFK 4 + +/* +** The Vdbe.aColName array contains 5n Mem structures, where n is the +** number of columns of data returned by the statement. +*/ +#define COLNAME_NAME 0 +#define COLNAME_DECLTYPE 1 +#define COLNAME_DATABASE 2 +#define COLNAME_TABLE 3 +#define COLNAME_COLUMN 4 +#ifdef SQLITE_ENABLE_COLUMN_METADATA +# define COLNAME_N 5 /* Number of COLNAME_xxx symbols */ +#else +# ifdef SQLITE_OMIT_DECLTYPE +# define COLNAME_N 1 /* Store only the name */ +# else +# define COLNAME_N 2 /* Store the name and decltype */ +# endif +#endif + +/* +** The following macro converts a label returned by sqlite3VdbeMakeLabel() +** into an index into the Parse.aLabel[] array that contains the resolved +** address of that label. +*/ +#define ADDR(X) (~(X)) + +/* +** The makefile scans the vdbe.c source file and creates the "opcodes.h" +** header file that defines a number for each opcode used by the VDBE. +*/ +/************** Include opcodes.h in the middle of vdbe.h ********************/ +/************** Begin file opcodes.h *****************************************/ +/* Automatically generated. Do not edit */ +/* See the tool/mkopcodeh.tcl script for details */ +#define OP_Savepoint 0 +#define OP_AutoCommit 1 +#define OP_Transaction 2 +#define OP_Checkpoint 3 +#define OP_JournalMode 4 +#define OP_Vacuum 5 +#define OP_VFilter 6 /* jump, synopsis: iplan=r[P3] zplan='P4' */ +#define OP_VUpdate 7 /* synopsis: data=r[P3@P2] */ +#define OP_Init 8 /* jump, synopsis: Start at P2 */ +#define OP_Goto 9 /* jump */ +#define OP_Gosub 10 /* jump */ +#define OP_InitCoroutine 11 /* jump */ +#define OP_Yield 12 /* jump */ +#define OP_MustBeInt 13 /* jump */ +#define OP_Jump 14 /* jump */ +#define OP_Once 15 /* jump */ +#define OP_If 16 /* jump */ +#define OP_IfNot 17 /* jump */ +#define OP_IsType 18 /* jump, synopsis: if typeof(P1.P3) in P5 goto P2 */ +#define OP_Not 19 /* same as TK_NOT, synopsis: r[P2]= !r[P1] */ +#define OP_IfNullRow 20 /* jump, synopsis: if P1.nullRow then r[P3]=NULL, goto P2 */ +#define OP_SeekLT 21 /* jump, synopsis: key=r[P3@P4] */ +#define OP_SeekLE 22 /* jump, synopsis: key=r[P3@P4] */ +#define OP_SeekGE 23 /* jump, synopsis: key=r[P3@P4] */ +#define OP_SeekGT 24 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IfNotOpen 25 /* jump, synopsis: if( !csr[P1] ) goto P2 */ +#define OP_IfNoHope 26 /* jump, synopsis: key=r[P3@P4] */ +#define OP_NoConflict 27 /* jump, synopsis: key=r[P3@P4] */ +#define OP_NotFound 28 /* jump, synopsis: key=r[P3@P4] */ +#define OP_Found 29 /* jump, synopsis: key=r[P3@P4] */ +#define OP_SeekRowid 30 /* jump, synopsis: intkey=r[P3] */ +#define OP_NotExists 31 /* jump, synopsis: intkey=r[P3] */ +#define OP_Last 32 /* jump */ +#define OP_IfSmaller 33 /* jump */ +#define OP_SorterSort 34 /* jump */ +#define OP_Sort 35 /* jump */ +#define OP_Rewind 36 /* jump */ +#define OP_SorterNext 37 /* jump */ +#define OP_Prev 38 /* jump */ +#define OP_Next 39 /* jump */ +#define OP_IdxLE 40 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IdxGT 41 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IdxLT 42 /* jump, synopsis: key=r[P3@P4] */ +#define OP_Or 43 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */ +#define OP_And 44 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */ +#define OP_IdxGE 45 /* jump, synopsis: key=r[P3@P4] */ +#define OP_RowSetRead 46 /* jump, synopsis: r[P3]=rowset(P1) */ +#define OP_RowSetTest 47 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */ +#define OP_Program 48 /* jump */ +#define OP_FkIfZero 49 /* jump, synopsis: if fkctr[P1]==0 goto P2 */ +#define OP_IsNull 50 /* jump, same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */ +#define OP_NotNull 51 /* jump, same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */ +#define OP_Ne 52 /* jump, same as TK_NE, synopsis: IF r[P3]!=r[P1] */ +#define OP_Eq 53 /* jump, same as TK_EQ, synopsis: IF r[P3]==r[P1] */ +#define OP_Gt 54 /* jump, same as TK_GT, synopsis: IF r[P3]>r[P1] */ +#define OP_Le 55 /* jump, same as TK_LE, synopsis: IF r[P3]<=r[P1] */ +#define OP_Lt 56 /* jump, same as TK_LT, synopsis: IF r[P3]<r[P1] */ +#define OP_Ge 57 /* jump, same as TK_GE, synopsis: IF r[P3]>=r[P1] */ +#define OP_ElseEq 58 /* jump, same as TK_ESCAPE */ +#define OP_IfPos 59 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ +#define OP_IfNotZero 60 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */ +#define OP_DecrJumpZero 61 /* jump, synopsis: if (--r[P1])==0 goto P2 */ +#define OP_IncrVacuum 62 /* jump */ +#define OP_VNext 63 /* jump */ +#define OP_Filter 64 /* jump, synopsis: if key(P3@P4) not in filter(P1) goto P2 */ +#define OP_PureFunc 65 /* synopsis: r[P3]=func(r[P2@NP]) */ +#define OP_Function 66 /* synopsis: r[P3]=func(r[P2@NP]) */ +#define OP_Return 67 +#define OP_EndCoroutine 68 +#define OP_HaltIfNull 69 /* synopsis: if r[P3]=null halt */ +#define OP_Halt 70 +#define OP_Integer 71 /* synopsis: r[P2]=P1 */ +#define OP_Int64 72 /* synopsis: r[P2]=P4 */ +#define OP_String 73 /* synopsis: r[P2]='P4' (len=P1) */ +#define OP_BeginSubrtn 74 /* synopsis: r[P2]=NULL */ +#define OP_Null 75 /* synopsis: r[P2..P3]=NULL */ +#define OP_SoftNull 76 /* synopsis: r[P1]=NULL */ +#define OP_Blob 77 /* synopsis: r[P2]=P4 (len=P1) */ +#define OP_Variable 78 /* synopsis: r[P2]=parameter(P1,P4) */ +#define OP_Move 79 /* synopsis: r[P2@P3]=r[P1@P3] */ +#define OP_Copy 80 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ +#define OP_SCopy 81 /* synopsis: r[P2]=r[P1] */ +#define OP_IntCopy 82 /* synopsis: r[P2]=r[P1] */ +#define OP_FkCheck 83 +#define OP_ResultRow 84 /* synopsis: output=r[P1@P2] */ +#define OP_CollSeq 85 +#define OP_AddImm 86 /* synopsis: r[P1]=r[P1]+P2 */ +#define OP_RealAffinity 87 +#define OP_Cast 88 /* synopsis: affinity(r[P1]) */ +#define OP_Permutation 89 +#define OP_Compare 90 /* synopsis: r[P1@P3] <-> r[P2@P3] */ +#define OP_IsTrue 91 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */ +#define OP_ZeroOrNull 92 /* synopsis: r[P2] = 0 OR NULL */ +#define OP_Offset 93 /* synopsis: r[P3] = sqlite_offset(P1) */ +#define OP_Column 94 /* synopsis: r[P3]=PX cursor P1 column P2 */ +#define OP_TypeCheck 95 /* synopsis: typecheck(r[P1@P2]) */ +#define OP_Affinity 96 /* synopsis: affinity(r[P1@P2]) */ +#define OP_MakeRecord 97 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ +#define OP_Count 98 /* synopsis: r[P2]=count() */ +#define OP_ReadCookie 99 +#define OP_SetCookie 100 +#define OP_ReopenIdx 101 /* synopsis: root=P2 iDb=P3 */ +#define OP_BitAnd 102 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */ +#define OP_BitOr 103 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */ +#define OP_ShiftLeft 104 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<<r[P1] */ +#define OP_ShiftRight 105 /* same as TK_RSHIFT, synopsis: r[P3]=r[P2]>>r[P1] */ +#define OP_Add 106 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */ +#define OP_Subtract 107 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */ +#define OP_Multiply 108 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */ +#define OP_Divide 109 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */ +#define OP_Remainder 110 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */ +#define OP_Concat 111 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */ +#define OP_OpenRead 112 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenWrite 113 /* synopsis: root=P2 iDb=P3 */ +#define OP_BitNot 114 /* same as TK_BITNOT, synopsis: r[P2]= ~r[P1] */ +#define OP_OpenDup 115 +#define OP_OpenAutoindex 116 /* synopsis: nColumn=P2 */ +#define OP_String8 117 /* same as TK_STRING, synopsis: r[P2]='P4' */ +#define OP_OpenEphemeral 118 /* synopsis: nColumn=P2 */ +#define OP_SorterOpen 119 +#define OP_SequenceTest 120 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ +#define OP_OpenPseudo 121 /* synopsis: P3 columns in r[P2] */ +#define OP_Close 122 +#define OP_ColumnsUsed 123 +#define OP_SeekScan 124 /* synopsis: Scan-ahead up to P1 rows */ +#define OP_SeekHit 125 /* synopsis: set P2<=seekHit<=P3 */ +#define OP_Sequence 126 /* synopsis: r[P2]=cursor[P1].ctr++ */ +#define OP_NewRowid 127 /* synopsis: r[P2]=rowid */ +#define OP_Insert 128 /* synopsis: intkey=r[P3] data=r[P2] */ +#define OP_RowCell 129 +#define OP_Delete 130 +#define OP_ResetCount 131 +#define OP_SorterCompare 132 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ +#define OP_SorterData 133 /* synopsis: r[P2]=data */ +#define OP_RowData 134 /* synopsis: r[P2]=data */ +#define OP_Rowid 135 /* synopsis: r[P2]=PX rowid of P1 */ +#define OP_NullRow 136 +#define OP_SeekEnd 137 +#define OP_IdxInsert 138 /* synopsis: key=r[P2] */ +#define OP_SorterInsert 139 /* synopsis: key=r[P2] */ +#define OP_IdxDelete 140 /* synopsis: key=r[P2@P3] */ +#define OP_DeferredSeek 141 /* synopsis: Move P3 to P1.rowid if needed */ +#define OP_IdxRowid 142 /* synopsis: r[P2]=rowid */ +#define OP_FinishSeek 143 +#define OP_Destroy 144 +#define OP_Clear 145 +#define OP_ResetSorter 146 +#define OP_CreateBtree 147 /* synopsis: r[P2]=root iDb=P1 flags=P3 */ +#define OP_SqlExec 148 +#define OP_ParseSchema 149 +#define OP_LoadAnalysis 150 +#define OP_DropTable 151 +#define OP_DropIndex 152 +#define OP_Real 153 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ +#define OP_DropTrigger 154 +#define OP_IntegrityCk 155 +#define OP_RowSetAdd 156 /* synopsis: rowset(P1)=r[P2] */ +#define OP_Param 157 +#define OP_FkCounter 158 /* synopsis: fkctr[P1]+=P2 */ +#define OP_MemMax 159 /* synopsis: r[P1]=max(r[P1],r[P2]) */ +#define OP_OffsetLimit 160 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ +#define OP_AggInverse 161 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */ +#define OP_AggStep 162 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggStep1 163 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggValue 164 /* synopsis: r[P3]=value N=P2 */ +#define OP_AggFinal 165 /* synopsis: accum=r[P1] N=P2 */ +#define OP_Expire 166 +#define OP_CursorLock 167 +#define OP_CursorUnlock 168 +#define OP_TableLock 169 /* synopsis: iDb=P1 root=P2 write=P3 */ +#define OP_VBegin 170 +#define OP_VCreate 171 +#define OP_VDestroy 172 +#define OP_VOpen 173 +#define OP_VCheck 174 +#define OP_VInitIn 175 /* synopsis: r[P2]=ValueList(P1,P3) */ +#define OP_VColumn 176 /* synopsis: r[P3]=vcolumn(P2) */ +#define OP_VRename 177 +#define OP_Pagecount 178 +#define OP_MaxPgcnt 179 +#define OP_ClrSubtype 180 /* synopsis: r[P1].subtype = 0 */ +#define OP_FilterAdd 181 /* synopsis: filter(P1) += key(P3@P4) */ +#define OP_Trace 182 +#define OP_CursorHint 183 +#define OP_ReleaseReg 184 /* synopsis: release r[P1@P2] mask P3 */ +#define OP_Noop 185 +#define OP_Explain 186 +#define OP_Abortable 187 + +/* Properties such as "out2" or "jump" that are specified in +** comments following the "case" for each opcode in the vdbe.c +** are encoded into bitvectors as follows: +*/ +#define OPFLG_JUMP 0x01 /* jump: P2 holds jmp target */ +#define OPFLG_IN1 0x02 /* in1: P1 is an input */ +#define OPFLG_IN2 0x04 /* in2: P2 is an input */ +#define OPFLG_IN3 0x08 /* in3: P3 is an input */ +#define OPFLG_OUT2 0x10 /* out2: P2 is an output */ +#define OPFLG_OUT3 0x20 /* out3: P3 is an output */ +#define OPFLG_NCYCLE 0x40 /* ncycle:Cycles count against P1 */ +#define OPFLG_INITIALIZER {\ +/* 0 */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x41, 0x00,\ +/* 8 */ 0x01, 0x01, 0x01, 0x01, 0x03, 0x03, 0x01, 0x01,\ +/* 16 */ 0x03, 0x03, 0x01, 0x12, 0x01, 0x49, 0x49, 0x49,\ +/* 24 */ 0x49, 0x01, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49,\ +/* 32 */ 0x41, 0x01, 0x41, 0x41, 0x41, 0x01, 0x41, 0x41,\ +/* 40 */ 0x41, 0x41, 0x41, 0x26, 0x26, 0x41, 0x23, 0x0b,\ +/* 48 */ 0x01, 0x01, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\ +/* 56 */ 0x0b, 0x0b, 0x01, 0x03, 0x03, 0x03, 0x01, 0x41,\ +/* 64 */ 0x01, 0x00, 0x00, 0x02, 0x02, 0x08, 0x00, 0x10,\ +/* 72 */ 0x10, 0x10, 0x00, 0x10, 0x00, 0x10, 0x10, 0x00,\ +/* 80 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x02, 0x02,\ +/* 88 */ 0x02, 0x00, 0x00, 0x12, 0x1e, 0x20, 0x40, 0x00,\ +/* 96 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x40, 0x26, 0x26,\ +/* 104 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,\ +/* 112 */ 0x40, 0x00, 0x12, 0x40, 0x40, 0x10, 0x40, 0x00,\ +/* 120 */ 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x10, 0x10,\ +/* 128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x50,\ +/* 136 */ 0x00, 0x40, 0x04, 0x04, 0x00, 0x40, 0x50, 0x40,\ +/* 144 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\ +/* 152 */ 0x00, 0x10, 0x00, 0x00, 0x06, 0x10, 0x00, 0x04,\ +/* 160 */ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x10, 0x50,\ +/* 176 */ 0x40, 0x00, 0x10, 0x10, 0x02, 0x00, 0x00, 0x00,\ +/* 184 */ 0x00, 0x00, 0x00, 0x00,} + +/* The resolve3P2Values() routine is able to run faster if it knows +** the value of the largest JUMP opcode. The smaller the maximum +** JUMP opcode the better, so the mkopcodeh.tcl script that +** generated this include file strives to group all JUMP opcodes +** together near the beginning of the list. +*/ +#define SQLITE_MX_JUMP_OPCODE 64 /* Maximum JUMP opcode */ + +/************** End of opcodes.h *********************************************/ +/************** Continuing where we left off in vdbe.h ***********************/ + +/* +** Additional non-public SQLITE_PREPARE_* flags +*/ +#define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */ +#define SQLITE_PREPARE_MASK 0x0f /* Mask of public flags */ + +/* +** Prototypes for the VDBE interface. See comments on the implementation +** for a description of what each of these routines does. +*/ +SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse*); +SQLITE_PRIVATE Parse *sqlite3VdbeParser(Vdbe*); +SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe*,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe*,int,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe*,int,int,int); +SQLITE_PRIVATE int sqlite3VdbeGoto(Vdbe*,int); +SQLITE_PRIVATE int sqlite3VdbeLoadString(Vdbe*,int,const char*); +SQLITE_PRIVATE void sqlite3VdbeMultiLoad(Vdbe*,int,const char*,...); +SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe*,int,int,int,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp4(Vdbe*,int,int,int,int,const char *zP4,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp4Dup8(Vdbe*,int,int,int,int,const u8*,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp4Int(Vdbe*,int,int,int,int,int); +SQLITE_PRIVATE int sqlite3VdbeAddFunctionCall(Parse*,int,int,int,int,const FuncDef*,int); +SQLITE_PRIVATE void sqlite3VdbeEndCoroutine(Vdbe*,int); +#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS) +SQLITE_PRIVATE void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N); +SQLITE_PRIVATE void sqlite3VdbeVerifyNoResultRow(Vdbe *p); +#else +# define sqlite3VdbeVerifyNoMallocRequired(A,B) +# define sqlite3VdbeVerifyNoResultRow(A) +#endif +#if defined(SQLITE_DEBUG) +SQLITE_PRIVATE void sqlite3VdbeVerifyAbortable(Vdbe *p, int); +SQLITE_PRIVATE void sqlite3VdbeNoJumpsOutsideSubrtn(Vdbe*,int,int,int); +#else +# define sqlite3VdbeVerifyAbortable(A,B) +# define sqlite3VdbeNoJumpsOutsideSubrtn(A,B,C,D) +#endif +SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno); +#ifndef SQLITE_OMIT_EXPLAIN +SQLITE_PRIVATE int sqlite3VdbeExplain(Parse*,u8,const char*,...); +SQLITE_PRIVATE void sqlite3VdbeExplainPop(Parse*); +SQLITE_PRIVATE int sqlite3VdbeExplainParent(Parse*); +# define ExplainQueryPlan(P) sqlite3VdbeExplain P +# ifdef SQLITE_ENABLE_STMT_SCANSTATUS +# define ExplainQueryPlan2(V,P) (V = sqlite3VdbeExplain P) +# else +# define ExplainQueryPlan2(V,P) ExplainQueryPlan(P) +# endif +# define ExplainQueryPlanPop(P) sqlite3VdbeExplainPop(P) +# define ExplainQueryPlanParent(P) sqlite3VdbeExplainParent(P) +#else +# define ExplainQueryPlan(P) +# define ExplainQueryPlan2(V,P) +# define ExplainQueryPlanPop(P) +# define ExplainQueryPlanParent(P) 0 +# define sqlite3ExplainBreakpoint(A,B) /*no-op*/ +#endif +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN) +SQLITE_PRIVATE void sqlite3ExplainBreakpoint(const char*,const char*); +#else +# define sqlite3ExplainBreakpoint(A,B) /*no-op*/ +#endif +SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe*, int, char*, u16); +SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe*, int addr, u8); +SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1); +SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2); +SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, int addr, int P3); +SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u16 P5); +SQLITE_PRIVATE void sqlite3VdbeTypeofColumn(Vdbe*, int); +SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe*, int addr); +SQLITE_PRIVATE void sqlite3VdbeJumpHereOrPopInst(Vdbe*, int addr); +SQLITE_PRIVATE int sqlite3VdbeChangeToNoop(Vdbe*, int addr); +SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe*, u8 op); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE void sqlite3VdbeReleaseRegisters(Parse*,int addr, int n, u32 mask, int); +#else +# define sqlite3VdbeReleaseRegisters(P,A,N,M,F) +#endif +SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); +SQLITE_PRIVATE void sqlite3VdbeAppendP4(Vdbe*, void *pP4, int p4type); +SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse*, Index*); +SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe*, int); +SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); +SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetLastOp(Vdbe*); +SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Parse*); +SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeMakeReady(Vdbe*,Parse*); +SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe*, int); +SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe*); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *, int); +#endif +SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe*); +SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe*,int); +SQLITE_PRIVATE int sqlite3VdbeSetColName(Vdbe*, int, int, const char *, void(*)(void*)); +SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe*); +SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe*); +SQLITE_PRIVATE u8 sqlite3VdbePrepareFlags(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe*, const char *z, int n, u8); +#ifdef SQLITE_ENABLE_NORMALIZE +SQLITE_PRIVATE void sqlite3VdbeAddDblquoteStr(sqlite3*,Vdbe*,const char*); +SQLITE_PRIVATE int sqlite3VdbeUsesDoubleQuotedString(Vdbe*,const char*); +#endif +SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe*,Vdbe*); +SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe*, int*, int*); +SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe*, int, u8); +SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe*, int); +#ifndef SQLITE_OMIT_TRACE +SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*); +#endif +SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*); +SQLITE_PRIVATE int sqlite3BlobCompare(const Mem*, const Mem*); + +SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*); +SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); +SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int); +SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo*); + +typedef int (*RecordCompare)(int,const void*,UnpackedRecord*); +SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord*); + +SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *); +SQLITE_PRIVATE int sqlite3VdbeHasSubProgram(Vdbe*); + +SQLITE_PRIVATE int sqlite3NotPureFunc(sqlite3_context*); +#ifdef SQLITE_ENABLE_BYTECODE_VTAB +SQLITE_PRIVATE int sqlite3VdbeBytecodeVtabInit(sqlite3*); +#endif + +/* Use SQLITE_ENABLE_COMMENTS to enable generation of extra comments on +** each VDBE opcode. +** +** Use the SQLITE_ENABLE_MODULE_COMMENTS macro to see some extra no-op +** comments in VDBE programs that show key decision points in the code +** generator. +*/ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS +SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe*, const char*, ...); +# define VdbeComment(X) sqlite3VdbeComment X +SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); +# define VdbeNoopComment(X) sqlite3VdbeNoopComment X +# ifdef SQLITE_ENABLE_MODULE_COMMENTS +# define VdbeModuleComment(X) sqlite3VdbeNoopComment X +# else +# define VdbeModuleComment(X) +# endif +#else +# define VdbeComment(X) +# define VdbeNoopComment(X) +# define VdbeModuleComment(X) +#endif + +/* +** The VdbeCoverage macros are used to set a coverage testing point +** for VDBE branch instructions. The coverage testing points are line +** numbers in the sqlite3.c source file. VDBE branch coverage testing +** only works with an amalgamation build. That's ok since a VDBE branch +** coverage build designed for testing the test suite only. No application +** should ever ship with VDBE branch coverage measuring turned on. +** +** VdbeCoverage(v) // Mark the previously coded instruction +** // as a branch +** +** VdbeCoverageIf(v, conditional) // Mark previous if conditional true +** +** VdbeCoverageAlwaysTaken(v) // Previous branch is always taken +** +** VdbeCoverageNeverTaken(v) // Previous branch is never taken +** +** VdbeCoverageNeverNull(v) // Previous three-way branch is only +** // taken on the first two ways. The +** // NULL option is not possible +** +** VdbeCoverageEqNe(v) // Previous OP_Jump is only interested +** // in distinguishing equal and not-equal. +** +** Every VDBE branch operation must be tagged with one of the macros above. +** If not, then when "make test" is run with -DSQLITE_VDBE_COVERAGE and +** -DSQLITE_DEBUG then an ALWAYS() will fail in the vdbeTakeBranch() +** routine in vdbe.c, alerting the developer to the missed tag. +** +** During testing, the test application will invoke +** sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE,...) to set a callback +** routine that is invoked as each bytecode branch is taken. The callback +** contains the sqlite3.c source line number of the VdbeCoverage macro and +** flags to indicate whether or not the branch was taken. The test application +** is responsible for keeping track of this and reporting byte-code branches +** that are never taken. +** +** See the VdbeBranchTaken() macro and vdbeTakeBranch() function in the +** vdbe.c source file for additional information. +*/ +#ifdef SQLITE_VDBE_COVERAGE +SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe*,int); +# define VdbeCoverage(v) sqlite3VdbeSetLineNumber(v,__LINE__) +# define VdbeCoverageIf(v,x) if(x)sqlite3VdbeSetLineNumber(v,__LINE__) +# define VdbeCoverageAlwaysTaken(v) \ + sqlite3VdbeSetLineNumber(v,__LINE__|0x5000000); +# define VdbeCoverageNeverTaken(v) \ + sqlite3VdbeSetLineNumber(v,__LINE__|0x6000000); +# define VdbeCoverageNeverNull(v) \ + sqlite3VdbeSetLineNumber(v,__LINE__|0x4000000); +# define VdbeCoverageNeverNullIf(v,x) \ + if(x)sqlite3VdbeSetLineNumber(v,__LINE__|0x4000000); +# define VdbeCoverageEqNe(v) \ + sqlite3VdbeSetLineNumber(v,__LINE__|0x8000000); +# define VDBE_OFFSET_LINENO(x) (__LINE__+x) +#else +# define VdbeCoverage(v) +# define VdbeCoverageIf(v,x) +# define VdbeCoverageAlwaysTaken(v) +# define VdbeCoverageNeverTaken(v) +# define VdbeCoverageNeverNull(v) +# define VdbeCoverageNeverNullIf(v,x) +# define VdbeCoverageEqNe(v) +# define VDBE_OFFSET_LINENO(x) 0 +#endif + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +SQLITE_PRIVATE void sqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const char*); +SQLITE_PRIVATE void sqlite3VdbeScanStatusRange(Vdbe*, int, int, int); +SQLITE_PRIVATE void sqlite3VdbeScanStatusCounters(Vdbe*, int, int, int); +#else +# define sqlite3VdbeScanStatus(a,b,c,d,e,f) +# define sqlite3VdbeScanStatusRange(a,b,c,d) +# define sqlite3VdbeScanStatusCounters(a,b,c,d) +#endif + +#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) +SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE*, int, VdbeOp*); +#endif + +#if defined(SQLITE_ENABLE_CURSOR_HINTS) && defined(SQLITE_DEBUG) +SQLITE_PRIVATE int sqlite3CursorRangeHintExprCheck(Walker *pWalker, Expr *pExpr); +#endif + +#endif /* SQLITE_VDBE_H */ + +/************** End of vdbe.h ************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include pcache.h in the middle of sqliteInt.h ****************/ +/************** Begin file pcache.h ******************************************/ +/* +** 2008 August 05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite page cache +** subsystem. +*/ + +#ifndef _PCACHE_H_ + +typedef struct PgHdr PgHdr; +typedef struct PCache PCache; + +/* +** Every page in the cache is controlled by an instance of the following +** structure. +*/ +struct PgHdr { + sqlite3_pcache_page *pPage; /* Pcache object page handle */ + void *pData; /* Page data */ + void *pExtra; /* Extra content */ + PCache *pCache; /* PRIVATE: Cache that owns this page */ + PgHdr *pDirty; /* Transient list of dirty sorted by pgno */ + Pager *pPager; /* The pager this page is part of */ + Pgno pgno; /* Page number for this page */ +#ifdef SQLITE_CHECK_PAGES + u32 pageHash; /* Hash of page content */ +#endif + u16 flags; /* PGHDR flags defined below */ + + /********************************************************************** + ** Elements above, except pCache, are public. All that follow are + ** private to pcache.c and should not be accessed by other modules. + ** pCache is grouped with the public elements for efficiency. + */ + i64 nRef; /* Number of users of this page */ + PgHdr *pDirtyNext; /* Next element in list of dirty pages */ + PgHdr *pDirtyPrev; /* Previous element in list of dirty pages */ + /* NB: pDirtyNext and pDirtyPrev are undefined if the + ** PgHdr object is not dirty */ +}; + +/* Bit values for PgHdr.flags */ +#define PGHDR_CLEAN 0x001 /* Page not on the PCache.pDirty list */ +#define PGHDR_DIRTY 0x002 /* Page is on the PCache.pDirty list */ +#define PGHDR_WRITEABLE 0x004 /* Journaled and ready to modify */ +#define PGHDR_NEED_SYNC 0x008 /* Fsync the rollback journal before + ** writing this page to the database */ +#define PGHDR_DONT_WRITE 0x010 /* Do not write content to disk */ +#define PGHDR_MMAP 0x020 /* This is an mmap page object */ + +#define PGHDR_WAL_APPEND 0x040 /* Appended to wal file */ + +/* Initialize and shutdown the page cache subsystem */ +SQLITE_PRIVATE int sqlite3PcacheInitialize(void); +SQLITE_PRIVATE void sqlite3PcacheShutdown(void); + +/* Page cache buffer management: +** These routines implement SQLITE_CONFIG_PAGECACHE. +*/ +SQLITE_PRIVATE void sqlite3PCacheBufferSetup(void *, int sz, int n); + +/* Create a new pager cache. +** Under memory stress, invoke xStress to try to make pages clean. +** Only clean and unpinned pages can be reclaimed. +*/ +SQLITE_PRIVATE int sqlite3PcacheOpen( + int szPage, /* Size of every page */ + int szExtra, /* Extra space associated with each page */ + int bPurgeable, /* True if pages are on backing store */ + int (*xStress)(void*, PgHdr*), /* Call to try to make pages clean */ + void *pStress, /* Argument to xStress */ + PCache *pToInit /* Preallocated space for the PCache */ +); + +/* Modify the page-size after the cache has been created. */ +SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *, int); + +/* Return the size in bytes of a PCache object. Used to preallocate +** storage space. +*/ +SQLITE_PRIVATE int sqlite3PcacheSize(void); + +/* One release per successful fetch. Page is pinned until released. +** Reference counted. +*/ +SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch(PCache*, Pgno, int createFlag); +SQLITE_PRIVATE int sqlite3PcacheFetchStress(PCache*, Pgno, sqlite3_pcache_page**); +SQLITE_PRIVATE PgHdr *sqlite3PcacheFetchFinish(PCache*, Pgno, sqlite3_pcache_page *pPage); +SQLITE_PRIVATE void sqlite3PcacheRelease(PgHdr*); + +SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr*); /* Remove page from cache */ +SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr*); /* Make sure page is marked dirty */ +SQLITE_PRIVATE void sqlite3PcacheMakeClean(PgHdr*); /* Mark a single page as clean */ +SQLITE_PRIVATE void sqlite3PcacheCleanAll(PCache*); /* Mark all dirty list pages as clean */ +SQLITE_PRIVATE void sqlite3PcacheClearWritable(PCache*); + +/* Change a page number. Used by incr-vacuum. */ +SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr*, Pgno); + +/* Remove all pages with pgno>x. Reset the cache if x==0 */ +SQLITE_PRIVATE void sqlite3PcacheTruncate(PCache*, Pgno x); + +/* Get a list of all dirty pages in the cache, sorted by page number */ +SQLITE_PRIVATE PgHdr *sqlite3PcacheDirtyList(PCache*); + +/* Reset and close the cache object */ +SQLITE_PRIVATE void sqlite3PcacheClose(PCache*); + +/* Clear flags from pages of the page cache */ +SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *); + +/* Discard the contents of the cache */ +SQLITE_PRIVATE void sqlite3PcacheClear(PCache*); + +/* Return the total number of outstanding page references */ +SQLITE_PRIVATE i64 sqlite3PcacheRefCount(PCache*); + +/* Increment the reference count of an existing page */ +SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr*); + +SQLITE_PRIVATE i64 sqlite3PcachePageRefcount(PgHdr*); + +/* Return the total number of pages stored in the cache */ +SQLITE_PRIVATE int sqlite3PcachePagecount(PCache*); + +#if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG) +/* Iterate through all dirty pages currently stored in the cache. This +** interface is only available if SQLITE_CHECK_PAGES is defined when the +** library is built. +*/ +SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *)); +#endif + +#if defined(SQLITE_DEBUG) +/* Check invariants on a PgHdr object */ +SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr*); +#endif + +/* Set and get the suggested cache-size for the specified pager-cache. +** +** If no global maximum is configured, then the system attempts to limit +** the total number of pages cached by purgeable pager-caches to the sum +** of the suggested cache-sizes. +*/ +SQLITE_PRIVATE void sqlite3PcacheSetCachesize(PCache *, int); +#ifdef SQLITE_TEST +SQLITE_PRIVATE int sqlite3PcacheGetCachesize(PCache *); +#endif + +/* Set or get the suggested spill-size for the specified pager-cache. +** +** The spill-size is the minimum number of pages in cache before the cache +** will attempt to spill dirty pages by calling xStress. +*/ +SQLITE_PRIVATE int sqlite3PcacheSetSpillsize(PCache *, int); + +/* Free up as much memory as possible from the page cache */ +SQLITE_PRIVATE void sqlite3PcacheShrink(PCache*); + +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT +/* Try to return memory used by the pcache module to the main memory heap */ +SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int); +#endif + +#ifdef SQLITE_TEST +SQLITE_PRIVATE void sqlite3PcacheStats(int*,int*,int*,int*); +#endif + +SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); + +/* Return the header size */ +SQLITE_PRIVATE int sqlite3HeaderSizePcache(void); +SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void); + +/* Number of dirty pages as a percentage of the configured cache size */ +SQLITE_PRIVATE int sqlite3PCachePercentDirty(PCache*); + +#ifdef SQLITE_DIRECT_OVERFLOW_READ +SQLITE_PRIVATE int sqlite3PCacheIsDirty(PCache *pCache); +#endif + +#endif /* _PCACHE_H_ */ + +/************** End of pcache.h **********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include mutex.h in the middle of sqliteInt.h *****************/ +/************** Begin file mutex.h *******************************************/ +/* +** 2007 August 28 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the common header for all mutex implementations. +** The sqliteInt.h header #includes this file so that it is available +** to all source files. We break it out in an effort to keep the code +** better organized. +** +** NOTE: source files should *not* #include this header file directly. +** Source files should #include the sqliteInt.h file and let that file +** include this one indirectly. +*/ + + +/* +** Figure out what version of the code to use. The choices are +** +** SQLITE_MUTEX_OMIT No mutex logic. Not even stubs. The +** mutexes implementation cannot be overridden +** at start-time. +** +** SQLITE_MUTEX_NOOP For single-threaded applications. No +** mutual exclusion is provided. But this +** implementation can be overridden at +** start-time. +** +** SQLITE_MUTEX_PTHREADS For multi-threaded applications on Unix. +** +** SQLITE_MUTEX_W32 For multi-threaded applications on Win32. +*/ +#if !SQLITE_THREADSAFE +# define SQLITE_MUTEX_OMIT +#endif +#if SQLITE_THREADSAFE && !defined(SQLITE_MUTEX_NOOP) +# if SQLITE_OS_UNIX +# define SQLITE_MUTEX_PTHREADS +# elif SQLITE_OS_WIN +# define SQLITE_MUTEX_W32 +# else +# define SQLITE_MUTEX_NOOP +# endif +#endif + +#ifdef SQLITE_MUTEX_OMIT +/* +** If this is a no-op implementation, implement everything as macros. +*/ +#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) +#define sqlite3_mutex_free(X) +#define sqlite3_mutex_enter(X) +#define sqlite3_mutex_try(X) SQLITE_OK +#define sqlite3_mutex_leave(X) +#define sqlite3_mutex_held(X) ((void)(X),1) +#define sqlite3_mutex_notheld(X) ((void)(X),1) +#define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8) +#define sqlite3MutexInit() SQLITE_OK +#define sqlite3MutexEnd() +#define MUTEX_LOGIC(X) +#else +#define MUTEX_LOGIC(X) X +SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); +#endif /* defined(SQLITE_MUTEX_OMIT) */ + +/************** End of mutex.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + +/* The SQLITE_EXTRA_DURABLE compile-time option used to set the default +** synchronous setting to EXTRA. It is no longer supported. +*/ +#ifdef SQLITE_EXTRA_DURABLE +# warning Use SQLITE_DEFAULT_SYNCHRONOUS=3 instead of SQLITE_EXTRA_DURABLE +# define SQLITE_DEFAULT_SYNCHRONOUS 3 +#endif + +/* +** Default synchronous levels. +** +** Note that (for historical reasons) the PAGER_SYNCHRONOUS_* macros differ +** from the SQLITE_DEFAULT_SYNCHRONOUS value by 1. +** +** PAGER_SYNCHRONOUS DEFAULT_SYNCHRONOUS +** OFF 1 0 +** NORMAL 2 1 +** FULL 3 2 +** EXTRA 4 3 +** +** The "PRAGMA synchronous" statement also uses the zero-based numbers. +** In other words, the zero-based numbers are used for all external interfaces +** and the one-based values are used internally. +*/ +#ifndef SQLITE_DEFAULT_SYNCHRONOUS +# define SQLITE_DEFAULT_SYNCHRONOUS 2 +#endif +#ifndef SQLITE_DEFAULT_WAL_SYNCHRONOUS +# define SQLITE_DEFAULT_WAL_SYNCHRONOUS SQLITE_DEFAULT_SYNCHRONOUS +#endif + +/* +** Each database file to be accessed by the system is an instance +** of the following structure. There are normally two of these structures +** in the sqlite.aDb[] array. aDb[0] is the main database file and +** aDb[1] is the database file used to hold temporary tables. Additional +** databases may be attached. +*/ +struct Db { + char *zDbSName; /* Name of this database. (schema name, not filename) */ + Btree *pBt; /* The B*Tree structure for this database file */ + u8 safety_level; /* How aggressive at syncing data to disk */ + u8 bSyncSet; /* True if "PRAGMA synchronous=N" has been run */ + Schema *pSchema; /* Pointer to database schema (possibly shared) */ +}; + +/* +** An instance of the following structure stores a database schema. +** +** Most Schema objects are associated with a Btree. The exception is +** the Schema for the TEMP database (sqlite3.aDb[1]) which is free-standing. +** In shared cache mode, a single Schema object can be shared by multiple +** Btrees that refer to the same underlying BtShared object. +** +** Schema objects are automatically deallocated when the last Btree that +** references them is destroyed. The TEMP Schema is manually freed by +** sqlite3_close(). +* +** A thread must be holding a mutex on the corresponding Btree in order +** to access Schema content. This implies that the thread must also be +** holding a mutex on the sqlite3 connection pointer that owns the Btree. +** For a TEMP Schema, only the connection mutex is required. +*/ +struct Schema { + int schema_cookie; /* Database schema version number for this file */ + int iGeneration; /* Generation counter. Incremented with each change */ + Hash tblHash; /* All tables indexed by name */ + Hash idxHash; /* All (named) indices indexed by name */ + Hash trigHash; /* All triggers indexed by name */ + Hash fkeyHash; /* All foreign keys by referenced table name */ + Table *pSeqTab; /* The sqlite_sequence table used by AUTOINCREMENT */ + u8 file_format; /* Schema format version for this file */ + u8 enc; /* Text encoding used by this database */ + u16 schemaFlags; /* Flags associated with this schema */ + int cache_size; /* Number of pages to use in the cache */ +}; + +/* +** These macros can be used to test, set, or clear bits in the +** Db.pSchema->flags field. +*/ +#define DbHasProperty(D,I,P) (((D)->aDb[I].pSchema->schemaFlags&(P))==(P)) +#define DbHasAnyProperty(D,I,P) (((D)->aDb[I].pSchema->schemaFlags&(P))!=0) +#define DbSetProperty(D,I,P) (D)->aDb[I].pSchema->schemaFlags|=(P) +#define DbClearProperty(D,I,P) (D)->aDb[I].pSchema->schemaFlags&=~(P) + +/* +** Allowed values for the DB.pSchema->flags field. +** +** The DB_SchemaLoaded flag is set after the database schema has been +** read into internal hash tables. +** +** DB_UnresetViews means that one or more views have column names that +** have been filled out. If the schema changes, these column names might +** changes and so the view will need to be reset. +*/ +#define DB_SchemaLoaded 0x0001 /* The schema has been loaded */ +#define DB_UnresetViews 0x0002 /* Some views have defined column names */ +#define DB_ResetWanted 0x0008 /* Reset the schema when nSchemaLock==0 */ + +/* +** The number of different kinds of things that can be limited +** using the sqlite3_limit() interface. +*/ +#define SQLITE_N_LIMIT (SQLITE_LIMIT_WORKER_THREADS+1) + +/* +** Lookaside malloc is a set of fixed-size buffers that can be used +** to satisfy small transient memory allocation requests for objects +** associated with a particular database connection. The use of +** lookaside malloc provides a significant performance enhancement +** (approx 10%) by avoiding numerous malloc/free requests while parsing +** SQL statements. +** +** The Lookaside structure holds configuration information about the +** lookaside malloc subsystem. Each available memory allocation in +** the lookaside subsystem is stored on a linked list of LookasideSlot +** objects. +** +** Lookaside allocations are only allowed for objects that are associated +** with a particular database connection. Hence, schema information cannot +** be stored in lookaside because in shared cache mode the schema information +** is shared by multiple database connections. Therefore, while parsing +** schema information, the Lookaside.bEnabled flag is cleared so that +** lookaside allocations are not used to construct the schema objects. +** +** New lookaside allocations are only allowed if bDisable==0. When +** bDisable is greater than zero, sz is set to zero which effectively +** disables lookaside without adding a new test for the bDisable flag +** in a performance-critical path. sz should be set by to szTrue whenever +** bDisable changes back to zero. +** +** Lookaside buffers are initially held on the pInit list. As they are +** used and freed, they are added back to the pFree list. New allocations +** come off of pFree first, then pInit as a fallback. This dual-list +** allows use to compute a high-water mark - the maximum number of allocations +** outstanding at any point in the past - by subtracting the number of +** allocations on the pInit list from the total number of allocations. +** +** Enhancement on 2019-12-12: Two-size-lookaside +** The default lookaside configuration is 100 slots of 1200 bytes each. +** The larger slot sizes are important for performance, but they waste +** a lot of space, as most lookaside allocations are less than 128 bytes. +** The two-size-lookaside enhancement breaks up the lookaside allocation +** into two pools: One of 128-byte slots and the other of the default size +** (1200-byte) slots. Allocations are filled from the small-pool first, +** failing over to the full-size pool if that does not work. Thus more +** lookaside slots are available while also using less memory. +** This enhancement can be omitted by compiling with +** SQLITE_OMIT_TWOSIZE_LOOKASIDE. +*/ +struct Lookaside { + u32 bDisable; /* Only operate the lookaside when zero */ + u16 sz; /* Size of each buffer in bytes */ + u16 szTrue; /* True value of sz, even if disabled */ + u8 bMalloced; /* True if pStart obtained from sqlite3_malloc() */ + u32 nSlot; /* Number of lookaside slots allocated */ + u32 anStat[3]; /* 0: hits. 1: size misses. 2: full misses */ + LookasideSlot *pInit; /* List of buffers not previously used */ + LookasideSlot *pFree; /* List of available buffers */ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + LookasideSlot *pSmallInit; /* List of small buffers not previously used */ + LookasideSlot *pSmallFree; /* List of available small buffers */ + void *pMiddle; /* First byte past end of full-size buffers and + ** the first byte of LOOKASIDE_SMALL buffers */ +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + void *pStart; /* First byte of available memory space */ + void *pEnd; /* First byte past end of available space */ + void *pTrueEnd; /* True value of pEnd, when db->pnBytesFreed!=0 */ +}; +struct LookasideSlot { + LookasideSlot *pNext; /* Next buffer in the list of free buffers */ +}; + +#define DisableLookaside db->lookaside.bDisable++;db->lookaside.sz=0 +#define EnableLookaside db->lookaside.bDisable--;\ + db->lookaside.sz=db->lookaside.bDisable?0:db->lookaside.szTrue + +/* Size of the smaller allocations in two-size lookaside */ +#ifdef SQLITE_OMIT_TWOSIZE_LOOKASIDE +# define LOOKASIDE_SMALL 0 +#else +# define LOOKASIDE_SMALL 128 +#endif + +/* +** A hash table for built-in function definitions. (Application-defined +** functions use a regular table table from hash.h.) +** +** Hash each FuncDef structure into one of the FuncDefHash.a[] slots. +** Collisions are on the FuncDef.u.pHash chain. Use the SQLITE_FUNC_HASH() +** macro to compute a hash on the function name. +*/ +#define SQLITE_FUNC_HASH_SZ 23 +struct FuncDefHash { + FuncDef *a[SQLITE_FUNC_HASH_SZ]; /* Hash table for functions */ +}; +#define SQLITE_FUNC_HASH(C,L) (((C)+(L))%SQLITE_FUNC_HASH_SZ) + +#ifdef SQLITE_USER_AUTHENTICATION +/* +** Information held in the "sqlite3" database connection object and used +** to manage user authentication. +*/ +typedef struct sqlite3_userauth sqlite3_userauth; +struct sqlite3_userauth { + u8 authLevel; /* Current authentication level */ + int nAuthPW; /* Size of the zAuthPW in bytes */ + char *zAuthPW; /* Password used to authenticate */ + char *zAuthUser; /* User name used to authenticate */ +}; + +/* Allowed values for sqlite3_userauth.authLevel */ +#define UAUTH_Unknown 0 /* Authentication not yet checked */ +#define UAUTH_Fail 1 /* User authentication failed */ +#define UAUTH_User 2 /* Authenticated as a normal user */ +#define UAUTH_Admin 3 /* Authenticated as an administrator */ + +/* Functions used only by user authorization logic */ +SQLITE_PRIVATE int sqlite3UserAuthTable(const char*); +SQLITE_PRIVATE int sqlite3UserAuthCheckLogin(sqlite3*,const char*,u8*); +SQLITE_PRIVATE void sqlite3UserAuthInit(sqlite3*); +SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**); + +#endif /* SQLITE_USER_AUTHENTICATION */ + +/* +** typedef for the authorization callback function. +*/ +#ifdef SQLITE_USER_AUTHENTICATION + typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, + const char*, const char*); +#else + typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, + const char*); +#endif + +#ifndef SQLITE_OMIT_DEPRECATED +/* This is an extra SQLITE_TRACE macro that indicates "legacy" tracing +** in the style of sqlite3_trace() +*/ +#define SQLITE_TRACE_LEGACY 0x40 /* Use the legacy xTrace */ +#define SQLITE_TRACE_XPROFILE 0x80 /* Use the legacy xProfile */ +#else +#define SQLITE_TRACE_LEGACY 0 +#define SQLITE_TRACE_XPROFILE 0 +#endif /* SQLITE_OMIT_DEPRECATED */ +#define SQLITE_TRACE_NONLEGACY_MASK 0x0f /* Normal flags */ + +/* +** Maximum number of sqlite3.aDb[] entries. This is the number of attached +** databases plus 2 for "main" and "temp". +*/ +#define SQLITE_MAX_DB (SQLITE_MAX_ATTACHED+2) + +/* +** Each database connection is an instance of the following structure. +*/ +struct sqlite3 { + sqlite3_vfs *pVfs; /* OS Interface */ + struct Vdbe *pVdbe; /* List of active virtual machines */ + CollSeq *pDfltColl; /* BINARY collseq for the database encoding */ + sqlite3_mutex *mutex; /* Connection mutex */ + Db *aDb; /* All backends */ + int nDb; /* Number of backends currently in use */ + u32 mDbFlags; /* flags recording internal state */ + u64 flags; /* flags settable by pragmas. See below */ + i64 lastRowid; /* ROWID of most recent insert (see above) */ + i64 szMmap; /* Default mmap_size setting */ + u32 nSchemaLock; /* Do not reset the schema when non-zero */ + unsigned int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ + int errCode; /* Most recent error code (SQLITE_*) */ + int errByteOffset; /* Byte offset of error in SQL statement */ + int errMask; /* & result codes with this before returning */ + int iSysErrno; /* Errno value from last system error */ + u32 dbOptFlags; /* Flags to enable/disable optimizations */ + u8 enc; /* Text encoding */ + u8 autoCommit; /* The auto-commit flag. */ + u8 temp_store; /* 1: file 2: memory 0: default */ + u8 mallocFailed; /* True if we have seen a malloc failure */ + u8 bBenignMalloc; /* Do not require OOMs if true */ + u8 dfltLockMode; /* Default locking-mode for attached dbs */ + signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */ + u8 suppressErr; /* Do not issue error messages if true */ + u8 vtabOnConflict; /* Value to return for s3_vtab_on_conflict() */ + u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */ + u8 mTrace; /* zero or more SQLITE_TRACE flags */ + u8 noSharedCache; /* True if no shared-cache backends */ + u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */ + u8 eOpenState; /* Current condition of the connection */ + int nextPagesize; /* Pagesize after VACUUM if >0 */ + i64 nChange; /* Value returned by sqlite3_changes() */ + i64 nTotalChange; /* Value returned by sqlite3_total_changes() */ + int aLimit[SQLITE_N_LIMIT]; /* Limits */ + int nMaxSorterMmap; /* Maximum size of regions mapped by sorter */ + struct sqlite3InitInfo { /* Information used during initialization */ + Pgno newTnum; /* Rootpage of table being initialized */ + u8 iDb; /* Which db file is being initialized */ + u8 busy; /* TRUE if currently initializing */ + unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */ + unsigned imposterTable : 1; /* Building an imposter table */ + unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */ + const char **azInit; /* "type", "name", and "tbl_name" columns */ + } init; + int nVdbeActive; /* Number of VDBEs currently running */ + int nVdbeRead; /* Number of active VDBEs that read or write */ + int nVdbeWrite; /* Number of active VDBEs that read and write */ + int nVdbeExec; /* Number of nested calls to VdbeExec() */ + int nVDestroy; /* Number of active OP_VDestroy operations */ + int nExtension; /* Number of loaded extensions */ + void **aExtension; /* Array of shared library handles */ + union { + void (*xLegacy)(void*,const char*); /* mTrace==SQLITE_TRACE_LEGACY */ + int (*xV2)(u32,void*,void*,void*); /* All other mTrace values */ + } trace; + void *pTraceArg; /* Argument to the trace function */ +#ifndef SQLITE_OMIT_DEPRECATED + void (*xProfile)(void*,const char*,u64); /* Profiling function */ + void *pProfileArg; /* Argument to profile function */ +#endif + void *pCommitArg; /* Argument to xCommitCallback() */ + int (*xCommitCallback)(void*); /* Invoked at every commit. */ + void *pRollbackArg; /* Argument to xRollbackCallback() */ + void (*xRollbackCallback)(void*); /* Invoked at every commit. */ + void *pUpdateArg; + void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64); + void *pAutovacPagesArg; /* Client argument to autovac_pages */ + void (*xAutovacDestr)(void*); /* Destructor for pAutovacPAgesArg */ + unsigned int (*xAutovacPages)(void*,const char*,u32,u32,u32); + Parse *pParse; /* Current parse */ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + void *pPreUpdateArg; /* First argument to xPreUpdateCallback */ + void (*xPreUpdateCallback)( /* Registered using sqlite3_preupdate_hook() */ + void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64 + ); + PreUpdate *pPreUpdate; /* Context for active pre-update callback */ +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +#ifndef SQLITE_OMIT_WAL + int (*xWalCallback)(void *, sqlite3 *, const char *, int); + void *pWalArg; +#endif + void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*); + void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*); + void *pCollNeededArg; + sqlite3_value *pErr; /* Most recent error message */ + union { + volatile int isInterrupted; /* True if sqlite3_interrupt has been called */ + double notUsed1; /* Spacer */ + } u1; + Lookaside lookaside; /* Lookaside malloc configuration */ +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth; /* Access authorization function */ + void *pAuthArg; /* 1st argument to the access auth function */ +#endif +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + int (*xProgress)(void *); /* The progress callback */ + void *pProgressArg; /* Argument to the progress callback */ + unsigned nProgressOps; /* Number of opcodes for progress callback */ +#endif +#ifndef SQLITE_OMIT_VIRTUALTABLE + int nVTrans; /* Allocated size of aVTrans */ + Hash aModule; /* populated by sqlite3_create_module() */ + VtabCtx *pVtabCtx; /* Context for active vtab connect/create */ + VTable **aVTrans; /* Virtual tables with open transactions */ + VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */ +#endif + Hash aFunc; /* Hash table of connection functions */ + Hash aCollSeq; /* All collating sequences */ + BusyHandler busyHandler; /* Busy callback */ + Db aDbStatic[2]; /* Static space for the 2 default backends */ + Savepoint *pSavepoint; /* List of active savepoints */ + int nAnalysisLimit; /* Number of index rows to ANALYZE */ + int busyTimeout; /* Busy handler timeout, in msec */ + int nSavepoint; /* Number of non-transaction savepoints */ + int nStatement; /* Number of nested statement-transactions */ + i64 nDeferredCons; /* Net deferred constraints this transaction. */ + i64 nDeferredImmCons; /* Net deferred immediate constraints */ + int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ + DbClientData *pDbData; /* sqlite3_set_clientdata() content */ +#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY + /* The following variables are all protected by the STATIC_MAIN + ** mutex, not by sqlite3.mutex. They are used by code in notify.c. + ** + ** When X.pUnlockConnection==Y, that means that X is waiting for Y to + ** unlock so that it can proceed. + ** + ** When X.pBlockingConnection==Y, that means that something that X tried + ** tried to do recently failed with an SQLITE_LOCKED error due to locks + ** held by Y. + */ + sqlite3 *pBlockingConnection; /* Connection that caused SQLITE_LOCKED */ + sqlite3 *pUnlockConnection; /* Connection to watch for unlock */ + void *pUnlockArg; /* Argument to xUnlockNotify */ + void (*xUnlockNotify)(void **, int); /* Unlock notify callback */ + sqlite3 *pNextBlocked; /* Next in list of all blocked connections */ +#endif +#ifdef SQLITE_USER_AUTHENTICATION + sqlite3_userauth auth; /* User authentication information */ +#endif +}; + +/* +** A macro to discover the encoding of a database. +*/ +#define SCHEMA_ENC(db) ((db)->aDb[0].pSchema->enc) +#define ENC(db) ((db)->enc) + +/* +** A u64 constant where the lower 32 bits are all zeros. Only the +** upper 32 bits are included in the argument. Necessary because some +** C-compilers still do not accept LL integer literals. +*/ +#define HI(X) ((u64)(X)<<32) + +/* +** Possible values for the sqlite3.flags. +** +** Value constraints (enforced via assert()): +** SQLITE_FullFSync == PAGER_FULLFSYNC +** SQLITE_CkptFullFSync == PAGER_CKPT_FULLFSYNC +** SQLITE_CacheSpill == PAGER_CACHE_SPILL +*/ +#define SQLITE_WriteSchema 0x00000001 /* OK to update SQLITE_SCHEMA */ +#define SQLITE_LegacyFileFmt 0x00000002 /* Create new databases in format 1 */ +#define SQLITE_FullColNames 0x00000004 /* Show full column names on SELECT */ +#define SQLITE_FullFSync 0x00000008 /* Use full fsync on the backend */ +#define SQLITE_CkptFullFSync 0x00000010 /* Use full fsync for checkpoint */ +#define SQLITE_CacheSpill 0x00000020 /* OK to spill pager cache */ +#define SQLITE_ShortColNames 0x00000040 /* Show short columns names */ +#define SQLITE_TrustedSchema 0x00000080 /* Allow unsafe functions and + ** vtabs in the schema definition */ +#define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */ + /* result set is empty */ +#define SQLITE_IgnoreChecks 0x00000200 /* Do not enforce check constraints */ +#define SQLITE_StmtScanStatus 0x00000400 /* Enable stmt_scanstats() counters */ +#define SQLITE_NoCkptOnClose 0x00000800 /* No checkpoint on close()/DETACH */ +#define SQLITE_ReverseOrder 0x00001000 /* Reverse unordered SELECTs */ +#define SQLITE_RecTriggers 0x00002000 /* Enable recursive triggers */ +#define SQLITE_ForeignKeys 0x00004000 /* Enforce foreign key constraints */ +#define SQLITE_AutoIndex 0x00008000 /* Enable automatic indexes */ +#define SQLITE_LoadExtension 0x00010000 /* Enable load_extension */ +#define SQLITE_LoadExtFunc 0x00020000 /* Enable load_extension() SQL func */ +#define SQLITE_EnableTrigger 0x00040000 /* True to enable triggers */ +#define SQLITE_DeferFKs 0x00080000 /* Defer all FK constraints */ +#define SQLITE_QueryOnly 0x00100000 /* Disable database changes */ +#define SQLITE_CellSizeCk 0x00200000 /* Check btree cell sizes on load */ +#define SQLITE_Fts3Tokenizer 0x00400000 /* Enable fts3_tokenizer(2) */ +#define SQLITE_EnableQPSG 0x00800000 /* Query Planner Stability Guarantee*/ +#define SQLITE_TriggerEQP 0x01000000 /* Show trigger EXPLAIN QUERY PLAN */ +#define SQLITE_ResetDatabase 0x02000000 /* Reset the database */ +#define SQLITE_LegacyAlter 0x04000000 /* Legacy ALTER TABLE behaviour */ +#define SQLITE_NoSchemaError 0x08000000 /* Do not report schema parse errors*/ +#define SQLITE_Defensive 0x10000000 /* Input SQL is likely hostile */ +#define SQLITE_DqsDDL 0x20000000 /* dbl-quoted strings allowed in DDL*/ +#define SQLITE_DqsDML 0x40000000 /* dbl-quoted strings allowed in DML*/ +#define SQLITE_EnableView 0x80000000 /* Enable the use of views */ +#define SQLITE_CountRows HI(0x00001) /* Count rows changed by INSERT, */ + /* DELETE, or UPDATE and return */ + /* the count using a callback. */ +#define SQLITE_CorruptRdOnly HI(0x00002) /* Prohibit writes due to error */ +#define SQLITE_ReadUncommit HI(0x00004) /* READ UNCOMMITTED in shared-cache */ + +/* Flags used only if debugging */ +#ifdef SQLITE_DEBUG +#define SQLITE_SqlTrace HI(0x0100000) /* Debug print SQL as it executes */ +#define SQLITE_VdbeListing HI(0x0200000) /* Debug listings of VDBE progs */ +#define SQLITE_VdbeTrace HI(0x0400000) /* True to trace VDBE execution */ +#define SQLITE_VdbeAddopTrace HI(0x0800000) /* Trace sqlite3VdbeAddOp() calls */ +#define SQLITE_VdbeEQP HI(0x1000000) /* Debug EXPLAIN QUERY PLAN */ +#define SQLITE_ParserTrace HI(0x2000000) /* PRAGMA parser_trace=ON */ +#endif + +/* +** Allowed values for sqlite3.mDbFlags +*/ +#define DBFLAG_SchemaChange 0x0001 /* Uncommitted Hash table changes */ +#define DBFLAG_PreferBuiltin 0x0002 /* Preference to built-in funcs */ +#define DBFLAG_Vacuum 0x0004 /* Currently in a VACUUM */ +#define DBFLAG_VacuumInto 0x0008 /* Currently running VACUUM INTO */ +#define DBFLAG_SchemaKnownOk 0x0010 /* Schema is known to be valid */ +#define DBFLAG_InternalFunc 0x0020 /* Allow use of internal functions */ +#define DBFLAG_EncodingFixed 0x0040 /* No longer possible to change enc. */ + +/* +** Bits of the sqlite3.dbOptFlags field that are used by the +** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to +** selectively disable various optimizations. +*/ +#define SQLITE_QueryFlattener 0x00000001 /* Query flattening */ +#define SQLITE_WindowFunc 0x00000002 /* Use xInverse for window functions */ +#define SQLITE_GroupByOrder 0x00000004 /* GROUPBY cover of ORDERBY */ +#define SQLITE_FactorOutConst 0x00000008 /* Constant factoring */ +#define SQLITE_DistinctOpt 0x00000010 /* DISTINCT using indexes */ +#define SQLITE_CoverIdxScan 0x00000020 /* Covering index scans */ +#define SQLITE_OrderByIdxJoin 0x00000040 /* ORDER BY of joins via index */ +#define SQLITE_Transitive 0x00000080 /* Transitive constraints */ +#define SQLITE_OmitNoopJoin 0x00000100 /* Omit unused tables in joins */ +#define SQLITE_CountOfView 0x00000200 /* The count-of-view optimization */ +#define SQLITE_CursorHints 0x00000400 /* Add OP_CursorHint opcodes */ +#define SQLITE_Stat4 0x00000800 /* Use STAT4 data */ + /* TH3 expects this value ^^^^^^^^^^ to be 0x0000800. Don't change it */ +#define SQLITE_PushDown 0x00001000 /* The push-down optimization */ +#define SQLITE_SimplifyJoin 0x00002000 /* Convert LEFT JOIN to JOIN */ +#define SQLITE_SkipScan 0x00004000 /* Skip-scans */ +#define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */ +#define SQLITE_MinMaxOpt 0x00010000 /* The min/max optimization */ +#define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */ +#define SQLITE_OmitOrderBy 0x00040000 /* Omit pointless ORDER BY */ + /* TH3 expects this value ^^^^^^^^^^ to be 0x40000. Coordinate any change */ +#define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filter on searches */ +#define SQLITE_BloomPulldown 0x00100000 /* Run Bloom filters early */ +#define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */ +#define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */ +#define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */ + /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */ +#define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */ +#define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */ +#define SQLITE_NullUnusedCols 0x04000000 /* NULL unused columns in subqueries */ +#define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */ +#define SQLITE_AllOpts 0xffffffff /* All optimizations */ + +/* +** Macros for testing whether or not optimizations are enabled or disabled. +*/ +#define OptimizationDisabled(db, mask) (((db)->dbOptFlags&(mask))!=0) +#define OptimizationEnabled(db, mask) (((db)->dbOptFlags&(mask))==0) + +/* +** Return true if it OK to factor constant expressions into the initialization +** code. The argument is a Parse object for the code generator. +*/ +#define ConstFactorOk(P) ((P)->okConstFactor) + +/* Possible values for the sqlite3.eOpenState field. +** The numbers are randomly selected such that a minimum of three bits must +** change to convert any number to another or to zero +*/ +#define SQLITE_STATE_OPEN 0x76 /* Database is open */ +#define SQLITE_STATE_CLOSED 0xce /* Database is closed */ +#define SQLITE_STATE_SICK 0xba /* Error and awaiting close */ +#define SQLITE_STATE_BUSY 0x6d /* Database currently in use */ +#define SQLITE_STATE_ERROR 0xd5 /* An SQLITE_MISUSE error occurred */ +#define SQLITE_STATE_ZOMBIE 0xa7 /* Close with last statement close */ + +/* +** Each SQL function is defined by an instance of the following +** structure. For global built-in functions (ex: substr(), max(), count()) +** a pointer to this structure is held in the sqlite3BuiltinFunctions object. +** For per-connection application-defined functions, a pointer to this +** structure is held in the db->aHash hash table. +** +** The u.pHash field is used by the global built-ins. The u.pDestructor +** field is used by per-connection app-def functions. +*/ +struct FuncDef { + i8 nArg; /* Number of arguments. -1 means unlimited */ + u32 funcFlags; /* Some combination of SQLITE_FUNC_* */ + void *pUserData; /* User data parameter */ + FuncDef *pNext; /* Next function with same name */ + void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */ + void (*xFinalize)(sqlite3_context*); /* Agg finalizer */ + void (*xValue)(sqlite3_context*); /* Current agg value */ + void (*xInverse)(sqlite3_context*,int,sqlite3_value**); /* inverse agg-step */ + const char *zName; /* SQL name of the function. */ + union { + FuncDef *pHash; /* Next with a different name but the same hash */ + FuncDestructor *pDestructor; /* Reference counted destructor function */ + } u; /* pHash if SQLITE_FUNC_BUILTIN, pDestructor otherwise */ +}; + +/* +** This structure encapsulates a user-function destructor callback (as +** configured using create_function_v2()) and a reference counter. When +** create_function_v2() is called to create a function with a destructor, +** a single object of this type is allocated. FuncDestructor.nRef is set to +** the number of FuncDef objects created (either 1 or 3, depending on whether +** or not the specified encoding is SQLITE_ANY). The FuncDef.pDestructor +** member of each of the new FuncDef objects is set to point to the allocated +** FuncDestructor. +** +** Thereafter, when one of the FuncDef objects is deleted, the reference +** count on this object is decremented. When it reaches 0, the destructor +** is invoked and the FuncDestructor structure freed. +*/ +struct FuncDestructor { + int nRef; + void (*xDestroy)(void *); + void *pUserData; +}; + +/* +** Possible values for FuncDef.flags. Note that the _LENGTH and _TYPEOF +** values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG. And +** SQLITE_FUNC_CONSTANT must be the same as SQLITE_DETERMINISTIC. There +** are assert() statements in the code to verify this. +** +** Value constraints (enforced via assert()): +** SQLITE_FUNC_MINMAX == NC_MinMaxAgg == SF_MinMaxAgg +** SQLITE_FUNC_ANYORDER == NC_OrderAgg == SF_OrderByReqd +** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG +** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG +** SQLITE_FUNC_BYTELEN == OPFLAG_BYTELENARG +** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API +** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API +** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS -- opposite meanings!!! +** SQLITE_FUNC_ENCMASK depends on SQLITE_UTF* macros in the API +** +** Note that even though SQLITE_FUNC_UNSAFE and SQLITE_INNOCUOUS have the +** same bit value, their meanings are inverted. SQLITE_FUNC_UNSAFE is +** used internally and if set means that the function has side effects. +** SQLITE_INNOCUOUS is used by application code and means "not unsafe". +** See multiple instances of tag-20230109-1. +*/ +#define SQLITE_FUNC_ENCMASK 0x0003 /* SQLITE_UTF8, SQLITE_UTF16BE or UTF16LE */ +#define SQLITE_FUNC_LIKE 0x0004 /* Candidate for the LIKE optimization */ +#define SQLITE_FUNC_CASE 0x0008 /* Case-sensitive LIKE-type function */ +#define SQLITE_FUNC_EPHEM 0x0010 /* Ephemeral. Delete with VDBE */ +#define SQLITE_FUNC_NEEDCOLL 0x0020 /* sqlite3GetFuncCollSeq() might be called*/ +#define SQLITE_FUNC_LENGTH 0x0040 /* Built-in length() function */ +#define SQLITE_FUNC_TYPEOF 0x0080 /* Built-in typeof() function */ +#define SQLITE_FUNC_BYTELEN 0x00c0 /* Built-in octet_length() function */ +#define SQLITE_FUNC_COUNT 0x0100 /* Built-in count(*) aggregate */ +/* 0x0200 -- available for reuse */ +#define SQLITE_FUNC_UNLIKELY 0x0400 /* Built-in unlikely() function */ +#define SQLITE_FUNC_CONSTANT 0x0800 /* Constant inputs give a constant output */ +#define SQLITE_FUNC_MINMAX 0x1000 /* True for min() and max() aggregates */ +#define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a + ** single query - might change over time */ +#define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */ +/* 0x8000 -- available for reuse */ +#define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */ +#define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */ +#define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */ +#define SQLITE_FUNC_SUBTYPE 0x00100000 /* Result likely to have sub-type */ +#define SQLITE_FUNC_UNSAFE 0x00200000 /* Function has side effects */ +#define SQLITE_FUNC_INLINE 0x00400000 /* Functions implemented in-line */ +#define SQLITE_FUNC_BUILTIN 0x00800000 /* This is a built-in function */ +#define SQLITE_FUNC_ANYORDER 0x08000000 /* count/min/max aggregate */ + +/* Identifier numbers for each in-line function */ +#define INLINEFUNC_coalesce 0 +#define INLINEFUNC_implies_nonnull_row 1 +#define INLINEFUNC_expr_implies_expr 2 +#define INLINEFUNC_expr_compare 3 +#define INLINEFUNC_affinity 4 +#define INLINEFUNC_iif 5 +#define INLINEFUNC_sqlite_offset 6 +#define INLINEFUNC_unlikely 99 /* Default case */ + +/* +** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are +** used to create the initializers for the FuncDef structures. +** +** FUNCTION(zName, nArg, iArg, bNC, xFunc) +** Used to create a scalar function definition of a function zName +** implemented by C function xFunc that accepts nArg arguments. The +** value passed as iArg is cast to a (void*) and made available +** as the user-data (sqlite3_user_data()) for the function. If +** argument bNC is true, then the SQLITE_FUNC_NEEDCOLL flag is set. +** +** VFUNCTION(zName, nArg, iArg, bNC, xFunc) +** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag. +** +** SFUNCTION(zName, nArg, iArg, bNC, xFunc) +** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag and +** adds the SQLITE_DIRECTONLY flag. +** +** INLINE_FUNC(zName, nArg, iFuncId, mFlags) +** zName is the name of a function that is implemented by in-line +** byte code rather than by the usual callbacks. The iFuncId +** parameter determines the function id. The mFlags parameter is +** optional SQLITE_FUNC_ flags for this function. +** +** TEST_FUNC(zName, nArg, iFuncId, mFlags) +** zName is the name of a test-only function implemented by in-line +** byte code rather than by the usual callbacks. The iFuncId +** parameter determines the function id. The mFlags parameter is +** optional SQLITE_FUNC_ flags for this function. +** +** DFUNCTION(zName, nArg, iArg, bNC, xFunc) +** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag and +** adds the SQLITE_FUNC_SLOCHNG flag. Used for date & time functions +** and functions like sqlite_version() that can change, but not during +** a single query. The iArg is ignored. The user-data is always set +** to a NULL pointer. The bNC parameter is not used. +** +** MFUNCTION(zName, nArg, xPtr, xFunc) +** For math-library functions. xPtr is an arbitrary pointer. +** +** PURE_DATE(zName, nArg, iArg, bNC, xFunc) +** Used for "pure" date/time functions, this macro is like DFUNCTION +** except that it does set the SQLITE_FUNC_CONSTANT flags. iArg is +** ignored and the user-data for these functions is set to an +** arbitrary non-NULL pointer. The bNC parameter is not used. +** +** AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal) +** Used to create an aggregate function definition implemented by +** the C functions xStep and xFinal. The first four parameters +** are interpreted in the same way as the first 4 parameters to +** FUNCTION(). +** +** WAGGREGATE(zName, nArg, iArg, xStep, xFinal, xValue, xInverse) +** Used to create an aggregate function definition implemented by +** the C functions xStep and xFinal. The first four parameters +** are interpreted in the same way as the first 4 parameters to +** FUNCTION(). +** +** LIKEFUNC(zName, nArg, pArg, flags) +** Used to create a scalar function definition of a function zName +** that accepts nArg arguments and is implemented by a call to C +** function likeFunc. Argument pArg is cast to a (void *) and made +** available as the function user-data (sqlite3_user_data()). The +** FuncDef.flags variable is set to the value passed as the flags +** parameter. +*/ +#define FUNCTION(zName, nArg, iArg, bNC, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } +#define VFUNCTION(zName, nArg, iArg, bNC, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } +#define SFUNCTION(zName, nArg, iArg, bNC, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_DIRECTONLY|SQLITE_FUNC_UNSAFE, \ + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } +#define MFUNCTION(zName, nArg, xPtr, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ + xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } +#define JFUNCTION(zName, nArg, iArg, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } +#define INLINE_FUNC(zName, nArg, iArg, mFlags) \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ + SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} } +#define TEST_FUNC(zName, nArg, iArg, mFlags) \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_UTF8|SQLITE_FUNC_INTERNAL|SQLITE_FUNC_TEST| \ + SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ + SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} } +#define DFUNCTION(zName, nArg, iArg, bNC, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_SLOCHNG|SQLITE_UTF8, \ + 0, 0, xFunc, 0, 0, 0, #zName, {0} } +#define PURE_DATE(zName, nArg, iArg, bNC, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ + (void*)&sqlite3Config, 0, xFunc, 0, 0, 0, #zName, {0} } +#define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\ + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } +#define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + pArg, 0, xFunc, 0, 0, 0, #zName, } +#define LIKEFUNC(zName, nArg, arg, flags) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \ + (void *)arg, 0, likeFunc, 0, 0, 0, #zName, {0} } +#define WAGGREGATE(zName, nArg, arg, nc, xStep, xFinal, xValue, xInverse, f) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|f, \ + SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,xValue,xInverse,#zName, {0}} +#define INTERNAL_FUNCTION(zName, nArg, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_INTERNAL|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ + 0, 0, xFunc, 0, 0, 0, #zName, {0} } + + +/* +** All current savepoints are stored in a linked list starting at +** sqlite3.pSavepoint. The first element in the list is the most recently +** opened savepoint. Savepoints are added to the list by the vdbe +** OP_Savepoint instruction. +*/ +struct Savepoint { + char *zName; /* Savepoint name (nul-terminated) */ + i64 nDeferredCons; /* Number of deferred fk violations */ + i64 nDeferredImmCons; /* Number of deferred imm fk. */ + Savepoint *pNext; /* Parent savepoint (if any) */ +}; + +/* +** The following are used as the second parameter to sqlite3Savepoint(), +** and as the P1 argument to the OP_Savepoint instruction. +*/ +#define SAVEPOINT_BEGIN 0 +#define SAVEPOINT_RELEASE 1 +#define SAVEPOINT_ROLLBACK 2 + + +/* +** Each SQLite module (virtual table definition) is defined by an +** instance of the following structure, stored in the sqlite3.aModule +** hash table. +*/ +struct Module { + const sqlite3_module *pModule; /* Callback pointers */ + const char *zName; /* Name passed to create_module() */ + int nRefModule; /* Number of pointers to this object */ + void *pAux; /* pAux passed to create_module() */ + void (*xDestroy)(void *); /* Module destructor function */ + Table *pEpoTab; /* Eponymous table for this module */ +}; + +/* +** Information about each column of an SQL table is held in an instance +** of the Column structure, in the Table.aCol[] array. +** +** Definitions: +** +** "table column index" This is the index of the column in the +** Table.aCol[] array, and also the index of +** the column in the original CREATE TABLE stmt. +** +** "storage column index" This is the index of the column in the +** record BLOB generated by the OP_MakeRecord +** opcode. The storage column index is less than +** or equal to the table column index. It is +** equal if and only if there are no VIRTUAL +** columns to the left. +** +** Notes on zCnName: +** The zCnName field stores the name of the column, the datatype of the +** column, and the collating sequence for the column, in that order, all in +** a single allocation. Each string is 0x00 terminated. The datatype +** is only included if the COLFLAG_HASTYPE bit of colFlags is set and the +** collating sequence name is only included if the COLFLAG_HASCOLL bit is +** set. +*/ +struct Column { + char *zCnName; /* Name of this column */ + unsigned notNull :4; /* An OE_ code for handling a NOT NULL constraint */ + unsigned eCType :4; /* One of the standard types */ + char affinity; /* One of the SQLITE_AFF_... values */ + u8 szEst; /* Est size of value in this column. sizeof(INT)==1 */ + u8 hName; /* Column name hash for faster lookup */ + u16 iDflt; /* 1-based index of DEFAULT. 0 means "none" */ + u16 colFlags; /* Boolean properties. See COLFLAG_ defines below */ +}; + +/* Allowed values for Column.eCType. +** +** Values must match entries in the global constant arrays +** sqlite3StdTypeLen[] and sqlite3StdType[]. Each value is one more +** than the offset into these arrays for the corresponding name. +** Adjust the SQLITE_N_STDTYPE value if adding or removing entries. +*/ +#define COLTYPE_CUSTOM 0 /* Type appended to zName */ +#define COLTYPE_ANY 1 +#define COLTYPE_BLOB 2 +#define COLTYPE_INT 3 +#define COLTYPE_INTEGER 4 +#define COLTYPE_REAL 5 +#define COLTYPE_TEXT 6 +#define SQLITE_N_STDTYPE 6 /* Number of standard types */ + +/* Allowed values for Column.colFlags. +** +** Constraints: +** TF_HasVirtual == COLFLAG_VIRTUAL +** TF_HasStored == COLFLAG_STORED +** TF_HasHidden == COLFLAG_HIDDEN +*/ +#define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */ +#define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */ +#define COLFLAG_HASTYPE 0x0004 /* Type name follows column name */ +#define COLFLAG_UNIQUE 0x0008 /* Column def contains "UNIQUE" or "PK" */ +#define COLFLAG_SORTERREF 0x0010 /* Use sorter-refs with this column */ +#define COLFLAG_VIRTUAL 0x0020 /* GENERATED ALWAYS AS ... VIRTUAL */ +#define COLFLAG_STORED 0x0040 /* GENERATED ALWAYS AS ... STORED */ +#define COLFLAG_NOTAVAIL 0x0080 /* STORED column not yet calculated */ +#define COLFLAG_BUSY 0x0100 /* Blocks recursion on GENERATED columns */ +#define COLFLAG_HASCOLL 0x0200 /* Has collating sequence name in zCnName */ +#define COLFLAG_NOEXPAND 0x0400 /* Omit this column when expanding "*" */ +#define COLFLAG_GENERATED 0x0060 /* Combo: _STORED, _VIRTUAL */ +#define COLFLAG_NOINSERT 0x0062 /* Combo: _HIDDEN, _STORED, _VIRTUAL */ + +/* +** A "Collating Sequence" is defined by an instance of the following +** structure. Conceptually, a collating sequence consists of a name and +** a comparison routine that defines the order of that sequence. +** +** If CollSeq.xCmp is NULL, it means that the +** collating sequence is undefined. Indices built on an undefined +** collating sequence may not be read or written. +*/ +struct CollSeq { + char *zName; /* Name of the collating sequence, UTF-8 encoded */ + u8 enc; /* Text encoding handled by xCmp() */ + void *pUser; /* First argument to xCmp() */ + int (*xCmp)(void*,int, const void*, int, const void*); + void (*xDel)(void*); /* Destructor for pUser */ +}; + +/* +** A sort order can be either ASC or DESC. +*/ +#define SQLITE_SO_ASC 0 /* Sort in ascending order */ +#define SQLITE_SO_DESC 1 /* Sort in ascending order */ +#define SQLITE_SO_UNDEFINED -1 /* No sort order specified */ + +/* +** Column affinity types. +** +** These used to have mnemonic name like 'i' for SQLITE_AFF_INTEGER and +** 't' for SQLITE_AFF_TEXT. But we can save a little space and improve +** the speed a little by numbering the values consecutively. +** +** But rather than start with 0 or 1, we begin with 'A'. That way, +** when multiple affinity types are concatenated into a string and +** used as the P4 operand, they will be more readable. +** +** Note also that the numeric types are grouped together so that testing +** for a numeric type is a single comparison. And the BLOB type is first. +*/ +#define SQLITE_AFF_NONE 0x40 /* '@' */ +#define SQLITE_AFF_BLOB 0x41 /* 'A' */ +#define SQLITE_AFF_TEXT 0x42 /* 'B' */ +#define SQLITE_AFF_NUMERIC 0x43 /* 'C' */ +#define SQLITE_AFF_INTEGER 0x44 /* 'D' */ +#define SQLITE_AFF_REAL 0x45 /* 'E' */ +#define SQLITE_AFF_FLEXNUM 0x46 /* 'F' */ + +#define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) + +/* +** The SQLITE_AFF_MASK values masks off the significant bits of an +** affinity value. +*/ +#define SQLITE_AFF_MASK 0x47 + +/* +** Additional bit values that can be ORed with an affinity without +** changing the affinity. +** +** The SQLITE_NOTNULL flag is a combination of NULLEQ and JUMPIFNULL. +** It causes an assert() to fire if either operand to a comparison +** operator is NULL. It is added to certain comparison operators to +** prove that the operands are always NOT NULL. +*/ +#define SQLITE_JUMPIFNULL 0x10 /* jumps if either operand is NULL */ +#define SQLITE_NULLEQ 0x80 /* NULL=NULL */ +#define SQLITE_NOTNULL 0x90 /* Assert that operands are never NULL */ + +/* +** An object of this type is created for each virtual table present in +** the database schema. +** +** If the database schema is shared, then there is one instance of this +** structure for each database connection (sqlite3*) that uses the shared +** schema. This is because each database connection requires its own unique +** instance of the sqlite3_vtab* handle used to access the virtual table +** implementation. sqlite3_vtab* handles can not be shared between +** database connections, even when the rest of the in-memory database +** schema is shared, as the implementation often stores the database +** connection handle passed to it via the xConnect() or xCreate() method +** during initialization internally. This database connection handle may +** then be used by the virtual table implementation to access real tables +** within the database. So that they appear as part of the callers +** transaction, these accesses need to be made via the same database +** connection as that used to execute SQL operations on the virtual table. +** +** All VTable objects that correspond to a single table in a shared +** database schema are initially stored in a linked-list pointed to by +** the Table.pVTable member variable of the corresponding Table object. +** When an sqlite3_prepare() operation is required to access the virtual +** table, it searches the list for the VTable that corresponds to the +** database connection doing the preparing so as to use the correct +** sqlite3_vtab* handle in the compiled query. +** +** When an in-memory Table object is deleted (for example when the +** schema is being reloaded for some reason), the VTable objects are not +** deleted and the sqlite3_vtab* handles are not xDisconnect()ed +** immediately. Instead, they are moved from the Table.pVTable list to +** another linked list headed by the sqlite3.pDisconnect member of the +** corresponding sqlite3 structure. They are then deleted/xDisconnected +** next time a statement is prepared using said sqlite3*. This is done +** to avoid deadlock issues involving multiple sqlite3.mutex mutexes. +** Refer to comments above function sqlite3VtabUnlockList() for an +** explanation as to why it is safe to add an entry to an sqlite3.pDisconnect +** list without holding the corresponding sqlite3.mutex mutex. +** +** The memory for objects of this type is always allocated by +** sqlite3DbMalloc(), using the connection handle stored in VTable.db as +** the first argument. +*/ +struct VTable { + sqlite3 *db; /* Database connection associated with this table */ + Module *pMod; /* Pointer to module implementation */ + sqlite3_vtab *pVtab; /* Pointer to vtab instance */ + int nRef; /* Number of pointers to this structure */ + u8 bConstraint; /* True if constraints are supported */ + u8 bAllSchemas; /* True if might use any attached schema */ + u8 eVtabRisk; /* Riskiness of allowing hacker access */ + int iSavepoint; /* Depth of the SAVEPOINT stack */ + VTable *pNext; /* Next in linked list (see above) */ +}; + +/* Allowed values for VTable.eVtabRisk +*/ +#define SQLITE_VTABRISK_Low 0 +#define SQLITE_VTABRISK_Normal 1 +#define SQLITE_VTABRISK_High 2 + +/* +** The schema for each SQL table, virtual table, and view is represented +** in memory by an instance of the following structure. +*/ +struct Table { + char *zName; /* Name of the table or view */ + Column *aCol; /* Information about each column */ + Index *pIndex; /* List of SQL indexes on this table. */ + char *zColAff; /* String defining the affinity of each column */ + ExprList *pCheck; /* All CHECK constraints */ + /* ... also used as column name list in a VIEW */ + Pgno tnum; /* Root BTree page for this table */ + u32 nTabRef; /* Number of pointers to this Table */ + u32 tabFlags; /* Mask of TF_* values */ + i16 iPKey; /* If not negative, use aCol[iPKey] as the rowid */ + i16 nCol; /* Number of columns in this table */ + i16 nNVCol; /* Number of columns that are not VIRTUAL */ + LogEst nRowLogEst; /* Estimated rows in table - from sqlite_stat1 table */ + LogEst szTabRow; /* Estimated size of each table row in bytes */ +#ifdef SQLITE_ENABLE_COSTMULT + LogEst costMult; /* Cost multiplier for using this table */ +#endif + u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ + u8 eTabType; /* 0: normal, 1: virtual, 2: view */ + union { + struct { /* Used by ordinary tables: */ + int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ + FKey *pFKey; /* Linked list of all foreign keys in this table */ + ExprList *pDfltList; /* DEFAULT clauses on various columns. + ** Or the AS clause for generated columns. */ + } tab; + struct { /* Used by views: */ + Select *pSelect; /* View definition */ + } view; + struct { /* Used by virtual tables only: */ + int nArg; /* Number of arguments to the module */ + char **azArg; /* 0: module 1: schema 2: vtab name 3...: args */ + VTable *p; /* List of VTable objects. */ + } vtab; + } u; + Trigger *pTrigger; /* List of triggers on this object */ + Schema *pSchema; /* Schema that contains this table */ +}; + +/* +** Allowed values for Table.tabFlags. +** +** TF_OOOHidden applies to tables or view that have hidden columns that are +** followed by non-hidden columns. Example: "CREATE VIRTUAL TABLE x USING +** vtab1(a HIDDEN, b);". Since "b" is a non-hidden column but "a" is hidden, +** the TF_OOOHidden attribute would apply in this case. Such tables require +** special handling during INSERT processing. The "OOO" means "Out Of Order". +** +** Constraints: +** +** TF_HasVirtual == COLFLAG_VIRTUAL +** TF_HasStored == COLFLAG_STORED +** TF_HasHidden == COLFLAG_HIDDEN +*/ +#define TF_Readonly 0x00000001 /* Read-only system table */ +#define TF_HasHidden 0x00000002 /* Has one or more hidden columns */ +#define TF_HasPrimaryKey 0x00000004 /* Table has a primary key */ +#define TF_Autoincrement 0x00000008 /* Integer primary key is autoincrement */ +#define TF_HasStat1 0x00000010 /* nRowLogEst set from sqlite_stat1 */ +#define TF_HasVirtual 0x00000020 /* Has one or more VIRTUAL columns */ +#define TF_HasStored 0x00000040 /* Has one or more STORED columns */ +#define TF_HasGenerated 0x00000060 /* Combo: HasVirtual + HasStored */ +#define TF_WithoutRowid 0x00000080 /* No rowid. PRIMARY KEY is the key */ +#define TF_StatsUsed 0x00000100 /* Query planner decisions affected by + ** Index.aiRowLogEst[] values */ +#define TF_NoVisibleRowid 0x00000200 /* No user-visible "rowid" column */ +#define TF_OOOHidden 0x00000400 /* Out-of-Order hidden columns */ +#define TF_HasNotNull 0x00000800 /* Contains NOT NULL constraints */ +#define TF_Shadow 0x00001000 /* True for a shadow table */ +#define TF_HasStat4 0x00002000 /* STAT4 info available for this table */ +#define TF_Ephemeral 0x00004000 /* An ephemeral table */ +#define TF_Eponymous 0x00008000 /* An eponymous virtual table */ +#define TF_Strict 0x00010000 /* STRICT mode */ + +/* +** Allowed values for Table.eTabType +*/ +#define TABTYP_NORM 0 /* Ordinary table */ +#define TABTYP_VTAB 1 /* Virtual table */ +#define TABTYP_VIEW 2 /* A view */ + +#define IsView(X) ((X)->eTabType==TABTYP_VIEW) +#define IsOrdinaryTable(X) ((X)->eTabType==TABTYP_NORM) + +/* +** Test to see whether or not a table is a virtual table. This is +** done as a macro so that it will be optimized out when virtual +** table support is omitted from the build. +*/ +#ifndef SQLITE_OMIT_VIRTUALTABLE +# define IsVirtual(X) ((X)->eTabType==TABTYP_VTAB) +# define ExprIsVtab(X) \ + ((X)->op==TK_COLUMN && (X)->y.pTab->eTabType==TABTYP_VTAB) +#else +# define IsVirtual(X) 0 +# define ExprIsVtab(X) 0 +#endif + +/* +** Macros to determine if a column is hidden. IsOrdinaryHiddenColumn() +** only works for non-virtual tables (ordinary tables and views) and is +** always false unless SQLITE_ENABLE_HIDDEN_COLUMNS is defined. The +** IsHiddenColumn() macro is general purpose. +*/ +#if defined(SQLITE_ENABLE_HIDDEN_COLUMNS) +# define IsHiddenColumn(X) (((X)->colFlags & COLFLAG_HIDDEN)!=0) +# define IsOrdinaryHiddenColumn(X) (((X)->colFlags & COLFLAG_HIDDEN)!=0) +#elif !defined(SQLITE_OMIT_VIRTUALTABLE) +# define IsHiddenColumn(X) (((X)->colFlags & COLFLAG_HIDDEN)!=0) +# define IsOrdinaryHiddenColumn(X) 0 +#else +# define IsHiddenColumn(X) 0 +# define IsOrdinaryHiddenColumn(X) 0 +#endif + + +/* Does the table have a rowid */ +#define HasRowid(X) (((X)->tabFlags & TF_WithoutRowid)==0) +#define VisibleRowid(X) (((X)->tabFlags & TF_NoVisibleRowid)==0) + +/* +** Each foreign key constraint is an instance of the following structure. +** +** A foreign key is associated with two tables. The "from" table is +** the table that contains the REFERENCES clause that creates the foreign +** key. The "to" table is the table that is named in the REFERENCES clause. +** Consider this example: +** +** CREATE TABLE ex1( +** a INTEGER PRIMARY KEY, +** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x) +** ); +** +** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2". +** Equivalent names: +** +** from-table == child-table +** to-table == parent-table +** +** Each REFERENCES clause generates an instance of the following structure +** which is attached to the from-table. The to-table need not exist when +** the from-table is created. The existence of the to-table is not checked. +** +** The list of all parents for child Table X is held at X.pFKey. +** +** A list of all children for a table named Z (which might not even exist) +** is held in Schema.fkeyHash with a hash key of Z. +*/ +struct FKey { + Table *pFrom; /* Table containing the REFERENCES clause (aka: Child) */ + FKey *pNextFrom; /* Next FKey with the same in pFrom. Next parent of pFrom */ + char *zTo; /* Name of table that the key points to (aka: Parent) */ + FKey *pNextTo; /* Next with the same zTo. Next child of zTo. */ + FKey *pPrevTo; /* Previous with the same zTo */ + int nCol; /* Number of columns in this key */ + /* EV: R-30323-21917 */ + u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ + u8 aAction[2]; /* ON DELETE and ON UPDATE actions, respectively */ + Trigger *apTrigger[2];/* Triggers for aAction[] actions */ + struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ + int iFrom; /* Index of column in pFrom */ + char *zCol; /* Name of column in zTo. If NULL use PRIMARY KEY */ + } aCol[1]; /* One entry for each of nCol columns */ +}; + +/* +** SQLite supports many different ways to resolve a constraint +** error. ROLLBACK processing means that a constraint violation +** causes the operation in process to fail and for the current transaction +** to be rolled back. ABORT processing means the operation in process +** fails and any prior changes from that one operation are backed out, +** but the transaction is not rolled back. FAIL processing means that +** the operation in progress stops and returns an error code. But prior +** changes due to the same operation are not backed out and no rollback +** occurs. IGNORE means that the particular row that caused the constraint +** error is not inserted or updated. Processing continues and no error +** is returned. REPLACE means that preexisting database rows that caused +** a UNIQUE constraint violation are removed so that the new insert or +** update can proceed. Processing continues and no error is reported. +** UPDATE applies to insert operations only and means that the insert +** is omitted and the DO UPDATE clause of an upsert is run instead. +** +** RESTRICT, SETNULL, SETDFLT, and CASCADE actions apply only to foreign keys. +** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the +** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign +** key is set to NULL. SETDFLT means that the foreign key is set +** to its default value. CASCADE means that a DELETE or UPDATE of the +** referenced table row is propagated into the row that holds the +** foreign key. +** +** The OE_Default value is a place holder that means to use whatever +** conflict resolution algorithm is required from context. +** +** The following symbolic values are used to record which type +** of conflict resolution action to take. +*/ +#define OE_None 0 /* There is no constraint to check */ +#define OE_Rollback 1 /* Fail the operation and rollback the transaction */ +#define OE_Abort 2 /* Back out changes but do no rollback transaction */ +#define OE_Fail 3 /* Stop the operation but leave all prior changes */ +#define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */ +#define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */ +#define OE_Update 6 /* Process as a DO UPDATE in an upsert */ +#define OE_Restrict 7 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */ +#define OE_SetNull 8 /* Set the foreign key value to NULL */ +#define OE_SetDflt 9 /* Set the foreign key value to its default */ +#define OE_Cascade 10 /* Cascade the changes */ +#define OE_Default 11 /* Do whatever the default action is */ + + +/* +** An instance of the following structure is passed as the first +** argument to sqlite3VdbeKeyCompare and is used to control the +** comparison of the two index keys. +** +** Note that aSortOrder[] and aColl[] have nField+1 slots. There +** are nField slots for the columns of an index then one extra slot +** for the rowid at the end. +*/ +struct KeyInfo { + u32 nRef; /* Number of references to this KeyInfo object */ + u8 enc; /* Text encoding - one of the SQLITE_UTF* values */ + u16 nKeyField; /* Number of key columns in the index */ + u16 nAllField; /* Total columns, including key plus others */ + sqlite3 *db; /* The database connection */ + u8 *aSortFlags; /* Sort order for each column. */ + CollSeq *aColl[1]; /* Collating sequence for each term of the key */ +}; + +/* +** Allowed bit values for entries in the KeyInfo.aSortFlags[] array. +*/ +#define KEYINFO_ORDER_DESC 0x01 /* DESC sort order */ +#define KEYINFO_ORDER_BIGNULL 0x02 /* NULL is larger than any other value */ + +/* +** This object holds a record which has been parsed out into individual +** fields, for the purposes of doing a comparison. +** +** A record is an object that contains one or more fields of data. +** Records are used to store the content of a table row and to store +** the key of an index. A blob encoding of a record is created by +** the OP_MakeRecord opcode of the VDBE and is disassembled by the +** OP_Column opcode. +** +** An instance of this object serves as a "key" for doing a search on +** an index b+tree. The goal of the search is to find the entry that +** is closed to the key described by this object. This object might hold +** just a prefix of the key. The number of fields is given by +** pKeyInfo->nField. +** +** The r1 and r2 fields are the values to return if this key is less than +** or greater than a key in the btree, respectively. These are normally +** -1 and +1 respectively, but might be inverted to +1 and -1 if the b-tree +** is in DESC order. +** +** The key comparison functions actually return default_rc when they find +** an equals comparison. default_rc can be -1, 0, or +1. If there are +** multiple entries in the b-tree with the same key (when only looking +** at the first pKeyInfo->nFields,) then default_rc can be set to -1 to +** cause the search to find the last match, or +1 to cause the search to +** find the first match. +** +** The key comparison functions will set eqSeen to true if they ever +** get and equal results when comparing this structure to a b-tree record. +** When default_rc!=0, the search might end up on the record immediately +** before the first match or immediately after the last match. The +** eqSeen field will indicate whether or not an exact match exists in the +** b-tree. +*/ +struct UnpackedRecord { + KeyInfo *pKeyInfo; /* Collation and sort-order information */ + Mem *aMem; /* Values */ + union { + char *z; /* Cache of aMem[0].z for vdbeRecordCompareString() */ + i64 i; /* Cache of aMem[0].u.i for vdbeRecordCompareInt() */ + } u; + int n; /* Cache of aMem[0].n used by vdbeRecordCompareString() */ + u16 nField; /* Number of entries in apMem[] */ + i8 default_rc; /* Comparison result if keys are equal */ + u8 errCode; /* Error detected by xRecordCompare (CORRUPT or NOMEM) */ + i8 r1; /* Value to return if (lhs < rhs) */ + i8 r2; /* Value to return if (lhs > rhs) */ + u8 eqSeen; /* True if an equality comparison has been seen */ +}; + + +/* +** Each SQL index is represented in memory by an +** instance of the following structure. +** +** The columns of the table that are to be indexed are described +** by the aiColumn[] field of this structure. For example, suppose +** we have the following table and index: +** +** CREATE TABLE Ex1(c1 int, c2 int, c3 text); +** CREATE INDEX Ex2 ON Ex1(c3,c1); +** +** In the Table structure describing Ex1, nCol==3 because there are +** three columns in the table. In the Index structure describing +** Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed. +** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the +** first column to be indexed (c3) has an index of 2 in Ex1.aCol[]. +** The second column to be indexed (c1) has an index of 0 in +** Ex1.aCol[], hence Ex2.aiColumn[1]==0. +** +** The Index.onError field determines whether or not the indexed columns +** must be unique and what to do if they are not. When Index.onError=OE_None, +** it means this is not a unique index. Otherwise it is a unique index +** and the value of Index.onError indicates which conflict resolution +** algorithm to employ when an attempt is made to insert a non-unique +** element. +** +** The colNotIdxed bitmask is used in combination with SrcItem.colUsed +** for a fast test to see if an index can serve as a covering index. +** colNotIdxed has a 1 bit for every column of the original table that +** is *not* available in the index. Thus the expression +** "colUsed & colNotIdxed" will be non-zero if the index is not a +** covering index. The most significant bit of of colNotIdxed will always +** be true (note-20221022-a). If a column beyond the 63rd column of the +** table is used, the "colUsed & colNotIdxed" test will always be non-zero +** and we have to assume either that the index is not covering, or use +** an alternative (slower) algorithm to determine whether or not +** the index is covering. +** +** While parsing a CREATE TABLE or CREATE INDEX statement in order to +** generate VDBE code (as opposed to parsing one read from an sqlite_schema +** table as part of parsing an existing database schema), transient instances +** of this structure may be created. In this case the Index.tnum variable is +** used to store the address of a VDBE instruction, not a database page +** number (it cannot - the database page is not allocated until the VDBE +** program is executed). See convertToWithoutRowidTable() for details. +*/ +struct Index { + char *zName; /* Name of this index */ + i16 *aiColumn; /* Which columns are used by this index. 1st is 0 */ + LogEst *aiRowLogEst; /* From ANALYZE: Est. rows selected by each column */ + Table *pTable; /* The SQL table being indexed */ + char *zColAff; /* String defining the affinity of each column */ + Index *pNext; /* The next index associated with the same table */ + Schema *pSchema; /* Schema containing this index */ + u8 *aSortOrder; /* for each column: True==DESC, False==ASC */ + const char **azColl; /* Array of collation sequence names for index */ + Expr *pPartIdxWhere; /* WHERE clause for partial indices */ + ExprList *aColExpr; /* Column expressions */ + Pgno tnum; /* DB Page containing root of this index */ + LogEst szIdxRow; /* Estimated average row size in bytes */ + u16 nKeyCol; /* Number of columns forming the key */ + u16 nColumn; /* Number of columns stored in the index */ + u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ + unsigned idxType:2; /* 0:Normal 1:UNIQUE, 2:PRIMARY KEY, 3:IPK */ + unsigned bUnordered:1; /* Use this index for == or IN queries only */ + unsigned uniqNotNull:1; /* True if UNIQUE and NOT NULL for all columns */ + unsigned isResized:1; /* True if resizeIndexObject() has been called */ + unsigned isCovering:1; /* True if this is a covering index */ + unsigned noSkipScan:1; /* Do not try to use skip-scan if true */ + unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */ + unsigned bNoQuery:1; /* Do not use this index to optimize queries */ + unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */ + unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */ + unsigned bHasExpr:1; /* Index contains an expression, either a literal + ** expression, or a reference to a VIRTUAL column */ +#ifdef SQLITE_ENABLE_STAT4 + int nSample; /* Number of elements in aSample[] */ + int mxSample; /* Number of slots allocated to aSample[] */ + int nSampleCol; /* Size of IndexSample.anEq[] and so on */ + tRowcnt *aAvgEq; /* Average nEq values for keys not in aSample */ + IndexSample *aSample; /* Samples of the left-most key */ + tRowcnt *aiRowEst; /* Non-logarithmic stat1 data for this index */ + tRowcnt nRowEst0; /* Non-logarithmic number of rows in the index */ +#endif + Bitmask colNotIdxed; /* Unindexed columns in pTab */ +}; + +/* +** Allowed values for Index.idxType +*/ +#define SQLITE_IDXTYPE_APPDEF 0 /* Created using CREATE INDEX */ +#define SQLITE_IDXTYPE_UNIQUE 1 /* Implements a UNIQUE constraint */ +#define SQLITE_IDXTYPE_PRIMARYKEY 2 /* Is the PRIMARY KEY for the table */ +#define SQLITE_IDXTYPE_IPK 3 /* INTEGER PRIMARY KEY index */ + +/* Return true if index X is a PRIMARY KEY index */ +#define IsPrimaryKeyIndex(X) ((X)->idxType==SQLITE_IDXTYPE_PRIMARYKEY) + +/* Return true if index X is a UNIQUE index */ +#define IsUniqueIndex(X) ((X)->onError!=OE_None) + +/* The Index.aiColumn[] values are normally positive integer. But +** there are some negative values that have special meaning: +*/ +#define XN_ROWID (-1) /* Indexed column is the rowid */ +#define XN_EXPR (-2) /* Indexed column is an expression */ + +/* +** Each sample stored in the sqlite_stat4 table is represented in memory +** using a structure of this type. See documentation at the top of the +** analyze.c source file for additional information. +*/ +struct IndexSample { + void *p; /* Pointer to sampled record */ + int n; /* Size of record in bytes */ + tRowcnt *anEq; /* Est. number of rows where the key equals this sample */ + tRowcnt *anLt; /* Est. number of rows where key is less than this sample */ + tRowcnt *anDLt; /* Est. number of distinct keys less than this sample */ +}; + +/* +** Possible values to use within the flags argument to sqlite3GetToken(). +*/ +#define SQLITE_TOKEN_QUOTED 0x1 /* Token is a quoted identifier. */ +#define SQLITE_TOKEN_KEYWORD 0x2 /* Token is a keyword. */ + +/* +** Each token coming out of the lexer is an instance of +** this structure. Tokens are also used as part of an expression. +** +** The memory that "z" points to is owned by other objects. Take care +** that the owner of the "z" string does not deallocate the string before +** the Token goes out of scope! Very often, the "z" points to some place +** in the middle of the Parse.zSql text. But it might also point to a +** static string. +*/ +struct Token { + const char *z; /* Text of the token. Not NULL-terminated! */ + unsigned int n; /* Number of characters in this token */ +}; + +/* +** An instance of this structure contains information needed to generate +** code for a SELECT that contains aggregate functions. +** +** If Expr.op==TK_AGG_COLUMN or TK_AGG_FUNCTION then Expr.pAggInfo is a +** pointer to this structure. The Expr.iAgg field is the index in +** AggInfo.aCol[] or AggInfo.aFunc[] of information needed to generate +** code for that node. +** +** AggInfo.pGroupBy and AggInfo.aFunc.pExpr point to fields within the +** original Select structure that describes the SELECT statement. These +** fields do not need to be freed when deallocating the AggInfo structure. +*/ +struct AggInfo { + u8 directMode; /* Direct rendering mode means take data directly + ** from source tables rather than from accumulators */ + u8 useSortingIdx; /* In direct mode, reference the sorting index rather + ** than the source table */ + u16 nSortingColumn; /* Number of columns in the sorting index */ + int sortingIdx; /* Cursor number of the sorting index */ + int sortingIdxPTab; /* Cursor number of pseudo-table */ + int iFirstReg; /* First register in range for aCol[] and aFunc[] */ + ExprList *pGroupBy; /* The group by clause */ + struct AggInfo_col { /* For each column used in source tables */ + Table *pTab; /* Source table */ + Expr *pCExpr; /* The original expression */ + int iTable; /* Cursor number of the source table */ + i16 iColumn; /* Column number within the source table */ + i16 iSorterColumn; /* Column number in the sorting index */ + } *aCol; + int nColumn; /* Number of used entries in aCol[] */ + int nAccumulator; /* Number of columns that show through to the output. + ** Additional columns are used only as parameters to + ** aggregate functions */ + struct AggInfo_func { /* For each aggregate function */ + Expr *pFExpr; /* Expression encoding the function */ + FuncDef *pFunc; /* The aggregate function implementation */ + int iDistinct; /* Ephemeral table used to enforce DISTINCT */ + int iDistAddr; /* Address of OP_OpenEphemeral */ + } *aFunc; + int nFunc; /* Number of entries in aFunc[] */ + u32 selId; /* Select to which this AggInfo belongs */ +#ifdef SQLITE_DEBUG + Select *pSelect; /* SELECT statement that this AggInfo supports */ +#endif +}; + +/* +** Macros to compute aCol[] and aFunc[] register numbers. +** +** These macros should not be used prior to the call to +** assignAggregateRegisters() that computes the value of pAggInfo->iFirstReg. +** The assert()s that are part of this macro verify that constraint. +*/ +#define AggInfoColumnReg(A,I) (assert((A)->iFirstReg),(A)->iFirstReg+(I)) +#define AggInfoFuncReg(A,I) \ + (assert((A)->iFirstReg),(A)->iFirstReg+(A)->nColumn+(I)) + +/* +** The datatype ynVar is a signed integer, either 16-bit or 32-bit. +** Usually it is 16-bits. But if SQLITE_MAX_VARIABLE_NUMBER is greater +** than 32767 we have to make it 32-bit. 16-bit is preferred because +** it uses less memory in the Expr object, which is a big memory user +** in systems with lots of prepared statements. And few applications +** need more than about 10 or 20 variables. But some extreme users want +** to have prepared statements with over 32766 variables, and for them +** the option is available (at compile-time). +*/ +#if SQLITE_MAX_VARIABLE_NUMBER<32767 +typedef i16 ynVar; +#else +typedef int ynVar; +#endif + +/* +** Each node of an expression in the parse tree is an instance +** of this structure. +** +** Expr.op is the opcode. The integer parser token codes are reused +** as opcodes here. For example, the parser defines TK_GE to be an integer +** code representing the ">=" operator. This same integer code is reused +** to represent the greater-than-or-equal-to operator in the expression +** tree. +** +** If the expression is an SQL literal (TK_INTEGER, TK_FLOAT, TK_BLOB, +** or TK_STRING), then Expr.u.zToken contains the text of the SQL literal. If +** the expression is a variable (TK_VARIABLE), then Expr.u.zToken contains the +** variable name. Finally, if the expression is an SQL function (TK_FUNCTION), +** then Expr.u.zToken contains the name of the function. +** +** Expr.pRight and Expr.pLeft are the left and right subexpressions of a +** binary operator. Either or both may be NULL. +** +** Expr.x.pList is a list of arguments if the expression is an SQL function, +** a CASE expression or an IN expression of the form "<lhs> IN (<y>, <z>...)". +** Expr.x.pSelect is used if the expression is a sub-select or an expression of +** the form "<lhs> IN (SELECT ...)". If the EP_xIsSelect bit is set in the +** Expr.flags mask, then Expr.x.pSelect is valid. Otherwise, Expr.x.pList is +** valid. +** +** An expression of the form ID or ID.ID refers to a column in a table. +** For such expressions, Expr.op is set to TK_COLUMN and Expr.iTable is +** the integer cursor number of a VDBE cursor pointing to that table and +** Expr.iColumn is the column number for the specific column. If the +** expression is used as a result in an aggregate SELECT, then the +** value is also stored in the Expr.iAgg column in the aggregate so that +** it can be accessed after all aggregates are computed. +** +** If the expression is an unbound variable marker (a question mark +** character '?' in the original SQL) then the Expr.iTable holds the index +** number for that variable. +** +** If the expression is a subquery then Expr.iColumn holds an integer +** register number containing the result of the subquery. If the +** subquery gives a constant result, then iTable is -1. If the subquery +** gives a different answer at different times during statement processing +** then iTable is the address of a subroutine that computes the subquery. +** +** If the Expr is of type OP_Column, and the table it is selecting from +** is a disk table or the "old.*" pseudo-table, then pTab points to the +** corresponding table definition. +** +** ALLOCATION NOTES: +** +** Expr objects can use a lot of memory space in database schema. To +** help reduce memory requirements, sometimes an Expr object will be +** truncated. And to reduce the number of memory allocations, sometimes +** two or more Expr objects will be stored in a single memory allocation, +** together with Expr.u.zToken strings. +** +** If the EP_Reduced and EP_TokenOnly flags are set when +** an Expr object is truncated. When EP_Reduced is set, then all +** the child Expr objects in the Expr.pLeft and Expr.pRight subtrees +** are contained within the same memory allocation. Note, however, that +** the subtrees in Expr.x.pList or Expr.x.pSelect are always separately +** allocated, regardless of whether or not EP_Reduced is set. +*/ +struct Expr { + u8 op; /* Operation performed by this node */ + char affExpr; /* affinity, or RAISE type */ + u8 op2; /* TK_REGISTER/TK_TRUTH: original value of Expr.op + ** TK_COLUMN: the value of p5 for OP_Column + ** TK_AGG_FUNCTION: nesting depth + ** TK_FUNCTION: NC_SelfRef flag if needs OP_PureFunc */ +#ifdef SQLITE_DEBUG + u8 vvaFlags; /* Verification flags. */ +#endif + u32 flags; /* Various flags. EP_* See below */ + union { + char *zToken; /* Token value. Zero terminated and dequoted */ + int iValue; /* Non-negative integer value if EP_IntValue */ + } u; + + /* If the EP_TokenOnly flag is set in the Expr.flags mask, then no + ** space is allocated for the fields below this point. An attempt to + ** access them will result in a segfault or malfunction. + *********************************************************************/ + + Expr *pLeft; /* Left subnode */ + Expr *pRight; /* Right subnode */ + union { + ExprList *pList; /* op = IN, EXISTS, SELECT, CASE, FUNCTION, BETWEEN */ + Select *pSelect; /* EP_xIsSelect and op = IN, EXISTS, SELECT */ + } x; + + /* If the EP_Reduced flag is set in the Expr.flags mask, then no + ** space is allocated for the fields below this point. An attempt to + ** access them will result in a segfault or malfunction. + *********************************************************************/ + +#if SQLITE_MAX_EXPR_DEPTH>0 + int nHeight; /* Height of the tree headed by this node */ +#endif + int iTable; /* TK_COLUMN: cursor number of table holding column + ** TK_REGISTER: register number + ** TK_TRIGGER: 1 -> new, 0 -> old + ** EP_Unlikely: 134217728 times likelihood + ** TK_IN: ephemeral table holding RHS + ** TK_SELECT_COLUMN: Number of columns on the LHS + ** TK_SELECT: 1st register of result vector */ + ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid. + ** TK_VARIABLE: variable number (always >= 1). + ** TK_SELECT_COLUMN: column of the result vector */ + i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ + union { + int iJoin; /* If EP_OuterON or EP_InnerON, the right table */ + int iOfst; /* else: start of token from start of statement */ + } w; + AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ + union { + Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL + ** for a column of an index on an expression */ + Window *pWin; /* EP_WinFunc: Window/Filter defn for a function */ + struct { /* TK_IN, TK_SELECT, and TK_EXISTS */ + int iAddr; /* Subroutine entry address */ + int regReturn; /* Register used to hold return address */ + } sub; + } y; +}; + +/* The following are the meanings of bits in the Expr.flags field. +** Value restrictions: +** +** EP_Agg == NC_HasAgg == SF_HasAgg +** EP_Win == NC_HasWin +*/ +#define EP_OuterON 0x000001 /* Originates in ON/USING clause of outer join */ +#define EP_InnerON 0x000002 /* Originates in ON/USING of an inner join */ +#define EP_Distinct 0x000004 /* Aggregate function with DISTINCT keyword */ +#define EP_HasFunc 0x000008 /* Contains one or more functions of any kind */ +#define EP_Agg 0x000010 /* Contains one or more aggregate functions */ +#define EP_FixedCol 0x000020 /* TK_Column with a known fixed value */ +#define EP_VarSelect 0x000040 /* pSelect is correlated, not constant */ +#define EP_DblQuoted 0x000080 /* token.z was originally in "..." */ +#define EP_InfixFunc 0x000100 /* True for an infix function: LIKE, GLOB, etc */ +#define EP_Collate 0x000200 /* Tree contains a TK_COLLATE operator */ +#define EP_Commuted 0x000400 /* Comparison operator has been commuted */ +#define EP_IntValue 0x000800 /* Integer value contained in u.iValue */ +#define EP_xIsSelect 0x001000 /* x.pSelect is valid (otherwise x.pList is) */ +#define EP_Skip 0x002000 /* Operator does not contribute to affinity */ +#define EP_Reduced 0x004000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ +#define EP_Win 0x008000 /* Contains window functions */ +#define EP_TokenOnly 0x010000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ + /* 0x020000 // Available for reuse */ +#define EP_IfNullRow 0x040000 /* The TK_IF_NULL_ROW opcode */ +#define EP_Unlikely 0x080000 /* unlikely() or likelihood() function */ +#define EP_ConstFunc 0x100000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ +#define EP_CanBeNull 0x200000 /* Can be null despite NOT NULL constraint */ +#define EP_Subquery 0x400000 /* Tree contains a TK_SELECT operator */ +#define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */ +#define EP_WinFunc 0x1000000 /* TK_FUNCTION with Expr.y.pWin set */ +#define EP_Subrtn 0x2000000 /* Uses Expr.y.sub. TK_IN, _SELECT, or _EXISTS */ +#define EP_Quoted 0x4000000 /* TK_ID was originally quoted */ +#define EP_Static 0x8000000 /* Held in memory not obtained from malloc() */ +#define EP_IsTrue 0x10000000 /* Always has boolean value of TRUE */ +#define EP_IsFalse 0x20000000 /* Always has boolean value of FALSE */ +#define EP_FromDDL 0x40000000 /* Originates from sqlite_schema */ + /* 0x80000000 // Available */ + +/* The EP_Propagate mask is a set of properties that automatically propagate +** upwards into parent nodes. +*/ +#define EP_Propagate (EP_Collate|EP_Subquery|EP_HasFunc) + +/* Macros can be used to test, set, or clear bits in the +** Expr.flags field. +*/ +#define ExprHasProperty(E,P) (((E)->flags&(P))!=0) +#define ExprHasAllProperty(E,P) (((E)->flags&(P))==(P)) +#define ExprSetProperty(E,P) (E)->flags|=(P) +#define ExprClearProperty(E,P) (E)->flags&=~(P) +#define ExprAlwaysTrue(E) (((E)->flags&(EP_OuterON|EP_IsTrue))==EP_IsTrue) +#define ExprAlwaysFalse(E) (((E)->flags&(EP_OuterON|EP_IsFalse))==EP_IsFalse) + +/* Macros used to ensure that the correct members of unions are accessed +** in Expr. +*/ +#define ExprUseUToken(E) (((E)->flags&EP_IntValue)==0) +#define ExprUseUValue(E) (((E)->flags&EP_IntValue)!=0) +#define ExprUseWOfst(E) (((E)->flags&(EP_InnerON|EP_OuterON))==0) +#define ExprUseWJoin(E) (((E)->flags&(EP_InnerON|EP_OuterON))!=0) +#define ExprUseXList(E) (((E)->flags&EP_xIsSelect)==0) +#define ExprUseXSelect(E) (((E)->flags&EP_xIsSelect)!=0) +#define ExprUseYTab(E) (((E)->flags&(EP_WinFunc|EP_Subrtn))==0) +#define ExprUseYWin(E) (((E)->flags&EP_WinFunc)!=0) +#define ExprUseYSub(E) (((E)->flags&EP_Subrtn)!=0) + +/* Flags for use with Expr.vvaFlags +*/ +#define EP_NoReduce 0x01 /* Cannot EXPRDUP_REDUCE this Expr */ +#define EP_Immutable 0x02 /* Do not change this Expr node */ + +/* The ExprSetVVAProperty() macro is used for Verification, Validation, +** and Accreditation only. It works like ExprSetProperty() during VVA +** processes but is a no-op for delivery. +*/ +#ifdef SQLITE_DEBUG +# define ExprSetVVAProperty(E,P) (E)->vvaFlags|=(P) +# define ExprHasVVAProperty(E,P) (((E)->vvaFlags&(P))!=0) +# define ExprClearVVAProperties(E) (E)->vvaFlags = 0 +#else +# define ExprSetVVAProperty(E,P) +# define ExprHasVVAProperty(E,P) 0 +# define ExprClearVVAProperties(E) +#endif + +/* +** Macros to determine the number of bytes required by a normal Expr +** struct, an Expr struct with the EP_Reduced flag set in Expr.flags +** and an Expr struct with the EP_TokenOnly flag set. +*/ +#define EXPR_FULLSIZE sizeof(Expr) /* Full size */ +#define EXPR_REDUCEDSIZE offsetof(Expr,iTable) /* Common features */ +#define EXPR_TOKENONLYSIZE offsetof(Expr,pLeft) /* Fewer features */ + +/* +** Flags passed to the sqlite3ExprDup() function. See the header comment +** above sqlite3ExprDup() for details. +*/ +#define EXPRDUP_REDUCE 0x0001 /* Used reduced-size Expr nodes */ + +/* +** True if the expression passed as an argument was a function with +** an OVER() clause (a window function). +*/ +#ifdef SQLITE_OMIT_WINDOWFUNC +# define IsWindowFunc(p) 0 +#else +# define IsWindowFunc(p) ( \ + ExprHasProperty((p), EP_WinFunc) && p->y.pWin->eFrmType!=TK_FILTER \ + ) +#endif + +/* +** A list of expressions. Each expression may optionally have a +** name. An expr/name combination can be used in several ways, such +** as the list of "expr AS ID" fields following a "SELECT" or in the +** list of "ID = expr" items in an UPDATE. A list of expressions can +** also be used as the argument to a function, in which case the a.zName +** field is not used. +** +** In order to try to keep memory usage down, the Expr.a.zEName field +** is used for multiple purposes: +** +** eEName Usage +** ---------- ------------------------- +** ENAME_NAME (1) the AS of result set column +** (2) COLUMN= of an UPDATE +** +** ENAME_TAB DB.TABLE.NAME used to resolve names +** of subqueries +** +** ENAME_SPAN Text of the original result set +** expression. +*/ +struct ExprList { + int nExpr; /* Number of expressions on the list */ + int nAlloc; /* Number of a[] slots allocated */ + struct ExprList_item { /* For each expression in the list */ + Expr *pExpr; /* The parse tree for this expression */ + char *zEName; /* Token associated with this expression */ + struct { + u8 sortFlags; /* Mask of KEYINFO_ORDER_* flags */ + unsigned eEName :2; /* Meaning of zEName */ + unsigned done :1; /* Indicates when processing is finished */ + unsigned reusable :1; /* Constant expression is reusable */ + unsigned bSorterRef :1; /* Defer evaluation until after sorting */ + unsigned bNulls :1; /* True if explicit "NULLS FIRST/LAST" */ + unsigned bUsed :1; /* This column used in a SF_NestedFrom subquery */ + unsigned bUsingTerm:1; /* Term from the USING clause of a NestedFrom */ + unsigned bNoExpand: 1; /* Term is an auxiliary in NestedFrom and should + ** not be expanded by "*" in parent queries */ + } fg; + union { + struct { /* Used by any ExprList other than Parse.pConsExpr */ + u16 iOrderByCol; /* For ORDER BY, column number in result set */ + u16 iAlias; /* Index into Parse.aAlias[] for zName */ + } x; + int iConstExprReg; /* Register in which Expr value is cached. Used only + ** by Parse.pConstExpr */ + } u; + } a[1]; /* One slot for each expression in the list */ +}; + +/* +** Allowed values for Expr.a.eEName +*/ +#define ENAME_NAME 0 /* The AS clause of a result set */ +#define ENAME_SPAN 1 /* Complete text of the result set expression */ +#define ENAME_TAB 2 /* "DB.TABLE.NAME" for the result set */ + +/* +** An instance of this structure can hold a simple list of identifiers, +** such as the list "a,b,c" in the following statements: +** +** INSERT INTO t(a,b,c) VALUES ...; +** CREATE INDEX idx ON t(a,b,c); +** CREATE TRIGGER trig BEFORE UPDATE ON t(a,b,c) ...; +** +** The IdList.a.idx field is used when the IdList represents the list of +** column names after a table name in an INSERT statement. In the statement +** +** INSERT INTO t(a,b,c) ... +** +** If "a" is the k-th column of table "t", then IdList.a[0].idx==k. +*/ +struct IdList { + int nId; /* Number of identifiers on the list */ + u8 eU4; /* Which element of a.u4 is valid */ + struct IdList_item { + char *zName; /* Name of the identifier */ + union { + int idx; /* Index in some Table.aCol[] of a column named zName */ + Expr *pExpr; /* Expr to implement a USING variable -- NOT USED */ + } u4; + } a[1]; +}; + +/* +** Allowed values for IdList.eType, which determines which value of the a.u4 +** is valid. +*/ +#define EU4_NONE 0 /* Does not use IdList.a.u4 */ +#define EU4_IDX 1 /* Uses IdList.a.u4.idx */ +#define EU4_EXPR 2 /* Uses IdList.a.u4.pExpr -- NOT CURRENTLY USED */ + +/* +** The SrcItem object represents a single term in the FROM clause of a query. +** The SrcList object is mostly an array of SrcItems. +** +** The jointype starts out showing the join type between the current table +** and the next table on the list. The parser builds the list this way. +** But sqlite3SrcListShiftJoinType() later shifts the jointypes so that each +** jointype expresses the join between the table and the previous table. +** +** In the colUsed field, the high-order bit (bit 63) is set if the table +** contains more than 63 columns and the 64-th or later column is used. +** +** Union member validity: +** +** u1.zIndexedBy fg.isIndexedBy && !fg.isTabFunc +** u1.pFuncArg fg.isTabFunc && !fg.isIndexedBy +** u2.pIBIndex fg.isIndexedBy && !fg.isCte +** u2.pCteUse fg.isCte && !fg.isIndexedBy +*/ +struct SrcItem { + Schema *pSchema; /* Schema to which this item is fixed */ + char *zDatabase; /* Name of database holding this table */ + char *zName; /* Name of the table */ + char *zAlias; /* The "B" part of a "A AS B" phrase. zName is the "A" */ + Table *pTab; /* An SQL table corresponding to zName */ + Select *pSelect; /* A SELECT statement used in place of a table name */ + int addrFillSub; /* Address of subroutine to manifest a subquery */ + int regReturn; /* Register holding return address of addrFillSub */ + int regResult; /* Registers holding results of a co-routine */ + struct { + u8 jointype; /* Type of join between this table and the previous */ + unsigned notIndexed :1; /* True if there is a NOT INDEXED clause */ + unsigned isIndexedBy :1; /* True if there is an INDEXED BY clause */ + unsigned isTabFunc :1; /* True if table-valued-function syntax */ + unsigned isCorrelated :1; /* True if sub-query is correlated */ + unsigned isMaterialized:1; /* This is a materialized view */ + unsigned viaCoroutine :1; /* Implemented as a co-routine */ + unsigned isRecursive :1; /* True for recursive reference in WITH */ + unsigned fromDDL :1; /* Comes from sqlite_schema */ + unsigned isCte :1; /* This is a CTE */ + unsigned notCte :1; /* This item may not match a CTE */ + unsigned isUsing :1; /* u3.pUsing is valid */ + unsigned isOn :1; /* u3.pOn was once valid and non-NULL */ + unsigned isSynthUsing :1; /* u3.pUsing is synthesized from NATURAL */ + unsigned isNestedFrom :1; /* pSelect is a SF_NestedFrom subquery */ + } fg; + int iCursor; /* The VDBE cursor number used to access this table */ + union { + Expr *pOn; /* fg.isUsing==0 => The ON clause of a join */ + IdList *pUsing; /* fg.isUsing==1 => The USING clause of a join */ + } u3; + Bitmask colUsed; /* Bit N set if column N used. Details above for N>62 */ + union { + char *zIndexedBy; /* Identifier from "INDEXED BY <zIndex>" clause */ + ExprList *pFuncArg; /* Arguments to table-valued-function */ + } u1; + union { + Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */ + CteUse *pCteUse; /* CTE Usage info when fg.isCte is true */ + } u2; +}; + +/* +** The OnOrUsing object represents either an ON clause or a USING clause. +** It can never be both at the same time, but it can be neither. +*/ +struct OnOrUsing { + Expr *pOn; /* The ON clause of a join */ + IdList *pUsing; /* The USING clause of a join */ +}; + +/* +** This object represents one or more tables that are the source of +** content for an SQL statement. For example, a single SrcList object +** is used to hold the FROM clause of a SELECT statement. SrcList also +** represents the target tables for DELETE, INSERT, and UPDATE statements. +** +*/ +struct SrcList { + int nSrc; /* Number of tables or subqueries in the FROM clause */ + u32 nAlloc; /* Number of entries allocated in a[] below */ + SrcItem a[1]; /* One entry for each identifier on the list */ +}; + +/* +** Permitted values of the SrcList.a.jointype field +*/ +#define JT_INNER 0x01 /* Any kind of inner or cross join */ +#define JT_CROSS 0x02 /* Explicit use of the CROSS keyword */ +#define JT_NATURAL 0x04 /* True for a "natural" join */ +#define JT_LEFT 0x08 /* Left outer join */ +#define JT_RIGHT 0x10 /* Right outer join */ +#define JT_OUTER 0x20 /* The "OUTER" keyword is present */ +#define JT_LTORJ 0x40 /* One of the LEFT operands of a RIGHT JOIN + ** Mnemonic: Left Table Of Right Join */ +#define JT_ERROR 0x80 /* unknown or unsupported join type */ + +/* +** Flags appropriate for the wctrlFlags parameter of sqlite3WhereBegin() +** and the WhereInfo.wctrlFlags member. +** +** Value constraints (enforced via assert()): +** WHERE_USE_LIMIT == SF_FixedLimit +*/ +#define WHERE_ORDERBY_NORMAL 0x0000 /* No-op */ +#define WHERE_ORDERBY_MIN 0x0001 /* ORDER BY processing for min() func */ +#define WHERE_ORDERBY_MAX 0x0002 /* ORDER BY processing for max() func */ +#define WHERE_ONEPASS_DESIRED 0x0004 /* Want to do one-pass UPDATE/DELETE */ +#define WHERE_ONEPASS_MULTIROW 0x0008 /* ONEPASS is ok with multiple rows */ +#define WHERE_DUPLICATES_OK 0x0010 /* Ok to return a row more than once */ +#define WHERE_OR_SUBCLAUSE 0x0020 /* Processing a sub-WHERE as part of + ** the OR optimization */ +#define WHERE_GROUPBY 0x0040 /* pOrderBy is really a GROUP BY */ +#define WHERE_DISTINCTBY 0x0080 /* pOrderby is really a DISTINCT clause */ +#define WHERE_WANT_DISTINCT 0x0100 /* All output needs to be distinct */ +#define WHERE_SORTBYGROUP 0x0200 /* Support sqlite3WhereIsSorted() */ +#define WHERE_AGG_DISTINCT 0x0400 /* Query is "SELECT agg(DISTINCT ...)" */ +#define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */ +#define WHERE_RIGHT_JOIN 0x1000 /* Processing a RIGHT JOIN */ + /* 0x2000 not currently used */ +#define WHERE_USE_LIMIT 0x4000 /* Use the LIMIT in cost estimates */ + /* 0x8000 not currently used */ + +/* Allowed return values from sqlite3WhereIsDistinct() +*/ +#define WHERE_DISTINCT_NOOP 0 /* DISTINCT keyword not used */ +#define WHERE_DISTINCT_UNIQUE 1 /* No duplicates */ +#define WHERE_DISTINCT_ORDERED 2 /* All duplicates are adjacent */ +#define WHERE_DISTINCT_UNORDERED 3 /* Duplicates are scattered */ + +/* +** A NameContext defines a context in which to resolve table and column +** names. The context consists of a list of tables (the pSrcList) field and +** a list of named expression (pEList). The named expression list may +** be NULL. The pSrc corresponds to the FROM clause of a SELECT or +** to the table being operated on by INSERT, UPDATE, or DELETE. The +** pEList corresponds to the result set of a SELECT and is NULL for +** other statements. +** +** NameContexts can be nested. When resolving names, the inner-most +** context is searched first. If no match is found, the next outer +** context is checked. If there is still no match, the next context +** is checked. This process continues until either a match is found +** or all contexts are check. When a match is found, the nRef member of +** the context containing the match is incremented. +** +** Each subquery gets a new NameContext. The pNext field points to the +** NameContext in the parent query. Thus the process of scanning the +** NameContext list corresponds to searching through successively outer +** subqueries looking for a match. +*/ +struct NameContext { + Parse *pParse; /* The parser */ + SrcList *pSrcList; /* One or more tables used to resolve names */ + union { + ExprList *pEList; /* Optional list of result-set columns */ + AggInfo *pAggInfo; /* Information about aggregates at this level */ + Upsert *pUpsert; /* ON CONFLICT clause information from an upsert */ + int iBaseReg; /* For TK_REGISTER when parsing RETURNING */ + } uNC; + NameContext *pNext; /* Next outer name context. NULL for outermost */ + int nRef; /* Number of names resolved by this context */ + int nNcErr; /* Number of errors encountered while resolving names */ + int ncFlags; /* Zero or more NC_* flags defined below */ + Select *pWinSelect; /* SELECT statement for any window functions */ +}; + +/* +** Allowed values for the NameContext, ncFlags field. +** +** Value constraints (all checked via assert()): +** NC_HasAgg == SF_HasAgg == EP_Agg +** NC_MinMaxAgg == SF_MinMaxAgg == SQLITE_FUNC_MINMAX +** NC_OrderAgg == SF_OrderByReqd == SQLITE_FUNC_ANYORDER +** NC_HasWin == EP_Win +** +*/ +#define NC_AllowAgg 0x000001 /* Aggregate functions are allowed here */ +#define NC_PartIdx 0x000002 /* True if resolving a partial index WHERE */ +#define NC_IsCheck 0x000004 /* True if resolving a CHECK constraint */ +#define NC_GenCol 0x000008 /* True for a GENERATED ALWAYS AS clause */ +#define NC_HasAgg 0x000010 /* One or more aggregate functions seen */ +#define NC_IdxExpr 0x000020 /* True if resolving columns of CREATE INDEX */ +#define NC_SelfRef 0x00002e /* Combo: PartIdx, isCheck, GenCol, and IdxExpr */ +#define NC_Subquery 0x000040 /* A subquery has been seen */ +#define NC_UEList 0x000080 /* True if uNC.pEList is used */ +#define NC_UAggInfo 0x000100 /* True if uNC.pAggInfo is used */ +#define NC_UUpsert 0x000200 /* True if uNC.pUpsert is used */ +#define NC_UBaseReg 0x000400 /* True if uNC.iBaseReg is used */ +#define NC_MinMaxAgg 0x001000 /* min/max aggregates seen. See note above */ +#define NC_Complex 0x002000 /* True if a function or subquery seen */ +#define NC_AllowWin 0x004000 /* Window functions are allowed here */ +#define NC_HasWin 0x008000 /* One or more window functions seen */ +#define NC_IsDDL 0x010000 /* Resolving names in a CREATE statement */ +#define NC_InAggFunc 0x020000 /* True if analyzing arguments to an agg func */ +#define NC_FromDDL 0x040000 /* SQL text comes from sqlite_schema */ +#define NC_NoSelect 0x080000 /* Do not descend into sub-selects */ +#define NC_OrderAgg 0x8000000 /* Has an aggregate other than count/min/max */ + +/* +** An instance of the following object describes a single ON CONFLICT +** clause in an upsert. +** +** The pUpsertTarget field is only set if the ON CONFLICT clause includes +** conflict-target clause. (In "ON CONFLICT(a,b)" the "(a,b)" is the +** conflict-target clause.) The pUpsertTargetWhere is the optional +** WHERE clause used to identify partial unique indexes. +** +** pUpsertSet is the list of column=expr terms of the UPDATE statement. +** The pUpsertSet field is NULL for a ON CONFLICT DO NOTHING. The +** pUpsertWhere is the WHERE clause for the UPDATE and is NULL if the +** WHERE clause is omitted. +*/ +struct Upsert { + ExprList *pUpsertTarget; /* Optional description of conflict target */ + Expr *pUpsertTargetWhere; /* WHERE clause for partial index targets */ + ExprList *pUpsertSet; /* The SET clause from an ON CONFLICT UPDATE */ + Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */ + Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */ + u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */ + /* Above this point is the parse tree for the ON CONFLICT clauses. + ** The next group of fields stores intermediate data. */ + void *pToFree; /* Free memory when deleting the Upsert object */ + /* All fields above are owned by the Upsert object and must be freed + ** when the Upsert is destroyed. The fields below are used to transfer + ** information from the INSERT processing down into the UPDATE processing + ** while generating code. The fields below are owned by the INSERT + ** statement and will be freed by INSERT processing. */ + Index *pUpsertIdx; /* UNIQUE constraint specified by pUpsertTarget */ + SrcList *pUpsertSrc; /* Table to be updated */ + int regData; /* First register holding array of VALUES */ + int iDataCur; /* Index of the data cursor */ + int iIdxCur; /* Index of the first index cursor */ +}; + +/* +** An instance of the following structure contains all information +** needed to generate code for a single SELECT statement. +** +** See the header comment on the computeLimitRegisters() routine for a +** detailed description of the meaning of the iLimit and iOffset fields. +** +** addrOpenEphm[] entries contain the address of OP_OpenEphemeral opcodes. +** These addresses must be stored so that we can go back and fill in +** the P4_KEYINFO and P2 parameters later. Neither the KeyInfo nor +** the number of columns in P2 can be computed at the same time +** as the OP_OpenEphm instruction is coded because not +** enough information about the compound query is known at that point. +** The KeyInfo for addrOpenTran[0] and [1] contains collating sequences +** for the result set. The KeyInfo for addrOpenEphm[2] contains collating +** sequences for the ORDER BY clause. +*/ +struct Select { + u8 op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */ + LogEst nSelectRow; /* Estimated number of result rows */ + u32 selFlags; /* Various SF_* values */ + int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */ + u32 selId; /* Unique identifier number for this SELECT */ + int addrOpenEphm[2]; /* OP_OpenEphem opcodes related to this select */ + ExprList *pEList; /* The fields of the result */ + SrcList *pSrc; /* The FROM clause */ + Expr *pWhere; /* The WHERE clause */ + ExprList *pGroupBy; /* The GROUP BY clause */ + Expr *pHaving; /* The HAVING clause */ + ExprList *pOrderBy; /* The ORDER BY clause */ + Select *pPrior; /* Prior select in a compound select statement */ + Select *pNext; /* Next select to the left in a compound */ + Expr *pLimit; /* LIMIT expression. NULL means not used. */ + With *pWith; /* WITH clause attached to this select. Or NULL. */ +#ifndef SQLITE_OMIT_WINDOWFUNC + Window *pWin; /* List of window functions */ + Window *pWinDefn; /* List of named window definitions */ +#endif +}; + +/* +** Allowed values for Select.selFlags. The "SF" prefix stands for +** "Select Flag". +** +** Value constraints (all checked via assert()) +** SF_HasAgg == NC_HasAgg +** SF_MinMaxAgg == NC_MinMaxAgg == SQLITE_FUNC_MINMAX +** SF_OrderByReqd == NC_OrderAgg == SQLITE_FUNC_ANYORDER +** SF_FixedLimit == WHERE_USE_LIMIT +*/ +#define SF_Distinct 0x0000001 /* Output should be DISTINCT */ +#define SF_All 0x0000002 /* Includes the ALL keyword */ +#define SF_Resolved 0x0000004 /* Identifiers have been resolved */ +#define SF_Aggregate 0x0000008 /* Contains agg functions or a GROUP BY */ +#define SF_HasAgg 0x0000010 /* Contains aggregate functions */ +#define SF_UsesEphemeral 0x0000020 /* Uses the OpenEphemeral opcode */ +#define SF_Expanded 0x0000040 /* sqlite3SelectExpand() called on this */ +#define SF_HasTypeInfo 0x0000080 /* FROM subqueries have Table metadata */ +#define SF_Compound 0x0000100 /* Part of a compound query */ +#define SF_Values 0x0000200 /* Synthesized from VALUES clause */ +#define SF_MultiValue 0x0000400 /* Single VALUES term with multiple rows */ +#define SF_NestedFrom 0x0000800 /* Part of a parenthesized FROM clause */ +#define SF_MinMaxAgg 0x0001000 /* Aggregate containing min() or max() */ +#define SF_Recursive 0x0002000 /* The recursive part of a recursive CTE */ +#define SF_FixedLimit 0x0004000 /* nSelectRow set by a constant LIMIT */ +#define SF_MaybeConvert 0x0008000 /* Need convertCompoundSelectToSubquery() */ +#define SF_Converted 0x0010000 /* By convertCompoundSelectToSubquery() */ +#define SF_IncludeHidden 0x0020000 /* Include hidden columns in output */ +#define SF_ComplexResult 0x0040000 /* Result contains subquery or function */ +#define SF_WhereBegin 0x0080000 /* Really a WhereBegin() call. Debug Only */ +#define SF_WinRewrite 0x0100000 /* Window function rewrite accomplished */ +#define SF_View 0x0200000 /* SELECT statement is a view */ +#define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ +#define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ +#define SF_PushDown 0x1000000 /* SELECT has be modified by push-down opt */ +#define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ +#define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ +#define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ +#define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */ + +/* True if S exists and has SF_NestedFrom */ +#define IsNestedFrom(S) ((S)!=0 && ((S)->selFlags&SF_NestedFrom)!=0) + +/* +** The results of a SELECT can be distributed in several ways, as defined +** by one of the following macros. The "SRT" prefix means "SELECT Result +** Type". +** +** SRT_Union Store results as a key in a temporary index +** identified by pDest->iSDParm. +** +** SRT_Except Remove results from the temporary index pDest->iSDParm. +** +** SRT_Exists Store a 1 in memory cell pDest->iSDParm if the result +** set is not empty. +** +** SRT_Discard Throw the results away. This is used by SELECT +** statements within triggers whose only purpose is +** the side-effects of functions. +** +** SRT_Output Generate a row of output (using the OP_ResultRow +** opcode) for each row in the result set. +** +** SRT_Mem Only valid if the result is a single column. +** Store the first column of the first result row +** in register pDest->iSDParm then abandon the rest +** of the query. This destination implies "LIMIT 1". +** +** SRT_Set The result must be a single column. Store each +** row of result as the key in table pDest->iSDParm. +** Apply the affinity pDest->affSdst before storing +** results. Used to implement "IN (SELECT ...)". +** +** SRT_EphemTab Create an temporary table pDest->iSDParm and store +** the result there. The cursor is left open after +** returning. This is like SRT_Table except that +** this destination uses OP_OpenEphemeral to create +** the table first. +** +** SRT_Coroutine Generate a co-routine that returns a new row of +** results each time it is invoked. The entry point +** of the co-routine is stored in register pDest->iSDParm +** and the result row is stored in pDest->nDest registers +** starting with pDest->iSdst. +** +** SRT_Table Store results in temporary table pDest->iSDParm. +** SRT_Fifo This is like SRT_EphemTab except that the table +** is assumed to already be open. SRT_Fifo has +** the additional property of being able to ignore +** the ORDER BY clause. +** +** SRT_DistFifo Store results in a temporary table pDest->iSDParm. +** But also use temporary table pDest->iSDParm+1 as +** a record of all prior results and ignore any duplicate +** rows. Name means: "Distinct Fifo". +** +** SRT_Queue Store results in priority queue pDest->iSDParm (really +** an index). Append a sequence number so that all entries +** are distinct. +** +** SRT_DistQueue Store results in priority queue pDest->iSDParm only if +** the same record has never been stored before. The +** index at pDest->iSDParm+1 hold all prior stores. +** +** SRT_Upfrom Store results in the temporary table already opened by +** pDest->iSDParm. If (pDest->iSDParm<0), then the temp +** table is an intkey table - in this case the first +** column returned by the SELECT is used as the integer +** key. If (pDest->iSDParm>0), then the table is an index +** table. (pDest->iSDParm) is the number of key columns in +** each index record in this case. +*/ +#define SRT_Union 1 /* Store result as keys in an index */ +#define SRT_Except 2 /* Remove result from a UNION index */ +#define SRT_Exists 3 /* Store 1 if the result is not empty */ +#define SRT_Discard 4 /* Do not save the results anywhere */ +#define SRT_DistFifo 5 /* Like SRT_Fifo, but unique results only */ +#define SRT_DistQueue 6 /* Like SRT_Queue, but unique results only */ + +/* The DISTINCT clause is ignored for all of the above. Not that +** IgnorableDistinct() implies IgnorableOrderby() */ +#define IgnorableDistinct(X) ((X->eDest)<=SRT_DistQueue) + +#define SRT_Queue 7 /* Store result in an queue */ +#define SRT_Fifo 8 /* Store result as data with an automatic rowid */ + +/* The ORDER BY clause is ignored for all of the above */ +#define IgnorableOrderby(X) ((X->eDest)<=SRT_Fifo) + +#define SRT_Output 9 /* Output each row of result */ +#define SRT_Mem 10 /* Store result in a memory cell */ +#define SRT_Set 11 /* Store results as keys in an index */ +#define SRT_EphemTab 12 /* Create transient tab and store like SRT_Table */ +#define SRT_Coroutine 13 /* Generate a single row of result */ +#define SRT_Table 14 /* Store result as data with an automatic rowid */ +#define SRT_Upfrom 15 /* Store result as data with rowid */ + +/* +** An instance of this object describes where to put of the results of +** a SELECT statement. +*/ +struct SelectDest { + u8 eDest; /* How to dispose of the results. One of SRT_* above. */ + int iSDParm; /* A parameter used by the eDest disposal method */ + int iSDParm2; /* A second parameter for the eDest disposal method */ + int iSdst; /* Base register where results are written */ + int nSdst; /* Number of registers allocated */ + char *zAffSdst; /* Affinity used for SRT_Set */ + ExprList *pOrderBy; /* Key columns for SRT_Queue and SRT_DistQueue */ +}; + +/* +** During code generation of statements that do inserts into AUTOINCREMENT +** tables, the following information is attached to the Table.u.autoInc.p +** pointer of each autoincrement table to record some side information that +** the code generator needs. We have to keep per-table autoincrement +** information in case inserts are done within triggers. Triggers do not +** normally coordinate their activities, but we do need to coordinate the +** loading and saving of autoincrement information. +*/ +struct AutoincInfo { + AutoincInfo *pNext; /* Next info block in a list of them all */ + Table *pTab; /* Table this info block refers to */ + int iDb; /* Index in sqlite3.aDb[] of database holding pTab */ + int regCtr; /* Memory register holding the rowid counter */ +}; + +/* +** At least one instance of the following structure is created for each +** trigger that may be fired while parsing an INSERT, UPDATE or DELETE +** statement. All such objects are stored in the linked list headed at +** Parse.pTriggerPrg and deleted once statement compilation has been +** completed. +** +** A Vdbe sub-program that implements the body and WHEN clause of trigger +** TriggerPrg.pTrigger, assuming a default ON CONFLICT clause of +** TriggerPrg.orconf, is stored in the TriggerPrg.pProgram variable. +** The Parse.pTriggerPrg list never contains two entries with the same +** values for both pTrigger and orconf. +** +** The TriggerPrg.aColmask[0] variable is set to a mask of old.* columns +** accessed (or set to 0 for triggers fired as a result of INSERT +** statements). Similarly, the TriggerPrg.aColmask[1] variable is set to +** a mask of new.* columns used by the program. +*/ +struct TriggerPrg { + Trigger *pTrigger; /* Trigger this program was coded from */ + TriggerPrg *pNext; /* Next entry in Parse.pTriggerPrg list */ + SubProgram *pProgram; /* Program implementing pTrigger/orconf */ + int orconf; /* Default ON CONFLICT policy */ + u32 aColmask[2]; /* Masks of old.*, new.* columns accessed */ +}; + +/* +** The yDbMask datatype for the bitmask of all attached databases. +*/ +#if SQLITE_MAX_ATTACHED>30 + typedef unsigned char yDbMask[(SQLITE_MAX_ATTACHED+9)/8]; +# define DbMaskTest(M,I) (((M)[(I)/8]&(1<<((I)&7)))!=0) +# define DbMaskZero(M) memset((M),0,sizeof(M)) +# define DbMaskSet(M,I) (M)[(I)/8]|=(1<<((I)&7)) +# define DbMaskAllZero(M) sqlite3DbMaskAllZero(M) +# define DbMaskNonZero(M) (sqlite3DbMaskAllZero(M)==0) +#else + typedef unsigned int yDbMask; +# define DbMaskTest(M,I) (((M)&(((yDbMask)1)<<(I)))!=0) +# define DbMaskZero(M) ((M)=0) +# define DbMaskSet(M,I) ((M)|=(((yDbMask)1)<<(I))) +# define DbMaskAllZero(M) ((M)==0) +# define DbMaskNonZero(M) ((M)!=0) +#endif + +/* +** For each index X that has as one of its arguments either an expression +** or the name of a virtual generated column, and if X is in scope such that +** the value of the expression can simply be read from the index, then +** there is an instance of this object on the Parse.pIdxExpr list. +** +** During code generation, while generating code to evaluate expressions, +** this list is consulted and if a matching expression is found, the value +** is read from the index rather than being recomputed. +*/ +struct IndexedExpr { + Expr *pExpr; /* The expression contained in the index */ + int iDataCur; /* The data cursor associated with the index */ + int iIdxCur; /* The index cursor */ + int iIdxCol; /* The index column that contains value of pExpr */ + u8 bMaybeNullRow; /* True if we need an OP_IfNullRow check */ + u8 aff; /* Affinity of the pExpr expression */ + IndexedExpr *pIENext; /* Next in a list of all indexed expressions */ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + const char *zIdxName; /* Name of index, used only for bytecode comments */ +#endif +}; + +/* +** An instance of the ParseCleanup object specifies an operation that +** should be performed after parsing to deallocation resources obtained +** during the parse and which are no longer needed. +*/ +struct ParseCleanup { + ParseCleanup *pNext; /* Next cleanup task */ + void *pPtr; /* Pointer to object to deallocate */ + void (*xCleanup)(sqlite3*,void*); /* Deallocation routine */ +}; + +/* +** An SQL parser context. A copy of this structure is passed through +** the parser and down into all the parser action routine in order to +** carry around information that is global to the entire parse. +** +** The structure is divided into two parts. When the parser and code +** generate call themselves recursively, the first part of the structure +** is constant but the second part is reset at the beginning and end of +** each recursion. +** +** The nTableLock and aTableLock variables are only used if the shared-cache +** feature is enabled (if sqlite3Tsd()->useSharedData is true). They are +** used to store the set of table-locks required by the statement being +** compiled. Function sqlite3TableLock() is used to add entries to the +** list. +*/ +struct Parse { + sqlite3 *db; /* The main database structure */ + char *zErrMsg; /* An error message */ + Vdbe *pVdbe; /* An engine for executing database bytecode */ + int rc; /* Return code from execution */ + u8 colNamesSet; /* TRUE after OP_ColumnName has been issued to pVdbe */ + u8 checkSchema; /* Causes schema cookie check after an error */ + u8 nested; /* Number of nested calls to the parser/code generator */ + u8 nTempReg; /* Number of temporary registers in aTempReg[] */ + u8 isMultiWrite; /* True if statement may modify/insert multiple rows */ + u8 mayAbort; /* True if statement may throw an ABORT exception */ + u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */ + u8 okConstFactor; /* OK to factor out constants */ + u8 disableLookaside; /* Number of times lookaside has been disabled */ + u8 prepFlags; /* SQLITE_PREPARE_* flags */ + u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) + u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ +#endif +#ifdef SQLITE_DEBUG + u8 ifNotExists; /* Might be true if IF NOT EXISTS. Assert()s only */ +#endif + int nRangeReg; /* Size of the temporary register block */ + int iRangeReg; /* First register in temporary register block */ + int nErr; /* Number of errors seen */ + int nTab; /* Number of previously allocated VDBE cursors */ + int nMem; /* Number of memory cells used so far */ + int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ + int iSelfTab; /* Table associated with an index on expr, or negative + ** of the base register during check-constraint eval */ + int nLabel; /* The *negative* of the number of labels used */ + int nLabelAlloc; /* Number of slots in aLabel */ + int *aLabel; /* Space to hold the labels */ + ExprList *pConstExpr;/* Constant expressions */ + IndexedExpr *pIdxEpr;/* List of expressions used by active indexes */ + Token constraintName;/* Name of the constraint currently being parsed */ + yDbMask writeMask; /* Start a write transaction on these databases */ + yDbMask cookieMask; /* Bitmask of schema verified databases */ + int regRowid; /* Register holding rowid of CREATE TABLE entry */ + int regRoot; /* Register holding root page number for new objects */ + int nMaxArg; /* Max args passed to user function by sub-program */ + int nSelect; /* Number of SELECT stmts. Counter for Select.selId */ +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + u32 nProgressSteps; /* xProgress steps taken during sqlite3_prepare() */ +#endif +#ifndef SQLITE_OMIT_SHARED_CACHE + int nTableLock; /* Number of locks in aTableLock */ + TableLock *aTableLock; /* Required table locks for shared-cache mode */ +#endif + AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */ + Parse *pToplevel; /* Parse structure for main program (or NULL) */ + Table *pTriggerTab; /* Table triggers are being coded for */ + TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ + ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */ + union { + int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */ + Returning *pReturning; /* The RETURNING clause */ + } u1; + u32 oldmask; /* Mask of old.* columns referenced */ + u32 newmask; /* Mask of new.* columns referenced */ + LogEst nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ + u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ + u8 bReturning; /* Coding a RETURNING trigger */ + u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ + u8 disableTriggers; /* True to disable triggers */ + + /************************************************************************** + ** Fields above must be initialized to zero. The fields that follow, + ** down to the beginning of the recursive section, do not need to be + ** initialized as they will be set before being used. The boundary is + ** determined by offsetof(Parse,aTempReg). + **************************************************************************/ + + int aTempReg[8]; /* Holding area for temporary registers */ + Parse *pOuterParse; /* Outer Parse object when nested */ + Token sNameToken; /* Token with unqualified schema object name */ + + /************************************************************************ + ** Above is constant between recursions. Below is reset before and after + ** each recursion. The boundary between these two regions is determined + ** using offsetof(Parse,sLastToken) so the sLastToken field must be the + ** first field in the recursive region. + ************************************************************************/ + + Token sLastToken; /* The last token parsed */ + ynVar nVar; /* Number of '?' variables seen in the SQL so far */ + u8 iPkSortOrder; /* ASC or DESC for INTEGER PRIMARY KEY */ + u8 explain; /* True if the EXPLAIN flag is found on the query */ + u8 eParseMode; /* PARSE_MODE_XXX constant */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + int nVtabLock; /* Number of virtual tables to lock */ +#endif + int nHeight; /* Expression tree height of current sub-select */ +#ifndef SQLITE_OMIT_EXPLAIN + int addrExplain; /* Address of current OP_Explain opcode */ +#endif + VList *pVList; /* Mapping between variable names and numbers */ + Vdbe *pReprepare; /* VM being reprepared (sqlite3Reprepare()) */ + const char *zTail; /* All SQL text past the last semicolon parsed */ + Table *pNewTable; /* A table being constructed by CREATE TABLE */ + Index *pNewIndex; /* An index being constructed by CREATE INDEX. + ** Also used to hold redundant UNIQUE constraints + ** during a RENAME COLUMN */ + Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */ + const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + Token sArg; /* Complete text of a module argument */ + Table **apVtabLock; /* Pointer to virtual tables needing locking */ +#endif + With *pWith; /* Current WITH clause, or NULL */ +#ifndef SQLITE_OMIT_ALTERTABLE + RenameToken *pRename; /* Tokens subject to renaming by ALTER TABLE */ +#endif +}; + +/* Allowed values for Parse.eParseMode +*/ +#define PARSE_MODE_NORMAL 0 +#define PARSE_MODE_DECLARE_VTAB 1 +#define PARSE_MODE_RENAME 2 +#define PARSE_MODE_UNMAP 3 + +/* +** Sizes and pointers of various parts of the Parse object. +*/ +#define PARSE_HDR(X) (((char*)(X))+offsetof(Parse,zErrMsg)) +#define PARSE_HDR_SZ (offsetof(Parse,aTempReg)-offsetof(Parse,zErrMsg)) /* Recursive part w/o aColCache*/ +#define PARSE_RECURSE_SZ offsetof(Parse,sLastToken) /* Recursive part */ +#define PARSE_TAIL_SZ (sizeof(Parse)-PARSE_RECURSE_SZ) /* Non-recursive part */ +#define PARSE_TAIL(X) (((char*)(X))+PARSE_RECURSE_SZ) /* Pointer to tail */ + +/* +** Return true if currently inside an sqlite3_declare_vtab() call. +*/ +#ifdef SQLITE_OMIT_VIRTUALTABLE + #define IN_DECLARE_VTAB 0 +#else + #define IN_DECLARE_VTAB (pParse->eParseMode==PARSE_MODE_DECLARE_VTAB) +#endif + +#if defined(SQLITE_OMIT_ALTERTABLE) + #define IN_RENAME_OBJECT 0 +#else + #define IN_RENAME_OBJECT (pParse->eParseMode>=PARSE_MODE_RENAME) +#endif + +#if defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_OMIT_ALTERTABLE) + #define IN_SPECIAL_PARSE 0 +#else + #define IN_SPECIAL_PARSE (pParse->eParseMode!=PARSE_MODE_NORMAL) +#endif + +/* +** An instance of the following structure can be declared on a stack and used +** to save the Parse.zAuthContext value so that it can be restored later. +*/ +struct AuthContext { + const char *zAuthContext; /* Put saved Parse.zAuthContext here */ + Parse *pParse; /* The Parse structure */ +}; + +/* +** Bitfield flags for P5 value in various opcodes. +** +** Value constraints (enforced via assert()): +** OPFLAG_LENGTHARG == SQLITE_FUNC_LENGTH +** OPFLAG_TYPEOFARG == SQLITE_FUNC_TYPEOF +** OPFLAG_BULKCSR == BTREE_BULKLOAD +** OPFLAG_SEEKEQ == BTREE_SEEK_EQ +** OPFLAG_FORDELETE == BTREE_FORDELETE +** OPFLAG_SAVEPOSITION == BTREE_SAVEPOSITION +** OPFLAG_AUXDELETE == BTREE_AUXDELETE +*/ +#define OPFLAG_NCHANGE 0x01 /* OP_Insert: Set to update db->nChange */ + /* Also used in P2 (not P5) of OP_Delete */ +#define OPFLAG_NOCHNG 0x01 /* OP_VColumn nochange for UPDATE */ +#define OPFLAG_EPHEM 0x01 /* OP_Column: Ephemeral output is ok */ +#define OPFLAG_LASTROWID 0x20 /* Set to update db->lastRowid */ +#define OPFLAG_ISUPDATE 0x04 /* This OP_Insert is an sql UPDATE */ +#define OPFLAG_APPEND 0x08 /* This is likely to be an append */ +#define OPFLAG_USESEEKRESULT 0x10 /* Try to avoid a seek in BtreeInsert() */ +#define OPFLAG_ISNOOP 0x40 /* OP_Delete does pre-update-hook only */ +#define OPFLAG_LENGTHARG 0x40 /* OP_Column only used for length() */ +#define OPFLAG_TYPEOFARG 0x80 /* OP_Column only used for typeof() */ +#define OPFLAG_BYTELENARG 0xc0 /* OP_Column only for octet_length() */ +#define OPFLAG_BULKCSR 0x01 /* OP_Open** used to open bulk cursor */ +#define OPFLAG_SEEKEQ 0x02 /* OP_Open** cursor uses EQ seek only */ +#define OPFLAG_FORDELETE 0x08 /* OP_Open should use BTREE_FORDELETE */ +#define OPFLAG_P2ISREG 0x10 /* P2 to OP_Open** is a register number */ +#define OPFLAG_PERMUTE 0x01 /* OP_Compare: use the permutation */ +#define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete/Insert: save cursor pos */ +#define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */ +#define OPFLAG_NOCHNG_MAGIC 0x6d /* OP_MakeRecord: serialtype 10 is ok */ +#define OPFLAG_PREFORMAT 0x80 /* OP_Insert uses preformatted cell */ + +/* +** Each trigger present in the database schema is stored as an instance of +** struct Trigger. +** +** Pointers to instances of struct Trigger are stored in two ways. +** 1. In the "trigHash" hash table (part of the sqlite3* that represents the +** database). This allows Trigger structures to be retrieved by name. +** 2. All triggers associated with a single table form a linked list, using the +** pNext member of struct Trigger. A pointer to the first element of the +** linked list is stored as the "pTrigger" member of the associated +** struct Table. +** +** The "step_list" member points to the first element of a linked list +** containing the SQL statements specified as the trigger program. +*/ +struct Trigger { + char *zName; /* The name of the trigger */ + char *table; /* The table or view to which the trigger applies */ + u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT */ + u8 tr_tm; /* One of TRIGGER_BEFORE, TRIGGER_AFTER */ + u8 bReturning; /* This trigger implements a RETURNING clause */ + Expr *pWhen; /* The WHEN clause of the expression (may be NULL) */ + IdList *pColumns; /* If this is an UPDATE OF <column-list> trigger, + the <column-list> is stored here */ + Schema *pSchema; /* Schema containing the trigger */ + Schema *pTabSchema; /* Schema containing the table */ + TriggerStep *step_list; /* Link list of trigger program steps */ + Trigger *pNext; /* Next trigger associated with the table */ +}; + +/* +** A trigger is either a BEFORE or an AFTER trigger. The following constants +** determine which. +** +** If there are multiple triggers, you might of some BEFORE and some AFTER. +** In that cases, the constants below can be ORed together. +*/ +#define TRIGGER_BEFORE 1 +#define TRIGGER_AFTER 2 + +/* +** An instance of struct TriggerStep is used to store a single SQL statement +** that is a part of a trigger-program. +** +** Instances of struct TriggerStep are stored in a singly linked list (linked +** using the "pNext" member) referenced by the "step_list" member of the +** associated struct Trigger instance. The first element of the linked list is +** the first step of the trigger-program. +** +** The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or +** "SELECT" statement. The meanings of the other members is determined by the +** value of "op" as follows: +** +** (op == TK_INSERT) +** orconf -> stores the ON CONFLICT algorithm +** pSelect -> The content to be inserted - either a SELECT statement or +** a VALUES clause. +** zTarget -> Dequoted name of the table to insert into. +** pIdList -> If this is an INSERT INTO ... (<column-names>) VALUES ... +** statement, then this stores the column-names to be +** inserted into. +** pUpsert -> The ON CONFLICT clauses for an Upsert +** +** (op == TK_DELETE) +** zTarget -> Dequoted name of the table to delete from. +** pWhere -> The WHERE clause of the DELETE statement if one is specified. +** Otherwise NULL. +** +** (op == TK_UPDATE) +** zTarget -> Dequoted name of the table to update. +** pWhere -> The WHERE clause of the UPDATE statement if one is specified. +** Otherwise NULL. +** pExprList -> A list of the columns to update and the expressions to update +** them to. See sqlite3Update() documentation of "pChanges" +** argument. +** +** (op == TK_SELECT) +** pSelect -> The SELECT statement +** +** (op == TK_RETURNING) +** pExprList -> The list of expressions that follow the RETURNING keyword. +** +*/ +struct TriggerStep { + u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT, + ** or TK_RETURNING */ + u8 orconf; /* OE_Rollback etc. */ + Trigger *pTrig; /* The trigger that this step is a part of */ + Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */ + char *zTarget; /* Target table for DELETE, UPDATE, INSERT */ + SrcList *pFrom; /* FROM clause for UPDATE statement (if any) */ + Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */ + ExprList *pExprList; /* SET clause for UPDATE, or RETURNING clause */ + IdList *pIdList; /* Column names for INSERT */ + Upsert *pUpsert; /* Upsert clauses on an INSERT */ + char *zSpan; /* Original SQL text of this command */ + TriggerStep *pNext; /* Next in the link-list */ + TriggerStep *pLast; /* Last element in link-list. Valid for 1st elem only */ +}; + +/* +** Information about a RETURNING clause +*/ +struct Returning { + Parse *pParse; /* The parse that includes the RETURNING clause */ + ExprList *pReturnEL; /* List of expressions to return */ + Trigger retTrig; /* The transient trigger that implements RETURNING */ + TriggerStep retTStep; /* The trigger step */ + int iRetCur; /* Transient table holding RETURNING results */ + int nRetCol; /* Number of in pReturnEL after expansion */ + int iRetReg; /* Register array for holding a row of RETURNING */ +}; + +/* +** An objected used to accumulate the text of a string where we +** do not necessarily know how big the string will be in the end. +*/ +struct sqlite3_str { + sqlite3 *db; /* Optional database for lookaside. Can be NULL */ + char *zText; /* The string collected so far */ + u32 nAlloc; /* Amount of space allocated in zText */ + u32 mxAlloc; /* Maximum allowed allocation. 0 for no malloc usage */ + u32 nChar; /* Length of the string so far */ + u8 accError; /* SQLITE_NOMEM or SQLITE_TOOBIG */ + u8 printfFlags; /* SQLITE_PRINTF flags below */ +}; +#define SQLITE_PRINTF_INTERNAL 0x01 /* Internal-use-only converters allowed */ +#define SQLITE_PRINTF_SQLFUNC 0x02 /* SQL function arguments to VXPrintf */ +#define SQLITE_PRINTF_MALLOCED 0x04 /* True if xText is allocated space */ + +#define isMalloced(X) (((X)->printfFlags & SQLITE_PRINTF_MALLOCED)!=0) + +/* +** The following object is the header for an "RCStr" or "reference-counted +** string". An RCStr is passed around and used like any other char* +** that has been dynamically allocated. The important interface +** differences: +** +** 1. RCStr strings are reference counted. They are deallocated +** when the reference count reaches zero. +** +** 2. Use sqlite3RCStrUnref() to free an RCStr string rather than +** sqlite3_free() +** +** 3. Make a (read-only) copy of a read-only RCStr string using +** sqlite3RCStrRef(). +*/ +struct RCStr { + u64 nRCRef; /* Number of references */ + /* Total structure size should be a multiple of 8 bytes for alignment */ +}; + +/* +** A pointer to this structure is used to communicate information +** from sqlite3Init and OP_ParseSchema into the sqlite3InitCallback. +*/ +typedef struct { + sqlite3 *db; /* The database being initialized */ + char **pzErrMsg; /* Error message stored here */ + int iDb; /* 0 for main database. 1 for TEMP, 2.. for ATTACHed */ + int rc; /* Result code stored here */ + u32 mInitFlags; /* Flags controlling error messages */ + u32 nInitRow; /* Number of rows processed */ + Pgno mxPage; /* Maximum page number. 0 for no limit. */ +} InitData; + +/* +** Allowed values for mInitFlags +*/ +#define INITFLAG_AlterMask 0x0003 /* Types of ALTER */ +#define INITFLAG_AlterRename 0x0001 /* Reparse after a RENAME */ +#define INITFLAG_AlterDrop 0x0002 /* Reparse after a DROP COLUMN */ +#define INITFLAG_AlterAdd 0x0003 /* Reparse after an ADD COLUMN */ + +/* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled +** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning +** parameters are for temporary use during development, to help find +** optimal values for parameters in the query planner. The should not +** be used on trunk check-ins. They are a temporary mechanism available +** for transient development builds only. +** +** Tuning parameters are numbered starting with 1. +*/ +#define SQLITE_NTUNE 6 /* Should be zero for all trunk check-ins */ +#ifdef SQLITE_DEBUG +# define Tuning(X) (sqlite3Config.aTune[(X)-1]) +#else +# define Tuning(X) 0 +#endif + +/* +** Structure containing global configuration data for the SQLite library. +** +** This structure also contains some state information. +*/ +struct Sqlite3Config { + int bMemstat; /* True to enable memory status */ + u8 bCoreMutex; /* True to enable core mutexing */ + u8 bFullMutex; /* True to enable full mutexing */ + u8 bOpenUri; /* True to interpret filenames as URIs */ + u8 bUseCis; /* Use covering indices for full-scans */ + u8 bSmallMalloc; /* Avoid large memory allocations if true */ + u8 bExtraSchemaChecks; /* Verify type,name,tbl_name in schema */ + u8 bUseLongDouble; /* Make use of long double */ + int mxStrlen; /* Maximum string length */ + int neverCorrupt; /* Database is always well-formed */ + int szLookaside; /* Default lookaside buffer size */ + int nLookaside; /* Default lookaside buffer count */ + int nStmtSpill; /* Stmt-journal spill-to-disk threshold */ + sqlite3_mem_methods m; /* Low-level memory allocation interface */ + sqlite3_mutex_methods mutex; /* Low-level mutex interface */ + sqlite3_pcache_methods2 pcache2; /* Low-level page-cache interface */ + void *pHeap; /* Heap storage space */ + int nHeap; /* Size of pHeap[] */ + int mnReq, mxReq; /* Min and max heap requests sizes */ + sqlite3_int64 szMmap; /* mmap() space per open file */ + sqlite3_int64 mxMmap; /* Maximum value for szMmap */ + void *pPage; /* Page cache memory */ + int szPage; /* Size of each page in pPage[] */ + int nPage; /* Number of pages in pPage[] */ + int mxParserStack; /* maximum depth of the parser stack */ + int sharedCacheEnabled; /* true if shared-cache mode enabled */ + u32 szPma; /* Maximum Sorter PMA size */ + /* The above might be initialized to non-zero. The following need to always + ** initially be zero, however. */ + int isInit; /* True after initialization has finished */ + int inProgress; /* True while initialization in progress */ + int isMutexInit; /* True after mutexes are initialized */ + int isMallocInit; /* True after malloc is initialized */ + int isPCacheInit; /* True after malloc is initialized */ + int nRefInitMutex; /* Number of users of pInitMutex */ + sqlite3_mutex *pInitMutex; /* Mutex used by sqlite3_initialize() */ + void (*xLog)(void*,int,const char*); /* Function for logging */ + void *pLogArg; /* First argument to xLog() */ +#ifdef SQLITE_ENABLE_SQLLOG + void(*xSqllog)(void*,sqlite3*,const char*, int); + void *pSqllogArg; +#endif +#ifdef SQLITE_VDBE_COVERAGE + /* The following callback (if not NULL) is invoked on every VDBE branch + ** operation. Set the callback using SQLITE_TESTCTRL_VDBE_COVERAGE. + */ + void (*xVdbeBranch)(void*,unsigned iSrcLine,u8 eThis,u8 eMx); /* Callback */ + void *pVdbeBranchArg; /* 1st argument */ +#endif +#ifndef SQLITE_OMIT_DESERIALIZE + sqlite3_int64 mxMemdbSize; /* Default max memdb size */ +#endif +#ifndef SQLITE_UNTESTABLE + int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ +#endif + int bLocaltimeFault; /* True to fail localtime() calls */ + int (*xAltLocaltime)(const void*,void*); /* Alternative localtime() routine */ + int iOnceResetThreshold; /* When to reset OP_Once counters */ + u32 szSorterRef; /* Min size in bytes to use sorter-refs */ + unsigned int iPrngSeed; /* Alternative fixed seed for the PRNG */ + /* vvvv--- must be last ---vvv */ +#ifdef SQLITE_DEBUG + sqlite3_int64 aTune[SQLITE_NTUNE]; /* Tuning parameters */ +#endif +}; + +/* +** This macro is used inside of assert() statements to indicate that +** the assert is only valid on a well-formed database. Instead of: +** +** assert( X ); +** +** One writes: +** +** assert( X || CORRUPT_DB ); +** +** CORRUPT_DB is true during normal operation. CORRUPT_DB does not indicate +** that the database is definitely corrupt, only that it might be corrupt. +** For most test cases, CORRUPT_DB is set to false using a special +** sqlite3_test_control(). This enables assert() statements to prove +** things that are always true for well-formed databases. +*/ +#define CORRUPT_DB (sqlite3Config.neverCorrupt==0) + +/* +** Context pointer passed down through the tree-walk. +*/ +struct Walker { + Parse *pParse; /* Parser context. */ + int (*xExprCallback)(Walker*, Expr*); /* Callback for expressions */ + int (*xSelectCallback)(Walker*,Select*); /* Callback for SELECTs */ + void (*xSelectCallback2)(Walker*,Select*);/* Second callback for SELECTs */ + int walkerDepth; /* Number of subqueries */ + u16 eCode; /* A small processing code */ + u16 mWFlags; /* Use-dependent flags */ + union { /* Extra data for callback */ + NameContext *pNC; /* Naming context */ + int n; /* A counter */ + int iCur; /* A cursor number */ + SrcList *pSrcList; /* FROM clause */ + struct CCurHint *pCCurHint; /* Used by codeCursorHint() */ + struct RefSrcList *pRefSrcList; /* sqlite3ReferencesSrcList() */ + int *aiCol; /* array of column indexes */ + struct IdxCover *pIdxCover; /* Check for index coverage */ + ExprList *pGroupBy; /* GROUP BY clause */ + Select *pSelect; /* HAVING to WHERE clause ctx */ + struct WindowRewrite *pRewrite; /* Window rewrite context */ + struct WhereConst *pConst; /* WHERE clause constants */ + struct RenameCtx *pRename; /* RENAME COLUMN context */ + struct Table *pTab; /* Table of generated column */ + struct CoveringIndexCheck *pCovIdxCk; /* Check for covering index */ + SrcItem *pSrcItem; /* A single FROM clause item */ + DbFixer *pFix; /* See sqlite3FixSelect() */ + Mem *aMem; /* See sqlite3BtreeCursorHint() */ + } u; +}; + +/* +** The following structure contains information used by the sqliteFix... +** routines as they walk the parse tree to make database references +** explicit. +*/ +struct DbFixer { + Parse *pParse; /* The parsing context. Error messages written here */ + Walker w; /* Walker object */ + Schema *pSchema; /* Fix items to this schema */ + u8 bTemp; /* True for TEMP schema entries */ + const char *zDb; /* Make sure all objects are contained in this database */ + const char *zType; /* Type of the container - used for error messages */ + const Token *pName; /* Name of the container - used for error messages */ +}; + +/* Forward declarations */ +SQLITE_PRIVATE int sqlite3WalkExpr(Walker*, Expr*); +SQLITE_PRIVATE int sqlite3WalkExprNN(Walker*, Expr*); +SQLITE_PRIVATE int sqlite3WalkExprList(Walker*, ExprList*); +SQLITE_PRIVATE int sqlite3WalkSelect(Walker*, Select*); +SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker*, Select*); +SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker*, Select*); +SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker*, Expr*); +SQLITE_PRIVATE int sqlite3SelectWalkNoop(Walker*, Select*); +SQLITE_PRIVATE int sqlite3SelectWalkFail(Walker*, Select*); +SQLITE_PRIVATE int sqlite3WalkerDepthIncrease(Walker*,Select*); +SQLITE_PRIVATE void sqlite3WalkerDepthDecrease(Walker*,Select*); +SQLITE_PRIVATE void sqlite3WalkWinDefnDummyCallback(Walker*,Select*); + +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE void sqlite3SelectWalkAssert2(Walker*, Select*); +#endif + +#ifndef SQLITE_OMIT_CTE +SQLITE_PRIVATE void sqlite3SelectPopWith(Walker*, Select*); +#else +# define sqlite3SelectPopWith 0 +#endif + +/* +** Return code from the parse-tree walking primitives and their +** callbacks. +*/ +#define WRC_Continue 0 /* Continue down into children */ +#define WRC_Prune 1 /* Omit children but continue walking siblings */ +#define WRC_Abort 2 /* Abandon the tree walk */ + +/* +** A single common table expression +*/ +struct Cte { + char *zName; /* Name of this CTE */ + ExprList *pCols; /* List of explicit column names, or NULL */ + Select *pSelect; /* The definition of this CTE */ + const char *zCteErr; /* Error message for circular references */ + CteUse *pUse; /* Usage information for this CTE */ + u8 eM10d; /* The MATERIALIZED flag */ +}; + +/* +** Allowed values for the materialized flag (eM10d): +*/ +#define M10d_Yes 0 /* AS MATERIALIZED */ +#define M10d_Any 1 /* Not specified. Query planner's choice */ +#define M10d_No 2 /* AS NOT MATERIALIZED */ + +/* +** An instance of the With object represents a WITH clause containing +** one or more CTEs (common table expressions). +*/ +struct With { + int nCte; /* Number of CTEs in the WITH clause */ + int bView; /* Belongs to the outermost Select of a view */ + With *pOuter; /* Containing WITH clause, or NULL */ + Cte a[1]; /* For each CTE in the WITH clause.... */ +}; + +/* +** The Cte object is not guaranteed to persist for the entire duration +** of code generation. (The query flattener or other parser tree +** edits might delete it.) The following object records information +** about each Common Table Expression that must be preserved for the +** duration of the parse. +** +** The CteUse objects are freed using sqlite3ParserAddCleanup() rather +** than sqlite3SelectDelete(), which is what enables them to persist +** until the end of code generation. +*/ +struct CteUse { + int nUse; /* Number of users of this CTE */ + int addrM9e; /* Start of subroutine to compute materialization */ + int regRtn; /* Return address register for addrM9e subroutine */ + int iCur; /* Ephemeral table holding the materialization */ + LogEst nRowEst; /* Estimated number of rows in the table */ + u8 eM10d; /* The MATERIALIZED flag */ +}; + + +/* Client data associated with sqlite3_set_clientdata() and +** sqlite3_get_clientdata(). +*/ +struct DbClientData { + DbClientData *pNext; /* Next in a linked list */ + void *pData; /* The data */ + void (*xDestructor)(void*); /* Destructor. Might be NULL */ + char zName[1]; /* Name of this client data. MUST BE LAST */ +}; + +#ifdef SQLITE_DEBUG +/* +** An instance of the TreeView object is used for printing the content of +** data structures on sqlite3DebugPrintf() using a tree-like view. +*/ +struct TreeView { + int iLevel; /* Which level of the tree we are on */ + u8 bLine[100]; /* Draw vertical in column i if bLine[i] is true */ +}; +#endif /* SQLITE_DEBUG */ + +/* +** This object is used in various ways, most (but not all) related to window +** functions. +** +** (1) A single instance of this structure is attached to the +** the Expr.y.pWin field for each window function in an expression tree. +** This object holds the information contained in the OVER clause, +** plus additional fields used during code generation. +** +** (2) All window functions in a single SELECT form a linked-list +** attached to Select.pWin. The Window.pFunc and Window.pExpr +** fields point back to the expression that is the window function. +** +** (3) The terms of the WINDOW clause of a SELECT are instances of this +** object on a linked list attached to Select.pWinDefn. +** +** (4) For an aggregate function with a FILTER clause, an instance +** of this object is stored in Expr.y.pWin with eFrmType set to +** TK_FILTER. In this case the only field used is Window.pFilter. +** +** The uses (1) and (2) are really the same Window object that just happens +** to be accessible in two different ways. Use case (3) are separate objects. +*/ +struct Window { + char *zName; /* Name of window (may be NULL) */ + char *zBase; /* Name of base window for chaining (may be NULL) */ + ExprList *pPartition; /* PARTITION BY clause */ + ExprList *pOrderBy; /* ORDER BY clause */ + u8 eFrmType; /* TK_RANGE, TK_GROUPS, TK_ROWS, or 0 */ + u8 eStart; /* UNBOUNDED, CURRENT, PRECEDING or FOLLOWING */ + u8 eEnd; /* UNBOUNDED, CURRENT, PRECEDING or FOLLOWING */ + u8 bImplicitFrame; /* True if frame was implicitly specified */ + u8 eExclude; /* TK_NO, TK_CURRENT, TK_TIES, TK_GROUP, or 0 */ + Expr *pStart; /* Expression for "<expr> PRECEDING" */ + Expr *pEnd; /* Expression for "<expr> FOLLOWING" */ + Window **ppThis; /* Pointer to this object in Select.pWin list */ + Window *pNextWin; /* Next window function belonging to this SELECT */ + Expr *pFilter; /* The FILTER expression */ + FuncDef *pWFunc; /* The function */ + int iEphCsr; /* Partition buffer or Peer buffer */ + int regAccum; /* Accumulator */ + int regResult; /* Interim result */ + int csrApp; /* Function cursor (used by min/max) */ + int regApp; /* Function register (also used by min/max) */ + int regPart; /* Array of registers for PARTITION BY values */ + Expr *pOwner; /* Expression object this window is attached to */ + int nBufferCol; /* Number of columns in buffer table */ + int iArgCol; /* Offset of first argument for this function */ + int regOne; /* Register containing constant value 1 */ + int regStartRowid; + int regEndRowid; + u8 bExprArgs; /* Defer evaluation of window function arguments + ** due to the SQLITE_SUBTYPE flag */ +}; + +#ifndef SQLITE_OMIT_WINDOWFUNC +SQLITE_PRIVATE void sqlite3WindowDelete(sqlite3*, Window*); +SQLITE_PRIVATE void sqlite3WindowUnlinkFromSelect(Window*); +SQLITE_PRIVATE void sqlite3WindowListDelete(sqlite3 *db, Window *p); +SQLITE_PRIVATE Window *sqlite3WindowAlloc(Parse*, int, int, Expr*, int , Expr*, u8); +SQLITE_PRIVATE void sqlite3WindowAttach(Parse*, Expr*, Window*); +SQLITE_PRIVATE void sqlite3WindowLink(Select *pSel, Window *pWin); +SQLITE_PRIVATE int sqlite3WindowCompare(const Parse*, const Window*, const Window*, int); +SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse*, Select*); +SQLITE_PRIVATE void sqlite3WindowCodeStep(Parse*, Select*, WhereInfo*, int, int); +SQLITE_PRIVATE int sqlite3WindowRewrite(Parse*, Select*); +SQLITE_PRIVATE void sqlite3WindowUpdate(Parse*, Window*, Window*, FuncDef*); +SQLITE_PRIVATE Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p); +SQLITE_PRIVATE Window *sqlite3WindowListDup(sqlite3 *db, Window *p); +SQLITE_PRIVATE void sqlite3WindowFunctions(void); +SQLITE_PRIVATE void sqlite3WindowChain(Parse*, Window*, Window*); +SQLITE_PRIVATE Window *sqlite3WindowAssemble(Parse*, Window*, ExprList*, ExprList*, Token*); +#else +# define sqlite3WindowDelete(a,b) +# define sqlite3WindowFunctions() +# define sqlite3WindowAttach(a,b,c) +#endif + +/* +** Assuming zIn points to the first byte of a UTF-8 character, +** advance zIn to point to the first byte of the next UTF-8 character. +*/ +#define SQLITE_SKIP_UTF8(zIn) { \ + if( (*(zIn++))>=0xc0 ){ \ + while( (*zIn & 0xc0)==0x80 ){ zIn++; } \ + } \ +} + +/* +** The SQLITE_*_BKPT macros are substitutes for the error codes with +** the same name but without the _BKPT suffix. These macros invoke +** routines that report the line-number on which the error originated +** using sqlite3_log(). The routines also provide a convenient place +** to set a debugger breakpoint. +*/ +SQLITE_PRIVATE int sqlite3ReportError(int iErr, int lineno, const char *zType); +SQLITE_PRIVATE int sqlite3CorruptError(int); +SQLITE_PRIVATE int sqlite3MisuseError(int); +SQLITE_PRIVATE int sqlite3CantopenError(int); +#define SQLITE_CORRUPT_BKPT sqlite3CorruptError(__LINE__) +#define SQLITE_MISUSE_BKPT sqlite3MisuseError(__LINE__) +#define SQLITE_CANTOPEN_BKPT sqlite3CantopenError(__LINE__) +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3NomemError(int); +SQLITE_PRIVATE int sqlite3IoerrnomemError(int); +# define SQLITE_NOMEM_BKPT sqlite3NomemError(__LINE__) +# define SQLITE_IOERR_NOMEM_BKPT sqlite3IoerrnomemError(__LINE__) +#else +# define SQLITE_NOMEM_BKPT SQLITE_NOMEM +# define SQLITE_IOERR_NOMEM_BKPT SQLITE_IOERR_NOMEM +#endif +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_CORRUPT_PGNO) +SQLITE_PRIVATE int sqlite3CorruptPgnoError(int,Pgno); +# define SQLITE_CORRUPT_PGNO(P) sqlite3CorruptPgnoError(__LINE__,(P)) +#else +# define SQLITE_CORRUPT_PGNO(P) sqlite3CorruptError(__LINE__) +#endif + +/* +** FTS3 and FTS4 both require virtual table support +*/ +#if defined(SQLITE_OMIT_VIRTUALTABLE) +# undef SQLITE_ENABLE_FTS3 +# undef SQLITE_ENABLE_FTS4 +#endif + +/* +** FTS4 is really an extension for FTS3. It is enabled using the +** SQLITE_ENABLE_FTS3 macro. But to avoid confusion we also call +** the SQLITE_ENABLE_FTS4 macro to serve as an alias for SQLITE_ENABLE_FTS3. +*/ +#if defined(SQLITE_ENABLE_FTS4) && !defined(SQLITE_ENABLE_FTS3) +# define SQLITE_ENABLE_FTS3 1 +#endif + +/* +** The ctype.h header is needed for non-ASCII systems. It is also +** needed by FTS3 when FTS3 is included in the amalgamation. +*/ +#if !defined(SQLITE_ASCII) || \ + (defined(SQLITE_ENABLE_FTS3) && defined(SQLITE_AMALGAMATION)) +# include <ctype.h> +#endif + +/* +** The following macros mimic the standard library functions toupper(), +** isspace(), isalnum(), isdigit() and isxdigit(), respectively. The +** sqlite versions only work for ASCII characters, regardless of locale. +*/ +#ifdef SQLITE_ASCII +# define sqlite3Toupper(x) ((x)&~(sqlite3CtypeMap[(unsigned char)(x)]&0x20)) +# define sqlite3Isspace(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x01) +# define sqlite3Isalnum(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x06) +# define sqlite3Isalpha(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x02) +# define sqlite3Isdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x04) +# define sqlite3Isxdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x08) +# define sqlite3Tolower(x) (sqlite3UpperToLower[(unsigned char)(x)]) +# define sqlite3Isquote(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x80) +# define sqlite3JsonId1(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x42) +# define sqlite3JsonId2(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x46) +#else +# define sqlite3Toupper(x) toupper((unsigned char)(x)) +# define sqlite3Isspace(x) isspace((unsigned char)(x)) +# define sqlite3Isalnum(x) isalnum((unsigned char)(x)) +# define sqlite3Isalpha(x) isalpha((unsigned char)(x)) +# define sqlite3Isdigit(x) isdigit((unsigned char)(x)) +# define sqlite3Isxdigit(x) isxdigit((unsigned char)(x)) +# define sqlite3Tolower(x) tolower((unsigned char)(x)) +# define sqlite3Isquote(x) ((x)=='"'||(x)=='\''||(x)=='['||(x)=='`') +# define sqlite3JsonId1(x) (sqlite3IsIdChar(x)&&(x)<'0') +# define sqlite3JsonId2(x) sqlite3IsIdChar(x) +#endif +SQLITE_PRIVATE int sqlite3IsIdChar(u8); + +/* +** Internal function prototypes +*/ +SQLITE_PRIVATE int sqlite3StrICmp(const char*,const char*); +SQLITE_PRIVATE int sqlite3Strlen30(const char*); +#define sqlite3Strlen30NN(C) (strlen(C)&0x3fffffff) +SQLITE_PRIVATE char *sqlite3ColumnType(Column*,char*); +#define sqlite3StrNICmp sqlite3_strnicmp + +SQLITE_PRIVATE int sqlite3MallocInit(void); +SQLITE_PRIVATE void sqlite3MallocEnd(void); +SQLITE_PRIVATE void *sqlite3Malloc(u64); +SQLITE_PRIVATE void *sqlite3MallocZero(u64); +SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3*, u64); +SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3*, u64); +SQLITE_PRIVATE void *sqlite3DbMallocRawNN(sqlite3*, u64); +SQLITE_PRIVATE char *sqlite3DbStrDup(sqlite3*,const char*); +SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3*,const char*, u64); +SQLITE_PRIVATE char *sqlite3DbSpanDup(sqlite3*,const char*,const char*); +SQLITE_PRIVATE void *sqlite3Realloc(void*, u64); +SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64); +SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, u64); +SQLITE_PRIVATE void sqlite3DbFree(sqlite3*, void*); +SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3*, void*); +SQLITE_PRIVATE void sqlite3DbNNFreeNN(sqlite3*, void*); +SQLITE_PRIVATE int sqlite3MallocSize(const void*); +SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, const void*); +SQLITE_PRIVATE void *sqlite3PageMalloc(int); +SQLITE_PRIVATE void sqlite3PageFree(void*); +SQLITE_PRIVATE void sqlite3MemSetDefault(void); +#ifndef SQLITE_UNTESTABLE +SQLITE_PRIVATE void sqlite3BenignMallocHooks(void (*)(void), void (*)(void)); +#endif +SQLITE_PRIVATE int sqlite3HeapNearlyFull(void); + +/* +** On systems with ample stack space and that support alloca(), make +** use of alloca() to obtain space for large automatic objects. By default, +** obtain space from malloc(). +** +** The alloca() routine never returns NULL. This will cause code paths +** that deal with sqlite3StackAlloc() failures to be unreachable. +*/ +#ifdef SQLITE_USE_ALLOCA +# define sqlite3StackAllocRaw(D,N) alloca(N) +# define sqlite3StackAllocRawNN(D,N) alloca(N) +# define sqlite3StackFree(D,P) +# define sqlite3StackFreeNN(D,P) +#else +# define sqlite3StackAllocRaw(D,N) sqlite3DbMallocRaw(D,N) +# define sqlite3StackAllocRawNN(D,N) sqlite3DbMallocRawNN(D,N) +# define sqlite3StackFree(D,P) sqlite3DbFree(D,P) +# define sqlite3StackFreeNN(D,P) sqlite3DbFreeNN(D,P) +#endif + +/* Do not allow both MEMSYS5 and MEMSYS3 to be defined together. If they +** are, disable MEMSYS3 +*/ +#ifdef SQLITE_ENABLE_MEMSYS5 +SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys5(void); +#undef SQLITE_ENABLE_MEMSYS3 +#endif +#ifdef SQLITE_ENABLE_MEMSYS3 +SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys3(void); +#endif + + +#ifndef SQLITE_MUTEX_OMIT +SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void); +SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3NoopMutex(void); +SQLITE_PRIVATE sqlite3_mutex *sqlite3MutexAlloc(int); +SQLITE_PRIVATE int sqlite3MutexInit(void); +SQLITE_PRIVATE int sqlite3MutexEnd(void); +#endif +#if !defined(SQLITE_MUTEX_OMIT) && !defined(SQLITE_MUTEX_NOOP) +SQLITE_PRIVATE void sqlite3MemoryBarrier(void); +#else +# define sqlite3MemoryBarrier() +#endif + +SQLITE_PRIVATE sqlite3_int64 sqlite3StatusValue(int); +SQLITE_PRIVATE void sqlite3StatusUp(int, int); +SQLITE_PRIVATE void sqlite3StatusDown(int, int); +SQLITE_PRIVATE void sqlite3StatusHighwater(int, int); +SQLITE_PRIVATE int sqlite3LookasideUsed(sqlite3*,int*); + +/* Access to mutexes used by sqlite3_status() */ +SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void); +SQLITE_PRIVATE sqlite3_mutex *sqlite3MallocMutex(void); + +#if defined(SQLITE_ENABLE_MULTITHREADED_CHECKS) && !defined(SQLITE_MUTEX_OMIT) +SQLITE_PRIVATE void sqlite3MutexWarnOnContention(sqlite3_mutex*); +#else +# define sqlite3MutexWarnOnContention(x) +#endif + +#ifndef SQLITE_OMIT_FLOATING_POINT +# define EXP754 (((u64)0x7ff)<<52) +# define MAN754 ((((u64)1)<<52)-1) +# define IsNaN(X) (((X)&EXP754)==EXP754 && ((X)&MAN754)!=0) +SQLITE_PRIVATE int sqlite3IsNaN(double); +#else +# define IsNaN(X) 0 +# define sqlite3IsNaN(X) 0 +#endif + +/* +** An instance of the following structure holds information about SQL +** functions arguments that are the parameters to the printf() function. +*/ +struct PrintfArguments { + int nArg; /* Total number of arguments */ + int nUsed; /* Number of arguments used so far */ + sqlite3_value **apArg; /* The argument values */ +}; + +/* +** An instance of this object receives the decoding of a floating point +** value into an approximate decimal representation. +*/ +struct FpDecode { + char sign; /* '+' or '-' */ + char isSpecial; /* 1: Infinity 2: NaN */ + int n; /* Significant digits in the decode */ + int iDP; /* Location of the decimal point */ + char *z; /* Start of significant digits */ + char zBuf[24]; /* Storage for significant digits */ +}; + +SQLITE_PRIVATE void sqlite3FpDecode(FpDecode*,double,int,int); +SQLITE_PRIVATE char *sqlite3MPrintf(sqlite3*,const char*, ...); +SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3*,const char*, va_list); +#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) +SQLITE_PRIVATE void sqlite3DebugPrintf(const char*, ...); +#endif +#if defined(SQLITE_TEST) +SQLITE_PRIVATE void *sqlite3TestTextToPtr(const char*); +#endif + +#if defined(SQLITE_DEBUG) +SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView*, const char *zFormat, ...); +SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView*, const Expr*, u8); +SQLITE_PRIVATE void sqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*); +SQLITE_PRIVATE void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*); +SQLITE_PRIVATE void sqlite3TreeViewBareIdList(TreeView*, const IdList*, const char*); +SQLITE_PRIVATE void sqlite3TreeViewIdList(TreeView*, const IdList*, u8, const char*); +SQLITE_PRIVATE void sqlite3TreeViewColumnList(TreeView*, const Column*, int, u8); +SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView*, const SrcList*); +SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView*, const Select*, u8); +SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView*, const With*, u8); +SQLITE_PRIVATE void sqlite3TreeViewUpsert(TreeView*, const Upsert*, u8); +#if TREETRACE_ENABLED +SQLITE_PRIVATE void sqlite3TreeViewDelete(const With*, const SrcList*, const Expr*, + const ExprList*,const Expr*, const Trigger*); +SQLITE_PRIVATE void sqlite3TreeViewInsert(const With*, const SrcList*, + const IdList*, const Select*, const ExprList*, + int, const Upsert*, const Trigger*); +SQLITE_PRIVATE void sqlite3TreeViewUpdate(const With*, const SrcList*, const ExprList*, + const Expr*, int, const ExprList*, const Expr*, + const Upsert*, const Trigger*); +#endif +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE void sqlite3TreeViewTriggerStep(TreeView*, const TriggerStep*, u8, u8); +SQLITE_PRIVATE void sqlite3TreeViewTrigger(TreeView*, const Trigger*, u8, u8); +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC +SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView*, const Window*, u8); +SQLITE_PRIVATE void sqlite3TreeViewWinFunc(TreeView*, const Window*, u8); +#endif +SQLITE_PRIVATE void sqlite3ShowExpr(const Expr*); +SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList*); +SQLITE_PRIVATE void sqlite3ShowIdList(const IdList*); +SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList*); +SQLITE_PRIVATE void sqlite3ShowSelect(const Select*); +SQLITE_PRIVATE void sqlite3ShowWith(const With*); +SQLITE_PRIVATE void sqlite3ShowUpsert(const Upsert*); +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE void sqlite3ShowTriggerStep(const TriggerStep*); +SQLITE_PRIVATE void sqlite3ShowTriggerStepList(const TriggerStep*); +SQLITE_PRIVATE void sqlite3ShowTrigger(const Trigger*); +SQLITE_PRIVATE void sqlite3ShowTriggerList(const Trigger*); +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC +SQLITE_PRIVATE void sqlite3ShowWindow(const Window*); +SQLITE_PRIVATE void sqlite3ShowWinFunc(const Window*); +#endif +#endif + +SQLITE_PRIVATE void sqlite3SetString(char **, sqlite3*, const char*); +SQLITE_PRIVATE void sqlite3ProgressCheck(Parse*); +SQLITE_PRIVATE void sqlite3ErrorMsg(Parse*, const char*, ...); +SQLITE_PRIVATE int sqlite3ErrorToParser(sqlite3*,int); +SQLITE_PRIVATE void sqlite3Dequote(char*); +SQLITE_PRIVATE void sqlite3DequoteExpr(Expr*); +SQLITE_PRIVATE void sqlite3DequoteToken(Token*); +SQLITE_PRIVATE void sqlite3TokenInit(Token*,char*); +SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char*, int); +SQLITE_PRIVATE int sqlite3RunParser(Parse*, const char*); +SQLITE_PRIVATE void sqlite3FinishCoding(Parse*); +SQLITE_PRIVATE int sqlite3GetTempReg(Parse*); +SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse*,int); +SQLITE_PRIVATE int sqlite3GetTempRange(Parse*,int); +SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse*,int,int); +SQLITE_PRIVATE void sqlite3ClearTempRegCache(Parse*); +SQLITE_PRIVATE void sqlite3TouchRegister(Parse*,int); +#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_DEBUG) +SQLITE_PRIVATE int sqlite3FirstAvailableRegister(Parse*,int); +#endif +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse*,int,int); +#endif +SQLITE_PRIVATE Expr *sqlite3ExprAlloc(sqlite3*,int,const Token*,int); +SQLITE_PRIVATE Expr *sqlite3Expr(sqlite3*,int,const char*); +SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*); +SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*); +SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse*, Expr*, Select*); +SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse*,Expr*, Expr*); +SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr*); +SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, const Token*, int); +SQLITE_PRIVATE void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*); +SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); +SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3*, Expr*); +SQLITE_PRIVATE void sqlite3ExprDeferredDelete(Parse*, Expr*); +SQLITE_PRIVATE void sqlite3ExprUnmapAndDelete(Parse*, Expr*); +SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); +SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*); +SQLITE_PRIVATE Select *sqlite3ExprListToValues(Parse*, int, ExprList*); +SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList*,int,int); +SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,const Token*,int); +SQLITE_PRIVATE void sqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*); +SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3*, ExprList*); +SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList*); +SQLITE_PRIVATE int sqlite3IndexHasDuplicateRootPage(Index*); +SQLITE_PRIVATE int sqlite3Init(sqlite3*, char**); +SQLITE_PRIVATE int sqlite3InitCallback(void*, int, char**, char**); +SQLITE_PRIVATE int sqlite3InitOne(sqlite3*, int, char**, u32); +SQLITE_PRIVATE void sqlite3Pragma(Parse*,Token*,Token*,Token*,int); +#ifndef SQLITE_OMIT_VIRTUALTABLE +SQLITE_PRIVATE Module *sqlite3PragmaVtabRegister(sqlite3*,const char *zName); +#endif +SQLITE_PRIVATE void sqlite3ResetAllSchemasOfConnection(sqlite3*); +SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3*,int); +SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3*); +SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3*); +SQLITE_PRIVATE void sqlite3ColumnSetExpr(Parse*,Table*,Column*,Expr*); +SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table*,Column*); +SQLITE_PRIVATE void sqlite3ColumnSetColl(sqlite3*,Column*,const char*zColl); +SQLITE_PRIVATE const char *sqlite3ColumnColl(Column*); +SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3*,Table*); +SQLITE_PRIVATE void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect); +SQLITE_PRIVATE int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**); +SQLITE_PRIVATE void sqlite3SubqueryColumnTypes(Parse*,Table*,Select*,char); +SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse*,Select*,char); +SQLITE_PRIVATE void sqlite3OpenSchemaTable(Parse *, int); +SQLITE_PRIVATE Index *sqlite3PrimaryKeyIndex(Table*); +SQLITE_PRIVATE i16 sqlite3TableColumnToIndex(Index*, i16); +#ifdef SQLITE_OMIT_GENERATED_COLUMNS +# define sqlite3TableColumnToStorage(T,X) (X) /* No-op pass-through */ +# define sqlite3StorageColumnToTable(T,X) (X) /* No-op pass-through */ +#else +SQLITE_PRIVATE i16 sqlite3TableColumnToStorage(Table*, i16); +SQLITE_PRIVATE i16 sqlite3StorageColumnToTable(Table*, i16); +#endif +SQLITE_PRIVATE void sqlite3StartTable(Parse*,Token*,Token*,int,int,int,int); +#if SQLITE_ENABLE_HIDDEN_COLUMNS +SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table*, Column*); +#else +# define sqlite3ColumnPropertiesFromName(T,C) /* no-op */ +#endif +SQLITE_PRIVATE void sqlite3AddColumn(Parse*,Token,Token); +SQLITE_PRIVATE void sqlite3AddNotNull(Parse*, int); +SQLITE_PRIVATE void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int); +SQLITE_PRIVATE void sqlite3AddCheckConstraint(Parse*, Expr*, const char*, const char*); +SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse*,Expr*,const char*,const char*); +SQLITE_PRIVATE void sqlite3AddCollateType(Parse*, Token*); +SQLITE_PRIVATE void sqlite3AddGenerated(Parse*,Expr*,Token*); +SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,u32,Select*); +SQLITE_PRIVATE void sqlite3AddReturning(Parse*,ExprList*); +SQLITE_PRIVATE int sqlite3ParseUri(const char*,const char*,unsigned int*, + sqlite3_vfs**,char**,char **); +#define sqlite3CodecQueryParameters(A,B,C) 0 +SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3*,const char*); + +#ifdef SQLITE_UNTESTABLE +# define sqlite3FaultSim(X) SQLITE_OK +#else +SQLITE_PRIVATE int sqlite3FaultSim(int); +#endif + +SQLITE_PRIVATE Bitvec *sqlite3BitvecCreate(u32); +SQLITE_PRIVATE int sqlite3BitvecTest(Bitvec*, u32); +SQLITE_PRIVATE int sqlite3BitvecTestNotNull(Bitvec*, u32); +SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec*, u32); +SQLITE_PRIVATE void sqlite3BitvecClear(Bitvec*, u32, void*); +SQLITE_PRIVATE void sqlite3BitvecDestroy(Bitvec*); +SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec*); +#ifndef SQLITE_UNTESTABLE +SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int,int*); +#endif + +SQLITE_PRIVATE RowSet *sqlite3RowSetInit(sqlite3*); +SQLITE_PRIVATE void sqlite3RowSetDelete(void*); +SQLITE_PRIVATE void sqlite3RowSetClear(void*); +SQLITE_PRIVATE void sqlite3RowSetInsert(RowSet*, i64); +SQLITE_PRIVATE int sqlite3RowSetTest(RowSet*, int iBatch, i64); +SQLITE_PRIVATE int sqlite3RowSetNext(RowSet*, i64*); + +SQLITE_PRIVATE void sqlite3CreateView(Parse*,Token*,Token*,Token*,ExprList*,Select*,int,int); + +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) +SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse*,Table*); +#else +# define sqlite3ViewGetColumnNames(A,B) 0 +#endif + +#if SQLITE_MAX_ATTACHED>30 +SQLITE_PRIVATE int sqlite3DbMaskAllZero(yDbMask); +#endif +SQLITE_PRIVATE void sqlite3DropTable(Parse*, SrcList*, int, int); +SQLITE_PRIVATE void sqlite3CodeDropTable(Parse*, Table*, int, int); +SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3*, Table*); +SQLITE_PRIVATE void sqlite3FreeIndex(sqlite3*, Index*); +#ifndef SQLITE_OMIT_AUTOINCREMENT +SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse); +SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse); +#else +# define sqlite3AutoincrementBegin(X) +# define sqlite3AutoincrementEnd(X) +#endif +SQLITE_PRIVATE void sqlite3Insert(Parse*, SrcList*, Select*, IdList*, int, Upsert*); +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +SQLITE_PRIVATE void sqlite3ComputeGeneratedColumns(Parse*, int, Table*); +#endif +SQLITE_PRIVATE void *sqlite3ArrayAllocate(sqlite3*,void*,int,int*,int*); +SQLITE_PRIVATE IdList *sqlite3IdListAppend(Parse*, IdList*, Token*); +SQLITE_PRIVATE int sqlite3IdListIndex(IdList*,const char*); +SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge(Parse*, SrcList*, int, int); +SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2); +SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(Parse*, SrcList*, Token*, Token*); +SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*, + Token*, Select*, OnOrUsing*); +SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *, SrcList *, Token *); +SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse*, SrcList*, ExprList*); +SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *, SrcItem *); +SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(Parse*,SrcList*); +SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse*, SrcList*); +SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3*, IdList*); +SQLITE_PRIVATE void sqlite3ClearOnOrUsing(sqlite3*, OnOrUsing*); +SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3*, SrcList*); +SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(sqlite3*,i16,int,char**); +SQLITE_PRIVATE void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*, + Expr*, int, int, u8); +SQLITE_PRIVATE void sqlite3DropIndex(Parse*, SrcList*, int); +SQLITE_PRIVATE int sqlite3Select(Parse*, Select*, SelectDest*); +SQLITE_PRIVATE Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*, + Expr*,ExprList*,u32,Expr*); +SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3*, Select*); +SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse*, SrcList*); +SQLITE_PRIVATE int sqlite3IsReadOnly(Parse*, Table*, Trigger*); +SQLITE_PRIVATE void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int); +#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) +SQLITE_PRIVATE Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*); +#endif +SQLITE_PRIVATE void sqlite3CodeChangeCount(Vdbe*,int,const char*); +SQLITE_PRIVATE void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*); +SQLITE_PRIVATE void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*, + Upsert*); +SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*, + ExprList*,Select*,u16,int); +SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo*); +SQLITE_PRIVATE LogEst sqlite3WhereOutputRowCount(WhereInfo*); +SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo*); +SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo*); +SQLITE_PRIVATE int sqlite3WhereOrderByLimitOptLabel(WhereInfo*); +SQLITE_PRIVATE void sqlite3WhereMinMaxOptEarlyOut(Vdbe*,WhereInfo*); +SQLITE_PRIVATE int sqlite3WhereIsSorted(WhereInfo*); +SQLITE_PRIVATE int sqlite3WhereContinueLabel(WhereInfo*); +SQLITE_PRIVATE int sqlite3WhereBreakLabel(WhereInfo*); +SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo*, int*); +#define ONEPASS_OFF 0 /* Use of ONEPASS not allowed */ +#define ONEPASS_SINGLE 1 /* ONEPASS valid for a single row update */ +#define ONEPASS_MULTI 2 /* ONEPASS is valid for multiple rows */ +SQLITE_PRIVATE int sqlite3WhereUsesDeferredSeek(WhereInfo*); +SQLITE_PRIVATE void sqlite3ExprCodeLoadIndexColumn(Parse*, Index*, int, int, int); +SQLITE_PRIVATE int sqlite3ExprCodeGetColumn(Parse*, Table*, int, int, int, u8); +SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable(Vdbe*, Table*, int, int, int); +SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse*, int, int, int); +SQLITE_PRIVATE void sqlite3ExprCode(Parse*, Expr*, int); +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Table*, Column*, int); +#endif +SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int); +SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int); +SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int); +SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*); +SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int); +SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8); +#define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */ +#define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */ +#define SQLITE_ECEL_REF 0x04 /* Use ExprList.u.x.iOrderByCol */ +#define SQLITE_ECEL_OMITREF 0x08 /* Omit if ExprList.u.x.iOrderByCol */ +SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse*, Expr*, int, int); +SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse*, Expr*, int, int); +SQLITE_PRIVATE void sqlite3ExprIfFalseDup(Parse*, Expr*, int, int); +SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3*,const char*, const char*); +#define LOCATE_VIEW 0x01 +#define LOCATE_NOERR 0x02 +SQLITE_PRIVATE Table *sqlite3LocateTable(Parse*,u32 flags,const char*, const char*); +SQLITE_PRIVATE const char *sqlite3PreferredTableName(const char*); +SQLITE_PRIVATE Table *sqlite3LocateTableItem(Parse*,u32 flags,SrcItem *); +SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3*,const char*, const char*); +SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*); +SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*); +SQLITE_PRIVATE void sqlite3Vacuum(Parse*,Token*,Expr*); +SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*, int, sqlite3_value*); +SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, const Token*); +SQLITE_PRIVATE int sqlite3ExprCompare(const Parse*,const Expr*,const Expr*, int); +SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*,Expr*,int); +SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList*,const ExprList*, int); +SQLITE_PRIVATE int sqlite3ExprImpliesExpr(const Parse*,const Expr*,const Expr*, int); +SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr*,int,int); +SQLITE_PRIVATE void sqlite3AggInfoPersistWalkerInit(Walker*,Parse*); +SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*); +SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*); +SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx); +SQLITE_PRIVATE int sqlite3ReferencesSrcList(Parse*, Expr*, SrcList*); +SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse*); +#ifndef SQLITE_UNTESTABLE +SQLITE_PRIVATE void sqlite3PrngSaveState(void); +SQLITE_PRIVATE void sqlite3PrngRestoreState(void); +#endif +SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3*,int); +SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse*, int); +SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse*, const char *zDb); +SQLITE_PRIVATE void sqlite3BeginTransaction(Parse*, int); +SQLITE_PRIVATE void sqlite3EndTransaction(Parse*,int); +SQLITE_PRIVATE void sqlite3Savepoint(Parse*, int, Token*); +SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *); +SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3*); +SQLITE_PRIVATE u32 sqlite3IsTrueOrFalse(const char*); +SQLITE_PRIVATE int sqlite3ExprIdToTrueFalse(Expr*); +SQLITE_PRIVATE int sqlite3ExprTruthValue(const Expr*); +SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr*); +SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*); +SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*, u8); +SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*); +SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int); +SQLITE_PRIVATE int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int); +#ifdef SQLITE_ENABLE_CURSOR_HINTS +SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*); +#endif +SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr*, int*); +SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*); +SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); +SQLITE_PRIVATE int sqlite3IsRowid(const char*); +SQLITE_PRIVATE void sqlite3GenerateRowDelete( + Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int); +SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int); +SQLITE_PRIVATE int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*,Index*,int); +SQLITE_PRIVATE void sqlite3ResolvePartIdxLabel(Parse*,int); +SQLITE_PRIVATE int sqlite3ExprReferencesUpdatedColumn(Expr*,int*,int); +SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int, + u8,u8,int,int*,int*,Upsert*); +#ifdef SQLITE_ENABLE_NULL_TRIM +SQLITE_PRIVATE void sqlite3SetMakeRecordP5(Vdbe*,Table*); +#else +# define sqlite3SetMakeRecordP5(A,B) +#endif +SQLITE_PRIVATE void sqlite3CompleteInsertion(Parse*,Table*,int,int,int,int*,int,int,int); +SQLITE_PRIVATE int sqlite3OpenTableAndIndices(Parse*, Table*, int, u8, int, u8*, int*, int*); +SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse*, int, int); +SQLITE_PRIVATE void sqlite3MultiWrite(Parse*); +SQLITE_PRIVATE void sqlite3MayAbort(Parse*); +SQLITE_PRIVATE void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8); +SQLITE_PRIVATE void sqlite3UniqueConstraint(Parse*, int, Index*); +SQLITE_PRIVATE void sqlite3RowidConstraint(Parse*, int, Table*); +SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,const Expr*,int); +SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,const ExprList*,int); +SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,const SrcList*,int); +SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,const IdList*); +SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,const Select*,int); +SQLITE_PRIVATE FuncDef *sqlite3FunctionSearch(int,const char*); +SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs(FuncDef*,int); +SQLITE_PRIVATE FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,u8,u8); +SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum*,sqlite3_value*); +SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void); +SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void); +SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void); +SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*); +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) +SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3*); +#endif +SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*); +SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*); +SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int); +SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p); + +#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) +SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); +#endif + +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE void sqlite3BeginTrigger(Parse*, Token*,Token*,int,int,IdList*,SrcList*, + Expr*,int, int); +SQLITE_PRIVATE void sqlite3FinishTrigger(Parse*, TriggerStep*, Token*); +SQLITE_PRIVATE void sqlite3DropTrigger(Parse*, SrcList*, int); +SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse*, Trigger*); +SQLITE_PRIVATE Trigger *sqlite3TriggersExist(Parse *, Table*, int, ExprList*, int *pMask); +SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *, Table *); +SQLITE_PRIVATE void sqlite3CodeRowTrigger(Parse*, Trigger *, int, ExprList*, int, Table *, + int, int, int); +SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, int, int, int); + void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*); +SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*); +SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*, + const char*,const char*); +SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(Parse*,Token*, IdList*, + Select*,u8,Upsert*, + const char*,const char*); +SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(Parse*,Token*,SrcList*,ExprList*, + Expr*, u8, const char*,const char*); +SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(Parse*,Token*, Expr*, + const char*,const char*); +SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3*, Trigger*); +SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); +SQLITE_PRIVATE u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int); +SQLITE_PRIVATE SrcList *sqlite3TriggerStepSrc(Parse*, TriggerStep*); +# define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p)) +# define sqlite3IsToplevel(p) ((p)->pToplevel==0) +#else +# define sqlite3TriggersExist(B,C,D,E,F) 0 +# define sqlite3DeleteTrigger(A,B) +# define sqlite3DropTriggerPtr(A,B) +# define sqlite3UnlinkAndDeleteTrigger(A,B,C) +# define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I) +# define sqlite3CodeRowTriggerDirect(A,B,C,D,E,F) +# define sqlite3TriggerList(X, Y) 0 +# define sqlite3ParseToplevel(p) p +# define sqlite3IsToplevel(p) 1 +# define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0 +# define sqlite3TriggerStepSrc(A,B) 0 +#endif + +SQLITE_PRIVATE int sqlite3JoinType(Parse*, Token*, Token*, Token*); +SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol); +SQLITE_PRIVATE void sqlite3SrcItemColumnUsed(SrcItem*,int); +SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr*,int,u32); +SQLITE_PRIVATE void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int); +SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse*, int); +#ifndef SQLITE_OMIT_AUTHORIZATION +SQLITE_PRIVATE void sqlite3AuthRead(Parse*,Expr*,Schema*,SrcList*); +SQLITE_PRIVATE int sqlite3AuthCheck(Parse*,int, const char*, const char*, const char*); +SQLITE_PRIVATE void sqlite3AuthContextPush(Parse*, AuthContext*, const char*); +SQLITE_PRIVATE void sqlite3AuthContextPop(AuthContext*); +SQLITE_PRIVATE int sqlite3AuthReadCol(Parse*, const char *, const char *, int); +#else +# define sqlite3AuthRead(a,b,c,d) +# define sqlite3AuthCheck(a,b,c,d,e) SQLITE_OK +# define sqlite3AuthContextPush(a,b,c) +# define sqlite3AuthContextPop(a) ((void)(a)) +#endif +SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName); +SQLITE_PRIVATE void sqlite3Attach(Parse*, Expr*, Expr*, Expr*); +SQLITE_PRIVATE void sqlite3Detach(Parse*, Expr*); +SQLITE_PRIVATE void sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*); +SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*); +SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*); +SQLITE_PRIVATE int sqlite3FixExpr(DbFixer*, Expr*); +SQLITE_PRIVATE int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); + +SQLITE_PRIVATE int sqlite3RealSameAsInt(double,sqlite3_int64); +SQLITE_PRIVATE i64 sqlite3RealToI64(double); +SQLITE_PRIVATE int sqlite3Int64ToText(i64,char*); +SQLITE_PRIVATE int sqlite3AtoF(const char *z, double*, int, u8); +SQLITE_PRIVATE int sqlite3GetInt32(const char *, int*); +SQLITE_PRIVATE int sqlite3GetUInt32(const char*, u32*); +SQLITE_PRIVATE int sqlite3Atoi(const char*); +#ifndef SQLITE_OMIT_UTF16 +SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *pData, int nChar); +#endif +SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *pData, int nByte); +SQLITE_PRIVATE u32 sqlite3Utf8Read(const u8**); +SQLITE_PRIVATE LogEst sqlite3LogEst(u64); +SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst,LogEst); +SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double); +SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst); +SQLITE_PRIVATE VList *sqlite3VListAdd(sqlite3*,VList*,const char*,int,int); +SQLITE_PRIVATE const char *sqlite3VListNumToName(VList*,int); +SQLITE_PRIVATE int sqlite3VListNameToNum(VList*,const char*,int); + +/* +** Routines to read and write variable-length integers. These used to +** be defined locally, but now we use the varint routines in the util.c +** file. +*/ +SQLITE_PRIVATE int sqlite3PutVarint(unsigned char*, u64); +SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *, u64 *); +SQLITE_PRIVATE u8 sqlite3GetVarint32(const unsigned char *, u32 *); +SQLITE_PRIVATE int sqlite3VarintLen(u64 v); + +/* +** The common case is for a varint to be a single byte. They following +** macros handle the common case without a procedure call, but then call +** the procedure for larger varints. +*/ +#define getVarint32(A,B) \ + (u8)((*(A)<(u8)0x80)?((B)=(u32)*(A)),1:sqlite3GetVarint32((A),(u32 *)&(B))) +#define getVarint32NR(A,B) \ + B=(u32)*(A);if(B>=0x80)sqlite3GetVarint32((A),(u32*)&(B)) +#define putVarint32(A,B) \ + (u8)(((u32)(B)<(u32)0x80)?(*(A)=(unsigned char)(B)),1:\ + sqlite3PutVarint((A),(B))) +#define getVarint sqlite3GetVarint +#define putVarint sqlite3PutVarint + + +SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*); +SQLITE_PRIVATE char *sqlite3TableAffinityStr(sqlite3*,const Table*); +SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int); +SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2); +SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity); +SQLITE_PRIVATE char sqlite3TableColumnAffinity(const Table*,int); +SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr); +SQLITE_PRIVATE int sqlite3ExprDataType(const Expr *pExpr); +SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8); +SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*); +SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...); +SQLITE_PRIVATE void sqlite3Error(sqlite3*,int); +SQLITE_PRIVATE void sqlite3ErrorClear(sqlite3*); +SQLITE_PRIVATE void sqlite3SystemError(sqlite3*,int); +SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3*, const char *z, int n); +SQLITE_PRIVATE u8 sqlite3HexToInt(int h); +SQLITE_PRIVATE int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); + +#if defined(SQLITE_NEED_ERR_NAME) +SQLITE_PRIVATE const char *sqlite3ErrName(int); +#endif + +#ifndef SQLITE_OMIT_DESERIALIZE +SQLITE_PRIVATE int sqlite3MemdbInit(void); +SQLITE_PRIVATE int sqlite3IsMemdb(const sqlite3_vfs*); +#else +# define sqlite3IsMemdb(X) 0 +#endif + +SQLITE_PRIVATE const char *sqlite3ErrStr(int); +SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse); +SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int); +SQLITE_PRIVATE int sqlite3IsBinary(const CollSeq*); +SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName); +SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8); +SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr); +SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr); +SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,const Expr*,const Expr*); +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(const Parse *pParse, Expr*, const Token*, int); +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(const Parse*,Expr*,const char*); +SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*); +SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr*); +SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *); +SQLITE_PRIVATE int sqlite3WritableSchema(sqlite3*); +SQLITE_PRIVATE int sqlite3CheckObjectName(Parse*, const char*,const char*,const char*); +SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *, i64); +SQLITE_PRIVATE int sqlite3AddInt64(i64*,i64); +SQLITE_PRIVATE int sqlite3SubInt64(i64*,i64); +SQLITE_PRIVATE int sqlite3MulInt64(i64*,i64); +SQLITE_PRIVATE int sqlite3AbsInt32(int); +#ifdef SQLITE_ENABLE_8_3_NAMES +SQLITE_PRIVATE void sqlite3FileSuffix3(const char*, char*); +#else +# define sqlite3FileSuffix3(X,Y) +#endif +SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z,u8); + +SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value*, u8); +SQLITE_PRIVATE int sqlite3ValueIsOfClass(const sqlite3_value*, void(*)(void*)); +SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value*, u8); +SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, + void(*)(void*)); +SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value*); +SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value*); +#ifndef SQLITE_UNTESTABLE +SQLITE_PRIVATE void sqlite3ResultIntReal(sqlite3_context*); +#endif +SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *); +#ifndef SQLITE_OMIT_UTF16 +SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8); +#endif +SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, const Expr *, u8, u8, sqlite3_value **); +SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8); +#ifndef SQLITE_AMALGAMATION +SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[]; +SQLITE_PRIVATE const char sqlite3StrBINARY[]; +SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[]; +SQLITE_PRIVATE const char sqlite3StdTypeAffinity[]; +SQLITE_PRIVATE const char *sqlite3StdType[]; +SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[]; +SQLITE_PRIVATE const unsigned char *sqlite3aLTb; +SQLITE_PRIVATE const unsigned char *sqlite3aEQb; +SQLITE_PRIVATE const unsigned char *sqlite3aGTb; +SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[]; +SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config; +SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions; +#ifndef SQLITE_OMIT_WSD +SQLITE_PRIVATE int sqlite3PendingByte; +#endif +#endif /* SQLITE_AMALGAMATION */ +#ifdef VDBE_PROFILE +SQLITE_PRIVATE sqlite3_uint64 sqlite3NProfileCnt; +#endif +SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3*, int, Pgno, Pgno); +SQLITE_PRIVATE void sqlite3Reindex(Parse*, Token*, Token*); +SQLITE_PRIVATE void sqlite3AlterFunctions(void); +SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*); +SQLITE_PRIVATE void sqlite3AlterRenameColumn(Parse*, SrcList*, Token*, Token*); +SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *, int *); +SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...); +SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*, int); +SQLITE_PRIVATE void sqlite3CodeRhsOfIN(Parse*, Expr*, int); +SQLITE_PRIVATE int sqlite3CodeSubselect(Parse*, Expr*); +SQLITE_PRIVATE void sqlite3SelectPrep(Parse*, Select*, NameContext*); +SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse*, SrcItem*); +SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p); +SQLITE_PRIVATE int sqlite3MatchEName( + const struct ExprList_item*, + const char*, + const char*, + const char* +); +SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr*); +SQLITE_PRIVATE u8 sqlite3StrIHash(const char*); +SQLITE_PRIVATE int sqlite3ResolveExprNames(NameContext*, Expr*); +SQLITE_PRIVATE int sqlite3ResolveExprListNames(NameContext*, ExprList*); +SQLITE_PRIVATE void sqlite3ResolveSelectNames(Parse*, Select*, NameContext*); +SQLITE_PRIVATE int sqlite3ResolveSelfReference(Parse*,Table*,int,Expr*,ExprList*); +SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*); +SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *, Table *, int, int); +SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *, Token *); +SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *, SrcList *); +SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse*, SrcList*, const Token*); +SQLITE_PRIVATE const void *sqlite3RenameTokenMap(Parse*, const void*, const Token*); +SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse*, const void *pTo, const void *pFrom); +SQLITE_PRIVATE void sqlite3RenameExprUnmap(Parse*, Expr*); +SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse*, ExprList*); +SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*); +SQLITE_PRIVATE char sqlite3AffinityType(const char*, Column*); +SQLITE_PRIVATE void sqlite3Analyze(Parse*, Token*, Token*); +SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler*); +SQLITE_PRIVATE int sqlite3FindDb(sqlite3*, Token*); +SQLITE_PRIVATE int sqlite3FindDbName(sqlite3 *, const char *); +SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3*,int iDB); +SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3*,Index*); +SQLITE_PRIVATE void sqlite3DefaultRowEst(Index*); +SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3*, int); +SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*); +SQLITE_PRIVATE void sqlite3SchemaClear(void *); +SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *, Btree *); +SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *); +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3*,int,int); +SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo*); +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoRef(KeyInfo*); +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse*, Index*); +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoFromExprList(Parse*, ExprList*, int, int); +SQLITE_PRIVATE const char *sqlite3SelectOpName(int); +SQLITE_PRIVATE int sqlite3HasExplicitNulls(Parse*, ExprList*); + +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3KeyInfoIsWriteable(KeyInfo*); +#endif +SQLITE_PRIVATE int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, + void (*)(sqlite3_context*,int,sqlite3_value **), + void (*)(sqlite3_context*,int,sqlite3_value **), + void (*)(sqlite3_context*), + void (*)(sqlite3_context*), + void (*)(sqlite3_context*,int,sqlite3_value **), + FuncDestructor *pDestructor +); +SQLITE_PRIVATE void sqlite3NoopDestructor(void*); +SQLITE_PRIVATE void *sqlite3OomFault(sqlite3*); +SQLITE_PRIVATE void sqlite3OomClear(sqlite3*); +SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int); +SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *); + +SQLITE_PRIVATE char *sqlite3RCStrRef(char*); +SQLITE_PRIVATE void sqlite3RCStrUnref(char*); +SQLITE_PRIVATE char *sqlite3RCStrNew(u64); +SQLITE_PRIVATE char *sqlite3RCStrResize(char*,u64); + +SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); +SQLITE_PRIVATE int sqlite3StrAccumEnlarge(StrAccum*, i64); +SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*); +SQLITE_PRIVATE void sqlite3StrAccumSetError(StrAccum*, u8); +SQLITE_PRIVATE void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*); +SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest*,int,int); +SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); +SQLITE_PRIVATE void sqlite3RecordErrorByteOffset(sqlite3*,const char*); +SQLITE_PRIVATE void sqlite3RecordErrorOffsetOfExpr(sqlite3*,const Expr*); + +SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *); +SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *); + +#ifndef SQLITE_OMIT_SUBQUERY +SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse*, Expr*); +#else +# define sqlite3ExprCheckIN(x,y) SQLITE_OK +#endif + +#ifdef SQLITE_ENABLE_STAT4 +SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue( + Parse*,Index*,UnpackedRecord**,Expr*,int,int,int*); +SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr(Parse*, Expr*, u8, sqlite3_value**); +SQLITE_PRIVATE void sqlite3Stat4ProbeFree(UnpackedRecord*); +SQLITE_PRIVATE int sqlite3Stat4Column(sqlite3*, const void*, int, int, sqlite3_value**); +SQLITE_PRIVATE char sqlite3IndexColumnAffinity(sqlite3*, Index*, int); +#endif + +/* +** The interface to the LEMON-generated parser +*/ +#ifndef SQLITE_AMALGAMATION +SQLITE_PRIVATE void *sqlite3ParserAlloc(void*(*)(u64), Parse*); +SQLITE_PRIVATE void sqlite3ParserFree(void*, void(*)(void*)); +#endif +SQLITE_PRIVATE void sqlite3Parser(void*, int, Token); +SQLITE_PRIVATE int sqlite3ParserFallback(int); +#ifdef YYTRACKMAXSTACKDEPTH +SQLITE_PRIVATE int sqlite3ParserStackPeak(void*); +#endif + +SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3*); +#ifndef SQLITE_OMIT_LOAD_EXTENSION +SQLITE_PRIVATE void sqlite3CloseExtensions(sqlite3*); +#else +# define sqlite3CloseExtensions(X) +#endif + +#ifndef SQLITE_OMIT_SHARED_CACHE +SQLITE_PRIVATE void sqlite3TableLock(Parse *, int, Pgno, u8, const char *); +#else + #define sqlite3TableLock(v,w,x,y,z) +#endif + +#ifdef SQLITE_TEST +SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char*); +#endif + +#ifdef SQLITE_OMIT_VIRTUALTABLE +# define sqlite3VtabClear(D,T) +# define sqlite3VtabSync(X,Y) SQLITE_OK +# define sqlite3VtabRollback(X) +# define sqlite3VtabCommit(X) +# define sqlite3VtabInSync(db) 0 +# define sqlite3VtabLock(X) +# define sqlite3VtabUnlock(X) +# define sqlite3VtabModuleUnref(D,X) +# define sqlite3VtabUnlockList(X) +# define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK +# define sqlite3GetVTable(X,Y) ((VTable*)0) +#else +SQLITE_PRIVATE void sqlite3VtabClear(sqlite3 *db, Table*); +SQLITE_PRIVATE void sqlite3VtabDisconnect(sqlite3 *db, Table *p); +SQLITE_PRIVATE int sqlite3VtabSync(sqlite3 *db, Vdbe*); +SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db); +SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db); +SQLITE_PRIVATE void sqlite3VtabLock(VTable *); +SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *); +SQLITE_PRIVATE void sqlite3VtabModuleUnref(sqlite3*,Module*); +SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3*); +SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *, int, int); +SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe*, sqlite3_vtab*); +SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3*, Table*); +SQLITE_PRIVATE Module *sqlite3VtabCreateModule( + sqlite3*, + const char*, + const sqlite3_module*, + void*, + void(*)(void*) + ); +# define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0) +#endif +SQLITE_PRIVATE int sqlite3ReadOnlyShadowTables(sqlite3 *db); +#ifndef SQLITE_OMIT_VIRTUALTABLE +SQLITE_PRIVATE int sqlite3ShadowTableName(sqlite3 *db, const char *zName); +SQLITE_PRIVATE int sqlite3IsShadowTableOf(sqlite3*,Table*,const char*); +SQLITE_PRIVATE void sqlite3MarkAllShadowTablesOf(sqlite3*, Table*); +#else +# define sqlite3ShadowTableName(A,B) 0 +# define sqlite3IsShadowTableOf(A,B,C) 0 +# define sqlite3MarkAllShadowTablesOf(A,B) +#endif +SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse*,Module*); +SQLITE_PRIVATE void sqlite3VtabEponymousTableClear(sqlite3*,Module*); +SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse*,Table*); +SQLITE_PRIVATE void sqlite3VtabBeginParse(Parse*, Token*, Token*, Token*, int); +SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse*, Token*); +SQLITE_PRIVATE void sqlite3VtabArgInit(Parse*); +SQLITE_PRIVATE void sqlite3VtabArgExtend(Parse*, Token*); +SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3*, int, const char *, char **); +SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse*, Table*); +SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3*, int, const char *); +SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *, VTable *); + +SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*); +SQLITE_PRIVATE void sqlite3VtabUsesAllSchemas(Parse*); +SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*); +SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe*, const char*, int); +SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); +SQLITE_PRIVATE void sqlite3ParseObjectInit(Parse*,sqlite3*); +SQLITE_PRIVATE void sqlite3ParseObjectReset(Parse*); +SQLITE_PRIVATE void *sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*); +#ifdef SQLITE_ENABLE_NORMALIZE +SQLITE_PRIVATE char *sqlite3Normalize(Vdbe*, const char*); +#endif +SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*); +SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*); +SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse*,const Expr*); +SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, const Expr*, const Expr*); +SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*); +SQLITE_PRIVATE const char *sqlite3JournalModename(int); +#ifndef SQLITE_OMIT_WAL +SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*); +SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int); +#endif +#ifndef SQLITE_OMIT_CTE +SQLITE_PRIVATE Cte *sqlite3CteNew(Parse*,Token*,ExprList*,Select*,u8); +SQLITE_PRIVATE void sqlite3CteDelete(sqlite3*,Cte*); +SQLITE_PRIVATE With *sqlite3WithAdd(Parse*,With*,Cte*); +SQLITE_PRIVATE void sqlite3WithDelete(sqlite3*,With*); +SQLITE_PRIVATE With *sqlite3WithPush(Parse*, With*, u8); +#else +# define sqlite3CteNew(P,T,E,S) ((void*)0) +# define sqlite3CteDelete(D,C) +# define sqlite3CteWithAdd(P,W,C) ((void*)0) +# define sqlite3WithDelete(x,y) +# define sqlite3WithPush(x,y,z) ((void*)0) +#endif +#ifndef SQLITE_OMIT_UPSERT +SQLITE_PRIVATE Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*); +SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3*,Upsert*); +SQLITE_PRIVATE Upsert *sqlite3UpsertDup(sqlite3*,Upsert*); +SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*); +SQLITE_PRIVATE void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int); +SQLITE_PRIVATE Upsert *sqlite3UpsertOfIndex(Upsert*,Index*); +SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert*); +#else +#define sqlite3UpsertNew(u,v,w,x,y,z) ((Upsert*)0) +#define sqlite3UpsertDelete(x,y) +#define sqlite3UpsertDup(x,y) ((Upsert*)0) +#define sqlite3UpsertOfIndex(x,y) ((Upsert*)0) +#define sqlite3UpsertNextIsIPK(x) 0 +#endif + + +/* Declarations for functions in fkey.c. All of these are replaced by +** no-op macros if OMIT_FOREIGN_KEY is defined. In this case no foreign +** key functionality is available. If OMIT_TRIGGER is defined but +** OMIT_FOREIGN_KEY is not, only some of the functions are no-oped. In +** this case foreign keys are parsed, but no other functionality is +** provided (enforcement of FK constraints requires the triggers sub-system). +*/ +#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) +SQLITE_PRIVATE void sqlite3FkCheck(Parse*, Table*, int, int, int*, int); +SQLITE_PRIVATE void sqlite3FkDropTable(Parse*, SrcList *, Table*); +SQLITE_PRIVATE void sqlite3FkActions(Parse*, Table*, ExprList*, int, int*, int); +SQLITE_PRIVATE int sqlite3FkRequired(Parse*, Table*, int*, int); +SQLITE_PRIVATE u32 sqlite3FkOldmask(Parse*, Table*); +SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *); +SQLITE_PRIVATE void sqlite3FkClearTriggerCache(sqlite3*,int); +#else + #define sqlite3FkActions(a,b,c,d,e,f) + #define sqlite3FkCheck(a,b,c,d,e,f) + #define sqlite3FkDropTable(a,b,c) + #define sqlite3FkOldmask(a,b) 0 + #define sqlite3FkRequired(a,b,c,d) 0 + #define sqlite3FkReferences(a) 0 + #define sqlite3FkClearTriggerCache(a,b) +#endif +#ifndef SQLITE_OMIT_FOREIGN_KEY +SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *, Table*); +SQLITE_PRIVATE int sqlite3FkLocateIndex(Parse*,Table*,FKey*,Index**,int**); +#else + #define sqlite3FkDelete(a,b) + #define sqlite3FkLocateIndex(a,b,c,d,e) +#endif + + +/* +** Available fault injectors. Should be numbered beginning with 0. +*/ +#define SQLITE_FAULTINJECTOR_MALLOC 0 +#define SQLITE_FAULTINJECTOR_COUNT 1 + +/* +** The interface to the code in fault.c used for identifying "benign" +** malloc failures. This is only present if SQLITE_UNTESTABLE +** is not defined. +*/ +#ifndef SQLITE_UNTESTABLE +SQLITE_PRIVATE void sqlite3BeginBenignMalloc(void); +SQLITE_PRIVATE void sqlite3EndBenignMalloc(void); +#else + #define sqlite3BeginBenignMalloc() + #define sqlite3EndBenignMalloc() +#endif + +/* +** Allowed return values from sqlite3FindInIndex() +*/ +#define IN_INDEX_ROWID 1 /* Search the rowid of the table */ +#define IN_INDEX_EPH 2 /* Search an ephemeral b-tree */ +#define IN_INDEX_INDEX_ASC 3 /* Existing index ASCENDING */ +#define IN_INDEX_INDEX_DESC 4 /* Existing index DESCENDING */ +#define IN_INDEX_NOOP 5 /* No table available. Use comparisons */ +/* +** Allowed flags for the 3rd parameter to sqlite3FindInIndex(). +*/ +#define IN_INDEX_NOOP_OK 0x0001 /* OK to return IN_INDEX_NOOP */ +#define IN_INDEX_MEMBERSHIP 0x0002 /* IN operator used for membership test */ +#define IN_INDEX_LOOP 0x0004 /* IN operator used as a loop */ +SQLITE_PRIVATE int sqlite3FindInIndex(Parse *, Expr *, u32, int*, int*, int*); + +SQLITE_PRIVATE int sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int); +SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *); +#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ + || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) +SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *); +#endif + +SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p); +SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *); + +SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p); +#if SQLITE_MAX_EXPR_DEPTH>0 +SQLITE_PRIVATE int sqlite3SelectExprHeight(const Select *); +SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse*, int); +#else + #define sqlite3SelectExprHeight(x) 0 + #define sqlite3ExprCheckHeight(x,y) +#endif +SQLITE_PRIVATE void sqlite3ExprSetErrorOffset(Expr*,int); + +SQLITE_PRIVATE u32 sqlite3Get4byte(const u8*); +SQLITE_PRIVATE void sqlite3Put4byte(u8*, u32); + +#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY +SQLITE_PRIVATE void sqlite3ConnectionBlocked(sqlite3 *, sqlite3 *); +SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db); +SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db); +#else + #define sqlite3ConnectionBlocked(x,y) + #define sqlite3ConnectionUnlocked(x) + #define sqlite3ConnectionClosed(x) +#endif + +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE void sqlite3ParserTrace(FILE*, char *); +#endif +#if defined(YYCOVERAGE) +SQLITE_PRIVATE int sqlite3ParserCoverage(FILE*); +#endif + +/* +** If the SQLITE_ENABLE IOTRACE exists then the global variable +** sqlite3IoTrace is a pointer to a printf-like routine used to +** print I/O tracing messages. +*/ +#ifdef SQLITE_ENABLE_IOTRACE +# define IOTRACE(A) if( sqlite3IoTrace ){ sqlite3IoTrace A; } +SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe*); +SQLITE_API SQLITE_EXTERN void (SQLITE_CDECL *sqlite3IoTrace)(const char*,...); +#else +# define IOTRACE(A) +# define sqlite3VdbeIOTraceSql(X) +#endif + +/* +** These routines are available for the mem2.c debugging memory allocator +** only. They are used to verify that different "types" of memory +** allocations are properly tracked by the system. +** +** sqlite3MemdebugSetType() sets the "type" of an allocation to one of +** the MEMTYPE_* macros defined below. The type must be a bitmask with +** a single bit set. +** +** sqlite3MemdebugHasType() returns true if any of the bits in its second +** argument match the type set by the previous sqlite3MemdebugSetType(). +** sqlite3MemdebugHasType() is intended for use inside assert() statements. +** +** sqlite3MemdebugNoType() returns true if none of the bits in its second +** argument match the type set by the previous sqlite3MemdebugSetType(). +** +** Perhaps the most important point is the difference between MEMTYPE_HEAP +** and MEMTYPE_LOOKASIDE. If an allocation is MEMTYPE_LOOKASIDE, that means +** it might have been allocated by lookaside, except the allocation was +** too large or lookaside was already full. It is important to verify +** that allocations that might have been satisfied by lookaside are not +** passed back to non-lookaside free() routines. Asserts such as the +** example above are placed on the non-lookaside free() routines to verify +** this constraint. +** +** All of this is no-op for a production build. It only comes into +** play when the SQLITE_MEMDEBUG compile-time option is used. +*/ +#ifdef SQLITE_MEMDEBUG +SQLITE_PRIVATE void sqlite3MemdebugSetType(void*,u8); +SQLITE_PRIVATE int sqlite3MemdebugHasType(const void*,u8); +SQLITE_PRIVATE int sqlite3MemdebugNoType(const void*,u8); +#else +# define sqlite3MemdebugSetType(X,Y) /* no-op */ +# define sqlite3MemdebugHasType(X,Y) 1 +# define sqlite3MemdebugNoType(X,Y) 1 +#endif +#define MEMTYPE_HEAP 0x01 /* General heap allocations */ +#define MEMTYPE_LOOKASIDE 0x02 /* Heap that might have been lookaside */ +#define MEMTYPE_PCACHE 0x04 /* Page cache allocations */ + +/* +** Threading interface +*/ +#if SQLITE_MAX_WORKER_THREADS>0 +SQLITE_PRIVATE int sqlite3ThreadCreate(SQLiteThread**,void*(*)(void*),void*); +SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread*, void**); +#endif + +#if defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST) +SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3*); +#endif +#if defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST) +SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3*); +#endif + +SQLITE_PRIVATE int sqlite3ExprVectorSize(const Expr *pExpr); +SQLITE_PRIVATE int sqlite3ExprIsVector(const Expr *pExpr); +SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr*, int); +SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int,int); +SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse*, Expr*); + +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS +SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt); +#endif + +#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) +SQLITE_PRIVATE int sqlite3KvvfsInit(void); +#endif + +#if defined(VDBE_PROFILE) \ + || defined(SQLITE_PERFORMANCE_TRACE) \ + || defined(SQLITE_ENABLE_STMT_SCANSTATUS) +SQLITE_PRIVATE sqlite3_uint64 sqlite3Hwtime(void); +#endif + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +# define IS_STMT_SCANSTATUS(db) (db->flags & SQLITE_StmtScanStatus) +#else +# define IS_STMT_SCANSTATUS(db) 0 +#endif + +#endif /* SQLITEINT_H */ + +/************** End of sqliteInt.h *******************************************/ +/************** Begin file os_common.h ***************************************/ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains macros and a little bit of code that is common to +** all of the platform-specific files (os_*.c) and is #included into those +** files. +** +** This file should be #included by the os_*.c files only. It is not a +** general purpose header file. +*/ +#ifndef _OS_COMMON_H_ +#define _OS_COMMON_H_ + +/* +** At least two bugs have slipped in because we changed the MEMORY_DEBUG +** macro to SQLITE_DEBUG and some older makefiles have not yet made the +** switch. The following code should catch this problem at compile-time. +*/ +#ifdef MEMORY_DEBUG +# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." +#endif + +/* +** Macros for performance tracing. Normally turned off. Only works +** on i486 hardware. +*/ +#ifdef SQLITE_PERFORMANCE_TRACE + +static sqlite_uint64 g_start; +static sqlite_uint64 g_elapsed; +#define TIMER_START g_start=sqlite3Hwtime() +#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start +#define TIMER_ELAPSED g_elapsed +#else +#define TIMER_START +#define TIMER_END +#define TIMER_ELAPSED ((sqlite_uint64)0) +#endif + +/* +** If we compile with the SQLITE_TEST macro set, then the following block +** of code will give us the ability to simulate a disk I/O error. This +** is used for testing the I/O recovery logic. +*/ +#if defined(SQLITE_TEST) +SQLITE_API extern int sqlite3_io_error_hit; +SQLITE_API extern int sqlite3_io_error_hardhit; +SQLITE_API extern int sqlite3_io_error_pending; +SQLITE_API extern int sqlite3_io_error_persist; +SQLITE_API extern int sqlite3_io_error_benign; +SQLITE_API extern int sqlite3_diskfull_pending; +SQLITE_API extern int sqlite3_diskfull; +#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) +#define SimulateIOError(CODE) \ + if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ + || sqlite3_io_error_pending-- == 1 ) \ + { local_ioerr(); CODE; } +static void local_ioerr(){ + IOTRACE(("IOERR\n")); + sqlite3_io_error_hit++; + if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; +} +#define SimulateDiskfullError(CODE) \ + if( sqlite3_diskfull_pending ){ \ + if( sqlite3_diskfull_pending == 1 ){ \ + local_ioerr(); \ + sqlite3_diskfull = 1; \ + sqlite3_io_error_hit = 1; \ + CODE; \ + }else{ \ + sqlite3_diskfull_pending--; \ + } \ + } +#else +#define SimulateIOErrorBenign(X) +#define SimulateIOError(A) +#define SimulateDiskfullError(A) +#endif /* defined(SQLITE_TEST) */ + +/* +** When testing, keep a count of the number of open files. +*/ +#if defined(SQLITE_TEST) +SQLITE_API extern int sqlite3_open_file_count; +#define OpenCounter(X) sqlite3_open_file_count+=(X) +#else +#define OpenCounter(X) +#endif /* defined(SQLITE_TEST) */ + +#endif /* !defined(_OS_COMMON_H_) */ + +/************** End of os_common.h *******************************************/ +/************** Begin file ctime.c *******************************************/ +/* DO NOT EDIT! +** This file is automatically generated by the script in the canonical +** SQLite source tree at tool/mkctimec.tcl. +** +** To modify this header, edit any of the various lists in that script +** which specify categories of generated conditionals in this file. +*/ + +/* +** 2010 February 23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements routines used to report what compile-time options +** SQLite was built with. +*/ +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS /* IMP: R-16824-07538 */ + +/* +** Include the configuration header output by 'configure' if we're using the +** autoconf-based build +*/ +#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) +/* #include "sqlite_cfg.h" */ +#define SQLITECONFIG_H 1 +#endif + +/* These macros are provided to "stringify" the value of the define +** for those options in which the value is meaningful. */ +#define CTIMEOPT_VAL_(opt) #opt +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) + +/* Like CTIMEOPT_VAL, but especially for SQLITE_DEFAULT_LOOKASIDE. This +** option requires a separate macro because legal values contain a single +** comma. e.g. (-DSQLITE_DEFAULT_LOOKASIDE="100,100") */ +#define CTIMEOPT_VAL2_(opt1,opt2) #opt1 "," #opt2 +#define CTIMEOPT_VAL2(opt) CTIMEOPT_VAL2_(opt) +/* #include "sqliteInt.h" */ + +/* +** An array of names of all compile-time options. This array should +** be sorted A-Z. +** +** This array looks large, but in a typical installation actually uses +** only a handful of compile-time options, so most times this array is usually +** rather short and uses little memory space. +*/ +static const char * const sqlite3azCompileOpt[] = { + +#ifdef SQLITE_32BIT_ROWID + "32BIT_ROWID", +#endif +#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC + "4_BYTE_ALIGNED_MALLOC", +#endif +#ifdef SQLITE_ALLOW_COVERING_INDEX_SCAN +# if SQLITE_ALLOW_COVERING_INDEX_SCAN != 1 + "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), +# endif +#endif +#ifdef SQLITE_ALLOW_URI_AUTHORITY + "ALLOW_URI_AUTHORITY", +#endif +#ifdef SQLITE_ATOMIC_INTRINSICS + "ATOMIC_INTRINSICS=" CTIMEOPT_VAL(SQLITE_ATOMIC_INTRINSICS), +#endif +#ifdef SQLITE_BITMASK_TYPE + "BITMASK_TYPE=" CTIMEOPT_VAL(SQLITE_BITMASK_TYPE), +#endif +#ifdef SQLITE_BUG_COMPATIBLE_20160819 + "BUG_COMPATIBLE_20160819", +#endif +#ifdef SQLITE_CASE_SENSITIVE_LIKE + "CASE_SENSITIVE_LIKE", +#endif +#ifdef SQLITE_CHECK_PAGES + "CHECK_PAGES", +#endif +#if defined(__clang__) && defined(__clang_major__) + "COMPILER=clang-" CTIMEOPT_VAL(__clang_major__) "." + CTIMEOPT_VAL(__clang_minor__) "." + CTIMEOPT_VAL(__clang_patchlevel__), +#elif defined(_MSC_VER) + "COMPILER=msvc-" CTIMEOPT_VAL(_MSC_VER), +#elif defined(__GNUC__) && defined(__VERSION__) + "COMPILER=gcc-" __VERSION__, +#endif +#ifdef SQLITE_COVERAGE_TEST + "COVERAGE_TEST", +#endif +#ifdef SQLITE_DEBUG + "DEBUG", +#endif +#ifdef SQLITE_DEFAULT_AUTOMATIC_INDEX + "DEFAULT_AUTOMATIC_INDEX", +#endif +#ifdef SQLITE_DEFAULT_AUTOVACUUM + "DEFAULT_AUTOVACUUM", +#endif +#ifdef SQLITE_DEFAULT_CACHE_SIZE + "DEFAULT_CACHE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_CACHE_SIZE), +#endif +#ifdef SQLITE_DEFAULT_CKPTFULLFSYNC + "DEFAULT_CKPTFULLFSYNC", +#endif +#ifdef SQLITE_DEFAULT_FILE_FORMAT + "DEFAULT_FILE_FORMAT=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_FORMAT), +#endif +#ifdef SQLITE_DEFAULT_FILE_PERMISSIONS + "DEFAULT_FILE_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_PERMISSIONS), +#endif +#ifdef SQLITE_DEFAULT_FOREIGN_KEYS + "DEFAULT_FOREIGN_KEYS", +#endif +#ifdef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT + "DEFAULT_JOURNAL_SIZE_LIMIT=" CTIMEOPT_VAL(SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT), +#endif +#ifdef SQLITE_DEFAULT_LOCKING_MODE + "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE), +#endif +#ifdef SQLITE_DEFAULT_LOOKASIDE + "DEFAULT_LOOKASIDE=" CTIMEOPT_VAL2(SQLITE_DEFAULT_LOOKASIDE), +#endif +#ifdef SQLITE_DEFAULT_MEMSTATUS +# if SQLITE_DEFAULT_MEMSTATUS != 1 + "DEFAULT_MEMSTATUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_MEMSTATUS), +# endif +#endif +#ifdef SQLITE_DEFAULT_MMAP_SIZE + "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), +#endif +#ifdef SQLITE_DEFAULT_PAGE_SIZE + "DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_PAGE_SIZE), +#endif +#ifdef SQLITE_DEFAULT_PCACHE_INITSZ + "DEFAULT_PCACHE_INITSZ=" CTIMEOPT_VAL(SQLITE_DEFAULT_PCACHE_INITSZ), +#endif +#ifdef SQLITE_DEFAULT_PROXYDIR_PERMISSIONS + "DEFAULT_PROXYDIR_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_PROXYDIR_PERMISSIONS), +#endif +#ifdef SQLITE_DEFAULT_RECURSIVE_TRIGGERS + "DEFAULT_RECURSIVE_TRIGGERS", +#endif +#ifdef SQLITE_DEFAULT_ROWEST + "DEFAULT_ROWEST=" CTIMEOPT_VAL(SQLITE_DEFAULT_ROWEST), +#endif +#ifdef SQLITE_DEFAULT_SECTOR_SIZE + "DEFAULT_SECTOR_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_SECTOR_SIZE), +#endif +#ifdef SQLITE_DEFAULT_SYNCHRONOUS + "DEFAULT_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_SYNCHRONOUS), +#endif +#ifdef SQLITE_DEFAULT_WAL_AUTOCHECKPOINT + "DEFAULT_WAL_AUTOCHECKPOINT=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_AUTOCHECKPOINT), +#endif +#ifdef SQLITE_DEFAULT_WAL_SYNCHRONOUS + "DEFAULT_WAL_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_SYNCHRONOUS), +#endif +#ifdef SQLITE_DEFAULT_WORKER_THREADS + "DEFAULT_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WORKER_THREADS), +#endif +#ifdef SQLITE_DIRECT_OVERFLOW_READ + "DIRECT_OVERFLOW_READ", +#endif +#ifdef SQLITE_DISABLE_DIRSYNC + "DISABLE_DIRSYNC", +#endif +#ifdef SQLITE_DISABLE_FTS3_UNICODE + "DISABLE_FTS3_UNICODE", +#endif +#ifdef SQLITE_DISABLE_FTS4_DEFERRED + "DISABLE_FTS4_DEFERRED", +#endif +#ifdef SQLITE_DISABLE_INTRINSIC + "DISABLE_INTRINSIC", +#endif +#ifdef SQLITE_DISABLE_LFS + "DISABLE_LFS", +#endif +#ifdef SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS + "DISABLE_PAGECACHE_OVERFLOW_STATS", +#endif +#ifdef SQLITE_DISABLE_SKIPAHEAD_DISTINCT + "DISABLE_SKIPAHEAD_DISTINCT", +#endif +#ifdef SQLITE_DQS + "DQS=" CTIMEOPT_VAL(SQLITE_DQS), +#endif +#ifdef SQLITE_ENABLE_8_3_NAMES + "ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES), +#endif +#ifdef SQLITE_ENABLE_API_ARMOR + "ENABLE_API_ARMOR", +#endif +#ifdef SQLITE_ENABLE_ATOMIC_WRITE + "ENABLE_ATOMIC_WRITE", +#endif +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + "ENABLE_BATCH_ATOMIC_WRITE", +#endif +#ifdef SQLITE_ENABLE_BYTECODE_VTAB + "ENABLE_BYTECODE_VTAB", +#endif +#ifdef SQLITE_ENABLE_CEROD + "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD), +#endif +#ifdef SQLITE_ENABLE_COLUMN_METADATA + "ENABLE_COLUMN_METADATA", +#endif +#ifdef SQLITE_ENABLE_COLUMN_USED_MASK + "ENABLE_COLUMN_USED_MASK", +#endif +#ifdef SQLITE_ENABLE_COSTMULT + "ENABLE_COSTMULT", +#endif +#ifdef SQLITE_ENABLE_CURSOR_HINTS + "ENABLE_CURSOR_HINTS", +#endif +#ifdef SQLITE_ENABLE_DBPAGE_VTAB + "ENABLE_DBPAGE_VTAB", +#endif +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + "ENABLE_DBSTAT_VTAB", +#endif +#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT + "ENABLE_EXPENSIVE_ASSERT", +#endif +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + "ENABLE_EXPLAIN_COMMENTS", +#endif +#ifdef SQLITE_ENABLE_FTS3 + "ENABLE_FTS3", +#endif +#ifdef SQLITE_ENABLE_FTS3_PARENTHESIS + "ENABLE_FTS3_PARENTHESIS", +#endif +#ifdef SQLITE_ENABLE_FTS3_TOKENIZER + "ENABLE_FTS3_TOKENIZER", +#endif +#ifdef SQLITE_ENABLE_FTS4 + "ENABLE_FTS4", +#endif +#ifdef SQLITE_ENABLE_FTS5 + "ENABLE_FTS5", +#endif +#ifdef SQLITE_ENABLE_GEOPOLY + "ENABLE_GEOPOLY", +#endif +#ifdef SQLITE_ENABLE_HIDDEN_COLUMNS + "ENABLE_HIDDEN_COLUMNS", +#endif +#ifdef SQLITE_ENABLE_ICU + "ENABLE_ICU", +#endif +#ifdef SQLITE_ENABLE_IOTRACE + "ENABLE_IOTRACE", +#endif +#ifdef SQLITE_ENABLE_LOAD_EXTENSION + "ENABLE_LOAD_EXTENSION", +#endif +#ifdef SQLITE_ENABLE_LOCKING_STYLE + "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE), +#endif +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS + "ENABLE_MATH_FUNCTIONS", +#endif +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT + "ENABLE_MEMORY_MANAGEMENT", +#endif +#ifdef SQLITE_ENABLE_MEMSYS3 + "ENABLE_MEMSYS3", +#endif +#ifdef SQLITE_ENABLE_MEMSYS5 + "ENABLE_MEMSYS5", +#endif +#ifdef SQLITE_ENABLE_MULTIPLEX + "ENABLE_MULTIPLEX", +#endif +#ifdef SQLITE_ENABLE_NORMALIZE + "ENABLE_NORMALIZE", +#endif +#ifdef SQLITE_ENABLE_NULL_TRIM + "ENABLE_NULL_TRIM", +#endif +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + "ENABLE_OFFSET_SQL_FUNC", +#endif +#ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK + "ENABLE_OVERSIZE_CELL_CHECK", +#endif +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + "ENABLE_PREUPDATE_HOOK", +#endif +#ifdef SQLITE_ENABLE_QPSG + "ENABLE_QPSG", +#endif +#ifdef SQLITE_ENABLE_RBU + "ENABLE_RBU", +#endif +#ifdef SQLITE_ENABLE_RTREE + "ENABLE_RTREE", +#endif +#ifdef SQLITE_ENABLE_SESSION + "ENABLE_SESSION", +#endif +#ifdef SQLITE_ENABLE_SNAPSHOT + "ENABLE_SNAPSHOT", +#endif +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + "ENABLE_SORTER_REFERENCES", +#endif +#ifdef SQLITE_ENABLE_SQLLOG + "ENABLE_SQLLOG", +#endif +#ifdef SQLITE_ENABLE_STAT4 + "ENABLE_STAT4", +#endif +#ifdef SQLITE_ENABLE_STMTVTAB + "ENABLE_STMTVTAB", +#endif +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + "ENABLE_STMT_SCANSTATUS", +#endif +#ifdef SQLITE_ENABLE_TREETRACE + "ENABLE_TREETRACE", +#endif +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + "ENABLE_UNKNOWN_SQL_FUNCTION", +#endif +#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY + "ENABLE_UNLOCK_NOTIFY", +#endif +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + "ENABLE_UPDATE_DELETE_LIMIT", +#endif +#ifdef SQLITE_ENABLE_URI_00_ERROR + "ENABLE_URI_00_ERROR", +#endif +#ifdef SQLITE_ENABLE_VFSTRACE + "ENABLE_VFSTRACE", +#endif +#ifdef SQLITE_ENABLE_WHERETRACE + "ENABLE_WHERETRACE", +#endif +#ifdef SQLITE_ENABLE_ZIPVFS + "ENABLE_ZIPVFS", +#endif +#ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS + "EXPLAIN_ESTIMATED_ROWS", +#endif +#ifdef SQLITE_EXTRA_AUTOEXT + "EXTRA_AUTOEXT=" CTIMEOPT_VAL(SQLITE_EXTRA_AUTOEXT), +#endif +#ifdef SQLITE_EXTRA_IFNULLROW + "EXTRA_IFNULLROW", +#endif +#ifdef SQLITE_EXTRA_INIT + "EXTRA_INIT=" CTIMEOPT_VAL(SQLITE_EXTRA_INIT), +#endif +#ifdef SQLITE_EXTRA_SHUTDOWN + "EXTRA_SHUTDOWN=" CTIMEOPT_VAL(SQLITE_EXTRA_SHUTDOWN), +#endif +#ifdef SQLITE_FTS3_MAX_EXPR_DEPTH + "FTS3_MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_FTS3_MAX_EXPR_DEPTH), +#endif +#ifdef SQLITE_FTS5_ENABLE_TEST_MI + "FTS5_ENABLE_TEST_MI", +#endif +#ifdef SQLITE_FTS5_NO_WITHOUT_ROWID + "FTS5_NO_WITHOUT_ROWID", +#endif +#if HAVE_ISNAN || SQLITE_HAVE_ISNAN + "HAVE_ISNAN", +#endif +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX +# if SQLITE_HOMEGROWN_RECURSIVE_MUTEX != 1 + "HOMEGROWN_RECURSIVE_MUTEX=" CTIMEOPT_VAL(SQLITE_HOMEGROWN_RECURSIVE_MUTEX), +# endif +#endif +#ifdef SQLITE_IGNORE_AFP_LOCK_ERRORS + "IGNORE_AFP_LOCK_ERRORS", +#endif +#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS + "IGNORE_FLOCK_LOCK_ERRORS", +#endif +#ifdef SQLITE_INLINE_MEMCPY + "INLINE_MEMCPY", +#endif +#ifdef SQLITE_INT64_TYPE + "INT64_TYPE", +#endif +#ifdef SQLITE_INTEGRITY_CHECK_ERROR_MAX + "INTEGRITY_CHECK_ERROR_MAX=" CTIMEOPT_VAL(SQLITE_INTEGRITY_CHECK_ERROR_MAX), +#endif +#ifdef SQLITE_LEGACY_JSON_VALID + "LEGACY_JSON_VALID", +#endif +#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS + "LIKE_DOESNT_MATCH_BLOBS", +#endif +#ifdef SQLITE_LOCK_TRACE + "LOCK_TRACE", +#endif +#ifdef SQLITE_LOG_CACHE_SPILL + "LOG_CACHE_SPILL", +#endif +#ifdef SQLITE_MALLOC_SOFT_LIMIT + "MALLOC_SOFT_LIMIT=" CTIMEOPT_VAL(SQLITE_MALLOC_SOFT_LIMIT), +#endif +#ifdef SQLITE_MAX_ATTACHED + "MAX_ATTACHED=" CTIMEOPT_VAL(SQLITE_MAX_ATTACHED), +#endif +#ifdef SQLITE_MAX_COLUMN + "MAX_COLUMN=" CTIMEOPT_VAL(SQLITE_MAX_COLUMN), +#endif +#ifdef SQLITE_MAX_COMPOUND_SELECT + "MAX_COMPOUND_SELECT=" CTIMEOPT_VAL(SQLITE_MAX_COMPOUND_SELECT), +#endif +#ifdef SQLITE_MAX_DEFAULT_PAGE_SIZE + "MAX_DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_DEFAULT_PAGE_SIZE), +#endif +#ifdef SQLITE_MAX_EXPR_DEPTH + "MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_EXPR_DEPTH), +#endif +#ifdef SQLITE_MAX_FUNCTION_ARG + "MAX_FUNCTION_ARG=" CTIMEOPT_VAL(SQLITE_MAX_FUNCTION_ARG), +#endif +#ifdef SQLITE_MAX_LENGTH + "MAX_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LENGTH), +#endif +#ifdef SQLITE_MAX_LIKE_PATTERN_LENGTH + "MAX_LIKE_PATTERN_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LIKE_PATTERN_LENGTH), +#endif +#ifdef SQLITE_MAX_MEMORY + "MAX_MEMORY=" CTIMEOPT_VAL(SQLITE_MAX_MEMORY), +#endif +#ifdef SQLITE_MAX_MMAP_SIZE + "MAX_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE), +#endif +#ifdef SQLITE_MAX_MMAP_SIZE_ + "MAX_MMAP_SIZE_=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE_), +#endif +#ifdef SQLITE_MAX_PAGE_COUNT + "MAX_PAGE_COUNT=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_COUNT), +#endif +#ifdef SQLITE_MAX_PAGE_SIZE + "MAX_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_SIZE), +#endif +#ifdef SQLITE_MAX_SCHEMA_RETRY + "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY), +#endif +#ifdef SQLITE_MAX_SQL_LENGTH + "MAX_SQL_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_SQL_LENGTH), +#endif +#ifdef SQLITE_MAX_TRIGGER_DEPTH + "MAX_TRIGGER_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_TRIGGER_DEPTH), +#endif +#ifdef SQLITE_MAX_VARIABLE_NUMBER + "MAX_VARIABLE_NUMBER=" CTIMEOPT_VAL(SQLITE_MAX_VARIABLE_NUMBER), +#endif +#ifdef SQLITE_MAX_VDBE_OP + "MAX_VDBE_OP=" CTIMEOPT_VAL(SQLITE_MAX_VDBE_OP), +#endif +#ifdef SQLITE_MAX_WORKER_THREADS + "MAX_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_MAX_WORKER_THREADS), +#endif +#ifdef SQLITE_MEMDEBUG + "MEMDEBUG", +#endif +#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT + "MIXED_ENDIAN_64BIT_FLOAT", +#endif +#ifdef SQLITE_MMAP_READWRITE + "MMAP_READWRITE", +#endif +#ifdef SQLITE_MUTEX_NOOP + "MUTEX_NOOP", +#endif +#ifdef SQLITE_MUTEX_OMIT + "MUTEX_OMIT", +#endif +#ifdef SQLITE_MUTEX_PTHREADS + "MUTEX_PTHREADS", +#endif +#ifdef SQLITE_MUTEX_W32 + "MUTEX_W32", +#endif +#ifdef SQLITE_NEED_ERR_NAME + "NEED_ERR_NAME", +#endif +#ifdef SQLITE_NO_SYNC + "NO_SYNC", +#endif +#ifdef SQLITE_OMIT_ALTERTABLE + "OMIT_ALTERTABLE", +#endif +#ifdef SQLITE_OMIT_ANALYZE + "OMIT_ANALYZE", +#endif +#ifdef SQLITE_OMIT_ATTACH + "OMIT_ATTACH", +#endif +#ifdef SQLITE_OMIT_AUTHORIZATION + "OMIT_AUTHORIZATION", +#endif +#ifdef SQLITE_OMIT_AUTOINCREMENT + "OMIT_AUTOINCREMENT", +#endif +#ifdef SQLITE_OMIT_AUTOINIT + "OMIT_AUTOINIT", +#endif +#ifdef SQLITE_OMIT_AUTOMATIC_INDEX + "OMIT_AUTOMATIC_INDEX", +#endif +#ifdef SQLITE_OMIT_AUTORESET + "OMIT_AUTORESET", +#endif +#ifdef SQLITE_OMIT_AUTOVACUUM + "OMIT_AUTOVACUUM", +#endif +#ifdef SQLITE_OMIT_BETWEEN_OPTIMIZATION + "OMIT_BETWEEN_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_BLOB_LITERAL + "OMIT_BLOB_LITERAL", +#endif +#ifdef SQLITE_OMIT_CAST + "OMIT_CAST", +#endif +#ifdef SQLITE_OMIT_CHECK + "OMIT_CHECK", +#endif +#ifdef SQLITE_OMIT_COMPLETE + "OMIT_COMPLETE", +#endif +#ifdef SQLITE_OMIT_COMPOUND_SELECT + "OMIT_COMPOUND_SELECT", +#endif +#ifdef SQLITE_OMIT_CONFLICT_CLAUSE + "OMIT_CONFLICT_CLAUSE", +#endif +#ifdef SQLITE_OMIT_CTE + "OMIT_CTE", +#endif +#if defined(SQLITE_OMIT_DATETIME_FUNCS) || defined(SQLITE_OMIT_FLOATING_POINT) + "OMIT_DATETIME_FUNCS", +#endif +#ifdef SQLITE_OMIT_DECLTYPE + "OMIT_DECLTYPE", +#endif +#ifdef SQLITE_OMIT_DEPRECATED + "OMIT_DEPRECATED", +#endif +#ifdef SQLITE_OMIT_DESERIALIZE + "OMIT_DESERIALIZE", +#endif +#ifdef SQLITE_OMIT_DISKIO + "OMIT_DISKIO", +#endif +#ifdef SQLITE_OMIT_EXPLAIN + "OMIT_EXPLAIN", +#endif +#ifdef SQLITE_OMIT_FLAG_PRAGMAS + "OMIT_FLAG_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_FLOATING_POINT + "OMIT_FLOATING_POINT", +#endif +#ifdef SQLITE_OMIT_FOREIGN_KEY + "OMIT_FOREIGN_KEY", +#endif +#ifdef SQLITE_OMIT_GET_TABLE + "OMIT_GET_TABLE", +#endif +#ifdef SQLITE_OMIT_HEX_INTEGER + "OMIT_HEX_INTEGER", +#endif +#ifdef SQLITE_OMIT_INCRBLOB + "OMIT_INCRBLOB", +#endif +#ifdef SQLITE_OMIT_INTEGRITY_CHECK + "OMIT_INTEGRITY_CHECK", +#endif +#ifdef SQLITE_OMIT_INTROSPECTION_PRAGMAS + "OMIT_INTROSPECTION_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_JSON + "OMIT_JSON", +#endif +#ifdef SQLITE_OMIT_LIKE_OPTIMIZATION + "OMIT_LIKE_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_LOAD_EXTENSION + "OMIT_LOAD_EXTENSION", +#endif +#ifdef SQLITE_OMIT_LOCALTIME + "OMIT_LOCALTIME", +#endif +#ifdef SQLITE_OMIT_LOOKASIDE + "OMIT_LOOKASIDE", +#endif +#ifdef SQLITE_OMIT_MEMORYDB + "OMIT_MEMORYDB", +#endif +#ifdef SQLITE_OMIT_OR_OPTIMIZATION + "OMIT_OR_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_PAGER_PRAGMAS + "OMIT_PAGER_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_PARSER_TRACE + "OMIT_PARSER_TRACE", +#endif +#ifdef SQLITE_OMIT_POPEN + "OMIT_POPEN", +#endif +#ifdef SQLITE_OMIT_PRAGMA + "OMIT_PRAGMA", +#endif +#ifdef SQLITE_OMIT_PROGRESS_CALLBACK + "OMIT_PROGRESS_CALLBACK", +#endif +#ifdef SQLITE_OMIT_QUICKBALANCE + "OMIT_QUICKBALANCE", +#endif +#ifdef SQLITE_OMIT_REINDEX + "OMIT_REINDEX", +#endif +#ifdef SQLITE_OMIT_SCHEMA_PRAGMAS + "OMIT_SCHEMA_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS + "OMIT_SCHEMA_VERSION_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_SHARED_CACHE + "OMIT_SHARED_CACHE", +#endif +#ifdef SQLITE_OMIT_SHUTDOWN_DIRECTORIES + "OMIT_SHUTDOWN_DIRECTORIES", +#endif +#ifdef SQLITE_OMIT_SUBQUERY + "OMIT_SUBQUERY", +#endif +#ifdef SQLITE_OMIT_TCL_VARIABLE + "OMIT_TCL_VARIABLE", +#endif +#ifdef SQLITE_OMIT_TEMPDB + "OMIT_TEMPDB", +#endif +#ifdef SQLITE_OMIT_TEST_CONTROL + "OMIT_TEST_CONTROL", +#endif +#ifdef SQLITE_OMIT_TRACE +# if SQLITE_OMIT_TRACE != 1 + "OMIT_TRACE=" CTIMEOPT_VAL(SQLITE_OMIT_TRACE), +# endif +#endif +#ifdef SQLITE_OMIT_TRIGGER + "OMIT_TRIGGER", +#endif +#ifdef SQLITE_OMIT_TRUNCATE_OPTIMIZATION + "OMIT_TRUNCATE_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_UTF16 + "OMIT_UTF16", +#endif +#ifdef SQLITE_OMIT_VACUUM + "OMIT_VACUUM", +#endif +#ifdef SQLITE_OMIT_VIEW + "OMIT_VIEW", +#endif +#ifdef SQLITE_OMIT_VIRTUALTABLE + "OMIT_VIRTUALTABLE", +#endif +#ifdef SQLITE_OMIT_WAL + "OMIT_WAL", +#endif +#ifdef SQLITE_OMIT_WSD + "OMIT_WSD", +#endif +#ifdef SQLITE_OMIT_XFER_OPT + "OMIT_XFER_OPT", +#endif +#ifdef SQLITE_PERFORMANCE_TRACE + "PERFORMANCE_TRACE", +#endif +#ifdef SQLITE_POWERSAFE_OVERWRITE +# if SQLITE_POWERSAFE_OVERWRITE != 1 + "POWERSAFE_OVERWRITE=" CTIMEOPT_VAL(SQLITE_POWERSAFE_OVERWRITE), +# endif +#endif +#ifdef SQLITE_PREFER_PROXY_LOCKING + "PREFER_PROXY_LOCKING", +#endif +#ifdef SQLITE_PROXY_DEBUG + "PROXY_DEBUG", +#endif +#ifdef SQLITE_REVERSE_UNORDERED_SELECTS + "REVERSE_UNORDERED_SELECTS", +#endif +#ifdef SQLITE_RTREE_INT_ONLY + "RTREE_INT_ONLY", +#endif +#ifdef SQLITE_SECURE_DELETE + "SECURE_DELETE", +#endif +#ifdef SQLITE_SMALL_STACK + "SMALL_STACK", +#endif +#ifdef SQLITE_SORTER_PMASZ + "SORTER_PMASZ=" CTIMEOPT_VAL(SQLITE_SORTER_PMASZ), +#endif +#ifdef SQLITE_SOUNDEX + "SOUNDEX", +#endif +#ifdef SQLITE_STAT4_SAMPLES + "STAT4_SAMPLES=" CTIMEOPT_VAL(SQLITE_STAT4_SAMPLES), +#endif +#ifdef SQLITE_STMTJRNL_SPILL + "STMTJRNL_SPILL=" CTIMEOPT_VAL(SQLITE_STMTJRNL_SPILL), +#endif +#ifdef SQLITE_SUBSTR_COMPATIBILITY + "SUBSTR_COMPATIBILITY", +#endif +#if (!defined(SQLITE_WIN32_MALLOC) \ + && !defined(SQLITE_ZERO_MALLOC) \ + && !defined(SQLITE_MEMDEBUG) \ + ) || defined(SQLITE_SYSTEM_MALLOC) + "SYSTEM_MALLOC", +#endif +#ifdef SQLITE_TCL + "TCL", +#endif +#ifdef SQLITE_TEMP_STORE + "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE), +#endif +#ifdef SQLITE_TEST + "TEST", +#endif +#if defined(SQLITE_THREADSAFE) + "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), +#elif defined(THREADSAFE) + "THREADSAFE=" CTIMEOPT_VAL(THREADSAFE), +#else + "THREADSAFE=1", +#endif +#ifdef SQLITE_UNLINK_AFTER_CLOSE + "UNLINK_AFTER_CLOSE", +#endif +#ifdef SQLITE_UNTESTABLE + "UNTESTABLE", +#endif +#ifdef SQLITE_USER_AUTHENTICATION + "USER_AUTHENTICATION", +#endif +#ifdef SQLITE_USE_ALLOCA + "USE_ALLOCA", +#endif +#ifdef SQLITE_USE_FCNTL_TRACE + "USE_FCNTL_TRACE", +#endif +#ifdef SQLITE_USE_URI + "USE_URI", +#endif +#ifdef SQLITE_VDBE_COVERAGE + "VDBE_COVERAGE", +#endif +#ifdef SQLITE_WIN32_MALLOC + "WIN32_MALLOC", +#endif +#ifdef SQLITE_ZERO_MALLOC + "ZERO_MALLOC", +#endif + +} ; + +SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ + *pnOpt = sizeof(sqlite3azCompileOpt) / sizeof(sqlite3azCompileOpt[0]); + return (const char**)sqlite3azCompileOpt; +} + +#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ + +/************** End of ctime.c ***********************************************/ +/************** Begin file global.c ******************************************/ +/* +** 2008 June 13 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains definitions of global variables and constants. +*/ +/* #include "sqliteInt.h" */ + +/* An array to map all upper-case characters into their corresponding +** lower-case character. +** +** SQLite only considers US-ASCII (or EBCDIC) characters. We do not +** handle case conversions for the UTF character set since the tables +** involved are nearly as big or bigger than SQLite itself. +*/ +SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = { +#ifdef SQLITE_ASCII + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125, + 126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161, + 162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179, + 180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197, + 198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233, + 234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251, + 252,253,254,255, +#endif +#ifdef SQLITE_EBCDIC + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 0x */ + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, /* 1x */ + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, /* 2x */ + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, /* 3x */ + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, /* 4x */ + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, /* 5x */ + 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, /* 6x */ + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, /* 7x */ + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, /* 8x */ + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, /* 9x */ + 160,161,162,163,164,165,166,167,168,169,170,171,140,141,142,175, /* Ax */ + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, /* Bx */ + 192,129,130,131,132,133,134,135,136,137,202,203,204,205,206,207, /* Cx */ + 208,145,146,147,148,149,150,151,152,153,218,219,220,221,222,223, /* Dx */ + 224,225,162,163,164,165,166,167,168,169,234,235,236,237,238,239, /* Ex */ + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, /* Fx */ +#endif +/* All of the upper-to-lower conversion data is above. The following +** 18 integers are completely unrelated. They are appended to the +** sqlite3UpperToLower[] array to avoid UBSAN warnings. Here's what is +** going on: +** +** The SQL comparison operators (<>, =, >, <=, <, and >=) are implemented +** by invoking sqlite3MemCompare(A,B) which compares values A and B and +** returns negative, zero, or positive if A is less then, equal to, or +** greater than B, respectively. Then the true false results is found by +** consulting sqlite3aLTb[opcode], sqlite3aEQb[opcode], or +** sqlite3aGTb[opcode] depending on whether the result of compare(A,B) +** is negative, zero, or positive, where opcode is the specific opcode. +** The only works because the comparison opcodes are consecutive and in +** this order: NE EQ GT LE LT GE. Various assert()s throughout the code +** ensure that is the case. +** +** These elements must be appended to another array. Otherwise the +** index (here shown as [256-OP_Ne]) would be out-of-bounds and thus +** be undefined behavior. That's goofy, but the C-standards people thought +** it was a good idea, so here we are. +*/ +/* NE EQ GT LE LT GE */ + 1, 0, 0, 1, 1, 0, /* aLTb[]: Use when compare(A,B) less than zero */ + 0, 1, 0, 1, 0, 1, /* aEQb[]: Use when compare(A,B) equals zero */ + 1, 0, 1, 0, 0, 1 /* aGTb[]: Use when compare(A,B) greater than zero*/ +}; +SQLITE_PRIVATE const unsigned char *sqlite3aLTb = &sqlite3UpperToLower[256-OP_Ne]; +SQLITE_PRIVATE const unsigned char *sqlite3aEQb = &sqlite3UpperToLower[256+6-OP_Ne]; +SQLITE_PRIVATE const unsigned char *sqlite3aGTb = &sqlite3UpperToLower[256+12-OP_Ne]; + +/* +** The following 256 byte lookup table is used to support SQLites built-in +** equivalents to the following standard library functions: +** +** isspace() 0x01 +** isalpha() 0x02 +** isdigit() 0x04 +** isalnum() 0x06 +** isxdigit() 0x08 +** toupper() 0x20 +** SQLite identifier character 0x40 $, _, or non-ascii +** Quote character 0x80 +** +** Bit 0x20 is set if the mapped character requires translation to upper +** case. i.e. if the character is a lower-case ASCII character. +** If x is a lower-case ASCII character, then its upper-case equivalent +** is (x - 0x20). Therefore toupper() can be implemented as: +** +** (x & ~(map[x]&0x20)) +** +** The equivalent of tolower() is implemented using the sqlite3UpperToLower[] +** array. tolower() is used more often than toupper() by SQLite. +** +** Bit 0x40 is set if the character is non-alphanumeric and can be used in an +** SQLite identifier. Identifiers are alphanumerics, "_", "$", and any +** non-ASCII UTF character. Hence the test for whether or not a character is +** part of an identifier is 0x46. +*/ +SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00..07 ........ */ + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, /* 08..0f ........ */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10..17 ........ */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 18..1f ........ */ + 0x01, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x80, /* 20..27 !"#$%&' */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 28..2f ()*+,-./ */ + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, /* 30..37 01234567 */ + 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 38..3f 89:;<=>? */ + + 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x02, /* 40..47 @ABCDEFG */ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 48..4f HIJKLMNO */ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 50..57 PQRSTUVW */ + 0x02, 0x02, 0x02, 0x80, 0x00, 0x00, 0x00, 0x40, /* 58..5f XYZ[\]^_ */ + 0x80, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x22, /* 60..67 `abcdefg */ + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 68..6f hijklmno */ + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 70..77 pqrstuvw */ + 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, /* 78..7f xyz{|}~. */ + + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 80..87 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 88..8f ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 90..97 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 98..9f ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* a0..a7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* a8..af ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* b0..b7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* b8..bf ........ */ + + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* c0..c7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* c8..cf ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* d0..d7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* d8..df ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* e0..e7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* e8..ef ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* f0..f7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 /* f8..ff ........ */ +}; + +/* EVIDENCE-OF: R-02982-34736 In order to maintain full backwards +** compatibility for legacy applications, the URI filename capability is +** disabled by default. +** +** EVIDENCE-OF: R-38799-08373 URI filenames can be enabled or disabled +** using the SQLITE_USE_URI=1 or SQLITE_USE_URI=0 compile-time options. +** +** EVIDENCE-OF: R-43642-56306 By default, URI handling is globally +** disabled. The default value may be changed by compiling with the +** SQLITE_USE_URI symbol defined. +*/ +#ifndef SQLITE_USE_URI +# define SQLITE_USE_URI 0 +#endif + +/* EVIDENCE-OF: R-38720-18127 The default setting is determined by the +** SQLITE_ALLOW_COVERING_INDEX_SCAN compile-time option, or is "on" if +** that compile-time option is omitted. +*/ +#if !defined(SQLITE_ALLOW_COVERING_INDEX_SCAN) +# define SQLITE_ALLOW_COVERING_INDEX_SCAN 1 +#else +# if !SQLITE_ALLOW_COVERING_INDEX_SCAN +# error "Compile-time disabling of covering index scan using the\ + -DSQLITE_ALLOW_COVERING_INDEX_SCAN=0 option is deprecated.\ + Contact SQLite developers if this is a problem for you, and\ + delete this #error macro to continue with your build." +# endif +#endif + +/* The minimum PMA size is set to this value multiplied by the database +** page size in bytes. +*/ +#ifndef SQLITE_SORTER_PMASZ +# define SQLITE_SORTER_PMASZ 250 +#endif + +/* Statement journals spill to disk when their size exceeds the following +** threshold (in bytes). 0 means that statement journals are created and +** written to disk immediately (the default behavior for SQLite versions +** before 3.12.0). -1 means always keep the entire statement journal in +** memory. (The statement journal is also always held entirely in memory +** if journal_mode=MEMORY or if temp_store=MEMORY, regardless of this +** setting.) +*/ +#ifndef SQLITE_STMTJRNL_SPILL +# define SQLITE_STMTJRNL_SPILL (64*1024) +#endif + +/* +** The default lookaside-configuration, the format "SZ,N". SZ is the +** number of bytes in each lookaside slot (should be a multiple of 8) +** and N is the number of slots. The lookaside-configuration can be +** changed as start-time using sqlite3_config(SQLITE_CONFIG_LOOKASIDE) +** or at run-time for an individual database connection using +** sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE); +** +** With the two-size-lookaside enhancement, less lookaside is required. +** The default configuration of 1200,40 actually provides 30 1200-byte slots +** and 93 128-byte slots, which is more lookaside than is available +** using the older 1200,100 configuration without two-size-lookaside. +*/ +#ifndef SQLITE_DEFAULT_LOOKASIDE +# ifdef SQLITE_OMIT_TWOSIZE_LOOKASIDE +# define SQLITE_DEFAULT_LOOKASIDE 1200,100 /* 120KB of memory */ +# else +# define SQLITE_DEFAULT_LOOKASIDE 1200,40 /* 48KB of memory */ +# endif +#endif + + +/* The default maximum size of an in-memory database created using +** sqlite3_deserialize() +*/ +#ifndef SQLITE_MEMDB_DEFAULT_MAXSIZE +# define SQLITE_MEMDB_DEFAULT_MAXSIZE 1073741824 +#endif + +/* +** The following singleton contains the global configuration for +** the SQLite library. +*/ +SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { + SQLITE_DEFAULT_MEMSTATUS, /* bMemstat */ + 1, /* bCoreMutex */ + SQLITE_THREADSAFE==1, /* bFullMutex */ + SQLITE_USE_URI, /* bOpenUri */ + SQLITE_ALLOW_COVERING_INDEX_SCAN, /* bUseCis */ + 0, /* bSmallMalloc */ + 1, /* bExtraSchemaChecks */ + sizeof(LONGDOUBLE_TYPE)>8, /* bUseLongDouble */ + 0x7ffffffe, /* mxStrlen */ + 0, /* neverCorrupt */ + SQLITE_DEFAULT_LOOKASIDE, /* szLookaside, nLookaside */ + SQLITE_STMTJRNL_SPILL, /* nStmtSpill */ + {0,0,0,0,0,0,0,0}, /* m */ + {0,0,0,0,0,0,0,0,0}, /* mutex */ + {0,0,0,0,0,0,0,0,0,0,0,0,0},/* pcache2 */ + (void*)0, /* pHeap */ + 0, /* nHeap */ + 0, 0, /* mnHeap, mxHeap */ + SQLITE_DEFAULT_MMAP_SIZE, /* szMmap */ + SQLITE_MAX_MMAP_SIZE, /* mxMmap */ + (void*)0, /* pPage */ + 0, /* szPage */ + SQLITE_DEFAULT_PCACHE_INITSZ, /* nPage */ + 0, /* mxParserStack */ + 0, /* sharedCacheEnabled */ + SQLITE_SORTER_PMASZ, /* szPma */ + /* All the rest should always be initialized to zero */ + 0, /* isInit */ + 0, /* inProgress */ + 0, /* isMutexInit */ + 0, /* isMallocInit */ + 0, /* isPCacheInit */ + 0, /* nRefInitMutex */ + 0, /* pInitMutex */ + 0, /* xLog */ + 0, /* pLogArg */ +#ifdef SQLITE_ENABLE_SQLLOG + 0, /* xSqllog */ + 0, /* pSqllogArg */ +#endif +#ifdef SQLITE_VDBE_COVERAGE + 0, /* xVdbeBranch */ + 0, /* pVbeBranchArg */ +#endif +#ifndef SQLITE_OMIT_DESERIALIZE + SQLITE_MEMDB_DEFAULT_MAXSIZE, /* mxMemdbSize */ +#endif +#ifndef SQLITE_UNTESTABLE + 0, /* xTestCallback */ +#endif + 0, /* bLocaltimeFault */ + 0, /* xAltLocaltime */ + 0x7ffffffe, /* iOnceResetThreshold */ + SQLITE_DEFAULT_SORTERREF_SIZE, /* szSorterRef */ + 0, /* iPrngSeed */ +#ifdef SQLITE_DEBUG + {0,0,0,0,0,0}, /* aTune */ +#endif +}; + +/* +** Hash table for global functions - functions common to all +** database connections. After initialization, this table is +** read-only. +*/ +SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions; + +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) +/* +** Counter used for coverage testing. Does not come into play for +** release builds. +** +** Access to this global variable is not mutex protected. This might +** result in TSAN warnings. But as the variable does not exist in +** release builds, that should not be a concern. +*/ +SQLITE_PRIVATE unsigned int sqlite3CoverageCounter; +#endif /* SQLITE_COVERAGE_TEST || SQLITE_DEBUG */ + +#ifdef VDBE_PROFILE +/* +** The following performance counter can be used in place of +** sqlite3Hwtime() for profiling. This is a no-op on standard builds. +*/ +SQLITE_PRIVATE sqlite3_uint64 sqlite3NProfileCnt = 0; +#endif + +/* +** The value of the "pending" byte must be 0x40000000 (1 byte past the +** 1-gibabyte boundary) in a compatible database. SQLite never uses +** the database page that contains the pending byte. It never attempts +** to read or write that page. The pending byte page is set aside +** for use by the VFS layers as space for managing file locks. +** +** During testing, it is often desirable to move the pending byte to +** a different position in the file. This allows code that has to +** deal with the pending byte to run on files that are much smaller +** than 1 GiB. The sqlite3_test_control() interface can be used to +** move the pending byte. +** +** IMPORTANT: Changing the pending byte to any value other than +** 0x40000000 results in an incompatible database file format! +** Changing the pending byte during operation will result in undefined +** and incorrect behavior. +*/ +#ifndef SQLITE_OMIT_WSD +SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000; +#endif + +/* +** Tracing flags set by SQLITE_TESTCTRL_TRACEFLAGS. +*/ +SQLITE_PRIVATE u32 sqlite3TreeTrace = 0; +SQLITE_PRIVATE u32 sqlite3WhereTrace = 0; + +/* #include "opcodes.h" */ +/* +** Properties of opcodes. The OPFLG_INITIALIZER macro is +** created by mkopcodeh.awk during compilation. Data is obtained +** from the comments following the "case OP_xxxx:" statements in +** the vdbe.c file. +*/ +SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[] = OPFLG_INITIALIZER; + +/* +** Name of the default collating sequence +*/ +SQLITE_PRIVATE const char sqlite3StrBINARY[] = "BINARY"; + +/* +** Standard typenames. These names must match the COLTYPE_* definitions. +** Adjust the SQLITE_N_STDTYPE value if adding or removing entries. +** +** sqlite3StdType[] The actual names of the datatypes. +** +** sqlite3StdTypeLen[] The length (in bytes) of each entry +** in sqlite3StdType[]. +** +** sqlite3StdTypeAffinity[] The affinity associated with each entry +** in sqlite3StdType[]. +*/ +SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[] = { 3, 4, 3, 7, 4, 4 }; +SQLITE_PRIVATE const char sqlite3StdTypeAffinity[] = { + SQLITE_AFF_NUMERIC, + SQLITE_AFF_BLOB, + SQLITE_AFF_INTEGER, + SQLITE_AFF_INTEGER, + SQLITE_AFF_REAL, + SQLITE_AFF_TEXT +}; +SQLITE_PRIVATE const char *sqlite3StdType[] = { + "ANY", + "BLOB", + "INT", + "INTEGER", + "REAL", + "TEXT" +}; + +/************** End of global.c **********************************************/ +/************** Begin file status.c ******************************************/ +/* +** 2008 June 18 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This module implements the sqlite3_status() interface and related +** functionality. +*/ +/* #include "sqliteInt.h" */ +/************** Include vdbeInt.h in the middle of status.c ******************/ +/************** Begin file vdbeInt.h *****************************************/ +/* +** 2003 September 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the header file for information that is private to the +** VDBE. This information used to all be at the top of the single +** source code file "vdbe.c". When that file became too big (over +** 6000 lines long) it was split up into several smaller files and +** this header information was factored out. +*/ +#ifndef SQLITE_VDBEINT_H +#define SQLITE_VDBEINT_H + +/* +** The maximum number of times that a statement will try to reparse +** itself before giving up and returning SQLITE_SCHEMA. +*/ +#ifndef SQLITE_MAX_SCHEMA_RETRY +# define SQLITE_MAX_SCHEMA_RETRY 50 +#endif + +/* +** VDBE_DISPLAY_P4 is true or false depending on whether or not the +** "explain" P4 display logic is enabled. +*/ +#if !defined(SQLITE_OMIT_EXPLAIN) || !defined(NDEBUG) \ + || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) \ + || defined(SQLITE_ENABLE_BYTECODE_VTAB) +# define VDBE_DISPLAY_P4 1 +#else +# define VDBE_DISPLAY_P4 0 +#endif + +/* +** SQL is translated into a sequence of instructions to be +** executed by a virtual machine. Each instruction is an instance +** of the following structure. +*/ +typedef struct VdbeOp Op; + +/* +** Boolean values +*/ +typedef unsigned Bool; + +/* Opaque type used by code in vdbesort.c */ +typedef struct VdbeSorter VdbeSorter; + +/* Elements of the linked list at Vdbe.pAuxData */ +typedef struct AuxData AuxData; + +/* A cache of large TEXT or BLOB values in a VdbeCursor */ +typedef struct VdbeTxtBlbCache VdbeTxtBlbCache; + +/* Types of VDBE cursors */ +#define CURTYPE_BTREE 0 +#define CURTYPE_SORTER 1 +#define CURTYPE_VTAB 2 +#define CURTYPE_PSEUDO 3 + +/* +** A VdbeCursor is an superclass (a wrapper) for various cursor objects: +** +** * A b-tree cursor +** - In the main database or in an ephemeral database +** - On either an index or a table +** * A sorter +** * A virtual table +** * A one-row "pseudotable" stored in a single register +*/ +typedef struct VdbeCursor VdbeCursor; +struct VdbeCursor { + u8 eCurType; /* One of the CURTYPE_* values above */ + i8 iDb; /* Index of cursor database in db->aDb[] */ + u8 nullRow; /* True if pointing to a row with no data */ + u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ + u8 isTable; /* True for rowid tables. False for indexes */ +#ifdef SQLITE_DEBUG + u8 seekOp; /* Most recent seek operation on this cursor */ + u8 wrFlag; /* The wrFlag argument to sqlite3BtreeCursor() */ +#endif + Bool isEphemeral:1; /* True for an ephemeral table */ + Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ + Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ + Bool noReuse:1; /* OpenEphemeral may not reuse this cursor */ + Bool colCache:1; /* pCache pointer is initialized and non-NULL */ + u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */ + union { /* pBtx for isEphermeral. pAltMap otherwise */ + Btree *pBtx; /* Separate file holding temporary table */ + u32 *aAltMap; /* Mapping from table to index column numbers */ + } ub; + i64 seqCount; /* Sequence counter */ + + /* Cached OP_Column parse information is only valid if cacheStatus matches + ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of + ** CACHE_STALE (0) and so setting cacheStatus=CACHE_STALE guarantees that + ** the cache is out of date. */ + u32 cacheStatus; /* Cache is valid if this matches Vdbe.cacheCtr */ + int seekResult; /* Result of previous sqlite3BtreeMoveto() or 0 + ** if there have been no prior seeks on the cursor. */ + /* seekResult does not distinguish between "no seeks have ever occurred + ** on this cursor" and "the most recent seek was an exact match". + ** For CURTYPE_PSEUDO, seekResult is the register holding the record */ + + /* When a new VdbeCursor is allocated, only the fields above are zeroed. + ** The fields that follow are uninitialized, and must be individually + ** initialized prior to first use. */ + VdbeCursor *pAltCursor; /* Associated index cursor from which to read */ + union { + BtCursor *pCursor; /* CURTYPE_BTREE or _PSEUDO. Btree cursor */ + sqlite3_vtab_cursor *pVCur; /* CURTYPE_VTAB. Vtab cursor */ + VdbeSorter *pSorter; /* CURTYPE_SORTER. Sorter object */ + } uc; + KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */ + u32 iHdrOffset; /* Offset to next unparsed byte of the header */ + Pgno pgnoRoot; /* Root page of the open btree cursor */ + i16 nField; /* Number of fields in the header */ + u16 nHdrParsed; /* Number of header fields parsed so far */ + i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */ + u32 *aOffset; /* Pointer to aType[nField] */ + const u8 *aRow; /* Data for the current row, if all on one page */ + u32 payloadSize; /* Total number of bytes in the record */ + u32 szRow; /* Byte available in aRow */ +#ifdef SQLITE_ENABLE_COLUMN_USED_MASK + u64 maskUsed; /* Mask of columns used by this cursor */ +#endif + VdbeTxtBlbCache *pCache; /* Cache of large TEXT or BLOB values */ + + /* 2*nField extra array elements allocated for aType[], beyond the one + ** static element declared in the structure. nField total array slots for + ** aType[] and nField+1 array slots for aOffset[] */ + u32 aType[1]; /* Type values record decode. MUST BE LAST */ +}; + +/* Return true if P is a null-only cursor +*/ +#define IsNullCursor(P) \ + ((P)->eCurType==CURTYPE_PSEUDO && (P)->nullRow && (P)->seekResult==0) + +/* +** A value for VdbeCursor.cacheStatus that means the cache is always invalid. +*/ +#define CACHE_STALE 0 + +/* +** Large TEXT or BLOB values can be slow to load, so we want to avoid +** loading them more than once. For that reason, large TEXT and BLOB values +** can be stored in a cache defined by this object, and attached to the +** VdbeCursor using the pCache field. +*/ +struct VdbeTxtBlbCache { + char *pCValue; /* A RCStr buffer to hold the value */ + i64 iOffset; /* File offset of the row being cached */ + int iCol; /* Column for which the cache is valid */ + u32 cacheStatus; /* Vdbe.cacheCtr value */ + u32 colCacheCtr; /* Column cache counter */ +}; + +/* +** When a sub-program is executed (OP_Program), a structure of this type +** is allocated to store the current value of the program counter, as +** well as the current memory cell array and various other frame specific +** values stored in the Vdbe struct. When the sub-program is finished, +** these values are copied back to the Vdbe from the VdbeFrame structure, +** restoring the state of the VM to as it was before the sub-program +** began executing. +** +** The memory for a VdbeFrame object is allocated and managed by a memory +** cell in the parent (calling) frame. When the memory cell is deleted or +** overwritten, the VdbeFrame object is not freed immediately. Instead, it +** is linked into the Vdbe.pDelFrame list. The contents of the Vdbe.pDelFrame +** list is deleted when the VM is reset in VdbeHalt(). The reason for doing +** this instead of deleting the VdbeFrame immediately is to avoid recursive +** calls to sqlite3VdbeMemRelease() when the memory cells belonging to the +** child frame are released. +** +** The currently executing frame is stored in Vdbe.pFrame. Vdbe.pFrame is +** set to NULL if the currently executing frame is the main program. +*/ +typedef struct VdbeFrame VdbeFrame; +struct VdbeFrame { + Vdbe *v; /* VM this frame belongs to */ + VdbeFrame *pParent; /* Parent of this frame, or NULL if parent is main */ + Op *aOp; /* Program instructions for parent frame */ + Mem *aMem; /* Array of memory cells for parent frame */ + VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ + u8 *aOnce; /* Bitmask used by OP_Once */ + void *token; /* Copy of SubProgram.token */ + i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ + AuxData *pAuxData; /* Linked list of auxdata allocations */ +#if SQLITE_DEBUG + u32 iFrameMagic; /* magic number for sanity checking */ +#endif + int nCursor; /* Number of entries in apCsr */ + int pc; /* Program Counter in parent (calling) frame */ + int nOp; /* Size of aOp array */ + int nMem; /* Number of entries in aMem */ + int nChildMem; /* Number of memory cells for child frame */ + int nChildCsr; /* Number of cursors for child frame */ + i64 nChange; /* Statement changes (Vdbe.nChange) */ + i64 nDbChange; /* Value of db->nChange */ +}; + +/* Magic number for sanity checking on VdbeFrame objects */ +#define SQLITE_FRAME_MAGIC 0x879fb71e + +/* +** Return a pointer to the array of registers allocated for use +** by a VdbeFrame. +*/ +#define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) + +/* +** Internally, the vdbe manipulates nearly all SQL values as Mem +** structures. Each Mem struct may cache multiple representations (string, +** integer etc.) of the same value. +*/ +struct sqlite3_value { + union MemValue { + double r; /* Real value used when MEM_Real is set in flags */ + i64 i; /* Integer value used when MEM_Int is set in flags */ + int nZero; /* Extra zero bytes when MEM_Zero and MEM_Blob set */ + const char *zPType; /* Pointer type when MEM_Term|MEM_Subtype|MEM_Null */ + FuncDef *pDef; /* Used only when flags==MEM_Agg */ + } u; + char *z; /* String or BLOB value */ + int n; /* Number of characters in string value, excluding '\0' */ + u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */ + u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */ + u8 eSubtype; /* Subtype for this value */ + /* ShallowCopy only needs to copy the information above */ + sqlite3 *db; /* The associated database connection */ + int szMalloc; /* Size of the zMalloc allocation */ + u32 uTemp; /* Transient storage for serial_type in OP_MakeRecord */ + char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */ + void (*xDel)(void*);/* Destructor for Mem.z - only valid if MEM_Dyn */ +#ifdef SQLITE_DEBUG + Mem *pScopyFrom; /* This Mem is a shallow copy of pScopyFrom */ + u16 mScopyFlags; /* flags value immediately after the shallow copy */ +#endif +}; + +/* +** Size of struct Mem not including the Mem.zMalloc member or anything that +** follows. +*/ +#define MEMCELLSIZE offsetof(Mem,db) + +/* One or more of the following flags are set to indicate the +** representations of the value stored in the Mem struct. +** +** * MEM_Null An SQL NULL value +** +** * MEM_Null|MEM_Zero An SQL NULL with the virtual table +** UPDATE no-change flag set +** +** * MEM_Null|MEM_Term| An SQL NULL, but also contains a +** MEM_Subtype pointer accessible using +** sqlite3_value_pointer(). +** +** * MEM_Null|MEM_Cleared Special SQL NULL that compares non-equal +** to other NULLs even using the IS operator. +** +** * MEM_Str A string, stored in Mem.z with +** length Mem.n. Zero-terminated if +** MEM_Term is set. This flag is +** incompatible with MEM_Blob and +** MEM_Null, but can appear with MEM_Int, +** MEM_Real, and MEM_IntReal. +** +** * MEM_Blob A blob, stored in Mem.z length Mem.n. +** Incompatible with MEM_Str, MEM_Null, +** MEM_Int, MEM_Real, and MEM_IntReal. +** +** * MEM_Blob|MEM_Zero A blob in Mem.z of length Mem.n plus +** MEM.u.i extra 0x00 bytes at the end. +** +** * MEM_Int Integer stored in Mem.u.i. +** +** * MEM_Real Real stored in Mem.u.r. +** +** * MEM_IntReal Real stored as an integer in Mem.u.i. +** +** If the MEM_Null flag is set, then the value is an SQL NULL value. +** For a pointer type created using sqlite3_bind_pointer() or +** sqlite3_result_pointer() the MEM_Term and MEM_Subtype flags are also set. +** +** If the MEM_Str flag is set then Mem.z points at a string representation. +** Usually this is encoded in the same unicode encoding as the main +** database (see below for exceptions). If the MEM_Term flag is also +** set, then the string is nul terminated. The MEM_Int and MEM_Real +** flags may coexist with the MEM_Str flag. +*/ +#define MEM_Undefined 0x0000 /* Value is undefined */ +#define MEM_Null 0x0001 /* Value is NULL (or a pointer) */ +#define MEM_Str 0x0002 /* Value is a string */ +#define MEM_Int 0x0004 /* Value is an integer */ +#define MEM_Real 0x0008 /* Value is a real number */ +#define MEM_Blob 0x0010 /* Value is a BLOB */ +#define MEM_IntReal 0x0020 /* MEM_Int that stringifies like MEM_Real */ +#define MEM_AffMask 0x003f /* Mask of affinity bits */ + +/* Extra bits that modify the meanings of the core datatypes above +*/ +#define MEM_FromBind 0x0040 /* Value originates from sqlite3_bind() */ + /* 0x0080 // Available */ +#define MEM_Cleared 0x0100 /* NULL set by OP_Null, not from data */ +#define MEM_Term 0x0200 /* String in Mem.z is zero terminated */ +#define MEM_Zero 0x0400 /* Mem.i contains count of 0s appended to blob */ +#define MEM_Subtype 0x0800 /* Mem.eSubtype is valid */ +#define MEM_TypeMask 0x0dbf /* Mask of type bits */ + +/* Bits that determine the storage for Mem.z for a string or blob or +** aggregate accumulator. +*/ +#define MEM_Dyn 0x1000 /* Need to call Mem.xDel() on Mem.z */ +#define MEM_Static 0x2000 /* Mem.z points to a static string */ +#define MEM_Ephem 0x4000 /* Mem.z points to an ephemeral string */ +#define MEM_Agg 0x8000 /* Mem.z points to an agg function context */ + +/* Return TRUE if Mem X contains dynamically allocated content - anything +** that needs to be deallocated to avoid a leak. +*/ +#define VdbeMemDynamic(X) \ + (((X)->flags&(MEM_Agg|MEM_Dyn))!=0) + +/* +** Clear any existing type flags from a Mem and replace them with f +*/ +#define MemSetTypeFlag(p, f) \ + ((p)->flags = ((p)->flags&~(MEM_TypeMask|MEM_Zero))|f) + +/* +** True if Mem X is a NULL-nochng type. +*/ +#define MemNullNochng(X) \ + (((X)->flags&MEM_TypeMask)==(MEM_Null|MEM_Zero) \ + && (X)->n==0 && (X)->u.nZero==0) + +/* +** Return true if a memory cell has been initialized and is valid. +** is for use inside assert() statements only. +** +** A Memory cell is initialized if at least one of the +** MEM_Null, MEM_Str, MEM_Int, MEM_Real, MEM_Blob, or MEM_IntReal bits +** is set. It is "undefined" if all those bits are zero. +*/ +#ifdef SQLITE_DEBUG +#define memIsValid(M) ((M)->flags & MEM_AffMask)!=0 +#endif + +/* +** Each auxiliary data pointer stored by a user defined function +** implementation calling sqlite3_set_auxdata() is stored in an instance +** of this structure. All such structures associated with a single VM +** are stored in a linked list headed at Vdbe.pAuxData. All are destroyed +** when the VM is halted (if not before). +*/ +struct AuxData { + int iAuxOp; /* Instruction number of OP_Function opcode */ + int iAuxArg; /* Index of function argument. */ + void *pAux; /* Aux data pointer */ + void (*xDeleteAux)(void*); /* Destructor for the aux data */ + AuxData *pNextAux; /* Next element in list */ +}; + +/* +** The "context" argument for an installable function. A pointer to an +** instance of this structure is the first argument to the routines used +** implement the SQL functions. +** +** There is a typedef for this structure in sqlite.h. So all routines, +** even the public interface to SQLite, can use a pointer to this structure. +** But this file is the only place where the internal details of this +** structure are known. +** +** This structure is defined inside of vdbeInt.h because it uses substructures +** (Mem) which are only defined there. +*/ +struct sqlite3_context { + Mem *pOut; /* The return value is stored here */ + FuncDef *pFunc; /* Pointer to function information */ + Mem *pMem; /* Memory cell used to store aggregate context */ + Vdbe *pVdbe; /* The VM that owns this context */ + int iOp; /* Instruction number of OP_Function */ + int isError; /* Error code returned by the function. */ + u8 enc; /* Encoding to use for results */ + u8 skipFlag; /* Skip accumulator loading if true */ + u8 argc; /* Number of arguments */ + sqlite3_value *argv[1]; /* Argument set */ +}; + +/* A bitfield type for use inside of structures. Always follow with :N where +** N is the number of bits. +*/ +typedef unsigned bft; /* Bit Field Type */ + +/* The ScanStatus object holds a single value for the +** sqlite3_stmt_scanstatus() interface. +** +** aAddrRange[]: +** This array is used by ScanStatus elements associated with EQP +** notes that make an SQLITE_SCANSTAT_NCYCLE value available. It is +** an array of up to 3 ranges of VM addresses for which the Vdbe.anCycle[] +** values should be summed to calculate the NCYCLE value. Each pair of +** integer addresses is a start and end address (both inclusive) for a range +** instructions. A start value of 0 indicates an empty range. +*/ +typedef struct ScanStatus ScanStatus; +struct ScanStatus { + int addrExplain; /* OP_Explain for loop */ + int aAddrRange[6]; + int addrLoop; /* Address of "loops" counter */ + int addrVisit; /* Address of "rows visited" counter */ + int iSelectID; /* The "Select-ID" for this loop */ + LogEst nEst; /* Estimated output rows per loop */ + char *zName; /* Name of table or index */ +}; + +/* The DblquoteStr object holds the text of a double-quoted +** string for a prepared statement. A linked list of these objects +** is constructed during statement parsing and is held on Vdbe.pDblStr. +** When computing a normalized SQL statement for an SQL statement, that +** list is consulted for each double-quoted identifier to see if the +** identifier should really be a string literal. +*/ +typedef struct DblquoteStr DblquoteStr; +struct DblquoteStr { + DblquoteStr *pNextStr; /* Next string literal in the list */ + char z[8]; /* Dequoted value for the string */ +}; + +/* +** An instance of the virtual machine. This structure contains the complete +** state of the virtual machine. +** +** The "sqlite3_stmt" structure pointer that is returned by sqlite3_prepare() +** is really a pointer to an instance of this structure. +*/ +struct Vdbe { + sqlite3 *db; /* The database connection that owns this statement */ + Vdbe **ppVPrev,*pVNext; /* Linked list of VDBEs with the same Vdbe.db */ + Parse *pParse; /* Parsing context used to create this Vdbe */ + ynVar nVar; /* Number of entries in aVar[] */ + int nMem; /* Number of memory locations currently allocated */ + int nCursor; /* Number of slots in apCsr[] */ + u32 cacheCtr; /* VdbeCursor row cache generation counter */ + int pc; /* The program counter */ + int rc; /* Value to return */ + i64 nChange; /* Number of db changes made since last reset */ + int iStatement; /* Statement number (or 0 if has no opened stmt) */ + i64 iCurrentTime; /* Value of julianday('now') for this statement */ + i64 nFkConstraint; /* Number of imm. FK constraints this VM */ + i64 nStmtDefCons; /* Number of def. constraints when stmt started */ + i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */ + Mem *aMem; /* The memory locations */ + Mem **apArg; /* Arguments to currently executing user function */ + VdbeCursor **apCsr; /* One element of this array for each open cursor */ + Mem *aVar; /* Values for the OP_Variable opcode. */ + + /* When allocating a new Vdbe object, all of the fields below should be + ** initialized to zero or NULL */ + + Op *aOp; /* Space to hold the virtual machine's program */ + int nOp; /* Number of instructions in the program */ + int nOpAlloc; /* Slots allocated for aOp[] */ + Mem *aColName; /* Column names to return */ + Mem *pResultRow; /* Current output row */ + char *zErrMsg; /* Error message written here */ + VList *pVList; /* Name of variables */ +#ifndef SQLITE_OMIT_TRACE + i64 startTime; /* Time when query started - used for profiling */ +#endif +#ifdef SQLITE_DEBUG + int rcApp; /* errcode set by sqlite3_result_error_code() */ + u32 nWrite; /* Number of write operations that have occurred */ +#endif + u16 nResColumn; /* Number of columns in one row of the result set */ + u16 nResAlloc; /* Column slots allocated to aColName[] */ + u8 errorAction; /* Recovery action to do in case of an error */ + u8 minWriteFileFormat; /* Minimum file format for writable database files */ + u8 prepFlags; /* SQLITE_PREPARE_* flags */ + u8 eVdbeState; /* On of the VDBE_*_STATE values */ + bft expired:2; /* 1: recompile VM immediately 2: when convenient */ + bft explain:2; /* 0: normal, 1: EXPLAIN, 2: EXPLAIN QUERY PLAN */ + bft changeCntOn:1; /* True to update the change-counter */ + bft usesStmtJournal:1; /* True if uses a statement journal */ + bft readOnly:1; /* True for statements that do not write */ + bft bIsReader:1; /* True for statements that read */ + bft haveEqpOps:1; /* Bytecode supports EXPLAIN QUERY PLAN */ + yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ + yDbMask lockMask; /* Subset of btreeMask that requires a lock */ + u32 aCounter[9]; /* Counters used by sqlite3_stmt_status() */ + char *zSql; /* Text of the SQL statement that generated this */ +#ifdef SQLITE_ENABLE_NORMALIZE + char *zNormSql; /* Normalization of the associated SQL statement */ + DblquoteStr *pDblStr; /* List of double-quoted string literals */ +#endif + void *pFree; /* Free this when deleting the vdbe */ + VdbeFrame *pFrame; /* Parent frame */ + VdbeFrame *pDelFrame; /* List of frame objects to free on VM reset */ + int nFrame; /* Number of frames in pFrame list */ + u32 expmask; /* Binding to these vars invalidates VM */ + SubProgram *pProgram; /* Linked list of all sub-programs used by VM */ + AuxData *pAuxData; /* Linked list of auxdata allocations */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int nScan; /* Entries in aScan[] */ + ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */ +#endif +}; + +/* +** The following are allowed values for Vdbe.eVdbeState +*/ +#define VDBE_INIT_STATE 0 /* Prepared statement under construction */ +#define VDBE_READY_STATE 1 /* Ready to run but not yet started */ +#define VDBE_RUN_STATE 2 /* Run in progress */ +#define VDBE_HALT_STATE 3 /* Finished. Need reset() or finalize() */ + +/* +** Structure used to store the context required by the +** sqlite3_preupdate_*() API functions. +*/ +struct PreUpdate { + Vdbe *v; + VdbeCursor *pCsr; /* Cursor to read old values from */ + int op; /* One of SQLITE_INSERT, UPDATE, DELETE */ + u8 *aRecord; /* old.* database record */ + KeyInfo keyinfo; + UnpackedRecord *pUnpacked; /* Unpacked version of aRecord[] */ + UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */ + int iNewReg; /* Register for new.* values */ + int iBlobWrite; /* Value returned by preupdate_blobwrite() */ + i64 iKey1; /* First key value passed to hook */ + i64 iKey2; /* Second key value passed to hook */ + Mem *aNew; /* Array of new.* values */ + Table *pTab; /* Schema object being updated */ + Index *pPk; /* PK index if pTab is WITHOUT ROWID */ +}; + +/* +** An instance of this object is used to pass an vector of values into +** OP_VFilter, the xFilter method of a virtual table. The vector is the +** set of values on the right-hand side of an IN constraint. +** +** The value as passed into xFilter is an sqlite3_value with a "pointer" +** type, such as is generated by sqlite3_result_pointer() and read by +** sqlite3_value_pointer. Such values have MEM_Term|MEM_Subtype|MEM_Null +** and a subtype of 'p'. The sqlite3_vtab_in_first() and _next() interfaces +** know how to use this object to step through all the values in the +** right operand of the IN constraint. +*/ +typedef struct ValueList ValueList; +struct ValueList { + BtCursor *pCsr; /* An ephemeral table holding all values */ + sqlite3_value *pOut; /* Register to hold each decoded output value */ +}; + +/* Size of content associated with serial types that fit into a +** single-byte varint. +*/ +#ifndef SQLITE_AMALGAMATION +SQLITE_PRIVATE const u8 sqlite3SmallTypeSizes[]; +#endif + +/* +** Function prototypes +*/ +SQLITE_PRIVATE void sqlite3VdbeError(Vdbe*, const char *, ...); +SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*); +SQLITE_PRIVATE void sqlite3VdbeFreeCursorNN(Vdbe*,VdbeCursor*); +void sqliteVdbePopStack(Vdbe*,int); +SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeHandleMovedCursor(VdbeCursor *p); +SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor*); +SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor*); +SQLITE_PRIVATE u32 sqlite3VdbeSerialTypeLen(u32); +SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8); +#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT +SQLITE_PRIVATE u64 sqlite3FloatSwap(u64 in); +# define swapMixedEndianFloat(X) X = sqlite3FloatSwap(X) +#else +# define swapMixedEndianFloat(X) +#endif +SQLITE_PRIVATE void sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*); +SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3*, AuxData**, int, int); + +int sqlite2BtreeKeyCompare(BtCursor *, const void *, int, int, int *); +SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(sqlite3*,VdbeCursor*,UnpackedRecord*,int*); +SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3*, BtCursor*, i64*); +SQLITE_PRIVATE int sqlite3VdbeExec(Vdbe*); +#if !defined(SQLITE_OMIT_EXPLAIN) || defined(SQLITE_ENABLE_BYTECODE_VTAB) +SQLITE_PRIVATE int sqlite3VdbeNextOpcode(Vdbe*,Mem*,int,int*,int*,Op**); +SQLITE_PRIVATE char *sqlite3VdbeDisplayP4(sqlite3*,Op*); +#endif +#if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) +SQLITE_PRIVATE char *sqlite3VdbeDisplayComment(sqlite3*,const Op*,const char*); +#endif +#if !defined(SQLITE_OMIT_EXPLAIN) +SQLITE_PRIVATE int sqlite3VdbeList(Vdbe*); +#endif +SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe*); +SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *, int); +SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem*, const Mem*); +SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int); +SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem*, Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemSetStr(Mem*, const char*, i64, u8, void(*)(void*)); +SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem*, i64); +#ifdef SQLITE_OMIT_FLOATING_POINT +# define sqlite3VdbeMemSetDouble sqlite3VdbeMemSetInt64 +#else +SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem*, double); +#endif +SQLITE_PRIVATE void sqlite3VdbeMemSetPointer(Mem*, void*, const char*, void(*)(void*)); +SQLITE_PRIVATE void sqlite3VdbeMemInit(Mem*,sqlite3*,u16); +SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem*); +#ifndef SQLITE_OMIT_INCRBLOB +SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem*,int); +#else +SQLITE_PRIVATE int sqlite3VdbeMemSetZeroBlob(Mem*,int); +#endif +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3VdbeMemIsRowSet(const Mem*); +#endif +SQLITE_PRIVATE int sqlite3VdbeMemSetRowSet(Mem*); +SQLITE_PRIVATE void sqlite3VdbeMemZeroTerminateIfAble(Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem*, u8, u8); +SQLITE_PRIVATE int sqlite3IntFloatCompare(i64,double); +SQLITE_PRIVATE i64 sqlite3VdbeIntValue(const Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem*); +SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem*); +SQLITE_PRIVATE int sqlite3VdbeBooleanValue(Mem*, int ifNull); +SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemCast(Mem*,u8,u8); +SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemFromBtreeZeroOffset(BtCursor*,u32,Mem*); +SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p); +SQLITE_PRIVATE void sqlite3VdbeMemReleaseMalloc(Mem*p); +SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem*, FuncDef*); +#ifndef SQLITE_OMIT_WINDOWFUNC +SQLITE_PRIVATE int sqlite3VdbeMemAggValue(Mem*, Mem*, FuncDef*); +#endif +#if !defined(SQLITE_OMIT_EXPLAIN) || defined(SQLITE_ENABLE_BYTECODE_VTAB) +SQLITE_PRIVATE const char *sqlite3OpcodeName(int); +#endif +SQLITE_PRIVATE int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve); +SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int n); +SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *, int); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3VdbeFrameIsValid(VdbeFrame*); +#endif +SQLITE_PRIVATE void sqlite3VdbeFrameMemDel(void*); /* Destructor on Mem */ +SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame*); /* Actually deletes the Frame */ +SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( + Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int,int); +#endif +SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p); + +SQLITE_PRIVATE int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *); +SQLITE_PRIVATE void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *); +SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *, VdbeCursor *); +SQLITE_PRIVATE int sqlite3VdbeSorterRowkey(const VdbeCursor *, Mem *); +SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *, const VdbeCursor *); +SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *, int *); +SQLITE_PRIVATE int sqlite3VdbeSorterWrite(const VdbeCursor *, Mem *); +SQLITE_PRIVATE int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *); + +SQLITE_PRIVATE void sqlite3VdbeValueListFree(void*); + +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE void sqlite3VdbeIncrWriteCounter(Vdbe*, VdbeCursor*); +SQLITE_PRIVATE void sqlite3VdbeAssertAbortable(Vdbe*); +#else +# define sqlite3VdbeIncrWriteCounter(V,C) +# define sqlite3VdbeAssertAbortable(V) +#endif + +#if !defined(SQLITE_OMIT_SHARED_CACHE) +SQLITE_PRIVATE void sqlite3VdbeEnter(Vdbe*); +#else +# define sqlite3VdbeEnter(X) +#endif + +#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0 +SQLITE_PRIVATE void sqlite3VdbeLeave(Vdbe*); +#else +# define sqlite3VdbeLeave(X) +#endif + +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe*,Mem*); +SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem*); +#endif + +#ifndef SQLITE_OMIT_FOREIGN_KEY +SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *, int); +#else +# define sqlite3VdbeCheckFk(p,i) 0 +#endif + +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr); +#endif +#ifndef SQLITE_OMIT_UTF16 +SQLITE_PRIVATE int sqlite3VdbeMemTranslate(Mem*, u8); +SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem); +#endif + +#ifndef SQLITE_OMIT_INCRBLOB +SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *); + #define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlite3VdbeMemExpandBlob(P):0) +#else + #define sqlite3VdbeMemExpandBlob(x) SQLITE_OK + #define ExpandBlob(P) SQLITE_OK +#endif + +#endif /* !defined(SQLITE_VDBEINT_H) */ + +/************** End of vdbeInt.h *********************************************/ +/************** Continuing where we left off in status.c *********************/ + +/* +** Variables in which to record status information. +*/ +#if SQLITE_PTRSIZE>4 +typedef sqlite3_int64 sqlite3StatValueType; +#else +typedef u32 sqlite3StatValueType; +#endif +typedef struct sqlite3StatType sqlite3StatType; +static SQLITE_WSD struct sqlite3StatType { + sqlite3StatValueType nowValue[10]; /* Current value */ + sqlite3StatValueType mxValue[10]; /* Maximum value */ +} sqlite3Stat = { {0,}, {0,} }; + +/* +** Elements of sqlite3Stat[] are protected by either the memory allocator +** mutex, or by the pcache1 mutex. The following array determines which. +*/ +static const char statMutex[] = { + 0, /* SQLITE_STATUS_MEMORY_USED */ + 1, /* SQLITE_STATUS_PAGECACHE_USED */ + 1, /* SQLITE_STATUS_PAGECACHE_OVERFLOW */ + 0, /* SQLITE_STATUS_SCRATCH_USED */ + 0, /* SQLITE_STATUS_SCRATCH_OVERFLOW */ + 0, /* SQLITE_STATUS_MALLOC_SIZE */ + 0, /* SQLITE_STATUS_PARSER_STACK */ + 1, /* SQLITE_STATUS_PAGECACHE_SIZE */ + 0, /* SQLITE_STATUS_SCRATCH_SIZE */ + 0, /* SQLITE_STATUS_MALLOC_COUNT */ +}; + + +/* The "wsdStat" macro will resolve to the status information +** state vector. If writable static data is unsupported on the target, +** we have to locate the state vector at run-time. In the more common +** case where writable static data is supported, wsdStat can refer directly +** to the "sqlite3Stat" state vector declared above. +*/ +#ifdef SQLITE_OMIT_WSD +# define wsdStatInit sqlite3StatType *x = &GLOBAL(sqlite3StatType,sqlite3Stat) +# define wsdStat x[0] +#else +# define wsdStatInit +# define wsdStat sqlite3Stat +#endif + +/* +** Return the current value of a status parameter. The caller must +** be holding the appropriate mutex. +*/ +SQLITE_PRIVATE sqlite3_int64 sqlite3StatusValue(int op){ + wsdStatInit; + assert( op>=0 && op<ArraySize(wsdStat.nowValue) ); + assert( op>=0 && op<ArraySize(statMutex) ); + assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex() + : sqlite3MallocMutex()) ); + return wsdStat.nowValue[op]; +} + +/* +** Add N to the value of a status record. The caller must hold the +** appropriate mutex. (Locking is checked by assert()). +** +** The StatusUp() routine can accept positive or negative values for N. +** The value of N is added to the current status value and the high-water +** mark is adjusted if necessary. +** +** The StatusDown() routine lowers the current value by N. The highwater +** mark is unchanged. N must be non-negative for StatusDown(). +*/ +SQLITE_PRIVATE void sqlite3StatusUp(int op, int N){ + wsdStatInit; + assert( op>=0 && op<ArraySize(wsdStat.nowValue) ); + assert( op>=0 && op<ArraySize(statMutex) ); + assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex() + : sqlite3MallocMutex()) ); + wsdStat.nowValue[op] += N; + if( wsdStat.nowValue[op]>wsdStat.mxValue[op] ){ + wsdStat.mxValue[op] = wsdStat.nowValue[op]; + } +} +SQLITE_PRIVATE void sqlite3StatusDown(int op, int N){ + wsdStatInit; + assert( N>=0 ); + assert( op>=0 && op<ArraySize(statMutex) ); + assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex() + : sqlite3MallocMutex()) ); + assert( op>=0 && op<ArraySize(wsdStat.nowValue) ); + wsdStat.nowValue[op] -= N; +} + +/* +** Adjust the highwater mark if necessary. +** The caller must hold the appropriate mutex. +*/ +SQLITE_PRIVATE void sqlite3StatusHighwater(int op, int X){ + sqlite3StatValueType newValue; + wsdStatInit; + assert( X>=0 ); + newValue = (sqlite3StatValueType)X; + assert( op>=0 && op<ArraySize(wsdStat.nowValue) ); + assert( op>=0 && op<ArraySize(statMutex) ); + assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex() + : sqlite3MallocMutex()) ); + assert( op==SQLITE_STATUS_MALLOC_SIZE + || op==SQLITE_STATUS_PAGECACHE_SIZE + || op==SQLITE_STATUS_PARSER_STACK ); + if( newValue>wsdStat.mxValue[op] ){ + wsdStat.mxValue[op] = newValue; + } +} + +/* +** Query status information. +*/ +SQLITE_API int sqlite3_status64( + int op, + sqlite3_int64 *pCurrent, + sqlite3_int64 *pHighwater, + int resetFlag +){ + sqlite3_mutex *pMutex; + wsdStatInit; + if( op<0 || op>=ArraySize(wsdStat.nowValue) ){ + return SQLITE_MISUSE_BKPT; + } +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCurrent==0 || pHighwater==0 ) return SQLITE_MISUSE_BKPT; +#endif + pMutex = statMutex[op] ? sqlite3Pcache1Mutex() : sqlite3MallocMutex(); + sqlite3_mutex_enter(pMutex); + *pCurrent = wsdStat.nowValue[op]; + *pHighwater = wsdStat.mxValue[op]; + if( resetFlag ){ + wsdStat.mxValue[op] = wsdStat.nowValue[op]; + } + sqlite3_mutex_leave(pMutex); + (void)pMutex; /* Prevent warning when SQLITE_THREADSAFE=0 */ + return SQLITE_OK; +} +SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag){ + sqlite3_int64 iCur = 0, iHwtr = 0; + int rc; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCurrent==0 || pHighwater==0 ) return SQLITE_MISUSE_BKPT; +#endif + rc = sqlite3_status64(op, &iCur, &iHwtr, resetFlag); + if( rc==0 ){ + *pCurrent = (int)iCur; + *pHighwater = (int)iHwtr; + } + return rc; +} + +/* +** Return the number of LookasideSlot elements on the linked list +*/ +static u32 countLookasideSlots(LookasideSlot *p){ + u32 cnt = 0; + while( p ){ + p = p->pNext; + cnt++; + } + return cnt; +} + +/* +** Count the number of slots of lookaside memory that are outstanding +*/ +SQLITE_PRIVATE int sqlite3LookasideUsed(sqlite3 *db, int *pHighwater){ + u32 nInit = countLookasideSlots(db->lookaside.pInit); + u32 nFree = countLookasideSlots(db->lookaside.pFree); +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + nInit += countLookasideSlots(db->lookaside.pSmallInit); + nFree += countLookasideSlots(db->lookaside.pSmallFree); +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + if( pHighwater ) *pHighwater = db->lookaside.nSlot - nInit; + return db->lookaside.nSlot - (nInit+nFree); +} + +/* +** Query status information for a single database connection +*/ +SQLITE_API int sqlite3_db_status( + sqlite3 *db, /* The database connection whose status is desired */ + int op, /* Status verb */ + int *pCurrent, /* Write current value here */ + int *pHighwater, /* Write high-water mark here */ + int resetFlag /* Reset high-water mark if true */ +){ + int rc = SQLITE_OK; /* Return code */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + switch( op ){ + case SQLITE_DBSTATUS_LOOKASIDE_USED: { + *pCurrent = sqlite3LookasideUsed(db, pHighwater); + if( resetFlag ){ + LookasideSlot *p = db->lookaside.pFree; + if( p ){ + while( p->pNext ) p = p->pNext; + p->pNext = db->lookaside.pInit; + db->lookaside.pInit = db->lookaside.pFree; + db->lookaside.pFree = 0; + } +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + p = db->lookaside.pSmallFree; + if( p ){ + while( p->pNext ) p = p->pNext; + p->pNext = db->lookaside.pSmallInit; + db->lookaside.pSmallInit = db->lookaside.pSmallFree; + db->lookaside.pSmallFree = 0; + } +#endif + } + break; + } + + case SQLITE_DBSTATUS_LOOKASIDE_HIT: + case SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE: + case SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL: { + testcase( op==SQLITE_DBSTATUS_LOOKASIDE_HIT ); + testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE ); + testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL ); + assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)>=0 ); + assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)<3 ); + *pCurrent = 0; + *pHighwater = db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT]; + if( resetFlag ){ + db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT] = 0; + } + break; + } + + /* + ** Return an approximation for the amount of memory currently used + ** by all pagers associated with the given database connection. The + ** highwater mark is meaningless and is returned as zero. + */ + case SQLITE_DBSTATUS_CACHE_USED_SHARED: + case SQLITE_DBSTATUS_CACHE_USED: { + int totalUsed = 0; + int i; + sqlite3BtreeEnterAll(db); + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + Pager *pPager = sqlite3BtreePager(pBt); + int nByte = sqlite3PagerMemUsed(pPager); + if( op==SQLITE_DBSTATUS_CACHE_USED_SHARED ){ + nByte = nByte / sqlite3BtreeConnectionCount(pBt); + } + totalUsed += nByte; + } + } + sqlite3BtreeLeaveAll(db); + *pCurrent = totalUsed; + *pHighwater = 0; + break; + } + + /* + ** *pCurrent gets an accurate estimate of the amount of memory used + ** to store the schema for all databases (main, temp, and any ATTACHed + ** databases. *pHighwater is set to zero. + */ + case SQLITE_DBSTATUS_SCHEMA_USED: { + int i; /* Used to iterate through schemas */ + int nByte = 0; /* Used to accumulate return value */ + + sqlite3BtreeEnterAll(db); + db->pnBytesFreed = &nByte; + assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); + db->lookaside.pEnd = db->lookaside.pStart; + for(i=0; i<db->nDb; i++){ + Schema *pSchema = db->aDb[i].pSchema; + if( ALWAYS(pSchema!=0) ){ + HashElem *p; + + nByte += sqlite3GlobalConfig.m.xRoundup(sizeof(HashElem)) * ( + pSchema->tblHash.count + + pSchema->trigHash.count + + pSchema->idxHash.count + + pSchema->fkeyHash.count + ); + nByte += sqlite3_msize(pSchema->tblHash.ht); + nByte += sqlite3_msize(pSchema->trigHash.ht); + nByte += sqlite3_msize(pSchema->idxHash.ht); + nByte += sqlite3_msize(pSchema->fkeyHash.ht); + + for(p=sqliteHashFirst(&pSchema->trigHash); p; p=sqliteHashNext(p)){ + sqlite3DeleteTrigger(db, (Trigger*)sqliteHashData(p)); + } + for(p=sqliteHashFirst(&pSchema->tblHash); p; p=sqliteHashNext(p)){ + sqlite3DeleteTable(db, (Table *)sqliteHashData(p)); + } + } + } + db->pnBytesFreed = 0; + db->lookaside.pEnd = db->lookaside.pTrueEnd; + sqlite3BtreeLeaveAll(db); + + *pHighwater = 0; + *pCurrent = nByte; + break; + } + + /* + ** *pCurrent gets an accurate estimate of the amount of memory used + ** to store all prepared statements. + ** *pHighwater is set to zero. + */ + case SQLITE_DBSTATUS_STMT_USED: { + struct Vdbe *pVdbe; /* Used to iterate through VMs */ + int nByte = 0; /* Used to accumulate return value */ + + db->pnBytesFreed = &nByte; + assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); + db->lookaside.pEnd = db->lookaside.pStart; + for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pVNext){ + sqlite3VdbeDelete(pVdbe); + } + db->lookaside.pEnd = db->lookaside.pTrueEnd; + db->pnBytesFreed = 0; + + *pHighwater = 0; /* IMP: R-64479-57858 */ + *pCurrent = nByte; + + break; + } + + /* + ** Set *pCurrent to the total cache hits or misses encountered by all + ** pagers the database handle is connected to. *pHighwater is always set + ** to zero. + */ + case SQLITE_DBSTATUS_CACHE_SPILL: + op = SQLITE_DBSTATUS_CACHE_WRITE+1; + /* no break */ deliberate_fall_through + case SQLITE_DBSTATUS_CACHE_HIT: + case SQLITE_DBSTATUS_CACHE_MISS: + case SQLITE_DBSTATUS_CACHE_WRITE:{ + int i; + int nRet = 0; + assert( SQLITE_DBSTATUS_CACHE_MISS==SQLITE_DBSTATUS_CACHE_HIT+1 ); + assert( SQLITE_DBSTATUS_CACHE_WRITE==SQLITE_DBSTATUS_CACHE_HIT+2 ); + + for(i=0; i<db->nDb; i++){ + if( db->aDb[i].pBt ){ + Pager *pPager = sqlite3BtreePager(db->aDb[i].pBt); + sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet); + } + } + *pHighwater = 0; /* IMP: R-42420-56072 */ + /* IMP: R-54100-20147 */ + /* IMP: R-29431-39229 */ + *pCurrent = nRet; + break; + } + + /* Set *pCurrent to non-zero if there are unresolved deferred foreign + ** key constraints. Set *pCurrent to zero if all foreign key constraints + ** have been satisfied. The *pHighwater is always set to zero. + */ + case SQLITE_DBSTATUS_DEFERRED_FKS: { + *pHighwater = 0; /* IMP: R-11967-56545 */ + *pCurrent = db->nDeferredImmCons>0 || db->nDeferredCons>0; + break; + } + + default: { + rc = SQLITE_ERROR; + } + } + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/************** End of status.c **********************************************/ +/************** Begin file date.c ********************************************/ +/* +** 2003 October 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement date and time +** functions for SQLite. +** +** There is only one exported symbol in this file - the function +** sqlite3RegisterDateTimeFunctions() found at the bottom of the file. +** All other code has file scope. +** +** SQLite processes all times and dates as julian day numbers. The +** dates and times are stored as the number of days since noon +** in Greenwich on November 24, 4714 B.C. according to the Gregorian +** calendar system. +** +** 1970-01-01 00:00:00 is JD 2440587.5 +** 2000-01-01 00:00:00 is JD 2451544.5 +** +** This implementation requires years to be expressed as a 4-digit number +** which means that only dates between 0000-01-01 and 9999-12-31 can +** be represented, even though julian day numbers allow a much wider +** range of dates. +** +** The Gregorian calendar system is used for all dates and times, +** even those that predate the Gregorian calendar. Historians usually +** use the julian calendar for dates prior to 1582-10-15 and for some +** dates afterwards, depending on locale. Beware of this difference. +** +** The conversion algorithms are implemented based on descriptions +** in the following text: +** +** Jean Meeus +** Astronomical Algorithms, 2nd Edition, 1998 +** ISBN 0-943396-61-1 +** Willmann-Bell, Inc +** Richmond, Virginia (USA) +*/ +/* #include "sqliteInt.h" */ +/* #include <stdlib.h> */ +/* #include <assert.h> */ +#include <time.h> + +#ifndef SQLITE_OMIT_DATETIME_FUNCS + +/* +** The MSVC CRT on Windows CE may not have a localtime() function. +** So declare a substitute. The substitute function itself is +** defined in "os_win.c". +*/ +#if !defined(SQLITE_OMIT_LOCALTIME) && defined(_WIN32_WCE) && \ + (!defined(SQLITE_MSVC_LOCALTIME_API) || !SQLITE_MSVC_LOCALTIME_API) +struct tm *__cdecl localtime(const time_t *); +#endif + +/* +** A structure for holding a single date and time. +*/ +typedef struct DateTime DateTime; +struct DateTime { + sqlite3_int64 iJD; /* The julian day number times 86400000 */ + int Y, M, D; /* Year, month, and day */ + int h, m; /* Hour and minutes */ + int tz; /* Timezone offset in minutes */ + double s; /* Seconds */ + char validJD; /* True (1) if iJD is valid */ + char rawS; /* Raw numeric value stored in s */ + char validYMD; /* True (1) if Y,M,D are valid */ + char validHMS; /* True (1) if h,m,s are valid */ + char validTZ; /* True (1) if tz is valid */ + char tzSet; /* Timezone was set explicitly */ + char isError; /* An overflow has occurred */ + char useSubsec; /* Display subsecond precision */ +}; + + +/* +** Convert zDate into one or more integers according to the conversion +** specifier zFormat. +** +** zFormat[] contains 4 characters for each integer converted, except for +** the last integer which is specified by three characters. The meaning +** of a four-character format specifiers ABCD is: +** +** A: number of digits to convert. Always "2" or "4". +** B: minimum value. Always "0" or "1". +** C: maximum value, decoded as: +** a: 12 +** b: 14 +** c: 24 +** d: 31 +** e: 59 +** f: 9999 +** D: the separator character, or \000 to indicate this is the +** last number to convert. +** +** Example: To translate an ISO-8601 date YYYY-MM-DD, the format would +** be "40f-21a-20c". The "40f-" indicates the 4-digit year followed by "-". +** The "21a-" indicates the 2-digit month followed by "-". The "20c" indicates +** the 2-digit day which is the last integer in the set. +** +** The function returns the number of successful conversions. +*/ +static int getDigits(const char *zDate, const char *zFormat, ...){ + /* The aMx[] array translates the 3rd character of each format + ** spec into a max size: a b c d e f */ + static const u16 aMx[] = { 12, 14, 24, 31, 59, 14712 }; + va_list ap; + int cnt = 0; + char nextC; + va_start(ap, zFormat); + do{ + char N = zFormat[0] - '0'; + char min = zFormat[1] - '0'; + int val = 0; + u16 max; + + assert( zFormat[2]>='a' && zFormat[2]<='f' ); + max = aMx[zFormat[2] - 'a']; + nextC = zFormat[3]; + val = 0; + while( N-- ){ + if( !sqlite3Isdigit(*zDate) ){ + goto end_getDigits; + } + val = val*10 + *zDate - '0'; + zDate++; + } + if( val<(int)min || val>(int)max || (nextC!=0 && nextC!=*zDate) ){ + goto end_getDigits; + } + *va_arg(ap,int*) = val; + zDate++; + cnt++; + zFormat += 4; + }while( nextC ); +end_getDigits: + va_end(ap); + return cnt; +} + +/* +** Parse a timezone extension on the end of a date-time. +** The extension is of the form: +** +** (+/-)HH:MM +** +** Or the "zulu" notation: +** +** Z +** +** If the parse is successful, write the number of minutes +** of change in p->tz and return 0. If a parser error occurs, +** return non-zero. +** +** A missing specifier is not considered an error. +*/ +static int parseTimezone(const char *zDate, DateTime *p){ + int sgn = 0; + int nHr, nMn; + int c; + while( sqlite3Isspace(*zDate) ){ zDate++; } + p->tz = 0; + c = *zDate; + if( c=='-' ){ + sgn = -1; + }else if( c=='+' ){ + sgn = +1; + }else if( c=='Z' || c=='z' ){ + zDate++; + goto zulu_time; + }else{ + return c!=0; + } + zDate++; + if( getDigits(zDate, "20b:20e", &nHr, &nMn)!=2 ){ + return 1; + } + zDate += 5; + p->tz = sgn*(nMn + nHr*60); +zulu_time: + while( sqlite3Isspace(*zDate) ){ zDate++; } + p->tzSet = 1; + return *zDate!=0; +} + +/* +** Parse times of the form HH:MM or HH:MM:SS or HH:MM:SS.FFFF. +** The HH, MM, and SS must each be exactly 2 digits. The +** fractional seconds FFFF can be one or more digits. +** +** Return 1 if there is a parsing error and 0 on success. +*/ +static int parseHhMmSs(const char *zDate, DateTime *p){ + int h, m, s; + double ms = 0.0; + if( getDigits(zDate, "20c:20e", &h, &m)!=2 ){ + return 1; + } + zDate += 5; + if( *zDate==':' ){ + zDate++; + if( getDigits(zDate, "20e", &s)!=1 ){ + return 1; + } + zDate += 2; + if( *zDate=='.' && sqlite3Isdigit(zDate[1]) ){ + double rScale = 1.0; + zDate++; + while( sqlite3Isdigit(*zDate) ){ + ms = ms*10.0 + *zDate - '0'; + rScale *= 10.0; + zDate++; + } + ms /= rScale; + } + }else{ + s = 0; + } + p->validJD = 0; + p->rawS = 0; + p->validHMS = 1; + p->h = h; + p->m = m; + p->s = s + ms; + if( parseTimezone(zDate, p) ) return 1; + p->validTZ = (p->tz!=0)?1:0; + return 0; +} + +/* +** Put the DateTime object into its error state. +*/ +static void datetimeError(DateTime *p){ + memset(p, 0, sizeof(*p)); + p->isError = 1; +} + +/* +** Convert from YYYY-MM-DD HH:MM:SS to julian day. We always assume +** that the YYYY-MM-DD is according to the Gregorian calendar. +** +** Reference: Meeus page 61 +*/ +static void computeJD(DateTime *p){ + int Y, M, D, A, B, X1, X2; + + if( p->validJD ) return; + if( p->validYMD ){ + Y = p->Y; + M = p->M; + D = p->D; + }else{ + Y = 2000; /* If no YMD specified, assume 2000-Jan-01 */ + M = 1; + D = 1; + } + if( Y<-4713 || Y>9999 || p->rawS ){ + datetimeError(p); + return; + } + if( M<=2 ){ + Y--; + M += 12; + } + A = Y/100; + B = 2 - A + (A/4); + X1 = 36525*(Y+4716)/100; + X2 = 306001*(M+1)/10000; + p->iJD = (sqlite3_int64)((X1 + X2 + D + B - 1524.5 ) * 86400000); + p->validJD = 1; + if( p->validHMS ){ + p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000 + 0.5); + if( p->validTZ ){ + p->iJD -= p->tz*60000; + p->validYMD = 0; + p->validHMS = 0; + p->validTZ = 0; + } + } +} + +/* +** Parse dates of the form +** +** YYYY-MM-DD HH:MM:SS.FFF +** YYYY-MM-DD HH:MM:SS +** YYYY-MM-DD HH:MM +** YYYY-MM-DD +** +** Write the result into the DateTime structure and return 0 +** on success and 1 if the input string is not a well-formed +** date. +*/ +static int parseYyyyMmDd(const char *zDate, DateTime *p){ + int Y, M, D, neg; + + if( zDate[0]=='-' ){ + zDate++; + neg = 1; + }else{ + neg = 0; + } + if( getDigits(zDate, "40f-21a-21d", &Y, &M, &D)!=3 ){ + return 1; + } + zDate += 10; + while( sqlite3Isspace(*zDate) || 'T'==*(u8*)zDate ){ zDate++; } + if( parseHhMmSs(zDate, p)==0 ){ + /* We got the time */ + }else if( *zDate==0 ){ + p->validHMS = 0; + }else{ + return 1; + } + p->validJD = 0; + p->validYMD = 1; + p->Y = neg ? -Y : Y; + p->M = M; + p->D = D; + if( p->validTZ ){ + computeJD(p); + } + return 0; +} + +/* +** Set the time to the current time reported by the VFS. +** +** Return the number of errors. +*/ +static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ + p->iJD = sqlite3StmtCurrentTime(context); + if( p->iJD>0 ){ + p->validJD = 1; + return 0; + }else{ + return 1; + } +} + +/* +** Input "r" is a numeric quantity which might be a julian day number, +** or the number of seconds since 1970. If the value if r is within +** range of a julian day number, install it as such and set validJD. +** If the value is a valid unix timestamp, put it in p->s and set p->rawS. +*/ +static void setRawDateNumber(DateTime *p, double r){ + p->s = r; + p->rawS = 1; + if( r>=0.0 && r<5373484.5 ){ + p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5); + p->validJD = 1; + } +} + +/* +** Attempt to parse the given string into a julian day number. Return +** the number of errors. +** +** The following are acceptable forms for the input string: +** +** YYYY-MM-DD HH:MM:SS.FFF +/-HH:MM +** DDDD.DD +** now +** +** In the first form, the +/-HH:MM is always optional. The fractional +** seconds extension (the ".FFF") is optional. The seconds portion +** (":SS.FFF") is option. The year and date can be omitted as long +** as there is a time string. The time string can be omitted as long +** as there is a year and date. +*/ +static int parseDateOrTime( + sqlite3_context *context, + const char *zDate, + DateTime *p +){ + double r; + if( parseYyyyMmDd(zDate,p)==0 ){ + return 0; + }else if( parseHhMmSs(zDate, p)==0 ){ + return 0; + }else if( sqlite3StrICmp(zDate,"now")==0 && sqlite3NotPureFunc(context) ){ + return setDateTimeToCurrent(context, p); + }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8)>0 ){ + setRawDateNumber(p, r); + return 0; + }else if( (sqlite3StrICmp(zDate,"subsec")==0 + || sqlite3StrICmp(zDate,"subsecond")==0) + && sqlite3NotPureFunc(context) ){ + p->useSubsec = 1; + return setDateTimeToCurrent(context, p); + } + return 1; +} + +/* The julian day number for 9999-12-31 23:59:59.999 is 5373484.4999999. +** Multiplying this by 86400000 gives 464269060799999 as the maximum value +** for DateTime.iJD. +** +** But some older compilers (ex: gcc 4.2.1 on older Macs) cannot deal with +** such a large integer literal, so we have to encode it. +*/ +#define INT_464269060799999 ((((i64)0x1a640)<<32)|0x1072fdff) + +/* +** Return TRUE if the given julian day number is within range. +** +** The input is the JulianDay times 86400000. +*/ +static int validJulianDay(sqlite3_int64 iJD){ + return iJD>=0 && iJD<=INT_464269060799999; +} + +/* +** Compute the Year, Month, and Day from the julian day number. +*/ +static void computeYMD(DateTime *p){ + int Z, A, B, C, D, E, X1; + if( p->validYMD ) return; + if( !p->validJD ){ + p->Y = 2000; + p->M = 1; + p->D = 1; + }else if( !validJulianDay(p->iJD) ){ + datetimeError(p); + return; + }else{ + Z = (int)((p->iJD + 43200000)/86400000); + A = (int)((Z - 1867216.25)/36524.25); + A = Z + 1 + A - (A/4); + B = A + 1524; + C = (int)((B - 122.1)/365.25); + D = (36525*(C&32767))/100; + E = (int)((B-D)/30.6001); + X1 = (int)(30.6001*E); + p->D = B - D - X1; + p->M = E<14 ? E-1 : E-13; + p->Y = p->M>2 ? C - 4716 : C - 4715; + } + p->validYMD = 1; +} + +/* +** Compute the Hour, Minute, and Seconds from the julian day number. +*/ +static void computeHMS(DateTime *p){ + int day_ms, day_min; /* milliseconds, minutes into the day */ + if( p->validHMS ) return; + computeJD(p); + day_ms = (int)((p->iJD + 43200000) % 86400000); + p->s = (day_ms % 60000)/1000.0; + day_min = day_ms/60000; + p->m = day_min % 60; + p->h = day_min / 60; + p->rawS = 0; + p->validHMS = 1; +} + +/* +** Compute both YMD and HMS +*/ +static void computeYMD_HMS(DateTime *p){ + computeYMD(p); + computeHMS(p); +} + +/* +** Clear the YMD and HMS and the TZ +*/ +static void clearYMD_HMS_TZ(DateTime *p){ + p->validYMD = 0; + p->validHMS = 0; + p->validTZ = 0; +} + +#ifndef SQLITE_OMIT_LOCALTIME +/* +** On recent Windows platforms, the localtime_s() function is available +** as part of the "Secure CRT". It is essentially equivalent to +** localtime_r() available under most POSIX platforms, except that the +** order of the parameters is reversed. +** +** See http://msdn.microsoft.com/en-us/library/a442x3ye(VS.80).aspx. +** +** If the user has not indicated to use localtime_r() or localtime_s() +** already, check for an MSVC build environment that provides +** localtime_s(). +*/ +#if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S \ + && defined(_MSC_VER) && defined(_CRT_INSECURE_DEPRECATE) +#undef HAVE_LOCALTIME_S +#define HAVE_LOCALTIME_S 1 +#endif + +/* +** The following routine implements the rough equivalent of localtime_r() +** using whatever operating-system specific localtime facility that +** is available. This routine returns 0 on success and +** non-zero on any kind of error. +** +** If the sqlite3GlobalConfig.bLocaltimeFault variable is non-zero then this +** routine will always fail. If bLocaltimeFault is nonzero and +** sqlite3GlobalConfig.xAltLocaltime is not NULL, then xAltLocaltime() is +** invoked in place of the OS-defined localtime() function. +** +** EVIDENCE-OF: R-62172-00036 In this implementation, the standard C +** library function localtime_r() is used to assist in the calculation of +** local time. +*/ +static int osLocaltime(time_t *t, struct tm *pTm){ + int rc; +#if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S + struct tm *pX; +#if SQLITE_THREADSAFE>0 + sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); +#endif + sqlite3_mutex_enter(mutex); + pX = localtime(t); +#ifndef SQLITE_UNTESTABLE + if( sqlite3GlobalConfig.bLocaltimeFault ){ + if( sqlite3GlobalConfig.xAltLocaltime!=0 + && 0==sqlite3GlobalConfig.xAltLocaltime((const void*)t,(void*)pTm) + ){ + pX = pTm; + }else{ + pX = 0; + } + } +#endif + if( pX ) *pTm = *pX; +#if SQLITE_THREADSAFE>0 + sqlite3_mutex_leave(mutex); +#endif + rc = pX==0; +#else +#ifndef SQLITE_UNTESTABLE + if( sqlite3GlobalConfig.bLocaltimeFault ){ + if( sqlite3GlobalConfig.xAltLocaltime!=0 ){ + return sqlite3GlobalConfig.xAltLocaltime((const void*)t,(void*)pTm); + }else{ + return 1; + } + } +#endif +#if HAVE_LOCALTIME_R + rc = localtime_r(t, pTm)==0; +#else + rc = localtime_s(pTm, t); +#endif /* HAVE_LOCALTIME_R */ +#endif /* HAVE_LOCALTIME_R || HAVE_LOCALTIME_S */ + return rc; +} +#endif /* SQLITE_OMIT_LOCALTIME */ + + +#ifndef SQLITE_OMIT_LOCALTIME +/* +** Assuming the input DateTime is UTC, move it to its localtime equivalent. +*/ +static int toLocaltime( + DateTime *p, /* Date at which to calculate offset */ + sqlite3_context *pCtx /* Write error here if one occurs */ +){ + time_t t; + struct tm sLocal; + int iYearDiff; + + /* Initialize the contents of sLocal to avoid a compiler warning. */ + memset(&sLocal, 0, sizeof(sLocal)); + + computeJD(p); + if( p->iJD<2108667600*(i64)100000 /* 1970-01-01 */ + || p->iJD>2130141456*(i64)100000 /* 2038-01-18 */ + ){ + /* EVIDENCE-OF: R-55269-29598 The localtime_r() C function normally only + ** works for years between 1970 and 2037. For dates outside this range, + ** SQLite attempts to map the year into an equivalent year within this + ** range, do the calculation, then map the year back. + */ + DateTime x = *p; + computeYMD_HMS(&x); + iYearDiff = (2000 + x.Y%4) - x.Y; + x.Y += iYearDiff; + x.validJD = 0; + computeJD(&x); + t = (time_t)(x.iJD/1000 - 21086676*(i64)10000); + }else{ + iYearDiff = 0; + t = (time_t)(p->iJD/1000 - 21086676*(i64)10000); + } + if( osLocaltime(&t, &sLocal) ){ + sqlite3_result_error(pCtx, "local time unavailable", -1); + return SQLITE_ERROR; + } + p->Y = sLocal.tm_year + 1900 - iYearDiff; + p->M = sLocal.tm_mon + 1; + p->D = sLocal.tm_mday; + p->h = sLocal.tm_hour; + p->m = sLocal.tm_min; + p->s = sLocal.tm_sec + (p->iJD%1000)*0.001; + p->validYMD = 1; + p->validHMS = 1; + p->validJD = 0; + p->rawS = 0; + p->validTZ = 0; + p->isError = 0; + return SQLITE_OK; +} +#endif /* SQLITE_OMIT_LOCALTIME */ + +/* +** The following table defines various date transformations of the form +** +** 'NNN days' +** +** Where NNN is an arbitrary floating-point number and "days" can be one +** of several units of time. +*/ +static const struct { + u8 nName; /* Length of the name */ + char zName[7]; /* Name of the transformation */ + float rLimit; /* Maximum NNN value for this transform */ + float rXform; /* Constant used for this transform */ +} aXformType[] = { + { 6, "second", 4.6427e+14, 1.0 }, + { 6, "minute", 7.7379e+12, 60.0 }, + { 4, "hour", 1.2897e+11, 3600.0 }, + { 3, "day", 5373485.0, 86400.0 }, + { 5, "month", 176546.0, 2592000.0 }, + { 4, "year", 14713.0, 31536000.0 }, +}; + +/* +** If the DateTime p is raw number, try to figure out if it is +** a julian day number of a unix timestamp. Set the p value +** appropriately. +*/ +static void autoAdjustDate(DateTime *p){ + if( !p->rawS || p->validJD ){ + p->rawS = 0; + }else if( p->s>=-21086676*(i64)10000 /* -4713-11-24 12:00:00 */ + && p->s<=(25340230*(i64)10000)+799 /* 9999-12-31 23:59:59 */ + ){ + double r = p->s*1000.0 + 210866760000000.0; + clearYMD_HMS_TZ(p); + p->iJD = (sqlite3_int64)(r + 0.5); + p->validJD = 1; + p->rawS = 0; + } +} + +/* +** Process a modifier to a date-time stamp. The modifiers are +** as follows: +** +** NNN days +** NNN hours +** NNN minutes +** NNN.NNNN seconds +** NNN months +** NNN years +** start of month +** start of year +** start of week +** start of day +** weekday N +** unixepoch +** localtime +** utc +** +** Return 0 on success and 1 if there is any kind of error. If the error +** is in a system call (i.e. localtime()), then an error message is written +** to context pCtx. If the error is an unrecognized modifier, no error is +** written to pCtx. +*/ +static int parseModifier( + sqlite3_context *pCtx, /* Function context */ + const char *z, /* The text of the modifier */ + int n, /* Length of zMod in bytes */ + DateTime *p, /* The date/time value to be modified */ + int idx /* Parameter index of the modifier */ +){ + int rc = 1; + double r; + switch(sqlite3UpperToLower[(u8)z[0]] ){ + case 'a': { + /* + ** auto + ** + ** If rawS is available, then interpret as a julian day number, or + ** a unix timestamp, depending on its magnitude. + */ + if( sqlite3_stricmp(z, "auto")==0 ){ + if( idx>1 ) return 1; /* IMP: R-33611-57934 */ + autoAdjustDate(p); + rc = 0; + } + break; + } + case 'j': { + /* + ** julianday + ** + ** Always interpret the prior number as a julian-day value. If this + ** is not the first modifier, or if the prior argument is not a numeric + ** value in the allowed range of julian day numbers understood by + ** SQLite (0..5373484.5) then the result will be NULL. + */ + if( sqlite3_stricmp(z, "julianday")==0 ){ + if( idx>1 ) return 1; /* IMP: R-31176-64601 */ + if( p->validJD && p->rawS ){ + rc = 0; + p->rawS = 0; + } + } + break; + } +#ifndef SQLITE_OMIT_LOCALTIME + case 'l': { + /* localtime + ** + ** Assuming the current time value is UTC (a.k.a. GMT), shift it to + ** show local time. + */ + if( sqlite3_stricmp(z, "localtime")==0 && sqlite3NotPureFunc(pCtx) ){ + rc = toLocaltime(p, pCtx); + } + break; + } +#endif + case 'u': { + /* + ** unixepoch + ** + ** Treat the current value of p->s as the number of + ** seconds since 1970. Convert to a real julian day number. + */ + if( sqlite3_stricmp(z, "unixepoch")==0 && p->rawS ){ + if( idx>1 ) return 1; /* IMP: R-49255-55373 */ + r = p->s*1000.0 + 210866760000000.0; + if( r>=0.0 && r<464269060800000.0 ){ + clearYMD_HMS_TZ(p); + p->iJD = (sqlite3_int64)(r + 0.5); + p->validJD = 1; + p->rawS = 0; + rc = 0; + } + } +#ifndef SQLITE_OMIT_LOCALTIME + else if( sqlite3_stricmp(z, "utc")==0 && sqlite3NotPureFunc(pCtx) ){ + if( p->tzSet==0 ){ + i64 iOrigJD; /* Original localtime */ + i64 iGuess; /* Guess at the corresponding utc time */ + int cnt = 0; /* Safety to prevent infinite loop */ + i64 iErr; /* Guess is off by this much */ + + computeJD(p); + iGuess = iOrigJD = p->iJD; + iErr = 0; + do{ + DateTime new; + memset(&new, 0, sizeof(new)); + iGuess -= iErr; + new.iJD = iGuess; + new.validJD = 1; + rc = toLocaltime(&new, pCtx); + if( rc ) return rc; + computeJD(&new); + iErr = new.iJD - iOrigJD; + }while( iErr && cnt++<3 ); + memset(p, 0, sizeof(*p)); + p->iJD = iGuess; + p->validJD = 1; + p->tzSet = 1; + } + rc = SQLITE_OK; + } +#endif + break; + } + case 'w': { + /* + ** weekday N + ** + ** Move the date to the same time on the next occurrence of + ** weekday N where 0==Sunday, 1==Monday, and so forth. If the + ** date is already on the appropriate weekday, this is a no-op. + */ + if( sqlite3_strnicmp(z, "weekday ", 8)==0 + && sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8)>0 + && r>=0.0 && r<7.0 && (n=(int)r)==r ){ + sqlite3_int64 Z; + computeYMD_HMS(p); + p->validTZ = 0; + p->validJD = 0; + computeJD(p); + Z = ((p->iJD + 129600000)/86400000) % 7; + if( Z>n ) Z -= 7; + p->iJD += (n - Z)*86400000; + clearYMD_HMS_TZ(p); + rc = 0; + } + break; + } + case 's': { + /* + ** start of TTTTT + ** + ** Move the date backwards to the beginning of the current day, + ** or month or year. + ** + ** subsecond + ** subsec + ** + ** Show subsecond precision in the output of datetime() and + ** unixepoch() and strftime('%s'). + */ + if( sqlite3_strnicmp(z, "start of ", 9)!=0 ){ + if( sqlite3_stricmp(z, "subsec")==0 + || sqlite3_stricmp(z, "subsecond")==0 + ){ + p->useSubsec = 1; + rc = 0; + } + break; + } + if( !p->validJD && !p->validYMD && !p->validHMS ) break; + z += 9; + computeYMD(p); + p->validHMS = 1; + p->h = p->m = 0; + p->s = 0.0; + p->rawS = 0; + p->validTZ = 0; + p->validJD = 0; + if( sqlite3_stricmp(z,"month")==0 ){ + p->D = 1; + rc = 0; + }else if( sqlite3_stricmp(z,"year")==0 ){ + p->M = 1; + p->D = 1; + rc = 0; + }else if( sqlite3_stricmp(z,"day")==0 ){ + rc = 0; + } + break; + } + case '+': + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + double rRounder; + int i; + int Y,M,D,h,m,x; + const char *z2 = z; + char z0 = z[0]; + for(n=1; z[n]; n++){ + if( z[n]==':' ) break; + if( sqlite3Isspace(z[n]) ) break; + if( z[n]=='-' ){ + if( n==5 && getDigits(&z[1], "40f", &Y)==1 ) break; + if( n==6 && getDigits(&z[1], "50f", &Y)==1 ) break; + } + } + if( sqlite3AtoF(z, &r, n, SQLITE_UTF8)<=0 ){ + assert( rc==1 ); + break; + } + if( z[n]=='-' ){ + /* A modifier of the form (+|-)YYYY-MM-DD adds or subtracts the + ** specified number of years, months, and days. MM is limited to + ** the range 0-11 and DD is limited to 0-30. + */ + if( z0!='+' && z0!='-' ) break; /* Must start with +/- */ + if( n==5 ){ + if( getDigits(&z[1], "40f-20a-20d", &Y, &M, &D)!=3 ) break; + }else{ + assert( n==6 ); + if( getDigits(&z[1], "50f-20a-20d", &Y, &M, &D)!=3 ) break; + z++; + } + if( M>=12 ) break; /* M range 0..11 */ + if( D>=31 ) break; /* D range 0..30 */ + computeYMD_HMS(p); + p->validJD = 0; + if( z0=='-' ){ + p->Y -= Y; + p->M -= M; + D = -D; + }else{ + p->Y += Y; + p->M += M; + } + x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; + p->Y += x; + p->M -= x*12; + computeJD(p); + p->validHMS = 0; + p->validYMD = 0; + p->iJD += (i64)D*86400000; + if( z[11]==0 ){ + rc = 0; + break; + } + if( sqlite3Isspace(z[11]) + && getDigits(&z[12], "20c:20e", &h, &m)==2 + ){ + z2 = &z[12]; + n = 2; + }else{ + break; + } + } + if( z2[n]==':' ){ + /* A modifier of the form (+|-)HH:MM:SS.FFF adds (or subtracts) the + ** specified number of hours, minutes, seconds, and fractional seconds + ** to the time. The ".FFF" may be omitted. The ":SS.FFF" may be + ** omitted. + */ + + DateTime tx; + sqlite3_int64 day; + if( !sqlite3Isdigit(*z2) ) z2++; + memset(&tx, 0, sizeof(tx)); + if( parseHhMmSs(z2, &tx) ) break; + computeJD(&tx); + tx.iJD -= 43200000; + day = tx.iJD/86400000; + tx.iJD -= day*86400000; + if( z0=='-' ) tx.iJD = -tx.iJD; + computeJD(p); + clearYMD_HMS_TZ(p); + p->iJD += tx.iJD; + rc = 0; + break; + } + + /* If control reaches this point, it means the transformation is + ** one of the forms like "+NNN days". */ + z += n; + while( sqlite3Isspace(*z) ) z++; + n = sqlite3Strlen30(z); + if( n>10 || n<3 ) break; + if( sqlite3UpperToLower[(u8)z[n-1]]=='s' ) n--; + computeJD(p); + assert( rc==1 ); + rRounder = r<0 ? -0.5 : +0.5; + for(i=0; i<ArraySize(aXformType); i++){ + if( aXformType[i].nName==n + && sqlite3_strnicmp(aXformType[i].zName, z, n)==0 + && r>-aXformType[i].rLimit && r<aXformType[i].rLimit + ){ + switch( i ){ + case 4: { /* Special processing to add months */ + assert( strcmp(aXformType[i].zName,"month")==0 ); + computeYMD_HMS(p); + p->M += (int)r; + x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; + p->Y += x; + p->M -= x*12; + p->validJD = 0; + r -= (int)r; + break; + } + case 5: { /* Special processing to add years */ + int y = (int)r; + assert( strcmp(aXformType[i].zName,"year")==0 ); + computeYMD_HMS(p); + p->Y += y; + p->validJD = 0; + r -= (int)r; + break; + } + } + computeJD(p); + p->iJD += (sqlite3_int64)(r*1000.0*aXformType[i].rXform + rRounder); + rc = 0; + break; + } + } + clearYMD_HMS_TZ(p); + break; + } + default: { + break; + } + } + return rc; +} + +/* +** Process time function arguments. argv[0] is a date-time stamp. +** argv[1] and following are modifiers. Parse them all and write +** the resulting time into the DateTime structure p. Return 0 +** on success and 1 if there are any errors. +** +** If there are zero parameters (if even argv[0] is undefined) +** then assume a default value of "now" for argv[0]. +*/ +static int isDate( + sqlite3_context *context, + int argc, + sqlite3_value **argv, + DateTime *p +){ + int i, n; + const unsigned char *z; + int eType; + memset(p, 0, sizeof(*p)); + if( argc==0 ){ + if( !sqlite3NotPureFunc(context) ) return 1; + return setDateTimeToCurrent(context, p); + } + if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT + || eType==SQLITE_INTEGER ){ + setRawDateNumber(p, sqlite3_value_double(argv[0])); + }else{ + z = sqlite3_value_text(argv[0]); + if( !z || parseDateOrTime(context, (char*)z, p) ){ + return 1; + } + } + for(i=1; i<argc; i++){ + z = sqlite3_value_text(argv[i]); + n = sqlite3_value_bytes(argv[i]); + if( z==0 || parseModifier(context, (char*)z, n, p, i) ) return 1; + } + computeJD(p); + if( p->isError || !validJulianDay(p->iJD) ) return 1; + return 0; +} + + +/* +** The following routines implement the various date and time functions +** of SQLite. +*/ + +/* +** julianday( TIMESTRING, MOD, MOD, ...) +** +** Return the julian day number of the date specified in the arguments +*/ +static void juliandayFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + DateTime x; + if( isDate(context, argc, argv, &x)==0 ){ + computeJD(&x); + sqlite3_result_double(context, x.iJD/86400000.0); + } +} + +/* +** unixepoch( TIMESTRING, MOD, MOD, ...) +** +** Return the number of seconds (including fractional seconds) since +** the unix epoch of 1970-01-01 00:00:00 GMT. +*/ +static void unixepochFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + DateTime x; + if( isDate(context, argc, argv, &x)==0 ){ + computeJD(&x); + if( x.useSubsec ){ + sqlite3_result_double(context, (x.iJD - 21086676*(i64)10000000)/1000.0); + }else{ + sqlite3_result_int64(context, x.iJD/1000 - 21086676*(i64)10000); + } + } +} + +/* +** datetime( TIMESTRING, MOD, MOD, ...) +** +** Return YYYY-MM-DD HH:MM:SS +*/ +static void datetimeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + DateTime x; + if( isDate(context, argc, argv, &x)==0 ){ + int Y, s, n; + char zBuf[32]; + computeYMD_HMS(&x); + Y = x.Y; + if( Y<0 ) Y = -Y; + zBuf[1] = '0' + (Y/1000)%10; + zBuf[2] = '0' + (Y/100)%10; + zBuf[3] = '0' + (Y/10)%10; + zBuf[4] = '0' + (Y)%10; + zBuf[5] = '-'; + zBuf[6] = '0' + (x.M/10)%10; + zBuf[7] = '0' + (x.M)%10; + zBuf[8] = '-'; + zBuf[9] = '0' + (x.D/10)%10; + zBuf[10] = '0' + (x.D)%10; + zBuf[11] = ' '; + zBuf[12] = '0' + (x.h/10)%10; + zBuf[13] = '0' + (x.h)%10; + zBuf[14] = ':'; + zBuf[15] = '0' + (x.m/10)%10; + zBuf[16] = '0' + (x.m)%10; + zBuf[17] = ':'; + if( x.useSubsec ){ + s = (int)(1000.0*x.s + 0.5); + zBuf[18] = '0' + (s/10000)%10; + zBuf[19] = '0' + (s/1000)%10; + zBuf[20] = '.'; + zBuf[21] = '0' + (s/100)%10; + zBuf[22] = '0' + (s/10)%10; + zBuf[23] = '0' + (s)%10; + zBuf[24] = 0; + n = 24; + }else{ + s = (int)x.s; + zBuf[18] = '0' + (s/10)%10; + zBuf[19] = '0' + (s)%10; + zBuf[20] = 0; + n = 20; + } + if( x.Y<0 ){ + zBuf[0] = '-'; + sqlite3_result_text(context, zBuf, n, SQLITE_TRANSIENT); + }else{ + sqlite3_result_text(context, &zBuf[1], n-1, SQLITE_TRANSIENT); + } + } +} + +/* +** time( TIMESTRING, MOD, MOD, ...) +** +** Return HH:MM:SS +*/ +static void timeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + DateTime x; + if( isDate(context, argc, argv, &x)==0 ){ + int s, n; + char zBuf[16]; + computeHMS(&x); + zBuf[0] = '0' + (x.h/10)%10; + zBuf[1] = '0' + (x.h)%10; + zBuf[2] = ':'; + zBuf[3] = '0' + (x.m/10)%10; + zBuf[4] = '0' + (x.m)%10; + zBuf[5] = ':'; + if( x.useSubsec ){ + s = (int)(1000.0*x.s + 0.5); + zBuf[6] = '0' + (s/10000)%10; + zBuf[7] = '0' + (s/1000)%10; + zBuf[8] = '.'; + zBuf[9] = '0' + (s/100)%10; + zBuf[10] = '0' + (s/10)%10; + zBuf[11] = '0' + (s)%10; + zBuf[12] = 0; + n = 12; + }else{ + s = (int)x.s; + zBuf[6] = '0' + (s/10)%10; + zBuf[7] = '0' + (s)%10; + zBuf[8] = 0; + n = 8; + } + sqlite3_result_text(context, zBuf, n, SQLITE_TRANSIENT); + } +} + +/* +** date( TIMESTRING, MOD, MOD, ...) +** +** Return YYYY-MM-DD +*/ +static void dateFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + DateTime x; + if( isDate(context, argc, argv, &x)==0 ){ + int Y; + char zBuf[16]; + computeYMD(&x); + Y = x.Y; + if( Y<0 ) Y = -Y; + zBuf[1] = '0' + (Y/1000)%10; + zBuf[2] = '0' + (Y/100)%10; + zBuf[3] = '0' + (Y/10)%10; + zBuf[4] = '0' + (Y)%10; + zBuf[5] = '-'; + zBuf[6] = '0' + (x.M/10)%10; + zBuf[7] = '0' + (x.M)%10; + zBuf[8] = '-'; + zBuf[9] = '0' + (x.D/10)%10; + zBuf[10] = '0' + (x.D)%10; + zBuf[11] = 0; + if( x.Y<0 ){ + zBuf[0] = '-'; + sqlite3_result_text(context, zBuf, 11, SQLITE_TRANSIENT); + }else{ + sqlite3_result_text(context, &zBuf[1], 10, SQLITE_TRANSIENT); + } + } +} + +/* +** strftime( FORMAT, TIMESTRING, MOD, MOD, ...) +** +** Return a string described by FORMAT. Conversions as follows: +** +** %d day of month +** %f ** fractional seconds SS.SSS +** %H hour 00-24 +** %j day of year 000-366 +** %J ** julian day number +** %m month 01-12 +** %M minute 00-59 +** %s seconds since 1970-01-01 +** %S seconds 00-59 +** %w day of week 0-6 Sunday==0 +** %W week of year 00-53 +** %Y year 0000-9999 +** %% % +*/ +static void strftimeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + DateTime x; + size_t i,j; + sqlite3 *db; + const char *zFmt; + sqlite3_str sRes; + + + if( argc==0 ) return; + zFmt = (const char*)sqlite3_value_text(argv[0]); + if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return; + db = sqlite3_context_db_handle(context); + sqlite3StrAccumInit(&sRes, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); + + computeJD(&x); + computeYMD_HMS(&x); + for(i=j=0; zFmt[i]; i++){ + char cf; + if( zFmt[i]!='%' ) continue; + if( j<i ) sqlite3_str_append(&sRes, zFmt+j, (int)(i-j)); + i++; + j = i + 1; + cf = zFmt[i]; + switch( cf ){ + case 'd': /* Fall thru */ + case 'e': { + sqlite3_str_appendf(&sRes, cf=='d' ? "%02d" : "%2d", x.D); + break; + } + case 'f': { + double s = x.s; + if( s>59.999 ) s = 59.999; + sqlite3_str_appendf(&sRes, "%06.3f", s); + break; + } + case 'F': { + sqlite3_str_appendf(&sRes, "%04d-%02d-%02d", x.Y, x.M, x.D); + break; + } + case 'H': + case 'k': { + sqlite3_str_appendf(&sRes, cf=='H' ? "%02d" : "%2d", x.h); + break; + } + case 'I': /* Fall thru */ + case 'l': { + int h = x.h; + if( h>12 ) h -= 12; + if( h==0 ) h = 12; + sqlite3_str_appendf(&sRes, cf=='I' ? "%02d" : "%2d", h); + break; + } + case 'W': /* Fall thru */ + case 'j': { + int nDay; /* Number of days since 1st day of year */ + DateTime y = x; + y.validJD = 0; + y.M = 1; + y.D = 1; + computeJD(&y); + nDay = (int)((x.iJD-y.iJD+43200000)/86400000); + if( cf=='W' ){ + int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */ + wd = (int)(((x.iJD+43200000)/86400000)%7); + sqlite3_str_appendf(&sRes,"%02d",(nDay+7-wd)/7); + }else{ + sqlite3_str_appendf(&sRes,"%03d",nDay+1); + } + break; + } + case 'J': { + sqlite3_str_appendf(&sRes,"%.16g",x.iJD/86400000.0); + break; + } + case 'm': { + sqlite3_str_appendf(&sRes,"%02d",x.M); + break; + } + case 'M': { + sqlite3_str_appendf(&sRes,"%02d",x.m); + break; + } + case 'p': /* Fall thru */ + case 'P': { + if( x.h>=12 ){ + sqlite3_str_append(&sRes, cf=='p' ? "PM" : "pm", 2); + }else{ + sqlite3_str_append(&sRes, cf=='p' ? "AM" : "am", 2); + } + break; + } + case 'R': { + sqlite3_str_appendf(&sRes, "%02d:%02d", x.h, x.m); + break; + } + case 's': { + if( x.useSubsec ){ + sqlite3_str_appendf(&sRes,"%.3f", + (x.iJD - 21086676*(i64)10000000)/1000.0); + }else{ + i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000); + sqlite3_str_appendf(&sRes,"%lld",iS); + } + break; + } + case 'S': { + sqlite3_str_appendf(&sRes,"%02d",(int)x.s); + break; + } + case 'T': { + sqlite3_str_appendf(&sRes,"%02d:%02d:%02d", x.h, x.m, (int)x.s); + break; + } + case 'u': /* Fall thru */ + case 'w': { + char c = (char)(((x.iJD+129600000)/86400000) % 7) + '0'; + if( c=='0' && cf=='u' ) c = '7'; + sqlite3_str_appendchar(&sRes, 1, c); + break; + } + case 'Y': { + sqlite3_str_appendf(&sRes,"%04d",x.Y); + break; + } + case '%': { + sqlite3_str_appendchar(&sRes, 1, '%'); + break; + } + default: { + sqlite3_str_reset(&sRes); + return; + } + } + } + if( j<i ) sqlite3_str_append(&sRes, zFmt+j, (int)(i-j)); + sqlite3ResultStrAccum(context, &sRes); +} + +/* +** current_time() +** +** This function returns the same value as time('now'). +*/ +static void ctimeFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **NotUsed2 +){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + timeFunc(context, 0, 0); +} + +/* +** current_date() +** +** This function returns the same value as date('now'). +*/ +static void cdateFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **NotUsed2 +){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + dateFunc(context, 0, 0); +} + +/* +** timediff(DATE1, DATE2) +** +** Return the amount of time that must be added to DATE2 in order to +** convert it into DATE2. The time difference format is: +** +** +YYYY-MM-DD HH:MM:SS.SSS +** +** The initial "+" becomes "-" if DATE1 occurs before DATE2. For +** date/time values A and B, the following invariant should hold: +** +** datetime(A) == (datetime(B, timediff(A,B)) +** +** Both DATE arguments must be either a julian day number, or an +** ISO-8601 string. The unix timestamps are not supported by this +** routine. +*/ +static void timediffFunc( + sqlite3_context *context, + int NotUsed1, + sqlite3_value **argv +){ + char sign; + int Y, M; + DateTime d1, d2; + sqlite3_str sRes; + UNUSED_PARAMETER(NotUsed1); + if( isDate(context, 1, &argv[0], &d1) ) return; + if( isDate(context, 1, &argv[1], &d2) ) return; + computeYMD_HMS(&d1); + computeYMD_HMS(&d2); + if( d1.iJD>=d2.iJD ){ + sign = '+'; + Y = d1.Y - d2.Y; + if( Y ){ + d2.Y = d1.Y; + d2.validJD = 0; + computeJD(&d2); + } + M = d1.M - d2.M; + if( M<0 ){ + Y--; + M += 12; + } + if( M!=0 ){ + d2.M = d1.M; + d2.validJD = 0; + computeJD(&d2); + } + while( d1.iJD<d2.iJD ){ + M--; + if( M<0 ){ + M = 11; + Y--; + } + d2.M--; + if( d2.M<1 ){ + d2.M = 12; + d2.Y--; + } + d2.validJD = 0; + computeJD(&d2); + } + d1.iJD -= d2.iJD; + d1.iJD += (u64)1486995408 * (u64)100000; + }else /* d1<d2 */{ + sign = '-'; + Y = d2.Y - d1.Y; + if( Y ){ + d2.Y = d1.Y; + d2.validJD = 0; + computeJD(&d2); + } + M = d2.M - d1.M; + if( M<0 ){ + Y--; + M += 12; + } + if( M!=0 ){ + d2.M = d1.M; + d2.validJD = 0; + computeJD(&d2); + } + while( d1.iJD>d2.iJD ){ + M--; + if( M<0 ){ + M = 11; + Y--; + } + d2.M++; + if( d2.M>12 ){ + d2.M = 1; + d2.Y++; + } + d2.validJD = 0; + computeJD(&d2); + } + d1.iJD = d2.iJD - d1.iJD; + d1.iJD += (u64)1486995408 * (u64)100000; + } + d1.validYMD = 0; + d1.validHMS = 0; + d1.validTZ = 0; + computeYMD_HMS(&d1); + sqlite3StrAccumInit(&sRes, 0, 0, 0, 100); + sqlite3_str_appendf(&sRes, "%c%04d-%02d-%02d %02d:%02d:%06.3f", + sign, Y, M, d1.D-1, d1.h, d1.m, d1.s); + sqlite3ResultStrAccum(context, &sRes); +} + + +/* +** current_timestamp() +** +** This function returns the same value as datetime('now'). +*/ +static void ctimestampFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **NotUsed2 +){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + datetimeFunc(context, 0, 0); +} +#endif /* !defined(SQLITE_OMIT_DATETIME_FUNCS) */ + +#ifdef SQLITE_OMIT_DATETIME_FUNCS +/* +** If the library is compiled to omit the full-scale date and time +** handling (to get a smaller binary), the following minimal version +** of the functions current_time(), current_date() and current_timestamp() +** are included instead. This is to support column declarations that +** include "DEFAULT CURRENT_TIME" etc. +** +** This function uses the C-library functions time(), gmtime() +** and strftime(). The format string to pass to strftime() is supplied +** as the user-data for the function. +*/ +static void currentTimeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + time_t t; + char *zFormat = (char *)sqlite3_user_data(context); + sqlite3_int64 iT; + struct tm *pTm; + struct tm sNow; + char zBuf[20]; + + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + + iT = sqlite3StmtCurrentTime(context); + if( iT<=0 ) return; + t = iT/1000 - 10000*(sqlite3_int64)21086676; +#if HAVE_GMTIME_R + pTm = gmtime_r(&t, &sNow); +#else + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)); + pTm = gmtime(&t); + if( pTm ) memcpy(&sNow, pTm, sizeof(sNow)); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)); +#endif + if( pTm ){ + strftime(zBuf, 20, zFormat, &sNow); + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + } +} +#endif + +/* +** This function registered all of the above C functions as SQL +** functions. This should be the only routine in this file with +** external linkage. +*/ +SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void){ + static FuncDef aDateTimeFuncs[] = { +#ifndef SQLITE_OMIT_DATETIME_FUNCS + PURE_DATE(julianday, -1, 0, 0, juliandayFunc ), + PURE_DATE(unixepoch, -1, 0, 0, unixepochFunc ), + PURE_DATE(date, -1, 0, 0, dateFunc ), + PURE_DATE(time, -1, 0, 0, timeFunc ), + PURE_DATE(datetime, -1, 0, 0, datetimeFunc ), + PURE_DATE(strftime, -1, 0, 0, strftimeFunc ), + PURE_DATE(timediff, 2, 0, 0, timediffFunc ), + DFUNCTION(current_time, 0, 0, 0, ctimeFunc ), + DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc), + DFUNCTION(current_date, 0, 0, 0, cdateFunc ), +#else + STR_FUNCTION(current_time, 0, "%H:%M:%S", 0, currentTimeFunc), + STR_FUNCTION(current_date, 0, "%Y-%m-%d", 0, currentTimeFunc), + STR_FUNCTION(current_timestamp, 0, "%Y-%m-%d %H:%M:%S", 0, currentTimeFunc), +#endif + }; + sqlite3InsertBuiltinFuncs(aDateTimeFuncs, ArraySize(aDateTimeFuncs)); +} + +/************** End of date.c ************************************************/ +/************** Begin file os.c **********************************************/ +/* +** 2005 November 29 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains OS interface code that is common to all +** architectures. +*/ +/* #include "sqliteInt.h" */ + +/* +** If we compile with the SQLITE_TEST macro set, then the following block +** of code will give us the ability to simulate a disk I/O error. This +** is used for testing the I/O recovery logic. +*/ +#if defined(SQLITE_TEST) +SQLITE_API int sqlite3_io_error_hit = 0; /* Total number of I/O Errors */ +SQLITE_API int sqlite3_io_error_hardhit = 0; /* Number of non-benign errors */ +SQLITE_API int sqlite3_io_error_pending = 0; /* Count down to first I/O error */ +SQLITE_API int sqlite3_io_error_persist = 0; /* True if I/O errors persist */ +SQLITE_API int sqlite3_io_error_benign = 0; /* True if errors are benign */ +SQLITE_API int sqlite3_diskfull_pending = 0; +SQLITE_API int sqlite3_diskfull = 0; +#endif /* defined(SQLITE_TEST) */ + +/* +** When testing, also keep a count of the number of open files. +*/ +#if defined(SQLITE_TEST) +SQLITE_API int sqlite3_open_file_count = 0; +#endif /* defined(SQLITE_TEST) */ + +/* +** The default SQLite sqlite3_vfs implementations do not allocate +** memory (actually, os_unix.c allocates a small amount of memory +** from within OsOpen()), but some third-party implementations may. +** So we test the effects of a malloc() failing and the sqlite3OsXXX() +** function returning SQLITE_IOERR_NOMEM using the DO_OS_MALLOC_TEST macro. +** +** The following functions are instrumented for malloc() failure +** testing: +** +** sqlite3OsRead() +** sqlite3OsWrite() +** sqlite3OsSync() +** sqlite3OsFileSize() +** sqlite3OsLock() +** sqlite3OsCheckReservedLock() +** sqlite3OsFileControl() +** sqlite3OsShmMap() +** sqlite3OsOpen() +** sqlite3OsDelete() +** sqlite3OsAccess() +** sqlite3OsFullPathname() +** +*/ +#if defined(SQLITE_TEST) +SQLITE_API int sqlite3_memdebug_vfs_oom_test = 1; + #define DO_OS_MALLOC_TEST(x) \ + if (sqlite3_memdebug_vfs_oom_test && (!x || !sqlite3JournalIsInMemory(x))) { \ + void *pTstAlloc = sqlite3Malloc(10); \ + if (!pTstAlloc) return SQLITE_IOERR_NOMEM_BKPT; \ + sqlite3_free(pTstAlloc); \ + } +#else + #define DO_OS_MALLOC_TEST(x) +#endif + +/* +** The following routines are convenience wrappers around methods +** of the sqlite3_file object. This is mostly just syntactic sugar. All +** of this would be completely automatic if SQLite were coded using +** C++ instead of plain old C. +*/ +SQLITE_PRIVATE void sqlite3OsClose(sqlite3_file *pId){ + if( pId->pMethods ){ + pId->pMethods->xClose(pId); + pId->pMethods = 0; + } +} +SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file *id, void *pBuf, int amt, i64 offset){ + DO_OS_MALLOC_TEST(id); + return id->pMethods->xRead(id, pBuf, amt, offset); +} +SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file *id, const void *pBuf, int amt, i64 offset){ + DO_OS_MALLOC_TEST(id); + return id->pMethods->xWrite(id, pBuf, amt, offset); +} +SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file *id, i64 size){ + return id->pMethods->xTruncate(id, size); +} +SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file *id, int flags){ + DO_OS_MALLOC_TEST(id); + return flags ? id->pMethods->xSync(id, flags) : SQLITE_OK; +} +SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file *id, i64 *pSize){ + DO_OS_MALLOC_TEST(id); + return id->pMethods->xFileSize(id, pSize); +} +SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file *id, int lockType){ + DO_OS_MALLOC_TEST(id); + assert( lockType>=SQLITE_LOCK_SHARED && lockType<=SQLITE_LOCK_EXCLUSIVE ); + return id->pMethods->xLock(id, lockType); +} +SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file *id, int lockType){ + assert( lockType==SQLITE_LOCK_NONE || lockType==SQLITE_LOCK_SHARED ); + return id->pMethods->xUnlock(id, lockType); +} +SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut){ + DO_OS_MALLOC_TEST(id); + return id->pMethods->xCheckReservedLock(id, pResOut); +} + +/* +** Use sqlite3OsFileControl() when we are doing something that might fail +** and we need to know about the failures. Use sqlite3OsFileControlHint() +** when simply tossing information over the wall to the VFS and we do not +** really care if the VFS receives and understands the information since it +** is only a hint and can be safely ignored. The sqlite3OsFileControlHint() +** routine has no return value since the return value would be meaningless. +*/ +SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){ + if( id->pMethods==0 ) return SQLITE_NOTFOUND; +#ifdef SQLITE_TEST + if( op!=SQLITE_FCNTL_COMMIT_PHASETWO + && op!=SQLITE_FCNTL_LOCK_TIMEOUT + && op!=SQLITE_FCNTL_CKPT_DONE + && op!=SQLITE_FCNTL_CKPT_START + ){ + /* Faults are not injected into COMMIT_PHASETWO because, assuming SQLite + ** is using a regular VFS, it is called after the corresponding + ** transaction has been committed. Injecting a fault at this point + ** confuses the test scripts - the COMMIT command returns SQLITE_NOMEM + ** but the transaction is committed anyway. + ** + ** The core must call OsFileControl() though, not OsFileControlHint(), + ** as if a custom VFS (e.g. zipvfs) returns an error here, it probably + ** means the commit really has failed and an error should be returned + ** to the user. + ** + ** The CKPT_DONE and CKPT_START file-controls are write-only signals + ** to the cksumvfs. Their return code is meaningless and is ignored + ** by the SQLite core, so there is no point in simulating OOMs for them. + */ + DO_OS_MALLOC_TEST(id); + } +#endif + return id->pMethods->xFileControl(id, op, pArg); +} +SQLITE_PRIVATE void sqlite3OsFileControlHint(sqlite3_file *id, int op, void *pArg){ + if( id->pMethods ) (void)id->pMethods->xFileControl(id, op, pArg); +} + +SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id){ + int (*xSectorSize)(sqlite3_file*) = id->pMethods->xSectorSize; + return (xSectorSize ? xSectorSize(id) : SQLITE_DEFAULT_SECTOR_SIZE); +} +SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id){ + if( NEVER(id->pMethods==0) ) return 0; + return id->pMethods->xDeviceCharacteristics(id); +} +#ifndef SQLITE_OMIT_WAL +SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int offset, int n, int flags){ + return id->pMethods->xShmLock(id, offset, n, flags); +} +SQLITE_PRIVATE void sqlite3OsShmBarrier(sqlite3_file *id){ + id->pMethods->xShmBarrier(id); +} +SQLITE_PRIVATE int sqlite3OsShmUnmap(sqlite3_file *id, int deleteFlag){ + return id->pMethods->xShmUnmap(id, deleteFlag); +} +SQLITE_PRIVATE int sqlite3OsShmMap( + sqlite3_file *id, /* Database file handle */ + int iPage, + int pgsz, + int bExtend, /* True to extend file if necessary */ + void volatile **pp /* OUT: Pointer to mapping */ +){ + DO_OS_MALLOC_TEST(id); + return id->pMethods->xShmMap(id, iPage, pgsz, bExtend, pp); +} +#endif /* SQLITE_OMIT_WAL */ + +#if SQLITE_MAX_MMAP_SIZE>0 +/* The real implementation of xFetch and xUnfetch */ +SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64 iOff, int iAmt, void **pp){ + DO_OS_MALLOC_TEST(id); + return id->pMethods->xFetch(id, iOff, iAmt, pp); +} +SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *id, i64 iOff, void *p){ + return id->pMethods->xUnfetch(id, iOff, p); +} +#else +/* No-op stubs to use when memory-mapped I/O is disabled */ +SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64 iOff, int iAmt, void **pp){ + *pp = 0; + return SQLITE_OK; +} +SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *id, i64 iOff, void *p){ + return SQLITE_OK; +} +#endif + +/* +** The next group of routines are convenience wrappers around the +** VFS methods. +*/ +SQLITE_PRIVATE int sqlite3OsOpen( + sqlite3_vfs *pVfs, + const char *zPath, + sqlite3_file *pFile, + int flags, + int *pFlagsOut +){ + int rc; + DO_OS_MALLOC_TEST(0); + /* 0x87f7f is a mask of SQLITE_OPEN_ flags that are valid to be passed + ** down into the VFS layer. Some SQLITE_OPEN_ flags (for example, + ** SQLITE_OPEN_FULLMUTEX or SQLITE_OPEN_SHAREDCACHE) are blocked before + ** reaching the VFS. */ + assert( zPath || (flags & SQLITE_OPEN_EXCLUSIVE) ); + rc = pVfs->xOpen(pVfs, zPath, pFile, flags & 0x1087f7f, pFlagsOut); + assert( rc==SQLITE_OK || pFile->pMethods==0 ); + return rc; +} +SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + DO_OS_MALLOC_TEST(0); + assert( dirSync==0 || dirSync==1 ); + return pVfs->xDelete!=0 ? pVfs->xDelete(pVfs, zPath, dirSync) : SQLITE_OK; +} +SQLITE_PRIVATE int sqlite3OsAccess( + sqlite3_vfs *pVfs, + const char *zPath, + int flags, + int *pResOut +){ + DO_OS_MALLOC_TEST(0); + return pVfs->xAccess(pVfs, zPath, flags, pResOut); +} +SQLITE_PRIVATE int sqlite3OsFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nPathOut, + char *zPathOut +){ + DO_OS_MALLOC_TEST(0); + zPathOut[0] = 0; + return pVfs->xFullPathname(pVfs, zPath, nPathOut, zPathOut); +} +#ifndef SQLITE_OMIT_LOAD_EXTENSION +SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + assert( zPath!=0 ); + assert( strlen(zPath)<=SQLITE_MAX_PATHLEN ); /* tag-20210611-1 */ + return pVfs->xDlOpen(pVfs, zPath); +} +SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ + pVfs->xDlError(pVfs, nByte, zBufOut); +} +SQLITE_PRIVATE void (*sqlite3OsDlSym(sqlite3_vfs *pVfs, void *pHdle, const char *zSym))(void){ + return pVfs->xDlSym(pVfs, pHdle, zSym); +} +SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *pVfs, void *pHandle){ + pVfs->xDlClose(pVfs, pHandle); +} +#endif /* SQLITE_OMIT_LOAD_EXTENSION */ +SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ + if( sqlite3Config.iPrngSeed ){ + memset(zBufOut, 0, nByte); + if( ALWAYS(nByte>(signed)sizeof(unsigned)) ) nByte = sizeof(unsigned int); + memcpy(zBufOut, &sqlite3Config.iPrngSeed, nByte); + return SQLITE_OK; + }else{ + return pVfs->xRandomness(pVfs, nByte, zBufOut); + } + +} +SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *pVfs, int nMicro){ + return pVfs->xSleep(pVfs, nMicro); +} +SQLITE_PRIVATE int sqlite3OsGetLastError(sqlite3_vfs *pVfs){ + return pVfs->xGetLastError ? pVfs->xGetLastError(pVfs, 0, 0) : 0; +} +SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ + int rc; + /* IMPLEMENTATION-OF: R-49045-42493 SQLite will use the xCurrentTimeInt64() + ** method to get the current date and time if that method is available + ** (if iVersion is 2 or greater and the function pointer is not NULL) and + ** will fall back to xCurrentTime() if xCurrentTimeInt64() is + ** unavailable. + */ + if( pVfs->iVersion>=2 && pVfs->xCurrentTimeInt64 ){ + rc = pVfs->xCurrentTimeInt64(pVfs, pTimeOut); + }else{ + double r; + rc = pVfs->xCurrentTime(pVfs, &r); + *pTimeOut = (sqlite3_int64)(r*86400000.0); + } + return rc; +} + +SQLITE_PRIVATE int sqlite3OsOpenMalloc( + sqlite3_vfs *pVfs, + const char *zFile, + sqlite3_file **ppFile, + int flags, + int *pOutFlags +){ + int rc; + sqlite3_file *pFile; + pFile = (sqlite3_file *)sqlite3MallocZero(pVfs->szOsFile); + if( pFile ){ + rc = sqlite3OsOpen(pVfs, zFile, pFile, flags, pOutFlags); + if( rc!=SQLITE_OK ){ + sqlite3_free(pFile); + *ppFile = 0; + }else{ + *ppFile = pFile; + } + }else{ + *ppFile = 0; + rc = SQLITE_NOMEM_BKPT; + } + assert( *ppFile!=0 || rc!=SQLITE_OK ); + return rc; +} +SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *pFile){ + assert( pFile ); + sqlite3OsClose(pFile); + sqlite3_free(pFile); +} + +/* +** This function is a wrapper around the OS specific implementation of +** sqlite3_os_init(). The purpose of the wrapper is to provide the +** ability to simulate a malloc failure, so that the handling of an +** error in sqlite3_os_init() by the upper layers can be tested. +*/ +SQLITE_PRIVATE int sqlite3OsInit(void){ + void *p = sqlite3_malloc(10); + if( p==0 ) return SQLITE_NOMEM_BKPT; + sqlite3_free(p); + return sqlite3_os_init(); +} + +/* +** The list of all registered VFS implementations. +*/ +static sqlite3_vfs * SQLITE_WSD vfsList = 0; +#define vfsList GLOBAL(sqlite3_vfs *, vfsList) + +/* +** Locate a VFS by name. If no name is given, simply return the +** first VFS on the list. +*/ +SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfs){ + sqlite3_vfs *pVfs = 0; +#if SQLITE_THREADSAFE + sqlite3_mutex *mutex; +#endif +#ifndef SQLITE_OMIT_AUTOINIT + int rc = sqlite3_initialize(); + if( rc ) return 0; +#endif +#if SQLITE_THREADSAFE + mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); +#endif + sqlite3_mutex_enter(mutex); + for(pVfs = vfsList; pVfs; pVfs=pVfs->pNext){ + if( zVfs==0 ) break; + if( strcmp(zVfs, pVfs->zName)==0 ) break; + } + sqlite3_mutex_leave(mutex); + return pVfs; +} + +/* +** Unlink a VFS from the linked list +*/ +static void vfsUnlink(sqlite3_vfs *pVfs){ + assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)) ); + if( pVfs==0 ){ + /* No-op */ + }else if( vfsList==pVfs ){ + vfsList = pVfs->pNext; + }else if( vfsList ){ + sqlite3_vfs *p = vfsList; + while( p->pNext && p->pNext!=pVfs ){ + p = p->pNext; + } + if( p->pNext==pVfs ){ + p->pNext = pVfs->pNext; + } + } +} + +/* +** Register a VFS with the system. It is harmless to register the same +** VFS multiple times. The new VFS becomes the default if makeDflt is +** true. +*/ +SQLITE_API int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){ + MUTEX_LOGIC(sqlite3_mutex *mutex;) +#ifndef SQLITE_OMIT_AUTOINIT + int rc = sqlite3_initialize(); + if( rc ) return rc; +#endif +#ifdef SQLITE_ENABLE_API_ARMOR + if( pVfs==0 ) return SQLITE_MISUSE_BKPT; +#endif + + MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); ) + sqlite3_mutex_enter(mutex); + vfsUnlink(pVfs); + if( makeDflt || vfsList==0 ){ + pVfs->pNext = vfsList; + vfsList = pVfs; + }else{ + pVfs->pNext = vfsList->pNext; + vfsList->pNext = pVfs; + } + assert(vfsList); + sqlite3_mutex_leave(mutex); + return SQLITE_OK; +} + +/* +** Unregister a VFS so that it is no longer accessible. +*/ +SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs *pVfs){ + MUTEX_LOGIC(sqlite3_mutex *mutex;) +#ifndef SQLITE_OMIT_AUTOINIT + int rc = sqlite3_initialize(); + if( rc ) return rc; +#endif + MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); ) + sqlite3_mutex_enter(mutex); + vfsUnlink(pVfs); + sqlite3_mutex_leave(mutex); + return SQLITE_OK; +} + +/************** End of os.c **************************************************/ +/************** Begin file fault.c *******************************************/ +/* +** 2008 Jan 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains code to support the concept of "benign" +** malloc failures (when the xMalloc() or xRealloc() method of the +** sqlite3_mem_methods structure fails to allocate a block of memory +** and returns 0). +** +** Most malloc failures are non-benign. After they occur, SQLite +** abandons the current operation and returns an error code (usually +** SQLITE_NOMEM) to the user. However, sometimes a fault is not necessarily +** fatal. For example, if a malloc fails while resizing a hash table, this +** is completely recoverable simply by not carrying out the resize. The +** hash table will continue to function normally. So a malloc failure +** during a hash table resize is a benign fault. +*/ + +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_UNTESTABLE + +/* +** Global variables. +*/ +typedef struct BenignMallocHooks BenignMallocHooks; +static SQLITE_WSD struct BenignMallocHooks { + void (*xBenignBegin)(void); + void (*xBenignEnd)(void); +} sqlite3Hooks = { 0, 0 }; + +/* The "wsdHooks" macro will resolve to the appropriate BenignMallocHooks +** structure. If writable static data is unsupported on the target, +** we have to locate the state vector at run-time. In the more common +** case where writable static data is supported, wsdHooks can refer directly +** to the "sqlite3Hooks" state vector declared above. +*/ +#ifdef SQLITE_OMIT_WSD +# define wsdHooksInit \ + BenignMallocHooks *x = &GLOBAL(BenignMallocHooks,sqlite3Hooks) +# define wsdHooks x[0] +#else +# define wsdHooksInit +# define wsdHooks sqlite3Hooks +#endif + + +/* +** Register hooks to call when sqlite3BeginBenignMalloc() and +** sqlite3EndBenignMalloc() are called, respectively. +*/ +SQLITE_PRIVATE void sqlite3BenignMallocHooks( + void (*xBenignBegin)(void), + void (*xBenignEnd)(void) +){ + wsdHooksInit; + wsdHooks.xBenignBegin = xBenignBegin; + wsdHooks.xBenignEnd = xBenignEnd; +} + +/* +** This (sqlite3EndBenignMalloc()) is called by SQLite code to indicate that +** subsequent malloc failures are benign. A call to sqlite3EndBenignMalloc() +** indicates that subsequent malloc failures are non-benign. +*/ +SQLITE_PRIVATE void sqlite3BeginBenignMalloc(void){ + wsdHooksInit; + if( wsdHooks.xBenignBegin ){ + wsdHooks.xBenignBegin(); + } +} +SQLITE_PRIVATE void sqlite3EndBenignMalloc(void){ + wsdHooksInit; + if( wsdHooks.xBenignEnd ){ + wsdHooks.xBenignEnd(); + } +} + +#endif /* #ifndef SQLITE_UNTESTABLE */ + +/************** End of fault.c ***********************************************/ +/************** Begin file mem0.c ********************************************/ +/* +** 2008 October 28 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains a no-op memory allocation drivers for use when +** SQLITE_ZERO_MALLOC is defined. The allocation drivers implemented +** here always fail. SQLite will not operate with these drivers. These +** are merely placeholders. Real drivers must be substituted using +** sqlite3_config() before SQLite will operate. +*/ +/* #include "sqliteInt.h" */ + +/* +** This version of the memory allocator is the default. It is +** used when no other memory allocator is specified using compile-time +** macros. +*/ +#ifdef SQLITE_ZERO_MALLOC + +/* +** No-op versions of all memory allocation routines +*/ +static void *sqlite3MemMalloc(int nByte){ return 0; } +static void sqlite3MemFree(void *pPrior){ return; } +static void *sqlite3MemRealloc(void *pPrior, int nByte){ return 0; } +static int sqlite3MemSize(void *pPrior){ return 0; } +static int sqlite3MemRoundup(int n){ return n; } +static int sqlite3MemInit(void *NotUsed){ return SQLITE_OK; } +static void sqlite3MemShutdown(void *NotUsed){ return; } + +/* +** This routine is the only routine in this file with external linkage. +** +** Populate the low-level memory allocation function pointers in +** sqlite3GlobalConfig.m with pointers to the routines in this file. +*/ +SQLITE_PRIVATE void sqlite3MemSetDefault(void){ + static const sqlite3_mem_methods defaultMethods = { + sqlite3MemMalloc, + sqlite3MemFree, + sqlite3MemRealloc, + sqlite3MemSize, + sqlite3MemRoundup, + sqlite3MemInit, + sqlite3MemShutdown, + 0 + }; + sqlite3_config(SQLITE_CONFIG_MALLOC, &defaultMethods); +} + +#endif /* SQLITE_ZERO_MALLOC */ + +/************** End of mem0.c ************************************************/ +/************** Begin file mem1.c ********************************************/ +/* +** 2007 August 14 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains low-level memory allocation drivers for when +** SQLite will use the standard C-library malloc/realloc/free interface +** to obtain the memory it needs. +** +** This file contains implementations of the low-level memory allocation +** routines specified in the sqlite3_mem_methods object. The content of +** this file is only used if SQLITE_SYSTEM_MALLOC is defined. The +** SQLITE_SYSTEM_MALLOC macro is defined automatically if neither the +** SQLITE_MEMDEBUG nor the SQLITE_WIN32_MALLOC macros are defined. The +** default configuration is to use memory allocation routines in this +** file. +** +** C-preprocessor macro summary: +** +** HAVE_MALLOC_USABLE_SIZE The configure script sets this symbol if +** the malloc_usable_size() interface exists +** on the target platform. Or, this symbol +** can be set manually, if desired. +** If an equivalent interface exists by +** a different name, using a separate -D +** option to rename it. +** +** SQLITE_WITHOUT_ZONEMALLOC Some older macs lack support for the zone +** memory allocator. Set this symbol to enable +** building on older macs. +** +** SQLITE_WITHOUT_MSIZE Set this symbol to disable the use of +** _msize() on windows systems. This might +** be necessary when compiling for Delphi, +** for example. +*/ +/* #include "sqliteInt.h" */ + +/* +** This version of the memory allocator is the default. It is +** used when no other memory allocator is specified using compile-time +** macros. +*/ +#ifdef SQLITE_SYSTEM_MALLOC +#if defined(__APPLE__) && !defined(SQLITE_WITHOUT_ZONEMALLOC) + +/* +** Use the zone allocator available on apple products unless the +** SQLITE_WITHOUT_ZONEMALLOC symbol is defined. +*/ +#include <sys/sysctl.h> +#include <malloc/malloc.h> +#ifdef SQLITE_MIGHT_BE_SINGLE_CORE +#include <libkern/OSAtomic.h> +#endif /* SQLITE_MIGHT_BE_SINGLE_CORE */ +static malloc_zone_t* _sqliteZone_; +#define SQLITE_MALLOC(x) malloc_zone_malloc(_sqliteZone_, (x)) +#define SQLITE_FREE(x) malloc_zone_free(_sqliteZone_, (x)); +#define SQLITE_REALLOC(x,y) malloc_zone_realloc(_sqliteZone_, (x), (y)) +#define SQLITE_MALLOCSIZE(x) \ + (_sqliteZone_ ? _sqliteZone_->size(_sqliteZone_,x) : malloc_size(x)) + +#else /* if not __APPLE__ */ + +/* +** Use standard C library malloc and free on non-Apple systems. +** Also used by Apple systems if SQLITE_WITHOUT_ZONEMALLOC is defined. +*/ +#define SQLITE_MALLOC(x) malloc(x) +#define SQLITE_FREE(x) free(x) +#define SQLITE_REALLOC(x,y) realloc((x),(y)) + +/* +** The malloc.h header file is needed for malloc_usable_size() function +** on some systems (e.g. Linux). +*/ +#if HAVE_MALLOC_H && HAVE_MALLOC_USABLE_SIZE +# define SQLITE_USE_MALLOC_H 1 +# define SQLITE_USE_MALLOC_USABLE_SIZE 1 +/* +** The MSVCRT has malloc_usable_size(), but it is called _msize(). The +** use of _msize() is automatic, but can be disabled by compiling with +** -DSQLITE_WITHOUT_MSIZE. Using the _msize() function also requires +** the malloc.h header file. +*/ +#elif defined(_MSC_VER) && !defined(SQLITE_WITHOUT_MSIZE) +# define SQLITE_USE_MALLOC_H +# define SQLITE_USE_MSIZE +#endif + +/* +** Include the malloc.h header file, if necessary. Also set define macro +** SQLITE_MALLOCSIZE to the appropriate function name, which is _msize() +** for MSVC and malloc_usable_size() for most other systems (e.g. Linux). +** The memory size function can always be overridden manually by defining +** the macro SQLITE_MALLOCSIZE to the desired function name. +*/ +#if defined(SQLITE_USE_MALLOC_H) +# include <malloc.h> +# if defined(SQLITE_USE_MALLOC_USABLE_SIZE) +# if !defined(SQLITE_MALLOCSIZE) +# define SQLITE_MALLOCSIZE(x) malloc_usable_size(x) +# endif +# elif defined(SQLITE_USE_MSIZE) +# if !defined(SQLITE_MALLOCSIZE) +# define SQLITE_MALLOCSIZE _msize +# endif +# endif +#endif /* defined(SQLITE_USE_MALLOC_H) */ + +#endif /* __APPLE__ or not __APPLE__ */ + +/* +** Like malloc(), but remember the size of the allocation +** so that we can find it later using sqlite3MemSize(). +** +** For this low-level routine, we are guaranteed that nByte>0 because +** cases of nByte<=0 will be intercepted and dealt with by higher level +** routines. +*/ +static void *sqlite3MemMalloc(int nByte){ +#ifdef SQLITE_MALLOCSIZE + void *p; + testcase( ROUND8(nByte)==nByte ); + p = SQLITE_MALLOC( nByte ); + if( p==0 ){ + testcase( sqlite3GlobalConfig.xLog!=0 ); + sqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes of memory", nByte); + } + return p; +#else + sqlite3_int64 *p; + assert( nByte>0 ); + testcase( ROUND8(nByte)!=nByte ); + p = SQLITE_MALLOC( nByte+8 ); + if( p ){ + p[0] = nByte; + p++; + }else{ + testcase( sqlite3GlobalConfig.xLog!=0 ); + sqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes of memory", nByte); + } + return (void *)p; +#endif +} + +/* +** Like free() but works for allocations obtained from sqlite3MemMalloc() +** or sqlite3MemRealloc(). +** +** For this low-level routine, we already know that pPrior!=0 since +** cases where pPrior==0 will have been intercepted and dealt with +** by higher-level routines. +*/ +static void sqlite3MemFree(void *pPrior){ +#ifdef SQLITE_MALLOCSIZE + SQLITE_FREE(pPrior); +#else + sqlite3_int64 *p = (sqlite3_int64*)pPrior; + assert( pPrior!=0 ); + p--; + SQLITE_FREE(p); +#endif +} + +/* +** Report the allocated size of a prior return from xMalloc() +** or xRealloc(). +*/ +static int sqlite3MemSize(void *pPrior){ +#ifdef SQLITE_MALLOCSIZE + assert( pPrior!=0 ); + return (int)SQLITE_MALLOCSIZE(pPrior); +#else + sqlite3_int64 *p; + assert( pPrior!=0 ); + p = (sqlite3_int64*)pPrior; + p--; + return (int)p[0]; +#endif +} + +/* +** Like realloc(). Resize an allocation previously obtained from +** sqlite3MemMalloc(). +** +** For this low-level interface, we know that pPrior!=0. Cases where +** pPrior==0 while have been intercepted by higher-level routine and +** redirected to xMalloc. Similarly, we know that nByte>0 because +** cases where nByte<=0 will have been intercepted by higher-level +** routines and redirected to xFree. +*/ +static void *sqlite3MemRealloc(void *pPrior, int nByte){ +#ifdef SQLITE_MALLOCSIZE + void *p = SQLITE_REALLOC(pPrior, nByte); + if( p==0 ){ + testcase( sqlite3GlobalConfig.xLog!=0 ); + sqlite3_log(SQLITE_NOMEM, + "failed memory resize %u to %u bytes", + SQLITE_MALLOCSIZE(pPrior), nByte); + } + return p; +#else + sqlite3_int64 *p = (sqlite3_int64*)pPrior; + assert( pPrior!=0 && nByte>0 ); + assert( nByte==ROUND8(nByte) ); /* EV: R-46199-30249 */ + p--; + p = SQLITE_REALLOC(p, nByte+8 ); + if( p ){ + p[0] = nByte; + p++; + }else{ + testcase( sqlite3GlobalConfig.xLog!=0 ); + sqlite3_log(SQLITE_NOMEM, + "failed memory resize %u to %u bytes", + sqlite3MemSize(pPrior), nByte); + } + return (void*)p; +#endif +} + +/* +** Round up a request size to the next valid allocation size. +*/ +static int sqlite3MemRoundup(int n){ + return ROUND8(n); +} + +/* +** Initialize this module. +*/ +static int sqlite3MemInit(void *NotUsed){ +#if defined(__APPLE__) && !defined(SQLITE_WITHOUT_ZONEMALLOC) + int cpuCount; + size_t len; + if( _sqliteZone_ ){ + return SQLITE_OK; + } + len = sizeof(cpuCount); + /* One usually wants to use hw.activecpu for MT decisions, but not here */ + sysctlbyname("hw.ncpu", &cpuCount, &len, NULL, 0); + if( cpuCount>1 ){ + /* defer MT decisions to system malloc */ + _sqliteZone_ = malloc_default_zone(); + }else{ + /* only 1 core, use our own zone to contention over global locks, + ** e.g. we have our own dedicated locks */ + _sqliteZone_ = malloc_create_zone(4096, 0); + malloc_set_zone_name(_sqliteZone_, "Sqlite_Heap"); + } +#endif /* defined(__APPLE__) && !defined(SQLITE_WITHOUT_ZONEMALLOC) */ + UNUSED_PARAMETER(NotUsed); + return SQLITE_OK; +} + +/* +** Deinitialize this module. +*/ +static void sqlite3MemShutdown(void *NotUsed){ + UNUSED_PARAMETER(NotUsed); + return; +} + +/* +** This routine is the only routine in this file with external linkage. +** +** Populate the low-level memory allocation function pointers in +** sqlite3GlobalConfig.m with pointers to the routines in this file. +*/ +SQLITE_PRIVATE void sqlite3MemSetDefault(void){ + static const sqlite3_mem_methods defaultMethods = { + sqlite3MemMalloc, + sqlite3MemFree, + sqlite3MemRealloc, + sqlite3MemSize, + sqlite3MemRoundup, + sqlite3MemInit, + sqlite3MemShutdown, + 0 + }; + sqlite3_config(SQLITE_CONFIG_MALLOC, &defaultMethods); +} + +#endif /* SQLITE_SYSTEM_MALLOC */ + +/************** End of mem1.c ************************************************/ +/************** Begin file mem2.c ********************************************/ +/* +** 2007 August 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains low-level memory allocation drivers for when +** SQLite will use the standard C-library malloc/realloc/free interface +** to obtain the memory it needs while adding lots of additional debugging +** information to each allocation in order to help detect and fix memory +** leaks and memory usage errors. +** +** This file contains implementations of the low-level memory allocation +** routines specified in the sqlite3_mem_methods object. +*/ +/* #include "sqliteInt.h" */ + +/* +** This version of the memory allocator is used only if the +** SQLITE_MEMDEBUG macro is defined +*/ +#ifdef SQLITE_MEMDEBUG + +/* +** The backtrace functionality is only available with GLIBC +*/ +#ifdef __GLIBC__ + extern int backtrace(void**,int); + extern void backtrace_symbols_fd(void*const*,int,int); +#else +# define backtrace(A,B) 1 +# define backtrace_symbols_fd(A,B,C) +#endif +/* #include <stdio.h> */ + +/* +** Each memory allocation looks like this: +** +** ------------------------------------------------------------------------ +** | Title | backtrace pointers | MemBlockHdr | allocation | EndGuard | +** ------------------------------------------------------------------------ +** +** The application code sees only a pointer to the allocation. We have +** to back up from the allocation pointer to find the MemBlockHdr. The +** MemBlockHdr tells us the size of the allocation and the number of +** backtrace pointers. There is also a guard word at the end of the +** MemBlockHdr. +*/ +struct MemBlockHdr { + i64 iSize; /* Size of this allocation */ + struct MemBlockHdr *pNext, *pPrev; /* Linked list of all unfreed memory */ + char nBacktrace; /* Number of backtraces on this alloc */ + char nBacktraceSlots; /* Available backtrace slots */ + u8 nTitle; /* Bytes of title; includes '\0' */ + u8 eType; /* Allocation type code */ + int iForeGuard; /* Guard word for sanity */ +}; + +/* +** Guard words +*/ +#define FOREGUARD 0x80F5E153 +#define REARGUARD 0xE4676B53 + +/* +** Number of malloc size increments to track. +*/ +#define NCSIZE 1000 + +/* +** All of the static variables used by this module are collected +** into a single structure named "mem". This is to keep the +** static variables organized and to reduce namespace pollution +** when this module is combined with other in the amalgamation. +*/ +static struct { + + /* + ** Mutex to control access to the memory allocation subsystem. + */ + sqlite3_mutex *mutex; + + /* + ** Head and tail of a linked list of all outstanding allocations + */ + struct MemBlockHdr *pFirst; + struct MemBlockHdr *pLast; + + /* + ** The number of levels of backtrace to save in new allocations. + */ + int nBacktrace; + void (*xBacktrace)(int, int, void **); + + /* + ** Title text to insert in front of each block + */ + int nTitle; /* Bytes of zTitle to save. Includes '\0' and padding */ + char zTitle[100]; /* The title text */ + + /* + ** sqlite3MallocDisallow() increments the following counter. + ** sqlite3MallocAllow() decrements it. + */ + int disallow; /* Do not allow memory allocation */ + + /* + ** Gather statistics on the sizes of memory allocations. + ** nAlloc[i] is the number of allocation attempts of i*8 + ** bytes. i==NCSIZE is the number of allocation attempts for + ** sizes more than NCSIZE*8 bytes. + */ + int nAlloc[NCSIZE]; /* Total number of allocations */ + int nCurrent[NCSIZE]; /* Current number of allocations */ + int mxCurrent[NCSIZE]; /* Highwater mark for nCurrent */ + +} mem; + + +/* +** Adjust memory usage statistics +*/ +static void adjustStats(int iSize, int increment){ + int i = ROUND8(iSize)/8; + if( i>NCSIZE-1 ){ + i = NCSIZE - 1; + } + if( increment>0 ){ + mem.nAlloc[i]++; + mem.nCurrent[i]++; + if( mem.nCurrent[i]>mem.mxCurrent[i] ){ + mem.mxCurrent[i] = mem.nCurrent[i]; + } + }else{ + mem.nCurrent[i]--; + assert( mem.nCurrent[i]>=0 ); + } +} + +/* +** Given an allocation, find the MemBlockHdr for that allocation. +** +** This routine checks the guards at either end of the allocation and +** if they are incorrect it asserts. +*/ +static struct MemBlockHdr *sqlite3MemsysGetHeader(const void *pAllocation){ + struct MemBlockHdr *p; + int *pInt; + u8 *pU8; + int nReserve; + + p = (struct MemBlockHdr*)pAllocation; + p--; + assert( p->iForeGuard==(int)FOREGUARD ); + nReserve = ROUND8(p->iSize); + pInt = (int*)pAllocation; + pU8 = (u8*)pAllocation; + assert( pInt[nReserve/sizeof(int)]==(int)REARGUARD ); + /* This checks any of the "extra" bytes allocated due + ** to rounding up to an 8 byte boundary to ensure + ** they haven't been overwritten. + */ + while( nReserve-- > p->iSize ) assert( pU8[nReserve]==0x65 ); + return p; +} + +/* +** Return the number of bytes currently allocated at address p. +*/ +static int sqlite3MemSize(void *p){ + struct MemBlockHdr *pHdr; + if( !p ){ + return 0; + } + pHdr = sqlite3MemsysGetHeader(p); + return (int)pHdr->iSize; +} + +/* +** Initialize the memory allocation subsystem. +*/ +static int sqlite3MemInit(void *NotUsed){ + UNUSED_PARAMETER(NotUsed); + assert( (sizeof(struct MemBlockHdr)&7) == 0 ); + if( !sqlite3GlobalConfig.bMemstat ){ + /* If memory status is enabled, then the malloc.c wrapper will already + ** hold the STATIC_MEM mutex when the routines here are invoked. */ + mem.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); + } + return SQLITE_OK; +} + +/* +** Deinitialize the memory allocation subsystem. +*/ +static void sqlite3MemShutdown(void *NotUsed){ + UNUSED_PARAMETER(NotUsed); + mem.mutex = 0; +} + +/* +** Round up a request size to the next valid allocation size. +*/ +static int sqlite3MemRoundup(int n){ + return ROUND8(n); +} + +/* +** Fill a buffer with pseudo-random bytes. This is used to preset +** the content of a new memory allocation to unpredictable values and +** to clear the content of a freed allocation to unpredictable values. +*/ +static void randomFill(char *pBuf, int nByte){ + unsigned int x, y, r; + x = SQLITE_PTR_TO_INT(pBuf); + y = nByte | 1; + while( nByte >= 4 ){ + x = (x>>1) ^ (-(int)(x&1) & 0xd0000001); + y = y*1103515245 + 12345; + r = x ^ y; + *(int*)pBuf = r; + pBuf += 4; + nByte -= 4; + } + while( nByte-- > 0 ){ + x = (x>>1) ^ (-(int)(x&1) & 0xd0000001); + y = y*1103515245 + 12345; + r = x ^ y; + *(pBuf++) = r & 0xff; + } +} + +/* +** Allocate nByte bytes of memory. +*/ +static void *sqlite3MemMalloc(int nByte){ + struct MemBlockHdr *pHdr; + void **pBt; + char *z; + int *pInt; + void *p = 0; + int totalSize; + int nReserve; + sqlite3_mutex_enter(mem.mutex); + assert( mem.disallow==0 ); + nReserve = ROUND8(nByte); + totalSize = nReserve + sizeof(*pHdr) + sizeof(int) + + mem.nBacktrace*sizeof(void*) + mem.nTitle; + p = malloc(totalSize); + if( p ){ + z = p; + pBt = (void**)&z[mem.nTitle]; + pHdr = (struct MemBlockHdr*)&pBt[mem.nBacktrace]; + pHdr->pNext = 0; + pHdr->pPrev = mem.pLast; + if( mem.pLast ){ + mem.pLast->pNext = pHdr; + }else{ + mem.pFirst = pHdr; + } + mem.pLast = pHdr; + pHdr->iForeGuard = FOREGUARD; + pHdr->eType = MEMTYPE_HEAP; + pHdr->nBacktraceSlots = mem.nBacktrace; + pHdr->nTitle = mem.nTitle; + if( mem.nBacktrace ){ + void *aAddr[40]; + pHdr->nBacktrace = backtrace(aAddr, mem.nBacktrace+1)-1; + memcpy(pBt, &aAddr[1], pHdr->nBacktrace*sizeof(void*)); + assert(pBt[0]); + if( mem.xBacktrace ){ + mem.xBacktrace(nByte, pHdr->nBacktrace-1, &aAddr[1]); + } + }else{ + pHdr->nBacktrace = 0; + } + if( mem.nTitle ){ + memcpy(z, mem.zTitle, mem.nTitle); + } + pHdr->iSize = nByte; + adjustStats(nByte, +1); + pInt = (int*)&pHdr[1]; + pInt[nReserve/sizeof(int)] = REARGUARD; + randomFill((char*)pInt, nByte); + memset(((char*)pInt)+nByte, 0x65, nReserve-nByte); + p = (void*)pInt; + } + sqlite3_mutex_leave(mem.mutex); + return p; +} + +/* +** Free memory. +*/ +static void sqlite3MemFree(void *pPrior){ + struct MemBlockHdr *pHdr; + void **pBt; + char *z; + assert( sqlite3GlobalConfig.bMemstat || sqlite3GlobalConfig.bCoreMutex==0 + || mem.mutex!=0 ); + pHdr = sqlite3MemsysGetHeader(pPrior); + pBt = (void**)pHdr; + pBt -= pHdr->nBacktraceSlots; + sqlite3_mutex_enter(mem.mutex); + if( pHdr->pPrev ){ + assert( pHdr->pPrev->pNext==pHdr ); + pHdr->pPrev->pNext = pHdr->pNext; + }else{ + assert( mem.pFirst==pHdr ); + mem.pFirst = pHdr->pNext; + } + if( pHdr->pNext ){ + assert( pHdr->pNext->pPrev==pHdr ); + pHdr->pNext->pPrev = pHdr->pPrev; + }else{ + assert( mem.pLast==pHdr ); + mem.pLast = pHdr->pPrev; + } + z = (char*)pBt; + z -= pHdr->nTitle; + adjustStats((int)pHdr->iSize, -1); + randomFill(z, sizeof(void*)*pHdr->nBacktraceSlots + sizeof(*pHdr) + + (int)pHdr->iSize + sizeof(int) + pHdr->nTitle); + free(z); + sqlite3_mutex_leave(mem.mutex); +} + +/* +** Change the size of an existing memory allocation. +** +** For this debugging implementation, we *always* make a copy of the +** allocation into a new place in memory. In this way, if the +** higher level code is using pointer to the old allocation, it is +** much more likely to break and we are much more liking to find +** the error. +*/ +static void *sqlite3MemRealloc(void *pPrior, int nByte){ + struct MemBlockHdr *pOldHdr; + void *pNew; + assert( mem.disallow==0 ); + assert( (nByte & 7)==0 ); /* EV: R-46199-30249 */ + pOldHdr = sqlite3MemsysGetHeader(pPrior); + pNew = sqlite3MemMalloc(nByte); + if( pNew ){ + memcpy(pNew, pPrior, (int)(nByte<pOldHdr->iSize ? nByte : pOldHdr->iSize)); + if( nByte>pOldHdr->iSize ){ + randomFill(&((char*)pNew)[pOldHdr->iSize], nByte - (int)pOldHdr->iSize); + } + sqlite3MemFree(pPrior); + } + return pNew; +} + +/* +** Populate the low-level memory allocation function pointers in +** sqlite3GlobalConfig.m with pointers to the routines in this file. +*/ +SQLITE_PRIVATE void sqlite3MemSetDefault(void){ + static const sqlite3_mem_methods defaultMethods = { + sqlite3MemMalloc, + sqlite3MemFree, + sqlite3MemRealloc, + sqlite3MemSize, + sqlite3MemRoundup, + sqlite3MemInit, + sqlite3MemShutdown, + 0 + }; + sqlite3_config(SQLITE_CONFIG_MALLOC, &defaultMethods); +} + +/* +** Set the "type" of an allocation. +*/ +SQLITE_PRIVATE void sqlite3MemdebugSetType(void *p, u8 eType){ + if( p && sqlite3GlobalConfig.m.xFree==sqlite3MemFree ){ + struct MemBlockHdr *pHdr; + pHdr = sqlite3MemsysGetHeader(p); + assert( pHdr->iForeGuard==FOREGUARD ); + pHdr->eType = eType; + } +} + +/* +** Return TRUE if the mask of type in eType matches the type of the +** allocation p. Also return true if p==NULL. +** +** This routine is designed for use within an assert() statement, to +** verify the type of an allocation. For example: +** +** assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); +*/ +SQLITE_PRIVATE int sqlite3MemdebugHasType(const void *p, u8 eType){ + int rc = 1; + if( p && sqlite3GlobalConfig.m.xFree==sqlite3MemFree ){ + struct MemBlockHdr *pHdr; + pHdr = sqlite3MemsysGetHeader(p); + assert( pHdr->iForeGuard==FOREGUARD ); /* Allocation is valid */ + if( (pHdr->eType&eType)==0 ){ + rc = 0; + } + } + return rc; +} + +/* +** Return TRUE if the mask of type in eType matches no bits of the type of the +** allocation p. Also return true if p==NULL. +** +** This routine is designed for use within an assert() statement, to +** verify the type of an allocation. For example: +** +** assert( sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) ); +*/ +SQLITE_PRIVATE int sqlite3MemdebugNoType(const void *p, u8 eType){ + int rc = 1; + if( p && sqlite3GlobalConfig.m.xFree==sqlite3MemFree ){ + struct MemBlockHdr *pHdr; + pHdr = sqlite3MemsysGetHeader(p); + assert( pHdr->iForeGuard==FOREGUARD ); /* Allocation is valid */ + if( (pHdr->eType&eType)!=0 ){ + rc = 0; + } + } + return rc; +} + +/* +** Set the number of backtrace levels kept for each allocation. +** A value of zero turns off backtracing. The number is always rounded +** up to a multiple of 2. +*/ +SQLITE_PRIVATE void sqlite3MemdebugBacktrace(int depth){ + if( depth<0 ){ depth = 0; } + if( depth>20 ){ depth = 20; } + depth = (depth+1)&0xfe; + mem.nBacktrace = depth; +} + +SQLITE_PRIVATE void sqlite3MemdebugBacktraceCallback(void (*xBacktrace)(int, int, void **)){ + mem.xBacktrace = xBacktrace; +} + +/* +** Set the title string for subsequent allocations. +*/ +SQLITE_PRIVATE void sqlite3MemdebugSettitle(const char *zTitle){ + unsigned int n = sqlite3Strlen30(zTitle) + 1; + sqlite3_mutex_enter(mem.mutex); + if( n>=sizeof(mem.zTitle) ) n = sizeof(mem.zTitle)-1; + memcpy(mem.zTitle, zTitle, n); + mem.zTitle[n] = 0; + mem.nTitle = ROUND8(n); + sqlite3_mutex_leave(mem.mutex); +} + +SQLITE_PRIVATE void sqlite3MemdebugSync(){ + struct MemBlockHdr *pHdr; + for(pHdr=mem.pFirst; pHdr; pHdr=pHdr->pNext){ + void **pBt = (void**)pHdr; + pBt -= pHdr->nBacktraceSlots; + mem.xBacktrace((int)pHdr->iSize, pHdr->nBacktrace-1, &pBt[1]); + } +} + +/* +** Open the file indicated and write a log of all unfreed memory +** allocations into that log. +*/ +SQLITE_PRIVATE void sqlite3MemdebugDump(const char *zFilename){ + FILE *out; + struct MemBlockHdr *pHdr; + void **pBt; + int i; + out = fopen(zFilename, "w"); + if( out==0 ){ + fprintf(stderr, "** Unable to output memory debug output log: %s **\n", + zFilename); + return; + } + for(pHdr=mem.pFirst; pHdr; pHdr=pHdr->pNext){ + char *z = (char*)pHdr; + z -= pHdr->nBacktraceSlots*sizeof(void*) + pHdr->nTitle; + fprintf(out, "**** %lld bytes at %p from %s ****\n", + pHdr->iSize, &pHdr[1], pHdr->nTitle ? z : "???"); + if( pHdr->nBacktrace ){ + fflush(out); + pBt = (void**)pHdr; + pBt -= pHdr->nBacktraceSlots; + backtrace_symbols_fd(pBt, pHdr->nBacktrace, fileno(out)); + fprintf(out, "\n"); + } + } + fprintf(out, "COUNTS:\n"); + for(i=0; i<NCSIZE-1; i++){ + if( mem.nAlloc[i] ){ + fprintf(out, " %5d: %10d %10d %10d\n", + i*8, mem.nAlloc[i], mem.nCurrent[i], mem.mxCurrent[i]); + } + } + if( mem.nAlloc[NCSIZE-1] ){ + fprintf(out, " %5d: %10d %10d %10d\n", + NCSIZE*8-8, mem.nAlloc[NCSIZE-1], + mem.nCurrent[NCSIZE-1], mem.mxCurrent[NCSIZE-1]); + } + fclose(out); +} + +/* +** Return the number of times sqlite3MemMalloc() has been called. +*/ +SQLITE_PRIVATE int sqlite3MemdebugMallocCount(){ + int i; + int nTotal = 0; + for(i=0; i<NCSIZE; i++){ + nTotal += mem.nAlloc[i]; + } + return nTotal; +} + + +#endif /* SQLITE_MEMDEBUG */ + +/************** End of mem2.c ************************************************/ +/************** Begin file mem3.c ********************************************/ +/* +** 2007 October 14 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement a memory +** allocation subsystem for use by SQLite. +** +** This version of the memory allocation subsystem omits all +** use of malloc(). The SQLite user supplies a block of memory +** before calling sqlite3_initialize() from which allocations +** are made and returned by the xMalloc() and xRealloc() +** implementations. Once sqlite3_initialize() has been called, +** the amount of memory available to SQLite is fixed and cannot +** be changed. +** +** This version of the memory allocation subsystem is included +** in the build only if SQLITE_ENABLE_MEMSYS3 is defined. +*/ +/* #include "sqliteInt.h" */ + +/* +** This version of the memory allocator is only built into the library +** SQLITE_ENABLE_MEMSYS3 is defined. Defining this symbol does not +** mean that the library will use a memory-pool by default, just that +** it is available. The mempool allocator is activated by calling +** sqlite3_config(). +*/ +#ifdef SQLITE_ENABLE_MEMSYS3 + +/* +** Maximum size (in Mem3Blocks) of a "small" chunk. +*/ +#define MX_SMALL 10 + + +/* +** Number of freelist hash slots +*/ +#define N_HASH 61 + +/* +** A memory allocation (also called a "chunk") consists of two or +** more blocks where each block is 8 bytes. The first 8 bytes are +** a header that is not returned to the user. +** +** A chunk is two or more blocks that is either checked out or +** free. The first block has format u.hdr. u.hdr.size4x is 4 times the +** size of the allocation in blocks if the allocation is free. +** The u.hdr.size4x&1 bit is true if the chunk is checked out and +** false if the chunk is on the freelist. The u.hdr.size4x&2 bit +** is true if the previous chunk is checked out and false if the +** previous chunk is free. The u.hdr.prevSize field is the size of +** the previous chunk in blocks if the previous chunk is on the +** freelist. If the previous chunk is checked out, then +** u.hdr.prevSize can be part of the data for that chunk and should +** not be read or written. +** +** We often identify a chunk by its index in mem3.aPool[]. When +** this is done, the chunk index refers to the second block of +** the chunk. In this way, the first chunk has an index of 1. +** A chunk index of 0 means "no such chunk" and is the equivalent +** of a NULL pointer. +** +** The second block of free chunks is of the form u.list. The +** two fields form a double-linked list of chunks of related sizes. +** Pointers to the head of the list are stored in mem3.aiSmall[] +** for smaller chunks and mem3.aiHash[] for larger chunks. +** +** The second block of a chunk is user data if the chunk is checked +** out. If a chunk is checked out, the user data may extend into +** the u.hdr.prevSize value of the following chunk. +*/ +typedef struct Mem3Block Mem3Block; +struct Mem3Block { + union { + struct { + u32 prevSize; /* Size of previous chunk in Mem3Block elements */ + u32 size4x; /* 4x the size of current chunk in Mem3Block elements */ + } hdr; + struct { + u32 next; /* Index in mem3.aPool[] of next free chunk */ + u32 prev; /* Index in mem3.aPool[] of previous free chunk */ + } list; + } u; +}; + +/* +** All of the static variables used by this module are collected +** into a single structure named "mem3". This is to keep the +** static variables organized and to reduce namespace pollution +** when this module is combined with other in the amalgamation. +*/ +static SQLITE_WSD struct Mem3Global { + /* + ** Memory available for allocation. nPool is the size of the array + ** (in Mem3Blocks) pointed to by aPool less 2. + */ + u32 nPool; + Mem3Block *aPool; + + /* + ** True if we are evaluating an out-of-memory callback. + */ + int alarmBusy; + + /* + ** Mutex to control access to the memory allocation subsystem. + */ + sqlite3_mutex *mutex; + + /* + ** The minimum amount of free space that we have seen. + */ + u32 mnKeyBlk; + + /* + ** iKeyBlk is the index of the key chunk. Most new allocations + ** occur off of this chunk. szKeyBlk is the size (in Mem3Blocks) + ** of the current key chunk. iKeyBlk is 0 if there is no key chunk. + ** The key chunk is not in either the aiHash[] or aiSmall[]. + */ + u32 iKeyBlk; + u32 szKeyBlk; + + /* + ** Array of lists of free blocks according to the block size + ** for smaller chunks, or a hash on the block size for larger + ** chunks. + */ + u32 aiSmall[MX_SMALL-1]; /* For sizes 2 through MX_SMALL, inclusive */ + u32 aiHash[N_HASH]; /* For sizes MX_SMALL+1 and larger */ +} mem3 = { 97535575 }; + +#define mem3 GLOBAL(struct Mem3Global, mem3) + +/* +** Unlink the chunk at mem3.aPool[i] from list it is currently +** on. *pRoot is the list that i is a member of. +*/ +static void memsys3UnlinkFromList(u32 i, u32 *pRoot){ + u32 next = mem3.aPool[i].u.list.next; + u32 prev = mem3.aPool[i].u.list.prev; + assert( sqlite3_mutex_held(mem3.mutex) ); + if( prev==0 ){ + *pRoot = next; + }else{ + mem3.aPool[prev].u.list.next = next; + } + if( next ){ + mem3.aPool[next].u.list.prev = prev; + } + mem3.aPool[i].u.list.next = 0; + mem3.aPool[i].u.list.prev = 0; +} + +/* +** Unlink the chunk at index i from +** whatever list is currently a member of. +*/ +static void memsys3Unlink(u32 i){ + u32 size, hash; + assert( sqlite3_mutex_held(mem3.mutex) ); + assert( (mem3.aPool[i-1].u.hdr.size4x & 1)==0 ); + assert( i>=1 ); + size = mem3.aPool[i-1].u.hdr.size4x/4; + assert( size==mem3.aPool[i+size-1].u.hdr.prevSize ); + assert( size>=2 ); + if( size <= MX_SMALL ){ + memsys3UnlinkFromList(i, &mem3.aiSmall[size-2]); + }else{ + hash = size % N_HASH; + memsys3UnlinkFromList(i, &mem3.aiHash[hash]); + } +} + +/* +** Link the chunk at mem3.aPool[i] so that is on the list rooted +** at *pRoot. +*/ +static void memsys3LinkIntoList(u32 i, u32 *pRoot){ + assert( sqlite3_mutex_held(mem3.mutex) ); + mem3.aPool[i].u.list.next = *pRoot; + mem3.aPool[i].u.list.prev = 0; + if( *pRoot ){ + mem3.aPool[*pRoot].u.list.prev = i; + } + *pRoot = i; +} + +/* +** Link the chunk at index i into either the appropriate +** small chunk list, or into the large chunk hash table. +*/ +static void memsys3Link(u32 i){ + u32 size, hash; + assert( sqlite3_mutex_held(mem3.mutex) ); + assert( i>=1 ); + assert( (mem3.aPool[i-1].u.hdr.size4x & 1)==0 ); + size = mem3.aPool[i-1].u.hdr.size4x/4; + assert( size==mem3.aPool[i+size-1].u.hdr.prevSize ); + assert( size>=2 ); + if( size <= MX_SMALL ){ + memsys3LinkIntoList(i, &mem3.aiSmall[size-2]); + }else{ + hash = size % N_HASH; + memsys3LinkIntoList(i, &mem3.aiHash[hash]); + } +} + +/* +** If the STATIC_MEM mutex is not already held, obtain it now. The mutex +** will already be held (obtained by code in malloc.c) if +** sqlite3GlobalConfig.bMemStat is true. +*/ +static void memsys3Enter(void){ + if( sqlite3GlobalConfig.bMemstat==0 && mem3.mutex==0 ){ + mem3.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); + } + sqlite3_mutex_enter(mem3.mutex); +} +static void memsys3Leave(void){ + sqlite3_mutex_leave(mem3.mutex); +} + +/* +** Called when we are unable to satisfy an allocation of nBytes. +*/ +static void memsys3OutOfMemory(int nByte){ + if( !mem3.alarmBusy ){ + mem3.alarmBusy = 1; + assert( sqlite3_mutex_held(mem3.mutex) ); + sqlite3_mutex_leave(mem3.mutex); + sqlite3_release_memory(nByte); + sqlite3_mutex_enter(mem3.mutex); + mem3.alarmBusy = 0; + } +} + + +/* +** Chunk i is a free chunk that has been unlinked. Adjust its +** size parameters for check-out and return a pointer to the +** user portion of the chunk. +*/ +static void *memsys3Checkout(u32 i, u32 nBlock){ + u32 x; + assert( sqlite3_mutex_held(mem3.mutex) ); + assert( i>=1 ); + assert( mem3.aPool[i-1].u.hdr.size4x/4==nBlock ); + assert( mem3.aPool[i+nBlock-1].u.hdr.prevSize==nBlock ); + x = mem3.aPool[i-1].u.hdr.size4x; + mem3.aPool[i-1].u.hdr.size4x = nBlock*4 | 1 | (x&2); + mem3.aPool[i+nBlock-1].u.hdr.prevSize = nBlock; + mem3.aPool[i+nBlock-1].u.hdr.size4x |= 2; + return &mem3.aPool[i]; +} + +/* +** Carve a piece off of the end of the mem3.iKeyBlk free chunk. +** Return a pointer to the new allocation. Or, if the key chunk +** is not large enough, return 0. +*/ +static void *memsys3FromKeyBlk(u32 nBlock){ + assert( sqlite3_mutex_held(mem3.mutex) ); + assert( mem3.szKeyBlk>=nBlock ); + if( nBlock>=mem3.szKeyBlk-1 ){ + /* Use the entire key chunk */ + void *p = memsys3Checkout(mem3.iKeyBlk, mem3.szKeyBlk); + mem3.iKeyBlk = 0; + mem3.szKeyBlk = 0; + mem3.mnKeyBlk = 0; + return p; + }else{ + /* Split the key block. Return the tail. */ + u32 newi, x; + newi = mem3.iKeyBlk + mem3.szKeyBlk - nBlock; + assert( newi > mem3.iKeyBlk+1 ); + mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.prevSize = nBlock; + mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.size4x |= 2; + mem3.aPool[newi-1].u.hdr.size4x = nBlock*4 + 1; + mem3.szKeyBlk -= nBlock; + mem3.aPool[newi-1].u.hdr.prevSize = mem3.szKeyBlk; + x = mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x & 2; + mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x = mem3.szKeyBlk*4 | x; + if( mem3.szKeyBlk < mem3.mnKeyBlk ){ + mem3.mnKeyBlk = mem3.szKeyBlk; + } + return (void*)&mem3.aPool[newi]; + } +} + +/* +** *pRoot is the head of a list of free chunks of the same size +** or same size hash. In other words, *pRoot is an entry in either +** mem3.aiSmall[] or mem3.aiHash[]. +** +** This routine examines all entries on the given list and tries +** to coalesce each entries with adjacent free chunks. +** +** If it sees a chunk that is larger than mem3.iKeyBlk, it replaces +** the current mem3.iKeyBlk with the new larger chunk. In order for +** this mem3.iKeyBlk replacement to work, the key chunk must be +** linked into the hash tables. That is not the normal state of +** affairs, of course. The calling routine must link the key +** chunk before invoking this routine, then must unlink the (possibly +** changed) key chunk once this routine has finished. +*/ +static void memsys3Merge(u32 *pRoot){ + u32 iNext, prev, size, i, x; + + assert( sqlite3_mutex_held(mem3.mutex) ); + for(i=*pRoot; i>0; i=iNext){ + iNext = mem3.aPool[i].u.list.next; + size = mem3.aPool[i-1].u.hdr.size4x; + assert( (size&1)==0 ); + if( (size&2)==0 ){ + memsys3UnlinkFromList(i, pRoot); + assert( i > mem3.aPool[i-1].u.hdr.prevSize ); + prev = i - mem3.aPool[i-1].u.hdr.prevSize; + if( prev==iNext ){ + iNext = mem3.aPool[prev].u.list.next; + } + memsys3Unlink(prev); + size = i + size/4 - prev; + x = mem3.aPool[prev-1].u.hdr.size4x & 2; + mem3.aPool[prev-1].u.hdr.size4x = size*4 | x; + mem3.aPool[prev+size-1].u.hdr.prevSize = size; + memsys3Link(prev); + i = prev; + }else{ + size /= 4; + } + if( size>mem3.szKeyBlk ){ + mem3.iKeyBlk = i; + mem3.szKeyBlk = size; + } + } +} + +/* +** Return a block of memory of at least nBytes in size. +** Return NULL if unable. +** +** This function assumes that the necessary mutexes, if any, are +** already held by the caller. Hence "Unsafe". +*/ +static void *memsys3MallocUnsafe(int nByte){ + u32 i; + u32 nBlock; + u32 toFree; + + assert( sqlite3_mutex_held(mem3.mutex) ); + assert( sizeof(Mem3Block)==8 ); + if( nByte<=12 ){ + nBlock = 2; + }else{ + nBlock = (nByte + 11)/8; + } + assert( nBlock>=2 ); + + /* STEP 1: + ** Look for an entry of the correct size in either the small + ** chunk table or in the large chunk hash table. This is + ** successful most of the time (about 9 times out of 10). + */ + if( nBlock <= MX_SMALL ){ + i = mem3.aiSmall[nBlock-2]; + if( i>0 ){ + memsys3UnlinkFromList(i, &mem3.aiSmall[nBlock-2]); + return memsys3Checkout(i, nBlock); + } + }else{ + int hash = nBlock % N_HASH; + for(i=mem3.aiHash[hash]; i>0; i=mem3.aPool[i].u.list.next){ + if( mem3.aPool[i-1].u.hdr.size4x/4==nBlock ){ + memsys3UnlinkFromList(i, &mem3.aiHash[hash]); + return memsys3Checkout(i, nBlock); + } + } + } + + /* STEP 2: + ** Try to satisfy the allocation by carving a piece off of the end + ** of the key chunk. This step usually works if step 1 fails. + */ + if( mem3.szKeyBlk>=nBlock ){ + return memsys3FromKeyBlk(nBlock); + } + + + /* STEP 3: + ** Loop through the entire memory pool. Coalesce adjacent free + ** chunks. Recompute the key chunk as the largest free chunk. + ** Then try again to satisfy the allocation by carving a piece off + ** of the end of the key chunk. This step happens very + ** rarely (we hope!) + */ + for(toFree=nBlock*16; toFree<(mem3.nPool*16); toFree *= 2){ + memsys3OutOfMemory(toFree); + if( mem3.iKeyBlk ){ + memsys3Link(mem3.iKeyBlk); + mem3.iKeyBlk = 0; + mem3.szKeyBlk = 0; + } + for(i=0; i<N_HASH; i++){ + memsys3Merge(&mem3.aiHash[i]); + } + for(i=0; i<MX_SMALL-1; i++){ + memsys3Merge(&mem3.aiSmall[i]); + } + if( mem3.szKeyBlk ){ + memsys3Unlink(mem3.iKeyBlk); + if( mem3.szKeyBlk>=nBlock ){ + return memsys3FromKeyBlk(nBlock); + } + } + } + + /* If none of the above worked, then we fail. */ + return 0; +} + +/* +** Free an outstanding memory allocation. +** +** This function assumes that the necessary mutexes, if any, are +** already held by the caller. Hence "Unsafe". +*/ +static void memsys3FreeUnsafe(void *pOld){ + Mem3Block *p = (Mem3Block*)pOld; + int i; + u32 size, x; + assert( sqlite3_mutex_held(mem3.mutex) ); + assert( p>mem3.aPool && p<&mem3.aPool[mem3.nPool] ); + i = p - mem3.aPool; + assert( (mem3.aPool[i-1].u.hdr.size4x&1)==1 ); + size = mem3.aPool[i-1].u.hdr.size4x/4; + assert( i+size<=mem3.nPool+1 ); + mem3.aPool[i-1].u.hdr.size4x &= ~1; + mem3.aPool[i+size-1].u.hdr.prevSize = size; + mem3.aPool[i+size-1].u.hdr.size4x &= ~2; + memsys3Link(i); + + /* Try to expand the key using the newly freed chunk */ + if( mem3.iKeyBlk ){ + while( (mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x&2)==0 ){ + size = mem3.aPool[mem3.iKeyBlk-1].u.hdr.prevSize; + mem3.iKeyBlk -= size; + mem3.szKeyBlk += size; + memsys3Unlink(mem3.iKeyBlk); + x = mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x & 2; + mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x = mem3.szKeyBlk*4 | x; + mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.prevSize = mem3.szKeyBlk; + } + x = mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x & 2; + while( (mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.size4x&1)==0 ){ + memsys3Unlink(mem3.iKeyBlk+mem3.szKeyBlk); + mem3.szKeyBlk += mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.size4x/4; + mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x = mem3.szKeyBlk*4 | x; + mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.prevSize = mem3.szKeyBlk; + } + } +} + +/* +** Return the size of an outstanding allocation, in bytes. The +** size returned omits the 8-byte header overhead. This only +** works for chunks that are currently checked out. +*/ +static int memsys3Size(void *p){ + Mem3Block *pBlock; + assert( p!=0 ); + pBlock = (Mem3Block*)p; + assert( (pBlock[-1].u.hdr.size4x&1)!=0 ); + return (pBlock[-1].u.hdr.size4x&~3)*2 - 4; +} + +/* +** Round up a request size to the next valid allocation size. +*/ +static int memsys3Roundup(int n){ + if( n<=12 ){ + return 12; + }else{ + return ((n+11)&~7) - 4; + } +} + +/* +** Allocate nBytes of memory. +*/ +static void *memsys3Malloc(int nBytes){ + sqlite3_int64 *p; + assert( nBytes>0 ); /* malloc.c filters out 0 byte requests */ + memsys3Enter(); + p = memsys3MallocUnsafe(nBytes); + memsys3Leave(); + return (void*)p; +} + +/* +** Free memory. +*/ +static void memsys3Free(void *pPrior){ + assert( pPrior ); + memsys3Enter(); + memsys3FreeUnsafe(pPrior); + memsys3Leave(); +} + +/* +** Change the size of an existing memory allocation +*/ +static void *memsys3Realloc(void *pPrior, int nBytes){ + int nOld; + void *p; + if( pPrior==0 ){ + return sqlite3_malloc(nBytes); + } + if( nBytes<=0 ){ + sqlite3_free(pPrior); + return 0; + } + nOld = memsys3Size(pPrior); + if( nBytes<=nOld && nBytes>=nOld-128 ){ + return pPrior; + } + memsys3Enter(); + p = memsys3MallocUnsafe(nBytes); + if( p ){ + if( nOld<nBytes ){ + memcpy(p, pPrior, nOld); + }else{ + memcpy(p, pPrior, nBytes); + } + memsys3FreeUnsafe(pPrior); + } + memsys3Leave(); + return p; +} + +/* +** Initialize this module. +*/ +static int memsys3Init(void *NotUsed){ + UNUSED_PARAMETER(NotUsed); + if( !sqlite3GlobalConfig.pHeap ){ + return SQLITE_ERROR; + } + + /* Store a pointer to the memory block in global structure mem3. */ + assert( sizeof(Mem3Block)==8 ); + mem3.aPool = (Mem3Block *)sqlite3GlobalConfig.pHeap; + mem3.nPool = (sqlite3GlobalConfig.nHeap / sizeof(Mem3Block)) - 2; + + /* Initialize the key block. */ + mem3.szKeyBlk = mem3.nPool; + mem3.mnKeyBlk = mem3.szKeyBlk; + mem3.iKeyBlk = 1; + mem3.aPool[0].u.hdr.size4x = (mem3.szKeyBlk<<2) + 2; + mem3.aPool[mem3.nPool].u.hdr.prevSize = mem3.nPool; + mem3.aPool[mem3.nPool].u.hdr.size4x = 1; + + return SQLITE_OK; +} + +/* +** Deinitialize this module. +*/ +static void memsys3Shutdown(void *NotUsed){ + UNUSED_PARAMETER(NotUsed); + mem3.mutex = 0; + return; +} + + + +/* +** Open the file indicated and write a log of all unfreed memory +** allocations into that log. +*/ +SQLITE_PRIVATE void sqlite3Memsys3Dump(const char *zFilename){ +#ifdef SQLITE_DEBUG + FILE *out; + u32 i, j; + u32 size; + if( zFilename==0 || zFilename[0]==0 ){ + out = stdout; + }else{ + out = fopen(zFilename, "w"); + if( out==0 ){ + fprintf(stderr, "** Unable to output memory debug output log: %s **\n", + zFilename); + return; + } + } + memsys3Enter(); + fprintf(out, "CHUNKS:\n"); + for(i=1; i<=mem3.nPool; i+=size/4){ + size = mem3.aPool[i-1].u.hdr.size4x; + if( size/4<=1 ){ + fprintf(out, "%p size error\n", &mem3.aPool[i]); + assert( 0 ); + break; + } + if( (size&1)==0 && mem3.aPool[i+size/4-1].u.hdr.prevSize!=size/4 ){ + fprintf(out, "%p tail size does not match\n", &mem3.aPool[i]); + assert( 0 ); + break; + } + if( ((mem3.aPool[i+size/4-1].u.hdr.size4x&2)>>1)!=(size&1) ){ + fprintf(out, "%p tail checkout bit is incorrect\n", &mem3.aPool[i]); + assert( 0 ); + break; + } + if( size&1 ){ + fprintf(out, "%p %6d bytes checked out\n", &mem3.aPool[i], (size/4)*8-8); + }else{ + fprintf(out, "%p %6d bytes free%s\n", &mem3.aPool[i], (size/4)*8-8, + i==mem3.iKeyBlk ? " **key**" : ""); + } + } + for(i=0; i<MX_SMALL-1; i++){ + if( mem3.aiSmall[i]==0 ) continue; + fprintf(out, "small(%2d):", i); + for(j = mem3.aiSmall[i]; j>0; j=mem3.aPool[j].u.list.next){ + fprintf(out, " %p(%d)", &mem3.aPool[j], + (mem3.aPool[j-1].u.hdr.size4x/4)*8-8); + } + fprintf(out, "\n"); + } + for(i=0; i<N_HASH; i++){ + if( mem3.aiHash[i]==0 ) continue; + fprintf(out, "hash(%2d):", i); + for(j = mem3.aiHash[i]; j>0; j=mem3.aPool[j].u.list.next){ + fprintf(out, " %p(%d)", &mem3.aPool[j], + (mem3.aPool[j-1].u.hdr.size4x/4)*8-8); + } + fprintf(out, "\n"); + } + fprintf(out, "key=%d\n", mem3.iKeyBlk); + fprintf(out, "nowUsed=%d\n", mem3.nPool*8 - mem3.szKeyBlk*8); + fprintf(out, "mxUsed=%d\n", mem3.nPool*8 - mem3.mnKeyBlk*8); + sqlite3_mutex_leave(mem3.mutex); + if( out==stdout ){ + fflush(stdout); + }else{ + fclose(out); + } +#else + UNUSED_PARAMETER(zFilename); +#endif +} + +/* +** This routine is the only routine in this file with external +** linkage. +** +** Populate the low-level memory allocation function pointers in +** sqlite3GlobalConfig.m with pointers to the routines in this file. The +** arguments specify the block of memory to manage. +** +** This routine is only called by sqlite3_config(), and therefore +** is not required to be threadsafe (it is not). +*/ +SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys3(void){ + static const sqlite3_mem_methods mempoolMethods = { + memsys3Malloc, + memsys3Free, + memsys3Realloc, + memsys3Size, + memsys3Roundup, + memsys3Init, + memsys3Shutdown, + 0 + }; + return &mempoolMethods; +} + +#endif /* SQLITE_ENABLE_MEMSYS3 */ + +/************** End of mem3.c ************************************************/ +/************** Begin file mem5.c ********************************************/ +/* +** 2007 October 14 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement a memory +** allocation subsystem for use by SQLite. +** +** This version of the memory allocation subsystem omits all +** use of malloc(). The application gives SQLite a block of memory +** before calling sqlite3_initialize() from which allocations +** are made and returned by the xMalloc() and xRealloc() +** implementations. Once sqlite3_initialize() has been called, +** the amount of memory available to SQLite is fixed and cannot +** be changed. +** +** This version of the memory allocation subsystem is included +** in the build only if SQLITE_ENABLE_MEMSYS5 is defined. +** +** This memory allocator uses the following algorithm: +** +** 1. All memory allocation sizes are rounded up to a power of 2. +** +** 2. If two adjacent free blocks are the halves of a larger block, +** then the two blocks are coalesced into the single larger block. +** +** 3. New memory is allocated from the first available free block. +** +** This algorithm is described in: J. M. Robson. "Bounds for Some Functions +** Concerning Dynamic Storage Allocation". Journal of the Association for +** Computing Machinery, Volume 21, Number 8, July 1974, pages 491-499. +** +** Let n be the size of the largest allocation divided by the minimum +** allocation size (after rounding all sizes up to a power of 2.) Let M +** be the maximum amount of memory ever outstanding at one time. Let +** N be the total amount of memory available for allocation. Robson +** proved that this memory allocator will never breakdown due to +** fragmentation as long as the following constraint holds: +** +** N >= M*(1 + log2(n)/2) - n + 1 +** +** The sqlite3_status() logic tracks the maximum values of n and M so +** that an application can, at any time, verify this constraint. +*/ +/* #include "sqliteInt.h" */ + +/* +** This version of the memory allocator is used only when +** SQLITE_ENABLE_MEMSYS5 is defined. +*/ +#ifdef SQLITE_ENABLE_MEMSYS5 + +/* +** A minimum allocation is an instance of the following structure. +** Larger allocations are an array of these structures where the +** size of the array is a power of 2. +** +** The size of this object must be a power of two. That fact is +** verified in memsys5Init(). +*/ +typedef struct Mem5Link Mem5Link; +struct Mem5Link { + int next; /* Index of next free chunk */ + int prev; /* Index of previous free chunk */ +}; + +/* +** Maximum size of any allocation is ((1<<LOGMAX)*mem5.szAtom). Since +** mem5.szAtom is always at least 8 and 32-bit integers are used, +** it is not actually possible to reach this limit. +*/ +#define LOGMAX 30 + +/* +** Masks used for mem5.aCtrl[] elements. +*/ +#define CTRL_LOGSIZE 0x1f /* Log2 Size of this block */ +#define CTRL_FREE 0x20 /* True if not checked out */ + +/* +** All of the static variables used by this module are collected +** into a single structure named "mem5". This is to keep the +** static variables organized and to reduce namespace pollution +** when this module is combined with other in the amalgamation. +*/ +static SQLITE_WSD struct Mem5Global { + /* + ** Memory available for allocation + */ + int szAtom; /* Smallest possible allocation in bytes */ + int nBlock; /* Number of szAtom sized blocks in zPool */ + u8 *zPool; /* Memory available to be allocated */ + + /* + ** Mutex to control access to the memory allocation subsystem. + */ + sqlite3_mutex *mutex; + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + /* + ** Performance statistics + */ + u64 nAlloc; /* Total number of calls to malloc */ + u64 totalAlloc; /* Total of all malloc calls - includes internal frag */ + u64 totalExcess; /* Total internal fragmentation */ + u32 currentOut; /* Current checkout, including internal fragmentation */ + u32 currentCount; /* Current number of distinct checkouts */ + u32 maxOut; /* Maximum instantaneous currentOut */ + u32 maxCount; /* Maximum instantaneous currentCount */ + u32 maxRequest; /* Largest allocation (exclusive of internal frag) */ +#endif + + /* + ** Lists of free blocks. aiFreelist[0] is a list of free blocks of + ** size mem5.szAtom. aiFreelist[1] holds blocks of size szAtom*2. + ** aiFreelist[2] holds free blocks of size szAtom*4. And so forth. + */ + int aiFreelist[LOGMAX+1]; + + /* + ** Space for tracking which blocks are checked out and the size + ** of each block. One byte per block. + */ + u8 *aCtrl; + +} mem5; + +/* +** Access the static variable through a macro for SQLITE_OMIT_WSD. +*/ +#define mem5 GLOBAL(struct Mem5Global, mem5) + +/* +** Assuming mem5.zPool is divided up into an array of Mem5Link +** structures, return a pointer to the idx-th such link. +*/ +#define MEM5LINK(idx) ((Mem5Link *)(&mem5.zPool[(idx)*mem5.szAtom])) + +/* +** Unlink the chunk at mem5.aPool[i] from list it is currently +** on. It should be found on mem5.aiFreelist[iLogsize]. +*/ +static void memsys5Unlink(int i, int iLogsize){ + int next, prev; + assert( i>=0 && i<mem5.nBlock ); + assert( iLogsize>=0 && iLogsize<=LOGMAX ); + assert( (mem5.aCtrl[i] & CTRL_LOGSIZE)==iLogsize ); + + next = MEM5LINK(i)->next; + prev = MEM5LINK(i)->prev; + if( prev<0 ){ + mem5.aiFreelist[iLogsize] = next; + }else{ + MEM5LINK(prev)->next = next; + } + if( next>=0 ){ + MEM5LINK(next)->prev = prev; + } +} + +/* +** Link the chunk at mem5.aPool[i] so that is on the iLogsize +** free list. +*/ +static void memsys5Link(int i, int iLogsize){ + int x; + assert( sqlite3_mutex_held(mem5.mutex) ); + assert( i>=0 && i<mem5.nBlock ); + assert( iLogsize>=0 && iLogsize<=LOGMAX ); + assert( (mem5.aCtrl[i] & CTRL_LOGSIZE)==iLogsize ); + + x = MEM5LINK(i)->next = mem5.aiFreelist[iLogsize]; + MEM5LINK(i)->prev = -1; + if( x>=0 ){ + assert( x<mem5.nBlock ); + MEM5LINK(x)->prev = i; + } + mem5.aiFreelist[iLogsize] = i; +} + +/* +** Obtain or release the mutex needed to access global data structures. +*/ +static void memsys5Enter(void){ + sqlite3_mutex_enter(mem5.mutex); +} +static void memsys5Leave(void){ + sqlite3_mutex_leave(mem5.mutex); +} + +/* +** Return the size of an outstanding allocation, in bytes. +** This only works for chunks that are currently checked out. +*/ +static int memsys5Size(void *p){ + int iSize, i; + assert( p!=0 ); + i = (int)(((u8 *)p-mem5.zPool)/mem5.szAtom); + assert( i>=0 && i<mem5.nBlock ); + iSize = mem5.szAtom * (1 << (mem5.aCtrl[i]&CTRL_LOGSIZE)); + return iSize; +} + +/* +** Return a block of memory of at least nBytes in size. +** Return NULL if unable. Return NULL if nBytes==0. +** +** The caller guarantees that nByte is positive. +** +** The caller has obtained a mutex prior to invoking this +** routine so there is never any chance that two or more +** threads can be in this routine at the same time. +*/ +static void *memsys5MallocUnsafe(int nByte){ + int i; /* Index of a mem5.aPool[] slot */ + int iBin; /* Index into mem5.aiFreelist[] */ + int iFullSz; /* Size of allocation rounded up to power of 2 */ + int iLogsize; /* Log2 of iFullSz/POW2_MIN */ + + /* nByte must be a positive */ + assert( nByte>0 ); + + /* No more than 1GiB per allocation */ + if( nByte > 0x40000000 ) return 0; + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + /* Keep track of the maximum allocation request. Even unfulfilled + ** requests are counted */ + if( (u32)nByte>mem5.maxRequest ){ + mem5.maxRequest = nByte; + } +#endif + + + /* Round nByte up to the next valid power of two */ + for(iFullSz=mem5.szAtom,iLogsize=0; iFullSz<nByte; iFullSz*=2,iLogsize++){} + + /* Make sure mem5.aiFreelist[iLogsize] contains at least one free + ** block. If not, then split a block of the next larger power of + ** two in order to create a new free block of size iLogsize. + */ + for(iBin=iLogsize; iBin<=LOGMAX && mem5.aiFreelist[iBin]<0; iBin++){} + if( iBin>LOGMAX ){ + testcase( sqlite3GlobalConfig.xLog!=0 ); + sqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes", nByte); + return 0; + } + i = mem5.aiFreelist[iBin]; + memsys5Unlink(i, iBin); + while( iBin>iLogsize ){ + int newSize; + + iBin--; + newSize = 1 << iBin; + mem5.aCtrl[i+newSize] = CTRL_FREE | iBin; + memsys5Link(i+newSize, iBin); + } + mem5.aCtrl[i] = iLogsize; + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + /* Update allocator performance statistics. */ + mem5.nAlloc++; + mem5.totalAlloc += iFullSz; + mem5.totalExcess += iFullSz - nByte; + mem5.currentCount++; + mem5.currentOut += iFullSz; + if( mem5.maxCount<mem5.currentCount ) mem5.maxCount = mem5.currentCount; + if( mem5.maxOut<mem5.currentOut ) mem5.maxOut = mem5.currentOut; +#endif + +#ifdef SQLITE_DEBUG + /* Make sure the allocated memory does not assume that it is set to zero + ** or retains a value from a previous allocation */ + memset(&mem5.zPool[i*mem5.szAtom], 0xAA, iFullSz); +#endif + + /* Return a pointer to the allocated memory. */ + return (void*)&mem5.zPool[i*mem5.szAtom]; +} + +/* +** Free an outstanding memory allocation. +*/ +static void memsys5FreeUnsafe(void *pOld){ + u32 size, iLogsize; + int iBlock; + + /* Set iBlock to the index of the block pointed to by pOld in + ** the array of mem5.szAtom byte blocks pointed to by mem5.zPool. + */ + iBlock = (int)(((u8 *)pOld-mem5.zPool)/mem5.szAtom); + + /* Check that the pointer pOld points to a valid, non-free block. */ + assert( iBlock>=0 && iBlock<mem5.nBlock ); + assert( ((u8 *)pOld-mem5.zPool)%mem5.szAtom==0 ); + assert( (mem5.aCtrl[iBlock] & CTRL_FREE)==0 ); + + iLogsize = mem5.aCtrl[iBlock] & CTRL_LOGSIZE; + size = 1<<iLogsize; + assert( iBlock+size-1<(u32)mem5.nBlock ); + + mem5.aCtrl[iBlock] |= CTRL_FREE; + mem5.aCtrl[iBlock+size-1] |= CTRL_FREE; + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + assert( mem5.currentCount>0 ); + assert( mem5.currentOut>=(size*mem5.szAtom) ); + mem5.currentCount--; + mem5.currentOut -= size*mem5.szAtom; + assert( mem5.currentOut>0 || mem5.currentCount==0 ); + assert( mem5.currentCount>0 || mem5.currentOut==0 ); +#endif + + mem5.aCtrl[iBlock] = CTRL_FREE | iLogsize; + while( ALWAYS(iLogsize<LOGMAX) ){ + int iBuddy; + if( (iBlock>>iLogsize) & 1 ){ + iBuddy = iBlock - size; + assert( iBuddy>=0 ); + }else{ + iBuddy = iBlock + size; + if( iBuddy>=mem5.nBlock ) break; + } + if( mem5.aCtrl[iBuddy]!=(CTRL_FREE | iLogsize) ) break; + memsys5Unlink(iBuddy, iLogsize); + iLogsize++; + if( iBuddy<iBlock ){ + mem5.aCtrl[iBuddy] = CTRL_FREE | iLogsize; + mem5.aCtrl[iBlock] = 0; + iBlock = iBuddy; + }else{ + mem5.aCtrl[iBlock] = CTRL_FREE | iLogsize; + mem5.aCtrl[iBuddy] = 0; + } + size *= 2; + } + +#ifdef SQLITE_DEBUG + /* Overwrite freed memory with the 0x55 bit pattern to verify that it is + ** not used after being freed */ + memset(&mem5.zPool[iBlock*mem5.szAtom], 0x55, size); +#endif + + memsys5Link(iBlock, iLogsize); +} + +/* +** Allocate nBytes of memory. +*/ +static void *memsys5Malloc(int nBytes){ + sqlite3_int64 *p = 0; + if( nBytes>0 ){ + memsys5Enter(); + p = memsys5MallocUnsafe(nBytes); + memsys5Leave(); + } + return (void*)p; +} + +/* +** Free memory. +** +** The outer layer memory allocator prevents this routine from +** being called with pPrior==0. +*/ +static void memsys5Free(void *pPrior){ + assert( pPrior!=0 ); + memsys5Enter(); + memsys5FreeUnsafe(pPrior); + memsys5Leave(); +} + +/* +** Change the size of an existing memory allocation. +** +** The outer layer memory allocator prevents this routine from +** being called with pPrior==0. +** +** nBytes is always a value obtained from a prior call to +** memsys5Round(). Hence nBytes is always a non-negative power +** of two. If nBytes==0 that means that an oversize allocation +** (an allocation larger than 0x40000000) was requested and this +** routine should return 0 without freeing pPrior. +*/ +static void *memsys5Realloc(void *pPrior, int nBytes){ + int nOld; + void *p; + assert( pPrior!=0 ); + assert( (nBytes&(nBytes-1))==0 ); /* EV: R-46199-30249 */ + assert( nBytes>=0 ); + if( nBytes==0 ){ + return 0; + } + nOld = memsys5Size(pPrior); + if( nBytes<=nOld ){ + return pPrior; + } + p = memsys5Malloc(nBytes); + if( p ){ + memcpy(p, pPrior, nOld); + memsys5Free(pPrior); + } + return p; +} + +/* +** Round up a request size to the next valid allocation size. If +** the allocation is too large to be handled by this allocation system, +** return 0. +** +** All allocations must be a power of two and must be expressed by a +** 32-bit signed integer. Hence the largest allocation is 0x40000000 +** or 1073741824 bytes. +*/ +static int memsys5Roundup(int n){ + int iFullSz; + if( n<=mem5.szAtom*2 ){ + if( n<=mem5.szAtom ) return mem5.szAtom; + return mem5.szAtom*2; + } + if( n>0x10000000 ){ + if( n>0x40000000 ) return 0; + if( n>0x20000000 ) return 0x40000000; + return 0x20000000; + } + for(iFullSz=mem5.szAtom*8; iFullSz<n; iFullSz *= 4); + if( (iFullSz/2)>=(i64)n ) return iFullSz/2; + return iFullSz; +} + +/* +** Return the ceiling of the logarithm base 2 of iValue. +** +** Examples: memsys5Log(1) -> 0 +** memsys5Log(2) -> 1 +** memsys5Log(4) -> 2 +** memsys5Log(5) -> 3 +** memsys5Log(8) -> 3 +** memsys5Log(9) -> 4 +*/ +static int memsys5Log(int iValue){ + int iLog; + for(iLog=0; (iLog<(int)((sizeof(int)*8)-1)) && (1<<iLog)<iValue; iLog++); + return iLog; +} + +/* +** Initialize the memory allocator. +** +** This routine is not threadsafe. The caller must be holding a mutex +** to prevent multiple threads from entering at the same time. +*/ +static int memsys5Init(void *NotUsed){ + int ii; /* Loop counter */ + int nByte; /* Number of bytes of memory available to this allocator */ + u8 *zByte; /* Memory usable by this allocator */ + int nMinLog; /* Log base 2 of minimum allocation size in bytes */ + int iOffset; /* An offset into mem5.aCtrl[] */ + + UNUSED_PARAMETER(NotUsed); + + /* For the purposes of this routine, disable the mutex */ + mem5.mutex = 0; + + /* The size of a Mem5Link object must be a power of two. Verify that + ** this is case. + */ + assert( (sizeof(Mem5Link)&(sizeof(Mem5Link)-1))==0 ); + + nByte = sqlite3GlobalConfig.nHeap; + zByte = (u8*)sqlite3GlobalConfig.pHeap; + assert( zByte!=0 ); /* sqlite3_config() does not allow otherwise */ + + /* boundaries on sqlite3GlobalConfig.mnReq are enforced in sqlite3_config() */ + nMinLog = memsys5Log(sqlite3GlobalConfig.mnReq); + mem5.szAtom = (1<<nMinLog); + while( (int)sizeof(Mem5Link)>mem5.szAtom ){ + mem5.szAtom = mem5.szAtom << 1; + } + + mem5.nBlock = (nByte / (mem5.szAtom+sizeof(u8))); + mem5.zPool = zByte; + mem5.aCtrl = (u8 *)&mem5.zPool[mem5.nBlock*mem5.szAtom]; + + for(ii=0; ii<=LOGMAX; ii++){ + mem5.aiFreelist[ii] = -1; + } + + iOffset = 0; + for(ii=LOGMAX; ii>=0; ii--){ + int nAlloc = (1<<ii); + if( (iOffset+nAlloc)<=mem5.nBlock ){ + mem5.aCtrl[iOffset] = ii | CTRL_FREE; + memsys5Link(iOffset, ii); + iOffset += nAlloc; + } + assert((iOffset+nAlloc)>mem5.nBlock); + } + + /* If a mutex is required for normal operation, allocate one */ + if( sqlite3GlobalConfig.bMemstat==0 ){ + mem5.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); + } + + return SQLITE_OK; +} + +/* +** Deinitialize this module. +*/ +static void memsys5Shutdown(void *NotUsed){ + UNUSED_PARAMETER(NotUsed); + mem5.mutex = 0; + return; +} + +#ifdef SQLITE_TEST +/* +** Open the file indicated and write a log of all unfreed memory +** allocations into that log. +*/ +SQLITE_PRIVATE void sqlite3Memsys5Dump(const char *zFilename){ + FILE *out; + int i, j, n; + int nMinLog; + + if( zFilename==0 || zFilename[0]==0 ){ + out = stdout; + }else{ + out = fopen(zFilename, "w"); + if( out==0 ){ + fprintf(stderr, "** Unable to output memory debug output log: %s **\n", + zFilename); + return; + } + } + memsys5Enter(); + nMinLog = memsys5Log(mem5.szAtom); + for(i=0; i<=LOGMAX && i+nMinLog<32; i++){ + for(n=0, j=mem5.aiFreelist[i]; j>=0; j = MEM5LINK(j)->next, n++){} + fprintf(out, "freelist items of size %d: %d\n", mem5.szAtom << i, n); + } + fprintf(out, "mem5.nAlloc = %llu\n", mem5.nAlloc); + fprintf(out, "mem5.totalAlloc = %llu\n", mem5.totalAlloc); + fprintf(out, "mem5.totalExcess = %llu\n", mem5.totalExcess); + fprintf(out, "mem5.currentOut = %u\n", mem5.currentOut); + fprintf(out, "mem5.currentCount = %u\n", mem5.currentCount); + fprintf(out, "mem5.maxOut = %u\n", mem5.maxOut); + fprintf(out, "mem5.maxCount = %u\n", mem5.maxCount); + fprintf(out, "mem5.maxRequest = %u\n", mem5.maxRequest); + memsys5Leave(); + if( out==stdout ){ + fflush(stdout); + }else{ + fclose(out); + } +} +#endif + +/* +** This routine is the only routine in this file with external +** linkage. It returns a pointer to a static sqlite3_mem_methods +** struct populated with the memsys5 methods. +*/ +SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys5(void){ + static const sqlite3_mem_methods memsys5Methods = { + memsys5Malloc, + memsys5Free, + memsys5Realloc, + memsys5Size, + memsys5Roundup, + memsys5Init, + memsys5Shutdown, + 0 + }; + return &memsys5Methods; +} + +#endif /* SQLITE_ENABLE_MEMSYS5 */ + +/************** End of mem5.c ************************************************/ +/************** Begin file mutex.c *******************************************/ +/* +** 2007 August 14 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement mutexes. +** +** This file contains code that is common across all mutex implementations. +*/ +/* #include "sqliteInt.h" */ + +#if defined(SQLITE_DEBUG) && !defined(SQLITE_MUTEX_OMIT) +/* +** For debugging purposes, record when the mutex subsystem is initialized +** and uninitialized so that we can assert() if there is an attempt to +** allocate a mutex while the system is uninitialized. +*/ +static SQLITE_WSD int mutexIsInit = 0; +#endif /* SQLITE_DEBUG && !defined(SQLITE_MUTEX_OMIT) */ + + +#ifndef SQLITE_MUTEX_OMIT + +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS +/* +** This block (enclosed by SQLITE_ENABLE_MULTITHREADED_CHECKS) contains +** the implementation of a wrapper around the system default mutex +** implementation (sqlite3DefaultMutex()). +** +** Most calls are passed directly through to the underlying default +** mutex implementation. Except, if a mutex is configured by calling +** sqlite3MutexWarnOnContention() on it, then if contention is ever +** encountered within xMutexEnter() a warning is emitted via sqlite3_log(). +** +** This type of mutex is used as the database handle mutex when testing +** apps that usually use SQLITE_CONFIG_MULTITHREAD mode. +*/ + +/* +** Type for all mutexes used when SQLITE_ENABLE_MULTITHREADED_CHECKS +** is defined. Variable CheckMutex.mutex is a pointer to the real mutex +** allocated by the system mutex implementation. Variable iType is usually set +** to the type of mutex requested - SQLITE_MUTEX_RECURSIVE, SQLITE_MUTEX_FAST +** or one of the static mutex identifiers. Or, if this is a recursive mutex +** that has been configured using sqlite3MutexWarnOnContention(), it is +** set to SQLITE_MUTEX_WARNONCONTENTION. +*/ +typedef struct CheckMutex CheckMutex; +struct CheckMutex { + int iType; + sqlite3_mutex *mutex; +}; + +#define SQLITE_MUTEX_WARNONCONTENTION (-1) + +/* +** Pointer to real mutex methods object used by the CheckMutex +** implementation. Set by checkMutexInit(). +*/ +static SQLITE_WSD const sqlite3_mutex_methods *pGlobalMutexMethods; + +#ifdef SQLITE_DEBUG +static int checkMutexHeld(sqlite3_mutex *p){ + return pGlobalMutexMethods->xMutexHeld(((CheckMutex*)p)->mutex); +} +static int checkMutexNotheld(sqlite3_mutex *p){ + return pGlobalMutexMethods->xMutexNotheld(((CheckMutex*)p)->mutex); +} +#endif + +/* +** Initialize and deinitialize the mutex subsystem. +*/ +static int checkMutexInit(void){ + pGlobalMutexMethods = sqlite3DefaultMutex(); + return SQLITE_OK; +} +static int checkMutexEnd(void){ + pGlobalMutexMethods = 0; + return SQLITE_OK; +} + +/* +** Allocate a mutex. +*/ +static sqlite3_mutex *checkMutexAlloc(int iType){ + static CheckMutex staticMutexes[] = { + {2, 0}, {3, 0}, {4, 0}, {5, 0}, + {6, 0}, {7, 0}, {8, 0}, {9, 0}, + {10, 0}, {11, 0}, {12, 0}, {13, 0} + }; + CheckMutex *p = 0; + + assert( SQLITE_MUTEX_RECURSIVE==1 && SQLITE_MUTEX_FAST==0 ); + if( iType<2 ){ + p = sqlite3MallocZero(sizeof(CheckMutex)); + if( p==0 ) return 0; + p->iType = iType; + }else{ +#ifdef SQLITE_ENABLE_API_ARMOR + if( iType-2>=ArraySize(staticMutexes) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + p = &staticMutexes[iType-2]; + } + + if( p->mutex==0 ){ + p->mutex = pGlobalMutexMethods->xMutexAlloc(iType); + if( p->mutex==0 ){ + if( iType<2 ){ + sqlite3_free(p); + } + p = 0; + } + } + + return (sqlite3_mutex*)p; +} + +/* +** Free a mutex. +*/ +static void checkMutexFree(sqlite3_mutex *p){ + assert( SQLITE_MUTEX_RECURSIVE<2 ); + assert( SQLITE_MUTEX_FAST<2 ); + assert( SQLITE_MUTEX_WARNONCONTENTION<2 ); + +#if SQLITE_ENABLE_API_ARMOR + if( ((CheckMutex*)p)->iType<2 ) +#endif + { + CheckMutex *pCheck = (CheckMutex*)p; + pGlobalMutexMethods->xMutexFree(pCheck->mutex); + sqlite3_free(pCheck); + } +#ifdef SQLITE_ENABLE_API_ARMOR + else{ + (void)SQLITE_MISUSE_BKPT; + } +#endif +} + +/* +** Enter the mutex. +*/ +static void checkMutexEnter(sqlite3_mutex *p){ + CheckMutex *pCheck = (CheckMutex*)p; + if( pCheck->iType==SQLITE_MUTEX_WARNONCONTENTION ){ + if( SQLITE_OK==pGlobalMutexMethods->xMutexTry(pCheck->mutex) ){ + return; + } + sqlite3_log(SQLITE_MISUSE, + "illegal multi-threaded access to database connection" + ); + } + pGlobalMutexMethods->xMutexEnter(pCheck->mutex); +} + +/* +** Enter the mutex (do not block). +*/ +static int checkMutexTry(sqlite3_mutex *p){ + CheckMutex *pCheck = (CheckMutex*)p; + return pGlobalMutexMethods->xMutexTry(pCheck->mutex); +} + +/* +** Leave the mutex. +*/ +static void checkMutexLeave(sqlite3_mutex *p){ + CheckMutex *pCheck = (CheckMutex*)p; + pGlobalMutexMethods->xMutexLeave(pCheck->mutex); +} + +sqlite3_mutex_methods const *multiThreadedCheckMutex(void){ + static const sqlite3_mutex_methods sMutex = { + checkMutexInit, + checkMutexEnd, + checkMutexAlloc, + checkMutexFree, + checkMutexEnter, + checkMutexTry, + checkMutexLeave, +#ifdef SQLITE_DEBUG + checkMutexHeld, + checkMutexNotheld +#else + 0, + 0 +#endif + }; + return &sMutex; +} + +/* +** Mark the SQLITE_MUTEX_RECURSIVE mutex passed as the only argument as +** one on which there should be no contention. +*/ +SQLITE_PRIVATE void sqlite3MutexWarnOnContention(sqlite3_mutex *p){ + if( sqlite3GlobalConfig.mutex.xMutexAlloc==checkMutexAlloc ){ + CheckMutex *pCheck = (CheckMutex*)p; + assert( pCheck->iType==SQLITE_MUTEX_RECURSIVE ); + pCheck->iType = SQLITE_MUTEX_WARNONCONTENTION; + } +} +#endif /* ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS */ + +/* +** Initialize the mutex system. +*/ +SQLITE_PRIVATE int sqlite3MutexInit(void){ + int rc = SQLITE_OK; + if( !sqlite3GlobalConfig.mutex.xMutexAlloc ){ + /* If the xMutexAlloc method has not been set, then the user did not + ** install a mutex implementation via sqlite3_config() prior to + ** sqlite3_initialize() being called. This block copies pointers to + ** the default implementation into the sqlite3GlobalConfig structure. + */ + sqlite3_mutex_methods const *pFrom; + sqlite3_mutex_methods *pTo = &sqlite3GlobalConfig.mutex; + + if( sqlite3GlobalConfig.bCoreMutex ){ +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS + pFrom = multiThreadedCheckMutex(); +#else + pFrom = sqlite3DefaultMutex(); +#endif + }else{ + pFrom = sqlite3NoopMutex(); + } + pTo->xMutexInit = pFrom->xMutexInit; + pTo->xMutexEnd = pFrom->xMutexEnd; + pTo->xMutexFree = pFrom->xMutexFree; + pTo->xMutexEnter = pFrom->xMutexEnter; + pTo->xMutexTry = pFrom->xMutexTry; + pTo->xMutexLeave = pFrom->xMutexLeave; + pTo->xMutexHeld = pFrom->xMutexHeld; + pTo->xMutexNotheld = pFrom->xMutexNotheld; + sqlite3MemoryBarrier(); + pTo->xMutexAlloc = pFrom->xMutexAlloc; + } + assert( sqlite3GlobalConfig.mutex.xMutexInit ); + rc = sqlite3GlobalConfig.mutex.xMutexInit(); + +#ifdef SQLITE_DEBUG + GLOBAL(int, mutexIsInit) = 1; +#endif + + sqlite3MemoryBarrier(); + return rc; +} + +/* +** Shutdown the mutex system. This call frees resources allocated by +** sqlite3MutexInit(). +*/ +SQLITE_PRIVATE int sqlite3MutexEnd(void){ + int rc = SQLITE_OK; + if( sqlite3GlobalConfig.mutex.xMutexEnd ){ + rc = sqlite3GlobalConfig.mutex.xMutexEnd(); + } + +#ifdef SQLITE_DEBUG + GLOBAL(int, mutexIsInit) = 0; +#endif + + return rc; +} + +/* +** Retrieve a pointer to a static mutex or allocate a new dynamic one. +*/ +SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int id){ +#ifndef SQLITE_OMIT_AUTOINIT + if( id<=SQLITE_MUTEX_RECURSIVE && sqlite3_initialize() ) return 0; + if( id>SQLITE_MUTEX_RECURSIVE && sqlite3MutexInit() ) return 0; +#endif + assert( sqlite3GlobalConfig.mutex.xMutexAlloc ); + return sqlite3GlobalConfig.mutex.xMutexAlloc(id); +} + +SQLITE_PRIVATE sqlite3_mutex *sqlite3MutexAlloc(int id){ + if( !sqlite3GlobalConfig.bCoreMutex ){ + return 0; + } + assert( GLOBAL(int, mutexIsInit) ); + assert( sqlite3GlobalConfig.mutex.xMutexAlloc ); + return sqlite3GlobalConfig.mutex.xMutexAlloc(id); +} + +/* +** Free a dynamic mutex. +*/ +SQLITE_API void sqlite3_mutex_free(sqlite3_mutex *p){ + if( p ){ + assert( sqlite3GlobalConfig.mutex.xMutexFree ); + sqlite3GlobalConfig.mutex.xMutexFree(p); + } +} + +/* +** Obtain the mutex p. If some other thread already has the mutex, block +** until it can be obtained. +*/ +SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex *p){ + if( p ){ + assert( sqlite3GlobalConfig.mutex.xMutexEnter ); + sqlite3GlobalConfig.mutex.xMutexEnter(p); + } +} + +/* +** Obtain the mutex p. If successful, return SQLITE_OK. Otherwise, if another +** thread holds the mutex and it cannot be obtained, return SQLITE_BUSY. +*/ +SQLITE_API int sqlite3_mutex_try(sqlite3_mutex *p){ + int rc = SQLITE_OK; + if( p ){ + assert( sqlite3GlobalConfig.mutex.xMutexTry ); + return sqlite3GlobalConfig.mutex.xMutexTry(p); + } + return rc; +} + +/* +** The sqlite3_mutex_leave() routine exits a mutex that was previously +** entered by the same thread. The behavior is undefined if the mutex +** is not currently entered. If a NULL pointer is passed as an argument +** this function is a no-op. +*/ +SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex *p){ + if( p ){ + assert( sqlite3GlobalConfig.mutex.xMutexLeave ); + sqlite3GlobalConfig.mutex.xMutexLeave(p); + } +} + +#ifndef NDEBUG +/* +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are +** intended for use inside assert() statements. +*/ +SQLITE_API int sqlite3_mutex_held(sqlite3_mutex *p){ + assert( p==0 || sqlite3GlobalConfig.mutex.xMutexHeld ); + return p==0 || sqlite3GlobalConfig.mutex.xMutexHeld(p); +} +SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){ + assert( p==0 || sqlite3GlobalConfig.mutex.xMutexNotheld ); + return p==0 || sqlite3GlobalConfig.mutex.xMutexNotheld(p); +} +#endif + +#endif /* !defined(SQLITE_MUTEX_OMIT) */ + +/************** End of mutex.c ***********************************************/ +/************** Begin file mutex_noop.c **************************************/ +/* +** 2008 October 07 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement mutexes. +** +** This implementation in this file does not provide any mutual +** exclusion and is thus suitable for use only in applications +** that use SQLite in a single thread. The routines defined +** here are place-holders. Applications can substitute working +** mutex routines at start-time using the +** +** sqlite3_config(SQLITE_CONFIG_MUTEX,...) +** +** interface. +** +** If compiled with SQLITE_DEBUG, then additional logic is inserted +** that does error checking on mutexes to make sure they are being +** called correctly. +*/ +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_MUTEX_OMIT + +#ifndef SQLITE_DEBUG +/* +** Stub routines for all mutex methods. +** +** This routines provide no mutual exclusion or error checking. +*/ +static int noopMutexInit(void){ return SQLITE_OK; } +static int noopMutexEnd(void){ return SQLITE_OK; } +static sqlite3_mutex *noopMutexAlloc(int id){ + UNUSED_PARAMETER(id); + return (sqlite3_mutex*)8; +} +static void noopMutexFree(sqlite3_mutex *p){ UNUSED_PARAMETER(p); return; } +static void noopMutexEnter(sqlite3_mutex *p){ UNUSED_PARAMETER(p); return; } +static int noopMutexTry(sqlite3_mutex *p){ + UNUSED_PARAMETER(p); + return SQLITE_OK; +} +static void noopMutexLeave(sqlite3_mutex *p){ UNUSED_PARAMETER(p); return; } + +SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3NoopMutex(void){ + static const sqlite3_mutex_methods sMutex = { + noopMutexInit, + noopMutexEnd, + noopMutexAlloc, + noopMutexFree, + noopMutexEnter, + noopMutexTry, + noopMutexLeave, + + 0, + 0, + }; + + return &sMutex; +} +#endif /* !SQLITE_DEBUG */ + +#ifdef SQLITE_DEBUG +/* +** In this implementation, error checking is provided for testing +** and debugging purposes. The mutexes still do not provide any +** mutual exclusion. +*/ + +/* +** The mutex object +*/ +typedef struct sqlite3_debug_mutex { + int id; /* The mutex type */ + int cnt; /* Number of entries without a matching leave */ +} sqlite3_debug_mutex; + +/* +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are +** intended for use inside assert() statements. +*/ +static int debugMutexHeld(sqlite3_mutex *pX){ + sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX; + return p==0 || p->cnt>0; +} +static int debugMutexNotheld(sqlite3_mutex *pX){ + sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX; + return p==0 || p->cnt==0; +} + +/* +** Initialize and deinitialize the mutex subsystem. +*/ +static int debugMutexInit(void){ return SQLITE_OK; } +static int debugMutexEnd(void){ return SQLITE_OK; } + +/* +** The sqlite3_mutex_alloc() routine allocates a new +** mutex and returns a pointer to it. If it returns NULL +** that means that a mutex could not be allocated. +*/ +static sqlite3_mutex *debugMutexAlloc(int id){ + static sqlite3_debug_mutex aStatic[SQLITE_MUTEX_STATIC_VFS3 - 1]; + sqlite3_debug_mutex *pNew = 0; + switch( id ){ + case SQLITE_MUTEX_FAST: + case SQLITE_MUTEX_RECURSIVE: { + pNew = sqlite3Malloc(sizeof(*pNew)); + if( pNew ){ + pNew->id = id; + pNew->cnt = 0; + } + break; + } + default: { +#ifdef SQLITE_ENABLE_API_ARMOR + if( id-2<0 || id-2>=ArraySize(aStatic) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + pNew = &aStatic[id-2]; + pNew->id = id; + break; + } + } + return (sqlite3_mutex*)pNew; +} + +/* +** This routine deallocates a previously allocated mutex. +*/ +static void debugMutexFree(sqlite3_mutex *pX){ + sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX; + assert( p->cnt==0 ); + if( p->id==SQLITE_MUTEX_RECURSIVE || p->id==SQLITE_MUTEX_FAST ){ + sqlite3_free(p); + }else{ +#ifdef SQLITE_ENABLE_API_ARMOR + (void)SQLITE_MISUSE_BKPT; +#endif + } +} + +/* +** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt +** to enter a mutex. If another thread is already within the mutex, +** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return +** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK +** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can +** be entered multiple times by the same thread. In such cases the, +** mutex must be exited an equal number of times before another thread +** can enter. If the same thread tries to enter any other kind of mutex +** more than once, the behavior is undefined. +*/ +static void debugMutexEnter(sqlite3_mutex *pX){ + sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX; + assert( p->id==SQLITE_MUTEX_RECURSIVE || debugMutexNotheld(pX) ); + p->cnt++; +} +static int debugMutexTry(sqlite3_mutex *pX){ + sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX; + assert( p->id==SQLITE_MUTEX_RECURSIVE || debugMutexNotheld(pX) ); + p->cnt++; + return SQLITE_OK; +} + +/* +** The sqlite3_mutex_leave() routine exits a mutex that was +** previously entered by the same thread. The behavior +** is undefined if the mutex is not currently entered or +** is not currently allocated. SQLite will never do either. +*/ +static void debugMutexLeave(sqlite3_mutex *pX){ + sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX; + assert( debugMutexHeld(pX) ); + p->cnt--; + assert( p->id==SQLITE_MUTEX_RECURSIVE || debugMutexNotheld(pX) ); +} + +SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3NoopMutex(void){ + static const sqlite3_mutex_methods sMutex = { + debugMutexInit, + debugMutexEnd, + debugMutexAlloc, + debugMutexFree, + debugMutexEnter, + debugMutexTry, + debugMutexLeave, + + debugMutexHeld, + debugMutexNotheld + }; + + return &sMutex; +} +#endif /* SQLITE_DEBUG */ + +/* +** If compiled with SQLITE_MUTEX_NOOP, then the no-op mutex implementation +** is used regardless of the run-time threadsafety setting. +*/ +#ifdef SQLITE_MUTEX_NOOP +SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ + return sqlite3NoopMutex(); +} +#endif /* defined(SQLITE_MUTEX_NOOP) */ +#endif /* !defined(SQLITE_MUTEX_OMIT) */ + +/************** End of mutex_noop.c ******************************************/ +/************** Begin file mutex_unix.c **************************************/ +/* +** 2007 August 28 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement mutexes for pthreads +*/ +/* #include "sqliteInt.h" */ + +/* +** The code in this file is only used if we are compiling threadsafe +** under unix with pthreads. +** +** Note that this implementation requires a version of pthreads that +** supports recursive mutexes. +*/ +#ifdef SQLITE_MUTEX_PTHREADS + +#include <pthread.h> + +/* +** The sqlite3_mutex.id, sqlite3_mutex.nRef, and sqlite3_mutex.owner fields +** are necessary under two conditions: (1) Debug builds and (2) using +** home-grown mutexes. Encapsulate these conditions into a single #define. +*/ +#if defined(SQLITE_DEBUG) || defined(SQLITE_HOMEGROWN_RECURSIVE_MUTEX) +# define SQLITE_MUTEX_NREF 1 +#else +# define SQLITE_MUTEX_NREF 0 +#endif + +/* +** Each recursive mutex is an instance of the following structure. +*/ +struct sqlite3_mutex { + pthread_mutex_t mutex; /* Mutex controlling the lock */ +#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR) + int id; /* Mutex type */ +#endif +#if SQLITE_MUTEX_NREF + volatile int nRef; /* Number of entrances */ + volatile pthread_t owner; /* Thread that is within this mutex */ + int trace; /* True to trace changes */ +#endif +}; +#if SQLITE_MUTEX_NREF +# define SQLITE3_MUTEX_INITIALIZER(id) \ + {PTHREAD_MUTEX_INITIALIZER,id,0,(pthread_t)0,0} +#elif defined(SQLITE_ENABLE_API_ARMOR) +# define SQLITE3_MUTEX_INITIALIZER(id) { PTHREAD_MUTEX_INITIALIZER, id } +#else +#define SQLITE3_MUTEX_INITIALIZER(id) { PTHREAD_MUTEX_INITIALIZER } +#endif + +/* +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are +** intended for use only inside assert() statements. On some platforms, +** there might be race conditions that can cause these routines to +** deliver incorrect results. In particular, if pthread_equal() is +** not an atomic operation, then these routines might delivery +** incorrect results. On most platforms, pthread_equal() is a +** comparison of two integers and is therefore atomic. But we are +** told that HPUX is not such a platform. If so, then these routines +** will not always work correctly on HPUX. +** +** On those platforms where pthread_equal() is not atomic, SQLite +** should be compiled without -DSQLITE_DEBUG and with -DNDEBUG to +** make sure no assert() statements are evaluated and hence these +** routines are never called. +*/ +#if !defined(NDEBUG) || defined(SQLITE_DEBUG) +static int pthreadMutexHeld(sqlite3_mutex *p){ + return (p->nRef!=0 && pthread_equal(p->owner, pthread_self())); +} +static int pthreadMutexNotheld(sqlite3_mutex *p){ + return p->nRef==0 || pthread_equal(p->owner, pthread_self())==0; +} +#endif + +/* +** Try to provide a memory barrier operation, needed for initialization +** and also for the implementation of xShmBarrier in the VFS in cases +** where SQLite is compiled without mutexes. +*/ +SQLITE_PRIVATE void sqlite3MemoryBarrier(void){ +#if defined(SQLITE_MEMORY_BARRIER) + SQLITE_MEMORY_BARRIER; +#elif defined(__GNUC__) && GCC_VERSION>=4001000 + __sync_synchronize(); +#endif +} + +/* +** Initialize and deinitialize the mutex subsystem. +*/ +static int pthreadMutexInit(void){ return SQLITE_OK; } +static int pthreadMutexEnd(void){ return SQLITE_OK; } + +/* +** The sqlite3_mutex_alloc() routine allocates a new +** mutex and returns a pointer to it. If it returns NULL +** that means that a mutex could not be allocated. SQLite +** will unwind its stack and return an error. The argument +** to sqlite3_mutex_alloc() is one of these integer constants: +** +** <ul> +** <li> SQLITE_MUTEX_FAST +** <li> SQLITE_MUTEX_RECURSIVE +** <li> SQLITE_MUTEX_STATIC_MAIN +** <li> SQLITE_MUTEX_STATIC_MEM +** <li> SQLITE_MUTEX_STATIC_OPEN +** <li> SQLITE_MUTEX_STATIC_PRNG +** <li> SQLITE_MUTEX_STATIC_LRU +** <li> SQLITE_MUTEX_STATIC_PMEM +** <li> SQLITE_MUTEX_STATIC_APP1 +** <li> SQLITE_MUTEX_STATIC_APP2 +** <li> SQLITE_MUTEX_STATIC_APP3 +** <li> SQLITE_MUTEX_STATIC_VFS1 +** <li> SQLITE_MUTEX_STATIC_VFS2 +** <li> SQLITE_MUTEX_STATIC_VFS3 +** </ul> +** +** The first two constants cause sqlite3_mutex_alloc() to create +** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE +** is used but not necessarily so when SQLITE_MUTEX_FAST is used. +** The mutex implementation does not need to make a distinction +** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does +** not want to. But SQLite will only request a recursive mutex in +** cases where it really needs one. If a faster non-recursive mutex +** implementation is available on the host platform, the mutex subsystem +** might return such a mutex in response to SQLITE_MUTEX_FAST. +** +** The other allowed parameters to sqlite3_mutex_alloc() each return +** a pointer to a static preexisting mutex. Six static mutexes are +** used by the current version of SQLite. Future versions of SQLite +** may add additional static mutexes. Static mutexes are for internal +** use by SQLite only. Applications that use SQLite mutexes should +** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or +** SQLITE_MUTEX_RECURSIVE. +** +** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST +** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() +** returns a different mutex on every call. But for the static +** mutex types, the same mutex is returned on every call that has +** the same type number. +*/ +static sqlite3_mutex *pthreadMutexAlloc(int iType){ + static sqlite3_mutex staticMutexes[] = { + SQLITE3_MUTEX_INITIALIZER(2), + SQLITE3_MUTEX_INITIALIZER(3), + SQLITE3_MUTEX_INITIALIZER(4), + SQLITE3_MUTEX_INITIALIZER(5), + SQLITE3_MUTEX_INITIALIZER(6), + SQLITE3_MUTEX_INITIALIZER(7), + SQLITE3_MUTEX_INITIALIZER(8), + SQLITE3_MUTEX_INITIALIZER(9), + SQLITE3_MUTEX_INITIALIZER(10), + SQLITE3_MUTEX_INITIALIZER(11), + SQLITE3_MUTEX_INITIALIZER(12), + SQLITE3_MUTEX_INITIALIZER(13) + }; + sqlite3_mutex *p; + switch( iType ){ + case SQLITE_MUTEX_RECURSIVE: { + p = sqlite3MallocZero( sizeof(*p) ); + if( p ){ +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX + /* If recursive mutexes are not available, we will have to + ** build our own. See below. */ + pthread_mutex_init(&p->mutex, 0); +#else + /* Use a recursive mutex if it is available */ + pthread_mutexattr_t recursiveAttr; + pthread_mutexattr_init(&recursiveAttr); + pthread_mutexattr_settype(&recursiveAttr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&p->mutex, &recursiveAttr); + pthread_mutexattr_destroy(&recursiveAttr); +#endif +#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR) + p->id = SQLITE_MUTEX_RECURSIVE; +#endif + } + break; + } + case SQLITE_MUTEX_FAST: { + p = sqlite3MallocZero( sizeof(*p) ); + if( p ){ + pthread_mutex_init(&p->mutex, 0); +#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR) + p->id = SQLITE_MUTEX_FAST; +#endif + } + break; + } + default: { +#ifdef SQLITE_ENABLE_API_ARMOR + if( iType-2<0 || iType-2>=ArraySize(staticMutexes) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + p = &staticMutexes[iType-2]; + break; + } + } +#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR) + assert( p==0 || p->id==iType ); +#endif + return p; +} + + +/* +** This routine deallocates a previously +** allocated mutex. SQLite is careful to deallocate every +** mutex that it allocates. +*/ +static void pthreadMutexFree(sqlite3_mutex *p){ + assert( p->nRef==0 ); +#if SQLITE_ENABLE_API_ARMOR + if( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ) +#endif + { + pthread_mutex_destroy(&p->mutex); + sqlite3_free(p); + } +#ifdef SQLITE_ENABLE_API_ARMOR + else{ + (void)SQLITE_MISUSE_BKPT; + } +#endif +} + +/* +** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt +** to enter a mutex. If another thread is already within the mutex, +** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return +** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK +** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can +** be entered multiple times by the same thread. In such cases the, +** mutex must be exited an equal number of times before another thread +** can enter. If the same thread tries to enter any other kind of mutex +** more than once, the behavior is undefined. +*/ +static void pthreadMutexEnter(sqlite3_mutex *p){ + assert( p->id==SQLITE_MUTEX_RECURSIVE || pthreadMutexNotheld(p) ); + +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX + /* If recursive mutexes are not available, then we have to grow + ** our own. This implementation assumes that pthread_equal() + ** is atomic - that it cannot be deceived into thinking self + ** and p->owner are equal if p->owner changes between two values + ** that are not equal to self while the comparison is taking place. + ** This implementation also assumes a coherent cache - that + ** separate processes cannot read different values from the same + ** address at the same time. If either of these two conditions + ** are not met, then the mutexes will fail and problems will result. + */ + { + pthread_t self = pthread_self(); + if( p->nRef>0 && pthread_equal(p->owner, self) ){ + p->nRef++; + }else{ + pthread_mutex_lock(&p->mutex); + assert( p->nRef==0 ); + p->owner = self; + p->nRef = 1; + } + } +#else + /* Use the built-in recursive mutexes if they are available. + */ + pthread_mutex_lock(&p->mutex); +#if SQLITE_MUTEX_NREF + assert( p->nRef>0 || p->owner==0 ); + p->owner = pthread_self(); + p->nRef++; +#endif +#endif + +#ifdef SQLITE_DEBUG + if( p->trace ){ + printf("enter mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef); + } +#endif +} +static int pthreadMutexTry(sqlite3_mutex *p){ + int rc; + assert( p->id==SQLITE_MUTEX_RECURSIVE || pthreadMutexNotheld(p) ); + +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX + /* If recursive mutexes are not available, then we have to grow + ** our own. This implementation assumes that pthread_equal() + ** is atomic - that it cannot be deceived into thinking self + ** and p->owner are equal if p->owner changes between two values + ** that are not equal to self while the comparison is taking place. + ** This implementation also assumes a coherent cache - that + ** separate processes cannot read different values from the same + ** address at the same time. If either of these two conditions + ** are not met, then the mutexes will fail and problems will result. + */ + { + pthread_t self = pthread_self(); + if( p->nRef>0 && pthread_equal(p->owner, self) ){ + p->nRef++; + rc = SQLITE_OK; + }else if( pthread_mutex_trylock(&p->mutex)==0 ){ + assert( p->nRef==0 ); + p->owner = self; + p->nRef = 1; + rc = SQLITE_OK; + }else{ + rc = SQLITE_BUSY; + } + } +#else + /* Use the built-in recursive mutexes if they are available. + */ + if( pthread_mutex_trylock(&p->mutex)==0 ){ +#if SQLITE_MUTEX_NREF + p->owner = pthread_self(); + p->nRef++; +#endif + rc = SQLITE_OK; + }else{ + rc = SQLITE_BUSY; + } +#endif + +#ifdef SQLITE_DEBUG + if( rc==SQLITE_OK && p->trace ){ + printf("enter mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef); + } +#endif + return rc; +} + +/* +** The sqlite3_mutex_leave() routine exits a mutex that was +** previously entered by the same thread. The behavior +** is undefined if the mutex is not currently entered or +** is not currently allocated. SQLite will never do either. +*/ +static void pthreadMutexLeave(sqlite3_mutex *p){ + assert( pthreadMutexHeld(p) ); +#if SQLITE_MUTEX_NREF + p->nRef--; + if( p->nRef==0 ) p->owner = 0; +#endif + assert( p->nRef==0 || p->id==SQLITE_MUTEX_RECURSIVE ); + +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX + if( p->nRef==0 ){ + pthread_mutex_unlock(&p->mutex); + } +#else + pthread_mutex_unlock(&p->mutex); +#endif + +#ifdef SQLITE_DEBUG + if( p->trace ){ + printf("leave mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef); + } +#endif +} + +SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ + static const sqlite3_mutex_methods sMutex = { + pthreadMutexInit, + pthreadMutexEnd, + pthreadMutexAlloc, + pthreadMutexFree, + pthreadMutexEnter, + pthreadMutexTry, + pthreadMutexLeave, +#ifdef SQLITE_DEBUG + pthreadMutexHeld, + pthreadMutexNotheld +#else + 0, + 0 +#endif + }; + + return &sMutex; +} + +#endif /* SQLITE_MUTEX_PTHREADS */ + +/************** End of mutex_unix.c ******************************************/ +/************** Begin file mutex_w32.c ***************************************/ +/* +** 2007 August 14 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement mutexes for Win32. +*/ +/* #include "sqliteInt.h" */ + +#if SQLITE_OS_WIN +/* +** Include code that is common to all os_*.c files +*/ +/* #include "os_common.h" */ + +/* +** Include the header file for the Windows VFS. +*/ +/************** Include os_win.h in the middle of mutex_w32.c ****************/ +/************** Begin file os_win.h ******************************************/ +/* +** 2013 November 25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to Windows. +*/ +#ifndef SQLITE_OS_WIN_H +#define SQLITE_OS_WIN_H + +/* +** Include the primary Windows SDK header file. +*/ +#include "windows.h" + +#ifdef __CYGWIN__ +# include <sys/cygwin.h> +# include <errno.h> /* amalgamator: dontcache */ +#endif + +/* +** Determine if we are dealing with Windows NT. +** +** We ought to be able to determine if we are compiling for Windows 9x or +** Windows NT using the _WIN32_WINNT macro as follows: +** +** #if defined(_WIN32_WINNT) +** # define SQLITE_OS_WINNT 1 +** #else +** # define SQLITE_OS_WINNT 0 +** #endif +** +** However, Visual Studio 2005 does not set _WIN32_WINNT by default, as +** it ought to, so the above test does not work. We'll just assume that +** everything is Windows NT unless the programmer explicitly says otherwise +** by setting SQLITE_OS_WINNT to 0. +*/ +#if SQLITE_OS_WIN && !defined(SQLITE_OS_WINNT) +# define SQLITE_OS_WINNT 1 +#endif + +/* +** Determine if we are dealing with Windows CE - which has a much reduced +** API. +*/ +#if defined(_WIN32_WCE) +# define SQLITE_OS_WINCE 1 +#else +# define SQLITE_OS_WINCE 0 +#endif + +/* +** Determine if we are dealing with WinRT, which provides only a subset of +** the full Win32 API. +*/ +#if !defined(SQLITE_OS_WINRT) +# define SQLITE_OS_WINRT 0 +#endif + +/* +** For WinCE, some API function parameters do not appear to be declared as +** volatile. +*/ +#if SQLITE_OS_WINCE +# define SQLITE_WIN32_VOLATILE +#else +# define SQLITE_WIN32_VOLATILE volatile +#endif + +/* +** For some Windows sub-platforms, the _beginthreadex() / _endthreadex() +** functions are not available (e.g. those not using MSVC, Cygwin, etc). +*/ +#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ + SQLITE_THREADSAFE>0 && !defined(__CYGWIN__) +# define SQLITE_OS_WIN_THREADS 1 +#else +# define SQLITE_OS_WIN_THREADS 0 +#endif + +#endif /* SQLITE_OS_WIN_H */ + +/************** End of os_win.h **********************************************/ +/************** Continuing where we left off in mutex_w32.c ******************/ +#endif + +/* +** The code in this file is only used if we are compiling multithreaded +** on a Win32 system. +*/ +#ifdef SQLITE_MUTEX_W32 + +/* +** Each recursive mutex is an instance of the following structure. +*/ +struct sqlite3_mutex { + CRITICAL_SECTION mutex; /* Mutex controlling the lock */ + int id; /* Mutex type */ +#ifdef SQLITE_DEBUG + volatile int nRef; /* Number of entrances */ + volatile DWORD owner; /* Thread holding this mutex */ + volatile LONG trace; /* True to trace changes */ +#endif +}; + +/* +** These are the initializer values used when declaring a "static" mutex +** on Win32. It should be noted that all mutexes require initialization +** on the Win32 platform. +*/ +#define SQLITE_W32_MUTEX_INITIALIZER { 0 } + +#ifdef SQLITE_DEBUG +#define SQLITE3_MUTEX_INITIALIZER(id) { SQLITE_W32_MUTEX_INITIALIZER, id, \ + 0L, (DWORD)0, 0 } +#else +#define SQLITE3_MUTEX_INITIALIZER(id) { SQLITE_W32_MUTEX_INITIALIZER, id } +#endif + +#ifdef SQLITE_DEBUG +/* +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are +** intended for use only inside assert() statements. +*/ +static int winMutexHeld(sqlite3_mutex *p){ + return p->nRef!=0 && p->owner==GetCurrentThreadId(); +} + +static int winMutexNotheld2(sqlite3_mutex *p, DWORD tid){ + return p->nRef==0 || p->owner!=tid; +} + +static int winMutexNotheld(sqlite3_mutex *p){ + DWORD tid = GetCurrentThreadId(); + return winMutexNotheld2(p, tid); +} +#endif + +/* +** Try to provide a memory barrier operation, needed for initialization +** and also for the xShmBarrier method of the VFS in cases when SQLite is +** compiled without mutexes (SQLITE_THREADSAFE=0). +*/ +SQLITE_PRIVATE void sqlite3MemoryBarrier(void){ +#if defined(SQLITE_MEMORY_BARRIER) + SQLITE_MEMORY_BARRIER; +#elif defined(__GNUC__) + __sync_synchronize(); +#elif MSVC_VERSION>=1300 + _ReadWriteBarrier(); +#elif defined(MemoryBarrier) + MemoryBarrier(); +#endif +} + +/* +** Initialize and deinitialize the mutex subsystem. +*/ +static sqlite3_mutex winMutex_staticMutexes[] = { + SQLITE3_MUTEX_INITIALIZER(2), + SQLITE3_MUTEX_INITIALIZER(3), + SQLITE3_MUTEX_INITIALIZER(4), + SQLITE3_MUTEX_INITIALIZER(5), + SQLITE3_MUTEX_INITIALIZER(6), + SQLITE3_MUTEX_INITIALIZER(7), + SQLITE3_MUTEX_INITIALIZER(8), + SQLITE3_MUTEX_INITIALIZER(9), + SQLITE3_MUTEX_INITIALIZER(10), + SQLITE3_MUTEX_INITIALIZER(11), + SQLITE3_MUTEX_INITIALIZER(12), + SQLITE3_MUTEX_INITIALIZER(13) +}; + +static int winMutex_isInit = 0; +static int winMutex_isNt = -1; /* <0 means "need to query" */ + +/* As the winMutexInit() and winMutexEnd() functions are called as part +** of the sqlite3_initialize() and sqlite3_shutdown() processing, the +** "interlocked" magic used here is probably not strictly necessary. +*/ +static LONG SQLITE_WIN32_VOLATILE winMutex_lock = 0; + +SQLITE_API int sqlite3_win32_is_nt(void); /* os_win.c */ +SQLITE_API void sqlite3_win32_sleep(DWORD milliseconds); /* os_win.c */ + +static int winMutexInit(void){ + /* The first to increment to 1 does actual initialization */ + if( InterlockedCompareExchange(&winMutex_lock, 1, 0)==0 ){ + int i; + for(i=0; i<ArraySize(winMutex_staticMutexes); i++){ +#if SQLITE_OS_WINRT + InitializeCriticalSectionEx(&winMutex_staticMutexes[i].mutex, 0, 0); +#else + InitializeCriticalSection(&winMutex_staticMutexes[i].mutex); +#endif + } + winMutex_isInit = 1; + }else{ + /* Another thread is (in the process of) initializing the static + ** mutexes */ + while( !winMutex_isInit ){ + sqlite3_win32_sleep(1); + } + } + return SQLITE_OK; +} + +static int winMutexEnd(void){ + /* The first to decrement to 0 does actual shutdown + ** (which should be the last to shutdown.) */ + if( InterlockedCompareExchange(&winMutex_lock, 0, 1)==1 ){ + if( winMutex_isInit==1 ){ + int i; + for(i=0; i<ArraySize(winMutex_staticMutexes); i++){ + DeleteCriticalSection(&winMutex_staticMutexes[i].mutex); + } + winMutex_isInit = 0; + } + } + return SQLITE_OK; +} + +/* +** The sqlite3_mutex_alloc() routine allocates a new +** mutex and returns a pointer to it. If it returns NULL +** that means that a mutex could not be allocated. SQLite +** will unwind its stack and return an error. The argument +** to sqlite3_mutex_alloc() is one of these integer constants: +** +** <ul> +** <li> SQLITE_MUTEX_FAST +** <li> SQLITE_MUTEX_RECURSIVE +** <li> SQLITE_MUTEX_STATIC_MAIN +** <li> SQLITE_MUTEX_STATIC_MEM +** <li> SQLITE_MUTEX_STATIC_OPEN +** <li> SQLITE_MUTEX_STATIC_PRNG +** <li> SQLITE_MUTEX_STATIC_LRU +** <li> SQLITE_MUTEX_STATIC_PMEM +** <li> SQLITE_MUTEX_STATIC_APP1 +** <li> SQLITE_MUTEX_STATIC_APP2 +** <li> SQLITE_MUTEX_STATIC_APP3 +** <li> SQLITE_MUTEX_STATIC_VFS1 +** <li> SQLITE_MUTEX_STATIC_VFS2 +** <li> SQLITE_MUTEX_STATIC_VFS3 +** </ul> +** +** The first two constants cause sqlite3_mutex_alloc() to create +** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE +** is used but not necessarily so when SQLITE_MUTEX_FAST is used. +** The mutex implementation does not need to make a distinction +** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does +** not want to. But SQLite will only request a recursive mutex in +** cases where it really needs one. If a faster non-recursive mutex +** implementation is available on the host platform, the mutex subsystem +** might return such a mutex in response to SQLITE_MUTEX_FAST. +** +** The other allowed parameters to sqlite3_mutex_alloc() each return +** a pointer to a static preexisting mutex. Six static mutexes are +** used by the current version of SQLite. Future versions of SQLite +** may add additional static mutexes. Static mutexes are for internal +** use by SQLite only. Applications that use SQLite mutexes should +** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or +** SQLITE_MUTEX_RECURSIVE. +** +** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST +** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() +** returns a different mutex on every call. But for the static +** mutex types, the same mutex is returned on every call that has +** the same type number. +*/ +static sqlite3_mutex *winMutexAlloc(int iType){ + sqlite3_mutex *p; + + switch( iType ){ + case SQLITE_MUTEX_FAST: + case SQLITE_MUTEX_RECURSIVE: { + p = sqlite3MallocZero( sizeof(*p) ); + if( p ){ + p->id = iType; +#ifdef SQLITE_DEBUG +#ifdef SQLITE_WIN32_MUTEX_TRACE_DYNAMIC + p->trace = 1; +#endif +#endif +#if SQLITE_OS_WINRT + InitializeCriticalSectionEx(&p->mutex, 0, 0); +#else + InitializeCriticalSection(&p->mutex); +#endif + } + break; + } + default: { +#ifdef SQLITE_ENABLE_API_ARMOR + if( iType-2<0 || iType-2>=ArraySize(winMutex_staticMutexes) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + p = &winMutex_staticMutexes[iType-2]; +#ifdef SQLITE_DEBUG +#ifdef SQLITE_WIN32_MUTEX_TRACE_STATIC + InterlockedCompareExchange(&p->trace, 1, 0); +#endif +#endif + break; + } + } + assert( p==0 || p->id==iType ); + return p; +} + + +/* +** This routine deallocates a previously +** allocated mutex. SQLite is careful to deallocate every +** mutex that it allocates. +*/ +static void winMutexFree(sqlite3_mutex *p){ + assert( p ); + assert( p->nRef==0 && p->owner==0 ); + if( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ){ + DeleteCriticalSection(&p->mutex); + sqlite3_free(p); + }else{ +#ifdef SQLITE_ENABLE_API_ARMOR + (void)SQLITE_MISUSE_BKPT; +#endif + } +} + +/* +** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt +** to enter a mutex. If another thread is already within the mutex, +** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return +** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK +** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can +** be entered multiple times by the same thread. In such cases the, +** mutex must be exited an equal number of times before another thread +** can enter. If the same thread tries to enter any other kind of mutex +** more than once, the behavior is undefined. +*/ +static void winMutexEnter(sqlite3_mutex *p){ +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + DWORD tid = GetCurrentThreadId(); +#endif +#ifdef SQLITE_DEBUG + assert( p ); + assert( p->id==SQLITE_MUTEX_RECURSIVE || winMutexNotheld2(p, tid) ); +#else + assert( p ); +#endif + assert( winMutex_isInit==1 ); + EnterCriticalSection(&p->mutex); +#ifdef SQLITE_DEBUG + assert( p->nRef>0 || p->owner==0 ); + p->owner = tid; + p->nRef++; + if( p->trace ){ + OSTRACE(("ENTER-MUTEX tid=%lu, mutex(%d)=%p (%d), nRef=%d\n", + tid, p->id, p, p->trace, p->nRef)); + } +#endif +} + +static int winMutexTry(sqlite3_mutex *p){ +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + DWORD tid = GetCurrentThreadId(); +#endif + int rc = SQLITE_BUSY; + assert( p ); + assert( p->id==SQLITE_MUTEX_RECURSIVE || winMutexNotheld2(p, tid) ); + /* + ** The sqlite3_mutex_try() routine is very rarely used, and when it + ** is used it is merely an optimization. So it is OK for it to always + ** fail. + ** + ** The TryEnterCriticalSection() interface is only available on WinNT. + ** And some windows compilers complain if you try to use it without + ** first doing some #defines that prevent SQLite from building on Win98. + ** For that reason, we will omit this optimization for now. See + ** ticket #2685. + */ +#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0400 + assert( winMutex_isInit==1 ); + assert( winMutex_isNt>=-1 && winMutex_isNt<=1 ); + if( winMutex_isNt<0 ){ + winMutex_isNt = sqlite3_win32_is_nt(); + } + assert( winMutex_isNt==0 || winMutex_isNt==1 ); + if( winMutex_isNt && TryEnterCriticalSection(&p->mutex) ){ +#ifdef SQLITE_DEBUG + p->owner = tid; + p->nRef++; +#endif + rc = SQLITE_OK; + } +#else + UNUSED_PARAMETER(p); +#endif +#ifdef SQLITE_DEBUG + if( p->trace ){ + OSTRACE(("TRY-MUTEX tid=%lu, mutex(%d)=%p (%d), owner=%lu, nRef=%d, rc=%s\n", + tid, p->id, p, p->trace, p->owner, p->nRef, sqlite3ErrName(rc))); + } +#endif + return rc; +} + +/* +** The sqlite3_mutex_leave() routine exits a mutex that was +** previously entered by the same thread. The behavior +** is undefined if the mutex is not currently entered or +** is not currently allocated. SQLite will never do either. +*/ +static void winMutexLeave(sqlite3_mutex *p){ +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + DWORD tid = GetCurrentThreadId(); +#endif + assert( p ); +#ifdef SQLITE_DEBUG + assert( p->nRef>0 ); + assert( p->owner==tid ); + p->nRef--; + if( p->nRef==0 ) p->owner = 0; + assert( p->nRef==0 || p->id==SQLITE_MUTEX_RECURSIVE ); +#endif + assert( winMutex_isInit==1 ); + LeaveCriticalSection(&p->mutex); +#ifdef SQLITE_DEBUG + if( p->trace ){ + OSTRACE(("LEAVE-MUTEX tid=%lu, mutex(%d)=%p (%d), nRef=%d\n", + tid, p->id, p, p->trace, p->nRef)); + } +#endif +} + +SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ + static const sqlite3_mutex_methods sMutex = { + winMutexInit, + winMutexEnd, + winMutexAlloc, + winMutexFree, + winMutexEnter, + winMutexTry, + winMutexLeave, +#ifdef SQLITE_DEBUG + winMutexHeld, + winMutexNotheld +#else + 0, + 0 +#endif + }; + return &sMutex; +} + +#endif /* SQLITE_MUTEX_W32 */ + +/************** End of mutex_w32.c *******************************************/ +/************** Begin file malloc.c ******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** Memory allocation functions used throughout sqlite. +*/ +/* #include "sqliteInt.h" */ +/* #include <stdarg.h> */ + +/* +** Attempt to release up to n bytes of non-essential memory currently +** held by SQLite. An example of non-essential memory is memory used to +** cache database pages that are not currently in use. +*/ +SQLITE_API int sqlite3_release_memory(int n){ +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT + return sqlite3PcacheReleaseMemory(n); +#else + /* IMPLEMENTATION-OF: R-34391-24921 The sqlite3_release_memory() routine + ** is a no-op returning zero if SQLite is not compiled with + ** SQLITE_ENABLE_MEMORY_MANAGEMENT. */ + UNUSED_PARAMETER(n); + return 0; +#endif +} + +/* +** Default value of the hard heap limit. 0 means "no limit". +*/ +#ifndef SQLITE_MAX_MEMORY +# define SQLITE_MAX_MEMORY 0 +#endif + +/* +** State information local to the memory allocation subsystem. +*/ +static SQLITE_WSD struct Mem0Global { + sqlite3_mutex *mutex; /* Mutex to serialize access */ + sqlite3_int64 alarmThreshold; /* The soft heap limit */ + sqlite3_int64 hardLimit; /* The hard upper bound on memory */ + + /* + ** True if heap is nearly "full" where "full" is defined by the + ** sqlite3_soft_heap_limit() setting. + */ + int nearlyFull; +} mem0 = { 0, SQLITE_MAX_MEMORY, SQLITE_MAX_MEMORY, 0 }; + +#define mem0 GLOBAL(struct Mem0Global, mem0) + +/* +** Return the memory allocator mutex. sqlite3_status() needs it. +*/ +SQLITE_PRIVATE sqlite3_mutex *sqlite3MallocMutex(void){ + return mem0.mutex; +} + +#ifndef SQLITE_OMIT_DEPRECATED +/* +** Deprecated external interface. It used to set an alarm callback +** that was invoked when memory usage grew too large. Now it is a +** no-op. +*/ +SQLITE_API int sqlite3_memory_alarm( + void(*xCallback)(void *pArg, sqlite3_int64 used,int N), + void *pArg, + sqlite3_int64 iThreshold +){ + (void)xCallback; + (void)pArg; + (void)iThreshold; + return SQLITE_OK; +} +#endif + +/* +** Set the soft heap-size limit for the library. An argument of +** zero disables the limit. A negative argument is a no-op used to +** obtain the return value. +** +** The return value is the value of the heap limit just before this +** interface was called. +** +** If the hard heap limit is enabled, then the soft heap limit cannot +** be disabled nor raised above the hard heap limit. +*/ +SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 n){ + sqlite3_int64 priorLimit; + sqlite3_int64 excess; + sqlite3_int64 nUsed; +#ifndef SQLITE_OMIT_AUTOINIT + int rc = sqlite3_initialize(); + if( rc ) return -1; +#endif + sqlite3_mutex_enter(mem0.mutex); + priorLimit = mem0.alarmThreshold; + if( n<0 ){ + sqlite3_mutex_leave(mem0.mutex); + return priorLimit; + } + if( mem0.hardLimit>0 && (n>mem0.hardLimit || n==0) ){ + n = mem0.hardLimit; + } + mem0.alarmThreshold = n; + nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); + AtomicStore(&mem0.nearlyFull, n>0 && n<=nUsed); + sqlite3_mutex_leave(mem0.mutex); + excess = sqlite3_memory_used() - n; + if( excess>0 ) sqlite3_release_memory((int)(excess & 0x7fffffff)); + return priorLimit; +} +SQLITE_API void sqlite3_soft_heap_limit(int n){ + if( n<0 ) n = 0; + sqlite3_soft_heap_limit64(n); +} + +/* +** Set the hard heap-size limit for the library. An argument of zero +** disables the hard heap limit. A negative argument is a no-op used +** to obtain the return value without affecting the hard heap limit. +** +** The return value is the value of the hard heap limit just prior to +** calling this interface. +** +** Setting the hard heap limit will also activate the soft heap limit +** and constrain the soft heap limit to be no more than the hard heap +** limit. +*/ +SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 n){ + sqlite3_int64 priorLimit; +#ifndef SQLITE_OMIT_AUTOINIT + int rc = sqlite3_initialize(); + if( rc ) return -1; +#endif + sqlite3_mutex_enter(mem0.mutex); + priorLimit = mem0.hardLimit; + if( n>=0 ){ + mem0.hardLimit = n; + if( n<mem0.alarmThreshold || mem0.alarmThreshold==0 ){ + mem0.alarmThreshold = n; + } + } + sqlite3_mutex_leave(mem0.mutex); + return priorLimit; +} + + +/* +** Initialize the memory allocation subsystem. +*/ +SQLITE_PRIVATE int sqlite3MallocInit(void){ + int rc; + if( sqlite3GlobalConfig.m.xMalloc==0 ){ + sqlite3MemSetDefault(); + } + mem0.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); + if( sqlite3GlobalConfig.pPage==0 || sqlite3GlobalConfig.szPage<512 + || sqlite3GlobalConfig.nPage<=0 ){ + sqlite3GlobalConfig.pPage = 0; + sqlite3GlobalConfig.szPage = 0; + } + rc = sqlite3GlobalConfig.m.xInit(sqlite3GlobalConfig.m.pAppData); + if( rc!=SQLITE_OK ) memset(&mem0, 0, sizeof(mem0)); + return rc; +} + +/* +** Return true if the heap is currently under memory pressure - in other +** words if the amount of heap used is close to the limit set by +** sqlite3_soft_heap_limit(). +*/ +SQLITE_PRIVATE int sqlite3HeapNearlyFull(void){ + return AtomicLoad(&mem0.nearlyFull); +} + +/* +** Deinitialize the memory allocation subsystem. +*/ +SQLITE_PRIVATE void sqlite3MallocEnd(void){ + if( sqlite3GlobalConfig.m.xShutdown ){ + sqlite3GlobalConfig.m.xShutdown(sqlite3GlobalConfig.m.pAppData); + } + memset(&mem0, 0, sizeof(mem0)); +} + +/* +** Return the amount of memory currently checked out. +*/ +SQLITE_API sqlite3_int64 sqlite3_memory_used(void){ + sqlite3_int64 res, mx; + sqlite3_status64(SQLITE_STATUS_MEMORY_USED, &res, &mx, 0); + return res; +} + +/* +** Return the maximum amount of memory that has ever been +** checked out since either the beginning of this process +** or since the most recent reset. +*/ +SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag){ + sqlite3_int64 res, mx; + sqlite3_status64(SQLITE_STATUS_MEMORY_USED, &res, &mx, resetFlag); + return mx; +} + +/* +** Trigger the alarm +*/ +static void sqlite3MallocAlarm(int nByte){ + if( mem0.alarmThreshold<=0 ) return; + sqlite3_mutex_leave(mem0.mutex); + sqlite3_release_memory(nByte); + sqlite3_mutex_enter(mem0.mutex); +} + +/* +** Do a memory allocation with statistics and alarms. Assume the +** lock is already held. +*/ +static void mallocWithAlarm(int n, void **pp){ + void *p; + int nFull; + assert( sqlite3_mutex_held(mem0.mutex) ); + assert( n>0 ); + + /* In Firefox (circa 2017-02-08), xRoundup() is remapped to an internal + ** implementation of malloc_good_size(), which must be called in debug + ** mode and specifically when the DMD "Dark Matter Detector" is enabled + ** or else a crash results. Hence, do not attempt to optimize out the + ** following xRoundup() call. */ + nFull = sqlite3GlobalConfig.m.xRoundup(n); + + sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, n); + if( mem0.alarmThreshold>0 ){ + sqlite3_int64 nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); + if( nUsed >= mem0.alarmThreshold - nFull ){ + AtomicStore(&mem0.nearlyFull, 1); + sqlite3MallocAlarm(nFull); + if( mem0.hardLimit ){ + nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); + if( nUsed >= mem0.hardLimit - nFull ){ + *pp = 0; + return; + } + } + }else{ + AtomicStore(&mem0.nearlyFull, 0); + } + } + p = sqlite3GlobalConfig.m.xMalloc(nFull); +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT + if( p==0 && mem0.alarmThreshold>0 ){ + sqlite3MallocAlarm(nFull); + p = sqlite3GlobalConfig.m.xMalloc(nFull); + } +#endif + if( p ){ + nFull = sqlite3MallocSize(p); + sqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nFull); + sqlite3StatusUp(SQLITE_STATUS_MALLOC_COUNT, 1); + } + *pp = p; +} + +/* +** Maximum size of any single memory allocation. +** +** This is not a limit on the total amount of memory used. This is +** a limit on the size parameter to sqlite3_malloc() and sqlite3_realloc(). +** +** The upper bound is slightly less than 2GiB: 0x7ffffeff == 2,147,483,391 +** This provides a 256-byte safety margin for defense against 32-bit +** signed integer overflow bugs when computing memory allocation sizes. +** Paranoid applications might want to reduce the maximum allocation size +** further for an even larger safety margin. 0x3fffffff or 0x0fffffff +** or even smaller would be reasonable upper bounds on the size of a memory +** allocations for most applications. +*/ +#ifndef SQLITE_MAX_ALLOCATION_SIZE +# define SQLITE_MAX_ALLOCATION_SIZE 2147483391 +#endif +#if SQLITE_MAX_ALLOCATION_SIZE>2147483391 +# error Maximum size for SQLITE_MAX_ALLOCATION_SIZE is 2147483391 +#endif + +/* +** Allocate memory. This routine is like sqlite3_malloc() except that it +** assumes the memory subsystem has already been initialized. +*/ +SQLITE_PRIVATE void *sqlite3Malloc(u64 n){ + void *p; + if( n==0 || n>SQLITE_MAX_ALLOCATION_SIZE ){ + p = 0; + }else if( sqlite3GlobalConfig.bMemstat ){ + sqlite3_mutex_enter(mem0.mutex); + mallocWithAlarm((int)n, &p); + sqlite3_mutex_leave(mem0.mutex); + }else{ + p = sqlite3GlobalConfig.m.xMalloc((int)n); + } + assert( EIGHT_BYTE_ALIGNMENT(p) ); /* IMP: R-11148-40995 */ + return p; +} + +/* +** This version of the memory allocation is for use by the application. +** First make sure the memory subsystem is initialized, then do the +** allocation. +*/ +SQLITE_API void *sqlite3_malloc(int n){ +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return n<=0 ? 0 : sqlite3Malloc(n); +} +SQLITE_API void *sqlite3_malloc64(sqlite3_uint64 n){ +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return sqlite3Malloc(n); +} + +/* +** TRUE if p is a lookaside memory allocation from db +*/ +#ifndef SQLITE_OMIT_LOOKASIDE +static int isLookaside(sqlite3 *db, const void *p){ + return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pTrueEnd); +} +#else +#define isLookaside(A,B) 0 +#endif + +/* +** Return the size of a memory allocation previously obtained from +** sqlite3Malloc() or sqlite3_malloc(). +*/ +SQLITE_PRIVATE int sqlite3MallocSize(const void *p){ + assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); + return sqlite3GlobalConfig.m.xSize((void*)p); +} +static int lookasideMallocSize(sqlite3 *db, const void *p){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + return p<db->lookaside.pMiddle ? db->lookaside.szTrue : LOOKASIDE_SMALL; +#else + return db->lookaside.szTrue; +#endif +} +SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, const void *p){ + assert( p!=0 ); +#ifdef SQLITE_DEBUG + if( db==0 ){ + assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); + assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); + }else if( !isLookaside(db,p) ){ + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + } +#endif + if( db ){ + if( ((uptr)p)<(uptr)(db->lookaside.pTrueEnd) ){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){ + assert( sqlite3_mutex_held(db->mutex) ); + return LOOKASIDE_SMALL; + } +#endif + if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){ + assert( sqlite3_mutex_held(db->mutex) ); + return db->lookaside.szTrue; + } + } + } + return sqlite3GlobalConfig.m.xSize((void*)p); +} +SQLITE_API sqlite3_uint64 sqlite3_msize(void *p){ + assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); + assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); + return p ? sqlite3GlobalConfig.m.xSize(p) : 0; +} + +/* +** Free memory previously obtained from sqlite3Malloc(). +*/ +SQLITE_API void sqlite3_free(void *p){ + if( p==0 ) return; /* IMP: R-49053-54554 */ + assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); + assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); + if( sqlite3GlobalConfig.bMemstat ){ + sqlite3_mutex_enter(mem0.mutex); + sqlite3StatusDown(SQLITE_STATUS_MEMORY_USED, sqlite3MallocSize(p)); + sqlite3StatusDown(SQLITE_STATUS_MALLOC_COUNT, 1); + sqlite3GlobalConfig.m.xFree(p); + sqlite3_mutex_leave(mem0.mutex); + }else{ + sqlite3GlobalConfig.m.xFree(p); + } +} + +/* +** Add the size of memory allocation "p" to the count in +** *db->pnBytesFreed. +*/ +static SQLITE_NOINLINE void measureAllocationSize(sqlite3 *db, void *p){ + *db->pnBytesFreed += sqlite3DbMallocSize(db,p); +} + +/* +** Free memory that might be associated with a particular database +** connection. Calling sqlite3DbFree(D,X) for X==0 is a harmless no-op. +** The sqlite3DbFreeNN(D,X) version requires that X be non-NULL. +*/ +SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3 *db, void *p){ + assert( db==0 || sqlite3_mutex_held(db->mutex) ); + assert( p!=0 ); + if( db ){ + if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){ + LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); +#ifdef SQLITE_DEBUG + memset(p, 0xaa, LOOKASIDE_SMALL); /* Trash freed content */ +#endif + pBuf->pNext = db->lookaside.pSmallFree; + db->lookaside.pSmallFree = pBuf; + return; + } +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){ + LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); +#ifdef SQLITE_DEBUG + memset(p, 0xaa, db->lookaside.szTrue); /* Trash freed content */ +#endif + pBuf->pNext = db->lookaside.pFree; + db->lookaside.pFree = pBuf; + return; + } + } + if( db->pnBytesFreed ){ + measureAllocationSize(db, p); + return; + } + } + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( db!=0 || sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) ); + sqlite3MemdebugSetType(p, MEMTYPE_HEAP); + sqlite3_free(p); +} +SQLITE_PRIVATE void sqlite3DbNNFreeNN(sqlite3 *db, void *p){ + assert( db!=0 ); + assert( sqlite3_mutex_held(db->mutex) ); + assert( p!=0 ); + if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){ + LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); +#ifdef SQLITE_DEBUG + memset(p, 0xaa, LOOKASIDE_SMALL); /* Trash freed content */ +#endif + pBuf->pNext = db->lookaside.pSmallFree; + db->lookaside.pSmallFree = pBuf; + return; + } +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){ + LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); +#ifdef SQLITE_DEBUG + memset(p, 0xaa, db->lookaside.szTrue); /* Trash freed content */ +#endif + pBuf->pNext = db->lookaside.pFree; + db->lookaside.pFree = pBuf; + return; + } + } + if( db->pnBytesFreed ){ + measureAllocationSize(db, p); + return; + } + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + sqlite3MemdebugSetType(p, MEMTYPE_HEAP); + sqlite3_free(p); +} +SQLITE_PRIVATE void sqlite3DbFree(sqlite3 *db, void *p){ + assert( db==0 || sqlite3_mutex_held(db->mutex) ); + if( p ) sqlite3DbFreeNN(db, p); +} + +/* +** Change the size of an existing memory allocation +*/ +SQLITE_PRIVATE void *sqlite3Realloc(void *pOld, u64 nBytes){ + int nOld, nNew, nDiff; + void *pNew; + assert( sqlite3MemdebugHasType(pOld, MEMTYPE_HEAP) ); + assert( sqlite3MemdebugNoType(pOld, (u8)~MEMTYPE_HEAP) ); + if( pOld==0 ){ + return sqlite3Malloc(nBytes); /* IMP: R-04300-56712 */ + } + if( nBytes==0 ){ + sqlite3_free(pOld); /* IMP: R-26507-47431 */ + return 0; + } + if( nBytes>=0x7fffff00 ){ + /* The 0x7ffff00 limit term is explained in comments on sqlite3Malloc() */ + return 0; + } + nOld = sqlite3MallocSize(pOld); + /* IMPLEMENTATION-OF: R-46199-30249 SQLite guarantees that the second + ** argument to xRealloc is always a value returned by a prior call to + ** xRoundup. */ + nNew = sqlite3GlobalConfig.m.xRoundup((int)nBytes); + if( nOld==nNew ){ + pNew = pOld; + }else if( sqlite3GlobalConfig.bMemstat ){ + sqlite3_int64 nUsed; + sqlite3_mutex_enter(mem0.mutex); + sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes); + nDiff = nNew - nOld; + if( nDiff>0 && (nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED)) >= + mem0.alarmThreshold-nDiff ){ + sqlite3MallocAlarm(nDiff); + if( mem0.hardLimit>0 && nUsed >= mem0.hardLimit - nDiff ){ + sqlite3_mutex_leave(mem0.mutex); + return 0; + } + } + pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT + if( pNew==0 && mem0.alarmThreshold>0 ){ + sqlite3MallocAlarm((int)nBytes); + pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); + } +#endif + if( pNew ){ + nNew = sqlite3MallocSize(pNew); + sqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nNew-nOld); + } + sqlite3_mutex_leave(mem0.mutex); + }else{ + pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); + } + assert( EIGHT_BYTE_ALIGNMENT(pNew) ); /* IMP: R-11148-40995 */ + return pNew; +} + +/* +** The public interface to sqlite3Realloc. Make sure that the memory +** subsystem is initialized prior to invoking sqliteRealloc. +*/ +SQLITE_API void *sqlite3_realloc(void *pOld, int n){ +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + if( n<0 ) n = 0; /* IMP: R-26507-47431 */ + return sqlite3Realloc(pOld, n); +} +SQLITE_API void *sqlite3_realloc64(void *pOld, sqlite3_uint64 n){ +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return sqlite3Realloc(pOld, n); +} + + +/* +** Allocate and zero memory. +*/ +SQLITE_PRIVATE void *sqlite3MallocZero(u64 n){ + void *p = sqlite3Malloc(n); + if( p ){ + memset(p, 0, (size_t)n); + } + return p; +} + +/* +** Allocate and zero memory. If the allocation fails, make +** the mallocFailed flag in the connection pointer. +*/ +SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3 *db, u64 n){ + void *p; + testcase( db==0 ); + p = sqlite3DbMallocRaw(db, n); + if( p ) memset(p, 0, (size_t)n); + return p; +} + + +/* Finish the work of sqlite3DbMallocRawNN for the unusual and +** slower case when the allocation cannot be fulfilled using lookaside. +*/ +static SQLITE_NOINLINE void *dbMallocRawFinish(sqlite3 *db, u64 n){ + void *p; + assert( db!=0 ); + p = sqlite3Malloc(n); + if( !p ) sqlite3OomFault(db); + sqlite3MemdebugSetType(p, + (db->lookaside.bDisable==0) ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP); + return p; +} + +/* +** Allocate memory, either lookaside (if possible) or heap. +** If the allocation fails, set the mallocFailed flag in +** the connection pointer. +** +** If db!=0 and db->mallocFailed is true (indicating a prior malloc +** failure on the same database connection) then always return 0. +** Hence for a particular database connection, once malloc starts +** failing, it fails consistently until mallocFailed is reset. +** This is an important assumption. There are many places in the +** code that do things like this: +** +** int *a = (int*)sqlite3DbMallocRaw(db, 100); +** int *b = (int*)sqlite3DbMallocRaw(db, 200); +** if( b ) a[10] = 9; +** +** In other words, if a subsequent malloc (ex: "b") worked, it is assumed +** that all prior mallocs (ex: "a") worked too. +** +** The sqlite3MallocRawNN() variant guarantees that the "db" parameter is +** not a NULL pointer. +*/ +SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3 *db, u64 n){ + void *p; + if( db ) return sqlite3DbMallocRawNN(db, n); + p = sqlite3Malloc(n); + sqlite3MemdebugSetType(p, MEMTYPE_HEAP); + return p; +} +SQLITE_PRIVATE void *sqlite3DbMallocRawNN(sqlite3 *db, u64 n){ +#ifndef SQLITE_OMIT_LOOKASIDE + LookasideSlot *pBuf; + assert( db!=0 ); + assert( sqlite3_mutex_held(db->mutex) ); + assert( db->pnBytesFreed==0 ); + if( n>db->lookaside.sz ){ + if( !db->lookaside.bDisable ){ + db->lookaside.anStat[1]++; + }else if( db->mallocFailed ){ + return 0; + } + return dbMallocRawFinish(db, n); + } +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( n<=LOOKASIDE_SMALL ){ + if( (pBuf = db->lookaside.pSmallFree)!=0 ){ + db->lookaside.pSmallFree = pBuf->pNext; + db->lookaside.anStat[0]++; + return (void*)pBuf; + }else if( (pBuf = db->lookaside.pSmallInit)!=0 ){ + db->lookaside.pSmallInit = pBuf->pNext; + db->lookaside.anStat[0]++; + return (void*)pBuf; + } + } +#endif + if( (pBuf = db->lookaside.pFree)!=0 ){ + db->lookaside.pFree = pBuf->pNext; + db->lookaside.anStat[0]++; + return (void*)pBuf; + }else if( (pBuf = db->lookaside.pInit)!=0 ){ + db->lookaside.pInit = pBuf->pNext; + db->lookaside.anStat[0]++; + return (void*)pBuf; + }else{ + db->lookaside.anStat[2]++; + } +#else + assert( db!=0 ); + assert( sqlite3_mutex_held(db->mutex) ); + assert( db->pnBytesFreed==0 ); + if( db->mallocFailed ){ + return 0; + } +#endif + return dbMallocRawFinish(db, n); +} + +/* Forward declaration */ +static SQLITE_NOINLINE void *dbReallocFinish(sqlite3 *db, void *p, u64 n); + +/* +** Resize the block of memory pointed to by p to n bytes. If the +** resize fails, set the mallocFailed flag in the connection object. +*/ +SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *db, void *p, u64 n){ + assert( db!=0 ); + if( p==0 ) return sqlite3DbMallocRawNN(db, n); + assert( sqlite3_mutex_held(db->mutex) ); + if( ((uptr)p)<(uptr)db->lookaside.pEnd ){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( ((uptr)p)>=(uptr)db->lookaside.pMiddle ){ + if( n<=LOOKASIDE_SMALL ) return p; + }else +#endif + if( ((uptr)p)>=(uptr)db->lookaside.pStart ){ + if( n<=db->lookaside.szTrue ) return p; + } + } + return dbReallocFinish(db, p, n); +} +static SQLITE_NOINLINE void *dbReallocFinish(sqlite3 *db, void *p, u64 n){ + void *pNew = 0; + assert( db!=0 ); + assert( p!=0 ); + if( db->mallocFailed==0 ){ + if( isLookaside(db, p) ){ + pNew = sqlite3DbMallocRawNN(db, n); + if( pNew ){ + memcpy(pNew, p, lookasideMallocSize(db, p)); + sqlite3DbFree(db, p); + } + }else{ + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + sqlite3MemdebugSetType(p, MEMTYPE_HEAP); + pNew = sqlite3Realloc(p, n); + if( !pNew ){ + sqlite3OomFault(db); + } + sqlite3MemdebugSetType(pNew, + (db->lookaside.bDisable==0 ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP)); + } + } + return pNew; +} + +/* +** Attempt to reallocate p. If the reallocation fails, then free p +** and set the mallocFailed flag in the database connection. +*/ +SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *db, void *p, u64 n){ + void *pNew; + pNew = sqlite3DbRealloc(db, p, n); + if( !pNew ){ + sqlite3DbFree(db, p); + } + return pNew; +} + +/* +** Make a copy of a string in memory obtained from sqliteMalloc(). These +** functions call sqlite3MallocRaw() directly instead of sqliteMalloc(). This +** is because when memory debugging is turned on, these two functions are +** called via macros that record the current file and line number in the +** ThreadData structure. +*/ +SQLITE_PRIVATE char *sqlite3DbStrDup(sqlite3 *db, const char *z){ + char *zNew; + size_t n; + if( z==0 ){ + return 0; + } + n = strlen(z) + 1; + zNew = sqlite3DbMallocRaw(db, n); + if( zNew ){ + memcpy(zNew, z, n); + } + return zNew; +} +SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3 *db, const char *z, u64 n){ + char *zNew; + assert( db!=0 ); + assert( z!=0 || n==0 ); + assert( (n&0x7fffffff)==n ); + zNew = z ? sqlite3DbMallocRawNN(db, n+1) : 0; + if( zNew ){ + memcpy(zNew, z, (size_t)n); + zNew[n] = 0; + } + return zNew; +} + +/* +** The text between zStart and zEnd represents a phrase within a larger +** SQL statement. Make a copy of this phrase in space obtained form +** sqlite3DbMalloc(). Omit leading and trailing whitespace. +*/ +SQLITE_PRIVATE char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){ + int n; +#ifdef SQLITE_DEBUG + /* Because of the way the parser works, the span is guaranteed to contain + ** at least one non-space character */ + for(n=0; sqlite3Isspace(zStart[n]); n++){ assert( &zStart[n]<zEnd ); } +#endif + while( sqlite3Isspace(zStart[0]) ) zStart++; + n = (int)(zEnd - zStart); + while( sqlite3Isspace(zStart[n-1]) ) n--; + return sqlite3DbStrNDup(db, zStart, n); +} + +/* +** Free any prior content in *pz and replace it with a copy of zNew. +*/ +SQLITE_PRIVATE void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){ + char *z = sqlite3DbStrDup(db, zNew); + sqlite3DbFree(db, *pz); + *pz = z; +} + +/* +** Call this routine to record the fact that an OOM (out-of-memory) error +** has happened. This routine will set db->mallocFailed, and also +** temporarily disable the lookaside memory allocator and interrupt +** any running VDBEs. +** +** Always return a NULL pointer so that this routine can be invoked using +** +** return sqlite3OomFault(db); +** +** and thereby avoid unnecessary stack frame allocations for the overwhelmingly +** common case where no OOM occurs. +*/ +SQLITE_PRIVATE void *sqlite3OomFault(sqlite3 *db){ + if( db->mallocFailed==0 && db->bBenignMalloc==0 ){ + db->mallocFailed = 1; + if( db->nVdbeExec>0 ){ + AtomicStore(&db->u1.isInterrupted, 1); + } + DisableLookaside; + if( db->pParse ){ + Parse *pParse; + sqlite3ErrorMsg(db->pParse, "out of memory"); + db->pParse->rc = SQLITE_NOMEM_BKPT; + for(pParse=db->pParse->pOuterParse; pParse; pParse = pParse->pOuterParse){ + pParse->nErr++; + pParse->rc = SQLITE_NOMEM; + } + } + } + return 0; +} + +/* +** This routine reactivates the memory allocator and clears the +** db->mallocFailed flag as necessary. +** +** The memory allocator is not restarted if there are running +** VDBEs. +*/ +SQLITE_PRIVATE void sqlite3OomClear(sqlite3 *db){ + if( db->mallocFailed && db->nVdbeExec==0 ){ + db->mallocFailed = 0; + AtomicStore(&db->u1.isInterrupted, 0); + assert( db->lookaside.bDisable>0 ); + EnableLookaside; + } +} + +/* +** Take actions at the end of an API call to deal with error codes. +*/ +static SQLITE_NOINLINE int apiHandleError(sqlite3 *db, int rc){ + if( db->mallocFailed || rc==SQLITE_IOERR_NOMEM ){ + sqlite3OomClear(db); + sqlite3Error(db, SQLITE_NOMEM); + return SQLITE_NOMEM_BKPT; + } + return rc & db->errMask; +} + +/* +** This function must be called before exiting any API function (i.e. +** returning control to the user) that has called sqlite3_malloc or +** sqlite3_realloc. +** +** The returned value is normally a copy of the second argument to this +** function. However, if a malloc() failure has occurred since the previous +** invocation SQLITE_NOMEM is returned instead. +** +** If an OOM as occurred, then the connection error-code (the value +** returned by sqlite3_errcode()) is set to SQLITE_NOMEM. +*/ +SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){ + /* If the db handle must hold the connection handle mutex here. + ** Otherwise the read (and possible write) of db->mallocFailed + ** is unsafe, as is the call to sqlite3Error(). + */ + assert( db!=0 ); + assert( sqlite3_mutex_held(db->mutex) ); + if( db->mallocFailed || rc ){ + return apiHandleError(db, rc); + } + return rc & db->errMask; +} + +/************** End of malloc.c **********************************************/ +/************** Begin file printf.c ******************************************/ +/* +** The "printf" code that follows dates from the 1980's. It is in +** the public domain. +** +************************************************************************** +** +** This file contains code for a set of "printf"-like routines. These +** routines format strings much like the printf() from the standard C +** library, though the implementation here has enhancements to support +** SQLite. +*/ +/* #include "sqliteInt.h" */ + +/* +** Conversion types fall into various categories as defined by the +** following enumeration. +*/ +#define etRADIX 0 /* non-decimal integer types. %x %o */ +#define etFLOAT 1 /* Floating point. %f */ +#define etEXP 2 /* Exponentional notation. %e and %E */ +#define etGENERIC 3 /* Floating or exponential, depending on exponent. %g */ +#define etSIZE 4 /* Return number of characters processed so far. %n */ +#define etSTRING 5 /* Strings. %s */ +#define etDYNSTRING 6 /* Dynamically allocated strings. %z */ +#define etPERCENT 7 /* Percent symbol. %% */ +#define etCHARX 8 /* Characters. %c */ +/* The rest are extensions, not normally found in printf() */ +#define etSQLESCAPE 9 /* Strings with '\'' doubled. %q */ +#define etSQLESCAPE2 10 /* Strings with '\'' doubled and enclosed in '', + NULL pointers replaced by SQL NULL. %Q */ +#define etTOKEN 11 /* a pointer to a Token structure */ +#define etSRCITEM 12 /* a pointer to a SrcItem */ +#define etPOINTER 13 /* The %p conversion */ +#define etSQLESCAPE3 14 /* %w -> Strings with '\"' doubled */ +#define etORDINAL 15 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ +#define etDECIMAL 16 /* %d or %u, but not %x, %o */ + +#define etINVALID 17 /* Any unrecognized conversion type */ + + +/* +** An "etByte" is an 8-bit unsigned value. +*/ +typedef unsigned char etByte; + +/* +** Each builtin conversion character (ex: the 'd' in "%d") is described +** by an instance of the following structure +*/ +typedef struct et_info { /* Information about each format field */ + char fmttype; /* The format field code letter */ + etByte base; /* The base for radix conversion */ + etByte flags; /* One or more of FLAG_ constants below */ + etByte type; /* Conversion paradigm */ + etByte charset; /* Offset into aDigits[] of the digits string */ + etByte prefix; /* Offset into aPrefix[] of the prefix string */ +} et_info; + +/* +** Allowed values for et_info.flags +*/ +#define FLAG_SIGNED 1 /* True if the value to convert is signed */ +#define FLAG_STRING 4 /* Allow infinite precision */ + + +/* +** The following table is searched linearly, so it is good to put the +** most frequently used conversion types first. +*/ +static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; +static const char aPrefix[] = "-x0\000X0"; +static const et_info fmtinfo[] = { + { 'd', 10, 1, etDECIMAL, 0, 0 }, + { 's', 0, 4, etSTRING, 0, 0 }, + { 'g', 0, 1, etGENERIC, 30, 0 }, + { 'z', 0, 4, etDYNSTRING, 0, 0 }, + { 'q', 0, 4, etSQLESCAPE, 0, 0 }, + { 'Q', 0, 4, etSQLESCAPE2, 0, 0 }, + { 'w', 0, 4, etSQLESCAPE3, 0, 0 }, + { 'c', 0, 0, etCHARX, 0, 0 }, + { 'o', 8, 0, etRADIX, 0, 2 }, + { 'u', 10, 0, etDECIMAL, 0, 0 }, + { 'x', 16, 0, etRADIX, 16, 1 }, + { 'X', 16, 0, etRADIX, 0, 4 }, +#ifndef SQLITE_OMIT_FLOATING_POINT + { 'f', 0, 1, etFLOAT, 0, 0 }, + { 'e', 0, 1, etEXP, 30, 0 }, + { 'E', 0, 1, etEXP, 14, 0 }, + { 'G', 0, 1, etGENERIC, 14, 0 }, +#endif + { 'i', 10, 1, etDECIMAL, 0, 0 }, + { 'n', 0, 0, etSIZE, 0, 0 }, + { '%', 0, 0, etPERCENT, 0, 0 }, + { 'p', 16, 0, etPOINTER, 0, 1 }, + + /* All the rest are undocumented and are for internal use only */ + { 'T', 0, 0, etTOKEN, 0, 0 }, + { 'S', 0, 0, etSRCITEM, 0, 0 }, + { 'r', 10, 1, etORDINAL, 0, 0 }, +}; + +/* Notes: +** +** %S Takes a pointer to SrcItem. Shows name or database.name +** %!S Like %S but prefer the zName over the zAlias +*/ + +/* +** Set the StrAccum object to an error mode. +*/ +SQLITE_PRIVATE void sqlite3StrAccumSetError(StrAccum *p, u8 eError){ + assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG ); + p->accError = eError; + if( p->mxAlloc ) sqlite3_str_reset(p); + if( eError==SQLITE_TOOBIG ) sqlite3ErrorToParser(p->db, eError); +} + +/* +** Extra argument values from a PrintfArguments object +*/ +static sqlite3_int64 getIntArg(PrintfArguments *p){ + if( p->nArg<=p->nUsed ) return 0; + return sqlite3_value_int64(p->apArg[p->nUsed++]); +} +static double getDoubleArg(PrintfArguments *p){ + if( p->nArg<=p->nUsed ) return 0.0; + return sqlite3_value_double(p->apArg[p->nUsed++]); +} +static char *getTextArg(PrintfArguments *p){ + if( p->nArg<=p->nUsed ) return 0; + return (char*)sqlite3_value_text(p->apArg[p->nUsed++]); +} + +/* +** Allocate memory for a temporary buffer needed for printf rendering. +** +** If the requested size of the temp buffer is larger than the size +** of the output buffer in pAccum, then cause an SQLITE_TOOBIG error. +** Do the size check before the memory allocation to prevent rogue +** SQL from requesting large allocations using the precision or width +** field of the printf() function. +*/ +static char *printfTempBuf(sqlite3_str *pAccum, sqlite3_int64 n){ + char *z; + if( pAccum->accError ) return 0; + if( n>pAccum->nAlloc && n>pAccum->mxAlloc ){ + sqlite3StrAccumSetError(pAccum, SQLITE_TOOBIG); + return 0; + } + z = sqlite3DbMallocRaw(pAccum->db, n); + if( z==0 ){ + sqlite3StrAccumSetError(pAccum, SQLITE_NOMEM); + } + return z; +} + +/* +** On machines with a small stack size, you can redefine the +** SQLITE_PRINT_BUF_SIZE to be something smaller, if desired. +*/ +#ifndef SQLITE_PRINT_BUF_SIZE +# define SQLITE_PRINT_BUF_SIZE 70 +#endif +#define etBUFSIZE SQLITE_PRINT_BUF_SIZE /* Size of the output buffer */ + +/* +** Hard limit on the precision of floating-point conversions. +*/ +#ifndef SQLITE_PRINTF_PRECISION_LIMIT +# define SQLITE_FP_PRECISION_LIMIT 100000000 +#endif + +/* +** Render a string given by "fmt" into the StrAccum object. +*/ +SQLITE_API void sqlite3_str_vappendf( + sqlite3_str *pAccum, /* Accumulate results here */ + const char *fmt, /* Format string */ + va_list ap /* arguments */ +){ + int c; /* Next character in the format string */ + char *bufpt; /* Pointer to the conversion buffer */ + int precision; /* Precision of the current field */ + int length; /* Length of the field */ + int idx; /* A general purpose loop counter */ + int width; /* Width of the current field */ + etByte flag_leftjustify; /* True if "-" flag is present */ + etByte flag_prefix; /* '+' or ' ' or 0 for prefix */ + etByte flag_alternateform; /* True if "#" flag is present */ + etByte flag_altform2; /* True if "!" flag is present */ + etByte flag_zeropad; /* True if field width constant starts with zero */ + etByte flag_long; /* 1 for the "l" flag, 2 for "ll", 0 by default */ + etByte done; /* Loop termination flag */ + etByte cThousand; /* Thousands separator for %d and %u */ + etByte xtype = etINVALID; /* Conversion paradigm */ + u8 bArgList; /* True for SQLITE_PRINTF_SQLFUNC */ + char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ + sqlite_uint64 longvalue; /* Value for integer types */ + double realvalue; /* Value for real types */ + const et_info *infop; /* Pointer to the appropriate info structure */ + char *zOut; /* Rendering buffer */ + int nOut; /* Size of the rendering buffer */ + char *zExtra = 0; /* Malloced memory used by some conversion */ + int exp, e2; /* exponent of real numbers */ + etByte flag_dp; /* True if decimal point should be shown */ + etByte flag_rtz; /* True if trailing zeros should be removed */ + + PrintfArguments *pArgList = 0; /* Arguments for SQLITE_PRINTF_SQLFUNC */ + char buf[etBUFSIZE]; /* Conversion buffer */ + + /* pAccum never starts out with an empty buffer that was obtained from + ** malloc(). This precondition is required by the mprintf("%z...") + ** optimization. */ + assert( pAccum->nChar>0 || (pAccum->printfFlags&SQLITE_PRINTF_MALLOCED)==0 ); + + bufpt = 0; + if( (pAccum->printfFlags & SQLITE_PRINTF_SQLFUNC)!=0 ){ + pArgList = va_arg(ap, PrintfArguments*); + bArgList = 1; + }else{ + bArgList = 0; + } + for(; (c=(*fmt))!=0; ++fmt){ + if( c!='%' ){ + bufpt = (char *)fmt; +#if HAVE_STRCHRNUL + fmt = strchrnul(fmt, '%'); +#else + do{ fmt++; }while( *fmt && *fmt != '%' ); +#endif + sqlite3_str_append(pAccum, bufpt, (int)(fmt - bufpt)); + if( *fmt==0 ) break; + } + if( (c=(*++fmt))==0 ){ + sqlite3_str_append(pAccum, "%", 1); + break; + } + /* Find out what flags are present */ + flag_leftjustify = flag_prefix = cThousand = + flag_alternateform = flag_altform2 = flag_zeropad = 0; + done = 0; + width = 0; + flag_long = 0; + precision = -1; + do{ + switch( c ){ + case '-': flag_leftjustify = 1; break; + case '+': flag_prefix = '+'; break; + case ' ': flag_prefix = ' '; break; + case '#': flag_alternateform = 1; break; + case '!': flag_altform2 = 1; break; + case '0': flag_zeropad = 1; break; + case ',': cThousand = ','; break; + default: done = 1; break; + case 'l': { + flag_long = 1; + c = *++fmt; + if( c=='l' ){ + c = *++fmt; + flag_long = 2; + } + done = 1; + break; + } + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': { + unsigned wx = c - '0'; + while( (c = *++fmt)>='0' && c<='9' ){ + wx = wx*10 + c - '0'; + } + testcase( wx>0x7fffffff ); + width = wx & 0x7fffffff; +#ifdef SQLITE_PRINTF_PRECISION_LIMIT + if( width>SQLITE_PRINTF_PRECISION_LIMIT ){ + width = SQLITE_PRINTF_PRECISION_LIMIT; + } +#endif + if( c!='.' && c!='l' ){ + done = 1; + }else{ + fmt--; + } + break; + } + case '*': { + if( bArgList ){ + width = (int)getIntArg(pArgList); + }else{ + width = va_arg(ap,int); + } + if( width<0 ){ + flag_leftjustify = 1; + width = width >= -2147483647 ? -width : 0; + } +#ifdef SQLITE_PRINTF_PRECISION_LIMIT + if( width>SQLITE_PRINTF_PRECISION_LIMIT ){ + width = SQLITE_PRINTF_PRECISION_LIMIT; + } +#endif + if( (c = fmt[1])!='.' && c!='l' ){ + c = *++fmt; + done = 1; + } + break; + } + case '.': { + c = *++fmt; + if( c=='*' ){ + if( bArgList ){ + precision = (int)getIntArg(pArgList); + }else{ + precision = va_arg(ap,int); + } + if( precision<0 ){ + precision = precision >= -2147483647 ? -precision : -1; + } + c = *++fmt; + }else{ + unsigned px = 0; + while( c>='0' && c<='9' ){ + px = px*10 + c - '0'; + c = *++fmt; + } + testcase( px>0x7fffffff ); + precision = px & 0x7fffffff; + } +#ifdef SQLITE_PRINTF_PRECISION_LIMIT + if( precision>SQLITE_PRINTF_PRECISION_LIMIT ){ + precision = SQLITE_PRINTF_PRECISION_LIMIT; + } +#endif + if( c=='l' ){ + --fmt; + }else{ + done = 1; + } + break; + } + } + }while( !done && (c=(*++fmt))!=0 ); + + /* Fetch the info entry for the field */ + infop = &fmtinfo[0]; + xtype = etINVALID; + for(idx=0; idx<ArraySize(fmtinfo); idx++){ + if( c==fmtinfo[idx].fmttype ){ + infop = &fmtinfo[idx]; + xtype = infop->type; + break; + } + } + + /* + ** At this point, variables are initialized as follows: + ** + ** flag_alternateform TRUE if a '#' is present. + ** flag_altform2 TRUE if a '!' is present. + ** flag_prefix '+' or ' ' or zero + ** flag_leftjustify TRUE if a '-' is present or if the + ** field width was negative. + ** flag_zeropad TRUE if the width began with 0. + ** flag_long 1 for "l", 2 for "ll" + ** width The specified field width. This is + ** always non-negative. Zero is the default. + ** precision The specified precision. The default + ** is -1. + ** xtype The class of the conversion. + ** infop Pointer to the appropriate info struct. + */ + assert( width>=0 ); + assert( precision>=(-1) ); + switch( xtype ){ + case etPOINTER: + flag_long = sizeof(char*)==sizeof(i64) ? 2 : + sizeof(char*)==sizeof(long int) ? 1 : 0; + /* no break */ deliberate_fall_through + case etORDINAL: + case etRADIX: + cThousand = 0; + /* no break */ deliberate_fall_through + case etDECIMAL: + if( infop->flags & FLAG_SIGNED ){ + i64 v; + if( bArgList ){ + v = getIntArg(pArgList); + }else if( flag_long ){ + if( flag_long==2 ){ + v = va_arg(ap,i64) ; + }else{ + v = va_arg(ap,long int); + } + }else{ + v = va_arg(ap,int); + } + if( v<0 ){ + testcase( v==SMALLEST_INT64 ); + testcase( v==(-1) ); + longvalue = ~v; + longvalue++; + prefix = '-'; + }else{ + longvalue = v; + prefix = flag_prefix; + } + }else{ + if( bArgList ){ + longvalue = (u64)getIntArg(pArgList); + }else if( flag_long ){ + if( flag_long==2 ){ + longvalue = va_arg(ap,u64); + }else{ + longvalue = va_arg(ap,unsigned long int); + } + }else{ + longvalue = va_arg(ap,unsigned int); + } + prefix = 0; + } + if( longvalue==0 ) flag_alternateform = 0; + if( flag_zeropad && precision<width-(prefix!=0) ){ + precision = width-(prefix!=0); + } + if( precision<etBUFSIZE-10-etBUFSIZE/3 ){ + nOut = etBUFSIZE; + zOut = buf; + }else{ + u64 n; + n = (u64)precision + 10; + if( cThousand ) n += precision/3; + zOut = zExtra = printfTempBuf(pAccum, n); + if( zOut==0 ) return; + nOut = (int)n; + } + bufpt = &zOut[nOut-1]; + if( xtype==etORDINAL ){ + static const char zOrd[] = "thstndrd"; + int x = (int)(longvalue % 10); + if( x>=4 || (longvalue/10)%10==1 ){ + x = 0; + } + *(--bufpt) = zOrd[x*2+1]; + *(--bufpt) = zOrd[x*2]; + } + { + const char *cset = &aDigits[infop->charset]; + u8 base = infop->base; + do{ /* Convert to ascii */ + *(--bufpt) = cset[longvalue%base]; + longvalue = longvalue/base; + }while( longvalue>0 ); + } + length = (int)(&zOut[nOut-1]-bufpt); + while( precision>length ){ + *(--bufpt) = '0'; /* Zero pad */ + length++; + } + if( cThousand ){ + int nn = (length - 1)/3; /* Number of "," to insert */ + int ix = (length - 1)%3 + 1; + bufpt -= nn; + for(idx=0; nn>0; idx++){ + bufpt[idx] = bufpt[idx+nn]; + ix--; + if( ix==0 ){ + bufpt[++idx] = cThousand; + nn--; + ix = 3; + } + } + } + if( prefix ) *(--bufpt) = prefix; /* Add sign */ + if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ + const char *pre; + char x; + pre = &aPrefix[infop->prefix]; + for(; (x=(*pre))!=0; pre++) *(--bufpt) = x; + } + length = (int)(&zOut[nOut-1]-bufpt); + break; + case etFLOAT: + case etEXP: + case etGENERIC: { + FpDecode s; + int iRound; + int j; + + if( bArgList ){ + realvalue = getDoubleArg(pArgList); + }else{ + realvalue = va_arg(ap,double); + } + if( precision<0 ) precision = 6; /* Set default precision */ +#ifdef SQLITE_FP_PRECISION_LIMIT + if( precision>SQLITE_FP_PRECISION_LIMIT ){ + precision = SQLITE_FP_PRECISION_LIMIT; + } +#endif + if( xtype==etFLOAT ){ + iRound = -precision; + }else if( xtype==etGENERIC ){ + iRound = precision; + }else{ + iRound = precision+1; + } + sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 26 : 16); + if( s.isSpecial ){ + if( s.isSpecial==2 ){ + bufpt = flag_zeropad ? "null" : "NaN"; + length = sqlite3Strlen30(bufpt); + break; + }else if( flag_zeropad ){ + s.z[0] = '9'; + s.iDP = 1000; + s.n = 1; + }else{ + memcpy(buf, "-Inf", 5); + bufpt = buf; + if( s.sign=='-' ){ + /* no-op */ + }else if( flag_prefix ){ + buf[0] = flag_prefix; + }else{ + bufpt++; + } + length = sqlite3Strlen30(bufpt); + break; + } + } + if( s.sign=='-' ){ + prefix = '-'; + }else{ + prefix = flag_prefix; + } + + exp = s.iDP-1; + if( xtype==etGENERIC && precision>0 ) precision--; + + /* + ** If the field type is etGENERIC, then convert to either etEXP + ** or etFLOAT, as appropriate. + */ + if( xtype==etGENERIC ){ + flag_rtz = !flag_alternateform; + if( exp<-4 || exp>precision ){ + xtype = etEXP; + }else{ + precision = precision - exp; + xtype = etFLOAT; + } + }else{ + flag_rtz = flag_altform2; + } + if( xtype==etEXP ){ + e2 = 0; + }else{ + e2 = s.iDP - 1; + } + bufpt = buf; + { + i64 szBufNeeded; /* Size of a temporary buffer needed */ + szBufNeeded = MAX(e2,0)+(i64)precision+(i64)width+15; + if( cThousand && e2>0 ) szBufNeeded += (e2+2)/3; + if( szBufNeeded > etBUFSIZE ){ + bufpt = zExtra = printfTempBuf(pAccum, szBufNeeded); + if( bufpt==0 ) return; + } + } + zOut = bufpt; + flag_dp = (precision>0 ?1:0) | flag_alternateform | flag_altform2; + /* The sign in front of the number */ + if( prefix ){ + *(bufpt++) = prefix; + } + /* Digits prior to the decimal point */ + j = 0; + if( e2<0 ){ + *(bufpt++) = '0'; + }else{ + for(; e2>=0; e2--){ + *(bufpt++) = j<s.n ? s.z[j++] : '0'; + if( cThousand && (e2%3)==0 && e2>1 ) *(bufpt++) = ','; + } + } + /* The decimal point */ + if( flag_dp ){ + *(bufpt++) = '.'; + } + /* "0" digits after the decimal point but before the first + ** significant digit of the number */ + for(e2++; e2<0 && precision>0; precision--, e2++){ + *(bufpt++) = '0'; + } + /* Significant digits after the decimal point */ + while( (precision--)>0 ){ + *(bufpt++) = j<s.n ? s.z[j++] : '0'; + } + /* Remove trailing zeros and the "." if no digits follow the "." */ + if( flag_rtz && flag_dp ){ + while( bufpt[-1]=='0' ) *(--bufpt) = 0; + assert( bufpt>zOut ); + if( bufpt[-1]=='.' ){ + if( flag_altform2 ){ + *(bufpt++) = '0'; + }else{ + *(--bufpt) = 0; + } + } + } + /* Add the "eNNN" suffix */ + if( xtype==etEXP ){ + exp = s.iDP - 1; + *(bufpt++) = aDigits[infop->charset]; + if( exp<0 ){ + *(bufpt++) = '-'; exp = -exp; + }else{ + *(bufpt++) = '+'; + } + if( exp>=100 ){ + *(bufpt++) = (char)((exp/100)+'0'); /* 100's digit */ + exp %= 100; + } + *(bufpt++) = (char)(exp/10+'0'); /* 10's digit */ + *(bufpt++) = (char)(exp%10+'0'); /* 1's digit */ + } + *bufpt = 0; + + /* The converted number is in buf[] and zero terminated. Output it. + ** Note that the number is in the usual order, not reversed as with + ** integer conversions. */ + length = (int)(bufpt-zOut); + bufpt = zOut; + + /* Special case: Add leading zeros if the flag_zeropad flag is + ** set and we are not left justified */ + if( flag_zeropad && !flag_leftjustify && length < width){ + int i; + int nPad = width - length; + for(i=width; i>=nPad; i--){ + bufpt[i] = bufpt[i-nPad]; + } + i = prefix!=0; + while( nPad-- ) bufpt[i++] = '0'; + length = width; + } + break; + } + case etSIZE: + if( !bArgList ){ + *(va_arg(ap,int*)) = pAccum->nChar; + } + length = width = 0; + break; + case etPERCENT: + buf[0] = '%'; + bufpt = buf; + length = 1; + break; + case etCHARX: + if( bArgList ){ + bufpt = getTextArg(pArgList); + length = 1; + if( bufpt ){ + buf[0] = c = *(bufpt++); + if( (c&0xc0)==0xc0 ){ + while( length<4 && (bufpt[0]&0xc0)==0x80 ){ + buf[length++] = *(bufpt++); + } + } + }else{ + buf[0] = 0; + } + }else{ + unsigned int ch = va_arg(ap,unsigned int); + if( ch<0x00080 ){ + buf[0] = ch & 0xff; + length = 1; + }else if( ch<0x00800 ){ + buf[0] = 0xc0 + (u8)((ch>>6)&0x1f); + buf[1] = 0x80 + (u8)(ch & 0x3f); + length = 2; + }else if( ch<0x10000 ){ + buf[0] = 0xe0 + (u8)((ch>>12)&0x0f); + buf[1] = 0x80 + (u8)((ch>>6) & 0x3f); + buf[2] = 0x80 + (u8)(ch & 0x3f); + length = 3; + }else{ + buf[0] = 0xf0 + (u8)((ch>>18) & 0x07); + buf[1] = 0x80 + (u8)((ch>>12) & 0x3f); + buf[2] = 0x80 + (u8)((ch>>6) & 0x3f); + buf[3] = 0x80 + (u8)(ch & 0x3f); + length = 4; + } + } + if( precision>1 ){ + i64 nPrior = 1; + width -= precision-1; + if( width>1 && !flag_leftjustify ){ + sqlite3_str_appendchar(pAccum, width-1, ' '); + width = 0; + } + sqlite3_str_append(pAccum, buf, length); + precision--; + while( precision > 1 ){ + i64 nCopyBytes; + if( nPrior > precision-1 ) nPrior = precision - 1; + nCopyBytes = length*nPrior; + if( nCopyBytes + pAccum->nChar >= pAccum->nAlloc ){ + sqlite3StrAccumEnlarge(pAccum, nCopyBytes); + } + if( pAccum->accError ) break; + sqlite3_str_append(pAccum, + &pAccum->zText[pAccum->nChar-nCopyBytes], nCopyBytes); + precision -= nPrior; + nPrior *= 2; + } + } + bufpt = buf; + flag_altform2 = 1; + goto adjust_width_for_utf8; + case etSTRING: + case etDYNSTRING: + if( bArgList ){ + bufpt = getTextArg(pArgList); + xtype = etSTRING; + }else{ + bufpt = va_arg(ap,char*); + } + if( bufpt==0 ){ + bufpt = ""; + }else if( xtype==etDYNSTRING ){ + if( pAccum->nChar==0 + && pAccum->mxAlloc + && width==0 + && precision<0 + && pAccum->accError==0 + ){ + /* Special optimization for sqlite3_mprintf("%z..."): + ** Extend an existing memory allocation rather than creating + ** a new one. */ + assert( (pAccum->printfFlags&SQLITE_PRINTF_MALLOCED)==0 ); + pAccum->zText = bufpt; + pAccum->nAlloc = sqlite3DbMallocSize(pAccum->db, bufpt); + pAccum->nChar = 0x7fffffff & (int)strlen(bufpt); + pAccum->printfFlags |= SQLITE_PRINTF_MALLOCED; + length = 0; + break; + } + zExtra = bufpt; + } + if( precision>=0 ){ + if( flag_altform2 ){ + /* Set length to the number of bytes needed in order to display + ** precision characters */ + unsigned char *z = (unsigned char*)bufpt; + while( precision-- > 0 && z[0] ){ + SQLITE_SKIP_UTF8(z); + } + length = (int)(z - (unsigned char*)bufpt); + }else{ + for(length=0; length<precision && bufpt[length]; length++){} + } + }else{ + length = 0x7fffffff & (int)strlen(bufpt); + } + adjust_width_for_utf8: + if( flag_altform2 && width>0 ){ + /* Adjust width to account for extra bytes in UTF-8 characters */ + int ii = length - 1; + while( ii>=0 ) if( (bufpt[ii--] & 0xc0)==0x80 ) width++; + } + break; + case etSQLESCAPE: /* %q: Escape ' characters */ + case etSQLESCAPE2: /* %Q: Escape ' and enclose in '...' */ + case etSQLESCAPE3: { /* %w: Escape " characters */ + i64 i, j, k, n; + int needQuote, isnull; + char ch; + char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */ + char *escarg; + + if( bArgList ){ + escarg = getTextArg(pArgList); + }else{ + escarg = va_arg(ap,char*); + } + isnull = escarg==0; + if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)"); + /* For %q, %Q, and %w, the precision is the number of bytes (or + ** characters if the ! flags is present) to use from the input. + ** Because of the extra quoting characters inserted, the number + ** of output characters may be larger than the precision. + */ + k = precision; + for(i=n=0; k!=0 && (ch=escarg[i])!=0; i++, k--){ + if( ch==q ) n++; + if( flag_altform2 && (ch&0xc0)==0xc0 ){ + while( (escarg[i+1]&0xc0)==0x80 ){ i++; } + } + } + needQuote = !isnull && xtype==etSQLESCAPE2; + n += i + 3; + if( n>etBUFSIZE ){ + bufpt = zExtra = printfTempBuf(pAccum, n); + if( bufpt==0 ) return; + }else{ + bufpt = buf; + } + j = 0; + if( needQuote ) bufpt[j++] = q; + k = i; + for(i=0; i<k; i++){ + bufpt[j++] = ch = escarg[i]; + if( ch==q ) bufpt[j++] = ch; + } + if( needQuote ) bufpt[j++] = q; + bufpt[j] = 0; + length = j; + goto adjust_width_for_utf8; + } + case etTOKEN: { + if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; + if( flag_alternateform ){ + /* %#T means an Expr pointer that uses Expr.u.zToken */ + Expr *pExpr = va_arg(ap,Expr*); + if( ALWAYS(pExpr) && ALWAYS(!ExprHasProperty(pExpr,EP_IntValue)) ){ + sqlite3_str_appendall(pAccum, (const char*)pExpr->u.zToken); + sqlite3RecordErrorOffsetOfExpr(pAccum->db, pExpr); + } + }else{ + /* %T means a Token pointer */ + Token *pToken = va_arg(ap, Token*); + assert( bArgList==0 ); + if( pToken && pToken->n ){ + sqlite3_str_append(pAccum, (const char*)pToken->z, pToken->n); + sqlite3RecordErrorByteOffset(pAccum->db, pToken->z); + } + } + length = width = 0; + break; + } + case etSRCITEM: { + SrcItem *pItem; + if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; + pItem = va_arg(ap, SrcItem*); + assert( bArgList==0 ); + if( pItem->zAlias && !flag_altform2 ){ + sqlite3_str_appendall(pAccum, pItem->zAlias); + }else if( pItem->zName ){ + if( pItem->zDatabase ){ + sqlite3_str_appendall(pAccum, pItem->zDatabase); + sqlite3_str_append(pAccum, ".", 1); + } + sqlite3_str_appendall(pAccum, pItem->zName); + }else if( pItem->zAlias ){ + sqlite3_str_appendall(pAccum, pItem->zAlias); + }else{ + Select *pSel = pItem->pSelect; + assert( pSel!=0 ); + if( pSel->selFlags & SF_NestedFrom ){ + sqlite3_str_appendf(pAccum, "(join-%u)", pSel->selId); + }else{ + sqlite3_str_appendf(pAccum, "(subquery-%u)", pSel->selId); + } + } + length = width = 0; + break; + } + default: { + assert( xtype==etINVALID ); + return; + } + }/* End switch over the format type */ + /* + ** The text of the conversion is pointed to by "bufpt" and is + ** "length" characters long. The field width is "width". Do + ** the output. Both length and width are in bytes, not characters, + ** at this point. If the "!" flag was present on string conversions + ** indicating that width and precision should be expressed in characters, + ** then the values have been translated prior to reaching this point. + */ + width -= length; + if( width>0 ){ + if( !flag_leftjustify ) sqlite3_str_appendchar(pAccum, width, ' '); + sqlite3_str_append(pAccum, bufpt, length); + if( flag_leftjustify ) sqlite3_str_appendchar(pAccum, width, ' '); + }else{ + sqlite3_str_append(pAccum, bufpt, length); + } + + if( zExtra ){ + sqlite3DbFree(pAccum->db, zExtra); + zExtra = 0; + } + }/* End for loop over the format string */ +} /* End of function */ + + +/* +** The z string points to the first character of a token that is +** associated with an error. If db does not already have an error +** byte offset recorded, try to compute the error byte offset for +** z and set the error byte offset in db. +*/ +SQLITE_PRIVATE void sqlite3RecordErrorByteOffset(sqlite3 *db, const char *z){ + const Parse *pParse; + const char *zText; + const char *zEnd; + assert( z!=0 ); + if( NEVER(db==0) ) return; + if( db->errByteOffset!=(-2) ) return; + pParse = db->pParse; + if( NEVER(pParse==0) ) return; + zText =pParse->zTail; + if( NEVER(zText==0) ) return; + zEnd = &zText[strlen(zText)]; + if( SQLITE_WITHIN(z,zText,zEnd) ){ + db->errByteOffset = (int)(z-zText); + } +} + +/* +** If pExpr has a byte offset for the start of a token, record that as +** as the error offset. +*/ +SQLITE_PRIVATE void sqlite3RecordErrorOffsetOfExpr(sqlite3 *db, const Expr *pExpr){ + while( pExpr + && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0) + ){ + pExpr = pExpr->pLeft; + } + if( pExpr==0 ) return; + db->errByteOffset = pExpr->w.iOfst; +} + +/* +** Enlarge the memory allocation on a StrAccum object so that it is +** able to accept at least N more bytes of text. +** +** Return the number of bytes of text that StrAccum is able to accept +** after the attempted enlargement. The value returned might be zero. +*/ +SQLITE_PRIVATE int sqlite3StrAccumEnlarge(StrAccum *p, i64 N){ + char *zNew; + assert( p->nChar+N >= p->nAlloc ); /* Only called if really needed */ + if( p->accError ){ + testcase(p->accError==SQLITE_TOOBIG); + testcase(p->accError==SQLITE_NOMEM); + return 0; + } + if( p->mxAlloc==0 ){ + sqlite3StrAccumSetError(p, SQLITE_TOOBIG); + return p->nAlloc - p->nChar - 1; + }else{ + char *zOld = isMalloced(p) ? p->zText : 0; + i64 szNew = p->nChar + N + 1; + if( szNew+p->nChar<=p->mxAlloc ){ + /* Force exponential buffer size growth as long as it does not overflow, + ** to avoid having to call this routine too often */ + szNew += p->nChar; + } + if( szNew > p->mxAlloc ){ + sqlite3_str_reset(p); + sqlite3StrAccumSetError(p, SQLITE_TOOBIG); + return 0; + }else{ + p->nAlloc = (int)szNew; + } + if( p->db ){ + zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc); + }else{ + zNew = sqlite3Realloc(zOld, p->nAlloc); + } + if( zNew ){ + assert( p->zText!=0 || p->nChar==0 ); + if( !isMalloced(p) && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar); + p->zText = zNew; + p->nAlloc = sqlite3DbMallocSize(p->db, zNew); + p->printfFlags |= SQLITE_PRINTF_MALLOCED; + }else{ + sqlite3_str_reset(p); + sqlite3StrAccumSetError(p, SQLITE_NOMEM); + return 0; + } + } + assert( N>=0 && N<=0x7fffffff ); + return (int)N; +} + +/* +** Append N copies of character c to the given string buffer. +*/ +SQLITE_API void sqlite3_str_appendchar(sqlite3_str *p, int N, char c){ + testcase( p->nChar + (i64)N > 0x7fffffff ); + if( p->nChar+(i64)N >= p->nAlloc && (N = sqlite3StrAccumEnlarge(p, N))<=0 ){ + return; + } + while( (N--)>0 ) p->zText[p->nChar++] = c; +} + +/* +** The StrAccum "p" is not large enough to accept N new bytes of z[]. +** So enlarge if first, then do the append. +** +** This is a helper routine to sqlite3_str_append() that does special-case +** work (enlarging the buffer) using tail recursion, so that the +** sqlite3_str_append() routine can use fast calling semantics. +*/ +static void SQLITE_NOINLINE enlargeAndAppend(StrAccum *p, const char *z, int N){ + N = sqlite3StrAccumEnlarge(p, N); + if( N>0 ){ + memcpy(&p->zText[p->nChar], z, N); + p->nChar += N; + } +} + +/* +** Append N bytes of text from z to the StrAccum object. Increase the +** size of the memory allocation for StrAccum if necessary. +*/ +SQLITE_API void sqlite3_str_append(sqlite3_str *p, const char *z, int N){ + assert( z!=0 || N==0 ); + assert( p->zText!=0 || p->nChar==0 || p->accError ); + assert( N>=0 ); + assert( p->accError==0 || p->nAlloc==0 || p->mxAlloc==0 ); + if( p->nChar+N >= p->nAlloc ){ + enlargeAndAppend(p,z,N); + }else if( N ){ + assert( p->zText ); + p->nChar += N; + memcpy(&p->zText[p->nChar-N], z, N); + } +} + +/* +** Append the complete text of zero-terminated string z[] to the p string. +*/ +SQLITE_API void sqlite3_str_appendall(sqlite3_str *p, const char *z){ + sqlite3_str_append(p, z, sqlite3Strlen30(z)); +} + + +/* +** Finish off a string by making sure it is zero-terminated. +** Return a pointer to the resulting string. Return a NULL +** pointer if any kind of error was encountered. +*/ +static SQLITE_NOINLINE char *strAccumFinishRealloc(StrAccum *p){ + char *zText; + assert( p->mxAlloc>0 && !isMalloced(p) ); + zText = sqlite3DbMallocRaw(p->db, p->nChar+1 ); + if( zText ){ + memcpy(zText, p->zText, p->nChar+1); + p->printfFlags |= SQLITE_PRINTF_MALLOCED; + }else{ + sqlite3StrAccumSetError(p, SQLITE_NOMEM); + } + p->zText = zText; + return zText; +} +SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){ + if( p->zText ){ + p->zText[p->nChar] = 0; + if( p->mxAlloc>0 && !isMalloced(p) ){ + return strAccumFinishRealloc(p); + } + } + return p->zText; +} + +/* +** Use the content of the StrAccum passed as the second argument +** as the result of an SQL function. +*/ +SQLITE_PRIVATE void sqlite3ResultStrAccum(sqlite3_context *pCtx, StrAccum *p){ + if( p->accError ){ + sqlite3_result_error_code(pCtx, p->accError); + sqlite3_str_reset(p); + }else if( isMalloced(p) ){ + sqlite3_result_text(pCtx, p->zText, p->nChar, SQLITE_DYNAMIC); + }else{ + sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC); + sqlite3_str_reset(p); + } +} + +/* +** This singleton is an sqlite3_str object that is returned if +** sqlite3_malloc() fails to provide space for a real one. This +** sqlite3_str object accepts no new text and always returns +** an SQLITE_NOMEM error. +*/ +static sqlite3_str sqlite3OomStr = { + 0, 0, 0, 0, 0, SQLITE_NOMEM, 0 +}; + +/* Finalize a string created using sqlite3_str_new(). +*/ +SQLITE_API char *sqlite3_str_finish(sqlite3_str *p){ + char *z; + if( p!=0 && p!=&sqlite3OomStr ){ + z = sqlite3StrAccumFinish(p); + sqlite3_free(p); + }else{ + z = 0; + } + return z; +} + +/* Return any error code associated with p */ +SQLITE_API int sqlite3_str_errcode(sqlite3_str *p){ + return p ? p->accError : SQLITE_NOMEM; +} + +/* Return the current length of p in bytes */ +SQLITE_API int sqlite3_str_length(sqlite3_str *p){ + return p ? p->nChar : 0; +} + +/* Return the current value for p */ +SQLITE_API char *sqlite3_str_value(sqlite3_str *p){ + if( p==0 || p->nChar==0 ) return 0; + p->zText[p->nChar] = 0; + return p->zText; +} + +/* +** Reset an StrAccum string. Reclaim all malloced memory. +*/ +SQLITE_API void sqlite3_str_reset(StrAccum *p){ + if( isMalloced(p) ){ + sqlite3DbFree(p->db, p->zText); + p->printfFlags &= ~SQLITE_PRINTF_MALLOCED; + } + p->nAlloc = 0; + p->nChar = 0; + p->zText = 0; +} + +/* +** Initialize a string accumulator. +** +** p: The accumulator to be initialized. +** db: Pointer to a database connection. May be NULL. Lookaside +** memory is used if not NULL. db->mallocFailed is set appropriately +** when not NULL. +** zBase: An initial buffer. May be NULL in which case the initial buffer +** is malloced. +** n: Size of zBase in bytes. If total space requirements never exceed +** n then no memory allocations ever occur. +** mx: Maximum number of bytes to accumulate. If mx==0 then no memory +** allocations will ever occur. +*/ +SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum *p, sqlite3 *db, char *zBase, int n, int mx){ + p->zText = zBase; + p->db = db; + p->nAlloc = n; + p->mxAlloc = mx; + p->nChar = 0; + p->accError = 0; + p->printfFlags = 0; +} + +/* Allocate and initialize a new dynamic string object */ +SQLITE_API sqlite3_str *sqlite3_str_new(sqlite3 *db){ + sqlite3_str *p = sqlite3_malloc64(sizeof(*p)); + if( p ){ + sqlite3StrAccumInit(p, 0, 0, 0, + db ? db->aLimit[SQLITE_LIMIT_LENGTH] : SQLITE_MAX_LENGTH); + }else{ + p = &sqlite3OomStr; + } + return p; +} + +/* +** Print into memory obtained from sqliteMalloc(). Use the internal +** %-conversion extensions. +*/ +SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3 *db, const char *zFormat, va_list ap){ + char *z; + char zBase[SQLITE_PRINT_BUF_SIZE]; + StrAccum acc; + assert( db!=0 ); + sqlite3StrAccumInit(&acc, db, zBase, sizeof(zBase), + db->aLimit[SQLITE_LIMIT_LENGTH]); + acc.printfFlags = SQLITE_PRINTF_INTERNAL; + sqlite3_str_vappendf(&acc, zFormat, ap); + z = sqlite3StrAccumFinish(&acc); + if( acc.accError==SQLITE_NOMEM ){ + sqlite3OomFault(db); + } + return z; +} + +/* +** Print into memory obtained from sqliteMalloc(). Use the internal +** %-conversion extensions. +*/ +SQLITE_PRIVATE char *sqlite3MPrintf(sqlite3 *db, const char *zFormat, ...){ + va_list ap; + char *z; + va_start(ap, zFormat); + z = sqlite3VMPrintf(db, zFormat, ap); + va_end(ap); + return z; +} + +/* +** Print into memory obtained from sqlite3_malloc(). Omit the internal +** %-conversion extensions. +*/ +SQLITE_API char *sqlite3_vmprintf(const char *zFormat, va_list ap){ + char *z; + char zBase[SQLITE_PRINT_BUF_SIZE]; + StrAccum acc; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( zFormat==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + sqlite3StrAccumInit(&acc, 0, zBase, sizeof(zBase), SQLITE_MAX_LENGTH); + sqlite3_str_vappendf(&acc, zFormat, ap); + z = sqlite3StrAccumFinish(&acc); + return z; +} + +/* +** Print into memory obtained from sqlite3_malloc()(). Omit the internal +** %-conversion extensions. +*/ +SQLITE_API char *sqlite3_mprintf(const char *zFormat, ...){ + va_list ap; + char *z; +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + va_start(ap, zFormat); + z = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + return z; +} + +/* +** sqlite3_snprintf() works like snprintf() except that it ignores the +** current locale settings. This is important for SQLite because we +** are not able to use a "," as the decimal point in place of "." as +** specified by some locales. +** +** Oops: The first two arguments of sqlite3_snprintf() are backwards +** from the snprintf() standard. Unfortunately, it is too late to change +** this without breaking compatibility, so we just have to live with the +** mistake. +** +** sqlite3_vsnprintf() is the varargs version. +*/ +SQLITE_API char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){ + StrAccum acc; + if( n<=0 ) return zBuf; +#ifdef SQLITE_ENABLE_API_ARMOR + if( zBuf==0 || zFormat==0 ) { + (void)SQLITE_MISUSE_BKPT; + if( zBuf ) zBuf[0] = 0; + return zBuf; + } +#endif + sqlite3StrAccumInit(&acc, 0, zBuf, n, 0); + sqlite3_str_vappendf(&acc, zFormat, ap); + zBuf[acc.nChar] = 0; + return zBuf; +} +SQLITE_API char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ + StrAccum acc; + va_list ap; + if( n<=0 ) return zBuf; +#ifdef SQLITE_ENABLE_API_ARMOR + if( zBuf==0 || zFormat==0 ) { + (void)SQLITE_MISUSE_BKPT; + if( zBuf ) zBuf[0] = 0; + return zBuf; + } +#endif + sqlite3StrAccumInit(&acc, 0, zBuf, n, 0); + va_start(ap,zFormat); + sqlite3_str_vappendf(&acc, zFormat, ap); + va_end(ap); + zBuf[acc.nChar] = 0; + return zBuf; +} + +/* +** This is the routine that actually formats the sqlite3_log() message. +** We house it in a separate routine from sqlite3_log() to avoid using +** stack space on small-stack systems when logging is disabled. +** +** sqlite3_log() must render into a static buffer. It cannot dynamically +** allocate memory because it might be called while the memory allocator +** mutex is held. +** +** sqlite3_str_vappendf() might ask for *temporary* memory allocations for +** certain format characters (%q) or for very large precisions or widths. +** Care must be taken that any sqlite3_log() calls that occur while the +** memory mutex is held do not use these mechanisms. +*/ +static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){ + StrAccum acc; /* String accumulator */ + char zMsg[SQLITE_PRINT_BUF_SIZE*3]; /* Complete log message */ + + sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0); + sqlite3_str_vappendf(&acc, zFormat, ap); + sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode, + sqlite3StrAccumFinish(&acc)); +} + +/* +** Format and write a message to the log if logging is enabled. +*/ +SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...){ + va_list ap; /* Vararg list */ + if( sqlite3GlobalConfig.xLog ){ + va_start(ap, zFormat); + renderLogMsg(iErrCode, zFormat, ap); + va_end(ap); + } +} + +#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) +/* +** A version of printf() that understands %lld. Used for debugging. +** The printf() built into some versions of windows does not understand %lld +** and segfaults if you give it a long long int. +*/ +SQLITE_PRIVATE void sqlite3DebugPrintf(const char *zFormat, ...){ + va_list ap; + StrAccum acc; + char zBuf[SQLITE_PRINT_BUF_SIZE*10]; + sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); + va_start(ap,zFormat); + sqlite3_str_vappendf(&acc, zFormat, ap); + va_end(ap); + sqlite3StrAccumFinish(&acc); +#ifdef SQLITE_OS_TRACE_PROC + { + extern void SQLITE_OS_TRACE_PROC(const char *zBuf, int nBuf); + SQLITE_OS_TRACE_PROC(zBuf, sizeof(zBuf)); + } +#else + fprintf(stdout,"%s", zBuf); + fflush(stdout); +#endif +} +#endif + + +/* +** variable-argument wrapper around sqlite3_str_vappendf(). The bFlags argument +** can contain the bit SQLITE_PRINTF_INTERNAL enable internal formats. +*/ +SQLITE_API void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){ + va_list ap; + va_start(ap,zFormat); + sqlite3_str_vappendf(p, zFormat, ap); + va_end(ap); +} + + +/***************************************************************************** +** Reference counted string storage +*****************************************************************************/ + +/* +** Increase the reference count of the string by one. +** +** The input parameter is returned. +*/ +SQLITE_PRIVATE char *sqlite3RCStrRef(char *z){ + RCStr *p = (RCStr*)z; + assert( p!=0 ); + p--; + p->nRCRef++; + return z; +} + +/* +** Decrease the reference count by one. Free the string when the +** reference count reaches zero. +*/ +SQLITE_PRIVATE void sqlite3RCStrUnref(char *z){ + RCStr *p = (RCStr*)z; + assert( p!=0 ); + p--; + assert( p->nRCRef>0 ); + if( p->nRCRef>=2 ){ + p->nRCRef--; + }else{ + sqlite3_free(p); + } +} + +/* +** Create a new string that is capable of holding N bytes of text, not counting +** the zero byte at the end. The string is uninitialized. +** +** The reference count is initially 1. Call sqlite3RCStrUnref() to free the +** newly allocated string. +** +** This routine returns 0 on an OOM. +*/ +SQLITE_PRIVATE char *sqlite3RCStrNew(u64 N){ + RCStr *p = sqlite3_malloc64( N + sizeof(*p) + 1 ); + if( p==0 ) return 0; + p->nRCRef = 1; + return (char*)&p[1]; +} + +/* +** Change the size of the string so that it is able to hold N bytes. +** The string might be reallocated, so return the new allocation. +*/ +SQLITE_PRIVATE char *sqlite3RCStrResize(char *z, u64 N){ + RCStr *p = (RCStr*)z; + RCStr *pNew; + assert( p!=0 ); + p--; + assert( p->nRCRef==1 ); + pNew = sqlite3_realloc64(p, N+sizeof(RCStr)+1); + if( pNew==0 ){ + sqlite3_free(p); + return 0; + }else{ + return (char*)&pNew[1]; + } +} + +/************** End of printf.c **********************************************/ +/************** Begin file treeview.c ****************************************/ +/* +** 2015-06-08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains C code to implement the TreeView debugging routines. +** These routines print a parse tree to standard output for debugging and +** analysis. +** +** The interfaces in this file is only available when compiling +** with SQLITE_DEBUG. +*/ +/* #include "sqliteInt.h" */ +#ifdef SQLITE_DEBUG + +/* +** Add a new subitem to the tree. The moreToFollow flag indicates that this +** is not the last item in the tree. +*/ +static void sqlite3TreeViewPush(TreeView **pp, u8 moreToFollow){ + TreeView *p = *pp; + if( p==0 ){ + *pp = p = sqlite3_malloc64( sizeof(*p) ); + if( p==0 ) return; + memset(p, 0, sizeof(*p)); + }else{ + p->iLevel++; + } + assert( moreToFollow==0 || moreToFollow==1 ); + if( p->iLevel<(int)sizeof(p->bLine) ) p->bLine[p->iLevel] = moreToFollow; +} + +/* +** Finished with one layer of the tree +*/ +static void sqlite3TreeViewPop(TreeView **pp){ + TreeView *p = *pp; + if( p==0 ) return; + p->iLevel--; + if( p->iLevel<0 ){ + sqlite3_free(p); + *pp = 0; + } +} + +/* +** Generate a single line of output for the tree, with a prefix that contains +** all the appropriate tree lines +*/ +SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ + va_list ap; + int i; + StrAccum acc; + char zBuf[1000]; + sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); + if( p ){ + for(i=0; i<p->iLevel && i<(int)sizeof(p->bLine)-1; i++){ + sqlite3_str_append(&acc, p->bLine[i] ? "| " : " ", 4); + } + sqlite3_str_append(&acc, p->bLine[i] ? "|-- " : "'-- ", 4); + } + if( zFormat!=0 ){ + va_start(ap, zFormat); + sqlite3_str_vappendf(&acc, zFormat, ap); + va_end(ap); + assert( acc.nChar>0 || acc.accError ); + sqlite3_str_append(&acc, "\n", 1); + } + sqlite3StrAccumFinish(&acc); + fprintf(stdout,"%s", zBuf); + fflush(stdout); +} + +/* +** Shorthand for starting a new tree item that consists of a single label +*/ +static void sqlite3TreeViewItem(TreeView *p, const char *zLabel,u8 moreFollows){ + sqlite3TreeViewPush(&p, moreFollows); + sqlite3TreeViewLine(p, "%s", zLabel); +} + +/* +** Show a list of Column objects in tree format. +*/ +SQLITE_PRIVATE void sqlite3TreeViewColumnList( + TreeView *pView, + const Column *aCol, + int nCol, + u8 moreToFollow +){ + int i; + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewLine(pView, "COLUMNS"); + for(i=0; i<nCol; i++){ + u16 flg = aCol[i].colFlags; + int colMoreToFollow = i<(nCol - 1); + sqlite3TreeViewPush(&pView, colMoreToFollow); + sqlite3TreeViewLine(pView, 0); + printf(" %s", aCol[i].zCnName); + switch( aCol[i].eCType ){ + case COLTYPE_ANY: printf(" ANY"); break; + case COLTYPE_BLOB: printf(" BLOB"); break; + case COLTYPE_INT: printf(" INT"); break; + case COLTYPE_INTEGER: printf(" INTEGER"); break; + case COLTYPE_REAL: printf(" REAL"); break; + case COLTYPE_TEXT: printf(" TEXT"); break; + case COLTYPE_CUSTOM: { + if( flg & COLFLAG_HASTYPE ){ + const char *z = aCol[i].zCnName; + z += strlen(z)+1; + printf(" X-%s", z); + break; + } + } + } + if( flg & COLFLAG_PRIMKEY ) printf(" PRIMARY KEY"); + if( flg & COLFLAG_HIDDEN ) printf(" HIDDEN"); +#ifdef COLFLAG_NOEXPAND + if( flg & COLFLAG_NOEXPAND ) printf(" NO-EXPAND"); +#endif + if( flg ) printf(" flags=%04x", flg); + printf("\n"); + fflush(stdout); + sqlite3TreeViewPop(&pView); + } + sqlite3TreeViewPop(&pView); +} + +/* +** Generate a human-readable description of a WITH clause. +*/ +SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){ + int i; + if( pWith==0 ) return; + if( pWith->nCte==0 ) return; + if( pWith->pOuter ){ + sqlite3TreeViewLine(pView, "WITH (0x%p, pOuter=0x%p)",pWith,pWith->pOuter); + }else{ + sqlite3TreeViewLine(pView, "WITH (0x%p)", pWith); + } + if( pWith->nCte>0 ){ + sqlite3TreeViewPush(&pView, moreToFollow); + for(i=0; i<pWith->nCte; i++){ + StrAccum x; + char zLine[1000]; + const struct Cte *pCte = &pWith->a[i]; + sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0); + sqlite3_str_appendf(&x, "%s", pCte->zName); + if( pCte->pCols && pCte->pCols->nExpr>0 ){ + char cSep = '('; + int j; + for(j=0; j<pCte->pCols->nExpr; j++){ + sqlite3_str_appendf(&x, "%c%s", cSep, pCte->pCols->a[j].zEName); + cSep = ','; + } + sqlite3_str_appendf(&x, ")"); + } + if( pCte->eM10d!=M10d_Any ){ + sqlite3_str_appendf(&x, " %sMATERIALIZED", + pCte->eM10d==M10d_No ? "NOT " : ""); + } + if( pCte->pUse ){ + sqlite3_str_appendf(&x, " (pUse=0x%p, nUse=%d)", pCte->pUse, + pCte->pUse->nUse); + } + sqlite3StrAccumFinish(&x); + sqlite3TreeViewItem(pView, zLine, i<pWith->nCte-1); + sqlite3TreeViewSelect(pView, pCte->pSelect, 0); + sqlite3TreeViewPop(&pView); + } + sqlite3TreeViewPop(&pView); + } +} + +/* +** Generate a human-readable description of a SrcList object. +*/ +SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){ + int i; + if( pSrc==0 ) return; + for(i=0; i<pSrc->nSrc; i++){ + const SrcItem *pItem = &pSrc->a[i]; + StrAccum x; + int n = 0; + char zLine[1000]; + sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0); + x.printfFlags |= SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&x, "{%d:*} %!S", pItem->iCursor, pItem); + if( pItem->pTab ){ + sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx", + pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, pItem->colUsed); + } + if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==(JT_LEFT|JT_RIGHT) ){ + sqlite3_str_appendf(&x, " FULL-OUTER-JOIN"); + }else if( pItem->fg.jointype & JT_LEFT ){ + sqlite3_str_appendf(&x, " LEFT-JOIN"); + }else if( pItem->fg.jointype & JT_RIGHT ){ + sqlite3_str_appendf(&x, " RIGHT-JOIN"); + }else if( pItem->fg.jointype & JT_CROSS ){ + sqlite3_str_appendf(&x, " CROSS-JOIN"); + } + if( pItem->fg.jointype & JT_LTORJ ){ + sqlite3_str_appendf(&x, " LTORJ"); + } + if( pItem->fg.fromDDL ){ + sqlite3_str_appendf(&x, " DDL"); + } + if( pItem->fg.isCte ){ + sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse); + } + if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){ + sqlite3_str_appendf(&x, " ON"); + } + if( pItem->fg.isTabFunc ) sqlite3_str_appendf(&x, " isTabFunc"); + if( pItem->fg.isCorrelated ) sqlite3_str_appendf(&x, " isCorrelated"); + if( pItem->fg.isMaterialized ) sqlite3_str_appendf(&x, " isMaterialized"); + if( pItem->fg.viaCoroutine ) sqlite3_str_appendf(&x, " viaCoroutine"); + if( pItem->fg.notCte ) sqlite3_str_appendf(&x, " notCte"); + if( pItem->fg.isNestedFrom ) sqlite3_str_appendf(&x, " isNestedFrom"); + + sqlite3StrAccumFinish(&x); + sqlite3TreeViewItem(pView, zLine, i<pSrc->nSrc-1); + n = 0; + if( pItem->pSelect ) n++; + if( pItem->fg.isTabFunc ) n++; + if( pItem->fg.isUsing ) n++; + if( pItem->fg.isUsing ){ + sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); + } + if( pItem->pSelect ){ + if( pItem->pTab ){ + Table *pTab = pItem->pTab; + sqlite3TreeViewColumnList(pView, pTab->aCol, pTab->nCol, 1); + } + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); + sqlite3TreeViewSelect(pView, pItem->pSelect, (--n)>0); + } + if( pItem->fg.isTabFunc ){ + sqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:"); + } + sqlite3TreeViewPop(&pView); + } +} + +/* +** Generate a human-readable description of a Select object. +*/ +SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ + int n = 0; + int cnt = 0; + if( p==0 ){ + sqlite3TreeViewLine(pView, "nil-SELECT"); + return; + } + sqlite3TreeViewPush(&pView, moreToFollow); + if( p->pWith ){ + sqlite3TreeViewWith(pView, p->pWith, 1); + cnt = 1; + sqlite3TreeViewPush(&pView, 1); + } + do{ + if( p->selFlags & SF_WhereBegin ){ + sqlite3TreeViewLine(pView, "sqlite3WhereBegin()"); + }else{ + sqlite3TreeViewLine(pView, + "SELECT%s%s (%u/%p) selFlags=0x%x nSelectRow=%d", + ((p->selFlags & SF_Distinct) ? " DISTINCT" : ""), + ((p->selFlags & SF_Aggregate) ? " agg_flag" : ""), + p->selId, p, p->selFlags, + (int)p->nSelectRow + ); + } + if( cnt++ ) sqlite3TreeViewPop(&pView); + if( p->pPrior ){ + n = 1000; + }else{ + n = 0; + if( p->pSrc && p->pSrc->nSrc ) n++; + if( p->pWhere ) n++; + if( p->pGroupBy ) n++; + if( p->pHaving ) n++; + if( p->pOrderBy ) n++; + if( p->pLimit ) n++; +#ifndef SQLITE_OMIT_WINDOWFUNC + if( p->pWin ) n++; + if( p->pWinDefn ) n++; +#endif + } + if( p->pEList ){ + sqlite3TreeViewExprList(pView, p->pEList, n>0, "result-set"); + } + n--; +#ifndef SQLITE_OMIT_WINDOWFUNC + if( p->pWin ){ + Window *pX; + sqlite3TreeViewPush(&pView, (n--)>0); + sqlite3TreeViewLine(pView, "window-functions"); + for(pX=p->pWin; pX; pX=pX->pNextWin){ + sqlite3TreeViewWinFunc(pView, pX, pX->pNextWin!=0); + } + sqlite3TreeViewPop(&pView); + } +#endif + if( p->pSrc && p->pSrc->nSrc ){ + sqlite3TreeViewPush(&pView, (n--)>0); + sqlite3TreeViewLine(pView, "FROM"); + sqlite3TreeViewSrcList(pView, p->pSrc); + sqlite3TreeViewPop(&pView); + } + if( p->pWhere ){ + sqlite3TreeViewItem(pView, "WHERE", (n--)>0); + sqlite3TreeViewExpr(pView, p->pWhere, 0); + sqlite3TreeViewPop(&pView); + } + if( p->pGroupBy ){ + sqlite3TreeViewExprList(pView, p->pGroupBy, (n--)>0, "GROUPBY"); + } + if( p->pHaving ){ + sqlite3TreeViewItem(pView, "HAVING", (n--)>0); + sqlite3TreeViewExpr(pView, p->pHaving, 0); + sqlite3TreeViewPop(&pView); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( p->pWinDefn ){ + Window *pX; + sqlite3TreeViewItem(pView, "WINDOW", (n--)>0); + for(pX=p->pWinDefn; pX; pX=pX->pNextWin){ + sqlite3TreeViewWindow(pView, pX, pX->pNextWin!=0); + } + sqlite3TreeViewPop(&pView); + } +#endif + if( p->pOrderBy ){ + sqlite3TreeViewExprList(pView, p->pOrderBy, (n--)>0, "ORDERBY"); + } + if( p->pLimit ){ + sqlite3TreeViewItem(pView, "LIMIT", (n--)>0); + sqlite3TreeViewExpr(pView, p->pLimit->pLeft, p->pLimit->pRight!=0); + if( p->pLimit->pRight ){ + sqlite3TreeViewItem(pView, "OFFSET", (n--)>0); + sqlite3TreeViewExpr(pView, p->pLimit->pRight, 0); + sqlite3TreeViewPop(&pView); + } + sqlite3TreeViewPop(&pView); + } + if( p->pPrior ){ + const char *zOp = "UNION"; + switch( p->op ){ + case TK_ALL: zOp = "UNION ALL"; break; + case TK_INTERSECT: zOp = "INTERSECT"; break; + case TK_EXCEPT: zOp = "EXCEPT"; break; + } + sqlite3TreeViewItem(pView, zOp, 1); + } + p = p->pPrior; + }while( p!=0 ); + sqlite3TreeViewPop(&pView); +} + +#ifndef SQLITE_OMIT_WINDOWFUNC +/* +** Generate a description of starting or stopping bounds +*/ +SQLITE_PRIVATE void sqlite3TreeViewBound( + TreeView *pView, /* View context */ + u8 eBound, /* UNBOUNDED, CURRENT, PRECEDING, FOLLOWING */ + Expr *pExpr, /* Value for PRECEDING or FOLLOWING */ + u8 moreToFollow /* True if more to follow */ +){ + switch( eBound ){ + case TK_UNBOUNDED: { + sqlite3TreeViewItem(pView, "UNBOUNDED", moreToFollow); + sqlite3TreeViewPop(&pView); + break; + } + case TK_CURRENT: { + sqlite3TreeViewItem(pView, "CURRENT", moreToFollow); + sqlite3TreeViewPop(&pView); + break; + } + case TK_PRECEDING: { + sqlite3TreeViewItem(pView, "PRECEDING", moreToFollow); + sqlite3TreeViewExpr(pView, pExpr, 0); + sqlite3TreeViewPop(&pView); + break; + } + case TK_FOLLOWING: { + sqlite3TreeViewItem(pView, "FOLLOWING", moreToFollow); + sqlite3TreeViewExpr(pView, pExpr, 0); + sqlite3TreeViewPop(&pView); + break; + } + } +} +#endif /* SQLITE_OMIT_WINDOWFUNC */ + +#ifndef SQLITE_OMIT_WINDOWFUNC +/* +** Generate a human-readable explanation for a Window object +*/ +SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){ + int nElement = 0; + if( pWin==0 ) return; + if( pWin->pFilter ){ + sqlite3TreeViewItem(pView, "FILTER", 1); + sqlite3TreeViewExpr(pView, pWin->pFilter, 0); + sqlite3TreeViewPop(&pView); + } + sqlite3TreeViewPush(&pView, more); + if( pWin->zName ){ + sqlite3TreeViewLine(pView, "OVER %s (%p)", pWin->zName, pWin); + }else{ + sqlite3TreeViewLine(pView, "OVER (%p)", pWin); + } + if( pWin->zBase ) nElement++; + if( pWin->pOrderBy ) nElement++; + if( pWin->eFrmType ) nElement++; + if( pWin->eExclude ) nElement++; + if( pWin->zBase ){ + sqlite3TreeViewPush(&pView, (--nElement)>0); + sqlite3TreeViewLine(pView, "window: %s", pWin->zBase); + sqlite3TreeViewPop(&pView); + } + if( pWin->pPartition ){ + sqlite3TreeViewExprList(pView, pWin->pPartition, nElement>0,"PARTITION-BY"); + } + if( pWin->pOrderBy ){ + sqlite3TreeViewExprList(pView, pWin->pOrderBy, (--nElement)>0, "ORDER-BY"); + } + if( pWin->eFrmType ){ + char zBuf[30]; + const char *zFrmType = "ROWS"; + if( pWin->eFrmType==TK_RANGE ) zFrmType = "RANGE"; + if( pWin->eFrmType==TK_GROUPS ) zFrmType = "GROUPS"; + sqlite3_snprintf(sizeof(zBuf),zBuf,"%s%s",zFrmType, + pWin->bImplicitFrame ? " (implied)" : ""); + sqlite3TreeViewItem(pView, zBuf, (--nElement)>0); + sqlite3TreeViewBound(pView, pWin->eStart, pWin->pStart, 1); + sqlite3TreeViewBound(pView, pWin->eEnd, pWin->pEnd, 0); + sqlite3TreeViewPop(&pView); + } + if( pWin->eExclude ){ + char zBuf[30]; + const char *zExclude; + switch( pWin->eExclude ){ + case TK_NO: zExclude = "NO OTHERS"; break; + case TK_CURRENT: zExclude = "CURRENT ROW"; break; + case TK_GROUP: zExclude = "GROUP"; break; + case TK_TIES: zExclude = "TIES"; break; + default: + sqlite3_snprintf(sizeof(zBuf),zBuf,"invalid(%d)", pWin->eExclude); + zExclude = zBuf; + break; + } + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, "EXCLUDE %s", zExclude); + sqlite3TreeViewPop(&pView); + } + sqlite3TreeViewPop(&pView); +} +#endif /* SQLITE_OMIT_WINDOWFUNC */ + +#ifndef SQLITE_OMIT_WINDOWFUNC +/* +** Generate a human-readable explanation for a Window Function object +*/ +SQLITE_PRIVATE void sqlite3TreeViewWinFunc(TreeView *pView, const Window *pWin, u8 more){ + if( pWin==0 ) return; + sqlite3TreeViewPush(&pView, more); + sqlite3TreeViewLine(pView, "WINFUNC %s(%d)", + pWin->pWFunc->zName, pWin->pWFunc->nArg); + sqlite3TreeViewWindow(pView, pWin, 0); + sqlite3TreeViewPop(&pView); +} +#endif /* SQLITE_OMIT_WINDOWFUNC */ + +/* +** Generate a human-readable explanation of an expression tree. +*/ +SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ + const char *zBinOp = 0; /* Binary operator */ + const char *zUniOp = 0; /* Unary operator */ + char zFlgs[200]; + sqlite3TreeViewPush(&pView, moreToFollow); + if( pExpr==0 ){ + sqlite3TreeViewLine(pView, "nil"); + sqlite3TreeViewPop(&pView); + return; + } + if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags || pExpr->pAggInfo ){ + StrAccum x; + sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0); + sqlite3_str_appendf(&x, " fg.af=%x.%c", + pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n'); + if( ExprHasProperty(pExpr, EP_OuterON) ){ + sqlite3_str_appendf(&x, " outer.iJoin=%d", pExpr->w.iJoin); + } + if( ExprHasProperty(pExpr, EP_InnerON) ){ + sqlite3_str_appendf(&x, " inner.iJoin=%d", pExpr->w.iJoin); + } + if( ExprHasProperty(pExpr, EP_FromDDL) ){ + sqlite3_str_appendf(&x, " DDL"); + } + if( ExprHasVVAProperty(pExpr, EP_Immutable) ){ + sqlite3_str_appendf(&x, " IMMUTABLE"); + } + if( pExpr->pAggInfo!=0 ){ + sqlite3_str_appendf(&x, " agg-column[%d]", pExpr->iAgg); + } + sqlite3StrAccumFinish(&x); + }else{ + zFlgs[0] = 0; + } + switch( pExpr->op ){ + case TK_AGG_COLUMN: { + sqlite3TreeViewLine(pView, "AGG{%d:%d}%s", + pExpr->iTable, pExpr->iColumn, zFlgs); + break; + } + case TK_COLUMN: { + if( pExpr->iTable<0 ){ + /* This only happens when coding check constraints */ + char zOp2[16]; + if( pExpr->op2 ){ + sqlite3_snprintf(sizeof(zOp2),zOp2," op2=0x%02x",pExpr->op2); + }else{ + zOp2[0] = 0; + } + sqlite3TreeViewLine(pView, "COLUMN(%d)%s%s", + pExpr->iColumn, zFlgs, zOp2); + }else{ + assert( ExprUseYTab(pExpr) ); + sqlite3TreeViewLine(pView, "{%d:%d} pTab=%p%s", + pExpr->iTable, pExpr->iColumn, + pExpr->y.pTab, zFlgs); + } + if( ExprHasProperty(pExpr, EP_FixedCol) ){ + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + } + break; + } + case TK_INTEGER: { + if( pExpr->flags & EP_IntValue ){ + sqlite3TreeViewLine(pView, "%d", pExpr->u.iValue); + }else{ + sqlite3TreeViewLine(pView, "%s", pExpr->u.zToken); + } + break; + } +#ifndef SQLITE_OMIT_FLOATING_POINT + case TK_FLOAT: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); + break; + } +#endif + case TK_STRING: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView,"%Q", pExpr->u.zToken); + break; + } + case TK_NULL: { + sqlite3TreeViewLine(pView,"NULL"); + break; + } + case TK_TRUEFALSE: { + sqlite3TreeViewLine(pView,"%s%s", + sqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE", zFlgs); + break; + } +#ifndef SQLITE_OMIT_BLOB_LITERAL + case TK_BLOB: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); + break; + } +#endif + case TK_VARIABLE: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView,"VARIABLE(%s,%d)", + pExpr->u.zToken, pExpr->iColumn); + break; + } + case TK_REGISTER: { + sqlite3TreeViewLine(pView,"REGISTER(%d)", pExpr->iTable); + break; + } + case TK_ID: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView,"ID \"%w\"", pExpr->u.zToken); + break; + } +#ifndef SQLITE_OMIT_CAST + case TK_CAST: { + /* Expressions of the form: CAST(pLeft AS token) */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView,"CAST %Q", pExpr->u.zToken); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + break; + } +#endif /* SQLITE_OMIT_CAST */ + case TK_LT: zBinOp = "LT"; break; + case TK_LE: zBinOp = "LE"; break; + case TK_GT: zBinOp = "GT"; break; + case TK_GE: zBinOp = "GE"; break; + case TK_NE: zBinOp = "NE"; break; + case TK_EQ: zBinOp = "EQ"; break; + case TK_IS: zBinOp = "IS"; break; + case TK_ISNOT: zBinOp = "ISNOT"; break; + case TK_AND: zBinOp = "AND"; break; + case TK_OR: zBinOp = "OR"; break; + case TK_PLUS: zBinOp = "ADD"; break; + case TK_STAR: zBinOp = "MUL"; break; + case TK_MINUS: zBinOp = "SUB"; break; + case TK_REM: zBinOp = "REM"; break; + case TK_BITAND: zBinOp = "BITAND"; break; + case TK_BITOR: zBinOp = "BITOR"; break; + case TK_SLASH: zBinOp = "DIV"; break; + case TK_LSHIFT: zBinOp = "LSHIFT"; break; + case TK_RSHIFT: zBinOp = "RSHIFT"; break; + case TK_CONCAT: zBinOp = "CONCAT"; break; + case TK_DOT: zBinOp = "DOT"; break; + case TK_LIMIT: zBinOp = "LIMIT"; break; + + case TK_UMINUS: zUniOp = "UMINUS"; break; + case TK_UPLUS: zUniOp = "UPLUS"; break; + case TK_BITNOT: zUniOp = "BITNOT"; break; + case TK_NOT: zUniOp = "NOT"; break; + case TK_ISNULL: zUniOp = "ISNULL"; break; + case TK_NOTNULL: zUniOp = "NOTNULL"; break; + + case TK_TRUTH: { + int x; + const char *azOp[] = { + "IS-FALSE", "IS-TRUE", "IS-NOT-FALSE", "IS-NOT-TRUE" + }; + assert( pExpr->op2==TK_IS || pExpr->op2==TK_ISNOT ); + assert( pExpr->pRight ); + assert( sqlite3ExprSkipCollateAndLikely(pExpr->pRight)->op + == TK_TRUEFALSE ); + x = (pExpr->op2==TK_ISNOT)*2 + sqlite3ExprTruthValue(pExpr->pRight); + zUniOp = azOp[x]; + break; + } + + case TK_SPAN: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView, "SPAN %Q", pExpr->u.zToken); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + break; + } + + case TK_COLLATE: { + /* COLLATE operators without the EP_Collate flag are intended to + ** emulate collation associated with a table column. These show + ** up in the treeview output as "SOFT-COLLATE". Explicit COLLATE + ** operators that appear in the original SQL always have the + ** EP_Collate bit set and appear in treeview output as just "COLLATE" */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView, "%sCOLLATE %Q%s", + !ExprHasProperty(pExpr, EP_Collate) ? "SOFT-" : "", + pExpr->u.zToken, zFlgs); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + break; + } + + case TK_AGG_FUNCTION: + case TK_FUNCTION: { + ExprList *pFarg; /* List of function arguments */ + Window *pWin; + if( ExprHasProperty(pExpr, EP_TokenOnly) ){ + pFarg = 0; + pWin = 0; + }else{ + assert( ExprUseXList(pExpr) ); + pFarg = pExpr->x.pList; +#ifndef SQLITE_OMIT_WINDOWFUNC + pWin = ExprHasProperty(pExpr, EP_WinFunc) ? pExpr->y.pWin : 0; +#else + pWin = 0; +#endif + } + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + if( pExpr->op==TK_AGG_FUNCTION ){ + sqlite3TreeViewLine(pView, "AGG_FUNCTION%d %Q%s agg=%d[%d]/%p", + pExpr->op2, pExpr->u.zToken, zFlgs, + pExpr->pAggInfo ? pExpr->pAggInfo->selId : 0, + pExpr->iAgg, pExpr->pAggInfo); + }else if( pExpr->op2!=0 ){ + const char *zOp2; + char zBuf[8]; + sqlite3_snprintf(sizeof(zBuf),zBuf,"0x%02x",pExpr->op2); + zOp2 = zBuf; + if( pExpr->op2==NC_IsCheck ) zOp2 = "NC_IsCheck"; + if( pExpr->op2==NC_IdxExpr ) zOp2 = "NC_IdxExpr"; + if( pExpr->op2==NC_PartIdx ) zOp2 = "NC_PartIdx"; + if( pExpr->op2==NC_GenCol ) zOp2 = "NC_GenCol"; + sqlite3TreeViewLine(pView, "FUNCTION %Q%s op2=%s", + pExpr->u.zToken, zFlgs, zOp2); + }else{ + sqlite3TreeViewLine(pView, "FUNCTION %Q%s", pExpr->u.zToken, zFlgs); + } + if( pFarg ){ + sqlite3TreeViewExprList(pView, pFarg, pWin!=0, 0); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pWin ){ + sqlite3TreeViewWindow(pView, pWin, 0); + } +#endif + break; + } +#ifndef SQLITE_OMIT_SUBQUERY + case TK_EXISTS: { + assert( ExprUseXSelect(pExpr) ); + sqlite3TreeViewLine(pView, "EXISTS-expr flags=0x%x", pExpr->flags); + sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); + break; + } + case TK_SELECT: { + assert( ExprUseXSelect(pExpr) ); + sqlite3TreeViewLine(pView, "subquery-expr flags=0x%x", pExpr->flags); + sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); + break; + } + case TK_IN: { + sqlite3_str *pStr = sqlite3_str_new(0); + char *z; + sqlite3_str_appendf(pStr, "IN flags=0x%x", pExpr->flags); + if( pExpr->iTable ) sqlite3_str_appendf(pStr, " iTable=%d",pExpr->iTable); + if( ExprHasProperty(pExpr, EP_Subrtn) ){ + sqlite3_str_appendf(pStr, " subrtn(%d,%d)", + pExpr->y.sub.regReturn, pExpr->y.sub.iAddr); + } + z = sqlite3_str_finish(pStr); + sqlite3TreeViewLine(pView, z); + sqlite3_free(z); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); + if( ExprUseXSelect(pExpr) ){ + sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); + }else{ + sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); + } + break; + } +#endif /* SQLITE_OMIT_SUBQUERY */ + + /* + ** x BETWEEN y AND z + ** + ** This is equivalent to + ** + ** x>=y AND x<=z + ** + ** X is stored in pExpr->pLeft. + ** Y is stored in pExpr->pList->a[0].pExpr. + ** Z is stored in pExpr->pList->a[1].pExpr. + */ + case TK_BETWEEN: { + const Expr *pX, *pY, *pZ; + pX = pExpr->pLeft; + assert( ExprUseXList(pExpr) ); + assert( pExpr->x.pList->nExpr==2 ); + pY = pExpr->x.pList->a[0].pExpr; + pZ = pExpr->x.pList->a[1].pExpr; + sqlite3TreeViewLine(pView, "BETWEEN"); + sqlite3TreeViewExpr(pView, pX, 1); + sqlite3TreeViewExpr(pView, pY, 1); + sqlite3TreeViewExpr(pView, pZ, 0); + break; + } + case TK_TRIGGER: { + /* If the opcode is TK_TRIGGER, then the expression is a reference + ** to a column in the new.* or old.* pseudo-tables available to + ** trigger programs. In this case Expr.iTable is set to 1 for the + ** new.* pseudo-table, or 0 for the old.* pseudo-table. Expr.iColumn + ** is set to the column of the pseudo-table to read, or to -1 to + ** read the rowid field. + */ + sqlite3TreeViewLine(pView, "%s(%d)", + pExpr->iTable ? "NEW" : "OLD", pExpr->iColumn); + break; + } + case TK_CASE: { + sqlite3TreeViewLine(pView, "CASE"); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); + assert( ExprUseXList(pExpr) ); + sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); + break; + } +#ifndef SQLITE_OMIT_TRIGGER + case TK_RAISE: { + const char *zType = "unk"; + switch( pExpr->affExpr ){ + case OE_Rollback: zType = "rollback"; break; + case OE_Abort: zType = "abort"; break; + case OE_Fail: zType = "fail"; break; + case OE_Ignore: zType = "ignore"; break; + } + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView, "RAISE %s(%Q)", zType, pExpr->u.zToken); + break; + } +#endif + case TK_MATCH: { + sqlite3TreeViewLine(pView, "MATCH {%d:%d}%s", + pExpr->iTable, pExpr->iColumn, zFlgs); + sqlite3TreeViewExpr(pView, pExpr->pRight, 0); + break; + } + case TK_VECTOR: { + char *z = sqlite3_mprintf("VECTOR%s",zFlgs); + assert( ExprUseXList(pExpr) ); + sqlite3TreeViewBareExprList(pView, pExpr->x.pList, z); + sqlite3_free(z); + break; + } + case TK_SELECT_COLUMN: { + sqlite3TreeViewLine(pView, "SELECT-COLUMN %d of [0..%d]%s", + pExpr->iColumn, pExpr->iTable-1, + pExpr->pRight==pExpr->pLeft ? " (SELECT-owner)" : ""); + assert( ExprUseXSelect(pExpr->pLeft) ); + sqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0); + break; + } + case TK_IF_NULL_ROW: { + sqlite3TreeViewLine(pView, "IF-NULL-ROW %d", pExpr->iTable); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + break; + } + case TK_ERROR: { + Expr tmp; + sqlite3TreeViewLine(pView, "ERROR"); + tmp = *pExpr; + tmp.op = pExpr->op2; + sqlite3TreeViewExpr(pView, &tmp, 0); + break; + } + case TK_ROW: { + if( pExpr->iColumn<=0 ){ + sqlite3TreeViewLine(pView, "First FROM table rowid"); + }else{ + sqlite3TreeViewLine(pView, "First FROM table column %d", + pExpr->iColumn-1); + } + break; + } + default: { + sqlite3TreeViewLine(pView, "op=%d", pExpr->op); + break; + } + } + if( zBinOp ){ + sqlite3TreeViewLine(pView, "%s%s", zBinOp, zFlgs); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); + sqlite3TreeViewExpr(pView, pExpr->pRight, 0); + }else if( zUniOp ){ + sqlite3TreeViewLine(pView, "%s%s", zUniOp, zFlgs); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + } + sqlite3TreeViewPop(&pView); +} + + +/* +** Generate a human-readable explanation of an expression list. +*/ +SQLITE_PRIVATE void sqlite3TreeViewBareExprList( + TreeView *pView, + const ExprList *pList, + const char *zLabel +){ + if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST"; + if( pList==0 ){ + sqlite3TreeViewLine(pView, "%s (empty)", zLabel); + }else{ + int i; + sqlite3TreeViewLine(pView, "%s", zLabel); + for(i=0; i<pList->nExpr; i++){ + int j = pList->a[i].u.x.iOrderByCol; + char *zName = pList->a[i].zEName; + int moreToFollow = i<pList->nExpr - 1; + if( j || zName ){ + sqlite3TreeViewPush(&pView, moreToFollow); + moreToFollow = 0; + sqlite3TreeViewLine(pView, 0); + if( zName ){ + switch( pList->a[i].fg.eEName ){ + default: + fprintf(stdout, "AS %s ", zName); + break; + case ENAME_TAB: + fprintf(stdout, "TABLE-ALIAS-NAME(\"%s\") ", zName); + if( pList->a[i].fg.bUsed ) fprintf(stdout, "(used) "); + if( pList->a[i].fg.bUsingTerm ) fprintf(stdout, "(USING-term) "); + if( pList->a[i].fg.bNoExpand ) fprintf(stdout, "(NoExpand) "); + break; + case ENAME_SPAN: + fprintf(stdout, "SPAN(\"%s\") ", zName); + break; + } + } + if( j ){ + fprintf(stdout, "iOrderByCol=%d", j); + } + fprintf(stdout, "\n"); + fflush(stdout); + } + sqlite3TreeViewExpr(pView, pList->a[i].pExpr, moreToFollow); + if( j || zName ){ + sqlite3TreeViewPop(&pView); + } + } + } +} +SQLITE_PRIVATE void sqlite3TreeViewExprList( + TreeView *pView, + const ExprList *pList, + u8 moreToFollow, + const char *zLabel +){ + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewBareExprList(pView, pList, zLabel); + sqlite3TreeViewPop(&pView); +} + +/* +** Generate a human-readable explanation of an id-list. +*/ +SQLITE_PRIVATE void sqlite3TreeViewBareIdList( + TreeView *pView, + const IdList *pList, + const char *zLabel +){ + if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST"; + if( pList==0 ){ + sqlite3TreeViewLine(pView, "%s (empty)", zLabel); + }else{ + int i; + sqlite3TreeViewLine(pView, "%s", zLabel); + for(i=0; i<pList->nId; i++){ + char *zName = pList->a[i].zName; + int moreToFollow = i<pList->nId - 1; + if( zName==0 ) zName = "(null)"; + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewLine(pView, 0); + if( pList->eU4==EU4_NONE ){ + fprintf(stdout, "%s\n", zName); + }else if( pList->eU4==EU4_IDX ){ + fprintf(stdout, "%s (%d)\n", zName, pList->a[i].u4.idx); + }else{ + assert( pList->eU4==EU4_EXPR ); + if( pList->a[i].u4.pExpr==0 ){ + fprintf(stdout, "%s (pExpr=NULL)\n", zName); + }else{ + fprintf(stdout, "%s\n", zName); + sqlite3TreeViewPush(&pView, i<pList->nId-1); + sqlite3TreeViewExpr(pView, pList->a[i].u4.pExpr, 0); + sqlite3TreeViewPop(&pView); + } + } + sqlite3TreeViewPop(&pView); + } + } +} +SQLITE_PRIVATE void sqlite3TreeViewIdList( + TreeView *pView, + const IdList *pList, + u8 moreToFollow, + const char *zLabel +){ + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewBareIdList(pView, pList, zLabel); + sqlite3TreeViewPop(&pView); +} + +/* +** Generate a human-readable explanation of a list of Upsert objects +*/ +SQLITE_PRIVATE void sqlite3TreeViewUpsert( + TreeView *pView, + const Upsert *pUpsert, + u8 moreToFollow +){ + if( pUpsert==0 ) return; + sqlite3TreeViewPush(&pView, moreToFollow); + while( pUpsert ){ + int n; + sqlite3TreeViewPush(&pView, pUpsert->pNextUpsert!=0 || moreToFollow); + sqlite3TreeViewLine(pView, "ON CONFLICT DO %s", + pUpsert->isDoUpdate ? "UPDATE" : "NOTHING"); + n = (pUpsert->pUpsertSet!=0) + (pUpsert->pUpsertWhere!=0); + sqlite3TreeViewExprList(pView, pUpsert->pUpsertTarget, (n--)>0, "TARGET"); + sqlite3TreeViewExprList(pView, pUpsert->pUpsertSet, (n--)>0, "SET"); + if( pUpsert->pUpsertWhere ){ + sqlite3TreeViewItem(pView, "WHERE", (n--)>0); + sqlite3TreeViewExpr(pView, pUpsert->pUpsertWhere, 0); + sqlite3TreeViewPop(&pView); + } + sqlite3TreeViewPop(&pView); + pUpsert = pUpsert->pNextUpsert; + } + sqlite3TreeViewPop(&pView); +} + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an DELETE statement. +*/ +SQLITE_PRIVATE void sqlite3TreeViewDelete( + const With *pWith, + const SrcList *pTabList, + const Expr *pWhere, + const ExprList *pOrderBy, + const Expr *pLimit, + const Trigger *pTrigger +){ + int n = 0; + TreeView *pView = 0; + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, "DELETE"); + if( pWith ) n++; + if( pTabList ) n++; + if( pWhere ) n++; + if( pOrderBy ) n++; + if( pLimit ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "FROM"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pWhere ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "WHERE"); + sqlite3TreeViewExpr(pView, pWhere, 0); + sqlite3TreeViewPop(&pView); + } + if( pOrderBy ){ + sqlite3TreeViewExprList(pView, pOrderBy, (--n)>0, "ORDER-BY"); + } + if( pLimit ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "LIMIT"); + sqlite3TreeViewExpr(pView, pLimit, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an INSERT statement. +*/ +SQLITE_PRIVATE void sqlite3TreeViewInsert( + const With *pWith, + const SrcList *pTabList, + const IdList *pColumnList, + const Select *pSelect, + const ExprList *pExprList, + int onError, + const Upsert *pUpsert, + const Trigger *pTrigger +){ + TreeView *pView = 0; + int n = 0; + const char *zLabel = "INSERT"; + switch( onError ){ + case OE_Replace: zLabel = "REPLACE"; break; + case OE_Ignore: zLabel = "INSERT OR IGNORE"; break; + case OE_Rollback: zLabel = "INSERT OR ROLLBACK"; break; + case OE_Abort: zLabel = "INSERT OR ABORT"; break; + case OE_Fail: zLabel = "INSERT OR FAIL"; break; + } + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, zLabel); + if( pWith ) n++; + if( pTabList ) n++; + if( pColumnList ) n++; + if( pSelect ) n++; + if( pExprList ) n++; + if( pUpsert ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "INTO"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pColumnList ){ + sqlite3TreeViewIdList(pView, pColumnList, (--n)>0, "COLUMNS"); + } + if( pSelect ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "DATA-SOURCE"); + sqlite3TreeViewSelect(pView, pSelect, 0); + sqlite3TreeViewPop(&pView); + } + if( pExprList ){ + sqlite3TreeViewExprList(pView, pExprList, (--n)>0, "VALUES"); + } + if( pUpsert ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "UPSERT"); + sqlite3TreeViewUpsert(pView, pUpsert, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an UPDATE statement. +*/ +SQLITE_PRIVATE void sqlite3TreeViewUpdate( + const With *pWith, + const SrcList *pTabList, + const ExprList *pChanges, + const Expr *pWhere, + int onError, + const ExprList *pOrderBy, + const Expr *pLimit, + const Upsert *pUpsert, + const Trigger *pTrigger +){ + int n = 0; + TreeView *pView = 0; + const char *zLabel = "UPDATE"; + switch( onError ){ + case OE_Replace: zLabel = "UPDATE OR REPLACE"; break; + case OE_Ignore: zLabel = "UPDATE OR IGNORE"; break; + case OE_Rollback: zLabel = "UPDATE OR ROLLBACK"; break; + case OE_Abort: zLabel = "UPDATE OR ABORT"; break; + case OE_Fail: zLabel = "UPDATE OR FAIL"; break; + } + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, zLabel); + if( pWith ) n++; + if( pTabList ) n++; + if( pChanges ) n++; + if( pWhere ) n++; + if( pOrderBy ) n++; + if( pLimit ) n++; + if( pUpsert ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "FROM"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pChanges ){ + sqlite3TreeViewExprList(pView, pChanges, (--n)>0, "SET"); + } + if( pWhere ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "WHERE"); + sqlite3TreeViewExpr(pView, pWhere, 0); + sqlite3TreeViewPop(&pView); + } + if( pOrderBy ){ + sqlite3TreeViewExprList(pView, pOrderBy, (--n)>0, "ORDER-BY"); + } + if( pLimit ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "LIMIT"); + sqlite3TreeViewExpr(pView, pLimit, 0); + sqlite3TreeViewPop(&pView); + } + if( pUpsert ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "UPSERT"); + sqlite3TreeViewUpsert(pView, pUpsert, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#ifndef SQLITE_OMIT_TRIGGER +/* +** Show a human-readable graph of a TriggerStep +*/ +SQLITE_PRIVATE void sqlite3TreeViewTriggerStep( + TreeView *pView, + const TriggerStep *pStep, + u8 moreToFollow, + u8 showFullList +){ + int cnt = 0; + if( pStep==0 ) return; + sqlite3TreeViewPush(&pView, + moreToFollow || (showFullList && pStep->pNext!=0)); + do{ + if( cnt++ && pStep->pNext==0 ){ + sqlite3TreeViewPop(&pView); + sqlite3TreeViewPush(&pView, 0); + } + sqlite3TreeViewLine(pView, "%s", pStep->zSpan ? pStep->zSpan : "RETURNING"); + }while( showFullList && (pStep = pStep->pNext)!=0 ); + sqlite3TreeViewPop(&pView); +} + +/* +** Show a human-readable graph of a Trigger +*/ +SQLITE_PRIVATE void sqlite3TreeViewTrigger( + TreeView *pView, + const Trigger *pTrigger, + u8 moreToFollow, + u8 showFullList +){ + int cnt = 0; + if( pTrigger==0 ) return; + sqlite3TreeViewPush(&pView, + moreToFollow || (showFullList && pTrigger->pNext!=0)); + do{ + if( cnt++ && pTrigger->pNext==0 ){ + sqlite3TreeViewPop(&pView); + sqlite3TreeViewPush(&pView, 0); + } + sqlite3TreeViewLine(pView, "TRIGGER %s", pTrigger->zName); + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewTriggerStep(pView, pTrigger->step_list, 0, 1); + sqlite3TreeViewPop(&pView); + }while( showFullList && (pTrigger = pTrigger->pNext)!=0 ); + sqlite3TreeViewPop(&pView); +} +#endif /* SQLITE_OMIT_TRIGGER */ + + +/* +** These simplified versions of the tree-view routines omit unnecessary +** parameters. These variants are intended to be used from a symbolic +** debugger, such as "gdb", during interactive debugging sessions. +** +** This routines are given external linkage so that they will always be +** accessible to the debugging, and to avoid warnings about unused +** functions. But these routines only exist in debugging builds, so they +** do not contaminate the interface. +*/ +SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);} +SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); } +SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); } +SQLITE_PRIVATE void sqlite3ShowSelect(const Select *p){ sqlite3TreeViewSelect(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowWith(const With *p){ sqlite3TreeViewWith(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowUpsert(const Upsert *p){ sqlite3TreeViewUpsert(0,p,0); } +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE void sqlite3ShowTriggerStep(const TriggerStep *p){ + sqlite3TreeViewTriggerStep(0,p,0,0); +} +SQLITE_PRIVATE void sqlite3ShowTriggerStepList(const TriggerStep *p){ + sqlite3TreeViewTriggerStep(0,p,0,1); +} +SQLITE_PRIVATE void sqlite3ShowTrigger(const Trigger *p){ sqlite3TreeViewTrigger(0,p,0,0); } +SQLITE_PRIVATE void sqlite3ShowTriggerList(const Trigger *p){ sqlite3TreeViewTrigger(0,p,0,1);} +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC +SQLITE_PRIVATE void sqlite3ShowWindow(const Window *p){ sqlite3TreeViewWindow(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowWinFunc(const Window *p){ sqlite3TreeViewWinFunc(0,p,0); } +#endif + +#endif /* SQLITE_DEBUG */ + +/************** End of treeview.c ********************************************/ +/************** Begin file random.c ******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement a pseudo-random number +** generator (PRNG) for SQLite. +** +** Random numbers are used by some of the database backends in order +** to generate random integer keys for tables or random filenames. +*/ +/* #include "sqliteInt.h" */ + + +/* All threads share a single random number generator. +** This structure is the current state of the generator. +*/ +static SQLITE_WSD struct sqlite3PrngType { + u32 s[16]; /* 64 bytes of chacha20 state */ + u8 out[64]; /* Output bytes */ + u8 n; /* Output bytes remaining */ +} sqlite3Prng; + + +/* The RFC-7539 ChaCha20 block function +*/ +#define ROTL(a,b) (((a) << (b)) | ((a) >> (32 - (b)))) +#define QR(a, b, c, d) ( \ + a += b, d ^= a, d = ROTL(d,16), \ + c += d, b ^= c, b = ROTL(b,12), \ + a += b, d ^= a, d = ROTL(d, 8), \ + c += d, b ^= c, b = ROTL(b, 7)) +static void chacha_block(u32 *out, const u32 *in){ + int i; + u32 x[16]; + memcpy(x, in, 64); + for(i=0; i<10; i++){ + QR(x[0], x[4], x[ 8], x[12]); + QR(x[1], x[5], x[ 9], x[13]); + QR(x[2], x[6], x[10], x[14]); + QR(x[3], x[7], x[11], x[15]); + QR(x[0], x[5], x[10], x[15]); + QR(x[1], x[6], x[11], x[12]); + QR(x[2], x[7], x[ 8], x[13]); + QR(x[3], x[4], x[ 9], x[14]); + } + for(i=0; i<16; i++) out[i] = x[i]+in[i]; +} + +/* +** Return N random bytes. +*/ +SQLITE_API void sqlite3_randomness(int N, void *pBuf){ + unsigned char *zBuf = pBuf; + + /* The "wsdPrng" macro will resolve to the pseudo-random number generator + ** state vector. If writable static data is unsupported on the target, + ** we have to locate the state vector at run-time. In the more common + ** case where writable static data is supported, wsdPrng can refer directly + ** to the "sqlite3Prng" state vector declared above. + */ +#ifdef SQLITE_OMIT_WSD + struct sqlite3PrngType *p = &GLOBAL(struct sqlite3PrngType, sqlite3Prng); +# define wsdPrng p[0] +#else +# define wsdPrng sqlite3Prng +#endif + +#if SQLITE_THREADSAFE + sqlite3_mutex *mutex; +#endif + +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return; +#endif + +#if SQLITE_THREADSAFE + mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PRNG); +#endif + + sqlite3_mutex_enter(mutex); + if( N<=0 || pBuf==0 ){ + wsdPrng.s[0] = 0; + sqlite3_mutex_leave(mutex); + return; + } + + /* Initialize the state of the random number generator once, + ** the first time this routine is called. + */ + if( wsdPrng.s[0]==0 ){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + static const u32 chacha20_init[] = { + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 + }; + memcpy(&wsdPrng.s[0], chacha20_init, 16); + if( NEVER(pVfs==0) ){ + memset(&wsdPrng.s[4], 0, 44); + }else{ + sqlite3OsRandomness(pVfs, 44, (char*)&wsdPrng.s[4]); + } + wsdPrng.s[15] = wsdPrng.s[12]; + wsdPrng.s[12] = 0; + wsdPrng.n = 0; + } + + assert( N>0 ); + while( 1 /* exit by break */ ){ + if( N<=wsdPrng.n ){ + memcpy(zBuf, &wsdPrng.out[wsdPrng.n-N], N); + wsdPrng.n -= N; + break; + } + if( wsdPrng.n>0 ){ + memcpy(zBuf, wsdPrng.out, wsdPrng.n); + N -= wsdPrng.n; + zBuf += wsdPrng.n; + } + wsdPrng.s[12]++; + chacha_block((u32*)wsdPrng.out, wsdPrng.s); + wsdPrng.n = 64; + } + sqlite3_mutex_leave(mutex); +} + +#ifndef SQLITE_UNTESTABLE +/* +** For testing purposes, we sometimes want to preserve the state of +** PRNG and restore the PRNG to its saved state at a later time, or +** to reset the PRNG to its initial state. These routines accomplish +** those tasks. +** +** The sqlite3_test_control() interface calls these routines to +** control the PRNG. +*/ +static SQLITE_WSD struct sqlite3PrngType sqlite3SavedPrng; +SQLITE_PRIVATE void sqlite3PrngSaveState(void){ + memcpy( + &GLOBAL(struct sqlite3PrngType, sqlite3SavedPrng), + &GLOBAL(struct sqlite3PrngType, sqlite3Prng), + sizeof(sqlite3Prng) + ); +} +SQLITE_PRIVATE void sqlite3PrngRestoreState(void){ + memcpy( + &GLOBAL(struct sqlite3PrngType, sqlite3Prng), + &GLOBAL(struct sqlite3PrngType, sqlite3SavedPrng), + sizeof(sqlite3Prng) + ); +} +#endif /* SQLITE_UNTESTABLE */ + +/************** End of random.c **********************************************/ +/************** Begin file threads.c *****************************************/ +/* +** 2012 July 21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file presents a simple cross-platform threading interface for +** use internally by SQLite. +** +** A "thread" can be created using sqlite3ThreadCreate(). This thread +** runs independently of its creator until it is joined using +** sqlite3ThreadJoin(), at which point it terminates. +** +** Threads do not have to be real. It could be that the work of the +** "thread" is done by the main thread at either the sqlite3ThreadCreate() +** or sqlite3ThreadJoin() call. This is, in fact, what happens in +** single threaded systems. Nothing in SQLite requires multiple threads. +** This interface exists so that applications that want to take advantage +** of multiple cores can do so, while also allowing applications to stay +** single-threaded if desired. +*/ +/* #include "sqliteInt.h" */ +#if SQLITE_OS_WIN +/* # include "os_win.h" */ +#endif + +#if SQLITE_MAX_WORKER_THREADS>0 + +/********************************* Unix Pthreads ****************************/ +#if SQLITE_OS_UNIX && defined(SQLITE_MUTEX_PTHREADS) && SQLITE_THREADSAFE>0 + +#define SQLITE_THREADS_IMPLEMENTED 1 /* Prevent the single-thread code below */ +/* #include <pthread.h> */ + +/* A running thread */ +struct SQLiteThread { + pthread_t tid; /* Thread ID */ + int done; /* Set to true when thread finishes */ + void *pOut; /* Result returned by the thread */ + void *(*xTask)(void*); /* The thread routine */ + void *pIn; /* Argument to the thread */ +}; + +/* Create a new thread */ +SQLITE_PRIVATE int sqlite3ThreadCreate( + SQLiteThread **ppThread, /* OUT: Write the thread object here */ + void *(*xTask)(void*), /* Routine to run in a separate thread */ + void *pIn /* Argument passed into xTask() */ +){ + SQLiteThread *p; + int rc; + + assert( ppThread!=0 ); + assert( xTask!=0 ); + /* This routine is never used in single-threaded mode */ + assert( sqlite3GlobalConfig.bCoreMutex!=0 ); + + *ppThread = 0; + p = sqlite3Malloc(sizeof(*p)); + if( p==0 ) return SQLITE_NOMEM_BKPT; + memset(p, 0, sizeof(*p)); + p->xTask = xTask; + p->pIn = pIn; + /* If the SQLITE_TESTCTRL_FAULT_INSTALL callback is registered to a + ** function that returns SQLITE_ERROR when passed the argument 200, that + ** forces worker threads to run sequentially and deterministically + ** for testing purposes. */ + if( sqlite3FaultSim(200) ){ + rc = 1; + }else{ + rc = pthread_create(&p->tid, 0, xTask, pIn); + } + if( rc ){ + p->done = 1; + p->pOut = xTask(pIn); + } + *ppThread = p; + return SQLITE_OK; +} + +/* Get the results of the thread */ +SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ + int rc; + + assert( ppOut!=0 ); + if( NEVER(p==0) ) return SQLITE_NOMEM_BKPT; + if( p->done ){ + *ppOut = p->pOut; + rc = SQLITE_OK; + }else{ + rc = pthread_join(p->tid, ppOut) ? SQLITE_ERROR : SQLITE_OK; + } + sqlite3_free(p); + return rc; +} + +#endif /* SQLITE_OS_UNIX && defined(SQLITE_MUTEX_PTHREADS) */ +/******************************** End Unix Pthreads *************************/ + + +/********************************* Win32 Threads ****************************/ +#if SQLITE_OS_WIN_THREADS + +#define SQLITE_THREADS_IMPLEMENTED 1 /* Prevent the single-thread code below */ +#include <process.h> + +/* A running thread */ +struct SQLiteThread { + void *tid; /* The thread handle */ + unsigned id; /* The thread identifier */ + void *(*xTask)(void*); /* The routine to run as a thread */ + void *pIn; /* Argument to xTask */ + void *pResult; /* Result of xTask */ +}; + +/* Thread procedure Win32 compatibility shim */ +static unsigned __stdcall sqlite3ThreadProc( + void *pArg /* IN: Pointer to the SQLiteThread structure */ +){ + SQLiteThread *p = (SQLiteThread *)pArg; + + assert( p!=0 ); +#if 0 + /* + ** This assert appears to trigger spuriously on certain + ** versions of Windows, possibly due to _beginthreadex() + ** and/or CreateThread() not fully setting their thread + ** ID parameter before starting the thread. + */ + assert( p->id==GetCurrentThreadId() ); +#endif + assert( p->xTask!=0 ); + p->pResult = p->xTask(p->pIn); + + _endthreadex(0); + return 0; /* NOT REACHED */ +} + +/* Create a new thread */ +SQLITE_PRIVATE int sqlite3ThreadCreate( + SQLiteThread **ppThread, /* OUT: Write the thread object here */ + void *(*xTask)(void*), /* Routine to run in a separate thread */ + void *pIn /* Argument passed into xTask() */ +){ + SQLiteThread *p; + + assert( ppThread!=0 ); + assert( xTask!=0 ); + *ppThread = 0; + p = sqlite3Malloc(sizeof(*p)); + if( p==0 ) return SQLITE_NOMEM_BKPT; + /* If the SQLITE_TESTCTRL_FAULT_INSTALL callback is registered to a + ** function that returns SQLITE_ERROR when passed the argument 200, that + ** forces worker threads to run sequentially and deterministically + ** (via the sqlite3FaultSim() term of the conditional) for testing + ** purposes. */ + if( sqlite3GlobalConfig.bCoreMutex==0 || sqlite3FaultSim(200) ){ + memset(p, 0, sizeof(*p)); + }else{ + p->xTask = xTask; + p->pIn = pIn; + p->tid = (void*)_beginthreadex(0, 0, sqlite3ThreadProc, p, 0, &p->id); + if( p->tid==0 ){ + memset(p, 0, sizeof(*p)); + } + } + if( p->xTask==0 ){ + p->id = GetCurrentThreadId(); + p->pResult = xTask(pIn); + } + *ppThread = p; + return SQLITE_OK; +} + +SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject); /* os_win.c */ + +/* Get the results of the thread */ +SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ + DWORD rc; + BOOL bRc; + + assert( ppOut!=0 ); + if( NEVER(p==0) ) return SQLITE_NOMEM_BKPT; + if( p->xTask==0 ){ + /* assert( p->id==GetCurrentThreadId() ); */ + rc = WAIT_OBJECT_0; + assert( p->tid==0 ); + }else{ + assert( p->id!=0 && p->id!=GetCurrentThreadId() ); + rc = sqlite3Win32Wait((HANDLE)p->tid); + assert( rc!=WAIT_IO_COMPLETION ); + bRc = CloseHandle((HANDLE)p->tid); + assert( bRc ); + } + if( rc==WAIT_OBJECT_0 ) *ppOut = p->pResult; + sqlite3_free(p); + return (rc==WAIT_OBJECT_0) ? SQLITE_OK : SQLITE_ERROR; +} + +#endif /* SQLITE_OS_WIN_THREADS */ +/******************************** End Win32 Threads *************************/ + + +/********************************* Single-Threaded **************************/ +#ifndef SQLITE_THREADS_IMPLEMENTED +/* +** This implementation does not actually create a new thread. It does the +** work of the thread in the main thread, when either the thread is created +** or when it is joined +*/ + +/* A running thread */ +struct SQLiteThread { + void *(*xTask)(void*); /* The routine to run as a thread */ + void *pIn; /* Argument to xTask */ + void *pResult; /* Result of xTask */ +}; + +/* Create a new thread */ +SQLITE_PRIVATE int sqlite3ThreadCreate( + SQLiteThread **ppThread, /* OUT: Write the thread object here */ + void *(*xTask)(void*), /* Routine to run in a separate thread */ + void *pIn /* Argument passed into xTask() */ +){ + SQLiteThread *p; + + assert( ppThread!=0 ); + assert( xTask!=0 ); + *ppThread = 0; + p = sqlite3Malloc(sizeof(*p)); + if( p==0 ) return SQLITE_NOMEM_BKPT; + if( (SQLITE_PTR_TO_INT(p)/17)&1 ){ + p->xTask = xTask; + p->pIn = pIn; + }else{ + p->xTask = 0; + p->pResult = xTask(pIn); + } + *ppThread = p; + return SQLITE_OK; +} + +/* Get the results of the thread */ +SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ + + assert( ppOut!=0 ); + if( NEVER(p==0) ) return SQLITE_NOMEM_BKPT; + if( p->xTask ){ + *ppOut = p->xTask(p->pIn); + }else{ + *ppOut = p->pResult; + } + sqlite3_free(p); + +#if defined(SQLITE_TEST) + { + void *pTstAlloc = sqlite3Malloc(10); + if (!pTstAlloc) return SQLITE_NOMEM_BKPT; + sqlite3_free(pTstAlloc); + } +#endif + + return SQLITE_OK; +} + +#endif /* !defined(SQLITE_THREADS_IMPLEMENTED) */ +/****************************** End Single-Threaded *************************/ +#endif /* SQLITE_MAX_WORKER_THREADS>0 */ + +/************** End of threads.c *********************************************/ +/************** Begin file utf.c *********************************************/ +/* +** 2004 April 13 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains routines used to translate between UTF-8, +** UTF-16, UTF-16BE, and UTF-16LE. +** +** Notes on UTF-8: +** +** Byte-0 Byte-1 Byte-2 Byte-3 Value +** 0xxxxxxx 00000000 00000000 0xxxxxxx +** 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx +** 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx +** 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx +** +** +** Notes on UTF-16: (with wwww+1==uuuuu) +** +** Word-0 Word-1 Value +** 110110ww wwzzzzyy 110111yy yyxxxxxx 000uuuuu zzzzyyyy yyxxxxxx +** zzzzyyyy yyxxxxxx 00000000 zzzzyyyy yyxxxxxx +** +** +** BOM or Byte Order Mark: +** 0xff 0xfe little-endian utf-16 follows +** 0xfe 0xff big-endian utf-16 follows +** +*/ +/* #include "sqliteInt.h" */ +/* #include <assert.h> */ +/* #include "vdbeInt.h" */ + +#if !defined(SQLITE_AMALGAMATION) && SQLITE_BYTEORDER==0 +/* +** The following constant value is used by the SQLITE_BIGENDIAN and +** SQLITE_LITTLEENDIAN macros. +*/ +SQLITE_PRIVATE const int sqlite3one = 1; +#endif /* SQLITE_AMALGAMATION && SQLITE_BYTEORDER==0 */ + +/* +** This lookup table is used to help decode the first byte of +** a multi-byte UTF8 character. +*/ +static const unsigned char sqlite3Utf8Trans1[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, +}; + + +#define WRITE_UTF8(zOut, c) { \ + if( c<0x00080 ){ \ + *zOut++ = (u8)(c&0xFF); \ + } \ + else if( c<0x00800 ){ \ + *zOut++ = 0xC0 + (u8)((c>>6)&0x1F); \ + *zOut++ = 0x80 + (u8)(c & 0x3F); \ + } \ + else if( c<0x10000 ){ \ + *zOut++ = 0xE0 + (u8)((c>>12)&0x0F); \ + *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); \ + *zOut++ = 0x80 + (u8)(c & 0x3F); \ + }else{ \ + *zOut++ = 0xF0 + (u8)((c>>18) & 0x07); \ + *zOut++ = 0x80 + (u8)((c>>12) & 0x3F); \ + *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); \ + *zOut++ = 0x80 + (u8)(c & 0x3F); \ + } \ +} + +#define WRITE_UTF16LE(zOut, c) { \ + if( c<=0xFFFF ){ \ + *zOut++ = (u8)(c&0x00FF); \ + *zOut++ = (u8)((c>>8)&0x00FF); \ + }else{ \ + *zOut++ = (u8)(((c>>10)&0x003F) + (((c-0x10000)>>10)&0x00C0)); \ + *zOut++ = (u8)(0x00D8 + (((c-0x10000)>>18)&0x03)); \ + *zOut++ = (u8)(c&0x00FF); \ + *zOut++ = (u8)(0x00DC + ((c>>8)&0x03)); \ + } \ +} + +#define WRITE_UTF16BE(zOut, c) { \ + if( c<=0xFFFF ){ \ + *zOut++ = (u8)((c>>8)&0x00FF); \ + *zOut++ = (u8)(c&0x00FF); \ + }else{ \ + *zOut++ = (u8)(0x00D8 + (((c-0x10000)>>18)&0x03)); \ + *zOut++ = (u8)(((c>>10)&0x003F) + (((c-0x10000)>>10)&0x00C0)); \ + *zOut++ = (u8)(0x00DC + ((c>>8)&0x03)); \ + *zOut++ = (u8)(c&0x00FF); \ + } \ +} + +/* +** Translate a single UTF-8 character. Return the unicode value. +** +** During translation, assume that the byte that zTerm points +** is a 0x00. +** +** Write a pointer to the next unread byte back into *pzNext. +** +** Notes On Invalid UTF-8: +** +** * This routine never allows a 7-bit character (0x00 through 0x7f) to +** be encoded as a multi-byte character. Any multi-byte character that +** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd. +** +** * This routine never allows a UTF16 surrogate value to be encoded. +** If a multi-byte character attempts to encode a value between +** 0xd800 and 0xe000 then it is rendered as 0xfffd. +** +** * Bytes in the range of 0x80 through 0xbf which occur as the first +** byte of a character are interpreted as single-byte characters +** and rendered as themselves even though they are technically +** invalid characters. +** +** * This routine accepts over-length UTF8 encodings +** for unicode values 0x80 and greater. It does not change over-length +** encodings to 0xfffd as some systems recommend. +*/ +#define READ_UTF8(zIn, zTerm, c) \ + c = *(zIn++); \ + if( c>=0xc0 ){ \ + c = sqlite3Utf8Trans1[c-0xc0]; \ + while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ + c = (c<<6) + (0x3f & *(zIn++)); \ + } \ + if( c<0x80 \ + || (c&0xFFFFF800)==0xD800 \ + || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ + } +SQLITE_PRIVATE u32 sqlite3Utf8Read( + const unsigned char **pz /* Pointer to string from which to read char */ +){ + unsigned int c; + + /* Same as READ_UTF8() above but without the zTerm parameter. + ** For this routine, we assume the UTF8 string is always zero-terminated. + */ + c = *((*pz)++); + if( c>=0xc0 ){ + c = sqlite3Utf8Trans1[c-0xc0]; + while( (*(*pz) & 0xc0)==0x80 ){ + c = (c<<6) + (0x3f & *((*pz)++)); + } + if( c<0x80 + || (c&0xFFFFF800)==0xD800 + || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } + } + return c; +} + + + + +/* +** If the TRANSLATE_TRACE macro is defined, the value of each Mem is +** printed on stderr on the way into and out of sqlite3VdbeMemTranslate(). +*/ +/* #define TRANSLATE_TRACE 1 */ + +#ifndef SQLITE_OMIT_UTF16 +/* +** This routine transforms the internal text encoding used by pMem to +** desiredEnc. It is an error if the string is already of the desired +** encoding, or if *pMem does not contain a string value. +*/ +SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desiredEnc){ + sqlite3_int64 len; /* Maximum length of output string in bytes */ + unsigned char *zOut; /* Output buffer */ + unsigned char *zIn; /* Input iterator */ + unsigned char *zTerm; /* End of input */ + unsigned char *z; /* Output iterator */ + unsigned int c; + + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( pMem->flags&MEM_Str ); + assert( pMem->enc!=desiredEnc ); + assert( pMem->enc!=0 ); + assert( pMem->n>=0 ); + +#if defined(TRANSLATE_TRACE) && defined(SQLITE_DEBUG) + { + StrAccum acc; + char zBuf[1000]; + sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); + sqlite3VdbeMemPrettyPrint(pMem, &acc); + fprintf(stderr, "INPUT: %s\n", sqlite3StrAccumFinish(&acc)); + } +#endif + + /* If the translation is between UTF-16 little and big endian, then + ** all that is required is to swap the byte order. This case is handled + ** differently from the others. + */ + if( pMem->enc!=SQLITE_UTF8 && desiredEnc!=SQLITE_UTF8 ){ + u8 temp; + int rc; + rc = sqlite3VdbeMemMakeWriteable(pMem); + if( rc!=SQLITE_OK ){ + assert( rc==SQLITE_NOMEM ); + return SQLITE_NOMEM_BKPT; + } + zIn = (u8*)pMem->z; + zTerm = &zIn[pMem->n&~1]; + while( zIn<zTerm ){ + temp = *zIn; + *zIn = *(zIn+1); + zIn++; + *zIn++ = temp; + } + pMem->enc = desiredEnc; + goto translate_out; + } + + /* Set len to the maximum number of bytes required in the output buffer. */ + if( desiredEnc==SQLITE_UTF8 ){ + /* When converting from UTF-16, the maximum growth results from + ** translating a 2-byte character to a 4-byte UTF-8 character. + ** A single byte is required for the output string + ** nul-terminator. + */ + pMem->n &= ~1; + len = 2 * (sqlite3_int64)pMem->n + 1; + }else{ + /* When converting from UTF-8 to UTF-16 the maximum growth is caused + ** when a 1-byte UTF-8 character is translated into a 2-byte UTF-16 + ** character. Two bytes are required in the output buffer for the + ** nul-terminator. + */ + len = 2 * (sqlite3_int64)pMem->n + 2; + } + + /* Set zIn to point at the start of the input buffer and zTerm to point 1 + ** byte past the end. + ** + ** Variable zOut is set to point at the output buffer, space obtained + ** from sqlite3_malloc(). + */ + zIn = (u8*)pMem->z; + zTerm = &zIn[pMem->n]; + zOut = sqlite3DbMallocRaw(pMem->db, len); + if( !zOut ){ + return SQLITE_NOMEM_BKPT; + } + z = zOut; + + if( pMem->enc==SQLITE_UTF8 ){ + if( desiredEnc==SQLITE_UTF16LE ){ + /* UTF-8 -> UTF-16 Little-endian */ + while( zIn<zTerm ){ + READ_UTF8(zIn, zTerm, c); + WRITE_UTF16LE(z, c); + } + }else{ + assert( desiredEnc==SQLITE_UTF16BE ); + /* UTF-8 -> UTF-16 Big-endian */ + while( zIn<zTerm ){ + READ_UTF8(zIn, zTerm, c); + WRITE_UTF16BE(z, c); + } + } + pMem->n = (int)(z - zOut); + *z++ = 0; + }else{ + assert( desiredEnc==SQLITE_UTF8 ); + if( pMem->enc==SQLITE_UTF16LE ){ + /* UTF-16 Little-endian -> UTF-8 */ + while( zIn<zTerm ){ + c = *(zIn++); + c += (*(zIn++))<<8; + if( c>=0xd800 && c<0xe000 ){ +#ifdef SQLITE_REPLACE_INVALID_UTF + if( c>=0xdc00 || zIn>=zTerm ){ + c = 0xfffd; + }else{ + int c2 = *(zIn++); + c2 += (*(zIn++))<<8; + if( c2<0xdc00 || c2>=0xe000 ){ + zIn -= 2; + c = 0xfffd; + }else{ + c = ((c&0x3ff)<<10) + (c2&0x3ff) + 0x10000; + } + } +#else + if( zIn<zTerm ){ + int c2 = (*zIn++); + c2 += ((*zIn++)<<8); + c = (c2&0x03FF) + ((c&0x003F)<<10) + (((c&0x03C0)+0x0040)<<10); + } +#endif + } + WRITE_UTF8(z, c); + } + }else{ + /* UTF-16 Big-endian -> UTF-8 */ + while( zIn<zTerm ){ + c = (*(zIn++))<<8; + c += *(zIn++); + if( c>=0xd800 && c<0xe000 ){ +#ifdef SQLITE_REPLACE_INVALID_UTF + if( c>=0xdc00 || zIn>=zTerm ){ + c = 0xfffd; + }else{ + int c2 = (*(zIn++))<<8; + c2 += *(zIn++); + if( c2<0xdc00 || c2>=0xe000 ){ + zIn -= 2; + c = 0xfffd; + }else{ + c = ((c&0x3ff)<<10) + (c2&0x3ff) + 0x10000; + } + } +#else + if( zIn<zTerm ){ + int c2 = ((*zIn++)<<8); + c2 += (*zIn++); + c = (c2&0x03FF) + ((c&0x003F)<<10) + (((c&0x03C0)+0x0040)<<10); + } +#endif + } + WRITE_UTF8(z, c); + } + } + pMem->n = (int)(z - zOut); + } + *z = 0; + assert( (pMem->n+(desiredEnc==SQLITE_UTF8?1:2))<=len ); + + c = MEM_Str|MEM_Term|(pMem->flags&(MEM_AffMask|MEM_Subtype)); + sqlite3VdbeMemRelease(pMem); + pMem->flags = c; + pMem->enc = desiredEnc; + pMem->z = (char*)zOut; + pMem->zMalloc = pMem->z; + pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->z); + +translate_out: +#if defined(TRANSLATE_TRACE) && defined(SQLITE_DEBUG) + { + StrAccum acc; + char zBuf[1000]; + sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); + sqlite3VdbeMemPrettyPrint(pMem, &acc); + fprintf(stderr, "OUTPUT: %s\n", sqlite3StrAccumFinish(&acc)); + } +#endif + return SQLITE_OK; +} +#endif /* SQLITE_OMIT_UTF16 */ + +#ifndef SQLITE_OMIT_UTF16 +/* +** This routine checks for a byte-order mark at the beginning of the +** UTF-16 string stored in *pMem. If one is present, it is removed and +** the encoding of the Mem adjusted. This routine does not do any +** byte-swapping, it just sets Mem.enc appropriately. +** +** The allocation (static, dynamic etc.) and encoding of the Mem may be +** changed by this function. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem){ + int rc = SQLITE_OK; + u8 bom = 0; + + assert( pMem->n>=0 ); + if( pMem->n>1 ){ + u8 b1 = *(u8 *)pMem->z; + u8 b2 = *(((u8 *)pMem->z) + 1); + if( b1==0xFE && b2==0xFF ){ + bom = SQLITE_UTF16BE; + } + if( b1==0xFF && b2==0xFE ){ + bom = SQLITE_UTF16LE; + } + } + + if( bom ){ + rc = sqlite3VdbeMemMakeWriteable(pMem); + if( rc==SQLITE_OK ){ + pMem->n -= 2; + memmove(pMem->z, &pMem->z[2], pMem->n); + pMem->z[pMem->n] = '\0'; + pMem->z[pMem->n+1] = '\0'; + pMem->flags |= MEM_Term; + pMem->enc = bom; + } + } + return rc; +} +#endif /* SQLITE_OMIT_UTF16 */ + +/* +** pZ is a UTF-8 encoded unicode string. If nByte is less than zero, +** return the number of unicode characters in pZ up to (but not including) +** the first 0x00 byte. If nByte is not less than zero, return the +** number of unicode characters in the first nByte of pZ (or up to +** the first 0x00, whichever comes first). +*/ +SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *zIn, int nByte){ + int r = 0; + const u8 *z = (const u8*)zIn; + const u8 *zTerm; + if( nByte>=0 ){ + zTerm = &z[nByte]; + }else{ + zTerm = (const u8*)(-1); + } + assert( z<=zTerm ); + while( *z!=0 && z<zTerm ){ + SQLITE_SKIP_UTF8(z); + r++; + } + return r; +} + +/* This test function is not currently used by the automated test-suite. +** Hence it is only available in debug builds. +*/ +#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) +/* +** Translate UTF-8 to UTF-8. +** +** This has the effect of making sure that the string is well-formed +** UTF-8. Miscoded characters are removed. +** +** The translation is done in-place and aborted if the output +** overruns the input. +*/ +SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char *zIn){ + unsigned char *zOut = zIn; + unsigned char *zStart = zIn; + u32 c; + + while( zIn[0] && zOut<=zIn ){ + c = sqlite3Utf8Read((const u8**)&zIn); + if( c!=0xfffd ){ + WRITE_UTF8(zOut, c); + } + } + *zOut = 0; + return (int)(zOut - zStart); +} +#endif + +#ifndef SQLITE_OMIT_UTF16 +/* +** Convert a UTF-16 string in the native encoding into a UTF-8 string. +** Memory to hold the UTF-8 string is obtained from sqlite3_malloc and must +** be freed by the calling function. +** +** NULL is returned if there is an allocation error. +*/ +SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *db, const void *z, int nByte, u8 enc){ + Mem m; + memset(&m, 0, sizeof(m)); + m.db = db; + sqlite3VdbeMemSetStr(&m, z, nByte, enc, SQLITE_STATIC); + sqlite3VdbeChangeEncoding(&m, SQLITE_UTF8); + if( db->mallocFailed ){ + sqlite3VdbeMemRelease(&m); + m.z = 0; + } + assert( (m.flags & MEM_Term)!=0 || db->mallocFailed ); + assert( (m.flags & MEM_Str)!=0 || db->mallocFailed ); + assert( m.z || db->mallocFailed ); + return m.z; +} + +/* +** zIn is a UTF-16 encoded unicode string at least nChar characters long. +** Return the number of bytes in the first nChar unicode characters +** in pZ. nChar must be non-negative. +*/ +SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *zIn, int nChar){ + int c; + unsigned char const *z = zIn; + int n = 0; + + if( SQLITE_UTF16NATIVE==SQLITE_UTF16LE ) z++; + while( n<nChar ){ + c = z[0]; + z += 2; + if( c>=0xd8 && c<0xdc && z[0]>=0xdc && z[0]<0xe0 ) z += 2; + n++; + } + return (int)(z-(unsigned char const *)zIn) + - (SQLITE_UTF16NATIVE==SQLITE_UTF16LE); +} + +#if defined(SQLITE_TEST) +/* +** This routine is called from the TCL test function "translate_selftest". +** It checks that the primitives for serializing and deserializing +** characters in each encoding are inverses of each other. +*/ +SQLITE_PRIVATE void sqlite3UtfSelfTest(void){ + unsigned int i, t; + unsigned char zBuf[20]; + unsigned char *z; + int n; + unsigned int c; + + for(i=0; i<0x00110000; i++){ + z = zBuf; + WRITE_UTF8(z, i); + n = (int)(z-zBuf); + assert( n>0 && n<=4 ); + z[0] = 0; + z = zBuf; + c = sqlite3Utf8Read((const u8**)&z); + t = i; + if( i>=0xD800 && i<=0xDFFF ) t = 0xFFFD; + if( (i&0xFFFFFFFE)==0xFFFE ) t = 0xFFFD; + assert( c==t ); + assert( (z-zBuf)==n ); + } +} +#endif /* SQLITE_TEST */ +#endif /* SQLITE_OMIT_UTF16 */ + +/************** End of utf.c *************************************************/ +/************** Begin file util.c ********************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Utility functions used throughout sqlite. +** +** This file contains functions for allocating memory, comparing +** strings, and stuff like that. +** +*/ +/* #include "sqliteInt.h" */ +/* #include <stdarg.h> */ +#ifndef SQLITE_OMIT_FLOATING_POINT +#include <math.h> +#endif + +/* +** Calls to sqlite3FaultSim() are used to simulate a failure during testing, +** or to bypass normal error detection during testing in order to let +** execute proceed further downstream. +** +** In deployment, sqlite3FaultSim() *always* return SQLITE_OK (0). The +** sqlite3FaultSim() function only returns non-zero during testing. +** +** During testing, if the test harness has set a fault-sim callback using +** a call to sqlite3_test_control(SQLITE_TESTCTRL_FAULT_INSTALL), then +** each call to sqlite3FaultSim() is relayed to that application-supplied +** callback and the integer return value form the application-supplied +** callback is returned by sqlite3FaultSim(). +** +** The integer argument to sqlite3FaultSim() is a code to identify which +** sqlite3FaultSim() instance is being invoked. Each call to sqlite3FaultSim() +** should have a unique code. To prevent legacy testing applications from +** breaking, the codes should not be changed or reused. +*/ +#ifndef SQLITE_UNTESTABLE +SQLITE_PRIVATE int sqlite3FaultSim(int iTest){ + int (*xCallback)(int) = sqlite3GlobalConfig.xTestCallback; + return xCallback ? xCallback(iTest) : SQLITE_OK; +} +#endif + +#ifndef SQLITE_OMIT_FLOATING_POINT +/* +** Return true if the floating point value is Not a Number (NaN). +** +** Use the math library isnan() function if compiled with SQLITE_HAVE_ISNAN. +** Otherwise, we have our own implementation that works on most systems. +*/ +SQLITE_PRIVATE int sqlite3IsNaN(double x){ + int rc; /* The value return */ +#if !SQLITE_HAVE_ISNAN && !HAVE_ISNAN + u64 y; + memcpy(&y,&x,sizeof(y)); + rc = IsNaN(y); +#else + rc = isnan(x); +#endif /* HAVE_ISNAN */ + testcase( rc ); + return rc; +} +#endif /* SQLITE_OMIT_FLOATING_POINT */ + +/* +** Compute a string length that is limited to what can be stored in +** lower 30 bits of a 32-bit signed integer. +** +** The value returned will never be negative. Nor will it ever be greater +** than the actual length of the string. For very long strings (greater +** than 1GiB) the value returned might be less than the true string length. +*/ +SQLITE_PRIVATE int sqlite3Strlen30(const char *z){ + if( z==0 ) return 0; + return 0x3fffffff & (int)strlen(z); +} + +/* +** Return the declared type of a column. Or return zDflt if the column +** has no declared type. +** +** The column type is an extra string stored after the zero-terminator on +** the column name if and only if the COLFLAG_HASTYPE flag is set. +*/ +SQLITE_PRIVATE char *sqlite3ColumnType(Column *pCol, char *zDflt){ + if( pCol->colFlags & COLFLAG_HASTYPE ){ + return pCol->zCnName + strlen(pCol->zCnName) + 1; + }else if( pCol->eCType ){ + assert( pCol->eCType<=SQLITE_N_STDTYPE ); + return (char*)sqlite3StdType[pCol->eCType-1]; + }else{ + return zDflt; + } +} + +/* +** Helper function for sqlite3Error() - called rarely. Broken out into +** a separate routine to avoid unnecessary register saves on entry to +** sqlite3Error(). +*/ +static SQLITE_NOINLINE void sqlite3ErrorFinish(sqlite3 *db, int err_code){ + if( db->pErr ) sqlite3ValueSetNull(db->pErr); + sqlite3SystemError(db, err_code); +} + +/* +** Set the current error code to err_code and clear any prior error message. +** Also set iSysErrno (by calling sqlite3System) if the err_code indicates +** that would be appropriate. +*/ +SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code){ + assert( db!=0 ); + db->errCode = err_code; + if( err_code || db->pErr ){ + sqlite3ErrorFinish(db, err_code); + }else{ + db->errByteOffset = -1; + } +} + +/* +** The equivalent of sqlite3Error(db, SQLITE_OK). Clear the error state +** and error message. +*/ +SQLITE_PRIVATE void sqlite3ErrorClear(sqlite3 *db){ + assert( db!=0 ); + db->errCode = SQLITE_OK; + db->errByteOffset = -1; + if( db->pErr ) sqlite3ValueSetNull(db->pErr); +} + +/* +** Load the sqlite3.iSysErrno field if that is an appropriate thing +** to do based on the SQLite error code in rc. +*/ +SQLITE_PRIVATE void sqlite3SystemError(sqlite3 *db, int rc){ + if( rc==SQLITE_IOERR_NOMEM ) return; +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_IOERR_IN_PAGE ){ + int ii; + int iErr; + sqlite3BtreeEnterAll(db); + for(ii=0; ii<db->nDb; ii++){ + if( db->aDb[ii].pBt ){ + iErr = sqlite3PagerWalSystemErrno(sqlite3BtreePager(db->aDb[ii].pBt)); + if( iErr ){ + db->iSysErrno = iErr; + } + } + } + sqlite3BtreeLeaveAll(db); + return; + } +#endif + rc &= 0xff; + if( rc==SQLITE_CANTOPEN || rc==SQLITE_IOERR ){ + db->iSysErrno = sqlite3OsGetLastError(db->pVfs); + } +} + +/* +** Set the most recent error code and error string for the sqlite +** handle "db". The error code is set to "err_code". +** +** If it is not NULL, string zFormat specifies the format of the +** error string. zFormat and any string tokens that follow it are +** assumed to be encoded in UTF-8. +** +** To clear the most recent error for sqlite handle "db", sqlite3Error +** should be called with err_code set to SQLITE_OK and zFormat set +** to NULL. +*/ +SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3 *db, int err_code, const char *zFormat, ...){ + assert( db!=0 ); + db->errCode = err_code; + sqlite3SystemError(db, err_code); + if( zFormat==0 ){ + sqlite3Error(db, err_code); + }else if( db->pErr || (db->pErr = sqlite3ValueNew(db))!=0 ){ + char *z; + va_list ap; + va_start(ap, zFormat); + z = sqlite3VMPrintf(db, zFormat, ap); + va_end(ap); + sqlite3ValueSetStr(db->pErr, -1, z, SQLITE_UTF8, SQLITE_DYNAMIC); + } +} + +/* +** Check for interrupts and invoke progress callback. +*/ +SQLITE_PRIVATE void sqlite3ProgressCheck(Parse *p){ + sqlite3 *db = p->db; + if( AtomicLoad(&db->u1.isInterrupted) ){ + p->nErr++; + p->rc = SQLITE_INTERRUPT; + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( db->xProgress ){ + if( p->rc==SQLITE_INTERRUPT ){ + p->nProgressSteps = 0; + }else if( (++p->nProgressSteps)>=db->nProgressOps ){ + if( db->xProgress(db->pProgressArg) ){ + p->nErr++; + p->rc = SQLITE_INTERRUPT; + } + p->nProgressSteps = 0; + } + } +#endif +} + +/* +** Add an error message to pParse->zErrMsg and increment pParse->nErr. +** +** This function should be used to report any error that occurs while +** compiling an SQL statement (i.e. within sqlite3_prepare()). The +** last thing the sqlite3_prepare() function does is copy the error +** stored by this function into the database handle using sqlite3Error(). +** Functions sqlite3Error() or sqlite3ErrorWithMsg() should be used +** during statement execution (sqlite3_step() etc.). +*/ +SQLITE_PRIVATE void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){ + char *zMsg; + va_list ap; + sqlite3 *db = pParse->db; + assert( db!=0 ); + assert( db->pParse==pParse || db->pParse->pToplevel==pParse ); + db->errByteOffset = -2; + va_start(ap, zFormat); + zMsg = sqlite3VMPrintf(db, zFormat, ap); + va_end(ap); + if( db->errByteOffset<-1 ) db->errByteOffset = -1; + if( db->suppressErr ){ + sqlite3DbFree(db, zMsg); + if( db->mallocFailed ){ + pParse->nErr++; + pParse->rc = SQLITE_NOMEM; + } + }else{ + pParse->nErr++; + sqlite3DbFree(db, pParse->zErrMsg); + pParse->zErrMsg = zMsg; + pParse->rc = SQLITE_ERROR; + pParse->pWith = 0; + } +} + +/* +** If database connection db is currently parsing SQL, then transfer +** error code errCode to that parser if the parser has not already +** encountered some other kind of error. +*/ +SQLITE_PRIVATE int sqlite3ErrorToParser(sqlite3 *db, int errCode){ + Parse *pParse; + if( db==0 || (pParse = db->pParse)==0 ) return errCode; + pParse->rc = errCode; + pParse->nErr++; + return errCode; +} + +/* +** Convert an SQL-style quoted string into a normal string by removing +** the quote characters. The conversion is done in-place. If the +** input does not begin with a quote character, then this routine +** is a no-op. +** +** The input string must be zero-terminated. A new zero-terminator +** is added to the dequoted string. +** +** The return value is -1 if no dequoting occurs or the length of the +** dequoted string, exclusive of the zero terminator, if dequoting does +** occur. +** +** 2002-02-14: This routine is extended to remove MS-Access style +** brackets from around identifiers. For example: "[a-b-c]" becomes +** "a-b-c". +*/ +SQLITE_PRIVATE void sqlite3Dequote(char *z){ + char quote; + int i, j; + if( z==0 ) return; + quote = z[0]; + if( !sqlite3Isquote(quote) ) return; + if( quote=='[' ) quote = ']'; + for(i=1, j=0;; i++){ + assert( z[i] ); + if( z[i]==quote ){ + if( z[i+1]==quote ){ + z[j++] = quote; + i++; + }else{ + break; + } + }else{ + z[j++] = z[i]; + } + } + z[j] = 0; +} +SQLITE_PRIVATE void sqlite3DequoteExpr(Expr *p){ + assert( !ExprHasProperty(p, EP_IntValue) ); + assert( sqlite3Isquote(p->u.zToken[0]) ); + p->flags |= p->u.zToken[0]=='"' ? EP_Quoted|EP_DblQuoted : EP_Quoted; + sqlite3Dequote(p->u.zToken); +} + +/* +** If the input token p is quoted, try to adjust the token to remove +** the quotes. This is not always possible: +** +** "abc" -> abc +** "ab""cd" -> (not possible because of the interior "") +** +** Remove the quotes if possible. This is a optimization. The overall +** system should still return the correct answer even if this routine +** is always a no-op. +*/ +SQLITE_PRIVATE void sqlite3DequoteToken(Token *p){ + unsigned int i; + if( p->n<2 ) return; + if( !sqlite3Isquote(p->z[0]) ) return; + for(i=1; i<p->n-1; i++){ + if( sqlite3Isquote(p->z[i]) ) return; + } + p->n -= 2; + p->z++; +} + +/* +** Generate a Token object from a string +*/ +SQLITE_PRIVATE void sqlite3TokenInit(Token *p, char *z){ + p->z = z; + p->n = sqlite3Strlen30(z); +} + +/* Convenient short-hand */ +#define UpperToLower sqlite3UpperToLower + +/* +** Some systems have stricmp(). Others have strcasecmp(). Because +** there is no consistency, we will define our own. +** +** IMPLEMENTATION-OF: R-30243-02494 The sqlite3_stricmp() and +** sqlite3_strnicmp() APIs allow applications and extensions to compare +** the contents of two buffers containing UTF-8 strings in a +** case-independent fashion, using the same definition of "case +** independence" that SQLite uses internally when comparing identifiers. +*/ +SQLITE_API int sqlite3_stricmp(const char *zLeft, const char *zRight){ + if( zLeft==0 ){ + return zRight ? -1 : 0; + }else if( zRight==0 ){ + return 1; + } + return sqlite3StrICmp(zLeft, zRight); +} +SQLITE_PRIVATE int sqlite3StrICmp(const char *zLeft, const char *zRight){ + unsigned char *a, *b; + int c, x; + a = (unsigned char *)zLeft; + b = (unsigned char *)zRight; + for(;;){ + c = *a; + x = *b; + if( c==x ){ + if( c==0 ) break; + }else{ + c = (int)UpperToLower[c] - (int)UpperToLower[x]; + if( c ) break; + } + a++; + b++; + } + return c; +} +SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){ + register unsigned char *a, *b; + if( zLeft==0 ){ + return zRight ? -1 : 0; + }else if( zRight==0 ){ + return 1; + } + a = (unsigned char *)zLeft; + b = (unsigned char *)zRight; + while( N-- > 0 && *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; } + return N<0 ? 0 : UpperToLower[*a] - UpperToLower[*b]; +} + +/* +** Compute an 8-bit hash on a string that is insensitive to case differences +*/ +SQLITE_PRIVATE u8 sqlite3StrIHash(const char *z){ + u8 h = 0; + if( z==0 ) return 0; + while( z[0] ){ + h += UpperToLower[(unsigned char)z[0]]; + z++; + } + return h; +} + +/* Double-Double multiplication. (x[0],x[1]) *= (y,yy) +** +** Reference: +** T. J. Dekker, "A Floating-Point Technique for Extending the +** Available Precision". 1971-07-26. +*/ +static void dekkerMul2(volatile double *x, double y, double yy){ + /* + ** The "volatile" keywords on parameter x[] and on local variables + ** below are needed force intermediate results to be truncated to + ** binary64 rather than be carried around in an extended-precision + ** format. The truncation is necessary for the Dekker algorithm to + ** work. Intel x86 floating point might omit the truncation without + ** the use of volatile. + */ + volatile double tx, ty, p, q, c, cc; + double hx, hy; + u64 m; + memcpy(&m, (void*)&x[0], 8); + m &= 0xfffffffffc000000LL; + memcpy(&hx, &m, 8); + tx = x[0] - hx; + memcpy(&m, &y, 8); + m &= 0xfffffffffc000000LL; + memcpy(&hy, &m, 8); + ty = y - hy; + p = hx*hy; + q = hx*ty + tx*hy; + c = p+q; + cc = p - c + q + tx*ty; + cc = x[0]*yy + x[1]*y + cc; + x[0] = c + cc; + x[1] = c - x[0]; + x[1] += cc; +} + +/* +** The string z[] is an text representation of a real number. +** Convert this string to a double and write it into *pResult. +** +** The string z[] is length bytes in length (bytes, not characters) and +** uses the encoding enc. The string is not necessarily zero-terminated. +** +** Return TRUE if the result is a valid real number (or integer) and FALSE +** if the string is empty or contains extraneous text. More specifically +** return +** 1 => The input string is a pure integer +** 2 or more => The input has a decimal point or eNNN clause +** 0 or less => The input string is not a valid number +** -1 => Not a valid number, but has a valid prefix which +** includes a decimal point and/or an eNNN clause +** +** Valid numbers are in one of these formats: +** +** [+-]digits[E[+-]digits] +** [+-]digits.[digits][E[+-]digits] +** [+-].digits[E[+-]digits] +** +** Leading and trailing whitespace is ignored for the purpose of determining +** validity. +** +** If some prefix of the input string is a valid number, this routine +** returns FALSE but it still converts the prefix and writes the result +** into *pResult. +*/ +#if defined(_MSC_VER) +#pragma warning(disable : 4756) +#endif +SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ +#ifndef SQLITE_OMIT_FLOATING_POINT + int incr; + const char *zEnd; + /* sign * significand * (10 ^ (esign * exponent)) */ + int sign = 1; /* sign of significand */ + u64 s = 0; /* significand */ + int d = 0; /* adjust exponent for shifting decimal point */ + int esign = 1; /* sign of exponent */ + int e = 0; /* exponent */ + int eValid = 1; /* True exponent is either not used or is well-formed */ + int nDigit = 0; /* Number of digits processed */ + int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ + + assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); + *pResult = 0.0; /* Default return value, in case of an error */ + if( length==0 ) return 0; + + if( enc==SQLITE_UTF8 ){ + incr = 1; + zEnd = z + length; + }else{ + int i; + incr = 2; + length &= ~1; + assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); + testcase( enc==SQLITE_UTF16LE ); + testcase( enc==SQLITE_UTF16BE ); + for(i=3-enc; i<length && z[i]==0; i+=2){} + if( i<length ) eType = -100; + zEnd = &z[i^1]; + z += (enc&1); + } + + /* skip leading spaces */ + while( z<zEnd && sqlite3Isspace(*z) ) z+=incr; + if( z>=zEnd ) return 0; + + /* get sign of significand */ + if( *z=='-' ){ + sign = -1; + z+=incr; + }else if( *z=='+' ){ + z+=incr; + } + + /* copy max significant digits to significand */ + while( z<zEnd && sqlite3Isdigit(*z) ){ + s = s*10 + (*z - '0'); + z+=incr; nDigit++; + if( s>=((LARGEST_UINT64-9)/10) ){ + /* skip non-significant significand digits + ** (increase exponent by d to shift decimal left) */ + while( z<zEnd && sqlite3Isdigit(*z) ){ z+=incr; d++; } + } + } + if( z>=zEnd ) goto do_atof_calc; + + /* if decimal point is present */ + if( *z=='.' ){ + z+=incr; + eType++; + /* copy digits from after decimal to significand + ** (decrease exponent by d to shift decimal right) */ + while( z<zEnd && sqlite3Isdigit(*z) ){ + if( s<((LARGEST_UINT64-9)/10) ){ + s = s*10 + (*z - '0'); + d--; + nDigit++; + } + z+=incr; + } + } + if( z>=zEnd ) goto do_atof_calc; + + /* if exponent is present */ + if( *z=='e' || *z=='E' ){ + z+=incr; + eValid = 0; + eType++; + + /* This branch is needed to avoid a (harmless) buffer overread. The + ** special comment alerts the mutation tester that the correct answer + ** is obtained even if the branch is omitted */ + if( z>=zEnd ) goto do_atof_calc; /*PREVENTS-HARMLESS-OVERREAD*/ + + /* get sign of exponent */ + if( *z=='-' ){ + esign = -1; + z+=incr; + }else if( *z=='+' ){ + z+=incr; + } + /* copy digits to exponent */ + while( z<zEnd && sqlite3Isdigit(*z) ){ + e = e<10000 ? (e*10 + (*z - '0')) : 10000; + z+=incr; + eValid = 1; + } + } + + /* skip trailing spaces */ + while( z<zEnd && sqlite3Isspace(*z) ) z+=incr; + +do_atof_calc: + /* Zero is a special case */ + if( s==0 ){ + *pResult = sign<0 ? -0.0 : +0.0; + goto atof_return; + } + + /* adjust exponent by d, and update sign */ + e = (e*esign) + d; + + /* Try to adjust the exponent to make it smaller */ + while( e>0 && s<(LARGEST_UINT64/10) ){ + s *= 10; + e--; + } + while( e<0 && (s%10)==0 ){ + s /= 10; + e++; + } + + if( e==0 ){ + *pResult = s; + }else if( sqlite3Config.bUseLongDouble ){ + LONGDOUBLE_TYPE r = (LONGDOUBLE_TYPE)s; + if( e>0 ){ + while( e>=100 ){ e-=100; r *= 1.0e+100L; } + while( e>=10 ){ e-=10; r *= 1.0e+10L; } + while( e>=1 ){ e-=1; r *= 1.0e+01L; } + }else{ + while( e<=-100 ){ e+=100; r *= 1.0e-100L; } + while( e<=-10 ){ e+=10; r *= 1.0e-10L; } + while( e<=-1 ){ e+=1; r *= 1.0e-01L; } + } + assert( r>=0.0 ); + if( r>+1.7976931348623157081452742373e+308L ){ +#ifdef INFINITY + *pResult = +INFINITY; +#else + *pResult = 1.0e308*10.0; +#endif + }else{ + *pResult = (double)r; + } + }else{ + double rr[2]; + u64 s2; + rr[0] = (double)s; + s2 = (u64)rr[0]; + rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); + if( e>0 ){ + while( e>=100 ){ + e -= 100; + dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); + } + while( e>=10 ){ + e -= 10; + dekkerMul2(rr, 1.0e+10, 0.0); + } + while( e>=1 ){ + e -= 1; + dekkerMul2(rr, 1.0e+01, 0.0); + } + }else{ + while( e<=-100 ){ + e += 100; + dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); + } + while( e<=-10 ){ + e += 10; + dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); + } + while( e<=-1 ){ + e += 1; + dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); + } + } + *pResult = rr[0]+rr[1]; + if( sqlite3IsNaN(*pResult) ) *pResult = 1e300*1e300; + } + if( sign<0 ) *pResult = -*pResult; + assert( !sqlite3IsNaN(*pResult) ); + +atof_return: + /* return true if number and no extra non-whitespace characters after */ + if( z==zEnd && nDigit>0 && eValid && eType>0 ){ + return eType; + }else if( eType>=2 && (eType==3 || eValid) && nDigit>0 ){ + return -1; + }else{ + return 0; + } +#else + return !sqlite3Atoi64(z, pResult, length, enc); +#endif /* SQLITE_OMIT_FLOATING_POINT */ +} +#if defined(_MSC_VER) +#pragma warning(default : 4756) +#endif + +/* +** Render an signed 64-bit integer as text. Store the result in zOut[] and +** return the length of the string that was stored, in bytes. The value +** returned does not include the zero terminator at the end of the output +** string. +** +** The caller must ensure that zOut[] is at least 21 bytes in size. +*/ +SQLITE_PRIVATE int sqlite3Int64ToText(i64 v, char *zOut){ + int i; + u64 x; + char zTemp[22]; + if( v<0 ){ + x = (v==SMALLEST_INT64) ? ((u64)1)<<63 : (u64)-v; + }else{ + x = v; + } + i = sizeof(zTemp)-2; + zTemp[sizeof(zTemp)-1] = 0; + while( 1 /*exit-by-break*/ ){ + zTemp[i] = (x%10) + '0'; + x = x/10; + if( x==0 ) break; + i--; + }; + if( v<0 ) zTemp[--i] = '-'; + memcpy(zOut, &zTemp[i], sizeof(zTemp)-i); + return sizeof(zTemp)-1-i; +} + +/* +** Compare the 19-character string zNum against the text representation +** value 2^63: 9223372036854775808. Return negative, zero, or positive +** if zNum is less than, equal to, or greater than the string. +** Note that zNum must contain exactly 19 characters. +** +** Unlike memcmp() this routine is guaranteed to return the difference +** in the values of the last digit if the only difference is in the +** last digit. So, for example, +** +** compare2pow63("9223372036854775800", 1) +** +** will return -8. +*/ +static int compare2pow63(const char *zNum, int incr){ + int c = 0; + int i; + /* 012345678901234567 */ + const char *pow63 = "922337203685477580"; + for(i=0; c==0 && i<18; i++){ + c = (zNum[i*incr]-pow63[i])*10; + } + if( c==0 ){ + c = zNum[18*incr] - '8'; + testcase( c==(-1) ); + testcase( c==0 ); + testcase( c==(+1) ); + } + return c; +} + +/* +** Convert zNum to a 64-bit signed integer. zNum must be decimal. This +** routine does *not* accept hexadecimal notation. +** +** Returns: +** +** -1 Not even a prefix of the input text looks like an integer +** 0 Successful transformation. Fits in a 64-bit signed integer. +** 1 Excess non-space text after the integer value +** 2 Integer too large for a 64-bit signed integer or is malformed +** 3 Special case of 9223372036854775808 +** +** length is the number of bytes in the string (bytes, not characters). +** The string is not necessarily zero-terminated. The encoding is +** given by enc. +*/ +SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){ + int incr; + u64 u = 0; + int neg = 0; /* assume positive */ + int i; + int c = 0; + int nonNum = 0; /* True if input contains UTF16 with high byte non-zero */ + int rc; /* Baseline return code */ + const char *zStart; + const char *zEnd = zNum + length; + assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); + if( enc==SQLITE_UTF8 ){ + incr = 1; + }else{ + incr = 2; + length &= ~1; + assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); + for(i=3-enc; i<length && zNum[i]==0; i+=2){} + nonNum = i<length; + zEnd = &zNum[i^1]; + zNum += (enc&1); + } + while( zNum<zEnd && sqlite3Isspace(*zNum) ) zNum+=incr; + if( zNum<zEnd ){ + if( *zNum=='-' ){ + neg = 1; + zNum+=incr; + }else if( *zNum=='+' ){ + zNum+=incr; + } + } + zStart = zNum; + while( zNum<zEnd && zNum[0]=='0' ){ zNum+=incr; } /* Skip leading zeros. */ + for(i=0; &zNum[i]<zEnd && (c=zNum[i])>='0' && c<='9'; i+=incr){ + u = u*10 + c - '0'; + } + testcase( i==18*incr ); + testcase( i==19*incr ); + testcase( i==20*incr ); + if( u>LARGEST_INT64 ){ + /* This test and assignment is needed only to suppress UB warnings + ** from clang and -fsanitize=undefined. This test and assignment make + ** the code a little larger and slower, and no harm comes from omitting + ** them, but we must appease the undefined-behavior pharisees. */ + *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64; + }else if( neg ){ + *pNum = -(i64)u; + }else{ + *pNum = (i64)u; + } + rc = 0; + if( i==0 && zStart==zNum ){ /* No digits */ + rc = -1; + }else if( nonNum ){ /* UTF16 with high-order bytes non-zero */ + rc = 1; + }else if( &zNum[i]<zEnd ){ /* Extra bytes at the end */ + int jj = i; + do{ + if( !sqlite3Isspace(zNum[jj]) ){ + rc = 1; /* Extra non-space text after the integer */ + break; + } + jj += incr; + }while( &zNum[jj]<zEnd ); + } + if( i<19*incr ){ + /* Less than 19 digits, so we know that it fits in 64 bits */ + assert( u<=LARGEST_INT64 ); + return rc; + }else{ + /* zNum is a 19-digit numbers. Compare it against 9223372036854775808. */ + c = i>19*incr ? 1 : compare2pow63(zNum, incr); + if( c<0 ){ + /* zNum is less than 9223372036854775808 so it fits */ + assert( u<=LARGEST_INT64 ); + return rc; + }else{ + *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64; + if( c>0 ){ + /* zNum is greater than 9223372036854775808 so it overflows */ + return 2; + }else{ + /* zNum is exactly 9223372036854775808. Fits if negative. The + ** special case 2 overflow if positive */ + assert( u-1==LARGEST_INT64 ); + return neg ? rc : 3; + } + } + } +} + +/* +** Transform a UTF-8 integer literal, in either decimal or hexadecimal, +** into a 64-bit signed integer. This routine accepts hexadecimal literals, +** whereas sqlite3Atoi64() does not. +** +** Returns: +** +** 0 Successful transformation. Fits in a 64-bit signed integer. +** 1 Excess text after the integer value +** 2 Integer too large for a 64-bit signed integer or is malformed +** 3 Special case of 9223372036854775808 +*/ +SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char *z, i64 *pOut){ +#ifndef SQLITE_OMIT_HEX_INTEGER + if( z[0]=='0' + && (z[1]=='x' || z[1]=='X') + ){ + u64 u = 0; + int i, k; + for(i=2; z[i]=='0'; i++){} + for(k=i; sqlite3Isxdigit(z[k]); k++){ + u = u*16 + sqlite3HexToInt(z[k]); + } + memcpy(pOut, &u, 8); + if( k-i>16 ) return 2; + if( z[k]!=0 ) return 1; + return 0; + }else +#endif /* SQLITE_OMIT_HEX_INTEGER */ + { + int n = (int)(0x3fffffff&strspn(z,"+- \n\t0123456789")); + if( z[n] ) n++; + return sqlite3Atoi64(z, pOut, n, SQLITE_UTF8); + } +} + +/* +** If zNum represents an integer that will fit in 32-bits, then set +** *pValue to that integer and return true. Otherwise return false. +** +** This routine accepts both decimal and hexadecimal notation for integers. +** +** Any non-numeric characters that following zNum are ignored. +** This is different from sqlite3Atoi64() which requires the +** input number to be zero-terminated. +*/ +SQLITE_PRIVATE int sqlite3GetInt32(const char *zNum, int *pValue){ + sqlite_int64 v = 0; + int i, c; + int neg = 0; + if( zNum[0]=='-' ){ + neg = 1; + zNum++; + }else if( zNum[0]=='+' ){ + zNum++; + } +#ifndef SQLITE_OMIT_HEX_INTEGER + else if( zNum[0]=='0' + && (zNum[1]=='x' || zNum[1]=='X') + && sqlite3Isxdigit(zNum[2]) + ){ + u32 u = 0; + zNum += 2; + while( zNum[0]=='0' ) zNum++; + for(i=0; i<8 && sqlite3Isxdigit(zNum[i]); i++){ + u = u*16 + sqlite3HexToInt(zNum[i]); + } + if( (u&0x80000000)==0 && sqlite3Isxdigit(zNum[i])==0 ){ + memcpy(pValue, &u, 4); + return 1; + }else{ + return 0; + } + } +#endif + if( !sqlite3Isdigit(zNum[0]) ) return 0; + while( zNum[0]=='0' ) zNum++; + for(i=0; i<11 && (c = zNum[i] - '0')>=0 && c<=9; i++){ + v = v*10 + c; + } + + /* The longest decimal representation of a 32 bit integer is 10 digits: + ** + ** 1234567890 + ** 2^31 -> 2147483648 + */ + testcase( i==10 ); + if( i>10 ){ + return 0; + } + testcase( v-neg==2147483647 ); + if( v-neg>2147483647 ){ + return 0; + } + if( neg ){ + v = -v; + } + *pValue = (int)v; + return 1; +} + +/* +** Return a 32-bit integer value extracted from a string. If the +** string is not an integer, just return 0. +*/ +SQLITE_PRIVATE int sqlite3Atoi(const char *z){ + int x = 0; + sqlite3GetInt32(z, &x); + return x; +} + +/* +** Decode a floating-point value into an approximate decimal +** representation. +** +** Round the decimal representation to n significant digits if +** n is positive. Or round to -n signficant digits after the +** decimal point if n is negative. No rounding is performed if +** n is zero. +** +** The significant digits of the decimal representation are +** stored in p->z[] which is a often (but not always) a pointer +** into the middle of p->zBuf[]. There are p->n significant digits. +** The p->z[] array is *not* zero-terminated. +*/ +SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ + int i; + u64 v; + int e, exp = 0; + p->isSpecial = 0; + p->z = p->zBuf; + + /* Convert negative numbers to positive. Deal with Infinity, 0.0, and + ** NaN. */ + if( r<0.0 ){ + p->sign = '-'; + r = -r; + }else if( r==0.0 ){ + p->sign = '+'; + p->n = 1; + p->iDP = 1; + p->z = "0"; + return; + }else{ + p->sign = '+'; + } + memcpy(&v,&r,8); + e = v>>52; + if( (e&0x7ff)==0x7ff ){ + p->isSpecial = 1 + (v!=0x7ff0000000000000LL); + p->n = 0; + p->iDP = 0; + return; + } + + /* Multiply r by powers of ten until it lands somewhere in between + ** 1.0e+19 and 1.0e+17. + */ + if( sqlite3Config.bUseLongDouble ){ + LONGDOUBLE_TYPE rr = r; + if( rr>=1.0e+19 ){ + while( rr>=1.0e+119L ){ exp+=100; rr *= 1.0e-100L; } + while( rr>=1.0e+29L ){ exp+=10; rr *= 1.0e-10L; } + while( rr>=1.0e+19L ){ exp++; rr *= 1.0e-1L; } + }else{ + while( rr<1.0e-97L ){ exp-=100; rr *= 1.0e+100L; } + while( rr<1.0e+07L ){ exp-=10; rr *= 1.0e+10L; } + while( rr<1.0e+17L ){ exp--; rr *= 1.0e+1L; } + } + v = (u64)rr; + }else{ + /* If high-precision floating point is not available using "long double", + ** then use Dekker-style double-double computation to increase the + ** precision. + ** + ** The error terms on constants like 1.0e+100 computed using the + ** decimal extension, for example as follows: + ** + ** SELECT decimal_exp(decimal_sub('1.0e+100',decimal(1.0e+100))); + */ + double rr[2]; + rr[0] = r; + rr[1] = 0.0; + if( rr[0]>1.84e+19 ){ + while( rr[0]>1.84e+119 ){ + exp += 100; + dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); + } + while( rr[0]>1.84e+29 ){ + exp += 10; + dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); + } + while( rr[0]>1.84e+19 ){ + exp += 1; + dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); + } + }else{ + while( rr[0]<1.84e-82 ){ + exp -= 100; + dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); + } + while( rr[0]<1.84e+08 ){ + exp -= 10; + dekkerMul2(rr, 1.0e+10, 0.0); + } + while( rr[0]<1.84e+18 ){ + exp -= 1; + dekkerMul2(rr, 1.0e+01, 0.0); + } + } + v = rr[1]<0.0 ? (u64)rr[0]-(u64)(-rr[1]) : (u64)rr[0]+(u64)rr[1]; + } + + + /* Extract significant digits. */ + i = sizeof(p->zBuf)-1; + assert( v>0 ); + while( v ){ p->zBuf[i--] = (v%10) + '0'; v /= 10; } + assert( i>=0 && i<sizeof(p->zBuf)-1 ); + p->n = sizeof(p->zBuf) - 1 - i; + assert( p->n>0 ); + assert( p->n<sizeof(p->zBuf) ); + p->iDP = p->n + exp; + if( iRound<0 ){ + iRound = p->iDP - iRound; + if( iRound==0 && p->zBuf[i+1]>='5' ){ + iRound = 1; + p->zBuf[i--] = '0'; + p->n++; + p->iDP++; + } + } + if( iRound>0 && (iRound<p->n || p->n>mxRound) ){ + char *z = &p->zBuf[i+1]; + if( iRound>mxRound ) iRound = mxRound; + p->n = iRound; + if( z[iRound]>='5' ){ + int j = iRound-1; + while( 1 /*exit-by-break*/ ){ + z[j]++; + if( z[j]<='9' ) break; + z[j] = '0'; + if( j==0 ){ + p->z[i--] = '1'; + p->n++; + p->iDP++; + break; + }else{ + j--; + } + } + } + } + p->z = &p->zBuf[i+1]; + assert( i+p->n < sizeof(p->zBuf) ); + while( ALWAYS(p->n>0) && p->z[p->n-1]=='0' ){ p->n--; } +} + +/* +** Try to convert z into an unsigned 32-bit integer. Return true on +** success and false if there is an error. +** +** Only decimal notation is accepted. +*/ +SQLITE_PRIVATE int sqlite3GetUInt32(const char *z, u32 *pI){ + u64 v = 0; + int i; + for(i=0; sqlite3Isdigit(z[i]); i++){ + v = v*10 + z[i] - '0'; + if( v>4294967296LL ){ *pI = 0; return 0; } + } + if( i==0 || z[i]!=0 ){ *pI = 0; return 0; } + *pI = (u32)v; + return 1; +} + +/* +** The variable-length integer encoding is as follows: +** +** KEY: +** A = 0xxxxxxx 7 bits of data and one flag bit +** B = 1xxxxxxx 7 bits of data and one flag bit +** C = xxxxxxxx 8 bits of data +** +** 7 bits - A +** 14 bits - BA +** 21 bits - BBA +** 28 bits - BBBA +** 35 bits - BBBBA +** 42 bits - BBBBBA +** 49 bits - BBBBBBA +** 56 bits - BBBBBBBA +** 64 bits - BBBBBBBBC +*/ + +/* +** Write a 64-bit variable-length integer to memory starting at p[0]. +** The length of data write will be between 1 and 9 bytes. The number +** of bytes written is returned. +** +** A variable-length integer consists of the lower 7 bits of each byte +** for all bytes that have the 8th bit set and one byte with the 8th +** bit clear. Except, if we get to the 9th byte, it stores the full +** 8 bits and is the last byte. +*/ +static int SQLITE_NOINLINE putVarint64(unsigned char *p, u64 v){ + int i, j, n; + u8 buf[10]; + if( v & (((u64)0xff000000)<<32) ){ + p[8] = (u8)v; + v >>= 8; + for(i=7; i>=0; i--){ + p[i] = (u8)((v & 0x7f) | 0x80); + v >>= 7; + } + return 9; + } + n = 0; + do{ + buf[n++] = (u8)((v & 0x7f) | 0x80); + v >>= 7; + }while( v!=0 ); + buf[0] &= 0x7f; + assert( n<=9 ); + for(i=0, j=n-1; j>=0; j--, i++){ + p[i] = buf[j]; + } + return n; +} +SQLITE_PRIVATE int sqlite3PutVarint(unsigned char *p, u64 v){ + if( v<=0x7f ){ + p[0] = v&0x7f; + return 1; + } + if( v<=0x3fff ){ + p[0] = ((v>>7)&0x7f)|0x80; + p[1] = v&0x7f; + return 2; + } + return putVarint64(p,v); +} + +/* +** Bitmasks used by sqlite3GetVarint(). These precomputed constants +** are defined here rather than simply putting the constant expressions +** inline in order to work around bugs in the RVT compiler. +** +** SLOT_2_0 A mask for (0x7f<<14) | 0x7f +** +** SLOT_4_2_0 A mask for (0x7f<<28) | SLOT_2_0 +*/ +#define SLOT_2_0 0x001fc07f +#define SLOT_4_2_0 0xf01fc07f + + +/* +** Read a 64-bit variable-length integer from memory starting at p[0]. +** Return the number of bytes read. The value is stored in *v. +*/ +SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *p, u64 *v){ + u32 a,b,s; + + if( ((signed char*)p)[0]>=0 ){ + *v = *p; + return 1; + } + if( ((signed char*)p)[1]>=0 ){ + *v = ((u32)(p[0]&0x7f)<<7) | p[1]; + return 2; + } + + /* Verify that constants are precomputed correctly */ + assert( SLOT_2_0 == ((0x7f<<14) | (0x7f)) ); + assert( SLOT_4_2_0 == ((0xfU<<28) | (0x7f<<14) | (0x7f)) ); + + a = ((u32)p[0])<<14; + b = p[1]; + p += 2; + a |= *p; + /* a: p0<<14 | p2 (unmasked) */ + if (!(a&0x80)) + { + a &= SLOT_2_0; + b &= 0x7f; + b = b<<7; + a |= b; + *v = a; + return 3; + } + + /* CSE1 from below */ + a &= SLOT_2_0; + p++; + b = b<<14; + b |= *p; + /* b: p1<<14 | p3 (unmasked) */ + if (!(b&0x80)) + { + b &= SLOT_2_0; + /* moved CSE1 up */ + /* a &= (0x7f<<14)|(0x7f); */ + a = a<<7; + a |= b; + *v = a; + return 4; + } + + /* a: p0<<14 | p2 (masked) */ + /* b: p1<<14 | p3 (unmasked) */ + /* 1:save off p0<<21 | p1<<14 | p2<<7 | p3 (masked) */ + /* moved CSE1 up */ + /* a &= (0x7f<<14)|(0x7f); */ + b &= SLOT_2_0; + s = a; + /* s: p0<<14 | p2 (masked) */ + + p++; + a = a<<14; + a |= *p; + /* a: p0<<28 | p2<<14 | p4 (unmasked) */ + if (!(a&0x80)) + { + /* we can skip these cause they were (effectively) done above + ** while calculating s */ + /* a &= (0x7f<<28)|(0x7f<<14)|(0x7f); */ + /* b &= (0x7f<<14)|(0x7f); */ + b = b<<7; + a |= b; + s = s>>18; + *v = ((u64)s)<<32 | a; + return 5; + } + + /* 2:save off p0<<21 | p1<<14 | p2<<7 | p3 (masked) */ + s = s<<7; + s |= b; + /* s: p0<<21 | p1<<14 | p2<<7 | p3 (masked) */ + + p++; + b = b<<14; + b |= *p; + /* b: p1<<28 | p3<<14 | p5 (unmasked) */ + if (!(b&0x80)) + { + /* we can skip this cause it was (effectively) done above in calc'ing s */ + /* b &= (0x7f<<28)|(0x7f<<14)|(0x7f); */ + a &= SLOT_2_0; + a = a<<7; + a |= b; + s = s>>18; + *v = ((u64)s)<<32 | a; + return 6; + } + + p++; + a = a<<14; + a |= *p; + /* a: p2<<28 | p4<<14 | p6 (unmasked) */ + if (!(a&0x80)) + { + a &= SLOT_4_2_0; + b &= SLOT_2_0; + b = b<<7; + a |= b; + s = s>>11; + *v = ((u64)s)<<32 | a; + return 7; + } + + /* CSE2 from below */ + a &= SLOT_2_0; + p++; + b = b<<14; + b |= *p; + /* b: p3<<28 | p5<<14 | p7 (unmasked) */ + if (!(b&0x80)) + { + b &= SLOT_4_2_0; + /* moved CSE2 up */ + /* a &= (0x7f<<14)|(0x7f); */ + a = a<<7; + a |= b; + s = s>>4; + *v = ((u64)s)<<32 | a; + return 8; + } + + p++; + a = a<<15; + a |= *p; + /* a: p4<<29 | p6<<15 | p8 (unmasked) */ + + /* moved CSE2 up */ + /* a &= (0x7f<<29)|(0x7f<<15)|(0xff); */ + b &= SLOT_2_0; + b = b<<8; + a |= b; + + s = s<<4; + b = p[-4]; + b &= 0x7f; + b = b>>3; + s |= b; + + *v = ((u64)s)<<32 | a; + + return 9; +} + +/* +** Read a 32-bit variable-length integer from memory starting at p[0]. +** Return the number of bytes read. The value is stored in *v. +** +** If the varint stored in p[0] is larger than can fit in a 32-bit unsigned +** integer, then set *v to 0xffffffff. +** +** A MACRO version, getVarint32, is provided which inlines the +** single-byte case. All code should use the MACRO version as +** this function assumes the single-byte case has already been handled. +*/ +SQLITE_PRIVATE u8 sqlite3GetVarint32(const unsigned char *p, u32 *v){ + u32 a,b; + + /* The 1-byte case. Overwhelmingly the most common. Handled inline + ** by the getVarin32() macro */ + a = *p; + /* a: p0 (unmasked) */ +#ifndef getVarint32 + if (!(a&0x80)) + { + /* Values between 0 and 127 */ + *v = a; + return 1; + } +#endif + + /* The 2-byte case */ + p++; + b = *p; + /* b: p1 (unmasked) */ + if (!(b&0x80)) + { + /* Values between 128 and 16383 */ + a &= 0x7f; + a = a<<7; + *v = a | b; + return 2; + } + + /* The 3-byte case */ + p++; + a = a<<14; + a |= *p; + /* a: p0<<14 | p2 (unmasked) */ + if (!(a&0x80)) + { + /* Values between 16384 and 2097151 */ + a &= (0x7f<<14)|(0x7f); + b &= 0x7f; + b = b<<7; + *v = a | b; + return 3; + } + + /* A 32-bit varint is used to store size information in btrees. + ** Objects are rarely larger than 2MiB limit of a 3-byte varint. + ** A 3-byte varint is sufficient, for example, to record the size + ** of a 1048569-byte BLOB or string. + ** + ** We only unroll the first 1-, 2-, and 3- byte cases. The very + ** rare larger cases can be handled by the slower 64-bit varint + ** routine. + */ +#if 1 + { + u64 v64; + u8 n; + + n = sqlite3GetVarint(p-2, &v64); + assert( n>3 && n<=9 ); + if( (v64 & SQLITE_MAX_U32)!=v64 ){ + *v = 0xffffffff; + }else{ + *v = (u32)v64; + } + return n; + } + +#else + /* For following code (kept for historical record only) shows an + ** unrolling for the 3- and 4-byte varint cases. This code is + ** slightly faster, but it is also larger and much harder to test. + */ + p++; + b = b<<14; + b |= *p; + /* b: p1<<14 | p3 (unmasked) */ + if (!(b&0x80)) + { + /* Values between 2097152 and 268435455 */ + b &= (0x7f<<14)|(0x7f); + a &= (0x7f<<14)|(0x7f); + a = a<<7; + *v = a | b; + return 4; + } + + p++; + a = a<<14; + a |= *p; + /* a: p0<<28 | p2<<14 | p4 (unmasked) */ + if (!(a&0x80)) + { + /* Values between 268435456 and 34359738367 */ + a &= SLOT_4_2_0; + b &= SLOT_4_2_0; + b = b<<7; + *v = a | b; + return 5; + } + + /* We can only reach this point when reading a corrupt database + ** file. In that case we are not in any hurry. Use the (relatively + ** slow) general-purpose sqlite3GetVarint() routine to extract the + ** value. */ + { + u64 v64; + u8 n; + + p -= 4; + n = sqlite3GetVarint(p, &v64); + assert( n>5 && n<=9 ); + *v = (u32)v64; + return n; + } +#endif +} + +/* +** Return the number of bytes that will be needed to store the given +** 64-bit integer. +*/ +SQLITE_PRIVATE int sqlite3VarintLen(u64 v){ + int i; + for(i=1; (v >>= 7)!=0; i++){ assert( i<10 ); } + return i; +} + + +/* +** Read or write a four-byte big-endian integer value. +*/ +SQLITE_PRIVATE u32 sqlite3Get4byte(const u8 *p){ +#if SQLITE_BYTEORDER==4321 + u32 x; + memcpy(&x,p,4); + return x; +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 + u32 x; + memcpy(&x,p,4); + return __builtin_bswap32(x); +#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 + u32 x; + memcpy(&x,p,4); + return _byteswap_ulong(x); +#else + testcase( p[0]&0x80 ); + return ((unsigned)p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3]; +#endif +} +SQLITE_PRIVATE void sqlite3Put4byte(unsigned char *p, u32 v){ +#if SQLITE_BYTEORDER==4321 + memcpy(p,&v,4); +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 + u32 x = __builtin_bswap32(v); + memcpy(p,&x,4); +#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 + u32 x = _byteswap_ulong(v); + memcpy(p,&x,4); +#else + p[0] = (u8)(v>>24); + p[1] = (u8)(v>>16); + p[2] = (u8)(v>>8); + p[3] = (u8)v; +#endif +} + + + +/* +** Translate a single byte of Hex into an integer. +** This routine only works if h really is a valid hexadecimal +** character: 0..9a..fA..F +*/ +SQLITE_PRIVATE u8 sqlite3HexToInt(int h){ + assert( (h>='0' && h<='9') || (h>='a' && h<='f') || (h>='A' && h<='F') ); +#ifdef SQLITE_ASCII + h += 9*(1&(h>>6)); +#endif +#ifdef SQLITE_EBCDIC + h += 9*(1&~(h>>4)); +#endif + return (u8)(h & 0xf); +} + +#if !defined(SQLITE_OMIT_BLOB_LITERAL) +/* +** Convert a BLOB literal of the form "x'hhhhhh'" into its binary +** value. Return a pointer to its binary value. Space to hold the +** binary value has been obtained from malloc and must be freed by +** the calling routine. +*/ +SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3 *db, const char *z, int n){ + char *zBlob; + int i; + + zBlob = (char *)sqlite3DbMallocRawNN(db, n/2 + 1); + n--; + if( zBlob ){ + for(i=0; i<n; i+=2){ + zBlob[i/2] = (sqlite3HexToInt(z[i])<<4) | sqlite3HexToInt(z[i+1]); + } + zBlob[i/2] = 0; + } + return zBlob; +} +#endif /* !SQLITE_OMIT_BLOB_LITERAL */ + +/* +** Log an error that is an API call on a connection pointer that should +** not have been used. The "type" of connection pointer is given as the +** argument. The zType is a word like "NULL" or "closed" or "invalid". +*/ +static void logBadConnection(const char *zType){ + sqlite3_log(SQLITE_MISUSE, + "API call with %s database connection pointer", + zType + ); +} + +/* +** Check to make sure we have a valid db pointer. This test is not +** foolproof but it does provide some measure of protection against +** misuse of the interface such as passing in db pointers that are +** NULL or which have been previously closed. If this routine returns +** 1 it means that the db pointer is valid and 0 if it should not be +** dereferenced for any reason. The calling function should invoke +** SQLITE_MISUSE immediately. +** +** sqlite3SafetyCheckOk() requires that the db pointer be valid for +** use. sqlite3SafetyCheckSickOrOk() allows a db pointer that failed to +** open properly and is not fit for general use but which can be +** used as an argument to sqlite3_errmsg() or sqlite3_close(). +*/ +SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3 *db){ + u8 eOpenState; + if( db==0 ){ + logBadConnection("NULL"); + return 0; + } + eOpenState = db->eOpenState; + if( eOpenState!=SQLITE_STATE_OPEN ){ + if( sqlite3SafetyCheckSickOrOk(db) ){ + testcase( sqlite3GlobalConfig.xLog!=0 ); + logBadConnection("unopened"); + } + return 0; + }else{ + return 1; + } +} +SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3 *db){ + u8 eOpenState; + eOpenState = db->eOpenState; + if( eOpenState!=SQLITE_STATE_SICK && + eOpenState!=SQLITE_STATE_OPEN && + eOpenState!=SQLITE_STATE_BUSY ){ + testcase( sqlite3GlobalConfig.xLog!=0 ); + logBadConnection("invalid"); + return 0; + }else{ + return 1; + } +} + +/* +** Attempt to add, subtract, or multiply the 64-bit signed value iB against +** the other 64-bit signed integer at *pA and store the result in *pA. +** Return 0 on success. Or if the operation would have resulted in an +** overflow, leave *pA unchanged and return 1. +*/ +SQLITE_PRIVATE int sqlite3AddInt64(i64 *pA, i64 iB){ +#if GCC_VERSION>=5004000 && !defined(__INTEL_COMPILER) + return __builtin_add_overflow(*pA, iB, pA); +#else + i64 iA = *pA; + testcase( iA==0 ); testcase( iA==1 ); + testcase( iB==-1 ); testcase( iB==0 ); + if( iB>=0 ){ + testcase( iA>0 && LARGEST_INT64 - iA == iB ); + testcase( iA>0 && LARGEST_INT64 - iA == iB - 1 ); + if( iA>0 && LARGEST_INT64 - iA < iB ) return 1; + }else{ + testcase( iA<0 && -(iA + LARGEST_INT64) == iB + 1 ); + testcase( iA<0 && -(iA + LARGEST_INT64) == iB + 2 ); + if( iA<0 && -(iA + LARGEST_INT64) > iB + 1 ) return 1; + } + *pA += iB; + return 0; +#endif +} +SQLITE_PRIVATE int sqlite3SubInt64(i64 *pA, i64 iB){ +#if GCC_VERSION>=5004000 && !defined(__INTEL_COMPILER) + return __builtin_sub_overflow(*pA, iB, pA); +#else + testcase( iB==SMALLEST_INT64+1 ); + if( iB==SMALLEST_INT64 ){ + testcase( (*pA)==(-1) ); testcase( (*pA)==0 ); + if( (*pA)>=0 ) return 1; + *pA -= iB; + return 0; + }else{ + return sqlite3AddInt64(pA, -iB); + } +#endif +} +SQLITE_PRIVATE int sqlite3MulInt64(i64 *pA, i64 iB){ +#if GCC_VERSION>=5004000 && !defined(__INTEL_COMPILER) + return __builtin_mul_overflow(*pA, iB, pA); +#else + i64 iA = *pA; + if( iB>0 ){ + if( iA>LARGEST_INT64/iB ) return 1; + if( iA<SMALLEST_INT64/iB ) return 1; + }else if( iB<0 ){ + if( iA>0 ){ + if( iB<SMALLEST_INT64/iA ) return 1; + }else if( iA<0 ){ + if( iB==SMALLEST_INT64 ) return 1; + if( iA==SMALLEST_INT64 ) return 1; + if( -iA>LARGEST_INT64/-iB ) return 1; + } + } + *pA = iA*iB; + return 0; +#endif +} + +/* +** Compute the absolute value of a 32-bit signed integer, of possible. Or +** if the integer has a value of -2147483648, return +2147483647 +*/ +SQLITE_PRIVATE int sqlite3AbsInt32(int x){ + if( x>=0 ) return x; + if( x==(int)0x80000000 ) return 0x7fffffff; + return -x; +} + +#ifdef SQLITE_ENABLE_8_3_NAMES +/* +** If SQLITE_ENABLE_8_3_NAMES is set at compile-time and if the database +** filename in zBaseFilename is a URI with the "8_3_names=1" parameter and +** if filename in z[] has a suffix (a.k.a. "extension") that is longer than +** three characters, then shorten the suffix on z[] to be the last three +** characters of the original suffix. +** +** If SQLITE_ENABLE_8_3_NAMES is set to 2 at compile-time, then always +** do the suffix shortening regardless of URI parameter. +** +** Examples: +** +** test.db-journal => test.nal +** test.db-wal => test.wal +** test.db-shm => test.shm +** test.db-mj7f3319fa => test.9fa +*/ +SQLITE_PRIVATE void sqlite3FileSuffix3(const char *zBaseFilename, char *z){ +#if SQLITE_ENABLE_8_3_NAMES<2 + if( sqlite3_uri_boolean(zBaseFilename, "8_3_names", 0) ) +#endif + { + int i, sz; + sz = sqlite3Strlen30(z); + for(i=sz-1; i>0 && z[i]!='/' && z[i]!='.'; i--){} + if( z[i]=='.' && ALWAYS(sz>i+4) ) memmove(&z[i+1], &z[sz-3], 4); + } +} +#endif + +/* +** Find (an approximate) sum of two LogEst values. This computation is +** not a simple "+" operator because LogEst is stored as a logarithmic +** value. +** +*/ +SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst a, LogEst b){ + static const unsigned char x[] = { + 10, 10, /* 0,1 */ + 9, 9, /* 2,3 */ + 8, 8, /* 4,5 */ + 7, 7, 7, /* 6,7,8 */ + 6, 6, 6, /* 9,10,11 */ + 5, 5, 5, /* 12-14 */ + 4, 4, 4, 4, /* 15-18 */ + 3, 3, 3, 3, 3, 3, /* 19-24 */ + 2, 2, 2, 2, 2, 2, 2, /* 25-31 */ + }; + if( a>=b ){ + if( a>b+49 ) return a; + if( a>b+31 ) return a+1; + return a+x[a-b]; + }else{ + if( b>a+49 ) return b; + if( b>a+31 ) return b+1; + return b+x[b-a]; + } +} + +/* +** Convert an integer into a LogEst. In other words, compute an +** approximation for 10*log2(x). +*/ +SQLITE_PRIVATE LogEst sqlite3LogEst(u64 x){ + static LogEst a[] = { 0, 2, 3, 5, 6, 7, 8, 9 }; + LogEst y = 40; + if( x<8 ){ + if( x<2 ) return 0; + while( x<8 ){ y -= 10; x <<= 1; } + }else{ +#if GCC_VERSION>=5004000 + int i = 60 - __builtin_clzll(x); + y += i*10; + x >>= i; +#else + while( x>255 ){ y += 40; x >>= 4; } /*OPTIMIZATION-IF-TRUE*/ + while( x>15 ){ y += 10; x >>= 1; } +#endif + } + return a[x&7] + y - 10; +} + +/* +** Convert a double into a LogEst +** In other words, compute an approximation for 10*log2(x). +*/ +SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double x){ + u64 a; + LogEst e; + assert( sizeof(x)==8 && sizeof(a)==8 ); + if( x<=1 ) return 0; + if( x<=2000000000 ) return sqlite3LogEst((u64)x); + memcpy(&a, &x, 8); + e = (a>>52) - 1022; + return e*10; +} + +/* +** Convert a LogEst into an integer. +*/ +SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){ + u64 n; + n = x%10; + x /= 10; + if( n>=5 ) n -= 2; + else if( n>=1 ) n -= 1; + if( x>60 ) return (u64)LARGEST_INT64; + return x>=3 ? (n+8)<<(x-3) : (n+8)>>(3-x); +} + +/* +** Add a new name/number pair to a VList. This might require that the +** VList object be reallocated, so return the new VList. If an OOM +** error occurs, the original VList returned and the +** db->mallocFailed flag is set. +** +** A VList is really just an array of integers. To destroy a VList, +** simply pass it to sqlite3DbFree(). +** +** The first integer is the number of integers allocated for the whole +** VList. The second integer is the number of integers actually used. +** Each name/number pair is encoded by subsequent groups of 3 or more +** integers. +** +** Each name/number pair starts with two integers which are the numeric +** value for the pair and the size of the name/number pair, respectively. +** The text name overlays one or more following integers. The text name +** is always zero-terminated. +** +** Conceptually: +** +** struct VList { +** int nAlloc; // Number of allocated slots +** int nUsed; // Number of used slots +** struct VListEntry { +** int iValue; // Value for this entry +** int nSlot; // Slots used by this entry +** // ... variable name goes here +** } a[0]; +** } +** +** During code generation, pointers to the variable names within the +** VList are taken. When that happens, nAlloc is set to zero as an +** indication that the VList may never again be enlarged, since the +** accompanying realloc() would invalidate the pointers. +*/ +SQLITE_PRIVATE VList *sqlite3VListAdd( + sqlite3 *db, /* The database connection used for malloc() */ + VList *pIn, /* The input VList. Might be NULL */ + const char *zName, /* Name of symbol to add */ + int nName, /* Bytes of text in zName */ + int iVal /* Value to associate with zName */ +){ + int nInt; /* number of sizeof(int) objects needed for zName */ + char *z; /* Pointer to where zName will be stored */ + int i; /* Index in pIn[] where zName is stored */ + + nInt = nName/4 + 3; + assert( pIn==0 || pIn[0]>=3 ); /* Verify ok to add new elements */ + if( pIn==0 || pIn[1]+nInt > pIn[0] ){ + /* Enlarge the allocation */ + sqlite3_int64 nAlloc = (pIn ? 2*(sqlite3_int64)pIn[0] : 10) + nInt; + VList *pOut = sqlite3DbRealloc(db, pIn, nAlloc*sizeof(int)); + if( pOut==0 ) return pIn; + if( pIn==0 ) pOut[1] = 2; + pIn = pOut; + pIn[0] = nAlloc; + } + i = pIn[1]; + pIn[i] = iVal; + pIn[i+1] = nInt; + z = (char*)&pIn[i+2]; + pIn[1] = i+nInt; + assert( pIn[1]<=pIn[0] ); + memcpy(z, zName, nName); + z[nName] = 0; + return pIn; +} + +/* +** Return a pointer to the name of a variable in the given VList that +** has the value iVal. Or return a NULL if there is no such variable in +** the list +*/ +SQLITE_PRIVATE const char *sqlite3VListNumToName(VList *pIn, int iVal){ + int i, mx; + if( pIn==0 ) return 0; + mx = pIn[1]; + i = 2; + do{ + if( pIn[i]==iVal ) return (char*)&pIn[i+2]; + i += pIn[i+1]; + }while( i<mx ); + return 0; +} + +/* +** Return the number of the variable named zName, if it is in VList. +** or return 0 if there is no such variable. +*/ +SQLITE_PRIVATE int sqlite3VListNameToNum(VList *pIn, const char *zName, int nName){ + int i, mx; + if( pIn==0 ) return 0; + mx = pIn[1]; + i = 2; + do{ + const char *z = (const char*)&pIn[i+2]; + if( strncmp(z,zName,nName)==0 && z[nName]==0 ) return pIn[i]; + i += pIn[i+1]; + }while( i<mx ); + return 0; +} + +/* +** High-resolution hardware timer used for debugging and testing only. +*/ +#if defined(VDBE_PROFILE) \ + || defined(SQLITE_PERFORMANCE_TRACE) \ + || defined(SQLITE_ENABLE_STMT_SCANSTATUS) +/************** Include hwtime.h in the middle of util.c *********************/ +/************** Begin file hwtime.h ******************************************/ +/* +** 2008 May 27 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains inline asm code for retrieving "high-performance" +** counters for x86 and x86_64 class CPUs. +*/ +#ifndef SQLITE_HWTIME_H +#define SQLITE_HWTIME_H + +/* +** The following routine only works on Pentium-class (or newer) processors. +** It uses the RDTSC opcode to read the cycle count value out of the +** processor and returns that value. This can be used for high-res +** profiling. +*/ +#if !defined(__STRICT_ANSI__) && \ + (defined(__GNUC__) || defined(_MSC_VER)) && \ + (defined(i386) || defined(__i386__) || defined(_M_IX86)) + + #if defined(__GNUC__) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned int lo, hi; + __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); + return (sqlite_uint64)hi << 32 | lo; + } + + #elif defined(_MSC_VER) + + __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ + __asm { + rdtsc + ret ; return value at EDX:EAX + } + } + + #endif + +#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned int lo, hi; + __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); + return (sqlite_uint64)hi << 32 | lo; + } + +#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned long long retval; + unsigned long junk; + __asm__ __volatile__ ("\n\ + 1: mftbu %1\n\ + mftb %L0\n\ + mftbu %0\n\ + cmpw %0,%1\n\ + bne 1b" + : "=r" (retval), "=r" (junk)); + return retval; + } + +#else + + /* + ** asm() is needed for hardware timing support. Without asm(), + ** disable the sqlite3Hwtime() routine. + ** + ** sqlite3Hwtime() is only used for some obscure debugging + ** and analysis configurations, not in any deliverable, so this + ** should not be a great loss. + */ +SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } + +#endif + +#endif /* !defined(SQLITE_HWTIME_H) */ + +/************** End of hwtime.h **********************************************/ +/************** Continuing where we left off in util.c ***********************/ +#endif + +/************** End of util.c ************************************************/ +/************** Begin file hash.c ********************************************/ +/* +** 2001 September 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the implementation of generic hash-tables +** used in SQLite. +*/ +/* #include "sqliteInt.h" */ +/* #include <assert.h> */ + +/* Turn bulk memory into a hash table object by initializing the +** fields of the Hash structure. +** +** "pNew" is a pointer to the hash table that is to be initialized. +*/ +SQLITE_PRIVATE void sqlite3HashInit(Hash *pNew){ + assert( pNew!=0 ); + pNew->first = 0; + pNew->count = 0; + pNew->htsize = 0; + pNew->ht = 0; +} + +/* Remove all entries from a hash table. Reclaim all memory. +** Call this routine to delete a hash table or to reset a hash table +** to the empty state. +*/ +SQLITE_PRIVATE void sqlite3HashClear(Hash *pH){ + HashElem *elem; /* For looping over all elements of the table */ + + assert( pH!=0 ); + elem = pH->first; + pH->first = 0; + sqlite3_free(pH->ht); + pH->ht = 0; + pH->htsize = 0; + while( elem ){ + HashElem *next_elem = elem->next; + sqlite3_free(elem); + elem = next_elem; + } + pH->count = 0; +} + +/* +** The hashing function. +*/ +static unsigned int strHash(const char *z){ + unsigned int h = 0; + unsigned char c; + while( (c = (unsigned char)*z++)!=0 ){ /*OPTIMIZATION-IF-TRUE*/ + /* Knuth multiplicative hashing. (Sorting & Searching, p. 510). + ** 0x9e3779b1 is 2654435761 which is the closest prime number to + ** (2**32)*golden_ratio, where golden_ratio = (sqrt(5) - 1)/2. */ + h += sqlite3UpperToLower[c]; + h *= 0x9e3779b1; + } + return h; +} + + +/* Link pNew element into the hash table pH. If pEntry!=0 then also +** insert pNew into the pEntry hash bucket. +*/ +static void insertElement( + Hash *pH, /* The complete hash table */ + struct _ht *pEntry, /* The entry into which pNew is inserted */ + HashElem *pNew /* The element to be inserted */ +){ + HashElem *pHead; /* First element already in pEntry */ + if( pEntry ){ + pHead = pEntry->count ? pEntry->chain : 0; + pEntry->count++; + pEntry->chain = pNew; + }else{ + pHead = 0; + } + if( pHead ){ + pNew->next = pHead; + pNew->prev = pHead->prev; + if( pHead->prev ){ pHead->prev->next = pNew; } + else { pH->first = pNew; } + pHead->prev = pNew; + }else{ + pNew->next = pH->first; + if( pH->first ){ pH->first->prev = pNew; } + pNew->prev = 0; + pH->first = pNew; + } +} + + +/* Resize the hash table so that it contains "new_size" buckets. +** +** The hash table might fail to resize if sqlite3_malloc() fails or +** if the new size is the same as the prior size. +** Return TRUE if the resize occurs and false if not. +*/ +static int rehash(Hash *pH, unsigned int new_size){ + struct _ht *new_ht; /* The new hash table */ + HashElem *elem, *next_elem; /* For looping over existing elements */ + +#if SQLITE_MALLOC_SOFT_LIMIT>0 + if( new_size*sizeof(struct _ht)>SQLITE_MALLOC_SOFT_LIMIT ){ + new_size = SQLITE_MALLOC_SOFT_LIMIT/sizeof(struct _ht); + } + if( new_size==pH->htsize ) return 0; +#endif + + /* The inability to allocates space for a larger hash table is + ** a performance hit but it is not a fatal error. So mark the + ** allocation as a benign. Use sqlite3Malloc()/memset(0) instead of + ** sqlite3MallocZero() to make the allocation, as sqlite3MallocZero() + ** only zeroes the requested number of bytes whereas this module will + ** use the actual amount of space allocated for the hash table (which + ** may be larger than the requested amount). + */ + sqlite3BeginBenignMalloc(); + new_ht = (struct _ht *)sqlite3Malloc( new_size*sizeof(struct _ht) ); + sqlite3EndBenignMalloc(); + + if( new_ht==0 ) return 0; + sqlite3_free(pH->ht); + pH->ht = new_ht; + pH->htsize = new_size = sqlite3MallocSize(new_ht)/sizeof(struct _ht); + memset(new_ht, 0, new_size*sizeof(struct _ht)); + for(elem=pH->first, pH->first=0; elem; elem = next_elem){ + unsigned int h = strHash(elem->pKey) % new_size; + next_elem = elem->next; + insertElement(pH, &new_ht[h], elem); + } + return 1; +} + +/* This function (for internal use only) locates an element in an +** hash table that matches the given key. If no element is found, +** a pointer to a static null element with HashElem.data==0 is returned. +** If pH is not NULL, then the hash for this key is written to *pH. +*/ +static HashElem *findElementWithHash( + const Hash *pH, /* The pH to be searched */ + const char *pKey, /* The key we are searching for */ + unsigned int *pHash /* Write the hash value here */ +){ + HashElem *elem; /* Used to loop thru the element list */ + unsigned int count; /* Number of elements left to test */ + unsigned int h; /* The computed hash */ + static HashElem nullElement = { 0, 0, 0, 0 }; + + if( pH->ht ){ /*OPTIMIZATION-IF-TRUE*/ + struct _ht *pEntry; + h = strHash(pKey) % pH->htsize; + pEntry = &pH->ht[h]; + elem = pEntry->chain; + count = pEntry->count; + }else{ + h = 0; + elem = pH->first; + count = pH->count; + } + if( pHash ) *pHash = h; + while( count ){ + assert( elem!=0 ); + if( sqlite3StrICmp(elem->pKey,pKey)==0 ){ + return elem; + } + elem = elem->next; + count--; + } + return &nullElement; +} + +/* Remove a single entry from the hash table given a pointer to that +** element and a hash on the element's key. +*/ +static void removeElementGivenHash( + Hash *pH, /* The pH containing "elem" */ + HashElem* elem, /* The element to be removed from the pH */ + unsigned int h /* Hash value for the element */ +){ + struct _ht *pEntry; + if( elem->prev ){ + elem->prev->next = elem->next; + }else{ + pH->first = elem->next; + } + if( elem->next ){ + elem->next->prev = elem->prev; + } + if( pH->ht ){ + pEntry = &pH->ht[h]; + if( pEntry->chain==elem ){ + pEntry->chain = elem->next; + } + assert( pEntry->count>0 ); + pEntry->count--; + } + sqlite3_free( elem ); + pH->count--; + if( pH->count==0 ){ + assert( pH->first==0 ); + assert( pH->count==0 ); + sqlite3HashClear(pH); + } +} + +/* Attempt to locate an element of the hash table pH with a key +** that matches pKey. Return the data for this element if it is +** found, or NULL if there is no match. +*/ +SQLITE_PRIVATE void *sqlite3HashFind(const Hash *pH, const char *pKey){ + assert( pH!=0 ); + assert( pKey!=0 ); + return findElementWithHash(pH, pKey, 0)->data; +} + +/* Insert an element into the hash table pH. The key is pKey +** and the data is "data". +** +** If no element exists with a matching key, then a new +** element is created and NULL is returned. +** +** If another element already exists with the same key, then the +** new data replaces the old data and the old data is returned. +** The key is not copied in this instance. If a malloc fails, then +** the new data is returned and the hash table is unchanged. +** +** If the "data" parameter to this function is NULL, then the +** element corresponding to "key" is removed from the hash table. +*/ +SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){ + unsigned int h; /* the hash of the key modulo hash table size */ + HashElem *elem; /* Used to loop thru the element list */ + HashElem *new_elem; /* New element added to the pH */ + + assert( pH!=0 ); + assert( pKey!=0 ); + elem = findElementWithHash(pH,pKey,&h); + if( elem->data ){ + void *old_data = elem->data; + if( data==0 ){ + removeElementGivenHash(pH,elem,h); + }else{ + elem->data = data; + elem->pKey = pKey; + } + return old_data; + } + if( data==0 ) return 0; + new_elem = (HashElem*)sqlite3Malloc( sizeof(HashElem) ); + if( new_elem==0 ) return data; + new_elem->pKey = pKey; + new_elem->data = data; + pH->count++; + if( pH->count>=10 && pH->count > 2*pH->htsize ){ + if( rehash(pH, pH->count*2) ){ + assert( pH->htsize>0 ); + h = strHash(pKey) % pH->htsize; + } + } + insertElement(pH, pH->ht ? &pH->ht[h] : 0, new_elem); + return 0; +} + +/************** End of hash.c ************************************************/ +/************** Begin file opcodes.c *****************************************/ +/* Automatically generated. Do not edit */ +/* See the tool/mkopcodec.tcl script for details. */ +#if !defined(SQLITE_OMIT_EXPLAIN) \ + || defined(VDBE_PROFILE) \ + || defined(SQLITE_DEBUG) +#if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) || defined(SQLITE_DEBUG) +# define OpHelp(X) "\0" X +#else +# define OpHelp(X) +#endif +SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ + static const char *const azName[] = { + /* 0 */ "Savepoint" OpHelp(""), + /* 1 */ "AutoCommit" OpHelp(""), + /* 2 */ "Transaction" OpHelp(""), + /* 3 */ "Checkpoint" OpHelp(""), + /* 4 */ "JournalMode" OpHelp(""), + /* 5 */ "Vacuum" OpHelp(""), + /* 6 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"), + /* 7 */ "VUpdate" OpHelp("data=r[P3@P2]"), + /* 8 */ "Init" OpHelp("Start at P2"), + /* 9 */ "Goto" OpHelp(""), + /* 10 */ "Gosub" OpHelp(""), + /* 11 */ "InitCoroutine" OpHelp(""), + /* 12 */ "Yield" OpHelp(""), + /* 13 */ "MustBeInt" OpHelp(""), + /* 14 */ "Jump" OpHelp(""), + /* 15 */ "Once" OpHelp(""), + /* 16 */ "If" OpHelp(""), + /* 17 */ "IfNot" OpHelp(""), + /* 18 */ "IsType" OpHelp("if typeof(P1.P3) in P5 goto P2"), + /* 19 */ "Not" OpHelp("r[P2]= !r[P1]"), + /* 20 */ "IfNullRow" OpHelp("if P1.nullRow then r[P3]=NULL, goto P2"), + /* 21 */ "SeekLT" OpHelp("key=r[P3@P4]"), + /* 22 */ "SeekLE" OpHelp("key=r[P3@P4]"), + /* 23 */ "SeekGE" OpHelp("key=r[P3@P4]"), + /* 24 */ "SeekGT" OpHelp("key=r[P3@P4]"), + /* 25 */ "IfNotOpen" OpHelp("if( !csr[P1] ) goto P2"), + /* 26 */ "IfNoHope" OpHelp("key=r[P3@P4]"), + /* 27 */ "NoConflict" OpHelp("key=r[P3@P4]"), + /* 28 */ "NotFound" OpHelp("key=r[P3@P4]"), + /* 29 */ "Found" OpHelp("key=r[P3@P4]"), + /* 30 */ "SeekRowid" OpHelp("intkey=r[P3]"), + /* 31 */ "NotExists" OpHelp("intkey=r[P3]"), + /* 32 */ "Last" OpHelp(""), + /* 33 */ "IfSmaller" OpHelp(""), + /* 34 */ "SorterSort" OpHelp(""), + /* 35 */ "Sort" OpHelp(""), + /* 36 */ "Rewind" OpHelp(""), + /* 37 */ "SorterNext" OpHelp(""), + /* 38 */ "Prev" OpHelp(""), + /* 39 */ "Next" OpHelp(""), + /* 40 */ "IdxLE" OpHelp("key=r[P3@P4]"), + /* 41 */ "IdxGT" OpHelp("key=r[P3@P4]"), + /* 42 */ "IdxLT" OpHelp("key=r[P3@P4]"), + /* 43 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"), + /* 44 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"), + /* 45 */ "IdxGE" OpHelp("key=r[P3@P4]"), + /* 46 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), + /* 47 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), + /* 48 */ "Program" OpHelp(""), + /* 49 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), + /* 50 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"), + /* 51 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"), + /* 52 */ "Ne" OpHelp("IF r[P3]!=r[P1]"), + /* 53 */ "Eq" OpHelp("IF r[P3]==r[P1]"), + /* 54 */ "Gt" OpHelp("IF r[P3]>r[P1]"), + /* 55 */ "Le" OpHelp("IF r[P3]<=r[P1]"), + /* 56 */ "Lt" OpHelp("IF r[P3]<r[P1]"), + /* 57 */ "Ge" OpHelp("IF r[P3]>=r[P1]"), + /* 58 */ "ElseEq" OpHelp(""), + /* 59 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), + /* 60 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"), + /* 61 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), + /* 62 */ "IncrVacuum" OpHelp(""), + /* 63 */ "VNext" OpHelp(""), + /* 64 */ "Filter" OpHelp("if key(P3@P4) not in filter(P1) goto P2"), + /* 65 */ "PureFunc" OpHelp("r[P3]=func(r[P2@NP])"), + /* 66 */ "Function" OpHelp("r[P3]=func(r[P2@NP])"), + /* 67 */ "Return" OpHelp(""), + /* 68 */ "EndCoroutine" OpHelp(""), + /* 69 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), + /* 70 */ "Halt" OpHelp(""), + /* 71 */ "Integer" OpHelp("r[P2]=P1"), + /* 72 */ "Int64" OpHelp("r[P2]=P4"), + /* 73 */ "String" OpHelp("r[P2]='P4' (len=P1)"), + /* 74 */ "BeginSubrtn" OpHelp("r[P2]=NULL"), + /* 75 */ "Null" OpHelp("r[P2..P3]=NULL"), + /* 76 */ "SoftNull" OpHelp("r[P1]=NULL"), + /* 77 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), + /* 78 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), + /* 79 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), + /* 80 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), + /* 81 */ "SCopy" OpHelp("r[P2]=r[P1]"), + /* 82 */ "IntCopy" OpHelp("r[P2]=r[P1]"), + /* 83 */ "FkCheck" OpHelp(""), + /* 84 */ "ResultRow" OpHelp("output=r[P1@P2]"), + /* 85 */ "CollSeq" OpHelp(""), + /* 86 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), + /* 87 */ "RealAffinity" OpHelp(""), + /* 88 */ "Cast" OpHelp("affinity(r[P1])"), + /* 89 */ "Permutation" OpHelp(""), + /* 90 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), + /* 91 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"), + /* 92 */ "ZeroOrNull" OpHelp("r[P2] = 0 OR NULL"), + /* 93 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"), + /* 94 */ "Column" OpHelp("r[P3]=PX cursor P1 column P2"), + /* 95 */ "TypeCheck" OpHelp("typecheck(r[P1@P2])"), + /* 96 */ "Affinity" OpHelp("affinity(r[P1@P2])"), + /* 97 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), + /* 98 */ "Count" OpHelp("r[P2]=count()"), + /* 99 */ "ReadCookie" OpHelp(""), + /* 100 */ "SetCookie" OpHelp(""), + /* 101 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), + /* 102 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"), + /* 103 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"), + /* 104 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<<r[P1]"), + /* 105 */ "ShiftRight" OpHelp("r[P3]=r[P2]>>r[P1]"), + /* 106 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"), + /* 107 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"), + /* 108 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"), + /* 109 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"), + /* 110 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"), + /* 111 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"), + /* 112 */ "OpenRead" OpHelp("root=P2 iDb=P3"), + /* 113 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), + /* 114 */ "BitNot" OpHelp("r[P2]= ~r[P1]"), + /* 115 */ "OpenDup" OpHelp(""), + /* 116 */ "OpenAutoindex" OpHelp("nColumn=P2"), + /* 117 */ "String8" OpHelp("r[P2]='P4'"), + /* 118 */ "OpenEphemeral" OpHelp("nColumn=P2"), + /* 119 */ "SorterOpen" OpHelp(""), + /* 120 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), + /* 121 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), + /* 122 */ "Close" OpHelp(""), + /* 123 */ "ColumnsUsed" OpHelp(""), + /* 124 */ "SeekScan" OpHelp("Scan-ahead up to P1 rows"), + /* 125 */ "SeekHit" OpHelp("set P2<=seekHit<=P3"), + /* 126 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), + /* 127 */ "NewRowid" OpHelp("r[P2]=rowid"), + /* 128 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), + /* 129 */ "RowCell" OpHelp(""), + /* 130 */ "Delete" OpHelp(""), + /* 131 */ "ResetCount" OpHelp(""), + /* 132 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), + /* 133 */ "SorterData" OpHelp("r[P2]=data"), + /* 134 */ "RowData" OpHelp("r[P2]=data"), + /* 135 */ "Rowid" OpHelp("r[P2]=PX rowid of P1"), + /* 136 */ "NullRow" OpHelp(""), + /* 137 */ "SeekEnd" OpHelp(""), + /* 138 */ "IdxInsert" OpHelp("key=r[P2]"), + /* 139 */ "SorterInsert" OpHelp("key=r[P2]"), + /* 140 */ "IdxDelete" OpHelp("key=r[P2@P3]"), + /* 141 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"), + /* 142 */ "IdxRowid" OpHelp("r[P2]=rowid"), + /* 143 */ "FinishSeek" OpHelp(""), + /* 144 */ "Destroy" OpHelp(""), + /* 145 */ "Clear" OpHelp(""), + /* 146 */ "ResetSorter" OpHelp(""), + /* 147 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"), + /* 148 */ "SqlExec" OpHelp(""), + /* 149 */ "ParseSchema" OpHelp(""), + /* 150 */ "LoadAnalysis" OpHelp(""), + /* 151 */ "DropTable" OpHelp(""), + /* 152 */ "DropIndex" OpHelp(""), + /* 153 */ "Real" OpHelp("r[P2]=P4"), + /* 154 */ "DropTrigger" OpHelp(""), + /* 155 */ "IntegrityCk" OpHelp(""), + /* 156 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), + /* 157 */ "Param" OpHelp(""), + /* 158 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), + /* 159 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), + /* 160 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), + /* 161 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"), + /* 162 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 163 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 164 */ "AggValue" OpHelp("r[P3]=value N=P2"), + /* 165 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), + /* 166 */ "Expire" OpHelp(""), + /* 167 */ "CursorLock" OpHelp(""), + /* 168 */ "CursorUnlock" OpHelp(""), + /* 169 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), + /* 170 */ "VBegin" OpHelp(""), + /* 171 */ "VCreate" OpHelp(""), + /* 172 */ "VDestroy" OpHelp(""), + /* 173 */ "VOpen" OpHelp(""), + /* 174 */ "VCheck" OpHelp(""), + /* 175 */ "VInitIn" OpHelp("r[P2]=ValueList(P1,P3)"), + /* 176 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), + /* 177 */ "VRename" OpHelp(""), + /* 178 */ "Pagecount" OpHelp(""), + /* 179 */ "MaxPgcnt" OpHelp(""), + /* 180 */ "ClrSubtype" OpHelp("r[P1].subtype = 0"), + /* 181 */ "FilterAdd" OpHelp("filter(P1) += key(P3@P4)"), + /* 182 */ "Trace" OpHelp(""), + /* 183 */ "CursorHint" OpHelp(""), + /* 184 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"), + /* 185 */ "Noop" OpHelp(""), + /* 186 */ "Explain" OpHelp(""), + /* 187 */ "Abortable" OpHelp(""), + }; + return azName[i]; +} +#endif + +/************** End of opcodes.c *********************************************/ +/************** Begin file os_kv.c *******************************************/ +/* +** 2022-09-06 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains an experimental VFS layer that operates on a +** Key/Value storage engine where both keys and values must be pure +** text. +*/ +/* #include <sqliteInt.h> */ +#if SQLITE_OS_KV || (SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL)) + +/***************************************************************************** +** Debugging logic +*/ + +/* SQLITE_KV_TRACE() is used for tracing calls to kvstorage routines. */ +#if 0 +#define SQLITE_KV_TRACE(X) printf X +#else +#define SQLITE_KV_TRACE(X) +#endif + +/* SQLITE_KV_LOG() is used for tracing calls to the VFS interface */ +#if 0 +#define SQLITE_KV_LOG(X) printf X +#else +#define SQLITE_KV_LOG(X) +#endif + + +/* +** Forward declaration of objects used by this VFS implementation +*/ +typedef struct KVVfsFile KVVfsFile; + +/* A single open file. There are only two files represented by this +** VFS - the database and the rollback journal. +*/ +struct KVVfsFile { + sqlite3_file base; /* IO methods */ + const char *zClass; /* Storage class */ + int isJournal; /* True if this is a journal file */ + unsigned int nJrnl; /* Space allocated for aJrnl[] */ + char *aJrnl; /* Journal content */ + int szPage; /* Last known page size */ + sqlite3_int64 szDb; /* Database file size. -1 means unknown */ + char *aData; /* Buffer to hold page data */ +}; +#define SQLITE_KVOS_SZ 133073 + +/* +** Methods for KVVfsFile +*/ +static int kvvfsClose(sqlite3_file*); +static int kvvfsReadDb(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int kvvfsReadJrnl(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int kvvfsWriteDb(sqlite3_file*,const void*,int iAmt, sqlite3_int64); +static int kvvfsWriteJrnl(sqlite3_file*,const void*,int iAmt, sqlite3_int64); +static int kvvfsTruncateDb(sqlite3_file*, sqlite3_int64 size); +static int kvvfsTruncateJrnl(sqlite3_file*, sqlite3_int64 size); +static int kvvfsSyncDb(sqlite3_file*, int flags); +static int kvvfsSyncJrnl(sqlite3_file*, int flags); +static int kvvfsFileSizeDb(sqlite3_file*, sqlite3_int64 *pSize); +static int kvvfsFileSizeJrnl(sqlite3_file*, sqlite3_int64 *pSize); +static int kvvfsLock(sqlite3_file*, int); +static int kvvfsUnlock(sqlite3_file*, int); +static int kvvfsCheckReservedLock(sqlite3_file*, int *pResOut); +static int kvvfsFileControlDb(sqlite3_file*, int op, void *pArg); +static int kvvfsFileControlJrnl(sqlite3_file*, int op, void *pArg); +static int kvvfsSectorSize(sqlite3_file*); +static int kvvfsDeviceCharacteristics(sqlite3_file*); + +/* +** Methods for sqlite3_vfs +*/ +static int kvvfsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); +static int kvvfsDelete(sqlite3_vfs*, const char *zName, int syncDir); +static int kvvfsAccess(sqlite3_vfs*, const char *zName, int flags, int *); +static int kvvfsFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); +static void *kvvfsDlOpen(sqlite3_vfs*, const char *zFilename); +static int kvvfsRandomness(sqlite3_vfs*, int nByte, char *zOut); +static int kvvfsSleep(sqlite3_vfs*, int microseconds); +static int kvvfsCurrentTime(sqlite3_vfs*, double*); +static int kvvfsCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); + +static sqlite3_vfs sqlite3OsKvvfsObject = { + 1, /* iVersion */ + sizeof(KVVfsFile), /* szOsFile */ + 1024, /* mxPathname */ + 0, /* pNext */ + "kvvfs", /* zName */ + 0, /* pAppData */ + kvvfsOpen, /* xOpen */ + kvvfsDelete, /* xDelete */ + kvvfsAccess, /* xAccess */ + kvvfsFullPathname, /* xFullPathname */ + kvvfsDlOpen, /* xDlOpen */ + 0, /* xDlError */ + 0, /* xDlSym */ + 0, /* xDlClose */ + kvvfsRandomness, /* xRandomness */ + kvvfsSleep, /* xSleep */ + kvvfsCurrentTime, /* xCurrentTime */ + 0, /* xGetLastError */ + kvvfsCurrentTimeInt64 /* xCurrentTimeInt64 */ +}; + +/* Methods for sqlite3_file objects referencing a database file +*/ +static sqlite3_io_methods kvvfs_db_io_methods = { + 1, /* iVersion */ + kvvfsClose, /* xClose */ + kvvfsReadDb, /* xRead */ + kvvfsWriteDb, /* xWrite */ + kvvfsTruncateDb, /* xTruncate */ + kvvfsSyncDb, /* xSync */ + kvvfsFileSizeDb, /* xFileSize */ + kvvfsLock, /* xLock */ + kvvfsUnlock, /* xUnlock */ + kvvfsCheckReservedLock, /* xCheckReservedLock */ + kvvfsFileControlDb, /* xFileControl */ + kvvfsSectorSize, /* xSectorSize */ + kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */ + 0, /* xShmMap */ + 0, /* xShmLock */ + 0, /* xShmBarrier */ + 0, /* xShmUnmap */ + 0, /* xFetch */ + 0 /* xUnfetch */ +}; + +/* Methods for sqlite3_file objects referencing a rollback journal +*/ +static sqlite3_io_methods kvvfs_jrnl_io_methods = { + 1, /* iVersion */ + kvvfsClose, /* xClose */ + kvvfsReadJrnl, /* xRead */ + kvvfsWriteJrnl, /* xWrite */ + kvvfsTruncateJrnl, /* xTruncate */ + kvvfsSyncJrnl, /* xSync */ + kvvfsFileSizeJrnl, /* xFileSize */ + kvvfsLock, /* xLock */ + kvvfsUnlock, /* xUnlock */ + kvvfsCheckReservedLock, /* xCheckReservedLock */ + kvvfsFileControlJrnl, /* xFileControl */ + kvvfsSectorSize, /* xSectorSize */ + kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */ + 0, /* xShmMap */ + 0, /* xShmLock */ + 0, /* xShmBarrier */ + 0, /* xShmUnmap */ + 0, /* xFetch */ + 0 /* xUnfetch */ +}; + +/****** Storage subsystem **************************************************/ +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +/* Forward declarations for the low-level storage engine +*/ +static int kvstorageWrite(const char*, const char *zKey, const char *zData); +static int kvstorageDelete(const char*, const char *zKey); +static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf); +#define KVSTORAGE_KEY_SZ 32 + +/* Expand the key name with an appropriate prefix and put the result +** zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least +** KVSTORAGE_KEY_SZ bytes. +*/ +static void kvstorageMakeKey( + const char *zClass, + const char *zKeyIn, + char *zKeyOut +){ + sqlite3_snprintf(KVSTORAGE_KEY_SZ, zKeyOut, "kvvfs-%s-%s", zClass, zKeyIn); +} + +/* Write content into a key. zClass is the particular namespace of the +** underlying key/value store to use - either "local" or "session". +** +** Both zKey and zData are zero-terminated pure text strings. +** +** Return the number of errors. +*/ +static int kvstorageWrite( + const char *zClass, + const char *zKey, + const char *zData +){ + FILE *fd; + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); + fd = fopen(zXKey, "wb"); + if( fd ){ + SQLITE_KV_TRACE(("KVVFS-WRITE %-15s (%d) %.50s%s\n", zXKey, + (int)strlen(zData), zData, + strlen(zData)>50 ? "..." : "")); + fputs(zData, fd); + fclose(fd); + return 0; + }else{ + return 1; + } +} + +/* Delete a key (with its corresponding data) from the key/value +** namespace given by zClass. If the key does not previously exist, +** this routine is a no-op. +*/ +static int kvstorageDelete(const char *zClass, const char *zKey){ + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); + unlink(zXKey); + SQLITE_KV_TRACE(("KVVFS-DELETE %-15s\n", zXKey)); + return 0; +} + +/* Read the value associated with a zKey from the key/value namespace given +** by zClass and put the text data associated with that key in the first +** nBuf bytes of zBuf[]. The value might be truncated if zBuf is not large +** enough to hold it all. The value put into zBuf must always be zero +** terminated, even if it gets truncated because nBuf is not large enough. +** +** Return the total number of bytes in the data, without truncation, and +** not counting the final zero terminator. Return -1 if the key does +** not exist. +** +** If nBuf<=0 then this routine simply returns the size of the data without +** actually reading it. +*/ +static int kvstorageRead( + const char *zClass, + const char *zKey, + char *zBuf, + int nBuf +){ + FILE *fd; + struct stat buf; + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); + if( access(zXKey, R_OK)!=0 + || stat(zXKey, &buf)!=0 + || !S_ISREG(buf.st_mode) + ){ + SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey)); + return -1; + } + if( nBuf<=0 ){ + return (int)buf.st_size; + }else if( nBuf==1 ){ + zBuf[0] = 0; + SQLITE_KV_TRACE(("KVVFS-READ %-15s (%d)\n", zXKey, + (int)buf.st_size)); + return (int)buf.st_size; + } + if( nBuf > buf.st_size + 1 ){ + nBuf = buf.st_size + 1; + } + fd = fopen(zXKey, "rb"); + if( fd==0 ){ + SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey)); + return -1; + }else{ + sqlite3_int64 n = fread(zBuf, 1, nBuf-1, fd); + fclose(fd); + zBuf[n] = 0; + SQLITE_KV_TRACE(("KVVFS-READ %-15s (%lld) %.50s%s\n", zXKey, + n, zBuf, n>50 ? "..." : "")); + return (int)n; + } +} + +/* +** An internal level of indirection which enables us to replace the +** kvvfs i/o methods with JavaScript implementations in WASM builds. +** Maintenance reminder: if this struct changes in any way, the JSON +** rendering of its structure must be updated in +** sqlite3_wasm_enum_json(). There are no binary compatibility +** concerns, so it does not need an iVersion member. This file is +** necessarily always compiled together with sqlite3_wasm_enum_json(), +** and JS code dynamically creates the mapping of members based on +** that JSON description. +*/ +typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; +struct sqlite3_kvvfs_methods { + int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf); + int (*xWrite)(const char *zClass, const char *zKey, const char *zData); + int (*xDelete)(const char *zClass, const char *zKey); + const int nKeySize; +}; + +/* +** This object holds the kvvfs I/O methods which may be swapped out +** for JavaScript-side implementations in WASM builds. In such builds +** it cannot be const, but in native builds it should be so that +** the compiler can hopefully optimize this level of indirection out. +** That said, kvvfs is intended primarily for use in WASM builds. +** +** Note that this is not explicitly flagged as static because the +** amalgamation build will tag it with SQLITE_PRIVATE. +*/ +#ifndef SQLITE_WASM +const +#endif +SQLITE_PRIVATE sqlite3_kvvfs_methods sqlite3KvvfsMethods = { +kvstorageRead, +kvstorageWrite, +kvstorageDelete, +KVSTORAGE_KEY_SZ +}; + +/****** Utility subroutines ************************************************/ + +/* +** Encode binary into the text encoded used to persist on disk. +** The output text is stored in aOut[], which must be at least +** nData+1 bytes in length. +** +** Return the actual length of the encoded text, not counting the +** zero terminator at the end. +** +** Encoding format +** --------------- +** +** * Non-zero bytes are encoded as upper-case hexadecimal +** +** * A sequence of one or more zero-bytes that are not at the +** beginning of the buffer are encoded as a little-endian +** base-26 number using a..z. "a" means 0. "b" means 1, +** "z" means 25. "ab" means 26. "ac" means 52. And so forth. +** +** * Because there is no overlap between the encoding characters +** of hexadecimal and base-26 numbers, it is always clear where +** one stops and the next begins. +*/ +static int kvvfsEncode(const char *aData, int nData, char *aOut){ + int i, j; + const unsigned char *a = (const unsigned char*)aData; + for(i=j=0; i<nData; i++){ + unsigned char c = a[i]; + if( c!=0 ){ + aOut[j++] = "0123456789ABCDEF"[c>>4]; + aOut[j++] = "0123456789ABCDEF"[c&0xf]; + }else{ + /* A sequence of 1 or more zeros is stored as a little-endian + ** base-26 number using a..z as the digits. So one zero is "b". + ** Two zeros is "c". 25 zeros is "z", 26 zeros is "ab", 27 is "bb", + ** and so forth. + */ + int k; + for(k=1; i+k<nData && a[i+k]==0; k++){} + i += k-1; + while( k>0 ){ + aOut[j++] = 'a'+(k%26); + k /= 26; + } + } + } + aOut[j] = 0; + return j; +} + +static const signed char kvvfsHexValue[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +/* +** Decode the text encoding back to binary. The binary content is +** written into pOut, which must be at least nOut bytes in length. +** +** The return value is the number of bytes actually written into aOut[]. +*/ +static int kvvfsDecode(const char *a, char *aOut, int nOut){ + int i, j; + int c; + const unsigned char *aIn = (const unsigned char*)a; + i = 0; + j = 0; + while( 1 ){ + c = kvvfsHexValue[aIn[i]]; + if( c<0 ){ + int n = 0; + int mult = 1; + c = aIn[i]; + if( c==0 ) break; + while( c>='a' && c<='z' ){ + n += (c - 'a')*mult; + mult *= 26; + c = aIn[++i]; + } + if( j+n>nOut ) return -1; + memset(&aOut[j], 0, n); + j += n; + if( c==0 || mult==1 ) break; /* progress stalled if mult==1 */ + }else{ + aOut[j] = c<<4; + c = kvvfsHexValue[aIn[++i]]; + if( c<0 ) break; + aOut[j++] += c; + i++; + } + } + return j; +} + +/* +** Decode a complete journal file. Allocate space in pFile->aJrnl +** and store the decoding there. Or leave pFile->aJrnl set to NULL +** if an error is encountered. +** +** The first few characters of the text encoding will be a little-endian +** base-26 number (digits a..z) that is the total number of bytes +** in the decoded journal file image. This base-26 number is followed +** by a single space, then the encoding of the journal. The space +** separator is required to act as a terminator for the base-26 number. +*/ +static void kvvfsDecodeJournal( + KVVfsFile *pFile, /* Store decoding in pFile->aJrnl */ + const char *zTxt, /* Text encoding. Zero-terminated */ + int nTxt /* Bytes in zTxt, excluding zero terminator */ +){ + unsigned int n = 0; + int c, i, mult; + i = 0; + mult = 1; + while( (c = zTxt[i++])>='a' && c<='z' ){ + n += (zTxt[i] - 'a')*mult; + mult *= 26; + } + sqlite3_free(pFile->aJrnl); + pFile->aJrnl = sqlite3_malloc64( n ); + if( pFile->aJrnl==0 ){ + pFile->nJrnl = 0; + return; + } + pFile->nJrnl = n; + n = kvvfsDecode(zTxt+i, pFile->aJrnl, pFile->nJrnl); + if( n<pFile->nJrnl ){ + sqlite3_free(pFile->aJrnl); + pFile->aJrnl = 0; + pFile->nJrnl = 0; + } +} + +/* +** Read or write the "sz" element, containing the database file size. +*/ +static sqlite3_int64 kvvfsReadFileSize(KVVfsFile *pFile){ + char zData[50]; + zData[0] = 0; + sqlite3KvvfsMethods.xRead(pFile->zClass, "sz", zData, sizeof(zData)-1); + return strtoll(zData, 0, 0); +} +static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){ + char zData[50]; + sqlite3_snprintf(sizeof(zData), zData, "%lld", sz); + return sqlite3KvvfsMethods.xWrite(pFile->zClass, "sz", zData); +} + +/****** sqlite3_io_methods methods ******************************************/ + +/* +** Close an kvvfs-file. +*/ +static int kvvfsClose(sqlite3_file *pProtoFile){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + + SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, + pFile->isJournal ? "journal" : "db")); + sqlite3_free(pFile->aJrnl); + sqlite3_free(pFile->aData); + return SQLITE_OK; +} + +/* +** Read from the -journal file. +*/ +static int kvvfsReadJrnl( + sqlite3_file *pProtoFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + assert( pFile->isJournal ); + SQLITE_KV_LOG(("xRead('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); + if( pFile->aJrnl==0 ){ + int szTxt = kvstorageRead(pFile->zClass, "jrnl", 0, 0); + char *aTxt; + if( szTxt<=4 ){ + return SQLITE_IOERR; + } + aTxt = sqlite3_malloc64( szTxt+1 ); + if( aTxt==0 ) return SQLITE_NOMEM; + kvstorageRead(pFile->zClass, "jrnl", aTxt, szTxt+1); + kvvfsDecodeJournal(pFile, aTxt, szTxt); + sqlite3_free(aTxt); + if( pFile->aJrnl==0 ) return SQLITE_IOERR; + } + if( iOfst+iAmt>pFile->nJrnl ){ + return SQLITE_IOERR_SHORT_READ; + } + memcpy(zBuf, pFile->aJrnl+iOfst, iAmt); + return SQLITE_OK; +} + +/* +** Read from the database file. +*/ +static int kvvfsReadDb( + sqlite3_file *pProtoFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + unsigned int pgno; + int got, n; + char zKey[30]; + char *aData = pFile->aData; + assert( iOfst>=0 ); + assert( iAmt>=0 ); + SQLITE_KV_LOG(("xRead('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); + if( iOfst+iAmt>=512 ){ + if( (iOfst % iAmt)!=0 ){ + return SQLITE_IOERR_READ; + } + if( (iAmt & (iAmt-1))!=0 || iAmt<512 || iAmt>65536 ){ + return SQLITE_IOERR_READ; + } + pFile->szPage = iAmt; + pgno = 1 + iOfst/iAmt; + }else{ + pgno = 1; + } + sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); + got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey, + aData, SQLITE_KVOS_SZ-1); + if( got<0 ){ + n = 0; + }else{ + aData[got] = 0; + if( iOfst+iAmt<512 ){ + int k = iOfst+iAmt; + aData[k*2] = 0; + n = kvvfsDecode(aData, &aData[2000], SQLITE_KVOS_SZ-2000); + if( n>=iOfst+iAmt ){ + memcpy(zBuf, &aData[2000+iOfst], iAmt); + n = iAmt; + }else{ + n = 0; + } + }else{ + n = kvvfsDecode(aData, zBuf, iAmt); + } + } + if( n<iAmt ){ + memset(zBuf+n, 0, iAmt-n); + return SQLITE_IOERR_SHORT_READ; + } + return SQLITE_OK; +} + + +/* +** Write into the -journal file. +*/ +static int kvvfsWriteJrnl( + sqlite3_file *pProtoFile, + const void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + sqlite3_int64 iEnd = iOfst+iAmt; + SQLITE_KV_LOG(("xWrite('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); + if( iEnd>=0x10000000 ) return SQLITE_FULL; + if( pFile->aJrnl==0 || pFile->nJrnl<iEnd ){ + char *aNew = sqlite3_realloc(pFile->aJrnl, iEnd); + if( aNew==0 ){ + return SQLITE_IOERR_NOMEM; + } + pFile->aJrnl = aNew; + if( pFile->nJrnl<iOfst ){ + memset(pFile->aJrnl+pFile->nJrnl, 0, iOfst-pFile->nJrnl); + } + pFile->nJrnl = iEnd; + } + memcpy(pFile->aJrnl+iOfst, zBuf, iAmt); + return SQLITE_OK; +} + +/* +** Write into the database file. +*/ +static int kvvfsWriteDb( + sqlite3_file *pProtoFile, + const void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + unsigned int pgno; + char zKey[30]; + char *aData = pFile->aData; + SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); + assert( iAmt>=512 && iAmt<=65536 ); + assert( (iAmt & (iAmt-1))==0 ); + assert( pFile->szPage<0 || pFile->szPage==iAmt ); + pFile->szPage = iAmt; + pgno = 1 + iOfst/iAmt; + sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); + kvvfsEncode(zBuf, iAmt, aData); + if( sqlite3KvvfsMethods.xWrite(pFile->zClass, zKey, aData) ){ + return SQLITE_IOERR; + } + if( iOfst+iAmt > pFile->szDb ){ + pFile->szDb = iOfst + iAmt; + } + return SQLITE_OK; +} + +/* +** Truncate an kvvfs-file. +*/ +static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + SQLITE_KV_LOG(("xTruncate('%s-journal',%lld)\n", pFile->zClass, size)); + assert( size==0 ); + sqlite3KvvfsMethods.xDelete(pFile->zClass, "jrnl"); + sqlite3_free(pFile->aJrnl); + pFile->aJrnl = 0; + pFile->nJrnl = 0; + return SQLITE_OK; +} +static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + if( pFile->szDb>size + && pFile->szPage>0 + && (size % pFile->szPage)==0 + ){ + char zKey[50]; + unsigned int pgno, pgnoMax; + SQLITE_KV_LOG(("xTruncate('%s-db',%lld)\n", pFile->zClass, size)); + pgno = 1 + size/pFile->szPage; + pgnoMax = 2 + pFile->szDb/pFile->szPage; + while( pgno<=pgnoMax ){ + sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); + sqlite3KvvfsMethods.xDelete(pFile->zClass, zKey); + pgno++; + } + pFile->szDb = size; + return kvvfsWriteFileSize(pFile, size) ? SQLITE_IOERR : SQLITE_OK; + } + return SQLITE_IOERR; +} + +/* +** Sync an kvvfs-file. +*/ +static int kvvfsSyncJrnl(sqlite3_file *pProtoFile, int flags){ + int i, n; + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + char *zOut; + SQLITE_KV_LOG(("xSync('%s-journal')\n", pFile->zClass)); + if( pFile->nJrnl<=0 ){ + return kvvfsTruncateJrnl(pProtoFile, 0); + } + zOut = sqlite3_malloc64( pFile->nJrnl*2 + 50 ); + if( zOut==0 ){ + return SQLITE_IOERR_NOMEM; + } + n = pFile->nJrnl; + i = 0; + do{ + zOut[i++] = 'a' + (n%26); + n /= 26; + }while( n>0 ); + zOut[i++] = ' '; + kvvfsEncode(pFile->aJrnl, pFile->nJrnl, &zOut[i]); + i = sqlite3KvvfsMethods.xWrite(pFile->zClass, "jrnl", zOut); + sqlite3_free(zOut); + return i ? SQLITE_IOERR : SQLITE_OK; +} +static int kvvfsSyncDb(sqlite3_file *pProtoFile, int flags){ + return SQLITE_OK; +} + +/* +** Return the current file-size of an kvvfs-file. +*/ +static int kvvfsFileSizeJrnl(sqlite3_file *pProtoFile, sqlite_int64 *pSize){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + SQLITE_KV_LOG(("xFileSize('%s-journal')\n", pFile->zClass)); + *pSize = pFile->nJrnl; + return SQLITE_OK; +} +static int kvvfsFileSizeDb(sqlite3_file *pProtoFile, sqlite_int64 *pSize){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + SQLITE_KV_LOG(("xFileSize('%s-db')\n", pFile->zClass)); + if( pFile->szDb>=0 ){ + *pSize = pFile->szDb; + }else{ + *pSize = kvvfsReadFileSize(pFile); + } + return SQLITE_OK; +} + +/* +** Lock an kvvfs-file. +*/ +static int kvvfsLock(sqlite3_file *pProtoFile, int eLock){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + assert( !pFile->isJournal ); + SQLITE_KV_LOG(("xLock(%s,%d)\n", pFile->zClass, eLock)); + + if( eLock!=SQLITE_LOCK_NONE ){ + pFile->szDb = kvvfsReadFileSize(pFile); + } + return SQLITE_OK; +} + +/* +** Unlock an kvvfs-file. +*/ +static int kvvfsUnlock(sqlite3_file *pProtoFile, int eLock){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + assert( !pFile->isJournal ); + SQLITE_KV_LOG(("xUnlock(%s,%d)\n", pFile->zClass, eLock)); + if( eLock==SQLITE_LOCK_NONE ){ + pFile->szDb = -1; + } + return SQLITE_OK; +} + +/* +** Check if another file-handle holds a RESERVED lock on an kvvfs-file. +*/ +static int kvvfsCheckReservedLock(sqlite3_file *pProtoFile, int *pResOut){ + SQLITE_KV_LOG(("xCheckReservedLock\n")); + *pResOut = 0; + return SQLITE_OK; +} + +/* +** File control method. For custom operations on an kvvfs-file. +*/ +static int kvvfsFileControlJrnl(sqlite3_file *pProtoFile, int op, void *pArg){ + SQLITE_KV_LOG(("xFileControl(%d) on journal\n", op)); + return SQLITE_NOTFOUND; +} +static int kvvfsFileControlDb(sqlite3_file *pProtoFile, int op, void *pArg){ + SQLITE_KV_LOG(("xFileControl(%d) on database\n", op)); + if( op==SQLITE_FCNTL_SYNC ){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + int rc = SQLITE_OK; + SQLITE_KV_LOG(("xSync('%s-db')\n", pFile->zClass)); + if( pFile->szDb>0 && 0!=kvvfsWriteFileSize(pFile, pFile->szDb) ){ + rc = SQLITE_IOERR; + } + return rc; + } + return SQLITE_NOTFOUND; +} + +/* +** Return the sector-size in bytes for an kvvfs-file. +*/ +static int kvvfsSectorSize(sqlite3_file *pFile){ + return 512; +} + +/* +** Return the device characteristic flags supported by an kvvfs-file. +*/ +static int kvvfsDeviceCharacteristics(sqlite3_file *pProtoFile){ + return 0; +} + +/****** sqlite3_vfs methods *************************************************/ + +/* +** Open an kvvfs file handle. +*/ +static int kvvfsOpen( + sqlite3_vfs *pProtoVfs, + const char *zName, + sqlite3_file *pProtoFile, + int flags, + int *pOutFlags +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + if( zName==0 ) zName = ""; + SQLITE_KV_LOG(("xOpen(\"%s\")\n", zName)); + if( strcmp(zName, "local")==0 + || strcmp(zName, "session")==0 + ){ + pFile->isJournal = 0; + pFile->base.pMethods = &kvvfs_db_io_methods; + }else + if( strcmp(zName, "local-journal")==0 + || strcmp(zName, "session-journal")==0 + ){ + pFile->isJournal = 1; + pFile->base.pMethods = &kvvfs_jrnl_io_methods; + }else{ + return SQLITE_CANTOPEN; + } + if( zName[0]=='s' ){ + pFile->zClass = "session"; + }else{ + pFile->zClass = "local"; + } + pFile->aData = sqlite3_malloc64(SQLITE_KVOS_SZ); + if( pFile->aData==0 ){ + return SQLITE_NOMEM; + } + pFile->aJrnl = 0; + pFile->nJrnl = 0; + pFile->szPage = -1; + pFile->szDb = -1; + return SQLITE_OK; +} + +/* +** Delete the file located at zPath. If the dirSync argument is true, +** ensure the file-system modifications are synced to disk before +** returning. +*/ +static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + if( strcmp(zPath, "local-journal")==0 ){ + sqlite3KvvfsMethods.xDelete("local", "jrnl"); + }else + if( strcmp(zPath, "session-journal")==0 ){ + sqlite3KvvfsMethods.xDelete("session", "jrnl"); + } + return SQLITE_OK; +} + +/* +** Test for access permissions. Return true if the requested permission +** is available, or false otherwise. +*/ +static int kvvfsAccess( + sqlite3_vfs *pProtoVfs, + const char *zPath, + int flags, + int *pResOut +){ + SQLITE_KV_LOG(("xAccess(\"%s\")\n", zPath)); + if( strcmp(zPath, "local-journal")==0 ){ + *pResOut = sqlite3KvvfsMethods.xRead("local", "jrnl", 0, 0)>0; + }else + if( strcmp(zPath, "session-journal")==0 ){ + *pResOut = sqlite3KvvfsMethods.xRead("session", "jrnl", 0, 0)>0; + }else + if( strcmp(zPath, "local")==0 ){ + *pResOut = sqlite3KvvfsMethods.xRead("local", "sz", 0, 0)>0; + }else + if( strcmp(zPath, "session")==0 ){ + *pResOut = sqlite3KvvfsMethods.xRead("session", "sz", 0, 0)>0; + }else + { + *pResOut = 0; + } + SQLITE_KV_LOG(("xAccess returns %d\n",*pResOut)); + return SQLITE_OK; +} + +/* +** Populate buffer zOut with the full canonical pathname corresponding +** to the pathname in zPath. zOut is guaranteed to point to a buffer +** of at least (INST_MAX_PATHNAME+1) bytes. +*/ +static int kvvfsFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, + char *zOut +){ + size_t nPath; +#ifdef SQLITE_OS_KV_ALWAYS_LOCAL + zPath = "local"; +#endif + nPath = strlen(zPath); + SQLITE_KV_LOG(("xFullPathname(\"%s\")\n", zPath)); + if( nOut<nPath+1 ) nPath = nOut - 1; + memcpy(zOut, zPath, nPath); + zOut[nPath] = 0; + return SQLITE_OK; +} + +/* +** Open the dynamic library located at zPath and return a handle. +*/ +static void *kvvfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + return 0; +} + +/* +** Populate the buffer pointed to by zBufOut with nByte bytes of +** random data. +*/ +static int kvvfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ + memset(zBufOut, 0, nByte); + return nByte; +} + +/* +** Sleep for nMicro microseconds. Return the number of microseconds +** actually slept. +*/ +static int kvvfsSleep(sqlite3_vfs *pVfs, int nMicro){ + return SQLITE_OK; +} + +/* +** Return the current time as a Julian Day number in *pTimeOut. +*/ +static int kvvfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ + sqlite3_int64 i = 0; + int rc; + rc = kvvfsCurrentTimeInt64(0, &i); + *pTimeOut = i/86400000.0; + return rc; +} +#include <sys/time.h> +static int kvvfsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ + static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; + struct timeval sNow; + (void)gettimeofday(&sNow, 0); /* Cannot fail given valid arguments */ + *pTimeOut = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; + return SQLITE_OK; +} +#endif /* SQLITE_OS_KV || SQLITE_OS_UNIX */ + +#if SQLITE_OS_KV +/* +** This routine is called initialize the KV-vfs as the default VFS. +*/ +SQLITE_API int sqlite3_os_init(void){ + return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 1); +} +SQLITE_API int sqlite3_os_end(void){ + return SQLITE_OK; +} +#endif /* SQLITE_OS_KV */ + +#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) +SQLITE_PRIVATE int sqlite3KvvfsInit(void){ + return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 0); +} +#endif + +/************** End of os_kv.c ***********************************************/ +/************** Begin file os_unix.c *****************************************/ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains the VFS implementation for unix-like operating systems +** include Linux, MacOSX, *BSD, QNX, VxWorks, AIX, HPUX, and others. +** +** There are actually several different VFS implementations in this file. +** The differences are in the way that file locking is done. The default +** implementation uses Posix Advisory Locks. Alternative implementations +** use flock(), dot-files, various proprietary locking schemas, or simply +** skip locking all together. +** +** This source file is organized into divisions where the logic for various +** subfunctions is contained within the appropriate division. PLEASE +** KEEP THE STRUCTURE OF THIS FILE INTACT. New code should be placed +** in the correct division and should be clearly labelled. +** +** The layout of divisions is as follows: +** +** * General-purpose declarations and utility functions. +** * Unique file ID logic used by VxWorks. +** * Various locking primitive implementations (all except proxy locking): +** + for Posix Advisory Locks +** + for no-op locks +** + for dot-file locks +** + for flock() locking +** + for named semaphore locks (VxWorks only) +** + for AFP filesystem locks (MacOSX only) +** * sqlite3_file methods not associated with locking. +** * Definitions of sqlite3_io_methods objects for all locking +** methods plus "finder" functions for each locking method. +** * sqlite3_vfs method implementations. +** * Locking primitives for the proxy uber-locking-method. (MacOSX only) +** * Definitions of sqlite3_vfs objects for all locking methods +** plus implementations of sqlite3_os_init() and sqlite3_os_end(). +*/ +/* #include "sqliteInt.h" */ +#if SQLITE_OS_UNIX /* This file is used on unix only */ + +/* +** There are various methods for file locking used for concurrency +** control: +** +** 1. POSIX locking (the default), +** 2. No locking, +** 3. Dot-file locking, +** 4. flock() locking, +** 5. AFP locking (OSX only), +** 6. Named POSIX semaphores (VXWorks only), +** 7. proxy locking. (OSX only) +** +** Styles 4, 5, and 7 are only available of SQLITE_ENABLE_LOCKING_STYLE +** is defined to 1. The SQLITE_ENABLE_LOCKING_STYLE also enables automatic +** selection of the appropriate locking style based on the filesystem +** where the database is located. +*/ +#if !defined(SQLITE_ENABLE_LOCKING_STYLE) +# if defined(__APPLE__) +# define SQLITE_ENABLE_LOCKING_STYLE 1 +# else +# define SQLITE_ENABLE_LOCKING_STYLE 0 +# endif +#endif + +/* Use pread() and pwrite() if they are available */ +#if defined(__APPLE__) || defined(__linux__) +# define HAVE_PREAD 1 +# define HAVE_PWRITE 1 +#endif +#if defined(HAVE_PREAD64) && defined(HAVE_PWRITE64) +# undef USE_PREAD +# define USE_PREAD64 1 +#elif defined(HAVE_PREAD) && defined(HAVE_PWRITE) +# undef USE_PREAD64 +# define USE_PREAD 1 +#endif + +/* +** standard include files. +*/ +#include <sys/types.h> /* amalgamator: keep */ +#include <sys/stat.h> /* amalgamator: keep */ +#include <fcntl.h> +#include <sys/ioctl.h> +#include <unistd.h> /* amalgamator: keep */ +/* #include <time.h> */ +#include <sys/time.h> /* amalgamator: keep */ +#include <errno.h> +#if (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) \ + && !defined(SQLITE_WASI) +# include <sys/mman.h> +#endif + +#if SQLITE_ENABLE_LOCKING_STYLE +/* # include <sys/ioctl.h> */ +# include <sys/file.h> +# include <sys/param.h> +#endif /* SQLITE_ENABLE_LOCKING_STYLE */ + +/* +** Try to determine if gethostuuid() is available based on standard +** macros. This might sometimes compute the wrong value for some +** obscure platforms. For those cases, simply compile with one of +** the following: +** +** -DHAVE_GETHOSTUUID=0 +** -DHAVE_GETHOSTUUID=1 +** +** None if this matters except when building on Apple products with +** -DSQLITE_ENABLE_LOCKING_STYLE. +*/ +#ifndef HAVE_GETHOSTUUID +# define HAVE_GETHOSTUUID 0 +# if defined(__APPLE__) && ((__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) || \ + (__IPHONE_OS_VERSION_MIN_REQUIRED > 2000)) +# if (!defined(TARGET_OS_EMBEDDED) || (TARGET_OS_EMBEDDED==0)) \ + && (!defined(TARGET_IPHONE_SIMULATOR) || (TARGET_IPHONE_SIMULATOR==0))\ + && (!defined(TARGET_OS_MACCATALYST) || (TARGET_OS_MACCATALYST==0)) +# undef HAVE_GETHOSTUUID +# define HAVE_GETHOSTUUID 1 +# else +# warning "gethostuuid() is disabled." +# endif +# endif +#endif + + +#if OS_VXWORKS +/* # include <sys/ioctl.h> */ +# include <semaphore.h> +# include <limits.h> +#endif /* OS_VXWORKS */ + +#if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE +# include <sys/mount.h> +#endif + +#ifdef HAVE_UTIME +# include <utime.h> +#endif + +/* +** Allowed values of unixFile.fsFlags +*/ +#define SQLITE_FSFLAGS_IS_MSDOS 0x1 + +/* +** If we are to be thread-safe, include the pthreads header. +*/ +#if SQLITE_THREADSAFE +/* # include <pthread.h> */ +#endif + +/* +** Default permissions when creating a new file +*/ +#ifndef SQLITE_DEFAULT_FILE_PERMISSIONS +# define SQLITE_DEFAULT_FILE_PERMISSIONS 0644 +#endif + +/* +** Default permissions when creating auto proxy dir +*/ +#ifndef SQLITE_DEFAULT_PROXYDIR_PERMISSIONS +# define SQLITE_DEFAULT_PROXYDIR_PERMISSIONS 0755 +#endif + +/* +** Maximum supported path-length. +*/ +#define MAX_PATHNAME 512 + +/* +** Maximum supported symbolic links +*/ +#define SQLITE_MAX_SYMLINKS 100 + +/* +** Remove and stub certain info for WASI (WebAssembly System +** Interface) builds. +*/ +#ifdef SQLITE_WASI +# undef HAVE_FCHMOD +# undef HAVE_FCHOWN +# undef HAVE_MREMAP +# define HAVE_MREMAP 0 +# ifndef SQLITE_DEFAULT_UNIX_VFS +# define SQLITE_DEFAULT_UNIX_VFS "unix-dotfile" + /* ^^^ should SQLITE_DEFAULT_UNIX_VFS be "unix-none"? */ +# endif +# ifndef F_RDLCK +# define F_RDLCK 0 +# define F_WRLCK 1 +# define F_UNLCK 2 +# if __LONG_MAX == 0x7fffffffL +# define F_GETLK 12 +# define F_SETLK 13 +# define F_SETLKW 14 +# else +# define F_GETLK 5 +# define F_SETLK 6 +# define F_SETLKW 7 +# endif +# endif +#else /* !SQLITE_WASI */ +# ifndef HAVE_FCHMOD +# define HAVE_FCHMOD +# endif +#endif /* SQLITE_WASI */ + +#ifdef SQLITE_WASI +# define osGetpid(X) (pid_t)1 +#else +/* Always cast the getpid() return type for compatibility with +** kernel modules in VxWorks. */ +# define osGetpid(X) (pid_t)getpid() +#endif + +/* +** Only set the lastErrno if the error code is a real error and not +** a normal expected return code of SQLITE_BUSY or SQLITE_OK +*/ +#define IS_LOCK_ERROR(x) ((x != SQLITE_OK) && (x != SQLITE_BUSY)) + +/* Forward references */ +typedef struct unixShm unixShm; /* Connection shared memory */ +typedef struct unixShmNode unixShmNode; /* Shared memory instance */ +typedef struct unixInodeInfo unixInodeInfo; /* An i-node */ +typedef struct UnixUnusedFd UnixUnusedFd; /* An unused file descriptor */ + +/* +** Sometimes, after a file handle is closed by SQLite, the file descriptor +** cannot be closed immediately. In these cases, instances of the following +** structure are used to store the file descriptor while waiting for an +** opportunity to either close or reuse it. +*/ +struct UnixUnusedFd { + int fd; /* File descriptor to close */ + int flags; /* Flags this file descriptor was opened with */ + UnixUnusedFd *pNext; /* Next unused file descriptor on same file */ +}; + +/* +** The unixFile structure is subclass of sqlite3_file specific to the unix +** VFS implementations. +*/ +typedef struct unixFile unixFile; +struct unixFile { + sqlite3_io_methods const *pMethod; /* Always the first entry */ + sqlite3_vfs *pVfs; /* The VFS that created this unixFile */ + unixInodeInfo *pInode; /* Info about locks on this inode */ + int h; /* The file descriptor */ + unsigned char eFileLock; /* The type of lock held on this fd */ + unsigned short int ctrlFlags; /* Behavioral bits. UNIXFILE_* flags */ + int lastErrno; /* The unix errno from last I/O error */ + void *lockingContext; /* Locking style specific state */ + UnixUnusedFd *pPreallocatedUnused; /* Pre-allocated UnixUnusedFd */ + const char *zPath; /* Name of the file */ + unixShm *pShm; /* Shared memory segment information */ + int szChunk; /* Configured by FCNTL_CHUNK_SIZE */ +#if SQLITE_MAX_MMAP_SIZE>0 + int nFetchOut; /* Number of outstanding xFetch refs */ + sqlite3_int64 mmapSize; /* Usable size of mapping at pMapRegion */ + sqlite3_int64 mmapSizeActual; /* Actual size of mapping at pMapRegion */ + sqlite3_int64 mmapSizeMax; /* Configured FCNTL_MMAP_SIZE value */ + void *pMapRegion; /* Memory mapped region */ +#endif + int sectorSize; /* Device sector size */ + int deviceCharacteristics; /* Precomputed device characteristics */ +#if SQLITE_ENABLE_LOCKING_STYLE + int openFlags; /* The flags specified at open() */ +#endif +#if SQLITE_ENABLE_LOCKING_STYLE || defined(__APPLE__) + unsigned fsFlags; /* cached details from statfs() */ +#endif +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + unsigned iBusyTimeout; /* Wait this many millisec on locks */ +#endif +#if OS_VXWORKS + struct vxworksFileId *pId; /* Unique file ID */ +#endif +#ifdef SQLITE_DEBUG + /* The next group of variables are used to track whether or not the + ** transaction counter in bytes 24-27 of database files are updated + ** whenever any part of the database changes. An assertion fault will + ** occur if a file is updated without also updating the transaction + ** counter. This test is made to avoid new problems similar to the + ** one described by ticket #3584. + */ + unsigned char transCntrChng; /* True if the transaction counter changed */ + unsigned char dbUpdate; /* True if any part of database file changed */ + unsigned char inNormalWrite; /* True if in a normal write operation */ + +#endif + +#ifdef SQLITE_TEST + /* In test mode, increase the size of this structure a bit so that + ** it is larger than the struct CrashFile defined in test6.c. + */ + char aPadding[32]; +#endif +}; + +/* This variable holds the process id (pid) from when the xRandomness() +** method was called. If xOpen() is called from a different process id, +** indicating that a fork() has occurred, the PRNG will be reset. +*/ +static pid_t randomnessPid = 0; + +/* +** Allowed values for the unixFile.ctrlFlags bitmask: +*/ +#define UNIXFILE_EXCL 0x01 /* Connections from one process only */ +#define UNIXFILE_RDONLY 0x02 /* Connection is read only */ +#define UNIXFILE_PERSIST_WAL 0x04 /* Persistent WAL mode */ +#ifndef SQLITE_DISABLE_DIRSYNC +# define UNIXFILE_DIRSYNC 0x08 /* Directory sync needed */ +#else +# define UNIXFILE_DIRSYNC 0x00 +#endif +#define UNIXFILE_PSOW 0x10 /* SQLITE_IOCAP_POWERSAFE_OVERWRITE */ +#define UNIXFILE_DELETE 0x20 /* Delete on close */ +#define UNIXFILE_URI 0x40 /* Filename might have query parameters */ +#define UNIXFILE_NOLOCK 0x80 /* Do no file locking */ + +/* +** Include code that is common to all os_*.c files +*/ +/* #include "os_common.h" */ + +/* +** Define various macros that are missing from some systems. +*/ +#ifndef O_LARGEFILE +# define O_LARGEFILE 0 +#endif +#ifdef SQLITE_DISABLE_LFS +# undef O_LARGEFILE +# define O_LARGEFILE 0 +#endif +#ifndef O_NOFOLLOW +# define O_NOFOLLOW 0 +#endif +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +/* +** The threadid macro resolves to the thread-id or to 0. Used for +** testing and debugging only. +*/ +#if SQLITE_THREADSAFE +#define threadid pthread_self() +#else +#define threadid 0 +#endif + +/* +** HAVE_MREMAP defaults to true on Linux and false everywhere else. +*/ +#if !defined(HAVE_MREMAP) +# if defined(__linux__) && defined(_GNU_SOURCE) +# define HAVE_MREMAP 1 +# else +# define HAVE_MREMAP 0 +# endif +#endif + +/* +** Explicitly call the 64-bit version of lseek() on Android. Otherwise, lseek() +** is the 32-bit version, even if _FILE_OFFSET_BITS=64 is defined. +*/ +#ifdef __ANDROID__ +# define lseek lseek64 +#endif + +#ifdef __linux__ +/* +** Linux-specific IOCTL magic numbers used for controlling F2FS +*/ +#define F2FS_IOCTL_MAGIC 0xf5 +#define F2FS_IOC_START_ATOMIC_WRITE _IO(F2FS_IOCTL_MAGIC, 1) +#define F2FS_IOC_COMMIT_ATOMIC_WRITE _IO(F2FS_IOCTL_MAGIC, 2) +#define F2FS_IOC_START_VOLATILE_WRITE _IO(F2FS_IOCTL_MAGIC, 3) +#define F2FS_IOC_ABORT_VOLATILE_WRITE _IO(F2FS_IOCTL_MAGIC, 5) +#define F2FS_IOC_GET_FEATURES _IOR(F2FS_IOCTL_MAGIC, 12, u32) +#define F2FS_FEATURE_ATOMIC_WRITE 0x0004 +#endif /* __linux__ */ + + +/* +** Different Unix systems declare open() in different ways. Same use +** open(const char*,int,mode_t). Others use open(const char*,int,...). +** The difference is important when using a pointer to the function. +** +** The safest way to deal with the problem is to always use this wrapper +** which always has the same well-defined interface. +*/ +static int posixOpen(const char *zFile, int flags, int mode){ + return open(zFile, flags, mode); +} + +/* Forward reference */ +static int openDirectory(const char*, int*); +static int unixGetpagesize(void); + +/* +** Many system calls are accessed through pointer-to-functions so that +** they may be overridden at runtime to facilitate fault injection during +** testing and sandboxing. The following array holds the names and pointers +** to all overrideable system calls. +*/ +static struct unix_syscall { + const char *zName; /* Name of the system call */ + sqlite3_syscall_ptr pCurrent; /* Current value of the system call */ + sqlite3_syscall_ptr pDefault; /* Default value */ +} aSyscall[] = { + { "open", (sqlite3_syscall_ptr)posixOpen, 0 }, +#define osOpen ((int(*)(const char*,int,int))aSyscall[0].pCurrent) + + { "close", (sqlite3_syscall_ptr)close, 0 }, +#define osClose ((int(*)(int))aSyscall[1].pCurrent) + + { "access", (sqlite3_syscall_ptr)access, 0 }, +#define osAccess ((int(*)(const char*,int))aSyscall[2].pCurrent) + + { "getcwd", (sqlite3_syscall_ptr)getcwd, 0 }, +#define osGetcwd ((char*(*)(char*,size_t))aSyscall[3].pCurrent) + + { "stat", (sqlite3_syscall_ptr)stat, 0 }, +#define osStat ((int(*)(const char*,struct stat*))aSyscall[4].pCurrent) + +/* +** The DJGPP compiler environment looks mostly like Unix, but it +** lacks the fcntl() system call. So redefine fcntl() to be something +** that always succeeds. This means that locking does not occur under +** DJGPP. But it is DOS - what did you expect? +*/ +#ifdef __DJGPP__ + { "fstat", 0, 0 }, +#define osFstat(a,b,c) 0 +#else + { "fstat", (sqlite3_syscall_ptr)fstat, 0 }, +#define osFstat ((int(*)(int,struct stat*))aSyscall[5].pCurrent) +#endif + + { "ftruncate", (sqlite3_syscall_ptr)ftruncate, 0 }, +#define osFtruncate ((int(*)(int,off_t))aSyscall[6].pCurrent) + + { "fcntl", (sqlite3_syscall_ptr)fcntl, 0 }, +#define osFcntl ((int(*)(int,int,...))aSyscall[7].pCurrent) + + { "read", (sqlite3_syscall_ptr)read, 0 }, +#define osRead ((ssize_t(*)(int,void*,size_t))aSyscall[8].pCurrent) + +#if defined(USE_PREAD) || SQLITE_ENABLE_LOCKING_STYLE + { "pread", (sqlite3_syscall_ptr)pread, 0 }, +#else + { "pread", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osPread ((ssize_t(*)(int,void*,size_t,off_t))aSyscall[9].pCurrent) + +#if defined(USE_PREAD64) + { "pread64", (sqlite3_syscall_ptr)pread64, 0 }, +#else + { "pread64", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osPread64 ((ssize_t(*)(int,void*,size_t,off64_t))aSyscall[10].pCurrent) + + { "write", (sqlite3_syscall_ptr)write, 0 }, +#define osWrite ((ssize_t(*)(int,const void*,size_t))aSyscall[11].pCurrent) + +#if defined(USE_PREAD) || SQLITE_ENABLE_LOCKING_STYLE + { "pwrite", (sqlite3_syscall_ptr)pwrite, 0 }, +#else + { "pwrite", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osPwrite ((ssize_t(*)(int,const void*,size_t,off_t))\ + aSyscall[12].pCurrent) + +#if defined(USE_PREAD64) + { "pwrite64", (sqlite3_syscall_ptr)pwrite64, 0 }, +#else + { "pwrite64", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osPwrite64 ((ssize_t(*)(int,const void*,size_t,off64_t))\ + aSyscall[13].pCurrent) + +#if defined(HAVE_FCHMOD) + { "fchmod", (sqlite3_syscall_ptr)fchmod, 0 }, +#else + { "fchmod", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent) + +#if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE + { "fallocate", (sqlite3_syscall_ptr)posix_fallocate, 0 }, +#else + { "fallocate", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osFallocate ((int(*)(int,off_t,off_t))aSyscall[15].pCurrent) + + { "unlink", (sqlite3_syscall_ptr)unlink, 0 }, +#define osUnlink ((int(*)(const char*))aSyscall[16].pCurrent) + + { "openDirectory", (sqlite3_syscall_ptr)openDirectory, 0 }, +#define osOpenDirectory ((int(*)(const char*,int*))aSyscall[17].pCurrent) + + { "mkdir", (sqlite3_syscall_ptr)mkdir, 0 }, +#define osMkdir ((int(*)(const char*,mode_t))aSyscall[18].pCurrent) + + { "rmdir", (sqlite3_syscall_ptr)rmdir, 0 }, +#define osRmdir ((int(*)(const char*))aSyscall[19].pCurrent) + +#if defined(HAVE_FCHOWN) + { "fchown", (sqlite3_syscall_ptr)fchown, 0 }, +#else + { "fchown", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osFchown ((int(*)(int,uid_t,gid_t))aSyscall[20].pCurrent) + +#if defined(HAVE_FCHOWN) + { "geteuid", (sqlite3_syscall_ptr)geteuid, 0 }, +#else + { "geteuid", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osGeteuid ((uid_t(*)(void))aSyscall[21].pCurrent) + +#if (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) \ + && !defined(SQLITE_WASI) + { "mmap", (sqlite3_syscall_ptr)mmap, 0 }, +#else + { "mmap", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osMmap ((void*(*)(void*,size_t,int,int,int,off_t))aSyscall[22].pCurrent) + +#if (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) \ + && !defined(SQLITE_WASI) + { "munmap", (sqlite3_syscall_ptr)munmap, 0 }, +#else + { "munmap", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osMunmap ((int(*)(void*,size_t))aSyscall[23].pCurrent) + +#if HAVE_MREMAP && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) + { "mremap", (sqlite3_syscall_ptr)mremap, 0 }, +#else + { "mremap", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osMremap ((void*(*)(void*,size_t,size_t,int,...))aSyscall[24].pCurrent) + +#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 + { "getpagesize", (sqlite3_syscall_ptr)unixGetpagesize, 0 }, +#else + { "getpagesize", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osGetpagesize ((int(*)(void))aSyscall[25].pCurrent) + +#if defined(HAVE_READLINK) + { "readlink", (sqlite3_syscall_ptr)readlink, 0 }, +#else + { "readlink", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[26].pCurrent) + +#if defined(HAVE_LSTAT) + { "lstat", (sqlite3_syscall_ptr)lstat, 0 }, +#else + { "lstat", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osLstat ((int(*)(const char*,struct stat*))aSyscall[27].pCurrent) + +#if defined(__linux__) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) +# ifdef __ANDROID__ + { "ioctl", (sqlite3_syscall_ptr)(int(*)(int, int, ...))ioctl, 0 }, +#define osIoctl ((int(*)(int,int,...))aSyscall[28].pCurrent) +# else + { "ioctl", (sqlite3_syscall_ptr)ioctl, 0 }, +#define osIoctl ((int(*)(int,unsigned long,...))aSyscall[28].pCurrent) +# endif +#else + { "ioctl", (sqlite3_syscall_ptr)0, 0 }, +#endif + +}; /* End of the overrideable system calls */ + + +/* +** On some systems, calls to fchown() will trigger a message in a security +** log if they come from non-root processes. So avoid calling fchown() if +** we are not running as root. +*/ +static int robustFchown(int fd, uid_t uid, gid_t gid){ +#if defined(HAVE_FCHOWN) + return osGeteuid() ? 0 : osFchown(fd,uid,gid); +#else + return 0; +#endif +} + +/* +** This is the xSetSystemCall() method of sqlite3_vfs for all of the +** "unix" VFSes. Return SQLITE_OK upon successfully updating the +** system call pointer, or SQLITE_NOTFOUND if there is no configurable +** system call named zName. +*/ +static int unixSetSystemCall( + sqlite3_vfs *pNotUsed, /* The VFS pointer. Not used */ + const char *zName, /* Name of system call to override */ + sqlite3_syscall_ptr pNewFunc /* Pointer to new system call value */ +){ + unsigned int i; + int rc = SQLITE_NOTFOUND; + + UNUSED_PARAMETER(pNotUsed); + if( zName==0 ){ + /* If no zName is given, restore all system calls to their default + ** settings and return NULL + */ + rc = SQLITE_OK; + for(i=0; i<sizeof(aSyscall)/sizeof(aSyscall[0]); i++){ + if( aSyscall[i].pDefault ){ + aSyscall[i].pCurrent = aSyscall[i].pDefault; + } + } + }else{ + /* If zName is specified, operate on only the one system call + ** specified. + */ + for(i=0; i<sizeof(aSyscall)/sizeof(aSyscall[0]); i++){ + if( strcmp(zName, aSyscall[i].zName)==0 ){ + if( aSyscall[i].pDefault==0 ){ + aSyscall[i].pDefault = aSyscall[i].pCurrent; + } + rc = SQLITE_OK; + if( pNewFunc==0 ) pNewFunc = aSyscall[i].pDefault; + aSyscall[i].pCurrent = pNewFunc; + break; + } + } + } + return rc; +} + +/* +** Return the value of a system call. Return NULL if zName is not a +** recognized system call name. NULL is also returned if the system call +** is currently undefined. +*/ +static sqlite3_syscall_ptr unixGetSystemCall( + sqlite3_vfs *pNotUsed, + const char *zName +){ + unsigned int i; + + UNUSED_PARAMETER(pNotUsed); + for(i=0; i<sizeof(aSyscall)/sizeof(aSyscall[0]); i++){ + if( strcmp(zName, aSyscall[i].zName)==0 ) return aSyscall[i].pCurrent; + } + return 0; +} + +/* +** Return the name of the first system call after zName. If zName==NULL +** then return the name of the first system call. Return NULL if zName +** is the last system call or if zName is not the name of a valid +** system call. +*/ +static const char *unixNextSystemCall(sqlite3_vfs *p, const char *zName){ + int i = -1; + + UNUSED_PARAMETER(p); + if( zName ){ + for(i=0; i<ArraySize(aSyscall)-1; i++){ + if( strcmp(zName, aSyscall[i].zName)==0 ) break; + } + } + for(i++; i<ArraySize(aSyscall); i++){ + if( aSyscall[i].pCurrent!=0 ) return aSyscall[i].zName; + } + return 0; +} + +/* +** Do not accept any file descriptor less than this value, in order to avoid +** opening database file using file descriptors that are commonly used for +** standard input, output, and error. +*/ +#ifndef SQLITE_MINIMUM_FILE_DESCRIPTOR +# define SQLITE_MINIMUM_FILE_DESCRIPTOR 3 +#endif + +/* +** Invoke open(). Do so multiple times, until it either succeeds or +** fails for some reason other than EINTR. +** +** If the file creation mode "m" is 0 then set it to the default for +** SQLite. The default is SQLITE_DEFAULT_FILE_PERMISSIONS (normally +** 0644) as modified by the system umask. If m is not 0, then +** make the file creation mode be exactly m ignoring the umask. +** +** The m parameter will be non-zero only when creating -wal, -journal, +** and -shm files. We want those files to have *exactly* the same +** permissions as their original database, unadulterated by the umask. +** In that way, if a database file is -rw-rw-rw or -rw-rw-r-, and a +** transaction crashes and leaves behind hot journals, then any +** process that is able to write to the database will also be able to +** recover the hot journals. +*/ +static int robust_open(const char *z, int f, mode_t m){ + int fd; + mode_t m2 = m ? m : SQLITE_DEFAULT_FILE_PERMISSIONS; + while(1){ +#if defined(O_CLOEXEC) + fd = osOpen(z,f|O_CLOEXEC,m2); +#else + fd = osOpen(z,f,m2); +#endif + if( fd<0 ){ + if( errno==EINTR ) continue; + break; + } + if( fd>=SQLITE_MINIMUM_FILE_DESCRIPTOR ) break; + if( (f & (O_EXCL|O_CREAT))==(O_EXCL|O_CREAT) ){ + (void)osUnlink(z); + } + osClose(fd); + sqlite3_log(SQLITE_WARNING, + "attempt to open \"%s\" as file descriptor %d", z, fd); + fd = -1; + if( osOpen("/dev/null", O_RDONLY, m)<0 ) break; + } + if( fd>=0 ){ + if( m!=0 ){ + struct stat statbuf; + if( osFstat(fd, &statbuf)==0 + && statbuf.st_size==0 + && (statbuf.st_mode&0777)!=m + ){ + osFchmod(fd, m); + } + } +#if defined(FD_CLOEXEC) && (!defined(O_CLOEXEC) || O_CLOEXEC==0) + osFcntl(fd, F_SETFD, osFcntl(fd, F_GETFD, 0) | FD_CLOEXEC); +#endif + } + return fd; +} + +/* +** Helper functions to obtain and relinquish the global mutex. The +** global mutex is used to protect the unixInodeInfo and +** vxworksFileId objects used by this file, all of which may be +** shared by multiple threads. +** +** Function unixMutexHeld() is used to assert() that the global mutex +** is held when required. This function is only used as part of assert() +** statements. e.g. +** +** unixEnterMutex() +** assert( unixMutexHeld() ); +** unixEnterLeave() +** +** To prevent deadlock, the global unixBigLock must must be acquired +** before the unixInodeInfo.pLockMutex mutex, if both are held. It is +** OK to get the pLockMutex without holding unixBigLock first, but if +** that happens, the unixBigLock mutex must not be acquired until after +** pLockMutex is released. +** +** OK: enter(unixBigLock), enter(pLockInfo) +** OK: enter(unixBigLock) +** OK: enter(pLockInfo) +** ERROR: enter(pLockInfo), enter(unixBigLock) +*/ +static sqlite3_mutex *unixBigLock = 0; +static void unixEnterMutex(void){ + assert( sqlite3_mutex_notheld(unixBigLock) ); /* Not a recursive mutex */ + sqlite3_mutex_enter(unixBigLock); +} +static void unixLeaveMutex(void){ + assert( sqlite3_mutex_held(unixBigLock) ); + sqlite3_mutex_leave(unixBigLock); +} +#ifdef SQLITE_DEBUG +static int unixMutexHeld(void) { + return sqlite3_mutex_held(unixBigLock); +} +#endif + + +#ifdef SQLITE_HAVE_OS_TRACE +/* +** Helper function for printing out trace information from debugging +** binaries. This returns the string representation of the supplied +** integer lock-type. +*/ +static const char *azFileLock(int eFileLock){ + switch( eFileLock ){ + case NO_LOCK: return "NONE"; + case SHARED_LOCK: return "SHARED"; + case RESERVED_LOCK: return "RESERVED"; + case PENDING_LOCK: return "PENDING"; + case EXCLUSIVE_LOCK: return "EXCLUSIVE"; + } + return "ERROR"; +} +#endif + +#ifdef SQLITE_LOCK_TRACE +/* +** Print out information about all locking operations. +** +** This routine is used for troubleshooting locks on multithreaded +** platforms. Enable by compiling with the -DSQLITE_LOCK_TRACE +** command-line option on the compiler. This code is normally +** turned off. +*/ +static int lockTrace(int fd, int op, struct flock *p){ + char *zOpName, *zType; + int s; + int savedErrno; + if( op==F_GETLK ){ + zOpName = "GETLK"; + }else if( op==F_SETLK ){ + zOpName = "SETLK"; + }else{ + s = osFcntl(fd, op, p); + sqlite3DebugPrintf("fcntl unknown %d %d %d\n", fd, op, s); + return s; + } + if( p->l_type==F_RDLCK ){ + zType = "RDLCK"; + }else if( p->l_type==F_WRLCK ){ + zType = "WRLCK"; + }else if( p->l_type==F_UNLCK ){ + zType = "UNLCK"; + }else{ + assert( 0 ); + } + assert( p->l_whence==SEEK_SET ); + s = osFcntl(fd, op, p); + savedErrno = errno; + sqlite3DebugPrintf("fcntl %d %d %s %s %d %d %d %d\n", + threadid, fd, zOpName, zType, (int)p->l_start, (int)p->l_len, + (int)p->l_pid, s); + if( s==(-1) && op==F_SETLK && (p->l_type==F_RDLCK || p->l_type==F_WRLCK) ){ + struct flock l2; + l2 = *p; + osFcntl(fd, F_GETLK, &l2); + if( l2.l_type==F_RDLCK ){ + zType = "RDLCK"; + }else if( l2.l_type==F_WRLCK ){ + zType = "WRLCK"; + }else if( l2.l_type==F_UNLCK ){ + zType = "UNLCK"; + }else{ + assert( 0 ); + } + sqlite3DebugPrintf("fcntl-failure-reason: %s %d %d %d\n", + zType, (int)l2.l_start, (int)l2.l_len, (int)l2.l_pid); + } + errno = savedErrno; + return s; +} +#undef osFcntl +#define osFcntl lockTrace +#endif /* SQLITE_LOCK_TRACE */ + +/* +** Retry ftruncate() calls that fail due to EINTR +** +** All calls to ftruncate() within this file should be made through +** this wrapper. On the Android platform, bypassing the logic below +** could lead to a corrupt database. +*/ +static int robust_ftruncate(int h, sqlite3_int64 sz){ + int rc; +#ifdef __ANDROID__ + /* On Android, ftruncate() always uses 32-bit offsets, even if + ** _FILE_OFFSET_BITS=64 is defined. This means it is unsafe to attempt to + ** truncate a file to any size larger than 2GiB. Silently ignore any + ** such attempts. */ + if( sz>(sqlite3_int64)0x7FFFFFFF ){ + rc = SQLITE_OK; + }else +#endif + do{ rc = osFtruncate(h,sz); }while( rc<0 && errno==EINTR ); + return rc; +} + +/* +** This routine translates a standard POSIX errno code into something +** useful to the clients of the sqlite3 functions. Specifically, it is +** intended to translate a variety of "try again" errors into SQLITE_BUSY +** and a variety of "please close the file descriptor NOW" errors into +** SQLITE_IOERR +** +** Errors during initialization of locks, or file system support for locks, +** should handle ENOLCK, ENOTSUP, EOPNOTSUPP separately. +*/ +static int sqliteErrorFromPosixError(int posixError, int sqliteIOErr) { + assert( (sqliteIOErr == SQLITE_IOERR_LOCK) || + (sqliteIOErr == SQLITE_IOERR_UNLOCK) || + (sqliteIOErr == SQLITE_IOERR_RDLOCK) || + (sqliteIOErr == SQLITE_IOERR_CHECKRESERVEDLOCK) ); + switch (posixError) { + case EACCES: + case EAGAIN: + case ETIMEDOUT: + case EBUSY: + case EINTR: + case ENOLCK: + /* random NFS retry error, unless during file system support + * introspection, in which it actually means what it says */ + return SQLITE_BUSY; + + case EPERM: + return SQLITE_PERM; + + default: + return sqliteIOErr; + } +} + + +/****************************************************************************** +****************** Begin Unique File ID Utility Used By VxWorks *************** +** +** On most versions of unix, we can get a unique ID for a file by concatenating +** the device number and the inode number. But this does not work on VxWorks. +** On VxWorks, a unique file id must be based on the canonical filename. +** +** A pointer to an instance of the following structure can be used as a +** unique file ID in VxWorks. Each instance of this structure contains +** a copy of the canonical filename. There is also a reference count. +** The structure is reclaimed when the number of pointers to it drops to +** zero. +** +** There are never very many files open at one time and lookups are not +** a performance-critical path, so it is sufficient to put these +** structures on a linked list. +*/ +struct vxworksFileId { + struct vxworksFileId *pNext; /* Next in a list of them all */ + int nRef; /* Number of references to this one */ + int nName; /* Length of the zCanonicalName[] string */ + char *zCanonicalName; /* Canonical filename */ +}; + +#if OS_VXWORKS +/* +** All unique filenames are held on a linked list headed by this +** variable: +*/ +static struct vxworksFileId *vxworksFileList = 0; + +/* +** Simplify a filename into its canonical form +** by making the following changes: +** +** * removing any trailing and duplicate / +** * convert /./ into just / +** * convert /A/../ where A is any simple name into just / +** +** Changes are made in-place. Return the new name length. +** +** The original filename is in z[0..n-1]. Return the number of +** characters in the simplified name. +*/ +static int vxworksSimplifyName(char *z, int n){ + int i, j; + while( n>1 && z[n-1]=='/' ){ n--; } + for(i=j=0; i<n; i++){ + if( z[i]=='/' ){ + if( z[i+1]=='/' ) continue; + if( z[i+1]=='.' && i+2<n && z[i+2]=='/' ){ + i += 1; + continue; + } + if( z[i+1]=='.' && i+3<n && z[i+2]=='.' && z[i+3]=='/' ){ + while( j>0 && z[j-1]!='/' ){ j--; } + if( j>0 ){ j--; } + i += 2; + continue; + } + } + z[j++] = z[i]; + } + z[j] = 0; + return j; +} + +/* +** Find a unique file ID for the given absolute pathname. Return +** a pointer to the vxworksFileId object. This pointer is the unique +** file ID. +** +** The nRef field of the vxworksFileId object is incremented before +** the object is returned. A new vxworksFileId object is created +** and added to the global list if necessary. +** +** If a memory allocation error occurs, return NULL. +*/ +static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){ + struct vxworksFileId *pNew; /* search key and new file ID */ + struct vxworksFileId *pCandidate; /* For looping over existing file IDs */ + int n; /* Length of zAbsoluteName string */ + + assert( zAbsoluteName[0]=='/' ); + n = (int)strlen(zAbsoluteName); + pNew = sqlite3_malloc64( sizeof(*pNew) + (n+1) ); + if( pNew==0 ) return 0; + pNew->zCanonicalName = (char*)&pNew[1]; + memcpy(pNew->zCanonicalName, zAbsoluteName, n+1); + n = vxworksSimplifyName(pNew->zCanonicalName, n); + + /* Search for an existing entry that matching the canonical name. + ** If found, increment the reference count and return a pointer to + ** the existing file ID. + */ + unixEnterMutex(); + for(pCandidate=vxworksFileList; pCandidate; pCandidate=pCandidate->pNext){ + if( pCandidate->nName==n + && memcmp(pCandidate->zCanonicalName, pNew->zCanonicalName, n)==0 + ){ + sqlite3_free(pNew); + pCandidate->nRef++; + unixLeaveMutex(); + return pCandidate; + } + } + + /* No match was found. We will make a new file ID */ + pNew->nRef = 1; + pNew->nName = n; + pNew->pNext = vxworksFileList; + vxworksFileList = pNew; + unixLeaveMutex(); + return pNew; +} + +/* +** Decrement the reference count on a vxworksFileId object. Free +** the object when the reference count reaches zero. +*/ +static void vxworksReleaseFileId(struct vxworksFileId *pId){ + unixEnterMutex(); + assert( pId->nRef>0 ); + pId->nRef--; + if( pId->nRef==0 ){ + struct vxworksFileId **pp; + for(pp=&vxworksFileList; *pp && *pp!=pId; pp = &((*pp)->pNext)){} + assert( *pp==pId ); + *pp = pId->pNext; + sqlite3_free(pId); + } + unixLeaveMutex(); +} +#endif /* OS_VXWORKS */ +/*************** End of Unique File ID Utility Used By VxWorks **************** +******************************************************************************/ + + +/****************************************************************************** +*************************** Posix Advisory Locking **************************** +** +** POSIX advisory locks are broken by design. ANSI STD 1003.1 (1996) +** section 6.5.2.2 lines 483 through 490 specify that when a process +** sets or clears a lock, that operation overrides any prior locks set +** by the same process. It does not explicitly say so, but this implies +** that it overrides locks set by the same process using a different +** file descriptor. Consider this test case: +** +** int fd1 = open("./file1", O_RDWR|O_CREAT, 0644); +** int fd2 = open("./file2", O_RDWR|O_CREAT, 0644); +** +** Suppose ./file1 and ./file2 are really the same file (because +** one is a hard or symbolic link to the other) then if you set +** an exclusive lock on fd1, then try to get an exclusive lock +** on fd2, it works. I would have expected the second lock to +** fail since there was already a lock on the file due to fd1. +** But not so. Since both locks came from the same process, the +** second overrides the first, even though they were on different +** file descriptors opened on different file names. +** +** This means that we cannot use POSIX locks to synchronize file access +** among competing threads of the same process. POSIX locks will work fine +** to synchronize access for threads in separate processes, but not +** threads within the same process. +** +** To work around the problem, SQLite has to manage file locks internally +** on its own. Whenever a new database is opened, we have to find the +** specific inode of the database file (the inode is determined by the +** st_dev and st_ino fields of the stat structure that fstat() fills in) +** and check for locks already existing on that inode. When locks are +** created or removed, we have to look at our own internal record of the +** locks to see if another thread has previously set a lock on that same +** inode. +** +** (Aside: The use of inode numbers as unique IDs does not work on VxWorks. +** For VxWorks, we have to use the alternative unique ID system based on +** canonical filename and implemented in the previous division.) +** +** The sqlite3_file structure for POSIX is no longer just an integer file +** descriptor. It is now a structure that holds the integer file +** descriptor and a pointer to a structure that describes the internal +** locks on the corresponding inode. There is one locking structure +** per inode, so if the same inode is opened twice, both unixFile structures +** point to the same locking structure. The locking structure keeps +** a reference count (so we will know when to delete it) and a "cnt" +** field that tells us its internal lock status. cnt==0 means the +** file is unlocked. cnt==-1 means the file has an exclusive lock. +** cnt>0 means there are cnt shared locks on the file. +** +** Any attempt to lock or unlock a file first checks the locking +** structure. The fcntl() system call is only invoked to set a +** POSIX lock if the internal lock structure transitions between +** a locked and an unlocked state. +** +** But wait: there are yet more problems with POSIX advisory locks. +** +** If you close a file descriptor that points to a file that has locks, +** all locks on that file that are owned by the current process are +** released. To work around this problem, each unixInodeInfo object +** maintains a count of the number of pending locks on the inode. +** When an attempt is made to close an unixFile, if there are +** other unixFile open on the same inode that are holding locks, the call +** to close() the file descriptor is deferred until all of the locks clear. +** The unixInodeInfo structure keeps a list of file descriptors that need to +** be closed and that list is walked (and cleared) when the last lock +** clears. +** +** Yet another problem: LinuxThreads do not play well with posix locks. +** +** Many older versions of linux use the LinuxThreads library which is +** not posix compliant. Under LinuxThreads, a lock created by thread +** A cannot be modified or overridden by a different thread B. +** Only thread A can modify the lock. Locking behavior is correct +** if the application uses the newer Native Posix Thread Library (NPTL) +** on linux - with NPTL a lock created by thread A can override locks +** in thread B. But there is no way to know at compile-time which +** threading library is being used. So there is no way to know at +** compile-time whether or not thread A can override locks on thread B. +** One has to do a run-time check to discover the behavior of the +** current process. +** +** SQLite used to support LinuxThreads. But support for LinuxThreads +** was dropped beginning with version 3.7.0. SQLite will still work with +** LinuxThreads provided that (1) there is no more than one connection +** per database file in the same process and (2) database connections +** do not move across threads. +*/ + +/* +** An instance of the following structure serves as the key used +** to locate a particular unixInodeInfo object. +*/ +struct unixFileId { + dev_t dev; /* Device number */ +#if OS_VXWORKS + struct vxworksFileId *pId; /* Unique file ID for vxworks. */ +#else + /* We are told that some versions of Android contain a bug that + ** sizes ino_t at only 32-bits instead of 64-bits. (See + ** https://android-review.googlesource.com/#/c/115351/3/dist/sqlite3.c) + ** To work around this, always allocate 64-bits for the inode number. + ** On small machines that only have 32-bit inodes, this wastes 4 bytes, + ** but that should not be a big deal. */ + /* WAS: ino_t ino; */ + u64 ino; /* Inode number */ +#endif +}; + +/* +** An instance of the following structure is allocated for each open +** inode. +** +** A single inode can have multiple file descriptors, so each unixFile +** structure contains a pointer to an instance of this object and this +** object keeps a count of the number of unixFile pointing to it. +** +** Mutex rules: +** +** (1) Only the pLockMutex mutex must be held in order to read or write +** any of the locking fields: +** nShared, nLock, eFileLock, bProcessLock, pUnused +** +** (2) When nRef>0, then the following fields are unchanging and can +** be read (but not written) without holding any mutex: +** fileId, pLockMutex +** +** (3) With the exceptions above, all the fields may only be read +** or written while holding the global unixBigLock mutex. +** +** Deadlock prevention: The global unixBigLock mutex may not +** be acquired while holding the pLockMutex mutex. If both unixBigLock +** and pLockMutex are needed, then unixBigLock must be acquired first. +*/ +struct unixInodeInfo { + struct unixFileId fileId; /* The lookup key */ + sqlite3_mutex *pLockMutex; /* Hold this mutex for... */ + int nShared; /* Number of SHARED locks held */ + int nLock; /* Number of outstanding file locks */ + unsigned char eFileLock; /* One of SHARED_LOCK, RESERVED_LOCK etc. */ + unsigned char bProcessLock; /* An exclusive process lock is held */ + UnixUnusedFd *pUnused; /* Unused file descriptors to close */ + int nRef; /* Number of pointers to this structure */ + unixShmNode *pShmNode; /* Shared memory associated with this inode */ + unixInodeInfo *pNext; /* List of all unixInodeInfo objects */ + unixInodeInfo *pPrev; /* .... doubly linked */ +#if SQLITE_ENABLE_LOCKING_STYLE + unsigned long long sharedByte; /* for AFP simulated shared lock */ +#endif +#if OS_VXWORKS + sem_t *pSem; /* Named POSIX semaphore */ + char aSemName[MAX_PATHNAME+2]; /* Name of that semaphore */ +#endif +}; + +/* +** A lists of all unixInodeInfo objects. +** +** Must hold unixBigLock in order to read or write this variable. +*/ +static unixInodeInfo *inodeList = 0; /* All unixInodeInfo objects */ + +#ifdef SQLITE_DEBUG +/* +** True if the inode mutex (on the unixFile.pFileMutex field) is held, or not. +** This routine is used only within assert() to help verify correct mutex +** usage. +*/ +int unixFileMutexHeld(unixFile *pFile){ + assert( pFile->pInode ); + return sqlite3_mutex_held(pFile->pInode->pLockMutex); +} +int unixFileMutexNotheld(unixFile *pFile){ + assert( pFile->pInode ); + return sqlite3_mutex_notheld(pFile->pInode->pLockMutex); +} +#endif + +/* +** +** This function - unixLogErrorAtLine(), is only ever called via the macro +** unixLogError(). +** +** It is invoked after an error occurs in an OS function and errno has been +** set. It logs a message using sqlite3_log() containing the current value of +** errno and, if possible, the human-readable equivalent from strerror() or +** strerror_r(). +** +** The first argument passed to the macro should be the error code that +** will be returned to SQLite (e.g. SQLITE_IOERR_DELETE, SQLITE_CANTOPEN). +** The two subsequent arguments should be the name of the OS function that +** failed (e.g. "unlink", "open") and the associated file-system path, +** if any. +*/ +#define unixLogError(a,b,c) unixLogErrorAtLine(a,b,c,__LINE__) +static int unixLogErrorAtLine( + int errcode, /* SQLite error code */ + const char *zFunc, /* Name of OS function that failed */ + const char *zPath, /* File path associated with error */ + int iLine /* Source line number where error occurred */ +){ + char *zErr; /* Message from strerror() or equivalent */ + int iErrno = errno; /* Saved syscall error number */ + + /* If this is not a threadsafe build (SQLITE_THREADSAFE==0), then use + ** the strerror() function to obtain the human-readable error message + ** equivalent to errno. Otherwise, use strerror_r(). + */ +#if SQLITE_THREADSAFE && defined(HAVE_STRERROR_R) + char aErr[80]; + memset(aErr, 0, sizeof(aErr)); + zErr = aErr; + + /* If STRERROR_R_CHAR_P (set by autoconf scripts) or __USE_GNU is defined, + ** assume that the system provides the GNU version of strerror_r() that + ** returns a pointer to a buffer containing the error message. That pointer + ** may point to aErr[], or it may point to some static storage somewhere. + ** Otherwise, assume that the system provides the POSIX version of + ** strerror_r(), which always writes an error message into aErr[]. + ** + ** If the code incorrectly assumes that it is the POSIX version that is + ** available, the error message will often be an empty string. Not a + ** huge problem. Incorrectly concluding that the GNU version is available + ** could lead to a segfault though. + */ +#if defined(STRERROR_R_CHAR_P) || defined(__USE_GNU) + zErr = +# endif + strerror_r(iErrno, aErr, sizeof(aErr)-1); + +#elif SQLITE_THREADSAFE + /* This is a threadsafe build, but strerror_r() is not available. */ + zErr = ""; +#else + /* Non-threadsafe build, use strerror(). */ + zErr = strerror(iErrno); +#endif + + if( zPath==0 ) zPath = ""; + sqlite3_log(errcode, + "os_unix.c:%d: (%d) %s(%s) - %s", + iLine, iErrno, zFunc, zPath, zErr + ); + + return errcode; +} + +/* +** Close a file descriptor. +** +** We assume that close() almost always works, since it is only in a +** very sick application or on a very sick platform that it might fail. +** If it does fail, simply leak the file descriptor, but do log the +** error. +** +** Note that it is not safe to retry close() after EINTR since the +** file descriptor might have already been reused by another thread. +** So we don't even try to recover from an EINTR. Just log the error +** and move on. +*/ +static void robust_close(unixFile *pFile, int h, int lineno){ + if( osClose(h) ){ + unixLogErrorAtLine(SQLITE_IOERR_CLOSE, "close", + pFile ? pFile->zPath : 0, lineno); + } +} + +/* +** Set the pFile->lastErrno. Do this in a subroutine as that provides +** a convenient place to set a breakpoint. +*/ +static void storeLastErrno(unixFile *pFile, int error){ + pFile->lastErrno = error; +} + +/* +** Close all file descriptors accumulated in the unixInodeInfo->pUnused list. +*/ +static void closePendingFds(unixFile *pFile){ + unixInodeInfo *pInode = pFile->pInode; + UnixUnusedFd *p; + UnixUnusedFd *pNext; + assert( unixFileMutexHeld(pFile) ); + for(p=pInode->pUnused; p; p=pNext){ + pNext = p->pNext; + robust_close(pFile, p->fd, __LINE__); + sqlite3_free(p); + } + pInode->pUnused = 0; +} + +/* +** Release a unixInodeInfo structure previously allocated by findInodeInfo(). +** +** The global mutex must be held when this routine is called, but the mutex +** on the inode being deleted must NOT be held. +*/ +static void releaseInodeInfo(unixFile *pFile){ + unixInodeInfo *pInode = pFile->pInode; + assert( unixMutexHeld() ); + assert( unixFileMutexNotheld(pFile) ); + if( ALWAYS(pInode) ){ + pInode->nRef--; + if( pInode->nRef==0 ){ + assert( pInode->pShmNode==0 ); + sqlite3_mutex_enter(pInode->pLockMutex); + closePendingFds(pFile); + sqlite3_mutex_leave(pInode->pLockMutex); + if( pInode->pPrev ){ + assert( pInode->pPrev->pNext==pInode ); + pInode->pPrev->pNext = pInode->pNext; + }else{ + assert( inodeList==pInode ); + inodeList = pInode->pNext; + } + if( pInode->pNext ){ + assert( pInode->pNext->pPrev==pInode ); + pInode->pNext->pPrev = pInode->pPrev; + } + sqlite3_mutex_free(pInode->pLockMutex); + sqlite3_free(pInode); + } + } +} + +/* +** Given a file descriptor, locate the unixInodeInfo object that +** describes that file descriptor. Create a new one if necessary. The +** return value might be uninitialized if an error occurs. +** +** The global mutex must held when calling this routine. +** +** Return an appropriate error code. +*/ +static int findInodeInfo( + unixFile *pFile, /* Unix file with file desc used in the key */ + unixInodeInfo **ppInode /* Return the unixInodeInfo object here */ +){ + int rc; /* System call return code */ + int fd; /* The file descriptor for pFile */ + struct unixFileId fileId; /* Lookup key for the unixInodeInfo */ + struct stat statbuf; /* Low-level file information */ + unixInodeInfo *pInode = 0; /* Candidate unixInodeInfo object */ + + assert( unixMutexHeld() ); + + /* Get low-level information about the file that we can used to + ** create a unique name for the file. + */ + fd = pFile->h; + rc = osFstat(fd, &statbuf); + if( rc!=0 ){ + storeLastErrno(pFile, errno); +#if defined(EOVERFLOW) && defined(SQLITE_DISABLE_LFS) + if( pFile->lastErrno==EOVERFLOW ) return SQLITE_NOLFS; +#endif + return SQLITE_IOERR; + } + +#ifdef __APPLE__ + /* On OS X on an msdos filesystem, the inode number is reported + ** incorrectly for zero-size files. See ticket #3260. To work + ** around this problem (we consider it a bug in OS X, not SQLite) + ** we always increase the file size to 1 by writing a single byte + ** prior to accessing the inode number. The one byte written is + ** an ASCII 'S' character which also happens to be the first byte + ** in the header of every SQLite database. In this way, if there + ** is a race condition such that another thread has already populated + ** the first page of the database, no damage is done. + */ + if( statbuf.st_size==0 && (pFile->fsFlags & SQLITE_FSFLAGS_IS_MSDOS)!=0 ){ + do{ rc = osWrite(fd, "S", 1); }while( rc<0 && errno==EINTR ); + if( rc!=1 ){ + storeLastErrno(pFile, errno); + return SQLITE_IOERR; + } + rc = osFstat(fd, &statbuf); + if( rc!=0 ){ + storeLastErrno(pFile, errno); + return SQLITE_IOERR; + } + } +#endif + + memset(&fileId, 0, sizeof(fileId)); + fileId.dev = statbuf.st_dev; +#if OS_VXWORKS + fileId.pId = pFile->pId; +#else + fileId.ino = (u64)statbuf.st_ino; +#endif + assert( unixMutexHeld() ); + pInode = inodeList; + while( pInode && memcmp(&fileId, &pInode->fileId, sizeof(fileId)) ){ + pInode = pInode->pNext; + } + if( pInode==0 ){ + pInode = sqlite3_malloc64( sizeof(*pInode) ); + if( pInode==0 ){ + return SQLITE_NOMEM_BKPT; + } + memset(pInode, 0, sizeof(*pInode)); + memcpy(&pInode->fileId, &fileId, sizeof(fileId)); + if( sqlite3GlobalConfig.bCoreMutex ){ + pInode->pLockMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( pInode->pLockMutex==0 ){ + sqlite3_free(pInode); + return SQLITE_NOMEM_BKPT; + } + } + pInode->nRef = 1; + assert( unixMutexHeld() ); + pInode->pNext = inodeList; + pInode->pPrev = 0; + if( inodeList ) inodeList->pPrev = pInode; + inodeList = pInode; + }else{ + pInode->nRef++; + } + *ppInode = pInode; + return SQLITE_OK; +} + +/* +** Return TRUE if pFile has been renamed or unlinked since it was first opened. +*/ +static int fileHasMoved(unixFile *pFile){ +#if OS_VXWORKS + return pFile->pInode!=0 && pFile->pId!=pFile->pInode->fileId.pId; +#else + struct stat buf; + return pFile->pInode!=0 && + (osStat(pFile->zPath, &buf)!=0 + || (u64)buf.st_ino!=pFile->pInode->fileId.ino); +#endif +} + + +/* +** Check a unixFile that is a database. Verify the following: +** +** (1) There is exactly one hard link on the file +** (2) The file is not a symbolic link +** (3) The file has not been renamed or unlinked +** +** Issue sqlite3_log(SQLITE_WARNING,...) messages if anything is not right. +*/ +static void verifyDbFile(unixFile *pFile){ + struct stat buf; + int rc; + + /* These verifications occurs for the main database only */ + if( pFile->ctrlFlags & UNIXFILE_NOLOCK ) return; + + rc = osFstat(pFile->h, &buf); + if( rc!=0 ){ + sqlite3_log(SQLITE_WARNING, "cannot fstat db file %s", pFile->zPath); + return; + } + if( buf.st_nlink==0 ){ + sqlite3_log(SQLITE_WARNING, "file unlinked while open: %s", pFile->zPath); + return; + } + if( buf.st_nlink>1 ){ + sqlite3_log(SQLITE_WARNING, "multiple links to file: %s", pFile->zPath); + return; + } + if( fileHasMoved(pFile) ){ + sqlite3_log(SQLITE_WARNING, "file renamed while open: %s", pFile->zPath); + return; + } +} + + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +*/ +static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){ + int rc = SQLITE_OK; + int reserved = 0; + unixFile *pFile = (unixFile*)id; + + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); + + assert( pFile ); + assert( pFile->eFileLock<=SHARED_LOCK ); + sqlite3_mutex_enter(pFile->pInode->pLockMutex); + + /* Check if a thread in this process holds such a lock */ + if( pFile->pInode->eFileLock>SHARED_LOCK ){ + reserved = 1; + } + + /* Otherwise see if some other process holds it. + */ +#ifndef __DJGPP__ + if( !reserved && !pFile->pInode->bProcessLock ){ + struct flock lock; + lock.l_whence = SEEK_SET; + lock.l_start = RESERVED_BYTE; + lock.l_len = 1; + lock.l_type = F_WRLCK; + if( osFcntl(pFile->h, F_GETLK, &lock) ){ + rc = SQLITE_IOERR_CHECKRESERVEDLOCK; + storeLastErrno(pFile, errno); + } else if( lock.l_type!=F_UNLCK ){ + reserved = 1; + } + } +#endif + + sqlite3_mutex_leave(pFile->pInode->pLockMutex); + OSTRACE(("TEST WR-LOCK %d %d %d (unix)\n", pFile->h, rc, reserved)); + + *pResOut = reserved; + return rc; +} + +/* Forward declaration*/ +static int unixSleep(sqlite3_vfs*,int); + +/* +** Set a posix-advisory-lock. +** +** There are two versions of this routine. If compiled with +** SQLITE_ENABLE_SETLK_TIMEOUT then the routine has an extra parameter +** which is a pointer to a unixFile. If the unixFile->iBusyTimeout +** value is set, then it is the number of milliseconds to wait before +** failing the lock. The iBusyTimeout value is always reset back to +** zero on each call. +** +** If SQLITE_ENABLE_SETLK_TIMEOUT is not defined, then do a non-blocking +** attempt to set the lock. +*/ +#ifndef SQLITE_ENABLE_SETLK_TIMEOUT +# define osSetPosixAdvisoryLock(h,x,t) osFcntl(h,F_SETLK,x) +#else +static int osSetPosixAdvisoryLock( + int h, /* The file descriptor on which to take the lock */ + struct flock *pLock, /* The description of the lock */ + unixFile *pFile /* Structure holding timeout value */ +){ + int tm = pFile->iBusyTimeout; + int rc = osFcntl(h,F_SETLK,pLock); + while( rc<0 && tm>0 ){ + /* On systems that support some kind of blocking file lock with a timeout, + ** make appropriate changes here to invoke that blocking file lock. On + ** generic posix, however, there is no such API. So we simply try the + ** lock once every millisecond until either the timeout expires, or until + ** the lock is obtained. */ + unixSleep(0,1000); + rc = osFcntl(h,F_SETLK,pLock); + tm--; + } + return rc; +} +#endif /* SQLITE_ENABLE_SETLK_TIMEOUT */ + + +/* +** Attempt to set a system-lock on the file pFile. The lock is +** described by pLock. +** +** If the pFile was opened read/write from unix-excl, then the only lock +** ever obtained is an exclusive lock, and it is obtained exactly once +** the first time any lock is attempted. All subsequent system locking +** operations become no-ops. Locking operations still happen internally, +** in order to coordinate access between separate database connections +** within this process, but all of that is handled in memory and the +** operating system does not participate. +** +** This function is a pass-through to fcntl(F_SETLK) if pFile is using +** any VFS other than "unix-excl" or if pFile is opened on "unix-excl" +** and is read-only. +** +** Zero is returned if the call completes successfully, or -1 if a call +** to fcntl() fails. In this case, errno is set appropriately (by fcntl()). +*/ +static int unixFileLock(unixFile *pFile, struct flock *pLock){ + int rc; + unixInodeInfo *pInode = pFile->pInode; + assert( pInode!=0 ); + assert( sqlite3_mutex_held(pInode->pLockMutex) ); + if( (pFile->ctrlFlags & (UNIXFILE_EXCL|UNIXFILE_RDONLY))==UNIXFILE_EXCL ){ + if( pInode->bProcessLock==0 ){ + struct flock lock; + assert( pInode->nLock==0 ); + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + lock.l_type = F_WRLCK; + rc = osSetPosixAdvisoryLock(pFile->h, &lock, pFile); + if( rc<0 ) return rc; + pInode->bProcessLock = 1; + pInode->nLock++; + }else{ + rc = 0; + } + }else{ + rc = osSetPosixAdvisoryLock(pFile->h, pLock, pFile); + } + return rc; +} + +/* +** Lock the file with the lock specified by parameter eFileLock - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ +static int unixLock(sqlite3_file *id, int eFileLock){ + /* The following describes the implementation of the various locks and + ** lock transitions in terms of the POSIX advisory shared and exclusive + ** lock primitives (called read-locks and write-locks below, to avoid + ** confusion with SQLite lock names). The algorithms are complicated + ** slightly in order to be compatible with Windows95 systems simultaneously + ** accessing the same database file, in case that is ever required. + ** + ** Symbols defined in os.h identify the 'pending byte' and the 'reserved + ** byte', each single bytes at well known offsets, and the 'shared byte + ** range', a range of 510 bytes at a well known offset. + ** + ** To obtain a SHARED lock, a read-lock is obtained on the 'pending + ** byte'. If this is successful, 'shared byte range' is read-locked + ** and the lock on the 'pending byte' released. (Legacy note: When + ** SQLite was first developed, Windows95 systems were still very common, + ** and Windows95 lacks a shared-lock capability. So on Windows95, a + ** single randomly selected by from the 'shared byte range' is locked. + ** Windows95 is now pretty much extinct, but this work-around for the + ** lack of shared-locks on Windows95 lives on, for backwards + ** compatibility.) + ** + ** A process may only obtain a RESERVED lock after it has a SHARED lock. + ** A RESERVED lock is implemented by grabbing a write-lock on the + ** 'reserved byte'. + ** + ** An EXCLUSIVE lock may only be requested after either a SHARED or + ** RESERVED lock is held. An EXCLUSIVE lock is implemented by obtaining + ** a write-lock on the entire 'shared byte range'. Since all other locks + ** require a read-lock on one of the bytes within this range, this ensures + ** that no other locks are held on the database. + ** + ** If a process that holds a RESERVED lock requests an EXCLUSIVE, then + ** a PENDING lock is obtained first. A PENDING lock is implemented by + ** obtaining a write-lock on the 'pending byte'. This ensures that no new + ** SHARED locks can be obtained, but existing SHARED locks are allowed to + ** persist. If the call to this function fails to obtain the EXCLUSIVE + ** lock in this case, it holds the PENDING lock instead. The client may + ** then re-attempt the EXCLUSIVE lock later on, after existing SHARED + ** locks have cleared. + */ + int rc = SQLITE_OK; + unixFile *pFile = (unixFile*)id; + unixInodeInfo *pInode; + struct flock lock; + int tErrno = 0; + + assert( pFile ); + OSTRACE(("LOCK %d %s was %s(%s,%d) pid=%d (unix)\n", pFile->h, + azFileLock(eFileLock), azFileLock(pFile->eFileLock), + azFileLock(pFile->pInode->eFileLock), pFile->pInode->nShared, + osGetpid(0))); + + /* If there is already a lock of this type or more restrictive on the + ** unixFile, do nothing. Don't use the end_lock: exit path, as + ** unixEnterMutex() hasn't been called yet. + */ + if( pFile->eFileLock>=eFileLock ){ + OSTRACE(("LOCK %d %s ok (already held) (unix)\n", pFile->h, + azFileLock(eFileLock))); + return SQLITE_OK; + } + + /* Make sure the locking sequence is correct. + ** (1) We never move from unlocked to anything higher than shared lock. + ** (2) SQLite never explicitly requests a pending lock. + ** (3) A shared lock is always held when a reserve lock is requested. + */ + assert( pFile->eFileLock!=NO_LOCK || eFileLock==SHARED_LOCK ); + assert( eFileLock!=PENDING_LOCK ); + assert( eFileLock!=RESERVED_LOCK || pFile->eFileLock==SHARED_LOCK ); + + /* This mutex is needed because pFile->pInode is shared across threads + */ + pInode = pFile->pInode; + sqlite3_mutex_enter(pInode->pLockMutex); + + /* If some thread using this PID has a lock via a different unixFile* + ** handle that precludes the requested lock, return BUSY. + */ + if( (pFile->eFileLock!=pInode->eFileLock && + (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK)) + ){ + rc = SQLITE_BUSY; + goto end_lock; + } + + /* If a SHARED lock is requested, and some thread using this PID already + ** has a SHARED or RESERVED lock, then increment reference counts and + ** return SQLITE_OK. + */ + if( eFileLock==SHARED_LOCK && + (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){ + assert( eFileLock==SHARED_LOCK ); + assert( pFile->eFileLock==0 ); + assert( pInode->nShared>0 ); + pFile->eFileLock = SHARED_LOCK; + pInode->nShared++; + pInode->nLock++; + goto end_lock; + } + + + /* A PENDING lock is needed before acquiring a SHARED lock and before + ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will + ** be released. + */ + lock.l_len = 1L; + lock.l_whence = SEEK_SET; + if( eFileLock==SHARED_LOCK + || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLock==RESERVED_LOCK) + ){ + lock.l_type = (eFileLock==SHARED_LOCK?F_RDLCK:F_WRLCK); + lock.l_start = PENDING_BYTE; + if( unixFileLock(pFile, &lock) ){ + tErrno = errno; + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); + if( rc!=SQLITE_BUSY ){ + storeLastErrno(pFile, tErrno); + } + goto end_lock; + }else if( eFileLock==EXCLUSIVE_LOCK ){ + pFile->eFileLock = PENDING_LOCK; + pInode->eFileLock = PENDING_LOCK; + } + } + + + /* If control gets to this point, then actually go ahead and make + ** operating system calls for the specified lock. + */ + if( eFileLock==SHARED_LOCK ){ + assert( pInode->nShared==0 ); + assert( pInode->eFileLock==0 ); + assert( rc==SQLITE_OK ); + + /* Now get the read-lock */ + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + if( unixFileLock(pFile, &lock) ){ + tErrno = errno; + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); + } + + /* Drop the temporary PENDING lock */ + lock.l_start = PENDING_BYTE; + lock.l_len = 1L; + lock.l_type = F_UNLCK; + if( unixFileLock(pFile, &lock) && rc==SQLITE_OK ){ + /* This could happen with a network mount */ + tErrno = errno; + rc = SQLITE_IOERR_UNLOCK; + } + + if( rc ){ + if( rc!=SQLITE_BUSY ){ + storeLastErrno(pFile, tErrno); + } + goto end_lock; + }else{ + pFile->eFileLock = SHARED_LOCK; + pInode->nLock++; + pInode->nShared = 1; + } + }else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){ + /* We are trying for an exclusive lock but another thread in this + ** same process is still holding a shared lock. */ + rc = SQLITE_BUSY; + }else{ + /* The request was for a RESERVED or EXCLUSIVE lock. It is + ** assumed that there is a SHARED or greater lock on the file + ** already. + */ + assert( 0!=pFile->eFileLock ); + lock.l_type = F_WRLCK; + + assert( eFileLock==RESERVED_LOCK || eFileLock==EXCLUSIVE_LOCK ); + if( eFileLock==RESERVED_LOCK ){ + lock.l_start = RESERVED_BYTE; + lock.l_len = 1L; + }else{ + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + } + + if( unixFileLock(pFile, &lock) ){ + tErrno = errno; + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); + if( rc!=SQLITE_BUSY ){ + storeLastErrno(pFile, tErrno); + } + } + } + + +#ifdef SQLITE_DEBUG + /* Set up the transaction-counter change checking flags when + ** transitioning from a SHARED to a RESERVED lock. The change + ** from SHARED to RESERVED marks the beginning of a normal + ** write operation (not a hot journal rollback). + */ + if( rc==SQLITE_OK + && pFile->eFileLock<=SHARED_LOCK + && eFileLock==RESERVED_LOCK + ){ + pFile->transCntrChng = 0; + pFile->dbUpdate = 0; + pFile->inNormalWrite = 1; + } +#endif + + if( rc==SQLITE_OK ){ + pFile->eFileLock = eFileLock; + pInode->eFileLock = eFileLock; + } + +end_lock: + sqlite3_mutex_leave(pInode->pLockMutex); + OSTRACE(("LOCK %d %s %s (unix)\n", pFile->h, azFileLock(eFileLock), + rc==SQLITE_OK ? "ok" : "failed")); + return rc; +} + +/* +** Add the file descriptor used by file handle pFile to the corresponding +** pUnused list. +*/ +static void setPendingFd(unixFile *pFile){ + unixInodeInfo *pInode = pFile->pInode; + UnixUnusedFd *p = pFile->pPreallocatedUnused; + assert( unixFileMutexHeld(pFile) ); + p->pNext = pInode->pUnused; + pInode->pUnused = p; + pFile->h = -1; + pFile->pPreallocatedUnused = 0; +} + +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +** +** If handleNFSUnlock is true, then on downgrading an EXCLUSIVE_LOCK to SHARED +** the byte range is divided into 2 parts and the first part is unlocked then +** set to a read lock, then the other part is simply unlocked. This works +** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to +** remove the write lock on a region when a read lock is set. +*/ +static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ + unixFile *pFile = (unixFile*)id; + unixInodeInfo *pInode; + struct flock lock; + int rc = SQLITE_OK; + + assert( pFile ); + OSTRACE(("UNLOCK %d %d was %d(%d,%d) pid=%d (unix)\n", pFile->h, eFileLock, + pFile->eFileLock, pFile->pInode->eFileLock, pFile->pInode->nShared, + osGetpid(0))); + + assert( eFileLock<=SHARED_LOCK ); + if( pFile->eFileLock<=eFileLock ){ + return SQLITE_OK; + } + pInode = pFile->pInode; + sqlite3_mutex_enter(pInode->pLockMutex); + assert( pInode->nShared!=0 ); + if( pFile->eFileLock>SHARED_LOCK ){ + assert( pInode->eFileLock==pFile->eFileLock ); + +#ifdef SQLITE_DEBUG + /* When reducing a lock such that other processes can start + ** reading the database file again, make sure that the + ** transaction counter was updated if any part of the database + ** file changed. If the transaction counter is not updated, + ** other connections to the same file might not realize that + ** the file has changed and hence might not know to flush their + ** cache. The use of a stale cache can lead to database corruption. + */ + pFile->inNormalWrite = 0; +#endif + + /* downgrading to a shared lock on NFS involves clearing the write lock + ** before establishing the readlock - to avoid a race condition we downgrade + ** the lock in 2 blocks, so that part of the range will be covered by a + ** write lock until the rest is covered by a read lock: + ** 1: [WWWWW] + ** 2: [....W] + ** 3: [RRRRW] + ** 4: [RRRR.] + */ + if( eFileLock==SHARED_LOCK ){ +#if !defined(__APPLE__) || !SQLITE_ENABLE_LOCKING_STYLE + (void)handleNFSUnlock; + assert( handleNFSUnlock==0 ); +#endif +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE + if( handleNFSUnlock ){ + int tErrno; /* Error code from system call errors */ + off_t divSize = SHARED_SIZE - 1; + + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = divSize; + if( unixFileLock(pFile, &lock)==(-1) ){ + tErrno = errno; + rc = SQLITE_IOERR_UNLOCK; + storeLastErrno(pFile, tErrno); + goto end_unlock; + } + lock.l_type = F_RDLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = divSize; + if( unixFileLock(pFile, &lock)==(-1) ){ + tErrno = errno; + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_RDLOCK); + if( IS_LOCK_ERROR(rc) ){ + storeLastErrno(pFile, tErrno); + } + goto end_unlock; + } + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST+divSize; + lock.l_len = SHARED_SIZE-divSize; + if( unixFileLock(pFile, &lock)==(-1) ){ + tErrno = errno; + rc = SQLITE_IOERR_UNLOCK; + storeLastErrno(pFile, tErrno); + goto end_unlock; + } + }else +#endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ + { + lock.l_type = F_RDLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + if( unixFileLock(pFile, &lock) ){ + /* In theory, the call to unixFileLock() cannot fail because another + ** process is holding an incompatible lock. If it does, this + ** indicates that the other process is not following the locking + ** protocol. If this happens, return SQLITE_IOERR_RDLOCK. Returning + ** SQLITE_BUSY would confuse the upper layer (in practice it causes + ** an assert to fail). */ + rc = SQLITE_IOERR_RDLOCK; + storeLastErrno(pFile, errno); + goto end_unlock; + } + } + } + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = PENDING_BYTE; + lock.l_len = 2L; assert( PENDING_BYTE+1==RESERVED_BYTE ); + if( unixFileLock(pFile, &lock)==0 ){ + pInode->eFileLock = SHARED_LOCK; + }else{ + rc = SQLITE_IOERR_UNLOCK; + storeLastErrno(pFile, errno); + goto end_unlock; + } + } + if( eFileLock==NO_LOCK ){ + /* Decrement the shared lock counter. Release the lock using an + ** OS call only when all threads in this same process have released + ** the lock. + */ + pInode->nShared--; + if( pInode->nShared==0 ){ + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = lock.l_len = 0L; + if( unixFileLock(pFile, &lock)==0 ){ + pInode->eFileLock = NO_LOCK; + }else{ + rc = SQLITE_IOERR_UNLOCK; + storeLastErrno(pFile, errno); + pInode->eFileLock = NO_LOCK; + pFile->eFileLock = NO_LOCK; + } + } + + /* Decrement the count of locks against this same file. When the + ** count reaches zero, close any other file descriptors whose close + ** was deferred because of outstanding locks. + */ + pInode->nLock--; + assert( pInode->nLock>=0 ); + if( pInode->nLock==0 ) closePendingFds(pFile); + } + +end_unlock: + sqlite3_mutex_leave(pInode->pLockMutex); + if( rc==SQLITE_OK ){ + pFile->eFileLock = eFileLock; + } + return rc; +} + +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +*/ +static int unixUnlock(sqlite3_file *id, int eFileLock){ +#if SQLITE_MAX_MMAP_SIZE>0 + assert( eFileLock==SHARED_LOCK || ((unixFile *)id)->nFetchOut==0 ); +#endif + return posixUnlock(id, eFileLock, 0); +} + +#if SQLITE_MAX_MMAP_SIZE>0 +static int unixMapfile(unixFile *pFd, i64 nByte); +static void unixUnmapfile(unixFile *pFd); +#endif + +/* +** This function performs the parts of the "close file" operation +** common to all locking schemes. It closes the directory and file +** handles, if they are valid, and sets all fields of the unixFile +** structure to 0. +** +** It is *not* necessary to hold the mutex when this routine is called, +** even on VxWorks. A mutex will be acquired on VxWorks by the +** vxworksReleaseFileId() routine. +*/ +static int closeUnixFile(sqlite3_file *id){ + unixFile *pFile = (unixFile*)id; +#if SQLITE_MAX_MMAP_SIZE>0 + unixUnmapfile(pFile); +#endif + if( pFile->h>=0 ){ + robust_close(pFile, pFile->h, __LINE__); + pFile->h = -1; + } +#if OS_VXWORKS + if( pFile->pId ){ + if( pFile->ctrlFlags & UNIXFILE_DELETE ){ + osUnlink(pFile->pId->zCanonicalName); + } + vxworksReleaseFileId(pFile->pId); + pFile->pId = 0; + } +#endif +#ifdef SQLITE_UNLINK_AFTER_CLOSE + if( pFile->ctrlFlags & UNIXFILE_DELETE ){ + osUnlink(pFile->zPath); + sqlite3_free(*(char**)&pFile->zPath); + pFile->zPath = 0; + } +#endif + OSTRACE(("CLOSE %-3d\n", pFile->h)); + OpenCounter(-1); + sqlite3_free(pFile->pPreallocatedUnused); + memset(pFile, 0, sizeof(unixFile)); + return SQLITE_OK; +} + +/* +** Close a file. +*/ +static int unixClose(sqlite3_file *id){ + int rc = SQLITE_OK; + unixFile *pFile = (unixFile *)id; + unixInodeInfo *pInode = pFile->pInode; + + assert( pInode!=0 ); + verifyDbFile(pFile); + unixUnlock(id, NO_LOCK); + assert( unixFileMutexNotheld(pFile) ); + unixEnterMutex(); + + /* unixFile.pInode is always valid here. Otherwise, a different close + ** routine (e.g. nolockClose()) would be called instead. + */ + assert( pFile->pInode->nLock>0 || pFile->pInode->bProcessLock==0 ); + sqlite3_mutex_enter(pInode->pLockMutex); + if( pInode->nLock ){ + /* If there are outstanding locks, do not actually close the file just + ** yet because that would clear those locks. Instead, add the file + ** descriptor to pInode->pUnused list. It will be automatically closed + ** when the last lock is cleared. + */ + setPendingFd(pFile); + } + sqlite3_mutex_leave(pInode->pLockMutex); + releaseInodeInfo(pFile); + assert( pFile->pShm==0 ); + rc = closeUnixFile(id); + unixLeaveMutex(); + return rc; +} + +/************** End of the posix advisory lock implementation ***************** +******************************************************************************/ + +/****************************************************************************** +****************************** No-op Locking ********************************** +** +** Of the various locking implementations available, this is by far the +** simplest: locking is ignored. No attempt is made to lock the database +** file for reading or writing. +** +** This locking mode is appropriate for use on read-only databases +** (ex: databases that are burned into CD-ROM, for example.) It can +** also be used if the application employs some external mechanism to +** prevent simultaneous access of the same database by two or more +** database connections. But there is a serious risk of database +** corruption if this locking mode is used in situations where multiple +** database connections are accessing the same database file at the same +** time and one or more of those connections are writing. +*/ + +static int nolockCheckReservedLock(sqlite3_file *NotUsed, int *pResOut){ + UNUSED_PARAMETER(NotUsed); + *pResOut = 0; + return SQLITE_OK; +} +static int nolockLock(sqlite3_file *NotUsed, int NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + return SQLITE_OK; +} +static int nolockUnlock(sqlite3_file *NotUsed, int NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + return SQLITE_OK; +} + +/* +** Close the file. +*/ +static int nolockClose(sqlite3_file *id) { + return closeUnixFile(id); +} + +/******************* End of the no-op lock implementation ********************* +******************************************************************************/ + +/****************************************************************************** +************************* Begin dot-file Locking ****************************** +** +** The dotfile locking implementation uses the existence of separate lock +** files (really a directory) to control access to the database. This works +** on just about every filesystem imaginable. But there are serious downsides: +** +** (1) There is zero concurrency. A single reader blocks all other +** connections from reading or writing the database. +** +** (2) An application crash or power loss can leave stale lock files +** sitting around that need to be cleared manually. +** +** Nevertheless, a dotlock is an appropriate locking mode for use if no +** other locking strategy is available. +** +** Dotfile locking works by creating a subdirectory in the same directory as +** the database and with the same name but with a ".lock" extension added. +** The existence of a lock directory implies an EXCLUSIVE lock. All other +** lock types (SHARED, RESERVED, PENDING) are mapped into EXCLUSIVE. +*/ + +/* +** The file suffix added to the data base filename in order to create the +** lock directory. +*/ +#define DOTLOCK_SUFFIX ".lock" + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +** +** In dotfile locking, either a lock exists or it does not. So in this +** variation of CheckReservedLock(), *pResOut is set to true if any lock +** is held on the file and false if the file is unlocked. +*/ +static int dotlockCheckReservedLock(sqlite3_file *id, int *pResOut) { + int rc = SQLITE_OK; + int reserved = 0; + unixFile *pFile = (unixFile*)id; + + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); + + assert( pFile ); + reserved = osAccess((const char*)pFile->lockingContext, 0)==0; + OSTRACE(("TEST WR-LOCK %d %d %d (dotlock)\n", pFile->h, rc, reserved)); + *pResOut = reserved; + return rc; +} + +/* +** Lock the file with the lock specified by parameter eFileLock - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +** +** With dotfile locking, we really only support state (4): EXCLUSIVE. +** But we track the other locking levels internally. +*/ +static int dotlockLock(sqlite3_file *id, int eFileLock) { + unixFile *pFile = (unixFile*)id; + char *zLockFile = (char *)pFile->lockingContext; + int rc = SQLITE_OK; + + + /* If we have any lock, then the lock file already exists. All we have + ** to do is adjust our internal record of the lock level. + */ + if( pFile->eFileLock > NO_LOCK ){ + pFile->eFileLock = eFileLock; + /* Always update the timestamp on the old file */ +#ifdef HAVE_UTIME + utime(zLockFile, NULL); +#else + utimes(zLockFile, NULL); +#endif + return SQLITE_OK; + } + + /* grab an exclusive lock */ + rc = osMkdir(zLockFile, 0777); + if( rc<0 ){ + /* failed to open/create the lock directory */ + int tErrno = errno; + if( EEXIST == tErrno ){ + rc = SQLITE_BUSY; + } else { + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); + if( rc!=SQLITE_BUSY ){ + storeLastErrno(pFile, tErrno); + } + } + return rc; + } + + /* got it, set the type and return ok */ + pFile->eFileLock = eFileLock; + return rc; +} + +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +** +** When the locking level reaches NO_LOCK, delete the lock file. +*/ +static int dotlockUnlock(sqlite3_file *id, int eFileLock) { + unixFile *pFile = (unixFile*)id; + char *zLockFile = (char *)pFile->lockingContext; + int rc; + + assert( pFile ); + OSTRACE(("UNLOCK %d %d was %d pid=%d (dotlock)\n", pFile->h, eFileLock, + pFile->eFileLock, osGetpid(0))); + assert( eFileLock<=SHARED_LOCK ); + + /* no-op if possible */ + if( pFile->eFileLock==eFileLock ){ + return SQLITE_OK; + } + + /* To downgrade to shared, simply update our internal notion of the + ** lock state. No need to mess with the file on disk. + */ + if( eFileLock==SHARED_LOCK ){ + pFile->eFileLock = SHARED_LOCK; + return SQLITE_OK; + } + + /* To fully unlock the database, delete the lock file */ + assert( eFileLock==NO_LOCK ); + rc = osRmdir(zLockFile); + if( rc<0 ){ + int tErrno = errno; + if( tErrno==ENOENT ){ + rc = SQLITE_OK; + }else{ + rc = SQLITE_IOERR_UNLOCK; + storeLastErrno(pFile, tErrno); + } + return rc; + } + pFile->eFileLock = NO_LOCK; + return SQLITE_OK; +} + +/* +** Close a file. Make sure the lock has been released before closing. +*/ +static int dotlockClose(sqlite3_file *id) { + unixFile *pFile = (unixFile*)id; + assert( id!=0 ); + dotlockUnlock(id, NO_LOCK); + sqlite3_free(pFile->lockingContext); + return closeUnixFile(id); +} +/****************** End of the dot-file lock implementation ******************* +******************************************************************************/ + +/****************************************************************************** +************************** Begin flock Locking ******************************** +** +** Use the flock() system call to do file locking. +** +** flock() locking is like dot-file locking in that the various +** fine-grain locking levels supported by SQLite are collapsed into +** a single exclusive lock. In other words, SHARED, RESERVED, and +** PENDING locks are the same thing as an EXCLUSIVE lock. SQLite +** still works when you do this, but concurrency is reduced since +** only a single process can be reading the database at a time. +** +** Omit this section if SQLITE_ENABLE_LOCKING_STYLE is turned off +*/ +#if SQLITE_ENABLE_LOCKING_STYLE + +/* +** Retry flock() calls that fail with EINTR +*/ +#ifdef EINTR +static int robust_flock(int fd, int op){ + int rc; + do{ rc = flock(fd,op); }while( rc<0 && errno==EINTR ); + return rc; +} +#else +# define robust_flock(a,b) flock(a,b) +#endif + + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +*/ +static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){ + int rc = SQLITE_OK; + int reserved = 0; + unixFile *pFile = (unixFile*)id; + + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); + + assert( pFile ); + + /* Check if a thread in this process holds such a lock */ + if( pFile->eFileLock>SHARED_LOCK ){ + reserved = 1; + } + + /* Otherwise see if some other process holds it. */ + if( !reserved ){ + /* attempt to get the lock */ + int lrc = robust_flock(pFile->h, LOCK_EX | LOCK_NB); + if( !lrc ){ + /* got the lock, unlock it */ + lrc = robust_flock(pFile->h, LOCK_UN); + if ( lrc ) { + int tErrno = errno; + /* unlock failed with an error */ + lrc = SQLITE_IOERR_UNLOCK; + storeLastErrno(pFile, tErrno); + rc = lrc; + } + } else { + int tErrno = errno; + reserved = 1; + /* someone else might have it reserved */ + lrc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); + if( IS_LOCK_ERROR(lrc) ){ + storeLastErrno(pFile, tErrno); + rc = lrc; + } + } + } + OSTRACE(("TEST WR-LOCK %d %d %d (flock)\n", pFile->h, rc, reserved)); + +#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS + if( (rc & 0xff) == SQLITE_IOERR ){ + rc = SQLITE_OK; + reserved=1; + } +#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */ + *pResOut = reserved; + return rc; +} + +/* +** Lock the file with the lock specified by parameter eFileLock - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** flock() only really support EXCLUSIVE locks. We track intermediate +** lock states in the sqlite3_file structure, but all locks SHARED or +** above are really EXCLUSIVE locks and exclude all other processes from +** access the file. +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ +static int flockLock(sqlite3_file *id, int eFileLock) { + int rc = SQLITE_OK; + unixFile *pFile = (unixFile*)id; + + assert( pFile ); + + /* if we already have a lock, it is exclusive. + ** Just adjust level and punt on outta here. */ + if (pFile->eFileLock > NO_LOCK) { + pFile->eFileLock = eFileLock; + return SQLITE_OK; + } + + /* grab an exclusive lock */ + + if (robust_flock(pFile->h, LOCK_EX | LOCK_NB)) { + int tErrno = errno; + /* didn't get, must be busy */ + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); + if( IS_LOCK_ERROR(rc) ){ + storeLastErrno(pFile, tErrno); + } + } else { + /* got it, set the type and return ok */ + pFile->eFileLock = eFileLock; + } + OSTRACE(("LOCK %d %s %s (flock)\n", pFile->h, azFileLock(eFileLock), + rc==SQLITE_OK ? "ok" : "failed")); +#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS + if( (rc & 0xff) == SQLITE_IOERR ){ + rc = SQLITE_BUSY; + } +#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */ + return rc; +} + + +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +*/ +static int flockUnlock(sqlite3_file *id, int eFileLock) { + unixFile *pFile = (unixFile*)id; + + assert( pFile ); + OSTRACE(("UNLOCK %d %d was %d pid=%d (flock)\n", pFile->h, eFileLock, + pFile->eFileLock, osGetpid(0))); + assert( eFileLock<=SHARED_LOCK ); + + /* no-op if possible */ + if( pFile->eFileLock==eFileLock ){ + return SQLITE_OK; + } + + /* shared can just be set because we always have an exclusive */ + if (eFileLock==SHARED_LOCK) { + pFile->eFileLock = eFileLock; + return SQLITE_OK; + } + + /* no, really, unlock. */ + if( robust_flock(pFile->h, LOCK_UN) ){ +#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS + return SQLITE_OK; +#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */ + return SQLITE_IOERR_UNLOCK; + }else{ + pFile->eFileLock = NO_LOCK; + return SQLITE_OK; + } +} + +/* +** Close a file. +*/ +static int flockClose(sqlite3_file *id) { + assert( id!=0 ); + flockUnlock(id, NO_LOCK); + return closeUnixFile(id); +} + +#endif /* SQLITE_ENABLE_LOCKING_STYLE && !OS_VXWORK */ + +/******************* End of the flock lock implementation ********************* +******************************************************************************/ + +/****************************************************************************** +************************ Begin Named Semaphore Locking ************************ +** +** Named semaphore locking is only supported on VxWorks. +** +** Semaphore locking is like dot-lock and flock in that it really only +** supports EXCLUSIVE locking. Only a single process can read or write +** the database file at a time. This reduces potential concurrency, but +** makes the lock implementation much easier. +*/ +#if OS_VXWORKS + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +*/ +static int semXCheckReservedLock(sqlite3_file *id, int *pResOut) { + int rc = SQLITE_OK; + int reserved = 0; + unixFile *pFile = (unixFile*)id; + + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); + + assert( pFile ); + + /* Check if a thread in this process holds such a lock */ + if( pFile->eFileLock>SHARED_LOCK ){ + reserved = 1; + } + + /* Otherwise see if some other process holds it. */ + if( !reserved ){ + sem_t *pSem = pFile->pInode->pSem; + + if( sem_trywait(pSem)==-1 ){ + int tErrno = errno; + if( EAGAIN != tErrno ){ + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_CHECKRESERVEDLOCK); + storeLastErrno(pFile, tErrno); + } else { + /* someone else has the lock when we are in NO_LOCK */ + reserved = (pFile->eFileLock < SHARED_LOCK); + } + }else{ + /* we could have it if we want it */ + sem_post(pSem); + } + } + OSTRACE(("TEST WR-LOCK %d %d %d (sem)\n", pFile->h, rc, reserved)); + + *pResOut = reserved; + return rc; +} + +/* +** Lock the file with the lock specified by parameter eFileLock - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** Semaphore locks only really support EXCLUSIVE locks. We track intermediate +** lock states in the sqlite3_file structure, but all locks SHARED or +** above are really EXCLUSIVE locks and exclude all other processes from +** access the file. +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ +static int semXLock(sqlite3_file *id, int eFileLock) { + unixFile *pFile = (unixFile*)id; + sem_t *pSem = pFile->pInode->pSem; + int rc = SQLITE_OK; + + /* if we already have a lock, it is exclusive. + ** Just adjust level and punt on outta here. */ + if (pFile->eFileLock > NO_LOCK) { + pFile->eFileLock = eFileLock; + rc = SQLITE_OK; + goto sem_end_lock; + } + + /* lock semaphore now but bail out when already locked. */ + if( sem_trywait(pSem)==-1 ){ + rc = SQLITE_BUSY; + goto sem_end_lock; + } + + /* got it, set the type and return ok */ + pFile->eFileLock = eFileLock; + + sem_end_lock: + return rc; +} + +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +*/ +static int semXUnlock(sqlite3_file *id, int eFileLock) { + unixFile *pFile = (unixFile*)id; + sem_t *pSem = pFile->pInode->pSem; + + assert( pFile ); + assert( pSem ); + OSTRACE(("UNLOCK %d %d was %d pid=%d (sem)\n", pFile->h, eFileLock, + pFile->eFileLock, osGetpid(0))); + assert( eFileLock<=SHARED_LOCK ); + + /* no-op if possible */ + if( pFile->eFileLock==eFileLock ){ + return SQLITE_OK; + } + + /* shared can just be set because we always have an exclusive */ + if (eFileLock==SHARED_LOCK) { + pFile->eFileLock = eFileLock; + return SQLITE_OK; + } + + /* no, really unlock. */ + if ( sem_post(pSem)==-1 ) { + int rc, tErrno = errno; + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); + if( IS_LOCK_ERROR(rc) ){ + storeLastErrno(pFile, tErrno); + } + return rc; + } + pFile->eFileLock = NO_LOCK; + return SQLITE_OK; +} + +/* + ** Close a file. + */ +static int semXClose(sqlite3_file *id) { + if( id ){ + unixFile *pFile = (unixFile*)id; + semXUnlock(id, NO_LOCK); + assert( pFile ); + assert( unixFileMutexNotheld(pFile) ); + unixEnterMutex(); + releaseInodeInfo(pFile); + unixLeaveMutex(); + closeUnixFile(id); + } + return SQLITE_OK; +} + +#endif /* OS_VXWORKS */ +/* +** Named semaphore locking is only available on VxWorks. +** +*************** End of the named semaphore lock implementation **************** +******************************************************************************/ + + +/****************************************************************************** +*************************** Begin AFP Locking ********************************* +** +** AFP is the Apple Filing Protocol. AFP is a network filesystem found +** on Apple Macintosh computers - both OS9 and OSX. +** +** Third-party implementations of AFP are available. But this code here +** only works on OSX. +*/ + +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE +/* +** The afpLockingContext structure contains all afp lock specific state +*/ +typedef struct afpLockingContext afpLockingContext; +struct afpLockingContext { + int reserved; + const char *dbPath; /* Name of the open file */ +}; + +struct ByteRangeLockPB2 +{ + unsigned long long offset; /* offset to first byte to lock */ + unsigned long long length; /* nbr of bytes to lock */ + unsigned long long retRangeStart; /* nbr of 1st byte locked if successful */ + unsigned char unLockFlag; /* 1 = unlock, 0 = lock */ + unsigned char startEndFlag; /* 1=rel to end of fork, 0=rel to start */ + int fd; /* file desc to assoc this lock with */ +}; + +#define afpfsByteRangeLock2FSCTL _IOWR('z', 23, struct ByteRangeLockPB2) + +/* +** This is a utility for setting or clearing a bit-range lock on an +** AFP filesystem. +** +** Return SQLITE_OK on success, SQLITE_BUSY on failure. +*/ +static int afpSetLock( + const char *path, /* Name of the file to be locked or unlocked */ + unixFile *pFile, /* Open file descriptor on path */ + unsigned long long offset, /* First byte to be locked */ + unsigned long long length, /* Number of bytes to lock */ + int setLockFlag /* True to set lock. False to clear lock */ +){ + struct ByteRangeLockPB2 pb; + int err; + + pb.unLockFlag = setLockFlag ? 0 : 1; + pb.startEndFlag = 0; + pb.offset = offset; + pb.length = length; + pb.fd = pFile->h; + + OSTRACE(("AFPSETLOCK [%s] for %d%s in range %llx:%llx\n", + (setLockFlag?"ON":"OFF"), pFile->h, (pb.fd==-1?"[testval-1]":""), + offset, length)); + err = fsctl(path, afpfsByteRangeLock2FSCTL, &pb, 0); + if ( err==-1 ) { + int rc; + int tErrno = errno; + OSTRACE(("AFPSETLOCK failed to fsctl() '%s' %d %s\n", + path, tErrno, strerror(tErrno))); +#ifdef SQLITE_IGNORE_AFP_LOCK_ERRORS + rc = SQLITE_BUSY; +#else + rc = sqliteErrorFromPosixError(tErrno, + setLockFlag ? SQLITE_IOERR_LOCK : SQLITE_IOERR_UNLOCK); +#endif /* SQLITE_IGNORE_AFP_LOCK_ERRORS */ + if( IS_LOCK_ERROR(rc) ){ + storeLastErrno(pFile, tErrno); + } + return rc; + } else { + return SQLITE_OK; + } +} + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +*/ +static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ + int rc = SQLITE_OK; + int reserved = 0; + unixFile *pFile = (unixFile*)id; + afpLockingContext *context; + + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); + + assert( pFile ); + context = (afpLockingContext *) pFile->lockingContext; + if( context->reserved ){ + *pResOut = 1; + return SQLITE_OK; + } + sqlite3_mutex_enter(pFile->pInode->pLockMutex); + /* Check if a thread in this process holds such a lock */ + if( pFile->pInode->eFileLock>SHARED_LOCK ){ + reserved = 1; + } + + /* Otherwise see if some other process holds it. + */ + if( !reserved ){ + /* lock the RESERVED byte */ + int lrc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1); + if( SQLITE_OK==lrc ){ + /* if we succeeded in taking the reserved lock, unlock it to restore + ** the original state */ + lrc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1, 0); + } else { + /* if we failed to get the lock then someone else must have it */ + reserved = 1; + } + if( IS_LOCK_ERROR(lrc) ){ + rc=lrc; + } + } + + sqlite3_mutex_leave(pFile->pInode->pLockMutex); + OSTRACE(("TEST WR-LOCK %d %d %d (afp)\n", pFile->h, rc, reserved)); + + *pResOut = reserved; + return rc; +} + +/* +** Lock the file with the lock specified by parameter eFileLock - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ +static int afpLock(sqlite3_file *id, int eFileLock){ + int rc = SQLITE_OK; + unixFile *pFile = (unixFile*)id; + unixInodeInfo *pInode = pFile->pInode; + afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; + + assert( pFile ); + OSTRACE(("LOCK %d %s was %s(%s,%d) pid=%d (afp)\n", pFile->h, + azFileLock(eFileLock), azFileLock(pFile->eFileLock), + azFileLock(pInode->eFileLock), pInode->nShared , osGetpid(0))); + + /* If there is already a lock of this type or more restrictive on the + ** unixFile, do nothing. Don't use the afp_end_lock: exit path, as + ** unixEnterMutex() hasn't been called yet. + */ + if( pFile->eFileLock>=eFileLock ){ + OSTRACE(("LOCK %d %s ok (already held) (afp)\n", pFile->h, + azFileLock(eFileLock))); + return SQLITE_OK; + } + + /* Make sure the locking sequence is correct + ** (1) We never move from unlocked to anything higher than shared lock. + ** (2) SQLite never explicitly requests a pending lock. + ** (3) A shared lock is always held when a reserve lock is requested. + */ + assert( pFile->eFileLock!=NO_LOCK || eFileLock==SHARED_LOCK ); + assert( eFileLock!=PENDING_LOCK ); + assert( eFileLock!=RESERVED_LOCK || pFile->eFileLock==SHARED_LOCK ); + + /* This mutex is needed because pFile->pInode is shared across threads + */ + pInode = pFile->pInode; + sqlite3_mutex_enter(pInode->pLockMutex); + + /* If some thread using this PID has a lock via a different unixFile* + ** handle that precludes the requested lock, return BUSY. + */ + if( (pFile->eFileLock!=pInode->eFileLock && + (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK)) + ){ + rc = SQLITE_BUSY; + goto afp_end_lock; + } + + /* If a SHARED lock is requested, and some thread using this PID already + ** has a SHARED or RESERVED lock, then increment reference counts and + ** return SQLITE_OK. + */ + if( eFileLock==SHARED_LOCK && + (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){ + assert( eFileLock==SHARED_LOCK ); + assert( pFile->eFileLock==0 ); + assert( pInode->nShared>0 ); + pFile->eFileLock = SHARED_LOCK; + pInode->nShared++; + pInode->nLock++; + goto afp_end_lock; + } + + /* A PENDING lock is needed before acquiring a SHARED lock and before + ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will + ** be released. + */ + if( eFileLock==SHARED_LOCK + || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLock<PENDING_LOCK) + ){ + int failed; + failed = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 1); + if (failed) { + rc = failed; + goto afp_end_lock; + } + } + + /* If control gets to this point, then actually go ahead and make + ** operating system calls for the specified lock. + */ + if( eFileLock==SHARED_LOCK ){ + int lrc1, lrc2, lrc1Errno = 0; + long lk, mask; + + assert( pInode->nShared==0 ); + assert( pInode->eFileLock==0 ); + + mask = (sizeof(long)==8) ? LARGEST_INT64 : 0x7fffffff; + /* Now get the read-lock SHARED_LOCK */ + /* note that the quality of the randomness doesn't matter that much */ + lk = random(); + pInode->sharedByte = (lk & mask)%(SHARED_SIZE - 1); + lrc1 = afpSetLock(context->dbPath, pFile, + SHARED_FIRST+pInode->sharedByte, 1, 1); + if( IS_LOCK_ERROR(lrc1) ){ + lrc1Errno = pFile->lastErrno; + } + /* Drop the temporary PENDING lock */ + lrc2 = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 0); + + if( IS_LOCK_ERROR(lrc1) ) { + storeLastErrno(pFile, lrc1Errno); + rc = lrc1; + goto afp_end_lock; + } else if( IS_LOCK_ERROR(lrc2) ){ + rc = lrc2; + goto afp_end_lock; + } else if( lrc1 != SQLITE_OK ) { + rc = lrc1; + } else { + pFile->eFileLock = SHARED_LOCK; + pInode->nLock++; + pInode->nShared = 1; + } + }else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){ + /* We are trying for an exclusive lock but another thread in this + ** same process is still holding a shared lock. */ + rc = SQLITE_BUSY; + }else{ + /* The request was for a RESERVED or EXCLUSIVE lock. It is + ** assumed that there is a SHARED or greater lock on the file + ** already. + */ + int failed = 0; + assert( 0!=pFile->eFileLock ); + if (eFileLock >= RESERVED_LOCK && pFile->eFileLock < RESERVED_LOCK) { + /* Acquire a RESERVED lock */ + failed = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1); + if( !failed ){ + context->reserved = 1; + } + } + if (!failed && eFileLock == EXCLUSIVE_LOCK) { + /* Acquire an EXCLUSIVE lock */ + + /* Remove the shared lock before trying the range. we'll need to + ** reestablish the shared lock if we can't get the afpUnlock + */ + if( !(failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST + + pInode->sharedByte, 1, 0)) ){ + int failed2 = SQLITE_OK; + /* now attempt to get the exclusive lock range */ + failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST, + SHARED_SIZE, 1); + if( failed && (failed2 = afpSetLock(context->dbPath, pFile, + SHARED_FIRST + pInode->sharedByte, 1, 1)) ){ + /* Can't reestablish the shared lock. Sqlite can't deal, this is + ** a critical I/O error + */ + rc = ((failed & 0xff) == SQLITE_IOERR) ? failed2 : + SQLITE_IOERR_LOCK; + goto afp_end_lock; + } + }else{ + rc = failed; + } + } + if( failed ){ + rc = failed; + } + } + + if( rc==SQLITE_OK ){ + pFile->eFileLock = eFileLock; + pInode->eFileLock = eFileLock; + }else if( eFileLock==EXCLUSIVE_LOCK ){ + pFile->eFileLock = PENDING_LOCK; + pInode->eFileLock = PENDING_LOCK; + } + +afp_end_lock: + sqlite3_mutex_leave(pInode->pLockMutex); + OSTRACE(("LOCK %d %s %s (afp)\n", pFile->h, azFileLock(eFileLock), + rc==SQLITE_OK ? "ok" : "failed")); + return rc; +} + +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +*/ +static int afpUnlock(sqlite3_file *id, int eFileLock) { + int rc = SQLITE_OK; + unixFile *pFile = (unixFile*)id; + unixInodeInfo *pInode; + afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; + int skipShared = 0; +#ifdef SQLITE_TEST + int h = pFile->h; +#endif + + assert( pFile ); + OSTRACE(("UNLOCK %d %d was %d(%d,%d) pid=%d (afp)\n", pFile->h, eFileLock, + pFile->eFileLock, pFile->pInode->eFileLock, pFile->pInode->nShared, + osGetpid(0))); + + assert( eFileLock<=SHARED_LOCK ); + if( pFile->eFileLock<=eFileLock ){ + return SQLITE_OK; + } + pInode = pFile->pInode; + sqlite3_mutex_enter(pInode->pLockMutex); + assert( pInode->nShared!=0 ); + if( pFile->eFileLock>SHARED_LOCK ){ + assert( pInode->eFileLock==pFile->eFileLock ); + SimulateIOErrorBenign(1); + SimulateIOError( h=(-1) ) + SimulateIOErrorBenign(0); + +#ifdef SQLITE_DEBUG + /* When reducing a lock such that other processes can start + ** reading the database file again, make sure that the + ** transaction counter was updated if any part of the database + ** file changed. If the transaction counter is not updated, + ** other connections to the same file might not realize that + ** the file has changed and hence might not know to flush their + ** cache. The use of a stale cache can lead to database corruption. + */ + assert( pFile->inNormalWrite==0 + || pFile->dbUpdate==0 + || pFile->transCntrChng==1 ); + pFile->inNormalWrite = 0; +#endif + + if( pFile->eFileLock==EXCLUSIVE_LOCK ){ + rc = afpSetLock(context->dbPath, pFile, SHARED_FIRST, SHARED_SIZE, 0); + if( rc==SQLITE_OK && (eFileLock==SHARED_LOCK || pInode->nShared>1) ){ + /* only re-establish the shared lock if necessary */ + int sharedLockByte = SHARED_FIRST+pInode->sharedByte; + rc = afpSetLock(context->dbPath, pFile, sharedLockByte, 1, 1); + } else { + skipShared = 1; + } + } + if( rc==SQLITE_OK && pFile->eFileLock>=PENDING_LOCK ){ + rc = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 0); + } + if( rc==SQLITE_OK && pFile->eFileLock>=RESERVED_LOCK && context->reserved ){ + rc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1, 0); + if( !rc ){ + context->reserved = 0; + } + } + if( rc==SQLITE_OK && (eFileLock==SHARED_LOCK || pInode->nShared>1)){ + pInode->eFileLock = SHARED_LOCK; + } + } + if( rc==SQLITE_OK && eFileLock==NO_LOCK ){ + + /* Decrement the shared lock counter. Release the lock using an + ** OS call only when all threads in this same process have released + ** the lock. + */ + unsigned long long sharedLockByte = SHARED_FIRST+pInode->sharedByte; + pInode->nShared--; + if( pInode->nShared==0 ){ + SimulateIOErrorBenign(1); + SimulateIOError( h=(-1) ) + SimulateIOErrorBenign(0); + if( !skipShared ){ + rc = afpSetLock(context->dbPath, pFile, sharedLockByte, 1, 0); + } + if( !rc ){ + pInode->eFileLock = NO_LOCK; + pFile->eFileLock = NO_LOCK; + } + } + if( rc==SQLITE_OK ){ + pInode->nLock--; + assert( pInode->nLock>=0 ); + if( pInode->nLock==0 ) closePendingFds(pFile); + } + } + + sqlite3_mutex_leave(pInode->pLockMutex); + if( rc==SQLITE_OK ){ + pFile->eFileLock = eFileLock; + } + return rc; +} + +/* +** Close a file & cleanup AFP specific locking context +*/ +static int afpClose(sqlite3_file *id) { + int rc = SQLITE_OK; + unixFile *pFile = (unixFile*)id; + assert( id!=0 ); + afpUnlock(id, NO_LOCK); + assert( unixFileMutexNotheld(pFile) ); + unixEnterMutex(); + if( pFile->pInode ){ + unixInodeInfo *pInode = pFile->pInode; + sqlite3_mutex_enter(pInode->pLockMutex); + if( pInode->nLock ){ + /* If there are outstanding locks, do not actually close the file just + ** yet because that would clear those locks. Instead, add the file + ** descriptor to pInode->aPending. It will be automatically closed when + ** the last lock is cleared. + */ + setPendingFd(pFile); + } + sqlite3_mutex_leave(pInode->pLockMutex); + } + releaseInodeInfo(pFile); + sqlite3_free(pFile->lockingContext); + rc = closeUnixFile(id); + unixLeaveMutex(); + return rc; +} + +#endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ +/* +** The code above is the AFP lock implementation. The code is specific +** to MacOSX and does not work on other unix platforms. No alternative +** is available. If you don't compile for a mac, then the "unix-afp" +** VFS is not available. +** +********************* End of the AFP lock implementation ********************** +******************************************************************************/ + +/****************************************************************************** +*************************** Begin NFS Locking ********************************/ + +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE +/* + ** Lower the locking level on file descriptor pFile to eFileLock. eFileLock + ** must be either NO_LOCK or SHARED_LOCK. + ** + ** If the locking level of the file descriptor is already at or below + ** the requested locking level, this routine is a no-op. + */ +static int nfsUnlock(sqlite3_file *id, int eFileLock){ + return posixUnlock(id, eFileLock, 1); +} + +#endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ +/* +** The code above is the NFS lock implementation. The code is specific +** to MacOSX and does not work on other unix platforms. No alternative +** is available. +** +********************* End of the NFS lock implementation ********************** +******************************************************************************/ + +/****************************************************************************** +**************** Non-locking sqlite3_file methods ***************************** +** +** The next division contains implementations for all methods of the +** sqlite3_file object other than the locking methods. The locking +** methods were defined in divisions above (one locking method per +** division). Those methods that are common to all locking modes +** are gather together into this division. +*/ + +/* +** Seek to the offset passed as the second argument, then read cnt +** bytes into pBuf. Return the number of bytes actually read. +** +** To avoid stomping the errno value on a failed read the lastErrno value +** is set before returning. +*/ +static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){ + int got; + int prior = 0; +#if (!defined(USE_PREAD) && !defined(USE_PREAD64)) + i64 newOffset; +#endif + TIMER_START; + assert( cnt==(cnt&0x1ffff) ); + assert( id->h>2 ); + do{ +#if defined(USE_PREAD) + got = osPread(id->h, pBuf, cnt, offset); + SimulateIOError( got = -1 ); +#elif defined(USE_PREAD64) + got = osPread64(id->h, pBuf, cnt, offset); + SimulateIOError( got = -1 ); +#else + newOffset = lseek(id->h, offset, SEEK_SET); + SimulateIOError( newOffset = -1 ); + if( newOffset<0 ){ + storeLastErrno((unixFile*)id, errno); + return -1; + } + got = osRead(id->h, pBuf, cnt); +#endif + if( got==cnt ) break; + if( got<0 ){ + if( errno==EINTR ){ got = 1; continue; } + prior = 0; + storeLastErrno((unixFile*)id, errno); + break; + }else if( got>0 ){ + cnt -= got; + offset += got; + prior += got; + pBuf = (void*)(got + (char*)pBuf); + } + }while( got>0 ); + TIMER_END; + OSTRACE(("READ %-3d %5d %7lld %llu\n", + id->h, got+prior, offset-prior, TIMER_ELAPSED)); + return got+prior; +} + +/* +** Read data from a file into a buffer. Return SQLITE_OK if all +** bytes were read successfully and SQLITE_IOERR if anything goes +** wrong. +*/ +static int unixRead( + sqlite3_file *id, + void *pBuf, + int amt, + sqlite3_int64 offset +){ + unixFile *pFile = (unixFile *)id; + int got; + assert( id ); + assert( offset>=0 ); + assert( amt>0 ); + + /* If this is a database file (not a journal, super-journal or temp + ** file), the bytes in the locking range should never be read or written. */ +#if 0 + assert( pFile->pPreallocatedUnused==0 + || offset>=PENDING_BYTE+512 + || offset+amt<=PENDING_BYTE + ); +#endif + +#if SQLITE_MAX_MMAP_SIZE>0 + /* Deal with as much of this read request as possible by transferring + ** data from the memory mapping using memcpy(). */ + if( offset<pFile->mmapSize ){ + if( offset+amt <= pFile->mmapSize ){ + memcpy(pBuf, &((u8 *)(pFile->pMapRegion))[offset], amt); + return SQLITE_OK; + }else{ + int nCopy = pFile->mmapSize - offset; + memcpy(pBuf, &((u8 *)(pFile->pMapRegion))[offset], nCopy); + pBuf = &((u8 *)pBuf)[nCopy]; + amt -= nCopy; + offset += nCopy; + } + } +#endif + + got = seekAndRead(pFile, offset, pBuf, amt); + if( got==amt ){ + return SQLITE_OK; + }else if( got<0 ){ + /* pFile->lastErrno has been set by seekAndRead(). + ** Usually we return SQLITE_IOERR_READ here, though for some + ** kinds of errors we return SQLITE_IOERR_CORRUPTFS. The + ** SQLITE_IOERR_CORRUPTFS will be converted into SQLITE_CORRUPT + ** prior to returning to the application by the sqlite3ApiExit() + ** routine. + */ + switch( pFile->lastErrno ){ + case ERANGE: + case EIO: +#ifdef ENXIO + case ENXIO: +#endif +#ifdef EDEVERR + case EDEVERR: +#endif + return SQLITE_IOERR_CORRUPTFS; + } + return SQLITE_IOERR_READ; + }else{ + storeLastErrno(pFile, 0); /* not a system error */ + /* Unread parts of the buffer must be zero-filled */ + memset(&((char*)pBuf)[got], 0, amt-got); + return SQLITE_IOERR_SHORT_READ; + } +} + +/* +** Attempt to seek the file-descriptor passed as the first argument to +** absolute offset iOff, then attempt to write nBuf bytes of data from +** pBuf to it. If an error occurs, return -1 and set *piErrno. Otherwise, +** return the actual number of bytes written (which may be less than +** nBuf). +*/ +static int seekAndWriteFd( + int fd, /* File descriptor to write to */ + i64 iOff, /* File offset to begin writing at */ + const void *pBuf, /* Copy data from this buffer to the file */ + int nBuf, /* Size of buffer pBuf in bytes */ + int *piErrno /* OUT: Error number if error occurs */ +){ + int rc = 0; /* Value returned by system call */ + + assert( nBuf==(nBuf&0x1ffff) ); + assert( fd>2 ); + assert( piErrno!=0 ); + nBuf &= 0x1ffff; + TIMER_START; + +#if defined(USE_PREAD) + do{ rc = (int)osPwrite(fd, pBuf, nBuf, iOff); }while( rc<0 && errno==EINTR ); +#elif defined(USE_PREAD64) + do{ rc = (int)osPwrite64(fd, pBuf, nBuf, iOff);}while( rc<0 && errno==EINTR); +#else + do{ + i64 iSeek = lseek(fd, iOff, SEEK_SET); + SimulateIOError( iSeek = -1 ); + if( iSeek<0 ){ + rc = -1; + break; + } + rc = osWrite(fd, pBuf, nBuf); + }while( rc<0 && errno==EINTR ); +#endif + + TIMER_END; + OSTRACE(("WRITE %-3d %5d %7lld %llu\n", fd, rc, iOff, TIMER_ELAPSED)); + + if( rc<0 ) *piErrno = errno; + return rc; +} + + +/* +** Seek to the offset in id->offset then read cnt bytes into pBuf. +** Return the number of bytes actually read. Update the offset. +** +** To avoid stomping the errno value on a failed write the lastErrno value +** is set before returning. +*/ +static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){ + return seekAndWriteFd(id->h, offset, pBuf, cnt, &id->lastErrno); +} + + +/* +** Write data from a buffer into a file. Return SQLITE_OK on success +** or some other error code on failure. +*/ +static int unixWrite( + sqlite3_file *id, + const void *pBuf, + int amt, + sqlite3_int64 offset +){ + unixFile *pFile = (unixFile*)id; + int wrote = 0; + assert( id ); + assert( amt>0 ); + + /* If this is a database file (not a journal, super-journal or temp + ** file), the bytes in the locking range should never be read or written. */ +#if 0 + assert( pFile->pPreallocatedUnused==0 + || offset>=PENDING_BYTE+512 + || offset+amt<=PENDING_BYTE + ); +#endif + +#ifdef SQLITE_DEBUG + /* If we are doing a normal write to a database file (as opposed to + ** doing a hot-journal rollback or a write to some file other than a + ** normal database file) then record the fact that the database + ** has changed. If the transaction counter is modified, record that + ** fact too. + */ + if( pFile->inNormalWrite ){ + pFile->dbUpdate = 1; /* The database has been modified */ + if( offset<=24 && offset+amt>=27 ){ + int rc; + char oldCntr[4]; + SimulateIOErrorBenign(1); + rc = seekAndRead(pFile, 24, oldCntr, 4); + SimulateIOErrorBenign(0); + if( rc!=4 || memcmp(oldCntr, &((char*)pBuf)[24-offset], 4)!=0 ){ + pFile->transCntrChng = 1; /* The transaction counter has changed */ + } + } + } +#endif + +#if defined(SQLITE_MMAP_READWRITE) && SQLITE_MAX_MMAP_SIZE>0 + /* Deal with as much of this write request as possible by transferring + ** data from the memory mapping using memcpy(). */ + if( offset<pFile->mmapSize ){ + if( offset+amt <= pFile->mmapSize ){ + memcpy(&((u8 *)(pFile->pMapRegion))[offset], pBuf, amt); + return SQLITE_OK; + }else{ + int nCopy = pFile->mmapSize - offset; + memcpy(&((u8 *)(pFile->pMapRegion))[offset], pBuf, nCopy); + pBuf = &((u8 *)pBuf)[nCopy]; + amt -= nCopy; + offset += nCopy; + } + } +#endif + + while( (wrote = seekAndWrite(pFile, offset, pBuf, amt))<amt && wrote>0 ){ + amt -= wrote; + offset += wrote; + pBuf = &((char*)pBuf)[wrote]; + } + SimulateIOError(( wrote=(-1), amt=1 )); + SimulateDiskfullError(( wrote=0, amt=1 )); + + if( amt>wrote ){ + if( wrote<0 && pFile->lastErrno!=ENOSPC ){ + /* lastErrno set by seekAndWrite */ + return SQLITE_IOERR_WRITE; + }else{ + storeLastErrno(pFile, 0); /* not a system error */ + return SQLITE_FULL; + } + } + + return SQLITE_OK; +} + +#ifdef SQLITE_TEST +/* +** Count the number of fullsyncs and normal syncs. This is used to test +** that syncs and fullsyncs are occurring at the right times. +*/ +SQLITE_API int sqlite3_sync_count = 0; +SQLITE_API int sqlite3_fullsync_count = 0; +#endif + +/* +** We do not trust systems to provide a working fdatasync(). Some do. +** Others do no. To be safe, we will stick with the (slightly slower) +** fsync(). If you know that your system does support fdatasync() correctly, +** then simply compile with -Dfdatasync=fdatasync or -DHAVE_FDATASYNC +*/ +#if !defined(fdatasync) && !HAVE_FDATASYNC +# define fdatasync fsync +#endif + +/* +** Define HAVE_FULLFSYNC to 0 or 1 depending on whether or not +** the F_FULLFSYNC macro is defined. F_FULLFSYNC is currently +** only available on Mac OS X. But that could change. +*/ +#ifdef F_FULLFSYNC +# define HAVE_FULLFSYNC 1 +#else +# define HAVE_FULLFSYNC 0 +#endif + + +/* +** The fsync() system call does not work as advertised on many +** unix systems. The following procedure is an attempt to make +** it work better. +** +** The SQLITE_NO_SYNC macro disables all fsync()s. This is useful +** for testing when we want to run through the test suite quickly. +** You are strongly advised *not* to deploy with SQLITE_NO_SYNC +** enabled, however, since with SQLITE_NO_SYNC enabled, an OS crash +** or power failure will likely corrupt the database file. +** +** SQLite sets the dataOnly flag if the size of the file is unchanged. +** The idea behind dataOnly is that it should only write the file content +** to disk, not the inode. We only set dataOnly if the file size is +** unchanged since the file size is part of the inode. However, +** Ted Ts'o tells us that fdatasync() will also write the inode if the +** file size has changed. The only real difference between fdatasync() +** and fsync(), Ted tells us, is that fdatasync() will not flush the +** inode if the mtime or owner or other inode attributes have changed. +** We only care about the file size, not the other file attributes, so +** as far as SQLite is concerned, an fdatasync() is always adequate. +** So, we always use fdatasync() if it is available, regardless of +** the value of the dataOnly flag. +*/ +static int full_fsync(int fd, int fullSync, int dataOnly){ + int rc; + + /* The following "ifdef/elif/else/" block has the same structure as + ** the one below. It is replicated here solely to avoid cluttering + ** up the real code with the UNUSED_PARAMETER() macros. + */ +#ifdef SQLITE_NO_SYNC + UNUSED_PARAMETER(fd); + UNUSED_PARAMETER(fullSync); + UNUSED_PARAMETER(dataOnly); +#elif HAVE_FULLFSYNC + UNUSED_PARAMETER(dataOnly); +#else + UNUSED_PARAMETER(fullSync); + UNUSED_PARAMETER(dataOnly); +#endif + + /* Record the number of times that we do a normal fsync() and + ** FULLSYNC. This is used during testing to verify that this procedure + ** gets called with the correct arguments. + */ +#ifdef SQLITE_TEST + if( fullSync ) sqlite3_fullsync_count++; + sqlite3_sync_count++; +#endif + + /* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a + ** no-op. But go ahead and call fstat() to validate the file + ** descriptor as we need a method to provoke a failure during + ** coverage testing. + */ +#ifdef SQLITE_NO_SYNC + { + struct stat buf; + rc = osFstat(fd, &buf); + } +#elif HAVE_FULLFSYNC + if( fullSync ){ + rc = osFcntl(fd, F_FULLFSYNC, 0); + }else{ + rc = 1; + } + /* If the FULLFSYNC failed, fall back to attempting an fsync(). + ** It shouldn't be possible for fullfsync to fail on the local + ** file system (on OSX), so failure indicates that FULLFSYNC + ** isn't supported for this file system. So, attempt an fsync + ** and (for now) ignore the overhead of a superfluous fcntl call. + ** It'd be better to detect fullfsync support once and avoid + ** the fcntl call every time sync is called. + */ + if( rc ) rc = fsync(fd); + +#elif defined(__APPLE__) + /* fdatasync() on HFS+ doesn't yet flush the file size if it changed correctly + ** so currently we default to the macro that redefines fdatasync to fsync + */ + rc = fsync(fd); +#else + rc = fdatasync(fd); +#if OS_VXWORKS + if( rc==-1 && errno==ENOTSUP ){ + rc = fsync(fd); + } +#endif /* OS_VXWORKS */ +#endif /* ifdef SQLITE_NO_SYNC elif HAVE_FULLFSYNC */ + + if( OS_VXWORKS && rc!= -1 ){ + rc = 0; + } + return rc; +} + +/* +** Open a file descriptor to the directory containing file zFilename. +** If successful, *pFd is set to the opened file descriptor and +** SQLITE_OK is returned. If an error occurs, either SQLITE_NOMEM +** or SQLITE_CANTOPEN is returned and *pFd is set to an undefined +** value. +** +** The directory file descriptor is used for only one thing - to +** fsync() a directory to make sure file creation and deletion events +** are flushed to disk. Such fsyncs are not needed on newer +** journaling filesystems, but are required on older filesystems. +** +** This routine can be overridden using the xSetSysCall interface. +** The ability to override this routine was added in support of the +** chromium sandbox. Opening a directory is a security risk (we are +** told) so making it overrideable allows the chromium sandbox to +** replace this routine with a harmless no-op. To make this routine +** a no-op, replace it with a stub that returns SQLITE_OK but leaves +** *pFd set to a negative number. +** +** If SQLITE_OK is returned, the caller is responsible for closing +** the file descriptor *pFd using close(). +*/ +static int openDirectory(const char *zFilename, int *pFd){ + int ii; + int fd = -1; + char zDirname[MAX_PATHNAME+1]; + + sqlite3_snprintf(MAX_PATHNAME, zDirname, "%s", zFilename); + for(ii=(int)strlen(zDirname); ii>0 && zDirname[ii]!='/'; ii--); + if( ii>0 ){ + zDirname[ii] = '\0'; + }else{ + if( zDirname[0]!='/' ) zDirname[0] = '.'; + zDirname[1] = 0; + } + fd = robust_open(zDirname, O_RDONLY|O_BINARY, 0); + if( fd>=0 ){ + OSTRACE(("OPENDIR %-3d %s\n", fd, zDirname)); + } + *pFd = fd; + if( fd>=0 ) return SQLITE_OK; + return unixLogError(SQLITE_CANTOPEN_BKPT, "openDirectory", zDirname); +} + +/* +** Make sure all writes to a particular file are committed to disk. +** +** If dataOnly==0 then both the file itself and its metadata (file +** size, access time, etc) are synced. If dataOnly!=0 then only the +** file data is synced. +** +** Under Unix, also make sure that the directory entry for the file +** has been created by fsync-ing the directory that contains the file. +** If we do not do this and we encounter a power failure, the directory +** entry for the journal might not exist after we reboot. The next +** SQLite to access the file will not know that the journal exists (because +** the directory entry for the journal was never created) and the transaction +** will not roll back - possibly leading to database corruption. +*/ +static int unixSync(sqlite3_file *id, int flags){ + int rc; + unixFile *pFile = (unixFile*)id; + + int isDataOnly = (flags&SQLITE_SYNC_DATAONLY); + int isFullsync = (flags&0x0F)==SQLITE_SYNC_FULL; + + /* Check that one of SQLITE_SYNC_NORMAL or FULL was passed */ + assert((flags&0x0F)==SQLITE_SYNC_NORMAL + || (flags&0x0F)==SQLITE_SYNC_FULL + ); + + /* Unix cannot, but some systems may return SQLITE_FULL from here. This + ** line is to test that doing so does not cause any problems. + */ + SimulateDiskfullError( return SQLITE_FULL ); + + assert( pFile ); + OSTRACE(("SYNC %-3d\n", pFile->h)); + rc = full_fsync(pFile->h, isFullsync, isDataOnly); + SimulateIOError( rc=1 ); + if( rc ){ + storeLastErrno(pFile, errno); + return unixLogError(SQLITE_IOERR_FSYNC, "full_fsync", pFile->zPath); + } + + /* Also fsync the directory containing the file if the DIRSYNC flag + ** is set. This is a one-time occurrence. Many systems (examples: AIX) + ** are unable to fsync a directory, so ignore errors on the fsync. + */ + if( pFile->ctrlFlags & UNIXFILE_DIRSYNC ){ + int dirfd; + OSTRACE(("DIRSYNC %s (have_fullfsync=%d fullsync=%d)\n", pFile->zPath, + HAVE_FULLFSYNC, isFullsync)); + rc = osOpenDirectory(pFile->zPath, &dirfd); + if( rc==SQLITE_OK ){ + full_fsync(dirfd, 0, 0); + robust_close(pFile, dirfd, __LINE__); + }else{ + assert( rc==SQLITE_CANTOPEN ); + rc = SQLITE_OK; + } + pFile->ctrlFlags &= ~UNIXFILE_DIRSYNC; + } + return rc; +} + +/* +** Truncate an open file to a specified size +*/ +static int unixTruncate(sqlite3_file *id, i64 nByte){ + unixFile *pFile = (unixFile *)id; + int rc; + assert( pFile ); + SimulateIOError( return SQLITE_IOERR_TRUNCATE ); + + /* If the user has configured a chunk-size for this file, truncate the + ** file so that it consists of an integer number of chunks (i.e. the + ** actual file size after the operation may be larger than the requested + ** size). + */ + if( pFile->szChunk>0 ){ + nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk; + } + + rc = robust_ftruncate(pFile->h, nByte); + if( rc ){ + storeLastErrno(pFile, errno); + return unixLogError(SQLITE_IOERR_TRUNCATE, "ftruncate", pFile->zPath); + }else{ +#ifdef SQLITE_DEBUG + /* If we are doing a normal write to a database file (as opposed to + ** doing a hot-journal rollback or a write to some file other than a + ** normal database file) and we truncate the file to zero length, + ** that effectively updates the change counter. This might happen + ** when restoring a database using the backup API from a zero-length + ** source. + */ + if( pFile->inNormalWrite && nByte==0 ){ + pFile->transCntrChng = 1; + } +#endif + +#if SQLITE_MAX_MMAP_SIZE>0 + /* If the file was just truncated to a size smaller than the currently + ** mapped region, reduce the effective mapping size as well. SQLite will + ** use read() and write() to access data beyond this point from now on. + */ + if( nByte<pFile->mmapSize ){ + pFile->mmapSize = nByte; + } +#endif + + return SQLITE_OK; + } +} + +/* +** Determine the current size of a file in bytes +*/ +static int unixFileSize(sqlite3_file *id, i64 *pSize){ + int rc; + struct stat buf; + assert( id ); + rc = osFstat(((unixFile*)id)->h, &buf); + SimulateIOError( rc=1 ); + if( rc!=0 ){ + storeLastErrno((unixFile*)id, errno); + return SQLITE_IOERR_FSTAT; + } + *pSize = buf.st_size; + + /* When opening a zero-size database, the findInodeInfo() procedure + ** writes a single byte into that file in order to work around a bug + ** in the OS-X msdos filesystem. In order to avoid problems with upper + ** layers, we need to report this file size as zero even though it is + ** really 1. Ticket #3260. + */ + if( *pSize==1 ) *pSize = 0; + + + return SQLITE_OK; +} + +#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) +/* +** Handler for proxy-locking file-control verbs. Defined below in the +** proxying locking division. +*/ +static int proxyFileControl(sqlite3_file*,int,void*); +#endif + +/* +** This function is called to handle the SQLITE_FCNTL_SIZE_HINT +** file-control operation. Enlarge the database to nBytes in size +** (rounded up to the next chunk-size). If the database is already +** nBytes or larger, this routine is a no-op. +*/ +static int fcntlSizeHint(unixFile *pFile, i64 nByte){ + if( pFile->szChunk>0 ){ + i64 nSize; /* Required file size */ + struct stat buf; /* Used to hold return values of fstat() */ + + if( osFstat(pFile->h, &buf) ){ + return SQLITE_IOERR_FSTAT; + } + + nSize = ((nByte+pFile->szChunk-1) / pFile->szChunk) * pFile->szChunk; + if( nSize>(i64)buf.st_size ){ + +#if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE + /* The code below is handling the return value of osFallocate() + ** correctly. posix_fallocate() is defined to "returns zero on success, + ** or an error number on failure". See the manpage for details. */ + int err; + do{ + err = osFallocate(pFile->h, buf.st_size, nSize-buf.st_size); + }while( err==EINTR ); + if( err && err!=EINVAL ) return SQLITE_IOERR_WRITE; +#else + /* If the OS does not have posix_fallocate(), fake it. Write a + ** single byte to the last byte in each block that falls entirely + ** within the extended region. Then, if required, a single byte + ** at offset (nSize-1), to set the size of the file correctly. + ** This is a similar technique to that used by glibc on systems + ** that do not have a real fallocate() call. + */ + int nBlk = buf.st_blksize; /* File-system block size */ + int nWrite = 0; /* Number of bytes written by seekAndWrite */ + i64 iWrite; /* Next offset to write to */ + + iWrite = (buf.st_size/nBlk)*nBlk + nBlk - 1; + assert( iWrite>=buf.st_size ); + assert( ((iWrite+1)%nBlk)==0 ); + for(/*no-op*/; iWrite<nSize+nBlk-1; iWrite+=nBlk ){ + if( iWrite>=nSize ) iWrite = nSize - 1; + nWrite = seekAndWrite(pFile, iWrite, "", 1); + if( nWrite!=1 ) return SQLITE_IOERR_WRITE; + } +#endif + } + } + +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFile->mmapSizeMax>0 && nByte>pFile->mmapSize ){ + int rc; + if( pFile->szChunk<=0 ){ + if( robust_ftruncate(pFile->h, nByte) ){ + storeLastErrno(pFile, errno); + return unixLogError(SQLITE_IOERR_TRUNCATE, "ftruncate", pFile->zPath); + } + } + + rc = unixMapfile(pFile, nByte); + return rc; + } +#endif + + return SQLITE_OK; +} + +/* +** If *pArg is initially negative then this is a query. Set *pArg to +** 1 or 0 depending on whether or not bit mask of pFile->ctrlFlags is set. +** +** If *pArg is 0 or 1, then clear or set the mask bit of pFile->ctrlFlags. +*/ +static void unixModeBit(unixFile *pFile, unsigned char mask, int *pArg){ + if( *pArg<0 ){ + *pArg = (pFile->ctrlFlags & mask)!=0; + }else if( (*pArg)==0 ){ + pFile->ctrlFlags &= ~mask; + }else{ + pFile->ctrlFlags |= mask; + } +} + +/* Forward declaration */ +static int unixGetTempname(int nBuf, char *zBuf); +#ifndef SQLITE_OMIT_WAL + static int unixFcntlExternalReader(unixFile*, int*); +#endif + +/* +** Information and control of an open file handle. +*/ +static int unixFileControl(sqlite3_file *id, int op, void *pArg){ + unixFile *pFile = (unixFile*)id; + switch( op ){ +#if defined(__linux__) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) + case SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: { + int rc = osIoctl(pFile->h, F2FS_IOC_START_ATOMIC_WRITE); + return rc ? SQLITE_IOERR_BEGIN_ATOMIC : SQLITE_OK; + } + case SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: { + int rc = osIoctl(pFile->h, F2FS_IOC_COMMIT_ATOMIC_WRITE); + return rc ? SQLITE_IOERR_COMMIT_ATOMIC : SQLITE_OK; + } + case SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: { + int rc = osIoctl(pFile->h, F2FS_IOC_ABORT_VOLATILE_WRITE); + return rc ? SQLITE_IOERR_ROLLBACK_ATOMIC : SQLITE_OK; + } +#endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */ + + case SQLITE_FCNTL_LOCKSTATE: { + *(int*)pArg = pFile->eFileLock; + return SQLITE_OK; + } + case SQLITE_FCNTL_LAST_ERRNO: { + *(int*)pArg = pFile->lastErrno; + return SQLITE_OK; + } + case SQLITE_FCNTL_CHUNK_SIZE: { + pFile->szChunk = *(int *)pArg; + return SQLITE_OK; + } + case SQLITE_FCNTL_SIZE_HINT: { + int rc; + SimulateIOErrorBenign(1); + rc = fcntlSizeHint(pFile, *(i64 *)pArg); + SimulateIOErrorBenign(0); + return rc; + } + case SQLITE_FCNTL_PERSIST_WAL: { + unixModeBit(pFile, UNIXFILE_PERSIST_WAL, (int*)pArg); + return SQLITE_OK; + } + case SQLITE_FCNTL_POWERSAFE_OVERWRITE: { + unixModeBit(pFile, UNIXFILE_PSOW, (int*)pArg); + return SQLITE_OK; + } + case SQLITE_FCNTL_VFSNAME: { + *(char**)pArg = sqlite3_mprintf("%s", pFile->pVfs->zName); + return SQLITE_OK; + } + case SQLITE_FCNTL_TEMPFILENAME: { + char *zTFile = sqlite3_malloc64( pFile->pVfs->mxPathname ); + if( zTFile ){ + unixGetTempname(pFile->pVfs->mxPathname, zTFile); + *(char**)pArg = zTFile; + } + return SQLITE_OK; + } + case SQLITE_FCNTL_HAS_MOVED: { + *(int*)pArg = fileHasMoved(pFile); + return SQLITE_OK; + } +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + case SQLITE_FCNTL_LOCK_TIMEOUT: { + int iOld = pFile->iBusyTimeout; + pFile->iBusyTimeout = *(int*)pArg; + *(int*)pArg = iOld; + return SQLITE_OK; + } +#endif +#if SQLITE_MAX_MMAP_SIZE>0 + case SQLITE_FCNTL_MMAP_SIZE: { + i64 newLimit = *(i64*)pArg; + int rc = SQLITE_OK; + if( newLimit>sqlite3GlobalConfig.mxMmap ){ + newLimit = sqlite3GlobalConfig.mxMmap; + } + + /* The value of newLimit may be eventually cast to (size_t) and passed + ** to mmap(). Restrict its value to 2GB if (size_t) is not at least a + ** 64-bit type. */ + if( newLimit>0 && sizeof(size_t)<8 ){ + newLimit = (newLimit & 0x7FFFFFFF); + } + + *(i64*)pArg = pFile->mmapSizeMax; + if( newLimit>=0 && newLimit!=pFile->mmapSizeMax && pFile->nFetchOut==0 ){ + pFile->mmapSizeMax = newLimit; + if( pFile->mmapSize>0 ){ + unixUnmapfile(pFile); + rc = unixMapfile(pFile, -1); + } + } + return rc; + } +#endif +#ifdef SQLITE_DEBUG + /* The pager calls this method to signal that it has done + ** a rollback and that the database is therefore unchanged and + ** it hence it is OK for the transaction change counter to be + ** unchanged. + */ + case SQLITE_FCNTL_DB_UNCHANGED: { + ((unixFile*)id)->dbUpdate = 0; + return SQLITE_OK; + } +#endif +#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) + case SQLITE_FCNTL_SET_LOCKPROXYFILE: + case SQLITE_FCNTL_GET_LOCKPROXYFILE: { + return proxyFileControl(id,op,pArg); + } +#endif /* SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) */ + + case SQLITE_FCNTL_EXTERNAL_READER: { +#ifndef SQLITE_OMIT_WAL + return unixFcntlExternalReader((unixFile*)id, (int*)pArg); +#else + *(int*)pArg = 0; + return SQLITE_OK; +#endif + } + } + return SQLITE_NOTFOUND; +} + +/* +** If pFd->sectorSize is non-zero when this function is called, it is a +** no-op. Otherwise, the values of pFd->sectorSize and +** pFd->deviceCharacteristics are set according to the file-system +** characteristics. +** +** There are two versions of this function. One for QNX and one for all +** other systems. +*/ +#ifndef __QNXNTO__ +static void setDeviceCharacteristics(unixFile *pFd){ + assert( pFd->deviceCharacteristics==0 || pFd->sectorSize!=0 ); + if( pFd->sectorSize==0 ){ +#if defined(__linux__) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) + int res; + u32 f = 0; + + /* Check for support for F2FS atomic batch writes. */ + res = osIoctl(pFd->h, F2FS_IOC_GET_FEATURES, &f); + if( res==0 && (f & F2FS_FEATURE_ATOMIC_WRITE) ){ + pFd->deviceCharacteristics = SQLITE_IOCAP_BATCH_ATOMIC; + } +#endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */ + + /* Set the POWERSAFE_OVERWRITE flag if requested. */ + if( pFd->ctrlFlags & UNIXFILE_PSOW ){ + pFd->deviceCharacteristics |= SQLITE_IOCAP_POWERSAFE_OVERWRITE; + } + + pFd->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; + } +} +#else +#include <sys/dcmd_blk.h> +#include <sys/statvfs.h> +static void setDeviceCharacteristics(unixFile *pFile){ + if( pFile->sectorSize == 0 ){ + struct statvfs fsInfo; + + /* Set defaults for non-supported filesystems */ + pFile->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; + pFile->deviceCharacteristics = 0; + if( fstatvfs(pFile->h, &fsInfo) == -1 ) { + return; + } + + if( !strcmp(fsInfo.f_basetype, "tmp") ) { + pFile->sectorSize = fsInfo.f_bsize; + pFile->deviceCharacteristics = + SQLITE_IOCAP_ATOMIC4K | /* All ram filesystem writes are atomic */ + SQLITE_IOCAP_SAFE_APPEND | /* growing the file does not occur until + ** the write succeeds */ + SQLITE_IOCAP_SEQUENTIAL | /* The ram filesystem has no write behind + ** so it is ordered */ + 0; + }else if( strstr(fsInfo.f_basetype, "etfs") ){ + pFile->sectorSize = fsInfo.f_bsize; + pFile->deviceCharacteristics = + /* etfs cluster size writes are atomic */ + (pFile->sectorSize / 512 * SQLITE_IOCAP_ATOMIC512) | + SQLITE_IOCAP_SAFE_APPEND | /* growing the file does not occur until + ** the write succeeds */ + SQLITE_IOCAP_SEQUENTIAL | /* The ram filesystem has no write behind + ** so it is ordered */ + 0; + }else if( !strcmp(fsInfo.f_basetype, "qnx6") ){ + pFile->sectorSize = fsInfo.f_bsize; + pFile->deviceCharacteristics = + SQLITE_IOCAP_ATOMIC | /* All filesystem writes are atomic */ + SQLITE_IOCAP_SAFE_APPEND | /* growing the file does not occur until + ** the write succeeds */ + SQLITE_IOCAP_SEQUENTIAL | /* The ram filesystem has no write behind + ** so it is ordered */ + 0; + }else if( !strcmp(fsInfo.f_basetype, "qnx4") ){ + pFile->sectorSize = fsInfo.f_bsize; + pFile->deviceCharacteristics = + /* full bitset of atomics from max sector size and smaller */ + ((pFile->sectorSize / 512 * SQLITE_IOCAP_ATOMIC512) << 1) - 2 | + SQLITE_IOCAP_SEQUENTIAL | /* The ram filesystem has no write behind + ** so it is ordered */ + 0; + }else if( strstr(fsInfo.f_basetype, "dos") ){ + pFile->sectorSize = fsInfo.f_bsize; + pFile->deviceCharacteristics = + /* full bitset of atomics from max sector size and smaller */ + ((pFile->sectorSize / 512 * SQLITE_IOCAP_ATOMIC512) << 1) - 2 | + SQLITE_IOCAP_SEQUENTIAL | /* The ram filesystem has no write behind + ** so it is ordered */ + 0; + }else{ + pFile->deviceCharacteristics = + SQLITE_IOCAP_ATOMIC512 | /* blocks are atomic */ + SQLITE_IOCAP_SAFE_APPEND | /* growing the file does not occur until + ** the write succeeds */ + 0; + } + } + /* Last chance verification. If the sector size isn't a multiple of 512 + ** then it isn't valid.*/ + if( pFile->sectorSize % 512 != 0 ){ + pFile->deviceCharacteristics = 0; + pFile->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; + } +} +#endif + +/* +** Return the sector size in bytes of the underlying block device for +** the specified file. This is almost always 512 bytes, but may be +** larger for some devices. +** +** SQLite code assumes this function cannot fail. It also assumes that +** if two files are created in the same file-system directory (i.e. +** a database and its journal file) that the sector size will be the +** same for both. +*/ +static int unixSectorSize(sqlite3_file *id){ + unixFile *pFd = (unixFile*)id; + setDeviceCharacteristics(pFd); + return pFd->sectorSize; +} + +/* +** Return the device characteristics for the file. +** +** This VFS is set up to return SQLITE_IOCAP_POWERSAFE_OVERWRITE by default. +** However, that choice is controversial since technically the underlying +** file system does not always provide powersafe overwrites. (In other +** words, after a power-loss event, parts of the file that were never +** written might end up being altered.) However, non-PSOW behavior is very, +** very rare. And asserting PSOW makes a large reduction in the amount +** of required I/O for journaling, since a lot of padding is eliminated. +** Hence, while POWERSAFE_OVERWRITE is on by default, there is a file-control +** available to turn it off and URI query parameter available to turn it off. +*/ +static int unixDeviceCharacteristics(sqlite3_file *id){ + unixFile *pFd = (unixFile*)id; + setDeviceCharacteristics(pFd); + return pFd->deviceCharacteristics; +} + +#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 + +/* +** Return the system page size. +** +** This function should not be called directly by other code in this file. +** Instead, it should be called via macro osGetpagesize(). +*/ +static int unixGetpagesize(void){ +#if OS_VXWORKS + return 1024; +#elif defined(_BSD_SOURCE) + return getpagesize(); +#else + return (int)sysconf(_SC_PAGESIZE); +#endif +} + +#endif /* !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 */ + +#ifndef SQLITE_OMIT_WAL + +/* +** Object used to represent an shared memory buffer. +** +** When multiple threads all reference the same wal-index, each thread +** has its own unixShm object, but they all point to a single instance +** of this unixShmNode object. In other words, each wal-index is opened +** only once per process. +** +** Each unixShmNode object is connected to a single unixInodeInfo object. +** We could coalesce this object into unixInodeInfo, but that would mean +** every open file that does not use shared memory (in other words, most +** open files) would have to carry around this extra information. So +** the unixInodeInfo object contains a pointer to this unixShmNode object +** and the unixShmNode object is created only when needed. +** +** unixMutexHeld() must be true when creating or destroying +** this object or while reading or writing the following fields: +** +** nRef +** +** The following fields are read-only after the object is created: +** +** hShm +** zFilename +** +** Either unixShmNode.pShmMutex must be held or unixShmNode.nRef==0 and +** unixMutexHeld() is true when reading or writing any other field +** in this structure. +*/ +struct unixShmNode { + unixInodeInfo *pInode; /* unixInodeInfo that owns this SHM node */ + sqlite3_mutex *pShmMutex; /* Mutex to access this object */ + char *zFilename; /* Name of the mmapped file */ + int hShm; /* Open file descriptor */ + int szRegion; /* Size of shared-memory regions */ + u16 nRegion; /* Size of array apRegion */ + u8 isReadonly; /* True if read-only */ + u8 isUnlocked; /* True if no DMS lock held */ + char **apRegion; /* Array of mapped shared-memory regions */ + int nRef; /* Number of unixShm objects pointing to this */ + unixShm *pFirst; /* All unixShm objects pointing to this */ + int aLock[SQLITE_SHM_NLOCK]; /* # shared locks on slot, -1==excl lock */ +#ifdef SQLITE_DEBUG + u8 exclMask; /* Mask of exclusive locks held */ + u8 sharedMask; /* Mask of shared locks held */ + u8 nextShmId; /* Next available unixShm.id value */ +#endif +}; + +/* +** Structure used internally by this VFS to record the state of an +** open shared memory connection. +** +** The following fields are initialized when this object is created and +** are read-only thereafter: +** +** unixShm.pShmNode +** unixShm.id +** +** All other fields are read/write. The unixShm.pShmNode->pShmMutex must +** be held while accessing any read/write fields. +*/ +struct unixShm { + unixShmNode *pShmNode; /* The underlying unixShmNode object */ + unixShm *pNext; /* Next unixShm with the same unixShmNode */ + u8 hasMutex; /* True if holding the unixShmNode->pShmMutex */ + u8 id; /* Id of this connection within its unixShmNode */ + u16 sharedMask; /* Mask of shared locks held */ + u16 exclMask; /* Mask of exclusive locks held */ +}; + +/* +** Constants used for locking +*/ +#define UNIX_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ +#define UNIX_SHM_DMS (UNIX_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ + +/* +** Use F_GETLK to check whether or not there are any readers with open +** wal-mode transactions in other processes on database file pFile. If +** no error occurs, return SQLITE_OK and set (*piOut) to 1 if there are +** such transactions, or 0 otherwise. If an error occurs, return an +** SQLite error code. The final value of *piOut is undefined in this +** case. +*/ +static int unixFcntlExternalReader(unixFile *pFile, int *piOut){ + int rc = SQLITE_OK; + *piOut = 0; + if( pFile->pShm){ + unixShmNode *pShmNode = pFile->pShm->pShmNode; + struct flock f; + + memset(&f, 0, sizeof(f)); + f.l_type = F_WRLCK; + f.l_whence = SEEK_SET; + f.l_start = UNIX_SHM_BASE + 3; + f.l_len = SQLITE_SHM_NLOCK - 3; + + sqlite3_mutex_enter(pShmNode->pShmMutex); + if( osFcntl(pShmNode->hShm, F_GETLK, &f)<0 ){ + rc = SQLITE_IOERR_LOCK; + }else{ + *piOut = (f.l_type!=F_UNLCK); + } + sqlite3_mutex_leave(pShmNode->pShmMutex); + } + + return rc; +} + + +/* +** Apply posix advisory locks for all bytes from ofst through ofst+n-1. +** +** Locks block if the mask is exactly UNIX_SHM_C and are non-blocking +** otherwise. +*/ +static int unixShmSystemLock( + unixFile *pFile, /* Open connection to the WAL file */ + int lockType, /* F_UNLCK, F_RDLCK, or F_WRLCK */ + int ofst, /* First byte of the locking range */ + int n /* Number of bytes to lock */ +){ + unixShmNode *pShmNode; /* Apply locks to this open shared-memory segment */ + struct flock f; /* The posix advisory locking structure */ + int rc = SQLITE_OK; /* Result code form fcntl() */ + + /* Access to the unixShmNode object is serialized by the caller */ + pShmNode = pFile->pInode->pShmNode; + assert( pShmNode->nRef==0 || sqlite3_mutex_held(pShmNode->pShmMutex) ); + assert( pShmNode->nRef>0 || unixMutexHeld() ); + + /* Shared locks never span more than one byte */ + assert( n==1 || lockType!=F_RDLCK ); + + /* Locks are within range */ + assert( n>=1 && n<=SQLITE_SHM_NLOCK ); + + if( pShmNode->hShm>=0 ){ + int res; + /* Initialize the locking parameters */ + f.l_type = lockType; + f.l_whence = SEEK_SET; + f.l_start = ofst; + f.l_len = n; + res = osSetPosixAdvisoryLock(pShmNode->hShm, &f, pFile); + if( res==-1 ){ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + rc = (pFile->iBusyTimeout ? SQLITE_BUSY_TIMEOUT : SQLITE_BUSY); +#else + rc = SQLITE_BUSY; +#endif + } + } + + /* Update the global lock state and do debug tracing */ +#ifdef SQLITE_DEBUG + { u16 mask; + OSTRACE(("SHM-LOCK ")); + mask = ofst>31 ? 0xffff : (1<<(ofst+n)) - (1<<ofst); + if( rc==SQLITE_OK ){ + if( lockType==F_UNLCK ){ + OSTRACE(("unlock %d ok", ofst)); + pShmNode->exclMask &= ~mask; + pShmNode->sharedMask &= ~mask; + }else if( lockType==F_RDLCK ){ + OSTRACE(("read-lock %d ok", ofst)); + pShmNode->exclMask &= ~mask; + pShmNode->sharedMask |= mask; + }else{ + assert( lockType==F_WRLCK ); + OSTRACE(("write-lock %d ok", ofst)); + pShmNode->exclMask |= mask; + pShmNode->sharedMask &= ~mask; + } + }else{ + if( lockType==F_UNLCK ){ + OSTRACE(("unlock %d failed", ofst)); + }else if( lockType==F_RDLCK ){ + OSTRACE(("read-lock failed")); + }else{ + assert( lockType==F_WRLCK ); + OSTRACE(("write-lock %d failed", ofst)); + } + } + OSTRACE((" - afterwards %03x,%03x\n", + pShmNode->sharedMask, pShmNode->exclMask)); + } +#endif + + return rc; +} + +/* +** Return the minimum number of 32KB shm regions that should be mapped at +** a time, assuming that each mapping must be an integer multiple of the +** current system page-size. +** +** Usually, this is 1. The exception seems to be systems that are configured +** to use 64KB pages - in this case each mapping must cover at least two +** shm regions. +*/ +static int unixShmRegionPerMap(void){ + int shmsz = 32*1024; /* SHM region size */ + int pgsz = osGetpagesize(); /* System page size */ + assert( ((pgsz-1)&pgsz)==0 ); /* Page size must be a power of 2 */ + if( pgsz<shmsz ) return 1; + return pgsz/shmsz; +} + +/* +** Purge the unixShmNodeList list of all entries with unixShmNode.nRef==0. +** +** This is not a VFS shared-memory method; it is a utility function called +** by VFS shared-memory methods. +*/ +static void unixShmPurge(unixFile *pFd){ + unixShmNode *p = pFd->pInode->pShmNode; + assert( unixMutexHeld() ); + if( p && ALWAYS(p->nRef==0) ){ + int nShmPerMap = unixShmRegionPerMap(); + int i; + assert( p->pInode==pFd->pInode ); + sqlite3_mutex_free(p->pShmMutex); + for(i=0; i<p->nRegion; i+=nShmPerMap){ + if( p->hShm>=0 ){ + osMunmap(p->apRegion[i], p->szRegion); + }else{ + sqlite3_free(p->apRegion[i]); + } + } + sqlite3_free(p->apRegion); + if( p->hShm>=0 ){ + robust_close(pFd, p->hShm, __LINE__); + p->hShm = -1; + } + p->pInode->pShmNode = 0; + sqlite3_free(p); + } +} + +/* +** The DMS lock has not yet been taken on shm file pShmNode. Attempt to +** take it now. Return SQLITE_OK if successful, or an SQLite error +** code otherwise. +** +** If the DMS cannot be locked because this is a readonly_shm=1 +** connection and no other process already holds a lock, return +** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1. +*/ +static int unixLockSharedMemory(unixFile *pDbFd, unixShmNode *pShmNode){ + struct flock lock; + int rc = SQLITE_OK; + + /* Use F_GETLK to determine the locks other processes are holding + ** on the DMS byte. If it indicates that another process is holding + ** a SHARED lock, then this process may also take a SHARED lock + ** and proceed with opening the *-shm file. + ** + ** Or, if no other process is holding any lock, then this process + ** is the first to open it. In this case take an EXCLUSIVE lock on the + ** DMS byte and truncate the *-shm file to zero bytes in size. Then + ** downgrade to a SHARED lock on the DMS byte. + ** + ** If another process is holding an EXCLUSIVE lock on the DMS byte, + ** return SQLITE_BUSY to the caller (it will try again). An earlier + ** version of this code attempted the SHARED lock at this point. But + ** this introduced a subtle race condition: if the process holding + ** EXCLUSIVE failed just before truncating the *-shm file, then this + ** process might open and use the *-shm file without truncating it. + ** And if the *-shm file has been corrupted by a power failure or + ** system crash, the database itself may also become corrupt. */ + lock.l_whence = SEEK_SET; + lock.l_start = UNIX_SHM_DMS; + lock.l_len = 1; + lock.l_type = F_WRLCK; + if( osFcntl(pShmNode->hShm, F_GETLK, &lock)!=0 ) { + rc = SQLITE_IOERR_LOCK; + }else if( lock.l_type==F_UNLCK ){ + if( pShmNode->isReadonly ){ + pShmNode->isUnlocked = 1; + rc = SQLITE_READONLY_CANTINIT; + }else{ + rc = unixShmSystemLock(pDbFd, F_WRLCK, UNIX_SHM_DMS, 1); + /* The first connection to attach must truncate the -shm file. We + ** truncate to 3 bytes (an arbitrary small number, less than the + ** -shm header size) rather than 0 as a system debugging aid, to + ** help detect if a -shm file truncation is legitimate or is the work + ** or a rogue process. */ + if( rc==SQLITE_OK && robust_ftruncate(pShmNode->hShm, 3) ){ + rc = unixLogError(SQLITE_IOERR_SHMOPEN,"ftruncate",pShmNode->zFilename); + } + } + }else if( lock.l_type==F_WRLCK ){ + rc = SQLITE_BUSY; + } + + if( rc==SQLITE_OK ){ + assert( lock.l_type==F_UNLCK || lock.l_type==F_RDLCK ); + rc = unixShmSystemLock(pDbFd, F_RDLCK, UNIX_SHM_DMS, 1); + } + return rc; +} + +/* +** Open a shared-memory area associated with open database file pDbFd. +** This particular implementation uses mmapped files. +** +** The file used to implement shared-memory is in the same directory +** as the open database file and has the same name as the open database +** file with the "-shm" suffix added. For example, if the database file +** is "/home/user1/config.db" then the file that is created and mmapped +** for shared memory will be called "/home/user1/config.db-shm". +** +** Another approach to is to use files in /dev/shm or /dev/tmp or an +** some other tmpfs mount. But if a file in a different directory +** from the database file is used, then differing access permissions +** or a chroot() might cause two different processes on the same +** database to end up using different files for shared memory - +** meaning that their memory would not really be shared - resulting +** in database corruption. Nevertheless, this tmpfs file usage +** can be enabled at compile-time using -DSQLITE_SHM_DIRECTORY="/dev/shm" +** or the equivalent. The use of the SQLITE_SHM_DIRECTORY compile-time +** option results in an incompatible build of SQLite; builds of SQLite +** that with differing SQLITE_SHM_DIRECTORY settings attempt to use the +** same database file at the same time, database corruption will likely +** result. The SQLITE_SHM_DIRECTORY compile-time option is considered +** "unsupported" and may go away in a future SQLite release. +** +** When opening a new shared-memory file, if no other instances of that +** file are currently open, in this process or in other processes, then +** the file must be truncated to zero length or have its header cleared. +** +** If the original database file (pDbFd) is using the "unix-excl" VFS +** that means that an exclusive lock is held on the database file and +** that no other processes are able to read or write the database. In +** that case, we do not really need shared memory. No shared memory +** file is created. The shared memory will be simulated with heap memory. +*/ +static int unixOpenSharedMemory(unixFile *pDbFd){ + struct unixShm *p = 0; /* The connection to be opened */ + struct unixShmNode *pShmNode; /* The underlying mmapped file */ + int rc = SQLITE_OK; /* Result code */ + unixInodeInfo *pInode; /* The inode of fd */ + char *zShm; /* Name of the file used for SHM */ + int nShmFilename; /* Size of the SHM filename in bytes */ + + /* Allocate space for the new unixShm object. */ + p = sqlite3_malloc64( sizeof(*p) ); + if( p==0 ) return SQLITE_NOMEM_BKPT; + memset(p, 0, sizeof(*p)); + assert( pDbFd->pShm==0 ); + + /* Check to see if a unixShmNode object already exists. Reuse an existing + ** one if present. Create a new one if necessary. + */ + assert( unixFileMutexNotheld(pDbFd) ); + unixEnterMutex(); + pInode = pDbFd->pInode; + pShmNode = pInode->pShmNode; + if( pShmNode==0 ){ + struct stat sStat; /* fstat() info for database file */ +#ifndef SQLITE_SHM_DIRECTORY + const char *zBasePath = pDbFd->zPath; +#endif + + /* Call fstat() to figure out the permissions on the database file. If + ** a new *-shm file is created, an attempt will be made to create it + ** with the same permissions. + */ + if( osFstat(pDbFd->h, &sStat) ){ + rc = SQLITE_IOERR_FSTAT; + goto shm_open_err; + } + +#ifdef SQLITE_SHM_DIRECTORY + nShmFilename = sizeof(SQLITE_SHM_DIRECTORY) + 31; +#else + nShmFilename = 6 + (int)strlen(zBasePath); +#endif + pShmNode = sqlite3_malloc64( sizeof(*pShmNode) + nShmFilename ); + if( pShmNode==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto shm_open_err; + } + memset(pShmNode, 0, sizeof(*pShmNode)+nShmFilename); + zShm = pShmNode->zFilename = (char*)&pShmNode[1]; +#ifdef SQLITE_SHM_DIRECTORY + sqlite3_snprintf(nShmFilename, zShm, + SQLITE_SHM_DIRECTORY "/sqlite-shm-%x-%x", + (u32)sStat.st_ino, (u32)sStat.st_dev); +#else + sqlite3_snprintf(nShmFilename, zShm, "%s-shm", zBasePath); + sqlite3FileSuffix3(pDbFd->zPath, zShm); +#endif + pShmNode->hShm = -1; + pDbFd->pInode->pShmNode = pShmNode; + pShmNode->pInode = pDbFd->pInode; + if( sqlite3GlobalConfig.bCoreMutex ){ + pShmNode->pShmMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( pShmNode->pShmMutex==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto shm_open_err; + } + } + + if( pInode->bProcessLock==0 ){ + if( 0==sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){ + pShmNode->hShm = robust_open(zShm, O_RDWR|O_CREAT|O_NOFOLLOW, + (sStat.st_mode&0777)); + } + if( pShmNode->hShm<0 ){ + pShmNode->hShm = robust_open(zShm, O_RDONLY|O_NOFOLLOW, + (sStat.st_mode&0777)); + if( pShmNode->hShm<0 ){ + rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShm); + goto shm_open_err; + } + pShmNode->isReadonly = 1; + } + + /* If this process is running as root, make sure that the SHM file + ** is owned by the same user that owns the original database. Otherwise, + ** the original owner will not be able to connect. + */ + robustFchown(pShmNode->hShm, sStat.st_uid, sStat.st_gid); + + rc = unixLockSharedMemory(pDbFd, pShmNode); + if( rc!=SQLITE_OK && rc!=SQLITE_READONLY_CANTINIT ) goto shm_open_err; + } + } + + /* Make the new connection a child of the unixShmNode */ + p->pShmNode = pShmNode; +#ifdef SQLITE_DEBUG + p->id = pShmNode->nextShmId++; +#endif + pShmNode->nRef++; + pDbFd->pShm = p; + unixLeaveMutex(); + + /* The reference count on pShmNode has already been incremented under + ** the cover of the unixEnterMutex() mutex and the pointer from the + ** new (struct unixShm) object to the pShmNode has been set. All that is + ** left to do is to link the new object into the linked list starting + ** at pShmNode->pFirst. This must be done while holding the + ** pShmNode->pShmMutex. + */ + sqlite3_mutex_enter(pShmNode->pShmMutex); + p->pNext = pShmNode->pFirst; + pShmNode->pFirst = p; + sqlite3_mutex_leave(pShmNode->pShmMutex); + return rc; + + /* Jump here on any error */ +shm_open_err: + unixShmPurge(pDbFd); /* This call frees pShmNode if required */ + sqlite3_free(p); + unixLeaveMutex(); + return rc; +} + +/* +** This function is called to obtain a pointer to region iRegion of the +** shared-memory associated with the database file fd. Shared-memory regions +** are numbered starting from zero. Each shared-memory region is szRegion +** bytes in size. +** +** If an error occurs, an error code is returned and *pp is set to NULL. +** +** Otherwise, if the bExtend parameter is 0 and the requested shared-memory +** region has not been allocated (by any client, including one running in a +** separate process), then *pp is set to NULL and SQLITE_OK returned. If +** bExtend is non-zero and the requested shared-memory region has not yet +** been allocated, it is allocated by this function. +** +** If the shared-memory region has already been allocated or is allocated by +** this call as described above, then it is mapped into this processes +** address space (if it is not already), *pp is set to point to the mapped +** memory and SQLITE_OK returned. +*/ +static int unixShmMap( + sqlite3_file *fd, /* Handle open on database file */ + int iRegion, /* Region to retrieve */ + int szRegion, /* Size of regions */ + int bExtend, /* True to extend file if necessary */ + void volatile **pp /* OUT: Mapped memory */ +){ + unixFile *pDbFd = (unixFile*)fd; + unixShm *p; + unixShmNode *pShmNode; + int rc = SQLITE_OK; + int nShmPerMap = unixShmRegionPerMap(); + int nReqRegion; + + /* If the shared-memory file has not yet been opened, open it now. */ + if( pDbFd->pShm==0 ){ + rc = unixOpenSharedMemory(pDbFd); + if( rc!=SQLITE_OK ) return rc; + } + + p = pDbFd->pShm; + pShmNode = p->pShmNode; + sqlite3_mutex_enter(pShmNode->pShmMutex); + if( pShmNode->isUnlocked ){ + rc = unixLockSharedMemory(pDbFd, pShmNode); + if( rc!=SQLITE_OK ) goto shmpage_out; + pShmNode->isUnlocked = 0; + } + assert( szRegion==pShmNode->szRegion || pShmNode->nRegion==0 ); + assert( pShmNode->pInode==pDbFd->pInode ); + assert( pShmNode->hShm>=0 || pDbFd->pInode->bProcessLock==1 ); + assert( pShmNode->hShm<0 || pDbFd->pInode->bProcessLock==0 ); + + /* Minimum number of regions required to be mapped. */ + nReqRegion = ((iRegion+nShmPerMap) / nShmPerMap) * nShmPerMap; + + if( pShmNode->nRegion<nReqRegion ){ + char **apNew; /* New apRegion[] array */ + int nByte = nReqRegion*szRegion; /* Minimum required file size */ + struct stat sStat; /* Used by fstat() */ + + pShmNode->szRegion = szRegion; + + if( pShmNode->hShm>=0 ){ + /* The requested region is not mapped into this processes address space. + ** Check to see if it has been allocated (i.e. if the wal-index file is + ** large enough to contain the requested region). + */ + if( osFstat(pShmNode->hShm, &sStat) ){ + rc = SQLITE_IOERR_SHMSIZE; + goto shmpage_out; + } + + if( sStat.st_size<nByte ){ + /* The requested memory region does not exist. If bExtend is set to + ** false, exit early. *pp will be set to NULL and SQLITE_OK returned. + */ + if( !bExtend ){ + goto shmpage_out; + } + + /* Alternatively, if bExtend is true, extend the file. Do this by + ** writing a single byte to the end of each (OS) page being + ** allocated or extended. Technically, we need only write to the + ** last page in order to extend the file. But writing to all new + ** pages forces the OS to allocate them immediately, which reduces + ** the chances of SIGBUS while accessing the mapped region later on. + */ + else{ + static const int pgsz = 4096; + int iPg; + + /* Write to the last byte of each newly allocated or extended page */ + assert( (nByte % pgsz)==0 ); + for(iPg=(sStat.st_size/pgsz); iPg<(nByte/pgsz); iPg++){ + int x = 0; + if( seekAndWriteFd(pShmNode->hShm, iPg*pgsz + pgsz-1,"",1,&x)!=1 ){ + const char *zFile = pShmNode->zFilename; + rc = unixLogError(SQLITE_IOERR_SHMSIZE, "write", zFile); + goto shmpage_out; + } + } + } + } + } + + /* Map the requested memory region into this processes address space. */ + apNew = (char **)sqlite3_realloc( + pShmNode->apRegion, nReqRegion*sizeof(char *) + ); + if( !apNew ){ + rc = SQLITE_IOERR_NOMEM_BKPT; + goto shmpage_out; + } + pShmNode->apRegion = apNew; + while( pShmNode->nRegion<nReqRegion ){ + int nMap = szRegion*nShmPerMap; + int i; + void *pMem; + if( pShmNode->hShm>=0 ){ + pMem = osMmap(0, nMap, + pShmNode->isReadonly ? PROT_READ : PROT_READ|PROT_WRITE, + MAP_SHARED, pShmNode->hShm, szRegion*(i64)pShmNode->nRegion + ); + if( pMem==MAP_FAILED ){ + rc = unixLogError(SQLITE_IOERR_SHMMAP, "mmap", pShmNode->zFilename); + goto shmpage_out; + } + }else{ + pMem = sqlite3_malloc64(nMap); + if( pMem==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto shmpage_out; + } + memset(pMem, 0, nMap); + } + + for(i=0; i<nShmPerMap; i++){ + pShmNode->apRegion[pShmNode->nRegion+i] = &((char*)pMem)[szRegion*i]; + } + pShmNode->nRegion += nShmPerMap; + } + } + +shmpage_out: + if( pShmNode->nRegion>iRegion ){ + *pp = pShmNode->apRegion[iRegion]; + }else{ + *pp = 0; + } + if( pShmNode->isReadonly && rc==SQLITE_OK ) rc = SQLITE_READONLY; + sqlite3_mutex_leave(pShmNode->pShmMutex); + return rc; +} + +/* +** Check that the pShmNode->aLock[] array comports with the locking bitmasks +** held by each client. Return true if it does, or false otherwise. This +** is to be used in an assert(). e.g. +** +** assert( assertLockingArrayOk(pShmNode) ); +*/ +#ifdef SQLITE_DEBUG +static int assertLockingArrayOk(unixShmNode *pShmNode){ + unixShm *pX; + int aLock[SQLITE_SHM_NLOCK]; + assert( sqlite3_mutex_held(pShmNode->pShmMutex) ); + + memset(aLock, 0, sizeof(aLock)); + for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ + int i; + for(i=0; i<SQLITE_SHM_NLOCK; i++){ + if( pX->exclMask & (1<<i) ){ + assert( aLock[i]==0 ); + aLock[i] = -1; + }else if( pX->sharedMask & (1<<i) ){ + assert( aLock[i]>=0 ); + aLock[i]++; + } + } + } + + assert( 0==memcmp(pShmNode->aLock, aLock, sizeof(aLock)) ); + return (memcmp(pShmNode->aLock, aLock, sizeof(aLock))==0); +} +#endif + +/* +** Change the lock state for a shared-memory segment. +** +** Note that the relationship between SHAREd and EXCLUSIVE locks is a little +** different here than in posix. In xShmLock(), one can go from unlocked +** to shared and back or from unlocked to exclusive and back. But one may +** not go from shared to exclusive or from exclusive to shared. +*/ +static int unixShmLock( + sqlite3_file *fd, /* Database file holding the shared memory */ + int ofst, /* First lock to acquire or release */ + int n, /* Number of locks to acquire or release */ + int flags /* What to do with the lock */ +){ + unixFile *pDbFd = (unixFile*)fd; /* Connection holding shared memory */ + unixShm *p; /* The shared memory being locked */ + unixShmNode *pShmNode; /* The underlying file iNode */ + int rc = SQLITE_OK; /* Result code */ + u16 mask; /* Mask of locks to take or release */ + int *aLock; + + p = pDbFd->pShm; + if( p==0 ) return SQLITE_IOERR_SHMLOCK; + pShmNode = p->pShmNode; + if( NEVER(pShmNode==0) ) return SQLITE_IOERR_SHMLOCK; + aLock = pShmNode->aLock; + + assert( pShmNode==pDbFd->pInode->pShmNode ); + assert( pShmNode->pInode==pDbFd->pInode ); + assert( ofst>=0 && ofst+n<=SQLITE_SHM_NLOCK ); + assert( n>=1 ); + assert( flags==(SQLITE_SHM_LOCK | SQLITE_SHM_SHARED) + || flags==(SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE) + || flags==(SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED) + || flags==(SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE) ); + assert( n==1 || (flags & SQLITE_SHM_EXCLUSIVE)!=0 ); + assert( pShmNode->hShm>=0 || pDbFd->pInode->bProcessLock==1 ); + assert( pShmNode->hShm<0 || pDbFd->pInode->bProcessLock==0 ); + + /* Check that, if this to be a blocking lock, no locks that occur later + ** in the following list than the lock being obtained are already held: + ** + ** 1. Checkpointer lock (ofst==1). + ** 2. Write lock (ofst==0). + ** 3. Read locks (ofst>=3 && ofst<SQLITE_SHM_NLOCK). + ** + ** In other words, if this is a blocking lock, none of the locks that + ** occur later in the above list than the lock being obtained may be + ** held. + ** + ** It is not permitted to block on the RECOVER lock. + */ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || ( + (ofst!=2) /* not RECOVER */ + && (ofst!=1 || (p->exclMask|p->sharedMask)==0) + && (ofst!=0 || (p->exclMask|p->sharedMask)<3) + && (ofst<3 || (p->exclMask|p->sharedMask)<(1<<ofst)) + )); +#endif + + mask = (1<<(ofst+n)) - (1<<ofst); + assert( n>1 || mask==(1<<ofst) ); + sqlite3_mutex_enter(pShmNode->pShmMutex); + assert( assertLockingArrayOk(pShmNode) ); + if( flags & SQLITE_SHM_UNLOCK ){ + if( (p->exclMask|p->sharedMask) & mask ){ + int ii; + int bUnlock = 1; + + for(ii=ofst; ii<ofst+n; ii++){ + if( aLock[ii]>((p->sharedMask & (1<<ii)) ? 1 : 0) ){ + bUnlock = 0; + } + } + + if( bUnlock ){ + rc = unixShmSystemLock(pDbFd, F_UNLCK, ofst+UNIX_SHM_BASE, n); + if( rc==SQLITE_OK ){ + memset(&aLock[ofst], 0, sizeof(int)*n); + } + }else if( ALWAYS(p->sharedMask & (1<<ofst)) ){ + assert( n==1 && aLock[ofst]>1 ); + aLock[ofst]--; + } + + /* Undo the local locks */ + if( rc==SQLITE_OK ){ + p->exclMask &= ~mask; + p->sharedMask &= ~mask; + } + } + }else if( flags & SQLITE_SHM_SHARED ){ + assert( n==1 ); + assert( (p->exclMask & (1<<ofst))==0 ); + if( (p->sharedMask & mask)==0 ){ + if( aLock[ofst]<0 ){ + rc = SQLITE_BUSY; + }else if( aLock[ofst]==0 ){ + rc = unixShmSystemLock(pDbFd, F_RDLCK, ofst+UNIX_SHM_BASE, n); + } + + /* Get the local shared locks */ + if( rc==SQLITE_OK ){ + p->sharedMask |= mask; + aLock[ofst]++; + } + } + }else{ + /* Make sure no sibling connections hold locks that will block this + ** lock. If any do, return SQLITE_BUSY right away. */ + int ii; + for(ii=ofst; ii<ofst+n; ii++){ + assert( (p->sharedMask & mask)==0 ); + if( ALWAYS((p->exclMask & (1<<ii))==0) && aLock[ii] ){ + rc = SQLITE_BUSY; + break; + } + } + + /* Get the exclusive locks at the system level. Then if successful + ** also update the in-memory values. */ + if( rc==SQLITE_OK ){ + rc = unixShmSystemLock(pDbFd, F_WRLCK, ofst+UNIX_SHM_BASE, n); + if( rc==SQLITE_OK ){ + assert( (p->sharedMask & mask)==0 ); + p->exclMask |= mask; + for(ii=ofst; ii<ofst+n; ii++){ + aLock[ii] = -1; + } + } + } + } + assert( assertLockingArrayOk(pShmNode) ); + sqlite3_mutex_leave(pShmNode->pShmMutex); + OSTRACE(("SHM-LOCK shmid-%d, pid-%d got %03x,%03x\n", + p->id, osGetpid(0), p->sharedMask, p->exclMask)); + return rc; +} + +/* +** Implement a memory barrier or memory fence on shared memory. +** +** All loads and stores begun before the barrier must complete before +** any load or store begun after the barrier. +*/ +static void unixShmBarrier( + sqlite3_file *fd /* Database file holding the shared memory */ +){ + UNUSED_PARAMETER(fd); + sqlite3MemoryBarrier(); /* compiler-defined memory barrier */ + assert( fd->pMethods->xLock==nolockLock + || unixFileMutexNotheld((unixFile*)fd) + ); + unixEnterMutex(); /* Also mutex, for redundancy */ + unixLeaveMutex(); +} + +/* +** Close a connection to shared-memory. Delete the underlying +** storage if deleteFlag is true. +** +** If there is no shared memory associated with the connection then this +** routine is a harmless no-op. +*/ +static int unixShmUnmap( + sqlite3_file *fd, /* The underlying database file */ + int deleteFlag /* Delete shared-memory if true */ +){ + unixShm *p; /* The connection to be closed */ + unixShmNode *pShmNode; /* The underlying shared-memory file */ + unixShm **pp; /* For looping over sibling connections */ + unixFile *pDbFd; /* The underlying database file */ + + pDbFd = (unixFile*)fd; + p = pDbFd->pShm; + if( p==0 ) return SQLITE_OK; + pShmNode = p->pShmNode; + + assert( pShmNode==pDbFd->pInode->pShmNode ); + assert( pShmNode->pInode==pDbFd->pInode ); + + /* Remove connection p from the set of connections associated + ** with pShmNode */ + sqlite3_mutex_enter(pShmNode->pShmMutex); + for(pp=&pShmNode->pFirst; (*pp)!=p; pp = &(*pp)->pNext){} + *pp = p->pNext; + + /* Free the connection p */ + sqlite3_free(p); + pDbFd->pShm = 0; + sqlite3_mutex_leave(pShmNode->pShmMutex); + + /* If pShmNode->nRef has reached 0, then close the underlying + ** shared-memory file, too */ + assert( unixFileMutexNotheld(pDbFd) ); + unixEnterMutex(); + assert( pShmNode->nRef>0 ); + pShmNode->nRef--; + if( pShmNode->nRef==0 ){ + if( deleteFlag && pShmNode->hShm>=0 ){ + osUnlink(pShmNode->zFilename); + } + unixShmPurge(pDbFd); + } + unixLeaveMutex(); + + return SQLITE_OK; +} + + +#else +# define unixShmMap 0 +# define unixShmLock 0 +# define unixShmBarrier 0 +# define unixShmUnmap 0 +#endif /* #ifndef SQLITE_OMIT_WAL */ + +#if SQLITE_MAX_MMAP_SIZE>0 +/* +** If it is currently memory mapped, unmap file pFd. +*/ +static void unixUnmapfile(unixFile *pFd){ + assert( pFd->nFetchOut==0 ); + if( pFd->pMapRegion ){ + osMunmap(pFd->pMapRegion, pFd->mmapSizeActual); + pFd->pMapRegion = 0; + pFd->mmapSize = 0; + pFd->mmapSizeActual = 0; + } +} + +/* +** Attempt to set the size of the memory mapping maintained by file +** descriptor pFd to nNew bytes. Any existing mapping is discarded. +** +** If successful, this function sets the following variables: +** +** unixFile.pMapRegion +** unixFile.mmapSize +** unixFile.mmapSizeActual +** +** If unsuccessful, an error message is logged via sqlite3_log() and +** the three variables above are zeroed. In this case SQLite should +** continue accessing the database using the xRead() and xWrite() +** methods. +*/ +static void unixRemapfile( + unixFile *pFd, /* File descriptor object */ + i64 nNew /* Required mapping size */ +){ + const char *zErr = "mmap"; + int h = pFd->h; /* File descriptor open on db file */ + u8 *pOrig = (u8 *)pFd->pMapRegion; /* Pointer to current file mapping */ + i64 nOrig = pFd->mmapSizeActual; /* Size of pOrig region in bytes */ + u8 *pNew = 0; /* Location of new mapping */ + int flags = PROT_READ; /* Flags to pass to mmap() */ + + assert( pFd->nFetchOut==0 ); + assert( nNew>pFd->mmapSize ); + assert( nNew<=pFd->mmapSizeMax ); + assert( nNew>0 ); + assert( pFd->mmapSizeActual>=pFd->mmapSize ); + assert( MAP_FAILED!=0 ); + +#ifdef SQLITE_MMAP_READWRITE + if( (pFd->ctrlFlags & UNIXFILE_RDONLY)==0 ) flags |= PROT_WRITE; +#endif + + if( pOrig ){ +#if HAVE_MREMAP + i64 nReuse = pFd->mmapSize; +#else + const int szSyspage = osGetpagesize(); + i64 nReuse = (pFd->mmapSize & ~(szSyspage-1)); +#endif + u8 *pReq = &pOrig[nReuse]; + + /* Unmap any pages of the existing mapping that cannot be reused. */ + if( nReuse!=nOrig ){ + osMunmap(pReq, nOrig-nReuse); + } + +#if HAVE_MREMAP + pNew = osMremap(pOrig, nReuse, nNew, MREMAP_MAYMOVE); + zErr = "mremap"; +#else + pNew = osMmap(pReq, nNew-nReuse, flags, MAP_SHARED, h, nReuse); + if( pNew!=MAP_FAILED ){ + if( pNew!=pReq ){ + osMunmap(pNew, nNew - nReuse); + pNew = 0; + }else{ + pNew = pOrig; + } + } +#endif + + /* The attempt to extend the existing mapping failed. Free it. */ + if( pNew==MAP_FAILED || pNew==0 ){ + osMunmap(pOrig, nReuse); + } + } + + /* If pNew is still NULL, try to create an entirely new mapping. */ + if( pNew==0 ){ + pNew = osMmap(0, nNew, flags, MAP_SHARED, h, 0); + } + + if( pNew==MAP_FAILED ){ + pNew = 0; + nNew = 0; + unixLogError(SQLITE_OK, zErr, pFd->zPath); + + /* If the mmap() above failed, assume that all subsequent mmap() calls + ** will probably fail too. Fall back to using xRead/xWrite exclusively + ** in this case. */ + pFd->mmapSizeMax = 0; + } + pFd->pMapRegion = (void *)pNew; + pFd->mmapSize = pFd->mmapSizeActual = nNew; +} + +/* +** Memory map or remap the file opened by file-descriptor pFd (if the file +** is already mapped, the existing mapping is replaced by the new). Or, if +** there already exists a mapping for this file, and there are still +** outstanding xFetch() references to it, this function is a no-op. +** +** If parameter nByte is non-negative, then it is the requested size of +** the mapping to create. Otherwise, if nByte is less than zero, then the +** requested size is the size of the file on disk. The actual size of the +** created mapping is either the requested size or the value configured +** using SQLITE_FCNTL_MMAP_LIMIT, whichever is smaller. +** +** SQLITE_OK is returned if no error occurs (even if the mapping is not +** recreated as a result of outstanding references) or an SQLite error +** code otherwise. +*/ +static int unixMapfile(unixFile *pFd, i64 nMap){ + assert( nMap>=0 || pFd->nFetchOut==0 ); + assert( nMap>0 || (pFd->mmapSize==0 && pFd->pMapRegion==0) ); + if( pFd->nFetchOut>0 ) return SQLITE_OK; + + if( nMap<0 ){ + struct stat statbuf; /* Low-level file information */ + if( osFstat(pFd->h, &statbuf) ){ + return SQLITE_IOERR_FSTAT; + } + nMap = statbuf.st_size; + } + if( nMap>pFd->mmapSizeMax ){ + nMap = pFd->mmapSizeMax; + } + + assert( nMap>0 || (pFd->mmapSize==0 && pFd->pMapRegion==0) ); + if( nMap!=pFd->mmapSize ){ + unixRemapfile(pFd, nMap); + } + + return SQLITE_OK; +} +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + +/* +** If possible, return a pointer to a mapping of file fd starting at offset +** iOff. The mapping must be valid for at least nAmt bytes. +** +** If such a pointer can be obtained, store it in *pp and return SQLITE_OK. +** Or, if one cannot but no error occurs, set *pp to 0 and return SQLITE_OK. +** Finally, if an error does occur, return an SQLite error code. The final +** value of *pp is undefined in this case. +** +** If this function does return a pointer, the caller must eventually +** release the reference by calling unixUnfetch(). +*/ +static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ +#if SQLITE_MAX_MMAP_SIZE>0 + unixFile *pFd = (unixFile *)fd; /* The underlying database file */ +#endif + *pp = 0; + +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFd->mmapSizeMax>0 ){ + if( pFd->pMapRegion==0 ){ + int rc = unixMapfile(pFd, -1); + if( rc!=SQLITE_OK ) return rc; + } + if( pFd->mmapSize >= iOff+nAmt ){ + *pp = &((u8 *)pFd->pMapRegion)[iOff]; + pFd->nFetchOut++; + } + } +#endif + return SQLITE_OK; +} + +/* +** If the third argument is non-NULL, then this function releases a +** reference obtained by an earlier call to unixFetch(). The second +** argument passed to this function must be the same as the corresponding +** argument that was passed to the unixFetch() invocation. +** +** Or, if the third argument is NULL, then this function is being called +** to inform the VFS layer that, according to POSIX, any existing mapping +** may now be invalid and should be unmapped. +*/ +static int unixUnfetch(sqlite3_file *fd, i64 iOff, void *p){ +#if SQLITE_MAX_MMAP_SIZE>0 + unixFile *pFd = (unixFile *)fd; /* The underlying database file */ + UNUSED_PARAMETER(iOff); + + /* If p==0 (unmap the entire file) then there must be no outstanding + ** xFetch references. Or, if p!=0 (meaning it is an xFetch reference), + ** then there must be at least one outstanding. */ + assert( (p==0)==(pFd->nFetchOut==0) ); + + /* If p!=0, it must match the iOff value. */ + assert( p==0 || p==&((u8 *)pFd->pMapRegion)[iOff] ); + + if( p ){ + pFd->nFetchOut--; + }else{ + unixUnmapfile(pFd); + } + + assert( pFd->nFetchOut>=0 ); +#else + UNUSED_PARAMETER(fd); + UNUSED_PARAMETER(p); + UNUSED_PARAMETER(iOff); +#endif + return SQLITE_OK; +} + +/* +** Here ends the implementation of all sqlite3_file methods. +** +********************** End sqlite3_file Methods ******************************* +******************************************************************************/ + +/* +** This division contains definitions of sqlite3_io_methods objects that +** implement various file locking strategies. It also contains definitions +** of "finder" functions. A finder-function is used to locate the appropriate +** sqlite3_io_methods object for a particular database file. The pAppData +** field of the sqlite3_vfs VFS objects are initialized to be pointers to +** the correct finder-function for that VFS. +** +** Most finder functions return a pointer to a fixed sqlite3_io_methods +** object. The only interesting finder-function is autolockIoFinder, which +** looks at the filesystem type and tries to guess the best locking +** strategy from that. +** +** For finder-function F, two objects are created: +** +** (1) The real finder-function named "FImpt()". +** +** (2) A constant pointer to this function named just "F". +** +** +** A pointer to the F pointer is used as the pAppData value for VFS +** objects. We have to do this instead of letting pAppData point +** directly at the finder-function since C90 rules prevent a void* +** from be cast into a function pointer. +** +** +** Each instance of this macro generates two objects: +** +** * A constant sqlite3_io_methods object call METHOD that has locking +** methods CLOSE, LOCK, UNLOCK, CKRESLOCK. +** +** * An I/O method finder function called FINDER that returns a pointer +** to the METHOD object in the previous bullet. +*/ +#define IOMETHODS(FINDER,METHOD,VERSION,CLOSE,LOCK,UNLOCK,CKLOCK,SHMMAP) \ +static const sqlite3_io_methods METHOD = { \ + VERSION, /* iVersion */ \ + CLOSE, /* xClose */ \ + unixRead, /* xRead */ \ + unixWrite, /* xWrite */ \ + unixTruncate, /* xTruncate */ \ + unixSync, /* xSync */ \ + unixFileSize, /* xFileSize */ \ + LOCK, /* xLock */ \ + UNLOCK, /* xUnlock */ \ + CKLOCK, /* xCheckReservedLock */ \ + unixFileControl, /* xFileControl */ \ + unixSectorSize, /* xSectorSize */ \ + unixDeviceCharacteristics, /* xDeviceCapabilities */ \ + SHMMAP, /* xShmMap */ \ + unixShmLock, /* xShmLock */ \ + unixShmBarrier, /* xShmBarrier */ \ + unixShmUnmap, /* xShmUnmap */ \ + unixFetch, /* xFetch */ \ + unixUnfetch, /* xUnfetch */ \ +}; \ +static const sqlite3_io_methods *FINDER##Impl(const char *z, unixFile *p){ \ + UNUSED_PARAMETER(z); UNUSED_PARAMETER(p); \ + return &METHOD; \ +} \ +static const sqlite3_io_methods *(*const FINDER)(const char*,unixFile *p) \ + = FINDER##Impl; + +/* +** Here are all of the sqlite3_io_methods objects for each of the +** locking strategies. Functions that return pointers to these methods +** are also created. +*/ +IOMETHODS( + posixIoFinder, /* Finder function name */ + posixIoMethods, /* sqlite3_io_methods object name */ + 3, /* shared memory and mmap are enabled */ + unixClose, /* xClose method */ + unixLock, /* xLock method */ + unixUnlock, /* xUnlock method */ + unixCheckReservedLock, /* xCheckReservedLock method */ + unixShmMap /* xShmMap method */ +) +IOMETHODS( + nolockIoFinder, /* Finder function name */ + nolockIoMethods, /* sqlite3_io_methods object name */ + 3, /* shared memory and mmap are enabled */ + nolockClose, /* xClose method */ + nolockLock, /* xLock method */ + nolockUnlock, /* xUnlock method */ + nolockCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ +) +IOMETHODS( + dotlockIoFinder, /* Finder function name */ + dotlockIoMethods, /* sqlite3_io_methods object name */ + 1, /* shared memory is disabled */ + dotlockClose, /* xClose method */ + dotlockLock, /* xLock method */ + dotlockUnlock, /* xUnlock method */ + dotlockCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ +) + +#if SQLITE_ENABLE_LOCKING_STYLE +IOMETHODS( + flockIoFinder, /* Finder function name */ + flockIoMethods, /* sqlite3_io_methods object name */ + 1, /* shared memory is disabled */ + flockClose, /* xClose method */ + flockLock, /* xLock method */ + flockUnlock, /* xUnlock method */ + flockCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ +) +#endif + +#if OS_VXWORKS +IOMETHODS( + semIoFinder, /* Finder function name */ + semIoMethods, /* sqlite3_io_methods object name */ + 1, /* shared memory is disabled */ + semXClose, /* xClose method */ + semXLock, /* xLock method */ + semXUnlock, /* xUnlock method */ + semXCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ +) +#endif + +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE +IOMETHODS( + afpIoFinder, /* Finder function name */ + afpIoMethods, /* sqlite3_io_methods object name */ + 1, /* shared memory is disabled */ + afpClose, /* xClose method */ + afpLock, /* xLock method */ + afpUnlock, /* xUnlock method */ + afpCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ +) +#endif + +/* +** The proxy locking method is a "super-method" in the sense that it +** opens secondary file descriptors for the conch and lock files and +** it uses proxy, dot-file, AFP, and flock() locking methods on those +** secondary files. For this reason, the division that implements +** proxy locking is located much further down in the file. But we need +** to go ahead and define the sqlite3_io_methods and finder function +** for proxy locking here. So we forward declare the I/O methods. +*/ +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE +static int proxyClose(sqlite3_file*); +static int proxyLock(sqlite3_file*, int); +static int proxyUnlock(sqlite3_file*, int); +static int proxyCheckReservedLock(sqlite3_file*, int*); +IOMETHODS( + proxyIoFinder, /* Finder function name */ + proxyIoMethods, /* sqlite3_io_methods object name */ + 1, /* shared memory is disabled */ + proxyClose, /* xClose method */ + proxyLock, /* xLock method */ + proxyUnlock, /* xUnlock method */ + proxyCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ +) +#endif + +/* nfs lockd on OSX 10.3+ doesn't clear write locks when a read lock is set */ +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE +IOMETHODS( + nfsIoFinder, /* Finder function name */ + nfsIoMethods, /* sqlite3_io_methods object name */ + 1, /* shared memory is disabled */ + unixClose, /* xClose method */ + unixLock, /* xLock method */ + nfsUnlock, /* xUnlock method */ + unixCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ +) +#endif + +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE +/* +** This "finder" function attempts to determine the best locking strategy +** for the database file "filePath". It then returns the sqlite3_io_methods +** object that implements that strategy. +** +** This is for MacOSX only. +*/ +static const sqlite3_io_methods *autolockIoFinderImpl( + const char *filePath, /* name of the database file */ + unixFile *pNew /* open file object for the database file */ +){ + static const struct Mapping { + const char *zFilesystem; /* Filesystem type name */ + const sqlite3_io_methods *pMethods; /* Appropriate locking method */ + } aMap[] = { + { "hfs", &posixIoMethods }, + { "ufs", &posixIoMethods }, + { "afpfs", &afpIoMethods }, + { "smbfs", &afpIoMethods }, + { "webdav", &nolockIoMethods }, + { 0, 0 } + }; + int i; + struct statfs fsInfo; + struct flock lockInfo; + + if( !filePath ){ + /* If filePath==NULL that means we are dealing with a transient file + ** that does not need to be locked. */ + return &nolockIoMethods; + } + if( statfs(filePath, &fsInfo) != -1 ){ + if( fsInfo.f_flags & MNT_RDONLY ){ + return &nolockIoMethods; + } + for(i=0; aMap[i].zFilesystem; i++){ + if( strcmp(fsInfo.f_fstypename, aMap[i].zFilesystem)==0 ){ + return aMap[i].pMethods; + } + } + } + + /* Default case. Handles, amongst others, "nfs". + ** Test byte-range lock using fcntl(). If the call succeeds, + ** assume that the file-system supports POSIX style locks. + */ + lockInfo.l_len = 1; + lockInfo.l_start = 0; + lockInfo.l_whence = SEEK_SET; + lockInfo.l_type = F_RDLCK; + if( osFcntl(pNew->h, F_GETLK, &lockInfo)!=-1 ) { + if( strcmp(fsInfo.f_fstypename, "nfs")==0 ){ + return &nfsIoMethods; + } else { + return &posixIoMethods; + } + }else{ + return &dotlockIoMethods; + } +} +static const sqlite3_io_methods + *(*const autolockIoFinder)(const char*,unixFile*) = autolockIoFinderImpl; + +#endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ + +#if OS_VXWORKS +/* +** This "finder" function for VxWorks checks to see if posix advisory +** locking works. If it does, then that is what is used. If it does not +** work, then fallback to named semaphore locking. +*/ +static const sqlite3_io_methods *vxworksIoFinderImpl( + const char *filePath, /* name of the database file */ + unixFile *pNew /* the open file object */ +){ + struct flock lockInfo; + + if( !filePath ){ + /* If filePath==NULL that means we are dealing with a transient file + ** that does not need to be locked. */ + return &nolockIoMethods; + } + + /* Test if fcntl() is supported and use POSIX style locks. + ** Otherwise fall back to the named semaphore method. + */ + lockInfo.l_len = 1; + lockInfo.l_start = 0; + lockInfo.l_whence = SEEK_SET; + lockInfo.l_type = F_RDLCK; + if( osFcntl(pNew->h, F_GETLK, &lockInfo)!=-1 ) { + return &posixIoMethods; + }else{ + return &semIoMethods; + } +} +static const sqlite3_io_methods + *(*const vxworksIoFinder)(const char*,unixFile*) = vxworksIoFinderImpl; + +#endif /* OS_VXWORKS */ + +/* +** An abstract type for a pointer to an IO method finder function: +*/ +typedef const sqlite3_io_methods *(*finder_type)(const char*,unixFile*); + + +/**************************************************************************** +**************************** sqlite3_vfs methods **************************** +** +** This division contains the implementation of methods on the +** sqlite3_vfs object. +*/ + +/* +** Initialize the contents of the unixFile structure pointed to by pId. +*/ +static int fillInUnixFile( + sqlite3_vfs *pVfs, /* Pointer to vfs object */ + int h, /* Open file descriptor of file being opened */ + sqlite3_file *pId, /* Write to the unixFile structure here */ + const char *zFilename, /* Name of the file being opened */ + int ctrlFlags /* Zero or more UNIXFILE_* values */ +){ + const sqlite3_io_methods *pLockingStyle; + unixFile *pNew = (unixFile *)pId; + int rc = SQLITE_OK; + + assert( pNew->pInode==NULL ); + + /* No locking occurs in temporary files */ + assert( zFilename!=0 || (ctrlFlags & UNIXFILE_NOLOCK)!=0 ); + + OSTRACE(("OPEN %-3d %s\n", h, zFilename)); + pNew->h = h; + pNew->pVfs = pVfs; + pNew->zPath = zFilename; + pNew->ctrlFlags = (u8)ctrlFlags; +#if SQLITE_MAX_MMAP_SIZE>0 + pNew->mmapSizeMax = sqlite3GlobalConfig.szMmap; +#endif + if( sqlite3_uri_boolean(((ctrlFlags & UNIXFILE_URI) ? zFilename : 0), + "psow", SQLITE_POWERSAFE_OVERWRITE) ){ + pNew->ctrlFlags |= UNIXFILE_PSOW; + } + if( strcmp(pVfs->zName,"unix-excl")==0 ){ + pNew->ctrlFlags |= UNIXFILE_EXCL; + } + +#if OS_VXWORKS + pNew->pId = vxworksFindFileId(zFilename); + if( pNew->pId==0 ){ + ctrlFlags |= UNIXFILE_NOLOCK; + rc = SQLITE_NOMEM_BKPT; + } +#endif + + if( ctrlFlags & UNIXFILE_NOLOCK ){ + pLockingStyle = &nolockIoMethods; + }else{ + pLockingStyle = (**(finder_type*)pVfs->pAppData)(zFilename, pNew); +#if SQLITE_ENABLE_LOCKING_STYLE + /* Cache zFilename in the locking context (AFP and dotlock override) for + ** proxyLock activation is possible (remote proxy is based on db name) + ** zFilename remains valid until file is closed, to support */ + pNew->lockingContext = (void*)zFilename; +#endif + } + + if( pLockingStyle == &posixIoMethods +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE + || pLockingStyle == &nfsIoMethods +#endif + ){ + unixEnterMutex(); + rc = findInodeInfo(pNew, &pNew->pInode); + if( rc!=SQLITE_OK ){ + /* If an error occurred in findInodeInfo(), close the file descriptor + ** immediately, before releasing the mutex. findInodeInfo() may fail + ** in two scenarios: + ** + ** (a) A call to fstat() failed. + ** (b) A malloc failed. + ** + ** Scenario (b) may only occur if the process is holding no other + ** file descriptors open on the same file. If there were other file + ** descriptors on this file, then no malloc would be required by + ** findInodeInfo(). If this is the case, it is quite safe to close + ** handle h - as it is guaranteed that no posix locks will be released + ** by doing so. + ** + ** If scenario (a) caused the error then things are not so safe. The + ** implicit assumption here is that if fstat() fails, things are in + ** such bad shape that dropping a lock or two doesn't matter much. + */ + robust_close(pNew, h, __LINE__); + h = -1; + } + unixLeaveMutex(); + } + +#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) + else if( pLockingStyle == &afpIoMethods ){ + /* AFP locking uses the file path so it needs to be included in + ** the afpLockingContext. + */ + afpLockingContext *pCtx; + pNew->lockingContext = pCtx = sqlite3_malloc64( sizeof(*pCtx) ); + if( pCtx==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + /* NB: zFilename exists and remains valid until the file is closed + ** according to requirement F11141. So we do not need to make a + ** copy of the filename. */ + pCtx->dbPath = zFilename; + pCtx->reserved = 0; + srandomdev(); + unixEnterMutex(); + rc = findInodeInfo(pNew, &pNew->pInode); + if( rc!=SQLITE_OK ){ + sqlite3_free(pNew->lockingContext); + robust_close(pNew, h, __LINE__); + h = -1; + } + unixLeaveMutex(); + } + } +#endif + + else if( pLockingStyle == &dotlockIoMethods ){ + /* Dotfile locking uses the file path so it needs to be included in + ** the dotlockLockingContext + */ + char *zLockFile; + int nFilename; + assert( zFilename!=0 ); + nFilename = (int)strlen(zFilename) + 6; + zLockFile = (char *)sqlite3_malloc64(nFilename); + if( zLockFile==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + sqlite3_snprintf(nFilename, zLockFile, "%s" DOTLOCK_SUFFIX, zFilename); + } + pNew->lockingContext = zLockFile; + } + +#if OS_VXWORKS + else if( pLockingStyle == &semIoMethods ){ + /* Named semaphore locking uses the file path so it needs to be + ** included in the semLockingContext + */ + unixEnterMutex(); + rc = findInodeInfo(pNew, &pNew->pInode); + if( (rc==SQLITE_OK) && (pNew->pInode->pSem==NULL) ){ + char *zSemName = pNew->pInode->aSemName; + int n; + sqlite3_snprintf(MAX_PATHNAME, zSemName, "/%s.sem", + pNew->pId->zCanonicalName); + for( n=1; zSemName[n]; n++ ) + if( zSemName[n]=='/' ) zSemName[n] = '_'; + pNew->pInode->pSem = sem_open(zSemName, O_CREAT, 0666, 1); + if( pNew->pInode->pSem == SEM_FAILED ){ + rc = SQLITE_NOMEM_BKPT; + pNew->pInode->aSemName[0] = '\0'; + } + } + unixLeaveMutex(); + } +#endif + + storeLastErrno(pNew, 0); +#if OS_VXWORKS + if( rc!=SQLITE_OK ){ + if( h>=0 ) robust_close(pNew, h, __LINE__); + h = -1; + osUnlink(zFilename); + pNew->ctrlFlags |= UNIXFILE_DELETE; + } +#endif + if( rc!=SQLITE_OK ){ + if( h>=0 ) robust_close(pNew, h, __LINE__); + }else{ + pId->pMethods = pLockingStyle; + OpenCounter(+1); + verifyDbFile(pNew); + } + return rc; +} + +/* +** Directories to consider for temp files. +*/ +static const char *azTempDirs[] = { + 0, + 0, + "/var/tmp", + "/usr/tmp", + "/tmp", + "." +}; + +/* +** Initialize first two members of azTempDirs[] array. +*/ +static void unixTempFileInit(void){ + azTempDirs[0] = getenv("SQLITE_TMPDIR"); + azTempDirs[1] = getenv("TMPDIR"); +} + +/* +** Return the name of a directory in which to put temporary files. +** If no suitable temporary file directory can be found, return NULL. +*/ +static const char *unixTempFileDir(void){ + unsigned int i = 0; + struct stat buf; + const char *zDir = sqlite3_temp_directory; + + while(1){ + if( zDir!=0 + && osStat(zDir, &buf)==0 + && S_ISDIR(buf.st_mode) + && osAccess(zDir, 03)==0 + ){ + return zDir; + } + if( i>=sizeof(azTempDirs)/sizeof(azTempDirs[0]) ) break; + zDir = azTempDirs[i++]; + } + return 0; +} + +/* +** Create a temporary file name in zBuf. zBuf must be allocated +** by the calling process and must be big enough to hold at least +** pVfs->mxPathname bytes. +*/ +static int unixGetTempname(int nBuf, char *zBuf){ + const char *zDir; + int iLimit = 0; + int rc = SQLITE_OK; + + /* It's odd to simulate an io-error here, but really this is just + ** using the io-error infrastructure to test that SQLite handles this + ** function failing. + */ + zBuf[0] = 0; + SimulateIOError( return SQLITE_IOERR ); + + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + zDir = unixTempFileDir(); + if( zDir==0 ){ + rc = SQLITE_IOERR_GETTEMPPATH; + }else{ + do{ + u64 r; + sqlite3_randomness(sizeof(r), &r); + assert( nBuf>2 ); + zBuf[nBuf-2] = 0; + sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c", + zDir, r, 0); + if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ){ + rc = SQLITE_ERROR; + break; + } + }while( osAccess(zBuf,0)==0 ); + } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return rc; +} + +#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) +/* +** Routine to transform a unixFile into a proxy-locking unixFile. +** Implementation in the proxy-lock division, but used by unixOpen() +** if SQLITE_PREFER_PROXY_LOCKING is defined. +*/ +static int proxyTransformUnixFile(unixFile*, const char*); +#endif + +/* +** Search for an unused file descriptor that was opened on the database +** file (not a journal or super-journal file) identified by pathname +** zPath with SQLITE_OPEN_XXX flags matching those passed as the second +** argument to this function. +** +** Such a file descriptor may exist if a database connection was closed +** but the associated file descriptor could not be closed because some +** other file descriptor open on the same file is holding a file-lock. +** Refer to comments in the unixClose() function and the lengthy comment +** describing "Posix Advisory Locking" at the start of this file for +** further details. Also, ticket #4018. +** +** If a suitable file descriptor is found, then it is returned. If no +** such file descriptor is located, -1 is returned. +*/ +static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ + UnixUnusedFd *pUnused = 0; + + /* Do not search for an unused file descriptor on vxworks. Not because + ** vxworks would not benefit from the change (it might, we're not sure), + ** but because no way to test it is currently available. It is better + ** not to risk breaking vxworks support for the sake of such an obscure + ** feature. */ +#if !OS_VXWORKS + struct stat sStat; /* Results of stat() call */ + + unixEnterMutex(); + + /* A stat() call may fail for various reasons. If this happens, it is + ** almost certain that an open() call on the same path will also fail. + ** For this reason, if an error occurs in the stat() call here, it is + ** ignored and -1 is returned. The caller will try to open a new file + ** descriptor on the same path, fail, and return an error to SQLite. + ** + ** Even if a subsequent open() call does succeed, the consequences of + ** not searching for a reusable file descriptor are not dire. */ + if( inodeList!=0 && 0==osStat(zPath, &sStat) ){ + unixInodeInfo *pInode; + + pInode = inodeList; + while( pInode && (pInode->fileId.dev!=sStat.st_dev + || pInode->fileId.ino!=(u64)sStat.st_ino) ){ + pInode = pInode->pNext; + } + if( pInode ){ + UnixUnusedFd **pp; + assert( sqlite3_mutex_notheld(pInode->pLockMutex) ); + sqlite3_mutex_enter(pInode->pLockMutex); + flags &= (SQLITE_OPEN_READONLY|SQLITE_OPEN_READWRITE); + for(pp=&pInode->pUnused; *pp && (*pp)->flags!=flags; pp=&((*pp)->pNext)); + pUnused = *pp; + if( pUnused ){ + *pp = pUnused->pNext; + } + sqlite3_mutex_leave(pInode->pLockMutex); + } + } + unixLeaveMutex(); +#endif /* if !OS_VXWORKS */ + return pUnused; +} + +/* +** Find the mode, uid and gid of file zFile. +*/ +static int getFileMode( + const char *zFile, /* File name */ + mode_t *pMode, /* OUT: Permissions of zFile */ + uid_t *pUid, /* OUT: uid of zFile. */ + gid_t *pGid /* OUT: gid of zFile. */ +){ + struct stat sStat; /* Output of stat() on database file */ + int rc = SQLITE_OK; + if( 0==osStat(zFile, &sStat) ){ + *pMode = sStat.st_mode & 0777; + *pUid = sStat.st_uid; + *pGid = sStat.st_gid; + }else{ + rc = SQLITE_IOERR_FSTAT; + } + return rc; +} + +/* +** This function is called by unixOpen() to determine the unix permissions +** to create new files with. If no error occurs, then SQLITE_OK is returned +** and a value suitable for passing as the third argument to open(2) is +** written to *pMode. If an IO error occurs, an SQLite error code is +** returned and the value of *pMode is not modified. +** +** In most cases, this routine sets *pMode to 0, which will become +** an indication to robust_open() to create the file using +** SQLITE_DEFAULT_FILE_PERMISSIONS adjusted by the umask. +** But if the file being opened is a WAL or regular journal file, then +** this function queries the file-system for the permissions on the +** corresponding database file and sets *pMode to this value. Whenever +** possible, WAL and journal files are created using the same permissions +** as the associated database file. +** +** If the SQLITE_ENABLE_8_3_NAMES option is enabled, then the +** original filename is unavailable. But 8_3_NAMES is only used for +** FAT filesystems and permissions do not matter there, so just use +** the default permissions. In 8_3_NAMES mode, leave *pMode set to zero. +*/ +static int findCreateFileMode( + const char *zPath, /* Path of file (possibly) being created */ + int flags, /* Flags passed as 4th argument to xOpen() */ + mode_t *pMode, /* OUT: Permissions to open file with */ + uid_t *pUid, /* OUT: uid to set on the file */ + gid_t *pGid /* OUT: gid to set on the file */ +){ + int rc = SQLITE_OK; /* Return Code */ + *pMode = 0; + *pUid = 0; + *pGid = 0; + if( flags & (SQLITE_OPEN_WAL|SQLITE_OPEN_MAIN_JOURNAL) ){ + char zDb[MAX_PATHNAME+1]; /* Database file path */ + int nDb; /* Number of valid bytes in zDb */ + + /* zPath is a path to a WAL or journal file. The following block derives + ** the path to the associated database file from zPath. This block handles + ** the following naming conventions: + ** + ** "<path to db>-journal" + ** "<path to db>-wal" + ** "<path to db>-journalNN" + ** "<path to db>-walNN" + ** + ** where NN is a decimal number. The NN naming schemes are + ** used by the test_multiplex.c module. + ** + ** In normal operation, the journal file name will always contain + ** a '-' character. However in 8+3 filename mode, or if a corrupt + ** rollback journal specifies a super-journal with a goofy name, then + ** the '-' might be missing or the '-' might be the first character in + ** the filename. In that case, just return SQLITE_OK with *pMode==0. + */ + nDb = sqlite3Strlen30(zPath) - 1; + while( nDb>0 && zPath[nDb]!='.' ){ + if( zPath[nDb]=='-' ){ + memcpy(zDb, zPath, nDb); + zDb[nDb] = '\0'; + rc = getFileMode(zDb, pMode, pUid, pGid); + break; + } + nDb--; + } + }else if( flags & SQLITE_OPEN_DELETEONCLOSE ){ + *pMode = 0600; + }else if( flags & SQLITE_OPEN_URI ){ + /* If this is a main database file and the file was opened using a URI + ** filename, check for the "modeof" parameter. If present, interpret + ** its value as a filename and try to copy the mode, uid and gid from + ** that file. */ + const char *z = sqlite3_uri_parameter(zPath, "modeof"); + if( z ){ + rc = getFileMode(z, pMode, pUid, pGid); + } + } + return rc; +} + +/* +** Open the file zPath. +** +** Previously, the SQLite OS layer used three functions in place of this +** one: +** +** sqlite3OsOpenReadWrite(); +** sqlite3OsOpenReadOnly(); +** sqlite3OsOpenExclusive(); +** +** These calls correspond to the following combinations of flags: +** +** ReadWrite() -> (READWRITE | CREATE) +** ReadOnly() -> (READONLY) +** OpenExclusive() -> (READWRITE | CREATE | EXCLUSIVE) +** +** The old OpenExclusive() accepted a boolean argument - "delFlag". If +** true, the file was configured to be automatically deleted when the +** file handle closed. To achieve the same effect using this new +** interface, add the DELETEONCLOSE flag to those specified above for +** OpenExclusive(). +*/ +static int unixOpen( + sqlite3_vfs *pVfs, /* The VFS for which this is the xOpen method */ + const char *zPath, /* Pathname of file to be opened */ + sqlite3_file *pFile, /* The file descriptor to be filled in */ + int flags, /* Input flags to control the opening */ + int *pOutFlags /* Output flags returned to SQLite core */ +){ + unixFile *p = (unixFile *)pFile; + int fd = -1; /* File descriptor returned by open() */ + int openFlags = 0; /* Flags to pass to open() */ + int eType = flags&0x0FFF00; /* Type of file to open */ + int noLock; /* True to omit locking primitives */ + int rc = SQLITE_OK; /* Function Return Code */ + int ctrlFlags = 0; /* UNIXFILE_* flags */ + + int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE); + int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE); + int isCreate = (flags & SQLITE_OPEN_CREATE); + int isReadonly = (flags & SQLITE_OPEN_READONLY); + int isReadWrite = (flags & SQLITE_OPEN_READWRITE); +#if SQLITE_ENABLE_LOCKING_STYLE + int isAutoProxy = (flags & SQLITE_OPEN_AUTOPROXY); +#endif +#if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE + struct statfs fsInfo; +#endif + + /* If creating a super- or main-file journal, this function will open + ** a file-descriptor on the directory too. The first time unixSync() + ** is called the directory file descriptor will be fsync()ed and close()d. + */ + int isNewJrnl = (isCreate && ( + eType==SQLITE_OPEN_SUPER_JOURNAL + || eType==SQLITE_OPEN_MAIN_JOURNAL + || eType==SQLITE_OPEN_WAL + )); + + /* If argument zPath is a NULL pointer, this function is required to open + ** a temporary file. Use this buffer to store the file name in. + */ + char zTmpname[MAX_PATHNAME+2]; + const char *zName = zPath; + + /* Check the following statements are true: + ** + ** (a) Exactly one of the READWRITE and READONLY flags must be set, and + ** (b) if CREATE is set, then READWRITE must also be set, and + ** (c) if EXCLUSIVE is set, then CREATE must also be set. + ** (d) if DELETEONCLOSE is set, then CREATE must also be set. + */ + assert((isReadonly==0 || isReadWrite==0) && (isReadWrite || isReadonly)); + assert(isCreate==0 || isReadWrite); + assert(isExclusive==0 || isCreate); + assert(isDelete==0 || isCreate); + + /* The main DB, main journal, WAL file and super-journal are never + ** automatically deleted. Nor are they ever temporary files. */ + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_DB ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_JOURNAL ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_SUPER_JOURNAL ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_WAL ); + + /* Assert that the upper layer has set one of the "file-type" flags. */ + assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL + || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_SUPER_JOURNAL + || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL + ); + + /* Detect a pid change and reset the PRNG. There is a race condition + ** here such that two or more threads all trying to open databases at + ** the same instant might all reset the PRNG. But multiple resets + ** are harmless. + */ + if( randomnessPid!=osGetpid(0) ){ + randomnessPid = osGetpid(0); + sqlite3_randomness(0,0); + } + memset(p, 0, sizeof(unixFile)); + +#ifdef SQLITE_ASSERT_NO_FILES + /* Applications that never read or write a persistent disk files */ + assert( zName==0 ); +#endif + + if( eType==SQLITE_OPEN_MAIN_DB ){ + UnixUnusedFd *pUnused; + pUnused = findReusableFd(zName, flags); + if( pUnused ){ + fd = pUnused->fd; + }else{ + pUnused = sqlite3_malloc64(sizeof(*pUnused)); + if( !pUnused ){ + return SQLITE_NOMEM_BKPT; + } + } + p->pPreallocatedUnused = pUnused; + + /* Database filenames are double-zero terminated if they are not + ** URIs with parameters. Hence, they can always be passed into + ** sqlite3_uri_parameter(). */ + assert( (flags & SQLITE_OPEN_URI) || zName[strlen(zName)+1]==0 ); + + }else if( !zName ){ + /* If zName is NULL, the upper layer is requesting a temp file. */ + assert(isDelete && !isNewJrnl); + rc = unixGetTempname(pVfs->mxPathname, zTmpname); + if( rc!=SQLITE_OK ){ + return rc; + } + zName = zTmpname; + + /* Generated temporary filenames are always double-zero terminated + ** for use by sqlite3_uri_parameter(). */ + assert( zName[strlen(zName)+1]==0 ); + } + + /* Determine the value of the flags parameter passed to POSIX function + ** open(). These must be calculated even if open() is not called, as + ** they may be stored as part of the file handle and used by the + ** 'conch file' locking functions later on. */ + if( isReadonly ) openFlags |= O_RDONLY; + if( isReadWrite ) openFlags |= O_RDWR; + if( isCreate ) openFlags |= O_CREAT; + if( isExclusive ) openFlags |= (O_EXCL|O_NOFOLLOW); + openFlags |= (O_LARGEFILE|O_BINARY|O_NOFOLLOW); + + if( fd<0 ){ + mode_t openMode; /* Permissions to create file with */ + uid_t uid; /* Userid for the file */ + gid_t gid; /* Groupid for the file */ + rc = findCreateFileMode(zName, flags, &openMode, &uid, &gid); + if( rc!=SQLITE_OK ){ + assert( !p->pPreallocatedUnused ); + assert( eType==SQLITE_OPEN_WAL || eType==SQLITE_OPEN_MAIN_JOURNAL ); + return rc; + } + fd = robust_open(zName, openFlags, openMode); + OSTRACE(("OPENX %-3d %s 0%o\n", fd, zName, openFlags)); + assert( !isExclusive || (openFlags & O_CREAT)!=0 ); + if( fd<0 ){ + if( isNewJrnl && errno==EACCES && osAccess(zName, F_OK) ){ + /* If unable to create a journal because the directory is not + ** writable, change the error code to indicate that. */ + rc = SQLITE_READONLY_DIRECTORY; + }else if( errno!=EISDIR && isReadWrite ){ + /* Failed to open the file for read/write access. Try read-only. */ + flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); + openFlags &= ~(O_RDWR|O_CREAT); + flags |= SQLITE_OPEN_READONLY; + openFlags |= O_RDONLY; + isReadonly = 1; + fd = robust_open(zName, openFlags, openMode); + } + } + if( fd<0 ){ + int rc2 = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zName); + if( rc==SQLITE_OK ) rc = rc2; + goto open_finished; + } + + /* The owner of the rollback journal or WAL file should always be the + ** same as the owner of the database file. Try to ensure that this is + ** the case. The chown() system call will be a no-op if the current + ** process lacks root privileges, be we should at least try. Without + ** this step, if a root process opens a database file, it can leave + ** behinds a journal/WAL that is owned by root and hence make the + ** database inaccessible to unprivileged processes. + ** + ** If openMode==0, then that means uid and gid are not set correctly + ** (probably because SQLite is configured to use 8+3 filename mode) and + ** in that case we do not want to attempt the chown(). + */ + if( openMode && (flags & (SQLITE_OPEN_WAL|SQLITE_OPEN_MAIN_JOURNAL))!=0 ){ + robustFchown(fd, uid, gid); + } + } + assert( fd>=0 ); + if( pOutFlags ){ + *pOutFlags = flags; + } + + if( p->pPreallocatedUnused ){ + p->pPreallocatedUnused->fd = fd; + p->pPreallocatedUnused->flags = + flags & (SQLITE_OPEN_READONLY|SQLITE_OPEN_READWRITE); + } + + if( isDelete ){ +#if OS_VXWORKS + zPath = zName; +#elif defined(SQLITE_UNLINK_AFTER_CLOSE) + zPath = sqlite3_mprintf("%s", zName); + if( zPath==0 ){ + robust_close(p, fd, __LINE__); + return SQLITE_NOMEM_BKPT; + } +#else + osUnlink(zName); +#endif + } +#if SQLITE_ENABLE_LOCKING_STYLE + else{ + p->openFlags = openFlags; + } +#endif + +#if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE + if( fstatfs(fd, &fsInfo) == -1 ){ + storeLastErrno(p, errno); + robust_close(p, fd, __LINE__); + return SQLITE_IOERR_ACCESS; + } + if (0 == strncmp("msdos", fsInfo.f_fstypename, 5)) { + ((unixFile*)pFile)->fsFlags |= SQLITE_FSFLAGS_IS_MSDOS; + } + if (0 == strncmp("exfat", fsInfo.f_fstypename, 5)) { + ((unixFile*)pFile)->fsFlags |= SQLITE_FSFLAGS_IS_MSDOS; + } +#endif + + /* Set up appropriate ctrlFlags */ + if( isDelete ) ctrlFlags |= UNIXFILE_DELETE; + if( isReadonly ) ctrlFlags |= UNIXFILE_RDONLY; + noLock = eType!=SQLITE_OPEN_MAIN_DB; + if( noLock ) ctrlFlags |= UNIXFILE_NOLOCK; + if( isNewJrnl ) ctrlFlags |= UNIXFILE_DIRSYNC; + if( flags & SQLITE_OPEN_URI ) ctrlFlags |= UNIXFILE_URI; + +#if SQLITE_ENABLE_LOCKING_STYLE +#if SQLITE_PREFER_PROXY_LOCKING + isAutoProxy = 1; +#endif + if( isAutoProxy && (zPath!=NULL) && (!noLock) && pVfs->xOpen ){ + char *envforce = getenv("SQLITE_FORCE_PROXY_LOCKING"); + int useProxy = 0; + + /* SQLITE_FORCE_PROXY_LOCKING==1 means force always use proxy, 0 means + ** never use proxy, NULL means use proxy for non-local files only. */ + if( envforce!=NULL ){ + useProxy = atoi(envforce)>0; + }else{ + useProxy = !(fsInfo.f_flags&MNT_LOCAL); + } + if( useProxy ){ + rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags); + if( rc==SQLITE_OK ){ + rc = proxyTransformUnixFile((unixFile*)pFile, ":auto:"); + if( rc!=SQLITE_OK ){ + /* Use unixClose to clean up the resources added in fillInUnixFile + ** and clear all the structure's references. Specifically, + ** pFile->pMethods will be NULL so sqlite3OsClose will be a no-op + */ + unixClose(pFile); + return rc; + } + } + goto open_finished; + } + } +#endif + + assert( zPath==0 || zPath[0]=='/' + || eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL + ); + rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags); + +open_finished: + if( rc!=SQLITE_OK ){ + sqlite3_free(p->pPreallocatedUnused); + } + return rc; +} + + +/* +** Delete the file at zPath. If the dirSync argument is true, fsync() +** the directory after deleting the file. +*/ +static int unixDelete( + sqlite3_vfs *NotUsed, /* VFS containing this as the xDelete method */ + const char *zPath, /* Name of file to be deleted */ + int dirSync /* If true, fsync() directory after deleting file */ +){ + int rc = SQLITE_OK; + UNUSED_PARAMETER(NotUsed); + SimulateIOError(return SQLITE_IOERR_DELETE); + if( osUnlink(zPath)==(-1) ){ + if( errno==ENOENT +#if OS_VXWORKS + || osAccess(zPath,0)!=0 +#endif + ){ + rc = SQLITE_IOERR_DELETE_NOENT; + }else{ + rc = unixLogError(SQLITE_IOERR_DELETE, "unlink", zPath); + } + return rc; + } +#ifndef SQLITE_DISABLE_DIRSYNC + if( (dirSync & 1)!=0 ){ + int fd; + rc = osOpenDirectory(zPath, &fd); + if( rc==SQLITE_OK ){ + if( full_fsync(fd,0,0) ){ + rc = unixLogError(SQLITE_IOERR_DIR_FSYNC, "fsync", zPath); + } + robust_close(0, fd, __LINE__); + }else{ + assert( rc==SQLITE_CANTOPEN ); + rc = SQLITE_OK; + } + } +#endif + return rc; +} + +/* +** Test the existence of or access permissions of file zPath. The +** test performed depends on the value of flags: +** +** SQLITE_ACCESS_EXISTS: Return 1 if the file exists +** SQLITE_ACCESS_READWRITE: Return 1 if the file is read and writable. +** SQLITE_ACCESS_READONLY: Return 1 if the file is readable. +** +** Otherwise return 0. +*/ +static int unixAccess( + sqlite3_vfs *NotUsed, /* The VFS containing this xAccess method */ + const char *zPath, /* Path of the file to examine */ + int flags, /* What do we want to learn about the zPath file? */ + int *pResOut /* Write result boolean here */ +){ + UNUSED_PARAMETER(NotUsed); + SimulateIOError( return SQLITE_IOERR_ACCESS; ); + assert( pResOut!=0 ); + + /* The spec says there are three possible values for flags. But only + ** two of them are actually used */ + assert( flags==SQLITE_ACCESS_EXISTS || flags==SQLITE_ACCESS_READWRITE ); + + if( flags==SQLITE_ACCESS_EXISTS ){ + struct stat buf; + *pResOut = 0==osStat(zPath, &buf) && + (!S_ISREG(buf.st_mode) || buf.st_size>0); + }else{ + *pResOut = osAccess(zPath, W_OK|R_OK)==0; + } + return SQLITE_OK; +} + +/* +** A pathname under construction +*/ +typedef struct DbPath DbPath; +struct DbPath { + int rc; /* Non-zero following any error */ + int nSymlink; /* Number of symlinks resolved */ + char *zOut; /* Write the pathname here */ + int nOut; /* Bytes of space available to zOut[] */ + int nUsed; /* Bytes of zOut[] currently being used */ +}; + +/* Forward reference */ +static void appendAllPathElements(DbPath*,const char*); + +/* +** Append a single path element to the DbPath under construction +*/ +static void appendOnePathElement( + DbPath *pPath, /* Path under construction, to which to append zName */ + const char *zName, /* Name to append to pPath. Not zero-terminated */ + int nName /* Number of significant bytes in zName */ +){ + assert( nName>0 ); + assert( zName!=0 ); + if( zName[0]=='.' ){ + if( nName==1 ) return; + if( zName[1]=='.' && nName==2 ){ + if( pPath->nUsed>1 ){ + assert( pPath->zOut[0]=='/' ); + while( pPath->zOut[--pPath->nUsed]!='/' ){} + } + return; + } + } + if( pPath->nUsed + nName + 2 >= pPath->nOut ){ + pPath->rc = SQLITE_ERROR; + return; + } + pPath->zOut[pPath->nUsed++] = '/'; + memcpy(&pPath->zOut[pPath->nUsed], zName, nName); + pPath->nUsed += nName; +#if defined(HAVE_READLINK) && defined(HAVE_LSTAT) + if( pPath->rc==SQLITE_OK ){ + const char *zIn; + struct stat buf; + pPath->zOut[pPath->nUsed] = 0; + zIn = pPath->zOut; + if( osLstat(zIn, &buf)!=0 ){ + if( errno!=ENOENT ){ + pPath->rc = unixLogError(SQLITE_CANTOPEN_BKPT, "lstat", zIn); + } + }else if( S_ISLNK(buf.st_mode) ){ + ssize_t got; + char zLnk[SQLITE_MAX_PATHLEN+2]; + if( pPath->nSymlink++ > SQLITE_MAX_SYMLINK ){ + pPath->rc = SQLITE_CANTOPEN_BKPT; + return; + } + got = osReadlink(zIn, zLnk, sizeof(zLnk)-2); + if( got<=0 || got>=(ssize_t)sizeof(zLnk)-2 ){ + pPath->rc = unixLogError(SQLITE_CANTOPEN_BKPT, "readlink", zIn); + return; + } + zLnk[got] = 0; + if( zLnk[0]=='/' ){ + pPath->nUsed = 0; + }else{ + pPath->nUsed -= nName + 1; + } + appendAllPathElements(pPath, zLnk); + } + } +#endif +} + +/* +** Append all path elements in zPath to the DbPath under construction. +*/ +static void appendAllPathElements( + DbPath *pPath, /* Path under construction, to which to append zName */ + const char *zPath /* Path to append to pPath. Is zero-terminated */ +){ + int i = 0; + int j = 0; + do{ + while( zPath[i] && zPath[i]!='/' ){ i++; } + if( i>j ){ + appendOnePathElement(pPath, &zPath[j], i-j); + } + j = i+1; + }while( zPath[i++] ); +} + +/* +** Turn a relative pathname into a full pathname. The relative path +** is stored as a nul-terminated string in the buffer pointed to by +** zPath. +** +** zOut points to a buffer of at least sqlite3_vfs.mxPathname bytes +** (in this case, MAX_PATHNAME bytes). The full-path is written to +** this buffer before returning. +*/ +static int unixFullPathname( + sqlite3_vfs *pVfs, /* Pointer to vfs object */ + const char *zPath, /* Possibly relative input path */ + int nOut, /* Size of output buffer in bytes */ + char *zOut /* Output buffer */ +){ + DbPath path; + UNUSED_PARAMETER(pVfs); + path.rc = 0; + path.nUsed = 0; + path.nSymlink = 0; + path.nOut = nOut; + path.zOut = zOut; + if( zPath[0]!='/' ){ + char zPwd[SQLITE_MAX_PATHLEN+2]; + if( osGetcwd(zPwd, sizeof(zPwd)-2)==0 ){ + return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath); + } + appendAllPathElements(&path, zPwd); + } + appendAllPathElements(&path, zPath); + zOut[path.nUsed] = 0; + if( path.rc || path.nUsed<2 ) return SQLITE_CANTOPEN_BKPT; + if( path.nSymlink ) return SQLITE_OK_SYMLINK; + return SQLITE_OK; +} + +#ifndef SQLITE_OMIT_LOAD_EXTENSION +/* +** Interfaces for opening a shared library, finding entry points +** within the shared library, and closing the shared library. +*/ +#include <dlfcn.h> +static void *unixDlOpen(sqlite3_vfs *NotUsed, const char *zFilename){ + UNUSED_PARAMETER(NotUsed); + return dlopen(zFilename, RTLD_NOW | RTLD_GLOBAL); +} + +/* +** SQLite calls this function immediately after a call to unixDlSym() or +** unixDlOpen() fails (returns a null pointer). If a more detailed error +** message is available, it is written to zBufOut. If no error message +** is available, zBufOut is left unmodified and SQLite uses a default +** error message. +*/ +static void unixDlError(sqlite3_vfs *NotUsed, int nBuf, char *zBufOut){ + const char *zErr; + UNUSED_PARAMETER(NotUsed); + unixEnterMutex(); + zErr = dlerror(); + if( zErr ){ + sqlite3_snprintf(nBuf, zBufOut, "%s", zErr); + } + unixLeaveMutex(); +} +static void (*unixDlSym(sqlite3_vfs *NotUsed, void *p, const char*zSym))(void){ + /* + ** GCC with -pedantic-errors says that C90 does not allow a void* to be + ** cast into a pointer to a function. And yet the library dlsym() routine + ** returns a void* which is really a pointer to a function. So how do we + ** use dlsym() with -pedantic-errors? + ** + ** Variable x below is defined to be a pointer to a function taking + ** parameters void* and const char* and returning a pointer to a function. + ** We initialize x by assigning it a pointer to the dlsym() function. + ** (That assignment requires a cast.) Then we call the function that + ** x points to. + ** + ** This work-around is unlikely to work correctly on any system where + ** you really cannot cast a function pointer into void*. But then, on the + ** other hand, dlsym() will not work on such a system either, so we have + ** not really lost anything. + */ + void (*(*x)(void*,const char*))(void); + UNUSED_PARAMETER(NotUsed); + x = (void(*(*)(void*,const char*))(void))dlsym; + return (*x)(p, zSym); +} +static void unixDlClose(sqlite3_vfs *NotUsed, void *pHandle){ + UNUSED_PARAMETER(NotUsed); + dlclose(pHandle); +} +#else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */ + #define unixDlOpen 0 + #define unixDlError 0 + #define unixDlSym 0 + #define unixDlClose 0 +#endif + +/* +** Write nBuf bytes of random data to the supplied buffer zBuf. +*/ +static int unixRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){ + UNUSED_PARAMETER(NotUsed); + assert((size_t)nBuf>=(sizeof(time_t)+sizeof(int))); + + /* We have to initialize zBuf to prevent valgrind from reporting + ** errors. The reports issued by valgrind are incorrect - we would + ** prefer that the randomness be increased by making use of the + ** uninitialized space in zBuf - but valgrind errors tend to worry + ** some users. Rather than argue, it seems easier just to initialize + ** the whole array and silence valgrind, even if that means less randomness + ** in the random seed. + ** + ** When testing, initializing zBuf[] to zero is all we do. That means + ** that we always use the same random number sequence. This makes the + ** tests repeatable. + */ + memset(zBuf, 0, nBuf); + randomnessPid = osGetpid(0); +#if !defined(SQLITE_TEST) && !defined(SQLITE_OMIT_RANDOMNESS) + { + int fd, got; + fd = robust_open("/dev/urandom", O_RDONLY, 0); + if( fd<0 ){ + time_t t; + time(&t); + memcpy(zBuf, &t, sizeof(t)); + memcpy(&zBuf[sizeof(t)], &randomnessPid, sizeof(randomnessPid)); + assert( sizeof(t)+sizeof(randomnessPid)<=(size_t)nBuf ); + nBuf = sizeof(t) + sizeof(randomnessPid); + }else{ + do{ got = osRead(fd, zBuf, nBuf); }while( got<0 && errno==EINTR ); + robust_close(0, fd, __LINE__); + } + } +#endif + return nBuf; +} + + +/* +** Sleep for a little while. Return the amount of time slept. +** The argument is the number of microseconds we want to sleep. +** The return value is the number of microseconds of sleep actually +** requested from the underlying operating system, a number which +** might be greater than or equal to the argument, but not less +** than the argument. +*/ +static int unixSleep(sqlite3_vfs *NotUsed, int microseconds){ +#if !defined(HAVE_NANOSLEEP) || HAVE_NANOSLEEP+0 + struct timespec sp; + sp.tv_sec = microseconds / 1000000; + sp.tv_nsec = (microseconds % 1000000) * 1000; + + /* Almost all modern unix systems support nanosleep(). But if you are + ** compiling for one of the rare exceptions, you can use + ** -DHAVE_NANOSLEEP=0 (perhaps in conjuction with -DHAVE_USLEEP if + ** usleep() is available) in order to bypass the use of nanosleep() */ + nanosleep(&sp, NULL); + + UNUSED_PARAMETER(NotUsed); + return microseconds; +#elif defined(HAVE_USLEEP) && HAVE_USLEEP + if( microseconds>=1000000 ) sleep(microseconds/1000000); + if( microseconds%1000000 ) usleep(microseconds%1000000); + UNUSED_PARAMETER(NotUsed); + return microseconds; +#else + int seconds = (microseconds+999999)/1000000; + sleep(seconds); + UNUSED_PARAMETER(NotUsed); + return seconds*1000000; +#endif +} + +/* +** The following variable, if set to a non-zero value, is interpreted as +** the number of seconds since 1970 and is used to set the result of +** sqlite3OsCurrentTime() during testing. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_current_time = 0; /* Fake system time in seconds since 1970. */ +#endif + +/* +** Find the current time (in Universal Coordinated Time). Write into *piNow +** the current time and date as a Julian Day number times 86_400_000. In +** other words, write into *piNow the number of milliseconds since the Julian +** epoch of noon in Greenwich on November 24, 4714 B.C according to the +** proleptic Gregorian calendar. +** +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** cannot be found. +*/ +static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){ + static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; + int rc = SQLITE_OK; +#if defined(NO_GETTOD) + time_t t; + time(&t); + *piNow = ((sqlite3_int64)t)*1000 + unixEpoch; +#elif OS_VXWORKS + struct timespec sNow; + clock_gettime(CLOCK_REALTIME, &sNow); + *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_nsec/1000000; +#else + struct timeval sNow; + (void)gettimeofday(&sNow, 0); /* Cannot fail given valid arguments */ + *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; +#endif + +#ifdef SQLITE_TEST + if( sqlite3_current_time ){ + *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch; + } +#endif + UNUSED_PARAMETER(NotUsed); + return rc; +} + +#ifndef SQLITE_OMIT_DEPRECATED +/* +** Find the current time (in Universal Coordinated Time). Write the +** current time and date as a Julian Day number into *prNow and +** return 0. Return 1 if the time and date cannot be found. +*/ +static int unixCurrentTime(sqlite3_vfs *NotUsed, double *prNow){ + sqlite3_int64 i = 0; + int rc; + UNUSED_PARAMETER(NotUsed); + rc = unixCurrentTimeInt64(0, &i); + *prNow = i/86400000.0; + return rc; +} +#else +# define unixCurrentTime 0 +#endif + +/* +** The xGetLastError() method is designed to return a better +** low-level error message when operating-system problems come up +** during SQLite operation. Only the integer return code is currently +** used. +*/ +static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ + UNUSED_PARAMETER(NotUsed); + UNUSED_PARAMETER(NotUsed2); + UNUSED_PARAMETER(NotUsed3); + return errno; +} + + +/* +************************ End of sqlite3_vfs methods *************************** +******************************************************************************/ + +/****************************************************************************** +************************** Begin Proxy Locking ******************************** +** +** Proxy locking is a "uber-locking-method" in this sense: It uses the +** other locking methods on secondary lock files. Proxy locking is a +** meta-layer over top of the primitive locking implemented above. For +** this reason, the division that implements of proxy locking is deferred +** until late in the file (here) after all of the other I/O methods have +** been defined - so that the primitive locking methods are available +** as services to help with the implementation of proxy locking. +** +**** +** +** The default locking schemes in SQLite use byte-range locks on the +** database file to coordinate safe, concurrent access by multiple readers +** and writers [http://sqlite.org/lockingv3.html]. The five file locking +** states (UNLOCKED, PENDING, SHARED, RESERVED, EXCLUSIVE) are implemented +** as POSIX read & write locks over fixed set of locations (via fsctl), +** on AFP and SMB only exclusive byte-range locks are available via fsctl +** with _IOWR('z', 23, struct ByteRangeLockPB2) to track the same 5 states. +** To simulate a F_RDLCK on the shared range, on AFP a randomly selected +** address in the shared range is taken for a SHARED lock, the entire +** shared range is taken for an EXCLUSIVE lock): +** +** PENDING_BYTE 0x40000000 +** RESERVED_BYTE 0x40000001 +** SHARED_RANGE 0x40000002 -> 0x40000200 +** +** This works well on the local file system, but shows a nearly 100x +** slowdown in read performance on AFP because the AFP client disables +** the read cache when byte-range locks are present. Enabling the read +** cache exposes a cache coherency problem that is present on all OS X +** supported network file systems. NFS and AFP both observe the +** close-to-open semantics for ensuring cache coherency +** [http://nfs.sourceforge.net/#faq_a8], which does not effectively +** address the requirements for concurrent database access by multiple +** readers and writers +** [http://www.nabble.com/SQLite-on-NFS-cache-coherency-td15655701.html]. +** +** To address the performance and cache coherency issues, proxy file locking +** changes the way database access is controlled by limiting access to a +** single host at a time and moving file locks off of the database file +** and onto a proxy file on the local file system. +** +** +** Using proxy locks +** ----------------- +** +** C APIs +** +** sqlite3_file_control(db, dbname, SQLITE_FCNTL_SET_LOCKPROXYFILE, +** <proxy_path> | ":auto:"); +** sqlite3_file_control(db, dbname, SQLITE_FCNTL_GET_LOCKPROXYFILE, +** &<proxy_path>); +** +** +** SQL pragmas +** +** PRAGMA [database.]lock_proxy_file=<proxy_path> | :auto: +** PRAGMA [database.]lock_proxy_file +** +** Specifying ":auto:" means that if there is a conch file with a matching +** host ID in it, the proxy path in the conch file will be used, otherwise +** a proxy path based on the user's temp dir +** (via confstr(_CS_DARWIN_USER_TEMP_DIR,...)) will be used and the +** actual proxy file name is generated from the name and path of the +** database file. For example: +** +** For database path "/Users/me/foo.db" +** The lock path will be "<tmpdir>/sqliteplocks/_Users_me_foo.db:auto:") +** +** Once a lock proxy is configured for a database connection, it can not +** be removed, however it may be switched to a different proxy path via +** the above APIs (assuming the conch file is not being held by another +** connection or process). +** +** +** How proxy locking works +** ----------------------- +** +** Proxy file locking relies primarily on two new supporting files: +** +** * conch file to limit access to the database file to a single host +** at a time +** +** * proxy file to act as a proxy for the advisory locks normally +** taken on the database +** +** The conch file - to use a proxy file, sqlite must first "hold the conch" +** by taking an sqlite-style shared lock on the conch file, reading the +** contents and comparing the host's unique host ID (see below) and lock +** proxy path against the values stored in the conch. The conch file is +** stored in the same directory as the database file and the file name +** is patterned after the database file name as ".<databasename>-conch". +** If the conch file does not exist, or its contents do not match the +** host ID and/or proxy path, then the lock is escalated to an exclusive +** lock and the conch file contents is updated with the host ID and proxy +** path and the lock is downgraded to a shared lock again. If the conch +** is held by another process (with a shared lock), the exclusive lock +** will fail and SQLITE_BUSY is returned. +** +** The proxy file - a single-byte file used for all advisory file locks +** normally taken on the database file. This allows for safe sharing +** of the database file for multiple readers and writers on the same +** host (the conch ensures that they all use the same local lock file). +** +** Requesting the lock proxy does not immediately take the conch, it is +** only taken when the first request to lock database file is made. +** This matches the semantics of the traditional locking behavior, where +** opening a connection to a database file does not take a lock on it. +** The shared lock and an open file descriptor are maintained until +** the connection to the database is closed. +** +** The proxy file and the lock file are never deleted so they only need +** to be created the first time they are used. +** +** Configuration options +** --------------------- +** +** SQLITE_PREFER_PROXY_LOCKING +** +** Database files accessed on non-local file systems are +** automatically configured for proxy locking, lock files are +** named automatically using the same logic as +** PRAGMA lock_proxy_file=":auto:" +** +** SQLITE_PROXY_DEBUG +** +** Enables the logging of error messages during host id file +** retrieval and creation +** +** LOCKPROXYDIR +** +** Overrides the default directory used for lock proxy files that +** are named automatically via the ":auto:" setting +** +** SQLITE_DEFAULT_PROXYDIR_PERMISSIONS +** +** Permissions to use when creating a directory for storing the +** lock proxy files, only used when LOCKPROXYDIR is not set. +** +** +** As mentioned above, when compiled with SQLITE_PREFER_PROXY_LOCKING, +** setting the environment variable SQLITE_FORCE_PROXY_LOCKING to 1 will +** force proxy locking to be used for every database file opened, and 0 +** will force automatic proxy locking to be disabled for all database +** files (explicitly calling the SQLITE_FCNTL_SET_LOCKPROXYFILE pragma or +** sqlite_file_control API is not affected by SQLITE_FORCE_PROXY_LOCKING). +*/ + +/* +** Proxy locking is only available on MacOSX +*/ +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE + +/* +** The proxyLockingContext has the path and file structures for the remote +** and local proxy files in it +*/ +typedef struct proxyLockingContext proxyLockingContext; +struct proxyLockingContext { + unixFile *conchFile; /* Open conch file */ + char *conchFilePath; /* Name of the conch file */ + unixFile *lockProxy; /* Open proxy lock file */ + char *lockProxyPath; /* Name of the proxy lock file */ + char *dbPath; /* Name of the open file */ + int conchHeld; /* 1 if the conch is held, -1 if lockless */ + int nFails; /* Number of conch taking failures */ + void *oldLockingContext; /* Original lockingcontext to restore on close */ + sqlite3_io_methods const *pOldMethod; /* Original I/O methods for close */ +}; + +/* +** The proxy lock file path for the database at dbPath is written into lPath, +** which must point to valid, writable memory large enough for a maxLen length +** file path. +*/ +static int proxyGetLockPath(const char *dbPath, char *lPath, size_t maxLen){ + int len; + int dbLen; + int i; + +#ifdef LOCKPROXYDIR + len = strlcpy(lPath, LOCKPROXYDIR, maxLen); +#else +# ifdef _CS_DARWIN_USER_TEMP_DIR + { + if( !confstr(_CS_DARWIN_USER_TEMP_DIR, lPath, maxLen) ){ + OSTRACE(("GETLOCKPATH failed %s errno=%d pid=%d\n", + lPath, errno, osGetpid(0))); + return SQLITE_IOERR_LOCK; + } + len = strlcat(lPath, "sqliteplocks", maxLen); + } +# else + len = strlcpy(lPath, "/tmp/", maxLen); +# endif +#endif + + if( lPath[len-1]!='/' ){ + len = strlcat(lPath, "/", maxLen); + } + + /* transform the db path to a unique cache name */ + dbLen = (int)strlen(dbPath); + for( i=0; i<dbLen && (i+len+7)<(int)maxLen; i++){ + char c = dbPath[i]; + lPath[i+len] = (c=='/')?'_':c; + } + lPath[i+len]='\0'; + strlcat(lPath, ":auto:", maxLen); + OSTRACE(("GETLOCKPATH proxy lock path=%s pid=%d\n", lPath, osGetpid(0))); + return SQLITE_OK; +} + +/* + ** Creates the lock file and any missing directories in lockPath + */ +static int proxyCreateLockPath(const char *lockPath){ + int i, len; + char buf[MAXPATHLEN]; + int start = 0; + + assert(lockPath!=NULL); + /* try to create all the intermediate directories */ + len = (int)strlen(lockPath); + buf[0] = lockPath[0]; + for( i=1; i<len; i++ ){ + if( lockPath[i] == '/' && (i - start > 0) ){ + /* only mkdir if leaf dir != "." or "/" or ".." */ + if( i-start>2 || (i-start==1 && buf[start] != '.' && buf[start] != '/') + || (i-start==2 && buf[start] != '.' && buf[start+1] != '.') ){ + buf[i]='\0'; + if( osMkdir(buf, SQLITE_DEFAULT_PROXYDIR_PERMISSIONS) ){ + int err=errno; + if( err!=EEXIST ) { + OSTRACE(("CREATELOCKPATH FAILED creating %s, " + "'%s' proxy lock path=%s pid=%d\n", + buf, strerror(err), lockPath, osGetpid(0))); + return err; + } + } + } + start=i+1; + } + buf[i] = lockPath[i]; + } + OSTRACE(("CREATELOCKPATH proxy lock path=%s pid=%d\n",lockPath,osGetpid(0))); + return 0; +} + +/* +** Create a new VFS file descriptor (stored in memory obtained from +** sqlite3_malloc) and open the file named "path" in the file descriptor. +** +** The caller is responsible not only for closing the file descriptor +** but also for freeing the memory associated with the file descriptor. +*/ +static int proxyCreateUnixFile( + const char *path, /* path for the new unixFile */ + unixFile **ppFile, /* unixFile created and returned by ref */ + int islockfile /* if non zero missing dirs will be created */ +) { + int fd = -1; + unixFile *pNew; + int rc = SQLITE_OK; + int openFlags = O_RDWR | O_CREAT | O_NOFOLLOW; + sqlite3_vfs dummyVfs; + int terrno = 0; + UnixUnusedFd *pUnused = NULL; + + /* 1. first try to open/create the file + ** 2. if that fails, and this is a lock file (not-conch), try creating + ** the parent directories and then try again. + ** 3. if that fails, try to open the file read-only + ** otherwise return BUSY (if lock file) or CANTOPEN for the conch file + */ + pUnused = findReusableFd(path, openFlags); + if( pUnused ){ + fd = pUnused->fd; + }else{ + pUnused = sqlite3_malloc64(sizeof(*pUnused)); + if( !pUnused ){ + return SQLITE_NOMEM_BKPT; + } + } + if( fd<0 ){ + fd = robust_open(path, openFlags, 0); + terrno = errno; + if( fd<0 && errno==ENOENT && islockfile ){ + if( proxyCreateLockPath(path) == SQLITE_OK ){ + fd = robust_open(path, openFlags, 0); + } + } + } + if( fd<0 ){ + openFlags = O_RDONLY | O_NOFOLLOW; + fd = robust_open(path, openFlags, 0); + terrno = errno; + } + if( fd<0 ){ + if( islockfile ){ + return SQLITE_BUSY; + } + switch (terrno) { + case EACCES: + return SQLITE_PERM; + case EIO: + return SQLITE_IOERR_LOCK; /* even though it is the conch */ + default: + return SQLITE_CANTOPEN_BKPT; + } + } + + pNew = (unixFile *)sqlite3_malloc64(sizeof(*pNew)); + if( pNew==NULL ){ + rc = SQLITE_NOMEM_BKPT; + goto end_create_proxy; + } + memset(pNew, 0, sizeof(unixFile)); + pNew->openFlags = openFlags; + memset(&dummyVfs, 0, sizeof(dummyVfs)); + dummyVfs.pAppData = (void*)&autolockIoFinder; + dummyVfs.zName = "dummy"; + pUnused->fd = fd; + pUnused->flags = openFlags; + pNew->pPreallocatedUnused = pUnused; + + rc = fillInUnixFile(&dummyVfs, fd, (sqlite3_file*)pNew, path, 0); + if( rc==SQLITE_OK ){ + *ppFile = pNew; + return SQLITE_OK; + } +end_create_proxy: + robust_close(pNew, fd, __LINE__); + sqlite3_free(pNew); + sqlite3_free(pUnused); + return rc; +} + +#ifdef SQLITE_TEST +/* simulate multiple hosts by creating unique hostid file paths */ +SQLITE_API int sqlite3_hostid_num = 0; +#endif + +#define PROXY_HOSTIDLEN 16 /* conch file host id length */ + +#if HAVE_GETHOSTUUID +/* Not always defined in the headers as it ought to be */ +extern int gethostuuid(uuid_t id, const struct timespec *wait); +#endif + +/* get the host ID via gethostuuid(), pHostID must point to PROXY_HOSTIDLEN +** bytes of writable memory. +*/ +static int proxyGetHostID(unsigned char *pHostID, int *pError){ + assert(PROXY_HOSTIDLEN == sizeof(uuid_t)); + memset(pHostID, 0, PROXY_HOSTIDLEN); +#if HAVE_GETHOSTUUID + { + struct timespec timeout = {1, 0}; /* 1 sec timeout */ + if( gethostuuid(pHostID, &timeout) ){ + int err = errno; + if( pError ){ + *pError = err; + } + return SQLITE_IOERR; + } + } +#else + UNUSED_PARAMETER(pError); +#endif +#ifdef SQLITE_TEST + /* simulate multiple hosts by creating unique hostid file paths */ + if( sqlite3_hostid_num != 0){ + pHostID[0] = (char)(pHostID[0] + (char)(sqlite3_hostid_num & 0xFF)); + } +#endif + + return SQLITE_OK; +} + +/* The conch file contains the header, host id and lock file path + */ +#define PROXY_CONCHVERSION 2 /* 1-byte header, 16-byte host id, path */ +#define PROXY_HEADERLEN 1 /* conch file header length */ +#define PROXY_PATHINDEX (PROXY_HEADERLEN+PROXY_HOSTIDLEN) +#define PROXY_MAXCONCHLEN (PROXY_HEADERLEN+PROXY_HOSTIDLEN+MAXPATHLEN) + +/* +** Takes an open conch file, copies the contents to a new path and then moves +** it back. The newly created file's file descriptor is assigned to the +** conch file structure and finally the original conch file descriptor is +** closed. Returns zero if successful. +*/ +static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){ + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + unixFile *conchFile = pCtx->conchFile; + char tPath[MAXPATHLEN]; + char buf[PROXY_MAXCONCHLEN]; + char *cPath = pCtx->conchFilePath; + size_t readLen = 0; + size_t pathLen = 0; + char errmsg[64] = ""; + int fd = -1; + int rc = -1; + UNUSED_PARAMETER(myHostID); + + /* create a new path by replace the trailing '-conch' with '-break' */ + pathLen = strlcpy(tPath, cPath, MAXPATHLEN); + if( pathLen>MAXPATHLEN || pathLen<6 || + (strlcpy(&tPath[pathLen-5], "break", 6) != 5) ){ + sqlite3_snprintf(sizeof(errmsg),errmsg,"path error (len %d)",(int)pathLen); + goto end_breaklock; + } + /* read the conch content */ + readLen = osPread(conchFile->h, buf, PROXY_MAXCONCHLEN, 0); + if( readLen<PROXY_PATHINDEX ){ + sqlite3_snprintf(sizeof(errmsg),errmsg,"read error (len %d)",(int)readLen); + goto end_breaklock; + } + /* write it out to the temporary break file */ + fd = robust_open(tPath, (O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW), 0); + if( fd<0 ){ + sqlite3_snprintf(sizeof(errmsg), errmsg, "create failed (%d)", errno); + goto end_breaklock; + } + if( osPwrite(fd, buf, readLen, 0) != (ssize_t)readLen ){ + sqlite3_snprintf(sizeof(errmsg), errmsg, "write failed (%d)", errno); + goto end_breaklock; + } + if( rename(tPath, cPath) ){ + sqlite3_snprintf(sizeof(errmsg), errmsg, "rename failed (%d)", errno); + goto end_breaklock; + } + rc = 0; + fprintf(stderr, "broke stale lock on %s\n", cPath); + robust_close(pFile, conchFile->h, __LINE__); + conchFile->h = fd; + conchFile->openFlags = O_RDWR | O_CREAT; + +end_breaklock: + if( rc ){ + if( fd>=0 ){ + osUnlink(tPath); + robust_close(pFile, fd, __LINE__); + } + fprintf(stderr, "failed to break stale lock on %s, %s\n", cPath, errmsg); + } + return rc; +} + +/* Take the requested lock on the conch file and break a stale lock if the +** host id matches. +*/ +static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){ + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + unixFile *conchFile = pCtx->conchFile; + int rc = SQLITE_OK; + int nTries = 0; + struct timespec conchModTime; + + memset(&conchModTime, 0, sizeof(conchModTime)); + do { + rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, lockType); + nTries ++; + if( rc==SQLITE_BUSY ){ + /* If the lock failed (busy): + * 1st try: get the mod time of the conch, wait 0.5s and try again. + * 2nd try: fail if the mod time changed or host id is different, wait + * 10 sec and try again + * 3rd try: break the lock unless the mod time has changed. + */ + struct stat buf; + if( osFstat(conchFile->h, &buf) ){ + storeLastErrno(pFile, errno); + return SQLITE_IOERR_LOCK; + } + + if( nTries==1 ){ + conchModTime = buf.st_mtimespec; + unixSleep(0,500000); /* wait 0.5 sec and try the lock again*/ + continue; + } + + assert( nTries>1 ); + if( conchModTime.tv_sec != buf.st_mtimespec.tv_sec || + conchModTime.tv_nsec != buf.st_mtimespec.tv_nsec ){ + return SQLITE_BUSY; + } + + if( nTries==2 ){ + char tBuf[PROXY_MAXCONCHLEN]; + int len = osPread(conchFile->h, tBuf, PROXY_MAXCONCHLEN, 0); + if( len<0 ){ + storeLastErrno(pFile, errno); + return SQLITE_IOERR_LOCK; + } + if( len>PROXY_PATHINDEX && tBuf[0]==(char)PROXY_CONCHVERSION){ + /* don't break the lock if the host id doesn't match */ + if( 0!=memcmp(&tBuf[PROXY_HEADERLEN], myHostID, PROXY_HOSTIDLEN) ){ + return SQLITE_BUSY; + } + }else{ + /* don't break the lock on short read or a version mismatch */ + return SQLITE_BUSY; + } + unixSleep(0,10000000); /* wait 10 sec and try the lock again */ + continue; + } + + assert( nTries==3 ); + if( 0==proxyBreakConchLock(pFile, myHostID) ){ + rc = SQLITE_OK; + if( lockType==EXCLUSIVE_LOCK ){ + rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, SHARED_LOCK); + } + if( !rc ){ + rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, lockType); + } + } + } + } while( rc==SQLITE_BUSY && nTries<3 ); + + return rc; +} + +/* Takes the conch by taking a shared lock and read the contents conch, if +** lockPath is non-NULL, the host ID and lock file path must match. A NULL +** lockPath means that the lockPath in the conch file will be used if the +** host IDs match, or a new lock path will be generated automatically +** and written to the conch file. +*/ +static int proxyTakeConch(unixFile *pFile){ + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + + if( pCtx->conchHeld!=0 ){ + return SQLITE_OK; + }else{ + unixFile *conchFile = pCtx->conchFile; + uuid_t myHostID; + int pError = 0; + char readBuf[PROXY_MAXCONCHLEN]; + char lockPath[MAXPATHLEN]; + char *tempLockPath = NULL; + int rc = SQLITE_OK; + int createConch = 0; + int hostIdMatch = 0; + int readLen = 0; + int tryOldLockPath = 0; + int forceNewLockPath = 0; + + OSTRACE(("TAKECONCH %d for %s pid=%d\n", conchFile->h, + (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), + osGetpid(0))); + + rc = proxyGetHostID(myHostID, &pError); + if( (rc&0xff)==SQLITE_IOERR ){ + storeLastErrno(pFile, pError); + goto end_takeconch; + } + rc = proxyConchLock(pFile, myHostID, SHARED_LOCK); + if( rc!=SQLITE_OK ){ + goto end_takeconch; + } + /* read the existing conch file */ + readLen = seekAndRead((unixFile*)conchFile, 0, readBuf, PROXY_MAXCONCHLEN); + if( readLen<0 ){ + /* I/O error: lastErrno set by seekAndRead */ + storeLastErrno(pFile, conchFile->lastErrno); + rc = SQLITE_IOERR_READ; + goto end_takeconch; + }else if( readLen<=(PROXY_HEADERLEN+PROXY_HOSTIDLEN) || + readBuf[0]!=(char)PROXY_CONCHVERSION ){ + /* a short read or version format mismatch means we need to create a new + ** conch file. + */ + createConch = 1; + } + /* if the host id matches and the lock path already exists in the conch + ** we'll try to use the path there, if we can't open that path, we'll + ** retry with a new auto-generated path + */ + do { /* in case we need to try again for an :auto: named lock file */ + + if( !createConch && !forceNewLockPath ){ + hostIdMatch = !memcmp(&readBuf[PROXY_HEADERLEN], myHostID, + PROXY_HOSTIDLEN); + /* if the conch has data compare the contents */ + if( !pCtx->lockProxyPath ){ + /* for auto-named local lock file, just check the host ID and we'll + ** use the local lock file path that's already in there + */ + if( hostIdMatch ){ + size_t pathLen = (readLen - PROXY_PATHINDEX); + + if( pathLen>=MAXPATHLEN ){ + pathLen=MAXPATHLEN-1; + } + memcpy(lockPath, &readBuf[PROXY_PATHINDEX], pathLen); + lockPath[pathLen] = 0; + tempLockPath = lockPath; + tryOldLockPath = 1; + /* create a copy of the lock path if the conch is taken */ + goto end_takeconch; + } + }else if( hostIdMatch + && !strncmp(pCtx->lockProxyPath, &readBuf[PROXY_PATHINDEX], + readLen-PROXY_PATHINDEX) + ){ + /* conch host and lock path match */ + goto end_takeconch; + } + } + + /* if the conch isn't writable and doesn't match, we can't take it */ + if( (conchFile->openFlags&O_RDWR) == 0 ){ + rc = SQLITE_BUSY; + goto end_takeconch; + } + + /* either the conch didn't match or we need to create a new one */ + if( !pCtx->lockProxyPath ){ + proxyGetLockPath(pCtx->dbPath, lockPath, MAXPATHLEN); + tempLockPath = lockPath; + /* create a copy of the lock path _only_ if the conch is taken */ + } + + /* update conch with host and path (this will fail if other process + ** has a shared lock already), if the host id matches, use the big + ** stick. + */ + futimes(conchFile->h, NULL); + if( hostIdMatch && !createConch ){ + if( conchFile->pInode && conchFile->pInode->nShared>1 ){ + /* We are trying for an exclusive lock but another thread in this + ** same process is still holding a shared lock. */ + rc = SQLITE_BUSY; + } else { + rc = proxyConchLock(pFile, myHostID, EXCLUSIVE_LOCK); + } + }else{ + rc = proxyConchLock(pFile, myHostID, EXCLUSIVE_LOCK); + } + if( rc==SQLITE_OK ){ + char writeBuffer[PROXY_MAXCONCHLEN]; + int writeSize = 0; + + writeBuffer[0] = (char)PROXY_CONCHVERSION; + memcpy(&writeBuffer[PROXY_HEADERLEN], myHostID, PROXY_HOSTIDLEN); + if( pCtx->lockProxyPath!=NULL ){ + strlcpy(&writeBuffer[PROXY_PATHINDEX], pCtx->lockProxyPath, + MAXPATHLEN); + }else{ + strlcpy(&writeBuffer[PROXY_PATHINDEX], tempLockPath, MAXPATHLEN); + } + writeSize = PROXY_PATHINDEX + strlen(&writeBuffer[PROXY_PATHINDEX]); + robust_ftruncate(conchFile->h, writeSize); + rc = unixWrite((sqlite3_file *)conchFile, writeBuffer, writeSize, 0); + full_fsync(conchFile->h,0,0); + /* If we created a new conch file (not just updated the contents of a + ** valid conch file), try to match the permissions of the database + */ + if( rc==SQLITE_OK && createConch ){ + struct stat buf; + int err = osFstat(pFile->h, &buf); + if( err==0 ){ + mode_t cmode = buf.st_mode&(S_IRUSR|S_IWUSR | S_IRGRP|S_IWGRP | + S_IROTH|S_IWOTH); + /* try to match the database file R/W permissions, ignore failure */ +#ifndef SQLITE_PROXY_DEBUG + osFchmod(conchFile->h, cmode); +#else + do{ + rc = osFchmod(conchFile->h, cmode); + }while( rc==(-1) && errno==EINTR ); + if( rc!=0 ){ + int code = errno; + fprintf(stderr, "fchmod %o FAILED with %d %s\n", + cmode, code, strerror(code)); + } else { + fprintf(stderr, "fchmod %o SUCCEDED\n",cmode); + } + }else{ + int code = errno; + fprintf(stderr, "STAT FAILED[%d] with %d %s\n", + err, code, strerror(code)); +#endif + } + } + } + conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, SHARED_LOCK); + + end_takeconch: + OSTRACE(("TRANSPROXY: CLOSE %d\n", pFile->h)); + if( rc==SQLITE_OK && pFile->openFlags ){ + int fd; + if( pFile->h>=0 ){ + robust_close(pFile, pFile->h, __LINE__); + } + pFile->h = -1; + fd = robust_open(pCtx->dbPath, pFile->openFlags, 0); + OSTRACE(("TRANSPROXY: OPEN %d\n", fd)); + if( fd>=0 ){ + pFile->h = fd; + }else{ + rc=SQLITE_CANTOPEN_BKPT; /* SQLITE_BUSY? proxyTakeConch called + during locking */ + } + } + if( rc==SQLITE_OK && !pCtx->lockProxy ){ + char *path = tempLockPath ? tempLockPath : pCtx->lockProxyPath; + rc = proxyCreateUnixFile(path, &pCtx->lockProxy, 1); + if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM && tryOldLockPath ){ + /* we couldn't create the proxy lock file with the old lock file path + ** so try again via auto-naming + */ + forceNewLockPath = 1; + tryOldLockPath = 0; + continue; /* go back to the do {} while start point, try again */ + } + } + if( rc==SQLITE_OK ){ + /* Need to make a copy of path if we extracted the value + ** from the conch file or the path was allocated on the stack + */ + if( tempLockPath ){ + pCtx->lockProxyPath = sqlite3DbStrDup(0, tempLockPath); + if( !pCtx->lockProxyPath ){ + rc = SQLITE_NOMEM_BKPT; + } + } + } + if( rc==SQLITE_OK ){ + pCtx->conchHeld = 1; + + if( pCtx->lockProxy->pMethod == &afpIoMethods ){ + afpLockingContext *afpCtx; + afpCtx = (afpLockingContext *)pCtx->lockProxy->lockingContext; + afpCtx->dbPath = pCtx->lockProxyPath; + } + } else { + conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK); + } + OSTRACE(("TAKECONCH %d %s\n", conchFile->h, + rc==SQLITE_OK?"ok":"failed")); + return rc; + } while (1); /* in case we need to retry the :auto: lock file - + ** we should never get here except via the 'continue' call. */ + } +} + +/* +** If pFile holds a lock on a conch file, then release that lock. +*/ +static int proxyReleaseConch(unixFile *pFile){ + int rc = SQLITE_OK; /* Subroutine return code */ + proxyLockingContext *pCtx; /* The locking context for the proxy lock */ + unixFile *conchFile; /* Name of the conch file */ + + pCtx = (proxyLockingContext *)pFile->lockingContext; + conchFile = pCtx->conchFile; + OSTRACE(("RELEASECONCH %d for %s pid=%d\n", conchFile->h, + (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), + osGetpid(0))); + if( pCtx->conchHeld>0 ){ + rc = conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK); + } + pCtx->conchHeld = 0; + OSTRACE(("RELEASECONCH %d %s\n", conchFile->h, + (rc==SQLITE_OK ? "ok" : "failed"))); + return rc; +} + +/* +** Given the name of a database file, compute the name of its conch file. +** Store the conch filename in memory obtained from sqlite3_malloc64(). +** Make *pConchPath point to the new name. Return SQLITE_OK on success +** or SQLITE_NOMEM if unable to obtain memory. +** +** The caller is responsible for ensuring that the allocated memory +** space is eventually freed. +** +** *pConchPath is set to NULL if a memory allocation error occurs. +*/ +static int proxyCreateConchPathname(char *dbPath, char **pConchPath){ + int i; /* Loop counter */ + int len = (int)strlen(dbPath); /* Length of database filename - dbPath */ + char *conchPath; /* buffer in which to construct conch name */ + + /* Allocate space for the conch filename and initialize the name to + ** the name of the original database file. */ + *pConchPath = conchPath = (char *)sqlite3_malloc64(len + 8); + if( conchPath==0 ){ + return SQLITE_NOMEM_BKPT; + } + memcpy(conchPath, dbPath, len+1); + + /* now insert a "." before the last / character */ + for( i=(len-1); i>=0; i-- ){ + if( conchPath[i]=='/' ){ + i++; + break; + } + } + conchPath[i]='.'; + while ( i<len ){ + conchPath[i+1]=dbPath[i]; + i++; + } + + /* append the "-conch" suffix to the file */ + memcpy(&conchPath[i+1], "-conch", 7); + assert( (int)strlen(conchPath) == len+7 ); + + return SQLITE_OK; +} + + +/* Takes a fully configured proxy locking-style unix file and switches +** the local lock file path +*/ +static int switchLockProxyPath(unixFile *pFile, const char *path) { + proxyLockingContext *pCtx = (proxyLockingContext*)pFile->lockingContext; + char *oldPath = pCtx->lockProxyPath; + int rc = SQLITE_OK; + + if( pFile->eFileLock!=NO_LOCK ){ + return SQLITE_BUSY; + } + + /* nothing to do if the path is NULL, :auto: or matches the existing path */ + if( !path || path[0]=='\0' || !strcmp(path, ":auto:") || + (oldPath && !strncmp(oldPath, path, MAXPATHLEN)) ){ + return SQLITE_OK; + }else{ + unixFile *lockProxy = pCtx->lockProxy; + pCtx->lockProxy=NULL; + pCtx->conchHeld = 0; + if( lockProxy!=NULL ){ + rc=lockProxy->pMethod->xClose((sqlite3_file *)lockProxy); + if( rc ) return rc; + sqlite3_free(lockProxy); + } + sqlite3_free(oldPath); + pCtx->lockProxyPath = sqlite3DbStrDup(0, path); + } + + return rc; +} + +/* +** pFile is a file that has been opened by a prior xOpen call. dbPath +** is a string buffer at least MAXPATHLEN+1 characters in size. +** +** This routine find the filename associated with pFile and writes it +** int dbPath. +*/ +static int proxyGetDbPathForUnixFile(unixFile *pFile, char *dbPath){ +#if defined(__APPLE__) + if( pFile->pMethod == &afpIoMethods ){ + /* afp style keeps a reference to the db path in the filePath field + ** of the struct */ + assert( (int)strlen((char*)pFile->lockingContext)<=MAXPATHLEN ); + strlcpy(dbPath, ((afpLockingContext *)pFile->lockingContext)->dbPath, + MAXPATHLEN); + } else +#endif + if( pFile->pMethod == &dotlockIoMethods ){ + /* dot lock style uses the locking context to store the dot lock + ** file path */ + int len = strlen((char *)pFile->lockingContext) - strlen(DOTLOCK_SUFFIX); + memcpy(dbPath, (char *)pFile->lockingContext, len + 1); + }else{ + /* all other styles use the locking context to store the db file path */ + assert( strlen((char*)pFile->lockingContext)<=MAXPATHLEN ); + strlcpy(dbPath, (char *)pFile->lockingContext, MAXPATHLEN); + } + return SQLITE_OK; +} + +/* +** Takes an already filled in unix file and alters it so all file locking +** will be performed on the local proxy lock file. The following fields +** are preserved in the locking context so that they can be restored and +** the unix structure properly cleaned up at close time: +** ->lockingContext +** ->pMethod +*/ +static int proxyTransformUnixFile(unixFile *pFile, const char *path) { + proxyLockingContext *pCtx; + char dbPath[MAXPATHLEN+1]; /* Name of the database file */ + char *lockPath=NULL; + int rc = SQLITE_OK; + + if( pFile->eFileLock!=NO_LOCK ){ + return SQLITE_BUSY; + } + proxyGetDbPathForUnixFile(pFile, dbPath); + if( !path || path[0]=='\0' || !strcmp(path, ":auto:") ){ + lockPath=NULL; + }else{ + lockPath=(char *)path; + } + + OSTRACE(("TRANSPROXY %d for %s pid=%d\n", pFile->h, + (lockPath ? lockPath : ":auto:"), osGetpid(0))); + + pCtx = sqlite3_malloc64( sizeof(*pCtx) ); + if( pCtx==0 ){ + return SQLITE_NOMEM_BKPT; + } + memset(pCtx, 0, sizeof(*pCtx)); + + rc = proxyCreateConchPathname(dbPath, &pCtx->conchFilePath); + if( rc==SQLITE_OK ){ + rc = proxyCreateUnixFile(pCtx->conchFilePath, &pCtx->conchFile, 0); + if( rc==SQLITE_CANTOPEN && ((pFile->openFlags&O_RDWR) == 0) ){ + /* if (a) the open flags are not O_RDWR, (b) the conch isn't there, and + ** (c) the file system is read-only, then enable no-locking access. + ** Ugh, since O_RDONLY==0x0000 we test for !O_RDWR since unixOpen asserts + ** that openFlags will have only one of O_RDONLY or O_RDWR. + */ + struct statfs fsInfo; + struct stat conchInfo; + int goLockless = 0; + + if( osStat(pCtx->conchFilePath, &conchInfo) == -1 ) { + int err = errno; + if( (err==ENOENT) && (statfs(dbPath, &fsInfo) != -1) ){ + goLockless = (fsInfo.f_flags&MNT_RDONLY) == MNT_RDONLY; + } + } + if( goLockless ){ + pCtx->conchHeld = -1; /* read only FS/ lockless */ + rc = SQLITE_OK; + } + } + } + if( rc==SQLITE_OK && lockPath ){ + pCtx->lockProxyPath = sqlite3DbStrDup(0, lockPath); + } + + if( rc==SQLITE_OK ){ + pCtx->dbPath = sqlite3DbStrDup(0, dbPath); + if( pCtx->dbPath==NULL ){ + rc = SQLITE_NOMEM_BKPT; + } + } + if( rc==SQLITE_OK ){ + /* all memory is allocated, proxys are created and assigned, + ** switch the locking context and pMethod then return. + */ + pCtx->oldLockingContext = pFile->lockingContext; + pFile->lockingContext = pCtx; + pCtx->pOldMethod = pFile->pMethod; + pFile->pMethod = &proxyIoMethods; + }else{ + if( pCtx->conchFile ){ + pCtx->conchFile->pMethod->xClose((sqlite3_file *)pCtx->conchFile); + sqlite3_free(pCtx->conchFile); + } + sqlite3DbFree(0, pCtx->lockProxyPath); + sqlite3_free(pCtx->conchFilePath); + sqlite3_free(pCtx); + } + OSTRACE(("TRANSPROXY %d %s\n", pFile->h, + (rc==SQLITE_OK ? "ok" : "failed"))); + return rc; +} + + +/* +** This routine handles sqlite3_file_control() calls that are specific +** to proxy locking. +*/ +static int proxyFileControl(sqlite3_file *id, int op, void *pArg){ + switch( op ){ + case SQLITE_FCNTL_GET_LOCKPROXYFILE: { + unixFile *pFile = (unixFile*)id; + if( pFile->pMethod == &proxyIoMethods ){ + proxyLockingContext *pCtx = (proxyLockingContext*)pFile->lockingContext; + proxyTakeConch(pFile); + if( pCtx->lockProxyPath ){ + *(const char **)pArg = pCtx->lockProxyPath; + }else{ + *(const char **)pArg = ":auto: (not held)"; + } + } else { + *(const char **)pArg = NULL; + } + return SQLITE_OK; + } + case SQLITE_FCNTL_SET_LOCKPROXYFILE: { + unixFile *pFile = (unixFile*)id; + int rc = SQLITE_OK; + int isProxyStyle = (pFile->pMethod == &proxyIoMethods); + if( pArg==NULL || (const char *)pArg==0 ){ + if( isProxyStyle ){ + /* turn off proxy locking - not supported. If support is added for + ** switching proxy locking mode off then it will need to fail if + ** the journal mode is WAL mode. + */ + rc = SQLITE_ERROR /*SQLITE_PROTOCOL? SQLITE_MISUSE?*/; + }else{ + /* turn off proxy locking - already off - NOOP */ + rc = SQLITE_OK; + } + }else{ + const char *proxyPath = (const char *)pArg; + if( isProxyStyle ){ + proxyLockingContext *pCtx = + (proxyLockingContext*)pFile->lockingContext; + if( !strcmp(pArg, ":auto:") + || (pCtx->lockProxyPath && + !strncmp(pCtx->lockProxyPath, proxyPath, MAXPATHLEN)) + ){ + rc = SQLITE_OK; + }else{ + rc = switchLockProxyPath(pFile, proxyPath); + } + }else{ + /* turn on proxy file locking */ + rc = proxyTransformUnixFile(pFile, proxyPath); + } + } + return rc; + } + default: { + assert( 0 ); /* The call assures that only valid opcodes are sent */ + } + } + /*NOTREACHED*/ assert(0); + return SQLITE_ERROR; +} + +/* +** Within this division (the proxying locking implementation) the procedures +** above this point are all utilities. The lock-related methods of the +** proxy-locking sqlite3_io_method object follow. +*/ + + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +*/ +static int proxyCheckReservedLock(sqlite3_file *id, int *pResOut) { + unixFile *pFile = (unixFile*)id; + int rc = proxyTakeConch(pFile); + if( rc==SQLITE_OK ){ + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + if( pCtx->conchHeld>0 ){ + unixFile *proxy = pCtx->lockProxy; + return proxy->pMethod->xCheckReservedLock((sqlite3_file*)proxy, pResOut); + }else{ /* conchHeld < 0 is lockless */ + pResOut=0; + } + } + return rc; +} + +/* +** Lock the file with the lock specified by parameter eFileLock - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ +static int proxyLock(sqlite3_file *id, int eFileLock) { + unixFile *pFile = (unixFile*)id; + int rc = proxyTakeConch(pFile); + if( rc==SQLITE_OK ){ + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + if( pCtx->conchHeld>0 ){ + unixFile *proxy = pCtx->lockProxy; + rc = proxy->pMethod->xLock((sqlite3_file*)proxy, eFileLock); + pFile->eFileLock = proxy->eFileLock; + }else{ + /* conchHeld < 0 is lockless */ + } + } + return rc; +} + + +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +*/ +static int proxyUnlock(sqlite3_file *id, int eFileLock) { + unixFile *pFile = (unixFile*)id; + int rc = proxyTakeConch(pFile); + if( rc==SQLITE_OK ){ + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + if( pCtx->conchHeld>0 ){ + unixFile *proxy = pCtx->lockProxy; + rc = proxy->pMethod->xUnlock((sqlite3_file*)proxy, eFileLock); + pFile->eFileLock = proxy->eFileLock; + }else{ + /* conchHeld < 0 is lockless */ + } + } + return rc; +} + +/* +** Close a file that uses proxy locks. +*/ +static int proxyClose(sqlite3_file *id) { + if( ALWAYS(id) ){ + unixFile *pFile = (unixFile*)id; + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + unixFile *lockProxy = pCtx->lockProxy; + unixFile *conchFile = pCtx->conchFile; + int rc = SQLITE_OK; + + if( lockProxy ){ + rc = lockProxy->pMethod->xUnlock((sqlite3_file*)lockProxy, NO_LOCK); + if( rc ) return rc; + rc = lockProxy->pMethod->xClose((sqlite3_file*)lockProxy); + if( rc ) return rc; + sqlite3_free(lockProxy); + pCtx->lockProxy = 0; + } + if( conchFile ){ + if( pCtx->conchHeld ){ + rc = proxyReleaseConch(pFile); + if( rc ) return rc; + } + rc = conchFile->pMethod->xClose((sqlite3_file*)conchFile); + if( rc ) return rc; + sqlite3_free(conchFile); + } + sqlite3DbFree(0, pCtx->lockProxyPath); + sqlite3_free(pCtx->conchFilePath); + sqlite3DbFree(0, pCtx->dbPath); + /* restore the original locking context and pMethod then close it */ + pFile->lockingContext = pCtx->oldLockingContext; + pFile->pMethod = pCtx->pOldMethod; + sqlite3_free(pCtx); + return pFile->pMethod->xClose(id); + } + return SQLITE_OK; +} + + + +#endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ +/* +** The proxy locking style is intended for use with AFP filesystems. +** And since AFP is only supported on MacOSX, the proxy locking is also +** restricted to MacOSX. +** +** +******************* End of the proxy lock implementation ********************** +******************************************************************************/ + +/* +** Initialize the operating system interface. +** +** This routine registers all VFS implementations for unix-like operating +** systems. This routine, and the sqlite3_os_end() routine that follows, +** should be the only routines in this file that are visible from other +** files. +** +** This routine is called once during SQLite initialization and by a +** single thread. The memory allocation and mutex subsystems have not +** necessarily been initialized when this routine is called, and so they +** should not be used. +*/ +SQLITE_API int sqlite3_os_init(void){ + /* + ** The following macro defines an initializer for an sqlite3_vfs object. + ** The name of the VFS is NAME. The pAppData is a pointer to a pointer + ** to the "finder" function. (pAppData is a pointer to a pointer because + ** silly C90 rules prohibit a void* from being cast to a function pointer + ** and so we have to go through the intermediate pointer to avoid problems + ** when compiling with -pedantic-errors on GCC.) + ** + ** The FINDER parameter to this macro is the name of the pointer to the + ** finder-function. The finder-function returns a pointer to the + ** sqlite_io_methods object that implements the desired locking + ** behaviors. See the division above that contains the IOMETHODS + ** macro for addition information on finder-functions. + ** + ** Most finders simply return a pointer to a fixed sqlite3_io_methods + ** object. But the "autolockIoFinder" available on MacOSX does a little + ** more than that; it looks at the filesystem type that hosts the + ** database file and tries to choose an locking method appropriate for + ** that filesystem time. + */ + #define UNIXVFS(VFSNAME, FINDER) { \ + 3, /* iVersion */ \ + sizeof(unixFile), /* szOsFile */ \ + MAX_PATHNAME, /* mxPathname */ \ + 0, /* pNext */ \ + VFSNAME, /* zName */ \ + (void*)&FINDER, /* pAppData */ \ + unixOpen, /* xOpen */ \ + unixDelete, /* xDelete */ \ + unixAccess, /* xAccess */ \ + unixFullPathname, /* xFullPathname */ \ + unixDlOpen, /* xDlOpen */ \ + unixDlError, /* xDlError */ \ + unixDlSym, /* xDlSym */ \ + unixDlClose, /* xDlClose */ \ + unixRandomness, /* xRandomness */ \ + unixSleep, /* xSleep */ \ + unixCurrentTime, /* xCurrentTime */ \ + unixGetLastError, /* xGetLastError */ \ + unixCurrentTimeInt64, /* xCurrentTimeInt64 */ \ + unixSetSystemCall, /* xSetSystemCall */ \ + unixGetSystemCall, /* xGetSystemCall */ \ + unixNextSystemCall, /* xNextSystemCall */ \ + } + + /* + ** All default VFSes for unix are contained in the following array. + ** + ** Note that the sqlite3_vfs.pNext field of the VFS object is modified + ** by the SQLite core when the VFS is registered. So the following + ** array cannot be const. + */ + static sqlite3_vfs aVfs[] = { +#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) + UNIXVFS("unix", autolockIoFinder ), +#elif OS_VXWORKS + UNIXVFS("unix", vxworksIoFinder ), +#else + UNIXVFS("unix", posixIoFinder ), +#endif + UNIXVFS("unix-none", nolockIoFinder ), + UNIXVFS("unix-dotfile", dotlockIoFinder ), + UNIXVFS("unix-excl", posixIoFinder ), +#if OS_VXWORKS + UNIXVFS("unix-namedsem", semIoFinder ), +#endif +#if SQLITE_ENABLE_LOCKING_STYLE || OS_VXWORKS + UNIXVFS("unix-posix", posixIoFinder ), +#endif +#if SQLITE_ENABLE_LOCKING_STYLE + UNIXVFS("unix-flock", flockIoFinder ), +#endif +#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) + UNIXVFS("unix-afp", afpIoFinder ), + UNIXVFS("unix-nfs", nfsIoFinder ), + UNIXVFS("unix-proxy", proxyIoFinder ), +#endif + }; + unsigned int i; /* Loop counter */ + + /* Double-check that the aSyscall[] array has been constructed + ** correctly. See ticket [bb3a86e890c8e96ab] */ + assert( ArraySize(aSyscall)==29 ); + + /* Register all VFSes defined in the aVfs[] array */ + for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){ +#ifdef SQLITE_DEFAULT_UNIX_VFS + sqlite3_vfs_register(&aVfs[i], + 0==strcmp(aVfs[i].zName,SQLITE_DEFAULT_UNIX_VFS)); +#else + sqlite3_vfs_register(&aVfs[i], i==0); +#endif + } +#ifdef SQLITE_OS_KV_OPTIONAL + sqlite3KvvfsInit(); +#endif + unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); + +#ifndef SQLITE_OMIT_WAL + /* Validate lock assumptions */ + assert( SQLITE_SHM_NLOCK==8 ); /* Number of available locks */ + assert( UNIX_SHM_BASE==120 ); /* Start of locking area */ + /* Locks: + ** WRITE UNIX_SHM_BASE 120 + ** CKPT UNIX_SHM_BASE+1 121 + ** RECOVER UNIX_SHM_BASE+2 122 + ** READ-0 UNIX_SHM_BASE+3 123 + ** READ-1 UNIX_SHM_BASE+4 124 + ** READ-2 UNIX_SHM_BASE+5 125 + ** READ-3 UNIX_SHM_BASE+6 126 + ** READ-4 UNIX_SHM_BASE+7 127 + ** DMS UNIX_SHM_BASE+8 128 + */ + assert( UNIX_SHM_DMS==128 ); /* Byte offset of the deadman-switch */ +#endif + + /* Initialize temp file dir array. */ + unixTempFileInit(); + + return SQLITE_OK; +} + +/* +** Shutdown the operating system interface. +** +** Some operating systems might need to do some cleanup in this routine, +** to release dynamically allocated objects. But not on unix. +** This routine is a no-op for unix. +*/ +SQLITE_API int sqlite3_os_end(void){ + unixBigLock = 0; + return SQLITE_OK; +} + +#endif /* SQLITE_OS_UNIX */ + +/************** End of os_unix.c *********************************************/ +/************** Begin file os_win.c ******************************************/ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to Windows. +*/ +/* #include "sqliteInt.h" */ +#if SQLITE_OS_WIN /* This file is used for Windows only */ + +/* +** Include code that is common to all os_*.c files +*/ +/* #include "os_common.h" */ + +/* +** Include the header file for the Windows VFS. +*/ +/* #include "os_win.h" */ + +/* +** Compiling and using WAL mode requires several APIs that are only +** available in Windows platforms based on the NT kernel. +*/ +#if !SQLITE_OS_WINNT && !defined(SQLITE_OMIT_WAL) +# error "WAL mode requires support from the Windows NT kernel, compile\ + with SQLITE_OMIT_WAL." +#endif + +#if !SQLITE_OS_WINNT && SQLITE_MAX_MMAP_SIZE>0 +# error "Memory mapped files require support from the Windows NT kernel,\ + compile with SQLITE_MAX_MMAP_SIZE=0." +#endif + +/* +** Are most of the Win32 ANSI APIs available (i.e. with certain exceptions +** based on the sub-platform)? +*/ +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(SQLITE_WIN32_NO_ANSI) +# define SQLITE_WIN32_HAS_ANSI +#endif + +/* +** Are most of the Win32 Unicode APIs available (i.e. with certain exceptions +** based on the sub-platform)? +*/ +#if (SQLITE_OS_WINCE || SQLITE_OS_WINNT || SQLITE_OS_WINRT) && \ + !defined(SQLITE_WIN32_NO_WIDE) +# define SQLITE_WIN32_HAS_WIDE +#endif + +/* +** Make sure at least one set of Win32 APIs is available. +*/ +#if !defined(SQLITE_WIN32_HAS_ANSI) && !defined(SQLITE_WIN32_HAS_WIDE) +# error "At least one of SQLITE_WIN32_HAS_ANSI and SQLITE_WIN32_HAS_WIDE\ + must be defined." +#endif + +/* +** Define the required Windows SDK version constants if they are not +** already available. +*/ +#ifndef NTDDI_WIN8 +# define NTDDI_WIN8 0x06020000 +#endif + +#ifndef NTDDI_WINBLUE +# define NTDDI_WINBLUE 0x06030000 +#endif + +#ifndef NTDDI_WINTHRESHOLD +# define NTDDI_WINTHRESHOLD 0x06040000 +#endif + +/* +** Check to see if the GetVersionEx[AW] functions are deprecated on the +** target system. GetVersionEx was first deprecated in Win8.1. +*/ +#ifndef SQLITE_WIN32_GETVERSIONEX +# if defined(NTDDI_VERSION) && NTDDI_VERSION >= NTDDI_WINBLUE +# define SQLITE_WIN32_GETVERSIONEX 0 /* GetVersionEx() is deprecated */ +# else +# define SQLITE_WIN32_GETVERSIONEX 1 /* GetVersionEx() is current */ +# endif +#endif + +/* +** Check to see if the CreateFileMappingA function is supported on the +** target system. It is unavailable when using "mincore.lib" on Win10. +** When compiling for Windows 10, always assume "mincore.lib" is in use. +*/ +#ifndef SQLITE_WIN32_CREATEFILEMAPPINGA +# if defined(NTDDI_VERSION) && NTDDI_VERSION >= NTDDI_WINTHRESHOLD +# define SQLITE_WIN32_CREATEFILEMAPPINGA 0 +# else +# define SQLITE_WIN32_CREATEFILEMAPPINGA 1 +# endif +#endif + +/* +** This constant should already be defined (in the "WinDef.h" SDK file). +*/ +#ifndef MAX_PATH +# define MAX_PATH (260) +#endif + +/* +** Maximum pathname length (in chars) for Win32. This should normally be +** MAX_PATH. +*/ +#ifndef SQLITE_WIN32_MAX_PATH_CHARS +# define SQLITE_WIN32_MAX_PATH_CHARS (MAX_PATH) +#endif + +/* +** This constant should already be defined (in the "WinNT.h" SDK file). +*/ +#ifndef UNICODE_STRING_MAX_CHARS +# define UNICODE_STRING_MAX_CHARS (32767) +#endif + +/* +** Maximum pathname length (in chars) for WinNT. This should normally be +** UNICODE_STRING_MAX_CHARS. +*/ +#ifndef SQLITE_WINNT_MAX_PATH_CHARS +# define SQLITE_WINNT_MAX_PATH_CHARS (UNICODE_STRING_MAX_CHARS) +#endif + +/* +** Maximum pathname length (in bytes) for Win32. The MAX_PATH macro is in +** characters, so we allocate 4 bytes per character assuming worst-case of +** 4-bytes-per-character for UTF8. +*/ +#ifndef SQLITE_WIN32_MAX_PATH_BYTES +# define SQLITE_WIN32_MAX_PATH_BYTES (SQLITE_WIN32_MAX_PATH_CHARS*4) +#endif + +/* +** Maximum pathname length (in bytes) for WinNT. This should normally be +** UNICODE_STRING_MAX_CHARS * sizeof(WCHAR). +*/ +#ifndef SQLITE_WINNT_MAX_PATH_BYTES +# define SQLITE_WINNT_MAX_PATH_BYTES \ + (sizeof(WCHAR) * SQLITE_WINNT_MAX_PATH_CHARS) +#endif + +/* +** Maximum error message length (in chars) for WinRT. +*/ +#ifndef SQLITE_WIN32_MAX_ERRMSG_CHARS +# define SQLITE_WIN32_MAX_ERRMSG_CHARS (1024) +#endif + +/* +** Returns non-zero if the character should be treated as a directory +** separator. +*/ +#ifndef winIsDirSep +# define winIsDirSep(a) (((a) == '/') || ((a) == '\\')) +#endif + +/* +** This macro is used when a local variable is set to a value that is +** [sometimes] not used by the code (e.g. via conditional compilation). +*/ +#ifndef UNUSED_VARIABLE_VALUE +# define UNUSED_VARIABLE_VALUE(x) (void)(x) +#endif + +/* +** Returns the character that should be used as the directory separator. +*/ +#ifndef winGetDirSep +# define winGetDirSep() '\\' +#endif + +/* +** Do we need to manually define the Win32 file mapping APIs for use with WAL +** mode or memory mapped files (e.g. these APIs are available in the Windows +** CE SDK; however, they are not present in the header file)? +*/ +#if SQLITE_WIN32_FILEMAPPING_API && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) +/* +** Two of the file mapping APIs are different under WinRT. Figure out which +** set we need. +*/ +#if SQLITE_OS_WINRT +WINBASEAPI HANDLE WINAPI CreateFileMappingFromApp(HANDLE, \ + LPSECURITY_ATTRIBUTES, ULONG, ULONG64, LPCWSTR); + +WINBASEAPI LPVOID WINAPI MapViewOfFileFromApp(HANDLE, ULONG, ULONG64, SIZE_T); +#else +#if defined(SQLITE_WIN32_HAS_ANSI) +WINBASEAPI HANDLE WINAPI CreateFileMappingA(HANDLE, LPSECURITY_ATTRIBUTES, \ + DWORD, DWORD, DWORD, LPCSTR); +#endif /* defined(SQLITE_WIN32_HAS_ANSI) */ + +#if defined(SQLITE_WIN32_HAS_WIDE) +WINBASEAPI HANDLE WINAPI CreateFileMappingW(HANDLE, LPSECURITY_ATTRIBUTES, \ + DWORD, DWORD, DWORD, LPCWSTR); +#endif /* defined(SQLITE_WIN32_HAS_WIDE) */ + +WINBASEAPI LPVOID WINAPI MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, SIZE_T); +#endif /* SQLITE_OS_WINRT */ + +/* +** These file mapping APIs are common to both Win32 and WinRT. +*/ + +WINBASEAPI BOOL WINAPI FlushViewOfFile(LPCVOID, SIZE_T); +WINBASEAPI BOOL WINAPI UnmapViewOfFile(LPCVOID); +#endif /* SQLITE_WIN32_FILEMAPPING_API */ + +/* +** Some Microsoft compilers lack this definition. +*/ +#ifndef INVALID_FILE_ATTRIBUTES +# define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif + +#ifndef FILE_FLAG_MASK +# define FILE_FLAG_MASK (0xFF3C0000) +#endif + +#ifndef FILE_ATTRIBUTE_MASK +# define FILE_ATTRIBUTE_MASK (0x0003FFF7) +#endif + +#ifndef SQLITE_OMIT_WAL +/* Forward references to structures used for WAL */ +typedef struct winShm winShm; /* A connection to shared-memory */ +typedef struct winShmNode winShmNode; /* A region of shared-memory */ +#endif + +/* +** WinCE lacks native support for file locking so we have to fake it +** with some code of our own. +*/ +#if SQLITE_OS_WINCE +typedef struct winceLock { + int nReaders; /* Number of reader locks obtained */ + BOOL bPending; /* Indicates a pending lock has been obtained */ + BOOL bReserved; /* Indicates a reserved lock has been obtained */ + BOOL bExclusive; /* Indicates an exclusive lock has been obtained */ +} winceLock; +#endif + +/* +** The winFile structure is a subclass of sqlite3_file* specific to the win32 +** portability layer. +*/ +typedef struct winFile winFile; +struct winFile { + const sqlite3_io_methods *pMethod; /*** Must be first ***/ + sqlite3_vfs *pVfs; /* The VFS used to open this file */ + HANDLE h; /* Handle for accessing the file */ + u8 locktype; /* Type of lock currently held on this file */ + short sharedLockByte; /* Randomly chosen byte used as a shared lock */ + u8 ctrlFlags; /* Flags. See WINFILE_* below */ + DWORD lastErrno; /* The Windows errno from the last I/O error */ +#ifndef SQLITE_OMIT_WAL + winShm *pShm; /* Instance of shared memory on this file */ +#endif + const char *zPath; /* Full pathname of this file */ + int szChunk; /* Chunk size configured by FCNTL_CHUNK_SIZE */ +#if SQLITE_OS_WINCE + LPWSTR zDeleteOnClose; /* Name of file to delete when closing */ + HANDLE hMutex; /* Mutex used to control access to shared lock */ + HANDLE hShared; /* Shared memory segment used for locking */ + winceLock local; /* Locks obtained by this instance of winFile */ + winceLock *shared; /* Global shared lock memory for the file */ +#endif +#if SQLITE_MAX_MMAP_SIZE>0 + int nFetchOut; /* Number of outstanding xFetch references */ + HANDLE hMap; /* Handle for accessing memory mapping */ + void *pMapRegion; /* Area memory mapped */ + sqlite3_int64 mmapSize; /* Size of mapped region */ + sqlite3_int64 mmapSizeMax; /* Configured FCNTL_MMAP_SIZE value */ +#endif +}; + +/* +** The winVfsAppData structure is used for the pAppData member for all of the +** Win32 VFS variants. +*/ +typedef struct winVfsAppData winVfsAppData; +struct winVfsAppData { + const sqlite3_io_methods *pMethod; /* The file I/O methods to use. */ + void *pAppData; /* The extra pAppData, if any. */ + BOOL bNoLock; /* Non-zero if locking is disabled. */ +}; + +/* +** Allowed values for winFile.ctrlFlags +*/ +#define WINFILE_RDONLY 0x02 /* Connection is read only */ +#define WINFILE_PERSIST_WAL 0x04 /* Persistent WAL mode */ +#define WINFILE_PSOW 0x10 /* SQLITE_IOCAP_POWERSAFE_OVERWRITE */ + +/* + * The size of the buffer used by sqlite3_win32_write_debug(). + */ +#ifndef SQLITE_WIN32_DBG_BUF_SIZE +# define SQLITE_WIN32_DBG_BUF_SIZE ((int)(4096-sizeof(DWORD))) +#endif + +/* + * If compiled with SQLITE_WIN32_MALLOC on Windows, we will use the + * various Win32 API heap functions instead of our own. + */ +#ifdef SQLITE_WIN32_MALLOC + +/* + * If this is non-zero, an isolated heap will be created by the native Win32 + * allocator subsystem; otherwise, the default process heap will be used. This + * setting has no effect when compiling for WinRT. By default, this is enabled + * and an isolated heap will be created to store all allocated data. + * + ****************************************************************************** + * WARNING: It is important to note that when this setting is non-zero and the + * winMemShutdown function is called (e.g. by the sqlite3_shutdown + * function), all data that was allocated using the isolated heap will + * be freed immediately and any attempt to access any of that freed + * data will almost certainly result in an immediate access violation. + ****************************************************************************** + */ +#ifndef SQLITE_WIN32_HEAP_CREATE +# define SQLITE_WIN32_HEAP_CREATE (TRUE) +#endif + +/* + * This is the maximum possible initial size of the Win32-specific heap, in + * bytes. + */ +#ifndef SQLITE_WIN32_HEAP_MAX_INIT_SIZE +# define SQLITE_WIN32_HEAP_MAX_INIT_SIZE (4294967295U) +#endif + +/* + * This is the extra space for the initial size of the Win32-specific heap, + * in bytes. This value may be zero. + */ +#ifndef SQLITE_WIN32_HEAP_INIT_EXTRA +# define SQLITE_WIN32_HEAP_INIT_EXTRA (4194304) +#endif + +/* + * Calculate the maximum legal cache size, in pages, based on the maximum + * possible initial heap size and the default page size, setting aside the + * needed extra space. + */ +#ifndef SQLITE_WIN32_MAX_CACHE_SIZE +# define SQLITE_WIN32_MAX_CACHE_SIZE (((SQLITE_WIN32_HEAP_MAX_INIT_SIZE) - \ + (SQLITE_WIN32_HEAP_INIT_EXTRA)) / \ + (SQLITE_DEFAULT_PAGE_SIZE)) +#endif + +/* + * This is cache size used in the calculation of the initial size of the + * Win32-specific heap. It cannot be negative. + */ +#ifndef SQLITE_WIN32_CACHE_SIZE +# if SQLITE_DEFAULT_CACHE_SIZE>=0 +# define SQLITE_WIN32_CACHE_SIZE (SQLITE_DEFAULT_CACHE_SIZE) +# else +# define SQLITE_WIN32_CACHE_SIZE (-(SQLITE_DEFAULT_CACHE_SIZE)) +# endif +#endif + +/* + * Make sure that the calculated cache size, in pages, cannot cause the + * initial size of the Win32-specific heap to exceed the maximum amount + * of memory that can be specified in the call to HeapCreate. + */ +#if SQLITE_WIN32_CACHE_SIZE>SQLITE_WIN32_MAX_CACHE_SIZE +# undef SQLITE_WIN32_CACHE_SIZE +# define SQLITE_WIN32_CACHE_SIZE (2000) +#endif + +/* + * The initial size of the Win32-specific heap. This value may be zero. + */ +#ifndef SQLITE_WIN32_HEAP_INIT_SIZE +# define SQLITE_WIN32_HEAP_INIT_SIZE ((SQLITE_WIN32_CACHE_SIZE) * \ + (SQLITE_DEFAULT_PAGE_SIZE) + \ + (SQLITE_WIN32_HEAP_INIT_EXTRA)) +#endif + +/* + * The maximum size of the Win32-specific heap. This value may be zero. + */ +#ifndef SQLITE_WIN32_HEAP_MAX_SIZE +# define SQLITE_WIN32_HEAP_MAX_SIZE (0) +#endif + +/* + * The extra flags to use in calls to the Win32 heap APIs. This value may be + * zero for the default behavior. + */ +#ifndef SQLITE_WIN32_HEAP_FLAGS +# define SQLITE_WIN32_HEAP_FLAGS (0) +#endif + + +/* +** The winMemData structure stores information required by the Win32-specific +** sqlite3_mem_methods implementation. +*/ +typedef struct winMemData winMemData; +struct winMemData { +#ifndef NDEBUG + u32 magic1; /* Magic number to detect structure corruption. */ +#endif + HANDLE hHeap; /* The handle to our heap. */ + BOOL bOwned; /* Do we own the heap (i.e. destroy it on shutdown)? */ +#ifndef NDEBUG + u32 magic2; /* Magic number to detect structure corruption. */ +#endif +}; + +#ifndef NDEBUG +#define WINMEM_MAGIC1 0x42b2830b +#define WINMEM_MAGIC2 0xbd4d7cf4 +#endif + +static struct winMemData win_mem_data = { +#ifndef NDEBUG + WINMEM_MAGIC1, +#endif + NULL, FALSE +#ifndef NDEBUG + ,WINMEM_MAGIC2 +#endif +}; + +#ifndef NDEBUG +#define winMemAssertMagic1() assert( win_mem_data.magic1==WINMEM_MAGIC1 ) +#define winMemAssertMagic2() assert( win_mem_data.magic2==WINMEM_MAGIC2 ) +#define winMemAssertMagic() winMemAssertMagic1(); winMemAssertMagic2(); +#else +#define winMemAssertMagic() +#endif + +#define winMemGetDataPtr() &win_mem_data +#define winMemGetHeap() win_mem_data.hHeap +#define winMemGetOwned() win_mem_data.bOwned + +static void *winMemMalloc(int nBytes); +static void winMemFree(void *pPrior); +static void *winMemRealloc(void *pPrior, int nBytes); +static int winMemSize(void *p); +static int winMemRoundup(int n); +static int winMemInit(void *pAppData); +static void winMemShutdown(void *pAppData); + +SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetWin32(void); +#endif /* SQLITE_WIN32_MALLOC */ + +/* +** The following variable is (normally) set once and never changes +** thereafter. It records whether the operating system is Win9x +** or WinNT. +** +** 0: Operating system unknown. +** 1: Operating system is Win9x. +** 2: Operating system is WinNT. +** +** In order to facilitate testing on a WinNT system, the test fixture +** can manually set this value to 1 to emulate Win98 behavior. +*/ +#ifdef SQLITE_TEST +SQLITE_API LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0; +#else +static LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0; +#endif + +#ifndef SYSCALL +# define SYSCALL sqlite3_syscall_ptr +#endif + +/* +** This function is not available on Windows CE or WinRT. + */ + +#if SQLITE_OS_WINCE || SQLITE_OS_WINRT +# define osAreFileApisANSI() 1 +#endif + +/* +** Many system calls are accessed through pointer-to-functions so that +** they may be overridden at runtime to facilitate fault injection during +** testing and sandboxing. The following array holds the names and pointers +** to all overrideable system calls. +*/ +static struct win_syscall { + const char *zName; /* Name of the system call */ + sqlite3_syscall_ptr pCurrent; /* Current value of the system call */ + sqlite3_syscall_ptr pDefault; /* Default value */ +} aSyscall[] = { +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT + { "AreFileApisANSI", (SYSCALL)AreFileApisANSI, 0 }, +#else + { "AreFileApisANSI", (SYSCALL)0, 0 }, +#endif + +#ifndef osAreFileApisANSI +#define osAreFileApisANSI ((BOOL(WINAPI*)(VOID))aSyscall[0].pCurrent) +#endif + +#if SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_WIDE) + { "CharLowerW", (SYSCALL)CharLowerW, 0 }, +#else + { "CharLowerW", (SYSCALL)0, 0 }, +#endif + +#define osCharLowerW ((LPWSTR(WINAPI*)(LPWSTR))aSyscall[1].pCurrent) + +#if SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_WIDE) + { "CharUpperW", (SYSCALL)CharUpperW, 0 }, +#else + { "CharUpperW", (SYSCALL)0, 0 }, +#endif + +#define osCharUpperW ((LPWSTR(WINAPI*)(LPWSTR))aSyscall[2].pCurrent) + + { "CloseHandle", (SYSCALL)CloseHandle, 0 }, + +#define osCloseHandle ((BOOL(WINAPI*)(HANDLE))aSyscall[3].pCurrent) + +#if defined(SQLITE_WIN32_HAS_ANSI) + { "CreateFileA", (SYSCALL)CreateFileA, 0 }, +#else + { "CreateFileA", (SYSCALL)0, 0 }, +#endif + +#define osCreateFileA ((HANDLE(WINAPI*)(LPCSTR,DWORD,DWORD, \ + LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[4].pCurrent) + +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) + { "CreateFileW", (SYSCALL)CreateFileW, 0 }, +#else + { "CreateFileW", (SYSCALL)0, 0 }, +#endif + +#define osCreateFileW ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD, \ + LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[5].pCurrent) + +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_ANSI) && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) && \ + SQLITE_WIN32_CREATEFILEMAPPINGA + { "CreateFileMappingA", (SYSCALL)CreateFileMappingA, 0 }, +#else + { "CreateFileMappingA", (SYSCALL)0, 0 }, +#endif + +#define osCreateFileMappingA ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ + DWORD,DWORD,DWORD,LPCSTR))aSyscall[6].pCurrent) + +#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) + { "CreateFileMappingW", (SYSCALL)CreateFileMappingW, 0 }, +#else + { "CreateFileMappingW", (SYSCALL)0, 0 }, +#endif + +#define osCreateFileMappingW ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ + DWORD,DWORD,DWORD,LPCWSTR))aSyscall[7].pCurrent) + +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) + { "CreateMutexW", (SYSCALL)CreateMutexW, 0 }, +#else + { "CreateMutexW", (SYSCALL)0, 0 }, +#endif + +#define osCreateMutexW ((HANDLE(WINAPI*)(LPSECURITY_ATTRIBUTES,BOOL, \ + LPCWSTR))aSyscall[8].pCurrent) + +#if defined(SQLITE_WIN32_HAS_ANSI) + { "DeleteFileA", (SYSCALL)DeleteFileA, 0 }, +#else + { "DeleteFileA", (SYSCALL)0, 0 }, +#endif + +#define osDeleteFileA ((BOOL(WINAPI*)(LPCSTR))aSyscall[9].pCurrent) + +#if defined(SQLITE_WIN32_HAS_WIDE) + { "DeleteFileW", (SYSCALL)DeleteFileW, 0 }, +#else + { "DeleteFileW", (SYSCALL)0, 0 }, +#endif + +#define osDeleteFileW ((BOOL(WINAPI*)(LPCWSTR))aSyscall[10].pCurrent) + +#if SQLITE_OS_WINCE + { "FileTimeToLocalFileTime", (SYSCALL)FileTimeToLocalFileTime, 0 }, +#else + { "FileTimeToLocalFileTime", (SYSCALL)0, 0 }, +#endif + +#define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(CONST FILETIME*, \ + LPFILETIME))aSyscall[11].pCurrent) + +#if SQLITE_OS_WINCE + { "FileTimeToSystemTime", (SYSCALL)FileTimeToSystemTime, 0 }, +#else + { "FileTimeToSystemTime", (SYSCALL)0, 0 }, +#endif + +#define osFileTimeToSystemTime ((BOOL(WINAPI*)(CONST FILETIME*, \ + LPSYSTEMTIME))aSyscall[12].pCurrent) + + { "FlushFileBuffers", (SYSCALL)FlushFileBuffers, 0 }, + +#define osFlushFileBuffers ((BOOL(WINAPI*)(HANDLE))aSyscall[13].pCurrent) + +#if defined(SQLITE_WIN32_HAS_ANSI) + { "FormatMessageA", (SYSCALL)FormatMessageA, 0 }, +#else + { "FormatMessageA", (SYSCALL)0, 0 }, +#endif + +#define osFormatMessageA ((DWORD(WINAPI*)(DWORD,LPCVOID,DWORD,DWORD,LPSTR, \ + DWORD,va_list*))aSyscall[14].pCurrent) + +#if defined(SQLITE_WIN32_HAS_WIDE) + { "FormatMessageW", (SYSCALL)FormatMessageW, 0 }, +#else + { "FormatMessageW", (SYSCALL)0, 0 }, +#endif + +#define osFormatMessageW ((DWORD(WINAPI*)(DWORD,LPCVOID,DWORD,DWORD,LPWSTR, \ + DWORD,va_list*))aSyscall[15].pCurrent) + +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) + { "FreeLibrary", (SYSCALL)FreeLibrary, 0 }, +#else + { "FreeLibrary", (SYSCALL)0, 0 }, +#endif + +#define osFreeLibrary ((BOOL(WINAPI*)(HMODULE))aSyscall[16].pCurrent) + + { "GetCurrentProcessId", (SYSCALL)GetCurrentProcessId, 0 }, + +#define osGetCurrentProcessId ((DWORD(WINAPI*)(VOID))aSyscall[17].pCurrent) + +#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_ANSI) + { "GetDiskFreeSpaceA", (SYSCALL)GetDiskFreeSpaceA, 0 }, +#else + { "GetDiskFreeSpaceA", (SYSCALL)0, 0 }, +#endif + +#define osGetDiskFreeSpaceA ((BOOL(WINAPI*)(LPCSTR,LPDWORD,LPDWORD,LPDWORD, \ + LPDWORD))aSyscall[18].pCurrent) + +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) + { "GetDiskFreeSpaceW", (SYSCALL)GetDiskFreeSpaceW, 0 }, +#else + { "GetDiskFreeSpaceW", (SYSCALL)0, 0 }, +#endif + +#define osGetDiskFreeSpaceW ((BOOL(WINAPI*)(LPCWSTR,LPDWORD,LPDWORD,LPDWORD, \ + LPDWORD))aSyscall[19].pCurrent) + +#if defined(SQLITE_WIN32_HAS_ANSI) + { "GetFileAttributesA", (SYSCALL)GetFileAttributesA, 0 }, +#else + { "GetFileAttributesA", (SYSCALL)0, 0 }, +#endif + +#define osGetFileAttributesA ((DWORD(WINAPI*)(LPCSTR))aSyscall[20].pCurrent) + +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) + { "GetFileAttributesW", (SYSCALL)GetFileAttributesW, 0 }, +#else + { "GetFileAttributesW", (SYSCALL)0, 0 }, +#endif + +#define osGetFileAttributesW ((DWORD(WINAPI*)(LPCWSTR))aSyscall[21].pCurrent) + +#if defined(SQLITE_WIN32_HAS_WIDE) + { "GetFileAttributesExW", (SYSCALL)GetFileAttributesExW, 0 }, +#else + { "GetFileAttributesExW", (SYSCALL)0, 0 }, +#endif + +#define osGetFileAttributesExW ((BOOL(WINAPI*)(LPCWSTR,GET_FILEEX_INFO_LEVELS, \ + LPVOID))aSyscall[22].pCurrent) + +#if !SQLITE_OS_WINRT + { "GetFileSize", (SYSCALL)GetFileSize, 0 }, +#else + { "GetFileSize", (SYSCALL)0, 0 }, +#endif + +#define osGetFileSize ((DWORD(WINAPI*)(HANDLE,LPDWORD))aSyscall[23].pCurrent) + +#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_ANSI) + { "GetFullPathNameA", (SYSCALL)GetFullPathNameA, 0 }, +#else + { "GetFullPathNameA", (SYSCALL)0, 0 }, +#endif + +#define osGetFullPathNameA ((DWORD(WINAPI*)(LPCSTR,DWORD,LPSTR, \ + LPSTR*))aSyscall[24].pCurrent) + +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) + { "GetFullPathNameW", (SYSCALL)GetFullPathNameW, 0 }, +#else + { "GetFullPathNameW", (SYSCALL)0, 0 }, +#endif + +#define osGetFullPathNameW ((DWORD(WINAPI*)(LPCWSTR,DWORD,LPWSTR, \ + LPWSTR*))aSyscall[25].pCurrent) + + { "GetLastError", (SYSCALL)GetLastError, 0 }, + +#define osGetLastError ((DWORD(WINAPI*)(VOID))aSyscall[26].pCurrent) + +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) +#if SQLITE_OS_WINCE + /* The GetProcAddressA() routine is only available on Windows CE. */ + { "GetProcAddressA", (SYSCALL)GetProcAddressA, 0 }, +#else + /* All other Windows platforms expect GetProcAddress() to take + ** an ANSI string regardless of the _UNICODE setting */ + { "GetProcAddressA", (SYSCALL)GetProcAddress, 0 }, +#endif +#else + { "GetProcAddressA", (SYSCALL)0, 0 }, +#endif + +#define osGetProcAddressA ((FARPROC(WINAPI*)(HMODULE, \ + LPCSTR))aSyscall[27].pCurrent) + +#if !SQLITE_OS_WINRT + { "GetSystemInfo", (SYSCALL)GetSystemInfo, 0 }, +#else + { "GetSystemInfo", (SYSCALL)0, 0 }, +#endif + +#define osGetSystemInfo ((VOID(WINAPI*)(LPSYSTEM_INFO))aSyscall[28].pCurrent) + + { "GetSystemTime", (SYSCALL)GetSystemTime, 0 }, + +#define osGetSystemTime ((VOID(WINAPI*)(LPSYSTEMTIME))aSyscall[29].pCurrent) + +#if !SQLITE_OS_WINCE + { "GetSystemTimeAsFileTime", (SYSCALL)GetSystemTimeAsFileTime, 0 }, +#else + { "GetSystemTimeAsFileTime", (SYSCALL)0, 0 }, +#endif + +#define osGetSystemTimeAsFileTime ((VOID(WINAPI*)( \ + LPFILETIME))aSyscall[30].pCurrent) + +#if defined(SQLITE_WIN32_HAS_ANSI) + { "GetTempPathA", (SYSCALL)GetTempPathA, 0 }, +#else + { "GetTempPathA", (SYSCALL)0, 0 }, +#endif + +#define osGetTempPathA ((DWORD(WINAPI*)(DWORD,LPSTR))aSyscall[31].pCurrent) + +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) + { "GetTempPathW", (SYSCALL)GetTempPathW, 0 }, +#else + { "GetTempPathW", (SYSCALL)0, 0 }, +#endif + +#define osGetTempPathW ((DWORD(WINAPI*)(DWORD,LPWSTR))aSyscall[32].pCurrent) + +#if !SQLITE_OS_WINRT + { "GetTickCount", (SYSCALL)GetTickCount, 0 }, +#else + { "GetTickCount", (SYSCALL)0, 0 }, +#endif + +#define osGetTickCount ((DWORD(WINAPI*)(VOID))aSyscall[33].pCurrent) + +#if defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_GETVERSIONEX + { "GetVersionExA", (SYSCALL)GetVersionExA, 0 }, +#else + { "GetVersionExA", (SYSCALL)0, 0 }, +#endif + +#define osGetVersionExA ((BOOL(WINAPI*)( \ + LPOSVERSIONINFOA))aSyscall[34].pCurrent) + +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ + SQLITE_WIN32_GETVERSIONEX + { "GetVersionExW", (SYSCALL)GetVersionExW, 0 }, +#else + { "GetVersionExW", (SYSCALL)0, 0 }, +#endif + +#define osGetVersionExW ((BOOL(WINAPI*)( \ + LPOSVERSIONINFOW))aSyscall[35].pCurrent) + + { "HeapAlloc", (SYSCALL)HeapAlloc, 0 }, + +#define osHeapAlloc ((LPVOID(WINAPI*)(HANDLE,DWORD, \ + SIZE_T))aSyscall[36].pCurrent) + +#if !SQLITE_OS_WINRT + { "HeapCreate", (SYSCALL)HeapCreate, 0 }, +#else + { "HeapCreate", (SYSCALL)0, 0 }, +#endif + +#define osHeapCreate ((HANDLE(WINAPI*)(DWORD,SIZE_T, \ + SIZE_T))aSyscall[37].pCurrent) + +#if !SQLITE_OS_WINRT + { "HeapDestroy", (SYSCALL)HeapDestroy, 0 }, +#else + { "HeapDestroy", (SYSCALL)0, 0 }, +#endif + +#define osHeapDestroy ((BOOL(WINAPI*)(HANDLE))aSyscall[38].pCurrent) + + { "HeapFree", (SYSCALL)HeapFree, 0 }, + +#define osHeapFree ((BOOL(WINAPI*)(HANDLE,DWORD,LPVOID))aSyscall[39].pCurrent) + + { "HeapReAlloc", (SYSCALL)HeapReAlloc, 0 }, + +#define osHeapReAlloc ((LPVOID(WINAPI*)(HANDLE,DWORD,LPVOID, \ + SIZE_T))aSyscall[40].pCurrent) + + { "HeapSize", (SYSCALL)HeapSize, 0 }, + +#define osHeapSize ((SIZE_T(WINAPI*)(HANDLE,DWORD, \ + LPCVOID))aSyscall[41].pCurrent) + +#if !SQLITE_OS_WINRT + { "HeapValidate", (SYSCALL)HeapValidate, 0 }, +#else + { "HeapValidate", (SYSCALL)0, 0 }, +#endif + +#define osHeapValidate ((BOOL(WINAPI*)(HANDLE,DWORD, \ + LPCVOID))aSyscall[42].pCurrent) + +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT + { "HeapCompact", (SYSCALL)HeapCompact, 0 }, +#else + { "HeapCompact", (SYSCALL)0, 0 }, +#endif + +#define osHeapCompact ((UINT(WINAPI*)(HANDLE,DWORD))aSyscall[43].pCurrent) + +#if defined(SQLITE_WIN32_HAS_ANSI) && !defined(SQLITE_OMIT_LOAD_EXTENSION) + { "LoadLibraryA", (SYSCALL)LoadLibraryA, 0 }, +#else + { "LoadLibraryA", (SYSCALL)0, 0 }, +#endif + +#define osLoadLibraryA ((HMODULE(WINAPI*)(LPCSTR))aSyscall[44].pCurrent) + +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ + !defined(SQLITE_OMIT_LOAD_EXTENSION) + { "LoadLibraryW", (SYSCALL)LoadLibraryW, 0 }, +#else + { "LoadLibraryW", (SYSCALL)0, 0 }, +#endif + +#define osLoadLibraryW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[45].pCurrent) + +#if !SQLITE_OS_WINRT + { "LocalFree", (SYSCALL)LocalFree, 0 }, +#else + { "LocalFree", (SYSCALL)0, 0 }, +#endif + +#define osLocalFree ((HLOCAL(WINAPI*)(HLOCAL))aSyscall[46].pCurrent) + +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT + { "LockFile", (SYSCALL)LockFile, 0 }, +#else + { "LockFile", (SYSCALL)0, 0 }, +#endif + +#ifndef osLockFile +#define osLockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ + DWORD))aSyscall[47].pCurrent) +#endif + +#if !SQLITE_OS_WINCE + { "LockFileEx", (SYSCALL)LockFileEx, 0 }, +#else + { "LockFileEx", (SYSCALL)0, 0 }, +#endif + +#ifndef osLockFileEx +#define osLockFileEx ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD,DWORD, \ + LPOVERLAPPED))aSyscall[48].pCurrent) +#endif + +#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) + { "MapViewOfFile", (SYSCALL)MapViewOfFile, 0 }, +#else + { "MapViewOfFile", (SYSCALL)0, 0 }, +#endif + +#define osMapViewOfFile ((LPVOID(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ + SIZE_T))aSyscall[49].pCurrent) + + { "MultiByteToWideChar", (SYSCALL)MultiByteToWideChar, 0 }, + +#define osMultiByteToWideChar ((int(WINAPI*)(UINT,DWORD,LPCSTR,int,LPWSTR, \ + int))aSyscall[50].pCurrent) + + { "QueryPerformanceCounter", (SYSCALL)QueryPerformanceCounter, 0 }, + +#define osQueryPerformanceCounter ((BOOL(WINAPI*)( \ + LARGE_INTEGER*))aSyscall[51].pCurrent) + + { "ReadFile", (SYSCALL)ReadFile, 0 }, + +#define osReadFile ((BOOL(WINAPI*)(HANDLE,LPVOID,DWORD,LPDWORD, \ + LPOVERLAPPED))aSyscall[52].pCurrent) + + { "SetEndOfFile", (SYSCALL)SetEndOfFile, 0 }, + +#define osSetEndOfFile ((BOOL(WINAPI*)(HANDLE))aSyscall[53].pCurrent) + +#if !SQLITE_OS_WINRT + { "SetFilePointer", (SYSCALL)SetFilePointer, 0 }, +#else + { "SetFilePointer", (SYSCALL)0, 0 }, +#endif + +#define osSetFilePointer ((DWORD(WINAPI*)(HANDLE,LONG,PLONG, \ + DWORD))aSyscall[54].pCurrent) + +#if !SQLITE_OS_WINRT + { "Sleep", (SYSCALL)Sleep, 0 }, +#else + { "Sleep", (SYSCALL)0, 0 }, +#endif + +#define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent) + + { "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 }, + +#define osSystemTimeToFileTime ((BOOL(WINAPI*)(CONST SYSTEMTIME*, \ + LPFILETIME))aSyscall[56].pCurrent) + +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT + { "UnlockFile", (SYSCALL)UnlockFile, 0 }, +#else + { "UnlockFile", (SYSCALL)0, 0 }, +#endif + +#ifndef osUnlockFile +#define osUnlockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ + DWORD))aSyscall[57].pCurrent) +#endif + +#if !SQLITE_OS_WINCE + { "UnlockFileEx", (SYSCALL)UnlockFileEx, 0 }, +#else + { "UnlockFileEx", (SYSCALL)0, 0 }, +#endif + +#define osUnlockFileEx ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ + LPOVERLAPPED))aSyscall[58].pCurrent) + +#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 + { "UnmapViewOfFile", (SYSCALL)UnmapViewOfFile, 0 }, +#else + { "UnmapViewOfFile", (SYSCALL)0, 0 }, +#endif + +#define osUnmapViewOfFile ((BOOL(WINAPI*)(LPCVOID))aSyscall[59].pCurrent) + + { "WideCharToMultiByte", (SYSCALL)WideCharToMultiByte, 0 }, + +#define osWideCharToMultiByte ((int(WINAPI*)(UINT,DWORD,LPCWSTR,int,LPSTR,int, \ + LPCSTR,LPBOOL))aSyscall[60].pCurrent) + + { "WriteFile", (SYSCALL)WriteFile, 0 }, + +#define osWriteFile ((BOOL(WINAPI*)(HANDLE,LPCVOID,DWORD,LPDWORD, \ + LPOVERLAPPED))aSyscall[61].pCurrent) + +#if SQLITE_OS_WINRT + { "CreateEventExW", (SYSCALL)CreateEventExW, 0 }, +#else + { "CreateEventExW", (SYSCALL)0, 0 }, +#endif + +#define osCreateEventExW ((HANDLE(WINAPI*)(LPSECURITY_ATTRIBUTES,LPCWSTR, \ + DWORD,DWORD))aSyscall[62].pCurrent) + +#if !SQLITE_OS_WINRT + { "WaitForSingleObject", (SYSCALL)WaitForSingleObject, 0 }, +#else + { "WaitForSingleObject", (SYSCALL)0, 0 }, +#endif + +#define osWaitForSingleObject ((DWORD(WINAPI*)(HANDLE, \ + DWORD))aSyscall[63].pCurrent) + +#if !SQLITE_OS_WINCE + { "WaitForSingleObjectEx", (SYSCALL)WaitForSingleObjectEx, 0 }, +#else + { "WaitForSingleObjectEx", (SYSCALL)0, 0 }, +#endif + +#define osWaitForSingleObjectEx ((DWORD(WINAPI*)(HANDLE,DWORD, \ + BOOL))aSyscall[64].pCurrent) + +#if SQLITE_OS_WINRT + { "SetFilePointerEx", (SYSCALL)SetFilePointerEx, 0 }, +#else + { "SetFilePointerEx", (SYSCALL)0, 0 }, +#endif + +#define osSetFilePointerEx ((BOOL(WINAPI*)(HANDLE,LARGE_INTEGER, \ + PLARGE_INTEGER,DWORD))aSyscall[65].pCurrent) + +#if SQLITE_OS_WINRT + { "GetFileInformationByHandleEx", (SYSCALL)GetFileInformationByHandleEx, 0 }, +#else + { "GetFileInformationByHandleEx", (SYSCALL)0, 0 }, +#endif + +#define osGetFileInformationByHandleEx ((BOOL(WINAPI*)(HANDLE, \ + FILE_INFO_BY_HANDLE_CLASS,LPVOID,DWORD))aSyscall[66].pCurrent) + +#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) + { "MapViewOfFileFromApp", (SYSCALL)MapViewOfFileFromApp, 0 }, +#else + { "MapViewOfFileFromApp", (SYSCALL)0, 0 }, +#endif + +#define osMapViewOfFileFromApp ((LPVOID(WINAPI*)(HANDLE,ULONG,ULONG64, \ + SIZE_T))aSyscall[67].pCurrent) + +#if SQLITE_OS_WINRT + { "CreateFile2", (SYSCALL)CreateFile2, 0 }, +#else + { "CreateFile2", (SYSCALL)0, 0 }, +#endif + +#define osCreateFile2 ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD,DWORD, \ + LPCREATEFILE2_EXTENDED_PARAMETERS))aSyscall[68].pCurrent) + +#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_LOAD_EXTENSION) + { "LoadPackagedLibrary", (SYSCALL)LoadPackagedLibrary, 0 }, +#else + { "LoadPackagedLibrary", (SYSCALL)0, 0 }, +#endif + +#define osLoadPackagedLibrary ((HMODULE(WINAPI*)(LPCWSTR, \ + DWORD))aSyscall[69].pCurrent) + +#if SQLITE_OS_WINRT + { "GetTickCount64", (SYSCALL)GetTickCount64, 0 }, +#else + { "GetTickCount64", (SYSCALL)0, 0 }, +#endif + +#define osGetTickCount64 ((ULONGLONG(WINAPI*)(VOID))aSyscall[70].pCurrent) + +#if SQLITE_OS_WINRT + { "GetNativeSystemInfo", (SYSCALL)GetNativeSystemInfo, 0 }, +#else + { "GetNativeSystemInfo", (SYSCALL)0, 0 }, +#endif + +#define osGetNativeSystemInfo ((VOID(WINAPI*)( \ + LPSYSTEM_INFO))aSyscall[71].pCurrent) + +#if defined(SQLITE_WIN32_HAS_ANSI) + { "OutputDebugStringA", (SYSCALL)OutputDebugStringA, 0 }, +#else + { "OutputDebugStringA", (SYSCALL)0, 0 }, +#endif + +#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[72].pCurrent) + +#if defined(SQLITE_WIN32_HAS_WIDE) + { "OutputDebugStringW", (SYSCALL)OutputDebugStringW, 0 }, +#else + { "OutputDebugStringW", (SYSCALL)0, 0 }, +#endif + +#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[73].pCurrent) + + { "GetProcessHeap", (SYSCALL)GetProcessHeap, 0 }, + +#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[74].pCurrent) + +#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) + { "CreateFileMappingFromApp", (SYSCALL)CreateFileMappingFromApp, 0 }, +#else + { "CreateFileMappingFromApp", (SYSCALL)0, 0 }, +#endif + +#define osCreateFileMappingFromApp ((HANDLE(WINAPI*)(HANDLE, \ + LPSECURITY_ATTRIBUTES,ULONG,ULONG64,LPCWSTR))aSyscall[75].pCurrent) + +/* +** NOTE: On some sub-platforms, the InterlockedCompareExchange "function" +** is really just a macro that uses a compiler intrinsic (e.g. x64). +** So do not try to make this is into a redefinable interface. +*/ +#if defined(InterlockedCompareExchange) + { "InterlockedCompareExchange", (SYSCALL)0, 0 }, + +#define osInterlockedCompareExchange InterlockedCompareExchange +#else + { "InterlockedCompareExchange", (SYSCALL)InterlockedCompareExchange, 0 }, + +#define osInterlockedCompareExchange ((LONG(WINAPI*)(LONG \ + SQLITE_WIN32_VOLATILE*, LONG,LONG))aSyscall[76].pCurrent) +#endif /* defined(InterlockedCompareExchange) */ + +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID + { "UuidCreate", (SYSCALL)UuidCreate, 0 }, +#else + { "UuidCreate", (SYSCALL)0, 0 }, +#endif + +#define osUuidCreate ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[77].pCurrent) + +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID + { "UuidCreateSequential", (SYSCALL)UuidCreateSequential, 0 }, +#else + { "UuidCreateSequential", (SYSCALL)0, 0 }, +#endif + +#define osUuidCreateSequential \ + ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[78].pCurrent) + +#if !defined(SQLITE_NO_SYNC) && SQLITE_MAX_MMAP_SIZE>0 + { "FlushViewOfFile", (SYSCALL)FlushViewOfFile, 0 }, +#else + { "FlushViewOfFile", (SYSCALL)0, 0 }, +#endif + +#define osFlushViewOfFile \ + ((BOOL(WINAPI*)(LPCVOID,SIZE_T))aSyscall[79].pCurrent) + +}; /* End of the overrideable system calls */ + +/* +** This is the xSetSystemCall() method of sqlite3_vfs for all of the +** "win32" VFSes. Return SQLITE_OK upon successfully updating the +** system call pointer, or SQLITE_NOTFOUND if there is no configurable +** system call named zName. +*/ +static int winSetSystemCall( + sqlite3_vfs *pNotUsed, /* The VFS pointer. Not used */ + const char *zName, /* Name of system call to override */ + sqlite3_syscall_ptr pNewFunc /* Pointer to new system call value */ +){ + unsigned int i; + int rc = SQLITE_NOTFOUND; + + UNUSED_PARAMETER(pNotUsed); + if( zName==0 ){ + /* If no zName is given, restore all system calls to their default + ** settings and return NULL + */ + rc = SQLITE_OK; + for(i=0; i<sizeof(aSyscall)/sizeof(aSyscall[0]); i++){ + if( aSyscall[i].pDefault ){ + aSyscall[i].pCurrent = aSyscall[i].pDefault; + } + } + }else{ + /* If zName is specified, operate on only the one system call + ** specified. + */ + for(i=0; i<sizeof(aSyscall)/sizeof(aSyscall[0]); i++){ + if( strcmp(zName, aSyscall[i].zName)==0 ){ + if( aSyscall[i].pDefault==0 ){ + aSyscall[i].pDefault = aSyscall[i].pCurrent; + } + rc = SQLITE_OK; + if( pNewFunc==0 ) pNewFunc = aSyscall[i].pDefault; + aSyscall[i].pCurrent = pNewFunc; + break; + } + } + } + return rc; +} + +/* +** Return the value of a system call. Return NULL if zName is not a +** recognized system call name. NULL is also returned if the system call +** is currently undefined. +*/ +static sqlite3_syscall_ptr winGetSystemCall( + sqlite3_vfs *pNotUsed, + const char *zName +){ + unsigned int i; + + UNUSED_PARAMETER(pNotUsed); + for(i=0; i<sizeof(aSyscall)/sizeof(aSyscall[0]); i++){ + if( strcmp(zName, aSyscall[i].zName)==0 ) return aSyscall[i].pCurrent; + } + return 0; +} + +/* +** Return the name of the first system call after zName. If zName==NULL +** then return the name of the first system call. Return NULL if zName +** is the last system call or if zName is not the name of a valid +** system call. +*/ +static const char *winNextSystemCall(sqlite3_vfs *p, const char *zName){ + int i = -1; + + UNUSED_PARAMETER(p); + if( zName ){ + for(i=0; i<ArraySize(aSyscall)-1; i++){ + if( strcmp(zName, aSyscall[i].zName)==0 ) break; + } + } + for(i++; i<ArraySize(aSyscall); i++){ + if( aSyscall[i].pCurrent!=0 ) return aSyscall[i].zName; + } + return 0; +} + +#ifdef SQLITE_WIN32_MALLOC +/* +** If a Win32 native heap has been configured, this function will attempt to +** compact it. Upon success, SQLITE_OK will be returned. Upon failure, one +** of SQLITE_NOMEM, SQLITE_ERROR, or SQLITE_NOTFOUND will be returned. The +** "pnLargest" argument, if non-zero, will be used to return the size of the +** largest committed free block in the heap, in bytes. +*/ +SQLITE_API int sqlite3_win32_compact_heap(LPUINT pnLargest){ + int rc = SQLITE_OK; + UINT nLargest = 0; + HANDLE hHeap; + + winMemAssertMagic(); + hHeap = winMemGetHeap(); + assert( hHeap!=0 ); + assert( hHeap!=INVALID_HANDLE_VALUE ); +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) + assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); +#endif +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT + if( (nLargest=osHeapCompact(hHeap, SQLITE_WIN32_HEAP_FLAGS))==0 ){ + DWORD lastErrno = osGetLastError(); + if( lastErrno==NO_ERROR ){ + sqlite3_log(SQLITE_NOMEM, "failed to HeapCompact (no space), heap=%p", + (void*)hHeap); + rc = SQLITE_NOMEM_BKPT; + }else{ + sqlite3_log(SQLITE_ERROR, "failed to HeapCompact (%lu), heap=%p", + osGetLastError(), (void*)hHeap); + rc = SQLITE_ERROR; + } + } +#else + sqlite3_log(SQLITE_NOTFOUND, "failed to HeapCompact, heap=%p", + (void*)hHeap); + rc = SQLITE_NOTFOUND; +#endif + if( pnLargest ) *pnLargest = nLargest; + return rc; +} + +/* +** If a Win32 native heap has been configured, this function will attempt to +** destroy and recreate it. If the Win32 native heap is not isolated and/or +** the sqlite3_memory_used() function does not return zero, SQLITE_BUSY will +** be returned and no changes will be made to the Win32 native heap. +*/ +SQLITE_API int sqlite3_win32_reset_heap(){ + int rc; + MUTEX_LOGIC( sqlite3_mutex *pMainMtx; ) /* The main static mutex */ + MUTEX_LOGIC( sqlite3_mutex *pMem; ) /* The memsys static mutex */ + MUTEX_LOGIC( pMainMtx = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); ) + MUTEX_LOGIC( pMem = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); ) + sqlite3_mutex_enter(pMainMtx); + sqlite3_mutex_enter(pMem); + winMemAssertMagic(); + if( winMemGetHeap()!=NULL && winMemGetOwned() && sqlite3_memory_used()==0 ){ + /* + ** At this point, there should be no outstanding memory allocations on + ** the heap. Also, since both the main and memsys locks are currently + ** being held by us, no other function (i.e. from another thread) should + ** be able to even access the heap. Attempt to destroy and recreate our + ** isolated Win32 native heap now. + */ + assert( winMemGetHeap()!=NULL ); + assert( winMemGetOwned() ); + assert( sqlite3_memory_used()==0 ); + winMemShutdown(winMemGetDataPtr()); + assert( winMemGetHeap()==NULL ); + assert( !winMemGetOwned() ); + assert( sqlite3_memory_used()==0 ); + rc = winMemInit(winMemGetDataPtr()); + assert( rc!=SQLITE_OK || winMemGetHeap()!=NULL ); + assert( rc!=SQLITE_OK || winMemGetOwned() ); + assert( rc!=SQLITE_OK || sqlite3_memory_used()==0 ); + }else{ + /* + ** The Win32 native heap cannot be modified because it may be in use. + */ + rc = SQLITE_BUSY; + } + sqlite3_mutex_leave(pMem); + sqlite3_mutex_leave(pMainMtx); + return rc; +} +#endif /* SQLITE_WIN32_MALLOC */ + +/* +** This function outputs the specified (ANSI) string to the Win32 debugger +** (if available). +*/ + +SQLITE_API void sqlite3_win32_write_debug(const char *zBuf, int nBuf){ + char zDbgBuf[SQLITE_WIN32_DBG_BUF_SIZE]; + int nMin = MIN(nBuf, (SQLITE_WIN32_DBG_BUF_SIZE - 1)); /* may be negative. */ + if( nMin<-1 ) nMin = -1; /* all negative values become -1. */ + assert( nMin==-1 || nMin==0 || nMin<SQLITE_WIN32_DBG_BUF_SIZE ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zBuf ){ + (void)SQLITE_MISUSE_BKPT; + return; + } +#endif +#if defined(SQLITE_WIN32_HAS_ANSI) + if( nMin>0 ){ + memset(zDbgBuf, 0, SQLITE_WIN32_DBG_BUF_SIZE); + memcpy(zDbgBuf, zBuf, nMin); + osOutputDebugStringA(zDbgBuf); + }else{ + osOutputDebugStringA(zBuf); + } +#elif defined(SQLITE_WIN32_HAS_WIDE) + memset(zDbgBuf, 0, SQLITE_WIN32_DBG_BUF_SIZE); + if ( osMultiByteToWideChar( + osAreFileApisANSI() ? CP_ACP : CP_OEMCP, 0, zBuf, + nMin, (LPWSTR)zDbgBuf, SQLITE_WIN32_DBG_BUF_SIZE/sizeof(WCHAR))<=0 ){ + return; + } + osOutputDebugStringW((LPCWSTR)zDbgBuf); +#else + if( nMin>0 ){ + memset(zDbgBuf, 0, SQLITE_WIN32_DBG_BUF_SIZE); + memcpy(zDbgBuf, zBuf, nMin); + fprintf(stderr, "%s", zDbgBuf); + }else{ + fprintf(stderr, "%s", zBuf); + } +#endif +} + +/* +** The following routine suspends the current thread for at least ms +** milliseconds. This is equivalent to the Win32 Sleep() interface. +*/ +#if SQLITE_OS_WINRT +static HANDLE sleepObj = NULL; +#endif + +SQLITE_API void sqlite3_win32_sleep(DWORD milliseconds){ +#if SQLITE_OS_WINRT + if ( sleepObj==NULL ){ + sleepObj = osCreateEventExW(NULL, NULL, CREATE_EVENT_MANUAL_RESET, + SYNCHRONIZE); + } + assert( sleepObj!=NULL ); + osWaitForSingleObjectEx(sleepObj, milliseconds, FALSE); +#else + osSleep(milliseconds); +#endif +} + +#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ + SQLITE_THREADSAFE>0 +SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){ + DWORD rc; + while( (rc = osWaitForSingleObjectEx(hObject, INFINITE, + TRUE))==WAIT_IO_COMPLETION ){} + return rc; +} +#endif + +/* +** Return true (non-zero) if we are running under WinNT, Win2K, WinXP, +** or WinCE. Return false (zero) for Win95, Win98, or WinME. +** +** Here is an interesting observation: Win95, Win98, and WinME lack +** the LockFileEx() API. But we can still statically link against that +** API as long as we don't call it when running Win95/98/ME. A call to +** this routine is used to determine if the host is Win95/98/ME or +** WinNT/2K/XP so that we will know whether or not we can safely call +** the LockFileEx() API. +*/ + +#if !SQLITE_WIN32_GETVERSIONEX +# define osIsNT() (1) +#elif SQLITE_OS_WINCE || SQLITE_OS_WINRT || !defined(SQLITE_WIN32_HAS_ANSI) +# define osIsNT() (1) +#elif !defined(SQLITE_WIN32_HAS_WIDE) +# define osIsNT() (0) +#else +# define osIsNT() ((sqlite3_os_type==2) || sqlite3_win32_is_nt()) +#endif + +/* +** This function determines if the machine is running a version of Windows +** based on the NT kernel. +*/ +SQLITE_API int sqlite3_win32_is_nt(void){ +#if SQLITE_OS_WINRT + /* + ** NOTE: The WinRT sub-platform is always assumed to be based on the NT + ** kernel. + */ + return 1; +#elif SQLITE_WIN32_GETVERSIONEX + if( osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 ){ +#if defined(SQLITE_WIN32_HAS_ANSI) + OSVERSIONINFOA sInfo; + sInfo.dwOSVersionInfoSize = sizeof(sInfo); + osGetVersionExA(&sInfo); + osInterlockedCompareExchange(&sqlite3_os_type, + (sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) ? 2 : 1, 0); +#elif defined(SQLITE_WIN32_HAS_WIDE) + OSVERSIONINFOW sInfo; + sInfo.dwOSVersionInfoSize = sizeof(sInfo); + osGetVersionExW(&sInfo); + osInterlockedCompareExchange(&sqlite3_os_type, + (sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) ? 2 : 1, 0); +#endif + } + return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2; +#elif SQLITE_TEST + return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2; +#else + /* + ** NOTE: All sub-platforms where the GetVersionEx[AW] functions are + ** deprecated are always assumed to be based on the NT kernel. + */ + return 1; +#endif +} + +#ifdef SQLITE_WIN32_MALLOC +/* +** Allocate nBytes of memory. +*/ +static void *winMemMalloc(int nBytes){ + HANDLE hHeap; + void *p; + + winMemAssertMagic(); + hHeap = winMemGetHeap(); + assert( hHeap!=0 ); + assert( hHeap!=INVALID_HANDLE_VALUE ); +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) + assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); +#endif + assert( nBytes>=0 ); + p = osHeapAlloc(hHeap, SQLITE_WIN32_HEAP_FLAGS, (SIZE_T)nBytes); + if( !p ){ + sqlite3_log(SQLITE_NOMEM, "failed to HeapAlloc %u bytes (%lu), heap=%p", + nBytes, osGetLastError(), (void*)hHeap); + } + return p; +} + +/* +** Free memory. +*/ +static void winMemFree(void *pPrior){ + HANDLE hHeap; + + winMemAssertMagic(); + hHeap = winMemGetHeap(); + assert( hHeap!=0 ); + assert( hHeap!=INVALID_HANDLE_VALUE ); +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) + assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); +#endif + if( !pPrior ) return; /* Passing NULL to HeapFree is undefined. */ + if( !osHeapFree(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ){ + sqlite3_log(SQLITE_NOMEM, "failed to HeapFree block %p (%lu), heap=%p", + pPrior, osGetLastError(), (void*)hHeap); + } +} + +/* +** Change the size of an existing memory allocation +*/ +static void *winMemRealloc(void *pPrior, int nBytes){ + HANDLE hHeap; + void *p; + + winMemAssertMagic(); + hHeap = winMemGetHeap(); + assert( hHeap!=0 ); + assert( hHeap!=INVALID_HANDLE_VALUE ); +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) + assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); +#endif + assert( nBytes>=0 ); + if( !pPrior ){ + p = osHeapAlloc(hHeap, SQLITE_WIN32_HEAP_FLAGS, (SIZE_T)nBytes); + }else{ + p = osHeapReAlloc(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior, (SIZE_T)nBytes); + } + if( !p ){ + sqlite3_log(SQLITE_NOMEM, "failed to %s %u bytes (%lu), heap=%p", + pPrior ? "HeapReAlloc" : "HeapAlloc", nBytes, osGetLastError(), + (void*)hHeap); + } + return p; +} + +/* +** Return the size of an outstanding allocation, in bytes. +*/ +static int winMemSize(void *p){ + HANDLE hHeap; + SIZE_T n; + + winMemAssertMagic(); + hHeap = winMemGetHeap(); + assert( hHeap!=0 ); + assert( hHeap!=INVALID_HANDLE_VALUE ); +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) + assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, p) ); +#endif + if( !p ) return 0; + n = osHeapSize(hHeap, SQLITE_WIN32_HEAP_FLAGS, p); + if( n==(SIZE_T)-1 ){ + sqlite3_log(SQLITE_NOMEM, "failed to HeapSize block %p (%lu), heap=%p", + p, osGetLastError(), (void*)hHeap); + return 0; + } + return (int)n; +} + +/* +** Round up a request size to the next valid allocation size. +*/ +static int winMemRoundup(int n){ + return n; +} + +/* +** Initialize this module. +*/ +static int winMemInit(void *pAppData){ + winMemData *pWinMemData = (winMemData *)pAppData; + + if( !pWinMemData ) return SQLITE_ERROR; + assert( pWinMemData->magic1==WINMEM_MAGIC1 ); + assert( pWinMemData->magic2==WINMEM_MAGIC2 ); + +#if !SQLITE_OS_WINRT && SQLITE_WIN32_HEAP_CREATE + if( !pWinMemData->hHeap ){ + DWORD dwInitialSize = SQLITE_WIN32_HEAP_INIT_SIZE; + DWORD dwMaximumSize = (DWORD)sqlite3GlobalConfig.nHeap; + if( dwMaximumSize==0 ){ + dwMaximumSize = SQLITE_WIN32_HEAP_MAX_SIZE; + }else if( dwInitialSize>dwMaximumSize ){ + dwInitialSize = dwMaximumSize; + } + pWinMemData->hHeap = osHeapCreate(SQLITE_WIN32_HEAP_FLAGS, + dwInitialSize, dwMaximumSize); + if( !pWinMemData->hHeap ){ + sqlite3_log(SQLITE_NOMEM, + "failed to HeapCreate (%lu), flags=%u, initSize=%lu, maxSize=%lu", + osGetLastError(), SQLITE_WIN32_HEAP_FLAGS, dwInitialSize, + dwMaximumSize); + return SQLITE_NOMEM_BKPT; + } + pWinMemData->bOwned = TRUE; + assert( pWinMemData->bOwned ); + } +#else + pWinMemData->hHeap = osGetProcessHeap(); + if( !pWinMemData->hHeap ){ + sqlite3_log(SQLITE_NOMEM, + "failed to GetProcessHeap (%lu)", osGetLastError()); + return SQLITE_NOMEM_BKPT; + } + pWinMemData->bOwned = FALSE; + assert( !pWinMemData->bOwned ); +#endif + assert( pWinMemData->hHeap!=0 ); + assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) + assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); +#endif + return SQLITE_OK; +} + +/* +** Deinitialize this module. +*/ +static void winMemShutdown(void *pAppData){ + winMemData *pWinMemData = (winMemData *)pAppData; + + if( !pWinMemData ) return; + assert( pWinMemData->magic1==WINMEM_MAGIC1 ); + assert( pWinMemData->magic2==WINMEM_MAGIC2 ); + + if( pWinMemData->hHeap ){ + assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) + assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); +#endif + if( pWinMemData->bOwned ){ + if( !osHeapDestroy(pWinMemData->hHeap) ){ + sqlite3_log(SQLITE_NOMEM, "failed to HeapDestroy (%lu), heap=%p", + osGetLastError(), (void*)pWinMemData->hHeap); + } + pWinMemData->bOwned = FALSE; + } + pWinMemData->hHeap = NULL; + } +} + +/* +** Populate the low-level memory allocation function pointers in +** sqlite3GlobalConfig.m with pointers to the routines in this file. The +** arguments specify the block of memory to manage. +** +** This routine is only called by sqlite3_config(), and therefore +** is not required to be threadsafe (it is not). +*/ +SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetWin32(void){ + static const sqlite3_mem_methods winMemMethods = { + winMemMalloc, + winMemFree, + winMemRealloc, + winMemSize, + winMemRoundup, + winMemInit, + winMemShutdown, + &win_mem_data + }; + return &winMemMethods; +} + +SQLITE_PRIVATE void sqlite3MemSetDefault(void){ + sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32()); +} +#endif /* SQLITE_WIN32_MALLOC */ + +/* +** Convert a UTF-8 string to Microsoft Unicode. +** +** Space to hold the returned string is obtained from sqlite3_malloc(). +*/ +static LPWSTR winUtf8ToUnicode(const char *zText){ + int nChar; + LPWSTR zWideText; + + nChar = osMultiByteToWideChar(CP_UTF8, 0, zText, -1, NULL, 0); + if( nChar==0 ){ + return 0; + } + zWideText = sqlite3MallocZero( nChar*sizeof(WCHAR) ); + if( zWideText==0 ){ + return 0; + } + nChar = osMultiByteToWideChar(CP_UTF8, 0, zText, -1, zWideText, + nChar); + if( nChar==0 ){ + sqlite3_free(zWideText); + zWideText = 0; + } + return zWideText; +} + +/* +** Convert a Microsoft Unicode string to UTF-8. +** +** Space to hold the returned string is obtained from sqlite3_malloc(). +*/ +static char *winUnicodeToUtf8(LPCWSTR zWideText){ + int nByte; + char *zText; + + nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideText, -1, 0, 0, 0, 0); + if( nByte == 0 ){ + return 0; + } + zText = sqlite3MallocZero( nByte ); + if( zText==0 ){ + return 0; + } + nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideText, -1, zText, nByte, + 0, 0); + if( nByte == 0 ){ + sqlite3_free(zText); + zText = 0; + } + return zText; +} + +/* +** Convert an ANSI string to Microsoft Unicode, using the ANSI or OEM +** code page. +** +** Space to hold the returned string is obtained from sqlite3_malloc(). +*/ +static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){ + int nByte; + LPWSTR zMbcsText; + int codepage = useAnsi ? CP_ACP : CP_OEMCP; + + nByte = osMultiByteToWideChar(codepage, 0, zText, -1, NULL, + 0)*sizeof(WCHAR); + if( nByte==0 ){ + return 0; + } + zMbcsText = sqlite3MallocZero( nByte*sizeof(WCHAR) ); + if( zMbcsText==0 ){ + return 0; + } + nByte = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText, + nByte); + if( nByte==0 ){ + sqlite3_free(zMbcsText); + zMbcsText = 0; + } + return zMbcsText; +} + +/* +** Convert a Microsoft Unicode string to a multi-byte character string, +** using the ANSI or OEM code page. +** +** Space to hold the returned string is obtained from sqlite3_malloc(). +*/ +static char *winUnicodeToMbcs(LPCWSTR zWideText, int useAnsi){ + int nByte; + char *zText; + int codepage = useAnsi ? CP_ACP : CP_OEMCP; + + nByte = osWideCharToMultiByte(codepage, 0, zWideText, -1, 0, 0, 0, 0); + if( nByte == 0 ){ + return 0; + } + zText = sqlite3MallocZero( nByte ); + if( zText==0 ){ + return 0; + } + nByte = osWideCharToMultiByte(codepage, 0, zWideText, -1, zText, + nByte, 0, 0); + if( nByte == 0 ){ + sqlite3_free(zText); + zText = 0; + } + return zText; +} + +/* +** Convert a multi-byte character string to UTF-8. +** +** Space to hold the returned string is obtained from sqlite3_malloc(). +*/ +static char *winMbcsToUtf8(const char *zText, int useAnsi){ + char *zTextUtf8; + LPWSTR zTmpWide; + + zTmpWide = winMbcsToUnicode(zText, useAnsi); + if( zTmpWide==0 ){ + return 0; + } + zTextUtf8 = winUnicodeToUtf8(zTmpWide); + sqlite3_free(zTmpWide); + return zTextUtf8; +} + +/* +** Convert a UTF-8 string to a multi-byte character string. +** +** Space to hold the returned string is obtained from sqlite3_malloc(). +*/ +static char *winUtf8ToMbcs(const char *zText, int useAnsi){ + char *zTextMbcs; + LPWSTR zTmpWide; + + zTmpWide = winUtf8ToUnicode(zText); + if( zTmpWide==0 ){ + return 0; + } + zTextMbcs = winUnicodeToMbcs(zTmpWide, useAnsi); + sqlite3_free(zTmpWide); + return zTextMbcs; +} + +/* +** This is a public wrapper for the winUtf8ToUnicode() function. +*/ +SQLITE_API LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winUtf8ToUnicode(zText); +} + +/* +** This is a public wrapper for the winUnicodeToUtf8() function. +*/ +SQLITE_API char *sqlite3_win32_unicode_to_utf8(LPCWSTR zWideText){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zWideText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winUnicodeToUtf8(zWideText); +} + +/* +** This is a public wrapper for the winMbcsToUtf8() function. +*/ +SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winMbcsToUtf8(zText, osAreFileApisANSI()); +} + +/* +** This is a public wrapper for the winMbcsToUtf8() function. +*/ +SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winMbcsToUtf8(zText, useAnsi); +} + +/* +** This is a public wrapper for the winUtf8ToMbcs() function. +*/ +SQLITE_API char *sqlite3_win32_utf8_to_mbcs(const char *zText){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winUtf8ToMbcs(zText, osAreFileApisANSI()); +} + +/* +** This is a public wrapper for the winUtf8ToMbcs() function. +*/ +SQLITE_API char *sqlite3_win32_utf8_to_mbcs_v2(const char *zText, int useAnsi){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winUtf8ToMbcs(zText, useAnsi); +} + +/* +** This function is the same as sqlite3_win32_set_directory (below); however, +** it accepts a UTF-8 string. +*/ +SQLITE_API int sqlite3_win32_set_directory8( + unsigned long type, /* Identifier for directory being set or reset */ + const char *zValue /* New value for directory being set or reset */ +){ + char **ppDirectory = 0; + int rc; +#ifndef SQLITE_OMIT_AUTOINIT + rc = sqlite3_initialize(); + if( rc ) return rc; +#endif + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + if( type==SQLITE_WIN32_DATA_DIRECTORY_TYPE ){ + ppDirectory = &sqlite3_data_directory; + }else if( type==SQLITE_WIN32_TEMP_DIRECTORY_TYPE ){ + ppDirectory = &sqlite3_temp_directory; + } + assert( !ppDirectory || type==SQLITE_WIN32_DATA_DIRECTORY_TYPE + || type==SQLITE_WIN32_TEMP_DIRECTORY_TYPE + ); + assert( !ppDirectory || sqlite3MemdebugHasType(*ppDirectory, MEMTYPE_HEAP) ); + if( ppDirectory ){ + char *zCopy = 0; + if( zValue && zValue[0] ){ + zCopy = sqlite3_mprintf("%s", zValue); + if ( zCopy==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto set_directory8_done; + } + } + sqlite3_free(*ppDirectory); + *ppDirectory = zCopy; + rc = SQLITE_OK; + }else{ + rc = SQLITE_ERROR; + } +set_directory8_done: + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return rc; +} + +/* +** This function is the same as sqlite3_win32_set_directory (below); however, +** it accepts a UTF-16 string. +*/ +SQLITE_API int sqlite3_win32_set_directory16( + unsigned long type, /* Identifier for directory being set or reset */ + const void *zValue /* New value for directory being set or reset */ +){ + int rc; + char *zUtf8 = 0; + if( zValue ){ + zUtf8 = sqlite3_win32_unicode_to_utf8(zValue); + if( zUtf8==0 ) return SQLITE_NOMEM_BKPT; + } + rc = sqlite3_win32_set_directory8(type, zUtf8); + if( zUtf8 ) sqlite3_free(zUtf8); + return rc; +} + +/* +** This function sets the data directory or the temporary directory based on +** the provided arguments. The type argument must be 1 in order to set the +** data directory or 2 in order to set the temporary directory. The zValue +** argument is the name of the directory to use. The return value will be +** SQLITE_OK if successful. +*/ +SQLITE_API int sqlite3_win32_set_directory( + unsigned long type, /* Identifier for directory being set or reset */ + void *zValue /* New value for directory being set or reset */ +){ + return sqlite3_win32_set_directory16(type, zValue); +} + +/* +** The return value of winGetLastErrorMsg +** is zero if the error message fits in the buffer, or non-zero +** otherwise (if the message was truncated). +*/ +static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ + /* FormatMessage returns 0 on failure. Otherwise it + ** returns the number of TCHARs written to the output + ** buffer, excluding the terminating null char. + */ + DWORD dwLen = 0; + char *zOut = 0; + + if( osIsNT() ){ +#if SQLITE_OS_WINRT + WCHAR zTempWide[SQLITE_WIN32_MAX_ERRMSG_CHARS+1]; + dwLen = osFormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + lastErrno, + 0, + zTempWide, + SQLITE_WIN32_MAX_ERRMSG_CHARS, + 0); +#else + LPWSTR zTempWide = NULL; + dwLen = osFormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + lastErrno, + 0, + (LPWSTR) &zTempWide, + 0, + 0); +#endif + if( dwLen > 0 ){ + /* allocate a buffer and convert to UTF8 */ + sqlite3BeginBenignMalloc(); + zOut = winUnicodeToUtf8(zTempWide); + sqlite3EndBenignMalloc(); +#if !SQLITE_OS_WINRT + /* free the system buffer allocated by FormatMessage */ + osLocalFree(zTempWide); +#endif + } + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + char *zTemp = NULL; + dwLen = osFormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + lastErrno, + 0, + (LPSTR) &zTemp, + 0, + 0); + if( dwLen > 0 ){ + /* allocate a buffer and convert to UTF8 */ + sqlite3BeginBenignMalloc(); + zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI()); + sqlite3EndBenignMalloc(); + /* free the system buffer allocated by FormatMessage */ + osLocalFree(zTemp); + } + } +#endif + if( 0 == dwLen ){ + sqlite3_snprintf(nBuf, zBuf, "OsError 0x%lx (%lu)", lastErrno, lastErrno); + }else{ + /* copy a maximum of nBuf chars to output buffer */ + sqlite3_snprintf(nBuf, zBuf, "%s", zOut); + /* free the UTF8 buffer */ + sqlite3_free(zOut); + } + return 0; +} + +/* +** +** This function - winLogErrorAtLine() - is only ever called via the macro +** winLogError(). +** +** This routine is invoked after an error occurs in an OS function. +** It logs a message using sqlite3_log() containing the current value of +** error code and, if possible, the human-readable equivalent from +** FormatMessage. +** +** The first argument passed to the macro should be the error code that +** will be returned to SQLite (e.g. SQLITE_IOERR_DELETE, SQLITE_CANTOPEN). +** The two subsequent arguments should be the name of the OS function that +** failed and the associated file-system path, if any. +*/ +#define winLogError(a,b,c,d) winLogErrorAtLine(a,b,c,d,__LINE__) +static int winLogErrorAtLine( + int errcode, /* SQLite error code */ + DWORD lastErrno, /* Win32 last error */ + const char *zFunc, /* Name of OS function that failed */ + const char *zPath, /* File path associated with error */ + int iLine /* Source line number where error occurred */ +){ + char zMsg[500]; /* Human readable error text */ + int i; /* Loop counter */ + + zMsg[0] = 0; + winGetLastErrorMsg(lastErrno, sizeof(zMsg), zMsg); + assert( errcode!=SQLITE_OK ); + if( zPath==0 ) zPath = ""; + for(i=0; zMsg[i] && zMsg[i]!='\r' && zMsg[i]!='\n'; i++){} + zMsg[i] = 0; + sqlite3_log(errcode, + "os_win.c:%d: (%lu) %s(%s) - %s", + iLine, lastErrno, zFunc, zPath, zMsg + ); + + return errcode; +} + +/* +** The number of times that a ReadFile(), WriteFile(), and DeleteFile() +** will be retried following a locking error - probably caused by +** antivirus software. Also the initial delay before the first retry. +** The delay increases linearly with each retry. +*/ +#ifndef SQLITE_WIN32_IOERR_RETRY +# define SQLITE_WIN32_IOERR_RETRY 10 +#endif +#ifndef SQLITE_WIN32_IOERR_RETRY_DELAY +# define SQLITE_WIN32_IOERR_RETRY_DELAY 25 +#endif +static int winIoerrRetry = SQLITE_WIN32_IOERR_RETRY; +static int winIoerrRetryDelay = SQLITE_WIN32_IOERR_RETRY_DELAY; + +/* +** The "winIoerrCanRetry1" macro is used to determine if a particular I/O +** error code obtained via GetLastError() is eligible to be retried. It +** must accept the error code DWORD as its only argument and should return +** non-zero if the error code is transient in nature and the operation +** responsible for generating the original error might succeed upon being +** retried. The argument to this macro should be a variable. +** +** Additionally, a macro named "winIoerrCanRetry2" may be defined. If it +** is defined, it will be consulted only when the macro "winIoerrCanRetry1" +** returns zero. The "winIoerrCanRetry2" macro is completely optional and +** may be used to include additional error codes in the set that should +** result in the failing I/O operation being retried by the caller. If +** defined, the "winIoerrCanRetry2" macro must exhibit external semantics +** identical to those of the "winIoerrCanRetry1" macro. +*/ +#if !defined(winIoerrCanRetry1) +#define winIoerrCanRetry1(a) (((a)==ERROR_ACCESS_DENIED) || \ + ((a)==ERROR_SHARING_VIOLATION) || \ + ((a)==ERROR_LOCK_VIOLATION) || \ + ((a)==ERROR_DEV_NOT_EXIST) || \ + ((a)==ERROR_NETNAME_DELETED) || \ + ((a)==ERROR_SEM_TIMEOUT) || \ + ((a)==ERROR_NETWORK_UNREACHABLE)) +#endif + +/* +** If a ReadFile() or WriteFile() error occurs, invoke this routine +** to see if it should be retried. Return TRUE to retry. Return FALSE +** to give up with an error. +*/ +static int winRetryIoerr(int *pnRetry, DWORD *pError){ + DWORD e = osGetLastError(); + if( *pnRetry>=winIoerrRetry ){ + if( pError ){ + *pError = e; + } + return 0; + } + if( winIoerrCanRetry1(e) ){ + sqlite3_win32_sleep(winIoerrRetryDelay*(1+*pnRetry)); + ++*pnRetry; + return 1; + } +#if defined(winIoerrCanRetry2) + else if( winIoerrCanRetry2(e) ){ + sqlite3_win32_sleep(winIoerrRetryDelay*(1+*pnRetry)); + ++*pnRetry; + return 1; + } +#endif + if( pError ){ + *pError = e; + } + return 0; +} + +/* +** Log a I/O error retry episode. +*/ +static void winLogIoerr(int nRetry, int lineno){ + if( nRetry ){ + sqlite3_log(SQLITE_NOTICE, + "delayed %dms for lock/sharing conflict at line %d", + winIoerrRetryDelay*nRetry*(nRetry+1)/2, lineno + ); + } +} + +/* +** This #if does not rely on the SQLITE_OS_WINCE define because the +** corresponding section in "date.c" cannot use it. +*/ +#if !defined(SQLITE_OMIT_LOCALTIME) && defined(_WIN32_WCE) && \ + (!defined(SQLITE_MSVC_LOCALTIME_API) || !SQLITE_MSVC_LOCALTIME_API) +/* +** The MSVC CRT on Windows CE may not have a localtime() function. +** So define a substitute. +*/ +/* # include <time.h> */ +struct tm *__cdecl localtime(const time_t *t) +{ + static struct tm y; + FILETIME uTm, lTm; + SYSTEMTIME pTm; + sqlite3_int64 t64; + t64 = *t; + t64 = (t64 + 11644473600)*10000000; + uTm.dwLowDateTime = (DWORD)(t64 & 0xFFFFFFFF); + uTm.dwHighDateTime= (DWORD)(t64 >> 32); + osFileTimeToLocalFileTime(&uTm,&lTm); + osFileTimeToSystemTime(&lTm,&pTm); + y.tm_year = pTm.wYear - 1900; + y.tm_mon = pTm.wMonth - 1; + y.tm_wday = pTm.wDayOfWeek; + y.tm_mday = pTm.wDay; + y.tm_hour = pTm.wHour; + y.tm_min = pTm.wMinute; + y.tm_sec = pTm.wSecond; + return &y; +} +#endif + +#if SQLITE_OS_WINCE +/************************************************************************* +** This section contains code for WinCE only. +*/ +#define HANDLE_TO_WINFILE(a) (winFile*)&((char*)a)[-(int)offsetof(winFile,h)] + +/* +** Acquire a lock on the handle h +*/ +static void winceMutexAcquire(HANDLE h){ + DWORD dwErr; + do { + dwErr = osWaitForSingleObject(h, INFINITE); + } while (dwErr != WAIT_OBJECT_0 && dwErr != WAIT_ABANDONED); +} +/* +** Release a lock acquired by winceMutexAcquire() +*/ +#define winceMutexRelease(h) ReleaseMutex(h) + +/* +** Create the mutex and shared memory used for locking in the file +** descriptor pFile +*/ +static int winceCreateLock(const char *zFilename, winFile *pFile){ + LPWSTR zTok; + LPWSTR zName; + DWORD lastErrno; + BOOL bLogged = FALSE; + BOOL bInit = TRUE; + + zName = winUtf8ToUnicode(zFilename); + if( zName==0 ){ + /* out of memory */ + return SQLITE_IOERR_NOMEM_BKPT; + } + + /* Initialize the local lockdata */ + memset(&pFile->local, 0, sizeof(pFile->local)); + + /* Replace the backslashes from the filename and lowercase it + ** to derive a mutex name. */ + zTok = osCharLowerW(zName); + for (;*zTok;zTok++){ + if (*zTok == '\\') *zTok = '_'; + } + + /* Create/open the named mutex */ + pFile->hMutex = osCreateMutexW(NULL, FALSE, zName); + if (!pFile->hMutex){ + pFile->lastErrno = osGetLastError(); + sqlite3_free(zName); + return winLogError(SQLITE_IOERR, pFile->lastErrno, + "winceCreateLock1", zFilename); + } + + /* Acquire the mutex before continuing */ + winceMutexAcquire(pFile->hMutex); + + /* Since the names of named mutexes, semaphores, file mappings etc are + ** case-sensitive, take advantage of that by uppercasing the mutex name + ** and using that as the shared filemapping name. + */ + osCharUpperW(zName); + pFile->hShared = osCreateFileMappingW(INVALID_HANDLE_VALUE, NULL, + PAGE_READWRITE, 0, sizeof(winceLock), + zName); + + /* Set a flag that indicates we're the first to create the memory so it + ** must be zero-initialized */ + lastErrno = osGetLastError(); + if (lastErrno == ERROR_ALREADY_EXISTS){ + bInit = FALSE; + } + + sqlite3_free(zName); + + /* If we succeeded in making the shared memory handle, map it. */ + if( pFile->hShared ){ + pFile->shared = (winceLock*)osMapViewOfFile(pFile->hShared, + FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, sizeof(winceLock)); + /* If mapping failed, close the shared memory handle and erase it */ + if( !pFile->shared ){ + pFile->lastErrno = osGetLastError(); + winLogError(SQLITE_IOERR, pFile->lastErrno, + "winceCreateLock2", zFilename); + bLogged = TRUE; + osCloseHandle(pFile->hShared); + pFile->hShared = NULL; + } + } + + /* If shared memory could not be created, then close the mutex and fail */ + if( pFile->hShared==NULL ){ + if( !bLogged ){ + pFile->lastErrno = lastErrno; + winLogError(SQLITE_IOERR, pFile->lastErrno, + "winceCreateLock3", zFilename); + bLogged = TRUE; + } + winceMutexRelease(pFile->hMutex); + osCloseHandle(pFile->hMutex); + pFile->hMutex = NULL; + return SQLITE_IOERR; + } + + /* Initialize the shared memory if we're supposed to */ + if( bInit ){ + memset(pFile->shared, 0, sizeof(winceLock)); + } + + winceMutexRelease(pFile->hMutex); + return SQLITE_OK; +} + +/* +** Destroy the part of winFile that deals with wince locks +*/ +static void winceDestroyLock(winFile *pFile){ + if (pFile->hMutex){ + /* Acquire the mutex */ + winceMutexAcquire(pFile->hMutex); + + /* The following blocks should probably assert in debug mode, but they + are to cleanup in case any locks remained open */ + if (pFile->local.nReaders){ + pFile->shared->nReaders --; + } + if (pFile->local.bReserved){ + pFile->shared->bReserved = FALSE; + } + if (pFile->local.bPending){ + pFile->shared->bPending = FALSE; + } + if (pFile->local.bExclusive){ + pFile->shared->bExclusive = FALSE; + } + + /* De-reference and close our copy of the shared memory handle */ + osUnmapViewOfFile(pFile->shared); + osCloseHandle(pFile->hShared); + + /* Done with the mutex */ + winceMutexRelease(pFile->hMutex); + osCloseHandle(pFile->hMutex); + pFile->hMutex = NULL; + } +} + +/* +** An implementation of the LockFile() API of Windows for CE +*/ +static BOOL winceLockFile( + LPHANDLE phFile, + DWORD dwFileOffsetLow, + DWORD dwFileOffsetHigh, + DWORD nNumberOfBytesToLockLow, + DWORD nNumberOfBytesToLockHigh +){ + winFile *pFile = HANDLE_TO_WINFILE(phFile); + BOOL bReturn = FALSE; + + UNUSED_PARAMETER(dwFileOffsetHigh); + UNUSED_PARAMETER(nNumberOfBytesToLockHigh); + + if (!pFile->hMutex) return TRUE; + winceMutexAcquire(pFile->hMutex); + + /* Wanting an exclusive lock? */ + if (dwFileOffsetLow == (DWORD)SHARED_FIRST + && nNumberOfBytesToLockLow == (DWORD)SHARED_SIZE){ + if (pFile->shared->nReaders == 0 && pFile->shared->bExclusive == 0){ + pFile->shared->bExclusive = TRUE; + pFile->local.bExclusive = TRUE; + bReturn = TRUE; + } + } + + /* Want a read-only lock? */ + else if (dwFileOffsetLow == (DWORD)SHARED_FIRST && + nNumberOfBytesToLockLow == 1){ + if (pFile->shared->bExclusive == 0){ + pFile->local.nReaders ++; + if (pFile->local.nReaders == 1){ + pFile->shared->nReaders ++; + } + bReturn = TRUE; + } + } + + /* Want a pending lock? */ + else if (dwFileOffsetLow == (DWORD)PENDING_BYTE + && nNumberOfBytesToLockLow == 1){ + /* If no pending lock has been acquired, then acquire it */ + if (pFile->shared->bPending == 0) { + pFile->shared->bPending = TRUE; + pFile->local.bPending = TRUE; + bReturn = TRUE; + } + } + + /* Want a reserved lock? */ + else if (dwFileOffsetLow == (DWORD)RESERVED_BYTE + && nNumberOfBytesToLockLow == 1){ + if (pFile->shared->bReserved == 0) { + pFile->shared->bReserved = TRUE; + pFile->local.bReserved = TRUE; + bReturn = TRUE; + } + } + + winceMutexRelease(pFile->hMutex); + return bReturn; +} + +/* +** An implementation of the UnlockFile API of Windows for CE +*/ +static BOOL winceUnlockFile( + LPHANDLE phFile, + DWORD dwFileOffsetLow, + DWORD dwFileOffsetHigh, + DWORD nNumberOfBytesToUnlockLow, + DWORD nNumberOfBytesToUnlockHigh +){ + winFile *pFile = HANDLE_TO_WINFILE(phFile); + BOOL bReturn = FALSE; + + UNUSED_PARAMETER(dwFileOffsetHigh); + UNUSED_PARAMETER(nNumberOfBytesToUnlockHigh); + + if (!pFile->hMutex) return TRUE; + winceMutexAcquire(pFile->hMutex); + + /* Releasing a reader lock or an exclusive lock */ + if (dwFileOffsetLow == (DWORD)SHARED_FIRST){ + /* Did we have an exclusive lock? */ + if (pFile->local.bExclusive){ + assert(nNumberOfBytesToUnlockLow == (DWORD)SHARED_SIZE); + pFile->local.bExclusive = FALSE; + pFile->shared->bExclusive = FALSE; + bReturn = TRUE; + } + + /* Did we just have a reader lock? */ + else if (pFile->local.nReaders){ + assert(nNumberOfBytesToUnlockLow == (DWORD)SHARED_SIZE + || nNumberOfBytesToUnlockLow == 1); + pFile->local.nReaders --; + if (pFile->local.nReaders == 0) + { + pFile->shared->nReaders --; + } + bReturn = TRUE; + } + } + + /* Releasing a pending lock */ + else if (dwFileOffsetLow == (DWORD)PENDING_BYTE + && nNumberOfBytesToUnlockLow == 1){ + if (pFile->local.bPending){ + pFile->local.bPending = FALSE; + pFile->shared->bPending = FALSE; + bReturn = TRUE; + } + } + /* Releasing a reserved lock */ + else if (dwFileOffsetLow == (DWORD)RESERVED_BYTE + && nNumberOfBytesToUnlockLow == 1){ + if (pFile->local.bReserved) { + pFile->local.bReserved = FALSE; + pFile->shared->bReserved = FALSE; + bReturn = TRUE; + } + } + + winceMutexRelease(pFile->hMutex); + return bReturn; +} +/* +** End of the special code for wince +*****************************************************************************/ +#endif /* SQLITE_OS_WINCE */ + +/* +** Lock a file region. +*/ +static BOOL winLockFile( + LPHANDLE phFile, + DWORD flags, + DWORD offsetLow, + DWORD offsetHigh, + DWORD numBytesLow, + DWORD numBytesHigh +){ +#if SQLITE_OS_WINCE + /* + ** NOTE: Windows CE is handled differently here due its lack of the Win32 + ** API LockFile. + */ + return winceLockFile(phFile, offsetLow, offsetHigh, + numBytesLow, numBytesHigh); +#else + if( osIsNT() ){ + OVERLAPPED ovlp; + memset(&ovlp, 0, sizeof(OVERLAPPED)); + ovlp.Offset = offsetLow; + ovlp.OffsetHigh = offsetHigh; + return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp); + }else{ + return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow, + numBytesHigh); + } +#endif +} + +/* +** Unlock a file region. + */ +static BOOL winUnlockFile( + LPHANDLE phFile, + DWORD offsetLow, + DWORD offsetHigh, + DWORD numBytesLow, + DWORD numBytesHigh +){ +#if SQLITE_OS_WINCE + /* + ** NOTE: Windows CE is handled differently here due its lack of the Win32 + ** API UnlockFile. + */ + return winceUnlockFile(phFile, offsetLow, offsetHigh, + numBytesLow, numBytesHigh); +#else + if( osIsNT() ){ + OVERLAPPED ovlp; + memset(&ovlp, 0, sizeof(OVERLAPPED)); + ovlp.Offset = offsetLow; + ovlp.OffsetHigh = offsetHigh; + return osUnlockFileEx(*phFile, 0, numBytesLow, numBytesHigh, &ovlp); + }else{ + return osUnlockFile(*phFile, offsetLow, offsetHigh, numBytesLow, + numBytesHigh); + } +#endif +} + +/***************************************************************************** +** The next group of routines implement the I/O methods specified +** by the sqlite3_io_methods object. +******************************************************************************/ + +/* +** Some Microsoft compilers lack this definition. +*/ +#ifndef INVALID_SET_FILE_POINTER +# define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif + +/* +** Move the current position of the file handle passed as the first +** argument to offset iOffset within the file. If successful, return 0. +** Otherwise, set pFile->lastErrno and return non-zero. +*/ +static int winSeekFile(winFile *pFile, sqlite3_int64 iOffset){ +#if !SQLITE_OS_WINRT + LONG upperBits; /* Most sig. 32 bits of new offset */ + LONG lowerBits; /* Least sig. 32 bits of new offset */ + DWORD dwRet; /* Value returned by SetFilePointer() */ + DWORD lastErrno; /* Value returned by GetLastError() */ + + OSTRACE(("SEEK file=%p, offset=%lld\n", pFile->h, iOffset)); + + upperBits = (LONG)((iOffset>>32) & 0x7fffffff); + lowerBits = (LONG)(iOffset & 0xffffffff); + + /* API oddity: If successful, SetFilePointer() returns a dword + ** containing the lower 32-bits of the new file-offset. Or, if it fails, + ** it returns INVALID_SET_FILE_POINTER. However according to MSDN, + ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine + ** whether an error has actually occurred, it is also necessary to call + ** GetLastError(). + */ + dwRet = osSetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN); + + if( (dwRet==INVALID_SET_FILE_POINTER + && ((lastErrno = osGetLastError())!=NO_ERROR)) ){ + pFile->lastErrno = lastErrno; + winLogError(SQLITE_IOERR_SEEK, pFile->lastErrno, + "winSeekFile", pFile->zPath); + OSTRACE(("SEEK file=%p, rc=SQLITE_IOERR_SEEK\n", pFile->h)); + return 1; + } + + OSTRACE(("SEEK file=%p, rc=SQLITE_OK\n", pFile->h)); + return 0; +#else + /* + ** Same as above, except that this implementation works for WinRT. + */ + + LARGE_INTEGER x; /* The new offset */ + BOOL bRet; /* Value returned by SetFilePointerEx() */ + + x.QuadPart = iOffset; + bRet = osSetFilePointerEx(pFile->h, x, 0, FILE_BEGIN); + + if(!bRet){ + pFile->lastErrno = osGetLastError(); + winLogError(SQLITE_IOERR_SEEK, pFile->lastErrno, + "winSeekFile", pFile->zPath); + OSTRACE(("SEEK file=%p, rc=SQLITE_IOERR_SEEK\n", pFile->h)); + return 1; + } + + OSTRACE(("SEEK file=%p, rc=SQLITE_OK\n", pFile->h)); + return 0; +#endif +} + +#if SQLITE_MAX_MMAP_SIZE>0 +/* Forward references to VFS helper methods used for memory mapped files */ +static int winMapfile(winFile*, sqlite3_int64); +static int winUnmapfile(winFile*); +#endif + +/* +** Close a file. +** +** It is reported that an attempt to close a handle might sometimes +** fail. This is a very unreasonable result, but Windows is notorious +** for being unreasonable so I do not doubt that it might happen. If +** the close fails, we pause for 100 milliseconds and try again. As +** many as MX_CLOSE_ATTEMPT attempts to close the handle are made before +** giving up and returning an error. +*/ +#define MX_CLOSE_ATTEMPT 3 +static int winClose(sqlite3_file *id){ + int rc, cnt = 0; + winFile *pFile = (winFile*)id; + + assert( id!=0 ); +#ifndef SQLITE_OMIT_WAL + assert( pFile->pShm==0 ); +#endif + assert( pFile->h!=NULL && pFile->h!=INVALID_HANDLE_VALUE ); + OSTRACE(("CLOSE pid=%lu, pFile=%p, file=%p\n", + osGetCurrentProcessId(), pFile, pFile->h)); + +#if SQLITE_MAX_MMAP_SIZE>0 + winUnmapfile(pFile); +#endif + + do{ + rc = osCloseHandle(pFile->h); + /* SimulateIOError( rc=0; cnt=MX_CLOSE_ATTEMPT; ); */ + }while( rc==0 && ++cnt < MX_CLOSE_ATTEMPT && (sqlite3_win32_sleep(100), 1) ); +#if SQLITE_OS_WINCE +#define WINCE_DELETION_ATTEMPTS 3 + { + winVfsAppData *pAppData = (winVfsAppData*)pFile->pVfs->pAppData; + if( pAppData==NULL || !pAppData->bNoLock ){ + winceDestroyLock(pFile); + } + } + if( pFile->zDeleteOnClose ){ + int cnt = 0; + while( + osDeleteFileW(pFile->zDeleteOnClose)==0 + && osGetFileAttributesW(pFile->zDeleteOnClose)!=0xffffffff + && cnt++ < WINCE_DELETION_ATTEMPTS + ){ + sqlite3_win32_sleep(100); /* Wait a little before trying again */ + } + sqlite3_free(pFile->zDeleteOnClose); + } +#endif + if( rc ){ + pFile->h = NULL; + } + OpenCounter(-1); + OSTRACE(("CLOSE pid=%lu, pFile=%p, file=%p, rc=%s\n", + osGetCurrentProcessId(), pFile, pFile->h, rc ? "ok" : "failed")); + return rc ? SQLITE_OK + : winLogError(SQLITE_IOERR_CLOSE, osGetLastError(), + "winClose", pFile->zPath); +} + +/* +** Read data from a file into a buffer. Return SQLITE_OK if all +** bytes were read successfully and SQLITE_IOERR if anything goes +** wrong. +*/ +static int winRead( + sqlite3_file *id, /* File to read from */ + void *pBuf, /* Write content into this buffer */ + int amt, /* Number of bytes to read */ + sqlite3_int64 offset /* Begin reading at this offset */ +){ +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) + OVERLAPPED overlapped; /* The offset for ReadFile. */ +#endif + winFile *pFile = (winFile*)id; /* file handle */ + DWORD nRead; /* Number of bytes actually read from file */ + int nRetry = 0; /* Number of retrys */ + + assert( id!=0 ); + assert( amt>0 ); + assert( offset>=0 ); + SimulateIOError(return SQLITE_IOERR_READ); + OSTRACE(("READ pid=%lu, pFile=%p, file=%p, buffer=%p, amount=%d, " + "offset=%lld, lock=%d\n", osGetCurrentProcessId(), pFile, + pFile->h, pBuf, amt, offset, pFile->locktype)); + +#if SQLITE_MAX_MMAP_SIZE>0 + /* Deal with as much of this read request as possible by transferring + ** data from the memory mapping using memcpy(). */ + if( offset<pFile->mmapSize ){ + if( offset+amt <= pFile->mmapSize ){ + memcpy(pBuf, &((u8 *)(pFile->pMapRegion))[offset], amt); + OSTRACE(("READ-MMAP pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return SQLITE_OK; + }else{ + int nCopy = (int)(pFile->mmapSize - offset); + memcpy(pBuf, &((u8 *)(pFile->pMapRegion))[offset], nCopy); + pBuf = &((u8 *)pBuf)[nCopy]; + amt -= nCopy; + offset += nCopy; + } + } +#endif + +#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED) + if( winSeekFile(pFile, offset) ){ + OSTRACE(("READ pid=%lu, pFile=%p, file=%p, rc=SQLITE_FULL\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return SQLITE_FULL; + } + while( !osReadFile(pFile->h, pBuf, amt, &nRead, 0) ){ +#else + memset(&overlapped, 0, sizeof(OVERLAPPED)); + overlapped.Offset = (LONG)(offset & 0xffffffff); + overlapped.OffsetHigh = (LONG)((offset>>32) & 0x7fffffff); + while( !osReadFile(pFile->h, pBuf, amt, &nRead, &overlapped) && + osGetLastError()!=ERROR_HANDLE_EOF ){ +#endif + DWORD lastErrno; + if( winRetryIoerr(&nRetry, &lastErrno) ) continue; + pFile->lastErrno = lastErrno; + OSTRACE(("READ pid=%lu, pFile=%p, file=%p, rc=SQLITE_IOERR_READ\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return winLogError(SQLITE_IOERR_READ, pFile->lastErrno, + "winRead", pFile->zPath); + } + winLogIoerr(nRetry, __LINE__); + if( nRead<(DWORD)amt ){ + /* Unread parts of the buffer must be zero-filled */ + memset(&((char*)pBuf)[nRead], 0, amt-nRead); + OSTRACE(("READ pid=%lu, pFile=%p, file=%p, rc=SQLITE_IOERR_SHORT_READ\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return SQLITE_IOERR_SHORT_READ; + } + + OSTRACE(("READ pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return SQLITE_OK; +} + +/* +** Write data from a buffer into a file. Return SQLITE_OK on success +** or some other error code on failure. +*/ +static int winWrite( + sqlite3_file *id, /* File to write into */ + const void *pBuf, /* The bytes to be written */ + int amt, /* Number of bytes to write */ + sqlite3_int64 offset /* Offset into the file to begin writing at */ +){ + int rc = 0; /* True if error has occurred, else false */ + winFile *pFile = (winFile*)id; /* File handle */ + int nRetry = 0; /* Number of retries */ + + assert( amt>0 ); + assert( pFile ); + SimulateIOError(return SQLITE_IOERR_WRITE); + SimulateDiskfullError(return SQLITE_FULL); + + OSTRACE(("WRITE pid=%lu, pFile=%p, file=%p, buffer=%p, amount=%d, " + "offset=%lld, lock=%d\n", osGetCurrentProcessId(), pFile, + pFile->h, pBuf, amt, offset, pFile->locktype)); + +#if defined(SQLITE_MMAP_READWRITE) && SQLITE_MAX_MMAP_SIZE>0 + /* Deal with as much of this write request as possible by transferring + ** data from the memory mapping using memcpy(). */ + if( offset<pFile->mmapSize ){ + if( offset+amt <= pFile->mmapSize ){ + memcpy(&((u8 *)(pFile->pMapRegion))[offset], pBuf, amt); + OSTRACE(("WRITE-MMAP pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return SQLITE_OK; + }else{ + int nCopy = (int)(pFile->mmapSize - offset); + memcpy(&((u8 *)(pFile->pMapRegion))[offset], pBuf, nCopy); + pBuf = &((u8 *)pBuf)[nCopy]; + amt -= nCopy; + offset += nCopy; + } + } +#endif + +#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED) + rc = winSeekFile(pFile, offset); + if( rc==0 ){ +#else + { +#endif +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) + OVERLAPPED overlapped; /* The offset for WriteFile. */ +#endif + u8 *aRem = (u8 *)pBuf; /* Data yet to be written */ + int nRem = amt; /* Number of bytes yet to be written */ + DWORD nWrite; /* Bytes written by each WriteFile() call */ + DWORD lastErrno = NO_ERROR; /* Value returned by GetLastError() */ + +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) + memset(&overlapped, 0, sizeof(OVERLAPPED)); + overlapped.Offset = (LONG)(offset & 0xffffffff); + overlapped.OffsetHigh = (LONG)((offset>>32) & 0x7fffffff); +#endif + + while( nRem>0 ){ +#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED) + if( !osWriteFile(pFile->h, aRem, nRem, &nWrite, 0) ){ +#else + if( !osWriteFile(pFile->h, aRem, nRem, &nWrite, &overlapped) ){ +#endif + if( winRetryIoerr(&nRetry, &lastErrno) ) continue; + break; + } + assert( nWrite==0 || nWrite<=(DWORD)nRem ); + if( nWrite==0 || nWrite>(DWORD)nRem ){ + lastErrno = osGetLastError(); + break; + } +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) + offset += nWrite; + overlapped.Offset = (LONG)(offset & 0xffffffff); + overlapped.OffsetHigh = (LONG)((offset>>32) & 0x7fffffff); +#endif + aRem += nWrite; + nRem -= nWrite; + } + if( nRem>0 ){ + pFile->lastErrno = lastErrno; + rc = 1; + } + } + + if( rc ){ + if( ( pFile->lastErrno==ERROR_HANDLE_DISK_FULL ) + || ( pFile->lastErrno==ERROR_DISK_FULL )){ + OSTRACE(("WRITE pid=%lu, pFile=%p, file=%p, rc=SQLITE_FULL\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return winLogError(SQLITE_FULL, pFile->lastErrno, + "winWrite1", pFile->zPath); + } + OSTRACE(("WRITE pid=%lu, pFile=%p, file=%p, rc=SQLITE_IOERR_WRITE\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return winLogError(SQLITE_IOERR_WRITE, pFile->lastErrno, + "winWrite2", pFile->zPath); + }else{ + winLogIoerr(nRetry, __LINE__); + } + OSTRACE(("WRITE pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return SQLITE_OK; +} + +/* +** Truncate an open file to a specified size +*/ +static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){ + winFile *pFile = (winFile*)id; /* File handle object */ + int rc = SQLITE_OK; /* Return code for this function */ + DWORD lastErrno; +#if SQLITE_MAX_MMAP_SIZE>0 + sqlite3_int64 oldMmapSize; + if( pFile->nFetchOut>0 ){ + /* File truncation is a no-op if there are outstanding memory mapped + ** pages. This is because truncating the file means temporarily unmapping + ** the file, and that might delete memory out from under existing cursors. + ** + ** This can result in incremental vacuum not truncating the file, + ** if there is an active read cursor when the incremental vacuum occurs. + ** No real harm comes of this - the database file is not corrupted, + ** though some folks might complain that the file is bigger than it + ** needs to be. + ** + ** The only feasible work-around is to defer the truncation until after + ** all references to memory-mapped content are closed. That is doable, + ** but involves adding a few branches in the common write code path which + ** could slow down normal operations slightly. Hence, we have decided for + ** now to simply make transactions a no-op if there are pending reads. We + ** can maybe revisit this decision in the future. + */ + return SQLITE_OK; + } +#endif + + assert( pFile ); + SimulateIOError(return SQLITE_IOERR_TRUNCATE); + OSTRACE(("TRUNCATE pid=%lu, pFile=%p, file=%p, size=%lld, lock=%d\n", + osGetCurrentProcessId(), pFile, pFile->h, nByte, pFile->locktype)); + + /* If the user has configured a chunk-size for this file, truncate the + ** file so that it consists of an integer number of chunks (i.e. the + ** actual file size after the operation may be larger than the requested + ** size). + */ + if( pFile->szChunk>0 ){ + nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk; + } + +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFile->pMapRegion ){ + oldMmapSize = pFile->mmapSize; + }else{ + oldMmapSize = 0; + } + winUnmapfile(pFile); +#endif + + /* SetEndOfFile() returns non-zero when successful, or zero when it fails. */ + if( winSeekFile(pFile, nByte) ){ + rc = winLogError(SQLITE_IOERR_TRUNCATE, pFile->lastErrno, + "winTruncate1", pFile->zPath); + }else if( 0==osSetEndOfFile(pFile->h) && + ((lastErrno = osGetLastError())!=ERROR_USER_MAPPED_FILE) ){ + pFile->lastErrno = lastErrno; + rc = winLogError(SQLITE_IOERR_TRUNCATE, pFile->lastErrno, + "winTruncate2", pFile->zPath); + } + +#if SQLITE_MAX_MMAP_SIZE>0 + if( rc==SQLITE_OK && oldMmapSize>0 ){ + if( oldMmapSize>nByte ){ + winMapfile(pFile, -1); + }else{ + winMapfile(pFile, oldMmapSize); + } + } +#endif + + OSTRACE(("TRUNCATE pid=%lu, pFile=%p, file=%p, rc=%s\n", + osGetCurrentProcessId(), pFile, pFile->h, sqlite3ErrName(rc))); + return rc; +} + +#ifdef SQLITE_TEST +/* +** Count the number of fullsyncs and normal syncs. This is used to test +** that syncs and fullsyncs are occurring at the right times. +*/ +SQLITE_API int sqlite3_sync_count = 0; +SQLITE_API int sqlite3_fullsync_count = 0; +#endif + +/* +** Make sure all writes to a particular file are committed to disk. +*/ +static int winSync(sqlite3_file *id, int flags){ +#ifndef SQLITE_NO_SYNC + /* + ** Used only when SQLITE_NO_SYNC is not defined. + */ + BOOL rc; +#endif +#if !defined(NDEBUG) || !defined(SQLITE_NO_SYNC) || \ + defined(SQLITE_HAVE_OS_TRACE) + /* + ** Used when SQLITE_NO_SYNC is not defined and by the assert() and/or + ** OSTRACE() macros. + */ + winFile *pFile = (winFile*)id; +#else + UNUSED_PARAMETER(id); +#endif + + assert( pFile ); + /* Check that one of SQLITE_SYNC_NORMAL or FULL was passed */ + assert((flags&0x0F)==SQLITE_SYNC_NORMAL + || (flags&0x0F)==SQLITE_SYNC_FULL + ); + + /* Unix cannot, but some systems may return SQLITE_FULL from here. This + ** line is to test that doing so does not cause any problems. + */ + SimulateDiskfullError( return SQLITE_FULL ); + + OSTRACE(("SYNC pid=%lu, pFile=%p, file=%p, flags=%x, lock=%d\n", + osGetCurrentProcessId(), pFile, pFile->h, flags, + pFile->locktype)); + +#ifndef SQLITE_TEST + UNUSED_PARAMETER(flags); +#else + if( (flags&0x0F)==SQLITE_SYNC_FULL ){ + sqlite3_fullsync_count++; + } + sqlite3_sync_count++; +#endif + + /* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a + ** no-op + */ +#ifdef SQLITE_NO_SYNC + OSTRACE(("SYNC-NOP pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return SQLITE_OK; +#else +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFile->pMapRegion ){ + if( osFlushViewOfFile(pFile->pMapRegion, 0) ){ + OSTRACE(("SYNC-MMAP pid=%lu, pFile=%p, pMapRegion=%p, " + "rc=SQLITE_OK\n", osGetCurrentProcessId(), + pFile, pFile->pMapRegion)); + }else{ + pFile->lastErrno = osGetLastError(); + OSTRACE(("SYNC-MMAP pid=%lu, pFile=%p, pMapRegion=%p, " + "rc=SQLITE_IOERR_MMAP\n", osGetCurrentProcessId(), + pFile, pFile->pMapRegion)); + return winLogError(SQLITE_IOERR_MMAP, pFile->lastErrno, + "winSync1", pFile->zPath); + } + } +#endif + rc = osFlushFileBuffers(pFile->h); + SimulateIOError( rc=FALSE ); + if( rc ){ + OSTRACE(("SYNC pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return SQLITE_OK; + }else{ + pFile->lastErrno = osGetLastError(); + OSTRACE(("SYNC pid=%lu, pFile=%p, file=%p, rc=SQLITE_IOERR_FSYNC\n", + osGetCurrentProcessId(), pFile, pFile->h)); + return winLogError(SQLITE_IOERR_FSYNC, pFile->lastErrno, + "winSync2", pFile->zPath); + } +#endif +} + +/* +** Determine the current size of a file in bytes +*/ +static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){ + winFile *pFile = (winFile*)id; + int rc = SQLITE_OK; + + assert( id!=0 ); + assert( pSize!=0 ); + SimulateIOError(return SQLITE_IOERR_FSTAT); + OSTRACE(("SIZE file=%p, pSize=%p\n", pFile->h, pSize)); + +#if SQLITE_OS_WINRT + { + FILE_STANDARD_INFO info; + if( osGetFileInformationByHandleEx(pFile->h, FileStandardInfo, + &info, sizeof(info)) ){ + *pSize = info.EndOfFile.QuadPart; + }else{ + pFile->lastErrno = osGetLastError(); + rc = winLogError(SQLITE_IOERR_FSTAT, pFile->lastErrno, + "winFileSize", pFile->zPath); + } + } +#else + { + DWORD upperBits; + DWORD lowerBits; + DWORD lastErrno; + + lowerBits = osGetFileSize(pFile->h, &upperBits); + *pSize = (((sqlite3_int64)upperBits)<<32) + lowerBits; + if( (lowerBits == INVALID_FILE_SIZE) + && ((lastErrno = osGetLastError())!=NO_ERROR) ){ + pFile->lastErrno = lastErrno; + rc = winLogError(SQLITE_IOERR_FSTAT, pFile->lastErrno, + "winFileSize", pFile->zPath); + } + } +#endif + OSTRACE(("SIZE file=%p, pSize=%p, *pSize=%lld, rc=%s\n", + pFile->h, pSize, *pSize, sqlite3ErrName(rc))); + return rc; +} + +/* +** LOCKFILE_FAIL_IMMEDIATELY is undefined on some Windows systems. +*/ +#ifndef LOCKFILE_FAIL_IMMEDIATELY +# define LOCKFILE_FAIL_IMMEDIATELY 1 +#endif + +#ifndef LOCKFILE_EXCLUSIVE_LOCK +# define LOCKFILE_EXCLUSIVE_LOCK 2 +#endif + +/* +** Historically, SQLite has used both the LockFile and LockFileEx functions. +** When the LockFile function was used, it was always expected to fail +** immediately if the lock could not be obtained. Also, it always expected to +** obtain an exclusive lock. These flags are used with the LockFileEx function +** and reflect those expectations; therefore, they should not be changed. +*/ +#ifndef SQLITE_LOCKFILE_FLAGS +# define SQLITE_LOCKFILE_FLAGS (LOCKFILE_FAIL_IMMEDIATELY | \ + LOCKFILE_EXCLUSIVE_LOCK) +#endif + +/* +** Currently, SQLite never calls the LockFileEx function without wanting the +** call to fail immediately if the lock cannot be obtained. +*/ +#ifndef SQLITE_LOCKFILEEX_FLAGS +# define SQLITE_LOCKFILEEX_FLAGS (LOCKFILE_FAIL_IMMEDIATELY) +#endif + +/* +** Acquire a reader lock. +** Different API routines are called depending on whether or not this +** is Win9x or WinNT. +*/ +static int winGetReadLock(winFile *pFile){ + int res; + OSTRACE(("READ-LOCK file=%p, lock=%d\n", pFile->h, pFile->locktype)); + if( osIsNT() ){ +#if SQLITE_OS_WINCE + /* + ** NOTE: Windows CE is handled differently here due its lack of the Win32 + ** API LockFileEx. + */ + res = winceLockFile(&pFile->h, SHARED_FIRST, 0, 1, 0); +#else + res = winLockFile(&pFile->h, SQLITE_LOCKFILEEX_FLAGS, SHARED_FIRST, 0, + SHARED_SIZE, 0); +#endif + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + int lk; + sqlite3_randomness(sizeof(lk), &lk); + pFile->sharedLockByte = (short)((lk & 0x7fffffff)%(SHARED_SIZE - 1)); + res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, + SHARED_FIRST+pFile->sharedLockByte, 0, 1, 0); + } +#endif + if( res == 0 ){ + pFile->lastErrno = osGetLastError(); + /* No need to log a failure to lock */ + } + OSTRACE(("READ-LOCK file=%p, result=%d\n", pFile->h, res)); + return res; +} + +/* +** Undo a readlock +*/ +static int winUnlockReadLock(winFile *pFile){ + int res; + DWORD lastErrno; + OSTRACE(("READ-UNLOCK file=%p, lock=%d\n", pFile->h, pFile->locktype)); + if( osIsNT() ){ + res = winUnlockFile(&pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + res = winUnlockFile(&pFile->h, SHARED_FIRST+pFile->sharedLockByte, 0, 1, 0); + } +#endif + if( res==0 && ((lastErrno = osGetLastError())!=ERROR_NOT_LOCKED) ){ + pFile->lastErrno = lastErrno; + winLogError(SQLITE_IOERR_UNLOCK, pFile->lastErrno, + "winUnlockReadLock", pFile->zPath); + } + OSTRACE(("READ-UNLOCK file=%p, result=%d\n", pFile->h, res)); + return res; +} + +/* +** Lock the file with the lock specified by parameter locktype - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. The winUnlock() routine +** erases all locks at once and returns us immediately to locking level 0. +** It is not possible to lower the locking level one step at a time. You +** must go straight to locking level 0. +*/ +static int winLock(sqlite3_file *id, int locktype){ + int rc = SQLITE_OK; /* Return code from subroutines */ + int res = 1; /* Result of a Windows lock call */ + int newLocktype; /* Set pFile->locktype to this value before exiting */ + int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */ + winFile *pFile = (winFile*)id; + DWORD lastErrno = NO_ERROR; + + assert( id!=0 ); + OSTRACE(("LOCK file=%p, oldLock=%d(%d), newLock=%d\n", + pFile->h, pFile->locktype, pFile->sharedLockByte, locktype)); + + /* If there is already a lock of this type or more restrictive on the + ** OsFile, do nothing. Don't use the end_lock: exit path, as + ** sqlite3OsEnterMutex() hasn't been called yet. + */ + if( pFile->locktype>=locktype ){ + OSTRACE(("LOCK-HELD file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } + + /* Do not allow any kind of write-lock on a read-only database + */ + if( (pFile->ctrlFlags & WINFILE_RDONLY)!=0 && locktype>=RESERVED_LOCK ){ + return SQLITE_IOERR_LOCK; + } + + /* Make sure the locking sequence is correct + */ + assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK ); + assert( locktype!=PENDING_LOCK ); + assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK ); + + /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or + ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of + ** the PENDING_LOCK byte is temporary. + */ + newLocktype = pFile->locktype; + if( pFile->locktype==NO_LOCK + || (locktype==EXCLUSIVE_LOCK && pFile->locktype<=RESERVED_LOCK) + ){ + int cnt = 3; + while( cnt-->0 && (res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, + PENDING_BYTE, 0, 1, 0))==0 ){ + /* Try 3 times to get the pending lock. This is needed to work + ** around problems caused by indexing and/or anti-virus software on + ** Windows systems. + ** If you are using this code as a model for alternative VFSes, do not + ** copy this retry logic. It is a hack intended for Windows only. + */ + lastErrno = osGetLastError(); + OSTRACE(("LOCK-PENDING-FAIL file=%p, count=%d, result=%d\n", + pFile->h, cnt, res)); + if( lastErrno==ERROR_INVALID_HANDLE ){ + pFile->lastErrno = lastErrno; + rc = SQLITE_IOERR_LOCK; + OSTRACE(("LOCK-FAIL file=%p, count=%d, rc=%s\n", + pFile->h, cnt, sqlite3ErrName(rc))); + return rc; + } + if( cnt ) sqlite3_win32_sleep(1); + } + gotPendingLock = res; + if( !res ){ + lastErrno = osGetLastError(); + } + } + + /* Acquire a shared lock + */ + if( locktype==SHARED_LOCK && res ){ + assert( pFile->locktype==NO_LOCK ); + res = winGetReadLock(pFile); + if( res ){ + newLocktype = SHARED_LOCK; + }else{ + lastErrno = osGetLastError(); + } + } + + /* Acquire a RESERVED lock + */ + if( locktype==RESERVED_LOCK && res ){ + assert( pFile->locktype==SHARED_LOCK ); + res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, RESERVED_BYTE, 0, 1, 0); + if( res ){ + newLocktype = RESERVED_LOCK; + }else{ + lastErrno = osGetLastError(); + } + } + + /* Acquire a PENDING lock + */ + if( locktype==EXCLUSIVE_LOCK && res ){ + newLocktype = PENDING_LOCK; + gotPendingLock = 0; + } + + /* Acquire an EXCLUSIVE lock + */ + if( locktype==EXCLUSIVE_LOCK && res ){ + assert( pFile->locktype>=SHARED_LOCK ); + (void)winUnlockReadLock(pFile); + res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, SHARED_FIRST, 0, + SHARED_SIZE, 0); + if( res ){ + newLocktype = EXCLUSIVE_LOCK; + }else{ + lastErrno = osGetLastError(); + winGetReadLock(pFile); + } + } + + /* If we are holding a PENDING lock that ought to be released, then + ** release it now. + */ + if( gotPendingLock && locktype==SHARED_LOCK ){ + winUnlockFile(&pFile->h, PENDING_BYTE, 0, 1, 0); + } + + /* Update the state of the lock has held in the file descriptor then + ** return the appropriate result code. + */ + if( res ){ + rc = SQLITE_OK; + }else{ + pFile->lastErrno = lastErrno; + rc = SQLITE_BUSY; + OSTRACE(("LOCK-FAIL file=%p, wanted=%d, got=%d\n", + pFile->h, locktype, newLocktype)); + } + pFile->locktype = (u8)newLocktype; + OSTRACE(("LOCK file=%p, lock=%d, rc=%s\n", + pFile->h, pFile->locktype, sqlite3ErrName(rc))); + return rc; +} + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, return +** non-zero, otherwise zero. +*/ +static int winCheckReservedLock(sqlite3_file *id, int *pResOut){ + int res; + winFile *pFile = (winFile*)id; + + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); + OSTRACE(("TEST-WR-LOCK file=%p, pResOut=%p\n", pFile->h, pResOut)); + + assert( id!=0 ); + if( pFile->locktype>=RESERVED_LOCK ){ + res = 1; + OSTRACE(("TEST-WR-LOCK file=%p, result=%d (local)\n", pFile->h, res)); + }else{ + res = winLockFile(&pFile->h, SQLITE_LOCKFILEEX_FLAGS,RESERVED_BYTE,0,1,0); + if( res ){ + winUnlockFile(&pFile->h, RESERVED_BYTE, 0, 1, 0); + } + res = !res; + OSTRACE(("TEST-WR-LOCK file=%p, result=%d (remote)\n", pFile->h, res)); + } + *pResOut = res; + OSTRACE(("TEST-WR-LOCK file=%p, pResOut=%p, *pResOut=%d, rc=SQLITE_OK\n", + pFile->h, pResOut, *pResOut)); + return SQLITE_OK; +} + +/* +** Lower the locking level on file descriptor id to locktype. locktype +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +** +** It is not possible for this routine to fail if the second argument +** is NO_LOCK. If the second argument is SHARED_LOCK then this routine +** might return SQLITE_IOERR; +*/ +static int winUnlock(sqlite3_file *id, int locktype){ + int type; + winFile *pFile = (winFile*)id; + int rc = SQLITE_OK; + assert( pFile!=0 ); + assert( locktype<=SHARED_LOCK ); + OSTRACE(("UNLOCK file=%p, oldLock=%d(%d), newLock=%d\n", + pFile->h, pFile->locktype, pFile->sharedLockByte, locktype)); + type = pFile->locktype; + if( type>=EXCLUSIVE_LOCK ){ + winUnlockFile(&pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); + if( locktype==SHARED_LOCK && !winGetReadLock(pFile) ){ + /* This should never happen. We should always be able to + ** reacquire the read lock */ + rc = winLogError(SQLITE_IOERR_UNLOCK, osGetLastError(), + "winUnlock", pFile->zPath); + } + } + if( type>=RESERVED_LOCK ){ + winUnlockFile(&pFile->h, RESERVED_BYTE, 0, 1, 0); + } + if( locktype==NO_LOCK && type>=SHARED_LOCK ){ + winUnlockReadLock(pFile); + } + if( type>=PENDING_LOCK ){ + winUnlockFile(&pFile->h, PENDING_BYTE, 0, 1, 0); + } + pFile->locktype = (u8)locktype; + OSTRACE(("UNLOCK file=%p, lock=%d, rc=%s\n", + pFile->h, pFile->locktype, sqlite3ErrName(rc))); + return rc; +} + +/****************************************************************************** +****************************** No-op Locking ********************************** +** +** Of the various locking implementations available, this is by far the +** simplest: locking is ignored. No attempt is made to lock the database +** file for reading or writing. +** +** This locking mode is appropriate for use on read-only databases +** (ex: databases that are burned into CD-ROM, for example.) It can +** also be used if the application employs some external mechanism to +** prevent simultaneous access of the same database by two or more +** database connections. But there is a serious risk of database +** corruption if this locking mode is used in situations where multiple +** database connections are accessing the same database file at the same +** time and one or more of those connections are writing. +*/ + +static int winNolockLock(sqlite3_file *id, int locktype){ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(locktype); + return SQLITE_OK; +} + +static int winNolockCheckReservedLock(sqlite3_file *id, int *pResOut){ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(pResOut); + return SQLITE_OK; +} + +static int winNolockUnlock(sqlite3_file *id, int locktype){ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(locktype); + return SQLITE_OK; +} + +/******************* End of the no-op lock implementation ********************* +******************************************************************************/ + +/* +** If *pArg is initially negative then this is a query. Set *pArg to +** 1 or 0 depending on whether or not bit mask of pFile->ctrlFlags is set. +** +** If *pArg is 0 or 1, then clear or set the mask bit of pFile->ctrlFlags. +*/ +static void winModeBit(winFile *pFile, unsigned char mask, int *pArg){ + if( *pArg<0 ){ + *pArg = (pFile->ctrlFlags & mask)!=0; + }else if( (*pArg)==0 ){ + pFile->ctrlFlags &= ~mask; + }else{ + pFile->ctrlFlags |= mask; + } +} + +/* Forward references to VFS helper methods used for temporary files */ +static int winGetTempname(sqlite3_vfs *, char **); +static int winIsDir(const void *); +static BOOL winIsLongPathPrefix(const char *); +static BOOL winIsDriveLetterAndColon(const char *); + +/* +** Control and query of the open file handle. +*/ +static int winFileControl(sqlite3_file *id, int op, void *pArg){ + winFile *pFile = (winFile*)id; + OSTRACE(("FCNTL file=%p, op=%d, pArg=%p\n", pFile->h, op, pArg)); + switch( op ){ + case SQLITE_FCNTL_LOCKSTATE: { + *(int*)pArg = pFile->locktype; + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } + case SQLITE_FCNTL_LAST_ERRNO: { + *(int*)pArg = (int)pFile->lastErrno; + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } + case SQLITE_FCNTL_CHUNK_SIZE: { + pFile->szChunk = *(int *)pArg; + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } + case SQLITE_FCNTL_SIZE_HINT: { + if( pFile->szChunk>0 ){ + sqlite3_int64 oldSz; + int rc = winFileSize(id, &oldSz); + if( rc==SQLITE_OK ){ + sqlite3_int64 newSz = *(sqlite3_int64*)pArg; + if( newSz>oldSz ){ + SimulateIOErrorBenign(1); + rc = winTruncate(id, newSz); + SimulateIOErrorBenign(0); + } + } + OSTRACE(("FCNTL file=%p, rc=%s\n", pFile->h, sqlite3ErrName(rc))); + return rc; + } + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } + case SQLITE_FCNTL_PERSIST_WAL: { + winModeBit(pFile, WINFILE_PERSIST_WAL, (int*)pArg); + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } + case SQLITE_FCNTL_POWERSAFE_OVERWRITE: { + winModeBit(pFile, WINFILE_PSOW, (int*)pArg); + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } + case SQLITE_FCNTL_VFSNAME: { + *(char**)pArg = sqlite3_mprintf("%s", pFile->pVfs->zName); + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } + case SQLITE_FCNTL_WIN32_AV_RETRY: { + int *a = (int*)pArg; + if( a[0]>0 ){ + winIoerrRetry = a[0]; + }else{ + a[0] = winIoerrRetry; + } + if( a[1]>0 ){ + winIoerrRetryDelay = a[1]; + }else{ + a[1] = winIoerrRetryDelay; + } + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } + case SQLITE_FCNTL_WIN32_GET_HANDLE: { + LPHANDLE phFile = (LPHANDLE)pArg; + *phFile = pFile->h; + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } +#ifdef SQLITE_TEST + case SQLITE_FCNTL_WIN32_SET_HANDLE: { + LPHANDLE phFile = (LPHANDLE)pArg; + HANDLE hOldFile = pFile->h; + pFile->h = *phFile; + *phFile = hOldFile; + OSTRACE(("FCNTL oldFile=%p, newFile=%p, rc=SQLITE_OK\n", + hOldFile, pFile->h)); + return SQLITE_OK; + } +#endif + case SQLITE_FCNTL_TEMPFILENAME: { + char *zTFile = 0; + int rc = winGetTempname(pFile->pVfs, &zTFile); + if( rc==SQLITE_OK ){ + *(char**)pArg = zTFile; + } + OSTRACE(("FCNTL file=%p, rc=%s\n", pFile->h, sqlite3ErrName(rc))); + return rc; + } +#if SQLITE_MAX_MMAP_SIZE>0 + case SQLITE_FCNTL_MMAP_SIZE: { + i64 newLimit = *(i64*)pArg; + int rc = SQLITE_OK; + if( newLimit>sqlite3GlobalConfig.mxMmap ){ + newLimit = sqlite3GlobalConfig.mxMmap; + } + + /* The value of newLimit may be eventually cast to (SIZE_T) and passed + ** to MapViewOfFile(). Restrict its value to 2GB if (SIZE_T) is not at + ** least a 64-bit type. */ + if( newLimit>0 && sizeof(SIZE_T)<8 ){ + newLimit = (newLimit & 0x7FFFFFFF); + } + + *(i64*)pArg = pFile->mmapSizeMax; + if( newLimit>=0 && newLimit!=pFile->mmapSizeMax && pFile->nFetchOut==0 ){ + pFile->mmapSizeMax = newLimit; + if( pFile->mmapSize>0 ){ + winUnmapfile(pFile); + rc = winMapfile(pFile, -1); + } + } + OSTRACE(("FCNTL file=%p, rc=%s\n", pFile->h, sqlite3ErrName(rc))); + return rc; + } +#endif + } + OSTRACE(("FCNTL file=%p, rc=SQLITE_NOTFOUND\n", pFile->h)); + return SQLITE_NOTFOUND; +} + +/* +** Return the sector size in bytes of the underlying block device for +** the specified file. This is almost always 512 bytes, but may be +** larger for some devices. +** +** SQLite code assumes this function cannot fail. It also assumes that +** if two files are created in the same file-system directory (i.e. +** a database and its journal file) that the sector size will be the +** same for both. +*/ +static int winSectorSize(sqlite3_file *id){ + (void)id; + return SQLITE_DEFAULT_SECTOR_SIZE; +} + +/* +** Return a vector of device characteristics. +*/ +static int winDeviceCharacteristics(sqlite3_file *id){ + winFile *p = (winFile*)id; + return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | + ((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0); +} + +/* +** Windows will only let you create file view mappings +** on allocation size granularity boundaries. +** During sqlite3_os_init() we do a GetSystemInfo() +** to get the granularity size. +*/ +static SYSTEM_INFO winSysInfo; + +#ifndef SQLITE_OMIT_WAL + +/* +** Helper functions to obtain and relinquish the global mutex. The +** global mutex is used to protect the winLockInfo objects used by +** this file, all of which may be shared by multiple threads. +** +** Function winShmMutexHeld() is used to assert() that the global mutex +** is held when required. This function is only used as part of assert() +** statements. e.g. +** +** winShmEnterMutex() +** assert( winShmMutexHeld() ); +** winShmLeaveMutex() +*/ +static sqlite3_mutex *winBigLock = 0; +static void winShmEnterMutex(void){ + sqlite3_mutex_enter(winBigLock); +} +static void winShmLeaveMutex(void){ + sqlite3_mutex_leave(winBigLock); +} +#ifndef NDEBUG +static int winShmMutexHeld(void) { + return sqlite3_mutex_held(winBigLock); +} +#endif + +/* +** Object used to represent a single file opened and mmapped to provide +** shared memory. When multiple threads all reference the same +** log-summary, each thread has its own winFile object, but they all +** point to a single instance of this object. In other words, each +** log-summary is opened only once per process. +** +** winShmMutexHeld() must be true when creating or destroying +** this object or while reading or writing the following fields: +** +** nRef +** pNext +** +** The following fields are read-only after the object is created: +** +** fid +** zFilename +** +** Either winShmNode.mutex must be held or winShmNode.nRef==0 and +** winShmMutexHeld() is true when reading or writing any other field +** in this structure. +** +*/ +struct winShmNode { + sqlite3_mutex *mutex; /* Mutex to access this object */ + char *zFilename; /* Name of the file */ + winFile hFile; /* File handle from winOpen */ + + int szRegion; /* Size of shared-memory regions */ + int nRegion; /* Size of array apRegion */ + u8 isReadonly; /* True if read-only */ + u8 isUnlocked; /* True if no DMS lock held */ + + struct ShmRegion { + HANDLE hMap; /* File handle from CreateFileMapping */ + void *pMap; + } *aRegion; + DWORD lastErrno; /* The Windows errno from the last I/O error */ + + int nRef; /* Number of winShm objects pointing to this */ + winShm *pFirst; /* All winShm objects pointing to this */ + winShmNode *pNext; /* Next in list of all winShmNode objects */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) + u8 nextShmId; /* Next available winShm.id value */ +#endif +}; + +/* +** A global array of all winShmNode objects. +** +** The winShmMutexHeld() must be true while reading or writing this list. +*/ +static winShmNode *winShmNodeList = 0; + +/* +** Structure used internally by this VFS to record the state of an +** open shared memory connection. +** +** The following fields are initialized when this object is created and +** are read-only thereafter: +** +** winShm.pShmNode +** winShm.id +** +** All other fields are read/write. The winShm.pShmNode->mutex must be held +** while accessing any read/write fields. +*/ +struct winShm { + winShmNode *pShmNode; /* The underlying winShmNode object */ + winShm *pNext; /* Next winShm with the same winShmNode */ + u8 hasMutex; /* True if holding the winShmNode mutex */ + u16 sharedMask; /* Mask of shared locks held */ + u16 exclMask; /* Mask of exclusive locks held */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) + u8 id; /* Id of this connection with its winShmNode */ +#endif +}; + +/* +** Constants used for locking +*/ +#define WIN_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ +#define WIN_SHM_DMS (WIN_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ + +/* +** Apply advisory locks for all n bytes beginning at ofst. +*/ +#define WINSHM_UNLCK 1 +#define WINSHM_RDLCK 2 +#define WINSHM_WRLCK 3 +static int winShmSystemLock( + winShmNode *pFile, /* Apply locks to this open shared-memory segment */ + int lockType, /* WINSHM_UNLCK, WINSHM_RDLCK, or WINSHM_WRLCK */ + int ofst, /* Offset to first byte to be locked/unlocked */ + int nByte /* Number of bytes to lock or unlock */ +){ + int rc = 0; /* Result code form Lock/UnlockFileEx() */ + + /* Access to the winShmNode object is serialized by the caller */ + assert( pFile->nRef==0 || sqlite3_mutex_held(pFile->mutex) ); + + OSTRACE(("SHM-LOCK file=%p, lock=%d, offset=%d, size=%d\n", + pFile->hFile.h, lockType, ofst, nByte)); + + /* Release/Acquire the system-level lock */ + if( lockType==WINSHM_UNLCK ){ + rc = winUnlockFile(&pFile->hFile.h, ofst, 0, nByte, 0); + }else{ + /* Initialize the locking parameters */ + DWORD dwFlags = LOCKFILE_FAIL_IMMEDIATELY; + if( lockType == WINSHM_WRLCK ) dwFlags |= LOCKFILE_EXCLUSIVE_LOCK; + rc = winLockFile(&pFile->hFile.h, dwFlags, ofst, 0, nByte, 0); + } + + if( rc!= 0 ){ + rc = SQLITE_OK; + }else{ + pFile->lastErrno = osGetLastError(); + rc = SQLITE_BUSY; + } + + OSTRACE(("SHM-LOCK file=%p, func=%s, errno=%lu, rc=%s\n", + pFile->hFile.h, (lockType == WINSHM_UNLCK) ? "winUnlockFile" : + "winLockFile", pFile->lastErrno, sqlite3ErrName(rc))); + + return rc; +} + +/* Forward references to VFS methods */ +static int winOpen(sqlite3_vfs*,const char*,sqlite3_file*,int,int*); +static int winDelete(sqlite3_vfs *,const char*,int); + +/* +** Purge the winShmNodeList list of all entries with winShmNode.nRef==0. +** +** This is not a VFS shared-memory method; it is a utility function called +** by VFS shared-memory methods. +*/ +static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ + winShmNode **pp; + winShmNode *p; + assert( winShmMutexHeld() ); + OSTRACE(("SHM-PURGE pid=%lu, deleteFlag=%d\n", + osGetCurrentProcessId(), deleteFlag)); + pp = &winShmNodeList; + while( (p = *pp)!=0 ){ + if( p->nRef==0 ){ + int i; + if( p->mutex ){ sqlite3_mutex_free(p->mutex); } + for(i=0; i<p->nRegion; i++){ + BOOL bRc = osUnmapViewOfFile(p->aRegion[i].pMap); + OSTRACE(("SHM-PURGE-UNMAP pid=%lu, region=%d, rc=%s\n", + osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); + UNUSED_VARIABLE_VALUE(bRc); + bRc = osCloseHandle(p->aRegion[i].hMap); + OSTRACE(("SHM-PURGE-CLOSE pid=%lu, region=%d, rc=%s\n", + osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); + UNUSED_VARIABLE_VALUE(bRc); + } + if( p->hFile.h!=NULL && p->hFile.h!=INVALID_HANDLE_VALUE ){ + SimulateIOErrorBenign(1); + winClose((sqlite3_file *)&p->hFile); + SimulateIOErrorBenign(0); + } + if( deleteFlag ){ + SimulateIOErrorBenign(1); + sqlite3BeginBenignMalloc(); + winDelete(pVfs, p->zFilename, 0); + sqlite3EndBenignMalloc(); + SimulateIOErrorBenign(0); + } + *pp = p->pNext; + sqlite3_free(p->aRegion); + sqlite3_free(p); + }else{ + pp = &p->pNext; + } + } +} + +/* +** The DMS lock has not yet been taken on shm file pShmNode. Attempt to +** take it now. Return SQLITE_OK if successful, or an SQLite error +** code otherwise. +** +** If the DMS cannot be locked because this is a readonly_shm=1 +** connection and no other process already holds a lock, return +** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1. +*/ +static int winLockSharedMemory(winShmNode *pShmNode){ + int rc = winShmSystemLock(pShmNode, WINSHM_WRLCK, WIN_SHM_DMS, 1); + + if( rc==SQLITE_OK ){ + if( pShmNode->isReadonly ){ + pShmNode->isUnlocked = 1; + winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); + return SQLITE_READONLY_CANTINIT; + }else if( winTruncate((sqlite3_file*)&pShmNode->hFile, 0) ){ + winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); + return winLogError(SQLITE_IOERR_SHMOPEN, osGetLastError(), + "winLockSharedMemory", pShmNode->zFilename); + } + } + + if( rc==SQLITE_OK ){ + winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); + } + + return winShmSystemLock(pShmNode, WINSHM_RDLCK, WIN_SHM_DMS, 1); +} + +/* +** Open the shared-memory area associated with database file pDbFd. +** +** When opening a new shared-memory file, if no other instances of that +** file are currently open, in this process or in other processes, then +** the file must be truncated to zero length or have its header cleared. +*/ +static int winOpenSharedMemory(winFile *pDbFd){ + struct winShm *p; /* The connection to be opened */ + winShmNode *pShmNode = 0; /* The underlying mmapped file */ + int rc = SQLITE_OK; /* Result code */ + winShmNode *pNew; /* Newly allocated winShmNode */ + int nName; /* Size of zName in bytes */ + + assert( pDbFd->pShm==0 ); /* Not previously opened */ + + /* Allocate space for the new sqlite3_shm object. Also speculatively + ** allocate space for a new winShmNode and filename. + */ + p = sqlite3MallocZero( sizeof(*p) ); + if( p==0 ) return SQLITE_IOERR_NOMEM_BKPT; + nName = sqlite3Strlen30(pDbFd->zPath); + pNew = sqlite3MallocZero( sizeof(*pShmNode) + nName + 17 ); + if( pNew==0 ){ + sqlite3_free(p); + return SQLITE_IOERR_NOMEM_BKPT; + } + pNew->zFilename = (char*)&pNew[1]; + sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath); + sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename); + + /* Look to see if there is an existing winShmNode that can be used. + ** If no matching winShmNode currently exists, create a new one. + */ + winShmEnterMutex(); + for(pShmNode = winShmNodeList; pShmNode; pShmNode=pShmNode->pNext){ + /* TBD need to come up with better match here. Perhaps + ** use FILE_ID_BOTH_DIR_INFO Structure. + */ + if( sqlite3StrICmp(pShmNode->zFilename, pNew->zFilename)==0 ) break; + } + if( pShmNode ){ + sqlite3_free(pNew); + }else{ + int inFlags = SQLITE_OPEN_WAL; + int outFlags = 0; + + pShmNode = pNew; + pNew = 0; + ((winFile*)(&pShmNode->hFile))->h = INVALID_HANDLE_VALUE; + pShmNode->pNext = winShmNodeList; + winShmNodeList = pShmNode; + + if( sqlite3GlobalConfig.bCoreMutex ){ + pShmNode->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( pShmNode->mutex==0 ){ + rc = SQLITE_IOERR_NOMEM_BKPT; + goto shm_open_err; + } + } + + if( 0==sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){ + inFlags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + }else{ + inFlags |= SQLITE_OPEN_READONLY; + } + rc = winOpen(pDbFd->pVfs, pShmNode->zFilename, + (sqlite3_file*)&pShmNode->hFile, + inFlags, &outFlags); + if( rc!=SQLITE_OK ){ + rc = winLogError(rc, osGetLastError(), "winOpenShm", + pShmNode->zFilename); + goto shm_open_err; + } + if( outFlags==SQLITE_OPEN_READONLY ) pShmNode->isReadonly = 1; + + rc = winLockSharedMemory(pShmNode); + if( rc!=SQLITE_OK && rc!=SQLITE_READONLY_CANTINIT ) goto shm_open_err; + } + + /* Make the new connection a child of the winShmNode */ + p->pShmNode = pShmNode; +#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) + p->id = pShmNode->nextShmId++; +#endif + pShmNode->nRef++; + pDbFd->pShm = p; + winShmLeaveMutex(); + + /* The reference count on pShmNode has already been incremented under + ** the cover of the winShmEnterMutex() mutex and the pointer from the + ** new (struct winShm) object to the pShmNode has been set. All that is + ** left to do is to link the new object into the linked list starting + ** at pShmNode->pFirst. This must be done while holding the pShmNode->mutex + ** mutex. + */ + sqlite3_mutex_enter(pShmNode->mutex); + p->pNext = pShmNode->pFirst; + pShmNode->pFirst = p; + sqlite3_mutex_leave(pShmNode->mutex); + return rc; + + /* Jump here on any error */ +shm_open_err: + winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); + winShmPurge(pDbFd->pVfs, 0); /* This call frees pShmNode if required */ + sqlite3_free(p); + sqlite3_free(pNew); + winShmLeaveMutex(); + return rc; +} + +/* +** Close a connection to shared-memory. Delete the underlying +** storage if deleteFlag is true. +*/ +static int winShmUnmap( + sqlite3_file *fd, /* Database holding shared memory */ + int deleteFlag /* Delete after closing if true */ +){ + winFile *pDbFd; /* Database holding shared-memory */ + winShm *p; /* The connection to be closed */ + winShmNode *pShmNode; /* The underlying shared-memory file */ + winShm **pp; /* For looping over sibling connections */ + + pDbFd = (winFile*)fd; + p = pDbFd->pShm; + if( p==0 ) return SQLITE_OK; + pShmNode = p->pShmNode; + + /* Remove connection p from the set of connections associated + ** with pShmNode */ + sqlite3_mutex_enter(pShmNode->mutex); + for(pp=&pShmNode->pFirst; (*pp)!=p; pp = &(*pp)->pNext){} + *pp = p->pNext; + + /* Free the connection p */ + sqlite3_free(p); + pDbFd->pShm = 0; + sqlite3_mutex_leave(pShmNode->mutex); + + /* If pShmNode->nRef has reached 0, then close the underlying + ** shared-memory file, too */ + winShmEnterMutex(); + assert( pShmNode->nRef>0 ); + pShmNode->nRef--; + if( pShmNode->nRef==0 ){ + winShmPurge(pDbFd->pVfs, deleteFlag); + } + winShmLeaveMutex(); + + return SQLITE_OK; +} + +/* +** Change the lock state for a shared-memory segment. +*/ +static int winShmLock( + sqlite3_file *fd, /* Database file holding the shared memory */ + int ofst, /* First lock to acquire or release */ + int n, /* Number of locks to acquire or release */ + int flags /* What to do with the lock */ +){ + winFile *pDbFd = (winFile*)fd; /* Connection holding shared memory */ + winShm *p = pDbFd->pShm; /* The shared memory being locked */ + winShm *pX; /* For looping over all siblings */ + winShmNode *pShmNode; + int rc = SQLITE_OK; /* Result code */ + u16 mask; /* Mask of locks to take or release */ + + if( p==0 ) return SQLITE_IOERR_SHMLOCK; + pShmNode = p->pShmNode; + if( NEVER(pShmNode==0) ) return SQLITE_IOERR_SHMLOCK; + + assert( ofst>=0 && ofst+n<=SQLITE_SHM_NLOCK ); + assert( n>=1 ); + assert( flags==(SQLITE_SHM_LOCK | SQLITE_SHM_SHARED) + || flags==(SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE) + || flags==(SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED) + || flags==(SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE) ); + assert( n==1 || (flags & SQLITE_SHM_EXCLUSIVE)!=0 ); + + mask = (u16)((1U<<(ofst+n)) - (1U<<ofst)); + assert( n>1 || mask==(1<<ofst) ); + sqlite3_mutex_enter(pShmNode->mutex); + if( flags & SQLITE_SHM_UNLOCK ){ + u16 allMask = 0; /* Mask of locks held by siblings */ + + /* See if any siblings hold this same lock */ + for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ + if( pX==p ) continue; + assert( (pX->exclMask & (p->exclMask|p->sharedMask))==0 ); + allMask |= pX->sharedMask; + } + + /* Unlock the system-level locks */ + if( (mask & allMask)==0 ){ + rc = winShmSystemLock(pShmNode, WINSHM_UNLCK, ofst+WIN_SHM_BASE, n); + }else{ + rc = SQLITE_OK; + } + + /* Undo the local locks */ + if( rc==SQLITE_OK ){ + p->exclMask &= ~mask; + p->sharedMask &= ~mask; + } + }else if( flags & SQLITE_SHM_SHARED ){ + u16 allShared = 0; /* Union of locks held by connections other than "p" */ + + /* Find out which shared locks are already held by sibling connections. + ** If any sibling already holds an exclusive lock, go ahead and return + ** SQLITE_BUSY. + */ + for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ + if( (pX->exclMask & mask)!=0 ){ + rc = SQLITE_BUSY; + break; + } + allShared |= pX->sharedMask; + } + + /* Get shared locks at the system level, if necessary */ + if( rc==SQLITE_OK ){ + if( (allShared & mask)==0 ){ + rc = winShmSystemLock(pShmNode, WINSHM_RDLCK, ofst+WIN_SHM_BASE, n); + }else{ + rc = SQLITE_OK; + } + } + + /* Get the local shared locks */ + if( rc==SQLITE_OK ){ + p->sharedMask |= mask; + } + }else{ + /* Make sure no sibling connections hold locks that will block this + ** lock. If any do, return SQLITE_BUSY right away. + */ + for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ + if( (pX->exclMask & mask)!=0 || (pX->sharedMask & mask)!=0 ){ + rc = SQLITE_BUSY; + break; + } + } + + /* Get the exclusive locks at the system level. Then if successful + ** also mark the local connection as being locked. + */ + if( rc==SQLITE_OK ){ + rc = winShmSystemLock(pShmNode, WINSHM_WRLCK, ofst+WIN_SHM_BASE, n); + if( rc==SQLITE_OK ){ + assert( (p->sharedMask & mask)==0 ); + p->exclMask |= mask; + } + } + } + sqlite3_mutex_leave(pShmNode->mutex); + OSTRACE(("SHM-LOCK pid=%lu, id=%d, sharedMask=%03x, exclMask=%03x, rc=%s\n", + osGetCurrentProcessId(), p->id, p->sharedMask, p->exclMask, + sqlite3ErrName(rc))); + return rc; +} + +/* +** Implement a memory barrier or memory fence on shared memory. +** +** All loads and stores begun before the barrier must complete before +** any load or store begun after the barrier. +*/ +static void winShmBarrier( + sqlite3_file *fd /* Database holding the shared memory */ +){ + UNUSED_PARAMETER(fd); + sqlite3MemoryBarrier(); /* compiler-defined memory barrier */ + winShmEnterMutex(); /* Also mutex, for redundancy */ + winShmLeaveMutex(); +} + +/* +** This function is called to obtain a pointer to region iRegion of the +** shared-memory associated with the database file fd. Shared-memory regions +** are numbered starting from zero. Each shared-memory region is szRegion +** bytes in size. +** +** If an error occurs, an error code is returned and *pp is set to NULL. +** +** Otherwise, if the isWrite parameter is 0 and the requested shared-memory +** region has not been allocated (by any client, including one running in a +** separate process), then *pp is set to NULL and SQLITE_OK returned. If +** isWrite is non-zero and the requested shared-memory region has not yet +** been allocated, it is allocated by this function. +** +** If the shared-memory region has already been allocated or is allocated by +** this call as described above, then it is mapped into this processes +** address space (if it is not already), *pp is set to point to the mapped +** memory and SQLITE_OK returned. +*/ +static int winShmMap( + sqlite3_file *fd, /* Handle open on database file */ + int iRegion, /* Region to retrieve */ + int szRegion, /* Size of regions */ + int isWrite, /* True to extend file if necessary */ + void volatile **pp /* OUT: Mapped memory */ +){ + winFile *pDbFd = (winFile*)fd; + winShm *pShm = pDbFd->pShm; + winShmNode *pShmNode; + DWORD protect = PAGE_READWRITE; + DWORD flags = FILE_MAP_WRITE | FILE_MAP_READ; + int rc = SQLITE_OK; + + if( !pShm ){ + rc = winOpenSharedMemory(pDbFd); + if( rc!=SQLITE_OK ) return rc; + pShm = pDbFd->pShm; + assert( pShm!=0 ); + } + pShmNode = pShm->pShmNode; + + sqlite3_mutex_enter(pShmNode->mutex); + if( pShmNode->isUnlocked ){ + rc = winLockSharedMemory(pShmNode); + if( rc!=SQLITE_OK ) goto shmpage_out; + pShmNode->isUnlocked = 0; + } + assert( szRegion==pShmNode->szRegion || pShmNode->nRegion==0 ); + + if( pShmNode->nRegion<=iRegion ){ + struct ShmRegion *apNew; /* New aRegion[] array */ + int nByte = (iRegion+1)*szRegion; /* Minimum required file size */ + sqlite3_int64 sz; /* Current size of wal-index file */ + + pShmNode->szRegion = szRegion; + + /* The requested region is not mapped into this processes address space. + ** Check to see if it has been allocated (i.e. if the wal-index file is + ** large enough to contain the requested region). + */ + rc = winFileSize((sqlite3_file *)&pShmNode->hFile, &sz); + if( rc!=SQLITE_OK ){ + rc = winLogError(SQLITE_IOERR_SHMSIZE, osGetLastError(), + "winShmMap1", pDbFd->zPath); + goto shmpage_out; + } + + if( sz<nByte ){ + /* The requested memory region does not exist. If isWrite is set to + ** zero, exit early. *pp will be set to NULL and SQLITE_OK returned. + ** + ** Alternatively, if isWrite is non-zero, use ftruncate() to allocate + ** the requested memory region. + */ + if( !isWrite ) goto shmpage_out; + rc = winTruncate((sqlite3_file *)&pShmNode->hFile, nByte); + if( rc!=SQLITE_OK ){ + rc = winLogError(SQLITE_IOERR_SHMSIZE, osGetLastError(), + "winShmMap2", pDbFd->zPath); + goto shmpage_out; + } + } + + /* Map the requested memory region into this processes address space. */ + apNew = (struct ShmRegion *)sqlite3_realloc64( + pShmNode->aRegion, (iRegion+1)*sizeof(apNew[0]) + ); + if( !apNew ){ + rc = SQLITE_IOERR_NOMEM_BKPT; + goto shmpage_out; + } + pShmNode->aRegion = apNew; + + if( pShmNode->isReadonly ){ + protect = PAGE_READONLY; + flags = FILE_MAP_READ; + } + + while( pShmNode->nRegion<=iRegion ){ + HANDLE hMap = NULL; /* file-mapping handle */ + void *pMap = 0; /* Mapped memory region */ + +#if SQLITE_OS_WINRT + hMap = osCreateFileMappingFromApp(pShmNode->hFile.h, + NULL, protect, nByte, NULL + ); +#elif defined(SQLITE_WIN32_HAS_WIDE) + hMap = osCreateFileMappingW(pShmNode->hFile.h, + NULL, protect, 0, nByte, NULL + ); +#elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA + hMap = osCreateFileMappingA(pShmNode->hFile.h, + NULL, protect, 0, nByte, NULL + ); +#endif + OSTRACE(("SHM-MAP-CREATE pid=%lu, region=%d, size=%d, rc=%s\n", + osGetCurrentProcessId(), pShmNode->nRegion, nByte, + hMap ? "ok" : "failed")); + if( hMap ){ + int iOffset = pShmNode->nRegion*szRegion; + int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; +#if SQLITE_OS_WINRT + pMap = osMapViewOfFileFromApp(hMap, flags, + iOffset - iOffsetShift, szRegion + iOffsetShift + ); +#else + pMap = osMapViewOfFile(hMap, flags, + 0, iOffset - iOffsetShift, szRegion + iOffsetShift + ); +#endif + OSTRACE(("SHM-MAP-MAP pid=%lu, region=%d, offset=%d, size=%d, rc=%s\n", + osGetCurrentProcessId(), pShmNode->nRegion, iOffset, + szRegion, pMap ? "ok" : "failed")); + } + if( !pMap ){ + pShmNode->lastErrno = osGetLastError(); + rc = winLogError(SQLITE_IOERR_SHMMAP, pShmNode->lastErrno, + "winShmMap3", pDbFd->zPath); + if( hMap ) osCloseHandle(hMap); + goto shmpage_out; + } + + pShmNode->aRegion[pShmNode->nRegion].pMap = pMap; + pShmNode->aRegion[pShmNode->nRegion].hMap = hMap; + pShmNode->nRegion++; + } + } + +shmpage_out: + if( pShmNode->nRegion>iRegion ){ + int iOffset = iRegion*szRegion; + int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; + char *p = (char *)pShmNode->aRegion[iRegion].pMap; + *pp = (void *)&p[iOffsetShift]; + }else{ + *pp = 0; + } + if( pShmNode->isReadonly && rc==SQLITE_OK ) rc = SQLITE_READONLY; + sqlite3_mutex_leave(pShmNode->mutex); + return rc; +} + +#else +# define winShmMap 0 +# define winShmLock 0 +# define winShmBarrier 0 +# define winShmUnmap 0 +#endif /* #ifndef SQLITE_OMIT_WAL */ + +/* +** Cleans up the mapped region of the specified file, if any. +*/ +#if SQLITE_MAX_MMAP_SIZE>0 +static int winUnmapfile(winFile *pFile){ + assert( pFile!=0 ); + OSTRACE(("UNMAP-FILE pid=%lu, pFile=%p, hMap=%p, pMapRegion=%p, " + "mmapSize=%lld, mmapSizeMax=%lld\n", + osGetCurrentProcessId(), pFile, pFile->hMap, pFile->pMapRegion, + pFile->mmapSize, pFile->mmapSizeMax)); + if( pFile->pMapRegion ){ + if( !osUnmapViewOfFile(pFile->pMapRegion) ){ + pFile->lastErrno = osGetLastError(); + OSTRACE(("UNMAP-FILE pid=%lu, pFile=%p, pMapRegion=%p, " + "rc=SQLITE_IOERR_MMAP\n", osGetCurrentProcessId(), pFile, + pFile->pMapRegion)); + return winLogError(SQLITE_IOERR_MMAP, pFile->lastErrno, + "winUnmapfile1", pFile->zPath); + } + pFile->pMapRegion = 0; + pFile->mmapSize = 0; + } + if( pFile->hMap!=NULL ){ + if( !osCloseHandle(pFile->hMap) ){ + pFile->lastErrno = osGetLastError(); + OSTRACE(("UNMAP-FILE pid=%lu, pFile=%p, hMap=%p, rc=SQLITE_IOERR_MMAP\n", + osGetCurrentProcessId(), pFile, pFile->hMap)); + return winLogError(SQLITE_IOERR_MMAP, pFile->lastErrno, + "winUnmapfile2", pFile->zPath); + } + pFile->hMap = NULL; + } + OSTRACE(("UNMAP-FILE pid=%lu, pFile=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile)); + return SQLITE_OK; +} + +/* +** Memory map or remap the file opened by file-descriptor pFd (if the file +** is already mapped, the existing mapping is replaced by the new). Or, if +** there already exists a mapping for this file, and there are still +** outstanding xFetch() references to it, this function is a no-op. +** +** If parameter nByte is non-negative, then it is the requested size of +** the mapping to create. Otherwise, if nByte is less than zero, then the +** requested size is the size of the file on disk. The actual size of the +** created mapping is either the requested size or the value configured +** using SQLITE_FCNTL_MMAP_SIZE, whichever is smaller. +** +** SQLITE_OK is returned if no error occurs (even if the mapping is not +** recreated as a result of outstanding references) or an SQLite error +** code otherwise. +*/ +static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ + sqlite3_int64 nMap = nByte; + int rc; + + assert( nMap>=0 || pFd->nFetchOut==0 ); + OSTRACE(("MAP-FILE pid=%lu, pFile=%p, size=%lld\n", + osGetCurrentProcessId(), pFd, nByte)); + + if( pFd->nFetchOut>0 ) return SQLITE_OK; + + if( nMap<0 ){ + rc = winFileSize((sqlite3_file*)pFd, &nMap); + if( rc ){ + OSTRACE(("MAP-FILE pid=%lu, pFile=%p, rc=SQLITE_IOERR_FSTAT\n", + osGetCurrentProcessId(), pFd)); + return SQLITE_IOERR_FSTAT; + } + } + if( nMap>pFd->mmapSizeMax ){ + nMap = pFd->mmapSizeMax; + } + nMap &= ~(sqlite3_int64)(winSysInfo.dwPageSize - 1); + + if( nMap==0 && pFd->mmapSize>0 ){ + winUnmapfile(pFd); + } + if( nMap!=pFd->mmapSize ){ + void *pNew = 0; + DWORD protect = PAGE_READONLY; + DWORD flags = FILE_MAP_READ; + + winUnmapfile(pFd); +#ifdef SQLITE_MMAP_READWRITE + if( (pFd->ctrlFlags & WINFILE_RDONLY)==0 ){ + protect = PAGE_READWRITE; + flags |= FILE_MAP_WRITE; + } +#endif +#if SQLITE_OS_WINRT + pFd->hMap = osCreateFileMappingFromApp(pFd->h, NULL, protect, nMap, NULL); +#elif defined(SQLITE_WIN32_HAS_WIDE) + pFd->hMap = osCreateFileMappingW(pFd->h, NULL, protect, + (DWORD)((nMap>>32) & 0xffffffff), + (DWORD)(nMap & 0xffffffff), NULL); +#elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA + pFd->hMap = osCreateFileMappingA(pFd->h, NULL, protect, + (DWORD)((nMap>>32) & 0xffffffff), + (DWORD)(nMap & 0xffffffff), NULL); +#endif + if( pFd->hMap==NULL ){ + pFd->lastErrno = osGetLastError(); + rc = winLogError(SQLITE_IOERR_MMAP, pFd->lastErrno, + "winMapfile1", pFd->zPath); + /* Log the error, but continue normal operation using xRead/xWrite */ + OSTRACE(("MAP-FILE-CREATE pid=%lu, pFile=%p, rc=%s\n", + osGetCurrentProcessId(), pFd, sqlite3ErrName(rc))); + return SQLITE_OK; + } + assert( (nMap % winSysInfo.dwPageSize)==0 ); + assert( sizeof(SIZE_T)==sizeof(sqlite3_int64) || nMap<=0xffffffff ); +#if SQLITE_OS_WINRT + pNew = osMapViewOfFileFromApp(pFd->hMap, flags, 0, (SIZE_T)nMap); +#else + pNew = osMapViewOfFile(pFd->hMap, flags, 0, 0, (SIZE_T)nMap); +#endif + if( pNew==NULL ){ + osCloseHandle(pFd->hMap); + pFd->hMap = NULL; + pFd->lastErrno = osGetLastError(); + rc = winLogError(SQLITE_IOERR_MMAP, pFd->lastErrno, + "winMapfile2", pFd->zPath); + /* Log the error, but continue normal operation using xRead/xWrite */ + OSTRACE(("MAP-FILE-MAP pid=%lu, pFile=%p, rc=%s\n", + osGetCurrentProcessId(), pFd, sqlite3ErrName(rc))); + return SQLITE_OK; + } + pFd->pMapRegion = pNew; + pFd->mmapSize = nMap; + } + + OSTRACE(("MAP-FILE pid=%lu, pFile=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFd)); + return SQLITE_OK; +} +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + +/* +** If possible, return a pointer to a mapping of file fd starting at offset +** iOff. The mapping must be valid for at least nAmt bytes. +** +** If such a pointer can be obtained, store it in *pp and return SQLITE_OK. +** Or, if one cannot but no error occurs, set *pp to 0 and return SQLITE_OK. +** Finally, if an error does occur, return an SQLite error code. The final +** value of *pp is undefined in this case. +** +** If this function does return a pointer, the caller must eventually +** release the reference by calling winUnfetch(). +*/ +static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ +#if SQLITE_MAX_MMAP_SIZE>0 + winFile *pFd = (winFile*)fd; /* The underlying database file */ +#endif + *pp = 0; + + OSTRACE(("FETCH pid=%lu, pFile=%p, offset=%lld, amount=%d, pp=%p\n", + osGetCurrentProcessId(), fd, iOff, nAmt, pp)); + +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFd->mmapSizeMax>0 ){ + if( pFd->pMapRegion==0 ){ + int rc = winMapfile(pFd, -1); + if( rc!=SQLITE_OK ){ + OSTRACE(("FETCH pid=%lu, pFile=%p, rc=%s\n", + osGetCurrentProcessId(), pFd, sqlite3ErrName(rc))); + return rc; + } + } + if( pFd->mmapSize >= iOff+nAmt ){ + assert( pFd->pMapRegion!=0 ); + *pp = &((u8 *)pFd->pMapRegion)[iOff]; + pFd->nFetchOut++; + } + } +#endif + + OSTRACE(("FETCH pid=%lu, pFile=%p, pp=%p, *pp=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), fd, pp, *pp)); + return SQLITE_OK; +} + +/* +** If the third argument is non-NULL, then this function releases a +** reference obtained by an earlier call to winFetch(). The second +** argument passed to this function must be the same as the corresponding +** argument that was passed to the winFetch() invocation. +** +** Or, if the third argument is NULL, then this function is being called +** to inform the VFS layer that, according to POSIX, any existing mapping +** may now be invalid and should be unmapped. +*/ +static int winUnfetch(sqlite3_file *fd, i64 iOff, void *p){ +#if SQLITE_MAX_MMAP_SIZE>0 + winFile *pFd = (winFile*)fd; /* The underlying database file */ + + /* If p==0 (unmap the entire file) then there must be no outstanding + ** xFetch references. Or, if p!=0 (meaning it is an xFetch reference), + ** then there must be at least one outstanding. */ + assert( (p==0)==(pFd->nFetchOut==0) ); + + /* If p!=0, it must match the iOff value. */ + assert( p==0 || p==&((u8 *)pFd->pMapRegion)[iOff] ); + + OSTRACE(("UNFETCH pid=%lu, pFile=%p, offset=%lld, p=%p\n", + osGetCurrentProcessId(), pFd, iOff, p)); + + if( p ){ + pFd->nFetchOut--; + }else{ + /* FIXME: If Windows truly always prevents truncating or deleting a + ** file while a mapping is held, then the following winUnmapfile() call + ** is unnecessary can be omitted - potentially improving + ** performance. */ + winUnmapfile(pFd); + } + + assert( pFd->nFetchOut>=0 ); +#endif + + OSTRACE(("UNFETCH pid=%lu, pFile=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), fd)); + return SQLITE_OK; +} + +/* +** Here ends the implementation of all sqlite3_file methods. +** +********************** End sqlite3_file Methods ******************************* +******************************************************************************/ + +/* +** This vector defines all the methods that can operate on an +** sqlite3_file for win32. +*/ +static const sqlite3_io_methods winIoMethod = { + 3, /* iVersion */ + winClose, /* xClose */ + winRead, /* xRead */ + winWrite, /* xWrite */ + winTruncate, /* xTruncate */ + winSync, /* xSync */ + winFileSize, /* xFileSize */ + winLock, /* xLock */ + winUnlock, /* xUnlock */ + winCheckReservedLock, /* xCheckReservedLock */ + winFileControl, /* xFileControl */ + winSectorSize, /* xSectorSize */ + winDeviceCharacteristics, /* xDeviceCharacteristics */ + winShmMap, /* xShmMap */ + winShmLock, /* xShmLock */ + winShmBarrier, /* xShmBarrier */ + winShmUnmap, /* xShmUnmap */ + winFetch, /* xFetch */ + winUnfetch /* xUnfetch */ +}; + +/* +** This vector defines all the methods that can operate on an +** sqlite3_file for win32 without performing any locking. +*/ +static const sqlite3_io_methods winIoNolockMethod = { + 3, /* iVersion */ + winClose, /* xClose */ + winRead, /* xRead */ + winWrite, /* xWrite */ + winTruncate, /* xTruncate */ + winSync, /* xSync */ + winFileSize, /* xFileSize */ + winNolockLock, /* xLock */ + winNolockUnlock, /* xUnlock */ + winNolockCheckReservedLock, /* xCheckReservedLock */ + winFileControl, /* xFileControl */ + winSectorSize, /* xSectorSize */ + winDeviceCharacteristics, /* xDeviceCharacteristics */ + winShmMap, /* xShmMap */ + winShmLock, /* xShmLock */ + winShmBarrier, /* xShmBarrier */ + winShmUnmap, /* xShmUnmap */ + winFetch, /* xFetch */ + winUnfetch /* xUnfetch */ +}; + +static winVfsAppData winAppData = { + &winIoMethod, /* pMethod */ + 0, /* pAppData */ + 0 /* bNoLock */ +}; + +static winVfsAppData winNolockAppData = { + &winIoNolockMethod, /* pMethod */ + 0, /* pAppData */ + 1 /* bNoLock */ +}; + +/**************************************************************************** +**************************** sqlite3_vfs methods **************************** +** +** This division contains the implementation of methods on the +** sqlite3_vfs object. +*/ + +#if defined(__CYGWIN__) +/* +** Convert a filename from whatever the underlying operating system +** supports for filenames into UTF-8. Space to hold the result is +** obtained from malloc and must be freed by the calling function. +*/ +static char *winConvertToUtf8Filename(const void *zFilename){ + char *zConverted = 0; + if( osIsNT() ){ + zConverted = winUnicodeToUtf8(zFilename); + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + zConverted = winMbcsToUtf8(zFilename, osAreFileApisANSI()); + } +#endif + /* caller will handle out of memory */ + return zConverted; +} +#endif + +/* +** Convert a UTF-8 filename into whatever form the underlying +** operating system wants filenames in. Space to hold the result +** is obtained from malloc and must be freed by the calling +** function. +*/ +static void *winConvertFromUtf8Filename(const char *zFilename){ + void *zConverted = 0; + if( osIsNT() ){ + zConverted = winUtf8ToUnicode(zFilename); + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI()); + } +#endif + /* caller will handle out of memory */ + return zConverted; +} + +/* +** This function returns non-zero if the specified UTF-8 string buffer +** ends with a directory separator character or one was successfully +** added to it. +*/ +static int winMakeEndInDirSep(int nBuf, char *zBuf){ + if( zBuf ){ + int nLen = sqlite3Strlen30(zBuf); + if( nLen>0 ){ + if( winIsDirSep(zBuf[nLen-1]) ){ + return 1; + }else if( nLen+1<nBuf ){ + zBuf[nLen] = winGetDirSep(); + zBuf[nLen+1] = '\0'; + return 1; + } + } + } + return 0; +} + +/* +** If sqlite3_temp_directory is defined, take the mutex and return true. +** +** If sqlite3_temp_directory is NULL (undefined), omit the mutex and +** return false. +*/ +static int winTempDirDefined(void){ + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + if( sqlite3_temp_directory!=0 ) return 1; + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return 0; +} + +/* +** Create a temporary file name and store the resulting pointer into pzBuf. +** The pointer returned in pzBuf must be freed via sqlite3_free(). +*/ +static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ + static char zChars[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + size_t i, j; + DWORD pid; + int nPre = sqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX); + int nMax, nBuf, nDir, nLen; + char *zBuf; + + /* It's odd to simulate an io-error here, but really this is just + ** using the io-error infrastructure to test that SQLite handles this + ** function failing. + */ + SimulateIOError( return SQLITE_IOERR ); + + /* Allocate a temporary buffer to store the fully qualified file + ** name for the temporary file. If this fails, we cannot continue. + */ + nMax = pVfs->mxPathname; nBuf = nMax + 2; + zBuf = sqlite3MallocZero( nBuf ); + if( !zBuf ){ + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); + return SQLITE_IOERR_NOMEM_BKPT; + } + + /* Figure out the effective temporary directory. First, check if one + ** has been explicitly set by the application; otherwise, use the one + ** configured by the operating system. + */ + nDir = nMax - (nPre + 15); + assert( nDir>0 ); + if( winTempDirDefined() ){ + int nDirLen = sqlite3Strlen30(sqlite3_temp_directory); + if( nDirLen>0 ){ + if( !winIsDirSep(sqlite3_temp_directory[nDirLen-1]) ){ + nDirLen++; + } + if( nDirLen>nDir ){ + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n")); + return winLogError(SQLITE_ERROR, 0, "winGetTempname1", 0); + } + sqlite3_snprintf(nMax, zBuf, "%s", sqlite3_temp_directory); + } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + } + +#if defined(__CYGWIN__) + else{ + static const char *azDirs[] = { + 0, /* getenv("SQLITE_TMPDIR") */ + 0, /* getenv("TMPDIR") */ + 0, /* getenv("TMP") */ + 0, /* getenv("TEMP") */ + 0, /* getenv("USERPROFILE") */ + "/var/tmp", + "/usr/tmp", + "/tmp", + ".", + 0 /* List terminator */ + }; + unsigned int i; + const char *zDir = 0; + + if( !azDirs[0] ) azDirs[0] = getenv("SQLITE_TMPDIR"); + if( !azDirs[1] ) azDirs[1] = getenv("TMPDIR"); + if( !azDirs[2] ) azDirs[2] = getenv("TMP"); + if( !azDirs[3] ) azDirs[3] = getenv("TEMP"); + if( !azDirs[4] ) azDirs[4] = getenv("USERPROFILE"); + for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); zDir=azDirs[i++]){ + void *zConverted; + if( zDir==0 ) continue; + /* If the path starts with a drive letter followed by the colon + ** character, assume it is already a native Win32 path; otherwise, + ** it must be converted to a native Win32 path via the Cygwin API + ** prior to using it. + */ + if( winIsDriveLetterAndColon(zDir) ){ + zConverted = winConvertFromUtf8Filename(zDir); + if( !zConverted ){ + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); + return SQLITE_IOERR_NOMEM_BKPT; + } + if( winIsDir(zConverted) ){ + sqlite3_snprintf(nMax, zBuf, "%s", zDir); + sqlite3_free(zConverted); + break; + } + sqlite3_free(zConverted); + }else{ + zConverted = sqlite3MallocZero( nMax+1 ); + if( !zConverted ){ + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); + return SQLITE_IOERR_NOMEM_BKPT; + } + if( cygwin_conv_path( + osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A, zDir, + zConverted, nMax+1)<0 ){ + sqlite3_free(zConverted); + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_CONVPATH\n")); + return winLogError(SQLITE_IOERR_CONVPATH, (DWORD)errno, + "winGetTempname2", zDir); + } + if( winIsDir(zConverted) ){ + /* At this point, we know the candidate directory exists and should + ** be used. However, we may need to convert the string containing + ** its name into UTF-8 (i.e. if it is UTF-16 right now). + */ + char *zUtf8 = winConvertToUtf8Filename(zConverted); + if( !zUtf8 ){ + sqlite3_free(zConverted); + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); + return SQLITE_IOERR_NOMEM_BKPT; + } + sqlite3_snprintf(nMax, zBuf, "%s", zUtf8); + sqlite3_free(zUtf8); + sqlite3_free(zConverted); + break; + } + sqlite3_free(zConverted); + } + } + } +#elif !SQLITE_OS_WINRT && !defined(__CYGWIN__) + else if( osIsNT() ){ + char *zMulti; + LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) ); + if( !zWidePath ){ + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); + return SQLITE_IOERR_NOMEM_BKPT; + } + if( osGetTempPathW(nMax, zWidePath)==0 ){ + sqlite3_free(zWidePath); + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_GETTEMPPATH\n")); + return winLogError(SQLITE_IOERR_GETTEMPPATH, osGetLastError(), + "winGetTempname2", 0); + } + zMulti = winUnicodeToUtf8(zWidePath); + if( zMulti ){ + sqlite3_snprintf(nMax, zBuf, "%s", zMulti); + sqlite3_free(zMulti); + sqlite3_free(zWidePath); + }else{ + sqlite3_free(zWidePath); + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); + return SQLITE_IOERR_NOMEM_BKPT; + } + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + char *zUtf8; + char *zMbcsPath = sqlite3MallocZero( nMax ); + if( !zMbcsPath ){ + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); + return SQLITE_IOERR_NOMEM_BKPT; + } + if( osGetTempPathA(nMax, zMbcsPath)==0 ){ + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_GETTEMPPATH\n")); + return winLogError(SQLITE_IOERR_GETTEMPPATH, osGetLastError(), + "winGetTempname3", 0); + } + zUtf8 = winMbcsToUtf8(zMbcsPath, osAreFileApisANSI()); + if( zUtf8 ){ + sqlite3_snprintf(nMax, zBuf, "%s", zUtf8); + sqlite3_free(zUtf8); + }else{ + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); + return SQLITE_IOERR_NOMEM_BKPT; + } + } +#endif /* SQLITE_WIN32_HAS_ANSI */ +#endif /* !SQLITE_OS_WINRT */ + + /* + ** Check to make sure the temporary directory ends with an appropriate + ** separator. If it does not and there is not enough space left to add + ** one, fail. + */ + if( !winMakeEndInDirSep(nDir+1, zBuf) ){ + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n")); + return winLogError(SQLITE_ERROR, 0, "winGetTempname4", 0); + } + + /* + ** Check that the output buffer is large enough for the temporary file + ** name in the following format: + ** + ** "<temporary_directory>/etilqs_XXXXXXXXXXXXXXX\0\0" + ** + ** If not, return SQLITE_ERROR. The number 17 is used here in order to + ** account for the space used by the 15 character random suffix and the + ** two trailing NUL characters. The final directory separator character + ** has already added if it was not already present. + */ + nLen = sqlite3Strlen30(zBuf); + if( (nLen + nPre + 17) > nBuf ){ + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n")); + return winLogError(SQLITE_ERROR, 0, "winGetTempname5", 0); + } + + sqlite3_snprintf(nBuf-16-nLen, zBuf+nLen, SQLITE_TEMP_FILE_PREFIX); + + j = sqlite3Strlen30(zBuf); + sqlite3_randomness(15, &zBuf[j]); + pid = osGetCurrentProcessId(); + for(i=0; i<15; i++, j++){ + zBuf[j] += pid & 0xff; + pid >>= 8; + zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; + } + zBuf[j] = 0; + zBuf[j+1] = 0; + *pzBuf = zBuf; + + OSTRACE(("TEMP-FILENAME name=%s, rc=SQLITE_OK\n", zBuf)); + return SQLITE_OK; +} + +/* +** Return TRUE if the named file is really a directory. Return false if +** it is something other than a directory, or if there is any kind of memory +** allocation failure. +*/ +static int winIsDir(const void *zConverted){ + DWORD attr; + int rc = 0; + DWORD lastErrno; + + if( osIsNT() ){ + int cnt = 0; + WIN32_FILE_ATTRIBUTE_DATA sAttrData; + memset(&sAttrData, 0, sizeof(sAttrData)); + while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted, + GetFileExInfoStandard, + &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){} + if( !rc ){ + return 0; /* Invalid name? */ + } + attr = sAttrData.dwFileAttributes; +#if SQLITE_OS_WINCE==0 + }else{ + attr = osGetFileAttributesA((char*)zConverted); +#endif + } + return (attr!=INVALID_FILE_ATTRIBUTES) && (attr&FILE_ATTRIBUTE_DIRECTORY); +} + +/* forward reference */ +static int winAccess( + sqlite3_vfs *pVfs, /* Not used on win32 */ + const char *zFilename, /* Name of file to check */ + int flags, /* Type of test to make on this file */ + int *pResOut /* OUT: Result */ +); + +/* +** Open a file. +*/ +static int winOpen( + sqlite3_vfs *pVfs, /* Used to get maximum path length and AppData */ + const char *zName, /* Name of the file (UTF-8) */ + sqlite3_file *id, /* Write the SQLite file handle here */ + int flags, /* Open mode flags */ + int *pOutFlags /* Status return flags */ +){ + HANDLE h; + DWORD lastErrno = 0; + DWORD dwDesiredAccess; + DWORD dwShareMode; + DWORD dwCreationDisposition; + DWORD dwFlagsAndAttributes = 0; +#if SQLITE_OS_WINCE + int isTemp = 0; +#endif + winVfsAppData *pAppData; + winFile *pFile = (winFile*)id; + void *zConverted; /* Filename in OS encoding */ + const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ + int cnt = 0; + + /* If argument zPath is a NULL pointer, this function is required to open + ** a temporary file. Use this buffer to store the file name in. + */ + char *zTmpname = 0; /* For temporary filename, if necessary. */ + + int rc = SQLITE_OK; /* Function Return Code */ +#if !defined(NDEBUG) || SQLITE_OS_WINCE + int eType = flags&0xFFFFFF00; /* Type of file to open */ +#endif + + int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE); + int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE); + int isCreate = (flags & SQLITE_OPEN_CREATE); + int isReadonly = (flags & SQLITE_OPEN_READONLY); + int isReadWrite = (flags & SQLITE_OPEN_READWRITE); + +#ifndef NDEBUG + int isOpenJournal = (isCreate && ( + eType==SQLITE_OPEN_SUPER_JOURNAL + || eType==SQLITE_OPEN_MAIN_JOURNAL + || eType==SQLITE_OPEN_WAL + )); +#endif + + OSTRACE(("OPEN name=%s, pFile=%p, flags=%x, pOutFlags=%p\n", + zUtf8Name, id, flags, pOutFlags)); + + /* Check the following statements are true: + ** + ** (a) Exactly one of the READWRITE and READONLY flags must be set, and + ** (b) if CREATE is set, then READWRITE must also be set, and + ** (c) if EXCLUSIVE is set, then CREATE must also be set. + ** (d) if DELETEONCLOSE is set, then CREATE must also be set. + */ + assert((isReadonly==0 || isReadWrite==0) && (isReadWrite || isReadonly)); + assert(isCreate==0 || isReadWrite); + assert(isExclusive==0 || isCreate); + assert(isDelete==0 || isCreate); + + /* The main DB, main journal, WAL file and super-journal are never + ** automatically deleted. Nor are they ever temporary files. */ + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_DB ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_JOURNAL ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_SUPER_JOURNAL ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_WAL ); + + /* Assert that the upper layer has set one of the "file-type" flags. */ + assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL + || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_SUPER_JOURNAL + || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL + ); + + assert( pFile!=0 ); + memset(pFile, 0, sizeof(winFile)); + pFile->h = INVALID_HANDLE_VALUE; + +#if SQLITE_OS_WINRT + if( !zUtf8Name && !sqlite3_temp_directory ){ + sqlite3_log(SQLITE_ERROR, + "sqlite3_temp_directory variable should be set for WinRT"); + } +#endif + + /* If the second argument to this function is NULL, generate a + ** temporary file name to use + */ + if( !zUtf8Name ){ + assert( isDelete && !isOpenJournal ); + rc = winGetTempname(pVfs, &zTmpname); + if( rc!=SQLITE_OK ){ + OSTRACE(("OPEN name=%s, rc=%s", zUtf8Name, sqlite3ErrName(rc))); + return rc; + } + zUtf8Name = zTmpname; + } + + /* Database filenames are double-zero terminated if they are not + ** URIs with parameters. Hence, they can always be passed into + ** sqlite3_uri_parameter(). + */ + assert( (eType!=SQLITE_OPEN_MAIN_DB) || (flags & SQLITE_OPEN_URI) || + zUtf8Name[sqlite3Strlen30(zUtf8Name)+1]==0 ); + + /* Convert the filename to the system encoding. */ + zConverted = winConvertFromUtf8Filename(zUtf8Name); + if( zConverted==0 ){ + sqlite3_free(zTmpname); + OSTRACE(("OPEN name=%s, rc=SQLITE_IOERR_NOMEM", zUtf8Name)); + return SQLITE_IOERR_NOMEM_BKPT; + } + + if( winIsDir(zConverted) ){ + sqlite3_free(zConverted); + sqlite3_free(zTmpname); + OSTRACE(("OPEN name=%s, rc=SQLITE_CANTOPEN_ISDIR", zUtf8Name)); + return SQLITE_CANTOPEN_ISDIR; + } + + if( isReadWrite ){ + dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; + }else{ + dwDesiredAccess = GENERIC_READ; + } + + /* SQLITE_OPEN_EXCLUSIVE is used to make sure that a new file is + ** created. SQLite doesn't use it to indicate "exclusive access" + ** as it is usually understood. + */ + if( isExclusive ){ + /* Creates a new file, only if it does not already exist. */ + /* If the file exists, it fails. */ + dwCreationDisposition = CREATE_NEW; + }else if( isCreate ){ + /* Open existing file, or create if it doesn't exist */ + dwCreationDisposition = OPEN_ALWAYS; + }else{ + /* Opens a file, only if it exists. */ + dwCreationDisposition = OPEN_EXISTING; + } + + if( 0==sqlite3_uri_boolean(zName, "exclusive", 0) ){ + dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + }else{ + dwShareMode = 0; + } + + if( isDelete ){ +#if SQLITE_OS_WINCE + dwFlagsAndAttributes = FILE_ATTRIBUTE_HIDDEN; + isTemp = 1; +#else + dwFlagsAndAttributes = FILE_ATTRIBUTE_TEMPORARY + | FILE_ATTRIBUTE_HIDDEN + | FILE_FLAG_DELETE_ON_CLOSE; +#endif + }else{ + dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; + } + /* Reports from the internet are that performance is always + ** better if FILE_FLAG_RANDOM_ACCESS is used. Ticket #2699. */ +#if SQLITE_OS_WINCE + dwFlagsAndAttributes |= FILE_FLAG_RANDOM_ACCESS; +#endif + + if( osIsNT() ){ +#if SQLITE_OS_WINRT + CREATEFILE2_EXTENDED_PARAMETERS extendedParameters; + extendedParameters.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS); + extendedParameters.dwFileAttributes = + dwFlagsAndAttributes & FILE_ATTRIBUTE_MASK; + extendedParameters.dwFileFlags = dwFlagsAndAttributes & FILE_FLAG_MASK; + extendedParameters.dwSecurityQosFlags = SECURITY_ANONYMOUS; + extendedParameters.lpSecurityAttributes = NULL; + extendedParameters.hTemplateFile = NULL; + do{ + h = osCreateFile2((LPCWSTR)zConverted, + dwDesiredAccess, + dwShareMode, + dwCreationDisposition, + &extendedParameters); + if( h!=INVALID_HANDLE_VALUE ) break; + if( isReadWrite ){ + int rc2, isRO = 0; + sqlite3BeginBenignMalloc(); + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); + sqlite3EndBenignMalloc(); + if( rc2==SQLITE_OK && isRO ) break; + } + }while( winRetryIoerr(&cnt, &lastErrno) ); +#else + do{ + h = osCreateFileW((LPCWSTR)zConverted, + dwDesiredAccess, + dwShareMode, NULL, + dwCreationDisposition, + dwFlagsAndAttributes, + NULL); + if( h!=INVALID_HANDLE_VALUE ) break; + if( isReadWrite ){ + int rc2, isRO = 0; + sqlite3BeginBenignMalloc(); + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); + sqlite3EndBenignMalloc(); + if( rc2==SQLITE_OK && isRO ) break; + } + }while( winRetryIoerr(&cnt, &lastErrno) ); +#endif + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + do{ + h = osCreateFileA((LPCSTR)zConverted, + dwDesiredAccess, + dwShareMode, NULL, + dwCreationDisposition, + dwFlagsAndAttributes, + NULL); + if( h!=INVALID_HANDLE_VALUE ) break; + if( isReadWrite ){ + int rc2, isRO = 0; + sqlite3BeginBenignMalloc(); + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); + sqlite3EndBenignMalloc(); + if( rc2==SQLITE_OK && isRO ) break; + } + }while( winRetryIoerr(&cnt, &lastErrno) ); + } +#endif + winLogIoerr(cnt, __LINE__); + + OSTRACE(("OPEN file=%p, name=%s, access=%lx, rc=%s\n", h, zUtf8Name, + dwDesiredAccess, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok")); + + if( h==INVALID_HANDLE_VALUE ){ + sqlite3_free(zConverted); + sqlite3_free(zTmpname); + if( isReadWrite && !isExclusive ){ + return winOpen(pVfs, zName, id, + ((flags|SQLITE_OPEN_READONLY) & + ~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), + pOutFlags); + }else{ + pFile->lastErrno = lastErrno; + winLogError(SQLITE_CANTOPEN, pFile->lastErrno, "winOpen", zUtf8Name); + return SQLITE_CANTOPEN_BKPT; + } + } + + if( pOutFlags ){ + if( isReadWrite ){ + *pOutFlags = SQLITE_OPEN_READWRITE; + }else{ + *pOutFlags = SQLITE_OPEN_READONLY; + } + } + + OSTRACE(("OPEN file=%p, name=%s, access=%lx, pOutFlags=%p, *pOutFlags=%d, " + "rc=%s\n", h, zUtf8Name, dwDesiredAccess, pOutFlags, pOutFlags ? + *pOutFlags : 0, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok")); + + pAppData = (winVfsAppData*)pVfs->pAppData; + +#if SQLITE_OS_WINCE + { + if( isReadWrite && eType==SQLITE_OPEN_MAIN_DB + && ((pAppData==NULL) || !pAppData->bNoLock) + && (rc = winceCreateLock(zName, pFile))!=SQLITE_OK + ){ + osCloseHandle(h); + sqlite3_free(zConverted); + sqlite3_free(zTmpname); + OSTRACE(("OPEN-CE-LOCK name=%s, rc=%s\n", zName, sqlite3ErrName(rc))); + return rc; + } + } + if( isTemp ){ + pFile->zDeleteOnClose = zConverted; + }else +#endif + { + sqlite3_free(zConverted); + } + + sqlite3_free(zTmpname); + id->pMethods = pAppData ? pAppData->pMethod : &winIoMethod; + pFile->pVfs = pVfs; + pFile->h = h; + if( isReadonly ){ + pFile->ctrlFlags |= WINFILE_RDONLY; + } + if( (flags & SQLITE_OPEN_MAIN_DB) + && sqlite3_uri_boolean(zName, "psow", SQLITE_POWERSAFE_OVERWRITE) + ){ + pFile->ctrlFlags |= WINFILE_PSOW; + } + pFile->lastErrno = NO_ERROR; + pFile->zPath = zName; +#if SQLITE_MAX_MMAP_SIZE>0 + pFile->hMap = NULL; + pFile->pMapRegion = 0; + pFile->mmapSize = 0; + pFile->mmapSizeMax = sqlite3GlobalConfig.szMmap; +#endif + + OpenCounter(+1); + return rc; +} + +/* +** Delete the named file. +** +** Note that Windows does not allow a file to be deleted if some other +** process has it open. Sometimes a virus scanner or indexing program +** will open a journal file shortly after it is created in order to do +** whatever it does. While this other process is holding the +** file open, we will be unable to delete it. To work around this +** problem, we delay 100 milliseconds and try to delete again. Up +** to MX_DELETION_ATTEMPTs deletion attempts are run before giving +** up and returning an error. +*/ +static int winDelete( + sqlite3_vfs *pVfs, /* Not used on win32 */ + const char *zFilename, /* Name of file to delete */ + int syncDir /* Not used on win32 */ +){ + int cnt = 0; + int rc; + DWORD attr; + DWORD lastErrno = 0; + void *zConverted; + UNUSED_PARAMETER(pVfs); + UNUSED_PARAMETER(syncDir); + + SimulateIOError(return SQLITE_IOERR_DELETE); + OSTRACE(("DELETE name=%s, syncDir=%d\n", zFilename, syncDir)); + + zConverted = winConvertFromUtf8Filename(zFilename); + if( zConverted==0 ){ + OSTRACE(("DELETE name=%s, rc=SQLITE_IOERR_NOMEM\n", zFilename)); + return SQLITE_IOERR_NOMEM_BKPT; + } + if( osIsNT() ){ + do { +#if SQLITE_OS_WINRT + WIN32_FILE_ATTRIBUTE_DATA sAttrData; + memset(&sAttrData, 0, sizeof(sAttrData)); + if ( osGetFileAttributesExW(zConverted, GetFileExInfoStandard, + &sAttrData) ){ + attr = sAttrData.dwFileAttributes; + }else{ + lastErrno = osGetLastError(); + if( lastErrno==ERROR_FILE_NOT_FOUND + || lastErrno==ERROR_PATH_NOT_FOUND ){ + rc = SQLITE_IOERR_DELETE_NOENT; /* Already gone? */ + }else{ + rc = SQLITE_ERROR; + } + break; + } +#else + attr = osGetFileAttributesW(zConverted); +#endif + if ( attr==INVALID_FILE_ATTRIBUTES ){ + lastErrno = osGetLastError(); + if( lastErrno==ERROR_FILE_NOT_FOUND + || lastErrno==ERROR_PATH_NOT_FOUND ){ + rc = SQLITE_IOERR_DELETE_NOENT; /* Already gone? */ + }else{ + rc = SQLITE_ERROR; + } + break; + } + if ( attr&FILE_ATTRIBUTE_DIRECTORY ){ + rc = SQLITE_ERROR; /* Files only. */ + break; + } + if ( osDeleteFileW(zConverted) ){ + rc = SQLITE_OK; /* Deleted OK. */ + break; + } + if ( !winRetryIoerr(&cnt, &lastErrno) ){ + rc = SQLITE_ERROR; /* No more retries. */ + break; + } + } while(1); + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + do { + attr = osGetFileAttributesA(zConverted); + if ( attr==INVALID_FILE_ATTRIBUTES ){ + lastErrno = osGetLastError(); + if( lastErrno==ERROR_FILE_NOT_FOUND + || lastErrno==ERROR_PATH_NOT_FOUND ){ + rc = SQLITE_IOERR_DELETE_NOENT; /* Already gone? */ + }else{ + rc = SQLITE_ERROR; + } + break; + } + if ( attr&FILE_ATTRIBUTE_DIRECTORY ){ + rc = SQLITE_ERROR; /* Files only. */ + break; + } + if ( osDeleteFileA(zConverted) ){ + rc = SQLITE_OK; /* Deleted OK. */ + break; + } + if ( !winRetryIoerr(&cnt, &lastErrno) ){ + rc = SQLITE_ERROR; /* No more retries. */ + break; + } + } while(1); + } +#endif + if( rc && rc!=SQLITE_IOERR_DELETE_NOENT ){ + rc = winLogError(SQLITE_IOERR_DELETE, lastErrno, "winDelete", zFilename); + }else{ + winLogIoerr(cnt, __LINE__); + } + sqlite3_free(zConverted); + OSTRACE(("DELETE name=%s, rc=%s\n", zFilename, sqlite3ErrName(rc))); + return rc; +} + +/* +** Check the existence and status of a file. +*/ +static int winAccess( + sqlite3_vfs *pVfs, /* Not used on win32 */ + const char *zFilename, /* Name of file to check */ + int flags, /* Type of test to make on this file */ + int *pResOut /* OUT: Result */ +){ + DWORD attr; + int rc = 0; + DWORD lastErrno = 0; + void *zConverted; + UNUSED_PARAMETER(pVfs); + + SimulateIOError( return SQLITE_IOERR_ACCESS; ); + OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n", + zFilename, flags, pResOut)); + + if( zFilename==0 ){ + *pResOut = 0; + OSTRACE(("ACCESS name=%s, pResOut=%p, *pResOut=%d, rc=SQLITE_OK\n", + zFilename, pResOut, *pResOut)); + return SQLITE_OK; + } + + zConverted = winConvertFromUtf8Filename(zFilename); + if( zConverted==0 ){ + OSTRACE(("ACCESS name=%s, rc=SQLITE_IOERR_NOMEM\n", zFilename)); + return SQLITE_IOERR_NOMEM_BKPT; + } + if( osIsNT() ){ + int cnt = 0; + WIN32_FILE_ATTRIBUTE_DATA sAttrData; + memset(&sAttrData, 0, sizeof(sAttrData)); + while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted, + GetFileExInfoStandard, + &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){} + if( rc ){ + /* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file + ** as if it does not exist. + */ + if( flags==SQLITE_ACCESS_EXISTS + && sAttrData.nFileSizeHigh==0 + && sAttrData.nFileSizeLow==0 ){ + attr = INVALID_FILE_ATTRIBUTES; + }else{ + attr = sAttrData.dwFileAttributes; + } + }else{ + winLogIoerr(cnt, __LINE__); + if( lastErrno!=ERROR_FILE_NOT_FOUND && lastErrno!=ERROR_PATH_NOT_FOUND ){ + sqlite3_free(zConverted); + return winLogError(SQLITE_IOERR_ACCESS, lastErrno, "winAccess", + zFilename); + }else{ + attr = INVALID_FILE_ATTRIBUTES; + } + } + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + attr = osGetFileAttributesA((char*)zConverted); + } +#endif + sqlite3_free(zConverted); + switch( flags ){ + case SQLITE_ACCESS_READ: + case SQLITE_ACCESS_EXISTS: + rc = attr!=INVALID_FILE_ATTRIBUTES; + break; + case SQLITE_ACCESS_READWRITE: + rc = attr!=INVALID_FILE_ATTRIBUTES && + (attr & FILE_ATTRIBUTE_READONLY)==0; + break; + default: + assert(!"Invalid flags argument"); + } + *pResOut = rc; + OSTRACE(("ACCESS name=%s, pResOut=%p, *pResOut=%d, rc=SQLITE_OK\n", + zFilename, pResOut, *pResOut)); + return SQLITE_OK; +} + +/* +** Returns non-zero if the specified path name starts with the "long path" +** prefix. +*/ +static BOOL winIsLongPathPrefix( + const char *zPathname +){ + return ( zPathname[0]=='\\' && zPathname[1]=='\\' + && zPathname[2]=='?' && zPathname[3]=='\\' ); +} + +/* +** Returns non-zero if the specified path name starts with a drive letter +** followed by a colon character. +*/ +static BOOL winIsDriveLetterAndColon( + const char *zPathname +){ + return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' ); +} + +/* +** Returns non-zero if the specified path name should be used verbatim. If +** non-zero is returned from this function, the calling function must simply +** use the provided path name verbatim -OR- resolve it into a full path name +** using the GetFullPathName Win32 API function (if available). +*/ +static BOOL winIsVerbatimPathname( + const char *zPathname +){ + /* + ** If the path name starts with a forward slash or a backslash, it is either + ** a legal UNC name, a volume relative path, or an absolute path name in the + ** "Unix" format on Windows. There is no easy way to differentiate between + ** the final two cases; therefore, we return the safer return value of TRUE + ** so that callers of this function will simply use it verbatim. + */ + if ( winIsDirSep(zPathname[0]) ){ + return TRUE; + } + + /* + ** If the path name starts with a letter and a colon it is either a volume + ** relative path or an absolute path. Callers of this function must not + ** attempt to treat it as a relative path name (i.e. they should simply use + ** it verbatim). + */ + if ( winIsDriveLetterAndColon(zPathname) ){ + return TRUE; + } + + /* + ** If we get to this point, the path name should almost certainly be a purely + ** relative one (i.e. not a UNC name, not absolute, and not volume relative). + */ + return FALSE; +} + +/* +** Turn a relative pathname into a full pathname. Write the full +** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname +** bytes in size. +*/ +static int winFullPathnameNoMutex( + sqlite3_vfs *pVfs, /* Pointer to vfs object */ + const char *zRelative, /* Possibly relative input path */ + int nFull, /* Size of output buffer in bytes */ + char *zFull /* Output buffer */ +){ +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__) + DWORD nByte; + void *zConverted; + char *zOut; +#endif + + /* If this path name begins with "/X:" or "\\?\", where "X" is any + ** alphabetic character, discard the initial "/" from the pathname. + */ + if( zRelative[0]=='/' && (winIsDriveLetterAndColon(zRelative+1) + || winIsLongPathPrefix(zRelative+1)) ){ + zRelative++; + } + +#if defined(__CYGWIN__) + SimulateIOError( return SQLITE_ERROR ); + UNUSED_PARAMETER(nFull); + assert( nFull>=pVfs->mxPathname ); + if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){ + /* + ** NOTE: We are dealing with a relative path name and the data + ** directory has been set. Therefore, use it as the basis + ** for converting the relative path name to an absolute + ** one by prepending the data directory and a slash. + */ + char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); + if( !zOut ){ + return SQLITE_IOERR_NOMEM_BKPT; + } + if( cygwin_conv_path( + (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A) | + CCP_RELATIVE, zRelative, zOut, pVfs->mxPathname+1)<0 ){ + sqlite3_free(zOut); + return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, + "winFullPathname1", zRelative); + }else{ + char *zUtf8 = winConvertToUtf8Filename(zOut); + if( !zUtf8 ){ + sqlite3_free(zOut); + return SQLITE_IOERR_NOMEM_BKPT; + } + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", + sqlite3_data_directory, winGetDirSep(), zUtf8); + sqlite3_free(zUtf8); + sqlite3_free(zOut); + } + }else{ + char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); + if( !zOut ){ + return SQLITE_IOERR_NOMEM_BKPT; + } + if( cygwin_conv_path( + (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A), + zRelative, zOut, pVfs->mxPathname+1)<0 ){ + sqlite3_free(zOut); + return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, + "winFullPathname2", zRelative); + }else{ + char *zUtf8 = winConvertToUtf8Filename(zOut); + if( !zUtf8 ){ + sqlite3_free(zOut); + return SQLITE_IOERR_NOMEM_BKPT; + } + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8); + sqlite3_free(zUtf8); + sqlite3_free(zOut); + } + } + return SQLITE_OK; +#endif + +#if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && !defined(__CYGWIN__) + SimulateIOError( return SQLITE_ERROR ); + /* WinCE has no concept of a relative pathname, or so I am told. */ + /* WinRT has no way to convert a relative path to an absolute one. */ + if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){ + /* + ** NOTE: We are dealing with a relative path name and the data + ** directory has been set. Therefore, use it as the basis + ** for converting the relative path name to an absolute + ** one by prepending the data directory and a backslash. + */ + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", + sqlite3_data_directory, winGetDirSep(), zRelative); + }else{ + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative); + } + return SQLITE_OK; +#endif + +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__) + /* It's odd to simulate an io-error here, but really this is just + ** using the io-error infrastructure to test that SQLite handles this + ** function failing. This function could fail if, for example, the + ** current working directory has been unlinked. + */ + SimulateIOError( return SQLITE_ERROR ); + if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){ + /* + ** NOTE: We are dealing with a relative path name and the data + ** directory has been set. Therefore, use it as the basis + ** for converting the relative path name to an absolute + ** one by prepending the data directory and a backslash. + */ + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", + sqlite3_data_directory, winGetDirSep(), zRelative); + return SQLITE_OK; + } + zConverted = winConvertFromUtf8Filename(zRelative); + if( zConverted==0 ){ + return SQLITE_IOERR_NOMEM_BKPT; + } + if( osIsNT() ){ + LPWSTR zTemp; + nByte = osGetFullPathNameW((LPCWSTR)zConverted, 0, 0, 0); + if( nByte==0 ){ + sqlite3_free(zConverted); + return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), + "winFullPathname1", zRelative); + } + nByte += 3; + zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) ); + if( zTemp==0 ){ + sqlite3_free(zConverted); + return SQLITE_IOERR_NOMEM_BKPT; + } + nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte, zTemp, 0); + if( nByte==0 ){ + sqlite3_free(zConverted); + sqlite3_free(zTemp); + return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), + "winFullPathname2", zRelative); + } + sqlite3_free(zConverted); + zOut = winUnicodeToUtf8(zTemp); + sqlite3_free(zTemp); + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + char *zTemp; + nByte = osGetFullPathNameA((char*)zConverted, 0, 0, 0); + if( nByte==0 ){ + sqlite3_free(zConverted); + return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), + "winFullPathname3", zRelative); + } + nByte += 3; + zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) ); + if( zTemp==0 ){ + sqlite3_free(zConverted); + return SQLITE_IOERR_NOMEM_BKPT; + } + nByte = osGetFullPathNameA((char*)zConverted, nByte, zTemp, 0); + if( nByte==0 ){ + sqlite3_free(zConverted); + sqlite3_free(zTemp); + return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), + "winFullPathname4", zRelative); + } + sqlite3_free(zConverted); + zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI()); + sqlite3_free(zTemp); + } +#endif + if( zOut ){ + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut); + sqlite3_free(zOut); + return SQLITE_OK; + }else{ + return SQLITE_IOERR_NOMEM_BKPT; + } +#endif +} +static int winFullPathname( + sqlite3_vfs *pVfs, /* Pointer to vfs object */ + const char *zRelative, /* Possibly relative input path */ + int nFull, /* Size of output buffer in bytes */ + char *zFull /* Output buffer */ +){ + int rc; + MUTEX_LOGIC( sqlite3_mutex *pMutex; ) + MUTEX_LOGIC( pMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR); ) + sqlite3_mutex_enter(pMutex); + rc = winFullPathnameNoMutex(pVfs, zRelative, nFull, zFull); + sqlite3_mutex_leave(pMutex); + return rc; +} + +#ifndef SQLITE_OMIT_LOAD_EXTENSION +/* +** Interfaces for opening a shared library, finding entry points +** within the shared library, and closing the shared library. +*/ +static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){ + HANDLE h; +#if defined(__CYGWIN__) + int nFull = pVfs->mxPathname+1; + char *zFull = sqlite3MallocZero( nFull ); + void *zConverted = 0; + if( zFull==0 ){ + OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); + return 0; + } + if( winFullPathname(pVfs, zFilename, nFull, zFull)!=SQLITE_OK ){ + sqlite3_free(zFull); + OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); + return 0; + } + zConverted = winConvertFromUtf8Filename(zFull); + sqlite3_free(zFull); +#else + void *zConverted = winConvertFromUtf8Filename(zFilename); + UNUSED_PARAMETER(pVfs); +#endif + if( zConverted==0 ){ + OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); + return 0; + } + if( osIsNT() ){ +#if SQLITE_OS_WINRT + h = osLoadPackagedLibrary((LPCWSTR)zConverted, 0); +#else + h = osLoadLibraryW((LPCWSTR)zConverted); +#endif + } +#ifdef SQLITE_WIN32_HAS_ANSI + else{ + h = osLoadLibraryA((char*)zConverted); + } +#endif + OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)h)); + sqlite3_free(zConverted); + return (void*)h; +} +static void winDlError(sqlite3_vfs *pVfs, int nBuf, char *zBufOut){ + UNUSED_PARAMETER(pVfs); + winGetLastErrorMsg(osGetLastError(), nBuf, zBufOut); +} +static void (*winDlSym(sqlite3_vfs *pVfs,void *pH,const char *zSym))(void){ + FARPROC proc; + UNUSED_PARAMETER(pVfs); + proc = osGetProcAddressA((HANDLE)pH, zSym); + OSTRACE(("DLSYM handle=%p, symbol=%s, address=%p\n", + (void*)pH, zSym, (void*)proc)); + return (void(*)(void))proc; +} +static void winDlClose(sqlite3_vfs *pVfs, void *pHandle){ + UNUSED_PARAMETER(pVfs); + osFreeLibrary((HANDLE)pHandle); + OSTRACE(("DLCLOSE handle=%p\n", (void*)pHandle)); +} +#else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */ + #define winDlOpen 0 + #define winDlError 0 + #define winDlSym 0 + #define winDlClose 0 +#endif + +/* State information for the randomness gatherer. */ +typedef struct EntropyGatherer EntropyGatherer; +struct EntropyGatherer { + unsigned char *a; /* Gather entropy into this buffer */ + int na; /* Size of a[] in bytes */ + int i; /* XOR next input into a[i] */ + int nXor; /* Number of XOR operations done */ +}; + +#if !defined(SQLITE_TEST) && !defined(SQLITE_OMIT_RANDOMNESS) +/* Mix sz bytes of entropy into p. */ +static void xorMemory(EntropyGatherer *p, unsigned char *x, int sz){ + int j, k; + for(j=0, k=p->i; j<sz; j++){ + p->a[k++] ^= x[j]; + if( k>=p->na ) k = 0; + } + p->i = k; + p->nXor += sz; +} +#endif /* !defined(SQLITE_TEST) && !defined(SQLITE_OMIT_RANDOMNESS) */ + +/* +** Write up to nBuf bytes of randomness into zBuf. +*/ +static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ +#if defined(SQLITE_TEST) || defined(SQLITE_OMIT_RANDOMNESS) + UNUSED_PARAMETER(pVfs); + memset(zBuf, 0, nBuf); + return nBuf; +#else + EntropyGatherer e; + UNUSED_PARAMETER(pVfs); + memset(zBuf, 0, nBuf); + e.a = (unsigned char*)zBuf; + e.na = nBuf; + e.nXor = 0; + e.i = 0; + { + SYSTEMTIME x; + osGetSystemTime(&x); + xorMemory(&e, (unsigned char*)&x, sizeof(SYSTEMTIME)); + } + { + DWORD pid = osGetCurrentProcessId(); + xorMemory(&e, (unsigned char*)&pid, sizeof(DWORD)); + } +#if SQLITE_OS_WINRT + { + ULONGLONG cnt = osGetTickCount64(); + xorMemory(&e, (unsigned char*)&cnt, sizeof(ULONGLONG)); + } +#else + { + DWORD cnt = osGetTickCount(); + xorMemory(&e, (unsigned char*)&cnt, sizeof(DWORD)); + } +#endif /* SQLITE_OS_WINRT */ + { + LARGE_INTEGER i; + osQueryPerformanceCounter(&i); + xorMemory(&e, (unsigned char*)&i, sizeof(LARGE_INTEGER)); + } +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID + { + UUID id; + memset(&id, 0, sizeof(UUID)); + osUuidCreate(&id); + xorMemory(&e, (unsigned char*)&id, sizeof(UUID)); + memset(&id, 0, sizeof(UUID)); + osUuidCreateSequential(&id); + xorMemory(&e, (unsigned char*)&id, sizeof(UUID)); + } +#endif /* !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID */ + return e.nXor>nBuf ? nBuf : e.nXor; +#endif /* defined(SQLITE_TEST) || defined(SQLITE_OMIT_RANDOMNESS) */ +} + + +/* +** Sleep for a little while. Return the amount of time slept. +*/ +static int winSleep(sqlite3_vfs *pVfs, int microsec){ + sqlite3_win32_sleep((microsec+999)/1000); + UNUSED_PARAMETER(pVfs); + return ((microsec+999)/1000)*1000; +} + +/* +** The following variable, if set to a non-zero value, is interpreted as +** the number of seconds since 1970 and is used to set the result of +** sqlite3OsCurrentTime() during testing. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_current_time = 0; /* Fake system time in seconds since 1970. */ +#endif + +/* +** Find the current time (in Universal Coordinated Time). Write into *piNow +** the current time and date as a Julian Day number times 86_400_000. In +** other words, write into *piNow the number of milliseconds since the Julian +** epoch of noon in Greenwich on November 24, 4714 B.C according to the +** proleptic Gregorian calendar. +** +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** cannot be found. +*/ +static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){ + /* FILETIME structure is a 64-bit value representing the number of + 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). + */ + FILETIME ft; + static const sqlite3_int64 winFiletimeEpoch = 23058135*(sqlite3_int64)8640000; +#ifdef SQLITE_TEST + static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; +#endif + /* 2^32 - to avoid use of LL and warnings in gcc */ + static const sqlite3_int64 max32BitValue = + (sqlite3_int64)2000000000 + (sqlite3_int64)2000000000 + + (sqlite3_int64)294967296; + +#if SQLITE_OS_WINCE + SYSTEMTIME time; + osGetSystemTime(&time); + /* if SystemTimeToFileTime() fails, it returns zero. */ + if (!osSystemTimeToFileTime(&time,&ft)){ + return SQLITE_ERROR; + } +#else + osGetSystemTimeAsFileTime( &ft ); +#endif + + *piNow = winFiletimeEpoch + + ((((sqlite3_int64)ft.dwHighDateTime)*max32BitValue) + + (sqlite3_int64)ft.dwLowDateTime)/(sqlite3_int64)10000; + +#ifdef SQLITE_TEST + if( sqlite3_current_time ){ + *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch; + } +#endif + UNUSED_PARAMETER(pVfs); + return SQLITE_OK; +} + +/* +** Find the current time (in Universal Coordinated Time). Write the +** current time and date as a Julian Day number into *prNow and +** return 0. Return 1 if the time and date cannot be found. +*/ +static int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){ + int rc; + sqlite3_int64 i; + rc = winCurrentTimeInt64(pVfs, &i); + if( !rc ){ + *prNow = i/86400000.0; + } + return rc; +} + +/* +** The idea is that this function works like a combination of +** GetLastError() and FormatMessage() on Windows (or errno and +** strerror_r() on Unix). After an error is returned by an OS +** function, SQLite calls this function with zBuf pointing to +** a buffer of nBuf bytes. The OS layer should populate the +** buffer with a nul-terminated UTF-8 encoded error message +** describing the last IO error to have occurred within the calling +** thread. +** +** If the error message is too large for the supplied buffer, +** it should be truncated. The return value of xGetLastError +** is zero if the error message fits in the buffer, or non-zero +** otherwise (if the message was truncated). If non-zero is returned, +** then it is not necessary to include the nul-terminator character +** in the output buffer. +** +** Not supplying an error message will have no adverse effect +** on SQLite. It is fine to have an implementation that never +** returns an error message: +** +** int xGetLastError(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ +** assert(zBuf[0]=='\0'); +** return 0; +** } +** +** However if an error message is supplied, it will be incorporated +** by sqlite into the error message available to the user using +** sqlite3_errmsg(), possibly making IO errors easier to debug. +*/ +static int winGetLastError(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ + DWORD e = osGetLastError(); + UNUSED_PARAMETER(pVfs); + if( nBuf>0 ) winGetLastErrorMsg(e, nBuf, zBuf); + return e; +} + +/* +** Initialize and deinitialize the operating system interface. +*/ +SQLITE_API int sqlite3_os_init(void){ + static sqlite3_vfs winVfs = { + 3, /* iVersion */ + sizeof(winFile), /* szOsFile */ + SQLITE_WIN32_MAX_PATH_BYTES, /* mxPathname */ + 0, /* pNext */ + "win32", /* zName */ + &winAppData, /* pAppData */ + winOpen, /* xOpen */ + winDelete, /* xDelete */ + winAccess, /* xAccess */ + winFullPathname, /* xFullPathname */ + winDlOpen, /* xDlOpen */ + winDlError, /* xDlError */ + winDlSym, /* xDlSym */ + winDlClose, /* xDlClose */ + winRandomness, /* xRandomness */ + winSleep, /* xSleep */ + winCurrentTime, /* xCurrentTime */ + winGetLastError, /* xGetLastError */ + winCurrentTimeInt64, /* xCurrentTimeInt64 */ + winSetSystemCall, /* xSetSystemCall */ + winGetSystemCall, /* xGetSystemCall */ + winNextSystemCall, /* xNextSystemCall */ + }; +#if defined(SQLITE_WIN32_HAS_WIDE) + static sqlite3_vfs winLongPathVfs = { + 3, /* iVersion */ + sizeof(winFile), /* szOsFile */ + SQLITE_WINNT_MAX_PATH_BYTES, /* mxPathname */ + 0, /* pNext */ + "win32-longpath", /* zName */ + &winAppData, /* pAppData */ + winOpen, /* xOpen */ + winDelete, /* xDelete */ + winAccess, /* xAccess */ + winFullPathname, /* xFullPathname */ + winDlOpen, /* xDlOpen */ + winDlError, /* xDlError */ + winDlSym, /* xDlSym */ + winDlClose, /* xDlClose */ + winRandomness, /* xRandomness */ + winSleep, /* xSleep */ + winCurrentTime, /* xCurrentTime */ + winGetLastError, /* xGetLastError */ + winCurrentTimeInt64, /* xCurrentTimeInt64 */ + winSetSystemCall, /* xSetSystemCall */ + winGetSystemCall, /* xGetSystemCall */ + winNextSystemCall, /* xNextSystemCall */ + }; +#endif + static sqlite3_vfs winNolockVfs = { + 3, /* iVersion */ + sizeof(winFile), /* szOsFile */ + SQLITE_WIN32_MAX_PATH_BYTES, /* mxPathname */ + 0, /* pNext */ + "win32-none", /* zName */ + &winNolockAppData, /* pAppData */ + winOpen, /* xOpen */ + winDelete, /* xDelete */ + winAccess, /* xAccess */ + winFullPathname, /* xFullPathname */ + winDlOpen, /* xDlOpen */ + winDlError, /* xDlError */ + winDlSym, /* xDlSym */ + winDlClose, /* xDlClose */ + winRandomness, /* xRandomness */ + winSleep, /* xSleep */ + winCurrentTime, /* xCurrentTime */ + winGetLastError, /* xGetLastError */ + winCurrentTimeInt64, /* xCurrentTimeInt64 */ + winSetSystemCall, /* xSetSystemCall */ + winGetSystemCall, /* xGetSystemCall */ + winNextSystemCall, /* xNextSystemCall */ + }; +#if defined(SQLITE_WIN32_HAS_WIDE) + static sqlite3_vfs winLongPathNolockVfs = { + 3, /* iVersion */ + sizeof(winFile), /* szOsFile */ + SQLITE_WINNT_MAX_PATH_BYTES, /* mxPathname */ + 0, /* pNext */ + "win32-longpath-none", /* zName */ + &winNolockAppData, /* pAppData */ + winOpen, /* xOpen */ + winDelete, /* xDelete */ + winAccess, /* xAccess */ + winFullPathname, /* xFullPathname */ + winDlOpen, /* xDlOpen */ + winDlError, /* xDlError */ + winDlSym, /* xDlSym */ + winDlClose, /* xDlClose */ + winRandomness, /* xRandomness */ + winSleep, /* xSleep */ + winCurrentTime, /* xCurrentTime */ + winGetLastError, /* xGetLastError */ + winCurrentTimeInt64, /* xCurrentTimeInt64 */ + winSetSystemCall, /* xSetSystemCall */ + winGetSystemCall, /* xGetSystemCall */ + winNextSystemCall, /* xNextSystemCall */ + }; +#endif + + /* Double-check that the aSyscall[] array has been constructed + ** correctly. See ticket [bb3a86e890c8e96ab] */ + assert( ArraySize(aSyscall)==80 ); + + /* get memory map allocation granularity */ + memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); +#if SQLITE_OS_WINRT + osGetNativeSystemInfo(&winSysInfo); +#else + osGetSystemInfo(&winSysInfo); +#endif + assert( winSysInfo.dwAllocationGranularity>0 ); + assert( winSysInfo.dwPageSize>0 ); + + sqlite3_vfs_register(&winVfs, 1); + +#if defined(SQLITE_WIN32_HAS_WIDE) + sqlite3_vfs_register(&winLongPathVfs, 0); +#endif + + sqlite3_vfs_register(&winNolockVfs, 0); + +#if defined(SQLITE_WIN32_HAS_WIDE) + sqlite3_vfs_register(&winLongPathNolockVfs, 0); +#endif + +#ifndef SQLITE_OMIT_WAL + winBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#endif + + return SQLITE_OK; +} + +SQLITE_API int sqlite3_os_end(void){ +#if SQLITE_OS_WINRT + if( sleepObj!=NULL ){ + osCloseHandle(sleepObj); + sleepObj = NULL; + } +#endif + +#ifndef SQLITE_OMIT_WAL + winBigLock = 0; +#endif + + return SQLITE_OK; +} + +#endif /* SQLITE_OS_WIN */ + +/************** End of os_win.c **********************************************/ +/************** Begin file memdb.c *******************************************/ +/* +** 2016-09-07 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file implements an in-memory VFS. A database is held as a contiguous +** block of memory. +** +** This file also implements interface sqlite3_serialize() and +** sqlite3_deserialize(). +*/ +/* #include "sqliteInt.h" */ +#ifndef SQLITE_OMIT_DESERIALIZE + +/* +** Forward declaration of objects used by this utility +*/ +typedef struct sqlite3_vfs MemVfs; +typedef struct MemFile MemFile; +typedef struct MemStore MemStore; + +/* Access to a lower-level VFS that (might) implement dynamic loading, +** access to randomness, etc. +*/ +#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) + +/* Storage for a memdb file. +** +** An memdb object can be shared or separate. Shared memdb objects can be +** used by more than one database connection. Mutexes are used by shared +** memdb objects to coordinate access. Separate memdb objects are only +** connected to a single database connection and do not require additional +** mutexes. +** +** Shared memdb objects have .zFName!=0 and .pMutex!=0. They are created +** using "file:/name?vfs=memdb". The first character of the name must be +** "/" or else the object will be a separate memdb object. All shared +** memdb objects are stored in memdb_g.apMemStore[] in an arbitrary order. +** +** Separate memdb objects are created using a name that does not begin +** with "/" or using sqlite3_deserialize(). +** +** Access rules for shared MemStore objects: +** +** * .zFName is initialized when the object is created and afterwards +** is unchanged until the object is destroyed. So it can be accessed +** at any time as long as we know the object is not being destroyed, +** which means while either the SQLITE_MUTEX_STATIC_VFS1 or +** .pMutex is held or the object is not part of memdb_g.apMemStore[]. +** +** * Can .pMutex can only be changed while holding the +** SQLITE_MUTEX_STATIC_VFS1 mutex or while the object is not part +** of memdb_g.apMemStore[]. +** +** * Other fields can only be changed while holding the .pMutex mutex +** or when the .nRef is less than zero and the object is not part of +** memdb_g.apMemStore[]. +** +** * The .aData pointer has the added requirement that it can can only +** be changed (for resizing) when nMmap is zero. +** +*/ +struct MemStore { + sqlite3_int64 sz; /* Size of the file */ + sqlite3_int64 szAlloc; /* Space allocated to aData */ + sqlite3_int64 szMax; /* Maximum allowed size of the file */ + unsigned char *aData; /* content of the file */ + sqlite3_mutex *pMutex; /* Used by shared stores only */ + int nMmap; /* Number of memory mapped pages */ + unsigned mFlags; /* Flags */ + int nRdLock; /* Number of readers */ + int nWrLock; /* Number of writers. (Always 0 or 1) */ + int nRef; /* Number of users of this MemStore */ + char *zFName; /* The filename for shared stores */ +}; + +/* An open file */ +struct MemFile { + sqlite3_file base; /* IO methods */ + MemStore *pStore; /* The storage */ + int eLock; /* Most recent lock against this file */ +}; + +/* +** File-scope variables for holding the memdb files that are accessible +** to multiple database connections in separate threads. +** +** Must hold SQLITE_MUTEX_STATIC_VFS1 to access any part of this object. +*/ +static struct MemFS { + int nMemStore; /* Number of shared MemStore objects */ + MemStore **apMemStore; /* Array of all shared MemStore objects */ +} memdb_g; + +/* +** Methods for MemFile +*/ +static int memdbClose(sqlite3_file*); +static int memdbRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int memdbWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst); +static int memdbTruncate(sqlite3_file*, sqlite3_int64 size); +static int memdbSync(sqlite3_file*, int flags); +static int memdbFileSize(sqlite3_file*, sqlite3_int64 *pSize); +static int memdbLock(sqlite3_file*, int); +static int memdbUnlock(sqlite3_file*, int); +/* static int memdbCheckReservedLock(sqlite3_file*, int *pResOut);// not used */ +static int memdbFileControl(sqlite3_file*, int op, void *pArg); +/* static int memdbSectorSize(sqlite3_file*); // not used */ +static int memdbDeviceCharacteristics(sqlite3_file*); +static int memdbFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); +static int memdbUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p); + +/* +** Methods for MemVfs +*/ +static int memdbOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); +/* static int memdbDelete(sqlite3_vfs*, const char *zName, int syncDir); */ +static int memdbAccess(sqlite3_vfs*, const char *zName, int flags, int *); +static int memdbFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); +static void *memdbDlOpen(sqlite3_vfs*, const char *zFilename); +static void memdbDlError(sqlite3_vfs*, int nByte, char *zErrMsg); +static void (*memdbDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void); +static void memdbDlClose(sqlite3_vfs*, void*); +static int memdbRandomness(sqlite3_vfs*, int nByte, char *zOut); +static int memdbSleep(sqlite3_vfs*, int microseconds); +/* static int memdbCurrentTime(sqlite3_vfs*, double*); */ +static int memdbGetLastError(sqlite3_vfs*, int, char *); +static int memdbCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); + +static sqlite3_vfs memdb_vfs = { + 2, /* iVersion */ + 0, /* szOsFile (set when registered) */ + 1024, /* mxPathname */ + 0, /* pNext */ + "memdb", /* zName */ + 0, /* pAppData (set when registered) */ + memdbOpen, /* xOpen */ + 0, /* memdbDelete, */ /* xDelete */ + memdbAccess, /* xAccess */ + memdbFullPathname, /* xFullPathname */ + memdbDlOpen, /* xDlOpen */ + memdbDlError, /* xDlError */ + memdbDlSym, /* xDlSym */ + memdbDlClose, /* xDlClose */ + memdbRandomness, /* xRandomness */ + memdbSleep, /* xSleep */ + 0, /* memdbCurrentTime, */ /* xCurrentTime */ + memdbGetLastError, /* xGetLastError */ + memdbCurrentTimeInt64, /* xCurrentTimeInt64 */ + 0, /* xSetSystemCall */ + 0, /* xGetSystemCall */ + 0, /* xNextSystemCall */ +}; + +static const sqlite3_io_methods memdb_io_methods = { + 3, /* iVersion */ + memdbClose, /* xClose */ + memdbRead, /* xRead */ + memdbWrite, /* xWrite */ + memdbTruncate, /* xTruncate */ + memdbSync, /* xSync */ + memdbFileSize, /* xFileSize */ + memdbLock, /* xLock */ + memdbUnlock, /* xUnlock */ + 0, /* memdbCheckReservedLock, */ /* xCheckReservedLock */ + memdbFileControl, /* xFileControl */ + 0, /* memdbSectorSize,*/ /* xSectorSize */ + memdbDeviceCharacteristics, /* xDeviceCharacteristics */ + 0, /* xShmMap */ + 0, /* xShmLock */ + 0, /* xShmBarrier */ + 0, /* xShmUnmap */ + memdbFetch, /* xFetch */ + memdbUnfetch /* xUnfetch */ +}; + +/* +** Enter/leave the mutex on a MemStore +*/ +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 +static void memdbEnter(MemStore *p){ + UNUSED_PARAMETER(p); +} +static void memdbLeave(MemStore *p){ + UNUSED_PARAMETER(p); +} +#else +static void memdbEnter(MemStore *p){ + sqlite3_mutex_enter(p->pMutex); +} +static void memdbLeave(MemStore *p){ + sqlite3_mutex_leave(p->pMutex); +} +#endif + + + +/* +** Close an memdb-file. +** Free the underlying MemStore object when its refcount drops to zero +** or less. +*/ +static int memdbClose(sqlite3_file *pFile){ + MemStore *p = ((MemFile*)pFile)->pStore; + if( p->zFName ){ + int i; +#ifndef SQLITE_MUTEX_OMIT + sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#endif + sqlite3_mutex_enter(pVfsMutex); + for(i=0; ALWAYS(i<memdb_g.nMemStore); i++){ + if( memdb_g.apMemStore[i]==p ){ + memdbEnter(p); + if( p->nRef==1 ){ + memdb_g.apMemStore[i] = memdb_g.apMemStore[--memdb_g.nMemStore]; + if( memdb_g.nMemStore==0 ){ + sqlite3_free(memdb_g.apMemStore); + memdb_g.apMemStore = 0; + } + } + break; + } + } + sqlite3_mutex_leave(pVfsMutex); + }else{ + memdbEnter(p); + } + p->nRef--; + if( p->nRef<=0 ){ + if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ){ + sqlite3_free(p->aData); + } + memdbLeave(p); + sqlite3_mutex_free(p->pMutex); + sqlite3_free(p); + }else{ + memdbLeave(p); + } + return SQLITE_OK; +} + +/* +** Read data from an memdb-file. +*/ +static int memdbRead( + sqlite3_file *pFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); + if( iOfst+iAmt>p->sz ){ + memset(zBuf, 0, iAmt); + if( iOfst<p->sz ) memcpy(zBuf, p->aData+iOfst, p->sz - iOfst); + memdbLeave(p); + return SQLITE_IOERR_SHORT_READ; + } + memcpy(zBuf, p->aData+iOfst, iAmt); + memdbLeave(p); + return SQLITE_OK; +} + +/* +** Try to enlarge the memory allocation to hold at least sz bytes +*/ +static int memdbEnlarge(MemStore *p, sqlite3_int64 newSz){ + unsigned char *pNew; + if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || NEVER(p->nMmap>0) ){ + return SQLITE_FULL; + } + if( newSz>p->szMax ){ + return SQLITE_FULL; + } + newSz *= 2; + if( newSz>p->szMax ) newSz = p->szMax; + pNew = sqlite3Realloc(p->aData, newSz); + if( pNew==0 ) return SQLITE_IOERR_NOMEM; + p->aData = pNew; + p->szAlloc = newSz; + return SQLITE_OK; +} + +/* +** Write data to an memdb-file. +*/ +static int memdbWrite( + sqlite3_file *pFile, + const void *z, + int iAmt, + sqlite_int64 iOfst +){ + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); + if( NEVER(p->mFlags & SQLITE_DESERIALIZE_READONLY) ){ + /* Can't happen: memdbLock() will return SQLITE_READONLY before + ** reaching this point */ + memdbLeave(p); + return SQLITE_IOERR_WRITE; + } + if( iOfst+iAmt>p->sz ){ + int rc; + if( iOfst+iAmt>p->szAlloc + && (rc = memdbEnlarge(p, iOfst+iAmt))!=SQLITE_OK + ){ + memdbLeave(p); + return rc; + } + if( iOfst>p->sz ) memset(p->aData+p->sz, 0, iOfst-p->sz); + p->sz = iOfst+iAmt; + } + memcpy(p->aData+iOfst, z, iAmt); + memdbLeave(p); + return SQLITE_OK; +} + +/* +** Truncate an memdb-file. +** +** In rollback mode (which is always the case for memdb, as it does not +** support WAL mode) the truncate() method is only used to reduce +** the size of a file, never to increase the size. +*/ +static int memdbTruncate(sqlite3_file *pFile, sqlite_int64 size){ + MemStore *p = ((MemFile*)pFile)->pStore; + int rc = SQLITE_OK; + memdbEnter(p); + if( size>p->sz ){ + /* This can only happen with a corrupt wal mode db */ + rc = SQLITE_CORRUPT; + }else{ + p->sz = size; + } + memdbLeave(p); + return rc; +} + +/* +** Sync an memdb-file. +*/ +static int memdbSync(sqlite3_file *pFile, int flags){ + UNUSED_PARAMETER(pFile); + UNUSED_PARAMETER(flags); + return SQLITE_OK; +} + +/* +** Return the current file-size of an memdb-file. +*/ +static int memdbFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); + *pSize = p->sz; + memdbLeave(p); + return SQLITE_OK; +} + +/* +** Lock an memdb-file. +*/ +static int memdbLock(sqlite3_file *pFile, int eLock){ + MemFile *pThis = (MemFile*)pFile; + MemStore *p = pThis->pStore; + int rc = SQLITE_OK; + if( eLock<=pThis->eLock ) return SQLITE_OK; + memdbEnter(p); + + assert( p->nWrLock==0 || p->nWrLock==1 ); + assert( pThis->eLock<=SQLITE_LOCK_SHARED || p->nWrLock==1 ); + assert( pThis->eLock==SQLITE_LOCK_NONE || p->nRdLock>=1 ); + + if( eLock>SQLITE_LOCK_SHARED && (p->mFlags & SQLITE_DESERIALIZE_READONLY) ){ + rc = SQLITE_READONLY; + }else{ + switch( eLock ){ + case SQLITE_LOCK_SHARED: { + assert( pThis->eLock==SQLITE_LOCK_NONE ); + if( p->nWrLock>0 ){ + rc = SQLITE_BUSY; + }else{ + p->nRdLock++; + } + break; + }; + + case SQLITE_LOCK_RESERVED: + case SQLITE_LOCK_PENDING: { + assert( pThis->eLock>=SQLITE_LOCK_SHARED ); + if( ALWAYS(pThis->eLock==SQLITE_LOCK_SHARED) ){ + if( p->nWrLock>0 ){ + rc = SQLITE_BUSY; + }else{ + p->nWrLock = 1; + } + } + break; + } + + default: { + assert( eLock==SQLITE_LOCK_EXCLUSIVE ); + assert( pThis->eLock>=SQLITE_LOCK_SHARED ); + if( p->nRdLock>1 ){ + rc = SQLITE_BUSY; + }else if( pThis->eLock==SQLITE_LOCK_SHARED ){ + p->nWrLock = 1; + } + break; + } + } + } + if( rc==SQLITE_OK ) pThis->eLock = eLock; + memdbLeave(p); + return rc; +} + +/* +** Unlock an memdb-file. +*/ +static int memdbUnlock(sqlite3_file *pFile, int eLock){ + MemFile *pThis = (MemFile*)pFile; + MemStore *p = pThis->pStore; + if( eLock>=pThis->eLock ) return SQLITE_OK; + memdbEnter(p); + + assert( eLock==SQLITE_LOCK_SHARED || eLock==SQLITE_LOCK_NONE ); + if( eLock==SQLITE_LOCK_SHARED ){ + if( ALWAYS(pThis->eLock>SQLITE_LOCK_SHARED) ){ + p->nWrLock--; + } + }else{ + if( pThis->eLock>SQLITE_LOCK_SHARED ){ + p->nWrLock--; + } + p->nRdLock--; + } + + pThis->eLock = eLock; + memdbLeave(p); + return SQLITE_OK; +} + +#if 0 +/* +** This interface is only used for crash recovery, which does not +** occur on an in-memory database. +*/ +static int memdbCheckReservedLock(sqlite3_file *pFile, int *pResOut){ + *pResOut = 0; + return SQLITE_OK; +} +#endif + + +/* +** File control method. For custom operations on an memdb-file. +*/ +static int memdbFileControl(sqlite3_file *pFile, int op, void *pArg){ + MemStore *p = ((MemFile*)pFile)->pStore; + int rc = SQLITE_NOTFOUND; + memdbEnter(p); + if( op==SQLITE_FCNTL_VFSNAME ){ + *(char**)pArg = sqlite3_mprintf("memdb(%p,%lld)", p->aData, p->sz); + rc = SQLITE_OK; + } + if( op==SQLITE_FCNTL_SIZE_LIMIT ){ + sqlite3_int64 iLimit = *(sqlite3_int64*)pArg; + if( iLimit<p->sz ){ + if( iLimit<0 ){ + iLimit = p->szMax; + }else{ + iLimit = p->sz; + } + } + p->szMax = iLimit; + *(sqlite3_int64*)pArg = iLimit; + rc = SQLITE_OK; + } + memdbLeave(p); + return rc; +} + +#if 0 /* Not used because of SQLITE_IOCAP_POWERSAFE_OVERWRITE */ +/* +** Return the sector-size in bytes for an memdb-file. +*/ +static int memdbSectorSize(sqlite3_file *pFile){ + return 1024; +} +#endif + +/* +** Return the device characteristic flags supported by an memdb-file. +*/ +static int memdbDeviceCharacteristics(sqlite3_file *pFile){ + UNUSED_PARAMETER(pFile); + return SQLITE_IOCAP_ATOMIC | + SQLITE_IOCAP_POWERSAFE_OVERWRITE | + SQLITE_IOCAP_SAFE_APPEND | + SQLITE_IOCAP_SEQUENTIAL; +} + +/* Fetch a page of a memory-mapped file */ +static int memdbFetch( + sqlite3_file *pFile, + sqlite3_int64 iOfst, + int iAmt, + void **pp +){ + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); + if( iOfst+iAmt>p->sz || (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)!=0 ){ + *pp = 0; + }else{ + p->nMmap++; + *pp = (void*)(p->aData + iOfst); + } + memdbLeave(p); + return SQLITE_OK; +} + +/* Release a memory-mapped page */ +static int memdbUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ + MemStore *p = ((MemFile*)pFile)->pStore; + UNUSED_PARAMETER(iOfst); + UNUSED_PARAMETER(pPage); + memdbEnter(p); + p->nMmap--; + memdbLeave(p); + return SQLITE_OK; +} + +/* +** Open an mem file handle. +*/ +static int memdbOpen( + sqlite3_vfs *pVfs, + const char *zName, + sqlite3_file *pFd, + int flags, + int *pOutFlags +){ + MemFile *pFile = (MemFile*)pFd; + MemStore *p = 0; + int szName; + UNUSED_PARAMETER(pVfs); + + memset(pFile, 0, sizeof(*pFile)); + szName = sqlite3Strlen30(zName); + if( szName>1 && (zName[0]=='/' || zName[0]=='\\') ){ + int i; +#ifndef SQLITE_MUTEX_OMIT + sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#endif + sqlite3_mutex_enter(pVfsMutex); + for(i=0; i<memdb_g.nMemStore; i++){ + if( strcmp(memdb_g.apMemStore[i]->zFName,zName)==0 ){ + p = memdb_g.apMemStore[i]; + break; + } + } + if( p==0 ){ + MemStore **apNew; + p = sqlite3Malloc( sizeof(*p) + szName + 3 ); + if( p==0 ){ + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + apNew = sqlite3Realloc(memdb_g.apMemStore, + sizeof(apNew[0])*(memdb_g.nMemStore+1) ); + if( apNew==0 ){ + sqlite3_free(p); + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + apNew[memdb_g.nMemStore++] = p; + memdb_g.apMemStore = apNew; + memset(p, 0, sizeof(*p)); + p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE|SQLITE_DESERIALIZE_FREEONCLOSE; + p->szMax = sqlite3GlobalConfig.mxMemdbSize; + p->zFName = (char*)&p[1]; + memcpy(p->zFName, zName, szName+1); + p->pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( p->pMutex==0 ){ + memdb_g.nMemStore--; + sqlite3_free(p); + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + p->nRef = 1; + memdbEnter(p); + }else{ + memdbEnter(p); + p->nRef++; + } + sqlite3_mutex_leave(pVfsMutex); + }else{ + p = sqlite3Malloc( sizeof(*p) ); + if( p==0 ){ + return SQLITE_NOMEM; + } + memset(p, 0, sizeof(*p)); + p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE; + p->szMax = sqlite3GlobalConfig.mxMemdbSize; + } + pFile->pStore = p; + if( pOutFlags!=0 ){ + *pOutFlags = flags | SQLITE_OPEN_MEMORY; + } + pFd->pMethods = &memdb_io_methods; + memdbLeave(p); + return SQLITE_OK; +} + +#if 0 /* Only used to delete rollback journals, super-journals, and WAL + ** files, none of which exist in memdb. So this routine is never used */ +/* +** Delete the file located at zPath. If the dirSync argument is true, +** ensure the file-system modifications are synced to disk before +** returning. +*/ +static int memdbDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + return SQLITE_IOERR_DELETE; +} +#endif + +/* +** Test for access permissions. Return true if the requested permission +** is available, or false otherwise. +** +** With memdb, no files ever exist on disk. So always return false. +*/ +static int memdbAccess( + sqlite3_vfs *pVfs, + const char *zPath, + int flags, + int *pResOut +){ + UNUSED_PARAMETER(pVfs); + UNUSED_PARAMETER(zPath); + UNUSED_PARAMETER(flags); + *pResOut = 0; + return SQLITE_OK; +} + +/* +** Populate buffer zOut with the full canonical pathname corresponding +** to the pathname in zPath. zOut is guaranteed to point to a buffer +** of at least (INST_MAX_PATHNAME+1) bytes. +*/ +static int memdbFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, + char *zOut +){ + UNUSED_PARAMETER(pVfs); + sqlite3_snprintf(nOut, zOut, "%s", zPath); + return SQLITE_OK; +} + +/* +** Open the dynamic library located at zPath and return a handle. +*/ +static void *memdbDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath); +} + +/* +** Populate the buffer zErrMsg (size nByte bytes) with a human readable +** utf-8 string describing the most recent error encountered associated +** with dynamic libraries. +*/ +static void memdbDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ + ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg); +} + +/* +** Return a pointer to the symbol zSymbol in the dynamic library pHandle. +*/ +static void (*memdbDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){ + return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym); +} + +/* +** Close the dynamic library handle pHandle. +*/ +static void memdbDlClose(sqlite3_vfs *pVfs, void *pHandle){ + ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle); +} + +/* +** Populate the buffer pointed to by zBufOut with nByte bytes of +** random data. +*/ +static int memdbRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ + return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut); +} + +/* +** Sleep for nMicro microseconds. Return the number of microseconds +** actually slept. +*/ +static int memdbSleep(sqlite3_vfs *pVfs, int nMicro){ + return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro); +} + +#if 0 /* Never used. Modern cores only call xCurrentTimeInt64() */ +/* +** Return the current time as a Julian Day number in *pTimeOut. +*/ +static int memdbCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ + return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut); +} +#endif + +static int memdbGetLastError(sqlite3_vfs *pVfs, int a, char *b){ + return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b); +} +static int memdbCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ + return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p); +} + +/* +** Translate a database connection pointer and schema name into a +** MemFile pointer. +*/ +static MemFile *memdbFromDbSchema(sqlite3 *db, const char *zSchema){ + MemFile *p = 0; + MemStore *pStore; + int rc = sqlite3_file_control(db, zSchema, SQLITE_FCNTL_FILE_POINTER, &p); + if( rc ) return 0; + if( p->base.pMethods!=&memdb_io_methods ) return 0; + pStore = p->pStore; + memdbEnter(pStore); + if( pStore->zFName!=0 ) p = 0; + memdbLeave(pStore); + return p; +} + +/* +** Return the serialization of a database +*/ +SQLITE_API unsigned char *sqlite3_serialize( + sqlite3 *db, /* The database connection */ + const char *zSchema, /* Which database within the connection */ + sqlite3_int64 *piSize, /* Write size here, if not NULL */ + unsigned int mFlags /* Maybe SQLITE_SERIALIZE_NOCOPY */ +){ + MemFile *p; + int iDb; + Btree *pBt; + sqlite3_int64 sz; + int szPage = 0; + sqlite3_stmt *pStmt = 0; + unsigned char *pOut; + char *zSql; + int rc; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + + if( zSchema==0 ) zSchema = db->aDb[0].zDbSName; + p = memdbFromDbSchema(db, zSchema); + iDb = sqlite3FindDbName(db, zSchema); + if( piSize ) *piSize = -1; + if( iDb<0 ) return 0; + if( p ){ + MemStore *pStore = p->pStore; + assert( pStore->pMutex==0 ); + if( piSize ) *piSize = pStore->sz; + if( mFlags & SQLITE_SERIALIZE_NOCOPY ){ + pOut = pStore->aData; + }else{ + pOut = sqlite3_malloc64( pStore->sz ); + if( pOut ) memcpy(pOut, pStore->aData, pStore->sz); + } + return pOut; + } + pBt = db->aDb[iDb].pBt; + if( pBt==0 ) return 0; + szPage = sqlite3BtreeGetPageSize(pBt); + zSql = sqlite3_mprintf("PRAGMA \"%w\".page_count", zSchema); + rc = zSql ? sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0) : SQLITE_NOMEM; + sqlite3_free(zSql); + if( rc ) return 0; + rc = sqlite3_step(pStmt); + if( rc!=SQLITE_ROW ){ + pOut = 0; + }else{ + sz = sqlite3_column_int64(pStmt, 0)*szPage; + if( piSize ) *piSize = sz; + if( mFlags & SQLITE_SERIALIZE_NOCOPY ){ + pOut = 0; + }else{ + pOut = sqlite3_malloc64( sz ); + if( pOut ){ + int nPage = sqlite3_column_int(pStmt, 0); + Pager *pPager = sqlite3BtreePager(pBt); + int pgno; + for(pgno=1; pgno<=nPage; pgno++){ + DbPage *pPage = 0; + unsigned char *pTo = pOut + szPage*(sqlite3_int64)(pgno-1); + rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pPage, 0); + if( rc==SQLITE_OK ){ + memcpy(pTo, sqlite3PagerGetData(pPage), szPage); + }else{ + memset(pTo, 0, szPage); + } + sqlite3PagerUnref(pPage); + } + } + } + } + sqlite3_finalize(pStmt); + return pOut; +} + +/* Convert zSchema to a MemDB and initialize its content. +*/ +SQLITE_API int sqlite3_deserialize( + sqlite3 *db, /* The database connection */ + const char *zSchema, /* Which DB to reopen with the deserialization */ + unsigned char *pData, /* The serialized database content */ + sqlite3_int64 szDb, /* Number bytes in the deserialization */ + sqlite3_int64 szBuf, /* Total size of buffer pData[] */ + unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */ +){ + MemFile *p; + char *zSql; + sqlite3_stmt *pStmt = 0; + int rc; + int iDb; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } + if( szDb<0 ) return SQLITE_MISUSE_BKPT; + if( szBuf<0 ) return SQLITE_MISUSE_BKPT; +#endif + + sqlite3_mutex_enter(db->mutex); + if( zSchema==0 ) zSchema = db->aDb[0].zDbSName; + iDb = sqlite3FindDbName(db, zSchema); + testcase( iDb==1 ); + if( iDb<2 && iDb!=0 ){ + rc = SQLITE_ERROR; + goto end_deserialize; + } + zSql = sqlite3_mprintf("ATTACH x AS %Q", zSchema); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + } + if( rc ) goto end_deserialize; + db->init.iDb = (u8)iDb; + db->init.reopenMemdb = 1; + rc = sqlite3_step(pStmt); + db->init.reopenMemdb = 0; + if( rc!=SQLITE_DONE ){ + rc = SQLITE_ERROR; + goto end_deserialize; + } + p = memdbFromDbSchema(db, zSchema); + if( p==0 ){ + rc = SQLITE_ERROR; + }else{ + MemStore *pStore = p->pStore; + pStore->aData = pData; + pData = 0; + pStore->sz = szDb; + pStore->szAlloc = szBuf; + pStore->szMax = szBuf; + if( pStore->szMax<sqlite3GlobalConfig.mxMemdbSize ){ + pStore->szMax = sqlite3GlobalConfig.mxMemdbSize; + } + pStore->mFlags = mFlags; + rc = SQLITE_OK; + } + +end_deserialize: + sqlite3_finalize(pStmt); + if( pData && (mFlags & SQLITE_DESERIALIZE_FREEONCLOSE)!=0 ){ + sqlite3_free(pData); + } + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Return true if the VFS is the memvfs. +*/ +SQLITE_PRIVATE int sqlite3IsMemdb(const sqlite3_vfs *pVfs){ + return pVfs==&memdb_vfs; +} + +/* +** This routine is called when the extension is loaded. +** Register the new VFS. +*/ +SQLITE_PRIVATE int sqlite3MemdbInit(void){ + sqlite3_vfs *pLower = sqlite3_vfs_find(0); + unsigned int sz; + if( NEVER(pLower==0) ) return SQLITE_ERROR; + sz = pLower->szOsFile; + memdb_vfs.pAppData = pLower; + /* The following conditional can only be true when compiled for + ** Windows x86 and SQLITE_MAX_MMAP_SIZE=0. We always leave + ** it in, to be safe, but it is marked as NO_TEST since there + ** is no way to reach it under most builds. */ + if( sz<sizeof(MemFile) ) sz = sizeof(MemFile); /*NO_TEST*/ + memdb_vfs.szOsFile = sz; + return sqlite3_vfs_register(&memdb_vfs, 0); +} +#endif /* SQLITE_OMIT_DESERIALIZE */ + +/************** End of memdb.c ***********************************************/ +/************** Begin file bitvec.c ******************************************/ +/* +** 2008 February 16 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file implements an object that represents a fixed-length +** bitmap. Bits are numbered starting with 1. +** +** A bitmap is used to record which pages of a database file have been +** journalled during a transaction, or which pages have the "dont-write" +** property. Usually only a few pages are meet either condition. +** So the bitmap is usually sparse and has low cardinality. +** But sometimes (for example when during a DROP of a large table) most +** or all of the pages in a database can get journalled. In those cases, +** the bitmap becomes dense with high cardinality. The algorithm needs +** to handle both cases well. +** +** The size of the bitmap is fixed when the object is created. +** +** All bits are clear when the bitmap is created. Individual bits +** may be set or cleared one at a time. +** +** Test operations are about 100 times more common that set operations. +** Clear operations are exceedingly rare. There are usually between +** 5 and 500 set operations per Bitvec object, though the number of sets can +** sometimes grow into tens of thousands or larger. The size of the +** Bitvec object is the number of pages in the database file at the +** start of a transaction, and is thus usually less than a few thousand, +** but can be as large as 2 billion for a really big database. +*/ +/* #include "sqliteInt.h" */ + +/* Size of the Bitvec structure in bytes. */ +#define BITVEC_SZ 512 + +/* Round the union size down to the nearest pointer boundary, since that's how +** it will be aligned within the Bitvec struct. */ +#define BITVEC_USIZE \ + (((BITVEC_SZ-(3*sizeof(u32)))/sizeof(Bitvec*))*sizeof(Bitvec*)) + +/* Type of the array "element" for the bitmap representation. +** Should be a power of 2, and ideally, evenly divide into BITVEC_USIZE. +** Setting this to the "natural word" size of your CPU may improve +** performance. */ +#define BITVEC_TELEM u8 +/* Size, in bits, of the bitmap element. */ +#define BITVEC_SZELEM 8 +/* Number of elements in a bitmap array. */ +#define BITVEC_NELEM (BITVEC_USIZE/sizeof(BITVEC_TELEM)) +/* Number of bits in the bitmap array. */ +#define BITVEC_NBIT (BITVEC_NELEM*BITVEC_SZELEM) + +/* Number of u32 values in hash table. */ +#define BITVEC_NINT (BITVEC_USIZE/sizeof(u32)) +/* Maximum number of entries in hash table before +** sub-dividing and re-hashing. */ +#define BITVEC_MXHASH (BITVEC_NINT/2) +/* Hashing function for the aHash representation. +** Empirical testing showed that the *37 multiplier +** (an arbitrary prime)in the hash function provided +** no fewer collisions than the no-op *1. */ +#define BITVEC_HASH(X) (((X)*1)%BITVEC_NINT) + +#define BITVEC_NPTR (BITVEC_USIZE/sizeof(Bitvec *)) + + +/* +** A bitmap is an instance of the following structure. +** +** This bitmap records the existence of zero or more bits +** with values between 1 and iSize, inclusive. +** +** There are three possible representations of the bitmap. +** If iSize<=BITVEC_NBIT, then Bitvec.u.aBitmap[] is a straight +** bitmap. The least significant bit is bit 1. +** +** If iSize>BITVEC_NBIT and iDivisor==0 then Bitvec.u.aHash[] is +** a hash table that will hold up to BITVEC_MXHASH distinct values. +** +** Otherwise, the value i is redirected into one of BITVEC_NPTR +** sub-bitmaps pointed to by Bitvec.u.apSub[]. Each subbitmap +** handles up to iDivisor separate values of i. apSub[0] holds +** values between 1 and iDivisor. apSub[1] holds values between +** iDivisor+1 and 2*iDivisor. apSub[N] holds values between +** N*iDivisor+1 and (N+1)*iDivisor. Each subbitmap is normalized +** to hold deal with values between 1 and iDivisor. +*/ +struct Bitvec { + u32 iSize; /* Maximum bit index. Max iSize is 4,294,967,296. */ + u32 nSet; /* Number of bits that are set - only valid for aHash + ** element. Max is BITVEC_NINT. For BITVEC_SZ of 512, + ** this would be 125. */ + u32 iDivisor; /* Number of bits handled by each apSub[] entry. */ + /* Should >=0 for apSub element. */ + /* Max iDivisor is max(u32) / BITVEC_NPTR + 1. */ + /* For a BITVEC_SZ of 512, this would be 34,359,739. */ + union { + BITVEC_TELEM aBitmap[BITVEC_NELEM]; /* Bitmap representation */ + u32 aHash[BITVEC_NINT]; /* Hash table representation */ + Bitvec *apSub[BITVEC_NPTR]; /* Recursive representation */ + } u; +}; + +/* +** Create a new bitmap object able to handle bits between 0 and iSize, +** inclusive. Return a pointer to the new object. Return NULL if +** malloc fails. +*/ +SQLITE_PRIVATE Bitvec *sqlite3BitvecCreate(u32 iSize){ + Bitvec *p; + assert( sizeof(*p)==BITVEC_SZ ); + p = sqlite3MallocZero( sizeof(*p) ); + if( p ){ + p->iSize = iSize; + } + return p; +} + +/* +** Check to see if the i-th bit is set. Return true or false. +** If p is NULL (if the bitmap has not been created) or if +** i is out of range, then return false. +*/ +SQLITE_PRIVATE int sqlite3BitvecTestNotNull(Bitvec *p, u32 i){ + assert( p!=0 ); + i--; + if( i>=p->iSize ) return 0; + while( p->iDivisor ){ + u32 bin = i/p->iDivisor; + i = i%p->iDivisor; + p = p->u.apSub[bin]; + if (!p) { + return 0; + } + } + if( p->iSize<=BITVEC_NBIT ){ + return (p->u.aBitmap[i/BITVEC_SZELEM] & (1<<(i&(BITVEC_SZELEM-1))))!=0; + } else{ + u32 h = BITVEC_HASH(i++); + while( p->u.aHash[h] ){ + if( p->u.aHash[h]==i ) return 1; + h = (h+1) % BITVEC_NINT; + } + return 0; + } +} +SQLITE_PRIVATE int sqlite3BitvecTest(Bitvec *p, u32 i){ + return p!=0 && sqlite3BitvecTestNotNull(p,i); +} + +/* +** Set the i-th bit. Return 0 on success and an error code if +** anything goes wrong. +** +** This routine might cause sub-bitmaps to be allocated. Failing +** to get the memory needed to hold the sub-bitmap is the only +** that can go wrong with an insert, assuming p and i are valid. +** +** The calling function must ensure that p is a valid Bitvec object +** and that the value for "i" is within range of the Bitvec object. +** Otherwise the behavior is undefined. +*/ +SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec *p, u32 i){ + u32 h; + if( p==0 ) return SQLITE_OK; + assert( i>0 ); + assert( i<=p->iSize ); + i--; + while((p->iSize > BITVEC_NBIT) && p->iDivisor) { + u32 bin = i/p->iDivisor; + i = i%p->iDivisor; + if( p->u.apSub[bin]==0 ){ + p->u.apSub[bin] = sqlite3BitvecCreate( p->iDivisor ); + if( p->u.apSub[bin]==0 ) return SQLITE_NOMEM_BKPT; + } + p = p->u.apSub[bin]; + } + if( p->iSize<=BITVEC_NBIT ){ + p->u.aBitmap[i/BITVEC_SZELEM] |= 1 << (i&(BITVEC_SZELEM-1)); + return SQLITE_OK; + } + h = BITVEC_HASH(i++); + /* if there wasn't a hash collision, and this doesn't */ + /* completely fill the hash, then just add it without */ + /* worrying about sub-dividing and re-hashing. */ + if( !p->u.aHash[h] ){ + if (p->nSet<(BITVEC_NINT-1)) { + goto bitvec_set_end; + } else { + goto bitvec_set_rehash; + } + } + /* there was a collision, check to see if it's already */ + /* in hash, if not, try to find a spot for it */ + do { + if( p->u.aHash[h]==i ) return SQLITE_OK; + h++; + if( h>=BITVEC_NINT ) h = 0; + } while( p->u.aHash[h] ); + /* we didn't find it in the hash. h points to the first */ + /* available free spot. check to see if this is going to */ + /* make our hash too "full". */ +bitvec_set_rehash: + if( p->nSet>=BITVEC_MXHASH ){ + unsigned int j; + int rc; + u32 *aiValues = sqlite3StackAllocRaw(0, sizeof(p->u.aHash)); + if( aiValues==0 ){ + return SQLITE_NOMEM_BKPT; + }else{ + memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash)); + memset(p->u.apSub, 0, sizeof(p->u.apSub)); + p->iDivisor = (p->iSize + BITVEC_NPTR - 1)/BITVEC_NPTR; + rc = sqlite3BitvecSet(p, i); + for(j=0; j<BITVEC_NINT; j++){ + if( aiValues[j] ) rc |= sqlite3BitvecSet(p, aiValues[j]); + } + sqlite3StackFree(0, aiValues); + return rc; + } + } +bitvec_set_end: + p->nSet++; + p->u.aHash[h] = i; + return SQLITE_OK; +} + +/* +** Clear the i-th bit. +** +** pBuf must be a pointer to at least BITVEC_SZ bytes of temporary storage +** that BitvecClear can use to rebuilt its hash table. +*/ +SQLITE_PRIVATE void sqlite3BitvecClear(Bitvec *p, u32 i, void *pBuf){ + if( p==0 ) return; + assert( i>0 ); + i--; + while( p->iDivisor ){ + u32 bin = i/p->iDivisor; + i = i%p->iDivisor; + p = p->u.apSub[bin]; + if (!p) { + return; + } + } + if( p->iSize<=BITVEC_NBIT ){ + p->u.aBitmap[i/BITVEC_SZELEM] &= ~(1 << (i&(BITVEC_SZELEM-1))); + }else{ + unsigned int j; + u32 *aiValues = pBuf; + memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash)); + memset(p->u.aHash, 0, sizeof(p->u.aHash)); + p->nSet = 0; + for(j=0; j<BITVEC_NINT; j++){ + if( aiValues[j] && aiValues[j]!=(i+1) ){ + u32 h = BITVEC_HASH(aiValues[j]-1); + p->nSet++; + while( p->u.aHash[h] ){ + h++; + if( h>=BITVEC_NINT ) h = 0; + } + p->u.aHash[h] = aiValues[j]; + } + } + } +} + +/* +** Destroy a bitmap object. Reclaim all memory used. +*/ +SQLITE_PRIVATE void sqlite3BitvecDestroy(Bitvec *p){ + if( p==0 ) return; + if( p->iDivisor ){ + unsigned int i; + for(i=0; i<BITVEC_NPTR; i++){ + sqlite3BitvecDestroy(p->u.apSub[i]); + } + } + sqlite3_free(p); +} + +/* +** Return the value of the iSize parameter specified when Bitvec *p +** was created. +*/ +SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){ + return p->iSize; +} + +#ifndef SQLITE_UNTESTABLE +/* +** Let V[] be an array of unsigned characters sufficient to hold +** up to N bits. Let I be an integer between 0 and N. 0<=I<N. +** Then the following macros can be used to set, clear, or test +** individual bits within V. +*/ +#define SETBIT(V,I) V[I>>3] |= (1<<(I&7)) +#define CLEARBIT(V,I) V[I>>3] &= ~(1<<(I&7)) +#define TESTBIT(V,I) (V[I>>3]&(1<<(I&7)))!=0 + +/* +** This routine runs an extensive test of the Bitvec code. +** +** The input is an array of integers that acts as a program +** to test the Bitvec. The integers are opcodes followed +** by 0, 1, or 3 operands, depending on the opcode. Another +** opcode follows immediately after the last operand. +** +** There are 6 opcodes numbered from 0 through 5. 0 is the +** "halt" opcode and causes the test to end. +** +** 0 Halt and return the number of errors +** 1 N S X Set N bits beginning with S and incrementing by X +** 2 N S X Clear N bits beginning with S and incrementing by X +** 3 N Set N randomly chosen bits +** 4 N Clear N randomly chosen bits +** 5 N S X Set N bits from S increment X in array only, not in bitvec +** +** The opcodes 1 through 4 perform set and clear operations are performed +** on both a Bitvec object and on a linear array of bits obtained from malloc. +** Opcode 5 works on the linear array only, not on the Bitvec. +** Opcode 5 is used to deliberately induce a fault in order to +** confirm that error detection works. +** +** At the conclusion of the test the linear array is compared +** against the Bitvec object. If there are any differences, +** an error is returned. If they are the same, zero is returned. +** +** If a memory allocation error occurs, return -1. +*/ +SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ + Bitvec *pBitvec = 0; + unsigned char *pV = 0; + int rc = -1; + int i, nx, pc, op; + void *pTmpSpace; + + /* Allocate the Bitvec to be tested and a linear array of + ** bits to act as the reference */ + pBitvec = sqlite3BitvecCreate( sz ); + pV = sqlite3MallocZero( (sz+7)/8 + 1 ); + pTmpSpace = sqlite3_malloc64(BITVEC_SZ); + if( pBitvec==0 || pV==0 || pTmpSpace==0 ) goto bitvec_end; + + /* NULL pBitvec tests */ + sqlite3BitvecSet(0, 1); + sqlite3BitvecClear(0, 1, pTmpSpace); + + /* Run the program */ + pc = i = 0; + while( (op = aOp[pc])!=0 ){ + switch( op ){ + case 1: + case 2: + case 5: { + nx = 4; + i = aOp[pc+2] - 1; + aOp[pc+2] += aOp[pc+3]; + break; + } + case 3: + case 4: + default: { + nx = 2; + sqlite3_randomness(sizeof(i), &i); + break; + } + } + if( (--aOp[pc+1]) > 0 ) nx = 0; + pc += nx; + i = (i & 0x7fffffff)%sz; + if( (op & 1)!=0 ){ + SETBIT(pV, (i+1)); + if( op!=5 ){ + if( sqlite3BitvecSet(pBitvec, i+1) ) goto bitvec_end; + } + }else{ + CLEARBIT(pV, (i+1)); + sqlite3BitvecClear(pBitvec, i+1, pTmpSpace); + } + } + + /* Test to make sure the linear array exactly matches the + ** Bitvec object. Start with the assumption that they do + ** match (rc==0). Change rc to non-zero if a discrepancy + ** is found. + */ + rc = sqlite3BitvecTest(0,0) + sqlite3BitvecTest(pBitvec, sz+1) + + sqlite3BitvecTest(pBitvec, 0) + + (sqlite3BitvecSize(pBitvec) - sz); + for(i=1; i<=sz; i++){ + if( (TESTBIT(pV,i))!=sqlite3BitvecTest(pBitvec,i) ){ + rc = i; + break; + } + } + + /* Free allocated structure */ +bitvec_end: + sqlite3_free(pTmpSpace); + sqlite3_free(pV); + sqlite3BitvecDestroy(pBitvec); + return rc; +} +#endif /* SQLITE_UNTESTABLE */ + +/************** End of bitvec.c **********************************************/ +/************** Begin file pcache.c ******************************************/ +/* +** 2008 August 05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file implements that page cache. +*/ +/* #include "sqliteInt.h" */ + +/* +** A complete page cache is an instance of this structure. Every +** entry in the cache holds a single page of the database file. The +** btree layer only operates on the cached copy of the database pages. +** +** A page cache entry is "clean" if it exactly matches what is currently +** on disk. A page is "dirty" if it has been modified and needs to be +** persisted to disk. +** +** pDirty, pDirtyTail, pSynced: +** All dirty pages are linked into the doubly linked list using +** PgHdr.pDirtyNext and pDirtyPrev. The list is maintained in LRU order +** such that p was added to the list more recently than p->pDirtyNext. +** PCache.pDirty points to the first (newest) element in the list and +** pDirtyTail to the last (oldest). +** +** The PCache.pSynced variable is used to optimize searching for a dirty +** page to eject from the cache mid-transaction. It is better to eject +** a page that does not require a journal sync than one that does. +** Therefore, pSynced is maintained so that it *almost* always points +** to either the oldest page in the pDirty/pDirtyTail list that has a +** clear PGHDR_NEED_SYNC flag or to a page that is older than this one +** (so that the right page to eject can be found by following pDirtyPrev +** pointers). +*/ +struct PCache { + PgHdr *pDirty, *pDirtyTail; /* List of dirty pages in LRU order */ + PgHdr *pSynced; /* Last synced page in dirty page list */ + i64 nRefSum; /* Sum of ref counts over all pages */ + int szCache; /* Configured cache size */ + int szSpill; /* Size before spilling occurs */ + int szPage; /* Size of every page in this cache */ + int szExtra; /* Size of extra space for each page */ + u8 bPurgeable; /* True if pages are on backing store */ + u8 eCreate; /* eCreate value for for xFetch() */ + int (*xStress)(void*,PgHdr*); /* Call to try make a page clean */ + void *pStress; /* Argument to xStress */ + sqlite3_pcache *pCache; /* Pluggable cache module */ +}; + +/********************************** Test and Debug Logic **********************/ +/* +** Debug tracing macros. Enable by by changing the "0" to "1" and +** recompiling. +** +** When sqlite3PcacheTrace is 1, single line trace messages are issued. +** When sqlite3PcacheTrace is 2, a dump of the pcache showing all cache entries +** is displayed for many operations, resulting in a lot of output. +*/ +#if defined(SQLITE_DEBUG) && 0 + int sqlite3PcacheTrace = 2; /* 0: off 1: simple 2: cache dumps */ + int sqlite3PcacheMxDump = 9999; /* Max cache entries for pcacheDump() */ +# define pcacheTrace(X) if(sqlite3PcacheTrace){sqlite3DebugPrintf X;} + static void pcachePageTrace(int i, sqlite3_pcache_page *pLower){ + PgHdr *pPg; + unsigned char *a; + int j; + if( pLower==0 ){ + printf("%3d: NULL\n", i); + }else{ + pPg = (PgHdr*)pLower->pExtra; + printf("%3d: nRef %2lld flgs %02x data ", i, pPg->nRef, pPg->flags); + a = (unsigned char *)pLower->pBuf; + for(j=0; j<12; j++) printf("%02x", a[j]); + printf(" ptr %p\n", pPg); + } + } + static void pcacheDump(PCache *pCache){ + int N; + int i; + sqlite3_pcache_page *pLower; + + if( sqlite3PcacheTrace<2 ) return; + if( pCache->pCache==0 ) return; + N = sqlite3PcachePagecount(pCache); + if( N>sqlite3PcacheMxDump ) N = sqlite3PcacheMxDump; + for(i=1; i<=N; i++){ + pLower = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, i, 0); + pcachePageTrace(i, pLower); + if( pLower && ((PgHdr*)pLower)->pPage==0 ){ + sqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, pLower, 0); + } + } + } +#else +# define pcacheTrace(X) +# define pcachePageTrace(PGNO, X) +# define pcacheDump(X) +#endif + +/* +** Return 1 if pPg is on the dirty list for pCache. Return 0 if not. +** This routine runs inside of assert() statements only. +*/ +#if defined(SQLITE_ENABLE_EXPENSIVE_ASSERT) +static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){ + PgHdr *p; + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + if( p==pPg ) return 1; + } + return 0; +} +static int pageNotOnDirtyList(PCache *pCache, PgHdr *pPg){ + PgHdr *p; + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + if( p==pPg ) return 0; + } + return 1; +} +#else +# define pageOnDirtyList(A,B) 1 +# define pageNotOnDirtyList(A,B) 1 +#endif + +/* +** Check invariants on a PgHdr entry. Return true if everything is OK. +** Return false if any invariant is violated. +** +** This routine is for use inside of assert() statements only. For +** example: +** +** assert( sqlite3PcachePageSanity(pPg) ); +*/ +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr *pPg){ + PCache *pCache; + assert( pPg!=0 ); + assert( pPg->pgno>0 || pPg->pPager==0 ); /* Page number is 1 or more */ + pCache = pPg->pCache; + assert( pCache!=0 ); /* Every page has an associated PCache */ + if( pPg->flags & PGHDR_CLEAN ){ + assert( (pPg->flags & PGHDR_DIRTY)==0 );/* Cannot be both CLEAN and DIRTY */ + assert( pageNotOnDirtyList(pCache, pPg) );/* CLEAN pages not on dirtylist */ + }else{ + assert( (pPg->flags & PGHDR_DIRTY)!=0 );/* If not CLEAN must be DIRTY */ + assert( pPg->pDirtyNext==0 || pPg->pDirtyNext->pDirtyPrev==pPg ); + assert( pPg->pDirtyPrev==0 || pPg->pDirtyPrev->pDirtyNext==pPg ); + assert( pPg->pDirtyPrev!=0 || pCache->pDirty==pPg ); + assert( pageOnDirtyList(pCache, pPg) ); + } + /* WRITEABLE pages must also be DIRTY */ + if( pPg->flags & PGHDR_WRITEABLE ){ + assert( pPg->flags & PGHDR_DIRTY ); /* WRITEABLE implies DIRTY */ + } + /* NEED_SYNC can be set independently of WRITEABLE. This can happen, + ** for example, when using the sqlite3PagerDontWrite() optimization: + ** (1) Page X is journalled, and gets WRITEABLE and NEED_SEEK. + ** (2) Page X moved to freelist, WRITEABLE is cleared + ** (3) Page X reused, WRITEABLE is set again + ** If NEED_SYNC had been cleared in step 2, then it would not be reset + ** in step 3, and page might be written into the database without first + ** syncing the rollback journal, which might cause corruption on a power + ** loss. + ** + ** Another example is when the database page size is smaller than the + ** disk sector size. When any page of a sector is journalled, all pages + ** in that sector are marked NEED_SYNC even if they are still CLEAN, just + ** in case they are later modified, since all pages in the same sector + ** must be journalled and synced before any of those pages can be safely + ** written. + */ + return 1; +} +#endif /* SQLITE_DEBUG */ + + +/********************************** Linked List Management ********************/ + +/* Allowed values for second argument to pcacheManageDirtyList() */ +#define PCACHE_DIRTYLIST_REMOVE 1 /* Remove pPage from dirty list */ +#define PCACHE_DIRTYLIST_ADD 2 /* Add pPage to the dirty list */ +#define PCACHE_DIRTYLIST_FRONT 3 /* Move pPage to the front of the list */ + +/* +** Manage pPage's participation on the dirty list. Bits of the addRemove +** argument determines what operation to do. The 0x01 bit means first +** remove pPage from the dirty list. The 0x02 means add pPage back to +** the dirty list. Doing both moves pPage to the front of the dirty list. +*/ +static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){ + PCache *p = pPage->pCache; + + pcacheTrace(("%p.DIRTYLIST.%s %d\n", p, + addRemove==1 ? "REMOVE" : addRemove==2 ? "ADD" : "FRONT", + pPage->pgno)); + if( addRemove & PCACHE_DIRTYLIST_REMOVE ){ + assert( pPage->pDirtyNext || pPage==p->pDirtyTail ); + assert( pPage->pDirtyPrev || pPage==p->pDirty ); + + /* Update the PCache1.pSynced variable if necessary. */ + if( p->pSynced==pPage ){ + p->pSynced = pPage->pDirtyPrev; + } + + if( pPage->pDirtyNext ){ + pPage->pDirtyNext->pDirtyPrev = pPage->pDirtyPrev; + }else{ + assert( pPage==p->pDirtyTail ); + p->pDirtyTail = pPage->pDirtyPrev; + } + if( pPage->pDirtyPrev ){ + pPage->pDirtyPrev->pDirtyNext = pPage->pDirtyNext; + }else{ + /* If there are now no dirty pages in the cache, set eCreate to 2. + ** This is an optimization that allows sqlite3PcacheFetch() to skip + ** searching for a dirty page to eject from the cache when it might + ** otherwise have to. */ + assert( pPage==p->pDirty ); + p->pDirty = pPage->pDirtyNext; + assert( p->bPurgeable || p->eCreate==2 ); + if( p->pDirty==0 ){ /*OPTIMIZATION-IF-TRUE*/ + assert( p->bPurgeable==0 || p->eCreate==1 ); + p->eCreate = 2; + } + } + } + if( addRemove & PCACHE_DIRTYLIST_ADD ){ + pPage->pDirtyPrev = 0; + pPage->pDirtyNext = p->pDirty; + if( pPage->pDirtyNext ){ + assert( pPage->pDirtyNext->pDirtyPrev==0 ); + pPage->pDirtyNext->pDirtyPrev = pPage; + }else{ + p->pDirtyTail = pPage; + if( p->bPurgeable ){ + assert( p->eCreate==2 ); + p->eCreate = 1; + } + } + p->pDirty = pPage; + + /* If pSynced is NULL and this page has a clear NEED_SYNC flag, set + ** pSynced to point to it. Checking the NEED_SYNC flag is an + ** optimization, as if pSynced points to a page with the NEED_SYNC + ** flag set sqlite3PcacheFetchStress() searches through all newer + ** entries of the dirty-list for a page with NEED_SYNC clear anyway. */ + if( !p->pSynced + && 0==(pPage->flags&PGHDR_NEED_SYNC) /*OPTIMIZATION-IF-FALSE*/ + ){ + p->pSynced = pPage; + } + } + pcacheDump(p); +} + +/* +** Wrapper around the pluggable caches xUnpin method. If the cache is +** being used for an in-memory database, this function is a no-op. +*/ +static void pcacheUnpin(PgHdr *p){ + if( p->pCache->bPurgeable ){ + pcacheTrace(("%p.UNPIN %d\n", p->pCache, p->pgno)); + sqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 0); + pcacheDump(p->pCache); + } +} + +/* +** Compute the number of pages of cache requested. p->szCache is the +** cache size requested by the "PRAGMA cache_size" statement. +*/ +static int numberOfCachePages(PCache *p){ + if( p->szCache>=0 ){ + /* IMPLEMENTATION-OF: R-42059-47211 If the argument N is positive then the + ** suggested cache size is set to N. */ + return p->szCache; + }else{ + i64 n; + /* IMPLEMENTATION-OF: R-59858-46238 If the argument N is negative, then the + ** number of cache pages is adjusted to be a number of pages that would + ** use approximately abs(N*1024) bytes of memory based on the current + ** page size. */ + n = ((-1024*(i64)p->szCache)/(p->szPage+p->szExtra)); + if( n>1000000000 ) n = 1000000000; + return (int)n; + } +} + +/*************************************************** General Interfaces ****** +** +** Initialize and shutdown the page cache subsystem. Neither of these +** functions are threadsafe. +*/ +SQLITE_PRIVATE int sqlite3PcacheInitialize(void){ + if( sqlite3GlobalConfig.pcache2.xInit==0 ){ + /* IMPLEMENTATION-OF: R-26801-64137 If the xInit() method is NULL, then the + ** built-in default page cache is used instead of the application defined + ** page cache. */ + sqlite3PCacheSetDefault(); + assert( sqlite3GlobalConfig.pcache2.xInit!=0 ); + } + return sqlite3GlobalConfig.pcache2.xInit(sqlite3GlobalConfig.pcache2.pArg); +} +SQLITE_PRIVATE void sqlite3PcacheShutdown(void){ + if( sqlite3GlobalConfig.pcache2.xShutdown ){ + /* IMPLEMENTATION-OF: R-26000-56589 The xShutdown() method may be NULL. */ + sqlite3GlobalConfig.pcache2.xShutdown(sqlite3GlobalConfig.pcache2.pArg); + } +} + +/* +** Return the size in bytes of a PCache object. +*/ +SQLITE_PRIVATE int sqlite3PcacheSize(void){ return sizeof(PCache); } + +/* +** Create a new PCache object. Storage space to hold the object +** has already been allocated and is passed in as the p pointer. +** The caller discovers how much space needs to be allocated by +** calling sqlite3PcacheSize(). +** +** szExtra is some extra space allocated for each page. The first +** 8 bytes of the extra space will be zeroed as the page is allocated, +** but remaining content will be uninitialized. Though it is opaque +** to this module, the extra space really ends up being the MemPage +** structure in the pager. +*/ +SQLITE_PRIVATE int sqlite3PcacheOpen( + int szPage, /* Size of every page */ + int szExtra, /* Extra space associated with each page */ + int bPurgeable, /* True if pages are on backing store */ + int (*xStress)(void*,PgHdr*),/* Call to try to make pages clean */ + void *pStress, /* Argument to xStress */ + PCache *p /* Preallocated space for the PCache */ +){ + memset(p, 0, sizeof(PCache)); + p->szPage = 1; + p->szExtra = szExtra; + assert( szExtra>=8 ); /* First 8 bytes will be zeroed */ + p->bPurgeable = bPurgeable; + p->eCreate = 2; + p->xStress = xStress; + p->pStress = pStress; + p->szCache = 100; + p->szSpill = 1; + pcacheTrace(("%p.OPEN szPage %d bPurgeable %d\n",p,szPage,bPurgeable)); + return sqlite3PcacheSetPageSize(p, szPage); +} + +/* +** Change the page size for PCache object. The caller must ensure that there +** are no outstanding page references when this function is called. +*/ +SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *pCache, int szPage){ + assert( pCache->nRefSum==0 && pCache->pDirty==0 ); + if( pCache->szPage ){ + sqlite3_pcache *pNew; + pNew = sqlite3GlobalConfig.pcache2.xCreate( + szPage, pCache->szExtra + ROUND8(sizeof(PgHdr)), + pCache->bPurgeable + ); + if( pNew==0 ) return SQLITE_NOMEM_BKPT; + sqlite3GlobalConfig.pcache2.xCachesize(pNew, numberOfCachePages(pCache)); + if( pCache->pCache ){ + sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache); + } + pCache->pCache = pNew; + pCache->szPage = szPage; + pcacheTrace(("%p.PAGESIZE %d\n",pCache,szPage)); + } + return SQLITE_OK; +} + +/* +** Try to obtain a page from the cache. +** +** This routine returns a pointer to an sqlite3_pcache_page object if +** such an object is already in cache, or if a new one is created. +** This routine returns a NULL pointer if the object was not in cache +** and could not be created. +** +** The createFlags should be 0 to check for existing pages and should +** be 3 (not 1, but 3) to try to create a new page. +** +** If the createFlag is 0, then NULL is always returned if the page +** is not already in the cache. If createFlag is 1, then a new page +** is created only if that can be done without spilling dirty pages +** and without exceeding the cache size limit. +** +** The caller needs to invoke sqlite3PcacheFetchFinish() to properly +** initialize the sqlite3_pcache_page object and convert it into a +** PgHdr object. The sqlite3PcacheFetch() and sqlite3PcacheFetchFinish() +** routines are split this way for performance reasons. When separated +** they can both (usually) operate without having to push values to +** the stack on entry and pop them back off on exit, which saves a +** lot of pushing and popping. +*/ +SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch( + PCache *pCache, /* Obtain the page from this cache */ + Pgno pgno, /* Page number to obtain */ + int createFlag /* If true, create page if it does not exist already */ +){ + int eCreate; + sqlite3_pcache_page *pRes; + + assert( pCache!=0 ); + assert( pCache->pCache!=0 ); + assert( createFlag==3 || createFlag==0 ); + assert( pCache->eCreate==((pCache->bPurgeable && pCache->pDirty) ? 1 : 2) ); + + /* eCreate defines what to do if the page does not exist. + ** 0 Do not allocate a new page. (createFlag==0) + ** 1 Allocate a new page if doing so is inexpensive. + ** (createFlag==1 AND bPurgeable AND pDirty) + ** 2 Allocate a new page even it doing so is difficult. + ** (createFlag==1 AND !(bPurgeable AND pDirty) + */ + eCreate = createFlag & pCache->eCreate; + assert( eCreate==0 || eCreate==1 || eCreate==2 ); + assert( createFlag==0 || pCache->eCreate==eCreate ); + assert( createFlag==0 || eCreate==1+(!pCache->bPurgeable||!pCache->pDirty) ); + pRes = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate); + pcacheTrace(("%p.FETCH %d%s (result: %p) ",pCache,pgno, + createFlag?" create":"",pRes)); + pcachePageTrace(pgno, pRes); + return pRes; +} + +/* +** If the sqlite3PcacheFetch() routine is unable to allocate a new +** page because no clean pages are available for reuse and the cache +** size limit has been reached, then this routine can be invoked to +** try harder to allocate a page. This routine might invoke the stress +** callback to spill dirty pages to the journal. It will then try to +** allocate the new page and will only fail to allocate a new page on +** an OOM error. +** +** This routine should be invoked only after sqlite3PcacheFetch() fails. +*/ +SQLITE_PRIVATE int sqlite3PcacheFetchStress( + PCache *pCache, /* Obtain the page from this cache */ + Pgno pgno, /* Page number to obtain */ + sqlite3_pcache_page **ppPage /* Write result here */ +){ + PgHdr *pPg; + if( pCache->eCreate==2 ) return 0; + + if( sqlite3PcachePagecount(pCache)>pCache->szSpill ){ + /* Find a dirty page to write-out and recycle. First try to find a + ** page that does not require a journal-sync (one with PGHDR_NEED_SYNC + ** cleared), but if that is not possible settle for any other + ** unreferenced dirty page. + ** + ** If the LRU page in the dirty list that has a clear PGHDR_NEED_SYNC + ** flag is currently referenced, then the following may leave pSynced + ** set incorrectly (pointing to other than the LRU page with NEED_SYNC + ** cleared). This is Ok, as pSynced is just an optimization. */ + for(pPg=pCache->pSynced; + pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC)); + pPg=pPg->pDirtyPrev + ); + pCache->pSynced = pPg; + if( !pPg ){ + for(pPg=pCache->pDirtyTail; pPg && pPg->nRef; pPg=pPg->pDirtyPrev); + } + if( pPg ){ + int rc; +#ifdef SQLITE_LOG_CACHE_SPILL + sqlite3_log(SQLITE_FULL, + "spill page %d making room for %d - cache used: %d/%d", + pPg->pgno, pgno, + sqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache), + numberOfCachePages(pCache)); +#endif + pcacheTrace(("%p.SPILL %d\n",pCache,pPg->pgno)); + rc = pCache->xStress(pCache->pStress, pPg); + pcacheDump(pCache); + if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){ + return rc; + } + } + } + *ppPage = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, 2); + return *ppPage==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK; +} + +/* +** This is a helper routine for sqlite3PcacheFetchFinish() +** +** In the uncommon case where the page being fetched has not been +** initialized, this routine is invoked to do the initialization. +** This routine is broken out into a separate function since it +** requires extra stack manipulation that can be avoided in the common +** case. +*/ +static SQLITE_NOINLINE PgHdr *pcacheFetchFinishWithInit( + PCache *pCache, /* Obtain the page from this cache */ + Pgno pgno, /* Page number obtained */ + sqlite3_pcache_page *pPage /* Page obtained by prior PcacheFetch() call */ +){ + PgHdr *pPgHdr; + assert( pPage!=0 ); + pPgHdr = (PgHdr*)pPage->pExtra; + assert( pPgHdr->pPage==0 ); + memset(&pPgHdr->pDirty, 0, sizeof(PgHdr) - offsetof(PgHdr,pDirty)); + pPgHdr->pPage = pPage; + pPgHdr->pData = pPage->pBuf; + pPgHdr->pExtra = (void *)&pPgHdr[1]; + memset(pPgHdr->pExtra, 0, 8); + pPgHdr->pCache = pCache; + pPgHdr->pgno = pgno; + pPgHdr->flags = PGHDR_CLEAN; + return sqlite3PcacheFetchFinish(pCache,pgno,pPage); +} + +/* +** This routine converts the sqlite3_pcache_page object returned by +** sqlite3PcacheFetch() into an initialized PgHdr object. This routine +** must be called after sqlite3PcacheFetch() in order to get a usable +** result. +*/ +SQLITE_PRIVATE PgHdr *sqlite3PcacheFetchFinish( + PCache *pCache, /* Obtain the page from this cache */ + Pgno pgno, /* Page number obtained */ + sqlite3_pcache_page *pPage /* Page obtained by prior PcacheFetch() call */ +){ + PgHdr *pPgHdr; + + assert( pPage!=0 ); + pPgHdr = (PgHdr *)pPage->pExtra; + + if( !pPgHdr->pPage ){ + return pcacheFetchFinishWithInit(pCache, pgno, pPage); + } + pCache->nRefSum++; + pPgHdr->nRef++; + assert( sqlite3PcachePageSanity(pPgHdr) ); + return pPgHdr; +} + +/* +** Decrement the reference count on a page. If the page is clean and the +** reference count drops to 0, then it is made eligible for recycling. +*/ +SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){ + assert( p->nRef>0 ); + p->pCache->nRefSum--; + if( (--p->nRef)==0 ){ + if( p->flags&PGHDR_CLEAN ){ + pcacheUnpin(p); + }else{ + pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT); + assert( sqlite3PcachePageSanity(p) ); + } + } +} + +/* +** Increase the reference count of a supplied page by 1. +*/ +SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr *p){ + assert(p->nRef>0); + assert( sqlite3PcachePageSanity(p) ); + p->nRef++; + p->pCache->nRefSum++; +} + +/* +** Drop a page from the cache. There must be exactly one reference to the +** page. This function deletes that reference, so after it returns the +** page pointed to by p is invalid. +*/ +SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr *p){ + assert( p->nRef==1 ); + assert( sqlite3PcachePageSanity(p) ); + if( p->flags&PGHDR_DIRTY ){ + pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE); + } + p->pCache->nRefSum--; + sqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 1); +} + +/* +** Make sure the page is marked as dirty. If it isn't dirty already, +** make it so. +*/ +SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){ + assert( p->nRef>0 ); + assert( sqlite3PcachePageSanity(p) ); + if( p->flags & (PGHDR_CLEAN|PGHDR_DONT_WRITE) ){ /*OPTIMIZATION-IF-FALSE*/ + p->flags &= ~PGHDR_DONT_WRITE; + if( p->flags & PGHDR_CLEAN ){ + p->flags ^= (PGHDR_DIRTY|PGHDR_CLEAN); + pcacheTrace(("%p.DIRTY %d\n",p->pCache,p->pgno)); + assert( (p->flags & (PGHDR_DIRTY|PGHDR_CLEAN))==PGHDR_DIRTY ); + pcacheManageDirtyList(p, PCACHE_DIRTYLIST_ADD); + assert( sqlite3PcachePageSanity(p) ); + } + assert( sqlite3PcachePageSanity(p) ); + } +} + +/* +** Make sure the page is marked as clean. If it isn't clean already, +** make it so. +*/ +SQLITE_PRIVATE void sqlite3PcacheMakeClean(PgHdr *p){ + assert( sqlite3PcachePageSanity(p) ); + assert( (p->flags & PGHDR_DIRTY)!=0 ); + assert( (p->flags & PGHDR_CLEAN)==0 ); + pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE); + p->flags &= ~(PGHDR_DIRTY|PGHDR_NEED_SYNC|PGHDR_WRITEABLE); + p->flags |= PGHDR_CLEAN; + pcacheTrace(("%p.CLEAN %d\n",p->pCache,p->pgno)); + assert( sqlite3PcachePageSanity(p) ); + if( p->nRef==0 ){ + pcacheUnpin(p); + } +} + +/* +** Make every page in the cache clean. +*/ +SQLITE_PRIVATE void sqlite3PcacheCleanAll(PCache *pCache){ + PgHdr *p; + pcacheTrace(("%p.CLEAN-ALL\n",pCache)); + while( (p = pCache->pDirty)!=0 ){ + sqlite3PcacheMakeClean(p); + } +} + +/* +** Clear the PGHDR_NEED_SYNC and PGHDR_WRITEABLE flag from all dirty pages. +*/ +SQLITE_PRIVATE void sqlite3PcacheClearWritable(PCache *pCache){ + PgHdr *p; + pcacheTrace(("%p.CLEAR-WRITEABLE\n",pCache)); + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + p->flags &= ~(PGHDR_NEED_SYNC|PGHDR_WRITEABLE); + } + pCache->pSynced = pCache->pDirtyTail; +} + +/* +** Clear the PGHDR_NEED_SYNC flag from all dirty pages. +*/ +SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *pCache){ + PgHdr *p; + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + p->flags &= ~PGHDR_NEED_SYNC; + } + pCache->pSynced = pCache->pDirtyTail; +} + +/* +** Change the page number of page p to newPgno. +*/ +SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){ + PCache *pCache = p->pCache; + sqlite3_pcache_page *pOther; + assert( p->nRef>0 ); + assert( newPgno>0 ); + assert( sqlite3PcachePageSanity(p) ); + pcacheTrace(("%p.MOVE %d -> %d\n",pCache,p->pgno,newPgno)); + pOther = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, newPgno, 0); + if( pOther ){ + PgHdr *pXPage = (PgHdr*)pOther->pExtra; + assert( pXPage->nRef==0 ); + pXPage->nRef++; + pCache->nRefSum++; + sqlite3PcacheDrop(pXPage); + } + sqlite3GlobalConfig.pcache2.xRekey(pCache->pCache, p->pPage, p->pgno,newPgno); + p->pgno = newPgno; + if( (p->flags&PGHDR_DIRTY) && (p->flags&PGHDR_NEED_SYNC) ){ + pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT); + assert( sqlite3PcachePageSanity(p) ); + } +} + +/* +** Drop every cache entry whose page number is greater than "pgno". The +** caller must ensure that there are no outstanding references to any pages +** other than page 1 with a page number greater than pgno. +** +** If there is a reference to page 1 and the pgno parameter passed to this +** function is 0, then the data area associated with page 1 is zeroed, but +** the page object is not dropped. +*/ +SQLITE_PRIVATE void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){ + if( pCache->pCache ){ + PgHdr *p; + PgHdr *pNext; + pcacheTrace(("%p.TRUNCATE %d\n",pCache,pgno)); + for(p=pCache->pDirty; p; p=pNext){ + pNext = p->pDirtyNext; + /* This routine never gets call with a positive pgno except right + ** after sqlite3PcacheCleanAll(). So if there are dirty pages, + ** it must be that pgno==0. + */ + assert( p->pgno>0 ); + if( p->pgno>pgno ){ + assert( p->flags&PGHDR_DIRTY ); + sqlite3PcacheMakeClean(p); + } + } + if( pgno==0 && pCache->nRefSum ){ + sqlite3_pcache_page *pPage1; + pPage1 = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache,1,0); + if( ALWAYS(pPage1) ){ /* Page 1 is always available in cache, because + ** pCache->nRefSum>0 */ + memset(pPage1->pBuf, 0, pCache->szPage); + pgno = 1; + } + } + sqlite3GlobalConfig.pcache2.xTruncate(pCache->pCache, pgno+1); + } +} + +/* +** Close a cache. +*/ +SQLITE_PRIVATE void sqlite3PcacheClose(PCache *pCache){ + assert( pCache->pCache!=0 ); + pcacheTrace(("%p.CLOSE\n",pCache)); + sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache); +} + +/* +** Discard the contents of the cache. +*/ +SQLITE_PRIVATE void sqlite3PcacheClear(PCache *pCache){ + sqlite3PcacheTruncate(pCache, 0); +} + +/* +** Merge two lists of pages connected by pDirty and in pgno order. +** Do not bother fixing the pDirtyPrev pointers. +*/ +static PgHdr *pcacheMergeDirtyList(PgHdr *pA, PgHdr *pB){ + PgHdr result, *pTail; + pTail = &result; + assert( pA!=0 && pB!=0 ); + for(;;){ + if( pA->pgno<pB->pgno ){ + pTail->pDirty = pA; + pTail = pA; + pA = pA->pDirty; + if( pA==0 ){ + pTail->pDirty = pB; + break; + } + }else{ + pTail->pDirty = pB; + pTail = pB; + pB = pB->pDirty; + if( pB==0 ){ + pTail->pDirty = pA; + break; + } + } + } + return result.pDirty; +} + +/* +** Sort the list of pages in ascending order by pgno. Pages are +** connected by pDirty pointers. The pDirtyPrev pointers are +** corrupted by this sort. +** +** Since there cannot be more than 2^31 distinct pages in a database, +** there cannot be more than 31 buckets required by the merge sorter. +** One extra bucket is added to catch overflow in case something +** ever changes to make the previous sentence incorrect. +*/ +#define N_SORT_BUCKET 32 +static PgHdr *pcacheSortDirtyList(PgHdr *pIn){ + PgHdr *a[N_SORT_BUCKET], *p; + int i; + memset(a, 0, sizeof(a)); + while( pIn ){ + p = pIn; + pIn = p->pDirty; + p->pDirty = 0; + for(i=0; ALWAYS(i<N_SORT_BUCKET-1); i++){ + if( a[i]==0 ){ + a[i] = p; + break; + }else{ + p = pcacheMergeDirtyList(a[i], p); + a[i] = 0; + } + } + if( NEVER(i==N_SORT_BUCKET-1) ){ + /* To get here, there need to be 2^(N_SORT_BUCKET) elements in + ** the input list. But that is impossible. + */ + a[i] = pcacheMergeDirtyList(a[i], p); + } + } + p = a[0]; + for(i=1; i<N_SORT_BUCKET; i++){ + if( a[i]==0 ) continue; + p = p ? pcacheMergeDirtyList(p, a[i]) : a[i]; + } + return p; +} + +/* +** Return a list of all dirty pages in the cache, sorted by page number. +*/ +SQLITE_PRIVATE PgHdr *sqlite3PcacheDirtyList(PCache *pCache){ + PgHdr *p; + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + p->pDirty = p->pDirtyNext; + } + return pcacheSortDirtyList(pCache->pDirty); +} + +/* +** Return the total number of references to all pages held by the cache. +** +** This is not the total number of pages referenced, but the sum of the +** reference count for all pages. +*/ +SQLITE_PRIVATE i64 sqlite3PcacheRefCount(PCache *pCache){ + return pCache->nRefSum; +} + +/* +** Return the number of references to the page supplied as an argument. +*/ +SQLITE_PRIVATE i64 sqlite3PcachePageRefcount(PgHdr *p){ + return p->nRef; +} + +/* +** Return the total number of pages in the cache. +*/ +SQLITE_PRIVATE int sqlite3PcachePagecount(PCache *pCache){ + assert( pCache->pCache!=0 ); + return sqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache); +} + +#ifdef SQLITE_TEST +/* +** Get the suggested cache-size value. +*/ +SQLITE_PRIVATE int sqlite3PcacheGetCachesize(PCache *pCache){ + return numberOfCachePages(pCache); +} +#endif + +/* +** Set the suggested cache-size value. +*/ +SQLITE_PRIVATE void sqlite3PcacheSetCachesize(PCache *pCache, int mxPage){ + assert( pCache->pCache!=0 ); + pCache->szCache = mxPage; + sqlite3GlobalConfig.pcache2.xCachesize(pCache->pCache, + numberOfCachePages(pCache)); +} + +/* +** Set the suggested cache-spill value. Make no changes if if the +** argument is zero. Return the effective cache-spill size, which will +** be the larger of the szSpill and szCache. +*/ +SQLITE_PRIVATE int sqlite3PcacheSetSpillsize(PCache *p, int mxPage){ + int res; + assert( p->pCache!=0 ); + if( mxPage ){ + if( mxPage<0 ){ + mxPage = (int)((-1024*(i64)mxPage)/(p->szPage+p->szExtra)); + } + p->szSpill = mxPage; + } + res = numberOfCachePages(p); + if( res<p->szSpill ) res = p->szSpill; + return res; +} + +/* +** Free up as much memory as possible from the page cache. +*/ +SQLITE_PRIVATE void sqlite3PcacheShrink(PCache *pCache){ + assert( pCache->pCache!=0 ); + sqlite3GlobalConfig.pcache2.xShrink(pCache->pCache); +} + +/* +** Return the size of the header added by this middleware layer +** in the page-cache hierarchy. +*/ +SQLITE_PRIVATE int sqlite3HeaderSizePcache(void){ return ROUND8(sizeof(PgHdr)); } + +/* +** Return the number of dirty pages currently in the cache, as a percentage +** of the configured cache size. +*/ +SQLITE_PRIVATE int sqlite3PCachePercentDirty(PCache *pCache){ + PgHdr *pDirty; + int nDirty = 0; + int nCache = numberOfCachePages(pCache); + for(pDirty=pCache->pDirty; pDirty; pDirty=pDirty->pDirtyNext) nDirty++; + return nCache ? (int)(((i64)nDirty * 100) / nCache) : 0; +} + +#ifdef SQLITE_DIRECT_OVERFLOW_READ +/* +** Return true if there are one or more dirty pages in the cache. Else false. +*/ +SQLITE_PRIVATE int sqlite3PCacheIsDirty(PCache *pCache){ + return (pCache->pDirty!=0); +} +#endif + +#if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG) +/* +** For all dirty pages currently in the cache, invoke the specified +** callback. This is only used if the SQLITE_CHECK_PAGES macro is +** defined. +*/ +SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *)){ + PgHdr *pDirty; + for(pDirty=pCache->pDirty; pDirty; pDirty=pDirty->pDirtyNext){ + xIter(pDirty); + } +} +#endif + +/************** End of pcache.c **********************************************/ +/************** Begin file pcache1.c *****************************************/ +/* +** 2008 November 05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements the default page cache implementation (the +** sqlite3_pcache interface). It also contains part of the implementation +** of the SQLITE_CONFIG_PAGECACHE and sqlite3_release_memory() features. +** If the default page cache implementation is overridden, then neither of +** these two features are available. +** +** A Page cache line looks like this: +** +** ------------------------------------------------------------- +** | database page content | PgHdr1 | MemPage | PgHdr | +** ------------------------------------------------------------- +** +** The database page content is up front (so that buffer overreads tend to +** flow harmlessly into the PgHdr1, MemPage, and PgHdr extensions). MemPage +** is the extension added by the btree.c module containing information such +** as the database page number and how that database page is used. PgHdr +** is added by the pcache.c layer and contains information used to keep track +** of which pages are "dirty". PgHdr1 is an extension added by this +** module (pcache1.c). The PgHdr1 header is a subclass of sqlite3_pcache_page. +** PgHdr1 contains information needed to look up a page by its page number. +** The superclass sqlite3_pcache_page.pBuf points to the start of the +** database page content and sqlite3_pcache_page.pExtra points to PgHdr. +** +** The size of the extension (MemPage+PgHdr+PgHdr1) can be determined at +** runtime using sqlite3_config(SQLITE_CONFIG_PCACHE_HDRSZ, &size). The +** sizes of the extensions sum to 272 bytes on x64 for 3.8.10, but this +** size can vary according to architecture, compile-time options, and +** SQLite library version number. +** +** Historical note: It used to be that if the SQLITE_PCACHE_SEPARATE_HEADER +** was defined, then the page content would be held in a separate memory +** allocation from the PgHdr1. This was intended to avoid clownshoe memory +** allocations. However, the btree layer needs a small (16-byte) overrun +** area after the page content buffer. The header serves as that overrun +** area. Therefore SQLITE_PCACHE_SEPARATE_HEADER was discontinued to avoid +** any possibility of a memory error. +** +** This module tracks pointers to PgHdr1 objects. Only pcache.c communicates +** with this module. Information is passed back and forth as PgHdr1 pointers. +** +** The pcache.c and pager.c modules deal pointers to PgHdr objects. +** The btree.c module deals with pointers to MemPage objects. +** +** SOURCE OF PAGE CACHE MEMORY: +** +** Memory for a page might come from any of three sources: +** +** (1) The general-purpose memory allocator - sqlite3Malloc() +** (2) Global page-cache memory provided using sqlite3_config() with +** SQLITE_CONFIG_PAGECACHE. +** (3) PCache-local bulk allocation. +** +** The third case is a chunk of heap memory (defaulting to 100 pages worth) +** that is allocated when the page cache is created. The size of the local +** bulk allocation can be adjusted using +** +** sqlite3_config(SQLITE_CONFIG_PAGECACHE, (void*)0, 0, N). +** +** If N is positive, then N pages worth of memory are allocated using a single +** sqlite3Malloc() call and that memory is used for the first N pages allocated. +** Or if N is negative, then -1024*N bytes of memory are allocated and used +** for as many pages as can be accommodated. +** +** Only one of (2) or (3) can be used. Once the memory available to (2) or +** (3) is exhausted, subsequent allocations fail over to the general-purpose +** memory allocator (1). +** +** Earlier versions of SQLite used only methods (1) and (2). But experiments +** show that method (3) with N==100 provides about a 5% performance boost for +** common workloads. +*/ +/* #include "sqliteInt.h" */ + +typedef struct PCache1 PCache1; +typedef struct PgHdr1 PgHdr1; +typedef struct PgFreeslot PgFreeslot; +typedef struct PGroup PGroup; + +/* +** Each cache entry is represented by an instance of the following +** structure. A buffer of PgHdr1.pCache->szPage bytes is allocated +** directly before this structure and is used to cache the page content. +** +** When reading a corrupt database file, it is possible that SQLite might +** read a few bytes (no more than 16 bytes) past the end of the page buffer. +** It will only read past the end of the page buffer, never write. This +** object is positioned immediately after the page buffer to serve as an +** overrun area, so that overreads are harmless. +** +** Variables isBulkLocal and isAnchor were once type "u8". That works, +** but causes a 2-byte gap in the structure for most architectures (since +** pointers must be either 4 or 8-byte aligned). As this structure is located +** in memory directly after the associated page data, if the database is +** corrupt, code at the b-tree layer may overread the page buffer and +** read part of this structure before the corruption is detected. This +** can cause a valgrind error if the uninitialized gap is accessed. Using u16 +** ensures there is no such gap, and therefore no bytes of uninitialized +** memory in the structure. +** +** The pLruNext and pLruPrev pointers form a double-linked circular list +** of all pages that are unpinned. The PGroup.lru element (which should be +** the only element on the list with PgHdr1.isAnchor set to 1) forms the +** beginning and the end of the list. +*/ +struct PgHdr1 { + sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */ + unsigned int iKey; /* Key value (page number) */ + u16 isBulkLocal; /* This page from bulk local storage */ + u16 isAnchor; /* This is the PGroup.lru element */ + PgHdr1 *pNext; /* Next in hash table chain */ + PCache1 *pCache; /* Cache that currently owns this page */ + PgHdr1 *pLruNext; /* Next in circular LRU list of unpinned pages */ + PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */ + /* NB: pLruPrev is only valid if pLruNext!=0 */ +}; + +/* +** A page is pinned if it is not on the LRU list. To be "pinned" means +** that the page is in active use and must not be deallocated. +*/ +#define PAGE_IS_PINNED(p) ((p)->pLruNext==0) +#define PAGE_IS_UNPINNED(p) ((p)->pLruNext!=0) + +/* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set +** of one or more PCaches that are able to recycle each other's unpinned +** pages when they are under memory pressure. A PGroup is an instance of +** the following object. +** +** This page cache implementation works in one of two modes: +** +** (1) Every PCache is the sole member of its own PGroup. There is +** one PGroup per PCache. +** +** (2) There is a single global PGroup that all PCaches are a member +** of. +** +** Mode 1 uses more memory (since PCache instances are not able to rob +** unused pages from other PCaches) but it also operates without a mutex, +** and is therefore often faster. Mode 2 requires a mutex in order to be +** threadsafe, but recycles pages more efficiently. +** +** For mode (1), PGroup.mutex is NULL. For mode (2) there is only a single +** PGroup which is the pcache1.grp global variable and its mutex is +** SQLITE_MUTEX_STATIC_LRU. +*/ +struct PGroup { + sqlite3_mutex *mutex; /* MUTEX_STATIC_LRU or NULL */ + unsigned int nMaxPage; /* Sum of nMax for purgeable caches */ + unsigned int nMinPage; /* Sum of nMin for purgeable caches */ + unsigned int mxPinned; /* nMaxpage + 10 - nMinPage */ + unsigned int nPurgeable; /* Number of purgeable pages allocated */ + PgHdr1 lru; /* The beginning and end of the LRU list */ +}; + +/* Each page cache is an instance of the following object. Every +** open database file (including each in-memory database and each +** temporary or transient database) has a single page cache which +** is an instance of this object. +** +** Pointers to structures of this type are cast and returned as +** opaque sqlite3_pcache* handles. +*/ +struct PCache1 { + /* Cache configuration parameters. Page size (szPage) and the purgeable + ** flag (bPurgeable) and the pnPurgeable pointer are all set when the + ** cache is created and are never changed thereafter. nMax may be + ** modified at any time by a call to the pcache1Cachesize() method. + ** The PGroup mutex must be held when accessing nMax. + */ + PGroup *pGroup; /* PGroup this cache belongs to */ + unsigned int *pnPurgeable; /* Pointer to pGroup->nPurgeable */ + int szPage; /* Size of database content section */ + int szExtra; /* sizeof(MemPage)+sizeof(PgHdr) */ + int szAlloc; /* Total size of one pcache line */ + int bPurgeable; /* True if cache is purgeable */ + unsigned int nMin; /* Minimum number of pages reserved */ + unsigned int nMax; /* Configured "cache_size" value */ + unsigned int n90pct; /* nMax*9/10 */ + unsigned int iMaxKey; /* Largest key seen since xTruncate() */ + unsigned int nPurgeableDummy; /* pnPurgeable points here when not used*/ + + /* Hash table of all pages. The following variables may only be accessed + ** when the accessor is holding the PGroup mutex. + */ + unsigned int nRecyclable; /* Number of pages in the LRU list */ + unsigned int nPage; /* Total number of pages in apHash */ + unsigned int nHash; /* Number of slots in apHash[] */ + PgHdr1 **apHash; /* Hash table for fast lookup by key */ + PgHdr1 *pFree; /* List of unused pcache-local pages */ + void *pBulk; /* Bulk memory used by pcache-local */ +}; + +/* +** Free slots in the allocator used to divide up the global page cache +** buffer provided using the SQLITE_CONFIG_PAGECACHE mechanism. +*/ +struct PgFreeslot { + PgFreeslot *pNext; /* Next free slot */ +}; + +/* +** Global data used by this cache. +*/ +static SQLITE_WSD struct PCacheGlobal { + PGroup grp; /* The global PGroup for mode (2) */ + + /* Variables related to SQLITE_CONFIG_PAGECACHE settings. The + ** szSlot, nSlot, pStart, pEnd, nReserve, and isInit values are all + ** fixed at sqlite3_initialize() time and do not require mutex protection. + ** The nFreeSlot and pFree values do require mutex protection. + */ + int isInit; /* True if initialized */ + int separateCache; /* Use a new PGroup for each PCache */ + int nInitPage; /* Initial bulk allocation size */ + int szSlot; /* Size of each free slot */ + int nSlot; /* The number of pcache slots */ + int nReserve; /* Try to keep nFreeSlot above this */ + void *pStart, *pEnd; /* Bounds of global page cache memory */ + /* Above requires no mutex. Use mutex below for variable that follow. */ + sqlite3_mutex *mutex; /* Mutex for accessing the following: */ + PgFreeslot *pFree; /* Free page blocks */ + int nFreeSlot; /* Number of unused pcache slots */ + /* The following value requires a mutex to change. We skip the mutex on + ** reading because (1) most platforms read a 32-bit integer atomically and + ** (2) even if an incorrect value is read, no great harm is done since this + ** is really just an optimization. */ + int bUnderPressure; /* True if low on PAGECACHE memory */ +} pcache1_g; + +/* +** All code in this file should access the global structure above via the +** alias "pcache1". This ensures that the WSD emulation is used when +** compiling for systems that do not support real WSD. +*/ +#define pcache1 (GLOBAL(struct PCacheGlobal, pcache1_g)) + +/* +** Macros to enter and leave the PCache LRU mutex. +*/ +#if !defined(SQLITE_ENABLE_MEMORY_MANAGEMENT) || SQLITE_THREADSAFE==0 +# define pcache1EnterMutex(X) assert((X)->mutex==0) +# define pcache1LeaveMutex(X) assert((X)->mutex==0) +# define PCACHE1_MIGHT_USE_GROUP_MUTEX 0 +#else +# define pcache1EnterMutex(X) sqlite3_mutex_enter((X)->mutex) +# define pcache1LeaveMutex(X) sqlite3_mutex_leave((X)->mutex) +# define PCACHE1_MIGHT_USE_GROUP_MUTEX 1 +#endif + +/******************************************************************************/ +/******** Page Allocation/SQLITE_CONFIG_PCACHE Related Functions **************/ + + +/* +** This function is called during initialization if a static buffer is +** supplied to use for the page-cache by passing the SQLITE_CONFIG_PAGECACHE +** verb to sqlite3_config(). Parameter pBuf points to an allocation large +** enough to contain 'n' buffers of 'sz' bytes each. +** +** This routine is called from sqlite3_initialize() and so it is guaranteed +** to be serialized already. There is no need for further mutexing. +*/ +SQLITE_PRIVATE void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){ + if( pcache1.isInit ){ + PgFreeslot *p; + if( pBuf==0 ) sz = n = 0; + if( n==0 ) sz = 0; + sz = ROUNDDOWN8(sz); + pcache1.szSlot = sz; + pcache1.nSlot = pcache1.nFreeSlot = n; + pcache1.nReserve = n>90 ? 10 : (n/10 + 1); + pcache1.pStart = pBuf; + pcache1.pFree = 0; + pcache1.bUnderPressure = 0; + while( n-- ){ + p = (PgFreeslot*)pBuf; + p->pNext = pcache1.pFree; + pcache1.pFree = p; + pBuf = (void*)&((char*)pBuf)[sz]; + } + pcache1.pEnd = pBuf; + } +} + +/* +** Try to initialize the pCache->pFree and pCache->pBulk fields. Return +** true if pCache->pFree ends up containing one or more free pages. +*/ +static int pcache1InitBulk(PCache1 *pCache){ + i64 szBulk; + char *zBulk; + if( pcache1.nInitPage==0 ) return 0; + /* Do not bother with a bulk allocation if the cache size very small */ + if( pCache->nMax<3 ) return 0; + sqlite3BeginBenignMalloc(); + if( pcache1.nInitPage>0 ){ + szBulk = pCache->szAlloc * (i64)pcache1.nInitPage; + }else{ + szBulk = -1024 * (i64)pcache1.nInitPage; + } + if( szBulk > pCache->szAlloc*(i64)pCache->nMax ){ + szBulk = pCache->szAlloc*(i64)pCache->nMax; + } + zBulk = pCache->pBulk = sqlite3Malloc( szBulk ); + sqlite3EndBenignMalloc(); + if( zBulk ){ + int nBulk = sqlite3MallocSize(zBulk)/pCache->szAlloc; + do{ + PgHdr1 *pX = (PgHdr1*)&zBulk[pCache->szPage]; + pX->page.pBuf = zBulk; + pX->page.pExtra = &pX[1]; + pX->isBulkLocal = 1; + pX->isAnchor = 0; + pX->pNext = pCache->pFree; + pX->pLruPrev = 0; /* Initializing this saves a valgrind error */ + pCache->pFree = pX; + zBulk += pCache->szAlloc; + }while( --nBulk ); + } + return pCache->pFree!=0; +} + +/* +** Malloc function used within this file to allocate space from the buffer +** configured using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no +** such buffer exists or there is no space left in it, this function falls +** back to sqlite3Malloc(). +** +** Multiple threads can run this routine at the same time. Global variables +** in pcache1 need to be protected via mutex. +*/ +static void *pcache1Alloc(int nByte){ + void *p = 0; + assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); + if( nByte<=pcache1.szSlot ){ + sqlite3_mutex_enter(pcache1.mutex); + p = (PgHdr1 *)pcache1.pFree; + if( p ){ + pcache1.pFree = pcache1.pFree->pNext; + pcache1.nFreeSlot--; + pcache1.bUnderPressure = pcache1.nFreeSlot<pcache1.nReserve; + assert( pcache1.nFreeSlot>=0 ); + sqlite3StatusHighwater(SQLITE_STATUS_PAGECACHE_SIZE, nByte); + sqlite3StatusUp(SQLITE_STATUS_PAGECACHE_USED, 1); + } + sqlite3_mutex_leave(pcache1.mutex); + } + if( p==0 ){ + /* Memory is not available in the SQLITE_CONFIG_PAGECACHE pool. Get + ** it from sqlite3Malloc instead. + */ + p = sqlite3Malloc(nByte); +#ifndef SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS + if( p ){ + int sz = sqlite3MallocSize(p); + sqlite3_mutex_enter(pcache1.mutex); + sqlite3StatusHighwater(SQLITE_STATUS_PAGECACHE_SIZE, nByte); + sqlite3StatusUp(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz); + sqlite3_mutex_leave(pcache1.mutex); + } +#endif + sqlite3MemdebugSetType(p, MEMTYPE_PCACHE); + } + return p; +} + +/* +** Free an allocated buffer obtained from pcache1Alloc(). +*/ +static void pcache1Free(void *p){ + if( p==0 ) return; + if( SQLITE_WITHIN(p, pcache1.pStart, pcache1.pEnd) ){ + PgFreeslot *pSlot; + sqlite3_mutex_enter(pcache1.mutex); + sqlite3StatusDown(SQLITE_STATUS_PAGECACHE_USED, 1); + pSlot = (PgFreeslot*)p; + pSlot->pNext = pcache1.pFree; + pcache1.pFree = pSlot; + pcache1.nFreeSlot++; + pcache1.bUnderPressure = pcache1.nFreeSlot<pcache1.nReserve; + assert( pcache1.nFreeSlot<=pcache1.nSlot ); + sqlite3_mutex_leave(pcache1.mutex); + }else{ + assert( sqlite3MemdebugHasType(p, MEMTYPE_PCACHE) ); + sqlite3MemdebugSetType(p, MEMTYPE_HEAP); +#ifndef SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS + { + int nFreed = 0; + nFreed = sqlite3MallocSize(p); + sqlite3_mutex_enter(pcache1.mutex); + sqlite3StatusDown(SQLITE_STATUS_PAGECACHE_OVERFLOW, nFreed); + sqlite3_mutex_leave(pcache1.mutex); + } +#endif + sqlite3_free(p); + } +} + +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT +/* +** Return the size of a pcache allocation +*/ +static int pcache1MemSize(void *p){ + if( p>=pcache1.pStart && p<pcache1.pEnd ){ + return pcache1.szSlot; + }else{ + int iSize; + assert( sqlite3MemdebugHasType(p, MEMTYPE_PCACHE) ); + sqlite3MemdebugSetType(p, MEMTYPE_HEAP); + iSize = sqlite3MallocSize(p); + sqlite3MemdebugSetType(p, MEMTYPE_PCACHE); + return iSize; + } +} +#endif /* SQLITE_ENABLE_MEMORY_MANAGEMENT */ + +/* +** Allocate a new page object initially associated with cache pCache. +*/ +static PgHdr1 *pcache1AllocPage(PCache1 *pCache, int benignMalloc){ + PgHdr1 *p = 0; + void *pPg; + + assert( sqlite3_mutex_held(pCache->pGroup->mutex) ); + if( pCache->pFree || (pCache->nPage==0 && pcache1InitBulk(pCache)) ){ + assert( pCache->pFree!=0 ); + p = pCache->pFree; + pCache->pFree = p->pNext; + p->pNext = 0; + }else{ +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT + /* The group mutex must be released before pcache1Alloc() is called. This + ** is because it might call sqlite3_release_memory(), which assumes that + ** this mutex is not held. */ + assert( pcache1.separateCache==0 ); + assert( pCache->pGroup==&pcache1.grp ); + pcache1LeaveMutex(pCache->pGroup); +#endif + if( benignMalloc ){ sqlite3BeginBenignMalloc(); } + pPg = pcache1Alloc(pCache->szAlloc); + if( benignMalloc ){ sqlite3EndBenignMalloc(); } +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT + pcache1EnterMutex(pCache->pGroup); +#endif + if( pPg==0 ) return 0; + p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage]; + p->page.pBuf = pPg; + p->page.pExtra = &p[1]; + p->isBulkLocal = 0; + p->isAnchor = 0; + p->pLruPrev = 0; /* Initializing this saves a valgrind error */ + } + (*pCache->pnPurgeable)++; + return p; +} + +/* +** Free a page object allocated by pcache1AllocPage(). +*/ +static void pcache1FreePage(PgHdr1 *p){ + PCache1 *pCache; + assert( p!=0 ); + pCache = p->pCache; + assert( sqlite3_mutex_held(p->pCache->pGroup->mutex) ); + if( p->isBulkLocal ){ + p->pNext = pCache->pFree; + pCache->pFree = p; + }else{ + pcache1Free(p->page.pBuf); + } + (*pCache->pnPurgeable)--; +} + +/* +** Malloc function used by SQLite to obtain space from the buffer configured +** using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no such buffer +** exists, this function falls back to sqlite3Malloc(). +*/ +SQLITE_PRIVATE void *sqlite3PageMalloc(int sz){ + assert( sz<=65536+8 ); /* These allocations are never very large */ + return pcache1Alloc(sz); +} + +/* +** Free an allocated buffer obtained from sqlite3PageMalloc(). +*/ +SQLITE_PRIVATE void sqlite3PageFree(void *p){ + pcache1Free(p); +} + + +/* +** Return true if it desirable to avoid allocating a new page cache +** entry. +** +** If memory was allocated specifically to the page cache using +** SQLITE_CONFIG_PAGECACHE but that memory has all been used, then +** it is desirable to avoid allocating a new page cache entry because +** presumably SQLITE_CONFIG_PAGECACHE was suppose to be sufficient +** for all page cache needs and we should not need to spill the +** allocation onto the heap. +** +** Or, the heap is used for all page cache memory but the heap is +** under memory pressure, then again it is desirable to avoid +** allocating a new page cache entry in order to avoid stressing +** the heap even further. +*/ +static int pcache1UnderMemoryPressure(PCache1 *pCache){ + if( pcache1.nSlot && (pCache->szPage+pCache->szExtra)<=pcache1.szSlot ){ + return pcache1.bUnderPressure; + }else{ + return sqlite3HeapNearlyFull(); + } +} + +/******************************************************************************/ +/******** General Implementation Functions ************************************/ + +/* +** This function is used to resize the hash table used by the cache passed +** as the first argument. +** +** The PCache mutex must be held when this function is called. +*/ +static void pcache1ResizeHash(PCache1 *p){ + PgHdr1 **apNew; + unsigned int nNew; + unsigned int i; + + assert( sqlite3_mutex_held(p->pGroup->mutex) ); + + nNew = p->nHash*2; + if( nNew<256 ){ + nNew = 256; + } + + pcache1LeaveMutex(p->pGroup); + if( p->nHash ){ sqlite3BeginBenignMalloc(); } + apNew = (PgHdr1 **)sqlite3MallocZero(sizeof(PgHdr1 *)*nNew); + if( p->nHash ){ sqlite3EndBenignMalloc(); } + pcache1EnterMutex(p->pGroup); + if( apNew ){ + for(i=0; i<p->nHash; i++){ + PgHdr1 *pPage; + PgHdr1 *pNext = p->apHash[i]; + while( (pPage = pNext)!=0 ){ + unsigned int h = pPage->iKey % nNew; + pNext = pPage->pNext; + pPage->pNext = apNew[h]; + apNew[h] = pPage; + } + } + sqlite3_free(p->apHash); + p->apHash = apNew; + p->nHash = nNew; + } +} + +/* +** This function is used internally to remove the page pPage from the +** PGroup LRU list, if is part of it. If pPage is not part of the PGroup +** LRU list, then this function is a no-op. +** +** The PGroup mutex must be held when this function is called. +*/ +static PgHdr1 *pcache1PinPage(PgHdr1 *pPage){ + assert( pPage!=0 ); + assert( PAGE_IS_UNPINNED(pPage) ); + assert( pPage->pLruNext ); + assert( pPage->pLruPrev ); + assert( sqlite3_mutex_held(pPage->pCache->pGroup->mutex) ); + pPage->pLruPrev->pLruNext = pPage->pLruNext; + pPage->pLruNext->pLruPrev = pPage->pLruPrev; + pPage->pLruNext = 0; + /* pPage->pLruPrev = 0; + ** No need to clear pLruPrev as it is never accessed if pLruNext is 0 */ + assert( pPage->isAnchor==0 ); + assert( pPage->pCache->pGroup->lru.isAnchor==1 ); + pPage->pCache->nRecyclable--; + return pPage; +} + + +/* +** Remove the page supplied as an argument from the hash table +** (PCache1.apHash structure) that it is currently stored in. +** Also free the page if freePage is true. +** +** The PGroup mutex must be held when this function is called. +*/ +static void pcache1RemoveFromHash(PgHdr1 *pPage, int freeFlag){ + unsigned int h; + PCache1 *pCache = pPage->pCache; + PgHdr1 **pp; + + assert( sqlite3_mutex_held(pCache->pGroup->mutex) ); + h = pPage->iKey % pCache->nHash; + for(pp=&pCache->apHash[h]; (*pp)!=pPage; pp=&(*pp)->pNext); + *pp = (*pp)->pNext; + + pCache->nPage--; + if( freeFlag ) pcache1FreePage(pPage); +} + +/* +** If there are currently more than nMaxPage pages allocated, try +** to recycle pages to reduce the number allocated to nMaxPage. +*/ +static void pcache1EnforceMaxPage(PCache1 *pCache){ + PGroup *pGroup = pCache->pGroup; + PgHdr1 *p; + assert( sqlite3_mutex_held(pGroup->mutex) ); + while( pGroup->nPurgeable>pGroup->nMaxPage + && (p=pGroup->lru.pLruPrev)->isAnchor==0 + ){ + assert( p->pCache->pGroup==pGroup ); + assert( PAGE_IS_UNPINNED(p) ); + pcache1PinPage(p); + pcache1RemoveFromHash(p, 1); + } + if( pCache->nPage==0 && pCache->pBulk ){ + sqlite3_free(pCache->pBulk); + pCache->pBulk = pCache->pFree = 0; + } +} + +/* +** Discard all pages from cache pCache with a page number (key value) +** greater than or equal to iLimit. Any pinned pages that meet this +** criteria are unpinned before they are discarded. +** +** The PCache mutex must be held when this function is called. +*/ +static void pcache1TruncateUnsafe( + PCache1 *pCache, /* The cache to truncate */ + unsigned int iLimit /* Drop pages with this pgno or larger */ +){ + TESTONLY( int nPage = 0; ) /* To assert pCache->nPage is correct */ + unsigned int h, iStop; + assert( sqlite3_mutex_held(pCache->pGroup->mutex) ); + assert( pCache->iMaxKey >= iLimit ); + assert( pCache->nHash > 0 ); + if( pCache->iMaxKey - iLimit < pCache->nHash ){ + /* If we are just shaving the last few pages off the end of the + ** cache, then there is no point in scanning the entire hash table. + ** Only scan those hash slots that might contain pages that need to + ** be removed. */ + h = iLimit % pCache->nHash; + iStop = pCache->iMaxKey % pCache->nHash; + TESTONLY( nPage = -10; ) /* Disable the pCache->nPage validity check */ + }else{ + /* This is the general case where many pages are being removed. + ** It is necessary to scan the entire hash table */ + h = pCache->nHash/2; + iStop = h - 1; + } + for(;;){ + PgHdr1 **pp; + PgHdr1 *pPage; + assert( h<pCache->nHash ); + pp = &pCache->apHash[h]; + while( (pPage = *pp)!=0 ){ + if( pPage->iKey>=iLimit ){ + pCache->nPage--; + *pp = pPage->pNext; + if( PAGE_IS_UNPINNED(pPage) ) pcache1PinPage(pPage); + pcache1FreePage(pPage); + }else{ + pp = &pPage->pNext; + TESTONLY( if( nPage>=0 ) nPage++; ) + } + } + if( h==iStop ) break; + h = (h+1) % pCache->nHash; + } + assert( nPage<0 || pCache->nPage==(unsigned)nPage ); +} + +/******************************************************************************/ +/******** sqlite3_pcache Methods **********************************************/ + +/* +** Implementation of the sqlite3_pcache.xInit method. +*/ +static int pcache1Init(void *NotUsed){ + UNUSED_PARAMETER(NotUsed); + assert( pcache1.isInit==0 ); + memset(&pcache1, 0, sizeof(pcache1)); + + + /* + ** The pcache1.separateCache variable is true if each PCache has its own + ** private PGroup (mode-1). pcache1.separateCache is false if the single + ** PGroup in pcache1.grp is used for all page caches (mode-2). + ** + ** * Always use a unified cache (mode-2) if ENABLE_MEMORY_MANAGEMENT + ** + ** * Use a unified cache in single-threaded applications that have + ** configured a start-time buffer for use as page-cache memory using + ** sqlite3_config(SQLITE_CONFIG_PAGECACHE, pBuf, sz, N) with non-NULL + ** pBuf argument. + ** + ** * Otherwise use separate caches (mode-1) + */ +#if defined(SQLITE_ENABLE_MEMORY_MANAGEMENT) + pcache1.separateCache = 0; +#elif SQLITE_THREADSAFE + pcache1.separateCache = sqlite3GlobalConfig.pPage==0 + || sqlite3GlobalConfig.bCoreMutex>0; +#else + pcache1.separateCache = sqlite3GlobalConfig.pPage==0; +#endif + +#if SQLITE_THREADSAFE + if( sqlite3GlobalConfig.bCoreMutex ){ + pcache1.grp.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_LRU); + pcache1.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PMEM); + } +#endif + if( pcache1.separateCache + && sqlite3GlobalConfig.nPage!=0 + && sqlite3GlobalConfig.pPage==0 + ){ + pcache1.nInitPage = sqlite3GlobalConfig.nPage; + }else{ + pcache1.nInitPage = 0; + } + pcache1.grp.mxPinned = 10; + pcache1.isInit = 1; + return SQLITE_OK; +} + +/* +** Implementation of the sqlite3_pcache.xShutdown method. +** Note that the static mutex allocated in xInit does +** not need to be freed. +*/ +static void pcache1Shutdown(void *NotUsed){ + UNUSED_PARAMETER(NotUsed); + assert( pcache1.isInit!=0 ); + memset(&pcache1, 0, sizeof(pcache1)); +} + +/* forward declaration */ +static void pcache1Destroy(sqlite3_pcache *p); + +/* +** Implementation of the sqlite3_pcache.xCreate method. +** +** Allocate a new cache. +*/ +static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){ + PCache1 *pCache; /* The newly created page cache */ + PGroup *pGroup; /* The group the new page cache will belong to */ + int sz; /* Bytes of memory required to allocate the new cache */ + + assert( (szPage & (szPage-1))==0 && szPage>=512 && szPage<=65536 ); + assert( szExtra < 300 ); + + sz = sizeof(PCache1) + sizeof(PGroup)*pcache1.separateCache; + pCache = (PCache1 *)sqlite3MallocZero(sz); + if( pCache ){ + if( pcache1.separateCache ){ + pGroup = (PGroup*)&pCache[1]; + pGroup->mxPinned = 10; + }else{ + pGroup = &pcache1.grp; + } + pcache1EnterMutex(pGroup); + if( pGroup->lru.isAnchor==0 ){ + pGroup->lru.isAnchor = 1; + pGroup->lru.pLruPrev = pGroup->lru.pLruNext = &pGroup->lru; + } + pCache->pGroup = pGroup; + pCache->szPage = szPage; + pCache->szExtra = szExtra; + pCache->szAlloc = szPage + szExtra + ROUND8(sizeof(PgHdr1)); + pCache->bPurgeable = (bPurgeable ? 1 : 0); + pcache1ResizeHash(pCache); + if( bPurgeable ){ + pCache->nMin = 10; + pGroup->nMinPage += pCache->nMin; + pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; + pCache->pnPurgeable = &pGroup->nPurgeable; + }else{ + pCache->pnPurgeable = &pCache->nPurgeableDummy; + } + pcache1LeaveMutex(pGroup); + if( pCache->nHash==0 ){ + pcache1Destroy((sqlite3_pcache*)pCache); + pCache = 0; + } + } + return (sqlite3_pcache *)pCache; +} + +/* +** Implementation of the sqlite3_pcache.xCachesize method. +** +** Configure the cache_size limit for a cache. +*/ +static void pcache1Cachesize(sqlite3_pcache *p, int nMax){ + PCache1 *pCache = (PCache1 *)p; + u32 n; + assert( nMax>=0 ); + if( pCache->bPurgeable ){ + PGroup *pGroup = pCache->pGroup; + pcache1EnterMutex(pGroup); + n = (u32)nMax; + if( n > 0x7fff0000 - pGroup->nMaxPage + pCache->nMax ){ + n = 0x7fff0000 - pGroup->nMaxPage + pCache->nMax; + } + pGroup->nMaxPage += (n - pCache->nMax); + pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; + pCache->nMax = n; + pCache->n90pct = pCache->nMax*9/10; + pcache1EnforceMaxPage(pCache); + pcache1LeaveMutex(pGroup); + } +} + +/* +** Implementation of the sqlite3_pcache.xShrink method. +** +** Free up as much memory as possible. +*/ +static void pcache1Shrink(sqlite3_pcache *p){ + PCache1 *pCache = (PCache1*)p; + if( pCache->bPurgeable ){ + PGroup *pGroup = pCache->pGroup; + unsigned int savedMaxPage; + pcache1EnterMutex(pGroup); + savedMaxPage = pGroup->nMaxPage; + pGroup->nMaxPage = 0; + pcache1EnforceMaxPage(pCache); + pGroup->nMaxPage = savedMaxPage; + pcache1LeaveMutex(pGroup); + } +} + +/* +** Implementation of the sqlite3_pcache.xPagecount method. +*/ +static int pcache1Pagecount(sqlite3_pcache *p){ + int n; + PCache1 *pCache = (PCache1*)p; + pcache1EnterMutex(pCache->pGroup); + n = pCache->nPage; + pcache1LeaveMutex(pCache->pGroup); + return n; +} + + +/* +** Implement steps 3, 4, and 5 of the pcache1Fetch() algorithm described +** in the header of the pcache1Fetch() procedure. +** +** This steps are broken out into a separate procedure because they are +** usually not needed, and by avoiding the stack initialization required +** for these steps, the main pcache1Fetch() procedure can run faster. +*/ +static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( + PCache1 *pCache, + unsigned int iKey, + int createFlag +){ + unsigned int nPinned; + PGroup *pGroup = pCache->pGroup; + PgHdr1 *pPage = 0; + + /* Step 3: Abort if createFlag is 1 but the cache is nearly full */ + assert( pCache->nPage >= pCache->nRecyclable ); + nPinned = pCache->nPage - pCache->nRecyclable; + assert( pGroup->mxPinned == pGroup->nMaxPage + 10 - pGroup->nMinPage ); + assert( pCache->n90pct == pCache->nMax*9/10 ); + if( createFlag==1 && ( + nPinned>=pGroup->mxPinned + || nPinned>=pCache->n90pct + || (pcache1UnderMemoryPressure(pCache) && pCache->nRecyclable<nPinned) + )){ + return 0; + } + + if( pCache->nPage>=pCache->nHash ) pcache1ResizeHash(pCache); + assert( pCache->nHash>0 && pCache->apHash ); + + /* Step 4. Try to recycle a page. */ + if( pCache->bPurgeable + && !pGroup->lru.pLruPrev->isAnchor + && ((pCache->nPage+1>=pCache->nMax) || pcache1UnderMemoryPressure(pCache)) + ){ + PCache1 *pOther; + pPage = pGroup->lru.pLruPrev; + assert( PAGE_IS_UNPINNED(pPage) ); + pcache1RemoveFromHash(pPage, 0); + pcache1PinPage(pPage); + pOther = pPage->pCache; + if( pOther->szAlloc != pCache->szAlloc ){ + pcache1FreePage(pPage); + pPage = 0; + }else{ + pGroup->nPurgeable -= (pOther->bPurgeable - pCache->bPurgeable); + } + } + + /* Step 5. If a usable page buffer has still not been found, + ** attempt to allocate a new one. + */ + if( !pPage ){ + pPage = pcache1AllocPage(pCache, createFlag==1); + } + + if( pPage ){ + unsigned int h = iKey % pCache->nHash; + pCache->nPage++; + pPage->iKey = iKey; + pPage->pNext = pCache->apHash[h]; + pPage->pCache = pCache; + pPage->pLruNext = 0; + /* pPage->pLruPrev = 0; + ** No need to clear pLruPrev since it is not accessed when pLruNext==0 */ + *(void **)pPage->page.pExtra = 0; + pCache->apHash[h] = pPage; + if( iKey>pCache->iMaxKey ){ + pCache->iMaxKey = iKey; + } + } + return pPage; +} + +/* +** Implementation of the sqlite3_pcache.xFetch method. +** +** Fetch a page by key value. +** +** Whether or not a new page may be allocated by this function depends on +** the value of the createFlag argument. 0 means do not allocate a new +** page. 1 means allocate a new page if space is easily available. 2 +** means to try really hard to allocate a new page. +** +** For a non-purgeable cache (a cache used as the storage for an in-memory +** database) there is really no difference between createFlag 1 and 2. So +** the calling function (pcache.c) will never have a createFlag of 1 on +** a non-purgeable cache. +** +** There are three different approaches to obtaining space for a page, +** depending on the value of parameter createFlag (which may be 0, 1 or 2). +** +** 1. Regardless of the value of createFlag, the cache is searched for a +** copy of the requested page. If one is found, it is returned. +** +** 2. If createFlag==0 and the page is not already in the cache, NULL is +** returned. +** +** 3. If createFlag is 1, and the page is not already in the cache, then +** return NULL (do not allocate a new page) if any of the following +** conditions are true: +** +** (a) the number of pages pinned by the cache is greater than +** PCache1.nMax, or +** +** (b) the number of pages pinned by the cache is greater than +** the sum of nMax for all purgeable caches, less the sum of +** nMin for all other purgeable caches, or +** +** 4. If none of the first three conditions apply and the cache is marked +** as purgeable, and if one of the following is true: +** +** (a) The number of pages allocated for the cache is already +** PCache1.nMax, or +** +** (b) The number of pages allocated for all purgeable caches is +** already equal to or greater than the sum of nMax for all +** purgeable caches, +** +** (c) The system is under memory pressure and wants to avoid +** unnecessary pages cache entry allocations +** +** then attempt to recycle a page from the LRU list. If it is the right +** size, return the recycled buffer. Otherwise, free the buffer and +** proceed to step 5. +** +** 5. Otherwise, allocate and return a new page buffer. +** +** There are two versions of this routine. pcache1FetchWithMutex() is +** the general case. pcache1FetchNoMutex() is a faster implementation for +** the common case where pGroup->mutex is NULL. The pcache1Fetch() wrapper +** invokes the appropriate routine. +*/ +static PgHdr1 *pcache1FetchNoMutex( + sqlite3_pcache *p, + unsigned int iKey, + int createFlag +){ + PCache1 *pCache = (PCache1 *)p; + PgHdr1 *pPage = 0; + + /* Step 1: Search the hash table for an existing entry. */ + pPage = pCache->apHash[iKey % pCache->nHash]; + while( pPage && pPage->iKey!=iKey ){ pPage = pPage->pNext; } + + /* Step 2: If the page was found in the hash table, then return it. + ** If the page was not in the hash table and createFlag is 0, abort. + ** Otherwise (page not in hash and createFlag!=0) continue with + ** subsequent steps to try to create the page. */ + if( pPage ){ + if( PAGE_IS_UNPINNED(pPage) ){ + return pcache1PinPage(pPage); + }else{ + return pPage; + } + }else if( createFlag ){ + /* Steps 3, 4, and 5 implemented by this subroutine */ + return pcache1FetchStage2(pCache, iKey, createFlag); + }else{ + return 0; + } +} +#if PCACHE1_MIGHT_USE_GROUP_MUTEX +static PgHdr1 *pcache1FetchWithMutex( + sqlite3_pcache *p, + unsigned int iKey, + int createFlag +){ + PCache1 *pCache = (PCache1 *)p; + PgHdr1 *pPage; + + pcache1EnterMutex(pCache->pGroup); + pPage = pcache1FetchNoMutex(p, iKey, createFlag); + assert( pPage==0 || pCache->iMaxKey>=iKey ); + pcache1LeaveMutex(pCache->pGroup); + return pPage; +} +#endif +static sqlite3_pcache_page *pcache1Fetch( + sqlite3_pcache *p, + unsigned int iKey, + int createFlag +){ +#if PCACHE1_MIGHT_USE_GROUP_MUTEX || defined(SQLITE_DEBUG) + PCache1 *pCache = (PCache1 *)p; +#endif + + assert( offsetof(PgHdr1,page)==0 ); + assert( pCache->bPurgeable || createFlag!=1 ); + assert( pCache->bPurgeable || pCache->nMin==0 ); + assert( pCache->bPurgeable==0 || pCache->nMin==10 ); + assert( pCache->nMin==0 || pCache->bPurgeable ); + assert( pCache->nHash>0 ); +#if PCACHE1_MIGHT_USE_GROUP_MUTEX + if( pCache->pGroup->mutex ){ + return (sqlite3_pcache_page*)pcache1FetchWithMutex(p, iKey, createFlag); + }else +#endif + { + return (sqlite3_pcache_page*)pcache1FetchNoMutex(p, iKey, createFlag); + } +} + + +/* +** Implementation of the sqlite3_pcache.xUnpin method. +** +** Mark a page as unpinned (eligible for asynchronous recycling). +*/ +static void pcache1Unpin( + sqlite3_pcache *p, + sqlite3_pcache_page *pPg, + int reuseUnlikely +){ + PCache1 *pCache = (PCache1 *)p; + PgHdr1 *pPage = (PgHdr1 *)pPg; + PGroup *pGroup = pCache->pGroup; + + assert( pPage->pCache==pCache ); + pcache1EnterMutex(pGroup); + + /* It is an error to call this function if the page is already + ** part of the PGroup LRU list. + */ + assert( pPage->pLruNext==0 ); + assert( PAGE_IS_PINNED(pPage) ); + + if( reuseUnlikely || pGroup->nPurgeable>pGroup->nMaxPage ){ + pcache1RemoveFromHash(pPage, 1); + }else{ + /* Add the page to the PGroup LRU list. */ + PgHdr1 **ppFirst = &pGroup->lru.pLruNext; + pPage->pLruPrev = &pGroup->lru; + (pPage->pLruNext = *ppFirst)->pLruPrev = pPage; + *ppFirst = pPage; + pCache->nRecyclable++; + } + + pcache1LeaveMutex(pCache->pGroup); +} + +/* +** Implementation of the sqlite3_pcache.xRekey method. +*/ +static void pcache1Rekey( + sqlite3_pcache *p, + sqlite3_pcache_page *pPg, + unsigned int iOld, + unsigned int iNew +){ + PCache1 *pCache = (PCache1 *)p; + PgHdr1 *pPage = (PgHdr1 *)pPg; + PgHdr1 **pp; + unsigned int hOld, hNew; + assert( pPage->iKey==iOld ); + assert( pPage->pCache==pCache ); + assert( iOld!=iNew ); /* The page number really is changing */ + + pcache1EnterMutex(pCache->pGroup); + + assert( pcache1FetchNoMutex(p, iOld, 0)==pPage ); /* pPg really is iOld */ + hOld = iOld%pCache->nHash; + pp = &pCache->apHash[hOld]; + while( (*pp)!=pPage ){ + pp = &(*pp)->pNext; + } + *pp = pPage->pNext; + + assert( pcache1FetchNoMutex(p, iNew, 0)==0 ); /* iNew not in cache */ + hNew = iNew%pCache->nHash; + pPage->iKey = iNew; + pPage->pNext = pCache->apHash[hNew]; + pCache->apHash[hNew] = pPage; + if( iNew>pCache->iMaxKey ){ + pCache->iMaxKey = iNew; + } + + pcache1LeaveMutex(pCache->pGroup); +} + +/* +** Implementation of the sqlite3_pcache.xTruncate method. +** +** Discard all unpinned pages in the cache with a page number equal to +** or greater than parameter iLimit. Any pinned pages with a page number +** equal to or greater than iLimit are implicitly unpinned. +*/ +static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){ + PCache1 *pCache = (PCache1 *)p; + pcache1EnterMutex(pCache->pGroup); + if( iLimit<=pCache->iMaxKey ){ + pcache1TruncateUnsafe(pCache, iLimit); + pCache->iMaxKey = iLimit-1; + } + pcache1LeaveMutex(pCache->pGroup); +} + +/* +** Implementation of the sqlite3_pcache.xDestroy method. +** +** Destroy a cache allocated using pcache1Create(). +*/ +static void pcache1Destroy(sqlite3_pcache *p){ + PCache1 *pCache = (PCache1 *)p; + PGroup *pGroup = pCache->pGroup; + assert( pCache->bPurgeable || (pCache->nMax==0 && pCache->nMin==0) ); + pcache1EnterMutex(pGroup); + if( pCache->nPage ) pcache1TruncateUnsafe(pCache, 0); + assert( pGroup->nMaxPage >= pCache->nMax ); + pGroup->nMaxPage -= pCache->nMax; + assert( pGroup->nMinPage >= pCache->nMin ); + pGroup->nMinPage -= pCache->nMin; + pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; + pcache1EnforceMaxPage(pCache); + pcache1LeaveMutex(pGroup); + sqlite3_free(pCache->pBulk); + sqlite3_free(pCache->apHash); + sqlite3_free(pCache); +} + +/* +** This function is called during initialization (sqlite3_initialize()) to +** install the default pluggable cache module, assuming the user has not +** already provided an alternative. +*/ +SQLITE_PRIVATE void sqlite3PCacheSetDefault(void){ + static const sqlite3_pcache_methods2 defaultMethods = { + 1, /* iVersion */ + 0, /* pArg */ + pcache1Init, /* xInit */ + pcache1Shutdown, /* xShutdown */ + pcache1Create, /* xCreate */ + pcache1Cachesize, /* xCachesize */ + pcache1Pagecount, /* xPagecount */ + pcache1Fetch, /* xFetch */ + pcache1Unpin, /* xUnpin */ + pcache1Rekey, /* xRekey */ + pcache1Truncate, /* xTruncate */ + pcache1Destroy, /* xDestroy */ + pcache1Shrink /* xShrink */ + }; + sqlite3_config(SQLITE_CONFIG_PCACHE2, &defaultMethods); +} + +/* +** Return the size of the header on each page of this PCACHE implementation. +*/ +SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void){ return ROUND8(sizeof(PgHdr1)); } + +/* +** Return the global mutex used by this PCACHE implementation. The +** sqlite3_status() routine needs access to this mutex. +*/ +SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void){ + return pcache1.mutex; +} + +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT +/* +** This function is called to free superfluous dynamically allocated memory +** held by the pager system. Memory in use by any SQLite pager allocated +** by the current thread may be sqlite3_free()ed. +** +** nReq is the number of bytes of memory required. Once this much has +** been released, the function returns. The return value is the total number +** of bytes of memory released. +*/ +SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int nReq){ + int nFree = 0; + assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); + assert( sqlite3_mutex_notheld(pcache1.mutex) ); + if( sqlite3GlobalConfig.pPage==0 ){ + PgHdr1 *p; + pcache1EnterMutex(&pcache1.grp); + while( (nReq<0 || nFree<nReq) + && (p=pcache1.grp.lru.pLruPrev)!=0 + && p->isAnchor==0 + ){ + nFree += pcache1MemSize(p->page.pBuf); + assert( PAGE_IS_UNPINNED(p) ); + pcache1PinPage(p); + pcache1RemoveFromHash(p, 1); + } + pcache1LeaveMutex(&pcache1.grp); + } + return nFree; +} +#endif /* SQLITE_ENABLE_MEMORY_MANAGEMENT */ + +#ifdef SQLITE_TEST +/* +** This function is used by test procedures to inspect the internal state +** of the global cache. +*/ +SQLITE_PRIVATE void sqlite3PcacheStats( + int *pnCurrent, /* OUT: Total number of pages cached */ + int *pnMax, /* OUT: Global maximum cache size */ + int *pnMin, /* OUT: Sum of PCache1.nMin for purgeable caches */ + int *pnRecyclable /* OUT: Total number of pages available for recycling */ +){ + PgHdr1 *p; + int nRecyclable = 0; + for(p=pcache1.grp.lru.pLruNext; p && !p->isAnchor; p=p->pLruNext){ + assert( PAGE_IS_UNPINNED(p) ); + nRecyclable++; + } + *pnCurrent = pcache1.grp.nPurgeable; + *pnMax = (int)pcache1.grp.nMaxPage; + *pnMin = (int)pcache1.grp.nMinPage; + *pnRecyclable = nRecyclable; +} +#endif + +/************** End of pcache1.c *********************************************/ +/************** Begin file rowset.c ******************************************/ +/* +** 2008 December 3 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This module implements an object we call a "RowSet". +** +** The RowSet object is a collection of rowids. Rowids +** are inserted into the RowSet in an arbitrary order. Inserts +** can be intermixed with tests to see if a given rowid has been +** previously inserted into the RowSet. +** +** After all inserts are finished, it is possible to extract the +** elements of the RowSet in sorted order. Once this extraction +** process has started, no new elements may be inserted. +** +** Hence, the primitive operations for a RowSet are: +** +** CREATE +** INSERT +** TEST +** SMALLEST +** DESTROY +** +** The CREATE and DESTROY primitives are the constructor and destructor, +** obviously. The INSERT primitive adds a new element to the RowSet. +** TEST checks to see if an element is already in the RowSet. SMALLEST +** extracts the least value from the RowSet. +** +** The INSERT primitive might allocate additional memory. Memory is +** allocated in chunks so most INSERTs do no allocation. There is an +** upper bound on the size of allocated memory. No memory is freed +** until DESTROY. +** +** The TEST primitive includes a "batch" number. The TEST primitive +** will only see elements that were inserted before the last change +** in the batch number. In other words, if an INSERT occurs between +** two TESTs where the TESTs have the same batch number, then the +** value added by the INSERT will not be visible to the second TEST. +** The initial batch number is zero, so if the very first TEST contains +** a non-zero batch number, it will see all prior INSERTs. +** +** No INSERTs may occurs after a SMALLEST. An assertion will fail if +** that is attempted. +** +** The cost of an INSERT is roughly constant. (Sometimes new memory +** has to be allocated on an INSERT.) The cost of a TEST with a new +** batch number is O(NlogN) where N is the number of elements in the RowSet. +** The cost of a TEST using the same batch number is O(logN). The cost +** of the first SMALLEST is O(NlogN). Second and subsequent SMALLEST +** primitives are constant time. The cost of DESTROY is O(N). +** +** TEST and SMALLEST may not be used by the same RowSet. This used to +** be possible, but the feature was not used, so it was removed in order +** to simplify the code. +*/ +/* #include "sqliteInt.h" */ + + +/* +** Target size for allocation chunks. +*/ +#define ROWSET_ALLOCATION_SIZE 1024 + +/* +** The number of rowset entries per allocation chunk. +*/ +#define ROWSET_ENTRY_PER_CHUNK \ + ((ROWSET_ALLOCATION_SIZE-8)/sizeof(struct RowSetEntry)) + +/* +** Each entry in a RowSet is an instance of the following object. +** +** This same object is reused to store a linked list of trees of RowSetEntry +** objects. In that alternative use, pRight points to the next entry +** in the list, pLeft points to the tree, and v is unused. The +** RowSet.pForest value points to the head of this forest list. +*/ +struct RowSetEntry { + i64 v; /* ROWID value for this entry */ + struct RowSetEntry *pRight; /* Right subtree (larger entries) or list */ + struct RowSetEntry *pLeft; /* Left subtree (smaller entries) */ +}; + +/* +** RowSetEntry objects are allocated in large chunks (instances of the +** following structure) to reduce memory allocation overhead. The +** chunks are kept on a linked list so that they can be deallocated +** when the RowSet is destroyed. +*/ +struct RowSetChunk { + struct RowSetChunk *pNextChunk; /* Next chunk on list of them all */ + struct RowSetEntry aEntry[ROWSET_ENTRY_PER_CHUNK]; /* Allocated entries */ +}; + +/* +** A RowSet in an instance of the following structure. +** +** A typedef of this structure if found in sqliteInt.h. +*/ +struct RowSet { + struct RowSetChunk *pChunk; /* List of all chunk allocations */ + sqlite3 *db; /* The database connection */ + struct RowSetEntry *pEntry; /* List of entries using pRight */ + struct RowSetEntry *pLast; /* Last entry on the pEntry list */ + struct RowSetEntry *pFresh; /* Source of new entry objects */ + struct RowSetEntry *pForest; /* List of binary trees of entries */ + u16 nFresh; /* Number of objects on pFresh */ + u16 rsFlags; /* Various flags */ + int iBatch; /* Current insert batch */ +}; + +/* +** Allowed values for RowSet.rsFlags +*/ +#define ROWSET_SORTED 0x01 /* True if RowSet.pEntry is sorted */ +#define ROWSET_NEXT 0x02 /* True if sqlite3RowSetNext() has been called */ + +/* +** Allocate a RowSet object. Return NULL if a memory allocation +** error occurs. +*/ +SQLITE_PRIVATE RowSet *sqlite3RowSetInit(sqlite3 *db){ + RowSet *p = sqlite3DbMallocRawNN(db, sizeof(*p)); + if( p ){ + int N = sqlite3DbMallocSize(db, p); + p->pChunk = 0; + p->db = db; + p->pEntry = 0; + p->pLast = 0; + p->pForest = 0; + p->pFresh = (struct RowSetEntry*)(ROUND8(sizeof(*p)) + (char*)p); + p->nFresh = (u16)((N - ROUND8(sizeof(*p)))/sizeof(struct RowSetEntry)); + p->rsFlags = ROWSET_SORTED; + p->iBatch = 0; + } + return p; +} + +/* +** Deallocate all chunks from a RowSet. This frees all memory that +** the RowSet has allocated over its lifetime. This routine is +** the destructor for the RowSet. +*/ +SQLITE_PRIVATE void sqlite3RowSetClear(void *pArg){ + RowSet *p = (RowSet*)pArg; + struct RowSetChunk *pChunk, *pNextChunk; + for(pChunk=p->pChunk; pChunk; pChunk = pNextChunk){ + pNextChunk = pChunk->pNextChunk; + sqlite3DbFree(p->db, pChunk); + } + p->pChunk = 0; + p->nFresh = 0; + p->pEntry = 0; + p->pLast = 0; + p->pForest = 0; + p->rsFlags = ROWSET_SORTED; +} + +/* +** Deallocate all chunks from a RowSet. This frees all memory that +** the RowSet has allocated over its lifetime. This routine is +** the destructor for the RowSet. +*/ +SQLITE_PRIVATE void sqlite3RowSetDelete(void *pArg){ + sqlite3RowSetClear(pArg); + sqlite3DbFree(((RowSet*)pArg)->db, pArg); +} + +/* +** Allocate a new RowSetEntry object that is associated with the +** given RowSet. Return a pointer to the new and completely uninitialized +** object. +** +** In an OOM situation, the RowSet.db->mallocFailed flag is set and this +** routine returns NULL. +*/ +static struct RowSetEntry *rowSetEntryAlloc(RowSet *p){ + assert( p!=0 ); + if( p->nFresh==0 ){ /*OPTIMIZATION-IF-FALSE*/ + /* We could allocate a fresh RowSetEntry each time one is needed, but it + ** is more efficient to pull a preallocated entry from the pool */ + struct RowSetChunk *pNew; + pNew = sqlite3DbMallocRawNN(p->db, sizeof(*pNew)); + if( pNew==0 ){ + return 0; + } + pNew->pNextChunk = p->pChunk; + p->pChunk = pNew; + p->pFresh = pNew->aEntry; + p->nFresh = ROWSET_ENTRY_PER_CHUNK; + } + p->nFresh--; + return p->pFresh++; +} + +/* +** Insert a new value into a RowSet. +** +** The mallocFailed flag of the database connection is set if a +** memory allocation fails. +*/ +SQLITE_PRIVATE void sqlite3RowSetInsert(RowSet *p, i64 rowid){ + struct RowSetEntry *pEntry; /* The new entry */ + struct RowSetEntry *pLast; /* The last prior entry */ + + /* This routine is never called after sqlite3RowSetNext() */ + assert( p!=0 && (p->rsFlags & ROWSET_NEXT)==0 ); + + pEntry = rowSetEntryAlloc(p); + if( pEntry==0 ) return; + pEntry->v = rowid; + pEntry->pRight = 0; + pLast = p->pLast; + if( pLast ){ + if( rowid<=pLast->v ){ /*OPTIMIZATION-IF-FALSE*/ + /* Avoid unnecessary sorts by preserving the ROWSET_SORTED flags + ** where possible */ + p->rsFlags &= ~ROWSET_SORTED; + } + pLast->pRight = pEntry; + }else{ + p->pEntry = pEntry; + } + p->pLast = pEntry; +} + +/* +** Merge two lists of RowSetEntry objects. Remove duplicates. +** +** The input lists are connected via pRight pointers and are +** assumed to each already be in sorted order. +*/ +static struct RowSetEntry *rowSetEntryMerge( + struct RowSetEntry *pA, /* First sorted list to be merged */ + struct RowSetEntry *pB /* Second sorted list to be merged */ +){ + struct RowSetEntry head; + struct RowSetEntry *pTail; + + pTail = &head; + assert( pA!=0 && pB!=0 ); + for(;;){ + assert( pA->pRight==0 || pA->v<=pA->pRight->v ); + assert( pB->pRight==0 || pB->v<=pB->pRight->v ); + if( pA->v<=pB->v ){ + if( pA->v<pB->v ) pTail = pTail->pRight = pA; + pA = pA->pRight; + if( pA==0 ){ + pTail->pRight = pB; + break; + } + }else{ + pTail = pTail->pRight = pB; + pB = pB->pRight; + if( pB==0 ){ + pTail->pRight = pA; + break; + } + } + } + return head.pRight; +} + +/* +** Sort all elements on the list of RowSetEntry objects into order of +** increasing v. +*/ +static struct RowSetEntry *rowSetEntrySort(struct RowSetEntry *pIn){ + unsigned int i; + struct RowSetEntry *pNext, *aBucket[40]; + + memset(aBucket, 0, sizeof(aBucket)); + while( pIn ){ + pNext = pIn->pRight; + pIn->pRight = 0; + for(i=0; aBucket[i]; i++){ + pIn = rowSetEntryMerge(aBucket[i], pIn); + aBucket[i] = 0; + } + aBucket[i] = pIn; + pIn = pNext; + } + pIn = aBucket[0]; + for(i=1; i<sizeof(aBucket)/sizeof(aBucket[0]); i++){ + if( aBucket[i]==0 ) continue; + pIn = pIn ? rowSetEntryMerge(pIn, aBucket[i]) : aBucket[i]; + } + return pIn; +} + + +/* +** The input, pIn, is a binary tree (or subtree) of RowSetEntry objects. +** Convert this tree into a linked list connected by the pRight pointers +** and return pointers to the first and last elements of the new list. +*/ +static void rowSetTreeToList( + struct RowSetEntry *pIn, /* Root of the input tree */ + struct RowSetEntry **ppFirst, /* Write head of the output list here */ + struct RowSetEntry **ppLast /* Write tail of the output list here */ +){ + assert( pIn!=0 ); + if( pIn->pLeft ){ + struct RowSetEntry *p; + rowSetTreeToList(pIn->pLeft, ppFirst, &p); + p->pRight = pIn; + }else{ + *ppFirst = pIn; + } + if( pIn->pRight ){ + rowSetTreeToList(pIn->pRight, &pIn->pRight, ppLast); + }else{ + *ppLast = pIn; + } + assert( (*ppLast)->pRight==0 ); +} + + +/* +** Convert a sorted list of elements (connected by pRight) into a binary +** tree with depth of iDepth. A depth of 1 means the tree contains a single +** node taken from the head of *ppList. A depth of 2 means a tree with +** three nodes. And so forth. +** +** Use as many entries from the input list as required and update the +** *ppList to point to the unused elements of the list. If the input +** list contains too few elements, then construct an incomplete tree +** and leave *ppList set to NULL. +** +** Return a pointer to the root of the constructed binary tree. +*/ +static struct RowSetEntry *rowSetNDeepTree( + struct RowSetEntry **ppList, + int iDepth +){ + struct RowSetEntry *p; /* Root of the new tree */ + struct RowSetEntry *pLeft; /* Left subtree */ + if( *ppList==0 ){ /*OPTIMIZATION-IF-TRUE*/ + /* Prevent unnecessary deep recursion when we run out of entries */ + return 0; + } + if( iDepth>1 ){ /*OPTIMIZATION-IF-TRUE*/ + /* This branch causes a *balanced* tree to be generated. A valid tree + ** is still generated without this branch, but the tree is wildly + ** unbalanced and inefficient. */ + pLeft = rowSetNDeepTree(ppList, iDepth-1); + p = *ppList; + if( p==0 ){ /*OPTIMIZATION-IF-FALSE*/ + /* It is safe to always return here, but the resulting tree + ** would be unbalanced */ + return pLeft; + } + p->pLeft = pLeft; + *ppList = p->pRight; + p->pRight = rowSetNDeepTree(ppList, iDepth-1); + }else{ + p = *ppList; + *ppList = p->pRight; + p->pLeft = p->pRight = 0; + } + return p; +} + +/* +** Convert a sorted list of elements into a binary tree. Make the tree +** as deep as it needs to be in order to contain the entire list. +*/ +static struct RowSetEntry *rowSetListToTree(struct RowSetEntry *pList){ + int iDepth; /* Depth of the tree so far */ + struct RowSetEntry *p; /* Current tree root */ + struct RowSetEntry *pLeft; /* Left subtree */ + + assert( pList!=0 ); + p = pList; + pList = p->pRight; + p->pLeft = p->pRight = 0; + for(iDepth=1; pList; iDepth++){ + pLeft = p; + p = pList; + pList = p->pRight; + p->pLeft = pLeft; + p->pRight = rowSetNDeepTree(&pList, iDepth); + } + return p; +} + +/* +** Extract the smallest element from the RowSet. +** Write the element into *pRowid. Return 1 on success. Return +** 0 if the RowSet is already empty. +** +** After this routine has been called, the sqlite3RowSetInsert() +** routine may not be called again. +** +** This routine may not be called after sqlite3RowSetTest() has +** been used. Older versions of RowSet allowed that, but as the +** capability was not used by the code generator, it was removed +** for code economy. +*/ +SQLITE_PRIVATE int sqlite3RowSetNext(RowSet *p, i64 *pRowid){ + assert( p!=0 ); + assert( p->pForest==0 ); /* Cannot be used with sqlite3RowSetText() */ + + /* Merge the forest into a single sorted list on first call */ + if( (p->rsFlags & ROWSET_NEXT)==0 ){ /*OPTIMIZATION-IF-FALSE*/ + if( (p->rsFlags & ROWSET_SORTED)==0 ){ /*OPTIMIZATION-IF-FALSE*/ + p->pEntry = rowSetEntrySort(p->pEntry); + } + p->rsFlags |= ROWSET_SORTED|ROWSET_NEXT; + } + + /* Return the next entry on the list */ + if( p->pEntry ){ + *pRowid = p->pEntry->v; + p->pEntry = p->pEntry->pRight; + if( p->pEntry==0 ){ /*OPTIMIZATION-IF-TRUE*/ + /* Free memory immediately, rather than waiting on sqlite3_finalize() */ + sqlite3RowSetClear(p); + } + return 1; + }else{ + return 0; + } +} + +/* +** Check to see if element iRowid was inserted into the rowset as +** part of any insert batch prior to iBatch. Return 1 or 0. +** +** If this is the first test of a new batch and if there exist entries +** on pRowSet->pEntry, then sort those entries into the forest at +** pRowSet->pForest so that they can be tested. +*/ +SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 iRowid){ + struct RowSetEntry *p, *pTree; + + /* This routine is never called after sqlite3RowSetNext() */ + assert( pRowSet!=0 && (pRowSet->rsFlags & ROWSET_NEXT)==0 ); + + /* Sort entries into the forest on the first test of a new batch. + ** To save unnecessary work, only do this when the batch number changes. + */ + if( iBatch!=pRowSet->iBatch ){ /*OPTIMIZATION-IF-FALSE*/ + p = pRowSet->pEntry; + if( p ){ + struct RowSetEntry **ppPrevTree = &pRowSet->pForest; + if( (pRowSet->rsFlags & ROWSET_SORTED)==0 ){ /*OPTIMIZATION-IF-FALSE*/ + /* Only sort the current set of entries if they need it */ + p = rowSetEntrySort(p); + } + for(pTree = pRowSet->pForest; pTree; pTree=pTree->pRight){ + ppPrevTree = &pTree->pRight; + if( pTree->pLeft==0 ){ + pTree->pLeft = rowSetListToTree(p); + break; + }else{ + struct RowSetEntry *pAux, *pTail; + rowSetTreeToList(pTree->pLeft, &pAux, &pTail); + pTree->pLeft = 0; + p = rowSetEntryMerge(pAux, p); + } + } + if( pTree==0 ){ + *ppPrevTree = pTree = rowSetEntryAlloc(pRowSet); + if( pTree ){ + pTree->v = 0; + pTree->pRight = 0; + pTree->pLeft = rowSetListToTree(p); + } + } + pRowSet->pEntry = 0; + pRowSet->pLast = 0; + pRowSet->rsFlags |= ROWSET_SORTED; + } + pRowSet->iBatch = iBatch; + } + + /* Test to see if the iRowid value appears anywhere in the forest. + ** Return 1 if it does and 0 if not. + */ + for(pTree = pRowSet->pForest; pTree; pTree=pTree->pRight){ + p = pTree->pLeft; + while( p ){ + if( p->v<iRowid ){ + p = p->pRight; + }else if( p->v>iRowid ){ + p = p->pLeft; + }else{ + return 1; + } + } + } + return 0; +} + +/************** End of rowset.c **********************************************/ +/************** Begin file pager.c *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the implementation of the page cache subsystem or "pager". +** +** The pager is used to access a database disk file. It implements +** atomic commit and rollback through the use of a journal file that +** is separate from the database file. The pager also implements file +** locking to prevent two processes from writing the same database +** file simultaneously, or one process from reading the database while +** another is writing. +*/ +#ifndef SQLITE_OMIT_DISKIO +/* #include "sqliteInt.h" */ +/************** Include wal.h in the middle of pager.c ***********************/ +/************** Begin file wal.h *********************************************/ +/* +** 2010 February 1 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface to the write-ahead logging +** system. Refer to the comments below and the header comment attached to +** the implementation of each function in log.c for further details. +*/ + +#ifndef SQLITE_WAL_H +#define SQLITE_WAL_H + +/* #include "sqliteInt.h" */ + +/* Macros for extracting appropriate sync flags for either transaction +** commits (WAL_SYNC_FLAGS(X)) or for checkpoint ops (CKPT_SYNC_FLAGS(X)): +*/ +#define WAL_SYNC_FLAGS(X) ((X)&0x03) +#define CKPT_SYNC_FLAGS(X) (((X)>>2)&0x03) + +#ifdef SQLITE_OMIT_WAL +# define sqlite3WalOpen(x,y,z) 0 +# define sqlite3WalLimit(x,y) +# define sqlite3WalClose(v,w,x,y,z) 0 +# define sqlite3WalBeginReadTransaction(y,z) 0 +# define sqlite3WalEndReadTransaction(z) +# define sqlite3WalDbsize(y) 0 +# define sqlite3WalBeginWriteTransaction(y) 0 +# define sqlite3WalEndWriteTransaction(x) 0 +# define sqlite3WalUndo(x,y,z) 0 +# define sqlite3WalSavepoint(y,z) +# define sqlite3WalSavepointUndo(y,z) 0 +# define sqlite3WalFrames(u,v,w,x,y,z) 0 +# define sqlite3WalCheckpoint(q,r,s,t,u,v,w,x,y,z) 0 +# define sqlite3WalCallback(z) 0 +# define sqlite3WalExclusiveMode(y,z) 0 +# define sqlite3WalHeapMemory(z) 0 +# define sqlite3WalFramesize(z) 0 +# define sqlite3WalFindFrame(x,y,z) 0 +# define sqlite3WalFile(x) 0 +# undef SQLITE_USE_SEH +#else + +#define WAL_SAVEPOINT_NDATA 4 + +/* Connection to a write-ahead log (WAL) file. +** There is one object of this type for each pager. +*/ +typedef struct Wal Wal; + +/* Open and close a connection to a write-ahead log. */ +SQLITE_PRIVATE int sqlite3WalOpen(sqlite3_vfs*, sqlite3_file*, const char *, int, i64, Wal**); +SQLITE_PRIVATE int sqlite3WalClose(Wal *pWal, sqlite3*, int sync_flags, int, u8 *); + +/* Set the limiting size of a WAL file. */ +SQLITE_PRIVATE void sqlite3WalLimit(Wal*, i64); + +/* Used by readers to open (lock) and close (unlock) a snapshot. A +** snapshot is like a read-transaction. It is the state of the database +** at an instant in time. sqlite3WalOpenSnapshot gets a read lock and +** preserves the current state even if the other threads or processes +** write to or checkpoint the WAL. sqlite3WalCloseSnapshot() closes the +** transaction and releases the lock. +*/ +SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *); +SQLITE_PRIVATE void sqlite3WalEndReadTransaction(Wal *pWal); + +/* Read a page from the write-ahead log, if it is present. */ +SQLITE_PRIVATE int sqlite3WalFindFrame(Wal *, Pgno, u32 *); +SQLITE_PRIVATE int sqlite3WalReadFrame(Wal *, u32, int, u8 *); + +/* If the WAL is not empty, return the size of the database. */ +SQLITE_PRIVATE Pgno sqlite3WalDbsize(Wal *pWal); + +/* Obtain or release the WRITER lock. */ +SQLITE_PRIVATE int sqlite3WalBeginWriteTransaction(Wal *pWal); +SQLITE_PRIVATE int sqlite3WalEndWriteTransaction(Wal *pWal); + +/* Undo any frames written (but not committed) to the log */ +SQLITE_PRIVATE int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx); + +/* Return an integer that records the current (uncommitted) write +** position in the WAL */ +SQLITE_PRIVATE void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData); + +/* Move the write position of the WAL back to iFrame. Called in +** response to a ROLLBACK TO command. */ +SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData); + +/* Write a frame or frames to the log. */ +SQLITE_PRIVATE int sqlite3WalFrames(Wal *pWal, int, PgHdr *, Pgno, int, int); + +/* Copy pages from the log to the database file */ +SQLITE_PRIVATE int sqlite3WalCheckpoint( + Wal *pWal, /* Write-ahead log connection */ + sqlite3 *db, /* Check this handle's interrupt flag */ + int eMode, /* One of PASSIVE, FULL and RESTART */ + int (*xBusy)(void*), /* Function to call when busy */ + void *pBusyArg, /* Context argument for xBusyHandler */ + int sync_flags, /* Flags to sync db file with (or 0) */ + int nBuf, /* Size of buffer nBuf */ + u8 *zBuf, /* Temporary buffer to use */ + int *pnLog, /* OUT: Number of frames in WAL */ + int *pnCkpt /* OUT: Number of backfilled frames in WAL */ +); + +/* Return the value to pass to a sqlite3_wal_hook callback, the +** number of frames in the WAL at the point of the last commit since +** sqlite3WalCallback() was called. If no commits have occurred since +** the last call, then return 0. +*/ +SQLITE_PRIVATE int sqlite3WalCallback(Wal *pWal); + +/* Tell the wal layer that an EXCLUSIVE lock has been obtained (or released) +** by the pager layer on the database file. +*/ +SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op); + +/* Return true if the argument is non-NULL and the WAL module is using +** heap-memory for the wal-index. Otherwise, if the argument is NULL or the +** WAL module is using shared-memory, return false. +*/ +SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal); + +#ifdef SQLITE_ENABLE_SNAPSHOT +SQLITE_PRIVATE int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot); +SQLITE_PRIVATE void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot); +SQLITE_PRIVATE int sqlite3WalSnapshotRecover(Wal *pWal); +SQLITE_PRIVATE int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot); +SQLITE_PRIVATE void sqlite3WalSnapshotUnlock(Wal *pWal); +#endif + +#ifdef SQLITE_ENABLE_ZIPVFS +/* If the WAL file is not empty, return the number of bytes of content +** stored in each frame (i.e. the db page-size when the WAL was created). +*/ +SQLITE_PRIVATE int sqlite3WalFramesize(Wal *pWal); +#endif + +/* Return the sqlite3_file object for the WAL file */ +SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal); + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +SQLITE_PRIVATE int sqlite3WalWriteLock(Wal *pWal, int bLock); +SQLITE_PRIVATE void sqlite3WalDb(Wal *pWal, sqlite3 *db); +#endif + +#ifdef SQLITE_USE_SEH +SQLITE_PRIVATE int sqlite3WalSystemErrno(Wal*); +#endif + +#endif /* ifndef SQLITE_OMIT_WAL */ +#endif /* SQLITE_WAL_H */ + +/************** End of wal.h *************************************************/ +/************** Continuing where we left off in pager.c **********************/ + + +/******************* NOTES ON THE DESIGN OF THE PAGER ************************ +** +** This comment block describes invariants that hold when using a rollback +** journal. These invariants do not apply for journal_mode=WAL, +** journal_mode=MEMORY, or journal_mode=OFF. +** +** Within this comment block, a page is deemed to have been synced +** automatically as soon as it is written when PRAGMA synchronous=OFF. +** Otherwise, the page is not synced until the xSync method of the VFS +** is called successfully on the file containing the page. +** +** Definition: A page of the database file is said to be "overwriteable" if +** one or more of the following are true about the page: +** +** (a) The original content of the page as it was at the beginning of +** the transaction has been written into the rollback journal and +** synced. +** +** (b) The page was a freelist leaf page at the start of the transaction. +** +** (c) The page number is greater than the largest page that existed in +** the database file at the start of the transaction. +** +** (1) A page of the database file is never overwritten unless one of the +** following are true: +** +** (a) The page and all other pages on the same sector are overwriteable. +** +** (b) The atomic page write optimization is enabled, and the entire +** transaction other than the update of the transaction sequence +** number consists of a single page change. +** +** (2) The content of a page written into the rollback journal exactly matches +** both the content in the database when the rollback journal was written +** and the content in the database at the beginning of the current +** transaction. +** +** (3) Writes to the database file are an integer multiple of the page size +** in length and are aligned on a page boundary. +** +** (4) Reads from the database file are either aligned on a page boundary and +** an integer multiple of the page size in length or are taken from the +** first 100 bytes of the database file. +** +** (5) All writes to the database file are synced prior to the rollback journal +** being deleted, truncated, or zeroed. +** +** (6) If a super-journal file is used, then all writes to the database file +** are synced prior to the super-journal being deleted. +** +** Definition: Two databases (or the same database at two points it time) +** are said to be "logically equivalent" if they give the same answer to +** all queries. Note in particular the content of freelist leaf +** pages can be changed arbitrarily without affecting the logical equivalence +** of the database. +** +** (7) At any time, if any subset, including the empty set and the total set, +** of the unsynced changes to a rollback journal are removed and the +** journal is rolled back, the resulting database file will be logically +** equivalent to the database file at the beginning of the transaction. +** +** (8) When a transaction is rolled back, the xTruncate method of the VFS +** is called to restore the database file to the same size it was at +** the beginning of the transaction. (In some VFSes, the xTruncate +** method is a no-op, but that does not change the fact the SQLite will +** invoke it.) +** +** (9) Whenever the database file is modified, at least one bit in the range +** of bytes from 24 through 39 inclusive will be changed prior to releasing +** the EXCLUSIVE lock, thus signaling other connections on the same +** database to flush their caches. +** +** (10) The pattern of bits in bytes 24 through 39 shall not repeat in less +** than one billion transactions. +** +** (11) A database file is well-formed at the beginning and at the conclusion +** of every transaction. +** +** (12) An EXCLUSIVE lock is held on the database file when writing to +** the database file. +** +** (13) A SHARED lock is held on the database file while reading any +** content out of the database file. +** +******************************************************************************/ + +/* +** Macros for troubleshooting. Normally turned off +*/ +#if 0 +int sqlite3PagerTrace=1; /* True to enable tracing */ +#define sqlite3DebugPrintf printf +#define PAGERTRACE(X) if( sqlite3PagerTrace ){ sqlite3DebugPrintf X; } +#else +#define PAGERTRACE(X) +#endif + +/* +** The following two macros are used within the PAGERTRACE() macros above +** to print out file-descriptors. +** +** PAGERID() takes a pointer to a Pager struct as its argument. The +** associated file-descriptor is returned. FILEHANDLEID() takes an sqlite3_file +** struct as its argument. +*/ +#define PAGERID(p) (SQLITE_PTR_TO_INT(p->fd)) +#define FILEHANDLEID(fd) (SQLITE_PTR_TO_INT(fd)) + +/* +** The Pager.eState variable stores the current 'state' of a pager. A +** pager may be in any one of the seven states shown in the following +** state diagram. +** +** OPEN <------+------+ +** | | | +** V | | +** +---------> READER-------+ | +** | | | +** | V | +** |<-------WRITER_LOCKED------> ERROR +** | | ^ +** | V | +** |<------WRITER_CACHEMOD-------->| +** | | | +** | V | +** |<-------WRITER_DBMOD---------->| +** | | | +** | V | +** +<------WRITER_FINISHED-------->+ +** +** +** List of state transitions and the C [function] that performs each: +** +** OPEN -> READER [sqlite3PagerSharedLock] +** READER -> OPEN [pager_unlock] +** +** READER -> WRITER_LOCKED [sqlite3PagerBegin] +** WRITER_LOCKED -> WRITER_CACHEMOD [pager_open_journal] +** WRITER_CACHEMOD -> WRITER_DBMOD [syncJournal] +** WRITER_DBMOD -> WRITER_FINISHED [sqlite3PagerCommitPhaseOne] +** WRITER_*** -> READER [pager_end_transaction] +** +** WRITER_*** -> ERROR [pager_error] +** ERROR -> OPEN [pager_unlock] +** +** +** OPEN: +** +** The pager starts up in this state. Nothing is guaranteed in this +** state - the file may or may not be locked and the database size is +** unknown. The database may not be read or written. +** +** * No read or write transaction is active. +** * Any lock, or no lock at all, may be held on the database file. +** * The dbSize, dbOrigSize and dbFileSize variables may not be trusted. +** +** READER: +** +** In this state all the requirements for reading the database in +** rollback (non-WAL) mode are met. Unless the pager is (or recently +** was) in exclusive-locking mode, a user-level read transaction is +** open. The database size is known in this state. +** +** A connection running with locking_mode=normal enters this state when +** it opens a read-transaction on the database and returns to state +** OPEN after the read-transaction is completed. However a connection +** running in locking_mode=exclusive (including temp databases) remains in +** this state even after the read-transaction is closed. The only way +** a locking_mode=exclusive connection can transition from READER to OPEN +** is via the ERROR state (see below). +** +** * A read transaction may be active (but a write-transaction cannot). +** * A SHARED or greater lock is held on the database file. +** * The dbSize variable may be trusted (even if a user-level read +** transaction is not active). The dbOrigSize and dbFileSize variables +** may not be trusted at this point. +** * If the database is a WAL database, then the WAL connection is open. +** * Even if a read-transaction is not open, it is guaranteed that +** there is no hot-journal in the file-system. +** +** WRITER_LOCKED: +** +** The pager moves to this state from READER when a write-transaction +** is first opened on the database. In WRITER_LOCKED state, all locks +** required to start a write-transaction are held, but no actual +** modifications to the cache or database have taken place. +** +** In rollback mode, a RESERVED or (if the transaction was opened with +** BEGIN EXCLUSIVE) EXCLUSIVE lock is obtained on the database file when +** moving to this state, but the journal file is not written to or opened +** to in this state. If the transaction is committed or rolled back while +** in WRITER_LOCKED state, all that is required is to unlock the database +** file. +** +** IN WAL mode, WalBeginWriteTransaction() is called to lock the log file. +** If the connection is running with locking_mode=exclusive, an attempt +** is made to obtain an EXCLUSIVE lock on the database file. +** +** * A write transaction is active. +** * If the connection is open in rollback-mode, a RESERVED or greater +** lock is held on the database file. +** * If the connection is open in WAL-mode, a WAL write transaction +** is open (i.e. sqlite3WalBeginWriteTransaction() has been successfully +** called). +** * The dbSize, dbOrigSize and dbFileSize variables are all valid. +** * The contents of the pager cache have not been modified. +** * The journal file may or may not be open. +** * Nothing (not even the first header) has been written to the journal. +** +** WRITER_CACHEMOD: +** +** A pager moves from WRITER_LOCKED state to this state when a page is +** first modified by the upper layer. In rollback mode the journal file +** is opened (if it is not already open) and a header written to the +** start of it. The database file on disk has not been modified. +** +** * A write transaction is active. +** * A RESERVED or greater lock is held on the database file. +** * The journal file is open and the first header has been written +** to it, but the header has not been synced to disk. +** * The contents of the page cache have been modified. +** +** WRITER_DBMOD: +** +** The pager transitions from WRITER_CACHEMOD into WRITER_DBMOD state +** when it modifies the contents of the database file. WAL connections +** never enter this state (since they do not modify the database file, +** just the log file). +** +** * A write transaction is active. +** * An EXCLUSIVE or greater lock is held on the database file. +** * The journal file is open and the first header has been written +** and synced to disk. +** * The contents of the page cache have been modified (and possibly +** written to disk). +** +** WRITER_FINISHED: +** +** It is not possible for a WAL connection to enter this state. +** +** A rollback-mode pager changes to WRITER_FINISHED state from WRITER_DBMOD +** state after the entire transaction has been successfully written into the +** database file. In this state the transaction may be committed simply +** by finalizing the journal file. Once in WRITER_FINISHED state, it is +** not possible to modify the database further. At this point, the upper +** layer must either commit or rollback the transaction. +** +** * A write transaction is active. +** * An EXCLUSIVE or greater lock is held on the database file. +** * All writing and syncing of journal and database data has finished. +** If no error occurred, all that remains is to finalize the journal to +** commit the transaction. If an error did occur, the caller will need +** to rollback the transaction. +** +** ERROR: +** +** The ERROR state is entered when an IO or disk-full error (including +** SQLITE_IOERR_NOMEM) occurs at a point in the code that makes it +** difficult to be sure that the in-memory pager state (cache contents, +** db size etc.) are consistent with the contents of the file-system. +** +** Temporary pager files may enter the ERROR state, but in-memory pagers +** cannot. +** +** For example, if an IO error occurs while performing a rollback, +** the contents of the page-cache may be left in an inconsistent state. +** At this point it would be dangerous to change back to READER state +** (as usually happens after a rollback). Any subsequent readers might +** report database corruption (due to the inconsistent cache), and if +** they upgrade to writers, they may inadvertently corrupt the database +** file. To avoid this hazard, the pager switches into the ERROR state +** instead of READER following such an error. +** +** Once it has entered the ERROR state, any attempt to use the pager +** to read or write data returns an error. Eventually, once all +** outstanding transactions have been abandoned, the pager is able to +** transition back to OPEN state, discarding the contents of the +** page-cache and any other in-memory state at the same time. Everything +** is reloaded from disk (and, if necessary, hot-journal rollback performed) +** when a read-transaction is next opened on the pager (transitioning +** the pager into READER state). At that point the system has recovered +** from the error. +** +** Specifically, the pager jumps into the ERROR state if: +** +** 1. An error occurs while attempting a rollback. This happens in +** function sqlite3PagerRollback(). +** +** 2. An error occurs while attempting to finalize a journal file +** following a commit in function sqlite3PagerCommitPhaseTwo(). +** +** 3. An error occurs while attempting to write to the journal or +** database file in function pagerStress() in order to free up +** memory. +** +** In other cases, the error is returned to the b-tree layer. The b-tree +** layer then attempts a rollback operation. If the error condition +** persists, the pager enters the ERROR state via condition (1) above. +** +** Condition (3) is necessary because it can be triggered by a read-only +** statement executed within a transaction. In this case, if the error +** code were simply returned to the user, the b-tree layer would not +** automatically attempt a rollback, as it assumes that an error in a +** read-only statement cannot leave the pager in an internally inconsistent +** state. +** +** * The Pager.errCode variable is set to something other than SQLITE_OK. +** * There are one or more outstanding references to pages (after the +** last reference is dropped the pager should move back to OPEN state). +** * The pager is not an in-memory pager. +** +** +** Notes: +** +** * A pager is never in WRITER_DBMOD or WRITER_FINISHED state if the +** connection is open in WAL mode. A WAL connection is always in one +** of the first four states. +** +** * Normally, a connection open in exclusive mode is never in PAGER_OPEN +** state. There are two exceptions: immediately after exclusive-mode has +** been turned on (and before any read or write transactions are +** executed), and when the pager is leaving the "error state". +** +** * See also: assert_pager_state(). +*/ +#define PAGER_OPEN 0 +#define PAGER_READER 1 +#define PAGER_WRITER_LOCKED 2 +#define PAGER_WRITER_CACHEMOD 3 +#define PAGER_WRITER_DBMOD 4 +#define PAGER_WRITER_FINISHED 5 +#define PAGER_ERROR 6 + +/* +** The Pager.eLock variable is almost always set to one of the +** following locking-states, according to the lock currently held on +** the database file: NO_LOCK, SHARED_LOCK, RESERVED_LOCK or EXCLUSIVE_LOCK. +** This variable is kept up to date as locks are taken and released by +** the pagerLockDb() and pagerUnlockDb() wrappers. +** +** If the VFS xLock() or xUnlock() returns an error other than SQLITE_BUSY +** (i.e. one of the SQLITE_IOERR subtypes), it is not clear whether or not +** the operation was successful. In these circumstances pagerLockDb() and +** pagerUnlockDb() take a conservative approach - eLock is always updated +** when unlocking the file, and only updated when locking the file if the +** VFS call is successful. This way, the Pager.eLock variable may be set +** to a less exclusive (lower) value than the lock that is actually held +** at the system level, but it is never set to a more exclusive value. +** +** This is usually safe. If an xUnlock fails or appears to fail, there may +** be a few redundant xLock() calls or a lock may be held for longer than +** required, but nothing really goes wrong. +** +** The exception is when the database file is unlocked as the pager moves +** from ERROR to OPEN state. At this point there may be a hot-journal file +** in the file-system that needs to be rolled back (as part of an OPEN->SHARED +** transition, by the same pager or any other). If the call to xUnlock() +** fails at this point and the pager is left holding an EXCLUSIVE lock, this +** can confuse the call to xCheckReservedLock() call made later as part +** of hot-journal detection. +** +** xCheckReservedLock() is defined as returning true "if there is a RESERVED +** lock held by this process or any others". So xCheckReservedLock may +** return true because the caller itself is holding an EXCLUSIVE lock (but +** doesn't know it because of a previous error in xUnlock). If this happens +** a hot-journal may be mistaken for a journal being created by an active +** transaction in another process, causing SQLite to read from the database +** without rolling it back. +** +** To work around this, if a call to xUnlock() fails when unlocking the +** database in the ERROR state, Pager.eLock is set to UNKNOWN_LOCK. It +** is only changed back to a real locking state after a successful call +** to xLock(EXCLUSIVE). Also, the code to do the OPEN->SHARED state transition +** omits the check for a hot-journal if Pager.eLock is set to UNKNOWN_LOCK +** lock. Instead, it assumes a hot-journal exists and obtains an EXCLUSIVE +** lock on the database file before attempting to roll it back. See function +** PagerSharedLock() for more detail. +** +** Pager.eLock may only be set to UNKNOWN_LOCK when the pager is in +** PAGER_OPEN state. +*/ +#define UNKNOWN_LOCK (EXCLUSIVE_LOCK+1) + +/* +** The maximum allowed sector size. 64KiB. If the xSectorsize() method +** returns a value larger than this, then MAX_SECTOR_SIZE is used instead. +** This could conceivably cause corruption following a power failure on +** such a system. This is currently an undocumented limit. +*/ +#define MAX_SECTOR_SIZE 0x10000 + + +/* +** An instance of the following structure is allocated for each active +** savepoint and statement transaction in the system. All such structures +** are stored in the Pager.aSavepoint[] array, which is allocated and +** resized using sqlite3Realloc(). +** +** When a savepoint is created, the PagerSavepoint.iHdrOffset field is +** set to 0. If a journal-header is written into the main journal while +** the savepoint is active, then iHdrOffset is set to the byte offset +** immediately following the last journal record written into the main +** journal before the journal-header. This is required during savepoint +** rollback (see pagerPlaybackSavepoint()). +*/ +typedef struct PagerSavepoint PagerSavepoint; +struct PagerSavepoint { + i64 iOffset; /* Starting offset in main journal */ + i64 iHdrOffset; /* See above */ + Bitvec *pInSavepoint; /* Set of pages in this savepoint */ + Pgno nOrig; /* Original number of pages in file */ + Pgno iSubRec; /* Index of first record in sub-journal */ + int bTruncateOnRelease; /* If stmt journal may be truncated on RELEASE */ +#ifndef SQLITE_OMIT_WAL + u32 aWalData[WAL_SAVEPOINT_NDATA]; /* WAL savepoint context */ +#endif +}; + +/* +** Bits of the Pager.doNotSpill flag. See further description below. +*/ +#define SPILLFLAG_OFF 0x01 /* Never spill cache. Set via pragma */ +#define SPILLFLAG_ROLLBACK 0x02 /* Current rolling back, so do not spill */ +#define SPILLFLAG_NOSYNC 0x04 /* Spill is ok, but do not sync */ + +/* +** An open page cache is an instance of struct Pager. A description of +** some of the more important member variables follows: +** +** eState +** +** The current 'state' of the pager object. See the comment and state +** diagram above for a description of the pager state. +** +** eLock +** +** For a real on-disk database, the current lock held on the database file - +** NO_LOCK, SHARED_LOCK, RESERVED_LOCK or EXCLUSIVE_LOCK. +** +** For a temporary or in-memory database (neither of which require any +** locks), this variable is always set to EXCLUSIVE_LOCK. Since such +** databases always have Pager.exclusiveMode==1, this tricks the pager +** logic into thinking that it already has all the locks it will ever +** need (and no reason to release them). +** +** In some (obscure) circumstances, this variable may also be set to +** UNKNOWN_LOCK. See the comment above the #define of UNKNOWN_LOCK for +** details. +** +** changeCountDone +** +** This boolean variable is used to make sure that the change-counter +** (the 4-byte header field at byte offset 24 of the database file) is +** not updated more often than necessary. +** +** It is set to true when the change-counter field is updated, which +** can only happen if an exclusive lock is held on the database file. +** It is cleared (set to false) whenever an exclusive lock is +** relinquished on the database file. Each time a transaction is committed, +** The changeCountDone flag is inspected. If it is true, the work of +** updating the change-counter is omitted for the current transaction. +** +** This mechanism means that when running in exclusive mode, a connection +** need only update the change-counter once, for the first transaction +** committed. +** +** setSuper +** +** When PagerCommitPhaseOne() is called to commit a transaction, it may +** (or may not) specify a super-journal name to be written into the +** journal file before it is synced to disk. +** +** Whether or not a journal file contains a super-journal pointer affects +** the way in which the journal file is finalized after the transaction is +** committed or rolled back when running in "journal_mode=PERSIST" mode. +** If a journal file does not contain a super-journal pointer, it is +** finalized by overwriting the first journal header with zeroes. If +** it does contain a super-journal pointer the journal file is finalized +** by truncating it to zero bytes, just as if the connection were +** running in "journal_mode=truncate" mode. +** +** Journal files that contain super-journal pointers cannot be finalized +** simply by overwriting the first journal-header with zeroes, as the +** super-journal pointer could interfere with hot-journal rollback of any +** subsequently interrupted transaction that reuses the journal file. +** +** The flag is cleared as soon as the journal file is finalized (either +** by PagerCommitPhaseTwo or PagerRollback). If an IO error prevents the +** journal file from being successfully finalized, the setSuper flag +** is cleared anyway (and the pager will move to ERROR state). +** +** doNotSpill +** +** This variables control the behavior of cache-spills (calls made by +** the pcache module to the pagerStress() routine to write cached data +** to the file-system in order to free up memory). +** +** When bits SPILLFLAG_OFF or SPILLFLAG_ROLLBACK of doNotSpill are set, +** writing to the database from pagerStress() is disabled altogether. +** The SPILLFLAG_ROLLBACK case is done in a very obscure case that +** comes up during savepoint rollback that requires the pcache module +** to allocate a new page to prevent the journal file from being written +** while it is being traversed by code in pager_playback(). The SPILLFLAG_OFF +** case is a user preference. +** +** If the SPILLFLAG_NOSYNC bit is set, writing to the database from +** pagerStress() is permitted, but syncing the journal file is not. +** This flag is set by sqlite3PagerWrite() when the file-system sector-size +** is larger than the database page-size in order to prevent a journal sync +** from happening in between the journalling of two pages on the same sector. +** +** subjInMemory +** +** This is a boolean variable. If true, then any required sub-journal +** is opened as an in-memory journal file. If false, then in-memory +** sub-journals are only used for in-memory pager files. +** +** This variable is updated by the upper layer each time a new +** write-transaction is opened. +** +** dbSize, dbOrigSize, dbFileSize +** +** Variable dbSize is set to the number of pages in the database file. +** It is valid in PAGER_READER and higher states (all states except for +** OPEN and ERROR). +** +** dbSize is set based on the size of the database file, which may be +** larger than the size of the database (the value stored at offset +** 28 of the database header by the btree). If the size of the file +** is not an integer multiple of the page-size, the value stored in +** dbSize is rounded down (i.e. a 5KB file with 2K page-size has dbSize==2). +** Except, any file that is greater than 0 bytes in size is considered +** to have at least one page. (i.e. a 1KB file with 2K page-size leads +** to dbSize==1). +** +** During a write-transaction, if pages with page-numbers greater than +** dbSize are modified in the cache, dbSize is updated accordingly. +** Similarly, if the database is truncated using PagerTruncateImage(), +** dbSize is updated. +** +** Variables dbOrigSize and dbFileSize are valid in states +** PAGER_WRITER_LOCKED and higher. dbOrigSize is a copy of the dbSize +** variable at the start of the transaction. It is used during rollback, +** and to determine whether or not pages need to be journalled before +** being modified. +** +** Throughout a write-transaction, dbFileSize contains the size of +** the file on disk in pages. It is set to a copy of dbSize when the +** write-transaction is first opened, and updated when VFS calls are made +** to write or truncate the database file on disk. +** +** The only reason the dbFileSize variable is required is to suppress +** unnecessary calls to xTruncate() after committing a transaction. If, +** when a transaction is committed, the dbFileSize variable indicates +** that the database file is larger than the database image (Pager.dbSize), +** pager_truncate() is called. The pager_truncate() call uses xFilesize() +** to measure the database file on disk, and then truncates it if required. +** dbFileSize is not used when rolling back a transaction. In this case +** pager_truncate() is called unconditionally (which means there may be +** a call to xFilesize() that is not strictly required). In either case, +** pager_truncate() may cause the file to become smaller or larger. +** +** dbHintSize +** +** The dbHintSize variable is used to limit the number of calls made to +** the VFS xFileControl(FCNTL_SIZE_HINT) method. +** +** dbHintSize is set to a copy of the dbSize variable when a +** write-transaction is opened (at the same time as dbFileSize and +** dbOrigSize). If the xFileControl(FCNTL_SIZE_HINT) method is called, +** dbHintSize is increased to the number of pages that correspond to the +** size-hint passed to the method call. See pager_write_pagelist() for +** details. +** +** errCode +** +** The Pager.errCode variable is only ever used in PAGER_ERROR state. It +** is set to zero in all other states. In PAGER_ERROR state, Pager.errCode +** is always set to SQLITE_FULL, SQLITE_IOERR or one of the SQLITE_IOERR_XXX +** sub-codes. +** +** syncFlags, walSyncFlags +** +** syncFlags is either SQLITE_SYNC_NORMAL (0x02) or SQLITE_SYNC_FULL (0x03). +** syncFlags is used for rollback mode. walSyncFlags is used for WAL mode +** and contains the flags used to sync the checkpoint operations in the +** lower two bits, and sync flags used for transaction commits in the WAL +** file in bits 0x04 and 0x08. In other words, to get the correct sync flags +** for checkpoint operations, use (walSyncFlags&0x03) and to get the correct +** sync flags for transaction commit, use ((walSyncFlags>>2)&0x03). Note +** that with synchronous=NORMAL in WAL mode, transaction commit is not synced +** meaning that the 0x04 and 0x08 bits are both zero. +*/ +struct Pager { + sqlite3_vfs *pVfs; /* OS functions to use for IO */ + u8 exclusiveMode; /* Boolean. True if locking_mode==EXCLUSIVE */ + u8 journalMode; /* One of the PAGER_JOURNALMODE_* values */ + u8 useJournal; /* Use a rollback journal on this file */ + u8 noSync; /* Do not sync the journal if true */ + u8 fullSync; /* Do extra syncs of the journal for robustness */ + u8 extraSync; /* sync directory after journal delete */ + u8 syncFlags; /* SYNC_NORMAL or SYNC_FULL otherwise */ + u8 walSyncFlags; /* See description above */ + u8 tempFile; /* zFilename is a temporary or immutable file */ + u8 noLock; /* Do not lock (except in WAL mode) */ + u8 readOnly; /* True for a read-only database */ + u8 memDb; /* True to inhibit all file I/O */ + u8 memVfs; /* VFS-implemented memory database */ + + /************************************************************************** + ** The following block contains those class members that change during + ** routine operation. Class members not in this block are either fixed + ** when the pager is first created or else only change when there is a + ** significant mode change (such as changing the page_size, locking_mode, + ** or the journal_mode). From another view, these class members describe + ** the "state" of the pager, while other class members describe the + ** "configuration" of the pager. + */ + u8 eState; /* Pager state (OPEN, READER, WRITER_LOCKED..) */ + u8 eLock; /* Current lock held on database file */ + u8 changeCountDone; /* Set after incrementing the change-counter */ + u8 setSuper; /* Super-jrnl name is written into jrnl */ + u8 doNotSpill; /* Do not spill the cache when non-zero */ + u8 subjInMemory; /* True to use in-memory sub-journals */ + u8 bUseFetch; /* True to use xFetch() */ + u8 hasHeldSharedLock; /* True if a shared lock has ever been held */ + Pgno dbSize; /* Number of pages in the database */ + Pgno dbOrigSize; /* dbSize before the current transaction */ + Pgno dbFileSize; /* Number of pages in the database file */ + Pgno dbHintSize; /* Value passed to FCNTL_SIZE_HINT call */ + int errCode; /* One of several kinds of errors */ + int nRec; /* Pages journalled since last j-header written */ + u32 cksumInit; /* Quasi-random value added to every checksum */ + u32 nSubRec; /* Number of records written to sub-journal */ + Bitvec *pInJournal; /* One bit for each page in the database file */ + sqlite3_file *fd; /* File descriptor for database */ + sqlite3_file *jfd; /* File descriptor for main journal */ + sqlite3_file *sjfd; /* File descriptor for sub-journal */ + i64 journalOff; /* Current write offset in the journal file */ + i64 journalHdr; /* Byte offset to previous journal header */ + sqlite3_backup *pBackup; /* Pointer to list of ongoing backup processes */ + PagerSavepoint *aSavepoint; /* Array of active savepoints */ + int nSavepoint; /* Number of elements in aSavepoint[] */ + u32 iDataVersion; /* Changes whenever database content changes */ + char dbFileVers[16]; /* Changes whenever database file changes */ + + int nMmapOut; /* Number of mmap pages currently outstanding */ + sqlite3_int64 szMmap; /* Desired maximum mmap size */ + PgHdr *pMmapFreelist; /* List of free mmap page headers (pDirty) */ + /* + ** End of the routinely-changing class members + ***************************************************************************/ + + u16 nExtra; /* Add this many bytes to each in-memory page */ + i16 nReserve; /* Number of unused bytes at end of each page */ + u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */ + u32 sectorSize; /* Assumed sector size during rollback */ + Pgno mxPgno; /* Maximum allowed size of the database */ + Pgno lckPgno; /* Page number for the locking page */ + i64 pageSize; /* Number of bytes in a page */ + i64 journalSizeLimit; /* Size limit for persistent journal files */ + char *zFilename; /* Name of the database file */ + char *zJournal; /* Name of the journal file */ + int (*xBusyHandler)(void*); /* Function to call when busy */ + void *pBusyHandlerArg; /* Context argument for xBusyHandler */ + int aStat[4]; /* Total cache hits, misses, writes, spills */ +#ifdef SQLITE_TEST + int nRead; /* Database pages read */ +#endif + void (*xReiniter)(DbPage*); /* Call this routine when reloading pages */ + int (*xGet)(Pager*,Pgno,DbPage**,int); /* Routine to fetch a patch */ + char *pTmpSpace; /* Pager.pageSize bytes of space for tmp use */ + PCache *pPCache; /* Pointer to page cache object */ +#ifndef SQLITE_OMIT_WAL + Wal *pWal; /* Write-ahead log used by "journal_mode=wal" */ + char *zWal; /* File name for write-ahead log */ +#endif +}; + +/* +** Indexes for use with Pager.aStat[]. The Pager.aStat[] array contains +** the values accessed by passing SQLITE_DBSTATUS_CACHE_HIT, CACHE_MISS +** or CACHE_WRITE to sqlite3_db_status(). +*/ +#define PAGER_STAT_HIT 0 +#define PAGER_STAT_MISS 1 +#define PAGER_STAT_WRITE 2 +#define PAGER_STAT_SPILL 3 + +/* +** The following global variables hold counters used for +** testing purposes only. These variables do not exist in +** a non-testing build. These variables are not thread-safe. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_pager_readdb_count = 0; /* Number of full pages read from DB */ +SQLITE_API int sqlite3_pager_writedb_count = 0; /* Number of full pages written to DB */ +SQLITE_API int sqlite3_pager_writej_count = 0; /* Number of pages written to journal */ +# define PAGER_INCR(v) v++ +#else +# define PAGER_INCR(v) +#endif + + + +/* +** Journal files begin with the following magic string. The data +** was obtained from /dev/random. It is used only as a sanity check. +** +** Since version 2.8.0, the journal format contains additional sanity +** checking information. If the power fails while the journal is being +** written, semi-random garbage data might appear in the journal +** file after power is restored. If an attempt is then made +** to roll the journal back, the database could be corrupted. The additional +** sanity checking data is an attempt to discover the garbage in the +** journal and ignore it. +** +** The sanity checking information for the new journal format consists +** of a 32-bit checksum on each page of data. The checksum covers both +** the page number and the pPager->pageSize bytes of data for the page. +** This cksum is initialized to a 32-bit random value that appears in the +** journal file right after the header. The random initializer is important, +** because garbage data that appears at the end of a journal is likely +** data that was once in other files that have now been deleted. If the +** garbage data came from an obsolete journal file, the checksums might +** be correct. But by initializing the checksum to random value which +** is different for every journal, we minimize that risk. +*/ +static const unsigned char aJournalMagic[] = { + 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd7, +}; + +/* +** The size of the of each page record in the journal is given by +** the following macro. +*/ +#define JOURNAL_PG_SZ(pPager) ((pPager->pageSize) + 8) + +/* +** The journal header size for this pager. This is usually the same +** size as a single disk sector. See also setSectorSize(). +*/ +#define JOURNAL_HDR_SZ(pPager) (pPager->sectorSize) + +/* +** The macro MEMDB is true if we are dealing with an in-memory database. +** We do this as a macro so that if the SQLITE_OMIT_MEMORYDB macro is set, +** the value of MEMDB will be a constant and the compiler will optimize +** out code that would never execute. +*/ +#ifdef SQLITE_OMIT_MEMORYDB +# define MEMDB 0 +#else +# define MEMDB pPager->memDb +#endif + +/* +** The macro USEFETCH is true if we are allowed to use the xFetch and xUnfetch +** interfaces to access the database using memory-mapped I/O. +*/ +#if SQLITE_MAX_MMAP_SIZE>0 +# define USEFETCH(x) ((x)->bUseFetch) +#else +# define USEFETCH(x) 0 +#endif + +/* +** The argument to this macro is a file descriptor (type sqlite3_file*). +** Return 0 if it is not open, or non-zero (but not 1) if it is. +** +** This is so that expressions can be written as: +** +** if( isOpen(pPager->jfd) ){ ... +** +** instead of +** +** if( pPager->jfd->pMethods ){ ... +*/ +#define isOpen(pFd) ((pFd)->pMethods!=0) + +#ifdef SQLITE_DIRECT_OVERFLOW_READ +/* +** Return true if page pgno can be read directly from the database file +** by the b-tree layer. This is the case if: +** +** * the database file is open, +** * there are no dirty pages in the cache, and +** * the desired page is not currently in the wal file. +*/ +SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ + if( pPager->fd->pMethods==0 ) return 0; + if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0; +#ifndef SQLITE_OMIT_WAL + if( pPager->pWal ){ + u32 iRead = 0; + int rc; + rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); + return (rc==SQLITE_OK && iRead==0); + } +#endif + return 1; +} +#endif + +#ifndef SQLITE_OMIT_WAL +# define pagerUseWal(x) ((x)->pWal!=0) +#else +# define pagerUseWal(x) 0 +# define pagerRollbackWal(x) 0 +# define pagerWalFrames(v,w,x,y) 0 +# define pagerOpenWalIfPresent(z) SQLITE_OK +# define pagerBeginReadTransaction(z) SQLITE_OK +#endif + +#ifndef NDEBUG +/* +** Usage: +** +** assert( assert_pager_state(pPager) ); +** +** This function runs many asserts to try to find inconsistencies in +** the internal state of the Pager object. +*/ +static int assert_pager_state(Pager *p){ + Pager *pPager = p; + + /* State must be valid. */ + assert( p->eState==PAGER_OPEN + || p->eState==PAGER_READER + || p->eState==PAGER_WRITER_LOCKED + || p->eState==PAGER_WRITER_CACHEMOD + || p->eState==PAGER_WRITER_DBMOD + || p->eState==PAGER_WRITER_FINISHED + || p->eState==PAGER_ERROR + ); + + /* Regardless of the current state, a temp-file connection always behaves + ** as if it has an exclusive lock on the database file. It never updates + ** the change-counter field, so the changeCountDone flag is always set. + */ + assert( p->tempFile==0 || p->eLock==EXCLUSIVE_LOCK ); + assert( p->tempFile==0 || pPager->changeCountDone ); + + /* If the useJournal flag is clear, the journal-mode must be "OFF". + ** And if the journal-mode is "OFF", the journal file must not be open. + */ + assert( p->journalMode==PAGER_JOURNALMODE_OFF || p->useJournal ); + assert( p->journalMode!=PAGER_JOURNALMODE_OFF || !isOpen(p->jfd) ); + + /* Check that MEMDB implies noSync. And an in-memory journal. Since + ** this means an in-memory pager performs no IO at all, it cannot encounter + ** either SQLITE_IOERR or SQLITE_FULL during rollback or while finalizing + ** a journal file. (although the in-memory journal implementation may + ** return SQLITE_IOERR_NOMEM while the journal file is being written). It + ** is therefore not possible for an in-memory pager to enter the ERROR + ** state. + */ + if( MEMDB ){ + assert( !isOpen(p->fd) ); + assert( p->noSync ); + assert( p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_MEMORY + ); + assert( p->eState!=PAGER_ERROR && p->eState!=PAGER_OPEN ); + assert( pagerUseWal(p)==0 ); + } + + /* If changeCountDone is set, a RESERVED lock or greater must be held + ** on the file. + */ + assert( pPager->changeCountDone==0 || pPager->eLock>=RESERVED_LOCK ); + assert( p->eLock!=PENDING_LOCK ); + + switch( p->eState ){ + case PAGER_OPEN: + assert( !MEMDB ); + assert( pPager->errCode==SQLITE_OK ); + assert( sqlite3PcacheRefCount(pPager->pPCache)==0 || pPager->tempFile ); + break; + + case PAGER_READER: + assert( pPager->errCode==SQLITE_OK ); + assert( p->eLock!=UNKNOWN_LOCK ); + assert( p->eLock>=SHARED_LOCK ); + break; + + case PAGER_WRITER_LOCKED: + assert( p->eLock!=UNKNOWN_LOCK ); + assert( pPager->errCode==SQLITE_OK ); + if( !pagerUseWal(pPager) ){ + assert( p->eLock>=RESERVED_LOCK ); + } + assert( pPager->dbSize==pPager->dbOrigSize ); + assert( pPager->dbOrigSize==pPager->dbFileSize ); + assert( pPager->dbOrigSize==pPager->dbHintSize ); + assert( pPager->setSuper==0 ); + break; + + case PAGER_WRITER_CACHEMOD: + assert( p->eLock!=UNKNOWN_LOCK ); + assert( pPager->errCode==SQLITE_OK ); + if( !pagerUseWal(pPager) ){ + /* It is possible that if journal_mode=wal here that neither the + ** journal file nor the WAL file are open. This happens during + ** a rollback transaction that switches from journal_mode=off + ** to journal_mode=wal. + */ + assert( p->eLock>=RESERVED_LOCK ); + assert( isOpen(p->jfd) + || p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_WAL + ); + } + assert( pPager->dbOrigSize==pPager->dbFileSize ); + assert( pPager->dbOrigSize==pPager->dbHintSize ); + break; + + case PAGER_WRITER_DBMOD: + assert( p->eLock==EXCLUSIVE_LOCK ); + assert( pPager->errCode==SQLITE_OK ); + assert( !pagerUseWal(pPager) ); + assert( p->eLock>=EXCLUSIVE_LOCK ); + assert( isOpen(p->jfd) + || p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_WAL + || (sqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC) + ); + assert( pPager->dbOrigSize<=pPager->dbHintSize ); + break; + + case PAGER_WRITER_FINISHED: + assert( p->eLock==EXCLUSIVE_LOCK ); + assert( pPager->errCode==SQLITE_OK ); + assert( !pagerUseWal(pPager) ); + assert( isOpen(p->jfd) + || p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_WAL + || (sqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC) + ); + break; + + case PAGER_ERROR: + /* There must be at least one outstanding reference to the pager if + ** in ERROR state. Otherwise the pager should have already dropped + ** back to OPEN state. + */ + assert( pPager->errCode!=SQLITE_OK ); + assert( sqlite3PcacheRefCount(pPager->pPCache)>0 || pPager->tempFile ); + break; + } + + return 1; +} +#endif /* ifndef NDEBUG */ + +#ifdef SQLITE_DEBUG +/* +** Return a pointer to a human readable string in a static buffer +** containing the state of the Pager object passed as an argument. This +** is intended to be used within debuggers. For example, as an alternative +** to "print *pPager" in gdb: +** +** (gdb) printf "%s", print_pager_state(pPager) +** +** This routine has external linkage in order to suppress compiler warnings +** about an unused function. It is enclosed within SQLITE_DEBUG and so does +** not appear in normal builds. +*/ +char *print_pager_state(Pager *p){ + static char zRet[1024]; + + sqlite3_snprintf(1024, zRet, + "Filename: %s\n" + "State: %s errCode=%d\n" + "Lock: %s\n" + "Locking mode: locking_mode=%s\n" + "Journal mode: journal_mode=%s\n" + "Backing store: tempFile=%d memDb=%d useJournal=%d\n" + "Journal: journalOff=%lld journalHdr=%lld\n" + "Size: dbsize=%d dbOrigSize=%d dbFileSize=%d\n" + , p->zFilename + , p->eState==PAGER_OPEN ? "OPEN" : + p->eState==PAGER_READER ? "READER" : + p->eState==PAGER_WRITER_LOCKED ? "WRITER_LOCKED" : + p->eState==PAGER_WRITER_CACHEMOD ? "WRITER_CACHEMOD" : + p->eState==PAGER_WRITER_DBMOD ? "WRITER_DBMOD" : + p->eState==PAGER_WRITER_FINISHED ? "WRITER_FINISHED" : + p->eState==PAGER_ERROR ? "ERROR" : "?error?" + , (int)p->errCode + , p->eLock==NO_LOCK ? "NO_LOCK" : + p->eLock==RESERVED_LOCK ? "RESERVED" : + p->eLock==EXCLUSIVE_LOCK ? "EXCLUSIVE" : + p->eLock==SHARED_LOCK ? "SHARED" : + p->eLock==UNKNOWN_LOCK ? "UNKNOWN" : "?error?" + , p->exclusiveMode ? "exclusive" : "normal" + , p->journalMode==PAGER_JOURNALMODE_MEMORY ? "memory" : + p->journalMode==PAGER_JOURNALMODE_OFF ? "off" : + p->journalMode==PAGER_JOURNALMODE_DELETE ? "delete" : + p->journalMode==PAGER_JOURNALMODE_PERSIST ? "persist" : + p->journalMode==PAGER_JOURNALMODE_TRUNCATE ? "truncate" : + p->journalMode==PAGER_JOURNALMODE_WAL ? "wal" : "?error?" + , (int)p->tempFile, (int)p->memDb, (int)p->useJournal + , p->journalOff, p->journalHdr + , (int)p->dbSize, (int)p->dbOrigSize, (int)p->dbFileSize + ); + + return zRet; +} +#endif + +/* Forward references to the various page getters */ +static int getPageNormal(Pager*,Pgno,DbPage**,int); +static int getPageError(Pager*,Pgno,DbPage**,int); +#if SQLITE_MAX_MMAP_SIZE>0 +static int getPageMMap(Pager*,Pgno,DbPage**,int); +#endif + +/* +** Set the Pager.xGet method for the appropriate routine used to fetch +** content from the pager. +*/ +static void setGetterMethod(Pager *pPager){ + if( pPager->errCode ){ + pPager->xGet = getPageError; +#if SQLITE_MAX_MMAP_SIZE>0 + }else if( USEFETCH(pPager) ){ + pPager->xGet = getPageMMap; +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + }else{ + pPager->xGet = getPageNormal; + } +} + +/* +** Return true if it is necessary to write page *pPg into the sub-journal. +** A page needs to be written into the sub-journal if there exists one +** or more open savepoints for which: +** +** * The page-number is less than or equal to PagerSavepoint.nOrig, and +** * The bit corresponding to the page-number is not set in +** PagerSavepoint.pInSavepoint. +*/ +static int subjRequiresPage(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + PagerSavepoint *p; + Pgno pgno = pPg->pgno; + int i; + for(i=0; i<pPager->nSavepoint; i++){ + p = &pPager->aSavepoint[i]; + if( p->nOrig>=pgno && 0==sqlite3BitvecTestNotNull(p->pInSavepoint, pgno) ){ + for(i=i+1; i<pPager->nSavepoint; i++){ + pPager->aSavepoint[i].bTruncateOnRelease = 0; + } + return 1; + } + } + return 0; +} + +#ifdef SQLITE_DEBUG +/* +** Return true if the page is already in the journal file. +*/ +static int pageInJournal(Pager *pPager, PgHdr *pPg){ + return sqlite3BitvecTest(pPager->pInJournal, pPg->pgno); +} +#endif + +/* +** Read a 32-bit integer from the given file descriptor. Store the integer +** that is read in *pRes. Return SQLITE_OK if everything worked, or an +** error code is something goes wrong. +** +** All values are stored on disk as big-endian. +*/ +static int read32bits(sqlite3_file *fd, i64 offset, u32 *pRes){ + unsigned char ac[4]; + int rc = sqlite3OsRead(fd, ac, sizeof(ac), offset); + if( rc==SQLITE_OK ){ + *pRes = sqlite3Get4byte(ac); + } + return rc; +} + +/* +** Write a 32-bit integer into a string buffer in big-endian byte order. +*/ +#define put32bits(A,B) sqlite3Put4byte((u8*)A,B) + + +/* +** Write a 32-bit integer into the given file descriptor. Return SQLITE_OK +** on success or an error code is something goes wrong. +*/ +static int write32bits(sqlite3_file *fd, i64 offset, u32 val){ + char ac[4]; + put32bits(ac, val); + return sqlite3OsWrite(fd, ac, 4, offset); +} + +/* +** Unlock the database file to level eLock, which must be either NO_LOCK +** or SHARED_LOCK. Regardless of whether or not the call to xUnlock() +** succeeds, set the Pager.eLock variable to match the (attempted) new lock. +** +** Except, if Pager.eLock is set to UNKNOWN_LOCK when this function is +** called, do not modify it. See the comment above the #define of +** UNKNOWN_LOCK for an explanation of this. +*/ +static int pagerUnlockDb(Pager *pPager, int eLock){ + int rc = SQLITE_OK; + + assert( !pPager->exclusiveMode || pPager->eLock==eLock ); + assert( eLock==NO_LOCK || eLock==SHARED_LOCK ); + assert( eLock!=NO_LOCK || pagerUseWal(pPager)==0 ); + if( isOpen(pPager->fd) ){ + assert( pPager->eLock>=eLock ); + rc = pPager->noLock ? SQLITE_OK : sqlite3OsUnlock(pPager->fd, eLock); + if( pPager->eLock!=UNKNOWN_LOCK ){ + pPager->eLock = (u8)eLock; + } + IOTRACE(("UNLOCK %p %d\n", pPager, eLock)) + } + pPager->changeCountDone = pPager->tempFile; /* ticket fb3b3024ea238d5c */ + return rc; +} + +/* +** Lock the database file to level eLock, which must be either SHARED_LOCK, +** RESERVED_LOCK or EXCLUSIVE_LOCK. If the caller is successful, set the +** Pager.eLock variable to the new locking state. +** +** Except, if Pager.eLock is set to UNKNOWN_LOCK when this function is +** called, do not modify it unless the new locking state is EXCLUSIVE_LOCK. +** See the comment above the #define of UNKNOWN_LOCK for an explanation +** of this. +*/ +static int pagerLockDb(Pager *pPager, int eLock){ + int rc = SQLITE_OK; + + assert( eLock==SHARED_LOCK || eLock==RESERVED_LOCK || eLock==EXCLUSIVE_LOCK ); + if( pPager->eLock<eLock || pPager->eLock==UNKNOWN_LOCK ){ + rc = pPager->noLock ? SQLITE_OK : sqlite3OsLock(pPager->fd, eLock); + if( rc==SQLITE_OK && (pPager->eLock!=UNKNOWN_LOCK||eLock==EXCLUSIVE_LOCK) ){ + pPager->eLock = (u8)eLock; + IOTRACE(("LOCK %p %d\n", pPager, eLock)) + } + } + return rc; +} + +/* +** This function determines whether or not the atomic-write or +** atomic-batch-write optimizations can be used with this pager. The +** atomic-write optimization can be used if: +** +** (a) the value returned by OsDeviceCharacteristics() indicates that +** a database page may be written atomically, and +** (b) the value returned by OsSectorSize() is less than or equal +** to the page size. +** +** If it can be used, then the value returned is the size of the journal +** file when it contains rollback data for exactly one page. +** +** The atomic-batch-write optimization can be used if OsDeviceCharacteristics() +** returns a value with the SQLITE_IOCAP_BATCH_ATOMIC bit set. -1 is +** returned in this case. +** +** If neither optimization can be used, 0 is returned. +*/ +static int jrnlBufferSize(Pager *pPager){ + assert( !MEMDB ); + +#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ + || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) + int dc; /* Device characteristics */ + + assert( isOpen(pPager->fd) ); + dc = sqlite3OsDeviceCharacteristics(pPager->fd); +#else + UNUSED_PARAMETER(pPager); +#endif + +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + if( pPager->dbSize>0 && (dc&SQLITE_IOCAP_BATCH_ATOMIC) ){ + return -1; + } +#endif + +#ifdef SQLITE_ENABLE_ATOMIC_WRITE + { + int nSector = pPager->sectorSize; + int szPage = pPager->pageSize; + + assert(SQLITE_IOCAP_ATOMIC512==(512>>8)); + assert(SQLITE_IOCAP_ATOMIC64K==(65536>>8)); + if( 0==(dc&(SQLITE_IOCAP_ATOMIC|(szPage>>8)) || nSector>szPage) ){ + return 0; + } + } + + return JOURNAL_HDR_SZ(pPager) + JOURNAL_PG_SZ(pPager); +#endif + + return 0; +} + +/* +** If SQLITE_CHECK_PAGES is defined then we do some sanity checking +** on the cache using a hash function. This is used for testing +** and debugging only. +*/ +#ifdef SQLITE_CHECK_PAGES +/* +** Return a 32-bit hash of the page data for pPage. +*/ +static u32 pager_datahash(int nByte, unsigned char *pData){ + u32 hash = 0; + int i; + for(i=0; i<nByte; i++){ + hash = (hash*1039) + pData[i]; + } + return hash; +} +static u32 pager_pagehash(PgHdr *pPage){ + return pager_datahash(pPage->pPager->pageSize, (unsigned char *)pPage->pData); +} +static void pager_set_pagehash(PgHdr *pPage){ + pPage->pageHash = pager_pagehash(pPage); +} + +/* +** The CHECK_PAGE macro takes a PgHdr* as an argument. If SQLITE_CHECK_PAGES +** is defined, and NDEBUG is not defined, an assert() statement checks +** that the page is either dirty or still matches the calculated page-hash. +*/ +#define CHECK_PAGE(x) checkPage(x) +static void checkPage(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + assert( pPager->eState!=PAGER_ERROR ); + assert( (pPg->flags&PGHDR_DIRTY) || pPg->pageHash==pager_pagehash(pPg) ); +} + +#else +#define pager_datahash(X,Y) 0 +#define pager_pagehash(X) 0 +#define pager_set_pagehash(X) +#define CHECK_PAGE(x) +#endif /* SQLITE_CHECK_PAGES */ + +/* +** When this is called the journal file for pager pPager must be open. +** This function attempts to read a super-journal file name from the +** end of the file and, if successful, copies it into memory supplied +** by the caller. See comments above writeSuperJournal() for the format +** used to store a super-journal file name at the end of a journal file. +** +** zSuper must point to a buffer of at least nSuper bytes allocated by +** the caller. This should be sqlite3_vfs.mxPathname+1 (to ensure there is +** enough space to write the super-journal name). If the super-journal +** name in the journal is longer than nSuper bytes (including a +** nul-terminator), then this is handled as if no super-journal name +** were present in the journal. +** +** If a super-journal file name is present at the end of the journal +** file, then it is copied into the buffer pointed to by zSuper. A +** nul-terminator byte is appended to the buffer following the +** super-journal file name. +** +** If it is determined that no super-journal file name is present +** zSuper[0] is set to 0 and SQLITE_OK returned. +** +** If an error occurs while reading from the journal file, an SQLite +** error code is returned. +*/ +static int readSuperJournal(sqlite3_file *pJrnl, char *zSuper, u32 nSuper){ + int rc; /* Return code */ + u32 len; /* Length in bytes of super-journal name */ + i64 szJ; /* Total size in bytes of journal file pJrnl */ + u32 cksum; /* MJ checksum value read from journal */ + u32 u; /* Unsigned loop counter */ + unsigned char aMagic[8]; /* A buffer to hold the magic header */ + zSuper[0] = '\0'; + + if( SQLITE_OK!=(rc = sqlite3OsFileSize(pJrnl, &szJ)) + || szJ<16 + || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-16, &len)) + || len>=nSuper + || len>szJ-16 + || len==0 + || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-12, &cksum)) + || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, aMagic, 8, szJ-8)) + || memcmp(aMagic, aJournalMagic, 8) + || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, zSuper, len, szJ-16-len)) + ){ + return rc; + } + + /* See if the checksum matches the super-journal name */ + for(u=0; u<len; u++){ + cksum -= zSuper[u]; + } + if( cksum ){ + /* If the checksum doesn't add up, then one or more of the disk sectors + ** containing the super-journal filename is corrupted. This means + ** definitely roll back, so just return SQLITE_OK and report a (nul) + ** super-journal filename. + */ + len = 0; + } + zSuper[len] = '\0'; + zSuper[len+1] = '\0'; + + return SQLITE_OK; +} + +/* +** Return the offset of the sector boundary at or immediately +** following the value in pPager->journalOff, assuming a sector +** size of pPager->sectorSize bytes. +** +** i.e for a sector size of 512: +** +** Pager.journalOff Return value +** --------------------------------------- +** 0 0 +** 512 512 +** 100 512 +** 2000 2048 +** +*/ +static i64 journalHdrOffset(Pager *pPager){ + i64 offset = 0; + i64 c = pPager->journalOff; + if( c ){ + offset = ((c-1)/JOURNAL_HDR_SZ(pPager) + 1) * JOURNAL_HDR_SZ(pPager); + } + assert( offset%JOURNAL_HDR_SZ(pPager)==0 ); + assert( offset>=c ); + assert( (offset-c)<JOURNAL_HDR_SZ(pPager) ); + return offset; +} + +/* +** The journal file must be open when this function is called. +** +** This function is a no-op if the journal file has not been written to +** within the current transaction (i.e. if Pager.journalOff==0). +** +** If doTruncate is non-zero or the Pager.journalSizeLimit variable is +** set to 0, then truncate the journal file to zero bytes in size. Otherwise, +** zero the 28-byte header at the start of the journal file. In either case, +** if the pager is not in no-sync mode, sync the journal file immediately +** after writing or truncating it. +** +** If Pager.journalSizeLimit is set to a positive, non-zero value, and +** following the truncation or zeroing described above the size of the +** journal file in bytes is larger than this value, then truncate the +** journal file to Pager.journalSizeLimit bytes. The journal file does +** not need to be synced following this operation. +** +** If an IO error occurs, abandon processing and return the IO error code. +** Otherwise, return SQLITE_OK. +*/ +static int zeroJournalHdr(Pager *pPager, int doTruncate){ + int rc = SQLITE_OK; /* Return code */ + assert( isOpen(pPager->jfd) ); + assert( !sqlite3JournalIsInMemory(pPager->jfd) ); + if( pPager->journalOff ){ + const i64 iLimit = pPager->journalSizeLimit; /* Local cache of jsl */ + + IOTRACE(("JZEROHDR %p\n", pPager)) + if( doTruncate || iLimit==0 ){ + rc = sqlite3OsTruncate(pPager->jfd, 0); + }else{ + static const char zeroHdr[28] = {0}; + rc = sqlite3OsWrite(pPager->jfd, zeroHdr, sizeof(zeroHdr), 0); + } + if( rc==SQLITE_OK && !pPager->noSync ){ + rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->syncFlags); + } + + /* At this point the transaction is committed but the write lock + ** is still held on the file. If there is a size limit configured for + ** the persistent journal and the journal file currently consumes more + ** space than that limit allows for, truncate it now. There is no need + ** to sync the file following this operation. + */ + if( rc==SQLITE_OK && iLimit>0 ){ + i64 sz; + rc = sqlite3OsFileSize(pPager->jfd, &sz); + if( rc==SQLITE_OK && sz>iLimit ){ + rc = sqlite3OsTruncate(pPager->jfd, iLimit); + } + } + } + return rc; +} + +/* +** The journal file must be open when this routine is called. A journal +** header (JOURNAL_HDR_SZ bytes) is written into the journal file at the +** current location. +** +** The format for the journal header is as follows: +** - 8 bytes: Magic identifying journal format. +** - 4 bytes: Number of records in journal, or -1 no-sync mode is on. +** - 4 bytes: Random number used for page hash. +** - 4 bytes: Initial database page count. +** - 4 bytes: Sector size used by the process that wrote this journal. +** - 4 bytes: Database page size. +** +** Followed by (JOURNAL_HDR_SZ - 28) bytes of unused space. +*/ +static int writeJournalHdr(Pager *pPager){ + int rc = SQLITE_OK; /* Return code */ + char *zHeader = pPager->pTmpSpace; /* Temporary space used to build header */ + u32 nHeader = (u32)pPager->pageSize;/* Size of buffer pointed to by zHeader */ + u32 nWrite; /* Bytes of header sector written */ + int ii; /* Loop counter */ + + assert( isOpen(pPager->jfd) ); /* Journal file must be open. */ + + if( nHeader>JOURNAL_HDR_SZ(pPager) ){ + nHeader = JOURNAL_HDR_SZ(pPager); + } + + /* If there are active savepoints and any of them were created + ** since the most recent journal header was written, update the + ** PagerSavepoint.iHdrOffset fields now. + */ + for(ii=0; ii<pPager->nSavepoint; ii++){ + if( pPager->aSavepoint[ii].iHdrOffset==0 ){ + pPager->aSavepoint[ii].iHdrOffset = pPager->journalOff; + } + } + + pPager->journalHdr = pPager->journalOff = journalHdrOffset(pPager); + + /* + ** Write the nRec Field - the number of page records that follow this + ** journal header. Normally, zero is written to this value at this time. + ** After the records are added to the journal (and the journal synced, + ** if in full-sync mode), the zero is overwritten with the true number + ** of records (see syncJournal()). + ** + ** A faster alternative is to write 0xFFFFFFFF to the nRec field. When + ** reading the journal this value tells SQLite to assume that the + ** rest of the journal file contains valid page records. This assumption + ** is dangerous, as if a failure occurred whilst writing to the journal + ** file it may contain some garbage data. There are two scenarios + ** where this risk can be ignored: + ** + ** * When the pager is in no-sync mode. Corruption can follow a + ** power failure in this case anyway. + ** + ** * When the SQLITE_IOCAP_SAFE_APPEND flag is set. This guarantees + ** that garbage data is never appended to the journal file. + */ + assert( isOpen(pPager->fd) || pPager->noSync ); + if( pPager->noSync || (pPager->journalMode==PAGER_JOURNALMODE_MEMORY) + || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_SAFE_APPEND) + ){ + memcpy(zHeader, aJournalMagic, sizeof(aJournalMagic)); + put32bits(&zHeader[sizeof(aJournalMagic)], 0xffffffff); + }else{ + memset(zHeader, 0, sizeof(aJournalMagic)+4); + } + + /* The random check-hash initializer */ + sqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit); + put32bits(&zHeader[sizeof(aJournalMagic)+4], pPager->cksumInit); + /* The initial database size */ + put32bits(&zHeader[sizeof(aJournalMagic)+8], pPager->dbOrigSize); + /* The assumed sector size for this process */ + put32bits(&zHeader[sizeof(aJournalMagic)+12], pPager->sectorSize); + + /* The page size */ + put32bits(&zHeader[sizeof(aJournalMagic)+16], pPager->pageSize); + + /* Initializing the tail of the buffer is not necessary. Everything + ** works find if the following memset() is omitted. But initializing + ** the memory prevents valgrind from complaining, so we are willing to + ** take the performance hit. + */ + memset(&zHeader[sizeof(aJournalMagic)+20], 0, + nHeader-(sizeof(aJournalMagic)+20)); + + /* In theory, it is only necessary to write the 28 bytes that the + ** journal header consumes to the journal file here. Then increment the + ** Pager.journalOff variable by JOURNAL_HDR_SZ so that the next + ** record is written to the following sector (leaving a gap in the file + ** that will be implicitly filled in by the OS). + ** + ** However it has been discovered that on some systems this pattern can + ** be significantly slower than contiguously writing data to the file, + ** even if that means explicitly writing data to the block of + ** (JOURNAL_HDR_SZ - 28) bytes that will not be used. So that is what + ** is done. + ** + ** The loop is required here in case the sector-size is larger than the + ** database page size. Since the zHeader buffer is only Pager.pageSize + ** bytes in size, more than one call to sqlite3OsWrite() may be required + ** to populate the entire journal header sector. + */ + for(nWrite=0; rc==SQLITE_OK&&nWrite<JOURNAL_HDR_SZ(pPager); nWrite+=nHeader){ + IOTRACE(("JHDR %p %lld %d\n", pPager, pPager->journalHdr, nHeader)) + rc = sqlite3OsWrite(pPager->jfd, zHeader, nHeader, pPager->journalOff); + assert( pPager->journalHdr <= pPager->journalOff ); + pPager->journalOff += nHeader; + } + + return rc; +} + +/* +** The journal file must be open when this is called. A journal header file +** (JOURNAL_HDR_SZ bytes) is read from the current location in the journal +** file. The current location in the journal file is given by +** pPager->journalOff. See comments above function writeJournalHdr() for +** a description of the journal header format. +** +** If the header is read successfully, *pNRec is set to the number of +** page records following this header and *pDbSize is set to the size of the +** database before the transaction began, in pages. Also, pPager->cksumInit +** is set to the value read from the journal header. SQLITE_OK is returned +** in this case. +** +** If the journal header file appears to be corrupted, SQLITE_DONE is +** returned and *pNRec and *PDbSize are undefined. If JOURNAL_HDR_SZ bytes +** cannot be read from the journal file an error code is returned. +*/ +static int readJournalHdr( + Pager *pPager, /* Pager object */ + int isHot, + i64 journalSize, /* Size of the open journal file in bytes */ + u32 *pNRec, /* OUT: Value read from the nRec field */ + u32 *pDbSize /* OUT: Value of original database size field */ +){ + int rc; /* Return code */ + unsigned char aMagic[8]; /* A buffer to hold the magic header */ + i64 iHdrOff; /* Offset of journal header being read */ + + assert( isOpen(pPager->jfd) ); /* Journal file must be open. */ + + /* Advance Pager.journalOff to the start of the next sector. If the + ** journal file is too small for there to be a header stored at this + ** point, return SQLITE_DONE. + */ + pPager->journalOff = journalHdrOffset(pPager); + if( pPager->journalOff+JOURNAL_HDR_SZ(pPager) > journalSize ){ + return SQLITE_DONE; + } + iHdrOff = pPager->journalOff; + + /* Read in the first 8 bytes of the journal header. If they do not match + ** the magic string found at the start of each journal header, return + ** SQLITE_DONE. If an IO error occurs, return an error code. Otherwise, + ** proceed. + */ + if( isHot || iHdrOff!=pPager->journalHdr ){ + rc = sqlite3OsRead(pPager->jfd, aMagic, sizeof(aMagic), iHdrOff); + if( rc ){ + return rc; + } + if( memcmp(aMagic, aJournalMagic, sizeof(aMagic))!=0 ){ + return SQLITE_DONE; + } + } + + /* Read the first three 32-bit fields of the journal header: The nRec + ** field, the checksum-initializer and the database size at the start + ** of the transaction. Return an error code if anything goes wrong. + */ + if( SQLITE_OK!=(rc = read32bits(pPager->jfd, iHdrOff+8, pNRec)) + || SQLITE_OK!=(rc = read32bits(pPager->jfd, iHdrOff+12, &pPager->cksumInit)) + || SQLITE_OK!=(rc = read32bits(pPager->jfd, iHdrOff+16, pDbSize)) + ){ + return rc; + } + + if( pPager->journalOff==0 ){ + u32 iPageSize; /* Page-size field of journal header */ + u32 iSectorSize; /* Sector-size field of journal header */ + + /* Read the page-size and sector-size journal header fields. */ + if( SQLITE_OK!=(rc = read32bits(pPager->jfd, iHdrOff+20, &iSectorSize)) + || SQLITE_OK!=(rc = read32bits(pPager->jfd, iHdrOff+24, &iPageSize)) + ){ + return rc; + } + + /* Versions of SQLite prior to 3.5.8 set the page-size field of the + ** journal header to zero. In this case, assume that the Pager.pageSize + ** variable is already set to the correct page size. + */ + if( iPageSize==0 ){ + iPageSize = pPager->pageSize; + } + + /* Check that the values read from the page-size and sector-size fields + ** are within range. To be 'in range', both values need to be a power + ** of two greater than or equal to 512 or 32, and not greater than their + ** respective compile time maximum limits. + */ + if( iPageSize<512 || iSectorSize<32 + || iPageSize>SQLITE_MAX_PAGE_SIZE || iSectorSize>MAX_SECTOR_SIZE + || ((iPageSize-1)&iPageSize)!=0 || ((iSectorSize-1)&iSectorSize)!=0 + ){ + /* If the either the page-size or sector-size in the journal-header is + ** invalid, then the process that wrote the journal-header must have + ** crashed before the header was synced. In this case stop reading + ** the journal file here. + */ + return SQLITE_DONE; + } + + /* Update the page-size to match the value read from the journal. + ** Use a testcase() macro to make sure that malloc failure within + ** PagerSetPagesize() is tested. + */ + rc = sqlite3PagerSetPagesize(pPager, &iPageSize, -1); + testcase( rc!=SQLITE_OK ); + + /* Update the assumed sector-size to match the value used by + ** the process that created this journal. If this journal was + ** created by a process other than this one, then this routine + ** is being called from within pager_playback(). The local value + ** of Pager.sectorSize is restored at the end of that routine. + */ + pPager->sectorSize = iSectorSize; + } + + pPager->journalOff += JOURNAL_HDR_SZ(pPager); + return rc; +} + + +/* +** Write the supplied super-journal name into the journal file for pager +** pPager at the current location. The super-journal name must be the last +** thing written to a journal file. If the pager is in full-sync mode, the +** journal file descriptor is advanced to the next sector boundary before +** anything is written. The format is: +** +** + 4 bytes: PAGER_SJ_PGNO. +** + N bytes: super-journal filename in utf-8. +** + 4 bytes: N (length of super-journal name in bytes, no nul-terminator). +** + 4 bytes: super-journal name checksum. +** + 8 bytes: aJournalMagic[]. +** +** The super-journal page checksum is the sum of the bytes in the super-journal +** name, where each byte is interpreted as a signed 8-bit integer. +** +** If zSuper is a NULL pointer (occurs for a single database transaction), +** this call is a no-op. +*/ +static int writeSuperJournal(Pager *pPager, const char *zSuper){ + int rc; /* Return code */ + int nSuper; /* Length of string zSuper */ + i64 iHdrOff; /* Offset of header in journal file */ + i64 jrnlSize; /* Size of journal file on disk */ + u32 cksum = 0; /* Checksum of string zSuper */ + + assert( pPager->setSuper==0 ); + assert( !pagerUseWal(pPager) ); + + if( !zSuper + || pPager->journalMode==PAGER_JOURNALMODE_MEMORY + || !isOpen(pPager->jfd) + ){ + return SQLITE_OK; + } + pPager->setSuper = 1; + assert( pPager->journalHdr <= pPager->journalOff ); + + /* Calculate the length in bytes and the checksum of zSuper */ + for(nSuper=0; zSuper[nSuper]; nSuper++){ + cksum += zSuper[nSuper]; + } + + /* If in full-sync mode, advance to the next disk sector before writing + ** the super-journal name. This is in case the previous page written to + ** the journal has already been synced. + */ + if( pPager->fullSync ){ + pPager->journalOff = journalHdrOffset(pPager); + } + iHdrOff = pPager->journalOff; + + /* Write the super-journal data to the end of the journal file. If + ** an error occurs, return the error code to the caller. + */ + if( (0 != (rc = write32bits(pPager->jfd, iHdrOff, PAGER_SJ_PGNO(pPager)))) + || (0 != (rc = sqlite3OsWrite(pPager->jfd, zSuper, nSuper, iHdrOff+4))) + || (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nSuper, nSuper))) + || (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nSuper+4, cksum))) + || (0 != (rc = sqlite3OsWrite(pPager->jfd, aJournalMagic, 8, + iHdrOff+4+nSuper+8))) + ){ + return rc; + } + pPager->journalOff += (nSuper+20); + + /* If the pager is in persistent-journal mode, then the physical + ** journal-file may extend past the end of the super-journal name + ** and 8 bytes of magic data just written to the file. This is + ** dangerous because the code to rollback a hot-journal file + ** will not be able to find the super-journal name to determine + ** whether or not the journal is hot. + ** + ** Easiest thing to do in this scenario is to truncate the journal + ** file to the required size. + */ + if( SQLITE_OK==(rc = sqlite3OsFileSize(pPager->jfd, &jrnlSize)) + && jrnlSize>pPager->journalOff + ){ + rc = sqlite3OsTruncate(pPager->jfd, pPager->journalOff); + } + return rc; +} + +/* +** Discard the entire contents of the in-memory page-cache. +*/ +static void pager_reset(Pager *pPager){ + pPager->iDataVersion++; + sqlite3BackupRestart(pPager->pBackup); + sqlite3PcacheClear(pPager->pPCache); +} + +/* +** Return the pPager->iDataVersion value +*/ +SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager *pPager){ + return pPager->iDataVersion; +} + +/* +** Free all structures in the Pager.aSavepoint[] array and set both +** Pager.aSavepoint and Pager.nSavepoint to zero. Close the sub-journal +** if it is open and the pager is not in exclusive mode. +*/ +static void releaseAllSavepoints(Pager *pPager){ + int ii; /* Iterator for looping through Pager.aSavepoint */ + for(ii=0; ii<pPager->nSavepoint; ii++){ + sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint); + } + if( !pPager->exclusiveMode || sqlite3JournalIsInMemory(pPager->sjfd) ){ + sqlite3OsClose(pPager->sjfd); + } + sqlite3_free(pPager->aSavepoint); + pPager->aSavepoint = 0; + pPager->nSavepoint = 0; + pPager->nSubRec = 0; +} + +/* +** Set the bit number pgno in the PagerSavepoint.pInSavepoint +** bitvecs of all open savepoints. Return SQLITE_OK if successful +** or SQLITE_NOMEM if a malloc failure occurs. +*/ +static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){ + int ii; /* Loop counter */ + int rc = SQLITE_OK; /* Result code */ + + for(ii=0; ii<pPager->nSavepoint; ii++){ + PagerSavepoint *p = &pPager->aSavepoint[ii]; + if( pgno<=p->nOrig ){ + rc |= sqlite3BitvecSet(p->pInSavepoint, pgno); + testcase( rc==SQLITE_NOMEM ); + assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); + } + } + return rc; +} + +/* +** This function is a no-op if the pager is in exclusive mode and not +** in the ERROR state. Otherwise, it switches the pager to PAGER_OPEN +** state. +** +** If the pager is not in exclusive-access mode, the database file is +** completely unlocked. If the file is unlocked and the file-system does +** not exhibit the UNDELETABLE_WHEN_OPEN property, the journal file is +** closed (if it is open). +** +** If the pager is in ERROR state when this function is called, the +** contents of the pager cache are discarded before switching back to +** the OPEN state. Regardless of whether the pager is in exclusive-mode +** or not, any journal file left in the file-system will be treated +** as a hot-journal and rolled back the next time a read-transaction +** is opened (by this or by any other connection). +*/ +static void pager_unlock(Pager *pPager){ + + assert( pPager->eState==PAGER_READER + || pPager->eState==PAGER_OPEN + || pPager->eState==PAGER_ERROR + ); + + sqlite3BitvecDestroy(pPager->pInJournal); + pPager->pInJournal = 0; + releaseAllSavepoints(pPager); + + if( pagerUseWal(pPager) ){ + assert( !isOpen(pPager->jfd) ); + sqlite3WalEndReadTransaction(pPager->pWal); + pPager->eState = PAGER_OPEN; + }else if( !pPager->exclusiveMode ){ + int rc; /* Error code returned by pagerUnlockDb() */ + int iDc = isOpen(pPager->fd)?sqlite3OsDeviceCharacteristics(pPager->fd):0; + + /* If the operating system support deletion of open files, then + ** close the journal file when dropping the database lock. Otherwise + ** another connection with journal_mode=delete might delete the file + ** out from under us. + */ + assert( (PAGER_JOURNALMODE_MEMORY & 5)!=1 ); + assert( (PAGER_JOURNALMODE_OFF & 5)!=1 ); + assert( (PAGER_JOURNALMODE_WAL & 5)!=1 ); + assert( (PAGER_JOURNALMODE_DELETE & 5)!=1 ); + assert( (PAGER_JOURNALMODE_TRUNCATE & 5)==1 ); + assert( (PAGER_JOURNALMODE_PERSIST & 5)==1 ); + if( 0==(iDc & SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN) + || 1!=(pPager->journalMode & 5) + ){ + sqlite3OsClose(pPager->jfd); + } + + /* If the pager is in the ERROR state and the call to unlock the database + ** file fails, set the current lock to UNKNOWN_LOCK. See the comment + ** above the #define for UNKNOWN_LOCK for an explanation of why this + ** is necessary. + */ + rc = pagerUnlockDb(pPager, NO_LOCK); + if( rc!=SQLITE_OK && pPager->eState==PAGER_ERROR ){ + pPager->eLock = UNKNOWN_LOCK; + } + + /* The pager state may be changed from PAGER_ERROR to PAGER_OPEN here + ** without clearing the error code. This is intentional - the error + ** code is cleared and the cache reset in the block below. + */ + assert( pPager->errCode || pPager->eState!=PAGER_ERROR ); + pPager->eState = PAGER_OPEN; + } + + /* If Pager.errCode is set, the contents of the pager cache cannot be + ** trusted. Now that there are no outstanding references to the pager, + ** it can safely move back to PAGER_OPEN state. This happens in both + ** normal and exclusive-locking mode. + */ + assert( pPager->errCode==SQLITE_OK || !MEMDB ); + if( pPager->errCode ){ + if( pPager->tempFile==0 ){ + pager_reset(pPager); + pPager->changeCountDone = 0; + pPager->eState = PAGER_OPEN; + }else{ + pPager->eState = (isOpen(pPager->jfd) ? PAGER_OPEN : PAGER_READER); + } + if( USEFETCH(pPager) ) sqlite3OsUnfetch(pPager->fd, 0, 0); + pPager->errCode = SQLITE_OK; + setGetterMethod(pPager); + } + + pPager->journalOff = 0; + pPager->journalHdr = 0; + pPager->setSuper = 0; +} + +/* +** This function is called whenever an IOERR or FULL error that requires +** the pager to transition into the ERROR state may have occurred. +** The first argument is a pointer to the pager structure, the second +** the error-code about to be returned by a pager API function. The +** value returned is a copy of the second argument to this function. +** +** If the second argument is SQLITE_FULL, SQLITE_IOERR or one of the +** IOERR sub-codes, the pager enters the ERROR state and the error code +** is stored in Pager.errCode. While the pager remains in the ERROR state, +** all major API calls on the Pager will immediately return Pager.errCode. +** +** The ERROR state indicates that the contents of the pager-cache +** cannot be trusted. This state can be cleared by completely discarding +** the contents of the pager-cache. If a transaction was active when +** the persistent error occurred, then the rollback journal may need +** to be replayed to restore the contents of the database file (as if +** it were a hot-journal). +*/ +static int pager_error(Pager *pPager, int rc){ + int rc2 = rc & 0xff; + assert( rc==SQLITE_OK || !MEMDB ); + assert( + pPager->errCode==SQLITE_FULL || + pPager->errCode==SQLITE_OK || + (pPager->errCode & 0xff)==SQLITE_IOERR + ); + if( rc2==SQLITE_FULL || rc2==SQLITE_IOERR ){ + pPager->errCode = rc; + pPager->eState = PAGER_ERROR; + setGetterMethod(pPager); + } + return rc; +} + +static int pager_truncate(Pager *pPager, Pgno nPage); + +/* +** The write transaction open on pPager is being committed (bCommit==1) +** or rolled back (bCommit==0). +** +** Return TRUE if and only if all dirty pages should be flushed to disk. +** +** Rules: +** +** * For non-TEMP databases, always sync to disk. This is necessary +** for transactions to be durable. +** +** * Sync TEMP database only on a COMMIT (not a ROLLBACK) when the backing +** file has been created already (via a spill on pagerStress()) and +** when the number of dirty pages in memory exceeds 25% of the total +** cache size. +*/ +static int pagerFlushOnCommit(Pager *pPager, int bCommit){ + if( pPager->tempFile==0 ) return 1; + if( !bCommit ) return 0; + if( !isOpen(pPager->fd) ) return 0; + return (sqlite3PCachePercentDirty(pPager->pPCache)>=25); +} + +/* +** This routine ends a transaction. A transaction is usually ended by +** either a COMMIT or a ROLLBACK operation. This routine may be called +** after rollback of a hot-journal, or if an error occurs while opening +** the journal file or writing the very first journal-header of a +** database transaction. +** +** This routine is never called in PAGER_ERROR state. If it is called +** in PAGER_NONE or PAGER_SHARED state and the lock held is less +** exclusive than a RESERVED lock, it is a no-op. +** +** Otherwise, any active savepoints are released. +** +** If the journal file is open, then it is "finalized". Once a journal +** file has been finalized it is not possible to use it to roll back a +** transaction. Nor will it be considered to be a hot-journal by this +** or any other database connection. Exactly how a journal is finalized +** depends on whether or not the pager is running in exclusive mode and +** the current journal-mode (Pager.journalMode value), as follows: +** +** journalMode==MEMORY +** Journal file descriptor is simply closed. This destroys an +** in-memory journal. +** +** journalMode==TRUNCATE +** Journal file is truncated to zero bytes in size. +** +** journalMode==PERSIST +** The first 28 bytes of the journal file are zeroed. This invalidates +** the first journal header in the file, and hence the entire journal +** file. An invalid journal file cannot be rolled back. +** +** journalMode==DELETE +** The journal file is closed and deleted using sqlite3OsDelete(). +** +** If the pager is running in exclusive mode, this method of finalizing +** the journal file is never used. Instead, if the journalMode is +** DELETE and the pager is in exclusive mode, the method described under +** journalMode==PERSIST is used instead. +** +** After the journal is finalized, the pager moves to PAGER_READER state. +** If running in non-exclusive rollback mode, the lock on the file is +** downgraded to a SHARED_LOCK. +** +** SQLITE_OK is returned if no error occurs. If an error occurs during +** any of the IO operations to finalize the journal file or unlock the +** database then the IO error code is returned to the user. If the +** operation to finalize the journal file fails, then the code still +** tries to unlock the database file if not in exclusive mode. If the +** unlock operation fails as well, then the first error code related +** to the first error encountered (the journal finalization one) is +** returned. +*/ +static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){ + int rc = SQLITE_OK; /* Error code from journal finalization operation */ + int rc2 = SQLITE_OK; /* Error code from db file unlock operation */ + + /* Do nothing if the pager does not have an open write transaction + ** or at least a RESERVED lock. This function may be called when there + ** is no write-transaction active but a RESERVED or greater lock is + ** held under two circumstances: + ** + ** 1. After a successful hot-journal rollback, it is called with + ** eState==PAGER_NONE and eLock==EXCLUSIVE_LOCK. + ** + ** 2. If a connection with locking_mode=exclusive holding an EXCLUSIVE + ** lock switches back to locking_mode=normal and then executes a + ** read-transaction, this function is called with eState==PAGER_READER + ** and eLock==EXCLUSIVE_LOCK when the read-transaction is closed. + */ + assert( assert_pager_state(pPager) ); + assert( pPager->eState!=PAGER_ERROR ); + if( pPager->eState<PAGER_WRITER_LOCKED && pPager->eLock<RESERVED_LOCK ){ + return SQLITE_OK; + } + + releaseAllSavepoints(pPager); + assert( isOpen(pPager->jfd) || pPager->pInJournal==0 + || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_BATCH_ATOMIC) + ); + if( isOpen(pPager->jfd) ){ + assert( !pagerUseWal(pPager) ); + + /* Finalize the journal file. */ + if( sqlite3JournalIsInMemory(pPager->jfd) ){ + /* assert( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ); */ + sqlite3OsClose(pPager->jfd); + }else if( pPager->journalMode==PAGER_JOURNALMODE_TRUNCATE ){ + if( pPager->journalOff==0 ){ + rc = SQLITE_OK; + }else{ + rc = sqlite3OsTruncate(pPager->jfd, 0); + if( rc==SQLITE_OK && pPager->fullSync ){ + /* Make sure the new file size is written into the inode right away. + ** Otherwise the journal might resurrect following a power loss and + ** cause the last transaction to roll back. See + ** https://bugzilla.mozilla.org/show_bug.cgi?id=1072773 + */ + rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags); + } + } + pPager->journalOff = 0; + }else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST + || (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL) + ){ + rc = zeroJournalHdr(pPager, hasSuper||pPager->tempFile); + pPager->journalOff = 0; + }else{ + /* This branch may be executed with Pager.journalMode==MEMORY if + ** a hot-journal was just rolled back. In this case the journal + ** file should be closed and deleted. If this connection writes to + ** the database file, it will do so using an in-memory journal. + */ + int bDelete = !pPager->tempFile; + assert( sqlite3JournalIsInMemory(pPager->jfd)==0 ); + assert( pPager->journalMode==PAGER_JOURNALMODE_DELETE + || pPager->journalMode==PAGER_JOURNALMODE_MEMORY + || pPager->journalMode==PAGER_JOURNALMODE_WAL + ); + sqlite3OsClose(pPager->jfd); + if( bDelete ){ + rc = sqlite3OsDelete(pPager->pVfs, pPager->zJournal, pPager->extraSync); + } + } + } + +#ifdef SQLITE_CHECK_PAGES + sqlite3PcacheIterateDirty(pPager->pPCache, pager_set_pagehash); + if( pPager->dbSize==0 && sqlite3PcacheRefCount(pPager->pPCache)>0 ){ + PgHdr *p = sqlite3PagerLookup(pPager, 1); + if( p ){ + p->pageHash = 0; + sqlite3PagerUnrefNotNull(p); + } + } +#endif + + sqlite3BitvecDestroy(pPager->pInJournal); + pPager->pInJournal = 0; + pPager->nRec = 0; + if( rc==SQLITE_OK ){ + if( MEMDB || pagerFlushOnCommit(pPager, bCommit) ){ + sqlite3PcacheCleanAll(pPager->pPCache); + }else{ + sqlite3PcacheClearWritable(pPager->pPCache); + } + sqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize); + } + + if( pagerUseWal(pPager) ){ + /* Drop the WAL write-lock, if any. Also, if the connection was in + ** locking_mode=exclusive mode but is no longer, drop the EXCLUSIVE + ** lock held on the database file. + */ + rc2 = sqlite3WalEndWriteTransaction(pPager->pWal); + assert( rc2==SQLITE_OK ); + }else if( rc==SQLITE_OK && bCommit && pPager->dbFileSize>pPager->dbSize ){ + /* This branch is taken when committing a transaction in rollback-journal + ** mode if the database file on disk is larger than the database image. + ** At this point the journal has been finalized and the transaction + ** successfully committed, but the EXCLUSIVE lock is still held on the + ** file. So it is safe to truncate the database file to its minimum + ** required size. */ + assert( pPager->eLock==EXCLUSIVE_LOCK ); + rc = pager_truncate(pPager, pPager->dbSize); + } + + if( rc==SQLITE_OK && bCommit ){ + rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_COMMIT_PHASETWO, 0); + if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; + } + + if( !pPager->exclusiveMode + && (!pagerUseWal(pPager) || sqlite3WalExclusiveMode(pPager->pWal, 0)) + ){ + rc2 = pagerUnlockDb(pPager, SHARED_LOCK); + } + pPager->eState = PAGER_READER; + pPager->setSuper = 0; + + return (rc==SQLITE_OK?rc2:rc); +} + +/* +** Execute a rollback if a transaction is active and unlock the +** database file. +** +** If the pager has already entered the ERROR state, do not attempt +** the rollback at this time. Instead, pager_unlock() is called. The +** call to pager_unlock() will discard all in-memory pages, unlock +** the database file and move the pager back to OPEN state. If this +** means that there is a hot-journal left in the file-system, the next +** connection to obtain a shared lock on the pager (which may be this one) +** will roll it back. +** +** If the pager has not already entered the ERROR state, but an IO or +** malloc error occurs during a rollback, then this will itself cause +** the pager to enter the ERROR state. Which will be cleared by the +** call to pager_unlock(), as described above. +*/ +static void pagerUnlockAndRollback(Pager *pPager){ + if( pPager->eState!=PAGER_ERROR && pPager->eState!=PAGER_OPEN ){ + assert( assert_pager_state(pPager) ); + if( pPager->eState>=PAGER_WRITER_LOCKED ){ + sqlite3BeginBenignMalloc(); + sqlite3PagerRollback(pPager); + sqlite3EndBenignMalloc(); + }else if( !pPager->exclusiveMode ){ + assert( pPager->eState==PAGER_READER ); + pager_end_transaction(pPager, 0, 0); + } + } + pager_unlock(pPager); +} + +/* +** Parameter aData must point to a buffer of pPager->pageSize bytes +** of data. Compute and return a checksum based on the contents of the +** page of data and the current value of pPager->cksumInit. +** +** This is not a real checksum. It is really just the sum of the +** random initial value (pPager->cksumInit) and every 200th byte +** of the page data, starting with byte offset (pPager->pageSize%200). +** Each byte is interpreted as an 8-bit unsigned integer. +** +** Changing the formula used to compute this checksum results in an +** incompatible journal file format. +** +** If journal corruption occurs due to a power failure, the most likely +** scenario is that one end or the other of the record will be changed. +** It is much less likely that the two ends of the journal record will be +** correct and the middle be corrupt. Thus, this "checksum" scheme, +** though fast and simple, catches the mostly likely kind of corruption. +*/ +static u32 pager_cksum(Pager *pPager, const u8 *aData){ + u32 cksum = pPager->cksumInit; /* Checksum value to return */ + int i = pPager->pageSize-200; /* Loop counter */ + while( i>0 ){ + cksum += aData[i]; + i -= 200; + } + return cksum; +} + +/* +** Read a single page from either the journal file (if isMainJrnl==1) or +** from the sub-journal (if isMainJrnl==0) and playback that page. +** The page begins at offset *pOffset into the file. The *pOffset +** value is increased to the start of the next page in the journal. +** +** The main rollback journal uses checksums - the statement journal does +** not. +** +** If the page number of the page record read from the (sub-)journal file +** is greater than the current value of Pager.dbSize, then playback is +** skipped and SQLITE_OK is returned. +** +** If pDone is not NULL, then it is a record of pages that have already +** been played back. If the page at *pOffset has already been played back +** (if the corresponding pDone bit is set) then skip the playback. +** Make sure the pDone bit corresponding to the *pOffset page is set +** prior to returning. +** +** If the page record is successfully read from the (sub-)journal file +** and played back, then SQLITE_OK is returned. If an IO error occurs +** while reading the record from the (sub-)journal file or while writing +** to the database file, then the IO error code is returned. If data +** is successfully read from the (sub-)journal file but appears to be +** corrupted, SQLITE_DONE is returned. Data is considered corrupted in +** two circumstances: +** +** * If the record page-number is illegal (0 or PAGER_SJ_PGNO), or +** * If the record is being rolled back from the main journal file +** and the checksum field does not match the record content. +** +** Neither of these two scenarios are possible during a savepoint rollback. +** +** If this is a savepoint rollback, then memory may have to be dynamically +** allocated by this function. If this is the case and an allocation fails, +** SQLITE_NOMEM is returned. +*/ +static int pager_playback_one_page( + Pager *pPager, /* The pager being played back */ + i64 *pOffset, /* Offset of record to playback */ + Bitvec *pDone, /* Bitvec of pages already played back */ + int isMainJrnl, /* 1 -> main journal. 0 -> sub-journal. */ + int isSavepnt /* True for a savepoint rollback */ +){ + int rc; + PgHdr *pPg; /* An existing page in the cache */ + Pgno pgno; /* The page number of a page in journal */ + u32 cksum; /* Checksum used for sanity checking */ + char *aData; /* Temporary storage for the page */ + sqlite3_file *jfd; /* The file descriptor for the journal file */ + int isSynced; /* True if journal page is synced */ + + assert( (isMainJrnl&~1)==0 ); /* isMainJrnl is 0 or 1 */ + assert( (isSavepnt&~1)==0 ); /* isSavepnt is 0 or 1 */ + assert( isMainJrnl || pDone ); /* pDone always used on sub-journals */ + assert( isSavepnt || pDone==0 ); /* pDone never used on non-savepoint */ + + aData = pPager->pTmpSpace; + assert( aData ); /* Temp storage must have already been allocated */ + assert( pagerUseWal(pPager)==0 || (!isMainJrnl && isSavepnt) ); + + /* Either the state is greater than PAGER_WRITER_CACHEMOD (a transaction + ** or savepoint rollback done at the request of the caller) or this is + ** a hot-journal rollback. If it is a hot-journal rollback, the pager + ** is in state OPEN and holds an EXCLUSIVE lock. Hot-journal rollback + ** only reads from the main journal, not the sub-journal. + */ + assert( pPager->eState>=PAGER_WRITER_CACHEMOD + || (pPager->eState==PAGER_OPEN && pPager->eLock==EXCLUSIVE_LOCK) + ); + assert( pPager->eState>=PAGER_WRITER_CACHEMOD || isMainJrnl ); + + /* Read the page number and page data from the journal or sub-journal + ** file. Return an error code to the caller if an IO error occurs. + */ + jfd = isMainJrnl ? pPager->jfd : pPager->sjfd; + rc = read32bits(jfd, *pOffset, &pgno); + if( rc!=SQLITE_OK ) return rc; + rc = sqlite3OsRead(jfd, (u8*)aData, pPager->pageSize, (*pOffset)+4); + if( rc!=SQLITE_OK ) return rc; + *pOffset += pPager->pageSize + 4 + isMainJrnl*4; + + /* Sanity checking on the page. This is more important that I originally + ** thought. If a power failure occurs while the journal is being written, + ** it could cause invalid data to be written into the journal. We need to + ** detect this invalid data (with high probability) and ignore it. + */ + if( pgno==0 || pgno==PAGER_SJ_PGNO(pPager) ){ + assert( !isSavepnt ); + return SQLITE_DONE; + } + if( pgno>(Pgno)pPager->dbSize || sqlite3BitvecTest(pDone, pgno) ){ + return SQLITE_OK; + } + if( isMainJrnl ){ + rc = read32bits(jfd, (*pOffset)-4, &cksum); + if( rc ) return rc; + if( !isSavepnt && pager_cksum(pPager, (u8*)aData)!=cksum ){ + return SQLITE_DONE; + } + } + + /* If this page has already been played back before during the current + ** rollback, then don't bother to play it back again. + */ + if( pDone && (rc = sqlite3BitvecSet(pDone, pgno))!=SQLITE_OK ){ + return rc; + } + + /* When playing back page 1, restore the nReserve setting + */ + if( pgno==1 && pPager->nReserve!=((u8*)aData)[20] ){ + pPager->nReserve = ((u8*)aData)[20]; + } + + /* If the pager is in CACHEMOD state, then there must be a copy of this + ** page in the pager cache. In this case just update the pager cache, + ** not the database file. The page is left marked dirty in this case. + ** + ** An exception to the above rule: If the database is in no-sync mode + ** and a page is moved during an incremental vacuum then the page may + ** not be in the pager cache. Later: if a malloc() or IO error occurs + ** during a Movepage() call, then the page may not be in the cache + ** either. So the condition described in the above paragraph is not + ** assert()able. + ** + ** If in WRITER_DBMOD, WRITER_FINISHED or OPEN state, then we update the + ** pager cache if it exists and the main file. The page is then marked + ** not dirty. Since this code is only executed in PAGER_OPEN state for + ** a hot-journal rollback, it is guaranteed that the page-cache is empty + ** if the pager is in OPEN state. + ** + ** Ticket #1171: The statement journal might contain page content that is + ** different from the page content at the start of the transaction. + ** This occurs when a page is changed prior to the start of a statement + ** then changed again within the statement. When rolling back such a + ** statement we must not write to the original database unless we know + ** for certain that original page contents are synced into the main rollback + ** journal. Otherwise, a power loss might leave modified data in the + ** database file without an entry in the rollback journal that can + ** restore the database to its original form. Two conditions must be + ** met before writing to the database files. (1) the database must be + ** locked. (2) we know that the original page content is fully synced + ** in the main journal either because the page is not in cache or else + ** the page is marked as needSync==0. + ** + ** 2008-04-14: When attempting to vacuum a corrupt database file, it + ** is possible to fail a statement on a database that does not yet exist. + ** Do not attempt to write if database file has never been opened. + */ + if( pagerUseWal(pPager) ){ + pPg = 0; + }else{ + pPg = sqlite3PagerLookup(pPager, pgno); + } + assert( pPg || !MEMDB ); + assert( pPager->eState!=PAGER_OPEN || pPg==0 || pPager->tempFile ); + PAGERTRACE(("PLAYBACK %d page %d hash(%08x) %s\n", + PAGERID(pPager), pgno, pager_datahash(pPager->pageSize, (u8*)aData), + (isMainJrnl?"main-journal":"sub-journal") + )); + if( isMainJrnl ){ + isSynced = pPager->noSync || (*pOffset <= pPager->journalHdr); + }else{ + isSynced = (pPg==0 || 0==(pPg->flags & PGHDR_NEED_SYNC)); + } + if( isOpen(pPager->fd) + && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) + && isSynced + ){ + i64 ofst = (pgno-1)*(i64)pPager->pageSize; + testcase( !isSavepnt && pPg!=0 && (pPg->flags&PGHDR_NEED_SYNC)!=0 ); + assert( !pagerUseWal(pPager) ); + + /* Write the data read from the journal back into the database file. + ** This is usually safe even for an encrypted database - as the data + ** was encrypted before it was written to the journal file. The exception + ** is if the data was just read from an in-memory sub-journal. In that + ** case it must be encrypted here before it is copied into the database + ** file. */ + rc = sqlite3OsWrite(pPager->fd, (u8 *)aData, pPager->pageSize, ofst); + + if( pgno>pPager->dbFileSize ){ + pPager->dbFileSize = pgno; + } + if( pPager->pBackup ){ + sqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)aData); + } + }else if( !isMainJrnl && pPg==0 ){ + /* If this is a rollback of a savepoint and data was not written to + ** the database and the page is not in-memory, there is a potential + ** problem. When the page is next fetched by the b-tree layer, it + ** will be read from the database file, which may or may not be + ** current. + ** + ** There are a couple of different ways this can happen. All are quite + ** obscure. When running in synchronous mode, this can only happen + ** if the page is on the free-list at the start of the transaction, then + ** populated, then moved using sqlite3PagerMovepage(). + ** + ** The solution is to add an in-memory page to the cache containing + ** the data just read from the sub-journal. Mark the page as dirty + ** and if the pager requires a journal-sync, then mark the page as + ** requiring a journal-sync before it is written. + */ + assert( isSavepnt ); + assert( (pPager->doNotSpill & SPILLFLAG_ROLLBACK)==0 ); + pPager->doNotSpill |= SPILLFLAG_ROLLBACK; + rc = sqlite3PagerGet(pPager, pgno, &pPg, 1); + assert( (pPager->doNotSpill & SPILLFLAG_ROLLBACK)!=0 ); + pPager->doNotSpill &= ~SPILLFLAG_ROLLBACK; + if( rc!=SQLITE_OK ) return rc; + sqlite3PcacheMakeDirty(pPg); + } + if( pPg ){ + /* No page should ever be explicitly rolled back that is in use, except + ** for page 1 which is held in use in order to keep the lock on the + ** database active. However such a page may be rolled back as a result + ** of an internal error resulting in an automatic call to + ** sqlite3PagerRollback(). + */ + void *pData; + pData = pPg->pData; + memcpy(pData, (u8*)aData, pPager->pageSize); + pPager->xReiniter(pPg); + /* It used to be that sqlite3PcacheMakeClean(pPg) was called here. But + ** that call was dangerous and had no detectable benefit since the cache + ** is normally cleaned by sqlite3PcacheCleanAll() after rollback and so + ** has been removed. */ + pager_set_pagehash(pPg); + + /* If this was page 1, then restore the value of Pager.dbFileVers. + ** Do this before any decoding. */ + if( pgno==1 ){ + memcpy(&pPager->dbFileVers, &((u8*)pData)[24],sizeof(pPager->dbFileVers)); + } + sqlite3PcacheRelease(pPg); + } + return rc; +} + +/* +** Parameter zSuper is the name of a super-journal file. A single journal +** file that referred to the super-journal file has just been rolled back. +** This routine checks if it is possible to delete the super-journal file, +** and does so if it is. +** +** Argument zSuper may point to Pager.pTmpSpace. So that buffer is not +** available for use within this function. +** +** When a super-journal file is created, it is populated with the names +** of all of its child journals, one after another, formatted as utf-8 +** encoded text. The end of each child journal file is marked with a +** nul-terminator byte (0x00). i.e. the entire contents of a super-journal +** file for a transaction involving two databases might be: +** +** "/home/bill/a.db-journal\x00/home/bill/b.db-journal\x00" +** +** A super-journal file may only be deleted once all of its child +** journals have been rolled back. +** +** This function reads the contents of the super-journal file into +** memory and loops through each of the child journal names. For +** each child journal, it checks if: +** +** * if the child journal exists, and if so +** * if the child journal contains a reference to super-journal +** file zSuper +** +** If a child journal can be found that matches both of the criteria +** above, this function returns without doing anything. Otherwise, if +** no such child journal can be found, file zSuper is deleted from +** the file-system using sqlite3OsDelete(). +** +** If an IO error within this function, an error code is returned. This +** function allocates memory by calling sqlite3Malloc(). If an allocation +** fails, SQLITE_NOMEM is returned. Otherwise, if no IO or malloc errors +** occur, SQLITE_OK is returned. +** +** TODO: This function allocates a single block of memory to load +** the entire contents of the super-journal file. This could be +** a couple of kilobytes or so - potentially larger than the page +** size. +*/ +static int pager_delsuper(Pager *pPager, const char *zSuper){ + sqlite3_vfs *pVfs = pPager->pVfs; + int rc; /* Return code */ + sqlite3_file *pSuper; /* Malloc'd super-journal file descriptor */ + sqlite3_file *pJournal; /* Malloc'd child-journal file descriptor */ + char *zSuperJournal = 0; /* Contents of super-journal file */ + i64 nSuperJournal; /* Size of super-journal file */ + char *zJournal; /* Pointer to one journal within MJ file */ + char *zSuperPtr; /* Space to hold super-journal filename */ + char *zFree = 0; /* Free this buffer */ + int nSuperPtr; /* Amount of space allocated to zSuperPtr[] */ + + /* Allocate space for both the pJournal and pSuper file descriptors. + ** If successful, open the super-journal file for reading. + */ + pSuper = (sqlite3_file *)sqlite3MallocZero(pVfs->szOsFile * 2); + if( !pSuper ){ + rc = SQLITE_NOMEM_BKPT; + pJournal = 0; + }else{ + const int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_SUPER_JOURNAL); + rc = sqlite3OsOpen(pVfs, zSuper, pSuper, flags, 0); + pJournal = (sqlite3_file *)(((u8 *)pSuper) + pVfs->szOsFile); + } + if( rc!=SQLITE_OK ) goto delsuper_out; + + /* Load the entire super-journal file into space obtained from + ** sqlite3_malloc() and pointed to by zSuperJournal. Also obtain + ** sufficient space (in zSuperPtr) to hold the names of super-journal + ** files extracted from regular rollback-journals. + */ + rc = sqlite3OsFileSize(pSuper, &nSuperJournal); + if( rc!=SQLITE_OK ) goto delsuper_out; + nSuperPtr = pVfs->mxPathname+1; + zFree = sqlite3Malloc(4 + nSuperJournal + nSuperPtr + 2); + if( !zFree ){ + rc = SQLITE_NOMEM_BKPT; + goto delsuper_out; + } + zFree[0] = zFree[1] = zFree[2] = zFree[3] = 0; + zSuperJournal = &zFree[4]; + zSuperPtr = &zSuperJournal[nSuperJournal+2]; + rc = sqlite3OsRead(pSuper, zSuperJournal, (int)nSuperJournal, 0); + if( rc!=SQLITE_OK ) goto delsuper_out; + zSuperJournal[nSuperJournal] = 0; + zSuperJournal[nSuperJournal+1] = 0; + + zJournal = zSuperJournal; + while( (zJournal-zSuperJournal)<nSuperJournal ){ + int exists; + rc = sqlite3OsAccess(pVfs, zJournal, SQLITE_ACCESS_EXISTS, &exists); + if( rc!=SQLITE_OK ){ + goto delsuper_out; + } + if( exists ){ + /* One of the journals pointed to by the super-journal exists. + ** Open it and check if it points at the super-journal. If + ** so, return without deleting the super-journal file. + ** NB: zJournal is really a MAIN_JOURNAL. But call it a + ** SUPER_JOURNAL here so that the VFS will not send the zJournal + ** name into sqlite3_database_file_object(). + */ + int c; + int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_SUPER_JOURNAL); + rc = sqlite3OsOpen(pVfs, zJournal, pJournal, flags, 0); + if( rc!=SQLITE_OK ){ + goto delsuper_out; + } + + rc = readSuperJournal(pJournal, zSuperPtr, nSuperPtr); + sqlite3OsClose(pJournal); + if( rc!=SQLITE_OK ){ + goto delsuper_out; + } + + c = zSuperPtr[0]!=0 && strcmp(zSuperPtr, zSuper)==0; + if( c ){ + /* We have a match. Do not delete the super-journal file. */ + goto delsuper_out; + } + } + zJournal += (sqlite3Strlen30(zJournal)+1); + } + + sqlite3OsClose(pSuper); + rc = sqlite3OsDelete(pVfs, zSuper, 0); + +delsuper_out: + sqlite3_free(zFree); + if( pSuper ){ + sqlite3OsClose(pSuper); + assert( !isOpen(pJournal) ); + sqlite3_free(pSuper); + } + return rc; +} + + +/* +** This function is used to change the actual size of the database +** file in the file-system. This only happens when committing a transaction, +** or rolling back a transaction (including rolling back a hot-journal). +** +** If the main database file is not open, or the pager is not in either +** DBMOD or OPEN state, this function is a no-op. Otherwise, the size +** of the file is changed to nPage pages (nPage*pPager->pageSize bytes). +** If the file on disk is currently larger than nPage pages, then use the VFS +** xTruncate() method to truncate it. +** +** Or, it might be the case that the file on disk is smaller than +** nPage pages. Some operating system implementations can get confused if +** you try to truncate a file to some size that is larger than it +** currently is, so detect this case and write a single zero byte to +** the end of the new file instead. +** +** If successful, return SQLITE_OK. If an IO error occurs while modifying +** the database file, return the error code to the caller. +*/ +static int pager_truncate(Pager *pPager, Pgno nPage){ + int rc = SQLITE_OK; + assert( pPager->eState!=PAGER_ERROR ); + assert( pPager->eState!=PAGER_READER ); + PAGERTRACE(("Truncate %d npage %u\n", PAGERID(pPager), nPage)); + + + if( isOpen(pPager->fd) + && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) + ){ + i64 currentSize, newSize; + int szPage = pPager->pageSize; + assert( pPager->eLock==EXCLUSIVE_LOCK ); + /* TODO: Is it safe to use Pager.dbFileSize here? */ + rc = sqlite3OsFileSize(pPager->fd, &currentSize); + newSize = szPage*(i64)nPage; + if( rc==SQLITE_OK && currentSize!=newSize ){ + if( currentSize>newSize ){ + rc = sqlite3OsTruncate(pPager->fd, newSize); + }else if( (currentSize+szPage)<=newSize ){ + char *pTmp = pPager->pTmpSpace; + memset(pTmp, 0, szPage); + testcase( (newSize-szPage) == currentSize ); + testcase( (newSize-szPage) > currentSize ); + sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &newSize); + rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, newSize-szPage); + } + if( rc==SQLITE_OK ){ + pPager->dbFileSize = nPage; + } + } + } + return rc; +} + +/* +** Return a sanitized version of the sector-size of OS file pFile. The +** return value is guaranteed to lie between 32 and MAX_SECTOR_SIZE. +*/ +SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *pFile){ + int iRet = sqlite3OsSectorSize(pFile); + if( iRet<32 ){ + iRet = 512; + }else if( iRet>MAX_SECTOR_SIZE ){ + assert( MAX_SECTOR_SIZE>=512 ); + iRet = MAX_SECTOR_SIZE; + } + return iRet; +} + +/* +** Set the value of the Pager.sectorSize variable for the given +** pager based on the value returned by the xSectorSize method +** of the open database file. The sector size will be used +** to determine the size and alignment of journal header and +** super-journal pointers within created journal files. +** +** For temporary files the effective sector size is always 512 bytes. +** +** Otherwise, for non-temporary files, the effective sector size is +** the value returned by the xSectorSize() method rounded up to 32 if +** it is less than 32, or rounded down to MAX_SECTOR_SIZE if it +** is greater than MAX_SECTOR_SIZE. +** +** If the file has the SQLITE_IOCAP_POWERSAFE_OVERWRITE property, then set +** the effective sector size to its minimum value (512). The purpose of +** pPager->sectorSize is to define the "blast radius" of bytes that +** might change if a crash occurs while writing to a single byte in +** that range. But with POWERSAFE_OVERWRITE, the blast radius is zero +** (that is what POWERSAFE_OVERWRITE means), so we minimize the sector +** size. For backwards compatibility of the rollback journal file format, +** we cannot reduce the effective sector size below 512. +*/ +static void setSectorSize(Pager *pPager){ + assert( isOpen(pPager->fd) || pPager->tempFile ); + + if( pPager->tempFile + || (sqlite3OsDeviceCharacteristics(pPager->fd) & + SQLITE_IOCAP_POWERSAFE_OVERWRITE)!=0 + ){ + /* Sector size doesn't matter for temporary files. Also, the file + ** may not have been opened yet, in which case the OsSectorSize() + ** call will segfault. */ + pPager->sectorSize = 512; + }else{ + pPager->sectorSize = sqlite3SectorSize(pPager->fd); + } +} + +/* +** Playback the journal and thus restore the database file to +** the state it was in before we started making changes. +** +** The journal file format is as follows: +** +** (1) 8 byte prefix. A copy of aJournalMagic[]. +** (2) 4 byte big-endian integer which is the number of valid page records +** in the journal. If this value is 0xffffffff, then compute the +** number of page records from the journal size. +** (3) 4 byte big-endian integer which is the initial value for the +** sanity checksum. +** (4) 4 byte integer which is the number of pages to truncate the +** database to during a rollback. +** (5) 4 byte big-endian integer which is the sector size. The header +** is this many bytes in size. +** (6) 4 byte big-endian integer which is the page size. +** (7) zero padding out to the next sector size. +** (8) Zero or more pages instances, each as follows: +** + 4 byte page number. +** + pPager->pageSize bytes of data. +** + 4 byte checksum +** +** When we speak of the journal header, we mean the first 7 items above. +** Each entry in the journal is an instance of the 8th item. +** +** Call the value from the second bullet "nRec". nRec is the number of +** valid page entries in the journal. In most cases, you can compute the +** value of nRec from the size of the journal file. But if a power +** failure occurred while the journal was being written, it could be the +** case that the size of the journal file had already been increased but +** the extra entries had not yet made it safely to disk. In such a case, +** the value of nRec computed from the file size would be too large. For +** that reason, we always use the nRec value in the header. +** +** If the nRec value is 0xffffffff it means that nRec should be computed +** from the file size. This value is used when the user selects the +** no-sync option for the journal. A power failure could lead to corruption +** in this case. But for things like temporary table (which will be +** deleted when the power is restored) we don't care. +** +** If the file opened as the journal file is not a well-formed +** journal file then all pages up to the first corrupted page are rolled +** back (or no pages if the journal header is corrupted). The journal file +** is then deleted and SQLITE_OK returned, just as if no corruption had +** been encountered. +** +** If an I/O or malloc() error occurs, the journal-file is not deleted +** and an error code is returned. +** +** The isHot parameter indicates that we are trying to rollback a journal +** that might be a hot journal. Or, it could be that the journal is +** preserved because of JOURNALMODE_PERSIST or JOURNALMODE_TRUNCATE. +** If the journal really is hot, reset the pager cache prior rolling +** back any content. If the journal is merely persistent, no reset is +** needed. +*/ +static int pager_playback(Pager *pPager, int isHot){ + sqlite3_vfs *pVfs = pPager->pVfs; + i64 szJ; /* Size of the journal file in bytes */ + u32 nRec; /* Number of Records in the journal */ + u32 u; /* Unsigned loop counter */ + Pgno mxPg = 0; /* Size of the original file in pages */ + int rc; /* Result code of a subroutine */ + int res = 1; /* Value returned by sqlite3OsAccess() */ + char *zSuper = 0; /* Name of super-journal file if any */ + int needPagerReset; /* True to reset page prior to first page rollback */ + int nPlayback = 0; /* Total number of pages restored from journal */ + u32 savedPageSize = pPager->pageSize; + + /* Figure out how many records are in the journal. Abort early if + ** the journal is empty. + */ + assert( isOpen(pPager->jfd) ); + rc = sqlite3OsFileSize(pPager->jfd, &szJ); + if( rc!=SQLITE_OK ){ + goto end_playback; + } + + /* Read the super-journal name from the journal, if it is present. + ** If a super-journal file name is specified, but the file is not + ** present on disk, then the journal is not hot and does not need to be + ** played back. + ** + ** TODO: Technically the following is an error because it assumes that + ** buffer Pager.pTmpSpace is (mxPathname+1) bytes or larger. i.e. that + ** (pPager->pageSize >= pPager->pVfs->mxPathname+1). Using os_unix.c, + ** mxPathname is 512, which is the same as the minimum allowable value + ** for pageSize. + */ + zSuper = pPager->pTmpSpace; + rc = readSuperJournal(pPager->jfd, zSuper, pPager->pVfs->mxPathname+1); + if( rc==SQLITE_OK && zSuper[0] ){ + rc = sqlite3OsAccess(pVfs, zSuper, SQLITE_ACCESS_EXISTS, &res); + } + zSuper = 0; + if( rc!=SQLITE_OK || !res ){ + goto end_playback; + } + pPager->journalOff = 0; + needPagerReset = isHot; + + /* This loop terminates either when a readJournalHdr() or + ** pager_playback_one_page() call returns SQLITE_DONE or an IO error + ** occurs. + */ + while( 1 ){ + /* Read the next journal header from the journal file. If there are + ** not enough bytes left in the journal file for a complete header, or + ** it is corrupted, then a process must have failed while writing it. + ** This indicates nothing more needs to be rolled back. + */ + rc = readJournalHdr(pPager, isHot, szJ, &nRec, &mxPg); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + } + goto end_playback; + } + + /* If nRec is 0xffffffff, then this journal was created by a process + ** working in no-sync mode. This means that the rest of the journal + ** file consists of pages, there are no more journal headers. Compute + ** the value of nRec based on this assumption. + */ + if( nRec==0xffffffff ){ + assert( pPager->journalOff==JOURNAL_HDR_SZ(pPager) ); + nRec = (int)((szJ - JOURNAL_HDR_SZ(pPager))/JOURNAL_PG_SZ(pPager)); + } + + /* If nRec is 0 and this rollback is of a transaction created by this + ** process and if this is the final header in the journal, then it means + ** that this part of the journal was being filled but has not yet been + ** synced to disk. Compute the number of pages based on the remaining + ** size of the file. + ** + ** The third term of the test was added to fix ticket #2565. + ** When rolling back a hot journal, nRec==0 always means that the next + ** chunk of the journal contains zero pages to be rolled back. But + ** when doing a ROLLBACK and the nRec==0 chunk is the last chunk in + ** the journal, it means that the journal might contain additional + ** pages that need to be rolled back and that the number of pages + ** should be computed based on the journal file size. + */ + if( nRec==0 && !isHot && + pPager->journalHdr+JOURNAL_HDR_SZ(pPager)==pPager->journalOff ){ + nRec = (int)((szJ - pPager->journalOff) / JOURNAL_PG_SZ(pPager)); + } + + /* If this is the first header read from the journal, truncate the + ** database file back to its original size. + */ + if( pPager->journalOff==JOURNAL_HDR_SZ(pPager) ){ + rc = pager_truncate(pPager, mxPg); + if( rc!=SQLITE_OK ){ + goto end_playback; + } + pPager->dbSize = mxPg; + if( pPager->mxPgno<mxPg ){ + pPager->mxPgno = mxPg; + } + } + + /* Copy original pages out of the journal and back into the + ** database file and/or page cache. + */ + for(u=0; u<nRec; u++){ + if( needPagerReset ){ + pager_reset(pPager); + needPagerReset = 0; + } + rc = pager_playback_one_page(pPager,&pPager->journalOff,0,1,0); + if( rc==SQLITE_OK ){ + nPlayback++; + }else{ + if( rc==SQLITE_DONE ){ + pPager->journalOff = szJ; + break; + }else if( rc==SQLITE_IOERR_SHORT_READ ){ + /* If the journal has been truncated, simply stop reading and + ** processing the journal. This might happen if the journal was + ** not completely written and synced prior to a crash. In that + ** case, the database should have never been written in the + ** first place so it is OK to simply abandon the rollback. */ + rc = SQLITE_OK; + goto end_playback; + }else{ + /* If we are unable to rollback, quit and return the error + ** code. This will cause the pager to enter the error state + ** so that no further harm will be done. Perhaps the next + ** process to come along will be able to rollback the database. + */ + goto end_playback; + } + } + } + } + /*NOTREACHED*/ + assert( 0 ); + +end_playback: + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSetPagesize(pPager, &savedPageSize, -1); + } + /* Following a rollback, the database file should be back in its original + ** state prior to the start of the transaction, so invoke the + ** SQLITE_FCNTL_DB_UNCHANGED file-control method to disable the + ** assertion that the transaction counter was modified. + */ +#ifdef SQLITE_DEBUG + sqlite3OsFileControlHint(pPager->fd,SQLITE_FCNTL_DB_UNCHANGED,0); +#endif + + /* If this playback is happening automatically as a result of an IO or + ** malloc error that occurred after the change-counter was updated but + ** before the transaction was committed, then the change-counter + ** modification may just have been reverted. If this happens in exclusive + ** mode, then subsequent transactions performed by the connection will not + ** update the change-counter at all. This may lead to cache inconsistency + ** problems for other processes at some point in the future. So, just + ** in case this has happened, clear the changeCountDone flag now. + */ + pPager->changeCountDone = pPager->tempFile; + + if( rc==SQLITE_OK ){ + /* Leave 4 bytes of space before the super-journal filename in memory. + ** This is because it may end up being passed to sqlite3OsOpen(), in + ** which case it requires 4 0x00 bytes in memory immediately before + ** the filename. */ + zSuper = &pPager->pTmpSpace[4]; + rc = readSuperJournal(pPager->jfd, zSuper, pPager->pVfs->mxPathname+1); + testcase( rc!=SQLITE_OK ); + } + if( rc==SQLITE_OK + && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) + ){ + rc = sqlite3PagerSync(pPager, 0); + } + if( rc==SQLITE_OK ){ + rc = pager_end_transaction(pPager, zSuper[0]!='\0', 0); + testcase( rc!=SQLITE_OK ); + } + if( rc==SQLITE_OK && zSuper[0] && res ){ + /* If there was a super-journal and this routine will return success, + ** see if it is possible to delete the super-journal. + */ + assert( zSuper==&pPager->pTmpSpace[4] ); + memset(pPager->pTmpSpace, 0, 4); + rc = pager_delsuper(pPager, zSuper); + testcase( rc!=SQLITE_OK ); + } + if( isHot && nPlayback ){ + sqlite3_log(SQLITE_NOTICE_RECOVER_ROLLBACK, "recovered %d pages from %s", + nPlayback, pPager->zJournal); + } + + /* The Pager.sectorSize variable may have been updated while rolling + ** back a journal created by a process with a different sector size + ** value. Reset it to the correct value for this process. + */ + setSectorSize(pPager); + return rc; +} + + +/* +** Read the content for page pPg out of the database file (or out of +** the WAL if that is where the most recent copy if found) into +** pPg->pData. A shared lock or greater must be held on the database +** file before this function is called. +** +** If page 1 is read, then the value of Pager.dbFileVers[] is set to +** the value read from the database file. +** +** If an IO error occurs, then the IO error is returned to the caller. +** Otherwise, SQLITE_OK is returned. +*/ +static int readDbPage(PgHdr *pPg){ + Pager *pPager = pPg->pPager; /* Pager object associated with page pPg */ + int rc = SQLITE_OK; /* Return code */ + +#ifndef SQLITE_OMIT_WAL + u32 iFrame = 0; /* Frame of WAL containing pgno */ + + assert( pPager->eState>=PAGER_READER && !MEMDB ); + assert( isOpen(pPager->fd) ); + + if( pagerUseWal(pPager) ){ + rc = sqlite3WalFindFrame(pPager->pWal, pPg->pgno, &iFrame); + if( rc ) return rc; + } + if( iFrame ){ + rc = sqlite3WalReadFrame(pPager->pWal, iFrame,pPager->pageSize,pPg->pData); + }else +#endif + { + i64 iOffset = (pPg->pgno-1)*(i64)pPager->pageSize; + rc = sqlite3OsRead(pPager->fd, pPg->pData, pPager->pageSize, iOffset); + if( rc==SQLITE_IOERR_SHORT_READ ){ + rc = SQLITE_OK; + } + } + + if( pPg->pgno==1 ){ + if( rc ){ + /* If the read is unsuccessful, set the dbFileVers[] to something + ** that will never be a valid file version. dbFileVers[] is a copy + ** of bytes 24..39 of the database. Bytes 28..31 should always be + ** zero or the size of the database in page. Bytes 32..35 and 35..39 + ** should be page numbers which are never 0xffffffff. So filling + ** pPager->dbFileVers[] with all 0xff bytes should suffice. + ** + ** For an encrypted database, the situation is more complex: bytes + ** 24..39 of the database are white noise. But the probability of + ** white noise equaling 16 bytes of 0xff is vanishingly small so + ** we should still be ok. + */ + memset(pPager->dbFileVers, 0xff, sizeof(pPager->dbFileVers)); + }else{ + u8 *dbFileVers = &((u8*)pPg->pData)[24]; + memcpy(&pPager->dbFileVers, dbFileVers, sizeof(pPager->dbFileVers)); + } + } + PAGER_INCR(sqlite3_pager_readdb_count); + PAGER_INCR(pPager->nRead); + IOTRACE(("PGIN %p %d\n", pPager, pPg->pgno)); + PAGERTRACE(("FETCH %d page %d hash(%08x)\n", + PAGERID(pPager), pPg->pgno, pager_pagehash(pPg))); + + return rc; +} + +/* +** Update the value of the change-counter at offsets 24 and 92 in +** the header and the sqlite version number at offset 96. +** +** This is an unconditional update. See also the pager_incr_changecounter() +** routine which only updates the change-counter if the update is actually +** needed, as determined by the pPager->changeCountDone state variable. +*/ +static void pager_write_changecounter(PgHdr *pPg){ + u32 change_counter; + if( NEVER(pPg==0) ) return; + + /* Increment the value just read and write it back to byte 24. */ + change_counter = sqlite3Get4byte((u8*)pPg->pPager->dbFileVers)+1; + put32bits(((char*)pPg->pData)+24, change_counter); + + /* Also store the SQLite version number in bytes 96..99 and in + ** bytes 92..95 store the change counter for which the version number + ** is valid. */ + put32bits(((char*)pPg->pData)+92, change_counter); + put32bits(((char*)pPg->pData)+96, SQLITE_VERSION_NUMBER); +} + +#ifndef SQLITE_OMIT_WAL +/* +** This function is invoked once for each page that has already been +** written into the log file when a WAL transaction is rolled back. +** Parameter iPg is the page number of said page. The pCtx argument +** is actually a pointer to the Pager structure. +** +** If page iPg is present in the cache, and has no outstanding references, +** it is discarded. Otherwise, if there are one or more outstanding +** references, the page content is reloaded from the database. If the +** attempt to reload content from the database is required and fails, +** return an SQLite error code. Otherwise, SQLITE_OK. +*/ +static int pagerUndoCallback(void *pCtx, Pgno iPg){ + int rc = SQLITE_OK; + Pager *pPager = (Pager *)pCtx; + PgHdr *pPg; + + assert( pagerUseWal(pPager) ); + pPg = sqlite3PagerLookup(pPager, iPg); + if( pPg ){ + if( sqlite3PcachePageRefcount(pPg)==1 ){ + sqlite3PcacheDrop(pPg); + }else{ + rc = readDbPage(pPg); + if( rc==SQLITE_OK ){ + pPager->xReiniter(pPg); + } + sqlite3PagerUnrefNotNull(pPg); + } + } + + /* Normally, if a transaction is rolled back, any backup processes are + ** updated as data is copied out of the rollback journal and into the + ** database. This is not generally possible with a WAL database, as + ** rollback involves simply truncating the log file. Therefore, if one + ** or more frames have already been written to the log (and therefore + ** also copied into the backup databases) as part of this transaction, + ** the backups must be restarted. + */ + sqlite3BackupRestart(pPager->pBackup); + + return rc; +} + +/* +** This function is called to rollback a transaction on a WAL database. +*/ +static int pagerRollbackWal(Pager *pPager){ + int rc; /* Return Code */ + PgHdr *pList; /* List of dirty pages to revert */ + + /* For all pages in the cache that are currently dirty or have already + ** been written (but not committed) to the log file, do one of the + ** following: + ** + ** + Discard the cached page (if refcount==0), or + ** + Reload page content from the database (if refcount>0). + */ + pPager->dbSize = pPager->dbOrigSize; + rc = sqlite3WalUndo(pPager->pWal, pagerUndoCallback, (void *)pPager); + pList = sqlite3PcacheDirtyList(pPager->pPCache); + while( pList && rc==SQLITE_OK ){ + PgHdr *pNext = pList->pDirty; + rc = pagerUndoCallback((void *)pPager, pList->pgno); + pList = pNext; + } + + return rc; +} + +/* +** This function is a wrapper around sqlite3WalFrames(). As well as logging +** the contents of the list of pages headed by pList (connected by pDirty), +** this function notifies any active backup processes that the pages have +** changed. +** +** The list of pages passed into this routine is always sorted by page number. +** Hence, if page 1 appears anywhere on the list, it will be the first page. +*/ +static int pagerWalFrames( + Pager *pPager, /* Pager object */ + PgHdr *pList, /* List of frames to log */ + Pgno nTruncate, /* Database size after this commit */ + int isCommit /* True if this is a commit */ +){ + int rc; /* Return code */ + int nList; /* Number of pages in pList */ + PgHdr *p; /* For looping over pages */ + + assert( pPager->pWal ); + assert( pList ); +#ifdef SQLITE_DEBUG + /* Verify that the page list is in ascending order */ + for(p=pList; p && p->pDirty; p=p->pDirty){ + assert( p->pgno < p->pDirty->pgno ); + } +#endif + + assert( pList->pDirty==0 || isCommit ); + if( isCommit ){ + /* If a WAL transaction is being committed, there is no point in writing + ** any pages with page numbers greater than nTruncate into the WAL file. + ** They will never be read by any client. So remove them from the pDirty + ** list here. */ + PgHdr **ppNext = &pList; + nList = 0; + for(p=pList; (*ppNext = p)!=0; p=p->pDirty){ + if( p->pgno<=nTruncate ){ + ppNext = &p->pDirty; + nList++; + } + } + assert( pList ); + }else{ + nList = 1; + } + pPager->aStat[PAGER_STAT_WRITE] += nList; + + if( pList->pgno==1 ) pager_write_changecounter(pList); + rc = sqlite3WalFrames(pPager->pWal, + pPager->pageSize, pList, nTruncate, isCommit, pPager->walSyncFlags + ); + if( rc==SQLITE_OK && pPager->pBackup ){ + for(p=pList; p; p=p->pDirty){ + sqlite3BackupUpdate(pPager->pBackup, p->pgno, (u8 *)p->pData); + } + } + +#ifdef SQLITE_CHECK_PAGES + pList = sqlite3PcacheDirtyList(pPager->pPCache); + for(p=pList; p; p=p->pDirty){ + pager_set_pagehash(p); + } +#endif + + return rc; +} + +/* +** Begin a read transaction on the WAL. +** +** This routine used to be called "pagerOpenSnapshot()" because it essentially +** makes a snapshot of the database at the current point in time and preserves +** that snapshot for use by the reader in spite of concurrently changes by +** other writers or checkpointers. +*/ +static int pagerBeginReadTransaction(Pager *pPager){ + int rc; /* Return code */ + int changed = 0; /* True if cache must be reset */ + + assert( pagerUseWal(pPager) ); + assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER ); + + /* sqlite3WalEndReadTransaction() was not called for the previous + ** transaction in locking_mode=EXCLUSIVE. So call it now. If we + ** are in locking_mode=NORMAL and EndRead() was previously called, + ** the duplicate call is harmless. + */ + sqlite3WalEndReadTransaction(pPager->pWal); + + rc = sqlite3WalBeginReadTransaction(pPager->pWal, &changed); + if( rc!=SQLITE_OK || changed ){ + pager_reset(pPager); + if( USEFETCH(pPager) ) sqlite3OsUnfetch(pPager->fd, 0, 0); + } + + return rc; +} +#endif + +/* +** This function is called as part of the transition from PAGER_OPEN +** to PAGER_READER state to determine the size of the database file +** in pages (assuming the page size currently stored in Pager.pageSize). +** +** If no error occurs, SQLITE_OK is returned and the size of the database +** in pages is stored in *pnPage. Otherwise, an error code (perhaps +** SQLITE_IOERR_FSTAT) is returned and *pnPage is left unmodified. +*/ +static int pagerPagecount(Pager *pPager, Pgno *pnPage){ + Pgno nPage; /* Value to return via *pnPage */ + + /* Query the WAL sub-system for the database size. The WalDbsize() + ** function returns zero if the WAL is not open (i.e. Pager.pWal==0), or + ** if the database size is not available. The database size is not + ** available from the WAL sub-system if the log file is empty or + ** contains no valid committed transactions. + */ + assert( pPager->eState==PAGER_OPEN ); + assert( pPager->eLock>=SHARED_LOCK ); + assert( isOpen(pPager->fd) ); + assert( pPager->tempFile==0 ); + nPage = sqlite3WalDbsize(pPager->pWal); + + /* If the number of pages in the database is not available from the + ** WAL sub-system, determine the page count based on the size of + ** the database file. If the size of the database file is not an + ** integer multiple of the page-size, round up the result. + */ + if( nPage==0 && ALWAYS(isOpen(pPager->fd)) ){ + i64 n = 0; /* Size of db file in bytes */ + int rc = sqlite3OsFileSize(pPager->fd, &n); + if( rc!=SQLITE_OK ){ + return rc; + } + nPage = (Pgno)((n+pPager->pageSize-1) / pPager->pageSize); + } + + /* If the current number of pages in the file is greater than the + ** configured maximum pager number, increase the allowed limit so + ** that the file can be read. + */ + if( nPage>pPager->mxPgno ){ + pPager->mxPgno = (Pgno)nPage; + } + + *pnPage = nPage; + return SQLITE_OK; +} + +#ifndef SQLITE_OMIT_WAL +/* +** Check if the *-wal file that corresponds to the database opened by pPager +** exists if the database is not empty, or verify that the *-wal file does +** not exist (by deleting it) if the database file is empty. +** +** If the database is not empty and the *-wal file exists, open the pager +** in WAL mode. If the database is empty or if no *-wal file exists and +** if no error occurs, make sure Pager.journalMode is not set to +** PAGER_JOURNALMODE_WAL. +** +** Return SQLITE_OK or an error code. +** +** The caller must hold a SHARED lock on the database file to call this +** function. Because an EXCLUSIVE lock on the db file is required to delete +** a WAL on a none-empty database, this ensures there is no race condition +** between the xAccess() below and an xDelete() being executed by some +** other connection. +*/ +static int pagerOpenWalIfPresent(Pager *pPager){ + int rc = SQLITE_OK; + assert( pPager->eState==PAGER_OPEN ); + assert( pPager->eLock>=SHARED_LOCK ); + + if( !pPager->tempFile ){ + int isWal; /* True if WAL file exists */ + rc = sqlite3OsAccess( + pPager->pVfs, pPager->zWal, SQLITE_ACCESS_EXISTS, &isWal + ); + if( rc==SQLITE_OK ){ + if( isWal ){ + Pgno nPage; /* Size of the database file */ + + rc = pagerPagecount(pPager, &nPage); + if( rc ) return rc; + if( nPage==0 ){ + rc = sqlite3OsDelete(pPager->pVfs, pPager->zWal, 0); + }else{ + testcase( sqlite3PcachePagecount(pPager->pPCache)==0 ); + rc = sqlite3PagerOpenWal(pPager, 0); + } + }else if( pPager->journalMode==PAGER_JOURNALMODE_WAL ){ + pPager->journalMode = PAGER_JOURNALMODE_DELETE; + } + } + } + return rc; +} +#endif + +/* +** Playback savepoint pSavepoint. Or, if pSavepoint==NULL, then playback +** the entire super-journal file. The case pSavepoint==NULL occurs when +** a ROLLBACK TO command is invoked on a SAVEPOINT that is a transaction +** savepoint. +** +** When pSavepoint is not NULL (meaning a non-transaction savepoint is +** being rolled back), then the rollback consists of up to three stages, +** performed in the order specified: +** +** * Pages are played back from the main journal starting at byte +** offset PagerSavepoint.iOffset and continuing to +** PagerSavepoint.iHdrOffset, or to the end of the main journal +** file if PagerSavepoint.iHdrOffset is zero. +** +** * If PagerSavepoint.iHdrOffset is not zero, then pages are played +** back starting from the journal header immediately following +** PagerSavepoint.iHdrOffset to the end of the main journal file. +** +** * Pages are then played back from the sub-journal file, starting +** with the PagerSavepoint.iSubRec and continuing to the end of +** the journal file. +** +** Throughout the rollback process, each time a page is rolled back, the +** corresponding bit is set in a bitvec structure (variable pDone in the +** implementation below). This is used to ensure that a page is only +** rolled back the first time it is encountered in either journal. +** +** If pSavepoint is NULL, then pages are only played back from the main +** journal file. There is no need for a bitvec in this case. +** +** In either case, before playback commences the Pager.dbSize variable +** is reset to the value that it held at the start of the savepoint +** (or transaction). No page with a page-number greater than this value +** is played back. If one is encountered it is simply skipped. +*/ +static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){ + i64 szJ; /* Effective size of the main journal */ + i64 iHdrOff; /* End of first segment of main-journal records */ + int rc = SQLITE_OK; /* Return code */ + Bitvec *pDone = 0; /* Bitvec to ensure pages played back only once */ + + assert( pPager->eState!=PAGER_ERROR ); + assert( pPager->eState>=PAGER_WRITER_LOCKED ); + + /* Allocate a bitvec to use to store the set of pages rolled back */ + if( pSavepoint ){ + pDone = sqlite3BitvecCreate(pSavepoint->nOrig); + if( !pDone ){ + return SQLITE_NOMEM_BKPT; + } + } + + /* Set the database size back to the value it was before the savepoint + ** being reverted was opened. + */ + pPager->dbSize = pSavepoint ? pSavepoint->nOrig : pPager->dbOrigSize; + pPager->changeCountDone = pPager->tempFile; + + if( !pSavepoint && pagerUseWal(pPager) ){ + return pagerRollbackWal(pPager); + } + + /* Use pPager->journalOff as the effective size of the main rollback + ** journal. The actual file might be larger than this in + ** PAGER_JOURNALMODE_TRUNCATE or PAGER_JOURNALMODE_PERSIST. But anything + ** past pPager->journalOff is off-limits to us. + */ + szJ = pPager->journalOff; + assert( pagerUseWal(pPager)==0 || szJ==0 ); + + /* Begin by rolling back records from the main journal starting at + ** PagerSavepoint.iOffset and continuing to the next journal header. + ** There might be records in the main journal that have a page number + ** greater than the current database size (pPager->dbSize) but those + ** will be skipped automatically. Pages are added to pDone as they + ** are played back. + */ + if( pSavepoint && !pagerUseWal(pPager) ){ + iHdrOff = pSavepoint->iHdrOffset ? pSavepoint->iHdrOffset : szJ; + pPager->journalOff = pSavepoint->iOffset; + while( rc==SQLITE_OK && pPager->journalOff<iHdrOff ){ + rc = pager_playback_one_page(pPager, &pPager->journalOff, pDone, 1, 1); + } + assert( rc!=SQLITE_DONE ); + }else{ + pPager->journalOff = 0; + } + + /* Continue rolling back records out of the main journal starting at + ** the first journal header seen and continuing until the effective end + ** of the main journal file. Continue to skip out-of-range pages and + ** continue adding pages rolled back to pDone. + */ + while( rc==SQLITE_OK && pPager->journalOff<szJ ){ + u32 ii; /* Loop counter */ + u32 nJRec = 0; /* Number of Journal Records */ + u32 dummy; + rc = readJournalHdr(pPager, 0, szJ, &nJRec, &dummy); + assert( rc!=SQLITE_DONE ); + + /* + ** The "pPager->journalHdr+JOURNAL_HDR_SZ(pPager)==pPager->journalOff" + ** test is related to ticket #2565. See the discussion in the + ** pager_playback() function for additional information. + */ + if( nJRec==0 + && pPager->journalHdr+JOURNAL_HDR_SZ(pPager)==pPager->journalOff + ){ + nJRec = (u32)((szJ - pPager->journalOff)/JOURNAL_PG_SZ(pPager)); + } + for(ii=0; rc==SQLITE_OK && ii<nJRec && pPager->journalOff<szJ; ii++){ + rc = pager_playback_one_page(pPager, &pPager->journalOff, pDone, 1, 1); + } + assert( rc!=SQLITE_DONE ); + } + assert( rc!=SQLITE_OK || pPager->journalOff>=szJ ); + + /* Finally, rollback pages from the sub-journal. Page that were + ** previously rolled back out of the main journal (and are hence in pDone) + ** will be skipped. Out-of-range pages are also skipped. + */ + if( pSavepoint ){ + u32 ii; /* Loop counter */ + i64 offset = (i64)pSavepoint->iSubRec*(4+pPager->pageSize); + + if( pagerUseWal(pPager) ){ + rc = sqlite3WalSavepointUndo(pPager->pWal, pSavepoint->aWalData); + } + for(ii=pSavepoint->iSubRec; rc==SQLITE_OK && ii<pPager->nSubRec; ii++){ + assert( offset==(i64)ii*(4+pPager->pageSize) ); + rc = pager_playback_one_page(pPager, &offset, pDone, 0, 1); + } + assert( rc!=SQLITE_DONE ); + } + + sqlite3BitvecDestroy(pDone); + if( rc==SQLITE_OK ){ + pPager->journalOff = szJ; + } + + return rc; +} + +/* +** Change the maximum number of in-memory pages that are allowed +** before attempting to recycle clean and unused pages. +*/ +SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager *pPager, int mxPage){ + sqlite3PcacheSetCachesize(pPager->pPCache, mxPage); +} + +/* +** Change the maximum number of in-memory pages that are allowed +** before attempting to spill pages to journal. +*/ +SQLITE_PRIVATE int sqlite3PagerSetSpillsize(Pager *pPager, int mxPage){ + return sqlite3PcacheSetSpillsize(pPager->pPCache, mxPage); +} + +/* +** Invoke SQLITE_FCNTL_MMAP_SIZE based on the current value of szMmap. +*/ +static void pagerFixMaplimit(Pager *pPager){ +#if SQLITE_MAX_MMAP_SIZE>0 + sqlite3_file *fd = pPager->fd; + if( isOpen(fd) && fd->pMethods->iVersion>=3 ){ + sqlite3_int64 sz; + sz = pPager->szMmap; + pPager->bUseFetch = (sz>0); + setGetterMethod(pPager); + sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_MMAP_SIZE, &sz); + } +#endif +} + +/* +** Change the maximum size of any memory mapping made of the database file. +*/ +SQLITE_PRIVATE void sqlite3PagerSetMmapLimit(Pager *pPager, sqlite3_int64 szMmap){ + pPager->szMmap = szMmap; + pagerFixMaplimit(pPager); +} + +/* +** Free as much memory as possible from the pager. +*/ +SQLITE_PRIVATE void sqlite3PagerShrink(Pager *pPager){ + sqlite3PcacheShrink(pPager->pPCache); +} + +/* +** Adjust settings of the pager to those specified in the pgFlags parameter. +** +** The "level" in pgFlags & PAGER_SYNCHRONOUS_MASK sets the robustness +** of the database to damage due to OS crashes or power failures by +** changing the number of syncs()s when writing the journals. +** There are four levels: +** +** OFF sqlite3OsSync() is never called. This is the default +** for temporary and transient files. +** +** NORMAL The journal is synced once before writes begin on the +** database. This is normally adequate protection, but +** it is theoretically possible, though very unlikely, +** that an inopertune power failure could leave the journal +** in a state which would cause damage to the database +** when it is rolled back. +** +** FULL The journal is synced twice before writes begin on the +** database (with some additional information - the nRec field +** of the journal header - being written in between the two +** syncs). If we assume that writing a +** single disk sector is atomic, then this mode provides +** assurance that the journal will not be corrupted to the +** point of causing damage to the database during rollback. +** +** EXTRA This is like FULL except that is also syncs the directory +** that contains the rollback journal after the rollback +** journal is unlinked. +** +** The above is for a rollback-journal mode. For WAL mode, OFF continues +** to mean that no syncs ever occur. NORMAL means that the WAL is synced +** prior to the start of checkpoint and that the database file is synced +** at the conclusion of the checkpoint if the entire content of the WAL +** was written back into the database. But no sync operations occur for +** an ordinary commit in NORMAL mode with WAL. FULL means that the WAL +** file is synced following each commit operation, in addition to the +** syncs associated with NORMAL. There is no difference between FULL +** and EXTRA for WAL mode. +** +** Do not confuse synchronous=FULL with SQLITE_SYNC_FULL. The +** SQLITE_SYNC_FULL macro means to use the MacOSX-style full-fsync +** using fcntl(F_FULLFSYNC). SQLITE_SYNC_NORMAL means to do an +** ordinary fsync() call. There is no difference between SQLITE_SYNC_FULL +** and SQLITE_SYNC_NORMAL on platforms other than MacOSX. But the +** synchronous=FULL versus synchronous=NORMAL setting determines when +** the xSync primitive is called and is relevant to all platforms. +** +** Numeric values associated with these states are OFF==1, NORMAL=2, +** and FULL=3. +*/ +SQLITE_PRIVATE void sqlite3PagerSetFlags( + Pager *pPager, /* The pager to set safety level for */ + unsigned pgFlags /* Various flags */ +){ + unsigned level = pgFlags & PAGER_SYNCHRONOUS_MASK; + if( pPager->tempFile ){ + pPager->noSync = 1; + pPager->fullSync = 0; + pPager->extraSync = 0; + }else{ + pPager->noSync = level==PAGER_SYNCHRONOUS_OFF ?1:0; + pPager->fullSync = level>=PAGER_SYNCHRONOUS_FULL ?1:0; + pPager->extraSync = level==PAGER_SYNCHRONOUS_EXTRA ?1:0; + } + if( pPager->noSync ){ + pPager->syncFlags = 0; + }else if( pgFlags & PAGER_FULLFSYNC ){ + pPager->syncFlags = SQLITE_SYNC_FULL; + }else{ + pPager->syncFlags = SQLITE_SYNC_NORMAL; + } + pPager->walSyncFlags = (pPager->syncFlags<<2); + if( pPager->fullSync ){ + pPager->walSyncFlags |= pPager->syncFlags; + } + if( (pgFlags & PAGER_CKPT_FULLFSYNC) && !pPager->noSync ){ + pPager->walSyncFlags |= (SQLITE_SYNC_FULL<<2); + } + if( pgFlags & PAGER_CACHESPILL ){ + pPager->doNotSpill &= ~SPILLFLAG_OFF; + }else{ + pPager->doNotSpill |= SPILLFLAG_OFF; + } +} + +/* +** The following global variable is incremented whenever the library +** attempts to open a temporary file. This information is used for +** testing and analysis only. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_opentemp_count = 0; +#endif + +/* +** Open a temporary file. +** +** Write the file descriptor into *pFile. Return SQLITE_OK on success +** or some other error code if we fail. The OS will automatically +** delete the temporary file when it is closed. +** +** The flags passed to the VFS layer xOpen() call are those specified +** by parameter vfsFlags ORed with the following: +** +** SQLITE_OPEN_READWRITE +** SQLITE_OPEN_CREATE +** SQLITE_OPEN_EXCLUSIVE +** SQLITE_OPEN_DELETEONCLOSE +*/ +static int pagerOpentemp( + Pager *pPager, /* The pager object */ + sqlite3_file *pFile, /* Write the file descriptor here */ + int vfsFlags /* Flags passed through to the VFS */ +){ + int rc; /* Return code */ + +#ifdef SQLITE_TEST + sqlite3_opentemp_count++; /* Used for testing and analysis only */ +#endif + + vfsFlags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | + SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE; + rc = sqlite3OsOpen(pPager->pVfs, 0, pFile, vfsFlags, 0); + assert( rc!=SQLITE_OK || isOpen(pFile) ); + return rc; +} + +/* +** Set the busy handler function. +** +** The pager invokes the busy-handler if sqlite3OsLock() returns +** SQLITE_BUSY when trying to upgrade from no-lock to a SHARED lock, +** or when trying to upgrade from a RESERVED lock to an EXCLUSIVE +** lock. It does *not* invoke the busy handler when upgrading from +** SHARED to RESERVED, or when upgrading from SHARED to EXCLUSIVE +** (which occurs during hot-journal rollback). Summary: +** +** Transition | Invokes xBusyHandler +** -------------------------------------------------------- +** NO_LOCK -> SHARED_LOCK | Yes +** SHARED_LOCK -> RESERVED_LOCK | No +** SHARED_LOCK -> EXCLUSIVE_LOCK | No +** RESERVED_LOCK -> EXCLUSIVE_LOCK | Yes +** +** If the busy-handler callback returns non-zero, the lock is +** retried. If it returns zero, then the SQLITE_BUSY error is +** returned to the caller of the pager API function. +*/ +SQLITE_PRIVATE void sqlite3PagerSetBusyHandler( + Pager *pPager, /* Pager object */ + int (*xBusyHandler)(void *), /* Pointer to busy-handler function */ + void *pBusyHandlerArg /* Argument to pass to xBusyHandler */ +){ + void **ap; + pPager->xBusyHandler = xBusyHandler; + pPager->pBusyHandlerArg = pBusyHandlerArg; + ap = (void **)&pPager->xBusyHandler; + assert( ((int(*)(void *))(ap[0]))==xBusyHandler ); + assert( ap[1]==pBusyHandlerArg ); + sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_BUSYHANDLER, (void *)ap); +} + +/* +** Change the page size used by the Pager object. The new page size +** is passed in *pPageSize. +** +** If the pager is in the error state when this function is called, it +** is a no-op. The value returned is the error state error code (i.e. +** one of SQLITE_IOERR, an SQLITE_IOERR_xxx sub-code or SQLITE_FULL). +** +** Otherwise, if all of the following are true: +** +** * the new page size (value of *pPageSize) is valid (a power +** of two between 512 and SQLITE_MAX_PAGE_SIZE, inclusive), and +** +** * there are no outstanding page references, and +** +** * the database is either not an in-memory database or it is +** an in-memory database that currently consists of zero pages. +** +** then the pager object page size is set to *pPageSize. +** +** If the page size is changed, then this function uses sqlite3PagerMalloc() +** to obtain a new Pager.pTmpSpace buffer. If this allocation attempt +** fails, SQLITE_NOMEM is returned and the page size remains unchanged. +** In all other cases, SQLITE_OK is returned. +** +** If the page size is not changed, either because one of the enumerated +** conditions above is not true, the pager was in error state when this +** function was called, or because the memory allocation attempt failed, +** then *pPageSize is set to the old, retained page size before returning. +*/ +SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nReserve){ + int rc = SQLITE_OK; + + /* It is not possible to do a full assert_pager_state() here, as this + ** function may be called from within PagerOpen(), before the state + ** of the Pager object is internally consistent. + ** + ** At one point this function returned an error if the pager was in + ** PAGER_ERROR state. But since PAGER_ERROR state guarantees that + ** there is at least one outstanding page reference, this function + ** is a no-op for that case anyhow. + */ + + u32 pageSize = *pPageSize; + assert( pageSize==0 || (pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE) ); + if( (pPager->memDb==0 || pPager->dbSize==0) + && sqlite3PcacheRefCount(pPager->pPCache)==0 + && pageSize && pageSize!=(u32)pPager->pageSize + ){ + char *pNew = NULL; /* New temp space */ + i64 nByte = 0; + + if( pPager->eState>PAGER_OPEN && isOpen(pPager->fd) ){ + rc = sqlite3OsFileSize(pPager->fd, &nByte); + } + if( rc==SQLITE_OK ){ + /* 8 bytes of zeroed overrun space is sufficient so that the b-tree + * cell header parser will never run off the end of the allocation */ + pNew = (char *)sqlite3PageMalloc(pageSize+8); + if( !pNew ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + memset(pNew+pageSize, 0, 8); + } + } + + if( rc==SQLITE_OK ){ + pager_reset(pPager); + rc = sqlite3PcacheSetPageSize(pPager->pPCache, pageSize); + } + if( rc==SQLITE_OK ){ + sqlite3PageFree(pPager->pTmpSpace); + pPager->pTmpSpace = pNew; + pPager->dbSize = (Pgno)((nByte+pageSize-1)/pageSize); + pPager->pageSize = pageSize; + pPager->lckPgno = (Pgno)(PENDING_BYTE/pageSize) + 1; + }else{ + sqlite3PageFree(pNew); + } + } + + *pPageSize = pPager->pageSize; + if( rc==SQLITE_OK ){ + if( nReserve<0 ) nReserve = pPager->nReserve; + assert( nReserve>=0 && nReserve<1000 ); + pPager->nReserve = (i16)nReserve; + pagerFixMaplimit(pPager); + } + return rc; +} + +/* +** Return a pointer to the "temporary page" buffer held internally +** by the pager. This is a buffer that is big enough to hold the +** entire content of a database page. This buffer is used internally +** during rollback and will be overwritten whenever a rollback +** occurs. But other modules are free to use it too, as long as +** no rollbacks are happening. +*/ +SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager *pPager){ + return pPager->pTmpSpace; +} + +/* +** Attempt to set the maximum database page count if mxPage is positive. +** Make no changes if mxPage is zero or negative. And never reduce the +** maximum page count below the current size of the database. +** +** Regardless of mxPage, return the current maximum page count. +*/ +SQLITE_PRIVATE Pgno sqlite3PagerMaxPageCount(Pager *pPager, Pgno mxPage){ + if( mxPage>0 ){ + pPager->mxPgno = mxPage; + } + assert( pPager->eState!=PAGER_OPEN ); /* Called only by OP_MaxPgcnt */ + /* assert( pPager->mxPgno>=pPager->dbSize ); */ + /* OP_MaxPgcnt ensures that the parameter passed to this function is not + ** less than the total number of valid pages in the database. But this + ** may be less than Pager.dbSize, and so the assert() above is not valid */ + return pPager->mxPgno; +} + +/* +** The following set of routines are used to disable the simulated +** I/O error mechanism. These routines are used to avoid simulated +** errors in places where we do not care about errors. +** +** Unless -DSQLITE_TEST=1 is used, these routines are all no-ops +** and generate no code. +*/ +#ifdef SQLITE_TEST +SQLITE_API extern int sqlite3_io_error_pending; +SQLITE_API extern int sqlite3_io_error_hit; +static int saved_cnt; +void disable_simulated_io_errors(void){ + saved_cnt = sqlite3_io_error_pending; + sqlite3_io_error_pending = -1; +} +void enable_simulated_io_errors(void){ + sqlite3_io_error_pending = saved_cnt; +} +#else +# define disable_simulated_io_errors() +# define enable_simulated_io_errors() +#endif + +/* +** Read the first N bytes from the beginning of the file into memory +** that pDest points to. +** +** If the pager was opened on a transient file (zFilename==""), or +** opened on a file less than N bytes in size, the output buffer is +** zeroed and SQLITE_OK returned. The rationale for this is that this +** function is used to read database headers, and a new transient or +** zero sized database has a header than consists entirely of zeroes. +** +** If any IO error apart from SQLITE_IOERR_SHORT_READ is encountered, +** the error code is returned to the caller and the contents of the +** output buffer undefined. +*/ +SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager *pPager, int N, unsigned char *pDest){ + int rc = SQLITE_OK; + memset(pDest, 0, N); + assert( isOpen(pPager->fd) || pPager->tempFile ); + + /* This routine is only called by btree immediately after creating + ** the Pager object. There has not been an opportunity to transition + ** to WAL mode yet. + */ + assert( !pagerUseWal(pPager) ); + + if( isOpen(pPager->fd) ){ + IOTRACE(("DBHDR %p 0 %d\n", pPager, N)) + rc = sqlite3OsRead(pPager->fd, pDest, N, 0); + if( rc==SQLITE_IOERR_SHORT_READ ){ + rc = SQLITE_OK; + } + } + return rc; +} + +/* +** This function may only be called when a read-transaction is open on +** the pager. It returns the total number of pages in the database. +** +** However, if the file is between 1 and <page-size> bytes in size, then +** this is considered a 1 page file. +*/ +SQLITE_PRIVATE void sqlite3PagerPagecount(Pager *pPager, int *pnPage){ + assert( pPager->eState>=PAGER_READER ); + assert( pPager->eState!=PAGER_WRITER_FINISHED ); + *pnPage = (int)pPager->dbSize; +} + + +/* +** Try to obtain a lock of type locktype on the database file. If +** a similar or greater lock is already held, this function is a no-op +** (returning SQLITE_OK immediately). +** +** Otherwise, attempt to obtain the lock using sqlite3OsLock(). Invoke +** the busy callback if the lock is currently not available. Repeat +** until the busy callback returns false or until the attempt to +** obtain the lock succeeds. +** +** Return SQLITE_OK on success and an error code if we cannot obtain +** the lock. If the lock is obtained successfully, set the Pager.state +** variable to locktype before returning. +*/ +static int pager_wait_on_lock(Pager *pPager, int locktype){ + int rc; /* Return code */ + + /* Check that this is either a no-op (because the requested lock is + ** already held), or one of the transitions that the busy-handler + ** may be invoked during, according to the comment above + ** sqlite3PagerSetBusyhandler(). + */ + assert( (pPager->eLock>=locktype) + || (pPager->eLock==NO_LOCK && locktype==SHARED_LOCK) + || (pPager->eLock==RESERVED_LOCK && locktype==EXCLUSIVE_LOCK) + ); + + do { + rc = pagerLockDb(pPager, locktype); + }while( rc==SQLITE_BUSY && pPager->xBusyHandler(pPager->pBusyHandlerArg) ); + return rc; +} + +/* +** Function assertTruncateConstraint(pPager) checks that one of the +** following is true for all dirty pages currently in the page-cache: +** +** a) The page number is less than or equal to the size of the +** current database image, in pages, OR +** +** b) if the page content were written at this time, it would not +** be necessary to write the current content out to the sub-journal. +** +** If the condition asserted by this function were not true, and the +** dirty page were to be discarded from the cache via the pagerStress() +** routine, pagerStress() would not write the current page content to +** the database file. If a savepoint transaction were rolled back after +** this happened, the correct behavior would be to restore the current +** content of the page. However, since this content is not present in either +** the database file or the portion of the rollback journal and +** sub-journal rolled back the content could not be restored and the +** database image would become corrupt. It is therefore fortunate that +** this circumstance cannot arise. +*/ +#if defined(SQLITE_DEBUG) +static void assertTruncateConstraintCb(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + assert( pPg->flags&PGHDR_DIRTY ); + if( pPg->pgno>pPager->dbSize ){ /* if (a) is false */ + Pgno pgno = pPg->pgno; + int i; + for(i=0; i<pPg->pPager->nSavepoint; i++){ + PagerSavepoint *p = &pPager->aSavepoint[i]; + assert( p->nOrig<pgno || sqlite3BitvecTestNotNull(p->pInSavepoint,pgno) ); + } + } +} +static void assertTruncateConstraint(Pager *pPager){ + sqlite3PcacheIterateDirty(pPager->pPCache, assertTruncateConstraintCb); +} +#else +# define assertTruncateConstraint(pPager) +#endif + +/* +** Truncate the in-memory database file image to nPage pages. This +** function does not actually modify the database file on disk. It +** just sets the internal state of the pager object so that the +** truncation will be done when the current transaction is committed. +** +** This function is only called right before committing a transaction. +** Once this function has been called, the transaction must either be +** rolled back or committed. It is not safe to call this function and +** then continue writing to the database. +*/ +SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){ + assert( pPager->dbSize>=nPage || CORRUPT_DB ); + assert( pPager->eState>=PAGER_WRITER_CACHEMOD ); + pPager->dbSize = nPage; + + /* At one point the code here called assertTruncateConstraint() to + ** ensure that all pages being truncated away by this operation are, + ** if one or more savepoints are open, present in the savepoint + ** journal so that they can be restored if the savepoint is rolled + ** back. This is no longer necessary as this function is now only + ** called right before committing a transaction. So although the + ** Pager object may still have open savepoints (Pager.nSavepoint!=0), + ** they cannot be rolled back. So the assertTruncateConstraint() call + ** is no longer correct. */ +} + + +/* +** This function is called before attempting a hot-journal rollback. It +** syncs the journal file to disk, then sets pPager->journalHdr to the +** size of the journal file so that the pager_playback() routine knows +** that the entire journal file has been synced. +** +** Syncing a hot-journal to disk before attempting to roll it back ensures +** that if a power-failure occurs during the rollback, the process that +** attempts rollback following system recovery sees the same journal +** content as this process. +** +** If everything goes as planned, SQLITE_OK is returned. Otherwise, +** an SQLite error code. +*/ +static int pagerSyncHotJournal(Pager *pPager){ + int rc = SQLITE_OK; + if( !pPager->noSync ){ + rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_NORMAL); + } + if( rc==SQLITE_OK ){ + rc = sqlite3OsFileSize(pPager->jfd, &pPager->journalHdr); + } + return rc; +} + +#if SQLITE_MAX_MMAP_SIZE>0 +/* +** Obtain a reference to a memory mapped page object for page number pgno. +** The new object will use the pointer pData, obtained from xFetch(). +** If successful, set *ppPage to point to the new page reference +** and return SQLITE_OK. Otherwise, return an SQLite error code and set +** *ppPage to zero. +** +** Page references obtained by calling this function should be released +** by calling pagerReleaseMapPage(). +*/ +static int pagerAcquireMapPage( + Pager *pPager, /* Pager object */ + Pgno pgno, /* Page number */ + void *pData, /* xFetch()'d data for this page */ + PgHdr **ppPage /* OUT: Acquired page object */ +){ + PgHdr *p; /* Memory mapped page to return */ + + if( pPager->pMmapFreelist ){ + *ppPage = p = pPager->pMmapFreelist; + pPager->pMmapFreelist = p->pDirty; + p->pDirty = 0; + assert( pPager->nExtra>=8 ); + memset(p->pExtra, 0, 8); + }else{ + *ppPage = p = (PgHdr *)sqlite3MallocZero(sizeof(PgHdr) + pPager->nExtra); + if( p==0 ){ + sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1) * pPager->pageSize, pData); + return SQLITE_NOMEM_BKPT; + } + p->pExtra = (void *)&p[1]; + p->flags = PGHDR_MMAP; + p->nRef = 1; + p->pPager = pPager; + } + + assert( p->pExtra==(void *)&p[1] ); + assert( p->pPage==0 ); + assert( p->flags==PGHDR_MMAP ); + assert( p->pPager==pPager ); + assert( p->nRef==1 ); + + p->pgno = pgno; + p->pData = pData; + pPager->nMmapOut++; + + return SQLITE_OK; +} +#endif + +/* +** Release a reference to page pPg. pPg must have been returned by an +** earlier call to pagerAcquireMapPage(). +*/ +static void pagerReleaseMapPage(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + pPager->nMmapOut--; + pPg->pDirty = pPager->pMmapFreelist; + pPager->pMmapFreelist = pPg; + + assert( pPager->fd->pMethods->iVersion>=3 ); + sqlite3OsUnfetch(pPager->fd, (i64)(pPg->pgno-1)*pPager->pageSize, pPg->pData); +} + +/* +** Free all PgHdr objects stored in the Pager.pMmapFreelist list. +*/ +static void pagerFreeMapHdrs(Pager *pPager){ + PgHdr *p; + PgHdr *pNext; + for(p=pPager->pMmapFreelist; p; p=pNext){ + pNext = p->pDirty; + sqlite3_free(p); + } +} + +/* Verify that the database file has not be deleted or renamed out from +** under the pager. Return SQLITE_OK if the database is still where it ought +** to be on disk. Return non-zero (SQLITE_READONLY_DBMOVED or some other error +** code from sqlite3OsAccess()) if the database has gone missing. +*/ +static int databaseIsUnmoved(Pager *pPager){ + int bHasMoved = 0; + int rc; + + if( pPager->tempFile ) return SQLITE_OK; + if( pPager->dbSize==0 ) return SQLITE_OK; + assert( pPager->zFilename && pPager->zFilename[0] ); + rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_HAS_MOVED, &bHasMoved); + if( rc==SQLITE_NOTFOUND ){ + /* If the HAS_MOVED file-control is unimplemented, assume that the file + ** has not been moved. That is the historical behavior of SQLite: prior to + ** version 3.8.3, it never checked */ + rc = SQLITE_OK; + }else if( rc==SQLITE_OK && bHasMoved ){ + rc = SQLITE_READONLY_DBMOVED; + } + return rc; +} + + +/* +** Shutdown the page cache. Free all memory and close all files. +** +** If a transaction was in progress when this routine is called, that +** transaction is rolled back. All outstanding pages are invalidated +** and their memory is freed. Any attempt to use a page associated +** with this page cache after this function returns will likely +** result in a coredump. +** +** This function always succeeds. If a transaction is active an attempt +** is made to roll it back. If an error occurs during the rollback +** a hot journal may be left in the filesystem but no error is returned +** to the caller. +*/ +SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ + u8 *pTmp = (u8*)pPager->pTmpSpace; + assert( db || pagerUseWal(pPager)==0 ); + assert( assert_pager_state(pPager) ); + disable_simulated_io_errors(); + sqlite3BeginBenignMalloc(); + pagerFreeMapHdrs(pPager); + /* pPager->errCode = 0; */ + pPager->exclusiveMode = 0; +#ifndef SQLITE_OMIT_WAL + { + u8 *a = 0; + assert( db || pPager->pWal==0 ); + if( db && 0==(db->flags & SQLITE_NoCkptOnClose) + && SQLITE_OK==databaseIsUnmoved(pPager) + ){ + a = pTmp; + } + sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize,a); + pPager->pWal = 0; + } +#endif + pager_reset(pPager); + if( MEMDB ){ + pager_unlock(pPager); + }else{ + /* If it is open, sync the journal file before calling UnlockAndRollback. + ** If this is not done, then an unsynced portion of the open journal + ** file may be played back into the database. If a power failure occurs + ** while this is happening, the database could become corrupt. + ** + ** If an error occurs while trying to sync the journal, shift the pager + ** into the ERROR state. This causes UnlockAndRollback to unlock the + ** database and close the journal file without attempting to roll it + ** back or finalize it. The next database user will have to do hot-journal + ** rollback before accessing the database file. + */ + if( isOpen(pPager->jfd) ){ + pager_error(pPager, pagerSyncHotJournal(pPager)); + } + pagerUnlockAndRollback(pPager); + } + sqlite3EndBenignMalloc(); + enable_simulated_io_errors(); + PAGERTRACE(("CLOSE %d\n", PAGERID(pPager))); + IOTRACE(("CLOSE %p\n", pPager)) + sqlite3OsClose(pPager->jfd); + sqlite3OsClose(pPager->fd); + sqlite3PageFree(pTmp); + sqlite3PcacheClose(pPager->pPCache); + assert( !pPager->aSavepoint && !pPager->pInJournal ); + assert( !isOpen(pPager->jfd) && !isOpen(pPager->sjfd) ); + + sqlite3_free(pPager); + return SQLITE_OK; +} + +#if !defined(NDEBUG) || defined(SQLITE_TEST) +/* +** Return the page number for page pPg. +*/ +SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage *pPg){ + return pPg->pgno; +} +#endif + +/* +** Increment the reference count for page pPg. +*/ +SQLITE_PRIVATE void sqlite3PagerRef(DbPage *pPg){ + sqlite3PcacheRef(pPg); +} + +/* +** Sync the journal. In other words, make sure all the pages that have +** been written to the journal have actually reached the surface of the +** disk and can be restored in the event of a hot-journal rollback. +** +** If the Pager.noSync flag is set, then this function is a no-op. +** Otherwise, the actions required depend on the journal-mode and the +** device characteristics of the file-system, as follows: +** +** * If the journal file is an in-memory journal file, no action need +** be taken. +** +** * Otherwise, if the device does not support the SAFE_APPEND property, +** then the nRec field of the most recently written journal header +** is updated to contain the number of journal records that have +** been written following it. If the pager is operating in full-sync +** mode, then the journal file is synced before this field is updated. +** +** * If the device does not support the SEQUENTIAL property, then +** journal file is synced. +** +** Or, in pseudo-code: +** +** if( NOT <in-memory journal> ){ +** if( NOT SAFE_APPEND ){ +** if( <full-sync mode> ) xSync(<journal file>); +** <update nRec field> +** } +** if( NOT SEQUENTIAL ) xSync(<journal file>); +** } +** +** If successful, this routine clears the PGHDR_NEED_SYNC flag of every +** page currently held in memory before returning SQLITE_OK. If an IO +** error is encountered, then the IO error code is returned to the caller. +*/ +static int syncJournal(Pager *pPager, int newHdr){ + int rc; /* Return code */ + + assert( pPager->eState==PAGER_WRITER_CACHEMOD + || pPager->eState==PAGER_WRITER_DBMOD + ); + assert( assert_pager_state(pPager) ); + assert( !pagerUseWal(pPager) ); + + rc = sqlite3PagerExclusiveLock(pPager); + if( rc!=SQLITE_OK ) return rc; + + if( !pPager->noSync ){ + assert( !pPager->tempFile ); + if( isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){ + const int iDc = sqlite3OsDeviceCharacteristics(pPager->fd); + assert( isOpen(pPager->jfd) ); + + if( 0==(iDc&SQLITE_IOCAP_SAFE_APPEND) ){ + /* This block deals with an obscure problem. If the last connection + ** that wrote to this database was operating in persistent-journal + ** mode, then the journal file may at this point actually be larger + ** than Pager.journalOff bytes. If the next thing in the journal + ** file happens to be a journal-header (written as part of the + ** previous connection's transaction), and a crash or power-failure + ** occurs after nRec is updated but before this connection writes + ** anything else to the journal file (or commits/rolls back its + ** transaction), then SQLite may become confused when doing the + ** hot-journal rollback following recovery. It may roll back all + ** of this connections data, then proceed to rolling back the old, + ** out-of-date data that follows it. Database corruption. + ** + ** To work around this, if the journal file does appear to contain + ** a valid header following Pager.journalOff, then write a 0x00 + ** byte to the start of it to prevent it from being recognized. + ** + ** Variable iNextHdrOffset is set to the offset at which this + ** problematic header will occur, if it exists. aMagic is used + ** as a temporary buffer to inspect the first couple of bytes of + ** the potential journal header. + */ + i64 iNextHdrOffset; + u8 aMagic[8]; + u8 zHeader[sizeof(aJournalMagic)+4]; + + memcpy(zHeader, aJournalMagic, sizeof(aJournalMagic)); + put32bits(&zHeader[sizeof(aJournalMagic)], pPager->nRec); + + iNextHdrOffset = journalHdrOffset(pPager); + rc = sqlite3OsRead(pPager->jfd, aMagic, 8, iNextHdrOffset); + if( rc==SQLITE_OK && 0==memcmp(aMagic, aJournalMagic, 8) ){ + static const u8 zerobyte = 0; + rc = sqlite3OsWrite(pPager->jfd, &zerobyte, 1, iNextHdrOffset); + } + if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){ + return rc; + } + + /* Write the nRec value into the journal file header. If in + ** full-synchronous mode, sync the journal first. This ensures that + ** all data has really hit the disk before nRec is updated to mark + ** it as a candidate for rollback. + ** + ** This is not required if the persistent media supports the + ** SAFE_APPEND property. Because in this case it is not possible + ** for garbage data to be appended to the file, the nRec field + ** is populated with 0xFFFFFFFF when the journal header is written + ** and never needs to be updated. + */ + if( pPager->fullSync && 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){ + PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager))); + IOTRACE(("JSYNC %p\n", pPager)) + rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags); + if( rc!=SQLITE_OK ) return rc; + } + IOTRACE(("JHDR %p %lld\n", pPager, pPager->journalHdr)); + rc = sqlite3OsWrite( + pPager->jfd, zHeader, sizeof(zHeader), pPager->journalHdr + ); + if( rc!=SQLITE_OK ) return rc; + } + if( 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){ + PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager))); + IOTRACE(("JSYNC %p\n", pPager)) + rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags| + (pPager->syncFlags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0) + ); + if( rc!=SQLITE_OK ) return rc; + } + + pPager->journalHdr = pPager->journalOff; + if( newHdr && 0==(iDc&SQLITE_IOCAP_SAFE_APPEND) ){ + pPager->nRec = 0; + rc = writeJournalHdr(pPager); + if( rc!=SQLITE_OK ) return rc; + } + }else{ + pPager->journalHdr = pPager->journalOff; + } + } + + /* Unless the pager is in noSync mode, the journal file was just + ** successfully synced. Either way, clear the PGHDR_NEED_SYNC flag on + ** all pages. + */ + sqlite3PcacheClearSyncFlags(pPager->pPCache); + pPager->eState = PAGER_WRITER_DBMOD; + assert( assert_pager_state(pPager) ); + return SQLITE_OK; +} + +/* +** The argument is the first in a linked list of dirty pages connected +** by the PgHdr.pDirty pointer. This function writes each one of the +** in-memory pages in the list to the database file. The argument may +** be NULL, representing an empty list. In this case this function is +** a no-op. +** +** The pager must hold at least a RESERVED lock when this function +** is called. Before writing anything to the database file, this lock +** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained, +** SQLITE_BUSY is returned and no data is written to the database file. +** +** If the pager is a temp-file pager and the actual file-system file +** is not yet open, it is created and opened before any data is +** written out. +** +** Once the lock has been upgraded and, if necessary, the file opened, +** the pages are written out to the database file in list order. Writing +** a page is skipped if it meets either of the following criteria: +** +** * The page number is greater than Pager.dbSize, or +** * The PGHDR_DONT_WRITE flag is set on the page. +** +** If writing out a page causes the database file to grow, Pager.dbFileSize +** is updated accordingly. If page 1 is written out, then the value cached +** in Pager.dbFileVers[] is updated to match the new value stored in +** the database file. +** +** If everything is successful, SQLITE_OK is returned. If an IO error +** occurs, an IO error code is returned. Or, if the EXCLUSIVE lock cannot +** be obtained, SQLITE_BUSY is returned. +*/ +static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ + int rc = SQLITE_OK; /* Return code */ + + /* This function is only called for rollback pagers in WRITER_DBMOD state. */ + assert( !pagerUseWal(pPager) ); + assert( pPager->tempFile || pPager->eState==PAGER_WRITER_DBMOD ); + assert( pPager->eLock==EXCLUSIVE_LOCK ); + assert( isOpen(pPager->fd) || pList->pDirty==0 ); + + /* If the file is a temp-file has not yet been opened, open it now. It + ** is not possible for rc to be other than SQLITE_OK if this branch + ** is taken, as pager_wait_on_lock() is a no-op for temp-files. + */ + if( !isOpen(pPager->fd) ){ + assert( pPager->tempFile && rc==SQLITE_OK ); + rc = pagerOpentemp(pPager, pPager->fd, pPager->vfsFlags); + } + + /* Before the first write, give the VFS a hint of what the final + ** file size will be. + */ + assert( rc!=SQLITE_OK || isOpen(pPager->fd) ); + if( rc==SQLITE_OK + && pPager->dbHintSize<pPager->dbSize + && (pList->pDirty || pList->pgno>pPager->dbHintSize) + ){ + sqlite3_int64 szFile = pPager->pageSize * (sqlite3_int64)pPager->dbSize; + sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &szFile); + pPager->dbHintSize = pPager->dbSize; + } + + while( rc==SQLITE_OK && pList ){ + Pgno pgno = pList->pgno; + + /* If there are dirty pages in the page cache with page numbers greater + ** than Pager.dbSize, this means sqlite3PagerTruncateImage() was called to + ** make the file smaller (presumably by auto-vacuum code). Do not write + ** any such pages to the file. + ** + ** Also, do not write out any page that has the PGHDR_DONT_WRITE flag + ** set (set by sqlite3PagerDontWrite()). + */ + if( pgno<=pPager->dbSize && 0==(pList->flags&PGHDR_DONT_WRITE) ){ + i64 offset = (pgno-1)*(i64)pPager->pageSize; /* Offset to write */ + char *pData; /* Data to write */ + + assert( (pList->flags&PGHDR_NEED_SYNC)==0 ); + if( pList->pgno==1 ) pager_write_changecounter(pList); + + pData = pList->pData; + + /* Write out the page data. */ + rc = sqlite3OsWrite(pPager->fd, pData, pPager->pageSize, offset); + + /* If page 1 was just written, update Pager.dbFileVers to match + ** the value now stored in the database file. If writing this + ** page caused the database file to grow, update dbFileSize. + */ + if( pgno==1 ){ + memcpy(&pPager->dbFileVers, &pData[24], sizeof(pPager->dbFileVers)); + } + if( pgno>pPager->dbFileSize ){ + pPager->dbFileSize = pgno; + } + pPager->aStat[PAGER_STAT_WRITE]++; + + /* Update any backup objects copying the contents of this pager. */ + sqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)pList->pData); + + PAGERTRACE(("STORE %d page %d hash(%08x)\n", + PAGERID(pPager), pgno, pager_pagehash(pList))); + IOTRACE(("PGOUT %p %d\n", pPager, pgno)); + PAGER_INCR(sqlite3_pager_writedb_count); + }else{ + PAGERTRACE(("NOSTORE %d page %d\n", PAGERID(pPager), pgno)); + } + pager_set_pagehash(pList); + pList = pList->pDirty; + } + + return rc; +} + +/* +** Ensure that the sub-journal file is open. If it is already open, this +** function is a no-op. +** +** SQLITE_OK is returned if everything goes according to plan. An +** SQLITE_IOERR_XXX error code is returned if a call to sqlite3OsOpen() +** fails. +*/ +static int openSubJournal(Pager *pPager){ + int rc = SQLITE_OK; + if( !isOpen(pPager->sjfd) ){ + const int flags = SQLITE_OPEN_SUBJOURNAL | SQLITE_OPEN_READWRITE + | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE + | SQLITE_OPEN_DELETEONCLOSE; + int nStmtSpill = sqlite3Config.nStmtSpill; + if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY || pPager->subjInMemory ){ + nStmtSpill = -1; + } + rc = sqlite3JournalOpen(pPager->pVfs, 0, pPager->sjfd, flags, nStmtSpill); + } + return rc; +} + +/* +** Append a record of the current state of page pPg to the sub-journal. +** +** If successful, set the bit corresponding to pPg->pgno in the bitvecs +** for all open savepoints before returning. +** +** This function returns SQLITE_OK if everything is successful, an IO +** error code if the attempt to write to the sub-journal fails, or +** SQLITE_NOMEM if a malloc fails while setting a bit in a savepoint +** bitvec. +*/ +static int subjournalPage(PgHdr *pPg){ + int rc = SQLITE_OK; + Pager *pPager = pPg->pPager; + if( pPager->journalMode!=PAGER_JOURNALMODE_OFF ){ + + /* Open the sub-journal, if it has not already been opened */ + assert( pPager->useJournal ); + assert( isOpen(pPager->jfd) || pagerUseWal(pPager) ); + assert( isOpen(pPager->sjfd) || pPager->nSubRec==0 ); + assert( pagerUseWal(pPager) + || pageInJournal(pPager, pPg) + || pPg->pgno>pPager->dbOrigSize + ); + rc = openSubJournal(pPager); + + /* If the sub-journal was opened successfully (or was already open), + ** write the journal record into the file. */ + if( rc==SQLITE_OK ){ + void *pData = pPg->pData; + i64 offset = (i64)pPager->nSubRec*(4+pPager->pageSize); + char *pData2; + pData2 = pData; + PAGERTRACE(("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno)); + rc = write32bits(pPager->sjfd, offset, pPg->pgno); + if( rc==SQLITE_OK ){ + rc = sqlite3OsWrite(pPager->sjfd, pData2, pPager->pageSize, offset+4); + } + } + } + if( rc==SQLITE_OK ){ + pPager->nSubRec++; + assert( pPager->nSavepoint>0 ); + rc = addToSavepointBitvecs(pPager, pPg->pgno); + } + return rc; +} +static int subjournalPageIfRequired(PgHdr *pPg){ + if( subjRequiresPage(pPg) ){ + return subjournalPage(pPg); + }else{ + return SQLITE_OK; + } +} + +/* +** This function is called by the pcache layer when it has reached some +** soft memory limit. The first argument is a pointer to a Pager object +** (cast as a void*). The pager is always 'purgeable' (not an in-memory +** database). The second argument is a reference to a page that is +** currently dirty but has no outstanding references. The page +** is always associated with the Pager object passed as the first +** argument. +** +** The job of this function is to make pPg clean by writing its contents +** out to the database file, if possible. This may involve syncing the +** journal file. +** +** If successful, sqlite3PcacheMakeClean() is called on the page and +** SQLITE_OK returned. If an IO error occurs while trying to make the +** page clean, the IO error code is returned. If the page cannot be +** made clean for some other reason, but no error occurs, then SQLITE_OK +** is returned by sqlite3PcacheMakeClean() is not called. +*/ +static int pagerStress(void *p, PgHdr *pPg){ + Pager *pPager = (Pager *)p; + int rc = SQLITE_OK; + + assert( pPg->pPager==pPager ); + assert( pPg->flags&PGHDR_DIRTY ); + + /* The doNotSpill NOSYNC bit is set during times when doing a sync of + ** journal (and adding a new header) is not allowed. This occurs + ** during calls to sqlite3PagerWrite() while trying to journal multiple + ** pages belonging to the same sector. + ** + ** The doNotSpill ROLLBACK and OFF bits inhibits all cache spilling + ** regardless of whether or not a sync is required. This is set during + ** a rollback or by user request, respectively. + ** + ** Spilling is also prohibited when in an error state since that could + ** lead to database corruption. In the current implementation it + ** is impossible for sqlite3PcacheFetch() to be called with createFlag==3 + ** while in the error state, hence it is impossible for this routine to + ** be called in the error state. Nevertheless, we include a NEVER() + ** test for the error state as a safeguard against future changes. + */ + if( NEVER(pPager->errCode) ) return SQLITE_OK; + testcase( pPager->doNotSpill & SPILLFLAG_ROLLBACK ); + testcase( pPager->doNotSpill & SPILLFLAG_OFF ); + testcase( pPager->doNotSpill & SPILLFLAG_NOSYNC ); + if( pPager->doNotSpill + && ((pPager->doNotSpill & (SPILLFLAG_ROLLBACK|SPILLFLAG_OFF))!=0 + || (pPg->flags & PGHDR_NEED_SYNC)!=0) + ){ + return SQLITE_OK; + } + + pPager->aStat[PAGER_STAT_SPILL]++; + pPg->pDirty = 0; + if( pagerUseWal(pPager) ){ + /* Write a single frame for this page to the log. */ + rc = subjournalPageIfRequired(pPg); + if( rc==SQLITE_OK ){ + rc = pagerWalFrames(pPager, pPg, 0, 0); + } + }else{ + +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + if( pPager->tempFile==0 ){ + rc = sqlite3JournalCreate(pPager->jfd); + if( rc!=SQLITE_OK ) return pager_error(pPager, rc); + } +#endif + + /* Sync the journal file if required. */ + if( pPg->flags&PGHDR_NEED_SYNC + || pPager->eState==PAGER_WRITER_CACHEMOD + ){ + rc = syncJournal(pPager, 1); + } + + /* Write the contents of the page out to the database file. */ + if( rc==SQLITE_OK ){ + assert( (pPg->flags&PGHDR_NEED_SYNC)==0 ); + rc = pager_write_pagelist(pPager, pPg); + } + } + + /* Mark the page as clean. */ + if( rc==SQLITE_OK ){ + PAGERTRACE(("STRESS %d page %d\n", PAGERID(pPager), pPg->pgno)); + sqlite3PcacheMakeClean(pPg); + } + + return pager_error(pPager, rc); +} + +/* +** Flush all unreferenced dirty pages to disk. +*/ +SQLITE_PRIVATE int sqlite3PagerFlush(Pager *pPager){ + int rc = pPager->errCode; + if( !MEMDB ){ + PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache); + assert( assert_pager_state(pPager) ); + while( rc==SQLITE_OK && pList ){ + PgHdr *pNext = pList->pDirty; + if( pList->nRef==0 ){ + rc = pagerStress((void*)pPager, pList); + } + pList = pNext; + } + } + + return rc; +} + +/* +** Allocate and initialize a new Pager object and put a pointer to it +** in *ppPager. The pager should eventually be freed by passing it +** to sqlite3PagerClose(). +** +** The zFilename argument is the path to the database file to open. +** If zFilename is NULL then a randomly-named temporary file is created +** and used as the file to be cached. Temporary files are be deleted +** automatically when they are closed. If zFilename is ":memory:" then +** all information is held in cache. It is never written to disk. +** This can be used to implement an in-memory database. +** +** The nExtra parameter specifies the number of bytes of space allocated +** along with each page reference. This space is available to the user +** via the sqlite3PagerGetExtra() API. When a new page is allocated, the +** first 8 bytes of this space are zeroed but the remainder is uninitialized. +** (The extra space is used by btree as the MemPage object.) +** +** The flags argument is used to specify properties that affect the +** operation of the pager. It should be passed some bitwise combination +** of the PAGER_* flags. +** +** The vfsFlags parameter is a bitmask to pass to the flags parameter +** of the xOpen() method of the supplied VFS when opening files. +** +** If the pager object is allocated and the specified file opened +** successfully, SQLITE_OK is returned and *ppPager set to point to +** the new pager object. If an error occurs, *ppPager is set to NULL +** and error code returned. This function may return SQLITE_NOMEM +** (sqlite3Malloc() is used to allocate memory), SQLITE_CANTOPEN or +** various SQLITE_IO_XXX errors. +*/ +SQLITE_PRIVATE int sqlite3PagerOpen( + sqlite3_vfs *pVfs, /* The virtual file system to use */ + Pager **ppPager, /* OUT: Return the Pager structure here */ + const char *zFilename, /* Name of the database file to open */ + int nExtra, /* Extra bytes append to each in-memory page */ + int flags, /* flags controlling this file */ + int vfsFlags, /* flags passed through to sqlite3_vfs.xOpen() */ + void (*xReinit)(DbPage*) /* Function to reinitialize pages */ +){ + u8 *pPtr; + Pager *pPager = 0; /* Pager object to allocate and return */ + int rc = SQLITE_OK; /* Return code */ + int tempFile = 0; /* True for temp files (incl. in-memory files) */ + int memDb = 0; /* True if this is an in-memory file */ + int memJM = 0; /* Memory journal mode */ + int readOnly = 0; /* True if this is a read-only file */ + int journalFileSize; /* Bytes to allocate for each journal fd */ + char *zPathname = 0; /* Full path to database file */ + int nPathname = 0; /* Number of bytes in zPathname */ + int useJournal = (flags & PAGER_OMIT_JOURNAL)==0; /* False to omit journal */ + int pcacheSize = sqlite3PcacheSize(); /* Bytes to allocate for PCache */ + u32 szPageDflt = SQLITE_DEFAULT_PAGE_SIZE; /* Default page size */ + const char *zUri = 0; /* URI args to copy */ + int nUriByte = 1; /* Number of bytes of URI args at *zUri */ + + /* Figure out how much space is required for each journal file-handle + ** (there are two of them, the main journal and the sub-journal). */ + journalFileSize = ROUND8(sqlite3JournalSize(pVfs)); + + /* Set the output variable to NULL in case an error occurs. */ + *ppPager = 0; + +#ifndef SQLITE_OMIT_MEMORYDB + if( flags & PAGER_MEMORY ){ + memDb = 1; + if( zFilename && zFilename[0] ){ + zPathname = sqlite3DbStrDup(0, zFilename); + if( zPathname==0 ) return SQLITE_NOMEM_BKPT; + nPathname = sqlite3Strlen30(zPathname); + zFilename = 0; + } + } +#endif + + /* Compute and store the full pathname in an allocated buffer pointed + ** to by zPathname, length nPathname. Or, if this is a temporary file, + ** leave both nPathname and zPathname set to 0. + */ + if( zFilename && zFilename[0] ){ + const char *z; + nPathname = pVfs->mxPathname+1; + zPathname = sqlite3DbMallocRaw(0, nPathname*2); + if( zPathname==0 ){ + return SQLITE_NOMEM_BKPT; + } + zPathname[0] = 0; /* Make sure initialized even if FullPathname() fails */ + rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_OK_SYMLINK ){ + if( vfsFlags & SQLITE_OPEN_NOFOLLOW ){ + rc = SQLITE_CANTOPEN_SYMLINK; + }else{ + rc = SQLITE_OK; + } + } + } + nPathname = sqlite3Strlen30(zPathname); + z = zUri = &zFilename[sqlite3Strlen30(zFilename)+1]; + while( *z ){ + z += strlen(z)+1; + z += strlen(z)+1; + } + nUriByte = (int)(&z[1] - zUri); + assert( nUriByte>=1 ); + if( rc==SQLITE_OK && nPathname+8>pVfs->mxPathname ){ + /* This branch is taken when the journal path required by + ** the database being opened will be more than pVfs->mxPathname + ** bytes in length. This means the database cannot be opened, + ** as it will not be possible to open the journal file or even + ** check for a hot-journal before reading. + */ + rc = SQLITE_CANTOPEN_BKPT; + } + if( rc!=SQLITE_OK ){ + sqlite3DbFree(0, zPathname); + return rc; + } + } + + /* Allocate memory for the Pager structure, PCache object, the + ** three file descriptors, the database file name and the journal + ** file name. The layout in memory is as follows: + ** + ** Pager object (sizeof(Pager) bytes) + ** PCache object (sqlite3PcacheSize() bytes) + ** Database file handle (pVfs->szOsFile bytes) + ** Sub-journal file handle (journalFileSize bytes) + ** Main journal file handle (journalFileSize bytes) + ** Ptr back to the Pager (sizeof(Pager*) bytes) + ** \0\0\0\0 database prefix (4 bytes) + ** Database file name (nPathname+1 bytes) + ** URI query parameters (nUriByte bytes) + ** Journal filename (nPathname+8+1 bytes) + ** WAL filename (nPathname+4+1 bytes) + ** \0\0\0 terminator (3 bytes) + ** + ** Some 3rd-party software, over which we have no control, depends on + ** the specific order of the filenames and the \0 separators between them + ** so that it can (for example) find the database filename given the WAL + ** filename without using the sqlite3_filename_database() API. This is a + ** misuse of SQLite and a bug in the 3rd-party software, but the 3rd-party + ** software is in widespread use, so we try to avoid changing the filename + ** order and formatting if possible. In particular, the details of the + ** filename format expected by 3rd-party software should be as follows: + ** + ** - Main Database Path + ** - \0 + ** - Multiple URI components consisting of: + ** - Key + ** - \0 + ** - Value + ** - \0 + ** - \0 + ** - Journal Path + ** - \0 + ** - WAL Path (zWALName) + ** - \0 + ** + ** The sqlite3_create_filename() interface and the databaseFilename() utility + ** that is used by sqlite3_filename_database() and kin also depend on the + ** specific formatting and order of the various filenames, so if the format + ** changes here, be sure to change it there as well. + */ + assert( SQLITE_PTRSIZE==sizeof(Pager*) ); + pPtr = (u8 *)sqlite3MallocZero( + ROUND8(sizeof(*pPager)) + /* Pager structure */ + ROUND8(pcacheSize) + /* PCache object */ + ROUND8(pVfs->szOsFile) + /* The main db file */ + journalFileSize * 2 + /* The two journal files */ + SQLITE_PTRSIZE + /* Space to hold a pointer */ + 4 + /* Database prefix */ + nPathname + 1 + /* database filename */ + nUriByte + /* query parameters */ + nPathname + 8 + 1 + /* Journal filename */ +#ifndef SQLITE_OMIT_WAL + nPathname + 4 + 1 + /* WAL filename */ +#endif + 3 /* Terminator */ + ); + assert( EIGHT_BYTE_ALIGNMENT(SQLITE_INT_TO_PTR(journalFileSize)) ); + if( !pPtr ){ + sqlite3DbFree(0, zPathname); + return SQLITE_NOMEM_BKPT; + } + pPager = (Pager*)pPtr; pPtr += ROUND8(sizeof(*pPager)); + pPager->pPCache = (PCache*)pPtr; pPtr += ROUND8(pcacheSize); + pPager->fd = (sqlite3_file*)pPtr; pPtr += ROUND8(pVfs->szOsFile); + pPager->sjfd = (sqlite3_file*)pPtr; pPtr += journalFileSize; + pPager->jfd = (sqlite3_file*)pPtr; pPtr += journalFileSize; + assert( EIGHT_BYTE_ALIGNMENT(pPager->jfd) ); + memcpy(pPtr, &pPager, SQLITE_PTRSIZE); pPtr += SQLITE_PTRSIZE; + + /* Fill in the Pager.zFilename and pPager.zQueryParam fields */ + pPtr += 4; /* Skip zero prefix */ + pPager->zFilename = (char*)pPtr; + if( nPathname>0 ){ + memcpy(pPtr, zPathname, nPathname); pPtr += nPathname + 1; + if( zUri ){ + memcpy(pPtr, zUri, nUriByte); pPtr += nUriByte; + }else{ + pPtr++; + } + } + + + /* Fill in Pager.zJournal */ + if( nPathname>0 ){ + pPager->zJournal = (char*)pPtr; + memcpy(pPtr, zPathname, nPathname); pPtr += nPathname; + memcpy(pPtr, "-journal",8); pPtr += 8 + 1; +#ifdef SQLITE_ENABLE_8_3_NAMES + sqlite3FileSuffix3(zFilename,pPager->zJournal); + pPtr = (u8*)(pPager->zJournal + sqlite3Strlen30(pPager->zJournal)+1); +#endif + }else{ + pPager->zJournal = 0; + } + +#ifndef SQLITE_OMIT_WAL + /* Fill in Pager.zWal */ + if( nPathname>0 ){ + pPager->zWal = (char*)pPtr; + memcpy(pPtr, zPathname, nPathname); pPtr += nPathname; + memcpy(pPtr, "-wal", 4); pPtr += 4 + 1; +#ifdef SQLITE_ENABLE_8_3_NAMES + sqlite3FileSuffix3(zFilename, pPager->zWal); + pPtr = (u8*)(pPager->zWal + sqlite3Strlen30(pPager->zWal)+1); +#endif + }else{ + pPager->zWal = 0; + } +#endif + (void)pPtr; /* Suppress warning about unused pPtr value */ + + if( nPathname ) sqlite3DbFree(0, zPathname); + pPager->pVfs = pVfs; + pPager->vfsFlags = vfsFlags; + + /* Open the pager file. + */ + if( zFilename && zFilename[0] ){ + int fout = 0; /* VFS flags returned by xOpen() */ + rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout); + assert( !memDb ); + pPager->memVfs = memJM = (fout&SQLITE_OPEN_MEMORY)!=0; + readOnly = (fout&SQLITE_OPEN_READONLY)!=0; + + /* If the file was successfully opened for read/write access, + ** choose a default page size in case we have to create the + ** database file. The default page size is the maximum of: + ** + ** + SQLITE_DEFAULT_PAGE_SIZE, + ** + The value returned by sqlite3OsSectorSize() + ** + The largest page size that can be written atomically. + */ + if( rc==SQLITE_OK ){ + int iDc = sqlite3OsDeviceCharacteristics(pPager->fd); + if( !readOnly ){ + setSectorSize(pPager); + assert(SQLITE_DEFAULT_PAGE_SIZE<=SQLITE_MAX_DEFAULT_PAGE_SIZE); + if( szPageDflt<pPager->sectorSize ){ + if( pPager->sectorSize>SQLITE_MAX_DEFAULT_PAGE_SIZE ){ + szPageDflt = SQLITE_MAX_DEFAULT_PAGE_SIZE; + }else{ + szPageDflt = (u32)pPager->sectorSize; + } + } +#ifdef SQLITE_ENABLE_ATOMIC_WRITE + { + int ii; + assert(SQLITE_IOCAP_ATOMIC512==(512>>8)); + assert(SQLITE_IOCAP_ATOMIC64K==(65536>>8)); + assert(SQLITE_MAX_DEFAULT_PAGE_SIZE<=65536); + for(ii=szPageDflt; ii<=SQLITE_MAX_DEFAULT_PAGE_SIZE; ii=ii*2){ + if( iDc&(SQLITE_IOCAP_ATOMIC|(ii>>8)) ){ + szPageDflt = ii; + } + } + } +#endif + } + pPager->noLock = sqlite3_uri_boolean(pPager->zFilename, "nolock", 0); + if( (iDc & SQLITE_IOCAP_IMMUTABLE)!=0 + || sqlite3_uri_boolean(pPager->zFilename, "immutable", 0) ){ + vfsFlags |= SQLITE_OPEN_READONLY; + goto act_like_temp_file; + } + } + }else{ + /* If a temporary file is requested, it is not opened immediately. + ** In this case we accept the default page size and delay actually + ** opening the file until the first call to OsWrite(). + ** + ** This branch is also run for an in-memory database. An in-memory + ** database is the same as a temp-file that is never written out to + ** disk and uses an in-memory rollback journal. + ** + ** This branch also runs for files marked as immutable. + */ +act_like_temp_file: + tempFile = 1; + pPager->eState = PAGER_READER; /* Pretend we already have a lock */ + pPager->eLock = EXCLUSIVE_LOCK; /* Pretend we are in EXCLUSIVE mode */ + pPager->noLock = 1; /* Do no locking */ + readOnly = (vfsFlags&SQLITE_OPEN_READONLY); + } + + /* The following call to PagerSetPagesize() serves to set the value of + ** Pager.pageSize and to allocate the Pager.pTmpSpace buffer. + */ + if( rc==SQLITE_OK ){ + assert( pPager->memDb==0 ); + rc = sqlite3PagerSetPagesize(pPager, &szPageDflt, -1); + testcase( rc!=SQLITE_OK ); + } + + /* Initialize the PCache object. */ + if( rc==SQLITE_OK ){ + nExtra = ROUND8(nExtra); + assert( nExtra>=8 && nExtra<1000 ); + rc = sqlite3PcacheOpen(szPageDflt, nExtra, !memDb, + !memDb?pagerStress:0, (void *)pPager, pPager->pPCache); + } + + /* If an error occurred above, free the Pager structure and close the file. + */ + if( rc!=SQLITE_OK ){ + sqlite3OsClose(pPager->fd); + sqlite3PageFree(pPager->pTmpSpace); + sqlite3_free(pPager); + return rc; + } + + PAGERTRACE(("OPEN %d %s\n", FILEHANDLEID(pPager->fd), pPager->zFilename)); + IOTRACE(("OPEN %p %s\n", pPager, pPager->zFilename)) + + pPager->useJournal = (u8)useJournal; + /* pPager->stmtOpen = 0; */ + /* pPager->stmtInUse = 0; */ + /* pPager->nRef = 0; */ + /* pPager->stmtSize = 0; */ + /* pPager->stmtJSize = 0; */ + /* pPager->nPage = 0; */ + pPager->mxPgno = SQLITE_MAX_PAGE_COUNT; + /* pPager->state = PAGER_UNLOCK; */ + /* pPager->errMask = 0; */ + pPager->tempFile = (u8)tempFile; + assert( tempFile==PAGER_LOCKINGMODE_NORMAL + || tempFile==PAGER_LOCKINGMODE_EXCLUSIVE ); + assert( PAGER_LOCKINGMODE_EXCLUSIVE==1 ); + pPager->exclusiveMode = (u8)tempFile; + pPager->changeCountDone = pPager->tempFile; + pPager->memDb = (u8)memDb; + pPager->readOnly = (u8)readOnly; + assert( useJournal || pPager->tempFile ); + sqlite3PagerSetFlags(pPager, (SQLITE_DEFAULT_SYNCHRONOUS+1)|PAGER_CACHESPILL); + /* pPager->pFirst = 0; */ + /* pPager->pFirstSynced = 0; */ + /* pPager->pLast = 0; */ + pPager->nExtra = (u16)nExtra; + pPager->journalSizeLimit = SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT; + assert( isOpen(pPager->fd) || tempFile ); + setSectorSize(pPager); + if( !useJournal ){ + pPager->journalMode = PAGER_JOURNALMODE_OFF; + }else if( memDb || memJM ){ + pPager->journalMode = PAGER_JOURNALMODE_MEMORY; + } + /* pPager->xBusyHandler = 0; */ + /* pPager->pBusyHandlerArg = 0; */ + pPager->xReiniter = xReinit; + setGetterMethod(pPager); + /* memset(pPager->aHash, 0, sizeof(pPager->aHash)); */ + /* pPager->szMmap = SQLITE_DEFAULT_MMAP_SIZE // will be set by btree.c */ + + *ppPager = pPager; + return SQLITE_OK; +} + +/* +** Return the sqlite3_file for the main database given the name +** of the corresponding WAL or Journal name as passed into +** xOpen. +*/ +SQLITE_API sqlite3_file *sqlite3_database_file_object(const char *zName){ + Pager *pPager; + while( zName[-1]!=0 || zName[-2]!=0 || zName[-3]!=0 || zName[-4]!=0 ){ + zName--; + } + pPager = *(Pager**)(zName - 4 - sizeof(Pager*)); + return pPager->fd; +} + + +/* +** This function is called after transitioning from PAGER_UNLOCK to +** PAGER_SHARED state. It tests if there is a hot journal present in +** the file-system for the given pager. A hot journal is one that +** needs to be played back. According to this function, a hot-journal +** file exists if the following criteria are met: +** +** * The journal file exists in the file system, and +** * No process holds a RESERVED or greater lock on the database file, and +** * The database file itself is greater than 0 bytes in size, and +** * The first byte of the journal file exists and is not 0x00. +** +** If the current size of the database file is 0 but a journal file +** exists, that is probably an old journal left over from a prior +** database with the same name. In this case the journal file is +** just deleted using OsDelete, *pExists is set to 0 and SQLITE_OK +** is returned. +** +** This routine does not check if there is a super-journal filename +** at the end of the file. If there is, and that super-journal file +** does not exist, then the journal file is not really hot. In this +** case this routine will return a false-positive. The pager_playback() +** routine will discover that the journal file is not really hot and +** will not roll it back. +** +** If a hot-journal file is found to exist, *pExists is set to 1 and +** SQLITE_OK returned. If no hot-journal file is present, *pExists is +** set to 0 and SQLITE_OK returned. If an IO error occurs while trying +** to determine whether or not a hot-journal file exists, the IO error +** code is returned and the value of *pExists is undefined. +*/ +static int hasHotJournal(Pager *pPager, int *pExists){ + sqlite3_vfs * const pVfs = pPager->pVfs; + int rc = SQLITE_OK; /* Return code */ + int exists = 1; /* True if a journal file is present */ + int jrnlOpen = !!isOpen(pPager->jfd); + + assert( pPager->useJournal ); + assert( isOpen(pPager->fd) ); + assert( pPager->eState==PAGER_OPEN ); + + assert( jrnlOpen==0 || ( sqlite3OsDeviceCharacteristics(pPager->jfd) & + SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN + )); + + *pExists = 0; + if( !jrnlOpen ){ + rc = sqlite3OsAccess(pVfs, pPager->zJournal, SQLITE_ACCESS_EXISTS, &exists); + } + if( rc==SQLITE_OK && exists ){ + int locked = 0; /* True if some process holds a RESERVED lock */ + + /* Race condition here: Another process might have been holding the + ** the RESERVED lock and have a journal open at the sqlite3OsAccess() + ** call above, but then delete the journal and drop the lock before + ** we get to the following sqlite3OsCheckReservedLock() call. If that + ** is the case, this routine might think there is a hot journal when + ** in fact there is none. This results in a false-positive which will + ** be dealt with by the playback routine. Ticket #3883. + */ + rc = sqlite3OsCheckReservedLock(pPager->fd, &locked); + if( rc==SQLITE_OK && !locked ){ + Pgno nPage; /* Number of pages in database file */ + + assert( pPager->tempFile==0 ); + rc = pagerPagecount(pPager, &nPage); + if( rc==SQLITE_OK ){ + /* If the database is zero pages in size, that means that either (1) the + ** journal is a remnant from a prior database with the same name where + ** the database file but not the journal was deleted, or (2) the initial + ** transaction that populates a new database is being rolled back. + ** In either case, the journal file can be deleted. However, take care + ** not to delete the journal file if it is already open due to + ** journal_mode=PERSIST. + */ + if( nPage==0 && !jrnlOpen ){ + sqlite3BeginBenignMalloc(); + if( pagerLockDb(pPager, RESERVED_LOCK)==SQLITE_OK ){ + sqlite3OsDelete(pVfs, pPager->zJournal, 0); + if( !pPager->exclusiveMode ) pagerUnlockDb(pPager, SHARED_LOCK); + } + sqlite3EndBenignMalloc(); + }else{ + /* The journal file exists and no other connection has a reserved + ** or greater lock on the database file. Now check that there is + ** at least one non-zero bytes at the start of the journal file. + ** If there is, then we consider this journal to be hot. If not, + ** it can be ignored. + */ + if( !jrnlOpen ){ + int f = SQLITE_OPEN_READONLY|SQLITE_OPEN_MAIN_JOURNAL; + rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, f, &f); + } + if( rc==SQLITE_OK ){ + u8 first = 0; + rc = sqlite3OsRead(pPager->jfd, (void *)&first, 1, 0); + if( rc==SQLITE_IOERR_SHORT_READ ){ + rc = SQLITE_OK; + } + if( !jrnlOpen ){ + sqlite3OsClose(pPager->jfd); + } + *pExists = (first!=0); + }else if( rc==SQLITE_CANTOPEN ){ + /* If we cannot open the rollback journal file in order to see if + ** it has a zero header, that might be due to an I/O error, or + ** it might be due to the race condition described above and in + ** ticket #3883. Either way, assume that the journal is hot. + ** This might be a false positive. But if it is, then the + ** automatic journal playback and recovery mechanism will deal + ** with it under an EXCLUSIVE lock where we do not need to + ** worry so much with race conditions. + */ + *pExists = 1; + rc = SQLITE_OK; + } + } + } + } + } + + return rc; +} + +/* +** This function is called to obtain a shared lock on the database file. +** It is illegal to call sqlite3PagerGet() until after this function +** has been successfully called. If a shared-lock is already held when +** this function is called, it is a no-op. +** +** The following operations are also performed by this function. +** +** 1) If the pager is currently in PAGER_OPEN state (no lock held +** on the database file), then an attempt is made to obtain a +** SHARED lock on the database file. Immediately after obtaining +** the SHARED lock, the file-system is checked for a hot-journal, +** which is played back if present. Following any hot-journal +** rollback, the contents of the cache are validated by checking +** the 'change-counter' field of the database file header and +** discarded if they are found to be invalid. +** +** 2) If the pager is running in exclusive-mode, and there are currently +** no outstanding references to any pages, and is in the error state, +** then an attempt is made to clear the error state by discarding +** the contents of the page cache and rolling back any open journal +** file. +** +** If everything is successful, SQLITE_OK is returned. If an IO error +** occurs while locking the database, checking for a hot-journal file or +** rolling back a journal file, the IO error code is returned. +*/ +SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ + int rc = SQLITE_OK; /* Return code */ + + /* This routine is only called from b-tree and only when there are no + ** outstanding pages. This implies that the pager state should either + ** be OPEN or READER. READER is only possible if the pager is or was in + ** exclusive access mode. */ + assert( sqlite3PcacheRefCount(pPager->pPCache)==0 ); + assert( assert_pager_state(pPager) ); + assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER ); + assert( pPager->errCode==SQLITE_OK ); + + if( !pagerUseWal(pPager) && pPager->eState==PAGER_OPEN ){ + int bHotJournal = 1; /* True if there exists a hot journal-file */ + + assert( !MEMDB ); + assert( pPager->tempFile==0 || pPager->eLock==EXCLUSIVE_LOCK ); + + rc = pager_wait_on_lock(pPager, SHARED_LOCK); + if( rc!=SQLITE_OK ){ + assert( pPager->eLock==NO_LOCK || pPager->eLock==UNKNOWN_LOCK ); + goto failed; + } + + /* If a journal file exists, and there is no RESERVED lock on the + ** database file, then it either needs to be played back or deleted. + */ + if( pPager->eLock<=SHARED_LOCK ){ + rc = hasHotJournal(pPager, &bHotJournal); + } + if( rc!=SQLITE_OK ){ + goto failed; + } + if( bHotJournal ){ + if( pPager->readOnly ){ + rc = SQLITE_READONLY_ROLLBACK; + goto failed; + } + + /* Get an EXCLUSIVE lock on the database file. At this point it is + ** important that a RESERVED lock is not obtained on the way to the + ** EXCLUSIVE lock. If it were, another process might open the + ** database file, detect the RESERVED lock, and conclude that the + ** database is safe to read while this process is still rolling the + ** hot-journal back. + ** + ** Because the intermediate RESERVED lock is not requested, any + ** other process attempting to access the database file will get to + ** this point in the code and fail to obtain its own EXCLUSIVE lock + ** on the database file. + ** + ** Unless the pager is in locking_mode=exclusive mode, the lock is + ** downgraded to SHARED_LOCK before this function returns. + */ + rc = pagerLockDb(pPager, EXCLUSIVE_LOCK); + if( rc!=SQLITE_OK ){ + goto failed; + } + + /* If it is not already open and the file exists on disk, open the + ** journal for read/write access. Write access is required because + ** in exclusive-access mode the file descriptor will be kept open + ** and possibly used for a transaction later on. Also, write-access + ** is usually required to finalize the journal in journal_mode=persist + ** mode (and also for journal_mode=truncate on some systems). + ** + ** If the journal does not exist, it usually means that some + ** other connection managed to get in and roll it back before + ** this connection obtained the exclusive lock above. Or, it + ** may mean that the pager was in the error-state when this + ** function was called and the journal file does not exist. + */ + if( !isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){ + sqlite3_vfs * const pVfs = pPager->pVfs; + int bExists; /* True if journal file exists */ + rc = sqlite3OsAccess( + pVfs, pPager->zJournal, SQLITE_ACCESS_EXISTS, &bExists); + if( rc==SQLITE_OK && bExists ){ + int fout = 0; + int f = SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_JOURNAL; + assert( !pPager->tempFile ); + rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, f, &fout); + assert( rc!=SQLITE_OK || isOpen(pPager->jfd) ); + if( rc==SQLITE_OK && fout&SQLITE_OPEN_READONLY ){ + rc = SQLITE_CANTOPEN_BKPT; + sqlite3OsClose(pPager->jfd); + } + } + } + + /* Playback and delete the journal. Drop the database write + ** lock and reacquire the read lock. Purge the cache before + ** playing back the hot-journal so that we don't end up with + ** an inconsistent cache. Sync the hot journal before playing + ** it back since the process that crashed and left the hot journal + ** probably did not sync it and we are required to always sync + ** the journal before playing it back. + */ + if( isOpen(pPager->jfd) ){ + assert( rc==SQLITE_OK ); + rc = pagerSyncHotJournal(pPager); + if( rc==SQLITE_OK ){ + rc = pager_playback(pPager, !pPager->tempFile); + pPager->eState = PAGER_OPEN; + } + }else if( !pPager->exclusiveMode ){ + pagerUnlockDb(pPager, SHARED_LOCK); + } + + if( rc!=SQLITE_OK ){ + /* This branch is taken if an error occurs while trying to open + ** or roll back a hot-journal while holding an EXCLUSIVE lock. The + ** pager_unlock() routine will be called before returning to unlock + ** the file. If the unlock attempt fails, then Pager.eLock must be + ** set to UNKNOWN_LOCK (see the comment above the #define for + ** UNKNOWN_LOCK above for an explanation). + ** + ** In order to get pager_unlock() to do this, set Pager.eState to + ** PAGER_ERROR now. This is not actually counted as a transition + ** to ERROR state in the state diagram at the top of this file, + ** since we know that the same call to pager_unlock() will very + ** shortly transition the pager object to the OPEN state. Calling + ** assert_pager_state() would fail now, as it should not be possible + ** to be in ERROR state when there are zero outstanding page + ** references. + */ + pager_error(pPager, rc); + goto failed; + } + + assert( pPager->eState==PAGER_OPEN ); + assert( (pPager->eLock==SHARED_LOCK) + || (pPager->exclusiveMode && pPager->eLock>SHARED_LOCK) + ); + } + + if( !pPager->tempFile && pPager->hasHeldSharedLock ){ + /* The shared-lock has just been acquired then check to + ** see if the database has been modified. If the database has changed, + ** flush the cache. The hasHeldSharedLock flag prevents this from + ** occurring on the very first access to a file, in order to save a + ** single unnecessary sqlite3OsRead() call at the start-up. + ** + ** Database changes are detected by looking at 15 bytes beginning + ** at offset 24 into the file. The first 4 of these 16 bytes are + ** a 32-bit counter that is incremented with each change. The + ** other bytes change randomly with each file change when + ** a codec is in use. + ** + ** There is a vanishingly small chance that a change will not be + ** detected. The chance of an undetected change is so small that + ** it can be neglected. + */ + char dbFileVers[sizeof(pPager->dbFileVers)]; + + IOTRACE(("CKVERS %p %d\n", pPager, sizeof(dbFileVers))); + rc = sqlite3OsRead(pPager->fd, &dbFileVers, sizeof(dbFileVers), 24); + if( rc!=SQLITE_OK ){ + if( rc!=SQLITE_IOERR_SHORT_READ ){ + goto failed; + } + memset(dbFileVers, 0, sizeof(dbFileVers)); + } + + if( memcmp(pPager->dbFileVers, dbFileVers, sizeof(dbFileVers))!=0 ){ + pager_reset(pPager); + + /* Unmap the database file. It is possible that external processes + ** may have truncated the database file and then extended it back + ** to its original size while this process was not holding a lock. + ** In this case there may exist a Pager.pMap mapping that appears + ** to be the right size but is not actually valid. Avoid this + ** possibility by unmapping the db here. */ + if( USEFETCH(pPager) ){ + sqlite3OsUnfetch(pPager->fd, 0, 0); + } + } + } + + /* If there is a WAL file in the file-system, open this database in WAL + ** mode. Otherwise, the following function call is a no-op. + */ + rc = pagerOpenWalIfPresent(pPager); +#ifndef SQLITE_OMIT_WAL + assert( pPager->pWal==0 || rc==SQLITE_OK ); +#endif + } + + if( pagerUseWal(pPager) ){ + assert( rc==SQLITE_OK ); + rc = pagerBeginReadTransaction(pPager); + } + + if( pPager->tempFile==0 && pPager->eState==PAGER_OPEN && rc==SQLITE_OK ){ + rc = pagerPagecount(pPager, &pPager->dbSize); + } + + failed: + if( rc!=SQLITE_OK ){ + assert( !MEMDB ); + pager_unlock(pPager); + assert( pPager->eState==PAGER_OPEN ); + }else{ + pPager->eState = PAGER_READER; + pPager->hasHeldSharedLock = 1; + } + return rc; +} + +/* +** If the reference count has reached zero, rollback any active +** transaction and unlock the pager. +** +** Except, in locking_mode=EXCLUSIVE when there is nothing to in +** the rollback journal, the unlock is not performed and there is +** nothing to rollback, so this routine is a no-op. +*/ +static void pagerUnlockIfUnused(Pager *pPager){ + if( sqlite3PcacheRefCount(pPager->pPCache)==0 ){ + assert( pPager->nMmapOut==0 ); /* because page1 is never memory mapped */ + pagerUnlockAndRollback(pPager); + } +} + +/* +** The page getter methods each try to acquire a reference to a +** page with page number pgno. If the requested reference is +** successfully obtained, it is copied to *ppPage and SQLITE_OK returned. +** +** There are different implementations of the getter method depending +** on the current state of the pager. +** +** getPageNormal() -- The normal getter +** getPageError() -- Used if the pager is in an error state +** getPageMmap() -- Used if memory-mapped I/O is enabled +** +** If the requested page is already in the cache, it is returned. +** Otherwise, a new page object is allocated and populated with data +** read from the database file. In some cases, the pcache module may +** choose not to allocate a new page object and may reuse an existing +** object with no outstanding references. +** +** The extra data appended to a page is always initialized to zeros the +** first time a page is loaded into memory. If the page requested is +** already in the cache when this function is called, then the extra +** data is left as it was when the page object was last used. +** +** If the database image is smaller than the requested page or if +** the flags parameter contains the PAGER_GET_NOCONTENT bit and the +** requested page is not already stored in the cache, then no +** actual disk read occurs. In this case the memory image of the +** page is initialized to all zeros. +** +** If PAGER_GET_NOCONTENT is true, it means that we do not care about +** the contents of the page. This occurs in two scenarios: +** +** a) When reading a free-list leaf page from the database, and +** +** b) When a savepoint is being rolled back and we need to load +** a new page into the cache to be filled with the data read +** from the savepoint journal. +** +** If PAGER_GET_NOCONTENT is true, then the data returned is zeroed instead +** of being read from the database. Additionally, the bits corresponding +** to pgno in Pager.pInJournal (bitvec of pages already written to the +** journal file) and the PagerSavepoint.pInSavepoint bitvecs of any open +** savepoints are set. This means if the page is made writable at any +** point in the future, using a call to sqlite3PagerWrite(), its contents +** will not be journaled. This saves IO. +** +** The acquisition might fail for several reasons. In all cases, +** an appropriate error code is returned and *ppPage is set to NULL. +** +** See also sqlite3PagerLookup(). Both this routine and Lookup() attempt +** to find a page in the in-memory cache first. If the page is not already +** in memory, this routine goes to disk to read it in whereas Lookup() +** just returns 0. This routine acquires a read-lock the first time it +** has to go to disk, and could also playback an old journal if necessary. +** Since Lookup() never goes to disk, it never has to deal with locks +** or journal files. +*/ +static int getPageNormal( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + int rc = SQLITE_OK; + PgHdr *pPg; + u8 noContent; /* True if PAGER_GET_NOCONTENT is set */ + sqlite3_pcache_page *pBase; + + assert( pPager->errCode==SQLITE_OK ); + assert( pPager->eState>=PAGER_READER ); + assert( assert_pager_state(pPager) ); + assert( pPager->hasHeldSharedLock==1 ); + + if( pgno==0 ) return SQLITE_CORRUPT_BKPT; + pBase = sqlite3PcacheFetch(pPager->pPCache, pgno, 3); + if( pBase==0 ){ + pPg = 0; + rc = sqlite3PcacheFetchStress(pPager->pPCache, pgno, &pBase); + if( rc!=SQLITE_OK ) goto pager_acquire_err; + if( pBase==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto pager_acquire_err; + } + } + pPg = *ppPage = sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pBase); + assert( pPg==(*ppPage) ); + assert( pPg->pgno==pgno ); + assert( pPg->pPager==pPager || pPg->pPager==0 ); + + noContent = (flags & PAGER_GET_NOCONTENT)!=0; + if( pPg->pPager && !noContent ){ + /* In this case the pcache already contains an initialized copy of + ** the page. Return without further ado. */ + assert( pgno!=PAGER_SJ_PGNO(pPager) ); + pPager->aStat[PAGER_STAT_HIT]++; + return SQLITE_OK; + + }else{ + /* The pager cache has created a new page. Its content needs to + ** be initialized. But first some error checks: + ** + ** (*) obsolete. Was: maximum page number is 2^31 + ** (2) Never try to fetch the locking page + */ + if( pgno==PAGER_SJ_PGNO(pPager) ){ + rc = SQLITE_CORRUPT_BKPT; + goto pager_acquire_err; + } + + pPg->pPager = pPager; + + assert( !isOpen(pPager->fd) || !MEMDB ); + if( !isOpen(pPager->fd) || pPager->dbSize<pgno || noContent ){ + if( pgno>pPager->mxPgno ){ + rc = SQLITE_FULL; + if( pgno<=pPager->dbSize ){ + sqlite3PcacheRelease(pPg); + pPg = 0; + } + goto pager_acquire_err; + } + if( noContent ){ + /* Failure to set the bits in the InJournal bit-vectors is benign. + ** It merely means that we might do some extra work to journal a + ** page that does not need to be journaled. Nevertheless, be sure + ** to test the case where a malloc error occurs while trying to set + ** a bit in a bit vector. + */ + sqlite3BeginBenignMalloc(); + if( pgno<=pPager->dbOrigSize ){ + TESTONLY( rc = ) sqlite3BitvecSet(pPager->pInJournal, pgno); + testcase( rc==SQLITE_NOMEM ); + } + TESTONLY( rc = ) addToSavepointBitvecs(pPager, pgno); + testcase( rc==SQLITE_NOMEM ); + sqlite3EndBenignMalloc(); + } + memset(pPg->pData, 0, pPager->pageSize); + IOTRACE(("ZERO %p %d\n", pPager, pgno)); + }else{ + assert( pPg->pPager==pPager ); + pPager->aStat[PAGER_STAT_MISS]++; + rc = readDbPage(pPg); + if( rc!=SQLITE_OK ){ + goto pager_acquire_err; + } + } + pager_set_pagehash(pPg); + } + return SQLITE_OK; + +pager_acquire_err: + assert( rc!=SQLITE_OK ); + if( pPg ){ + sqlite3PcacheDrop(pPg); + } + pagerUnlockIfUnused(pPager); + *ppPage = 0; + return rc; +} + +#if SQLITE_MAX_MMAP_SIZE>0 +/* The page getter for when memory-mapped I/O is enabled */ +static int getPageMMap( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + int rc = SQLITE_OK; + PgHdr *pPg = 0; + u32 iFrame = 0; /* Frame to read from WAL file */ + + /* It is acceptable to use a read-only (mmap) page for any page except + ** page 1 if there is no write-transaction open or the ACQUIRE_READONLY + ** flag was specified by the caller. And so long as the db is not a + ** temporary or in-memory database. */ + const int bMmapOk = (pgno>1 + && (pPager->eState==PAGER_READER || (flags & PAGER_GET_READONLY)) + ); + + assert( USEFETCH(pPager) ); + + /* Optimization note: Adding the "pgno<=1" term before "pgno==0" here + ** allows the compiler optimizer to reuse the results of the "pgno>1" + ** test in the previous statement, and avoid testing pgno==0 in the + ** common case where pgno is large. */ + if( pgno<=1 && pgno==0 ){ + return SQLITE_CORRUPT_BKPT; + } + assert( pPager->eState>=PAGER_READER ); + assert( assert_pager_state(pPager) ); + assert( pPager->hasHeldSharedLock==1 ); + assert( pPager->errCode==SQLITE_OK ); + + if( bMmapOk && pagerUseWal(pPager) ){ + rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame); + if( rc!=SQLITE_OK ){ + *ppPage = 0; + return rc; + } + } + if( bMmapOk && iFrame==0 ){ + void *pData = 0; + rc = sqlite3OsFetch(pPager->fd, + (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData + ); + if( rc==SQLITE_OK && pData ){ + if( pPager->eState>PAGER_READER || pPager->tempFile ){ + pPg = sqlite3PagerLookup(pPager, pgno); + } + if( pPg==0 ){ + rc = pagerAcquireMapPage(pPager, pgno, pData, &pPg); + }else{ + sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1)*pPager->pageSize, pData); + } + if( pPg ){ + assert( rc==SQLITE_OK ); + *ppPage = pPg; + return SQLITE_OK; + } + } + if( rc!=SQLITE_OK ){ + *ppPage = 0; + return rc; + } + } + return getPageNormal(pPager, pgno, ppPage, flags); +} +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + +/* The page getter method for when the pager is an error state */ +static int getPageError( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + UNUSED_PARAMETER(pgno); + UNUSED_PARAMETER(flags); + assert( pPager->errCode!=SQLITE_OK ); + *ppPage = 0; + return pPager->errCode; +} + + +/* Dispatch all page fetch requests to the appropriate getter method. +*/ +SQLITE_PRIVATE int sqlite3PagerGet( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + /* printf("PAGE %u\n", pgno); fflush(stdout); */ + return pPager->xGet(pPager, pgno, ppPage, flags); +} + +/* +** Acquire a page if it is already in the in-memory cache. Do +** not read the page from disk. Return a pointer to the page, +** or 0 if the page is not in cache. +** +** See also sqlite3PagerGet(). The difference between this routine +** and sqlite3PagerGet() is that _get() will go to the disk and read +** in the page if the page is not already in cache. This routine +** returns NULL if the page is not in cache or if a disk I/O error +** has ever happened. +*/ +SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){ + sqlite3_pcache_page *pPage; + assert( pPager!=0 ); + assert( pgno!=0 ); + assert( pPager->pPCache!=0 ); + pPage = sqlite3PcacheFetch(pPager->pPCache, pgno, 0); + assert( pPage==0 || pPager->hasHeldSharedLock ); + if( pPage==0 ) return 0; + return sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pPage); +} + +/* +** Release a page reference. +** +** The sqlite3PagerUnref() and sqlite3PagerUnrefNotNull() may only be used +** if we know that the page being released is not the last reference to page1. +** The btree layer always holds page1 open until the end, so these first +** two routines can be used to release any page other than BtShared.pPage1. +** The assert() at tag-20230419-2 proves that this constraint is always +** honored. +** +** Use sqlite3PagerUnrefPageOne() to release page1. This latter routine +** checks the total number of outstanding pages and if the number of +** pages reaches zero it drops the database lock. +*/ +SQLITE_PRIVATE void sqlite3PagerUnrefNotNull(DbPage *pPg){ + TESTONLY( Pager *pPager = pPg->pPager; ) + assert( pPg!=0 ); + if( pPg->flags & PGHDR_MMAP ){ + assert( pPg->pgno!=1 ); /* Page1 is never memory mapped */ + pagerReleaseMapPage(pPg); + }else{ + sqlite3PcacheRelease(pPg); + } + /* Do not use this routine to release the last reference to page1 */ + assert( sqlite3PcacheRefCount(pPager->pPCache)>0 ); /* tag-20230419-2 */ +} +SQLITE_PRIVATE void sqlite3PagerUnref(DbPage *pPg){ + if( pPg ) sqlite3PagerUnrefNotNull(pPg); +} +SQLITE_PRIVATE void sqlite3PagerUnrefPageOne(DbPage *pPg){ + Pager *pPager; + assert( pPg!=0 ); + assert( pPg->pgno==1 ); + assert( (pPg->flags & PGHDR_MMAP)==0 ); /* Page1 is never memory mapped */ + pPager = pPg->pPager; + sqlite3PcacheRelease(pPg); + pagerUnlockIfUnused(pPager); +} + +/* +** This function is called at the start of every write transaction. +** There must already be a RESERVED or EXCLUSIVE lock on the database +** file when this routine is called. +** +** Open the journal file for pager pPager and write a journal header +** to the start of it. If there are active savepoints, open the sub-journal +** as well. This function is only used when the journal file is being +** opened to write a rollback log for a transaction. It is not used +** when opening a hot journal file to roll it back. +** +** If the journal file is already open (as it may be in exclusive mode), +** then this function just writes a journal header to the start of the +** already open file. +** +** Whether or not the journal file is opened by this function, the +** Pager.pInJournal bitvec structure is allocated. +** +** Return SQLITE_OK if everything is successful. Otherwise, return +** SQLITE_NOMEM if the attempt to allocate Pager.pInJournal fails, or +** an IO error code if opening or writing the journal file fails. +*/ +static int pager_open_journal(Pager *pPager){ + int rc = SQLITE_OK; /* Return code */ + sqlite3_vfs * const pVfs = pPager->pVfs; /* Local cache of vfs pointer */ + + assert( pPager->eState==PAGER_WRITER_LOCKED ); + assert( assert_pager_state(pPager) ); + assert( pPager->pInJournal==0 ); + + /* If already in the error state, this function is a no-op. But on + ** the other hand, this routine is never called if we are already in + ** an error state. */ + if( NEVER(pPager->errCode) ) return pPager->errCode; + + if( !pagerUseWal(pPager) && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){ + pPager->pInJournal = sqlite3BitvecCreate(pPager->dbSize); + if( pPager->pInJournal==0 ){ + return SQLITE_NOMEM_BKPT; + } + + /* Open the journal file if it is not already open. */ + if( !isOpen(pPager->jfd) ){ + if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){ + sqlite3MemJournalOpen(pPager->jfd); + }else{ + int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; + int nSpill; + + if( pPager->tempFile ){ + flags |= (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL); + flags |= SQLITE_OPEN_EXCLUSIVE; + nSpill = sqlite3Config.nStmtSpill; + }else{ + flags |= SQLITE_OPEN_MAIN_JOURNAL; + nSpill = jrnlBufferSize(pPager); + } + + /* Verify that the database still has the same name as it did when + ** it was originally opened. */ + rc = databaseIsUnmoved(pPager); + if( rc==SQLITE_OK ){ + rc = sqlite3JournalOpen ( + pVfs, pPager->zJournal, pPager->jfd, flags, nSpill + ); + } + } + assert( rc!=SQLITE_OK || isOpen(pPager->jfd) ); + } + + + /* Write the first journal header to the journal file and open + ** the sub-journal if necessary. + */ + if( rc==SQLITE_OK ){ + /* TODO: Check if all of these are really required. */ + pPager->nRec = 0; + pPager->journalOff = 0; + pPager->setSuper = 0; + pPager->journalHdr = 0; + rc = writeJournalHdr(pPager); + } + } + + if( rc!=SQLITE_OK ){ + sqlite3BitvecDestroy(pPager->pInJournal); + pPager->pInJournal = 0; + pPager->journalOff = 0; + }else{ + assert( pPager->eState==PAGER_WRITER_LOCKED ); + pPager->eState = PAGER_WRITER_CACHEMOD; + } + + return rc; +} + +/* +** Begin a write-transaction on the specified pager object. If a +** write-transaction has already been opened, this function is a no-op. +** +** If the exFlag argument is false, then acquire at least a RESERVED +** lock on the database file. If exFlag is true, then acquire at least +** an EXCLUSIVE lock. If such a lock is already held, no locking +** functions need be called. +** +** If the subjInMemory argument is non-zero, then any sub-journal opened +** within this transaction will be opened as an in-memory file. This +** has no effect if the sub-journal is already opened (as it may be when +** running in exclusive mode) or if the transaction does not require a +** sub-journal. If the subjInMemory argument is zero, then any required +** sub-journal is implemented in-memory if pPager is an in-memory database, +** or using a temporary file otherwise. +*/ +SQLITE_PRIVATE int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){ + int rc = SQLITE_OK; + + if( pPager->errCode ) return pPager->errCode; + assert( pPager->eState>=PAGER_READER && pPager->eState<PAGER_ERROR ); + pPager->subjInMemory = (u8)subjInMemory; + + if( pPager->eState==PAGER_READER ){ + assert( pPager->pInJournal==0 ); + + if( pagerUseWal(pPager) ){ + /* If the pager is configured to use locking_mode=exclusive, and an + ** exclusive lock on the database is not already held, obtain it now. + */ + if( pPager->exclusiveMode && sqlite3WalExclusiveMode(pPager->pWal, -1) ){ + rc = pagerLockDb(pPager, EXCLUSIVE_LOCK); + if( rc!=SQLITE_OK ){ + return rc; + } + (void)sqlite3WalExclusiveMode(pPager->pWal, 1); + } + + /* Grab the write lock on the log file. If successful, upgrade to + ** PAGER_RESERVED state. Otherwise, return an error code to the caller. + ** The busy-handler is not invoked if another connection already + ** holds the write-lock. If possible, the upper layer will call it. + */ + rc = sqlite3WalBeginWriteTransaction(pPager->pWal); + }else{ + /* Obtain a RESERVED lock on the database file. If the exFlag parameter + ** is true, then immediately upgrade this to an EXCLUSIVE lock. The + ** busy-handler callback can be used when upgrading to the EXCLUSIVE + ** lock, but not when obtaining the RESERVED lock. + */ + rc = pagerLockDb(pPager, RESERVED_LOCK); + if( rc==SQLITE_OK && exFlag ){ + rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); + } + } + + if( rc==SQLITE_OK ){ + /* Change to WRITER_LOCKED state. + ** + ** WAL mode sets Pager.eState to PAGER_WRITER_LOCKED or CACHEMOD + ** when it has an open transaction, but never to DBMOD or FINISHED. + ** This is because in those states the code to roll back savepoint + ** transactions may copy data from the sub-journal into the database + ** file as well as into the page cache. Which would be incorrect in + ** WAL mode. + */ + pPager->eState = PAGER_WRITER_LOCKED; + pPager->dbHintSize = pPager->dbSize; + pPager->dbFileSize = pPager->dbSize; + pPager->dbOrigSize = pPager->dbSize; + pPager->journalOff = 0; + } + + assert( rc==SQLITE_OK || pPager->eState==PAGER_READER ); + assert( rc!=SQLITE_OK || pPager->eState==PAGER_WRITER_LOCKED ); + assert( assert_pager_state(pPager) ); + } + + PAGERTRACE(("TRANSACTION %d\n", PAGERID(pPager))); + return rc; +} + +/* +** Write page pPg onto the end of the rollback journal. +*/ +static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + int rc; + u32 cksum; + char *pData2; + i64 iOff = pPager->journalOff; + + /* We should never write to the journal file the page that + ** contains the database locks. The following assert verifies + ** that we do not. */ + assert( pPg->pgno!=PAGER_SJ_PGNO(pPager) ); + + assert( pPager->journalHdr<=pPager->journalOff ); + pData2 = pPg->pData; + cksum = pager_cksum(pPager, (u8*)pData2); + + /* Even if an IO or diskfull error occurs while journalling the + ** page in the block above, set the need-sync flag for the page. + ** Otherwise, when the transaction is rolled back, the logic in + ** playback_one_page() will think that the page needs to be restored + ** in the database file. And if an IO error occurs while doing so, + ** then corruption may follow. + */ + pPg->flags |= PGHDR_NEED_SYNC; + + rc = write32bits(pPager->jfd, iOff, pPg->pgno); + if( rc!=SQLITE_OK ) return rc; + rc = sqlite3OsWrite(pPager->jfd, pData2, pPager->pageSize, iOff+4); + if( rc!=SQLITE_OK ) return rc; + rc = write32bits(pPager->jfd, iOff+pPager->pageSize+4, cksum); + if( rc!=SQLITE_OK ) return rc; + + IOTRACE(("JOUT %p %d %lld %d\n", pPager, pPg->pgno, + pPager->journalOff, pPager->pageSize)); + PAGER_INCR(sqlite3_pager_writej_count); + PAGERTRACE(("JOURNAL %d page %d needSync=%d hash(%08x)\n", + PAGERID(pPager), pPg->pgno, + ((pPg->flags&PGHDR_NEED_SYNC)?1:0), pager_pagehash(pPg))); + + pPager->journalOff += 8 + pPager->pageSize; + pPager->nRec++; + assert( pPager->pInJournal!=0 ); + rc = sqlite3BitvecSet(pPager->pInJournal, pPg->pgno); + testcase( rc==SQLITE_NOMEM ); + assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); + rc |= addToSavepointBitvecs(pPager, pPg->pgno); + assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); + return rc; +} + +/* +** Mark a single data page as writeable. The page is written into the +** main journal or sub-journal as required. If the page is written into +** one of the journals, the corresponding bit is set in the +** Pager.pInJournal bitvec and the PagerSavepoint.pInSavepoint bitvecs +** of any open savepoints as appropriate. +*/ +static int pager_write(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + int rc = SQLITE_OK; + + /* This routine is not called unless a write-transaction has already + ** been started. The journal file may or may not be open at this point. + ** It is never called in the ERROR state. + */ + assert( pPager->eState==PAGER_WRITER_LOCKED + || pPager->eState==PAGER_WRITER_CACHEMOD + || pPager->eState==PAGER_WRITER_DBMOD + ); + assert( assert_pager_state(pPager) ); + assert( pPager->errCode==0 ); + assert( pPager->readOnly==0 ); + CHECK_PAGE(pPg); + + /* The journal file needs to be opened. Higher level routines have already + ** obtained the necessary locks to begin the write-transaction, but the + ** rollback journal might not yet be open. Open it now if this is the case. + ** + ** This is done before calling sqlite3PcacheMakeDirty() on the page. + ** Otherwise, if it were done after calling sqlite3PcacheMakeDirty(), then + ** an error might occur and the pager would end up in WRITER_LOCKED state + ** with pages marked as dirty in the cache. + */ + if( pPager->eState==PAGER_WRITER_LOCKED ){ + rc = pager_open_journal(pPager); + if( rc!=SQLITE_OK ) return rc; + } + assert( pPager->eState>=PAGER_WRITER_CACHEMOD ); + assert( assert_pager_state(pPager) ); + + /* Mark the page that is about to be modified as dirty. */ + sqlite3PcacheMakeDirty(pPg); + + /* If a rollback journal is in use, them make sure the page that is about + ** to change is in the rollback journal, or if the page is a new page off + ** then end of the file, make sure it is marked as PGHDR_NEED_SYNC. + */ + assert( (pPager->pInJournal!=0) == isOpen(pPager->jfd) ); + if( pPager->pInJournal!=0 + && sqlite3BitvecTestNotNull(pPager->pInJournal, pPg->pgno)==0 + ){ + assert( pagerUseWal(pPager)==0 ); + if( pPg->pgno<=pPager->dbOrigSize ){ + rc = pagerAddPageToRollbackJournal(pPg); + if( rc!=SQLITE_OK ){ + return rc; + } + }else{ + if( pPager->eState!=PAGER_WRITER_DBMOD ){ + pPg->flags |= PGHDR_NEED_SYNC; + } + PAGERTRACE(("APPEND %d page %d needSync=%d\n", + PAGERID(pPager), pPg->pgno, + ((pPg->flags&PGHDR_NEED_SYNC)?1:0))); + } + } + + /* The PGHDR_DIRTY bit is set above when the page was added to the dirty-list + ** and before writing the page into the rollback journal. Wait until now, + ** after the page has been successfully journalled, before setting the + ** PGHDR_WRITEABLE bit that indicates that the page can be safely modified. + */ + pPg->flags |= PGHDR_WRITEABLE; + + /* If the statement journal is open and the page is not in it, + ** then write the page into the statement journal. + */ + if( pPager->nSavepoint>0 ){ + rc = subjournalPageIfRequired(pPg); + } + + /* Update the database size and return. */ + if( pPager->dbSize<pPg->pgno ){ + pPager->dbSize = pPg->pgno; + } + return rc; +} + +/* +** This is a variant of sqlite3PagerWrite() that runs when the sector size +** is larger than the page size. SQLite makes the (reasonable) assumption that +** all bytes of a sector are written together by hardware. Hence, all bytes of +** a sector need to be journalled in case of a power loss in the middle of +** a write. +** +** Usually, the sector size is less than or equal to the page size, in which +** case pages can be individually written. This routine only runs in the +** exceptional case where the page size is smaller than the sector size. +*/ +static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){ + int rc = SQLITE_OK; /* Return code */ + Pgno nPageCount; /* Total number of pages in database file */ + Pgno pg1; /* First page of the sector pPg is located on. */ + int nPage = 0; /* Number of pages starting at pg1 to journal */ + int ii; /* Loop counter */ + int needSync = 0; /* True if any page has PGHDR_NEED_SYNC */ + Pager *pPager = pPg->pPager; /* The pager that owns pPg */ + Pgno nPagePerSector = (pPager->sectorSize/pPager->pageSize); + + /* Set the doNotSpill NOSYNC bit to 1. This is because we cannot allow + ** a journal header to be written between the pages journaled by + ** this function. + */ + assert( !MEMDB ); + assert( (pPager->doNotSpill & SPILLFLAG_NOSYNC)==0 ); + pPager->doNotSpill |= SPILLFLAG_NOSYNC; + + /* This trick assumes that both the page-size and sector-size are + ** an integer power of 2. It sets variable pg1 to the identifier + ** of the first page of the sector pPg is located on. + */ + pg1 = ((pPg->pgno-1) & ~(nPagePerSector-1)) + 1; + + nPageCount = pPager->dbSize; + if( pPg->pgno>nPageCount ){ + nPage = (pPg->pgno - pg1)+1; + }else if( (pg1+nPagePerSector-1)>nPageCount ){ + nPage = nPageCount+1-pg1; + }else{ + nPage = nPagePerSector; + } + assert(nPage>0); + assert(pg1<=pPg->pgno); + assert((pg1+nPage)>pPg->pgno); + + for(ii=0; ii<nPage && rc==SQLITE_OK; ii++){ + Pgno pg = pg1+ii; + PgHdr *pPage; + if( pg==pPg->pgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){ + if( pg!=PAGER_SJ_PGNO(pPager) ){ + rc = sqlite3PagerGet(pPager, pg, &pPage, 0); + if( rc==SQLITE_OK ){ + rc = pager_write(pPage); + if( pPage->flags&PGHDR_NEED_SYNC ){ + needSync = 1; + } + sqlite3PagerUnrefNotNull(pPage); + } + } + }else if( (pPage = sqlite3PagerLookup(pPager, pg))!=0 ){ + if( pPage->flags&PGHDR_NEED_SYNC ){ + needSync = 1; + } + sqlite3PagerUnrefNotNull(pPage); + } + } + + /* If the PGHDR_NEED_SYNC flag is set for any of the nPage pages + ** starting at pg1, then it needs to be set for all of them. Because + ** writing to any of these nPage pages may damage the others, the + ** journal file must contain sync()ed copies of all of them + ** before any of them can be written out to the database file. + */ + if( rc==SQLITE_OK && needSync ){ + assert( !MEMDB ); + for(ii=0; ii<nPage; ii++){ + PgHdr *pPage = sqlite3PagerLookup(pPager, pg1+ii); + if( pPage ){ + pPage->flags |= PGHDR_NEED_SYNC; + sqlite3PagerUnrefNotNull(pPage); + } + } + } + + assert( (pPager->doNotSpill & SPILLFLAG_NOSYNC)!=0 ); + pPager->doNotSpill &= ~SPILLFLAG_NOSYNC; + return rc; +} + +/* +** Mark a data page as writeable. This routine must be called before +** making changes to a page. The caller must check the return value +** of this function and be careful not to change any page data unless +** this routine returns SQLITE_OK. +** +** The difference between this function and pager_write() is that this +** function also deals with the special case where 2 or more pages +** fit on a single disk sector. In this case all co-resident pages +** must have been written to the journal file before returning. +** +** If an error occurs, SQLITE_NOMEM or an IO error code is returned +** as appropriate. Otherwise, SQLITE_OK. +*/ +SQLITE_PRIVATE int sqlite3PagerWrite(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + assert( (pPg->flags & PGHDR_MMAP)==0 ); + assert( pPager->eState>=PAGER_WRITER_LOCKED ); + assert( assert_pager_state(pPager) ); + if( (pPg->flags & PGHDR_WRITEABLE)!=0 && pPager->dbSize>=pPg->pgno ){ + if( pPager->nSavepoint ) return subjournalPageIfRequired(pPg); + return SQLITE_OK; + }else if( pPager->errCode ){ + return pPager->errCode; + }else if( pPager->sectorSize > (u32)pPager->pageSize ){ + assert( pPager->tempFile==0 ); + return pagerWriteLargeSector(pPg); + }else{ + return pager_write(pPg); + } +} + +/* +** Return TRUE if the page given in the argument was previously passed +** to sqlite3PagerWrite(). In other words, return TRUE if it is ok +** to change the content of the page. +*/ +#ifndef NDEBUG +SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage *pPg){ + return pPg->flags & PGHDR_WRITEABLE; +} +#endif + +/* +** A call to this routine tells the pager that it is not necessary to +** write the information on page pPg back to the disk, even though +** that page might be marked as dirty. This happens, for example, when +** the page has been added as a leaf of the freelist and so its +** content no longer matters. +** +** The overlying software layer calls this routine when all of the data +** on the given page is unused. The pager marks the page as clean so +** that it does not get written to disk. +** +** Tests show that this optimization can quadruple the speed of large +** DELETE operations. +** +** This optimization cannot be used with a temp-file, as the page may +** have been dirty at the start of the transaction. In that case, if +** memory pressure forces page pPg out of the cache, the data does need +** to be written out to disk so that it may be read back in if the +** current transaction is rolled back. +*/ +SQLITE_PRIVATE void sqlite3PagerDontWrite(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + if( !pPager->tempFile && (pPg->flags&PGHDR_DIRTY) && pPager->nSavepoint==0 ){ + PAGERTRACE(("DONT_WRITE page %d of %d\n", pPg->pgno, PAGERID(pPager))); + IOTRACE(("CLEAN %p %d\n", pPager, pPg->pgno)) + pPg->flags |= PGHDR_DONT_WRITE; + pPg->flags &= ~PGHDR_WRITEABLE; + testcase( pPg->flags & PGHDR_NEED_SYNC ); + pager_set_pagehash(pPg); + } +} + +/* +** This routine is called to increment the value of the database file +** change-counter, stored as a 4-byte big-endian integer starting at +** byte offset 24 of the pager file. The secondary change counter at +** 92 is also updated, as is the SQLite version number at offset 96. +** +** But this only happens if the pPager->changeCountDone flag is false. +** To avoid excess churning of page 1, the update only happens once. +** See also the pager_write_changecounter() routine that does an +** unconditional update of the change counters. +** +** If the isDirectMode flag is zero, then this is done by calling +** sqlite3PagerWrite() on page 1, then modifying the contents of the +** page data. In this case the file will be updated when the current +** transaction is committed. +** +** The isDirectMode flag may only be non-zero if the library was compiled +** with the SQLITE_ENABLE_ATOMIC_WRITE macro defined. In this case, +** if isDirect is non-zero, then the database file is updated directly +** by writing an updated version of page 1 using a call to the +** sqlite3OsWrite() function. +*/ +static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ + int rc = SQLITE_OK; + + assert( pPager->eState==PAGER_WRITER_CACHEMOD + || pPager->eState==PAGER_WRITER_DBMOD + ); + assert( assert_pager_state(pPager) ); + + /* Declare and initialize constant integer 'isDirect'. If the + ** atomic-write optimization is enabled in this build, then isDirect + ** is initialized to the value passed as the isDirectMode parameter + ** to this function. Otherwise, it is always set to zero. + ** + ** The idea is that if the atomic-write optimization is not + ** enabled at compile time, the compiler can omit the tests of + ** 'isDirect' below, as well as the block enclosed in the + ** "if( isDirect )" condition. + */ +#ifndef SQLITE_ENABLE_ATOMIC_WRITE +# define DIRECT_MODE 0 + assert( isDirectMode==0 ); + UNUSED_PARAMETER(isDirectMode); +#else +# define DIRECT_MODE isDirectMode +#endif + + if( !pPager->changeCountDone && pPager->dbSize>0 ){ + PgHdr *pPgHdr; /* Reference to page 1 */ + + assert( !pPager->tempFile && isOpen(pPager->fd) ); + + /* Open page 1 of the file for writing. */ + rc = sqlite3PagerGet(pPager, 1, &pPgHdr, 0); + assert( pPgHdr==0 || rc==SQLITE_OK ); + + /* If page one was fetched successfully, and this function is not + ** operating in direct-mode, make page 1 writable. When not in + ** direct mode, page 1 is always held in cache and hence the PagerGet() + ** above is always successful - hence the ALWAYS on rc==SQLITE_OK. + */ + if( !DIRECT_MODE && ALWAYS(rc==SQLITE_OK) ){ + rc = sqlite3PagerWrite(pPgHdr); + } + + if( rc==SQLITE_OK ){ + /* Actually do the update of the change counter */ + pager_write_changecounter(pPgHdr); + + /* If running in direct mode, write the contents of page 1 to the file. */ + if( DIRECT_MODE ){ + const void *zBuf; + assert( pPager->dbFileSize>0 ); + zBuf = pPgHdr->pData; + if( rc==SQLITE_OK ){ + rc = sqlite3OsWrite(pPager->fd, zBuf, pPager->pageSize, 0); + pPager->aStat[PAGER_STAT_WRITE]++; + } + if( rc==SQLITE_OK ){ + /* Update the pager's copy of the change-counter. Otherwise, the + ** next time a read transaction is opened the cache will be + ** flushed (as the change-counter values will not match). */ + const void *pCopy = (const void *)&((const char *)zBuf)[24]; + memcpy(&pPager->dbFileVers, pCopy, sizeof(pPager->dbFileVers)); + pPager->changeCountDone = 1; + } + }else{ + pPager->changeCountDone = 1; + } + } + + /* Release the page reference. */ + sqlite3PagerUnref(pPgHdr); + } + return rc; +} + +/* +** Sync the database file to disk. This is a no-op for in-memory databases +** or pages with the Pager.noSync flag set. +** +** If successful, or if called on a pager for which it is a no-op, this +** function returns SQLITE_OK. Otherwise, an IO error code is returned. +*/ +SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zSuper){ + int rc = SQLITE_OK; + void *pArg = (void*)zSuper; + rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SYNC, pArg); + if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; + if( rc==SQLITE_OK && !pPager->noSync ){ + assert( !MEMDB ); + rc = sqlite3OsSync(pPager->fd, pPager->syncFlags); + } + return rc; +} + +/* +** This function may only be called while a write-transaction is active in +** rollback. If the connection is in WAL mode, this call is a no-op. +** Otherwise, if the connection does not already have an EXCLUSIVE lock on +** the database file, an attempt is made to obtain one. +** +** If the EXCLUSIVE lock is already held or the attempt to obtain it is +** successful, or the connection is in WAL mode, SQLITE_OK is returned. +** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is +** returned. +*/ +SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager *pPager){ + int rc = pPager->errCode; + assert( assert_pager_state(pPager) ); + if( rc==SQLITE_OK ){ + assert( pPager->eState==PAGER_WRITER_CACHEMOD + || pPager->eState==PAGER_WRITER_DBMOD + || pPager->eState==PAGER_WRITER_LOCKED + ); + assert( assert_pager_state(pPager) ); + if( 0==pagerUseWal(pPager) ){ + rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); + } + } + return rc; +} + +/* +** Sync the database file for the pager pPager. zSuper points to the name +** of a super-journal file that should be written into the individual +** journal file. zSuper may be NULL, which is interpreted as no +** super-journal (a single database transaction). +** +** This routine ensures that: +** +** * The database file change-counter is updated, +** * the journal is synced (unless the atomic-write optimization is used), +** * all dirty pages are written to the database file, +** * the database file is truncated (if required), and +** * the database file synced. +** +** The only thing that remains to commit the transaction is to finalize +** (delete, truncate or zero the first part of) the journal file (or +** delete the super-journal file if specified). +** +** Note that if zSuper==NULL, this does not overwrite a previous value +** passed to an sqlite3PagerCommitPhaseOne() call. +** +** If the final parameter - noSync - is true, then the database file itself +** is not synced. The caller must call sqlite3PagerSync() directly to +** sync the database file before calling CommitPhaseTwo() to delete the +** journal file in this case. +*/ +SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( + Pager *pPager, /* Pager object */ + const char *zSuper, /* If not NULL, the super-journal name */ + int noSync /* True to omit the xSync on the db file */ +){ + int rc = SQLITE_OK; /* Return code */ + + assert( pPager->eState==PAGER_WRITER_LOCKED + || pPager->eState==PAGER_WRITER_CACHEMOD + || pPager->eState==PAGER_WRITER_DBMOD + || pPager->eState==PAGER_ERROR + ); + assert( assert_pager_state(pPager) ); + + /* If a prior error occurred, report that error again. */ + if( NEVER(pPager->errCode) ) return pPager->errCode; + + /* Provide the ability to easily simulate an I/O error during testing */ + if( sqlite3FaultSim(400) ) return SQLITE_IOERR; + + PAGERTRACE(("DATABASE SYNC: File=%s zSuper=%s nSize=%d\n", + pPager->zFilename, zSuper, pPager->dbSize)); + + /* If no database changes have been made, return early. */ + if( pPager->eState<PAGER_WRITER_CACHEMOD ) return SQLITE_OK; + + assert( MEMDB==0 || pPager->tempFile ); + assert( isOpen(pPager->fd) || pPager->tempFile ); + if( 0==pagerFlushOnCommit(pPager, 1) ){ + /* If this is an in-memory db, or no pages have been written to, or this + ** function has already been called, it is mostly a no-op. However, any + ** backup in progress needs to be restarted. */ + sqlite3BackupRestart(pPager->pBackup); + }else{ + PgHdr *pList; + if( pagerUseWal(pPager) ){ + PgHdr *pPageOne = 0; + pList = sqlite3PcacheDirtyList(pPager->pPCache); + if( pList==0 ){ + /* Must have at least one page for the WAL commit flag. + ** Ticket [2d1a5c67dfc2363e44f29d9bbd57f] 2011-05-18 */ + rc = sqlite3PagerGet(pPager, 1, &pPageOne, 0); + pList = pPageOne; + pList->pDirty = 0; + } + assert( rc==SQLITE_OK ); + if( ALWAYS(pList) ){ + rc = pagerWalFrames(pPager, pList, pPager->dbSize, 1); + } + sqlite3PagerUnref(pPageOne); + if( rc==SQLITE_OK ){ + sqlite3PcacheCleanAll(pPager->pPCache); + } + }else{ + /* The bBatch boolean is true if the batch-atomic-write commit method + ** should be used. No rollback journal is created if batch-atomic-write + ** is enabled. + */ +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + sqlite3_file *fd = pPager->fd; + int bBatch = zSuper==0 /* An SQLITE_IOCAP_BATCH_ATOMIC commit */ + && (sqlite3OsDeviceCharacteristics(fd) & SQLITE_IOCAP_BATCH_ATOMIC) + && !pPager->noSync + && sqlite3JournalIsInMemory(pPager->jfd); +#else +# define bBatch 0 +#endif + +#ifdef SQLITE_ENABLE_ATOMIC_WRITE + /* The following block updates the change-counter. Exactly how it + ** does this depends on whether or not the atomic-update optimization + ** was enabled at compile time, and if this transaction meets the + ** runtime criteria to use the operation: + ** + ** * The file-system supports the atomic-write property for + ** blocks of size page-size, and + ** * This commit is not part of a multi-file transaction, and + ** * Exactly one page has been modified and store in the journal file. + ** + ** If the optimization was not enabled at compile time, then the + ** pager_incr_changecounter() function is called to update the change + ** counter in 'indirect-mode'. If the optimization is compiled in but + ** is not applicable to this transaction, call sqlite3JournalCreate() + ** to make sure the journal file has actually been created, then call + ** pager_incr_changecounter() to update the change-counter in indirect + ** mode. + ** + ** Otherwise, if the optimization is both enabled and applicable, + ** then call pager_incr_changecounter() to update the change-counter + ** in 'direct' mode. In this case the journal file will never be + ** created for this transaction. + */ + if( bBatch==0 ){ + PgHdr *pPg; + assert( isOpen(pPager->jfd) + || pPager->journalMode==PAGER_JOURNALMODE_OFF + || pPager->journalMode==PAGER_JOURNALMODE_WAL + ); + if( !zSuper && isOpen(pPager->jfd) + && pPager->journalOff==jrnlBufferSize(pPager) + && pPager->dbSize>=pPager->dbOrigSize + && (!(pPg = sqlite3PcacheDirtyList(pPager->pPCache)) || 0==pPg->pDirty) + ){ + /* Update the db file change counter via the direct-write method. The + ** following call will modify the in-memory representation of page 1 + ** to include the updated change counter and then write page 1 + ** directly to the database file. Because of the atomic-write + ** property of the host file-system, this is safe. + */ + rc = pager_incr_changecounter(pPager, 1); + }else{ + rc = sqlite3JournalCreate(pPager->jfd); + if( rc==SQLITE_OK ){ + rc = pager_incr_changecounter(pPager, 0); + } + } + } +#else /* SQLITE_ENABLE_ATOMIC_WRITE */ +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + if( zSuper ){ + rc = sqlite3JournalCreate(pPager->jfd); + if( rc!=SQLITE_OK ) goto commit_phase_one_exit; + assert( bBatch==0 ); + } +#endif + rc = pager_incr_changecounter(pPager, 0); +#endif /* !SQLITE_ENABLE_ATOMIC_WRITE */ + if( rc!=SQLITE_OK ) goto commit_phase_one_exit; + + /* Write the super-journal name into the journal file. If a + ** super-journal file name has already been written to the journal file, + ** or if zSuper is NULL (no super-journal), then this call is a no-op. + */ + rc = writeSuperJournal(pPager, zSuper); + if( rc!=SQLITE_OK ) goto commit_phase_one_exit; + + /* Sync the journal file and write all dirty pages to the database. + ** If the atomic-update optimization is being used, this sync will not + ** create the journal file or perform any real IO. + ** + ** Because the change-counter page was just modified, unless the + ** atomic-update optimization is used it is almost certain that the + ** journal requires a sync here. However, in locking_mode=exclusive + ** on a system under memory pressure it is just possible that this is + ** not the case. In this case it is likely enough that the redundant + ** xSync() call will be changed to a no-op by the OS anyhow. + */ + rc = syncJournal(pPager, 0); + if( rc!=SQLITE_OK ) goto commit_phase_one_exit; + + pList = sqlite3PcacheDirtyList(pPager->pPCache); +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + if( bBatch ){ + rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, 0); + if( rc==SQLITE_OK ){ + rc = pager_write_pagelist(pPager, pList); + if( rc==SQLITE_OK ){ + rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE, 0); + } + if( rc!=SQLITE_OK ){ + sqlite3OsFileControlHint(fd, SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE, 0); + } + } + + if( (rc&0xFF)==SQLITE_IOERR && rc!=SQLITE_IOERR_NOMEM ){ + rc = sqlite3JournalCreate(pPager->jfd); + if( rc!=SQLITE_OK ){ + sqlite3OsClose(pPager->jfd); + goto commit_phase_one_exit; + } + bBatch = 0; + }else{ + sqlite3OsClose(pPager->jfd); + } + } +#endif /* SQLITE_ENABLE_BATCH_ATOMIC_WRITE */ + + if( bBatch==0 ){ + rc = pager_write_pagelist(pPager, pList); + } + if( rc!=SQLITE_OK ){ + assert( rc!=SQLITE_IOERR_BLOCKED ); + goto commit_phase_one_exit; + } + sqlite3PcacheCleanAll(pPager->pPCache); + + /* If the file on disk is smaller than the database image, use + ** pager_truncate to grow the file here. This can happen if the database + ** image was extended as part of the current transaction and then the + ** last page in the db image moved to the free-list. In this case the + ** last page is never written out to disk, leaving the database file + ** undersized. Fix this now if it is the case. */ + if( pPager->dbSize>pPager->dbFileSize ){ + Pgno nNew = pPager->dbSize - (pPager->dbSize==PAGER_SJ_PGNO(pPager)); + assert( pPager->eState==PAGER_WRITER_DBMOD ); + rc = pager_truncate(pPager, nNew); + if( rc!=SQLITE_OK ) goto commit_phase_one_exit; + } + + /* Finally, sync the database file. */ + if( !noSync ){ + rc = sqlite3PagerSync(pPager, zSuper); + } + IOTRACE(("DBSYNC %p\n", pPager)) + } + } + +commit_phase_one_exit: + if( rc==SQLITE_OK && !pagerUseWal(pPager) ){ + pPager->eState = PAGER_WRITER_FINISHED; + } + return rc; +} + + +/* +** When this function is called, the database file has been completely +** updated to reflect the changes made by the current transaction and +** synced to disk. The journal file still exists in the file-system +** though, and if a failure occurs at this point it will eventually +** be used as a hot-journal and the current transaction rolled back. +** +** This function finalizes the journal file, either by deleting, +** truncating or partially zeroing it, so that it cannot be used +** for hot-journal rollback. Once this is done the transaction is +** irrevocably committed. +** +** If an error occurs, an IO error code is returned and the pager +** moves into the error state. Otherwise, SQLITE_OK is returned. +*/ +SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){ + int rc = SQLITE_OK; /* Return code */ + + /* This routine should not be called if a prior error has occurred. + ** But if (due to a coding error elsewhere in the system) it does get + ** called, just return the same error code without doing anything. */ + if( NEVER(pPager->errCode) ) return pPager->errCode; + pPager->iDataVersion++; + + assert( pPager->eState==PAGER_WRITER_LOCKED + || pPager->eState==PAGER_WRITER_FINISHED + || (pagerUseWal(pPager) && pPager->eState==PAGER_WRITER_CACHEMOD) + ); + assert( assert_pager_state(pPager) ); + + /* An optimization. If the database was not actually modified during + ** this transaction, the pager is running in exclusive-mode and is + ** using persistent journals, then this function is a no-op. + ** + ** The start of the journal file currently contains a single journal + ** header with the nRec field set to 0. If such a journal is used as + ** a hot-journal during hot-journal rollback, 0 changes will be made + ** to the database file. So there is no need to zero the journal + ** header. Since the pager is in exclusive mode, there is no need + ** to drop any locks either. + */ + if( pPager->eState==PAGER_WRITER_LOCKED + && pPager->exclusiveMode + && pPager->journalMode==PAGER_JOURNALMODE_PERSIST + ){ + assert( pPager->journalOff==JOURNAL_HDR_SZ(pPager) || !pPager->journalOff ); + pPager->eState = PAGER_READER; + return SQLITE_OK; + } + + PAGERTRACE(("COMMIT %d\n", PAGERID(pPager))); + rc = pager_end_transaction(pPager, pPager->setSuper, 1); + return pager_error(pPager, rc); +} + +/* +** If a write transaction is open, then all changes made within the +** transaction are reverted and the current write-transaction is closed. +** The pager falls back to PAGER_READER state if successful, or PAGER_ERROR +** state if an error occurs. +** +** If the pager is already in PAGER_ERROR state when this function is called, +** it returns Pager.errCode immediately. No work is performed in this case. +** +** Otherwise, in rollback mode, this function performs two functions: +** +** 1) It rolls back the journal file, restoring all database file and +** in-memory cache pages to the state they were in when the transaction +** was opened, and +** +** 2) It finalizes the journal file, so that it is not used for hot +** rollback at any point in the future. +** +** Finalization of the journal file (task 2) is only performed if the +** rollback is successful. +** +** In WAL mode, all cache-entries containing data modified within the +** current transaction are either expelled from the cache or reverted to +** their pre-transaction state by re-reading data from the database or +** WAL files. The WAL transaction is then closed. +*/ +SQLITE_PRIVATE int sqlite3PagerRollback(Pager *pPager){ + int rc = SQLITE_OK; /* Return code */ + PAGERTRACE(("ROLLBACK %d\n", PAGERID(pPager))); + + /* PagerRollback() is a no-op if called in READER or OPEN state. If + ** the pager is already in the ERROR state, the rollback is not + ** attempted here. Instead, the error code is returned to the caller. + */ + assert( assert_pager_state(pPager) ); + if( pPager->eState==PAGER_ERROR ) return pPager->errCode; + if( pPager->eState<=PAGER_READER ) return SQLITE_OK; + + if( pagerUseWal(pPager) ){ + int rc2; + rc = sqlite3PagerSavepoint(pPager, SAVEPOINT_ROLLBACK, -1); + rc2 = pager_end_transaction(pPager, pPager->setSuper, 0); + if( rc==SQLITE_OK ) rc = rc2; + }else if( !isOpen(pPager->jfd) || pPager->eState==PAGER_WRITER_LOCKED ){ + int eState = pPager->eState; + rc = pager_end_transaction(pPager, 0, 0); + if( !MEMDB && eState>PAGER_WRITER_LOCKED ){ + /* This can happen using journal_mode=off. Move the pager to the error + ** state to indicate that the contents of the cache may not be trusted. + ** Any active readers will get SQLITE_ABORT. + */ + pPager->errCode = SQLITE_ABORT; + pPager->eState = PAGER_ERROR; + setGetterMethod(pPager); + return rc; + } + }else{ + rc = pager_playback(pPager, 0); + } + + assert( pPager->eState==PAGER_READER || rc!=SQLITE_OK ); + assert( rc==SQLITE_OK || rc==SQLITE_FULL || rc==SQLITE_CORRUPT + || rc==SQLITE_NOMEM || (rc&0xFF)==SQLITE_IOERR + || rc==SQLITE_CANTOPEN + ); + + /* If an error occurs during a ROLLBACK, we can no longer trust the pager + ** cache. So call pager_error() on the way out to make any error persistent. + */ + return pager_error(pPager, rc); +} + +/* +** Return TRUE if the database file is opened read-only. Return FALSE +** if the database is (in theory) writable. +*/ +SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager *pPager){ + return pPager->readOnly; +} + +#ifdef SQLITE_DEBUG +/* +** Return the sum of the reference counts for all pages held by pPager. +*/ +SQLITE_PRIVATE int sqlite3PagerRefcount(Pager *pPager){ + return sqlite3PcacheRefCount(pPager->pPCache); +} +#endif + +/* +** Return the approximate number of bytes of memory currently +** used by the pager and its associated cache. +*/ +SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager *pPager){ + int perPageSize = pPager->pageSize + pPager->nExtra + + (int)(sizeof(PgHdr) + 5*sizeof(void*)); + return perPageSize*sqlite3PcachePagecount(pPager->pPCache) + + sqlite3MallocSize(pPager) + + pPager->pageSize; +} + +/* +** Return the number of references to the specified page. +*/ +SQLITE_PRIVATE int sqlite3PagerPageRefcount(DbPage *pPage){ + return sqlite3PcachePageRefcount(pPage); +} + +#ifdef SQLITE_TEST +/* +** This routine is used for testing and analysis only. +*/ +SQLITE_PRIVATE int *sqlite3PagerStats(Pager *pPager){ + static int a[11]; + a[0] = sqlite3PcacheRefCount(pPager->pPCache); + a[1] = sqlite3PcachePagecount(pPager->pPCache); + a[2] = sqlite3PcacheGetCachesize(pPager->pPCache); + a[3] = pPager->eState==PAGER_OPEN ? -1 : (int) pPager->dbSize; + a[4] = pPager->eState; + a[5] = pPager->errCode; + a[6] = pPager->aStat[PAGER_STAT_HIT]; + a[7] = pPager->aStat[PAGER_STAT_MISS]; + a[8] = 0; /* Used to be pPager->nOvfl */ + a[9] = pPager->nRead; + a[10] = pPager->aStat[PAGER_STAT_WRITE]; + return a; +} +#endif + +/* +** Parameter eStat must be one of SQLITE_DBSTATUS_CACHE_HIT, _MISS, _WRITE, +** or _WRITE+1. The SQLITE_DBSTATUS_CACHE_WRITE+1 case is a translation +** of SQLITE_DBSTATUS_CACHE_SPILL. The _SPILL case is not contiguous because +** it was added later. +** +** Before returning, *pnVal is incremented by the +** current cache hit or miss count, according to the value of eStat. If the +** reset parameter is non-zero, the cache hit or miss count is zeroed before +** returning. +*/ +SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){ + + assert( eStat==SQLITE_DBSTATUS_CACHE_HIT + || eStat==SQLITE_DBSTATUS_CACHE_MISS + || eStat==SQLITE_DBSTATUS_CACHE_WRITE + || eStat==SQLITE_DBSTATUS_CACHE_WRITE+1 + ); + + assert( SQLITE_DBSTATUS_CACHE_HIT+1==SQLITE_DBSTATUS_CACHE_MISS ); + assert( SQLITE_DBSTATUS_CACHE_HIT+2==SQLITE_DBSTATUS_CACHE_WRITE ); + assert( PAGER_STAT_HIT==0 && PAGER_STAT_MISS==1 + && PAGER_STAT_WRITE==2 && PAGER_STAT_SPILL==3 ); + + eStat -= SQLITE_DBSTATUS_CACHE_HIT; + *pnVal += pPager->aStat[eStat]; + if( reset ){ + pPager->aStat[eStat] = 0; + } +} + +/* +** Return true if this is an in-memory or temp-file backed pager. +*/ +SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager *pPager){ + return pPager->tempFile || pPager->memVfs; +} + +/* +** Check that there are at least nSavepoint savepoints open. If there are +** currently less than nSavepoints open, then open one or more savepoints +** to make up the difference. If the number of savepoints is already +** equal to nSavepoint, then this function is a no-op. +** +** If a memory allocation fails, SQLITE_NOMEM is returned. If an error +** occurs while opening the sub-journal file, then an IO error code is +** returned. Otherwise, SQLITE_OK. +*/ +static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){ + int rc = SQLITE_OK; /* Return code */ + int nCurrent = pPager->nSavepoint; /* Current number of savepoints */ + int ii; /* Iterator variable */ + PagerSavepoint *aNew; /* New Pager.aSavepoint array */ + + assert( pPager->eState>=PAGER_WRITER_LOCKED ); + assert( assert_pager_state(pPager) ); + assert( nSavepoint>nCurrent && pPager->useJournal ); + + /* Grow the Pager.aSavepoint array using realloc(). Return SQLITE_NOMEM + ** if the allocation fails. Otherwise, zero the new portion in case a + ** malloc failure occurs while populating it in the for(...) loop below. + */ + aNew = (PagerSavepoint *)sqlite3Realloc( + pPager->aSavepoint, sizeof(PagerSavepoint)*nSavepoint + ); + if( !aNew ){ + return SQLITE_NOMEM_BKPT; + } + memset(&aNew[nCurrent], 0, (nSavepoint-nCurrent) * sizeof(PagerSavepoint)); + pPager->aSavepoint = aNew; + + /* Populate the PagerSavepoint structures just allocated. */ + for(ii=nCurrent; ii<nSavepoint; ii++){ + aNew[ii].nOrig = pPager->dbSize; + if( isOpen(pPager->jfd) && pPager->journalOff>0 ){ + aNew[ii].iOffset = pPager->journalOff; + }else{ + aNew[ii].iOffset = JOURNAL_HDR_SZ(pPager); + } + aNew[ii].iSubRec = pPager->nSubRec; + aNew[ii].pInSavepoint = sqlite3BitvecCreate(pPager->dbSize); + aNew[ii].bTruncateOnRelease = 1; + if( !aNew[ii].pInSavepoint ){ + return SQLITE_NOMEM_BKPT; + } + if( pagerUseWal(pPager) ){ + sqlite3WalSavepoint(pPager->pWal, aNew[ii].aWalData); + } + pPager->nSavepoint = ii+1; + } + assert( pPager->nSavepoint==nSavepoint ); + assertTruncateConstraint(pPager); + return rc; +} +SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){ + assert( pPager->eState>=PAGER_WRITER_LOCKED ); + assert( assert_pager_state(pPager) ); + + if( nSavepoint>pPager->nSavepoint && pPager->useJournal ){ + return pagerOpenSavepoint(pPager, nSavepoint); + }else{ + return SQLITE_OK; + } +} + + +/* +** This function is called to rollback or release (commit) a savepoint. +** The savepoint to release or rollback need not be the most recently +** created savepoint. +** +** Parameter op is always either SAVEPOINT_ROLLBACK or SAVEPOINT_RELEASE. +** If it is SAVEPOINT_RELEASE, then release and destroy the savepoint with +** index iSavepoint. If it is SAVEPOINT_ROLLBACK, then rollback all changes +** that have occurred since the specified savepoint was created. +** +** The savepoint to rollback or release is identified by parameter +** iSavepoint. A value of 0 means to operate on the outermost savepoint +** (the first created). A value of (Pager.nSavepoint-1) means operate +** on the most recently created savepoint. If iSavepoint is greater than +** (Pager.nSavepoint-1), then this function is a no-op. +** +** If a negative value is passed to this function, then the current +** transaction is rolled back. This is different to calling +** sqlite3PagerRollback() because this function does not terminate +** the transaction or unlock the database, it just restores the +** contents of the database to its original state. +** +** In any case, all savepoints with an index greater than iSavepoint +** are destroyed. If this is a release operation (op==SAVEPOINT_RELEASE), +** then savepoint iSavepoint is also destroyed. +** +** This function may return SQLITE_NOMEM if a memory allocation fails, +** or an IO error code if an IO error occurs while rolling back a +** savepoint. If no errors occur, SQLITE_OK is returned. +*/ +SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ + int rc = pPager->errCode; + +#ifdef SQLITE_ENABLE_ZIPVFS + if( op==SAVEPOINT_RELEASE ) rc = SQLITE_OK; +#endif + + assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK ); + assert( iSavepoint>=0 || op==SAVEPOINT_ROLLBACK ); + + if( rc==SQLITE_OK && iSavepoint<pPager->nSavepoint ){ + int ii; /* Iterator variable */ + int nNew; /* Number of remaining savepoints after this op. */ + + /* Figure out how many savepoints will still be active after this + ** operation. Store this value in nNew. Then free resources associated + ** with any savepoints that are destroyed by this operation. + */ + nNew = iSavepoint + (( op==SAVEPOINT_RELEASE ) ? 0 : 1); + for(ii=nNew; ii<pPager->nSavepoint; ii++){ + sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint); + } + pPager->nSavepoint = nNew; + + /* Truncate the sub-journal so that it only includes the parts + ** that are still in use. */ + if( op==SAVEPOINT_RELEASE ){ + PagerSavepoint *pRel = &pPager->aSavepoint[nNew]; + if( pRel->bTruncateOnRelease && isOpen(pPager->sjfd) ){ + /* Only truncate if it is an in-memory sub-journal. */ + if( sqlite3JournalIsInMemory(pPager->sjfd) ){ + i64 sz = (pPager->pageSize+4)*(i64)pRel->iSubRec; + rc = sqlite3OsTruncate(pPager->sjfd, sz); + assert( rc==SQLITE_OK ); + } + pPager->nSubRec = pRel->iSubRec; + } + } + /* Else this is a rollback operation, playback the specified savepoint. + ** If this is a temp-file, it is possible that the journal file has + ** not yet been opened. In this case there have been no changes to + ** the database file, so the playback operation can be skipped. + */ + else if( pagerUseWal(pPager) || isOpen(pPager->jfd) ){ + PagerSavepoint *pSavepoint = (nNew==0)?0:&pPager->aSavepoint[nNew-1]; + rc = pagerPlaybackSavepoint(pPager, pSavepoint); + assert(rc!=SQLITE_DONE); + } + +#ifdef SQLITE_ENABLE_ZIPVFS + /* If the cache has been modified but the savepoint cannot be rolled + ** back journal_mode=off, put the pager in the error state. This way, + ** if the VFS used by this pager includes ZipVFS, the entire transaction + ** can be rolled back at the ZipVFS level. */ + else if( + pPager->journalMode==PAGER_JOURNALMODE_OFF + && pPager->eState>=PAGER_WRITER_CACHEMOD + ){ + pPager->errCode = SQLITE_ABORT; + pPager->eState = PAGER_ERROR; + setGetterMethod(pPager); + } +#endif + } + + return rc; +} + +/* +** Return the full pathname of the database file. +** +** Except, if the pager is in-memory only, then return an empty string if +** nullIfMemDb is true. This routine is called with nullIfMemDb==1 when +** used to report the filename to the user, for compatibility with legacy +** behavior. But when the Btree needs to know the filename for matching to +** shared cache, it uses nullIfMemDb==0 so that in-memory databases can +** participate in shared-cache. +** +** The return value to this routine is always safe to use with +** sqlite3_uri_parameter() and sqlite3_filename_database() and friends. +*/ +SQLITE_PRIVATE const char *sqlite3PagerFilename(const Pager *pPager, int nullIfMemDb){ + static const char zFake[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + if( nullIfMemDb && (pPager->memDb || sqlite3IsMemdb(pPager->pVfs)) ){ + return &zFake[4]; + }else{ + return pPager->zFilename; + } +} + +/* +** Return the VFS structure for the pager. +*/ +SQLITE_PRIVATE sqlite3_vfs *sqlite3PagerVfs(Pager *pPager){ + return pPager->pVfs; +} + +/* +** Return the file handle for the database file associated +** with the pager. This might return NULL if the file has +** not yet been opened. +*/ +SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager *pPager){ + return pPager->fd; +} + +/* +** Return the file handle for the journal file (if it exists). +** This will be either the rollback journal or the WAL file. +*/ +SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager *pPager){ +#if SQLITE_OMIT_WAL + return pPager->jfd; +#else + return pPager->pWal ? sqlite3WalFile(pPager->pWal) : pPager->jfd; +#endif +} + +/* +** Return the full pathname of the journal file. +*/ +SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager *pPager){ + return pPager->zJournal; +} + +#ifndef SQLITE_OMIT_AUTOVACUUM +/* +** Move the page pPg to location pgno in the file. +** +** There must be no references to the page previously located at +** pgno (which we call pPgOld) though that page is allowed to be +** in cache. If the page previously located at pgno is not already +** in the rollback journal, it is not put there by by this routine. +** +** References to the page pPg remain valid. Updating any +** meta-data associated with pPg (i.e. data stored in the nExtra bytes +** allocated along with the page) is the responsibility of the caller. +** +** A transaction must be active when this routine is called. It used to be +** required that a statement transaction was not active, but this restriction +** has been removed (CREATE INDEX needs to move a page when a statement +** transaction is active). +** +** If the fourth argument, isCommit, is non-zero, then this page is being +** moved as part of a database reorganization just before the transaction +** is being committed. In this case, it is guaranteed that the database page +** pPg refers to will not be written to again within this transaction. +** +** This function may return SQLITE_NOMEM or an IO error code if an error +** occurs. Otherwise, it returns SQLITE_OK. +*/ +SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){ + PgHdr *pPgOld; /* The page being overwritten. */ + Pgno needSyncPgno = 0; /* Old value of pPg->pgno, if sync is required */ + int rc; /* Return code */ + Pgno origPgno; /* The original page number */ + + assert( pPg->nRef>0 ); + assert( pPager->eState==PAGER_WRITER_CACHEMOD + || pPager->eState==PAGER_WRITER_DBMOD + ); + assert( assert_pager_state(pPager) ); + + /* In order to be able to rollback, an in-memory database must journal + ** the page we are moving from. + */ + assert( pPager->tempFile || !MEMDB ); + if( pPager->tempFile ){ + rc = sqlite3PagerWrite(pPg); + if( rc ) return rc; + } + + /* If the page being moved is dirty and has not been saved by the latest + ** savepoint, then save the current contents of the page into the + ** sub-journal now. This is required to handle the following scenario: + ** + ** BEGIN; + ** <journal page X, then modify it in memory> + ** SAVEPOINT one; + ** <Move page X to location Y> + ** ROLLBACK TO one; + ** + ** If page X were not written to the sub-journal here, it would not + ** be possible to restore its contents when the "ROLLBACK TO one" + ** statement were is processed. + ** + ** subjournalPage() may need to allocate space to store pPg->pgno into + ** one or more savepoint bitvecs. This is the reason this function + ** may return SQLITE_NOMEM. + */ + if( (pPg->flags & PGHDR_DIRTY)!=0 + && SQLITE_OK!=(rc = subjournalPageIfRequired(pPg)) + ){ + return rc; + } + + PAGERTRACE(("MOVE %d page %d (needSync=%d) moves to %d\n", + PAGERID(pPager), pPg->pgno, (pPg->flags&PGHDR_NEED_SYNC)?1:0, pgno)); + IOTRACE(("MOVE %p %d %d\n", pPager, pPg->pgno, pgno)) + + /* If the journal needs to be sync()ed before page pPg->pgno can + ** be written to, store pPg->pgno in local variable needSyncPgno. + ** + ** If the isCommit flag is set, there is no need to remember that + ** the journal needs to be sync()ed before database page pPg->pgno + ** can be written to. The caller has already promised not to write to it. + */ + if( (pPg->flags&PGHDR_NEED_SYNC) && !isCommit ){ + needSyncPgno = pPg->pgno; + assert( pPager->journalMode==PAGER_JOURNALMODE_OFF || + pageInJournal(pPager, pPg) || pPg->pgno>pPager->dbOrigSize ); + assert( pPg->flags&PGHDR_DIRTY ); + } + + /* If the cache contains a page with page-number pgno, remove it + ** from its hash chain. Also, if the PGHDR_NEED_SYNC flag was set for + ** page pgno before the 'move' operation, it needs to be retained + ** for the page moved there. + */ + pPg->flags &= ~PGHDR_NEED_SYNC; + pPgOld = sqlite3PagerLookup(pPager, pgno); + assert( !pPgOld || pPgOld->nRef==1 || CORRUPT_DB ); + if( pPgOld ){ + if( NEVER(pPgOld->nRef>1) ){ + sqlite3PagerUnrefNotNull(pPgOld); + return SQLITE_CORRUPT_BKPT; + } + pPg->flags |= (pPgOld->flags&PGHDR_NEED_SYNC); + if( pPager->tempFile ){ + /* Do not discard pages from an in-memory database since we might + ** need to rollback later. Just move the page out of the way. */ + sqlite3PcacheMove(pPgOld, pPager->dbSize+1); + }else{ + sqlite3PcacheDrop(pPgOld); + } + } + + origPgno = pPg->pgno; + sqlite3PcacheMove(pPg, pgno); + sqlite3PcacheMakeDirty(pPg); + + /* For an in-memory database, make sure the original page continues + ** to exist, in case the transaction needs to roll back. Use pPgOld + ** as the original page since it has already been allocated. + */ + if( pPager->tempFile && pPgOld ){ + sqlite3PcacheMove(pPgOld, origPgno); + sqlite3PagerUnrefNotNull(pPgOld); + } + + if( needSyncPgno ){ + /* If needSyncPgno is non-zero, then the journal file needs to be + ** sync()ed before any data is written to database file page needSyncPgno. + ** Currently, no such page exists in the page-cache and the + ** "is journaled" bitvec flag has been set. This needs to be remedied by + ** loading the page into the pager-cache and setting the PGHDR_NEED_SYNC + ** flag. + ** + ** If the attempt to load the page into the page-cache fails, (due + ** to a malloc() or IO failure), clear the bit in the pInJournal[] + ** array. Otherwise, if the page is loaded and written again in + ** this transaction, it may be written to the database file before + ** it is synced into the journal file. This way, it may end up in + ** the journal file twice, but that is not a problem. + */ + PgHdr *pPgHdr; + rc = sqlite3PagerGet(pPager, needSyncPgno, &pPgHdr, 0); + if( rc!=SQLITE_OK ){ + if( needSyncPgno<=pPager->dbOrigSize ){ + assert( pPager->pTmpSpace!=0 ); + sqlite3BitvecClear(pPager->pInJournal, needSyncPgno, pPager->pTmpSpace); + } + return rc; + } + pPgHdr->flags |= PGHDR_NEED_SYNC; + sqlite3PcacheMakeDirty(pPgHdr); + sqlite3PagerUnrefNotNull(pPgHdr); + } + + return SQLITE_OK; +} +#endif + +/* +** The page handle passed as the first argument refers to a dirty page +** with a page number other than iNew. This function changes the page's +** page number to iNew and sets the value of the PgHdr.flags field to +** the value passed as the third parameter. +*/ +SQLITE_PRIVATE void sqlite3PagerRekey(DbPage *pPg, Pgno iNew, u16 flags){ + assert( pPg->pgno!=iNew ); + pPg->flags = flags; + sqlite3PcacheMove(pPg, iNew); +} + +/* +** Return a pointer to the data for the specified page. +*/ +SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *pPg){ + assert( pPg->nRef>0 || pPg->pPager->memDb ); + return pPg->pData; +} + +/* +** Return a pointer to the Pager.nExtra bytes of "extra" space +** allocated along with the specified page. +*/ +SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *pPg){ + return pPg->pExtra; +} + +/* +** Get/set the locking-mode for this pager. Parameter eMode must be one +** of PAGER_LOCKINGMODE_QUERY, PAGER_LOCKINGMODE_NORMAL or +** PAGER_LOCKINGMODE_EXCLUSIVE. If the parameter is not _QUERY, then +** the locking-mode is set to the value specified. +** +** The returned value is either PAGER_LOCKINGMODE_NORMAL or +** PAGER_LOCKINGMODE_EXCLUSIVE, indicating the current (possibly updated) +** locking-mode. +*/ +SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *pPager, int eMode){ + assert( eMode==PAGER_LOCKINGMODE_QUERY + || eMode==PAGER_LOCKINGMODE_NORMAL + || eMode==PAGER_LOCKINGMODE_EXCLUSIVE ); + assert( PAGER_LOCKINGMODE_QUERY<0 ); + assert( PAGER_LOCKINGMODE_NORMAL>=0 && PAGER_LOCKINGMODE_EXCLUSIVE>=0 ); + assert( pPager->exclusiveMode || 0==sqlite3WalHeapMemory(pPager->pWal) ); + if( eMode>=0 && !pPager->tempFile && !sqlite3WalHeapMemory(pPager->pWal) ){ + pPager->exclusiveMode = (u8)eMode; + } + return (int)pPager->exclusiveMode; +} + +/* +** Set the journal-mode for this pager. Parameter eMode must be one of: +** +** PAGER_JOURNALMODE_DELETE +** PAGER_JOURNALMODE_TRUNCATE +** PAGER_JOURNALMODE_PERSIST +** PAGER_JOURNALMODE_OFF +** PAGER_JOURNALMODE_MEMORY +** PAGER_JOURNALMODE_WAL +** +** The journalmode is set to the value specified if the change is allowed. +** The change may be disallowed for the following reasons: +** +** * An in-memory database can only have its journal_mode set to _OFF +** or _MEMORY. +** +** * Temporary databases cannot have _WAL journalmode. +** +** The returned indicate the current (possibly updated) journal-mode. +*/ +SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ + u8 eOld = pPager->journalMode; /* Prior journalmode */ + + /* The eMode parameter is always valid */ + assert( eMode==PAGER_JOURNALMODE_DELETE /* 0 */ + || eMode==PAGER_JOURNALMODE_PERSIST /* 1 */ + || eMode==PAGER_JOURNALMODE_OFF /* 2 */ + || eMode==PAGER_JOURNALMODE_TRUNCATE /* 3 */ + || eMode==PAGER_JOURNALMODE_MEMORY /* 4 */ + || eMode==PAGER_JOURNALMODE_WAL /* 5 */ ); + + /* This routine is only called from the OP_JournalMode opcode, and + ** the logic there will never allow a temporary file to be changed + ** to WAL mode. + */ + assert( pPager->tempFile==0 || eMode!=PAGER_JOURNALMODE_WAL ); + + /* Do allow the journalmode of an in-memory database to be set to + ** anything other than MEMORY or OFF + */ + if( MEMDB ){ + assert( eOld==PAGER_JOURNALMODE_MEMORY || eOld==PAGER_JOURNALMODE_OFF ); + if( eMode!=PAGER_JOURNALMODE_MEMORY && eMode!=PAGER_JOURNALMODE_OFF ){ + eMode = eOld; + } + } + + if( eMode!=eOld ){ + + /* Change the journal mode. */ + assert( pPager->eState!=PAGER_ERROR ); + pPager->journalMode = (u8)eMode; + + /* When transitioning from TRUNCATE or PERSIST to any other journal + ** mode except WAL, unless the pager is in locking_mode=exclusive mode, + ** delete the journal file. + */ + assert( (PAGER_JOURNALMODE_TRUNCATE & 5)==1 ); + assert( (PAGER_JOURNALMODE_PERSIST & 5)==1 ); + assert( (PAGER_JOURNALMODE_DELETE & 5)==0 ); + assert( (PAGER_JOURNALMODE_MEMORY & 5)==4 ); + assert( (PAGER_JOURNALMODE_OFF & 5)==0 ); + assert( (PAGER_JOURNALMODE_WAL & 5)==5 ); + + assert( isOpen(pPager->fd) || pPager->exclusiveMode ); + if( !pPager->exclusiveMode && (eOld & 5)==1 && (eMode & 1)==0 ){ + /* In this case we would like to delete the journal file. If it is + ** not possible, then that is not a problem. Deleting the journal file + ** here is an optimization only. + ** + ** Before deleting the journal file, obtain a RESERVED lock on the + ** database file. This ensures that the journal file is not deleted + ** while it is in use by some other client. + */ + sqlite3OsClose(pPager->jfd); + if( pPager->eLock>=RESERVED_LOCK ){ + sqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0); + }else{ + int rc = SQLITE_OK; + int state = pPager->eState; + assert( state==PAGER_OPEN || state==PAGER_READER ); + if( state==PAGER_OPEN ){ + rc = sqlite3PagerSharedLock(pPager); + } + if( pPager->eState==PAGER_READER ){ + assert( rc==SQLITE_OK ); + rc = pagerLockDb(pPager, RESERVED_LOCK); + } + if( rc==SQLITE_OK ){ + sqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0); + } + if( rc==SQLITE_OK && state==PAGER_READER ){ + pagerUnlockDb(pPager, SHARED_LOCK); + }else if( state==PAGER_OPEN ){ + pager_unlock(pPager); + } + assert( state==pPager->eState ); + } + }else if( eMode==PAGER_JOURNALMODE_OFF ){ + sqlite3OsClose(pPager->jfd); + } + } + + /* Return the new journal mode */ + return (int)pPager->journalMode; +} + +/* +** Return the current journal mode. +*/ +SQLITE_PRIVATE int sqlite3PagerGetJournalMode(Pager *pPager){ + return (int)pPager->journalMode; +} + +/* +** Return TRUE if the pager is in a state where it is OK to change the +** journalmode. Journalmode changes can only happen when the database +** is unmodified. +*/ +SQLITE_PRIVATE int sqlite3PagerOkToChangeJournalMode(Pager *pPager){ + assert( assert_pager_state(pPager) ); + if( pPager->eState>=PAGER_WRITER_CACHEMOD ) return 0; + if( NEVER(isOpen(pPager->jfd) && pPager->journalOff>0) ) return 0; + return 1; +} + +/* +** Get/set the size-limit used for persistent journal files. +** +** Setting the size limit to -1 means no limit is enforced. +** An attempt to set a limit smaller than -1 is a no-op. +*/ +SQLITE_PRIVATE i64 sqlite3PagerJournalSizeLimit(Pager *pPager, i64 iLimit){ + if( iLimit>=-1 ){ + pPager->journalSizeLimit = iLimit; + sqlite3WalLimit(pPager->pWal, iLimit); + } + return pPager->journalSizeLimit; +} + +/* +** Return a pointer to the pPager->pBackup variable. The backup module +** in backup.c maintains the content of this variable. This module +** uses it opaquely as an argument to sqlite3BackupRestart() and +** sqlite3BackupUpdate() only. +*/ +SQLITE_PRIVATE sqlite3_backup **sqlite3PagerBackupPtr(Pager *pPager){ + return &pPager->pBackup; +} + +#ifndef SQLITE_OMIT_VACUUM +/* +** Unless this is an in-memory or temporary database, clear the pager cache. +*/ +SQLITE_PRIVATE void sqlite3PagerClearCache(Pager *pPager){ + assert( MEMDB==0 || pPager->tempFile ); + if( pPager->tempFile==0 ) pager_reset(pPager); +} +#endif + + +#ifndef SQLITE_OMIT_WAL +/* +** This function is called when the user invokes "PRAGMA wal_checkpoint", +** "PRAGMA wal_blocking_checkpoint" or calls the sqlite3_wal_checkpoint() +** or wal_blocking_checkpoint() API functions. +** +** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART. +*/ +SQLITE_PRIVATE int sqlite3PagerCheckpoint( + Pager *pPager, /* Checkpoint on this pager */ + sqlite3 *db, /* Db handle used to check for interrupts */ + int eMode, /* Type of checkpoint */ + int *pnLog, /* OUT: Final number of frames in log */ + int *pnCkpt /* OUT: Final number of checkpointed frames */ +){ + int rc = SQLITE_OK; + if( pPager->pWal==0 && pPager->journalMode==PAGER_JOURNALMODE_WAL ){ + /* This only happens when a database file is zero bytes in size opened and + ** then "PRAGMA journal_mode=WAL" is run and then sqlite3_wal_checkpoint() + ** is invoked without any intervening transactions. We need to start + ** a transaction to initialize pWal. The PRAGMA table_list statement is + ** used for this since it starts transactions on every database file, + ** including all ATTACHed databases. This seems expensive for a single + ** sqlite3_wal_checkpoint() call, but it happens very rarely. + ** https://sqlite.org/forum/forumpost/fd0f19d229156939 + */ + sqlite3_exec(db, "PRAGMA table_list",0,0,0); + } + if( pPager->pWal ){ + rc = sqlite3WalCheckpoint(pPager->pWal, db, eMode, + (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), + pPager->pBusyHandlerArg, + pPager->walSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, + pnLog, pnCkpt + ); + } + return rc; +} + +SQLITE_PRIVATE int sqlite3PagerWalCallback(Pager *pPager){ + return sqlite3WalCallback(pPager->pWal); +} + +/* +** Return true if the underlying VFS for the given pager supports the +** primitives necessary for write-ahead logging. +*/ +SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager){ + const sqlite3_io_methods *pMethods = pPager->fd->pMethods; + if( pPager->noLock ) return 0; + return pPager->exclusiveMode || (pMethods->iVersion>=2 && pMethods->xShmMap); +} + +/* +** Attempt to take an exclusive lock on the database file. If a PENDING lock +** is obtained instead, immediately release it. +*/ +static int pagerExclusiveLock(Pager *pPager){ + int rc; /* Return code */ + u8 eOrigLock; /* Original lock */ + + assert( pPager->eLock>=SHARED_LOCK ); + eOrigLock = pPager->eLock; + rc = pagerLockDb(pPager, EXCLUSIVE_LOCK); + if( rc!=SQLITE_OK ){ + /* If the attempt to grab the exclusive lock failed, release the + ** pending lock that may have been obtained instead. */ + pagerUnlockDb(pPager, eOrigLock); + } + + return rc; +} + +/* +** Call sqlite3WalOpen() to open the WAL handle. If the pager is in +** exclusive-locking mode when this function is called, take an EXCLUSIVE +** lock on the database file and use heap-memory to store the wal-index +** in. Otherwise, use the normal shared-memory. +*/ +static int pagerOpenWal(Pager *pPager){ + int rc = SQLITE_OK; + + assert( pPager->pWal==0 && pPager->tempFile==0 ); + assert( pPager->eLock==SHARED_LOCK || pPager->eLock==EXCLUSIVE_LOCK ); + + /* If the pager is already in exclusive-mode, the WAL module will use + ** heap-memory for the wal-index instead of the VFS shared-memory + ** implementation. Take the exclusive lock now, before opening the WAL + ** file, to make sure this is safe. + */ + if( pPager->exclusiveMode ){ + rc = pagerExclusiveLock(pPager); + } + + /* Open the connection to the log file. If this operation fails, + ** (e.g. due to malloc() failure), return an error code. + */ + if( rc==SQLITE_OK ){ + rc = sqlite3WalOpen(pPager->pVfs, + pPager->fd, pPager->zWal, pPager->exclusiveMode, + pPager->journalSizeLimit, &pPager->pWal + ); + } + pagerFixMaplimit(pPager); + + return rc; +} + + +/* +** The caller must be holding a SHARED lock on the database file to call +** this function. +** +** If the pager passed as the first argument is open on a real database +** file (not a temp file or an in-memory database), and the WAL file +** is not already open, make an attempt to open it now. If successful, +** return SQLITE_OK. If an error occurs or the VFS used by the pager does +** not support the xShmXXX() methods, return an error code. *pbOpen is +** not modified in either case. +** +** If the pager is open on a temp-file (or in-memory database), or if +** the WAL file is already open, set *pbOpen to 1 and return SQLITE_OK +** without doing anything. +*/ +SQLITE_PRIVATE int sqlite3PagerOpenWal( + Pager *pPager, /* Pager object */ + int *pbOpen /* OUT: Set to true if call is a no-op */ +){ + int rc = SQLITE_OK; /* Return code */ + + assert( assert_pager_state(pPager) ); + assert( pPager->eState==PAGER_OPEN || pbOpen ); + assert( pPager->eState==PAGER_READER || !pbOpen ); + assert( pbOpen==0 || *pbOpen==0 ); + assert( pbOpen!=0 || (!pPager->tempFile && !pPager->pWal) ); + + if( !pPager->tempFile && !pPager->pWal ){ + if( !sqlite3PagerWalSupported(pPager) ) return SQLITE_CANTOPEN; + + /* Close any rollback journal previously open */ + sqlite3OsClose(pPager->jfd); + + rc = pagerOpenWal(pPager); + if( rc==SQLITE_OK ){ + pPager->journalMode = PAGER_JOURNALMODE_WAL; + pPager->eState = PAGER_OPEN; + } + }else{ + *pbOpen = 1; + } + + return rc; +} + +/* +** This function is called to close the connection to the log file prior +** to switching from WAL to rollback mode. +** +** Before closing the log file, this function attempts to take an +** EXCLUSIVE lock on the database file. If this cannot be obtained, an +** error (SQLITE_BUSY) is returned and the log connection is not closed. +** If successful, the EXCLUSIVE lock is not released before returning. +*/ +SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ + int rc = SQLITE_OK; + + assert( pPager->journalMode==PAGER_JOURNALMODE_WAL ); + + /* If the log file is not already open, but does exist in the file-system, + ** it may need to be checkpointed before the connection can switch to + ** rollback mode. Open it now so this can happen. + */ + if( !pPager->pWal ){ + int logexists = 0; + rc = pagerLockDb(pPager, SHARED_LOCK); + if( rc==SQLITE_OK ){ + rc = sqlite3OsAccess( + pPager->pVfs, pPager->zWal, SQLITE_ACCESS_EXISTS, &logexists + ); + } + if( rc==SQLITE_OK && logexists ){ + rc = pagerOpenWal(pPager); + } + } + + /* Checkpoint and close the log. Because an EXCLUSIVE lock is held on + ** the database file, the log and log-summary files will be deleted. + */ + if( rc==SQLITE_OK && pPager->pWal ){ + rc = pagerExclusiveLock(pPager); + if( rc==SQLITE_OK ){ + rc = sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, + pPager->pageSize, (u8*)pPager->pTmpSpace); + pPager->pWal = 0; + pagerFixMaplimit(pPager); + if( rc && !pPager->exclusiveMode ) pagerUnlockDb(pPager, SHARED_LOCK); + } + } + return rc; +} + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +/* +** If pager pPager is a wal-mode database not in exclusive locking mode, +** invoke the sqlite3WalWriteLock() function on the associated Wal object +** with the same db and bLock parameters as were passed to this function. +** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise. +*/ +SQLITE_PRIVATE int sqlite3PagerWalWriteLock(Pager *pPager, int bLock){ + int rc = SQLITE_OK; + if( pagerUseWal(pPager) && pPager->exclusiveMode==0 ){ + rc = sqlite3WalWriteLock(pPager->pWal, bLock); + } + return rc; +} + +/* +** Set the database handle used by the wal layer to determine if +** blocking locks are required. +*/ +SQLITE_PRIVATE void sqlite3PagerWalDb(Pager *pPager, sqlite3 *db){ + if( pagerUseWal(pPager) ){ + sqlite3WalDb(pPager->pWal, db); + } +} +#endif + +#ifdef SQLITE_ENABLE_SNAPSHOT +/* +** If this is a WAL database, obtain a snapshot handle for the snapshot +** currently open. Otherwise, return an error. +*/ +SQLITE_PRIVATE int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot){ + int rc = SQLITE_ERROR; + if( pPager->pWal ){ + rc = sqlite3WalSnapshotGet(pPager->pWal, ppSnapshot); + } + return rc; +} + +/* +** If this is a WAL database, store a pointer to pSnapshot. Next time a +** read transaction is opened, attempt to read from the snapshot it +** identifies. If this is not a WAL database, return an error. +*/ +SQLITE_PRIVATE int sqlite3PagerSnapshotOpen( + Pager *pPager, + sqlite3_snapshot *pSnapshot +){ + int rc = SQLITE_OK; + if( pPager->pWal ){ + sqlite3WalSnapshotOpen(pPager->pWal, pSnapshot); + }else{ + rc = SQLITE_ERROR; + } + return rc; +} + +/* +** If this is a WAL database, call sqlite3WalSnapshotRecover(). If this +** is not a WAL database, return an error. +*/ +SQLITE_PRIVATE int sqlite3PagerSnapshotRecover(Pager *pPager){ + int rc; + if( pPager->pWal ){ + rc = sqlite3WalSnapshotRecover(pPager->pWal); + }else{ + rc = SQLITE_ERROR; + } + return rc; +} + +/* +** The caller currently has a read transaction open on the database. +** If this is not a WAL database, SQLITE_ERROR is returned. Otherwise, +** this function takes a SHARED lock on the CHECKPOINTER slot and then +** checks if the snapshot passed as the second argument is still +** available. If so, SQLITE_OK is returned. +** +** If the snapshot is not available, SQLITE_ERROR is returned. Or, if +** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error +** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER +** lock is released before returning. +*/ +SQLITE_PRIVATE int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot){ + int rc; + if( pPager->pWal ){ + rc = sqlite3WalSnapshotCheck(pPager->pWal, pSnapshot); + }else{ + rc = SQLITE_ERROR; + } + return rc; +} + +/* +** Release a lock obtained by an earlier successful call to +** sqlite3PagerSnapshotCheck(). +*/ +SQLITE_PRIVATE void sqlite3PagerSnapshotUnlock(Pager *pPager){ + assert( pPager->pWal ); + sqlite3WalSnapshotUnlock(pPager->pWal); +} + +#endif /* SQLITE_ENABLE_SNAPSHOT */ +#endif /* !SQLITE_OMIT_WAL */ + +#ifdef SQLITE_ENABLE_ZIPVFS +/* +** A read-lock must be held on the pager when this function is called. If +** the pager is in WAL mode and the WAL file currently contains one or more +** frames, return the size in bytes of the page images stored within the +** WAL frames. Otherwise, if this is not a WAL database or the WAL file +** is empty, return 0. +*/ +SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ + assert( pPager->eState>=PAGER_READER ); + return sqlite3WalFramesize(pPager->pWal); +} +#endif + +#ifdef SQLITE_USE_SEH +SQLITE_PRIVATE int sqlite3PagerWalSystemErrno(Pager *pPager){ + return sqlite3WalSystemErrno(pPager->pWal); +} +#endif + +#endif /* SQLITE_OMIT_DISKIO */ + +/************** End of pager.c ***********************************************/ +/************** Begin file wal.c *********************************************/ +/* +** 2010 February 1 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the implementation of a write-ahead log (WAL) used in +** "journal_mode=WAL" mode. +** +** WRITE-AHEAD LOG (WAL) FILE FORMAT +** +** A WAL file consists of a header followed by zero or more "frames". +** Each frame records the revised content of a single page from the +** database file. All changes to the database are recorded by writing +** frames into the WAL. Transactions commit when a frame is written that +** contains a commit marker. A single WAL can and usually does record +** multiple transactions. Periodically, the content of the WAL is +** transferred back into the database file in an operation called a +** "checkpoint". +** +** A single WAL file can be used multiple times. In other words, the +** WAL can fill up with frames and then be checkpointed and then new +** frames can overwrite the old ones. A WAL always grows from beginning +** toward the end. Checksums and counters attached to each frame are +** used to determine which frames within the WAL are valid and which +** are leftovers from prior checkpoints. +** +** The WAL header is 32 bytes in size and consists of the following eight +** big-endian 32-bit unsigned integer values: +** +** 0: Magic number. 0x377f0682 or 0x377f0683 +** 4: File format version. Currently 3007000 +** 8: Database page size. Example: 1024 +** 12: Checkpoint sequence number +** 16: Salt-1, random integer incremented with each checkpoint +** 20: Salt-2, a different random integer changing with each ckpt +** 24: Checksum-1 (first part of checksum for first 24 bytes of header). +** 28: Checksum-2 (second part of checksum for first 24 bytes of header). +** +** Immediately following the wal-header are zero or more frames. Each +** frame consists of a 24-byte frame-header followed by a <page-size> bytes +** of page data. The frame-header is six big-endian 32-bit unsigned +** integer values, as follows: +** +** 0: Page number. +** 4: For commit records, the size of the database image in pages +** after the commit. For all other records, zero. +** 8: Salt-1 (copied from the header) +** 12: Salt-2 (copied from the header) +** 16: Checksum-1. +** 20: Checksum-2. +** +** A frame is considered valid if and only if the following conditions are +** true: +** +** (1) The salt-1 and salt-2 values in the frame-header match +** salt values in the wal-header +** +** (2) The checksum values in the final 8 bytes of the frame-header +** exactly match the checksum computed consecutively on the +** WAL header and the first 8 bytes and the content of all frames +** up to and including the current frame. +** +** The checksum is computed using 32-bit big-endian integers if the +** magic number in the first 4 bytes of the WAL is 0x377f0683 and it +** is computed using little-endian if the magic number is 0x377f0682. +** The checksum values are always stored in the frame header in a +** big-endian format regardless of which byte order is used to compute +** the checksum. The checksum is computed by interpreting the input as +** an even number of unsigned 32-bit integers: x[0] through x[N]. The +** algorithm used for the checksum is as follows: +** +** for i from 0 to n-1 step 2: +** s0 += x[i] + s1; +** s1 += x[i+1] + s0; +** endfor +** +** Note that s0 and s1 are both weighted checksums using fibonacci weights +** in reverse order (the largest fibonacci weight occurs on the first element +** of the sequence being summed.) The s1 value spans all 32-bit +** terms of the sequence whereas s0 omits the final term. +** +** On a checkpoint, the WAL is first VFS.xSync-ed, then valid content of the +** WAL is transferred into the database, then the database is VFS.xSync-ed. +** The VFS.xSync operations serve as write barriers - all writes launched +** before the xSync must complete before any write that launches after the +** xSync begins. +** +** After each checkpoint, the salt-1 value is incremented and the salt-2 +** value is randomized. This prevents old and new frames in the WAL from +** being considered valid at the same time and being checkpointing together +** following a crash. +** +** READER ALGORITHM +** +** To read a page from the database (call it page number P), a reader +** first checks the WAL to see if it contains page P. If so, then the +** last valid instance of page P that is a followed by a commit frame +** or is a commit frame itself becomes the value read. If the WAL +** contains no copies of page P that are valid and which are a commit +** frame or are followed by a commit frame, then page P is read from +** the database file. +** +** To start a read transaction, the reader records the index of the last +** valid frame in the WAL. The reader uses this recorded "mxFrame" value +** for all subsequent read operations. New transactions can be appended +** to the WAL, but as long as the reader uses its original mxFrame value +** and ignores the newly appended content, it will see a consistent snapshot +** of the database from a single point in time. This technique allows +** multiple concurrent readers to view different versions of the database +** content simultaneously. +** +** The reader algorithm in the previous paragraphs works correctly, but +** because frames for page P can appear anywhere within the WAL, the +** reader has to scan the entire WAL looking for page P frames. If the +** WAL is large (multiple megabytes is typical) that scan can be slow, +** and read performance suffers. To overcome this problem, a separate +** data structure called the wal-index is maintained to expedite the +** search for frames of a particular page. +** +** WAL-INDEX FORMAT +** +** Conceptually, the wal-index is shared memory, though VFS implementations +** might choose to implement the wal-index using a mmapped file. Because +** the wal-index is shared memory, SQLite does not support journal_mode=WAL +** on a network filesystem. All users of the database must be able to +** share memory. +** +** In the default unix and windows implementation, the wal-index is a mmapped +** file whose name is the database name with a "-shm" suffix added. For that +** reason, the wal-index is sometimes called the "shm" file. +** +** The wal-index is transient. After a crash, the wal-index can (and should +** be) reconstructed from the original WAL file. In fact, the VFS is required +** to either truncate or zero the header of the wal-index when the last +** connection to it closes. Because the wal-index is transient, it can +** use an architecture-specific format; it does not have to be cross-platform. +** Hence, unlike the database and WAL file formats which store all values +** as big endian, the wal-index can store multi-byte values in the native +** byte order of the host computer. +** +** The purpose of the wal-index is to answer this question quickly: Given +** a page number P and a maximum frame index M, return the index of the +** last frame in the wal before frame M for page P in the WAL, or return +** NULL if there are no frames for page P in the WAL prior to M. +** +** The wal-index consists of a header region, followed by an one or +** more index blocks. +** +** The wal-index header contains the total number of frames within the WAL +** in the mxFrame field. +** +** Each index block except for the first contains information on +** HASHTABLE_NPAGE frames. The first index block contains information on +** HASHTABLE_NPAGE_ONE frames. The values of HASHTABLE_NPAGE_ONE and +** HASHTABLE_NPAGE are selected so that together the wal-index header and +** first index block are the same size as all other index blocks in the +** wal-index. The values are: +** +** HASHTABLE_NPAGE 4096 +** HASHTABLE_NPAGE_ONE 4062 +** +** Each index block contains two sections, a page-mapping that contains the +** database page number associated with each wal frame, and a hash-table +** that allows readers to query an index block for a specific page number. +** The page-mapping is an array of HASHTABLE_NPAGE (or HASHTABLE_NPAGE_ONE +** for the first index block) 32-bit page numbers. The first entry in the +** first index-block contains the database page number corresponding to the +** first frame in the WAL file. The first entry in the second index block +** in the WAL file corresponds to the (HASHTABLE_NPAGE_ONE+1)th frame in +** the log, and so on. +** +** The last index block in a wal-index usually contains less than the full +** complement of HASHTABLE_NPAGE (or HASHTABLE_NPAGE_ONE) page-numbers, +** depending on the contents of the WAL file. This does not change the +** allocated size of the page-mapping array - the page-mapping array merely +** contains unused entries. +** +** Even without using the hash table, the last frame for page P +** can be found by scanning the page-mapping sections of each index block +** starting with the last index block and moving toward the first, and +** within each index block, starting at the end and moving toward the +** beginning. The first entry that equals P corresponds to the frame +** holding the content for that page. +** +** The hash table consists of HASHTABLE_NSLOT 16-bit unsigned integers. +** HASHTABLE_NSLOT = 2*HASHTABLE_NPAGE, and there is one entry in the +** hash table for each page number in the mapping section, so the hash +** table is never more than half full. The expected number of collisions +** prior to finding a match is 1. Each entry of the hash table is an +** 1-based index of an entry in the mapping section of the same +** index block. Let K be the 1-based index of the largest entry in +** the mapping section. (For index blocks other than the last, K will +** always be exactly HASHTABLE_NPAGE (4096) and for the last index block +** K will be (mxFrame%HASHTABLE_NPAGE).) Unused slots of the hash table +** contain a value of 0. +** +** To look for page P in the hash table, first compute a hash iKey on +** P as follows: +** +** iKey = (P * 383) % HASHTABLE_NSLOT +** +** Then start scanning entries of the hash table, starting with iKey +** (wrapping around to the beginning when the end of the hash table is +** reached) until an unused hash slot is found. Let the first unused slot +** be at index iUnused. (iUnused might be less than iKey if there was +** wrap-around.) Because the hash table is never more than half full, +** the search is guaranteed to eventually hit an unused entry. Let +** iMax be the value between iKey and iUnused, closest to iUnused, +** where aHash[iMax]==P. If there is no iMax entry (if there exists +** no hash slot such that aHash[i]==p) then page P is not in the +** current index block. Otherwise the iMax-th mapping entry of the +** current index block corresponds to the last entry that references +** page P. +** +** A hash search begins with the last index block and moves toward the +** first index block, looking for entries corresponding to page P. On +** average, only two or three slots in each index block need to be +** examined in order to either find the last entry for page P, or to +** establish that no such entry exists in the block. Each index block +** holds over 4000 entries. So two or three index blocks are sufficient +** to cover a typical 10 megabyte WAL file, assuming 1K pages. 8 or 10 +** comparisons (on average) suffice to either locate a frame in the +** WAL or to establish that the frame does not exist in the WAL. This +** is much faster than scanning the entire 10MB WAL. +** +** Note that entries are added in order of increasing K. Hence, one +** reader might be using some value K0 and a second reader that started +** at a later time (after additional transactions were added to the WAL +** and to the wal-index) might be using a different value K1, where K1>K0. +** Both readers can use the same hash table and mapping section to get +** the correct result. There may be entries in the hash table with +** K>K0 but to the first reader, those entries will appear to be unused +** slots in the hash table and so the first reader will get an answer as +** if no values greater than K0 had ever been inserted into the hash table +** in the first place - which is what reader one wants. Meanwhile, the +** second reader using K1 will see additional values that were inserted +** later, which is exactly what reader two wants. +** +** When a rollback occurs, the value of K is decreased. Hash table entries +** that correspond to frames greater than the new K value are removed +** from the hash table at this point. +*/ +#ifndef SQLITE_OMIT_WAL + +/* #include "wal.h" */ + +/* +** Trace output macros +*/ +#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) +SQLITE_PRIVATE int sqlite3WalTrace = 0; +# define WALTRACE(X) if(sqlite3WalTrace) sqlite3DebugPrintf X +#else +# define WALTRACE(X) +#endif + +/* +** The maximum (and only) versions of the wal and wal-index formats +** that may be interpreted by this version of SQLite. +** +** If a client begins recovering a WAL file and finds that (a) the checksum +** values in the wal-header are correct and (b) the version field is not +** WAL_MAX_VERSION, recovery fails and SQLite returns SQLITE_CANTOPEN. +** +** Similarly, if a client successfully reads a wal-index header (i.e. the +** checksum test is successful) and finds that the version field is not +** WALINDEX_MAX_VERSION, then no read-transaction is opened and SQLite +** returns SQLITE_CANTOPEN. +*/ +#define WAL_MAX_VERSION 3007000 +#define WALINDEX_MAX_VERSION 3007000 + +/* +** Index numbers for various locking bytes. WAL_NREADER is the number +** of available reader locks and should be at least 3. The default +** is SQLITE_SHM_NLOCK==8 and WAL_NREADER==5. +** +** Technically, the various VFSes are free to implement these locks however +** they see fit. However, compatibility is encouraged so that VFSes can +** interoperate. The standard implementation used on both unix and windows +** is for the index number to indicate a byte offset into the +** WalCkptInfo.aLock[] array in the wal-index header. In other words, all +** locks are on the shm file. The WALINDEX_LOCK_OFFSET constant (which +** should be 120) is the location in the shm file for the first locking +** byte. +*/ +#define WAL_WRITE_LOCK 0 +#define WAL_ALL_BUT_WRITE 1 +#define WAL_CKPT_LOCK 1 +#define WAL_RECOVER_LOCK 2 +#define WAL_READ_LOCK(I) (3+(I)) +#define WAL_NREADER (SQLITE_SHM_NLOCK-3) + + +/* Object declarations */ +typedef struct WalIndexHdr WalIndexHdr; +typedef struct WalIterator WalIterator; +typedef struct WalCkptInfo WalCkptInfo; + + +/* +** The following object holds a copy of the wal-index header content. +** +** The actual header in the wal-index consists of two copies of this +** object followed by one instance of the WalCkptInfo object. +** For all versions of SQLite through 3.10.0 and probably beyond, +** the locking bytes (WalCkptInfo.aLock) start at offset 120 and +** the total header size is 136 bytes. +** +** The szPage value can be any power of 2 between 512 and 32768, inclusive. +** Or it can be 1 to represent a 65536-byte page. The latter case was +** added in 3.7.1 when support for 64K pages was added. +*/ +struct WalIndexHdr { + u32 iVersion; /* Wal-index version */ + u32 unused; /* Unused (padding) field */ + u32 iChange; /* Counter incremented each transaction */ + u8 isInit; /* 1 when initialized */ + u8 bigEndCksum; /* True if checksums in WAL are big-endian */ + u16 szPage; /* Database page size in bytes. 1==64K */ + u32 mxFrame; /* Index of last valid frame in the WAL */ + u32 nPage; /* Size of database in pages */ + u32 aFrameCksum[2]; /* Checksum of last frame in log */ + u32 aSalt[2]; /* Two salt values copied from WAL header */ + u32 aCksum[2]; /* Checksum over all prior fields */ +}; + +/* +** A copy of the following object occurs in the wal-index immediately +** following the second copy of the WalIndexHdr. This object stores +** information used by checkpoint. +** +** nBackfill is the number of frames in the WAL that have been written +** back into the database. (We call the act of moving content from WAL to +** database "backfilling".) The nBackfill number is never greater than +** WalIndexHdr.mxFrame. nBackfill can only be increased by threads +** holding the WAL_CKPT_LOCK lock (which includes a recovery thread). +** However, a WAL_WRITE_LOCK thread can move the value of nBackfill from +** mxFrame back to zero when the WAL is reset. +** +** nBackfillAttempted is the largest value of nBackfill that a checkpoint +** has attempted to achieve. Normally nBackfill==nBackfillAtempted, however +** the nBackfillAttempted is set before any backfilling is done and the +** nBackfill is only set after all backfilling completes. So if a checkpoint +** crashes, nBackfillAttempted might be larger than nBackfill. The +** WalIndexHdr.mxFrame must never be less than nBackfillAttempted. +** +** The aLock[] field is a set of bytes used for locking. These bytes should +** never be read or written. +** +** There is one entry in aReadMark[] for each reader lock. If a reader +** holds read-lock K, then the value in aReadMark[K] is no greater than +** the mxFrame for that reader. The value READMARK_NOT_USED (0xffffffff) +** for any aReadMark[] means that entry is unused. aReadMark[0] is +** a special case; its value is never used and it exists as a place-holder +** to avoid having to offset aReadMark[] indexes by one. Readers holding +** WAL_READ_LOCK(0) always ignore the entire WAL and read all content +** directly from the database. +** +** The value of aReadMark[K] may only be changed by a thread that +** is holding an exclusive lock on WAL_READ_LOCK(K). Thus, the value of +** aReadMark[K] cannot changed while there is a reader is using that mark +** since the reader will be holding a shared lock on WAL_READ_LOCK(K). +** +** The checkpointer may only transfer frames from WAL to database where +** the frame numbers are less than or equal to every aReadMark[] that is +** in use (that is, every aReadMark[j] for which there is a corresponding +** WAL_READ_LOCK(j)). New readers (usually) pick the aReadMark[] with the +** largest value and will increase an unused aReadMark[] to mxFrame if there +** is not already an aReadMark[] equal to mxFrame. The exception to the +** previous sentence is when nBackfill equals mxFrame (meaning that everything +** in the WAL has been backfilled into the database) then new readers +** will choose aReadMark[0] which has value 0 and hence such reader will +** get all their all content directly from the database file and ignore +** the WAL. +** +** Writers normally append new frames to the end of the WAL. However, +** if nBackfill equals mxFrame (meaning that all WAL content has been +** written back into the database) and if no readers are using the WAL +** (in other words, if there are no WAL_READ_LOCK(i) where i>0) then +** the writer will first "reset" the WAL back to the beginning and start +** writing new content beginning at frame 1. +** +** We assume that 32-bit loads are atomic and so no locks are needed in +** order to read from any aReadMark[] entries. +*/ +struct WalCkptInfo { + u32 nBackfill; /* Number of WAL frames backfilled into DB */ + u32 aReadMark[WAL_NREADER]; /* Reader marks */ + u8 aLock[SQLITE_SHM_NLOCK]; /* Reserved space for locks */ + u32 nBackfillAttempted; /* WAL frames perhaps written, or maybe not */ + u32 notUsed0; /* Available for future enhancements */ +}; +#define READMARK_NOT_USED 0xffffffff + +/* +** This is a schematic view of the complete 136-byte header of the +** wal-index file (also known as the -shm file): +** +** +-----------------------------+ +** 0: | iVersion | \ +** +-----------------------------+ | +** 4: | (unused padding) | | +** +-----------------------------+ | +** 8: | iChange | | +** +-------+-------+-------------+ | +** 12: | bInit | bBig | szPage | | +** +-------+-------+-------------+ | +** 16: | mxFrame | | First copy of the +** +-----------------------------+ | WalIndexHdr object +** 20: | nPage | | +** +-----------------------------+ | +** 24: | aFrameCksum | | +** | | | +** +-----------------------------+ | +** 32: | aSalt | | +** | | | +** +-----------------------------+ | +** 40: | aCksum | | +** | | / +** +-----------------------------+ +** 48: | iVersion | \ +** +-----------------------------+ | +** 52: | (unused padding) | | +** +-----------------------------+ | +** 56: | iChange | | +** +-------+-------+-------------+ | +** 60: | bInit | bBig | szPage | | +** +-------+-------+-------------+ | Second copy of the +** 64: | mxFrame | | WalIndexHdr +** +-----------------------------+ | +** 68: | nPage | | +** +-----------------------------+ | +** 72: | aFrameCksum | | +** | | | +** +-----------------------------+ | +** 80: | aSalt | | +** | | | +** +-----------------------------+ | +** 88: | aCksum | | +** | | / +** +-----------------------------+ +** 96: | nBackfill | +** +-----------------------------+ +** 100: | 5 read marks | +** | | +** | | +** | | +** | | +** +-------+-------+------+------+ +** 120: | Write | Ckpt | Rcvr | Rd0 | \ +** +-------+-------+------+------+ ) 8 lock bytes +** | Read1 | Read2 | Rd3 | Rd4 | / +** +-------+-------+------+------+ +** 128: | nBackfillAttempted | +** +-----------------------------+ +** 132: | (unused padding) | +** +-----------------------------+ +*/ + +/* A block of WALINDEX_LOCK_RESERVED bytes beginning at +** WALINDEX_LOCK_OFFSET is reserved for locks. Since some systems +** only support mandatory file-locks, we do not read or write data +** from the region of the file on which locks are applied. +*/ +#define WALINDEX_LOCK_OFFSET (sizeof(WalIndexHdr)*2+offsetof(WalCkptInfo,aLock)) +#define WALINDEX_HDR_SIZE (sizeof(WalIndexHdr)*2+sizeof(WalCkptInfo)) + +/* Size of header before each frame in wal */ +#define WAL_FRAME_HDRSIZE 24 + +/* Size of write ahead log header, including checksum. */ +#define WAL_HDRSIZE 32 + +/* WAL magic value. Either this value, or the same value with the least +** significant bit also set (WAL_MAGIC | 0x00000001) is stored in 32-bit +** big-endian format in the first 4 bytes of a WAL file. +** +** If the LSB is set, then the checksums for each frame within the WAL +** file are calculated by treating all data as an array of 32-bit +** big-endian words. Otherwise, they are calculated by interpreting +** all data as 32-bit little-endian words. +*/ +#define WAL_MAGIC 0x377f0682 + +/* +** Return the offset of frame iFrame in the write-ahead log file, +** assuming a database page size of szPage bytes. The offset returned +** is to the start of the write-ahead log frame-header. +*/ +#define walFrameOffset(iFrame, szPage) ( \ + WAL_HDRSIZE + ((iFrame)-1)*(i64)((szPage)+WAL_FRAME_HDRSIZE) \ +) + +/* +** An open write-ahead log file is represented by an instance of the +** following object. +*/ +struct Wal { + sqlite3_vfs *pVfs; /* The VFS used to create pDbFd */ + sqlite3_file *pDbFd; /* File handle for the database file */ + sqlite3_file *pWalFd; /* File handle for WAL file */ + u32 iCallback; /* Value to pass to log callback (or 0) */ + i64 mxWalSize; /* Truncate WAL to this size upon reset */ + int nWiData; /* Size of array apWiData */ + int szFirstBlock; /* Size of first block written to WAL file */ + volatile u32 **apWiData; /* Pointer to wal-index content in memory */ + u32 szPage; /* Database page size */ + i16 readLock; /* Which read lock is being held. -1 for none */ + u8 syncFlags; /* Flags to use to sync header writes */ + u8 exclusiveMode; /* Non-zero if connection is in exclusive mode */ + u8 writeLock; /* True if in a write transaction */ + u8 ckptLock; /* True if holding a checkpoint lock */ + u8 readOnly; /* WAL_RDWR, WAL_RDONLY, or WAL_SHM_RDONLY */ + u8 truncateOnCommit; /* True to truncate WAL file on commit */ + u8 syncHeader; /* Fsync the WAL header if true */ + u8 padToSectorBoundary; /* Pad transactions out to the next sector */ + u8 bShmUnreliable; /* SHM content is read-only and unreliable */ + WalIndexHdr hdr; /* Wal-index header for current transaction */ + u32 minFrame; /* Ignore wal frames before this one */ + u32 iReCksum; /* On commit, recalculate checksums from here */ + const char *zWalName; /* Name of WAL file */ + u32 nCkpt; /* Checkpoint sequence counter in the wal-header */ +#ifdef SQLITE_USE_SEH + u32 lockMask; /* Mask of locks held */ + void *pFree; /* Pointer to sqlite3_free() if exception thrown */ + u32 *pWiValue; /* Value to write into apWiData[iWiPg] */ + int iWiPg; /* Write pWiValue into apWiData[iWiPg] */ + int iSysErrno; /* System error code following exception */ +#endif +#ifdef SQLITE_DEBUG + int nSehTry; /* Number of nested SEH_TRY{} blocks */ + u8 lockError; /* True if a locking error has occurred */ +#endif +#ifdef SQLITE_ENABLE_SNAPSHOT + WalIndexHdr *pSnapshot; /* Start transaction here if not NULL */ +#endif +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + sqlite3 *db; +#endif +}; + +/* +** Candidate values for Wal.exclusiveMode. +*/ +#define WAL_NORMAL_MODE 0 +#define WAL_EXCLUSIVE_MODE 1 +#define WAL_HEAPMEMORY_MODE 2 + +/* +** Possible values for WAL.readOnly +*/ +#define WAL_RDWR 0 /* Normal read/write connection */ +#define WAL_RDONLY 1 /* The WAL file is readonly */ +#define WAL_SHM_RDONLY 2 /* The SHM file is readonly */ + +/* +** Each page of the wal-index mapping contains a hash-table made up of +** an array of HASHTABLE_NSLOT elements of the following type. +*/ +typedef u16 ht_slot; + +/* +** This structure is used to implement an iterator that loops through +** all frames in the WAL in database page order. Where two or more frames +** correspond to the same database page, the iterator visits only the +** frame most recently written to the WAL (in other words, the frame with +** the largest index). +** +** The internals of this structure are only accessed by: +** +** walIteratorInit() - Create a new iterator, +** walIteratorNext() - Step an iterator, +** walIteratorFree() - Free an iterator. +** +** This functionality is used by the checkpoint code (see walCheckpoint()). +*/ +struct WalIterator { + u32 iPrior; /* Last result returned from the iterator */ + int nSegment; /* Number of entries in aSegment[] */ + struct WalSegment { + int iNext; /* Next slot in aIndex[] not yet returned */ + ht_slot *aIndex; /* i0, i1, i2... such that aPgno[iN] ascend */ + u32 *aPgno; /* Array of page numbers. */ + int nEntry; /* Nr. of entries in aPgno[] and aIndex[] */ + int iZero; /* Frame number associated with aPgno[0] */ + } aSegment[1]; /* One for every 32KB page in the wal-index */ +}; + +/* +** Define the parameters of the hash tables in the wal-index file. There +** is a hash-table following every HASHTABLE_NPAGE page numbers in the +** wal-index. +** +** Changing any of these constants will alter the wal-index format and +** create incompatibilities. +*/ +#define HASHTABLE_NPAGE 4096 /* Must be power of 2 */ +#define HASHTABLE_HASH_1 383 /* Should be prime */ +#define HASHTABLE_NSLOT (HASHTABLE_NPAGE*2) /* Must be a power of 2 */ + +/* +** The block of page numbers associated with the first hash-table in a +** wal-index is smaller than usual. This is so that there is a complete +** hash-table on each aligned 32KB page of the wal-index. +*/ +#define HASHTABLE_NPAGE_ONE (HASHTABLE_NPAGE - (WALINDEX_HDR_SIZE/sizeof(u32))) + +/* The wal-index is divided into pages of WALINDEX_PGSZ bytes each. */ +#define WALINDEX_PGSZ ( \ + sizeof(ht_slot)*HASHTABLE_NSLOT + HASHTABLE_NPAGE*sizeof(u32) \ +) + +/* +** Structured Exception Handling (SEH) is a Windows-specific technique +** for catching exceptions raised while accessing memory-mapped files. +** +** The -DSQLITE_USE_SEH compile-time option means to use SEH to catch and +** deal with system-level errors that arise during WAL -shm file processing. +** Without this compile-time option, any system-level faults that appear +** while accessing the memory-mapped -shm file will cause a process-wide +** signal to be deliver, which will more than likely cause the entire +** process to exit. +*/ +#ifdef SQLITE_USE_SEH +#include <Windows.h> + +/* Beginning of a block of code in which an exception might occur */ +# define SEH_TRY __try { \ + assert( walAssertLockmask(pWal) && pWal->nSehTry==0 ); \ + VVA_ONLY(pWal->nSehTry++); + +/* The end of a block of code in which an exception might occur */ +# define SEH_EXCEPT(X) \ + VVA_ONLY(pWal->nSehTry--); \ + assert( pWal->nSehTry==0 ); \ + } __except( sehExceptionFilter(pWal, GetExceptionCode(), GetExceptionInformation() ) ){ X } + +/* Simulate a memory-mapping fault in the -shm file for testing purposes */ +# define SEH_INJECT_FAULT sehInjectFault(pWal) + +/* +** The second argument is the return value of GetExceptionCode() for the +** current exception. Return EXCEPTION_EXECUTE_HANDLER if the exception code +** indicates that the exception may have been caused by accessing the *-shm +** file mapping. Or EXCEPTION_CONTINUE_SEARCH otherwise. +*/ +static int sehExceptionFilter(Wal *pWal, int eCode, EXCEPTION_POINTERS *p){ + VVA_ONLY(pWal->nSehTry--); + if( eCode==EXCEPTION_IN_PAGE_ERROR ){ + if( p && p->ExceptionRecord && p->ExceptionRecord->NumberParameters>=3 ){ + /* From MSDN: For this type of exception, the first element of the + ** ExceptionInformation[] array is a read-write flag - 0 if the exception + ** was thrown while reading, 1 if while writing. The second element is + ** the virtual address being accessed. The "third array element specifies + ** the underlying NTSTATUS code that resulted in the exception". */ + pWal->iSysErrno = (int)p->ExceptionRecord->ExceptionInformation[2]; + } + return EXCEPTION_EXECUTE_HANDLER; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +/* +** If one is configured, invoke the xTestCallback callback with 650 as +** the argument. If it returns true, throw the same exception that is +** thrown by the system if the *-shm file mapping is accessed after it +** has been invalidated. +*/ +static void sehInjectFault(Wal *pWal){ + int res; + assert( pWal->nSehTry>0 ); + + res = sqlite3FaultSim(650); + if( res!=0 ){ + ULONG_PTR aArg[3]; + aArg[0] = 0; + aArg[1] = 0; + aArg[2] = (ULONG_PTR)res; + RaiseException(EXCEPTION_IN_PAGE_ERROR, 0, 3, (const ULONG_PTR*)aArg); + } +} + +/* +** There are two ways to use this macro. To set a pointer to be freed +** if an exception is thrown: +** +** SEH_FREE_ON_ERROR(0, pPtr); +** +** and to cancel the same: +** +** SEH_FREE_ON_ERROR(pPtr, 0); +** +** In the first case, there must not already be a pointer registered to +** be freed. In the second case, pPtr must be the registered pointer. +*/ +#define SEH_FREE_ON_ERROR(X,Y) \ + assert( (X==0 || Y==0) && pWal->pFree==X ); pWal->pFree = Y + +/* +** There are two ways to use this macro. To arrange for pWal->apWiData[iPg] +** to be set to pValue if an exception is thrown: +** +** SEH_SET_ON_ERROR(iPg, pValue); +** +** and to cancel the same: +** +** SEH_SET_ON_ERROR(0, 0); +*/ +#define SEH_SET_ON_ERROR(X,Y) pWal->iWiPg = X; pWal->pWiValue = Y + +#else +# define SEH_TRY VVA_ONLY(pWal->nSehTry++); +# define SEH_EXCEPT(X) VVA_ONLY(pWal->nSehTry--); assert( pWal->nSehTry==0 ); +# define SEH_INJECT_FAULT assert( pWal->nSehTry>0 ); +# define SEH_FREE_ON_ERROR(X,Y) +# define SEH_SET_ON_ERROR(X,Y) +#endif /* ifdef SQLITE_USE_SEH */ + + +/* +** Obtain a pointer to the iPage'th page of the wal-index. The wal-index +** is broken into pages of WALINDEX_PGSZ bytes. Wal-index pages are +** numbered from zero. +** +** If the wal-index is currently smaller the iPage pages then the size +** of the wal-index might be increased, but only if it is safe to do +** so. It is safe to enlarge the wal-index if pWal->writeLock is true +** or pWal->exclusiveMode==WAL_HEAPMEMORY_MODE. +** +** Three possible result scenarios: +** +** (1) rc==SQLITE_OK and *ppPage==Requested-Wal-Index-Page +** (2) rc>=SQLITE_ERROR and *ppPage==NULL +** (3) rc==SQLITE_OK and *ppPage==NULL // only if iPage==0 +** +** Scenario (3) can only occur when pWal->writeLock is false and iPage==0 +*/ +static SQLITE_NOINLINE int walIndexPageRealloc( + Wal *pWal, /* The WAL context */ + int iPage, /* The page we seek */ + volatile u32 **ppPage /* Write the page pointer here */ +){ + int rc = SQLITE_OK; + + /* Enlarge the pWal->apWiData[] array if required */ + if( pWal->nWiData<=iPage ){ + sqlite3_int64 nByte = sizeof(u32*)*(iPage+1); + volatile u32 **apNew; + apNew = (volatile u32 **)sqlite3Realloc((void *)pWal->apWiData, nByte); + if( !apNew ){ + *ppPage = 0; + return SQLITE_NOMEM_BKPT; + } + memset((void*)&apNew[pWal->nWiData], 0, + sizeof(u32*)*(iPage+1-pWal->nWiData)); + pWal->apWiData = apNew; + pWal->nWiData = iPage+1; + } + + /* Request a pointer to the required page from the VFS */ + assert( pWal->apWiData[iPage]==0 ); + if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){ + pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ); + if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT; + }else{ + rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, + pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] + ); + assert( pWal->apWiData[iPage]!=0 + || rc!=SQLITE_OK + || (pWal->writeLock==0 && iPage==0) ); + testcase( pWal->apWiData[iPage]==0 && rc==SQLITE_OK ); + if( rc==SQLITE_OK ){ + if( iPage>0 && sqlite3FaultSim(600) ) rc = SQLITE_NOMEM; + }else if( (rc&0xff)==SQLITE_READONLY ){ + pWal->readOnly |= WAL_SHM_RDONLY; + if( rc==SQLITE_READONLY ){ + rc = SQLITE_OK; + } + } + } + + *ppPage = pWal->apWiData[iPage]; + assert( iPage==0 || *ppPage || rc!=SQLITE_OK ); + return rc; +} +static int walIndexPage( + Wal *pWal, /* The WAL context */ + int iPage, /* The page we seek */ + volatile u32 **ppPage /* Write the page pointer here */ +){ + SEH_INJECT_FAULT; + if( pWal->nWiData<=iPage || (*ppPage = pWal->apWiData[iPage])==0 ){ + return walIndexPageRealloc(pWal, iPage, ppPage); + } + return SQLITE_OK; +} + +/* +** Return a pointer to the WalCkptInfo structure in the wal-index. +*/ +static volatile WalCkptInfo *walCkptInfo(Wal *pWal){ + assert( pWal->nWiData>0 && pWal->apWiData[0] ); + SEH_INJECT_FAULT; + return (volatile WalCkptInfo*)&(pWal->apWiData[0][sizeof(WalIndexHdr)/2]); +} + +/* +** Return a pointer to the WalIndexHdr structure in the wal-index. +*/ +static volatile WalIndexHdr *walIndexHdr(Wal *pWal){ + assert( pWal->nWiData>0 && pWal->apWiData[0] ); + SEH_INJECT_FAULT; + return (volatile WalIndexHdr*)pWal->apWiData[0]; +} + +/* +** The argument to this macro must be of type u32. On a little-endian +** architecture, it returns the u32 value that results from interpreting +** the 4 bytes as a big-endian value. On a big-endian architecture, it +** returns the value that would be produced by interpreting the 4 bytes +** of the input value as a little-endian integer. +*/ +#define BYTESWAP32(x) ( \ + (((x)&0x000000FF)<<24) + (((x)&0x0000FF00)<<8) \ + + (((x)&0x00FF0000)>>8) + (((x)&0xFF000000)>>24) \ +) + +/* +** Generate or extend an 8 byte checksum based on the data in +** array aByte[] and the initial values of aIn[0] and aIn[1] (or +** initial values of 0 and 0 if aIn==NULL). +** +** The checksum is written back into aOut[] before returning. +** +** nByte must be a positive multiple of 8. +*/ +static void walChecksumBytes( + int nativeCksum, /* True for native byte-order, false for non-native */ + u8 *a, /* Content to be checksummed */ + int nByte, /* Bytes of content in a[]. Must be a multiple of 8. */ + const u32 *aIn, /* Initial checksum value input */ + u32 *aOut /* OUT: Final checksum value output */ +){ + u32 s1, s2; + u32 *aData = (u32 *)a; + u32 *aEnd = (u32 *)&a[nByte]; + + if( aIn ){ + s1 = aIn[0]; + s2 = aIn[1]; + }else{ + s1 = s2 = 0; + } + + assert( nByte>=8 ); + assert( (nByte&0x00000007)==0 ); + assert( nByte<=65536 ); + assert( nByte%4==0 ); + + if( !nativeCksum ){ + do { + s1 += BYTESWAP32(aData[0]) + s2; + s2 += BYTESWAP32(aData[1]) + s1; + aData += 2; + }while( aData<aEnd ); + }else if( nByte%64==0 ){ + do { + s1 += *aData++ + s2; + s2 += *aData++ + s1; + s1 += *aData++ + s2; + s2 += *aData++ + s1; + s1 += *aData++ + s2; + s2 += *aData++ + s1; + s1 += *aData++ + s2; + s2 += *aData++ + s1; + s1 += *aData++ + s2; + s2 += *aData++ + s1; + s1 += *aData++ + s2; + s2 += *aData++ + s1; + s1 += *aData++ + s2; + s2 += *aData++ + s1; + s1 += *aData++ + s2; + s2 += *aData++ + s1; + }while( aData<aEnd ); + }else{ + do { + s1 += *aData++ + s2; + s2 += *aData++ + s1; + }while( aData<aEnd ); + } + assert( aData==aEnd ); + + aOut[0] = s1; + aOut[1] = s2; +} + +/* +** If there is the possibility of concurrent access to the SHM file +** from multiple threads and/or processes, then do a memory barrier. +*/ +static void walShmBarrier(Wal *pWal){ + if( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE ){ + sqlite3OsShmBarrier(pWal->pDbFd); + } +} + +/* +** Add the SQLITE_NO_TSAN as part of the return-type of a function +** definition as a hint that the function contains constructs that +** might give false-positive TSAN warnings. +** +** See tag-20200519-1. +*/ +#if defined(__clang__) && !defined(SQLITE_NO_TSAN) +# define SQLITE_NO_TSAN __attribute__((no_sanitize_thread)) +#else +# define SQLITE_NO_TSAN +#endif + +/* +** Write the header information in pWal->hdr into the wal-index. +** +** The checksum on pWal->hdr is updated before it is written. +*/ +static SQLITE_NO_TSAN void walIndexWriteHdr(Wal *pWal){ + volatile WalIndexHdr *aHdr = walIndexHdr(pWal); + const int nCksum = offsetof(WalIndexHdr, aCksum); + + assert( pWal->writeLock ); + pWal->hdr.isInit = 1; + pWal->hdr.iVersion = WALINDEX_MAX_VERSION; + walChecksumBytes(1, (u8*)&pWal->hdr, nCksum, 0, pWal->hdr.aCksum); + /* Possible TSAN false-positive. See tag-20200519-1 */ + memcpy((void*)&aHdr[1], (const void*)&pWal->hdr, sizeof(WalIndexHdr)); + walShmBarrier(pWal); + memcpy((void*)&aHdr[0], (const void*)&pWal->hdr, sizeof(WalIndexHdr)); +} + +/* +** This function encodes a single frame header and writes it to a buffer +** supplied by the caller. A frame-header is made up of a series of +** 4-byte big-endian integers, as follows: +** +** 0: Page number. +** 4: For commit records, the size of the database image in pages +** after the commit. For all other records, zero. +** 8: Salt-1 (copied from the wal-header) +** 12: Salt-2 (copied from the wal-header) +** 16: Checksum-1. +** 20: Checksum-2. +*/ +static void walEncodeFrame( + Wal *pWal, /* The write-ahead log */ + u32 iPage, /* Database page number for frame */ + u32 nTruncate, /* New db size (or 0 for non-commit frames) */ + u8 *aData, /* Pointer to page data */ + u8 *aFrame /* OUT: Write encoded frame here */ +){ + int nativeCksum; /* True for native byte-order checksums */ + u32 *aCksum = pWal->hdr.aFrameCksum; + assert( WAL_FRAME_HDRSIZE==24 ); + sqlite3Put4byte(&aFrame[0], iPage); + sqlite3Put4byte(&aFrame[4], nTruncate); + if( pWal->iReCksum==0 ){ + memcpy(&aFrame[8], pWal->hdr.aSalt, 8); + + nativeCksum = (pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN); + walChecksumBytes(nativeCksum, aFrame, 8, aCksum, aCksum); + walChecksumBytes(nativeCksum, aData, pWal->szPage, aCksum, aCksum); + + sqlite3Put4byte(&aFrame[16], aCksum[0]); + sqlite3Put4byte(&aFrame[20], aCksum[1]); + }else{ + memset(&aFrame[8], 0, 16); + } +} + +/* +** Check to see if the frame with header in aFrame[] and content +** in aData[] is valid. If it is a valid frame, fill *piPage and +** *pnTruncate and return true. Return if the frame is not valid. +*/ +static int walDecodeFrame( + Wal *pWal, /* The write-ahead log */ + u32 *piPage, /* OUT: Database page number for frame */ + u32 *pnTruncate, /* OUT: New db size (or 0 if not commit) */ + u8 *aData, /* Pointer to page data (for checksum) */ + u8 *aFrame /* Frame data */ +){ + int nativeCksum; /* True for native byte-order checksums */ + u32 *aCksum = pWal->hdr.aFrameCksum; + u32 pgno; /* Page number of the frame */ + assert( WAL_FRAME_HDRSIZE==24 ); + + /* A frame is only valid if the salt values in the frame-header + ** match the salt values in the wal-header. + */ + if( memcmp(&pWal->hdr.aSalt, &aFrame[8], 8)!=0 ){ + return 0; + } + + /* A frame is only valid if the page number is greater than zero. + */ + pgno = sqlite3Get4byte(&aFrame[0]); + if( pgno==0 ){ + return 0; + } + + /* A frame is only valid if a checksum of the WAL header, + ** all prior frames, the first 16 bytes of this frame-header, + ** and the frame-data matches the checksum in the last 8 + ** bytes of this frame-header. + */ + nativeCksum = (pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN); + walChecksumBytes(nativeCksum, aFrame, 8, aCksum, aCksum); + walChecksumBytes(nativeCksum, aData, pWal->szPage, aCksum, aCksum); + if( aCksum[0]!=sqlite3Get4byte(&aFrame[16]) + || aCksum[1]!=sqlite3Get4byte(&aFrame[20]) + ){ + /* Checksum failed. */ + return 0; + } + + /* If we reach this point, the frame is valid. Return the page number + ** and the new database size. + */ + *piPage = pgno; + *pnTruncate = sqlite3Get4byte(&aFrame[4]); + return 1; +} + + +#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) +/* +** Names of locks. This routine is used to provide debugging output and is not +** a part of an ordinary build. +*/ +static const char *walLockName(int lockIdx){ + if( lockIdx==WAL_WRITE_LOCK ){ + return "WRITE-LOCK"; + }else if( lockIdx==WAL_CKPT_LOCK ){ + return "CKPT-LOCK"; + }else if( lockIdx==WAL_RECOVER_LOCK ){ + return "RECOVER-LOCK"; + }else{ + static char zName[15]; + sqlite3_snprintf(sizeof(zName), zName, "READ-LOCK[%d]", + lockIdx-WAL_READ_LOCK(0)); + return zName; + } +} +#endif /*defined(SQLITE_TEST) || defined(SQLITE_DEBUG) */ + + +/* +** Set or release locks on the WAL. Locks are either shared or exclusive. +** A lock cannot be moved directly between shared and exclusive - it must go +** through the unlocked state first. +** +** In locking_mode=EXCLUSIVE, all of these routines become no-ops. +*/ +static int walLockShared(Wal *pWal, int lockIdx){ + int rc; + if( pWal->exclusiveMode ) return SQLITE_OK; + rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, 1, + SQLITE_SHM_LOCK | SQLITE_SHM_SHARED); + WALTRACE(("WAL%p: acquire SHARED-%s %s\n", pWal, + walLockName(lockIdx), rc ? "failed" : "ok")); + VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && (rc&0xFF)!=SQLITE_BUSY); ) +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_OK ) pWal->lockMask |= (1 << lockIdx); +#endif + return rc; +} +static void walUnlockShared(Wal *pWal, int lockIdx){ + if( pWal->exclusiveMode ) return; + (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, 1, + SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED); +#ifdef SQLITE_USE_SEH + pWal->lockMask &= ~(1 << lockIdx); +#endif + WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx))); +} +static int walLockExclusive(Wal *pWal, int lockIdx, int n){ + int rc; + if( pWal->exclusiveMode ) return SQLITE_OK; + rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, n, + SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE); + WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal, + walLockName(lockIdx), n, rc ? "failed" : "ok")); + VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && (rc&0xFF)!=SQLITE_BUSY); ) +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_OK ){ + pWal->lockMask |= (((1<<n)-1) << (SQLITE_SHM_NLOCK+lockIdx)); + } +#endif + return rc; +} +static void walUnlockExclusive(Wal *pWal, int lockIdx, int n){ + if( pWal->exclusiveMode ) return; + (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, n, + SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE); +#ifdef SQLITE_USE_SEH + pWal->lockMask &= ~(((1<<n)-1) << (SQLITE_SHM_NLOCK+lockIdx)); +#endif + WALTRACE(("WAL%p: release EXCLUSIVE-%s cnt=%d\n", pWal, + walLockName(lockIdx), n)); +} + +/* +** Compute a hash on a page number. The resulting hash value must land +** between 0 and (HASHTABLE_NSLOT-1). The walHashNext() function advances +** the hash to the next value in the event of a collision. +*/ +static int walHash(u32 iPage){ + assert( iPage>0 ); + assert( (HASHTABLE_NSLOT & (HASHTABLE_NSLOT-1))==0 ); + return (iPage*HASHTABLE_HASH_1) & (HASHTABLE_NSLOT-1); +} +static int walNextHash(int iPriorHash){ + return (iPriorHash+1)&(HASHTABLE_NSLOT-1); +} + +/* +** An instance of the WalHashLoc object is used to describe the location +** of a page hash table in the wal-index. This becomes the return value +** from walHashGet(). +*/ +typedef struct WalHashLoc WalHashLoc; +struct WalHashLoc { + volatile ht_slot *aHash; /* Start of the wal-index hash table */ + volatile u32 *aPgno; /* aPgno[1] is the page of first frame indexed */ + u32 iZero; /* One less than the frame number of first indexed*/ +}; + +/* +** Return pointers to the hash table and page number array stored on +** page iHash of the wal-index. The wal-index is broken into 32KB pages +** numbered starting from 0. +** +** Set output variable pLoc->aHash to point to the start of the hash table +** in the wal-index file. Set pLoc->iZero to one less than the frame +** number of the first frame indexed by this hash table. If a +** slot in the hash table is set to N, it refers to frame number +** (pLoc->iZero+N) in the log. +** +** Finally, set pLoc->aPgno so that pLoc->aPgno[0] is the page number of the +** first frame indexed by the hash table, frame (pLoc->iZero). +*/ +static int walHashGet( + Wal *pWal, /* WAL handle */ + int iHash, /* Find the iHash'th table */ + WalHashLoc *pLoc /* OUT: Hash table location */ +){ + int rc; /* Return code */ + + rc = walIndexPage(pWal, iHash, &pLoc->aPgno); + assert( rc==SQLITE_OK || iHash>0 ); + + if( pLoc->aPgno ){ + pLoc->aHash = (volatile ht_slot *)&pLoc->aPgno[HASHTABLE_NPAGE]; + if( iHash==0 ){ + pLoc->aPgno = &pLoc->aPgno[WALINDEX_HDR_SIZE/sizeof(u32)]; + pLoc->iZero = 0; + }else{ + pLoc->iZero = HASHTABLE_NPAGE_ONE + (iHash-1)*HASHTABLE_NPAGE; + } + }else if( NEVER(rc==SQLITE_OK) ){ + rc = SQLITE_ERROR; + } + return rc; +} + +/* +** Return the number of the wal-index page that contains the hash-table +** and page-number array that contain entries corresponding to WAL frame +** iFrame. The wal-index is broken up into 32KB pages. Wal-index pages +** are numbered starting from 0. +*/ +static int walFramePage(u32 iFrame){ + int iHash = (iFrame+HASHTABLE_NPAGE-HASHTABLE_NPAGE_ONE-1) / HASHTABLE_NPAGE; + assert( (iHash==0 || iFrame>HASHTABLE_NPAGE_ONE) + && (iHash>=1 || iFrame<=HASHTABLE_NPAGE_ONE) + && (iHash<=1 || iFrame>(HASHTABLE_NPAGE_ONE+HASHTABLE_NPAGE)) + && (iHash>=2 || iFrame<=HASHTABLE_NPAGE_ONE+HASHTABLE_NPAGE) + && (iHash<=2 || iFrame>(HASHTABLE_NPAGE_ONE+2*HASHTABLE_NPAGE)) + ); + assert( iHash>=0 ); + return iHash; +} + +/* +** Return the page number associated with frame iFrame in this WAL. +*/ +static u32 walFramePgno(Wal *pWal, u32 iFrame){ + int iHash = walFramePage(iFrame); + SEH_INJECT_FAULT; + if( iHash==0 ){ + return pWal->apWiData[0][WALINDEX_HDR_SIZE/sizeof(u32) + iFrame - 1]; + } + return pWal->apWiData[iHash][(iFrame-1-HASHTABLE_NPAGE_ONE)%HASHTABLE_NPAGE]; +} + +/* +** Remove entries from the hash table that point to WAL slots greater +** than pWal->hdr.mxFrame. +** +** This function is called whenever pWal->hdr.mxFrame is decreased due +** to a rollback or savepoint. +** +** At most only the hash table containing pWal->hdr.mxFrame needs to be +** updated. Any later hash tables will be automatically cleared when +** pWal->hdr.mxFrame advances to the point where those hash tables are +** actually needed. +*/ +static void walCleanupHash(Wal *pWal){ + WalHashLoc sLoc; /* Hash table location */ + int iLimit = 0; /* Zero values greater than this */ + int nByte; /* Number of bytes to zero in aPgno[] */ + int i; /* Used to iterate through aHash[] */ + + assert( pWal->writeLock ); + testcase( pWal->hdr.mxFrame==HASHTABLE_NPAGE_ONE-1 ); + testcase( pWal->hdr.mxFrame==HASHTABLE_NPAGE_ONE ); + testcase( pWal->hdr.mxFrame==HASHTABLE_NPAGE_ONE+1 ); + + if( pWal->hdr.mxFrame==0 ) return; + + /* Obtain pointers to the hash-table and page-number array containing + ** the entry that corresponds to frame pWal->hdr.mxFrame. It is guaranteed + ** that the page said hash-table and array reside on is already mapped.(1) + */ + assert( pWal->nWiData>walFramePage(pWal->hdr.mxFrame) ); + assert( pWal->apWiData[walFramePage(pWal->hdr.mxFrame)] ); + i = walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &sLoc); + if( NEVER(i) ) return; /* Defense-in-depth, in case (1) above is wrong */ + + /* Zero all hash-table entries that correspond to frame numbers greater + ** than pWal->hdr.mxFrame. + */ + iLimit = pWal->hdr.mxFrame - sLoc.iZero; + assert( iLimit>0 ); + for(i=0; i<HASHTABLE_NSLOT; i++){ + if( sLoc.aHash[i]>iLimit ){ + sLoc.aHash[i] = 0; + } + } + + /* Zero the entries in the aPgno array that correspond to frames with + ** frame numbers greater than pWal->hdr.mxFrame. + */ + nByte = (int)((char *)sLoc.aHash - (char *)&sLoc.aPgno[iLimit]); + assert( nByte>=0 ); + memset((void *)&sLoc.aPgno[iLimit], 0, nByte); + +#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT + /* Verify that the every entry in the mapping region is still reachable + ** via the hash table even after the cleanup. + */ + if( iLimit ){ + int j; /* Loop counter */ + int iKey; /* Hash key */ + for(j=0; j<iLimit; j++){ + for(iKey=walHash(sLoc.aPgno[j]);sLoc.aHash[iKey];iKey=walNextHash(iKey)){ + if( sLoc.aHash[iKey]==j+1 ) break; + } + assert( sLoc.aHash[iKey]==j+1 ); + } + } +#endif /* SQLITE_ENABLE_EXPENSIVE_ASSERT */ +} + + +/* +** Set an entry in the wal-index that will map database page number +** pPage into WAL frame iFrame. +*/ +static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ + int rc; /* Return code */ + WalHashLoc sLoc; /* Wal-index hash table location */ + + rc = walHashGet(pWal, walFramePage(iFrame), &sLoc); + + /* Assuming the wal-index file was successfully mapped, populate the + ** page number array and hash table entry. + */ + if( rc==SQLITE_OK ){ + int iKey; /* Hash table key */ + int idx; /* Value to write to hash-table slot */ + int nCollide; /* Number of hash collisions */ + + idx = iFrame - sLoc.iZero; + assert( idx <= HASHTABLE_NSLOT/2 + 1 ); + + /* If this is the first entry to be added to this hash-table, zero the + ** entire hash table and aPgno[] array before proceeding. + */ + if( idx==1 ){ + int nByte = (int)((u8*)&sLoc.aHash[HASHTABLE_NSLOT] - (u8*)sLoc.aPgno); + assert( nByte>=0 ); + memset((void*)sLoc.aPgno, 0, nByte); + } + + /* If the entry in aPgno[] is already set, then the previous writer + ** must have exited unexpectedly in the middle of a transaction (after + ** writing one or more dirty pages to the WAL to free up memory). + ** Remove the remnants of that writers uncommitted transaction from + ** the hash-table before writing any new entries. + */ + if( sLoc.aPgno[idx-1] ){ + walCleanupHash(pWal); + assert( !sLoc.aPgno[idx-1] ); + } + + /* Write the aPgno[] array entry and the hash-table slot. */ + nCollide = idx; + for(iKey=walHash(iPage); sLoc.aHash[iKey]; iKey=walNextHash(iKey)){ + if( (nCollide--)==0 ) return SQLITE_CORRUPT_BKPT; + } + sLoc.aPgno[idx-1] = iPage; + AtomicStore(&sLoc.aHash[iKey], (ht_slot)idx); + +#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT + /* Verify that the number of entries in the hash table exactly equals + ** the number of entries in the mapping region. + */ + { + int i; /* Loop counter */ + int nEntry = 0; /* Number of entries in the hash table */ + for(i=0; i<HASHTABLE_NSLOT; i++){ if( sLoc.aHash[i] ) nEntry++; } + assert( nEntry==idx ); + } + + /* Verify that the every entry in the mapping region is reachable + ** via the hash table. This turns out to be a really, really expensive + ** thing to check, so only do this occasionally - not on every + ** iteration. + */ + if( (idx&0x3ff)==0 ){ + int i; /* Loop counter */ + for(i=0; i<idx; i++){ + for(iKey=walHash(sLoc.aPgno[i]); + sLoc.aHash[iKey]; + iKey=walNextHash(iKey)){ + if( sLoc.aHash[iKey]==i+1 ) break; + } + assert( sLoc.aHash[iKey]==i+1 ); + } + } +#endif /* SQLITE_ENABLE_EXPENSIVE_ASSERT */ + } + + return rc; +} + + +/* +** Recover the wal-index by reading the write-ahead log file. +** +** This routine first tries to establish an exclusive lock on the +** wal-index to prevent other threads/processes from doing anything +** with the WAL or wal-index while recovery is running. The +** WAL_RECOVER_LOCK is also held so that other threads will know +** that this thread is running recovery. If unable to establish +** the necessary locks, this routine returns SQLITE_BUSY. +*/ +static int walIndexRecover(Wal *pWal){ + int rc; /* Return Code */ + i64 nSize; /* Size of log file */ + u32 aFrameCksum[2] = {0, 0}; + int iLock; /* Lock offset to lock for checkpoint */ + + /* Obtain an exclusive lock on all byte in the locking range not already + ** locked by the caller. The caller is guaranteed to have locked the + ** WAL_WRITE_LOCK byte, and may have also locked the WAL_CKPT_LOCK byte. + ** If successful, the same bytes that are locked here are unlocked before + ** this function returns. + */ + assert( pWal->ckptLock==1 || pWal->ckptLock==0 ); + assert( WAL_ALL_BUT_WRITE==WAL_WRITE_LOCK+1 ); + assert( WAL_CKPT_LOCK==WAL_ALL_BUT_WRITE ); + assert( pWal->writeLock ); + iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock; + rc = walLockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock); + if( rc ){ + return rc; + } + + WALTRACE(("WAL%p: recovery begin...\n", pWal)); + + memset(&pWal->hdr, 0, sizeof(WalIndexHdr)); + + rc = sqlite3OsFileSize(pWal->pWalFd, &nSize); + if( rc!=SQLITE_OK ){ + goto recovery_error; + } + + if( nSize>WAL_HDRSIZE ){ + u8 aBuf[WAL_HDRSIZE]; /* Buffer to load WAL header into */ + u32 *aPrivate = 0; /* Heap copy of *-shm hash being populated */ + u8 *aFrame = 0; /* Malloc'd buffer to load entire frame */ + int szFrame; /* Number of bytes in buffer aFrame[] */ + u8 *aData; /* Pointer to data part of aFrame buffer */ + int szPage; /* Page size according to the log */ + u32 magic; /* Magic value read from WAL header */ + u32 version; /* Magic value read from WAL header */ + int isValid; /* True if this frame is valid */ + u32 iPg; /* Current 32KB wal-index page */ + u32 iLastFrame; /* Last frame in wal, based on nSize alone */ + + /* Read in the WAL header. */ + rc = sqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0); + if( rc!=SQLITE_OK ){ + goto recovery_error; + } + + /* If the database page size is not a power of two, or is greater than + ** SQLITE_MAX_PAGE_SIZE, conclude that the WAL file contains no valid + ** data. Similarly, if the 'magic' value is invalid, ignore the whole + ** WAL file. + */ + magic = sqlite3Get4byte(&aBuf[0]); + szPage = sqlite3Get4byte(&aBuf[8]); + if( (magic&0xFFFFFFFE)!=WAL_MAGIC + || szPage&(szPage-1) + || szPage>SQLITE_MAX_PAGE_SIZE + || szPage<512 + ){ + goto finished; + } + pWal->hdr.bigEndCksum = (u8)(magic&0x00000001); + pWal->szPage = szPage; + pWal->nCkpt = sqlite3Get4byte(&aBuf[12]); + memcpy(&pWal->hdr.aSalt, &aBuf[16], 8); + + /* Verify that the WAL header checksum is correct */ + walChecksumBytes(pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN, + aBuf, WAL_HDRSIZE-2*4, 0, pWal->hdr.aFrameCksum + ); + if( pWal->hdr.aFrameCksum[0]!=sqlite3Get4byte(&aBuf[24]) + || pWal->hdr.aFrameCksum[1]!=sqlite3Get4byte(&aBuf[28]) + ){ + goto finished; + } + + /* Verify that the version number on the WAL format is one that + ** are able to understand */ + version = sqlite3Get4byte(&aBuf[4]); + if( version!=WAL_MAX_VERSION ){ + rc = SQLITE_CANTOPEN_BKPT; + goto finished; + } + + /* Malloc a buffer to read frames into. */ + szFrame = szPage + WAL_FRAME_HDRSIZE; + aFrame = (u8 *)sqlite3_malloc64(szFrame + WALINDEX_PGSZ); + SEH_FREE_ON_ERROR(0, aFrame); + if( !aFrame ){ + rc = SQLITE_NOMEM_BKPT; + goto recovery_error; + } + aData = &aFrame[WAL_FRAME_HDRSIZE]; + aPrivate = (u32*)&aData[szPage]; + + /* Read all frames from the log file. */ + iLastFrame = (nSize - WAL_HDRSIZE) / szFrame; + for(iPg=0; iPg<=(u32)walFramePage(iLastFrame); iPg++){ + u32 *aShare; + u32 iFrame; /* Index of last frame read */ + u32 iLast = MIN(iLastFrame, HASHTABLE_NPAGE_ONE+iPg*HASHTABLE_NPAGE); + u32 iFirst = 1 + (iPg==0?0:HASHTABLE_NPAGE_ONE+(iPg-1)*HASHTABLE_NPAGE); + u32 nHdr, nHdr32; + rc = walIndexPage(pWal, iPg, (volatile u32**)&aShare); + assert( aShare!=0 || rc!=SQLITE_OK ); + if( aShare==0 ) break; + SEH_SET_ON_ERROR(iPg, aShare); + pWal->apWiData[iPg] = aPrivate; + + for(iFrame=iFirst; iFrame<=iLast; iFrame++){ + i64 iOffset = walFrameOffset(iFrame, szPage); + u32 pgno; /* Database page number for frame */ + u32 nTruncate; /* dbsize field from frame header */ + + /* Read and decode the next log frame. */ + rc = sqlite3OsRead(pWal->pWalFd, aFrame, szFrame, iOffset); + if( rc!=SQLITE_OK ) break; + isValid = walDecodeFrame(pWal, &pgno, &nTruncate, aData, aFrame); + if( !isValid ) break; + rc = walIndexAppend(pWal, iFrame, pgno); + if( NEVER(rc!=SQLITE_OK) ) break; + + /* If nTruncate is non-zero, this is a commit record. */ + if( nTruncate ){ + pWal->hdr.mxFrame = iFrame; + pWal->hdr.nPage = nTruncate; + pWal->hdr.szPage = (u16)((szPage&0xff00) | (szPage>>16)); + testcase( szPage<=32768 ); + testcase( szPage>=65536 ); + aFrameCksum[0] = pWal->hdr.aFrameCksum[0]; + aFrameCksum[1] = pWal->hdr.aFrameCksum[1]; + } + } + pWal->apWiData[iPg] = aShare; + SEH_SET_ON_ERROR(0,0); + nHdr = (iPg==0 ? WALINDEX_HDR_SIZE : 0); + nHdr32 = nHdr / sizeof(u32); +#ifndef SQLITE_SAFER_WALINDEX_RECOVERY + /* Memcpy() should work fine here, on all reasonable implementations. + ** Technically, memcpy() might change the destination to some + ** intermediate value before setting to the final value, and that might + ** cause a concurrent reader to malfunction. Memcpy() is allowed to + ** do that, according to the spec, but no memcpy() implementation that + ** we know of actually does that, which is why we say that memcpy() + ** is safe for this. Memcpy() is certainly a lot faster. + */ + memcpy(&aShare[nHdr32], &aPrivate[nHdr32], WALINDEX_PGSZ-nHdr); +#else + /* In the event that some platform is found for which memcpy() + ** changes the destination to some intermediate value before + ** setting the final value, this alternative copy routine is + ** provided. + */ + { + int i; + for(i=nHdr32; i<WALINDEX_PGSZ/sizeof(u32); i++){ + if( aShare[i]!=aPrivate[i] ){ + /* Atomic memory operations are not required here because if + ** the value needs to be changed, that means it is not being + ** accessed concurrently. */ + aShare[i] = aPrivate[i]; + } + } + } +#endif + SEH_INJECT_FAULT; + if( iFrame<=iLast ) break; + } + + SEH_FREE_ON_ERROR(aFrame, 0); + sqlite3_free(aFrame); + } + +finished: + if( rc==SQLITE_OK ){ + volatile WalCkptInfo *pInfo; + int i; + pWal->hdr.aFrameCksum[0] = aFrameCksum[0]; + pWal->hdr.aFrameCksum[1] = aFrameCksum[1]; + walIndexWriteHdr(pWal); + + /* Reset the checkpoint-header. This is safe because this thread is + ** currently holding locks that exclude all other writers and + ** checkpointers. Then set the values of read-mark slots 1 through N. + */ + pInfo = walCkptInfo(pWal); + pInfo->nBackfill = 0; + pInfo->nBackfillAttempted = pWal->hdr.mxFrame; + pInfo->aReadMark[0] = 0; + for(i=1; i<WAL_NREADER; i++){ + rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1); + if( rc==SQLITE_OK ){ + if( i==1 && pWal->hdr.mxFrame ){ + pInfo->aReadMark[i] = pWal->hdr.mxFrame; + }else{ + pInfo->aReadMark[i] = READMARK_NOT_USED; + } + SEH_INJECT_FAULT; + walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); + }else if( rc!=SQLITE_BUSY ){ + goto recovery_error; + } + } + + /* If more than one frame was recovered from the log file, report an + ** event via sqlite3_log(). This is to help with identifying performance + ** problems caused by applications routinely shutting down without + ** checkpointing the log file. + */ + if( pWal->hdr.nPage ){ + sqlite3_log(SQLITE_NOTICE_RECOVER_WAL, + "recovered %d frames from WAL file %s", + pWal->hdr.mxFrame, pWal->zWalName + ); + } + } + +recovery_error: + WALTRACE(("WAL%p: recovery %s\n", pWal, rc ? "failed" : "ok")); + walUnlockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock); + return rc; +} + +/* +** Close an open wal-index. +*/ +static void walIndexClose(Wal *pWal, int isDelete){ + if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE || pWal->bShmUnreliable ){ + int i; + for(i=0; i<pWal->nWiData; i++){ + sqlite3_free((void *)pWal->apWiData[i]); + pWal->apWiData[i] = 0; + } + } + if( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE ){ + sqlite3OsShmUnmap(pWal->pDbFd, isDelete); + } +} + +/* +** Open a connection to the WAL file zWalName. The database file must +** already be opened on connection pDbFd. The buffer that zWalName points +** to must remain valid for the lifetime of the returned Wal* handle. +** +** A SHARED lock should be held on the database file when this function +** is called. The purpose of this SHARED lock is to prevent any other +** client from unlinking the WAL or wal-index file. If another process +** were to do this just after this client opened one of these files, the +** system would be badly broken. +** +** If the log file is successfully opened, SQLITE_OK is returned and +** *ppWal is set to point to a new WAL handle. If an error occurs, +** an SQLite error code is returned and *ppWal is left unmodified. +*/ +SQLITE_PRIVATE int sqlite3WalOpen( + sqlite3_vfs *pVfs, /* vfs module to open wal and wal-index */ + sqlite3_file *pDbFd, /* The open database file */ + const char *zWalName, /* Name of the WAL file */ + int bNoShm, /* True to run in heap-memory mode */ + i64 mxWalSize, /* Truncate WAL to this size on reset */ + Wal **ppWal /* OUT: Allocated Wal handle */ +){ + int rc; /* Return Code */ + Wal *pRet; /* Object to allocate and return */ + int flags; /* Flags passed to OsOpen() */ + + assert( zWalName && zWalName[0] ); + assert( pDbFd ); + + /* Verify the values of various constants. Any changes to the values + ** of these constants would result in an incompatible on-disk format + ** for the -shm file. Any change that causes one of these asserts to + ** fail is a backward compatibility problem, even if the change otherwise + ** works. + ** + ** This table also serves as a helpful cross-reference when trying to + ** interpret hex dumps of the -shm file. + */ + assert( 48 == sizeof(WalIndexHdr) ); + assert( 40 == sizeof(WalCkptInfo) ); + assert( 120 == WALINDEX_LOCK_OFFSET ); + assert( 136 == WALINDEX_HDR_SIZE ); + assert( 4096 == HASHTABLE_NPAGE ); + assert( 4062 == HASHTABLE_NPAGE_ONE ); + assert( 8192 == HASHTABLE_NSLOT ); + assert( 383 == HASHTABLE_HASH_1 ); + assert( 32768 == WALINDEX_PGSZ ); + assert( 8 == SQLITE_SHM_NLOCK ); + assert( 5 == WAL_NREADER ); + assert( 24 == WAL_FRAME_HDRSIZE ); + assert( 32 == WAL_HDRSIZE ); + assert( 120 == WALINDEX_LOCK_OFFSET + WAL_WRITE_LOCK ); + assert( 121 == WALINDEX_LOCK_OFFSET + WAL_CKPT_LOCK ); + assert( 122 == WALINDEX_LOCK_OFFSET + WAL_RECOVER_LOCK ); + assert( 123 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(0) ); + assert( 124 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(1) ); + assert( 125 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(2) ); + assert( 126 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(3) ); + assert( 127 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(4) ); + + /* In the amalgamation, the os_unix.c and os_win.c source files come before + ** this source file. Verify that the #defines of the locking byte offsets + ** in os_unix.c and os_win.c agree with the WALINDEX_LOCK_OFFSET value. + ** For that matter, if the lock offset ever changes from its initial design + ** value of 120, we need to know that so there is an assert() to check it. + */ +#ifdef WIN_SHM_BASE + assert( WIN_SHM_BASE==WALINDEX_LOCK_OFFSET ); +#endif +#ifdef UNIX_SHM_BASE + assert( UNIX_SHM_BASE==WALINDEX_LOCK_OFFSET ); +#endif + + + /* Allocate an instance of struct Wal to return. */ + *ppWal = 0; + pRet = (Wal*)sqlite3MallocZero(sizeof(Wal) + pVfs->szOsFile); + if( !pRet ){ + return SQLITE_NOMEM_BKPT; + } + + pRet->pVfs = pVfs; + pRet->pWalFd = (sqlite3_file *)&pRet[1]; + pRet->pDbFd = pDbFd; + pRet->readLock = -1; + pRet->mxWalSize = mxWalSize; + pRet->zWalName = zWalName; + pRet->syncHeader = 1; + pRet->padToSectorBoundary = 1; + pRet->exclusiveMode = (bNoShm ? WAL_HEAPMEMORY_MODE: WAL_NORMAL_MODE); + + /* Open file handle on the write-ahead log file. */ + flags = (SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_WAL); + rc = sqlite3OsOpen(pVfs, zWalName, pRet->pWalFd, flags, &flags); + if( rc==SQLITE_OK && flags&SQLITE_OPEN_READONLY ){ + pRet->readOnly = WAL_RDONLY; + } + + if( rc!=SQLITE_OK ){ + walIndexClose(pRet, 0); + sqlite3OsClose(pRet->pWalFd); + sqlite3_free(pRet); + }else{ + int iDC = sqlite3OsDeviceCharacteristics(pDbFd); + if( iDC & SQLITE_IOCAP_SEQUENTIAL ){ pRet->syncHeader = 0; } + if( iDC & SQLITE_IOCAP_POWERSAFE_OVERWRITE ){ + pRet->padToSectorBoundary = 0; + } + *ppWal = pRet; + WALTRACE(("WAL%d: opened\n", pRet)); + } + return rc; +} + +/* +** Change the size to which the WAL file is truncated on each reset. +*/ +SQLITE_PRIVATE void sqlite3WalLimit(Wal *pWal, i64 iLimit){ + if( pWal ) pWal->mxWalSize = iLimit; +} + +/* +** Find the smallest page number out of all pages held in the WAL that +** has not been returned by any prior invocation of this method on the +** same WalIterator object. Write into *piFrame the frame index where +** that page was last written into the WAL. Write into *piPage the page +** number. +** +** Return 0 on success. If there are no pages in the WAL with a page +** number larger than *piPage, then return 1. +*/ +static int walIteratorNext( + WalIterator *p, /* Iterator */ + u32 *piPage, /* OUT: The page number of the next page */ + u32 *piFrame /* OUT: Wal frame index of next page */ +){ + u32 iMin; /* Result pgno must be greater than iMin */ + u32 iRet = 0xFFFFFFFF; /* 0xffffffff is never a valid page number */ + int i; /* For looping through segments */ + + iMin = p->iPrior; + assert( iMin<0xffffffff ); + for(i=p->nSegment-1; i>=0; i--){ + struct WalSegment *pSegment = &p->aSegment[i]; + while( pSegment->iNext<pSegment->nEntry ){ + u32 iPg = pSegment->aPgno[pSegment->aIndex[pSegment->iNext]]; + if( iPg>iMin ){ + if( iPg<iRet ){ + iRet = iPg; + *piFrame = pSegment->iZero + pSegment->aIndex[pSegment->iNext]; + } + break; + } + pSegment->iNext++; + } + } + + *piPage = p->iPrior = iRet; + return (iRet==0xFFFFFFFF); +} + +/* +** This function merges two sorted lists into a single sorted list. +** +** aLeft[] and aRight[] are arrays of indices. The sort key is +** aContent[aLeft[]] and aContent[aRight[]]. Upon entry, the following +** is guaranteed for all J<K: +** +** aContent[aLeft[J]] < aContent[aLeft[K]] +** aContent[aRight[J]] < aContent[aRight[K]] +** +** This routine overwrites aRight[] with a new (probably longer) sequence +** of indices such that the aRight[] contains every index that appears in +** either aLeft[] or the old aRight[] and such that the second condition +** above is still met. +** +** The aContent[aLeft[X]] values will be unique for all X. And the +** aContent[aRight[X]] values will be unique too. But there might be +** one or more combinations of X and Y such that +** +** aLeft[X]!=aRight[Y] && aContent[aLeft[X]] == aContent[aRight[Y]] +** +** When that happens, omit the aLeft[X] and use the aRight[Y] index. +*/ +static void walMerge( + const u32 *aContent, /* Pages in wal - keys for the sort */ + ht_slot *aLeft, /* IN: Left hand input list */ + int nLeft, /* IN: Elements in array *paLeft */ + ht_slot **paRight, /* IN/OUT: Right hand input list */ + int *pnRight, /* IN/OUT: Elements in *paRight */ + ht_slot *aTmp /* Temporary buffer */ +){ + int iLeft = 0; /* Current index in aLeft */ + int iRight = 0; /* Current index in aRight */ + int iOut = 0; /* Current index in output buffer */ + int nRight = *pnRight; + ht_slot *aRight = *paRight; + + assert( nLeft>0 && nRight>0 ); + while( iRight<nRight || iLeft<nLeft ){ + ht_slot logpage; + Pgno dbpage; + + if( (iLeft<nLeft) + && (iRight>=nRight || aContent[aLeft[iLeft]]<aContent[aRight[iRight]]) + ){ + logpage = aLeft[iLeft++]; + }else{ + logpage = aRight[iRight++]; + } + dbpage = aContent[logpage]; + + aTmp[iOut++] = logpage; + if( iLeft<nLeft && aContent[aLeft[iLeft]]==dbpage ) iLeft++; + + assert( iLeft>=nLeft || aContent[aLeft[iLeft]]>dbpage ); + assert( iRight>=nRight || aContent[aRight[iRight]]>dbpage ); + } + + *paRight = aLeft; + *pnRight = iOut; + memcpy(aLeft, aTmp, sizeof(aTmp[0])*iOut); +} + +/* +** Sort the elements in list aList using aContent[] as the sort key. +** Remove elements with duplicate keys, preferring to keep the +** larger aList[] values. +** +** The aList[] entries are indices into aContent[]. The values in +** aList[] are to be sorted so that for all J<K: +** +** aContent[aList[J]] < aContent[aList[K]] +** +** For any X and Y such that +** +** aContent[aList[X]] == aContent[aList[Y]] +** +** Keep the larger of the two values aList[X] and aList[Y] and discard +** the smaller. +*/ +static void walMergesort( + const u32 *aContent, /* Pages in wal */ + ht_slot *aBuffer, /* Buffer of at least *pnList items to use */ + ht_slot *aList, /* IN/OUT: List to sort */ + int *pnList /* IN/OUT: Number of elements in aList[] */ +){ + struct Sublist { + int nList; /* Number of elements in aList */ + ht_slot *aList; /* Pointer to sub-list content */ + }; + + const int nList = *pnList; /* Size of input list */ + int nMerge = 0; /* Number of elements in list aMerge */ + ht_slot *aMerge = 0; /* List to be merged */ + int iList; /* Index into input list */ + u32 iSub = 0; /* Index into aSub array */ + struct Sublist aSub[13]; /* Array of sub-lists */ + + memset(aSub, 0, sizeof(aSub)); + assert( nList<=HASHTABLE_NPAGE && nList>0 ); + assert( HASHTABLE_NPAGE==(1<<(ArraySize(aSub)-1)) ); + + for(iList=0; iList<nList; iList++){ + nMerge = 1; + aMerge = &aList[iList]; + for(iSub=0; iList & (1<<iSub); iSub++){ + struct Sublist *p; + assert( iSub<ArraySize(aSub) ); + p = &aSub[iSub]; + assert( p->aList && p->nList<=(1<<iSub) ); + assert( p->aList==&aList[iList&~((2<<iSub)-1)] ); + walMerge(aContent, p->aList, p->nList, &aMerge, &nMerge, aBuffer); + } + aSub[iSub].aList = aMerge; + aSub[iSub].nList = nMerge; + } + + for(iSub++; iSub<ArraySize(aSub); iSub++){ + if( nList & (1<<iSub) ){ + struct Sublist *p; + assert( iSub<ArraySize(aSub) ); + p = &aSub[iSub]; + assert( p->nList<=(1<<iSub) ); + assert( p->aList==&aList[nList&~((2<<iSub)-1)] ); + walMerge(aContent, p->aList, p->nList, &aMerge, &nMerge, aBuffer); + } + } + assert( aMerge==aList ); + *pnList = nMerge; + +#ifdef SQLITE_DEBUG + { + int i; + for(i=1; i<*pnList; i++){ + assert( aContent[aList[i]] > aContent[aList[i-1]] ); + } + } +#endif +} + +/* +** Free an iterator allocated by walIteratorInit(). +*/ +static void walIteratorFree(WalIterator *p){ + sqlite3_free(p); +} + +/* +** Construct a WalInterator object that can be used to loop over all +** pages in the WAL following frame nBackfill in ascending order. Frames +** nBackfill or earlier may be included - excluding them is an optimization +** only. The caller must hold the checkpoint lock. +** +** On success, make *pp point to the newly allocated WalInterator object +** return SQLITE_OK. Otherwise, return an error code. If this routine +** returns an error, the value of *pp is undefined. +** +** The calling routine should invoke walIteratorFree() to destroy the +** WalIterator object when it has finished with it. +*/ +static int walIteratorInit(Wal *pWal, u32 nBackfill, WalIterator **pp){ + WalIterator *p; /* Return value */ + int nSegment; /* Number of segments to merge */ + u32 iLast; /* Last frame in log */ + sqlite3_int64 nByte; /* Number of bytes to allocate */ + int i; /* Iterator variable */ + ht_slot *aTmp; /* Temp space used by merge-sort */ + int rc = SQLITE_OK; /* Return Code */ + + /* This routine only runs while holding the checkpoint lock. And + ** it only runs if there is actually content in the log (mxFrame>0). + */ + assert( pWal->ckptLock && pWal->hdr.mxFrame>0 ); + iLast = pWal->hdr.mxFrame; + + /* Allocate space for the WalIterator object. */ + nSegment = walFramePage(iLast) + 1; + nByte = sizeof(WalIterator) + + (nSegment-1)*sizeof(struct WalSegment) + + iLast*sizeof(ht_slot); + p = (WalIterator *)sqlite3_malloc64(nByte + + sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast) + ); + if( !p ){ + return SQLITE_NOMEM_BKPT; + } + memset(p, 0, nByte); + p->nSegment = nSegment; + aTmp = (ht_slot*)&(((u8*)p)[nByte]); + SEH_FREE_ON_ERROR(0, p); + for(i=walFramePage(nBackfill+1); rc==SQLITE_OK && i<nSegment; i++){ + WalHashLoc sLoc; + + rc = walHashGet(pWal, i, &sLoc); + if( rc==SQLITE_OK ){ + int j; /* Counter variable */ + int nEntry; /* Number of entries in this segment */ + ht_slot *aIndex; /* Sorted index for this segment */ + + if( (i+1)==nSegment ){ + nEntry = (int)(iLast - sLoc.iZero); + }else{ + nEntry = (int)((u32*)sLoc.aHash - (u32*)sLoc.aPgno); + } + aIndex = &((ht_slot *)&p->aSegment[p->nSegment])[sLoc.iZero]; + sLoc.iZero++; + + for(j=0; j<nEntry; j++){ + aIndex[j] = (ht_slot)j; + } + walMergesort((u32 *)sLoc.aPgno, aTmp, aIndex, &nEntry); + p->aSegment[i].iZero = sLoc.iZero; + p->aSegment[i].nEntry = nEntry; + p->aSegment[i].aIndex = aIndex; + p->aSegment[i].aPgno = (u32 *)sLoc.aPgno; + } + } + if( rc!=SQLITE_OK ){ + SEH_FREE_ON_ERROR(p, 0); + walIteratorFree(p); + p = 0; + } + *pp = p; + return rc; +} + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +/* +** Attempt to enable blocking locks. Blocking locks are enabled only if (a) +** they are supported by the VFS, and (b) the database handle is configured +** with a busy-timeout. Return 1 if blocking locks are successfully enabled, +** or 0 otherwise. +*/ +static int walEnableBlocking(Wal *pWal){ + int res = 0; + if( pWal->db ){ + int tmout = pWal->db->busyTimeout; + if( tmout ){ + int rc; + rc = sqlite3OsFileControl( + pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout + ); + res = (rc==SQLITE_OK); + } + } + return res; +} + +/* +** Disable blocking locks. +*/ +static void walDisableBlocking(Wal *pWal){ + int tmout = 0; + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout); +} + +/* +** If parameter bLock is true, attempt to enable blocking locks, take +** the WRITER lock, and then disable blocking locks. If blocking locks +** cannot be enabled, no attempt to obtain the WRITER lock is made. Return +** an SQLite error code if an error occurs, or SQLITE_OK otherwise. It is not +** an error if blocking locks can not be enabled. +** +** If the bLock parameter is false and the WRITER lock is held, release it. +*/ +SQLITE_PRIVATE int sqlite3WalWriteLock(Wal *pWal, int bLock){ + int rc = SQLITE_OK; + assert( pWal->readLock<0 || bLock==0 ); + if( bLock ){ + assert( pWal->db ); + if( walEnableBlocking(pWal) ){ + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); + if( rc==SQLITE_OK ){ + pWal->writeLock = 1; + } + walDisableBlocking(pWal); + } + }else if( pWal->writeLock ){ + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + pWal->writeLock = 0; + } + return rc; +} + +/* +** Set the database handle used to determine if blocking locks are required. +*/ +SQLITE_PRIVATE void sqlite3WalDb(Wal *pWal, sqlite3 *db){ + pWal->db = db; +} + +/* +** Take an exclusive WRITE lock. Blocking if so configured. +*/ +static int walLockWriter(Wal *pWal){ + int rc; + walEnableBlocking(pWal); + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); + walDisableBlocking(pWal); + return rc; +} +#else +# define walEnableBlocking(x) 0 +# define walDisableBlocking(x) +# define walLockWriter(pWal) walLockExclusive((pWal), WAL_WRITE_LOCK, 1) +# define sqlite3WalDb(pWal, db) +#endif /* ifdef SQLITE_ENABLE_SETLK_TIMEOUT */ + + +/* +** Attempt to obtain the exclusive WAL lock defined by parameters lockIdx and +** n. If the attempt fails and parameter xBusy is not NULL, then it is a +** busy-handler function. Invoke it and retry the lock until either the +** lock is successfully obtained or the busy-handler returns 0. +*/ +static int walBusyLock( + Wal *pWal, /* WAL connection */ + int (*xBusy)(void*), /* Function to call when busy */ + void *pBusyArg, /* Context argument for xBusyHandler */ + int lockIdx, /* Offset of first byte to lock */ + int n /* Number of bytes to lock */ +){ + int rc; + do { + rc = walLockExclusive(pWal, lockIdx, n); + }while( xBusy && rc==SQLITE_BUSY && xBusy(pBusyArg) ); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_BUSY_TIMEOUT ){ + walDisableBlocking(pWal); + rc = SQLITE_BUSY; + } +#endif + return rc; +} + +/* +** The cache of the wal-index header must be valid to call this function. +** Return the page-size in bytes used by the database. +*/ +static int walPagesize(Wal *pWal){ + return (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16); +} + +/* +** The following is guaranteed when this function is called: +** +** a) the WRITER lock is held, +** b) the entire log file has been checkpointed, and +** c) any existing readers are reading exclusively from the database +** file - there are no readers that may attempt to read a frame from +** the log file. +** +** This function updates the shared-memory structures so that the next +** client to write to the database (which may be this one) does so by +** writing frames into the start of the log file. +** +** The value of parameter salt1 is used as the aSalt[1] value in the +** new wal-index header. It should be passed a pseudo-random value (i.e. +** one obtained from sqlite3_randomness()). +*/ +static void walRestartHdr(Wal *pWal, u32 salt1){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + int i; /* Loop counter */ + u32 *aSalt = pWal->hdr.aSalt; /* Big-endian salt values */ + pWal->nCkpt++; + pWal->hdr.mxFrame = 0; + sqlite3Put4byte((u8*)&aSalt[0], 1 + sqlite3Get4byte((u8*)&aSalt[0])); + memcpy(&pWal->hdr.aSalt[1], &salt1, 4); + walIndexWriteHdr(pWal); + AtomicStore(&pInfo->nBackfill, 0); + pInfo->nBackfillAttempted = 0; + pInfo->aReadMark[1] = 0; + for(i=2; i<WAL_NREADER; i++) pInfo->aReadMark[i] = READMARK_NOT_USED; + assert( pInfo->aReadMark[0]==0 ); +} + +/* +** Copy as much content as we can from the WAL back into the database file +** in response to an sqlite3_wal_checkpoint() request or the equivalent. +** +** The amount of information copies from WAL to database might be limited +** by active readers. This routine will never overwrite a database page +** that a concurrent reader might be using. +** +** All I/O barrier operations (a.k.a fsyncs) occur in this routine when +** SQLite is in WAL-mode in synchronous=NORMAL. That means that if +** checkpoints are always run by a background thread or background +** process, foreground threads will never block on a lengthy fsync call. +** +** Fsync is called on the WAL before writing content out of the WAL and +** into the database. This ensures that if the new content is persistent +** in the WAL and can be recovered following a power-loss or hard reset. +** +** Fsync is also called on the database file if (and only if) the entire +** WAL content is copied into the database file. This second fsync makes +** it safe to delete the WAL since the new content will persist in the +** database file. +** +** This routine uses and updates the nBackfill field of the wal-index header. +** This is the only routine that will increase the value of nBackfill. +** (A WAL reset or recovery will revert nBackfill to zero, but not increase +** its value.) +** +** The caller must be holding sufficient locks to ensure that no other +** checkpoint is running (in any other thread or process) at the same +** time. +*/ +static int walCheckpoint( + Wal *pWal, /* Wal connection */ + sqlite3 *db, /* Check for interrupts on this handle */ + int eMode, /* One of PASSIVE, FULL or RESTART */ + int (*xBusy)(void*), /* Function to call when busy */ + void *pBusyArg, /* Context argument for xBusyHandler */ + int sync_flags, /* Flags for OsSync() (or 0) */ + u8 *zBuf /* Temporary buffer to use */ +){ + int rc = SQLITE_OK; /* Return code */ + int szPage; /* Database page-size */ + WalIterator *pIter = 0; /* Wal iterator context */ + u32 iDbpage = 0; /* Next database page to write */ + u32 iFrame = 0; /* Wal frame containing data for iDbpage */ + u32 mxSafeFrame; /* Max frame that can be backfilled */ + u32 mxPage; /* Max database page to write */ + int i; /* Loop counter */ + volatile WalCkptInfo *pInfo; /* The checkpoint status information */ + + szPage = walPagesize(pWal); + testcase( szPage<=32768 ); + testcase( szPage>=65536 ); + pInfo = walCkptInfo(pWal); + if( pInfo->nBackfill<pWal->hdr.mxFrame ){ + + /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked + ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ + assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); + + /* Compute in mxSafeFrame the index of the last frame of the WAL that is + ** safe to write into the database. Frames beyond mxSafeFrame might + ** overwrite database pages that are in use by active readers and thus + ** cannot be backfilled from the WAL. + */ + mxSafeFrame = pWal->hdr.mxFrame; + mxPage = pWal->hdr.nPage; + for(i=1; i<WAL_NREADER; i++){ + u32 y = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; + if( mxSafeFrame>y ){ + assert( y<=pWal->hdr.mxFrame ); + rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); + if( rc==SQLITE_OK ){ + u32 iMark = (i==1 ? mxSafeFrame : READMARK_NOT_USED); + AtomicStore(pInfo->aReadMark+i, iMark); SEH_INJECT_FAULT; + walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); + }else if( rc==SQLITE_BUSY ){ + mxSafeFrame = y; + xBusy = 0; + }else{ + goto walcheckpoint_out; + } + } + } + + /* Allocate the iterator */ + if( pInfo->nBackfill<mxSafeFrame ){ + rc = walIteratorInit(pWal, pInfo->nBackfill, &pIter); + assert( rc==SQLITE_OK || pIter==0 ); + } + + if( pIter + && (rc = walBusyLock(pWal,xBusy,pBusyArg,WAL_READ_LOCK(0),1))==SQLITE_OK + ){ + u32 nBackfill = pInfo->nBackfill; + pInfo->nBackfillAttempted = mxSafeFrame; SEH_INJECT_FAULT; + + /* Sync the WAL to disk */ + rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags)); + + /* If the database may grow as a result of this checkpoint, hint + ** about the eventual size of the db file to the VFS layer. + */ + if( rc==SQLITE_OK ){ + i64 nReq = ((i64)mxPage * szPage); + i64 nSize; /* Current size of database file */ + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_START, 0); + rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); + if( rc==SQLITE_OK && nSize<nReq ){ + if( (nSize+65536+(i64)pWal->hdr.mxFrame*szPage)<nReq ){ + /* If the size of the final database is larger than the current + ** database plus the amount of data in the wal file, plus the + ** maximum size of the pending-byte page (65536 bytes), then + ** must be corruption somewhere. */ + rc = SQLITE_CORRUPT_BKPT; + }else{ + sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT,&nReq); + } + } + + } + + /* Iterate through the contents of the WAL, copying data to the db file */ + while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ + i64 iOffset; + assert( walFramePgno(pWal, iFrame)==iDbpage ); + SEH_INJECT_FAULT; + if( AtomicLoad(&db->u1.isInterrupted) ){ + rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; + break; + } + if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){ + continue; + } + iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; + /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ + rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); + if( rc!=SQLITE_OK ) break; + iOffset = (iDbpage-1)*(i64)szPage; + testcase( IS_BIG_INT(iOffset) ); + rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); + if( rc!=SQLITE_OK ) break; + } + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_DONE, 0); + + /* If work was actually accomplished... */ + if( rc==SQLITE_OK ){ + if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ + i64 szDb = pWal->hdr.nPage*(i64)szPage; + testcase( IS_BIG_INT(szDb) ); + rc = sqlite3OsTruncate(pWal->pDbFd, szDb); + if( rc==SQLITE_OK ){ + rc = sqlite3OsSync(pWal->pDbFd, CKPT_SYNC_FLAGS(sync_flags)); + } + } + if( rc==SQLITE_OK ){ + AtomicStore(&pInfo->nBackfill, mxSafeFrame); SEH_INJECT_FAULT; + } + } + + /* Release the reader lock held while backfilling */ + walUnlockExclusive(pWal, WAL_READ_LOCK(0), 1); + } + + if( rc==SQLITE_BUSY ){ + /* Reset the return code so as not to report a checkpoint failure + ** just because there are active readers. */ + rc = SQLITE_OK; + } + } + + /* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the + ** entire wal file has been copied into the database file, then block + ** until all readers have finished using the wal file. This ensures that + ** the next process to write to the database restarts the wal file. + */ + if( rc==SQLITE_OK && eMode!=SQLITE_CHECKPOINT_PASSIVE ){ + assert( pWal->writeLock ); + SEH_INJECT_FAULT; + if( pInfo->nBackfill<pWal->hdr.mxFrame ){ + rc = SQLITE_BUSY; + }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){ + u32 salt1; + sqlite3_randomness(4, &salt1); + assert( pInfo->nBackfill==pWal->hdr.mxFrame ); + rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1); + if( rc==SQLITE_OK ){ + if( eMode==SQLITE_CHECKPOINT_TRUNCATE ){ + /* IMPLEMENTATION-OF: R-44699-57140 This mode works the same way as + ** SQLITE_CHECKPOINT_RESTART with the addition that it also + ** truncates the log file to zero bytes just prior to a + ** successful return. + ** + ** In theory, it might be safe to do this without updating the + ** wal-index header in shared memory, as all subsequent reader or + ** writer clients should see that the entire log file has been + ** checkpointed and behave accordingly. This seems unsafe though, + ** as it would leave the system in a state where the contents of + ** the wal-index header do not match the contents of the + ** file-system. To avoid this, update the wal-index header to + ** indicate that the log file contains zero valid frames. */ + walRestartHdr(pWal, salt1); + rc = sqlite3OsTruncate(pWal->pWalFd, 0); + } + walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); + } + } + } + + walcheckpoint_out: + SEH_FREE_ON_ERROR(pIter, 0); + walIteratorFree(pIter); + return rc; +} + +/* +** If the WAL file is currently larger than nMax bytes in size, truncate +** it to exactly nMax bytes. If an error occurs while doing so, ignore it. +*/ +static void walLimitSize(Wal *pWal, i64 nMax){ + i64 sz; + int rx; + sqlite3BeginBenignMalloc(); + rx = sqlite3OsFileSize(pWal->pWalFd, &sz); + if( rx==SQLITE_OK && (sz > nMax ) ){ + rx = sqlite3OsTruncate(pWal->pWalFd, nMax); + } + sqlite3EndBenignMalloc(); + if( rx ){ + sqlite3_log(rx, "cannot limit WAL size: %s", pWal->zWalName); + } +} + +#ifdef SQLITE_USE_SEH +/* +** This is the "standard" exception handler used in a few places to handle +** an exception thrown by reading from the *-shm mapping after it has become +** invalid in SQLITE_USE_SEH builds. It is used as follows: +** +** SEH_TRY { ... } +** SEH_EXCEPT( rc = walHandleException(pWal); ) +** +** This function does three things: +** +** 1) Determines the locks that should be held, based on the contents of +** the Wal.readLock, Wal.writeLock and Wal.ckptLock variables. All other +** held locks are assumed to be transient locks that would have been +** released had the exception not been thrown and are dropped. +** +** 2) Frees the pointer at Wal.pFree, if any, using sqlite3_free(). +** +** 3) Set pWal->apWiData[pWal->iWiPg] to pWal->pWiValue if not NULL +** +** 4) Returns SQLITE_IOERR. +*/ +static int walHandleException(Wal *pWal){ + if( pWal->exclusiveMode==0 ){ + static const int S = 1; + static const int E = (1<<SQLITE_SHM_NLOCK); + int ii; + u32 mUnlock = pWal->lockMask & ~( + (pWal->readLock<0 ? 0 : (S << WAL_READ_LOCK(pWal->readLock))) + | (pWal->writeLock ? (E << WAL_WRITE_LOCK) : 0) + | (pWal->ckptLock ? (E << WAL_CKPT_LOCK) : 0) + ); + for(ii=0; ii<SQLITE_SHM_NLOCK; ii++){ + if( (S<<ii) & mUnlock ) walUnlockShared(pWal, ii); + if( (E<<ii) & mUnlock ) walUnlockExclusive(pWal, ii, 1); + } + } + sqlite3_free(pWal->pFree); + pWal->pFree = 0; + if( pWal->pWiValue ){ + pWal->apWiData[pWal->iWiPg] = pWal->pWiValue; + pWal->pWiValue = 0; + } + return SQLITE_IOERR_IN_PAGE; +} + +/* +** Assert that the Wal.lockMask mask, which indicates the locks held +** by the connenction, is consistent with the Wal.readLock, Wal.writeLock +** and Wal.ckptLock variables. To be used as: +** +** assert( walAssertLockmask(pWal) ); +*/ +static int walAssertLockmask(Wal *pWal){ + if( pWal->exclusiveMode==0 ){ + static const int S = 1; + static const int E = (1<<SQLITE_SHM_NLOCK); + u32 mExpect = ( + (pWal->readLock<0 ? 0 : (S << WAL_READ_LOCK(pWal->readLock))) + | (pWal->writeLock ? (E << WAL_WRITE_LOCK) : 0) + | (pWal->ckptLock ? (E << WAL_CKPT_LOCK) : 0) +#ifdef SQLITE_ENABLE_SNAPSHOT + | (pWal->pSnapshot ? (pWal->lockMask & (1 << WAL_CKPT_LOCK)) : 0) +#endif + ); + assert( mExpect==pWal->lockMask ); + } + return 1; +} + +/* +** Return and zero the "system error" field set when an +** EXCEPTION_IN_PAGE_ERROR exception is caught. +*/ +SQLITE_PRIVATE int sqlite3WalSystemErrno(Wal *pWal){ + int iRet = 0; + if( pWal ){ + iRet = pWal->iSysErrno; + pWal->iSysErrno = 0; + } + return iRet; +} + +#else +# define walAssertLockmask(x) 1 +#endif /* ifdef SQLITE_USE_SEH */ + +/* +** Close a connection to a log file. +*/ +SQLITE_PRIVATE int sqlite3WalClose( + Wal *pWal, /* Wal to close */ + sqlite3 *db, /* For interrupt flag */ + int sync_flags, /* Flags to pass to OsSync() (or 0) */ + int nBuf, + u8 *zBuf /* Buffer of at least nBuf bytes */ +){ + int rc = SQLITE_OK; + if( pWal ){ + int isDelete = 0; /* True to unlink wal and wal-index files */ + + assert( walAssertLockmask(pWal) ); + + /* If an EXCLUSIVE lock can be obtained on the database file (using the + ** ordinary, rollback-mode locking methods, this guarantees that the + ** connection associated with this log file is the only connection to + ** the database. In this case checkpoint the database and unlink both + ** the wal and wal-index files. + ** + ** The EXCLUSIVE lock is not released before returning. + */ + if( zBuf!=0 + && SQLITE_OK==(rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE)) + ){ + if( pWal->exclusiveMode==WAL_NORMAL_MODE ){ + pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; + } + rc = sqlite3WalCheckpoint(pWal, db, + SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0 + ); + if( rc==SQLITE_OK ){ + int bPersist = -1; + sqlite3OsFileControlHint( + pWal->pDbFd, SQLITE_FCNTL_PERSIST_WAL, &bPersist + ); + if( bPersist!=1 ){ + /* Try to delete the WAL file if the checkpoint completed and + ** fsynced (rc==SQLITE_OK) and if we are not in persistent-wal + ** mode (!bPersist) */ + isDelete = 1; + }else if( pWal->mxWalSize>=0 ){ + /* Try to truncate the WAL file to zero bytes if the checkpoint + ** completed and fsynced (rc==SQLITE_OK) and we are in persistent + ** WAL mode (bPersist) and if the PRAGMA journal_size_limit is a + ** non-negative value (pWal->mxWalSize>=0). Note that we truncate + ** to zero bytes as truncating to the journal_size_limit might + ** leave a corrupt WAL file on disk. */ + walLimitSize(pWal, 0); + } + } + } + + walIndexClose(pWal, isDelete); + sqlite3OsClose(pWal->pWalFd); + if( isDelete ){ + sqlite3BeginBenignMalloc(); + sqlite3OsDelete(pWal->pVfs, pWal->zWalName, 0); + sqlite3EndBenignMalloc(); + } + WALTRACE(("WAL%p: closed\n", pWal)); + sqlite3_free((void *)pWal->apWiData); + sqlite3_free(pWal); + } + return rc; +} + +/* +** Try to read the wal-index header. Return 0 on success and 1 if +** there is a problem. +** +** The wal-index is in shared memory. Another thread or process might +** be writing the header at the same time this procedure is trying to +** read it, which might result in inconsistency. A dirty read is detected +** by verifying that both copies of the header are the same and also by +** a checksum on the header. +** +** If and only if the read is consistent and the header is different from +** pWal->hdr, then pWal->hdr is updated to the content of the new header +** and *pChanged is set to 1. +** +** If the checksum cannot be verified return non-zero. If the header +** is read successfully and the checksum verified, return zero. +*/ +static SQLITE_NO_TSAN int walIndexTryHdr(Wal *pWal, int *pChanged){ + u32 aCksum[2]; /* Checksum on the header content */ + WalIndexHdr h1, h2; /* Two copies of the header content */ + WalIndexHdr volatile *aHdr; /* Header in shared memory */ + + /* The first page of the wal-index must be mapped at this point. */ + assert( pWal->nWiData>0 && pWal->apWiData[0] ); + + /* Read the header. This might happen concurrently with a write to the + ** same area of shared memory on a different CPU in a SMP, + ** meaning it is possible that an inconsistent snapshot is read + ** from the file. If this happens, return non-zero. + ** + ** tag-20200519-1: + ** There are two copies of the header at the beginning of the wal-index. + ** When reading, read [0] first then [1]. Writes are in the reverse order. + ** Memory barriers are used to prevent the compiler or the hardware from + ** reordering the reads and writes. TSAN and similar tools can sometimes + ** give false-positive warnings about these accesses because the tools do not + ** account for the double-read and the memory barrier. The use of mutexes + ** here would be problematic as the memory being accessed is potentially + ** shared among multiple processes and not all mutex implementations work + ** reliably in that environment. + */ + aHdr = walIndexHdr(pWal); + memcpy(&h1, (void *)&aHdr[0], sizeof(h1)); /* Possible TSAN false-positive */ + walShmBarrier(pWal); + memcpy(&h2, (void *)&aHdr[1], sizeof(h2)); + + if( memcmp(&h1, &h2, sizeof(h1))!=0 ){ + return 1; /* Dirty read */ + } + if( h1.isInit==0 ){ + return 1; /* Malformed header - probably all zeros */ + } + walChecksumBytes(1, (u8*)&h1, sizeof(h1)-sizeof(h1.aCksum), 0, aCksum); + if( aCksum[0]!=h1.aCksum[0] || aCksum[1]!=h1.aCksum[1] ){ + return 1; /* Checksum does not match */ + } + + if( memcmp(&pWal->hdr, &h1, sizeof(WalIndexHdr)) ){ + *pChanged = 1; + memcpy(&pWal->hdr, &h1, sizeof(WalIndexHdr)); + pWal->szPage = (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16); + testcase( pWal->szPage<=32768 ); + testcase( pWal->szPage>=65536 ); + } + + /* The header was successfully read. Return zero. */ + return 0; +} + +/* +** This is the value that walTryBeginRead returns when it needs to +** be retried. +*/ +#define WAL_RETRY (-1) + +/* +** Read the wal-index header from the wal-index and into pWal->hdr. +** If the wal-header appears to be corrupt, try to reconstruct the +** wal-index from the WAL before returning. +** +** Set *pChanged to 1 if the wal-index header value in pWal->hdr is +** changed by this operation. If pWal->hdr is unchanged, set *pChanged +** to 0. +** +** If the wal-index header is successfully read, return SQLITE_OK. +** Otherwise an SQLite error code. +*/ +static int walIndexReadHdr(Wal *pWal, int *pChanged){ + int rc; /* Return code */ + int badHdr; /* True if a header read failed */ + volatile u32 *page0; /* Chunk of wal-index containing header */ + + /* Ensure that page 0 of the wal-index (the page that contains the + ** wal-index header) is mapped. Return early if an error occurs here. + */ + assert( pChanged ); + rc = walIndexPage(pWal, 0, &page0); + if( rc!=SQLITE_OK ){ + assert( rc!=SQLITE_READONLY ); /* READONLY changed to OK in walIndexPage */ + if( rc==SQLITE_READONLY_CANTINIT ){ + /* The SQLITE_READONLY_CANTINIT return means that the shared-memory + ** was openable but is not writable, and this thread is unable to + ** confirm that another write-capable connection has the shared-memory + ** open, and hence the content of the shared-memory is unreliable, + ** since the shared-memory might be inconsistent with the WAL file + ** and there is no writer on hand to fix it. */ + assert( page0==0 ); + assert( pWal->writeLock==0 ); + assert( pWal->readOnly & WAL_SHM_RDONLY ); + pWal->bShmUnreliable = 1; + pWal->exclusiveMode = WAL_HEAPMEMORY_MODE; + *pChanged = 1; + }else{ + return rc; /* Any other non-OK return is just an error */ + } + }else{ + /* page0 can be NULL if the SHM is zero bytes in size and pWal->writeLock + ** is zero, which prevents the SHM from growing */ + testcase( page0!=0 ); + } + assert( page0!=0 || pWal->writeLock==0 ); + + /* If the first page of the wal-index has been mapped, try to read the + ** wal-index header immediately, without holding any lock. This usually + ** works, but may fail if the wal-index header is corrupt or currently + ** being modified by another thread or process. + */ + badHdr = (page0 ? walIndexTryHdr(pWal, pChanged) : 1); + + /* If the first attempt failed, it might have been due to a race + ** with a writer. So get a WRITE lock and try again. + */ + if( badHdr ){ + if( pWal->bShmUnreliable==0 && (pWal->readOnly & WAL_SHM_RDONLY) ){ + if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){ + walUnlockShared(pWal, WAL_WRITE_LOCK); + rc = SQLITE_READONLY_RECOVERY; + } + }else{ + int bWriteLock = pWal->writeLock; + if( bWriteLock || SQLITE_OK==(rc = walLockWriter(pWal)) ){ + pWal->writeLock = 1; + if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){ + badHdr = walIndexTryHdr(pWal, pChanged); + if( badHdr ){ + /* If the wal-index header is still malformed even while holding + ** a WRITE lock, it can only mean that the header is corrupted and + ** needs to be reconstructed. So run recovery to do exactly that. + */ + rc = walIndexRecover(pWal); + *pChanged = 1; + } + } + if( bWriteLock==0 ){ + pWal->writeLock = 0; + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + } + } + } + } + + /* If the header is read successfully, check the version number to make + ** sure the wal-index was not constructed with some future format that + ** this version of SQLite cannot understand. + */ + if( badHdr==0 && pWal->hdr.iVersion!=WALINDEX_MAX_VERSION ){ + rc = SQLITE_CANTOPEN_BKPT; + } + if( pWal->bShmUnreliable ){ + if( rc!=SQLITE_OK ){ + walIndexClose(pWal, 0); + pWal->bShmUnreliable = 0; + assert( pWal->nWiData>0 && pWal->apWiData[0]==0 ); + /* walIndexRecover() might have returned SHORT_READ if a concurrent + ** writer truncated the WAL out from under it. If that happens, it + ** indicates that a writer has fixed the SHM file for us, so retry */ + if( rc==SQLITE_IOERR_SHORT_READ ) rc = WAL_RETRY; + } + pWal->exclusiveMode = WAL_NORMAL_MODE; + } + + return rc; +} + +/* +** Open a transaction in a connection where the shared-memory is read-only +** and where we cannot verify that there is a separate write-capable connection +** on hand to keep the shared-memory up-to-date with the WAL file. +** +** This can happen, for example, when the shared-memory is implemented by +** memory-mapping a *-shm file, where a prior writer has shut down and +** left the *-shm file on disk, and now the present connection is trying +** to use that database but lacks write permission on the *-shm file. +** Other scenarios are also possible, depending on the VFS implementation. +** +** Precondition: +** +** The *-wal file has been read and an appropriate wal-index has been +** constructed in pWal->apWiData[] using heap memory instead of shared +** memory. +** +** If this function returns SQLITE_OK, then the read transaction has +** been successfully opened. In this case output variable (*pChanged) +** is set to true before returning if the caller should discard the +** contents of the page cache before proceeding. Or, if it returns +** WAL_RETRY, then the heap memory wal-index has been discarded and +** the caller should retry opening the read transaction from the +** beginning (including attempting to map the *-shm file). +** +** If an error occurs, an SQLite error code is returned. +*/ +static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ + i64 szWal; /* Size of wal file on disk in bytes */ + i64 iOffset; /* Current offset when reading wal file */ + u8 aBuf[WAL_HDRSIZE]; /* Buffer to load WAL header into */ + u8 *aFrame = 0; /* Malloc'd buffer to load entire frame */ + int szFrame; /* Number of bytes in buffer aFrame[] */ + u8 *aData; /* Pointer to data part of aFrame buffer */ + volatile void *pDummy; /* Dummy argument for xShmMap */ + int rc; /* Return code */ + u32 aSaveCksum[2]; /* Saved copy of pWal->hdr.aFrameCksum */ + + assert( pWal->bShmUnreliable ); + assert( pWal->readOnly & WAL_SHM_RDONLY ); + assert( pWal->nWiData>0 && pWal->apWiData[0] ); + + /* Take WAL_READ_LOCK(0). This has the effect of preventing any + ** writers from running a checkpoint, but does not stop them + ** from running recovery. */ + rc = walLockShared(pWal, WAL_READ_LOCK(0)); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_BUSY ) rc = WAL_RETRY; + goto begin_unreliable_shm_out; + } + pWal->readLock = 0; + + /* Check to see if a separate writer has attached to the shared-memory area, + ** thus making the shared-memory "reliable" again. Do this by invoking + ** the xShmMap() routine of the VFS and looking to see if the return + ** is SQLITE_READONLY instead of SQLITE_READONLY_CANTINIT. + ** + ** If the shared-memory is now "reliable" return WAL_RETRY, which will + ** cause the heap-memory WAL-index to be discarded and the actual + ** shared memory to be used in its place. + ** + ** This step is important because, even though this connection is holding + ** the WAL_READ_LOCK(0) which prevents a checkpoint, a writer might + ** have already checkpointed the WAL file and, while the current + ** is active, wrap the WAL and start overwriting frames that this + ** process wants to use. + ** + ** Once sqlite3OsShmMap() has been called for an sqlite3_file and has + ** returned any SQLITE_READONLY value, it must return only SQLITE_READONLY + ** or SQLITE_READONLY_CANTINIT or some error for all subsequent invocations, + ** even if some external agent does a "chmod" to make the shared-memory + ** writable by us, until sqlite3OsShmUnmap() has been called. + ** This is a requirement on the VFS implementation. + */ + rc = sqlite3OsShmMap(pWal->pDbFd, 0, WALINDEX_PGSZ, 0, &pDummy); + assert( rc!=SQLITE_OK ); /* SQLITE_OK not possible for read-only connection */ + if( rc!=SQLITE_READONLY_CANTINIT ){ + rc = (rc==SQLITE_READONLY ? WAL_RETRY : rc); + goto begin_unreliable_shm_out; + } + + /* We reach this point only if the real shared-memory is still unreliable. + ** Assume the in-memory WAL-index substitute is correct and load it + ** into pWal->hdr. + */ + memcpy(&pWal->hdr, (void*)walIndexHdr(pWal), sizeof(WalIndexHdr)); + + /* Make sure some writer hasn't come in and changed the WAL file out + ** from under us, then disconnected, while we were not looking. + */ + rc = sqlite3OsFileSize(pWal->pWalFd, &szWal); + if( rc!=SQLITE_OK ){ + goto begin_unreliable_shm_out; + } + if( szWal<WAL_HDRSIZE ){ + /* If the wal file is too small to contain a wal-header and the + ** wal-index header has mxFrame==0, then it must be safe to proceed + ** reading the database file only. However, the page cache cannot + ** be trusted, as a read/write connection may have connected, written + ** the db, run a checkpoint, truncated the wal file and disconnected + ** since this client's last read transaction. */ + *pChanged = 1; + rc = (pWal->hdr.mxFrame==0 ? SQLITE_OK : WAL_RETRY); + goto begin_unreliable_shm_out; + } + + /* Check the salt keys at the start of the wal file still match. */ + rc = sqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0); + if( rc!=SQLITE_OK ){ + goto begin_unreliable_shm_out; + } + if( memcmp(&pWal->hdr.aSalt, &aBuf[16], 8) ){ + /* Some writer has wrapped the WAL file while we were not looking. + ** Return WAL_RETRY which will cause the in-memory WAL-index to be + ** rebuilt. */ + rc = WAL_RETRY; + goto begin_unreliable_shm_out; + } + + /* Allocate a buffer to read frames into */ + assert( (pWal->szPage & (pWal->szPage-1))==0 ); + assert( pWal->szPage>=512 && pWal->szPage<=65536 ); + szFrame = pWal->szPage + WAL_FRAME_HDRSIZE; + aFrame = (u8 *)sqlite3_malloc64(szFrame); + if( aFrame==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto begin_unreliable_shm_out; + } + aData = &aFrame[WAL_FRAME_HDRSIZE]; + + /* Check to see if a complete transaction has been appended to the + ** wal file since the heap-memory wal-index was created. If so, the + ** heap-memory wal-index is discarded and WAL_RETRY returned to + ** the caller. */ + aSaveCksum[0] = pWal->hdr.aFrameCksum[0]; + aSaveCksum[1] = pWal->hdr.aFrameCksum[1]; + for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->szPage); + iOffset+szFrame<=szWal; + iOffset+=szFrame + ){ + u32 pgno; /* Database page number for frame */ + u32 nTruncate; /* dbsize field from frame header */ + + /* Read and decode the next log frame. */ + rc = sqlite3OsRead(pWal->pWalFd, aFrame, szFrame, iOffset); + if( rc!=SQLITE_OK ) break; + if( !walDecodeFrame(pWal, &pgno, &nTruncate, aData, aFrame) ) break; + + /* If nTruncate is non-zero, then a complete transaction has been + ** appended to this wal file. Set rc to WAL_RETRY and break out of + ** the loop. */ + if( nTruncate ){ + rc = WAL_RETRY; + break; + } + } + pWal->hdr.aFrameCksum[0] = aSaveCksum[0]; + pWal->hdr.aFrameCksum[1] = aSaveCksum[1]; + + begin_unreliable_shm_out: + sqlite3_free(aFrame); + if( rc!=SQLITE_OK ){ + int i; + for(i=0; i<pWal->nWiData; i++){ + sqlite3_free((void*)pWal->apWiData[i]); + pWal->apWiData[i] = 0; + } + pWal->bShmUnreliable = 0; + sqlite3WalEndReadTransaction(pWal); + *pChanged = 1; + } + return rc; +} + +/* +** Attempt to start a read transaction. This might fail due to a race or +** other transient condition. When that happens, it returns WAL_RETRY to +** indicate to the caller that it is safe to retry immediately. +** +** On success return SQLITE_OK. On a permanent failure (such an +** I/O error or an SQLITE_BUSY because another process is running +** recovery) return a positive error code. +** +** The useWal parameter is true to force the use of the WAL and disable +** the case where the WAL is bypassed because it has been completely +** checkpointed. If useWal==0 then this routine calls walIndexReadHdr() +** to make a copy of the wal-index header into pWal->hdr. If the +** wal-index header has changed, *pChanged is set to 1 (as an indication +** to the caller that the local page cache is obsolete and needs to be +** flushed.) When useWal==1, the wal-index header is assumed to already +** be loaded and the pChanged parameter is unused. +** +** The caller must set the cnt parameter to the number of prior calls to +** this routine during the current read attempt that returned WAL_RETRY. +** This routine will start taking more aggressive measures to clear the +** race conditions after multiple WAL_RETRY returns, and after an excessive +** number of errors will ultimately return SQLITE_PROTOCOL. The +** SQLITE_PROTOCOL return indicates that some other process has gone rogue +** and is not honoring the locking protocol. There is a vanishingly small +** chance that SQLITE_PROTOCOL could be returned because of a run of really +** bad luck when there is lots of contention for the wal-index, but that +** possibility is so small that it can be safely neglected, we believe. +** +** On success, this routine obtains a read lock on +** WAL_READ_LOCK(pWal->readLock). The pWal->readLock integer is +** in the range 0 <= pWal->readLock < WAL_NREADER. If pWal->readLock==(-1) +** that means the Wal does not hold any read lock. The reader must not +** access any database page that is modified by a WAL frame up to and +** including frame number aReadMark[pWal->readLock]. The reader will +** use WAL frames up to and including pWal->hdr.mxFrame if pWal->readLock>0 +** Or if pWal->readLock==0, then the reader will ignore the WAL +** completely and get all content directly from the database file. +** If the useWal parameter is 1 then the WAL will never be ignored and +** this routine will always set pWal->readLock>0 on success. +** When the read transaction is completed, the caller must release the +** lock on WAL_READ_LOCK(pWal->readLock) and set pWal->readLock to -1. +** +** This routine uses the nBackfill and aReadMark[] fields of the header +** to select a particular WAL_READ_LOCK() that strives to let the +** checkpoint process do as much work as possible. This routine might +** update values of the aReadMark[] array in the header, but if it does +** so it takes care to hold an exclusive lock on the corresponding +** WAL_READ_LOCK() while changing values. +*/ +static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ + volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */ + u32 mxReadMark; /* Largest aReadMark[] value */ + int mxI; /* Index of largest aReadMark[] value */ + int i; /* Loop counter */ + int rc = SQLITE_OK; /* Return code */ + u32 mxFrame; /* Wal frame to lock to */ + + assert( pWal->readLock<0 ); /* Not currently locked */ + + /* useWal may only be set for read/write connections */ + assert( (pWal->readOnly & WAL_SHM_RDONLY)==0 || useWal==0 ); + + /* Take steps to avoid spinning forever if there is a protocol error. + ** + ** Circumstances that cause a RETRY should only last for the briefest + ** instances of time. No I/O or other system calls are done while the + ** locks are held, so the locks should not be held for very long. But + ** if we are unlucky, another process that is holding a lock might get + ** paged out or take a page-fault that is time-consuming to resolve, + ** during the few nanoseconds that it is holding the lock. In that case, + ** it might take longer than normal for the lock to free. + ** + ** After 5 RETRYs, we begin calling sqlite3OsSleep(). The first few + ** calls to sqlite3OsSleep() have a delay of 1 microsecond. Really this + ** is more of a scheduler yield than an actual delay. But on the 10th + ** an subsequent retries, the delays start becoming longer and longer, + ** so that on the 100th (and last) RETRY we delay for 323 milliseconds. + ** The total delay time before giving up is less than 10 seconds. + */ + if( cnt>5 ){ + int nDelay = 1; /* Pause time in microseconds */ + if( cnt>100 ){ + VVA_ONLY( pWal->lockError = 1; ) + return SQLITE_PROTOCOL; + } + if( cnt>=10 ) nDelay = (cnt-9)*(cnt-9)*39; + sqlite3OsSleep(pWal->pVfs, nDelay); + } + + if( !useWal ){ + assert( rc==SQLITE_OK ); + if( pWal->bShmUnreliable==0 ){ + rc = walIndexReadHdr(pWal, pChanged); + } + if( rc==SQLITE_BUSY ){ + /* If there is not a recovery running in another thread or process + ** then convert BUSY errors to WAL_RETRY. If recovery is known to + ** be running, convert BUSY to BUSY_RECOVERY. There is a race here + ** which might cause WAL_RETRY to be returned even if BUSY_RECOVERY + ** would be technically correct. But the race is benign since with + ** WAL_RETRY this routine will be called again and will probably be + ** right on the second iteration. + */ + if( pWal->apWiData[0]==0 ){ + /* This branch is taken when the xShmMap() method returns SQLITE_BUSY. + ** We assume this is a transient condition, so return WAL_RETRY. The + ** xShmMap() implementation used by the default unix and win32 VFS + ** modules may return SQLITE_BUSY due to a race condition in the + ** code that determines whether or not the shared-memory region + ** must be zeroed before the requested page is returned. + */ + rc = WAL_RETRY; + }else if( SQLITE_OK==(rc = walLockShared(pWal, WAL_RECOVER_LOCK)) ){ + walUnlockShared(pWal, WAL_RECOVER_LOCK); + rc = WAL_RETRY; + }else if( rc==SQLITE_BUSY ){ + rc = SQLITE_BUSY_RECOVERY; + } + } + if( rc!=SQLITE_OK ){ + return rc; + } + else if( pWal->bShmUnreliable ){ + return walBeginShmUnreliable(pWal, pChanged); + } + } + + assert( pWal->nWiData>0 ); + assert( pWal->apWiData[0]!=0 ); + pInfo = walCkptInfo(pWal); + SEH_INJECT_FAULT; + if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame +#ifdef SQLITE_ENABLE_SNAPSHOT + && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0) +#endif + ){ + /* The WAL has been completely backfilled (or it is empty). + ** and can be safely ignored. + */ + rc = walLockShared(pWal, WAL_READ_LOCK(0)); + walShmBarrier(pWal); + if( rc==SQLITE_OK ){ + if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){ + /* It is not safe to allow the reader to continue here if frames + ** may have been appended to the log before READ_LOCK(0) was obtained. + ** When holding READ_LOCK(0), the reader ignores the entire log file, + ** which implies that the database file contains a trustworthy + ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from + ** happening, this is usually correct. + ** + ** However, if frames have been appended to the log (or if the log + ** is wrapped and written for that matter) before the READ_LOCK(0) + ** is obtained, that is not necessarily true. A checkpointer may + ** have started to backfill the appended frames but crashed before + ** it finished. Leaving a corrupt image in the database file. + */ + walUnlockShared(pWal, WAL_READ_LOCK(0)); + return WAL_RETRY; + } + pWal->readLock = 0; + return SQLITE_OK; + }else if( rc!=SQLITE_BUSY ){ + return rc; + } + } + + /* If we get this far, it means that the reader will want to use + ** the WAL to get at content from recent commits. The job now is + ** to select one of the aReadMark[] entries that is closest to + ** but not exceeding pWal->hdr.mxFrame and lock that entry. + */ + mxReadMark = 0; + mxI = 0; + mxFrame = pWal->hdr.mxFrame; +#ifdef SQLITE_ENABLE_SNAPSHOT + if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){ + mxFrame = pWal->pSnapshot->mxFrame; + } +#endif + for(i=1; i<WAL_NREADER; i++){ + u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; + if( mxReadMark<=thisMark && thisMark<=mxFrame ){ + assert( thisMark!=READMARK_NOT_USED ); + mxReadMark = thisMark; + mxI = i; + } + } + if( (pWal->readOnly & WAL_SHM_RDONLY)==0 + && (mxReadMark<mxFrame || mxI==0) + ){ + for(i=1; i<WAL_NREADER; i++){ + rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1); + if( rc==SQLITE_OK ){ + AtomicStore(pInfo->aReadMark+i,mxFrame); + mxReadMark = mxFrame; + mxI = i; + walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); + break; + }else if( rc!=SQLITE_BUSY ){ + return rc; + } + } + } + if( mxI==0 ){ + assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); + return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT; + } + + rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); + if( rc ){ + return rc==SQLITE_BUSY ? WAL_RETRY : rc; + } + /* Now that the read-lock has been obtained, check that neither the + ** value in the aReadMark[] array or the contents of the wal-index + ** header have changed. + ** + ** It is necessary to check that the wal-index header did not change + ** between the time it was read and when the shared-lock was obtained + ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility + ** that the log file may have been wrapped by a writer, or that frames + ** that occur later in the log than pWal->hdr.mxFrame may have been + ** copied into the database by a checkpointer. If either of these things + ** happened, then reading the database with the current value of + ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry + ** instead. + ** + ** Before checking that the live wal-index header has not changed + ** since it was read, set Wal.minFrame to the first frame in the wal + ** file that has not yet been checkpointed. This client will not need + ** to read any frames earlier than minFrame from the wal file - they + ** can be safely read directly from the database file. + ** + ** Because a ShmBarrier() call is made between taking the copy of + ** nBackfill and checking that the wal-header in shared-memory still + ** matches the one cached in pWal->hdr, it is guaranteed that the + ** checkpointer that set nBackfill was not working with a wal-index + ** header newer than that cached in pWal->hdr. If it were, that could + ** cause a problem. The checkpointer could omit to checkpoint + ** a version of page X that lies before pWal->minFrame (call that version + ** A) on the basis that there is a newer version (version B) of the same + ** page later in the wal file. But if version B happens to like past + ** frame pWal->hdr.mxFrame - then the client would incorrectly assume + ** that it can read version A from the database file. However, since + ** we can guarantee that the checkpointer that set nBackfill could not + ** see any pages past pWal->hdr.mxFrame, this problem does not come up. + */ + pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT; + walShmBarrier(pWal); + if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark + || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) + ){ + walUnlockShared(pWal, WAL_READ_LOCK(mxI)); + return WAL_RETRY; + }else{ + assert( mxReadMark<=pWal->hdr.mxFrame ); + pWal->readLock = (i16)mxI; + } + return rc; +} + +#ifdef SQLITE_ENABLE_SNAPSHOT +/* +** This function does the work of sqlite3WalSnapshotRecover(). +*/ +static int walSnapshotRecover( + Wal *pWal, /* WAL handle */ + void *pBuf1, /* Temp buffer pWal->szPage bytes in size */ + void *pBuf2 /* Temp buffer pWal->szPage bytes in size */ +){ + int szPage = (int)pWal->szPage; + int rc; + i64 szDb; /* Size of db file in bytes */ + + rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); + if( rc==SQLITE_OK ){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + u32 i = pInfo->nBackfillAttempted; + for(i=pInfo->nBackfillAttempted; i>AtomicLoad(&pInfo->nBackfill); i--){ + WalHashLoc sLoc; /* Hash table location */ + u32 pgno; /* Page number in db file */ + i64 iDbOff; /* Offset of db file entry */ + i64 iWalOff; /* Offset of wal file entry */ + + rc = walHashGet(pWal, walFramePage(i), &sLoc); + if( rc!=SQLITE_OK ) break; + assert( i - sLoc.iZero - 1 >=0 ); + pgno = sLoc.aPgno[i-sLoc.iZero-1]; + iDbOff = (i64)(pgno-1) * szPage; + + if( iDbOff+szPage<=szDb ){ + iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; + rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); + + if( rc==SQLITE_OK ){ + rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); + } + + if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ + break; + } + } + + pInfo->nBackfillAttempted = i-1; + } + } + + return rc; +} + +/* +** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted +** variable so that older snapshots can be accessed. To do this, loop +** through all wal frames from nBackfillAttempted to (nBackfill+1), +** comparing their content to the corresponding page with the database +** file, if any. Set nBackfillAttempted to the frame number of the +** first frame for which the wal file content matches the db file. +** +** This is only really safe if the file-system is such that any page +** writes made by earlier checkpointers were atomic operations, which +** is not always true. It is also possible that nBackfillAttempted +** may be left set to a value larger than expected, if a wal frame +** contains content that duplicate of an earlier version of the same +** page. +** +** SQLITE_OK is returned if successful, or an SQLite error code if an +** error occurs. It is not an error if nBackfillAttempted cannot be +** decreased at all. +*/ +SQLITE_PRIVATE int sqlite3WalSnapshotRecover(Wal *pWal){ + int rc; + + assert( pWal->readLock>=0 ); + rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); + if( rc==SQLITE_OK ){ + void *pBuf1 = sqlite3_malloc(pWal->szPage); + void *pBuf2 = sqlite3_malloc(pWal->szPage); + if( pBuf1==0 || pBuf2==0 ){ + rc = SQLITE_NOMEM; + }else{ + pWal->ckptLock = 1; + SEH_TRY { + rc = walSnapshotRecover(pWal, pBuf1, pBuf2); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + pWal->ckptLock = 0; + } + + sqlite3_free(pBuf1); + sqlite3_free(pBuf2); + walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); + } + + return rc; +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ + +/* +** This function does the work of sqlite3WalBeginReadTransaction() (see +** below). That function simply calls this one inside an SEH_TRY{...} block. +*/ +static int walBeginReadTransaction(Wal *pWal, int *pChanged){ + int rc; /* Return code */ + int cnt = 0; /* Number of TryBeginRead attempts */ +#ifdef SQLITE_ENABLE_SNAPSHOT + int ckptLock = 0; + int bChanged = 0; + WalIndexHdr *pSnapshot = pWal->pSnapshot; +#endif + + assert( pWal->ckptLock==0 ); + assert( pWal->nSehTry>0 ); + +#ifdef SQLITE_ENABLE_SNAPSHOT + if( pSnapshot ){ + if( memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){ + bChanged = 1; + } + + /* It is possible that there is a checkpointer thread running + ** concurrent with this code. If this is the case, it may be that the + ** checkpointer has already determined that it will checkpoint + ** snapshot X, where X is later in the wal file than pSnapshot, but + ** has not yet set the pInfo->nBackfillAttempted variable to indicate + ** its intent. To avoid the race condition this leads to, ensure that + ** there is no checkpointer process by taking a shared CKPT lock + ** before checking pInfo->nBackfillAttempted. */ + (void)walEnableBlocking(pWal); + rc = walLockShared(pWal, WAL_CKPT_LOCK); + walDisableBlocking(pWal); + + if( rc!=SQLITE_OK ){ + return rc; + } + ckptLock = 1; + } +#endif + + do{ + rc = walTryBeginRead(pWal, pChanged, 0, ++cnt); + }while( rc==WAL_RETRY ); + testcase( (rc&0xff)==SQLITE_BUSY ); + testcase( (rc&0xff)==SQLITE_IOERR ); + testcase( rc==SQLITE_PROTOCOL ); + testcase( rc==SQLITE_OK ); + +#ifdef SQLITE_ENABLE_SNAPSHOT + if( rc==SQLITE_OK ){ + if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){ + /* At this point the client has a lock on an aReadMark[] slot holding + ** a value equal to or smaller than pSnapshot->mxFrame, but pWal->hdr + ** is populated with the wal-index header corresponding to the head + ** of the wal file. Verify that pSnapshot is still valid before + ** continuing. Reasons why pSnapshot might no longer be valid: + ** + ** (1) The WAL file has been reset since the snapshot was taken. + ** In this case, the salt will have changed. + ** + ** (2) A checkpoint as been attempted that wrote frames past + ** pSnapshot->mxFrame into the database file. Note that the + ** checkpoint need not have completed for this to cause problems. + */ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + + assert( pWal->readLock>0 || pWal->hdr.mxFrame==0 ); + assert( pInfo->aReadMark[pWal->readLock]<=pSnapshot->mxFrame ); + + /* Check that the wal file has not been wrapped. Assuming that it has + ** not, also check that no checkpointer has attempted to checkpoint any + ** frames beyond pSnapshot->mxFrame. If either of these conditions are + ** true, return SQLITE_ERROR_SNAPSHOT. Otherwise, overwrite pWal->hdr + ** with *pSnapshot and set *pChanged as appropriate for opening the + ** snapshot. */ + if( !memcmp(pSnapshot->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) + && pSnapshot->mxFrame>=pInfo->nBackfillAttempted + ){ + assert( pWal->readLock>0 ); + memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr)); + *pChanged = bChanged; + }else{ + rc = SQLITE_ERROR_SNAPSHOT; + } + + /* A client using a non-current snapshot may not ignore any frames + ** from the start of the wal file. This is because, for a system + ** where (minFrame < iSnapshot < maxFrame), a checkpointer may + ** have omitted to checkpoint a frame earlier than minFrame in + ** the file because there exists a frame after iSnapshot that + ** is the same database page. */ + pWal->minFrame = 1; + + if( rc!=SQLITE_OK ){ + sqlite3WalEndReadTransaction(pWal); + } + } + } + + /* Release the shared CKPT lock obtained above. */ + if( ckptLock ){ + assert( pSnapshot ); + walUnlockShared(pWal, WAL_CKPT_LOCK); + } +#endif + return rc; +} + +/* +** Begin a read transaction on the database. +** +** This routine used to be called sqlite3OpenSnapshot() and with good reason: +** it takes a snapshot of the state of the WAL and wal-index for the current +** instant in time. The current thread will continue to use this snapshot. +** Other threads might append new content to the WAL and wal-index but +** that extra content is ignored by the current thread. +** +** If the database contents have changes since the previous read +** transaction, then *pChanged is set to 1 before returning. The +** Pager layer will use this to know that its cache is stale and +** needs to be flushed. +*/ +SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ + int rc; + SEH_TRY { + rc = walBeginReadTransaction(pWal, pChanged); + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + +/* +** Finish with a read transaction. All this does is release the +** read-lock. +*/ +SQLITE_PRIVATE void sqlite3WalEndReadTransaction(Wal *pWal){ + sqlite3WalEndWriteTransaction(pWal); + if( pWal->readLock>=0 ){ + walUnlockShared(pWal, WAL_READ_LOCK(pWal->readLock)); + pWal->readLock = -1; + } +} + +/* +** Search the wal file for page pgno. If found, set *piRead to the frame that +** contains the page. Otherwise, if pgno is not in the wal file, set *piRead +** to zero. +** +** Return SQLITE_OK if successful, or an error code if an error occurs. If an +** error does occur, the final value of *piRead is undefined. +*/ +static int walFindFrame( + Wal *pWal, /* WAL handle */ + Pgno pgno, /* Database page number to read data for */ + u32 *piRead /* OUT: Frame number (or zero) */ +){ + u32 iRead = 0; /* If !=0, WAL frame to return data from */ + u32 iLast = pWal->hdr.mxFrame; /* Last page in WAL for this reader */ + int iHash; /* Used to loop through N hash tables */ + int iMinHash; + + /* This routine is only be called from within a read transaction. */ + assert( pWal->readLock>=0 || pWal->lockError ); + + /* If the "last page" field of the wal-index header snapshot is 0, then + ** no data will be read from the wal under any circumstances. Return early + ** in this case as an optimization. Likewise, if pWal->readLock==0, + ** then the WAL is ignored by the reader so return early, as if the + ** WAL were empty. + */ + if( iLast==0 || (pWal->readLock==0 && pWal->bShmUnreliable==0) ){ + *piRead = 0; + return SQLITE_OK; + } + + /* Search the hash table or tables for an entry matching page number + ** pgno. Each iteration of the following for() loop searches one + ** hash table (each hash table indexes up to HASHTABLE_NPAGE frames). + ** + ** This code might run concurrently to the code in walIndexAppend() + ** that adds entries to the wal-index (and possibly to this hash + ** table). This means the value just read from the hash + ** slot (aHash[iKey]) may have been added before or after the + ** current read transaction was opened. Values added after the + ** read transaction was opened may have been written incorrectly - + ** i.e. these slots may contain garbage data. However, we assume + ** that any slots written before the current read transaction was + ** opened remain unmodified. + ** + ** For the reasons above, the if(...) condition featured in the inner + ** loop of the following block is more stringent that would be required + ** if we had exclusive access to the hash-table: + ** + ** (aPgno[iFrame]==pgno): + ** This condition filters out normal hash-table collisions. + ** + ** (iFrame<=iLast): + ** This condition filters out entries that were added to the hash + ** table after the current read-transaction had started. + */ + iMinHash = walFramePage(pWal->minFrame); + for(iHash=walFramePage(iLast); iHash>=iMinHash; iHash--){ + WalHashLoc sLoc; /* Hash table location */ + int iKey; /* Hash slot index */ + int nCollide; /* Number of hash collisions remaining */ + int rc; /* Error code */ + u32 iH; + + rc = walHashGet(pWal, iHash, &sLoc); + if( rc!=SQLITE_OK ){ + return rc; + } + nCollide = HASHTABLE_NSLOT; + iKey = walHash(pgno); + SEH_INJECT_FAULT; + while( (iH = AtomicLoad(&sLoc.aHash[iKey]))!=0 ){ + u32 iFrame = iH + sLoc.iZero; + if( iFrame<=iLast && iFrame>=pWal->minFrame && sLoc.aPgno[iH-1]==pgno ){ + assert( iFrame>iRead || CORRUPT_DB ); + iRead = iFrame; + } + if( (nCollide--)==0 ){ + return SQLITE_CORRUPT_BKPT; + } + iKey = walNextHash(iKey); + } + if( iRead ) break; + } + +#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT + /* If expensive assert() statements are available, do a linear search + ** of the wal-index file content. Make sure the results agree with the + ** result obtained using the hash indexes above. */ + { + u32 iRead2 = 0; + u32 iTest; + assert( pWal->bShmUnreliable || pWal->minFrame>0 ); + for(iTest=iLast; iTest>=pWal->minFrame && iTest>0; iTest--){ + if( walFramePgno(pWal, iTest)==pgno ){ + iRead2 = iTest; + break; + } + } + assert( iRead==iRead2 ); + } +#endif + + *piRead = iRead; + return SQLITE_OK; +} + +/* +** Search the wal file for page pgno. If found, set *piRead to the frame that +** contains the page. Otherwise, if pgno is not in the wal file, set *piRead +** to zero. +** +** Return SQLITE_OK if successful, or an error code if an error occurs. If an +** error does occur, the final value of *piRead is undefined. +** +** The difference between this function and walFindFrame() is that this +** function wraps walFindFrame() in an SEH_TRY{...} block. +*/ +SQLITE_PRIVATE int sqlite3WalFindFrame( + Wal *pWal, /* WAL handle */ + Pgno pgno, /* Database page number to read data for */ + u32 *piRead /* OUT: Frame number (or zero) */ +){ + int rc; + SEH_TRY { + rc = walFindFrame(pWal, pgno, piRead); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + return rc; +} + +/* +** Read the contents of frame iRead from the wal file into buffer pOut +** (which is nOut bytes in size). Return SQLITE_OK if successful, or an +** error code otherwise. +*/ +SQLITE_PRIVATE int sqlite3WalReadFrame( + Wal *pWal, /* WAL handle */ + u32 iRead, /* Frame to read */ + int nOut, /* Size of buffer pOut in bytes */ + u8 *pOut /* Buffer to write page data to */ +){ + int sz; + i64 iOffset; + sz = pWal->hdr.szPage; + sz = (sz&0xfe00) + ((sz&0x0001)<<16); + testcase( sz<=32768 ); + testcase( sz>=65536 ); + iOffset = walFrameOffset(iRead, sz) + WAL_FRAME_HDRSIZE; + /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL */ + return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); +} + +/* +** Return the size of the database in pages (or zero, if unknown). +*/ +SQLITE_PRIVATE Pgno sqlite3WalDbsize(Wal *pWal){ + if( pWal && ALWAYS(pWal->readLock>=0) ){ + return pWal->hdr.nPage; + } + return 0; +} + + +/* +** This function starts a write transaction on the WAL. +** +** A read transaction must have already been started by a prior call +** to sqlite3WalBeginReadTransaction(). +** +** If another thread or process has written into the database since +** the read transaction was started, then it is not possible for this +** thread to write as doing so would cause a fork. So this routine +** returns SQLITE_BUSY in that case and no write transaction is started. +** +** There can only be a single writer active at a time. +*/ +SQLITE_PRIVATE int sqlite3WalBeginWriteTransaction(Wal *pWal){ + int rc; + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* If the write-lock is already held, then it was obtained before the + ** read-transaction was even opened, making this call a no-op. + ** Return early. */ + if( pWal->writeLock ){ + assert( !memcmp(&pWal->hdr,(void *)walIndexHdr(pWal),sizeof(WalIndexHdr)) ); + return SQLITE_OK; + } +#endif + + /* Cannot start a write transaction without first holding a read + ** transaction. */ + assert( pWal->readLock>=0 ); + assert( pWal->writeLock==0 && pWal->iReCksum==0 ); + + if( pWal->readOnly ){ + return SQLITE_READONLY; + } + + /* Only one writer allowed at a time. Get the write lock. Return + ** SQLITE_BUSY if unable. + */ + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); + if( rc ){ + return rc; + } + pWal->writeLock = 1; + + /* If another connection has written to the database file since the + ** time the read transaction on this connection was started, then + ** the write is disallowed. + */ + SEH_TRY { + if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){ + rc = SQLITE_BUSY_SNAPSHOT; + } + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + + if( rc!=SQLITE_OK ){ + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + pWal->writeLock = 0; + } + return rc; +} + +/* +** End a write transaction. The commit has already been done. This +** routine merely releases the lock. +*/ +SQLITE_PRIVATE int sqlite3WalEndWriteTransaction(Wal *pWal){ + if( pWal->writeLock ){ + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + pWal->writeLock = 0; + pWal->iReCksum = 0; + pWal->truncateOnCommit = 0; + } + return SQLITE_OK; +} + +/* +** If any data has been written (but not committed) to the log file, this +** function moves the write-pointer back to the start of the transaction. +** +** Additionally, the callback function is invoked for each frame written +** to the WAL since the start of the transaction. If the callback returns +** other than SQLITE_OK, it is not invoked again and the error code is +** returned to the caller. +** +** Otherwise, if the callback function does not return an error, this +** function returns SQLITE_OK. +*/ +SQLITE_PRIVATE int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){ + int rc = SQLITE_OK; + if( ALWAYS(pWal->writeLock) ){ + Pgno iMax = pWal->hdr.mxFrame; + Pgno iFrame; + + SEH_TRY { + /* Restore the clients cache of the wal-index header to the state it + ** was in before the client began writing to the database. + */ + memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr)); + + for(iFrame=pWal->hdr.mxFrame+1; + ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; + iFrame++ + ){ + /* This call cannot fail. Unless the page for which the page number + ** is passed as the second argument is (a) in the cache and + ** (b) has an outstanding reference, then xUndo is either a no-op + ** (if (a) is false) or simply expels the page from the cache (if (b) + ** is false). + ** + ** If the upper layer is doing a rollback, it is guaranteed that there + ** are no outstanding references to any page other than page 1. And + ** page 1 is never written to the log until the transaction is + ** committed. As a result, the call to xUndo may not fail. + */ + assert( walFramePgno(pWal, iFrame)!=1 ); + rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame)); + } + if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + } + return rc; +} + +/* +** Argument aWalData must point to an array of WAL_SAVEPOINT_NDATA u32 +** values. This function populates the array with values required to +** "rollback" the write position of the WAL handle back to the current +** point in the event of a savepoint rollback (via WalSavepointUndo()). +*/ +SQLITE_PRIVATE void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){ + assert( pWal->writeLock ); + aWalData[0] = pWal->hdr.mxFrame; + aWalData[1] = pWal->hdr.aFrameCksum[0]; + aWalData[2] = pWal->hdr.aFrameCksum[1]; + aWalData[3] = pWal->nCkpt; +} + +/* +** Move the write position of the WAL back to the point identified by +** the values in the aWalData[] array. aWalData must point to an array +** of WAL_SAVEPOINT_NDATA u32 values that has been previously populated +** by a call to WalSavepoint(). +*/ +SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ + int rc = SQLITE_OK; + + assert( pWal->writeLock ); + assert( aWalData[3]!=pWal->nCkpt || aWalData[0]<=pWal->hdr.mxFrame ); + + if( aWalData[3]!=pWal->nCkpt ){ + /* This savepoint was opened immediately after the write-transaction + ** was started. Right after that, the writer decided to wrap around + ** to the start of the log. Update the savepoint values to match. + */ + aWalData[0] = 0; + aWalData[3] = pWal->nCkpt; + } + + if( aWalData[0]<pWal->hdr.mxFrame ){ + pWal->hdr.mxFrame = aWalData[0]; + pWal->hdr.aFrameCksum[0] = aWalData[1]; + pWal->hdr.aFrameCksum[1] = aWalData[2]; + SEH_TRY { + walCleanupHash(pWal); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + } + + return rc; +} + +/* +** This function is called just before writing a set of frames to the log +** file (see sqlite3WalFrames()). It checks to see if, instead of appending +** to the current log file, it is possible to overwrite the start of the +** existing log file with the new frames (i.e. "reset" the log). If so, +** it sets pWal->hdr.mxFrame to 0. Otherwise, pWal->hdr.mxFrame is left +** unchanged. +** +** SQLITE_OK is returned if no error is encountered (regardless of whether +** or not pWal->hdr.mxFrame is modified). An SQLite error code is returned +** if an error occurs. +*/ +static int walRestartLog(Wal *pWal){ + int rc = SQLITE_OK; + int cnt; + + if( pWal->readLock==0 ){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + assert( pInfo->nBackfill==pWal->hdr.mxFrame ); + if( pInfo->nBackfill>0 ){ + u32 salt1; + sqlite3_randomness(4, &salt1); + rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); + if( rc==SQLITE_OK ){ + /* If all readers are using WAL_READ_LOCK(0) (in other words if no + ** readers are currently using the WAL), then the transactions + ** frames will overwrite the start of the existing log. Update the + ** wal-index header to reflect this. + ** + ** In theory it would be Ok to update the cache of the header only + ** at this point. But updating the actual wal-index header is also + ** safe and means there is no special case for sqlite3WalUndo() + ** to handle if this transaction is rolled back. */ + walRestartHdr(pWal, salt1); + walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); + }else if( rc!=SQLITE_BUSY ){ + return rc; + } + } + walUnlockShared(pWal, WAL_READ_LOCK(0)); + pWal->readLock = -1; + cnt = 0; + do{ + int notUsed; + rc = walTryBeginRead(pWal, &notUsed, 1, ++cnt); + }while( rc==WAL_RETRY ); + assert( (rc&0xff)!=SQLITE_BUSY ); /* BUSY not possible when useWal==1 */ + testcase( (rc&0xff)==SQLITE_IOERR ); + testcase( rc==SQLITE_PROTOCOL ); + testcase( rc==SQLITE_OK ); + } + return rc; +} + +/* +** Information about the current state of the WAL file and where +** the next fsync should occur - passed from sqlite3WalFrames() into +** walWriteToLog(). +*/ +typedef struct WalWriter { + Wal *pWal; /* The complete WAL information */ + sqlite3_file *pFd; /* The WAL file to which we write */ + sqlite3_int64 iSyncPoint; /* Fsync at this offset */ + int syncFlags; /* Flags for the fsync */ + int szPage; /* Size of one page */ +} WalWriter; + +/* +** Write iAmt bytes of content into the WAL file beginning at iOffset. +** Do a sync when crossing the p->iSyncPoint boundary. +** +** In other words, if iSyncPoint is in between iOffset and iOffset+iAmt, +** first write the part before iSyncPoint, then sync, then write the +** rest. +*/ +static int walWriteToLog( + WalWriter *p, /* WAL to write to */ + void *pContent, /* Content to be written */ + int iAmt, /* Number of bytes to write */ + sqlite3_int64 iOffset /* Start writing at this offset */ +){ + int rc; + if( iOffset<p->iSyncPoint && iOffset+iAmt>=p->iSyncPoint ){ + int iFirstAmt = (int)(p->iSyncPoint - iOffset); + rc = sqlite3OsWrite(p->pFd, pContent, iFirstAmt, iOffset); + if( rc ) return rc; + iOffset += iFirstAmt; + iAmt -= iFirstAmt; + pContent = (void*)(iFirstAmt + (char*)pContent); + assert( WAL_SYNC_FLAGS(p->syncFlags)!=0 ); + rc = sqlite3OsSync(p->pFd, WAL_SYNC_FLAGS(p->syncFlags)); + if( iAmt==0 || rc ) return rc; + } + rc = sqlite3OsWrite(p->pFd, pContent, iAmt, iOffset); + return rc; +} + +/* +** Write out a single frame of the WAL +*/ +static int walWriteOneFrame( + WalWriter *p, /* Where to write the frame */ + PgHdr *pPage, /* The page of the frame to be written */ + int nTruncate, /* The commit flag. Usually 0. >0 for commit */ + sqlite3_int64 iOffset /* Byte offset at which to write */ +){ + int rc; /* Result code from subfunctions */ + void *pData; /* Data actually written */ + u8 aFrame[WAL_FRAME_HDRSIZE]; /* Buffer to assemble frame-header in */ + pData = pPage->pData; + walEncodeFrame(p->pWal, pPage->pgno, nTruncate, pData, aFrame); + rc = walWriteToLog(p, aFrame, sizeof(aFrame), iOffset); + if( rc ) return rc; + /* Write the page data */ + rc = walWriteToLog(p, pData, p->szPage, iOffset+sizeof(aFrame)); + return rc; +} + +/* +** This function is called as part of committing a transaction within which +** one or more frames have been overwritten. It updates the checksums for +** all frames written to the wal file by the current transaction starting +** with the earliest to have been overwritten. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +static int walRewriteChecksums(Wal *pWal, u32 iLast){ + const int szPage = pWal->szPage;/* Database page size */ + int rc = SQLITE_OK; /* Return code */ + u8 *aBuf; /* Buffer to load data from wal file into */ + u8 aFrame[WAL_FRAME_HDRSIZE]; /* Buffer to assemble frame-headers in */ + u32 iRead; /* Next frame to read from wal file */ + i64 iCksumOff; + + aBuf = sqlite3_malloc(szPage + WAL_FRAME_HDRSIZE); + if( aBuf==0 ) return SQLITE_NOMEM_BKPT; + + /* Find the checksum values to use as input for the recalculating the + ** first checksum. If the first frame is frame 1 (implying that the current + ** transaction restarted the wal file), these values must be read from the + ** wal-file header. Otherwise, read them from the frame header of the + ** previous frame. */ + assert( pWal->iReCksum>0 ); + if( pWal->iReCksum==1 ){ + iCksumOff = 24; + }else{ + iCksumOff = walFrameOffset(pWal->iReCksum-1, szPage) + 16; + } + rc = sqlite3OsRead(pWal->pWalFd, aBuf, sizeof(u32)*2, iCksumOff); + pWal->hdr.aFrameCksum[0] = sqlite3Get4byte(aBuf); + pWal->hdr.aFrameCksum[1] = sqlite3Get4byte(&aBuf[sizeof(u32)]); + + iRead = pWal->iReCksum; + pWal->iReCksum = 0; + for(; rc==SQLITE_OK && iRead<=iLast; iRead++){ + i64 iOff = walFrameOffset(iRead, szPage); + rc = sqlite3OsRead(pWal->pWalFd, aBuf, szPage+WAL_FRAME_HDRSIZE, iOff); + if( rc==SQLITE_OK ){ + u32 iPgno, nDbSize; + iPgno = sqlite3Get4byte(aBuf); + nDbSize = sqlite3Get4byte(&aBuf[4]); + + walEncodeFrame(pWal, iPgno, nDbSize, &aBuf[WAL_FRAME_HDRSIZE], aFrame); + rc = sqlite3OsWrite(pWal->pWalFd, aFrame, sizeof(aFrame), iOff); + } + } + + sqlite3_free(aBuf); + return rc; +} + +/* +** Write a set of frames to the log. The caller must hold the write-lock +** on the log file (obtained using sqlite3WalBeginWriteTransaction()). +*/ +static int walFrames( + Wal *pWal, /* Wal handle to write to */ + int szPage, /* Database page-size in bytes */ + PgHdr *pList, /* List of dirty pages to write */ + Pgno nTruncate, /* Database size after this commit */ + int isCommit, /* True if this is a commit */ + int sync_flags /* Flags to pass to OsSync() (or 0) */ +){ + int rc; /* Used to catch return codes */ + u32 iFrame; /* Next frame address */ + PgHdr *p; /* Iterator to run through pList with. */ + PgHdr *pLast = 0; /* Last frame in list */ + int nExtra = 0; /* Number of extra copies of last page */ + int szFrame; /* The size of a single frame */ + i64 iOffset; /* Next byte to write in WAL file */ + WalWriter w; /* The writer */ + u32 iFirst = 0; /* First frame that may be overwritten */ + WalIndexHdr *pLive; /* Pointer to shared header */ + + assert( pList ); + assert( pWal->writeLock ); + + /* If this frame set completes a transaction, then nTruncate>0. If + ** nTruncate==0 then this frame set does not complete the transaction. */ + assert( (isCommit!=0)==(nTruncate!=0) ); + +#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) + { int cnt; for(cnt=0, p=pList; p; p=p->pDirty, cnt++){} + WALTRACE(("WAL%p: frame write begin. %d frames. mxFrame=%d. %s\n", + pWal, cnt, pWal->hdr.mxFrame, isCommit ? "Commit" : "Spill")); + } +#endif + + pLive = (WalIndexHdr*)walIndexHdr(pWal); + if( memcmp(&pWal->hdr, (void *)pLive, sizeof(WalIndexHdr))!=0 ){ + iFirst = pLive->mxFrame+1; + } + + /* See if it is possible to write these frames into the start of the + ** log file, instead of appending to it at pWal->hdr.mxFrame. + */ + if( SQLITE_OK!=(rc = walRestartLog(pWal)) ){ + return rc; + } + + /* If this is the first frame written into the log, write the WAL + ** header to the start of the WAL file. See comments at the top of + ** this source file for a description of the WAL header format. + */ + iFrame = pWal->hdr.mxFrame; + if( iFrame==0 ){ + u8 aWalHdr[WAL_HDRSIZE]; /* Buffer to assemble wal-header in */ + u32 aCksum[2]; /* Checksum for wal-header */ + + sqlite3Put4byte(&aWalHdr[0], (WAL_MAGIC | SQLITE_BIGENDIAN)); + sqlite3Put4byte(&aWalHdr[4], WAL_MAX_VERSION); + sqlite3Put4byte(&aWalHdr[8], szPage); + sqlite3Put4byte(&aWalHdr[12], pWal->nCkpt); + if( pWal->nCkpt==0 ) sqlite3_randomness(8, pWal->hdr.aSalt); + memcpy(&aWalHdr[16], pWal->hdr.aSalt, 8); + walChecksumBytes(1, aWalHdr, WAL_HDRSIZE-2*4, 0, aCksum); + sqlite3Put4byte(&aWalHdr[24], aCksum[0]); + sqlite3Put4byte(&aWalHdr[28], aCksum[1]); + + pWal->szPage = szPage; + pWal->hdr.bigEndCksum = SQLITE_BIGENDIAN; + pWal->hdr.aFrameCksum[0] = aCksum[0]; + pWal->hdr.aFrameCksum[1] = aCksum[1]; + pWal->truncateOnCommit = 1; + + rc = sqlite3OsWrite(pWal->pWalFd, aWalHdr, sizeof(aWalHdr), 0); + WALTRACE(("WAL%p: wal-header write %s\n", pWal, rc ? "failed" : "ok")); + if( rc!=SQLITE_OK ){ + return rc; + } + + /* Sync the header (unless SQLITE_IOCAP_SEQUENTIAL is true or unless + ** all syncing is turned off by PRAGMA synchronous=OFF). Otherwise + ** an out-of-order write following a WAL restart could result in + ** database corruption. See the ticket: + ** + ** https://sqlite.org/src/info/ff5be73dee + */ + if( pWal->syncHeader ){ + rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags)); + if( rc ) return rc; + } + } + if( (int)pWal->szPage!=szPage ){ + return SQLITE_CORRUPT_BKPT; /* TH3 test case: cov1/corrupt155.test */ + } + + /* Setup information needed to write frames into the WAL */ + w.pWal = pWal; + w.pFd = pWal->pWalFd; + w.iSyncPoint = 0; + w.syncFlags = sync_flags; + w.szPage = szPage; + iOffset = walFrameOffset(iFrame+1, szPage); + szFrame = szPage + WAL_FRAME_HDRSIZE; + + /* Write all frames into the log file exactly once */ + for(p=pList; p; p=p->pDirty){ + int nDbSize; /* 0 normally. Positive == commit flag */ + + /* Check if this page has already been written into the wal file by + ** the current transaction. If so, overwrite the existing frame and + ** set Wal.writeLock to WAL_WRITELOCK_RECKSUM - indicating that + ** checksums must be recomputed when the transaction is committed. */ + if( iFirst && (p->pDirty || isCommit==0) ){ + u32 iWrite = 0; + VVA_ONLY(rc =) walFindFrame(pWal, p->pgno, &iWrite); + assert( rc==SQLITE_OK || iWrite==0 ); + if( iWrite>=iFirst ){ + i64 iOff = walFrameOffset(iWrite, szPage) + WAL_FRAME_HDRSIZE; + void *pData; + if( pWal->iReCksum==0 || iWrite<pWal->iReCksum ){ + pWal->iReCksum = iWrite; + } + pData = p->pData; + rc = sqlite3OsWrite(pWal->pWalFd, pData, szPage, iOff); + if( rc ) return rc; + p->flags &= ~PGHDR_WAL_APPEND; + continue; + } + } + + iFrame++; + assert( iOffset==walFrameOffset(iFrame, szPage) ); + nDbSize = (isCommit && p->pDirty==0) ? nTruncate : 0; + rc = walWriteOneFrame(&w, p, nDbSize, iOffset); + if( rc ) return rc; + pLast = p; + iOffset += szFrame; + p->flags |= PGHDR_WAL_APPEND; + } + + /* Recalculate checksums within the wal file if required. */ + if( isCommit && pWal->iReCksum ){ + rc = walRewriteChecksums(pWal, iFrame); + if( rc ) return rc; + } + + /* If this is the end of a transaction, then we might need to pad + ** the transaction and/or sync the WAL file. + ** + ** Padding and syncing only occur if this set of frames complete a + ** transaction and if PRAGMA synchronous=FULL. If synchronous==NORMAL + ** or synchronous==OFF, then no padding or syncing are needed. + ** + ** If SQLITE_IOCAP_POWERSAFE_OVERWRITE is defined, then padding is not + ** needed and only the sync is done. If padding is needed, then the + ** final frame is repeated (with its commit mark) until the next sector + ** boundary is crossed. Only the part of the WAL prior to the last + ** sector boundary is synced; the part of the last frame that extends + ** past the sector boundary is written after the sync. + */ + if( isCommit && WAL_SYNC_FLAGS(sync_flags)!=0 ){ + int bSync = 1; + if( pWal->padToSectorBoundary ){ + int sectorSize = sqlite3SectorSize(pWal->pWalFd); + w.iSyncPoint = ((iOffset+sectorSize-1)/sectorSize)*sectorSize; + bSync = (w.iSyncPoint==iOffset); + testcase( bSync ); + while( iOffset<w.iSyncPoint ){ + rc = walWriteOneFrame(&w, pLast, nTruncate, iOffset); + if( rc ) return rc; + iOffset += szFrame; + nExtra++; + assert( pLast!=0 ); + } + } + if( bSync ){ + assert( rc==SQLITE_OK ); + rc = sqlite3OsSync(w.pFd, WAL_SYNC_FLAGS(sync_flags)); + } + } + + /* If this frame set completes the first transaction in the WAL and + ** if PRAGMA journal_size_limit is set, then truncate the WAL to the + ** journal size limit, if possible. + */ + if( isCommit && pWal->truncateOnCommit && pWal->mxWalSize>=0 ){ + i64 sz = pWal->mxWalSize; + if( walFrameOffset(iFrame+nExtra+1, szPage)>pWal->mxWalSize ){ + sz = walFrameOffset(iFrame+nExtra+1, szPage); + } + walLimitSize(pWal, sz); + pWal->truncateOnCommit = 0; + } + + /* Append data to the wal-index. It is not necessary to lock the + ** wal-index to do this as the SQLITE_SHM_WRITE lock held on the wal-index + ** guarantees that there are no other writers, and no data that may + ** be in use by existing readers is being overwritten. + */ + iFrame = pWal->hdr.mxFrame; + for(p=pList; p && rc==SQLITE_OK; p=p->pDirty){ + if( (p->flags & PGHDR_WAL_APPEND)==0 ) continue; + iFrame++; + rc = walIndexAppend(pWal, iFrame, p->pgno); + } + assert( pLast!=0 || nExtra==0 ); + while( rc==SQLITE_OK && nExtra>0 ){ + iFrame++; + nExtra--; + rc = walIndexAppend(pWal, iFrame, pLast->pgno); + } + + if( rc==SQLITE_OK ){ + /* Update the private copy of the header. */ + pWal->hdr.szPage = (u16)((szPage&0xff00) | (szPage>>16)); + testcase( szPage<=32768 ); + testcase( szPage>=65536 ); + pWal->hdr.mxFrame = iFrame; + if( isCommit ){ + pWal->hdr.iChange++; + pWal->hdr.nPage = nTruncate; + } + /* If this is a commit, update the wal-index header too. */ + if( isCommit ){ + walIndexWriteHdr(pWal); + pWal->iCallback = iFrame; + } + } + + WALTRACE(("WAL%p: frame write %s\n", pWal, rc ? "failed" : "ok")); + return rc; +} + +/* +** Write a set of frames to the log. The caller must hold the write-lock +** on the log file (obtained using sqlite3WalBeginWriteTransaction()). +** +** The difference between this function and walFrames() is that this +** function wraps walFrames() in an SEH_TRY{...} block. +*/ +SQLITE_PRIVATE int sqlite3WalFrames( + Wal *pWal, /* Wal handle to write to */ + int szPage, /* Database page-size in bytes */ + PgHdr *pList, /* List of dirty pages to write */ + Pgno nTruncate, /* Database size after this commit */ + int isCommit, /* True if this is a commit */ + int sync_flags /* Flags to pass to OsSync() (or 0) */ +){ + int rc; + SEH_TRY { + rc = walFrames(pWal, szPage, pList, nTruncate, isCommit, sync_flags); + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + +/* +** This routine is called to implement sqlite3_wal_checkpoint() and +** related interfaces. +** +** Obtain a CHECKPOINT lock and then backfill as much information as +** we can from WAL into the database. +** +** If parameter xBusy is not NULL, it is a pointer to a busy-handler +** callback. In this case this function runs a blocking checkpoint. +*/ +SQLITE_PRIVATE int sqlite3WalCheckpoint( + Wal *pWal, /* Wal connection */ + sqlite3 *db, /* Check this handle's interrupt flag */ + int eMode, /* PASSIVE, FULL, RESTART, or TRUNCATE */ + int (*xBusy)(void*), /* Function to call when busy */ + void *pBusyArg, /* Context argument for xBusyHandler */ + int sync_flags, /* Flags to sync db file with (or 0) */ + int nBuf, /* Size of temporary buffer */ + u8 *zBuf, /* Temporary buffer to use */ + int *pnLog, /* OUT: Number of frames in WAL */ + int *pnCkpt /* OUT: Number of backfilled frames in WAL */ +){ + int rc; /* Return code */ + int isChanged = 0; /* True if a new wal-index header is loaded */ + int eMode2 = eMode; /* Mode to pass to walCheckpoint() */ + int (*xBusy2)(void*) = xBusy; /* Busy handler for eMode2 */ + + assert( pWal->ckptLock==0 ); + assert( pWal->writeLock==0 ); + + /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked + ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ + assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); + + if( pWal->readOnly ) return SQLITE_READONLY; + WALTRACE(("WAL%p: checkpoint begins\n", pWal)); + + /* Enable blocking locks, if possible. If blocking locks are successfully + ** enabled, set xBusy2=0 so that the busy-handler is never invoked. */ + sqlite3WalDb(pWal, db); + (void)walEnableBlocking(pWal); + + /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive + ** "checkpoint" lock on the database file. + ** EVIDENCE-OF: R-10421-19736 If any other process is running a + ** checkpoint operation at the same time, the lock cannot be obtained and + ** SQLITE_BUSY is returned. + ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured, + ** it will not be invoked in this case. + */ + rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); + testcase( rc==SQLITE_BUSY ); + testcase( rc!=SQLITE_OK && xBusy2!=0 ); + if( rc==SQLITE_OK ){ + pWal->ckptLock = 1; + + /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and + ** TRUNCATE modes also obtain the exclusive "writer" lock on the database + ** file. + ** + ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained + ** immediately, and a busy-handler is configured, it is invoked and the + ** writer lock retried until either the busy-handler returns 0 or the + ** lock is successfully obtained. + */ + if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ + rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1); + if( rc==SQLITE_OK ){ + pWal->writeLock = 1; + }else if( rc==SQLITE_BUSY ){ + eMode2 = SQLITE_CHECKPOINT_PASSIVE; + xBusy2 = 0; + rc = SQLITE_OK; + } + } + } + + + /* Read the wal-index header. */ + SEH_TRY { + if( rc==SQLITE_OK ){ + walDisableBlocking(pWal); + rc = walIndexReadHdr(pWal, &isChanged); + (void)walEnableBlocking(pWal); + if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ + sqlite3OsUnfetch(pWal->pDbFd, 0, 0); + } + } + + /* Copy data from the log to the database file. */ + if( rc==SQLITE_OK ){ + if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags,zBuf); + } + + /* If no error occurred, set the output variables. */ + if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ + if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame; + SEH_INJECT_FAULT; + if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill); + } + } + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + + if( isChanged ){ + /* If a new wal-index header was loaded before the checkpoint was + ** performed, then the pager-cache associated with pWal is now + ** out of date. So zero the cached wal-index header to ensure that + ** next time the pager opens a snapshot on this database it knows that + ** the cache needs to be reset. + */ + memset(&pWal->hdr, 0, sizeof(WalIndexHdr)); + } + + walDisableBlocking(pWal); + sqlite3WalDb(pWal, 0); + + /* Release the locks. */ + sqlite3WalEndWriteTransaction(pWal); + if( pWal->ckptLock ){ + walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); + pWal->ckptLock = 0; + } + WALTRACE(("WAL%p: checkpoint %s\n", pWal, rc ? "failed" : "ok")); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_BUSY_TIMEOUT ) rc = SQLITE_BUSY; +#endif + return (rc==SQLITE_OK && eMode!=eMode2 ? SQLITE_BUSY : rc); +} + +/* Return the value to pass to a sqlite3_wal_hook callback, the +** number of frames in the WAL at the point of the last commit since +** sqlite3WalCallback() was called. If no commits have occurred since +** the last call, then return 0. +*/ +SQLITE_PRIVATE int sqlite3WalCallback(Wal *pWal){ + u32 ret = 0; + if( pWal ){ + ret = pWal->iCallback; + pWal->iCallback = 0; + } + return (int)ret; +} + +/* +** This function is called to change the WAL subsystem into or out +** of locking_mode=EXCLUSIVE. +** +** If op is zero, then attempt to change from locking_mode=EXCLUSIVE +** into locking_mode=NORMAL. This means that we must acquire a lock +** on the pWal->readLock byte. If the WAL is already in locking_mode=NORMAL +** or if the acquisition of the lock fails, then return 0. If the +** transition out of exclusive-mode is successful, return 1. This +** operation must occur while the pager is still holding the exclusive +** lock on the main database file. +** +** If op is one, then change from locking_mode=NORMAL into +** locking_mode=EXCLUSIVE. This means that the pWal->readLock must +** be released. Return 1 if the transition is made and 0 if the +** WAL is already in exclusive-locking mode - meaning that this +** routine is a no-op. The pager must already hold the exclusive lock +** on the main database file before invoking this operation. +** +** If op is negative, then do a dry-run of the op==1 case but do +** not actually change anything. The pager uses this to see if it +** should acquire the database exclusive lock prior to invoking +** the op==1 case. +*/ +SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op){ + int rc; + assert( pWal->writeLock==0 ); + assert( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE || op==-1 ); + + /* pWal->readLock is usually set, but might be -1 if there was a + ** prior error while attempting to acquire are read-lock. This cannot + ** happen if the connection is actually in exclusive mode (as no xShmLock + ** locks are taken in this case). Nor should the pager attempt to + ** upgrade to exclusive-mode following such an error. + */ +#ifndef SQLITE_USE_SEH + assert( pWal->readLock>=0 || pWal->lockError ); +#endif + assert( pWal->readLock>=0 || (op<=0 && pWal->exclusiveMode==0) ); + + if( op==0 ){ + if( pWal->exclusiveMode!=WAL_NORMAL_MODE ){ + pWal->exclusiveMode = WAL_NORMAL_MODE; + if( walLockShared(pWal, WAL_READ_LOCK(pWal->readLock))!=SQLITE_OK ){ + pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; + } + rc = pWal->exclusiveMode==WAL_NORMAL_MODE; + }else{ + /* Already in locking_mode=NORMAL */ + rc = 0; + } + }else if( op>0 ){ + assert( pWal->exclusiveMode==WAL_NORMAL_MODE ); + assert( pWal->readLock>=0 ); + walUnlockShared(pWal, WAL_READ_LOCK(pWal->readLock)); + pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; + rc = 1; + }else{ + rc = pWal->exclusiveMode==WAL_NORMAL_MODE; + } + return rc; +} + +/* +** Return true if the argument is non-NULL and the WAL module is using +** heap-memory for the wal-index. Otherwise, if the argument is NULL or the +** WAL module is using shared-memory, return false. +*/ +SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal){ + return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ); +} + +#ifdef SQLITE_ENABLE_SNAPSHOT +/* Create a snapshot object. The content of a snapshot is opaque to +** every other subsystem, so the WAL module can put whatever it needs +** in the object. +*/ +SQLITE_PRIVATE int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){ + int rc = SQLITE_OK; + WalIndexHdr *pRet; + static const u32 aZero[4] = { 0, 0, 0, 0 }; + + assert( pWal->readLock>=0 && pWal->writeLock==0 ); + + if( memcmp(&pWal->hdr.aFrameCksum[0],aZero,16)==0 ){ + *ppSnapshot = 0; + return SQLITE_ERROR; + } + pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr)); + if( pRet==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + memcpy(pRet, &pWal->hdr, sizeof(WalIndexHdr)); + *ppSnapshot = (sqlite3_snapshot*)pRet; + } + + return rc; +} + +/* Try to open on pSnapshot when the next read-transaction starts +*/ +SQLITE_PRIVATE void sqlite3WalSnapshotOpen( + Wal *pWal, + sqlite3_snapshot *pSnapshot +){ + pWal->pSnapshot = (WalIndexHdr*)pSnapshot; +} + +/* +** Return a +ve value if snapshot p1 is newer than p2. A -ve value if +** p1 is older than p2 and zero if p1 and p2 are the same snapshot. +*/ +SQLITE_API int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){ + WalIndexHdr *pHdr1 = (WalIndexHdr*)p1; + WalIndexHdr *pHdr2 = (WalIndexHdr*)p2; + + /* aSalt[0] is a copy of the value stored in the wal file header. It + ** is incremented each time the wal file is restarted. */ + if( pHdr1->aSalt[0]<pHdr2->aSalt[0] ) return -1; + if( pHdr1->aSalt[0]>pHdr2->aSalt[0] ) return +1; + if( pHdr1->mxFrame<pHdr2->mxFrame ) return -1; + if( pHdr1->mxFrame>pHdr2->mxFrame ) return +1; + return 0; +} + +/* +** The caller currently has a read transaction open on the database. +** This function takes a SHARED lock on the CHECKPOINTER slot and then +** checks if the snapshot passed as the second argument is still +** available. If so, SQLITE_OK is returned. +** +** If the snapshot is not available, SQLITE_ERROR is returned. Or, if +** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error +** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER +** lock is released before returning. +*/ +SQLITE_PRIVATE int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot){ + int rc; + SEH_TRY { + rc = walLockShared(pWal, WAL_CKPT_LOCK); + if( rc==SQLITE_OK ){ + WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot; + if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) + || pNew->mxFrame<walCkptInfo(pWal)->nBackfillAttempted + ){ + rc = SQLITE_ERROR_SNAPSHOT; + walUnlockShared(pWal, WAL_CKPT_LOCK); + } + } + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + +/* +** Release a lock obtained by an earlier successful call to +** sqlite3WalSnapshotCheck(). +*/ +SQLITE_PRIVATE void sqlite3WalSnapshotUnlock(Wal *pWal){ + assert( pWal ); + walUnlockShared(pWal, WAL_CKPT_LOCK); +} + + +#endif /* SQLITE_ENABLE_SNAPSHOT */ + +#ifdef SQLITE_ENABLE_ZIPVFS +/* +** If the argument is not NULL, it points to a Wal object that holds a +** read-lock. This function returns the database page-size if it is known, +** or zero if it is not (or if pWal is NULL). +*/ +SQLITE_PRIVATE int sqlite3WalFramesize(Wal *pWal){ + assert( pWal==0 || pWal->readLock>=0 ); + return (pWal ? pWal->szPage : 0); +} +#endif + +/* Return the sqlite3_file object for the WAL file +*/ +SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal){ + return pWal->pWalFd; +} + +#endif /* #ifndef SQLITE_OMIT_WAL */ + +/************** End of wal.c *************************************************/ +/************** Begin file btmutex.c *****************************************/ +/* +** 2007 August 27 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains code used to implement mutexes on Btree objects. +** This code really belongs in btree.c. But btree.c is getting too +** big and we want to break it down some. This packaged seemed like +** a good breakout. +*/ +/************** Include btreeInt.h in the middle of btmutex.c ****************/ +/************** Begin file btreeInt.h ****************************************/ +/* +** 2004 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file implements an external (disk-based) database using BTrees. +** For a detailed discussion of BTrees, refer to +** +** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3: +** "Sorting And Searching", pages 473-480. Addison-Wesley +** Publishing Company, Reading, Massachusetts. +** +** The basic idea is that each page of the file contains N database +** entries and N+1 pointers to subpages. +** +** ---------------------------------------------------------------- +** | Ptr(0) | Key(0) | Ptr(1) | Key(1) | ... | Key(N-1) | Ptr(N) | +** ---------------------------------------------------------------- +** +** All of the keys on the page that Ptr(0) points to have values less +** than Key(0). All of the keys on page Ptr(1) and its subpages have +** values greater than Key(0) and less than Key(1). All of the keys +** on Ptr(N) and its subpages have values greater than Key(N-1). And +** so forth. +** +** Finding a particular key requires reading O(log(M)) pages from the +** disk where M is the number of entries in the tree. +** +** In this implementation, a single file can hold one or more separate +** BTrees. Each BTree is identified by the index of its root page. The +** key and data for any entry are combined to form the "payload". A +** fixed amount of payload can be carried directly on the database +** page. If the payload is larger than the preset amount then surplus +** bytes are stored on overflow pages. The payload for an entry +** and the preceding pointer are combined to form a "Cell". Each +** page has a small header which contains the Ptr(N) pointer and other +** information such as the size of key and data. +** +** FORMAT DETAILS +** +** The file is divided into pages. The first page is called page 1, +** the second is page 2, and so forth. A page number of zero indicates +** "no such page". The page size can be any power of 2 between 512 and 65536. +** Each page can be either a btree page, a freelist page, an overflow +** page, or a pointer-map page. +** +** The first page is always a btree page. The first 100 bytes of the first +** page contain a special header (the "file header") that describes the file. +** The format of the file header is as follows: +** +** OFFSET SIZE DESCRIPTION +** 0 16 Header string: "SQLite format 3\000" +** 16 2 Page size in bytes. (1 means 65536) +** 18 1 File format write version +** 19 1 File format read version +** 20 1 Bytes of unused space at the end of each page +** 21 1 Max embedded payload fraction (must be 64) +** 22 1 Min embedded payload fraction (must be 32) +** 23 1 Min leaf payload fraction (must be 32) +** 24 4 File change counter +** 28 4 Reserved for future use +** 32 4 First freelist page +** 36 4 Number of freelist pages in the file +** 40 60 15 4-byte meta values passed to higher layers +** +** 40 4 Schema cookie +** 44 4 File format of schema layer +** 48 4 Size of page cache +** 52 4 Largest root-page (auto/incr_vacuum) +** 56 4 1=UTF-8 2=UTF16le 3=UTF16be +** 60 4 User version +** 64 4 Incremental vacuum mode +** 68 4 Application-ID +** 72 20 unused +** 92 4 The version-valid-for number +** 96 4 SQLITE_VERSION_NUMBER +** +** All of the integer values are big-endian (most significant byte first). +** +** The file change counter is incremented when the database is changed +** This counter allows other processes to know when the file has changed +** and thus when they need to flush their cache. +** +** The max embedded payload fraction is the amount of the total usable +** space in a page that can be consumed by a single cell for standard +** B-tree (non-LEAFDATA) tables. A value of 255 means 100%. The default +** is to limit the maximum cell size so that at least 4 cells will fit +** on one page. Thus the default max embedded payload fraction is 64. +** +** If the payload for a cell is larger than the max payload, then extra +** payload is spilled to overflow pages. Once an overflow page is allocated, +** as many bytes as possible are moved into the overflow pages without letting +** the cell size drop below the min embedded payload fraction. +** +** The min leaf payload fraction is like the min embedded payload fraction +** except that it applies to leaf nodes in a LEAFDATA tree. The maximum +** payload fraction for a LEAFDATA tree is always 100% (or 255) and it +** not specified in the header. +** +** Each btree pages is divided into three sections: The header, the +** cell pointer array, and the cell content area. Page 1 also has a 100-byte +** file header that occurs before the page header. +** +** |----------------| +** | file header | 100 bytes. Page 1 only. +** |----------------| +** | page header | 8 bytes for leaves. 12 bytes for interior nodes +** |----------------| +** | cell pointer | | 2 bytes per cell. Sorted order. +** | array | | Grows downward +** | | v +** |----------------| +** | unallocated | +** | space | +** |----------------| ^ Grows upwards +** | cell content | | Arbitrary order interspersed with freeblocks. +** | area | | and free space fragments. +** |----------------| +** +** The page headers looks like this: +** +** OFFSET SIZE DESCRIPTION +** 0 1 Flags. 1: intkey, 2: zerodata, 4: leafdata, 8: leaf +** 1 2 byte offset to the first freeblock +** 3 2 number of cells on this page +** 5 2 first byte of the cell content area +** 7 1 number of fragmented free bytes +** 8 4 Right child (the Ptr(N) value). Omitted on leaves. +** +** The flags define the format of this btree page. The leaf flag means that +** this page has no children. The zerodata flag means that this page carries +** only keys and no data. The intkey flag means that the key is an integer +** which is stored in the key size entry of the cell header rather than in +** the payload area. +** +** The cell pointer array begins on the first byte after the page header. +** The cell pointer array contains zero or more 2-byte numbers which are +** offsets from the beginning of the page to the cell content in the cell +** content area. The cell pointers occur in sorted order. The system strives +** to keep free space after the last cell pointer so that new cells can +** be easily added without having to defragment the page. +** +** Cell content is stored at the very end of the page and grows toward the +** beginning of the page. +** +** Unused space within the cell content area is collected into a linked list of +** freeblocks. Each freeblock is at least 4 bytes in size. The byte offset +** to the first freeblock is given in the header. Freeblocks occur in +** increasing order. Because a freeblock must be at least 4 bytes in size, +** any group of 3 or fewer unused bytes in the cell content area cannot +** exist on the freeblock chain. A group of 3 or fewer free bytes is called +** a fragment. The total number of bytes in all fragments is recorded. +** in the page header at offset 7. +** +** SIZE DESCRIPTION +** 2 Byte offset of the next freeblock +** 2 Bytes in this freeblock +** +** Cells are of variable length. Cells are stored in the cell content area at +** the end of the page. Pointers to the cells are in the cell pointer array +** that immediately follows the page header. Cells is not necessarily +** contiguous or in order, but cell pointers are contiguous and in order. +** +** Cell content makes use of variable length integers. A variable +** length integer is 1 to 9 bytes where the lower 7 bits of each +** byte are used. The integer consists of all bytes that have bit 8 set and +** the first byte with bit 8 clear. The most significant byte of the integer +** appears first. A variable-length integer may not be more than 9 bytes long. +** As a special case, all 8 bits of the 9th byte are used as data. This +** allows a 64-bit integer to be encoded in 9 bytes. +** +** 0x00 becomes 0x00000000 +** 0x7f becomes 0x0000007f +** 0x81 0x00 becomes 0x00000080 +** 0x82 0x00 becomes 0x00000100 +** 0x80 0x7f becomes 0x0000007f +** 0x81 0x91 0xd1 0xac 0x78 becomes 0x12345678 +** 0x81 0x81 0x81 0x81 0x01 becomes 0x10204081 +** +** Variable length integers are used for rowids and to hold the number of +** bytes of key and data in a btree cell. +** +** The content of a cell looks like this: +** +** SIZE DESCRIPTION +** 4 Page number of the left child. Omitted if leaf flag is set. +** var Number of bytes of data. Omitted if the zerodata flag is set. +** var Number of bytes of key. Or the key itself if intkey flag is set. +** * Payload +** 4 First page of the overflow chain. Omitted if no overflow +** +** Overflow pages form a linked list. Each page except the last is completely +** filled with data (pagesize - 4 bytes). The last page can have as little +** as 1 byte of data. +** +** SIZE DESCRIPTION +** 4 Page number of next overflow page +** * Data +** +** Freelist pages come in two subtypes: trunk pages and leaf pages. The +** file header points to the first in a linked list of trunk page. Each trunk +** page points to multiple leaf pages. The content of a leaf page is +** unspecified. A trunk page looks like this: +** +** SIZE DESCRIPTION +** 4 Page number of next trunk page +** 4 Number of leaf pointers on this page +** * zero or more pages numbers of leaves +*/ +/* #include "sqliteInt.h" */ + + +/* The following value is the maximum cell size assuming a maximum page +** size give above. +*/ +#define MX_CELL_SIZE(pBt) ((int)(pBt->pageSize-8)) + +/* The maximum number of cells on a single page of the database. This +** assumes a minimum cell size of 6 bytes (4 bytes for the cell itself +** plus 2 bytes for the index to the cell in the page header). Such +** small cells will be rare, but they are possible. +*/ +#define MX_CELL(pBt) ((pBt->pageSize-8)/6) + +/* Forward declarations */ +typedef struct MemPage MemPage; +typedef struct BtLock BtLock; +typedef struct CellInfo CellInfo; + +/* +** This is a magic string that appears at the beginning of every +** SQLite database in order to identify the file as a real database. +** +** You can change this value at compile-time by specifying a +** -DSQLITE_FILE_HEADER="..." on the compiler command-line. The +** header must be exactly 16 bytes including the zero-terminator so +** the string itself should be 15 characters long. If you change +** the header, then your custom library will not be able to read +** databases generated by the standard tools and the standard tools +** will not be able to read databases created by your custom library. +*/ +#ifndef SQLITE_FILE_HEADER /* 123456789 123456 */ +# define SQLITE_FILE_HEADER "SQLite format 3" +#endif + +/* +** Page type flags. An ORed combination of these flags appear as the +** first byte of on-disk image of every BTree page. +*/ +#define PTF_INTKEY 0x01 +#define PTF_ZERODATA 0x02 +#define PTF_LEAFDATA 0x04 +#define PTF_LEAF 0x08 + +/* +** An instance of this object stores information about each a single database +** page that has been loaded into memory. The information in this object +** is derived from the raw on-disk page content. +** +** As each database page is loaded into memory, the pager allocates an +** instance of this object and zeros the first 8 bytes. (This is the +** "extra" information associated with each page of the pager.) +** +** Access to all fields of this structure is controlled by the mutex +** stored in MemPage.pBt->mutex. +*/ +struct MemPage { + u8 isInit; /* True if previously initialized. MUST BE FIRST! */ + u8 intKey; /* True if table b-trees. False for index b-trees */ + u8 intKeyLeaf; /* True if the leaf of an intKey table */ + Pgno pgno; /* Page number for this page */ + /* Only the first 8 bytes (above) are zeroed by pager.c when a new page + ** is allocated. All fields that follow must be initialized before use */ + u8 leaf; /* True if a leaf page */ + u8 hdrOffset; /* 100 for page 1. 0 otherwise */ + u8 childPtrSize; /* 0 if leaf==1. 4 if leaf==0 */ + u8 max1bytePayload; /* min(maxLocal,127) */ + u8 nOverflow; /* Number of overflow cell bodies in aCell[] */ + u16 maxLocal; /* Copy of BtShared.maxLocal or BtShared.maxLeaf */ + u16 minLocal; /* Copy of BtShared.minLocal or BtShared.minLeaf */ + u16 cellOffset; /* Index in aData of first cell pointer */ + int nFree; /* Number of free bytes on the page. -1 for unknown */ + u16 nCell; /* Number of cells on this page, local and ovfl */ + u16 maskPage; /* Mask for page offset */ + u16 aiOvfl[4]; /* Insert the i-th overflow cell before the aiOvfl-th + ** non-overflow cell */ + u8 *apOvfl[4]; /* Pointers to the body of overflow cells */ + BtShared *pBt; /* Pointer to BtShared that this page is part of */ + u8 *aData; /* Pointer to disk image of the page data */ + u8 *aDataEnd; /* One byte past the end of the entire page - not just + ** the usable space, the entire page. Used to prevent + ** corruption-induced buffer overflow. */ + u8 *aCellIdx; /* The cell index area */ + u8 *aDataOfst; /* Same as aData for leaves. aData+4 for interior */ + DbPage *pDbPage; /* Pager page handle */ + u16 (*xCellSize)(MemPage*,u8*); /* cellSizePtr method */ + void (*xParseCell)(MemPage*,u8*,CellInfo*); /* btreeParseCell method */ +}; + +/* +** A linked list of the following structures is stored at BtShared.pLock. +** Locks are added (or upgraded from READ_LOCK to WRITE_LOCK) when a cursor +** is opened on the table with root page BtShared.iTable. Locks are removed +** from this list when a transaction is committed or rolled back, or when +** a btree handle is closed. +*/ +struct BtLock { + Btree *pBtree; /* Btree handle holding this lock */ + Pgno iTable; /* Root page of table */ + u8 eLock; /* READ_LOCK or WRITE_LOCK */ + BtLock *pNext; /* Next in BtShared.pLock list */ +}; + +/* Candidate values for BtLock.eLock */ +#define READ_LOCK 1 +#define WRITE_LOCK 2 + +/* A Btree handle +** +** A database connection contains a pointer to an instance of +** this object for every database file that it has open. This structure +** is opaque to the database connection. The database connection cannot +** see the internals of this structure and only deals with pointers to +** this structure. +** +** For some database files, the same underlying database cache might be +** shared between multiple connections. In that case, each connection +** has it own instance of this object. But each instance of this object +** points to the same BtShared object. The database cache and the +** schema associated with the database file are all contained within +** the BtShared object. +** +** All fields in this structure are accessed under sqlite3.mutex. +** The pBt pointer itself may not be changed while there exists cursors +** in the referenced BtShared that point back to this Btree since those +** cursors have to go through this Btree to find their BtShared and +** they often do so without holding sqlite3.mutex. +*/ +struct Btree { + sqlite3 *db; /* The database connection holding this btree */ + BtShared *pBt; /* Sharable content of this btree */ + u8 inTrans; /* TRANS_NONE, TRANS_READ or TRANS_WRITE */ + u8 sharable; /* True if we can share pBt with another db */ + u8 locked; /* True if db currently has pBt locked */ + u8 hasIncrblobCur; /* True if there are one or more Incrblob cursors */ + int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */ + int nBackup; /* Number of backup operations reading this btree */ + u32 iBDataVersion; /* Combines with pBt->pPager->iDataVersion */ + Btree *pNext; /* List of other sharable Btrees from the same db */ + Btree *pPrev; /* Back pointer of the same list */ +#ifdef SQLITE_DEBUG + u64 nSeek; /* Calls to sqlite3BtreeMovetoUnpacked() */ +#endif +#ifndef SQLITE_OMIT_SHARED_CACHE + BtLock lock; /* Object used to lock page 1 */ +#endif +}; + +/* +** Btree.inTrans may take one of the following values. +** +** If the shared-data extension is enabled, there may be multiple users +** of the Btree structure. At most one of these may open a write transaction, +** but any number may have active read transactions. +** +** These values must match SQLITE_TXN_NONE, SQLITE_TXN_READ, and +** SQLITE_TXN_WRITE +*/ +#define TRANS_NONE 0 +#define TRANS_READ 1 +#define TRANS_WRITE 2 + +#if TRANS_NONE!=SQLITE_TXN_NONE +# error wrong numeric code for no-transaction +#endif +#if TRANS_READ!=SQLITE_TXN_READ +# error wrong numeric code for read-transaction +#endif +#if TRANS_WRITE!=SQLITE_TXN_WRITE +# error wrong numeric code for write-transaction +#endif + + +/* +** An instance of this object represents a single database file. +** +** A single database file can be in use at the same time by two +** or more database connections. When two or more connections are +** sharing the same database file, each connection has it own +** private Btree object for the file and each of those Btrees points +** to this one BtShared object. BtShared.nRef is the number of +** connections currently sharing this database file. +** +** Fields in this structure are accessed under the BtShared.mutex +** mutex, except for nRef and pNext which are accessed under the +** global SQLITE_MUTEX_STATIC_MAIN mutex. The pPager field +** may not be modified once it is initially set as long as nRef>0. +** The pSchema field may be set once under BtShared.mutex and +** thereafter is unchanged as long as nRef>0. +** +** isPending: +** +** If a BtShared client fails to obtain a write-lock on a database +** table (because there exists one or more read-locks on the table), +** the shared-cache enters 'pending-lock' state and isPending is +** set to true. +** +** The shared-cache leaves the 'pending lock' state when either of +** the following occur: +** +** 1) The current writer (BtShared.pWriter) concludes its transaction, OR +** 2) The number of locks held by other connections drops to zero. +** +** while in the 'pending-lock' state, no connection may start a new +** transaction. +** +** This feature is included to help prevent writer-starvation. +*/ +struct BtShared { + Pager *pPager; /* The page cache */ + sqlite3 *db; /* Database connection currently using this Btree */ + BtCursor *pCursor; /* A list of all open cursors */ + MemPage *pPage1; /* First page of the database */ + u8 openFlags; /* Flags to sqlite3BtreeOpen() */ +#ifndef SQLITE_OMIT_AUTOVACUUM + u8 autoVacuum; /* True if auto-vacuum is enabled */ + u8 incrVacuum; /* True if incr-vacuum is enabled */ + u8 bDoTruncate; /* True to truncate db on commit */ +#endif + u8 inTransaction; /* Transaction state */ + u8 max1bytePayload; /* Maximum first byte of cell for a 1-byte payload */ + u8 nReserveWanted; /* Desired number of extra bytes per page */ + u16 btsFlags; /* Boolean parameters. See BTS_* macros below */ + u16 maxLocal; /* Maximum local payload in non-LEAFDATA tables */ + u16 minLocal; /* Minimum local payload in non-LEAFDATA tables */ + u16 maxLeaf; /* Maximum local payload in a LEAFDATA table */ + u16 minLeaf; /* Minimum local payload in a LEAFDATA table */ + u32 pageSize; /* Total number of bytes on a page */ + u32 usableSize; /* Number of usable bytes on each page */ + int nTransaction; /* Number of open transactions (read + write) */ + u32 nPage; /* Number of pages in the database */ + void *pSchema; /* Pointer to space allocated by sqlite3BtreeSchema() */ + void (*xFreeSchema)(void*); /* Destructor for BtShared.pSchema */ + sqlite3_mutex *mutex; /* Non-recursive mutex required to access this object */ + Bitvec *pHasContent; /* Set of pages moved to free-list this transaction */ +#ifndef SQLITE_OMIT_SHARED_CACHE + int nRef; /* Number of references to this structure */ + BtShared *pNext; /* Next on a list of sharable BtShared structs */ + BtLock *pLock; /* List of locks held on this shared-btree struct */ + Btree *pWriter; /* Btree with currently open write transaction */ +#endif + u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */ + int nPreformatSize; /* Size of last cell written by TransferRow() */ +}; + +/* +** Allowed values for BtShared.btsFlags +*/ +#define BTS_READ_ONLY 0x0001 /* Underlying file is readonly */ +#define BTS_PAGESIZE_FIXED 0x0002 /* Page size can no longer be changed */ +#define BTS_SECURE_DELETE 0x0004 /* PRAGMA secure_delete is enabled */ +#define BTS_OVERWRITE 0x0008 /* Overwrite deleted content with zeros */ +#define BTS_FAST_SECURE 0x000c /* Combination of the previous two */ +#define BTS_INITIALLY_EMPTY 0x0010 /* Database was empty at trans start */ +#define BTS_NO_WAL 0x0020 /* Do not open write-ahead-log files */ +#define BTS_EXCLUSIVE 0x0040 /* pWriter has an exclusive lock */ +#define BTS_PENDING 0x0080 /* Waiting for read-locks to clear */ + +/* +** An instance of the following structure is used to hold information +** about a cell. The parseCellPtr() function fills in this structure +** based on information extract from the raw disk page. +*/ +struct CellInfo { + i64 nKey; /* The key for INTKEY tables, or nPayload otherwise */ + u8 *pPayload; /* Pointer to the start of payload */ + u32 nPayload; /* Bytes of payload */ + u16 nLocal; /* Amount of payload held locally, not on overflow */ + u16 nSize; /* Size of the cell content on the main b-tree page */ +}; + +/* +** Maximum depth of an SQLite B-Tree structure. Any B-Tree deeper than +** this will be declared corrupt. This value is calculated based on a +** maximum database size of 2^31 pages a minimum fanout of 2 for a +** root-node and 3 for all other internal nodes. +** +** If a tree that appears to be taller than this is encountered, it is +** assumed that the database is corrupt. +*/ +#define BTCURSOR_MAX_DEPTH 20 + +/* +** A cursor is a pointer to a particular entry within a particular +** b-tree within a database file. +** +** The entry is identified by its MemPage and the index in +** MemPage.aCell[] of the entry. +** +** A single database file can be shared by two more database connections, +** but cursors cannot be shared. Each cursor is associated with a +** particular database connection identified BtCursor.pBtree.db. +** +** Fields in this structure are accessed under the BtShared.mutex +** found at self->pBt->mutex. +** +** skipNext meaning: +** The meaning of skipNext depends on the value of eState: +** +** eState Meaning of skipNext +** VALID skipNext is meaningless and is ignored +** INVALID skipNext is meaningless and is ignored +** SKIPNEXT sqlite3BtreeNext() is a no-op if skipNext>0 and +** sqlite3BtreePrevious() is no-op if skipNext<0. +** REQUIRESEEK restoreCursorPosition() restores the cursor to +** eState=SKIPNEXT if skipNext!=0 +** FAULT skipNext holds the cursor fault error code. +*/ +struct BtCursor { + u8 eState; /* One of the CURSOR_XXX constants (see below) */ + u8 curFlags; /* zero or more BTCF_* flags defined below */ + u8 curPagerFlags; /* Flags to send to sqlite3PagerGet() */ + u8 hints; /* As configured by CursorSetHints() */ + int skipNext; /* Prev() is noop if negative. Next() is noop if positive. + ** Error code if eState==CURSOR_FAULT */ + Btree *pBtree; /* The Btree to which this cursor belongs */ + Pgno *aOverflow; /* Cache of overflow page locations */ + void *pKey; /* Saved key that was cursor last known position */ + /* All fields above are zeroed when the cursor is allocated. See + ** sqlite3BtreeCursorZero(). Fields that follow must be manually + ** initialized. */ +#define BTCURSOR_FIRST_UNINIT pBt /* Name of first uninitialized field */ + BtShared *pBt; /* The BtShared this cursor points to */ + BtCursor *pNext; /* Forms a linked list of all cursors */ + CellInfo info; /* A parse of the cell we are pointing at */ + i64 nKey; /* Size of pKey, or last integer key */ + Pgno pgnoRoot; /* The root page of this tree */ + i8 iPage; /* Index of current page in apPage */ + u8 curIntKey; /* Value of apPage[0]->intKey */ + u16 ix; /* Current index for apPage[iPage] */ + u16 aiIdx[BTCURSOR_MAX_DEPTH-1]; /* Current index in apPage[i] */ + struct KeyInfo *pKeyInfo; /* Arg passed to comparison function */ + MemPage *pPage; /* Current page */ + MemPage *apPage[BTCURSOR_MAX_DEPTH-1]; /* Stack of parents of current page */ +}; + +/* +** Legal values for BtCursor.curFlags +*/ +#define BTCF_WriteFlag 0x01 /* True if a write cursor */ +#define BTCF_ValidNKey 0x02 /* True if info.nKey is valid */ +#define BTCF_ValidOvfl 0x04 /* True if aOverflow is valid */ +#define BTCF_AtLast 0x08 /* Cursor is pointing to the last entry */ +#define BTCF_Incrblob 0x10 /* True if an incremental I/O handle */ +#define BTCF_Multiple 0x20 /* Maybe another cursor on the same btree */ +#define BTCF_Pinned 0x40 /* Cursor is busy and cannot be moved */ + +/* +** Potential values for BtCursor.eState. +** +** CURSOR_INVALID: +** Cursor does not point to a valid entry. This can happen (for example) +** because the table is empty or because BtreeCursorFirst() has not been +** called. +** +** CURSOR_VALID: +** Cursor points to a valid entry. getPayload() etc. may be called. +** +** CURSOR_SKIPNEXT: +** Cursor is valid except that the Cursor.skipNext field is non-zero +** indicating that the next sqlite3BtreeNext() or sqlite3BtreePrevious() +** operation should be a no-op. +** +** CURSOR_REQUIRESEEK: +** The table that this cursor was opened on still exists, but has been +** modified since the cursor was last used. The cursor position is saved +** in variables BtCursor.pKey and BtCursor.nKey. When a cursor is in +** this state, restoreCursorPosition() can be called to attempt to +** seek the cursor to the saved position. +** +** CURSOR_FAULT: +** An unrecoverable error (an I/O error or a malloc failure) has occurred +** on a different connection that shares the BtShared cache with this +** cursor. The error has left the cache in an inconsistent state. +** Do nothing else with this cursor. Any attempt to use the cursor +** should return the error code stored in BtCursor.skipNext +*/ +#define CURSOR_VALID 0 +#define CURSOR_INVALID 1 +#define CURSOR_SKIPNEXT 2 +#define CURSOR_REQUIRESEEK 3 +#define CURSOR_FAULT 4 + +/* +** The database page the PENDING_BYTE occupies. This page is never used. +*/ +#define PENDING_BYTE_PAGE(pBt) ((Pgno)((PENDING_BYTE/((pBt)->pageSize))+1)) + +/* +** These macros define the location of the pointer-map entry for a +** database page. The first argument to each is the number of usable +** bytes on each page of the database (often 1024). The second is the +** page number to look up in the pointer map. +** +** PTRMAP_PAGENO returns the database page number of the pointer-map +** page that stores the required pointer. PTRMAP_PTROFFSET returns +** the offset of the requested map entry. +** +** If the pgno argument passed to PTRMAP_PAGENO is a pointer-map page, +** then pgno is returned. So (pgno==PTRMAP_PAGENO(pgsz, pgno)) can be +** used to test if pgno is a pointer-map page. PTRMAP_ISPAGE implements +** this test. +*/ +#define PTRMAP_PAGENO(pBt, pgno) ptrmapPageno(pBt, pgno) +#define PTRMAP_PTROFFSET(pgptrmap, pgno) (5*(pgno-pgptrmap-1)) +#define PTRMAP_ISPAGE(pBt, pgno) (PTRMAP_PAGENO((pBt),(pgno))==(pgno)) + +/* +** The pointer map is a lookup table that identifies the parent page for +** each child page in the database file. The parent page is the page that +** contains a pointer to the child. Every page in the database contains +** 0 or 1 parent pages. (In this context 'database page' refers +** to any page that is not part of the pointer map itself.) Each pointer map +** entry consists of a single byte 'type' and a 4 byte parent page number. +** The PTRMAP_XXX identifiers below are the valid types. +** +** The purpose of the pointer map is to facility moving pages from one +** position in the file to another as part of autovacuum. When a page +** is moved, the pointer in its parent must be updated to point to the +** new location. The pointer map is used to locate the parent page quickly. +** +** PTRMAP_ROOTPAGE: The database page is a root-page. The page-number is not +** used in this case. +** +** PTRMAP_FREEPAGE: The database page is an unused (free) page. The page-number +** is not used in this case. +** +** PTRMAP_OVERFLOW1: The database page is the first page in a list of +** overflow pages. The page number identifies the page that +** contains the cell with a pointer to this overflow page. +** +** PTRMAP_OVERFLOW2: The database page is the second or later page in a list of +** overflow pages. The page-number identifies the previous +** page in the overflow page list. +** +** PTRMAP_BTREE: The database page is a non-root btree page. The page number +** identifies the parent page in the btree. +*/ +#define PTRMAP_ROOTPAGE 1 +#define PTRMAP_FREEPAGE 2 +#define PTRMAP_OVERFLOW1 3 +#define PTRMAP_OVERFLOW2 4 +#define PTRMAP_BTREE 5 + +/* A bunch of assert() statements to check the transaction state variables +** of handle p (type Btree*) are internally consistent. +*/ +#define btreeIntegrity(p) \ + assert( p->pBt->inTransaction!=TRANS_NONE || p->pBt->nTransaction==0 ); \ + assert( p->pBt->inTransaction>=p->inTrans ); + + +/* +** The ISAUTOVACUUM macro is used within balance_nonroot() to determine +** if the database supports auto-vacuum or not. Because it is used +** within an expression that is an argument to another macro +** (sqliteMallocRaw), it is not possible to use conditional compilation. +** So, this macro is defined instead. +*/ +#ifndef SQLITE_OMIT_AUTOVACUUM +#define ISAUTOVACUUM(pBt) (pBt->autoVacuum) +#else +#define ISAUTOVACUUM(pBt) 0 +#endif + + +/* +** This structure is passed around through all the PRAGMA integrity_check +** checking routines in order to keep track of some global state information. +** +** The aRef[] array is allocated so that there is 1 bit for each page in +** the database. As the integrity-check proceeds, for each page used in +** the database the corresponding bit is set. This allows integrity-check to +** detect pages that are used twice and orphaned pages (both of which +** indicate corruption). +*/ +typedef struct IntegrityCk IntegrityCk; +struct IntegrityCk { + BtShared *pBt; /* The tree being checked out */ + Pager *pPager; /* The associated pager. Also accessible by pBt->pPager */ + u8 *aPgRef; /* 1 bit per page in the db (see above) */ + Pgno nPage; /* Number of pages in the database */ + int mxErr; /* Stop accumulating errors when this reaches zero */ + int nErr; /* Number of messages written to zErrMsg so far */ + int rc; /* SQLITE_OK, SQLITE_NOMEM, or SQLITE_INTERRUPT */ + u32 nStep; /* Number of steps into the integrity_check process */ + const char *zPfx; /* Error message prefix */ + Pgno v0; /* Value for first %u substitution in zPfx (root page) */ + Pgno v1; /* Value for second %u substitution in zPfx (current pg) */ + int v2; /* Value for third %d substitution in zPfx */ + StrAccum errMsg; /* Accumulate the error message text here */ + u32 *heap; /* Min-heap used for analyzing cell coverage */ + sqlite3 *db; /* Database connection running the check */ +}; + +/* +** Routines to read or write a two- and four-byte big-endian integer values. +*/ +#define get2byte(x) ((x)[0]<<8 | (x)[1]) +#define put2byte(p,v) ((p)[0] = (u8)((v)>>8), (p)[1] = (u8)(v)) +#define get4byte sqlite3Get4byte +#define put4byte sqlite3Put4byte + +/* +** get2byteAligned(), unlike get2byte(), requires that its argument point to a +** two-byte aligned address. get2byteAligned() is only used for accessing the +** cell addresses in a btree header. +*/ +#if SQLITE_BYTEORDER==4321 +# define get2byteAligned(x) (*(u16*)(x)) +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4008000 +# define get2byteAligned(x) __builtin_bswap16(*(u16*)(x)) +#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 +# define get2byteAligned(x) _byteswap_ushort(*(u16*)(x)) +#else +# define get2byteAligned(x) ((x)[0]<<8 | (x)[1]) +#endif + +/************** End of btreeInt.h ********************************************/ +/************** Continuing where we left off in btmutex.c ********************/ +#ifndef SQLITE_OMIT_SHARED_CACHE +#if SQLITE_THREADSAFE + +/* +** Obtain the BtShared mutex associated with B-Tree handle p. Also, +** set BtShared.db to the database handle associated with p and the +** p->locked boolean to true. +*/ +static void lockBtreeMutex(Btree *p){ + assert( p->locked==0 ); + assert( sqlite3_mutex_notheld(p->pBt->mutex) ); + assert( sqlite3_mutex_held(p->db->mutex) ); + + sqlite3_mutex_enter(p->pBt->mutex); + p->pBt->db = p->db; + p->locked = 1; +} + +/* +** Release the BtShared mutex associated with B-Tree handle p and +** clear the p->locked boolean. +*/ +static void SQLITE_NOINLINE unlockBtreeMutex(Btree *p){ + BtShared *pBt = p->pBt; + assert( p->locked==1 ); + assert( sqlite3_mutex_held(pBt->mutex) ); + assert( sqlite3_mutex_held(p->db->mutex) ); + assert( p->db==pBt->db ); + + sqlite3_mutex_leave(pBt->mutex); + p->locked = 0; +} + +/* Forward reference */ +static void SQLITE_NOINLINE btreeLockCarefully(Btree *p); + +/* +** Enter a mutex on the given BTree object. +** +** If the object is not sharable, then no mutex is ever required +** and this routine is a no-op. The underlying mutex is non-recursive. +** But we keep a reference count in Btree.wantToLock so the behavior +** of this interface is recursive. +** +** To avoid deadlocks, multiple Btrees are locked in the same order +** by all database connections. The p->pNext is a list of other +** Btrees belonging to the same database connection as the p Btree +** which need to be locked after p. If we cannot get a lock on +** p, then first unlock all of the others on p->pNext, then wait +** for the lock to become available on p, then relock all of the +** subsequent Btrees that desire a lock. +*/ +SQLITE_PRIVATE void sqlite3BtreeEnter(Btree *p){ + /* Some basic sanity checking on the Btree. The list of Btrees + ** connected by pNext and pPrev should be in sorted order by + ** Btree.pBt value. All elements of the list should belong to + ** the same connection. Only shared Btrees are on the list. */ + assert( p->pNext==0 || p->pNext->pBt>p->pBt ); + assert( p->pPrev==0 || p->pPrev->pBt<p->pBt ); + assert( p->pNext==0 || p->pNext->db==p->db ); + assert( p->pPrev==0 || p->pPrev->db==p->db ); + assert( p->sharable || (p->pNext==0 && p->pPrev==0) ); + + /* Check for locking consistency */ + assert( !p->locked || p->wantToLock>0 ); + assert( p->sharable || p->wantToLock==0 ); + + /* We should already hold a lock on the database connection */ + assert( sqlite3_mutex_held(p->db->mutex) ); + + /* Unless the database is sharable and unlocked, then BtShared.db + ** should already be set correctly. */ + assert( (p->locked==0 && p->sharable) || p->pBt->db==p->db ); + + if( !p->sharable ) return; + p->wantToLock++; + if( p->locked ) return; + btreeLockCarefully(p); +} + +/* This is a helper function for sqlite3BtreeLock(). By moving +** complex, but seldom used logic, out of sqlite3BtreeLock() and +** into this routine, we avoid unnecessary stack pointer changes +** and thus help the sqlite3BtreeLock() routine to run much faster +** in the common case. +*/ +static void SQLITE_NOINLINE btreeLockCarefully(Btree *p){ + Btree *pLater; + + /* In most cases, we should be able to acquire the lock we + ** want without having to go through the ascending lock + ** procedure that follows. Just be sure not to block. + */ + if( sqlite3_mutex_try(p->pBt->mutex)==SQLITE_OK ){ + p->pBt->db = p->db; + p->locked = 1; + return; + } + + /* To avoid deadlock, first release all locks with a larger + ** BtShared address. Then acquire our lock. Then reacquire + ** the other BtShared locks that we used to hold in ascending + ** order. + */ + for(pLater=p->pNext; pLater; pLater=pLater->pNext){ + assert( pLater->sharable ); + assert( pLater->pNext==0 || pLater->pNext->pBt>pLater->pBt ); + assert( !pLater->locked || pLater->wantToLock>0 ); + if( pLater->locked ){ + unlockBtreeMutex(pLater); + } + } + lockBtreeMutex(p); + for(pLater=p->pNext; pLater; pLater=pLater->pNext){ + if( pLater->wantToLock ){ + lockBtreeMutex(pLater); + } + } +} + + +/* +** Exit the recursive mutex on a Btree. +*/ +SQLITE_PRIVATE void sqlite3BtreeLeave(Btree *p){ + assert( sqlite3_mutex_held(p->db->mutex) ); + if( p->sharable ){ + assert( p->wantToLock>0 ); + p->wantToLock--; + if( p->wantToLock==0 ){ + unlockBtreeMutex(p); + } + } +} + +#ifndef NDEBUG +/* +** Return true if the BtShared mutex is held on the btree, or if the +** B-Tree is not marked as sharable. +** +** This routine is used only from within assert() statements. +*/ +SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree *p){ + assert( p->sharable==0 || p->locked==0 || p->wantToLock>0 ); + assert( p->sharable==0 || p->locked==0 || p->db==p->pBt->db ); + assert( p->sharable==0 || p->locked==0 || sqlite3_mutex_held(p->pBt->mutex) ); + assert( p->sharable==0 || p->locked==0 || sqlite3_mutex_held(p->db->mutex) ); + + return (p->sharable==0 || p->locked); +} +#endif + + +/* +** Enter the mutex on every Btree associated with a database +** connection. This is needed (for example) prior to parsing +** a statement since we will be comparing table and column names +** against all schemas and we do not want those schemas being +** reset out from under us. +** +** There is a corresponding leave-all procedures. +** +** Enter the mutexes in ascending order by BtShared pointer address +** to avoid the possibility of deadlock when two threads with +** two or more btrees in common both try to lock all their btrees +** at the same instant. +*/ +static void SQLITE_NOINLINE btreeEnterAll(sqlite3 *db){ + int i; + int skipOk = 1; + Btree *p; + assert( sqlite3_mutex_held(db->mutex) ); + for(i=0; i<db->nDb; i++){ + p = db->aDb[i].pBt; + if( p && p->sharable ){ + sqlite3BtreeEnter(p); + skipOk = 0; + } + } + db->noSharedCache = skipOk; +} +SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3 *db){ + if( db->noSharedCache==0 ) btreeEnterAll(db); +} +static void SQLITE_NOINLINE btreeLeaveAll(sqlite3 *db){ + int i; + Btree *p; + assert( sqlite3_mutex_held(db->mutex) ); + for(i=0; i<db->nDb; i++){ + p = db->aDb[i].pBt; + if( p ) sqlite3BtreeLeave(p); + } +} +SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3 *db){ + if( db->noSharedCache==0 ) btreeLeaveAll(db); +} + +#ifndef NDEBUG +/* +** Return true if the current thread holds the database connection +** mutex and all required BtShared mutexes. +** +** This routine is used inside assert() statements only. +*/ +SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3 *db){ + int i; + if( !sqlite3_mutex_held(db->mutex) ){ + return 0; + } + for(i=0; i<db->nDb; i++){ + Btree *p; + p = db->aDb[i].pBt; + if( p && p->sharable && + (p->wantToLock==0 || !sqlite3_mutex_held(p->pBt->mutex)) ){ + return 0; + } + } + return 1; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Return true if the correct mutexes are held for accessing the +** db->aDb[iDb].pSchema structure. The mutexes required for schema +** access are: +** +** (1) The mutex on db +** (2) if iDb!=1, then the mutex on db->aDb[iDb].pBt. +** +** If pSchema is not NULL, then iDb is computed from pSchema and +** db using sqlite3SchemaToIndex(). +*/ +SQLITE_PRIVATE int sqlite3SchemaMutexHeld(sqlite3 *db, int iDb, Schema *pSchema){ + Btree *p; + assert( db!=0 ); + if( db->pVfs==0 && db->nDb==0 ) return 1; + if( pSchema ) iDb = sqlite3SchemaToIndex(db, pSchema); + assert( iDb>=0 && iDb<db->nDb ); + if( !sqlite3_mutex_held(db->mutex) ) return 0; + if( iDb==1 ) return 1; + p = db->aDb[iDb].pBt; + assert( p!=0 ); + return p->sharable==0 || p->locked==1; +} +#endif /* NDEBUG */ + +#else /* SQLITE_THREADSAFE>0 above. SQLITE_THREADSAFE==0 below */ +/* +** The following are special cases for mutex enter routines for use +** in single threaded applications that use shared cache. Except for +** these two routines, all mutex operations are no-ops in that case and +** are null #defines in btree.h. +** +** If shared cache is disabled, then all btree mutex routines, including +** the ones below, are no-ops and are null #defines in btree.h. +*/ + +SQLITE_PRIVATE void sqlite3BtreeEnter(Btree *p){ + p->pBt->db = p->db; +} +SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3 *db){ + int i; + for(i=0; i<db->nDb; i++){ + Btree *p = db->aDb[i].pBt; + if( p ){ + p->pBt->db = p->db; + } + } +} +#endif /* if SQLITE_THREADSAFE */ + +#ifndef SQLITE_OMIT_INCRBLOB +/* +** Enter a mutex on a Btree given a cursor owned by that Btree. +** +** These entry points are used by incremental I/O only. Enter() is required +** any time OMIT_SHARED_CACHE is not defined, regardless of whether or not +** the build is threadsafe. Leave() is only required by threadsafe builds. +*/ +SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor *pCur){ + sqlite3BtreeEnter(pCur->pBtree); +} +# if SQLITE_THREADSAFE +SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor *pCur){ + sqlite3BtreeLeave(pCur->pBtree); +} +# endif +#endif /* ifndef SQLITE_OMIT_INCRBLOB */ + +#endif /* ifndef SQLITE_OMIT_SHARED_CACHE */ + +/************** End of btmutex.c *********************************************/ +/************** Begin file btree.c *******************************************/ + +/* +** 2004 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file implements an external (disk-based) database using BTrees. +** See the header comment on "btreeInt.h" for additional information. +** Including a description of file format and an overview of operation. +*/ +/* #include "btreeInt.h" */ + +/* +** The header string that appears at the beginning of every +** SQLite database. +*/ +static const char zMagicHeader[] = SQLITE_FILE_HEADER; + +/* +** Set this global variable to 1 to enable tracing using the TRACE +** macro. +*/ +#if 0 +int sqlite3BtreeTrace=1; /* True to enable tracing */ +# define TRACE(X) if(sqlite3BtreeTrace){printf X;fflush(stdout);} +#else +# define TRACE(X) +#endif + +/* +** Extract a 2-byte big-endian integer from an array of unsigned bytes. +** But if the value is zero, make it 65536. +** +** This routine is used to extract the "offset to cell content area" value +** from the header of a btree page. If the page size is 65536 and the page +** is empty, the offset should be 65536, but the 2-byte value stores zero. +** This routine makes the necessary adjustment to 65536. +*/ +#define get2byteNotZero(X) (((((int)get2byte(X))-1)&0xffff)+1) + +/* +** Values passed as the 5th argument to allocateBtreePage() +*/ +#define BTALLOC_ANY 0 /* Allocate any page */ +#define BTALLOC_EXACT 1 /* Allocate exact page if possible */ +#define BTALLOC_LE 2 /* Allocate any page <= the parameter */ + +/* +** Macro IfNotOmitAV(x) returns (x) if SQLITE_OMIT_AUTOVACUUM is not +** defined, or 0 if it is. For example: +** +** bIncrVacuum = IfNotOmitAV(pBtShared->incrVacuum); +*/ +#ifndef SQLITE_OMIT_AUTOVACUUM +#define IfNotOmitAV(expr) (expr) +#else +#define IfNotOmitAV(expr) 0 +#endif + +#ifndef SQLITE_OMIT_SHARED_CACHE +/* +** A list of BtShared objects that are eligible for participation +** in shared cache. This variable has file scope during normal builds, +** but the test harness needs to access it so we make it global for +** test builds. +** +** Access to this variable is protected by SQLITE_MUTEX_STATIC_MAIN. +*/ +#ifdef SQLITE_TEST +SQLITE_PRIVATE BtShared *SQLITE_WSD sqlite3SharedCacheList = 0; +#else +static BtShared *SQLITE_WSD sqlite3SharedCacheList = 0; +#endif +#endif /* SQLITE_OMIT_SHARED_CACHE */ + +#ifndef SQLITE_OMIT_SHARED_CACHE +/* +** Enable or disable the shared pager and schema features. +** +** This routine has no effect on existing database connections. +** The shared cache setting effects only future calls to +** sqlite3_open(), sqlite3_open16(), or sqlite3_open_v2(). +*/ +SQLITE_API int sqlite3_enable_shared_cache(int enable){ + sqlite3GlobalConfig.sharedCacheEnabled = enable; + return SQLITE_OK; +} +#endif + + + +#ifdef SQLITE_OMIT_SHARED_CACHE + /* + ** The functions querySharedCacheTableLock(), setSharedCacheTableLock(), + ** and clearAllSharedCacheTableLocks() + ** manipulate entries in the BtShared.pLock linked list used to store + ** shared-cache table level locks. If the library is compiled with the + ** shared-cache feature disabled, then there is only ever one user + ** of each BtShared structure and so this locking is not necessary. + ** So define the lock related functions as no-ops. + */ + #define querySharedCacheTableLock(a,b,c) SQLITE_OK + #define setSharedCacheTableLock(a,b,c) SQLITE_OK + #define clearAllSharedCacheTableLocks(a) + #define downgradeAllSharedCacheTableLocks(a) + #define hasSharedCacheTableLock(a,b,c,d) 1 + #define hasReadConflicts(a, b) 0 +#endif + +#ifdef SQLITE_DEBUG +/* +** Return and reset the seek counter for a Btree object. +*/ +SQLITE_PRIVATE sqlite3_uint64 sqlite3BtreeSeekCount(Btree *pBt){ + u64 n = pBt->nSeek; + pBt->nSeek = 0; + return n; +} +#endif + +/* +** Implementation of the SQLITE_CORRUPT_PAGE() macro. Takes a single +** (MemPage*) as an argument. The (MemPage*) must not be NULL. +** +** If SQLITE_DEBUG is not defined, then this macro is equivalent to +** SQLITE_CORRUPT_BKPT. Or, if SQLITE_DEBUG is set, then the log message +** normally produced as a side-effect of SQLITE_CORRUPT_BKPT is augmented +** with the page number and filename associated with the (MemPage*). +*/ +#ifdef SQLITE_DEBUG +int corruptPageError(int lineno, MemPage *p){ + char *zMsg; + sqlite3BeginBenignMalloc(); + zMsg = sqlite3_mprintf("database corruption page %u of %s", + p->pgno, sqlite3PagerFilename(p->pBt->pPager, 0) + ); + sqlite3EndBenignMalloc(); + if( zMsg ){ + sqlite3ReportError(SQLITE_CORRUPT, lineno, zMsg); + } + sqlite3_free(zMsg); + return SQLITE_CORRUPT_BKPT; +} +# define SQLITE_CORRUPT_PAGE(pMemPage) corruptPageError(__LINE__, pMemPage) +#else +# define SQLITE_CORRUPT_PAGE(pMemPage) SQLITE_CORRUPT_PGNO(pMemPage->pgno) +#endif + +#ifndef SQLITE_OMIT_SHARED_CACHE + +#ifdef SQLITE_DEBUG +/* +**** This function is only used as part of an assert() statement. *** +** +** Check to see if pBtree holds the required locks to read or write to the +** table with root page iRoot. Return 1 if it does and 0 if not. +** +** For example, when writing to a table with root-page iRoot via +** Btree connection pBtree: +** +** assert( hasSharedCacheTableLock(pBtree, iRoot, 0, WRITE_LOCK) ); +** +** When writing to an index that resides in a sharable database, the +** caller should have first obtained a lock specifying the root page of +** the corresponding table. This makes things a bit more complicated, +** as this module treats each table as a separate structure. To determine +** the table corresponding to the index being written, this +** function has to search through the database schema. +** +** Instead of a lock on the table/index rooted at page iRoot, the caller may +** hold a write-lock on the schema table (root page 1). This is also +** acceptable. +*/ +static int hasSharedCacheTableLock( + Btree *pBtree, /* Handle that must hold lock */ + Pgno iRoot, /* Root page of b-tree */ + int isIndex, /* True if iRoot is the root of an index b-tree */ + int eLockType /* Required lock type (READ_LOCK or WRITE_LOCK) */ +){ + Schema *pSchema = (Schema *)pBtree->pBt->pSchema; + Pgno iTab = 0; + BtLock *pLock; + + /* If this database is not shareable, or if the client is reading + ** and has the read-uncommitted flag set, then no lock is required. + ** Return true immediately. + */ + if( (pBtree->sharable==0) + || (eLockType==READ_LOCK && (pBtree->db->flags & SQLITE_ReadUncommit)) + ){ + return 1; + } + + /* If the client is reading or writing an index and the schema is + ** not loaded, then it is too difficult to actually check to see if + ** the correct locks are held. So do not bother - just return true. + ** This case does not come up very often anyhow. + */ + if( isIndex && (!pSchema || (pSchema->schemaFlags&DB_SchemaLoaded)==0) ){ + return 1; + } + + /* Figure out the root-page that the lock should be held on. For table + ** b-trees, this is just the root page of the b-tree being read or + ** written. For index b-trees, it is the root page of the associated + ** table. */ + if( isIndex ){ + HashElem *p; + int bSeen = 0; + for(p=sqliteHashFirst(&pSchema->idxHash); p; p=sqliteHashNext(p)){ + Index *pIdx = (Index *)sqliteHashData(p); + if( pIdx->tnum==iRoot ){ + if( bSeen ){ + /* Two or more indexes share the same root page. There must + ** be imposter tables. So just return true. The assert is not + ** useful in that case. */ + return 1; + } + iTab = pIdx->pTable->tnum; + bSeen = 1; + } + } + }else{ + iTab = iRoot; + } + + /* Search for the required lock. Either a write-lock on root-page iTab, a + ** write-lock on the schema table, or (if the client is reading) a + ** read-lock on iTab will suffice. Return 1 if any of these are found. */ + for(pLock=pBtree->pBt->pLock; pLock; pLock=pLock->pNext){ + if( pLock->pBtree==pBtree + && (pLock->iTable==iTab || (pLock->eLock==WRITE_LOCK && pLock->iTable==1)) + && pLock->eLock>=eLockType + ){ + return 1; + } + } + + /* Failed to find the required lock. */ + return 0; +} +#endif /* SQLITE_DEBUG */ + +#ifdef SQLITE_DEBUG +/* +**** This function may be used as part of assert() statements only. **** +** +** Return true if it would be illegal for pBtree to write into the +** table or index rooted at iRoot because other shared connections are +** simultaneously reading that same table or index. +** +** It is illegal for pBtree to write if some other Btree object that +** shares the same BtShared object is currently reading or writing +** the iRoot table. Except, if the other Btree object has the +** read-uncommitted flag set, then it is OK for the other object to +** have a read cursor. +** +** For example, before writing to any part of the table or index +** rooted at page iRoot, one should call: +** +** assert( !hasReadConflicts(pBtree, iRoot) ); +*/ +static int hasReadConflicts(Btree *pBtree, Pgno iRoot){ + BtCursor *p; + for(p=pBtree->pBt->pCursor; p; p=p->pNext){ + if( p->pgnoRoot==iRoot + && p->pBtree!=pBtree + && 0==(p->pBtree->db->flags & SQLITE_ReadUncommit) + ){ + return 1; + } + } + return 0; +} +#endif /* #ifdef SQLITE_DEBUG */ + +/* +** Query to see if Btree handle p may obtain a lock of type eLock +** (READ_LOCK or WRITE_LOCK) on the table with root-page iTab. Return +** SQLITE_OK if the lock may be obtained (by calling +** setSharedCacheTableLock()), or SQLITE_LOCKED if not. +*/ +static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){ + BtShared *pBt = p->pBt; + BtLock *pIter; + + assert( sqlite3BtreeHoldsMutex(p) ); + assert( eLock==READ_LOCK || eLock==WRITE_LOCK ); + assert( p->db!=0 ); + assert( !(p->db->flags&SQLITE_ReadUncommit)||eLock==WRITE_LOCK||iTab==1 ); + + /* If requesting a write-lock, then the Btree must have an open write + ** transaction on this file. And, obviously, for this to be so there + ** must be an open write transaction on the file itself. + */ + assert( eLock==READ_LOCK || (p==pBt->pWriter && p->inTrans==TRANS_WRITE) ); + assert( eLock==READ_LOCK || pBt->inTransaction==TRANS_WRITE ); + + /* This routine is a no-op if the shared-cache is not enabled */ + if( !p->sharable ){ + return SQLITE_OK; + } + + /* If some other connection is holding an exclusive lock, the + ** requested lock may not be obtained. + */ + if( pBt->pWriter!=p && (pBt->btsFlags & BTS_EXCLUSIVE)!=0 ){ + sqlite3ConnectionBlocked(p->db, pBt->pWriter->db); + return SQLITE_LOCKED_SHAREDCACHE; + } + + for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){ + /* The condition (pIter->eLock!=eLock) in the following if(...) + ** statement is a simplification of: + ** + ** (eLock==WRITE_LOCK || pIter->eLock==WRITE_LOCK) + ** + ** since we know that if eLock==WRITE_LOCK, then no other connection + ** may hold a WRITE_LOCK on any table in this file (since there can + ** only be a single writer). + */ + assert( pIter->eLock==READ_LOCK || pIter->eLock==WRITE_LOCK ); + assert( eLock==READ_LOCK || pIter->pBtree==p || pIter->eLock==READ_LOCK); + if( pIter->pBtree!=p && pIter->iTable==iTab && pIter->eLock!=eLock ){ + sqlite3ConnectionBlocked(p->db, pIter->pBtree->db); + if( eLock==WRITE_LOCK ){ + assert( p==pBt->pWriter ); + pBt->btsFlags |= BTS_PENDING; + } + return SQLITE_LOCKED_SHAREDCACHE; + } + } + return SQLITE_OK; +} +#endif /* !SQLITE_OMIT_SHARED_CACHE */ + +#ifndef SQLITE_OMIT_SHARED_CACHE +/* +** Add a lock on the table with root-page iTable to the shared-btree used +** by Btree handle p. Parameter eLock must be either READ_LOCK or +** WRITE_LOCK. +** +** This function assumes the following: +** +** (a) The specified Btree object p is connected to a sharable +** database (one with the BtShared.sharable flag set), and +** +** (b) No other Btree objects hold a lock that conflicts +** with the requested lock (i.e. querySharedCacheTableLock() has +** already been called and returned SQLITE_OK). +** +** SQLITE_OK is returned if the lock is added successfully. SQLITE_NOMEM +** is returned if a malloc attempt fails. +*/ +static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){ + BtShared *pBt = p->pBt; + BtLock *pLock = 0; + BtLock *pIter; + + assert( sqlite3BtreeHoldsMutex(p) ); + assert( eLock==READ_LOCK || eLock==WRITE_LOCK ); + assert( p->db!=0 ); + + /* A connection with the read-uncommitted flag set will never try to + ** obtain a read-lock using this function. The only read-lock obtained + ** by a connection in read-uncommitted mode is on the sqlite_schema + ** table, and that lock is obtained in BtreeBeginTrans(). */ + assert( 0==(p->db->flags&SQLITE_ReadUncommit) || eLock==WRITE_LOCK ); + + /* This function should only be called on a sharable b-tree after it + ** has been determined that no other b-tree holds a conflicting lock. */ + assert( p->sharable ); + assert( SQLITE_OK==querySharedCacheTableLock(p, iTable, eLock) ); + + /* First search the list for an existing lock on this table. */ + for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){ + if( pIter->iTable==iTable && pIter->pBtree==p ){ + pLock = pIter; + break; + } + } + + /* If the above search did not find a BtLock struct associating Btree p + ** with table iTable, allocate one and link it into the list. + */ + if( !pLock ){ + pLock = (BtLock *)sqlite3MallocZero(sizeof(BtLock)); + if( !pLock ){ + return SQLITE_NOMEM_BKPT; + } + pLock->iTable = iTable; + pLock->pBtree = p; + pLock->pNext = pBt->pLock; + pBt->pLock = pLock; + } + + /* Set the BtLock.eLock variable to the maximum of the current lock + ** and the requested lock. This means if a write-lock was already held + ** and a read-lock requested, we don't incorrectly downgrade the lock. + */ + assert( WRITE_LOCK>READ_LOCK ); + if( eLock>pLock->eLock ){ + pLock->eLock = eLock; + } + + return SQLITE_OK; +} +#endif /* !SQLITE_OMIT_SHARED_CACHE */ + +#ifndef SQLITE_OMIT_SHARED_CACHE +/* +** Release all the table locks (locks obtained via calls to +** the setSharedCacheTableLock() procedure) held by Btree object p. +** +** This function assumes that Btree p has an open read or write +** transaction. If it does not, then the BTS_PENDING flag +** may be incorrectly cleared. +*/ +static void clearAllSharedCacheTableLocks(Btree *p){ + BtShared *pBt = p->pBt; + BtLock **ppIter = &pBt->pLock; + + assert( sqlite3BtreeHoldsMutex(p) ); + assert( p->sharable || 0==*ppIter ); + assert( p->inTrans>0 ); + + while( *ppIter ){ + BtLock *pLock = *ppIter; + assert( (pBt->btsFlags & BTS_EXCLUSIVE)==0 || pBt->pWriter==pLock->pBtree ); + assert( pLock->pBtree->inTrans>=pLock->eLock ); + if( pLock->pBtree==p ){ + *ppIter = pLock->pNext; + assert( pLock->iTable!=1 || pLock==&p->lock ); + if( pLock->iTable!=1 ){ + sqlite3_free(pLock); + } + }else{ + ppIter = &pLock->pNext; + } + } + + assert( (pBt->btsFlags & BTS_PENDING)==0 || pBt->pWriter ); + if( pBt->pWriter==p ){ + pBt->pWriter = 0; + pBt->btsFlags &= ~(BTS_EXCLUSIVE|BTS_PENDING); + }else if( pBt->nTransaction==2 ){ + /* This function is called when Btree p is concluding its + ** transaction. If there currently exists a writer, and p is not + ** that writer, then the number of locks held by connections other + ** than the writer must be about to drop to zero. In this case + ** set the BTS_PENDING flag to 0. + ** + ** If there is not currently a writer, then BTS_PENDING must + ** be zero already. So this next line is harmless in that case. + */ + pBt->btsFlags &= ~BTS_PENDING; + } +} + +/* +** This function changes all write-locks held by Btree p into read-locks. +*/ +static void downgradeAllSharedCacheTableLocks(Btree *p){ + BtShared *pBt = p->pBt; + if( pBt->pWriter==p ){ + BtLock *pLock; + pBt->pWriter = 0; + pBt->btsFlags &= ~(BTS_EXCLUSIVE|BTS_PENDING); + for(pLock=pBt->pLock; pLock; pLock=pLock->pNext){ + assert( pLock->eLock==READ_LOCK || pLock->pBtree==p ); + pLock->eLock = READ_LOCK; + } + } +} + +#endif /* SQLITE_OMIT_SHARED_CACHE */ + +static void releasePage(MemPage *pPage); /* Forward reference */ +static void releasePageOne(MemPage *pPage); /* Forward reference */ +static void releasePageNotNull(MemPage *pPage); /* Forward reference */ + +/* +***** This routine is used inside of assert() only **** +** +** Verify that the cursor holds the mutex on its BtShared +*/ +#ifdef SQLITE_DEBUG +static int cursorHoldsMutex(BtCursor *p){ + return sqlite3_mutex_held(p->pBt->mutex); +} + +/* Verify that the cursor and the BtShared agree about what is the current +** database connetion. This is important in shared-cache mode. If the database +** connection pointers get out-of-sync, it is possible for routines like +** btreeInitPage() to reference an stale connection pointer that references a +** a connection that has already closed. This routine is used inside assert() +** statements only and for the purpose of double-checking that the btree code +** does keep the database connection pointers up-to-date. +*/ +static int cursorOwnsBtShared(BtCursor *p){ + assert( cursorHoldsMutex(p) ); + return (p->pBtree->db==p->pBt->db); +} +#endif + +/* +** Invalidate the overflow cache of the cursor passed as the first argument. +** on the shared btree structure pBt. +*/ +#define invalidateOverflowCache(pCur) (pCur->curFlags &= ~BTCF_ValidOvfl) + +/* +** Invalidate the overflow page-list cache for all cursors opened +** on the shared btree structure pBt. +*/ +static void invalidateAllOverflowCache(BtShared *pBt){ + BtCursor *p; + assert( sqlite3_mutex_held(pBt->mutex) ); + for(p=pBt->pCursor; p; p=p->pNext){ + invalidateOverflowCache(p); + } +} + +#ifndef SQLITE_OMIT_INCRBLOB +/* +** This function is called before modifying the contents of a table +** to invalidate any incrblob cursors that are open on the +** row or one of the rows being modified. +** +** If argument isClearTable is true, then the entire contents of the +** table is about to be deleted. In this case invalidate all incrblob +** cursors open on any row within the table with root-page pgnoRoot. +** +** Otherwise, if argument isClearTable is false, then the row with +** rowid iRow is being replaced or deleted. In this case invalidate +** only those incrblob cursors open on that specific row. +*/ +static void invalidateIncrblobCursors( + Btree *pBtree, /* The database file to check */ + Pgno pgnoRoot, /* The table that might be changing */ + i64 iRow, /* The rowid that might be changing */ + int isClearTable /* True if all rows are being deleted */ +){ + BtCursor *p; + assert( pBtree->hasIncrblobCur ); + assert( sqlite3BtreeHoldsMutex(pBtree) ); + pBtree->hasIncrblobCur = 0; + for(p=pBtree->pBt->pCursor; p; p=p->pNext){ + if( (p->curFlags & BTCF_Incrblob)!=0 ){ + pBtree->hasIncrblobCur = 1; + if( p->pgnoRoot==pgnoRoot && (isClearTable || p->info.nKey==iRow) ){ + p->eState = CURSOR_INVALID; + } + } + } +} + +#else + /* Stub function when INCRBLOB is omitted */ + #define invalidateIncrblobCursors(w,x,y,z) +#endif /* SQLITE_OMIT_INCRBLOB */ + +/* +** Set bit pgno of the BtShared.pHasContent bitvec. This is called +** when a page that previously contained data becomes a free-list leaf +** page. +** +** The BtShared.pHasContent bitvec exists to work around an obscure +** bug caused by the interaction of two useful IO optimizations surrounding +** free-list leaf pages: +** +** 1) When all data is deleted from a page and the page becomes +** a free-list leaf page, the page is not written to the database +** (as free-list leaf pages contain no meaningful data). Sometimes +** such a page is not even journalled (as it will not be modified, +** why bother journalling it?). +** +** 2) When a free-list leaf page is reused, its content is not read +** from the database or written to the journal file (why should it +** be, if it is not at all meaningful?). +** +** By themselves, these optimizations work fine and provide a handy +** performance boost to bulk delete or insert operations. However, if +** a page is moved to the free-list and then reused within the same +** transaction, a problem comes up. If the page is not journalled when +** it is moved to the free-list and it is also not journalled when it +** is extracted from the free-list and reused, then the original data +** may be lost. In the event of a rollback, it may not be possible +** to restore the database to its original configuration. +** +** The solution is the BtShared.pHasContent bitvec. Whenever a page is +** moved to become a free-list leaf page, the corresponding bit is +** set in the bitvec. Whenever a leaf page is extracted from the free-list, +** optimization 2 above is omitted if the corresponding bit is already +** set in BtShared.pHasContent. The contents of the bitvec are cleared +** at the end of every transaction. +*/ +static int btreeSetHasContent(BtShared *pBt, Pgno pgno){ + int rc = SQLITE_OK; + if( !pBt->pHasContent ){ + assert( pgno<=pBt->nPage ); + pBt->pHasContent = sqlite3BitvecCreate(pBt->nPage); + if( !pBt->pHasContent ){ + rc = SQLITE_NOMEM_BKPT; + } + } + if( rc==SQLITE_OK && pgno<=sqlite3BitvecSize(pBt->pHasContent) ){ + rc = sqlite3BitvecSet(pBt->pHasContent, pgno); + } + return rc; +} + +/* +** Query the BtShared.pHasContent vector. +** +** This function is called when a free-list leaf page is removed from the +** free-list for reuse. It returns false if it is safe to retrieve the +** page from the pager layer with the 'no-content' flag set. True otherwise. +*/ +static int btreeGetHasContent(BtShared *pBt, Pgno pgno){ + Bitvec *p = pBt->pHasContent; + return p && (pgno>sqlite3BitvecSize(p) || sqlite3BitvecTestNotNull(p, pgno)); +} + +/* +** Clear (destroy) the BtShared.pHasContent bitvec. This should be +** invoked at the conclusion of each write-transaction. +*/ +static void btreeClearHasContent(BtShared *pBt){ + sqlite3BitvecDestroy(pBt->pHasContent); + pBt->pHasContent = 0; +} + +/* +** Release all of the apPage[] pages for a cursor. +*/ +static void btreeReleaseAllCursorPages(BtCursor *pCur){ + int i; + if( pCur->iPage>=0 ){ + for(i=0; i<pCur->iPage; i++){ + releasePageNotNull(pCur->apPage[i]); + } + releasePageNotNull(pCur->pPage); + pCur->iPage = -1; + } +} + +/* +** The cursor passed as the only argument must point to a valid entry +** when this function is called (i.e. have eState==CURSOR_VALID). This +** function saves the current cursor key in variables pCur->nKey and +** pCur->pKey. SQLITE_OK is returned if successful or an SQLite error +** code otherwise. +** +** If the cursor is open on an intkey table, then the integer key +** (the rowid) is stored in pCur->nKey and pCur->pKey is left set to +** NULL. If the cursor is open on a non-intkey table, then pCur->pKey is +** set to point to a malloced buffer pCur->nKey bytes in size containing +** the key. +*/ +static int saveCursorKey(BtCursor *pCur){ + int rc = SQLITE_OK; + assert( CURSOR_VALID==pCur->eState ); + assert( 0==pCur->pKey ); + assert( cursorHoldsMutex(pCur) ); + + if( pCur->curIntKey ){ + /* Only the rowid is required for a table btree */ + pCur->nKey = sqlite3BtreeIntegerKey(pCur); + }else{ + /* For an index btree, save the complete key content. It is possible + ** that the current key is corrupt. In that case, it is possible that + ** the sqlite3VdbeRecordUnpack() function may overread the buffer by + ** up to the size of 1 varint plus 1 8-byte value when the cursor + ** position is restored. Hence the 17 bytes of padding allocated + ** below. */ + void *pKey; + pCur->nKey = sqlite3BtreePayloadSize(pCur); + pKey = sqlite3Malloc( pCur->nKey + 9 + 8 ); + if( pKey ){ + rc = sqlite3BtreePayload(pCur, 0, (int)pCur->nKey, pKey); + if( rc==SQLITE_OK ){ + memset(((u8*)pKey)+pCur->nKey, 0, 9+8); + pCur->pKey = pKey; + }else{ + sqlite3_free(pKey); + } + }else{ + rc = SQLITE_NOMEM_BKPT; + } + } + assert( !pCur->curIntKey || !pCur->pKey ); + return rc; +} + +/* +** Save the current cursor position in the variables BtCursor.nKey +** and BtCursor.pKey. The cursor's state is set to CURSOR_REQUIRESEEK. +** +** The caller must ensure that the cursor is valid (has eState==CURSOR_VALID) +** prior to calling this routine. +*/ +static int saveCursorPosition(BtCursor *pCur){ + int rc; + + assert( CURSOR_VALID==pCur->eState || CURSOR_SKIPNEXT==pCur->eState ); + assert( 0==pCur->pKey ); + assert( cursorHoldsMutex(pCur) ); + + if( pCur->curFlags & BTCF_Pinned ){ + return SQLITE_CONSTRAINT_PINNED; + } + if( pCur->eState==CURSOR_SKIPNEXT ){ + pCur->eState = CURSOR_VALID; + }else{ + pCur->skipNext = 0; + } + + rc = saveCursorKey(pCur); + if( rc==SQLITE_OK ){ + btreeReleaseAllCursorPages(pCur); + pCur->eState = CURSOR_REQUIRESEEK; + } + + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl|BTCF_AtLast); + return rc; +} + +/* Forward reference */ +static int SQLITE_NOINLINE saveCursorsOnList(BtCursor*,Pgno,BtCursor*); + +/* +** Save the positions of all cursors (except pExcept) that are open on +** the table with root-page iRoot. "Saving the cursor position" means that +** the location in the btree is remembered in such a way that it can be +** moved back to the same spot after the btree has been modified. This +** routine is called just before cursor pExcept is used to modify the +** table, for example in BtreeDelete() or BtreeInsert(). +** +** If there are two or more cursors on the same btree, then all such +** cursors should have their BTCF_Multiple flag set. The btreeCursor() +** routine enforces that rule. This routine only needs to be called in +** the uncommon case when pExpect has the BTCF_Multiple flag set. +** +** If pExpect!=NULL and if no other cursors are found on the same root-page, +** then the BTCF_Multiple flag on pExpect is cleared, to avoid another +** pointless call to this routine. +** +** Implementation note: This routine merely checks to see if any cursors +** need to be saved. It calls out to saveCursorsOnList() in the (unusual) +** event that cursors are in need to being saved. +*/ +static int saveAllCursors(BtShared *pBt, Pgno iRoot, BtCursor *pExcept){ + BtCursor *p; + assert( sqlite3_mutex_held(pBt->mutex) ); + assert( pExcept==0 || pExcept->pBt==pBt ); + for(p=pBt->pCursor; p; p=p->pNext){ + if( p!=pExcept && (0==iRoot || p->pgnoRoot==iRoot) ) break; + } + if( p ) return saveCursorsOnList(p, iRoot, pExcept); + if( pExcept ) pExcept->curFlags &= ~BTCF_Multiple; + return SQLITE_OK; +} + +/* This helper routine to saveAllCursors does the actual work of saving +** the cursors if and when a cursor is found that actually requires saving. +** The common case is that no cursors need to be saved, so this routine is +** broken out from its caller to avoid unnecessary stack pointer movement. +*/ +static int SQLITE_NOINLINE saveCursorsOnList( + BtCursor *p, /* The first cursor that needs saving */ + Pgno iRoot, /* Only save cursor with this iRoot. Save all if zero */ + BtCursor *pExcept /* Do not save this cursor */ +){ + do{ + if( p!=pExcept && (0==iRoot || p->pgnoRoot==iRoot) ){ + if( p->eState==CURSOR_VALID || p->eState==CURSOR_SKIPNEXT ){ + int rc = saveCursorPosition(p); + if( SQLITE_OK!=rc ){ + return rc; + } + }else{ + testcase( p->iPage>=0 ); + btreeReleaseAllCursorPages(p); + } + } + p = p->pNext; + }while( p ); + return SQLITE_OK; +} + +/* +** Clear the current cursor position. +*/ +SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *pCur){ + assert( cursorHoldsMutex(pCur) ); + sqlite3_free(pCur->pKey); + pCur->pKey = 0; + pCur->eState = CURSOR_INVALID; +} + +/* +** In this version of BtreeMoveto, pKey is a packed index record +** such as is generated by the OP_MakeRecord opcode. Unpack the +** record and then call sqlite3BtreeIndexMoveto() to do the work. +*/ +static int btreeMoveto( + BtCursor *pCur, /* Cursor open on the btree to be searched */ + const void *pKey, /* Packed key if the btree is an index */ + i64 nKey, /* Integer key for tables. Size of pKey for indices */ + int bias, /* Bias search to the high end */ + int *pRes /* Write search results here */ +){ + int rc; /* Status code */ + UnpackedRecord *pIdxKey; /* Unpacked index key */ + + if( pKey ){ + KeyInfo *pKeyInfo = pCur->pKeyInfo; + assert( nKey==(i64)(int)nKey ); + pIdxKey = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); + if( pIdxKey==0 ) return SQLITE_NOMEM_BKPT; + sqlite3VdbeRecordUnpack(pKeyInfo, (int)nKey, pKey, pIdxKey); + if( pIdxKey->nField==0 || pIdxKey->nField>pKeyInfo->nAllField ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = sqlite3BtreeIndexMoveto(pCur, pIdxKey, pRes); + } + sqlite3DbFree(pCur->pKeyInfo->db, pIdxKey); + }else{ + pIdxKey = 0; + rc = sqlite3BtreeTableMoveto(pCur, nKey, bias, pRes); + } + return rc; +} + +/* +** Restore the cursor to the position it was in (or as close to as possible) +** when saveCursorPosition() was called. Note that this call deletes the +** saved position info stored by saveCursorPosition(), so there can be +** at most one effective restoreCursorPosition() call after each +** saveCursorPosition(). +*/ +static int btreeRestoreCursorPosition(BtCursor *pCur){ + int rc; + int skipNext = 0; + assert( cursorOwnsBtShared(pCur) ); + assert( pCur->eState>=CURSOR_REQUIRESEEK ); + if( pCur->eState==CURSOR_FAULT ){ + return pCur->skipNext; + } + pCur->eState = CURSOR_INVALID; + if( sqlite3FaultSim(410) ){ + rc = SQLITE_IOERR; + }else{ + rc = btreeMoveto(pCur, pCur->pKey, pCur->nKey, 0, &skipNext); + } + if( rc==SQLITE_OK ){ + sqlite3_free(pCur->pKey); + pCur->pKey = 0; + assert( pCur->eState==CURSOR_VALID || pCur->eState==CURSOR_INVALID ); + if( skipNext ) pCur->skipNext = skipNext; + if( pCur->skipNext && pCur->eState==CURSOR_VALID ){ + pCur->eState = CURSOR_SKIPNEXT; + } + } + return rc; +} + +#define restoreCursorPosition(p) \ + (p->eState>=CURSOR_REQUIRESEEK ? \ + btreeRestoreCursorPosition(p) : \ + SQLITE_OK) + +/* +** Determine whether or not a cursor has moved from the position where +** it was last placed, or has been invalidated for any other reason. +** Cursors can move when the row they are pointing at is deleted out +** from under them, for example. Cursor might also move if a btree +** is rebalanced. +** +** Calling this routine with a NULL cursor pointer returns false. +** +** Use the separate sqlite3BtreeCursorRestore() routine to restore a cursor +** back to where it ought to be if this routine returns true. +*/ +SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor *pCur){ + assert( EIGHT_BYTE_ALIGNMENT(pCur) + || pCur==sqlite3BtreeFakeValidCursor() ); + assert( offsetof(BtCursor, eState)==0 ); + assert( sizeof(pCur->eState)==1 ); + return CURSOR_VALID != *(u8*)pCur; +} + +/* +** Return a pointer to a fake BtCursor object that will always answer +** false to the sqlite3BtreeCursorHasMoved() routine above. The fake +** cursor returned must not be used with any other Btree interface. +*/ +SQLITE_PRIVATE BtCursor *sqlite3BtreeFakeValidCursor(void){ + static u8 fakeCursor = CURSOR_VALID; + assert( offsetof(BtCursor, eState)==0 ); + return (BtCursor*)&fakeCursor; +} + +/* +** This routine restores a cursor back to its original position after it +** has been moved by some outside activity (such as a btree rebalance or +** a row having been deleted out from under the cursor). +** +** On success, the *pDifferentRow parameter is false if the cursor is left +** pointing at exactly the same row. *pDifferntRow is the row the cursor +** was pointing to has been deleted, forcing the cursor to point to some +** nearby row. +** +** This routine should only be called for a cursor that just returned +** TRUE from sqlite3BtreeCursorHasMoved(). +*/ +SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor *pCur, int *pDifferentRow){ + int rc; + + assert( pCur!=0 ); + assert( pCur->eState!=CURSOR_VALID ); + rc = restoreCursorPosition(pCur); + if( rc ){ + *pDifferentRow = 1; + return rc; + } + if( pCur->eState!=CURSOR_VALID ){ + *pDifferentRow = 1; + }else{ + *pDifferentRow = 0; + } + return SQLITE_OK; +} + +#ifdef SQLITE_ENABLE_CURSOR_HINTS +/* +** Provide hints to the cursor. The particular hint given (and the type +** and number of the varargs parameters) is determined by the eHintType +** parameter. See the definitions of the BTREE_HINT_* macros for details. +*/ +SQLITE_PRIVATE void sqlite3BtreeCursorHint(BtCursor *pCur, int eHintType, ...){ + /* Used only by system that substitute their own storage engine */ +#ifdef SQLITE_DEBUG + if( ALWAYS(eHintType==BTREE_HINT_RANGE) ){ + va_list ap; + Expr *pExpr; + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = sqlite3CursorRangeHintExprCheck; + va_start(ap, eHintType); + pExpr = va_arg(ap, Expr*); + w.u.aMem = va_arg(ap, Mem*); + va_end(ap); + assert( pExpr!=0 ); + assert( w.u.aMem!=0 ); + sqlite3WalkExpr(&w, pExpr); + } +#endif /* SQLITE_DEBUG */ +} +#endif /* SQLITE_ENABLE_CURSOR_HINTS */ + + +/* +** Provide flag hints to the cursor. +*/ +SQLITE_PRIVATE void sqlite3BtreeCursorHintFlags(BtCursor *pCur, unsigned x){ + assert( x==BTREE_SEEK_EQ || x==BTREE_BULKLOAD || x==0 ); + pCur->hints = x; +} + + +#ifndef SQLITE_OMIT_AUTOVACUUM +/* +** Given a page number of a regular database page, return the page +** number for the pointer-map page that contains the entry for the +** input page number. +** +** Return 0 (not a valid page) for pgno==1 since there is +** no pointer map associated with page 1. The integrity_check logic +** requires that ptrmapPageno(*,1)!=1. +*/ +static Pgno ptrmapPageno(BtShared *pBt, Pgno pgno){ + int nPagesPerMapPage; + Pgno iPtrMap, ret; + assert( sqlite3_mutex_held(pBt->mutex) ); + if( pgno<2 ) return 0; + nPagesPerMapPage = (pBt->usableSize/5)+1; + iPtrMap = (pgno-2)/nPagesPerMapPage; + ret = (iPtrMap*nPagesPerMapPage) + 2; + if( ret==PENDING_BYTE_PAGE(pBt) ){ + ret++; + } + return ret; +} + +/* +** Write an entry into the pointer map. +** +** This routine updates the pointer map entry for page number 'key' +** so that it maps to type 'eType' and parent page number 'pgno'. +** +** If *pRC is initially non-zero (non-SQLITE_OK) then this routine is +** a no-op. If an error occurs, the appropriate error code is written +** into *pRC. +*/ +static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){ + DbPage *pDbPage; /* The pointer map page */ + u8 *pPtrmap; /* The pointer map data */ + Pgno iPtrmap; /* The pointer map page number */ + int offset; /* Offset in pointer map page */ + int rc; /* Return code from subfunctions */ + + if( *pRC ) return; + + assert( sqlite3_mutex_held(pBt->mutex) ); + /* The super-journal page number must never be used as a pointer map page */ + assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) ); + + assert( pBt->autoVacuum ); + if( key==0 ){ + *pRC = SQLITE_CORRUPT_BKPT; + return; + } + iPtrmap = PTRMAP_PAGENO(pBt, key); + rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage, 0); + if( rc!=SQLITE_OK ){ + *pRC = rc; + return; + } + if( ((char*)sqlite3PagerGetExtra(pDbPage))[0]!=0 ){ + /* The first byte of the extra data is the MemPage.isInit byte. + ** If that byte is set, it means this page is also being used + ** as a btree page. */ + *pRC = SQLITE_CORRUPT_BKPT; + goto ptrmap_exit; + } + offset = PTRMAP_PTROFFSET(iPtrmap, key); + if( offset<0 ){ + *pRC = SQLITE_CORRUPT_BKPT; + goto ptrmap_exit; + } + assert( offset <= (int)pBt->usableSize-5 ); + pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage); + + if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){ + TRACE(("PTRMAP_UPDATE: %u->(%u,%u)\n", key, eType, parent)); + *pRC= rc = sqlite3PagerWrite(pDbPage); + if( rc==SQLITE_OK ){ + pPtrmap[offset] = eType; + put4byte(&pPtrmap[offset+1], parent); + } + } + +ptrmap_exit: + sqlite3PagerUnref(pDbPage); +} + +/* +** Read an entry from the pointer map. +** +** This routine retrieves the pointer map entry for page 'key', writing +** the type and parent page number to *pEType and *pPgno respectively. +** An error code is returned if something goes wrong, otherwise SQLITE_OK. +*/ +static int ptrmapGet(BtShared *pBt, Pgno key, u8 *pEType, Pgno *pPgno){ + DbPage *pDbPage; /* The pointer map page */ + int iPtrmap; /* Pointer map page index */ + u8 *pPtrmap; /* Pointer map page data */ + int offset; /* Offset of entry in pointer map */ + int rc; + + assert( sqlite3_mutex_held(pBt->mutex) ); + + iPtrmap = PTRMAP_PAGENO(pBt, key); + rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage, 0); + if( rc!=0 ){ + return rc; + } + pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage); + + offset = PTRMAP_PTROFFSET(iPtrmap, key); + if( offset<0 ){ + sqlite3PagerUnref(pDbPage); + return SQLITE_CORRUPT_BKPT; + } + assert( offset <= (int)pBt->usableSize-5 ); + assert( pEType!=0 ); + *pEType = pPtrmap[offset]; + if( pPgno ) *pPgno = get4byte(&pPtrmap[offset+1]); + + sqlite3PagerUnref(pDbPage); + if( *pEType<1 || *pEType>5 ) return SQLITE_CORRUPT_PGNO(iPtrmap); + return SQLITE_OK; +} + +#else /* if defined SQLITE_OMIT_AUTOVACUUM */ + #define ptrmapPut(w,x,y,z,rc) + #define ptrmapGet(w,x,y,z) SQLITE_OK + #define ptrmapPutOvflPtr(x, y, z, rc) +#endif + +/* +** Given a btree page and a cell index (0 means the first cell on +** the page, 1 means the second cell, and so forth) return a pointer +** to the cell content. +** +** findCellPastPtr() does the same except it skips past the initial +** 4-byte child pointer found on interior pages, if there is one. +** +** This routine works only for pages that do not contain overflow cells. +*/ +#define findCell(P,I) \ + ((P)->aData + ((P)->maskPage & get2byteAligned(&(P)->aCellIdx[2*(I)]))) +#define findCellPastPtr(P,I) \ + ((P)->aDataOfst + ((P)->maskPage & get2byteAligned(&(P)->aCellIdx[2*(I)]))) + + +/* +** This is common tail processing for btreeParseCellPtr() and +** btreeParseCellPtrIndex() for the case when the cell does not fit entirely +** on a single B-tree page. Make necessary adjustments to the CellInfo +** structure. +*/ +static SQLITE_NOINLINE void btreeParseCellAdjustSizeForOverflow( + MemPage *pPage, /* Page containing the cell */ + u8 *pCell, /* Pointer to the cell text. */ + CellInfo *pInfo /* Fill in this structure */ +){ + /* If the payload will not fit completely on the local page, we have + ** to decide how much to store locally and how much to spill onto + ** overflow pages. The strategy is to minimize the amount of unused + ** space on overflow pages while keeping the amount of local storage + ** in between minLocal and maxLocal. + ** + ** Warning: changing the way overflow payload is distributed in any + ** way will result in an incompatible file format. + */ + int minLocal; /* Minimum amount of payload held locally */ + int maxLocal; /* Maximum amount of payload held locally */ + int surplus; /* Overflow payload available for local storage */ + + minLocal = pPage->minLocal; + maxLocal = pPage->maxLocal; + surplus = minLocal + (pInfo->nPayload - minLocal)%(pPage->pBt->usableSize-4); + testcase( surplus==maxLocal ); + testcase( surplus==maxLocal+1 ); + if( surplus <= maxLocal ){ + pInfo->nLocal = (u16)surplus; + }else{ + pInfo->nLocal = (u16)minLocal; + } + pInfo->nSize = (u16)(&pInfo->pPayload[pInfo->nLocal] - pCell) + 4; +} + +/* +** Given a record with nPayload bytes of payload stored within btree +** page pPage, return the number of bytes of payload stored locally. +*/ +static int btreePayloadToLocal(MemPage *pPage, i64 nPayload){ + int maxLocal; /* Maximum amount of payload held locally */ + maxLocal = pPage->maxLocal; + if( nPayload<=maxLocal ){ + return nPayload; + }else{ + int minLocal; /* Minimum amount of payload held locally */ + int surplus; /* Overflow payload available for local storage */ + minLocal = pPage->minLocal; + surplus = minLocal + (nPayload - minLocal)%(pPage->pBt->usableSize-4); + return ( surplus <= maxLocal ) ? surplus : minLocal; + } +} + +/* +** The following routines are implementations of the MemPage.xParseCell() +** method. +** +** Parse a cell content block and fill in the CellInfo structure. +** +** btreeParseCellPtr() => table btree leaf nodes +** btreeParseCellNoPayload() => table btree internal nodes +** btreeParseCellPtrIndex() => index btree nodes +** +** There is also a wrapper function btreeParseCell() that works for +** all MemPage types and that references the cell by index rather than +** by pointer. +*/ +static void btreeParseCellPtrNoPayload( + MemPage *pPage, /* Page containing the cell */ + u8 *pCell, /* Pointer to the cell text. */ + CellInfo *pInfo /* Fill in this structure */ +){ + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( pPage->leaf==0 ); + assert( pPage->childPtrSize==4 ); +#ifndef SQLITE_DEBUG + UNUSED_PARAMETER(pPage); +#endif + pInfo->nSize = 4 + getVarint(&pCell[4], (u64*)&pInfo->nKey); + pInfo->nPayload = 0; + pInfo->nLocal = 0; + pInfo->pPayload = 0; + return; +} +static void btreeParseCellPtr( + MemPage *pPage, /* Page containing the cell */ + u8 *pCell, /* Pointer to the cell text. */ + CellInfo *pInfo /* Fill in this structure */ +){ + u8 *pIter; /* For scanning through pCell */ + u32 nPayload; /* Number of bytes of cell payload */ + u64 iKey; /* Extracted Key value */ + + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( pPage->leaf==0 || pPage->leaf==1 ); + assert( pPage->intKeyLeaf ); + assert( pPage->childPtrSize==0 ); + pIter = pCell; + + /* The next block of code is equivalent to: + ** + ** pIter += getVarint32(pIter, nPayload); + ** + ** The code is inlined to avoid a function call. + */ + nPayload = *pIter; + if( nPayload>=0x80 ){ + u8 *pEnd = &pIter[8]; + nPayload &= 0x7f; + do{ + nPayload = (nPayload<<7) | (*++pIter & 0x7f); + }while( (*pIter)>=0x80 && pIter<pEnd ); + } + pIter++; + + /* The next block of code is equivalent to: + ** + ** pIter += getVarint(pIter, (u64*)&pInfo->nKey); + ** + ** The code is inlined and the loop is unrolled for performance. + ** This routine is a high-runner. + */ + iKey = *pIter; + if( iKey>=0x80 ){ + u8 x; + iKey = (iKey<<7) ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ 0x10204000 ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<8) ^ 0x8000 ^ (*++pIter); + } + } + } + } + } + }else{ + iKey ^= 0x204000; + } + }else{ + iKey ^= 0x4000; + } + } + pIter++; + + pInfo->nKey = *(i64*)&iKey; + pInfo->nPayload = nPayload; + pInfo->pPayload = pIter; + testcase( nPayload==pPage->maxLocal ); + testcase( nPayload==(u32)pPage->maxLocal+1 ); + if( nPayload<=pPage->maxLocal ){ + /* This is the (easy) common case where the entire payload fits + ** on the local page. No overflow is required. + */ + pInfo->nSize = nPayload + (u16)(pIter - pCell); + if( pInfo->nSize<4 ) pInfo->nSize = 4; + pInfo->nLocal = (u16)nPayload; + }else{ + btreeParseCellAdjustSizeForOverflow(pPage, pCell, pInfo); + } +} +static void btreeParseCellPtrIndex( + MemPage *pPage, /* Page containing the cell */ + u8 *pCell, /* Pointer to the cell text. */ + CellInfo *pInfo /* Fill in this structure */ +){ + u8 *pIter; /* For scanning through pCell */ + u32 nPayload; /* Number of bytes of cell payload */ + + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( pPage->leaf==0 || pPage->leaf==1 ); + assert( pPage->intKeyLeaf==0 ); + pIter = pCell + pPage->childPtrSize; + nPayload = *pIter; + if( nPayload>=0x80 ){ + u8 *pEnd = &pIter[8]; + nPayload &= 0x7f; + do{ + nPayload = (nPayload<<7) | (*++pIter & 0x7f); + }while( *(pIter)>=0x80 && pIter<pEnd ); + } + pIter++; + pInfo->nKey = nPayload; + pInfo->nPayload = nPayload; + pInfo->pPayload = pIter; + testcase( nPayload==pPage->maxLocal ); + testcase( nPayload==(u32)pPage->maxLocal+1 ); + if( nPayload<=pPage->maxLocal ){ + /* This is the (easy) common case where the entire payload fits + ** on the local page. No overflow is required. + */ + pInfo->nSize = nPayload + (u16)(pIter - pCell); + if( pInfo->nSize<4 ) pInfo->nSize = 4; + pInfo->nLocal = (u16)nPayload; + }else{ + btreeParseCellAdjustSizeForOverflow(pPage, pCell, pInfo); + } +} +static void btreeParseCell( + MemPage *pPage, /* Page containing the cell */ + int iCell, /* The cell index. First cell is 0 */ + CellInfo *pInfo /* Fill in this structure */ +){ + pPage->xParseCell(pPage, findCell(pPage, iCell), pInfo); +} + +/* +** The following routines are implementations of the MemPage.xCellSize +** method. +** +** Compute the total number of bytes that a Cell needs in the cell +** data area of the btree-page. The return number includes the cell +** data header and the local payload, but not any overflow page or +** the space used by the cell pointer. +** +** cellSizePtrNoPayload() => table internal nodes +** cellSizePtrTableLeaf() => table leaf nodes +** cellSizePtr() => index internal nodes +** cellSizeIdxLeaf() => index leaf nodes +*/ +static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ + u8 *pIter = pCell + 4; /* For looping over bytes of pCell */ + u8 *pEnd; /* End mark for a varint */ + u32 nSize; /* Size value to return */ + +#ifdef SQLITE_DEBUG + /* The value returned by this function should always be the same as + ** the (CellInfo.nSize) value found by doing a full parse of the + ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of + ** this function verifies that this invariant is not violated. */ + CellInfo debuginfo; + pPage->xParseCell(pPage, pCell, &debuginfo); +#endif + + assert( pPage->childPtrSize==4 ); + nSize = *pIter; + if( nSize>=0x80 ){ + pEnd = &pIter[8]; + nSize &= 0x7f; + do{ + nSize = (nSize<<7) | (*++pIter & 0x7f); + }while( *(pIter)>=0x80 && pIter<pEnd ); + } + pIter++; + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize<=pPage->maxLocal ){ + nSize += (u32)(pIter - pCell); + assert( nSize>4 ); + }else{ + int minLocal = pPage->minLocal; + nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize>pPage->maxLocal ){ + nSize = minLocal; + } + nSize += 4 + (u16)(pIter - pCell); + } + assert( nSize==debuginfo.nSize || CORRUPT_DB ); + return (u16)nSize; +} +static u16 cellSizePtrIdxLeaf(MemPage *pPage, u8 *pCell){ + u8 *pIter = pCell; /* For looping over bytes of pCell */ + u8 *pEnd; /* End mark for a varint */ + u32 nSize; /* Size value to return */ + +#ifdef SQLITE_DEBUG + /* The value returned by this function should always be the same as + ** the (CellInfo.nSize) value found by doing a full parse of the + ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of + ** this function verifies that this invariant is not violated. */ + CellInfo debuginfo; + pPage->xParseCell(pPage, pCell, &debuginfo); +#endif + + assert( pPage->childPtrSize==0 ); + nSize = *pIter; + if( nSize>=0x80 ){ + pEnd = &pIter[8]; + nSize &= 0x7f; + do{ + nSize = (nSize<<7) | (*++pIter & 0x7f); + }while( *(pIter)>=0x80 && pIter<pEnd ); + } + pIter++; + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize<=pPage->maxLocal ){ + nSize += (u32)(pIter - pCell); + if( nSize<4 ) nSize = 4; + }else{ + int minLocal = pPage->minLocal; + nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize>pPage->maxLocal ){ + nSize = minLocal; + } + nSize += 4 + (u16)(pIter - pCell); + } + assert( nSize==debuginfo.nSize || CORRUPT_DB ); + return (u16)nSize; +} +static u16 cellSizePtrNoPayload(MemPage *pPage, u8 *pCell){ + u8 *pIter = pCell + 4; /* For looping over bytes of pCell */ + u8 *pEnd; /* End mark for a varint */ + +#ifdef SQLITE_DEBUG + /* The value returned by this function should always be the same as + ** the (CellInfo.nSize) value found by doing a full parse of the + ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of + ** this function verifies that this invariant is not violated. */ + CellInfo debuginfo; + pPage->xParseCell(pPage, pCell, &debuginfo); +#else + UNUSED_PARAMETER(pPage); +#endif + + assert( pPage->childPtrSize==4 ); + pEnd = pIter + 9; + while( (*pIter++)&0x80 && pIter<pEnd ); + assert( debuginfo.nSize==(u16)(pIter - pCell) || CORRUPT_DB ); + return (u16)(pIter - pCell); +} +static u16 cellSizePtrTableLeaf(MemPage *pPage, u8 *pCell){ + u8 *pIter = pCell; /* For looping over bytes of pCell */ + u8 *pEnd; /* End mark for a varint */ + u32 nSize; /* Size value to return */ + +#ifdef SQLITE_DEBUG + /* The value returned by this function should always be the same as + ** the (CellInfo.nSize) value found by doing a full parse of the + ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of + ** this function verifies that this invariant is not violated. */ + CellInfo debuginfo; + pPage->xParseCell(pPage, pCell, &debuginfo); +#endif + + nSize = *pIter; + if( nSize>=0x80 ){ + pEnd = &pIter[8]; + nSize &= 0x7f; + do{ + nSize = (nSize<<7) | (*++pIter & 0x7f); + }while( *(pIter)>=0x80 && pIter<pEnd ); + } + pIter++; + /* pIter now points at the 64-bit integer key value, a variable length + ** integer. The following block moves pIter to point at the first byte + ** past the end of the key value. */ + if( (*pIter++)&0x80 + && (*pIter++)&0x80 + && (*pIter++)&0x80 + && (*pIter++)&0x80 + && (*pIter++)&0x80 + && (*pIter++)&0x80 + && (*pIter++)&0x80 + && (*pIter++)&0x80 ){ pIter++; } + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize<=pPage->maxLocal ){ + nSize += (u32)(pIter - pCell); + if( nSize<4 ) nSize = 4; + }else{ + int minLocal = pPage->minLocal; + nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize>pPage->maxLocal ){ + nSize = minLocal; + } + nSize += 4 + (u16)(pIter - pCell); + } + assert( nSize==debuginfo.nSize || CORRUPT_DB ); + return (u16)nSize; +} + + +#ifdef SQLITE_DEBUG +/* This variation on cellSizePtr() is used inside of assert() statements +** only. */ +static u16 cellSize(MemPage *pPage, int iCell){ + return pPage->xCellSize(pPage, findCell(pPage, iCell)); +} +#endif + +#ifndef SQLITE_OMIT_AUTOVACUUM +/* +** The cell pCell is currently part of page pSrc but will ultimately be part +** of pPage. (pSrc and pPage are often the same.) If pCell contains a +** pointer to an overflow page, insert an entry into the pointer-map for +** the overflow page that will be valid after pCell has been moved to pPage. +*/ +static void ptrmapPutOvflPtr(MemPage *pPage, MemPage *pSrc, u8 *pCell,int *pRC){ + CellInfo info; + if( *pRC ) return; + assert( pCell!=0 ); + pPage->xParseCell(pPage, pCell, &info); + if( info.nLocal<info.nPayload ){ + Pgno ovfl; + if( SQLITE_OVERFLOW(pSrc->aDataEnd, pCell, pCell+info.nLocal) ){ + testcase( pSrc!=pPage ); + *pRC = SQLITE_CORRUPT_BKPT; + return; + } + ovfl = get4byte(&pCell[info.nSize-4]); + ptrmapPut(pPage->pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, pRC); + } +} +#endif + + +/* +** Defragment the page given. This routine reorganizes cells within the +** page so that there are no free-blocks on the free-block list. +** +** Parameter nMaxFrag is the maximum amount of fragmented space that may be +** present in the page after this routine returns. +** +** EVIDENCE-OF: R-44582-60138 SQLite may from time to time reorganize a +** b-tree page so that there are no freeblocks or fragment bytes, all +** unused bytes are contained in the unallocated space region, and all +** cells are packed tightly at the end of the page. +*/ +static int defragmentPage(MemPage *pPage, int nMaxFrag){ + int i; /* Loop counter */ + int pc; /* Address of the i-th cell */ + int hdr; /* Offset to the page header */ + int size; /* Size of a cell */ + int usableSize; /* Number of usable bytes on a page */ + int cellOffset; /* Offset to the cell pointer array */ + int cbrk; /* Offset to the cell content area */ + int nCell; /* Number of cells on the page */ + unsigned char *data; /* The page data */ + unsigned char *temp; /* Temp area for cell content */ + unsigned char *src; /* Source of content */ + int iCellFirst; /* First allowable cell index */ + int iCellLast; /* Last possible cell index */ + int iCellStart; /* First cell offset in input */ + + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + assert( pPage->pBt!=0 ); + assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE ); + assert( pPage->nOverflow==0 ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + data = pPage->aData; + hdr = pPage->hdrOffset; + cellOffset = pPage->cellOffset; + nCell = pPage->nCell; + assert( nCell==get2byte(&data[hdr+3]) || CORRUPT_DB ); + iCellFirst = cellOffset + 2*nCell; + usableSize = pPage->pBt->usableSize; + + /* This block handles pages with two or fewer free blocks and nMaxFrag + ** or fewer fragmented bytes. In this case it is faster to move the + ** two (or one) blocks of cells using memmove() and add the required + ** offsets to each pointer in the cell-pointer array than it is to + ** reconstruct the entire page. */ + if( (int)data[hdr+7]<=nMaxFrag ){ + int iFree = get2byte(&data[hdr+1]); + if( iFree>usableSize-4 ) return SQLITE_CORRUPT_PAGE(pPage); + if( iFree ){ + int iFree2 = get2byte(&data[iFree]); + if( iFree2>usableSize-4 ) return SQLITE_CORRUPT_PAGE(pPage); + if( 0==iFree2 || (data[iFree2]==0 && data[iFree2+1]==0) ){ + u8 *pEnd = &data[cellOffset + nCell*2]; + u8 *pAddr; + int sz2 = 0; + int sz = get2byte(&data[iFree+2]); + int top = get2byte(&data[hdr+5]); + if( top>=iFree ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + if( iFree2 ){ + if( iFree+sz>iFree2 ) return SQLITE_CORRUPT_PAGE(pPage); + sz2 = get2byte(&data[iFree2+2]); + if( iFree2+sz2 > usableSize ) return SQLITE_CORRUPT_PAGE(pPage); + memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz)); + sz += sz2; + }else if( iFree+sz>usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + + cbrk = top+sz; + assert( cbrk+(iFree-top) <= usableSize ); + memmove(&data[cbrk], &data[top], iFree-top); + for(pAddr=&data[cellOffset]; pAddr<pEnd; pAddr+=2){ + pc = get2byte(pAddr); + if( pc<iFree ){ put2byte(pAddr, pc+sz); } + else if( pc<iFree2 ){ put2byte(pAddr, pc+sz2); } + } + goto defragment_out; + } + } + } + + cbrk = usableSize; + iCellLast = usableSize - 4; + iCellStart = get2byte(&data[hdr+5]); + if( nCell>0 ){ + temp = sqlite3PagerTempSpace(pPage->pBt->pPager); + memcpy(temp, data, usableSize); + src = temp; + for(i=0; i<nCell; i++){ + u8 *pAddr; /* The i-th cell pointer */ + pAddr = &data[cellOffset + i*2]; + pc = get2byte(pAddr); + testcase( pc==iCellFirst ); + testcase( pc==iCellLast ); + /* These conditions have already been verified in btreeInitPage() + ** if PRAGMA cell_size_check=ON. + */ + if( pc>iCellLast ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + assert( pc>=0 && pc<=iCellLast ); + size = pPage->xCellSize(pPage, &src[pc]); + cbrk -= size; + if( cbrk<iCellStart || pc+size>usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + assert( cbrk+size<=usableSize && cbrk>=iCellStart ); + testcase( cbrk+size==usableSize ); + testcase( pc+size==usableSize ); + put2byte(pAddr, cbrk); + memcpy(&data[cbrk], &src[pc], size); + } + } + data[hdr+7] = 0; + +defragment_out: + assert( pPage->nFree>=0 ); + if( data[hdr+7]+cbrk-iCellFirst!=pPage->nFree ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + assert( cbrk>=iCellFirst ); + put2byte(&data[hdr+5], cbrk); + data[hdr+1] = 0; + data[hdr+2] = 0; + memset(&data[iCellFirst], 0, cbrk-iCellFirst); + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + return SQLITE_OK; +} + +/* +** Search the free-list on page pPg for space to store a cell nByte bytes in +** size. If one can be found, return a pointer to the space and remove it +** from the free-list. +** +** If no suitable space can be found on the free-list, return NULL. +** +** This function may detect corruption within pPg. If corruption is +** detected then *pRc is set to SQLITE_CORRUPT and NULL is returned. +** +** Slots on the free list that are between 1 and 3 bytes larger than nByte +** will be ignored if adding the extra space to the fragmentation count +** causes the fragmentation count to exceed 60. +*/ +static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ + const int hdr = pPg->hdrOffset; /* Offset to page header */ + u8 * const aData = pPg->aData; /* Page data */ + int iAddr = hdr + 1; /* Address of ptr to pc */ + u8 *pTmp = &aData[iAddr]; /* Temporary ptr into aData[] */ + int pc = get2byte(pTmp); /* Address of a free slot */ + int x; /* Excess size of the slot */ + int maxPC = pPg->pBt->usableSize - nByte; /* Max address for a usable slot */ + int size; /* Size of the free slot */ + + assert( pc>0 ); + while( pc<=maxPC ){ + /* EVIDENCE-OF: R-22710-53328 The third and fourth bytes of each + ** freeblock form a big-endian integer which is the size of the freeblock + ** in bytes, including the 4-byte header. */ + pTmp = &aData[pc+2]; + size = get2byte(pTmp); + if( (x = size - nByte)>=0 ){ + testcase( x==4 ); + testcase( x==3 ); + if( x<4 ){ + /* EVIDENCE-OF: R-11498-58022 In a well-formed b-tree page, the total + ** number of bytes in fragments may not exceed 60. */ + if( aData[hdr+7]>57 ) return 0; + + /* Remove the slot from the free-list. Update the number of + ** fragmented bytes within the page. */ + memcpy(&aData[iAddr], &aData[pc], 2); + aData[hdr+7] += (u8)x; + return &aData[pc]; + }else if( x+pc > maxPC ){ + /* This slot extends off the end of the usable part of the page */ + *pRc = SQLITE_CORRUPT_PAGE(pPg); + return 0; + }else{ + /* The slot remains on the free-list. Reduce its size to account + ** for the portion used by the new allocation. */ + put2byte(&aData[pc+2], x); + } + return &aData[pc + x]; + } + iAddr = pc; + pTmp = &aData[pc]; + pc = get2byte(pTmp); + if( pc<=iAddr ){ + if( pc ){ + /* The next slot in the chain comes before the current slot */ + *pRc = SQLITE_CORRUPT_PAGE(pPg); + } + return 0; + } + } + if( pc>maxPC+nByte-4 ){ + /* The free slot chain extends off the end of the page */ + *pRc = SQLITE_CORRUPT_PAGE(pPg); + } + return 0; +} + +/* +** Allocate nByte bytes of space from within the B-Tree page passed +** as the first argument. Write into *pIdx the index into pPage->aData[] +** of the first byte of allocated space. Return either SQLITE_OK or +** an error code (usually SQLITE_CORRUPT). +** +** The caller guarantees that there is sufficient space to make the +** allocation. This routine might need to defragment in order to bring +** all the space together, however. This routine will avoid using +** the first two bytes past the cell pointer area since presumably this +** allocation is being made in order to insert a new cell, so we will +** also end up needing a new cell pointer. +*/ +static SQLITE_INLINE int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ + const int hdr = pPage->hdrOffset; /* Local cache of pPage->hdrOffset */ + u8 * const data = pPage->aData; /* Local cache of pPage->aData */ + int top; /* First byte of cell content area */ + int rc = SQLITE_OK; /* Integer return code */ + u8 *pTmp; /* Temp ptr into data[] */ + int gap; /* First byte of gap between cell pointers and cell content */ + + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + assert( pPage->pBt ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( nByte>=0 ); /* Minimum cell size is 4 */ + assert( pPage->nFree>=nByte ); + assert( pPage->nOverflow==0 ); + assert( nByte < (int)(pPage->pBt->usableSize-8) ); + + assert( pPage->cellOffset == hdr + 12 - 4*pPage->leaf ); + gap = pPage->cellOffset + 2*pPage->nCell; + assert( gap<=65536 ); + /* EVIDENCE-OF: R-29356-02391 If the database uses a 65536-byte page size + ** and the reserved space is zero (the usual value for reserved space) + ** then the cell content offset of an empty page wants to be 65536. + ** However, that integer is too large to be stored in a 2-byte unsigned + ** integer, so a value of 0 is used in its place. */ + pTmp = &data[hdr+5]; + top = get2byte(pTmp); + if( gap>top ){ + if( top==0 && pPage->pBt->usableSize==65536 ){ + top = 65536; + }else{ + return SQLITE_CORRUPT_PAGE(pPage); + } + }else if( top>(int)pPage->pBt->usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + + /* If there is enough space between gap and top for one more cell pointer, + ** and if the freelist is not empty, then search the + ** freelist looking for a slot big enough to satisfy the request. + */ + testcase( gap+2==top ); + testcase( gap+1==top ); + testcase( gap==top ); + if( (data[hdr+2] || data[hdr+1]) && gap+2<=top ){ + u8 *pSpace = pageFindSlot(pPage, nByte, &rc); + if( pSpace ){ + int g2; + assert( pSpace+nByte<=data+pPage->pBt->usableSize ); + *pIdx = g2 = (int)(pSpace-data); + if( g2<=gap ){ + return SQLITE_CORRUPT_PAGE(pPage); + }else{ + return SQLITE_OK; + } + }else if( rc ){ + return rc; + } + } + + /* The request could not be fulfilled using a freelist slot. Check + ** to see if defragmentation is necessary. + */ + testcase( gap+2+nByte==top ); + if( gap+2+nByte>top ){ + assert( pPage->nCell>0 || CORRUPT_DB ); + assert( pPage->nFree>=0 ); + rc = defragmentPage(pPage, MIN(4, pPage->nFree - (2+nByte))); + if( rc ) return rc; + top = get2byteNotZero(&data[hdr+5]); + assert( gap+2+nByte<=top ); + } + + + /* Allocate memory from the gap in between the cell pointer array + ** and the cell content area. The btreeComputeFreeSpace() call has already + ** validated the freelist. Given that the freelist is valid, there + ** is no way that the allocation can extend off the end of the page. + ** The assert() below verifies the previous sentence. + */ + top -= nByte; + put2byte(&data[hdr+5], top); + assert( top+nByte <= (int)pPage->pBt->usableSize ); + *pIdx = top; + return SQLITE_OK; +} + +/* +** Return a section of the pPage->aData to the freelist. +** The first byte of the new free block is pPage->aData[iStart] +** and the size of the block is iSize bytes. +** +** Adjacent freeblocks are coalesced. +** +** Even though the freeblock list was checked by btreeComputeFreeSpace(), +** that routine will not detect overlap between cells or freeblocks. Nor +** does it detect cells or freeblocks that encroach into the reserved bytes +** at the end of the page. So do additional corruption checks inside this +** routine and return SQLITE_CORRUPT if any problems are found. +*/ +static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ + u16 iPtr; /* Address of ptr to next freeblock */ + u16 iFreeBlk; /* Address of the next freeblock */ + u8 hdr; /* Page header size. 0 or 100 */ + u8 nFrag = 0; /* Reduction in fragmentation */ + u16 iOrigSize = iSize; /* Original value of iSize */ + u16 x; /* Offset to cell content area */ + u32 iEnd = iStart + iSize; /* First byte past the iStart buffer */ + unsigned char *data = pPage->aData; /* Page content */ + u8 *pTmp; /* Temporary ptr into data[] */ + + assert( pPage->pBt!=0 ); + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + assert( CORRUPT_DB || iStart>=pPage->hdrOffset+6+pPage->childPtrSize ); + assert( CORRUPT_DB || iEnd <= pPage->pBt->usableSize ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( iSize>=4 ); /* Minimum cell size is 4 */ + assert( CORRUPT_DB || iStart<=pPage->pBt->usableSize-4 ); + + /* The list of freeblocks must be in ascending order. Find the + ** spot on the list where iStart should be inserted. + */ + hdr = pPage->hdrOffset; + iPtr = hdr + 1; + if( data[iPtr+1]==0 && data[iPtr]==0 ){ + iFreeBlk = 0; /* Shortcut for the case when the freelist is empty */ + }else{ + while( (iFreeBlk = get2byte(&data[iPtr]))<iStart ){ + if( iFreeBlk<=iPtr ){ + if( iFreeBlk==0 ) break; /* TH3: corrupt082.100 */ + return SQLITE_CORRUPT_PAGE(pPage); + } + iPtr = iFreeBlk; + } + if( iFreeBlk>pPage->pBt->usableSize-4 ){ /* TH3: corrupt081.100 */ + return SQLITE_CORRUPT_PAGE(pPage); + } + assert( iFreeBlk>iPtr || iFreeBlk==0 || CORRUPT_DB ); + + /* At this point: + ** iFreeBlk: First freeblock after iStart, or zero if none + ** iPtr: The address of a pointer to iFreeBlk + ** + ** Check to see if iFreeBlk should be coalesced onto the end of iStart. + */ + if( iFreeBlk && iEnd+3>=iFreeBlk ){ + nFrag = iFreeBlk - iEnd; + if( iEnd>iFreeBlk ) return SQLITE_CORRUPT_PAGE(pPage); + iEnd = iFreeBlk + get2byte(&data[iFreeBlk+2]); + if( iEnd > pPage->pBt->usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + iSize = iEnd - iStart; + iFreeBlk = get2byte(&data[iFreeBlk]); + } + + /* If iPtr is another freeblock (that is, if iPtr is not the freelist + ** pointer in the page header) then check to see if iStart should be + ** coalesced onto the end of iPtr. + */ + if( iPtr>hdr+1 ){ + int iPtrEnd = iPtr + get2byte(&data[iPtr+2]); + if( iPtrEnd+3>=iStart ){ + if( iPtrEnd>iStart ) return SQLITE_CORRUPT_PAGE(pPage); + nFrag += iStart - iPtrEnd; + iSize = iEnd - iPtr; + iStart = iPtr; + } + } + if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_PAGE(pPage); + data[hdr+7] -= nFrag; + } + pTmp = &data[hdr+5]; + x = get2byte(pTmp); + if( pPage->pBt->btsFlags & BTS_FAST_SECURE ){ + /* Overwrite deleted information with zeros when the secure_delete + ** option is enabled */ + memset(&data[iStart], 0, iSize); + } + if( iStart<=x ){ + /* The new freeblock is at the beginning of the cell content area, + ** so just extend the cell content area rather than create another + ** freelist entry */ + if( iStart<x ) return SQLITE_CORRUPT_PAGE(pPage); + if( iPtr!=hdr+1 ) return SQLITE_CORRUPT_PAGE(pPage); + put2byte(&data[hdr+1], iFreeBlk); + put2byte(&data[hdr+5], iEnd); + }else{ + /* Insert the new freeblock into the freelist */ + put2byte(&data[iPtr], iStart); + put2byte(&data[iStart], iFreeBlk); + put2byte(&data[iStart+2], iSize); + } + pPage->nFree += iOrigSize; + return SQLITE_OK; +} + +/* +** Decode the flags byte (the first byte of the header) for a page +** and initialize fields of the MemPage structure accordingly. +** +** Only the following combinations are supported. Anything different +** indicates a corrupt database files: +** +** PTF_ZERODATA (0x02, 2) +** PTF_LEAFDATA | PTF_INTKEY (0x05, 5) +** PTF_ZERODATA | PTF_LEAF (0x0a, 10) +** PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF (0x0d, 13) +*/ +static int decodeFlags(MemPage *pPage, int flagByte){ + BtShared *pBt; /* A copy of pPage->pBt */ + + assert( pPage->hdrOffset==(pPage->pgno==1 ? 100 : 0) ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + pBt = pPage->pBt; + pPage->max1bytePayload = pBt->max1bytePayload; + if( flagByte>=(PTF_ZERODATA | PTF_LEAF) ){ + pPage->childPtrSize = 0; + pPage->leaf = 1; + if( flagByte==(PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF) ){ + pPage->intKeyLeaf = 1; + pPage->xCellSize = cellSizePtrTableLeaf; + pPage->xParseCell = btreeParseCellPtr; + pPage->intKey = 1; + pPage->maxLocal = pBt->maxLeaf; + pPage->minLocal = pBt->minLeaf; + }else if( flagByte==(PTF_ZERODATA | PTF_LEAF) ){ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtrIdxLeaf; + pPage->xParseCell = btreeParseCellPtrIndex; + pPage->maxLocal = pBt->maxLocal; + pPage->minLocal = pBt->minLocal; + }else{ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtrIdxLeaf; + pPage->xParseCell = btreeParseCellPtrIndex; + return SQLITE_CORRUPT_PAGE(pPage); + } + }else{ + pPage->childPtrSize = 4; + pPage->leaf = 0; + if( flagByte==(PTF_ZERODATA) ){ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; + pPage->xParseCell = btreeParseCellPtrIndex; + pPage->maxLocal = pBt->maxLocal; + pPage->minLocal = pBt->minLocal; + }else if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtrNoPayload; + pPage->xParseCell = btreeParseCellPtrNoPayload; + pPage->intKey = 1; + pPage->maxLocal = pBt->maxLeaf; + pPage->minLocal = pBt->minLeaf; + }else{ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; + pPage->xParseCell = btreeParseCellPtrIndex; + return SQLITE_CORRUPT_PAGE(pPage); + } + } + return SQLITE_OK; +} + +/* +** Compute the amount of freespace on the page. In other words, fill +** in the pPage->nFree field. +*/ +static int btreeComputeFreeSpace(MemPage *pPage){ + int pc; /* Address of a freeblock within pPage->aData[] */ + u8 hdr; /* Offset to beginning of page header */ + u8 *data; /* Equal to pPage->aData */ + int usableSize; /* Amount of usable space on each page */ + int nFree; /* Number of unused bytes on the page */ + int top; /* First byte of the cell content area */ + int iCellFirst; /* First allowable cell or freeblock offset */ + int iCellLast; /* Last possible cell or freeblock offset */ + + assert( pPage->pBt!=0 ); + assert( pPage->pBt->db!=0 ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( pPage->pgno==sqlite3PagerPagenumber(pPage->pDbPage) ); + assert( pPage == sqlite3PagerGetExtra(pPage->pDbPage) ); + assert( pPage->aData == sqlite3PagerGetData(pPage->pDbPage) ); + assert( pPage->isInit==1 ); + assert( pPage->nFree<0 ); + + usableSize = pPage->pBt->usableSize; + hdr = pPage->hdrOffset; + data = pPage->aData; + /* EVIDENCE-OF: R-58015-48175 The two-byte integer at offset 5 designates + ** the start of the cell content area. A zero value for this integer is + ** interpreted as 65536. */ + top = get2byteNotZero(&data[hdr+5]); + iCellFirst = hdr + 8 + pPage->childPtrSize + 2*pPage->nCell; + iCellLast = usableSize - 4; + + /* Compute the total free space on the page + ** EVIDENCE-OF: R-23588-34450 The two-byte integer at offset 1 gives the + ** start of the first freeblock on the page, or is zero if there are no + ** freeblocks. */ + pc = get2byte(&data[hdr+1]); + nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */ + if( pc>0 ){ + u32 next, size; + if( pc<top ){ + /* EVIDENCE-OF: R-55530-52930 In a well-formed b-tree page, there will + ** always be at least one cell before the first freeblock. + */ + return SQLITE_CORRUPT_PAGE(pPage); + } + while( 1 ){ + if( pc>iCellLast ){ + /* Freeblock off the end of the page */ + return SQLITE_CORRUPT_PAGE(pPage); + } + next = get2byte(&data[pc]); + size = get2byte(&data[pc+2]); + nFree = nFree + size; + if( next<=pc+size+3 ) break; + pc = next; + } + if( next>0 ){ + /* Freeblock not in ascending order */ + return SQLITE_CORRUPT_PAGE(pPage); + } + if( pc+size>(unsigned int)usableSize ){ + /* Last freeblock extends past page end */ + return SQLITE_CORRUPT_PAGE(pPage); + } + } + + /* At this point, nFree contains the sum of the offset to the start + ** of the cell-content area plus the number of free bytes within + ** the cell-content area. If this is greater than the usable-size + ** of the page, then the page must be corrupted. This check also + ** serves to verify that the offset to the start of the cell-content + ** area, according to the page header, lies within the page. + */ + if( nFree>usableSize || nFree<iCellFirst ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + pPage->nFree = (u16)(nFree - iCellFirst); + return SQLITE_OK; +} + +/* +** Do additional sanity check after btreeInitPage() if +** PRAGMA cell_size_check=ON +*/ +static SQLITE_NOINLINE int btreeCellSizeCheck(MemPage *pPage){ + int iCellFirst; /* First allowable cell or freeblock offset */ + int iCellLast; /* Last possible cell or freeblock offset */ + int i; /* Index into the cell pointer array */ + int sz; /* Size of a cell */ + int pc; /* Address of a freeblock within pPage->aData[] */ + u8 *data; /* Equal to pPage->aData */ + int usableSize; /* Maximum usable space on the page */ + int cellOffset; /* Start of cell content area */ + + iCellFirst = pPage->cellOffset + 2*pPage->nCell; + usableSize = pPage->pBt->usableSize; + iCellLast = usableSize - 4; + data = pPage->aData; + cellOffset = pPage->cellOffset; + if( !pPage->leaf ) iCellLast--; + for(i=0; i<pPage->nCell; i++){ + pc = get2byteAligned(&data[cellOffset+i*2]); + testcase( pc==iCellFirst ); + testcase( pc==iCellLast ); + if( pc<iCellFirst || pc>iCellLast ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + sz = pPage->xCellSize(pPage, &data[pc]); + testcase( pc+sz==usableSize ); + if( pc+sz>usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + } + return SQLITE_OK; +} + +/* +** Initialize the auxiliary information for a disk block. +** +** Return SQLITE_OK on success. If we see that the page does +** not contain a well-formed database page, then return +** SQLITE_CORRUPT. Note that a return of SQLITE_OK does not +** guarantee that the page is well-formed. It only shows that +** we failed to detect any corruption. +*/ +static int btreeInitPage(MemPage *pPage){ + u8 *data; /* Equal to pPage->aData */ + BtShared *pBt; /* The main btree structure */ + + assert( pPage->pBt!=0 ); + assert( pPage->pBt->db!=0 ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( pPage->pgno==sqlite3PagerPagenumber(pPage->pDbPage) ); + assert( pPage == sqlite3PagerGetExtra(pPage->pDbPage) ); + assert( pPage->aData == sqlite3PagerGetData(pPage->pDbPage) ); + assert( pPage->isInit==0 ); + + pBt = pPage->pBt; + data = pPage->aData + pPage->hdrOffset; + /* EVIDENCE-OF: R-28594-02890 The one-byte flag at offset 0 indicating + ** the b-tree page type. */ + if( decodeFlags(pPage, data[0]) ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + assert( pBt->pageSize>=512 && pBt->pageSize<=65536 ); + pPage->maskPage = (u16)(pBt->pageSize - 1); + pPage->nOverflow = 0; + pPage->cellOffset = pPage->hdrOffset + 8 + pPage->childPtrSize; + pPage->aCellIdx = data + pPage->childPtrSize + 8; + pPage->aDataEnd = pPage->aData + pBt->pageSize; + pPage->aDataOfst = pPage->aData + pPage->childPtrSize; + /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the + ** number of cells on the page. */ + pPage->nCell = get2byte(&data[3]); + if( pPage->nCell>MX_CELL(pBt) ){ + /* To many cells for a single page. The page must be corrupt */ + return SQLITE_CORRUPT_PAGE(pPage); + } + testcase( pPage->nCell==MX_CELL(pBt) ); + /* EVIDENCE-OF: R-24089-57979 If a page contains no cells (which is only + ** possible for a root page of a table that contains no rows) then the + ** offset to the cell content area will equal the page size minus the + ** bytes of reserved space. */ + assert( pPage->nCell>0 + || get2byteNotZero(&data[5])==(int)pBt->usableSize + || CORRUPT_DB ); + pPage->nFree = -1; /* Indicate that this value is yet uncomputed */ + pPage->isInit = 1; + if( pBt->db->flags & SQLITE_CellSizeCk ){ + return btreeCellSizeCheck(pPage); + } + return SQLITE_OK; +} + +/* +** Set up a raw page so that it looks like a database page holding +** no entries. +*/ +static void zeroPage(MemPage *pPage, int flags){ + unsigned char *data = pPage->aData; + BtShared *pBt = pPage->pBt; + u8 hdr = pPage->hdrOffset; + u16 first; + + assert( sqlite3PagerPagenumber(pPage->pDbPage)==pPage->pgno || CORRUPT_DB ); + assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage ); + assert( sqlite3PagerGetData(pPage->pDbPage) == data ); + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + assert( sqlite3_mutex_held(pBt->mutex) ); + if( pBt->btsFlags & BTS_FAST_SECURE ){ + memset(&data[hdr], 0, pBt->usableSize - hdr); + } + data[hdr] = (char)flags; + first = hdr + ((flags&PTF_LEAF)==0 ? 12 : 8); + memset(&data[hdr+1], 0, 4); + data[hdr+7] = 0; + put2byte(&data[hdr+5], pBt->usableSize); + pPage->nFree = (u16)(pBt->usableSize - first); + decodeFlags(pPage, flags); + pPage->cellOffset = first; + pPage->aDataEnd = &data[pBt->pageSize]; + pPage->aCellIdx = &data[first]; + pPage->aDataOfst = &data[pPage->childPtrSize]; + pPage->nOverflow = 0; + assert( pBt->pageSize>=512 && pBt->pageSize<=65536 ); + pPage->maskPage = (u16)(pBt->pageSize - 1); + pPage->nCell = 0; + pPage->isInit = 1; +} + + +/* +** Convert a DbPage obtained from the pager into a MemPage used by +** the btree layer. +*/ +static MemPage *btreePageFromDbPage(DbPage *pDbPage, Pgno pgno, BtShared *pBt){ + MemPage *pPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); + if( pgno!=pPage->pgno ){ + pPage->aData = sqlite3PagerGetData(pDbPage); + pPage->pDbPage = pDbPage; + pPage->pBt = pBt; + pPage->pgno = pgno; + pPage->hdrOffset = pgno==1 ? 100 : 0; + } + assert( pPage->aData==sqlite3PagerGetData(pDbPage) ); + return pPage; +} + +/* +** Get a page from the pager. Initialize the MemPage.pBt and +** MemPage.aData elements if needed. See also: btreeGetUnusedPage(). +** +** If the PAGER_GET_NOCONTENT flag is set, it means that we do not care +** about the content of the page at this time. So do not go to the disk +** to fetch the content. Just fill in the content with zeros for now. +** If in the future we call sqlite3PagerWrite() on this page, that +** means we have started to be concerned about content and the disk +** read should occur at that point. +*/ +static int btreeGetPage( + BtShared *pBt, /* The btree */ + Pgno pgno, /* Number of the page to fetch */ + MemPage **ppPage, /* Return the page in this parameter */ + int flags /* PAGER_GET_NOCONTENT or PAGER_GET_READONLY */ +){ + int rc; + DbPage *pDbPage; + + assert( flags==0 || flags==PAGER_GET_NOCONTENT || flags==PAGER_GET_READONLY ); + assert( sqlite3_mutex_held(pBt->mutex) ); + rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, flags); + if( rc ) return rc; + *ppPage = btreePageFromDbPage(pDbPage, pgno, pBt); + return SQLITE_OK; +} + +/* +** Retrieve a page from the pager cache. If the requested page is not +** already in the pager cache return NULL. Initialize the MemPage.pBt and +** MemPage.aData elements if needed. +*/ +static MemPage *btreePageLookup(BtShared *pBt, Pgno pgno){ + DbPage *pDbPage; + assert( sqlite3_mutex_held(pBt->mutex) ); + pDbPage = sqlite3PagerLookup(pBt->pPager, pgno); + if( pDbPage ){ + return btreePageFromDbPage(pDbPage, pgno, pBt); + } + return 0; +} + +/* +** Return the size of the database file in pages. If there is any kind of +** error, return ((unsigned int)-1). +*/ +static Pgno btreePagecount(BtShared *pBt){ + return pBt->nPage; +} +SQLITE_PRIVATE Pgno sqlite3BtreeLastPage(Btree *p){ + assert( sqlite3BtreeHoldsMutex(p) ); + return btreePagecount(p->pBt); +} + +/* +** Get a page from the pager and initialize it. +*/ +static int getAndInitPage( + BtShared *pBt, /* The database file */ + Pgno pgno, /* Number of the page to get */ + MemPage **ppPage, /* Write the page pointer here */ + int bReadOnly /* True for a read-only page */ +){ + int rc; + DbPage *pDbPage; + MemPage *pPage; + assert( sqlite3_mutex_held(pBt->mutex) ); + + if( pgno>btreePagecount(pBt) ){ + *ppPage = 0; + return SQLITE_CORRUPT_BKPT; + } + rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, bReadOnly); + if( rc ){ + *ppPage = 0; + return rc; + } + pPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); + if( pPage->isInit==0 ){ + btreePageFromDbPage(pDbPage, pgno, pBt); + rc = btreeInitPage(pPage); + if( rc!=SQLITE_OK ){ + releasePage(pPage); + *ppPage = 0; + return rc; + } + } + assert( pPage->pgno==pgno || CORRUPT_DB ); + assert( pPage->aData==sqlite3PagerGetData(pDbPage) ); + *ppPage = pPage; + return SQLITE_OK; +} + +/* +** Release a MemPage. This should be called once for each prior +** call to btreeGetPage. +** +** Page1 is a special case and must be released using releasePageOne(). +*/ +static void releasePageNotNull(MemPage *pPage){ + assert( pPage->aData ); + assert( pPage->pBt ); + assert( pPage->pDbPage!=0 ); + assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage ); + assert( sqlite3PagerGetData(pPage->pDbPage)==pPage->aData ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + sqlite3PagerUnrefNotNull(pPage->pDbPage); +} +static void releasePage(MemPage *pPage){ + if( pPage ) releasePageNotNull(pPage); +} +static void releasePageOne(MemPage *pPage){ + assert( pPage!=0 ); + assert( pPage->aData ); + assert( pPage->pBt ); + assert( pPage->pDbPage!=0 ); + assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage ); + assert( sqlite3PagerGetData(pPage->pDbPage)==pPage->aData ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + sqlite3PagerUnrefPageOne(pPage->pDbPage); +} + +/* +** Get an unused page. +** +** This works just like btreeGetPage() with the addition: +** +** * If the page is already in use for some other purpose, immediately +** release it and return an SQLITE_CURRUPT error. +** * Make sure the isInit flag is clear +*/ +static int btreeGetUnusedPage( + BtShared *pBt, /* The btree */ + Pgno pgno, /* Number of the page to fetch */ + MemPage **ppPage, /* Return the page in this parameter */ + int flags /* PAGER_GET_NOCONTENT or PAGER_GET_READONLY */ +){ + int rc = btreeGetPage(pBt, pgno, ppPage, flags); + if( rc==SQLITE_OK ){ + if( sqlite3PagerPageRefcount((*ppPage)->pDbPage)>1 ){ + releasePage(*ppPage); + *ppPage = 0; + return SQLITE_CORRUPT_BKPT; + } + (*ppPage)->isInit = 0; + }else{ + *ppPage = 0; + } + return rc; +} + + +/* +** During a rollback, when the pager reloads information into the cache +** so that the cache is restored to its original state at the start of +** the transaction, for each page restored this routine is called. +** +** This routine needs to reset the extra data section at the end of the +** page to agree with the restored data. +*/ +static void pageReinit(DbPage *pData){ + MemPage *pPage; + pPage = (MemPage *)sqlite3PagerGetExtra(pData); + assert( sqlite3PagerPageRefcount(pData)>0 ); + if( pPage->isInit ){ + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + pPage->isInit = 0; + if( sqlite3PagerPageRefcount(pData)>1 ){ + /* pPage might not be a btree page; it might be an overflow page + ** or ptrmap page or a free page. In those cases, the following + ** call to btreeInitPage() will likely return SQLITE_CORRUPT. + ** But no harm is done by this. And it is very important that + ** btreeInitPage() be called on every btree page so we make + ** the call for every page that comes in for re-initializing. */ + btreeInitPage(pPage); + } + } +} + +/* +** Invoke the busy handler for a btree. +*/ +static int btreeInvokeBusyHandler(void *pArg){ + BtShared *pBt = (BtShared*)pArg; + assert( pBt->db ); + assert( sqlite3_mutex_held(pBt->db->mutex) ); + return sqlite3InvokeBusyHandler(&pBt->db->busyHandler); +} + +/* +** Open a database file. +** +** zFilename is the name of the database file. If zFilename is NULL +** then an ephemeral database is created. The ephemeral database might +** be exclusively in memory, or it might use a disk-based memory cache. +** Either way, the ephemeral database will be automatically deleted +** when sqlite3BtreeClose() is called. +** +** If zFilename is ":memory:" then an in-memory database is created +** that is automatically destroyed when it is closed. +** +** The "flags" parameter is a bitmask that might contain bits like +** BTREE_OMIT_JOURNAL and/or BTREE_MEMORY. +** +** If the database is already opened in the same database connection +** and we are in shared cache mode, then the open will fail with an +** SQLITE_CONSTRAINT error. We cannot allow two or more BtShared +** objects in the same database connection since doing so will lead +** to problems with locking. +*/ +SQLITE_PRIVATE int sqlite3BtreeOpen( + sqlite3_vfs *pVfs, /* VFS to use for this b-tree */ + const char *zFilename, /* Name of the file containing the BTree database */ + sqlite3 *db, /* Associated database handle */ + Btree **ppBtree, /* Pointer to new Btree object written here */ + int flags, /* Options */ + int vfsFlags /* Flags passed through to sqlite3_vfs.xOpen() */ +){ + BtShared *pBt = 0; /* Shared part of btree structure */ + Btree *p; /* Handle to return */ + sqlite3_mutex *mutexOpen = 0; /* Prevents a race condition. Ticket #3537 */ + int rc = SQLITE_OK; /* Result code from this function */ + u8 nReserve; /* Byte of unused space on each page */ + unsigned char zDbHeader[100]; /* Database header content */ + + /* True if opening an ephemeral, temporary database */ + const int isTempDb = zFilename==0 || zFilename[0]==0; + + /* Set the variable isMemdb to true for an in-memory database, or + ** false for a file-based database. + */ +#ifdef SQLITE_OMIT_MEMORYDB + const int isMemdb = 0; +#else + const int isMemdb = (zFilename && strcmp(zFilename, ":memory:")==0) + || (isTempDb && sqlite3TempInMemory(db)) + || (vfsFlags & SQLITE_OPEN_MEMORY)!=0; +#endif + + assert( db!=0 ); + assert( pVfs!=0 ); + assert( sqlite3_mutex_held(db->mutex) ); + assert( (flags&0xff)==flags ); /* flags fit in 8 bits */ + + /* Only a BTREE_SINGLE database can be BTREE_UNORDERED */ + assert( (flags & BTREE_UNORDERED)==0 || (flags & BTREE_SINGLE)!=0 ); + + /* A BTREE_SINGLE database is always a temporary and/or ephemeral */ + assert( (flags & BTREE_SINGLE)==0 || isTempDb ); + + if( isMemdb ){ + flags |= BTREE_MEMORY; + } + if( (vfsFlags & SQLITE_OPEN_MAIN_DB)!=0 && (isMemdb || isTempDb) ){ + vfsFlags = (vfsFlags & ~SQLITE_OPEN_MAIN_DB) | SQLITE_OPEN_TEMP_DB; + } + p = sqlite3MallocZero(sizeof(Btree)); + if( !p ){ + return SQLITE_NOMEM_BKPT; + } + p->inTrans = TRANS_NONE; + p->db = db; +#ifndef SQLITE_OMIT_SHARED_CACHE + p->lock.pBtree = p; + p->lock.iTable = 1; +#endif + +#if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO) + /* + ** If this Btree is a candidate for shared cache, try to find an + ** existing BtShared object that we can share with + */ + if( isTempDb==0 && (isMemdb==0 || (vfsFlags&SQLITE_OPEN_URI)!=0) ){ + if( vfsFlags & SQLITE_OPEN_SHAREDCACHE ){ + int nFilename = sqlite3Strlen30(zFilename)+1; + int nFullPathname = pVfs->mxPathname+1; + char *zFullPathname = sqlite3Malloc(MAX(nFullPathname,nFilename)); + MUTEX_LOGIC( sqlite3_mutex *mutexShared; ) + + p->sharable = 1; + if( !zFullPathname ){ + sqlite3_free(p); + return SQLITE_NOMEM_BKPT; + } + if( isMemdb ){ + memcpy(zFullPathname, zFilename, nFilename); + }else{ + rc = sqlite3OsFullPathname(pVfs, zFilename, + nFullPathname, zFullPathname); + if( rc ){ + if( rc==SQLITE_OK_SYMLINK ){ + rc = SQLITE_OK; + }else{ + sqlite3_free(zFullPathname); + sqlite3_free(p); + return rc; + } + } + } +#if SQLITE_THREADSAFE + mutexOpen = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_OPEN); + sqlite3_mutex_enter(mutexOpen); + mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); + sqlite3_mutex_enter(mutexShared); +#endif + for(pBt=GLOBAL(BtShared*,sqlite3SharedCacheList); pBt; pBt=pBt->pNext){ + assert( pBt->nRef>0 ); + if( 0==strcmp(zFullPathname, sqlite3PagerFilename(pBt->pPager, 0)) + && sqlite3PagerVfs(pBt->pPager)==pVfs ){ + int iDb; + for(iDb=db->nDb-1; iDb>=0; iDb--){ + Btree *pExisting = db->aDb[iDb].pBt; + if( pExisting && pExisting->pBt==pBt ){ + sqlite3_mutex_leave(mutexShared); + sqlite3_mutex_leave(mutexOpen); + sqlite3_free(zFullPathname); + sqlite3_free(p); + return SQLITE_CONSTRAINT; + } + } + p->pBt = pBt; + pBt->nRef++; + break; + } + } + sqlite3_mutex_leave(mutexShared); + sqlite3_free(zFullPathname); + } +#ifdef SQLITE_DEBUG + else{ + /* In debug mode, we mark all persistent databases as sharable + ** even when they are not. This exercises the locking code and + ** gives more opportunity for asserts(sqlite3_mutex_held()) + ** statements to find locking problems. + */ + p->sharable = 1; + } +#endif + } +#endif + if( pBt==0 ){ + /* + ** The following asserts make sure that structures used by the btree are + ** the right size. This is to guard against size changes that result + ** when compiling on a different architecture. + */ + assert( sizeof(i64)==8 ); + assert( sizeof(u64)==8 ); + assert( sizeof(u32)==4 ); + assert( sizeof(u16)==2 ); + assert( sizeof(Pgno)==4 ); + + /* Suppress false-positive compiler warning from PVS-Studio */ + memset(&zDbHeader[16], 0, 8); + + pBt = sqlite3MallocZero( sizeof(*pBt) ); + if( pBt==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto btree_open_out; + } + rc = sqlite3PagerOpen(pVfs, &pBt->pPager, zFilename, + sizeof(MemPage), flags, vfsFlags, pageReinit); + if( rc==SQLITE_OK ){ + sqlite3PagerSetMmapLimit(pBt->pPager, db->szMmap); + rc = sqlite3PagerReadFileheader(pBt->pPager,sizeof(zDbHeader),zDbHeader); + } + if( rc!=SQLITE_OK ){ + goto btree_open_out; + } + pBt->openFlags = (u8)flags; + pBt->db = db; + sqlite3PagerSetBusyHandler(pBt->pPager, btreeInvokeBusyHandler, pBt); + p->pBt = pBt; + + pBt->pCursor = 0; + pBt->pPage1 = 0; + if( sqlite3PagerIsreadonly(pBt->pPager) ) pBt->btsFlags |= BTS_READ_ONLY; +#if defined(SQLITE_SECURE_DELETE) + pBt->btsFlags |= BTS_SECURE_DELETE; +#elif defined(SQLITE_FAST_SECURE_DELETE) + pBt->btsFlags |= BTS_OVERWRITE; +#endif + /* EVIDENCE-OF: R-51873-39618 The page size for a database file is + ** determined by the 2-byte integer located at an offset of 16 bytes from + ** the beginning of the database file. */ + pBt->pageSize = (zDbHeader[16]<<8) | (zDbHeader[17]<<16); + if( pBt->pageSize<512 || pBt->pageSize>SQLITE_MAX_PAGE_SIZE + || ((pBt->pageSize-1)&pBt->pageSize)!=0 ){ + pBt->pageSize = 0; +#ifndef SQLITE_OMIT_AUTOVACUUM + /* If the magic name ":memory:" will create an in-memory database, then + ** leave the autoVacuum mode at 0 (do not auto-vacuum), even if + ** SQLITE_DEFAULT_AUTOVACUUM is true. On the other hand, if + ** SQLITE_OMIT_MEMORYDB has been defined, then ":memory:" is just a + ** regular file-name. In this case the auto-vacuum applies as per normal. + */ + if( zFilename && !isMemdb ){ + pBt->autoVacuum = (SQLITE_DEFAULT_AUTOVACUUM ? 1 : 0); + pBt->incrVacuum = (SQLITE_DEFAULT_AUTOVACUUM==2 ? 1 : 0); + } +#endif + nReserve = 0; + }else{ + /* EVIDENCE-OF: R-37497-42412 The size of the reserved region is + ** determined by the one-byte unsigned integer found at an offset of 20 + ** into the database file header. */ + nReserve = zDbHeader[20]; + pBt->btsFlags |= BTS_PAGESIZE_FIXED; +#ifndef SQLITE_OMIT_AUTOVACUUM + pBt->autoVacuum = (get4byte(&zDbHeader[36 + 4*4])?1:0); + pBt->incrVacuum = (get4byte(&zDbHeader[36 + 7*4])?1:0); +#endif + } + rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve); + if( rc ) goto btree_open_out; + pBt->usableSize = pBt->pageSize - nReserve; + assert( (pBt->pageSize & 7)==0 ); /* 8-byte alignment of pageSize */ + +#if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO) + /* Add the new BtShared object to the linked list sharable BtShareds. + */ + pBt->nRef = 1; + if( p->sharable ){ + MUTEX_LOGIC( sqlite3_mutex *mutexShared; ) + MUTEX_LOGIC( mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN);) + if( SQLITE_THREADSAFE && sqlite3GlobalConfig.bCoreMutex ){ + pBt->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_FAST); + if( pBt->mutex==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto btree_open_out; + } + } + sqlite3_mutex_enter(mutexShared); + pBt->pNext = GLOBAL(BtShared*,sqlite3SharedCacheList); + GLOBAL(BtShared*,sqlite3SharedCacheList) = pBt; + sqlite3_mutex_leave(mutexShared); + } +#endif + } + +#if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO) + /* If the new Btree uses a sharable pBtShared, then link the new + ** Btree into the list of all sharable Btrees for the same connection. + ** The list is kept in ascending order by pBt address. + */ + if( p->sharable ){ + int i; + Btree *pSib; + for(i=0; i<db->nDb; i++){ + if( (pSib = db->aDb[i].pBt)!=0 && pSib->sharable ){ + while( pSib->pPrev ){ pSib = pSib->pPrev; } + if( (uptr)p->pBt<(uptr)pSib->pBt ){ + p->pNext = pSib; + p->pPrev = 0; + pSib->pPrev = p; + }else{ + while( pSib->pNext && (uptr)pSib->pNext->pBt<(uptr)p->pBt ){ + pSib = pSib->pNext; + } + p->pNext = pSib->pNext; + p->pPrev = pSib; + if( p->pNext ){ + p->pNext->pPrev = p; + } + pSib->pNext = p; + } + break; + } + } + } +#endif + *ppBtree = p; + +btree_open_out: + if( rc!=SQLITE_OK ){ + if( pBt && pBt->pPager ){ + sqlite3PagerClose(pBt->pPager, 0); + } + sqlite3_free(pBt); + sqlite3_free(p); + *ppBtree = 0; + }else{ + sqlite3_file *pFile; + + /* If the B-Tree was successfully opened, set the pager-cache size to the + ** default value. Except, when opening on an existing shared pager-cache, + ** do not change the pager-cache size. + */ + if( sqlite3BtreeSchema(p, 0, 0)==0 ){ + sqlite3BtreeSetCacheSize(p, SQLITE_DEFAULT_CACHE_SIZE); + } + + pFile = sqlite3PagerFile(pBt->pPager); + if( pFile->pMethods ){ + sqlite3OsFileControlHint(pFile, SQLITE_FCNTL_PDB, (void*)&pBt->db); + } + } + if( mutexOpen ){ + assert( sqlite3_mutex_held(mutexOpen) ); + sqlite3_mutex_leave(mutexOpen); + } + assert( rc!=SQLITE_OK || sqlite3BtreeConnectionCount(*ppBtree)>0 ); + return rc; +} + +/* +** Decrement the BtShared.nRef counter. When it reaches zero, +** remove the BtShared structure from the sharing list. Return +** true if the BtShared.nRef counter reaches zero and return +** false if it is still positive. +*/ +static int removeFromSharingList(BtShared *pBt){ +#ifndef SQLITE_OMIT_SHARED_CACHE + MUTEX_LOGIC( sqlite3_mutex *pMainMtx; ) + BtShared *pList; + int removed = 0; + + assert( sqlite3_mutex_notheld(pBt->mutex) ); + MUTEX_LOGIC( pMainMtx = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); ) + sqlite3_mutex_enter(pMainMtx); + pBt->nRef--; + if( pBt->nRef<=0 ){ + if( GLOBAL(BtShared*,sqlite3SharedCacheList)==pBt ){ + GLOBAL(BtShared*,sqlite3SharedCacheList) = pBt->pNext; + }else{ + pList = GLOBAL(BtShared*,sqlite3SharedCacheList); + while( ALWAYS(pList) && pList->pNext!=pBt ){ + pList=pList->pNext; + } + if( ALWAYS(pList) ){ + pList->pNext = pBt->pNext; + } + } + if( SQLITE_THREADSAFE ){ + sqlite3_mutex_free(pBt->mutex); + } + removed = 1; + } + sqlite3_mutex_leave(pMainMtx); + return removed; +#else + return 1; +#endif +} + +/* +** Make sure pBt->pTmpSpace points to an allocation of +** MX_CELL_SIZE(pBt) bytes with a 4-byte prefix for a left-child +** pointer. +*/ +static SQLITE_NOINLINE int allocateTempSpace(BtShared *pBt){ + assert( pBt!=0 ); + assert( pBt->pTmpSpace==0 ); + /* This routine is called only by btreeCursor() when allocating the + ** first write cursor for the BtShared object */ + assert( pBt->pCursor!=0 && (pBt->pCursor->curFlags & BTCF_WriteFlag)!=0 ); + pBt->pTmpSpace = sqlite3PageMalloc( pBt->pageSize ); + if( pBt->pTmpSpace==0 ){ + BtCursor *pCur = pBt->pCursor; + pBt->pCursor = pCur->pNext; /* Unlink the cursor */ + memset(pCur, 0, sizeof(*pCur)); + return SQLITE_NOMEM_BKPT; + } + + /* One of the uses of pBt->pTmpSpace is to format cells before + ** inserting them into a leaf page (function fillInCell()). If + ** a cell is less than 4 bytes in size, it is rounded up to 4 bytes + ** by the various routines that manipulate binary cells. Which + ** can mean that fillInCell() only initializes the first 2 or 3 + ** bytes of pTmpSpace, but that the first 4 bytes are copied from + ** it into a database page. This is not actually a problem, but it + ** does cause a valgrind error when the 1 or 2 bytes of uninitialized + ** data is passed to system call write(). So to avoid this error, + ** zero the first 4 bytes of temp space here. + ** + ** Also: Provide four bytes of initialized space before the + ** beginning of pTmpSpace as an area available to prepend the + ** left-child pointer to the beginning of a cell. + */ + memset(pBt->pTmpSpace, 0, 8); + pBt->pTmpSpace += 4; + return SQLITE_OK; +} + +/* +** Free the pBt->pTmpSpace allocation +*/ +static void freeTempSpace(BtShared *pBt){ + if( pBt->pTmpSpace ){ + pBt->pTmpSpace -= 4; + sqlite3PageFree(pBt->pTmpSpace); + pBt->pTmpSpace = 0; + } +} + +/* +** Close an open database and invalidate all cursors. +*/ +SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){ + BtShared *pBt = p->pBt; + + /* Close all cursors opened via this handle. */ + assert( sqlite3_mutex_held(p->db->mutex) ); + sqlite3BtreeEnter(p); + + /* Verify that no other cursors have this Btree open */ +#ifdef SQLITE_DEBUG + { + BtCursor *pCur = pBt->pCursor; + while( pCur ){ + BtCursor *pTmp = pCur; + pCur = pCur->pNext; + assert( pTmp->pBtree!=p ); + + } + } +#endif + + /* Rollback any active transaction and free the handle structure. + ** The call to sqlite3BtreeRollback() drops any table-locks held by + ** this handle. + */ + sqlite3BtreeRollback(p, SQLITE_OK, 0); + sqlite3BtreeLeave(p); + + /* If there are still other outstanding references to the shared-btree + ** structure, return now. The remainder of this procedure cleans + ** up the shared-btree. + */ + assert( p->wantToLock==0 && p->locked==0 ); + if( !p->sharable || removeFromSharingList(pBt) ){ + /* The pBt is no longer on the sharing list, so we can access + ** it without having to hold the mutex. + ** + ** Clean out and delete the BtShared object. + */ + assert( !pBt->pCursor ); + sqlite3PagerClose(pBt->pPager, p->db); + if( pBt->xFreeSchema && pBt->pSchema ){ + pBt->xFreeSchema(pBt->pSchema); + } + sqlite3DbFree(0, pBt->pSchema); + freeTempSpace(pBt); + sqlite3_free(pBt); + } + +#ifndef SQLITE_OMIT_SHARED_CACHE + assert( p->wantToLock==0 ); + assert( p->locked==0 ); + if( p->pPrev ) p->pPrev->pNext = p->pNext; + if( p->pNext ) p->pNext->pPrev = p->pPrev; +#endif + + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Change the "soft" limit on the number of pages in the cache. +** Unused and unmodified pages will be recycled when the number of +** pages in the cache exceeds this soft limit. But the size of the +** cache is allowed to grow larger than this limit if it contains +** dirty pages or pages still in active use. +*/ +SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree *p, int mxPage){ + BtShared *pBt = p->pBt; + assert( sqlite3_mutex_held(p->db->mutex) ); + sqlite3BtreeEnter(p); + sqlite3PagerSetCachesize(pBt->pPager, mxPage); + sqlite3BtreeLeave(p); + return SQLITE_OK; +} + +/* +** Change the "spill" limit on the number of pages in the cache. +** If the number of pages exceeds this limit during a write transaction, +** the pager might attempt to "spill" pages to the journal early in +** order to free up memory. +** +** The value returned is the current spill size. If zero is passed +** as an argument, no changes are made to the spill size setting, so +** using mxPage of 0 is a way to query the current spill size. +*/ +SQLITE_PRIVATE int sqlite3BtreeSetSpillSize(Btree *p, int mxPage){ + BtShared *pBt = p->pBt; + int res; + assert( sqlite3_mutex_held(p->db->mutex) ); + sqlite3BtreeEnter(p); + res = sqlite3PagerSetSpillsize(pBt->pPager, mxPage); + sqlite3BtreeLeave(p); + return res; +} + +#if SQLITE_MAX_MMAP_SIZE>0 +/* +** Change the limit on the amount of the database file that may be +** memory mapped. +*/ +SQLITE_PRIVATE int sqlite3BtreeSetMmapLimit(Btree *p, sqlite3_int64 szMmap){ + BtShared *pBt = p->pBt; + assert( sqlite3_mutex_held(p->db->mutex) ); + sqlite3BtreeEnter(p); + sqlite3PagerSetMmapLimit(pBt->pPager, szMmap); + sqlite3BtreeLeave(p); + return SQLITE_OK; +} +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + +/* +** Change the way data is synced to disk in order to increase or decrease +** how well the database resists damage due to OS crashes and power +** failures. Level 1 is the same as asynchronous (no syncs() occur and +** there is a high probability of damage) Level 2 is the default. There +** is a very low but non-zero probability of damage. Level 3 reduces the +** probability of damage to near zero but with a write performance reduction. +*/ +#ifndef SQLITE_OMIT_PAGER_PRAGMAS +SQLITE_PRIVATE int sqlite3BtreeSetPagerFlags( + Btree *p, /* The btree to set the safety level on */ + unsigned pgFlags /* Various PAGER_* flags */ +){ + BtShared *pBt = p->pBt; + assert( sqlite3_mutex_held(p->db->mutex) ); + sqlite3BtreeEnter(p); + sqlite3PagerSetFlags(pBt->pPager, pgFlags); + sqlite3BtreeLeave(p); + return SQLITE_OK; +} +#endif + +/* +** Change the default pages size and the number of reserved bytes per page. +** Or, if the page size has already been fixed, return SQLITE_READONLY +** without changing anything. +** +** The page size must be a power of 2 between 512 and 65536. If the page +** size supplied does not meet this constraint then the page size is not +** changed. +** +** Page sizes are constrained to be a power of two so that the region +** of the database file used for locking (beginning at PENDING_BYTE, +** the first byte past the 1GB boundary, 0x40000000) needs to occur +** at the beginning of a page. +** +** If parameter nReserve is less than zero, then the number of reserved +** bytes per page is left unchanged. +** +** If the iFix!=0 then the BTS_PAGESIZE_FIXED flag is set so that the page size +** and autovacuum mode can no longer be changed. +*/ +SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve, int iFix){ + int rc = SQLITE_OK; + int x; + BtShared *pBt = p->pBt; + assert( nReserve>=0 && nReserve<=255 ); + sqlite3BtreeEnter(p); + pBt->nReserveWanted = nReserve; + x = pBt->pageSize - pBt->usableSize; + if( nReserve<x ) nReserve = x; + if( pBt->btsFlags & BTS_PAGESIZE_FIXED ){ + sqlite3BtreeLeave(p); + return SQLITE_READONLY; + } + assert( nReserve>=0 && nReserve<=255 ); + if( pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE && + ((pageSize-1)&pageSize)==0 ){ + assert( (pageSize & 7)==0 ); + assert( !pBt->pCursor ); + if( nReserve>32 && pageSize==512 ) pageSize = 1024; + pBt->pageSize = (u32)pageSize; + freeTempSpace(pBt); + } + rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve); + pBt->usableSize = pBt->pageSize - (u16)nReserve; + if( iFix ) pBt->btsFlags |= BTS_PAGESIZE_FIXED; + sqlite3BtreeLeave(p); + return rc; +} + +/* +** Return the currently defined page size +*/ +SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree *p){ + return p->pBt->pageSize; +} + +/* +** This function is similar to sqlite3BtreeGetReserve(), except that it +** may only be called if it is guaranteed that the b-tree mutex is already +** held. +** +** This is useful in one special case in the backup API code where it is +** known that the shared b-tree mutex is held, but the mutex on the +** database handle that owns *p is not. In this case if sqlite3BtreeEnter() +** were to be called, it might collide with some other operation on the +** database handle that owns *p, causing undefined behavior. +*/ +SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p){ + int n; + assert( sqlite3_mutex_held(p->pBt->mutex) ); + n = p->pBt->pageSize - p->pBt->usableSize; + return n; +} + +/* +** Return the number of bytes of space at the end of every page that +** are intentionally left unused. This is the "reserved" space that is +** sometimes used by extensions. +** +** The value returned is the larger of the current reserve size and +** the latest reserve size requested by SQLITE_FILECTRL_RESERVE_BYTES. +** The amount of reserve can only grow - never shrink. +*/ +SQLITE_PRIVATE int sqlite3BtreeGetRequestedReserve(Btree *p){ + int n1, n2; + sqlite3BtreeEnter(p); + n1 = (int)p->pBt->nReserveWanted; + n2 = sqlite3BtreeGetReserveNoMutex(p); + sqlite3BtreeLeave(p); + return n1>n2 ? n1 : n2; +} + + +/* +** Set the maximum page count for a database if mxPage is positive. +** No changes are made if mxPage is 0 or negative. +** Regardless of the value of mxPage, return the maximum page count. +*/ +SQLITE_PRIVATE Pgno sqlite3BtreeMaxPageCount(Btree *p, Pgno mxPage){ + Pgno n; + sqlite3BtreeEnter(p); + n = sqlite3PagerMaxPageCount(p->pBt->pPager, mxPage); + sqlite3BtreeLeave(p); + return n; +} + +/* +** Change the values for the BTS_SECURE_DELETE and BTS_OVERWRITE flags: +** +** newFlag==0 Both BTS_SECURE_DELETE and BTS_OVERWRITE are cleared +** newFlag==1 BTS_SECURE_DELETE set and BTS_OVERWRITE is cleared +** newFlag==2 BTS_SECURE_DELETE cleared and BTS_OVERWRITE is set +** newFlag==(-1) No changes +** +** This routine acts as a query if newFlag is less than zero +** +** With BTS_OVERWRITE set, deleted content is overwritten by zeros, but +** freelist leaf pages are not written back to the database. Thus in-page +** deleted content is cleared, but freelist deleted content is not. +** +** With BTS_SECURE_DELETE, operation is like BTS_OVERWRITE with the addition +** that freelist leaf pages are written back into the database, increasing +** the amount of disk I/O. +*/ +SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree *p, int newFlag){ + int b; + if( p==0 ) return 0; + sqlite3BtreeEnter(p); + assert( BTS_OVERWRITE==BTS_SECURE_DELETE*2 ); + assert( BTS_FAST_SECURE==(BTS_OVERWRITE|BTS_SECURE_DELETE) ); + if( newFlag>=0 ){ + p->pBt->btsFlags &= ~BTS_FAST_SECURE; + p->pBt->btsFlags |= BTS_SECURE_DELETE*newFlag; + } + b = (p->pBt->btsFlags & BTS_FAST_SECURE)/BTS_SECURE_DELETE; + sqlite3BtreeLeave(p); + return b; +} + +/* +** Change the 'auto-vacuum' property of the database. If the 'autoVacuum' +** parameter is non-zero, then auto-vacuum mode is enabled. If zero, it +** is disabled. The default value for the auto-vacuum property is +** determined by the SQLITE_DEFAULT_AUTOVACUUM macro. +*/ +SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){ +#ifdef SQLITE_OMIT_AUTOVACUUM + return SQLITE_READONLY; +#else + BtShared *pBt = p->pBt; + int rc = SQLITE_OK; + u8 av = (u8)autoVacuum; + + sqlite3BtreeEnter(p); + if( (pBt->btsFlags & BTS_PAGESIZE_FIXED)!=0 && (av ?1:0)!=pBt->autoVacuum ){ + rc = SQLITE_READONLY; + }else{ + pBt->autoVacuum = av ?1:0; + pBt->incrVacuum = av==2 ?1:0; + } + sqlite3BtreeLeave(p); + return rc; +#endif +} + +/* +** Return the value of the 'auto-vacuum' property. If auto-vacuum is +** enabled 1 is returned. Otherwise 0. +*/ +SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *p){ +#ifdef SQLITE_OMIT_AUTOVACUUM + return BTREE_AUTOVACUUM_NONE; +#else + int rc; + sqlite3BtreeEnter(p); + rc = ( + (!p->pBt->autoVacuum)?BTREE_AUTOVACUUM_NONE: + (!p->pBt->incrVacuum)?BTREE_AUTOVACUUM_FULL: + BTREE_AUTOVACUUM_INCR + ); + sqlite3BtreeLeave(p); + return rc; +#endif +} + +/* +** If the user has not set the safety-level for this database connection +** using "PRAGMA synchronous", and if the safety-level is not already +** set to the value passed to this function as the second parameter, +** set it so. +*/ +#if SQLITE_DEFAULT_SYNCHRONOUS!=SQLITE_DEFAULT_WAL_SYNCHRONOUS \ + && !defined(SQLITE_OMIT_WAL) +static void setDefaultSyncFlag(BtShared *pBt, u8 safety_level){ + sqlite3 *db; + Db *pDb; + if( (db=pBt->db)!=0 && (pDb=db->aDb)!=0 ){ + while( pDb->pBt==0 || pDb->pBt->pBt!=pBt ){ pDb++; } + if( pDb->bSyncSet==0 + && pDb->safety_level!=safety_level + && pDb!=&db->aDb[1] + ){ + pDb->safety_level = safety_level; + sqlite3PagerSetFlags(pBt->pPager, + pDb->safety_level | (db->flags & PAGER_FLAGS_MASK)); + } + } +} +#else +# define setDefaultSyncFlag(pBt,safety_level) +#endif + +/* Forward declaration */ +static int newDatabase(BtShared*); + + +/* +** Get a reference to pPage1 of the database file. This will +** also acquire a readlock on that file. +** +** SQLITE_OK is returned on success. If the file is not a +** well-formed database file, then SQLITE_CORRUPT is returned. +** SQLITE_BUSY is returned if the database is locked. SQLITE_NOMEM +** is returned if we run out of memory. +*/ +static int lockBtree(BtShared *pBt){ + int rc; /* Result code from subfunctions */ + MemPage *pPage1; /* Page 1 of the database file */ + u32 nPage; /* Number of pages in the database */ + u32 nPageFile = 0; /* Number of pages in the database file */ + + assert( sqlite3_mutex_held(pBt->mutex) ); + assert( pBt->pPage1==0 ); + rc = sqlite3PagerSharedLock(pBt->pPager); + if( rc!=SQLITE_OK ) return rc; + rc = btreeGetPage(pBt, 1, &pPage1, 0); + if( rc!=SQLITE_OK ) return rc; + + /* Do some checking to help insure the file we opened really is + ** a valid database file. + */ + nPage = get4byte(28+(u8*)pPage1->aData); + sqlite3PagerPagecount(pBt->pPager, (int*)&nPageFile); + if( nPage==0 || memcmp(24+(u8*)pPage1->aData, 92+(u8*)pPage1->aData,4)!=0 ){ + nPage = nPageFile; + } + if( (pBt->db->flags & SQLITE_ResetDatabase)!=0 ){ + nPage = 0; + } + if( nPage>0 ){ + u32 pageSize; + u32 usableSize; + u8 *page1 = pPage1->aData; + rc = SQLITE_NOTADB; + /* EVIDENCE-OF: R-43737-39999 Every valid SQLite database file begins + ** with the following 16 bytes (in hex): 53 51 4c 69 74 65 20 66 6f 72 6d + ** 61 74 20 33 00. */ + if( memcmp(page1, zMagicHeader, 16)!=0 ){ + goto page1_init_failed; + } + +#ifdef SQLITE_OMIT_WAL + if( page1[18]>1 ){ + pBt->btsFlags |= BTS_READ_ONLY; + } + if( page1[19]>1 ){ + goto page1_init_failed; + } +#else + if( page1[18]>2 ){ + pBt->btsFlags |= BTS_READ_ONLY; + } + if( page1[19]>2 ){ + goto page1_init_failed; + } + + /* If the read version is set to 2, this database should be accessed + ** in WAL mode. If the log is not already open, open it now. Then + ** return SQLITE_OK and return without populating BtShared.pPage1. + ** The caller detects this and calls this function again. This is + ** required as the version of page 1 currently in the page1 buffer + ** may not be the latest version - there may be a newer one in the log + ** file. + */ + if( page1[19]==2 && (pBt->btsFlags & BTS_NO_WAL)==0 ){ + int isOpen = 0; + rc = sqlite3PagerOpenWal(pBt->pPager, &isOpen); + if( rc!=SQLITE_OK ){ + goto page1_init_failed; + }else{ + setDefaultSyncFlag(pBt, SQLITE_DEFAULT_WAL_SYNCHRONOUS+1); + if( isOpen==0 ){ + releasePageOne(pPage1); + return SQLITE_OK; + } + } + rc = SQLITE_NOTADB; + }else{ + setDefaultSyncFlag(pBt, SQLITE_DEFAULT_SYNCHRONOUS+1); + } +#endif + + /* EVIDENCE-OF: R-15465-20813 The maximum and minimum embedded payload + ** fractions and the leaf payload fraction values must be 64, 32, and 32. + ** + ** The original design allowed these amounts to vary, but as of + ** version 3.6.0, we require them to be fixed. + */ + if( memcmp(&page1[21], "\100\040\040",3)!=0 ){ + goto page1_init_failed; + } + /* EVIDENCE-OF: R-51873-39618 The page size for a database file is + ** determined by the 2-byte integer located at an offset of 16 bytes from + ** the beginning of the database file. */ + pageSize = (page1[16]<<8) | (page1[17]<<16); + /* EVIDENCE-OF: R-25008-21688 The size of a page is a power of two + ** between 512 and 65536 inclusive. */ + if( ((pageSize-1)&pageSize)!=0 + || pageSize>SQLITE_MAX_PAGE_SIZE + || pageSize<=256 + ){ + goto page1_init_failed; + } + assert( (pageSize & 7)==0 ); + /* EVIDENCE-OF: R-59310-51205 The "reserved space" size in the 1-byte + ** integer at offset 20 is the number of bytes of space at the end of + ** each page to reserve for extensions. + ** + ** EVIDENCE-OF: R-37497-42412 The size of the reserved region is + ** determined by the one-byte unsigned integer found at an offset of 20 + ** into the database file header. */ + usableSize = pageSize - page1[20]; + if( (u32)pageSize!=pBt->pageSize ){ + /* After reading the first page of the database assuming a page size + ** of BtShared.pageSize, we have discovered that the page-size is + ** actually pageSize. Unlock the database, leave pBt->pPage1 at + ** zero and return SQLITE_OK. The caller will call this function + ** again with the correct page-size. + */ + releasePageOne(pPage1); + pBt->usableSize = usableSize; + pBt->pageSize = pageSize; + pBt->btsFlags |= BTS_PAGESIZE_FIXED; + freeTempSpace(pBt); + rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, + pageSize-usableSize); + return rc; + } + if( nPage>nPageFile ){ + if( sqlite3WritableSchema(pBt->db)==0 ){ + rc = SQLITE_CORRUPT_BKPT; + goto page1_init_failed; + }else{ + nPage = nPageFile; + } + } + /* EVIDENCE-OF: R-28312-64704 However, the usable size is not allowed to + ** be less than 480. In other words, if the page size is 512, then the + ** reserved space size cannot exceed 32. */ + if( usableSize<480 ){ + goto page1_init_failed; + } + pBt->btsFlags |= BTS_PAGESIZE_FIXED; + pBt->pageSize = pageSize; + pBt->usableSize = usableSize; +#ifndef SQLITE_OMIT_AUTOVACUUM + pBt->autoVacuum = (get4byte(&page1[36 + 4*4])?1:0); + pBt->incrVacuum = (get4byte(&page1[36 + 7*4])?1:0); +#endif + } + + /* maxLocal is the maximum amount of payload to store locally for + ** a cell. Make sure it is small enough so that at least minFanout + ** cells can will fit on one page. We assume a 10-byte page header. + ** Besides the payload, the cell must store: + ** 2-byte pointer to the cell + ** 4-byte child pointer + ** 9-byte nKey value + ** 4-byte nData value + ** 4-byte overflow page pointer + ** So a cell consists of a 2-byte pointer, a header which is as much as + ** 17 bytes long, 0 to N bytes of payload, and an optional 4 byte overflow + ** page pointer. + */ + pBt->maxLocal = (u16)((pBt->usableSize-12)*64/255 - 23); + pBt->minLocal = (u16)((pBt->usableSize-12)*32/255 - 23); + pBt->maxLeaf = (u16)(pBt->usableSize - 35); + pBt->minLeaf = (u16)((pBt->usableSize-12)*32/255 - 23); + if( pBt->maxLocal>127 ){ + pBt->max1bytePayload = 127; + }else{ + pBt->max1bytePayload = (u8)pBt->maxLocal; + } + assert( pBt->maxLeaf + 23 <= MX_CELL_SIZE(pBt) ); + pBt->pPage1 = pPage1; + pBt->nPage = nPage; + return SQLITE_OK; + +page1_init_failed: + releasePageOne(pPage1); + pBt->pPage1 = 0; + return rc; +} + +#ifndef NDEBUG +/* +** Return the number of cursors open on pBt. This is for use +** in assert() expressions, so it is only compiled if NDEBUG is not +** defined. +** +** Only write cursors are counted if wrOnly is true. If wrOnly is +** false then all cursors are counted. +** +** For the purposes of this routine, a cursor is any cursor that +** is capable of reading or writing to the database. Cursors that +** have been tripped into the CURSOR_FAULT state are not counted. +*/ +static int countValidCursors(BtShared *pBt, int wrOnly){ + BtCursor *pCur; + int r = 0; + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + if( (wrOnly==0 || (pCur->curFlags & BTCF_WriteFlag)!=0) + && pCur->eState!=CURSOR_FAULT ) r++; + } + return r; +} +#endif + +/* +** If there are no outstanding cursors and we are not in the middle +** of a transaction but there is a read lock on the database, then +** this routine unrefs the first page of the database file which +** has the effect of releasing the read lock. +** +** If there is a transaction in progress, this routine is a no-op. +*/ +static void unlockBtreeIfUnused(BtShared *pBt){ + assert( sqlite3_mutex_held(pBt->mutex) ); + assert( countValidCursors(pBt,0)==0 || pBt->inTransaction>TRANS_NONE ); + if( pBt->inTransaction==TRANS_NONE && pBt->pPage1!=0 ){ + MemPage *pPage1 = pBt->pPage1; + assert( pPage1->aData ); + assert( sqlite3PagerRefcount(pBt->pPager)==1 ); + pBt->pPage1 = 0; + releasePageOne(pPage1); + } +} + +/* +** If pBt points to an empty file then convert that empty file +** into a new empty database by initializing the first page of +** the database. +*/ +static int newDatabase(BtShared *pBt){ + MemPage *pP1; + unsigned char *data; + int rc; + + assert( sqlite3_mutex_held(pBt->mutex) ); + if( pBt->nPage>0 ){ + return SQLITE_OK; + } + pP1 = pBt->pPage1; + assert( pP1!=0 ); + data = pP1->aData; + rc = sqlite3PagerWrite(pP1->pDbPage); + if( rc ) return rc; + memcpy(data, zMagicHeader, sizeof(zMagicHeader)); + assert( sizeof(zMagicHeader)==16 ); + data[16] = (u8)((pBt->pageSize>>8)&0xff); + data[17] = (u8)((pBt->pageSize>>16)&0xff); + data[18] = 1; + data[19] = 1; + assert( pBt->usableSize<=pBt->pageSize && pBt->usableSize+255>=pBt->pageSize); + data[20] = (u8)(pBt->pageSize - pBt->usableSize); + data[21] = 64; + data[22] = 32; + data[23] = 32; + memset(&data[24], 0, 100-24); + zeroPage(pP1, PTF_INTKEY|PTF_LEAF|PTF_LEAFDATA ); + pBt->btsFlags |= BTS_PAGESIZE_FIXED; +#ifndef SQLITE_OMIT_AUTOVACUUM + assert( pBt->autoVacuum==1 || pBt->autoVacuum==0 ); + assert( pBt->incrVacuum==1 || pBt->incrVacuum==0 ); + put4byte(&data[36 + 4*4], pBt->autoVacuum); + put4byte(&data[36 + 7*4], pBt->incrVacuum); +#endif + pBt->nPage = 1; + data[31] = 1; + return SQLITE_OK; +} + +/* +** Initialize the first page of the database file (creating a database +** consisting of a single page and no schema objects). Return SQLITE_OK +** if successful, or an SQLite error code otherwise. +*/ +SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p){ + int rc; + sqlite3BtreeEnter(p); + p->pBt->nPage = 0; + rc = newDatabase(p->pBt); + sqlite3BtreeLeave(p); + return rc; +} + +/* +** Attempt to start a new transaction. A write-transaction +** is started if the second argument is nonzero, otherwise a read- +** transaction. If the second argument is 2 or more and exclusive +** transaction is started, meaning that no other process is allowed +** to access the database. A preexisting transaction may not be +** upgraded to exclusive by calling this routine a second time - the +** exclusivity flag only works for a new transaction. +** +** A write-transaction must be started before attempting any +** changes to the database. None of the following routines +** will work unless a transaction is started first: +** +** sqlite3BtreeCreateTable() +** sqlite3BtreeCreateIndex() +** sqlite3BtreeClearTable() +** sqlite3BtreeDropTable() +** sqlite3BtreeInsert() +** sqlite3BtreeDelete() +** sqlite3BtreeUpdateMeta() +** +** If an initial attempt to acquire the lock fails because of lock contention +** and the database was previously unlocked, then invoke the busy handler +** if there is one. But if there was previously a read-lock, do not +** invoke the busy handler - just return SQLITE_BUSY. SQLITE_BUSY is +** returned when there is already a read-lock in order to avoid a deadlock. +** +** Suppose there are two processes A and B. A has a read lock and B has +** a reserved lock. B tries to promote to exclusive but is blocked because +** of A's read lock. A tries to promote to reserved but is blocked by B. +** One or the other of the two processes must give way or there can be +** no progress. By returning SQLITE_BUSY and not invoking the busy callback +** when A already has a read lock, we encourage A to give up and let B +** proceed. +*/ +static SQLITE_NOINLINE int btreeBeginTrans( + Btree *p, /* The btree in which to start the transaction */ + int wrflag, /* True to start a write transaction */ + int *pSchemaVersion /* Put schema version number here, if not NULL */ +){ + BtShared *pBt = p->pBt; + Pager *pPager = pBt->pPager; + int rc = SQLITE_OK; + + sqlite3BtreeEnter(p); + btreeIntegrity(p); + + /* If the btree is already in a write-transaction, or it + ** is already in a read-transaction and a read-transaction + ** is requested, this is a no-op. + */ + if( p->inTrans==TRANS_WRITE || (p->inTrans==TRANS_READ && !wrflag) ){ + goto trans_begun; + } + assert( pBt->inTransaction==TRANS_WRITE || IfNotOmitAV(pBt->bDoTruncate)==0 ); + + if( (p->db->flags & SQLITE_ResetDatabase) + && sqlite3PagerIsreadonly(pPager)==0 + ){ + pBt->btsFlags &= ~BTS_READ_ONLY; + } + + /* Write transactions are not possible on a read-only database */ + if( (pBt->btsFlags & BTS_READ_ONLY)!=0 && wrflag ){ + rc = SQLITE_READONLY; + goto trans_begun; + } + +#ifndef SQLITE_OMIT_SHARED_CACHE + { + sqlite3 *pBlock = 0; + /* If another database handle has already opened a write transaction + ** on this shared-btree structure and a second write transaction is + ** requested, return SQLITE_LOCKED. + */ + if( (wrflag && pBt->inTransaction==TRANS_WRITE) + || (pBt->btsFlags & BTS_PENDING)!=0 + ){ + pBlock = pBt->pWriter->db; + }else if( wrflag>1 ){ + BtLock *pIter; + for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){ + if( pIter->pBtree!=p ){ + pBlock = pIter->pBtree->db; + break; + } + } + } + if( pBlock ){ + sqlite3ConnectionBlocked(p->db, pBlock); + rc = SQLITE_LOCKED_SHAREDCACHE; + goto trans_begun; + } + } +#endif + + /* Any read-only or read-write transaction implies a read-lock on + ** page 1. So if some other shared-cache client already has a write-lock + ** on page 1, the transaction cannot be opened. */ + rc = querySharedCacheTableLock(p, SCHEMA_ROOT, READ_LOCK); + if( SQLITE_OK!=rc ) goto trans_begun; + + pBt->btsFlags &= ~BTS_INITIALLY_EMPTY; + if( pBt->nPage==0 ) pBt->btsFlags |= BTS_INITIALLY_EMPTY; + do { + sqlite3PagerWalDb(pPager, p->db); + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* If transitioning from no transaction directly to a write transaction, + ** block for the WRITER lock first if possible. */ + if( pBt->pPage1==0 && wrflag ){ + assert( pBt->inTransaction==TRANS_NONE ); + rc = sqlite3PagerWalWriteLock(pPager, 1); + if( rc!=SQLITE_BUSY && rc!=SQLITE_OK ) break; + } +#endif + + /* Call lockBtree() until either pBt->pPage1 is populated or + ** lockBtree() returns something other than SQLITE_OK. lockBtree() + ** may return SQLITE_OK but leave pBt->pPage1 set to 0 if after + ** reading page 1 it discovers that the page-size of the database + ** file is not pBt->pageSize. In this case lockBtree() will update + ** pBt->pageSize to the page-size of the file on disk. + */ + while( pBt->pPage1==0 && SQLITE_OK==(rc = lockBtree(pBt)) ); + + if( rc==SQLITE_OK && wrflag ){ + if( (pBt->btsFlags & BTS_READ_ONLY)!=0 ){ + rc = SQLITE_READONLY; + }else{ + rc = sqlite3PagerBegin(pPager, wrflag>1, sqlite3TempInMemory(p->db)); + if( rc==SQLITE_OK ){ + rc = newDatabase(pBt); + }else if( rc==SQLITE_BUSY_SNAPSHOT && pBt->inTransaction==TRANS_NONE ){ + /* if there was no transaction opened when this function was + ** called and SQLITE_BUSY_SNAPSHOT is returned, change the error + ** code to SQLITE_BUSY. */ + rc = SQLITE_BUSY; + } + } + } + + if( rc!=SQLITE_OK ){ + (void)sqlite3PagerWalWriteLock(pPager, 0); + unlockBtreeIfUnused(pBt); + } + }while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE && + btreeInvokeBusyHandler(pBt) ); + sqlite3PagerWalDb(pPager, 0); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_BUSY_TIMEOUT ) rc = SQLITE_BUSY; +#endif + + if( rc==SQLITE_OK ){ + if( p->inTrans==TRANS_NONE ){ + pBt->nTransaction++; +#ifndef SQLITE_OMIT_SHARED_CACHE + if( p->sharable ){ + assert( p->lock.pBtree==p && p->lock.iTable==1 ); + p->lock.eLock = READ_LOCK; + p->lock.pNext = pBt->pLock; + pBt->pLock = &p->lock; + } +#endif + } + p->inTrans = (wrflag?TRANS_WRITE:TRANS_READ); + if( p->inTrans>pBt->inTransaction ){ + pBt->inTransaction = p->inTrans; + } + if( wrflag ){ + MemPage *pPage1 = pBt->pPage1; +#ifndef SQLITE_OMIT_SHARED_CACHE + assert( !pBt->pWriter ); + pBt->pWriter = p; + pBt->btsFlags &= ~BTS_EXCLUSIVE; + if( wrflag>1 ) pBt->btsFlags |= BTS_EXCLUSIVE; +#endif + + /* If the db-size header field is incorrect (as it may be if an old + ** client has been writing the database file), update it now. Doing + ** this sooner rather than later means the database size can safely + ** re-read the database size from page 1 if a savepoint or transaction + ** rollback occurs within the transaction. + */ + if( pBt->nPage!=get4byte(&pPage1->aData[28]) ){ + rc = sqlite3PagerWrite(pPage1->pDbPage); + if( rc==SQLITE_OK ){ + put4byte(&pPage1->aData[28], pBt->nPage); + } + } + } + } + +trans_begun: + if( rc==SQLITE_OK ){ + if( pSchemaVersion ){ + *pSchemaVersion = get4byte(&pBt->pPage1->aData[40]); + } + if( wrflag ){ + /* This call makes sure that the pager has the correct number of + ** open savepoints. If the second parameter is greater than 0 and + ** the sub-journal is not already open, then it will be opened here. + */ + rc = sqlite3PagerOpenSavepoint(pPager, p->db->nSavepoint); + } + } + + btreeIntegrity(p); + sqlite3BtreeLeave(p); + return rc; +} +SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ + BtShared *pBt; + if( p->sharable + || p->inTrans==TRANS_NONE + || (p->inTrans==TRANS_READ && wrflag!=0) + ){ + return btreeBeginTrans(p,wrflag,pSchemaVersion); + } + pBt = p->pBt; + if( pSchemaVersion ){ + *pSchemaVersion = get4byte(&pBt->pPage1->aData[40]); + } + if( wrflag ){ + /* This call makes sure that the pager has the correct number of + ** open savepoints. If the second parameter is greater than 0 and + ** the sub-journal is not already open, then it will be opened here. + */ + return sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint); + }else{ + return SQLITE_OK; + } +} + +#ifndef SQLITE_OMIT_AUTOVACUUM + +/* +** Set the pointer-map entries for all children of page pPage. Also, if +** pPage contains cells that point to overflow pages, set the pointer +** map entries for the overflow pages as well. +*/ +static int setChildPtrmaps(MemPage *pPage){ + int i; /* Counter variable */ + int nCell; /* Number of cells in page pPage */ + int rc; /* Return code */ + BtShared *pBt = pPage->pBt; + Pgno pgno = pPage->pgno; + + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + rc = pPage->isInit ? SQLITE_OK : btreeInitPage(pPage); + if( rc!=SQLITE_OK ) return rc; + nCell = pPage->nCell; + + for(i=0; i<nCell; i++){ + u8 *pCell = findCell(pPage, i); + + ptrmapPutOvflPtr(pPage, pPage, pCell, &rc); + + if( !pPage->leaf ){ + Pgno childPgno = get4byte(pCell); + ptrmapPut(pBt, childPgno, PTRMAP_BTREE, pgno, &rc); + } + } + + if( !pPage->leaf ){ + Pgno childPgno = get4byte(&pPage->aData[pPage->hdrOffset+8]); + ptrmapPut(pBt, childPgno, PTRMAP_BTREE, pgno, &rc); + } + + return rc; +} + +/* +** Somewhere on pPage is a pointer to page iFrom. Modify this pointer so +** that it points to iTo. Parameter eType describes the type of pointer to +** be modified, as follows: +** +** PTRMAP_BTREE: pPage is a btree-page. The pointer points at a child +** page of pPage. +** +** PTRMAP_OVERFLOW1: pPage is a btree-page. The pointer points at an overflow +** page pointed to by one of the cells on pPage. +** +** PTRMAP_OVERFLOW2: pPage is an overflow-page. The pointer points at the next +** overflow page in the list. +*/ +static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + if( eType==PTRMAP_OVERFLOW2 ){ + /* The pointer is always the first 4 bytes of the page in this case. */ + if( get4byte(pPage->aData)!=iFrom ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + put4byte(pPage->aData, iTo); + }else{ + int i; + int nCell; + int rc; + + rc = pPage->isInit ? SQLITE_OK : btreeInitPage(pPage); + if( rc ) return rc; + nCell = pPage->nCell; + + for(i=0; i<nCell; i++){ + u8 *pCell = findCell(pPage, i); + if( eType==PTRMAP_OVERFLOW1 ){ + CellInfo info; + pPage->xParseCell(pPage, pCell, &info); + if( info.nLocal<info.nPayload ){ + if( pCell+info.nSize > pPage->aData+pPage->pBt->usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + if( iFrom==get4byte(pCell+info.nSize-4) ){ + put4byte(pCell+info.nSize-4, iTo); + break; + } + } + }else{ + if( pCell+4 > pPage->aData+pPage->pBt->usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + if( get4byte(pCell)==iFrom ){ + put4byte(pCell, iTo); + break; + } + } + } + + if( i==nCell ){ + if( eType!=PTRMAP_BTREE || + get4byte(&pPage->aData[pPage->hdrOffset+8])!=iFrom ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + put4byte(&pPage->aData[pPage->hdrOffset+8], iTo); + } + } + return SQLITE_OK; +} + + +/* +** Move the open database page pDbPage to location iFreePage in the +** database. The pDbPage reference remains valid. +** +** The isCommit flag indicates that there is no need to remember that +** the journal needs to be sync()ed before database page pDbPage->pgno +** can be written to. The caller has already promised not to write to that +** page. +*/ +static int relocatePage( + BtShared *pBt, /* Btree */ + MemPage *pDbPage, /* Open page to move */ + u8 eType, /* Pointer map 'type' entry for pDbPage */ + Pgno iPtrPage, /* Pointer map 'page-no' entry for pDbPage */ + Pgno iFreePage, /* The location to move pDbPage to */ + int isCommit /* isCommit flag passed to sqlite3PagerMovepage */ +){ + MemPage *pPtrPage; /* The page that contains a pointer to pDbPage */ + Pgno iDbPage = pDbPage->pgno; + Pager *pPager = pBt->pPager; + int rc; + + assert( eType==PTRMAP_OVERFLOW2 || eType==PTRMAP_OVERFLOW1 || + eType==PTRMAP_BTREE || eType==PTRMAP_ROOTPAGE ); + assert( sqlite3_mutex_held(pBt->mutex) ); + assert( pDbPage->pBt==pBt ); + if( iDbPage<3 ) return SQLITE_CORRUPT_BKPT; + + /* Move page iDbPage from its current location to page number iFreePage */ + TRACE(("AUTOVACUUM: Moving %u to free page %u (ptr page %u type %u)\n", + iDbPage, iFreePage, iPtrPage, eType)); + rc = sqlite3PagerMovepage(pPager, pDbPage->pDbPage, iFreePage, isCommit); + if( rc!=SQLITE_OK ){ + return rc; + } + pDbPage->pgno = iFreePage; + + /* If pDbPage was a btree-page, then it may have child pages and/or cells + ** that point to overflow pages. The pointer map entries for all these + ** pages need to be changed. + ** + ** If pDbPage is an overflow page, then the first 4 bytes may store a + ** pointer to a subsequent overflow page. If this is the case, then + ** the pointer map needs to be updated for the subsequent overflow page. + */ + if( eType==PTRMAP_BTREE || eType==PTRMAP_ROOTPAGE ){ + rc = setChildPtrmaps(pDbPage); + if( rc!=SQLITE_OK ){ + return rc; + } + }else{ + Pgno nextOvfl = get4byte(pDbPage->aData); + if( nextOvfl!=0 ){ + ptrmapPut(pBt, nextOvfl, PTRMAP_OVERFLOW2, iFreePage, &rc); + if( rc!=SQLITE_OK ){ + return rc; + } + } + } + + /* Fix the database pointer on page iPtrPage that pointed at iDbPage so + ** that it points at iFreePage. Also fix the pointer map entry for + ** iPtrPage. + */ + if( eType!=PTRMAP_ROOTPAGE ){ + rc = btreeGetPage(pBt, iPtrPage, &pPtrPage, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + rc = sqlite3PagerWrite(pPtrPage->pDbPage); + if( rc!=SQLITE_OK ){ + releasePage(pPtrPage); + return rc; + } + rc = modifyPagePointer(pPtrPage, iDbPage, iFreePage, eType); + releasePage(pPtrPage); + if( rc==SQLITE_OK ){ + ptrmapPut(pBt, iFreePage, eType, iPtrPage, &rc); + } + } + return rc; +} + +/* Forward declaration required by incrVacuumStep(). */ +static int allocateBtreePage(BtShared *, MemPage **, Pgno *, Pgno, u8); + +/* +** Perform a single step of an incremental-vacuum. If successful, return +** SQLITE_OK. If there is no work to do (and therefore no point in +** calling this function again), return SQLITE_DONE. Or, if an error +** occurs, return some other error code. +** +** More specifically, this function attempts to re-organize the database so +** that the last page of the file currently in use is no longer in use. +** +** Parameter nFin is the number of pages that this database would contain +** were this function called until it returns SQLITE_DONE. +** +** If the bCommit parameter is non-zero, this function assumes that the +** caller will keep calling incrVacuumStep() until it returns SQLITE_DONE +** or an error. bCommit is passed true for an auto-vacuum-on-commit +** operation, or false for an incremental vacuum. +*/ +static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ + Pgno nFreeList; /* Number of pages still on the free-list */ + int rc; + + assert( sqlite3_mutex_held(pBt->mutex) ); + assert( iLastPg>nFin ); + + if( !PTRMAP_ISPAGE(pBt, iLastPg) && iLastPg!=PENDING_BYTE_PAGE(pBt) ){ + u8 eType; + Pgno iPtrPage; + + nFreeList = get4byte(&pBt->pPage1->aData[36]); + if( nFreeList==0 ){ + return SQLITE_DONE; + } + + rc = ptrmapGet(pBt, iLastPg, &eType, &iPtrPage); + if( rc!=SQLITE_OK ){ + return rc; + } + if( eType==PTRMAP_ROOTPAGE ){ + return SQLITE_CORRUPT_BKPT; + } + + if( eType==PTRMAP_FREEPAGE ){ + if( bCommit==0 ){ + /* Remove the page from the files free-list. This is not required + ** if bCommit is non-zero. In that case, the free-list will be + ** truncated to zero after this function returns, so it doesn't + ** matter if it still contains some garbage entries. + */ + Pgno iFreePg; + MemPage *pFreePg; + rc = allocateBtreePage(pBt, &pFreePg, &iFreePg, iLastPg, BTALLOC_EXACT); + if( rc!=SQLITE_OK ){ + return rc; + } + assert( iFreePg==iLastPg ); + releasePage(pFreePg); + } + } else { + Pgno iFreePg; /* Index of free page to move pLastPg to */ + MemPage *pLastPg; + u8 eMode = BTALLOC_ANY; /* Mode parameter for allocateBtreePage() */ + Pgno iNear = 0; /* nearby parameter for allocateBtreePage() */ + + rc = btreeGetPage(pBt, iLastPg, &pLastPg, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + + /* If bCommit is zero, this loop runs exactly once and page pLastPg + ** is swapped with the first free page pulled off the free list. + ** + ** On the other hand, if bCommit is greater than zero, then keep + ** looping until a free-page located within the first nFin pages + ** of the file is found. + */ + if( bCommit==0 ){ + eMode = BTALLOC_LE; + iNear = nFin; + } + do { + MemPage *pFreePg; + Pgno dbSize = btreePagecount(pBt); + rc = allocateBtreePage(pBt, &pFreePg, &iFreePg, iNear, eMode); + if( rc!=SQLITE_OK ){ + releasePage(pLastPg); + return rc; + } + releasePage(pFreePg); + if( iFreePg>dbSize ){ + releasePage(pLastPg); + return SQLITE_CORRUPT_BKPT; + } + }while( bCommit && iFreePg>nFin ); + assert( iFreePg<iLastPg ); + + rc = relocatePage(pBt, pLastPg, eType, iPtrPage, iFreePg, bCommit); + releasePage(pLastPg); + if( rc!=SQLITE_OK ){ + return rc; + } + } + } + + if( bCommit==0 ){ + do { + iLastPg--; + }while( iLastPg==PENDING_BYTE_PAGE(pBt) || PTRMAP_ISPAGE(pBt, iLastPg) ); + pBt->bDoTruncate = 1; + pBt->nPage = iLastPg; + } + return SQLITE_OK; +} + +/* +** The database opened by the first argument is an auto-vacuum database +** nOrig pages in size containing nFree free pages. Return the expected +** size of the database in pages following an auto-vacuum operation. +*/ +static Pgno finalDbSize(BtShared *pBt, Pgno nOrig, Pgno nFree){ + int nEntry; /* Number of entries on one ptrmap page */ + Pgno nPtrmap; /* Number of PtrMap pages to be freed */ + Pgno nFin; /* Return value */ + + nEntry = pBt->usableSize/5; + nPtrmap = (nFree-nOrig+PTRMAP_PAGENO(pBt, nOrig)+nEntry)/nEntry; + nFin = nOrig - nFree - nPtrmap; + if( nOrig>PENDING_BYTE_PAGE(pBt) && nFin<PENDING_BYTE_PAGE(pBt) ){ + nFin--; + } + while( PTRMAP_ISPAGE(pBt, nFin) || nFin==PENDING_BYTE_PAGE(pBt) ){ + nFin--; + } + + return nFin; +} + +/* +** A write-transaction must be opened before calling this function. +** It performs a single unit of work towards an incremental vacuum. +** +** If the incremental vacuum is finished after this function has run, +** SQLITE_DONE is returned. If it is not finished, but no error occurred, +** SQLITE_OK is returned. Otherwise an SQLite error code. +*/ +SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *p){ + int rc; + BtShared *pBt = p->pBt; + + sqlite3BtreeEnter(p); + assert( pBt->inTransaction==TRANS_WRITE && p->inTrans==TRANS_WRITE ); + if( !pBt->autoVacuum ){ + rc = SQLITE_DONE; + }else{ + Pgno nOrig = btreePagecount(pBt); + Pgno nFree = get4byte(&pBt->pPage1->aData[36]); + Pgno nFin = finalDbSize(pBt, nOrig, nFree); + + if( nOrig<nFin || nFree>=nOrig ){ + rc = SQLITE_CORRUPT_BKPT; + }else if( nFree>0 ){ + rc = saveAllCursors(pBt, 0, 0); + if( rc==SQLITE_OK ){ + invalidateAllOverflowCache(pBt); + rc = incrVacuumStep(pBt, nFin, nOrig, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3PagerWrite(pBt->pPage1->pDbPage); + put4byte(&pBt->pPage1->aData[28], pBt->nPage); + } + }else{ + rc = SQLITE_DONE; + } + } + sqlite3BtreeLeave(p); + return rc; +} + +/* +** This routine is called prior to sqlite3PagerCommit when a transaction +** is committed for an auto-vacuum database. +*/ +static int autoVacuumCommit(Btree *p){ + int rc = SQLITE_OK; + Pager *pPager; + BtShared *pBt; + sqlite3 *db; + VVA_ONLY( int nRef ); + + assert( p!=0 ); + pBt = p->pBt; + pPager = pBt->pPager; + VVA_ONLY( nRef = sqlite3PagerRefcount(pPager); ) + + assert( sqlite3_mutex_held(pBt->mutex) ); + invalidateAllOverflowCache(pBt); + assert(pBt->autoVacuum); + if( !pBt->incrVacuum ){ + Pgno nFin; /* Number of pages in database after autovacuuming */ + Pgno nFree; /* Number of pages on the freelist initially */ + Pgno nVac; /* Number of pages to vacuum */ + Pgno iFree; /* The next page to be freed */ + Pgno nOrig; /* Database size before freeing */ + + nOrig = btreePagecount(pBt); + if( PTRMAP_ISPAGE(pBt, nOrig) || nOrig==PENDING_BYTE_PAGE(pBt) ){ + /* It is not possible to create a database for which the final page + ** is either a pointer-map page or the pending-byte page. If one + ** is encountered, this indicates corruption. + */ + return SQLITE_CORRUPT_BKPT; + } + + nFree = get4byte(&pBt->pPage1->aData[36]); + db = p->db; + if( db->xAutovacPages ){ + int iDb; + for(iDb=0; ALWAYS(iDb<db->nDb); iDb++){ + if( db->aDb[iDb].pBt==p ) break; + } + nVac = db->xAutovacPages( + db->pAutovacPagesArg, + db->aDb[iDb].zDbSName, + nOrig, + nFree, + pBt->pageSize + ); + if( nVac>nFree ){ + nVac = nFree; + } + if( nVac==0 ){ + return SQLITE_OK; + } + }else{ + nVac = nFree; + } + nFin = finalDbSize(pBt, nOrig, nVac); + if( nFin>nOrig ) return SQLITE_CORRUPT_BKPT; + if( nFin<nOrig ){ + rc = saveAllCursors(pBt, 0, 0); + } + for(iFree=nOrig; iFree>nFin && rc==SQLITE_OK; iFree--){ + rc = incrVacuumStep(pBt, nFin, iFree, nVac==nFree); + } + if( (rc==SQLITE_DONE || rc==SQLITE_OK) && nFree>0 ){ + rc = sqlite3PagerWrite(pBt->pPage1->pDbPage); + if( nVac==nFree ){ + put4byte(&pBt->pPage1->aData[32], 0); + put4byte(&pBt->pPage1->aData[36], 0); + } + put4byte(&pBt->pPage1->aData[28], nFin); + pBt->bDoTruncate = 1; + pBt->nPage = nFin; + } + if( rc!=SQLITE_OK ){ + sqlite3PagerRollback(pPager); + } + } + + assert( nRef>=sqlite3PagerRefcount(pPager) ); + return rc; +} + +#else /* ifndef SQLITE_OMIT_AUTOVACUUM */ +# define setChildPtrmaps(x) SQLITE_OK +#endif + +/* +** This routine does the first phase of a two-phase commit. This routine +** causes a rollback journal to be created (if it does not already exist) +** and populated with enough information so that if a power loss occurs +** the database can be restored to its original state by playing back +** the journal. Then the contents of the journal are flushed out to +** the disk. After the journal is safely on oxide, the changes to the +** database are written into the database file and flushed to oxide. +** At the end of this call, the rollback journal still exists on the +** disk and we are still holding all locks, so the transaction has not +** committed. See sqlite3BtreeCommitPhaseTwo() for the second phase of the +** commit process. +** +** This call is a no-op if no write-transaction is currently active on pBt. +** +** Otherwise, sync the database file for the btree pBt. zSuperJrnl points to +** the name of a super-journal file that should be written into the +** individual journal file, or is NULL, indicating no super-journal file +** (single database transaction). +** +** When this is called, the super-journal should already have been +** created, populated with this journal pointer and synced to disk. +** +** Once this is routine has returned, the only thing required to commit +** the write-transaction for this database file is to delete the journal. +*/ +SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zSuperJrnl){ + int rc = SQLITE_OK; + if( p->inTrans==TRANS_WRITE ){ + BtShared *pBt = p->pBt; + sqlite3BtreeEnter(p); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pBt->autoVacuum ){ + rc = autoVacuumCommit(p); + if( rc!=SQLITE_OK ){ + sqlite3BtreeLeave(p); + return rc; + } + } + if( pBt->bDoTruncate ){ + sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage); + } +#endif + rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zSuperJrnl, 0); + sqlite3BtreeLeave(p); + } + return rc; +} + +/* +** This function is called from both BtreeCommitPhaseTwo() and BtreeRollback() +** at the conclusion of a transaction. +*/ +static void btreeEndTransaction(Btree *p){ + BtShared *pBt = p->pBt; + sqlite3 *db = p->db; + assert( sqlite3BtreeHoldsMutex(p) ); + +#ifndef SQLITE_OMIT_AUTOVACUUM + pBt->bDoTruncate = 0; +#endif + if( p->inTrans>TRANS_NONE && db->nVdbeRead>1 ){ + /* If there are other active statements that belong to this database + ** handle, downgrade to a read-only transaction. The other statements + ** may still be reading from the database. */ + downgradeAllSharedCacheTableLocks(p); + p->inTrans = TRANS_READ; + }else{ + /* If the handle had any kind of transaction open, decrement the + ** transaction count of the shared btree. If the transaction count + ** reaches 0, set the shared state to TRANS_NONE. The unlockBtreeIfUnused() + ** call below will unlock the pager. */ + if( p->inTrans!=TRANS_NONE ){ + clearAllSharedCacheTableLocks(p); + pBt->nTransaction--; + if( 0==pBt->nTransaction ){ + pBt->inTransaction = TRANS_NONE; + } + } + + /* Set the current transaction state to TRANS_NONE and unlock the + ** pager if this call closed the only read or write transaction. */ + p->inTrans = TRANS_NONE; + unlockBtreeIfUnused(pBt); + } + + btreeIntegrity(p); +} + +/* +** Commit the transaction currently in progress. +** +** This routine implements the second phase of a 2-phase commit. The +** sqlite3BtreeCommitPhaseOne() routine does the first phase and should +** be invoked prior to calling this routine. The sqlite3BtreeCommitPhaseOne() +** routine did all the work of writing information out to disk and flushing the +** contents so that they are written onto the disk platter. All this +** routine has to do is delete or truncate or zero the header in the +** the rollback journal (which causes the transaction to commit) and +** drop locks. +** +** Normally, if an error occurs while the pager layer is attempting to +** finalize the underlying journal file, this function returns an error and +** the upper layer will attempt a rollback. However, if the second argument +** is non-zero then this b-tree transaction is part of a multi-file +** transaction. In this case, the transaction has already been committed +** (by deleting a super-journal file) and the caller will ignore this +** functions return code. So, even if an error occurs in the pager layer, +** reset the b-tree objects internal state to indicate that the write +** transaction has been closed. This is quite safe, as the pager will have +** transitioned to the error state. +** +** This will release the write lock on the database file. If there +** are no active cursors, it also releases the read lock. +*/ +SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){ + + if( p->inTrans==TRANS_NONE ) return SQLITE_OK; + sqlite3BtreeEnter(p); + btreeIntegrity(p); + + /* If the handle has a write-transaction open, commit the shared-btrees + ** transaction and set the shared state to TRANS_READ. + */ + if( p->inTrans==TRANS_WRITE ){ + int rc; + BtShared *pBt = p->pBt; + assert( pBt->inTransaction==TRANS_WRITE ); + assert( pBt->nTransaction>0 ); + rc = sqlite3PagerCommitPhaseTwo(pBt->pPager); + if( rc!=SQLITE_OK && bCleanup==0 ){ + sqlite3BtreeLeave(p); + return rc; + } + p->iBDataVersion--; /* Compensate for pPager->iDataVersion++; */ + pBt->inTransaction = TRANS_READ; + btreeClearHasContent(pBt); + } + + btreeEndTransaction(p); + sqlite3BtreeLeave(p); + return SQLITE_OK; +} + +/* +** Do both phases of a commit. +*/ +SQLITE_PRIVATE int sqlite3BtreeCommit(Btree *p){ + int rc; + sqlite3BtreeEnter(p); + rc = sqlite3BtreeCommitPhaseOne(p, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3BtreeCommitPhaseTwo(p, 0); + } + sqlite3BtreeLeave(p); + return rc; +} + +/* +** This routine sets the state to CURSOR_FAULT and the error +** code to errCode for every cursor on any BtShared that pBtree +** references. Or if the writeOnly flag is set to 1, then only +** trip write cursors and leave read cursors unchanged. +** +** Every cursor is a candidate to be tripped, including cursors +** that belong to other database connections that happen to be +** sharing the cache with pBtree. +** +** This routine gets called when a rollback occurs. If the writeOnly +** flag is true, then only write-cursors need be tripped - read-only +** cursors save their current positions so that they may continue +** following the rollback. Or, if writeOnly is false, all cursors are +** tripped. In general, writeOnly is false if the transaction being +** rolled back modified the database schema. In this case b-tree root +** pages may be moved or deleted from the database altogether, making +** it unsafe for read cursors to continue. +** +** If the writeOnly flag is true and an error is encountered while +** saving the current position of a read-only cursor, all cursors, +** including all read-cursors are tripped. +** +** SQLITE_OK is returned if successful, or if an error occurs while +** saving a cursor position, an SQLite error code. +*/ +SQLITE_PRIVATE int sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode, int writeOnly){ + BtCursor *p; + int rc = SQLITE_OK; + + assert( (writeOnly==0 || writeOnly==1) && BTCF_WriteFlag==1 ); + if( pBtree ){ + sqlite3BtreeEnter(pBtree); + for(p=pBtree->pBt->pCursor; p; p=p->pNext){ + if( writeOnly && (p->curFlags & BTCF_WriteFlag)==0 ){ + if( p->eState==CURSOR_VALID || p->eState==CURSOR_SKIPNEXT ){ + rc = saveCursorPosition(p); + if( rc!=SQLITE_OK ){ + (void)sqlite3BtreeTripAllCursors(pBtree, rc, 0); + break; + } + } + }else{ + sqlite3BtreeClearCursor(p); + p->eState = CURSOR_FAULT; + p->skipNext = errCode; + } + btreeReleaseAllCursorPages(p); + } + sqlite3BtreeLeave(pBtree); + } + return rc; +} + +/* +** Set the pBt->nPage field correctly, according to the current +** state of the database. Assume pBt->pPage1 is valid. +*/ +static void btreeSetNPage(BtShared *pBt, MemPage *pPage1){ + int nPage = get4byte(&pPage1->aData[28]); + testcase( nPage==0 ); + if( nPage==0 ) sqlite3PagerPagecount(pBt->pPager, &nPage); + testcase( pBt->nPage!=(u32)nPage ); + pBt->nPage = nPage; +} + +/* +** Rollback the transaction in progress. +** +** If tripCode is not SQLITE_OK then cursors will be invalidated (tripped). +** Only write cursors are tripped if writeOnly is true but all cursors are +** tripped if writeOnly is false. Any attempt to use +** a tripped cursor will result in an error. +** +** This will release the write lock on the database file. If there +** are no active cursors, it also releases the read lock. +*/ +SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){ + int rc; + BtShared *pBt = p->pBt; + MemPage *pPage1; + + assert( writeOnly==1 || writeOnly==0 ); + assert( tripCode==SQLITE_ABORT_ROLLBACK || tripCode==SQLITE_OK ); + sqlite3BtreeEnter(p); + if( tripCode==SQLITE_OK ){ + rc = tripCode = saveAllCursors(pBt, 0, 0); + if( rc ) writeOnly = 0; + }else{ + rc = SQLITE_OK; + } + if( tripCode ){ + int rc2 = sqlite3BtreeTripAllCursors(p, tripCode, writeOnly); + assert( rc==SQLITE_OK || (writeOnly==0 && rc2==SQLITE_OK) ); + if( rc2!=SQLITE_OK ) rc = rc2; + } + btreeIntegrity(p); + + if( p->inTrans==TRANS_WRITE ){ + int rc2; + + assert( TRANS_WRITE==pBt->inTransaction ); + rc2 = sqlite3PagerRollback(pBt->pPager); + if( rc2!=SQLITE_OK ){ + rc = rc2; + } + + /* The rollback may have destroyed the pPage1->aData value. So + ** call btreeGetPage() on page 1 again to make + ** sure pPage1->aData is set correctly. */ + if( btreeGetPage(pBt, 1, &pPage1, 0)==SQLITE_OK ){ + btreeSetNPage(pBt, pPage1); + releasePageOne(pPage1); + } + assert( countValidCursors(pBt, 1)==0 ); + pBt->inTransaction = TRANS_READ; + btreeClearHasContent(pBt); + } + + btreeEndTransaction(p); + sqlite3BtreeLeave(p); + return rc; +} + +/* +** Start a statement subtransaction. The subtransaction can be rolled +** back independently of the main transaction. You must start a transaction +** before starting a subtransaction. The subtransaction is ended automatically +** if the main transaction commits or rolls back. +** +** Statement subtransactions are used around individual SQL statements +** that are contained within a BEGIN...COMMIT block. If a constraint +** error occurs within the statement, the effect of that one statement +** can be rolled back without having to rollback the entire transaction. +** +** A statement sub-transaction is implemented as an anonymous savepoint. The +** value passed as the second parameter is the total number of savepoints, +** including the new anonymous savepoint, open on the B-Tree. i.e. if there +** are no active savepoints and no other statement-transactions open, +** iStatement is 1. This anonymous savepoint can be released or rolled back +** using the sqlite3BtreeSavepoint() function. +*/ +SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree *p, int iStatement){ + int rc; + BtShared *pBt = p->pBt; + sqlite3BtreeEnter(p); + assert( p->inTrans==TRANS_WRITE ); + assert( (pBt->btsFlags & BTS_READ_ONLY)==0 ); + assert( iStatement>0 ); + assert( iStatement>p->db->nSavepoint ); + assert( pBt->inTransaction==TRANS_WRITE ); + /* At the pager level, a statement transaction is a savepoint with + ** an index greater than all savepoints created explicitly using + ** SQL statements. It is illegal to open, release or rollback any + ** such savepoints while the statement transaction savepoint is active. + */ + rc = sqlite3PagerOpenSavepoint(pBt->pPager, iStatement); + sqlite3BtreeLeave(p); + return rc; +} + +/* +** The second argument to this function, op, is always SAVEPOINT_ROLLBACK +** or SAVEPOINT_RELEASE. This function either releases or rolls back the +** savepoint identified by parameter iSavepoint, depending on the value +** of op. +** +** Normally, iSavepoint is greater than or equal to zero. However, if op is +** SAVEPOINT_ROLLBACK, then iSavepoint may also be -1. In this case the +** contents of the entire transaction are rolled back. This is different +** from a normal transaction rollback, as no locks are released and the +** transaction remains open. +*/ +SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ + int rc = SQLITE_OK; + if( p && p->inTrans==TRANS_WRITE ){ + BtShared *pBt = p->pBt; + assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK ); + assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) ); + sqlite3BtreeEnter(p); + if( op==SAVEPOINT_ROLLBACK ){ + rc = saveAllCursors(pBt, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint); + } + if( rc==SQLITE_OK ){ + if( iSavepoint<0 && (pBt->btsFlags & BTS_INITIALLY_EMPTY)!=0 ){ + pBt->nPage = 0; + } + rc = newDatabase(pBt); + btreeSetNPage(pBt, pBt->pPage1); + + /* pBt->nPage might be zero if the database was corrupt when + ** the transaction was started. Otherwise, it must be at least 1. */ + assert( CORRUPT_DB || pBt->nPage>0 ); + } + sqlite3BtreeLeave(p); + } + return rc; +} + +/* +** Create a new cursor for the BTree whose root is on the page +** iTable. If a read-only cursor is requested, it is assumed that +** the caller already has at least a read-only transaction open +** on the database already. If a write-cursor is requested, then +** the caller is assumed to have an open write transaction. +** +** If the BTREE_WRCSR bit of wrFlag is clear, then the cursor can only +** be used for reading. If the BTREE_WRCSR bit is set, then the cursor +** can be used for reading or for writing if other conditions for writing +** are also met. These are the conditions that must be met in order +** for writing to be allowed: +** +** 1: The cursor must have been opened with wrFlag containing BTREE_WRCSR +** +** 2: Other database connections that share the same pager cache +** but which are not in the READ_UNCOMMITTED state may not have +** cursors open with wrFlag==0 on the same table. Otherwise +** the changes made by this write cursor would be visible to +** the read cursors in the other database connection. +** +** 3: The database must be writable (not on read-only media) +** +** 4: There must be an active transaction. +** +** The BTREE_FORDELETE bit of wrFlag may optionally be set if BTREE_WRCSR +** is set. If FORDELETE is set, that is a hint to the implementation that +** this cursor will only be used to seek to and delete entries of an index +** as part of a larger DELETE statement. The FORDELETE hint is not used by +** this implementation. But in a hypothetical alternative storage engine +** in which index entries are automatically deleted when corresponding table +** rows are deleted, the FORDELETE flag is a hint that all SEEK and DELETE +** operations on this cursor can be no-ops and all READ operations can +** return a null row (2-bytes: 0x01 0x00). +** +** No checking is done to make sure that page iTable really is the +** root page of a b-tree. If it is not, then the cursor acquired +** will not work correctly. +** +** It is assumed that the sqlite3BtreeCursorZero() has been called +** on pCur to initialize the memory space prior to invoking this routine. +*/ +static int btreeCursor( + Btree *p, /* The btree */ + Pgno iTable, /* Root page of table to open */ + int wrFlag, /* 1 to write. 0 read-only */ + struct KeyInfo *pKeyInfo, /* First arg to comparison function */ + BtCursor *pCur /* Space for new cursor */ +){ + BtShared *pBt = p->pBt; /* Shared b-tree handle */ + BtCursor *pX; /* Looping over other all cursors */ + + assert( sqlite3BtreeHoldsMutex(p) ); + assert( wrFlag==0 + || wrFlag==BTREE_WRCSR + || wrFlag==(BTREE_WRCSR|BTREE_FORDELETE) + ); + + /* The following assert statements verify that if this is a sharable + ** b-tree database, the connection is holding the required table locks, + ** and that no other connection has any open cursor that conflicts with + ** this lock. The iTable<1 term disables the check for corrupt schemas. */ + assert( hasSharedCacheTableLock(p, iTable, pKeyInfo!=0, (wrFlag?2:1)) + || iTable<1 ); + assert( wrFlag==0 || !hasReadConflicts(p, iTable) ); + + /* Assert that the caller has opened the required transaction. */ + assert( p->inTrans>TRANS_NONE ); + assert( wrFlag==0 || p->inTrans==TRANS_WRITE ); + assert( pBt->pPage1 && pBt->pPage1->aData ); + assert( wrFlag==0 || (pBt->btsFlags & BTS_READ_ONLY)==0 ); + + if( iTable<=1 ){ + if( iTable<1 ){ + return SQLITE_CORRUPT_BKPT; + }else if( btreePagecount(pBt)==0 ){ + assert( wrFlag==0 ); + iTable = 0; + } + } + + /* Now that no other errors can occur, finish filling in the BtCursor + ** variables and link the cursor into the BtShared list. */ + pCur->pgnoRoot = iTable; + pCur->iPage = -1; + pCur->pKeyInfo = pKeyInfo; + pCur->pBtree = p; + pCur->pBt = pBt; + pCur->curFlags = 0; + /* If there are two or more cursors on the same btree, then all such + ** cursors *must* have the BTCF_Multiple flag set. */ + for(pX=pBt->pCursor; pX; pX=pX->pNext){ + if( pX->pgnoRoot==iTable ){ + pX->curFlags |= BTCF_Multiple; + pCur->curFlags = BTCF_Multiple; + } + } + pCur->eState = CURSOR_INVALID; + pCur->pNext = pBt->pCursor; + pBt->pCursor = pCur; + if( wrFlag ){ + pCur->curFlags |= BTCF_WriteFlag; + pCur->curPagerFlags = 0; + if( pBt->pTmpSpace==0 ) return allocateTempSpace(pBt); + }else{ + pCur->curPagerFlags = PAGER_GET_READONLY; + } + return SQLITE_OK; +} +static int btreeCursorWithLock( + Btree *p, /* The btree */ + Pgno iTable, /* Root page of table to open */ + int wrFlag, /* 1 to write. 0 read-only */ + struct KeyInfo *pKeyInfo, /* First arg to comparison function */ + BtCursor *pCur /* Space for new cursor */ +){ + int rc; + sqlite3BtreeEnter(p); + rc = btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur); + sqlite3BtreeLeave(p); + return rc; +} +SQLITE_PRIVATE int sqlite3BtreeCursor( + Btree *p, /* The btree */ + Pgno iTable, /* Root page of table to open */ + int wrFlag, /* 1 to write. 0 read-only */ + struct KeyInfo *pKeyInfo, /* First arg to xCompare() */ + BtCursor *pCur /* Write new cursor here */ +){ + if( p->sharable ){ + return btreeCursorWithLock(p, iTable, wrFlag, pKeyInfo, pCur); + }else{ + return btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur); + } +} + +/* +** Return the size of a BtCursor object in bytes. +** +** This interfaces is needed so that users of cursors can preallocate +** sufficient storage to hold a cursor. The BtCursor object is opaque +** to users so they cannot do the sizeof() themselves - they must call +** this routine. +*/ +SQLITE_PRIVATE int sqlite3BtreeCursorSize(void){ + return ROUND8(sizeof(BtCursor)); +} + +/* +** Initialize memory that will be converted into a BtCursor object. +** +** The simple approach here would be to memset() the entire object +** to zero. But it turns out that the apPage[] and aiIdx[] arrays +** do not need to be zeroed and they are large, so we can save a lot +** of run-time by skipping the initialization of those elements. +*/ +SQLITE_PRIVATE void sqlite3BtreeCursorZero(BtCursor *p){ + memset(p, 0, offsetof(BtCursor, BTCURSOR_FIRST_UNINIT)); +} + +/* +** Close a cursor. The read lock on the database file is released +** when the last cursor is closed. +*/ +SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){ + Btree *pBtree = pCur->pBtree; + if( pBtree ){ + BtShared *pBt = pCur->pBt; + sqlite3BtreeEnter(pBtree); + assert( pBt->pCursor!=0 ); + if( pBt->pCursor==pCur ){ + pBt->pCursor = pCur->pNext; + }else{ + BtCursor *pPrev = pBt->pCursor; + do{ + if( pPrev->pNext==pCur ){ + pPrev->pNext = pCur->pNext; + break; + } + pPrev = pPrev->pNext; + }while( ALWAYS(pPrev) ); + } + btreeReleaseAllCursorPages(pCur); + unlockBtreeIfUnused(pBt); + sqlite3_free(pCur->aOverflow); + sqlite3_free(pCur->pKey); + if( (pBt->openFlags & BTREE_SINGLE) && pBt->pCursor==0 ){ + /* Since the BtShared is not sharable, there is no need to + ** worry about the missing sqlite3BtreeLeave() call here. */ + assert( pBtree->sharable==0 ); + sqlite3BtreeClose(pBtree); + }else{ + sqlite3BtreeLeave(pBtree); + } + pCur->pBtree = 0; + } + return SQLITE_OK; +} + +/* +** Make sure the BtCursor* given in the argument has a valid +** BtCursor.info structure. If it is not already valid, call +** btreeParseCell() to fill it in. +** +** BtCursor.info is a cache of the information in the current cell. +** Using this cache reduces the number of calls to btreeParseCell(). +*/ +#ifndef NDEBUG + static int cellInfoEqual(CellInfo *a, CellInfo *b){ + if( a->nKey!=b->nKey ) return 0; + if( a->pPayload!=b->pPayload ) return 0; + if( a->nPayload!=b->nPayload ) return 0; + if( a->nLocal!=b->nLocal ) return 0; + if( a->nSize!=b->nSize ) return 0; + return 1; + } + static void assertCellInfo(BtCursor *pCur){ + CellInfo info; + memset(&info, 0, sizeof(info)); + btreeParseCell(pCur->pPage, pCur->ix, &info); + assert( CORRUPT_DB || cellInfoEqual(&info, &pCur->info) ); + } +#else + #define assertCellInfo(x) +#endif +static SQLITE_NOINLINE void getCellInfo(BtCursor *pCur){ + if( pCur->info.nSize==0 ){ + pCur->curFlags |= BTCF_ValidNKey; + btreeParseCell(pCur->pPage,pCur->ix,&pCur->info); + }else{ + assertCellInfo(pCur); + } +} + +#ifndef NDEBUG /* The next routine used only within assert() statements */ +/* +** Return true if the given BtCursor is valid. A valid cursor is one +** that is currently pointing to a row in a (non-empty) table. +** This is a verification routine is used only within assert() statements. +*/ +SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor *pCur){ + return pCur && pCur->eState==CURSOR_VALID; +} +#endif /* NDEBUG */ +SQLITE_PRIVATE int sqlite3BtreeCursorIsValidNN(BtCursor *pCur){ + assert( pCur!=0 ); + return pCur->eState==CURSOR_VALID; +} + +/* +** Return the value of the integer key or "rowid" for a table btree. +** This routine is only valid for a cursor that is pointing into a +** ordinary table btree. If the cursor points to an index btree or +** is invalid, the result of this routine is undefined. +*/ +SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor *pCur){ + assert( cursorHoldsMutex(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + assert( pCur->curIntKey ); + getCellInfo(pCur); + return pCur->info.nKey; +} + +/* +** Pin or unpin a cursor. +*/ +SQLITE_PRIVATE void sqlite3BtreeCursorPin(BtCursor *pCur){ + assert( (pCur->curFlags & BTCF_Pinned)==0 ); + pCur->curFlags |= BTCF_Pinned; +} +SQLITE_PRIVATE void sqlite3BtreeCursorUnpin(BtCursor *pCur){ + assert( (pCur->curFlags & BTCF_Pinned)!=0 ); + pCur->curFlags &= ~BTCF_Pinned; +} + +/* +** Return the offset into the database file for the start of the +** payload to which the cursor is pointing. +*/ +SQLITE_PRIVATE i64 sqlite3BtreeOffset(BtCursor *pCur){ + assert( cursorHoldsMutex(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + getCellInfo(pCur); + return (i64)pCur->pBt->pageSize*((i64)pCur->pPage->pgno - 1) + + (i64)(pCur->info.pPayload - pCur->pPage->aData); +} + +/* +** Return the number of bytes of payload for the entry that pCur is +** currently pointing to. For table btrees, this will be the amount +** of data. For index btrees, this will be the size of the key. +** +** The caller must guarantee that the cursor is pointing to a non-NULL +** valid entry. In other words, the calling procedure must guarantee +** that the cursor has Cursor.eState==CURSOR_VALID. +*/ +SQLITE_PRIVATE u32 sqlite3BtreePayloadSize(BtCursor *pCur){ + assert( cursorHoldsMutex(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + getCellInfo(pCur); + return pCur->info.nPayload; +} + +/* +** Return an upper bound on the size of any record for the table +** that the cursor is pointing into. +** +** This is an optimization. Everything will still work if this +** routine always returns 2147483647 (which is the largest record +** that SQLite can handle) or more. But returning a smaller value might +** prevent large memory allocations when trying to interpret a +** corrupt database. +** +** The current implementation merely returns the size of the underlying +** database file. +*/ +SQLITE_PRIVATE sqlite3_int64 sqlite3BtreeMaxRecordSize(BtCursor *pCur){ + assert( cursorHoldsMutex(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + return pCur->pBt->pageSize * (sqlite3_int64)pCur->pBt->nPage; +} + +/* +** Given the page number of an overflow page in the database (parameter +** ovfl), this function finds the page number of the next page in the +** linked list of overflow pages. If possible, it uses the auto-vacuum +** pointer-map data instead of reading the content of page ovfl to do so. +** +** If an error occurs an SQLite error code is returned. Otherwise: +** +** The page number of the next overflow page in the linked list is +** written to *pPgnoNext. If page ovfl is the last page in its linked +** list, *pPgnoNext is set to zero. +** +** If ppPage is not NULL, and a reference to the MemPage object corresponding +** to page number pOvfl was obtained, then *ppPage is set to point to that +** reference. It is the responsibility of the caller to call releasePage() +** on *ppPage to free the reference. In no reference was obtained (because +** the pointer-map was used to obtain the value for *pPgnoNext), then +** *ppPage is set to zero. +*/ +static int getOverflowPage( + BtShared *pBt, /* The database file */ + Pgno ovfl, /* Current overflow page number */ + MemPage **ppPage, /* OUT: MemPage handle (may be NULL) */ + Pgno *pPgnoNext /* OUT: Next overflow page number */ +){ + Pgno next = 0; + MemPage *pPage = 0; + int rc = SQLITE_OK; + + assert( sqlite3_mutex_held(pBt->mutex) ); + assert(pPgnoNext); + +#ifndef SQLITE_OMIT_AUTOVACUUM + /* Try to find the next page in the overflow list using the + ** autovacuum pointer-map pages. Guess that the next page in + ** the overflow list is page number (ovfl+1). If that guess turns + ** out to be wrong, fall back to loading the data of page + ** number ovfl to determine the next page number. + */ + if( pBt->autoVacuum ){ + Pgno pgno; + Pgno iGuess = ovfl+1; + u8 eType; + + while( PTRMAP_ISPAGE(pBt, iGuess) || iGuess==PENDING_BYTE_PAGE(pBt) ){ + iGuess++; + } + + if( iGuess<=btreePagecount(pBt) ){ + rc = ptrmapGet(pBt, iGuess, &eType, &pgno); + if( rc==SQLITE_OK && eType==PTRMAP_OVERFLOW2 && pgno==ovfl ){ + next = iGuess; + rc = SQLITE_DONE; + } + } + } +#endif + + assert( next==0 || rc==SQLITE_DONE ); + if( rc==SQLITE_OK ){ + rc = btreeGetPage(pBt, ovfl, &pPage, (ppPage==0) ? PAGER_GET_READONLY : 0); + assert( rc==SQLITE_OK || pPage==0 ); + if( rc==SQLITE_OK ){ + next = get4byte(pPage->aData); + } + } + + *pPgnoNext = next; + if( ppPage ){ + *ppPage = pPage; + }else{ + releasePage(pPage); + } + return (rc==SQLITE_DONE ? SQLITE_OK : rc); +} + +/* +** Copy data from a buffer to a page, or from a page to a buffer. +** +** pPayload is a pointer to data stored on database page pDbPage. +** If argument eOp is false, then nByte bytes of data are copied +** from pPayload to the buffer pointed at by pBuf. If eOp is true, +** then sqlite3PagerWrite() is called on pDbPage and nByte bytes +** of data are copied from the buffer pBuf to pPayload. +** +** SQLITE_OK is returned on success, otherwise an error code. +*/ +static int copyPayload( + void *pPayload, /* Pointer to page data */ + void *pBuf, /* Pointer to buffer */ + int nByte, /* Number of bytes to copy */ + int eOp, /* 0 -> copy from page, 1 -> copy to page */ + DbPage *pDbPage /* Page containing pPayload */ +){ + if( eOp ){ + /* Copy data from buffer to page (a write operation) */ + int rc = sqlite3PagerWrite(pDbPage); + if( rc!=SQLITE_OK ){ + return rc; + } + memcpy(pPayload, pBuf, nByte); + }else{ + /* Copy data from page to buffer (a read operation) */ + memcpy(pBuf, pPayload, nByte); + } + return SQLITE_OK; +} + +/* +** This function is used to read or overwrite payload information +** for the entry that the pCur cursor is pointing to. The eOp +** argument is interpreted as follows: +** +** 0: The operation is a read. Populate the overflow cache. +** 1: The operation is a write. Populate the overflow cache. +** +** A total of "amt" bytes are read or written beginning at "offset". +** Data is read to or from the buffer pBuf. +** +** The content being read or written might appear on the main page +** or be scattered out on multiple overflow pages. +** +** If the current cursor entry uses one or more overflow pages +** this function may allocate space for and lazily populate +** the overflow page-list cache array (BtCursor.aOverflow). +** Subsequent calls use this cache to make seeking to the supplied offset +** more efficient. +** +** Once an overflow page-list cache has been allocated, it must be +** invalidated if some other cursor writes to the same table, or if +** the cursor is moved to a different row. Additionally, in auto-vacuum +** mode, the following events may invalidate an overflow page-list cache. +** +** * An incremental vacuum, +** * A commit in auto_vacuum="full" mode, +** * Creating a table (may require moving an overflow page). +*/ +static int accessPayload( + BtCursor *pCur, /* Cursor pointing to entry to read from */ + u32 offset, /* Begin reading this far into payload */ + u32 amt, /* Read this many bytes */ + unsigned char *pBuf, /* Write the bytes into this buffer */ + int eOp /* zero to read. non-zero to write. */ +){ + unsigned char *aPayload; + int rc = SQLITE_OK; + int iIdx = 0; + MemPage *pPage = pCur->pPage; /* Btree page of current entry */ + BtShared *pBt = pCur->pBt; /* Btree this cursor belongs to */ +#ifdef SQLITE_DIRECT_OVERFLOW_READ + unsigned char * const pBufStart = pBuf; /* Start of original out buffer */ +#endif + + assert( pPage ); + assert( eOp==0 || eOp==1 ); + assert( pCur->eState==CURSOR_VALID ); + if( pCur->ix>=pPage->nCell ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + assert( cursorHoldsMutex(pCur) ); + + getCellInfo(pCur); + aPayload = pCur->info.pPayload; + assert( offset+amt <= pCur->info.nPayload ); + + assert( aPayload > pPage->aData ); + if( (uptr)(aPayload - pPage->aData) > (pBt->usableSize - pCur->info.nLocal) ){ + /* Trying to read or write past the end of the data is an error. The + ** conditional above is really: + ** &aPayload[pCur->info.nLocal] > &pPage->aData[pBt->usableSize] + ** but is recast into its current form to avoid integer overflow problems + */ + return SQLITE_CORRUPT_PAGE(pPage); + } + + /* Check if data must be read/written to/from the btree page itself. */ + if( offset<pCur->info.nLocal ){ + int a = amt; + if( a+offset>pCur->info.nLocal ){ + a = pCur->info.nLocal - offset; + } + rc = copyPayload(&aPayload[offset], pBuf, a, eOp, pPage->pDbPage); + offset = 0; + pBuf += a; + amt -= a; + }else{ + offset -= pCur->info.nLocal; + } + + + if( rc==SQLITE_OK && amt>0 ){ + const u32 ovflSize = pBt->usableSize - 4; /* Bytes content per ovfl page */ + Pgno nextPage; + + nextPage = get4byte(&aPayload[pCur->info.nLocal]); + + /* If the BtCursor.aOverflow[] has not been allocated, allocate it now. + ** + ** The aOverflow[] array is sized at one entry for each overflow page + ** in the overflow chain. The page number of the first overflow page is + ** stored in aOverflow[0], etc. A value of 0 in the aOverflow[] array + ** means "not yet known" (the cache is lazily populated). + */ + if( (pCur->curFlags & BTCF_ValidOvfl)==0 ){ + int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize; + if( pCur->aOverflow==0 + || nOvfl*(int)sizeof(Pgno) > sqlite3MallocSize(pCur->aOverflow) + ){ + Pgno *aNew = (Pgno*)sqlite3Realloc( + pCur->aOverflow, nOvfl*2*sizeof(Pgno) + ); + if( aNew==0 ){ + return SQLITE_NOMEM_BKPT; + }else{ + pCur->aOverflow = aNew; + } + } + memset(pCur->aOverflow, 0, nOvfl*sizeof(Pgno)); + pCur->curFlags |= BTCF_ValidOvfl; + }else{ + /* If the overflow page-list cache has been allocated and the + ** entry for the first required overflow page is valid, skip + ** directly to it. + */ + if( pCur->aOverflow[offset/ovflSize] ){ + iIdx = (offset/ovflSize); + nextPage = pCur->aOverflow[iIdx]; + offset = (offset%ovflSize); + } + } + + assert( rc==SQLITE_OK && amt>0 ); + while( nextPage ){ + /* If required, populate the overflow page-list cache. */ + if( nextPage > pBt->nPage ) return SQLITE_CORRUPT_BKPT; + assert( pCur->aOverflow[iIdx]==0 + || pCur->aOverflow[iIdx]==nextPage + || CORRUPT_DB ); + pCur->aOverflow[iIdx] = nextPage; + + if( offset>=ovflSize ){ + /* The only reason to read this page is to obtain the page + ** number for the next page in the overflow chain. The page + ** data is not required. So first try to lookup the overflow + ** page-list cache, if any, then fall back to the getOverflowPage() + ** function. + */ + assert( pCur->curFlags & BTCF_ValidOvfl ); + assert( pCur->pBtree->db==pBt->db ); + if( pCur->aOverflow[iIdx+1] ){ + nextPage = pCur->aOverflow[iIdx+1]; + }else{ + rc = getOverflowPage(pBt, nextPage, 0, &nextPage); + } + offset -= ovflSize; + }else{ + /* Need to read this page properly. It contains some of the + ** range of data that is being read (eOp==0) or written (eOp!=0). + */ + int a = amt; + if( a + offset > ovflSize ){ + a = ovflSize - offset; + } + +#ifdef SQLITE_DIRECT_OVERFLOW_READ + /* If all the following are true: + ** + ** 1) this is a read operation, and + ** 2) data is required from the start of this overflow page, and + ** 3) there are no dirty pages in the page-cache + ** 4) the database is file-backed, and + ** 5) the page is not in the WAL file + ** 6) at least 4 bytes have already been read into the output buffer + ** + ** then data can be read directly from the database file into the + ** output buffer, bypassing the page-cache altogether. This speeds + ** up loading large records that span many overflow pages. + */ + if( eOp==0 /* (1) */ + && offset==0 /* (2) */ + && sqlite3PagerDirectReadOk(pBt->pPager, nextPage) /* (3,4,5) */ + && &pBuf[-4]>=pBufStart /* (6) */ + ){ + sqlite3_file *fd = sqlite3PagerFile(pBt->pPager); + u8 aSave[4]; + u8 *aWrite = &pBuf[-4]; + assert( aWrite>=pBufStart ); /* due to (6) */ + memcpy(aSave, aWrite, 4); + rc = sqlite3OsRead(fd, aWrite, a+4, (i64)pBt->pageSize*(nextPage-1)); + if( rc && nextPage>pBt->nPage ) rc = SQLITE_CORRUPT_BKPT; + nextPage = get4byte(aWrite); + memcpy(aWrite, aSave, 4); + }else +#endif + + { + DbPage *pDbPage; + rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage, + (eOp==0 ? PAGER_GET_READONLY : 0) + ); + if( rc==SQLITE_OK ){ + aPayload = sqlite3PagerGetData(pDbPage); + nextPage = get4byte(aPayload); + rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage); + sqlite3PagerUnref(pDbPage); + offset = 0; + } + } + amt -= a; + if( amt==0 ) return rc; + pBuf += a; + } + if( rc ) break; + iIdx++; + } + } + + if( rc==SQLITE_OK && amt>0 ){ + /* Overflow chain ends prematurely */ + return SQLITE_CORRUPT_PAGE(pPage); + } + return rc; +} + +/* +** Read part of the payload for the row at which that cursor pCur is currently +** pointing. "amt" bytes will be transferred into pBuf[]. The transfer +** begins at "offset". +** +** pCur can be pointing to either a table or an index b-tree. +** If pointing to a table btree, then the content section is read. If +** pCur is pointing to an index b-tree then the key section is read. +** +** For sqlite3BtreePayload(), the caller must ensure that pCur is pointing +** to a valid row in the table. For sqlite3BtreePayloadChecked(), the +** cursor might be invalid or might need to be restored before being read. +** +** Return SQLITE_OK on success or an error code if anything goes +** wrong. An error is returned if "offset+amt" is larger than +** the available payload. +*/ +SQLITE_PRIVATE int sqlite3BtreePayload(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ + assert( cursorHoldsMutex(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + assert( pCur->iPage>=0 && pCur->pPage ); + return accessPayload(pCur, offset, amt, (unsigned char*)pBuf, 0); +} + +/* +** This variant of sqlite3BtreePayload() works even if the cursor has not +** in the CURSOR_VALID state. It is only used by the sqlite3_blob_read() +** interface. +*/ +#ifndef SQLITE_OMIT_INCRBLOB +static SQLITE_NOINLINE int accessPayloadChecked( + BtCursor *pCur, + u32 offset, + u32 amt, + void *pBuf +){ + int rc; + if ( pCur->eState==CURSOR_INVALID ){ + return SQLITE_ABORT; + } + assert( cursorOwnsBtShared(pCur) ); + rc = btreeRestoreCursorPosition(pCur); + return rc ? rc : accessPayload(pCur, offset, amt, pBuf, 0); +} +SQLITE_PRIVATE int sqlite3BtreePayloadChecked(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ + if( pCur->eState==CURSOR_VALID ){ + assert( cursorOwnsBtShared(pCur) ); + return accessPayload(pCur, offset, amt, pBuf, 0); + }else{ + return accessPayloadChecked(pCur, offset, amt, pBuf); + } +} +#endif /* SQLITE_OMIT_INCRBLOB */ + +/* +** Return a pointer to payload information from the entry that the +** pCur cursor is pointing to. The pointer is to the beginning of +** the key if index btrees (pPage->intKey==0) and is the data for +** table btrees (pPage->intKey==1). The number of bytes of available +** key/data is written into *pAmt. If *pAmt==0, then the value +** returned will not be a valid pointer. +** +** This routine is an optimization. It is common for the entire key +** and data to fit on the local page and for there to be no overflow +** pages. When that is so, this routine can be used to access the +** key and data without making a copy. If the key and/or data spills +** onto overflow pages, then accessPayload() must be used to reassemble +** the key/data and copy it into a preallocated buffer. +** +** The pointer returned by this routine looks directly into the cached +** page of the database. The data might change or move the next time +** any btree routine is called. +*/ +static const void *fetchPayload( + BtCursor *pCur, /* Cursor pointing to entry to read from */ + u32 *pAmt /* Write the number of available bytes here */ +){ + int amt; + assert( pCur!=0 && pCur->iPage>=0 && pCur->pPage); + assert( pCur->eState==CURSOR_VALID ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + assert( cursorOwnsBtShared(pCur) ); + assert( pCur->ix<pCur->pPage->nCell || CORRUPT_DB ); + assert( pCur->info.nSize>0 ); + assert( pCur->info.pPayload>pCur->pPage->aData || CORRUPT_DB ); + assert( pCur->info.pPayload<pCur->pPage->aDataEnd ||CORRUPT_DB); + amt = pCur->info.nLocal; + if( amt>(int)(pCur->pPage->aDataEnd - pCur->info.pPayload) ){ + /* There is too little space on the page for the expected amount + ** of local content. Database must be corrupt. */ + assert( CORRUPT_DB ); + amt = MAX(0, (int)(pCur->pPage->aDataEnd - pCur->info.pPayload)); + } + *pAmt = (u32)amt; + return (void*)pCur->info.pPayload; +} + + +/* +** For the entry that cursor pCur is point to, return as +** many bytes of the key or data as are available on the local +** b-tree page. Write the number of available bytes into *pAmt. +** +** The pointer returned is ephemeral. The key/data may move +** or be destroyed on the next call to any Btree routine, +** including calls from other threads against the same cache. +** Hence, a mutex on the BtShared should be held prior to calling +** this routine. +** +** These routines is used to get quick access to key and data +** in the common case where no overflow pages are used. +*/ +SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt){ + return fetchPayload(pCur, pAmt); +} + + +/* +** Move the cursor down to a new child page. The newPgno argument is the +** page number of the child page to move to. +** +** This function returns SQLITE_CORRUPT if the page-header flags field of +** the new child page does not match the flags field of the parent (i.e. +** if an intkey page appears to be the parent of a non-intkey page, or +** vice-versa). +*/ +static int moveToChild(BtCursor *pCur, u32 newPgno){ + int rc; + assert( cursorOwnsBtShared(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + assert( pCur->iPage<BTCURSOR_MAX_DEPTH ); + assert( pCur->iPage>=0 ); + if( pCur->iPage>=(BTCURSOR_MAX_DEPTH-1) ){ + return SQLITE_CORRUPT_BKPT; + } + pCur->info.nSize = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); + pCur->aiIdx[pCur->iPage] = pCur->ix; + pCur->apPage[pCur->iPage] = pCur->pPage; + pCur->ix = 0; + pCur->iPage++; + rc = getAndInitPage(pCur->pBt, newPgno, &pCur->pPage, pCur->curPagerFlags); + assert( pCur->pPage!=0 || rc!=SQLITE_OK ); + if( rc==SQLITE_OK + && (pCur->pPage->nCell<1 || pCur->pPage->intKey!=pCur->curIntKey) + ){ + releasePage(pCur->pPage); + rc = SQLITE_CORRUPT_PGNO(newPgno); + } + if( rc ){ + pCur->pPage = pCur->apPage[--pCur->iPage]; + } + return rc; +} + +#ifdef SQLITE_DEBUG +/* +** Page pParent is an internal (non-leaf) tree page. This function +** asserts that page number iChild is the left-child if the iIdx'th +** cell in page pParent. Or, if iIdx is equal to the total number of +** cells in pParent, that page number iChild is the right-child of +** the page. +*/ +static void assertParentIndex(MemPage *pParent, int iIdx, Pgno iChild){ + if( CORRUPT_DB ) return; /* The conditions tested below might not be true + ** in a corrupt database */ + assert( iIdx<=pParent->nCell ); + if( iIdx==pParent->nCell ){ + assert( get4byte(&pParent->aData[pParent->hdrOffset+8])==iChild ); + }else{ + assert( get4byte(findCell(pParent, iIdx))==iChild ); + } +} +#else +# define assertParentIndex(x,y,z) +#endif + +/* +** Move the cursor up to the parent page. +** +** pCur->idx is set to the cell index that contains the pointer +** to the page we are coming from. If we are coming from the +** right-most child page then pCur->idx is set to one more than +** the largest cell index. +*/ +static void moveToParent(BtCursor *pCur){ + MemPage *pLeaf; + assert( cursorOwnsBtShared(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + assert( pCur->iPage>0 ); + assert( pCur->pPage ); + assertParentIndex( + pCur->apPage[pCur->iPage-1], + pCur->aiIdx[pCur->iPage-1], + pCur->pPage->pgno + ); + testcase( pCur->aiIdx[pCur->iPage-1] > pCur->apPage[pCur->iPage-1]->nCell ); + pCur->info.nSize = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); + pCur->ix = pCur->aiIdx[pCur->iPage-1]; + pLeaf = pCur->pPage; + pCur->pPage = pCur->apPage[--pCur->iPage]; + releasePageNotNull(pLeaf); +} + +/* +** Move the cursor to point to the root page of its b-tree structure. +** +** If the table has a virtual root page, then the cursor is moved to point +** to the virtual root page instead of the actual root page. A table has a +** virtual root page when the actual root page contains no cells and a +** single child page. This can only happen with the table rooted at page 1. +** +** If the b-tree structure is empty, the cursor state is set to +** CURSOR_INVALID and this routine returns SQLITE_EMPTY. Otherwise, +** the cursor is set to point to the first cell located on the root +** (or virtual root) page and the cursor state is set to CURSOR_VALID. +** +** If this function returns successfully, it may be assumed that the +** page-header flags indicate that the [virtual] root-page is the expected +** kind of b-tree page (i.e. if when opening the cursor the caller did not +** specify a KeyInfo structure the flags byte is set to 0x05 or 0x0D, +** indicating a table b-tree, or if the caller did specify a KeyInfo +** structure the flags byte is set to 0x02 or 0x0A, indicating an index +** b-tree). +*/ +static int moveToRoot(BtCursor *pCur){ + MemPage *pRoot; + int rc = SQLITE_OK; + + assert( cursorOwnsBtShared(pCur) ); + assert( CURSOR_INVALID < CURSOR_REQUIRESEEK ); + assert( CURSOR_VALID < CURSOR_REQUIRESEEK ); + assert( CURSOR_FAULT > CURSOR_REQUIRESEEK ); + assert( pCur->eState < CURSOR_REQUIRESEEK || pCur->iPage<0 ); + assert( pCur->pgnoRoot>0 || pCur->iPage<0 ); + + if( pCur->iPage>=0 ){ + if( pCur->iPage ){ + releasePageNotNull(pCur->pPage); + while( --pCur->iPage ){ + releasePageNotNull(pCur->apPage[pCur->iPage]); + } + pRoot = pCur->pPage = pCur->apPage[0]; + goto skip_init; + } + }else if( pCur->pgnoRoot==0 ){ + pCur->eState = CURSOR_INVALID; + return SQLITE_EMPTY; + }else{ + assert( pCur->iPage==(-1) ); + if( pCur->eState>=CURSOR_REQUIRESEEK ){ + if( pCur->eState==CURSOR_FAULT ){ + assert( pCur->skipNext!=SQLITE_OK ); + return pCur->skipNext; + } + sqlite3BtreeClearCursor(pCur); + } + rc = getAndInitPage(pCur->pBt, pCur->pgnoRoot, &pCur->pPage, + pCur->curPagerFlags); + if( rc!=SQLITE_OK ){ + pCur->eState = CURSOR_INVALID; + return rc; + } + pCur->iPage = 0; + pCur->curIntKey = pCur->pPage->intKey; + } + pRoot = pCur->pPage; + assert( pRoot->pgno==pCur->pgnoRoot || CORRUPT_DB ); + + /* If pCur->pKeyInfo is not NULL, then the caller that opened this cursor + ** expected to open it on an index b-tree. Otherwise, if pKeyInfo is + ** NULL, the caller expects a table b-tree. If this is not the case, + ** return an SQLITE_CORRUPT error. + ** + ** Earlier versions of SQLite assumed that this test could not fail + ** if the root page was already loaded when this function was called (i.e. + ** if pCur->iPage>=0). But this is not so if the database is corrupted + ** in such a way that page pRoot is linked into a second b-tree table + ** (or the freelist). */ + assert( pRoot->intKey==1 || pRoot->intKey==0 ); + if( pRoot->isInit==0 || (pCur->pKeyInfo==0)!=pRoot->intKey ){ + return SQLITE_CORRUPT_PAGE(pCur->pPage); + } + +skip_init: + pCur->ix = 0; + pCur->info.nSize = 0; + pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidNKey|BTCF_ValidOvfl); + + if( pRoot->nCell>0 ){ + pCur->eState = CURSOR_VALID; + }else if( !pRoot->leaf ){ + Pgno subpage; + if( pRoot->pgno!=1 ) return SQLITE_CORRUPT_BKPT; + subpage = get4byte(&pRoot->aData[pRoot->hdrOffset+8]); + pCur->eState = CURSOR_VALID; + rc = moveToChild(pCur, subpage); + }else{ + pCur->eState = CURSOR_INVALID; + rc = SQLITE_EMPTY; + } + return rc; +} + +/* +** Move the cursor down to the left-most leaf entry beneath the +** entry to which it is currently pointing. +** +** The left-most leaf is the one with the smallest key - the first +** in ascending order. +*/ +static int moveToLeftmost(BtCursor *pCur){ + Pgno pgno; + int rc = SQLITE_OK; + MemPage *pPage; + + assert( cursorOwnsBtShared(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + while( rc==SQLITE_OK && !(pPage = pCur->pPage)->leaf ){ + assert( pCur->ix<pPage->nCell ); + pgno = get4byte(findCell(pPage, pCur->ix)); + rc = moveToChild(pCur, pgno); + } + return rc; +} + +/* +** Move the cursor down to the right-most leaf entry beneath the +** page to which it is currently pointing. Notice the difference +** between moveToLeftmost() and moveToRightmost(). moveToLeftmost() +** finds the left-most entry beneath the *entry* whereas moveToRightmost() +** finds the right-most entry beneath the *page*. +** +** The right-most entry is the one with the largest key - the last +** key in ascending order. +*/ +static int moveToRightmost(BtCursor *pCur){ + Pgno pgno; + int rc = SQLITE_OK; + MemPage *pPage = 0; + + assert( cursorOwnsBtShared(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + while( !(pPage = pCur->pPage)->leaf ){ + pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]); + pCur->ix = pPage->nCell; + rc = moveToChild(pCur, pgno); + if( rc ) return rc; + } + pCur->ix = pPage->nCell-1; + assert( pCur->info.nSize==0 ); + assert( (pCur->curFlags & BTCF_ValidNKey)==0 ); + return SQLITE_OK; +} + +/* Move the cursor to the first entry in the table. Return SQLITE_OK +** on success. Set *pRes to 0 if the cursor actually points to something +** or set *pRes to 1 if the table is empty. +*/ +SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ + int rc; + + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + rc = moveToRoot(pCur); + if( rc==SQLITE_OK ){ + assert( pCur->pPage->nCell>0 ); + *pRes = 0; + rc = moveToLeftmost(pCur); + }else if( rc==SQLITE_EMPTY ){ + assert( pCur->pgnoRoot==0 || (pCur->pPage!=0 && pCur->pPage->nCell==0) ); + *pRes = 1; + rc = SQLITE_OK; + } + return rc; +} + +/* Move the cursor to the last entry in the table. Return SQLITE_OK +** on success. Set *pRes to 0 if the cursor actually points to something +** or set *pRes to 1 if the table is empty. +*/ +static SQLITE_NOINLINE int btreeLast(BtCursor *pCur, int *pRes){ + int rc = moveToRoot(pCur); + if( rc==SQLITE_OK ){ + assert( pCur->eState==CURSOR_VALID ); + *pRes = 0; + rc = moveToRightmost(pCur); + if( rc==SQLITE_OK ){ + pCur->curFlags |= BTCF_AtLast; + }else{ + pCur->curFlags &= ~BTCF_AtLast; + } + }else if( rc==SQLITE_EMPTY ){ + assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); + *pRes = 1; + rc = SQLITE_OK; + } + return rc; +} +SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + + /* If the cursor already points to the last entry, this is a no-op. */ + if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){ +#ifdef SQLITE_DEBUG + /* This block serves to assert() that the cursor really does point + ** to the last entry in the b-tree. */ + int ii; + for(ii=0; ii<pCur->iPage; ii++){ + assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell ); + } + assert( pCur->ix==pCur->pPage->nCell-1 || CORRUPT_DB ); + testcase( pCur->ix!=pCur->pPage->nCell-1 ); + /* ^-- dbsqlfuzz b92b72e4de80b5140c30ab71372ca719b8feb618 */ + assert( pCur->pPage->leaf ); +#endif + *pRes = 0; + return SQLITE_OK; + } + return btreeLast(pCur, pRes); +} + +/* Move the cursor so that it points to an entry in a table (a.k.a INTKEY) +** table near the key intKey. Return a success code. +** +** If an exact match is not found, then the cursor is always +** left pointing at a leaf page which would hold the entry if it +** were present. The cursor might point to an entry that comes +** before or after the key. +** +** An integer is written into *pRes which is the result of +** comparing the key with the entry to which the cursor is +** pointing. The meaning of the integer written into +** *pRes is as follows: +** +** *pRes<0 The cursor is left pointing at an entry that +** is smaller than intKey or if the table is empty +** and the cursor is therefore left point to nothing. +** +** *pRes==0 The cursor is left pointing at an entry that +** exactly matches intKey. +** +** *pRes>0 The cursor is left pointing at an entry that +** is larger than intKey. +*/ +SQLITE_PRIVATE int sqlite3BtreeTableMoveto( + BtCursor *pCur, /* The cursor to be moved */ + i64 intKey, /* The table key */ + int biasRight, /* If true, bias the search to the high end */ + int *pRes /* Write search results here */ +){ + int rc; + + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + assert( pRes ); + assert( pCur->pKeyInfo==0 ); + assert( pCur->eState!=CURSOR_VALID || pCur->curIntKey!=0 ); + + /* If the cursor is already positioned at the point we are trying + ** to move to, then just return without doing any work */ + if( pCur->eState==CURSOR_VALID && (pCur->curFlags & BTCF_ValidNKey)!=0 ){ + if( pCur->info.nKey==intKey ){ + *pRes = 0; + return SQLITE_OK; + } + if( pCur->info.nKey<intKey ){ + if( (pCur->curFlags & BTCF_AtLast)!=0 ){ + *pRes = -1; + return SQLITE_OK; + } + /* If the requested key is one more than the previous key, then + ** try to get there using sqlite3BtreeNext() rather than a full + ** binary search. This is an optimization only. The correct answer + ** is still obtained without this case, only a little more slowly. */ + if( pCur->info.nKey+1==intKey ){ + *pRes = 0; + rc = sqlite3BtreeNext(pCur, 0); + if( rc==SQLITE_OK ){ + getCellInfo(pCur); + if( pCur->info.nKey==intKey ){ + return SQLITE_OK; + } + }else if( rc!=SQLITE_DONE ){ + return rc; + } + } + } + } + +#ifdef SQLITE_DEBUG + pCur->pBtree->nSeek++; /* Performance measurement during testing */ +#endif + + rc = moveToRoot(pCur); + if( rc ){ + if( rc==SQLITE_EMPTY ){ + assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); + *pRes = -1; + return SQLITE_OK; + } + return rc; + } + assert( pCur->pPage ); + assert( pCur->pPage->isInit ); + assert( pCur->eState==CURSOR_VALID ); + assert( pCur->pPage->nCell > 0 ); + assert( pCur->iPage==0 || pCur->apPage[0]->intKey==pCur->curIntKey ); + assert( pCur->curIntKey ); + + for(;;){ + int lwr, upr, idx, c; + Pgno chldPg; + MemPage *pPage = pCur->pPage; + u8 *pCell; /* Pointer to current cell in pPage */ + + /* pPage->nCell must be greater than zero. If this is the root-page + ** the cursor would have been INVALID above and this for(;;) loop + ** not run. If this is not the root-page, then the moveToChild() routine + ** would have already detected db corruption. Similarly, pPage must + ** be the right kind (index or table) of b-tree page. Otherwise + ** a moveToChild() or moveToRoot() call would have detected corruption. */ + assert( pPage->nCell>0 ); + assert( pPage->intKey ); + lwr = 0; + upr = pPage->nCell-1; + assert( biasRight==0 || biasRight==1 ); + idx = upr>>(1-biasRight); /* idx = biasRight ? upr : (lwr+upr)/2; */ + for(;;){ + i64 nCellKey; + pCell = findCellPastPtr(pPage, idx); + if( pPage->intKeyLeaf ){ + while( 0x80 <= *(pCell++) ){ + if( pCell>=pPage->aDataEnd ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + } + } + getVarint(pCell, (u64*)&nCellKey); + if( nCellKey<intKey ){ + lwr = idx+1; + if( lwr>upr ){ c = -1; break; } + }else if( nCellKey>intKey ){ + upr = idx-1; + if( lwr>upr ){ c = +1; break; } + }else{ + assert( nCellKey==intKey ); + pCur->ix = (u16)idx; + if( !pPage->leaf ){ + lwr = idx; + goto moveto_table_next_layer; + }else{ + pCur->curFlags |= BTCF_ValidNKey; + pCur->info.nKey = nCellKey; + pCur->info.nSize = 0; + *pRes = 0; + return SQLITE_OK; + } + } + assert( lwr+upr>=0 ); + idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2; */ + } + assert( lwr==upr+1 || !pPage->leaf ); + assert( pPage->isInit ); + if( pPage->leaf ){ + assert( pCur->ix<pCur->pPage->nCell ); + pCur->ix = (u16)idx; + *pRes = c; + rc = SQLITE_OK; + goto moveto_table_finish; + } +moveto_table_next_layer: + if( lwr>=pPage->nCell ){ + chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); + }else{ + chldPg = get4byte(findCell(pPage, lwr)); + } + pCur->ix = (u16)lwr; + rc = moveToChild(pCur, chldPg); + if( rc ) break; + } +moveto_table_finish: + pCur->info.nSize = 0; + assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); + return rc; +} + +/* +** Compare the "idx"-th cell on the page the cursor pCur is currently +** pointing to to pIdxKey using xRecordCompare. Return negative or +** zero if the cell is less than or equal pIdxKey. Return positive +** if unknown. +** +** Return value negative: Cell at pCur[idx] less than pIdxKey +** +** Return value is zero: Cell at pCur[idx] equals pIdxKey +** +** Return value positive: Nothing is known about the relationship +** of the cell at pCur[idx] and pIdxKey. +** +** This routine is part of an optimization. It is always safe to return +** a positive value as that will cause the optimization to be skipped. +*/ +static int indexCellCompare( + BtCursor *pCur, + int idx, + UnpackedRecord *pIdxKey, + RecordCompare xRecordCompare +){ + MemPage *pPage = pCur->pPage; + int c; + int nCell; /* Size of the pCell cell in bytes */ + u8 *pCell = findCellPastPtr(pPage, idx); + + nCell = pCell[0]; + if( nCell<=pPage->max1bytePayload ){ + /* This branch runs if the record-size field of the cell is a + ** single byte varint and the record fits entirely on the main + ** b-tree page. */ + testcase( pCell+nCell+1==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + }else if( !(pCell[1] & 0x80) + && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal + ){ + /* The record-size field is a 2 byte varint and the record + ** fits entirely on the main b-tree page. */ + testcase( pCell+nCell+2==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + }else{ + /* If the record extends into overflow pages, do not attempt + ** the optimization. */ + c = 99; + } + return c; +} + +/* +** Return true (non-zero) if pCur is current pointing to the last +** page of a table. +*/ +static int cursorOnLastPage(BtCursor *pCur){ + int i; + assert( pCur->eState==CURSOR_VALID ); + for(i=0; i<pCur->iPage; i++){ + MemPage *pPage = pCur->apPage[i]; + if( pCur->aiIdx[i]<pPage->nCell ) return 0; + } + return 1; +} + +/* Move the cursor so that it points to an entry in an index table +** near the key pIdxKey. Return a success code. +** +** If an exact match is not found, then the cursor is always +** left pointing at a leaf page which would hold the entry if it +** were present. The cursor might point to an entry that comes +** before or after the key. +** +** An integer is written into *pRes which is the result of +** comparing the key with the entry to which the cursor is +** pointing. The meaning of the integer written into +** *pRes is as follows: +** +** *pRes<0 The cursor is left pointing at an entry that +** is smaller than pIdxKey or if the table is empty +** and the cursor is therefore left point to nothing. +** +** *pRes==0 The cursor is left pointing at an entry that +** exactly matches pIdxKey. +** +** *pRes>0 The cursor is left pointing at an entry that +** is larger than pIdxKey. +** +** The pIdxKey->eqSeen field is set to 1 if there +** exists an entry in the table that exactly matches pIdxKey. +*/ +SQLITE_PRIVATE int sqlite3BtreeIndexMoveto( + BtCursor *pCur, /* The cursor to be moved */ + UnpackedRecord *pIdxKey, /* Unpacked index key */ + int *pRes /* Write search results here */ +){ + int rc; + RecordCompare xRecordCompare; + + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + assert( pRes ); + assert( pCur->pKeyInfo!=0 ); + +#ifdef SQLITE_DEBUG + pCur->pBtree->nSeek++; /* Performance measurement during testing */ +#endif + + xRecordCompare = sqlite3VdbeFindCompare(pIdxKey); + pIdxKey->errCode = 0; + assert( pIdxKey->default_rc==1 + || pIdxKey->default_rc==0 + || pIdxKey->default_rc==-1 + ); + + + /* Check to see if we can skip a lot of work. Two cases: + ** + ** (1) If the cursor is already pointing to the very last cell + ** in the table and the pIdxKey search key is greater than or + ** equal to that last cell, then no movement is required. + ** + ** (2) If the cursor is on the last page of the table and the first + ** cell on that last page is less than or equal to the pIdxKey + ** search key, then we can start the search on the current page + ** without needing to go back to root. + */ + if( pCur->eState==CURSOR_VALID + && pCur->pPage->leaf + && cursorOnLastPage(pCur) + ){ + int c; + if( pCur->ix==pCur->pPage->nCell-1 + && (c = indexCellCompare(pCur, pCur->ix, pIdxKey, xRecordCompare))<=0 + && pIdxKey->errCode==SQLITE_OK + ){ + *pRes = c; + return SQLITE_OK; /* Cursor already pointing at the correct spot */ + } + if( pCur->iPage>0 + && indexCellCompare(pCur, 0, pIdxKey, xRecordCompare)<=0 + && pIdxKey->errCode==SQLITE_OK + ){ + pCur->curFlags &= ~BTCF_ValidOvfl; + if( !pCur->pPage->isInit ){ + return SQLITE_CORRUPT_BKPT; + } + goto bypass_moveto_root; /* Start search on the current page */ + } + pIdxKey->errCode = SQLITE_OK; + } + + rc = moveToRoot(pCur); + if( rc ){ + if( rc==SQLITE_EMPTY ){ + assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); + *pRes = -1; + return SQLITE_OK; + } + return rc; + } + +bypass_moveto_root: + assert( pCur->pPage ); + assert( pCur->pPage->isInit ); + assert( pCur->eState==CURSOR_VALID ); + assert( pCur->pPage->nCell > 0 ); + assert( pCur->curIntKey==0 ); + assert( pIdxKey!=0 ); + for(;;){ + int lwr, upr, idx, c; + Pgno chldPg; + MemPage *pPage = pCur->pPage; + u8 *pCell; /* Pointer to current cell in pPage */ + + /* pPage->nCell must be greater than zero. If this is the root-page + ** the cursor would have been INVALID above and this for(;;) loop + ** not run. If this is not the root-page, then the moveToChild() routine + ** would have already detected db corruption. Similarly, pPage must + ** be the right kind (index or table) of b-tree page. Otherwise + ** a moveToChild() or moveToRoot() call would have detected corruption. */ + assert( pPage->nCell>0 ); + assert( pPage->intKey==0 ); + lwr = 0; + upr = pPage->nCell-1; + idx = upr>>1; /* idx = (lwr+upr)/2; */ + for(;;){ + int nCell; /* Size of the pCell cell in bytes */ + pCell = findCellPastPtr(pPage, idx); + + /* The maximum supported page-size is 65536 bytes. This means that + ** the maximum number of record bytes stored on an index B-Tree + ** page is less than 16384 bytes and may be stored as a 2-byte + ** varint. This information is used to attempt to avoid parsing + ** the entire cell by checking for the cases where the record is + ** stored entirely within the b-tree page by inspecting the first + ** 2 bytes of the cell. + */ + nCell = pCell[0]; + if( nCell<=pPage->max1bytePayload ){ + /* This branch runs if the record-size field of the cell is a + ** single byte varint and the record fits entirely on the main + ** b-tree page. */ + testcase( pCell+nCell+1==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + }else if( !(pCell[1] & 0x80) + && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal + ){ + /* The record-size field is a 2 byte varint and the record + ** fits entirely on the main b-tree page. */ + testcase( pCell+nCell+2==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + }else{ + /* The record flows over onto one or more overflow pages. In + ** this case the whole cell needs to be parsed, a buffer allocated + ** and accessPayload() used to retrieve the record into the + ** buffer before VdbeRecordCompare() can be called. + ** + ** If the record is corrupt, the xRecordCompare routine may read + ** up to two varints past the end of the buffer. An extra 18 + ** bytes of padding is allocated at the end of the buffer in + ** case this happens. */ + void *pCellKey; + u8 * const pCellBody = pCell - pPage->childPtrSize; + const int nOverrun = 18; /* Size of the overrun padding */ + pPage->xParseCell(pPage, pCellBody, &pCur->info); + nCell = (int)pCur->info.nKey; + testcase( nCell<0 ); /* True if key size is 2^32 or more */ + testcase( nCell==0 ); /* Invalid key size: 0x80 0x80 0x00 */ + testcase( nCell==1 ); /* Invalid key size: 0x80 0x80 0x01 */ + testcase( nCell==2 ); /* Minimum legal index key size */ + if( nCell<2 || nCell/pCur->pBt->usableSize>pCur->pBt->nPage ){ + rc = SQLITE_CORRUPT_PAGE(pPage); + goto moveto_index_finish; + } + pCellKey = sqlite3Malloc( nCell+nOverrun ); + if( pCellKey==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto moveto_index_finish; + } + pCur->ix = (u16)idx; + rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0); + memset(((u8*)pCellKey)+nCell,0,nOverrun); /* Fix uninit warnings */ + pCur->curFlags &= ~BTCF_ValidOvfl; + if( rc ){ + sqlite3_free(pCellKey); + goto moveto_index_finish; + } + c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey); + sqlite3_free(pCellKey); + } + assert( + (pIdxKey->errCode!=SQLITE_CORRUPT || c==0) + && (pIdxKey->errCode!=SQLITE_NOMEM || pCur->pBtree->db->mallocFailed) + ); + if( c<0 ){ + lwr = idx+1; + }else if( c>0 ){ + upr = idx-1; + }else{ + assert( c==0 ); + *pRes = 0; + rc = SQLITE_OK; + pCur->ix = (u16)idx; + if( pIdxKey->errCode ) rc = SQLITE_CORRUPT_BKPT; + goto moveto_index_finish; + } + if( lwr>upr ) break; + assert( lwr+upr>=0 ); + idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2 */ + } + assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); + assert( pPage->isInit ); + if( pPage->leaf ){ + assert( pCur->ix<pCur->pPage->nCell || CORRUPT_DB ); + pCur->ix = (u16)idx; + *pRes = c; + rc = SQLITE_OK; + goto moveto_index_finish; + } + if( lwr>=pPage->nCell ){ + chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); + }else{ + chldPg = get4byte(findCell(pPage, lwr)); + } + + /* This block is similar to an in-lined version of: + ** + ** pCur->ix = (u16)lwr; + ** rc = moveToChild(pCur, chldPg); + ** if( rc ) break; + */ + pCur->info.nSize = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); + if( pCur->iPage>=(BTCURSOR_MAX_DEPTH-1) ){ + return SQLITE_CORRUPT_BKPT; + } + pCur->aiIdx[pCur->iPage] = (u16)lwr; + pCur->apPage[pCur->iPage] = pCur->pPage; + pCur->ix = 0; + pCur->iPage++; + rc = getAndInitPage(pCur->pBt, chldPg, &pCur->pPage, pCur->curPagerFlags); + if( rc==SQLITE_OK + && (pCur->pPage->nCell<1 || pCur->pPage->intKey!=pCur->curIntKey) + ){ + releasePage(pCur->pPage); + rc = SQLITE_CORRUPT_PGNO(chldPg); + } + if( rc ){ + pCur->pPage = pCur->apPage[--pCur->iPage]; + break; + } + /* + ***** End of in-lined moveToChild() call */ + } +moveto_index_finish: + pCur->info.nSize = 0; + assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); + return rc; +} + + +/* +** Return TRUE if the cursor is not pointing at an entry of the table. +** +** TRUE will be returned after a call to sqlite3BtreeNext() moves +** past the last entry in the table or sqlite3BtreePrev() moves past +** the first entry. TRUE is also returned if the table is empty. +*/ +SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor *pCur){ + /* TODO: What if the cursor is in CURSOR_REQUIRESEEK but all table entries + ** have been deleted? This API will need to change to return an error code + ** as well as the boolean result value. + */ + return (CURSOR_VALID!=pCur->eState); +} + +/* +** Return an estimate for the number of rows in the table that pCur is +** pointing to. Return a negative number if no estimate is currently +** available. +*/ +SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor *pCur){ + i64 n; + u8 i; + + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + + /* Currently this interface is only called by the OP_IfSmaller + ** opcode, and it that case the cursor will always be valid and + ** will always point to a leaf node. */ + if( NEVER(pCur->eState!=CURSOR_VALID) ) return -1; + if( NEVER(pCur->pPage->leaf==0) ) return -1; + + n = pCur->pPage->nCell; + for(i=0; i<pCur->iPage; i++){ + n *= pCur->apPage[i]->nCell; + } + return n; +} + +/* +** Advance the cursor to the next entry in the database. +** Return value: +** +** SQLITE_OK success +** SQLITE_DONE cursor is already pointing at the last element +** otherwise some kind of error occurred +** +** The main entry point is sqlite3BtreeNext(). That routine is optimized +** for the common case of merely incrementing the cell counter BtCursor.aiIdx +** to the next cell on the current page. The (slower) btreeNext() helper +** routine is called when it is necessary to move to a different page or +** to restore the cursor. +** +** If bit 0x01 of the F argument in sqlite3BtreeNext(C,F) is 1, then the +** cursor corresponds to an SQL index and this routine could have been +** skipped if the SQL index had been a unique index. The F argument +** is a hint to the implement. SQLite btree implementation does not use +** this hint, but COMDB2 does. +*/ +static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){ + int rc; + int idx; + MemPage *pPage; + + assert( cursorOwnsBtShared(pCur) ); + if( pCur->eState!=CURSOR_VALID ){ + assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); + rc = restoreCursorPosition(pCur); + if( rc!=SQLITE_OK ){ + return rc; + } + if( CURSOR_INVALID==pCur->eState ){ + return SQLITE_DONE; + } + if( pCur->eState==CURSOR_SKIPNEXT ){ + pCur->eState = CURSOR_VALID; + if( pCur->skipNext>0 ) return SQLITE_OK; + } + } + + pPage = pCur->pPage; + idx = ++pCur->ix; + if( sqlite3FaultSim(412) ) pPage->isInit = 0; + if( !pPage->isInit ){ + return SQLITE_CORRUPT_BKPT; + } + + if( idx>=pPage->nCell ){ + if( !pPage->leaf ){ + rc = moveToChild(pCur, get4byte(&pPage->aData[pPage->hdrOffset+8])); + if( rc ) return rc; + return moveToLeftmost(pCur); + } + do{ + if( pCur->iPage==0 ){ + pCur->eState = CURSOR_INVALID; + return SQLITE_DONE; + } + moveToParent(pCur); + pPage = pCur->pPage; + }while( pCur->ix>=pPage->nCell ); + if( pPage->intKey ){ + return sqlite3BtreeNext(pCur, 0); + }else{ + return SQLITE_OK; + } + } + if( pPage->leaf ){ + return SQLITE_OK; + }else{ + return moveToLeftmost(pCur); + } +} +SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int flags){ + MemPage *pPage; + UNUSED_PARAMETER( flags ); /* Used in COMDB2 but not native SQLite */ + assert( cursorOwnsBtShared(pCur) ); + assert( flags==0 || flags==1 ); + pCur->info.nSize = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); + if( pCur->eState!=CURSOR_VALID ) return btreeNext(pCur); + pPage = pCur->pPage; + if( (++pCur->ix)>=pPage->nCell ){ + pCur->ix--; + return btreeNext(pCur); + } + if( pPage->leaf ){ + return SQLITE_OK; + }else{ + return moveToLeftmost(pCur); + } +} + +/* +** Step the cursor to the back to the previous entry in the database. +** Return values: +** +** SQLITE_OK success +** SQLITE_DONE the cursor is already on the first element of the table +** otherwise some kind of error occurred +** +** The main entry point is sqlite3BtreePrevious(). That routine is optimized +** for the common case of merely decrementing the cell counter BtCursor.aiIdx +** to the previous cell on the current page. The (slower) btreePrevious() +** helper routine is called when it is necessary to move to a different page +** or to restore the cursor. +** +** If bit 0x01 of the F argument to sqlite3BtreePrevious(C,F) is 1, then +** the cursor corresponds to an SQL index and this routine could have been +** skipped if the SQL index had been a unique index. The F argument is a +** hint to the implement. The native SQLite btree implementation does not +** use this hint, but COMDB2 does. +*/ +static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur){ + int rc; + MemPage *pPage; + + assert( cursorOwnsBtShared(pCur) ); + assert( (pCur->curFlags & (BTCF_AtLast|BTCF_ValidOvfl|BTCF_ValidNKey))==0 ); + assert( pCur->info.nSize==0 ); + if( pCur->eState!=CURSOR_VALID ){ + rc = restoreCursorPosition(pCur); + if( rc!=SQLITE_OK ){ + return rc; + } + if( CURSOR_INVALID==pCur->eState ){ + return SQLITE_DONE; + } + if( CURSOR_SKIPNEXT==pCur->eState ){ + pCur->eState = CURSOR_VALID; + if( pCur->skipNext<0 ) return SQLITE_OK; + } + } + + pPage = pCur->pPage; + assert( pPage->isInit ); + if( !pPage->leaf ){ + int idx = pCur->ix; + rc = moveToChild(pCur, get4byte(findCell(pPage, idx))); + if( rc ) return rc; + rc = moveToRightmost(pCur); + }else{ + while( pCur->ix==0 ){ + if( pCur->iPage==0 ){ + pCur->eState = CURSOR_INVALID; + return SQLITE_DONE; + } + moveToParent(pCur); + } + assert( pCur->info.nSize==0 ); + assert( (pCur->curFlags & (BTCF_ValidOvfl))==0 ); + + pCur->ix--; + pPage = pCur->pPage; + if( pPage->intKey && !pPage->leaf ){ + rc = sqlite3BtreePrevious(pCur, 0); + }else{ + rc = SQLITE_OK; + } + } + return rc; +} +SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor *pCur, int flags){ + assert( cursorOwnsBtShared(pCur) ); + assert( flags==0 || flags==1 ); + UNUSED_PARAMETER( flags ); /* Used in COMDB2 but not native SQLite */ + pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidOvfl|BTCF_ValidNKey); + pCur->info.nSize = 0; + if( pCur->eState!=CURSOR_VALID + || pCur->ix==0 + || pCur->pPage->leaf==0 + ){ + return btreePrevious(pCur); + } + pCur->ix--; + return SQLITE_OK; +} + +/* +** Allocate a new page from the database file. +** +** The new page is marked as dirty. (In other words, sqlite3PagerWrite() +** has already been called on the new page.) The new page has also +** been referenced and the calling routine is responsible for calling +** sqlite3PagerUnref() on the new page when it is done. +** +** SQLITE_OK is returned on success. Any other return value indicates +** an error. *ppPage is set to NULL in the event of an error. +** +** If the "nearby" parameter is not 0, then an effort is made to +** locate a page close to the page number "nearby". This can be used in an +** attempt to keep related pages close to each other in the database file, +** which in turn can make database access faster. +** +** If the eMode parameter is BTALLOC_EXACT and the nearby page exists +** anywhere on the free-list, then it is guaranteed to be returned. If +** eMode is BTALLOC_LT then the page returned will be less than or equal +** to nearby if any such page exists. If eMode is BTALLOC_ANY then there +** are no restrictions on which page is returned. +*/ +static int allocateBtreePage( + BtShared *pBt, /* The btree */ + MemPage **ppPage, /* Store pointer to the allocated page here */ + Pgno *pPgno, /* Store the page number here */ + Pgno nearby, /* Search for a page near this one */ + u8 eMode /* BTALLOC_EXACT, BTALLOC_LT, or BTALLOC_ANY */ +){ + MemPage *pPage1; + int rc; + u32 n; /* Number of pages on the freelist */ + u32 k; /* Number of leaves on the trunk of the freelist */ + MemPage *pTrunk = 0; + MemPage *pPrevTrunk = 0; + Pgno mxPage; /* Total size of the database file */ + + assert( sqlite3_mutex_held(pBt->mutex) ); + assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) ); + pPage1 = pBt->pPage1; + mxPage = btreePagecount(pBt); + /* EVIDENCE-OF: R-21003-45125 The 4-byte big-endian integer at offset 36 + ** stores the total number of pages on the freelist. */ + n = get4byte(&pPage1->aData[36]); + testcase( n==mxPage-1 ); + if( n>=mxPage ){ + return SQLITE_CORRUPT_BKPT; + } + if( n>0 ){ + /* There are pages on the freelist. Reuse one of those pages. */ + Pgno iTrunk; + u8 searchList = 0; /* If the free-list must be searched for 'nearby' */ + u32 nSearch = 0; /* Count of the number of search attempts */ + + /* If eMode==BTALLOC_EXACT and a query of the pointer-map + ** shows that the page 'nearby' is somewhere on the free-list, then + ** the entire-list will be searched for that page. + */ +#ifndef SQLITE_OMIT_AUTOVACUUM + if( eMode==BTALLOC_EXACT ){ + if( nearby<=mxPage ){ + u8 eType; + assert( nearby>0 ); + assert( pBt->autoVacuum ); + rc = ptrmapGet(pBt, nearby, &eType, 0); + if( rc ) return rc; + if( eType==PTRMAP_FREEPAGE ){ + searchList = 1; + } + } + }else if( eMode==BTALLOC_LE ){ + searchList = 1; + } +#endif + + /* Decrement the free-list count by 1. Set iTrunk to the index of the + ** first free-list trunk page. iPrevTrunk is initially 1. + */ + rc = sqlite3PagerWrite(pPage1->pDbPage); + if( rc ) return rc; + put4byte(&pPage1->aData[36], n-1); + + /* The code within this loop is run only once if the 'searchList' variable + ** is not true. Otherwise, it runs once for each trunk-page on the + ** free-list until the page 'nearby' is located (eMode==BTALLOC_EXACT) + ** or until a page less than 'nearby' is located (eMode==BTALLOC_LT) + */ + do { + pPrevTrunk = pTrunk; + if( pPrevTrunk ){ + /* EVIDENCE-OF: R-01506-11053 The first integer on a freelist trunk page + ** is the page number of the next freelist trunk page in the list or + ** zero if this is the last freelist trunk page. */ + iTrunk = get4byte(&pPrevTrunk->aData[0]); + }else{ + /* EVIDENCE-OF: R-59841-13798 The 4-byte big-endian integer at offset 32 + ** stores the page number of the first page of the freelist, or zero if + ** the freelist is empty. */ + iTrunk = get4byte(&pPage1->aData[32]); + } + testcase( iTrunk==mxPage ); + if( iTrunk>mxPage || nSearch++ > n ){ + rc = SQLITE_CORRUPT_PGNO(pPrevTrunk ? pPrevTrunk->pgno : 1); + }else{ + rc = btreeGetUnusedPage(pBt, iTrunk, &pTrunk, 0); + } + if( rc ){ + pTrunk = 0; + goto end_allocate_page; + } + assert( pTrunk!=0 ); + assert( pTrunk->aData!=0 ); + /* EVIDENCE-OF: R-13523-04394 The second integer on a freelist trunk page + ** is the number of leaf page pointers to follow. */ + k = get4byte(&pTrunk->aData[4]); + if( k==0 && !searchList ){ + /* The trunk has no leaves and the list is not being searched. + ** So extract the trunk page itself and use it as the newly + ** allocated page */ + assert( pPrevTrunk==0 ); + rc = sqlite3PagerWrite(pTrunk->pDbPage); + if( rc ){ + goto end_allocate_page; + } + *pPgno = iTrunk; + memcpy(&pPage1->aData[32], &pTrunk->aData[0], 4); + *ppPage = pTrunk; + pTrunk = 0; + TRACE(("ALLOCATE: %u trunk - %u free pages left\n", *pPgno, n-1)); + }else if( k>(u32)(pBt->usableSize/4 - 2) ){ + /* Value of k is out of range. Database corruption */ + rc = SQLITE_CORRUPT_PGNO(iTrunk); + goto end_allocate_page; +#ifndef SQLITE_OMIT_AUTOVACUUM + }else if( searchList + && (nearby==iTrunk || (iTrunk<nearby && eMode==BTALLOC_LE)) + ){ + /* The list is being searched and this trunk page is the page + ** to allocate, regardless of whether it has leaves. + */ + *pPgno = iTrunk; + *ppPage = pTrunk; + searchList = 0; + rc = sqlite3PagerWrite(pTrunk->pDbPage); + if( rc ){ + goto end_allocate_page; + } + if( k==0 ){ + if( !pPrevTrunk ){ + memcpy(&pPage1->aData[32], &pTrunk->aData[0], 4); + }else{ + rc = sqlite3PagerWrite(pPrevTrunk->pDbPage); + if( rc!=SQLITE_OK ){ + goto end_allocate_page; + } + memcpy(&pPrevTrunk->aData[0], &pTrunk->aData[0], 4); + } + }else{ + /* The trunk page is required by the caller but it contains + ** pointers to free-list leaves. The first leaf becomes a trunk + ** page in this case. + */ + MemPage *pNewTrunk; + Pgno iNewTrunk = get4byte(&pTrunk->aData[8]); + if( iNewTrunk>mxPage ){ + rc = SQLITE_CORRUPT_PGNO(iTrunk); + goto end_allocate_page; + } + testcase( iNewTrunk==mxPage ); + rc = btreeGetUnusedPage(pBt, iNewTrunk, &pNewTrunk, 0); + if( rc!=SQLITE_OK ){ + goto end_allocate_page; + } + rc = sqlite3PagerWrite(pNewTrunk->pDbPage); + if( rc!=SQLITE_OK ){ + releasePage(pNewTrunk); + goto end_allocate_page; + } + memcpy(&pNewTrunk->aData[0], &pTrunk->aData[0], 4); + put4byte(&pNewTrunk->aData[4], k-1); + memcpy(&pNewTrunk->aData[8], &pTrunk->aData[12], (k-1)*4); + releasePage(pNewTrunk); + if( !pPrevTrunk ){ + assert( sqlite3PagerIswriteable(pPage1->pDbPage) ); + put4byte(&pPage1->aData[32], iNewTrunk); + }else{ + rc = sqlite3PagerWrite(pPrevTrunk->pDbPage); + if( rc ){ + goto end_allocate_page; + } + put4byte(&pPrevTrunk->aData[0], iNewTrunk); + } + } + pTrunk = 0; + TRACE(("ALLOCATE: %u trunk - %u free pages left\n", *pPgno, n-1)); +#endif + }else if( k>0 ){ + /* Extract a leaf from the trunk */ + u32 closest; + Pgno iPage; + unsigned char *aData = pTrunk->aData; + if( nearby>0 ){ + u32 i; + closest = 0; + if( eMode==BTALLOC_LE ){ + for(i=0; i<k; i++){ + iPage = get4byte(&aData[8+i*4]); + if( iPage<=nearby ){ + closest = i; + break; + } + } + }else{ + int dist; + dist = sqlite3AbsInt32(get4byte(&aData[8]) - nearby); + for(i=1; i<k; i++){ + int d2 = sqlite3AbsInt32(get4byte(&aData[8+i*4]) - nearby); + if( d2<dist ){ + closest = i; + dist = d2; + } + } + } + }else{ + closest = 0; + } + + iPage = get4byte(&aData[8+closest*4]); + testcase( iPage==mxPage ); + if( iPage>mxPage || iPage<2 ){ + rc = SQLITE_CORRUPT_PGNO(iTrunk); + goto end_allocate_page; + } + testcase( iPage==mxPage ); + if( !searchList + || (iPage==nearby || (iPage<nearby && eMode==BTALLOC_LE)) + ){ + int noContent; + *pPgno = iPage; + TRACE(("ALLOCATE: %u was leaf %u of %u on trunk %u" + ": %u more free pages\n", + *pPgno, closest+1, k, pTrunk->pgno, n-1)); + rc = sqlite3PagerWrite(pTrunk->pDbPage); + if( rc ) goto end_allocate_page; + if( closest<k-1 ){ + memcpy(&aData[8+closest*4], &aData[4+k*4], 4); + } + put4byte(&aData[4], k-1); + noContent = !btreeGetHasContent(pBt, *pPgno)? PAGER_GET_NOCONTENT : 0; + rc = btreeGetUnusedPage(pBt, *pPgno, ppPage, noContent); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerWrite((*ppPage)->pDbPage); + if( rc!=SQLITE_OK ){ + releasePage(*ppPage); + *ppPage = 0; + } + } + searchList = 0; + } + } + releasePage(pPrevTrunk); + pPrevTrunk = 0; + }while( searchList ); + }else{ + /* There are no pages on the freelist, so append a new page to the + ** database image. + ** + ** Normally, new pages allocated by this block can be requested from the + ** pager layer with the 'no-content' flag set. This prevents the pager + ** from trying to read the pages content from disk. However, if the + ** current transaction has already run one or more incremental-vacuum + ** steps, then the page we are about to allocate may contain content + ** that is required in the event of a rollback. In this case, do + ** not set the no-content flag. This causes the pager to load and journal + ** the current page content before overwriting it. + ** + ** Note that the pager will not actually attempt to load or journal + ** content for any page that really does lie past the end of the database + ** file on disk. So the effects of disabling the no-content optimization + ** here are confined to those pages that lie between the end of the + ** database image and the end of the database file. + */ + int bNoContent = (0==IfNotOmitAV(pBt->bDoTruncate))? PAGER_GET_NOCONTENT:0; + + rc = sqlite3PagerWrite(pBt->pPage1->pDbPage); + if( rc ) return rc; + pBt->nPage++; + if( pBt->nPage==PENDING_BYTE_PAGE(pBt) ) pBt->nPage++; + +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pBt->autoVacuum && PTRMAP_ISPAGE(pBt, pBt->nPage) ){ + /* If *pPgno refers to a pointer-map page, allocate two new pages + ** at the end of the file instead of one. The first allocated page + ** becomes a new pointer-map page, the second is used by the caller. + */ + MemPage *pPg = 0; + TRACE(("ALLOCATE: %u from end of file (pointer-map page)\n", pBt->nPage)); + assert( pBt->nPage!=PENDING_BYTE_PAGE(pBt) ); + rc = btreeGetUnusedPage(pBt, pBt->nPage, &pPg, bNoContent); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerWrite(pPg->pDbPage); + releasePage(pPg); + } + if( rc ) return rc; + pBt->nPage++; + if( pBt->nPage==PENDING_BYTE_PAGE(pBt) ){ pBt->nPage++; } + } +#endif + put4byte(28 + (u8*)pBt->pPage1->aData, pBt->nPage); + *pPgno = pBt->nPage; + + assert( *pPgno!=PENDING_BYTE_PAGE(pBt) ); + rc = btreeGetUnusedPage(pBt, *pPgno, ppPage, bNoContent); + if( rc ) return rc; + rc = sqlite3PagerWrite((*ppPage)->pDbPage); + if( rc!=SQLITE_OK ){ + releasePage(*ppPage); + *ppPage = 0; + } + TRACE(("ALLOCATE: %u from end of file\n", *pPgno)); + } + + assert( CORRUPT_DB || *pPgno!=PENDING_BYTE_PAGE(pBt) ); + +end_allocate_page: + releasePage(pTrunk); + releasePage(pPrevTrunk); + assert( rc!=SQLITE_OK || sqlite3PagerPageRefcount((*ppPage)->pDbPage)<=1 ); + assert( rc!=SQLITE_OK || (*ppPage)->isInit==0 ); + return rc; +} + +/* +** This function is used to add page iPage to the database file free-list. +** It is assumed that the page is not already a part of the free-list. +** +** The value passed as the second argument to this function is optional. +** If the caller happens to have a pointer to the MemPage object +** corresponding to page iPage handy, it may pass it as the second value. +** Otherwise, it may pass NULL. +** +** If a pointer to a MemPage object is passed as the second argument, +** its reference count is not altered by this function. +*/ +static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ + MemPage *pTrunk = 0; /* Free-list trunk page */ + Pgno iTrunk = 0; /* Page number of free-list trunk page */ + MemPage *pPage1 = pBt->pPage1; /* Local reference to page 1 */ + MemPage *pPage; /* Page being freed. May be NULL. */ + int rc; /* Return Code */ + u32 nFree; /* Initial number of pages on free-list */ + + assert( sqlite3_mutex_held(pBt->mutex) ); + assert( CORRUPT_DB || iPage>1 ); + assert( !pMemPage || pMemPage->pgno==iPage ); + + if( iPage<2 || iPage>pBt->nPage ){ + return SQLITE_CORRUPT_BKPT; + } + if( pMemPage ){ + pPage = pMemPage; + sqlite3PagerRef(pPage->pDbPage); + }else{ + pPage = btreePageLookup(pBt, iPage); + } + + /* Increment the free page count on pPage1 */ + rc = sqlite3PagerWrite(pPage1->pDbPage); + if( rc ) goto freepage_out; + nFree = get4byte(&pPage1->aData[36]); + put4byte(&pPage1->aData[36], nFree+1); + + if( pBt->btsFlags & BTS_SECURE_DELETE ){ + /* If the secure_delete option is enabled, then + ** always fully overwrite deleted information with zeros. + */ + if( (!pPage && ((rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0) ) + || ((rc = sqlite3PagerWrite(pPage->pDbPage))!=0) + ){ + goto freepage_out; + } + memset(pPage->aData, 0, pPage->pBt->pageSize); + } + + /* If the database supports auto-vacuum, write an entry in the pointer-map + ** to indicate that the page is free. + */ + if( ISAUTOVACUUM(pBt) ){ + ptrmapPut(pBt, iPage, PTRMAP_FREEPAGE, 0, &rc); + if( rc ) goto freepage_out; + } + + /* Now manipulate the actual database free-list structure. There are two + ** possibilities. If the free-list is currently empty, or if the first + ** trunk page in the free-list is full, then this page will become a + ** new free-list trunk page. Otherwise, it will become a leaf of the + ** first trunk page in the current free-list. This block tests if it + ** is possible to add the page as a new free-list leaf. + */ + if( nFree!=0 ){ + u32 nLeaf; /* Initial number of leaf cells on trunk page */ + + iTrunk = get4byte(&pPage1->aData[32]); + if( iTrunk>btreePagecount(pBt) ){ + rc = SQLITE_CORRUPT_BKPT; + goto freepage_out; + } + rc = btreeGetPage(pBt, iTrunk, &pTrunk, 0); + if( rc!=SQLITE_OK ){ + goto freepage_out; + } + + nLeaf = get4byte(&pTrunk->aData[4]); + assert( pBt->usableSize>32 ); + if( nLeaf > (u32)pBt->usableSize/4 - 2 ){ + rc = SQLITE_CORRUPT_BKPT; + goto freepage_out; + } + if( nLeaf < (u32)pBt->usableSize/4 - 8 ){ + /* In this case there is room on the trunk page to insert the page + ** being freed as a new leaf. + ** + ** Note that the trunk page is not really full until it contains + ** usableSize/4 - 2 entries, not usableSize/4 - 8 entries as we have + ** coded. But due to a coding error in versions of SQLite prior to + ** 3.6.0, databases with freelist trunk pages holding more than + ** usableSize/4 - 8 entries will be reported as corrupt. In order + ** to maintain backwards compatibility with older versions of SQLite, + ** we will continue to restrict the number of entries to usableSize/4 - 8 + ** for now. At some point in the future (once everyone has upgraded + ** to 3.6.0 or later) we should consider fixing the conditional above + ** to read "usableSize/4-2" instead of "usableSize/4-8". + ** + ** EVIDENCE-OF: R-19920-11576 However, newer versions of SQLite still + ** avoid using the last six entries in the freelist trunk page array in + ** order that database files created by newer versions of SQLite can be + ** read by older versions of SQLite. + */ + rc = sqlite3PagerWrite(pTrunk->pDbPage); + if( rc==SQLITE_OK ){ + put4byte(&pTrunk->aData[4], nLeaf+1); + put4byte(&pTrunk->aData[8+nLeaf*4], iPage); + if( pPage && (pBt->btsFlags & BTS_SECURE_DELETE)==0 ){ + sqlite3PagerDontWrite(pPage->pDbPage); + } + rc = btreeSetHasContent(pBt, iPage); + } + TRACE(("FREE-PAGE: %u leaf on trunk page %u\n",pPage->pgno,pTrunk->pgno)); + goto freepage_out; + } + } + + /* If control flows to this point, then it was not possible to add the + ** the page being freed as a leaf page of the first trunk in the free-list. + ** Possibly because the free-list is empty, or possibly because the + ** first trunk in the free-list is full. Either way, the page being freed + ** will become the new first trunk page in the free-list. + */ + if( pPage==0 && SQLITE_OK!=(rc = btreeGetPage(pBt, iPage, &pPage, 0)) ){ + goto freepage_out; + } + rc = sqlite3PagerWrite(pPage->pDbPage); + if( rc!=SQLITE_OK ){ + goto freepage_out; + } + put4byte(pPage->aData, iTrunk); + put4byte(&pPage->aData[4], 0); + put4byte(&pPage1->aData[32], iPage); + TRACE(("FREE-PAGE: %u new trunk page replacing %u\n", pPage->pgno, iTrunk)); + +freepage_out: + if( pPage ){ + pPage->isInit = 0; + } + releasePage(pPage); + releasePage(pTrunk); + return rc; +} +static void freePage(MemPage *pPage, int *pRC){ + if( (*pRC)==SQLITE_OK ){ + *pRC = freePage2(pPage->pBt, pPage, pPage->pgno); + } +} + +/* +** Free the overflow pages associated with the given Cell. +*/ +static SQLITE_NOINLINE int clearCellOverflow( + MemPage *pPage, /* The page that contains the Cell */ + unsigned char *pCell, /* First byte of the Cell */ + CellInfo *pInfo /* Size information about the cell */ +){ + BtShared *pBt; + Pgno ovflPgno; + int rc; + int nOvfl; + u32 ovflPageSize; + + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( pInfo->nLocal!=pInfo->nPayload ); + testcase( pCell + pInfo->nSize == pPage->aDataEnd ); + testcase( pCell + (pInfo->nSize-1) == pPage->aDataEnd ); + if( pCell + pInfo->nSize > pPage->aDataEnd ){ + /* Cell extends past end of page */ + return SQLITE_CORRUPT_PAGE(pPage); + } + ovflPgno = get4byte(pCell + pInfo->nSize - 4); + pBt = pPage->pBt; + assert( pBt->usableSize > 4 ); + ovflPageSize = pBt->usableSize - 4; + nOvfl = (pInfo->nPayload - pInfo->nLocal + ovflPageSize - 1)/ovflPageSize; + assert( nOvfl>0 || + (CORRUPT_DB && (pInfo->nPayload + ovflPageSize)<ovflPageSize) + ); + while( nOvfl-- ){ + Pgno iNext = 0; + MemPage *pOvfl = 0; + if( ovflPgno<2 || ovflPgno>btreePagecount(pBt) ){ + /* 0 is not a legal page number and page 1 cannot be an + ** overflow page. Therefore if ovflPgno<2 or past the end of the + ** file the database must be corrupt. */ + return SQLITE_CORRUPT_BKPT; + } + if( nOvfl ){ + rc = getOverflowPage(pBt, ovflPgno, &pOvfl, &iNext); + if( rc ) return rc; + } + + if( ( pOvfl || ((pOvfl = btreePageLookup(pBt, ovflPgno))!=0) ) + && sqlite3PagerPageRefcount(pOvfl->pDbPage)!=1 + ){ + /* There is no reason any cursor should have an outstanding reference + ** to an overflow page belonging to a cell that is being deleted/updated. + ** So if there exists more than one reference to this page, then it + ** must not really be an overflow page and the database must be corrupt. + ** It is helpful to detect this before calling freePage2(), as + ** freePage2() may zero the page contents if secure-delete mode is + ** enabled. If this 'overflow' page happens to be a page that the + ** caller is iterating through or using in some other way, this + ** can be problematic. + */ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = freePage2(pBt, pOvfl, ovflPgno); + } + + if( pOvfl ){ + sqlite3PagerUnref(pOvfl->pDbPage); + } + if( rc ) return rc; + ovflPgno = iNext; + } + return SQLITE_OK; +} + +/* Call xParseCell to compute the size of a cell. If the cell contains +** overflow, then invoke cellClearOverflow to clear out that overflow. +** Store the result code (SQLITE_OK or some error code) in rc. +** +** Implemented as macro to force inlining for performance. +*/ +#define BTREE_CLEAR_CELL(rc, pPage, pCell, sInfo) \ + pPage->xParseCell(pPage, pCell, &sInfo); \ + if( sInfo.nLocal!=sInfo.nPayload ){ \ + rc = clearCellOverflow(pPage, pCell, &sInfo); \ + }else{ \ + rc = SQLITE_OK; \ + } + + +/* +** Create the byte sequence used to represent a cell on page pPage +** and write that byte sequence into pCell[]. Overflow pages are +** allocated and filled in as necessary. The calling procedure +** is responsible for making sure sufficient space has been allocated +** for pCell[]. +** +** Note that pCell does not necessary need to point to the pPage->aData +** area. pCell might point to some temporary storage. The cell will +** be constructed in this temporary area then copied into pPage->aData +** later. +*/ +static int fillInCell( + MemPage *pPage, /* The page that contains the cell */ + unsigned char *pCell, /* Complete text of the cell */ + const BtreePayload *pX, /* Payload with which to construct the cell */ + int *pnSize /* Write cell size here */ +){ + int nPayload; + const u8 *pSrc; + int nSrc, n, rc, mn; + int spaceLeft; + MemPage *pToRelease; + unsigned char *pPrior; + unsigned char *pPayload; + BtShared *pBt; + Pgno pgnoOvfl; + int nHeader; + + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + + /* pPage is not necessarily writeable since pCell might be auxiliary + ** buffer space that is separate from the pPage buffer area */ + assert( pCell<pPage->aData || pCell>=&pPage->aData[pPage->pBt->pageSize] + || sqlite3PagerIswriteable(pPage->pDbPage) ); + + /* Fill in the header. */ + nHeader = pPage->childPtrSize; + if( pPage->intKey ){ + nPayload = pX->nData + pX->nZero; + pSrc = pX->pData; + nSrc = pX->nData; + assert( pPage->intKeyLeaf ); /* fillInCell() only called for leaves */ + nHeader += putVarint32(&pCell[nHeader], nPayload); + nHeader += putVarint(&pCell[nHeader], *(u64*)&pX->nKey); + }else{ + assert( pX->nKey<=0x7fffffff && pX->pKey!=0 ); + nSrc = nPayload = (int)pX->nKey; + pSrc = pX->pKey; + nHeader += putVarint32(&pCell[nHeader], nPayload); + } + + /* Fill in the payload */ + pPayload = &pCell[nHeader]; + if( nPayload<=pPage->maxLocal ){ + /* This is the common case where everything fits on the btree page + ** and no overflow pages are required. */ + n = nHeader + nPayload; + testcase( n==3 ); + testcase( n==4 ); + if( n<4 ) n = 4; + *pnSize = n; + assert( nSrc<=nPayload ); + testcase( nSrc<nPayload ); + memcpy(pPayload, pSrc, nSrc); + memset(pPayload+nSrc, 0, nPayload-nSrc); + return SQLITE_OK; + } + + /* If we reach this point, it means that some of the content will need + ** to spill onto overflow pages. + */ + mn = pPage->minLocal; + n = mn + (nPayload - mn) % (pPage->pBt->usableSize - 4); + testcase( n==pPage->maxLocal ); + testcase( n==pPage->maxLocal+1 ); + if( n > pPage->maxLocal ) n = mn; + spaceLeft = n; + *pnSize = n + nHeader + 4; + pPrior = &pCell[nHeader+n]; + pToRelease = 0; + pgnoOvfl = 0; + pBt = pPage->pBt; + + /* At this point variables should be set as follows: + ** + ** nPayload Total payload size in bytes + ** pPayload Begin writing payload here + ** spaceLeft Space available at pPayload. If nPayload>spaceLeft, + ** that means content must spill into overflow pages. + ** *pnSize Size of the local cell (not counting overflow pages) + ** pPrior Where to write the pgno of the first overflow page + ** + ** Use a call to btreeParseCellPtr() to verify that the values above + ** were computed correctly. + */ +#ifdef SQLITE_DEBUG + { + CellInfo info; + pPage->xParseCell(pPage, pCell, &info); + assert( nHeader==(int)(info.pPayload - pCell) ); + assert( info.nKey==pX->nKey ); + assert( *pnSize == info.nSize ); + assert( spaceLeft == info.nLocal ); + } +#endif + + /* Write the payload into the local Cell and any extra into overflow pages */ + while( 1 ){ + n = nPayload; + if( n>spaceLeft ) n = spaceLeft; + + /* If pToRelease is not zero than pPayload points into the data area + ** of pToRelease. Make sure pToRelease is still writeable. */ + assert( pToRelease==0 || sqlite3PagerIswriteable(pToRelease->pDbPage) ); + + /* If pPayload is part of the data area of pPage, then make sure pPage + ** is still writeable */ + assert( pPayload<pPage->aData || pPayload>=&pPage->aData[pBt->pageSize] + || sqlite3PagerIswriteable(pPage->pDbPage) ); + + if( nSrc>=n ){ + memcpy(pPayload, pSrc, n); + }else if( nSrc>0 ){ + n = nSrc; + memcpy(pPayload, pSrc, n); + }else{ + memset(pPayload, 0, n); + } + nPayload -= n; + if( nPayload<=0 ) break; + pPayload += n; + pSrc += n; + nSrc -= n; + spaceLeft -= n; + if( spaceLeft==0 ){ + MemPage *pOvfl = 0; +#ifndef SQLITE_OMIT_AUTOVACUUM + Pgno pgnoPtrmap = pgnoOvfl; /* Overflow page pointer-map entry page */ + if( pBt->autoVacuum ){ + do{ + pgnoOvfl++; + } while( + PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl==PENDING_BYTE_PAGE(pBt) + ); + } +#endif + rc = allocateBtreePage(pBt, &pOvfl, &pgnoOvfl, pgnoOvfl, 0); +#ifndef SQLITE_OMIT_AUTOVACUUM + /* If the database supports auto-vacuum, and the second or subsequent + ** overflow page is being allocated, add an entry to the pointer-map + ** for that page now. + ** + ** If this is the first overflow page, then write a partial entry + ** to the pointer-map. If we write nothing to this pointer-map slot, + ** then the optimistic overflow chain processing in clearCell() + ** may misinterpret the uninitialized values and delete the + ** wrong pages from the database. + */ + if( pBt->autoVacuum && rc==SQLITE_OK ){ + u8 eType = (pgnoPtrmap?PTRMAP_OVERFLOW2:PTRMAP_OVERFLOW1); + ptrmapPut(pBt, pgnoOvfl, eType, pgnoPtrmap, &rc); + if( rc ){ + releasePage(pOvfl); + } + } +#endif + if( rc ){ + releasePage(pToRelease); + return rc; + } + + /* If pToRelease is not zero than pPrior points into the data area + ** of pToRelease. Make sure pToRelease is still writeable. */ + assert( pToRelease==0 || sqlite3PagerIswriteable(pToRelease->pDbPage) ); + + /* If pPrior is part of the data area of pPage, then make sure pPage + ** is still writeable */ + assert( pPrior<pPage->aData || pPrior>=&pPage->aData[pBt->pageSize] + || sqlite3PagerIswriteable(pPage->pDbPage) ); + + put4byte(pPrior, pgnoOvfl); + releasePage(pToRelease); + pToRelease = pOvfl; + pPrior = pOvfl->aData; + put4byte(pPrior, 0); + pPayload = &pOvfl->aData[4]; + spaceLeft = pBt->usableSize - 4; + } + } + releasePage(pToRelease); + return SQLITE_OK; +} + +/* +** Remove the i-th cell from pPage. This routine effects pPage only. +** The cell content is not freed or deallocated. It is assumed that +** the cell content has been copied someplace else. This routine just +** removes the reference to the cell from pPage. +** +** "sz" must be the number of bytes in the cell. +*/ +static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ + u32 pc; /* Offset to cell content of cell being deleted */ + u8 *data; /* pPage->aData */ + u8 *ptr; /* Used to move bytes around within data[] */ + int rc; /* The return code */ + int hdr; /* Beginning of the header. 0 most pages. 100 page 1 */ + + if( *pRC ) return; + assert( idx>=0 ); + assert( idx<pPage->nCell ); + assert( CORRUPT_DB || sz==cellSize(pPage, idx) ); + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( pPage->nFree>=0 ); + data = pPage->aData; + ptr = &pPage->aCellIdx[2*idx]; + assert( pPage->pBt->usableSize > (u32)(ptr-data) ); + pc = get2byte(ptr); + hdr = pPage->hdrOffset; + testcase( pc==(u32)get2byte(&data[hdr+5]) ); + testcase( pc+sz==pPage->pBt->usableSize ); + if( pc+sz > pPage->pBt->usableSize ){ + *pRC = SQLITE_CORRUPT_BKPT; + return; + } + rc = freeSpace(pPage, pc, sz); + if( rc ){ + *pRC = rc; + return; + } + pPage->nCell--; + if( pPage->nCell==0 ){ + memset(&data[hdr+1], 0, 4); + data[hdr+7] = 0; + put2byte(&data[hdr+5], pPage->pBt->usableSize); + pPage->nFree = pPage->pBt->usableSize - pPage->hdrOffset + - pPage->childPtrSize - 8; + }else{ + memmove(ptr, ptr+2, 2*(pPage->nCell - idx)); + put2byte(&data[hdr+3], pPage->nCell); + pPage->nFree += 2; + } +} + +/* +** Insert a new cell on pPage at cell index "i". pCell points to the +** content of the cell. +** +** If the cell content will fit on the page, then put it there. If it +** will not fit, then make a copy of the cell content into pTemp if +** pTemp is not null. Regardless of pTemp, allocate a new entry +** in pPage->apOvfl[] and make it point to the cell content (either +** in pTemp or the original pCell) and also record its index. +** Allocating a new entry in pPage->aCell[] implies that +** pPage->nOverflow is incremented. +** +** The insertCellFast() routine below works exactly the same as +** insertCell() except that it lacks the pTemp and iChild parameters +** which are assumed zero. Other than that, the two routines are the +** same. +** +** Fixes or enhancements to this routine should be reflected in +** insertCellFast()! +*/ +static int insertCell( + MemPage *pPage, /* Page into which we are copying */ + int i, /* New cell becomes the i-th cell of the page */ + u8 *pCell, /* Content of the new cell */ + int sz, /* Bytes of content in pCell */ + u8 *pTemp, /* Temp storage space for pCell, if needed */ + Pgno iChild /* If non-zero, replace first 4 bytes with this value */ +){ + int idx = 0; /* Where to write new cell content in data[] */ + int j; /* Loop counter */ + u8 *data; /* The content of the whole page */ + u8 *pIns; /* The point in pPage->aCellIdx[] where no cell inserted */ + + assert( i>=0 && i<=pPage->nCell+pPage->nOverflow ); + assert( MX_CELL(pPage->pBt)<=10921 ); + assert( pPage->nCell<=MX_CELL(pPage->pBt) || CORRUPT_DB ); + assert( pPage->nOverflow<=ArraySize(pPage->apOvfl) ); + assert( ArraySize(pPage->apOvfl)==ArraySize(pPage->aiOvfl) ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( sz==pPage->xCellSize(pPage, pCell) || CORRUPT_DB ); + assert( pPage->nFree>=0 ); + assert( iChild>0 ); + if( pPage->nOverflow || sz+2>pPage->nFree ){ + if( pTemp ){ + memcpy(pTemp, pCell, sz); + pCell = pTemp; + } + put4byte(pCell, iChild); + j = pPage->nOverflow++; + /* Comparison against ArraySize-1 since we hold back one extra slot + ** as a contingency. In other words, never need more than 3 overflow + ** slots but 4 are allocated, just to be safe. */ + assert( j < ArraySize(pPage->apOvfl)-1 ); + pPage->apOvfl[j] = pCell; + pPage->aiOvfl[j] = (u16)i; + + /* When multiple overflows occur, they are always sequential and in + ** sorted order. This invariants arise because multiple overflows can + ** only occur when inserting divider cells into the parent page during + ** balancing, and the dividers are adjacent and sorted. + */ + assert( j==0 || pPage->aiOvfl[j-1]<(u16)i ); /* Overflows in sorted order */ + assert( j==0 || i==pPage->aiOvfl[j-1]+1 ); /* Overflows are sequential */ + }else{ + int rc = sqlite3PagerWrite(pPage->pDbPage); + if( NEVER(rc!=SQLITE_OK) ){ + return rc; + } + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + data = pPage->aData; + assert( &data[pPage->cellOffset]==pPage->aCellIdx ); + rc = allocateSpace(pPage, sz, &idx); + if( rc ){ return rc; } + /* The allocateSpace() routine guarantees the following properties + ** if it returns successfully */ + assert( idx >= 0 ); + assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB ); + assert( idx+sz <= (int)pPage->pBt->usableSize ); + pPage->nFree -= (u16)(2 + sz); + /* In a corrupt database where an entry in the cell index section of + ** a btree page has a value of 3 or less, the pCell value might point + ** as many as 4 bytes in front of the start of the aData buffer for + ** the source page. Make sure this does not cause problems by not + ** reading the first 4 bytes */ + memcpy(&data[idx+4], pCell+4, sz-4); + put4byte(&data[idx], iChild); + pIns = pPage->aCellIdx + i*2; + memmove(pIns+2, pIns, 2*(pPage->nCell - i)); + put2byte(pIns, idx); + pPage->nCell++; + /* increment the cell count */ + if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++; + assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell || CORRUPT_DB ); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pPage->pBt->autoVacuum ){ + int rc2 = SQLITE_OK; + /* The cell may contain a pointer to an overflow page. If so, write + ** the entry for the overflow page into the pointer map. + */ + ptrmapPutOvflPtr(pPage, pPage, pCell, &rc2); + if( rc2 ) return rc2; + } +#endif + } + return SQLITE_OK; +} + +/* +** This variant of insertCell() assumes that the pTemp and iChild +** parameters are both zero. Use this variant in sqlite3BtreeInsert() +** for performance improvement, and also so that this variant is only +** called from that one place, and is thus inlined, and thus runs must +** faster. +** +** Fixes or enhancements to this routine should be reflected into +** the insertCell() routine. +*/ +static int insertCellFast( + MemPage *pPage, /* Page into which we are copying */ + int i, /* New cell becomes the i-th cell of the page */ + u8 *pCell, /* Content of the new cell */ + int sz /* Bytes of content in pCell */ +){ + int idx = 0; /* Where to write new cell content in data[] */ + int j; /* Loop counter */ + u8 *data; /* The content of the whole page */ + u8 *pIns; /* The point in pPage->aCellIdx[] where no cell inserted */ + + assert( i>=0 && i<=pPage->nCell+pPage->nOverflow ); + assert( MX_CELL(pPage->pBt)<=10921 ); + assert( pPage->nCell<=MX_CELL(pPage->pBt) || CORRUPT_DB ); + assert( pPage->nOverflow<=ArraySize(pPage->apOvfl) ); + assert( ArraySize(pPage->apOvfl)==ArraySize(pPage->aiOvfl) ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( sz==pPage->xCellSize(pPage, pCell) || CORRUPT_DB ); + assert( pPage->nFree>=0 ); + assert( pPage->nOverflow==0 ); + if( sz+2>pPage->nFree ){ + j = pPage->nOverflow++; + /* Comparison against ArraySize-1 since we hold back one extra slot + ** as a contingency. In other words, never need more than 3 overflow + ** slots but 4 are allocated, just to be safe. */ + assert( j < ArraySize(pPage->apOvfl)-1 ); + pPage->apOvfl[j] = pCell; + pPage->aiOvfl[j] = (u16)i; + + /* When multiple overflows occur, they are always sequential and in + ** sorted order. This invariants arise because multiple overflows can + ** only occur when inserting divider cells into the parent page during + ** balancing, and the dividers are adjacent and sorted. + */ + assert( j==0 || pPage->aiOvfl[j-1]<(u16)i ); /* Overflows in sorted order */ + assert( j==0 || i==pPage->aiOvfl[j-1]+1 ); /* Overflows are sequential */ + }else{ + int rc = sqlite3PagerWrite(pPage->pDbPage); + if( rc!=SQLITE_OK ){ + return rc; + } + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + data = pPage->aData; + assert( &data[pPage->cellOffset]==pPage->aCellIdx ); + rc = allocateSpace(pPage, sz, &idx); + if( rc ){ return rc; } + /* The allocateSpace() routine guarantees the following properties + ** if it returns successfully */ + assert( idx >= 0 ); + assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB ); + assert( idx+sz <= (int)pPage->pBt->usableSize ); + pPage->nFree -= (u16)(2 + sz); + memcpy(&data[idx], pCell, sz); + pIns = pPage->aCellIdx + i*2; + memmove(pIns+2, pIns, 2*(pPage->nCell - i)); + put2byte(pIns, idx); + pPage->nCell++; + /* increment the cell count */ + if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++; + assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell || CORRUPT_DB ); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pPage->pBt->autoVacuum ){ + int rc2 = SQLITE_OK; + /* The cell may contain a pointer to an overflow page. If so, write + ** the entry for the overflow page into the pointer map. + */ + ptrmapPutOvflPtr(pPage, pPage, pCell, &rc2); + if( rc2 ) return rc2; + } +#endif + } + return SQLITE_OK; +} + +/* +** The following parameters determine how many adjacent pages get involved +** in a balancing operation. NN is the number of neighbors on either side +** of the page that participate in the balancing operation. NB is the +** total number of pages that participate, including the target page and +** NN neighbors on either side. +** +** The minimum value of NN is 1 (of course). Increasing NN above 1 +** (to 2 or 3) gives a modest improvement in SELECT and DELETE performance +** in exchange for a larger degradation in INSERT and UPDATE performance. +** The value of NN appears to give the best results overall. +** +** (Later:) The description above makes it seem as if these values are +** tunable - as if you could change them and recompile and it would all work. +** But that is unlikely. NB has been 3 since the inception of SQLite and +** we have never tested any other value. +*/ +#define NN 1 /* Number of neighbors on either side of pPage */ +#define NB 3 /* (NN*2+1): Total pages involved in the balance */ + +/* +** A CellArray object contains a cache of pointers and sizes for a +** consecutive sequence of cells that might be held on multiple pages. +** +** The cells in this array are the divider cell or cells from the pParent +** page plus up to three child pages. There are a total of nCell cells. +** +** pRef is a pointer to one of the pages that contributes cells. This is +** used to access information such as MemPage.intKey and MemPage.pBt->pageSize +** which should be common to all pages that contribute cells to this array. +** +** apCell[] and szCell[] hold, respectively, pointers to the start of each +** cell and the size of each cell. Some of the apCell[] pointers might refer +** to overflow cells. In other words, some apCel[] pointers might not point +** to content area of the pages. +** +** A szCell[] of zero means the size of that cell has not yet been computed. +** +** The cells come from as many as four different pages: +** +** ----------- +** | Parent | +** ----------- +** / | \ +** / | \ +** --------- --------- --------- +** |Child-1| |Child-2| |Child-3| +** --------- --------- --------- +** +** The order of cells is in the array is for an index btree is: +** +** 1. All cells from Child-1 in order +** 2. The first divider cell from Parent +** 3. All cells from Child-2 in order +** 4. The second divider cell from Parent +** 5. All cells from Child-3 in order +** +** For a table-btree (with rowids) the items 2 and 4 are empty because +** content exists only in leaves and there are no divider cells. +** +** For an index btree, the apEnd[] array holds pointer to the end of page +** for Child-1, the Parent, Child-2, the Parent (again), and Child-3, +** respectively. The ixNx[] array holds the number of cells contained in +** each of these 5 stages, and all stages to the left. Hence: +** +** ixNx[0] = Number of cells in Child-1. +** ixNx[1] = Number of cells in Child-1 plus 1 for first divider. +** ixNx[2] = Number of cells in Child-1 and Child-2 + 1 for 1st divider. +** ixNx[3] = Number of cells in Child-1 and Child-2 + both divider cells +** ixNx[4] = Total number of cells. +** +** For a table-btree, the concept is similar, except only apEnd[0]..apEnd[2] +** are used and they point to the leaf pages only, and the ixNx value are: +** +** ixNx[0] = Number of cells in Child-1. +** ixNx[1] = Number of cells in Child-1 and Child-2. +** ixNx[2] = Total number of cells. +** +** Sometimes when deleting, a child page can have zero cells. In those +** cases, ixNx[] entries with higher indexes, and the corresponding apEnd[] +** entries, shift down. The end result is that each ixNx[] entry should +** be larger than the previous +*/ +typedef struct CellArray CellArray; +struct CellArray { + int nCell; /* Number of cells in apCell[] */ + MemPage *pRef; /* Reference page */ + u8 **apCell; /* All cells begin balanced */ + u16 *szCell; /* Local size of all cells in apCell[] */ + u8 *apEnd[NB*2]; /* MemPage.aDataEnd values */ + int ixNx[NB*2]; /* Index of at which we move to the next apEnd[] */ +}; + +/* +** Make sure the cell sizes at idx, idx+1, ..., idx+N-1 have been +** computed. +*/ +static void populateCellCache(CellArray *p, int idx, int N){ + MemPage *pRef = p->pRef; + u16 *szCell = p->szCell; + assert( idx>=0 && idx+N<=p->nCell ); + while( N>0 ){ + assert( p->apCell[idx]!=0 ); + if( szCell[idx]==0 ){ + szCell[idx] = pRef->xCellSize(pRef, p->apCell[idx]); + }else{ + assert( CORRUPT_DB || + szCell[idx]==pRef->xCellSize(pRef, p->apCell[idx]) ); + } + idx++; + N--; + } +} + +/* +** Return the size of the Nth element of the cell array +*/ +static SQLITE_NOINLINE u16 computeCellSize(CellArray *p, int N){ + assert( N>=0 && N<p->nCell ); + assert( p->szCell[N]==0 ); + p->szCell[N] = p->pRef->xCellSize(p->pRef, p->apCell[N]); + return p->szCell[N]; +} +static u16 cachedCellSize(CellArray *p, int N){ + assert( N>=0 && N<p->nCell ); + if( p->szCell[N] ) return p->szCell[N]; + return computeCellSize(p, N); +} + +/* +** Array apCell[] contains pointers to nCell b-tree page cells. The +** szCell[] array contains the size in bytes of each cell. This function +** replaces the current contents of page pPg with the contents of the cell +** array. +** +** Some of the cells in apCell[] may currently be stored in pPg. This +** function works around problems caused by this by making a copy of any +** such cells before overwriting the page data. +** +** The MemPage.nFree field is invalidated by this function. It is the +** responsibility of the caller to set it correctly. +*/ +static int rebuildPage( + CellArray *pCArray, /* Content to be added to page pPg */ + int iFirst, /* First cell in pCArray to use */ + int nCell, /* Final number of cells on page */ + MemPage *pPg /* The page to be reconstructed */ +){ + const int hdr = pPg->hdrOffset; /* Offset of header on pPg */ + u8 * const aData = pPg->aData; /* Pointer to data for pPg */ + const int usableSize = pPg->pBt->usableSize; + u8 * const pEnd = &aData[usableSize]; + int i = iFirst; /* Which cell to copy from pCArray*/ + u32 j; /* Start of cell content area */ + int iEnd = i+nCell; /* Loop terminator */ + u8 *pCellptr = pPg->aCellIdx; + u8 *pTmp = sqlite3PagerTempSpace(pPg->pBt->pPager); + u8 *pData; + int k; /* Current slot in pCArray->apEnd[] */ + u8 *pSrcEnd; /* Current pCArray->apEnd[k] value */ + + assert( i<iEnd ); + j = get2byte(&aData[hdr+5]); + if( NEVER(j>(u32)usableSize) ){ j = 0; } + memcpy(&pTmp[j], &aData[j], usableSize - j); + + for(k=0; ALWAYS(k<NB*2) && pCArray->ixNx[k]<=i; k++){} + pSrcEnd = pCArray->apEnd[k]; + + pData = pEnd; + while( 1/*exit by break*/ ){ + u8 *pCell = pCArray->apCell[i]; + u16 sz = pCArray->szCell[i]; + assert( sz>0 ); + if( SQLITE_WITHIN(pCell,aData+j,pEnd) ){ + if( ((uptr)(pCell+sz))>(uptr)pEnd ) return SQLITE_CORRUPT_BKPT; + pCell = &pTmp[pCell - aData]; + }else if( (uptr)(pCell+sz)>(uptr)pSrcEnd + && (uptr)(pCell)<(uptr)pSrcEnd + ){ + return SQLITE_CORRUPT_BKPT; + } + + pData -= sz; + put2byte(pCellptr, (pData - aData)); + pCellptr += 2; + if( pData < pCellptr ) return SQLITE_CORRUPT_BKPT; + memmove(pData, pCell, sz); + assert( sz==pPg->xCellSize(pPg, pCell) || CORRUPT_DB ); + i++; + if( i>=iEnd ) break; + if( pCArray->ixNx[k]<=i ){ + k++; + pSrcEnd = pCArray->apEnd[k]; + } + } + + /* The pPg->nFree field is now set incorrectly. The caller will fix it. */ + pPg->nCell = nCell; + pPg->nOverflow = 0; + + put2byte(&aData[hdr+1], 0); + put2byte(&aData[hdr+3], pPg->nCell); + put2byte(&aData[hdr+5], pData - aData); + aData[hdr+7] = 0x00; + return SQLITE_OK; +} + +/* +** The pCArray objects contains pointers to b-tree cells and the cell sizes. +** This function attempts to add the cells stored in the array to page pPg. +** If it cannot (because the page needs to be defragmented before the cells +** will fit), non-zero is returned. Otherwise, if the cells are added +** successfully, zero is returned. +** +** Argument pCellptr points to the first entry in the cell-pointer array +** (part of page pPg) to populate. After cell apCell[0] is written to the +** page body, a 16-bit offset is written to pCellptr. And so on, for each +** cell in the array. It is the responsibility of the caller to ensure +** that it is safe to overwrite this part of the cell-pointer array. +** +** When this function is called, *ppData points to the start of the +** content area on page pPg. If the size of the content area is extended, +** *ppData is updated to point to the new start of the content area +** before returning. +** +** Finally, argument pBegin points to the byte immediately following the +** end of the space required by this page for the cell-pointer area (for +** all cells - not just those inserted by the current call). If the content +** area must be extended to before this point in order to accommodate all +** cells in apCell[], then the cells do not fit and non-zero is returned. +*/ +static int pageInsertArray( + MemPage *pPg, /* Page to add cells to */ + u8 *pBegin, /* End of cell-pointer array */ + u8 **ppData, /* IN/OUT: Page content-area pointer */ + u8 *pCellptr, /* Pointer to cell-pointer area */ + int iFirst, /* Index of first cell to add */ + int nCell, /* Number of cells to add to pPg */ + CellArray *pCArray /* Array of cells */ +){ + int i = iFirst; /* Loop counter - cell index to insert */ + u8 *aData = pPg->aData; /* Complete page */ + u8 *pData = *ppData; /* Content area. A subset of aData[] */ + int iEnd = iFirst + nCell; /* End of loop. One past last cell to ins */ + int k; /* Current slot in pCArray->apEnd[] */ + u8 *pEnd; /* Maximum extent of cell data */ + assert( CORRUPT_DB || pPg->hdrOffset==0 ); /* Never called on page 1 */ + if( iEnd<=iFirst ) return 0; + for(k=0; ALWAYS(k<NB*2) && pCArray->ixNx[k]<=i ; k++){} + pEnd = pCArray->apEnd[k]; + while( 1 /*Exit by break*/ ){ + int sz, rc; + u8 *pSlot; + assert( pCArray->szCell[i]!=0 ); + sz = pCArray->szCell[i]; + if( (aData[1]==0 && aData[2]==0) || (pSlot = pageFindSlot(pPg,sz,&rc))==0 ){ + if( (pData - pBegin)<sz ) return 1; + pData -= sz; + pSlot = pData; + } + /* pSlot and pCArray->apCell[i] will never overlap on a well-formed + ** database. But they might for a corrupt database. Hence use memmove() + ** since memcpy() sends SIGABORT with overlapping buffers on OpenBSD */ + assert( (pSlot+sz)<=pCArray->apCell[i] + || pSlot>=(pCArray->apCell[i]+sz) + || CORRUPT_DB ); + if( (uptr)(pCArray->apCell[i]+sz)>(uptr)pEnd + && (uptr)(pCArray->apCell[i])<(uptr)pEnd + ){ + assert( CORRUPT_DB ); + (void)SQLITE_CORRUPT_BKPT; + return 1; + } + memmove(pSlot, pCArray->apCell[i], sz); + put2byte(pCellptr, (pSlot - aData)); + pCellptr += 2; + i++; + if( i>=iEnd ) break; + if( pCArray->ixNx[k]<=i ){ + k++; + pEnd = pCArray->apEnd[k]; + } + } + *ppData = pData; + return 0; +} + +/* +** The pCArray object contains pointers to b-tree cells and their sizes. +** +** This function adds the space associated with each cell in the array +** that is currently stored within the body of pPg to the pPg free-list. +** The cell-pointers and other fields of the page are not updated. +** +** This function returns the total number of cells added to the free-list. +*/ +static int pageFreeArray( + MemPage *pPg, /* Page to edit */ + int iFirst, /* First cell to delete */ + int nCell, /* Cells to delete */ + CellArray *pCArray /* Array of cells */ +){ + u8 * const aData = pPg->aData; + u8 * const pEnd = &aData[pPg->pBt->usableSize]; + u8 * const pStart = &aData[pPg->hdrOffset + 8 + pPg->childPtrSize]; + int nRet = 0; + int i, j; + int iEnd = iFirst + nCell; + int nFree = 0; + int aOfst[10]; + int aAfter[10]; + + for(i=iFirst; i<iEnd; i++){ + u8 *pCell = pCArray->apCell[i]; + if( SQLITE_WITHIN(pCell, pStart, pEnd) ){ + int sz; + int iAfter; + int iOfst; + /* No need to use cachedCellSize() here. The sizes of all cells that + ** are to be freed have already been computing while deciding which + ** cells need freeing */ + sz = pCArray->szCell[i]; assert( sz>0 ); + iOfst = (u16)(pCell - aData); + iAfter = iOfst+sz; + for(j=0; j<nFree; j++){ + if( aOfst[j]==iAfter ){ + aOfst[j] = iOfst; + break; + }else if( aAfter[j]==iOfst ){ + aAfter[j] = iAfter; + break; + } + } + if( j>=nFree ){ + if( nFree>=(int)(sizeof(aOfst)/sizeof(aOfst[0])) ){ + for(j=0; j<nFree; j++){ + freeSpace(pPg, aOfst[j], aAfter[j]-aOfst[j]); + } + nFree = 0; + } + aOfst[nFree] = iOfst; + aAfter[nFree] = iAfter; + if( &aData[iAfter]>pEnd ) return 0; + nFree++; + } + nRet++; + } + } + for(j=0; j<nFree; j++){ + freeSpace(pPg, aOfst[j], aAfter[j]-aOfst[j]); + } + return nRet; +} + +/* +** pCArray contains pointers to and sizes of all cells in the page being +** balanced. The current page, pPg, has pPg->nCell cells starting with +** pCArray->apCell[iOld]. After balancing, this page should hold nNew cells +** starting at apCell[iNew]. +** +** This routine makes the necessary adjustments to pPg so that it contains +** the correct cells after being balanced. +** +** The pPg->nFree field is invalid when this function returns. It is the +** responsibility of the caller to set it correctly. +*/ +static int editPage( + MemPage *pPg, /* Edit this page */ + int iOld, /* Index of first cell currently on page */ + int iNew, /* Index of new first cell on page */ + int nNew, /* Final number of cells on page */ + CellArray *pCArray /* Array of cells and sizes */ +){ + u8 * const aData = pPg->aData; + const int hdr = pPg->hdrOffset; + u8 *pBegin = &pPg->aCellIdx[nNew * 2]; + int nCell = pPg->nCell; /* Cells stored on pPg */ + u8 *pData; + u8 *pCellptr; + int i; + int iOldEnd = iOld + pPg->nCell + pPg->nOverflow; + int iNewEnd = iNew + nNew; + +#ifdef SQLITE_DEBUG + u8 *pTmp = sqlite3PagerTempSpace(pPg->pBt->pPager); + memcpy(pTmp, aData, pPg->pBt->usableSize); +#endif + + /* Remove cells from the start and end of the page */ + assert( nCell>=0 ); + if( iOld<iNew ){ + int nShift = pageFreeArray(pPg, iOld, iNew-iOld, pCArray); + if( NEVER(nShift>nCell) ) return SQLITE_CORRUPT_BKPT; + memmove(pPg->aCellIdx, &pPg->aCellIdx[nShift*2], nCell*2); + nCell -= nShift; + } + if( iNewEnd < iOldEnd ){ + int nTail = pageFreeArray(pPg, iNewEnd, iOldEnd - iNewEnd, pCArray); + assert( nCell>=nTail ); + nCell -= nTail; + } + + pData = &aData[get2byte(&aData[hdr+5])]; + if( pData<pBegin ) goto editpage_fail; + if( NEVER(pData>pPg->aDataEnd) ) goto editpage_fail; + + /* Add cells to the start of the page */ + if( iNew<iOld ){ + int nAdd = MIN(nNew,iOld-iNew); + assert( (iOld-iNew)<nNew || nCell==0 || CORRUPT_DB ); + assert( nAdd>=0 ); + pCellptr = pPg->aCellIdx; + memmove(&pCellptr[nAdd*2], pCellptr, nCell*2); + if( pageInsertArray( + pPg, pBegin, &pData, pCellptr, + iNew, nAdd, pCArray + ) ) goto editpage_fail; + nCell += nAdd; + } + + /* Add any overflow cells */ + for(i=0; i<pPg->nOverflow; i++){ + int iCell = (iOld + pPg->aiOvfl[i]) - iNew; + if( iCell>=0 && iCell<nNew ){ + pCellptr = &pPg->aCellIdx[iCell * 2]; + if( nCell>iCell ){ + memmove(&pCellptr[2], pCellptr, (nCell - iCell) * 2); + } + nCell++; + cachedCellSize(pCArray, iCell+iNew); + if( pageInsertArray( + pPg, pBegin, &pData, pCellptr, + iCell+iNew, 1, pCArray + ) ) goto editpage_fail; + } + } + + /* Append cells to the end of the page */ + assert( nCell>=0 ); + pCellptr = &pPg->aCellIdx[nCell*2]; + if( pageInsertArray( + pPg, pBegin, &pData, pCellptr, + iNew+nCell, nNew-nCell, pCArray + ) ) goto editpage_fail; + + pPg->nCell = nNew; + pPg->nOverflow = 0; + + put2byte(&aData[hdr+3], pPg->nCell); + put2byte(&aData[hdr+5], pData - aData); + +#ifdef SQLITE_DEBUG + for(i=0; i<nNew && !CORRUPT_DB; i++){ + u8 *pCell = pCArray->apCell[i+iNew]; + int iOff = get2byteAligned(&pPg->aCellIdx[i*2]); + if( SQLITE_WITHIN(pCell, aData, &aData[pPg->pBt->usableSize]) ){ + pCell = &pTmp[pCell - aData]; + } + assert( 0==memcmp(pCell, &aData[iOff], + pCArray->pRef->xCellSize(pCArray->pRef, pCArray->apCell[i+iNew])) ); + } +#endif + + return SQLITE_OK; + editpage_fail: + /* Unable to edit this page. Rebuild it from scratch instead. */ + populateCellCache(pCArray, iNew, nNew); + return rebuildPage(pCArray, iNew, nNew, pPg); +} + + +#ifndef SQLITE_OMIT_QUICKBALANCE +/* +** This version of balance() handles the common special case where +** a new entry is being inserted on the extreme right-end of the +** tree, in other words, when the new entry will become the largest +** entry in the tree. +** +** Instead of trying to balance the 3 right-most leaf pages, just add +** a new page to the right-hand side and put the one new entry in +** that page. This leaves the right side of the tree somewhat +** unbalanced. But odds are that we will be inserting new entries +** at the end soon afterwards so the nearly empty page will quickly +** fill up. On average. +** +** pPage is the leaf page which is the right-most page in the tree. +** pParent is its parent. pPage must have a single overflow entry +** which is also the right-most entry on the page. +** +** The pSpace buffer is used to store a temporary copy of the divider +** cell that will be inserted into pParent. Such a cell consists of a 4 +** byte page number followed by a variable length integer. In other +** words, at most 13 bytes. Hence the pSpace buffer must be at +** least 13 bytes in size. +*/ +static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ + BtShared *const pBt = pPage->pBt; /* B-Tree Database */ + MemPage *pNew; /* Newly allocated page */ + int rc; /* Return Code */ + Pgno pgnoNew; /* Page number of pNew */ + + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( sqlite3PagerIswriteable(pParent->pDbPage) ); + assert( pPage->nOverflow==1 ); + + if( pPage->nCell==0 ) return SQLITE_CORRUPT_BKPT; /* dbfuzz001.test */ + assert( pPage->nFree>=0 ); + assert( pParent->nFree>=0 ); + + /* Allocate a new page. This page will become the right-sibling of + ** pPage. Make the parent page writable, so that the new divider cell + ** may be inserted. If both these operations are successful, proceed. + */ + rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); + + if( rc==SQLITE_OK ){ + + u8 *pOut = &pSpace[4]; + u8 *pCell = pPage->apOvfl[0]; + u16 szCell = pPage->xCellSize(pPage, pCell); + u8 *pStop; + CellArray b; + + assert( sqlite3PagerIswriteable(pNew->pDbPage) ); + assert( CORRUPT_DB || pPage->aData[0]==(PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF) ); + zeroPage(pNew, PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF); + b.nCell = 1; + b.pRef = pPage; + b.apCell = &pCell; + b.szCell = &szCell; + b.apEnd[0] = pPage->aDataEnd; + b.ixNx[0] = 2; + rc = rebuildPage(&b, 0, 1, pNew); + if( NEVER(rc) ){ + releasePage(pNew); + return rc; + } + pNew->nFree = pBt->usableSize - pNew->cellOffset - 2 - szCell; + + /* If this is an auto-vacuum database, update the pointer map + ** with entries for the new page, and any pointer from the + ** cell on the page to an overflow page. If either of these + ** operations fails, the return code is set, but the contents + ** of the parent page are still manipulated by the code below. + ** That is Ok, at this point the parent page is guaranteed to + ** be marked as dirty. Returning an error code will cause a + ** rollback, undoing any changes made to the parent page. + */ + if( ISAUTOVACUUM(pBt) ){ + ptrmapPut(pBt, pgnoNew, PTRMAP_BTREE, pParent->pgno, &rc); + if( szCell>pNew->minLocal ){ + ptrmapPutOvflPtr(pNew, pNew, pCell, &rc); + } + } + + /* Create a divider cell to insert into pParent. The divider cell + ** consists of a 4-byte page number (the page number of pPage) and + ** a variable length key value (which must be the same value as the + ** largest key on pPage). + ** + ** To find the largest key value on pPage, first find the right-most + ** cell on pPage. The first two fields of this cell are the + ** record-length (a variable length integer at most 32-bits in size) + ** and the key value (a variable length integer, may have any value). + ** The first of the while(...) loops below skips over the record-length + ** field. The second while(...) loop copies the key value from the + ** cell on pPage into the pSpace buffer. + */ + pCell = findCell(pPage, pPage->nCell-1); + pStop = &pCell[9]; + while( (*(pCell++)&0x80) && pCell<pStop ); + pStop = &pCell[9]; + while( ((*(pOut++) = *(pCell++))&0x80) && pCell<pStop ); + + /* Insert the new divider cell into pParent. */ + if( rc==SQLITE_OK ){ + rc = insertCell(pParent, pParent->nCell, pSpace, (int)(pOut-pSpace), + 0, pPage->pgno); + } + + /* Set the right-child pointer of pParent to point to the new page. */ + put4byte(&pParent->aData[pParent->hdrOffset+8], pgnoNew); + + /* Release the reference to the new page. */ + releasePage(pNew); + } + + return rc; +} +#endif /* SQLITE_OMIT_QUICKBALANCE */ + +#if 0 +/* +** This function does not contribute anything to the operation of SQLite. +** it is sometimes activated temporarily while debugging code responsible +** for setting pointer-map entries. +*/ +static int ptrmapCheckPages(MemPage **apPage, int nPage){ + int i, j; + for(i=0; i<nPage; i++){ + Pgno n; + u8 e; + MemPage *pPage = apPage[i]; + BtShared *pBt = pPage->pBt; + assert( pPage->isInit ); + + for(j=0; j<pPage->nCell; j++){ + CellInfo info; + u8 *z; + + z = findCell(pPage, j); + pPage->xParseCell(pPage, z, &info); + if( info.nLocal<info.nPayload ){ + Pgno ovfl = get4byte(&z[info.nSize-4]); + ptrmapGet(pBt, ovfl, &e, &n); + assert( n==pPage->pgno && e==PTRMAP_OVERFLOW1 ); + } + if( !pPage->leaf ){ + Pgno child = get4byte(z); + ptrmapGet(pBt, child, &e, &n); + assert( n==pPage->pgno && e==PTRMAP_BTREE ); + } + } + if( !pPage->leaf ){ + Pgno child = get4byte(&pPage->aData[pPage->hdrOffset+8]); + ptrmapGet(pBt, child, &e, &n); + assert( n==pPage->pgno && e==PTRMAP_BTREE ); + } + } + return 1; +} +#endif + +/* +** This function is used to copy the contents of the b-tree node stored +** on page pFrom to page pTo. If page pFrom was not a leaf page, then +** the pointer-map entries for each child page are updated so that the +** parent page stored in the pointer map is page pTo. If pFrom contained +** any cells with overflow page pointers, then the corresponding pointer +** map entries are also updated so that the parent page is page pTo. +** +** If pFrom is currently carrying any overflow cells (entries in the +** MemPage.apOvfl[] array), they are not copied to pTo. +** +** Before returning, page pTo is reinitialized using btreeInitPage(). +** +** The performance of this function is not critical. It is only used by +** the balance_shallower() and balance_deeper() procedures, neither of +** which are called often under normal circumstances. +*/ +static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ + if( (*pRC)==SQLITE_OK ){ + BtShared * const pBt = pFrom->pBt; + u8 * const aFrom = pFrom->aData; + u8 * const aTo = pTo->aData; + int const iFromHdr = pFrom->hdrOffset; + int const iToHdr = ((pTo->pgno==1) ? 100 : 0); + int rc; + int iData; + + + assert( pFrom->isInit ); + assert( pFrom->nFree>=iToHdr ); + assert( get2byte(&aFrom[iFromHdr+5]) <= (int)pBt->usableSize ); + + /* Copy the b-tree node content from page pFrom to page pTo. */ + iData = get2byte(&aFrom[iFromHdr+5]); + memcpy(&aTo[iData], &aFrom[iData], pBt->usableSize-iData); + memcpy(&aTo[iToHdr], &aFrom[iFromHdr], pFrom->cellOffset + 2*pFrom->nCell); + + /* Reinitialize page pTo so that the contents of the MemPage structure + ** match the new data. The initialization of pTo can actually fail under + ** fairly obscure circumstances, even though it is a copy of initialized + ** page pFrom. + */ + pTo->isInit = 0; + rc = btreeInitPage(pTo); + if( rc==SQLITE_OK ) rc = btreeComputeFreeSpace(pTo); + if( rc!=SQLITE_OK ){ + *pRC = rc; + return; + } + + /* If this is an auto-vacuum database, update the pointer-map entries + ** for any b-tree or overflow pages that pTo now contains the pointers to. + */ + if( ISAUTOVACUUM(pBt) ){ + *pRC = setChildPtrmaps(pTo); + } + } +} + +/* +** This routine redistributes cells on the iParentIdx'th child of pParent +** (hereafter "the page") and up to 2 siblings so that all pages have about the +** same amount of free space. Usually a single sibling on either side of the +** page are used in the balancing, though both siblings might come from one +** side if the page is the first or last child of its parent. If the page +** has fewer than 2 siblings (something which can only happen if the page +** is a root page or a child of a root page) then all available siblings +** participate in the balancing. +** +** The number of siblings of the page might be increased or decreased by +** one or two in an effort to keep pages nearly full but not over full. +** +** Note that when this routine is called, some of the cells on the page +** might not actually be stored in MemPage.aData[]. This can happen +** if the page is overfull. This routine ensures that all cells allocated +** to the page and its siblings fit into MemPage.aData[] before returning. +** +** In the course of balancing the page and its siblings, cells may be +** inserted into or removed from the parent page (pParent). Doing so +** may cause the parent page to become overfull or underfull. If this +** happens, it is the responsibility of the caller to invoke the correct +** balancing routine to fix this problem (see the balance() routine). +** +** If this routine fails for any reason, it might leave the database +** in a corrupted state. So if this routine fails, the database should +** be rolled back. +** +** The third argument to this function, aOvflSpace, is a pointer to a +** buffer big enough to hold one page. If while inserting cells into the parent +** page (pParent) the parent page becomes overfull, this buffer is +** used to store the parent's overflow cells. Because this function inserts +** a maximum of four divider cells into the parent page, and the maximum +** size of a cell stored within an internal node is always less than 1/4 +** of the page-size, the aOvflSpace[] buffer is guaranteed to be large +** enough for all overflow cells. +** +** If aOvflSpace is set to a null pointer, this function returns +** SQLITE_NOMEM. +*/ +static int balance_nonroot( + MemPage *pParent, /* Parent page of siblings being balanced */ + int iParentIdx, /* Index of "the page" in pParent */ + u8 *aOvflSpace, /* page-size bytes of space for parent ovfl */ + int isRoot, /* True if pParent is a root-page */ + int bBulk /* True if this call is part of a bulk load */ +){ + BtShared *pBt; /* The whole database */ + int nMaxCells = 0; /* Allocated size of apCell, szCell, aFrom. */ + int nNew = 0; /* Number of pages in apNew[] */ + int nOld; /* Number of pages in apOld[] */ + int i, j, k; /* Loop counters */ + int nxDiv; /* Next divider slot in pParent->aCell[] */ + int rc = SQLITE_OK; /* The return code */ + u16 leafCorrection; /* 4 if pPage is a leaf. 0 if not */ + int leafData; /* True if pPage is a leaf of a LEAFDATA tree */ + int usableSpace; /* Bytes in pPage beyond the header */ + int pageFlags; /* Value of pPage->aData[0] */ + int iSpace1 = 0; /* First unused byte of aSpace1[] */ + int iOvflSpace = 0; /* First unused byte of aOvflSpace[] */ + int szScratch; /* Size of scratch memory requested */ + MemPage *apOld[NB]; /* pPage and up to two siblings */ + MemPage *apNew[NB+2]; /* pPage and up to NB siblings after balancing */ + u8 *pRight; /* Location in parent of right-sibling pointer */ + u8 *apDiv[NB-1]; /* Divider cells in pParent */ + int cntNew[NB+2]; /* Index in b.paCell[] of cell after i-th page */ + int cntOld[NB+2]; /* Old index in b.apCell[] */ + int szNew[NB+2]; /* Combined size of cells placed on i-th page */ + u8 *aSpace1; /* Space for copies of dividers cells */ + Pgno pgno; /* Temp var to store a page number in */ + u8 abDone[NB+2]; /* True after i'th new page is populated */ + Pgno aPgno[NB+2]; /* Page numbers of new pages before shuffling */ + CellArray b; /* Parsed information on cells being balanced */ + + memset(abDone, 0, sizeof(abDone)); + memset(&b, 0, sizeof(b)); + pBt = pParent->pBt; + assert( sqlite3_mutex_held(pBt->mutex) ); + assert( sqlite3PagerIswriteable(pParent->pDbPage) ); + + /* At this point pParent may have at most one overflow cell. And if + ** this overflow cell is present, it must be the cell with + ** index iParentIdx. This scenario comes about when this function + ** is called (indirectly) from sqlite3BtreeDelete(). + */ + assert( pParent->nOverflow==0 || pParent->nOverflow==1 ); + assert( pParent->nOverflow==0 || pParent->aiOvfl[0]==iParentIdx ); + + if( !aOvflSpace ){ + return SQLITE_NOMEM_BKPT; + } + assert( pParent->nFree>=0 ); + + /* Find the sibling pages to balance. Also locate the cells in pParent + ** that divide the siblings. An attempt is made to find NN siblings on + ** either side of pPage. More siblings are taken from one side, however, + ** if there are fewer than NN siblings on the other side. If pParent + ** has NB or fewer children then all children of pParent are taken. + ** + ** This loop also drops the divider cells from the parent page. This + ** way, the remainder of the function does not have to deal with any + ** overflow cells in the parent page, since if any existed they will + ** have already been removed. + */ + i = pParent->nOverflow + pParent->nCell; + if( i<2 ){ + nxDiv = 0; + }else{ + assert( bBulk==0 || bBulk==1 ); + if( iParentIdx==0 ){ + nxDiv = 0; + }else if( iParentIdx==i ){ + nxDiv = i-2+bBulk; + }else{ + nxDiv = iParentIdx-1; + } + i = 2-bBulk; + } + nOld = i+1; + if( (i+nxDiv-pParent->nOverflow)==pParent->nCell ){ + pRight = &pParent->aData[pParent->hdrOffset+8]; + }else{ + pRight = findCell(pParent, i+nxDiv-pParent->nOverflow); + } + pgno = get4byte(pRight); + while( 1 ){ + if( rc==SQLITE_OK ){ + rc = getAndInitPage(pBt, pgno, &apOld[i], 0); + } + if( rc ){ + memset(apOld, 0, (i+1)*sizeof(MemPage*)); + goto balance_cleanup; + } + if( apOld[i]->nFree<0 ){ + rc = btreeComputeFreeSpace(apOld[i]); + if( rc ){ + memset(apOld, 0, (i)*sizeof(MemPage*)); + goto balance_cleanup; + } + } + nMaxCells += apOld[i]->nCell + ArraySize(pParent->apOvfl); + if( (i--)==0 ) break; + + if( pParent->nOverflow && i+nxDiv==pParent->aiOvfl[0] ){ + apDiv[i] = pParent->apOvfl[0]; + pgno = get4byte(apDiv[i]); + szNew[i] = pParent->xCellSize(pParent, apDiv[i]); + pParent->nOverflow = 0; + }else{ + apDiv[i] = findCell(pParent, i+nxDiv-pParent->nOverflow); + pgno = get4byte(apDiv[i]); + szNew[i] = pParent->xCellSize(pParent, apDiv[i]); + + /* Drop the cell from the parent page. apDiv[i] still points to + ** the cell within the parent, even though it has been dropped. + ** This is safe because dropping a cell only overwrites the first + ** four bytes of it, and this function does not need the first + ** four bytes of the divider cell. So the pointer is safe to use + ** later on. + ** + ** But not if we are in secure-delete mode. In secure-delete mode, + ** the dropCell() routine will overwrite the entire cell with zeroes. + ** In this case, temporarily copy the cell into the aOvflSpace[] + ** buffer. It will be copied out again as soon as the aSpace[] buffer + ** is allocated. */ + if( pBt->btsFlags & BTS_FAST_SECURE ){ + int iOff; + + /* If the following if() condition is not true, the db is corrupted. + ** The call to dropCell() below will detect this. */ + iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData); + if( (iOff+szNew[i])<=(int)pBt->usableSize ){ + memcpy(&aOvflSpace[iOff], apDiv[i], szNew[i]); + apDiv[i] = &aOvflSpace[apDiv[i]-pParent->aData]; + } + } + dropCell(pParent, i+nxDiv-pParent->nOverflow, szNew[i], &rc); + } + } + + /* Make nMaxCells a multiple of 4 in order to preserve 8-byte + ** alignment */ + nMaxCells = (nMaxCells + 3)&~3; + + /* + ** Allocate space for memory structures + */ + szScratch = + nMaxCells*sizeof(u8*) /* b.apCell */ + + nMaxCells*sizeof(u16) /* b.szCell */ + + pBt->pageSize; /* aSpace1 */ + + assert( szScratch<=7*(int)pBt->pageSize ); + b.apCell = sqlite3StackAllocRaw(0, szScratch ); + if( b.apCell==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto balance_cleanup; + } + b.szCell = (u16*)&b.apCell[nMaxCells]; + aSpace1 = (u8*)&b.szCell[nMaxCells]; + assert( EIGHT_BYTE_ALIGNMENT(aSpace1) ); + + /* + ** Load pointers to all cells on sibling pages and the divider cells + ** into the local b.apCell[] array. Make copies of the divider cells + ** into space obtained from aSpace1[]. The divider cells have already + ** been removed from pParent. + ** + ** If the siblings are on leaf pages, then the child pointers of the + ** divider cells are stripped from the cells before they are copied + ** into aSpace1[]. In this way, all cells in b.apCell[] are without + ** child pointers. If siblings are not leaves, then all cell in + ** b.apCell[] include child pointers. Either way, all cells in b.apCell[] + ** are alike. + ** + ** leafCorrection: 4 if pPage is a leaf. 0 if pPage is not a leaf. + ** leafData: 1 if pPage holds key+data and pParent holds only keys. + */ + b.pRef = apOld[0]; + leafCorrection = b.pRef->leaf*4; + leafData = b.pRef->intKeyLeaf; + for(i=0; i<nOld; i++){ + MemPage *pOld = apOld[i]; + int limit = pOld->nCell; + u8 *aData = pOld->aData; + u16 maskPage = pOld->maskPage; + u8 *piCell = aData + pOld->cellOffset; + u8 *piEnd; + VVA_ONLY( int nCellAtStart = b.nCell; ) + + /* Verify that all sibling pages are of the same "type" (table-leaf, + ** table-interior, index-leaf, or index-interior). + */ + if( pOld->aData[0]!=apOld[0]->aData[0] ){ + rc = SQLITE_CORRUPT_BKPT; + goto balance_cleanup; + } + + /* Load b.apCell[] with pointers to all cells in pOld. If pOld + ** contains overflow cells, include them in the b.apCell[] array + ** in the correct spot. + ** + ** Note that when there are multiple overflow cells, it is always the + ** case that they are sequential and adjacent. This invariant arises + ** because multiple overflows can only occurs when inserting divider + ** cells into a parent on a prior balance, and divider cells are always + ** adjacent and are inserted in order. There is an assert() tagged + ** with "NOTE 1" in the overflow cell insertion loop to prove this + ** invariant. + ** + ** This must be done in advance. Once the balance starts, the cell + ** offset section of the btree page will be overwritten and we will no + ** long be able to find the cells if a pointer to each cell is not saved + ** first. + */ + memset(&b.szCell[b.nCell], 0, sizeof(b.szCell[0])*(limit+pOld->nOverflow)); + if( pOld->nOverflow>0 ){ + if( NEVER(limit<pOld->aiOvfl[0]) ){ + rc = SQLITE_CORRUPT_BKPT; + goto balance_cleanup; + } + limit = pOld->aiOvfl[0]; + for(j=0; j<limit; j++){ + b.apCell[b.nCell] = aData + (maskPage & get2byteAligned(piCell)); + piCell += 2; + b.nCell++; + } + for(k=0; k<pOld->nOverflow; k++){ + assert( k==0 || pOld->aiOvfl[k-1]+1==pOld->aiOvfl[k] );/* NOTE 1 */ + b.apCell[b.nCell] = pOld->apOvfl[k]; + b.nCell++; + } + } + piEnd = aData + pOld->cellOffset + 2*pOld->nCell; + while( piCell<piEnd ){ + assert( b.nCell<nMaxCells ); + b.apCell[b.nCell] = aData + (maskPage & get2byteAligned(piCell)); + piCell += 2; + b.nCell++; + } + assert( (b.nCell-nCellAtStart)==(pOld->nCell+pOld->nOverflow) ); + + cntOld[i] = b.nCell; + if( i<nOld-1 && !leafData){ + u16 sz = (u16)szNew[i]; + u8 *pTemp; + assert( b.nCell<nMaxCells ); + b.szCell[b.nCell] = sz; + pTemp = &aSpace1[iSpace1]; + iSpace1 += sz; + assert( sz<=pBt->maxLocal+23 ); + assert( iSpace1 <= (int)pBt->pageSize ); + memcpy(pTemp, apDiv[i], sz); + b.apCell[b.nCell] = pTemp+leafCorrection; + assert( leafCorrection==0 || leafCorrection==4 ); + b.szCell[b.nCell] = b.szCell[b.nCell] - leafCorrection; + if( !pOld->leaf ){ + assert( leafCorrection==0 ); + assert( pOld->hdrOffset==0 || CORRUPT_DB ); + /* The right pointer of the child page pOld becomes the left + ** pointer of the divider cell */ + memcpy(b.apCell[b.nCell], &pOld->aData[8], 4); + }else{ + assert( leafCorrection==4 ); + while( b.szCell[b.nCell]<4 ){ + /* Do not allow any cells smaller than 4 bytes. If a smaller cell + ** does exist, pad it with 0x00 bytes. */ + assert( b.szCell[b.nCell]==3 || CORRUPT_DB ); + assert( b.apCell[b.nCell]==&aSpace1[iSpace1-3] || CORRUPT_DB ); + aSpace1[iSpace1++] = 0x00; + b.szCell[b.nCell]++; + } + } + b.nCell++; + } + } + + /* + ** Figure out the number of pages needed to hold all b.nCell cells. + ** Store this number in "k". Also compute szNew[] which is the total + ** size of all cells on the i-th page and cntNew[] which is the index + ** in b.apCell[] of the cell that divides page i from page i+1. + ** cntNew[k] should equal b.nCell. + ** + ** Values computed by this block: + ** + ** k: The total number of sibling pages + ** szNew[i]: Spaced used on the i-th sibling page. + ** cntNew[i]: Index in b.apCell[] and b.szCell[] for the first cell to + ** the right of the i-th sibling page. + ** usableSpace: Number of bytes of space available on each sibling. + ** + */ + usableSpace = pBt->usableSize - 12 + leafCorrection; + for(i=k=0; i<nOld; i++, k++){ + MemPage *p = apOld[i]; + b.apEnd[k] = p->aDataEnd; + b.ixNx[k] = cntOld[i]; + if( k && b.ixNx[k]==b.ixNx[k-1] ){ + k--; /* Omit b.ixNx[] entry for child pages with no cells */ + } + if( !leafData ){ + k++; + b.apEnd[k] = pParent->aDataEnd; + b.ixNx[k] = cntOld[i]+1; + } + assert( p->nFree>=0 ); + szNew[i] = usableSpace - p->nFree; + for(j=0; j<p->nOverflow; j++){ + szNew[i] += 2 + p->xCellSize(p, p->apOvfl[j]); + } + cntNew[i] = cntOld[i]; + } + k = nOld; + for(i=0; i<k; i++){ + int sz; + while( szNew[i]>usableSpace ){ + if( i+1>=k ){ + k = i+2; + if( k>NB+2 ){ rc = SQLITE_CORRUPT_BKPT; goto balance_cleanup; } + szNew[k-1] = 0; + cntNew[k-1] = b.nCell; + } + sz = 2 + cachedCellSize(&b, cntNew[i]-1); + szNew[i] -= sz; + if( !leafData ){ + if( cntNew[i]<b.nCell ){ + sz = 2 + cachedCellSize(&b, cntNew[i]); + }else{ + sz = 0; + } + } + szNew[i+1] += sz; + cntNew[i]--; + } + while( cntNew[i]<b.nCell ){ + sz = 2 + cachedCellSize(&b, cntNew[i]); + if( szNew[i]+sz>usableSpace ) break; + szNew[i] += sz; + cntNew[i]++; + if( !leafData ){ + if( cntNew[i]<b.nCell ){ + sz = 2 + cachedCellSize(&b, cntNew[i]); + }else{ + sz = 0; + } + } + szNew[i+1] -= sz; + } + if( cntNew[i]>=b.nCell ){ + k = i+1; + }else if( cntNew[i] <= (i>0 ? cntNew[i-1] : 0) ){ + rc = SQLITE_CORRUPT_BKPT; + goto balance_cleanup; + } + } + + /* + ** The packing computed by the previous block is biased toward the siblings + ** on the left side (siblings with smaller keys). The left siblings are + ** always nearly full, while the right-most sibling might be nearly empty. + ** The next block of code attempts to adjust the packing of siblings to + ** get a better balance. + ** + ** This adjustment is more than an optimization. The packing above might + ** be so out of balance as to be illegal. For example, the right-most + ** sibling might be completely empty. This adjustment is not optional. + */ + for(i=k-1; i>0; i--){ + int szRight = szNew[i]; /* Size of sibling on the right */ + int szLeft = szNew[i-1]; /* Size of sibling on the left */ + int r; /* Index of right-most cell in left sibling */ + int d; /* Index of first cell to the left of right sibling */ + + r = cntNew[i-1] - 1; + d = r + 1 - leafData; + (void)cachedCellSize(&b, d); + do{ + int szR, szD; + assert( d<nMaxCells ); + assert( r<nMaxCells ); + szR = cachedCellSize(&b, r); + szD = b.szCell[d]; + if( szRight!=0 + && (bBulk || szRight+szD+2 > szLeft-(szR+(i==k-1?0:2)))){ + break; + } + szRight += szD + 2; + szLeft -= szR + 2; + cntNew[i-1] = r; + r--; + d--; + }while( r>=0 ); + szNew[i] = szRight; + szNew[i-1] = szLeft; + if( cntNew[i-1] <= (i>1 ? cntNew[i-2] : 0) ){ + rc = SQLITE_CORRUPT_BKPT; + goto balance_cleanup; + } + } + + /* Sanity check: For a non-corrupt database file one of the following + ** must be true: + ** (1) We found one or more cells (cntNew[0])>0), or + ** (2) pPage is a virtual root page. A virtual root page is when + ** the real root page is page 1 and we are the only child of + ** that page. + */ + assert( cntNew[0]>0 || (pParent->pgno==1 && pParent->nCell==0) || CORRUPT_DB); + TRACE(("BALANCE: old: %u(nc=%u) %u(nc=%u) %u(nc=%u)\n", + apOld[0]->pgno, apOld[0]->nCell, + nOld>=2 ? apOld[1]->pgno : 0, nOld>=2 ? apOld[1]->nCell : 0, + nOld>=3 ? apOld[2]->pgno : 0, nOld>=3 ? apOld[2]->nCell : 0 + )); + + /* + ** Allocate k new pages. Reuse old pages where possible. + */ + pageFlags = apOld[0]->aData[0]; + for(i=0; i<k; i++){ + MemPage *pNew; + if( i<nOld ){ + pNew = apNew[i] = apOld[i]; + apOld[i] = 0; + rc = sqlite3PagerWrite(pNew->pDbPage); + nNew++; + if( sqlite3PagerPageRefcount(pNew->pDbPage)!=1+(i==(iParentIdx-nxDiv)) + && rc==SQLITE_OK + ){ + rc = SQLITE_CORRUPT_BKPT; + } + if( rc ) goto balance_cleanup; + }else{ + assert( i>0 ); + rc = allocateBtreePage(pBt, &pNew, &pgno, (bBulk ? 1 : pgno), 0); + if( rc ) goto balance_cleanup; + zeroPage(pNew, pageFlags); + apNew[i] = pNew; + nNew++; + cntOld[i] = b.nCell; + + /* Set the pointer-map entry for the new sibling page. */ + if( ISAUTOVACUUM(pBt) ){ + ptrmapPut(pBt, pNew->pgno, PTRMAP_BTREE, pParent->pgno, &rc); + if( rc!=SQLITE_OK ){ + goto balance_cleanup; + } + } + } + } + + /* + ** Reassign page numbers so that the new pages are in ascending order. + ** This helps to keep entries in the disk file in order so that a scan + ** of the table is closer to a linear scan through the file. That in turn + ** helps the operating system to deliver pages from the disk more rapidly. + ** + ** An O(N*N) sort algorithm is used, but since N is never more than NB+2 + ** (5), that is not a performance concern. + ** + ** When NB==3, this one optimization makes the database about 25% faster + ** for large insertions and deletions. + */ + for(i=0; i<nNew; i++){ + aPgno[i] = apNew[i]->pgno; + assert( apNew[i]->pDbPage->flags & PGHDR_WRITEABLE ); + assert( apNew[i]->pDbPage->flags & PGHDR_DIRTY ); + } + for(i=0; i<nNew-1; i++){ + int iB = i; + for(j=i+1; j<nNew; j++){ + if( apNew[j]->pgno < apNew[iB]->pgno ) iB = j; + } + + /* If apNew[i] has a page number that is bigger than any of the + ** subsequence apNew[i] entries, then swap apNew[i] with the subsequent + ** entry that has the smallest page number (which we know to be + ** entry apNew[iB]). + */ + if( iB!=i ){ + Pgno pgnoA = apNew[i]->pgno; + Pgno pgnoB = apNew[iB]->pgno; + Pgno pgnoTemp = (PENDING_BYTE/pBt->pageSize)+1; + u16 fgA = apNew[i]->pDbPage->flags; + u16 fgB = apNew[iB]->pDbPage->flags; + sqlite3PagerRekey(apNew[i]->pDbPage, pgnoTemp, fgB); + sqlite3PagerRekey(apNew[iB]->pDbPage, pgnoA, fgA); + sqlite3PagerRekey(apNew[i]->pDbPage, pgnoB, fgB); + apNew[i]->pgno = pgnoB; + apNew[iB]->pgno = pgnoA; + } + } + + TRACE(("BALANCE: new: %u(%u nc=%u) %u(%u nc=%u) %u(%u nc=%u) " + "%u(%u nc=%u) %u(%u nc=%u)\n", + apNew[0]->pgno, szNew[0], cntNew[0], + nNew>=2 ? apNew[1]->pgno : 0, nNew>=2 ? szNew[1] : 0, + nNew>=2 ? cntNew[1] - cntNew[0] - !leafData : 0, + nNew>=3 ? apNew[2]->pgno : 0, nNew>=3 ? szNew[2] : 0, + nNew>=3 ? cntNew[2] - cntNew[1] - !leafData : 0, + nNew>=4 ? apNew[3]->pgno : 0, nNew>=4 ? szNew[3] : 0, + nNew>=4 ? cntNew[3] - cntNew[2] - !leafData : 0, + nNew>=5 ? apNew[4]->pgno : 0, nNew>=5 ? szNew[4] : 0, + nNew>=5 ? cntNew[4] - cntNew[3] - !leafData : 0 + )); + + assert( sqlite3PagerIswriteable(pParent->pDbPage) ); + assert( nNew>=1 && nNew<=ArraySize(apNew) ); + assert( apNew[nNew-1]!=0 ); + put4byte(pRight, apNew[nNew-1]->pgno); + + /* If the sibling pages are not leaves, ensure that the right-child pointer + ** of the right-most new sibling page is set to the value that was + ** originally in the same field of the right-most old sibling page. */ + if( (pageFlags & PTF_LEAF)==0 && nOld!=nNew ){ + MemPage *pOld = (nNew>nOld ? apNew : apOld)[nOld-1]; + memcpy(&apNew[nNew-1]->aData[8], &pOld->aData[8], 4); + } + + /* Make any required updates to pointer map entries associated with + ** cells stored on sibling pages following the balance operation. Pointer + ** map entries associated with divider cells are set by the insertCell() + ** routine. The associated pointer map entries are: + ** + ** a) if the cell contains a reference to an overflow chain, the + ** entry associated with the first page in the overflow chain, and + ** + ** b) if the sibling pages are not leaves, the child page associated + ** with the cell. + ** + ** If the sibling pages are not leaves, then the pointer map entry + ** associated with the right-child of each sibling may also need to be + ** updated. This happens below, after the sibling pages have been + ** populated, not here. + */ + if( ISAUTOVACUUM(pBt) ){ + MemPage *pOld; + MemPage *pNew = pOld = apNew[0]; + int cntOldNext = pNew->nCell + pNew->nOverflow; + int iNew = 0; + int iOld = 0; + + for(i=0; i<b.nCell; i++){ + u8 *pCell = b.apCell[i]; + while( i==cntOldNext ){ + iOld++; + assert( iOld<nNew || iOld<nOld ); + assert( iOld>=0 && iOld<NB ); + pOld = iOld<nNew ? apNew[iOld] : apOld[iOld]; + cntOldNext += pOld->nCell + pOld->nOverflow + !leafData; + } + if( i==cntNew[iNew] ){ + pNew = apNew[++iNew]; + if( !leafData ) continue; + } + + /* Cell pCell is destined for new sibling page pNew. Originally, it + ** was either part of sibling page iOld (possibly an overflow cell), + ** or else the divider cell to the left of sibling page iOld. So, + ** if sibling page iOld had the same page number as pNew, and if + ** pCell really was a part of sibling page iOld (not a divider or + ** overflow cell), we can skip updating the pointer map entries. */ + if( iOld>=nNew + || pNew->pgno!=aPgno[iOld] + || !SQLITE_WITHIN(pCell,pOld->aData,pOld->aDataEnd) + ){ + if( !leafCorrection ){ + ptrmapPut(pBt, get4byte(pCell), PTRMAP_BTREE, pNew->pgno, &rc); + } + if( cachedCellSize(&b,i)>pNew->minLocal ){ + ptrmapPutOvflPtr(pNew, pOld, pCell, &rc); + } + if( rc ) goto balance_cleanup; + } + } + } + + /* Insert new divider cells into pParent. */ + for(i=0; i<nNew-1; i++){ + u8 *pCell; + u8 *pTemp; + int sz; + u8 *pSrcEnd; + MemPage *pNew = apNew[i]; + j = cntNew[i]; + + assert( j<nMaxCells ); + assert( b.apCell[j]!=0 ); + pCell = b.apCell[j]; + sz = b.szCell[j] + leafCorrection; + pTemp = &aOvflSpace[iOvflSpace]; + if( !pNew->leaf ){ + memcpy(&pNew->aData[8], pCell, 4); + }else if( leafData ){ + /* If the tree is a leaf-data tree, and the siblings are leaves, + ** then there is no divider cell in b.apCell[]. Instead, the divider + ** cell consists of the integer key for the right-most cell of + ** the sibling-page assembled above only. + */ + CellInfo info; + j--; + pNew->xParseCell(pNew, b.apCell[j], &info); + pCell = pTemp; + sz = 4 + putVarint(&pCell[4], info.nKey); + pTemp = 0; + }else{ + pCell -= 4; + /* Obscure case for non-leaf-data trees: If the cell at pCell was + ** previously stored on a leaf node, and its reported size was 4 + ** bytes, then it may actually be smaller than this + ** (see btreeParseCellPtr(), 4 bytes is the minimum size of + ** any cell). But it is important to pass the correct size to + ** insertCell(), so reparse the cell now. + ** + ** This can only happen for b-trees used to evaluate "IN (SELECT ...)" + ** and WITHOUT ROWID tables with exactly one column which is the + ** primary key. + */ + if( b.szCell[j]==4 ){ + assert(leafCorrection==4); + sz = pParent->xCellSize(pParent, pCell); + } + } + iOvflSpace += sz; + assert( sz<=pBt->maxLocal+23 ); + assert( iOvflSpace <= (int)pBt->pageSize ); + for(k=0; ALWAYS(k<NB*2) && b.ixNx[k]<=j; k++){} + pSrcEnd = b.apEnd[k]; + if( SQLITE_OVERFLOW(pSrcEnd, pCell, pCell+sz) ){ + rc = SQLITE_CORRUPT_BKPT; + goto balance_cleanup; + } + rc = insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno); + if( rc!=SQLITE_OK ) goto balance_cleanup; + assert( sqlite3PagerIswriteable(pParent->pDbPage) ); + } + + /* Now update the actual sibling pages. The order in which they are updated + ** is important, as this code needs to avoid disrupting any page from which + ** cells may still to be read. In practice, this means: + ** + ** (1) If cells are moving left (from apNew[iPg] to apNew[iPg-1]) + ** then it is not safe to update page apNew[iPg] until after + ** the left-hand sibling apNew[iPg-1] has been updated. + ** + ** (2) If cells are moving right (from apNew[iPg] to apNew[iPg+1]) + ** then it is not safe to update page apNew[iPg] until after + ** the right-hand sibling apNew[iPg+1] has been updated. + ** + ** If neither of the above apply, the page is safe to update. + ** + ** The iPg value in the following loop starts at nNew-1 goes down + ** to 0, then back up to nNew-1 again, thus making two passes over + ** the pages. On the initial downward pass, only condition (1) above + ** needs to be tested because (2) will always be true from the previous + ** step. On the upward pass, both conditions are always true, so the + ** upwards pass simply processes pages that were missed on the downward + ** pass. + */ + for(i=1-nNew; i<nNew; i++){ + int iPg = i<0 ? -i : i; + assert( iPg>=0 && iPg<nNew ); + assert( iPg>=1 || i>=0 ); + assert( iPg<ArraySize(cntOld) ); + if( abDone[iPg] ) continue; /* Skip pages already processed */ + if( i>=0 /* On the upwards pass, or... */ + || cntOld[iPg-1]>=cntNew[iPg-1] /* Condition (1) is true */ + ){ + int iNew; + int iOld; + int nNewCell; + + /* Verify condition (1): If cells are moving left, update iPg + ** only after iPg-1 has already been updated. */ + assert( iPg==0 || cntOld[iPg-1]>=cntNew[iPg-1] || abDone[iPg-1] ); + + /* Verify condition (2): If cells are moving right, update iPg + ** only after iPg+1 has already been updated. */ + assert( cntNew[iPg]>=cntOld[iPg] || abDone[iPg+1] ); + + if( iPg==0 ){ + iNew = iOld = 0; + nNewCell = cntNew[0]; + }else{ + iOld = iPg<nOld ? (cntOld[iPg-1] + !leafData) : b.nCell; + iNew = cntNew[iPg-1] + !leafData; + nNewCell = cntNew[iPg] - iNew; + } + + rc = editPage(apNew[iPg], iOld, iNew, nNewCell, &b); + if( rc ) goto balance_cleanup; + abDone[iPg]++; + apNew[iPg]->nFree = usableSpace-szNew[iPg]; + assert( apNew[iPg]->nOverflow==0 ); + assert( apNew[iPg]->nCell==nNewCell ); + } + } + + /* All pages have been processed exactly once */ + assert( memcmp(abDone, "\01\01\01\01\01", nNew)==0 ); + + assert( nOld>0 ); + assert( nNew>0 ); + + if( isRoot && pParent->nCell==0 && pParent->hdrOffset<=apNew[0]->nFree ){ + /* The root page of the b-tree now contains no cells. The only sibling + ** page is the right-child of the parent. Copy the contents of the + ** child page into the parent, decreasing the overall height of the + ** b-tree structure by one. This is described as the "balance-shallower" + ** sub-algorithm in some documentation. + ** + ** If this is an auto-vacuum database, the call to copyNodeContent() + ** sets all pointer-map entries corresponding to database image pages + ** for which the pointer is stored within the content being copied. + ** + ** It is critical that the child page be defragmented before being + ** copied into the parent, because if the parent is page 1 then it will + ** by smaller than the child due to the database header, and so all the + ** free space needs to be up front. + */ + assert( nNew==1 || CORRUPT_DB ); + rc = defragmentPage(apNew[0], -1); + testcase( rc!=SQLITE_OK ); + assert( apNew[0]->nFree == + (get2byteNotZero(&apNew[0]->aData[5]) - apNew[0]->cellOffset + - apNew[0]->nCell*2) + || rc!=SQLITE_OK + ); + copyNodeContent(apNew[0], pParent, &rc); + freePage(apNew[0], &rc); + }else if( ISAUTOVACUUM(pBt) && !leafCorrection ){ + /* Fix the pointer map entries associated with the right-child of each + ** sibling page. All other pointer map entries have already been taken + ** care of. */ + for(i=0; i<nNew; i++){ + u32 key = get4byte(&apNew[i]->aData[8]); + ptrmapPut(pBt, key, PTRMAP_BTREE, apNew[i]->pgno, &rc); + } + } + + assert( pParent->isInit ); + TRACE(("BALANCE: finished: old=%u new=%u cells=%u\n", + nOld, nNew, b.nCell)); + + /* Free any old pages that were not reused as new pages. + */ + for(i=nNew; i<nOld; i++){ + freePage(apOld[i], &rc); + } + +#if 0 + if( ISAUTOVACUUM(pBt) && rc==SQLITE_OK && apNew[0]->isInit ){ + /* The ptrmapCheckPages() contains assert() statements that verify that + ** all pointer map pages are set correctly. This is helpful while + ** debugging. This is usually disabled because a corrupt database may + ** cause an assert() statement to fail. */ + ptrmapCheckPages(apNew, nNew); + ptrmapCheckPages(&pParent, 1); + } +#endif + + /* + ** Cleanup before returning. + */ +balance_cleanup: + sqlite3StackFree(0, b.apCell); + for(i=0; i<nOld; i++){ + releasePage(apOld[i]); + } + for(i=0; i<nNew; i++){ + releasePage(apNew[i]); + } + + return rc; +} + + +/* +** This function is called when the root page of a b-tree structure is +** overfull (has one or more overflow pages). +** +** A new child page is allocated and the contents of the current root +** page, including overflow cells, are copied into the child. The root +** page is then overwritten to make it an empty page with the right-child +** pointer pointing to the new page. +** +** Before returning, all pointer-map entries corresponding to pages +** that the new child-page now contains pointers to are updated. The +** entry corresponding to the new right-child pointer of the root +** page is also updated. +** +** If successful, *ppChild is set to contain a reference to the child +** page and SQLITE_OK is returned. In this case the caller is required +** to call releasePage() on *ppChild exactly once. If an error occurs, +** an error code is returned and *ppChild is set to 0. +*/ +static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ + int rc; /* Return value from subprocedures */ + MemPage *pChild = 0; /* Pointer to a new child page */ + Pgno pgnoChild = 0; /* Page number of the new child page */ + BtShared *pBt = pRoot->pBt; /* The BTree */ + + assert( pRoot->nOverflow>0 ); + assert( sqlite3_mutex_held(pBt->mutex) ); + + /* Make pRoot, the root page of the b-tree, writable. Allocate a new + ** page that will become the new right-child of pPage. Copy the contents + ** of the node stored on pRoot into the new child page. + */ + rc = sqlite3PagerWrite(pRoot->pDbPage); + if( rc==SQLITE_OK ){ + rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0); + copyNodeContent(pRoot, pChild, &rc); + if( ISAUTOVACUUM(pBt) ){ + ptrmapPut(pBt, pgnoChild, PTRMAP_BTREE, pRoot->pgno, &rc); + } + } + if( rc ){ + *ppChild = 0; + releasePage(pChild); + return rc; + } + assert( sqlite3PagerIswriteable(pChild->pDbPage) ); + assert( sqlite3PagerIswriteable(pRoot->pDbPage) ); + assert( pChild->nCell==pRoot->nCell || CORRUPT_DB ); + + TRACE(("BALANCE: copy root %u into %u\n", pRoot->pgno, pChild->pgno)); + + /* Copy the overflow cells from pRoot to pChild */ + memcpy(pChild->aiOvfl, pRoot->aiOvfl, + pRoot->nOverflow*sizeof(pRoot->aiOvfl[0])); + memcpy(pChild->apOvfl, pRoot->apOvfl, + pRoot->nOverflow*sizeof(pRoot->apOvfl[0])); + pChild->nOverflow = pRoot->nOverflow; + + /* Zero the contents of pRoot. Then install pChild as the right-child. */ + zeroPage(pRoot, pChild->aData[0] & ~PTF_LEAF); + put4byte(&pRoot->aData[pRoot->hdrOffset+8], pgnoChild); + + *ppChild = pChild; + return SQLITE_OK; +} + +/* +** Return SQLITE_CORRUPT if any cursor other than pCur is currently valid +** on the same B-tree as pCur. +** +** This can occur if a database is corrupt with two or more SQL tables +** pointing to the same b-tree. If an insert occurs on one SQL table +** and causes a BEFORE TRIGGER to do a secondary insert on the other SQL +** table linked to the same b-tree. If the secondary insert causes a +** rebalance, that can change content out from under the cursor on the +** first SQL table, violating invariants on the first insert. +*/ +static int anotherValidCursor(BtCursor *pCur){ + BtCursor *pOther; + for(pOther=pCur->pBt->pCursor; pOther; pOther=pOther->pNext){ + if( pOther!=pCur + && pOther->eState==CURSOR_VALID + && pOther->pPage==pCur->pPage + ){ + return SQLITE_CORRUPT_BKPT; + } + } + return SQLITE_OK; +} + +/* +** The page that pCur currently points to has just been modified in +** some way. This function figures out if this modification means the +** tree needs to be balanced, and if so calls the appropriate balancing +** routine. Balancing routines are: +** +** balance_quick() +** balance_deeper() +** balance_nonroot() +*/ +static int balance(BtCursor *pCur){ + int rc = SQLITE_OK; + u8 aBalanceQuickSpace[13]; + u8 *pFree = 0; + + VVA_ONLY( int balance_quick_called = 0 ); + VVA_ONLY( int balance_deeper_called = 0 ); + + do { + int iPage; + MemPage *pPage = pCur->pPage; + + if( NEVER(pPage->nFree<0) && btreeComputeFreeSpace(pPage) ) break; + if( pPage->nOverflow==0 && pPage->nFree*3<=(int)pCur->pBt->usableSize*2 ){ + /* No rebalance required as long as: + ** (1) There are no overflow cells + ** (2) The amount of free space on the page is less than 2/3rds of + ** the total usable space on the page. */ + break; + }else if( (iPage = pCur->iPage)==0 ){ + if( pPage->nOverflow && (rc = anotherValidCursor(pCur))==SQLITE_OK ){ + /* The root page of the b-tree is overfull. In this case call the + ** balance_deeper() function to create a new child for the root-page + ** and copy the current contents of the root-page to it. The + ** next iteration of the do-loop will balance the child page. + */ + assert( balance_deeper_called==0 ); + VVA_ONLY( balance_deeper_called++ ); + rc = balance_deeper(pPage, &pCur->apPage[1]); + if( rc==SQLITE_OK ){ + pCur->iPage = 1; + pCur->ix = 0; + pCur->aiIdx[0] = 0; + pCur->apPage[0] = pPage; + pCur->pPage = pCur->apPage[1]; + assert( pCur->pPage->nOverflow ); + } + }else{ + break; + } + }else if( sqlite3PagerPageRefcount(pPage->pDbPage)>1 ){ + /* The page being written is not a root page, and there is currently + ** more than one reference to it. This only happens if the page is one + ** of its own ancestor pages. Corruption. */ + rc = SQLITE_CORRUPT_BKPT; + }else{ + MemPage * const pParent = pCur->apPage[iPage-1]; + int const iIdx = pCur->aiIdx[iPage-1]; + + rc = sqlite3PagerWrite(pParent->pDbPage); + if( rc==SQLITE_OK && pParent->nFree<0 ){ + rc = btreeComputeFreeSpace(pParent); + } + if( rc==SQLITE_OK ){ +#ifndef SQLITE_OMIT_QUICKBALANCE + if( pPage->intKeyLeaf + && pPage->nOverflow==1 + && pPage->aiOvfl[0]==pPage->nCell + && pParent->pgno!=1 + && pParent->nCell==iIdx + ){ + /* Call balance_quick() to create a new sibling of pPage on which + ** to store the overflow cell. balance_quick() inserts a new cell + ** into pParent, which may cause pParent overflow. If this + ** happens, the next iteration of the do-loop will balance pParent + ** use either balance_nonroot() or balance_deeper(). Until this + ** happens, the overflow cell is stored in the aBalanceQuickSpace[] + ** buffer. + ** + ** The purpose of the following assert() is to check that only a + ** single call to balance_quick() is made for each call to this + ** function. If this were not verified, a subtle bug involving reuse + ** of the aBalanceQuickSpace[] might sneak in. + */ + assert( balance_quick_called==0 ); + VVA_ONLY( balance_quick_called++ ); + rc = balance_quick(pParent, pPage, aBalanceQuickSpace); + }else +#endif + { + /* In this case, call balance_nonroot() to redistribute cells + ** between pPage and up to 2 of its sibling pages. This involves + ** modifying the contents of pParent, which may cause pParent to + ** become overfull or underfull. The next iteration of the do-loop + ** will balance the parent page to correct this. + ** + ** If the parent page becomes overfull, the overflow cell or cells + ** are stored in the pSpace buffer allocated immediately below. + ** A subsequent iteration of the do-loop will deal with this by + ** calling balance_nonroot() (balance_deeper() may be called first, + ** but it doesn't deal with overflow cells - just moves them to a + ** different page). Once this subsequent call to balance_nonroot() + ** has completed, it is safe to release the pSpace buffer used by + ** the previous call, as the overflow cell data will have been + ** copied either into the body of a database page or into the new + ** pSpace buffer passed to the latter call to balance_nonroot(). + */ + u8 *pSpace = sqlite3PageMalloc(pCur->pBt->pageSize); + rc = balance_nonroot(pParent, iIdx, pSpace, iPage==1, + pCur->hints&BTREE_BULKLOAD); + if( pFree ){ + /* If pFree is not NULL, it points to the pSpace buffer used + ** by a previous call to balance_nonroot(). Its contents are + ** now stored either on real database pages or within the + ** new pSpace buffer, so it may be safely freed here. */ + sqlite3PageFree(pFree); + } + + /* The pSpace buffer will be freed after the next call to + ** balance_nonroot(), or just before this function returns, whichever + ** comes first. */ + pFree = pSpace; + } + } + + pPage->nOverflow = 0; + + /* The next iteration of the do-loop balances the parent page. */ + releasePage(pPage); + pCur->iPage--; + assert( pCur->iPage>=0 ); + pCur->pPage = pCur->apPage[pCur->iPage]; + } + }while( rc==SQLITE_OK ); + + if( pFree ){ + sqlite3PageFree(pFree); + } + return rc; +} + +/* Overwrite content from pX into pDest. Only do the write if the +** content is different from what is already there. +*/ +static int btreeOverwriteContent( + MemPage *pPage, /* MemPage on which writing will occur */ + u8 *pDest, /* Pointer to the place to start writing */ + const BtreePayload *pX, /* Source of data to write */ + int iOffset, /* Offset of first byte to write */ + int iAmt /* Number of bytes to be written */ +){ + int nData = pX->nData - iOffset; + if( nData<=0 ){ + /* Overwriting with zeros */ + int i; + for(i=0; i<iAmt && pDest[i]==0; i++){} + if( i<iAmt ){ + int rc = sqlite3PagerWrite(pPage->pDbPage); + if( rc ) return rc; + memset(pDest + i, 0, iAmt - i); + } + }else{ + if( nData<iAmt ){ + /* Mixed read data and zeros at the end. Make a recursive call + ** to write the zeros then fall through to write the real data */ + int rc = btreeOverwriteContent(pPage, pDest+nData, pX, iOffset+nData, + iAmt-nData); + if( rc ) return rc; + iAmt = nData; + } + if( memcmp(pDest, ((u8*)pX->pData) + iOffset, iAmt)!=0 ){ + int rc = sqlite3PagerWrite(pPage->pDbPage); + if( rc ) return rc; + /* In a corrupt database, it is possible for the source and destination + ** buffers to overlap. This is harmless since the database is already + ** corrupt but it does cause valgrind and ASAN warnings. So use + ** memmove(). */ + memmove(pDest, ((u8*)pX->pData) + iOffset, iAmt); + } + } + return SQLITE_OK; +} + +/* +** Overwrite the cell that cursor pCur is pointing to with fresh content +** contained in pX. In this variant, pCur is pointing to an overflow +** cell. +*/ +static SQLITE_NOINLINE int btreeOverwriteOverflowCell( + BtCursor *pCur, /* Cursor pointing to cell to overwrite */ + const BtreePayload *pX /* Content to write into the cell */ +){ + int iOffset; /* Next byte of pX->pData to write */ + int nTotal = pX->nData + pX->nZero; /* Total bytes of to write */ + int rc; /* Return code */ + MemPage *pPage = pCur->pPage; /* Page being written */ + BtShared *pBt; /* Btree */ + Pgno ovflPgno; /* Next overflow page to write */ + u32 ovflPageSize; /* Size to write on overflow page */ + + assert( pCur->info.nLocal<nTotal ); /* pCur is an overflow cell */ + + /* Overwrite the local portion first */ + rc = btreeOverwriteContent(pPage, pCur->info.pPayload, pX, + 0, pCur->info.nLocal); + if( rc ) return rc; + + /* Now overwrite the overflow pages */ + iOffset = pCur->info.nLocal; + assert( nTotal>=0 ); + assert( iOffset>=0 ); + ovflPgno = get4byte(pCur->info.pPayload + iOffset); + pBt = pPage->pBt; + ovflPageSize = pBt->usableSize - 4; + do{ + rc = btreeGetPage(pBt, ovflPgno, &pPage, 0); + if( rc ) return rc; + if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 || pPage->isInit ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + if( iOffset+ovflPageSize<(u32)nTotal ){ + ovflPgno = get4byte(pPage->aData); + }else{ + ovflPageSize = nTotal - iOffset; + } + rc = btreeOverwriteContent(pPage, pPage->aData+4, pX, + iOffset, ovflPageSize); + } + sqlite3PagerUnref(pPage->pDbPage); + if( rc ) return rc; + iOffset += ovflPageSize; + }while( iOffset<nTotal ); + return SQLITE_OK; +} + +/* +** Overwrite the cell that cursor pCur is pointing to with fresh content +** contained in pX. +*/ +static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ + int nTotal = pX->nData + pX->nZero; /* Total bytes of to write */ + MemPage *pPage = pCur->pPage; /* Page being written */ + + if( pCur->info.pPayload + pCur->info.nLocal > pPage->aDataEnd + || pCur->info.pPayload < pPage->aData + pPage->cellOffset + ){ + return SQLITE_CORRUPT_BKPT; + } + if( pCur->info.nLocal==nTotal ){ + /* The entire cell is local */ + return btreeOverwriteContent(pPage, pCur->info.pPayload, pX, + 0, pCur->info.nLocal); + }else{ + /* The cell contains overflow content */ + return btreeOverwriteOverflowCell(pCur, pX); + } +} + + +/* +** Insert a new record into the BTree. The content of the new record +** is described by the pX object. The pCur cursor is used only to +** define what table the record should be inserted into, and is left +** pointing at a random location. +** +** For a table btree (used for rowid tables), only the pX.nKey value of +** the key is used. The pX.pKey value must be NULL. The pX.nKey is the +** rowid or INTEGER PRIMARY KEY of the row. The pX.nData,pData,nZero fields +** hold the content of the row. +** +** For an index btree (used for indexes and WITHOUT ROWID tables), the +** key is an arbitrary byte sequence stored in pX.pKey,nKey. The +** pX.pData,nData,nZero fields must be zero. +** +** If the seekResult parameter is non-zero, then a successful call to +** sqlite3BtreeIndexMoveto() to seek cursor pCur to (pKey,nKey) has already +** been performed. In other words, if seekResult!=0 then the cursor +** is currently pointing to a cell that will be adjacent to the cell +** to be inserted. If seekResult<0 then pCur points to a cell that is +** smaller then (pKey,nKey). If seekResult>0 then pCur points to a cell +** that is larger than (pKey,nKey). +** +** If seekResult==0, that means pCur is pointing at some unknown location. +** In that case, this routine must seek the cursor to the correct insertion +** point for (pKey,nKey) before doing the insertion. For index btrees, +** if pX->nMem is non-zero, then pX->aMem contains pointers to the unpacked +** key values and pX->aMem can be used instead of pX->pKey to avoid having +** to decode the key. +*/ +SQLITE_PRIVATE int sqlite3BtreeInsert( + BtCursor *pCur, /* Insert data into the table of this cursor */ + const BtreePayload *pX, /* Content of the row to be inserted */ + int flags, /* True if this is likely an append */ + int seekResult /* Result of prior IndexMoveto() call */ +){ + int rc; + int loc = seekResult; /* -1: before desired location +1: after */ + int szNew = 0; + int idx; + MemPage *pPage; + Btree *p = pCur->pBtree; + unsigned char *oldCell; + unsigned char *newCell = 0; + + assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND|BTREE_PREFORMAT))==flags ); + assert( (flags & BTREE_PREFORMAT)==0 || seekResult || pCur->pKeyInfo==0 ); + + /* Save the positions of any other cursors open on this table. + ** + ** In some cases, the call to btreeMoveto() below is a no-op. For + ** example, when inserting data into a table with auto-generated integer + ** keys, the VDBE layer invokes sqlite3BtreeLast() to figure out the + ** integer key to use. It then calls this function to actually insert the + ** data into the intkey B-Tree. In this case btreeMoveto() recognizes + ** that the cursor is already where it needs to be and returns without + ** doing any work. To avoid thwarting these optimizations, it is important + ** not to clear the cursor here. + */ + if( pCur->curFlags & BTCF_Multiple ){ + rc = saveAllCursors(p->pBt, pCur->pgnoRoot, pCur); + if( rc ) return rc; + if( loc && pCur->iPage<0 ){ + /* This can only happen if the schema is corrupt such that there is more + ** than one table or index with the same root page as used by the cursor. + ** Which can only happen if the SQLITE_NoSchemaError flag was set when + ** the schema was loaded. This cannot be asserted though, as a user might + ** set the flag, load the schema, and then unset the flag. */ + return SQLITE_CORRUPT_BKPT; + } + } + + /* Ensure that the cursor is not in the CURSOR_FAULT state and that it + ** points to a valid cell. + */ + if( pCur->eState>=CURSOR_REQUIRESEEK ){ + testcase( pCur->eState==CURSOR_REQUIRESEEK ); + testcase( pCur->eState==CURSOR_FAULT ); + rc = moveToRoot(pCur); + if( rc && rc!=SQLITE_EMPTY ) return rc; + } + + assert( cursorOwnsBtShared(pCur) ); + assert( (pCur->curFlags & BTCF_WriteFlag)!=0 + && p->pBt->inTransaction==TRANS_WRITE + && (p->pBt->btsFlags & BTS_READ_ONLY)==0 ); + assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); + + /* Assert that the caller has been consistent. If this cursor was opened + ** expecting an index b-tree, then the caller should be inserting blob + ** keys with no associated data. If the cursor was opened expecting an + ** intkey table, the caller should be inserting integer keys with a + ** blob of associated data. */ + assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) ); + + if( pCur->pKeyInfo==0 ){ + assert( pX->pKey==0 ); + /* If this is an insert into a table b-tree, invalidate any incrblob + ** cursors open on the row being replaced */ + if( p->hasIncrblobCur ){ + invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0); + } + + /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing + ** to a row with the same key as the new entry being inserted. + */ +#ifdef SQLITE_DEBUG + if( flags & BTREE_SAVEPOSITION ){ + assert( pCur->curFlags & BTCF_ValidNKey ); + assert( pX->nKey==pCur->info.nKey ); + assert( loc==0 ); + } +#endif + + /* On the other hand, BTREE_SAVEPOSITION==0 does not imply + ** that the cursor is not pointing to a row to be overwritten. + ** So do a complete check. + */ + if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey==pCur->info.nKey ){ + /* The cursor is pointing to the entry that is to be + ** overwritten */ + assert( pX->nData>=0 && pX->nZero>=0 ); + if( pCur->info.nSize!=0 + && pCur->info.nPayload==(u32)pX->nData+pX->nZero + ){ + /* New entry is the same size as the old. Do an overwrite */ + return btreeOverwriteCell(pCur, pX); + } + assert( loc==0 ); + }else if( loc==0 ){ + /* The cursor is *not* pointing to the cell to be overwritten, nor + ** to an adjacent cell. Move the cursor so that it is pointing either + ** to the cell to be overwritten or an adjacent cell. + */ + rc = sqlite3BtreeTableMoveto(pCur, pX->nKey, + (flags & BTREE_APPEND)!=0, &loc); + if( rc ) return rc; + } + }else{ + /* This is an index or a WITHOUT ROWID table */ + + /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing + ** to a row with the same key as the new entry being inserted. + */ + assert( (flags & BTREE_SAVEPOSITION)==0 || loc==0 ); + + /* If the cursor is not already pointing either to the cell to be + ** overwritten, or if a new cell is being inserted, if the cursor is + ** not pointing to an immediately adjacent cell, then move the cursor + ** so that it does. + */ + if( loc==0 && (flags & BTREE_SAVEPOSITION)==0 ){ + if( pX->nMem ){ + UnpackedRecord r; + r.pKeyInfo = pCur->pKeyInfo; + r.aMem = pX->aMem; + r.nField = pX->nMem; + r.default_rc = 0; + r.eqSeen = 0; + rc = sqlite3BtreeIndexMoveto(pCur, &r, &loc); + }else{ + rc = btreeMoveto(pCur, pX->pKey, pX->nKey, + (flags & BTREE_APPEND)!=0, &loc); + } + if( rc ) return rc; + } + + /* If the cursor is currently pointing to an entry to be overwritten + ** and the new content is the same as as the old, then use the + ** overwrite optimization. + */ + if( loc==0 ){ + getCellInfo(pCur); + if( pCur->info.nKey==pX->nKey ){ + BtreePayload x2; + x2.pData = pX->pKey; + x2.nData = pX->nKey; + x2.nZero = 0; + return btreeOverwriteCell(pCur, &x2); + } + } + } + assert( pCur->eState==CURSOR_VALID + || (pCur->eState==CURSOR_INVALID && loc) || CORRUPT_DB ); + + pPage = pCur->pPage; + assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) ); + assert( pPage->leaf || !pPage->intKey ); + if( pPage->nFree<0 ){ + if( NEVER(pCur->eState>CURSOR_INVALID) ){ + /* ^^^^^--- due to the moveToRoot() call above */ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = btreeComputeFreeSpace(pPage); + } + if( rc ) return rc; + } + + TRACE(("INSERT: table=%u nkey=%lld ndata=%u page=%u %s\n", + pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno, + loc==0 ? "overwrite" : "new entry")); + assert( pPage->isInit || CORRUPT_DB ); + newCell = p->pBt->pTmpSpace; + assert( newCell!=0 ); + assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT ); + if( flags & BTREE_PREFORMAT ){ + rc = SQLITE_OK; + szNew = p->pBt->nPreformatSize; + if( szNew<4 ) szNew = 4; + if( ISAUTOVACUUM(p->pBt) && szNew>pPage->maxLocal ){ + CellInfo info; + pPage->xParseCell(pPage, newCell, &info); + if( info.nPayload!=info.nLocal ){ + Pgno ovfl = get4byte(&newCell[szNew-4]); + ptrmapPut(p->pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, &rc); + if( NEVER(rc) ) goto end_insert; + } + } + }else{ + rc = fillInCell(pPage, newCell, pX, &szNew); + if( rc ) goto end_insert; + } + assert( szNew==pPage->xCellSize(pPage, newCell) ); + assert( szNew <= MX_CELL_SIZE(p->pBt) ); + idx = pCur->ix; + pCur->info.nSize = 0; + if( loc==0 ){ + CellInfo info; + assert( idx>=0 ); + if( idx>=pPage->nCell ){ + return SQLITE_CORRUPT_BKPT; + } + rc = sqlite3PagerWrite(pPage->pDbPage); + if( rc ){ + goto end_insert; + } + oldCell = findCell(pPage, idx); + if( !pPage->leaf ){ + memcpy(newCell, oldCell, 4); + } + BTREE_CLEAR_CELL(rc, pPage, oldCell, info); + testcase( pCur->curFlags & BTCF_ValidOvfl ); + invalidateOverflowCache(pCur); + if( info.nSize==szNew && info.nLocal==info.nPayload + && (!ISAUTOVACUUM(p->pBt) || szNew<pPage->minLocal) + ){ + /* Overwrite the old cell with the new if they are the same size. + ** We could also try to do this if the old cell is smaller, then add + ** the leftover space to the free list. But experiments show that + ** doing that is no faster then skipping this optimization and just + ** calling dropCell() and insertCell(). + ** + ** This optimization cannot be used on an autovacuum database if the + ** new entry uses overflow pages, as the insertCell() call below is + ** necessary to add the PTRMAP_OVERFLOW1 pointer-map entry. */ + assert( rc==SQLITE_OK ); /* clearCell never fails when nLocal==nPayload */ + if( oldCell < pPage->aData+pPage->hdrOffset+10 ){ + return SQLITE_CORRUPT_BKPT; + } + if( oldCell+szNew > pPage->aDataEnd ){ + return SQLITE_CORRUPT_BKPT; + } + memcpy(oldCell, newCell, szNew); + return SQLITE_OK; + } + dropCell(pPage, idx, info.nSize, &rc); + if( rc ) goto end_insert; + }else if( loc<0 && pPage->nCell>0 ){ + assert( pPage->leaf ); + idx = ++pCur->ix; + pCur->curFlags &= ~BTCF_ValidNKey; + }else{ + assert( pPage->leaf ); + } + rc = insertCellFast(pPage, idx, newCell, szNew); + assert( pPage->nOverflow==0 || rc==SQLITE_OK ); + assert( rc!=SQLITE_OK || pPage->nCell>0 || pPage->nOverflow>0 ); + + /* If no error has occurred and pPage has an overflow cell, call balance() + ** to redistribute the cells within the tree. Since balance() may move + ** the cursor, zero the BtCursor.info.nSize and BTCF_ValidNKey + ** variables. + ** + ** Previous versions of SQLite called moveToRoot() to move the cursor + ** back to the root page as balance() used to invalidate the contents + ** of BtCursor.apPage[] and BtCursor.aiIdx[]. Instead of doing that, + ** set the cursor state to "invalid". This makes common insert operations + ** slightly faster. + ** + ** There is a subtle but important optimization here too. When inserting + ** multiple records into an intkey b-tree using a single cursor (as can + ** happen while processing an "INSERT INTO ... SELECT" statement), it + ** is advantageous to leave the cursor pointing to the last entry in + ** the b-tree if possible. If the cursor is left pointing to the last + ** entry in the table, and the next row inserted has an integer key + ** larger than the largest existing key, it is possible to insert the + ** row without seeking the cursor. This can be a big performance boost. + */ + if( pPage->nOverflow ){ + assert( rc==SQLITE_OK ); + pCur->curFlags &= ~(BTCF_ValidNKey); + rc = balance(pCur); + + /* Must make sure nOverflow is reset to zero even if the balance() + ** fails. Internal data structure corruption will result otherwise. + ** Also, set the cursor state to invalid. This stops saveCursorPosition() + ** from trying to save the current position of the cursor. */ + pCur->pPage->nOverflow = 0; + pCur->eState = CURSOR_INVALID; + if( (flags & BTREE_SAVEPOSITION) && rc==SQLITE_OK ){ + btreeReleaseAllCursorPages(pCur); + if( pCur->pKeyInfo ){ + assert( pCur->pKey==0 ); + pCur->pKey = sqlite3Malloc( pX->nKey ); + if( pCur->pKey==0 ){ + rc = SQLITE_NOMEM; + }else{ + memcpy(pCur->pKey, pX->pKey, pX->nKey); + } + } + pCur->eState = CURSOR_REQUIRESEEK; + pCur->nKey = pX->nKey; + } + } + assert( pCur->iPage<0 || pCur->pPage->nOverflow==0 ); + +end_insert: + return rc; +} + +/* +** This function is used as part of copying the current row from cursor +** pSrc into cursor pDest. If the cursors are open on intkey tables, then +** parameter iKey is used as the rowid value when the record is copied +** into pDest. Otherwise, the record is copied verbatim. +** +** This function does not actually write the new value to cursor pDest. +** Instead, it creates and populates any required overflow pages and +** writes the data for the new cell into the BtShared.pTmpSpace buffer +** for the destination database. The size of the cell, in bytes, is left +** in BtShared.nPreformatSize. The caller completes the insertion by +** calling sqlite3BtreeInsert() with the BTREE_PREFORMAT flag specified. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ + BtShared *pBt = pDest->pBt; + u8 *aOut = pBt->pTmpSpace; /* Pointer to next output buffer */ + const u8 *aIn; /* Pointer to next input buffer */ + u32 nIn; /* Size of input buffer aIn[] */ + u32 nRem; /* Bytes of data still to copy */ + + getCellInfo(pSrc); + if( pSrc->info.nPayload<0x80 ){ + *(aOut++) = pSrc->info.nPayload; + }else{ + aOut += sqlite3PutVarint(aOut, pSrc->info.nPayload); + } + if( pDest->pKeyInfo==0 ) aOut += putVarint(aOut, iKey); + nIn = pSrc->info.nLocal; + aIn = pSrc->info.pPayload; + if( aIn+nIn>pSrc->pPage->aDataEnd ){ + return SQLITE_CORRUPT_BKPT; + } + nRem = pSrc->info.nPayload; + if( nIn==nRem && nIn<pDest->pPage->maxLocal ){ + memcpy(aOut, aIn, nIn); + pBt->nPreformatSize = nIn + (aOut - pBt->pTmpSpace); + return SQLITE_OK; + }else{ + int rc = SQLITE_OK; + Pager *pSrcPager = pSrc->pBt->pPager; + u8 *pPgnoOut = 0; + Pgno ovflIn = 0; + DbPage *pPageIn = 0; + MemPage *pPageOut = 0; + u32 nOut; /* Size of output buffer aOut[] */ + + nOut = btreePayloadToLocal(pDest->pPage, pSrc->info.nPayload); + pBt->nPreformatSize = nOut + (aOut - pBt->pTmpSpace); + if( nOut<pSrc->info.nPayload ){ + pPgnoOut = &aOut[nOut]; + pBt->nPreformatSize += 4; + } + + if( nRem>nIn ){ + if( aIn+nIn+4>pSrc->pPage->aDataEnd ){ + return SQLITE_CORRUPT_BKPT; + } + ovflIn = get4byte(&pSrc->info.pPayload[nIn]); + } + + do { + nRem -= nOut; + do{ + assert( nOut>0 ); + if( nIn>0 ){ + int nCopy = MIN(nOut, nIn); + memcpy(aOut, aIn, nCopy); + nOut -= nCopy; + nIn -= nCopy; + aOut += nCopy; + aIn += nCopy; + } + if( nOut>0 ){ + sqlite3PagerUnref(pPageIn); + pPageIn = 0; + rc = sqlite3PagerGet(pSrcPager, ovflIn, &pPageIn, PAGER_GET_READONLY); + if( rc==SQLITE_OK ){ + aIn = (const u8*)sqlite3PagerGetData(pPageIn); + ovflIn = get4byte(aIn); + aIn += 4; + nIn = pSrc->pBt->usableSize - 4; + } + } + }while( rc==SQLITE_OK && nOut>0 ); + + if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){ + Pgno pgnoNew; + MemPage *pNew = 0; + rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); + put4byte(pPgnoOut, pgnoNew); + if( ISAUTOVACUUM(pBt) && pPageOut ){ + ptrmapPut(pBt, pgnoNew, PTRMAP_OVERFLOW2, pPageOut->pgno, &rc); + } + releasePage(pPageOut); + pPageOut = pNew; + if( pPageOut ){ + pPgnoOut = pPageOut->aData; + put4byte(pPgnoOut, 0); + aOut = &pPgnoOut[4]; + nOut = MIN(pBt->usableSize - 4, nRem); + } + } + }while( nRem>0 && rc==SQLITE_OK ); + + releasePage(pPageOut); + sqlite3PagerUnref(pPageIn); + return rc; + } +} + +/* +** Delete the entry that the cursor is pointing to. +** +** If the BTREE_SAVEPOSITION bit of the flags parameter is zero, then +** the cursor is left pointing at an arbitrary location after the delete. +** But if that bit is set, then the cursor is left in a state such that +** the next call to BtreeNext() or BtreePrev() moves it to the same row +** as it would have been on if the call to BtreeDelete() had been omitted. +** +** The BTREE_AUXDELETE bit of flags indicates that is one of several deletes +** associated with a single table entry and its indexes. Only one of those +** deletes is considered the "primary" delete. The primary delete occurs +** on a cursor that is not a BTREE_FORDELETE cursor. All but one delete +** operation on non-FORDELETE cursors is tagged with the AUXDELETE flag. +** The BTREE_AUXDELETE bit is a hint that is not used by this implementation, +** but which might be used by alternative storage engines. +*/ +SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ + Btree *p = pCur->pBtree; + BtShared *pBt = p->pBt; + int rc; /* Return code */ + MemPage *pPage; /* Page to delete cell from */ + unsigned char *pCell; /* Pointer to cell to delete */ + int iCellIdx; /* Index of cell to delete */ + int iCellDepth; /* Depth of node containing pCell */ + CellInfo info; /* Size of the cell being deleted */ + u8 bPreserve; /* Keep cursor valid. 2 for CURSOR_SKIPNEXT */ + + assert( cursorOwnsBtShared(pCur) ); + assert( pBt->inTransaction==TRANS_WRITE ); + assert( (pBt->btsFlags & BTS_READ_ONLY)==0 ); + assert( pCur->curFlags & BTCF_WriteFlag ); + assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); + assert( !hasReadConflicts(p, pCur->pgnoRoot) ); + assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 ); + if( pCur->eState!=CURSOR_VALID ){ + if( pCur->eState>=CURSOR_REQUIRESEEK ){ + rc = btreeRestoreCursorPosition(pCur); + assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID ); + if( rc || pCur->eState!=CURSOR_VALID ) return rc; + }else{ + return SQLITE_CORRUPT_BKPT; + } + } + assert( pCur->eState==CURSOR_VALID ); + + iCellDepth = pCur->iPage; + iCellIdx = pCur->ix; + pPage = pCur->pPage; + if( pPage->nCell<=iCellIdx ){ + return SQLITE_CORRUPT_BKPT; + } + pCell = findCell(pPage, iCellIdx); + if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ){ + return SQLITE_CORRUPT_BKPT; + } + if( pCell<&pPage->aCellIdx[pPage->nCell] ){ + return SQLITE_CORRUPT_BKPT; + } + + /* If the BTREE_SAVEPOSITION bit is on, then the cursor position must + ** be preserved following this delete operation. If the current delete + ** will cause a b-tree rebalance, then this is done by saving the cursor + ** key and leaving the cursor in CURSOR_REQUIRESEEK state before + ** returning. + ** + ** If the current delete will not cause a rebalance, then the cursor + ** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately + ** before or after the deleted entry. + ** + ** The bPreserve value records which path is required: + ** + ** bPreserve==0 Not necessary to save the cursor position + ** bPreserve==1 Use CURSOR_REQUIRESEEK to save the cursor position + ** bPreserve==2 Cursor won't move. Set CURSOR_SKIPNEXT. + */ + bPreserve = (flags & BTREE_SAVEPOSITION)!=0; + if( bPreserve ){ + if( !pPage->leaf + || (pPage->nFree+pPage->xCellSize(pPage,pCell)+2) > + (int)(pBt->usableSize*2/3) + || pPage->nCell==1 /* See dbfuzz001.test for a test case */ + ){ + /* A b-tree rebalance will be required after deleting this entry. + ** Save the cursor key. */ + rc = saveCursorKey(pCur); + if( rc ) return rc; + }else{ + bPreserve = 2; + } + } + + /* If the page containing the entry to delete is not a leaf page, move + ** the cursor to the largest entry in the tree that is smaller than + ** the entry being deleted. This cell will replace the cell being deleted + ** from the internal node. The 'previous' entry is used for this instead + ** of the 'next' entry, as the previous entry is always a part of the + ** sub-tree headed by the child page of the cell being deleted. This makes + ** balancing the tree following the delete operation easier. */ + if( !pPage->leaf ){ + rc = sqlite3BtreePrevious(pCur, 0); + assert( rc!=SQLITE_DONE ); + if( rc ) return rc; + } + + /* Save the positions of any other cursors open on this table before + ** making any modifications. */ + if( pCur->curFlags & BTCF_Multiple ){ + rc = saveAllCursors(pBt, pCur->pgnoRoot, pCur); + if( rc ) return rc; + } + + /* If this is a delete operation to remove a row from a table b-tree, + ** invalidate any incrblob cursors open on the row being deleted. */ + if( pCur->pKeyInfo==0 && p->hasIncrblobCur ){ + invalidateIncrblobCursors(p, pCur->pgnoRoot, pCur->info.nKey, 0); + } + + /* Make the page containing the entry to be deleted writable. Then free any + ** overflow pages associated with the entry and finally remove the cell + ** itself from within the page. */ + rc = sqlite3PagerWrite(pPage->pDbPage); + if( rc ) return rc; + BTREE_CLEAR_CELL(rc, pPage, pCell, info); + dropCell(pPage, iCellIdx, info.nSize, &rc); + if( rc ) return rc; + + /* If the cell deleted was not located on a leaf page, then the cursor + ** is currently pointing to the largest entry in the sub-tree headed + ** by the child-page of the cell that was just deleted from an internal + ** node. The cell from the leaf node needs to be moved to the internal + ** node to replace the deleted cell. */ + if( !pPage->leaf ){ + MemPage *pLeaf = pCur->pPage; + int nCell; + Pgno n; + unsigned char *pTmp; + + if( pLeaf->nFree<0 ){ + rc = btreeComputeFreeSpace(pLeaf); + if( rc ) return rc; + } + if( iCellDepth<pCur->iPage-1 ){ + n = pCur->apPage[iCellDepth+1]->pgno; + }else{ + n = pCur->pPage->pgno; + } + pCell = findCell(pLeaf, pLeaf->nCell-1); + if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_BKPT; + nCell = pLeaf->xCellSize(pLeaf, pCell); + assert( MX_CELL_SIZE(pBt) >= nCell ); + pTmp = pBt->pTmpSpace; + assert( pTmp!=0 ); + rc = sqlite3PagerWrite(pLeaf->pDbPage); + if( rc==SQLITE_OK ){ + rc = insertCell(pPage, iCellIdx, pCell-4, nCell+4, pTmp, n); + } + dropCell(pLeaf, pLeaf->nCell-1, nCell, &rc); + if( rc ) return rc; + } + + /* Balance the tree. If the entry deleted was located on a leaf page, + ** then the cursor still points to that page. In this case the first + ** call to balance() repairs the tree, and the if(...) condition is + ** never true. + ** + ** Otherwise, if the entry deleted was on an internal node page, then + ** pCur is pointing to the leaf page from which a cell was removed to + ** replace the cell deleted from the internal node. This is slightly + ** tricky as the leaf node may be underfull, and the internal node may + ** be either under or overfull. In this case run the balancing algorithm + ** on the leaf node first. If the balance proceeds far enough up the + ** tree that we can be sure that any problem in the internal node has + ** been corrected, so be it. Otherwise, after balancing the leaf node, + ** walk the cursor up the tree to the internal node and balance it as + ** well. */ + assert( pCur->pPage->nOverflow==0 ); + assert( pCur->pPage->nFree>=0 ); + if( pCur->pPage->nFree*3<=(int)pCur->pBt->usableSize*2 ){ + /* Optimization: If the free space is less than 2/3rds of the page, + ** then balance() will always be a no-op. No need to invoke it. */ + rc = SQLITE_OK; + }else{ + rc = balance(pCur); + } + if( rc==SQLITE_OK && pCur->iPage>iCellDepth ){ + releasePageNotNull(pCur->pPage); + pCur->iPage--; + while( pCur->iPage>iCellDepth ){ + releasePage(pCur->apPage[pCur->iPage--]); + } + pCur->pPage = pCur->apPage[pCur->iPage]; + rc = balance(pCur); + } + + if( rc==SQLITE_OK ){ + if( bPreserve>1 ){ + assert( (pCur->iPage==iCellDepth || CORRUPT_DB) ); + assert( pPage==pCur->pPage || CORRUPT_DB ); + assert( (pPage->nCell>0 || CORRUPT_DB) && iCellIdx<=pPage->nCell ); + pCur->eState = CURSOR_SKIPNEXT; + if( iCellIdx>=pPage->nCell ){ + pCur->skipNext = -1; + pCur->ix = pPage->nCell-1; + }else{ + pCur->skipNext = 1; + } + }else{ + rc = moveToRoot(pCur); + if( bPreserve ){ + btreeReleaseAllCursorPages(pCur); + pCur->eState = CURSOR_REQUIRESEEK; + } + if( rc==SQLITE_EMPTY ) rc = SQLITE_OK; + } + } + return rc; +} + +/* +** Create a new BTree table. Write into *piTable the page +** number for the root page of the new table. +** +** The type of type is determined by the flags parameter. Only the +** following values of flags are currently in use. Other values for +** flags might not work: +** +** BTREE_INTKEY|BTREE_LEAFDATA Used for SQL tables with rowid keys +** BTREE_ZERODATA Used for SQL indices +*/ +static int btreeCreateTable(Btree *p, Pgno *piTable, int createTabFlags){ + BtShared *pBt = p->pBt; + MemPage *pRoot; + Pgno pgnoRoot; + int rc; + int ptfFlags; /* Page-type flags for the root page of new table */ + + assert( sqlite3BtreeHoldsMutex(p) ); + assert( pBt->inTransaction==TRANS_WRITE ); + assert( (pBt->btsFlags & BTS_READ_ONLY)==0 ); + +#ifdef SQLITE_OMIT_AUTOVACUUM + rc = allocateBtreePage(pBt, &pRoot, &pgnoRoot, 1, 0); + if( rc ){ + return rc; + } +#else + if( pBt->autoVacuum ){ + Pgno pgnoMove; /* Move a page here to make room for the root-page */ + MemPage *pPageMove; /* The page to move to. */ + + /* Creating a new table may probably require moving an existing database + ** to make room for the new tables root page. In case this page turns + ** out to be an overflow page, delete all overflow page-map caches + ** held by open cursors. + */ + invalidateAllOverflowCache(pBt); + + /* Read the value of meta[3] from the database to determine where the + ** root page of the new table should go. meta[3] is the largest root-page + ** created so far, so the new root-page is (meta[3]+1). + */ + sqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &pgnoRoot); + if( pgnoRoot>btreePagecount(pBt) ){ + return SQLITE_CORRUPT_BKPT; + } + pgnoRoot++; + + /* The new root-page may not be allocated on a pointer-map page, or the + ** PENDING_BYTE page. + */ + while( pgnoRoot==PTRMAP_PAGENO(pBt, pgnoRoot) || + pgnoRoot==PENDING_BYTE_PAGE(pBt) ){ + pgnoRoot++; + } + assert( pgnoRoot>=3 ); + + /* Allocate a page. The page that currently resides at pgnoRoot will + ** be moved to the allocated page (unless the allocated page happens + ** to reside at pgnoRoot). + */ + rc = allocateBtreePage(pBt, &pPageMove, &pgnoMove, pgnoRoot, BTALLOC_EXACT); + if( rc!=SQLITE_OK ){ + return rc; + } + + if( pgnoMove!=pgnoRoot ){ + /* pgnoRoot is the page that will be used for the root-page of + ** the new table (assuming an error did not occur). But we were + ** allocated pgnoMove. If required (i.e. if it was not allocated + ** by extending the file), the current page at position pgnoMove + ** is already journaled. + */ + u8 eType = 0; + Pgno iPtrPage = 0; + + /* Save the positions of any open cursors. This is required in + ** case they are holding a reference to an xFetch reference + ** corresponding to page pgnoRoot. */ + rc = saveAllCursors(pBt, 0, 0); + releasePage(pPageMove); + if( rc!=SQLITE_OK ){ + return rc; + } + + /* Move the page currently at pgnoRoot to pgnoMove. */ + rc = btreeGetPage(pBt, pgnoRoot, &pRoot, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + rc = ptrmapGet(pBt, pgnoRoot, &eType, &iPtrPage); + if( eType==PTRMAP_ROOTPAGE || eType==PTRMAP_FREEPAGE ){ + rc = SQLITE_CORRUPT_BKPT; + } + if( rc!=SQLITE_OK ){ + releasePage(pRoot); + return rc; + } + assert( eType!=PTRMAP_ROOTPAGE ); + assert( eType!=PTRMAP_FREEPAGE ); + rc = relocatePage(pBt, pRoot, eType, iPtrPage, pgnoMove, 0); + releasePage(pRoot); + + /* Obtain the page at pgnoRoot */ + if( rc!=SQLITE_OK ){ + return rc; + } + rc = btreeGetPage(pBt, pgnoRoot, &pRoot, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + rc = sqlite3PagerWrite(pRoot->pDbPage); + if( rc!=SQLITE_OK ){ + releasePage(pRoot); + return rc; + } + }else{ + pRoot = pPageMove; + } + + /* Update the pointer-map and meta-data with the new root-page number. */ + ptrmapPut(pBt, pgnoRoot, PTRMAP_ROOTPAGE, 0, &rc); + if( rc ){ + releasePage(pRoot); + return rc; + } + + /* When the new root page was allocated, page 1 was made writable in + ** order either to increase the database filesize, or to decrement the + ** freelist count. Hence, the sqlite3BtreeUpdateMeta() call cannot fail. + */ + assert( sqlite3PagerIswriteable(pBt->pPage1->pDbPage) ); + rc = sqlite3BtreeUpdateMeta(p, 4, pgnoRoot); + if( NEVER(rc) ){ + releasePage(pRoot); + return rc; + } + + }else{ + rc = allocateBtreePage(pBt, &pRoot, &pgnoRoot, 1, 0); + if( rc ) return rc; + } +#endif + assert( sqlite3PagerIswriteable(pRoot->pDbPage) ); + if( createTabFlags & BTREE_INTKEY ){ + ptfFlags = PTF_INTKEY | PTF_LEAFDATA | PTF_LEAF; + }else{ + ptfFlags = PTF_ZERODATA | PTF_LEAF; + } + zeroPage(pRoot, ptfFlags); + sqlite3PagerUnref(pRoot->pDbPage); + assert( (pBt->openFlags & BTREE_SINGLE)==0 || pgnoRoot==2 ); + *piTable = pgnoRoot; + return SQLITE_OK; +} +SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree *p, Pgno *piTable, int flags){ + int rc; + sqlite3BtreeEnter(p); + rc = btreeCreateTable(p, piTable, flags); + sqlite3BtreeLeave(p); + return rc; +} + +/* +** Erase the given database page and all its children. Return +** the page to the freelist. +*/ +static int clearDatabasePage( + BtShared *pBt, /* The BTree that contains the table */ + Pgno pgno, /* Page number to clear */ + int freePageFlag, /* Deallocate page if true */ + i64 *pnChange /* Add number of Cells freed to this counter */ +){ + MemPage *pPage; + int rc; + unsigned char *pCell; + int i; + int hdr; + CellInfo info; + + assert( sqlite3_mutex_held(pBt->mutex) ); + if( pgno>btreePagecount(pBt) ){ + return SQLITE_CORRUPT_BKPT; + } + rc = getAndInitPage(pBt, pgno, &pPage, 0); + if( rc ) return rc; + if( (pBt->openFlags & BTREE_SINGLE)==0 + && sqlite3PagerPageRefcount(pPage->pDbPage) != (1 + (pgno==1)) + ){ + rc = SQLITE_CORRUPT_BKPT; + goto cleardatabasepage_out; + } + hdr = pPage->hdrOffset; + for(i=0; i<pPage->nCell; i++){ + pCell = findCell(pPage, i); + if( !pPage->leaf ){ + rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange); + if( rc ) goto cleardatabasepage_out; + } + BTREE_CLEAR_CELL(rc, pPage, pCell, info); + if( rc ) goto cleardatabasepage_out; + } + if( !pPage->leaf ){ + rc = clearDatabasePage(pBt, get4byte(&pPage->aData[hdr+8]), 1, pnChange); + if( rc ) goto cleardatabasepage_out; + if( pPage->intKey ) pnChange = 0; + } + if( pnChange ){ + testcase( !pPage->intKey ); + *pnChange += pPage->nCell; + } + if( freePageFlag ){ + freePage(pPage, &rc); + }else if( (rc = sqlite3PagerWrite(pPage->pDbPage))==0 ){ + zeroPage(pPage, pPage->aData[hdr] | PTF_LEAF); + } + +cleardatabasepage_out: + releasePage(pPage); + return rc; +} + +/* +** Delete all information from a single table in the database. iTable is +** the page number of the root of the table. After this routine returns, +** the root page is empty, but still exists. +** +** This routine will fail with SQLITE_LOCKED if there are any open +** read cursors on the table. Open write cursors are moved to the +** root of the table. +** +** If pnChange is not NULL, then the integer value pointed to by pnChange +** is incremented by the number of entries in the table. +*/ +SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, i64 *pnChange){ + int rc; + BtShared *pBt = p->pBt; + sqlite3BtreeEnter(p); + assert( p->inTrans==TRANS_WRITE ); + + rc = saveAllCursors(pBt, (Pgno)iTable, 0); + + if( SQLITE_OK==rc ){ + /* Invalidate all incrblob cursors open on table iTable (assuming iTable + ** is the root of a table b-tree - if it is not, the following call is + ** a no-op). */ + if( p->hasIncrblobCur ){ + invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1); + } + rc = clearDatabasePage(pBt, (Pgno)iTable, 0, pnChange); + } + sqlite3BtreeLeave(p); + return rc; +} + +/* +** Delete all information from the single table that pCur is open on. +** +** This routine only work for pCur on an ephemeral table. +*/ +SQLITE_PRIVATE int sqlite3BtreeClearTableOfCursor(BtCursor *pCur){ + return sqlite3BtreeClearTable(pCur->pBtree, pCur->pgnoRoot, 0); +} + +/* +** Erase all information in a table and add the root of the table to +** the freelist. Except, the root of the principle table (the one on +** page 1) is never added to the freelist. +** +** This routine will fail with SQLITE_LOCKED if there are any open +** cursors on the table. +** +** If AUTOVACUUM is enabled and the page at iTable is not the last +** root page in the database file, then the last root page +** in the database file is moved into the slot formerly occupied by +** iTable and that last slot formerly occupied by the last root page +** is added to the freelist instead of iTable. In this say, all +** root pages are kept at the beginning of the database file, which +** is necessary for AUTOVACUUM to work right. *piMoved is set to the +** page number that used to be the last root page in the file before +** the move. If no page gets moved, *piMoved is set to 0. +** The last root page is recorded in meta[3] and the value of +** meta[3] is updated by this procedure. +*/ +static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ + int rc; + MemPage *pPage = 0; + BtShared *pBt = p->pBt; + + assert( sqlite3BtreeHoldsMutex(p) ); + assert( p->inTrans==TRANS_WRITE ); + assert( iTable>=2 ); + if( iTable>btreePagecount(pBt) ){ + return SQLITE_CORRUPT_BKPT; + } + + rc = sqlite3BtreeClearTable(p, iTable, 0); + if( rc ) return rc; + rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); + if( NEVER(rc) ){ + releasePage(pPage); + return rc; + } + + *piMoved = 0; + +#ifdef SQLITE_OMIT_AUTOVACUUM + freePage(pPage, &rc); + releasePage(pPage); +#else + if( pBt->autoVacuum ){ + Pgno maxRootPgno; + sqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &maxRootPgno); + + if( iTable==maxRootPgno ){ + /* If the table being dropped is the table with the largest root-page + ** number in the database, put the root page on the free list. + */ + freePage(pPage, &rc); + releasePage(pPage); + if( rc!=SQLITE_OK ){ + return rc; + } + }else{ + /* The table being dropped does not have the largest root-page + ** number in the database. So move the page that does into the + ** gap left by the deleted root-page. + */ + MemPage *pMove; + releasePage(pPage); + rc = btreeGetPage(pBt, maxRootPgno, &pMove, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + rc = relocatePage(pBt, pMove, PTRMAP_ROOTPAGE, 0, iTable, 0); + releasePage(pMove); + if( rc!=SQLITE_OK ){ + return rc; + } + pMove = 0; + rc = btreeGetPage(pBt, maxRootPgno, &pMove, 0); + freePage(pMove, &rc); + releasePage(pMove); + if( rc!=SQLITE_OK ){ + return rc; + } + *piMoved = maxRootPgno; + } + + /* Set the new 'max-root-page' value in the database header. This + ** is the old value less one, less one more if that happens to + ** be a root-page number, less one again if that is the + ** PENDING_BYTE_PAGE. + */ + maxRootPgno--; + while( maxRootPgno==PENDING_BYTE_PAGE(pBt) + || PTRMAP_ISPAGE(pBt, maxRootPgno) ){ + maxRootPgno--; + } + assert( maxRootPgno!=PENDING_BYTE_PAGE(pBt) ); + + rc = sqlite3BtreeUpdateMeta(p, 4, maxRootPgno); + }else{ + freePage(pPage, &rc); + releasePage(pPage); + } +#endif + return rc; +} +SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){ + int rc; + sqlite3BtreeEnter(p); + rc = btreeDropTable(p, iTable, piMoved); + sqlite3BtreeLeave(p); + return rc; +} + + +/* +** This function may only be called if the b-tree connection already +** has a read or write transaction open on the database. +** +** Read the meta-information out of a database file. Meta[0] +** is the number of free pages currently in the database. Meta[1] +** through meta[15] are available for use by higher layers. Meta[0] +** is read-only, the others are read/write. +** +** The schema layer numbers meta values differently. At the schema +** layer (and the SetCookie and ReadCookie opcodes) the number of +** free pages is not visible. So Cookie[0] is the same as Meta[1]. +** +** This routine treats Meta[BTREE_DATA_VERSION] as a special case. Instead +** of reading the value out of the header, it instead loads the "DataVersion" +** from the pager. The BTREE_DATA_VERSION value is not actually stored in the +** database file. It is a number computed by the pager. But its access +** pattern is the same as header meta values, and so it is convenient to +** read it from this routine. +*/ +SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){ + BtShared *pBt = p->pBt; + + sqlite3BtreeEnter(p); + assert( p->inTrans>TRANS_NONE ); + assert( SQLITE_OK==querySharedCacheTableLock(p, SCHEMA_ROOT, READ_LOCK) ); + assert( pBt->pPage1 ); + assert( idx>=0 && idx<=15 ); + + if( idx==BTREE_DATA_VERSION ){ + *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iBDataVersion; + }else{ + *pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]); + } + + /* If auto-vacuum is disabled in this build and this is an auto-vacuum + ** database, mark the database as read-only. */ +#ifdef SQLITE_OMIT_AUTOVACUUM + if( idx==BTREE_LARGEST_ROOT_PAGE && *pMeta>0 ){ + pBt->btsFlags |= BTS_READ_ONLY; + } +#endif + + sqlite3BtreeLeave(p); +} + +/* +** Write meta-information back into the database. Meta[0] is +** read-only and may not be written. +*/ +SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){ + BtShared *pBt = p->pBt; + unsigned char *pP1; + int rc; + assert( idx>=1 && idx<=15 ); + sqlite3BtreeEnter(p); + assert( p->inTrans==TRANS_WRITE ); + assert( pBt->pPage1!=0 ); + pP1 = pBt->pPage1->aData; + rc = sqlite3PagerWrite(pBt->pPage1->pDbPage); + if( rc==SQLITE_OK ){ + put4byte(&pP1[36 + idx*4], iMeta); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( idx==BTREE_INCR_VACUUM ){ + assert( pBt->autoVacuum || iMeta==0 ); + assert( iMeta==0 || iMeta==1 ); + pBt->incrVacuum = (u8)iMeta; + } +#endif + } + sqlite3BtreeLeave(p); + return rc; +} + +/* +** The first argument, pCur, is a cursor opened on some b-tree. Count the +** number of entries in the b-tree and write the result to *pnEntry. +** +** SQLITE_OK is returned if the operation is successfully executed. +** Otherwise, if an error is encountered (i.e. an IO error or database +** corruption) an SQLite error code is returned. +*/ +SQLITE_PRIVATE int sqlite3BtreeCount(sqlite3 *db, BtCursor *pCur, i64 *pnEntry){ + i64 nEntry = 0; /* Value to return in *pnEntry */ + int rc; /* Return code */ + + rc = moveToRoot(pCur); + if( rc==SQLITE_EMPTY ){ + *pnEntry = 0; + return SQLITE_OK; + } + + /* Unless an error occurs, the following loop runs one iteration for each + ** page in the B-Tree structure (not including overflow pages). + */ + while( rc==SQLITE_OK && !AtomicLoad(&db->u1.isInterrupted) ){ + int iIdx; /* Index of child node in parent */ + MemPage *pPage; /* Current page of the b-tree */ + + /* If this is a leaf page or the tree is not an int-key tree, then + ** this page contains countable entries. Increment the entry counter + ** accordingly. + */ + pPage = pCur->pPage; + if( pPage->leaf || !pPage->intKey ){ + nEntry += pPage->nCell; + } + + /* pPage is a leaf node. This loop navigates the cursor so that it + ** points to the first interior cell that it points to the parent of + ** the next page in the tree that has not yet been visited. The + ** pCur->aiIdx[pCur->iPage] value is set to the index of the parent cell + ** of the page, or to the number of cells in the page if the next page + ** to visit is the right-child of its parent. + ** + ** If all pages in the tree have been visited, return SQLITE_OK to the + ** caller. + */ + if( pPage->leaf ){ + do { + if( pCur->iPage==0 ){ + /* All pages of the b-tree have been visited. Return successfully. */ + *pnEntry = nEntry; + return moveToRoot(pCur); + } + moveToParent(pCur); + }while ( pCur->ix>=pCur->pPage->nCell ); + + pCur->ix++; + pPage = pCur->pPage; + } + + /* Descend to the child node of the cell that the cursor currently + ** points at. This is the right-child if (iIdx==pPage->nCell). + */ + iIdx = pCur->ix; + if( iIdx==pPage->nCell ){ + rc = moveToChild(pCur, get4byte(&pPage->aData[pPage->hdrOffset+8])); + }else{ + rc = moveToChild(pCur, get4byte(findCell(pPage, iIdx))); + } + } + + /* An error has occurred. Return an error code. */ + return rc; +} + +/* +** Return the pager associated with a BTree. This routine is used for +** testing and debugging only. +*/ +SQLITE_PRIVATE Pager *sqlite3BtreePager(Btree *p){ + return p->pBt->pPager; +} + +#ifndef SQLITE_OMIT_INTEGRITY_CHECK +/* +** Record an OOM error during integrity_check +*/ +static void checkOom(IntegrityCk *pCheck){ + pCheck->rc = SQLITE_NOMEM; + pCheck->mxErr = 0; /* Causes integrity_check processing to stop */ + if( pCheck->nErr==0 ) pCheck->nErr++; +} + +/* +** Invoke the progress handler, if appropriate. Also check for an +** interrupt. +*/ +static void checkProgress(IntegrityCk *pCheck){ + sqlite3 *db = pCheck->db; + if( AtomicLoad(&db->u1.isInterrupted) ){ + pCheck->rc = SQLITE_INTERRUPT; + pCheck->nErr++; + pCheck->mxErr = 0; + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( db->xProgress ){ + assert( db->nProgressOps>0 ); + pCheck->nStep++; + if( (pCheck->nStep % db->nProgressOps)==0 + && db->xProgress(db->pProgressArg) + ){ + pCheck->rc = SQLITE_INTERRUPT; + pCheck->nErr++; + pCheck->mxErr = 0; + } + } +#endif +} + +/* +** Append a message to the error message string. +*/ +static void checkAppendMsg( + IntegrityCk *pCheck, + const char *zFormat, + ... +){ + va_list ap; + checkProgress(pCheck); + if( !pCheck->mxErr ) return; + pCheck->mxErr--; + pCheck->nErr++; + va_start(ap, zFormat); + if( pCheck->errMsg.nChar ){ + sqlite3_str_append(&pCheck->errMsg, "\n", 1); + } + if( pCheck->zPfx ){ + sqlite3_str_appendf(&pCheck->errMsg, pCheck->zPfx, + pCheck->v0, pCheck->v1, pCheck->v2); + } + sqlite3_str_vappendf(&pCheck->errMsg, zFormat, ap); + va_end(ap); + if( pCheck->errMsg.accError==SQLITE_NOMEM ){ + checkOom(pCheck); + } +} +#endif /* SQLITE_OMIT_INTEGRITY_CHECK */ + +#ifndef SQLITE_OMIT_INTEGRITY_CHECK + +/* +** Return non-zero if the bit in the IntegrityCk.aPgRef[] array that +** corresponds to page iPg is already set. +*/ +static int getPageReferenced(IntegrityCk *pCheck, Pgno iPg){ + assert( iPg<=pCheck->nPage && sizeof(pCheck->aPgRef[0])==1 ); + return (pCheck->aPgRef[iPg/8] & (1 << (iPg & 0x07))); +} + +/* +** Set the bit in the IntegrityCk.aPgRef[] array that corresponds to page iPg. +*/ +static void setPageReferenced(IntegrityCk *pCheck, Pgno iPg){ + assert( iPg<=pCheck->nPage && sizeof(pCheck->aPgRef[0])==1 ); + pCheck->aPgRef[iPg/8] |= (1 << (iPg & 0x07)); +} + + +/* +** Add 1 to the reference count for page iPage. If this is the second +** reference to the page, add an error message to pCheck->zErrMsg. +** Return 1 if there are 2 or more references to the page and 0 if +** if this is the first reference to the page. +** +** Also check that the page number is in bounds. +*/ +static int checkRef(IntegrityCk *pCheck, Pgno iPage){ + if( iPage>pCheck->nPage || iPage==0 ){ + checkAppendMsg(pCheck, "invalid page number %u", iPage); + return 1; + } + if( getPageReferenced(pCheck, iPage) ){ + checkAppendMsg(pCheck, "2nd reference to page %u", iPage); + return 1; + } + setPageReferenced(pCheck, iPage); + return 0; +} + +#ifndef SQLITE_OMIT_AUTOVACUUM +/* +** Check that the entry in the pointer-map for page iChild maps to +** page iParent, pointer type ptrType. If not, append an error message +** to pCheck. +*/ +static void checkPtrmap( + IntegrityCk *pCheck, /* Integrity check context */ + Pgno iChild, /* Child page number */ + u8 eType, /* Expected pointer map type */ + Pgno iParent /* Expected pointer map parent page number */ +){ + int rc; + u8 ePtrmapType; + Pgno iPtrmapParent; + + rc = ptrmapGet(pCheck->pBt, iChild, &ePtrmapType, &iPtrmapParent); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ) checkOom(pCheck); + checkAppendMsg(pCheck, "Failed to read ptrmap key=%u", iChild); + return; + } + + if( ePtrmapType!=eType || iPtrmapParent!=iParent ){ + checkAppendMsg(pCheck, + "Bad ptr map entry key=%u expected=(%u,%u) got=(%u,%u)", + iChild, eType, iParent, ePtrmapType, iPtrmapParent); + } +} +#endif + +/* +** Check the integrity of the freelist or of an overflow page list. +** Verify that the number of pages on the list is N. +*/ +static void checkList( + IntegrityCk *pCheck, /* Integrity checking context */ + int isFreeList, /* True for a freelist. False for overflow page list */ + Pgno iPage, /* Page number for first page in the list */ + u32 N /* Expected number of pages in the list */ +){ + int i; + u32 expected = N; + int nErrAtStart = pCheck->nErr; + while( iPage!=0 && pCheck->mxErr ){ + DbPage *pOvflPage; + unsigned char *pOvflData; + if( checkRef(pCheck, iPage) ) break; + N--; + if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage, 0) ){ + checkAppendMsg(pCheck, "failed to get page %u", iPage); + break; + } + pOvflData = (unsigned char *)sqlite3PagerGetData(pOvflPage); + if( isFreeList ){ + u32 n = (u32)get4byte(&pOvflData[4]); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pCheck->pBt->autoVacuum ){ + checkPtrmap(pCheck, iPage, PTRMAP_FREEPAGE, 0); + } +#endif + if( n>pCheck->pBt->usableSize/4-2 ){ + checkAppendMsg(pCheck, + "freelist leaf count too big on page %u", iPage); + N--; + }else{ + for(i=0; i<(int)n; i++){ + Pgno iFreePage = get4byte(&pOvflData[8+i*4]); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pCheck->pBt->autoVacuum ){ + checkPtrmap(pCheck, iFreePage, PTRMAP_FREEPAGE, 0); + } +#endif + checkRef(pCheck, iFreePage); + } + N -= n; + } + } +#ifndef SQLITE_OMIT_AUTOVACUUM + else{ + /* If this database supports auto-vacuum and iPage is not the last + ** page in this overflow list, check that the pointer-map entry for + ** the following page matches iPage. + */ + if( pCheck->pBt->autoVacuum && N>0 ){ + i = get4byte(pOvflData); + checkPtrmap(pCheck, i, PTRMAP_OVERFLOW2, iPage); + } + } +#endif + iPage = get4byte(pOvflData); + sqlite3PagerUnref(pOvflPage); + } + if( N && nErrAtStart==pCheck->nErr ){ + checkAppendMsg(pCheck, + "%s is %u but should be %u", + isFreeList ? "size" : "overflow list length", + expected-N, expected); + } +} +#endif /* SQLITE_OMIT_INTEGRITY_CHECK */ + +/* +** An implementation of a min-heap. +** +** aHeap[0] is the number of elements on the heap. aHeap[1] is the +** root element. The daughter nodes of aHeap[N] are aHeap[N*2] +** and aHeap[N*2+1]. +** +** The heap property is this: Every node is less than or equal to both +** of its daughter nodes. A consequence of the heap property is that the +** root node aHeap[1] is always the minimum value currently in the heap. +** +** The btreeHeapInsert() routine inserts an unsigned 32-bit number onto +** the heap, preserving the heap property. The btreeHeapPull() routine +** removes the root element from the heap (the minimum value in the heap) +** and then moves other nodes around as necessary to preserve the heap +** property. +** +** This heap is used for cell overlap and coverage testing. Each u32 +** entry represents the span of a cell or freeblock on a btree page. +** The upper 16 bits are the index of the first byte of a range and the +** lower 16 bits are the index of the last byte of that range. +*/ +static void btreeHeapInsert(u32 *aHeap, u32 x){ + u32 j, i; + assert( aHeap!=0 ); + i = ++aHeap[0]; + aHeap[i] = x; + while( (j = i/2)>0 && aHeap[j]>aHeap[i] ){ + x = aHeap[j]; + aHeap[j] = aHeap[i]; + aHeap[i] = x; + i = j; + } +} +static int btreeHeapPull(u32 *aHeap, u32 *pOut){ + u32 j, i, x; + if( (x = aHeap[0])==0 ) return 0; + *pOut = aHeap[1]; + aHeap[1] = aHeap[x]; + aHeap[x] = 0xffffffff; + aHeap[0]--; + i = 1; + while( (j = i*2)<=aHeap[0] ){ + if( aHeap[j]>aHeap[j+1] ) j++; + if( aHeap[i]<aHeap[j] ) break; + x = aHeap[i]; + aHeap[i] = aHeap[j]; + aHeap[j] = x; + i = j; + } + return 1; +} + +#ifndef SQLITE_OMIT_INTEGRITY_CHECK +/* +** Do various sanity checks on a single page of a tree. Return +** the tree depth. Root pages return 0. Parents of root pages +** return 1, and so forth. +** +** These checks are done: +** +** 1. Make sure that cells and freeblocks do not overlap +** but combine to completely cover the page. +** 2. Make sure integer cell keys are in order. +** 3. Check the integrity of overflow pages. +** 4. Recursively call checkTreePage on all children. +** 5. Verify that the depth of all children is the same. +*/ +static int checkTreePage( + IntegrityCk *pCheck, /* Context for the sanity check */ + Pgno iPage, /* Page number of the page to check */ + i64 *piMinKey, /* Write minimum integer primary key here */ + i64 maxKey /* Error if integer primary key greater than this */ +){ + MemPage *pPage = 0; /* The page being analyzed */ + int i; /* Loop counter */ + int rc; /* Result code from subroutine call */ + int depth = -1, d2; /* Depth of a subtree */ + int pgno; /* Page number */ + int nFrag; /* Number of fragmented bytes on the page */ + int hdr; /* Offset to the page header */ + int cellStart; /* Offset to the start of the cell pointer array */ + int nCell; /* Number of cells */ + int doCoverageCheck = 1; /* True if cell coverage checking should be done */ + int keyCanBeEqual = 1; /* True if IPK can be equal to maxKey + ** False if IPK must be strictly less than maxKey */ + u8 *data; /* Page content */ + u8 *pCell; /* Cell content */ + u8 *pCellIdx; /* Next element of the cell pointer array */ + BtShared *pBt; /* The BtShared object that owns pPage */ + u32 pc; /* Address of a cell */ + u32 usableSize; /* Usable size of the page */ + u32 contentOffset; /* Offset to the start of the cell content area */ + u32 *heap = 0; /* Min-heap used for checking cell coverage */ + u32 x, prev = 0; /* Next and previous entry on the min-heap */ + const char *saved_zPfx = pCheck->zPfx; + int saved_v1 = pCheck->v1; + int saved_v2 = pCheck->v2; + u8 savedIsInit = 0; + + /* Check that the page exists + */ + checkProgress(pCheck); + if( pCheck->mxErr==0 ) goto end_of_check; + pBt = pCheck->pBt; + usableSize = pBt->usableSize; + if( iPage==0 ) return 0; + if( checkRef(pCheck, iPage) ) return 0; + pCheck->zPfx = "Tree %u page %u: "; + pCheck->v1 = iPage; + if( (rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0 ){ + checkAppendMsg(pCheck, + "unable to get the page. error code=%d", rc); + goto end_of_check; + } + + /* Clear MemPage.isInit to make sure the corruption detection code in + ** btreeInitPage() is executed. */ + savedIsInit = pPage->isInit; + pPage->isInit = 0; + if( (rc = btreeInitPage(pPage))!=0 ){ + assert( rc==SQLITE_CORRUPT ); /* The only possible error from InitPage */ + checkAppendMsg(pCheck, + "btreeInitPage() returns error code %d", rc); + goto end_of_check; + } + if( (rc = btreeComputeFreeSpace(pPage))!=0 ){ + assert( rc==SQLITE_CORRUPT ); + checkAppendMsg(pCheck, "free space corruption", rc); + goto end_of_check; + } + data = pPage->aData; + hdr = pPage->hdrOffset; + + /* Set up for cell analysis */ + pCheck->zPfx = "Tree %u page %u cell %u: "; + contentOffset = get2byteNotZero(&data[hdr+5]); + assert( contentOffset<=usableSize ); /* Enforced by btreeInitPage() */ + + /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the + ** number of cells on the page. */ + nCell = get2byte(&data[hdr+3]); + assert( pPage->nCell==nCell ); + + /* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page + ** immediately follows the b-tree page header. */ + cellStart = hdr + 12 - 4*pPage->leaf; + assert( pPage->aCellIdx==&data[cellStart] ); + pCellIdx = &data[cellStart + 2*(nCell-1)]; + + if( !pPage->leaf ){ + /* Analyze the right-child page of internal pages */ + pgno = get4byte(&data[hdr+8]); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pBt->autoVacuum ){ + pCheck->zPfx = "Tree %u page %u right child: "; + checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage); + } +#endif + depth = checkTreePage(pCheck, pgno, &maxKey, maxKey); + keyCanBeEqual = 0; + }else{ + /* For leaf pages, the coverage check will occur in the same loop + ** as the other cell checks, so initialize the heap. */ + heap = pCheck->heap; + heap[0] = 0; + } + + /* EVIDENCE-OF: R-02776-14802 The cell pointer array consists of K 2-byte + ** integer offsets to the cell contents. */ + for(i=nCell-1; i>=0 && pCheck->mxErr; i--){ + CellInfo info; + + /* Check cell size */ + pCheck->v2 = i; + assert( pCellIdx==&data[cellStart + i*2] ); + pc = get2byteAligned(pCellIdx); + pCellIdx -= 2; + if( pc<contentOffset || pc>usableSize-4 ){ + checkAppendMsg(pCheck, "Offset %u out of range %u..%u", + pc, contentOffset, usableSize-4); + doCoverageCheck = 0; + continue; + } + pCell = &data[pc]; + pPage->xParseCell(pPage, pCell, &info); + if( pc+info.nSize>usableSize ){ + checkAppendMsg(pCheck, "Extends off end of page"); + doCoverageCheck = 0; + continue; + } + + /* Check for integer primary key out of range */ + if( pPage->intKey ){ + if( keyCanBeEqual ? (info.nKey > maxKey) : (info.nKey >= maxKey) ){ + checkAppendMsg(pCheck, "Rowid %lld out of order", info.nKey); + } + maxKey = info.nKey; + keyCanBeEqual = 0; /* Only the first key on the page may ==maxKey */ + } + + /* Check the content overflow list */ + if( info.nPayload>info.nLocal ){ + u32 nPage; /* Number of pages on the overflow chain */ + Pgno pgnoOvfl; /* First page of the overflow chain */ + assert( pc + info.nSize - 4 <= usableSize ); + nPage = (info.nPayload - info.nLocal + usableSize - 5)/(usableSize - 4); + pgnoOvfl = get4byte(&pCell[info.nSize - 4]); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pBt->autoVacuum ){ + checkPtrmap(pCheck, pgnoOvfl, PTRMAP_OVERFLOW1, iPage); + } +#endif + checkList(pCheck, 0, pgnoOvfl, nPage); + } + + if( !pPage->leaf ){ + /* Check sanity of left child page for internal pages */ + pgno = get4byte(pCell); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pBt->autoVacuum ){ + checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage); + } +#endif + d2 = checkTreePage(pCheck, pgno, &maxKey, maxKey); + keyCanBeEqual = 0; + if( d2!=depth ){ + checkAppendMsg(pCheck, "Child page depth differs"); + depth = d2; + } + }else{ + /* Populate the coverage-checking heap for leaf pages */ + btreeHeapInsert(heap, (pc<<16)|(pc+info.nSize-1)); + } + } + *piMinKey = maxKey; + + /* Check for complete coverage of the page + */ + pCheck->zPfx = 0; + if( doCoverageCheck && pCheck->mxErr>0 ){ + /* For leaf pages, the min-heap has already been initialized and the + ** cells have already been inserted. But for internal pages, that has + ** not yet been done, so do it now */ + if( !pPage->leaf ){ + heap = pCheck->heap; + heap[0] = 0; + for(i=nCell-1; i>=0; i--){ + u32 size; + pc = get2byteAligned(&data[cellStart+i*2]); + size = pPage->xCellSize(pPage, &data[pc]); + btreeHeapInsert(heap, (pc<<16)|(pc+size-1)); + } + } + /* Add the freeblocks to the min-heap + ** + ** EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header + ** is the offset of the first freeblock, or zero if there are no + ** freeblocks on the page. + */ + i = get2byte(&data[hdr+1]); + while( i>0 ){ + int size, j; + assert( (u32)i<=usableSize-4 ); /* Enforced by btreeComputeFreeSpace() */ + size = get2byte(&data[i+2]); + assert( (u32)(i+size)<=usableSize ); /* due to btreeComputeFreeSpace() */ + btreeHeapInsert(heap, (((u32)i)<<16)|(i+size-1)); + /* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a + ** big-endian integer which is the offset in the b-tree page of the next + ** freeblock in the chain, or zero if the freeblock is the last on the + ** chain. */ + j = get2byte(&data[i]); + /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of + ** increasing offset. */ + assert( j==0 || j>i+size ); /* Enforced by btreeComputeFreeSpace() */ + assert( (u32)j<=usableSize-4 ); /* Enforced by btreeComputeFreeSpace() */ + i = j; + } + /* Analyze the min-heap looking for overlap between cells and/or + ** freeblocks, and counting the number of untracked bytes in nFrag. + ** + ** Each min-heap entry is of the form: (start_address<<16)|end_address. + ** There is an implied first entry the covers the page header, the cell + ** pointer index, and the gap between the cell pointer index and the start + ** of cell content. + ** + ** The loop below pulls entries from the min-heap in order and compares + ** the start_address against the previous end_address. If there is an + ** overlap, that means bytes are used multiple times. If there is a gap, + ** that gap is added to the fragmentation count. + */ + nFrag = 0; + prev = contentOffset - 1; /* Implied first min-heap entry */ + while( btreeHeapPull(heap,&x) ){ + if( (prev&0xffff)>=(x>>16) ){ + checkAppendMsg(pCheck, + "Multiple uses for byte %u of page %u", x>>16, iPage); + break; + }else{ + nFrag += (x>>16) - (prev&0xffff) - 1; + prev = x; + } + } + nFrag += usableSize - (prev&0xffff) - 1; + /* EVIDENCE-OF: R-43263-13491 The total number of bytes in all fragments + ** is stored in the fifth field of the b-tree page header. + ** EVIDENCE-OF: R-07161-27322 The one-byte integer at offset 7 gives the + ** number of fragmented free bytes within the cell content area. + */ + if( heap[0]==0 && nFrag!=data[hdr+7] ){ + checkAppendMsg(pCheck, + "Fragmentation of %u bytes reported as %u on page %u", + nFrag, data[hdr+7], iPage); + } + } + +end_of_check: + if( !doCoverageCheck ) pPage->isInit = savedIsInit; + releasePage(pPage); + pCheck->zPfx = saved_zPfx; + pCheck->v1 = saved_v1; + pCheck->v2 = saved_v2; + return depth+1; +} +#endif /* SQLITE_OMIT_INTEGRITY_CHECK */ + +#ifndef SQLITE_OMIT_INTEGRITY_CHECK +/* +** This routine does a complete check of the given BTree file. aRoot[] is +** an array of pages numbers were each page number is the root page of +** a table. nRoot is the number of entries in aRoot. +** +** A read-only or read-write transaction must be opened before calling +** this function. +** +** Write the number of error seen in *pnErr. Except for some memory +** allocation errors, an error message held in memory obtained from +** malloc is returned if *pnErr is non-zero. If *pnErr==0 then NULL is +** returned. If a memory allocation error occurs, NULL is returned. +** +** If the first entry in aRoot[] is 0, that indicates that the list of +** root pages is incomplete. This is a "partial integrity-check". This +** happens when performing an integrity check on a single table. The +** zero is skipped, of course. But in addition, the freelist checks +** and the checks to make sure every page is referenced are also skipped, +** since obviously it is not possible to know which pages are covered by +** the unverified btrees. Except, if aRoot[1] is 1, then the freelist +** checks are still performed. +*/ +SQLITE_PRIVATE int sqlite3BtreeIntegrityCheck( + sqlite3 *db, /* Database connection that is running the check */ + Btree *p, /* The btree to be checked */ + Pgno *aRoot, /* An array of root pages numbers for individual trees */ + int nRoot, /* Number of entries in aRoot[] */ + int mxErr, /* Stop reporting errors after this many */ + int *pnErr, /* OUT: Write number of errors seen to this variable */ + char **pzOut /* OUT: Write the error message string here */ +){ + Pgno i; + IntegrityCk sCheck; + BtShared *pBt = p->pBt; + u64 savedDbFlags = pBt->db->flags; + char zErr[100]; + int bPartial = 0; /* True if not checking all btrees */ + int bCkFreelist = 1; /* True to scan the freelist */ + VVA_ONLY( int nRef ); + assert( nRoot>0 ); + + /* aRoot[0]==0 means this is a partial check */ + if( aRoot[0]==0 ){ + assert( nRoot>1 ); + bPartial = 1; + if( aRoot[1]!=1 ) bCkFreelist = 0; + } + + sqlite3BtreeEnter(p); + assert( p->inTrans>TRANS_NONE && pBt->inTransaction>TRANS_NONE ); + VVA_ONLY( nRef = sqlite3PagerRefcount(pBt->pPager) ); + assert( nRef>=0 ); + memset(&sCheck, 0, sizeof(sCheck)); + sCheck.db = db; + sCheck.pBt = pBt; + sCheck.pPager = pBt->pPager; + sCheck.nPage = btreePagecount(sCheck.pBt); + sCheck.mxErr = mxErr; + sqlite3StrAccumInit(&sCheck.errMsg, 0, zErr, sizeof(zErr), SQLITE_MAX_LENGTH); + sCheck.errMsg.printfFlags = SQLITE_PRINTF_INTERNAL; + if( sCheck.nPage==0 ){ + goto integrity_ck_cleanup; + } + + sCheck.aPgRef = sqlite3MallocZero((sCheck.nPage / 8)+ 1); + if( !sCheck.aPgRef ){ + checkOom(&sCheck); + goto integrity_ck_cleanup; + } + sCheck.heap = (u32*)sqlite3PageMalloc( pBt->pageSize ); + if( sCheck.heap==0 ){ + checkOom(&sCheck); + goto integrity_ck_cleanup; + } + + i = PENDING_BYTE_PAGE(pBt); + if( i<=sCheck.nPage ) setPageReferenced(&sCheck, i); + + /* Check the integrity of the freelist + */ + if( bCkFreelist ){ + sCheck.zPfx = "Freelist: "; + checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]), + get4byte(&pBt->pPage1->aData[36])); + sCheck.zPfx = 0; + } + + /* Check all the tables. + */ +#ifndef SQLITE_OMIT_AUTOVACUUM + if( !bPartial ){ + if( pBt->autoVacuum ){ + Pgno mx = 0; + Pgno mxInHdr; + for(i=0; (int)i<nRoot; i++) if( mx<aRoot[i] ) mx = aRoot[i]; + mxInHdr = get4byte(&pBt->pPage1->aData[52]); + if( mx!=mxInHdr ){ + checkAppendMsg(&sCheck, + "max rootpage (%u) disagrees with header (%u)", + mx, mxInHdr + ); + } + }else if( get4byte(&pBt->pPage1->aData[64])!=0 ){ + checkAppendMsg(&sCheck, + "incremental_vacuum enabled with a max rootpage of zero" + ); + } + } +#endif + testcase( pBt->db->flags & SQLITE_CellSizeCk ); + pBt->db->flags &= ~(u64)SQLITE_CellSizeCk; + for(i=0; (int)i<nRoot && sCheck.mxErr; i++){ + i64 notUsed; + if( aRoot[i]==0 ) continue; +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pBt->autoVacuum && aRoot[i]>1 && !bPartial ){ + checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); + } +#endif + sCheck.v0 = aRoot[i]; + checkTreePage(&sCheck, aRoot[i], &notUsed, LARGEST_INT64); + } + pBt->db->flags = savedDbFlags; + + /* Make sure every page in the file is referenced + */ + if( !bPartial ){ + for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){ +#ifdef SQLITE_OMIT_AUTOVACUUM + if( getPageReferenced(&sCheck, i)==0 ){ + checkAppendMsg(&sCheck, "Page %u: never used", i); + } +#else + /* If the database supports auto-vacuum, make sure no tables contain + ** references to pointer-map pages. + */ + if( getPageReferenced(&sCheck, i)==0 && + (PTRMAP_PAGENO(pBt, i)!=i || !pBt->autoVacuum) ){ + checkAppendMsg(&sCheck, "Page %u: never used", i); + } + if( getPageReferenced(&sCheck, i)!=0 && + (PTRMAP_PAGENO(pBt, i)==i && pBt->autoVacuum) ){ + checkAppendMsg(&sCheck, "Page %u: pointer map referenced", i); + } +#endif + } + } + + /* Clean up and report errors. + */ +integrity_ck_cleanup: + sqlite3PageFree(sCheck.heap); + sqlite3_free(sCheck.aPgRef); + *pnErr = sCheck.nErr; + if( sCheck.nErr==0 ){ + sqlite3_str_reset(&sCheck.errMsg); + *pzOut = 0; + }else{ + *pzOut = sqlite3StrAccumFinish(&sCheck.errMsg); + } + /* Make sure this analysis did not leave any unref() pages. */ + assert( nRef==sqlite3PagerRefcount(pBt->pPager) ); + sqlite3BtreeLeave(p); + return sCheck.rc; +} +#endif /* SQLITE_OMIT_INTEGRITY_CHECK */ + +/* +** Return the full pathname of the underlying database file. Return +** an empty string if the database is in-memory or a TEMP database. +** +** The pager filename is invariant as long as the pager is +** open so it is safe to access without the BtShared mutex. +*/ +SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *p){ + assert( p->pBt->pPager!=0 ); + return sqlite3PagerFilename(p->pBt->pPager, 1); +} + +/* +** Return the pathname of the journal file for this database. The return +** value of this routine is the same regardless of whether the journal file +** has been created or not. +** +** The pager journal filename is invariant as long as the pager is +** open so it is safe to access without the BtShared mutex. +*/ +SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *p){ + assert( p->pBt->pPager!=0 ); + return sqlite3PagerJournalname(p->pBt->pPager); +} + +/* +** Return one of SQLITE_TXN_NONE, SQLITE_TXN_READ, or SQLITE_TXN_WRITE +** to describe the current transaction state of Btree p. +*/ +SQLITE_PRIVATE int sqlite3BtreeTxnState(Btree *p){ + assert( p==0 || sqlite3_mutex_held(p->db->mutex) ); + return p ? p->inTrans : 0; +} + +#ifndef SQLITE_OMIT_WAL +/* +** Run a checkpoint on the Btree passed as the first argument. +** +** Return SQLITE_LOCKED if this or any other connection has an open +** transaction on the shared-cache the argument Btree is connected to. +** +** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART. +*/ +SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree *p, int eMode, int *pnLog, int *pnCkpt){ + int rc = SQLITE_OK; + if( p ){ + BtShared *pBt = p->pBt; + sqlite3BtreeEnter(p); + if( pBt->inTransaction!=TRANS_NONE ){ + rc = SQLITE_LOCKED; + }else{ + rc = sqlite3PagerCheckpoint(pBt->pPager, p->db, eMode, pnLog, pnCkpt); + } + sqlite3BtreeLeave(p); + } + return rc; +} +#endif + +/* +** Return true if there is currently a backup running on Btree p. +*/ +SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree *p){ + assert( p ); + assert( sqlite3_mutex_held(p->db->mutex) ); + return p->nBackup!=0; +} + +/* +** This function returns a pointer to a blob of memory associated with +** a single shared-btree. The memory is used by client code for its own +** purposes (for example, to store a high-level schema associated with +** the shared-btree). The btree layer manages reference counting issues. +** +** The first time this is called on a shared-btree, nBytes bytes of memory +** are allocated, zeroed, and returned to the caller. For each subsequent +** call the nBytes parameter is ignored and a pointer to the same blob +** of memory returned. +** +** If the nBytes parameter is 0 and the blob of memory has not yet been +** allocated, a null pointer is returned. If the blob has already been +** allocated, it is returned as normal. +** +** Just before the shared-btree is closed, the function passed as the +** xFree argument when the memory allocation was made is invoked on the +** blob of allocated memory. The xFree function should not call sqlite3_free() +** on the memory, the btree layer does that. +*/ +SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void *)){ + BtShared *pBt = p->pBt; + sqlite3BtreeEnter(p); + if( !pBt->pSchema && nBytes ){ + pBt->pSchema = sqlite3DbMallocZero(0, nBytes); + pBt->xFreeSchema = xFree; + } + sqlite3BtreeLeave(p); + return pBt->pSchema; +} + +/* +** Return SQLITE_LOCKED_SHAREDCACHE if another user of the same shared +** btree as the argument handle holds an exclusive lock on the +** sqlite_schema table. Otherwise SQLITE_OK. +*/ +SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *p){ + int rc; + assert( sqlite3_mutex_held(p->db->mutex) ); + sqlite3BtreeEnter(p); + rc = querySharedCacheTableLock(p, SCHEMA_ROOT, READ_LOCK); + assert( rc==SQLITE_OK || rc==SQLITE_LOCKED_SHAREDCACHE ); + sqlite3BtreeLeave(p); + return rc; +} + + +#ifndef SQLITE_OMIT_SHARED_CACHE +/* +** Obtain a lock on the table whose root page is iTab. The +** lock is a write lock if isWritelock is true or a read lock +** if it is false. +*/ +SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){ + int rc = SQLITE_OK; + assert( p->inTrans!=TRANS_NONE ); + if( p->sharable ){ + u8 lockType = READ_LOCK + isWriteLock; + assert( READ_LOCK+1==WRITE_LOCK ); + assert( isWriteLock==0 || isWriteLock==1 ); + + sqlite3BtreeEnter(p); + rc = querySharedCacheTableLock(p, iTab, lockType); + if( rc==SQLITE_OK ){ + rc = setSharedCacheTableLock(p, iTab, lockType); + } + sqlite3BtreeLeave(p); + } + return rc; +} +#endif + +#ifndef SQLITE_OMIT_INCRBLOB +/* +** Argument pCsr must be a cursor opened for writing on an +** INTKEY table currently pointing at a valid table entry. +** This function modifies the data stored as part of that entry. +** +** Only the data content may only be modified, it is not possible to +** change the length of the data stored. If this function is called with +** parameters that attempt to write past the end of the existing data, +** no modifications are made and SQLITE_CORRUPT is returned. +*/ +SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){ + int rc; + assert( cursorOwnsBtShared(pCsr) ); + assert( sqlite3_mutex_held(pCsr->pBtree->db->mutex) ); + assert( pCsr->curFlags & BTCF_Incrblob ); + + rc = restoreCursorPosition(pCsr); + if( rc!=SQLITE_OK ){ + return rc; + } + assert( pCsr->eState!=CURSOR_REQUIRESEEK ); + if( pCsr->eState!=CURSOR_VALID ){ + return SQLITE_ABORT; + } + + /* Save the positions of all other cursors open on this table. This is + ** required in case any of them are holding references to an xFetch + ** version of the b-tree page modified by the accessPayload call below. + ** + ** Note that pCsr must be open on a INTKEY table and saveCursorPosition() + ** and hence saveAllCursors() cannot fail on a BTREE_INTKEY table, hence + ** saveAllCursors can only return SQLITE_OK. + */ + VVA_ONLY(rc =) saveAllCursors(pCsr->pBt, pCsr->pgnoRoot, pCsr); + assert( rc==SQLITE_OK ); + + /* Check some assumptions: + ** (a) the cursor is open for writing, + ** (b) there is a read/write transaction open, + ** (c) the connection holds a write-lock on the table (if required), + ** (d) there are no conflicting read-locks, and + ** (e) the cursor points at a valid row of an intKey table. + */ + if( (pCsr->curFlags & BTCF_WriteFlag)==0 ){ + return SQLITE_READONLY; + } + assert( (pCsr->pBt->btsFlags & BTS_READ_ONLY)==0 + && pCsr->pBt->inTransaction==TRANS_WRITE ); + assert( hasSharedCacheTableLock(pCsr->pBtree, pCsr->pgnoRoot, 0, 2) ); + assert( !hasReadConflicts(pCsr->pBtree, pCsr->pgnoRoot) ); + assert( pCsr->pPage->intKey ); + + return accessPayload(pCsr, offset, amt, (unsigned char *)z, 1); +} + +/* +** Mark this cursor as an incremental blob cursor. +*/ +SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *pCur){ + pCur->curFlags |= BTCF_Incrblob; + pCur->pBtree->hasIncrblobCur = 1; +} +#endif + +/* +** Set both the "read version" (single byte at byte offset 18) and +** "write version" (single byte at byte offset 19) fields in the database +** header to iVersion. +*/ +SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBtree, int iVersion){ + BtShared *pBt = pBtree->pBt; + int rc; /* Return code */ + + assert( iVersion==1 || iVersion==2 ); + + /* If setting the version fields to 1, do not automatically open the + ** WAL connection, even if the version fields are currently set to 2. + */ + pBt->btsFlags &= ~BTS_NO_WAL; + if( iVersion==1 ) pBt->btsFlags |= BTS_NO_WAL; + + rc = sqlite3BtreeBeginTrans(pBtree, 0, 0); + if( rc==SQLITE_OK ){ + u8 *aData = pBt->pPage1->aData; + if( aData[18]!=(u8)iVersion || aData[19]!=(u8)iVersion ){ + rc = sqlite3BtreeBeginTrans(pBtree, 2, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerWrite(pBt->pPage1->pDbPage); + if( rc==SQLITE_OK ){ + aData[18] = (u8)iVersion; + aData[19] = (u8)iVersion; + } + } + } + } + + pBt->btsFlags &= ~BTS_NO_WAL; + return rc; +} + +/* +** Return true if the cursor has a hint specified. This routine is +** only used from within assert() statements +*/ +SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor *pCsr, unsigned int mask){ + return (pCsr->hints & mask)!=0; +} + +/* +** Return true if the given Btree is read-only. +*/ +SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *p){ + return (p->pBt->btsFlags & BTS_READ_ONLY)!=0; +} + +/* +** Return the size of the header added to each page by this module. +*/ +SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); } + +/* +** If no transaction is active and the database is not a temp-db, clear +** the in-memory pager cache. +*/ +SQLITE_PRIVATE void sqlite3BtreeClearCache(Btree *p){ + BtShared *pBt = p->pBt; + if( pBt->inTransaction==TRANS_NONE ){ + sqlite3PagerClearCache(pBt->pPager); + } +} + +#if !defined(SQLITE_OMIT_SHARED_CACHE) +/* +** Return true if the Btree passed as the only argument is sharable. +*/ +SQLITE_PRIVATE int sqlite3BtreeSharable(Btree *p){ + return p->sharable; +} + +/* +** Return the number of connections to the BtShared object accessed by +** the Btree handle passed as the only argument. For private caches +** this is always 1. For shared caches it may be 1 or greater. +*/ +SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree *p){ + testcase( p->sharable ); + return p->pBt->nRef; +} +#endif + +/************** End of btree.c ***********************************************/ +/************** Begin file backup.c ******************************************/ +/* +** 2009 January 28 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the implementation of the sqlite3_backup_XXX() +** API functions and the related features. +*/ +/* #include "sqliteInt.h" */ +/* #include "btreeInt.h" */ + +/* +** Structure allocated for each backup operation. +*/ +struct sqlite3_backup { + sqlite3* pDestDb; /* Destination database handle */ + Btree *pDest; /* Destination b-tree file */ + u32 iDestSchema; /* Original schema cookie in destination */ + int bDestLocked; /* True once a write-transaction is open on pDest */ + + Pgno iNext; /* Page number of the next source page to copy */ + sqlite3* pSrcDb; /* Source database handle */ + Btree *pSrc; /* Source b-tree file */ + + int rc; /* Backup process error code */ + + /* These two variables are set by every call to backup_step(). They are + ** read by calls to backup_remaining() and backup_pagecount(). + */ + Pgno nRemaining; /* Number of pages left to copy */ + Pgno nPagecount; /* Total number of pages to copy */ + + int isAttached; /* True once backup has been registered with pager */ + sqlite3_backup *pNext; /* Next backup associated with source pager */ +}; + +/* +** THREAD SAFETY NOTES: +** +** Once it has been created using backup_init(), a single sqlite3_backup +** structure may be accessed via two groups of thread-safe entry points: +** +** * Via the sqlite3_backup_XXX() API function backup_step() and +** backup_finish(). Both these functions obtain the source database +** handle mutex and the mutex associated with the source BtShared +** structure, in that order. +** +** * Via the BackupUpdate() and BackupRestart() functions, which are +** invoked by the pager layer to report various state changes in +** the page cache associated with the source database. The mutex +** associated with the source database BtShared structure will always +** be held when either of these functions are invoked. +** +** The other sqlite3_backup_XXX() API functions, backup_remaining() and +** backup_pagecount() are not thread-safe functions. If they are called +** while some other thread is calling backup_step() or backup_finish(), +** the values returned may be invalid. There is no way for a call to +** BackupUpdate() or BackupRestart() to interfere with backup_remaining() +** or backup_pagecount(). +** +** Depending on the SQLite configuration, the database handles and/or +** the Btree objects may have their own mutexes that require locking. +** Non-sharable Btrees (in-memory databases for example), do not have +** associated mutexes. +*/ + +/* +** Return a pointer corresponding to database zDb (i.e. "main", "temp") +** in connection handle pDb. If such a database cannot be found, return +** a NULL pointer and write an error message to pErrorDb. +** +** If the "temp" database is requested, it may need to be opened by this +** function. If an error occurs while doing so, return 0 and write an +** error message to pErrorDb. +*/ +static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ + int i = sqlite3FindDbName(pDb, zDb); + + if( i==1 ){ + Parse sParse; + int rc = 0; + sqlite3ParseObjectInit(&sParse,pDb); + if( sqlite3OpenTempDatabase(&sParse) ){ + sqlite3ErrorWithMsg(pErrorDb, sParse.rc, "%s", sParse.zErrMsg); + rc = SQLITE_ERROR; + } + sqlite3DbFree(pErrorDb, sParse.zErrMsg); + sqlite3ParseObjectReset(&sParse); + if( rc ){ + return 0; + } + } + + if( i<0 ){ + sqlite3ErrorWithMsg(pErrorDb, SQLITE_ERROR, "unknown database %s", zDb); + return 0; + } + + return pDb->aDb[i].pBt; +} + +/* +** Attempt to set the page size of the destination to match the page size +** of the source. +*/ +static int setDestPgsz(sqlite3_backup *p){ + int rc; + rc = sqlite3BtreeSetPageSize(p->pDest,sqlite3BtreeGetPageSize(p->pSrc),0,0); + return rc; +} + +/* +** Check that there is no open read-transaction on the b-tree passed as the +** second argument. If there is not, return SQLITE_OK. Otherwise, if there +** is an open read-transaction, return SQLITE_ERROR and leave an error +** message in database handle db. +*/ +static int checkReadTransaction(sqlite3 *db, Btree *p){ + if( sqlite3BtreeTxnState(p)!=SQLITE_TXN_NONE ){ + sqlite3ErrorWithMsg(db, SQLITE_ERROR, "destination database is in use"); + return SQLITE_ERROR; + } + return SQLITE_OK; +} + +/* +** Create an sqlite3_backup process to copy the contents of zSrcDb from +** connection handle pSrcDb to zDestDb in pDestDb. If successful, return +** a pointer to the new sqlite3_backup object. +** +** If an error occurs, NULL is returned and an error code and error message +** stored in database handle pDestDb. +*/ +SQLITE_API sqlite3_backup *sqlite3_backup_init( + sqlite3* pDestDb, /* Database to write to */ + const char *zDestDb, /* Name of database within pDestDb */ + sqlite3* pSrcDb, /* Database connection to read from */ + const char *zSrcDb /* Name of database within pSrcDb */ +){ + sqlite3_backup *p; /* Value to return */ + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(pSrcDb)||!sqlite3SafetyCheckOk(pDestDb) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + + /* Lock the source database handle. The destination database + ** handle is not locked in this routine, but it is locked in + ** sqlite3_backup_step(). The user is required to ensure that no + ** other thread accesses the destination handle for the duration + ** of the backup operation. Any attempt to use the destination + ** database connection while a backup is in progress may cause + ** a malfunction or a deadlock. + */ + sqlite3_mutex_enter(pSrcDb->mutex); + sqlite3_mutex_enter(pDestDb->mutex); + + if( pSrcDb==pDestDb ){ + sqlite3ErrorWithMsg( + pDestDb, SQLITE_ERROR, "source and destination must be distinct" + ); + p = 0; + }else { + /* Allocate space for a new sqlite3_backup object... + ** EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a + ** call to sqlite3_backup_init() and is destroyed by a call to + ** sqlite3_backup_finish(). */ + p = (sqlite3_backup *)sqlite3MallocZero(sizeof(sqlite3_backup)); + if( !p ){ + sqlite3Error(pDestDb, SQLITE_NOMEM_BKPT); + } + } + + /* If the allocation succeeded, populate the new object. */ + if( p ){ + p->pSrc = findBtree(pDestDb, pSrcDb, zSrcDb); + p->pDest = findBtree(pDestDb, pDestDb, zDestDb); + p->pDestDb = pDestDb; + p->pSrcDb = pSrcDb; + p->iNext = 1; + p->isAttached = 0; + + if( 0==p->pSrc || 0==p->pDest + || checkReadTransaction(pDestDb, p->pDest)!=SQLITE_OK + ){ + /* One (or both) of the named databases did not exist or an OOM + ** error was hit. Or there is a transaction open on the destination + ** database. The error has already been written into the pDestDb + ** handle. All that is left to do here is free the sqlite3_backup + ** structure. */ + sqlite3_free(p); + p = 0; + } + } + if( p ){ + p->pSrc->nBackup++; + } + + sqlite3_mutex_leave(pDestDb->mutex); + sqlite3_mutex_leave(pSrcDb->mutex); + return p; +} + +/* +** Argument rc is an SQLite error code. Return true if this error is +** considered fatal if encountered during a backup operation. All errors +** are considered fatal except for SQLITE_BUSY and SQLITE_LOCKED. +*/ +static int isFatalError(int rc){ + return (rc!=SQLITE_OK && rc!=SQLITE_BUSY && ALWAYS(rc!=SQLITE_LOCKED)); +} + +/* +** Parameter zSrcData points to a buffer containing the data for +** page iSrcPg from the source database. Copy this data into the +** destination database. +*/ +static int backupOnePage( + sqlite3_backup *p, /* Backup handle */ + Pgno iSrcPg, /* Source database page to backup */ + const u8 *zSrcData, /* Source database page data */ + int bUpdate /* True for an update, false otherwise */ +){ + Pager * const pDestPager = sqlite3BtreePager(p->pDest); + const int nSrcPgsz = sqlite3BtreeGetPageSize(p->pSrc); + int nDestPgsz = sqlite3BtreeGetPageSize(p->pDest); + const int nCopy = MIN(nSrcPgsz, nDestPgsz); + const i64 iEnd = (i64)iSrcPg*(i64)nSrcPgsz; + int rc = SQLITE_OK; + i64 iOff; + + assert( sqlite3BtreeGetReserveNoMutex(p->pSrc)>=0 ); + assert( p->bDestLocked ); + assert( !isFatalError(p->rc) ); + assert( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ); + assert( zSrcData ); + assert( nSrcPgsz==nDestPgsz || sqlite3PagerIsMemdb(pDestPager)==0 ); + + /* This loop runs once for each destination page spanned by the source + ** page. For each iteration, variable iOff is set to the byte offset + ** of the destination page. + */ + for(iOff=iEnd-(i64)nSrcPgsz; rc==SQLITE_OK && iOff<iEnd; iOff+=nDestPgsz){ + DbPage *pDestPg = 0; + Pgno iDest = (Pgno)(iOff/nDestPgsz)+1; + if( iDest==PENDING_BYTE_PAGE(p->pDest->pBt) ) continue; + if( SQLITE_OK==(rc = sqlite3PagerGet(pDestPager, iDest, &pDestPg, 0)) + && SQLITE_OK==(rc = sqlite3PagerWrite(pDestPg)) + ){ + const u8 *zIn = &zSrcData[iOff%nSrcPgsz]; + u8 *zDestData = sqlite3PagerGetData(pDestPg); + u8 *zOut = &zDestData[iOff%nDestPgsz]; + + /* Copy the data from the source page into the destination page. + ** Then clear the Btree layer MemPage.isInit flag. Both this module + ** and the pager code use this trick (clearing the first byte + ** of the page 'extra' space to invalidate the Btree layers + ** cached parse of the page). MemPage.isInit is marked + ** "MUST BE FIRST" for this purpose. + */ + memcpy(zOut, zIn, nCopy); + ((u8 *)sqlite3PagerGetExtra(pDestPg))[0] = 0; + if( iOff==0 && bUpdate==0 ){ + sqlite3Put4byte(&zOut[28], sqlite3BtreeLastPage(p->pSrc)); + } + } + sqlite3PagerUnref(pDestPg); + } + + return rc; +} + +/* +** If pFile is currently larger than iSize bytes, then truncate it to +** exactly iSize bytes. If pFile is not larger than iSize bytes, then +** this function is a no-op. +** +** Return SQLITE_OK if everything is successful, or an SQLite error +** code if an error occurs. +*/ +static int backupTruncateFile(sqlite3_file *pFile, i64 iSize){ + i64 iCurrent; + int rc = sqlite3OsFileSize(pFile, &iCurrent); + if( rc==SQLITE_OK && iCurrent>iSize ){ + rc = sqlite3OsTruncate(pFile, iSize); + } + return rc; +} + +/* +** Register this backup object with the associated source pager for +** callbacks when pages are changed or the cache invalidated. +*/ +static void attachBackupObject(sqlite3_backup *p){ + sqlite3_backup **pp; + assert( sqlite3BtreeHoldsMutex(p->pSrc) ); + pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc)); + p->pNext = *pp; + *pp = p; + p->isAttached = 1; +} + +/* +** Copy nPage pages from the source b-tree to the destination. +*/ +SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ + int rc; + int destMode; /* Destination journal mode */ + int pgszSrc = 0; /* Source page size */ + int pgszDest = 0; /* Destination page size */ + +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(p->pSrcDb->mutex); + sqlite3BtreeEnter(p->pSrc); + if( p->pDestDb ){ + sqlite3_mutex_enter(p->pDestDb->mutex); + } + + rc = p->rc; + if( !isFatalError(rc) ){ + Pager * const pSrcPager = sqlite3BtreePager(p->pSrc); /* Source pager */ + Pager * const pDestPager = sqlite3BtreePager(p->pDest); /* Dest pager */ + int ii; /* Iterator variable */ + int nSrcPage = -1; /* Size of source db in pages */ + int bCloseTrans = 0; /* True if src db requires unlocking */ + + /* If the source pager is currently in a write-transaction, return + ** SQLITE_BUSY immediately. + */ + if( p->pDestDb && p->pSrc->pBt->inTransaction==TRANS_WRITE ){ + rc = SQLITE_BUSY; + }else{ + rc = SQLITE_OK; + } + + /* If there is no open read-transaction on the source database, open + ** one now. If a transaction is opened here, then it will be closed + ** before this function exits. + */ + if( rc==SQLITE_OK && SQLITE_TXN_NONE==sqlite3BtreeTxnState(p->pSrc) ){ + rc = sqlite3BtreeBeginTrans(p->pSrc, 0, 0); + bCloseTrans = 1; + } + + /* If the destination database has not yet been locked (i.e. if this + ** is the first call to backup_step() for the current backup operation), + ** try to set its page size to the same as the source database. This + ** is especially important on ZipVFS systems, as in that case it is + ** not possible to create a database file that uses one page size by + ** writing to it with another. */ + if( p->bDestLocked==0 && rc==SQLITE_OK && setDestPgsz(p)==SQLITE_NOMEM ){ + rc = SQLITE_NOMEM; + } + + /* Lock the destination database, if it is not locked already. */ + if( SQLITE_OK==rc && p->bDestLocked==0 + && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2, + (int*)&p->iDestSchema)) + ){ + p->bDestLocked = 1; + } + + /* Do not allow backup if the destination database is in WAL mode + ** and the page sizes are different between source and destination */ + pgszSrc = sqlite3BtreeGetPageSize(p->pSrc); + pgszDest = sqlite3BtreeGetPageSize(p->pDest); + destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(p->pDest)); + if( SQLITE_OK==rc + && (destMode==PAGER_JOURNALMODE_WAL || sqlite3PagerIsMemdb(pDestPager)) + && pgszSrc!=pgszDest + ){ + rc = SQLITE_READONLY; + } + + /* Now that there is a read-lock on the source database, query the + ** source pager for the number of pages in the database. + */ + nSrcPage = (int)sqlite3BtreeLastPage(p->pSrc); + assert( nSrcPage>=0 ); + for(ii=0; (nPage<0 || ii<nPage) && p->iNext<=(Pgno)nSrcPage && !rc; ii++){ + const Pgno iSrcPg = p->iNext; /* Source page number */ + if( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ){ + DbPage *pSrcPg; /* Source page object */ + rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg,PAGER_GET_READONLY); + if( rc==SQLITE_OK ){ + rc = backupOnePage(p, iSrcPg, sqlite3PagerGetData(pSrcPg), 0); + sqlite3PagerUnref(pSrcPg); + } + } + p->iNext++; + } + if( rc==SQLITE_OK ){ + p->nPagecount = nSrcPage; + p->nRemaining = nSrcPage+1-p->iNext; + if( p->iNext>(Pgno)nSrcPage ){ + rc = SQLITE_DONE; + }else if( !p->isAttached ){ + attachBackupObject(p); + } + } + + /* Update the schema version field in the destination database. This + ** is to make sure that the schema-version really does change in + ** the case where the source and destination databases have the + ** same schema version. + */ + if( rc==SQLITE_DONE ){ + if( nSrcPage==0 ){ + rc = sqlite3BtreeNewDb(p->pDest); + nSrcPage = 1; + } + if( rc==SQLITE_OK || rc==SQLITE_DONE ){ + rc = sqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1); + } + if( rc==SQLITE_OK ){ + if( p->pDestDb ){ + sqlite3ResetAllSchemasOfConnection(p->pDestDb); + } + if( destMode==PAGER_JOURNALMODE_WAL ){ + rc = sqlite3BtreeSetVersion(p->pDest, 2); + } + } + if( rc==SQLITE_OK ){ + int nDestTruncate; + /* Set nDestTruncate to the final number of pages in the destination + ** database. The complication here is that the destination page + ** size may be different to the source page size. + ** + ** If the source page size is smaller than the destination page size, + ** round up. In this case the call to sqlite3OsTruncate() below will + ** fix the size of the file. However it is important to call + ** sqlite3PagerTruncateImage() here so that any pages in the + ** destination file that lie beyond the nDestTruncate page mark are + ** journalled by PagerCommitPhaseOne() before they are destroyed + ** by the file truncation. + */ + assert( pgszSrc==sqlite3BtreeGetPageSize(p->pSrc) ); + assert( pgszDest==sqlite3BtreeGetPageSize(p->pDest) ); + if( pgszSrc<pgszDest ){ + int ratio = pgszDest/pgszSrc; + nDestTruncate = (nSrcPage+ratio-1)/ratio; + if( nDestTruncate==(int)PENDING_BYTE_PAGE(p->pDest->pBt) ){ + nDestTruncate--; + } + }else{ + nDestTruncate = nSrcPage * (pgszSrc/pgszDest); + } + assert( nDestTruncate>0 ); + + if( pgszSrc<pgszDest ){ + /* If the source page-size is smaller than the destination page-size, + ** two extra things may need to happen: + ** + ** * The destination may need to be truncated, and + ** + ** * Data stored on the pages immediately following the + ** pending-byte page in the source database may need to be + ** copied into the destination database. + */ + const i64 iSize = (i64)pgszSrc * (i64)nSrcPage; + sqlite3_file * const pFile = sqlite3PagerFile(pDestPager); + Pgno iPg; + int nDstPage; + i64 iOff; + i64 iEnd; + + assert( pFile ); + assert( nDestTruncate==0 + || (i64)nDestTruncate*(i64)pgszDest >= iSize || ( + nDestTruncate==(int)(PENDING_BYTE_PAGE(p->pDest->pBt)-1) + && iSize>=PENDING_BYTE && iSize<=PENDING_BYTE+pgszDest + )); + + /* This block ensures that all data required to recreate the original + ** database has been stored in the journal for pDestPager and the + ** journal synced to disk. So at this point we may safely modify + ** the database file in any way, knowing that if a power failure + ** occurs, the original database will be reconstructed from the + ** journal file. */ + sqlite3PagerPagecount(pDestPager, &nDstPage); + for(iPg=nDestTruncate; rc==SQLITE_OK && iPg<=(Pgno)nDstPage; iPg++){ + if( iPg!=PENDING_BYTE_PAGE(p->pDest->pBt) ){ + DbPage *pPg; + rc = sqlite3PagerGet(pDestPager, iPg, &pPg, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerWrite(pPg); + sqlite3PagerUnref(pPg); + } + } + } + if( rc==SQLITE_OK ){ + rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 1); + } + + /* Write the extra pages and truncate the database file as required */ + iEnd = MIN(PENDING_BYTE + pgszDest, iSize); + for( + iOff=PENDING_BYTE+pgszSrc; + rc==SQLITE_OK && iOff<iEnd; + iOff+=pgszSrc + ){ + PgHdr *pSrcPg = 0; + const Pgno iSrcPg = (Pgno)((iOff/pgszSrc)+1); + rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg, 0); + if( rc==SQLITE_OK ){ + u8 *zData = sqlite3PagerGetData(pSrcPg); + rc = sqlite3OsWrite(pFile, zData, pgszSrc, iOff); + } + sqlite3PagerUnref(pSrcPg); + } + if( rc==SQLITE_OK ){ + rc = backupTruncateFile(pFile, iSize); + } + + /* Sync the database file to disk. */ + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSync(pDestPager, 0); + } + }else{ + sqlite3PagerTruncateImage(pDestPager, nDestTruncate); + rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 0); + } + + /* Finish committing the transaction to the destination database. */ + if( SQLITE_OK==rc + && SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest, 0)) + ){ + rc = SQLITE_DONE; + } + } + } + + /* If bCloseTrans is true, then this function opened a read transaction + ** on the source database. Close the read transaction here. There is + ** no need to check the return values of the btree methods here, as + ** "committing" a read-only transaction cannot fail. + */ + if( bCloseTrans ){ + TESTONLY( int rc2 ); + TESTONLY( rc2 = ) sqlite3BtreeCommitPhaseOne(p->pSrc, 0); + TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(p->pSrc, 0); + assert( rc2==SQLITE_OK ); + } + + if( rc==SQLITE_IOERR_NOMEM ){ + rc = SQLITE_NOMEM_BKPT; + } + p->rc = rc; + } + if( p->pDestDb ){ + sqlite3_mutex_leave(p->pDestDb->mutex); + } + sqlite3BtreeLeave(p->pSrc); + sqlite3_mutex_leave(p->pSrcDb->mutex); + return rc; +} + +/* +** Release all resources associated with an sqlite3_backup* handle. +*/ +SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){ + sqlite3_backup **pp; /* Ptr to head of pagers backup list */ + sqlite3 *pSrcDb; /* Source database connection */ + int rc; /* Value to return */ + + /* Enter the mutexes */ + if( p==0 ) return SQLITE_OK; + pSrcDb = p->pSrcDb; + sqlite3_mutex_enter(pSrcDb->mutex); + sqlite3BtreeEnter(p->pSrc); + if( p->pDestDb ){ + sqlite3_mutex_enter(p->pDestDb->mutex); + } + + /* Detach this backup from the source pager. */ + if( p->pDestDb ){ + p->pSrc->nBackup--; + } + if( p->isAttached ){ + pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc)); + assert( pp!=0 ); + while( *pp!=p ){ + pp = &(*pp)->pNext; + assert( pp!=0 ); + } + *pp = p->pNext; + } + + /* If a transaction is still open on the Btree, roll it back. */ + sqlite3BtreeRollback(p->pDest, SQLITE_OK, 0); + + /* Set the error code of the destination database handle. */ + rc = (p->rc==SQLITE_DONE) ? SQLITE_OK : p->rc; + if( p->pDestDb ){ + sqlite3Error(p->pDestDb, rc); + + /* Exit the mutexes and free the backup context structure. */ + sqlite3LeaveMutexAndCloseZombie(p->pDestDb); + } + sqlite3BtreeLeave(p->pSrc); + if( p->pDestDb ){ + /* EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a + ** call to sqlite3_backup_init() and is destroyed by a call to + ** sqlite3_backup_finish(). */ + sqlite3_free(p); + } + sqlite3LeaveMutexAndCloseZombie(pSrcDb); + return rc; +} + +/* +** Return the number of pages still to be backed up as of the most recent +** call to sqlite3_backup_step(). +*/ +SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return p->nRemaining; +} + +/* +** Return the total number of pages in the source database as of the most +** recent call to sqlite3_backup_step(). +*/ +SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return p->nPagecount; +} + +/* +** This function is called after the contents of page iPage of the +** source database have been modified. If page iPage has already been +** copied into the destination database, then the data written to the +** destination is now invalidated. The destination copy of iPage needs +** to be updated with the new data before the backup operation is +** complete. +** +** It is assumed that the mutex associated with the BtShared object +** corresponding to the source database is held when this function is +** called. +*/ +static SQLITE_NOINLINE void backupUpdate( + sqlite3_backup *p, + Pgno iPage, + const u8 *aData +){ + assert( p!=0 ); + do{ + assert( sqlite3_mutex_held(p->pSrc->pBt->mutex) ); + if( !isFatalError(p->rc) && iPage<p->iNext ){ + /* The backup process p has already copied page iPage. But now it + ** has been modified by a transaction on the source pager. Copy + ** the new data into the backup. + */ + int rc; + assert( p->pDestDb ); + sqlite3_mutex_enter(p->pDestDb->mutex); + rc = backupOnePage(p, iPage, aData, 1); + sqlite3_mutex_leave(p->pDestDb->mutex); + assert( rc!=SQLITE_BUSY && rc!=SQLITE_LOCKED ); + if( rc!=SQLITE_OK ){ + p->rc = rc; + } + } + }while( (p = p->pNext)!=0 ); +} +SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *pBackup, Pgno iPage, const u8 *aData){ + if( pBackup ) backupUpdate(pBackup, iPage, aData); +} + +/* +** Restart the backup process. This is called when the pager layer +** detects that the database has been modified by an external database +** connection. In this case there is no way of knowing which of the +** pages that have been copied into the destination database are still +** valid and which are not, so the entire process needs to be restarted. +** +** It is assumed that the mutex associated with the BtShared object +** corresponding to the source database is held when this function is +** called. +*/ +SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *pBackup){ + sqlite3_backup *p; /* Iterator variable */ + for(p=pBackup; p; p=p->pNext){ + assert( sqlite3_mutex_held(p->pSrc->pBt->mutex) ); + p->iNext = 1; + } +} + +#ifndef SQLITE_OMIT_VACUUM +/* +** Copy the complete content of pBtFrom into pBtTo. A transaction +** must be active for both files. +** +** The size of file pTo may be reduced by this operation. If anything +** goes wrong, the transaction on pTo is rolled back. If successful, the +** transaction is committed before returning. +*/ +SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){ + int rc; + sqlite3_file *pFd; /* File descriptor for database pTo */ + sqlite3_backup b; + sqlite3BtreeEnter(pTo); + sqlite3BtreeEnter(pFrom); + + assert( sqlite3BtreeTxnState(pTo)==SQLITE_TXN_WRITE ); + pFd = sqlite3PagerFile(sqlite3BtreePager(pTo)); + if( pFd->pMethods ){ + i64 nByte = sqlite3BtreeGetPageSize(pFrom)*(i64)sqlite3BtreeLastPage(pFrom); + rc = sqlite3OsFileControl(pFd, SQLITE_FCNTL_OVERWRITE, &nByte); + if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; + if( rc ) goto copy_finished; + } + + /* Set up an sqlite3_backup object. sqlite3_backup.pDestDb must be set + ** to 0. This is used by the implementations of sqlite3_backup_step() + ** and sqlite3_backup_finish() to detect that they are being called + ** from this function, not directly by the user. + */ + memset(&b, 0, sizeof(b)); + b.pSrcDb = pFrom->db; + b.pSrc = pFrom; + b.pDest = pTo; + b.iNext = 1; + + /* 0x7FFFFFFF is the hard limit for the number of pages in a database + ** file. By passing this as the number of pages to copy to + ** sqlite3_backup_step(), we can guarantee that the copy finishes + ** within a single call (unless an error occurs). The assert() statement + ** checks this assumption - (p->rc) should be set to either SQLITE_DONE + ** or an error code. */ + sqlite3_backup_step(&b, 0x7FFFFFFF); + assert( b.rc!=SQLITE_OK ); + + rc = sqlite3_backup_finish(&b); + if( rc==SQLITE_OK ){ + pTo->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED; + }else{ + sqlite3PagerClearCache(sqlite3BtreePager(b.pDest)); + } + + assert( sqlite3BtreeTxnState(pTo)!=SQLITE_TXN_WRITE ); +copy_finished: + sqlite3BtreeLeave(pFrom); + sqlite3BtreeLeave(pTo); + return rc; +} +#endif /* SQLITE_OMIT_VACUUM */ + +/************** End of backup.c **********************************************/ +/************** Begin file vdbemem.c *****************************************/ +/* +** 2004 May 26 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains code use to manipulate "Mem" structure. A "Mem" +** stores a single value in the VDBE. Mem is an opaque structure visible +** only within the VDBE. Interface routines refer to a Mem using the +** name sqlite_value +*/ +/* #include "sqliteInt.h" */ +/* #include "vdbeInt.h" */ + +/* True if X is a power of two. 0 is considered a power of two here. +** In other words, return true if X has at most one bit set. +*/ +#define ISPOWEROF2(X) (((X)&((X)-1))==0) + +#ifdef SQLITE_DEBUG +/* +** Check invariants on a Mem object. +** +** This routine is intended for use inside of assert() statements, like +** this: assert( sqlite3VdbeCheckMemInvariants(pMem) ); +*/ +SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ + /* If MEM_Dyn is set then Mem.xDel!=0. + ** Mem.xDel might not be initialized if MEM_Dyn is clear. + */ + assert( (p->flags & MEM_Dyn)==0 || p->xDel!=0 ); + + /* MEM_Dyn may only be set if Mem.szMalloc==0. In this way we + ** ensure that if Mem.szMalloc>0 then it is safe to do + ** Mem.z = Mem.zMalloc without having to check Mem.flags&MEM_Dyn. + ** That saves a few cycles in inner loops. */ + assert( (p->flags & MEM_Dyn)==0 || p->szMalloc==0 ); + + /* Cannot have more than one of MEM_Int, MEM_Real, or MEM_IntReal */ + assert( ISPOWEROF2(p->flags & (MEM_Int|MEM_Real|MEM_IntReal)) ); + + if( p->flags & MEM_Null ){ + /* Cannot be both MEM_Null and some other type */ + assert( (p->flags & (MEM_Int|MEM_Real|MEM_Str|MEM_Blob|MEM_Agg))==0 ); + + /* If MEM_Null is set, then either the value is a pure NULL (the usual + ** case) or it is a pointer set using sqlite3_bind_pointer() or + ** sqlite3_result_pointer(). If a pointer, then MEM_Term must also be + ** set. + */ + if( (p->flags & (MEM_Term|MEM_Subtype))==(MEM_Term|MEM_Subtype) ){ + /* This is a pointer type. There may be a flag to indicate what to + ** do with the pointer. */ + assert( ((p->flags&MEM_Dyn)!=0 ? 1 : 0) + + ((p->flags&MEM_Ephem)!=0 ? 1 : 0) + + ((p->flags&MEM_Static)!=0 ? 1 : 0) <= 1 ); + + /* No other bits set */ + assert( (p->flags & ~(MEM_Null|MEM_Term|MEM_Subtype|MEM_FromBind + |MEM_Dyn|MEM_Ephem|MEM_Static))==0 ); + }else{ + /* A pure NULL might have other flags, such as MEM_Static, MEM_Dyn, + ** MEM_Ephem, MEM_Cleared, or MEM_Subtype */ + } + }else{ + /* The MEM_Cleared bit is only allowed on NULLs */ + assert( (p->flags & MEM_Cleared)==0 ); + } + + /* The szMalloc field holds the correct memory allocation size */ + assert( p->szMalloc==0 + || (p->flags==MEM_Undefined + && p->szMalloc<=sqlite3DbMallocSize(p->db,p->zMalloc)) + || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc)); + + /* If p holds a string or blob, the Mem.z must point to exactly + ** one of the following: + ** + ** (1) Memory in Mem.zMalloc and managed by the Mem object + ** (2) Memory to be freed using Mem.xDel + ** (3) An ephemeral string or blob + ** (4) A static string or blob + */ + if( (p->flags & (MEM_Str|MEM_Blob)) && p->n>0 ){ + assert( + ((p->szMalloc>0 && p->z==p->zMalloc)? 1 : 0) + + ((p->flags&MEM_Dyn)!=0 ? 1 : 0) + + ((p->flags&MEM_Ephem)!=0 ? 1 : 0) + + ((p->flags&MEM_Static)!=0 ? 1 : 0) == 1 + ); + } + return 1; +} +#endif + +/* +** Render a Mem object which is one of MEM_Int, MEM_Real, or MEM_IntReal +** into a buffer. +*/ +static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ + StrAccum acc; + assert( p->flags & (MEM_Int|MEM_Real|MEM_IntReal) ); + assert( sz>22 ); + if( p->flags & MEM_Int ){ +#if GCC_VERSION>=7000000 + /* Work-around for GCC bug + ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96270 */ + i64 x; + assert( (p->flags&MEM_Int)*2==sizeof(x) ); + memcpy(&x, (char*)&p->u, (p->flags&MEM_Int)*2); + p->n = sqlite3Int64ToText(x, zBuf); +#else + p->n = sqlite3Int64ToText(p->u.i, zBuf); +#endif + }else{ + sqlite3StrAccumInit(&acc, 0, zBuf, sz, 0); + sqlite3_str_appendf(&acc, "%!.15g", + (p->flags & MEM_IntReal)!=0 ? (double)p->u.i : p->u.r); + assert( acc.zText==zBuf && acc.mxAlloc<=0 ); + zBuf[acc.nChar] = 0; /* Fast version of sqlite3StrAccumFinish(&acc) */ + p->n = acc.nChar; + } +} + +#ifdef SQLITE_DEBUG +/* +** Validity checks on pMem. pMem holds a string. +** +** (1) Check that string value of pMem agrees with its integer or real value. +** (2) Check that the string is correctly zero terminated +** +** A single int or real value always converts to the same strings. But +** many different strings can be converted into the same int or real. +** If a table contains a numeric value and an index is based on the +** corresponding string value, then it is important that the string be +** derived from the numeric value, not the other way around, to ensure +** that the index and table are consistent. See ticket +** https://www.sqlite.org/src/info/343634942dd54ab (2018-01-31) for +** an example. +** +** This routine looks at pMem to verify that if it has both a numeric +** representation and a string representation then the string rep has +** been derived from the numeric and not the other way around. It returns +** true if everything is ok and false if there is a problem. +** +** This routine is for use inside of assert() statements only. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemValidStrRep(Mem *p){ + Mem tmp; + char zBuf[100]; + char *z; + int i, j, incr; + if( (p->flags & MEM_Str)==0 ) return 1; + if( p->db && p->db->mallocFailed ) return 1; + if( p->flags & MEM_Term ){ + /* Insure that the string is properly zero-terminated. Pay particular + ** attention to the case where p->n is odd */ + if( p->szMalloc>0 && p->z==p->zMalloc ){ + assert( p->enc==SQLITE_UTF8 || p->szMalloc >= ((p->n+1)&~1)+2 ); + assert( p->enc!=SQLITE_UTF8 || p->szMalloc >= p->n+1 ); + } + assert( p->z[p->n]==0 ); + assert( p->enc==SQLITE_UTF8 || p->z[(p->n+1)&~1]==0 ); + assert( p->enc==SQLITE_UTF8 || p->z[((p->n+1)&~1)+1]==0 ); + } + if( (p->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ) return 1; + memcpy(&tmp, p, sizeof(tmp)); + vdbeMemRenderNum(sizeof(zBuf), zBuf, &tmp); + z = p->z; + i = j = 0; + incr = 1; + if( p->enc!=SQLITE_UTF8 ){ + incr = 2; + if( p->enc==SQLITE_UTF16BE ) z++; + } + while( zBuf[j] ){ + if( zBuf[j++]!=z[i] ) return 0; + i += incr; + } + return 1; +} +#endif /* SQLITE_DEBUG */ + +/* +** If pMem is an object with a valid string representation, this routine +** ensures the internal encoding for the string representation is +** 'desiredEnc', one of SQLITE_UTF8, SQLITE_UTF16LE or SQLITE_UTF16BE. +** +** If pMem is not a string object, or the encoding of the string +** representation is already stored using the requested encoding, then this +** routine is a no-op. +** +** SQLITE_OK is returned if the conversion is successful (or not required). +** SQLITE_NOMEM may be returned if a malloc() fails during conversion +** between formats. +*/ +SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){ +#ifndef SQLITE_OMIT_UTF16 + int rc; +#endif + assert( pMem!=0 ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + assert( desiredEnc==SQLITE_UTF8 || desiredEnc==SQLITE_UTF16LE + || desiredEnc==SQLITE_UTF16BE ); + if( !(pMem->flags&MEM_Str) ){ + pMem->enc = desiredEnc; + return SQLITE_OK; + } + if( pMem->enc==desiredEnc ){ + return SQLITE_OK; + } + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); +#ifdef SQLITE_OMIT_UTF16 + return SQLITE_ERROR; +#else + + /* MemTranslate() may return SQLITE_OK or SQLITE_NOMEM. If NOMEM is returned, + ** then the encoding of the value may not have changed. + */ + rc = sqlite3VdbeMemTranslate(pMem, (u8)desiredEnc); + assert(rc==SQLITE_OK || rc==SQLITE_NOMEM); + assert(rc==SQLITE_OK || pMem->enc!=desiredEnc); + assert(rc==SQLITE_NOMEM || pMem->enc==desiredEnc); + return rc; +#endif +} + +/* +** Make sure pMem->z points to a writable allocation of at least n bytes. +** +** If the bPreserve argument is true, then copy of the content of +** pMem->z into the new allocation. pMem must be either a string or +** blob if bPreserve is true. If bPreserve is false, any prior content +** in pMem->z is discarded. +*/ +SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPreserve){ + assert( sqlite3VdbeCheckMemInvariants(pMem) ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + testcase( pMem->db==0 ); + + /* If the bPreserve flag is set to true, then the memory cell must already + ** contain a valid string or blob value. */ + assert( bPreserve==0 || pMem->flags&(MEM_Blob|MEM_Str) ); + testcase( bPreserve && pMem->z==0 ); + + assert( pMem->szMalloc==0 + || (pMem->flags==MEM_Undefined + && pMem->szMalloc<=sqlite3DbMallocSize(pMem->db,pMem->zMalloc)) + || pMem->szMalloc==sqlite3DbMallocSize(pMem->db,pMem->zMalloc)); + if( pMem->szMalloc>0 && bPreserve && pMem->z==pMem->zMalloc ){ + if( pMem->db ){ + pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n); + }else{ + pMem->zMalloc = sqlite3Realloc(pMem->z, n); + if( pMem->zMalloc==0 ) sqlite3_free(pMem->z); + pMem->z = pMem->zMalloc; + } + bPreserve = 0; + }else{ + if( pMem->szMalloc>0 ) sqlite3DbFreeNN(pMem->db, pMem->zMalloc); + pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, n); + } + if( pMem->zMalloc==0 ){ + sqlite3VdbeMemSetNull(pMem); + pMem->z = 0; + pMem->szMalloc = 0; + return SQLITE_NOMEM_BKPT; + }else{ + pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); + } + + if( bPreserve && pMem->z ){ + assert( pMem->z!=pMem->zMalloc ); + memcpy(pMem->zMalloc, pMem->z, pMem->n); + } + if( (pMem->flags&MEM_Dyn)!=0 ){ + assert( pMem->xDel!=0 && pMem->xDel!=SQLITE_DYNAMIC ); + pMem->xDel((void *)(pMem->z)); + } + + pMem->z = pMem->zMalloc; + pMem->flags &= ~(MEM_Dyn|MEM_Ephem|MEM_Static); + return SQLITE_OK; +} + +/* +** Change the pMem->zMalloc allocation to be at least szNew bytes. +** If pMem->zMalloc already meets or exceeds the requested size, this +** routine is a no-op. +** +** Any prior string or blob content in the pMem object may be discarded. +** The pMem->xDel destructor is called, if it exists. Though MEM_Str +** and MEM_Blob values may be discarded, MEM_Int, MEM_Real, MEM_IntReal, +** and MEM_Null values are preserved. +** +** Return SQLITE_OK on success or an error code (probably SQLITE_NOMEM) +** if unable to complete the resizing. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){ + assert( CORRUPT_DB || szNew>0 ); + assert( (pMem->flags & MEM_Dyn)==0 || pMem->szMalloc==0 ); + if( pMem->szMalloc<szNew ){ + return sqlite3VdbeMemGrow(pMem, szNew, 0); + } + assert( (pMem->flags & MEM_Dyn)==0 ); + pMem->z = pMem->zMalloc; + pMem->flags &= (MEM_Null|MEM_Int|MEM_Real|MEM_IntReal); + return SQLITE_OK; +} + +/* +** If pMem is already a string, detect if it is a zero-terminated +** string, or make it into one if possible, and mark it as such. +** +** This is an optimization. Correct operation continues even if +** this routine is a no-op. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ + if( (pMem->flags & (MEM_Str|MEM_Term|MEM_Ephem|MEM_Static))!=MEM_Str ){ + /* pMem must be a string, and it cannot be an ephemeral or static string */ + return; + } + if( pMem->enc!=SQLITE_UTF8 ) return; + if( NEVER(pMem->z==0) ) return; + if( pMem->flags & MEM_Dyn ){ + if( pMem->xDel==sqlite3_free + && sqlite3_msize(pMem->z) >= (u64)(pMem->n+1) + ){ + pMem->z[pMem->n] = 0; + pMem->flags |= MEM_Term; + return; + } + if( pMem->xDel==(void(*)(void*))sqlite3RCStrUnref ){ + /* Blindly assume that all RCStr objects are zero-terminated */ + pMem->flags |= MEM_Term; + return; + } + }else if( pMem->szMalloc >= pMem->n+1 ){ + pMem->z[pMem->n] = 0; + pMem->flags |= MEM_Term; + return; + } +} + +/* +** It is already known that pMem contains an unterminated string. +** Add the zero terminator. +** +** Three bytes of zero are added. In this way, there is guaranteed +** to be a double-zero byte at an even byte boundary in order to +** terminate a UTF16 string, even if the initial size of the buffer +** is an odd number of bytes. +*/ +static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){ + if( sqlite3VdbeMemGrow(pMem, pMem->n+3, 1) ){ + return SQLITE_NOMEM_BKPT; + } + pMem->z[pMem->n] = 0; + pMem->z[pMem->n+1] = 0; + pMem->z[pMem->n+2] = 0; + pMem->flags |= MEM_Term; + return SQLITE_OK; +} + +/* +** Change pMem so that its MEM_Str or MEM_Blob value is stored in +** MEM.zMalloc, where it can be safely written. +** +** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){ + assert( pMem!=0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + if( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ){ + if( ExpandBlob(pMem) ) return SQLITE_NOMEM; + if( pMem->szMalloc==0 || pMem->z!=pMem->zMalloc ){ + int rc = vdbeMemAddTerminator(pMem); + if( rc ) return rc; + } + } + pMem->flags &= ~MEM_Ephem; +#ifdef SQLITE_DEBUG + pMem->pScopyFrom = 0; +#endif + + return SQLITE_OK; +} + +/* +** If the given Mem* has a zero-filled tail, turn it into an ordinary +** blob stored in dynamically allocated space. +*/ +#ifndef SQLITE_OMIT_INCRBLOB +SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){ + int nByte; + assert( pMem!=0 ); + assert( pMem->flags & MEM_Zero ); + assert( (pMem->flags&MEM_Blob)!=0 || MemNullNochng(pMem) ); + testcase( sqlite3_value_nochange(pMem) ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + + /* Set nByte to the number of bytes required to store the expanded blob. */ + nByte = pMem->n + pMem->u.nZero; + if( nByte<=0 ){ + if( (pMem->flags & MEM_Blob)==0 ) return SQLITE_OK; + nByte = 1; + } + if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){ + return SQLITE_NOMEM_BKPT; + } + assert( pMem->z!=0 ); + assert( sqlite3DbMallocSize(pMem->db,pMem->z) >= nByte ); + + memset(&pMem->z[pMem->n], 0, pMem->u.nZero); + pMem->n += pMem->u.nZero; + pMem->flags &= ~(MEM_Zero|MEM_Term); + return SQLITE_OK; +} +#endif + +/* +** Make sure the given Mem is \u0000 terminated. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){ + assert( pMem!=0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + testcase( (pMem->flags & (MEM_Term|MEM_Str))==(MEM_Term|MEM_Str) ); + testcase( (pMem->flags & (MEM_Term|MEM_Str))==0 ); + if( (pMem->flags & (MEM_Term|MEM_Str))!=MEM_Str ){ + return SQLITE_OK; /* Nothing to do */ + }else{ + return vdbeMemAddTerminator(pMem); + } +} + +/* +** Add MEM_Str to the set of representations for the given Mem. This +** routine is only called if pMem is a number of some kind, not a NULL +** or a BLOB. +** +** Existing representations MEM_Int, MEM_Real, or MEM_IntReal are invalidated +** if bForce is true but are retained if bForce is false. +** +** A MEM_Null value will never be passed to this function. This function is +** used for converting values to text for returning to the user (i.e. via +** sqlite3_value_text()), or for ensuring that values to be used as btree +** keys are strings. In the former case a NULL pointer is returned the +** user and the latter is an internal programming error. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ + const int nByte = 32; + + assert( pMem!=0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( !(pMem->flags&MEM_Zero) ); + assert( !(pMem->flags&(MEM_Str|MEM_Blob)) ); + assert( pMem->flags&(MEM_Int|MEM_Real|MEM_IntReal) ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + assert( EIGHT_BYTE_ALIGNMENT(pMem) ); + + + if( sqlite3VdbeMemClearAndResize(pMem, nByte) ){ + pMem->enc = 0; + return SQLITE_NOMEM_BKPT; + } + + vdbeMemRenderNum(nByte, pMem->z, pMem); + assert( pMem->z!=0 ); + assert( pMem->n==(int)sqlite3Strlen30NN(pMem->z) ); + pMem->enc = SQLITE_UTF8; + pMem->flags |= MEM_Str|MEM_Term; + if( bForce ) pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); + sqlite3VdbeChangeEncoding(pMem, enc); + return SQLITE_OK; +} + +/* +** Memory cell pMem contains the context of an aggregate function. +** This routine calls the finalize method for that function. The +** result of the aggregate is stored back into pMem. +** +** Return SQLITE_ERROR if the finalizer reports an error. SQLITE_OK +** otherwise. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ + sqlite3_context ctx; + Mem t; + assert( pFunc!=0 ); + assert( pMem!=0 ); + assert( pMem->db!=0 ); + assert( pFunc->xFinalize!=0 ); + assert( (pMem->flags & MEM_Null)!=0 || pFunc==pMem->u.pDef ); + assert( sqlite3_mutex_held(pMem->db->mutex) ); + memset(&ctx, 0, sizeof(ctx)); + memset(&t, 0, sizeof(t)); + t.flags = MEM_Null; + t.db = pMem->db; + ctx.pOut = &t; + ctx.pMem = pMem; + ctx.pFunc = pFunc; + ctx.enc = ENC(t.db); + pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */ + assert( (pMem->flags & MEM_Dyn)==0 ); + if( pMem->szMalloc>0 ) sqlite3DbFreeNN(pMem->db, pMem->zMalloc); + memcpy(pMem, &t, sizeof(t)); + return ctx.isError; +} + +/* +** Memory cell pAccum contains the context of an aggregate function. +** This routine calls the xValue method for that function and stores +** the results in memory cell pMem. +** +** SQLITE_ERROR is returned if xValue() reports an error. SQLITE_OK +** otherwise. +*/ +#ifndef SQLITE_OMIT_WINDOWFUNC +SQLITE_PRIVATE int sqlite3VdbeMemAggValue(Mem *pAccum, Mem *pOut, FuncDef *pFunc){ + sqlite3_context ctx; + assert( pFunc!=0 ); + assert( pFunc->xValue!=0 ); + assert( (pAccum->flags & MEM_Null)!=0 || pFunc==pAccum->u.pDef ); + assert( pAccum->db!=0 ); + assert( sqlite3_mutex_held(pAccum->db->mutex) ); + memset(&ctx, 0, sizeof(ctx)); + sqlite3VdbeMemSetNull(pOut); + ctx.pOut = pOut; + ctx.pMem = pAccum; + ctx.pFunc = pFunc; + ctx.enc = ENC(pAccum->db); + pFunc->xValue(&ctx); + return ctx.isError; +} +#endif /* SQLITE_OMIT_WINDOWFUNC */ + +/* +** If the memory cell contains a value that must be freed by +** invoking the external callback in Mem.xDel, then this routine +** will free that value. It also sets Mem.flags to MEM_Null. +** +** This is a helper routine for sqlite3VdbeMemSetNull() and +** for sqlite3VdbeMemRelease(). Use those other routines as the +** entry point for releasing Mem resources. +*/ +static SQLITE_NOINLINE void vdbeMemClearExternAndSetNull(Mem *p){ + assert( p->db==0 || sqlite3_mutex_held(p->db->mutex) ); + assert( VdbeMemDynamic(p) ); + if( p->flags&MEM_Agg ){ + sqlite3VdbeMemFinalize(p, p->u.pDef); + assert( (p->flags & MEM_Agg)==0 ); + testcase( p->flags & MEM_Dyn ); + } + if( p->flags&MEM_Dyn ){ + assert( p->xDel!=SQLITE_DYNAMIC && p->xDel!=0 ); + p->xDel((void *)p->z); + } + p->flags = MEM_Null; +} + +/* +** Release memory held by the Mem p, both external memory cleared +** by p->xDel and memory in p->zMalloc. +** +** This is a helper routine invoked by sqlite3VdbeMemRelease() in +** the unusual case where there really is memory in p that needs +** to be freed. +*/ +static SQLITE_NOINLINE void vdbeMemClear(Mem *p){ + if( VdbeMemDynamic(p) ){ + vdbeMemClearExternAndSetNull(p); + } + if( p->szMalloc ){ + sqlite3DbFreeNN(p->db, p->zMalloc); + p->szMalloc = 0; + } + p->z = 0; +} + +/* +** Release any memory resources held by the Mem. Both the memory that is +** free by Mem.xDel and the Mem.zMalloc allocation are freed. +** +** Use this routine prior to clean up prior to abandoning a Mem, or to +** reset a Mem back to its minimum memory utilization. +** +** Use sqlite3VdbeMemSetNull() to release just the Mem.xDel space +** prior to inserting new content into the Mem. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p){ + assert( sqlite3VdbeCheckMemInvariants(p) ); + if( VdbeMemDynamic(p) || p->szMalloc ){ + vdbeMemClear(p); + } +} + +/* Like sqlite3VdbeMemRelease() but faster for cases where we +** know in advance that the Mem is not MEM_Dyn or MEM_Agg. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemReleaseMalloc(Mem *p){ + assert( !VdbeMemDynamic(p) ); + if( p->szMalloc ) vdbeMemClear(p); +} + +/* +** Return some kind of integer value which is the best we can do +** at representing the value that *pMem describes as an integer. +** If pMem is an integer, then the value is exact. If pMem is +** a floating-point then the value returned is the integer part. +** If pMem is a string or blob, then we make an attempt to convert +** it into an integer and return that. If pMem represents an +** an SQL-NULL value, return 0. +** +** If pMem represents a string value, its encoding might be changed. +*/ +static SQLITE_NOINLINE i64 memIntValue(const Mem *pMem){ + i64 value = 0; + sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc); + return value; +} +SQLITE_PRIVATE i64 sqlite3VdbeIntValue(const Mem *pMem){ + int flags; + assert( pMem!=0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( EIGHT_BYTE_ALIGNMENT(pMem) ); + flags = pMem->flags; + if( flags & (MEM_Int|MEM_IntReal) ){ + testcase( flags & MEM_IntReal ); + return pMem->u.i; + }else if( flags & MEM_Real ){ + return sqlite3RealToI64(pMem->u.r); + }else if( (flags & (MEM_Str|MEM_Blob))!=0 && pMem->z!=0 ){ + return memIntValue(pMem); + }else{ + return 0; + } +} + +/* +** Return the best representation of pMem that we can get into a +** double. If pMem is already a double or an integer, return its +** value. If it is a string or blob, try to convert it to a double. +** If it is a NULL, return 0.0. +*/ +static SQLITE_NOINLINE double memRealValue(Mem *pMem){ + /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ + double val = (double)0; + sqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc); + return val; +} +SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){ + assert( pMem!=0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( EIGHT_BYTE_ALIGNMENT(pMem) ); + if( pMem->flags & MEM_Real ){ + return pMem->u.r; + }else if( pMem->flags & (MEM_Int|MEM_IntReal) ){ + testcase( pMem->flags & MEM_IntReal ); + return (double)pMem->u.i; + }else if( pMem->flags & (MEM_Str|MEM_Blob) ){ + return memRealValue(pMem); + }else{ + /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ + return (double)0; + } +} + +/* +** Return 1 if pMem represents true, and return 0 if pMem represents false. +** Return the value ifNull if pMem is NULL. +*/ +SQLITE_PRIVATE int sqlite3VdbeBooleanValue(Mem *pMem, int ifNull){ + testcase( pMem->flags & MEM_IntReal ); + if( pMem->flags & (MEM_Int|MEM_IntReal) ) return pMem->u.i!=0; + if( pMem->flags & MEM_Null ) return ifNull; + return sqlite3VdbeRealValue(pMem)!=0.0; +} + +/* +** The MEM structure is already a MEM_Real or MEM_IntReal. Try to +** make it a MEM_Int if we can. +*/ +SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem *pMem){ + assert( pMem!=0 ); + assert( pMem->flags & (MEM_Real|MEM_IntReal) ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( EIGHT_BYTE_ALIGNMENT(pMem) ); + + if( pMem->flags & MEM_IntReal ){ + MemSetTypeFlag(pMem, MEM_Int); + }else{ + i64 ix = sqlite3RealToI64(pMem->u.r); + + /* Only mark the value as an integer if + ** + ** (1) the round-trip conversion real->int->real is a no-op, and + ** (2) The integer is neither the largest nor the smallest + ** possible integer (ticket #3922) + ** + ** The second and third terms in the following conditional enforces + ** the second condition under the assumption that addition overflow causes + ** values to wrap around. + */ + if( pMem->u.r==ix && ix>SMALLEST_INT64 && ix<LARGEST_INT64 ){ + pMem->u.i = ix; + MemSetTypeFlag(pMem, MEM_Int); + } + } +} + +/* +** Convert pMem to type integer. Invalidate any prior representations. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem *pMem){ + assert( pMem!=0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + assert( EIGHT_BYTE_ALIGNMENT(pMem) ); + + pMem->u.i = sqlite3VdbeIntValue(pMem); + MemSetTypeFlag(pMem, MEM_Int); + return SQLITE_OK; +} + +/* +** Convert pMem so that it is of type MEM_Real. +** Invalidate any prior representations. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem *pMem){ + assert( pMem!=0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( EIGHT_BYTE_ALIGNMENT(pMem) ); + + pMem->u.r = sqlite3VdbeRealValue(pMem); + MemSetTypeFlag(pMem, MEM_Real); + return SQLITE_OK; +} + +/* Compare a floating point value to an integer. Return true if the two +** values are the same within the precision of the floating point value. +** +** This function assumes that i was obtained by assignment from r1. +** +** For some versions of GCC on 32-bit machines, if you do the more obvious +** comparison of "r1==(double)i" you sometimes get an answer of false even +** though the r1 and (double)i values are bit-for-bit the same. +*/ +SQLITE_PRIVATE int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){ + double r2 = (double)i; + return r1==0.0 + || (memcmp(&r1, &r2, sizeof(r1))==0 + && i >= -2251799813685248LL && i < 2251799813685248LL); +} + +/* Convert a floating point value to its closest integer. Do so in +** a way that avoids 'outside the range of representable values' warnings +** from UBSAN. +*/ +SQLITE_PRIVATE i64 sqlite3RealToI64(double r){ + if( r<-9223372036854774784.0 ) return SMALLEST_INT64; + if( r>+9223372036854774784.0 ) return LARGEST_INT64; + return (i64)r; +} + +/* +** Convert pMem so that it has type MEM_Real or MEM_Int. +** Invalidate any prior representations. +** +** Every effort is made to force the conversion, even if the input +** is a string that does not look completely like a number. Convert +** as much of the string as we can and ignore the rest. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem *pMem){ + assert( pMem!=0 ); + testcase( pMem->flags & MEM_Int ); + testcase( pMem->flags & MEM_Real ); + testcase( pMem->flags & MEM_IntReal ); + testcase( pMem->flags & MEM_Null ); + if( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null))==0 ){ + int rc; + sqlite3_int64 ix; + assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); + if( ((rc==0 || rc==1) && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1) + || sqlite3RealSameAsInt(pMem->u.r, (ix = sqlite3RealToI64(pMem->u.r))) + ){ + pMem->u.i = ix; + MemSetTypeFlag(pMem, MEM_Int); + }else{ + MemSetTypeFlag(pMem, MEM_Real); + } + } + assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null))!=0 ); + pMem->flags &= ~(MEM_Str|MEM_Blob|MEM_Zero); + return SQLITE_OK; +} + +/* +** Cast the datatype of the value in pMem according to the affinity +** "aff". Casting is different from applying affinity in that a cast +** is forced. In other words, the value is converted into the desired +** affinity even if that results in loss of data. This routine is +** used (for example) to implement the SQL "cast()" operator. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ + if( pMem->flags & MEM_Null ) return SQLITE_OK; + switch( aff ){ + case SQLITE_AFF_BLOB: { /* Really a cast to BLOB */ + if( (pMem->flags & MEM_Blob)==0 ){ + sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding); + assert( pMem->flags & MEM_Str || pMem->db->mallocFailed ); + if( pMem->flags & MEM_Str ) MemSetTypeFlag(pMem, MEM_Blob); + }else{ + pMem->flags &= ~(MEM_TypeMask&~MEM_Blob); + } + break; + } + case SQLITE_AFF_NUMERIC: { + sqlite3VdbeMemNumerify(pMem); + break; + } + case SQLITE_AFF_INTEGER: { + sqlite3VdbeMemIntegerify(pMem); + break; + } + case SQLITE_AFF_REAL: { + sqlite3VdbeMemRealify(pMem); + break; + } + default: { + int rc; + assert( aff==SQLITE_AFF_TEXT ); + assert( MEM_Str==(MEM_Blob>>3) ); + pMem->flags |= (pMem->flags&MEM_Blob)>>3; + sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding); + assert( pMem->flags & MEM_Str || pMem->db->mallocFailed ); + pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal|MEM_Blob|MEM_Zero); + if( encoding!=SQLITE_UTF8 ) pMem->n &= ~1; + rc = sqlite3VdbeChangeEncoding(pMem, encoding); + if( rc ) return rc; + sqlite3VdbeMemZeroTerminateIfAble(pMem); + } + } + return SQLITE_OK; +} + +/* +** Initialize bulk memory to be a consistent Mem object. +** +** The minimum amount of initialization feasible is performed. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemInit(Mem *pMem, sqlite3 *db, u16 flags){ + assert( (flags & ~MEM_TypeMask)==0 ); + pMem->flags = flags; + pMem->db = db; + pMem->szMalloc = 0; +} + + +/* +** Delete any previous value and set the value stored in *pMem to NULL. +** +** This routine calls the Mem.xDel destructor to dispose of values that +** require the destructor. But it preserves the Mem.zMalloc memory allocation. +** To free all resources, use sqlite3VdbeMemRelease(), which both calls this +** routine to invoke the destructor and deallocates Mem.zMalloc. +** +** Use this routine to reset the Mem prior to insert a new value. +** +** Use sqlite3VdbeMemRelease() to complete erase the Mem prior to abandoning it. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem *pMem){ + if( VdbeMemDynamic(pMem) ){ + vdbeMemClearExternAndSetNull(pMem); + }else{ + pMem->flags = MEM_Null; + } +} +SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value *p){ + sqlite3VdbeMemSetNull((Mem*)p); +} + +/* +** Delete any previous value and set the value to be a BLOB of length +** n containing all zeros. +*/ +#ifndef SQLITE_OMIT_INCRBLOB +SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ + sqlite3VdbeMemRelease(pMem); + pMem->flags = MEM_Blob|MEM_Zero; + pMem->n = 0; + if( n<0 ) n = 0; + pMem->u.nZero = n; + pMem->enc = SQLITE_UTF8; + pMem->z = 0; +} +#else +SQLITE_PRIVATE int sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ + int nByte = n>0?n:1; + if( sqlite3VdbeMemGrow(pMem, nByte, 0) ){ + return SQLITE_NOMEM_BKPT; + } + assert( pMem->z!=0 ); + assert( sqlite3DbMallocSize(pMem->db, pMem->z)>=nByte ); + memset(pMem->z, 0, nByte); + pMem->n = n>0?n:0; + pMem->flags = MEM_Blob; + pMem->enc = SQLITE_UTF8; + return SQLITE_OK; +} +#endif + +/* +** The pMem is known to contain content that needs to be destroyed prior +** to a value change. So invoke the destructor, then set the value to +** a 64-bit integer. +*/ +static SQLITE_NOINLINE void vdbeReleaseAndSetInt64(Mem *pMem, i64 val){ + sqlite3VdbeMemSetNull(pMem); + pMem->u.i = val; + pMem->flags = MEM_Int; +} + +/* +** Delete any previous value and set the value stored in *pMem to val, +** manifest type INTEGER. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem *pMem, i64 val){ + if( VdbeMemDynamic(pMem) ){ + vdbeReleaseAndSetInt64(pMem, val); + }else{ + pMem->u.i = val; + pMem->flags = MEM_Int; + } +} + +/* A no-op destructor */ +SQLITE_PRIVATE void sqlite3NoopDestructor(void *p){ UNUSED_PARAMETER(p); } + +/* +** Set the value stored in *pMem should already be a NULL. +** Also store a pointer to go with it. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemSetPointer( + Mem *pMem, + void *pPtr, + const char *zPType, + void (*xDestructor)(void*) +){ + assert( pMem->flags==MEM_Null ); + vdbeMemClear(pMem); + pMem->u.zPType = zPType ? zPType : ""; + pMem->z = pPtr; + pMem->flags = MEM_Null|MEM_Dyn|MEM_Subtype|MEM_Term; + pMem->eSubtype = 'p'; + pMem->xDel = xDestructor ? xDestructor : sqlite3NoopDestructor; +} + +#ifndef SQLITE_OMIT_FLOATING_POINT +/* +** Delete any previous value and set the value stored in *pMem to val, +** manifest type REAL. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem *pMem, double val){ + sqlite3VdbeMemSetNull(pMem); + if( !sqlite3IsNaN(val) ){ + pMem->u.r = val; + pMem->flags = MEM_Real; + } +} +#endif + +#ifdef SQLITE_DEBUG +/* +** Return true if the Mem holds a RowSet object. This routine is intended +** for use inside of assert() statements. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemIsRowSet(const Mem *pMem){ + return (pMem->flags&(MEM_Blob|MEM_Dyn))==(MEM_Blob|MEM_Dyn) + && pMem->xDel==sqlite3RowSetDelete; +} +#endif + +/* +** Delete any previous value and set the value of pMem to be an +** empty boolean index. +** +** Return SQLITE_OK on success and SQLITE_NOMEM if a memory allocation +** error occurs. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemSetRowSet(Mem *pMem){ + sqlite3 *db = pMem->db; + RowSet *p; + assert( db!=0 ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + sqlite3VdbeMemRelease(pMem); + p = sqlite3RowSetInit(db); + if( p==0 ) return SQLITE_NOMEM; + pMem->z = (char*)p; + pMem->flags = MEM_Blob|MEM_Dyn; + pMem->xDel = sqlite3RowSetDelete; + return SQLITE_OK; +} + +/* +** Return true if the Mem object contains a TEXT or BLOB that is +** too large - whose size exceeds SQLITE_MAX_LENGTH. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem *p){ + assert( p->db!=0 ); + if( p->flags & (MEM_Str|MEM_Blob) ){ + int n = p->n; + if( p->flags & MEM_Zero ){ + n += p->u.nZero; + } + return n>p->db->aLimit[SQLITE_LIMIT_LENGTH]; + } + return 0; +} + +#ifdef SQLITE_DEBUG +/* +** This routine prepares a memory cell for modification by breaking +** its link to a shallow copy and by marking any current shallow +** copies of this cell as invalid. +** +** This is used for testing and debugging only - to help ensure that shallow +** copies (created by OP_SCopy) are not misused. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe *pVdbe, Mem *pMem){ + int i; + Mem *pX; + for(i=1, pX=pVdbe->aMem+1; i<pVdbe->nMem; i++, pX++){ + if( pX->pScopyFrom==pMem ){ + u16 mFlags; + if( pVdbe->db->flags & SQLITE_VdbeTrace ){ + sqlite3DebugPrintf("Invalidate R[%d] due to change in R[%d]\n", + (int)(pX - pVdbe->aMem), (int)(pMem - pVdbe->aMem)); + } + /* If pX is marked as a shallow copy of pMem, then try to verify that + ** no significant changes have been made to pX since the OP_SCopy. + ** A significant change would indicated a missed call to this + ** function for pX. Minor changes, such as adding or removing a + ** dual type, are allowed, as long as the underlying value is the + ** same. */ + mFlags = pMem->flags & pX->flags & pX->mScopyFlags; + assert( (mFlags&(MEM_Int|MEM_IntReal))==0 || pMem->u.i==pX->u.i ); + + /* pMem is the register that is changing. But also mark pX as + ** undefined so that we can quickly detect the shallow-copy error */ + pX->flags = MEM_Undefined; + pX->pScopyFrom = 0; + } + } + pMem->pScopyFrom = 0; +} +#endif /* SQLITE_DEBUG */ + +/* +** Make an shallow copy of pFrom into pTo. Prior contents of +** pTo are freed. The pFrom->z field is not duplicated. If +** pFrom->z is used, then pTo->z points to the same thing as pFrom->z +** and flags gets srcType (either MEM_Ephem or MEM_Static). +*/ +static SQLITE_NOINLINE void vdbeClrCopy(Mem *pTo, const Mem *pFrom, int eType){ + vdbeMemClearExternAndSetNull(pTo); + assert( !VdbeMemDynamic(pTo) ); + sqlite3VdbeMemShallowCopy(pTo, pFrom, eType); +} +SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem *pTo, const Mem *pFrom, int srcType){ + assert( !sqlite3VdbeMemIsRowSet(pFrom) ); + assert( pTo->db==pFrom->db ); + if( VdbeMemDynamic(pTo) ){ vdbeClrCopy(pTo,pFrom,srcType); return; } + memcpy(pTo, pFrom, MEMCELLSIZE); + if( (pFrom->flags&MEM_Static)==0 ){ + pTo->flags &= ~(MEM_Dyn|MEM_Static|MEM_Ephem); + assert( srcType==MEM_Ephem || srcType==MEM_Static ); + pTo->flags |= srcType; + } +} + +/* +** Make a full copy of pFrom into pTo. Prior contents of pTo are +** freed before the copy is made. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem *pTo, const Mem *pFrom){ + int rc = SQLITE_OK; + + assert( !sqlite3VdbeMemIsRowSet(pFrom) ); + if( VdbeMemDynamic(pTo) ) vdbeMemClearExternAndSetNull(pTo); + memcpy(pTo, pFrom, MEMCELLSIZE); + pTo->flags &= ~MEM_Dyn; + if( pTo->flags&(MEM_Str|MEM_Blob) ){ + if( 0==(pFrom->flags&MEM_Static) ){ + pTo->flags |= MEM_Ephem; + rc = sqlite3VdbeMemMakeWriteable(pTo); + } + } + + return rc; +} + +/* +** Transfer the contents of pFrom to pTo. Any existing value in pTo is +** freed. If pFrom contains ephemeral data, a copy is made. +** +** pFrom contains an SQL NULL when this routine returns. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){ + assert( pFrom->db==0 || sqlite3_mutex_held(pFrom->db->mutex) ); + assert( pTo->db==0 || sqlite3_mutex_held(pTo->db->mutex) ); + assert( pFrom->db==0 || pTo->db==0 || pFrom->db==pTo->db ); + + sqlite3VdbeMemRelease(pTo); + memcpy(pTo, pFrom, sizeof(Mem)); + pFrom->flags = MEM_Null; + pFrom->szMalloc = 0; +} + +/* +** Change the value of a Mem to be a string or a BLOB. +** +** The memory management strategy depends on the value of the xDel +** parameter. If the value passed is SQLITE_TRANSIENT, then the +** string is copied into a (possibly existing) buffer managed by the +** Mem structure. Otherwise, any existing buffer is freed and the +** pointer copied. +** +** If the string is too large (if it exceeds the SQLITE_LIMIT_LENGTH +** size limit) then no memory allocation occurs. If the string can be +** stored without allocating memory, then it is. If a memory allocation +** is required to store the string, then value of pMem is unchanged. In +** either case, SQLITE_TOOBIG is returned. +** +** The "enc" parameter is the text encoding for the string, or zero +** to store a blob. +** +** If n is negative, then the string consists of all bytes up to but +** excluding the first zero character. The n parameter must be +** non-negative for blobs. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemSetStr( + Mem *pMem, /* Memory cell to set to string value */ + const char *z, /* String pointer */ + i64 n, /* Bytes in string, or negative */ + u8 enc, /* Encoding of z. 0 for BLOBs */ + void (*xDel)(void*) /* Destructor function */ +){ + i64 nByte = n; /* New value for pMem->n */ + int iLimit; /* Maximum allowed string or blob size */ + u16 flags; /* New value for pMem->flags */ + + assert( pMem!=0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + assert( enc!=0 || n>=0 ); + + /* If z is a NULL pointer, set pMem to contain an SQL NULL. */ + if( !z ){ + sqlite3VdbeMemSetNull(pMem); + return SQLITE_OK; + } + + if( pMem->db ){ + iLimit = pMem->db->aLimit[SQLITE_LIMIT_LENGTH]; + }else{ + iLimit = SQLITE_MAX_LENGTH; + } + if( nByte<0 ){ + assert( enc!=0 ); + if( enc==SQLITE_UTF8 ){ + nByte = strlen(z); + }else{ + for(nByte=0; nByte<=iLimit && (z[nByte] | z[nByte+1]); nByte+=2){} + } + flags= MEM_Str|MEM_Term; + }else if( enc==0 ){ + flags = MEM_Blob; + enc = SQLITE_UTF8; + }else{ + flags = MEM_Str; + } + if( nByte>iLimit ){ + if( xDel && xDel!=SQLITE_TRANSIENT ){ + if( xDel==SQLITE_DYNAMIC ){ + sqlite3DbFree(pMem->db, (void*)z); + }else{ + xDel((void*)z); + } + } + sqlite3VdbeMemSetNull(pMem); + return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); + } + + /* The following block sets the new values of Mem.z and Mem.xDel. It + ** also sets a flag in local variable "flags" to indicate the memory + ** management (one of MEM_Dyn or MEM_Static). + */ + if( xDel==SQLITE_TRANSIENT ){ + i64 nAlloc = nByte; + if( flags&MEM_Term ){ + nAlloc += (enc==SQLITE_UTF8?1:2); + } + testcase( nAlloc==0 ); + testcase( nAlloc==31 ); + testcase( nAlloc==32 ); + if( sqlite3VdbeMemClearAndResize(pMem, (int)MAX(nAlloc,32)) ){ + return SQLITE_NOMEM_BKPT; + } + memcpy(pMem->z, z, nAlloc); + }else{ + sqlite3VdbeMemRelease(pMem); + pMem->z = (char *)z; + if( xDel==SQLITE_DYNAMIC ){ + pMem->zMalloc = pMem->z; + pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); + }else{ + pMem->xDel = xDel; + flags |= ((xDel==SQLITE_STATIC)?MEM_Static:MEM_Dyn); + } + } + + pMem->n = (int)(nByte & 0x7fffffff); + pMem->flags = flags; + pMem->enc = enc; + +#ifndef SQLITE_OMIT_UTF16 + if( enc>SQLITE_UTF8 && sqlite3VdbeMemHandleBom(pMem) ){ + return SQLITE_NOMEM_BKPT; + } +#endif + + + return SQLITE_OK; +} + +/* +** Move data out of a btree key or data field and into a Mem structure. +** The data is payload from the entry that pCur is currently pointing +** to. offset and amt determine what portion of the data or key to retrieve. +** The result is written into the pMem element. +** +** The pMem object must have been initialized. This routine will use +** pMem->zMalloc to hold the content from the btree, if possible. New +** pMem->zMalloc space will be allocated if necessary. The calling routine +** is responsible for making sure that the pMem object is eventually +** destroyed. +** +** If this routine fails for any reason (malloc returns NULL or unable +** to read from the disk) then the pMem is left in an inconsistent state. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemFromBtree( + BtCursor *pCur, /* Cursor pointing at record to retrieve. */ + u32 offset, /* Offset from the start of data to return bytes from. */ + u32 amt, /* Number of bytes to return. */ + Mem *pMem /* OUT: Return data in this Mem structure. */ +){ + int rc; + pMem->flags = MEM_Null; + if( sqlite3BtreeMaxRecordSize(pCur)<offset+amt ){ + return SQLITE_CORRUPT_BKPT; + } + if( SQLITE_OK==(rc = sqlite3VdbeMemClearAndResize(pMem, amt+1)) ){ + rc = sqlite3BtreePayload(pCur, offset, amt, pMem->z); + if( rc==SQLITE_OK ){ + pMem->z[amt] = 0; /* Overrun area used when reading malformed records */ + pMem->flags = MEM_Blob; + pMem->n = (int)amt; + }else{ + sqlite3VdbeMemRelease(pMem); + } + } + return rc; +} +SQLITE_PRIVATE int sqlite3VdbeMemFromBtreeZeroOffset( + BtCursor *pCur, /* Cursor pointing at record to retrieve. */ + u32 amt, /* Number of bytes to return. */ + Mem *pMem /* OUT: Return data in this Mem structure. */ +){ + u32 available = 0; /* Number of bytes available on the local btree page */ + int rc = SQLITE_OK; /* Return code */ + + assert( sqlite3BtreeCursorIsValid(pCur) ); + assert( !VdbeMemDynamic(pMem) ); + + /* Note: the calls to BtreeKeyFetch() and DataFetch() below assert() + ** that both the BtShared and database handle mutexes are held. */ + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + pMem->z = (char *)sqlite3BtreePayloadFetch(pCur, &available); + assert( pMem->z!=0 ); + + if( amt<=available ){ + pMem->flags = MEM_Blob|MEM_Ephem; + pMem->n = (int)amt; + }else{ + rc = sqlite3VdbeMemFromBtree(pCur, 0, amt, pMem); + } + + return rc; +} + +/* +** The pVal argument is known to be a value other than NULL. +** Convert it into a string with encoding enc and return a pointer +** to a zero-terminated version of that string. +*/ +static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){ + assert( pVal!=0 ); + assert( pVal->db==0 || sqlite3_mutex_held(pVal->db->mutex) ); + assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) ); + assert( !sqlite3VdbeMemIsRowSet(pVal) ); + assert( (pVal->flags & (MEM_Null))==0 ); + if( pVal->flags & (MEM_Blob|MEM_Str) ){ + if( ExpandBlob(pVal) ) return 0; + pVal->flags |= MEM_Str; + if( pVal->enc != (enc & ~SQLITE_UTF16_ALIGNED) ){ + sqlite3VdbeChangeEncoding(pVal, enc & ~SQLITE_UTF16_ALIGNED); + } + if( (enc & SQLITE_UTF16_ALIGNED)!=0 && 1==(1&SQLITE_PTR_TO_INT(pVal->z)) ){ + assert( (pVal->flags & (MEM_Ephem|MEM_Static))!=0 ); + if( sqlite3VdbeMemMakeWriteable(pVal)!=SQLITE_OK ){ + return 0; + } + } + sqlite3VdbeMemNulTerminate(pVal); /* IMP: R-31275-44060 */ + }else{ + sqlite3VdbeMemStringify(pVal, enc, 0); + assert( 0==(1&SQLITE_PTR_TO_INT(pVal->z)) ); + } + assert(pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) || pVal->db==0 + || pVal->db->mallocFailed ); + if( pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) ){ + assert( sqlite3VdbeMemValidStrRep(pVal) ); + return pVal->z; + }else{ + return 0; + } +} + +/* This function is only available internally, it is not part of the +** external API. It works in a similar way to sqlite3_value_text(), +** except the data returned is in the encoding specified by the second +** parameter, which must be one of SQLITE_UTF16BE, SQLITE_UTF16LE or +** SQLITE_UTF8. +** +** (2006-02-16:) The enc value can be or-ed with SQLITE_UTF16_ALIGNED. +** If that is the case, then the result must be aligned on an even byte +** boundary. +*/ +SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){ + if( !pVal ) return 0; + assert( pVal->db==0 || sqlite3_mutex_held(pVal->db->mutex) ); + assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) ); + assert( !sqlite3VdbeMemIsRowSet(pVal) ); + if( (pVal->flags&(MEM_Str|MEM_Term))==(MEM_Str|MEM_Term) && pVal->enc==enc ){ + assert( sqlite3VdbeMemValidStrRep(pVal) ); + return pVal->z; + } + if( pVal->flags&MEM_Null ){ + return 0; + } + return valueToText(pVal, enc); +} + +/* Return true if sqlit3_value object pVal is a string or blob value +** that uses the destructor specified in the second argument. +** +** TODO: Maybe someday promote this interface into a published API so +** that third-party extensions can get access to it? +*/ +SQLITE_PRIVATE int sqlite3ValueIsOfClass(const sqlite3_value *pVal, void(*xFree)(void*)){ + if( ALWAYS(pVal!=0) + && ALWAYS((pVal->flags & (MEM_Str|MEM_Blob))!=0) + && (pVal->flags & MEM_Dyn)!=0 + && pVal->xDel==xFree + ){ + return 1; + }else{ + return 0; + } +} + +/* +** Create a new sqlite3_value object. +*/ +SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *db){ + Mem *p = sqlite3DbMallocZero(db, sizeof(*p)); + if( p ){ + p->flags = MEM_Null; + p->db = db; + } + return p; +} + +/* +** Context object passed by sqlite3Stat4ProbeSetValue() through to +** valueNew(). See comments above valueNew() for details. +*/ +struct ValueNewStat4Ctx { + Parse *pParse; + Index *pIdx; + UnpackedRecord **ppRec; + int iVal; +}; + +/* +** Allocate and return a pointer to a new sqlite3_value object. If +** the second argument to this function is NULL, the object is allocated +** by calling sqlite3ValueNew(). +** +** Otherwise, if the second argument is non-zero, then this function is +** being called indirectly by sqlite3Stat4ProbeSetValue(). If it has not +** already been allocated, allocate the UnpackedRecord structure that +** that function will return to its caller here. Then return a pointer to +** an sqlite3_value within the UnpackedRecord.a[] array. +*/ +static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){ +#ifdef SQLITE_ENABLE_STAT4 + if( p ){ + UnpackedRecord *pRec = p->ppRec[0]; + + if( pRec==0 ){ + Index *pIdx = p->pIdx; /* Index being probed */ + int nByte; /* Bytes of space to allocate */ + int i; /* Counter variable */ + int nCol = pIdx->nColumn; /* Number of index columns including rowid */ + + nByte = sizeof(Mem) * nCol + ROUND8(sizeof(UnpackedRecord)); + pRec = (UnpackedRecord*)sqlite3DbMallocZero(db, nByte); + if( pRec ){ + pRec->pKeyInfo = sqlite3KeyInfoOfIndex(p->pParse, pIdx); + if( pRec->pKeyInfo ){ + assert( pRec->pKeyInfo->nAllField==nCol ); + assert( pRec->pKeyInfo->enc==ENC(db) ); + pRec->aMem = (Mem *)((u8*)pRec + ROUND8(sizeof(UnpackedRecord))); + for(i=0; i<nCol; i++){ + pRec->aMem[i].flags = MEM_Null; + pRec->aMem[i].db = db; + } + }else{ + sqlite3DbFreeNN(db, pRec); + pRec = 0; + } + } + if( pRec==0 ) return 0; + p->ppRec[0] = pRec; + } + + pRec->nField = p->iVal+1; + sqlite3VdbeMemSetNull(&pRec->aMem[p->iVal]); + return &pRec->aMem[p->iVal]; + } +#else + UNUSED_PARAMETER(p); +#endif /* defined(SQLITE_ENABLE_STAT4) */ + return sqlite3ValueNew(db); +} + +/* +** The expression object indicated by the second argument is guaranteed +** to be a scalar SQL function. If +** +** * all function arguments are SQL literals, +** * one of the SQLITE_FUNC_CONSTANT or _SLOCHNG function flags is set, and +** * the SQLITE_FUNC_NEEDCOLL function flag is not set, +** +** then this routine attempts to invoke the SQL function. Assuming no +** error occurs, output parameter (*ppVal) is set to point to a value +** object containing the result before returning SQLITE_OK. +** +** Affinity aff is applied to the result of the function before returning. +** If the result is a text value, the sqlite3_value object uses encoding +** enc. +** +** If the conditions above are not met, this function returns SQLITE_OK +** and sets (*ppVal) to NULL. Or, if an error occurs, (*ppVal) is set to +** NULL and an SQLite error code returned. +*/ +#ifdef SQLITE_ENABLE_STAT4 +static int valueFromFunction( + sqlite3 *db, /* The database connection */ + const Expr *p, /* The expression to evaluate */ + u8 enc, /* Encoding to use */ + u8 aff, /* Affinity to use */ + sqlite3_value **ppVal, /* Write the new value here */ + struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */ +){ + sqlite3_context ctx; /* Context object for function invocation */ + sqlite3_value **apVal = 0; /* Function arguments */ + int nVal = 0; /* Size of apVal[] array */ + FuncDef *pFunc = 0; /* Function definition */ + sqlite3_value *pVal = 0; /* New value */ + int rc = SQLITE_OK; /* Return code */ + ExprList *pList = 0; /* Function arguments */ + int i; /* Iterator variable */ + + assert( pCtx!=0 ); + assert( (p->flags & EP_TokenOnly)==0 ); + assert( ExprUseXList(p) ); + pList = p->x.pList; + if( pList ) nVal = pList->nExpr; + assert( !ExprHasProperty(p, EP_IntValue) ); + pFunc = sqlite3FindFunction(db, p->u.zToken, nVal, enc, 0); +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + if( pFunc==0 ) return SQLITE_OK; +#endif + assert( pFunc ); + if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 + || (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) + ){ + return SQLITE_OK; + } + + if( pList ){ + apVal = (sqlite3_value**)sqlite3DbMallocZero(db, sizeof(apVal[0]) * nVal); + if( apVal==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto value_from_function_out; + } + for(i=0; i<nVal; i++){ + rc = sqlite3ValueFromExpr(db, pList->a[i].pExpr, enc, aff, &apVal[i]); + if( apVal[i]==0 || rc!=SQLITE_OK ) goto value_from_function_out; + } + } + + pVal = valueNew(db, pCtx); + if( pVal==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto value_from_function_out; + } + + memset(&ctx, 0, sizeof(ctx)); + ctx.pOut = pVal; + ctx.pFunc = pFunc; + ctx.enc = ENC(db); + pFunc->xSFunc(&ctx, nVal, apVal); + if( ctx.isError ){ + rc = ctx.isError; + sqlite3ErrorMsg(pCtx->pParse, "%s", sqlite3_value_text(pVal)); + }else{ + sqlite3ValueApplyAffinity(pVal, aff, SQLITE_UTF8); + assert( rc==SQLITE_OK ); + rc = sqlite3VdbeChangeEncoding(pVal, enc); + if( NEVER(rc==SQLITE_OK && sqlite3VdbeMemTooBig(pVal)) ){ + rc = SQLITE_TOOBIG; + pCtx->pParse->nErr++; + } + } + + value_from_function_out: + if( rc!=SQLITE_OK ){ + pVal = 0; + pCtx->pParse->rc = rc; + } + if( apVal ){ + for(i=0; i<nVal; i++){ + sqlite3ValueFree(apVal[i]); + } + sqlite3DbFreeNN(db, apVal); + } + + *ppVal = pVal; + return rc; +} +#else +# define valueFromFunction(a,b,c,d,e,f) SQLITE_OK +#endif /* defined(SQLITE_ENABLE_STAT4) */ + +/* +** Extract a value from the supplied expression in the manner described +** above sqlite3ValueFromExpr(). Allocate the sqlite3_value object +** using valueNew(). +** +** If pCtx is NULL and an error occurs after the sqlite3_value object +** has been allocated, it is freed before returning. Or, if pCtx is not +** NULL, it is assumed that the caller will free any allocated object +** in all cases. +*/ +static int valueFromExpr( + sqlite3 *db, /* The database connection */ + const Expr *pExpr, /* The expression to evaluate */ + u8 enc, /* Encoding to use */ + u8 affinity, /* Affinity to use */ + sqlite3_value **ppVal, /* Write the new value here */ + struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */ +){ + int op; + char *zVal = 0; + sqlite3_value *pVal = 0; + int negInt = 1; + const char *zNeg = ""; + int rc = SQLITE_OK; + + assert( pExpr!=0 ); + while( (op = pExpr->op)==TK_UPLUS || op==TK_SPAN ) pExpr = pExpr->pLeft; + if( op==TK_REGISTER ) op = pExpr->op2; + + /* Compressed expressions only appear when parsing the DEFAULT clause + ** on a table column definition, and hence only when pCtx==0. This + ** check ensures that an EP_TokenOnly expression is never passed down + ** into valueFromFunction(). */ + assert( (pExpr->flags & EP_TokenOnly)==0 || pCtx==0 ); + + if( op==TK_CAST ){ + u8 aff; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + aff = sqlite3AffinityType(pExpr->u.zToken,0); + rc = valueFromExpr(db, pExpr->pLeft, enc, aff, ppVal, pCtx); + testcase( rc!=SQLITE_OK ); + if( *ppVal ){ +#ifdef SQLITE_ENABLE_STAT4 + rc = ExpandBlob(*ppVal); +#else + /* zero-blobs only come from functions, not literal values. And + ** functions are only processed under STAT4 */ + assert( (ppVal[0][0].flags & MEM_Zero)==0 ); +#endif + sqlite3VdbeMemCast(*ppVal, aff, enc); + sqlite3ValueApplyAffinity(*ppVal, affinity, enc); + } + return rc; + } + + /* Handle negative integers in a single step. This is needed in the + ** case when the value is -9223372036854775808. + */ + if( op==TK_UMINUS + && (pExpr->pLeft->op==TK_INTEGER || pExpr->pLeft->op==TK_FLOAT) ){ + pExpr = pExpr->pLeft; + op = pExpr->op; + negInt = -1; + zNeg = "-"; + } + + if( op==TK_STRING || op==TK_FLOAT || op==TK_INTEGER ){ + pVal = valueNew(db, pCtx); + if( pVal==0 ) goto no_mem; + if( ExprHasProperty(pExpr, EP_IntValue) ){ + sqlite3VdbeMemSetInt64(pVal, (i64)pExpr->u.iValue*negInt); + }else{ + zVal = sqlite3MPrintf(db, "%s%s", zNeg, pExpr->u.zToken); + if( zVal==0 ) goto no_mem; + sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, SQLITE_DYNAMIC); + } + if( (op==TK_INTEGER || op==TK_FLOAT ) && affinity==SQLITE_AFF_BLOB ){ + sqlite3ValueApplyAffinity(pVal, SQLITE_AFF_NUMERIC, SQLITE_UTF8); + }else{ + sqlite3ValueApplyAffinity(pVal, affinity, SQLITE_UTF8); + } + assert( (pVal->flags & MEM_IntReal)==0 ); + if( pVal->flags & (MEM_Int|MEM_IntReal|MEM_Real) ){ + testcase( pVal->flags & MEM_Int ); + testcase( pVal->flags & MEM_Real ); + pVal->flags &= ~MEM_Str; + } + if( enc!=SQLITE_UTF8 ){ + rc = sqlite3VdbeChangeEncoding(pVal, enc); + } + }else if( op==TK_UMINUS ) { + /* This branch happens for multiple negative signs. Ex: -(-5) */ + if( SQLITE_OK==valueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal,pCtx) + && pVal!=0 + ){ + sqlite3VdbeMemNumerify(pVal); + if( pVal->flags & MEM_Real ){ + pVal->u.r = -pVal->u.r; + }else if( pVal->u.i==SMALLEST_INT64 ){ +#ifndef SQLITE_OMIT_FLOATING_POINT + pVal->u.r = -(double)SMALLEST_INT64; +#else + pVal->u.r = LARGEST_INT64; +#endif + MemSetTypeFlag(pVal, MEM_Real); + }else{ + pVal->u.i = -pVal->u.i; + } + sqlite3ValueApplyAffinity(pVal, affinity, enc); + } + }else if( op==TK_NULL ){ + pVal = valueNew(db, pCtx); + if( pVal==0 ) goto no_mem; + sqlite3VdbeMemSetNull(pVal); + } +#ifndef SQLITE_OMIT_BLOB_LITERAL + else if( op==TK_BLOB ){ + int nVal; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + assert( pExpr->u.zToken[0]=='x' || pExpr->u.zToken[0]=='X' ); + assert( pExpr->u.zToken[1]=='\'' ); + pVal = valueNew(db, pCtx); + if( !pVal ) goto no_mem; + zVal = &pExpr->u.zToken[2]; + nVal = sqlite3Strlen30(zVal)-1; + assert( zVal[nVal]=='\'' ); + sqlite3VdbeMemSetStr(pVal, sqlite3HexToBlob(db, zVal, nVal), nVal/2, + 0, SQLITE_DYNAMIC); + } +#endif +#ifdef SQLITE_ENABLE_STAT4 + else if( op==TK_FUNCTION && pCtx!=0 ){ + rc = valueFromFunction(db, pExpr, enc, affinity, &pVal, pCtx); + } +#endif + else if( op==TK_TRUEFALSE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + pVal = valueNew(db, pCtx); + if( pVal ){ + pVal->flags = MEM_Int; + pVal->u.i = pExpr->u.zToken[4]==0; + } + } + + *ppVal = pVal; + return rc; + +no_mem: +#ifdef SQLITE_ENABLE_STAT4 + if( pCtx==0 || NEVER(pCtx->pParse->nErr==0) ) +#endif + sqlite3OomFault(db); + sqlite3DbFree(db, zVal); + assert( *ppVal==0 ); +#ifdef SQLITE_ENABLE_STAT4 + if( pCtx==0 ) sqlite3ValueFree(pVal); +#else + assert( pCtx==0 ); sqlite3ValueFree(pVal); +#endif + return SQLITE_NOMEM_BKPT; +} + +/* +** Create a new sqlite3_value object, containing the value of pExpr. +** +** This only works for very simple expressions that consist of one constant +** token (i.e. "5", "5.1", "'a string'"). If the expression can +** be converted directly into a value, then the value is allocated and +** a pointer written to *ppVal. The caller is responsible for deallocating +** the value by passing it to sqlite3ValueFree() later on. If the expression +** cannot be converted to a value, then *ppVal is set to NULL. +*/ +SQLITE_PRIVATE int sqlite3ValueFromExpr( + sqlite3 *db, /* The database connection */ + const Expr *pExpr, /* The expression to evaluate */ + u8 enc, /* Encoding to use */ + u8 affinity, /* Affinity to use */ + sqlite3_value **ppVal /* Write the new value here */ +){ + return pExpr ? valueFromExpr(db, pExpr, enc, affinity, ppVal, 0) : 0; +} + +#ifdef SQLITE_ENABLE_STAT4 +/* +** Attempt to extract a value from pExpr and use it to construct *ppVal. +** +** If pAlloc is not NULL, then an UnpackedRecord object is created for +** pAlloc if one does not exist and the new value is added to the +** UnpackedRecord object. +** +** A value is extracted in the following cases: +** +** * (pExpr==0). In this case the value is assumed to be an SQL NULL, +** +** * The expression is a bound variable, and this is a reprepare, or +** +** * The expression is a literal value. +** +** On success, *ppVal is made to point to the extracted value. The caller +** is responsible for ensuring that the value is eventually freed. +*/ +static int stat4ValueFromExpr( + Parse *pParse, /* Parse context */ + Expr *pExpr, /* The expression to extract a value from */ + u8 affinity, /* Affinity to use */ + struct ValueNewStat4Ctx *pAlloc,/* How to allocate space. Or NULL */ + sqlite3_value **ppVal /* OUT: New value object (or NULL) */ +){ + int rc = SQLITE_OK; + sqlite3_value *pVal = 0; + sqlite3 *db = pParse->db; + + /* Skip over any TK_COLLATE nodes */ + pExpr = sqlite3ExprSkipCollate(pExpr); + + assert( pExpr==0 || pExpr->op!=TK_REGISTER || pExpr->op2!=TK_VARIABLE ); + if( !pExpr ){ + pVal = valueNew(db, pAlloc); + if( pVal ){ + sqlite3VdbeMemSetNull((Mem*)pVal); + } + }else if( pExpr->op==TK_VARIABLE && (db->flags & SQLITE_EnableQPSG)==0 ){ + Vdbe *v; + int iBindVar = pExpr->iColumn; + sqlite3VdbeSetVarmask(pParse->pVdbe, iBindVar); + if( (v = pParse->pReprepare)!=0 ){ + pVal = valueNew(db, pAlloc); + if( pVal ){ + rc = sqlite3VdbeMemCopy((Mem*)pVal, &v->aVar[iBindVar-1]); + sqlite3ValueApplyAffinity(pVal, affinity, ENC(db)); + pVal->db = pParse->db; + } + } + }else{ + rc = valueFromExpr(db, pExpr, ENC(db), affinity, &pVal, pAlloc); + } + + assert( pVal==0 || pVal->db==db ); + *ppVal = pVal; + return rc; +} + +/* +** This function is used to allocate and populate UnpackedRecord +** structures intended to be compared against sample index keys stored +** in the sqlite_stat4 table. +** +** A single call to this function populates zero or more fields of the +** record starting with field iVal (fields are numbered from left to +** right starting with 0). A single field is populated if: +** +** * (pExpr==0). In this case the value is assumed to be an SQL NULL, +** +** * The expression is a bound variable, and this is a reprepare, or +** +** * The sqlite3ValueFromExpr() function is able to extract a value +** from the expression (i.e. the expression is a literal value). +** +** Or, if pExpr is a TK_VECTOR, one field is populated for each of the +** vector components that match either of the two latter criteria listed +** above. +** +** Before any value is appended to the record, the affinity of the +** corresponding column within index pIdx is applied to it. Before +** this function returns, output parameter *pnExtract is set to the +** number of values appended to the record. +** +** When this function is called, *ppRec must either point to an object +** allocated by an earlier call to this function, or must be NULL. If it +** is NULL and a value can be successfully extracted, a new UnpackedRecord +** is allocated (and *ppRec set to point to it) before returning. +** +** Unless an error is encountered, SQLITE_OK is returned. It is not an +** error if a value cannot be extracted from pExpr. If an error does +** occur, an SQLite error code is returned. +*/ +SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue( + Parse *pParse, /* Parse context */ + Index *pIdx, /* Index being probed */ + UnpackedRecord **ppRec, /* IN/OUT: Probe record */ + Expr *pExpr, /* The expression to extract a value from */ + int nElem, /* Maximum number of values to append */ + int iVal, /* Array element to populate */ + int *pnExtract /* OUT: Values appended to the record */ +){ + int rc = SQLITE_OK; + int nExtract = 0; + + if( pExpr==0 || pExpr->op!=TK_SELECT ){ + int i; + struct ValueNewStat4Ctx alloc; + + alloc.pParse = pParse; + alloc.pIdx = pIdx; + alloc.ppRec = ppRec; + + for(i=0; i<nElem; i++){ + sqlite3_value *pVal = 0; + Expr *pElem = (pExpr ? sqlite3VectorFieldSubexpr(pExpr, i) : 0); + u8 aff = sqlite3IndexColumnAffinity(pParse->db, pIdx, iVal+i); + alloc.iVal = iVal+i; + rc = stat4ValueFromExpr(pParse, pElem, aff, &alloc, &pVal); + if( !pVal ) break; + nExtract++; + } + } + + *pnExtract = nExtract; + return rc; +} + +/* +** Attempt to extract a value from expression pExpr using the methods +** as described for sqlite3Stat4ProbeSetValue() above. +** +** If successful, set *ppVal to point to a new value object and return +** SQLITE_OK. If no value can be extracted, but no other error occurs +** (e.g. OOM), return SQLITE_OK and set *ppVal to NULL. Or, if an error +** does occur, return an SQLite error code. The final value of *ppVal +** is undefined in this case. +*/ +SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr( + Parse *pParse, /* Parse context */ + Expr *pExpr, /* The expression to extract a value from */ + u8 affinity, /* Affinity to use */ + sqlite3_value **ppVal /* OUT: New value object (or NULL) */ +){ + return stat4ValueFromExpr(pParse, pExpr, affinity, 0, ppVal); +} + +/* +** Extract the iCol-th column from the nRec-byte record in pRec. Write +** the column value into *ppVal. If *ppVal is initially NULL then a new +** sqlite3_value object is allocated. +** +** If *ppVal is initially NULL then the caller is responsible for +** ensuring that the value written into *ppVal is eventually freed. +*/ +SQLITE_PRIVATE int sqlite3Stat4Column( + sqlite3 *db, /* Database handle */ + const void *pRec, /* Pointer to buffer containing record */ + int nRec, /* Size of buffer pRec in bytes */ + int iCol, /* Column to extract */ + sqlite3_value **ppVal /* OUT: Extracted value */ +){ + u32 t = 0; /* a column type code */ + int nHdr; /* Size of the header in the record */ + int iHdr; /* Next unread header byte */ + int iField; /* Next unread data byte */ + int szField = 0; /* Size of the current data field */ + int i; /* Column index */ + u8 *a = (u8*)pRec; /* Typecast byte array */ + Mem *pMem = *ppVal; /* Write result into this Mem object */ + + assert( iCol>0 ); + iHdr = getVarint32(a, nHdr); + if( nHdr>nRec || iHdr>=nHdr ) return SQLITE_CORRUPT_BKPT; + iField = nHdr; + for(i=0; i<=iCol; i++){ + iHdr += getVarint32(&a[iHdr], t); + testcase( iHdr==nHdr ); + testcase( iHdr==nHdr+1 ); + if( iHdr>nHdr ) return SQLITE_CORRUPT_BKPT; + szField = sqlite3VdbeSerialTypeLen(t); + iField += szField; + } + testcase( iField==nRec ); + testcase( iField==nRec+1 ); + if( iField>nRec ) return SQLITE_CORRUPT_BKPT; + if( pMem==0 ){ + pMem = *ppVal = sqlite3ValueNew(db); + if( pMem==0 ) return SQLITE_NOMEM_BKPT; + } + sqlite3VdbeSerialGet(&a[iField-szField], t, pMem); + pMem->enc = ENC(db); + return SQLITE_OK; +} + +/* +** Unless it is NULL, the argument must be an UnpackedRecord object returned +** by an earlier call to sqlite3Stat4ProbeSetValue(). This call deletes +** the object. +*/ +SQLITE_PRIVATE void sqlite3Stat4ProbeFree(UnpackedRecord *pRec){ + if( pRec ){ + int i; + int nCol = pRec->pKeyInfo->nAllField; + Mem *aMem = pRec->aMem; + sqlite3 *db = aMem[0].db; + for(i=0; i<nCol; i++){ + sqlite3VdbeMemRelease(&aMem[i]); + } + sqlite3KeyInfoUnref(pRec->pKeyInfo); + sqlite3DbFreeNN(db, pRec); + } +} +#endif /* ifdef SQLITE_ENABLE_STAT4 */ + +/* +** Change the string value of an sqlite3_value object +*/ +SQLITE_PRIVATE void sqlite3ValueSetStr( + sqlite3_value *v, /* Value to be set */ + int n, /* Length of string z */ + const void *z, /* Text of the new string */ + u8 enc, /* Encoding to use */ + void (*xDel)(void*) /* Destructor for the string */ +){ + if( v ) sqlite3VdbeMemSetStr((Mem *)v, z, n, enc, xDel); +} + +/* +** Free an sqlite3_value object +*/ +SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value *v){ + if( !v ) return; + sqlite3VdbeMemRelease((Mem *)v); + sqlite3DbFreeNN(((Mem*)v)->db, v); +} + +/* +** The sqlite3ValueBytes() routine returns the number of bytes in the +** sqlite3_value object assuming that it uses the encoding "enc". +** The valueBytes() routine is a helper function. +*/ +static SQLITE_NOINLINE int valueBytes(sqlite3_value *pVal, u8 enc){ + return valueToText(pVal, enc)!=0 ? pVal->n : 0; +} +SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value *pVal, u8 enc){ + Mem *p = (Mem*)pVal; + assert( (p->flags & MEM_Null)==0 || (p->flags & (MEM_Str|MEM_Blob))==0 ); + if( (p->flags & MEM_Str)!=0 && pVal->enc==enc ){ + return p->n; + } + if( (p->flags & MEM_Str)!=0 && enc!=SQLITE_UTF8 && pVal->enc!=SQLITE_UTF8 ){ + return p->n; + } + if( (p->flags & MEM_Blob)!=0 ){ + if( p->flags & MEM_Zero ){ + return p->n + p->u.nZero; + }else{ + return p->n; + } + } + if( p->flags & MEM_Null ) return 0; + return valueBytes(pVal, enc); +} + +/************** End of vdbemem.c *********************************************/ +/************** Begin file vdbeaux.c *****************************************/ +/* +** 2003 September 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used for creating, destroying, and populating +** a VDBE (or an "sqlite3_stmt" as it is known to the outside world.) +*/ +/* #include "sqliteInt.h" */ +/* #include "vdbeInt.h" */ + +/* Forward references */ +static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef); +static void vdbeFreeOpArray(sqlite3 *, Op *, int); + +/* +** Create a new virtual database engine. +*/ +SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){ + sqlite3 *db = pParse->db; + Vdbe *p; + p = sqlite3DbMallocRawNN(db, sizeof(Vdbe) ); + if( p==0 ) return 0; + memset(&p->aOp, 0, sizeof(Vdbe)-offsetof(Vdbe,aOp)); + p->db = db; + if( db->pVdbe ){ + db->pVdbe->ppVPrev = &p->pVNext; + } + p->pVNext = db->pVdbe; + p->ppVPrev = &db->pVdbe; + db->pVdbe = p; + assert( p->eVdbeState==VDBE_INIT_STATE ); + p->pParse = pParse; + pParse->pVdbe = p; + assert( pParse->aLabel==0 ); + assert( pParse->nLabel==0 ); + assert( p->nOpAlloc==0 ); + assert( pParse->szOpAlloc==0 ); + sqlite3VdbeAddOp2(p, OP_Init, 0, 1); + return p; +} + +/* +** Return the Parse object that owns a Vdbe object. +*/ +SQLITE_PRIVATE Parse *sqlite3VdbeParser(Vdbe *p){ + return p->pParse; +} + +/* +** Change the error string stored in Vdbe.zErrMsg +*/ +SQLITE_PRIVATE void sqlite3VdbeError(Vdbe *p, const char *zFormat, ...){ + va_list ap; + sqlite3DbFree(p->db, p->zErrMsg); + va_start(ap, zFormat); + p->zErrMsg = sqlite3VMPrintf(p->db, zFormat, ap); + va_end(ap); +} + +/* +** Remember the SQL string for a prepared statement. +*/ +SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe *p, const char *z, int n, u8 prepFlags){ + if( p==0 ) return; + p->prepFlags = prepFlags; + if( (prepFlags & SQLITE_PREPARE_SAVESQL)==0 ){ + p->expmask = 0; + } + assert( p->zSql==0 ); + p->zSql = sqlite3DbStrNDup(p->db, z, n); +} + +#ifdef SQLITE_ENABLE_NORMALIZE +/* +** Add a new element to the Vdbe->pDblStr list. +*/ +SQLITE_PRIVATE void sqlite3VdbeAddDblquoteStr(sqlite3 *db, Vdbe *p, const char *z){ + if( p ){ + int n = sqlite3Strlen30(z); + DblquoteStr *pStr = sqlite3DbMallocRawNN(db, + sizeof(*pStr)+n+1-sizeof(pStr->z)); + if( pStr ){ + pStr->pNextStr = p->pDblStr; + p->pDblStr = pStr; + memcpy(pStr->z, z, n+1); + } + } +} +#endif + +#ifdef SQLITE_ENABLE_NORMALIZE +/* +** zId of length nId is a double-quoted identifier. Check to see if +** that identifier is really used as a string literal. +*/ +SQLITE_PRIVATE int sqlite3VdbeUsesDoubleQuotedString( + Vdbe *pVdbe, /* The prepared statement */ + const char *zId /* The double-quoted identifier, already dequoted */ +){ + DblquoteStr *pStr; + assert( zId!=0 ); + if( pVdbe->pDblStr==0 ) return 0; + for(pStr=pVdbe->pDblStr; pStr; pStr=pStr->pNextStr){ + if( strcmp(zId, pStr->z)==0 ) return 1; + } + return 0; +} +#endif + +/* +** Swap byte-code between two VDBE structures. +** +** This happens after pB was previously run and returned +** SQLITE_SCHEMA. The statement was then reprepared in pA. +** This routine transfers the new bytecode in pA over to pB +** so that pB can be run again. The old pB byte code is +** moved back to pA so that it will be cleaned up when pA is +** finalized. +*/ +SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){ + Vdbe tmp, *pTmp, **ppTmp; + char *zTmp; + assert( pA->db==pB->db ); + tmp = *pA; + *pA = *pB; + *pB = tmp; + pTmp = pA->pVNext; + pA->pVNext = pB->pVNext; + pB->pVNext = pTmp; + ppTmp = pA->ppVPrev; + pA->ppVPrev = pB->ppVPrev; + pB->ppVPrev = ppTmp; + zTmp = pA->zSql; + pA->zSql = pB->zSql; + pB->zSql = zTmp; +#ifdef SQLITE_ENABLE_NORMALIZE + zTmp = pA->zNormSql; + pA->zNormSql = pB->zNormSql; + pB->zNormSql = zTmp; +#endif + pB->expmask = pA->expmask; + pB->prepFlags = pA->prepFlags; + memcpy(pB->aCounter, pA->aCounter, sizeof(pB->aCounter)); + pB->aCounter[SQLITE_STMTSTATUS_REPREPARE]++; +} + +/* +** Resize the Vdbe.aOp array so that it is at least nOp elements larger +** than its current size. nOp is guaranteed to be less than or equal +** to 1024/sizeof(Op). +** +** If an out-of-memory error occurs while resizing the array, return +** SQLITE_NOMEM. In this case Vdbe.aOp and Vdbe.nOpAlloc remain +** unchanged (this is so that any opcodes already allocated can be +** correctly deallocated along with the rest of the Vdbe). +*/ +static int growOpArray(Vdbe *v, int nOp){ + VdbeOp *pNew; + Parse *p = v->pParse; + + /* The SQLITE_TEST_REALLOC_STRESS compile-time option is designed to force + ** more frequent reallocs and hence provide more opportunities for + ** simulated OOM faults. SQLITE_TEST_REALLOC_STRESS is generally used + ** during testing only. With SQLITE_TEST_REALLOC_STRESS grow the op array + ** by the minimum* amount required until the size reaches 512. Normal + ** operation (without SQLITE_TEST_REALLOC_STRESS) is to double the current + ** size of the op array or add 1KB of space, whichever is smaller. */ +#ifdef SQLITE_TEST_REALLOC_STRESS + sqlite3_int64 nNew = (v->nOpAlloc>=512 ? 2*(sqlite3_int64)v->nOpAlloc + : (sqlite3_int64)v->nOpAlloc+nOp); +#else + sqlite3_int64 nNew = (v->nOpAlloc ? 2*(sqlite3_int64)v->nOpAlloc + : (sqlite3_int64)(1024/sizeof(Op))); + UNUSED_PARAMETER(nOp); +#endif + + /* Ensure that the size of a VDBE does not grow too large */ + if( nNew > p->db->aLimit[SQLITE_LIMIT_VDBE_OP] ){ + sqlite3OomFault(p->db); + return SQLITE_NOMEM; + } + + assert( nOp<=(int)(1024/sizeof(Op)) ); + assert( nNew>=(v->nOpAlloc+nOp) ); + pNew = sqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op)); + if( pNew ){ + p->szOpAlloc = sqlite3DbMallocSize(p->db, pNew); + v->nOpAlloc = p->szOpAlloc/sizeof(Op); + v->aOp = pNew; + } + return (pNew ? SQLITE_OK : SQLITE_NOMEM_BKPT); +} + +#ifdef SQLITE_DEBUG +/* This routine is just a convenient place to set a breakpoint that will +** fire after each opcode is inserted and displayed using +** "PRAGMA vdbe_addoptrace=on". Parameters "pc" (program counter) and +** pOp are available to make the breakpoint conditional. +** +** Other useful labels for breakpoints include: +** test_trace_breakpoint(pc,pOp) +** sqlite3CorruptError(lineno) +** sqlite3MisuseError(lineno) +** sqlite3CantopenError(lineno) +*/ +static void test_addop_breakpoint(int pc, Op *pOp){ + static int n = 0; + (void)pc; + (void)pOp; + n++; +} +#endif + +/* +** Slow paths for sqlite3VdbeAddOp3() and sqlite3VdbeAddOp4Int() for the +** unusual case when we need to increase the size of the Vdbe.aOp[] array +** before adding the new opcode. +*/ +static SQLITE_NOINLINE int growOp3(Vdbe *p, int op, int p1, int p2, int p3){ + assert( p->nOpAlloc<=p->nOp ); + if( growOpArray(p, 1) ) return 1; + assert( p->nOpAlloc>p->nOp ); + return sqlite3VdbeAddOp3(p, op, p1, p2, p3); +} +static SQLITE_NOINLINE int addOp4IntSlow( + Vdbe *p, /* Add the opcode to this VM */ + int op, /* The new opcode */ + int p1, /* The P1 operand */ + int p2, /* The P2 operand */ + int p3, /* The P3 operand */ + int p4 /* The P4 operand as an integer */ +){ + int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); + if( p->db->mallocFailed==0 ){ + VdbeOp *pOp = &p->aOp[addr]; + pOp->p4type = P4_INT32; + pOp->p4.i = p4; + } + return addr; +} + + +/* +** Add a new instruction to the list of instructions current in the +** VDBE. Return the address of the new instruction. +** +** Parameters: +** +** p Pointer to the VDBE +** +** op The opcode for this instruction +** +** p1, p2, p3, p4 Operands +*/ +SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe *p, int op){ + return sqlite3VdbeAddOp3(p, op, 0, 0, 0); +} +SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe *p, int op, int p1){ + return sqlite3VdbeAddOp3(p, op, p1, 0, 0); +} +SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe *p, int op, int p1, int p2){ + return sqlite3VdbeAddOp3(p, op, p1, p2, 0); +} +SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ + int i; + VdbeOp *pOp; + + i = p->nOp; + assert( p->eVdbeState==VDBE_INIT_STATE ); + assert( op>=0 && op<0xff ); + if( p->nOpAlloc<=i ){ + return growOp3(p, op, p1, p2, p3); + } + assert( p->aOp!=0 ); + p->nOp++; + pOp = &p->aOp[i]; + assert( pOp!=0 ); + pOp->opcode = (u8)op; + pOp->p5 = 0; + pOp->p1 = p1; + pOp->p2 = p2; + pOp->p3 = p3; + pOp->p4.p = 0; + pOp->p4type = P4_NOTUSED; + + /* Replicate this logic in sqlite3VdbeAddOp4Int() + ** vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + pOp->zComment = 0; +#endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + pOp->nExec = 0; + pOp->nCycle = 0; +#endif +#ifdef SQLITE_DEBUG + if( p->db->flags & SQLITE_VdbeAddopTrace ){ + sqlite3VdbePrintOp(0, i, &p->aOp[i]); + test_addop_breakpoint(i, &p->aOp[i]); + } +#endif +#ifdef SQLITE_VDBE_COVERAGE + pOp->iSrcLine = 0; +#endif + /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ** Replicate in sqlite3VdbeAddOp4Int() */ + + return i; +} +SQLITE_PRIVATE int sqlite3VdbeAddOp4Int( + Vdbe *p, /* Add the opcode to this VM */ + int op, /* The new opcode */ + int p1, /* The P1 operand */ + int p2, /* The P2 operand */ + int p3, /* The P3 operand */ + int p4 /* The P4 operand as an integer */ +){ + int i; + VdbeOp *pOp; + + i = p->nOp; + if( p->nOpAlloc<=i ){ + return addOp4IntSlow(p, op, p1, p2, p3, p4); + } + p->nOp++; + pOp = &p->aOp[i]; + assert( pOp!=0 ); + pOp->opcode = (u8)op; + pOp->p5 = 0; + pOp->p1 = p1; + pOp->p2 = p2; + pOp->p3 = p3; + pOp->p4.i = p4; + pOp->p4type = P4_INT32; + + /* Replicate this logic in sqlite3VdbeAddOp3() + ** vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + pOp->zComment = 0; +#endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + pOp->nExec = 0; + pOp->nCycle = 0; +#endif +#ifdef SQLITE_DEBUG + if( p->db->flags & SQLITE_VdbeAddopTrace ){ + sqlite3VdbePrintOp(0, i, &p->aOp[i]); + test_addop_breakpoint(i, &p->aOp[i]); + } +#endif +#ifdef SQLITE_VDBE_COVERAGE + pOp->iSrcLine = 0; +#endif + /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ** Replicate in sqlite3VdbeAddOp3() */ + + return i; +} + +/* Generate code for an unconditional jump to instruction iDest +*/ +SQLITE_PRIVATE int sqlite3VdbeGoto(Vdbe *p, int iDest){ + return sqlite3VdbeAddOp3(p, OP_Goto, 0, iDest, 0); +} + +/* Generate code to cause the string zStr to be loaded into +** register iDest +*/ +SQLITE_PRIVATE int sqlite3VdbeLoadString(Vdbe *p, int iDest, const char *zStr){ + return sqlite3VdbeAddOp4(p, OP_String8, 0, iDest, 0, zStr, 0); +} + +/* +** Generate code that initializes multiple registers to string or integer +** constants. The registers begin with iDest and increase consecutively. +** One register is initialized for each characgter in zTypes[]. For each +** "s" character in zTypes[], the register is a string if the argument is +** not NULL, or OP_Null if the value is a null pointer. For each "i" character +** in zTypes[], the register is initialized to an integer. +** +** If the input string does not end with "X" then an OP_ResultRow instruction +** is generated for the values inserted. +*/ +SQLITE_PRIVATE void sqlite3VdbeMultiLoad(Vdbe *p, int iDest, const char *zTypes, ...){ + va_list ap; + int i; + char c; + va_start(ap, zTypes); + for(i=0; (c = zTypes[i])!=0; i++){ + if( c=='s' ){ + const char *z = va_arg(ap, const char*); + sqlite3VdbeAddOp4(p, z==0 ? OP_Null : OP_String8, 0, iDest+i, 0, z, 0); + }else if( c=='i' ){ + sqlite3VdbeAddOp2(p, OP_Integer, va_arg(ap, int), iDest+i); + }else{ + goto skip_op_resultrow; + } + } + sqlite3VdbeAddOp2(p, OP_ResultRow, iDest, i); +skip_op_resultrow: + va_end(ap); +} + +/* +** Add an opcode that includes the p4 value as a pointer. +*/ +SQLITE_PRIVATE int sqlite3VdbeAddOp4( + Vdbe *p, /* Add the opcode to this VM */ + int op, /* The new opcode */ + int p1, /* The P1 operand */ + int p2, /* The P2 operand */ + int p3, /* The P3 operand */ + const char *zP4, /* The P4 operand */ + int p4type /* P4 operand type */ +){ + int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); + sqlite3VdbeChangeP4(p, addr, zP4, p4type); + return addr; +} + +/* +** Add an OP_Function or OP_PureFunc opcode. +** +** The eCallCtx argument is information (typically taken from Expr.op2) +** that describes the calling context of the function. 0 means a general +** function call. NC_IsCheck means called by a check constraint, +** NC_IdxExpr means called as part of an index expression. NC_PartIdx +** means in the WHERE clause of a partial index. NC_GenCol means called +** while computing a generated column value. 0 is the usual case. +*/ +SQLITE_PRIVATE int sqlite3VdbeAddFunctionCall( + Parse *pParse, /* Parsing context */ + int p1, /* Constant argument mask */ + int p2, /* First argument register */ + int p3, /* Register into which results are written */ + int nArg, /* Number of argument */ + const FuncDef *pFunc, /* The function to be invoked */ + int eCallCtx /* Calling context */ +){ + Vdbe *v = pParse->pVdbe; + int nByte; + int addr; + sqlite3_context *pCtx; + assert( v ); + nByte = sizeof(*pCtx) + (nArg-1)*sizeof(sqlite3_value*); + pCtx = sqlite3DbMallocRawNN(pParse->db, nByte); + if( pCtx==0 ){ + assert( pParse->db->mallocFailed ); + freeEphemeralFunction(pParse->db, (FuncDef*)pFunc); + return 0; + } + pCtx->pOut = 0; + pCtx->pFunc = (FuncDef*)pFunc; + pCtx->pVdbe = 0; + pCtx->isError = 0; + pCtx->argc = nArg; + pCtx->iOp = sqlite3VdbeCurrentAddr(v); + addr = sqlite3VdbeAddOp4(v, eCallCtx ? OP_PureFunc : OP_Function, + p1, p2, p3, (char*)pCtx, P4_FUNCCTX); + sqlite3VdbeChangeP5(v, eCallCtx & NC_SelfRef); + sqlite3MayAbort(pParse); + return addr; +} + +/* +** Add an opcode that includes the p4 value with a P4_INT64 or +** P4_REAL type. +*/ +SQLITE_PRIVATE int sqlite3VdbeAddOp4Dup8( + Vdbe *p, /* Add the opcode to this VM */ + int op, /* The new opcode */ + int p1, /* The P1 operand */ + int p2, /* The P2 operand */ + int p3, /* The P3 operand */ + const u8 *zP4, /* The P4 operand */ + int p4type /* P4 operand type */ +){ + char *p4copy = sqlite3DbMallocRawNN(sqlite3VdbeDb(p), 8); + if( p4copy ) memcpy(p4copy, zP4, 8); + return sqlite3VdbeAddOp4(p, op, p1, p2, p3, p4copy, p4type); +} + +#ifndef SQLITE_OMIT_EXPLAIN +/* +** Return the address of the current EXPLAIN QUERY PLAN baseline. +** 0 means "none". +*/ +SQLITE_PRIVATE int sqlite3VdbeExplainParent(Parse *pParse){ + VdbeOp *pOp; + if( pParse->addrExplain==0 ) return 0; + pOp = sqlite3VdbeGetOp(pParse->pVdbe, pParse->addrExplain); + return pOp->p2; +} + +/* +** Set a debugger breakpoint on the following routine in order to +** monitor the EXPLAIN QUERY PLAN code generation. +*/ +#if defined(SQLITE_DEBUG) +SQLITE_PRIVATE void sqlite3ExplainBreakpoint(const char *z1, const char *z2){ + (void)z1; + (void)z2; +} +#endif + +/* +** Add a new OP_Explain opcode. +** +** If the bPush flag is true, then make this opcode the parent for +** subsequent Explains until sqlite3VdbeExplainPop() is called. +*/ +SQLITE_PRIVATE int sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ + int addr = 0; +#if !defined(SQLITE_DEBUG) + /* Always include the OP_Explain opcodes if SQLITE_DEBUG is defined. + ** But omit them (for performance) during production builds */ + if( pParse->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) +#endif + { + char *zMsg; + Vdbe *v; + va_list ap; + int iThis; + va_start(ap, zFmt); + zMsg = sqlite3VMPrintf(pParse->db, zFmt, ap); + va_end(ap); + v = pParse->pVdbe; + iThis = v->nOp; + addr = sqlite3VdbeAddOp4(v, OP_Explain, iThis, pParse->addrExplain, 0, + zMsg, P4_DYNAMIC); + sqlite3ExplainBreakpoint(bPush?"PUSH":"", sqlite3VdbeGetLastOp(v)->p4.z); + if( bPush){ + pParse->addrExplain = iThis; + } + sqlite3VdbeScanStatus(v, iThis, -1, -1, 0, 0); + } + return addr; +} + +/* +** Pop the EXPLAIN QUERY PLAN stack one level. +*/ +SQLITE_PRIVATE void sqlite3VdbeExplainPop(Parse *pParse){ + sqlite3ExplainBreakpoint("POP", 0); + pParse->addrExplain = sqlite3VdbeExplainParent(pParse); +} +#endif /* SQLITE_OMIT_EXPLAIN */ + +/* +** Add an OP_ParseSchema opcode. This routine is broken out from +** sqlite3VdbeAddOp4() since it needs to also needs to mark all btrees +** as having been used. +** +** The zWhere string must have been obtained from sqlite3_malloc(). +** This routine will take ownership of the allocated memory. +*/ +SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere, u16 p5){ + int j; + sqlite3VdbeAddOp4(p, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC); + sqlite3VdbeChangeP5(p, p5); + for(j=0; j<p->db->nDb; j++) sqlite3VdbeUsesBtree(p, j); + sqlite3MayAbort(p->pParse); +} + +/* Insert the end of a co-routine +*/ +SQLITE_PRIVATE void sqlite3VdbeEndCoroutine(Vdbe *v, int regYield){ + sqlite3VdbeAddOp1(v, OP_EndCoroutine, regYield); + + /* Clear the temporary register cache, thereby ensuring that each + ** co-routine has its own independent set of registers, because co-routines + ** might expect their registers to be preserved across an OP_Yield, and + ** that could cause problems if two or more co-routines are using the same + ** temporary register. + */ + v->pParse->nTempReg = 0; + v->pParse->nRangeReg = 0; +} + +/* +** Create a new symbolic label for an instruction that has yet to be +** coded. The symbolic label is really just a negative number. The +** label can be used as the P2 value of an operation. Later, when +** the label is resolved to a specific address, the VDBE will scan +** through its operation list and change all values of P2 which match +** the label into the resolved address. +** +** The VDBE knows that a P2 value is a label because labels are +** always negative and P2 values are suppose to be non-negative. +** Hence, a negative P2 value is a label that has yet to be resolved. +** (Later:) This is only true for opcodes that have the OPFLG_JUMP +** property. +** +** Variable usage notes: +** +** Parse.aLabel[x] Stores the address that the x-th label resolves +** into. For testing (SQLITE_DEBUG), unresolved +** labels stores -1, but that is not required. +** Parse.nLabelAlloc Number of slots allocated to Parse.aLabel[] +** Parse.nLabel The *negative* of the number of labels that have +** been issued. The negative is stored because +** that gives a performance improvement over storing +** the equivalent positive value. +*/ +SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Parse *pParse){ + return --pParse->nLabel; +} + +/* +** Resolve label "x" to be the address of the next instruction to +** be inserted. The parameter "x" must have been obtained from +** a prior call to sqlite3VdbeMakeLabel(). +*/ +static SQLITE_NOINLINE void resizeResolveLabel(Parse *p, Vdbe *v, int j){ + int nNewSize = 10 - p->nLabel; + p->aLabel = sqlite3DbReallocOrFree(p->db, p->aLabel, + nNewSize*sizeof(p->aLabel[0])); + if( p->aLabel==0 ){ + p->nLabelAlloc = 0; + }else{ +#ifdef SQLITE_DEBUG + int i; + for(i=p->nLabelAlloc; i<nNewSize; i++) p->aLabel[i] = -1; +#endif + if( nNewSize>=100 && (nNewSize/100)>(p->nLabelAlloc/100) ){ + sqlite3ProgressCheck(p); + } + p->nLabelAlloc = nNewSize; + p->aLabel[j] = v->nOp; + } +} +SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){ + Parse *p = v->pParse; + int j = ADDR(x); + assert( v->eVdbeState==VDBE_INIT_STATE ); + assert( j<-p->nLabel ); + assert( j>=0 ); +#ifdef SQLITE_DEBUG + if( p->db->flags & SQLITE_VdbeAddopTrace ){ + printf("RESOLVE LABEL %d to %d\n", x, v->nOp); + } +#endif + if( p->nLabelAlloc + p->nLabel < 0 ){ + resizeResolveLabel(p,v,j); + }else{ + assert( p->aLabel[j]==(-1) ); /* Labels may only be resolved once */ + p->aLabel[j] = v->nOp; + } +} + +/* +** Mark the VDBE as one that can only be run one time. +*/ +SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe *p){ + sqlite3VdbeAddOp2(p, OP_Expire, 1, 1); +} + +/* +** Mark the VDBE as one that can be run multiple times. +*/ +SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe *p){ + int i; + for(i=1; ALWAYS(i<p->nOp); i++){ + if( ALWAYS(p->aOp[i].opcode==OP_Expire) ){ + p->aOp[1].opcode = OP_Noop; + break; + } + } +} + +#ifdef SQLITE_DEBUG /* sqlite3AssertMayAbort() logic */ + +/* +** The following type and function are used to iterate through all opcodes +** in a Vdbe main program and each of the sub-programs (triggers) it may +** invoke directly or indirectly. It should be used as follows: +** +** Op *pOp; +** VdbeOpIter sIter; +** +** memset(&sIter, 0, sizeof(sIter)); +** sIter.v = v; // v is of type Vdbe* +** while( (pOp = opIterNext(&sIter)) ){ +** // Do something with pOp +** } +** sqlite3DbFree(v->db, sIter.apSub); +** +*/ +typedef struct VdbeOpIter VdbeOpIter; +struct VdbeOpIter { + Vdbe *v; /* Vdbe to iterate through the opcodes of */ + SubProgram **apSub; /* Array of subprograms */ + int nSub; /* Number of entries in apSub */ + int iAddr; /* Address of next instruction to return */ + int iSub; /* 0 = main program, 1 = first sub-program etc. */ +}; +static Op *opIterNext(VdbeOpIter *p){ + Vdbe *v = p->v; + Op *pRet = 0; + Op *aOp; + int nOp; + + if( p->iSub<=p->nSub ){ + + if( p->iSub==0 ){ + aOp = v->aOp; + nOp = v->nOp; + }else{ + aOp = p->apSub[p->iSub-1]->aOp; + nOp = p->apSub[p->iSub-1]->nOp; + } + assert( p->iAddr<nOp ); + + pRet = &aOp[p->iAddr]; + p->iAddr++; + if( p->iAddr==nOp ){ + p->iSub++; + p->iAddr = 0; + } + + if( pRet->p4type==P4_SUBPROGRAM ){ + int nByte = (p->nSub+1)*sizeof(SubProgram*); + int j; + for(j=0; j<p->nSub; j++){ + if( p->apSub[j]==pRet->p4.pProgram ) break; + } + if( j==p->nSub ){ + p->apSub = sqlite3DbReallocOrFree(v->db, p->apSub, nByte); + if( !p->apSub ){ + pRet = 0; + }else{ + p->apSub[p->nSub++] = pRet->p4.pProgram; + } + } + } + } + + return pRet; +} + +/* +** Check if the program stored in the VM associated with pParse may +** throw an ABORT exception (causing the statement, but not entire transaction +** to be rolled back). This condition is true if the main program or any +** sub-programs contains any of the following: +** +** * OP_Halt with P1=SQLITE_CONSTRAINT and P2=OE_Abort. +** * OP_HaltIfNull with P1=SQLITE_CONSTRAINT and P2=OE_Abort. +** * OP_Destroy +** * OP_VUpdate +** * OP_VCreate +** * OP_VRename +** * OP_FkCounter with P2==0 (immediate foreign key constraint) +** * OP_CreateBtree/BTREE_INTKEY and OP_InitCoroutine +** (for CREATE TABLE AS SELECT ...) +** +** Then check that the value of Parse.mayAbort is true if an +** ABORT may be thrown, or false otherwise. Return true if it does +** match, or false otherwise. This function is intended to be used as +** part of an assert statement in the compiler. Similar to: +** +** assert( sqlite3VdbeAssertMayAbort(pParse->pVdbe, pParse->mayAbort) ); +*/ +SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ + int hasAbort = 0; + int hasFkCounter = 0; + int hasCreateTable = 0; + int hasCreateIndex = 0; + int hasInitCoroutine = 0; + Op *pOp; + VdbeOpIter sIter; + + if( v==0 ) return 0; + memset(&sIter, 0, sizeof(sIter)); + sIter.v = v; + + while( (pOp = opIterNext(&sIter))!=0 ){ + int opcode = pOp->opcode; + if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename + || opcode==OP_VDestroy + || opcode==OP_VCreate + || opcode==OP_ParseSchema + || opcode==OP_Function || opcode==OP_PureFunc + || ((opcode==OP_Halt || opcode==OP_HaltIfNull) + && ((pOp->p1)!=SQLITE_OK && pOp->p2==OE_Abort)) + ){ + hasAbort = 1; + break; + } + if( opcode==OP_CreateBtree && pOp->p3==BTREE_INTKEY ) hasCreateTable = 1; + if( mayAbort ){ + /* hasCreateIndex may also be set for some DELETE statements that use + ** OP_Clear. So this routine may end up returning true in the case + ** where a "DELETE FROM tbl" has a statement-journal but does not + ** require one. This is not so bad - it is an inefficiency, not a bug. */ + if( opcode==OP_CreateBtree && pOp->p3==BTREE_BLOBKEY ) hasCreateIndex = 1; + if( opcode==OP_Clear ) hasCreateIndex = 1; + } + if( opcode==OP_InitCoroutine ) hasInitCoroutine = 1; +#ifndef SQLITE_OMIT_FOREIGN_KEY + if( opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1 ){ + hasFkCounter = 1; + } +#endif + } + sqlite3DbFree(v->db, sIter.apSub); + + /* Return true if hasAbort==mayAbort. Or if a malloc failure occurred. + ** If malloc failed, then the while() loop above may not have iterated + ** through all opcodes and hasAbort may be set incorrectly. Return + ** true for this case to prevent the assert() in the callers frame + ** from failing. */ + return ( v->db->mallocFailed || hasAbort==mayAbort || hasFkCounter + || (hasCreateTable && hasInitCoroutine) || hasCreateIndex + ); +} +#endif /* SQLITE_DEBUG - the sqlite3AssertMayAbort() function */ + +#ifdef SQLITE_DEBUG +/* +** Increment the nWrite counter in the VDBE if the cursor is not an +** ephemeral cursor, or if the cursor argument is NULL. +*/ +SQLITE_PRIVATE void sqlite3VdbeIncrWriteCounter(Vdbe *p, VdbeCursor *pC){ + if( pC==0 + || (pC->eCurType!=CURTYPE_SORTER + && pC->eCurType!=CURTYPE_PSEUDO + && !pC->isEphemeral) + ){ + p->nWrite++; + } +} +#endif + +#ifdef SQLITE_DEBUG +/* +** Assert if an Abort at this point in time might result in a corrupt +** database. +*/ +SQLITE_PRIVATE void sqlite3VdbeAssertAbortable(Vdbe *p){ + assert( p->nWrite==0 || p->usesStmtJournal ); +} +#endif + +/* +** This routine is called after all opcodes have been inserted. It loops +** through all the opcodes and fixes up some details. +** +** (1) For each jump instruction with a negative P2 value (a label) +** resolve the P2 value to an actual address. +** +** (2) Compute the maximum number of arguments used by any SQL function +** and store that value in *pMaxFuncArgs. +** +** (3) Update the Vdbe.readOnly and Vdbe.bIsReader flags to accurately +** indicate what the prepared statement actually does. +** +** (4) (discontinued) +** +** (5) Reclaim the memory allocated for storing labels. +** +** This routine will only function correctly if the mkopcodeh.tcl generator +** script numbers the opcodes correctly. Changes to this routine must be +** coordinated with changes to mkopcodeh.tcl. +*/ +static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ + int nMaxArgs = *pMaxFuncArgs; + Op *pOp; + Parse *pParse = p->pParse; + int *aLabel = pParse->aLabel; + + assert( pParse->db->mallocFailed==0 ); /* tag-20230419-1 */ + p->readOnly = 1; + p->bIsReader = 0; + pOp = &p->aOp[p->nOp-1]; + assert( p->aOp[0].opcode==OP_Init ); + while( 1 /* Loop terminates when it reaches the OP_Init opcode */ ){ + /* Only JUMP opcodes and the short list of special opcodes in the switch + ** below need to be considered. The mkopcodeh.tcl generator script groups + ** all these opcodes together near the front of the opcode list. Skip + ** any opcode that does not need processing by virtual of the fact that + ** it is larger than SQLITE_MX_JUMP_OPCODE, as a performance optimization. + */ + if( pOp->opcode<=SQLITE_MX_JUMP_OPCODE ){ + /* NOTE: Be sure to update mkopcodeh.tcl when adding or removing + ** cases from this switch! */ + switch( pOp->opcode ){ + case OP_Transaction: { + if( pOp->p2!=0 ) p->readOnly = 0; + /* no break */ deliberate_fall_through + } + case OP_AutoCommit: + case OP_Savepoint: { + p->bIsReader = 1; + break; + } +#ifndef SQLITE_OMIT_WAL + case OP_Checkpoint: +#endif + case OP_Vacuum: + case OP_JournalMode: { + p->readOnly = 0; + p->bIsReader = 1; + break; + } + case OP_Init: { + assert( pOp->p2>=0 ); + goto resolve_p2_values_loop_exit; + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + case OP_VUpdate: { + if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2; + break; + } + case OP_VFilter: { + int n; + assert( (pOp - p->aOp) >= 3 ); + assert( pOp[-1].opcode==OP_Integer ); + n = pOp[-1].p1; + if( n>nMaxArgs ) nMaxArgs = n; + /* Fall through into the default case */ + /* no break */ deliberate_fall_through + } +#endif + default: { + if( pOp->p2<0 ){ + /* The mkopcodeh.tcl script has so arranged things that the only + ** non-jump opcodes less than SQLITE_MX_JUMP_CODE are guaranteed to + ** have non-negative values for P2. */ + assert( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 ); + assert( ADDR(pOp->p2)<-pParse->nLabel ); + assert( aLabel!=0 ); /* True because of tag-20230419-1 */ + pOp->p2 = aLabel[ADDR(pOp->p2)]; + } + break; + } + } + /* The mkopcodeh.tcl script has so arranged things that the only + ** non-jump opcodes less than SQLITE_MX_JUMP_CODE are guaranteed to + ** have non-negative values for P2. */ + assert( (sqlite3OpcodeProperty[pOp->opcode]&OPFLG_JUMP)==0 || pOp->p2>=0); + } + assert( pOp>p->aOp ); + pOp--; + } +resolve_p2_values_loop_exit: + if( aLabel ){ + sqlite3DbNNFreeNN(p->db, pParse->aLabel); + pParse->aLabel = 0; + } + pParse->nLabel = 0; + *pMaxFuncArgs = nMaxArgs; + assert( p->bIsReader!=0 || DbMaskAllZero(p->btreeMask) ); +} + +#ifdef SQLITE_DEBUG +/* +** Check to see if a subroutine contains a jump to a location outside of +** the subroutine. If a jump outside the subroutine is detected, add code +** that will cause the program to halt with an error message. +** +** The subroutine consists of opcodes between iFirst and iLast. Jumps to +** locations within the subroutine are acceptable. iRetReg is a register +** that contains the return address. Jumps to outside the range of iFirst +** through iLast are also acceptable as long as the jump destination is +** an OP_Return to iReturnAddr. +** +** A jump to an unresolved label means that the jump destination will be +** beyond the current address. That is normally a jump to an early +** termination and is consider acceptable. +** +** This routine only runs during debug builds. The purpose is (of course) +** to detect invalid escapes out of a subroutine. The OP_Halt opcode +** is generated rather than an assert() or other error, so that ".eqp full" +** will still work to show the original bytecode, to aid in debugging. +*/ +SQLITE_PRIVATE void sqlite3VdbeNoJumpsOutsideSubrtn( + Vdbe *v, /* The byte-code program under construction */ + int iFirst, /* First opcode of the subroutine */ + int iLast, /* Last opcode of the subroutine */ + int iRetReg /* Subroutine return address register */ +){ + VdbeOp *pOp; + Parse *pParse; + int i; + sqlite3_str *pErr = 0; + assert( v!=0 ); + pParse = v->pParse; + assert( pParse!=0 ); + if( pParse->nErr ) return; + assert( iLast>=iFirst ); + assert( iLast<v->nOp ); + pOp = &v->aOp[iFirst]; + for(i=iFirst; i<=iLast; i++, pOp++){ + if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 ){ + int iDest = pOp->p2; /* Jump destination */ + if( iDest==0 ) continue; + if( pOp->opcode==OP_Gosub ) continue; + if( iDest<0 ){ + int j = ADDR(iDest); + assert( j>=0 ); + if( j>=-pParse->nLabel || pParse->aLabel[j]<0 ){ + continue; + } + iDest = pParse->aLabel[j]; + } + if( iDest<iFirst || iDest>iLast ){ + int j = iDest; + for(; j<v->nOp; j++){ + VdbeOp *pX = &v->aOp[j]; + if( pX->opcode==OP_Return ){ + if( pX->p1==iRetReg ) break; + continue; + } + if( pX->opcode==OP_Noop ) continue; + if( pX->opcode==OP_Explain ) continue; + if( pErr==0 ){ + pErr = sqlite3_str_new(0); + }else{ + sqlite3_str_appendchar(pErr, 1, '\n'); + } + sqlite3_str_appendf(pErr, + "Opcode at %d jumps to %d which is outside the " + "subroutine at %d..%d", + i, iDest, iFirst, iLast); + break; + } + } + } + } + if( pErr ){ + char *zErr = sqlite3_str_finish(pErr); + sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_INTERNAL, OE_Abort, 0, zErr, 0); + sqlite3_free(zErr); + sqlite3MayAbort(pParse); + } +} +#endif /* SQLITE_DEBUG */ + +/* +** Return the address of the next instruction to be inserted. +*/ +SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe *p){ + assert( p->eVdbeState==VDBE_INIT_STATE ); + return p->nOp; +} + +/* +** Verify that at least N opcode slots are available in p without +** having to malloc for more space (except when compiled using +** SQLITE_TEST_REALLOC_STRESS). This interface is used during testing +** to verify that certain calls to sqlite3VdbeAddOpList() can never +** fail due to a OOM fault and hence that the return value from +** sqlite3VdbeAddOpList() will always be non-NULL. +*/ +#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS) +SQLITE_PRIVATE void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N){ + assert( p->nOp + N <= p->nOpAlloc ); +} +#endif + +/* +** Verify that the VM passed as the only argument does not contain +** an OP_ResultRow opcode. Fail an assert() if it does. This is used +** by code in pragma.c to ensure that the implementation of certain +** pragmas comports with the flags specified in the mkpragmatab.tcl +** script. +*/ +#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS) +SQLITE_PRIVATE void sqlite3VdbeVerifyNoResultRow(Vdbe *p){ + int i; + for(i=0; i<p->nOp; i++){ + assert( p->aOp[i].opcode!=OP_ResultRow ); + } +} +#endif + +/* +** Generate code (a single OP_Abortable opcode) that will +** verify that the VDBE program can safely call Abort in the current +** context. +*/ +#if defined(SQLITE_DEBUG) +SQLITE_PRIVATE void sqlite3VdbeVerifyAbortable(Vdbe *p, int onError){ + if( onError==OE_Abort ) sqlite3VdbeAddOp0(p, OP_Abortable); +} +#endif + +/* +** This function returns a pointer to the array of opcodes associated with +** the Vdbe passed as the first argument. It is the callers responsibility +** to arrange for the returned array to be eventually freed using the +** vdbeFreeOpArray() function. +** +** Before returning, *pnOp is set to the number of entries in the returned +** array. Also, *pnMaxArg is set to the larger of its current value and +** the number of entries in the Vdbe.apArg[] array required to execute the +** returned program. +*/ +SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){ + VdbeOp *aOp = p->aOp; + assert( aOp && !p->db->mallocFailed ); + + /* Check that sqlite3VdbeUsesBtree() was not called on this VM */ + assert( DbMaskAllZero(p->btreeMask) ); + + resolveP2Values(p, pnMaxArg); + *pnOp = p->nOp; + p->aOp = 0; + return aOp; +} + +/* +** Add a whole list of operations to the operation stack. Return a +** pointer to the first operation inserted. +** +** Non-zero P2 arguments to jump instructions are automatically adjusted +** so that the jump target is relative to the first operation inserted. +*/ +SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList( + Vdbe *p, /* Add opcodes to the prepared statement */ + int nOp, /* Number of opcodes to add */ + VdbeOpList const *aOp, /* The opcodes to be added */ + int iLineno /* Source-file line number of first opcode */ +){ + int i; + VdbeOp *pOut, *pFirst; + assert( nOp>0 ); + assert( p->eVdbeState==VDBE_INIT_STATE ); + if( p->nOp + nOp > p->nOpAlloc && growOpArray(p, nOp) ){ + return 0; + } + pFirst = pOut = &p->aOp[p->nOp]; + for(i=0; i<nOp; i++, aOp++, pOut++){ + pOut->opcode = aOp->opcode; + pOut->p1 = aOp->p1; + pOut->p2 = aOp->p2; + assert( aOp->p2>=0 ); + if( (sqlite3OpcodeProperty[aOp->opcode] & OPFLG_JUMP)!=0 && aOp->p2>0 ){ + pOut->p2 += p->nOp; + } + pOut->p3 = aOp->p3; + pOut->p4type = P4_NOTUSED; + pOut->p4.p = 0; + pOut->p5 = 0; +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + pOut->zComment = 0; +#endif +#ifdef SQLITE_VDBE_COVERAGE + pOut->iSrcLine = iLineno+i; +#else + (void)iLineno; +#endif +#ifdef SQLITE_DEBUG + if( p->db->flags & SQLITE_VdbeAddopTrace ){ + sqlite3VdbePrintOp(0, i+p->nOp, &p->aOp[i+p->nOp]); + } +#endif + } + p->nOp += nOp; + return pFirst; +} + +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) +/* +** Add an entry to the array of counters managed by sqlite3_stmt_scanstatus(). +*/ +SQLITE_PRIVATE void sqlite3VdbeScanStatus( + Vdbe *p, /* VM to add scanstatus() to */ + int addrExplain, /* Address of OP_Explain (or 0) */ + int addrLoop, /* Address of loop counter */ + int addrVisit, /* Address of rows visited counter */ + LogEst nEst, /* Estimated number of output rows */ + const char *zName /* Name of table or index being scanned */ +){ + if( IS_STMT_SCANSTATUS(p->db) ){ + sqlite3_int64 nByte = (p->nScan+1) * sizeof(ScanStatus); + ScanStatus *aNew; + aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte); + if( aNew ){ + ScanStatus *pNew = &aNew[p->nScan++]; + memset(pNew, 0, sizeof(ScanStatus)); + pNew->addrExplain = addrExplain; + pNew->addrLoop = addrLoop; + pNew->addrVisit = addrVisit; + pNew->nEst = nEst; + pNew->zName = sqlite3DbStrDup(p->db, zName); + p->aScan = aNew; + } + } +} + +/* +** Add the range of instructions from addrStart to addrEnd (inclusive) to +** the set of those corresponding to the sqlite3_stmt_scanstatus() counters +** associated with the OP_Explain instruction at addrExplain. The +** sum of the sqlite3Hwtime() values for each of these instructions +** will be returned for SQLITE_SCANSTAT_NCYCLE requests. +*/ +SQLITE_PRIVATE void sqlite3VdbeScanStatusRange( + Vdbe *p, + int addrExplain, + int addrStart, + int addrEnd +){ + if( IS_STMT_SCANSTATUS(p->db) ){ + ScanStatus *pScan = 0; + int ii; + for(ii=p->nScan-1; ii>=0; ii--){ + pScan = &p->aScan[ii]; + if( pScan->addrExplain==addrExplain ) break; + pScan = 0; + } + if( pScan ){ + if( addrEnd<0 ) addrEnd = sqlite3VdbeCurrentAddr(p)-1; + for(ii=0; ii<ArraySize(pScan->aAddrRange); ii+=2){ + if( pScan->aAddrRange[ii]==0 ){ + pScan->aAddrRange[ii] = addrStart; + pScan->aAddrRange[ii+1] = addrEnd; + break; + } + } + } + } +} + +/* +** Set the addresses for the SQLITE_SCANSTAT_NLOOP and SQLITE_SCANSTAT_NROW +** counters for the query element associated with the OP_Explain at +** addrExplain. +*/ +SQLITE_PRIVATE void sqlite3VdbeScanStatusCounters( + Vdbe *p, + int addrExplain, + int addrLoop, + int addrVisit +){ + if( IS_STMT_SCANSTATUS(p->db) ){ + ScanStatus *pScan = 0; + int ii; + for(ii=p->nScan-1; ii>=0; ii--){ + pScan = &p->aScan[ii]; + if( pScan->addrExplain==addrExplain ) break; + pScan = 0; + } + if( pScan ){ + if( addrLoop>0 ) pScan->addrLoop = addrLoop; + if( addrVisit>0 ) pScan->addrVisit = addrVisit; + } + } +} +#endif /* defined(SQLITE_ENABLE_STMT_SCANSTATUS) */ + + +/* +** Change the value of the opcode, or P1, P2, P3, or P5 operands +** for a specific instruction. +*/ +SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe *p, int addr, u8 iNewOpcode){ + assert( addr>=0 ); + sqlite3VdbeGetOp(p,addr)->opcode = iNewOpcode; +} +SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe *p, int addr, int val){ + assert( addr>=0 ); + sqlite3VdbeGetOp(p,addr)->p1 = val; +} +SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe *p, int addr, int val){ + assert( addr>=0 || p->db->mallocFailed ); + sqlite3VdbeGetOp(p,addr)->p2 = val; +} +SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe *p, int addr, int val){ + assert( addr>=0 ); + sqlite3VdbeGetOp(p,addr)->p3 = val; +} +SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u16 p5){ + assert( p->nOp>0 || p->db->mallocFailed ); + if( p->nOp>0 ) p->aOp[p->nOp-1].p5 = p5; +} + +/* +** If the previous opcode is an OP_Column that delivers results +** into register iDest, then add the OPFLAG_TYPEOFARG flag to that +** opcode. +*/ +SQLITE_PRIVATE void sqlite3VdbeTypeofColumn(Vdbe *p, int iDest){ + VdbeOp *pOp = sqlite3VdbeGetLastOp(p); + if( pOp->p3==iDest && pOp->opcode==OP_Column ){ + pOp->p5 |= OPFLAG_TYPEOFARG; + } +} + +/* +** Change the P2 operand of instruction addr so that it points to +** the address of the next instruction to be coded. +*/ +SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe *p, int addr){ + sqlite3VdbeChangeP2(p, addr, p->nOp); +} + +/* +** Change the P2 operand of the jump instruction at addr so that +** the jump lands on the next opcode. Or if the jump instruction was +** the previous opcode (and is thus a no-op) then simply back up +** the next instruction counter by one slot so that the jump is +** overwritten by the next inserted opcode. +** +** This routine is an optimization of sqlite3VdbeJumpHere() that +** strives to omit useless byte-code like this: +** +** 7 Once 0 8 0 +** 8 ... +*/ +SQLITE_PRIVATE void sqlite3VdbeJumpHereOrPopInst(Vdbe *p, int addr){ + if( addr==p->nOp-1 ){ + assert( p->aOp[addr].opcode==OP_Once + || p->aOp[addr].opcode==OP_If + || p->aOp[addr].opcode==OP_FkIfZero ); + assert( p->aOp[addr].p4type==0 ); +#ifdef SQLITE_VDBE_COVERAGE + sqlite3VdbeGetLastOp(p)->iSrcLine = 0; /* Erase VdbeCoverage() macros */ +#endif + p->nOp--; + }else{ + sqlite3VdbeChangeP2(p, addr, p->nOp); + } +} + + +/* +** If the input FuncDef structure is ephemeral, then free it. If +** the FuncDef is not ephemeral, then do nothing. +*/ +static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){ + assert( db!=0 ); + if( (pDef->funcFlags & SQLITE_FUNC_EPHEM)!=0 ){ + sqlite3DbNNFreeNN(db, pDef); + } +} + +/* +** Delete a P4 value if necessary. +*/ +static SQLITE_NOINLINE void freeP4Mem(sqlite3 *db, Mem *p){ + if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); + sqlite3DbNNFreeNN(db, p); +} +static SQLITE_NOINLINE void freeP4FuncCtx(sqlite3 *db, sqlite3_context *p){ + assert( db!=0 ); + freeEphemeralFunction(db, p->pFunc); + sqlite3DbNNFreeNN(db, p); +} +static void freeP4(sqlite3 *db, int p4type, void *p4){ + assert( db ); + switch( p4type ){ + case P4_FUNCCTX: { + freeP4FuncCtx(db, (sqlite3_context*)p4); + break; + } + case P4_REAL: + case P4_INT64: + case P4_DYNAMIC: + case P4_INTARRAY: { + if( p4 ) sqlite3DbNNFreeNN(db, p4); + break; + } + case P4_KEYINFO: { + if( db->pnBytesFreed==0 ) sqlite3KeyInfoUnref((KeyInfo*)p4); + break; + } +#ifdef SQLITE_ENABLE_CURSOR_HINTS + case P4_EXPR: { + sqlite3ExprDelete(db, (Expr*)p4); + break; + } +#endif + case P4_FUNCDEF: { + freeEphemeralFunction(db, (FuncDef*)p4); + break; + } + case P4_MEM: { + if( db->pnBytesFreed==0 ){ + sqlite3ValueFree((sqlite3_value*)p4); + }else{ + freeP4Mem(db, (Mem*)p4); + } + break; + } + case P4_VTAB : { + if( db->pnBytesFreed==0 ) sqlite3VtabUnlock((VTable *)p4); + break; + } + } +} + +/* +** Free the space allocated for aOp and any p4 values allocated for the +** opcodes contained within. If aOp is not NULL it is assumed to contain +** nOp entries. +*/ +static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ + assert( nOp>=0 ); + assert( db!=0 ); + if( aOp ){ + Op *pOp = &aOp[nOp-1]; + while(1){ /* Exit via break */ + if( pOp->p4type <= P4_FREE_IF_LE ) freeP4(db, pOp->p4type, pOp->p4.p); +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + sqlite3DbFree(db, pOp->zComment); +#endif + if( pOp==aOp ) break; + pOp--; + } + sqlite3DbNNFreeNN(db, aOp); + } +} + +/* +** Link the SubProgram object passed as the second argument into the linked +** list at Vdbe.pSubProgram. This list is used to delete all sub-program +** objects when the VM is no longer required. +*/ +SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *pVdbe, SubProgram *p){ + p->pNext = pVdbe->pProgram; + pVdbe->pProgram = p; +} + +/* +** Return true if the given Vdbe has any SubPrograms. +*/ +SQLITE_PRIVATE int sqlite3VdbeHasSubProgram(Vdbe *pVdbe){ + return pVdbe->pProgram!=0; +} + +/* +** Change the opcode at addr into OP_Noop +*/ +SQLITE_PRIVATE int sqlite3VdbeChangeToNoop(Vdbe *p, int addr){ + VdbeOp *pOp; + if( p->db->mallocFailed ) return 0; + assert( addr>=0 && addr<p->nOp ); + pOp = &p->aOp[addr]; + freeP4(p->db, pOp->p4type, pOp->p4.p); + pOp->p4type = P4_NOTUSED; + pOp->p4.z = 0; + pOp->opcode = OP_Noop; + return 1; +} + +/* +** If the last opcode is "op" and it is not a jump destination, +** then remove it. Return true if and only if an opcode was removed. +*/ +SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){ + if( p->nOp>0 && p->aOp[p->nOp-1].opcode==op ){ + return sqlite3VdbeChangeToNoop(p, p->nOp-1); + }else{ + return 0; + } +} + +#ifdef SQLITE_DEBUG +/* +** Generate an OP_ReleaseReg opcode to indicate that a range of +** registers, except any identified by mask, are no longer in use. +*/ +SQLITE_PRIVATE void sqlite3VdbeReleaseRegisters( + Parse *pParse, /* Parsing context */ + int iFirst, /* Index of first register to be released */ + int N, /* Number of registers to release */ + u32 mask, /* Mask of registers to NOT release */ + int bUndefine /* If true, mark registers as undefined */ +){ + if( N==0 || OptimizationDisabled(pParse->db, SQLITE_ReleaseReg) ) return; + assert( pParse->pVdbe ); + assert( iFirst>=1 ); + assert( iFirst+N-1<=pParse->nMem ); + if( N<=31 && mask!=0 ){ + while( N>0 && (mask&1)!=0 ){ + mask >>= 1; + iFirst++; + N--; + } + while( N>0 && N<=32 && (mask & MASKBIT32(N-1))!=0 ){ + mask &= ~MASKBIT32(N-1); + N--; + } + } + if( N>0 ){ + sqlite3VdbeAddOp3(pParse->pVdbe, OP_ReleaseReg, iFirst, N, *(int*)&mask); + if( bUndefine ) sqlite3VdbeChangeP5(pParse->pVdbe, 1); + } +} +#endif /* SQLITE_DEBUG */ + +/* +** Change the value of the P4 operand for a specific instruction. +** This routine is useful when a large program is loaded from a +** static array using sqlite3VdbeAddOpList but we want to make a +** few minor changes to the program. +** +** If n>=0 then the P4 operand is dynamic, meaning that a copy of +** the string is made into memory obtained from sqlite3_malloc(). +** A value of n==0 means copy bytes of zP4 up to and including the +** first null byte. If n>0 then copy n+1 bytes of zP4. +** +** Other values of n (P4_STATIC, P4_COLLSEQ etc.) indicate that zP4 points +** to a string or structure that is guaranteed to exist for the lifetime of +** the Vdbe. In these cases we can just copy the pointer. +** +** If addr<0 then change P4 on the most recently inserted instruction. +*/ +static void SQLITE_NOINLINE vdbeChangeP4Full( + Vdbe *p, + Op *pOp, + const char *zP4, + int n +){ + if( pOp->p4type ){ + freeP4(p->db, pOp->p4type, pOp->p4.p); + pOp->p4type = 0; + pOp->p4.p = 0; + } + if( n<0 ){ + sqlite3VdbeChangeP4(p, (int)(pOp - p->aOp), zP4, n); + }else{ + if( n==0 ) n = sqlite3Strlen30(zP4); + pOp->p4.z = sqlite3DbStrNDup(p->db, zP4, n); + pOp->p4type = P4_DYNAMIC; + } +} +SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){ + Op *pOp; + sqlite3 *db; + assert( p!=0 ); + db = p->db; + assert( p->eVdbeState==VDBE_INIT_STATE ); + assert( p->aOp!=0 || db->mallocFailed ); + if( db->mallocFailed ){ + if( n!=P4_VTAB ) freeP4(db, n, (void*)*(char**)&zP4); + return; + } + assert( p->nOp>0 ); + assert( addr<p->nOp ); + if( addr<0 ){ + addr = p->nOp - 1; + } + pOp = &p->aOp[addr]; + if( n>=0 || pOp->p4type ){ + vdbeChangeP4Full(p, pOp, zP4, n); + return; + } + if( n==P4_INT32 ){ + /* Note: this cast is safe, because the origin data point was an int + ** that was cast to a (const char *). */ + pOp->p4.i = SQLITE_PTR_TO_INT(zP4); + pOp->p4type = P4_INT32; + }else if( zP4!=0 ){ + assert( n<0 ); + pOp->p4.p = (void*)zP4; + pOp->p4type = (signed char)n; + if( n==P4_VTAB ) sqlite3VtabLock((VTable*)zP4); + } +} + +/* +** Change the P4 operand of the most recently coded instruction +** to the value defined by the arguments. This is a high-speed +** version of sqlite3VdbeChangeP4(). +** +** The P4 operand must not have been previously defined. And the new +** P4 must not be P4_INT32. Use sqlite3VdbeChangeP4() in either of +** those cases. +*/ +SQLITE_PRIVATE void sqlite3VdbeAppendP4(Vdbe *p, void *pP4, int n){ + VdbeOp *pOp; + assert( n!=P4_INT32 && n!=P4_VTAB ); + assert( n<=0 ); + if( p->db->mallocFailed ){ + freeP4(p->db, n, pP4); + }else{ + assert( pP4!=0 || n==P4_DYNAMIC ); + assert( p->nOp>0 ); + pOp = &p->aOp[p->nOp-1]; + assert( pOp->p4type==P4_NOTUSED ); + pOp->p4type = n; + pOp->p4.p = pP4; + } +} + +/* +** Set the P4 on the most recently added opcode to the KeyInfo for the +** index given. +*/ +SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){ + Vdbe *v = pParse->pVdbe; + KeyInfo *pKeyInfo; + assert( v!=0 ); + assert( pIdx!=0 ); + pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pIdx); + if( pKeyInfo ) sqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO); +} + +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS +/* +** Change the comment on the most recently coded instruction. Or +** insert a No-op and add the comment to that new instruction. This +** makes the code easier to read during debugging. None of this happens +** in a production build. +*/ +static void vdbeVComment(Vdbe *p, const char *zFormat, va_list ap){ + assert( p->nOp>0 || p->aOp==0 ); + assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->pParse->nErr>0 ); + if( p->nOp ){ + assert( p->aOp ); + sqlite3DbFree(p->db, p->aOp[p->nOp-1].zComment); + p->aOp[p->nOp-1].zComment = sqlite3VMPrintf(p->db, zFormat, ap); + } +} +SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){ + va_list ap; + if( p ){ + va_start(ap, zFormat); + vdbeVComment(p, zFormat, ap); + va_end(ap); + } +} +SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){ + va_list ap; + if( p ){ + sqlite3VdbeAddOp0(p, OP_Noop); + va_start(ap, zFormat); + vdbeVComment(p, zFormat, ap); + va_end(ap); + } +} +#endif /* NDEBUG */ + +#ifdef SQLITE_VDBE_COVERAGE +/* +** Set the value if the iSrcLine field for the previously coded instruction. +*/ +SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe *v, int iLine){ + sqlite3VdbeGetLastOp(v)->iSrcLine = iLine; +} +#endif /* SQLITE_VDBE_COVERAGE */ + +/* +** Return the opcode for a given address. The address must be non-negative. +** See sqlite3VdbeGetLastOp() to get the most recently added opcode. +** +** If a memory allocation error has occurred prior to the calling of this +** routine, then a pointer to a dummy VdbeOp will be returned. That opcode +** is readable but not writable, though it is cast to a writable value. +** The return of a dummy opcode allows the call to continue functioning +** after an OOM fault without having to check to see if the return from +** this routine is a valid pointer. But because the dummy.opcode is 0, +** dummy will never be written to. This is verified by code inspection and +** by running with Valgrind. +*/ +SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){ + /* C89 specifies that the constant "dummy" will be initialized to all + ** zeros, which is correct. MSVC generates a warning, nevertheless. */ + static VdbeOp dummy; /* Ignore the MSVC warning about no initializer */ + assert( p->eVdbeState==VDBE_INIT_STATE ); + assert( (addr>=0 && addr<p->nOp) || p->db->mallocFailed ); + if( p->db->mallocFailed ){ + return (VdbeOp*)&dummy; + }else{ + return &p->aOp[addr]; + } +} + +/* Return the most recently added opcode +*/ +SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetLastOp(Vdbe *p){ + return sqlite3VdbeGetOp(p, p->nOp - 1); +} + +#if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) +/* +** Return an integer value for one of the parameters to the opcode pOp +** determined by character c. +*/ +static int translateP(char c, const Op *pOp){ + if( c=='1' ) return pOp->p1; + if( c=='2' ) return pOp->p2; + if( c=='3' ) return pOp->p3; + if( c=='4' ) return pOp->p4.i; + return pOp->p5; +} + +/* +** Compute a string for the "comment" field of a VDBE opcode listing. +** +** The Synopsis: field in comments in the vdbe.c source file gets converted +** to an extra string that is appended to the sqlite3OpcodeName(). In the +** absence of other comments, this synopsis becomes the comment on the opcode. +** Some translation occurs: +** +** "PX" -> "r[X]" +** "PX@PY" -> "r[X..X+Y-1]" or "r[x]" if y is 0 or 1 +** "PX@PY+1" -> "r[X..X+Y]" or "r[x]" if y is 0 +** "PY..PY" -> "r[X..Y]" or "r[x]" if y<=x +*/ +SQLITE_PRIVATE char *sqlite3VdbeDisplayComment( + sqlite3 *db, /* Optional - Oom error reporting only */ + const Op *pOp, /* The opcode to be commented */ + const char *zP4 /* Previously obtained value for P4 */ +){ + const char *zOpName; + const char *zSynopsis; + int nOpName; + int ii; + char zAlt[50]; + StrAccum x; + + sqlite3StrAccumInit(&x, 0, 0, 0, SQLITE_MAX_LENGTH); + zOpName = sqlite3OpcodeName(pOp->opcode); + nOpName = sqlite3Strlen30(zOpName); + if( zOpName[nOpName+1] ){ + int seenCom = 0; + char c; + zSynopsis = zOpName + nOpName + 1; + if( strncmp(zSynopsis,"IF ",3)==0 ){ + sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3); + zSynopsis = zAlt; + } + for(ii=0; (c = zSynopsis[ii])!=0; ii++){ + if( c=='P' ){ + c = zSynopsis[++ii]; + if( c=='4' ){ + sqlite3_str_appendall(&x, zP4); + }else if( c=='X' ){ + if( pOp->zComment && pOp->zComment[0] ){ + sqlite3_str_appendall(&x, pOp->zComment); + seenCom = 1; + break; + } + }else{ + int v1 = translateP(c, pOp); + int v2; + if( strncmp(zSynopsis+ii+1, "@P", 2)==0 ){ + ii += 3; + v2 = translateP(zSynopsis[ii], pOp); + if( strncmp(zSynopsis+ii+1,"+1",2)==0 ){ + ii += 2; + v2++; + } + if( v2<2 ){ + sqlite3_str_appendf(&x, "%d", v1); + }else{ + sqlite3_str_appendf(&x, "%d..%d", v1, v1+v2-1); + } + }else if( strncmp(zSynopsis+ii+1, "@NP", 3)==0 ){ + sqlite3_context *pCtx = pOp->p4.pCtx; + if( pOp->p4type!=P4_FUNCCTX || pCtx->argc==1 ){ + sqlite3_str_appendf(&x, "%d", v1); + }else if( pCtx->argc>1 ){ + sqlite3_str_appendf(&x, "%d..%d", v1, v1+pCtx->argc-1); + }else if( x.accError==0 ){ + assert( x.nChar>2 ); + x.nChar -= 2; + ii++; + } + ii += 3; + }else{ + sqlite3_str_appendf(&x, "%d", v1); + if( strncmp(zSynopsis+ii+1, "..P3", 4)==0 && pOp->p3==0 ){ + ii += 4; + } + } + } + }else{ + sqlite3_str_appendchar(&x, 1, c); + } + } + if( !seenCom && pOp->zComment ){ + sqlite3_str_appendf(&x, "; %s", pOp->zComment); + } + }else if( pOp->zComment ){ + sqlite3_str_appendall(&x, pOp->zComment); + } + if( (x.accError & SQLITE_NOMEM)!=0 && db!=0 ){ + sqlite3OomFault(db); + } + return sqlite3StrAccumFinish(&x); +} +#endif /* SQLITE_ENABLE_EXPLAIN_COMMENTS */ + +#if VDBE_DISPLAY_P4 && defined(SQLITE_ENABLE_CURSOR_HINTS) +/* +** Translate the P4.pExpr value for an OP_CursorHint opcode into text +** that can be displayed in the P4 column of EXPLAIN output. +*/ +static void displayP4Expr(StrAccum *p, Expr *pExpr){ + const char *zOp = 0; + switch( pExpr->op ){ + case TK_STRING: + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3_str_appendf(p, "%Q", pExpr->u.zToken); + break; + case TK_INTEGER: + sqlite3_str_appendf(p, "%d", pExpr->u.iValue); + break; + case TK_NULL: + sqlite3_str_appendf(p, "NULL"); + break; + case TK_REGISTER: { + sqlite3_str_appendf(p, "r[%d]", pExpr->iTable); + break; + } + case TK_COLUMN: { + if( pExpr->iColumn<0 ){ + sqlite3_str_appendf(p, "rowid"); + }else{ + sqlite3_str_appendf(p, "c%d", (int)pExpr->iColumn); + } + break; + } + case TK_LT: zOp = "LT"; break; + case TK_LE: zOp = "LE"; break; + case TK_GT: zOp = "GT"; break; + case TK_GE: zOp = "GE"; break; + case TK_NE: zOp = "NE"; break; + case TK_EQ: zOp = "EQ"; break; + case TK_IS: zOp = "IS"; break; + case TK_ISNOT: zOp = "ISNOT"; break; + case TK_AND: zOp = "AND"; break; + case TK_OR: zOp = "OR"; break; + case TK_PLUS: zOp = "ADD"; break; + case TK_STAR: zOp = "MUL"; break; + case TK_MINUS: zOp = "SUB"; break; + case TK_REM: zOp = "REM"; break; + case TK_BITAND: zOp = "BITAND"; break; + case TK_BITOR: zOp = "BITOR"; break; + case TK_SLASH: zOp = "DIV"; break; + case TK_LSHIFT: zOp = "LSHIFT"; break; + case TK_RSHIFT: zOp = "RSHIFT"; break; + case TK_CONCAT: zOp = "CONCAT"; break; + case TK_UMINUS: zOp = "MINUS"; break; + case TK_UPLUS: zOp = "PLUS"; break; + case TK_BITNOT: zOp = "BITNOT"; break; + case TK_NOT: zOp = "NOT"; break; + case TK_ISNULL: zOp = "ISNULL"; break; + case TK_NOTNULL: zOp = "NOTNULL"; break; + + default: + sqlite3_str_appendf(p, "%s", "expr"); + break; + } + + if( zOp ){ + sqlite3_str_appendf(p, "%s(", zOp); + displayP4Expr(p, pExpr->pLeft); + if( pExpr->pRight ){ + sqlite3_str_append(p, ",", 1); + displayP4Expr(p, pExpr->pRight); + } + sqlite3_str_append(p, ")", 1); + } +} +#endif /* VDBE_DISPLAY_P4 && defined(SQLITE_ENABLE_CURSOR_HINTS) */ + + +#if VDBE_DISPLAY_P4 +/* +** Compute a string that describes the P4 parameter for an opcode. +** Use zTemp for any required temporary buffer space. +*/ +SQLITE_PRIVATE char *sqlite3VdbeDisplayP4(sqlite3 *db, Op *pOp){ + char *zP4 = 0; + StrAccum x; + + sqlite3StrAccumInit(&x, 0, 0, 0, SQLITE_MAX_LENGTH); + switch( pOp->p4type ){ + case P4_KEYINFO: { + int j; + KeyInfo *pKeyInfo = pOp->p4.pKeyInfo; + assert( pKeyInfo->aSortFlags!=0 ); + sqlite3_str_appendf(&x, "k(%d", pKeyInfo->nKeyField); + for(j=0; j<pKeyInfo->nKeyField; j++){ + CollSeq *pColl = pKeyInfo->aColl[j]; + const char *zColl = pColl ? pColl->zName : ""; + if( strcmp(zColl, "BINARY")==0 ) zColl = "B"; + sqlite3_str_appendf(&x, ",%s%s%s", + (pKeyInfo->aSortFlags[j] & KEYINFO_ORDER_DESC) ? "-" : "", + (pKeyInfo->aSortFlags[j] & KEYINFO_ORDER_BIGNULL)? "N." : "", + zColl); + } + sqlite3_str_append(&x, ")", 1); + break; + } +#ifdef SQLITE_ENABLE_CURSOR_HINTS + case P4_EXPR: { + displayP4Expr(&x, pOp->p4.pExpr); + break; + } +#endif + case P4_COLLSEQ: { + static const char *const encnames[] = {"?", "8", "16LE", "16BE"}; + CollSeq *pColl = pOp->p4.pColl; + assert( pColl->enc<4 ); + sqlite3_str_appendf(&x, "%.18s-%s", pColl->zName, + encnames[pColl->enc]); + break; + } + case P4_FUNCDEF: { + FuncDef *pDef = pOp->p4.pFunc; + sqlite3_str_appendf(&x, "%s(%d)", pDef->zName, pDef->nArg); + break; + } + case P4_FUNCCTX: { + FuncDef *pDef = pOp->p4.pCtx->pFunc; + sqlite3_str_appendf(&x, "%s(%d)", pDef->zName, pDef->nArg); + break; + } + case P4_INT64: { + sqlite3_str_appendf(&x, "%lld", *pOp->p4.pI64); + break; + } + case P4_INT32: { + sqlite3_str_appendf(&x, "%d", pOp->p4.i); + break; + } + case P4_REAL: { + sqlite3_str_appendf(&x, "%.16g", *pOp->p4.pReal); + break; + } + case P4_MEM: { + Mem *pMem = pOp->p4.pMem; + if( pMem->flags & MEM_Str ){ + zP4 = pMem->z; + }else if( pMem->flags & (MEM_Int|MEM_IntReal) ){ + sqlite3_str_appendf(&x, "%lld", pMem->u.i); + }else if( pMem->flags & MEM_Real ){ + sqlite3_str_appendf(&x, "%.16g", pMem->u.r); + }else if( pMem->flags & MEM_Null ){ + zP4 = "NULL"; + }else{ + assert( pMem->flags & MEM_Blob ); + zP4 = "(blob)"; + } + break; + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + case P4_VTAB: { + sqlite3_vtab *pVtab = pOp->p4.pVtab->pVtab; + sqlite3_str_appendf(&x, "vtab:%p", pVtab); + break; + } +#endif + case P4_INTARRAY: { + u32 i; + u32 *ai = pOp->p4.ai; + u32 n = ai[0]; /* The first element of an INTARRAY is always the + ** count of the number of elements to follow */ + for(i=1; i<=n; i++){ + sqlite3_str_appendf(&x, "%c%u", (i==1 ? '[' : ','), ai[i]); + } + sqlite3_str_append(&x, "]", 1); + break; + } + case P4_SUBPROGRAM: { + zP4 = "program"; + break; + } + case P4_TABLE: { + zP4 = pOp->p4.pTab->zName; + break; + } + default: { + zP4 = pOp->p4.z; + } + } + if( zP4 ) sqlite3_str_appendall(&x, zP4); + if( (x.accError & SQLITE_NOMEM)!=0 ){ + sqlite3OomFault(db); + } + return sqlite3StrAccumFinish(&x); +} +#endif /* VDBE_DISPLAY_P4 */ + +/* +** Declare to the Vdbe that the BTree object at db->aDb[i] is used. +** +** The prepared statements need to know in advance the complete set of +** attached databases that will be use. A mask of these databases +** is maintained in p->btreeMask. The p->lockMask value is the subset of +** p->btreeMask of databases that will require a lock. +*/ +SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe *p, int i){ + assert( i>=0 && i<p->db->nDb && i<(int)sizeof(yDbMask)*8 ); + assert( i<(int)sizeof(p->btreeMask)*8 ); + DbMaskSet(p->btreeMask, i); + if( i!=1 && sqlite3BtreeSharable(p->db->aDb[i].pBt) ){ + DbMaskSet(p->lockMask, i); + } +} + +#if !defined(SQLITE_OMIT_SHARED_CACHE) +/* +** If SQLite is compiled to support shared-cache mode and to be threadsafe, +** this routine obtains the mutex associated with each BtShared structure +** that may be accessed by the VM passed as an argument. In doing so it also +** sets the BtShared.db member of each of the BtShared structures, ensuring +** that the correct busy-handler callback is invoked if required. +** +** If SQLite is not threadsafe but does support shared-cache mode, then +** sqlite3BtreeEnter() is invoked to set the BtShared.db variables +** of all of BtShared structures accessible via the database handle +** associated with the VM. +** +** If SQLite is not threadsafe and does not support shared-cache mode, this +** function is a no-op. +** +** The p->btreeMask field is a bitmask of all btrees that the prepared +** statement p will ever use. Let N be the number of bits in p->btreeMask +** corresponding to btrees that use shared cache. Then the runtime of +** this routine is N*N. But as N is rarely more than 1, this should not +** be a problem. +*/ +SQLITE_PRIVATE void sqlite3VdbeEnter(Vdbe *p){ + int i; + sqlite3 *db; + Db *aDb; + int nDb; + if( DbMaskAllZero(p->lockMask) ) return; /* The common case */ + db = p->db; + aDb = db->aDb; + nDb = db->nDb; + for(i=0; i<nDb; i++){ + if( i!=1 && DbMaskTest(p->lockMask,i) && ALWAYS(aDb[i].pBt!=0) ){ + sqlite3BtreeEnter(aDb[i].pBt); + } + } +} +#endif + +#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0 +/* +** Unlock all of the btrees previously locked by a call to sqlite3VdbeEnter(). +*/ +static SQLITE_NOINLINE void vdbeLeave(Vdbe *p){ + int i; + sqlite3 *db; + Db *aDb; + int nDb; + db = p->db; + aDb = db->aDb; + nDb = db->nDb; + for(i=0; i<nDb; i++){ + if( i!=1 && DbMaskTest(p->lockMask,i) && ALWAYS(aDb[i].pBt!=0) ){ + sqlite3BtreeLeave(aDb[i].pBt); + } + } +} +SQLITE_PRIVATE void sqlite3VdbeLeave(Vdbe *p){ + if( DbMaskAllZero(p->lockMask) ) return; /* The common case */ + vdbeLeave(p); +} +#endif + +#if defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) +/* +** Print a single opcode. This routine is used for debugging only. +*/ +SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE *pOut, int pc, VdbeOp *pOp){ + char *zP4; + char *zCom; + sqlite3 dummyDb; + static const char *zFormat1 = "%4d %-13s %4d %4d %4d %-13s %.2X %s\n"; + if( pOut==0 ) pOut = stdout; + sqlite3BeginBenignMalloc(); + dummyDb.mallocFailed = 1; + zP4 = sqlite3VdbeDisplayP4(&dummyDb, pOp); +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + zCom = sqlite3VdbeDisplayComment(0, pOp, zP4); +#else + zCom = 0; +#endif + /* NB: The sqlite3OpcodeName() function is implemented by code created + ** by the mkopcodeh.awk and mkopcodec.awk scripts which extract the + ** information from the vdbe.c source text */ + fprintf(pOut, zFormat1, pc, + sqlite3OpcodeName(pOp->opcode), pOp->p1, pOp->p2, pOp->p3, + zP4 ? zP4 : "", pOp->p5, + zCom ? zCom : "" + ); + fflush(pOut); + sqlite3_free(zP4); + sqlite3_free(zCom); + sqlite3EndBenignMalloc(); +} +#endif + +/* +** Initialize an array of N Mem element. +** +** This is a high-runner, so only those fields that really do need to +** be initialized are set. The Mem structure is organized so that +** the fields that get initialized are nearby and hopefully on the same +** cache line. +** +** Mem.flags = flags +** Mem.db = db +** Mem.szMalloc = 0 +** +** All other fields of Mem can safely remain uninitialized for now. They +** will be initialized before use. +*/ +static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){ + if( N>0 ){ + do{ + p->flags = flags; + p->db = db; + p->szMalloc = 0; +#ifdef SQLITE_DEBUG + p->pScopyFrom = 0; +#endif + p++; + }while( (--N)>0 ); + } +} + +/* +** Release auxiliary memory held in an array of N Mem elements. +** +** After this routine returns, all Mem elements in the array will still +** be valid. Those Mem elements that were not holding auxiliary resources +** will be unchanged. Mem elements which had something freed will be +** set to MEM_Undefined. +*/ +static void releaseMemArray(Mem *p, int N){ + if( p && N ){ + Mem *pEnd = &p[N]; + sqlite3 *db = p->db; + if( db->pnBytesFreed ){ + do{ + if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); + }while( (++p)<pEnd ); + return; + } + do{ + assert( (&p[1])==pEnd || p[0].db==p[1].db ); + assert( sqlite3VdbeCheckMemInvariants(p) ); + + /* This block is really an inlined version of sqlite3VdbeMemRelease() + ** that takes advantage of the fact that the memory cell value is + ** being set to NULL after releasing any dynamic resources. + ** + ** The justification for duplicating code is that according to + ** callgrind, this causes a certain test case to hit the CPU 4.7 + ** percent less (x86 linux, gcc version 4.1.2, -O6) than if + ** sqlite3MemRelease() were called from here. With -O2, this jumps + ** to 6.6 percent. The test case is inserting 1000 rows into a table + ** with no indexes using a single prepared INSERT statement, bind() + ** and reset(). Inserts are grouped into a transaction. + */ + testcase( p->flags & MEM_Agg ); + testcase( p->flags & MEM_Dyn ); + if( p->flags&(MEM_Agg|MEM_Dyn) ){ + testcase( (p->flags & MEM_Dyn)!=0 && p->xDel==sqlite3VdbeFrameMemDel ); + sqlite3VdbeMemRelease(p); + p->flags = MEM_Undefined; + }else if( p->szMalloc ){ + sqlite3DbNNFreeNN(db, p->zMalloc); + p->szMalloc = 0; + p->flags = MEM_Undefined; + } +#ifdef SQLITE_DEBUG + else{ + p->flags = MEM_Undefined; + } +#endif + }while( (++p)<pEnd ); + } +} + +#ifdef SQLITE_DEBUG +/* +** Verify that pFrame is a valid VdbeFrame pointer. Return true if it is +** and false if something is wrong. +** +** This routine is intended for use inside of assert() statements only. +*/ +SQLITE_PRIVATE int sqlite3VdbeFrameIsValid(VdbeFrame *pFrame){ + if( pFrame->iFrameMagic!=SQLITE_FRAME_MAGIC ) return 0; + return 1; +} +#endif + + +/* +** This is a destructor on a Mem object (which is really an sqlite3_value) +** that deletes the Frame object that is attached to it as a blob. +** +** This routine does not delete the Frame right away. It merely adds the +** frame to a list of frames to be deleted when the Vdbe halts. +*/ +SQLITE_PRIVATE void sqlite3VdbeFrameMemDel(void *pArg){ + VdbeFrame *pFrame = (VdbeFrame*)pArg; + assert( sqlite3VdbeFrameIsValid(pFrame) ); + pFrame->pParent = pFrame->v->pDelFrame; + pFrame->v->pDelFrame = pFrame; +} + +#if defined(SQLITE_ENABLE_BYTECODE_VTAB) || !defined(SQLITE_OMIT_EXPLAIN) +/* +** Locate the next opcode to be displayed in EXPLAIN or EXPLAIN +** QUERY PLAN output. +** +** Return SQLITE_ROW on success. Return SQLITE_DONE if there are no +** more opcodes to be displayed. +*/ +SQLITE_PRIVATE int sqlite3VdbeNextOpcode( + Vdbe *p, /* The statement being explained */ + Mem *pSub, /* Storage for keeping track of subprogram nesting */ + int eMode, /* 0: normal. 1: EQP. 2: TablesUsed */ + int *piPc, /* IN/OUT: Current rowid. Overwritten with next rowid */ + int *piAddr, /* OUT: Write index into (*paOp)[] here */ + Op **paOp /* OUT: Write the opcode array here */ +){ + int nRow; /* Stop when row count reaches this */ + int nSub = 0; /* Number of sub-vdbes seen so far */ + SubProgram **apSub = 0; /* Array of sub-vdbes */ + int i; /* Next instruction address */ + int rc = SQLITE_OK; /* Result code */ + Op *aOp = 0; /* Opcode array */ + int iPc; /* Rowid. Copy of value in *piPc */ + + /* When the number of output rows reaches nRow, that means the + ** listing has finished and sqlite3_step() should return SQLITE_DONE. + ** nRow is the sum of the number of rows in the main program, plus + ** the sum of the number of rows in all trigger subprograms encountered + ** so far. The nRow value will increase as new trigger subprograms are + ** encountered, but p->pc will eventually catch up to nRow. + */ + nRow = p->nOp; + if( pSub!=0 ){ + if( pSub->flags&MEM_Blob ){ + /* pSub is initiallly NULL. It is initialized to a BLOB by + ** the P4_SUBPROGRAM processing logic below */ + nSub = pSub->n/sizeof(Vdbe*); + apSub = (SubProgram **)pSub->z; + } + for(i=0; i<nSub; i++){ + nRow += apSub[i]->nOp; + } + } + iPc = *piPc; + while(1){ /* Loop exits via break */ + i = iPc++; + if( i>=nRow ){ + p->rc = SQLITE_OK; + rc = SQLITE_DONE; + break; + } + if( i<p->nOp ){ + /* The rowid is small enough that we are still in the + ** main program. */ + aOp = p->aOp; + }else{ + /* We are currently listing subprograms. Figure out which one and + ** pick up the appropriate opcode. */ + int j; + i -= p->nOp; + assert( apSub!=0 ); + assert( nSub>0 ); + for(j=0; i>=apSub[j]->nOp; j++){ + i -= apSub[j]->nOp; + assert( i<apSub[j]->nOp || j+1<nSub ); + } + aOp = apSub[j]->aOp; + } + + /* When an OP_Program opcode is encounter (the only opcode that has + ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms + ** kept in p->aMem[9].z to hold the new program - assuming this subprogram + ** has not already been seen. + */ + if( pSub!=0 && aOp[i].p4type==P4_SUBPROGRAM ){ + int nByte = (nSub+1)*sizeof(SubProgram*); + int j; + for(j=0; j<nSub; j++){ + if( apSub[j]==aOp[i].p4.pProgram ) break; + } + if( j==nSub ){ + p->rc = sqlite3VdbeMemGrow(pSub, nByte, nSub!=0); + if( p->rc!=SQLITE_OK ){ + rc = SQLITE_ERROR; + break; + } + apSub = (SubProgram **)pSub->z; + apSub[nSub++] = aOp[i].p4.pProgram; + MemSetTypeFlag(pSub, MEM_Blob); + pSub->n = nSub*sizeof(SubProgram*); + nRow += aOp[i].p4.pProgram->nOp; + } + } + if( eMode==0 ) break; +#ifdef SQLITE_ENABLE_BYTECODE_VTAB + if( eMode==2 ){ + Op *pOp = aOp + i; + if( pOp->opcode==OP_OpenRead ) break; + if( pOp->opcode==OP_OpenWrite && (pOp->p5 & OPFLAG_P2ISREG)==0 ) break; + if( pOp->opcode==OP_ReopenIdx ) break; + }else +#endif + { + assert( eMode==1 ); + if( aOp[i].opcode==OP_Explain ) break; + if( aOp[i].opcode==OP_Init && iPc>1 ) break; + } + } + *piPc = iPc; + *piAddr = i; + *paOp = aOp; + return rc; +} +#endif /* SQLITE_ENABLE_BYTECODE_VTAB || !SQLITE_OMIT_EXPLAIN */ + + +/* +** Delete a VdbeFrame object and its contents. VdbeFrame objects are +** allocated by the OP_Program opcode in sqlite3VdbeExec(). +*/ +SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame *p){ + int i; + Mem *aMem = VdbeFrameMem(p); + VdbeCursor **apCsr = (VdbeCursor **)&aMem[p->nChildMem]; + assert( sqlite3VdbeFrameIsValid(p) ); + for(i=0; i<p->nChildCsr; i++){ + if( apCsr[i] ) sqlite3VdbeFreeCursorNN(p->v, apCsr[i]); + } + releaseMemArray(aMem, p->nChildMem); + sqlite3VdbeDeleteAuxData(p->v->db, &p->pAuxData, -1, 0); + sqlite3DbFree(p->v->db, p); +} + +#ifndef SQLITE_OMIT_EXPLAIN +/* +** Give a listing of the program in the virtual machine. +** +** The interface is the same as sqlite3VdbeExec(). But instead of +** running the code, it invokes the callback once for each instruction. +** This feature is used to implement "EXPLAIN". +** +** When p->explain==1, each instruction is listed. When +** p->explain==2, only OP_Explain instructions are listed and these +** are shown in a different format. p->explain==2 is used to implement +** EXPLAIN QUERY PLAN. +** 2018-04-24: In p->explain==2 mode, the OP_Init opcodes of triggers +** are also shown, so that the boundaries between the main program and +** each trigger are clear. +** +** When p->explain==1, first the main program is listed, then each of +** the trigger subprograms are listed one by one. +*/ +SQLITE_PRIVATE int sqlite3VdbeList( + Vdbe *p /* The VDBE */ +){ + Mem *pSub = 0; /* Memory cell hold array of subprogs */ + sqlite3 *db = p->db; /* The database connection */ + int i; /* Loop counter */ + int rc = SQLITE_OK; /* Return code */ + Mem *pMem = &p->aMem[1]; /* First Mem of result set */ + int bListSubprogs = (p->explain==1 || (db->flags & SQLITE_TriggerEQP)!=0); + Op *aOp; /* Array of opcodes */ + Op *pOp; /* Current opcode */ + + assert( p->explain ); + assert( p->eVdbeState==VDBE_RUN_STATE ); + assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM ); + + /* Even though this opcode does not use dynamic strings for + ** the result, result columns may become dynamic if the user calls + ** sqlite3_column_text16(), causing a translation to UTF-16 encoding. + */ + releaseMemArray(pMem, 8); + + if( p->rc==SQLITE_NOMEM ){ + /* This happens if a malloc() inside a call to sqlite3_column_text() or + ** sqlite3_column_text16() failed. */ + sqlite3OomFault(db); + return SQLITE_ERROR; + } + + if( bListSubprogs ){ + /* The first 8 memory cells are used for the result set. So we will + ** commandeer the 9th cell to use as storage for an array of pointers + ** to trigger subprograms. The VDBE is guaranteed to have at least 9 + ** cells. */ + assert( p->nMem>9 ); + pSub = &p->aMem[9]; + }else{ + pSub = 0; + } + + /* Figure out which opcode is next to display */ + rc = sqlite3VdbeNextOpcode(p, pSub, p->explain==2, &p->pc, &i, &aOp); + + if( rc==SQLITE_OK ){ + pOp = aOp + i; + if( AtomicLoad(&db->u1.isInterrupted) ){ + p->rc = SQLITE_INTERRUPT; + rc = SQLITE_ERROR; + sqlite3VdbeError(p, sqlite3ErrStr(p->rc)); + }else{ + char *zP4 = sqlite3VdbeDisplayP4(db, pOp); + if( p->explain==2 ){ + sqlite3VdbeMemSetInt64(pMem, pOp->p1); + sqlite3VdbeMemSetInt64(pMem+1, pOp->p2); + sqlite3VdbeMemSetInt64(pMem+2, pOp->p3); + sqlite3VdbeMemSetStr(pMem+3, zP4, -1, SQLITE_UTF8, sqlite3_free); + assert( p->nResColumn==4 ); + }else{ + sqlite3VdbeMemSetInt64(pMem+0, i); + sqlite3VdbeMemSetStr(pMem+1, (char*)sqlite3OpcodeName(pOp->opcode), + -1, SQLITE_UTF8, SQLITE_STATIC); + sqlite3VdbeMemSetInt64(pMem+2, pOp->p1); + sqlite3VdbeMemSetInt64(pMem+3, pOp->p2); + sqlite3VdbeMemSetInt64(pMem+4, pOp->p3); + /* pMem+5 for p4 is done last */ + sqlite3VdbeMemSetInt64(pMem+6, pOp->p5); +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + { + char *zCom = sqlite3VdbeDisplayComment(db, pOp, zP4); + sqlite3VdbeMemSetStr(pMem+7, zCom, -1, SQLITE_UTF8, sqlite3_free); + } +#else + sqlite3VdbeMemSetNull(pMem+7); +#endif + sqlite3VdbeMemSetStr(pMem+5, zP4, -1, SQLITE_UTF8, sqlite3_free); + assert( p->nResColumn==8 ); + } + p->pResultRow = pMem; + if( db->mallocFailed ){ + p->rc = SQLITE_NOMEM; + rc = SQLITE_ERROR; + }else{ + p->rc = SQLITE_OK; + rc = SQLITE_ROW; + } + } + } + return rc; +} +#endif /* SQLITE_OMIT_EXPLAIN */ + +#ifdef SQLITE_DEBUG +/* +** Print the SQL that was used to generate a VDBE program. +*/ +SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe *p){ + const char *z = 0; + if( p->zSql ){ + z = p->zSql; + }else if( p->nOp>=1 ){ + const VdbeOp *pOp = &p->aOp[0]; + if( pOp->opcode==OP_Init && pOp->p4.z!=0 ){ + z = pOp->p4.z; + while( sqlite3Isspace(*z) ) z++; + } + } + if( z ) printf("SQL: [%s]\n", z); +} +#endif + +#if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE) +/* +** Print an IOTRACE message showing SQL content. +*/ +SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe *p){ + int nOp = p->nOp; + VdbeOp *pOp; + if( sqlite3IoTrace==0 ) return; + if( nOp<1 ) return; + pOp = &p->aOp[0]; + if( pOp->opcode==OP_Init && pOp->p4.z!=0 ){ + int i, j; + char z[1000]; + sqlite3_snprintf(sizeof(z), z, "%s", pOp->p4.z); + for(i=0; sqlite3Isspace(z[i]); i++){} + for(j=0; z[i]; i++){ + if( sqlite3Isspace(z[i]) ){ + if( z[i-1]!=' ' ){ + z[j++] = ' '; + } + }else{ + z[j++] = z[i]; + } + } + z[j] = 0; + sqlite3IoTrace("SQL %s\n", z); + } +} +#endif /* !SQLITE_OMIT_TRACE && SQLITE_ENABLE_IOTRACE */ + +/* An instance of this object describes bulk memory available for use +** by subcomponents of a prepared statement. Space is allocated out +** of a ReusableSpace object by the allocSpace() routine below. +*/ +struct ReusableSpace { + u8 *pSpace; /* Available memory */ + sqlite3_int64 nFree; /* Bytes of available memory */ + sqlite3_int64 nNeeded; /* Total bytes that could not be allocated */ +}; + +/* Try to allocate nByte bytes of 8-byte aligned bulk memory for pBuf +** from the ReusableSpace object. Return a pointer to the allocated +** memory on success. If insufficient memory is available in the +** ReusableSpace object, increase the ReusableSpace.nNeeded +** value by the amount needed and return NULL. +** +** If pBuf is not initially NULL, that means that the memory has already +** been allocated by a prior call to this routine, so just return a copy +** of pBuf and leave ReusableSpace unchanged. +** +** This allocator is employed to repurpose unused slots at the end of the +** opcode array of prepared state for other memory needs of the prepared +** statement. +*/ +static void *allocSpace( + struct ReusableSpace *p, /* Bulk memory available for allocation */ + void *pBuf, /* Pointer to a prior allocation */ + sqlite3_int64 nByte /* Bytes of memory needed. */ +){ + assert( EIGHT_BYTE_ALIGNMENT(p->pSpace) ); + if( pBuf==0 ){ + nByte = ROUND8P(nByte); + if( nByte <= p->nFree ){ + p->nFree -= nByte; + pBuf = &p->pSpace[p->nFree]; + }else{ + p->nNeeded += nByte; + } + } + assert( EIGHT_BYTE_ALIGNMENT(pBuf) ); + return pBuf; +} + +/* +** Rewind the VDBE back to the beginning in preparation for +** running it. +*/ +SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){ +#if defined(SQLITE_DEBUG) + int i; +#endif + assert( p!=0 ); + assert( p->eVdbeState==VDBE_INIT_STATE + || p->eVdbeState==VDBE_READY_STATE + || p->eVdbeState==VDBE_HALT_STATE ); + + /* There should be at least one opcode. + */ + assert( p->nOp>0 ); + + p->eVdbeState = VDBE_READY_STATE; + +#ifdef SQLITE_DEBUG + for(i=0; i<p->nMem; i++){ + assert( p->aMem[i].db==p->db ); + } +#endif + p->pc = -1; + p->rc = SQLITE_OK; + p->errorAction = OE_Abort; + p->nChange = 0; + p->cacheCtr = 1; + p->minWriteFileFormat = 255; + p->iStatement = 0; + p->nFkConstraint = 0; +#ifdef VDBE_PROFILE + for(i=0; i<p->nOp; i++){ + p->aOp[i].nExec = 0; + p->aOp[i].nCycle = 0; + } +#endif +} + +/* +** Prepare a virtual machine for execution for the first time after +** creating the virtual machine. This involves things such +** as allocating registers and initializing the program counter. +** After the VDBE has be prepped, it can be executed by one or more +** calls to sqlite3VdbeExec(). +** +** This function may be called exactly once on each virtual machine. +** After this routine is called the VM has been "packaged" and is ready +** to run. After this routine is called, further calls to +** sqlite3VdbeAddOp() functions are prohibited. This routine disconnects +** the Vdbe from the Parse object that helped generate it so that the +** the Vdbe becomes an independent entity and the Parse object can be +** destroyed. +** +** Use the sqlite3VdbeRewind() procedure to restore a virtual machine back +** to its initial state after it has been run. +*/ +SQLITE_PRIVATE void sqlite3VdbeMakeReady( + Vdbe *p, /* The VDBE */ + Parse *pParse /* Parsing context */ +){ + sqlite3 *db; /* The database connection */ + int nVar; /* Number of parameters */ + int nMem; /* Number of VM memory registers */ + int nCursor; /* Number of cursors required */ + int nArg; /* Number of arguments in subprograms */ + int n; /* Loop counter */ + struct ReusableSpace x; /* Reusable bulk memory */ + + assert( p!=0 ); + assert( p->nOp>0 ); + assert( pParse!=0 ); + assert( p->eVdbeState==VDBE_INIT_STATE ); + assert( pParse==p->pParse ); + p->pVList = pParse->pVList; + pParse->pVList = 0; + db = p->db; + assert( db->mallocFailed==0 ); + nVar = pParse->nVar; + nMem = pParse->nMem; + nCursor = pParse->nTab; + nArg = pParse->nMaxArg; + + /* Each cursor uses a memory cell. The first cursor (cursor 0) can + ** use aMem[0] which is not otherwise used by the VDBE program. Allocate + ** space at the end of aMem[] for cursors 1 and greater. + ** See also: allocateCursor(). + */ + nMem += nCursor; + if( nCursor==0 && nMem>0 ) nMem++; /* Space for aMem[0] even if not used */ + + /* Figure out how much reusable memory is available at the end of the + ** opcode array. This extra memory will be reallocated for other elements + ** of the prepared statement. + */ + n = ROUND8P(sizeof(Op)*p->nOp); /* Bytes of opcode memory used */ + x.pSpace = &((u8*)p->aOp)[n]; /* Unused opcode memory */ + assert( EIGHT_BYTE_ALIGNMENT(x.pSpace) ); + x.nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused memory */ + assert( x.nFree>=0 ); + assert( EIGHT_BYTE_ALIGNMENT(&x.pSpace[x.nFree]) ); + + resolveP2Values(p, &nArg); + p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); + if( pParse->explain ){ + if( nMem<10 ) nMem = 10; + p->explain = pParse->explain; + p->nResColumn = 12 - 4*p->explain; + } + p->expired = 0; + + /* Memory for registers, parameters, cursor, etc, is allocated in one or two + ** passes. On the first pass, we try to reuse unused memory at the + ** end of the opcode array. If we are unable to satisfy all memory + ** requirements by reusing the opcode array tail, then the second + ** pass will fill in the remainder using a fresh memory allocation. + ** + ** This two-pass approach that reuses as much memory as possible from + ** the leftover memory at the end of the opcode array. This can significantly + ** reduce the amount of memory held by a prepared statement. + */ + x.nNeeded = 0; + p->aMem = allocSpace(&x, 0, nMem*sizeof(Mem)); + p->aVar = allocSpace(&x, 0, nVar*sizeof(Mem)); + p->apArg = allocSpace(&x, 0, nArg*sizeof(Mem*)); + p->apCsr = allocSpace(&x, 0, nCursor*sizeof(VdbeCursor*)); + if( x.nNeeded ){ + x.pSpace = p->pFree = sqlite3DbMallocRawNN(db, x.nNeeded); + x.nFree = x.nNeeded; + if( !db->mallocFailed ){ + p->aMem = allocSpace(&x, p->aMem, nMem*sizeof(Mem)); + p->aVar = allocSpace(&x, p->aVar, nVar*sizeof(Mem)); + p->apArg = allocSpace(&x, p->apArg, nArg*sizeof(Mem*)); + p->apCsr = allocSpace(&x, p->apCsr, nCursor*sizeof(VdbeCursor*)); + } + } + + if( db->mallocFailed ){ + p->nVar = 0; + p->nCursor = 0; + p->nMem = 0; + }else{ + p->nCursor = nCursor; + p->nVar = (ynVar)nVar; + initMemArray(p->aVar, nVar, db, MEM_Null); + p->nMem = nMem; + initMemArray(p->aMem, nMem, db, MEM_Undefined); + memset(p->apCsr, 0, nCursor*sizeof(VdbeCursor*)); + } + sqlite3VdbeRewind(p); +} + +/* +** Close a VDBE cursor and release all the resources that cursor +** happens to hold. +*/ +SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ + if( pCx ) sqlite3VdbeFreeCursorNN(p,pCx); +} +static SQLITE_NOINLINE void freeCursorWithCache(Vdbe *p, VdbeCursor *pCx){ + VdbeTxtBlbCache *pCache = pCx->pCache; + assert( pCx->colCache ); + pCx->colCache = 0; + pCx->pCache = 0; + if( pCache->pCValue ){ + sqlite3RCStrUnref(pCache->pCValue); + pCache->pCValue = 0; + } + sqlite3DbFree(p->db, pCache); + sqlite3VdbeFreeCursorNN(p, pCx); +} +SQLITE_PRIVATE void sqlite3VdbeFreeCursorNN(Vdbe *p, VdbeCursor *pCx){ + if( pCx->colCache ){ + freeCursorWithCache(p, pCx); + return; + } + switch( pCx->eCurType ){ + case CURTYPE_SORTER: { + sqlite3VdbeSorterClose(p->db, pCx); + break; + } + case CURTYPE_BTREE: { + assert( pCx->uc.pCursor!=0 ); + sqlite3BtreeCloseCursor(pCx->uc.pCursor); + break; + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + case CURTYPE_VTAB: { + sqlite3_vtab_cursor *pVCur = pCx->uc.pVCur; + const sqlite3_module *pModule = pVCur->pVtab->pModule; + assert( pVCur->pVtab->nRef>0 ); + pVCur->pVtab->nRef--; + pModule->xClose(pVCur); + break; + } +#endif + } +} + +/* +** Close all cursors in the current frame. +*/ +static void closeCursorsInFrame(Vdbe *p){ + int i; + for(i=0; i<p->nCursor; i++){ + VdbeCursor *pC = p->apCsr[i]; + if( pC ){ + sqlite3VdbeFreeCursorNN(p, pC); + p->apCsr[i] = 0; + } + } +} + +/* +** Copy the values stored in the VdbeFrame structure to its Vdbe. This +** is used, for example, when a trigger sub-program is halted to restore +** control to the main program. +*/ +SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ + Vdbe *v = pFrame->v; + closeCursorsInFrame(v); + v->aOp = pFrame->aOp; + v->nOp = pFrame->nOp; + v->aMem = pFrame->aMem; + v->nMem = pFrame->nMem; + v->apCsr = pFrame->apCsr; + v->nCursor = pFrame->nCursor; + v->db->lastRowid = pFrame->lastRowid; + v->nChange = pFrame->nChange; + v->db->nChange = pFrame->nDbChange; + sqlite3VdbeDeleteAuxData(v->db, &v->pAuxData, -1, 0); + v->pAuxData = pFrame->pAuxData; + pFrame->pAuxData = 0; + return pFrame->pc; +} + +/* +** Close all cursors. +** +** Also release any dynamic memory held by the VM in the Vdbe.aMem memory +** cell array. This is necessary as the memory cell array may contain +** pointers to VdbeFrame objects, which may in turn contain pointers to +** open cursors. +*/ +static void closeAllCursors(Vdbe *p){ + if( p->pFrame ){ + VdbeFrame *pFrame; + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + sqlite3VdbeFrameRestore(pFrame); + p->pFrame = 0; + p->nFrame = 0; + } + assert( p->nFrame==0 ); + closeCursorsInFrame(p); + releaseMemArray(p->aMem, p->nMem); + while( p->pDelFrame ){ + VdbeFrame *pDel = p->pDelFrame; + p->pDelFrame = pDel->pParent; + sqlite3VdbeFrameDelete(pDel); + } + + /* Delete any auxdata allocations made by the VM */ + if( p->pAuxData ) sqlite3VdbeDeleteAuxData(p->db, &p->pAuxData, -1, 0); + assert( p->pAuxData==0 ); +} + +/* +** Set the number of result columns that will be returned by this SQL +** statement. This is now set at compile time, rather than during +** execution of the vdbe program so that sqlite3_column_count() can +** be called on an SQL statement before sqlite3_step(). +*/ +SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){ + int n; + sqlite3 *db = p->db; + + if( p->nResAlloc ){ + releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); + sqlite3DbFree(db, p->aColName); + } + n = nResColumn*COLNAME_N; + p->nResColumn = p->nResAlloc = (u16)nResColumn; + p->aColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n ); + if( p->aColName==0 ) return; + initMemArray(p->aColName, n, db, MEM_Null); +} + +/* +** Set the name of the idx'th column to be returned by the SQL statement. +** zName must be a pointer to a nul terminated string. +** +** This call must be made after a call to sqlite3VdbeSetNumCols(). +** +** The final parameter, xDel, must be one of SQLITE_DYNAMIC, SQLITE_STATIC +** or SQLITE_TRANSIENT. If it is SQLITE_DYNAMIC, then the buffer pointed +** to by zName will be freed by sqlite3DbFree() when the vdbe is destroyed. +*/ +SQLITE_PRIVATE int sqlite3VdbeSetColName( + Vdbe *p, /* Vdbe being configured */ + int idx, /* Index of column zName applies to */ + int var, /* One of the COLNAME_* constants */ + const char *zName, /* Pointer to buffer containing name */ + void (*xDel)(void*) /* Memory management strategy for zName */ +){ + int rc; + Mem *pColName; + assert( idx<p->nResAlloc ); + assert( var<COLNAME_N ); + if( p->db->mallocFailed ){ + assert( !zName || xDel!=SQLITE_DYNAMIC ); + return SQLITE_NOMEM_BKPT; + } + assert( p->aColName!=0 ); + pColName = &(p->aColName[idx+var*p->nResAlloc]); + rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel); + assert( rc!=0 || !zName || (pColName->flags&MEM_Term)!=0 ); + return rc; +} + +/* +** A read or write transaction may or may not be active on database handle +** db. If a transaction is active, commit it. If there is a +** write-transaction spanning more than one database file, this routine +** takes care of the super-journal trickery. +*/ +static int vdbeCommit(sqlite3 *db, Vdbe *p){ + int i; + int nTrans = 0; /* Number of databases with an active write-transaction + ** that are candidates for a two-phase commit using a + ** super-journal */ + int rc = SQLITE_OK; + int needXcommit = 0; + +#ifdef SQLITE_OMIT_VIRTUALTABLE + /* With this option, sqlite3VtabSync() is defined to be simply + ** SQLITE_OK so p is not used. + */ + UNUSED_PARAMETER(p); +#endif + + /* Before doing anything else, call the xSync() callback for any + ** virtual module tables written in this transaction. This has to + ** be done before determining whether a super-journal file is + ** required, as an xSync() callback may add an attached database + ** to the transaction. + */ + rc = sqlite3VtabSync(db, p); + + /* This loop determines (a) if the commit hook should be invoked and + ** (b) how many database files have open write transactions, not + ** including the temp database. (b) is important because if more than + ** one database file has an open write transaction, a super-journal + ** file is required for an atomic commit. + */ + for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){ + /* Whether or not a database might need a super-journal depends upon + ** its journal mode (among other things). This matrix determines which + ** journal modes use a super-journal and which do not */ + static const u8 aMJNeeded[] = { + /* DELETE */ 1, + /* PERSIST */ 1, + /* OFF */ 0, + /* TRUNCATE */ 1, + /* MEMORY */ 0, + /* WAL */ 0 + }; + Pager *pPager; /* Pager associated with pBt */ + needXcommit = 1; + sqlite3BtreeEnter(pBt); + pPager = sqlite3BtreePager(pBt); + if( db->aDb[i].safety_level!=PAGER_SYNCHRONOUS_OFF + && aMJNeeded[sqlite3PagerGetJournalMode(pPager)] + && sqlite3PagerIsMemdb(pPager)==0 + ){ + assert( i!=1 ); + nTrans++; + } + rc = sqlite3PagerExclusiveLock(pPager); + sqlite3BtreeLeave(pBt); + } + } + if( rc!=SQLITE_OK ){ + return rc; + } + + /* If there are any write-transactions at all, invoke the commit hook */ + if( needXcommit && db->xCommitCallback ){ + rc = db->xCommitCallback(db->pCommitArg); + if( rc ){ + return SQLITE_CONSTRAINT_COMMITHOOK; + } + } + + /* The simple case - no more than one database file (not counting the + ** TEMP database) has a transaction active. There is no need for the + ** super-journal. + ** + ** If the return value of sqlite3BtreeGetFilename() is a zero length + ** string, it means the main database is :memory: or a temp file. In + ** that case we do not support atomic multi-file commits, so use the + ** simple case then too. + */ + if( 0==sqlite3Strlen30(sqlite3BtreeGetFilename(db->aDb[0].pBt)) + || nTrans<=1 + ){ + for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + rc = sqlite3BtreeCommitPhaseOne(pBt, 0); + } + } + + /* Do the commit only if all databases successfully complete phase 1. + ** If one of the BtreeCommitPhaseOne() calls fails, this indicates an + ** IO error while deleting or truncating a journal file. It is unlikely, + ** but could happen. In this case abandon processing and return the error. + */ + for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + rc = sqlite3BtreeCommitPhaseTwo(pBt, 0); + } + } + if( rc==SQLITE_OK ){ + sqlite3VtabCommit(db); + } + } + + /* The complex case - There is a multi-file write-transaction active. + ** This requires a super-journal file to ensure the transaction is + ** committed atomically. + */ +#ifndef SQLITE_OMIT_DISKIO + else{ + sqlite3_vfs *pVfs = db->pVfs; + char *zSuper = 0; /* File-name for the super-journal */ + char const *zMainFile = sqlite3BtreeGetFilename(db->aDb[0].pBt); + sqlite3_file *pSuperJrnl = 0; + i64 offset = 0; + int res; + int retryCount = 0; + int nMainFile; + + /* Select a super-journal file name */ + nMainFile = sqlite3Strlen30(zMainFile); + zSuper = sqlite3MPrintf(db, "%.4c%s%.16c", 0,zMainFile,0); + if( zSuper==0 ) return SQLITE_NOMEM_BKPT; + zSuper += 4; + do { + u32 iRandom; + if( retryCount ){ + if( retryCount>100 ){ + sqlite3_log(SQLITE_FULL, "MJ delete: %s", zSuper); + sqlite3OsDelete(pVfs, zSuper, 0); + break; + }else if( retryCount==1 ){ + sqlite3_log(SQLITE_FULL, "MJ collide: %s", zSuper); + } + } + retryCount++; + sqlite3_randomness(sizeof(iRandom), &iRandom); + sqlite3_snprintf(13, &zSuper[nMainFile], "-mj%06X9%02X", + (iRandom>>8)&0xffffff, iRandom&0xff); + /* The antipenultimate character of the super-journal name must + ** be "9" to avoid name collisions when using 8+3 filenames. */ + assert( zSuper[sqlite3Strlen30(zSuper)-3]=='9' ); + sqlite3FileSuffix3(zMainFile, zSuper); + rc = sqlite3OsAccess(pVfs, zSuper, SQLITE_ACCESS_EXISTS, &res); + }while( rc==SQLITE_OK && res ); + if( rc==SQLITE_OK ){ + /* Open the super-journal. */ + rc = sqlite3OsOpenMalloc(pVfs, zSuper, &pSuperJrnl, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE| + SQLITE_OPEN_EXCLUSIVE|SQLITE_OPEN_SUPER_JOURNAL, 0 + ); + } + if( rc!=SQLITE_OK ){ + sqlite3DbFree(db, zSuper-4); + return rc; + } + + /* Write the name of each database file in the transaction into the new + ** super-journal file. If an error occurs at this point close + ** and delete the super-journal file. All the individual journal files + ** still have 'null' as the super-journal pointer, so they will roll + ** back independently if a failure occurs. + */ + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){ + char const *zFile = sqlite3BtreeGetJournalname(pBt); + if( zFile==0 ){ + continue; /* Ignore TEMP and :memory: databases */ + } + assert( zFile[0]!=0 ); + rc = sqlite3OsWrite(pSuperJrnl, zFile, sqlite3Strlen30(zFile)+1,offset); + offset += sqlite3Strlen30(zFile)+1; + if( rc!=SQLITE_OK ){ + sqlite3OsCloseFree(pSuperJrnl); + sqlite3OsDelete(pVfs, zSuper, 0); + sqlite3DbFree(db, zSuper-4); + return rc; + } + } + } + + /* Sync the super-journal file. If the IOCAP_SEQUENTIAL device + ** flag is set this is not required. + */ + if( 0==(sqlite3OsDeviceCharacteristics(pSuperJrnl)&SQLITE_IOCAP_SEQUENTIAL) + && SQLITE_OK!=(rc = sqlite3OsSync(pSuperJrnl, SQLITE_SYNC_NORMAL)) + ){ + sqlite3OsCloseFree(pSuperJrnl); + sqlite3OsDelete(pVfs, zSuper, 0); + sqlite3DbFree(db, zSuper-4); + return rc; + } + + /* Sync all the db files involved in the transaction. The same call + ** sets the super-journal pointer in each individual journal. If + ** an error occurs here, do not delete the super-journal file. + ** + ** If the error occurs during the first call to + ** sqlite3BtreeCommitPhaseOne(), then there is a chance that the + ** super-journal file will be orphaned. But we cannot delete it, + ** in case the super-journal file name was written into the journal + ** file before the failure occurred. + */ + for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + rc = sqlite3BtreeCommitPhaseOne(pBt, zSuper); + } + } + sqlite3OsCloseFree(pSuperJrnl); + assert( rc!=SQLITE_BUSY ); + if( rc!=SQLITE_OK ){ + sqlite3DbFree(db, zSuper-4); + return rc; + } + + /* Delete the super-journal file. This commits the transaction. After + ** doing this the directory is synced again before any individual + ** transaction files are deleted. + */ + rc = sqlite3OsDelete(pVfs, zSuper, 1); + sqlite3DbFree(db, zSuper-4); + zSuper = 0; + if( rc ){ + return rc; + } + + /* All files and directories have already been synced, so the following + ** calls to sqlite3BtreeCommitPhaseTwo() are only closing files and + ** deleting or truncating journals. If something goes wrong while + ** this is happening we don't really care. The integrity of the + ** transaction is already guaranteed, but some stray 'cold' journals + ** may be lying around. Returning an error code won't help matters. + */ + disable_simulated_io_errors(); + sqlite3BeginBenignMalloc(); + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + sqlite3BtreeCommitPhaseTwo(pBt, 1); + } + } + sqlite3EndBenignMalloc(); + enable_simulated_io_errors(); + + sqlite3VtabCommit(db); + } +#endif + + return rc; +} + +/* +** This routine checks that the sqlite3.nVdbeActive count variable +** matches the number of vdbe's in the list sqlite3.pVdbe that are +** currently active. An assertion fails if the two counts do not match. +** This is an internal self-check only - it is not an essential processing +** step. +** +** This is a no-op if NDEBUG is defined. +*/ +#ifndef NDEBUG +static void checkActiveVdbeCnt(sqlite3 *db){ + Vdbe *p; + int cnt = 0; + int nWrite = 0; + int nRead = 0; + p = db->pVdbe; + while( p ){ + if( sqlite3_stmt_busy((sqlite3_stmt*)p) ){ + cnt++; + if( p->readOnly==0 ) nWrite++; + if( p->bIsReader ) nRead++; + } + p = p->pVNext; + } + assert( cnt==db->nVdbeActive ); + assert( nWrite==db->nVdbeWrite ); + assert( nRead==db->nVdbeRead ); +} +#else +#define checkActiveVdbeCnt(x) +#endif + +/* +** If the Vdbe passed as the first argument opened a statement-transaction, +** close it now. Argument eOp must be either SAVEPOINT_ROLLBACK or +** SAVEPOINT_RELEASE. If it is SAVEPOINT_ROLLBACK, then the statement +** transaction is rolled back. If eOp is SAVEPOINT_RELEASE, then the +** statement transaction is committed. +** +** If an IO error occurs, an SQLITE_IOERR_XXX error code is returned. +** Otherwise SQLITE_OK. +*/ +static SQLITE_NOINLINE int vdbeCloseStatement(Vdbe *p, int eOp){ + sqlite3 *const db = p->db; + int rc = SQLITE_OK; + int i; + const int iSavepoint = p->iStatement-1; + + assert( eOp==SAVEPOINT_ROLLBACK || eOp==SAVEPOINT_RELEASE); + assert( db->nStatement>0 ); + assert( p->iStatement==(db->nStatement+db->nSavepoint) ); + + for(i=0; i<db->nDb; i++){ + int rc2 = SQLITE_OK; + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + if( eOp==SAVEPOINT_ROLLBACK ){ + rc2 = sqlite3BtreeSavepoint(pBt, SAVEPOINT_ROLLBACK, iSavepoint); + } + if( rc2==SQLITE_OK ){ + rc2 = sqlite3BtreeSavepoint(pBt, SAVEPOINT_RELEASE, iSavepoint); + } + if( rc==SQLITE_OK ){ + rc = rc2; + } + } + } + db->nStatement--; + p->iStatement = 0; + + if( rc==SQLITE_OK ){ + if( eOp==SAVEPOINT_ROLLBACK ){ + rc = sqlite3VtabSavepoint(db, SAVEPOINT_ROLLBACK, iSavepoint); + } + if( rc==SQLITE_OK ){ + rc = sqlite3VtabSavepoint(db, SAVEPOINT_RELEASE, iSavepoint); + } + } + + /* If the statement transaction is being rolled back, also restore the + ** database handles deferred constraint counter to the value it had when + ** the statement transaction was opened. */ + if( eOp==SAVEPOINT_ROLLBACK ){ + db->nDeferredCons = p->nStmtDefCons; + db->nDeferredImmCons = p->nStmtDefImmCons; + } + return rc; +} +SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){ + if( p->db->nStatement && p->iStatement ){ + return vdbeCloseStatement(p, eOp); + } + return SQLITE_OK; +} + + +/* +** This function is called when a transaction opened by the database +** handle associated with the VM passed as an argument is about to be +** committed. If there are outstanding deferred foreign key constraint +** violations, return SQLITE_ERROR. Otherwise, SQLITE_OK. +** +** If there are outstanding FK violations and this function returns +** SQLITE_ERROR, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY +** and write an error message to it. Then return SQLITE_ERROR. +*/ +#ifndef SQLITE_OMIT_FOREIGN_KEY +SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ + sqlite3 *db = p->db; + if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0) + || (!deferred && p->nFkConstraint>0) + ){ + p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; + p->errorAction = OE_Abort; + sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR; + return SQLITE_CONSTRAINT_FOREIGNKEY; + } + return SQLITE_OK; +} +#endif + +/* +** This routine is called the when a VDBE tries to halt. If the VDBE +** has made changes and is in autocommit mode, then commit those +** changes. If a rollback is needed, then do the rollback. +** +** This routine is the only way to move the sqlite3eOpenState of a VM from +** SQLITE_STATE_RUN to SQLITE_STATE_HALT. It is harmless to +** call this on a VM that is in the SQLITE_STATE_HALT state. +** +** Return an error code. If the commit could not complete because of +** lock contention, return SQLITE_BUSY. If SQLITE_BUSY is returned, it +** means the close did not happen and needs to be repeated. +*/ +SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ + int rc; /* Used to store transient return codes */ + sqlite3 *db = p->db; + + /* This function contains the logic that determines if a statement or + ** transaction will be committed or rolled back as a result of the + ** execution of this virtual machine. + ** + ** If any of the following errors occur: + ** + ** SQLITE_NOMEM + ** SQLITE_IOERR + ** SQLITE_FULL + ** SQLITE_INTERRUPT + ** + ** Then the internal cache might have been left in an inconsistent + ** state. We need to rollback the statement transaction, if there is + ** one, or the complete transaction if there is no statement transaction. + */ + + assert( p->eVdbeState==VDBE_RUN_STATE ); + if( db->mallocFailed ){ + p->rc = SQLITE_NOMEM_BKPT; + } + closeAllCursors(p); + checkActiveVdbeCnt(db); + + /* No commit or rollback needed if the program never started or if the + ** SQL statement does not read or write a database file. */ + if( p->bIsReader ){ + int mrc; /* Primary error code from p->rc */ + int eStatementOp = 0; + int isSpecialError; /* Set to true if a 'special' error */ + + /* Lock all btrees used by the statement */ + sqlite3VdbeEnter(p); + + /* Check for one of the special errors */ + if( p->rc ){ + mrc = p->rc & 0xff; + isSpecialError = mrc==SQLITE_NOMEM + || mrc==SQLITE_IOERR + || mrc==SQLITE_INTERRUPT + || mrc==SQLITE_FULL; + }else{ + mrc = isSpecialError = 0; + } + if( isSpecialError ){ + /* If the query was read-only and the error code is SQLITE_INTERRUPT, + ** no rollback is necessary. Otherwise, at least a savepoint + ** transaction must be rolled back to restore the database to a + ** consistent state. + ** + ** Even if the statement is read-only, it is important to perform + ** a statement or transaction rollback operation. If the error + ** occurred while writing to the journal, sub-journal or database + ** file as part of an effort to free up cache space (see function + ** pagerStress() in pager.c), the rollback is required to restore + ** the pager to a consistent state. + */ + if( !p->readOnly || mrc!=SQLITE_INTERRUPT ){ + if( (mrc==SQLITE_NOMEM || mrc==SQLITE_FULL) && p->usesStmtJournal ){ + eStatementOp = SAVEPOINT_ROLLBACK; + }else{ + /* We are forced to roll back the active transaction. Before doing + ** so, abort any other statements this handle currently has active. + */ + sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); + sqlite3CloseSavepoints(db); + db->autoCommit = 1; + p->nChange = 0; + } + } + } + + /* Check for immediate foreign key violations. */ + if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ + sqlite3VdbeCheckFk(p, 0); + } + + /* If the auto-commit flag is set and this is the only active writer + ** VM, then we do either a commit or rollback of the current transaction. + ** + ** Note: This block also runs if one of the special errors handled + ** above has occurred. + */ + if( !sqlite3VtabInSync(db) + && db->autoCommit + && db->nVdbeWrite==(p->readOnly==0) + ){ + if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ + rc = sqlite3VdbeCheckFk(p, 1); + if( rc!=SQLITE_OK ){ + if( NEVER(p->readOnly) ){ + sqlite3VdbeLeave(p); + return SQLITE_ERROR; + } + rc = SQLITE_CONSTRAINT_FOREIGNKEY; + }else if( db->flags & SQLITE_CorruptRdOnly ){ + rc = SQLITE_CORRUPT; + db->flags &= ~SQLITE_CorruptRdOnly; + }else{ + /* The auto-commit flag is true, the vdbe program was successful + ** or hit an 'OR FAIL' constraint and there are no deferred foreign + ** key constraints to hold up the transaction. This means a commit + ** is required. */ + rc = vdbeCommit(db, p); + } + if( rc==SQLITE_BUSY && p->readOnly ){ + sqlite3VdbeLeave(p); + return SQLITE_BUSY; + }else if( rc!=SQLITE_OK ){ + sqlite3SystemError(db, rc); + p->rc = rc; + sqlite3RollbackAll(db, SQLITE_OK); + p->nChange = 0; + }else{ + db->nDeferredCons = 0; + db->nDeferredImmCons = 0; + db->flags &= ~(u64)SQLITE_DeferFKs; + sqlite3CommitInternalChanges(db); + } + }else if( p->rc==SQLITE_SCHEMA && db->nVdbeActive>1 ){ + p->nChange = 0; + }else{ + sqlite3RollbackAll(db, SQLITE_OK); + p->nChange = 0; + } + db->nStatement = 0; + }else if( eStatementOp==0 ){ + if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){ + eStatementOp = SAVEPOINT_RELEASE; + }else if( p->errorAction==OE_Abort ){ + eStatementOp = SAVEPOINT_ROLLBACK; + }else{ + sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); + sqlite3CloseSavepoints(db); + db->autoCommit = 1; + p->nChange = 0; + } + } + + /* If eStatementOp is non-zero, then a statement transaction needs to + ** be committed or rolled back. Call sqlite3VdbeCloseStatement() to + ** do so. If this operation returns an error, and the current statement + ** error code is SQLITE_OK or SQLITE_CONSTRAINT, then promote the + ** current statement error code. + */ + if( eStatementOp ){ + rc = sqlite3VdbeCloseStatement(p, eStatementOp); + if( rc ){ + if( p->rc==SQLITE_OK || (p->rc&0xff)==SQLITE_CONSTRAINT ){ + p->rc = rc; + sqlite3DbFree(db, p->zErrMsg); + p->zErrMsg = 0; + } + sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); + sqlite3CloseSavepoints(db); + db->autoCommit = 1; + p->nChange = 0; + } + } + + /* If this was an INSERT, UPDATE or DELETE and no statement transaction + ** has been rolled back, update the database connection change-counter. + */ + if( p->changeCntOn ){ + if( eStatementOp!=SAVEPOINT_ROLLBACK ){ + sqlite3VdbeSetChanges(db, p->nChange); + }else{ + sqlite3VdbeSetChanges(db, 0); + } + p->nChange = 0; + } + + /* Release the locks */ + sqlite3VdbeLeave(p); + } + + /* We have successfully halted and closed the VM. Record this fact. */ + db->nVdbeActive--; + if( !p->readOnly ) db->nVdbeWrite--; + if( p->bIsReader ) db->nVdbeRead--; + assert( db->nVdbeActive>=db->nVdbeRead ); + assert( db->nVdbeRead>=db->nVdbeWrite ); + assert( db->nVdbeWrite>=0 ); + p->eVdbeState = VDBE_HALT_STATE; + checkActiveVdbeCnt(db); + if( db->mallocFailed ){ + p->rc = SQLITE_NOMEM_BKPT; + } + + /* If the auto-commit flag is set to true, then any locks that were held + ** by connection db have now been released. Call sqlite3ConnectionUnlocked() + ** to invoke any required unlock-notify callbacks. + */ + if( db->autoCommit ){ + sqlite3ConnectionUnlocked(db); + } + + assert( db->nVdbeActive>0 || db->autoCommit==0 || db->nStatement==0 ); + return (p->rc==SQLITE_BUSY ? SQLITE_BUSY : SQLITE_OK); +} + + +/* +** Each VDBE holds the result of the most recent sqlite3_step() call +** in p->rc. This routine sets that result back to SQLITE_OK. +*/ +SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe *p){ + p->rc = SQLITE_OK; +} + +/* +** Copy the error code and error message belonging to the VDBE passed +** as the first argument to its database handle (so that they will be +** returned by calls to sqlite3_errcode() and sqlite3_errmsg()). +** +** This function does not clear the VDBE error code or message, just +** copies them to the database handle. +*/ +SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p){ + sqlite3 *db = p->db; + int rc = p->rc; + if( p->zErrMsg ){ + db->bBenignMalloc++; + sqlite3BeginBenignMalloc(); + if( db->pErr==0 ) db->pErr = sqlite3ValueNew(db); + sqlite3ValueSetStr(db->pErr, -1, p->zErrMsg, SQLITE_UTF8, SQLITE_TRANSIENT); + sqlite3EndBenignMalloc(); + db->bBenignMalloc--; + }else if( db->pErr ){ + sqlite3ValueSetNull(db->pErr); + } + db->errCode = rc; + db->errByteOffset = -1; + return rc; +} + +#ifdef SQLITE_ENABLE_SQLLOG +/* +** If an SQLITE_CONFIG_SQLLOG hook is registered and the VM has been run, +** invoke it. +*/ +static void vdbeInvokeSqllog(Vdbe *v){ + if( sqlite3GlobalConfig.xSqllog && v->rc==SQLITE_OK && v->zSql && v->pc>=0 ){ + char *zExpanded = sqlite3VdbeExpandSql(v, v->zSql); + assert( v->db->init.busy==0 ); + if( zExpanded ){ + sqlite3GlobalConfig.xSqllog( + sqlite3GlobalConfig.pSqllogArg, v->db, zExpanded, 1 + ); + sqlite3DbFree(v->db, zExpanded); + } + } +} +#else +# define vdbeInvokeSqllog(x) +#endif + +/* +** Clean up a VDBE after execution but do not delete the VDBE just yet. +** Write any error messages into *pzErrMsg. Return the result code. +** +** After this routine is run, the VDBE should be ready to be executed +** again. +** +** To look at it another way, this routine resets the state of the +** virtual machine from VDBE_RUN_STATE or VDBE_HALT_STATE back to +** VDBE_READY_STATE. +*/ +SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ +#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) + int i; +#endif + + sqlite3 *db; + db = p->db; + + /* If the VM did not run to completion or if it encountered an + ** error, then it might not have been halted properly. So halt + ** it now. + */ + if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); + + /* If the VDBE has been run even partially, then transfer the error code + ** and error message from the VDBE into the main database structure. But + ** if the VDBE has just been set to run but has not actually executed any + ** instructions yet, leave the main database error information unchanged. + */ + if( p->pc>=0 ){ + vdbeInvokeSqllog(p); + if( db->pErr || p->zErrMsg ){ + sqlite3VdbeTransferError(p); + }else{ + db->errCode = p->rc; + } + } + + /* Reset register contents and reclaim error message memory. + */ +#ifdef SQLITE_DEBUG + /* Execute assert() statements to ensure that the Vdbe.apCsr[] and + ** Vdbe.aMem[] arrays have already been cleaned up. */ + if( p->apCsr ) for(i=0; i<p->nCursor; i++) assert( p->apCsr[i]==0 ); + if( p->aMem ){ + for(i=0; i<p->nMem; i++) assert( p->aMem[i].flags==MEM_Undefined ); + } +#endif + if( p->zErrMsg ){ + sqlite3DbFree(db, p->zErrMsg); + p->zErrMsg = 0; + } + p->pResultRow = 0; +#ifdef SQLITE_DEBUG + p->nWrite = 0; +#endif + + /* Save profiling information from this VDBE run. + */ +#ifdef VDBE_PROFILE + { + FILE *out = fopen("vdbe_profile.out", "a"); + if( out ){ + fprintf(out, "---- "); + for(i=0; i<p->nOp; i++){ + fprintf(out, "%02x", p->aOp[i].opcode); + } + fprintf(out, "\n"); + if( p->zSql ){ + char c, pc = 0; + fprintf(out, "-- "); + for(i=0; (c = p->zSql[i])!=0; i++){ + if( pc=='\n' ) fprintf(out, "-- "); + putc(c, out); + pc = c; + } + if( pc!='\n' ) fprintf(out, "\n"); + } + for(i=0; i<p->nOp; i++){ + char zHdr[100]; + i64 cnt = p->aOp[i].nExec; + i64 cycles = p->aOp[i].nCycle; + sqlite3_snprintf(sizeof(zHdr), zHdr, "%6u %12llu %8llu ", + cnt, + cycles, + cnt>0 ? cycles/cnt : 0 + ); + fprintf(out, "%s", zHdr); + sqlite3VdbePrintOp(out, i, &p->aOp[i]); + } + fclose(out); + } + } +#endif + return p->rc & db->errMask; +} + +/* +** Clean up and delete a VDBE after execution. Return an integer which is +** the result code. Write any error message text into *pzErrMsg. +*/ +SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe *p){ + int rc = SQLITE_OK; + assert( VDBE_RUN_STATE>VDBE_READY_STATE ); + assert( VDBE_HALT_STATE>VDBE_READY_STATE ); + assert( VDBE_INIT_STATE<VDBE_READY_STATE ); + if( p->eVdbeState>=VDBE_READY_STATE ){ + rc = sqlite3VdbeReset(p); + assert( (rc & p->db->errMask)==rc ); + } + sqlite3VdbeDelete(p); + return rc; +} + +/* +** If parameter iOp is less than zero, then invoke the destructor for +** all auxiliary data pointers currently cached by the VM passed as +** the first argument. +** +** Or, if iOp is greater than or equal to zero, then the destructor is +** only invoked for those auxiliary data pointers created by the user +** function invoked by the OP_Function opcode at instruction iOp of +** VM pVdbe, and only then if: +** +** * the associated function parameter is the 32nd or later (counting +** from left to right), or +** +** * the corresponding bit in argument mask is clear (where the first +** function parameter corresponds to bit 0 etc.). +*/ +SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp, int mask){ + while( *pp ){ + AuxData *pAux = *pp; + if( (iOp<0) + || (pAux->iAuxOp==iOp + && pAux->iAuxArg>=0 + && (pAux->iAuxArg>31 || !(mask & MASKBIT32(pAux->iAuxArg)))) + ){ + testcase( pAux->iAuxArg==31 ); + if( pAux->xDeleteAux ){ + pAux->xDeleteAux(pAux->pAux); + } + *pp = pAux->pNextAux; + sqlite3DbFree(db, pAux); + }else{ + pp= &pAux->pNextAux; + } + } +} + +/* +** Free all memory associated with the Vdbe passed as the second argument, +** except for object itself, which is preserved. +** +** The difference between this function and sqlite3VdbeDelete() is that +** VdbeDelete() also unlinks the Vdbe from the list of VMs associated with +** the database connection and frees the object itself. +*/ +static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ + SubProgram *pSub, *pNext; + assert( db!=0 ); + assert( p->db==0 || p->db==db ); + if( p->aColName ){ + releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); + sqlite3DbNNFreeNN(db, p->aColName); + } + for(pSub=p->pProgram; pSub; pSub=pNext){ + pNext = pSub->pNext; + vdbeFreeOpArray(db, pSub->aOp, pSub->nOp); + sqlite3DbFree(db, pSub); + } + if( p->eVdbeState!=VDBE_INIT_STATE ){ + releaseMemArray(p->aVar, p->nVar); + if( p->pVList ) sqlite3DbNNFreeNN(db, p->pVList); + if( p->pFree ) sqlite3DbNNFreeNN(db, p->pFree); + } + vdbeFreeOpArray(db, p->aOp, p->nOp); + if( p->zSql ) sqlite3DbNNFreeNN(db, p->zSql); +#ifdef SQLITE_ENABLE_NORMALIZE + sqlite3DbFree(db, p->zNormSql); + { + DblquoteStr *pThis, *pNxt; + for(pThis=p->pDblStr; pThis; pThis=pNxt){ + pNxt = pThis->pNextStr; + sqlite3DbFree(db, pThis); + } + } +#endif +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + { + int i; + for(i=0; i<p->nScan; i++){ + sqlite3DbFree(db, p->aScan[i].zName); + } + sqlite3DbFree(db, p->aScan); + } +#endif +} + +/* +** Delete an entire VDBE. +*/ +SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){ + sqlite3 *db; + + assert( p!=0 ); + db = p->db; + assert( db!=0 ); + assert( sqlite3_mutex_held(db->mutex) ); + sqlite3VdbeClearObject(db, p); + if( db->pnBytesFreed==0 ){ + assert( p->ppVPrev!=0 ); + *p->ppVPrev = p->pVNext; + if( p->pVNext ){ + p->pVNext->ppVPrev = p->ppVPrev; + } + } + sqlite3DbNNFreeNN(db, p); +} + +/* +** The cursor "p" has a pending seek operation that has not yet been +** carried out. Seek the cursor now. If an error occurs, return +** the appropriate error code. +*/ +SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor *p){ + int res, rc; +#ifdef SQLITE_TEST + extern int sqlite3_search_count; +#endif + assert( p->deferredMoveto ); + assert( p->isTable ); + assert( p->eCurType==CURTYPE_BTREE ); + rc = sqlite3BtreeTableMoveto(p->uc.pCursor, p->movetoTarget, 0, &res); + if( rc ) return rc; + if( res!=0 ) return SQLITE_CORRUPT_BKPT; +#ifdef SQLITE_TEST + sqlite3_search_count++; +#endif + p->deferredMoveto = 0; + p->cacheStatus = CACHE_STALE; + return SQLITE_OK; +} + +/* +** Something has moved cursor "p" out of place. Maybe the row it was +** pointed to was deleted out from under it. Or maybe the btree was +** rebalanced. Whatever the cause, try to restore "p" to the place it +** is supposed to be pointing. If the row was deleted out from under the +** cursor, set the cursor to point to a NULL row. +*/ +SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeHandleMovedCursor(VdbeCursor *p){ + int isDifferentRow, rc; + assert( p->eCurType==CURTYPE_BTREE ); + assert( p->uc.pCursor!=0 ); + assert( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ); + rc = sqlite3BtreeCursorRestore(p->uc.pCursor, &isDifferentRow); + p->cacheStatus = CACHE_STALE; + if( isDifferentRow ) p->nullRow = 1; + return rc; +} + +/* +** Check to ensure that the cursor is valid. Restore the cursor +** if need be. Return any I/O error from the restore operation. +*/ +SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor *p){ + assert( p->eCurType==CURTYPE_BTREE || IsNullCursor(p) ); + if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){ + return sqlite3VdbeHandleMovedCursor(p); + } + return SQLITE_OK; +} + +/* +** The following functions: +** +** sqlite3VdbeSerialType() +** sqlite3VdbeSerialTypeLen() +** sqlite3VdbeSerialLen() +** sqlite3VdbeSerialPut() <--- in-lined into OP_MakeRecord as of 2022-04-02 +** sqlite3VdbeSerialGet() +** +** encapsulate the code that serializes values for storage in SQLite +** data and index records. Each serialized value consists of a +** 'serial-type' and a blob of data. The serial type is an 8-byte unsigned +** integer, stored as a varint. +** +** In an SQLite index record, the serial type is stored directly before +** the blob of data that it corresponds to. In a table record, all serial +** types are stored at the start of the record, and the blobs of data at +** the end. Hence these functions allow the caller to handle the +** serial-type and data blob separately. +** +** The following table describes the various storage classes for data: +** +** serial type bytes of data type +** -------------- --------------- --------------- +** 0 0 NULL +** 1 1 signed integer +** 2 2 signed integer +** 3 3 signed integer +** 4 4 signed integer +** 5 6 signed integer +** 6 8 signed integer +** 7 8 IEEE float +** 8 0 Integer constant 0 +** 9 0 Integer constant 1 +** 10,11 reserved for expansion +** N>=12 and even (N-12)/2 BLOB +** N>=13 and odd (N-13)/2 text +** +** The 8 and 9 types were added in 3.3.0, file format 4. Prior versions +** of SQLite will not understand those serial types. +*/ + +#if 0 /* Inlined into the OP_MakeRecord opcode */ +/* +** Return the serial-type for the value stored in pMem. +** +** This routine might convert a large MEM_IntReal value into MEM_Real. +** +** 2019-07-11: The primary user of this subroutine was the OP_MakeRecord +** opcode in the byte-code engine. But by moving this routine in-line, we +** can omit some redundant tests and make that opcode a lot faster. So +** this routine is now only used by the STAT3 logic and STAT3 support has +** ended. The code is kept here for historical reference only. +*/ +SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){ + int flags = pMem->flags; + u32 n; + + assert( pLen!=0 ); + if( flags&MEM_Null ){ + *pLen = 0; + return 0; + } + if( flags&(MEM_Int|MEM_IntReal) ){ + /* Figure out whether to use 1, 2, 4, 6 or 8 bytes. */ +# define MAX_6BYTE ((((i64)0x00008000)<<32)-1) + i64 i = pMem->u.i; + u64 u; + testcase( flags & MEM_Int ); + testcase( flags & MEM_IntReal ); + if( i<0 ){ + u = ~i; + }else{ + u = i; + } + if( u<=127 ){ + if( (i&1)==i && file_format>=4 ){ + *pLen = 0; + return 8+(u32)u; + }else{ + *pLen = 1; + return 1; + } + } + if( u<=32767 ){ *pLen = 2; return 2; } + if( u<=8388607 ){ *pLen = 3; return 3; } + if( u<=2147483647 ){ *pLen = 4; return 4; } + if( u<=MAX_6BYTE ){ *pLen = 6; return 5; } + *pLen = 8; + if( flags&MEM_IntReal ){ + /* If the value is IntReal and is going to take up 8 bytes to store + ** as an integer, then we might as well make it an 8-byte floating + ** point value */ + pMem->u.r = (double)pMem->u.i; + pMem->flags &= ~MEM_IntReal; + pMem->flags |= MEM_Real; + return 7; + } + return 6; + } + if( flags&MEM_Real ){ + *pLen = 8; + return 7; + } + assert( pMem->db->mallocFailed || flags&(MEM_Str|MEM_Blob) ); + assert( pMem->n>=0 ); + n = (u32)pMem->n; + if( flags & MEM_Zero ){ + n += pMem->u.nZero; + } + *pLen = n; + return ((n*2) + 12 + ((flags&MEM_Str)!=0)); +} +#endif /* inlined into OP_MakeRecord */ + +/* +** The sizes for serial types less than 128 +*/ +SQLITE_PRIVATE const u8 sqlite3SmallTypeSizes[128] = { + /* 0 1 2 3 4 5 6 7 8 9 */ +/* 0 */ 0, 1, 2, 3, 4, 6, 8, 8, 0, 0, +/* 10 */ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, +/* 20 */ 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, +/* 30 */ 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, +/* 40 */ 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, +/* 50 */ 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, +/* 60 */ 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, +/* 70 */ 29, 29, 30, 30, 31, 31, 32, 32, 33, 33, +/* 80 */ 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, +/* 90 */ 39, 39, 40, 40, 41, 41, 42, 42, 43, 43, +/* 100 */ 44, 44, 45, 45, 46, 46, 47, 47, 48, 48, +/* 110 */ 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, +/* 120 */ 54, 54, 55, 55, 56, 56, 57, 57 +}; + +/* +** Return the length of the data corresponding to the supplied serial-type. +*/ +SQLITE_PRIVATE u32 sqlite3VdbeSerialTypeLen(u32 serial_type){ + if( serial_type>=128 ){ + return (serial_type-12)/2; + }else{ + assert( serial_type<12 + || sqlite3SmallTypeSizes[serial_type]==(serial_type - 12)/2 ); + return sqlite3SmallTypeSizes[serial_type]; + } +} +SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){ + assert( serial_type<128 ); + return sqlite3SmallTypeSizes[serial_type]; +} + +/* +** If we are on an architecture with mixed-endian floating +** points (ex: ARM7) then swap the lower 4 bytes with the +** upper 4 bytes. Return the result. +** +** For most architectures, this is a no-op. +** +** (later): It is reported to me that the mixed-endian problem +** on ARM7 is an issue with GCC, not with the ARM7 chip. It seems +** that early versions of GCC stored the two words of a 64-bit +** float in the wrong order. And that error has been propagated +** ever since. The blame is not necessarily with GCC, though. +** GCC might have just copying the problem from a prior compiler. +** I am also told that newer versions of GCC that follow a different +** ABI get the byte order right. +** +** Developers using SQLite on an ARM7 should compile and run their +** application using -DSQLITE_DEBUG=1 at least once. With DEBUG +** enabled, some asserts below will ensure that the byte order of +** floating point values is correct. +** +** (2007-08-30) Frank van Vugt has studied this problem closely +** and has send his findings to the SQLite developers. Frank +** writes that some Linux kernels offer floating point hardware +** emulation that uses only 32-bit mantissas instead of a full +** 48-bits as required by the IEEE standard. (This is the +** CONFIG_FPE_FASTFPE option.) On such systems, floating point +** byte swapping becomes very complicated. To avoid problems, +** the necessary byte swapping is carried out using a 64-bit integer +** rather than a 64-bit float. Frank assures us that the code here +** works for him. We, the developers, have no way to independently +** verify this, but Frank seems to know what he is talking about +** so we trust him. +*/ +#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT +SQLITE_PRIVATE u64 sqlite3FloatSwap(u64 in){ + union { + u64 r; + u32 i[2]; + } u; + u32 t; + + u.r = in; + t = u.i[0]; + u.i[0] = u.i[1]; + u.i[1] = t; + return u.r; +} +#endif /* SQLITE_MIXED_ENDIAN_64BIT_FLOAT */ + + +/* Input "x" is a sequence of unsigned characters that represent a +** big-endian integer. Return the equivalent native integer +*/ +#define ONE_BYTE_INT(x) ((i8)(x)[0]) +#define TWO_BYTE_INT(x) (256*(i8)((x)[0])|(x)[1]) +#define THREE_BYTE_INT(x) (65536*(i8)((x)[0])|((x)[1]<<8)|(x)[2]) +#define FOUR_BYTE_UINT(x) (((u32)(x)[0]<<24)|((x)[1]<<16)|((x)[2]<<8)|(x)[3]) +#define FOUR_BYTE_INT(x) (16777216*(i8)((x)[0])|((x)[1]<<16)|((x)[2]<<8)|(x)[3]) + +/* +** Deserialize the data blob pointed to by buf as serial type serial_type +** and store the result in pMem. +** +** This function is implemented as two separate routines for performance. +** The few cases that require local variables are broken out into a separate +** routine so that in most cases the overhead of moving the stack pointer +** is avoided. +*/ +static void serialGet( + const unsigned char *buf, /* Buffer to deserialize from */ + u32 serial_type, /* Serial type to deserialize */ + Mem *pMem /* Memory cell to write value into */ +){ + u64 x = FOUR_BYTE_UINT(buf); + u32 y = FOUR_BYTE_UINT(buf+4); + x = (x<<32) + y; + if( serial_type==6 ){ + /* EVIDENCE-OF: R-29851-52272 Value is a big-endian 64-bit + ** twos-complement integer. */ + pMem->u.i = *(i64*)&x; + pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); + }else{ + /* EVIDENCE-OF: R-57343-49114 Value is a big-endian IEEE 754-2008 64-bit + ** floating point number. */ +#if !defined(NDEBUG) && !defined(SQLITE_OMIT_FLOATING_POINT) + /* Verify that integers and floating point values use the same + ** byte order. Or, that if SQLITE_MIXED_ENDIAN_64BIT_FLOAT is + ** defined that 64-bit floating point values really are mixed + ** endian. + */ + static const u64 t1 = ((u64)0x3ff00000)<<32; + static const double r1 = 1.0; + u64 t2 = t1; + swapMixedEndianFloat(t2); + assert( sizeof(r1)==sizeof(t2) && memcmp(&r1, &t2, sizeof(r1))==0 ); +#endif + assert( sizeof(x)==8 && sizeof(pMem->u.r)==8 ); + swapMixedEndianFloat(x); + memcpy(&pMem->u.r, &x, sizeof(x)); + pMem->flags = IsNaN(x) ? MEM_Null : MEM_Real; + } +} +SQLITE_PRIVATE void sqlite3VdbeSerialGet( + const unsigned char *buf, /* Buffer to deserialize from */ + u32 serial_type, /* Serial type to deserialize */ + Mem *pMem /* Memory cell to write value into */ +){ + switch( serial_type ){ + case 10: { /* Internal use only: NULL with virtual table + ** UPDATE no-change flag set */ + pMem->flags = MEM_Null|MEM_Zero; + pMem->n = 0; + pMem->u.nZero = 0; + return; + } + case 11: /* Reserved for future use */ + case 0: { /* Null */ + /* EVIDENCE-OF: R-24078-09375 Value is a NULL. */ + pMem->flags = MEM_Null; + return; + } + case 1: { + /* EVIDENCE-OF: R-44885-25196 Value is an 8-bit twos-complement + ** integer. */ + pMem->u.i = ONE_BYTE_INT(buf); + pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); + return; + } + case 2: { /* 2-byte signed integer */ + /* EVIDENCE-OF: R-49794-35026 Value is a big-endian 16-bit + ** twos-complement integer. */ + pMem->u.i = TWO_BYTE_INT(buf); + pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); + return; + } + case 3: { /* 3-byte signed integer */ + /* EVIDENCE-OF: R-37839-54301 Value is a big-endian 24-bit + ** twos-complement integer. */ + pMem->u.i = THREE_BYTE_INT(buf); + pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); + return; + } + case 4: { /* 4-byte signed integer */ + /* EVIDENCE-OF: R-01849-26079 Value is a big-endian 32-bit + ** twos-complement integer. */ + pMem->u.i = FOUR_BYTE_INT(buf); +#ifdef __HP_cc + /* Work around a sign-extension bug in the HP compiler for HP/UX */ + if( buf[0]&0x80 ) pMem->u.i |= 0xffffffff80000000LL; +#endif + pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); + return; + } + case 5: { /* 6-byte signed integer */ + /* EVIDENCE-OF: R-50385-09674 Value is a big-endian 48-bit + ** twos-complement integer. */ + pMem->u.i = FOUR_BYTE_UINT(buf+2) + (((i64)1)<<32)*TWO_BYTE_INT(buf); + pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); + return; + } + case 6: /* 8-byte signed integer */ + case 7: { /* IEEE floating point */ + /* These use local variables, so do them in a separate routine + ** to avoid having to move the frame pointer in the common case */ + serialGet(buf,serial_type,pMem); + return; + } + case 8: /* Integer 0 */ + case 9: { /* Integer 1 */ + /* EVIDENCE-OF: R-12976-22893 Value is the integer 0. */ + /* EVIDENCE-OF: R-18143-12121 Value is the integer 1. */ + pMem->u.i = serial_type-8; + pMem->flags = MEM_Int; + return; + } + default: { + /* EVIDENCE-OF: R-14606-31564 Value is a BLOB that is (N-12)/2 bytes in + ** length. + ** EVIDENCE-OF: R-28401-00140 Value is a string in the text encoding and + ** (N-13)/2 bytes in length. */ + static const u16 aFlag[] = { MEM_Blob|MEM_Ephem, MEM_Str|MEM_Ephem }; + pMem->z = (char *)buf; + pMem->n = (serial_type-12)/2; + pMem->flags = aFlag[serial_type&1]; + return; + } + } + return; +} +/* +** This routine is used to allocate sufficient space for an UnpackedRecord +** structure large enough to be used with sqlite3VdbeRecordUnpack() if +** the first argument is a pointer to KeyInfo structure pKeyInfo. +** +** The space is either allocated using sqlite3DbMallocRaw() or from within +** the unaligned buffer passed via the second and third arguments (presumably +** stack space). If the former, then *ppFree is set to a pointer that should +** be eventually freed by the caller using sqlite3DbFree(). Or, if the +** allocation comes from the pSpace/szSpace buffer, *ppFree is set to NULL +** before returning. +** +** If an OOM error occurs, NULL is returned. +*/ +SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord( + KeyInfo *pKeyInfo /* Description of the record */ +){ + UnpackedRecord *p; /* Unpacked record to return */ + int nByte; /* Number of bytes required for *p */ + nByte = ROUND8P(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1); + p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte); + if( !p ) return 0; + p->aMem = (Mem*)&((char*)p)[ROUND8P(sizeof(UnpackedRecord))]; + assert( pKeyInfo->aSortFlags!=0 ); + p->pKeyInfo = pKeyInfo; + p->nField = pKeyInfo->nKeyField + 1; + return p; +} + +/* +** Given the nKey-byte encoding of a record in pKey[], populate the +** UnpackedRecord structure indicated by the fourth argument with the +** contents of the decoded record. +*/ +SQLITE_PRIVATE void sqlite3VdbeRecordUnpack( + KeyInfo *pKeyInfo, /* Information about the record format */ + int nKey, /* Size of the binary record */ + const void *pKey, /* The binary record */ + UnpackedRecord *p /* Populate this structure before returning. */ +){ + const unsigned char *aKey = (const unsigned char *)pKey; + u32 d; + u32 idx; /* Offset in aKey[] to read from */ + u16 u; /* Unsigned loop counter */ + u32 szHdr; + Mem *pMem = p->aMem; + + p->default_rc = 0; + assert( EIGHT_BYTE_ALIGNMENT(pMem) ); + idx = getVarint32(aKey, szHdr); + d = szHdr; + u = 0; + while( idx<szHdr && d<=(u32)nKey ){ + u32 serial_type; + + idx += getVarint32(&aKey[idx], serial_type); + pMem->enc = pKeyInfo->enc; + pMem->db = pKeyInfo->db; + /* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */ + pMem->szMalloc = 0; + pMem->z = 0; + sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); + d += sqlite3VdbeSerialTypeLen(serial_type); + pMem++; + if( (++u)>=p->nField ) break; + } + if( d>(u32)nKey && u ){ + assert( CORRUPT_DB ); + /* In a corrupt record entry, the last pMem might have been set up using + ** uninitialized memory. Overwrite its value with NULL, to prevent + ** warnings from MSAN. */ + sqlite3VdbeMemSetNull(pMem-1); + } + assert( u<=pKeyInfo->nKeyField + 1 ); + p->nField = u; +} + +#ifdef SQLITE_DEBUG +/* +** This function compares two index or table record keys in the same way +** as the sqlite3VdbeRecordCompare() routine. Unlike VdbeRecordCompare(), +** this function deserializes and compares values using the +** sqlite3VdbeSerialGet() and sqlite3MemCompare() functions. It is used +** in assert() statements to ensure that the optimized code in +** sqlite3VdbeRecordCompare() returns results with these two primitives. +** +** Return true if the result of comparison is equivalent to desiredResult. +** Return false if there is a disagreement. +*/ +static int vdbeRecordCompareDebug( + int nKey1, const void *pKey1, /* Left key */ + const UnpackedRecord *pPKey2, /* Right key */ + int desiredResult /* Correct answer */ +){ + u32 d1; /* Offset into aKey[] of next data element */ + u32 idx1; /* Offset into aKey[] of next header element */ + u32 szHdr1; /* Number of bytes in header */ + int i = 0; + int rc = 0; + const unsigned char *aKey1 = (const unsigned char *)pKey1; + KeyInfo *pKeyInfo; + Mem mem1; + + pKeyInfo = pPKey2->pKeyInfo; + if( pKeyInfo->db==0 ) return 1; + mem1.enc = pKeyInfo->enc; + mem1.db = pKeyInfo->db; + /* mem1.flags = 0; // Will be initialized by sqlite3VdbeSerialGet() */ + VVA_ONLY( mem1.szMalloc = 0; ) /* Only needed by assert() statements */ + + /* Compilers may complain that mem1.u.i is potentially uninitialized. + ** We could initialize it, as shown here, to silence those complaints. + ** But in fact, mem1.u.i will never actually be used uninitialized, and doing + ** the unnecessary initialization has a measurable negative performance + ** impact, since this routine is a very high runner. And so, we choose + ** to ignore the compiler warnings and leave this variable uninitialized. + */ + /* mem1.u.i = 0; // not needed, here to silence compiler warning */ + + idx1 = getVarint32(aKey1, szHdr1); + if( szHdr1>98307 ) return SQLITE_CORRUPT; + d1 = szHdr1; + assert( pKeyInfo->nAllField>=pPKey2->nField || CORRUPT_DB ); + assert( pKeyInfo->aSortFlags!=0 ); + assert( pKeyInfo->nKeyField>0 ); + assert( idx1<=szHdr1 || CORRUPT_DB ); + do{ + u32 serial_type1; + + /* Read the serial types for the next element in each key. */ + idx1 += getVarint32( aKey1+idx1, serial_type1 ); + + /* Verify that there is enough key space remaining to avoid + ** a buffer overread. The "d1+serial_type1+2" subexpression will + ** always be greater than or equal to the amount of required key space. + ** Use that approximation to avoid the more expensive call to + ** sqlite3VdbeSerialTypeLen() in the common case. + */ + if( d1+(u64)serial_type1+2>(u64)nKey1 + && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)>(u64)nKey1 + ){ + if( serial_type1>=1 + && serial_type1<=7 + && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)<=(u64)nKey1+8 + && CORRUPT_DB + ){ + return 1; /* corrupt record not detected by + ** sqlite3VdbeRecordCompareWithSkip(). Return true + ** to avoid firing the assert() */ + } + break; + } + + /* Extract the values to be compared. + */ + sqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1); + d1 += sqlite3VdbeSerialTypeLen(serial_type1); + + /* Do the comparison + */ + rc = sqlite3MemCompare(&mem1, &pPKey2->aMem[i], + pKeyInfo->nAllField>i ? pKeyInfo->aColl[i] : 0); + if( rc!=0 ){ + assert( mem1.szMalloc==0 ); /* See comment below */ + if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) + && ((mem1.flags & MEM_Null) || (pPKey2->aMem[i].flags & MEM_Null)) + ){ + rc = -rc; + } + if( pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_DESC ){ + rc = -rc; /* Invert the result for DESC sort order. */ + } + goto debugCompareEnd; + } + i++; + }while( idx1<szHdr1 && i<pPKey2->nField ); + + /* No memory allocation is ever used on mem1. Prove this using + ** the following assert(). If the assert() fails, it indicates a + ** memory leak and a need to call sqlite3VdbeMemRelease(&mem1). + */ + assert( mem1.szMalloc==0 ); + + /* rc==0 here means that one of the keys ran out of fields and + ** all the fields up to that point were equal. Return the default_rc + ** value. */ + rc = pPKey2->default_rc; + +debugCompareEnd: + if( desiredResult==0 && rc==0 ) return 1; + if( desiredResult<0 && rc<0 ) return 1; + if( desiredResult>0 && rc>0 ) return 1; + if( CORRUPT_DB ) return 1; + if( pKeyInfo->db->mallocFailed ) return 1; + return 0; +} +#endif + +#ifdef SQLITE_DEBUG +/* +** Count the number of fields (a.k.a. columns) in the record given by +** pKey,nKey. The verify that this count is less than or equal to the +** limit given by pKeyInfo->nAllField. +** +** If this constraint is not satisfied, it means that the high-speed +** vdbeRecordCompareInt() and vdbeRecordCompareString() routines will +** not work correctly. If this assert() ever fires, it probably means +** that the KeyInfo.nKeyField or KeyInfo.nAllField values were computed +** incorrectly. +*/ +static void vdbeAssertFieldCountWithinLimits( + int nKey, const void *pKey, /* The record to verify */ + const KeyInfo *pKeyInfo /* Compare size with this KeyInfo */ +){ + int nField = 0; + u32 szHdr; + u32 idx; + u32 notUsed; + const unsigned char *aKey = (const unsigned char*)pKey; + + if( CORRUPT_DB ) return; + idx = getVarint32(aKey, szHdr); + assert( nKey>=0 ); + assert( szHdr<=(u32)nKey ); + while( idx<szHdr ){ + idx += getVarint32(aKey+idx, notUsed); + nField++; + } + assert( nField <= pKeyInfo->nAllField ); +} +#else +# define vdbeAssertFieldCountWithinLimits(A,B,C) +#endif + +/* +** Both *pMem1 and *pMem2 contain string values. Compare the two values +** using the collation sequence pColl. As usual, return a negative , zero +** or positive value if *pMem1 is less than, equal to or greater than +** *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);". +*/ +static int vdbeCompareMemString( + const Mem *pMem1, + const Mem *pMem2, + const CollSeq *pColl, + u8 *prcErr /* If an OOM occurs, set to SQLITE_NOMEM */ +){ + if( pMem1->enc==pColl->enc ){ + /* The strings are already in the correct encoding. Call the + ** comparison function directly */ + return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z); + }else{ + int rc; + const void *v1, *v2; + Mem c1; + Mem c2; + sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); + sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null); + sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); + sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); + v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); + v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); + if( (v1==0 || v2==0) ){ + if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT; + rc = 0; + }else{ + rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); + } + sqlite3VdbeMemReleaseMalloc(&c1); + sqlite3VdbeMemReleaseMalloc(&c2); + return rc; + } +} + +/* +** The input pBlob is guaranteed to be a Blob that is not marked +** with MEM_Zero. Return true if it could be a zero-blob. +*/ +static int isAllZero(const char *z, int n){ + int i; + for(i=0; i<n; i++){ + if( z[i] ) return 0; + } + return 1; +} + +/* +** Compare two blobs. Return negative, zero, or positive if the first +** is less than, equal to, or greater than the second, respectively. +** If one blob is a prefix of the other, then the shorter is the lessor. +*/ +SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem *pB2){ + int c; + int n1 = pB1->n; + int n2 = pB2->n; + + /* It is possible to have a Blob value that has some non-zero content + ** followed by zero content. But that only comes up for Blobs formed + ** by the OP_MakeRecord opcode, and such Blobs never get passed into + ** sqlite3MemCompare(). */ + assert( (pB1->flags & MEM_Zero)==0 || n1==0 ); + assert( (pB2->flags & MEM_Zero)==0 || n2==0 ); + + if( (pB1->flags|pB2->flags) & MEM_Zero ){ + if( pB1->flags & pB2->flags & MEM_Zero ){ + return pB1->u.nZero - pB2->u.nZero; + }else if( pB1->flags & MEM_Zero ){ + if( !isAllZero(pB2->z, pB2->n) ) return -1; + return pB1->u.nZero - n2; + }else{ + if( !isAllZero(pB1->z, pB1->n) ) return +1; + return n1 - pB2->u.nZero; + } + } + c = memcmp(pB1->z, pB2->z, n1>n2 ? n2 : n1); + if( c ) return c; + return n1 - n2; +} + +/* +** Do a comparison between a 64-bit signed integer and a 64-bit floating-point +** number. Return negative, zero, or positive if the first (i64) is less than, +** equal to, or greater than the second (double). +*/ +SQLITE_PRIVATE int sqlite3IntFloatCompare(i64 i, double r){ + if( sizeof(LONGDOUBLE_TYPE)>8 ){ + LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i; + testcase( x<r ); + testcase( x>r ); + testcase( x==r ); + if( x<r ) return -1; + if( x>r ) return +1; /*NO_TEST*/ /* work around bugs in gcov */ + return 0; /*NO_TEST*/ /* work around bugs in gcov */ + }else{ + i64 y; + double s; + if( r<-9223372036854775808.0 ) return +1; + if( r>=9223372036854775808.0 ) return -1; + y = (i64)r; + if( i<y ) return -1; + if( i>y ) return +1; + s = (double)i; + if( s<r ) return -1; + if( s>r ) return +1; + return 0; + } +} + +/* +** Compare the values contained by the two memory cells, returning +** negative, zero or positive if pMem1 is less than, equal to, or greater +** than pMem2. Sorting order is NULL's first, followed by numbers (integers +** and reals) sorted numerically, followed by text ordered by the collating +** sequence pColl and finally blob's ordered by memcmp(). +** +** Two NULL values are considered equal by this function. +*/ +SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){ + int f1, f2; + int combined_flags; + + f1 = pMem1->flags; + f2 = pMem2->flags; + combined_flags = f1|f2; + assert( !sqlite3VdbeMemIsRowSet(pMem1) && !sqlite3VdbeMemIsRowSet(pMem2) ); + + /* If one value is NULL, it is less than the other. If both values + ** are NULL, return 0. + */ + if( combined_flags&MEM_Null ){ + return (f2&MEM_Null) - (f1&MEM_Null); + } + + /* At least one of the two values is a number + */ + if( combined_flags&(MEM_Int|MEM_Real|MEM_IntReal) ){ + testcase( combined_flags & MEM_Int ); + testcase( combined_flags & MEM_Real ); + testcase( combined_flags & MEM_IntReal ); + if( (f1 & f2 & (MEM_Int|MEM_IntReal))!=0 ){ + testcase( f1 & f2 & MEM_Int ); + testcase( f1 & f2 & MEM_IntReal ); + if( pMem1->u.i < pMem2->u.i ) return -1; + if( pMem1->u.i > pMem2->u.i ) return +1; + return 0; + } + if( (f1 & f2 & MEM_Real)!=0 ){ + if( pMem1->u.r < pMem2->u.r ) return -1; + if( pMem1->u.r > pMem2->u.r ) return +1; + return 0; + } + if( (f1&(MEM_Int|MEM_IntReal))!=0 ){ + testcase( f1 & MEM_Int ); + testcase( f1 & MEM_IntReal ); + if( (f2&MEM_Real)!=0 ){ + return sqlite3IntFloatCompare(pMem1->u.i, pMem2->u.r); + }else if( (f2&(MEM_Int|MEM_IntReal))!=0 ){ + if( pMem1->u.i < pMem2->u.i ) return -1; + if( pMem1->u.i > pMem2->u.i ) return +1; + return 0; + }else{ + return -1; + } + } + if( (f1&MEM_Real)!=0 ){ + if( (f2&(MEM_Int|MEM_IntReal))!=0 ){ + testcase( f2 & MEM_Int ); + testcase( f2 & MEM_IntReal ); + return -sqlite3IntFloatCompare(pMem2->u.i, pMem1->u.r); + }else{ + return -1; + } + } + return +1; + } + + /* If one value is a string and the other is a blob, the string is less. + ** If both are strings, compare using the collating functions. + */ + if( combined_flags&MEM_Str ){ + if( (f1 & MEM_Str)==0 ){ + return 1; + } + if( (f2 & MEM_Str)==0 ){ + return -1; + } + + assert( pMem1->enc==pMem2->enc || pMem1->db->mallocFailed ); + assert( pMem1->enc==SQLITE_UTF8 || + pMem1->enc==SQLITE_UTF16LE || pMem1->enc==SQLITE_UTF16BE ); + + /* The collation sequence must be defined at this point, even if + ** the user deletes the collation sequence after the vdbe program is + ** compiled (this was not always the case). + */ + assert( !pColl || pColl->xCmp ); + + if( pColl ){ + return vdbeCompareMemString(pMem1, pMem2, pColl, 0); + } + /* If a NULL pointer was passed as the collate function, fall through + ** to the blob case and use memcmp(). */ + } + + /* Both values must be blobs. Compare using memcmp(). */ + return sqlite3BlobCompare(pMem1, pMem2); +} + + +/* +** The first argument passed to this function is a serial-type that +** corresponds to an integer - all values between 1 and 9 inclusive +** except 7. The second points to a buffer containing an integer value +** serialized according to serial_type. This function deserializes +** and returns the value. +*/ +static i64 vdbeRecordDecodeInt(u32 serial_type, const u8 *aKey){ + u32 y; + assert( CORRUPT_DB || (serial_type>=1 && serial_type<=9 && serial_type!=7) ); + switch( serial_type ){ + case 0: + case 1: + testcase( aKey[0]&0x80 ); + return ONE_BYTE_INT(aKey); + case 2: + testcase( aKey[0]&0x80 ); + return TWO_BYTE_INT(aKey); + case 3: + testcase( aKey[0]&0x80 ); + return THREE_BYTE_INT(aKey); + case 4: { + testcase( aKey[0]&0x80 ); + y = FOUR_BYTE_UINT(aKey); + return (i64)*(int*)&y; + } + case 5: { + testcase( aKey[0]&0x80 ); + return FOUR_BYTE_UINT(aKey+2) + (((i64)1)<<32)*TWO_BYTE_INT(aKey); + } + case 6: { + u64 x = FOUR_BYTE_UINT(aKey); + testcase( aKey[0]&0x80 ); + x = (x<<32) | FOUR_BYTE_UINT(aKey+4); + return (i64)*(i64*)&x; + } + } + + return (serial_type - 8); +} + +/* +** This function compares the two table rows or index records +** specified by {nKey1, pKey1} and pPKey2. It returns a negative, zero +** or positive integer if key1 is less than, equal to or +** greater than key2. The {nKey1, pKey1} key must be a blob +** created by the OP_MakeRecord opcode of the VDBE. The pPKey2 +** key must be a parsed key such as obtained from +** sqlite3VdbeParseRecord. +** +** If argument bSkip is non-zero, it is assumed that the caller has already +** determined that the first fields of the keys are equal. +** +** Key1 and Key2 do not have to contain the same number of fields. If all +** fields that appear in both keys are equal, then pPKey2->default_rc is +** returned. +** +** If database corruption is discovered, set pPKey2->errCode to +** SQLITE_CORRUPT and return 0. If an OOM error is encountered, +** pPKey2->errCode is set to SQLITE_NOMEM and, if it is not NULL, the +** malloc-failed flag set on database handle (pPKey2->pKeyInfo->db). +*/ +SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( + int nKey1, const void *pKey1, /* Left key */ + UnpackedRecord *pPKey2, /* Right key */ + int bSkip /* If true, skip the first field */ +){ + u32 d1; /* Offset into aKey[] of next data element */ + int i; /* Index of next field to compare */ + u32 szHdr1; /* Size of record header in bytes */ + u32 idx1; /* Offset of first type in header */ + int rc = 0; /* Return value */ + Mem *pRhs = pPKey2->aMem; /* Next field of pPKey2 to compare */ + KeyInfo *pKeyInfo; + const unsigned char *aKey1 = (const unsigned char *)pKey1; + Mem mem1; + + /* If bSkip is true, then the caller has already determined that the first + ** two elements in the keys are equal. Fix the various stack variables so + ** that this routine begins comparing at the second field. */ + if( bSkip ){ + u32 s1 = aKey1[1]; + if( s1<0x80 ){ + idx1 = 2; + }else{ + idx1 = 1 + sqlite3GetVarint32(&aKey1[1], &s1); + } + szHdr1 = aKey1[0]; + d1 = szHdr1 + sqlite3VdbeSerialTypeLen(s1); + i = 1; + pRhs++; + }else{ + if( (szHdr1 = aKey1[0])<0x80 ){ + idx1 = 1; + }else{ + idx1 = sqlite3GetVarint32(aKey1, &szHdr1); + } + d1 = szHdr1; + i = 0; + } + if( d1>(unsigned)nKey1 ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corruption */ + } + + VVA_ONLY( mem1.szMalloc = 0; ) /* Only needed by assert() statements */ + assert( pPKey2->pKeyInfo->nAllField>=pPKey2->nField + || CORRUPT_DB ); + assert( pPKey2->pKeyInfo->aSortFlags!=0 ); + assert( pPKey2->pKeyInfo->nKeyField>0 ); + assert( idx1<=szHdr1 || CORRUPT_DB ); + while( 1 /*exit-by-break*/ ){ + u32 serial_type; + + /* RHS is an integer */ + if( pRhs->flags & (MEM_Int|MEM_IntReal) ){ + testcase( pRhs->flags & MEM_Int ); + testcase( pRhs->flags & MEM_IntReal ); + serial_type = aKey1[idx1]; + testcase( serial_type==12 ); + if( serial_type>=10 ){ + rc = serial_type==10 ? -1 : +1; + }else if( serial_type==0 ){ + rc = -1; + }else if( serial_type==7 ){ + sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); + rc = -sqlite3IntFloatCompare(pRhs->u.i, mem1.u.r); + }else{ + i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]); + i64 rhs = pRhs->u.i; + if( lhs<rhs ){ + rc = -1; + }else if( lhs>rhs ){ + rc = +1; + } + } + } + + /* RHS is real */ + else if( pRhs->flags & MEM_Real ){ + serial_type = aKey1[idx1]; + if( serial_type>=10 ){ + /* Serial types 12 or greater are strings and blobs (greater than + ** numbers). Types 10 and 11 are currently "reserved for future + ** use", so it doesn't really matter what the results of comparing + ** them to numeric values are. */ + rc = serial_type==10 ? -1 : +1; + }else if( serial_type==0 ){ + rc = -1; + }else{ + sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); + if( serial_type==7 ){ + if( mem1.u.r<pRhs->u.r ){ + rc = -1; + }else if( mem1.u.r>pRhs->u.r ){ + rc = +1; + } + }else{ + rc = sqlite3IntFloatCompare(mem1.u.i, pRhs->u.r); + } + } + } + + /* RHS is a string */ + else if( pRhs->flags & MEM_Str ){ + getVarint32NR(&aKey1[idx1], serial_type); + testcase( serial_type==12 ); + if( serial_type<12 ){ + rc = -1; + }else if( !(serial_type & 0x01) ){ + rc = +1; + }else{ + mem1.n = (serial_type - 12) / 2; + testcase( (d1+mem1.n)==(unsigned)nKey1 ); + testcase( (d1+mem1.n+1)==(unsigned)nKey1 ); + if( (d1+mem1.n) > (unsigned)nKey1 + || (pKeyInfo = pPKey2->pKeyInfo)->nAllField<=i + ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corruption */ + }else if( pKeyInfo->aColl[i] ){ + mem1.enc = pKeyInfo->enc; + mem1.db = pKeyInfo->db; + mem1.flags = MEM_Str; + mem1.z = (char*)&aKey1[d1]; + rc = vdbeCompareMemString( + &mem1, pRhs, pKeyInfo->aColl[i], &pPKey2->errCode + ); + }else{ + int nCmp = MIN(mem1.n, pRhs->n); + rc = memcmp(&aKey1[d1], pRhs->z, nCmp); + if( rc==0 ) rc = mem1.n - pRhs->n; + } + } + } + + /* RHS is a blob */ + else if( pRhs->flags & MEM_Blob ){ + assert( (pRhs->flags & MEM_Zero)==0 || pRhs->n==0 ); + getVarint32NR(&aKey1[idx1], serial_type); + testcase( serial_type==12 ); + if( serial_type<12 || (serial_type & 0x01) ){ + rc = -1; + }else{ + int nStr = (serial_type - 12) / 2; + testcase( (d1+nStr)==(unsigned)nKey1 ); + testcase( (d1+nStr+1)==(unsigned)nKey1 ); + if( (d1+nStr) > (unsigned)nKey1 ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corruption */ + }else if( pRhs->flags & MEM_Zero ){ + if( !isAllZero((const char*)&aKey1[d1],nStr) ){ + rc = 1; + }else{ + rc = nStr - pRhs->u.nZero; + } + }else{ + int nCmp = MIN(nStr, pRhs->n); + rc = memcmp(&aKey1[d1], pRhs->z, nCmp); + if( rc==0 ) rc = nStr - pRhs->n; + } + } + } + + /* RHS is null */ + else{ + serial_type = aKey1[idx1]; + rc = (serial_type!=0 && serial_type!=10); + } + + if( rc!=0 ){ + int sortFlags = pPKey2->pKeyInfo->aSortFlags[i]; + if( sortFlags ){ + if( (sortFlags & KEYINFO_ORDER_BIGNULL)==0 + || ((sortFlags & KEYINFO_ORDER_DESC) + !=(serial_type==0 || (pRhs->flags&MEM_Null))) + ){ + rc = -rc; + } + } + assert( vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, rc) ); + assert( mem1.szMalloc==0 ); /* See comment below */ + return rc; + } + + i++; + if( i==pPKey2->nField ) break; + pRhs++; + d1 += sqlite3VdbeSerialTypeLen(serial_type); + if( d1>(unsigned)nKey1 ) break; + idx1 += sqlite3VarintLen(serial_type); + if( idx1>=(unsigned)szHdr1 ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corrupt index */ + } + } + + /* No memory allocation is ever used on mem1. Prove this using + ** the following assert(). If the assert() fails, it indicates a + ** memory leak and a need to call sqlite3VdbeMemRelease(&mem1). */ + assert( mem1.szMalloc==0 ); + + /* rc==0 here means that one or both of the keys ran out of fields and + ** all the fields up to that point were equal. Return the default_rc + ** value. */ + assert( CORRUPT_DB + || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, pPKey2->default_rc) + || pPKey2->pKeyInfo->db->mallocFailed + ); + pPKey2->eqSeen = 1; + return pPKey2->default_rc; +} +SQLITE_PRIVATE int sqlite3VdbeRecordCompare( + int nKey1, const void *pKey1, /* Left key */ + UnpackedRecord *pPKey2 /* Right key */ +){ + return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 0); +} + + +/* +** This function is an optimized version of sqlite3VdbeRecordCompare() +** that (a) the first field of pPKey2 is an integer, and (b) the +** size-of-header varint at the start of (pKey1/nKey1) fits in a single +** byte (i.e. is less than 128). +** +** To avoid concerns about buffer overreads, this routine is only used +** on schemas where the maximum valid header size is 63 bytes or less. +*/ +static int vdbeRecordCompareInt( + int nKey1, const void *pKey1, /* Left key */ + UnpackedRecord *pPKey2 /* Right key */ +){ + const u8 *aKey = &((const u8*)pKey1)[*(const u8*)pKey1 & 0x3F]; + int serial_type = ((const u8*)pKey1)[1]; + int res; + u32 y; + u64 x; + i64 v; + i64 lhs; + + vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); + assert( (*(u8*)pKey1)<=0x3F || CORRUPT_DB ); + switch( serial_type ){ + case 1: { /* 1-byte signed integer */ + lhs = ONE_BYTE_INT(aKey); + testcase( lhs<0 ); + break; + } + case 2: { /* 2-byte signed integer */ + lhs = TWO_BYTE_INT(aKey); + testcase( lhs<0 ); + break; + } + case 3: { /* 3-byte signed integer */ + lhs = THREE_BYTE_INT(aKey); + testcase( lhs<0 ); + break; + } + case 4: { /* 4-byte signed integer */ + y = FOUR_BYTE_UINT(aKey); + lhs = (i64)*(int*)&y; + testcase( lhs<0 ); + break; + } + case 5: { /* 6-byte signed integer */ + lhs = FOUR_BYTE_UINT(aKey+2) + (((i64)1)<<32)*TWO_BYTE_INT(aKey); + testcase( lhs<0 ); + break; + } + case 6: { /* 8-byte signed integer */ + x = FOUR_BYTE_UINT(aKey); + x = (x<<32) | FOUR_BYTE_UINT(aKey+4); + lhs = *(i64*)&x; + testcase( lhs<0 ); + break; + } + case 8: + lhs = 0; + break; + case 9: + lhs = 1; + break; + + /* This case could be removed without changing the results of running + ** this code. Including it causes gcc to generate a faster switch + ** statement (since the range of switch targets now starts at zero and + ** is contiguous) but does not cause any duplicate code to be generated + ** (as gcc is clever enough to combine the two like cases). Other + ** compilers might be similar. */ + case 0: case 7: + return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2); + + default: + return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2); + } + + assert( pPKey2->u.i == pPKey2->aMem[0].u.i ); + v = pPKey2->u.i; + if( v>lhs ){ + res = pPKey2->r1; + }else if( v<lhs ){ + res = pPKey2->r2; + }else if( pPKey2->nField>1 ){ + /* The first fields of the two keys are equal. Compare the trailing + ** fields. */ + res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); + }else{ + /* The first fields of the two keys are equal and there are no trailing + ** fields. Return pPKey2->default_rc in this case. */ + res = pPKey2->default_rc; + pPKey2->eqSeen = 1; + } + + assert( vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, res) ); + return res; +} + +/* +** This function is an optimized version of sqlite3VdbeRecordCompare() +** that (a) the first field of pPKey2 is a string, that (b) the first field +** uses the collation sequence BINARY and (c) that the size-of-header varint +** at the start of (pKey1/nKey1) fits in a single byte. +*/ +static int vdbeRecordCompareString( + int nKey1, const void *pKey1, /* Left key */ + UnpackedRecord *pPKey2 /* Right key */ +){ + const u8 *aKey1 = (const u8*)pKey1; + int serial_type; + int res; + + assert( pPKey2->aMem[0].flags & MEM_Str ); + assert( pPKey2->aMem[0].n == pPKey2->n ); + assert( pPKey2->aMem[0].z == pPKey2->u.z ); + vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); + serial_type = (signed char)(aKey1[1]); + +vrcs_restart: + if( serial_type<12 ){ + if( serial_type<0 ){ + sqlite3GetVarint32(&aKey1[1], (u32*)&serial_type); + if( serial_type>=12 ) goto vrcs_restart; + assert( CORRUPT_DB ); + } + res = pPKey2->r1; /* (pKey1/nKey1) is a number or a null */ + }else if( !(serial_type & 0x01) ){ + res = pPKey2->r2; /* (pKey1/nKey1) is a blob */ + }else{ + int nCmp; + int nStr; + int szHdr = aKey1[0]; + + nStr = (serial_type-12) / 2; + if( (szHdr + nStr) > nKey1 ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corruption */ + } + nCmp = MIN( pPKey2->n, nStr ); + res = memcmp(&aKey1[szHdr], pPKey2->u.z, nCmp); + + if( res>0 ){ + res = pPKey2->r2; + }else if( res<0 ){ + res = pPKey2->r1; + }else{ + res = nStr - pPKey2->n; + if( res==0 ){ + if( pPKey2->nField>1 ){ + res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); + }else{ + res = pPKey2->default_rc; + pPKey2->eqSeen = 1; + } + }else if( res>0 ){ + res = pPKey2->r2; + }else{ + res = pPKey2->r1; + } + } + } + + assert( vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, res) + || CORRUPT_DB + || pPKey2->pKeyInfo->db->mallocFailed + ); + return res; +} + +/* +** Return a pointer to an sqlite3VdbeRecordCompare() compatible function +** suitable for comparing serialized records to the unpacked record passed +** as the only argument. +*/ +SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ + /* varintRecordCompareInt() and varintRecordCompareString() both assume + ** that the size-of-header varint that occurs at the start of each record + ** fits in a single byte (i.e. is 127 or less). varintRecordCompareInt() + ** also assumes that it is safe to overread a buffer by at least the + ** maximum possible legal header size plus 8 bytes. Because there is + ** guaranteed to be at least 74 (but not 136) bytes of padding following each + ** buffer passed to varintRecordCompareInt() this makes it convenient to + ** limit the size of the header to 64 bytes in cases where the first field + ** is an integer. + ** + ** The easiest way to enforce this limit is to consider only records with + ** 13 fields or less. If the first field is an integer, the maximum legal + ** header size is (12*5 + 1 + 1) bytes. */ + if( p->pKeyInfo->nAllField<=13 ){ + int flags = p->aMem[0].flags; + if( p->pKeyInfo->aSortFlags[0] ){ + if( p->pKeyInfo->aSortFlags[0] & KEYINFO_ORDER_BIGNULL ){ + return sqlite3VdbeRecordCompare; + } + p->r1 = 1; + p->r2 = -1; + }else{ + p->r1 = -1; + p->r2 = 1; + } + if( (flags & MEM_Int) ){ + p->u.i = p->aMem[0].u.i; + return vdbeRecordCompareInt; + } + testcase( flags & MEM_Real ); + testcase( flags & MEM_Null ); + testcase( flags & MEM_Blob ); + if( (flags & (MEM_Real|MEM_IntReal|MEM_Null|MEM_Blob))==0 + && p->pKeyInfo->aColl[0]==0 + ){ + assert( flags & MEM_Str ); + p->u.z = p->aMem[0].z; + p->n = p->aMem[0].n; + return vdbeRecordCompareString; + } + } + + return sqlite3VdbeRecordCompare; +} + +/* +** pCur points at an index entry created using the OP_MakeRecord opcode. +** Read the rowid (the last field in the record) and store it in *rowid. +** Return SQLITE_OK if everything works, or an error code otherwise. +** +** pCur might be pointing to text obtained from a corrupt database file. +** So the content cannot be trusted. Do appropriate checks on the content. +*/ +SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ + i64 nCellKey = 0; + int rc; + u32 szHdr; /* Size of the header */ + u32 typeRowid; /* Serial type of the rowid */ + u32 lenRowid; /* Size of the rowid */ + Mem m, v; + + /* Get the size of the index entry. Only indices entries of less + ** than 2GiB are support - anything large must be database corruption. + ** Any corruption is detected in sqlite3BtreeParseCellPtr(), though, so + ** this code can safely assume that nCellKey is 32-bits + */ + assert( sqlite3BtreeCursorIsValid(pCur) ); + nCellKey = sqlite3BtreePayloadSize(pCur); + assert( (nCellKey & SQLITE_MAX_U32)==(u64)nCellKey ); + + /* Read in the complete content of the index entry */ + sqlite3VdbeMemInit(&m, db, 0); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pCur, (u32)nCellKey, &m); + if( rc ){ + return rc; + } + + /* The index entry must begin with a header size */ + getVarint32NR((u8*)m.z, szHdr); + testcase( szHdr==3 ); + testcase( szHdr==(u32)m.n ); + testcase( szHdr>0x7fffffff ); + assert( m.n>=0 ); + if( unlikely(szHdr<3 || szHdr>(unsigned)m.n) ){ + goto idx_rowid_corruption; + } + + /* The last field of the index should be an integer - the ROWID. + ** Verify that the last entry really is an integer. */ + getVarint32NR((u8*)&m.z[szHdr-1], typeRowid); + testcase( typeRowid==1 ); + testcase( typeRowid==2 ); + testcase( typeRowid==3 ); + testcase( typeRowid==4 ); + testcase( typeRowid==5 ); + testcase( typeRowid==6 ); + testcase( typeRowid==8 ); + testcase( typeRowid==9 ); + if( unlikely(typeRowid<1 || typeRowid>9 || typeRowid==7) ){ + goto idx_rowid_corruption; + } + lenRowid = sqlite3SmallTypeSizes[typeRowid]; + testcase( (u32)m.n==szHdr+lenRowid ); + if( unlikely((u32)m.n<szHdr+lenRowid) ){ + goto idx_rowid_corruption; + } + + /* Fetch the integer off the end of the index record */ + sqlite3VdbeSerialGet((u8*)&m.z[m.n-lenRowid], typeRowid, &v); + *rowid = v.u.i; + sqlite3VdbeMemReleaseMalloc(&m); + return SQLITE_OK; + + /* Jump here if database corruption is detected after m has been + ** allocated. Free the m object and return SQLITE_CORRUPT. */ +idx_rowid_corruption: + testcase( m.szMalloc!=0 ); + sqlite3VdbeMemReleaseMalloc(&m); + return SQLITE_CORRUPT_BKPT; +} + +/* +** Compare the key of the index entry that cursor pC is pointing to against +** the key string in pUnpacked. Write into *pRes a number +** that is negative, zero, or positive if pC is less than, equal to, +** or greater than pUnpacked. Return SQLITE_OK on success. +** +** pUnpacked is either created without a rowid or is truncated so that it +** omits the rowid at the end. The rowid at the end of the index entry +** is ignored as well. Hence, this routine only compares the prefixes +** of the keys prior to the final rowid, not the entire key. +*/ +SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare( + sqlite3 *db, /* Database connection */ + VdbeCursor *pC, /* The cursor to compare against */ + UnpackedRecord *pUnpacked, /* Unpacked version of key */ + int *res /* Write the comparison result here */ +){ + i64 nCellKey = 0; + int rc; + BtCursor *pCur; + Mem m; + + assert( pC->eCurType==CURTYPE_BTREE ); + pCur = pC->uc.pCursor; + assert( sqlite3BtreeCursorIsValid(pCur) ); + nCellKey = sqlite3BtreePayloadSize(pCur); + /* nCellKey will always be between 0 and 0xffffffff because of the way + ** that btreeParseCellPtr() and sqlite3GetVarint32() are implemented */ + if( nCellKey<=0 || nCellKey>0x7fffffff ){ + *res = 0; + return SQLITE_CORRUPT_BKPT; + } + sqlite3VdbeMemInit(&m, db, 0); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pCur, (u32)nCellKey, &m); + if( rc ){ + return rc; + } + *res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, pUnpacked, 0); + sqlite3VdbeMemReleaseMalloc(&m); + return SQLITE_OK; +} + +/* +** This routine sets the value to be returned by subsequent calls to +** sqlite3_changes() on the database handle 'db'. +*/ +SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *db, i64 nChange){ + assert( sqlite3_mutex_held(db->mutex) ); + db->nChange = nChange; + db->nTotalChange += nChange; +} + +/* +** Set a flag in the vdbe to update the change counter when it is finalised +** or reset. +*/ +SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe *v){ + v->changeCntOn = 1; +} + +/* +** Mark every prepared statement associated with a database connection +** as expired. +** +** An expired statement means that recompilation of the statement is +** recommend. Statements expire when things happen that make their +** programs obsolete. Removing user-defined functions or collating +** sequences, or changing an authorization function are the types of +** things that make prepared statements obsolete. +** +** If iCode is 1, then expiration is advisory. The statement should +** be reprepared before being restarted, but if it is already running +** it is allowed to run to completion. +** +** Internally, this function just sets the Vdbe.expired flag on all +** prepared statements. The flag is set to 1 for an immediate expiration +** and set to 2 for an advisory expiration. +*/ +SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3 *db, int iCode){ + Vdbe *p; + for(p = db->pVdbe; p; p=p->pVNext){ + p->expired = iCode+1; + } +} + +/* +** Return the database associated with the Vdbe. +*/ +SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe *v){ + return v->db; +} + +/* +** Return the SQLITE_PREPARE flags for a Vdbe. +*/ +SQLITE_PRIVATE u8 sqlite3VdbePrepareFlags(Vdbe *v){ + return v->prepFlags; +} + +/* +** Return a pointer to an sqlite3_value structure containing the value bound +** parameter iVar of VM v. Except, if the value is an SQL NULL, return +** 0 instead. Unless it is NULL, apply affinity aff (one of the SQLITE_AFF_* +** constants) to the value before returning it. +** +** The returned value must be freed by the caller using sqlite3ValueFree(). +*/ +SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe *v, int iVar, u8 aff){ + assert( iVar>0 ); + if( v ){ + Mem *pMem = &v->aVar[iVar-1]; + assert( (v->db->flags & SQLITE_EnableQPSG)==0 ); + if( 0==(pMem->flags & MEM_Null) ){ + sqlite3_value *pRet = sqlite3ValueNew(v->db); + if( pRet ){ + sqlite3VdbeMemCopy((Mem *)pRet, pMem); + sqlite3ValueApplyAffinity(pRet, aff, SQLITE_UTF8); + } + return pRet; + } + } + return 0; +} + +/* +** Configure SQL variable iVar so that binding a new value to it signals +** to sqlite3_reoptimize() that re-preparing the statement may result +** in a better query plan. +*/ +SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){ + assert( iVar>0 ); + assert( (v->db->flags & SQLITE_EnableQPSG)==0 ); + if( iVar>=32 ){ + v->expmask |= 0x80000000; + }else{ + v->expmask |= ((u32)1 << (iVar-1)); + } +} + +/* +** Cause a function to throw an error if it was call from OP_PureFunc +** rather than OP_Function. +** +** OP_PureFunc means that the function must be deterministic, and should +** throw an error if it is given inputs that would make it non-deterministic. +** This routine is invoked by date/time functions that use non-deterministic +** features such as 'now'. +*/ +SQLITE_PRIVATE int sqlite3NotPureFunc(sqlite3_context *pCtx){ + const VdbeOp *pOp; +#ifdef SQLITE_ENABLE_STAT4 + if( pCtx->pVdbe==0 ) return 1; +#endif + pOp = pCtx->pVdbe->aOp + pCtx->iOp; + if( pOp->opcode==OP_PureFunc ){ + const char *zContext; + char *zMsg; + if( pOp->p5 & NC_IsCheck ){ + zContext = "a CHECK constraint"; + }else if( pOp->p5 & NC_GenCol ){ + zContext = "a generated column"; + }else{ + zContext = "an index"; + } + zMsg = sqlite3_mprintf("non-deterministic use of %s() in %s", + pCtx->pFunc->zName, zContext); + sqlite3_result_error(pCtx, zMsg, -1); + sqlite3_free(zMsg); + return 0; + } + return 1; +} + +#if defined(SQLITE_ENABLE_CURSOR_HINTS) && defined(SQLITE_DEBUG) +/* +** This Walker callback is used to help verify that calls to +** sqlite3BtreeCursorHint() with opcode BTREE_HINT_RANGE have +** byte-code register values correctly initialized. +*/ +SQLITE_PRIVATE int sqlite3CursorRangeHintExprCheck(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_REGISTER ){ + assert( (pWalker->u.aMem[pExpr->iTable].flags & MEM_Undefined)==0 ); + } + return WRC_Continue; +} +#endif /* SQLITE_ENABLE_CURSOR_HINTS && SQLITE_DEBUG */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Transfer error message text from an sqlite3_vtab.zErrMsg (text stored +** in memory obtained from sqlite3_malloc) into a Vdbe.zErrMsg (text stored +** in memory obtained from sqlite3DbMalloc). +*/ +SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){ + if( pVtab->zErrMsg ){ + sqlite3 *db = p->db; + sqlite3DbFree(db, p->zErrMsg); + p->zErrMsg = sqlite3DbStrDup(db, pVtab->zErrMsg); + sqlite3_free(pVtab->zErrMsg); + pVtab->zErrMsg = 0; + } +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + +/* +** If the second argument is not NULL, release any allocations associated +** with the memory cells in the p->aMem[] array. Also free the UnpackedRecord +** structure itself, using sqlite3DbFree(). +** +** This function is used to free UnpackedRecord structures allocated by +** the vdbeUnpackRecord() function found in vdbeapi.c. +*/ +static void vdbeFreeUnpacked(sqlite3 *db, int nField, UnpackedRecord *p){ + assert( db!=0 ); + if( p ){ + int i; + for(i=0; i<nField; i++){ + Mem *pMem = &p->aMem[i]; + if( pMem->zMalloc ) sqlite3VdbeMemReleaseMalloc(pMem); + } + sqlite3DbNNFreeNN(db, p); + } +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** Invoke the pre-update hook. If this is an UPDATE or DELETE pre-update call, +** then cursor passed as the second argument should point to the row about +** to be update or deleted. If the application calls sqlite3_preupdate_old(), +** the required value will be read from the row the cursor points to. +*/ +SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( + Vdbe *v, /* Vdbe pre-update hook is invoked by */ + VdbeCursor *pCsr, /* Cursor to grab old.* values from */ + int op, /* SQLITE_INSERT, UPDATE or DELETE */ + const char *zDb, /* Database name */ + Table *pTab, /* Modified table */ + i64 iKey1, /* Initial key value */ + int iReg, /* Register for new.* record */ + int iBlobWrite +){ + sqlite3 *db = v->db; + i64 iKey2; + PreUpdate preupdate; + const char *zTbl = pTab->zName; + static const u8 fakeSortOrder = 0; +#ifdef SQLITE_DEBUG + int nRealCol; + if( pTab->tabFlags & TF_WithoutRowid ){ + nRealCol = sqlite3PrimaryKeyIndex(pTab)->nColumn; + }else if( pTab->tabFlags & TF_HasVirtual ){ + nRealCol = pTab->nNVCol; + }else{ + nRealCol = pTab->nCol; + } +#endif + + assert( db->pPreUpdate==0 ); + memset(&preupdate, 0, sizeof(PreUpdate)); + if( HasRowid(pTab)==0 ){ + iKey1 = iKey2 = 0; + preupdate.pPk = sqlite3PrimaryKeyIndex(pTab); + }else{ + if( op==SQLITE_UPDATE ){ + iKey2 = v->aMem[iReg].u.i; + }else{ + iKey2 = iKey1; + } + } + + assert( pCsr!=0 ); + assert( pCsr->eCurType==CURTYPE_BTREE ); + assert( pCsr->nField==nRealCol + || (pCsr->nField==nRealCol+1 && op==SQLITE_DELETE && iReg==-1) + ); + + preupdate.v = v; + preupdate.pCsr = pCsr; + preupdate.op = op; + preupdate.iNewReg = iReg; + preupdate.keyinfo.db = db; + preupdate.keyinfo.enc = ENC(db); + preupdate.keyinfo.nKeyField = pTab->nCol; + preupdate.keyinfo.aSortFlags = (u8*)&fakeSortOrder; + preupdate.iKey1 = iKey1; + preupdate.iKey2 = iKey2; + preupdate.pTab = pTab; + preupdate.iBlobWrite = iBlobWrite; + + db->pPreUpdate = &preupdate; + db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); + db->pPreUpdate = 0; + sqlite3DbFree(db, preupdate.aRecord); + vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pUnpacked); + vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pNewUnpacked); + if( preupdate.aNew ){ + int i; + for(i=0; i<pCsr->nField; i++){ + sqlite3VdbeMemRelease(&preupdate.aNew[i]); + } + sqlite3DbNNFreeNN(db, preupdate.aNew); + } +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +/************** End of vdbeaux.c *********************************************/ +/************** Begin file vdbeapi.c *****************************************/ +/* +** 2004 May 26 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains code use to implement APIs that are part of the +** VDBE. +*/ +/* #include "sqliteInt.h" */ +/* #include "vdbeInt.h" */ +/* #include "opcodes.h" */ + +#ifndef SQLITE_OMIT_DEPRECATED +/* +** Return TRUE (non-zero) of the statement supplied as an argument needs +** to be recompiled. A statement needs to be recompiled whenever the +** execution environment changes in a way that would alter the program +** that sqlite3_prepare() generates. For example, if new functions or +** collating sequences are registered or if an authorizer function is +** added or changed. +*/ +SQLITE_API int sqlite3_expired(sqlite3_stmt *pStmt){ + Vdbe *p = (Vdbe*)pStmt; + return p==0 || p->expired; +} +#endif + +/* +** Check on a Vdbe to make sure it has not been finalized. Log +** an error and return true if it has been finalized (or is otherwise +** invalid). Return false if it is ok. +*/ +static int vdbeSafety(Vdbe *p){ + if( p->db==0 ){ + sqlite3_log(SQLITE_MISUSE, "API called with finalized prepared statement"); + return 1; + }else{ + return 0; + } +} +static int vdbeSafetyNotNull(Vdbe *p){ + if( p==0 ){ + sqlite3_log(SQLITE_MISUSE, "API called with NULL prepared statement"); + return 1; + }else{ + return vdbeSafety(p); + } +} + +#ifndef SQLITE_OMIT_TRACE +/* +** Invoke the profile callback. This routine is only called if we already +** know that the profile callback is defined and needs to be invoked. +*/ +static SQLITE_NOINLINE void invokeProfileCallback(sqlite3 *db, Vdbe *p){ + sqlite3_int64 iNow; + sqlite3_int64 iElapse; + assert( p->startTime>0 ); + assert( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 ); + assert( db->init.busy==0 ); + assert( p->zSql!=0 ); + sqlite3OsCurrentTimeInt64(db->pVfs, &iNow); + iElapse = (iNow - p->startTime)*1000000; +#ifndef SQLITE_OMIT_DEPRECATED + if( db->xProfile ){ + db->xProfile(db->pProfileArg, p->zSql, iElapse); + } +#endif + if( db->mTrace & SQLITE_TRACE_PROFILE ){ + db->trace.xV2(SQLITE_TRACE_PROFILE, db->pTraceArg, p, (void*)&iElapse); + } + p->startTime = 0; +} +/* +** The checkProfileCallback(DB,P) macro checks to see if a profile callback +** is needed, and it invokes the callback if it is needed. +*/ +# define checkProfileCallback(DB,P) \ + if( ((P)->startTime)>0 ){ invokeProfileCallback(DB,P); } +#else +# define checkProfileCallback(DB,P) /*no-op*/ +#endif + +/* +** The following routine destroys a virtual machine that is created by +** the sqlite3_compile() routine. The integer returned is an SQLITE_ +** success/failure code that describes the result of executing the virtual +** machine. +** +** This routine sets the error code and string returned by +** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16(). +*/ +SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt){ + int rc; + if( pStmt==0 ){ + /* IMPLEMENTATION-OF: R-57228-12904 Invoking sqlite3_finalize() on a NULL + ** pointer is a harmless no-op. */ + rc = SQLITE_OK; + }else{ + Vdbe *v = (Vdbe*)pStmt; + sqlite3 *db = v->db; + if( vdbeSafety(v) ) return SQLITE_MISUSE_BKPT; + sqlite3_mutex_enter(db->mutex); + checkProfileCallback(db, v); + assert( v->eVdbeState>=VDBE_READY_STATE ); + rc = sqlite3VdbeReset(v); + sqlite3VdbeDelete(v); + rc = sqlite3ApiExit(db, rc); + sqlite3LeaveMutexAndCloseZombie(db); + } + return rc; +} + +/* +** Terminate the current execution of an SQL statement and reset it +** back to its starting state so that it can be reused. A success code from +** the prior execution is returned. +** +** This routine sets the error code and string returned by +** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16(). +*/ +SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt){ + int rc; + if( pStmt==0 ){ + rc = SQLITE_OK; + }else{ + Vdbe *v = (Vdbe*)pStmt; + sqlite3 *db = v->db; + sqlite3_mutex_enter(db->mutex); + checkProfileCallback(db, v); + rc = sqlite3VdbeReset(v); + sqlite3VdbeRewind(v); + assert( (rc & (db->errMask))==rc ); + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + } + return rc; +} + +/* +** Set all the parameters in the compiled SQL statement to NULL. +*/ +SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt *pStmt){ + int i; + int rc = SQLITE_OK; + Vdbe *p = (Vdbe*)pStmt; +#if SQLITE_THREADSAFE + sqlite3_mutex *mutex = ((Vdbe*)pStmt)->db->mutex; +#endif + sqlite3_mutex_enter(mutex); + for(i=0; i<p->nVar; i++){ + sqlite3VdbeMemRelease(&p->aVar[i]); + p->aVar[i].flags = MEM_Null; + } + assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || p->expmask==0 ); + if( p->expmask ){ + p->expired = 1; + } + sqlite3_mutex_leave(mutex); + return rc; +} + + +/**************************** sqlite3_value_ ******************************* +** The following routines extract information from a Mem or sqlite3_value +** structure. +*/ +SQLITE_API const void *sqlite3_value_blob(sqlite3_value *pVal){ + Mem *p = (Mem*)pVal; + if( p->flags & (MEM_Blob|MEM_Str) ){ + if( ExpandBlob(p)!=SQLITE_OK ){ + assert( p->flags==MEM_Null && p->z==0 ); + return 0; + } + p->flags |= MEM_Blob; + return p->n ? p->z : 0; + }else{ + return sqlite3_value_text(pVal); + } +} +SQLITE_API int sqlite3_value_bytes(sqlite3_value *pVal){ + return sqlite3ValueBytes(pVal, SQLITE_UTF8); +} +SQLITE_API int sqlite3_value_bytes16(sqlite3_value *pVal){ + return sqlite3ValueBytes(pVal, SQLITE_UTF16NATIVE); +} +SQLITE_API double sqlite3_value_double(sqlite3_value *pVal){ + return sqlite3VdbeRealValue((Mem*)pVal); +} +SQLITE_API int sqlite3_value_int(sqlite3_value *pVal){ + return (int)sqlite3VdbeIntValue((Mem*)pVal); +} +SQLITE_API sqlite_int64 sqlite3_value_int64(sqlite3_value *pVal){ + return sqlite3VdbeIntValue((Mem*)pVal); +} +SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value *pVal){ + Mem *pMem = (Mem*)pVal; + return ((pMem->flags & MEM_Subtype) ? pMem->eSubtype : 0); +} +SQLITE_API void *sqlite3_value_pointer(sqlite3_value *pVal, const char *zPType){ + Mem *p = (Mem*)pVal; + if( (p->flags&(MEM_TypeMask|MEM_Term|MEM_Subtype)) == + (MEM_Null|MEM_Term|MEM_Subtype) + && zPType!=0 + && p->eSubtype=='p' + && strcmp(p->u.zPType, zPType)==0 + ){ + return (void*)p->z; + }else{ + return 0; + } +} +SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value *pVal){ + return (const unsigned char *)sqlite3ValueText(pVal, SQLITE_UTF8); +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API const void *sqlite3_value_text16(sqlite3_value* pVal){ + return sqlite3ValueText(pVal, SQLITE_UTF16NATIVE); +} +SQLITE_API const void *sqlite3_value_text16be(sqlite3_value *pVal){ + return sqlite3ValueText(pVal, SQLITE_UTF16BE); +} +SQLITE_API const void *sqlite3_value_text16le(sqlite3_value *pVal){ + return sqlite3ValueText(pVal, SQLITE_UTF16LE); +} +#endif /* SQLITE_OMIT_UTF16 */ +/* EVIDENCE-OF: R-12793-43283 Every value in SQLite has one of five +** fundamental datatypes: 64-bit signed integer 64-bit IEEE floating +** point number string BLOB NULL +*/ +SQLITE_API int sqlite3_value_type(sqlite3_value* pVal){ + static const u8 aType[] = { + SQLITE_BLOB, /* 0x00 (not possible) */ + SQLITE_NULL, /* 0x01 NULL */ + SQLITE_TEXT, /* 0x02 TEXT */ + SQLITE_NULL, /* 0x03 (not possible) */ + SQLITE_INTEGER, /* 0x04 INTEGER */ + SQLITE_NULL, /* 0x05 (not possible) */ + SQLITE_INTEGER, /* 0x06 INTEGER + TEXT */ + SQLITE_NULL, /* 0x07 (not possible) */ + SQLITE_FLOAT, /* 0x08 FLOAT */ + SQLITE_NULL, /* 0x09 (not possible) */ + SQLITE_FLOAT, /* 0x0a FLOAT + TEXT */ + SQLITE_NULL, /* 0x0b (not possible) */ + SQLITE_INTEGER, /* 0x0c (not possible) */ + SQLITE_NULL, /* 0x0d (not possible) */ + SQLITE_INTEGER, /* 0x0e (not possible) */ + SQLITE_NULL, /* 0x0f (not possible) */ + SQLITE_BLOB, /* 0x10 BLOB */ + SQLITE_NULL, /* 0x11 (not possible) */ + SQLITE_TEXT, /* 0x12 (not possible) */ + SQLITE_NULL, /* 0x13 (not possible) */ + SQLITE_INTEGER, /* 0x14 INTEGER + BLOB */ + SQLITE_NULL, /* 0x15 (not possible) */ + SQLITE_INTEGER, /* 0x16 (not possible) */ + SQLITE_NULL, /* 0x17 (not possible) */ + SQLITE_FLOAT, /* 0x18 FLOAT + BLOB */ + SQLITE_NULL, /* 0x19 (not possible) */ + SQLITE_FLOAT, /* 0x1a (not possible) */ + SQLITE_NULL, /* 0x1b (not possible) */ + SQLITE_INTEGER, /* 0x1c (not possible) */ + SQLITE_NULL, /* 0x1d (not possible) */ + SQLITE_INTEGER, /* 0x1e (not possible) */ + SQLITE_NULL, /* 0x1f (not possible) */ + SQLITE_FLOAT, /* 0x20 INTREAL */ + SQLITE_NULL, /* 0x21 (not possible) */ + SQLITE_FLOAT, /* 0x22 INTREAL + TEXT */ + SQLITE_NULL, /* 0x23 (not possible) */ + SQLITE_FLOAT, /* 0x24 (not possible) */ + SQLITE_NULL, /* 0x25 (not possible) */ + SQLITE_FLOAT, /* 0x26 (not possible) */ + SQLITE_NULL, /* 0x27 (not possible) */ + SQLITE_FLOAT, /* 0x28 (not possible) */ + SQLITE_NULL, /* 0x29 (not possible) */ + SQLITE_FLOAT, /* 0x2a (not possible) */ + SQLITE_NULL, /* 0x2b (not possible) */ + SQLITE_FLOAT, /* 0x2c (not possible) */ + SQLITE_NULL, /* 0x2d (not possible) */ + SQLITE_FLOAT, /* 0x2e (not possible) */ + SQLITE_NULL, /* 0x2f (not possible) */ + SQLITE_BLOB, /* 0x30 (not possible) */ + SQLITE_NULL, /* 0x31 (not possible) */ + SQLITE_TEXT, /* 0x32 (not possible) */ + SQLITE_NULL, /* 0x33 (not possible) */ + SQLITE_FLOAT, /* 0x34 (not possible) */ + SQLITE_NULL, /* 0x35 (not possible) */ + SQLITE_FLOAT, /* 0x36 (not possible) */ + SQLITE_NULL, /* 0x37 (not possible) */ + SQLITE_FLOAT, /* 0x38 (not possible) */ + SQLITE_NULL, /* 0x39 (not possible) */ + SQLITE_FLOAT, /* 0x3a (not possible) */ + SQLITE_NULL, /* 0x3b (not possible) */ + SQLITE_FLOAT, /* 0x3c (not possible) */ + SQLITE_NULL, /* 0x3d (not possible) */ + SQLITE_FLOAT, /* 0x3e (not possible) */ + SQLITE_NULL, /* 0x3f (not possible) */ + }; +#ifdef SQLITE_DEBUG + { + int eType = SQLITE_BLOB; + if( pVal->flags & MEM_Null ){ + eType = SQLITE_NULL; + }else if( pVal->flags & (MEM_Real|MEM_IntReal) ){ + eType = SQLITE_FLOAT; + }else if( pVal->flags & MEM_Int ){ + eType = SQLITE_INTEGER; + }else if( pVal->flags & MEM_Str ){ + eType = SQLITE_TEXT; + } + assert( eType == aType[pVal->flags&MEM_AffMask] ); + } +#endif + return aType[pVal->flags&MEM_AffMask]; +} +SQLITE_API int sqlite3_value_encoding(sqlite3_value *pVal){ + return pVal->enc; +} + +/* Return true if a parameter to xUpdate represents an unchanged column */ +SQLITE_API int sqlite3_value_nochange(sqlite3_value *pVal){ + return (pVal->flags&(MEM_Null|MEM_Zero))==(MEM_Null|MEM_Zero); +} + +/* Return true if a parameter value originated from an sqlite3_bind() */ +SQLITE_API int sqlite3_value_frombind(sqlite3_value *pVal){ + return (pVal->flags&MEM_FromBind)!=0; +} + +/* Make a copy of an sqlite3_value object +*/ +SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){ + sqlite3_value *pNew; + if( pOrig==0 ) return 0; + pNew = sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return 0; + memset(pNew, 0, sizeof(*pNew)); + memcpy(pNew, pOrig, MEMCELLSIZE); + pNew->flags &= ~MEM_Dyn; + pNew->db = 0; + if( pNew->flags&(MEM_Str|MEM_Blob) ){ + pNew->flags &= ~(MEM_Static|MEM_Dyn); + pNew->flags |= MEM_Ephem; + if( sqlite3VdbeMemMakeWriteable(pNew)!=SQLITE_OK ){ + sqlite3ValueFree(pNew); + pNew = 0; + } + }else if( pNew->flags & MEM_Null ){ + /* Do not duplicate pointer values */ + pNew->flags &= ~(MEM_Term|MEM_Subtype); + } + return pNew; +} + +/* Destroy an sqlite3_value object previously obtained from +** sqlite3_value_dup(). +*/ +SQLITE_API void sqlite3_value_free(sqlite3_value *pOld){ + sqlite3ValueFree(pOld); +} + + +/**************************** sqlite3_result_ ******************************* +** The following routines are used by user-defined functions to specify +** the function result. +** +** The setStrOrError() function calls sqlite3VdbeMemSetStr() to store the +** result as a string or blob. Appropriate errors are set if the string/blob +** is too big or if an OOM occurs. +** +** The invokeValueDestructor(P,X) routine invokes destructor function X() +** on value P is not going to be used and need to be destroyed. +*/ +static void setResultStrOrError( + sqlite3_context *pCtx, /* Function context */ + const char *z, /* String pointer */ + int n, /* Bytes in string, or negative */ + u8 enc, /* Encoding of z. 0 for BLOBs */ + void (*xDel)(void*) /* Destructor function */ +){ + Mem *pOut = pCtx->pOut; + int rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel); + if( rc ){ + if( rc==SQLITE_TOOBIG ){ + sqlite3_result_error_toobig(pCtx); + }else{ + /* The only errors possible from sqlite3VdbeMemSetStr are + ** SQLITE_TOOBIG and SQLITE_NOMEM */ + assert( rc==SQLITE_NOMEM ); + sqlite3_result_error_nomem(pCtx); + } + return; + } + sqlite3VdbeChangeEncoding(pOut, pCtx->enc); + if( sqlite3VdbeMemTooBig(pOut) ){ + sqlite3_result_error_toobig(pCtx); + } +} +static int invokeValueDestructor( + const void *p, /* Value to destroy */ + void (*xDel)(void*), /* The destructor */ + sqlite3_context *pCtx /* Set a SQLITE_TOOBIG error if no NULL */ +){ + assert( xDel!=SQLITE_DYNAMIC ); + if( xDel==0 ){ + /* noop */ + }else if( xDel==SQLITE_TRANSIENT ){ + /* noop */ + }else{ + xDel((void*)p); + } + sqlite3_result_error_toobig(pCtx); + return SQLITE_TOOBIG; +} +SQLITE_API void sqlite3_result_blob( + sqlite3_context *pCtx, + const void *z, + int n, + void (*xDel)(void *) +){ + assert( n>=0 ); + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + setResultStrOrError(pCtx, z, n, 0, xDel); +} +SQLITE_API void sqlite3_result_blob64( + sqlite3_context *pCtx, + const void *z, + sqlite3_uint64 n, + void (*xDel)(void *) +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + assert( xDel!=SQLITE_DYNAMIC ); + if( n>0x7fffffff ){ + (void)invokeValueDestructor(z, xDel, pCtx); + }else{ + setResultStrOrError(pCtx, z, (int)n, 0, xDel); + } +} +SQLITE_API void sqlite3_result_double(sqlite3_context *pCtx, double rVal){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetDouble(pCtx->pOut, rVal); +} +SQLITE_API void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + pCtx->isError = SQLITE_ERROR; + sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF8, SQLITE_TRANSIENT); +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + pCtx->isError = SQLITE_ERROR; + sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT); +} +#endif +SQLITE_API void sqlite3_result_int(sqlite3_context *pCtx, int iVal){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetInt64(pCtx->pOut, (i64)iVal); +} +SQLITE_API void sqlite3_result_int64(sqlite3_context *pCtx, i64 iVal){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetInt64(pCtx->pOut, iVal); +} +SQLITE_API void sqlite3_result_null(sqlite3_context *pCtx){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetNull(pCtx->pOut); +} +SQLITE_API void sqlite3_result_pointer( + sqlite3_context *pCtx, + void *pPtr, + const char *zPType, + void (*xDestructor)(void*) +){ + Mem *pOut = pCtx->pOut; + assert( sqlite3_mutex_held(pOut->db->mutex) ); + sqlite3VdbeMemRelease(pOut); + pOut->flags = MEM_Null; + sqlite3VdbeMemSetPointer(pOut, pPtr, zPType, xDestructor); +} +SQLITE_API void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){ + Mem *pOut = pCtx->pOut; + assert( sqlite3_mutex_held(pOut->db->mutex) ); + pOut->eSubtype = eSubtype & 0xff; + pOut->flags |= MEM_Subtype; +} +SQLITE_API void sqlite3_result_text( + sqlite3_context *pCtx, + const char *z, + int n, + void (*xDel)(void *) +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + setResultStrOrError(pCtx, z, n, SQLITE_UTF8, xDel); +} +SQLITE_API void sqlite3_result_text64( + sqlite3_context *pCtx, + const char *z, + sqlite3_uint64 n, + void (*xDel)(void *), + unsigned char enc +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + assert( xDel!=SQLITE_DYNAMIC ); + if( enc!=SQLITE_UTF8 ){ + if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + n &= ~(u64)1; + } + if( n>0x7fffffff ){ + (void)invokeValueDestructor(z, xDel, pCtx); + }else{ + setResultStrOrError(pCtx, z, (int)n, enc, xDel); + sqlite3VdbeMemZeroTerminateIfAble(pCtx->pOut); + } +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API void sqlite3_result_text16( + sqlite3_context *pCtx, + const void *z, + int n, + void (*xDel)(void *) +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16NATIVE, xDel); +} +SQLITE_API void sqlite3_result_text16be( + sqlite3_context *pCtx, + const void *z, + int n, + void (*xDel)(void *) +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16BE, xDel); +} +SQLITE_API void sqlite3_result_text16le( + sqlite3_context *pCtx, + const void *z, + int n, + void (*xDel)(void *) +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16LE, xDel); +} +#endif /* SQLITE_OMIT_UTF16 */ +SQLITE_API void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){ + Mem *pOut = pCtx->pOut; + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemCopy(pOut, pValue); + sqlite3VdbeChangeEncoding(pOut, pCtx->enc); + if( sqlite3VdbeMemTooBig(pOut) ){ + sqlite3_result_error_toobig(pCtx); + } +} +SQLITE_API void sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){ + sqlite3_result_zeroblob64(pCtx, n>0 ? n : 0); +} +SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){ + Mem *pOut = pCtx->pOut; + assert( sqlite3_mutex_held(pOut->db->mutex) ); + if( n>(u64)pOut->db->aLimit[SQLITE_LIMIT_LENGTH] ){ + sqlite3_result_error_toobig(pCtx); + return SQLITE_TOOBIG; + } +#ifndef SQLITE_OMIT_INCRBLOB + sqlite3VdbeMemSetZeroBlob(pCtx->pOut, (int)n); + return SQLITE_OK; +#else + return sqlite3VdbeMemSetZeroBlob(pCtx->pOut, (int)n); +#endif +} +SQLITE_API void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ + pCtx->isError = errCode ? errCode : -1; +#ifdef SQLITE_DEBUG + if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode; +#endif + if( pCtx->pOut->flags & MEM_Null ){ + setResultStrOrError(pCtx, sqlite3ErrStr(errCode), -1, SQLITE_UTF8, + SQLITE_STATIC); + } +} + +/* Force an SQLITE_TOOBIG error. */ +SQLITE_API void sqlite3_result_error_toobig(sqlite3_context *pCtx){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + pCtx->isError = SQLITE_TOOBIG; + sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1, + SQLITE_UTF8, SQLITE_STATIC); +} + +/* An SQLITE_NOMEM error. */ +SQLITE_API void sqlite3_result_error_nomem(sqlite3_context *pCtx){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetNull(pCtx->pOut); + pCtx->isError = SQLITE_NOMEM_BKPT; + sqlite3OomFault(pCtx->pOut->db); +} + +#ifndef SQLITE_UNTESTABLE +/* Force the INT64 value currently stored as the result to be +** a MEM_IntReal value. See the SQLITE_TESTCTRL_RESULT_INTREAL +** test-control. +*/ +SQLITE_PRIVATE void sqlite3ResultIntReal(sqlite3_context *pCtx){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + if( pCtx->pOut->flags & MEM_Int ){ + pCtx->pOut->flags &= ~MEM_Int; + pCtx->pOut->flags |= MEM_IntReal; + } +} +#endif + + +/* +** This function is called after a transaction has been committed. It +** invokes callbacks registered with sqlite3_wal_hook() as required. +*/ +static int doWalCallbacks(sqlite3 *db){ + int rc = SQLITE_OK; +#ifndef SQLITE_OMIT_WAL + int i; + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + int nEntry; + sqlite3BtreeEnter(pBt); + nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt)); + sqlite3BtreeLeave(pBt); + if( nEntry>0 && db->xWalCallback && rc==SQLITE_OK ){ + rc = db->xWalCallback(db->pWalArg, db, db->aDb[i].zDbSName, nEntry); + } + } + } +#endif + return rc; +} + + +/* +** Execute the statement pStmt, either until a row of data is ready, the +** statement is completely executed or an error occurs. +** +** This routine implements the bulk of the logic behind the sqlite_step() +** API. The only thing omitted is the automatic recompile if a +** schema change has occurred. That detail is handled by the +** outer sqlite3_step() wrapper procedure. +*/ +static int sqlite3Step(Vdbe *p){ + sqlite3 *db; + int rc; + + assert(p); + db = p->db; + if( p->eVdbeState!=VDBE_RUN_STATE ){ + restart_step: + if( p->eVdbeState==VDBE_READY_STATE ){ + if( p->expired ){ + p->rc = SQLITE_SCHEMA; + rc = SQLITE_ERROR; + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ + /* If this statement was prepared using saved SQL and an + ** error has occurred, then return the error code in p->rc to the + ** caller. Set the error code in the database handle to the same + ** value. + */ + rc = sqlite3VdbeTransferError(p); + } + goto end_of_step; + } + + /* If there are no other statements currently running, then + ** reset the interrupt flag. This prevents a call to sqlite3_interrupt + ** from interrupting a statement that has not yet started. + */ + if( db->nVdbeActive==0 ){ + AtomicStore(&db->u1.isInterrupted, 0); + } + + assert( db->nVdbeWrite>0 || db->autoCommit==0 + || (db->nDeferredCons==0 && db->nDeferredImmCons==0) + ); + +#ifndef SQLITE_OMIT_TRACE + if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 + && !db->init.busy && p->zSql ){ + sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime); + }else{ + assert( p->startTime==0 ); + } +#endif + + db->nVdbeActive++; + if( p->readOnly==0 ) db->nVdbeWrite++; + if( p->bIsReader ) db->nVdbeRead++; + p->pc = 0; + p->eVdbeState = VDBE_RUN_STATE; + }else + + if( ALWAYS(p->eVdbeState==VDBE_HALT_STATE) ){ + /* We used to require that sqlite3_reset() be called before retrying + ** sqlite3_step() after any error or after SQLITE_DONE. But beginning + ** with version 3.7.0, we changed this so that sqlite3_reset() would + ** be called automatically instead of throwing the SQLITE_MISUSE error. + ** This "automatic-reset" change is not technically an incompatibility, + ** since any application that receives an SQLITE_MISUSE is broken by + ** definition. + ** + ** Nevertheless, some published applications that were originally written + ** for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE + ** returns, and those were broken by the automatic-reset change. As a + ** a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the + ** legacy behavior of returning SQLITE_MISUSE for cases where the + ** previous sqlite3_step() returned something other than a SQLITE_LOCKED + ** or SQLITE_BUSY error. + */ +#ifdef SQLITE_OMIT_AUTORESET + if( (rc = p->rc&0xff)==SQLITE_BUSY || rc==SQLITE_LOCKED ){ + sqlite3_reset((sqlite3_stmt*)p); + }else{ + return SQLITE_MISUSE_BKPT; + } +#else + sqlite3_reset((sqlite3_stmt*)p); +#endif + assert( p->eVdbeState==VDBE_READY_STATE ); + goto restart_step; + } + } + +#ifdef SQLITE_DEBUG + p->rcApp = SQLITE_OK; +#endif +#ifndef SQLITE_OMIT_EXPLAIN + if( p->explain ){ + rc = sqlite3VdbeList(p); + }else +#endif /* SQLITE_OMIT_EXPLAIN */ + { + db->nVdbeExec++; + rc = sqlite3VdbeExec(p); + db->nVdbeExec--; + } + + if( rc==SQLITE_ROW ){ + assert( p->rc==SQLITE_OK ); + assert( db->mallocFailed==0 ); + db->errCode = SQLITE_ROW; + return SQLITE_ROW; + }else{ +#ifndef SQLITE_OMIT_TRACE + /* If the statement completed successfully, invoke the profile callback */ + checkProfileCallback(db, p); +#endif + p->pResultRow = 0; + if( rc==SQLITE_DONE && db->autoCommit ){ + assert( p->rc==SQLITE_OK ); + p->rc = doWalCallbacks(db); + if( p->rc!=SQLITE_OK ){ + rc = SQLITE_ERROR; + } + }else if( rc!=SQLITE_DONE && (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ + /* If this statement was prepared using saved SQL and an + ** error has occurred, then return the error code in p->rc to the + ** caller. Set the error code in the database handle to the same value. + */ + rc = sqlite3VdbeTransferError(p); + } + } + + db->errCode = rc; + if( SQLITE_NOMEM==sqlite3ApiExit(p->db, p->rc) ){ + p->rc = SQLITE_NOMEM_BKPT; + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ) rc = p->rc; + } +end_of_step: + /* There are only a limited number of result codes allowed from the + ** statements prepared using the legacy sqlite3_prepare() interface */ + assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 + || rc==SQLITE_ROW || rc==SQLITE_DONE || rc==SQLITE_ERROR + || (rc&0xff)==SQLITE_BUSY || rc==SQLITE_MISUSE + ); + return (rc&db->errMask); +} + +/* +** This is the top-level implementation of sqlite3_step(). Call +** sqlite3Step() to do most of the work. If a schema error occurs, +** call sqlite3Reprepare() and try again. +*/ +SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){ + int rc = SQLITE_OK; /* Result from sqlite3Step() */ + Vdbe *v = (Vdbe*)pStmt; /* the prepared statement */ + int cnt = 0; /* Counter to prevent infinite loop of reprepares */ + sqlite3 *db; /* The database connection */ + + if( vdbeSafetyNotNull(v) ){ + return SQLITE_MISUSE_BKPT; + } + db = v->db; + sqlite3_mutex_enter(db->mutex); + while( (rc = sqlite3Step(v))==SQLITE_SCHEMA + && cnt++ < SQLITE_MAX_SCHEMA_RETRY ){ + int savedPc = v->pc; + rc = sqlite3Reprepare(v); + if( rc!=SQLITE_OK ){ + /* This case occurs after failing to recompile an sql statement. + ** The error message from the SQL compiler has already been loaded + ** into the database handle. This block copies the error message + ** from the database handle into the statement and sets the statement + ** program counter to 0 to ensure that when the statement is + ** finalized or reset the parser error message is available via + ** sqlite3_errmsg() and sqlite3_errcode(). + */ + const char *zErr = (const char *)sqlite3_value_text(db->pErr); + sqlite3DbFree(db, v->zErrMsg); + if( !db->mallocFailed ){ + v->zErrMsg = sqlite3DbStrDup(db, zErr); + v->rc = rc = sqlite3ApiExit(db, rc); + } else { + v->zErrMsg = 0; + v->rc = rc = SQLITE_NOMEM_BKPT; + } + break; + } + sqlite3_reset(pStmt); + if( savedPc>=0 ){ + /* Setting minWriteFileFormat to 254 is a signal to the OP_Init and + ** OP_Trace opcodes to *not* perform SQLITE_TRACE_STMT because it has + ** already been done once on a prior invocation that failed due to + ** SQLITE_SCHEMA. tag-20220401a */ + v->minWriteFileFormat = 254; + } + assert( v->expired==0 ); + } + sqlite3_mutex_leave(db->mutex); + return rc; +} + + +/* +** Extract the user data from a sqlite3_context structure and return a +** pointer to it. +*/ +SQLITE_API void *sqlite3_user_data(sqlite3_context *p){ + assert( p && p->pFunc ); + return p->pFunc->pUserData; +} + +/* +** Extract the user data from a sqlite3_context structure and return a +** pointer to it. +** +** IMPLEMENTATION-OF: R-46798-50301 The sqlite3_context_db_handle() interface +** returns a copy of the pointer to the database connection (the 1st +** parameter) of the sqlite3_create_function() and +** sqlite3_create_function16() routines that originally registered the +** application defined function. +*/ +SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){ + assert( p && p->pOut ); + return p->pOut->db; +} + +/* +** If this routine is invoked from within an xColumn method of a virtual +** table, then it returns true if and only if the the call is during an +** UPDATE operation and the value of the column will not be modified +** by the UPDATE. +** +** If this routine is called from any context other than within the +** xColumn method of a virtual table, then the return value is meaningless +** and arbitrary. +** +** Virtual table implements might use this routine to optimize their +** performance by substituting a NULL result, or some other light-weight +** value, as a signal to the xUpdate routine that the column is unchanged. +*/ +SQLITE_API int sqlite3_vtab_nochange(sqlite3_context *p){ + assert( p ); + return sqlite3_value_nochange(p->pOut); +} + +/* +** The destructor function for a ValueList object. This needs to be +** a separate function, unknowable to the application, to ensure that +** calls to sqlite3_vtab_in_first()/sqlite3_vtab_in_next() that are not +** preceded by activation of IN processing via sqlite3_vtab_int() do not +** try to access a fake ValueList object inserted by a hostile extension. +*/ +SQLITE_PRIVATE void sqlite3VdbeValueListFree(void *pToDelete){ + sqlite3_free(pToDelete); +} + +/* +** Implementation of sqlite3_vtab_in_first() (if bNext==0) and +** sqlite3_vtab_in_next() (if bNext!=0). +*/ +static int valueFromValueList( + sqlite3_value *pVal, /* Pointer to the ValueList object */ + sqlite3_value **ppOut, /* Store the next value from the list here */ + int bNext /* 1 for _next(). 0 for _first() */ +){ + int rc; + ValueList *pRhs; + + *ppOut = 0; + if( pVal==0 ) return SQLITE_MISUSE_BKPT; + if( (pVal->flags & MEM_Dyn)==0 || pVal->xDel!=sqlite3VdbeValueListFree ){ + return SQLITE_ERROR; + }else{ + assert( (pVal->flags&(MEM_TypeMask|MEM_Term|MEM_Subtype)) == + (MEM_Null|MEM_Term|MEM_Subtype) ); + assert( pVal->eSubtype=='p' ); + assert( pVal->u.zPType!=0 && strcmp(pVal->u.zPType,"ValueList")==0 ); + pRhs = (ValueList*)pVal->z; + } + if( bNext ){ + rc = sqlite3BtreeNext(pRhs->pCsr, 0); + }else{ + int dummy = 0; + rc = sqlite3BtreeFirst(pRhs->pCsr, &dummy); + assert( rc==SQLITE_OK || sqlite3BtreeEof(pRhs->pCsr) ); + if( sqlite3BtreeEof(pRhs->pCsr) ) rc = SQLITE_DONE; + } + if( rc==SQLITE_OK ){ + u32 sz; /* Size of current row in bytes */ + Mem sMem; /* Raw content of current row */ + memset(&sMem, 0, sizeof(sMem)); + sz = sqlite3BtreePayloadSize(pRhs->pCsr); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pRhs->pCsr,(int)sz,&sMem); + if( rc==SQLITE_OK ){ + u8 *zBuf = (u8*)sMem.z; + u32 iSerial; + sqlite3_value *pOut = pRhs->pOut; + int iOff = 1 + getVarint32(&zBuf[1], iSerial); + sqlite3VdbeSerialGet(&zBuf[iOff], iSerial, pOut); + pOut->enc = ENC(pOut->db); + if( (pOut->flags & MEM_Ephem)!=0 && sqlite3VdbeMemMakeWriteable(pOut) ){ + rc = SQLITE_NOMEM; + }else{ + *ppOut = pOut; + } + } + sqlite3VdbeMemRelease(&sMem); + } + return rc; +} + +/* +** Set the iterator value pVal to point to the first value in the set. +** Set (*ppOut) to point to this value before returning. +*/ +SQLITE_API int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut){ + return valueFromValueList(pVal, ppOut, 0); +} + +/* +** Set the iterator value pVal to point to the next value in the set. +** Set (*ppOut) to point to this value before returning. +*/ +SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut){ + return valueFromValueList(pVal, ppOut, 1); +} + +/* +** Return the current time for a statement. If the current time +** is requested more than once within the same run of a single prepared +** statement, the exact same time is returned for each invocation regardless +** of the amount of time that elapses between invocations. In other words, +** the time returned is always the time of the first call. +*/ +SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context *p){ + int rc; +#ifndef SQLITE_ENABLE_STAT4 + sqlite3_int64 *piTime = &p->pVdbe->iCurrentTime; + assert( p->pVdbe!=0 ); +#else + sqlite3_int64 iTime = 0; + sqlite3_int64 *piTime = p->pVdbe!=0 ? &p->pVdbe->iCurrentTime : &iTime; +#endif + if( *piTime==0 ){ + rc = sqlite3OsCurrentTimeInt64(p->pOut->db->pVfs, piTime); + if( rc ) *piTime = 0; + } + return *piTime; +} + +/* +** Create a new aggregate context for p and return a pointer to +** its pMem->z element. +*/ +static SQLITE_NOINLINE void *createAggContext(sqlite3_context *p, int nByte){ + Mem *pMem = p->pMem; + assert( (pMem->flags & MEM_Agg)==0 ); + if( nByte<=0 ){ + sqlite3VdbeMemSetNull(pMem); + pMem->z = 0; + }else{ + sqlite3VdbeMemClearAndResize(pMem, nByte); + pMem->flags = MEM_Agg; + pMem->u.pDef = p->pFunc; + if( pMem->z ){ + memset(pMem->z, 0, nByte); + } + } + return (void*)pMem->z; +} + +/* +** Allocate or return the aggregate context for a user function. A new +** context is allocated on the first call. Subsequent calls return the +** same context that was returned on prior calls. +*/ +SQLITE_API void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){ + assert( p && p->pFunc && p->pFunc->xFinalize ); + assert( sqlite3_mutex_held(p->pOut->db->mutex) ); + testcase( nByte<0 ); + if( (p->pMem->flags & MEM_Agg)==0 ){ + return createAggContext(p, nByte); + }else{ + return (void*)p->pMem->z; + } +} + +/* +** Return the auxiliary data pointer, if any, for the iArg'th argument to +** the user-function defined by pCtx. +** +** The left-most argument is 0. +** +** Undocumented behavior: If iArg is negative then access a cache of +** auxiliary data pointers that is available to all functions within a +** single prepared statement. The iArg values must match. +*/ +SQLITE_API void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){ + AuxData *pAuxData; + + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); +#if SQLITE_ENABLE_STAT4 + if( pCtx->pVdbe==0 ) return 0; +#else + assert( pCtx->pVdbe!=0 ); +#endif + for(pAuxData=pCtx->pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNextAux){ + if( pAuxData->iAuxArg==iArg && (pAuxData->iAuxOp==pCtx->iOp || iArg<0) ){ + return pAuxData->pAux; + } + } + return 0; +} + +/* +** Set the auxiliary data pointer and delete function, for the iArg'th +** argument to the user-function defined by pCtx. Any previous value is +** deleted by calling the delete function specified when it was set. +** +** The left-most argument is 0. +** +** Undocumented behavior: If iArg is negative then make the data available +** to all functions within the current prepared statement using iArg as an +** access code. +*/ +SQLITE_API void sqlite3_set_auxdata( + sqlite3_context *pCtx, + int iArg, + void *pAux, + void (*xDelete)(void*) +){ + AuxData *pAuxData; + Vdbe *pVdbe = pCtx->pVdbe; + + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); +#ifdef SQLITE_ENABLE_STAT4 + if( pVdbe==0 ) goto failed; +#else + assert( pVdbe!=0 ); +#endif + + for(pAuxData=pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNextAux){ + if( pAuxData->iAuxArg==iArg && (pAuxData->iAuxOp==pCtx->iOp || iArg<0) ){ + break; + } + } + if( pAuxData==0 ){ + pAuxData = sqlite3DbMallocZero(pVdbe->db, sizeof(AuxData)); + if( !pAuxData ) goto failed; + pAuxData->iAuxOp = pCtx->iOp; + pAuxData->iAuxArg = iArg; + pAuxData->pNextAux = pVdbe->pAuxData; + pVdbe->pAuxData = pAuxData; + if( pCtx->isError==0 ) pCtx->isError = -1; + }else if( pAuxData->xDeleteAux ){ + pAuxData->xDeleteAux(pAuxData->pAux); + } + + pAuxData->pAux = pAux; + pAuxData->xDeleteAux = xDelete; + return; + +failed: + if( xDelete ){ + xDelete(pAux); + } +} + +#ifndef SQLITE_OMIT_DEPRECATED +/* +** Return the number of times the Step function of an aggregate has been +** called. +** +** This function is deprecated. Do not use it for new code. It is +** provide only to avoid breaking legacy code. New aggregate function +** implementations should keep their own counts within their aggregate +** context. +*/ +SQLITE_API int sqlite3_aggregate_count(sqlite3_context *p){ + assert( p && p->pMem && p->pFunc && p->pFunc->xFinalize ); + return p->pMem->n; +} +#endif + +/* +** Return the number of columns in the result set for the statement pStmt. +*/ +SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt){ + Vdbe *pVm = (Vdbe *)pStmt; + if( pVm==0 ) return 0; + return pVm->nResColumn; +} + +/* +** Return the number of values available from the current row of the +** currently executing statement pStmt. +*/ +SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt){ + Vdbe *pVm = (Vdbe *)pStmt; + if( pVm==0 || pVm->pResultRow==0 ) return 0; + return pVm->nResColumn; +} + +/* +** Return a pointer to static memory containing an SQL NULL value. +*/ +static const Mem *columnNullValue(void){ + /* Even though the Mem structure contains an element + ** of type i64, on certain architectures (x86) with certain compiler + ** switches (-Os), gcc may align this Mem object on a 4-byte boundary + ** instead of an 8-byte one. This all works fine, except that when + ** running with SQLITE_DEBUG defined the SQLite code sometimes assert()s + ** that a Mem structure is located on an 8-byte boundary. To prevent + ** these assert()s from failing, when building with SQLITE_DEBUG defined + ** using gcc, we force nullMem to be 8-byte aligned using the magical + ** __attribute__((aligned(8))) macro. */ + static const Mem nullMem +#if defined(SQLITE_DEBUG) && defined(__GNUC__) + __attribute__((aligned(8))) +#endif + = { + /* .u = */ {0}, + /* .z = */ (char*)0, + /* .n = */ (int)0, + /* .flags = */ (u16)MEM_Null, + /* .enc = */ (u8)0, + /* .eSubtype = */ (u8)0, + /* .db = */ (sqlite3*)0, + /* .szMalloc = */ (int)0, + /* .uTemp = */ (u32)0, + /* .zMalloc = */ (char*)0, + /* .xDel = */ (void(*)(void*))0, +#ifdef SQLITE_DEBUG + /* .pScopyFrom = */ (Mem*)0, + /* .mScopyFlags= */ 0, +#endif + }; + return &nullMem; +} + +/* +** Check to see if column iCol of the given statement is valid. If +** it is, return a pointer to the Mem for the value of that column. +** If iCol is not valid, return a pointer to a Mem which has a value +** of NULL. +*/ +static Mem *columnMem(sqlite3_stmt *pStmt, int i){ + Vdbe *pVm; + Mem *pOut; + + pVm = (Vdbe *)pStmt; + if( pVm==0 ) return (Mem*)columnNullValue(); + assert( pVm->db ); + sqlite3_mutex_enter(pVm->db->mutex); + if( pVm->pResultRow!=0 && i<pVm->nResColumn && i>=0 ){ + pOut = &pVm->pResultRow[i]; + }else{ + sqlite3Error(pVm->db, SQLITE_RANGE); + pOut = (Mem*)columnNullValue(); + } + return pOut; +} + +/* +** This function is called after invoking an sqlite3_value_XXX function on a +** column value (i.e. a value returned by evaluating an SQL expression in the +** select list of a SELECT statement) that may cause a malloc() failure. If +** malloc() has failed, the threads mallocFailed flag is cleared and the result +** code of statement pStmt set to SQLITE_NOMEM. +** +** Specifically, this is called from within: +** +** sqlite3_column_int() +** sqlite3_column_int64() +** sqlite3_column_text() +** sqlite3_column_text16() +** sqlite3_column_real() +** sqlite3_column_bytes() +** sqlite3_column_bytes16() +** sqlite3_column_blob() +*/ +static void columnMallocFailure(sqlite3_stmt *pStmt) +{ + /* If malloc() failed during an encoding conversion within an + ** sqlite3_column_XXX API, then set the return code of the statement to + ** SQLITE_NOMEM. The next call to _step() (if any) will return SQLITE_ERROR + ** and _finalize() will return NOMEM. + */ + Vdbe *p = (Vdbe *)pStmt; + if( p ){ + assert( p->db!=0 ); + assert( sqlite3_mutex_held(p->db->mutex) ); + p->rc = sqlite3ApiExit(p->db, p->rc); + sqlite3_mutex_leave(p->db->mutex); + } +} + +/**************************** sqlite3_column_ ******************************* +** The following routines are used to access elements of the current row +** in the result set. +*/ +SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt *pStmt, int i){ + const void *val; + val = sqlite3_value_blob( columnMem(pStmt,i) ); + /* Even though there is no encoding conversion, value_blob() might + ** need to call malloc() to expand the result of a zeroblob() + ** expression. + */ + columnMallocFailure(pStmt); + return val; +} +SQLITE_API int sqlite3_column_bytes(sqlite3_stmt *pStmt, int i){ + int val = sqlite3_value_bytes( columnMem(pStmt,i) ); + columnMallocFailure(pStmt); + return val; +} +SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt *pStmt, int i){ + int val = sqlite3_value_bytes16( columnMem(pStmt,i) ); + columnMallocFailure(pStmt); + return val; +} +SQLITE_API double sqlite3_column_double(sqlite3_stmt *pStmt, int i){ + double val = sqlite3_value_double( columnMem(pStmt,i) ); + columnMallocFailure(pStmt); + return val; +} +SQLITE_API int sqlite3_column_int(sqlite3_stmt *pStmt, int i){ + int val = sqlite3_value_int( columnMem(pStmt,i) ); + columnMallocFailure(pStmt); + return val; +} +SQLITE_API sqlite_int64 sqlite3_column_int64(sqlite3_stmt *pStmt, int i){ + sqlite_int64 val = sqlite3_value_int64( columnMem(pStmt,i) ); + columnMallocFailure(pStmt); + return val; +} +SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt *pStmt, int i){ + const unsigned char *val = sqlite3_value_text( columnMem(pStmt,i) ); + columnMallocFailure(pStmt); + return val; +} +SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt *pStmt, int i){ + Mem *pOut = columnMem(pStmt, i); + if( pOut->flags&MEM_Static ){ + pOut->flags &= ~MEM_Static; + pOut->flags |= MEM_Ephem; + } + columnMallocFailure(pStmt); + return (sqlite3_value *)pOut; +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt *pStmt, int i){ + const void *val = sqlite3_value_text16( columnMem(pStmt,i) ); + columnMallocFailure(pStmt); + return val; +} +#endif /* SQLITE_OMIT_UTF16 */ +SQLITE_API int sqlite3_column_type(sqlite3_stmt *pStmt, int i){ + int iType = sqlite3_value_type( columnMem(pStmt,i) ); + columnMallocFailure(pStmt); + return iType; +} + +/* +** Column names appropriate for EXPLAIN or EXPLAIN QUERY PLAN. +*/ +static const char * const azExplainColNames8[] = { + "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", /* EXPLAIN */ + "id", "parent", "notused", "detail" /* EQP */ +}; +static const u16 azExplainColNames16data[] = { + /* 0 */ 'a', 'd', 'd', 'r', 0, + /* 5 */ 'o', 'p', 'c', 'o', 'd', 'e', 0, + /* 12 */ 'p', '1', 0, + /* 15 */ 'p', '2', 0, + /* 18 */ 'p', '3', 0, + /* 21 */ 'p', '4', 0, + /* 24 */ 'p', '5', 0, + /* 27 */ 'c', 'o', 'm', 'm', 'e', 'n', 't', 0, + /* 35 */ 'i', 'd', 0, + /* 38 */ 'p', 'a', 'r', 'e', 'n', 't', 0, + /* 45 */ 'n', 'o', 't', 'u', 's', 'e', 'd', 0, + /* 53 */ 'd', 'e', 't', 'a', 'i', 'l', 0 +}; +static const u8 iExplainColNames16[] = { + 0, 5, 12, 15, 18, 21, 24, 27, + 35, 38, 45, 53 +}; + +/* +** Convert the N-th element of pStmt->pColName[] into a string using +** xFunc() then return that string. If N is out of range, return 0. +** +** There are up to 5 names for each column. useType determines which +** name is returned. Here are the names: +** +** 0 The column name as it should be displayed for output +** 1 The datatype name for the column +** 2 The name of the database that the column derives from +** 3 The name of the table that the column derives from +** 4 The name of the table column that the result column derives from +** +** If the result is not a simple column reference (if it is an expression +** or a constant) then useTypes 2, 3, and 4 return NULL. +*/ +static const void *columnName( + sqlite3_stmt *pStmt, /* The statement */ + int N, /* Which column to get the name for */ + int useUtf16, /* True to return the name as UTF16 */ + int useType /* What type of name */ +){ + const void *ret; + Vdbe *p; + int n; + sqlite3 *db; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pStmt==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + if( N<0 ) return 0; + ret = 0; + p = (Vdbe *)pStmt; + db = p->db; + assert( db!=0 ); + sqlite3_mutex_enter(db->mutex); + + if( p->explain ){ + if( useType>0 ) goto columnName_end; + n = p->explain==1 ? 8 : 4; + if( N>=n ) goto columnName_end; + if( useUtf16 ){ + int i = iExplainColNames16[N + 8*p->explain - 8]; + ret = (void*)&azExplainColNames16data[i]; + }else{ + ret = (void*)azExplainColNames8[N + 8*p->explain - 8]; + } + goto columnName_end; + } + n = p->nResColumn; + if( N<n ){ + u8 prior_mallocFailed = db->mallocFailed; + N += useType*n; +#ifndef SQLITE_OMIT_UTF16 + if( useUtf16 ){ + ret = sqlite3_value_text16((sqlite3_value*)&p->aColName[N]); + }else +#endif + { + ret = sqlite3_value_text((sqlite3_value*)&p->aColName[N]); + } + /* A malloc may have failed inside of the _text() call. If this + ** is the case, clear the mallocFailed flag and return NULL. + */ + assert( db->mallocFailed==0 || db->mallocFailed==1 ); + if( db->mallocFailed > prior_mallocFailed ){ + sqlite3OomClear(db); + ret = 0; + } + } +columnName_end: + sqlite3_mutex_leave(db->mutex); + return ret; +} + +/* +** Return the name of the Nth column of the result set returned by SQL +** statement pStmt. +*/ +SQLITE_API const char *sqlite3_column_name(sqlite3_stmt *pStmt, int N){ + return columnName(pStmt, N, 0, COLNAME_NAME); +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt *pStmt, int N){ + return columnName(pStmt, N, 1, COLNAME_NAME); +} +#endif + +/* +** Constraint: If you have ENABLE_COLUMN_METADATA then you must +** not define OMIT_DECLTYPE. +*/ +#if defined(SQLITE_OMIT_DECLTYPE) && defined(SQLITE_ENABLE_COLUMN_METADATA) +# error "Must not define both SQLITE_OMIT_DECLTYPE \ + and SQLITE_ENABLE_COLUMN_METADATA" +#endif + +#ifndef SQLITE_OMIT_DECLTYPE +/* +** Return the column declaration type (if applicable) of the 'i'th column +** of the result set of SQL statement pStmt. +*/ +SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt *pStmt, int N){ + return columnName(pStmt, N, 0, COLNAME_DECLTYPE); +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt *pStmt, int N){ + return columnName(pStmt, N, 1, COLNAME_DECLTYPE); +} +#endif /* SQLITE_OMIT_UTF16 */ +#endif /* SQLITE_OMIT_DECLTYPE */ + +#ifdef SQLITE_ENABLE_COLUMN_METADATA +/* +** Return the name of the database from which a result column derives. +** NULL is returned if the result column is an expression or constant or +** anything else which is not an unambiguous reference to a database column. +*/ +SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt *pStmt, int N){ + return columnName(pStmt, N, 0, COLNAME_DATABASE); +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt *pStmt, int N){ + return columnName(pStmt, N, 1, COLNAME_DATABASE); +} +#endif /* SQLITE_OMIT_UTF16 */ + +/* +** Return the name of the table from which a result column derives. +** NULL is returned if the result column is an expression or constant or +** anything else which is not an unambiguous reference to a database column. +*/ +SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt *pStmt, int N){ + return columnName(pStmt, N, 0, COLNAME_TABLE); +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt *pStmt, int N){ + return columnName(pStmt, N, 1, COLNAME_TABLE); +} +#endif /* SQLITE_OMIT_UTF16 */ + +/* +** Return the name of the table column from which a result column derives. +** NULL is returned if the result column is an expression or constant or +** anything else which is not an unambiguous reference to a database column. +*/ +SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt *pStmt, int N){ + return columnName(pStmt, N, 0, COLNAME_COLUMN); +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){ + return columnName(pStmt, N, 1, COLNAME_COLUMN); +} +#endif /* SQLITE_OMIT_UTF16 */ +#endif /* SQLITE_ENABLE_COLUMN_METADATA */ + + +/******************************* sqlite3_bind_ *************************** +** +** Routines used to attach values to wildcards in a compiled SQL statement. +*/ +/* +** Unbind the value bound to variable i in virtual machine p. This is the +** the same as binding a NULL value to the column. If the "i" parameter is +** out of range, then SQLITE_RANGE is returned. Otherwise SQLITE_OK. +** +** A successful evaluation of this routine acquires the mutex on p. +** the mutex is released if any kind of error occurs. +** +** The error code stored in database p->db is overwritten with the return +** value in any case. +*/ +static int vdbeUnbind(Vdbe *p, unsigned int i){ + Mem *pVar; + if( vdbeSafetyNotNull(p) ){ + return SQLITE_MISUSE_BKPT; + } + sqlite3_mutex_enter(p->db->mutex); + if( p->eVdbeState!=VDBE_READY_STATE ){ + sqlite3Error(p->db, SQLITE_MISUSE_BKPT); + sqlite3_mutex_leave(p->db->mutex); + sqlite3_log(SQLITE_MISUSE, + "bind on a busy prepared statement: [%s]", p->zSql); + return SQLITE_MISUSE_BKPT; + } + if( i>=(unsigned int)p->nVar ){ + sqlite3Error(p->db, SQLITE_RANGE); + sqlite3_mutex_leave(p->db->mutex); + return SQLITE_RANGE; + } + pVar = &p->aVar[i]; + sqlite3VdbeMemRelease(pVar); + pVar->flags = MEM_Null; + p->db->errCode = SQLITE_OK; + + /* If the bit corresponding to this variable in Vdbe.expmask is set, then + ** binding a new value to this variable invalidates the current query plan. + ** + ** IMPLEMENTATION-OF: R-57496-20354 If the specific value bound to a host + ** parameter in the WHERE clause might influence the choice of query plan + ** for a statement, then the statement will be automatically recompiled, + ** as if there had been a schema change, on the first sqlite3_step() call + ** following any change to the bindings of that parameter. + */ + assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || p->expmask==0 ); + if( p->expmask!=0 && (p->expmask & (i>=31 ? 0x80000000 : (u32)1<<i))!=0 ){ + p->expired = 1; + } + return SQLITE_OK; +} + +/* +** Bind a text or BLOB value. +*/ +static int bindText( + sqlite3_stmt *pStmt, /* The statement to bind against */ + int i, /* Index of the parameter to bind */ + const void *zData, /* Pointer to the data to be bound */ + i64 nData, /* Number of bytes of data to be bound */ + void (*xDel)(void*), /* Destructor for the data */ + u8 encoding /* Encoding for the data */ +){ + Vdbe *p = (Vdbe *)pStmt; + Mem *pVar; + int rc; + + rc = vdbeUnbind(p, (u32)(i-1)); + if( rc==SQLITE_OK ){ + if( zData!=0 ){ + pVar = &p->aVar[i-1]; + rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); + if( rc==SQLITE_OK && encoding!=0 ){ + rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); + } + if( rc ){ + sqlite3Error(p->db, rc); + rc = sqlite3ApiExit(p->db, rc); + } + } + sqlite3_mutex_leave(p->db->mutex); + }else if( xDel!=SQLITE_STATIC && xDel!=SQLITE_TRANSIENT ){ + xDel((void*)zData); + } + return rc; +} + + +/* +** Bind a blob value to an SQL statement variable. +*/ +SQLITE_API int sqlite3_bind_blob( + sqlite3_stmt *pStmt, + int i, + const void *zData, + int nData, + void (*xDel)(void*) +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( nData<0 ) return SQLITE_MISUSE_BKPT; +#endif + return bindText(pStmt, i, zData, nData, xDel, 0); +} +SQLITE_API int sqlite3_bind_blob64( + sqlite3_stmt *pStmt, + int i, + const void *zData, + sqlite3_uint64 nData, + void (*xDel)(void*) +){ + assert( xDel!=SQLITE_DYNAMIC ); + return bindText(pStmt, i, zData, nData, xDel, 0); +} +SQLITE_API int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){ + int rc; + Vdbe *p = (Vdbe *)pStmt; + rc = vdbeUnbind(p, (u32)(i-1)); + if( rc==SQLITE_OK ){ + sqlite3VdbeMemSetDouble(&p->aVar[i-1], rValue); + sqlite3_mutex_leave(p->db->mutex); + } + return rc; +} +SQLITE_API int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){ + return sqlite3_bind_int64(p, i, (i64)iValue); +} +SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){ + int rc; + Vdbe *p = (Vdbe *)pStmt; + rc = vdbeUnbind(p, (u32)(i-1)); + if( rc==SQLITE_OK ){ + sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue); + sqlite3_mutex_leave(p->db->mutex); + } + return rc; +} +SQLITE_API int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){ + int rc; + Vdbe *p = (Vdbe*)pStmt; + rc = vdbeUnbind(p, (u32)(i-1)); + if( rc==SQLITE_OK ){ + sqlite3_mutex_leave(p->db->mutex); + } + return rc; +} +SQLITE_API int sqlite3_bind_pointer( + sqlite3_stmt *pStmt, + int i, + void *pPtr, + const char *zPTtype, + void (*xDestructor)(void*) +){ + int rc; + Vdbe *p = (Vdbe*)pStmt; + rc = vdbeUnbind(p, (u32)(i-1)); + if( rc==SQLITE_OK ){ + sqlite3VdbeMemSetPointer(&p->aVar[i-1], pPtr, zPTtype, xDestructor); + sqlite3_mutex_leave(p->db->mutex); + }else if( xDestructor ){ + xDestructor(pPtr); + } + return rc; +} +SQLITE_API int sqlite3_bind_text( + sqlite3_stmt *pStmt, + int i, + const char *zData, + int nData, + void (*xDel)(void*) +){ + return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF8); +} +SQLITE_API int sqlite3_bind_text64( + sqlite3_stmt *pStmt, + int i, + const char *zData, + sqlite3_uint64 nData, + void (*xDel)(void*), + unsigned char enc +){ + assert( xDel!=SQLITE_DYNAMIC ); + if( enc!=SQLITE_UTF8 ){ + if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + nData &= ~(u16)1; + } + return bindText(pStmt, i, zData, nData, xDel, enc); +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API int sqlite3_bind_text16( + sqlite3_stmt *pStmt, + int i, + const void *zData, + int n, + void (*xDel)(void*) +){ + return bindText(pStmt, i, zData, n & ~(u64)1, xDel, SQLITE_UTF16NATIVE); +} +#endif /* SQLITE_OMIT_UTF16 */ +SQLITE_API int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ + int rc; + switch( sqlite3_value_type((sqlite3_value*)pValue) ){ + case SQLITE_INTEGER: { + rc = sqlite3_bind_int64(pStmt, i, pValue->u.i); + break; + } + case SQLITE_FLOAT: { + assert( pValue->flags & (MEM_Real|MEM_IntReal) ); + rc = sqlite3_bind_double(pStmt, i, + (pValue->flags & MEM_Real) ? pValue->u.r : (double)pValue->u.i + ); + break; + } + case SQLITE_BLOB: { + if( pValue->flags & MEM_Zero ){ + rc = sqlite3_bind_zeroblob(pStmt, i, pValue->u.nZero); + }else{ + rc = sqlite3_bind_blob(pStmt, i, pValue->z, pValue->n,SQLITE_TRANSIENT); + } + break; + } + case SQLITE_TEXT: { + rc = bindText(pStmt,i, pValue->z, pValue->n, SQLITE_TRANSIENT, + pValue->enc); + break; + } + default: { + rc = sqlite3_bind_null(pStmt, i); + break; + } + } + return rc; +} +SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){ + int rc; + Vdbe *p = (Vdbe *)pStmt; + rc = vdbeUnbind(p, (u32)(i-1)); + if( rc==SQLITE_OK ){ +#ifndef SQLITE_OMIT_INCRBLOB + sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); +#else + rc = sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); +#endif + sqlite3_mutex_leave(p->db->mutex); + } + return rc; +} +SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt *pStmt, int i, sqlite3_uint64 n){ + int rc; + Vdbe *p = (Vdbe *)pStmt; + sqlite3_mutex_enter(p->db->mutex); + if( n>(u64)p->db->aLimit[SQLITE_LIMIT_LENGTH] ){ + rc = SQLITE_TOOBIG; + }else{ + assert( (n & 0x7FFFFFFF)==n ); + rc = sqlite3_bind_zeroblob(pStmt, i, n); + } + rc = sqlite3ApiExit(p->db, rc); + sqlite3_mutex_leave(p->db->mutex); + return rc; +} + +/* +** Return the number of wildcards that can be potentially bound to. +** This routine is added to support DBD::SQLite. +*/ +SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){ + Vdbe *p = (Vdbe*)pStmt; + return p ? p->nVar : 0; +} + +/* +** Return the name of a wildcard parameter. Return NULL if the index +** is out of range or if the wildcard is unnamed. +** +** The result is always UTF-8. +*/ +SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){ + Vdbe *p = (Vdbe*)pStmt; + if( p==0 ) return 0; + return sqlite3VListNumToName(p->pVList, i); +} + +/* +** Given a wildcard parameter name, return the index of the variable +** with that name. If there is no variable with the given name, +** return 0. +*/ +SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe *p, const char *zName, int nName){ + if( p==0 || zName==0 ) return 0; + return sqlite3VListNameToNum(p->pVList, zName, nName); +} +SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName){ + return sqlite3VdbeParameterIndex((Vdbe*)pStmt, zName, sqlite3Strlen30(zName)); +} + +/* +** Transfer all bindings from the first statement over to the second. +*/ +SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *pFromStmt, sqlite3_stmt *pToStmt){ + Vdbe *pFrom = (Vdbe*)pFromStmt; + Vdbe *pTo = (Vdbe*)pToStmt; + int i; + assert( pTo->db==pFrom->db ); + assert( pTo->nVar==pFrom->nVar ); + sqlite3_mutex_enter(pTo->db->mutex); + for(i=0; i<pFrom->nVar; i++){ + sqlite3VdbeMemMove(&pTo->aVar[i], &pFrom->aVar[i]); + } + sqlite3_mutex_leave(pTo->db->mutex); + return SQLITE_OK; +} + +#ifndef SQLITE_OMIT_DEPRECATED +/* +** Deprecated external interface. Internal/core SQLite code +** should call sqlite3TransferBindings. +** +** It is misuse to call this routine with statements from different +** database connections. But as this is a deprecated interface, we +** will not bother to check for that condition. +** +** If the two statements contain a different number of bindings, then +** an SQLITE_ERROR is returned. Nothing else can go wrong, so otherwise +** SQLITE_OK is returned. +*/ +SQLITE_API int sqlite3_transfer_bindings(sqlite3_stmt *pFromStmt, sqlite3_stmt *pToStmt){ + Vdbe *pFrom = (Vdbe*)pFromStmt; + Vdbe *pTo = (Vdbe*)pToStmt; + if( pFrom->nVar!=pTo->nVar ){ + return SQLITE_ERROR; + } + assert( (pTo->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || pTo->expmask==0 ); + if( pTo->expmask ){ + pTo->expired = 1; + } + assert( (pFrom->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || pFrom->expmask==0 ); + if( pFrom->expmask ){ + pFrom->expired = 1; + } + return sqlite3TransferBindings(pFromStmt, pToStmt); +} +#endif + +/* +** Return the sqlite3* database handle to which the prepared statement given +** in the argument belongs. This is the same database handle that was +** the first argument to the sqlite3_prepare() that was used to create +** the statement in the first place. +*/ +SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt *pStmt){ + return pStmt ? ((Vdbe*)pStmt)->db : 0; +} + +/* +** Return true if the prepared statement is guaranteed to not modify the +** database. +*/ +SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt){ + return pStmt ? ((Vdbe*)pStmt)->readOnly : 1; +} + +/* +** Return 1 if the statement is an EXPLAIN and return 2 if the +** statement is an EXPLAIN QUERY PLAN +*/ +SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ + return pStmt ? ((Vdbe*)pStmt)->explain : 0; +} + +/* +** Set the explain mode for a statement. +*/ +SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode){ + Vdbe *v = (Vdbe*)pStmt; + int rc; + sqlite3_mutex_enter(v->db->mutex); + if( ((int)v->explain)==eMode ){ + rc = SQLITE_OK; + }else if( eMode<0 || eMode>2 ){ + rc = SQLITE_ERROR; + }else if( (v->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ){ + rc = SQLITE_ERROR; + }else if( v->eVdbeState!=VDBE_READY_STATE ){ + rc = SQLITE_BUSY; + }else if( v->nMem>=10 && (eMode!=2 || v->haveEqpOps) ){ + /* No reprepare necessary */ + v->explain = eMode; + rc = SQLITE_OK; + }else{ + v->explain = eMode; + rc = sqlite3Reprepare(v); + v->haveEqpOps = eMode==2; + } + if( v->explain ){ + v->nResColumn = 12 - 4*v->explain; + }else{ + v->nResColumn = v->nResAlloc; + } + sqlite3_mutex_leave(v->db->mutex); + return rc; +} + +/* +** Return true if the prepared statement is in need of being reset. +*/ +SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ + Vdbe *v = (Vdbe*)pStmt; + return v!=0 && v->eVdbeState==VDBE_RUN_STATE; +} + +/* +** Return a pointer to the next prepared statement after pStmt associated +** with database connection pDb. If pStmt is NULL, return the first +** prepared statement for the database connection. Return NULL if there +** are no more. +*/ +SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){ + sqlite3_stmt *pNext; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(pDb) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + sqlite3_mutex_enter(pDb->mutex); + if( pStmt==0 ){ + pNext = (sqlite3_stmt*)pDb->pVdbe; + }else{ + pNext = (sqlite3_stmt*)((Vdbe*)pStmt)->pVNext; + } + sqlite3_mutex_leave(pDb->mutex); + return pNext; +} + +/* +** Return the value of a status counter for a prepared statement +*/ +SQLITE_API int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ + Vdbe *pVdbe = (Vdbe*)pStmt; + u32 v; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !pStmt + || (op!=SQLITE_STMTSTATUS_MEMUSED && (op<0||op>=ArraySize(pVdbe->aCounter))) + ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + if( op==SQLITE_STMTSTATUS_MEMUSED ){ + sqlite3 *db = pVdbe->db; + sqlite3_mutex_enter(db->mutex); + v = 0; + db->pnBytesFreed = (int*)&v; + assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); + db->lookaside.pEnd = db->lookaside.pStart; + sqlite3VdbeDelete(pVdbe); + db->pnBytesFreed = 0; + db->lookaside.pEnd = db->lookaside.pTrueEnd; + sqlite3_mutex_leave(db->mutex); + }else{ + v = pVdbe->aCounter[op]; + if( resetFlag ) pVdbe->aCounter[op] = 0; + } + return (int)v; +} + +/* +** Return the SQL associated with a prepared statement +*/ +SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt){ + Vdbe *p = (Vdbe *)pStmt; + return p ? p->zSql : 0; +} + +/* +** Return the SQL associated with a prepared statement with +** bound parameters expanded. Space to hold the returned string is +** obtained from sqlite3_malloc(). The caller is responsible for +** freeing the returned string by passing it to sqlite3_free(). +** +** The SQLITE_TRACE_SIZE_LIMIT puts an upper bound on the size of +** expanded bound parameters. +*/ +SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt){ +#ifdef SQLITE_OMIT_TRACE + return 0; +#else + char *z = 0; + const char *zSql = sqlite3_sql(pStmt); + if( zSql ){ + Vdbe *p = (Vdbe *)pStmt; + sqlite3_mutex_enter(p->db->mutex); + z = sqlite3VdbeExpandSql(p, zSql); + sqlite3_mutex_leave(p->db->mutex); + } + return z; +#endif +} + +#ifdef SQLITE_ENABLE_NORMALIZE +/* +** Return the normalized SQL associated with a prepared statement. +*/ +SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt){ + Vdbe *p = (Vdbe *)pStmt; + if( p==0 ) return 0; + if( p->zNormSql==0 && ALWAYS(p->zSql!=0) ){ + sqlite3_mutex_enter(p->db->mutex); + p->zNormSql = sqlite3Normalize(p, p->zSql); + sqlite3_mutex_leave(p->db->mutex); + } + return p->zNormSql; +} +#endif /* SQLITE_ENABLE_NORMALIZE */ + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** Allocate and populate an UnpackedRecord structure based on the serialized +** record in nKey/pKey. Return a pointer to the new UnpackedRecord structure +** if successful, or a NULL pointer if an OOM error is encountered. +*/ +static UnpackedRecord *vdbeUnpackRecord( + KeyInfo *pKeyInfo, + int nKey, + const void *pKey +){ + UnpackedRecord *pRet; /* Return value */ + + pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); + if( pRet ){ + memset(pRet->aMem, 0, sizeof(Mem)*(pKeyInfo->nKeyField+1)); + sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet); + } + return pRet; +} + +/* +** This function is called from within a pre-update callback to retrieve +** a field of the row currently being updated or deleted. +*/ +SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ + PreUpdate *p = db->pPreUpdate; + Mem *pMem; + int rc = SQLITE_OK; + + /* Test that this call is being made from within an SQLITE_DELETE or + ** SQLITE_UPDATE pre-update callback, and that iIdx is within range. */ + if( !p || p->op==SQLITE_INSERT ){ + rc = SQLITE_MISUSE_BKPT; + goto preupdate_old_out; + } + if( p->pPk ){ + iIdx = sqlite3TableColumnToIndex(p->pPk, iIdx); + } + if( iIdx>=p->pCsr->nField || iIdx<0 ){ + rc = SQLITE_RANGE; + goto preupdate_old_out; + } + + /* If the old.* record has not yet been loaded into memory, do so now. */ + if( p->pUnpacked==0 ){ + u32 nRec; + u8 *aRec; + + assert( p->pCsr->eCurType==CURTYPE_BTREE ); + nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); + aRec = sqlite3DbMallocRaw(db, nRec); + if( !aRec ) goto preupdate_old_out; + rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec); + if( rc==SQLITE_OK ){ + p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec); + if( !p->pUnpacked ) rc = SQLITE_NOMEM; + } + if( rc!=SQLITE_OK ){ + sqlite3DbFree(db, aRec); + goto preupdate_old_out; + } + p->aRecord = aRec; + } + + pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; + if( iIdx==p->pTab->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey1); + }else if( iIdx>=p->pUnpacked->nField ){ + *ppValue = (sqlite3_value *)columnNullValue(); + }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ + if( pMem->flags & (MEM_Int|MEM_IntReal) ){ + testcase( pMem->flags & MEM_Int ); + testcase( pMem->flags & MEM_IntReal ); + sqlite3VdbeMemRealify(pMem); + } + } + + preupdate_old_out: + sqlite3Error(db, rc); + return sqlite3ApiExit(db, rc); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** This function is called from within a pre-update callback to retrieve +** the number of columns in the row being updated, deleted or inserted. +*/ +SQLITE_API int sqlite3_preupdate_count(sqlite3 *db){ + PreUpdate *p = db->pPreUpdate; + return (p ? p->keyinfo.nKeyField : 0); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** This function is designed to be called from within a pre-update callback +** only. It returns zero if the change that caused the callback was made +** immediately by a user SQL statement. Or, if the change was made by a +** trigger program, it returns the number of trigger programs currently +** on the stack (1 for a top-level trigger, 2 for a trigger fired by a +** top-level trigger etc.). +** +** For the purposes of the previous paragraph, a foreign key CASCADE, SET NULL +** or SET DEFAULT action is considered a trigger. +*/ +SQLITE_API int sqlite3_preupdate_depth(sqlite3 *db){ + PreUpdate *p = db->pPreUpdate; + return (p ? p->v->nFrame : 0); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** This function is designed to be called from within a pre-update callback +** only. +*/ +SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *db){ + PreUpdate *p = db->pPreUpdate; + return (p ? p->iBlobWrite : -1); +} +#endif + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** This function is called from within a pre-update callback to retrieve +** a field of the row currently being updated or inserted. +*/ +SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ + PreUpdate *p = db->pPreUpdate; + int rc = SQLITE_OK; + Mem *pMem; + + if( !p || p->op==SQLITE_DELETE ){ + rc = SQLITE_MISUSE_BKPT; + goto preupdate_new_out; + } + if( p->pPk && p->op!=SQLITE_UPDATE ){ + iIdx = sqlite3TableColumnToIndex(p->pPk, iIdx); + } + if( iIdx>=p->pCsr->nField || iIdx<0 ){ + rc = SQLITE_RANGE; + goto preupdate_new_out; + } + + if( p->op==SQLITE_INSERT ){ + /* For an INSERT, memory cell p->iNewReg contains the serialized record + ** that is being inserted. Deserialize it. */ + UnpackedRecord *pUnpack = p->pNewUnpacked; + if( !pUnpack ){ + Mem *pData = &p->v->aMem[p->iNewReg]; + rc = ExpandBlob(pData); + if( rc!=SQLITE_OK ) goto preupdate_new_out; + pUnpack = vdbeUnpackRecord(&p->keyinfo, pData->n, pData->z); + if( !pUnpack ){ + rc = SQLITE_NOMEM; + goto preupdate_new_out; + } + p->pNewUnpacked = pUnpack; + } + pMem = &pUnpack->aMem[iIdx]; + if( iIdx==p->pTab->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey2); + }else if( iIdx>=pUnpack->nField ){ + pMem = (sqlite3_value *)columnNullValue(); + } + }else{ + /* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required + ** value. Make a copy of the cell contents and return a pointer to it. + ** It is not safe to return a pointer to the memory cell itself as the + ** caller may modify the value text encoding. + */ + assert( p->op==SQLITE_UPDATE ); + if( !p->aNew ){ + p->aNew = (Mem *)sqlite3DbMallocZero(db, sizeof(Mem) * p->pCsr->nField); + if( !p->aNew ){ + rc = SQLITE_NOMEM; + goto preupdate_new_out; + } + } + assert( iIdx>=0 && iIdx<p->pCsr->nField ); + pMem = &p->aNew[iIdx]; + if( pMem->flags==0 ){ + if( iIdx==p->pTab->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey2); + }else{ + rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]); + if( rc!=SQLITE_OK ) goto preupdate_new_out; + } + } + } + *ppValue = pMem; + + preupdate_new_out: + sqlite3Error(db, rc); + return sqlite3ApiExit(db, rc); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +/* +** Return status data for a single loop within query pStmt. +*/ +SQLITE_API int sqlite3_stmt_scanstatus_v2( + sqlite3_stmt *pStmt, /* Prepared statement being queried */ + int iScan, /* Index of loop to report on */ + int iScanStatusOp, /* Which metric to return */ + int flags, + void *pOut /* OUT: Write the answer here */ +){ + Vdbe *p = (Vdbe*)pStmt; + VdbeOp *aOp = p->aOp; + int nOp = p->nOp; + ScanStatus *pScan = 0; + int idx; + + if( p->pFrame ){ + VdbeFrame *pFrame; + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + aOp = pFrame->aOp; + nOp = pFrame->nOp; + } + + if( iScan<0 ){ + int ii; + if( iScanStatusOp==SQLITE_SCANSTAT_NCYCLE ){ + i64 res = 0; + for(ii=0; ii<nOp; ii++){ + res += aOp[ii].nCycle; + } + *(i64*)pOut = res; + return 0; + } + return 1; + } + if( flags & SQLITE_SCANSTAT_COMPLEX ){ + idx = iScan; + pScan = &p->aScan[idx]; + }else{ + /* If the COMPLEX flag is clear, then this function must ignore any + ** ScanStatus structures with ScanStatus.addrLoop set to 0. */ + for(idx=0; idx<p->nScan; idx++){ + pScan = &p->aScan[idx]; + if( pScan->zName ){ + iScan--; + if( iScan<0 ) break; + } + } + } + if( idx>=p->nScan ) return 1; + + switch( iScanStatusOp ){ + case SQLITE_SCANSTAT_NLOOP: { + if( pScan->addrLoop>0 ){ + *(sqlite3_int64*)pOut = aOp[pScan->addrLoop].nExec; + }else{ + *(sqlite3_int64*)pOut = -1; + } + break; + } + case SQLITE_SCANSTAT_NVISIT: { + if( pScan->addrVisit>0 ){ + *(sqlite3_int64*)pOut = aOp[pScan->addrVisit].nExec; + }else{ + *(sqlite3_int64*)pOut = -1; + } + break; + } + case SQLITE_SCANSTAT_EST: { + double r = 1.0; + LogEst x = pScan->nEst; + while( x<100 ){ + x += 10; + r *= 0.5; + } + *(double*)pOut = r*sqlite3LogEstToInt(x); + break; + } + case SQLITE_SCANSTAT_NAME: { + *(const char**)pOut = pScan->zName; + break; + } + case SQLITE_SCANSTAT_EXPLAIN: { + if( pScan->addrExplain ){ + *(const char**)pOut = aOp[ pScan->addrExplain ].p4.z; + }else{ + *(const char**)pOut = 0; + } + break; + } + case SQLITE_SCANSTAT_SELECTID: { + if( pScan->addrExplain ){ + *(int*)pOut = aOp[ pScan->addrExplain ].p1; + }else{ + *(int*)pOut = -1; + } + break; + } + case SQLITE_SCANSTAT_PARENTID: { + if( pScan->addrExplain ){ + *(int*)pOut = aOp[ pScan->addrExplain ].p2; + }else{ + *(int*)pOut = -1; + } + break; + } + case SQLITE_SCANSTAT_NCYCLE: { + i64 res = 0; + if( pScan->aAddrRange[0]==0 ){ + res = -1; + }else{ + int ii; + for(ii=0; ii<ArraySize(pScan->aAddrRange); ii+=2){ + int iIns = pScan->aAddrRange[ii]; + int iEnd = pScan->aAddrRange[ii+1]; + if( iIns==0 ) break; + if( iIns>0 ){ + while( iIns<=iEnd ){ + res += aOp[iIns].nCycle; + iIns++; + } + }else{ + int iOp; + for(iOp=0; iOp<nOp; iOp++){ + Op *pOp = &aOp[iOp]; + if( pOp->p1!=iEnd ) continue; + if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_NCYCLE)==0 ){ + continue; + } + res += aOp[iOp].nCycle; + } + } + } + } + *(i64*)pOut = res; + break; + } + default: { + return 1; + } + } + return 0; +} + +/* +** Return status data for a single loop within query pStmt. +*/ +SQLITE_API int sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement being queried */ + int iScan, /* Index of loop to report on */ + int iScanStatusOp, /* Which metric to return */ + void *pOut /* OUT: Write the answer here */ +){ + return sqlite3_stmt_scanstatus_v2(pStmt, iScan, iScanStatusOp, 0, pOut); +} + +/* +** Zero all counters associated with the sqlite3_stmt_scanstatus() data. +*/ +SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){ + Vdbe *p = (Vdbe*)pStmt; + int ii; + for(ii=0; ii<p->nOp; ii++){ + Op *pOp = &p->aOp[ii]; + pOp->nExec = 0; + pOp->nCycle = 0; + } +} +#endif /* SQLITE_ENABLE_STMT_SCANSTATUS */ + +/************** End of vdbeapi.c *********************************************/ +/************** Begin file vdbetrace.c ***************************************/ +/* +** 2009 November 25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains code used to insert the values of host parameters +** (aka "wildcards") into the SQL text output by sqlite3_trace(). +** +** The Vdbe parse-tree explainer is also found here. +*/ +/* #include "sqliteInt.h" */ +/* #include "vdbeInt.h" */ + +#ifndef SQLITE_OMIT_TRACE + +/* +** zSql is a zero-terminated string of UTF-8 SQL text. Return the number of +** bytes in this text up to but excluding the first character in +** a host parameter. If the text contains no host parameters, return +** the total number of bytes in the text. +*/ +static int findNextHostParameter(const char *zSql, int *pnToken){ + int tokenType; + int nTotal = 0; + int n; + + *pnToken = 0; + while( zSql[0] ){ + n = sqlite3GetToken((u8*)zSql, &tokenType); + assert( n>0 && tokenType!=TK_ILLEGAL ); + if( tokenType==TK_VARIABLE ){ + *pnToken = n; + break; + } + nTotal += n; + zSql += n; + } + return nTotal; +} + +/* +** This function returns a pointer to a nul-terminated string in memory +** obtained from sqlite3DbMalloc(). If sqlite3.nVdbeExec is 1, then the +** string contains a copy of zRawSql but with host parameters expanded to +** their current bindings. Or, if sqlite3.nVdbeExec is greater than 1, +** then the returned string holds a copy of zRawSql with "-- " prepended +** to each line of text. +** +** If the SQLITE_TRACE_SIZE_LIMIT macro is defined to an integer, then +** then long strings and blobs are truncated to that many bytes. This +** can be used to prevent unreasonably large trace strings when dealing +** with large (multi-megabyte) strings and blobs. +** +** The calling function is responsible for making sure the memory returned +** is eventually freed. +** +** ALGORITHM: Scan the input string looking for host parameters in any of +** these forms: ?, ?N, $A, @A, :A. Take care to avoid text within +** string literals, quoted identifier names, and comments. For text forms, +** the host parameter index is found by scanning the prepared +** statement for the corresponding OP_Variable opcode. Once the host +** parameter index is known, locate the value in p->aVar[]. Then render +** the value as a literal in place of the host parameter name. +*/ +SQLITE_PRIVATE char *sqlite3VdbeExpandSql( + Vdbe *p, /* The prepared statement being evaluated */ + const char *zRawSql /* Raw text of the SQL statement */ +){ + sqlite3 *db; /* The database connection */ + int idx = 0; /* Index of a host parameter */ + int nextIndex = 1; /* Index of next ? host parameter */ + int n; /* Length of a token prefix */ + int nToken; /* Length of the parameter token */ + int i; /* Loop counter */ + Mem *pVar; /* Value of a host parameter */ + StrAccum out; /* Accumulate the output here */ +#ifndef SQLITE_OMIT_UTF16 + Mem utf8; /* Used to convert UTF16 into UTF8 for display */ +#endif + + db = p->db; + sqlite3StrAccumInit(&out, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); + if( db->nVdbeExec>1 ){ + while( *zRawSql ){ + const char *zStart = zRawSql; + while( *(zRawSql++)!='\n' && *zRawSql ); + sqlite3_str_append(&out, "-- ", 3); + assert( (zRawSql - zStart) > 0 ); + sqlite3_str_append(&out, zStart, (int)(zRawSql-zStart)); + } + }else if( p->nVar==0 ){ + sqlite3_str_append(&out, zRawSql, sqlite3Strlen30(zRawSql)); + }else{ + while( zRawSql[0] ){ + n = findNextHostParameter(zRawSql, &nToken); + assert( n>0 ); + sqlite3_str_append(&out, zRawSql, n); + zRawSql += n; + assert( zRawSql[0] || nToken==0 ); + if( nToken==0 ) break; + if( zRawSql[0]=='?' ){ + if( nToken>1 ){ + assert( sqlite3Isdigit(zRawSql[1]) ); + sqlite3GetInt32(&zRawSql[1], &idx); + }else{ + idx = nextIndex; + } + }else{ + assert( zRawSql[0]==':' || zRawSql[0]=='$' || + zRawSql[0]=='@' || zRawSql[0]=='#' ); + testcase( zRawSql[0]==':' ); + testcase( zRawSql[0]=='$' ); + testcase( zRawSql[0]=='@' ); + testcase( zRawSql[0]=='#' ); + idx = sqlite3VdbeParameterIndex(p, zRawSql, nToken); + assert( idx>0 ); + } + zRawSql += nToken; + nextIndex = MAX(idx + 1, nextIndex); + assert( idx>0 && idx<=p->nVar ); + pVar = &p->aVar[idx-1]; + if( pVar->flags & MEM_Null ){ + sqlite3_str_append(&out, "NULL", 4); + }else if( pVar->flags & (MEM_Int|MEM_IntReal) ){ + sqlite3_str_appendf(&out, "%lld", pVar->u.i); + }else if( pVar->flags & MEM_Real ){ + sqlite3_str_appendf(&out, "%!.15g", pVar->u.r); + }else if( pVar->flags & MEM_Str ){ + int nOut; /* Number of bytes of the string text to include in output */ +#ifndef SQLITE_OMIT_UTF16 + u8 enc = ENC(db); + if( enc!=SQLITE_UTF8 ){ + memset(&utf8, 0, sizeof(utf8)); + utf8.db = db; + sqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, enc, SQLITE_STATIC); + if( SQLITE_NOMEM==sqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8) ){ + out.accError = SQLITE_NOMEM; + out.nAlloc = 0; + } + pVar = &utf8; + } +#endif + nOut = pVar->n; +#ifdef SQLITE_TRACE_SIZE_LIMIT + if( nOut>SQLITE_TRACE_SIZE_LIMIT ){ + nOut = SQLITE_TRACE_SIZE_LIMIT; + while( nOut<pVar->n && (pVar->z[nOut]&0xc0)==0x80 ){ nOut++; } + } +#endif + sqlite3_str_appendf(&out, "'%.*q'", nOut, pVar->z); +#ifdef SQLITE_TRACE_SIZE_LIMIT + if( nOut<pVar->n ){ + sqlite3_str_appendf(&out, "/*+%d bytes*/", pVar->n-nOut); + } +#endif +#ifndef SQLITE_OMIT_UTF16 + if( enc!=SQLITE_UTF8 ) sqlite3VdbeMemRelease(&utf8); +#endif + }else if( pVar->flags & MEM_Zero ){ + sqlite3_str_appendf(&out, "zeroblob(%d)", pVar->u.nZero); + }else{ + int nOut; /* Number of bytes of the blob to include in output */ + assert( pVar->flags & MEM_Blob ); + sqlite3_str_append(&out, "x'", 2); + nOut = pVar->n; +#ifdef SQLITE_TRACE_SIZE_LIMIT + if( nOut>SQLITE_TRACE_SIZE_LIMIT ) nOut = SQLITE_TRACE_SIZE_LIMIT; +#endif + for(i=0; i<nOut; i++){ + sqlite3_str_appendf(&out, "%02x", pVar->z[i]&0xff); + } + sqlite3_str_append(&out, "'", 1); +#ifdef SQLITE_TRACE_SIZE_LIMIT + if( nOut<pVar->n ){ + sqlite3_str_appendf(&out, "/*+%d bytes*/", pVar->n-nOut); + } +#endif + } + } + } + if( out.accError ) sqlite3_str_reset(&out); + return sqlite3StrAccumFinish(&out); +} + +#endif /* #ifndef SQLITE_OMIT_TRACE */ + +/************** End of vdbetrace.c *******************************************/ +/************** Begin file vdbe.c ********************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** The code in this file implements the function that runs the +** bytecode of a prepared statement. +** +** Various scripts scan this source file in order to generate HTML +** documentation, headers files, or other derived files. The formatting +** of the code in this file is, therefore, important. See other comments +** in this file for details. If in doubt, do not deviate from existing +** commenting and indentation practices when changing or adding code. +*/ +/* #include "sqliteInt.h" */ +/* #include "vdbeInt.h" */ + +/* +** Invoke this macro on memory cells just prior to changing the +** value of the cell. This macro verifies that shallow copies are +** not misused. A shallow copy of a string or blob just copies a +** pointer to the string or blob, not the content. If the original +** is changed while the copy is still in use, the string or blob might +** be changed out from under the copy. This macro verifies that nothing +** like that ever happens. +*/ +#ifdef SQLITE_DEBUG +# define memAboutToChange(P,M) sqlite3VdbeMemAboutToChange(P,M) +#else +# define memAboutToChange(P,M) +#endif + +/* +** The following global variable is incremented every time a cursor +** moves, either by the OP_SeekXX, OP_Next, or OP_Prev opcodes. The test +** procedures use this information to make sure that indices are +** working correctly. This variable has no function other than to +** help verify the correct operation of the library. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_search_count = 0; +#endif + +/* +** When this global variable is positive, it gets decremented once before +** each instruction in the VDBE. When it reaches zero, the u1.isInterrupted +** field of the sqlite3 structure is set in order to simulate an interrupt. +** +** This facility is used for testing purposes only. It does not function +** in an ordinary build. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_interrupt_count = 0; +#endif + +/* +** The next global variable is incremented each type the OP_Sort opcode +** is executed. The test procedures use this information to make sure that +** sorting is occurring or not occurring at appropriate times. This variable +** has no function other than to help verify the correct operation of the +** library. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_sort_count = 0; +#endif + +/* +** The next global variable records the size of the largest MEM_Blob +** or MEM_Str that has been used by a VDBE opcode. The test procedures +** use this information to make sure that the zero-blob functionality +** is working correctly. This variable has no function other than to +** help verify the correct operation of the library. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_max_blobsize = 0; +static void updateMaxBlobsize(Mem *p){ + if( (p->flags & (MEM_Str|MEM_Blob))!=0 && p->n>sqlite3_max_blobsize ){ + sqlite3_max_blobsize = p->n; + } +} +#endif + +/* +** This macro evaluates to true if either the update hook or the preupdate +** hook are enabled for database connect DB. +*/ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +# define HAS_UPDATE_HOOK(DB) ((DB)->xPreUpdateCallback||(DB)->xUpdateCallback) +#else +# define HAS_UPDATE_HOOK(DB) ((DB)->xUpdateCallback) +#endif + +/* +** The next global variable is incremented each time the OP_Found opcode +** is executed. This is used to test whether or not the foreign key +** operation implemented using OP_FkIsZero is working. This variable +** has no function other than to help verify the correct operation of the +** library. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_found_count = 0; +#endif + +/* +** Test a register to see if it exceeds the current maximum blob size. +** If it does, record the new maximum blob size. +*/ +#if defined(SQLITE_TEST) && !defined(SQLITE_UNTESTABLE) +# define UPDATE_MAX_BLOBSIZE(P) updateMaxBlobsize(P) +#else +# define UPDATE_MAX_BLOBSIZE(P) +#endif + +#ifdef SQLITE_DEBUG +/* This routine provides a convenient place to set a breakpoint during +** tracing with PRAGMA vdbe_trace=on. The breakpoint fires right after +** each opcode is printed. Variables "pc" (program counter) and pOp are +** available to add conditionals to the breakpoint. GDB example: +** +** break test_trace_breakpoint if pc=22 +** +** Other useful labels for breakpoints include: +** test_addop_breakpoint(pc,pOp) +** sqlite3CorruptError(lineno) +** sqlite3MisuseError(lineno) +** sqlite3CantopenError(lineno) +*/ +static void test_trace_breakpoint(int pc, Op *pOp, Vdbe *v){ + static int n = 0; + (void)pc; + (void)pOp; + (void)v; + n++; +} +#endif + +/* +** Invoke the VDBE coverage callback, if that callback is defined. This +** feature is used for test suite validation only and does not appear an +** production builds. +** +** M is the type of branch. I is the direction taken for this instance of +** the branch. +** +** M: 2 - two-way branch (I=0: fall-thru 1: jump ) +** 3 - two-way + NULL (I=0: fall-thru 1: jump 2: NULL ) +** 4 - OP_Jump (I=0: jump p1 1: jump p2 2: jump p3) +** +** In other words, if M is 2, then I is either 0 (for fall-through) or +** 1 (for when the branch is taken). If M is 3, the I is 0 for an +** ordinary fall-through, I is 1 if the branch was taken, and I is 2 +** if the result of comparison is NULL. For M=3, I=2 the jump may or +** may not be taken, depending on the SQLITE_JUMPIFNULL flags in p5. +** When M is 4, that means that an OP_Jump is being run. I is 0, 1, or 2 +** depending on if the operands are less than, equal, or greater than. +** +** iSrcLine is the source code line (from the __LINE__ macro) that +** generated the VDBE instruction combined with flag bits. The source +** code line number is in the lower 24 bits of iSrcLine and the upper +** 8 bytes are flags. The lower three bits of the flags indicate +** values for I that should never occur. For example, if the branch is +** always taken, the flags should be 0x05 since the fall-through and +** alternate branch are never taken. If a branch is never taken then +** flags should be 0x06 since only the fall-through approach is allowed. +** +** Bit 0x08 of the flags indicates an OP_Jump opcode that is only +** interested in equal or not-equal. In other words, I==0 and I==2 +** should be treated as equivalent +** +** Since only a line number is retained, not the filename, this macro +** only works for amalgamation builds. But that is ok, since these macros +** should be no-ops except for special builds used to measure test coverage. +*/ +#if !defined(SQLITE_VDBE_COVERAGE) +# define VdbeBranchTaken(I,M) +#else +# define VdbeBranchTaken(I,M) vdbeTakeBranch(pOp->iSrcLine,I,M) + static void vdbeTakeBranch(u32 iSrcLine, u8 I, u8 M){ + u8 mNever; + assert( I<=2 ); /* 0: fall through, 1: taken, 2: alternate taken */ + assert( M<=4 ); /* 2: two-way branch, 3: three-way branch, 4: OP_Jump */ + assert( I<M ); /* I can only be 2 if M is 3 or 4 */ + /* Transform I from a integer [0,1,2] into a bitmask of [1,2,4] */ + I = 1<<I; + /* The upper 8 bits of iSrcLine are flags. The lower three bits of + ** the flags indicate directions that the branch can never go. If + ** a branch really does go in one of those directions, assert right + ** away. */ + mNever = iSrcLine >> 24; + assert( (I & mNever)==0 ); + if( sqlite3GlobalConfig.xVdbeBranch==0 ) return; /*NO_TEST*/ + /* Invoke the branch coverage callback with three arguments: + ** iSrcLine - the line number of the VdbeCoverage() macro, with + ** flags removed. + ** I - Mask of bits 0x07 indicating which cases are are + ** fulfilled by this instance of the jump. 0x01 means + ** fall-thru, 0x02 means taken, 0x04 means NULL. Any + ** impossible cases (ex: if the comparison is never NULL) + ** are filled in automatically so that the coverage + ** measurement logic does not flag those impossible cases + ** as missed coverage. + ** M - Type of jump. Same as M argument above + */ + I |= mNever; + if( M==2 ) I |= 0x04; + if( M==4 ){ + I |= 0x08; + if( (mNever&0x08)!=0 && (I&0x05)!=0) I |= 0x05; /*NO_TEST*/ + } + sqlite3GlobalConfig.xVdbeBranch(sqlite3GlobalConfig.pVdbeBranchArg, + iSrcLine&0xffffff, I, M); + } +#endif + +/* +** An ephemeral string value (signified by the MEM_Ephem flag) contains +** a pointer to a dynamically allocated string where some other entity +** is responsible for deallocating that string. Because the register +** does not control the string, it might be deleted without the register +** knowing it. +** +** This routine converts an ephemeral string into a dynamically allocated +** string that the register itself controls. In other words, it +** converts an MEM_Ephem string into a string with P.z==P.zMalloc. +*/ +#define Deephemeralize(P) \ + if( ((P)->flags&MEM_Ephem)!=0 \ + && sqlite3VdbeMemMakeWriteable(P) ){ goto no_mem;} + +/* Return true if the cursor was opened using the OP_OpenSorter opcode. */ +#define isSorter(x) ((x)->eCurType==CURTYPE_SORTER) + +/* +** Allocate VdbeCursor number iCur. Return a pointer to it. Return NULL +** if we run out of memory. +*/ +static VdbeCursor *allocateCursor( + Vdbe *p, /* The virtual machine */ + int iCur, /* Index of the new VdbeCursor */ + int nField, /* Number of fields in the table or index */ + u8 eCurType /* Type of the new cursor */ +){ + /* Find the memory cell that will be used to store the blob of memory + ** required for this VdbeCursor structure. It is convenient to use a + ** vdbe memory cell to manage the memory allocation required for a + ** VdbeCursor structure for the following reasons: + ** + ** * Sometimes cursor numbers are used for a couple of different + ** purposes in a vdbe program. The different uses might require + ** different sized allocations. Memory cells provide growable + ** allocations. + ** + ** * When using ENABLE_MEMORY_MANAGEMENT, memory cell buffers can + ** be freed lazily via the sqlite3_release_memory() API. This + ** minimizes the number of malloc calls made by the system. + ** + ** The memory cell for cursor 0 is aMem[0]. The rest are allocated from + ** the top of the register space. Cursor 1 is at Mem[p->nMem-1]. + ** Cursor 2 is at Mem[p->nMem-2]. And so forth. + */ + Mem *pMem = iCur>0 ? &p->aMem[p->nMem-iCur] : p->aMem; + + int nByte; + VdbeCursor *pCx = 0; + nByte = + ROUND8P(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + + (eCurType==CURTYPE_BTREE?sqlite3BtreeCursorSize():0); + + assert( iCur>=0 && iCur<p->nCursor ); + if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/ + sqlite3VdbeFreeCursorNN(p, p->apCsr[iCur]); + p->apCsr[iCur] = 0; + } + + /* There used to be a call to sqlite3VdbeMemClearAndResize() to make sure + ** the pMem used to hold space for the cursor has enough storage available + ** in pMem->zMalloc. But for the special case of the aMem[] entries used + ** to hold cursors, it is faster to in-line the logic. */ + assert( pMem->flags==MEM_Undefined ); + assert( (pMem->flags & MEM_Dyn)==0 ); + assert( pMem->szMalloc==0 || pMem->z==pMem->zMalloc ); + if( pMem->szMalloc<nByte ){ + if( pMem->szMalloc>0 ){ + sqlite3DbFreeNN(pMem->db, pMem->zMalloc); + } + pMem->z = pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, nByte); + if( pMem->zMalloc==0 ){ + pMem->szMalloc = 0; + return 0; + } + pMem->szMalloc = nByte; + } + + p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->zMalloc; + memset(pCx, 0, offsetof(VdbeCursor,pAltCursor)); + pCx->eCurType = eCurType; + pCx->nField = nField; + pCx->aOffset = &pCx->aType[nField]; + if( eCurType==CURTYPE_BTREE ){ + pCx->uc.pCursor = (BtCursor*) + &pMem->z[ROUND8P(sizeof(VdbeCursor))+2*sizeof(u32)*nField]; + sqlite3BtreeCursorZero(pCx->uc.pCursor); + } + return pCx; +} + +/* +** The string in pRec is known to look like an integer and to have a +** floating point value of rValue. Return true and set *piValue to the +** integer value if the string is in range to be an integer. Otherwise, +** return false. +*/ +static int alsoAnInt(Mem *pRec, double rValue, i64 *piValue){ + i64 iValue; + iValue = sqlite3RealToI64(rValue); + if( sqlite3RealSameAsInt(rValue,iValue) ){ + *piValue = iValue; + return 1; + } + return 0==sqlite3Atoi64(pRec->z, piValue, pRec->n, pRec->enc); +} + +/* +** Try to convert a value into a numeric representation if we can +** do so without loss of information. In other words, if the string +** looks like a number, convert it into a number. If it does not +** look like a number, leave it alone. +** +** If the bTryForInt flag is true, then extra effort is made to give +** an integer representation. Strings that look like floating point +** values but which have no fractional component (example: '48.00') +** will have a MEM_Int representation when bTryForInt is true. +** +** If bTryForInt is false, then if the input string contains a decimal +** point or exponential notation, the result is only MEM_Real, even +** if there is an exact integer representation of the quantity. +*/ +static void applyNumericAffinity(Mem *pRec, int bTryForInt){ + double rValue; + u8 enc = pRec->enc; + int rc; + assert( (pRec->flags & (MEM_Str|MEM_Int|MEM_Real|MEM_IntReal))==MEM_Str ); + rc = sqlite3AtoF(pRec->z, &rValue, pRec->n, enc); + if( rc<=0 ) return; + if( rc==1 && alsoAnInt(pRec, rValue, &pRec->u.i) ){ + pRec->flags |= MEM_Int; + }else{ + pRec->u.r = rValue; + pRec->flags |= MEM_Real; + if( bTryForInt ) sqlite3VdbeIntegerAffinity(pRec); + } + /* TEXT->NUMERIC is many->one. Hence, it is important to invalidate the + ** string representation after computing a numeric equivalent, because the + ** string representation might not be the canonical representation for the + ** numeric value. Ticket [343634942dd54ab57b7024] 2018-01-31. */ + pRec->flags &= ~MEM_Str; +} + +/* +** Processing is determine by the affinity parameter: +** +** SQLITE_AFF_INTEGER: +** SQLITE_AFF_REAL: +** SQLITE_AFF_NUMERIC: +** Try to convert pRec to an integer representation or a +** floating-point representation if an integer representation +** is not possible. Note that the integer representation is +** always preferred, even if the affinity is REAL, because +** an integer representation is more space efficient on disk. +** +** SQLITE_AFF_FLEXNUM: +** If the value is text, then try to convert it into a number of +** some kind (integer or real) but do not make any other changes. +** +** SQLITE_AFF_TEXT: +** Convert pRec to a text representation. +** +** SQLITE_AFF_BLOB: +** SQLITE_AFF_NONE: +** No-op. pRec is unchanged. +*/ +static void applyAffinity( + Mem *pRec, /* The value to apply affinity to */ + char affinity, /* The affinity to be applied */ + u8 enc /* Use this text encoding */ +){ + if( affinity>=SQLITE_AFF_NUMERIC ){ + assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL + || affinity==SQLITE_AFF_NUMERIC || affinity==SQLITE_AFF_FLEXNUM ); + if( (pRec->flags & MEM_Int)==0 ){ /*OPTIMIZATION-IF-FALSE*/ + if( (pRec->flags & (MEM_Real|MEM_IntReal))==0 ){ + if( pRec->flags & MEM_Str ) applyNumericAffinity(pRec,1); + }else if( affinity<=SQLITE_AFF_REAL ){ + sqlite3VdbeIntegerAffinity(pRec); + } + } + }else if( affinity==SQLITE_AFF_TEXT ){ + /* Only attempt the conversion to TEXT if there is an integer or real + ** representation (blob and NULL do not get converted) but no string + ** representation. It would be harmless to repeat the conversion if + ** there is already a string rep, but it is pointless to waste those + ** CPU cycles. */ + if( 0==(pRec->flags&MEM_Str) ){ /*OPTIMIZATION-IF-FALSE*/ + if( (pRec->flags&(MEM_Real|MEM_Int|MEM_IntReal)) ){ + testcase( pRec->flags & MEM_Int ); + testcase( pRec->flags & MEM_Real ); + testcase( pRec->flags & MEM_IntReal ); + sqlite3VdbeMemStringify(pRec, enc, 1); + } + } + pRec->flags &= ~(MEM_Real|MEM_Int|MEM_IntReal); + } +} + +/* +** Try to convert the type of a function argument or a result column +** into a numeric representation. Use either INTEGER or REAL whichever +** is appropriate. But only do the conversion if it is possible without +** loss of information and return the revised type of the argument. +*/ +SQLITE_API int sqlite3_value_numeric_type(sqlite3_value *pVal){ + int eType = sqlite3_value_type(pVal); + if( eType==SQLITE_TEXT ){ + Mem *pMem = (Mem*)pVal; + applyNumericAffinity(pMem, 0); + eType = sqlite3_value_type(pVal); + } + return eType; +} + +/* +** Exported version of applyAffinity(). This one works on sqlite3_value*, +** not the internal Mem* type. +*/ +SQLITE_PRIVATE void sqlite3ValueApplyAffinity( + sqlite3_value *pVal, + u8 affinity, + u8 enc +){ + applyAffinity((Mem *)pVal, affinity, enc); +} + +/* +** pMem currently only holds a string type (or maybe a BLOB that we can +** interpret as a string if we want to). Compute its corresponding +** numeric type, if has one. Set the pMem->u.r and pMem->u.i fields +** accordingly. +*/ +static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ + int rc; + sqlite3_int64 ix; + assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ); + assert( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ); + if( ExpandBlob(pMem) ){ + pMem->u.i = 0; + return MEM_Int; + } + rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); + if( rc<=0 ){ + if( rc==0 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1 ){ + pMem->u.i = ix; + return MEM_Int; + }else{ + return MEM_Real; + } + }else if( rc==1 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)==0 ){ + pMem->u.i = ix; + return MEM_Int; + } + return MEM_Real; +} + +/* +** Return the numeric type for pMem, either MEM_Int or MEM_Real or both or +** none. +** +** Unlike applyNumericAffinity(), this routine does not modify pMem->flags. +** But it does set pMem->u.r and pMem->u.i appropriately. +*/ +static u16 numericType(Mem *pMem){ + assert( (pMem->flags & MEM_Null)==0 + || pMem->db==0 || pMem->db->mallocFailed ); + if( pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null) ){ + testcase( pMem->flags & MEM_Int ); + testcase( pMem->flags & MEM_Real ); + testcase( pMem->flags & MEM_IntReal ); + return pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null); + } + assert( pMem->flags & (MEM_Str|MEM_Blob) ); + testcase( pMem->flags & MEM_Str ); + testcase( pMem->flags & MEM_Blob ); + return computeNumericType(pMem); + return 0; +} + +#ifdef SQLITE_DEBUG +/* +** Write a nice string representation of the contents of cell pMem +** into buffer zBuf, length nBuf. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr){ + int f = pMem->flags; + static const char *const encnames[] = {"(X)", "(8)", "(16LE)", "(16BE)"}; + if( f&MEM_Blob ){ + int i; + char c; + if( f & MEM_Dyn ){ + c = 'z'; + assert( (f & (MEM_Static|MEM_Ephem))==0 ); + }else if( f & MEM_Static ){ + c = 't'; + assert( (f & (MEM_Dyn|MEM_Ephem))==0 ); + }else if( f & MEM_Ephem ){ + c = 'e'; + assert( (f & (MEM_Static|MEM_Dyn))==0 ); + }else{ + c = 's'; + } + sqlite3_str_appendf(pStr, "%cx[", c); + for(i=0; i<25 && i<pMem->n; i++){ + sqlite3_str_appendf(pStr, "%02X", ((int)pMem->z[i] & 0xFF)); + } + sqlite3_str_appendf(pStr, "|"); + for(i=0; i<25 && i<pMem->n; i++){ + char z = pMem->z[i]; + sqlite3_str_appendchar(pStr, 1, (z<32||z>126)?'.':z); + } + sqlite3_str_appendf(pStr,"]"); + if( f & MEM_Zero ){ + sqlite3_str_appendf(pStr, "+%dz",pMem->u.nZero); + } + }else if( f & MEM_Str ){ + int j; + u8 c; + if( f & MEM_Dyn ){ + c = 'z'; + assert( (f & (MEM_Static|MEM_Ephem))==0 ); + }else if( f & MEM_Static ){ + c = 't'; + assert( (f & (MEM_Dyn|MEM_Ephem))==0 ); + }else if( f & MEM_Ephem ){ + c = 'e'; + assert( (f & (MEM_Static|MEM_Dyn))==0 ); + }else{ + c = 's'; + } + sqlite3_str_appendf(pStr, " %c%d[", c, pMem->n); + for(j=0; j<25 && j<pMem->n; j++){ + c = pMem->z[j]; + sqlite3_str_appendchar(pStr, 1, (c>=0x20&&c<=0x7f) ? c : '.'); + } + sqlite3_str_appendf(pStr, "]%s", encnames[pMem->enc]); + if( f & MEM_Term ){ + sqlite3_str_appendf(pStr, "(0-term)"); + } + } +} +#endif + +#ifdef SQLITE_DEBUG +/* +** Print the value of a register for tracing purposes: +*/ +static void memTracePrint(Mem *p){ + if( p->flags & MEM_Undefined ){ + printf(" undefined"); + }else if( p->flags & MEM_Null ){ + printf(p->flags & MEM_Zero ? " NULL-nochng" : " NULL"); + }else if( (p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str) ){ + printf(" si:%lld", p->u.i); + }else if( (p->flags & (MEM_IntReal))!=0 ){ + printf(" ir:%lld", p->u.i); + }else if( p->flags & MEM_Int ){ + printf(" i:%lld", p->u.i); +#ifndef SQLITE_OMIT_FLOATING_POINT + }else if( p->flags & MEM_Real ){ + printf(" r:%.17g", p->u.r); +#endif + }else if( sqlite3VdbeMemIsRowSet(p) ){ + printf(" (rowset)"); + }else{ + StrAccum acc; + char zBuf[1000]; + sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); + sqlite3VdbeMemPrettyPrint(p, &acc); + printf(" %s", sqlite3StrAccumFinish(&acc)); + } + if( p->flags & MEM_Subtype ) printf(" subtype=0x%02x", p->eSubtype); +} +static void registerTrace(int iReg, Mem *p){ + printf("R[%d] = ", iReg); + memTracePrint(p); + if( p->pScopyFrom ){ + printf(" <== R[%d]", (int)(p->pScopyFrom - &p[-iReg])); + } + printf("\n"); + sqlite3VdbeCheckMemInvariants(p); +} +/**/ void sqlite3PrintMem(Mem *pMem){ + memTracePrint(pMem); + printf("\n"); + fflush(stdout); +} +#endif + +#ifdef SQLITE_DEBUG +/* +** Show the values of all registers in the virtual machine. Used for +** interactive debugging. +*/ +SQLITE_PRIVATE void sqlite3VdbeRegisterDump(Vdbe *v){ + int i; + for(i=1; i<v->nMem; i++) registerTrace(i, v->aMem+i); +} +#endif /* SQLITE_DEBUG */ + + +#ifdef SQLITE_DEBUG +# define REGISTER_TRACE(R,M) if(db->flags&SQLITE_VdbeTrace)registerTrace(R,M) +#else +# define REGISTER_TRACE(R,M) +#endif + +#ifndef NDEBUG +/* +** This function is only called from within an assert() expression. It +** checks that the sqlite3.nTransaction variable is correctly set to +** the number of non-transaction savepoints currently in the +** linked list starting at sqlite3.pSavepoint. +** +** Usage: +** +** assert( checkSavepointCount(db) ); +*/ +static int checkSavepointCount(sqlite3 *db){ + int n = 0; + Savepoint *p; + for(p=db->pSavepoint; p; p=p->pNext) n++; + assert( n==(db->nSavepoint + db->isTransactionSavepoint) ); + return 1; +} +#endif + +/* +** Return the register of pOp->p2 after first preparing it to be +** overwritten with an integer value. +*/ +static SQLITE_NOINLINE Mem *out2PrereleaseWithClear(Mem *pOut){ + sqlite3VdbeMemSetNull(pOut); + pOut->flags = MEM_Int; + return pOut; +} +static Mem *out2Prerelease(Vdbe *p, VdbeOp *pOp){ + Mem *pOut; + assert( pOp->p2>0 ); + assert( pOp->p2<=(p->nMem+1 - p->nCursor) ); + pOut = &p->aMem[pOp->p2]; + memAboutToChange(p, pOut); + if( VdbeMemDynamic(pOut) ){ /*OPTIMIZATION-IF-FALSE*/ + return out2PrereleaseWithClear(pOut); + }else{ + pOut->flags = MEM_Int; + return pOut; + } +} + +/* +** Compute a bloom filter hash using pOp->p4.i registers from aMem[] beginning +** with pOp->p3. Return the hash. +*/ +static u64 filterHash(const Mem *aMem, const Op *pOp){ + int i, mx; + u64 h = 0; + + assert( pOp->p4type==P4_INT32 ); + for(i=pOp->p3, mx=i+pOp->p4.i; i<mx; i++){ + const Mem *p = &aMem[i]; + if( p->flags & (MEM_Int|MEM_IntReal) ){ + h += p->u.i; + }else if( p->flags & MEM_Real ){ + h += sqlite3VdbeIntValue(p); + }else if( p->flags & (MEM_Str|MEM_Blob) ){ + /* All strings have the same hash and all blobs have the same hash, + ** though, at least, those hashes are different from each other and + ** from NULL. */ + h += 4093 + (p->flags & (MEM_Str|MEM_Blob)); + } + } + return h; +} + + +/* +** For OP_Column, factor out the case where content is loaded from +** overflow pages, so that the code to implement this case is separate +** the common case where all content fits on the page. Factoring out +** the code reduces register pressure and helps the common case +** to run faster. +*/ +static SQLITE_NOINLINE int vdbeColumnFromOverflow( + VdbeCursor *pC, /* The BTree cursor from which we are reading */ + int iCol, /* The column to read */ + int t, /* The serial-type code for the column value */ + i64 iOffset, /* Offset to the start of the content value */ + u32 cacheStatus, /* Current Vdbe.cacheCtr value */ + u32 colCacheCtr, /* Current value of the column cache counter */ + Mem *pDest /* Store the value into this register. */ +){ + int rc; + sqlite3 *db = pDest->db; + int encoding = pDest->enc; + int len = sqlite3VdbeSerialTypeLen(t); + assert( pC->eCurType==CURTYPE_BTREE ); + if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) return SQLITE_TOOBIG; + if( len > 4000 && pC->pKeyInfo==0 ){ + /* Cache large column values that are on overflow pages using + ** an RCStr (reference counted string) so that if they are reloaded, + ** that do not have to be copied a second time. The overhead of + ** creating and managing the cache is such that this is only + ** profitable for larger TEXT and BLOB values. + ** + ** Only do this on table-btrees so that writes to index-btrees do not + ** need to clear the cache. This buys performance in the common case + ** in exchange for generality. + */ + VdbeTxtBlbCache *pCache; + char *pBuf; + if( pC->colCache==0 ){ + pC->pCache = sqlite3DbMallocZero(db, sizeof(VdbeTxtBlbCache) ); + if( pC->pCache==0 ) return SQLITE_NOMEM; + pC->colCache = 1; + } + pCache = pC->pCache; + if( pCache->pCValue==0 + || pCache->iCol!=iCol + || pCache->cacheStatus!=cacheStatus + || pCache->colCacheCtr!=colCacheCtr + || pCache->iOffset!=sqlite3BtreeOffset(pC->uc.pCursor) + ){ + if( pCache->pCValue ) sqlite3RCStrUnref(pCache->pCValue); + pBuf = pCache->pCValue = sqlite3RCStrNew( len+3 ); + if( pBuf==0 ) return SQLITE_NOMEM; + rc = sqlite3BtreePayload(pC->uc.pCursor, iOffset, len, pBuf); + if( rc ) return rc; + pBuf[len] = 0; + pBuf[len+1] = 0; + pBuf[len+2] = 0; + pCache->iCol = iCol; + pCache->cacheStatus = cacheStatus; + pCache->colCacheCtr = colCacheCtr; + pCache->iOffset = sqlite3BtreeOffset(pC->uc.pCursor); + }else{ + pBuf = pCache->pCValue; + } + assert( t>=12 ); + sqlite3RCStrRef(pBuf); + if( t&1 ){ + rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, encoding, + (void(*)(void*))sqlite3RCStrUnref); + pDest->flags |= MEM_Term; + }else{ + rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, 0, + (void(*)(void*))sqlite3RCStrUnref); + } + }else{ + rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, iOffset, len, pDest); + if( rc ) return rc; + sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); + if( (t&1)!=0 && encoding==SQLITE_UTF8 ){ + pDest->z[len] = 0; + pDest->flags |= MEM_Term; + } + } + pDest->flags &= ~MEM_Ephem; + return rc; +} + + +/* +** Return the symbolic name for the data type of a pMem +*/ +static const char *vdbeMemTypeName(Mem *pMem){ + static const char *azTypes[] = { + /* SQLITE_INTEGER */ "INT", + /* SQLITE_FLOAT */ "REAL", + /* SQLITE_TEXT */ "TEXT", + /* SQLITE_BLOB */ "BLOB", + /* SQLITE_NULL */ "NULL" + }; + return azTypes[sqlite3_value_type(pMem)-1]; +} + +/* +** Execute as much of a VDBE program as we can. +** This is the core of sqlite3_step(). +*/ +SQLITE_PRIVATE int sqlite3VdbeExec( + Vdbe *p /* The VDBE */ +){ + Op *aOp = p->aOp; /* Copy of p->aOp */ + Op *pOp = aOp; /* Current operation */ +#ifdef SQLITE_DEBUG + Op *pOrigOp; /* Value of pOp at the top of the loop */ + int nExtraDelete = 0; /* Verifies FORDELETE and AUXDELETE flags */ + u8 iCompareIsInit = 0; /* iCompare is initialized */ +#endif + int rc = SQLITE_OK; /* Value to return */ + sqlite3 *db = p->db; /* The database */ + u8 resetSchemaOnFault = 0; /* Reset schema after an error if positive */ + u8 encoding = ENC(db); /* The database encoding */ + int iCompare = 0; /* Result of last comparison */ + u64 nVmStep = 0; /* Number of virtual machine steps */ +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + u64 nProgressLimit; /* Invoke xProgress() when nVmStep reaches this */ +#endif + Mem *aMem = p->aMem; /* Copy of p->aMem */ + Mem *pIn1 = 0; /* 1st input operand */ + Mem *pIn2 = 0; /* 2nd input operand */ + Mem *pIn3 = 0; /* 3rd input operand */ + Mem *pOut = 0; /* Output operand */ + u32 colCacheCtr = 0; /* Column cache counter */ +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + u64 *pnCycle = 0; + int bStmtScanStatus = IS_STMT_SCANSTATUS(db)!=0; +#endif + /*** INSERT STACK UNION HERE ***/ + + assert( p->eVdbeState==VDBE_RUN_STATE ); /* sqlite3_step() verifies this */ + if( DbMaskNonZero(p->lockMask) ){ + sqlite3VdbeEnter(p); + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( db->xProgress ){ + u32 iPrior = p->aCounter[SQLITE_STMTSTATUS_VM_STEP]; + assert( 0 < db->nProgressOps ); + nProgressLimit = db->nProgressOps - (iPrior % db->nProgressOps); + }else{ + nProgressLimit = LARGEST_UINT64; + } +#endif + if( p->rc==SQLITE_NOMEM ){ + /* This happens if a malloc() inside a call to sqlite3_column_text() or + ** sqlite3_column_text16() failed. */ + goto no_mem; + } + assert( p->rc==SQLITE_OK || (p->rc&0xff)==SQLITE_BUSY ); + testcase( p->rc!=SQLITE_OK ); + p->rc = SQLITE_OK; + assert( p->bIsReader || p->readOnly!=0 ); + p->iCurrentTime = 0; + assert( p->explain==0 ); + db->busyHandler.nBusy = 0; + if( AtomicLoad(&db->u1.isInterrupted) ) goto abort_due_to_interrupt; + sqlite3VdbeIOTraceSql(p); +#ifdef SQLITE_DEBUG + sqlite3BeginBenignMalloc(); + if( p->pc==0 + && (p->db->flags & (SQLITE_VdbeListing|SQLITE_VdbeEQP|SQLITE_VdbeTrace))!=0 + ){ + int i; + int once = 1; + sqlite3VdbePrintSql(p); + if( p->db->flags & SQLITE_VdbeListing ){ + printf("VDBE Program Listing:\n"); + for(i=0; i<p->nOp; i++){ + sqlite3VdbePrintOp(stdout, i, &aOp[i]); + } + } + if( p->db->flags & SQLITE_VdbeEQP ){ + for(i=0; i<p->nOp; i++){ + if( aOp[i].opcode==OP_Explain ){ + if( once ) printf("VDBE Query Plan:\n"); + printf("%s\n", aOp[i].p4.z); + once = 0; + } + } + } + if( p->db->flags & SQLITE_VdbeTrace ) printf("VDBE Trace:\n"); + } + sqlite3EndBenignMalloc(); +#endif + for(pOp=&aOp[p->pc]; 1; pOp++){ + /* Errors are detected by individual opcodes, with an immediate + ** jumps to abort_due_to_error. */ + assert( rc==SQLITE_OK ); + + assert( pOp>=aOp && pOp<&aOp[p->nOp]); + nVmStep++; + +#if defined(VDBE_PROFILE) + pOp->nExec++; + pnCycle = &pOp->nCycle; + if( sqlite3NProfileCnt==0 ) *pnCycle -= sqlite3Hwtime(); +#elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) + if( bStmtScanStatus ){ + pOp->nExec++; + pnCycle = &pOp->nCycle; + *pnCycle -= sqlite3Hwtime(); + } +#endif + + /* Only allow tracing if SQLITE_DEBUG is defined. + */ +#ifdef SQLITE_DEBUG + if( db->flags & SQLITE_VdbeTrace ){ + sqlite3VdbePrintOp(stdout, (int)(pOp - aOp), pOp); + test_trace_breakpoint((int)(pOp - aOp),pOp,p); + } +#endif + + + /* Check to see if we need to simulate an interrupt. This only happens + ** if we have a special test build. + */ +#ifdef SQLITE_TEST + if( sqlite3_interrupt_count>0 ){ + sqlite3_interrupt_count--; + if( sqlite3_interrupt_count==0 ){ + sqlite3_interrupt(db); + } + } +#endif + + /* Sanity checking on other operands */ +#ifdef SQLITE_DEBUG + { + u8 opProperty = sqlite3OpcodeProperty[pOp->opcode]; + if( (opProperty & OPFLG_IN1)!=0 ){ + assert( pOp->p1>0 ); + assert( pOp->p1<=(p->nMem+1 - p->nCursor) ); + assert( memIsValid(&aMem[pOp->p1]) ); + assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p1]) ); + REGISTER_TRACE(pOp->p1, &aMem[pOp->p1]); + } + if( (opProperty & OPFLG_IN2)!=0 ){ + assert( pOp->p2>0 ); + assert( pOp->p2<=(p->nMem+1 - p->nCursor) ); + assert( memIsValid(&aMem[pOp->p2]) ); + assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p2]) ); + REGISTER_TRACE(pOp->p2, &aMem[pOp->p2]); + } + if( (opProperty & OPFLG_IN3)!=0 ){ + assert( pOp->p3>0 ); + assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); + assert( memIsValid(&aMem[pOp->p3]) ); + assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p3]) ); + REGISTER_TRACE(pOp->p3, &aMem[pOp->p3]); + } + if( (opProperty & OPFLG_OUT2)!=0 ){ + assert( pOp->p2>0 ); + assert( pOp->p2<=(p->nMem+1 - p->nCursor) ); + memAboutToChange(p, &aMem[pOp->p2]); + } + if( (opProperty & OPFLG_OUT3)!=0 ){ + assert( pOp->p3>0 ); + assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); + memAboutToChange(p, &aMem[pOp->p3]); + } + } +#endif +#ifdef SQLITE_DEBUG + pOrigOp = pOp; +#endif + + switch( pOp->opcode ){ + +/***************************************************************************** +** What follows is a massive switch statement where each case implements a +** separate instruction in the virtual machine. If we follow the usual +** indentation conventions, each case should be indented by 6 spaces. But +** that is a lot of wasted space on the left margin. So the code within +** the switch statement will break with convention and be flush-left. Another +** big comment (similar to this one) will mark the point in the code where +** we transition back to normal indentation. +** +** The formatting of each case is important. The makefile for SQLite +** generates two C files "opcodes.h" and "opcodes.c" by scanning this +** file looking for lines that begin with "case OP_". The opcodes.h files +** will be filled with #defines that give unique integer values to each +** opcode and the opcodes.c file is filled with an array of strings where +** each string is the symbolic name for the corresponding opcode. If the +** case statement is followed by a comment of the form "/# same as ... #/" +** that comment is used to determine the particular value of the opcode. +** +** Other keywords in the comment that follows each case are used to +** construct the OPFLG_INITIALIZER value that initializes opcodeProperty[]. +** Keywords include: in1, in2, in3, out2, out3. See +** the mkopcodeh.awk script for additional information. +** +** Documentation about VDBE opcodes is generated by scanning this file +** for lines of that contain "Opcode:". That line and all subsequent +** comment lines are used in the generation of the opcode.html documentation +** file. +** +** SUMMARY: +** +** Formatting is important to scripts that scan this file. +** Do not deviate from the formatting style currently in use. +** +*****************************************************************************/ + +/* Opcode: Goto * P2 * * * +** +** An unconditional jump to address P2. +** The next instruction executed will be +** the one at index P2 from the beginning of +** the program. +** +** The P1 parameter is not actually used by this opcode. However, it +** is sometimes set to 1 instead of 0 as a hint to the command-line shell +** that this Goto is the bottom of a loop and that the lines from P2 down +** to the current line should be indented for EXPLAIN output. +*/ +case OP_Goto: { /* jump */ + +#ifdef SQLITE_DEBUG + /* In debugging mode, when the p5 flags is set on an OP_Goto, that + ** means we should really jump back to the preceding OP_ReleaseReg + ** instruction. */ + if( pOp->p5 ){ + assert( pOp->p2 < (int)(pOp - aOp) ); + assert( pOp->p2 > 1 ); + pOp = &aOp[pOp->p2 - 2]; + assert( pOp[1].opcode==OP_ReleaseReg ); + goto check_for_interrupt; + } +#endif + +jump_to_p2_and_check_for_interrupt: + pOp = &aOp[pOp->p2 - 1]; + + /* Opcodes that are used as the bottom of a loop (OP_Next, OP_Prev, + ** OP_VNext, or OP_SorterNext) all jump here upon + ** completion. Check to see if sqlite3_interrupt() has been called + ** or if the progress callback needs to be invoked. + ** + ** This code uses unstructured "goto" statements and does not look clean. + ** But that is not due to sloppy coding habits. The code is written this + ** way for performance, to avoid having to run the interrupt and progress + ** checks on every opcode. This helps sqlite3_step() to run about 1.5% + ** faster according to "valgrind --tool=cachegrind" */ +check_for_interrupt: + if( AtomicLoad(&db->u1.isInterrupted) ) goto abort_due_to_interrupt; +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + /* Call the progress callback if it is configured and the required number + ** of VDBE ops have been executed (either since this invocation of + ** sqlite3VdbeExec() or since last time the progress callback was called). + ** If the progress callback returns non-zero, exit the virtual machine with + ** a return code SQLITE_ABORT. + */ + while( nVmStep>=nProgressLimit && db->xProgress!=0 ){ + assert( db->nProgressOps!=0 ); + nProgressLimit += db->nProgressOps; + if( db->xProgress(db->pProgressArg) ){ + nProgressLimit = LARGEST_UINT64; + rc = SQLITE_INTERRUPT; + goto abort_due_to_error; + } + } +#endif + + break; +} + +/* Opcode: Gosub P1 P2 * * * +** +** Write the current address onto register P1 +** and then jump to address P2. +*/ +case OP_Gosub: { /* jump */ + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + pIn1 = &aMem[pOp->p1]; + assert( VdbeMemDynamic(pIn1)==0 ); + memAboutToChange(p, pIn1); + pIn1->flags = MEM_Int; + pIn1->u.i = (int)(pOp-aOp); + REGISTER_TRACE(pOp->p1, pIn1); + goto jump_to_p2_and_check_for_interrupt; +} + +/* Opcode: Return P1 P2 P3 * * +** +** Jump to the address stored in register P1. If P1 is a return address +** register, then this accomplishes a return from a subroutine. +** +** If P3 is 1, then the jump is only taken if register P1 holds an integer +** values, otherwise execution falls through to the next opcode, and the +** OP_Return becomes a no-op. If P3 is 0, then register P1 must hold an +** integer or else an assert() is raised. P3 should be set to 1 when +** this opcode is used in combination with OP_BeginSubrtn, and set to 0 +** otherwise. +** +** The value in register P1 is unchanged by this opcode. +** +** P2 is not used by the byte-code engine. However, if P2 is positive +** and also less than the current address, then the "EXPLAIN" output +** formatter in the CLI will indent all opcodes from the P2 opcode up +** to be not including the current Return. P2 should be the first opcode +** in the subroutine from which this opcode is returning. Thus the P2 +** value is a byte-code indentation hint. See tag-20220407a in +** wherecode.c and shell.c. +*/ +case OP_Return: { /* in1 */ + pIn1 = &aMem[pOp->p1]; + if( pIn1->flags & MEM_Int ){ + if( pOp->p3 ){ VdbeBranchTaken(1, 2); } + pOp = &aOp[pIn1->u.i]; + }else if( ALWAYS(pOp->p3) ){ + VdbeBranchTaken(0, 2); + } + break; +} + +/* Opcode: InitCoroutine P1 P2 P3 * * +** +** Set up register P1 so that it will Yield to the coroutine +** located at address P3. +** +** If P2!=0 then the coroutine implementation immediately follows +** this opcode. So jump over the coroutine implementation to +** address P2. +** +** See also: EndCoroutine +*/ +case OP_InitCoroutine: { /* jump */ + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + assert( pOp->p2>=0 && pOp->p2<p->nOp ); + assert( pOp->p3>=0 && pOp->p3<p->nOp ); + pOut = &aMem[pOp->p1]; + assert( !VdbeMemDynamic(pOut) ); + pOut->u.i = pOp->p3 - 1; + pOut->flags = MEM_Int; + if( pOp->p2==0 ) break; + + /* Most jump operations do a goto to this spot in order to update + ** the pOp pointer. */ +jump_to_p2: + assert( pOp->p2>0 ); /* There are never any jumps to instruction 0 */ + assert( pOp->p2<p->nOp ); /* Jumps must be in range */ + pOp = &aOp[pOp->p2 - 1]; + break; +} + +/* Opcode: EndCoroutine P1 * * * * +** +** The instruction at the address in register P1 is a Yield. +** Jump to the P2 parameter of that Yield. +** After the jump, register P1 becomes undefined. +** +** See also: InitCoroutine +*/ +case OP_EndCoroutine: { /* in1 */ + VdbeOp *pCaller; + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags==MEM_Int ); + assert( pIn1->u.i>=0 && pIn1->u.i<p->nOp ); + pCaller = &aOp[pIn1->u.i]; + assert( pCaller->opcode==OP_Yield ); + assert( pCaller->p2>=0 && pCaller->p2<p->nOp ); + pOp = &aOp[pCaller->p2 - 1]; + pIn1->flags = MEM_Undefined; + break; +} + +/* Opcode: Yield P1 P2 * * * +** +** Swap the program counter with the value in register P1. This +** has the effect of yielding to a coroutine. +** +** If the coroutine that is launched by this instruction ends with +** Yield or Return then continue to the next instruction. But if +** the coroutine launched by this instruction ends with +** EndCoroutine, then jump to P2 rather than continuing with the +** next instruction. +** +** See also: InitCoroutine +*/ +case OP_Yield: { /* in1, jump */ + int pcDest; + pIn1 = &aMem[pOp->p1]; + assert( VdbeMemDynamic(pIn1)==0 ); + pIn1->flags = MEM_Int; + pcDest = (int)pIn1->u.i; + pIn1->u.i = (int)(pOp - aOp); + REGISTER_TRACE(pOp->p1, pIn1); + pOp = &aOp[pcDest]; + break; +} + +/* Opcode: HaltIfNull P1 P2 P3 P4 P5 +** Synopsis: if r[P3]=null halt +** +** Check the value in register P3. If it is NULL then Halt using +** parameter P1, P2, and P4 as if this were a Halt instruction. If the +** value in register P3 is not NULL, then this routine is a no-op. +** The P5 parameter should be 1. +*/ +case OP_HaltIfNull: { /* in3 */ + pIn3 = &aMem[pOp->p3]; +#ifdef SQLITE_DEBUG + if( pOp->p2==OE_Abort ){ sqlite3VdbeAssertAbortable(p); } +#endif + if( (pIn3->flags & MEM_Null)==0 ) break; + /* Fall through into OP_Halt */ + /* no break */ deliberate_fall_through +} + +/* Opcode: Halt P1 P2 * P4 P5 +** +** Exit immediately. All open cursors, etc are closed +** automatically. +** +** P1 is the result code returned by sqlite3_exec(), sqlite3_reset(), +** or sqlite3_finalize(). For a normal halt, this should be SQLITE_OK (0). +** For errors, it can be some other value. If P1!=0 then P2 will determine +** whether or not to rollback the current transaction. Do not rollback +** if P2==OE_Fail. Do the rollback if P2==OE_Rollback. If P2==OE_Abort, +** then back out all changes that have occurred during this execution of the +** VDBE, but do not rollback the transaction. +** +** If P4 is not null then it is an error message string. +** +** P5 is a value between 0 and 4, inclusive, that modifies the P4 string. +** +** 0: (no change) +** 1: NOT NULL constraint failed: P4 +** 2: UNIQUE constraint failed: P4 +** 3: CHECK constraint failed: P4 +** 4: FOREIGN KEY constraint failed: P4 +** +** If P5 is not zero and P4 is NULL, then everything after the ":" is +** omitted. +** +** There is an implied "Halt 0 0 0" instruction inserted at the very end of +** every program. So a jump past the last instruction of the program +** is the same as executing Halt. +*/ +case OP_Halt: { + VdbeFrame *pFrame; + int pcx; + +#ifdef SQLITE_DEBUG + if( pOp->p2==OE_Abort ){ sqlite3VdbeAssertAbortable(p); } +#endif + + /* A deliberately coded "OP_Halt SQLITE_INTERNAL * * * *" opcode indicates + ** something is wrong with the code generator. Raise an assertion in order + ** to bring this to the attention of fuzzers and other testing tools. */ + assert( pOp->p1!=SQLITE_INTERNAL ); + + if( p->pFrame && pOp->p1==SQLITE_OK ){ + /* Halt the sub-program. Return control to the parent frame. */ + pFrame = p->pFrame; + p->pFrame = pFrame->pParent; + p->nFrame--; + sqlite3VdbeSetChanges(db, p->nChange); + pcx = sqlite3VdbeFrameRestore(pFrame); + if( pOp->p2==OE_Ignore ){ + /* Instruction pcx is the OP_Program that invoked the sub-program + ** currently being halted. If the p2 instruction of this OP_Halt + ** instruction is set to OE_Ignore, then the sub-program is throwing + ** an IGNORE exception. In this case jump to the address specified + ** as the p2 of the calling OP_Program. */ + pcx = p->aOp[pcx].p2-1; + } + aOp = p->aOp; + aMem = p->aMem; + pOp = &aOp[pcx]; + break; + } + p->rc = pOp->p1; + p->errorAction = (u8)pOp->p2; + assert( pOp->p5<=4 ); + if( p->rc ){ + if( pOp->p5 ){ + static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK", + "FOREIGN KEY" }; + testcase( pOp->p5==1 ); + testcase( pOp->p5==2 ); + testcase( pOp->p5==3 ); + testcase( pOp->p5==4 ); + sqlite3VdbeError(p, "%s constraint failed", azType[pOp->p5-1]); + if( pOp->p4.z ){ + p->zErrMsg = sqlite3MPrintf(db, "%z: %s", p->zErrMsg, pOp->p4.z); + } + }else{ + sqlite3VdbeError(p, "%s", pOp->p4.z); + } + pcx = (int)(pOp - aOp); + sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg); + } + rc = sqlite3VdbeHalt(p); + assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR ); + if( rc==SQLITE_BUSY ){ + p->rc = SQLITE_BUSY; + }else{ + assert( rc==SQLITE_OK || (p->rc&0xff)==SQLITE_CONSTRAINT ); + assert( rc==SQLITE_OK || db->nDeferredCons>0 || db->nDeferredImmCons>0 ); + rc = p->rc ? SQLITE_ERROR : SQLITE_DONE; + } + goto vdbe_return; +} + +/* Opcode: Integer P1 P2 * * * +** Synopsis: r[P2]=P1 +** +** The 32-bit integer value P1 is written into register P2. +*/ +case OP_Integer: { /* out2 */ + pOut = out2Prerelease(p, pOp); + pOut->u.i = pOp->p1; + break; +} + +/* Opcode: Int64 * P2 * P4 * +** Synopsis: r[P2]=P4 +** +** P4 is a pointer to a 64-bit integer value. +** Write that value into register P2. +*/ +case OP_Int64: { /* out2 */ + pOut = out2Prerelease(p, pOp); + assert( pOp->p4.pI64!=0 ); + pOut->u.i = *pOp->p4.pI64; + break; +} + +#ifndef SQLITE_OMIT_FLOATING_POINT +/* Opcode: Real * P2 * P4 * +** Synopsis: r[P2]=P4 +** +** P4 is a pointer to a 64-bit floating point value. +** Write that value into register P2. +*/ +case OP_Real: { /* same as TK_FLOAT, out2 */ + pOut = out2Prerelease(p, pOp); + pOut->flags = MEM_Real; + assert( !sqlite3IsNaN(*pOp->p4.pReal) ); + pOut->u.r = *pOp->p4.pReal; + break; +} +#endif + +/* Opcode: String8 * P2 * P4 * +** Synopsis: r[P2]='P4' +** +** P4 points to a nul terminated UTF-8 string. This opcode is transformed +** into a String opcode before it is executed for the first time. During +** this transformation, the length of string P4 is computed and stored +** as the P1 parameter. +*/ +case OP_String8: { /* same as TK_STRING, out2 */ + assert( pOp->p4.z!=0 ); + pOut = out2Prerelease(p, pOp); + pOp->p1 = sqlite3Strlen30(pOp->p4.z); + +#ifndef SQLITE_OMIT_UTF16 + if( encoding!=SQLITE_UTF8 ){ + rc = sqlite3VdbeMemSetStr(pOut, pOp->p4.z, -1, SQLITE_UTF8, SQLITE_STATIC); + assert( rc==SQLITE_OK || rc==SQLITE_TOOBIG ); + if( rc ) goto too_big; + if( SQLITE_OK!=sqlite3VdbeChangeEncoding(pOut, encoding) ) goto no_mem; + assert( pOut->szMalloc>0 && pOut->zMalloc==pOut->z ); + assert( VdbeMemDynamic(pOut)==0 ); + pOut->szMalloc = 0; + pOut->flags |= MEM_Static; + if( pOp->p4type==P4_DYNAMIC ){ + sqlite3DbFree(db, pOp->p4.z); + } + pOp->p4type = P4_DYNAMIC; + pOp->p4.z = pOut->z; + pOp->p1 = pOut->n; + } +#endif + if( pOp->p1>db->aLimit[SQLITE_LIMIT_LENGTH] ){ + goto too_big; + } + pOp->opcode = OP_String; + assert( rc==SQLITE_OK ); + /* Fall through to the next case, OP_String */ + /* no break */ deliberate_fall_through +} + +/* Opcode: String P1 P2 P3 P4 P5 +** Synopsis: r[P2]='P4' (len=P1) +** +** The string value P4 of length P1 (bytes) is stored in register P2. +** +** If P3 is not zero and the content of register P3 is equal to P5, then +** the datatype of the register P2 is converted to BLOB. The content is +** the same sequence of bytes, it is merely interpreted as a BLOB instead +** of a string, as if it had been CAST. In other words: +** +** if( P3!=0 and reg[P3]==P5 ) reg[P2] := CAST(reg[P2] as BLOB) +*/ +case OP_String: { /* out2 */ + assert( pOp->p4.z!=0 ); + pOut = out2Prerelease(p, pOp); + pOut->flags = MEM_Str|MEM_Static|MEM_Term; + pOut->z = pOp->p4.z; + pOut->n = pOp->p1; + pOut->enc = encoding; + UPDATE_MAX_BLOBSIZE(pOut); +#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS + if( pOp->p3>0 ){ + assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); + pIn3 = &aMem[pOp->p3]; + assert( pIn3->flags & MEM_Int ); + if( pIn3->u.i==pOp->p5 ) pOut->flags = MEM_Blob|MEM_Static|MEM_Term; + } +#endif + break; +} + +/* Opcode: BeginSubrtn * P2 * * * +** Synopsis: r[P2]=NULL +** +** Mark the beginning of a subroutine that can be entered in-line +** or that can be called using OP_Gosub. The subroutine should +** be terminated by an OP_Return instruction that has a P1 operand that +** is the same as the P2 operand to this opcode and that has P3 set to 1. +** If the subroutine is entered in-line, then the OP_Return will simply +** fall through. But if the subroutine is entered using OP_Gosub, then +** the OP_Return will jump back to the first instruction after the OP_Gosub. +** +** This routine works by loading a NULL into the P2 register. When the +** return address register contains a NULL, the OP_Return instruction is +** a no-op that simply falls through to the next instruction (assuming that +** the OP_Return opcode has a P3 value of 1). Thus if the subroutine is +** entered in-line, then the OP_Return will cause in-line execution to +** continue. But if the subroutine is entered via OP_Gosub, then the +** OP_Return will cause a return to the address following the OP_Gosub. +** +** This opcode is identical to OP_Null. It has a different name +** only to make the byte code easier to read and verify. +*/ +/* Opcode: Null P1 P2 P3 * * +** Synopsis: r[P2..P3]=NULL +** +** Write a NULL into registers P2. If P3 greater than P2, then also write +** NULL into register P3 and every register in between P2 and P3. If P3 +** is less than P2 (typically P3 is zero) then only register P2 is +** set to NULL. +** +** If the P1 value is non-zero, then also set the MEM_Cleared flag so that +** NULL values will not compare equal even if SQLITE_NULLEQ is set on +** OP_Ne or OP_Eq. +*/ +case OP_BeginSubrtn: +case OP_Null: { /* out2 */ + int cnt; + u16 nullFlag; + pOut = out2Prerelease(p, pOp); + cnt = pOp->p3-pOp->p2; + assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); + pOut->flags = nullFlag = pOp->p1 ? (MEM_Null|MEM_Cleared) : MEM_Null; + pOut->n = 0; +#ifdef SQLITE_DEBUG + pOut->uTemp = 0; +#endif + while( cnt>0 ){ + pOut++; + memAboutToChange(p, pOut); + sqlite3VdbeMemSetNull(pOut); + pOut->flags = nullFlag; + pOut->n = 0; + cnt--; + } + break; +} + +/* Opcode: SoftNull P1 * * * * +** Synopsis: r[P1]=NULL +** +** Set register P1 to have the value NULL as seen by the OP_MakeRecord +** instruction, but do not free any string or blob memory associated with +** the register, so that if the value was a string or blob that was +** previously copied using OP_SCopy, the copies will continue to be valid. +*/ +case OP_SoftNull: { + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + pOut = &aMem[pOp->p1]; + pOut->flags = (pOut->flags&~(MEM_Undefined|MEM_AffMask))|MEM_Null; + break; +} + +/* Opcode: Blob P1 P2 * P4 * +** Synopsis: r[P2]=P4 (len=P1) +** +** P4 points to a blob of data P1 bytes long. Store this +** blob in register P2. If P4 is a NULL pointer, then construct +** a zero-filled blob that is P1 bytes long in P2. +*/ +case OP_Blob: { /* out2 */ + assert( pOp->p1 <= SQLITE_MAX_LENGTH ); + pOut = out2Prerelease(p, pOp); + if( pOp->p4.z==0 ){ + sqlite3VdbeMemSetZeroBlob(pOut, pOp->p1); + if( sqlite3VdbeMemExpandBlob(pOut) ) goto no_mem; + }else{ + sqlite3VdbeMemSetStr(pOut, pOp->p4.z, pOp->p1, 0, 0); + } + pOut->enc = encoding; + UPDATE_MAX_BLOBSIZE(pOut); + break; +} + +/* Opcode: Variable P1 P2 * P4 * +** Synopsis: r[P2]=parameter(P1,P4) +** +** Transfer the values of bound parameter P1 into register P2 +** +** If the parameter is named, then its name appears in P4. +** The P4 value is used by sqlite3_bind_parameter_name(). +*/ +case OP_Variable: { /* out2 */ + Mem *pVar; /* Value being transferred */ + + assert( pOp->p1>0 && pOp->p1<=p->nVar ); + assert( pOp->p4.z==0 || pOp->p4.z==sqlite3VListNumToName(p->pVList,pOp->p1) ); + pVar = &p->aVar[pOp->p1 - 1]; + if( sqlite3VdbeMemTooBig(pVar) ){ + goto too_big; + } + pOut = &aMem[pOp->p2]; + if( VdbeMemDynamic(pOut) ) sqlite3VdbeMemSetNull(pOut); + memcpy(pOut, pVar, MEMCELLSIZE); + pOut->flags &= ~(MEM_Dyn|MEM_Ephem); + pOut->flags |= MEM_Static|MEM_FromBind; + UPDATE_MAX_BLOBSIZE(pOut); + break; +} + +/* Opcode: Move P1 P2 P3 * * +** Synopsis: r[P2@P3]=r[P1@P3] +** +** Move the P3 values in register P1..P1+P3-1 over into +** registers P2..P2+P3-1. Registers P1..P1+P3-1 are +** left holding a NULL. It is an error for register ranges +** P1..P1+P3-1 and P2..P2+P3-1 to overlap. It is an error +** for P3 to be less than 1. +*/ +case OP_Move: { + int n; /* Number of registers left to copy */ + int p1; /* Register to copy from */ + int p2; /* Register to copy to */ + + n = pOp->p3; + p1 = pOp->p1; + p2 = pOp->p2; + assert( n>0 && p1>0 && p2>0 ); + assert( p1+n<=p2 || p2+n<=p1 ); + + pIn1 = &aMem[p1]; + pOut = &aMem[p2]; + do{ + assert( pOut<=&aMem[(p->nMem+1 - p->nCursor)] ); + assert( pIn1<=&aMem[(p->nMem+1 - p->nCursor)] ); + assert( memIsValid(pIn1) ); + memAboutToChange(p, pOut); + sqlite3VdbeMemMove(pOut, pIn1); +#ifdef SQLITE_DEBUG + pIn1->pScopyFrom = 0; + { int i; + for(i=1; i<p->nMem; i++){ + if( aMem[i].pScopyFrom==pIn1 ){ + aMem[i].pScopyFrom = pOut; + } + } + } +#endif + Deephemeralize(pOut); + REGISTER_TRACE(p2++, pOut); + pIn1++; + pOut++; + }while( --n ); + break; +} + +/* Opcode: Copy P1 P2 P3 * P5 +** Synopsis: r[P2@P3+1]=r[P1@P3+1] +** +** Make a copy of registers P1..P1+P3 into registers P2..P2+P3. +** +** If the 0x0002 bit of P5 is set then also clear the MEM_Subtype flag in the +** destination. The 0x0001 bit of P5 indicates that this Copy opcode cannot +** be merged. The 0x0001 bit is used by the query planner and does not +** come into play during query execution. +** +** This instruction makes a deep copy of the value. A duplicate +** is made of any string or blob constant. See also OP_SCopy. +*/ +case OP_Copy: { + int n; + + n = pOp->p3; + pIn1 = &aMem[pOp->p1]; + pOut = &aMem[pOp->p2]; + assert( pOut!=pIn1 ); + while( 1 ){ + memAboutToChange(p, pOut); + sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem); + Deephemeralize(pOut); + if( (pOut->flags & MEM_Subtype)!=0 && (pOp->p5 & 0x0002)!=0 ){ + pOut->flags &= ~MEM_Subtype; + } +#ifdef SQLITE_DEBUG + pOut->pScopyFrom = 0; +#endif + REGISTER_TRACE(pOp->p2+pOp->p3-n, pOut); + if( (n--)==0 ) break; + pOut++; + pIn1++; + } + break; +} + +/* Opcode: SCopy P1 P2 * * * +** Synopsis: r[P2]=r[P1] +** +** Make a shallow copy of register P1 into register P2. +** +** This instruction makes a shallow copy of the value. If the value +** is a string or blob, then the copy is only a pointer to the +** original and hence if the original changes so will the copy. +** Worse, if the original is deallocated, the copy becomes invalid. +** Thus the program must guarantee that the original will not change +** during the lifetime of the copy. Use OP_Copy to make a complete +** copy. +*/ +case OP_SCopy: { /* out2 */ + pIn1 = &aMem[pOp->p1]; + pOut = &aMem[pOp->p2]; + assert( pOut!=pIn1 ); + sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem); +#ifdef SQLITE_DEBUG + pOut->pScopyFrom = pIn1; + pOut->mScopyFlags = pIn1->flags; +#endif + break; +} + +/* Opcode: IntCopy P1 P2 * * * +** Synopsis: r[P2]=r[P1] +** +** Transfer the integer value held in register P1 into register P2. +** +** This is an optimized version of SCopy that works only for integer +** values. +*/ +case OP_IntCopy: { /* out2 */ + pIn1 = &aMem[pOp->p1]; + assert( (pIn1->flags & MEM_Int)!=0 ); + pOut = &aMem[pOp->p2]; + sqlite3VdbeMemSetInt64(pOut, pIn1->u.i); + break; +} + +/* Opcode: FkCheck * * * * * +** +** Halt with an SQLITE_CONSTRAINT error if there are any unresolved +** foreign key constraint violations. If there are no foreign key +** constraint violations, this is a no-op. +** +** FK constraint violations are also checked when the prepared statement +** exits. This opcode is used to raise foreign key constraint errors prior +** to returning results such as a row change count or the result of a +** RETURNING clause. +*/ +case OP_FkCheck: { + if( (rc = sqlite3VdbeCheckFk(p,0))!=SQLITE_OK ){ + goto abort_due_to_error; + } + break; +} + +/* Opcode: ResultRow P1 P2 * * * +** Synopsis: output=r[P1@P2] +** +** The registers P1 through P1+P2-1 contain a single row of +** results. This opcode causes the sqlite3_step() call to terminate +** with an SQLITE_ROW return code and it sets up the sqlite3_stmt +** structure to provide access to the r(P1)..r(P1+P2-1) values as +** the result row. +*/ +case OP_ResultRow: { + assert( p->nResColumn==pOp->p2 ); + assert( pOp->p1>0 || CORRUPT_DB ); + assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); + + p->cacheCtr = (p->cacheCtr + 2)|1; + p->pResultRow = &aMem[pOp->p1]; +#ifdef SQLITE_DEBUG + { + Mem *pMem = p->pResultRow; + int i; + for(i=0; i<pOp->p2; i++){ + assert( memIsValid(&pMem[i]) ); + REGISTER_TRACE(pOp->p1+i, &pMem[i]); + /* The registers in the result will not be used again when the + ** prepared statement restarts. This is because sqlite3_column() + ** APIs might have caused type conversions of made other changes to + ** the register values. Therefore, we can go ahead and break any + ** OP_SCopy dependencies. */ + pMem[i].pScopyFrom = 0; + } + } +#endif + if( db->mallocFailed ) goto no_mem; + if( db->mTrace & SQLITE_TRACE_ROW ){ + db->trace.xV2(SQLITE_TRACE_ROW, db->pTraceArg, p, 0); + } + p->pc = (int)(pOp - aOp) + 1; + rc = SQLITE_ROW; + goto vdbe_return; +} + +/* Opcode: Concat P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]+r[P1] +** +** Add the text in register P1 onto the end of the text in +** register P2 and store the result in register P3. +** If either the P1 or P2 text are NULL then store NULL in P3. +** +** P3 = P2 || P1 +** +** It is illegal for P1 and P3 to be the same register. Sometimes, +** if P3 is the same register as P2, the implementation is able +** to avoid a memcpy(). +*/ +case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ + i64 nByte; /* Total size of the output string or blob */ + u16 flags1; /* Initial flags for P1 */ + u16 flags2; /* Initial flags for P2 */ + + pIn1 = &aMem[pOp->p1]; + pIn2 = &aMem[pOp->p2]; + pOut = &aMem[pOp->p3]; + testcase( pOut==pIn2 ); + assert( pIn1!=pOut ); + flags1 = pIn1->flags; + testcase( flags1 & MEM_Null ); + testcase( pIn2->flags & MEM_Null ); + if( (flags1 | pIn2->flags) & MEM_Null ){ + sqlite3VdbeMemSetNull(pOut); + break; + } + if( (flags1 & (MEM_Str|MEM_Blob))==0 ){ + if( sqlite3VdbeMemStringify(pIn1,encoding,0) ) goto no_mem; + flags1 = pIn1->flags & ~MEM_Str; + }else if( (flags1 & MEM_Zero)!=0 ){ + if( sqlite3VdbeMemExpandBlob(pIn1) ) goto no_mem; + flags1 = pIn1->flags & ~MEM_Str; + } + flags2 = pIn2->flags; + if( (flags2 & (MEM_Str|MEM_Blob))==0 ){ + if( sqlite3VdbeMemStringify(pIn2,encoding,0) ) goto no_mem; + flags2 = pIn2->flags & ~MEM_Str; + }else if( (flags2 & MEM_Zero)!=0 ){ + if( sqlite3VdbeMemExpandBlob(pIn2) ) goto no_mem; + flags2 = pIn2->flags & ~MEM_Str; + } + nByte = pIn1->n + pIn2->n; + if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ + goto too_big; + } + if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){ + goto no_mem; + } + MemSetTypeFlag(pOut, MEM_Str); + if( pOut!=pIn2 ){ + memcpy(pOut->z, pIn2->z, pIn2->n); + assert( (pIn2->flags & MEM_Dyn) == (flags2 & MEM_Dyn) ); + pIn2->flags = flags2; + } + memcpy(&pOut->z[pIn2->n], pIn1->z, pIn1->n); + assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); + pIn1->flags = flags1; + if( encoding>SQLITE_UTF8 ) nByte &= ~1; + pOut->z[nByte]=0; + pOut->z[nByte+1] = 0; + pOut->flags |= MEM_Term; + pOut->n = (int)nByte; + pOut->enc = encoding; + UPDATE_MAX_BLOBSIZE(pOut); + break; +} + +/* Opcode: Add P1 P2 P3 * * +** Synopsis: r[P3]=r[P1]+r[P2] +** +** Add the value in register P1 to the value in register P2 +** and store the result in register P3. +** If either input is NULL, the result is NULL. +*/ +/* Opcode: Multiply P1 P2 P3 * * +** Synopsis: r[P3]=r[P1]*r[P2] +** +** +** Multiply the value in register P1 by the value in register P2 +** and store the result in register P3. +** If either input is NULL, the result is NULL. +*/ +/* Opcode: Subtract P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]-r[P1] +** +** Subtract the value in register P1 from the value in register P2 +** and store the result in register P3. +** If either input is NULL, the result is NULL. +*/ +/* Opcode: Divide P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]/r[P1] +** +** Divide the value in register P1 by the value in register P2 +** and store the result in register P3 (P3=P2/P1). If the value in +** register P1 is zero, then the result is NULL. If either input is +** NULL, the result is NULL. +*/ +/* Opcode: Remainder P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]%r[P1] +** +** Compute the remainder after integer register P2 is divided by +** register P1 and store the result in register P3. +** If the value in register P1 is zero the result is NULL. +** If either operand is NULL, the result is NULL. +*/ +case OP_Add: /* same as TK_PLUS, in1, in2, out3 */ +case OP_Subtract: /* same as TK_MINUS, in1, in2, out3 */ +case OP_Multiply: /* same as TK_STAR, in1, in2, out3 */ +case OP_Divide: /* same as TK_SLASH, in1, in2, out3 */ +case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ + u16 type1; /* Numeric type of left operand */ + u16 type2; /* Numeric type of right operand */ + i64 iA; /* Integer value of left operand */ + i64 iB; /* Integer value of right operand */ + double rA; /* Real value of left operand */ + double rB; /* Real value of right operand */ + + pIn1 = &aMem[pOp->p1]; + type1 = pIn1->flags; + pIn2 = &aMem[pOp->p2]; + type2 = pIn2->flags; + pOut = &aMem[pOp->p3]; + if( (type1 & type2 & MEM_Int)!=0 ){ +int_math: + iA = pIn1->u.i; + iB = pIn2->u.i; + switch( pOp->opcode ){ + case OP_Add: if( sqlite3AddInt64(&iB,iA) ) goto fp_math; break; + case OP_Subtract: if( sqlite3SubInt64(&iB,iA) ) goto fp_math; break; + case OP_Multiply: if( sqlite3MulInt64(&iB,iA) ) goto fp_math; break; + case OP_Divide: { + if( iA==0 ) goto arithmetic_result_is_null; + if( iA==-1 && iB==SMALLEST_INT64 ) goto fp_math; + iB /= iA; + break; + } + default: { + if( iA==0 ) goto arithmetic_result_is_null; + if( iA==-1 ) iA = 1; + iB %= iA; + break; + } + } + pOut->u.i = iB; + MemSetTypeFlag(pOut, MEM_Int); + }else if( ((type1 | type2) & MEM_Null)!=0 ){ + goto arithmetic_result_is_null; + }else{ + type1 = numericType(pIn1); + type2 = numericType(pIn2); + if( (type1 & type2 & MEM_Int)!=0 ) goto int_math; +fp_math: + rA = sqlite3VdbeRealValue(pIn1); + rB = sqlite3VdbeRealValue(pIn2); + switch( pOp->opcode ){ + case OP_Add: rB += rA; break; + case OP_Subtract: rB -= rA; break; + case OP_Multiply: rB *= rA; break; + case OP_Divide: { + /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ + if( rA==(double)0 ) goto arithmetic_result_is_null; + rB /= rA; + break; + } + default: { + iA = sqlite3VdbeIntValue(pIn1); + iB = sqlite3VdbeIntValue(pIn2); + if( iA==0 ) goto arithmetic_result_is_null; + if( iA==-1 ) iA = 1; + rB = (double)(iB % iA); + break; + } + } +#ifdef SQLITE_OMIT_FLOATING_POINT + pOut->u.i = rB; + MemSetTypeFlag(pOut, MEM_Int); +#else + if( sqlite3IsNaN(rB) ){ + goto arithmetic_result_is_null; + } + pOut->u.r = rB; + MemSetTypeFlag(pOut, MEM_Real); +#endif + } + break; + +arithmetic_result_is_null: + sqlite3VdbeMemSetNull(pOut); + break; +} + +/* Opcode: CollSeq P1 * * P4 +** +** P4 is a pointer to a CollSeq object. If the next call to a user function +** or aggregate calls sqlite3GetFuncCollSeq(), this collation sequence will +** be returned. This is used by the built-in min(), max() and nullif() +** functions. +** +** If P1 is not zero, then it is a register that a subsequent min() or +** max() aggregate will set to 1 if the current row is not the minimum or +** maximum. The P1 register is initialized to 0 by this instruction. +** +** The interface used by the implementation of the aforementioned functions +** to retrieve the collation sequence set by this opcode is not available +** publicly. Only built-in functions have access to this feature. +*/ +case OP_CollSeq: { + assert( pOp->p4type==P4_COLLSEQ ); + if( pOp->p1 ){ + sqlite3VdbeMemSetInt64(&aMem[pOp->p1], 0); + } + break; +} + +/* Opcode: BitAnd P1 P2 P3 * * +** Synopsis: r[P3]=r[P1]&r[P2] +** +** Take the bit-wise AND of the values in register P1 and P2 and +** store the result in register P3. +** If either input is NULL, the result is NULL. +*/ +/* Opcode: BitOr P1 P2 P3 * * +** Synopsis: r[P3]=r[P1]|r[P2] +** +** Take the bit-wise OR of the values in register P1 and P2 and +** store the result in register P3. +** If either input is NULL, the result is NULL. +*/ +/* Opcode: ShiftLeft P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]<<r[P1] +** +** Shift the integer value in register P2 to the left by the +** number of bits specified by the integer in register P1. +** Store the result in register P3. +** If either input is NULL, the result is NULL. +*/ +/* Opcode: ShiftRight P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]>>r[P1] +** +** Shift the integer value in register P2 to the right by the +** number of bits specified by the integer in register P1. +** Store the result in register P3. +** If either input is NULL, the result is NULL. +*/ +case OP_BitAnd: /* same as TK_BITAND, in1, in2, out3 */ +case OP_BitOr: /* same as TK_BITOR, in1, in2, out3 */ +case OP_ShiftLeft: /* same as TK_LSHIFT, in1, in2, out3 */ +case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */ + i64 iA; + u64 uA; + i64 iB; + u8 op; + + pIn1 = &aMem[pOp->p1]; + pIn2 = &aMem[pOp->p2]; + pOut = &aMem[pOp->p3]; + if( (pIn1->flags | pIn2->flags) & MEM_Null ){ + sqlite3VdbeMemSetNull(pOut); + break; + } + iA = sqlite3VdbeIntValue(pIn2); + iB = sqlite3VdbeIntValue(pIn1); + op = pOp->opcode; + if( op==OP_BitAnd ){ + iA &= iB; + }else if( op==OP_BitOr ){ + iA |= iB; + }else if( iB!=0 ){ + assert( op==OP_ShiftRight || op==OP_ShiftLeft ); + + /* If shifting by a negative amount, shift in the other direction */ + if( iB<0 ){ + assert( OP_ShiftRight==OP_ShiftLeft+1 ); + op = 2*OP_ShiftLeft + 1 - op; + iB = iB>(-64) ? -iB : 64; + } + + if( iB>=64 ){ + iA = (iA>=0 || op==OP_ShiftLeft) ? 0 : -1; + }else{ + memcpy(&uA, &iA, sizeof(uA)); + if( op==OP_ShiftLeft ){ + uA <<= iB; + }else{ + uA >>= iB; + /* Sign-extend on a right shift of a negative number */ + if( iA<0 ) uA |= ((((u64)0xffffffff)<<32)|0xffffffff) << (64-iB); + } + memcpy(&iA, &uA, sizeof(iA)); + } + } + pOut->u.i = iA; + MemSetTypeFlag(pOut, MEM_Int); + break; +} + +/* Opcode: AddImm P1 P2 * * * +** Synopsis: r[P1]=r[P1]+P2 +** +** Add the constant P2 to the value in register P1. +** The result is always an integer. +** +** To force any register to be an integer, just add 0. +*/ +case OP_AddImm: { /* in1 */ + pIn1 = &aMem[pOp->p1]; + memAboutToChange(p, pIn1); + sqlite3VdbeMemIntegerify(pIn1); + pIn1->u.i += pOp->p2; + break; +} + +/* Opcode: MustBeInt P1 P2 * * * +** +** Force the value in register P1 to be an integer. If the value +** in P1 is not an integer and cannot be converted into an integer +** without data loss, then jump immediately to P2, or if P2==0 +** raise an SQLITE_MISMATCH exception. +*/ +case OP_MustBeInt: { /* jump, in1 */ + pIn1 = &aMem[pOp->p1]; + if( (pIn1->flags & MEM_Int)==0 ){ + applyAffinity(pIn1, SQLITE_AFF_NUMERIC, encoding); + if( (pIn1->flags & MEM_Int)==0 ){ + VdbeBranchTaken(1, 2); + if( pOp->p2==0 ){ + rc = SQLITE_MISMATCH; + goto abort_due_to_error; + }else{ + goto jump_to_p2; + } + } + } + VdbeBranchTaken(0, 2); + MemSetTypeFlag(pIn1, MEM_Int); + break; +} + +#ifndef SQLITE_OMIT_FLOATING_POINT +/* Opcode: RealAffinity P1 * * * * +** +** If register P1 holds an integer convert it to a real value. +** +** This opcode is used when extracting information from a column that +** has REAL affinity. Such column values may still be stored as +** integers, for space efficiency, but after extraction we want them +** to have only a real value. +*/ +case OP_RealAffinity: { /* in1 */ + pIn1 = &aMem[pOp->p1]; + if( pIn1->flags & (MEM_Int|MEM_IntReal) ){ + testcase( pIn1->flags & MEM_Int ); + testcase( pIn1->flags & MEM_IntReal ); + sqlite3VdbeMemRealify(pIn1); + REGISTER_TRACE(pOp->p1, pIn1); + } + break; +} +#endif + +#ifndef SQLITE_OMIT_CAST +/* Opcode: Cast P1 P2 * * * +** Synopsis: affinity(r[P1]) +** +** Force the value in register P1 to be the type defined by P2. +** +** <ul> +** <li> P2=='A' &rarr; BLOB +** <li> P2=='B' &rarr; TEXT +** <li> P2=='C' &rarr; NUMERIC +** <li> P2=='D' &rarr; INTEGER +** <li> P2=='E' &rarr; REAL +** </ul> +** +** A NULL value is not changed by this routine. It remains NULL. +*/ +case OP_Cast: { /* in1 */ + assert( pOp->p2>=SQLITE_AFF_BLOB && pOp->p2<=SQLITE_AFF_REAL ); + testcase( pOp->p2==SQLITE_AFF_TEXT ); + testcase( pOp->p2==SQLITE_AFF_BLOB ); + testcase( pOp->p2==SQLITE_AFF_NUMERIC ); + testcase( pOp->p2==SQLITE_AFF_INTEGER ); + testcase( pOp->p2==SQLITE_AFF_REAL ); + pIn1 = &aMem[pOp->p1]; + memAboutToChange(p, pIn1); + rc = ExpandBlob(pIn1); + if( rc ) goto abort_due_to_error; + rc = sqlite3VdbeMemCast(pIn1, pOp->p2, encoding); + if( rc ) goto abort_due_to_error; + UPDATE_MAX_BLOBSIZE(pIn1); + REGISTER_TRACE(pOp->p1, pIn1); + break; +} +#endif /* SQLITE_OMIT_CAST */ + +/* Opcode: Eq P1 P2 P3 P4 P5 +** Synopsis: IF r[P3]==r[P1] +** +** Compare the values in register P1 and P3. If reg(P3)==reg(P1) then +** jump to address P2. +** +** The SQLITE_AFF_MASK portion of P5 must be an affinity character - +** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made +** to coerce both inputs according to this affinity before the +** comparison is made. If the SQLITE_AFF_MASK is 0x00, then numeric +** affinity is used. Note that the affinity conversions are stored +** back into the input registers P1 and P3. So this opcode can cause +** persistent changes to registers P1 and P3. +** +** Once any conversions have taken place, and neither value is NULL, +** the values are compared. If both values are blobs then memcmp() is +** used to determine the results of the comparison. If both values +** are text, then the appropriate collating function specified in +** P4 is used to do the comparison. If P4 is not specified then +** memcmp() is used to compare text string. If both values are +** numeric, then a numeric comparison is used. If the two values +** are of different types, then numbers are considered less than +** strings and strings are considered less than blobs. +** +** If SQLITE_NULLEQ is set in P5 then the result of comparison is always either +** true or false and is never NULL. If both operands are NULL then the result +** of comparison is true. If either operand is NULL then the result is false. +** If neither operand is NULL the result is the same as it would be if +** the SQLITE_NULLEQ flag were omitted from P5. +** +** This opcode saves the result of comparison for use by the new +** OP_Jump opcode. +*/ +/* Opcode: Ne P1 P2 P3 P4 P5 +** Synopsis: IF r[P3]!=r[P1] +** +** This works just like the Eq opcode except that the jump is taken if +** the operands in registers P1 and P3 are not equal. See the Eq opcode for +** additional information. +*/ +/* Opcode: Lt P1 P2 P3 P4 P5 +** Synopsis: IF r[P3]<r[P1] +** +** Compare the values in register P1 and P3. If reg(P3)<reg(P1) then +** jump to address P2. +** +** If the SQLITE_JUMPIFNULL bit of P5 is set and either reg(P1) or +** reg(P3) is NULL then the take the jump. If the SQLITE_JUMPIFNULL +** bit is clear then fall through if either operand is NULL. +** +** The SQLITE_AFF_MASK portion of P5 must be an affinity character - +** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made +** to coerce both inputs according to this affinity before the +** comparison is made. If the SQLITE_AFF_MASK is 0x00, then numeric +** affinity is used. Note that the affinity conversions are stored +** back into the input registers P1 and P3. So this opcode can cause +** persistent changes to registers P1 and P3. +** +** Once any conversions have taken place, and neither value is NULL, +** the values are compared. If both values are blobs then memcmp() is +** used to determine the results of the comparison. If both values +** are text, then the appropriate collating function specified in +** P4 is used to do the comparison. If P4 is not specified then +** memcmp() is used to compare text string. If both values are +** numeric, then a numeric comparison is used. If the two values +** are of different types, then numbers are considered less than +** strings and strings are considered less than blobs. +** +** This opcode saves the result of comparison for use by the new +** OP_Jump opcode. +*/ +/* Opcode: Le P1 P2 P3 P4 P5 +** Synopsis: IF r[P3]<=r[P1] +** +** This works just like the Lt opcode except that the jump is taken if +** the content of register P3 is less than or equal to the content of +** register P1. See the Lt opcode for additional information. +*/ +/* Opcode: Gt P1 P2 P3 P4 P5 +** Synopsis: IF r[P3]>r[P1] +** +** This works just like the Lt opcode except that the jump is taken if +** the content of register P3 is greater than the content of +** register P1. See the Lt opcode for additional information. +*/ +/* Opcode: Ge P1 P2 P3 P4 P5 +** Synopsis: IF r[P3]>=r[P1] +** +** This works just like the Lt opcode except that the jump is taken if +** the content of register P3 is greater than or equal to the content of +** register P1. See the Lt opcode for additional information. +*/ +case OP_Eq: /* same as TK_EQ, jump, in1, in3 */ +case OP_Ne: /* same as TK_NE, jump, in1, in3 */ +case OP_Lt: /* same as TK_LT, jump, in1, in3 */ +case OP_Le: /* same as TK_LE, jump, in1, in3 */ +case OP_Gt: /* same as TK_GT, jump, in1, in3 */ +case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ + int res, res2; /* Result of the comparison of pIn1 against pIn3 */ + char affinity; /* Affinity to use for comparison */ + u16 flags1; /* Copy of initial value of pIn1->flags */ + u16 flags3; /* Copy of initial value of pIn3->flags */ + + pIn1 = &aMem[pOp->p1]; + pIn3 = &aMem[pOp->p3]; + flags1 = pIn1->flags; + flags3 = pIn3->flags; + if( (flags1 & flags3 & MEM_Int)!=0 ){ + /* Common case of comparison of two integers */ + if( pIn3->u.i > pIn1->u.i ){ + if( sqlite3aGTb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + iCompare = +1; + VVA_ONLY( iCompareIsInit = 1; ) + }else if( pIn3->u.i < pIn1->u.i ){ + if( sqlite3aLTb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + iCompare = -1; + VVA_ONLY( iCompareIsInit = 1; ) + }else{ + if( sqlite3aEQb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + iCompare = 0; + VVA_ONLY( iCompareIsInit = 1; ) + } + VdbeBranchTaken(0, (pOp->p5 & SQLITE_NULLEQ)?2:3); + break; + } + if( (flags1 | flags3)&MEM_Null ){ + /* One or both operands are NULL */ + if( pOp->p5 & SQLITE_NULLEQ ){ + /* If SQLITE_NULLEQ is set (which will only happen if the operator is + ** OP_Eq or OP_Ne) then take the jump or not depending on whether + ** or not both operands are null. + */ + assert( (flags1 & MEM_Cleared)==0 ); + assert( (pOp->p5 & SQLITE_JUMPIFNULL)==0 || CORRUPT_DB ); + testcase( (pOp->p5 & SQLITE_JUMPIFNULL)!=0 ); + if( (flags1&flags3&MEM_Null)!=0 + && (flags3&MEM_Cleared)==0 + ){ + res = 0; /* Operands are equal */ + }else{ + res = ((flags3 & MEM_Null) ? -1 : +1); /* Operands are not equal */ + } + }else{ + /* SQLITE_NULLEQ is clear and at least one operand is NULL, + ** then the result is always NULL. + ** The jump is taken if the SQLITE_JUMPIFNULL bit is set. + */ + VdbeBranchTaken(2,3); + if( pOp->p5 & SQLITE_JUMPIFNULL ){ + goto jump_to_p2; + } + iCompare = 1; /* Operands are not equal */ + VVA_ONLY( iCompareIsInit = 1; ) + break; + } + }else{ + /* Neither operand is NULL and we couldn't do the special high-speed + ** integer comparison case. So do a general-case comparison. */ + affinity = pOp->p5 & SQLITE_AFF_MASK; + if( affinity>=SQLITE_AFF_NUMERIC ){ + if( (flags1 | flags3)&MEM_Str ){ + if( (flags1 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){ + applyNumericAffinity(pIn1,0); + assert( flags3==pIn3->flags || CORRUPT_DB ); + flags3 = pIn3->flags; + } + if( (flags3 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){ + applyNumericAffinity(pIn3,0); + } + } + }else if( affinity==SQLITE_AFF_TEXT && ((flags1 | flags3) & MEM_Str)!=0 ){ + if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ + testcase( pIn1->flags & MEM_Int ); + testcase( pIn1->flags & MEM_Real ); + testcase( pIn1->flags & MEM_IntReal ); + sqlite3VdbeMemStringify(pIn1, encoding, 1); + testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) ); + flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); + if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str; + } + if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ + testcase( pIn3->flags & MEM_Int ); + testcase( pIn3->flags & MEM_Real ); + testcase( pIn3->flags & MEM_IntReal ); + sqlite3VdbeMemStringify(pIn3, encoding, 1); + testcase( (flags3&MEM_Dyn) != (pIn3->flags&MEM_Dyn) ); + flags3 = (pIn3->flags & ~MEM_TypeMask) | (flags3 & MEM_TypeMask); + } + } + assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 ); + res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl); + } + + /* At this point, res is negative, zero, or positive if reg[P1] is + ** less than, equal to, or greater than reg[P3], respectively. Compute + ** the answer to this operator in res2, depending on what the comparison + ** operator actually is. The next block of code depends on the fact + ** that the 6 comparison operators are consecutive integers in this + ** order: NE, EQ, GT, LE, LT, GE */ + assert( OP_Eq==OP_Ne+1 ); assert( OP_Gt==OP_Ne+2 ); assert( OP_Le==OP_Ne+3 ); + assert( OP_Lt==OP_Ne+4 ); assert( OP_Ge==OP_Ne+5 ); + if( res<0 ){ + res2 = sqlite3aLTb[pOp->opcode]; + }else if( res==0 ){ + res2 = sqlite3aEQb[pOp->opcode]; + }else{ + res2 = sqlite3aGTb[pOp->opcode]; + } + iCompare = res; + VVA_ONLY( iCompareIsInit = 1; ) + + /* Undo any changes made by applyAffinity() to the input registers. */ + assert( (pIn3->flags & MEM_Dyn) == (flags3 & MEM_Dyn) ); + pIn3->flags = flags3; + assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); + pIn1->flags = flags1; + + VdbeBranchTaken(res2!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); + if( res2 ){ + goto jump_to_p2; + } + break; +} + +/* Opcode: ElseEq * P2 * * * +** +** This opcode must follow an OP_Lt or OP_Gt comparison operator. There +** can be zero or more OP_ReleaseReg opcodes intervening, but no other +** opcodes are allowed to occur between this instruction and the previous +** OP_Lt or OP_Gt. +** +** If the result of an OP_Eq comparison on the same two operands as +** the prior OP_Lt or OP_Gt would have been true, then jump to P2. If +** the result of an OP_Eq comparison on the two previous operands +** would have been false or NULL, then fall through. +*/ +case OP_ElseEq: { /* same as TK_ESCAPE, jump */ + +#ifdef SQLITE_DEBUG + /* Verify the preconditions of this opcode - that it follows an OP_Lt or + ** OP_Gt with zero or more intervening OP_ReleaseReg opcodes */ + int iAddr; + for(iAddr = (int)(pOp - aOp) - 1; ALWAYS(iAddr>=0); iAddr--){ + if( aOp[iAddr].opcode==OP_ReleaseReg ) continue; + assert( aOp[iAddr].opcode==OP_Lt || aOp[iAddr].opcode==OP_Gt ); + break; + } +#endif /* SQLITE_DEBUG */ + assert( iCompareIsInit ); + VdbeBranchTaken(iCompare==0, 2); + if( iCompare==0 ) goto jump_to_p2; + break; +} + + +/* Opcode: Permutation * * * P4 * +** +** Set the permutation used by the OP_Compare operator in the next +** instruction. The permutation is stored in the P4 operand. +** +** The permutation is only valid for the next opcode which must be +** an OP_Compare that has the OPFLAG_PERMUTE bit set in P5. +** +** The first integer in the P4 integer array is the length of the array +** and does not become part of the permutation. +*/ +case OP_Permutation: { + assert( pOp->p4type==P4_INTARRAY ); + assert( pOp->p4.ai ); + assert( pOp[1].opcode==OP_Compare ); + assert( pOp[1].p5 & OPFLAG_PERMUTE ); + break; +} + +/* Opcode: Compare P1 P2 P3 P4 P5 +** Synopsis: r[P1@P3] <-> r[P2@P3] +** +** Compare two vectors of registers in reg(P1)..reg(P1+P3-1) (call this +** vector "A") and in reg(P2)..reg(P2+P3-1) ("B"). Save the result of +** the comparison for use by the next OP_Jump instruct. +** +** If P5 has the OPFLAG_PERMUTE bit set, then the order of comparison is +** determined by the most recent OP_Permutation operator. If the +** OPFLAG_PERMUTE bit is clear, then register are compared in sequential +** order. +** +** P4 is a KeyInfo structure that defines collating sequences and sort +** orders for the comparison. The permutation applies to registers +** only. The KeyInfo elements are used sequentially. +** +** The comparison is a sort comparison, so NULLs compare equal, +** NULLs are less than numbers, numbers are less than strings, +** and strings are less than blobs. +** +** This opcode must be immediately followed by an OP_Jump opcode. +*/ +case OP_Compare: { + int n; + int i; + int p1; + int p2; + const KeyInfo *pKeyInfo; + u32 idx; + CollSeq *pColl; /* Collating sequence to use on this term */ + int bRev; /* True for DESCENDING sort order */ + u32 *aPermute; /* The permutation */ + + if( (pOp->p5 & OPFLAG_PERMUTE)==0 ){ + aPermute = 0; + }else{ + assert( pOp>aOp ); + assert( pOp[-1].opcode==OP_Permutation ); + assert( pOp[-1].p4type==P4_INTARRAY ); + aPermute = pOp[-1].p4.ai + 1; + assert( aPermute!=0 ); + } + n = pOp->p3; + pKeyInfo = pOp->p4.pKeyInfo; + assert( n>0 ); + assert( pKeyInfo!=0 ); + p1 = pOp->p1; + p2 = pOp->p2; +#ifdef SQLITE_DEBUG + if( aPermute ){ + int k, mx = 0; + for(k=0; k<n; k++) if( aPermute[k]>(u32)mx ) mx = aPermute[k]; + assert( p1>0 && p1+mx<=(p->nMem+1 - p->nCursor)+1 ); + assert( p2>0 && p2+mx<=(p->nMem+1 - p->nCursor)+1 ); + }else{ + assert( p1>0 && p1+n<=(p->nMem+1 - p->nCursor)+1 ); + assert( p2>0 && p2+n<=(p->nMem+1 - p->nCursor)+1 ); + } +#endif /* SQLITE_DEBUG */ + for(i=0; i<n; i++){ + idx = aPermute ? aPermute[i] : (u32)i; + assert( memIsValid(&aMem[p1+idx]) ); + assert( memIsValid(&aMem[p2+idx]) ); + REGISTER_TRACE(p1+idx, &aMem[p1+idx]); + REGISTER_TRACE(p2+idx, &aMem[p2+idx]); + assert( i<pKeyInfo->nKeyField ); + pColl = pKeyInfo->aColl[i]; + bRev = (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_DESC); + iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl); + VVA_ONLY( iCompareIsInit = 1; ) + if( iCompare ){ + if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) + && ((aMem[p1+idx].flags & MEM_Null) || (aMem[p2+idx].flags & MEM_Null)) + ){ + iCompare = -iCompare; + } + if( bRev ) iCompare = -iCompare; + break; + } + } + assert( pOp[1].opcode==OP_Jump ); + break; +} + +/* Opcode: Jump P1 P2 P3 * * +** +** Jump to the instruction at address P1, P2, or P3 depending on whether +** in the most recent OP_Compare instruction the P1 vector was less than, +** equal to, or greater than the P2 vector, respectively. +** +** This opcode must immediately follow an OP_Compare opcode. +*/ +case OP_Jump: { /* jump */ + assert( pOp>aOp && pOp[-1].opcode==OP_Compare ); + assert( iCompareIsInit ); + if( iCompare<0 ){ + VdbeBranchTaken(0,4); pOp = &aOp[pOp->p1 - 1]; + }else if( iCompare==0 ){ + VdbeBranchTaken(1,4); pOp = &aOp[pOp->p2 - 1]; + }else{ + VdbeBranchTaken(2,4); pOp = &aOp[pOp->p3 - 1]; + } + break; +} + +/* Opcode: And P1 P2 P3 * * +** Synopsis: r[P3]=(r[P1] && r[P2]) +** +** Take the logical AND of the values in registers P1 and P2 and +** write the result into register P3. +** +** If either P1 or P2 is 0 (false) then the result is 0 even if +** the other input is NULL. A NULL and true or two NULLs give +** a NULL output. +*/ +/* Opcode: Or P1 P2 P3 * * +** Synopsis: r[P3]=(r[P1] || r[P2]) +** +** Take the logical OR of the values in register P1 and P2 and +** store the answer in register P3. +** +** If either P1 or P2 is nonzero (true) then the result is 1 (true) +** even if the other input is NULL. A NULL and false or two NULLs +** give a NULL output. +*/ +case OP_And: /* same as TK_AND, in1, in2, out3 */ +case OP_Or: { /* same as TK_OR, in1, in2, out3 */ + int v1; /* Left operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */ + int v2; /* Right operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */ + + v1 = sqlite3VdbeBooleanValue(&aMem[pOp->p1], 2); + v2 = sqlite3VdbeBooleanValue(&aMem[pOp->p2], 2); + if( pOp->opcode==OP_And ){ + static const unsigned char and_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 }; + v1 = and_logic[v1*3+v2]; + }else{ + static const unsigned char or_logic[] = { 0, 1, 2, 1, 1, 1, 2, 1, 2 }; + v1 = or_logic[v1*3+v2]; + } + pOut = &aMem[pOp->p3]; + if( v1==2 ){ + MemSetTypeFlag(pOut, MEM_Null); + }else{ + pOut->u.i = v1; + MemSetTypeFlag(pOut, MEM_Int); + } + break; +} + +/* Opcode: IsTrue P1 P2 P3 P4 * +** Synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 +** +** This opcode implements the IS TRUE, IS FALSE, IS NOT TRUE, and +** IS NOT FALSE operators. +** +** Interpret the value in register P1 as a boolean value. Store that +** boolean (a 0 or 1) in register P2. Or if the value in register P1 is +** NULL, then the P3 is stored in register P2. Invert the answer if P4 +** is 1. +** +** The logic is summarized like this: +** +** <ul> +** <li> If P3==0 and P4==0 then r[P2] := r[P1] IS TRUE +** <li> If P3==1 and P4==1 then r[P2] := r[P1] IS FALSE +** <li> If P3==0 and P4==1 then r[P2] := r[P1] IS NOT TRUE +** <li> If P3==1 and P4==0 then r[P2] := r[P1] IS NOT FALSE +** </ul> +*/ +case OP_IsTrue: { /* in1, out2 */ + assert( pOp->p4type==P4_INT32 ); + assert( pOp->p4.i==0 || pOp->p4.i==1 ); + assert( pOp->p3==0 || pOp->p3==1 ); + sqlite3VdbeMemSetInt64(&aMem[pOp->p2], + sqlite3VdbeBooleanValue(&aMem[pOp->p1], pOp->p3) ^ pOp->p4.i); + break; +} + +/* Opcode: Not P1 P2 * * * +** Synopsis: r[P2]= !r[P1] +** +** Interpret the value in register P1 as a boolean value. Store the +** boolean complement in register P2. If the value in register P1 is +** NULL, then a NULL is stored in P2. +*/ +case OP_Not: { /* same as TK_NOT, in1, out2 */ + pIn1 = &aMem[pOp->p1]; + pOut = &aMem[pOp->p2]; + if( (pIn1->flags & MEM_Null)==0 ){ + sqlite3VdbeMemSetInt64(pOut, !sqlite3VdbeBooleanValue(pIn1,0)); + }else{ + sqlite3VdbeMemSetNull(pOut); + } + break; +} + +/* Opcode: BitNot P1 P2 * * * +** Synopsis: r[P2]= ~r[P1] +** +** Interpret the content of register P1 as an integer. Store the +** ones-complement of the P1 value into register P2. If P1 holds +** a NULL then store a NULL in P2. +*/ +case OP_BitNot: { /* same as TK_BITNOT, in1, out2 */ + pIn1 = &aMem[pOp->p1]; + pOut = &aMem[pOp->p2]; + sqlite3VdbeMemSetNull(pOut); + if( (pIn1->flags & MEM_Null)==0 ){ + pOut->flags = MEM_Int; + pOut->u.i = ~sqlite3VdbeIntValue(pIn1); + } + break; +} + +/* Opcode: Once P1 P2 * * * +** +** Fall through to the next instruction the first time this opcode is +** encountered on each invocation of the byte-code program. Jump to P2 +** on the second and all subsequent encounters during the same invocation. +** +** Top-level programs determine first invocation by comparing the P1 +** operand against the P1 operand on the OP_Init opcode at the beginning +** of the program. If the P1 values differ, then fall through and make +** the P1 of this opcode equal to the P1 of OP_Init. If P1 values are +** the same then take the jump. +** +** For subprograms, there is a bitmask in the VdbeFrame that determines +** whether or not the jump should be taken. The bitmask is necessary +** because the self-altering code trick does not work for recursive +** triggers. +*/ +case OP_Once: { /* jump */ + u32 iAddr; /* Address of this instruction */ + assert( p->aOp[0].opcode==OP_Init ); + if( p->pFrame ){ + iAddr = (int)(pOp - p->aOp); + if( (p->pFrame->aOnce[iAddr/8] & (1<<(iAddr & 7)))!=0 ){ + VdbeBranchTaken(1, 2); + goto jump_to_p2; + } + p->pFrame->aOnce[iAddr/8] |= 1<<(iAddr & 7); + }else{ + if( p->aOp[0].p1==pOp->p1 ){ + VdbeBranchTaken(1, 2); + goto jump_to_p2; + } + } + VdbeBranchTaken(0, 2); + pOp->p1 = p->aOp[0].p1; + break; +} + +/* Opcode: If P1 P2 P3 * * +** +** Jump to P2 if the value in register P1 is true. The value +** is considered true if it is numeric and non-zero. If the value +** in P1 is NULL then take the jump if and only if P3 is non-zero. +*/ +case OP_If: { /* jump, in1 */ + int c; + c = sqlite3VdbeBooleanValue(&aMem[pOp->p1], pOp->p3); + VdbeBranchTaken(c!=0, 2); + if( c ) goto jump_to_p2; + break; +} + +/* Opcode: IfNot P1 P2 P3 * * +** +** Jump to P2 if the value in register P1 is False. The value +** is considered false if it has a numeric value of zero. If the value +** in P1 is NULL then take the jump if and only if P3 is non-zero. +*/ +case OP_IfNot: { /* jump, in1 */ + int c; + c = !sqlite3VdbeBooleanValue(&aMem[pOp->p1], !pOp->p3); + VdbeBranchTaken(c!=0, 2); + if( c ) goto jump_to_p2; + break; +} + +/* Opcode: IsNull P1 P2 * * * +** Synopsis: if r[P1]==NULL goto P2 +** +** Jump to P2 if the value in register P1 is NULL. +*/ +case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */ + pIn1 = &aMem[pOp->p1]; + VdbeBranchTaken( (pIn1->flags & MEM_Null)!=0, 2); + if( (pIn1->flags & MEM_Null)!=0 ){ + goto jump_to_p2; + } + break; +} + +/* Opcode: IsType P1 P2 P3 P4 P5 +** Synopsis: if typeof(P1.P3) in P5 goto P2 +** +** Jump to P2 if the type of a column in a btree is one of the types specified +** by the P5 bitmask. +** +** P1 is normally a cursor on a btree for which the row decode cache is +** valid through at least column P3. In other words, there should have been +** a prior OP_Column for column P3 or greater. If the cursor is not valid, +** then this opcode might give spurious results. +** The the btree row has fewer than P3 columns, then use P4 as the +** datatype. +** +** If P1 is -1, then P3 is a register number and the datatype is taken +** from the value in that register. +** +** P5 is a bitmask of data types. SQLITE_INTEGER is the least significant +** (0x01) bit. SQLITE_FLOAT is the 0x02 bit. SQLITE_TEXT is 0x04. +** SQLITE_BLOB is 0x08. SQLITE_NULL is 0x10. +** +** WARNING: This opcode does not reliably distinguish between NULL and REAL +** when P1>=0. If the database contains a NaN value, this opcode will think +** that the datatype is REAL when it should be NULL. When P1<0 and the value +** is already stored in register P3, then this opcode does reliably +** distinguish between NULL and REAL. The problem only arises then P1>=0. +** +** Take the jump to address P2 if and only if the datatype of the +** value determined by P1 and P3 corresponds to one of the bits in the +** P5 bitmask. +** +*/ +case OP_IsType: { /* jump */ + VdbeCursor *pC; + u16 typeMask; + u32 serialType; + + assert( pOp->p1>=(-1) && pOp->p1<p->nCursor ); + assert( pOp->p1>=0 || (pOp->p3>=0 && pOp->p3<=(p->nMem+1 - p->nCursor)) ); + if( pOp->p1>=0 ){ + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pOp->p3>=0 ); + if( pOp->p3<pC->nHdrParsed ){ + serialType = pC->aType[pOp->p3]; + if( serialType>=12 ){ + if( serialType&1 ){ + typeMask = 0x04; /* SQLITE_TEXT */ + }else{ + typeMask = 0x08; /* SQLITE_BLOB */ + } + }else{ + static const unsigned char aMask[] = { + 0x10, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x2, + 0x01, 0x01, 0x10, 0x10 + }; + testcase( serialType==0 ); + testcase( serialType==1 ); + testcase( serialType==2 ); + testcase( serialType==3 ); + testcase( serialType==4 ); + testcase( serialType==5 ); + testcase( serialType==6 ); + testcase( serialType==7 ); + testcase( serialType==8 ); + testcase( serialType==9 ); + testcase( serialType==10 ); + testcase( serialType==11 ); + typeMask = aMask[serialType]; + } + }else{ + typeMask = 1 << (pOp->p4.i - 1); + testcase( typeMask==0x01 ); + testcase( typeMask==0x02 ); + testcase( typeMask==0x04 ); + testcase( typeMask==0x08 ); + testcase( typeMask==0x10 ); + } + }else{ + assert( memIsValid(&aMem[pOp->p3]) ); + typeMask = 1 << (sqlite3_value_type((sqlite3_value*)&aMem[pOp->p3])-1); + testcase( typeMask==0x01 ); + testcase( typeMask==0x02 ); + testcase( typeMask==0x04 ); + testcase( typeMask==0x08 ); + testcase( typeMask==0x10 ); + } + VdbeBranchTaken( (typeMask & pOp->p5)!=0, 2); + if( typeMask & pOp->p5 ){ + goto jump_to_p2; + } + break; +} + +/* Opcode: ZeroOrNull P1 P2 P3 * * +** Synopsis: r[P2] = 0 OR NULL +** +** If both registers P1 and P3 are NOT NULL, then store a zero in +** register P2. If either registers P1 or P3 are NULL then put +** a NULL in register P2. +*/ +case OP_ZeroOrNull: { /* in1, in2, out2, in3 */ + if( (aMem[pOp->p1].flags & MEM_Null)!=0 + || (aMem[pOp->p3].flags & MEM_Null)!=0 + ){ + sqlite3VdbeMemSetNull(aMem + pOp->p2); + }else{ + sqlite3VdbeMemSetInt64(aMem + pOp->p2, 0); + } + break; +} + +/* Opcode: NotNull P1 P2 * * * +** Synopsis: if r[P1]!=NULL goto P2 +** +** Jump to P2 if the value in register P1 is not NULL. +*/ +case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ + pIn1 = &aMem[pOp->p1]; + VdbeBranchTaken( (pIn1->flags & MEM_Null)==0, 2); + if( (pIn1->flags & MEM_Null)==0 ){ + goto jump_to_p2; + } + break; +} + +/* Opcode: IfNullRow P1 P2 P3 * * +** Synopsis: if P1.nullRow then r[P3]=NULL, goto P2 +** +** Check the cursor P1 to see if it is currently pointing at a NULL row. +** If it is, then set register P3 to NULL and jump immediately to P2. +** If P1 is not on a NULL row, then fall through without making any +** changes. +** +** If P1 is not an open cursor, then this opcode is a no-op. +*/ +case OP_IfNullRow: { /* jump */ + VdbeCursor *pC; + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + if( pC && pC->nullRow ){ + sqlite3VdbeMemSetNull(aMem + pOp->p3); + goto jump_to_p2; + } + break; +} + +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC +/* Opcode: Offset P1 P2 P3 * * +** Synopsis: r[P3] = sqlite_offset(P1) +** +** Store in register r[P3] the byte offset into the database file that is the +** start of the payload for the record at which that cursor P1 is currently +** pointing. +** +** P2 is the column number for the argument to the sqlite_offset() function. +** This opcode does not use P2 itself, but the P2 value is used by the +** code generator. The P1, P2, and P3 operands to this opcode are the +** same as for OP_Column. +** +** This opcode is only available if SQLite is compiled with the +** -DSQLITE_ENABLE_OFFSET_SQL_FUNC option. +*/ +case OP_Offset: { /* out3 */ + VdbeCursor *pC; /* The VDBE cursor */ + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + pOut = &p->aMem[pOp->p3]; + if( pC==0 || pC->eCurType!=CURTYPE_BTREE ){ + sqlite3VdbeMemSetNull(pOut); + }else{ + if( pC->deferredMoveto ){ + rc = sqlite3VdbeFinishMoveto(pC); + if( rc ) goto abort_due_to_error; + } + if( sqlite3BtreeEof(pC->uc.pCursor) ){ + sqlite3VdbeMemSetNull(pOut); + }else{ + sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor)); + } + } + break; +} +#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ + +/* Opcode: Column P1 P2 P3 P4 P5 +** Synopsis: r[P3]=PX cursor P1 column P2 +** +** Interpret the data that cursor P1 points to as a structure built using +** the MakeRecord instruction. (See the MakeRecord opcode for additional +** information about the format of the data.) Extract the P2-th column +** from this record. If there are less than (P2+1) +** values in the record, extract a NULL. +** +** The value extracted is stored in register P3. +** +** If the record contains fewer than P2 fields, then extract a NULL. Or, +** if the P4 argument is a P4_MEM use the value of the P4 argument as +** the result. +** +** If the OPFLAG_LENGTHARG bit is set in P5 then the result is guaranteed +** to only be used by the length() function or the equivalent. The content +** of large blobs is not loaded, thus saving CPU cycles. If the +** OPFLAG_TYPEOFARG bit is set then the result will only be used by the +** typeof() function or the IS NULL or IS NOT NULL operators or the +** equivalent. In this case, all content loading can be omitted. +*/ +case OP_Column: { /* ncycle */ + u32 p2; /* column number to retrieve */ + VdbeCursor *pC; /* The VDBE cursor */ + BtCursor *pCrsr; /* The B-Tree cursor corresponding to pC */ + u32 *aOffset; /* aOffset[i] is offset to start of data for i-th column */ + int len; /* The length of the serialized data for the column */ + int i; /* Loop counter */ + Mem *pDest; /* Where to write the extracted value */ + Mem sMem; /* For storing the record being decoded */ + const u8 *zData; /* Part of the record being decoded */ + const u8 *zHdr; /* Next unparsed byte of the header */ + const u8 *zEndHdr; /* Pointer to first byte after the header */ + u64 offset64; /* 64-bit offset */ + u32 t; /* A type code from the record header */ + Mem *pReg; /* PseudoTable input register */ + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); + pC = p->apCsr[pOp->p1]; + p2 = (u32)pOp->p2; + +op_column_restart: + assert( pC!=0 ); + assert( p2<(u32)pC->nField + || (pC->eCurType==CURTYPE_PSEUDO && pC->seekResult==0) ); + aOffset = pC->aOffset; + assert( aOffset==pC->aType+pC->nField ); + assert( pC->eCurType!=CURTYPE_VTAB ); + assert( pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow ); + assert( pC->eCurType!=CURTYPE_SORTER ); + + if( pC->cacheStatus!=p->cacheCtr ){ /*OPTIMIZATION-IF-FALSE*/ + if( pC->nullRow ){ + if( pC->eCurType==CURTYPE_PSEUDO && pC->seekResult>0 ){ + /* For the special case of as pseudo-cursor, the seekResult field + ** identifies the register that holds the record */ + pReg = &aMem[pC->seekResult]; + assert( pReg->flags & MEM_Blob ); + assert( memIsValid(pReg) ); + pC->payloadSize = pC->szRow = pReg->n; + pC->aRow = (u8*)pReg->z; + }else{ + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); + sqlite3VdbeMemSetNull(pDest); + goto op_column_out; + } + }else{ + pCrsr = pC->uc.pCursor; + if( pC->deferredMoveto ){ + u32 iMap; + assert( !pC->isEphemeral ); + if( pC->ub.aAltMap && (iMap = pC->ub.aAltMap[1+p2])>0 ){ + pC = pC->pAltCursor; + p2 = iMap - 1; + goto op_column_restart; + } + rc = sqlite3VdbeFinishMoveto(pC); + if( rc ) goto abort_due_to_error; + }else if( sqlite3BtreeCursorHasMoved(pCrsr) ){ + rc = sqlite3VdbeHandleMovedCursor(pC); + if( rc ) goto abort_due_to_error; + goto op_column_restart; + } + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pCrsr ); + assert( sqlite3BtreeCursorIsValid(pCrsr) ); + pC->payloadSize = sqlite3BtreePayloadSize(pCrsr); + pC->aRow = sqlite3BtreePayloadFetch(pCrsr, &pC->szRow); + assert( pC->szRow<=pC->payloadSize ); + assert( pC->szRow<=65536 ); /* Maximum page size is 64KiB */ + } + pC->cacheStatus = p->cacheCtr; + if( (aOffset[0] = pC->aRow[0])<0x80 ){ + pC->iHdrOffset = 1; + }else{ + pC->iHdrOffset = sqlite3GetVarint32(pC->aRow, aOffset); + } + pC->nHdrParsed = 0; + + if( pC->szRow<aOffset[0] ){ /*OPTIMIZATION-IF-FALSE*/ + /* pC->aRow does not have to hold the entire row, but it does at least + ** need to cover the header of the record. If pC->aRow does not contain + ** the complete header, then set it to zero, forcing the header to be + ** dynamically allocated. */ + pC->aRow = 0; + pC->szRow = 0; + + /* Make sure a corrupt database has not given us an oversize header. + ** Do this now to avoid an oversize memory allocation. + ** + ** Type entries can be between 1 and 5 bytes each. But 4 and 5 byte + ** types use so much data space that there can only be 4096 and 32 of + ** them, respectively. So the maximum header length results from a + ** 3-byte type for each of the maximum of 32768 columns plus three + ** extra bytes for the header length itself. 32768*3 + 3 = 98307. + */ + if( aOffset[0] > 98307 || aOffset[0] > pC->payloadSize ){ + goto op_column_corrupt; + } + }else{ + /* This is an optimization. By skipping over the first few tests + ** (ex: pC->nHdrParsed<=p2) in the next section, we achieve a + ** measurable performance gain. + ** + ** This branch is taken even if aOffset[0]==0. Such a record is never + ** generated by SQLite, and could be considered corruption, but we + ** accept it for historical reasons. When aOffset[0]==0, the code this + ** branch jumps to reads past the end of the record, but never more + ** than a few bytes. Even if the record occurs at the end of the page + ** content area, the "page header" comes after the page content and so + ** this overread is harmless. Similar overreads can occur for a corrupt + ** database file. + */ + zData = pC->aRow; + assert( pC->nHdrParsed<=p2 ); /* Conditional skipped */ + testcase( aOffset[0]==0 ); + goto op_column_read_header; + } + }else if( sqlite3BtreeCursorHasMoved(pC->uc.pCursor) ){ + rc = sqlite3VdbeHandleMovedCursor(pC); + if( rc ) goto abort_due_to_error; + goto op_column_restart; + } + + /* Make sure at least the first p2+1 entries of the header have been + ** parsed and valid information is in aOffset[] and pC->aType[]. + */ + if( pC->nHdrParsed<=p2 ){ + /* If there is more header available for parsing in the record, try + ** to extract additional fields up through the p2+1-th field + */ + if( pC->iHdrOffset<aOffset[0] ){ + /* Make sure zData points to enough of the record to cover the header. */ + if( pC->aRow==0 ){ + memset(&sMem, 0, sizeof(sMem)); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pC->uc.pCursor,aOffset[0],&sMem); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + zData = (u8*)sMem.z; + }else{ + zData = pC->aRow; + } + + /* Fill in pC->aType[i] and aOffset[i] values through the p2-th field. */ + op_column_read_header: + i = pC->nHdrParsed; + offset64 = aOffset[i]; + zHdr = zData + pC->iHdrOffset; + zEndHdr = zData + aOffset[0]; + testcase( zHdr>=zEndHdr ); + do{ + if( (pC->aType[i] = t = zHdr[0])<0x80 ){ + zHdr++; + offset64 += sqlite3VdbeOneByteSerialTypeLen(t); + }else{ + zHdr += sqlite3GetVarint32(zHdr, &t); + pC->aType[i] = t; + offset64 += sqlite3VdbeSerialTypeLen(t); + } + aOffset[++i] = (u32)(offset64 & 0xffffffff); + }while( (u32)i<=p2 && zHdr<zEndHdr ); + + /* The record is corrupt if any of the following are true: + ** (1) the bytes of the header extend past the declared header size + ** (2) the entire header was used but not all data was used + ** (3) the end of the data extends beyond the end of the record. + */ + if( (zHdr>=zEndHdr && (zHdr>zEndHdr || offset64!=pC->payloadSize)) + || (offset64 > pC->payloadSize) + ){ + if( aOffset[0]==0 ){ + i = 0; + zHdr = zEndHdr; + }else{ + if( pC->aRow==0 ) sqlite3VdbeMemRelease(&sMem); + goto op_column_corrupt; + } + } + + pC->nHdrParsed = i; + pC->iHdrOffset = (u32)(zHdr - zData); + if( pC->aRow==0 ) sqlite3VdbeMemRelease(&sMem); + }else{ + t = 0; + } + + /* If after trying to extract new entries from the header, nHdrParsed is + ** still not up to p2, that means that the record has fewer than p2 + ** columns. So the result will be either the default value or a NULL. + */ + if( pC->nHdrParsed<=p2 ){ + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); + if( pOp->p4type==P4_MEM ){ + sqlite3VdbeMemShallowCopy(pDest, pOp->p4.pMem, MEM_Static); + }else{ + sqlite3VdbeMemSetNull(pDest); + } + goto op_column_out; + } + }else{ + t = pC->aType[p2]; + } + + /* Extract the content for the p2+1-th column. Control can only + ** reach this point if aOffset[p2], aOffset[p2+1], and pC->aType[p2] are + ** all valid. + */ + assert( p2<pC->nHdrParsed ); + assert( rc==SQLITE_OK ); + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); + assert( sqlite3VdbeCheckMemInvariants(pDest) ); + if( VdbeMemDynamic(pDest) ){ + sqlite3VdbeMemSetNull(pDest); + } + assert( t==pC->aType[p2] ); + if( pC->szRow>=aOffset[p2+1] ){ + /* This is the common case where the desired content fits on the original + ** page - where the content is not on an overflow page */ + zData = pC->aRow + aOffset[p2]; + if( t<12 ){ + sqlite3VdbeSerialGet(zData, t, pDest); + }else{ + /* If the column value is a string, we need a persistent value, not + ** a MEM_Ephem value. This branch is a fast short-cut that is equivalent + ** to calling sqlite3VdbeSerialGet() and sqlite3VdbeDeephemeralize(). + */ + static const u16 aFlag[] = { MEM_Blob, MEM_Str|MEM_Term }; + pDest->n = len = (t-12)/2; + pDest->enc = encoding; + if( pDest->szMalloc < len+2 ){ + if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) goto too_big; + pDest->flags = MEM_Null; + if( sqlite3VdbeMemGrow(pDest, len+2, 0) ) goto no_mem; + }else{ + pDest->z = pDest->zMalloc; + } + memcpy(pDest->z, zData, len); + pDest->z[len] = 0; + pDest->z[len+1] = 0; + pDest->flags = aFlag[t&1]; + } + }else{ + u8 p5; + pDest->enc = encoding; + assert( pDest->db==db ); + /* This branch happens only when content is on overflow pages */ + if( ((p5 = (pOp->p5 & OPFLAG_BYTELENARG))!=0 + && (p5==OPFLAG_TYPEOFARG + || (t>=12 && ((t&1)==0 || p5==OPFLAG_BYTELENARG)) + ) + ) + || sqlite3VdbeSerialTypeLen(t)==0 + ){ + /* Content is irrelevant for + ** 1. the typeof() function, + ** 2. the length(X) function if X is a blob, and + ** 3. if the content length is zero. + ** So we might as well use bogus content rather than reading + ** content from disk. + ** + ** Although sqlite3VdbeSerialGet() may read at most 8 bytes from the + ** buffer passed to it, debugging function VdbeMemPrettyPrint() may + ** read more. Use the global constant sqlite3CtypeMap[] as the array, + ** as that array is 256 bytes long (plenty for VdbeMemPrettyPrint()) + ** and it begins with a bunch of zeros. + */ + sqlite3VdbeSerialGet((u8*)sqlite3CtypeMap, t, pDest); + }else{ + rc = vdbeColumnFromOverflow(pC, p2, t, aOffset[p2], + p->cacheCtr, colCacheCtr, pDest); + if( rc ){ + if( rc==SQLITE_NOMEM ) goto no_mem; + if( rc==SQLITE_TOOBIG ) goto too_big; + goto abort_due_to_error; + } + } + } + +op_column_out: + UPDATE_MAX_BLOBSIZE(pDest); + REGISTER_TRACE(pOp->p3, pDest); + break; + +op_column_corrupt: + if( aOp[0].p3>0 ){ + pOp = &aOp[aOp[0].p3-1]; + break; + }else{ + rc = SQLITE_CORRUPT_BKPT; + goto abort_due_to_error; + } +} + +/* Opcode: TypeCheck P1 P2 P3 P4 * +** Synopsis: typecheck(r[P1@P2]) +** +** Apply affinities to the range of P2 registers beginning with P1. +** Take the affinities from the Table object in P4. If any value +** cannot be coerced into the correct type, then raise an error. +** +** This opcode is similar to OP_Affinity except that this opcode +** forces the register type to the Table column type. This is used +** to implement "strict affinity". +** +** GENERATED ALWAYS AS ... STATIC columns are only checked if P3 +** is zero. When P3 is non-zero, no type checking occurs for +** static generated columns. Virtual columns are computed at query time +** and so they are never checked. +** +** Preconditions: +** +** <ul> +** <li> P2 should be the number of non-virtual columns in the +** table of P4. +** <li> Table P4 should be a STRICT table. +** </ul> +** +** If any precondition is false, an assertion fault occurs. +*/ +case OP_TypeCheck: { + Table *pTab; + Column *aCol; + int i; + + assert( pOp->p4type==P4_TABLE ); + pTab = pOp->p4.pTab; + assert( pTab->tabFlags & TF_Strict ); + assert( pTab->nNVCol==pOp->p2 ); + aCol = pTab->aCol; + pIn1 = &aMem[pOp->p1]; + for(i=0; i<pTab->nCol; i++){ + if( aCol[i].colFlags & COLFLAG_GENERATED ){ + if( aCol[i].colFlags & COLFLAG_VIRTUAL ) continue; + if( pOp->p3 ){ pIn1++; continue; } + } + assert( pIn1 < &aMem[pOp->p1+pOp->p2] ); + applyAffinity(pIn1, aCol[i].affinity, encoding); + if( (pIn1->flags & MEM_Null)==0 ){ + switch( aCol[i].eCType ){ + case COLTYPE_BLOB: { + if( (pIn1->flags & MEM_Blob)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_INTEGER: + case COLTYPE_INT: { + if( (pIn1->flags & MEM_Int)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_TEXT: { + if( (pIn1->flags & MEM_Str)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_REAL: { + testcase( (pIn1->flags & (MEM_Real|MEM_IntReal))==MEM_Real ); + assert( (pIn1->flags & MEM_IntReal)==0 ); + if( pIn1->flags & MEM_Int ){ + /* When applying REAL affinity, if the result is still an MEM_Int + ** that will fit in 6 bytes, then change the type to MEM_IntReal + ** so that we keep the high-resolution integer value but know that + ** the type really wants to be REAL. */ + testcase( pIn1->u.i==140737488355328LL ); + testcase( pIn1->u.i==140737488355327LL ); + testcase( pIn1->u.i==-140737488355328LL ); + testcase( pIn1->u.i==-140737488355329LL ); + if( pIn1->u.i<=140737488355327LL && pIn1->u.i>=-140737488355328LL){ + pIn1->flags |= MEM_IntReal; + pIn1->flags &= ~MEM_Int; + }else{ + pIn1->u.r = (double)pIn1->u.i; + pIn1->flags |= MEM_Real; + pIn1->flags &= ~MEM_Int; + } + }else if( (pIn1->flags & (MEM_Real|MEM_IntReal))==0 ){ + goto vdbe_type_error; + } + break; + } + default: { + /* COLTYPE_ANY. Accept anything. */ + break; + } + } + } + REGISTER_TRACE((int)(pIn1-aMem), pIn1); + pIn1++; + } + assert( pIn1 == &aMem[pOp->p1+pOp->p2] ); + break; + +vdbe_type_error: + sqlite3VdbeError(p, "cannot store %s value in %s column %s.%s", + vdbeMemTypeName(pIn1), sqlite3StdType[aCol[i].eCType-1], + pTab->zName, aCol[i].zCnName); + rc = SQLITE_CONSTRAINT_DATATYPE; + goto abort_due_to_error; +} + +/* Opcode: Affinity P1 P2 * P4 * +** Synopsis: affinity(r[P1@P2]) +** +** Apply affinities to a range of P2 registers starting with P1. +** +** P4 is a string that is P2 characters long. The N-th character of the +** string indicates the column affinity that should be used for the N-th +** memory cell in the range. +*/ +case OP_Affinity: { + const char *zAffinity; /* The affinity to be applied */ + + zAffinity = pOp->p4.z; + assert( zAffinity!=0 ); + assert( pOp->p2>0 ); + assert( zAffinity[pOp->p2]==0 ); + pIn1 = &aMem[pOp->p1]; + while( 1 /*exit-by-break*/ ){ + assert( pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)] ); + assert( zAffinity[0]==SQLITE_AFF_NONE || memIsValid(pIn1) ); + applyAffinity(pIn1, zAffinity[0], encoding); + if( zAffinity[0]==SQLITE_AFF_REAL && (pIn1->flags & MEM_Int)!=0 ){ + /* When applying REAL affinity, if the result is still an MEM_Int + ** that will fit in 6 bytes, then change the type to MEM_IntReal + ** so that we keep the high-resolution integer value but know that + ** the type really wants to be REAL. */ + testcase( pIn1->u.i==140737488355328LL ); + testcase( pIn1->u.i==140737488355327LL ); + testcase( pIn1->u.i==-140737488355328LL ); + testcase( pIn1->u.i==-140737488355329LL ); + if( pIn1->u.i<=140737488355327LL && pIn1->u.i>=-140737488355328LL ){ + pIn1->flags |= MEM_IntReal; + pIn1->flags &= ~MEM_Int; + }else{ + pIn1->u.r = (double)pIn1->u.i; + pIn1->flags |= MEM_Real; + pIn1->flags &= ~(MEM_Int|MEM_Str); + } + } + REGISTER_TRACE((int)(pIn1-aMem), pIn1); + zAffinity++; + if( zAffinity[0]==0 ) break; + pIn1++; + } + break; +} + +/* Opcode: MakeRecord P1 P2 P3 P4 * +** Synopsis: r[P3]=mkrec(r[P1@P2]) +** +** Convert P2 registers beginning with P1 into the [record format] +** use as a data record in a database table or as a key +** in an index. The OP_Column opcode can decode the record later. +** +** P4 may be a string that is P2 characters long. The N-th character of the +** string indicates the column affinity that should be used for the N-th +** field of the index key. +** +** The mapping from character to affinity is given by the SQLITE_AFF_ +** macros defined in sqliteInt.h. +** +** If P4 is NULL then all index fields have the affinity BLOB. +** +** The meaning of P5 depends on whether or not the SQLITE_ENABLE_NULL_TRIM +** compile-time option is enabled: +** +** * If SQLITE_ENABLE_NULL_TRIM is enabled, then the P5 is the index +** of the right-most table that can be null-trimmed. +** +** * If SQLITE_ENABLE_NULL_TRIM is omitted, then P5 has the value +** OPFLAG_NOCHNG_MAGIC if the OP_MakeRecord opcode is allowed to +** accept no-change records with serial_type 10. This value is +** only used inside an assert() and does not affect the end result. +*/ +case OP_MakeRecord: { + Mem *pRec; /* The new record */ + u64 nData; /* Number of bytes of data space */ + int nHdr; /* Number of bytes of header space */ + i64 nByte; /* Data space required for this record */ + i64 nZero; /* Number of zero bytes at the end of the record */ + int nVarint; /* Number of bytes in a varint */ + u32 serial_type; /* Type field */ + Mem *pData0; /* First field to be combined into the record */ + Mem *pLast; /* Last field of the record */ + int nField; /* Number of fields in the record */ + char *zAffinity; /* The affinity string for the record */ + u32 len; /* Length of a field */ + u8 *zHdr; /* Where to write next byte of the header */ + u8 *zPayload; /* Where to write next byte of the payload */ + + /* Assuming the record contains N fields, the record format looks + ** like this: + ** + ** ------------------------------------------------------------------------ + ** | hdr-size | type 0 | type 1 | ... | type N-1 | data0 | ... | data N-1 | + ** ------------------------------------------------------------------------ + ** + ** Data(0) is taken from register P1. Data(1) comes from register P1+1 + ** and so forth. + ** + ** Each type field is a varint representing the serial type of the + ** corresponding data element (see sqlite3VdbeSerialType()). The + ** hdr-size field is also a varint which is the offset from the beginning + ** of the record to data0. + */ + nData = 0; /* Number of bytes of data space */ + nHdr = 0; /* Number of bytes of header space */ + nZero = 0; /* Number of zero bytes at the end of the record */ + nField = pOp->p1; + zAffinity = pOp->p4.z; + assert( nField>0 && pOp->p2>0 && pOp->p2+nField<=(p->nMem+1 - p->nCursor)+1 ); + pData0 = &aMem[nField]; + nField = pOp->p2; + pLast = &pData0[nField-1]; + + /* Identify the output register */ + assert( pOp->p3<pOp->p1 || pOp->p3>=pOp->p1+pOp->p2 ); + pOut = &aMem[pOp->p3]; + memAboutToChange(p, pOut); + + /* Apply the requested affinity to all inputs + */ + assert( pData0<=pLast ); + if( zAffinity ){ + pRec = pData0; + do{ + applyAffinity(pRec, zAffinity[0], encoding); + if( zAffinity[0]==SQLITE_AFF_REAL && (pRec->flags & MEM_Int) ){ + pRec->flags |= MEM_IntReal; + pRec->flags &= ~(MEM_Int); + } + REGISTER_TRACE((int)(pRec-aMem), pRec); + zAffinity++; + pRec++; + assert( zAffinity[0]==0 || pRec<=pLast ); + }while( zAffinity[0] ); + } + +#ifdef SQLITE_ENABLE_NULL_TRIM + /* NULLs can be safely trimmed from the end of the record, as long as + ** as the schema format is 2 or more and none of the omitted columns + ** have a non-NULL default value. Also, the record must be left with + ** at least one field. If P5>0 then it will be one more than the + ** index of the right-most column with a non-NULL default value */ + if( pOp->p5 ){ + while( (pLast->flags & MEM_Null)!=0 && nField>pOp->p5 ){ + pLast--; + nField--; + } + } +#endif + + /* Loop through the elements that will make up the record to figure + ** out how much space is required for the new record. After this loop, + ** the Mem.uTemp field of each term should hold the serial-type that will + ** be used for that term in the generated record: + ** + ** Mem.uTemp value type + ** --------------- --------------- + ** 0 NULL + ** 1 1-byte signed integer + ** 2 2-byte signed integer + ** 3 3-byte signed integer + ** 4 4-byte signed integer + ** 5 6-byte signed integer + ** 6 8-byte signed integer + ** 7 IEEE float + ** 8 Integer constant 0 + ** 9 Integer constant 1 + ** 10,11 reserved for expansion + ** N>=12 and even BLOB + ** N>=13 and odd text + ** + ** The following additional values are computed: + ** nHdr Number of bytes needed for the record header + ** nData Number of bytes of data space needed for the record + ** nZero Zero bytes at the end of the record + */ + pRec = pLast; + do{ + assert( memIsValid(pRec) ); + if( pRec->flags & MEM_Null ){ + if( pRec->flags & MEM_Zero ){ + /* Values with MEM_Null and MEM_Zero are created by xColumn virtual + ** table methods that never invoke sqlite3_result_xxxxx() while + ** computing an unchanging column value in an UPDATE statement. + ** Give such values a special internal-use-only serial-type of 10 + ** so that they can be passed through to xUpdate and have + ** a true sqlite3_value_nochange(). */ +#ifndef SQLITE_ENABLE_NULL_TRIM + assert( pOp->p5==OPFLAG_NOCHNG_MAGIC || CORRUPT_DB ); +#endif + pRec->uTemp = 10; + }else{ + pRec->uTemp = 0; + } + nHdr++; + }else if( pRec->flags & (MEM_Int|MEM_IntReal) ){ + /* Figure out whether to use 1, 2, 4, 6 or 8 bytes. */ + i64 i = pRec->u.i; + u64 uu; + testcase( pRec->flags & MEM_Int ); + testcase( pRec->flags & MEM_IntReal ); + if( i<0 ){ + uu = ~i; + }else{ + uu = i; + } + nHdr++; + testcase( uu==127 ); testcase( uu==128 ); + testcase( uu==32767 ); testcase( uu==32768 ); + testcase( uu==8388607 ); testcase( uu==8388608 ); + testcase( uu==2147483647 ); testcase( uu==2147483648LL ); + testcase( uu==140737488355327LL ); testcase( uu==140737488355328LL ); + if( uu<=127 ){ + if( (i&1)==i && p->minWriteFileFormat>=4 ){ + pRec->uTemp = 8+(u32)uu; + }else{ + nData++; + pRec->uTemp = 1; + } + }else if( uu<=32767 ){ + nData += 2; + pRec->uTemp = 2; + }else if( uu<=8388607 ){ + nData += 3; + pRec->uTemp = 3; + }else if( uu<=2147483647 ){ + nData += 4; + pRec->uTemp = 4; + }else if( uu<=140737488355327LL ){ + nData += 6; + pRec->uTemp = 5; + }else{ + nData += 8; + if( pRec->flags & MEM_IntReal ){ + /* If the value is IntReal and is going to take up 8 bytes to store + ** as an integer, then we might as well make it an 8-byte floating + ** point value */ + pRec->u.r = (double)pRec->u.i; + pRec->flags &= ~MEM_IntReal; + pRec->flags |= MEM_Real; + pRec->uTemp = 7; + }else{ + pRec->uTemp = 6; + } + } + }else if( pRec->flags & MEM_Real ){ + nHdr++; + nData += 8; + pRec->uTemp = 7; + }else{ + assert( db->mallocFailed || pRec->flags&(MEM_Str|MEM_Blob) ); + assert( pRec->n>=0 ); + len = (u32)pRec->n; + serial_type = (len*2) + 12 + ((pRec->flags & MEM_Str)!=0); + if( pRec->flags & MEM_Zero ){ + serial_type += pRec->u.nZero*2; + if( nData ){ + if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem; + len += pRec->u.nZero; + }else{ + nZero += pRec->u.nZero; + } + } + nData += len; + nHdr += sqlite3VarintLen(serial_type); + pRec->uTemp = serial_type; + } + if( pRec==pData0 ) break; + pRec--; + }while(1); + + /* EVIDENCE-OF: R-22564-11647 The header begins with a single varint + ** which determines the total number of bytes in the header. The varint + ** value is the size of the header in bytes including the size varint + ** itself. */ + testcase( nHdr==126 ); + testcase( nHdr==127 ); + if( nHdr<=126 ){ + /* The common case */ + nHdr += 1; + }else{ + /* Rare case of a really large header */ + nVarint = sqlite3VarintLen(nHdr); + nHdr += nVarint; + if( nVarint<sqlite3VarintLen(nHdr) ) nHdr++; + } + nByte = nHdr+nData; + + /* Make sure the output register has a buffer large enough to store + ** the new record. The output register (pOp->p3) is not allowed to + ** be one of the input registers (because the following call to + ** sqlite3VdbeMemClearAndResize() could clobber the value before it is used). + */ + if( nByte+nZero<=pOut->szMalloc ){ + /* The output register is already large enough to hold the record. + ** No error checks or buffer enlargement is required */ + pOut->z = pOut->zMalloc; + }else{ + /* Need to make sure that the output is not too big and then enlarge + ** the output register to hold the full result */ + if( nByte+nZero>db->aLimit[SQLITE_LIMIT_LENGTH] ){ + goto too_big; + } + if( sqlite3VdbeMemClearAndResize(pOut, (int)nByte) ){ + goto no_mem; + } + } + pOut->n = (int)nByte; + pOut->flags = MEM_Blob; + if( nZero ){ + pOut->u.nZero = nZero; + pOut->flags |= MEM_Zero; + } + UPDATE_MAX_BLOBSIZE(pOut); + zHdr = (u8 *)pOut->z; + zPayload = zHdr + nHdr; + + /* Write the record */ + if( nHdr<0x80 ){ + *(zHdr++) = nHdr; + }else{ + zHdr += sqlite3PutVarint(zHdr,nHdr); + } + assert( pData0<=pLast ); + pRec = pData0; + while( 1 /*exit-by-break*/ ){ + serial_type = pRec->uTemp; + /* EVIDENCE-OF: R-06529-47362 Following the size varint are one or more + ** additional varints, one per column. + ** EVIDENCE-OF: R-64536-51728 The values for each column in the record + ** immediately follow the header. */ + if( serial_type<=7 ){ + *(zHdr++) = serial_type; + if( serial_type==0 ){ + /* NULL value. No change in zPayload */ + }else{ + u64 v; + if( serial_type==7 ){ + assert( sizeof(v)==sizeof(pRec->u.r) ); + memcpy(&v, &pRec->u.r, sizeof(v)); + swapMixedEndianFloat(v); + }else{ + v = pRec->u.i; + } + len = sqlite3SmallTypeSizes[serial_type]; + assert( len>=1 && len<=8 && len!=5 && len!=7 ); + switch( len ){ + default: zPayload[7] = (u8)(v&0xff); v >>= 8; + zPayload[6] = (u8)(v&0xff); v >>= 8; + case 6: zPayload[5] = (u8)(v&0xff); v >>= 8; + zPayload[4] = (u8)(v&0xff); v >>= 8; + case 4: zPayload[3] = (u8)(v&0xff); v >>= 8; + case 3: zPayload[2] = (u8)(v&0xff); v >>= 8; + case 2: zPayload[1] = (u8)(v&0xff); v >>= 8; + case 1: zPayload[0] = (u8)(v&0xff); + } + zPayload += len; + } + }else if( serial_type<0x80 ){ + *(zHdr++) = serial_type; + if( serial_type>=14 && pRec->n>0 ){ + assert( pRec->z!=0 ); + memcpy(zPayload, pRec->z, pRec->n); + zPayload += pRec->n; + } + }else{ + zHdr += sqlite3PutVarint(zHdr, serial_type); + if( pRec->n ){ + assert( pRec->z!=0 ); + memcpy(zPayload, pRec->z, pRec->n); + zPayload += pRec->n; + } + } + if( pRec==pLast ) break; + pRec++; + } + assert( nHdr==(int)(zHdr - (u8*)pOut->z) ); + assert( nByte==(int)(zPayload - (u8*)pOut->z) ); + + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); + REGISTER_TRACE(pOp->p3, pOut); + break; +} + +/* Opcode: Count P1 P2 P3 * * +** Synopsis: r[P2]=count() +** +** Store the number of entries (an integer value) in the table or index +** opened by cursor P1 in register P2. +** +** If P3==0, then an exact count is obtained, which involves visiting +** every btree page of the table. But if P3 is non-zero, an estimate +** is returned based on the current cursor position. +*/ +case OP_Count: { /* out2 */ + i64 nEntry; + BtCursor *pCrsr; + + assert( p->apCsr[pOp->p1]->eCurType==CURTYPE_BTREE ); + pCrsr = p->apCsr[pOp->p1]->uc.pCursor; + assert( pCrsr ); + if( pOp->p3 ){ + nEntry = sqlite3BtreeRowCountEst(pCrsr); + }else{ + nEntry = 0; /* Not needed. Only used to silence a warning. */ + rc = sqlite3BtreeCount(db, pCrsr, &nEntry); + if( rc ) goto abort_due_to_error; + } + pOut = out2Prerelease(p, pOp); + pOut->u.i = nEntry; + goto check_for_interrupt; +} + +/* Opcode: Savepoint P1 * * P4 * +** +** Open, release or rollback the savepoint named by parameter P4, depending +** on the value of P1. To open a new savepoint set P1==0 (SAVEPOINT_BEGIN). +** To release (commit) an existing savepoint set P1==1 (SAVEPOINT_RELEASE). +** To rollback an existing savepoint set P1==2 (SAVEPOINT_ROLLBACK). +*/ +case OP_Savepoint: { + int p1; /* Value of P1 operand */ + char *zName; /* Name of savepoint */ + int nName; + Savepoint *pNew; + Savepoint *pSavepoint; + Savepoint *pTmp; + int iSavepoint; + int ii; + + p1 = pOp->p1; + zName = pOp->p4.z; + + /* Assert that the p1 parameter is valid. Also that if there is no open + ** transaction, then there cannot be any savepoints. + */ + assert( db->pSavepoint==0 || db->autoCommit==0 ); + assert( p1==SAVEPOINT_BEGIN||p1==SAVEPOINT_RELEASE||p1==SAVEPOINT_ROLLBACK ); + assert( db->pSavepoint || db->isTransactionSavepoint==0 ); + assert( checkSavepointCount(db) ); + assert( p->bIsReader ); + + if( p1==SAVEPOINT_BEGIN ){ + if( db->nVdbeWrite>0 ){ + /* A new savepoint cannot be created if there are active write + ** statements (i.e. open read/write incremental blob handles). + */ + sqlite3VdbeError(p, "cannot open savepoint - SQL statements in progress"); + rc = SQLITE_BUSY; + }else{ + nName = sqlite3Strlen30(zName); + +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* This call is Ok even if this savepoint is actually a transaction + ** savepoint (and therefore should not prompt xSavepoint()) callbacks. + ** If this is a transaction savepoint being opened, it is guaranteed + ** that the db->aVTrans[] array is empty. */ + assert( db->autoCommit==0 || db->nVTrans==0 ); + rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, + db->nStatement+db->nSavepoint); + if( rc!=SQLITE_OK ) goto abort_due_to_error; +#endif + + /* Create a new savepoint structure. */ + pNew = sqlite3DbMallocRawNN(db, sizeof(Savepoint)+nName+1); + if( pNew ){ + pNew->zName = (char *)&pNew[1]; + memcpy(pNew->zName, zName, nName+1); + + /* If there is no open transaction, then mark this as a special + ** "transaction savepoint". */ + if( db->autoCommit ){ + db->autoCommit = 0; + db->isTransactionSavepoint = 1; + }else{ + db->nSavepoint++; + } + + /* Link the new savepoint into the database handle's list. */ + pNew->pNext = db->pSavepoint; + db->pSavepoint = pNew; + pNew->nDeferredCons = db->nDeferredCons; + pNew->nDeferredImmCons = db->nDeferredImmCons; + } + } + }else{ + assert( p1==SAVEPOINT_RELEASE || p1==SAVEPOINT_ROLLBACK ); + iSavepoint = 0; + + /* Find the named savepoint. If there is no such savepoint, then an + ** an error is returned to the user. */ + for( + pSavepoint = db->pSavepoint; + pSavepoint && sqlite3StrICmp(pSavepoint->zName, zName); + pSavepoint = pSavepoint->pNext + ){ + iSavepoint++; + } + if( !pSavepoint ){ + sqlite3VdbeError(p, "no such savepoint: %s", zName); + rc = SQLITE_ERROR; + }else if( db->nVdbeWrite>0 && p1==SAVEPOINT_RELEASE ){ + /* It is not possible to release (commit) a savepoint if there are + ** active write statements. + */ + sqlite3VdbeError(p, "cannot release savepoint - " + "SQL statements in progress"); + rc = SQLITE_BUSY; + }else{ + + /* Determine whether or not this is a transaction savepoint. If so, + ** and this is a RELEASE command, then the current transaction + ** is committed. + */ + int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint; + if( isTransaction && p1==SAVEPOINT_RELEASE ){ + if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ + goto vdbe_return; + } + db->autoCommit = 1; + if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ + p->pc = (int)(pOp - aOp); + db->autoCommit = 0; + p->rc = rc = SQLITE_BUSY; + goto vdbe_return; + } + rc = p->rc; + if( rc ){ + db->autoCommit = 0; + }else{ + db->isTransactionSavepoint = 0; + } + }else{ + int isSchemaChange; + iSavepoint = db->nSavepoint - iSavepoint - 1; + if( p1==SAVEPOINT_ROLLBACK ){ + isSchemaChange = (db->mDbFlags & DBFLAG_SchemaChange)!=0; + for(ii=0; ii<db->nDb; ii++){ + rc = sqlite3BtreeTripAllCursors(db->aDb[ii].pBt, + SQLITE_ABORT_ROLLBACK, + isSchemaChange==0); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + } + }else{ + assert( p1==SAVEPOINT_RELEASE ); + isSchemaChange = 0; + } + for(ii=0; ii<db->nDb; ii++){ + rc = sqlite3BtreeSavepoint(db->aDb[ii].pBt, p1, iSavepoint); + if( rc!=SQLITE_OK ){ + goto abort_due_to_error; + } + } + if( isSchemaChange ){ + sqlite3ExpirePreparedStatements(db, 0); + sqlite3ResetAllSchemasOfConnection(db); + db->mDbFlags |= DBFLAG_SchemaChange; + } + } + if( rc ) goto abort_due_to_error; + + /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all + ** savepoints nested inside of the savepoint being operated on. */ + while( db->pSavepoint!=pSavepoint ){ + pTmp = db->pSavepoint; + db->pSavepoint = pTmp->pNext; + sqlite3DbFree(db, pTmp); + db->nSavepoint--; + } + + /* If it is a RELEASE, then destroy the savepoint being operated on + ** too. If it is a ROLLBACK TO, then set the number of deferred + ** constraint violations present in the database to the value stored + ** when the savepoint was created. */ + if( p1==SAVEPOINT_RELEASE ){ + assert( pSavepoint==db->pSavepoint ); + db->pSavepoint = pSavepoint->pNext; + sqlite3DbFree(db, pSavepoint); + if( !isTransaction ){ + db->nSavepoint--; + } + }else{ + assert( p1==SAVEPOINT_ROLLBACK ); + db->nDeferredCons = pSavepoint->nDeferredCons; + db->nDeferredImmCons = pSavepoint->nDeferredImmCons; + } + + if( !isTransaction || p1==SAVEPOINT_ROLLBACK ){ + rc = sqlite3VtabSavepoint(db, p1, iSavepoint); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + } + } + } + if( rc ) goto abort_due_to_error; + if( p->eVdbeState==VDBE_HALT_STATE ){ + rc = SQLITE_DONE; + goto vdbe_return; + } + break; +} + +/* Opcode: AutoCommit P1 P2 * * * +** +** Set the database auto-commit flag to P1 (1 or 0). If P2 is true, roll +** back any currently active btree transactions. If there are any active +** VMs (apart from this one), then a ROLLBACK fails. A COMMIT fails if +** there are active writing VMs or active VMs that use shared cache. +** +** This instruction causes the VM to halt. +*/ +case OP_AutoCommit: { + int desiredAutoCommit; + int iRollback; + + desiredAutoCommit = pOp->p1; + iRollback = pOp->p2; + assert( desiredAutoCommit==1 || desiredAutoCommit==0 ); + assert( desiredAutoCommit==1 || iRollback==0 ); + assert( db->nVdbeActive>0 ); /* At least this one VM is active */ + assert( p->bIsReader ); + + if( desiredAutoCommit!=db->autoCommit ){ + if( iRollback ){ + assert( desiredAutoCommit==1 ); + sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); + db->autoCommit = 1; + }else if( desiredAutoCommit && db->nVdbeWrite>0 ){ + /* If this instruction implements a COMMIT and other VMs are writing + ** return an error indicating that the other VMs must complete first. + */ + sqlite3VdbeError(p, "cannot commit transaction - " + "SQL statements in progress"); + rc = SQLITE_BUSY; + goto abort_due_to_error; + }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ + goto vdbe_return; + }else{ + db->autoCommit = (u8)desiredAutoCommit; + } + if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ + p->pc = (int)(pOp - aOp); + db->autoCommit = (u8)(1-desiredAutoCommit); + p->rc = rc = SQLITE_BUSY; + goto vdbe_return; + } + sqlite3CloseSavepoints(db); + if( p->rc==SQLITE_OK ){ + rc = SQLITE_DONE; + }else{ + rc = SQLITE_ERROR; + } + goto vdbe_return; + }else{ + sqlite3VdbeError(p, + (!desiredAutoCommit)?"cannot start a transaction within a transaction":( + (iRollback)?"cannot rollback - no transaction is active": + "cannot commit - no transaction is active")); + + rc = SQLITE_ERROR; + goto abort_due_to_error; + } + /*NOTREACHED*/ assert(0); +} + +/* Opcode: Transaction P1 P2 P3 P4 P5 +** +** Begin a transaction on database P1 if a transaction is not already +** active. +** If P2 is non-zero, then a write-transaction is started, or if a +** read-transaction is already active, it is upgraded to a write-transaction. +** If P2 is zero, then a read-transaction is started. If P2 is 2 or more +** then an exclusive transaction is started. +** +** P1 is the index of the database file on which the transaction is +** started. Index 0 is the main database file and index 1 is the +** file used for temporary tables. Indices of 2 or more are used for +** attached databases. +** +** If a write-transaction is started and the Vdbe.usesStmtJournal flag is +** true (this flag is set if the Vdbe may modify more than one row and may +** throw an ABORT exception), a statement transaction may also be opened. +** More specifically, a statement transaction is opened iff the database +** connection is currently not in autocommit mode, or if there are other +** active statements. A statement transaction allows the changes made by this +** VDBE to be rolled back after an error without having to roll back the +** entire transaction. If no error is encountered, the statement transaction +** will automatically commit when the VDBE halts. +** +** If P5!=0 then this opcode also checks the schema cookie against P3 +** and the schema generation counter against P4. +** The cookie changes its value whenever the database schema changes. +** This operation is used to detect when that the cookie has changed +** and that the current process needs to reread the schema. If the schema +** cookie in P3 differs from the schema cookie in the database header or +** if the schema generation counter in P4 differs from the current +** generation counter, then an SQLITE_SCHEMA error is raised and execution +** halts. The sqlite3_step() wrapper function might then reprepare the +** statement and rerun it from the beginning. +*/ +case OP_Transaction: { + Btree *pBt; + Db *pDb; + int iMeta = 0; + + assert( p->bIsReader ); + assert( p->readOnly==0 || pOp->p2==0 ); + assert( pOp->p2>=0 && pOp->p2<=2 ); + assert( pOp->p1>=0 && pOp->p1<db->nDb ); + assert( DbMaskTest(p->btreeMask, pOp->p1) ); + assert( rc==SQLITE_OK ); + if( pOp->p2 && (db->flags & (SQLITE_QueryOnly|SQLITE_CorruptRdOnly))!=0 ){ + if( db->flags & SQLITE_QueryOnly ){ + /* Writes prohibited by the "PRAGMA query_only=TRUE" statement */ + rc = SQLITE_READONLY; + }else{ + /* Writes prohibited due to a prior SQLITE_CORRUPT in the current + ** transaction */ + rc = SQLITE_CORRUPT; + } + goto abort_due_to_error; + } + pDb = &db->aDb[pOp->p1]; + pBt = pDb->pBt; + + if( pBt ){ + rc = sqlite3BtreeBeginTrans(pBt, pOp->p2, &iMeta); + testcase( rc==SQLITE_BUSY_SNAPSHOT ); + testcase( rc==SQLITE_BUSY_RECOVERY ); + if( rc!=SQLITE_OK ){ + if( (rc&0xff)==SQLITE_BUSY ){ + p->pc = (int)(pOp - aOp); + p->rc = rc; + goto vdbe_return; + } + goto abort_due_to_error; + } + + if( p->usesStmtJournal + && pOp->p2 + && (db->autoCommit==0 || db->nVdbeRead>1) + ){ + assert( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ); + if( p->iStatement==0 ){ + assert( db->nStatement>=0 && db->nSavepoint>=0 ); + db->nStatement++; + p->iStatement = db->nSavepoint + db->nStatement; + } + + rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement-1); + if( rc==SQLITE_OK ){ + rc = sqlite3BtreeBeginStmt(pBt, p->iStatement); + } + + /* Store the current value of the database handles deferred constraint + ** counter. If the statement transaction needs to be rolled back, + ** the value of this counter needs to be restored too. */ + p->nStmtDefCons = db->nDeferredCons; + p->nStmtDefImmCons = db->nDeferredImmCons; + } + } + assert( pOp->p5==0 || pOp->p4type==P4_INT32 ); + if( rc==SQLITE_OK + && pOp->p5 + && (iMeta!=pOp->p3 || pDb->pSchema->iGeneration!=pOp->p4.i) + ){ + /* + ** IMPLEMENTATION-OF: R-03189-51135 As each SQL statement runs, the schema + ** version is checked to ensure that the schema has not changed since the + ** SQL statement was prepared. + */ + sqlite3DbFree(db, p->zErrMsg); + p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed"); + /* If the schema-cookie from the database file matches the cookie + ** stored with the in-memory representation of the schema, do + ** not reload the schema from the database file. + ** + ** If virtual-tables are in use, this is not just an optimization. + ** Often, v-tables store their data in other SQLite tables, which + ** are queried from within xNext() and other v-table methods using + ** prepared queries. If such a query is out-of-date, we do not want to + ** discard the database schema, as the user code implementing the + ** v-table would have to be ready for the sqlite3_vtab structure itself + ** to be invalidated whenever sqlite3_step() is called from within + ** a v-table method. + */ + if( db->aDb[pOp->p1].pSchema->schema_cookie!=iMeta ){ + sqlite3ResetOneSchema(db, pOp->p1); + } + p->expired = 1; + rc = SQLITE_SCHEMA; + + /* Set changeCntOn to 0 to prevent the value returned by sqlite3_changes() + ** from being modified in sqlite3VdbeHalt(). If this statement is + ** reprepared, changeCntOn will be set again. */ + p->changeCntOn = 0; + } + if( rc ) goto abort_due_to_error; + break; +} + +/* Opcode: ReadCookie P1 P2 P3 * * +** +** Read cookie number P3 from database P1 and write it into register P2. +** P3==1 is the schema version. P3==2 is the database format. +** P3==3 is the recommended pager cache size, and so forth. P1==0 is +** the main database file and P1==1 is the database file used to store +** temporary tables. +** +** There must be a read-lock on the database (either a transaction +** must be started or there must be an open cursor) before +** executing this instruction. +*/ +case OP_ReadCookie: { /* out2 */ + int iMeta; + int iDb; + int iCookie; + + assert( p->bIsReader ); + iDb = pOp->p1; + iCookie = pOp->p3; + assert( pOp->p3<SQLITE_N_BTREE_META ); + assert( iDb>=0 && iDb<db->nDb ); + assert( db->aDb[iDb].pBt!=0 ); + assert( DbMaskTest(p->btreeMask, iDb) ); + + sqlite3BtreeGetMeta(db->aDb[iDb].pBt, iCookie, (u32 *)&iMeta); + pOut = out2Prerelease(p, pOp); + pOut->u.i = iMeta; + break; +} + +/* Opcode: SetCookie P1 P2 P3 * P5 +** +** Write the integer value P3 into cookie number P2 of database P1. +** P2==1 is the schema version. P2==2 is the database format. +** P2==3 is the recommended pager cache +** size, and so forth. P1==0 is the main database file and P1==1 is the +** database file used to store temporary tables. +** +** A transaction must be started before executing this opcode. +** +** If P2 is the SCHEMA_VERSION cookie (cookie number 1) then the internal +** schema version is set to P3-P5. The "PRAGMA schema_version=N" statement +** has P5 set to 1, so that the internal schema version will be different +** from the database schema version, resulting in a schema reset. +*/ +case OP_SetCookie: { + Db *pDb; + + sqlite3VdbeIncrWriteCounter(p, 0); + assert( pOp->p2<SQLITE_N_BTREE_META ); + assert( pOp->p1>=0 && pOp->p1<db->nDb ); + assert( DbMaskTest(p->btreeMask, pOp->p1) ); + assert( p->readOnly==0 ); + pDb = &db->aDb[pOp->p1]; + assert( pDb->pBt!=0 ); + assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) ); + /* See note about index shifting on OP_ReadCookie */ + rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, pOp->p3); + if( pOp->p2==BTREE_SCHEMA_VERSION ){ + /* When the schema cookie changes, record the new cookie internally */ + *(u32*)&pDb->pSchema->schema_cookie = *(u32*)&pOp->p3 - pOp->p5; + db->mDbFlags |= DBFLAG_SchemaChange; + sqlite3FkClearTriggerCache(db, pOp->p1); + }else if( pOp->p2==BTREE_FILE_FORMAT ){ + /* Record changes in the file format */ + pDb->pSchema->file_format = pOp->p3; + } + if( pOp->p1==1 ){ + /* Invalidate all prepared statements whenever the TEMP database + ** schema is changed. Ticket #1644 */ + sqlite3ExpirePreparedStatements(db, 0); + p->expired = 0; + } + if( rc ) goto abort_due_to_error; + break; +} + +/* Opcode: OpenRead P1 P2 P3 P4 P5 +** Synopsis: root=P2 iDb=P3 +** +** Open a read-only cursor for the database table whose root page is +** P2 in a database file. The database file is determined by P3. +** P3==0 means the main database, P3==1 means the database used for +** temporary tables, and P3>1 means used the corresponding attached +** database. Give the new cursor an identifier of P1. The P1 +** values need not be contiguous but all P1 values should be small integers. +** It is an error for P1 to be negative. +** +** Allowed P5 bits: +** <ul> +** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for +** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT +** of OP_SeekLE/OP_IdxLT) +** </ul> +** +** The P4 value may be either an integer (P4_INT32) or a pointer to +** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo +** object, then table being opened must be an [index b-tree] where the +** KeyInfo object defines the content and collating +** sequence of that index b-tree. Otherwise, if P4 is an integer +** value, then the table being opened must be a [table b-tree] with a +** number of columns no less than the value of P4. +** +** See also: OpenWrite, ReopenIdx +*/ +/* Opcode: ReopenIdx P1 P2 P3 P4 P5 +** Synopsis: root=P2 iDb=P3 +** +** The ReopenIdx opcode works like OP_OpenRead except that it first +** checks to see if the cursor on P1 is already open on the same +** b-tree and if it is this opcode becomes a no-op. In other words, +** if the cursor is already open, do not reopen it. +** +** The ReopenIdx opcode may only be used with P5==0 or P5==OPFLAG_SEEKEQ +** and with P4 being a P4_KEYINFO object. Furthermore, the P3 value must +** be the same as every other ReopenIdx or OpenRead for the same cursor +** number. +** +** Allowed P5 bits: +** <ul> +** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for +** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT +** of OP_SeekLE/OP_IdxLT) +** </ul> +** +** See also: OP_OpenRead, OP_OpenWrite +*/ +/* Opcode: OpenWrite P1 P2 P3 P4 P5 +** Synopsis: root=P2 iDb=P3 +** +** Open a read/write cursor named P1 on the table or index whose root +** page is P2 (or whose root page is held in register P2 if the +** OPFLAG_P2ISREG bit is set in P5 - see below). +** +** The P4 value may be either an integer (P4_INT32) or a pointer to +** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo +** object, then table being opened must be an [index b-tree] where the +** KeyInfo object defines the content and collating +** sequence of that index b-tree. Otherwise, if P4 is an integer +** value, then the table being opened must be a [table b-tree] with a +** number of columns no less than the value of P4. +** +** Allowed P5 bits: +** <ul> +** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for +** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT +** of OP_SeekLE/OP_IdxLT) +** <li> <b>0x08 OPFLAG_FORDELETE</b>: This cursor is used only to seek +** and subsequently delete entries in an index btree. This is a +** hint to the storage engine that the storage engine is allowed to +** ignore. The hint is not used by the official SQLite b*tree storage +** engine, but is used by COMDB2. +** <li> <b>0x10 OPFLAG_P2ISREG</b>: Use the content of register P2 +** as the root page, not the value of P2 itself. +** </ul> +** +** This instruction works like OpenRead except that it opens the cursor +** in read/write mode. +** +** See also: OP_OpenRead, OP_ReopenIdx +*/ +case OP_ReopenIdx: { /* ncycle */ + int nField; + KeyInfo *pKeyInfo; + u32 p2; + int iDb; + int wrFlag; + Btree *pX; + VdbeCursor *pCur; + Db *pDb; + + assert( pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ ); + assert( pOp->p4type==P4_KEYINFO ); + pCur = p->apCsr[pOp->p1]; + if( pCur && pCur->pgnoRoot==(u32)pOp->p2 ){ + assert( pCur->iDb==pOp->p3 ); /* Guaranteed by the code generator */ + assert( pCur->eCurType==CURTYPE_BTREE ); + sqlite3BtreeClearCursor(pCur->uc.pCursor); + goto open_cursor_set_hints; + } + /* If the cursor is not currently open or is open on a different + ** index, then fall through into OP_OpenRead to force a reopen */ +case OP_OpenRead: /* ncycle */ +case OP_OpenWrite: + + assert( pOp->opcode==OP_OpenWrite || pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ ); + assert( p->bIsReader ); + assert( pOp->opcode==OP_OpenRead || pOp->opcode==OP_ReopenIdx + || p->readOnly==0 ); + + if( p->expired==1 ){ + rc = SQLITE_ABORT_ROLLBACK; + goto abort_due_to_error; + } + + nField = 0; + pKeyInfo = 0; + p2 = (u32)pOp->p2; + iDb = pOp->p3; + assert( iDb>=0 && iDb<db->nDb ); + assert( DbMaskTest(p->btreeMask, iDb) ); + pDb = &db->aDb[iDb]; + pX = pDb->pBt; + assert( pX!=0 ); + if( pOp->opcode==OP_OpenWrite ){ + assert( OPFLAG_FORDELETE==BTREE_FORDELETE ); + wrFlag = BTREE_WRCSR | (pOp->p5 & OPFLAG_FORDELETE); + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( pDb->pSchema->file_format < p->minWriteFileFormat ){ + p->minWriteFileFormat = pDb->pSchema->file_format; + } + }else{ + wrFlag = 0; + } + if( pOp->p5 & OPFLAG_P2ISREG ){ + assert( p2>0 ); + assert( p2<=(u32)(p->nMem+1 - p->nCursor) ); + assert( pOp->opcode==OP_OpenWrite ); + pIn2 = &aMem[p2]; + assert( memIsValid(pIn2) ); + assert( (pIn2->flags & MEM_Int)!=0 ); + sqlite3VdbeMemIntegerify(pIn2); + p2 = (int)pIn2->u.i; + /* The p2 value always comes from a prior OP_CreateBtree opcode and + ** that opcode will always set the p2 value to 2 or more or else fail. + ** If there were a failure, the prepared statement would have halted + ** before reaching this instruction. */ + assert( p2>=2 ); + } + if( pOp->p4type==P4_KEYINFO ){ + pKeyInfo = pOp->p4.pKeyInfo; + assert( pKeyInfo->enc==ENC(db) ); + assert( pKeyInfo->db==db ); + nField = pKeyInfo->nAllField; + }else if( pOp->p4type==P4_INT32 ){ + nField = pOp->p4.i; + } + assert( pOp->p1>=0 ); + assert( nField>=0 ); + testcase( nField==0 ); /* Table with INTEGER PRIMARY KEY and nothing else */ + pCur = allocateCursor(p, pOp->p1, nField, CURTYPE_BTREE); + if( pCur==0 ) goto no_mem; + pCur->iDb = iDb; + pCur->nullRow = 1; + pCur->isOrdered = 1; + pCur->pgnoRoot = p2; +#ifdef SQLITE_DEBUG + pCur->wrFlag = wrFlag; +#endif + rc = sqlite3BtreeCursor(pX, p2, wrFlag, pKeyInfo, pCur->uc.pCursor); + pCur->pKeyInfo = pKeyInfo; + /* Set the VdbeCursor.isTable variable. Previous versions of + ** SQLite used to check if the root-page flags were sane at this point + ** and report database corruption if they were not, but this check has + ** since moved into the btree layer. */ + pCur->isTable = pOp->p4type!=P4_KEYINFO; + +open_cursor_set_hints: + assert( OPFLAG_BULKCSR==BTREE_BULKLOAD ); + assert( OPFLAG_SEEKEQ==BTREE_SEEK_EQ ); + testcase( pOp->p5 & OPFLAG_BULKCSR ); + testcase( pOp->p2 & OPFLAG_SEEKEQ ); + sqlite3BtreeCursorHintFlags(pCur->uc.pCursor, + (pOp->p5 & (OPFLAG_BULKCSR|OPFLAG_SEEKEQ))); + if( rc ) goto abort_due_to_error; + break; +} + +/* Opcode: OpenDup P1 P2 * * * +** +** Open a new cursor P1 that points to the same ephemeral table as +** cursor P2. The P2 cursor must have been opened by a prior OP_OpenEphemeral +** opcode. Only ephemeral cursors may be duplicated. +** +** Duplicate ephemeral cursors are used for self-joins of materialized views. +*/ +case OP_OpenDup: { /* ncycle */ + VdbeCursor *pOrig; /* The original cursor to be duplicated */ + VdbeCursor *pCx; /* The new cursor */ + + pOrig = p->apCsr[pOp->p2]; + assert( pOrig ); + assert( pOrig->isEphemeral ); /* Only ephemeral cursors can be duplicated */ + + pCx = allocateCursor(p, pOp->p1, pOrig->nField, CURTYPE_BTREE); + if( pCx==0 ) goto no_mem; + pCx->nullRow = 1; + pCx->isEphemeral = 1; + pCx->pKeyInfo = pOrig->pKeyInfo; + pCx->isTable = pOrig->isTable; + pCx->pgnoRoot = pOrig->pgnoRoot; + pCx->isOrdered = pOrig->isOrdered; + pCx->ub.pBtx = pOrig->ub.pBtx; + pCx->noReuse = 1; + pOrig->noReuse = 1; + rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, + pCx->pKeyInfo, pCx->uc.pCursor); + /* The sqlite3BtreeCursor() routine can only fail for the first cursor + ** opened for a database. Since there is already an open cursor when this + ** opcode is run, the sqlite3BtreeCursor() cannot fail */ + assert( rc==SQLITE_OK ); + break; +} + + +/* Opcode: OpenEphemeral P1 P2 P3 P4 P5 +** Synopsis: nColumn=P2 +** +** Open a new cursor P1 to a transient table. +** The cursor is always opened read/write even if +** the main database is read-only. The ephemeral +** table is deleted automatically when the cursor is closed. +** +** If the cursor P1 is already opened on an ephemeral table, the table +** is cleared (all content is erased). +** +** P2 is the number of columns in the ephemeral table. +** The cursor points to a BTree table if P4==0 and to a BTree index +** if P4 is not 0. If P4 is not NULL, it points to a KeyInfo structure +** that defines the format of keys in the index. +** +** The P5 parameter can be a mask of the BTREE_* flags defined +** in btree.h. These flags control aspects of the operation of +** the btree. The BTREE_OMIT_JOURNAL and BTREE_SINGLE flags are +** added automatically. +** +** If P3 is positive, then reg[P3] is modified slightly so that it +** can be used as zero-length data for OP_Insert. This is an optimization +** that avoids an extra OP_Blob opcode to initialize that register. +*/ +/* Opcode: OpenAutoindex P1 P2 * P4 * +** Synopsis: nColumn=P2 +** +** This opcode works the same as OP_OpenEphemeral. It has a +** different name to distinguish its use. Tables created using +** by this opcode will be used for automatically created transient +** indices in joins. +*/ +case OP_OpenAutoindex: /* ncycle */ +case OP_OpenEphemeral: { /* ncycle */ + VdbeCursor *pCx; + KeyInfo *pKeyInfo; + + static const int vfsFlags = + SQLITE_OPEN_READWRITE | + SQLITE_OPEN_CREATE | + SQLITE_OPEN_EXCLUSIVE | + SQLITE_OPEN_DELETEONCLOSE | + SQLITE_OPEN_TRANSIENT_DB; + assert( pOp->p1>=0 ); + assert( pOp->p2>=0 ); + if( pOp->p3>0 ){ + /* Make register reg[P3] into a value that can be used as the data + ** form sqlite3BtreeInsert() where the length of the data is zero. */ + assert( pOp->p2==0 ); /* Only used when number of columns is zero */ + assert( pOp->opcode==OP_OpenEphemeral ); + assert( aMem[pOp->p3].flags & MEM_Null ); + aMem[pOp->p3].n = 0; + aMem[pOp->p3].z = ""; + } + pCx = p->apCsr[pOp->p1]; + if( pCx && !pCx->noReuse && ALWAYS(pOp->p2<=pCx->nField) ){ + /* If the ephemeral table is already open and has no duplicates from + ** OP_OpenDup, then erase all existing content so that the table is + ** empty again, rather than creating a new table. */ + assert( pCx->isEphemeral ); + pCx->seqCount = 0; + pCx->cacheStatus = CACHE_STALE; + rc = sqlite3BtreeClearTable(pCx->ub.pBtx, pCx->pgnoRoot, 0); + }else{ + pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_BTREE); + if( pCx==0 ) goto no_mem; + pCx->isEphemeral = 1; + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->ub.pBtx, + BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, + vfsFlags); + if( rc==SQLITE_OK ){ + rc = sqlite3BtreeBeginTrans(pCx->ub.pBtx, 1, 0); + if( rc==SQLITE_OK ){ + /* If a transient index is required, create it by calling + ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before + ** opening it. If a transient table is required, just use the + ** automatically created table with root-page 1 (an BLOB_INTKEY table). + */ + if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ + assert( pOp->p4type==P4_KEYINFO ); + rc = sqlite3BtreeCreateTable(pCx->ub.pBtx, &pCx->pgnoRoot, + BTREE_BLOBKEY | pOp->p5); + if( rc==SQLITE_OK ){ + assert( pCx->pgnoRoot==SCHEMA_ROOT+1 ); + assert( pKeyInfo->db==db ); + assert( pKeyInfo->enc==ENC(db) ); + rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, + pKeyInfo, pCx->uc.pCursor); + } + pCx->isTable = 0; + }else{ + pCx->pgnoRoot = SCHEMA_ROOT; + rc = sqlite3BtreeCursor(pCx->ub.pBtx, SCHEMA_ROOT, BTREE_WRCSR, + 0, pCx->uc.pCursor); + pCx->isTable = 1; + } + } + pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); + if( rc ){ + sqlite3BtreeClose(pCx->ub.pBtx); + } + } + } + if( rc ) goto abort_due_to_error; + pCx->nullRow = 1; + break; +} + +/* Opcode: SorterOpen P1 P2 P3 P4 * +** +** This opcode works like OP_OpenEphemeral except that it opens +** a transient index that is specifically designed to sort large +** tables using an external merge-sort algorithm. +** +** If argument P3 is non-zero, then it indicates that the sorter may +** assume that a stable sort considering the first P3 fields of each +** key is sufficient to produce the required results. +*/ +case OP_SorterOpen: { + VdbeCursor *pCx; + + assert( pOp->p1>=0 ); + assert( pOp->p2>=0 ); + pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_SORTER); + if( pCx==0 ) goto no_mem; + pCx->pKeyInfo = pOp->p4.pKeyInfo; + assert( pCx->pKeyInfo->db==db ); + assert( pCx->pKeyInfo->enc==ENC(db) ); + rc = sqlite3VdbeSorterInit(db, pOp->p3, pCx); + if( rc ) goto abort_due_to_error; + break; +} + +/* Opcode: SequenceTest P1 P2 * * * +** Synopsis: if( cursor[P1].ctr++ ) pc = P2 +** +** P1 is a sorter cursor. If the sequence counter is currently zero, jump +** to P2. Regardless of whether or not the jump is taken, increment the +** the sequence value. +*/ +case OP_SequenceTest: { + VdbeCursor *pC; + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( isSorter(pC) ); + if( (pC->seqCount++)==0 ){ + goto jump_to_p2; + } + break; +} + +/* Opcode: OpenPseudo P1 P2 P3 * * +** Synopsis: P3 columns in r[P2] +** +** Open a new cursor that points to a fake table that contains a single +** row of data. The content of that one row is the content of memory +** register P2. In other words, cursor P1 becomes an alias for the +** MEM_Blob content contained in register P2. +** +** A pseudo-table created by this opcode is used to hold a single +** row output from the sorter so that the row can be decomposed into +** individual columns using the OP_Column opcode. The OP_Column opcode +** is the only cursor opcode that works with a pseudo-table. +** +** P3 is the number of fields in the records that will be stored by +** the pseudo-table. +*/ +case OP_OpenPseudo: { + VdbeCursor *pCx; + + assert( pOp->p1>=0 ); + assert( pOp->p3>=0 ); + pCx = allocateCursor(p, pOp->p1, pOp->p3, CURTYPE_PSEUDO); + if( pCx==0 ) goto no_mem; + pCx->nullRow = 1; + pCx->seekResult = pOp->p2; + pCx->isTable = 1; + /* Give this pseudo-cursor a fake BtCursor pointer so that pCx + ** can be safely passed to sqlite3VdbeCursorMoveto(). This avoids a test + ** for pCx->eCurType==CURTYPE_BTREE inside of sqlite3VdbeCursorMoveto() + ** which is a performance optimization */ + pCx->uc.pCursor = sqlite3BtreeFakeValidCursor(); + assert( pOp->p5==0 ); + break; +} + +/* Opcode: Close P1 * * * * +** +** Close a cursor previously opened as P1. If P1 is not +** currently open, this instruction is a no-op. +*/ +case OP_Close: { /* ncycle */ + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + sqlite3VdbeFreeCursor(p, p->apCsr[pOp->p1]); + p->apCsr[pOp->p1] = 0; + break; +} + +#ifdef SQLITE_ENABLE_COLUMN_USED_MASK +/* Opcode: ColumnsUsed P1 * * P4 * +** +** This opcode (which only exists if SQLite was compiled with +** SQLITE_ENABLE_COLUMN_USED_MASK) identifies which columns of the +** table or index for cursor P1 are used. P4 is a 64-bit integer +** (P4_INT64) in which the first 63 bits are one for each of the +** first 63 columns of the table or index that are actually used +** by the cursor. The high-order bit is set if any column after +** the 64th is used. +*/ +case OP_ColumnsUsed: { + VdbeCursor *pC; + pC = p->apCsr[pOp->p1]; + assert( pC->eCurType==CURTYPE_BTREE ); + pC->maskUsed = *(u64*)pOp->p4.pI64; + break; +} +#endif + +/* Opcode: SeekGE P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as the key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. +** +** Reposition cursor P1 so that it points to the smallest entry that +** is greater than or equal to the key value. If there are no records +** greater than or equal to the key and P2 is not zero, then jump to P2. +** +** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this +** opcode will either land on a record that exactly matches the key, or +** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ, +** this opcode must be followed by an IdxLE opcode with the same arguments. +** The IdxGT opcode will be skipped if this opcode succeeds, but the +** IdxGT opcode will be used on subsequent loop iterations. The +** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this +** is an equality search. +** +** This opcode leaves the cursor configured to move in forward order, +** from the beginning toward the end. In other words, the cursor is +** configured to use Next, not Prev. +** +** See also: Found, NotFound, SeekLt, SeekGt, SeekLe +*/ +/* Opcode: SeekGT P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as a key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. +** +** Reposition cursor P1 so that it points to the smallest entry that +** is greater than the key value. If there are no records greater than +** the key and P2 is not zero, then jump to P2. +** +** This opcode leaves the cursor configured to move in forward order, +** from the beginning toward the end. In other words, the cursor is +** configured to use Next, not Prev. +** +** See also: Found, NotFound, SeekLt, SeekGe, SeekLe +*/ +/* Opcode: SeekLT P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as a key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. +** +** Reposition cursor P1 so that it points to the largest entry that +** is less than the key value. If there are no records less than +** the key and P2 is not zero, then jump to P2. +** +** This opcode leaves the cursor configured to move in reverse order, +** from the end toward the beginning. In other words, the cursor is +** configured to use Prev, not Next. +** +** See also: Found, NotFound, SeekGt, SeekGe, SeekLe +*/ +/* Opcode: SeekLE P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as a key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. +** +** Reposition cursor P1 so that it points to the largest entry that +** is less than or equal to the key value. If there are no records +** less than or equal to the key and P2 is not zero, then jump to P2. +** +** This opcode leaves the cursor configured to move in reverse order, +** from the end toward the beginning. In other words, the cursor is +** configured to use Prev, not Next. +** +** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this +** opcode will either land on a record that exactly matches the key, or +** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ, +** this opcode must be followed by an IdxLE opcode with the same arguments. +** The IdxGE opcode will be skipped if this opcode succeeds, but the +** IdxGE opcode will be used on subsequent loop iterations. The +** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this +** is an equality search. +** +** See also: Found, NotFound, SeekGt, SeekGe, SeekLt +*/ +case OP_SeekLT: /* jump, in3, group, ncycle */ +case OP_SeekLE: /* jump, in3, group, ncycle */ +case OP_SeekGE: /* jump, in3, group, ncycle */ +case OP_SeekGT: { /* jump, in3, group, ncycle */ + int res; /* Comparison result */ + int oc; /* Opcode */ + VdbeCursor *pC; /* The cursor to seek */ + UnpackedRecord r; /* The key to seek for */ + int nField; /* Number of columns or fields in the key */ + i64 iKey; /* The rowid we are to seek to */ + int eqOnly; /* Only interested in == results */ + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + assert( pOp->p2!=0 ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( OP_SeekLE == OP_SeekLT+1 ); + assert( OP_SeekGE == OP_SeekLT+2 ); + assert( OP_SeekGT == OP_SeekLT+3 ); + assert( pC->isOrdered ); + assert( pC->uc.pCursor!=0 ); + oc = pOp->opcode; + eqOnly = 0; + pC->nullRow = 0; +#ifdef SQLITE_DEBUG + pC->seekOp = pOp->opcode; +#endif + + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; + if( pC->isTable ){ + u16 flags3, newType; + /* The OPFLAG_SEEKEQ/BTREE_SEEK_EQ flag is only set on index cursors */ + assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0 + || CORRUPT_DB ); + + /* The input value in P3 might be of any type: integer, real, string, + ** blob, or NULL. But it needs to be an integer before we can do + ** the seek, so convert it. */ + pIn3 = &aMem[pOp->p3]; + flags3 = pIn3->flags; + if( (flags3 & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Str))==MEM_Str ){ + applyNumericAffinity(pIn3, 0); + } + iKey = sqlite3VdbeIntValue(pIn3); /* Get the integer key value */ + newType = pIn3->flags; /* Record the type after applying numeric affinity */ + pIn3->flags = flags3; /* But convert the type back to its original */ + + /* If the P3 value could not be converted into an integer without + ** loss of information, then special processing is required... */ + if( (newType & (MEM_Int|MEM_IntReal))==0 ){ + int c; + if( (newType & MEM_Real)==0 ){ + if( (newType & MEM_Null) || oc>=OP_SeekGE ){ + VdbeBranchTaken(1,2); + goto jump_to_p2; + }else{ + rc = sqlite3BtreeLast(pC->uc.pCursor, &res); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + goto seek_not_found; + } + } + c = sqlite3IntFloatCompare(iKey, pIn3->u.r); + + /* If the approximation iKey is larger than the actual real search + ** term, substitute >= for > and < for <=. e.g. if the search term + ** is 4.9 and the integer approximation 5: + ** + ** (x > 4.9) -> (x >= 5) + ** (x <= 4.9) -> (x < 5) + */ + if( c>0 ){ + assert( OP_SeekGE==(OP_SeekGT-1) ); + assert( OP_SeekLT==(OP_SeekLE-1) ); + assert( (OP_SeekLE & 0x0001)==(OP_SeekGT & 0x0001) ); + if( (oc & 0x0001)==(OP_SeekGT & 0x0001) ) oc--; + } + + /* If the approximation iKey is smaller than the actual real search + ** term, substitute <= for < and > for >=. */ + else if( c<0 ){ + assert( OP_SeekLE==(OP_SeekLT+1) ); + assert( OP_SeekGT==(OP_SeekGE+1) ); + assert( (OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001) ); + if( (oc & 0x0001)==(OP_SeekLT & 0x0001) ) oc++; + } + } + rc = sqlite3BtreeTableMoveto(pC->uc.pCursor, (u64)iKey, 0, &res); + pC->movetoTarget = iKey; /* Used by OP_Delete */ + if( rc!=SQLITE_OK ){ + goto abort_due_to_error; + } + }else{ + /* For a cursor with the OPFLAG_SEEKEQ/BTREE_SEEK_EQ hint, only the + ** OP_SeekGE and OP_SeekLE opcodes are allowed, and these must be + ** immediately followed by an OP_IdxGT or OP_IdxLT opcode, respectively, + ** with the same key. + */ + if( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ) ){ + eqOnly = 1; + assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE ); + assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT ); + assert( pOp->opcode==OP_SeekGE || pOp[1].opcode==OP_IdxLT ); + assert( pOp->opcode==OP_SeekLE || pOp[1].opcode==OP_IdxGT ); + assert( pOp[1].p1==pOp[0].p1 ); + assert( pOp[1].p2==pOp[0].p2 ); + assert( pOp[1].p3==pOp[0].p3 ); + assert( pOp[1].p4.i==pOp[0].p4.i ); + } + + nField = pOp->p4.i; + assert( pOp->p4type==P4_INT32 ); + assert( nField>0 ); + r.pKeyInfo = pC->pKeyInfo; + r.nField = (u16)nField; + + /* The next line of code computes as follows, only faster: + ** if( oc==OP_SeekGT || oc==OP_SeekLE ){ + ** r.default_rc = -1; + ** }else{ + ** r.default_rc = +1; + ** } + */ + r.default_rc = ((1 & (oc - OP_SeekLT)) ? -1 : +1); + assert( oc!=OP_SeekGT || r.default_rc==-1 ); + assert( oc!=OP_SeekLE || r.default_rc==-1 ); + assert( oc!=OP_SeekGE || r.default_rc==+1 ); + assert( oc!=OP_SeekLT || r.default_rc==+1 ); + + r.aMem = &aMem[pOp->p3]; +#ifdef SQLITE_DEBUG + { + int i; + for(i=0; i<r.nField; i++){ + assert( memIsValid(&r.aMem[i]) ); + if( i>0 ) REGISTER_TRACE(pOp->p3+i, &r.aMem[i]); + } + } +#endif + r.eqSeen = 0; + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &res); + if( rc!=SQLITE_OK ){ + goto abort_due_to_error; + } + if( eqOnly && r.eqSeen==0 ){ + assert( res!=0 ); + goto seek_not_found; + } + } +#ifdef SQLITE_TEST + sqlite3_search_count++; +#endif + if( oc>=OP_SeekGE ){ assert( oc==OP_SeekGE || oc==OP_SeekGT ); + if( res<0 || (res==0 && oc==OP_SeekGT) ){ + res = 0; + rc = sqlite3BtreeNext(pC->uc.pCursor, 0); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + res = 1; + }else{ + goto abort_due_to_error; + } + } + }else{ + res = 0; + } + }else{ + assert( oc==OP_SeekLT || oc==OP_SeekLE ); + if( res>0 || (res==0 && oc==OP_SeekLT) ){ + res = 0; + rc = sqlite3BtreePrevious(pC->uc.pCursor, 0); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + res = 1; + }else{ + goto abort_due_to_error; + } + } + }else{ + /* res might be negative because the table is empty. Check to + ** see if this is the case. + */ + res = sqlite3BtreeEof(pC->uc.pCursor); + } + } +seek_not_found: + assert( pOp->p2>0 ); + VdbeBranchTaken(res!=0,2); + if( res ){ + goto jump_to_p2; + }else if( eqOnly ){ + assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT ); + pOp++; /* Skip the OP_IdxLt or OP_IdxGT that follows */ + } + break; +} + + +/* Opcode: SeekScan P1 P2 * * P5 +** Synopsis: Scan-ahead up to P1 rows +** +** This opcode is a prefix opcode to OP_SeekGE. In other words, this +** opcode must be immediately followed by OP_SeekGE. This constraint is +** checked by assert() statements. +** +** This opcode uses the P1 through P4 operands of the subsequent +** OP_SeekGE. In the text that follows, the operands of the subsequent +** OP_SeekGE opcode are denoted as SeekOP.P1 through SeekOP.P4. Only +** the P1, P2 and P5 operands of this opcode are also used, and are called +** This.P1, This.P2 and This.P5. +** +** This opcode helps to optimize IN operators on a multi-column index +** where the IN operator is on the later terms of the index by avoiding +** unnecessary seeks on the btree, substituting steps to the next row +** of the b-tree instead. A correct answer is obtained if this opcode +** is omitted or is a no-op. +** +** The SeekGE.P3 and SeekGE.P4 operands identify an unpacked key which +** is the desired entry that we want the cursor SeekGE.P1 to be pointing +** to. Call this SeekGE.P3/P4 row the "target". +** +** If the SeekGE.P1 cursor is not currently pointing to a valid row, +** then this opcode is a no-op and control passes through into the OP_SeekGE. +** +** If the SeekGE.P1 cursor is pointing to a valid row, then that row +** might be the target row, or it might be near and slightly before the +** target row, or it might be after the target row. If the cursor is +** currently before the target row, then this opcode attempts to position +** the cursor on or after the target row by invoking sqlite3BtreeStep() +** on the cursor between 1 and This.P1 times. +** +** The This.P5 parameter is a flag that indicates what to do if the +** cursor ends up pointing at a valid row that is past the target +** row. If This.P5 is false (0) then a jump is made to SeekGE.P2. If +** This.P5 is true (non-zero) then a jump is made to This.P2. The P5==0 +** case occurs when there are no inequality constraints to the right of +** the IN constraint. The jump to SeekGE.P2 ends the loop. The P5!=0 case +** occurs when there are inequality constraints to the right of the IN +** operator. In that case, the This.P2 will point either directly to or +** to setup code prior to the OP_IdxGT or OP_IdxGE opcode that checks for +** loop terminate. +** +** Possible outcomes from this opcode:<ol> +** +** <li> If the cursor is initially not pointed to any valid row, then +** fall through into the subsequent OP_SeekGE opcode. +** +** <li> If the cursor is left pointing to a row that is before the target +** row, even after making as many as This.P1 calls to +** sqlite3BtreeNext(), then also fall through into OP_SeekGE. +** +** <li> If the cursor is left pointing at the target row, either because it +** was at the target row to begin with or because one or more +** sqlite3BtreeNext() calls moved the cursor to the target row, +** then jump to This.P2.., +** +** <li> If the cursor started out before the target row and a call to +** to sqlite3BtreeNext() moved the cursor off the end of the index +** (indicating that the target row definitely does not exist in the +** btree) then jump to SeekGE.P2, ending the loop. +** +** <li> If the cursor ends up on a valid row that is past the target row +** (indicating that the target row does not exist in the btree) then +** jump to SeekOP.P2 if This.P5==0 or to This.P2 if This.P5>0. +** </ol> +*/ +case OP_SeekScan: { /* ncycle */ + VdbeCursor *pC; + int res; + int nStep; + UnpackedRecord r; + + assert( pOp[1].opcode==OP_SeekGE ); + + /* If pOp->p5 is clear, then pOp->p2 points to the first instruction past the + ** OP_IdxGT that follows the OP_SeekGE. Otherwise, it points to the first + ** opcode past the OP_SeekGE itself. */ + assert( pOp->p2>=(int)(pOp-aOp)+2 ); +#ifdef SQLITE_DEBUG + if( pOp->p5==0 ){ + /* There are no inequality constraints following the IN constraint. */ + assert( pOp[1].p1==aOp[pOp->p2-1].p1 ); + assert( pOp[1].p2==aOp[pOp->p2-1].p2 ); + assert( pOp[1].p3==aOp[pOp->p2-1].p3 ); + assert( aOp[pOp->p2-1].opcode==OP_IdxGT + || aOp[pOp->p2-1].opcode==OP_IdxGE ); + testcase( aOp[pOp->p2-1].opcode==OP_IdxGE ); + }else{ + /* There are inequality constraints. */ + assert( pOp->p2==(int)(pOp-aOp)+2 ); + assert( aOp[pOp->p2-1].opcode==OP_SeekGE ); + } +#endif + + assert( pOp->p1>0 ); + pC = p->apCsr[pOp[1].p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( !pC->isTable ); + if( !sqlite3BtreeCursorIsValidNN(pC->uc.pCursor) ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("... cursor not valid - fall through\n"); + } +#endif + break; + } + nStep = pOp->p1; + assert( nStep>=1 ); + r.pKeyInfo = pC->pKeyInfo; + r.nField = (u16)pOp[1].p4.i; + r.default_rc = 0; + r.aMem = &aMem[pOp[1].p3]; +#ifdef SQLITE_DEBUG + { + int i; + for(i=0; i<r.nField; i++){ + assert( memIsValid(&r.aMem[i]) ); + REGISTER_TRACE(pOp[1].p3+i, &aMem[pOp[1].p3+i]); + } + } +#endif + res = 0; /* Not needed. Only used to silence a warning. */ + while(1){ + rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res); + if( rc ) goto abort_due_to_error; + if( res>0 && pOp->p5==0 ){ + seekscan_search_fail: + /* Jump to SeekGE.P2, ending the loop */ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("... %d steps and then skip\n", pOp->p1 - nStep); + } +#endif + VdbeBranchTaken(1,3); + pOp++; + goto jump_to_p2; + } + if( res>=0 ){ + /* Jump to This.P2, bypassing the OP_SeekGE opcode */ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("... %d steps and then success\n", pOp->p1 - nStep); + } +#endif + VdbeBranchTaken(2,3); + goto jump_to_p2; + break; + } + if( nStep<=0 ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("... fall through after %d steps\n", pOp->p1); + } +#endif + VdbeBranchTaken(0,3); + break; + } + nStep--; + pC->cacheStatus = CACHE_STALE; + rc = sqlite3BtreeNext(pC->uc.pCursor, 0); + if( rc ){ + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + goto seekscan_search_fail; + }else{ + goto abort_due_to_error; + } + } + } + + break; +} + + +/* Opcode: SeekHit P1 P2 P3 * * +** Synopsis: set P2<=seekHit<=P3 +** +** Increase or decrease the seekHit value for cursor P1, if necessary, +** so that it is no less than P2 and no greater than P3. +** +** The seekHit integer represents the maximum of terms in an index for which +** there is known to be at least one match. If the seekHit value is smaller +** than the total number of equality terms in an index lookup, then the +** OP_IfNoHope opcode might run to see if the IN loop can be abandoned +** early, thus saving work. This is part of the IN-early-out optimization. +** +** P1 must be a valid b-tree cursor. +*/ +case OP_SeekHit: { /* ncycle */ + VdbeCursor *pC; + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pOp->p3>=pOp->p2 ); + if( pC->seekHit<pOp->p2 ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p2); + } +#endif + pC->seekHit = pOp->p2; + }else if( pC->seekHit>pOp->p3 ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p3); + } +#endif + pC->seekHit = pOp->p3; + } + break; +} + +/* Opcode: IfNotOpen P1 P2 * * * +** Synopsis: if( !csr[P1] ) goto P2 +** +** If cursor P1 is not open or if P1 is set to a NULL row using the +** OP_NullRow opcode, then jump to instruction P2. Otherwise, fall through. +*/ +case OP_IfNotOpen: { /* jump */ + VdbeCursor *pCur; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pCur = p->apCsr[pOp->p1]; + VdbeBranchTaken(pCur==0 || pCur->nullRow, 2); + if( pCur==0 || pCur->nullRow ){ + goto jump_to_p2_and_check_for_interrupt; + } + break; +} + +/* Opcode: Found P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** If P4==0 then register P3 holds a blob constructed by MakeRecord. If +** P4>0 then register P3 is the first of P4 registers that form an unpacked +** record. +** +** Cursor P1 is on an index btree. If the record identified by P3 and P4 +** is a prefix of any entry in P1 then a jump is made to P2 and +** P1 is left pointing at the matching entry. +** +** This operation leaves the cursor in a state where it can be +** advanced in the forward direction. The Next instruction will work, +** but not the Prev instruction. +** +** See also: NotFound, NoConflict, NotExists. SeekGe +*/ +/* Opcode: NotFound P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** If P4==0 then register P3 holds a blob constructed by MakeRecord. If +** P4>0 then register P3 is the first of P4 registers that form an unpacked +** record. +** +** Cursor P1 is on an index btree. If the record identified by P3 and P4 +** is not the prefix of any entry in P1 then a jump is made to P2. If P1 +** does contain an entry whose prefix matches the P3/P4 record then control +** falls through to the next instruction and P1 is left pointing at the +** matching entry. +** +** This operation leaves the cursor in a state where it cannot be +** advanced in either direction. In other words, the Next and Prev +** opcodes do not work after this operation. +** +** See also: Found, NotExists, NoConflict, IfNoHope +*/ +/* Opcode: IfNoHope P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** Register P3 is the first of P4 registers that form an unpacked +** record. Cursor P1 is an index btree. P2 is a jump destination. +** In other words, the operands to this opcode are the same as the +** operands to OP_NotFound and OP_IdxGT. +** +** This opcode is an optimization attempt only. If this opcode always +** falls through, the correct answer is still obtained, but extra work +** is performed. +** +** A value of N in the seekHit flag of cursor P1 means that there exists +** a key P3:N that will match some record in the index. We want to know +** if it is possible for a record P3:P4 to match some record in the +** index. If it is not possible, we can skip some work. So if seekHit +** is less than P4, attempt to find out if a match is possible by running +** OP_NotFound. +** +** This opcode is used in IN clause processing for a multi-column key. +** If an IN clause is attached to an element of the key other than the +** left-most element, and if there are no matches on the most recent +** seek over the whole key, then it might be that one of the key element +** to the left is prohibiting a match, and hence there is "no hope" of +** any match regardless of how many IN clause elements are checked. +** In such a case, we abandon the IN clause search early, using this +** opcode. The opcode name comes from the fact that the +** jump is taken if there is "no hope" of achieving a match. +** +** See also: NotFound, SeekHit +*/ +/* Opcode: NoConflict P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** If P4==0 then register P3 holds a blob constructed by MakeRecord. If +** P4>0 then register P3 is the first of P4 registers that form an unpacked +** record. +** +** Cursor P1 is on an index btree. If the record identified by P3 and P4 +** contains any NULL value, jump immediately to P2. If all terms of the +** record are not-NULL then a check is done to determine if any row in the +** P1 index btree has a matching key prefix. If there are no matches, jump +** immediately to P2. If there is a match, fall through and leave the P1 +** cursor pointing to the matching row. +** +** This opcode is similar to OP_NotFound with the exceptions that the +** branch is always taken if any part of the search key input is NULL. +** +** This operation leaves the cursor in a state where it cannot be +** advanced in either direction. In other words, the Next and Prev +** opcodes do not work after this operation. +** +** See also: NotFound, Found, NotExists +*/ +case OP_IfNoHope: { /* jump, in3, ncycle */ + VdbeCursor *pC; + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit is %d\n", pC->seekHit); + } +#endif + if( pC->seekHit>=pOp->p4.i ) break; + /* Fall through into OP_NotFound */ + /* no break */ deliberate_fall_through +} +case OP_NoConflict: /* jump, in3, ncycle */ +case OP_NotFound: /* jump, in3, ncycle */ +case OP_Found: { /* jump, in3, ncycle */ + int alreadyExists; + int ii; + VdbeCursor *pC; + UnpackedRecord *pIdxKey; + UnpackedRecord r; + +#ifdef SQLITE_TEST + if( pOp->opcode!=OP_NoConflict ) sqlite3_found_count++; +#endif + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + assert( pOp->p4type==P4_INT32 ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); +#ifdef SQLITE_DEBUG + pC->seekOp = pOp->opcode; +#endif + r.aMem = &aMem[pOp->p3]; + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); + assert( pC->isTable==0 ); + r.nField = (u16)pOp->p4.i; + if( r.nField>0 ){ + /* Key values in an array of registers */ + r.pKeyInfo = pC->pKeyInfo; + r.default_rc = 0; +#ifdef SQLITE_DEBUG + for(ii=0; ii<r.nField; ii++){ + assert( memIsValid(&r.aMem[ii]) ); + assert( (r.aMem[ii].flags & MEM_Zero)==0 || r.aMem[ii].n==0 ); + if( ii ) REGISTER_TRACE(pOp->p3+ii, &r.aMem[ii]); + } +#endif + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &pC->seekResult); + }else{ + /* Composite key generated by OP_MakeRecord */ + assert( r.aMem->flags & MEM_Blob ); + assert( pOp->opcode!=OP_NoConflict ); + rc = ExpandBlob(r.aMem); + assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); + if( rc ) goto no_mem; + pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); + if( pIdxKey==0 ) goto no_mem; + sqlite3VdbeRecordUnpack(pC->pKeyInfo, r.aMem->n, r.aMem->z, pIdxKey); + pIdxKey->default_rc = 0; + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &pC->seekResult); + sqlite3DbFreeNN(db, pIdxKey); + } + if( rc!=SQLITE_OK ){ + goto abort_due_to_error; + } + alreadyExists = (pC->seekResult==0); + pC->nullRow = 1-alreadyExists; + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; + if( pOp->opcode==OP_Found ){ + VdbeBranchTaken(alreadyExists!=0,2); + if( alreadyExists ) goto jump_to_p2; + }else{ + if( !alreadyExists ){ + VdbeBranchTaken(1,2); + goto jump_to_p2; + } + if( pOp->opcode==OP_NoConflict ){ + /* For the OP_NoConflict opcode, take the jump if any of the + ** input fields are NULL, since any key with a NULL will not + ** conflict */ + for(ii=0; ii<r.nField; ii++){ + if( r.aMem[ii].flags & MEM_Null ){ + VdbeBranchTaken(1,2); + goto jump_to_p2; + } + } + } + VdbeBranchTaken(0,2); + if( pOp->opcode==OP_IfNoHope ){ + pC->seekHit = pOp->p4.i; + } + } + break; +} + +/* Opcode: SeekRowid P1 P2 P3 * * +** Synopsis: intkey=r[P3] +** +** P1 is the index of a cursor open on an SQL table btree (with integer +** keys). If register P3 does not contain an integer or if P1 does not +** contain a record with rowid P3 then jump immediately to P2. +** Or, if P2 is 0, raise an SQLITE_CORRUPT error. If P1 does contain +** a record with rowid P3 then +** leave the cursor pointing at that record and fall through to the next +** instruction. +** +** The OP_NotExists opcode performs the same operation, but with OP_NotExists +** the P3 register must be guaranteed to contain an integer value. With this +** opcode, register P3 might not contain an integer. +** +** The OP_NotFound opcode performs the same operation on index btrees +** (with arbitrary multi-value keys). +** +** This opcode leaves the cursor in a state where it cannot be advanced +** in either direction. In other words, the Next and Prev opcodes will +** not work following this opcode. +** +** See also: Found, NotFound, NoConflict, SeekRowid +*/ +/* Opcode: NotExists P1 P2 P3 * * +** Synopsis: intkey=r[P3] +** +** P1 is the index of a cursor open on an SQL table btree (with integer +** keys). P3 is an integer rowid. If P1 does not contain a record with +** rowid P3 then jump immediately to P2. Or, if P2 is 0, raise an +** SQLITE_CORRUPT error. If P1 does contain a record with rowid P3 then +** leave the cursor pointing at that record and fall through to the next +** instruction. +** +** The OP_SeekRowid opcode performs the same operation but also allows the +** P3 register to contain a non-integer value, in which case the jump is +** always taken. This opcode requires that P3 always contain an integer. +** +** The OP_NotFound opcode performs the same operation on index btrees +** (with arbitrary multi-value keys). +** +** This opcode leaves the cursor in a state where it cannot be advanced +** in either direction. In other words, the Next and Prev opcodes will +** not work following this opcode. +** +** See also: Found, NotFound, NoConflict, SeekRowid +*/ +case OP_SeekRowid: { /* jump, in3, ncycle */ + VdbeCursor *pC; + BtCursor *pCrsr; + int res; + u64 iKey; + + pIn3 = &aMem[pOp->p3]; + testcase( pIn3->flags & MEM_Int ); + testcase( pIn3->flags & MEM_IntReal ); + testcase( pIn3->flags & MEM_Real ); + testcase( (pIn3->flags & (MEM_Str|MEM_Int))==MEM_Str ); + if( (pIn3->flags & (MEM_Int|MEM_IntReal))==0 ){ + /* If pIn3->u.i does not contain an integer, compute iKey as the + ** integer value of pIn3. Jump to P2 if pIn3 cannot be converted + ** into an integer without loss of information. Take care to avoid + ** changing the datatype of pIn3, however, as it is used by other + ** parts of the prepared statement. */ + Mem x = pIn3[0]; + applyAffinity(&x, SQLITE_AFF_NUMERIC, encoding); + if( (x.flags & MEM_Int)==0 ) goto jump_to_p2; + iKey = x.u.i; + goto notExistsWithKey; + } + /* Fall through into OP_NotExists */ + /* no break */ deliberate_fall_through +case OP_NotExists: /* jump, in3, ncycle */ + pIn3 = &aMem[pOp->p3]; + assert( (pIn3->flags & MEM_Int)!=0 || pOp->opcode==OP_SeekRowid ); + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + iKey = pIn3->u.i; +notExistsWithKey: + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); +#ifdef SQLITE_DEBUG + if( pOp->opcode==OP_SeekRowid ) pC->seekOp = OP_SeekRowid; +#endif + assert( pC->isTable ); + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; + assert( pCrsr!=0 ); + res = 0; + rc = sqlite3BtreeTableMoveto(pCrsr, iKey, 0, &res); + assert( rc==SQLITE_OK || res==0 ); + pC->movetoTarget = iKey; /* Used by OP_Delete */ + pC->nullRow = 0; + pC->cacheStatus = CACHE_STALE; + pC->deferredMoveto = 0; + VdbeBranchTaken(res!=0,2); + pC->seekResult = res; + if( res!=0 ){ + assert( rc==SQLITE_OK ); + if( pOp->p2==0 ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + goto jump_to_p2; + } + } + if( rc ) goto abort_due_to_error; + break; +} + +/* Opcode: Sequence P1 P2 * * * +** Synopsis: r[P2]=cursor[P1].ctr++ +** +** Find the next available sequence number for cursor P1. +** Write the sequence number into register P2. +** The sequence number on the cursor is incremented after this +** instruction. +*/ +case OP_Sequence: { /* out2 */ + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + assert( p->apCsr[pOp->p1]!=0 ); + assert( p->apCsr[pOp->p1]->eCurType!=CURTYPE_VTAB ); + pOut = out2Prerelease(p, pOp); + pOut->u.i = p->apCsr[pOp->p1]->seqCount++; + break; +} + + +/* Opcode: NewRowid P1 P2 P3 * * +** Synopsis: r[P2]=rowid +** +** Get a new integer record number (a.k.a "rowid") used as the key to a table. +** The record number is not previously used as a key in the database +** table that cursor P1 points to. The new record number is written +** written to register P2. +** +** If P3>0 then P3 is a register in the root frame of this VDBE that holds +** the largest previously generated record number. No new record numbers are +** allowed to be less than this value. When this value reaches its maximum, +** an SQLITE_FULL error is generated. The P3 register is updated with the ' +** generated record number. This P3 mechanism is used to help implement the +** AUTOINCREMENT feature. +*/ +case OP_NewRowid: { /* out2 */ + i64 v; /* The new rowid */ + VdbeCursor *pC; /* Cursor of table to get the new rowid */ + int res; /* Result of an sqlite3BtreeLast() */ + int cnt; /* Counter to limit the number of searches */ +#ifndef SQLITE_OMIT_AUTOINCREMENT + Mem *pMem; /* Register holding largest rowid for AUTOINCREMENT */ + VdbeFrame *pFrame; /* Root frame of VDBE */ +#endif + + v = 0; + res = 0; + pOut = out2Prerelease(p, pOp); + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->isTable ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); + { + /* The next rowid or record number (different terms for the same + ** thing) is obtained in a two-step algorithm. + ** + ** First we attempt to find the largest existing rowid and add one + ** to that. But if the largest existing rowid is already the maximum + ** positive integer, we have to fall through to the second + ** probabilistic algorithm + ** + ** The second algorithm is to select a rowid at random and see if + ** it already exists in the table. If it does not exist, we have + ** succeeded. If the random rowid does exist, we select a new one + ** and try again, up to 100 times. + */ + assert( pC->isTable ); + +#ifdef SQLITE_32BIT_ROWID +# define MAX_ROWID 0x7fffffff +#else + /* Some compilers complain about constants of the form 0x7fffffffffffffff. + ** Others complain about 0x7ffffffffffffffffLL. The following macro seems + ** to provide the constant while making all compilers happy. + */ +# define MAX_ROWID (i64)( (((u64)0x7fffffff)<<32) | (u64)0xffffffff ) +#endif + + if( !pC->useRandomRowid ){ + rc = sqlite3BtreeLast(pC->uc.pCursor, &res); + if( rc!=SQLITE_OK ){ + goto abort_due_to_error; + } + if( res ){ + v = 1; /* IMP: R-61914-48074 */ + }else{ + assert( sqlite3BtreeCursorIsValid(pC->uc.pCursor) ); + v = sqlite3BtreeIntegerKey(pC->uc.pCursor); + if( v>=MAX_ROWID ){ + pC->useRandomRowid = 1; + }else{ + v++; /* IMP: R-29538-34987 */ + } + } + } + +#ifndef SQLITE_OMIT_AUTOINCREMENT + if( pOp->p3 ){ + /* Assert that P3 is a valid memory cell. */ + assert( pOp->p3>0 ); + if( p->pFrame ){ + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + /* Assert that P3 is a valid memory cell. */ + assert( pOp->p3<=pFrame->nMem ); + pMem = &pFrame->aMem[pOp->p3]; + }else{ + /* Assert that P3 is a valid memory cell. */ + assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); + pMem = &aMem[pOp->p3]; + memAboutToChange(p, pMem); + } + assert( memIsValid(pMem) ); + + REGISTER_TRACE(pOp->p3, pMem); + sqlite3VdbeMemIntegerify(pMem); + assert( (pMem->flags & MEM_Int)!=0 ); /* mem(P3) holds an integer */ + if( pMem->u.i==MAX_ROWID || pC->useRandomRowid ){ + rc = SQLITE_FULL; /* IMP: R-17817-00630 */ + goto abort_due_to_error; + } + if( v<pMem->u.i+1 ){ + v = pMem->u.i + 1; + } + pMem->u.i = v; + } +#endif + if( pC->useRandomRowid ){ + /* IMPLEMENTATION-OF: R-07677-41881 If the largest ROWID is equal to the + ** largest possible integer (9223372036854775807) then the database + ** engine starts picking positive candidate ROWIDs at random until + ** it finds one that is not previously used. */ + assert( pOp->p3==0 ); /* We cannot be in random rowid mode if this is + ** an AUTOINCREMENT table. */ + cnt = 0; + do{ + sqlite3_randomness(sizeof(v), &v); + v &= (MAX_ROWID>>1); v++; /* Ensure that v is greater than zero */ + }while( ((rc = sqlite3BtreeTableMoveto(pC->uc.pCursor, (u64)v, + 0, &res))==SQLITE_OK) + && (res==0) + && (++cnt<100)); + if( rc ) goto abort_due_to_error; + if( res==0 ){ + rc = SQLITE_FULL; /* IMP: R-38219-53002 */ + goto abort_due_to_error; + } + assert( v>0 ); /* EV: R-40812-03570 */ + } + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; + } + pOut->u.i = v; + break; +} + +/* Opcode: Insert P1 P2 P3 P4 P5 +** Synopsis: intkey=r[P3] data=r[P2] +** +** Write an entry into the table of cursor P1. A new entry is +** created if it doesn't already exist or the data for an existing +** entry is overwritten. The data is the value MEM_Blob stored in register +** number P2. The key is stored in register P3. The key must +** be a MEM_Int. +** +** If the OPFLAG_NCHANGE flag of P5 is set, then the row change count is +** incremented (otherwise not). If the OPFLAG_LASTROWID flag of P5 is set, +** then rowid is stored for subsequent return by the +** sqlite3_last_insert_rowid() function (otherwise it is unmodified). +** +** If the OPFLAG_USESEEKRESULT flag of P5 is set, the implementation might +** run faster by avoiding an unnecessary seek on cursor P1. However, +** the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior +** seeks on the cursor or if the most recent seek used a key equal to P3. +** +** If the OPFLAG_ISUPDATE flag is set, then this opcode is part of an +** UPDATE operation. Otherwise (if the flag is clear) then this opcode +** is part of an INSERT operation. The difference is only important to +** the update hook. +** +** Parameter P4 may point to a Table structure, or may be NULL. If it is +** not NULL, then the update-hook (sqlite3.xUpdateCallback) is invoked +** following a successful insert. +** +** (WARNING/TODO: If P1 is a pseudo-cursor and P2 is dynamically +** allocated, then ownership of P2 is transferred to the pseudo-cursor +** and register P2 becomes ephemeral. If the cursor is changed, the +** value of register P2 will then change. Make sure this does not +** cause any problems.) +** +** This instruction only works on tables. The equivalent instruction +** for indices is OP_IdxInsert. +*/ +case OP_Insert: { + Mem *pData; /* MEM cell holding data for the record to be inserted */ + Mem *pKey; /* MEM cell holding key for the record */ + VdbeCursor *pC; /* Cursor to table into which insert is written */ + int seekResult; /* Result of prior seek or 0 if no USESEEKRESULT flag */ + const char *zDb; /* database name - used by the update hook */ + Table *pTab; /* Table structure - used by update and pre-update hooks */ + BtreePayload x; /* Payload to be inserted */ + + pData = &aMem[pOp->p2]; + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + assert( memIsValid(pData) ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->deferredMoveto==0 ); + assert( pC->uc.pCursor!=0 ); + assert( (pOp->p5 & OPFLAG_ISNOOP) || pC->isTable ); + assert( pOp->p4type==P4_TABLE || pOp->p4type>=P4_STATIC ); + REGISTER_TRACE(pOp->p2, pData); + sqlite3VdbeIncrWriteCounter(p, pC); + + pKey = &aMem[pOp->p3]; + assert( pKey->flags & MEM_Int ); + assert( memIsValid(pKey) ); + REGISTER_TRACE(pOp->p3, pKey); + x.nKey = pKey->u.i; + + if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ + assert( pC->iDb>=0 ); + zDb = db->aDb[pC->iDb].zDbSName; + pTab = pOp->p4.pTab; + assert( (pOp->p5 & OPFLAG_ISNOOP) || HasRowid(pTab) ); + }else{ + pTab = 0; + zDb = 0; + } + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + /* Invoke the pre-update hook, if any */ + if( pTab ){ + if( db->xPreUpdateCallback && !(pOp->p5 & OPFLAG_ISUPDATE) ){ + sqlite3VdbePreUpdateHook(p,pC,SQLITE_INSERT,zDb,pTab,x.nKey,pOp->p2,-1); + } + if( db->xUpdateCallback==0 || pTab->aCol==0 ){ + /* Prevent post-update hook from running in cases when it should not */ + pTab = 0; + } + } + if( pOp->p5 & OPFLAG_ISNOOP ) break; +#endif + + assert( (pOp->p5 & OPFLAG_LASTROWID)==0 || (pOp->p5 & OPFLAG_NCHANGE)!=0 ); + if( pOp->p5 & OPFLAG_NCHANGE ){ + p->nChange++; + if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey; + } + assert( (pData->flags & (MEM_Blob|MEM_Str))!=0 || pData->n==0 ); + x.pData = pData->z; + x.nData = pData->n; + seekResult = ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0); + if( pData->flags & MEM_Zero ){ + x.nZero = pData->u.nZero; + }else{ + x.nZero = 0; + } + x.pKey = 0; + assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT ); + rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), + seekResult + ); + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; + colCacheCtr++; + + /* Invoke the update-hook if required. */ + if( rc ) goto abort_due_to_error; + if( pTab ){ + assert( db->xUpdateCallback!=0 ); + assert( pTab->aCol!=0 ); + db->xUpdateCallback(db->pUpdateArg, + (pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT, + zDb, pTab->zName, x.nKey); + } + break; +} + +/* Opcode: RowCell P1 P2 P3 * * +** +** P1 and P2 are both open cursors. Both must be opened on the same type +** of table - intkey or index. This opcode is used as part of copying +** the current row from P2 into P1. If the cursors are opened on intkey +** tables, register P3 contains the rowid to use with the new record in +** P1. If they are opened on index tables, P3 is not used. +** +** This opcode must be followed by either an Insert or InsertIdx opcode +** with the OPFLAG_PREFORMAT flag set to complete the insert operation. +*/ +case OP_RowCell: { + VdbeCursor *pDest; /* Cursor to write to */ + VdbeCursor *pSrc; /* Cursor to read from */ + i64 iKey; /* Rowid value to insert with */ + assert( pOp[1].opcode==OP_Insert || pOp[1].opcode==OP_IdxInsert ); + assert( pOp[1].opcode==OP_Insert || pOp->p3==0 ); + assert( pOp[1].opcode==OP_IdxInsert || pOp->p3>0 ); + assert( pOp[1].p5 & OPFLAG_PREFORMAT ); + pDest = p->apCsr[pOp->p1]; + pSrc = p->apCsr[pOp->p2]; + iKey = pOp->p3 ? aMem[pOp->p3].u.i : 0; + rc = sqlite3BtreeTransferRow(pDest->uc.pCursor, pSrc->uc.pCursor, iKey); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + break; +}; + +/* Opcode: Delete P1 P2 P3 P4 P5 +** +** Delete the record at which the P1 cursor is currently pointing. +** +** If the OPFLAG_SAVEPOSITION bit of the P5 parameter is set, then +** the cursor will be left pointing at either the next or the previous +** record in the table. If it is left pointing at the next record, then +** the next Next instruction will be a no-op. As a result, in this case +** it is ok to delete a record from within a Next loop. If +** OPFLAG_SAVEPOSITION bit of P5 is clear, then the cursor will be +** left in an undefined state. +** +** If the OPFLAG_AUXDELETE bit is set on P5, that indicates that this +** delete is one of several associated with deleting a table row and +** all its associated index entries. Exactly one of those deletes is +** the "primary" delete. The others are all on OPFLAG_FORDELETE +** cursors or else are marked with the AUXDELETE flag. +** +** If the OPFLAG_NCHANGE flag of P2 (NB: P2 not P5) is set, then the row +** change count is incremented (otherwise not). +** +** P1 must not be pseudo-table. It has to be a real table with +** multiple rows. +** +** If P4 is not NULL then it points to a Table object. In this case either +** the update or pre-update hook, or both, may be invoked. The P1 cursor must +** have been positioned using OP_NotFound prior to invoking this opcode in +** this case. Specifically, if one is configured, the pre-update hook is +** invoked if P4 is not NULL. The update-hook is invoked if one is configured, +** P4 is not NULL, and the OPFLAG_NCHANGE flag is set in P2. +** +** If the OPFLAG_ISUPDATE flag is set in P2, then P3 contains the address +** of the memory cell that contains the value that the rowid of the row will +** be set to by the update. +*/ +case OP_Delete: { + VdbeCursor *pC; + const char *zDb; + Table *pTab; + int opflags; + + opflags = pOp->p2; + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); + assert( pC->deferredMoveto==0 ); + sqlite3VdbeIncrWriteCounter(p, pC); + +#ifdef SQLITE_DEBUG + if( pOp->p4type==P4_TABLE + && HasRowid(pOp->p4.pTab) + && pOp->p5==0 + && sqlite3BtreeCursorIsValidNN(pC->uc.pCursor) + ){ + /* If p5 is zero, the seek operation that positioned the cursor prior to + ** OP_Delete will have also set the pC->movetoTarget field to the rowid of + ** the row that is being deleted */ + i64 iKey = sqlite3BtreeIntegerKey(pC->uc.pCursor); + assert( CORRUPT_DB || pC->movetoTarget==iKey ); + } +#endif + + /* If the update-hook or pre-update-hook will be invoked, set zDb to + ** the name of the db to pass as to it. Also set local pTab to a copy + ** of p4.pTab. Finally, if p5 is true, indicating that this cursor was + ** last moved with OP_Next or OP_Prev, not Seek or NotFound, set + ** VdbeCursor.movetoTarget to the current rowid. */ + if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ + assert( pC->iDb>=0 ); + assert( pOp->p4.pTab!=0 ); + zDb = db->aDb[pC->iDb].zDbSName; + pTab = pOp->p4.pTab; + if( (pOp->p5 & OPFLAG_SAVEPOSITION)!=0 && pC->isTable ){ + pC->movetoTarget = sqlite3BtreeIntegerKey(pC->uc.pCursor); + } + }else{ + zDb = 0; + pTab = 0; + } + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + /* Invoke the pre-update-hook if required. */ + assert( db->xPreUpdateCallback==0 || pTab==pOp->p4.pTab ); + if( db->xPreUpdateCallback && pTab ){ + assert( !(opflags & OPFLAG_ISUPDATE) + || HasRowid(pTab)==0 + || (aMem[pOp->p3].flags & MEM_Int) + ); + sqlite3VdbePreUpdateHook(p, pC, + (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, + zDb, pTab, pC->movetoTarget, + pOp->p3, -1 + ); + } + if( opflags & OPFLAG_ISNOOP ) break; +#endif + + /* Only flags that can be set are SAVEPOISTION and AUXDELETE */ + assert( (pOp->p5 & ~(OPFLAG_SAVEPOSITION|OPFLAG_AUXDELETE))==0 ); + assert( OPFLAG_SAVEPOSITION==BTREE_SAVEPOSITION ); + assert( OPFLAG_AUXDELETE==BTREE_AUXDELETE ); + +#ifdef SQLITE_DEBUG + if( p->pFrame==0 ){ + if( pC->isEphemeral==0 + && (pOp->p5 & OPFLAG_AUXDELETE)==0 + && (pC->wrFlag & OPFLAG_FORDELETE)==0 + ){ + nExtraDelete++; + } + if( pOp->p2 & OPFLAG_NCHANGE ){ + nExtraDelete--; + } + } +#endif + + rc = sqlite3BtreeDelete(pC->uc.pCursor, pOp->p5); + pC->cacheStatus = CACHE_STALE; + colCacheCtr++; + pC->seekResult = 0; + if( rc ) goto abort_due_to_error; + + /* Invoke the update-hook if required. */ + if( opflags & OPFLAG_NCHANGE ){ + p->nChange++; + if( db->xUpdateCallback && ALWAYS(pTab!=0) && HasRowid(pTab) ){ + db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, pTab->zName, + pC->movetoTarget); + assert( pC->iDb>=0 ); + } + } + + break; +} +/* Opcode: ResetCount * * * * * +** +** The value of the change counter is copied to the database handle +** change counter (returned by subsequent calls to sqlite3_changes()). +** Then the VMs internal change counter resets to 0. +** This is used by trigger programs. +*/ +case OP_ResetCount: { + sqlite3VdbeSetChanges(db, p->nChange); + p->nChange = 0; + break; +} + +/* Opcode: SorterCompare P1 P2 P3 P4 +** Synopsis: if key(P1)!=trim(r[P3],P4) goto P2 +** +** P1 is a sorter cursor. This instruction compares a prefix of the +** record blob in register P3 against a prefix of the entry that +** the sorter cursor currently points to. Only the first P4 fields +** of r[P3] and the sorter record are compared. +** +** If either P3 or the sorter contains a NULL in one of their significant +** fields (not counting the P4 fields at the end which are ignored) then +** the comparison is assumed to be equal. +** +** Fall through to next instruction if the two records compare equal to +** each other. Jump to P2 if they are different. +*/ +case OP_SorterCompare: { + VdbeCursor *pC; + int res; + int nKeyCol; + + pC = p->apCsr[pOp->p1]; + assert( isSorter(pC) ); + assert( pOp->p4type==P4_INT32 ); + pIn3 = &aMem[pOp->p3]; + nKeyCol = pOp->p4.i; + res = 0; + rc = sqlite3VdbeSorterCompare(pC, pIn3, nKeyCol, &res); + VdbeBranchTaken(res!=0,2); + if( rc ) goto abort_due_to_error; + if( res ) goto jump_to_p2; + break; +}; + +/* Opcode: SorterData P1 P2 P3 * * +** Synopsis: r[P2]=data +** +** Write into register P2 the current sorter data for sorter cursor P1. +** Then clear the column header cache on cursor P3. +** +** This opcode is normally used to move a record out of the sorter and into +** a register that is the source for a pseudo-table cursor created using +** OpenPseudo. That pseudo-table cursor is the one that is identified by +** parameter P3. Clearing the P3 column cache as part of this opcode saves +** us from having to issue a separate NullRow instruction to clear that cache. +*/ +case OP_SorterData: { /* ncycle */ + VdbeCursor *pC; + + pOut = &aMem[pOp->p2]; + pC = p->apCsr[pOp->p1]; + assert( isSorter(pC) ); + rc = sqlite3VdbeSorterRowkey(pC, pOut); + assert( rc!=SQLITE_OK || (pOut->flags & MEM_Blob) ); + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + if( rc ) goto abort_due_to_error; + p->apCsr[pOp->p3]->cacheStatus = CACHE_STALE; + break; +} + +/* Opcode: RowData P1 P2 P3 * * +** Synopsis: r[P2]=data +** +** Write into register P2 the complete row content for the row at +** which cursor P1 is currently pointing. +** There is no interpretation of the data. +** It is just copied onto the P2 register exactly as +** it is found in the database file. +** +** If cursor P1 is an index, then the content is the key of the row. +** If cursor P2 is a table, then the content extracted is the data. +** +** If the P1 cursor must be pointing to a valid row (not a NULL row) +** of a real table, not a pseudo-table. +** +** If P3!=0 then this opcode is allowed to make an ephemeral pointer +** into the database page. That means that the content of the output +** register will be invalidated as soon as the cursor moves - including +** moves caused by other cursors that "save" the current cursors +** position in order that they can write to the same table. If P3==0 +** then a copy of the data is made into memory. P3!=0 is faster, but +** P3==0 is safer. +** +** If P3!=0 then the content of the P2 register is unsuitable for use +** in OP_Result and any OP_Result will invalidate the P2 register content. +** The P2 register content is invalidated by opcodes like OP_Function or +** by any use of another cursor pointing to the same table. +*/ +case OP_RowData: { + VdbeCursor *pC; + BtCursor *pCrsr; + u32 n; + + pOut = out2Prerelease(p, pOp); + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( isSorter(pC)==0 ); + assert( pC->nullRow==0 ); + assert( pC->uc.pCursor!=0 ); + pCrsr = pC->uc.pCursor; + + /* The OP_RowData opcodes always follow OP_NotExists or + ** OP_SeekRowid or OP_Rewind/Op_Next with no intervening instructions + ** that might invalidate the cursor. + ** If this where not the case, on of the following assert()s + ** would fail. Should this ever change (because of changes in the code + ** generator) then the fix would be to insert a call to + ** sqlite3VdbeCursorMoveto(). + */ + assert( pC->deferredMoveto==0 ); + assert( sqlite3BtreeCursorIsValid(pCrsr) ); + + n = sqlite3BtreePayloadSize(pCrsr); + if( n>(u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ + goto too_big; + } + testcase( n==0 ); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pCrsr, n, pOut); + if( rc ) goto abort_due_to_error; + if( !pOp->p3 ) Deephemeralize(pOut); + UPDATE_MAX_BLOBSIZE(pOut); + REGISTER_TRACE(pOp->p2, pOut); + break; +} + +/* Opcode: Rowid P1 P2 * * * +** Synopsis: r[P2]=PX rowid of P1 +** +** Store in register P2 an integer which is the key of the table entry that +** P1 is currently point to. +** +** P1 can be either an ordinary table or a virtual table. There used to +** be a separate OP_VRowid opcode for use with virtual tables, but this +** one opcode now works for both table types. +*/ +case OP_Rowid: { /* out2, ncycle */ + VdbeCursor *pC; + i64 v; + sqlite3_vtab *pVtab; + const sqlite3_module *pModule; + + pOut = out2Prerelease(p, pOp); + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow ); + if( pC->nullRow ){ + pOut->flags = MEM_Null; + break; + }else if( pC->deferredMoveto ){ + v = pC->movetoTarget; +#ifndef SQLITE_OMIT_VIRTUALTABLE + }else if( pC->eCurType==CURTYPE_VTAB ){ + assert( pC->uc.pVCur!=0 ); + pVtab = pC->uc.pVCur->pVtab; + pModule = pVtab->pModule; + assert( pModule->xRowid ); + rc = pModule->xRowid(pC->uc.pVCur, &v); + sqlite3VtabImportErrmsg(p, pVtab); + if( rc ) goto abort_due_to_error; +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + }else{ + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); + rc = sqlite3VdbeCursorRestore(pC); + if( rc ) goto abort_due_to_error; + if( pC->nullRow ){ + pOut->flags = MEM_Null; + break; + } + v = sqlite3BtreeIntegerKey(pC->uc.pCursor); + } + pOut->u.i = v; + break; +} + +/* Opcode: NullRow P1 * * * * +** +** Move the cursor P1 to a null row. Any OP_Column operations +** that occur while the cursor is on the null row will always +** write a NULL. +** +** If cursor P1 is not previously opened, open it now to a special +** pseudo-cursor that always returns NULL for every column. +*/ +case OP_NullRow: { + VdbeCursor *pC; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + if( pC==0 ){ + /* If the cursor is not already open, create a special kind of + ** pseudo-cursor that always gives null rows. */ + pC = allocateCursor(p, pOp->p1, 1, CURTYPE_PSEUDO); + if( pC==0 ) goto no_mem; + pC->seekResult = 0; + pC->isTable = 1; + pC->noReuse = 1; + pC->uc.pCursor = sqlite3BtreeFakeValidCursor(); + } + pC->nullRow = 1; + pC->cacheStatus = CACHE_STALE; + if( pC->eCurType==CURTYPE_BTREE ){ + assert( pC->uc.pCursor!=0 ); + sqlite3BtreeClearCursor(pC->uc.pCursor); + } +#ifdef SQLITE_DEBUG + if( pC->seekOp==0 ) pC->seekOp = OP_NullRow; +#endif + break; +} + +/* Opcode: SeekEnd P1 * * * * +** +** Position cursor P1 at the end of the btree for the purpose of +** appending a new entry onto the btree. +** +** It is assumed that the cursor is used only for appending and so +** if the cursor is valid, then the cursor must already be pointing +** at the end of the btree and so no changes are made to +** the cursor. +*/ +/* Opcode: Last P1 P2 * * * +** +** The next use of the Rowid or Column or Prev instruction for P1 +** will refer to the last entry in the database table or index. +** If the table or index is empty and P2>0, then jump immediately to P2. +** If P2 is 0 or if the table or index is not empty, fall through +** to the following instruction. +** +** This opcode leaves the cursor configured to move in reverse order, +** from the end toward the beginning. In other words, the cursor is +** configured to use Prev, not Next. +*/ +case OP_SeekEnd: /* ncycle */ +case OP_Last: { /* jump, ncycle */ + VdbeCursor *pC; + BtCursor *pCrsr; + int res; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; + res = 0; + assert( pCrsr!=0 ); +#ifdef SQLITE_DEBUG + pC->seekOp = pOp->opcode; +#endif + if( pOp->opcode==OP_SeekEnd ){ + assert( pOp->p2==0 ); + pC->seekResult = -1; + if( sqlite3BtreeCursorIsValidNN(pCrsr) ){ + break; + } + } + rc = sqlite3BtreeLast(pCrsr, &res); + pC->nullRow = (u8)res; + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; + if( rc ) goto abort_due_to_error; + if( pOp->p2>0 ){ + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + } + break; +} + +/* Opcode: IfSmaller P1 P2 P3 * * +** +** Estimate the number of rows in the table P1. Jump to P2 if that +** estimate is less than approximately 2**(0.1*P3). +*/ +case OP_IfSmaller: { /* jump */ + VdbeCursor *pC; + BtCursor *pCrsr; + int res; + i64 sz; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + pCrsr = pC->uc.pCursor; + assert( pCrsr ); + rc = sqlite3BtreeFirst(pCrsr, &res); + if( rc ) goto abort_due_to_error; + if( res==0 ){ + sz = sqlite3BtreeRowCountEst(pCrsr); + if( ALWAYS(sz>=0) && sqlite3LogEst((u64)sz)<pOp->p3 ) res = 1; + } + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + break; +} + + +/* Opcode: SorterSort P1 P2 * * * +** +** After all records have been inserted into the Sorter object +** identified by P1, invoke this opcode to actually do the sorting. +** Jump to P2 if there are no records to be sorted. +** +** This opcode is an alias for OP_Sort and OP_Rewind that is used +** for Sorter objects. +*/ +/* Opcode: Sort P1 P2 * * * +** +** This opcode does exactly the same thing as OP_Rewind except that +** it increments an undocumented global variable used for testing. +** +** Sorting is accomplished by writing records into a sorting index, +** then rewinding that index and playing it back from beginning to +** end. We use the OP_Sort opcode instead of OP_Rewind to do the +** rewinding so that the global variable will be incremented and +** regression tests can determine whether or not the optimizer is +** correctly optimizing out sorts. +*/ +case OP_SorterSort: /* jump ncycle */ +case OP_Sort: { /* jump ncycle */ +#ifdef SQLITE_TEST + sqlite3_sort_count++; + sqlite3_search_count--; +#endif + p->aCounter[SQLITE_STMTSTATUS_SORT]++; + /* Fall through into OP_Rewind */ + /* no break */ deliberate_fall_through +} +/* Opcode: Rewind P1 P2 * * * +** +** The next use of the Rowid or Column or Next instruction for P1 +** will refer to the first entry in the database table or index. +** If the table or index is empty, jump immediately to P2. +** If the table or index is not empty, fall through to the following +** instruction. +** +** If P2 is zero, that is an assertion that the P1 table is never +** empty and hence the jump will never be taken. +** +** This opcode leaves the cursor configured to move in forward order, +** from the beginning toward the end. In other words, the cursor is +** configured to use Next, not Prev. +*/ +case OP_Rewind: { /* jump, ncycle */ + VdbeCursor *pC; + BtCursor *pCrsr; + int res; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + assert( pOp->p5==0 ); + assert( pOp->p2>=0 && pOp->p2<p->nOp ); + + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( isSorter(pC)==(pOp->opcode==OP_SorterSort) ); + res = 1; +#ifdef SQLITE_DEBUG + pC->seekOp = OP_Rewind; +#endif + if( isSorter(pC) ){ + rc = sqlite3VdbeSorterRewind(pC, &res); + }else{ + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; + assert( pCrsr ); + rc = sqlite3BtreeFirst(pCrsr, &res); + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; + } + if( rc ) goto abort_due_to_error; + pC->nullRow = (u8)res; + if( pOp->p2>0 ){ + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + } + break; +} + +/* Opcode: Next P1 P2 P3 * P5 +** +** Advance cursor P1 so that it points to the next key/data pair in its +** table or index. If there are no more key/value pairs then fall through +** to the following instruction. But if the cursor advance was successful, +** jump immediately to P2. +** +** The Next opcode is only valid following an SeekGT, SeekGE, or +** OP_Rewind opcode used to position the cursor. Next is not allowed +** to follow SeekLT, SeekLE, or OP_Last. +** +** The P1 cursor must be for a real table, not a pseudo-table. P1 must have +** been opened prior to this opcode or the program will segfault. +** +** The P3 value is a hint to the btree implementation. If P3==1, that +** means P1 is an SQL index and that this instruction could have been +** omitted if that index had been unique. P3 is usually 0. P3 is +** always either 0 or 1. +** +** If P5 is positive and the jump is taken, then event counter +** number P5-1 in the prepared statement is incremented. +** +** See also: Prev +*/ +/* Opcode: Prev P1 P2 P3 * P5 +** +** Back up cursor P1 so that it points to the previous key/data pair in its +** table or index. If there is no previous key/value pairs then fall through +** to the following instruction. But if the cursor backup was successful, +** jump immediately to P2. +** +** +** The Prev opcode is only valid following an SeekLT, SeekLE, or +** OP_Last opcode used to position the cursor. Prev is not allowed +** to follow SeekGT, SeekGE, or OP_Rewind. +** +** The P1 cursor must be for a real table, not a pseudo-table. If P1 is +** not open then the behavior is undefined. +** +** The P3 value is a hint to the btree implementation. If P3==1, that +** means P1 is an SQL index and that this instruction could have been +** omitted if that index had been unique. P3 is usually 0. P3 is +** always either 0 or 1. +** +** If P5 is positive and the jump is taken, then event counter +** number P5-1 in the prepared statement is incremented. +*/ +/* Opcode: SorterNext P1 P2 * * P5 +** +** This opcode works just like OP_Next except that P1 must be a +** sorter object for which the OP_SorterSort opcode has been +** invoked. This opcode advances the cursor to the next sorted +** record, or jumps to P2 if there are no more sorted records. +*/ +case OP_SorterNext: { /* jump */ + VdbeCursor *pC; + + pC = p->apCsr[pOp->p1]; + assert( isSorter(pC) ); + rc = sqlite3VdbeSorterNext(db, pC); + goto next_tail; + +case OP_Prev: /* jump, ncycle */ + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + assert( pOp->p5==0 + || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP + || pOp->p5==SQLITE_STMTSTATUS_AUTOINDEX); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->deferredMoveto==0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE + || pC->seekOp==OP_Last || pC->seekOp==OP_IfNoHope + || pC->seekOp==OP_NullRow); + rc = sqlite3BtreePrevious(pC->uc.pCursor, pOp->p3); + goto next_tail; + +case OP_Next: /* jump, ncycle */ + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + assert( pOp->p5==0 + || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP + || pOp->p5==SQLITE_STMTSTATUS_AUTOINDEX); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->deferredMoveto==0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE + || pC->seekOp==OP_Rewind || pC->seekOp==OP_Found + || pC->seekOp==OP_NullRow|| pC->seekOp==OP_SeekRowid + || pC->seekOp==OP_IfNoHope); + rc = sqlite3BtreeNext(pC->uc.pCursor, pOp->p3); + +next_tail: + pC->cacheStatus = CACHE_STALE; + VdbeBranchTaken(rc==SQLITE_OK,2); + if( rc==SQLITE_OK ){ + pC->nullRow = 0; + p->aCounter[pOp->p5]++; +#ifdef SQLITE_TEST + sqlite3_search_count++; +#endif + goto jump_to_p2_and_check_for_interrupt; + } + if( rc!=SQLITE_DONE ) goto abort_due_to_error; + rc = SQLITE_OK; + pC->nullRow = 1; + goto check_for_interrupt; +} + +/* Opcode: IdxInsert P1 P2 P3 P4 P5 +** Synopsis: key=r[P2] +** +** Register P2 holds an SQL index key made using the +** MakeRecord instructions. This opcode writes that key +** into the index P1. Data for the entry is nil. +** +** If P4 is not zero, then it is the number of values in the unpacked +** key of reg(P2). In that case, P3 is the index of the first register +** for the unpacked key. The availability of the unpacked key can sometimes +** be an optimization. +** +** If P5 has the OPFLAG_APPEND bit set, that is a hint to the b-tree layer +** that this insert is likely to be an append. +** +** If P5 has the OPFLAG_NCHANGE bit set, then the change counter is +** incremented by this instruction. If the OPFLAG_NCHANGE bit is clear, +** then the change counter is unchanged. +** +** If the OPFLAG_USESEEKRESULT flag of P5 is set, the implementation might +** run faster by avoiding an unnecessary seek on cursor P1. However, +** the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior +** seeks on the cursor or if the most recent seek used a key equivalent +** to P2. +** +** This instruction only works for indices. The equivalent instruction +** for tables is OP_Insert. +*/ +case OP_IdxInsert: { /* in2 */ + VdbeCursor *pC; + BtreePayload x; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + sqlite3VdbeIncrWriteCounter(p, pC); + assert( pC!=0 ); + assert( !isSorter(pC) ); + pIn2 = &aMem[pOp->p2]; + assert( (pIn2->flags & MEM_Blob) || (pOp->p5 & OPFLAG_PREFORMAT) ); + if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->isTable==0 ); + rc = ExpandBlob(pIn2); + if( rc ) goto abort_due_to_error; + x.nKey = pIn2->n; + x.pKey = pIn2->z; + x.aMem = aMem + pOp->p3; + x.nMem = (u16)pOp->p4.i; + rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), + ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) + ); + assert( pC->deferredMoveto==0 ); + pC->cacheStatus = CACHE_STALE; + if( rc) goto abort_due_to_error; + break; +} + +/* Opcode: SorterInsert P1 P2 * * * +** Synopsis: key=r[P2] +** +** Register P2 holds an SQL index key made using the +** MakeRecord instructions. This opcode writes that key +** into the sorter P1. Data for the entry is nil. +*/ +case OP_SorterInsert: { /* in2 */ + VdbeCursor *pC; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + sqlite3VdbeIncrWriteCounter(p, pC); + assert( pC!=0 ); + assert( isSorter(pC) ); + pIn2 = &aMem[pOp->p2]; + assert( pIn2->flags & MEM_Blob ); + assert( pC->isTable==0 ); + rc = ExpandBlob(pIn2); + if( rc ) goto abort_due_to_error; + rc = sqlite3VdbeSorterWrite(pC, pIn2); + if( rc) goto abort_due_to_error; + break; +} + +/* Opcode: IdxDelete P1 P2 P3 * P5 +** Synopsis: key=r[P2@P3] +** +** The content of P3 registers starting at register P2 form +** an unpacked index key. This opcode removes that entry from the +** index opened by cursor P1. +** +** If P5 is not zero, then raise an SQLITE_CORRUPT_INDEX error +** if no matching index entry is found. This happens when running +** an UPDATE or DELETE statement and the index entry to be updated +** or deleted is not found. For some uses of IdxDelete +** (example: the EXCEPT operator) it does not matter that no matching +** entry is found. For those cases, P5 is zero. Also, do not raise +** this (self-correcting and non-critical) error if in writable_schema mode. +*/ +case OP_IdxDelete: { + VdbeCursor *pC; + BtCursor *pCrsr; + int res; + UnpackedRecord r; + + assert( pOp->p3>0 ); + assert( pOp->p2>0 && pOp->p2+pOp->p3<=(p->nMem+1 - p->nCursor)+1 ); + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + sqlite3VdbeIncrWriteCounter(p, pC); + pCrsr = pC->uc.pCursor; + assert( pCrsr!=0 ); + r.pKeyInfo = pC->pKeyInfo; + r.nField = (u16)pOp->p3; + r.default_rc = 0; + r.aMem = &aMem[pOp->p2]; + rc = sqlite3BtreeIndexMoveto(pCrsr, &r, &res); + if( rc ) goto abort_due_to_error; + if( res==0 ){ + rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE); + if( rc ) goto abort_due_to_error; + }else if( pOp->p5 && !sqlite3WritableSchema(db) ){ + rc = sqlite3ReportError(SQLITE_CORRUPT_INDEX, __LINE__, "index corruption"); + goto abort_due_to_error; + } + assert( pC->deferredMoveto==0 ); + pC->cacheStatus = CACHE_STALE; + pC->seekResult = 0; + break; +} + +/* Opcode: DeferredSeek P1 * P3 P4 * +** Synopsis: Move P3 to P1.rowid if needed +** +** P1 is an open index cursor and P3 is a cursor on the corresponding +** table. This opcode does a deferred seek of the P3 table cursor +** to the row that corresponds to the current row of P1. +** +** This is a deferred seek. Nothing actually happens until +** the cursor is used to read a record. That way, if no reads +** occur, no unnecessary I/O happens. +** +** P4 may be an array of integers (type P4_INTARRAY) containing +** one entry for each column in the P3 table. If array entry a(i) +** is non-zero, then reading column a(i)-1 from cursor P3 is +** equivalent to performing the deferred seek and then reading column i +** from P1. This information is stored in P3 and used to redirect +** reads against P3 over to P1, thus possibly avoiding the need to +** seek and read cursor P3. +*/ +/* Opcode: IdxRowid P1 P2 * * * +** Synopsis: r[P2]=rowid +** +** Write into register P2 an integer which is the last entry in the record at +** the end of the index key pointed to by cursor P1. This integer should be +** the rowid of the table entry to which this index entry points. +** +** See also: Rowid, MakeRecord. +*/ +case OP_DeferredSeek: /* ncycle */ +case OP_IdxRowid: { /* out2, ncycle */ + VdbeCursor *pC; /* The P1 index cursor */ + VdbeCursor *pTabCur; /* The P2 table cursor (OP_DeferredSeek only) */ + i64 rowid; /* Rowid that P1 current points to */ + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE || IsNullCursor(pC) ); + assert( pC->uc.pCursor!=0 ); + assert( pC->isTable==0 || IsNullCursor(pC) ); + assert( pC->deferredMoveto==0 ); + assert( !pC->nullRow || pOp->opcode==OP_IdxRowid ); + + /* The IdxRowid and Seek opcodes are combined because of the commonality + ** of sqlite3VdbeCursorRestore() and sqlite3VdbeIdxRowid(). */ + rc = sqlite3VdbeCursorRestore(pC); + + /* sqlite3VdbeCursorRestore() may fail if the cursor has been disturbed + ** since it was last positioned and an error (e.g. OOM or an IO error) + ** occurs while trying to reposition it. */ + if( rc!=SQLITE_OK ) goto abort_due_to_error; + + if( !pC->nullRow ){ + rowid = 0; /* Not needed. Only used to silence a warning. */ + rc = sqlite3VdbeIdxRowid(db, pC->uc.pCursor, &rowid); + if( rc!=SQLITE_OK ){ + goto abort_due_to_error; + } + if( pOp->opcode==OP_DeferredSeek ){ + assert( pOp->p3>=0 && pOp->p3<p->nCursor ); + pTabCur = p->apCsr[pOp->p3]; + assert( pTabCur!=0 ); + assert( pTabCur->eCurType==CURTYPE_BTREE ); + assert( pTabCur->uc.pCursor!=0 ); + assert( pTabCur->isTable ); + pTabCur->nullRow = 0; + pTabCur->movetoTarget = rowid; + pTabCur->deferredMoveto = 1; + pTabCur->cacheStatus = CACHE_STALE; + assert( pOp->p4type==P4_INTARRAY || pOp->p4.ai==0 ); + assert( !pTabCur->isEphemeral ); + pTabCur->ub.aAltMap = pOp->p4.ai; + assert( !pC->isEphemeral ); + pTabCur->pAltCursor = pC; + }else{ + pOut = out2Prerelease(p, pOp); + pOut->u.i = rowid; + } + }else{ + assert( pOp->opcode==OP_IdxRowid ); + sqlite3VdbeMemSetNull(&aMem[pOp->p2]); + } + break; +} + +/* Opcode: FinishSeek P1 * * * * +** +** If cursor P1 was previously moved via OP_DeferredSeek, complete that +** seek operation now, without further delay. If the cursor seek has +** already occurred, this instruction is a no-op. +*/ +case OP_FinishSeek: { /* ncycle */ + VdbeCursor *pC; /* The P1 index cursor */ + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + if( pC->deferredMoveto ){ + rc = sqlite3VdbeFinishMoveto(pC); + if( rc ) goto abort_due_to_error; + } + break; +} + +/* Opcode: IdxGE P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** The P4 register values beginning with P3 form an unpacked index +** key that omits the PRIMARY KEY. Compare this key value against the index +** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID +** fields at the end. +** +** If the P1 index entry is greater than or equal to the key value +** then jump to P2. Otherwise fall through to the next instruction. +*/ +/* Opcode: IdxGT P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** The P4 register values beginning with P3 form an unpacked index +** key that omits the PRIMARY KEY. Compare this key value against the index +** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID +** fields at the end. +** +** If the P1 index entry is greater than the key value +** then jump to P2. Otherwise fall through to the next instruction. +*/ +/* Opcode: IdxLT P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** The P4 register values beginning with P3 form an unpacked index +** key that omits the PRIMARY KEY or ROWID. Compare this key value against +** the index that P1 is currently pointing to, ignoring the PRIMARY KEY or +** ROWID on the P1 index. +** +** If the P1 index entry is less than the key value then jump to P2. +** Otherwise fall through to the next instruction. +*/ +/* Opcode: IdxLE P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** The P4 register values beginning with P3 form an unpacked index +** key that omits the PRIMARY KEY or ROWID. Compare this key value against +** the index that P1 is currently pointing to, ignoring the PRIMARY KEY or +** ROWID on the P1 index. +** +** If the P1 index entry is less than or equal to the key value then jump +** to P2. Otherwise fall through to the next instruction. +*/ +case OP_IdxLE: /* jump, ncycle */ +case OP_IdxGT: /* jump, ncycle */ +case OP_IdxLT: /* jump, ncycle */ +case OP_IdxGE: { /* jump, ncycle */ + VdbeCursor *pC; + int res; + UnpackedRecord r; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->isOrdered ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0); + assert( pC->deferredMoveto==0 ); + assert( pOp->p4type==P4_INT32 ); + r.pKeyInfo = pC->pKeyInfo; + r.nField = (u16)pOp->p4.i; + if( pOp->opcode<OP_IdxLT ){ + assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxGT ); + r.default_rc = -1; + }else{ + assert( pOp->opcode==OP_IdxGE || pOp->opcode==OP_IdxLT ); + r.default_rc = 0; + } + r.aMem = &aMem[pOp->p3]; +#ifdef SQLITE_DEBUG + { + int i; + for(i=0; i<r.nField; i++){ + assert( memIsValid(&r.aMem[i]) ); + REGISTER_TRACE(pOp->p3+i, &aMem[pOp->p3+i]); + } + } +#endif + + /* Inlined version of sqlite3VdbeIdxKeyCompare() */ + { + i64 nCellKey = 0; + BtCursor *pCur; + Mem m; + + assert( pC->eCurType==CURTYPE_BTREE ); + pCur = pC->uc.pCursor; + assert( sqlite3BtreeCursorIsValid(pCur) ); + nCellKey = sqlite3BtreePayloadSize(pCur); + /* nCellKey will always be between 0 and 0xffffffff because of the way + ** that btreeParseCellPtr() and sqlite3GetVarint32() are implemented */ + if( nCellKey<=0 || nCellKey>0x7fffffff ){ + rc = SQLITE_CORRUPT_BKPT; + goto abort_due_to_error; + } + sqlite3VdbeMemInit(&m, db, 0); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pCur, (u32)nCellKey, &m); + if( rc ) goto abort_due_to_error; + res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, &r, 0); + sqlite3VdbeMemReleaseMalloc(&m); + } + /* End of inlined sqlite3VdbeIdxKeyCompare() */ + + assert( (OP_IdxLE&1)==(OP_IdxLT&1) && (OP_IdxGE&1)==(OP_IdxGT&1) ); + if( (pOp->opcode&1)==(OP_IdxLT&1) ){ + assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxLT ); + res = -res; + }else{ + assert( pOp->opcode==OP_IdxGE || pOp->opcode==OP_IdxGT ); + res++; + } + VdbeBranchTaken(res>0,2); + assert( rc==SQLITE_OK ); + if( res>0 ) goto jump_to_p2; + break; +} + +/* Opcode: Destroy P1 P2 P3 * * +** +** Delete an entire database table or index whose root page in the database +** file is given by P1. +** +** The table being destroyed is in the main database file if P3==0. If +** P3==1 then the table to be destroyed is in the auxiliary database file +** that is used to store tables create using CREATE TEMPORARY TABLE. +** +** If AUTOVACUUM is enabled then it is possible that another root page +** might be moved into the newly deleted root page in order to keep all +** root pages contiguous at the beginning of the database. The former +** value of the root page that moved - its value before the move occurred - +** is stored in register P2. If no page movement was required (because the +** table being dropped was already the last one in the database) then a +** zero is stored in register P2. If AUTOVACUUM is disabled then a zero +** is stored in register P2. +** +** This opcode throws an error if there are any active reader VMs when +** it is invoked. This is done to avoid the difficulty associated with +** updating existing cursors when a root page is moved in an AUTOVACUUM +** database. This error is thrown even if the database is not an AUTOVACUUM +** db in order to avoid introducing an incompatibility between autovacuum +** and non-autovacuum modes. +** +** See also: Clear +*/ +case OP_Destroy: { /* out2 */ + int iMoved; + int iDb; + + sqlite3VdbeIncrWriteCounter(p, 0); + assert( p->readOnly==0 ); + assert( pOp->p1>1 ); + pOut = out2Prerelease(p, pOp); + pOut->flags = MEM_Null; + if( db->nVdbeRead > db->nVDestroy+1 ){ + rc = SQLITE_LOCKED; + p->errorAction = OE_Abort; + goto abort_due_to_error; + }else{ + iDb = pOp->p3; + assert( DbMaskTest(p->btreeMask, iDb) ); + iMoved = 0; /* Not needed. Only to silence a warning. */ + rc = sqlite3BtreeDropTable(db->aDb[iDb].pBt, pOp->p1, &iMoved); + pOut->flags = MEM_Int; + pOut->u.i = iMoved; + if( rc ) goto abort_due_to_error; +#ifndef SQLITE_OMIT_AUTOVACUUM + if( iMoved!=0 ){ + sqlite3RootPageMoved(db, iDb, iMoved, pOp->p1); + /* All OP_Destroy operations occur on the same btree */ + assert( resetSchemaOnFault==0 || resetSchemaOnFault==iDb+1 ); + resetSchemaOnFault = iDb+1; + } +#endif + } + break; +} + +/* Opcode: Clear P1 P2 P3 +** +** Delete all contents of the database table or index whose root page +** in the database file is given by P1. But, unlike Destroy, do not +** remove the table or index from the database file. +** +** The table being cleared is in the main database file if P2==0. If +** P2==1 then the table to be cleared is in the auxiliary database file +** that is used to store tables create using CREATE TEMPORARY TABLE. +** +** If the P3 value is non-zero, then the row change count is incremented +** by the number of rows in the table being cleared. If P3 is greater +** than zero, then the value stored in register P3 is also incremented +** by the number of rows in the table being cleared. +** +** See also: Destroy +*/ +case OP_Clear: { + i64 nChange; + + sqlite3VdbeIncrWriteCounter(p, 0); + nChange = 0; + assert( p->readOnly==0 ); + assert( DbMaskTest(p->btreeMask, pOp->p2) ); + rc = sqlite3BtreeClearTable(db->aDb[pOp->p2].pBt, (u32)pOp->p1, &nChange); + if( pOp->p3 ){ + p->nChange += nChange; + if( pOp->p3>0 ){ + assert( memIsValid(&aMem[pOp->p3]) ); + memAboutToChange(p, &aMem[pOp->p3]); + aMem[pOp->p3].u.i += nChange; + } + } + if( rc ) goto abort_due_to_error; + break; +} + +/* Opcode: ResetSorter P1 * * * * +** +** Delete all contents from the ephemeral table or sorter +** that is open on cursor P1. +** +** This opcode only works for cursors used for sorting and +** opened with OP_OpenEphemeral or OP_SorterOpen. +*/ +case OP_ResetSorter: { + VdbeCursor *pC; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + if( isSorter(pC) ){ + sqlite3VdbeSorterReset(db, pC->uc.pSorter); + }else{ + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->isEphemeral ); + rc = sqlite3BtreeClearTableOfCursor(pC->uc.pCursor); + if( rc ) goto abort_due_to_error; + } + break; +} + +/* Opcode: CreateBtree P1 P2 P3 * * +** Synopsis: r[P2]=root iDb=P1 flags=P3 +** +** Allocate a new b-tree in the main database file if P1==0 or in the +** TEMP database file if P1==1 or in an attached database if +** P1>1. The P3 argument must be 1 (BTREE_INTKEY) for a rowid table +** it must be 2 (BTREE_BLOBKEY) for an index or WITHOUT ROWID table. +** The root page number of the new b-tree is stored in register P2. +*/ +case OP_CreateBtree: { /* out2 */ + Pgno pgno; + Db *pDb; + + sqlite3VdbeIncrWriteCounter(p, 0); + pOut = out2Prerelease(p, pOp); + pgno = 0; + assert( pOp->p3==BTREE_INTKEY || pOp->p3==BTREE_BLOBKEY ); + assert( pOp->p1>=0 && pOp->p1<db->nDb ); + assert( DbMaskTest(p->btreeMask, pOp->p1) ); + assert( p->readOnly==0 ); + pDb = &db->aDb[pOp->p1]; + assert( pDb->pBt!=0 ); + rc = sqlite3BtreeCreateTable(pDb->pBt, &pgno, pOp->p3); + if( rc ) goto abort_due_to_error; + pOut->u.i = pgno; + break; +} + +/* Opcode: SqlExec * * * P4 * +** +** Run the SQL statement or statements specified in the P4 string. +*/ +case OP_SqlExec: { + sqlite3VdbeIncrWriteCounter(p, 0); + db->nSqlExec++; + rc = sqlite3_exec(db, pOp->p4.z, 0, 0, 0); + db->nSqlExec--; + if( rc ) goto abort_due_to_error; + break; +} + +/* Opcode: ParseSchema P1 * * P4 * +** +** Read and parse all entries from the schema table of database P1 +** that match the WHERE clause P4. If P4 is a NULL pointer, then the +** entire schema for P1 is reparsed. +** +** This opcode invokes the parser to create a new virtual machine, +** then runs the new virtual machine. It is thus a re-entrant opcode. +*/ +case OP_ParseSchema: { + int iDb; + const char *zSchema; + char *zSql; + InitData initData; + + /* Any prepared statement that invokes this opcode will hold mutexes + ** on every btree. This is a prerequisite for invoking + ** sqlite3InitCallback(). + */ +#ifdef SQLITE_DEBUG + for(iDb=0; iDb<db->nDb; iDb++){ + assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) ); + } +#endif + + iDb = pOp->p1; + assert( iDb>=0 && iDb<db->nDb ); + assert( DbHasProperty(db, iDb, DB_SchemaLoaded) + || db->mallocFailed + || (CORRUPT_DB && (db->flags & SQLITE_NoSchemaError)!=0) ); + +#ifndef SQLITE_OMIT_ALTERTABLE + if( pOp->p4.z==0 ){ + sqlite3SchemaClear(db->aDb[iDb].pSchema); + db->mDbFlags &= ~DBFLAG_SchemaKnownOk; + rc = sqlite3InitOne(db, iDb, &p->zErrMsg, pOp->p5); + db->mDbFlags |= DBFLAG_SchemaChange; + p->expired = 0; + }else +#endif + { + zSchema = LEGACY_SCHEMA_TABLE; + initData.db = db; + initData.iDb = iDb; + initData.pzErrMsg = &p->zErrMsg; + initData.mInitFlags = 0; + initData.mxPage = sqlite3BtreeLastPage(db->aDb[iDb].pBt); + zSql = sqlite3MPrintf(db, + "SELECT*FROM\"%w\".%s WHERE %s ORDER BY rowid", + db->aDb[iDb].zDbSName, zSchema, pOp->p4.z); + if( zSql==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + assert( db->init.busy==0 ); + db->init.busy = 1; + initData.rc = SQLITE_OK; + initData.nInitRow = 0; + assert( !db->mallocFailed ); + rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0); + if( rc==SQLITE_OK ) rc = initData.rc; + if( rc==SQLITE_OK && initData.nInitRow==0 ){ + /* The OP_ParseSchema opcode with a non-NULL P4 argument should parse + ** at least one SQL statement. Any less than that indicates that + ** the sqlite_schema table is corrupt. */ + rc = SQLITE_CORRUPT_BKPT; + } + sqlite3DbFreeNN(db, zSql); + db->init.busy = 0; + } + } + if( rc ){ + sqlite3ResetAllSchemasOfConnection(db); + if( rc==SQLITE_NOMEM ){ + goto no_mem; + } + goto abort_due_to_error; + } + break; +} + +#if !defined(SQLITE_OMIT_ANALYZE) +/* Opcode: LoadAnalysis P1 * * * * +** +** Read the sqlite_stat1 table for database P1 and load the content +** of that table into the internal index hash table. This will cause +** the analysis to be used when preparing all subsequent queries. +*/ +case OP_LoadAnalysis: { + assert( pOp->p1>=0 && pOp->p1<db->nDb ); + rc = sqlite3AnalysisLoad(db, pOp->p1); + if( rc ) goto abort_due_to_error; + break; +} +#endif /* !defined(SQLITE_OMIT_ANALYZE) */ + +/* Opcode: DropTable P1 * * P4 * +** +** Remove the internal (in-memory) data structures that describe +** the table named P4 in database P1. This is called after a table +** is dropped from disk (using the Destroy opcode) in order to keep +** the internal representation of the +** schema consistent with what is on disk. +*/ +case OP_DropTable: { + sqlite3VdbeIncrWriteCounter(p, 0); + sqlite3UnlinkAndDeleteTable(db, pOp->p1, pOp->p4.z); + break; +} + +/* Opcode: DropIndex P1 * * P4 * +** +** Remove the internal (in-memory) data structures that describe +** the index named P4 in database P1. This is called after an index +** is dropped from disk (using the Destroy opcode) +** in order to keep the internal representation of the +** schema consistent with what is on disk. +*/ +case OP_DropIndex: { + sqlite3VdbeIncrWriteCounter(p, 0); + sqlite3UnlinkAndDeleteIndex(db, pOp->p1, pOp->p4.z); + break; +} + +/* Opcode: DropTrigger P1 * * P4 * +** +** Remove the internal (in-memory) data structures that describe +** the trigger named P4 in database P1. This is called after a trigger +** is dropped from disk (using the Destroy opcode) in order to keep +** the internal representation of the +** schema consistent with what is on disk. +*/ +case OP_DropTrigger: { + sqlite3VdbeIncrWriteCounter(p, 0); + sqlite3UnlinkAndDeleteTrigger(db, pOp->p1, pOp->p4.z); + break; +} + + +#ifndef SQLITE_OMIT_INTEGRITY_CHECK +/* Opcode: IntegrityCk P1 P2 P3 P4 P5 +** +** Do an analysis of the currently open database. Store in +** register P1 the text of an error message describing any problems. +** If no problems are found, store a NULL in register P1. +** +** The register P3 contains one less than the maximum number of allowed errors. +** At most reg(P3) errors will be reported. +** In other words, the analysis stops as soon as reg(P1) errors are +** seen. Reg(P1) is updated with the number of errors remaining. +** +** The root page numbers of all tables in the database are integers +** stored in P4_INTARRAY argument. +** +** If P5 is not zero, the check is done on the auxiliary database +** file, not the main database file. +** +** This opcode is used to implement the integrity_check pragma. +*/ +case OP_IntegrityCk: { + int nRoot; /* Number of tables to check. (Number of root pages.) */ + Pgno *aRoot; /* Array of rootpage numbers for tables to be checked */ + int nErr; /* Number of errors reported */ + char *z; /* Text of the error report */ + Mem *pnErr; /* Register keeping track of errors remaining */ + + assert( p->bIsReader ); + nRoot = pOp->p2; + aRoot = pOp->p4.ai; + assert( nRoot>0 ); + assert( aRoot[0]==(Pgno)nRoot ); + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); + pnErr = &aMem[pOp->p3]; + assert( (pnErr->flags & MEM_Int)!=0 ); + assert( (pnErr->flags & (MEM_Str|MEM_Blob))==0 ); + pIn1 = &aMem[pOp->p1]; + assert( pOp->p5<db->nDb ); + assert( DbMaskTest(p->btreeMask, pOp->p5) ); + rc = sqlite3BtreeIntegrityCheck(db, db->aDb[pOp->p5].pBt, &aRoot[1], nRoot, + (int)pnErr->u.i+1, &nErr, &z); + sqlite3VdbeMemSetNull(pIn1); + if( nErr==0 ){ + assert( z==0 ); + }else if( rc ){ + sqlite3_free(z); + goto abort_due_to_error; + }else{ + pnErr->u.i -= nErr-1; + sqlite3VdbeMemSetStr(pIn1, z, -1, SQLITE_UTF8, sqlite3_free); + } + UPDATE_MAX_BLOBSIZE(pIn1); + sqlite3VdbeChangeEncoding(pIn1, encoding); + goto check_for_interrupt; +} +#endif /* SQLITE_OMIT_INTEGRITY_CHECK */ + +/* Opcode: RowSetAdd P1 P2 * * * +** Synopsis: rowset(P1)=r[P2] +** +** Insert the integer value held by register P2 into a RowSet object +** held in register P1. +** +** An assertion fails if P2 is not an integer. +*/ +case OP_RowSetAdd: { /* in1, in2 */ + pIn1 = &aMem[pOp->p1]; + pIn2 = &aMem[pOp->p2]; + assert( (pIn2->flags & MEM_Int)!=0 ); + if( (pIn1->flags & MEM_Blob)==0 ){ + if( sqlite3VdbeMemSetRowSet(pIn1) ) goto no_mem; + } + assert( sqlite3VdbeMemIsRowSet(pIn1) ); + sqlite3RowSetInsert((RowSet*)pIn1->z, pIn2->u.i); + break; +} + +/* Opcode: RowSetRead P1 P2 P3 * * +** Synopsis: r[P3]=rowset(P1) +** +** Extract the smallest value from the RowSet object in P1 +** and put that value into register P3. +** Or, if RowSet object P1 is initially empty, leave P3 +** unchanged and jump to instruction P2. +*/ +case OP_RowSetRead: { /* jump, in1, out3 */ + i64 val; + + pIn1 = &aMem[pOp->p1]; + assert( (pIn1->flags & MEM_Blob)==0 || sqlite3VdbeMemIsRowSet(pIn1) ); + if( (pIn1->flags & MEM_Blob)==0 + || sqlite3RowSetNext((RowSet*)pIn1->z, &val)==0 + ){ + /* The boolean index is empty */ + sqlite3VdbeMemSetNull(pIn1); + VdbeBranchTaken(1,2); + goto jump_to_p2_and_check_for_interrupt; + }else{ + /* A value was pulled from the index */ + VdbeBranchTaken(0,2); + sqlite3VdbeMemSetInt64(&aMem[pOp->p3], val); + } + goto check_for_interrupt; +} + +/* Opcode: RowSetTest P1 P2 P3 P4 +** Synopsis: if r[P3] in rowset(P1) goto P2 +** +** Register P3 is assumed to hold a 64-bit integer value. If register P1 +** contains a RowSet object and that RowSet object contains +** the value held in P3, jump to register P2. Otherwise, insert the +** integer in P3 into the RowSet and continue on to the +** next opcode. +** +** The RowSet object is optimized for the case where sets of integers +** are inserted in distinct phases, which each set contains no duplicates. +** Each set is identified by a unique P4 value. The first set +** must have P4==0, the final set must have P4==-1, and for all other sets +** must have P4>0. +** +** This allows optimizations: (a) when P4==0 there is no need to test +** the RowSet object for P3, as it is guaranteed not to contain it, +** (b) when P4==-1 there is no need to insert the value, as it will +** never be tested for, and (c) when a value that is part of set X is +** inserted, there is no need to search to see if the same value was +** previously inserted as part of set X (only if it was previously +** inserted as part of some other set). +*/ +case OP_RowSetTest: { /* jump, in1, in3 */ + int iSet; + int exists; + + pIn1 = &aMem[pOp->p1]; + pIn3 = &aMem[pOp->p3]; + iSet = pOp->p4.i; + assert( pIn3->flags&MEM_Int ); + + /* If there is anything other than a rowset object in memory cell P1, + ** delete it now and initialize P1 with an empty rowset + */ + if( (pIn1->flags & MEM_Blob)==0 ){ + if( sqlite3VdbeMemSetRowSet(pIn1) ) goto no_mem; + } + assert( sqlite3VdbeMemIsRowSet(pIn1) ); + assert( pOp->p4type==P4_INT32 ); + assert( iSet==-1 || iSet>=0 ); + if( iSet ){ + exists = sqlite3RowSetTest((RowSet*)pIn1->z, iSet, pIn3->u.i); + VdbeBranchTaken(exists!=0,2); + if( exists ) goto jump_to_p2; + } + if( iSet>=0 ){ + sqlite3RowSetInsert((RowSet*)pIn1->z, pIn3->u.i); + } + break; +} + + +#ifndef SQLITE_OMIT_TRIGGER + +/* Opcode: Program P1 P2 P3 P4 P5 +** +** Execute the trigger program passed as P4 (type P4_SUBPROGRAM). +** +** P1 contains the address of the memory cell that contains the first memory +** cell in an array of values used as arguments to the sub-program. P2 +** contains the address to jump to if the sub-program throws an IGNORE +** exception using the RAISE() function. Register P3 contains the address +** of a memory cell in this (the parent) VM that is used to allocate the +** memory required by the sub-vdbe at runtime. +** +** P4 is a pointer to the VM containing the trigger program. +** +** If P5 is non-zero, then recursive program invocation is enabled. +*/ +case OP_Program: { /* jump */ + int nMem; /* Number of memory registers for sub-program */ + int nByte; /* Bytes of runtime space required for sub-program */ + Mem *pRt; /* Register to allocate runtime space */ + Mem *pMem; /* Used to iterate through memory cells */ + Mem *pEnd; /* Last memory cell in new array */ + VdbeFrame *pFrame; /* New vdbe frame to execute in */ + SubProgram *pProgram; /* Sub-program to execute */ + void *t; /* Token identifying trigger */ + + pProgram = pOp->p4.pProgram; + pRt = &aMem[pOp->p3]; + assert( pProgram->nOp>0 ); + + /* If the p5 flag is clear, then recursive invocation of triggers is + ** disabled for backwards compatibility (p5 is set if this sub-program + ** is really a trigger, not a foreign key action, and the flag set + ** and cleared by the "PRAGMA recursive_triggers" command is clear). + ** + ** It is recursive invocation of triggers, at the SQL level, that is + ** disabled. In some cases a single trigger may generate more than one + ** SubProgram (if the trigger may be executed with more than one different + ** ON CONFLICT algorithm). SubProgram structures associated with a + ** single trigger all have the same value for the SubProgram.token + ** variable. */ + if( pOp->p5 ){ + t = pProgram->token; + for(pFrame=p->pFrame; pFrame && pFrame->token!=t; pFrame=pFrame->pParent); + if( pFrame ) break; + } + + if( p->nFrame>=db->aLimit[SQLITE_LIMIT_TRIGGER_DEPTH] ){ + rc = SQLITE_ERROR; + sqlite3VdbeError(p, "too many levels of trigger recursion"); + goto abort_due_to_error; + } + + /* Register pRt is used to store the memory required to save the state + ** of the current program, and the memory required at runtime to execute + ** the trigger program. If this trigger has been fired before, then pRt + ** is already allocated. Otherwise, it must be initialized. */ + if( (pRt->flags&MEM_Blob)==0 ){ + /* SubProgram.nMem is set to the number of memory cells used by the + ** program stored in SubProgram.aOp. As well as these, one memory + ** cell is required for each cursor used by the program. Set local + ** variable nMem (and later, VdbeFrame.nChildMem) to this value. + */ + nMem = pProgram->nMem + pProgram->nCsr; + assert( nMem>0 ); + if( pProgram->nCsr==0 ) nMem++; + nByte = ROUND8(sizeof(VdbeFrame)) + + nMem * sizeof(Mem) + + pProgram->nCsr * sizeof(VdbeCursor*) + + (pProgram->nOp + 7)/8; + pFrame = sqlite3DbMallocZero(db, nByte); + if( !pFrame ){ + goto no_mem; + } + sqlite3VdbeMemRelease(pRt); + pRt->flags = MEM_Blob|MEM_Dyn; + pRt->z = (char*)pFrame; + pRt->n = nByte; + pRt->xDel = sqlite3VdbeFrameMemDel; + + pFrame->v = p; + pFrame->nChildMem = nMem; + pFrame->nChildCsr = pProgram->nCsr; + pFrame->pc = (int)(pOp - aOp); + pFrame->aMem = p->aMem; + pFrame->nMem = p->nMem; + pFrame->apCsr = p->apCsr; + pFrame->nCursor = p->nCursor; + pFrame->aOp = p->aOp; + pFrame->nOp = p->nOp; + pFrame->token = pProgram->token; +#ifdef SQLITE_DEBUG + pFrame->iFrameMagic = SQLITE_FRAME_MAGIC; +#endif + + pEnd = &VdbeFrameMem(pFrame)[pFrame->nChildMem]; + for(pMem=VdbeFrameMem(pFrame); pMem!=pEnd; pMem++){ + pMem->flags = MEM_Undefined; + pMem->db = db; + } + }else{ + pFrame = (VdbeFrame*)pRt->z; + assert( pRt->xDel==sqlite3VdbeFrameMemDel ); + assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem + || (pProgram->nCsr==0 && pProgram->nMem+1==pFrame->nChildMem) ); + assert( pProgram->nCsr==pFrame->nChildCsr ); + assert( (int)(pOp - aOp)==pFrame->pc ); + } + + p->nFrame++; + pFrame->pParent = p->pFrame; + pFrame->lastRowid = db->lastRowid; + pFrame->nChange = p->nChange; + pFrame->nDbChange = p->db->nChange; + assert( pFrame->pAuxData==0 ); + pFrame->pAuxData = p->pAuxData; + p->pAuxData = 0; + p->nChange = 0; + p->pFrame = pFrame; + p->aMem = aMem = VdbeFrameMem(pFrame); + p->nMem = pFrame->nChildMem; + p->nCursor = (u16)pFrame->nChildCsr; + p->apCsr = (VdbeCursor **)&aMem[p->nMem]; + pFrame->aOnce = (u8*)&p->apCsr[pProgram->nCsr]; + memset(pFrame->aOnce, 0, (pProgram->nOp + 7)/8); + p->aOp = aOp = pProgram->aOp; + p->nOp = pProgram->nOp; +#ifdef SQLITE_DEBUG + /* Verify that second and subsequent executions of the same trigger do not + ** try to reuse register values from the first use. */ + { + int i; + for(i=0; i<p->nMem; i++){ + aMem[i].pScopyFrom = 0; /* Prevent false-positive AboutToChange() errs */ + MemSetTypeFlag(&aMem[i], MEM_Undefined); /* Fault if this reg is reused */ + } + } +#endif + pOp = &aOp[-1]; + goto check_for_interrupt; +} + +/* Opcode: Param P1 P2 * * * +** +** This opcode is only ever present in sub-programs called via the +** OP_Program instruction. Copy a value currently stored in a memory +** cell of the calling (parent) frame to cell P2 in the current frames +** address space. This is used by trigger programs to access the new.* +** and old.* values. +** +** The address of the cell in the parent frame is determined by adding +** the value of the P1 argument to the value of the P1 argument to the +** calling OP_Program instruction. +*/ +case OP_Param: { /* out2 */ + VdbeFrame *pFrame; + Mem *pIn; + pOut = out2Prerelease(p, pOp); + pFrame = p->pFrame; + pIn = &pFrame->aMem[pOp->p1 + pFrame->aOp[pFrame->pc].p1]; + sqlite3VdbeMemShallowCopy(pOut, pIn, MEM_Ephem); + break; +} + +#endif /* #ifndef SQLITE_OMIT_TRIGGER */ + +#ifndef SQLITE_OMIT_FOREIGN_KEY +/* Opcode: FkCounter P1 P2 * * * +** Synopsis: fkctr[P1]+=P2 +** +** Increment a "constraint counter" by P2 (P2 may be negative or positive). +** If P1 is non-zero, the database constraint counter is incremented +** (deferred foreign key constraints). Otherwise, if P1 is zero, the +** statement counter is incremented (immediate foreign key constraints). +*/ +case OP_FkCounter: { + if( db->flags & SQLITE_DeferFKs ){ + db->nDeferredImmCons += pOp->p2; + }else if( pOp->p1 ){ + db->nDeferredCons += pOp->p2; + }else{ + p->nFkConstraint += pOp->p2; + } + break; +} + +/* Opcode: FkIfZero P1 P2 * * * +** Synopsis: if fkctr[P1]==0 goto P2 +** +** This opcode tests if a foreign key constraint-counter is currently zero. +** If so, jump to instruction P2. Otherwise, fall through to the next +** instruction. +** +** If P1 is non-zero, then the jump is taken if the database constraint-counter +** is zero (the one that counts deferred constraint violations). If P1 is +** zero, the jump is taken if the statement constraint-counter is zero +** (immediate foreign key constraint violations). +*/ +case OP_FkIfZero: { /* jump */ + if( pOp->p1 ){ + VdbeBranchTaken(db->nDeferredCons==0 && db->nDeferredImmCons==0, 2); + if( db->nDeferredCons==0 && db->nDeferredImmCons==0 ) goto jump_to_p2; + }else{ + VdbeBranchTaken(p->nFkConstraint==0 && db->nDeferredImmCons==0, 2); + if( p->nFkConstraint==0 && db->nDeferredImmCons==0 ) goto jump_to_p2; + } + break; +} +#endif /* #ifndef SQLITE_OMIT_FOREIGN_KEY */ + +#ifndef SQLITE_OMIT_AUTOINCREMENT +/* Opcode: MemMax P1 P2 * * * +** Synopsis: r[P1]=max(r[P1],r[P2]) +** +** P1 is a register in the root frame of this VM (the root frame is +** different from the current frame if this instruction is being executed +** within a sub-program). Set the value of register P1 to the maximum of +** its current value and the value in register P2. +** +** This instruction throws an error if the memory cell is not initially +** an integer. +*/ +case OP_MemMax: { /* in2 */ + VdbeFrame *pFrame; + if( p->pFrame ){ + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + pIn1 = &pFrame->aMem[pOp->p1]; + }else{ + pIn1 = &aMem[pOp->p1]; + } + assert( memIsValid(pIn1) ); + sqlite3VdbeMemIntegerify(pIn1); + pIn2 = &aMem[pOp->p2]; + sqlite3VdbeMemIntegerify(pIn2); + if( pIn1->u.i<pIn2->u.i){ + pIn1->u.i = pIn2->u.i; + } + break; +} +#endif /* SQLITE_OMIT_AUTOINCREMENT */ + +/* Opcode: IfPos P1 P2 P3 * * +** Synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 +** +** Register P1 must contain an integer. +** If the value of register P1 is 1 or greater, subtract P3 from the +** value in P1 and jump to P2. +** +** If the initial value of register P1 is less than 1, then the +** value is unchanged and control passes through to the next instruction. +*/ +case OP_IfPos: { /* jump, in1 */ + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags&MEM_Int ); + VdbeBranchTaken( pIn1->u.i>0, 2); + if( pIn1->u.i>0 ){ + pIn1->u.i -= pOp->p3; + goto jump_to_p2; + } + break; +} + +/* Opcode: OffsetLimit P1 P2 P3 * * +** Synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) +** +** This opcode performs a commonly used computation associated with +** LIMIT and OFFSET processing. r[P1] holds the limit counter. r[P3] +** holds the offset counter. The opcode computes the combined value +** of the LIMIT and OFFSET and stores that value in r[P2]. The r[P2] +** value computed is the total number of rows that will need to be +** visited in order to complete the query. +** +** If r[P3] is zero or negative, that means there is no OFFSET +** and r[P2] is set to be the value of the LIMIT, r[P1]. +** +** if r[P1] is zero or negative, that means there is no LIMIT +** and r[P2] is set to -1. +** +** Otherwise, r[P2] is set to the sum of r[P1] and r[P3]. +*/ +case OP_OffsetLimit: { /* in1, out2, in3 */ + i64 x; + pIn1 = &aMem[pOp->p1]; + pIn3 = &aMem[pOp->p3]; + pOut = out2Prerelease(p, pOp); + assert( pIn1->flags & MEM_Int ); + assert( pIn3->flags & MEM_Int ); + x = pIn1->u.i; + if( x<=0 || sqlite3AddInt64(&x, pIn3->u.i>0?pIn3->u.i:0) ){ + /* If the LIMIT is less than or equal to zero, loop forever. This + ** is documented. But also, if the LIMIT+OFFSET exceeds 2^63 then + ** also loop forever. This is undocumented. In fact, one could argue + ** that the loop should terminate. But assuming 1 billion iterations + ** per second (far exceeding the capabilities of any current hardware) + ** it would take nearly 300 years to actually reach the limit. So + ** looping forever is a reasonable approximation. */ + pOut->u.i = -1; + }else{ + pOut->u.i = x; + } + break; +} + +/* Opcode: IfNotZero P1 P2 * * * +** Synopsis: if r[P1]!=0 then r[P1]--, goto P2 +** +** Register P1 must contain an integer. If the content of register P1 is +** initially greater than zero, then decrement the value in register P1. +** If it is non-zero (negative or positive) and then also jump to P2. +** If register P1 is initially zero, leave it unchanged and fall through. +*/ +case OP_IfNotZero: { /* jump, in1 */ + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags&MEM_Int ); + VdbeBranchTaken(pIn1->u.i<0, 2); + if( pIn1->u.i ){ + if( pIn1->u.i>0 ) pIn1->u.i--; + goto jump_to_p2; + } + break; +} + +/* Opcode: DecrJumpZero P1 P2 * * * +** Synopsis: if (--r[P1])==0 goto P2 +** +** Register P1 must hold an integer. Decrement the value in P1 +** and jump to P2 if the new value is exactly zero. +*/ +case OP_DecrJumpZero: { /* jump, in1 */ + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags&MEM_Int ); + if( pIn1->u.i>SMALLEST_INT64 ) pIn1->u.i--; + VdbeBranchTaken(pIn1->u.i==0, 2); + if( pIn1->u.i==0 ) goto jump_to_p2; + break; +} + + +/* Opcode: AggStep * P2 P3 P4 P5 +** Synopsis: accum=r[P3] step(r[P2@P5]) +** +** Execute the xStep function for an aggregate. +** The function has P5 arguments. P4 is a pointer to the +** FuncDef structure that specifies the function. Register P3 is the +** accumulator. +** +** The P5 arguments are taken from register P2 and its +** successors. +*/ +/* Opcode: AggInverse * P2 P3 P4 P5 +** Synopsis: accum=r[P3] inverse(r[P2@P5]) +** +** Execute the xInverse function for an aggregate. +** The function has P5 arguments. P4 is a pointer to the +** FuncDef structure that specifies the function. Register P3 is the +** accumulator. +** +** The P5 arguments are taken from register P2 and its +** successors. +*/ +/* Opcode: AggStep1 P1 P2 P3 P4 P5 +** Synopsis: accum=r[P3] step(r[P2@P5]) +** +** Execute the xStep (if P1==0) or xInverse (if P1!=0) function for an +** aggregate. The function has P5 arguments. P4 is a pointer to the +** FuncDef structure that specifies the function. Register P3 is the +** accumulator. +** +** The P5 arguments are taken from register P2 and its +** successors. +** +** This opcode is initially coded as OP_AggStep0. On first evaluation, +** the FuncDef stored in P4 is converted into an sqlite3_context and +** the opcode is changed. In this way, the initialization of the +** sqlite3_context only happens once, instead of on each call to the +** step function. +*/ +case OP_AggInverse: +case OP_AggStep: { + int n; + sqlite3_context *pCtx; + + assert( pOp->p4type==P4_FUNCDEF ); + n = pOp->p5; + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); + assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1) ); + assert( pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+n ); + pCtx = sqlite3DbMallocRawNN(db, n*sizeof(sqlite3_value*) + + (sizeof(pCtx[0]) + sizeof(Mem) - sizeof(sqlite3_value*))); + if( pCtx==0 ) goto no_mem; + pCtx->pMem = 0; + pCtx->pOut = (Mem*)&(pCtx->argv[n]); + sqlite3VdbeMemInit(pCtx->pOut, db, MEM_Null); + pCtx->pFunc = pOp->p4.pFunc; + pCtx->iOp = (int)(pOp - aOp); + pCtx->pVdbe = p; + pCtx->skipFlag = 0; + pCtx->isError = 0; + pCtx->enc = encoding; + pCtx->argc = n; + pOp->p4type = P4_FUNCCTX; + pOp->p4.pCtx = pCtx; + + /* OP_AggInverse must have P1==1 and OP_AggStep must have P1==0 */ + assert( pOp->p1==(pOp->opcode==OP_AggInverse) ); + + pOp->opcode = OP_AggStep1; + /* Fall through into OP_AggStep */ + /* no break */ deliberate_fall_through +} +case OP_AggStep1: { + int i; + sqlite3_context *pCtx; + Mem *pMem; + + assert( pOp->p4type==P4_FUNCCTX ); + pCtx = pOp->p4.pCtx; + pMem = &aMem[pOp->p3]; + +#ifdef SQLITE_DEBUG + if( pOp->p1 ){ + /* This is an OP_AggInverse call. Verify that xStep has always + ** been called at least once prior to any xInverse call. */ + assert( pMem->uTemp==0x1122e0e3 ); + }else{ + /* This is an OP_AggStep call. Mark it as such. */ + pMem->uTemp = 0x1122e0e3; + } +#endif + + /* If this function is inside of a trigger, the register array in aMem[] + ** might change from one evaluation to the next. The next block of code + ** checks to see if the register array has changed, and if so it + ** reinitializes the relevant parts of the sqlite3_context object */ + if( pCtx->pMem != pMem ){ + pCtx->pMem = pMem; + for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; + } + +#ifdef SQLITE_DEBUG + for(i=0; i<pCtx->argc; i++){ + assert( memIsValid(pCtx->argv[i]) ); + REGISTER_TRACE(pOp->p2+i, pCtx->argv[i]); + } +#endif + + pMem->n++; + assert( pCtx->pOut->flags==MEM_Null ); + assert( pCtx->isError==0 ); + assert( pCtx->skipFlag==0 ); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pOp->p1 ){ + (pCtx->pFunc->xInverse)(pCtx,pCtx->argc,pCtx->argv); + }else +#endif + (pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */ + + if( pCtx->isError ){ + if( pCtx->isError>0 ){ + sqlite3VdbeError(p, "%s", sqlite3_value_text(pCtx->pOut)); + rc = pCtx->isError; + } + if( pCtx->skipFlag ){ + assert( pOp[-1].opcode==OP_CollSeq ); + i = pOp[-1].p1; + if( i ) sqlite3VdbeMemSetInt64(&aMem[i], 1); + pCtx->skipFlag = 0; + } + sqlite3VdbeMemRelease(pCtx->pOut); + pCtx->pOut->flags = MEM_Null; + pCtx->isError = 0; + if( rc ) goto abort_due_to_error; + } + assert( pCtx->pOut->flags==MEM_Null ); + assert( pCtx->skipFlag==0 ); + break; +} + +/* Opcode: AggFinal P1 P2 * P4 * +** Synopsis: accum=r[P1] N=P2 +** +** P1 is the memory location that is the accumulator for an aggregate +** or window function. Execute the finalizer function +** for an aggregate and store the result in P1. +** +** P2 is the number of arguments that the step function takes and +** P4 is a pointer to the FuncDef for this function. The P2 +** argument is not used by this opcode. It is only there to disambiguate +** functions that can take varying numbers of arguments. The +** P4 argument is only needed for the case where +** the step function was not previously called. +*/ +/* Opcode: AggValue * P2 P3 P4 * +** Synopsis: r[P3]=value N=P2 +** +** Invoke the xValue() function and store the result in register P3. +** +** P2 is the number of arguments that the step function takes and +** P4 is a pointer to the FuncDef for this function. The P2 +** argument is not used by this opcode. It is only there to disambiguate +** functions that can take varying numbers of arguments. The +** P4 argument is only needed for the case where +** the step function was not previously called. +*/ +case OP_AggValue: +case OP_AggFinal: { + Mem *pMem; + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + assert( pOp->p3==0 || pOp->opcode==OP_AggValue ); + pMem = &aMem[pOp->p1]; + assert( (pMem->flags & ~(MEM_Null|MEM_Agg))==0 ); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pOp->p3 ){ + memAboutToChange(p, &aMem[pOp->p3]); + rc = sqlite3VdbeMemAggValue(pMem, &aMem[pOp->p3], pOp->p4.pFunc); + pMem = &aMem[pOp->p3]; + }else +#endif + { + rc = sqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc); + } + + if( rc ){ + sqlite3VdbeError(p, "%s", sqlite3_value_text(pMem)); + goto abort_due_to_error; + } + sqlite3VdbeChangeEncoding(pMem, encoding); + UPDATE_MAX_BLOBSIZE(pMem); + REGISTER_TRACE((int)(pMem-aMem), pMem); + break; +} + +#ifndef SQLITE_OMIT_WAL +/* Opcode: Checkpoint P1 P2 P3 * * +** +** Checkpoint database P1. This is a no-op if P1 is not currently in +** WAL mode. Parameter P2 is one of SQLITE_CHECKPOINT_PASSIVE, FULL, +** RESTART, or TRUNCATE. Write 1 or 0 into mem[P3] if the checkpoint returns +** SQLITE_BUSY or not, respectively. Write the number of pages in the +** WAL after the checkpoint into mem[P3+1] and the number of pages +** in the WAL that have been checkpointed after the checkpoint +** completes into mem[P3+2]. However on an error, mem[P3+1] and +** mem[P3+2] are initialized to -1. +*/ +case OP_Checkpoint: { + int i; /* Loop counter */ + int aRes[3]; /* Results */ + Mem *pMem; /* Write results here */ + + assert( p->readOnly==0 ); + aRes[0] = 0; + aRes[1] = aRes[2] = -1; + assert( pOp->p2==SQLITE_CHECKPOINT_PASSIVE + || pOp->p2==SQLITE_CHECKPOINT_FULL + || pOp->p2==SQLITE_CHECKPOINT_RESTART + || pOp->p2==SQLITE_CHECKPOINT_TRUNCATE + ); + rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]); + if( rc ){ + if( rc!=SQLITE_BUSY ) goto abort_due_to_error; + rc = SQLITE_OK; + aRes[0] = 1; + } + for(i=0, pMem = &aMem[pOp->p3]; i<3; i++, pMem++){ + sqlite3VdbeMemSetInt64(pMem, (i64)aRes[i]); + } + break; +}; +#endif + +#ifndef SQLITE_OMIT_PRAGMA +/* Opcode: JournalMode P1 P2 P3 * * +** +** Change the journal mode of database P1 to P3. P3 must be one of the +** PAGER_JOURNALMODE_XXX values. If changing between the various rollback +** modes (delete, truncate, persist, off and memory), this is a simple +** operation. No IO is required. +** +** If changing into or out of WAL mode the procedure is more complicated. +** +** Write a string containing the final journal-mode to register P2. +*/ +case OP_JournalMode: { /* out2 */ + Btree *pBt; /* Btree to change journal mode of */ + Pager *pPager; /* Pager associated with pBt */ + int eNew; /* New journal mode */ + int eOld; /* The old journal mode */ +#ifndef SQLITE_OMIT_WAL + const char *zFilename; /* Name of database file for pPager */ +#endif + + pOut = out2Prerelease(p, pOp); + eNew = pOp->p3; + assert( eNew==PAGER_JOURNALMODE_DELETE + || eNew==PAGER_JOURNALMODE_TRUNCATE + || eNew==PAGER_JOURNALMODE_PERSIST + || eNew==PAGER_JOURNALMODE_OFF + || eNew==PAGER_JOURNALMODE_MEMORY + || eNew==PAGER_JOURNALMODE_WAL + || eNew==PAGER_JOURNALMODE_QUERY + ); + assert( pOp->p1>=0 && pOp->p1<db->nDb ); + assert( p->readOnly==0 ); + + pBt = db->aDb[pOp->p1].pBt; + pPager = sqlite3BtreePager(pBt); + eOld = sqlite3PagerGetJournalMode(pPager); + if( eNew==PAGER_JOURNALMODE_QUERY ) eNew = eOld; + assert( sqlite3BtreeHoldsMutex(pBt) ); + if( !sqlite3PagerOkToChangeJournalMode(pPager) ) eNew = eOld; + +#ifndef SQLITE_OMIT_WAL + zFilename = sqlite3PagerFilename(pPager, 1); + + /* Do not allow a transition to journal_mode=WAL for a database + ** in temporary storage or if the VFS does not support shared memory + */ + if( eNew==PAGER_JOURNALMODE_WAL + && (sqlite3Strlen30(zFilename)==0 /* Temp file */ + || !sqlite3PagerWalSupported(pPager)) /* No shared-memory support */ + ){ + eNew = eOld; + } + + if( (eNew!=eOld) + && (eOld==PAGER_JOURNALMODE_WAL || eNew==PAGER_JOURNALMODE_WAL) + ){ + if( !db->autoCommit || db->nVdbeRead>1 ){ + rc = SQLITE_ERROR; + sqlite3VdbeError(p, + "cannot change %s wal mode from within a transaction", + (eNew==PAGER_JOURNALMODE_WAL ? "into" : "out of") + ); + goto abort_due_to_error; + }else{ + + if( eOld==PAGER_JOURNALMODE_WAL ){ + /* If leaving WAL mode, close the log file. If successful, the call + ** to PagerCloseWal() checkpoints and deletes the write-ahead-log + ** file. An EXCLUSIVE lock may still be held on the database file + ** after a successful return. + */ + rc = sqlite3PagerCloseWal(pPager, db); + if( rc==SQLITE_OK ){ + sqlite3PagerSetJournalMode(pPager, eNew); + } + }else if( eOld==PAGER_JOURNALMODE_MEMORY ){ + /* Cannot transition directly from MEMORY to WAL. Use mode OFF + ** as an intermediate */ + sqlite3PagerSetJournalMode(pPager, PAGER_JOURNALMODE_OFF); + } + + /* Open a transaction on the database file. Regardless of the journal + ** mode, this transaction always uses a rollback journal. + */ + assert( sqlite3BtreeTxnState(pBt)!=SQLITE_TXN_WRITE ); + if( rc==SQLITE_OK ){ + rc = sqlite3BtreeSetVersion(pBt, (eNew==PAGER_JOURNALMODE_WAL ? 2 : 1)); + } + } + } +#endif /* ifndef SQLITE_OMIT_WAL */ + + if( rc ) eNew = eOld; + eNew = sqlite3PagerSetJournalMode(pPager, eNew); + + pOut->flags = MEM_Str|MEM_Static|MEM_Term; + pOut->z = (char *)sqlite3JournalModename(eNew); + pOut->n = sqlite3Strlen30(pOut->z); + pOut->enc = SQLITE_UTF8; + sqlite3VdbeChangeEncoding(pOut, encoding); + if( rc ) goto abort_due_to_error; + break; +}; +#endif /* SQLITE_OMIT_PRAGMA */ + +#if !defined(SQLITE_OMIT_VACUUM) && !defined(SQLITE_OMIT_ATTACH) +/* Opcode: Vacuum P1 P2 * * * +** +** Vacuum the entire database P1. P1 is 0 for "main", and 2 or more +** for an attached database. The "temp" database may not be vacuumed. +** +** If P2 is not zero, then it is a register holding a string which is +** the file into which the result of vacuum should be written. When +** P2 is zero, the vacuum overwrites the original database. +*/ +case OP_Vacuum: { + assert( p->readOnly==0 ); + rc = sqlite3RunVacuum(&p->zErrMsg, db, pOp->p1, + pOp->p2 ? &aMem[pOp->p2] : 0); + if( rc ) goto abort_due_to_error; + break; +} +#endif + +#if !defined(SQLITE_OMIT_AUTOVACUUM) +/* Opcode: IncrVacuum P1 P2 * * * +** +** Perform a single step of the incremental vacuum procedure on +** the P1 database. If the vacuum has finished, jump to instruction +** P2. Otherwise, fall through to the next instruction. +*/ +case OP_IncrVacuum: { /* jump */ + Btree *pBt; + + assert( pOp->p1>=0 && pOp->p1<db->nDb ); + assert( DbMaskTest(p->btreeMask, pOp->p1) ); + assert( p->readOnly==0 ); + pBt = db->aDb[pOp->p1].pBt; + rc = sqlite3BtreeIncrVacuum(pBt); + VdbeBranchTaken(rc==SQLITE_DONE,2); + if( rc ){ + if( rc!=SQLITE_DONE ) goto abort_due_to_error; + rc = SQLITE_OK; + goto jump_to_p2; + } + break; +} +#endif + +/* Opcode: Expire P1 P2 * * * +** +** Cause precompiled statements to expire. When an expired statement +** is executed using sqlite3_step() it will either automatically +** reprepare itself (if it was originally created using sqlite3_prepare_v2()) +** or it will fail with SQLITE_SCHEMA. +** +** If P1 is 0, then all SQL statements become expired. If P1 is non-zero, +** then only the currently executing statement is expired. +** +** If P2 is 0, then SQL statements are expired immediately. If P2 is 1, +** then running SQL statements are allowed to continue to run to completion. +** The P2==1 case occurs when a CREATE INDEX or similar schema change happens +** that might help the statement run faster but which does not affect the +** correctness of operation. +*/ +case OP_Expire: { + assert( pOp->p2==0 || pOp->p2==1 ); + if( !pOp->p1 ){ + sqlite3ExpirePreparedStatements(db, pOp->p2); + }else{ + p->expired = pOp->p2+1; + } + break; +} + +/* Opcode: CursorLock P1 * * * * +** +** Lock the btree to which cursor P1 is pointing so that the btree cannot be +** written by an other cursor. +*/ +case OP_CursorLock: { + VdbeCursor *pC; + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + sqlite3BtreeCursorPin(pC->uc.pCursor); + break; +} + +/* Opcode: CursorUnlock P1 * * * * +** +** Unlock the btree to which cursor P1 is pointing so that it can be +** written by other cursors. +*/ +case OP_CursorUnlock: { + VdbeCursor *pC; + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + sqlite3BtreeCursorUnpin(pC->uc.pCursor); + break; +} + +#ifndef SQLITE_OMIT_SHARED_CACHE +/* Opcode: TableLock P1 P2 P3 P4 * +** Synopsis: iDb=P1 root=P2 write=P3 +** +** Obtain a lock on a particular table. This instruction is only used when +** the shared-cache feature is enabled. +** +** P1 is the index of the database in sqlite3.aDb[] of the database +** on which the lock is acquired. A readlock is obtained if P3==0 or +** a write lock if P3==1. +** +** P2 contains the root-page of the table to lock. +** +** P4 contains a pointer to the name of the table being locked. This is only +** used to generate an error message if the lock cannot be obtained. +*/ +case OP_TableLock: { + u8 isWriteLock = (u8)pOp->p3; + if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommit) ){ + int p1 = pOp->p1; + assert( p1>=0 && p1<db->nDb ); + assert( DbMaskTest(p->btreeMask, p1) ); + assert( isWriteLock==0 || isWriteLock==1 ); + rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock); + if( rc ){ + if( (rc&0xFF)==SQLITE_LOCKED ){ + const char *z = pOp->p4.z; + sqlite3VdbeError(p, "database table is locked: %s", z); + } + goto abort_due_to_error; + } + } + break; +} +#endif /* SQLITE_OMIT_SHARED_CACHE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VBegin * * * P4 * +** +** P4 may be a pointer to an sqlite3_vtab structure. If so, call the +** xBegin method for that table. +** +** Also, whether or not P4 is set, check that this is not being called from +** within a callback to a virtual table xSync() method. If it is, the error +** code will be set to SQLITE_LOCKED. +*/ +case OP_VBegin: { + VTable *pVTab; + pVTab = pOp->p4.pVtab; + rc = sqlite3VtabBegin(db, pVTab); + if( pVTab ) sqlite3VtabImportErrmsg(p, pVTab->pVtab); + if( rc ) goto abort_due_to_error; + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VCreate P1 P2 * * * +** +** P2 is a register that holds the name of a virtual table in database +** P1. Call the xCreate method for that table. +*/ +case OP_VCreate: { + Mem sMem; /* For storing the record being decoded */ + const char *zTab; /* Name of the virtual table */ + + memset(&sMem, 0, sizeof(sMem)); + sMem.db = db; + /* Because P2 is always a static string, it is impossible for the + ** sqlite3VdbeMemCopy() to fail */ + assert( (aMem[pOp->p2].flags & MEM_Str)!=0 ); + assert( (aMem[pOp->p2].flags & MEM_Static)!=0 ); + rc = sqlite3VdbeMemCopy(&sMem, &aMem[pOp->p2]); + assert( rc==SQLITE_OK ); + zTab = (const char*)sqlite3_value_text(&sMem); + assert( zTab || db->mallocFailed ); + if( zTab ){ + rc = sqlite3VtabCallCreate(db, pOp->p1, zTab, &p->zErrMsg); + } + sqlite3VdbeMemRelease(&sMem); + if( rc ) goto abort_due_to_error; + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VDestroy P1 * * P4 * +** +** P4 is the name of a virtual table in database P1. Call the xDestroy method +** of that table. +*/ +case OP_VDestroy: { + db->nVDestroy++; + rc = sqlite3VtabCallDestroy(db, pOp->p1, pOp->p4.z); + db->nVDestroy--; + assert( p->errorAction==OE_Abort && p->usesStmtJournal ); + if( rc ) goto abort_due_to_error; + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VOpen P1 * * P4 * +** +** P4 is a pointer to a virtual table object, an sqlite3_vtab structure. +** P1 is a cursor number. This opcode opens a cursor to the virtual +** table and stores that cursor in P1. +*/ +case OP_VOpen: { /* ncycle */ + VdbeCursor *pCur; + sqlite3_vtab_cursor *pVCur; + sqlite3_vtab *pVtab; + const sqlite3_module *pModule; + + assert( p->bIsReader ); + pCur = 0; + pVCur = 0; + pVtab = pOp->p4.pVtab->pVtab; + if( pVtab==0 || NEVER(pVtab->pModule==0) ){ + rc = SQLITE_LOCKED; + goto abort_due_to_error; + } + pModule = pVtab->pModule; + rc = pModule->xOpen(pVtab, &pVCur); + sqlite3VtabImportErrmsg(p, pVtab); + if( rc ) goto abort_due_to_error; + + /* Initialize sqlite3_vtab_cursor base class */ + pVCur->pVtab = pVtab; + + /* Initialize vdbe cursor object */ + pCur = allocateCursor(p, pOp->p1, 0, CURTYPE_VTAB); + if( pCur ){ + pCur->uc.pVCur = pVCur; + pVtab->nRef++; + }else{ + assert( db->mallocFailed ); + pModule->xClose(pVCur); + goto no_mem; + } + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VCheck * P2 * P4 * +** +** P4 is a pointer to a Table object that is a virtual table that +** supports the xIntegrity() method. This opcode runs the xIntegrity() +** method for that virtual table. If an error is reported back, the error +** message is stored in register P2. If no errors are seen, register P2 +** is set to NULL. +*/ +case OP_VCheck: { /* out2 */ + Table *pTab; + sqlite3_vtab *pVtab; + const sqlite3_module *pModule; + char *zErr = 0; + + pOut = &aMem[pOp->p2]; + sqlite3VdbeMemSetNull(pOut); /* Innocent until proven guilty */ + assert( pOp->p4type==P4_TABLE ); + pTab = pOp->p4.pTab; + assert( pTab!=0 ); + assert( IsVirtual(pTab) ); + assert( pTab->u.vtab.p!=0 ); + pVtab = pTab->u.vtab.p->pVtab; + assert( pVtab!=0 ); + pModule = pVtab->pModule; + assert( pModule!=0 ); + assert( pModule->iVersion>=4 ); + assert( pModule->xIntegrity!=0 ); + pTab->nTabRef++; + sqlite3VtabLock(pTab->u.vtab.p); + rc = pModule->xIntegrity(pVtab, &zErr); + sqlite3VtabUnlock(pTab->u.vtab.p); + sqlite3DeleteTable(db, pTab); + if( rc ){ + sqlite3_free(zErr); + goto abort_due_to_error; + } + if( zErr ){ + sqlite3VdbeMemSetStr(pOut, zErr, -1, SQLITE_UTF8, sqlite3_free); + } + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VInitIn P1 P2 P3 * * +** Synopsis: r[P2]=ValueList(P1,P3) +** +** Set register P2 to be a pointer to a ValueList object for cursor P1 +** with cache register P3 and output register P3+1. This ValueList object +** can be used as the first argument to sqlite3_vtab_in_first() and +** sqlite3_vtab_in_next() to extract all of the values stored in the P1 +** cursor. Register P3 is used to hold the values returned by +** sqlite3_vtab_in_first() and sqlite3_vtab_in_next(). +*/ +case OP_VInitIn: { /* out2, ncycle */ + VdbeCursor *pC; /* The cursor containing the RHS values */ + ValueList *pRhs; /* New ValueList object to put in reg[P2] */ + + pC = p->apCsr[pOp->p1]; + pRhs = sqlite3_malloc64( sizeof(*pRhs) ); + if( pRhs==0 ) goto no_mem; + pRhs->pCsr = pC->uc.pCursor; + pRhs->pOut = &aMem[pOp->p3]; + pOut = out2Prerelease(p, pOp); + pOut->flags = MEM_Null; + sqlite3VdbeMemSetPointer(pOut, pRhs, "ValueList", sqlite3VdbeValueListFree); + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VFilter P1 P2 P3 P4 * +** Synopsis: iplan=r[P3] zplan='P4' +** +** P1 is a cursor opened using VOpen. P2 is an address to jump to if +** the filtered result set is empty. +** +** P4 is either NULL or a string that was generated by the xBestIndex +** method of the module. The interpretation of the P4 string is left +** to the module implementation. +** +** This opcode invokes the xFilter method on the virtual table specified +** by P1. The integer query plan parameter to xFilter is stored in register +** P3. Register P3+1 stores the argc parameter to be passed to the +** xFilter method. Registers P3+2..P3+1+argc are the argc +** additional parameters which are passed to +** xFilter as argv. Register P3+2 becomes argv[0] when passed to xFilter. +** +** A jump is made to P2 if the result set after filtering would be empty. +*/ +case OP_VFilter: { /* jump, ncycle */ + int nArg; + int iQuery; + const sqlite3_module *pModule; + Mem *pQuery; + Mem *pArgc; + sqlite3_vtab_cursor *pVCur; + sqlite3_vtab *pVtab; + VdbeCursor *pCur; + int res; + int i; + Mem **apArg; + + pQuery = &aMem[pOp->p3]; + pArgc = &pQuery[1]; + pCur = p->apCsr[pOp->p1]; + assert( memIsValid(pQuery) ); + REGISTER_TRACE(pOp->p3, pQuery); + assert( pCur!=0 ); + assert( pCur->eCurType==CURTYPE_VTAB ); + pVCur = pCur->uc.pVCur; + pVtab = pVCur->pVtab; + pModule = pVtab->pModule; + + /* Grab the index number and argc parameters */ + assert( (pQuery->flags&MEM_Int)!=0 && pArgc->flags==MEM_Int ); + nArg = (int)pArgc->u.i; + iQuery = (int)pQuery->u.i; + + /* Invoke the xFilter method */ + apArg = p->apArg; + for(i = 0; i<nArg; i++){ + apArg[i] = &pArgc[i+1]; + } + rc = pModule->xFilter(pVCur, iQuery, pOp->p4.z, nArg, apArg); + sqlite3VtabImportErrmsg(p, pVtab); + if( rc ) goto abort_due_to_error; + res = pModule->xEof(pVCur); + pCur->nullRow = 0; + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VColumn P1 P2 P3 * P5 +** Synopsis: r[P3]=vcolumn(P2) +** +** Store in register P3 the value of the P2-th column of +** the current row of the virtual-table of cursor P1. +** +** If the VColumn opcode is being used to fetch the value of +** an unchanging column during an UPDATE operation, then the P5 +** value is OPFLAG_NOCHNG. This will cause the sqlite3_vtab_nochange() +** function to return true inside the xColumn method of the virtual +** table implementation. The P5 column might also contain other +** bits (OPFLAG_LENGTHARG or OPFLAG_TYPEOFARG) but those bits are +** unused by OP_VColumn. +*/ +case OP_VColumn: { /* ncycle */ + sqlite3_vtab *pVtab; + const sqlite3_module *pModule; + Mem *pDest; + sqlite3_context sContext; + + VdbeCursor *pCur = p->apCsr[pOp->p1]; + assert( pCur!=0 ); + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); + if( pCur->nullRow ){ + sqlite3VdbeMemSetNull(pDest); + break; + } + assert( pCur->eCurType==CURTYPE_VTAB ); + pVtab = pCur->uc.pVCur->pVtab; + pModule = pVtab->pModule; + assert( pModule->xColumn ); + memset(&sContext, 0, sizeof(sContext)); + sContext.pOut = pDest; + sContext.enc = encoding; + assert( pOp->p5==OPFLAG_NOCHNG || pOp->p5==0 ); + if( pOp->p5 & OPFLAG_NOCHNG ){ + sqlite3VdbeMemSetNull(pDest); + pDest->flags = MEM_Null|MEM_Zero; + pDest->u.nZero = 0; + }else{ + MemSetTypeFlag(pDest, MEM_Null); + } + rc = pModule->xColumn(pCur->uc.pVCur, &sContext, pOp->p2); + sqlite3VtabImportErrmsg(p, pVtab); + if( sContext.isError>0 ){ + sqlite3VdbeError(p, "%s", sqlite3_value_text(pDest)); + rc = sContext.isError; + } + sqlite3VdbeChangeEncoding(pDest, encoding); + REGISTER_TRACE(pOp->p3, pDest); + UPDATE_MAX_BLOBSIZE(pDest); + + if( rc ) goto abort_due_to_error; + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VNext P1 P2 * * * +** +** Advance virtual table P1 to the next row in its result set and +** jump to instruction P2. Or, if the virtual table has reached +** the end of its result set, then fall through to the next instruction. +*/ +case OP_VNext: { /* jump, ncycle */ + sqlite3_vtab *pVtab; + const sqlite3_module *pModule; + int res; + VdbeCursor *pCur; + + pCur = p->apCsr[pOp->p1]; + assert( pCur!=0 ); + assert( pCur->eCurType==CURTYPE_VTAB ); + if( pCur->nullRow ){ + break; + } + pVtab = pCur->uc.pVCur->pVtab; + pModule = pVtab->pModule; + assert( pModule->xNext ); + + /* Invoke the xNext() method of the module. There is no way for the + ** underlying implementation to return an error if one occurs during + ** xNext(). Instead, if an error occurs, true is returned (indicating that + ** data is available) and the error code returned when xColumn or + ** some other method is next invoked on the save virtual table cursor. + */ + rc = pModule->xNext(pCur->uc.pVCur); + sqlite3VtabImportErrmsg(p, pVtab); + if( rc ) goto abort_due_to_error; + res = pModule->xEof(pCur->uc.pVCur); + VdbeBranchTaken(!res,2); + if( !res ){ + /* If there is data, jump to P2 */ + goto jump_to_p2_and_check_for_interrupt; + } + goto check_for_interrupt; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VRename P1 * * P4 * +** +** P4 is a pointer to a virtual table object, an sqlite3_vtab structure. +** This opcode invokes the corresponding xRename method. The value +** in register P1 is passed as the zName argument to the xRename method. +*/ +case OP_VRename: { + sqlite3_vtab *pVtab; + Mem *pName; + int isLegacy; + + isLegacy = (db->flags & SQLITE_LegacyAlter); + db->flags |= SQLITE_LegacyAlter; + pVtab = pOp->p4.pVtab->pVtab; + pName = &aMem[pOp->p1]; + assert( pVtab->pModule->xRename ); + assert( memIsValid(pName) ); + assert( p->readOnly==0 ); + REGISTER_TRACE(pOp->p1, pName); + assert( pName->flags & MEM_Str ); + testcase( pName->enc==SQLITE_UTF8 ); + testcase( pName->enc==SQLITE_UTF16BE ); + testcase( pName->enc==SQLITE_UTF16LE ); + rc = sqlite3VdbeChangeEncoding(pName, SQLITE_UTF8); + if( rc ) goto abort_due_to_error; + rc = pVtab->pModule->xRename(pVtab, pName->z); + if( isLegacy==0 ) db->flags &= ~(u64)SQLITE_LegacyAlter; + sqlite3VtabImportErrmsg(p, pVtab); + p->expired = 0; + if( rc ) goto abort_due_to_error; + break; +} +#endif + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VUpdate P1 P2 P3 P4 P5 +** Synopsis: data=r[P3@P2] +** +** P4 is a pointer to a virtual table object, an sqlite3_vtab structure. +** This opcode invokes the corresponding xUpdate method. P2 values +** are contiguous memory cells starting at P3 to pass to the xUpdate +** invocation. The value in register (P3+P2-1) corresponds to the +** p2th element of the argv array passed to xUpdate. +** +** The xUpdate method will do a DELETE or an INSERT or both. +** The argv[0] element (which corresponds to memory cell P3) +** is the rowid of a row to delete. If argv[0] is NULL then no +** deletion occurs. The argv[1] element is the rowid of the new +** row. This can be NULL to have the virtual table select the new +** rowid for itself. The subsequent elements in the array are +** the values of columns in the new row. +** +** If P2==1 then no insert is performed. argv[0] is the rowid of +** a row to delete. +** +** P1 is a boolean flag. If it is set to true and the xUpdate call +** is successful, then the value returned by sqlite3_last_insert_rowid() +** is set to the value of the rowid for the row just inserted. +** +** P5 is the error actions (OE_Replace, OE_Fail, OE_Ignore, etc) to +** apply in the case of a constraint failure on an insert or update. +*/ +case OP_VUpdate: { + sqlite3_vtab *pVtab; + const sqlite3_module *pModule; + int nArg; + int i; + sqlite_int64 rowid = 0; + Mem **apArg; + Mem *pX; + + assert( pOp->p2==1 || pOp->p5==OE_Fail || pOp->p5==OE_Rollback + || pOp->p5==OE_Abort || pOp->p5==OE_Ignore || pOp->p5==OE_Replace + ); + assert( p->readOnly==0 ); + if( db->mallocFailed ) goto no_mem; + sqlite3VdbeIncrWriteCounter(p, 0); + pVtab = pOp->p4.pVtab->pVtab; + if( pVtab==0 || NEVER(pVtab->pModule==0) ){ + rc = SQLITE_LOCKED; + goto abort_due_to_error; + } + pModule = pVtab->pModule; + nArg = pOp->p2; + assert( pOp->p4type==P4_VTAB ); + if( ALWAYS(pModule->xUpdate) ){ + u8 vtabOnConflict = db->vtabOnConflict; + apArg = p->apArg; + pX = &aMem[pOp->p3]; + for(i=0; i<nArg; i++){ + assert( memIsValid(pX) ); + memAboutToChange(p, pX); + apArg[i] = pX; + pX++; + } + db->vtabOnConflict = pOp->p5; + rc = pModule->xUpdate(pVtab, nArg, apArg, &rowid); + db->vtabOnConflict = vtabOnConflict; + sqlite3VtabImportErrmsg(p, pVtab); + if( rc==SQLITE_OK && pOp->p1 ){ + assert( nArg>1 && apArg[0] && (apArg[0]->flags&MEM_Null) ); + db->lastRowid = rowid; + } + if( (rc&0xff)==SQLITE_CONSTRAINT && pOp->p4.pVtab->bConstraint ){ + if( pOp->p5==OE_Ignore ){ + rc = SQLITE_OK; + }else{ + p->errorAction = ((pOp->p5==OE_Replace) ? OE_Abort : pOp->p5); + } + }else{ + p->nChange++; + } + if( rc ) goto abort_due_to_error; + } + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_PAGER_PRAGMAS +/* Opcode: Pagecount P1 P2 * * * +** +** Write the current number of pages in database P1 to memory cell P2. +*/ +case OP_Pagecount: { /* out2 */ + pOut = out2Prerelease(p, pOp); + pOut->u.i = sqlite3BtreeLastPage(db->aDb[pOp->p1].pBt); + break; +} +#endif + + +#ifndef SQLITE_OMIT_PAGER_PRAGMAS +/* Opcode: MaxPgcnt P1 P2 P3 * * +** +** Try to set the maximum page count for database P1 to the value in P3. +** Do not let the maximum page count fall below the current page count and +** do not change the maximum page count value if P3==0. +** +** Store the maximum page count after the change in register P2. +*/ +case OP_MaxPgcnt: { /* out2 */ + unsigned int newMax; + Btree *pBt; + + pOut = out2Prerelease(p, pOp); + pBt = db->aDb[pOp->p1].pBt; + newMax = 0; + if( pOp->p3 ){ + newMax = sqlite3BtreeLastPage(pBt); + if( newMax < (unsigned)pOp->p3 ) newMax = (unsigned)pOp->p3; + } + pOut->u.i = sqlite3BtreeMaxPageCount(pBt, newMax); + break; +} +#endif + +/* Opcode: Function P1 P2 P3 P4 * +** Synopsis: r[P3]=func(r[P2@NP]) +** +** Invoke a user function (P4 is a pointer to an sqlite3_context object that +** contains a pointer to the function to be run) with arguments taken +** from register P2 and successors. The number of arguments is in +** the sqlite3_context object that P4 points to. +** The result of the function is stored +** in register P3. Register P3 must not be one of the function inputs. +** +** P1 is a 32-bit bitmask indicating whether or not each argument to the +** function was determined to be constant at compile time. If the first +** argument was constant then bit 0 of P1 is set. This is used to determine +** whether meta data associated with a user function argument using the +** sqlite3_set_auxdata() API may be safely retained until the next +** invocation of this opcode. +** +** See also: AggStep, AggFinal, PureFunc +*/ +/* Opcode: PureFunc P1 P2 P3 P4 * +** Synopsis: r[P3]=func(r[P2@NP]) +** +** Invoke a user function (P4 is a pointer to an sqlite3_context object that +** contains a pointer to the function to be run) with arguments taken +** from register P2 and successors. The number of arguments is in +** the sqlite3_context object that P4 points to. +** The result of the function is stored +** in register P3. Register P3 must not be one of the function inputs. +** +** P1 is a 32-bit bitmask indicating whether or not each argument to the +** function was determined to be constant at compile time. If the first +** argument was constant then bit 0 of P1 is set. This is used to determine +** whether meta data associated with a user function argument using the +** sqlite3_set_auxdata() API may be safely retained until the next +** invocation of this opcode. +** +** This opcode works exactly like OP_Function. The only difference is in +** its name. This opcode is used in places where the function must be +** purely non-deterministic. Some built-in date/time functions can be +** either deterministic of non-deterministic, depending on their arguments. +** When those function are used in a non-deterministic way, they will check +** to see if they were called using OP_PureFunc instead of OP_Function, and +** if they were, they throw an error. +** +** See also: AggStep, AggFinal, Function +*/ +case OP_PureFunc: /* group */ +case OP_Function: { /* group */ + int i; + sqlite3_context *pCtx; + + assert( pOp->p4type==P4_FUNCCTX ); + pCtx = pOp->p4.pCtx; + + /* If this function is inside of a trigger, the register array in aMem[] + ** might change from one evaluation to the next. The next block of code + ** checks to see if the register array has changed, and if so it + ** reinitializes the relevant parts of the sqlite3_context object */ + pOut = &aMem[pOp->p3]; + if( pCtx->pOut != pOut ){ + pCtx->pVdbe = p; + pCtx->pOut = pOut; + pCtx->enc = encoding; + for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; + } + assert( pCtx->pVdbe==p ); + + memAboutToChange(p, pOut); +#ifdef SQLITE_DEBUG + for(i=0; i<pCtx->argc; i++){ + assert( memIsValid(pCtx->argv[i]) ); + REGISTER_TRACE(pOp->p2+i, pCtx->argv[i]); + } +#endif + MemSetTypeFlag(pOut, MEM_Null); + assert( pCtx->isError==0 ); + (*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */ + + /* If the function returned an error, throw an exception */ + if( pCtx->isError ){ + if( pCtx->isError>0 ){ + sqlite3VdbeError(p, "%s", sqlite3_value_text(pOut)); + rc = pCtx->isError; + } + sqlite3VdbeDeleteAuxData(db, &p->pAuxData, pCtx->iOp, pOp->p1); + pCtx->isError = 0; + if( rc ) goto abort_due_to_error; + } + + assert( (pOut->flags&MEM_Str)==0 + || pOut->enc==encoding + || db->mallocFailed ); + assert( !sqlite3VdbeMemTooBig(pOut) ); + + REGISTER_TRACE(pOp->p3, pOut); + UPDATE_MAX_BLOBSIZE(pOut); + break; +} + +/* Opcode: ClrSubtype P1 * * * * +** Synopsis: r[P1].subtype = 0 +** +** Clear the subtype from register P1. +*/ +case OP_ClrSubtype: { /* in1 */ + pIn1 = &aMem[pOp->p1]; + pIn1->flags &= ~MEM_Subtype; + break; +} + +/* Opcode: FilterAdd P1 * P3 P4 * +** Synopsis: filter(P1) += key(P3@P4) +** +** Compute a hash on the P4 registers starting with r[P3] and +** add that hash to the bloom filter contained in r[P1]. +*/ +case OP_FilterAdd: { + u64 h; + + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags & MEM_Blob ); + assert( pIn1->n>0 ); + h = filterHash(aMem, pOp); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + int ii; + for(ii=pOp->p3; ii<pOp->p3+pOp->p4.i; ii++){ + registerTrace(ii, &aMem[ii]); + } + printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); + } +#endif + h %= (pIn1->n*8); + pIn1->z[h/8] |= 1<<(h&7); + break; +} + +/* Opcode: Filter P1 P2 P3 P4 * +** Synopsis: if key(P3@P4) not in filter(P1) goto P2 +** +** Compute a hash on the key contained in the P4 registers starting +** with r[P3]. Check to see if that hash is found in the +** bloom filter hosted by register P1. If it is not present then +** maybe jump to P2. Otherwise fall through. +** +** False negatives are harmless. It is always safe to fall through, +** even if the value is in the bloom filter. A false negative causes +** more CPU cycles to be used, but it should still yield the correct +** answer. However, an incorrect answer may well arise from a +** false positive - if the jump is taken when it should fall through. +*/ +case OP_Filter: { /* jump */ + u64 h; + + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + pIn1 = &aMem[pOp->p1]; + assert( (pIn1->flags & MEM_Blob)!=0 ); + assert( pIn1->n >= 1 ); + h = filterHash(aMem, pOp); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + int ii; + for(ii=pOp->p3; ii<pOp->p3+pOp->p4.i; ii++){ + registerTrace(ii, &aMem[ii]); + } + printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); + } +#endif + h %= (pIn1->n*8); + if( (pIn1->z[h/8] & (1<<(h&7)))==0 ){ + VdbeBranchTaken(1, 2); + p->aCounter[SQLITE_STMTSTATUS_FILTER_HIT]++; + goto jump_to_p2; + }else{ + p->aCounter[SQLITE_STMTSTATUS_FILTER_MISS]++; + VdbeBranchTaken(0, 2); + } + break; +} + +/* Opcode: Trace P1 P2 * P4 * +** +** Write P4 on the statement trace output if statement tracing is +** enabled. +** +** Operand P1 must be 0x7fffffff and P2 must positive. +*/ +/* Opcode: Init P1 P2 P3 P4 * +** Synopsis: Start at P2 +** +** Programs contain a single instance of this opcode as the very first +** opcode. +** +** If tracing is enabled (by the sqlite3_trace()) interface, then +** the UTF-8 string contained in P4 is emitted on the trace callback. +** Or if P4 is blank, use the string returned by sqlite3_sql(). +** +** If P2 is not zero, jump to instruction P2. +** +** Increment the value of P1 so that OP_Once opcodes will jump the +** first time they are evaluated for this run. +** +** If P3 is not zero, then it is an address to jump to if an SQLITE_CORRUPT +** error is encountered. +*/ +case OP_Trace: +case OP_Init: { /* jump */ + int i; +#ifndef SQLITE_OMIT_TRACE + char *zTrace; +#endif + + /* If the P4 argument is not NULL, then it must be an SQL comment string. + ** The "--" string is broken up to prevent false-positives with srcck1.c. + ** + ** This assert() provides evidence for: + ** EVIDENCE-OF: R-50676-09860 The callback can compute the same text that + ** would have been returned by the legacy sqlite3_trace() interface by + ** using the X argument when X begins with "--" and invoking + ** sqlite3_expanded_sql(P) otherwise. + */ + assert( pOp->p4.z==0 || strncmp(pOp->p4.z, "-" "- ", 3)==0 ); + + /* OP_Init is always instruction 0 */ + assert( pOp==p->aOp || pOp->opcode==OP_Trace ); + +#ifndef SQLITE_OMIT_TRACE + if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0 + && p->minWriteFileFormat!=254 /* tag-20220401a */ + && (zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0 + ){ +#ifndef SQLITE_OMIT_DEPRECATED + if( db->mTrace & SQLITE_TRACE_LEGACY ){ + char *z = sqlite3VdbeExpandSql(p, zTrace); + db->trace.xLegacy(db->pTraceArg, z); + sqlite3_free(z); + }else +#endif + if( db->nVdbeExec>1 ){ + char *z = sqlite3MPrintf(db, "-- %s", zTrace); + (void)db->trace.xV2(SQLITE_TRACE_STMT, db->pTraceArg, p, z); + sqlite3DbFree(db, z); + }else{ + (void)db->trace.xV2(SQLITE_TRACE_STMT, db->pTraceArg, p, zTrace); + } + } +#ifdef SQLITE_USE_FCNTL_TRACE + zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql); + if( zTrace ){ + int j; + for(j=0; j<db->nDb; j++){ + if( DbMaskTest(p->btreeMask, j)==0 ) continue; + sqlite3_file_control(db, db->aDb[j].zDbSName, SQLITE_FCNTL_TRACE, zTrace); + } + } +#endif /* SQLITE_USE_FCNTL_TRACE */ +#ifdef SQLITE_DEBUG + if( (db->flags & SQLITE_SqlTrace)!=0 + && (zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0 + ){ + sqlite3DebugPrintf("SQL-trace: %s\n", zTrace); + } +#endif /* SQLITE_DEBUG */ +#endif /* SQLITE_OMIT_TRACE */ + assert( pOp->p2>0 ); + if( pOp->p1>=sqlite3GlobalConfig.iOnceResetThreshold ){ + if( pOp->opcode==OP_Trace ) break; + for(i=1; i<p->nOp; i++){ + if( p->aOp[i].opcode==OP_Once ) p->aOp[i].p1 = 0; + } + pOp->p1 = 0; + } + pOp->p1++; + p->aCounter[SQLITE_STMTSTATUS_RUN]++; + goto jump_to_p2; +} + +#ifdef SQLITE_ENABLE_CURSOR_HINTS +/* Opcode: CursorHint P1 * * P4 * +** +** Provide a hint to cursor P1 that it only needs to return rows that +** satisfy the Expr in P4. TK_REGISTER terms in the P4 expression refer +** to values currently held in registers. TK_COLUMN terms in the P4 +** expression refer to columns in the b-tree to which cursor P1 is pointing. +*/ +case OP_CursorHint: { + VdbeCursor *pC; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + assert( pOp->p4type==P4_EXPR ); + pC = p->apCsr[pOp->p1]; + if( pC ){ + assert( pC->eCurType==CURTYPE_BTREE ); + sqlite3BtreeCursorHint(pC->uc.pCursor, BTREE_HINT_RANGE, + pOp->p4.pExpr, aMem); + } + break; +} +#endif /* SQLITE_ENABLE_CURSOR_HINTS */ + +#ifdef SQLITE_DEBUG +/* Opcode: Abortable * * * * * +** +** Verify that an Abort can happen. Assert if an Abort at this point +** might cause database corruption. This opcode only appears in debugging +** builds. +** +** An Abort is safe if either there have been no writes, or if there is +** an active statement journal. +*/ +case OP_Abortable: { + sqlite3VdbeAssertAbortable(p); + break; +} +#endif + +#ifdef SQLITE_DEBUG +/* Opcode: ReleaseReg P1 P2 P3 * P5 +** Synopsis: release r[P1@P2] mask P3 +** +** Release registers from service. Any content that was in the +** the registers is unreliable after this opcode completes. +** +** The registers released will be the P2 registers starting at P1, +** except if bit ii of P3 set, then do not release register P1+ii. +** In other words, P3 is a mask of registers to preserve. +** +** Releasing a register clears the Mem.pScopyFrom pointer. That means +** that if the content of the released register was set using OP_SCopy, +** a change to the value of the source register for the OP_SCopy will no longer +** generate an assertion fault in sqlite3VdbeMemAboutToChange(). +** +** If P5 is set, then all released registers have their type set +** to MEM_Undefined so that any subsequent attempt to read the released +** register (before it is reinitialized) will generate an assertion fault. +** +** P5 ought to be set on every call to this opcode. +** However, there are places in the code generator will release registers +** before their are used, under the (valid) assumption that the registers +** will not be reallocated for some other purpose before they are used and +** hence are safe to release. +** +** This opcode is only available in testing and debugging builds. It is +** not generated for release builds. The purpose of this opcode is to help +** validate the generated bytecode. This opcode does not actually contribute +** to computing an answer. +*/ +case OP_ReleaseReg: { + Mem *pMem; + int i; + u32 constMask; + assert( pOp->p1>0 ); + assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); + pMem = &aMem[pOp->p1]; + constMask = pOp->p3; + for(i=0; i<pOp->p2; i++, pMem++){ + if( i>=32 || (constMask & MASKBIT32(i))==0 ){ + pMem->pScopyFrom = 0; + if( i<32 && pOp->p5 ) MemSetTypeFlag(pMem, MEM_Undefined); + } + } + break; +} +#endif + +/* Opcode: Noop * * * * * +** +** Do nothing. This instruction is often useful as a jump +** destination. +*/ +/* +** The magic Explain opcode are only inserted when explain==2 (which +** is to say when the EXPLAIN QUERY PLAN syntax is used.) +** This opcode records information from the optimizer. It is the +** the same as a no-op. This opcodesnever appears in a real VM program. +*/ +default: { /* This is really OP_Noop, OP_Explain */ + assert( pOp->opcode==OP_Noop || pOp->opcode==OP_Explain ); + + break; +} + +/***************************************************************************** +** The cases of the switch statement above this line should all be indented +** by 6 spaces. But the left-most 6 spaces have been removed to improve the +** readability. From this point on down, the normal indentation rules are +** restored. +*****************************************************************************/ + } + +#if defined(VDBE_PROFILE) + *pnCycle += sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); + pnCycle = 0; +#elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) + if( pnCycle ){ + *pnCycle += sqlite3Hwtime(); + pnCycle = 0; + } +#endif + + /* The following code adds nothing to the actual functionality + ** of the program. It is only here for testing and debugging. + ** On the other hand, it does burn CPU cycles every time through + ** the evaluator loop. So we can leave it out when NDEBUG is defined. + */ +#ifndef NDEBUG + assert( pOp>=&aOp[-1] && pOp<&aOp[p->nOp-1] ); + +#ifdef SQLITE_DEBUG + if( db->flags & SQLITE_VdbeTrace ){ + u8 opProperty = sqlite3OpcodeProperty[pOrigOp->opcode]; + if( rc!=0 ) printf("rc=%d\n",rc); + if( opProperty & (OPFLG_OUT2) ){ + registerTrace(pOrigOp->p2, &aMem[pOrigOp->p2]); + } + if( opProperty & OPFLG_OUT3 ){ + registerTrace(pOrigOp->p3, &aMem[pOrigOp->p3]); + } + if( opProperty==0xff ){ + /* Never happens. This code exists to avoid a harmless linkage + ** warning about sqlite3VdbeRegisterDump() being defined but not + ** used. */ + sqlite3VdbeRegisterDump(p); + } + } +#endif /* SQLITE_DEBUG */ +#endif /* NDEBUG */ + } /* The end of the for(;;) loop the loops through opcodes */ + + /* If we reach this point, it means that execution is finished with + ** an error of some kind. + */ +abort_due_to_error: + if( db->mallocFailed ){ + rc = SQLITE_NOMEM_BKPT; + }else if( rc==SQLITE_IOERR_CORRUPTFS ){ + rc = SQLITE_CORRUPT_BKPT; + } + assert( rc ); +#ifdef SQLITE_DEBUG + if( db->flags & SQLITE_VdbeTrace ){ + const char *zTrace = p->zSql; + if( zTrace==0 ){ + if( aOp[0].opcode==OP_Trace ){ + zTrace = aOp[0].p4.z; + } + if( zTrace==0 ) zTrace = "???"; + } + printf("ABORT-due-to-error (rc=%d): %s\n", rc, zTrace); + } +#endif + if( p->zErrMsg==0 && rc!=SQLITE_IOERR_NOMEM ){ + sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc)); + } + p->rc = rc; + sqlite3SystemError(db, rc); + testcase( sqlite3GlobalConfig.xLog!=0 ); + sqlite3_log(rc, "statement aborts at %d: [%s] %s", + (int)(pOp - aOp), p->zSql, p->zErrMsg); + if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); + if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); + if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){ + db->flags |= SQLITE_CorruptRdOnly; + } + rc = SQLITE_ERROR; + if( resetSchemaOnFault>0 ){ + sqlite3ResetOneSchema(db, resetSchemaOnFault-1); + } + + /* This is the only way out of this procedure. We have to + ** release the mutexes on btrees that were acquired at the + ** top. */ +vdbe_return: +#if defined(VDBE_PROFILE) + if( pnCycle ){ + *pnCycle += sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); + pnCycle = 0; + } +#elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) + if( pnCycle ){ + *pnCycle += sqlite3Hwtime(); + pnCycle = 0; + } +#endif + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + while( nVmStep>=nProgressLimit && db->xProgress!=0 ){ + nProgressLimit += db->nProgressOps; + if( db->xProgress(db->pProgressArg) ){ + nProgressLimit = LARGEST_UINT64; + rc = SQLITE_INTERRUPT; + goto abort_due_to_error; + } + } +#endif + p->aCounter[SQLITE_STMTSTATUS_VM_STEP] += (int)nVmStep; + if( DbMaskNonZero(p->lockMask) ){ + sqlite3VdbeLeave(p); + } + assert( rc!=SQLITE_OK || nExtraDelete==0 + || sqlite3_strlike("DELETE%",p->zSql,0)!=0 + ); + return rc; + + /* Jump to here if a string or blob larger than SQLITE_MAX_LENGTH + ** is encountered. + */ +too_big: + sqlite3VdbeError(p, "string or blob too big"); + rc = SQLITE_TOOBIG; + goto abort_due_to_error; + + /* Jump to here if a malloc() fails. + */ +no_mem: + sqlite3OomFault(db); + sqlite3VdbeError(p, "out of memory"); + rc = SQLITE_NOMEM_BKPT; + goto abort_due_to_error; + + /* Jump to here if the sqlite3_interrupt() API sets the interrupt + ** flag. + */ +abort_due_to_interrupt: + assert( AtomicLoad(&db->u1.isInterrupted) ); + rc = SQLITE_INTERRUPT; + goto abort_due_to_error; +} + + +/************** End of vdbe.c ************************************************/ +/************** Begin file vdbeblob.c ****************************************/ +/* +** 2007 May 1 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains code used to implement incremental BLOB I/O. +*/ + +/* #include "sqliteInt.h" */ +/* #include "vdbeInt.h" */ + +#ifndef SQLITE_OMIT_INCRBLOB + +/* +** Valid sqlite3_blob* handles point to Incrblob structures. +*/ +typedef struct Incrblob Incrblob; +struct Incrblob { + int nByte; /* Size of open blob, in bytes */ + int iOffset; /* Byte offset of blob in cursor data */ + u16 iCol; /* Table column this handle is open on */ + BtCursor *pCsr; /* Cursor pointing at blob row */ + sqlite3_stmt *pStmt; /* Statement holding cursor open */ + sqlite3 *db; /* The associated database */ + char *zDb; /* Database name */ + Table *pTab; /* Table object */ +}; + + +/* +** This function is used by both blob_open() and blob_reopen(). It seeks +** the b-tree cursor associated with blob handle p to point to row iRow. +** If successful, SQLITE_OK is returned and subsequent calls to +** sqlite3_blob_read() or sqlite3_blob_write() access the specified row. +** +** If an error occurs, or if the specified row does not exist or does not +** contain a value of type TEXT or BLOB in the column nominated when the +** blob handle was opened, then an error code is returned and *pzErr may +** be set to point to a buffer containing an error message. It is the +** responsibility of the caller to free the error message buffer using +** sqlite3DbFree(). +** +** If an error does occur, then the b-tree cursor is closed. All subsequent +** calls to sqlite3_blob_read(), blob_write() or blob_reopen() will +** immediately return SQLITE_ABORT. +*/ +static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){ + int rc; /* Error code */ + char *zErr = 0; /* Error message */ + Vdbe *v = (Vdbe *)p->pStmt; + + /* Set the value of register r[1] in the SQL statement to integer iRow. + ** This is done directly as a performance optimization + */ + v->aMem[1].flags = MEM_Int; + v->aMem[1].u.i = iRow; + + /* If the statement has been run before (and is paused at the OP_ResultRow) + ** then back it up to the point where it does the OP_NotExists. This could + ** have been down with an extra OP_Goto, but simply setting the program + ** counter is faster. */ + if( v->pc>4 ){ + v->pc = 4; + assert( v->aOp[v->pc].opcode==OP_NotExists ); + rc = sqlite3VdbeExec(v); + }else{ + rc = sqlite3_step(p->pStmt); + } + if( rc==SQLITE_ROW ){ + VdbeCursor *pC = v->apCsr[0]; + u32 type; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + type = pC->nHdrParsed>p->iCol ? pC->aType[p->iCol] : 0; + testcase( pC->nHdrParsed==p->iCol ); + testcase( pC->nHdrParsed==p->iCol+1 ); + if( type<12 ){ + zErr = sqlite3MPrintf(p->db, "cannot open value of type %s", + type==0?"null": type==7?"real": "integer" + ); + rc = SQLITE_ERROR; + sqlite3_finalize(p->pStmt); + p->pStmt = 0; + }else{ + p->iOffset = pC->aType[p->iCol + pC->nField]; + p->nByte = sqlite3VdbeSerialTypeLen(type); + p->pCsr = pC->uc.pCursor; + sqlite3BtreeIncrblobCursor(p->pCsr); + } + } + + if( rc==SQLITE_ROW ){ + rc = SQLITE_OK; + }else if( p->pStmt ){ + rc = sqlite3_finalize(p->pStmt); + p->pStmt = 0; + if( rc==SQLITE_OK ){ + zErr = sqlite3MPrintf(p->db, "no such rowid: %lld", iRow); + rc = SQLITE_ERROR; + }else{ + zErr = sqlite3MPrintf(p->db, "%s", sqlite3_errmsg(p->db)); + } + } + + assert( rc!=SQLITE_OK || zErr==0 ); + assert( rc!=SQLITE_ROW && rc!=SQLITE_DONE ); + + *pzErr = zErr; + return rc; +} + +/* +** Open a blob handle. +*/ +SQLITE_API int sqlite3_blob_open( + sqlite3* db, /* The database connection */ + const char *zDb, /* The attached database containing the blob */ + const char *zTable, /* The table containing the blob */ + const char *zColumn, /* The column containing the blob */ + sqlite_int64 iRow, /* The row containing the glob */ + int wrFlag, /* True -> read/write access, false -> read-only */ + sqlite3_blob **ppBlob /* Handle for accessing the blob returned here */ +){ + int nAttempt = 0; + int iCol; /* Index of zColumn in row-record */ + int rc = SQLITE_OK; + char *zErr = 0; + Table *pTab; + Incrblob *pBlob = 0; + Parse sParse; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppBlob==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + *ppBlob = 0; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zTable==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + wrFlag = !!wrFlag; /* wrFlag = (wrFlag ? 1 : 0); */ + + sqlite3_mutex_enter(db->mutex); + + pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob)); + while(1){ + sqlite3ParseObjectInit(&sParse,db); + if( !pBlob ) goto blob_open_out; + sqlite3DbFree(db, zErr); + zErr = 0; + + sqlite3BtreeEnterAll(db); + pTab = sqlite3LocateTable(&sParse, 0, zTable, zDb); + if( pTab && IsVirtual(pTab) ){ + pTab = 0; + sqlite3ErrorMsg(&sParse, "cannot open virtual table: %s", zTable); + } + if( pTab && !HasRowid(pTab) ){ + pTab = 0; + sqlite3ErrorMsg(&sParse, "cannot open table without rowid: %s", zTable); + } +#ifndef SQLITE_OMIT_VIEW + if( pTab && IsView(pTab) ){ + pTab = 0; + sqlite3ErrorMsg(&sParse, "cannot open view: %s", zTable); + } +#endif + if( !pTab ){ + if( sParse.zErrMsg ){ + sqlite3DbFree(db, zErr); + zErr = sParse.zErrMsg; + sParse.zErrMsg = 0; + } + rc = SQLITE_ERROR; + sqlite3BtreeLeaveAll(db); + goto blob_open_out; + } + pBlob->pTab = pTab; + pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName; + + /* Now search pTab for the exact column. */ + for(iCol=0; iCol<pTab->nCol; iCol++) { + if( sqlite3StrICmp(pTab->aCol[iCol].zCnName, zColumn)==0 ){ + break; + } + } + if( iCol==pTab->nCol ){ + sqlite3DbFree(db, zErr); + zErr = sqlite3MPrintf(db, "no such column: \"%s\"", zColumn); + rc = SQLITE_ERROR; + sqlite3BtreeLeaveAll(db); + goto blob_open_out; + } + + /* If the value is being opened for writing, check that the + ** column is not indexed, and that it is not part of a foreign key. + */ + if( wrFlag ){ + const char *zFault = 0; + Index *pIdx; +#ifndef SQLITE_OMIT_FOREIGN_KEY + if( db->flags&SQLITE_ForeignKeys ){ + /* Check that the column is not part of an FK child key definition. It + ** is not necessary to check if it is part of a parent key, as parent + ** key columns must be indexed. The check below will pick up this + ** case. */ + FKey *pFKey; + assert( IsOrdinaryTable(pTab) ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ + int j; + for(j=0; j<pFKey->nCol; j++){ + if( pFKey->aCol[j].iFrom==iCol ){ + zFault = "foreign key"; + } + } + } + } +#endif + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int j; + for(j=0; j<pIdx->nKeyCol; j++){ + /* FIXME: Be smarter about indexes that use expressions */ + if( pIdx->aiColumn[j]==iCol || pIdx->aiColumn[j]==XN_EXPR ){ + zFault = "indexed"; + } + } + } + if( zFault ){ + sqlite3DbFree(db, zErr); + zErr = sqlite3MPrintf(db, "cannot open %s column for writing", zFault); + rc = SQLITE_ERROR; + sqlite3BtreeLeaveAll(db); + goto blob_open_out; + } + } + + pBlob->pStmt = (sqlite3_stmt *)sqlite3VdbeCreate(&sParse); + assert( pBlob->pStmt || db->mallocFailed ); + if( pBlob->pStmt ){ + + /* This VDBE program seeks a btree cursor to the identified + ** db/table/row entry. The reason for using a vdbe program instead + ** of writing code to use the b-tree layer directly is that the + ** vdbe program will take advantage of the various transaction, + ** locking and error handling infrastructure built into the vdbe. + ** + ** After seeking the cursor, the vdbe executes an OP_ResultRow. + ** Code external to the Vdbe then "borrows" the b-tree cursor and + ** uses it to implement the blob_read(), blob_write() and + ** blob_bytes() functions. + ** + ** The sqlite3_blob_close() function finalizes the vdbe program, + ** which closes the b-tree cursor and (possibly) commits the + ** transaction. + */ + static const int iLn = VDBE_OFFSET_LINENO(2); + static const VdbeOpList openBlob[] = { + {OP_TableLock, 0, 0, 0}, /* 0: Acquire a read or write lock */ + {OP_OpenRead, 0, 0, 0}, /* 1: Open a cursor */ + /* blobSeekToRow() will initialize r[1] to the desired rowid */ + {OP_NotExists, 0, 5, 1}, /* 2: Seek the cursor to rowid=r[1] */ + {OP_Column, 0, 0, 1}, /* 3 */ + {OP_ResultRow, 1, 0, 0}, /* 4 */ + {OP_Halt, 0, 0, 0}, /* 5 */ + }; + Vdbe *v = (Vdbe *)pBlob->pStmt; + int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + VdbeOp *aOp; + + sqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, wrFlag, + pTab->pSchema->schema_cookie, + pTab->pSchema->iGeneration); + sqlite3VdbeChangeP5(v, 1); + assert( sqlite3VdbeCurrentAddr(v)==2 || db->mallocFailed ); + aOp = sqlite3VdbeAddOpList(v, ArraySize(openBlob), openBlob, iLn); + + /* Make sure a mutex is held on the table to be accessed */ + sqlite3VdbeUsesBtree(v, iDb); + + if( db->mallocFailed==0 ){ + assert( aOp!=0 ); + /* Configure the OP_TableLock instruction */ +#ifdef SQLITE_OMIT_SHARED_CACHE + aOp[0].opcode = OP_Noop; +#else + aOp[0].p1 = iDb; + aOp[0].p2 = pTab->tnum; + aOp[0].p3 = wrFlag; + sqlite3VdbeChangeP4(v, 2, pTab->zName, P4_TRANSIENT); + } + if( db->mallocFailed==0 ){ +#endif + + /* Remove either the OP_OpenWrite or OpenRead. Set the P2 + ** parameter of the other to pTab->tnum. */ + if( wrFlag ) aOp[1].opcode = OP_OpenWrite; + aOp[1].p2 = pTab->tnum; + aOp[1].p3 = iDb; + + /* Configure the number of columns. Configure the cursor to + ** think that the table has one more column than it really + ** does. An OP_Column to retrieve this imaginary column will + ** always return an SQL NULL. This is useful because it means + ** we can invoke OP_Column to fill in the vdbe cursors type + ** and offset cache without causing any IO. + */ + aOp[1].p4type = P4_INT32; + aOp[1].p4.i = pTab->nCol+1; + aOp[3].p2 = pTab->nCol; + + sParse.nVar = 0; + sParse.nMem = 1; + sParse.nTab = 1; + sqlite3VdbeMakeReady(v, &sParse); + } + } + + pBlob->iCol = iCol; + pBlob->db = db; + sqlite3BtreeLeaveAll(db); + if( db->mallocFailed ){ + goto blob_open_out; + } + rc = blobSeekToRow(pBlob, iRow, &zErr); + if( (++nAttempt)>=SQLITE_MAX_SCHEMA_RETRY || rc!=SQLITE_SCHEMA ) break; + sqlite3ParseObjectReset(&sParse); + } + +blob_open_out: + if( rc==SQLITE_OK && db->mallocFailed==0 ){ + *ppBlob = (sqlite3_blob *)pBlob; + }else{ + if( pBlob && pBlob->pStmt ) sqlite3VdbeFinalize((Vdbe *)pBlob->pStmt); + sqlite3DbFree(db, pBlob); + } + sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : (char*)0), zErr); + sqlite3DbFree(db, zErr); + sqlite3ParseObjectReset(&sParse); + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Close a blob handle that was previously created using +** sqlite3_blob_open(). +*/ +SQLITE_API int sqlite3_blob_close(sqlite3_blob *pBlob){ + Incrblob *p = (Incrblob *)pBlob; + int rc; + sqlite3 *db; + + if( p ){ + sqlite3_stmt *pStmt = p->pStmt; + db = p->db; + sqlite3_mutex_enter(db->mutex); + sqlite3DbFree(db, p); + sqlite3_mutex_leave(db->mutex); + rc = sqlite3_finalize(pStmt); + }else{ + rc = SQLITE_OK; + } + return rc; +} + +/* +** Perform a read or write operation on a blob +*/ +static int blobReadWrite( + sqlite3_blob *pBlob, + void *z, + int n, + int iOffset, + int (*xCall)(BtCursor*, u32, u32, void*) +){ + int rc; + Incrblob *p = (Incrblob *)pBlob; + Vdbe *v; + sqlite3 *db; + + if( p==0 ) return SQLITE_MISUSE_BKPT; + db = p->db; + sqlite3_mutex_enter(db->mutex); + v = (Vdbe*)p->pStmt; + + if( n<0 || iOffset<0 || ((sqlite3_int64)iOffset+n)>p->nByte ){ + /* Request is out of range. Return a transient error. */ + rc = SQLITE_ERROR; + }else if( v==0 ){ + /* If there is no statement handle, then the blob-handle has + ** already been invalidated. Return SQLITE_ABORT in this case. + */ + rc = SQLITE_ABORT; + }else{ + /* Call either BtreeData() or BtreePutData(). If SQLITE_ABORT is + ** returned, clean-up the statement handle. + */ + assert( db == v->db ); + sqlite3BtreeEnterCursor(p->pCsr); + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( xCall==sqlite3BtreePutData && db->xPreUpdateCallback ){ + /* If a pre-update hook is registered and this is a write cursor, + ** invoke it here. + ** + ** TODO: The preupdate-hook is passed SQLITE_DELETE, even though this + ** operation should really be an SQLITE_UPDATE. This is probably + ** incorrect, but is convenient because at this point the new.* values + ** are not easily obtainable. And for the sessions module, an + ** SQLITE_UPDATE where the PK columns do not change is handled in the + ** same way as an SQLITE_DELETE (the SQLITE_DELETE code is actually + ** slightly more efficient). Since you cannot write to a PK column + ** using the incremental-blob API, this works. For the sessions module + ** anyhow. + */ + sqlite3_int64 iKey; + iKey = sqlite3BtreeIntegerKey(p->pCsr); + assert( v->apCsr[0]!=0 ); + assert( v->apCsr[0]->eCurType==CURTYPE_BTREE ); + sqlite3VdbePreUpdateHook( + v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol + ); + } +#endif + + rc = xCall(p->pCsr, iOffset+p->iOffset, n, z); + sqlite3BtreeLeaveCursor(p->pCsr); + if( rc==SQLITE_ABORT ){ + sqlite3VdbeFinalize(v); + p->pStmt = 0; + }else{ + v->rc = rc; + } + } + sqlite3Error(db, rc); + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Read data from a blob handle. +*/ +SQLITE_API int sqlite3_blob_read(sqlite3_blob *pBlob, void *z, int n, int iOffset){ + return blobReadWrite(pBlob, z, n, iOffset, sqlite3BtreePayloadChecked); +} + +/* +** Write data to a blob handle. +*/ +SQLITE_API int sqlite3_blob_write(sqlite3_blob *pBlob, const void *z, int n, int iOffset){ + return blobReadWrite(pBlob, (void *)z, n, iOffset, sqlite3BtreePutData); +} + +/* +** Query a blob handle for the size of the data. +** +** The Incrblob.nByte field is fixed for the lifetime of the Incrblob +** so no mutex is required for access. +*/ +SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *pBlob){ + Incrblob *p = (Incrblob *)pBlob; + return (p && p->pStmt) ? p->nByte : 0; +} + +/* +** Move an existing blob handle to point to a different row of the same +** database table. +** +** If an error occurs, or if the specified row does not exist or does not +** contain a blob or text value, then an error code is returned and the +** database handle error code and message set. If this happens, then all +** subsequent calls to sqlite3_blob_xxx() functions (except blob_close()) +** immediately return SQLITE_ABORT. +*/ +SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ + int rc; + Incrblob *p = (Incrblob *)pBlob; + sqlite3 *db; + + if( p==0 ) return SQLITE_MISUSE_BKPT; + db = p->db; + sqlite3_mutex_enter(db->mutex); + + if( p->pStmt==0 ){ + /* If there is no statement handle, then the blob-handle has + ** already been invalidated. Return SQLITE_ABORT in this case. + */ + rc = SQLITE_ABORT; + }else{ + char *zErr; + ((Vdbe*)p->pStmt)->rc = SQLITE_OK; + rc = blobSeekToRow(p, iRow, &zErr); + if( rc!=SQLITE_OK ){ + sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : (char*)0), zErr); + sqlite3DbFree(db, zErr); + } + assert( rc!=SQLITE_SCHEMA ); + } + + rc = sqlite3ApiExit(db, rc); + assert( rc==SQLITE_OK || p->pStmt==0 ); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +#endif /* #ifndef SQLITE_OMIT_INCRBLOB */ + +/************** End of vdbeblob.c ********************************************/ +/************** Begin file vdbesort.c ****************************************/ +/* +** 2011-07-09 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code for the VdbeSorter object, used in concert with +** a VdbeCursor to sort large numbers of keys for CREATE INDEX statements +** or by SELECT statements with ORDER BY clauses that cannot be satisfied +** using indexes and without LIMIT clauses. +** +** The VdbeSorter object implements a multi-threaded external merge sort +** algorithm that is efficient even if the number of elements being sorted +** exceeds the available memory. +** +** Here is the (internal, non-API) interface between this module and the +** rest of the SQLite system: +** +** sqlite3VdbeSorterInit() Create a new VdbeSorter object. +** +** sqlite3VdbeSorterWrite() Add a single new row to the VdbeSorter +** object. The row is a binary blob in the +** OP_MakeRecord format that contains both +** the ORDER BY key columns and result columns +** in the case of a SELECT w/ ORDER BY, or +** the complete record for an index entry +** in the case of a CREATE INDEX. +** +** sqlite3VdbeSorterRewind() Sort all content previously added. +** Position the read cursor on the +** first sorted element. +** +** sqlite3VdbeSorterNext() Advance the read cursor to the next sorted +** element. +** +** sqlite3VdbeSorterRowkey() Return the complete binary blob for the +** row currently under the read cursor. +** +** sqlite3VdbeSorterCompare() Compare the binary blob for the row +** currently under the read cursor against +** another binary blob X and report if +** X is strictly less than the read cursor. +** Used to enforce uniqueness in a +** CREATE UNIQUE INDEX statement. +** +** sqlite3VdbeSorterClose() Close the VdbeSorter object and reclaim +** all resources. +** +** sqlite3VdbeSorterReset() Refurbish the VdbeSorter for reuse. This +** is like Close() followed by Init() only +** much faster. +** +** The interfaces above must be called in a particular order. Write() can +** only occur in between Init()/Reset() and Rewind(). Next(), Rowkey(), and +** Compare() can only occur in between Rewind() and Close()/Reset(). i.e. +** +** Init() +** for each record: Write() +** Rewind() +** Rowkey()/Compare() +** Next() +** Close() +** +** Algorithm: +** +** Records passed to the sorter via calls to Write() are initially held +** unsorted in main memory. Assuming the amount of memory used never exceeds +** a threshold, when Rewind() is called the set of records is sorted using +** an in-memory merge sort. In this case, no temporary files are required +** and subsequent calls to Rowkey(), Next() and Compare() read records +** directly from main memory. +** +** If the amount of space used to store records in main memory exceeds the +** threshold, then the set of records currently in memory are sorted and +** written to a temporary file in "Packed Memory Array" (PMA) format. +** A PMA created at this point is known as a "level-0 PMA". Higher levels +** of PMAs may be created by merging existing PMAs together - for example +** merging two or more level-0 PMAs together creates a level-1 PMA. +** +** The threshold for the amount of main memory to use before flushing +** records to a PMA is roughly the same as the limit configured for the +** page-cache of the main database. Specifically, the threshold is set to +** the value returned by "PRAGMA main.page_size" multiplied by +** that returned by "PRAGMA main.cache_size", in bytes. +** +** If the sorter is running in single-threaded mode, then all PMAs generated +** are appended to a single temporary file. Or, if the sorter is running in +** multi-threaded mode then up to (N+1) temporary files may be opened, where +** N is the configured number of worker threads. In this case, instead of +** sorting the records and writing the PMA to a temporary file itself, the +** calling thread usually launches a worker thread to do so. Except, if +** there are already N worker threads running, the main thread does the work +** itself. +** +** The sorter is running in multi-threaded mode if (a) the library was built +** with pre-processor symbol SQLITE_MAX_WORKER_THREADS set to a value greater +** than zero, and (b) worker threads have been enabled at runtime by calling +** "PRAGMA threads=N" with some value of N greater than 0. +** +** When Rewind() is called, any data remaining in memory is flushed to a +** final PMA. So at this point the data is stored in some number of sorted +** PMAs within temporary files on disk. +** +** If there are fewer than SORTER_MAX_MERGE_COUNT PMAs in total and the +** sorter is running in single-threaded mode, then these PMAs are merged +** incrementally as keys are retrieved from the sorter by the VDBE. The +** MergeEngine object, described in further detail below, performs this +** merge. +** +** Or, if running in multi-threaded mode, then a background thread is +** launched to merge the existing PMAs. Once the background thread has +** merged T bytes of data into a single sorted PMA, the main thread +** begins reading keys from that PMA while the background thread proceeds +** with merging the next T bytes of data. And so on. +** +** Parameter T is set to half the value of the memory threshold used +** by Write() above to determine when to create a new PMA. +** +** If there are more than SORTER_MAX_MERGE_COUNT PMAs in total when +** Rewind() is called, then a hierarchy of incremental-merges is used. +** First, T bytes of data from the first SORTER_MAX_MERGE_COUNT PMAs on +** disk are merged together. Then T bytes of data from the second set, and +** so on, such that no operation ever merges more than SORTER_MAX_MERGE_COUNT +** PMAs at a time. This done is to improve locality. +** +** If running in multi-threaded mode and there are more than +** SORTER_MAX_MERGE_COUNT PMAs on disk when Rewind() is called, then more +** than one background thread may be created. Specifically, there may be +** one background thread for each temporary file on disk, and one background +** thread to merge the output of each of the others to a single PMA for +** the main thread to read from. +*/ +/* #include "sqliteInt.h" */ +/* #include "vdbeInt.h" */ + +/* +** If SQLITE_DEBUG_SORTER_THREADS is defined, this module outputs various +** messages to stderr that may be helpful in understanding the performance +** characteristics of the sorter in multi-threaded mode. +*/ +#if 0 +# define SQLITE_DEBUG_SORTER_THREADS 1 +#endif + +/* +** Hard-coded maximum amount of data to accumulate in memory before flushing +** to a level 0 PMA. The purpose of this limit is to prevent various integer +** overflows. 512MiB. +*/ +#define SQLITE_MAX_PMASZ (1<<29) + +/* +** Private objects used by the sorter +*/ +typedef struct MergeEngine MergeEngine; /* Merge PMAs together */ +typedef struct PmaReader PmaReader; /* Incrementally read one PMA */ +typedef struct PmaWriter PmaWriter; /* Incrementally write one PMA */ +typedef struct SorterRecord SorterRecord; /* A record being sorted */ +typedef struct SortSubtask SortSubtask; /* A sub-task in the sort process */ +typedef struct SorterFile SorterFile; /* Temporary file object wrapper */ +typedef struct SorterList SorterList; /* In-memory list of records */ +typedef struct IncrMerger IncrMerger; /* Read & merge multiple PMAs */ + +/* +** A container for a temp file handle and the current amount of data +** stored in the file. +*/ +struct SorterFile { + sqlite3_file *pFd; /* File handle */ + i64 iEof; /* Bytes of data stored in pFd */ +}; + +/* +** An in-memory list of objects to be sorted. +** +** If aMemory==0 then each object is allocated separately and the objects +** are connected using SorterRecord.u.pNext. If aMemory!=0 then all objects +** are stored in the aMemory[] bulk memory, one right after the other, and +** are connected using SorterRecord.u.iNext. +*/ +struct SorterList { + SorterRecord *pList; /* Linked list of records */ + u8 *aMemory; /* If non-NULL, bulk memory to hold pList */ + int szPMA; /* Size of pList as PMA in bytes */ +}; + +/* +** The MergeEngine object is used to combine two or more smaller PMAs into +** one big PMA using a merge operation. Separate PMAs all need to be +** combined into one big PMA in order to be able to step through the sorted +** records in order. +** +** The aReadr[] array contains a PmaReader object for each of the PMAs being +** merged. An aReadr[] object either points to a valid key or else is at EOF. +** ("EOF" means "End Of File". When aReadr[] is at EOF there is no more data.) +** For the purposes of the paragraphs below, we assume that the array is +** actually N elements in size, where N is the smallest power of 2 greater +** to or equal to the number of PMAs being merged. The extra aReadr[] elements +** are treated as if they are empty (always at EOF). +** +** The aTree[] array is also N elements in size. The value of N is stored in +** the MergeEngine.nTree variable. +** +** The final (N/2) elements of aTree[] contain the results of comparing +** pairs of PMA keys together. Element i contains the result of +** comparing aReadr[2*i-N] and aReadr[2*i-N+1]. Whichever key is smaller, the +** aTree element is set to the index of it. +** +** For the purposes of this comparison, EOF is considered greater than any +** other key value. If the keys are equal (only possible with two EOF +** values), it doesn't matter which index is stored. +** +** The (N/4) elements of aTree[] that precede the final (N/2) described +** above contains the index of the smallest of each block of 4 PmaReaders +** And so on. So that aTree[1] contains the index of the PmaReader that +** currently points to the smallest key value. aTree[0] is unused. +** +** Example: +** +** aReadr[0] -> Banana +** aReadr[1] -> Feijoa +** aReadr[2] -> Elderberry +** aReadr[3] -> Currant +** aReadr[4] -> Grapefruit +** aReadr[5] -> Apple +** aReadr[6] -> Durian +** aReadr[7] -> EOF +** +** aTree[] = { X, 5 0, 5 0, 3, 5, 6 } +** +** The current element is "Apple" (the value of the key indicated by +** PmaReader 5). When the Next() operation is invoked, PmaReader 5 will +** be advanced to the next key in its segment. Say the next key is +** "Eggplant": +** +** aReadr[5] -> Eggplant +** +** The contents of aTree[] are updated first by comparing the new PmaReader +** 5 key to the current key of PmaReader 4 (still "Grapefruit"). The PmaReader +** 5 value is still smaller, so aTree[6] is set to 5. And so on up the tree. +** The value of PmaReader 6 - "Durian" - is now smaller than that of PmaReader +** 5, so aTree[3] is set to 6. Key 0 is smaller than key 6 (Banana<Durian), +** so the value written into element 1 of the array is 0. As follows: +** +** aTree[] = { X, 0 0, 6 0, 3, 5, 6 } +** +** In other words, each time we advance to the next sorter element, log2(N) +** key comparison operations are required, where N is the number of segments +** being merged (rounded up to the next power of 2). +*/ +struct MergeEngine { + int nTree; /* Used size of aTree/aReadr (power of 2) */ + SortSubtask *pTask; /* Used by this thread only */ + int *aTree; /* Current state of incremental merge */ + PmaReader *aReadr; /* Array of PmaReaders to merge data from */ +}; + +/* +** This object represents a single thread of control in a sort operation. +** Exactly VdbeSorter.nTask instances of this object are allocated +** as part of each VdbeSorter object. Instances are never allocated any +** other way. VdbeSorter.nTask is set to the number of worker threads allowed +** (see SQLITE_CONFIG_WORKER_THREADS) plus one (the main thread). Thus for +** single-threaded operation, there is exactly one instance of this object +** and for multi-threaded operation there are two or more instances. +** +** Essentially, this structure contains all those fields of the VdbeSorter +** structure for which each thread requires a separate instance. For example, +** each thread requeries its own UnpackedRecord object to unpack records in +** as part of comparison operations. +** +** Before a background thread is launched, variable bDone is set to 0. Then, +** right before it exits, the thread itself sets bDone to 1. This is used for +** two purposes: +** +** 1. When flushing the contents of memory to a level-0 PMA on disk, to +** attempt to select a SortSubtask for which there is not already an +** active background thread (since doing so causes the main thread +** to block until it finishes). +** +** 2. If SQLITE_DEBUG_SORTER_THREADS is defined, to determine if a call +** to sqlite3ThreadJoin() is likely to block. Cases that are likely to +** block provoke debugging output. +** +** In both cases, the effects of the main thread seeing (bDone==0) even +** after the thread has finished are not dire. So we don't worry about +** memory barriers and such here. +*/ +typedef int (*SorterCompare)(SortSubtask*,int*,const void*,int,const void*,int); +struct SortSubtask { + SQLiteThread *pThread; /* Background thread, if any */ + int bDone; /* Set if thread is finished but not joined */ + VdbeSorter *pSorter; /* Sorter that owns this sub-task */ + UnpackedRecord *pUnpacked; /* Space to unpack a record */ + SorterList list; /* List for thread to write to a PMA */ + int nPMA; /* Number of PMAs currently in file */ + SorterCompare xCompare; /* Compare function to use */ + SorterFile file; /* Temp file for level-0 PMAs */ + SorterFile file2; /* Space for other PMAs */ +}; + + +/* +** Main sorter structure. A single instance of this is allocated for each +** sorter cursor created by the VDBE. +** +** mxKeysize: +** As records are added to the sorter by calls to sqlite3VdbeSorterWrite(), +** this variable is updated so as to be set to the size on disk of the +** largest record in the sorter. +*/ +struct VdbeSorter { + int mnPmaSize; /* Minimum PMA size, in bytes */ + int mxPmaSize; /* Maximum PMA size, in bytes. 0==no limit */ + int mxKeysize; /* Largest serialized key seen so far */ + int pgsz; /* Main database page size */ + PmaReader *pReader; /* Readr data from here after Rewind() */ + MergeEngine *pMerger; /* Or here, if bUseThreads==0 */ + sqlite3 *db; /* Database connection */ + KeyInfo *pKeyInfo; /* How to compare records */ + UnpackedRecord *pUnpacked; /* Used by VdbeSorterCompare() */ + SorterList list; /* List of in-memory records */ + int iMemory; /* Offset of free space in list.aMemory */ + int nMemory; /* Size of list.aMemory allocation in bytes */ + u8 bUsePMA; /* True if one or more PMAs created */ + u8 bUseThreads; /* True to use background threads */ + u8 iPrev; /* Previous thread used to flush PMA */ + u8 nTask; /* Size of aTask[] array */ + u8 typeMask; + SortSubtask aTask[1]; /* One or more subtasks */ +}; + +#define SORTER_TYPE_INTEGER 0x01 +#define SORTER_TYPE_TEXT 0x02 + +/* +** An instance of the following object is used to read records out of a +** PMA, in sorted order. The next key to be read is cached in nKey/aKey. +** aKey might point into aMap or into aBuffer. If neither of those locations +** contain a contiguous representation of the key, then aAlloc is allocated +** and the key is copied into aAlloc and aKey is made to point to aAlloc. +** +** pFd==0 at EOF. +*/ +struct PmaReader { + i64 iReadOff; /* Current read offset */ + i64 iEof; /* 1 byte past EOF for this PmaReader */ + int nAlloc; /* Bytes of space at aAlloc */ + int nKey; /* Number of bytes in key */ + sqlite3_file *pFd; /* File handle we are reading from */ + u8 *aAlloc; /* Space for aKey if aBuffer and pMap wont work */ + u8 *aKey; /* Pointer to current key */ + u8 *aBuffer; /* Current read buffer */ + int nBuffer; /* Size of read buffer in bytes */ + u8 *aMap; /* Pointer to mapping of entire file */ + IncrMerger *pIncr; /* Incremental merger */ +}; + +/* +** Normally, a PmaReader object iterates through an existing PMA stored +** within a temp file. However, if the PmaReader.pIncr variable points to +** an object of the following type, it may be used to iterate/merge through +** multiple PMAs simultaneously. +** +** There are two types of IncrMerger object - single (bUseThread==0) and +** multi-threaded (bUseThread==1). +** +** A multi-threaded IncrMerger object uses two temporary files - aFile[0] +** and aFile[1]. Neither file is allowed to grow to more than mxSz bytes in +** size. When the IncrMerger is initialized, it reads enough data from +** pMerger to populate aFile[0]. It then sets variables within the +** corresponding PmaReader object to read from that file and kicks off +** a background thread to populate aFile[1] with the next mxSz bytes of +** sorted record data from pMerger. +** +** When the PmaReader reaches the end of aFile[0], it blocks until the +** background thread has finished populating aFile[1]. It then exchanges +** the contents of the aFile[0] and aFile[1] variables within this structure, +** sets the PmaReader fields to read from the new aFile[0] and kicks off +** another background thread to populate the new aFile[1]. And so on, until +** the contents of pMerger are exhausted. +** +** A single-threaded IncrMerger does not open any temporary files of its +** own. Instead, it has exclusive access to mxSz bytes of space beginning +** at offset iStartOff of file pTask->file2. And instead of using a +** background thread to prepare data for the PmaReader, with a single +** threaded IncrMerger the allocate part of pTask->file2 is "refilled" with +** keys from pMerger by the calling thread whenever the PmaReader runs out +** of data. +*/ +struct IncrMerger { + SortSubtask *pTask; /* Task that owns this merger */ + MergeEngine *pMerger; /* Merge engine thread reads data from */ + i64 iStartOff; /* Offset to start writing file at */ + int mxSz; /* Maximum bytes of data to store */ + int bEof; /* Set to true when merge is finished */ + int bUseThread; /* True to use a bg thread for this object */ + SorterFile aFile[2]; /* aFile[0] for reading, [1] for writing */ +}; + +/* +** An instance of this object is used for writing a PMA. +** +** The PMA is written one record at a time. Each record is of an arbitrary +** size. But I/O is more efficient if it occurs in page-sized blocks where +** each block is aligned on a page boundary. This object caches writes to +** the PMA so that aligned, page-size blocks are written. +*/ +struct PmaWriter { + int eFWErr; /* Non-zero if in an error state */ + u8 *aBuffer; /* Pointer to write buffer */ + int nBuffer; /* Size of write buffer in bytes */ + int iBufStart; /* First byte of buffer to write */ + int iBufEnd; /* Last byte of buffer to write */ + i64 iWriteOff; /* Offset of start of buffer in file */ + sqlite3_file *pFd; /* File handle to write to */ +}; + +/* +** This object is the header on a single record while that record is being +** held in memory and prior to being written out as part of a PMA. +** +** How the linked list is connected depends on how memory is being managed +** by this module. If using a separate allocation for each in-memory record +** (VdbeSorter.list.aMemory==0), then the list is always connected using the +** SorterRecord.u.pNext pointers. +** +** Or, if using the single large allocation method (VdbeSorter.list.aMemory!=0), +** then while records are being accumulated the list is linked using the +** SorterRecord.u.iNext offset. This is because the aMemory[] array may +** be sqlite3Realloc()ed while records are being accumulated. Once the VM +** has finished passing records to the sorter, or when the in-memory buffer +** is full, the list is sorted. As part of the sorting process, it is +** converted to use the SorterRecord.u.pNext pointers. See function +** vdbeSorterSort() for details. +*/ +struct SorterRecord { + int nVal; /* Size of the record in bytes */ + union { + SorterRecord *pNext; /* Pointer to next record in list */ + int iNext; /* Offset within aMemory of next record */ + } u; + /* The data for the record immediately follows this header */ +}; + +/* Return a pointer to the buffer containing the record data for SorterRecord +** object p. Should be used as if: +** +** void *SRVAL(SorterRecord *p) { return (void*)&p[1]; } +*/ +#define SRVAL(p) ((void*)((SorterRecord*)(p) + 1)) + + +/* Maximum number of PMAs that a single MergeEngine can merge */ +#define SORTER_MAX_MERGE_COUNT 16 + +static int vdbeIncrSwap(IncrMerger*); +static void vdbeIncrFree(IncrMerger *); + +/* +** Free all memory belonging to the PmaReader object passed as the +** argument. All structure fields are set to zero before returning. +*/ +static void vdbePmaReaderClear(PmaReader *pReadr){ + sqlite3_free(pReadr->aAlloc); + sqlite3_free(pReadr->aBuffer); + if( pReadr->aMap ) sqlite3OsUnfetch(pReadr->pFd, 0, pReadr->aMap); + vdbeIncrFree(pReadr->pIncr); + memset(pReadr, 0, sizeof(PmaReader)); +} + +/* +** Read the next nByte bytes of data from the PMA p. +** If successful, set *ppOut to point to a buffer containing the data +** and return SQLITE_OK. Otherwise, if an error occurs, return an SQLite +** error code. +** +** The buffer returned in *ppOut is only valid until the +** next call to this function. +*/ +static int vdbePmaReadBlob( + PmaReader *p, /* PmaReader from which to take the blob */ + int nByte, /* Bytes of data to read */ + u8 **ppOut /* OUT: Pointer to buffer containing data */ +){ + int iBuf; /* Offset within buffer to read from */ + int nAvail; /* Bytes of data available in buffer */ + + if( p->aMap ){ + *ppOut = &p->aMap[p->iReadOff]; + p->iReadOff += nByte; + return SQLITE_OK; + } + + assert( p->aBuffer ); + + /* If there is no more data to be read from the buffer, read the next + ** p->nBuffer bytes of data from the file into it. Or, if there are less + ** than p->nBuffer bytes remaining in the PMA, read all remaining data. */ + iBuf = p->iReadOff % p->nBuffer; + if( iBuf==0 ){ + int nRead; /* Bytes to read from disk */ + int rc; /* sqlite3OsRead() return code */ + + /* Determine how many bytes of data to read. */ + if( (p->iEof - p->iReadOff) > (i64)p->nBuffer ){ + nRead = p->nBuffer; + }else{ + nRead = (int)(p->iEof - p->iReadOff); + } + assert( nRead>0 ); + + /* Readr data from the file. Return early if an error occurs. */ + rc = sqlite3OsRead(p->pFd, p->aBuffer, nRead, p->iReadOff); + assert( rc!=SQLITE_IOERR_SHORT_READ ); + if( rc!=SQLITE_OK ) return rc; + } + nAvail = p->nBuffer - iBuf; + + if( nByte<=nAvail ){ + /* The requested data is available in the in-memory buffer. In this + ** case there is no need to make a copy of the data, just return a + ** pointer into the buffer to the caller. */ + *ppOut = &p->aBuffer[iBuf]; + p->iReadOff += nByte; + }else{ + /* The requested data is not all available in the in-memory buffer. + ** In this case, allocate space at p->aAlloc[] to copy the requested + ** range into. Then return a copy of pointer p->aAlloc to the caller. */ + int nRem; /* Bytes remaining to copy */ + + /* Extend the p->aAlloc[] allocation if required. */ + if( p->nAlloc<nByte ){ + u8 *aNew; + sqlite3_int64 nNew = MAX(128, 2*(sqlite3_int64)p->nAlloc); + while( nByte>nNew ) nNew = nNew*2; + aNew = sqlite3Realloc(p->aAlloc, nNew); + if( !aNew ) return SQLITE_NOMEM_BKPT; + p->nAlloc = nNew; + p->aAlloc = aNew; + } + + /* Copy as much data as is available in the buffer into the start of + ** p->aAlloc[]. */ + memcpy(p->aAlloc, &p->aBuffer[iBuf], nAvail); + p->iReadOff += nAvail; + nRem = nByte - nAvail; + + /* The following loop copies up to p->nBuffer bytes per iteration into + ** the p->aAlloc[] buffer. */ + while( nRem>0 ){ + int rc; /* vdbePmaReadBlob() return code */ + int nCopy; /* Number of bytes to copy */ + u8 *aNext; /* Pointer to buffer to copy data from */ + + nCopy = nRem; + if( nRem>p->nBuffer ) nCopy = p->nBuffer; + rc = vdbePmaReadBlob(p, nCopy, &aNext); + if( rc!=SQLITE_OK ) return rc; + assert( aNext!=p->aAlloc ); + memcpy(&p->aAlloc[nByte - nRem], aNext, nCopy); + nRem -= nCopy; + } + + *ppOut = p->aAlloc; + } + + return SQLITE_OK; +} + +/* +** Read a varint from the stream of data accessed by p. Set *pnOut to +** the value read. +*/ +static int vdbePmaReadVarint(PmaReader *p, u64 *pnOut){ + int iBuf; + + if( p->aMap ){ + p->iReadOff += sqlite3GetVarint(&p->aMap[p->iReadOff], pnOut); + }else{ + iBuf = p->iReadOff % p->nBuffer; + if( iBuf && (p->nBuffer-iBuf)>=9 ){ + p->iReadOff += sqlite3GetVarint(&p->aBuffer[iBuf], pnOut); + }else{ + u8 aVarint[16], *a; + int i = 0, rc; + do{ + rc = vdbePmaReadBlob(p, 1, &a); + if( rc ) return rc; + aVarint[(i++)&0xf] = a[0]; + }while( (a[0]&0x80)!=0 ); + sqlite3GetVarint(aVarint, pnOut); + } + } + + return SQLITE_OK; +} + +/* +** Attempt to memory map file pFile. If successful, set *pp to point to the +** new mapping and return SQLITE_OK. If the mapping is not attempted +** (because the file is too large or the VFS layer is configured not to use +** mmap), return SQLITE_OK and set *pp to NULL. +** +** Or, if an error occurs, return an SQLite error code. The final value of +** *pp is undefined in this case. +*/ +static int vdbeSorterMapFile(SortSubtask *pTask, SorterFile *pFile, u8 **pp){ + int rc = SQLITE_OK; + if( pFile->iEof<=(i64)(pTask->pSorter->db->nMaxSorterMmap) ){ + sqlite3_file *pFd = pFile->pFd; + if( pFd->pMethods->iVersion>=3 ){ + rc = sqlite3OsFetch(pFd, 0, (int)pFile->iEof, (void**)pp); + testcase( rc!=SQLITE_OK ); + } + } + return rc; +} + +/* +** Attach PmaReader pReadr to file pFile (if it is not already attached to +** that file) and seek it to offset iOff within the file. Return SQLITE_OK +** if successful, or an SQLite error code if an error occurs. +*/ +static int vdbePmaReaderSeek( + SortSubtask *pTask, /* Task context */ + PmaReader *pReadr, /* Reader whose cursor is to be moved */ + SorterFile *pFile, /* Sorter file to read from */ + i64 iOff /* Offset in pFile */ +){ + int rc = SQLITE_OK; + + assert( pReadr->pIncr==0 || pReadr->pIncr->bEof==0 ); + + if( sqlite3FaultSim(201) ) return SQLITE_IOERR_READ; + if( pReadr->aMap ){ + sqlite3OsUnfetch(pReadr->pFd, 0, pReadr->aMap); + pReadr->aMap = 0; + } + pReadr->iReadOff = iOff; + pReadr->iEof = pFile->iEof; + pReadr->pFd = pFile->pFd; + + rc = vdbeSorterMapFile(pTask, pFile, &pReadr->aMap); + if( rc==SQLITE_OK && pReadr->aMap==0 ){ + int pgsz = pTask->pSorter->pgsz; + int iBuf = pReadr->iReadOff % pgsz; + if( pReadr->aBuffer==0 ){ + pReadr->aBuffer = (u8*)sqlite3Malloc(pgsz); + if( pReadr->aBuffer==0 ) rc = SQLITE_NOMEM_BKPT; + pReadr->nBuffer = pgsz; + } + if( rc==SQLITE_OK && iBuf ){ + int nRead = pgsz - iBuf; + if( (pReadr->iReadOff + nRead) > pReadr->iEof ){ + nRead = (int)(pReadr->iEof - pReadr->iReadOff); + } + rc = sqlite3OsRead( + pReadr->pFd, &pReadr->aBuffer[iBuf], nRead, pReadr->iReadOff + ); + testcase( rc!=SQLITE_OK ); + } + } + + return rc; +} + +/* +** Advance PmaReader pReadr to the next key in its PMA. Return SQLITE_OK if +** no error occurs, or an SQLite error code if one does. +*/ +static int vdbePmaReaderNext(PmaReader *pReadr){ + int rc = SQLITE_OK; /* Return Code */ + u64 nRec = 0; /* Size of record in bytes */ + + + if( pReadr->iReadOff>=pReadr->iEof ){ + IncrMerger *pIncr = pReadr->pIncr; + int bEof = 1; + if( pIncr ){ + rc = vdbeIncrSwap(pIncr); + if( rc==SQLITE_OK && pIncr->bEof==0 ){ + rc = vdbePmaReaderSeek( + pIncr->pTask, pReadr, &pIncr->aFile[0], pIncr->iStartOff + ); + bEof = 0; + } + } + + if( bEof ){ + /* This is an EOF condition */ + vdbePmaReaderClear(pReadr); + testcase( rc!=SQLITE_OK ); + return rc; + } + } + + if( rc==SQLITE_OK ){ + rc = vdbePmaReadVarint(pReadr, &nRec); + } + if( rc==SQLITE_OK ){ + pReadr->nKey = (int)nRec; + rc = vdbePmaReadBlob(pReadr, (int)nRec, &pReadr->aKey); + testcase( rc!=SQLITE_OK ); + } + + return rc; +} + +/* +** Initialize PmaReader pReadr to scan through the PMA stored in file pFile +** starting at offset iStart and ending at offset iEof-1. This function +** leaves the PmaReader pointing to the first key in the PMA (or EOF if the +** PMA is empty). +** +** If the pnByte parameter is NULL, then it is assumed that the file +** contains a single PMA, and that that PMA omits the initial length varint. +*/ +static int vdbePmaReaderInit( + SortSubtask *pTask, /* Task context */ + SorterFile *pFile, /* Sorter file to read from */ + i64 iStart, /* Start offset in pFile */ + PmaReader *pReadr, /* PmaReader to populate */ + i64 *pnByte /* IN/OUT: Increment this value by PMA size */ +){ + int rc; + + assert( pFile->iEof>iStart ); + assert( pReadr->aAlloc==0 && pReadr->nAlloc==0 ); + assert( pReadr->aBuffer==0 ); + assert( pReadr->aMap==0 ); + + rc = vdbePmaReaderSeek(pTask, pReadr, pFile, iStart); + if( rc==SQLITE_OK ){ + u64 nByte = 0; /* Size of PMA in bytes */ + rc = vdbePmaReadVarint(pReadr, &nByte); + pReadr->iEof = pReadr->iReadOff + nByte; + *pnByte += nByte; + } + + if( rc==SQLITE_OK ){ + rc = vdbePmaReaderNext(pReadr); + } + return rc; +} + +/* +** A version of vdbeSorterCompare() that assumes that it has already been +** determined that the first field of key1 is equal to the first field of +** key2. +*/ +static int vdbeSorterCompareTail( + SortSubtask *pTask, /* Subtask context (for pKeyInfo) */ + int *pbKey2Cached, /* True if pTask->pUnpacked is pKey2 */ + const void *pKey1, int nKey1, /* Left side of comparison */ + const void *pKey2, int nKey2 /* Right side of comparison */ +){ + UnpackedRecord *r2 = pTask->pUnpacked; + if( *pbKey2Cached==0 ){ + sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2); + *pbKey2Cached = 1; + } + return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, r2, 1); +} + +/* +** Compare key1 (buffer pKey1, size nKey1 bytes) with key2 (buffer pKey2, +** size nKey2 bytes). Use (pTask->pKeyInfo) for the collation sequences +** used by the comparison. Return the result of the comparison. +** +** If IN/OUT parameter *pbKey2Cached is true when this function is called, +** it is assumed that (pTask->pUnpacked) contains the unpacked version +** of key2. If it is false, (pTask->pUnpacked) is populated with the unpacked +** version of key2 and *pbKey2Cached set to true before returning. +** +** If an OOM error is encountered, (pTask->pUnpacked->error_rc) is set +** to SQLITE_NOMEM. +*/ +static int vdbeSorterCompare( + SortSubtask *pTask, /* Subtask context (for pKeyInfo) */ + int *pbKey2Cached, /* True if pTask->pUnpacked is pKey2 */ + const void *pKey1, int nKey1, /* Left side of comparison */ + const void *pKey2, int nKey2 /* Right side of comparison */ +){ + UnpackedRecord *r2 = pTask->pUnpacked; + if( !*pbKey2Cached ){ + sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2); + *pbKey2Cached = 1; + } + return sqlite3VdbeRecordCompare(nKey1, pKey1, r2); +} + +/* +** A specially optimized version of vdbeSorterCompare() that assumes that +** the first field of each key is a TEXT value and that the collation +** sequence to compare them with is BINARY. +*/ +static int vdbeSorterCompareText( + SortSubtask *pTask, /* Subtask context (for pKeyInfo) */ + int *pbKey2Cached, /* True if pTask->pUnpacked is pKey2 */ + const void *pKey1, int nKey1, /* Left side of comparison */ + const void *pKey2, int nKey2 /* Right side of comparison */ +){ + const u8 * const p1 = (const u8 * const)pKey1; + const u8 * const p2 = (const u8 * const)pKey2; + const u8 * const v1 = &p1[ p1[0] ]; /* Pointer to value 1 */ + const u8 * const v2 = &p2[ p2[0] ]; /* Pointer to value 2 */ + + int n1; + int n2; + int res; + + getVarint32NR(&p1[1], n1); + getVarint32NR(&p2[1], n2); + res = memcmp(v1, v2, (MIN(n1, n2) - 13)/2); + if( res==0 ){ + res = n1 - n2; + } + + if( res==0 ){ + if( pTask->pSorter->pKeyInfo->nKeyField>1 ){ + res = vdbeSorterCompareTail( + pTask, pbKey2Cached, pKey1, nKey1, pKey2, nKey2 + ); + } + }else{ + assert( !(pTask->pSorter->pKeyInfo->aSortFlags[0]&KEYINFO_ORDER_BIGNULL) ); + if( pTask->pSorter->pKeyInfo->aSortFlags[0] ){ + res = res * -1; + } + } + + return res; +} + +/* +** A specially optimized version of vdbeSorterCompare() that assumes that +** the first field of each key is an INTEGER value. +*/ +static int vdbeSorterCompareInt( + SortSubtask *pTask, /* Subtask context (for pKeyInfo) */ + int *pbKey2Cached, /* True if pTask->pUnpacked is pKey2 */ + const void *pKey1, int nKey1, /* Left side of comparison */ + const void *pKey2, int nKey2 /* Right side of comparison */ +){ + const u8 * const p1 = (const u8 * const)pKey1; + const u8 * const p2 = (const u8 * const)pKey2; + const int s1 = p1[1]; /* Left hand serial type */ + const int s2 = p2[1]; /* Right hand serial type */ + const u8 * const v1 = &p1[ p1[0] ]; /* Pointer to value 1 */ + const u8 * const v2 = &p2[ p2[0] ]; /* Pointer to value 2 */ + int res; /* Return value */ + + assert( (s1>0 && s1<7) || s1==8 || s1==9 ); + assert( (s2>0 && s2<7) || s2==8 || s2==9 ); + + if( s1==s2 ){ + /* The two values have the same sign. Compare using memcmp(). */ + static const u8 aLen[] = {0, 1, 2, 3, 4, 6, 8, 0, 0, 0 }; + const u8 n = aLen[s1]; + int i; + res = 0; + for(i=0; i<n; i++){ + if( (res = v1[i] - v2[i])!=0 ){ + if( ((v1[0] ^ v2[0]) & 0x80)!=0 ){ + res = v1[0] & 0x80 ? -1 : +1; + } + break; + } + } + }else if( s1>7 && s2>7 ){ + res = s1 - s2; + }else{ + if( s2>7 ){ + res = +1; + }else if( s1>7 ){ + res = -1; + }else{ + res = s1 - s2; + } + assert( res!=0 ); + + if( res>0 ){ + if( *v1 & 0x80 ) res = -1; + }else{ + if( *v2 & 0x80 ) res = +1; + } + } + + if( res==0 ){ + if( pTask->pSorter->pKeyInfo->nKeyField>1 ){ + res = vdbeSorterCompareTail( + pTask, pbKey2Cached, pKey1, nKey1, pKey2, nKey2 + ); + } + }else if( pTask->pSorter->pKeyInfo->aSortFlags[0] ){ + assert( !(pTask->pSorter->pKeyInfo->aSortFlags[0]&KEYINFO_ORDER_BIGNULL) ); + res = res * -1; + } + + return res; +} + +/* +** Initialize the temporary index cursor just opened as a sorter cursor. +** +** Usually, the sorter module uses the value of (pCsr->pKeyInfo->nKeyField) +** to determine the number of fields that should be compared from the +** records being sorted. However, if the value passed as argument nField +** is non-zero and the sorter is able to guarantee a stable sort, nField +** is used instead. This is used when sorting records for a CREATE INDEX +** statement. In this case, keys are always delivered to the sorter in +** order of the primary key, which happens to be make up the final part +** of the records being sorted. So if the sort is stable, there is never +** any reason to compare PK fields and they can be ignored for a small +** performance boost. +** +** The sorter can guarantee a stable sort when running in single-threaded +** mode, but not in multi-threaded mode. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +SQLITE_PRIVATE int sqlite3VdbeSorterInit( + sqlite3 *db, /* Database connection (for malloc()) */ + int nField, /* Number of key fields in each record */ + VdbeCursor *pCsr /* Cursor that holds the new sorter */ +){ + int pgsz; /* Page size of main database */ + int i; /* Used to iterate through aTask[] */ + VdbeSorter *pSorter; /* The new sorter */ + KeyInfo *pKeyInfo; /* Copy of pCsr->pKeyInfo with db==0 */ + int szKeyInfo; /* Size of pCsr->pKeyInfo in bytes */ + int sz; /* Size of pSorter in bytes */ + int rc = SQLITE_OK; +#if SQLITE_MAX_WORKER_THREADS==0 +# define nWorker 0 +#else + int nWorker; +#endif + + /* Initialize the upper limit on the number of worker threads */ +#if SQLITE_MAX_WORKER_THREADS>0 + if( sqlite3TempInMemory(db) || sqlite3GlobalConfig.bCoreMutex==0 ){ + nWorker = 0; + }else{ + nWorker = db->aLimit[SQLITE_LIMIT_WORKER_THREADS]; + } +#endif + + /* Do not allow the total number of threads (main thread + all workers) + ** to exceed the maximum merge count */ +#if SQLITE_MAX_WORKER_THREADS>=SORTER_MAX_MERGE_COUNT + if( nWorker>=SORTER_MAX_MERGE_COUNT ){ + nWorker = SORTER_MAX_MERGE_COUNT-1; + } +#endif + + assert( pCsr->pKeyInfo ); + assert( !pCsr->isEphemeral ); + assert( pCsr->eCurType==CURTYPE_SORTER ); + szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nKeyField-1)*sizeof(CollSeq*); + sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask); + + pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo); + pCsr->uc.pSorter = pSorter; + if( pSorter==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + Btree *pBt = db->aDb[0].pBt; + pSorter->pKeyInfo = pKeyInfo = (KeyInfo*)((u8*)pSorter + sz); + memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo); + pKeyInfo->db = 0; + if( nField && nWorker==0 ){ + pKeyInfo->nKeyField = nField; + } + sqlite3BtreeEnter(pBt); + pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(pBt); + sqlite3BtreeLeave(pBt); + pSorter->nTask = nWorker + 1; + pSorter->iPrev = (u8)(nWorker - 1); + pSorter->bUseThreads = (pSorter->nTask>1); + pSorter->db = db; + for(i=0; i<pSorter->nTask; i++){ + SortSubtask *pTask = &pSorter->aTask[i]; + pTask->pSorter = pSorter; + } + + if( !sqlite3TempInMemory(db) ){ + i64 mxCache; /* Cache size in bytes*/ + u32 szPma = sqlite3GlobalConfig.szPma; + pSorter->mnPmaSize = szPma * pgsz; + + mxCache = db->aDb[0].pSchema->cache_size; + if( mxCache<0 ){ + /* A negative cache-size value C indicates that the cache is abs(C) + ** KiB in size. */ + mxCache = mxCache * -1024; + }else{ + mxCache = mxCache * pgsz; + } + mxCache = MIN(mxCache, SQLITE_MAX_PMASZ); + pSorter->mxPmaSize = MAX(pSorter->mnPmaSize, (int)mxCache); + + /* Avoid large memory allocations if the application has requested + ** SQLITE_CONFIG_SMALL_MALLOC. */ + if( sqlite3GlobalConfig.bSmallMalloc==0 ){ + assert( pSorter->iMemory==0 ); + pSorter->nMemory = pgsz; + pSorter->list.aMemory = (u8*)sqlite3Malloc(pgsz); + if( !pSorter->list.aMemory ) rc = SQLITE_NOMEM_BKPT; + } + } + + if( pKeyInfo->nAllField<13 + && (pKeyInfo->aColl[0]==0 || pKeyInfo->aColl[0]==db->pDfltColl) + && (pKeyInfo->aSortFlags[0] & KEYINFO_ORDER_BIGNULL)==0 + ){ + pSorter->typeMask = SORTER_TYPE_INTEGER | SORTER_TYPE_TEXT; + } + } + + return rc; +} +#undef nWorker /* Defined at the top of this function */ + +/* +** Free the list of sorted records starting at pRecord. +*/ +static void vdbeSorterRecordFree(sqlite3 *db, SorterRecord *pRecord){ + SorterRecord *p; + SorterRecord *pNext; + for(p=pRecord; p; p=pNext){ + pNext = p->u.pNext; + sqlite3DbFree(db, p); + } +} + +/* +** Free all resources owned by the object indicated by argument pTask. All +** fields of *pTask are zeroed before returning. +*/ +static void vdbeSortSubtaskCleanup(sqlite3 *db, SortSubtask *pTask){ + sqlite3DbFree(db, pTask->pUnpacked); +#if SQLITE_MAX_WORKER_THREADS>0 + /* pTask->list.aMemory can only be non-zero if it was handed memory + ** from the main thread. That only occurs SQLITE_MAX_WORKER_THREADS>0 */ + if( pTask->list.aMemory ){ + sqlite3_free(pTask->list.aMemory); + }else +#endif + { + assert( pTask->list.aMemory==0 ); + vdbeSorterRecordFree(0, pTask->list.pList); + } + if( pTask->file.pFd ){ + sqlite3OsCloseFree(pTask->file.pFd); + } + if( pTask->file2.pFd ){ + sqlite3OsCloseFree(pTask->file2.pFd); + } + memset(pTask, 0, sizeof(SortSubtask)); +} + +#ifdef SQLITE_DEBUG_SORTER_THREADS +static void vdbeSorterWorkDebug(SortSubtask *pTask, const char *zEvent){ + i64 t; + int iTask = (pTask - pTask->pSorter->aTask); + sqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t); + fprintf(stderr, "%lld:%d %s\n", t, iTask, zEvent); +} +static void vdbeSorterRewindDebug(const char *zEvent){ + i64 t = 0; + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + if( ALWAYS(pVfs) ) sqlite3OsCurrentTimeInt64(pVfs, &t); + fprintf(stderr, "%lld:X %s\n", t, zEvent); +} +static void vdbeSorterPopulateDebug( + SortSubtask *pTask, + const char *zEvent +){ + i64 t; + int iTask = (pTask - pTask->pSorter->aTask); + sqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t); + fprintf(stderr, "%lld:bg%d %s\n", t, iTask, zEvent); +} +static void vdbeSorterBlockDebug( + SortSubtask *pTask, + int bBlocked, + const char *zEvent +){ + if( bBlocked ){ + i64 t; + sqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t); + fprintf(stderr, "%lld:main %s\n", t, zEvent); + } +} +#else +# define vdbeSorterWorkDebug(x,y) +# define vdbeSorterRewindDebug(y) +# define vdbeSorterPopulateDebug(x,y) +# define vdbeSorterBlockDebug(x,y,z) +#endif + +#if SQLITE_MAX_WORKER_THREADS>0 +/* +** Join thread pTask->thread. +*/ +static int vdbeSorterJoinThread(SortSubtask *pTask){ + int rc = SQLITE_OK; + if( pTask->pThread ){ +#ifdef SQLITE_DEBUG_SORTER_THREADS + int bDone = pTask->bDone; +#endif + void *pRet = SQLITE_INT_TO_PTR(SQLITE_ERROR); + vdbeSorterBlockDebug(pTask, !bDone, "enter"); + (void)sqlite3ThreadJoin(pTask->pThread, &pRet); + vdbeSorterBlockDebug(pTask, !bDone, "exit"); + rc = SQLITE_PTR_TO_INT(pRet); + assert( pTask->bDone==1 ); + pTask->bDone = 0; + pTask->pThread = 0; + } + return rc; +} + +/* +** Launch a background thread to run xTask(pIn). +*/ +static int vdbeSorterCreateThread( + SortSubtask *pTask, /* Thread will use this task object */ + void *(*xTask)(void*), /* Routine to run in a separate thread */ + void *pIn /* Argument passed into xTask() */ +){ + assert( pTask->pThread==0 && pTask->bDone==0 ); + return sqlite3ThreadCreate(&pTask->pThread, xTask, pIn); +} + +/* +** Join all outstanding threads launched by SorterWrite() to create +** level-0 PMAs. +*/ +static int vdbeSorterJoinAll(VdbeSorter *pSorter, int rcin){ + int rc = rcin; + int i; + + /* This function is always called by the main user thread. + ** + ** If this function is being called after SorterRewind() has been called, + ** it is possible that thread pSorter->aTask[pSorter->nTask-1].pThread + ** is currently attempt to join one of the other threads. To avoid a race + ** condition where this thread also attempts to join the same object, join + ** thread pSorter->aTask[pSorter->nTask-1].pThread first. */ + for(i=pSorter->nTask-1; i>=0; i--){ + SortSubtask *pTask = &pSorter->aTask[i]; + int rc2 = vdbeSorterJoinThread(pTask); + if( rc==SQLITE_OK ) rc = rc2; + } + return rc; +} +#else +# define vdbeSorterJoinAll(x,rcin) (rcin) +# define vdbeSorterJoinThread(pTask) SQLITE_OK +#endif + +/* +** Allocate a new MergeEngine object capable of handling up to +** nReader PmaReader inputs. +** +** nReader is automatically rounded up to the next power of two. +** nReader may not exceed SORTER_MAX_MERGE_COUNT even after rounding up. +*/ +static MergeEngine *vdbeMergeEngineNew(int nReader){ + int N = 2; /* Smallest power of two >= nReader */ + int nByte; /* Total bytes of space to allocate */ + MergeEngine *pNew; /* Pointer to allocated object to return */ + + assert( nReader<=SORTER_MAX_MERGE_COUNT ); + + while( N<nReader ) N += N; + nByte = sizeof(MergeEngine) + N * (sizeof(int) + sizeof(PmaReader)); + + pNew = sqlite3FaultSim(100) ? 0 : (MergeEngine*)sqlite3MallocZero(nByte); + if( pNew ){ + pNew->nTree = N; + pNew->pTask = 0; + pNew->aReadr = (PmaReader*)&pNew[1]; + pNew->aTree = (int*)&pNew->aReadr[N]; + } + return pNew; +} + +/* +** Free the MergeEngine object passed as the only argument. +*/ +static void vdbeMergeEngineFree(MergeEngine *pMerger){ + int i; + if( pMerger ){ + for(i=0; i<pMerger->nTree; i++){ + vdbePmaReaderClear(&pMerger->aReadr[i]); + } + } + sqlite3_free(pMerger); +} + +/* +** Free all resources associated with the IncrMerger object indicated by +** the first argument. +*/ +static void vdbeIncrFree(IncrMerger *pIncr){ + if( pIncr ){ +#if SQLITE_MAX_WORKER_THREADS>0 + if( pIncr->bUseThread ){ + vdbeSorterJoinThread(pIncr->pTask); + if( pIncr->aFile[0].pFd ) sqlite3OsCloseFree(pIncr->aFile[0].pFd); + if( pIncr->aFile[1].pFd ) sqlite3OsCloseFree(pIncr->aFile[1].pFd); + } +#endif + vdbeMergeEngineFree(pIncr->pMerger); + sqlite3_free(pIncr); + } +} + +/* +** Reset a sorting cursor back to its original empty state. +*/ +SQLITE_PRIVATE void sqlite3VdbeSorterReset(sqlite3 *db, VdbeSorter *pSorter){ + int i; + (void)vdbeSorterJoinAll(pSorter, SQLITE_OK); + assert( pSorter->bUseThreads || pSorter->pReader==0 ); +#if SQLITE_MAX_WORKER_THREADS>0 + if( pSorter->pReader ){ + vdbePmaReaderClear(pSorter->pReader); + sqlite3DbFree(db, pSorter->pReader); + pSorter->pReader = 0; + } +#endif + vdbeMergeEngineFree(pSorter->pMerger); + pSorter->pMerger = 0; + for(i=0; i<pSorter->nTask; i++){ + SortSubtask *pTask = &pSorter->aTask[i]; + vdbeSortSubtaskCleanup(db, pTask); + pTask->pSorter = pSorter; + } + if( pSorter->list.aMemory==0 ){ + vdbeSorterRecordFree(0, pSorter->list.pList); + } + pSorter->list.pList = 0; + pSorter->list.szPMA = 0; + pSorter->bUsePMA = 0; + pSorter->iMemory = 0; + pSorter->mxKeysize = 0; + sqlite3DbFree(db, pSorter->pUnpacked); + pSorter->pUnpacked = 0; +} + +/* +** Free any cursor components allocated by sqlite3VdbeSorterXXX routines. +*/ +SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){ + VdbeSorter *pSorter; + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; + if( pSorter ){ + sqlite3VdbeSorterReset(db, pSorter); + sqlite3_free(pSorter->list.aMemory); + sqlite3DbFree(db, pSorter); + pCsr->uc.pSorter = 0; + } +} + +#if SQLITE_MAX_MMAP_SIZE>0 +/* +** The first argument is a file-handle open on a temporary file. The file +** is guaranteed to be nByte bytes or smaller in size. This function +** attempts to extend the file to nByte bytes in size and to ensure that +** the VFS has memory mapped it. +** +** Whether or not the file does end up memory mapped of course depends on +** the specific VFS implementation. +*/ +static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFd, i64 nByte){ + if( nByte<=(i64)(db->nMaxSorterMmap) && pFd->pMethods->iVersion>=3 ){ + void *p = 0; + int chunksize = 4*1024; + sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_CHUNK_SIZE, &chunksize); + sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_SIZE_HINT, &nByte); + sqlite3OsFetch(pFd, 0, (int)nByte, &p); + if( p ) sqlite3OsUnfetch(pFd, 0, p); + } +} +#else +# define vdbeSorterExtendFile(x,y,z) +#endif + +/* +** Allocate space for a file-handle and open a temporary file. If successful, +** set *ppFd to point to the malloc'd file-handle and return SQLITE_OK. +** Otherwise, set *ppFd to 0 and return an SQLite error code. +*/ +static int vdbeSorterOpenTempFile( + sqlite3 *db, /* Database handle doing sort */ + i64 nExtend, /* Attempt to extend file to this size */ + sqlite3_file **ppFd +){ + int rc; + if( sqlite3FaultSim(202) ) return SQLITE_IOERR_ACCESS; + rc = sqlite3OsOpenMalloc(db->pVfs, 0, ppFd, + SQLITE_OPEN_TEMP_JOURNAL | + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | + SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE, &rc + ); + if( rc==SQLITE_OK ){ + i64 max = SQLITE_MAX_MMAP_SIZE; + sqlite3OsFileControlHint(*ppFd, SQLITE_FCNTL_MMAP_SIZE, (void*)&max); + if( nExtend>0 ){ + vdbeSorterExtendFile(db, *ppFd, nExtend); + } + } + return rc; +} + +/* +** If it has not already been allocated, allocate the UnpackedRecord +** structure at pTask->pUnpacked. Return SQLITE_OK if successful (or +** if no allocation was required), or SQLITE_NOMEM otherwise. +*/ +static int vdbeSortAllocUnpacked(SortSubtask *pTask){ + if( pTask->pUnpacked==0 ){ + pTask->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pTask->pSorter->pKeyInfo); + if( pTask->pUnpacked==0 ) return SQLITE_NOMEM_BKPT; + pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nKeyField; + pTask->pUnpacked->errCode = 0; + } + return SQLITE_OK; +} + + +/* +** Merge the two sorted lists p1 and p2 into a single list. +*/ +static SorterRecord *vdbeSorterMerge( + SortSubtask *pTask, /* Calling thread context */ + SorterRecord *p1, /* First list to merge */ + SorterRecord *p2 /* Second list to merge */ +){ + SorterRecord *pFinal = 0; + SorterRecord **pp = &pFinal; + int bCached = 0; + + assert( p1!=0 && p2!=0 ); + for(;;){ + int res; + res = pTask->xCompare( + pTask, &bCached, SRVAL(p1), p1->nVal, SRVAL(p2), p2->nVal + ); + + if( res<=0 ){ + *pp = p1; + pp = &p1->u.pNext; + p1 = p1->u.pNext; + if( p1==0 ){ + *pp = p2; + break; + } + }else{ + *pp = p2; + pp = &p2->u.pNext; + p2 = p2->u.pNext; + bCached = 0; + if( p2==0 ){ + *pp = p1; + break; + } + } + } + return pFinal; +} + +/* +** Return the SorterCompare function to compare values collected by the +** sorter object passed as the only argument. +*/ +static SorterCompare vdbeSorterGetCompare(VdbeSorter *p){ + if( p->typeMask==SORTER_TYPE_INTEGER ){ + return vdbeSorterCompareInt; + }else if( p->typeMask==SORTER_TYPE_TEXT ){ + return vdbeSorterCompareText; + } + return vdbeSorterCompare; +} + +/* +** Sort the linked list of records headed at pTask->pList. Return +** SQLITE_OK if successful, or an SQLite error code (i.e. SQLITE_NOMEM) if +** an error occurs. +*/ +static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){ + int i; + SorterRecord *p; + int rc; + SorterRecord *aSlot[64]; + + rc = vdbeSortAllocUnpacked(pTask); + if( rc!=SQLITE_OK ) return rc; + + p = pList->pList; + pTask->xCompare = vdbeSorterGetCompare(pTask->pSorter); + memset(aSlot, 0, sizeof(aSlot)); + + while( p ){ + SorterRecord *pNext; + if( pList->aMemory ){ + if( (u8*)p==pList->aMemory ){ + pNext = 0; + }else{ + assert( p->u.iNext<sqlite3MallocSize(pList->aMemory) ); + pNext = (SorterRecord*)&pList->aMemory[p->u.iNext]; + } + }else{ + pNext = p->u.pNext; + } + + p->u.pNext = 0; + for(i=0; aSlot[i]; i++){ + p = vdbeSorterMerge(pTask, p, aSlot[i]); + aSlot[i] = 0; + } + aSlot[i] = p; + p = pNext; + } + + p = 0; + for(i=0; i<ArraySize(aSlot); i++){ + if( aSlot[i]==0 ) continue; + p = p ? vdbeSorterMerge(pTask, p, aSlot[i]) : aSlot[i]; + } + pList->pList = p; + + assert( pTask->pUnpacked->errCode==SQLITE_OK + || pTask->pUnpacked->errCode==SQLITE_NOMEM + ); + return pTask->pUnpacked->errCode; +} + +/* +** Initialize a PMA-writer object. +*/ +static void vdbePmaWriterInit( + sqlite3_file *pFd, /* File handle to write to */ + PmaWriter *p, /* Object to populate */ + int nBuf, /* Buffer size */ + i64 iStart /* Offset of pFd to begin writing at */ +){ + memset(p, 0, sizeof(PmaWriter)); + p->aBuffer = (u8*)sqlite3Malloc(nBuf); + if( !p->aBuffer ){ + p->eFWErr = SQLITE_NOMEM_BKPT; + }else{ + p->iBufEnd = p->iBufStart = (iStart % nBuf); + p->iWriteOff = iStart - p->iBufStart; + p->nBuffer = nBuf; + p->pFd = pFd; + } +} + +/* +** Write nData bytes of data to the PMA. Return SQLITE_OK +** if successful, or an SQLite error code if an error occurs. +*/ +static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){ + int nRem = nData; + while( nRem>0 && p->eFWErr==0 ){ + int nCopy = nRem; + if( nCopy>(p->nBuffer - p->iBufEnd) ){ + nCopy = p->nBuffer - p->iBufEnd; + } + + memcpy(&p->aBuffer[p->iBufEnd], &pData[nData-nRem], nCopy); + p->iBufEnd += nCopy; + if( p->iBufEnd==p->nBuffer ){ + p->eFWErr = sqlite3OsWrite(p->pFd, + &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, + p->iWriteOff + p->iBufStart + ); + p->iBufStart = p->iBufEnd = 0; + p->iWriteOff += p->nBuffer; + } + assert( p->iBufEnd<p->nBuffer ); + + nRem -= nCopy; + } +} + +/* +** Flush any buffered data to disk and clean up the PMA-writer object. +** The results of using the PMA-writer after this call are undefined. +** Return SQLITE_OK if flushing the buffered data succeeds or is not +** required. Otherwise, return an SQLite error code. +** +** Before returning, set *piEof to the offset immediately following the +** last byte written to the file. +*/ +static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){ + int rc; + if( p->eFWErr==0 && ALWAYS(p->aBuffer) && p->iBufEnd>p->iBufStart ){ + p->eFWErr = sqlite3OsWrite(p->pFd, + &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, + p->iWriteOff + p->iBufStart + ); + } + *piEof = (p->iWriteOff + p->iBufEnd); + sqlite3_free(p->aBuffer); + rc = p->eFWErr; + memset(p, 0, sizeof(PmaWriter)); + return rc; +} + +/* +** Write value iVal encoded as a varint to the PMA. Return +** SQLITE_OK if successful, or an SQLite error code if an error occurs. +*/ +static void vdbePmaWriteVarint(PmaWriter *p, u64 iVal){ + int nByte; + u8 aByte[10]; + nByte = sqlite3PutVarint(aByte, iVal); + vdbePmaWriteBlob(p, aByte, nByte); +} + +/* +** Write the current contents of in-memory linked-list pList to a level-0 +** PMA in the temp file belonging to sub-task pTask. Return SQLITE_OK if +** successful, or an SQLite error code otherwise. +** +** The format of a PMA is: +** +** * A varint. This varint contains the total number of bytes of content +** in the PMA (not including the varint itself). +** +** * One or more records packed end-to-end in order of ascending keys. +** Each record consists of a varint followed by a blob of data (the +** key). The varint is the number of bytes in the blob of data. +*/ +static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){ + sqlite3 *db = pTask->pSorter->db; + int rc = SQLITE_OK; /* Return code */ + PmaWriter writer; /* Object used to write to the file */ + +#ifdef SQLITE_DEBUG + /* Set iSz to the expected size of file pTask->file after writing the PMA. + ** This is used by an assert() statement at the end of this function. */ + i64 iSz = pList->szPMA + sqlite3VarintLen(pList->szPMA) + pTask->file.iEof; +#endif + + vdbeSorterWorkDebug(pTask, "enter"); + memset(&writer, 0, sizeof(PmaWriter)); + assert( pList->szPMA>0 ); + + /* If the first temporary PMA file has not been opened, open it now. */ + if( pTask->file.pFd==0 ){ + rc = vdbeSorterOpenTempFile(db, 0, &pTask->file.pFd); + assert( rc!=SQLITE_OK || pTask->file.pFd ); + assert( pTask->file.iEof==0 ); + assert( pTask->nPMA==0 ); + } + + /* Try to get the file to memory map */ + if( rc==SQLITE_OK ){ + vdbeSorterExtendFile(db, pTask->file.pFd, pTask->file.iEof+pList->szPMA+9); + } + + /* Sort the list */ + if( rc==SQLITE_OK ){ + rc = vdbeSorterSort(pTask, pList); + } + + if( rc==SQLITE_OK ){ + SorterRecord *p; + SorterRecord *pNext = 0; + + vdbePmaWriterInit(pTask->file.pFd, &writer, pTask->pSorter->pgsz, + pTask->file.iEof); + pTask->nPMA++; + vdbePmaWriteVarint(&writer, pList->szPMA); + for(p=pList->pList; p; p=pNext){ + pNext = p->u.pNext; + vdbePmaWriteVarint(&writer, p->nVal); + vdbePmaWriteBlob(&writer, SRVAL(p), p->nVal); + if( pList->aMemory==0 ) sqlite3_free(p); + } + pList->pList = p; + rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof); + } + + vdbeSorterWorkDebug(pTask, "exit"); + assert( rc!=SQLITE_OK || pList->pList==0 ); + assert( rc!=SQLITE_OK || pTask->file.iEof==iSz ); + return rc; +} + +/* +** Advance the MergeEngine to its next entry. +** Set *pbEof to true there is no next entry because +** the MergeEngine has reached the end of all its inputs. +** +** Return SQLITE_OK if successful or an error code if an error occurs. +*/ +static int vdbeMergeEngineStep( + MergeEngine *pMerger, /* The merge engine to advance to the next row */ + int *pbEof /* Set TRUE at EOF. Set false for more content */ +){ + int rc; + int iPrev = pMerger->aTree[1];/* Index of PmaReader to advance */ + SortSubtask *pTask = pMerger->pTask; + + /* Advance the current PmaReader */ + rc = vdbePmaReaderNext(&pMerger->aReadr[iPrev]); + + /* Update contents of aTree[] */ + if( rc==SQLITE_OK ){ + int i; /* Index of aTree[] to recalculate */ + PmaReader *pReadr1; /* First PmaReader to compare */ + PmaReader *pReadr2; /* Second PmaReader to compare */ + int bCached = 0; + + /* Find the first two PmaReaders to compare. The one that was just + ** advanced (iPrev) and the one next to it in the array. */ + pReadr1 = &pMerger->aReadr[(iPrev & 0xFFFE)]; + pReadr2 = &pMerger->aReadr[(iPrev | 0x0001)]; + + for(i=(pMerger->nTree+iPrev)/2; i>0; i=i/2){ + /* Compare pReadr1 and pReadr2. Store the result in variable iRes. */ + int iRes; + if( pReadr1->pFd==0 ){ + iRes = +1; + }else if( pReadr2->pFd==0 ){ + iRes = -1; + }else{ + iRes = pTask->xCompare(pTask, &bCached, + pReadr1->aKey, pReadr1->nKey, pReadr2->aKey, pReadr2->nKey + ); + } + + /* If pReadr1 contained the smaller value, set aTree[i] to its index. + ** Then set pReadr2 to the next PmaReader to compare to pReadr1. In this + ** case there is no cache of pReadr2 in pTask->pUnpacked, so set + ** pKey2 to point to the record belonging to pReadr2. + ** + ** Alternatively, if pReadr2 contains the smaller of the two values, + ** set aTree[i] to its index and update pReadr1. If vdbeSorterCompare() + ** was actually called above, then pTask->pUnpacked now contains + ** a value equivalent to pReadr2. So set pKey2 to NULL to prevent + ** vdbeSorterCompare() from decoding pReadr2 again. + ** + ** If the two values were equal, then the value from the oldest + ** PMA should be considered smaller. The VdbeSorter.aReadr[] array + ** is sorted from oldest to newest, so pReadr1 contains older values + ** than pReadr2 iff (pReadr1<pReadr2). */ + if( iRes<0 || (iRes==0 && pReadr1<pReadr2) ){ + pMerger->aTree[i] = (int)(pReadr1 - pMerger->aReadr); + pReadr2 = &pMerger->aReadr[ pMerger->aTree[i ^ 0x0001] ]; + bCached = 0; + }else{ + if( pReadr1->pFd ) bCached = 0; + pMerger->aTree[i] = (int)(pReadr2 - pMerger->aReadr); + pReadr1 = &pMerger->aReadr[ pMerger->aTree[i ^ 0x0001] ]; + } + } + *pbEof = (pMerger->aReadr[pMerger->aTree[1]].pFd==0); + } + + return (rc==SQLITE_OK ? pTask->pUnpacked->errCode : rc); +} + +#if SQLITE_MAX_WORKER_THREADS>0 +/* +** The main routine for background threads that write level-0 PMAs. +*/ +static void *vdbeSorterFlushThread(void *pCtx){ + SortSubtask *pTask = (SortSubtask*)pCtx; + int rc; /* Return code */ + assert( pTask->bDone==0 ); + rc = vdbeSorterListToPMA(pTask, &pTask->list); + pTask->bDone = 1; + return SQLITE_INT_TO_PTR(rc); +} +#endif /* SQLITE_MAX_WORKER_THREADS>0 */ + +/* +** Flush the current contents of VdbeSorter.list to a new PMA, possibly +** using a background thread. +*/ +static int vdbeSorterFlushPMA(VdbeSorter *pSorter){ +#if SQLITE_MAX_WORKER_THREADS==0 + pSorter->bUsePMA = 1; + return vdbeSorterListToPMA(&pSorter->aTask[0], &pSorter->list); +#else + int rc = SQLITE_OK; + int i; + SortSubtask *pTask = 0; /* Thread context used to create new PMA */ + int nWorker = (pSorter->nTask-1); + + /* Set the flag to indicate that at least one PMA has been written. + ** Or will be, anyhow. */ + pSorter->bUsePMA = 1; + + /* Select a sub-task to sort and flush the current list of in-memory + ** records to disk. If the sorter is running in multi-threaded mode, + ** round-robin between the first (pSorter->nTask-1) tasks. Except, if + ** the background thread from a sub-tasks previous turn is still running, + ** skip it. If the first (pSorter->nTask-1) sub-tasks are all still busy, + ** fall back to using the final sub-task. The first (pSorter->nTask-1) + ** sub-tasks are preferred as they use background threads - the final + ** sub-task uses the main thread. */ + for(i=0; i<nWorker; i++){ + int iTest = (pSorter->iPrev + i + 1) % nWorker; + pTask = &pSorter->aTask[iTest]; + if( pTask->bDone ){ + rc = vdbeSorterJoinThread(pTask); + } + if( rc!=SQLITE_OK || pTask->pThread==0 ) break; + } + + if( rc==SQLITE_OK ){ + if( i==nWorker ){ + /* Use the foreground thread for this operation */ + rc = vdbeSorterListToPMA(&pSorter->aTask[nWorker], &pSorter->list); + }else{ + /* Launch a background thread for this operation */ + u8 *aMem; + void *pCtx; + + assert( pTask!=0 ); + assert( pTask->pThread==0 && pTask->bDone==0 ); + assert( pTask->list.pList==0 ); + assert( pTask->list.aMemory==0 || pSorter->list.aMemory!=0 ); + + aMem = pTask->list.aMemory; + pCtx = (void*)pTask; + pSorter->iPrev = (u8)(pTask - pSorter->aTask); + pTask->list = pSorter->list; + pSorter->list.pList = 0; + pSorter->list.szPMA = 0; + if( aMem ){ + pSorter->list.aMemory = aMem; + pSorter->nMemory = sqlite3MallocSize(aMem); + }else if( pSorter->list.aMemory ){ + pSorter->list.aMemory = sqlite3Malloc(pSorter->nMemory); + if( !pSorter->list.aMemory ) return SQLITE_NOMEM_BKPT; + } + + rc = vdbeSorterCreateThread(pTask, vdbeSorterFlushThread, pCtx); + } + } + + return rc; +#endif /* SQLITE_MAX_WORKER_THREADS!=0 */ +} + +/* +** Add a record to the sorter. +*/ +SQLITE_PRIVATE int sqlite3VdbeSorterWrite( + const VdbeCursor *pCsr, /* Sorter cursor */ + Mem *pVal /* Memory cell containing record */ +){ + VdbeSorter *pSorter; + int rc = SQLITE_OK; /* Return Code */ + SorterRecord *pNew; /* New list element */ + int bFlush; /* True to flush contents of memory to PMA */ + int nReq; /* Bytes of memory required */ + int nPMA; /* Bytes of PMA space required */ + int t; /* serial type of first record field */ + + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; + getVarint32NR((const u8*)&pVal->z[1], t); + if( t>0 && t<10 && t!=7 ){ + pSorter->typeMask &= SORTER_TYPE_INTEGER; + }else if( t>10 && (t & 0x01) ){ + pSorter->typeMask &= SORTER_TYPE_TEXT; + }else{ + pSorter->typeMask = 0; + } + + assert( pSorter ); + + /* Figure out whether or not the current contents of memory should be + ** flushed to a PMA before continuing. If so, do so. + ** + ** If using the single large allocation mode (pSorter->aMemory!=0), then + ** flush the contents of memory to a new PMA if (a) at least one value is + ** already in memory and (b) the new value will not fit in memory. + ** + ** Or, if using separate allocations for each record, flush the contents + ** of memory to a PMA if either of the following are true: + ** + ** * The total memory allocated for the in-memory list is greater + ** than (page-size * cache-size), or + ** + ** * The total memory allocated for the in-memory list is greater + ** than (page-size * 10) and sqlite3HeapNearlyFull() returns true. + */ + nReq = pVal->n + sizeof(SorterRecord); + nPMA = pVal->n + sqlite3VarintLen(pVal->n); + if( pSorter->mxPmaSize ){ + if( pSorter->list.aMemory ){ + bFlush = pSorter->iMemory && (pSorter->iMemory+nReq) > pSorter->mxPmaSize; + }else{ + bFlush = ( + (pSorter->list.szPMA > pSorter->mxPmaSize) + || (pSorter->list.szPMA > pSorter->mnPmaSize && sqlite3HeapNearlyFull()) + ); + } + if( bFlush ){ + rc = vdbeSorterFlushPMA(pSorter); + pSorter->list.szPMA = 0; + pSorter->iMemory = 0; + assert( rc!=SQLITE_OK || pSorter->list.pList==0 ); + } + } + + pSorter->list.szPMA += nPMA; + if( nPMA>pSorter->mxKeysize ){ + pSorter->mxKeysize = nPMA; + } + + if( pSorter->list.aMemory ){ + int nMin = pSorter->iMemory + nReq; + + if( nMin>pSorter->nMemory ){ + u8 *aNew; + sqlite3_int64 nNew = 2 * (sqlite3_int64)pSorter->nMemory; + int iListOff = -1; + if( pSorter->list.pList ){ + iListOff = (u8*)pSorter->list.pList - pSorter->list.aMemory; + } + while( nNew < nMin ) nNew = nNew*2; + if( nNew > pSorter->mxPmaSize ) nNew = pSorter->mxPmaSize; + if( nNew < nMin ) nNew = nMin; + aNew = sqlite3Realloc(pSorter->list.aMemory, nNew); + if( !aNew ) return SQLITE_NOMEM_BKPT; + if( iListOff>=0 ){ + pSorter->list.pList = (SorterRecord*)&aNew[iListOff]; + } + pSorter->list.aMemory = aNew; + pSorter->nMemory = nNew; + } + + pNew = (SorterRecord*)&pSorter->list.aMemory[pSorter->iMemory]; + pSorter->iMemory += ROUND8(nReq); + if( pSorter->list.pList ){ + pNew->u.iNext = (int)((u8*)(pSorter->list.pList) - pSorter->list.aMemory); + } + }else{ + pNew = (SorterRecord *)sqlite3Malloc(nReq); + if( pNew==0 ){ + return SQLITE_NOMEM_BKPT; + } + pNew->u.pNext = pSorter->list.pList; + } + + memcpy(SRVAL(pNew), pVal->z, pVal->n); + pNew->nVal = pVal->n; + pSorter->list.pList = pNew; + + return rc; +} + +/* +** Read keys from pIncr->pMerger and populate pIncr->aFile[1]. The format +** of the data stored in aFile[1] is the same as that used by regular PMAs, +** except that the number-of-bytes varint is omitted from the start. +*/ +static int vdbeIncrPopulate(IncrMerger *pIncr){ + int rc = SQLITE_OK; + int rc2; + i64 iStart = pIncr->iStartOff; + SorterFile *pOut = &pIncr->aFile[1]; + SortSubtask *pTask = pIncr->pTask; + MergeEngine *pMerger = pIncr->pMerger; + PmaWriter writer; + assert( pIncr->bEof==0 ); + + vdbeSorterPopulateDebug(pTask, "enter"); + + vdbePmaWriterInit(pOut->pFd, &writer, pTask->pSorter->pgsz, iStart); + while( rc==SQLITE_OK ){ + int dummy; + PmaReader *pReader = &pMerger->aReadr[ pMerger->aTree[1] ]; + int nKey = pReader->nKey; + i64 iEof = writer.iWriteOff + writer.iBufEnd; + + /* Check if the output file is full or if the input has been exhausted. + ** In either case exit the loop. */ + if( pReader->pFd==0 ) break; + if( (iEof + nKey + sqlite3VarintLen(nKey))>(iStart + pIncr->mxSz) ) break; + + /* Write the next key to the output. */ + vdbePmaWriteVarint(&writer, nKey); + vdbePmaWriteBlob(&writer, pReader->aKey, nKey); + assert( pIncr->pMerger->pTask==pTask ); + rc = vdbeMergeEngineStep(pIncr->pMerger, &dummy); + } + + rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof); + if( rc==SQLITE_OK ) rc = rc2; + vdbeSorterPopulateDebug(pTask, "exit"); + return rc; +} + +#if SQLITE_MAX_WORKER_THREADS>0 +/* +** The main routine for background threads that populate aFile[1] of +** multi-threaded IncrMerger objects. +*/ +static void *vdbeIncrPopulateThread(void *pCtx){ + IncrMerger *pIncr = (IncrMerger*)pCtx; + void *pRet = SQLITE_INT_TO_PTR( vdbeIncrPopulate(pIncr) ); + pIncr->pTask->bDone = 1; + return pRet; +} + +/* +** Launch a background thread to populate aFile[1] of pIncr. +*/ +static int vdbeIncrBgPopulate(IncrMerger *pIncr){ + void *p = (void*)pIncr; + assert( pIncr->bUseThread ); + return vdbeSorterCreateThread(pIncr->pTask, vdbeIncrPopulateThread, p); +} +#endif + +/* +** This function is called when the PmaReader corresponding to pIncr has +** finished reading the contents of aFile[0]. Its purpose is to "refill" +** aFile[0] such that the PmaReader should start rereading it from the +** beginning. +** +** For single-threaded objects, this is accomplished by literally reading +** keys from pIncr->pMerger and repopulating aFile[0]. +** +** For multi-threaded objects, all that is required is to wait until the +** background thread is finished (if it is not already) and then swap +** aFile[0] and aFile[1] in place. If the contents of pMerger have not +** been exhausted, this function also launches a new background thread +** to populate the new aFile[1]. +** +** SQLITE_OK is returned on success, or an SQLite error code otherwise. +*/ +static int vdbeIncrSwap(IncrMerger *pIncr){ + int rc = SQLITE_OK; + +#if SQLITE_MAX_WORKER_THREADS>0 + if( pIncr->bUseThread ){ + rc = vdbeSorterJoinThread(pIncr->pTask); + + if( rc==SQLITE_OK ){ + SorterFile f0 = pIncr->aFile[0]; + pIncr->aFile[0] = pIncr->aFile[1]; + pIncr->aFile[1] = f0; + } + + if( rc==SQLITE_OK ){ + if( pIncr->aFile[0].iEof==pIncr->iStartOff ){ + pIncr->bEof = 1; + }else{ + rc = vdbeIncrBgPopulate(pIncr); + } + } + }else +#endif + { + rc = vdbeIncrPopulate(pIncr); + pIncr->aFile[0] = pIncr->aFile[1]; + if( pIncr->aFile[0].iEof==pIncr->iStartOff ){ + pIncr->bEof = 1; + } + } + + return rc; +} + +/* +** Allocate and return a new IncrMerger object to read data from pMerger. +** +** If an OOM condition is encountered, return NULL. In this case free the +** pMerger argument before returning. +*/ +static int vdbeIncrMergerNew( + SortSubtask *pTask, /* The thread that will be using the new IncrMerger */ + MergeEngine *pMerger, /* The MergeEngine that the IncrMerger will control */ + IncrMerger **ppOut /* Write the new IncrMerger here */ +){ + int rc = SQLITE_OK; + IncrMerger *pIncr = *ppOut = (IncrMerger*) + (sqlite3FaultSim(100) ? 0 : sqlite3MallocZero(sizeof(*pIncr))); + if( pIncr ){ + pIncr->pMerger = pMerger; + pIncr->pTask = pTask; + pIncr->mxSz = MAX(pTask->pSorter->mxKeysize+9,pTask->pSorter->mxPmaSize/2); + pTask->file2.iEof += pIncr->mxSz; + }else{ + vdbeMergeEngineFree(pMerger); + rc = SQLITE_NOMEM_BKPT; + } + assert( *ppOut!=0 || rc!=SQLITE_OK ); + return rc; +} + +#if SQLITE_MAX_WORKER_THREADS>0 +/* +** Set the "use-threads" flag on object pIncr. +*/ +static void vdbeIncrMergerSetThreads(IncrMerger *pIncr){ + pIncr->bUseThread = 1; + pIncr->pTask->file2.iEof -= pIncr->mxSz; +} +#endif /* SQLITE_MAX_WORKER_THREADS>0 */ + + + +/* +** Recompute pMerger->aTree[iOut] by comparing the next keys on the +** two PmaReaders that feed that entry. Neither of the PmaReaders +** are advanced. This routine merely does the comparison. +*/ +static void vdbeMergeEngineCompare( + MergeEngine *pMerger, /* Merge engine containing PmaReaders to compare */ + int iOut /* Store the result in pMerger->aTree[iOut] */ +){ + int i1; + int i2; + int iRes; + PmaReader *p1; + PmaReader *p2; + + assert( iOut<pMerger->nTree && iOut>0 ); + + if( iOut>=(pMerger->nTree/2) ){ + i1 = (iOut - pMerger->nTree/2) * 2; + i2 = i1 + 1; + }else{ + i1 = pMerger->aTree[iOut*2]; + i2 = pMerger->aTree[iOut*2+1]; + } + + p1 = &pMerger->aReadr[i1]; + p2 = &pMerger->aReadr[i2]; + + if( p1->pFd==0 ){ + iRes = i2; + }else if( p2->pFd==0 ){ + iRes = i1; + }else{ + SortSubtask *pTask = pMerger->pTask; + int bCached = 0; + int res; + assert( pTask->pUnpacked!=0 ); /* from vdbeSortSubtaskMain() */ + res = pTask->xCompare( + pTask, &bCached, p1->aKey, p1->nKey, p2->aKey, p2->nKey + ); + if( res<=0 ){ + iRes = i1; + }else{ + iRes = i2; + } + } + + pMerger->aTree[iOut] = iRes; +} + +/* +** Allowed values for the eMode parameter to vdbeMergeEngineInit() +** and vdbePmaReaderIncrMergeInit(). +** +** Only INCRINIT_NORMAL is valid in single-threaded builds (when +** SQLITE_MAX_WORKER_THREADS==0). The other values are only used +** when there exists one or more separate worker threads. +*/ +#define INCRINIT_NORMAL 0 +#define INCRINIT_TASK 1 +#define INCRINIT_ROOT 2 + +/* +** Forward reference required as the vdbeIncrMergeInit() and +** vdbePmaReaderIncrInit() routines are called mutually recursively when +** building a merge tree. +*/ +static int vdbePmaReaderIncrInit(PmaReader *pReadr, int eMode); + +/* +** Initialize the MergeEngine object passed as the second argument. Once this +** function returns, the first key of merged data may be read from the +** MergeEngine object in the usual fashion. +** +** If argument eMode is INCRINIT_ROOT, then it is assumed that any IncrMerge +** objects attached to the PmaReader objects that the merger reads from have +** already been populated, but that they have not yet populated aFile[0] and +** set the PmaReader objects up to read from it. In this case all that is +** required is to call vdbePmaReaderNext() on each PmaReader to point it at +** its first key. +** +** Otherwise, if eMode is any value other than INCRINIT_ROOT, then use +** vdbePmaReaderIncrMergeInit() to initialize each PmaReader that feeds data +** to pMerger. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +static int vdbeMergeEngineInit( + SortSubtask *pTask, /* Thread that will run pMerger */ + MergeEngine *pMerger, /* MergeEngine to initialize */ + int eMode /* One of the INCRINIT_XXX constants */ +){ + int rc = SQLITE_OK; /* Return code */ + int i; /* For looping over PmaReader objects */ + int nTree; /* Number of subtrees to merge */ + + /* Failure to allocate the merge would have been detected prior to + ** invoking this routine */ + assert( pMerger!=0 ); + + /* eMode is always INCRINIT_NORMAL in single-threaded mode */ + assert( SQLITE_MAX_WORKER_THREADS>0 || eMode==INCRINIT_NORMAL ); + + /* Verify that the MergeEngine is assigned to a single thread */ + assert( pMerger->pTask==0 ); + pMerger->pTask = pTask; + + nTree = pMerger->nTree; + for(i=0; i<nTree; i++){ + if( SQLITE_MAX_WORKER_THREADS>0 && eMode==INCRINIT_ROOT ){ + /* PmaReaders should be normally initialized in order, as if they are + ** reading from the same temp file this makes for more linear file IO. + ** However, in the INCRINIT_ROOT case, if PmaReader aReadr[nTask-1] is + ** in use it will block the vdbePmaReaderNext() call while it uses + ** the main thread to fill its buffer. So calling PmaReaderNext() + ** on this PmaReader before any of the multi-threaded PmaReaders takes + ** better advantage of multi-processor hardware. */ + rc = vdbePmaReaderNext(&pMerger->aReadr[nTree-i-1]); + }else{ + rc = vdbePmaReaderIncrInit(&pMerger->aReadr[i], INCRINIT_NORMAL); + } + if( rc!=SQLITE_OK ) return rc; + } + + for(i=pMerger->nTree-1; i>0; i--){ + vdbeMergeEngineCompare(pMerger, i); + } + return pTask->pUnpacked->errCode; +} + +/* +** The PmaReader passed as the first argument is guaranteed to be an +** incremental-reader (pReadr->pIncr!=0). This function serves to open +** and/or initialize the temp file related fields of the IncrMerge +** object at (pReadr->pIncr). +** +** If argument eMode is set to INCRINIT_NORMAL, then all PmaReaders +** in the sub-tree headed by pReadr are also initialized. Data is then +** loaded into the buffers belonging to pReadr and it is set to point to +** the first key in its range. +** +** If argument eMode is set to INCRINIT_TASK, then pReadr is guaranteed +** to be a multi-threaded PmaReader and this function is being called in a +** background thread. In this case all PmaReaders in the sub-tree are +** initialized as for INCRINIT_NORMAL and the aFile[1] buffer belonging to +** pReadr is populated. However, pReadr itself is not set up to point +** to its first key. A call to vdbePmaReaderNext() is still required to do +** that. +** +** The reason this function does not call vdbePmaReaderNext() immediately +** in the INCRINIT_TASK case is that vdbePmaReaderNext() assumes that it has +** to block on thread (pTask->thread) before accessing aFile[1]. But, since +** this entire function is being run by thread (pTask->thread), that will +** lead to the current background thread attempting to join itself. +** +** Finally, if argument eMode is set to INCRINIT_ROOT, it may be assumed +** that pReadr->pIncr is a multi-threaded IncrMerge objects, and that all +** child-trees have already been initialized using IncrInit(INCRINIT_TASK). +** In this case vdbePmaReaderNext() is called on all child PmaReaders and +** the current PmaReader set to point to the first key in its range. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){ + int rc = SQLITE_OK; + IncrMerger *pIncr = pReadr->pIncr; + SortSubtask *pTask = pIncr->pTask; + sqlite3 *db = pTask->pSorter->db; + + /* eMode is always INCRINIT_NORMAL in single-threaded mode */ + assert( SQLITE_MAX_WORKER_THREADS>0 || eMode==INCRINIT_NORMAL ); + + rc = vdbeMergeEngineInit(pTask, pIncr->pMerger, eMode); + + /* Set up the required files for pIncr. A multi-threaded IncrMerge object + ** requires two temp files to itself, whereas a single-threaded object + ** only requires a region of pTask->file2. */ + if( rc==SQLITE_OK ){ + int mxSz = pIncr->mxSz; +#if SQLITE_MAX_WORKER_THREADS>0 + if( pIncr->bUseThread ){ + rc = vdbeSorterOpenTempFile(db, mxSz, &pIncr->aFile[0].pFd); + if( rc==SQLITE_OK ){ + rc = vdbeSorterOpenTempFile(db, mxSz, &pIncr->aFile[1].pFd); + } + }else +#endif + /*if( !pIncr->bUseThread )*/{ + if( pTask->file2.pFd==0 ){ + assert( pTask->file2.iEof>0 ); + rc = vdbeSorterOpenTempFile(db, pTask->file2.iEof, &pTask->file2.pFd); + pTask->file2.iEof = 0; + } + if( rc==SQLITE_OK ){ + pIncr->aFile[1].pFd = pTask->file2.pFd; + pIncr->iStartOff = pTask->file2.iEof; + pTask->file2.iEof += mxSz; + } + } + } + +#if SQLITE_MAX_WORKER_THREADS>0 + if( rc==SQLITE_OK && pIncr->bUseThread ){ + /* Use the current thread to populate aFile[1], even though this + ** PmaReader is multi-threaded. If this is an INCRINIT_TASK object, + ** then this function is already running in background thread + ** pIncr->pTask->thread. + ** + ** If this is the INCRINIT_ROOT object, then it is running in the + ** main VDBE thread. But that is Ok, as that thread cannot return + ** control to the VDBE or proceed with anything useful until the + ** first results are ready from this merger object anyway. + */ + assert( eMode==INCRINIT_ROOT || eMode==INCRINIT_TASK ); + rc = vdbeIncrPopulate(pIncr); + } +#endif + + if( rc==SQLITE_OK && (SQLITE_MAX_WORKER_THREADS==0 || eMode!=INCRINIT_TASK) ){ + rc = vdbePmaReaderNext(pReadr); + } + + return rc; +} + +#if SQLITE_MAX_WORKER_THREADS>0 +/* +** The main routine for vdbePmaReaderIncrMergeInit() operations run in +** background threads. +*/ +static void *vdbePmaReaderBgIncrInit(void *pCtx){ + PmaReader *pReader = (PmaReader*)pCtx; + void *pRet = SQLITE_INT_TO_PTR( + vdbePmaReaderIncrMergeInit(pReader,INCRINIT_TASK) + ); + pReader->pIncr->pTask->bDone = 1; + return pRet; +} +#endif + +/* +** If the PmaReader passed as the first argument is not an incremental-reader +** (if pReadr->pIncr==0), then this function is a no-op. Otherwise, it invokes +** the vdbePmaReaderIncrMergeInit() function with the parameters passed to +** this routine to initialize the incremental merge. +** +** If the IncrMerger object is multi-threaded (IncrMerger.bUseThread==1), +** then a background thread is launched to call vdbePmaReaderIncrMergeInit(). +** Or, if the IncrMerger is single threaded, the same function is called +** using the current thread. +*/ +static int vdbePmaReaderIncrInit(PmaReader *pReadr, int eMode){ + IncrMerger *pIncr = pReadr->pIncr; /* Incremental merger */ + int rc = SQLITE_OK; /* Return code */ + if( pIncr ){ +#if SQLITE_MAX_WORKER_THREADS>0 + assert( pIncr->bUseThread==0 || eMode==INCRINIT_TASK ); + if( pIncr->bUseThread ){ + void *pCtx = (void*)pReadr; + rc = vdbeSorterCreateThread(pIncr->pTask, vdbePmaReaderBgIncrInit, pCtx); + }else +#endif + { + rc = vdbePmaReaderIncrMergeInit(pReadr, eMode); + } + } + return rc; +} + +/* +** Allocate a new MergeEngine object to merge the contents of nPMA level-0 +** PMAs from pTask->file. If no error occurs, set *ppOut to point to +** the new object and return SQLITE_OK. Or, if an error does occur, set *ppOut +** to NULL and return an SQLite error code. +** +** When this function is called, *piOffset is set to the offset of the +** first PMA to read from pTask->file. Assuming no error occurs, it is +** set to the offset immediately following the last byte of the last +** PMA before returning. If an error does occur, then the final value of +** *piOffset is undefined. +*/ +static int vdbeMergeEngineLevel0( + SortSubtask *pTask, /* Sorter task to read from */ + int nPMA, /* Number of PMAs to read */ + i64 *piOffset, /* IN/OUT: Readr offset in pTask->file */ + MergeEngine **ppOut /* OUT: New merge-engine */ +){ + MergeEngine *pNew; /* Merge engine to return */ + i64 iOff = *piOffset; + int i; + int rc = SQLITE_OK; + + *ppOut = pNew = vdbeMergeEngineNew(nPMA); + if( pNew==0 ) rc = SQLITE_NOMEM_BKPT; + + for(i=0; i<nPMA && rc==SQLITE_OK; i++){ + i64 nDummy = 0; + PmaReader *pReadr = &pNew->aReadr[i]; + rc = vdbePmaReaderInit(pTask, &pTask->file, iOff, pReadr, &nDummy); + iOff = pReadr->iEof; + } + + if( rc!=SQLITE_OK ){ + vdbeMergeEngineFree(pNew); + *ppOut = 0; + } + *piOffset = iOff; + return rc; +} + +/* +** Return the depth of a tree comprising nPMA PMAs, assuming a fanout of +** SORTER_MAX_MERGE_COUNT. The returned value does not include leaf nodes. +** +** i.e. +** +** nPMA<=16 -> TreeDepth() == 0 +** nPMA<=256 -> TreeDepth() == 1 +** nPMA<=65536 -> TreeDepth() == 2 +*/ +static int vdbeSorterTreeDepth(int nPMA){ + int nDepth = 0; + i64 nDiv = SORTER_MAX_MERGE_COUNT; + while( nDiv < (i64)nPMA ){ + nDiv = nDiv * SORTER_MAX_MERGE_COUNT; + nDepth++; + } + return nDepth; +} + +/* +** pRoot is the root of an incremental merge-tree with depth nDepth (according +** to vdbeSorterTreeDepth()). pLeaf is the iSeq'th leaf to be added to the +** tree, counting from zero. This function adds pLeaf to the tree. +** +** If successful, SQLITE_OK is returned. If an error occurs, an SQLite error +** code is returned and pLeaf is freed. +*/ +static int vdbeSorterAddToTree( + SortSubtask *pTask, /* Task context */ + int nDepth, /* Depth of tree according to TreeDepth() */ + int iSeq, /* Sequence number of leaf within tree */ + MergeEngine *pRoot, /* Root of tree */ + MergeEngine *pLeaf /* Leaf to add to tree */ +){ + int rc = SQLITE_OK; + int nDiv = 1; + int i; + MergeEngine *p = pRoot; + IncrMerger *pIncr; + + rc = vdbeIncrMergerNew(pTask, pLeaf, &pIncr); + + for(i=1; i<nDepth; i++){ + nDiv = nDiv * SORTER_MAX_MERGE_COUNT; + } + + for(i=1; i<nDepth && rc==SQLITE_OK; i++){ + int iIter = (iSeq / nDiv) % SORTER_MAX_MERGE_COUNT; + PmaReader *pReadr = &p->aReadr[iIter]; + + if( pReadr->pIncr==0 ){ + MergeEngine *pNew = vdbeMergeEngineNew(SORTER_MAX_MERGE_COUNT); + if( pNew==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + rc = vdbeIncrMergerNew(pTask, pNew, &pReadr->pIncr); + } + } + if( rc==SQLITE_OK ){ + p = pReadr->pIncr->pMerger; + nDiv = nDiv / SORTER_MAX_MERGE_COUNT; + } + } + + if( rc==SQLITE_OK ){ + p->aReadr[iSeq % SORTER_MAX_MERGE_COUNT].pIncr = pIncr; + }else{ + vdbeIncrFree(pIncr); + } + return rc; +} + +/* +** This function is called as part of a SorterRewind() operation on a sorter +** that has already written two or more level-0 PMAs to one or more temp +** files. It builds a tree of MergeEngine/IncrMerger/PmaReader objects that +** can be used to incrementally merge all PMAs on disk. +** +** If successful, SQLITE_OK is returned and *ppOut set to point to the +** MergeEngine object at the root of the tree before returning. Or, if an +** error occurs, an SQLite error code is returned and the final value +** of *ppOut is undefined. +*/ +static int vdbeSorterMergeTreeBuild( + VdbeSorter *pSorter, /* The VDBE cursor that implements the sort */ + MergeEngine **ppOut /* Write the MergeEngine here */ +){ + MergeEngine *pMain = 0; + int rc = SQLITE_OK; + int iTask; + +#if SQLITE_MAX_WORKER_THREADS>0 + /* If the sorter uses more than one task, then create the top-level + ** MergeEngine here. This MergeEngine will read data from exactly + ** one PmaReader per sub-task. */ + assert( pSorter->bUseThreads || pSorter->nTask==1 ); + if( pSorter->nTask>1 ){ + pMain = vdbeMergeEngineNew(pSorter->nTask); + if( pMain==0 ) rc = SQLITE_NOMEM_BKPT; + } +#endif + + for(iTask=0; rc==SQLITE_OK && iTask<pSorter->nTask; iTask++){ + SortSubtask *pTask = &pSorter->aTask[iTask]; + assert( pTask->nPMA>0 || SQLITE_MAX_WORKER_THREADS>0 ); + if( SQLITE_MAX_WORKER_THREADS==0 || pTask->nPMA ){ + MergeEngine *pRoot = 0; /* Root node of tree for this task */ + int nDepth = vdbeSorterTreeDepth(pTask->nPMA); + i64 iReadOff = 0; + + if( pTask->nPMA<=SORTER_MAX_MERGE_COUNT ){ + rc = vdbeMergeEngineLevel0(pTask, pTask->nPMA, &iReadOff, &pRoot); + }else{ + int i; + int iSeq = 0; + pRoot = vdbeMergeEngineNew(SORTER_MAX_MERGE_COUNT); + if( pRoot==0 ) rc = SQLITE_NOMEM_BKPT; + for(i=0; i<pTask->nPMA && rc==SQLITE_OK; i += SORTER_MAX_MERGE_COUNT){ + MergeEngine *pMerger = 0; /* New level-0 PMA merger */ + int nReader; /* Number of level-0 PMAs to merge */ + + nReader = MIN(pTask->nPMA - i, SORTER_MAX_MERGE_COUNT); + rc = vdbeMergeEngineLevel0(pTask, nReader, &iReadOff, &pMerger); + if( rc==SQLITE_OK ){ + rc = vdbeSorterAddToTree(pTask, nDepth, iSeq++, pRoot, pMerger); + } + } + } + + if( rc==SQLITE_OK ){ +#if SQLITE_MAX_WORKER_THREADS>0 + if( pMain!=0 ){ + rc = vdbeIncrMergerNew(pTask, pRoot, &pMain->aReadr[iTask].pIncr); + }else +#endif + { + assert( pMain==0 ); + pMain = pRoot; + } + }else{ + vdbeMergeEngineFree(pRoot); + } + } + } + + if( rc!=SQLITE_OK ){ + vdbeMergeEngineFree(pMain); + pMain = 0; + } + *ppOut = pMain; + return rc; +} + +/* +** This function is called as part of an sqlite3VdbeSorterRewind() operation +** on a sorter that has written two or more PMAs to temporary files. It sets +** up either VdbeSorter.pMerger (for single threaded sorters) or pReader +** (for multi-threaded sorters) so that it can be used to iterate through +** all records stored in the sorter. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +static int vdbeSorterSetupMerge(VdbeSorter *pSorter){ + int rc; /* Return code */ + SortSubtask *pTask0 = &pSorter->aTask[0]; + MergeEngine *pMain = 0; +#if SQLITE_MAX_WORKER_THREADS + sqlite3 *db = pTask0->pSorter->db; + int i; + SorterCompare xCompare = vdbeSorterGetCompare(pSorter); + for(i=0; i<pSorter->nTask; i++){ + pSorter->aTask[i].xCompare = xCompare; + } +#endif + + rc = vdbeSorterMergeTreeBuild(pSorter, &pMain); + if( rc==SQLITE_OK ){ +#if SQLITE_MAX_WORKER_THREADS + assert( pSorter->bUseThreads==0 || pSorter->nTask>1 ); + if( pSorter->bUseThreads ){ + int iTask; + PmaReader *pReadr = 0; + SortSubtask *pLast = &pSorter->aTask[pSorter->nTask-1]; + rc = vdbeSortAllocUnpacked(pLast); + if( rc==SQLITE_OK ){ + pReadr = (PmaReader*)sqlite3DbMallocZero(db, sizeof(PmaReader)); + pSorter->pReader = pReadr; + if( pReadr==0 ) rc = SQLITE_NOMEM_BKPT; + } + if( rc==SQLITE_OK ){ + rc = vdbeIncrMergerNew(pLast, pMain, &pReadr->pIncr); + if( rc==SQLITE_OK ){ + vdbeIncrMergerSetThreads(pReadr->pIncr); + for(iTask=0; iTask<(pSorter->nTask-1); iTask++){ + IncrMerger *pIncr; + if( (pIncr = pMain->aReadr[iTask].pIncr) ){ + vdbeIncrMergerSetThreads(pIncr); + assert( pIncr->pTask!=pLast ); + } + } + for(iTask=0; rc==SQLITE_OK && iTask<pSorter->nTask; iTask++){ + /* Check that: + ** + ** a) The incremental merge object is configured to use the + ** right task, and + ** b) If it is using task (nTask-1), it is configured to run + ** in single-threaded mode. This is important, as the + ** root merge (INCRINIT_ROOT) will be using the same task + ** object. + */ + PmaReader *p = &pMain->aReadr[iTask]; + assert( p->pIncr==0 || ( + (p->pIncr->pTask==&pSorter->aTask[iTask]) /* a */ + && (iTask!=pSorter->nTask-1 || p->pIncr->bUseThread==0) /* b */ + )); + rc = vdbePmaReaderIncrInit(p, INCRINIT_TASK); + } + } + pMain = 0; + } + if( rc==SQLITE_OK ){ + rc = vdbePmaReaderIncrMergeInit(pReadr, INCRINIT_ROOT); + } + }else +#endif + { + rc = vdbeMergeEngineInit(pTask0, pMain, INCRINIT_NORMAL); + pSorter->pMerger = pMain; + pMain = 0; + } + } + + if( rc!=SQLITE_OK ){ + vdbeMergeEngineFree(pMain); + } + return rc; +} + + +/* +** Once the sorter has been populated by calls to sqlite3VdbeSorterWrite, +** this function is called to prepare for iterating through the records +** in sorted order. +*/ +SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){ + VdbeSorter *pSorter; + int rc = SQLITE_OK; /* Return code */ + + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; + assert( pSorter ); + + /* If no data has been written to disk, then do not do so now. Instead, + ** sort the VdbeSorter.pRecord list. The vdbe layer will read data directly + ** from the in-memory list. */ + if( pSorter->bUsePMA==0 ){ + if( pSorter->list.pList ){ + *pbEof = 0; + rc = vdbeSorterSort(&pSorter->aTask[0], &pSorter->list); + }else{ + *pbEof = 1; + } + return rc; + } + + /* Write the current in-memory list to a PMA. When the VdbeSorterWrite() + ** function flushes the contents of memory to disk, it immediately always + ** creates a new list consisting of a single key immediately afterwards. + ** So the list is never empty at this point. */ + assert( pSorter->list.pList ); + rc = vdbeSorterFlushPMA(pSorter); + + /* Join all threads */ + rc = vdbeSorterJoinAll(pSorter, rc); + + vdbeSorterRewindDebug("rewind"); + + /* Assuming no errors have occurred, set up a merger structure to + ** incrementally read and merge all remaining PMAs. */ + assert( pSorter->pReader==0 ); + if( rc==SQLITE_OK ){ + rc = vdbeSorterSetupMerge(pSorter); + *pbEof = 0; + } + + vdbeSorterRewindDebug("rewinddone"); + return rc; +} + +/* +** Advance to the next element in the sorter. Return value: +** +** SQLITE_OK success +** SQLITE_DONE end of data +** otherwise some kind of error. +*/ +SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr){ + VdbeSorter *pSorter; + int rc; /* Return code */ + + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; + assert( pSorter->bUsePMA || (pSorter->pReader==0 && pSorter->pMerger==0) ); + if( pSorter->bUsePMA ){ + assert( pSorter->pReader==0 || pSorter->pMerger==0 ); + assert( pSorter->bUseThreads==0 || pSorter->pReader ); + assert( pSorter->bUseThreads==1 || pSorter->pMerger ); +#if SQLITE_MAX_WORKER_THREADS>0 + if( pSorter->bUseThreads ){ + rc = vdbePmaReaderNext(pSorter->pReader); + if( rc==SQLITE_OK && pSorter->pReader->pFd==0 ) rc = SQLITE_DONE; + }else +#endif + /*if( !pSorter->bUseThreads )*/ { + int res = 0; + assert( pSorter->pMerger!=0 ); + assert( pSorter->pMerger->pTask==(&pSorter->aTask[0]) ); + rc = vdbeMergeEngineStep(pSorter->pMerger, &res); + if( rc==SQLITE_OK && res ) rc = SQLITE_DONE; + } + }else{ + SorterRecord *pFree = pSorter->list.pList; + pSorter->list.pList = pFree->u.pNext; + pFree->u.pNext = 0; + if( pSorter->list.aMemory==0 ) vdbeSorterRecordFree(db, pFree); + rc = pSorter->list.pList ? SQLITE_OK : SQLITE_DONE; + } + return rc; +} + +/* +** Return a pointer to a buffer owned by the sorter that contains the +** current key. +*/ +static void *vdbeSorterRowkey( + const VdbeSorter *pSorter, /* Sorter object */ + int *pnKey /* OUT: Size of current key in bytes */ +){ + void *pKey; + if( pSorter->bUsePMA ){ + PmaReader *pReader; +#if SQLITE_MAX_WORKER_THREADS>0 + if( pSorter->bUseThreads ){ + pReader = pSorter->pReader; + }else +#endif + /*if( !pSorter->bUseThreads )*/{ + pReader = &pSorter->pMerger->aReadr[pSorter->pMerger->aTree[1]]; + } + *pnKey = pReader->nKey; + pKey = pReader->aKey; + }else{ + *pnKey = pSorter->list.pList->nVal; + pKey = SRVAL(pSorter->list.pList); + } + return pKey; +} + +/* +** Copy the current sorter key into the memory cell pOut. +*/ +SQLITE_PRIVATE int sqlite3VdbeSorterRowkey(const VdbeCursor *pCsr, Mem *pOut){ + VdbeSorter *pSorter; + void *pKey; int nKey; /* Sorter key to copy into pOut */ + + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; + pKey = vdbeSorterRowkey(pSorter, &nKey); + if( sqlite3VdbeMemClearAndResize(pOut, nKey) ){ + return SQLITE_NOMEM_BKPT; + } + pOut->n = nKey; + MemSetTypeFlag(pOut, MEM_Blob); + memcpy(pOut->z, pKey, nKey); + + return SQLITE_OK; +} + +/* +** Compare the key in memory cell pVal with the key that the sorter cursor +** passed as the first argument currently points to. For the purposes of +** the comparison, ignore the rowid field at the end of each record. +** +** If the sorter cursor key contains any NULL values, consider it to be +** less than pVal. Even if pVal also contains NULL values. +** +** If an error occurs, return an SQLite error code (i.e. SQLITE_NOMEM). +** Otherwise, set *pRes to a negative, zero or positive value if the +** key in pVal is smaller than, equal to or larger than the current sorter +** key. +** +** This routine forms the core of the OP_SorterCompare opcode, which in +** turn is used to verify uniqueness when constructing a UNIQUE INDEX. +*/ +SQLITE_PRIVATE int sqlite3VdbeSorterCompare( + const VdbeCursor *pCsr, /* Sorter cursor */ + Mem *pVal, /* Value to compare to current sorter key */ + int nKeyCol, /* Compare this many columns */ + int *pRes /* OUT: Result of comparison */ +){ + VdbeSorter *pSorter; + UnpackedRecord *r2; + KeyInfo *pKeyInfo; + int i; + void *pKey; int nKey; /* Sorter key to compare pVal with */ + + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; + r2 = pSorter->pUnpacked; + pKeyInfo = pCsr->pKeyInfo; + if( r2==0 ){ + r2 = pSorter->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); + if( r2==0 ) return SQLITE_NOMEM_BKPT; + r2->nField = nKeyCol; + } + assert( r2->nField==nKeyCol ); + + pKey = vdbeSorterRowkey(pSorter, &nKey); + sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, r2); + for(i=0; i<nKeyCol; i++){ + if( r2->aMem[i].flags & MEM_Null ){ + *pRes = -1; + return SQLITE_OK; + } + } + + *pRes = sqlite3VdbeRecordCompare(pVal->n, pVal->z, r2); + return SQLITE_OK; +} + +/************** End of vdbesort.c ********************************************/ +/************** Begin file vdbevtab.c ****************************************/ +/* +** 2020-03-23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements virtual-tables for examining the bytecode content +** of a prepared statement. +*/ +/* #include "sqliteInt.h" */ +#if defined(SQLITE_ENABLE_BYTECODE_VTAB) && !defined(SQLITE_OMIT_VIRTUALTABLE) +/* #include "vdbeInt.h" */ + +/* An instance of the bytecode() table-valued function. +*/ +typedef struct bytecodevtab bytecodevtab; +struct bytecodevtab { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection */ + int bTablesUsed; /* 2 for tables_used(). 0 for bytecode(). */ +}; + +/* A cursor for scanning through the bytecode +*/ +typedef struct bytecodevtab_cursor bytecodevtab_cursor; +struct bytecodevtab_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3_stmt *pStmt; /* The statement whose bytecode is displayed */ + int iRowid; /* The rowid of the output table */ + int iAddr; /* Address */ + int needFinalize; /* Cursors owns pStmt and must finalize it */ + int showSubprograms; /* Provide a listing of subprograms */ + Op *aOp; /* Operand array */ + char *zP4; /* Rendered P4 value */ + const char *zType; /* tables_used.type */ + const char *zSchema; /* tables_used.schema */ + const char *zName; /* tables_used.name */ + Mem sub; /* Subprograms */ +}; + +/* +** Create a new bytecode() table-valued function. +*/ +static int bytecodevtabConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + bytecodevtab *pNew; + int rc; + int isTabUsed = pAux!=0; + const char *azSchema[2] = { + /* bytecode() schema */ + "CREATE TABLE x(" + "addr INT," + "opcode TEXT," + "p1 INT," + "p2 INT," + "p3 INT," + "p4 TEXT," + "p5 INT," + "comment TEXT," + "subprog TEXT," + "nexec INT," + "ncycle INT," + "stmt HIDDEN" + ");", + + /* Tables_used() schema */ + "CREATE TABLE x(" + "type TEXT," + "schema TEXT," + "name TEXT," + "wr INT," + "subprog TEXT," + "stmt HIDDEN" + ");" + }; + + (void)argc; + (void)argv; + (void)pzErr; + rc = sqlite3_declare_vtab(db, azSchema[isTabUsed]); + if( rc==SQLITE_OK ){ + pNew = sqlite3_malloc( sizeof(*pNew) ); + *ppVtab = (sqlite3_vtab*)pNew; + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + pNew->db = db; + pNew->bTablesUsed = isTabUsed*2; + } + return rc; +} + +/* +** This method is the destructor for bytecodevtab objects. +*/ +static int bytecodevtabDisconnect(sqlite3_vtab *pVtab){ + bytecodevtab *p = (bytecodevtab*)pVtab; + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Constructor for a new bytecodevtab_cursor object. +*/ +static int bytecodevtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + bytecodevtab *pVTab = (bytecodevtab*)p; + bytecodevtab_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + sqlite3VdbeMemInit(&pCur->sub, pVTab->db, 1); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Clear all internal content from a bytecodevtab cursor. +*/ +static void bytecodevtabCursorClear(bytecodevtab_cursor *pCur){ + sqlite3_free(pCur->zP4); + pCur->zP4 = 0; + sqlite3VdbeMemRelease(&pCur->sub); + sqlite3VdbeMemSetNull(&pCur->sub); + if( pCur->needFinalize ){ + sqlite3_finalize(pCur->pStmt); + } + pCur->pStmt = 0; + pCur->needFinalize = 0; + pCur->zType = 0; + pCur->zSchema = 0; + pCur->zName = 0; +} + +/* +** Destructor for a bytecodevtab_cursor. +*/ +static int bytecodevtabClose(sqlite3_vtab_cursor *cur){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor*)cur; + bytecodevtabCursorClear(pCur); + sqlite3_free(pCur); + return SQLITE_OK; +} + + +/* +** Advance a bytecodevtab_cursor to its next row of output. +*/ +static int bytecodevtabNext(sqlite3_vtab_cursor *cur){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor*)cur; + bytecodevtab *pTab = (bytecodevtab*)cur->pVtab; + int rc; + if( pCur->zP4 ){ + sqlite3_free(pCur->zP4); + pCur->zP4 = 0; + } + if( pCur->zName ){ + pCur->zName = 0; + pCur->zType = 0; + pCur->zSchema = 0; + } + rc = sqlite3VdbeNextOpcode( + (Vdbe*)pCur->pStmt, + pCur->showSubprograms ? &pCur->sub : 0, + pTab->bTablesUsed, + &pCur->iRowid, + &pCur->iAddr, + &pCur->aOp); + if( rc!=SQLITE_OK ){ + sqlite3VdbeMemSetNull(&pCur->sub); + pCur->aOp = 0; + } + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int bytecodevtabEof(sqlite3_vtab_cursor *cur){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor*)cur; + return pCur->aOp==0; +} + +/* +** Return values of columns for the row at which the bytecodevtab_cursor +** is currently pointing. +*/ +static int bytecodevtabColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor*)cur; + bytecodevtab *pVTab = (bytecodevtab*)cur->pVtab; + Op *pOp = pCur->aOp + pCur->iAddr; + if( pVTab->bTablesUsed ){ + if( i==4 ){ + i = 8; + }else{ + if( i<=2 && pCur->zType==0 ){ + Schema *pSchema; + HashElem *k; + int iDb = pOp->p3; + Pgno iRoot = (Pgno)pOp->p2; + sqlite3 *db = pVTab->db; + pSchema = db->aDb[iDb].pSchema; + pCur->zSchema = db->aDb[iDb].zDbSName; + for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ + Table *pTab = (Table*)sqliteHashData(k); + if( !IsVirtual(pTab) && pTab->tnum==iRoot ){ + pCur->zName = pTab->zName; + pCur->zType = "table"; + break; + } + } + if( pCur->zName==0 ){ + for(k=sqliteHashFirst(&pSchema->idxHash); k; k=sqliteHashNext(k)){ + Index *pIdx = (Index*)sqliteHashData(k); + if( pIdx->tnum==iRoot ){ + pCur->zName = pIdx->zName; + pCur->zType = "index"; + } + } + } + } + i += 20; + } + } + switch( i ){ + case 0: /* addr */ + sqlite3_result_int(ctx, pCur->iAddr); + break; + case 1: /* opcode */ + sqlite3_result_text(ctx, (char*)sqlite3OpcodeName(pOp->opcode), + -1, SQLITE_STATIC); + break; + case 2: /* p1 */ + sqlite3_result_int(ctx, pOp->p1); + break; + case 3: /* p2 */ + sqlite3_result_int(ctx, pOp->p2); + break; + case 4: /* p3 */ + sqlite3_result_int(ctx, pOp->p3); + break; + case 5: /* p4 */ + case 7: /* comment */ + if( pCur->zP4==0 ){ + pCur->zP4 = sqlite3VdbeDisplayP4(pVTab->db, pOp); + } + if( i==5 ){ + sqlite3_result_text(ctx, pCur->zP4, -1, SQLITE_STATIC); + }else{ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + char *zCom = sqlite3VdbeDisplayComment(pVTab->db, pOp, pCur->zP4); + sqlite3_result_text(ctx, zCom, -1, sqlite3_free); +#endif + } + break; + case 6: /* p5 */ + sqlite3_result_int(ctx, pOp->p5); + break; + case 8: { /* subprog */ + Op *aOp = pCur->aOp; + assert( aOp[0].opcode==OP_Init ); + assert( aOp[0].p4.z==0 || strncmp(aOp[0].p4.z,"-" "- ",3)==0 ); + if( pCur->iRowid==pCur->iAddr+1 ){ + break; /* Result is NULL for the main program */ + }else if( aOp[0].p4.z!=0 ){ + sqlite3_result_text(ctx, aOp[0].p4.z+3, -1, SQLITE_STATIC); + }else{ + sqlite3_result_text(ctx, "(FK)", 4, SQLITE_STATIC); + } + break; + } + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + case 9: /* nexec */ + sqlite3_result_int(ctx, pOp->nExec); + break; + case 10: /* ncycle */ + sqlite3_result_int(ctx, pOp->nCycle); + break; +#else + case 9: /* nexec */ + case 10: /* ncycle */ + sqlite3_result_int(ctx, 0); + break; +#endif + + case 20: /* tables_used.type */ + sqlite3_result_text(ctx, pCur->zType, -1, SQLITE_STATIC); + break; + case 21: /* tables_used.schema */ + sqlite3_result_text(ctx, pCur->zSchema, -1, SQLITE_STATIC); + break; + case 22: /* tables_used.name */ + sqlite3_result_text(ctx, pCur->zName, -1, SQLITE_STATIC); + break; + case 23: /* tables_used.wr */ + sqlite3_result_int(ctx, pOp->opcode==OP_OpenWrite); + break; + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int bytecodevtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Initialize a cursor. +** +** idxNum==0 means show all subprograms +** idxNum==1 means show only the main bytecode and omit subprograms. +*/ +static int bytecodevtabFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor *)pVtabCursor; + bytecodevtab *pVTab = (bytecodevtab *)pVtabCursor->pVtab; + int rc = SQLITE_OK; + (void)idxStr; + + bytecodevtabCursorClear(pCur); + pCur->iRowid = 0; + pCur->iAddr = 0; + pCur->showSubprograms = idxNum==0; + assert( argc==1 ); + if( sqlite3_value_type(argv[0])==SQLITE_TEXT ){ + const char *zSql = (const char*)sqlite3_value_text(argv[0]); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(pVTab->db, zSql, -1, &pCur->pStmt, 0); + pCur->needFinalize = 1; + } + }else{ + pCur->pStmt = (sqlite3_stmt*)sqlite3_value_pointer(argv[0],"stmt-pointer"); + } + if( pCur->pStmt==0 ){ + pVTab->base.zErrMsg = sqlite3_mprintf( + "argument to %s() is not a valid SQL statement", + pVTab->bTablesUsed ? "tables_used" : "bytecode" + ); + rc = SQLITE_ERROR; + }else{ + bytecodevtabNext(pVtabCursor); + } + return rc; +} + +/* +** We must have a single stmt=? constraint that will be passed through +** into the xFilter method. If there is no valid stmt=? constraint, +** then return an SQLITE_CONSTRAINT error. +*/ +static int bytecodevtabBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; + int rc = SQLITE_CONSTRAINT; + struct sqlite3_index_constraint *p; + bytecodevtab *pVTab = (bytecodevtab*)tab; + int iBaseCol = pVTab->bTablesUsed ? 4 : 10; + pIdxInfo->estimatedCost = (double)100; + pIdxInfo->estimatedRows = 100; + pIdxInfo->idxNum = 0; + for(i=0, p=pIdxInfo->aConstraint; i<pIdxInfo->nConstraint; i++, p++){ + if( p->usable==0 ) continue; + if( p->op==SQLITE_INDEX_CONSTRAINT_EQ && p->iColumn==iBaseCol+1 ){ + rc = SQLITE_OK; + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + } + if( p->op==SQLITE_INDEX_CONSTRAINT_ISNULL && p->iColumn==iBaseCol ){ + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->idxNum = 1; + } + } + return rc; +} + +/* +** This following structure defines all the methods for the +** virtual table. +*/ +static sqlite3_module bytecodevtabModule = { + /* iVersion */ 0, + /* xCreate */ 0, + /* xConnect */ bytecodevtabConnect, + /* xBestIndex */ bytecodevtabBestIndex, + /* xDisconnect */ bytecodevtabDisconnect, + /* xDestroy */ 0, + /* xOpen */ bytecodevtabOpen, + /* xClose */ bytecodevtabClose, + /* xFilter */ bytecodevtabFilter, + /* xNext */ bytecodevtabNext, + /* xEof */ bytecodevtabEof, + /* xColumn */ bytecodevtabColumn, + /* xRowid */ bytecodevtabRowid, + /* xUpdate */ 0, + /* xBegin */ 0, + /* xSync */ 0, + /* xCommit */ 0, + /* xRollback */ 0, + /* xFindMethod */ 0, + /* xRename */ 0, + /* xSavepoint */ 0, + /* xRelease */ 0, + /* xRollbackTo */ 0, + /* xShadowName */ 0 +}; + + +SQLITE_PRIVATE int sqlite3VdbeBytecodeVtabInit(sqlite3 *db){ + int rc; + rc = sqlite3_create_module(db, "bytecode", &bytecodevtabModule, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_module(db, "tables_used", &bytecodevtabModule, &db); + } + return rc; +} +#elif defined(SQLITE_ENABLE_BYTECODE_VTAB) +SQLITE_PRIVATE int sqlite3VdbeBytecodeVtabInit(sqlite3 *db){ return SQLITE_OK; } +#endif /* SQLITE_ENABLE_BYTECODE_VTAB */ + +/************** End of vdbevtab.c ********************************************/ +/************** Begin file memjournal.c **************************************/ +/* +** 2008 October 7 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains code use to implement an in-memory rollback journal. +** The in-memory rollback journal is used to journal transactions for +** ":memory:" databases and when the journal_mode=MEMORY pragma is used. +** +** Update: The in-memory journal is also used to temporarily cache +** smaller journals that are not critical for power-loss recovery. +** For example, statement journals that are not too big will be held +** entirely in memory, thus reducing the number of file I/O calls, and +** more importantly, reducing temporary file creation events. If these +** journals become too large for memory, they are spilled to disk. But +** in the common case, they are usually small and no file I/O needs to +** occur. +*/ +/* #include "sqliteInt.h" */ + +/* Forward references to internal structures */ +typedef struct MemJournal MemJournal; +typedef struct FilePoint FilePoint; +typedef struct FileChunk FileChunk; + +/* +** The rollback journal is composed of a linked list of these structures. +** +** The zChunk array is always at least 8 bytes in size - usually much more. +** Its actual size is stored in the MemJournal.nChunkSize variable. +*/ +struct FileChunk { + FileChunk *pNext; /* Next chunk in the journal */ + u8 zChunk[8]; /* Content of this chunk */ +}; + +/* +** By default, allocate this many bytes of memory for each FileChunk object. +*/ +#define MEMJOURNAL_DFLT_FILECHUNKSIZE 1024 + +/* +** For chunk size nChunkSize, return the number of bytes that should +** be allocated for each FileChunk structure. +*/ +#define fileChunkSize(nChunkSize) (sizeof(FileChunk) + ((nChunkSize)-8)) + +/* +** An instance of this object serves as a cursor into the rollback journal. +** The cursor can be either for reading or writing. +*/ +struct FilePoint { + sqlite3_int64 iOffset; /* Offset from the beginning of the file */ + FileChunk *pChunk; /* Specific chunk into which cursor points */ +}; + +/* +** This structure is a subclass of sqlite3_file. Each open memory-journal +** is an instance of this class. +*/ +struct MemJournal { + const sqlite3_io_methods *pMethod; /* Parent class. MUST BE FIRST */ + int nChunkSize; /* In-memory chunk-size */ + + int nSpill; /* Bytes of data before flushing */ + FileChunk *pFirst; /* Head of in-memory chunk-list */ + FilePoint endpoint; /* Pointer to the end of the file */ + FilePoint readpoint; /* Pointer to the end of the last xRead() */ + + int flags; /* xOpen flags */ + sqlite3_vfs *pVfs; /* The "real" underlying VFS */ + const char *zJournal; /* Name of the journal file */ +}; + +/* +** Read data from the in-memory journal file. This is the implementation +** of the sqlite3_vfs.xRead method. +*/ +static int memjrnlRead( + sqlite3_file *pJfd, /* The journal file from which to read */ + void *zBuf, /* Put the results here */ + int iAmt, /* Number of bytes to read */ + sqlite_int64 iOfst /* Begin reading at this offset */ +){ + MemJournal *p = (MemJournal *)pJfd; + u8 *zOut = zBuf; + int nRead = iAmt; + int iChunkOffset; + FileChunk *pChunk; + + if( (iAmt+iOfst)>p->endpoint.iOffset ){ + return SQLITE_IOERR_SHORT_READ; + } + assert( p->readpoint.iOffset==0 || p->readpoint.pChunk!=0 ); + if( p->readpoint.iOffset!=iOfst || iOfst==0 ){ + sqlite3_int64 iOff = 0; + for(pChunk=p->pFirst; + ALWAYS(pChunk) && (iOff+p->nChunkSize)<=iOfst; + pChunk=pChunk->pNext + ){ + iOff += p->nChunkSize; + } + }else{ + pChunk = p->readpoint.pChunk; + assert( pChunk!=0 ); + } + + iChunkOffset = (int)(iOfst%p->nChunkSize); + do { + int iSpace = p->nChunkSize - iChunkOffset; + int nCopy = MIN(nRead, (p->nChunkSize - iChunkOffset)); + memcpy(zOut, (u8*)pChunk->zChunk + iChunkOffset, nCopy); + zOut += nCopy; + nRead -= iSpace; + iChunkOffset = 0; + } while( nRead>=0 && (pChunk=pChunk->pNext)!=0 && nRead>0 ); + p->readpoint.iOffset = pChunk ? iOfst+iAmt : 0; + p->readpoint.pChunk = pChunk; + + return SQLITE_OK; +} + +/* +** Free the list of FileChunk structures headed at MemJournal.pFirst. +*/ +static void memjrnlFreeChunks(FileChunk *pFirst){ + FileChunk *pIter; + FileChunk *pNext; + for(pIter=pFirst; pIter; pIter=pNext){ + pNext = pIter->pNext; + sqlite3_free(pIter); + } +} + +/* +** Flush the contents of memory to a real file on disk. +*/ +static int memjrnlCreateFile(MemJournal *p){ + int rc; + sqlite3_file *pReal = (sqlite3_file*)p; + MemJournal copy = *p; + + memset(p, 0, sizeof(MemJournal)); + rc = sqlite3OsOpen(copy.pVfs, copy.zJournal, pReal, copy.flags, 0); + if( rc==SQLITE_OK ){ + int nChunk = copy.nChunkSize; + i64 iOff = 0; + FileChunk *pIter; + for(pIter=copy.pFirst; pIter; pIter=pIter->pNext){ + if( iOff + nChunk > copy.endpoint.iOffset ){ + nChunk = copy.endpoint.iOffset - iOff; + } + rc = sqlite3OsWrite(pReal, (u8*)pIter->zChunk, nChunk, iOff); + if( rc ) break; + iOff += nChunk; + } + if( rc==SQLITE_OK ){ + /* No error has occurred. Free the in-memory buffers. */ + memjrnlFreeChunks(copy.pFirst); + } + } + if( rc!=SQLITE_OK ){ + /* If an error occurred while creating or writing to the file, restore + ** the original before returning. This way, SQLite uses the in-memory + ** journal data to roll back changes made to the internal page-cache + ** before this function was called. */ + sqlite3OsClose(pReal); + *p = copy; + } + return rc; +} + + +/* Forward reference */ +static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size); + +/* +** Write data to the file. +*/ +static int memjrnlWrite( + sqlite3_file *pJfd, /* The journal file into which to write */ + const void *zBuf, /* Take data to be written from here */ + int iAmt, /* Number of bytes to write */ + sqlite_int64 iOfst /* Begin writing at this offset into the file */ +){ + MemJournal *p = (MemJournal *)pJfd; + int nWrite = iAmt; + u8 *zWrite = (u8 *)zBuf; + + /* If the file should be created now, create it and write the new data + ** into the file on disk. */ + if( p->nSpill>0 && (iAmt+iOfst)>p->nSpill ){ + int rc = memjrnlCreateFile(p); + if( rc==SQLITE_OK ){ + rc = sqlite3OsWrite(pJfd, zBuf, iAmt, iOfst); + } + return rc; + } + + /* If the contents of this write should be stored in memory */ + else{ + /* An in-memory journal file should only ever be appended to. Random + ** access writes are not required. The only exception to this is when + ** the in-memory journal is being used by a connection using the + ** atomic-write optimization. In this case the first 28 bytes of the + ** journal file may be written as part of committing the transaction. */ + assert( iOfst<=p->endpoint.iOffset ); + if( iOfst>0 && iOfst!=p->endpoint.iOffset ){ + memjrnlTruncate(pJfd, iOfst); + } + if( iOfst==0 && p->pFirst ){ + assert( p->nChunkSize>iAmt ); + memcpy((u8*)p->pFirst->zChunk, zBuf, iAmt); + }else{ + while( nWrite>0 ){ + FileChunk *pChunk = p->endpoint.pChunk; + int iChunkOffset = (int)(p->endpoint.iOffset%p->nChunkSize); + int iSpace = MIN(nWrite, p->nChunkSize - iChunkOffset); + + assert( pChunk!=0 || iChunkOffset==0 ); + if( iChunkOffset==0 ){ + /* New chunk is required to extend the file. */ + FileChunk *pNew = sqlite3_malloc(fileChunkSize(p->nChunkSize)); + if( !pNew ){ + return SQLITE_IOERR_NOMEM_BKPT; + } + pNew->pNext = 0; + if( pChunk ){ + assert( p->pFirst ); + pChunk->pNext = pNew; + }else{ + assert( !p->pFirst ); + p->pFirst = pNew; + } + pChunk = p->endpoint.pChunk = pNew; + } + + assert( pChunk!=0 ); + memcpy((u8*)pChunk->zChunk + iChunkOffset, zWrite, iSpace); + zWrite += iSpace; + nWrite -= iSpace; + p->endpoint.iOffset += iSpace; + } + } + } + + return SQLITE_OK; +} + +/* +** Truncate the in-memory file. +*/ +static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){ + MemJournal *p = (MemJournal *)pJfd; + assert( p->endpoint.pChunk==0 || p->endpoint.pChunk->pNext==0 ); + if( size<p->endpoint.iOffset ){ + FileChunk *pIter = 0; + if( size==0 ){ + memjrnlFreeChunks(p->pFirst); + p->pFirst = 0; + }else{ + i64 iOff = p->nChunkSize; + for(pIter=p->pFirst; ALWAYS(pIter) && iOff<size; pIter=pIter->pNext){ + iOff += p->nChunkSize; + } + if( ALWAYS(pIter) ){ + memjrnlFreeChunks(pIter->pNext); + pIter->pNext = 0; + } + } + + p->endpoint.pChunk = pIter; + p->endpoint.iOffset = size; + p->readpoint.pChunk = 0; + p->readpoint.iOffset = 0; + } + return SQLITE_OK; +} + +/* +** Close the file. +*/ +static int memjrnlClose(sqlite3_file *pJfd){ + MemJournal *p = (MemJournal *)pJfd; + memjrnlFreeChunks(p->pFirst); + return SQLITE_OK; +} + +/* +** Sync the file. +** +** If the real file has been created, call its xSync method. Otherwise, +** syncing an in-memory journal is a no-op. +*/ +static int memjrnlSync(sqlite3_file *pJfd, int flags){ + UNUSED_PARAMETER2(pJfd, flags); + return SQLITE_OK; +} + +/* +** Query the size of the file in bytes. +*/ +static int memjrnlFileSize(sqlite3_file *pJfd, sqlite_int64 *pSize){ + MemJournal *p = (MemJournal *)pJfd; + *pSize = (sqlite_int64) p->endpoint.iOffset; + return SQLITE_OK; +} + +/* +** Table of methods for MemJournal sqlite3_file object. +*/ +static const struct sqlite3_io_methods MemJournalMethods = { + 1, /* iVersion */ + memjrnlClose, /* xClose */ + memjrnlRead, /* xRead */ + memjrnlWrite, /* xWrite */ + memjrnlTruncate, /* xTruncate */ + memjrnlSync, /* xSync */ + memjrnlFileSize, /* xFileSize */ + 0, /* xLock */ + 0, /* xUnlock */ + 0, /* xCheckReservedLock */ + 0, /* xFileControl */ + 0, /* xSectorSize */ + 0, /* xDeviceCharacteristics */ + 0, /* xShmMap */ + 0, /* xShmLock */ + 0, /* xShmBarrier */ + 0, /* xShmUnmap */ + 0, /* xFetch */ + 0 /* xUnfetch */ +}; + +/* +** Open a journal file. +** +** The behaviour of the journal file depends on the value of parameter +** nSpill. If nSpill is 0, then the journal file is always create and +** accessed using the underlying VFS. If nSpill is less than zero, then +** all content is always stored in main-memory. Finally, if nSpill is a +** positive value, then the journal file is initially created in-memory +** but may be flushed to disk later on. In this case the journal file is +** flushed to disk either when it grows larger than nSpill bytes in size, +** or when sqlite3JournalCreate() is called. +*/ +SQLITE_PRIVATE int sqlite3JournalOpen( + sqlite3_vfs *pVfs, /* The VFS to use for actual file I/O */ + const char *zName, /* Name of the journal file */ + sqlite3_file *pJfd, /* Preallocated, blank file handle */ + int flags, /* Opening flags */ + int nSpill /* Bytes buffered before opening the file */ +){ + MemJournal *p = (MemJournal*)pJfd; + + assert( zName || nSpill<0 || (flags & SQLITE_OPEN_EXCLUSIVE) ); + + /* Zero the file-handle object. If nSpill was passed zero, initialize + ** it using the sqlite3OsOpen() function of the underlying VFS. In this + ** case none of the code in this module is executed as a result of calls + ** made on the journal file-handle. */ + memset(p, 0, sizeof(MemJournal)); + if( nSpill==0 ){ + return sqlite3OsOpen(pVfs, zName, pJfd, flags, 0); + } + + if( nSpill>0 ){ + p->nChunkSize = nSpill; + }else{ + p->nChunkSize = 8 + MEMJOURNAL_DFLT_FILECHUNKSIZE - sizeof(FileChunk); + assert( MEMJOURNAL_DFLT_FILECHUNKSIZE==fileChunkSize(p->nChunkSize) ); + } + + pJfd->pMethods = (const sqlite3_io_methods*)&MemJournalMethods; + p->nSpill = nSpill; + p->flags = flags; + p->zJournal = zName; + p->pVfs = pVfs; + return SQLITE_OK; +} + +/* +** Open an in-memory journal file. +*/ +SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *pJfd){ + sqlite3JournalOpen(0, 0, pJfd, 0, -1); +} + +#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ + || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) +/* +** If the argument p points to a MemJournal structure that is not an +** in-memory-only journal file (i.e. is one that was opened with a +ve +** nSpill parameter or as SQLITE_OPEN_MAIN_JOURNAL), and the underlying +** file has not yet been created, create it now. +*/ +SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *pJfd){ + int rc = SQLITE_OK; + MemJournal *p = (MemJournal*)pJfd; + if( pJfd->pMethods==&MemJournalMethods && ( +#ifdef SQLITE_ENABLE_ATOMIC_WRITE + p->nSpill>0 +#else + /* While this appears to not be possible without ATOMIC_WRITE, the + ** paths are complex, so it seems prudent to leave the test in as + ** a NEVER(), in case our analysis is subtly flawed. */ + NEVER(p->nSpill>0) +#endif +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + || (p->flags & SQLITE_OPEN_MAIN_JOURNAL) +#endif + )){ + rc = memjrnlCreateFile(p); + } + return rc; +} +#endif + +/* +** The file-handle passed as the only argument is open on a journal file. +** Return true if this "journal file" is currently stored in heap memory, +** or false otherwise. +*/ +SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p){ + return p->pMethods==&MemJournalMethods; +} + +/* +** Return the number of bytes required to store a JournalFile that uses vfs +** pVfs to create the underlying on-disk files. +*/ +SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *pVfs){ + return MAX(pVfs->szOsFile, (int)sizeof(MemJournal)); +} + +/************** End of memjournal.c ******************************************/ +/************** Begin file walker.c ******************************************/ +/* +** 2008 August 16 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains routines used for walking the parser tree for +** an SQL statement. +*/ +/* #include "sqliteInt.h" */ +/* #include <stdlib.h> */ +/* #include <string.h> */ + + +#if !defined(SQLITE_OMIT_WINDOWFUNC) +/* +** Walk all expressions linked into the list of Window objects passed +** as the second argument. +*/ +static int walkWindowList(Walker *pWalker, Window *pList, int bOneOnly){ + Window *pWin; + for(pWin=pList; pWin; pWin=pWin->pNextWin){ + int rc; + rc = sqlite3WalkExprList(pWalker, pWin->pOrderBy); + if( rc ) return WRC_Abort; + rc = sqlite3WalkExprList(pWalker, pWin->pPartition); + if( rc ) return WRC_Abort; + rc = sqlite3WalkExpr(pWalker, pWin->pFilter); + if( rc ) return WRC_Abort; + rc = sqlite3WalkExpr(pWalker, pWin->pStart); + if( rc ) return WRC_Abort; + rc = sqlite3WalkExpr(pWalker, pWin->pEnd); + if( rc ) return WRC_Abort; + if( bOneOnly ) break; + } + return WRC_Continue; +} +#endif + +/* +** Walk an expression tree. Invoke the callback once for each node +** of the expression, while descending. (In other words, the callback +** is invoked before visiting children.) +** +** The return value from the callback should be one of the WRC_* +** constants to specify how to proceed with the walk. +** +** WRC_Continue Continue descending down the tree. +** +** WRC_Prune Do not descend into child nodes, but allow +** the walk to continue with sibling nodes. +** +** WRC_Abort Do no more callbacks. Unwind the stack and +** return from the top-level walk call. +** +** The return value from this routine is WRC_Abort to abandon the tree walk +** and WRC_Continue to continue. +*/ +SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3WalkExprNN(Walker *pWalker, Expr *pExpr){ + int rc; + testcase( ExprHasProperty(pExpr, EP_TokenOnly) ); + testcase( ExprHasProperty(pExpr, EP_Reduced) ); + while(1){ + rc = pWalker->xExprCallback(pWalker, pExpr); + if( rc ) return rc & WRC_Abort; + if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ + assert( pExpr->x.pList==0 || pExpr->pRight==0 ); + if( pExpr->pLeft && sqlite3WalkExprNN(pWalker, pExpr->pLeft) ){ + return WRC_Abort; + } + if( pExpr->pRight ){ + assert( !ExprHasProperty(pExpr, EP_WinFunc) ); + pExpr = pExpr->pRight; + continue; + }else if( ExprUseXSelect(pExpr) ){ + assert( !ExprHasProperty(pExpr, EP_WinFunc) ); + if( sqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort; + }else{ + if( pExpr->x.pList ){ + if( sqlite3WalkExprList(pWalker, pExpr->x.pList) ) return WRC_Abort; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + if( walkWindowList(pWalker, pExpr->y.pWin, 1) ) return WRC_Abort; + } +#endif + } + } + break; + } + return WRC_Continue; +} +SQLITE_PRIVATE int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){ + return pExpr ? sqlite3WalkExprNN(pWalker,pExpr) : WRC_Continue; +} + +/* +** Call sqlite3WalkExpr() for every expression in list p or until +** an abort request is seen. +*/ +SQLITE_PRIVATE int sqlite3WalkExprList(Walker *pWalker, ExprList *p){ + int i; + struct ExprList_item *pItem; + if( p ){ + for(i=p->nExpr, pItem=p->a; i>0; i--, pItem++){ + if( sqlite3WalkExpr(pWalker, pItem->pExpr) ) return WRC_Abort; + } + } + return WRC_Continue; +} + +/* +** This is a no-op callback for Walker->xSelectCallback2. If this +** callback is set, then the Select->pWinDefn list is traversed. +*/ +SQLITE_PRIVATE void sqlite3WalkWinDefnDummyCallback(Walker *pWalker, Select *p){ + UNUSED_PARAMETER(pWalker); + UNUSED_PARAMETER(p); + /* No-op */ +} + +/* +** Walk all expressions associated with SELECT statement p. Do +** not invoke the SELECT callback on p, but do (of course) invoke +** any expr callbacks and SELECT callbacks that come from subqueries. +** Return WRC_Abort or WRC_Continue. +*/ +SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){ + if( sqlite3WalkExprList(pWalker, p->pEList) ) return WRC_Abort; + if( sqlite3WalkExpr(pWalker, p->pWhere) ) return WRC_Abort; + if( sqlite3WalkExprList(pWalker, p->pGroupBy) ) return WRC_Abort; + if( sqlite3WalkExpr(pWalker, p->pHaving) ) return WRC_Abort; + if( sqlite3WalkExprList(pWalker, p->pOrderBy) ) return WRC_Abort; + if( sqlite3WalkExpr(pWalker, p->pLimit) ) return WRC_Abort; +#if !defined(SQLITE_OMIT_WINDOWFUNC) + if( p->pWinDefn ){ + Parse *pParse; + if( pWalker->xSelectCallback2==sqlite3WalkWinDefnDummyCallback + || ((pParse = pWalker->pParse)!=0 && IN_RENAME_OBJECT) +#ifndef SQLITE_OMIT_CTE + || pWalker->xSelectCallback2==sqlite3SelectPopWith +#endif + ){ + /* The following may return WRC_Abort if there are unresolvable + ** symbols (e.g. a table that does not exist) in a window definition. */ + int rc = walkWindowList(pWalker, p->pWinDefn, 0); + return rc; + } + } +#endif + return WRC_Continue; +} + +/* +** Walk the parse trees associated with all subqueries in the +** FROM clause of SELECT statement p. Do not invoke the select +** callback on p, but do invoke it on each FROM clause subquery +** and on any subqueries further down in the tree. Return +** WRC_Abort or WRC_Continue; +*/ +SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){ + SrcList *pSrc; + int i; + SrcItem *pItem; + + pSrc = p->pSrc; + if( ALWAYS(pSrc) ){ + for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ + if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){ + return WRC_Abort; + } + if( pItem->fg.isTabFunc + && sqlite3WalkExprList(pWalker, pItem->u1.pFuncArg) + ){ + return WRC_Abort; + } + } + } + return WRC_Continue; +} + +/* +** Call sqlite3WalkExpr() for every expression in Select statement p. +** Invoke sqlite3WalkSelect() for subqueries in the FROM clause and +** on the compound select chain, p->pPrior. +** +** If it is not NULL, the xSelectCallback() callback is invoked before +** the walk of the expressions and FROM clause. The xSelectCallback2() +** method is invoked following the walk of the expressions and FROM clause, +** but only if both xSelectCallback and xSelectCallback2 are both non-NULL +** and if the expressions and FROM clause both return WRC_Continue; +** +** Return WRC_Continue under normal conditions. Return WRC_Abort if +** there is an abort request. +** +** If the Walker does not have an xSelectCallback() then this routine +** is a no-op returning WRC_Continue. +*/ +SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){ + int rc; + if( p==0 ) return WRC_Continue; + if( pWalker->xSelectCallback==0 ) return WRC_Continue; + do{ + rc = pWalker->xSelectCallback(pWalker, p); + if( rc ) return rc & WRC_Abort; + if( sqlite3WalkSelectExpr(pWalker, p) + || sqlite3WalkSelectFrom(pWalker, p) + ){ + return WRC_Abort; + } + if( pWalker->xSelectCallback2 ){ + pWalker->xSelectCallback2(pWalker, p); + } + p = p->pPrior; + }while( p!=0 ); + return WRC_Continue; +} + +/* Increase the walkerDepth when entering a subquery, and +** decrease when leaving the subquery. +*/ +SQLITE_PRIVATE int sqlite3WalkerDepthIncrease(Walker *pWalker, Select *pSelect){ + UNUSED_PARAMETER(pSelect); + pWalker->walkerDepth++; + return WRC_Continue; +} +SQLITE_PRIVATE void sqlite3WalkerDepthDecrease(Walker *pWalker, Select *pSelect){ + UNUSED_PARAMETER(pSelect); + pWalker->walkerDepth--; +} + + +/* +** No-op routine for the parse-tree walker. +** +** When this routine is the Walker.xExprCallback then expression trees +** are walked without any actions being taken at each node. Presumably, +** when this routine is used for Walker.xExprCallback then +** Walker.xSelectCallback is set to do something useful for every +** subquery in the parser tree. +*/ +SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker *NotUsed, Expr *NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + return WRC_Continue; +} + +/* +** No-op routine for the parse-tree walker for SELECT statements. +** subquery in the parser tree. +*/ +SQLITE_PRIVATE int sqlite3SelectWalkNoop(Walker *NotUsed, Select *NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + return WRC_Continue; +} + +/************** End of walker.c **********************************************/ +/************** Begin file resolve.c *****************************************/ +/* +** 2008 August 18 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains routines used for walking the parser tree and +** resolve all identifiers by associating them with a particular +** table and column. +*/ +/* #include "sqliteInt.h" */ + +/* +** Magic table number to mean the EXCLUDED table in an UPSERT statement. +*/ +#define EXCLUDED_TABLE_NUMBER 2 + +/* +** Walk the expression tree pExpr and increase the aggregate function +** depth (the Expr.op2 field) by N on every TK_AGG_FUNCTION node. +** This needs to occur when copying a TK_AGG_FUNCTION node from an +** outer query into an inner subquery. +** +** incrAggFunctionDepth(pExpr,n) is the main routine. incrAggDepth(..) +** is a helper function - a callback for the tree walker. +** +** See also the sqlite3WindowExtraAggFuncDepth() routine in window.c +*/ +static int incrAggDepth(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_AGG_FUNCTION ) pExpr->op2 += pWalker->u.n; + return WRC_Continue; +} +static void incrAggFunctionDepth(Expr *pExpr, int N){ + if( N>0 ){ + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = incrAggDepth; + w.u.n = N; + sqlite3WalkExpr(&w, pExpr); + } +} + +/* +** Turn the pExpr expression into an alias for the iCol-th column of the +** result set in pEList. +** +** If the reference is followed by a COLLATE operator, then make sure +** the COLLATE operator is preserved. For example: +** +** SELECT a+b, c+d FROM t1 ORDER BY 1 COLLATE nocase; +** +** Should be transformed into: +** +** SELECT a+b, c+d FROM t1 ORDER BY (a+b) COLLATE nocase; +** +** The nSubquery parameter specifies how many levels of subquery the +** alias is removed from the original expression. The usual value is +** zero but it might be more if the alias is contained within a subquery +** of the original expression. The Expr.op2 field of TK_AGG_FUNCTION +** structures must be increased by the nSubquery amount. +*/ +static void resolveAlias( + Parse *pParse, /* Parsing context */ + ExprList *pEList, /* A result set */ + int iCol, /* A column in the result set. 0..pEList->nExpr-1 */ + Expr *pExpr, /* Transform this into an alias to the result set */ + int nSubquery /* Number of subqueries that the label is moving */ +){ + Expr *pOrig; /* The iCol-th column of the result set */ + Expr *pDup; /* Copy of pOrig */ + sqlite3 *db; /* The database connection */ + + assert( iCol>=0 && iCol<pEList->nExpr ); + pOrig = pEList->a[iCol].pExpr; + assert( pOrig!=0 ); + db = pParse->db; + pDup = sqlite3ExprDup(db, pOrig, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDup); + pDup = 0; + }else{ + Expr temp; + incrAggFunctionDepth(pDup, nSubquery); + if( pExpr->op==TK_COLLATE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken); + } + memcpy(&temp, pDup, sizeof(Expr)); + memcpy(pDup, pExpr, sizeof(Expr)); + memcpy(pExpr, &temp, sizeof(Expr)); + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + if( ALWAYS(pExpr->y.pWin!=0) ){ + pExpr->y.pWin->pOwner = pExpr; + } + } + sqlite3ExprDeferredDelete(pParse, pDup); + } +} + +/* +** Subqueries stores the original database, table and column names for their +** result sets in ExprList.a[].zSpan, in the form "DATABASE.TABLE.COLUMN". +** Check to see if the zSpan given to this routine matches the zDb, zTab, +** and zCol. If any of zDb, zTab, and zCol are NULL then those fields will +** match anything. +*/ +SQLITE_PRIVATE int sqlite3MatchEName( + const struct ExprList_item *pItem, + const char *zCol, + const char *zTab, + const char *zDb +){ + int n; + const char *zSpan; + if( pItem->fg.eEName!=ENAME_TAB ) return 0; + zSpan = pItem->zEName; + for(n=0; ALWAYS(zSpan[n]) && zSpan[n]!='.'; n++){} + if( zDb && (sqlite3StrNICmp(zSpan, zDb, n)!=0 || zDb[n]!=0) ){ + return 0; + } + zSpan += n+1; + for(n=0; ALWAYS(zSpan[n]) && zSpan[n]!='.'; n++){} + if( zTab && (sqlite3StrNICmp(zSpan, zTab, n)!=0 || zTab[n]!=0) ){ + return 0; + } + zSpan += n+1; + if( zCol && sqlite3StrICmp(zSpan, zCol)!=0 ){ + return 0; + } + return 1; +} + +/* +** Return TRUE if the double-quoted string mis-feature should be supported. +*/ +static int areDoubleQuotedStringsEnabled(sqlite3 *db, NameContext *pTopNC){ + if( db->init.busy ) return 1; /* Always support for legacy schemas */ + if( pTopNC->ncFlags & NC_IsDDL ){ + /* Currently parsing a DDL statement */ + if( sqlite3WritableSchema(db) && (db->flags & SQLITE_DqsDML)!=0 ){ + return 1; + } + return (db->flags & SQLITE_DqsDDL)!=0; + }else{ + /* Currently parsing a DML statement */ + return (db->flags & SQLITE_DqsDML)!=0; + } +} + +/* +** The argument is guaranteed to be a non-NULL Expr node of type TK_COLUMN. +** return the appropriate colUsed mask. +*/ +SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr *pExpr){ + int n; + Table *pExTab; + + n = pExpr->iColumn; + assert( ExprUseYTab(pExpr) ); + pExTab = pExpr->y.pTab; + assert( pExTab!=0 ); + if( (pExTab->tabFlags & TF_HasGenerated)!=0 + && (pExTab->aCol[n].colFlags & COLFLAG_GENERATED)!=0 + ){ + testcase( pExTab->nCol==BMS-1 ); + testcase( pExTab->nCol==BMS ); + return pExTab->nCol>=BMS ? ALLBITS : MASKBIT(pExTab->nCol)-1; + }else{ + testcase( n==BMS-1 ); + testcase( n==BMS ); + if( n>=BMS ) n = BMS-1; + return ((Bitmask)1)<<n; + } +} + +/* +** Create a new expression term for the column specified by pMatch and +** iColumn. Append this new expression term to the FULL JOIN Match set +** in *ppList. Create a new *ppList if this is the first term in the +** set. +*/ +static void extendFJMatch( + Parse *pParse, /* Parsing context */ + ExprList **ppList, /* ExprList to extend */ + SrcItem *pMatch, /* Source table containing the column */ + i16 iColumn /* The column number */ +){ + Expr *pNew = sqlite3ExprAlloc(pParse->db, TK_COLUMN, 0, 0); + if( pNew ){ + pNew->iTable = pMatch->iCursor; + pNew->iColumn = iColumn; + pNew->y.pTab = pMatch->pTab; + assert( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 ); + ExprSetProperty(pNew, EP_CanBeNull); + *ppList = sqlite3ExprListAppend(pParse, *ppList, pNew); + } +} + +/* +** Return TRUE (non-zero) if zTab is a valid name for the schema table pTab. +*/ +static SQLITE_NOINLINE int isValidSchemaTableName( + const char *zTab, /* Name as it appears in the SQL */ + Table *pTab, /* The schema table we are trying to match */ + Schema *pSchema /* non-NULL if a database qualifier is present */ +){ + const char *zLegacy; + assert( pTab!=0 ); + assert( pTab->tnum==1 ); + if( sqlite3StrNICmp(zTab, "sqlite_", 7)!=0 ) return 0; + zLegacy = pTab->zName; + if( strcmp(zLegacy+7, &LEGACY_TEMP_SCHEMA_TABLE[7])==0 ){ + if( sqlite3StrICmp(zTab+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 ){ + return 1; + } + if( pSchema==0 ) return 0; + if( sqlite3StrICmp(zTab+7, &LEGACY_SCHEMA_TABLE[7])==0 ) return 1; + if( sqlite3StrICmp(zTab+7, &PREFERRED_SCHEMA_TABLE[7])==0 ) return 1; + }else{ + if( sqlite3StrICmp(zTab+7, &PREFERRED_SCHEMA_TABLE[7])==0 ) return 1; + } + return 0; +} + +/* +** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up +** that name in the set of source tables in pSrcList and make the pExpr +** expression node refer back to that source column. The following changes +** are made to pExpr: +** +** pExpr->iDb Set the index in db->aDb[] of the database X +** (even if X is implied). +** pExpr->iTable Set to the cursor number for the table obtained +** from pSrcList. +** pExpr->y.pTab Points to the Table structure of X.Y (even if +** X and/or Y are implied.) +** pExpr->iColumn Set to the column number within the table. +** pExpr->op Set to TK_COLUMN. +** pExpr->pLeft Any expression this points to is deleted +** pExpr->pRight Any expression this points to is deleted. +** +** The zDb variable is the name of the database (the "X"). This value may be +** NULL meaning that name is of the form Y.Z or Z. Any available database +** can be used. The zTable variable is the name of the table (the "Y"). This +** value can be NULL if zDb is also NULL. If zTable is NULL it +** means that the form of the name is Z and that columns from any table +** can be used. +** +** If the name cannot be resolved unambiguously, leave an error message +** in pParse and return WRC_Abort. Return WRC_Prune on success. +*/ +static int lookupName( + Parse *pParse, /* The parsing context */ + const char *zDb, /* Name of the database containing table, or NULL */ + const char *zTab, /* Name of table containing column, or NULL */ + const char *zCol, /* Name of the column. */ + NameContext *pNC, /* The name context used to resolve the name */ + Expr *pExpr /* Make this EXPR node point to the selected column */ +){ + int i, j; /* Loop counters */ + int cnt = 0; /* Number of matching column names */ + int cntTab = 0; /* Number of matching table names */ + int nSubquery = 0; /* How many levels of subquery */ + sqlite3 *db = pParse->db; /* The database connection */ + SrcItem *pItem; /* Use for looping over pSrcList items */ + SrcItem *pMatch = 0; /* The matching pSrcList item */ + NameContext *pTopNC = pNC; /* First namecontext in the list */ + Schema *pSchema = 0; /* Schema of the expression */ + int eNewExprOp = TK_COLUMN; /* New value for pExpr->op on success */ + Table *pTab = 0; /* Table holding the row */ + Column *pCol; /* A column of pTab */ + ExprList *pFJMatch = 0; /* Matches for FULL JOIN .. USING */ + + assert( pNC ); /* the name context cannot be NULL. */ + assert( zCol ); /* The Z in X.Y.Z cannot be NULL */ + assert( zDb==0 || zTab!=0 ); + assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); + + /* Initialize the node to no-match */ + pExpr->iTable = -1; + ExprSetVVAProperty(pExpr, EP_NoReduce); + + /* Translate the schema name in zDb into a pointer to the corresponding + ** schema. If not found, pSchema will remain NULL and nothing will match + ** resulting in an appropriate error message toward the end of this routine + */ + if( zDb ){ + testcase( pNC->ncFlags & NC_PartIdx ); + testcase( pNC->ncFlags & NC_IsCheck ); + if( (pNC->ncFlags & (NC_PartIdx|NC_IsCheck))!=0 ){ + /* Silently ignore database qualifiers inside CHECK constraints and + ** partial indices. Do not raise errors because that might break + ** legacy and because it does not hurt anything to just ignore the + ** database name. */ + zDb = 0; + }else{ + for(i=0; i<db->nDb; i++){ + assert( db->aDb[i].zDbSName ); + if( sqlite3StrICmp(db->aDb[i].zDbSName,zDb)==0 ){ + pSchema = db->aDb[i].pSchema; + break; + } + } + if( i==db->nDb && sqlite3StrICmp("main", zDb)==0 ){ + /* This branch is taken when the main database has been renamed + ** using SQLITE_DBCONFIG_MAINDBNAME. */ + pSchema = db->aDb[0].pSchema; + zDb = db->aDb[0].zDbSName; + } + } + } + + /* Start at the inner-most context and move outward until a match is found */ + assert( pNC && cnt==0 ); + do{ + ExprList *pEList; + SrcList *pSrcList = pNC->pSrcList; + + if( pSrcList ){ + for(i=0, pItem=pSrcList->a; i<pSrcList->nSrc; i++, pItem++){ + u8 hCol; + pTab = pItem->pTab; + assert( pTab!=0 && pTab->zName!=0 ); + assert( pTab->nCol>0 || pParse->nErr ); + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); + if( pItem->fg.isNestedFrom ){ + /* In this case, pItem is a subquery that has been formed from a + ** parenthesized subset of the FROM clause terms. Example: + ** .... FROM t1 LEFT JOIN (t2 RIGHT JOIN t3 USING(x)) USING(y) ... + ** \_________________________/ + ** This pItem -------------^ + */ + int hit = 0; + assert( pItem->pSelect!=0 ); + pEList = pItem->pSelect->pEList; + assert( pEList!=0 ); + assert( pEList->nExpr==pTab->nCol ); + for(j=0; j<pEList->nExpr; j++){ + if( !sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb) ){ + continue; + } + if( cnt>0 ){ + if( pItem->fg.isUsing==0 + || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 + ){ + /* Two or more tables have the same column name which is + ** not joined by USING. This is an error. Signal as much + ** by clearing pFJMatch and letting cnt go above 1. */ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else + if( (pItem->fg.jointype & JT_RIGHT)==0 ){ + /* An INNER or LEFT JOIN. Use the left-most table */ + continue; + }else + if( (pItem->fg.jointype & JT_LEFT)==0 ){ + /* A RIGHT JOIN. Use the right-most table */ + cnt = 0; + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else{ + /* For a FULL JOIN, we must construct a coalesce() func */ + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + } + } + cnt++; + cntTab = 2; + pMatch = pItem; + pExpr->iColumn = j; + pEList->a[j].fg.bUsed = 1; + hit = 1; + if( pEList->a[j].fg.bUsingTerm ) break; + } + if( hit || zTab==0 ) continue; + } + assert( zDb==0 || zTab!=0 ); + if( zTab ){ + if( zDb ){ + if( pTab->pSchema!=pSchema ) continue; + if( pSchema==0 && strcmp(zDb,"*")!=0 ) continue; + } + if( pItem->zAlias!=0 ){ + if( sqlite3StrICmp(zTab, pItem->zAlias)!=0 ){ + continue; + } + }else if( sqlite3StrICmp(zTab, pTab->zName)!=0 ){ + if( pTab->tnum!=1 ) continue; + if( !isValidSchemaTableName(zTab, pTab, pSchema) ) continue; + } + assert( ExprUseYTab(pExpr) ); + if( IN_RENAME_OBJECT && pItem->zAlias ){ + sqlite3RenameTokenRemap(pParse, 0, (void*)&pExpr->y.pTab); + } + } + hCol = sqlite3StrIHash(zCol); + for(j=0, pCol=pTab->aCol; j<pTab->nCol; j++, pCol++){ + if( pCol->hName==hCol + && sqlite3StrICmp(pCol->zCnName, zCol)==0 + ){ + if( cnt>0 ){ + if( pItem->fg.isUsing==0 + || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 + ){ + /* Two or more tables have the same column name which is + ** not joined by USING. This is an error. Signal as much + ** by clearing pFJMatch and letting cnt go above 1. */ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else + if( (pItem->fg.jointype & JT_RIGHT)==0 ){ + /* An INNER or LEFT JOIN. Use the left-most table */ + continue; + }else + if( (pItem->fg.jointype & JT_LEFT)==0 ){ + /* A RIGHT JOIN. Use the right-most table */ + cnt = 0; + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else{ + /* For a FULL JOIN, we must construct a coalesce() func */ + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + } + } + cnt++; + pMatch = pItem; + /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */ + pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j; + if( pItem->fg.isNestedFrom ){ + sqlite3SrcItemColumnUsed(pItem, j); + } + break; + } + } + if( 0==cnt && VisibleRowid(pTab) ){ + cntTab++; + pMatch = pItem; + } + } + if( pMatch ){ + pExpr->iTable = pMatch->iCursor; + assert( ExprUseYTab(pExpr) ); + pExpr->y.pTab = pMatch->pTab; + if( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 ){ + ExprSetProperty(pExpr, EP_CanBeNull); + } + pSchema = pExpr->y.pTab->pSchema; + } + } /* if( pSrcList ) */ + +#if !defined(SQLITE_OMIT_TRIGGER) || !defined(SQLITE_OMIT_UPSERT) + /* If we have not already resolved the name, then maybe + ** it is a new.* or old.* trigger argument reference. Or + ** maybe it is an excluded.* from an upsert. Or maybe it is + ** a reference in the RETURNING clause to a table being modified. + */ + if( cnt==0 && zDb==0 ){ + pTab = 0; +#ifndef SQLITE_OMIT_TRIGGER + if( pParse->pTriggerTab!=0 ){ + int op = pParse->eTriggerOp; + assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT ); + if( pParse->bReturning ){ + if( (pNC->ncFlags & NC_UBaseReg)!=0 + && ALWAYS(zTab==0 + || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0) + ){ + pExpr->iTable = op!=TK_DELETE; + pTab = pParse->pTriggerTab; + } + }else if( op!=TK_DELETE && zTab && sqlite3StrICmp("new",zTab) == 0 ){ + pExpr->iTable = 1; + pTab = pParse->pTriggerTab; + }else if( op!=TK_INSERT && zTab && sqlite3StrICmp("old",zTab)==0 ){ + pExpr->iTable = 0; + pTab = pParse->pTriggerTab; + } + } +#endif /* SQLITE_OMIT_TRIGGER */ +#ifndef SQLITE_OMIT_UPSERT + if( (pNC->ncFlags & NC_UUpsert)!=0 && zTab!=0 ){ + Upsert *pUpsert = pNC->uNC.pUpsert; + if( pUpsert && sqlite3StrICmp("excluded",zTab)==0 ){ + pTab = pUpsert->pUpsertSrc->a[0].pTab; + pExpr->iTable = EXCLUDED_TABLE_NUMBER; + } + } +#endif /* SQLITE_OMIT_UPSERT */ + + if( pTab ){ + int iCol; + u8 hCol = sqlite3StrIHash(zCol); + pSchema = pTab->pSchema; + cntTab++; + for(iCol=0, pCol=pTab->aCol; iCol<pTab->nCol; iCol++, pCol++){ + if( pCol->hName==hCol + && sqlite3StrICmp(pCol->zCnName, zCol)==0 + ){ + if( iCol==pTab->iPKey ){ + iCol = -1; + } + break; + } + } + if( iCol>=pTab->nCol && sqlite3IsRowid(zCol) && VisibleRowid(pTab) ){ + /* IMP: R-51414-32910 */ + iCol = -1; + } + if( iCol<pTab->nCol ){ + cnt++; + pMatch = 0; +#ifndef SQLITE_OMIT_UPSERT + if( pExpr->iTable==EXCLUDED_TABLE_NUMBER ){ + testcase( iCol==(-1) ); + assert( ExprUseYTab(pExpr) ); + if( IN_RENAME_OBJECT ){ + pExpr->iColumn = iCol; + pExpr->y.pTab = pTab; + eNewExprOp = TK_COLUMN; + }else{ + pExpr->iTable = pNC->uNC.pUpsert->regData + + sqlite3TableColumnToStorage(pTab, iCol); + eNewExprOp = TK_REGISTER; + } + }else +#endif /* SQLITE_OMIT_UPSERT */ + { + assert( ExprUseYTab(pExpr) ); + pExpr->y.pTab = pTab; + if( pParse->bReturning ){ + eNewExprOp = TK_REGISTER; + pExpr->op2 = TK_COLUMN; + pExpr->iColumn = iCol; + pExpr->iTable = pNC->uNC.iBaseReg + (pTab->nCol+1)*pExpr->iTable + + sqlite3TableColumnToStorage(pTab, iCol) + 1; + }else{ + pExpr->iColumn = (i16)iCol; + eNewExprOp = TK_TRIGGER; +#ifndef SQLITE_OMIT_TRIGGER + if( iCol<0 ){ + pExpr->affExpr = SQLITE_AFF_INTEGER; + }else if( pExpr->iTable==0 ){ + testcase( iCol==31 ); + testcase( iCol==32 ); + pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol)); + }else{ + testcase( iCol==31 ); + testcase( iCol==32 ); + pParse->newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol)); + } +#endif /* SQLITE_OMIT_TRIGGER */ + } + } + } + } + } +#endif /* !defined(SQLITE_OMIT_TRIGGER) || !defined(SQLITE_OMIT_UPSERT) */ + + /* + ** Perhaps the name is a reference to the ROWID + */ + if( cnt==0 + && cntTab==1 + && pMatch + && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0 + && sqlite3IsRowid(zCol) + && ALWAYS(VisibleRowid(pMatch->pTab)) + ){ + cnt = 1; + pExpr->iColumn = -1; + pExpr->affExpr = SQLITE_AFF_INTEGER; + } + + /* + ** If the input is of the form Z (not Y.Z or X.Y.Z) then the name Z + ** might refer to an result-set alias. This happens, for example, when + ** we are resolving names in the WHERE clause of the following command: + ** + ** SELECT a+b AS x FROM table WHERE x<10; + ** + ** In cases like this, replace pExpr with a copy of the expression that + ** forms the result set entry ("a+b" in the example) and return immediately. + ** Note that the expression in the result set should have already been + ** resolved by the time the WHERE clause is resolved. + ** + ** The ability to use an output result-set column in the WHERE, GROUP BY, + ** or HAVING clauses, or as part of a larger expression in the ORDER BY + ** clause is not standard SQL. This is a (goofy) SQLite extension, that + ** is supported for backwards compatibility only. Hence, we issue a warning + ** on sqlite3_log() whenever the capability is used. + */ + if( cnt==0 + && (pNC->ncFlags & NC_UEList)!=0 + && zTab==0 + ){ + pEList = pNC->uNC.pEList; + assert( pEList!=0 ); + for(j=0; j<pEList->nExpr; j++){ + char *zAs = pEList->a[j].zEName; + if( pEList->a[j].fg.eEName==ENAME_NAME + && sqlite3_stricmp(zAs, zCol)==0 + ){ + Expr *pOrig; + assert( pExpr->pLeft==0 && pExpr->pRight==0 ); + assert( ExprUseXList(pExpr)==0 || pExpr->x.pList==0 ); + assert( ExprUseXSelect(pExpr)==0 || pExpr->x.pSelect==0 ); + pOrig = pEList->a[j].pExpr; + if( (pNC->ncFlags&NC_AllowAgg)==0 && ExprHasProperty(pOrig, EP_Agg) ){ + sqlite3ErrorMsg(pParse, "misuse of aliased aggregate %s", zAs); + return WRC_Abort; + } + if( ExprHasProperty(pOrig, EP_Win) + && ((pNC->ncFlags&NC_AllowWin)==0 || pNC!=pTopNC ) + ){ + sqlite3ErrorMsg(pParse, "misuse of aliased window function %s",zAs); + return WRC_Abort; + } + if( sqlite3ExprVectorSize(pOrig)!=1 ){ + sqlite3ErrorMsg(pParse, "row value misused"); + return WRC_Abort; + } + resolveAlias(pParse, pEList, j, pExpr, nSubquery); + cnt = 1; + pMatch = 0; + assert( zTab==0 && zDb==0 ); + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenRemap(pParse, 0, (void*)pExpr); + } + goto lookupname_end; + } + } + } + + /* Advance to the next name context. The loop will exit when either + ** we have a match (cnt>0) or when we run out of name contexts. + */ + if( cnt ) break; + pNC = pNC->pNext; + nSubquery++; + }while( pNC ); + + + /* + ** If X and Y are NULL (in other words if only the column name Z is + ** supplied) and the value of Z is enclosed in double-quotes, then + ** Z is a string literal if it doesn't match any column names. In that + ** case, we need to return right away and not make any changes to + ** pExpr. + ** + ** Because no reference was made to outer contexts, the pNC->nRef + ** fields are not changed in any context. + */ + if( cnt==0 && zTab==0 ){ + assert( pExpr->op==TK_ID ); + if( ExprHasProperty(pExpr,EP_DblQuoted) + && areDoubleQuotedStringsEnabled(db, pTopNC) + ){ + /* If a double-quoted identifier does not match any known column name, + ** then treat it as a string. + ** + ** This hack was added in the early days of SQLite in a misguided attempt + ** to be compatible with MySQL 3.x, which used double-quotes for strings. + ** I now sorely regret putting in this hack. The effect of this hack is + ** that misspelled identifier names are silently converted into strings + ** rather than causing an error, to the frustration of countless + ** programmers. To all those frustrated programmers, my apologies. + ** + ** Someday, I hope to get rid of this hack. Unfortunately there is + ** a huge amount of legacy SQL that uses it. So for now, we just + ** issue a warning. + */ + sqlite3_log(SQLITE_WARNING, + "double-quoted string literal: \"%w\"", zCol); +#ifdef SQLITE_ENABLE_NORMALIZE + sqlite3VdbeAddDblquoteStr(db, pParse->pVdbe, zCol); +#endif + pExpr->op = TK_STRING; + memset(&pExpr->y, 0, sizeof(pExpr->y)); + return WRC_Prune; + } + if( sqlite3ExprIdToTrueFalse(pExpr) ){ + return WRC_Prune; + } + } + + /* + ** cnt==0 means there was not match. + ** cnt>1 means there were two or more matches. + ** + ** cnt==0 is always an error. cnt>1 is often an error, but might + ** be multiple matches for a NATURAL LEFT JOIN or a LEFT JOIN USING. + */ + assert( pFJMatch==0 || cnt>0 ); + assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) ); + if( cnt!=1 ){ + const char *zErr; + if( pFJMatch ){ + if( pFJMatch->nExpr==cnt-1 ){ + if( ExprHasProperty(pExpr,EP_Leaf) ){ + ExprClearProperty(pExpr,EP_Leaf); + }else{ + sqlite3ExprDelete(db, pExpr->pLeft); + pExpr->pLeft = 0; + sqlite3ExprDelete(db, pExpr->pRight); + pExpr->pRight = 0; + } + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + pExpr->op = TK_FUNCTION; + pExpr->u.zToken = "coalesce"; + pExpr->x.pList = pFJMatch; + cnt = 1; + goto lookupname_end; + }else{ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + } + } + zErr = cnt==0 ? "no such column" : "ambiguous column name"; + if( zDb ){ + sqlite3ErrorMsg(pParse, "%s: %s.%s.%s", zErr, zDb, zTab, zCol); + }else if( zTab ){ + sqlite3ErrorMsg(pParse, "%s: %s.%s", zErr, zTab, zCol); + }else{ + sqlite3ErrorMsg(pParse, "%s: %s", zErr, zCol); + } + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); + pParse->checkSchema = 1; + pTopNC->nNcErr++; + } + assert( pFJMatch==0 ); + + /* Remove all substructure from pExpr */ + if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ + sqlite3ExprDelete(db, pExpr->pLeft); + pExpr->pLeft = 0; + sqlite3ExprDelete(db, pExpr->pRight); + pExpr->pRight = 0; + ExprSetProperty(pExpr, EP_Leaf); + } + + /* If a column from a table in pSrcList is referenced, then record + ** this fact in the pSrcList.a[].colUsed bitmask. Column 0 causes + ** bit 0 to be set. Column 1 sets bit 1. And so forth. Bit 63 is + ** set if the 63rd or any subsequent column is used. + ** + ** The colUsed mask is an optimization used to help determine if an + ** index is a covering index. The correct answer is still obtained + ** if the mask contains extra set bits. However, it is important to + ** avoid setting bits beyond the maximum column number of the table. + ** (See ticket [b92e5e8ec2cdbaa1]). + ** + ** If a generated column is referenced, set bits for every column + ** of the table. + */ + if( pExpr->iColumn>=0 && pMatch!=0 ){ + pMatch->colUsed |= sqlite3ExprColUsed(pExpr); + } + + pExpr->op = eNewExprOp; +lookupname_end: + if( cnt==1 ){ + assert( pNC!=0 ); +#ifndef SQLITE_OMIT_AUTHORIZATION + if( pParse->db->xAuth + && (pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER) + ){ + sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList); + } +#endif + /* Increment the nRef value on all name contexts from TopNC up to + ** the point where the name matched. */ + for(;;){ + assert( pTopNC!=0 ); + pTopNC->nRef++; + if( pTopNC==pNC ) break; + pTopNC = pTopNC->pNext; + } + return WRC_Prune; + } else { + return WRC_Abort; + } +} + +/* +** Allocate and return a pointer to an expression to load the column iCol +** from datasource iSrc in SrcList pSrc. +*/ +SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSrc, int iCol){ + Expr *p = sqlite3ExprAlloc(db, TK_COLUMN, 0, 0); + if( p ){ + SrcItem *pItem = &pSrc->a[iSrc]; + Table *pTab; + assert( ExprUseYTab(p) ); + pTab = p->y.pTab = pItem->pTab; + p->iTable = pItem->iCursor; + if( p->y.pTab->iPKey==iCol ){ + p->iColumn = -1; + }else{ + p->iColumn = (ynVar)iCol; + if( (pTab->tabFlags & TF_HasGenerated)!=0 + && (pTab->aCol[iCol].colFlags & COLFLAG_GENERATED)!=0 + ){ + testcase( pTab->nCol==63 ); + testcase( pTab->nCol==64 ); + pItem->colUsed = pTab->nCol>=64 ? ALLBITS : MASKBIT(pTab->nCol)-1; + }else{ + testcase( iCol==BMS ); + testcase( iCol==BMS-1 ); + pItem->colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol); + } + } + } + return p; +} + +/* +** Report an error that an expression is not valid for some set of +** pNC->ncFlags values determined by validMask. +** +** static void notValid( +** Parse *pParse, // Leave error message here +** NameContext *pNC, // The name context +** const char *zMsg, // Type of error +** int validMask, // Set of contexts for which prohibited +** Expr *pExpr // Invalidate this expression on error +** ){...} +** +** As an optimization, since the conditional is almost always false +** (because errors are rare), the conditional is moved outside of the +** function call using a macro. +*/ +static void notValidImpl( + Parse *pParse, /* Leave error message here */ + NameContext *pNC, /* The name context */ + const char *zMsg, /* Type of error */ + Expr *pExpr, /* Invalidate this expression on error */ + Expr *pError /* Associate error with this expression */ +){ + const char *zIn = "partial index WHERE clauses"; + if( pNC->ncFlags & NC_IdxExpr ) zIn = "index expressions"; +#ifndef SQLITE_OMIT_CHECK + else if( pNC->ncFlags & NC_IsCheck ) zIn = "CHECK constraints"; +#endif +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + else if( pNC->ncFlags & NC_GenCol ) zIn = "generated columns"; +#endif + sqlite3ErrorMsg(pParse, "%s prohibited in %s", zMsg, zIn); + if( pExpr ) pExpr->op = TK_NULL; + sqlite3RecordErrorOffsetOfExpr(pParse->db, pError); +} +#define sqlite3ResolveNotValid(P,N,M,X,E,R) \ + assert( ((X)&~(NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol))==0 ); \ + if( ((N)->ncFlags & (X))!=0 ) notValidImpl(P,N,M,E,R); + +/* +** Expression p should encode a floating point value between 1.0 and 0.0. +** Return 1024 times this value. Or return -1 if p is not a floating point +** value between 1.0 and 0.0. +*/ +static int exprProbability(Expr *p){ + double r = -1.0; + if( p->op!=TK_FLOAT ) return -1; + assert( !ExprHasProperty(p, EP_IntValue) ); + sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8); + assert( r>=0.0 ); + if( r>1.0 ) return -1; + return (int)(r*134217728.0); +} + +/* +** This routine is callback for sqlite3WalkExpr(). +** +** Resolve symbolic names into TK_COLUMN operators for the current +** node in the expression tree. Return 0 to continue the search down +** the tree or 2 to abort the tree walk. +** +** This routine also does error checking and name resolution for +** function names. The operator for aggregate functions is changed +** to TK_AGG_FUNCTION. +*/ +static int resolveExprStep(Walker *pWalker, Expr *pExpr){ + NameContext *pNC; + Parse *pParse; + + pNC = pWalker->u.pNC; + assert( pNC!=0 ); + pParse = pNC->pParse; + assert( pParse==pWalker->pParse ); + +#ifndef NDEBUG + if( pNC->pSrcList && pNC->pSrcList->nAlloc>0 ){ + SrcList *pSrcList = pNC->pSrcList; + int i; + for(i=0; i<pNC->pSrcList->nSrc; i++){ + assert( pSrcList->a[i].iCursor>=0 && pSrcList->a[i].iCursor<pParse->nTab); + } + } +#endif + switch( pExpr->op ){ + + /* The special operator TK_ROW means use the rowid for the first + ** column in the FROM clause. This is used by the LIMIT and ORDER BY + ** clause processing on UPDATE and DELETE statements, and by + ** UPDATE ... FROM statement processing. + */ + case TK_ROW: { + SrcList *pSrcList = pNC->pSrcList; + SrcItem *pItem; + assert( pSrcList && pSrcList->nSrc>=1 ); + pItem = pSrcList->a; + pExpr->op = TK_COLUMN; + assert( ExprUseYTab(pExpr) ); + pExpr->y.pTab = pItem->pTab; + pExpr->iTable = pItem->iCursor; + pExpr->iColumn--; + pExpr->affExpr = SQLITE_AFF_INTEGER; + break; + } + + /* An optimization: Attempt to convert + ** + ** "expr IS NOT NULL" --> "TRUE" + ** "expr IS NULL" --> "FALSE" + ** + ** if we can prove that "expr" is never NULL. Call this the + ** "NOT NULL strength reduction optimization". + ** + ** If this optimization occurs, also restore the NameContext ref-counts + ** to the state they where in before the "column" LHS expression was + ** resolved. This prevents "column" from being counted as having been + ** referenced, which might prevent a SELECT from being erroneously + ** marked as correlated. + */ + case TK_NOTNULL: + case TK_ISNULL: { + int anRef[8]; + NameContext *p; + int i; + for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){ + anRef[i] = p->nRef; + } + sqlite3WalkExpr(pWalker, pExpr->pLeft); + if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){ + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + pExpr->u.iValue = (pExpr->op==TK_NOTNULL); + pExpr->flags |= EP_IntValue; + pExpr->op = TK_INTEGER; + + for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){ + p->nRef = anRef[i]; + } + sqlite3ExprDelete(pParse->db, pExpr->pLeft); + pExpr->pLeft = 0; + } + return WRC_Prune; + } + + /* A column name: ID + ** Or table name and column name: ID.ID + ** Or a database, table and column: ID.ID.ID + ** + ** The TK_ID and TK_OUT cases are combined so that there will only + ** be one call to lookupName(). Then the compiler will in-line + ** lookupName() for a size reduction and performance increase. + */ + case TK_ID: + case TK_DOT: { + const char *zColumn; + const char *zTable; + const char *zDb; + Expr *pRight; + + if( pExpr->op==TK_ID ){ + zDb = 0; + zTable = 0; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + zColumn = pExpr->u.zToken; + }else{ + Expr *pLeft = pExpr->pLeft; + testcase( pNC->ncFlags & NC_IdxExpr ); + testcase( pNC->ncFlags & NC_GenCol ); + sqlite3ResolveNotValid(pParse, pNC, "the \".\" operator", + NC_IdxExpr|NC_GenCol, 0, pExpr); + pRight = pExpr->pRight; + if( pRight->op==TK_ID ){ + zDb = 0; + }else{ + assert( pRight->op==TK_DOT ); + assert( !ExprHasProperty(pRight, EP_IntValue) ); + zDb = pLeft->u.zToken; + pLeft = pRight->pLeft; + pRight = pRight->pRight; + } + assert( ExprUseUToken(pLeft) && ExprUseUToken(pRight) ); + zTable = pLeft->u.zToken; + zColumn = pRight->u.zToken; + assert( ExprUseYTab(pExpr) ); + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenRemap(pParse, (void*)pExpr, (void*)pRight); + sqlite3RenameTokenRemap(pParse, (void*)&pExpr->y.pTab, (void*)pLeft); + } + } + return lookupName(pParse, zDb, zTable, zColumn, pNC, pExpr); + } + + /* Resolve function names + */ + case TK_FUNCTION: { + ExprList *pList = pExpr->x.pList; /* The argument list */ + int n = pList ? pList->nExpr : 0; /* Number of arguments */ + int no_such_func = 0; /* True if no such function exists */ + int wrong_num_args = 0; /* True if wrong number of arguments */ + int is_agg = 0; /* True if is an aggregate function */ + const char *zId; /* The function name. */ + FuncDef *pDef; /* Information about the function */ + u8 enc = ENC(pParse->db); /* The database encoding */ + int savedAllowFlags = (pNC->ncFlags & (NC_AllowAgg | NC_AllowWin)); +#ifndef SQLITE_OMIT_WINDOWFUNC + Window *pWin = (IsWindowFunc(pExpr) ? pExpr->y.pWin : 0); +#endif + assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) ); + zId = pExpr->u.zToken; + pDef = sqlite3FindFunction(pParse->db, zId, n, enc, 0); + if( pDef==0 ){ + pDef = sqlite3FindFunction(pParse->db, zId, -2, enc, 0); + if( pDef==0 ){ + no_such_func = 1; + }else{ + wrong_num_args = 1; + } + }else{ + is_agg = pDef->xFinalize!=0; + if( pDef->funcFlags & SQLITE_FUNC_UNLIKELY ){ + ExprSetProperty(pExpr, EP_Unlikely); + if( n==2 ){ + pExpr->iTable = exprProbability(pList->a[1].pExpr); + if( pExpr->iTable<0 ){ + sqlite3ErrorMsg(pParse, + "second argument to %#T() must be a " + "constant between 0.0 and 1.0", pExpr); + pNC->nNcErr++; + } + }else{ + /* EVIDENCE-OF: R-61304-29449 The unlikely(X) function is + ** equivalent to likelihood(X, 0.0625). + ** EVIDENCE-OF: R-01283-11636 The unlikely(X) function is + ** short-hand for likelihood(X,0.0625). + ** EVIDENCE-OF: R-36850-34127 The likely(X) function is short-hand + ** for likelihood(X,0.9375). + ** EVIDENCE-OF: R-53436-40973 The likely(X) function is equivalent + ** to likelihood(X,0.9375). */ + /* TUNING: unlikely() probability is 0.0625. likely() is 0.9375 */ + pExpr->iTable = pDef->zName[0]=='u' ? 8388608 : 125829120; + } + } +#ifndef SQLITE_OMIT_AUTHORIZATION + { + int auth = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0,pDef->zName,0); + if( auth!=SQLITE_OK ){ + if( auth==SQLITE_DENY ){ + sqlite3ErrorMsg(pParse, "not authorized to use function: %#T", + pExpr); + pNC->nNcErr++; + } + pExpr->op = TK_NULL; + return WRC_Prune; + } + } +#endif + if( pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG) ){ + /* For the purposes of the EP_ConstFunc flag, date and time + ** functions and other functions that change slowly are considered + ** constant because they are constant for the duration of one query. + ** This allows them to be factored out of inner loops. */ + ExprSetProperty(pExpr,EP_ConstFunc); + } + if( (pDef->funcFlags & SQLITE_FUNC_CONSTANT)==0 ){ + /* Clearly non-deterministic functions like random(), but also + ** date/time functions that use 'now', and other functions like + ** sqlite_version() that might change over time cannot be used + ** in an index or generated column. Curiously, they can be used + ** in a CHECK constraint. SQLServer, MySQL, and PostgreSQL all + ** all this. */ + sqlite3ResolveNotValid(pParse, pNC, "non-deterministic functions", + NC_IdxExpr|NC_PartIdx|NC_GenCol, 0, pExpr); + }else{ + assert( (NC_SelfRef & 0xff)==NC_SelfRef ); /* Must fit in 8 bits */ + pExpr->op2 = pNC->ncFlags & NC_SelfRef; + if( pNC->ncFlags & NC_FromDDL ) ExprSetProperty(pExpr, EP_FromDDL); + } + if( (pDef->funcFlags & SQLITE_FUNC_INTERNAL)!=0 + && pParse->nested==0 + && (pParse->db->mDbFlags & DBFLAG_InternalFunc)==0 + ){ + /* Internal-use-only functions are disallowed unless the + ** SQL is being compiled using sqlite3NestedParse() or + ** the SQLITE_TESTCTRL_INTERNAL_FUNCTIONS test-control has be + ** used to activate internal functions for testing purposes */ + no_such_func = 1; + pDef = 0; + }else + if( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 + && !IN_RENAME_OBJECT + ){ + sqlite3ExprFunctionUsable(pParse, pExpr, pDef); + } + } + + if( 0==IN_RENAME_OBJECT ){ +#ifndef SQLITE_OMIT_WINDOWFUNC + assert( is_agg==0 || (pDef->funcFlags & SQLITE_FUNC_MINMAX) + || (pDef->xValue==0 && pDef->xInverse==0) + || (pDef->xValue && pDef->xInverse && pDef->xSFunc && pDef->xFinalize) + ); + if( pDef && pDef->xValue==0 && pWin ){ + sqlite3ErrorMsg(pParse, + "%#T() may not be used as a window function", pExpr + ); + pNC->nNcErr++; + }else if( + (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) + || (is_agg && (pDef->funcFlags&SQLITE_FUNC_WINDOW) && !pWin) + || (is_agg && pWin && (pNC->ncFlags & NC_AllowWin)==0) + ){ + const char *zType; + if( (pDef->funcFlags & SQLITE_FUNC_WINDOW) || pWin ){ + zType = "window"; + }else{ + zType = "aggregate"; + } + sqlite3ErrorMsg(pParse, "misuse of %s function %#T()",zType,pExpr); + pNC->nNcErr++; + is_agg = 0; + } +#else + if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) ){ + sqlite3ErrorMsg(pParse,"misuse of aggregate function %#T()",pExpr); + pNC->nNcErr++; + is_agg = 0; + } +#endif + else if( no_such_func && pParse->db->init.busy==0 +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + && pParse->explain==0 +#endif + ){ + sqlite3ErrorMsg(pParse, "no such function: %#T", pExpr); + pNC->nNcErr++; + }else if( wrong_num_args ){ + sqlite3ErrorMsg(pParse,"wrong number of arguments to function %#T()", + pExpr); + pNC->nNcErr++; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else if( is_agg==0 && ExprHasProperty(pExpr, EP_WinFunc) ){ + sqlite3ErrorMsg(pParse, + "FILTER may not be used with non-aggregate %#T()", + pExpr + ); + pNC->nNcErr++; + } +#endif + if( is_agg ){ + /* Window functions may not be arguments of aggregate functions. + ** Or arguments of other window functions. But aggregate functions + ** may be arguments for window functions. */ +#ifndef SQLITE_OMIT_WINDOWFUNC + pNC->ncFlags &= ~(NC_AllowWin | (!pWin ? NC_AllowAgg : 0)); +#else + pNC->ncFlags &= ~NC_AllowAgg; +#endif + } + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else if( ExprHasProperty(pExpr, EP_WinFunc) ){ + is_agg = 1; + } +#endif + sqlite3WalkExprList(pWalker, pList); + if( is_agg ){ +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pWin ){ + Select *pSel = pNC->pWinSelect; + assert( pWin==0 || (ExprUseYWin(pExpr) && pWin==pExpr->y.pWin) ); + if( IN_RENAME_OBJECT==0 ){ + sqlite3WindowUpdate(pParse, pSel ? pSel->pWinDefn : 0, pWin, pDef); + if( pParse->db->mallocFailed ) break; + } + sqlite3WalkExprList(pWalker, pWin->pPartition); + sqlite3WalkExprList(pWalker, pWin->pOrderBy); + sqlite3WalkExpr(pWalker, pWin->pFilter); + sqlite3WindowLink(pSel, pWin); + pNC->ncFlags |= NC_HasWin; + }else +#endif /* SQLITE_OMIT_WINDOWFUNC */ + { + NameContext *pNC2; /* For looping up thru outer contexts */ + pExpr->op = TK_AGG_FUNCTION; + pExpr->op2 = 0; +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + sqlite3WalkExpr(pWalker, pExpr->y.pWin->pFilter); + } +#endif + pNC2 = pNC; + while( pNC2 + && sqlite3ReferencesSrcList(pParse, pExpr, pNC2->pSrcList)==0 + ){ + pExpr->op2++; + pNC2 = pNC2->pNext; + } + assert( pDef!=0 || IN_RENAME_OBJECT ); + if( pNC2 && pDef ){ + assert( SQLITE_FUNC_MINMAX==NC_MinMaxAgg ); + assert( SQLITE_FUNC_ANYORDER==NC_OrderAgg ); + testcase( (pDef->funcFlags & SQLITE_FUNC_MINMAX)!=0 ); + testcase( (pDef->funcFlags & SQLITE_FUNC_ANYORDER)!=0 ); + pNC2->ncFlags |= NC_HasAgg + | ((pDef->funcFlags^SQLITE_FUNC_ANYORDER) + & (SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER)); + } + } + pNC->ncFlags |= savedAllowFlags; + } + /* FIX ME: Compute pExpr->affinity based on the expected return + ** type of the function + */ + return WRC_Prune; + } +#ifndef SQLITE_OMIT_SUBQUERY + case TK_SELECT: + case TK_EXISTS: testcase( pExpr->op==TK_EXISTS ); +#endif + case TK_IN: { + testcase( pExpr->op==TK_IN ); + if( ExprUseXSelect(pExpr) ){ + int nRef = pNC->nRef; + testcase( pNC->ncFlags & NC_IsCheck ); + testcase( pNC->ncFlags & NC_PartIdx ); + testcase( pNC->ncFlags & NC_IdxExpr ); + testcase( pNC->ncFlags & NC_GenCol ); + if( pNC->ncFlags & NC_SelfRef ){ + notValidImpl(pParse, pNC, "subqueries", pExpr, pExpr); + }else{ + sqlite3WalkSelect(pWalker, pExpr->x.pSelect); + } + assert( pNC->nRef>=nRef ); + if( nRef!=pNC->nRef ){ + ExprSetProperty(pExpr, EP_VarSelect); + } + pNC->ncFlags |= NC_Subquery; + } + break; + } + case TK_VARIABLE: { + testcase( pNC->ncFlags & NC_IsCheck ); + testcase( pNC->ncFlags & NC_PartIdx ); + testcase( pNC->ncFlags & NC_IdxExpr ); + testcase( pNC->ncFlags & NC_GenCol ); + sqlite3ResolveNotValid(pParse, pNC, "parameters", + NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol, pExpr, pExpr); + break; + } + case TK_IS: + case TK_ISNOT: { + Expr *pRight = sqlite3ExprSkipCollateAndLikely(pExpr->pRight); + assert( !ExprHasProperty(pExpr, EP_Reduced) ); + /* Handle special cases of "x IS TRUE", "x IS FALSE", "x IS NOT TRUE", + ** and "x IS NOT FALSE". */ + if( ALWAYS(pRight) && (pRight->op==TK_ID || pRight->op==TK_TRUEFALSE) ){ + int rc = resolveExprStep(pWalker, pRight); + if( rc==WRC_Abort ) return WRC_Abort; + if( pRight->op==TK_TRUEFALSE ){ + pExpr->op2 = pExpr->op; + pExpr->op = TK_TRUTH; + return WRC_Continue; + } + } + /* no break */ deliberate_fall_through + } + case TK_BETWEEN: + case TK_EQ: + case TK_NE: + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: { + int nLeft, nRight; + if( pParse->db->mallocFailed ) break; + assert( pExpr->pLeft!=0 ); + nLeft = sqlite3ExprVectorSize(pExpr->pLeft); + if( pExpr->op==TK_BETWEEN ){ + assert( ExprUseXList(pExpr) ); + nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[0].pExpr); + if( nRight==nLeft ){ + nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[1].pExpr); + } + }else{ + assert( pExpr->pRight!=0 ); + nRight = sqlite3ExprVectorSize(pExpr->pRight); + } + if( nLeft!=nRight ){ + testcase( pExpr->op==TK_EQ ); + testcase( pExpr->op==TK_NE ); + testcase( pExpr->op==TK_LT ); + testcase( pExpr->op==TK_LE ); + testcase( pExpr->op==TK_GT ); + testcase( pExpr->op==TK_GE ); + testcase( pExpr->op==TK_IS ); + testcase( pExpr->op==TK_ISNOT ); + testcase( pExpr->op==TK_BETWEEN ); + sqlite3ErrorMsg(pParse, "row value misused"); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); + } + break; + } + } + assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); + return pParse->nErr ? WRC_Abort : WRC_Continue; +} + +/* +** pEList is a list of expressions which are really the result set of the +** a SELECT statement. pE is a term in an ORDER BY or GROUP BY clause. +** This routine checks to see if pE is a simple identifier which corresponds +** to the AS-name of one of the terms of the expression list. If it is, +** this routine return an integer between 1 and N where N is the number of +** elements in pEList, corresponding to the matching entry. If there is +** no match, or if pE is not a simple identifier, then this routine +** return 0. +** +** pEList has been resolved. pE has not. +*/ +static int resolveAsName( + Parse *pParse, /* Parsing context for error messages */ + ExprList *pEList, /* List of expressions to scan */ + Expr *pE /* Expression we are trying to match */ +){ + int i; /* Loop counter */ + + UNUSED_PARAMETER(pParse); + + if( pE->op==TK_ID ){ + const char *zCol; + assert( !ExprHasProperty(pE, EP_IntValue) ); + zCol = pE->u.zToken; + for(i=0; i<pEList->nExpr; i++){ + if( pEList->a[i].fg.eEName==ENAME_NAME + && sqlite3_stricmp(pEList->a[i].zEName, zCol)==0 + ){ + return i+1; + } + } + } + return 0; +} + +/* +** pE is a pointer to an expression which is a single term in the +** ORDER BY of a compound SELECT. The expression has not been +** name resolved. +** +** At the point this routine is called, we already know that the +** ORDER BY term is not an integer index into the result set. That +** case is handled by the calling routine. +** +** Attempt to match pE against result set columns in the left-most +** SELECT statement. Return the index i of the matching column, +** as an indication to the caller that it should sort by the i-th column. +** The left-most column is 1. In other words, the value returned is the +** same integer value that would be used in the SQL statement to indicate +** the column. +** +** If there is no match, return 0. Return -1 if an error occurs. +*/ +static int resolveOrderByTermToExprList( + Parse *pParse, /* Parsing context for error messages */ + Select *pSelect, /* The SELECT statement with the ORDER BY clause */ + Expr *pE /* The specific ORDER BY term */ +){ + int i; /* Loop counter */ + ExprList *pEList; /* The columns of the result set */ + NameContext nc; /* Name context for resolving pE */ + sqlite3 *db; /* Database connection */ + int rc; /* Return code from subprocedures */ + u8 savedSuppErr; /* Saved value of db->suppressErr */ + + assert( sqlite3ExprIsInteger(pE, &i)==0 ); + pEList = pSelect->pEList; + + /* Resolve all names in the ORDER BY term expression + */ + memset(&nc, 0, sizeof(nc)); + nc.pParse = pParse; + nc.pSrcList = pSelect->pSrc; + nc.uNC.pEList = pEList; + nc.ncFlags = NC_AllowAgg|NC_UEList|NC_NoSelect; + nc.nNcErr = 0; + db = pParse->db; + savedSuppErr = db->suppressErr; + db->suppressErr = 1; + rc = sqlite3ResolveExprNames(&nc, pE); + db->suppressErr = savedSuppErr; + if( rc ) return 0; + + /* Try to match the ORDER BY expression against an expression + ** in the result set. Return an 1-based index of the matching + ** result-set entry. + */ + for(i=0; i<pEList->nExpr; i++){ + if( sqlite3ExprCompare(0, pEList->a[i].pExpr, pE, -1)<2 ){ + return i+1; + } + } + + /* If no match, return 0. */ + return 0; +} + +/* +** Generate an ORDER BY or GROUP BY term out-of-range error. +*/ +static void resolveOutOfRangeError( + Parse *pParse, /* The error context into which to write the error */ + const char *zType, /* "ORDER" or "GROUP" */ + int i, /* The index (1-based) of the term out of range */ + int mx, /* Largest permissible value of i */ + Expr *pError /* Associate the error with the expression */ +){ + sqlite3ErrorMsg(pParse, + "%r %s BY term out of range - should be " + "between 1 and %d", i, zType, mx); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pError); +} + +/* +** Analyze the ORDER BY clause in a compound SELECT statement. Modify +** each term of the ORDER BY clause is a constant integer between 1 +** and N where N is the number of columns in the compound SELECT. +** +** ORDER BY terms that are already an integer between 1 and N are +** unmodified. ORDER BY terms that are integers outside the range of +** 1 through N generate an error. ORDER BY terms that are expressions +** are matched against result set expressions of compound SELECT +** beginning with the left-most SELECT and working toward the right. +** At the first match, the ORDER BY expression is transformed into +** the integer column number. +** +** Return the number of errors seen. +*/ +static int resolveCompoundOrderBy( + Parse *pParse, /* Parsing context. Leave error messages here */ + Select *pSelect /* The SELECT statement containing the ORDER BY */ +){ + int i; + ExprList *pOrderBy; + ExprList *pEList; + sqlite3 *db; + int moreToDo = 1; + + pOrderBy = pSelect->pOrderBy; + if( pOrderBy==0 ) return 0; + db = pParse->db; + if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){ + sqlite3ErrorMsg(pParse, "too many terms in ORDER BY clause"); + return 1; + } + for(i=0; i<pOrderBy->nExpr; i++){ + pOrderBy->a[i].fg.done = 0; + } + pSelect->pNext = 0; + while( pSelect->pPrior ){ + pSelect->pPrior->pNext = pSelect; + pSelect = pSelect->pPrior; + } + while( pSelect && moreToDo ){ + struct ExprList_item *pItem; + moreToDo = 0; + pEList = pSelect->pEList; + assert( pEList!=0 ); + for(i=0, pItem=pOrderBy->a; i<pOrderBy->nExpr; i++, pItem++){ + int iCol = -1; + Expr *pE, *pDup; + if( pItem->fg.done ) continue; + pE = sqlite3ExprSkipCollateAndLikely(pItem->pExpr); + if( NEVER(pE==0) ) continue; + if( sqlite3ExprIsInteger(pE, &iCol) ){ + if( iCol<=0 || iCol>pEList->nExpr ){ + resolveOutOfRangeError(pParse, "ORDER", i+1, pEList->nExpr, pE); + return 1; + } + }else{ + iCol = resolveAsName(pParse, pEList, pE); + if( iCol==0 ){ + /* Now test if expression pE matches one of the values returned + ** by pSelect. In the usual case this is done by duplicating the + ** expression, resolving any symbols in it, and then comparing + ** it against each expression returned by the SELECT statement. + ** Once the comparisons are finished, the duplicate expression + ** is deleted. + ** + ** If this is running as part of an ALTER TABLE operation and + ** the symbols resolve successfully, also resolve the symbols in the + ** actual expression. This allows the code in alter.c to modify + ** column references within the ORDER BY expression as required. */ + pDup = sqlite3ExprDup(db, pE, 0); + if( !db->mallocFailed ){ + assert(pDup); + iCol = resolveOrderByTermToExprList(pParse, pSelect, pDup); + if( IN_RENAME_OBJECT && iCol>0 ){ + resolveOrderByTermToExprList(pParse, pSelect, pE); + } + } + sqlite3ExprDelete(db, pDup); + } + } + if( iCol>0 ){ + /* Convert the ORDER BY term into an integer column number iCol, + ** taking care to preserve the COLLATE clause if it exists. */ + if( !IN_RENAME_OBJECT ){ + Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); + if( pNew==0 ) return 1; + pNew->flags |= EP_IntValue; + pNew->u.iValue = iCol; + if( pItem->pExpr==pE ){ + pItem->pExpr = pNew; + }else{ + Expr *pParent = pItem->pExpr; + assert( pParent->op==TK_COLLATE ); + while( pParent->pLeft->op==TK_COLLATE ) pParent = pParent->pLeft; + assert( pParent->pLeft==pE ); + pParent->pLeft = pNew; + } + sqlite3ExprDelete(db, pE); + pItem->u.x.iOrderByCol = (u16)iCol; + } + pItem->fg.done = 1; + }else{ + moreToDo = 1; + } + } + pSelect = pSelect->pNext; + } + for(i=0; i<pOrderBy->nExpr; i++){ + if( pOrderBy->a[i].fg.done==0 ){ + sqlite3ErrorMsg(pParse, "%r ORDER BY term does not match any " + "column in the result set", i+1); + return 1; + } + } + return 0; +} + +/* +** Check every term in the ORDER BY or GROUP BY clause pOrderBy of +** the SELECT statement pSelect. If any term is reference to a +** result set expression (as determined by the ExprList.a.u.x.iOrderByCol +** field) then convert that term into a copy of the corresponding result set +** column. +** +** If any errors are detected, add an error message to pParse and +** return non-zero. Return zero if no errors are seen. +*/ +SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy( + Parse *pParse, /* Parsing context. Leave error messages here */ + Select *pSelect, /* The SELECT statement containing the clause */ + ExprList *pOrderBy, /* The ORDER BY or GROUP BY clause to be processed */ + const char *zType /* "ORDER" or "GROUP" */ +){ + int i; + sqlite3 *db = pParse->db; + ExprList *pEList; + struct ExprList_item *pItem; + + if( pOrderBy==0 || pParse->db->mallocFailed || IN_RENAME_OBJECT ) return 0; + if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){ + sqlite3ErrorMsg(pParse, "too many terms in %s BY clause", zType); + return 1; + } + pEList = pSelect->pEList; + assert( pEList!=0 ); /* sqlite3SelectNew() guarantees this */ + for(i=0, pItem=pOrderBy->a; i<pOrderBy->nExpr; i++, pItem++){ + if( pItem->u.x.iOrderByCol ){ + if( pItem->u.x.iOrderByCol>pEList->nExpr ){ + resolveOutOfRangeError(pParse, zType, i+1, pEList->nExpr, 0); + return 1; + } + resolveAlias(pParse, pEList, pItem->u.x.iOrderByCol-1, pItem->pExpr,0); + } + } + return 0; +} + +#ifndef SQLITE_OMIT_WINDOWFUNC +/* +** Walker callback for windowRemoveExprFromSelect(). +*/ +static int resolveRemoveWindowsCb(Walker *pWalker, Expr *pExpr){ + UNUSED_PARAMETER(pWalker); + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + Window *pWin = pExpr->y.pWin; + sqlite3WindowUnlinkFromSelect(pWin); + } + return WRC_Continue; +} + +/* +** Remove any Window objects owned by the expression pExpr from the +** Select.pWin list of Select object pSelect. +*/ +static void windowRemoveExprFromSelect(Select *pSelect, Expr *pExpr){ + if( pSelect->pWin ){ + Walker sWalker; + memset(&sWalker, 0, sizeof(Walker)); + sWalker.xExprCallback = resolveRemoveWindowsCb; + sWalker.u.pSelect = pSelect; + sqlite3WalkExpr(&sWalker, pExpr); + } +} +#else +# define windowRemoveExprFromSelect(a, b) +#endif /* SQLITE_OMIT_WINDOWFUNC */ + +/* +** pOrderBy is an ORDER BY or GROUP BY clause in SELECT statement pSelect. +** The Name context of the SELECT statement is pNC. zType is either +** "ORDER" or "GROUP" depending on which type of clause pOrderBy is. +** +** This routine resolves each term of the clause into an expression. +** If the order-by term is an integer I between 1 and N (where N is the +** number of columns in the result set of the SELECT) then the expression +** in the resolution is a copy of the I-th result-set expression. If +** the order-by term is an identifier that corresponds to the AS-name of +** a result-set expression, then the term resolves to a copy of the +** result-set expression. Otherwise, the expression is resolved in +** the usual way - using sqlite3ResolveExprNames(). +** +** This routine returns the number of errors. If errors occur, then +** an appropriate error message might be left in pParse. (OOM errors +** excepted.) +*/ +static int resolveOrderGroupBy( + NameContext *pNC, /* The name context of the SELECT statement */ + Select *pSelect, /* The SELECT statement holding pOrderBy */ + ExprList *pOrderBy, /* An ORDER BY or GROUP BY clause to resolve */ + const char *zType /* Either "ORDER" or "GROUP", as appropriate */ +){ + int i, j; /* Loop counters */ + int iCol; /* Column number */ + struct ExprList_item *pItem; /* A term of the ORDER BY clause */ + Parse *pParse; /* Parsing context */ + int nResult; /* Number of terms in the result set */ + + assert( pOrderBy!=0 ); + nResult = pSelect->pEList->nExpr; + pParse = pNC->pParse; + for(i=0, pItem=pOrderBy->a; i<pOrderBy->nExpr; i++, pItem++){ + Expr *pE = pItem->pExpr; + Expr *pE2 = sqlite3ExprSkipCollateAndLikely(pE); + if( NEVER(pE2==0) ) continue; + if( zType[0]!='G' ){ + iCol = resolveAsName(pParse, pSelect->pEList, pE2); + if( iCol>0 ){ + /* If an AS-name match is found, mark this ORDER BY column as being + ** a copy of the iCol-th result-set column. The subsequent call to + ** sqlite3ResolveOrderGroupBy() will convert the expression to a + ** copy of the iCol-th result-set expression. */ + pItem->u.x.iOrderByCol = (u16)iCol; + continue; + } + } + if( sqlite3ExprIsInteger(pE2, &iCol) ){ + /* The ORDER BY term is an integer constant. Again, set the column + ** number so that sqlite3ResolveOrderGroupBy() will convert the + ** order-by term to a copy of the result-set expression */ + if( iCol<1 || iCol>0xffff ){ + resolveOutOfRangeError(pParse, zType, i+1, nResult, pE2); + return 1; + } + pItem->u.x.iOrderByCol = (u16)iCol; + continue; + } + + /* Otherwise, treat the ORDER BY term as an ordinary expression */ + pItem->u.x.iOrderByCol = 0; + if( sqlite3ResolveExprNames(pNC, pE) ){ + return 1; + } + for(j=0; j<pSelect->pEList->nExpr; j++){ + if( sqlite3ExprCompare(0, pE, pSelect->pEList->a[j].pExpr, -1)==0 ){ + /* Since this expression is being changed into a reference + ** to an identical expression in the result set, remove all Window + ** objects belonging to the expression from the Select.pWin list. */ + windowRemoveExprFromSelect(pSelect, pE); + pItem->u.x.iOrderByCol = j+1; + } + } + } + return sqlite3ResolveOrderGroupBy(pParse, pSelect, pOrderBy, zType); +} + +/* +** Resolve names in the SELECT statement p and all of its descendants. +*/ +static int resolveSelectStep(Walker *pWalker, Select *p){ + NameContext *pOuterNC; /* Context that contains this SELECT */ + NameContext sNC; /* Name context of this SELECT */ + int isCompound; /* True if p is a compound select */ + int nCompound; /* Number of compound terms processed so far */ + Parse *pParse; /* Parsing context */ + int i; /* Loop counter */ + ExprList *pGroupBy; /* The GROUP BY clause */ + Select *pLeftmost; /* Left-most of SELECT of a compound */ + sqlite3 *db; /* Database connection */ + + + assert( p!=0 ); + if( p->selFlags & SF_Resolved ){ + return WRC_Prune; + } + pOuterNC = pWalker->u.pNC; + pParse = pWalker->pParse; + db = pParse->db; + + /* Normally sqlite3SelectExpand() will be called first and will have + ** already expanded this SELECT. However, if this is a subquery within + ** an expression, sqlite3ResolveExprNames() will be called without a + ** prior call to sqlite3SelectExpand(). When that happens, let + ** sqlite3SelectPrep() do all of the processing for this SELECT. + ** sqlite3SelectPrep() will invoke both sqlite3SelectExpand() and + ** this routine in the correct order. + */ + if( (p->selFlags & SF_Expanded)==0 ){ + sqlite3SelectPrep(pParse, p, pOuterNC); + return pParse->nErr ? WRC_Abort : WRC_Prune; + } + + isCompound = p->pPrior!=0; + nCompound = 0; + pLeftmost = p; + while( p ){ + assert( (p->selFlags & SF_Expanded)!=0 ); + assert( (p->selFlags & SF_Resolved)==0 ); + assert( db->suppressErr==0 ); /* SF_Resolved not set if errors suppressed */ + p->selFlags |= SF_Resolved; + + + /* Resolve the expressions in the LIMIT and OFFSET clauses. These + ** are not allowed to refer to any names, so pass an empty NameContext. + */ + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = pParse; + sNC.pWinSelect = p; + if( sqlite3ResolveExprNames(&sNC, p->pLimit) ){ + return WRC_Abort; + } + + /* If the SF_Converted flags is set, then this Select object was + ** was created by the convertCompoundSelectToSubquery() function. + ** In this case the ORDER BY clause (p->pOrderBy) should be resolved + ** as if it were part of the sub-query, not the parent. This block + ** moves the pOrderBy down to the sub-query. It will be moved back + ** after the names have been resolved. */ + if( p->selFlags & SF_Converted ){ + Select *pSub = p->pSrc->a[0].pSelect; + assert( p->pSrc->nSrc==1 && p->pOrderBy ); + assert( pSub->pPrior && pSub->pOrderBy==0 ); + pSub->pOrderBy = p->pOrderBy; + p->pOrderBy = 0; + } + + /* Recursively resolve names in all subqueries in the FROM clause + */ + for(i=0; i<p->pSrc->nSrc; i++){ + SrcItem *pItem = &p->pSrc->a[i]; + if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){ + int nRef = pOuterNC ? pOuterNC->nRef : 0; + const char *zSavedContext = pParse->zAuthContext; + + if( pItem->zName ) pParse->zAuthContext = pItem->zName; + sqlite3ResolveSelectNames(pParse, pItem->pSelect, pOuterNC); + pParse->zAuthContext = zSavedContext; + if( pParse->nErr ) return WRC_Abort; + assert( db->mallocFailed==0 ); + + /* If the number of references to the outer context changed when + ** expressions in the sub-select were resolved, the sub-select + ** is correlated. It is not required to check the refcount on any + ** but the innermost outer context object, as lookupName() increments + ** the refcount on all contexts between the current one and the + ** context containing the column when it resolves a name. */ + if( pOuterNC ){ + assert( pItem->fg.isCorrelated==0 && pOuterNC->nRef>=nRef ); + pItem->fg.isCorrelated = (pOuterNC->nRef>nRef); + } + } + } + + /* Set up the local name-context to pass to sqlite3ResolveExprNames() to + ** resolve the result-set expression list. + */ + sNC.ncFlags = NC_AllowAgg|NC_AllowWin; + sNC.pSrcList = p->pSrc; + sNC.pNext = pOuterNC; + + /* Resolve names in the result set. */ + if( sqlite3ResolveExprListNames(&sNC, p->pEList) ) return WRC_Abort; + sNC.ncFlags &= ~NC_AllowWin; + + /* If there are no aggregate functions in the result-set, and no GROUP BY + ** expression, do not allow aggregates in any of the other expressions. + */ + assert( (p->selFlags & SF_Aggregate)==0 ); + pGroupBy = p->pGroupBy; + if( pGroupBy || (sNC.ncFlags & NC_HasAgg)!=0 ){ + assert( NC_MinMaxAgg==SF_MinMaxAgg ); + assert( NC_OrderAgg==SF_OrderByReqd ); + p->selFlags |= SF_Aggregate | (sNC.ncFlags&(NC_MinMaxAgg|NC_OrderAgg)); + }else{ + sNC.ncFlags &= ~NC_AllowAgg; + } + + /* Add the output column list to the name-context before parsing the + ** other expressions in the SELECT statement. This is so that + ** expressions in the WHERE clause (etc.) can refer to expressions by + ** aliases in the result set. + ** + ** Minor point: If this is the case, then the expression will be + ** re-evaluated for each reference to it. + */ + assert( (sNC.ncFlags & (NC_UAggInfo|NC_UUpsert|NC_UBaseReg))==0 ); + sNC.uNC.pEList = p->pEList; + sNC.ncFlags |= NC_UEList; + if( p->pHaving ){ + if( (p->selFlags & SF_Aggregate)==0 ){ + sqlite3ErrorMsg(pParse, "HAVING clause on a non-aggregate query"); + return WRC_Abort; + } + if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; + } + if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort; + + /* Resolve names in table-valued-function arguments */ + for(i=0; i<p->pSrc->nSrc; i++){ + SrcItem *pItem = &p->pSrc->a[i]; + if( pItem->fg.isTabFunc + && sqlite3ResolveExprListNames(&sNC, pItem->u1.pFuncArg) + ){ + return WRC_Abort; + } + } + +#ifndef SQLITE_OMIT_WINDOWFUNC + if( IN_RENAME_OBJECT ){ + Window *pWin; + for(pWin=p->pWinDefn; pWin; pWin=pWin->pNextWin){ + if( sqlite3ResolveExprListNames(&sNC, pWin->pOrderBy) + || sqlite3ResolveExprListNames(&sNC, pWin->pPartition) + ){ + return WRC_Abort; + } + } + } +#endif + + /* The ORDER BY and GROUP BY clauses may not refer to terms in + ** outer queries + */ + sNC.pNext = 0; + sNC.ncFlags |= NC_AllowAgg|NC_AllowWin; + + /* If this is a converted compound query, move the ORDER BY clause from + ** the sub-query back to the parent query. At this point each term + ** within the ORDER BY clause has been transformed to an integer value. + ** These integers will be replaced by copies of the corresponding result + ** set expressions by the call to resolveOrderGroupBy() below. */ + if( p->selFlags & SF_Converted ){ + Select *pSub = p->pSrc->a[0].pSelect; + p->pOrderBy = pSub->pOrderBy; + pSub->pOrderBy = 0; + } + + /* Process the ORDER BY clause for singleton SELECT statements. + ** The ORDER BY clause for compounds SELECT statements is handled + ** below, after all of the result-sets for all of the elements of + ** the compound have been resolved. + ** + ** If there is an ORDER BY clause on a term of a compound-select other + ** than the right-most term, then that is a syntax error. But the error + ** is not detected until much later, and so we need to go ahead and + ** resolve those symbols on the incorrect ORDER BY for consistency. + */ + if( p->pOrderBy!=0 + && isCompound<=nCompound /* Defer right-most ORDER BY of a compound */ + && resolveOrderGroupBy(&sNC, p, p->pOrderBy, "ORDER") + ){ + return WRC_Abort; + } + if( db->mallocFailed ){ + return WRC_Abort; + } + sNC.ncFlags &= ~NC_AllowWin; + + /* Resolve the GROUP BY clause. At the same time, make sure + ** the GROUP BY clause does not contain aggregate functions. + */ + if( pGroupBy ){ + struct ExprList_item *pItem; + + if( resolveOrderGroupBy(&sNC, p, pGroupBy, "GROUP") || db->mallocFailed ){ + return WRC_Abort; + } + for(i=0, pItem=pGroupBy->a; i<pGroupBy->nExpr; i++, pItem++){ + if( ExprHasProperty(pItem->pExpr, EP_Agg) ){ + sqlite3ErrorMsg(pParse, "aggregate functions are not allowed in " + "the GROUP BY clause"); + return WRC_Abort; + } + } + } + + /* If this is part of a compound SELECT, check that it has the right + ** number of expressions in the select list. */ + if( p->pNext && p->pEList->nExpr!=p->pNext->pEList->nExpr ){ + sqlite3SelectWrongNumTermsError(pParse, p->pNext); + return WRC_Abort; + } + + /* Advance to the next term of the compound + */ + p = p->pPrior; + nCompound++; + } + + /* Resolve the ORDER BY on a compound SELECT after all terms of + ** the compound have been resolved. + */ + if( isCompound && resolveCompoundOrderBy(pParse, pLeftmost) ){ + return WRC_Abort; + } + + return WRC_Prune; +} + +/* +** This routine walks an expression tree and resolves references to +** table columns and result-set columns. At the same time, do error +** checking on function usage and set a flag if any aggregate functions +** are seen. +** +** To resolve table columns references we look for nodes (or subtrees) of the +** form X.Y.Z or Y.Z or just Z where +** +** X: The name of a database. Ex: "main" or "temp" or +** the symbolic name assigned to an ATTACH-ed database. +** +** Y: The name of a table in a FROM clause. Or in a trigger +** one of the special names "old" or "new". +** +** Z: The name of a column in table Y. +** +** The node at the root of the subtree is modified as follows: +** +** Expr.op Changed to TK_COLUMN +** Expr.pTab Points to the Table object for X.Y +** Expr.iColumn The column index in X.Y. -1 for the rowid. +** Expr.iTable The VDBE cursor number for X.Y +** +** +** To resolve result-set references, look for expression nodes of the +** form Z (with no X and Y prefix) where the Z matches the right-hand +** size of an AS clause in the result-set of a SELECT. The Z expression +** is replaced by a copy of the left-hand side of the result-set expression. +** Table-name and function resolution occurs on the substituted expression +** tree. For example, in: +** +** SELECT a+b AS x, c+d AS y FROM t1 ORDER BY x; +** +** The "x" term of the order by is replaced by "a+b" to render: +** +** SELECT a+b AS x, c+d AS y FROM t1 ORDER BY a+b; +** +** Function calls are checked to make sure that the function is +** defined and that the correct number of arguments are specified. +** If the function is an aggregate function, then the NC_HasAgg flag is +** set and the opcode is changed from TK_FUNCTION to TK_AGG_FUNCTION. +** If an expression contains aggregate functions then the EP_Agg +** property on the expression is set. +** +** An error message is left in pParse if anything is amiss. The number +** if errors is returned. +*/ +SQLITE_PRIVATE int sqlite3ResolveExprNames( + NameContext *pNC, /* Namespace to resolve expressions in. */ + Expr *pExpr /* The expression to be analyzed. */ +){ + int savedHasAgg; + Walker w; + + if( pExpr==0 ) return SQLITE_OK; + savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + w.pParse = pNC->pParse; + w.xExprCallback = resolveExprStep; + w.xSelectCallback = (pNC->ncFlags & NC_NoSelect) ? 0 : resolveSelectStep; + w.xSelectCallback2 = 0; + w.u.pNC = pNC; +#if SQLITE_MAX_EXPR_DEPTH>0 + w.pParse->nHeight += pExpr->nHeight; + if( sqlite3ExprCheckHeight(w.pParse, w.pParse->nHeight) ){ + return SQLITE_ERROR; + } +#endif + assert( pExpr!=0 ); + sqlite3WalkExprNN(&w, pExpr); +#if SQLITE_MAX_EXPR_DEPTH>0 + w.pParse->nHeight -= pExpr->nHeight; +#endif + assert( EP_Agg==NC_HasAgg ); + assert( EP_Win==NC_HasWin ); + testcase( pNC->ncFlags & NC_HasAgg ); + testcase( pNC->ncFlags & NC_HasWin ); + ExprSetProperty(pExpr, pNC->ncFlags & (NC_HasAgg|NC_HasWin) ); + pNC->ncFlags |= savedHasAgg; + return pNC->nNcErr>0 || w.pParse->nErr>0; +} + +/* +** Resolve all names for all expression in an expression list. This is +** just like sqlite3ResolveExprNames() except that it works for an expression +** list rather than a single expression. +*/ +SQLITE_PRIVATE int sqlite3ResolveExprListNames( + NameContext *pNC, /* Namespace to resolve expressions in. */ + ExprList *pList /* The expression list to be analyzed. */ +){ + int i; + int savedHasAgg = 0; + Walker w; + if( pList==0 ) return WRC_Continue; + w.pParse = pNC->pParse; + w.xExprCallback = resolveExprStep; + w.xSelectCallback = resolveSelectStep; + w.xSelectCallback2 = 0; + w.u.pNC = pNC; + savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + for(i=0; i<pList->nExpr; i++){ + Expr *pExpr = pList->a[i].pExpr; + if( pExpr==0 ) continue; +#if SQLITE_MAX_EXPR_DEPTH>0 + w.pParse->nHeight += pExpr->nHeight; + if( sqlite3ExprCheckHeight(w.pParse, w.pParse->nHeight) ){ + return WRC_Abort; + } +#endif + sqlite3WalkExprNN(&w, pExpr); +#if SQLITE_MAX_EXPR_DEPTH>0 + w.pParse->nHeight -= pExpr->nHeight; +#endif + assert( EP_Agg==NC_HasAgg ); + assert( EP_Win==NC_HasWin ); + testcase( pNC->ncFlags & NC_HasAgg ); + testcase( pNC->ncFlags & NC_HasWin ); + if( pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg) ){ + ExprSetProperty(pExpr, pNC->ncFlags & (NC_HasAgg|NC_HasWin) ); + savedHasAgg |= pNC->ncFlags & + (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + } + if( w.pParse->nErr>0 ) return WRC_Abort; + } + pNC->ncFlags |= savedHasAgg; + return WRC_Continue; +} + +/* +** Resolve all names in all expressions of a SELECT and in all +** descendants of the SELECT, including compounds off of p->pPrior, +** subqueries in expressions, and subqueries used as FROM clause +** terms. +** +** See sqlite3ResolveExprNames() for a description of the kinds of +** transformations that occur. +** +** All SELECT statements should have been expanded using +** sqlite3SelectExpand() prior to invoking this routine. +*/ +SQLITE_PRIVATE void sqlite3ResolveSelectNames( + Parse *pParse, /* The parser context */ + Select *p, /* The SELECT statement being coded. */ + NameContext *pOuterNC /* Name context for parent SELECT statement */ +){ + Walker w; + + assert( p!=0 ); + w.xExprCallback = resolveExprStep; + w.xSelectCallback = resolveSelectStep; + w.xSelectCallback2 = 0; + w.pParse = pParse; + w.u.pNC = pOuterNC; + sqlite3WalkSelect(&w, p); +} + +/* +** Resolve names in expressions that can only reference a single table +** or which cannot reference any tables at all. Examples: +** +** "type" flag +** ------------ +** (1) CHECK constraints NC_IsCheck +** (2) WHERE clauses on partial indices NC_PartIdx +** (3) Expressions in indexes on expressions NC_IdxExpr +** (4) Expression arguments to VACUUM INTO. 0 +** (5) GENERATED ALWAYS as expressions NC_GenCol +** +** In all cases except (4), the Expr.iTable value for Expr.op==TK_COLUMN +** nodes of the expression is set to -1 and the Expr.iColumn value is +** set to the column number. In case (4), TK_COLUMN nodes cause an error. +** +** Any errors cause an error message to be set in pParse. +*/ +SQLITE_PRIVATE int sqlite3ResolveSelfReference( + Parse *pParse, /* Parsing context */ + Table *pTab, /* The table being referenced, or NULL */ + int type, /* NC_IsCheck, NC_PartIdx, NC_IdxExpr, NC_GenCol, or 0 */ + Expr *pExpr, /* Expression to resolve. May be NULL. */ + ExprList *pList /* Expression list to resolve. May be NULL. */ +){ + SrcList sSrc; /* Fake SrcList for pParse->pNewTable */ + NameContext sNC; /* Name context for pParse->pNewTable */ + int rc; + + assert( type==0 || pTab!=0 ); + assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr + || type==NC_GenCol || pTab==0 ); + memset(&sNC, 0, sizeof(sNC)); + memset(&sSrc, 0, sizeof(sSrc)); + if( pTab ){ + sSrc.nSrc = 1; + sSrc.a[0].zName = pTab->zName; + sSrc.a[0].pTab = pTab; + sSrc.a[0].iCursor = -1; + if( pTab->pSchema!=pParse->db->aDb[1].pSchema ){ + /* Cause EP_FromDDL to be set on TK_FUNCTION nodes of non-TEMP + ** schema elements */ + type |= NC_FromDDL; + } + } + sNC.pParse = pParse; + sNC.pSrcList = &sSrc; + sNC.ncFlags = type | NC_IsDDL; + if( (rc = sqlite3ResolveExprNames(&sNC, pExpr))!=SQLITE_OK ) return rc; + if( pList ) rc = sqlite3ResolveExprListNames(&sNC, pList); + return rc; +} + +/************** End of resolve.c *********************************************/ +/************** Begin file expr.c ********************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains routines used for analyzing expressions and +** for generating VDBE code that evaluates expressions in SQLite. +*/ +/* #include "sqliteInt.h" */ + +/* Forward declarations */ +static void exprCodeBetween(Parse*,Expr*,int,void(*)(Parse*,Expr*,int,int),int); +static int exprCodeVector(Parse *pParse, Expr *p, int *piToFree); + +/* +** Return the affinity character for a single column of a table. +*/ +SQLITE_PRIVATE char sqlite3TableColumnAffinity(const Table *pTab, int iCol){ + if( iCol<0 || NEVER(iCol>=pTab->nCol) ) return SQLITE_AFF_INTEGER; + return pTab->aCol[iCol].affinity; +} + +/* +** Return the 'affinity' of the expression pExpr if any. +** +** If pExpr is a column, a reference to a column via an 'AS' alias, +** or a sub-select with a column as the return value, then the +** affinity of that column is returned. Otherwise, 0x00 is returned, +** indicating no affinity for the expression. +** +** i.e. the WHERE clause expressions in the following statements all +** have an affinity: +** +** CREATE TABLE t1(a); +** SELECT * FROM t1 WHERE a; +** SELECT a AS b FROM t1 WHERE b; +** SELECT * FROM t1 WHERE (select a from t1); +*/ +SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){ + int op; + op = pExpr->op; + while( 1 /* exit-by-break */ ){ + if( op==TK_COLUMN || (op==TK_AGG_COLUMN && pExpr->y.pTab!=0) ){ + assert( ExprUseYTab(pExpr) ); + assert( pExpr->y.pTab!=0 ); + return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); + } + if( op==TK_SELECT ){ + assert( ExprUseXSelect(pExpr) ); + assert( pExpr->x.pSelect!=0 ); + assert( pExpr->x.pSelect->pEList!=0 ); + assert( pExpr->x.pSelect->pEList->a[0].pExpr!=0 ); + return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); + } +#ifndef SQLITE_OMIT_CAST + if( op==TK_CAST ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + return sqlite3AffinityType(pExpr->u.zToken, 0); + } +#endif + if( op==TK_SELECT_COLUMN ){ + assert( pExpr->pLeft!=0 && ExprUseXSelect(pExpr->pLeft) ); + assert( pExpr->iColumn < pExpr->iTable ); + assert( pExpr->iColumn >= 0 ); + assert( pExpr->iTable==pExpr->pLeft->x.pSelect->pEList->nExpr ); + return sqlite3ExprAffinity( + pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr + ); + } + if( op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); + return sqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr); + } + if( ExprHasProperty(pExpr, EP_Skip|EP_IfNullRow) ){ + assert( pExpr->op==TK_COLLATE + || pExpr->op==TK_IF_NULL_ROW + || (pExpr->op==TK_REGISTER && pExpr->op2==TK_IF_NULL_ROW) ); + pExpr = pExpr->pLeft; + op = pExpr->op; + continue; + } + if( op!=TK_REGISTER || (op = pExpr->op2)==TK_REGISTER ) break; + } + return pExpr->affExpr; +} + +/* +** Make a guess at all the possible datatypes of the result that could +** be returned by an expression. Return a bitmask indicating the answer: +** +** 0x01 Numeric +** 0x02 Text +** 0x04 Blob +** +** If the expression must return NULL, then 0x00 is returned. +*/ +SQLITE_PRIVATE int sqlite3ExprDataType(const Expr *pExpr){ + while( pExpr ){ + switch( pExpr->op ){ + case TK_COLLATE: + case TK_IF_NULL_ROW: + case TK_UPLUS: { + pExpr = pExpr->pLeft; + break; + } + case TK_NULL: { + pExpr = 0; + break; + } + case TK_STRING: { + return 0x02; + } + case TK_BLOB: { + return 0x04; + } + case TK_CONCAT: { + return 0x06; + } + case TK_VARIABLE: + case TK_AGG_FUNCTION: + case TK_FUNCTION: { + return 0x07; + } + case TK_COLUMN: + case TK_AGG_COLUMN: + case TK_SELECT: + case TK_CAST: + case TK_SELECT_COLUMN: + case TK_VECTOR: { + int aff = sqlite3ExprAffinity(pExpr); + if( aff>=SQLITE_AFF_NUMERIC ) return 0x05; + if( aff==SQLITE_AFF_TEXT ) return 0x06; + return 0x07; + } + case TK_CASE: { + int res = 0; + int ii; + ExprList *pList = pExpr->x.pList; + assert( ExprUseXList(pExpr) && pList!=0 ); + assert( pList->nExpr > 0); + for(ii=1; ii<pList->nExpr; ii+=2){ + res |= sqlite3ExprDataType(pList->a[ii].pExpr); + } + if( pList->nExpr % 2 ){ + res |= sqlite3ExprDataType(pList->a[pList->nExpr-1].pExpr); + } + return res; + } + default: { + return 0x01; + } + } /* End of switch(op) */ + } /* End of while(pExpr) */ + return 0x00; +} + +/* +** Set the collating sequence for expression pExpr to be the collating +** sequence named by pToken. Return a pointer to a new Expr node that +** implements the COLLATE operator. +** +** If a memory allocation error occurs, that fact is recorded in pParse->db +** and the pExpr parameter is returned unchanged. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken( + const Parse *pParse, /* Parsing context */ + Expr *pExpr, /* Add the "COLLATE" clause to this expression */ + const Token *pCollName, /* Name of collating sequence */ + int dequote /* True to dequote pCollName */ +){ + if( pCollName->n>0 ){ + Expr *pNew = sqlite3ExprAlloc(pParse->db, TK_COLLATE, pCollName, dequote); + if( pNew ){ + pNew->pLeft = pExpr; + pNew->flags |= EP_Collate|EP_Skip; + pExpr = pNew; + } + } + return pExpr; +} +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString( + const Parse *pParse, /* Parsing context */ + Expr *pExpr, /* Add the "COLLATE" clause to this expression */ + const char *zC /* The collating sequence name */ +){ + Token s; + assert( zC!=0 ); + sqlite3TokenInit(&s, (char*)zC); + return sqlite3ExprAddCollateToken(pParse, pExpr, &s, 0); +} + +/* +** Skip over any TK_COLLATE operators. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr *pExpr){ + while( pExpr && ExprHasProperty(pExpr, EP_Skip) ){ + assert( pExpr->op==TK_COLLATE ); + pExpr = pExpr->pLeft; + } + return pExpr; +} + +/* +** Skip over any TK_COLLATE operators and/or any unlikely() +** or likelihood() or likely() functions at the root of an +** expression. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr *pExpr){ + while( pExpr && ExprHasProperty(pExpr, EP_Skip|EP_Unlikely) ){ + if( ExprHasProperty(pExpr, EP_Unlikely) ){ + assert( ExprUseXList(pExpr) ); + assert( pExpr->x.pList->nExpr>0 ); + assert( pExpr->op==TK_FUNCTION ); + pExpr = pExpr->x.pList->a[0].pExpr; + }else{ + assert( pExpr->op==TK_COLLATE ); + pExpr = pExpr->pLeft; + } + } + return pExpr; +} + +/* +** Return the collation sequence for the expression pExpr. If +** there is no defined collating sequence, return NULL. +** +** See also: sqlite3ExprNNCollSeq() +** +** The sqlite3ExprNNCollSeq() works the same exact that it returns the +** default collation if pExpr has no defined collation. +** +** The collating sequence might be determined by a COLLATE operator +** or by the presence of a column with a defined collating sequence. +** COLLATE operators take first precedence. Left operands take +** precedence over right operands. +*/ +SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ + sqlite3 *db = pParse->db; + CollSeq *pColl = 0; + const Expr *p = pExpr; + while( p ){ + int op = p->op; + if( op==TK_REGISTER ) op = p->op2; + if( (op==TK_AGG_COLUMN && p->y.pTab!=0) + || op==TK_COLUMN || op==TK_TRIGGER + ){ + int j; + assert( ExprUseYTab(p) ); + assert( p->y.pTab!=0 ); + if( (j = p->iColumn)>=0 ){ + const char *zColl = sqlite3ColumnColl(&p->y.pTab->aCol[j]); + pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); + } + break; + } + if( op==TK_CAST || op==TK_UPLUS ){ + p = p->pLeft; + continue; + } + if( op==TK_VECTOR ){ + assert( ExprUseXList(p) ); + p = p->x.pList->a[0].pExpr; + continue; + } + if( op==TK_COLLATE ){ + assert( !ExprHasProperty(p, EP_IntValue) ); + pColl = sqlite3GetCollSeq(pParse, ENC(db), 0, p->u.zToken); + break; + } + if( p->flags & EP_Collate ){ + if( p->pLeft && (p->pLeft->flags & EP_Collate)!=0 ){ + p = p->pLeft; + }else{ + Expr *pNext = p->pRight; + /* The Expr.x union is never used at the same time as Expr.pRight */ + assert( !ExprUseXList(p) || p->x.pList==0 || p->pRight==0 ); + if( ExprUseXList(p) && p->x.pList!=0 && !db->mallocFailed ){ + int i; + for(i=0; i<p->x.pList->nExpr; i++){ + if( ExprHasProperty(p->x.pList->a[i].pExpr, EP_Collate) ){ + pNext = p->x.pList->a[i].pExpr; + break; + } + } + } + p = pNext; + } + }else{ + break; + } + } + if( sqlite3CheckCollSeq(pParse, pColl) ){ + pColl = 0; + } + return pColl; +} + +/* +** Return the collation sequence for the expression pExpr. If +** there is no defined collating sequence, return a pointer to the +** default collation sequence. +** +** See also: sqlite3ExprCollSeq() +** +** The sqlite3ExprCollSeq() routine works the same except that it +** returns NULL if there is no defined collation. +*/ +SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr){ + CollSeq *p = sqlite3ExprCollSeq(pParse, pExpr); + if( p==0 ) p = pParse->db->pDfltColl; + assert( p!=0 ); + return p; +} + +/* +** Return TRUE if the two expressions have equivalent collating sequences. +*/ +SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, const Expr *pE1, const Expr *pE2){ + CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pE1); + CollSeq *pColl2 = sqlite3ExprNNCollSeq(pParse, pE2); + return sqlite3StrICmp(pColl1->zName, pColl2->zName)==0; +} + +/* +** pExpr is an operand of a comparison operator. aff2 is the +** type affinity of the other operand. This routine returns the +** type affinity that should be used for the comparison operator. +*/ +SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2){ + char aff1 = sqlite3ExprAffinity(pExpr); + if( aff1>SQLITE_AFF_NONE && aff2>SQLITE_AFF_NONE ){ + /* Both sides of the comparison are columns. If one has numeric + ** affinity, use that. Otherwise use no affinity. + */ + if( sqlite3IsNumericAffinity(aff1) || sqlite3IsNumericAffinity(aff2) ){ + return SQLITE_AFF_NUMERIC; + }else{ + return SQLITE_AFF_BLOB; + } + }else{ + /* One side is a column, the other is not. Use the columns affinity. */ + assert( aff1<=SQLITE_AFF_NONE || aff2<=SQLITE_AFF_NONE ); + return (aff1<=SQLITE_AFF_NONE ? aff2 : aff1) | SQLITE_AFF_NONE; + } +} + +/* +** pExpr is a comparison operator. Return the type affinity that should +** be applied to both operands prior to doing the comparison. +*/ +static char comparisonAffinity(const Expr *pExpr){ + char aff; + assert( pExpr->op==TK_EQ || pExpr->op==TK_IN || pExpr->op==TK_LT || + pExpr->op==TK_GT || pExpr->op==TK_GE || pExpr->op==TK_LE || + pExpr->op==TK_NE || pExpr->op==TK_IS || pExpr->op==TK_ISNOT ); + assert( pExpr->pLeft ); + aff = sqlite3ExprAffinity(pExpr->pLeft); + if( pExpr->pRight ){ + aff = sqlite3CompareAffinity(pExpr->pRight, aff); + }else if( ExprUseXSelect(pExpr) ){ + aff = sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr, aff); + }else if( aff==0 ){ + aff = SQLITE_AFF_BLOB; + } + return aff; +} + +/* +** pExpr is a comparison expression, eg. '=', '<', IN(...) etc. +** idx_affinity is the affinity of an indexed column. Return true +** if the index with affinity idx_affinity may be used to implement +** the comparison in pExpr. +*/ +SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity){ + char aff = comparisonAffinity(pExpr); + if( aff<SQLITE_AFF_TEXT ){ + return 1; + } + if( aff==SQLITE_AFF_TEXT ){ + return idx_affinity==SQLITE_AFF_TEXT; + } + return sqlite3IsNumericAffinity(idx_affinity); +} + +/* +** Return the P5 value that should be used for a binary comparison +** opcode (OP_Eq, OP_Ge etc.) used to compare pExpr1 and pExpr2. +*/ +static u8 binaryCompareP5( + const Expr *pExpr1, /* Left operand */ + const Expr *pExpr2, /* Right operand */ + int jumpIfNull /* Extra flags added to P5 */ +){ + u8 aff = (char)sqlite3ExprAffinity(pExpr2); + aff = (u8)sqlite3CompareAffinity(pExpr1, aff) | (u8)jumpIfNull; + return aff; +} + +/* +** Return a pointer to the collation sequence that should be used by +** a binary comparison operator comparing pLeft and pRight. +** +** If the left hand expression has a collating sequence type, then it is +** used. Otherwise the collation sequence for the right hand expression +** is used, or the default (BINARY) if neither expression has a collating +** type. +** +** Argument pRight (but not pLeft) may be a null pointer. In this case, +** it is not considered. +*/ +SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq( + Parse *pParse, + const Expr *pLeft, + const Expr *pRight +){ + CollSeq *pColl; + assert( pLeft ); + if( pLeft->flags & EP_Collate ){ + pColl = sqlite3ExprCollSeq(pParse, pLeft); + }else if( pRight && (pRight->flags & EP_Collate)!=0 ){ + pColl = sqlite3ExprCollSeq(pParse, pRight); + }else{ + pColl = sqlite3ExprCollSeq(pParse, pLeft); + if( !pColl ){ + pColl = sqlite3ExprCollSeq(pParse, pRight); + } + } + return pColl; +} + +/* Expression p is a comparison operator. Return a collation sequence +** appropriate for the comparison operator. +** +** This is normally just a wrapper around sqlite3BinaryCompareCollSeq(). +** However, if the OP_Commuted flag is set, then the order of the operands +** is reversed in the sqlite3BinaryCompareCollSeq() call so that the +** correct collating sequence is found. +*/ +SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse *pParse, const Expr *p){ + if( ExprHasProperty(p, EP_Commuted) ){ + return sqlite3BinaryCompareCollSeq(pParse, p->pRight, p->pLeft); + }else{ + return sqlite3BinaryCompareCollSeq(pParse, p->pLeft, p->pRight); + } +} + +/* +** Generate code for a comparison operator. +*/ +static int codeCompare( + Parse *pParse, /* The parsing (and code generating) context */ + Expr *pLeft, /* The left operand */ + Expr *pRight, /* The right operand */ + int opcode, /* The comparison opcode */ + int in1, int in2, /* Register holding operands */ + int dest, /* Jump here if true. */ + int jumpIfNull, /* If true, jump if either operand is NULL */ + int isCommuted /* The comparison has been commuted */ +){ + int p5; + int addr; + CollSeq *p4; + + if( pParse->nErr ) return 0; + if( isCommuted ){ + p4 = sqlite3BinaryCompareCollSeq(pParse, pRight, pLeft); + }else{ + p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight); + } + p5 = binaryCompareP5(pLeft, pRight, jumpIfNull); + addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1, + (void*)p4, P4_COLLSEQ); + sqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5); + return addr; +} + +/* +** Return true if expression pExpr is a vector, or false otherwise. +** +** A vector is defined as any expression that results in two or more +** columns of result. Every TK_VECTOR node is an vector because the +** parser will not generate a TK_VECTOR with fewer than two entries. +** But a TK_SELECT might be either a vector or a scalar. It is only +** considered a vector if it has two or more result columns. +*/ +SQLITE_PRIVATE int sqlite3ExprIsVector(const Expr *pExpr){ + return sqlite3ExprVectorSize(pExpr)>1; +} + +/* +** If the expression passed as the only argument is of type TK_VECTOR +** return the number of expressions in the vector. Or, if the expression +** is a sub-select, return the number of columns in the sub-select. For +** any other type of expression, return 1. +*/ +SQLITE_PRIVATE int sqlite3ExprVectorSize(const Expr *pExpr){ + u8 op = pExpr->op; + if( op==TK_REGISTER ) op = pExpr->op2; + if( op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); + return pExpr->x.pList->nExpr; + }else if( op==TK_SELECT ){ + assert( ExprUseXSelect(pExpr) ); + return pExpr->x.pSelect->pEList->nExpr; + }else{ + return 1; + } +} + +/* +** Return a pointer to a subexpression of pVector that is the i-th +** column of the vector (numbered starting with 0). The caller must +** ensure that i is within range. +** +** If pVector is really a scalar (and "scalar" here includes subqueries +** that return a single column!) then return pVector unmodified. +** +** pVector retains ownership of the returned subexpression. +** +** If the vector is a (SELECT ...) then the expression returned is +** just the expression for the i-th term of the result set, and may +** not be ready for evaluation because the table cursor has not yet +** been positioned. +*/ +SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ + assert( i<sqlite3ExprVectorSize(pVector) || pVector->op==TK_ERROR ); + if( sqlite3ExprIsVector(pVector) ){ + assert( pVector->op2==0 || pVector->op==TK_REGISTER ); + if( pVector->op==TK_SELECT || pVector->op2==TK_SELECT ){ + assert( ExprUseXSelect(pVector) ); + return pVector->x.pSelect->pEList->a[i].pExpr; + }else{ + assert( ExprUseXList(pVector) ); + return pVector->x.pList->a[i].pExpr; + } + } + return pVector; +} + +/* +** Compute and return a new Expr object which when passed to +** sqlite3ExprCode() will generate all necessary code to compute +** the iField-th column of the vector expression pVector. +** +** It is ok for pVector to be a scalar (as long as iField==0). +** In that case, this routine works like sqlite3ExprDup(). +** +** The caller owns the returned Expr object and is responsible for +** ensuring that the returned value eventually gets freed. +** +** The caller retains ownership of pVector. If pVector is a TK_SELECT, +** then the returned object will reference pVector and so pVector must remain +** valid for the life of the returned object. If pVector is a TK_VECTOR +** or a scalar expression, then it can be deleted as soon as this routine +** returns. +** +** A trick to cause a TK_SELECT pVector to be deleted together with +** the returned Expr object is to attach the pVector to the pRight field +** of the returned TK_SELECT_COLUMN Expr object. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprForVectorField( + Parse *pParse, /* Parsing context */ + Expr *pVector, /* The vector. List of expressions or a sub-SELECT */ + int iField, /* Which column of the vector to return */ + int nField /* Total number of columns in the vector */ +){ + Expr *pRet; + if( pVector->op==TK_SELECT ){ + assert( ExprUseXSelect(pVector) ); + /* The TK_SELECT_COLUMN Expr node: + ** + ** pLeft: pVector containing TK_SELECT. Not deleted. + ** pRight: not used. But recursively deleted. + ** iColumn: Index of a column in pVector + ** iTable: 0 or the number of columns on the LHS of an assignment + ** pLeft->iTable: First in an array of register holding result, or 0 + ** if the result is not yet computed. + ** + ** sqlite3ExprDelete() specifically skips the recursive delete of + ** pLeft on TK_SELECT_COLUMN nodes. But pRight is followed, so pVector + ** can be attached to pRight to cause this node to take ownership of + ** pVector. Typically there will be multiple TK_SELECT_COLUMN nodes + ** with the same pLeft pointer to the pVector, but only one of them + ** will own the pVector. + */ + pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, 0, 0); + if( pRet ){ + pRet->iTable = nField; + pRet->iColumn = iField; + pRet->pLeft = pVector; + } + }else{ + if( pVector->op==TK_VECTOR ){ + Expr **ppVector; + assert( ExprUseXList(pVector) ); + ppVector = &pVector->x.pList->a[iField].pExpr; + pVector = *ppVector; + if( IN_RENAME_OBJECT ){ + /* This must be a vector UPDATE inside a trigger */ + *ppVector = 0; + return pVector; + } + } + pRet = sqlite3ExprDup(pParse->db, pVector, 0); + } + return pRet; +} + +/* +** If expression pExpr is of type TK_SELECT, generate code to evaluate +** it. Return the register in which the result is stored (or, if the +** sub-select returns more than one column, the first in an array +** of registers in which the result is stored). +** +** If pExpr is not a TK_SELECT expression, return 0. +*/ +static int exprCodeSubselect(Parse *pParse, Expr *pExpr){ + int reg = 0; +#ifndef SQLITE_OMIT_SUBQUERY + if( pExpr->op==TK_SELECT ){ + reg = sqlite3CodeSubselect(pParse, pExpr); + } +#endif + return reg; +} + +/* +** Argument pVector points to a vector expression - either a TK_VECTOR +** or TK_SELECT that returns more than one column. This function returns +** the register number of a register that contains the value of +** element iField of the vector. +** +** If pVector is a TK_SELECT expression, then code for it must have +** already been generated using the exprCodeSubselect() routine. In this +** case parameter regSelect should be the first in an array of registers +** containing the results of the sub-select. +** +** If pVector is of type TK_VECTOR, then code for the requested field +** is generated. In this case (*pRegFree) may be set to the number of +** a temporary register to be freed by the caller before returning. +** +** Before returning, output parameter (*ppExpr) is set to point to the +** Expr object corresponding to element iElem of the vector. +*/ +static int exprVectorRegister( + Parse *pParse, /* Parse context */ + Expr *pVector, /* Vector to extract element from */ + int iField, /* Field to extract from pVector */ + int regSelect, /* First in array of registers */ + Expr **ppExpr, /* OUT: Expression element */ + int *pRegFree /* OUT: Temp register to free */ +){ + u8 op = pVector->op; + assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT || op==TK_ERROR ); + if( op==TK_REGISTER ){ + *ppExpr = sqlite3VectorFieldSubexpr(pVector, iField); + return pVector->iTable+iField; + } + if( op==TK_SELECT ){ + assert( ExprUseXSelect(pVector) ); + *ppExpr = pVector->x.pSelect->pEList->a[iField].pExpr; + return regSelect+iField; + } + if( op==TK_VECTOR ){ + assert( ExprUseXList(pVector) ); + *ppExpr = pVector->x.pList->a[iField].pExpr; + return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree); + } + return 0; +} + +/* +** Expression pExpr is a comparison between two vector values. Compute +** the result of the comparison (1, 0, or NULL) and write that +** result into register dest. +** +** The caller must satisfy the following preconditions: +** +** if pExpr->op==TK_IS: op==TK_EQ and p5==SQLITE_NULLEQ +** if pExpr->op==TK_ISNOT: op==TK_NE and p5==SQLITE_NULLEQ +** otherwise: op==pExpr->op and p5==0 +*/ +static void codeVectorCompare( + Parse *pParse, /* Code generator context */ + Expr *pExpr, /* The comparison operation */ + int dest, /* Write results into this register */ + u8 op, /* Comparison operator */ + u8 p5 /* SQLITE_NULLEQ or zero */ +){ + Vdbe *v = pParse->pVdbe; + Expr *pLeft = pExpr->pLeft; + Expr *pRight = pExpr->pRight; + int nLeft = sqlite3ExprVectorSize(pLeft); + int i; + int regLeft = 0; + int regRight = 0; + u8 opx = op; + int addrCmp = 0; + int addrDone = sqlite3VdbeMakeLabel(pParse); + int isCommuted = ExprHasProperty(pExpr,EP_Commuted); + + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); + if( pParse->nErr ) return; + if( nLeft!=sqlite3ExprVectorSize(pRight) ){ + sqlite3ErrorMsg(pParse, "row value misused"); + return; + } + assert( pExpr->op==TK_EQ || pExpr->op==TK_NE + || pExpr->op==TK_IS || pExpr->op==TK_ISNOT + || pExpr->op==TK_LT || pExpr->op==TK_GT + || pExpr->op==TK_LE || pExpr->op==TK_GE + ); + assert( pExpr->op==op || (pExpr->op==TK_IS && op==TK_EQ) + || (pExpr->op==TK_ISNOT && op==TK_NE) ); + assert( p5==0 || pExpr->op!=op ); + assert( p5==SQLITE_NULLEQ || pExpr->op==op ); + + if( op==TK_LE ) opx = TK_LT; + if( op==TK_GE ) opx = TK_GT; + if( op==TK_NE ) opx = TK_EQ; + + regLeft = exprCodeSubselect(pParse, pLeft); + regRight = exprCodeSubselect(pParse, pRight); + + sqlite3VdbeAddOp2(v, OP_Integer, 1, dest); + for(i=0; 1 /*Loop exits by "break"*/; i++){ + int regFree1 = 0, regFree2 = 0; + Expr *pL = 0, *pR = 0; + int r1, r2; + assert( i>=0 && i<nLeft ); + if( addrCmp ) sqlite3VdbeJumpHere(v, addrCmp); + r1 = exprVectorRegister(pParse, pLeft, i, regLeft, &pL, &regFree1); + r2 = exprVectorRegister(pParse, pRight, i, regRight, &pR, &regFree2); + addrCmp = sqlite3VdbeCurrentAddr(v); + codeCompare(pParse, pL, pR, opx, r1, r2, addrDone, p5, isCommuted); + testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); + testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); + sqlite3ReleaseTempReg(pParse, regFree1); + sqlite3ReleaseTempReg(pParse, regFree2); + if( (opx==TK_LT || opx==TK_GT) && i<nLeft-1 ){ + addrCmp = sqlite3VdbeAddOp0(v, OP_ElseEq); + testcase(opx==TK_LT); VdbeCoverageIf(v,opx==TK_LT); + testcase(opx==TK_GT); VdbeCoverageIf(v,opx==TK_GT); + } + if( p5==SQLITE_NULLEQ ){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, dest); + }else{ + sqlite3VdbeAddOp3(v, OP_ZeroOrNull, r1, dest, r2); + } + if( i==nLeft-1 ){ + break; + } + if( opx==TK_EQ ){ + sqlite3VdbeAddOp2(v, OP_NotNull, dest, addrDone); VdbeCoverage(v); + }else{ + assert( op==TK_LT || op==TK_GT || op==TK_LE || op==TK_GE ); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrDone); + if( i==nLeft-2 ) opx = op; + } + } + sqlite3VdbeJumpHere(v, addrCmp); + sqlite3VdbeResolveLabel(v, addrDone); + if( op==TK_NE ){ + sqlite3VdbeAddOp2(v, OP_Not, dest, dest); + } +} + +#if SQLITE_MAX_EXPR_DEPTH>0 +/* +** Check that argument nHeight is less than or equal to the maximum +** expression depth allowed. If it is not, leave an error message in +** pParse. +*/ +SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse *pParse, int nHeight){ + int rc = SQLITE_OK; + int mxHeight = pParse->db->aLimit[SQLITE_LIMIT_EXPR_DEPTH]; + if( nHeight>mxHeight ){ + sqlite3ErrorMsg(pParse, + "Expression tree is too large (maximum depth %d)", mxHeight + ); + rc = SQLITE_ERROR; + } + return rc; +} + +/* The following three functions, heightOfExpr(), heightOfExprList() +** and heightOfSelect(), are used to determine the maximum height +** of any expression tree referenced by the structure passed as the +** first argument. +** +** If this maximum height is greater than the current value pointed +** to by pnHeight, the second parameter, then set *pnHeight to that +** value. +*/ +static void heightOfExpr(const Expr *p, int *pnHeight){ + if( p ){ + if( p->nHeight>*pnHeight ){ + *pnHeight = p->nHeight; + } + } +} +static void heightOfExprList(const ExprList *p, int *pnHeight){ + if( p ){ + int i; + for(i=0; i<p->nExpr; i++){ + heightOfExpr(p->a[i].pExpr, pnHeight); + } + } +} +static void heightOfSelect(const Select *pSelect, int *pnHeight){ + const Select *p; + for(p=pSelect; p; p=p->pPrior){ + heightOfExpr(p->pWhere, pnHeight); + heightOfExpr(p->pHaving, pnHeight); + heightOfExpr(p->pLimit, pnHeight); + heightOfExprList(p->pEList, pnHeight); + heightOfExprList(p->pGroupBy, pnHeight); + heightOfExprList(p->pOrderBy, pnHeight); + } +} + +/* +** Set the Expr.nHeight variable in the structure passed as an +** argument. An expression with no children, Expr.pList or +** Expr.pSelect member has a height of 1. Any other expression +** has a height equal to the maximum height of any other +** referenced Expr plus one. +** +** Also propagate EP_Propagate flags up from Expr.x.pList to Expr.flags, +** if appropriate. +*/ +static void exprSetHeight(Expr *p){ + int nHeight = p->pLeft ? p->pLeft->nHeight : 0; + if( NEVER(p->pRight) && p->pRight->nHeight>nHeight ){ + nHeight = p->pRight->nHeight; + } + if( ExprUseXSelect(p) ){ + heightOfSelect(p->x.pSelect, &nHeight); + }else if( p->x.pList ){ + heightOfExprList(p->x.pList, &nHeight); + p->flags |= EP_Propagate & sqlite3ExprListFlags(p->x.pList); + } + p->nHeight = nHeight + 1; +} + +/* +** Set the Expr.nHeight variable using the exprSetHeight() function. If +** the height is greater than the maximum allowed expression depth, +** leave an error in pParse. +** +** Also propagate all EP_Propagate flags from the Expr.x.pList into +** Expr.flags. +*/ +SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ + if( pParse->nErr ) return; + exprSetHeight(p); + sqlite3ExprCheckHeight(pParse, p->nHeight); +} + +/* +** Return the maximum height of any expression tree referenced +** by the select statement passed as an argument. +*/ +SQLITE_PRIVATE int sqlite3SelectExprHeight(const Select *p){ + int nHeight = 0; + heightOfSelect(p, &nHeight); + return nHeight; +} +#else /* ABOVE: Height enforcement enabled. BELOW: Height enforcement off */ +/* +** Propagate all EP_Propagate flags from the Expr.x.pList into +** Expr.flags. +*/ +SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ + if( pParse->nErr ) return; + if( p && ExprUseXList(p) && p->x.pList ){ + p->flags |= EP_Propagate & sqlite3ExprListFlags(p->x.pList); + } +} +#define exprSetHeight(y) +#endif /* SQLITE_MAX_EXPR_DEPTH>0 */ + +/* +** Set the error offset for an Expr node, if possible. +*/ +SQLITE_PRIVATE void sqlite3ExprSetErrorOffset(Expr *pExpr, int iOfst){ + if( pExpr==0 ) return; + if( NEVER(ExprUseWJoin(pExpr)) ) return; + pExpr->w.iOfst = iOfst; +} + +/* +** This routine is the core allocator for Expr nodes. +** +** Construct a new expression node and return a pointer to it. Memory +** for this node and for the pToken argument is a single allocation +** obtained from sqlite3DbMalloc(). The calling function +** is responsible for making sure the node eventually gets freed. +** +** If dequote is true, then the token (if it exists) is dequoted. +** If dequote is false, no dequoting is performed. The deQuote +** parameter is ignored if pToken is NULL or if the token does not +** appear to be quoted. If the quotes were of the form "..." (double-quotes) +** then the EP_DblQuoted flag is set on the expression node. +** +** Special case: If op==TK_INTEGER and pToken points to a string that +** can be translated into a 32-bit integer, then the token is not +** stored in u.zToken. Instead, the integer values is written +** into u.iValue and the EP_IntValue flag is set. No extra storage +** is allocated to hold the integer text and the dequote flag is ignored. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprAlloc( + sqlite3 *db, /* Handle for sqlite3DbMallocRawNN() */ + int op, /* Expression opcode */ + const Token *pToken, /* Token argument. Might be NULL */ + int dequote /* True to dequote */ +){ + Expr *pNew; + int nExtra = 0; + int iValue = 0; + + assert( db!=0 ); + if( pToken ){ + if( op!=TK_INTEGER || pToken->z==0 + || sqlite3GetInt32(pToken->z, &iValue)==0 ){ + nExtra = pToken->n+1; + assert( iValue>=0 ); + } + } + pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)+nExtra); + if( pNew ){ + memset(pNew, 0, sizeof(Expr)); + pNew->op = (u8)op; + pNew->iAgg = -1; + if( pToken ){ + if( nExtra==0 ){ + pNew->flags |= EP_IntValue|EP_Leaf|(iValue?EP_IsTrue:EP_IsFalse); + pNew->u.iValue = iValue; + }else{ + pNew->u.zToken = (char*)&pNew[1]; + assert( pToken->z!=0 || pToken->n==0 ); + if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); + pNew->u.zToken[pToken->n] = 0; + if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){ + sqlite3DequoteExpr(pNew); + } + } + } +#if SQLITE_MAX_EXPR_DEPTH>0 + pNew->nHeight = 1; +#endif + } + return pNew; +} + +/* +** Allocate a new expression node from a zero-terminated token that has +** already been dequoted. +*/ +SQLITE_PRIVATE Expr *sqlite3Expr( + sqlite3 *db, /* Handle for sqlite3DbMallocZero() (may be null) */ + int op, /* Expression opcode */ + const char *zToken /* Token argument. Might be NULL */ +){ + Token x; + x.z = zToken; + x.n = sqlite3Strlen30(zToken); + return sqlite3ExprAlloc(db, op, &x, 0); +} + +/* +** Attach subtrees pLeft and pRight to the Expr node pRoot. +** +** If pRoot==NULL that means that a memory allocation error has occurred. +** In that case, delete the subtrees pLeft and pRight. +*/ +SQLITE_PRIVATE void sqlite3ExprAttachSubtrees( + sqlite3 *db, + Expr *pRoot, + Expr *pLeft, + Expr *pRight +){ + if( pRoot==0 ){ + assert( db->mallocFailed ); + sqlite3ExprDelete(db, pLeft); + sqlite3ExprDelete(db, pRight); + }else{ + assert( ExprUseXList(pRoot) ); + assert( pRoot->x.pSelect==0 ); + if( pRight ){ + pRoot->pRight = pRight; + pRoot->flags |= EP_Propagate & pRight->flags; +#if SQLITE_MAX_EXPR_DEPTH>0 + pRoot->nHeight = pRight->nHeight+1; + }else{ + pRoot->nHeight = 1; +#endif + } + if( pLeft ){ + pRoot->pLeft = pLeft; + pRoot->flags |= EP_Propagate & pLeft->flags; +#if SQLITE_MAX_EXPR_DEPTH>0 + if( pLeft->nHeight>=pRoot->nHeight ){ + pRoot->nHeight = pLeft->nHeight+1; + } +#endif + } + } +} + +/* +** Allocate an Expr node which joins as many as two subtrees. +** +** One or both of the subtrees can be NULL. Return a pointer to the new +** Expr node. Or, if an OOM error occurs, set pParse->db->mallocFailed, +** free the subtrees and return NULL. +*/ +SQLITE_PRIVATE Expr *sqlite3PExpr( + Parse *pParse, /* Parsing context */ + int op, /* Expression opcode */ + Expr *pLeft, /* Left operand */ + Expr *pRight /* Right operand */ +){ + Expr *p; + p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)); + if( p ){ + memset(p, 0, sizeof(Expr)); + p->op = op & 0xff; + p->iAgg = -1; + sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight); + sqlite3ExprCheckHeight(pParse, p->nHeight); + }else{ + sqlite3ExprDelete(pParse->db, pLeft); + sqlite3ExprDelete(pParse->db, pRight); + } + return p; +} + +/* +** Add pSelect to the Expr.x.pSelect field. Or, if pExpr is NULL (due +** do a memory allocation failure) then delete the pSelect object. +*/ +SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse *pParse, Expr *pExpr, Select *pSelect){ + if( pExpr ){ + pExpr->x.pSelect = pSelect; + ExprSetProperty(pExpr, EP_xIsSelect|EP_Subquery); + sqlite3ExprSetHeightAndFlags(pParse, pExpr); + }else{ + assert( pParse->db->mallocFailed ); + sqlite3SelectDelete(pParse->db, pSelect); + } +} + +/* +** Expression list pEList is a list of vector values. This function +** converts the contents of pEList to a VALUES(...) Select statement +** returning 1 row for each element of the list. For example, the +** expression list: +** +** ( (1,2), (3,4) (5,6) ) +** +** is translated to the equivalent of: +** +** VALUES(1,2), (3,4), (5,6) +** +** Each of the vector values in pEList must contain exactly nElem terms. +** If a list element that is not a vector or does not contain nElem terms, +** an error message is left in pParse. +** +** This is used as part of processing IN(...) expressions with a list +** of vectors on the RHS. e.g. "... IN ((1,2), (3,4), (5,6))". +*/ +SQLITE_PRIVATE Select *sqlite3ExprListToValues(Parse *pParse, int nElem, ExprList *pEList){ + int ii; + Select *pRet = 0; + assert( nElem>1 ); + for(ii=0; ii<pEList->nExpr; ii++){ + Select *pSel; + Expr *pExpr = pEList->a[ii].pExpr; + int nExprElem; + if( pExpr->op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); + nExprElem = pExpr->x.pList->nExpr; + }else{ + nExprElem = 1; + } + if( nExprElem!=nElem ){ + sqlite3ErrorMsg(pParse, "IN(...) element has %d term%s - expected %d", + nExprElem, nExprElem>1?"s":"", nElem + ); + break; + } + assert( ExprUseXList(pExpr) ); + pSel = sqlite3SelectNew(pParse, pExpr->x.pList, 0, 0, 0, 0, 0, SF_Values,0); + pExpr->x.pList = 0; + if( pSel ){ + if( pRet ){ + pSel->op = TK_ALL; + pSel->pPrior = pRet; + } + pRet = pSel; + } + } + + if( pRet && pRet->pPrior ){ + pRet->selFlags |= SF_MultiValue; + } + sqlite3ExprListDelete(pParse->db, pEList); + return pRet; +} + +/* +** Join two expressions using an AND operator. If either expression is +** NULL, then just return the other expression. +** +** If one side or the other of the AND is known to be false, and neither side +** is part of an ON clause, then instead of returning an AND expression, +** just return a constant expression with a value of false. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ + sqlite3 *db = pParse->db; + if( pLeft==0 ){ + return pRight; + }else if( pRight==0 ){ + return pLeft; + }else{ + u32 f = pLeft->flags | pRight->flags; + if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse))==EP_IsFalse + && !IN_RENAME_OBJECT + ){ + sqlite3ExprDeferredDelete(pParse, pLeft); + sqlite3ExprDeferredDelete(pParse, pRight); + return sqlite3Expr(db, TK_INTEGER, "0"); + }else{ + return sqlite3PExpr(pParse, TK_AND, pLeft, pRight); + } + } +} + +/* +** Construct a new expression node for a function with multiple +** arguments. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprFunction( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* Argument list */ + const Token *pToken, /* Name of the function */ + int eDistinct /* SF_Distinct or SF_ALL or 0 */ +){ + Expr *pNew; + sqlite3 *db = pParse->db; + assert( pToken ); + pNew = sqlite3ExprAlloc(db, TK_FUNCTION, pToken, 1); + if( pNew==0 ){ + sqlite3ExprListDelete(db, pList); /* Avoid memory leak when malloc fails */ + return 0; + } + assert( !ExprHasProperty(pNew, EP_InnerON|EP_OuterON) ); + pNew->w.iOfst = (int)(pToken->z - pParse->zTail); + if( pList + && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] + && !pParse->nested + ){ + sqlite3ErrorMsg(pParse, "too many arguments on function %T", pToken); + } + pNew->x.pList = pList; + ExprSetProperty(pNew, EP_HasFunc); + assert( ExprUseXList(pNew) ); + sqlite3ExprSetHeightAndFlags(pParse, pNew); + if( eDistinct==SF_Distinct ) ExprSetProperty(pNew, EP_Distinct); + return pNew; +} + +/* +** Check to see if a function is usable according to current access +** rules: +** +** SQLITE_FUNC_DIRECT - Only usable from top-level SQL +** +** SQLITE_FUNC_UNSAFE - Usable if TRUSTED_SCHEMA or from +** top-level SQL +** +** If the function is not usable, create an error. +*/ +SQLITE_PRIVATE void sqlite3ExprFunctionUsable( + Parse *pParse, /* Parsing and code generating context */ + const Expr *pExpr, /* The function invocation */ + const FuncDef *pDef /* The function being invoked */ +){ + assert( !IN_RENAME_OBJECT ); + assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 ); + if( ExprHasProperty(pExpr, EP_FromDDL) ){ + if( (pDef->funcFlags & SQLITE_FUNC_DIRECT)!=0 + || (pParse->db->flags & SQLITE_TrustedSchema)==0 + ){ + /* Functions prohibited in triggers and views if: + ** (1) tagged with SQLITE_DIRECTONLY + ** (2) not tagged with SQLITE_INNOCUOUS (which means it + ** is tagged with SQLITE_FUNC_UNSAFE) and + ** SQLITE_DBCONFIG_TRUSTED_SCHEMA is off (meaning + ** that the schema is possibly tainted). + */ + sqlite3ErrorMsg(pParse, "unsafe use of %#T()", pExpr); + } + } +} + +/* +** Assign a variable number to an expression that encodes a wildcard +** in the original SQL statement. +** +** Wildcards consisting of a single "?" are assigned the next sequential +** variable number. +** +** Wildcards of the form "?nnn" are assigned the number "nnn". We make +** sure "nnn" is not too big to avoid a denial of service attack when +** the SQL statement comes from an external source. +** +** Wildcards of the form ":aaa", "@aaa", or "$aaa" are assigned the same number +** as the previous instance of the same wildcard. Or if this is the first +** instance of the wildcard, the next sequential variable number is +** assigned. +*/ +SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n){ + sqlite3 *db = pParse->db; + const char *z; + ynVar x; + + if( pExpr==0 ) return; + assert( !ExprHasProperty(pExpr, EP_IntValue|EP_Reduced|EP_TokenOnly) ); + z = pExpr->u.zToken; + assert( z!=0 ); + assert( z[0]!=0 ); + assert( n==(u32)sqlite3Strlen30(z) ); + if( z[1]==0 ){ + /* Wildcard of the form "?". Assign the next variable number */ + assert( z[0]=='?' ); + x = (ynVar)(++pParse->nVar); + }else{ + int doAdd = 0; + if( z[0]=='?' ){ + /* Wildcard of the form "?nnn". Convert "nnn" to an integer and + ** use it as the variable number */ + i64 i; + int bOk; + if( n==2 ){ /*OPTIMIZATION-IF-TRUE*/ + i = z[1]-'0'; /* The common case of ?N for a single digit N */ + bOk = 1; + }else{ + bOk = 0==sqlite3Atoi64(&z[1], &i, n-1, SQLITE_UTF8); + } + testcase( i==0 ); + testcase( i==1 ); + testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]-1 ); + testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ); + if( bOk==0 || i<1 || i>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ + sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d", + db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); + return; + } + x = (ynVar)i; + if( x>pParse->nVar ){ + pParse->nVar = (int)x; + doAdd = 1; + }else if( sqlite3VListNumToName(pParse->pVList, x)==0 ){ + doAdd = 1; + } + }else{ + /* Wildcards like ":aaa", "$aaa" or "@aaa". Reuse the same variable + ** number as the prior appearance of the same name, or if the name + ** has never appeared before, reuse the same variable number + */ + x = (ynVar)sqlite3VListNameToNum(pParse->pVList, z, n); + if( x==0 ){ + x = (ynVar)(++pParse->nVar); + doAdd = 1; + } + } + if( doAdd ){ + pParse->pVList = sqlite3VListAdd(db, pParse->pVList, z, n, x); + } + } + pExpr->iColumn = x; + if( x>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ + sqlite3ErrorMsg(pParse, "too many SQL variables"); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); + } +} + +/* +** Recursively delete an expression tree. +*/ +static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ + assert( p!=0 ); + assert( db!=0 ); + assert( !ExprUseUValue(p) || p->u.iValue>=0 ); + assert( !ExprUseYWin(p) || !ExprUseYSub(p) ); + assert( !ExprUseYWin(p) || p->y.pWin!=0 || db->mallocFailed ); + assert( p->op!=TK_FUNCTION || !ExprUseYSub(p) ); +#ifdef SQLITE_DEBUG + if( ExprHasProperty(p, EP_Leaf) && !ExprHasProperty(p, EP_TokenOnly) ){ + assert( p->pLeft==0 ); + assert( p->pRight==0 ); + assert( !ExprUseXSelect(p) || p->x.pSelect==0 ); + assert( !ExprUseXList(p) || p->x.pList==0 ); + } +#endif + if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){ + /* The Expr.x union is never used at the same time as Expr.pRight */ + assert( (ExprUseXList(p) && p->x.pList==0) || p->pRight==0 ); + if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft); + if( p->pRight ){ + assert( !ExprHasProperty(p, EP_WinFunc) ); + sqlite3ExprDeleteNN(db, p->pRight); + }else if( ExprUseXSelect(p) ){ + assert( !ExprHasProperty(p, EP_WinFunc) ); + sqlite3SelectDelete(db, p->x.pSelect); + }else{ + sqlite3ExprListDelete(db, p->x.pList); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(p, EP_WinFunc) ){ + sqlite3WindowDelete(db, p->y.pWin); + } +#endif + } + } + if( !ExprHasProperty(p, EP_Static) ){ + sqlite3DbNNFreeNN(db, p); + } +} +SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){ + if( p ) sqlite3ExprDeleteNN(db, p); +} + +/* +** Clear both elements of an OnOrUsing object +*/ +SQLITE_PRIVATE void sqlite3ClearOnOrUsing(sqlite3 *db, OnOrUsing *p){ + if( p==0 ){ + /* Nothing to clear */ + }else if( p->pOn ){ + sqlite3ExprDeleteNN(db, p->pOn); + }else if( p->pUsing ){ + sqlite3IdListDelete(db, p->pUsing); + } +} + +/* +** Arrange to cause pExpr to be deleted when the pParse is deleted. +** This is similar to sqlite3ExprDelete() except that the delete is +** deferred until the pParse is deleted. +** +** The pExpr might be deleted immediately on an OOM error. +** +** The deferred delete is (currently) implemented by adding the +** pExpr to the pParse->pConstExpr list with a register number of 0. +*/ +SQLITE_PRIVATE void sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){ + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3ExprDelete, + pExpr); +} + +/* Invoke sqlite3RenameExprUnmap() and sqlite3ExprDelete() on the +** expression. +*/ +SQLITE_PRIVATE void sqlite3ExprUnmapAndDelete(Parse *pParse, Expr *p){ + if( p ){ + if( IN_RENAME_OBJECT ){ + sqlite3RenameExprUnmap(pParse, p); + } + sqlite3ExprDeleteNN(pParse->db, p); + } +} + +/* +** Return the number of bytes allocated for the expression structure +** passed as the first argument. This is always one of EXPR_FULLSIZE, +** EXPR_REDUCEDSIZE or EXPR_TOKENONLYSIZE. +*/ +static int exprStructSize(const Expr *p){ + if( ExprHasProperty(p, EP_TokenOnly) ) return EXPR_TOKENONLYSIZE; + if( ExprHasProperty(p, EP_Reduced) ) return EXPR_REDUCEDSIZE; + return EXPR_FULLSIZE; +} + +/* +** The dupedExpr*Size() routines each return the number of bytes required +** to store a copy of an expression or expression tree. They differ in +** how much of the tree is measured. +** +** dupedExprStructSize() Size of only the Expr structure +** dupedExprNodeSize() Size of Expr + space for token +** dupedExprSize() Expr + token + subtree components +** +*************************************************************************** +** +** The dupedExprStructSize() function returns two values OR-ed together: +** (1) the space required for a copy of the Expr structure only and +** (2) the EP_xxx flags that indicate what the structure size should be. +** The return values is always one of: +** +** EXPR_FULLSIZE +** EXPR_REDUCEDSIZE | EP_Reduced +** EXPR_TOKENONLYSIZE | EP_TokenOnly +** +** The size of the structure can be found by masking the return value +** of this routine with 0xfff. The flags can be found by masking the +** return value with EP_Reduced|EP_TokenOnly. +** +** Note that with flags==EXPRDUP_REDUCE, this routines works on full-size +** (unreduced) Expr objects as they or originally constructed by the parser. +** During expression analysis, extra information is computed and moved into +** later parts of the Expr object and that extra information might get chopped +** off if the expression is reduced. Note also that it does not work to +** make an EXPRDUP_REDUCE copy of a reduced expression. It is only legal +** to reduce a pristine expression tree from the parser. The implementation +** of dupedExprStructSize() contain multiple assert() statements that attempt +** to enforce this constraint. +*/ +static int dupedExprStructSize(const Expr *p, int flags){ + int nSize; + assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */ + assert( EXPR_FULLSIZE<=0xfff ); + assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 ); + if( 0==flags || p->op==TK_SELECT_COLUMN +#ifndef SQLITE_OMIT_WINDOWFUNC + || ExprHasProperty(p, EP_WinFunc) +#endif + ){ + nSize = EXPR_FULLSIZE; + }else{ + assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); + assert( !ExprHasProperty(p, EP_OuterON) ); + assert( !ExprHasVVAProperty(p, EP_NoReduce) ); + if( p->pLeft || p->x.pList ){ + nSize = EXPR_REDUCEDSIZE | EP_Reduced; + }else{ + assert( p->pRight==0 ); + nSize = EXPR_TOKENONLYSIZE | EP_TokenOnly; + } + } + return nSize; +} + +/* +** This function returns the space in bytes required to store the copy +** of the Expr structure and a copy of the Expr.u.zToken string (if that +** string is defined.) +*/ +static int dupedExprNodeSize(const Expr *p, int flags){ + int nByte = dupedExprStructSize(p, flags) & 0xfff; + if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ + nByte += sqlite3Strlen30NN(p->u.zToken)+1; + } + return ROUND8(nByte); +} + +/* +** Return the number of bytes required to create a duplicate of the +** expression passed as the first argument. The second argument is a +** mask containing EXPRDUP_XXX flags. +** +** The value returned includes space to create a copy of the Expr struct +** itself and the buffer referred to by Expr.u.zToken, if any. +** +** If the EXPRDUP_REDUCE flag is set, then the return value includes +** space to duplicate all Expr nodes in the tree formed by Expr.pLeft +** and Expr.pRight variables (but not for any structures pointed to or +** descended from the Expr.x.pList or Expr.x.pSelect variables). +*/ +static int dupedExprSize(const Expr *p, int flags){ + int nByte = 0; + if( p ){ + nByte = dupedExprNodeSize(p, flags); + if( flags&EXPRDUP_REDUCE ){ + nByte += dupedExprSize(p->pLeft, flags) + dupedExprSize(p->pRight, flags); + } + } + return nByte; +} + +/* +** This function is similar to sqlite3ExprDup(), except that if pzBuffer +** is not NULL then *pzBuffer is assumed to point to a buffer large enough +** to store the copy of expression p, the copies of p->u.zToken +** (if applicable), and the copies of the p->pLeft and p->pRight expressions, +** if any. Before returning, *pzBuffer is set to the first byte past the +** portion of the buffer copied into by this function. +*/ +static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){ + Expr *pNew; /* Value to return */ + u8 *zAlloc; /* Memory space from which to build Expr object */ + u32 staticFlag; /* EP_Static if space not obtained from malloc */ + + assert( db!=0 ); + assert( p ); + assert( dupFlags==0 || dupFlags==EXPRDUP_REDUCE ); + assert( pzBuffer==0 || dupFlags==EXPRDUP_REDUCE ); + + /* Figure out where to write the new Expr structure. */ + if( pzBuffer ){ + zAlloc = *pzBuffer; + staticFlag = EP_Static; + assert( zAlloc!=0 ); + }else{ + zAlloc = sqlite3DbMallocRawNN(db, dupedExprSize(p, dupFlags)); + staticFlag = 0; + } + pNew = (Expr *)zAlloc; + + if( pNew ){ + /* Set nNewSize to the size allocated for the structure pointed to + ** by pNew. This is either EXPR_FULLSIZE, EXPR_REDUCEDSIZE or + ** EXPR_TOKENONLYSIZE. nToken is set to the number of bytes consumed + ** by the copy of the p->u.zToken string (if any). + */ + const unsigned nStructSize = dupedExprStructSize(p, dupFlags); + const int nNewSize = nStructSize & 0xfff; + int nToken; + if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ + nToken = sqlite3Strlen30(p->u.zToken) + 1; + }else{ + nToken = 0; + } + if( dupFlags ){ + assert( ExprHasProperty(p, EP_Reduced)==0 ); + memcpy(zAlloc, p, nNewSize); + }else{ + u32 nSize = (u32)exprStructSize(p); + memcpy(zAlloc, p, nSize); + if( nSize<EXPR_FULLSIZE ){ + memset(&zAlloc[nSize], 0, EXPR_FULLSIZE-nSize); + } + } + + /* Set the EP_Reduced, EP_TokenOnly, and EP_Static flags appropriately. */ + pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static); + pNew->flags |= nStructSize & (EP_Reduced|EP_TokenOnly); + pNew->flags |= staticFlag; + ExprClearVVAProperties(pNew); + if( dupFlags ){ + ExprSetVVAProperty(pNew, EP_Immutable); + } + + /* Copy the p->u.zToken string, if any. */ + if( nToken ){ + char *zToken = pNew->u.zToken = (char*)&zAlloc[nNewSize]; + memcpy(zToken, p->u.zToken, nToken); + } + + if( 0==((p->flags|pNew->flags) & (EP_TokenOnly|EP_Leaf)) ){ + /* Fill in the pNew->x.pSelect or pNew->x.pList member. */ + if( ExprUseXSelect(p) ){ + pNew->x.pSelect = sqlite3SelectDup(db, p->x.pSelect, dupFlags); + }else{ + pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, dupFlags); + } + } + + /* Fill in pNew->pLeft and pNew->pRight. */ + if( ExprHasProperty(pNew, EP_Reduced|EP_TokenOnly|EP_WinFunc) ){ + zAlloc += dupedExprNodeSize(p, dupFlags); + if( !ExprHasProperty(pNew, EP_TokenOnly|EP_Leaf) ){ + pNew->pLeft = p->pLeft ? + exprDup(db, p->pLeft, EXPRDUP_REDUCE, &zAlloc) : 0; + pNew->pRight = p->pRight ? + exprDup(db, p->pRight, EXPRDUP_REDUCE, &zAlloc) : 0; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(p, EP_WinFunc) ){ + pNew->y.pWin = sqlite3WindowDup(db, pNew, p->y.pWin); + assert( ExprHasProperty(pNew, EP_WinFunc) ); + } +#endif /* SQLITE_OMIT_WINDOWFUNC */ + if( pzBuffer ){ + *pzBuffer = zAlloc; + } + }else{ + if( !ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ + if( pNew->op==TK_SELECT_COLUMN ){ + pNew->pLeft = p->pLeft; + assert( p->pRight==0 || p->pRight==p->pLeft + || ExprHasProperty(p->pLeft, EP_Subquery) ); + }else{ + pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); + } + pNew->pRight = sqlite3ExprDup(db, p->pRight, 0); + } + } + } + return pNew; +} + +/* +** Create and return a deep copy of the object passed as the second +** argument. If an OOM condition is encountered, NULL is returned +** and the db->mallocFailed flag set. +*/ +#ifndef SQLITE_OMIT_CTE +SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p){ + With *pRet = 0; + if( p ){ + sqlite3_int64 nByte = sizeof(*p) + sizeof(p->a[0]) * (p->nCte-1); + pRet = sqlite3DbMallocZero(db, nByte); + if( pRet ){ + int i; + pRet->nCte = p->nCte; + for(i=0; i<p->nCte; i++){ + pRet->a[i].pSelect = sqlite3SelectDup(db, p->a[i].pSelect, 0); + pRet->a[i].pCols = sqlite3ExprListDup(db, p->a[i].pCols, 0); + pRet->a[i].zName = sqlite3DbStrDup(db, p->a[i].zName); + pRet->a[i].eM10d = p->a[i].eM10d; + } + } + } + return pRet; +} +#else +# define sqlite3WithDup(x,y) 0 +#endif + +#ifndef SQLITE_OMIT_WINDOWFUNC +/* +** The gatherSelectWindows() procedure and its helper routine +** gatherSelectWindowsCallback() are used to scan all the expressions +** an a newly duplicated SELECT statement and gather all of the Window +** objects found there, assembling them onto the linked list at Select->pWin. +*/ +static int gatherSelectWindowsCallback(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_FUNCTION && ExprHasProperty(pExpr, EP_WinFunc) ){ + Select *pSelect = pWalker->u.pSelect; + Window *pWin = pExpr->y.pWin; + assert( pWin ); + assert( IsWindowFunc(pExpr) ); + assert( pWin->ppThis==0 ); + sqlite3WindowLink(pSelect, pWin); + } + return WRC_Continue; +} +static int gatherSelectWindowsSelectCallback(Walker *pWalker, Select *p){ + return p==pWalker->u.pSelect ? WRC_Continue : WRC_Prune; +} +static void gatherSelectWindows(Select *p){ + Walker w; + w.xExprCallback = gatherSelectWindowsCallback; + w.xSelectCallback = gatherSelectWindowsSelectCallback; + w.xSelectCallback2 = 0; + w.pParse = 0; + w.u.pSelect = p; + sqlite3WalkSelect(&w, p); +} +#endif + + +/* +** The following group of routines make deep copies of expressions, +** expression lists, ID lists, and select statements. The copies can +** be deleted (by being passed to their respective ...Delete() routines) +** without effecting the originals. +** +** The expression list, ID, and source lists return by sqlite3ExprListDup(), +** sqlite3IdListDup(), and sqlite3SrcListDup() can not be further expanded +** by subsequent calls to sqlite*ListAppend() routines. +** +** Any tables that the SrcList might point to are not duplicated. +** +** The flags parameter contains a combination of the EXPRDUP_XXX flags. +** If the EXPRDUP_REDUCE flag is set, then the structure returned is a +** truncated version of the usual Expr structure that will be stored as +** part of the in-memory representation of the database schema. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, const Expr *p, int flags){ + assert( flags==0 || flags==EXPRDUP_REDUCE ); + return p ? exprDup(db, p, flags, 0) : 0; +} +SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, const ExprList *p, int flags){ + ExprList *pNew; + struct ExprList_item *pItem; + const struct ExprList_item *pOldItem; + int i; + Expr *pPriorSelectColOld = 0; + Expr *pPriorSelectColNew = 0; + assert( db!=0 ); + if( p==0 ) return 0; + pNew = sqlite3DbMallocRawNN(db, sqlite3DbMallocSize(db, p)); + if( pNew==0 ) return 0; + pNew->nExpr = p->nExpr; + pNew->nAlloc = p->nAlloc; + pItem = pNew->a; + pOldItem = p->a; + for(i=0; i<p->nExpr; i++, pItem++, pOldItem++){ + Expr *pOldExpr = pOldItem->pExpr; + Expr *pNewExpr; + pItem->pExpr = sqlite3ExprDup(db, pOldExpr, flags); + if( pOldExpr + && pOldExpr->op==TK_SELECT_COLUMN + && (pNewExpr = pItem->pExpr)!=0 + ){ + if( pNewExpr->pRight ){ + pPriorSelectColOld = pOldExpr->pRight; + pPriorSelectColNew = pNewExpr->pRight; + pNewExpr->pLeft = pNewExpr->pRight; + }else{ + if( pOldExpr->pLeft!=pPriorSelectColOld ){ + pPriorSelectColOld = pOldExpr->pLeft; + pPriorSelectColNew = sqlite3ExprDup(db, pPriorSelectColOld, flags); + pNewExpr->pRight = pPriorSelectColNew; + } + pNewExpr->pLeft = pPriorSelectColNew; + } + } + pItem->zEName = sqlite3DbStrDup(db, pOldItem->zEName); + pItem->fg = pOldItem->fg; + pItem->fg.done = 0; + pItem->u = pOldItem->u; + } + return pNew; +} + +/* +** If cursors, triggers, views and subqueries are all omitted from +** the build, then none of the following routines, except for +** sqlite3SelectDup(), can be called. sqlite3SelectDup() is sometimes +** called with a NULL argument. +*/ +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) \ + || !defined(SQLITE_OMIT_SUBQUERY) +SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){ + SrcList *pNew; + int i; + int nByte; + assert( db!=0 ); + if( p==0 ) return 0; + nByte = sizeof(*p) + (p->nSrc>0 ? sizeof(p->a[0]) * (p->nSrc-1) : 0); + pNew = sqlite3DbMallocRawNN(db, nByte ); + if( pNew==0 ) return 0; + pNew->nSrc = pNew->nAlloc = p->nSrc; + for(i=0; i<p->nSrc; i++){ + SrcItem *pNewItem = &pNew->a[i]; + const SrcItem *pOldItem = &p->a[i]; + Table *pTab; + pNewItem->pSchema = pOldItem->pSchema; + pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase); + pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName); + pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias); + pNewItem->fg = pOldItem->fg; + pNewItem->iCursor = pOldItem->iCursor; + pNewItem->addrFillSub = pOldItem->addrFillSub; + pNewItem->regReturn = pOldItem->regReturn; + if( pNewItem->fg.isIndexedBy ){ + pNewItem->u1.zIndexedBy = sqlite3DbStrDup(db, pOldItem->u1.zIndexedBy); + } + pNewItem->u2 = pOldItem->u2; + if( pNewItem->fg.isCte ){ + pNewItem->u2.pCteUse->nUse++; + } + if( pNewItem->fg.isTabFunc ){ + pNewItem->u1.pFuncArg = + sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags); + } + pTab = pNewItem->pTab = pOldItem->pTab; + if( pTab ){ + pTab->nTabRef++; + } + pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect, flags); + if( pOldItem->fg.isUsing ){ + assert( pNewItem->fg.isUsing ); + pNewItem->u3.pUsing = sqlite3IdListDup(db, pOldItem->u3.pUsing); + }else{ + pNewItem->u3.pOn = sqlite3ExprDup(db, pOldItem->u3.pOn, flags); + } + pNewItem->colUsed = pOldItem->colUsed; + } + return pNew; +} +SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, const IdList *p){ + IdList *pNew; + int i; + assert( db!=0 ); + if( p==0 ) return 0; + assert( p->eU4!=EU4_EXPR ); + pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew)+(p->nId-1)*sizeof(p->a[0]) ); + if( pNew==0 ) return 0; + pNew->nId = p->nId; + pNew->eU4 = p->eU4; + for(i=0; i<p->nId; i++){ + struct IdList_item *pNewItem = &pNew->a[i]; + const struct IdList_item *pOldItem = &p->a[i]; + pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName); + pNewItem->u4 = pOldItem->u4; + } + return pNew; +} +SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, const Select *pDup, int flags){ + Select *pRet = 0; + Select *pNext = 0; + Select **pp = &pRet; + const Select *p; + + assert( db!=0 ); + for(p=pDup; p; p=p->pPrior){ + Select *pNew = sqlite3DbMallocRawNN(db, sizeof(*p) ); + if( pNew==0 ) break; + pNew->pEList = sqlite3ExprListDup(db, p->pEList, flags); + pNew->pSrc = sqlite3SrcListDup(db, p->pSrc, flags); + pNew->pWhere = sqlite3ExprDup(db, p->pWhere, flags); + pNew->pGroupBy = sqlite3ExprListDup(db, p->pGroupBy, flags); + pNew->pHaving = sqlite3ExprDup(db, p->pHaving, flags); + pNew->pOrderBy = sqlite3ExprListDup(db, p->pOrderBy, flags); + pNew->op = p->op; + pNew->pNext = pNext; + pNew->pPrior = 0; + pNew->pLimit = sqlite3ExprDup(db, p->pLimit, flags); + pNew->iLimit = 0; + pNew->iOffset = 0; + pNew->selFlags = p->selFlags & ~SF_UsesEphemeral; + pNew->addrOpenEphm[0] = -1; + pNew->addrOpenEphm[1] = -1; + pNew->nSelectRow = p->nSelectRow; + pNew->pWith = sqlite3WithDup(db, p->pWith); +#ifndef SQLITE_OMIT_WINDOWFUNC + pNew->pWin = 0; + pNew->pWinDefn = sqlite3WindowListDup(db, p->pWinDefn); + if( p->pWin && db->mallocFailed==0 ) gatherSelectWindows(pNew); +#endif + pNew->selId = p->selId; + if( db->mallocFailed ){ + /* Any prior OOM might have left the Select object incomplete. + ** Delete the whole thing rather than allow an incomplete Select + ** to be used by the code generator. */ + pNew->pNext = 0; + sqlite3SelectDelete(db, pNew); + break; + } + *pp = pNew; + pp = &pNew->pPrior; + pNext = pNew; + } + + return pRet; +} +#else +SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, const Select *p, int flags){ + assert( p==0 ); + return 0; +} +#endif + + +/* +** Add a new element to the end of an expression list. If pList is +** initially NULL, then create a new expression list. +** +** The pList argument must be either NULL or a pointer to an ExprList +** obtained from a prior call to sqlite3ExprListAppend(). This routine +** may not be used with an ExprList obtained from sqlite3ExprListDup(). +** Reason: This routine assumes that the number of slots in pList->a[] +** is a power of two. That is true for sqlite3ExprListAppend() returns +** but is not necessarily true from the return value of sqlite3ExprListDup(). +** +** If a memory allocation error occurs, the entire list is freed and +** NULL is returned. If non-NULL is returned, then it is guaranteed +** that the new entry was successfully appended. +*/ +static const struct ExprList_item zeroItem = {0}; +SQLITE_PRIVATE SQLITE_NOINLINE ExprList *sqlite3ExprListAppendNew( + sqlite3 *db, /* Database handle. Used for memory allocation */ + Expr *pExpr /* Expression to be appended. Might be NULL */ +){ + struct ExprList_item *pItem; + ExprList *pList; + + pList = sqlite3DbMallocRawNN(db, sizeof(ExprList)+sizeof(pList->a[0])*4 ); + if( pList==0 ){ + sqlite3ExprDelete(db, pExpr); + return 0; + } + pList->nAlloc = 4; + pList->nExpr = 1; + pItem = &pList->a[0]; + *pItem = zeroItem; + pItem->pExpr = pExpr; + return pList; +} +SQLITE_PRIVATE SQLITE_NOINLINE ExprList *sqlite3ExprListAppendGrow( + sqlite3 *db, /* Database handle. Used for memory allocation */ + ExprList *pList, /* List to which to append. Might be NULL */ + Expr *pExpr /* Expression to be appended. Might be NULL */ +){ + struct ExprList_item *pItem; + ExprList *pNew; + pList->nAlloc *= 2; + pNew = sqlite3DbRealloc(db, pList, + sizeof(*pList)+(pList->nAlloc-1)*sizeof(pList->a[0])); + if( pNew==0 ){ + sqlite3ExprListDelete(db, pList); + sqlite3ExprDelete(db, pExpr); + return 0; + }else{ + pList = pNew; + } + pItem = &pList->a[pList->nExpr++]; + *pItem = zeroItem; + pItem->pExpr = pExpr; + return pList; +} +SQLITE_PRIVATE ExprList *sqlite3ExprListAppend( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* List to which to append. Might be NULL */ + Expr *pExpr /* Expression to be appended. Might be NULL */ +){ + struct ExprList_item *pItem; + if( pList==0 ){ + return sqlite3ExprListAppendNew(pParse->db,pExpr); + } + if( pList->nAlloc<pList->nExpr+1 ){ + return sqlite3ExprListAppendGrow(pParse->db,pList,pExpr); + } + pItem = &pList->a[pList->nExpr++]; + *pItem = zeroItem; + pItem->pExpr = pExpr; + return pList; +} + +/* +** pColumns and pExpr form a vector assignment which is part of the SET +** clause of an UPDATE statement. Like this: +** +** (a,b,c) = (expr1,expr2,expr3) +** Or: (a,b,c) = (SELECT x,y,z FROM ....) +** +** For each term of the vector assignment, append new entries to the +** expression list pList. In the case of a subquery on the RHS, append +** TK_SELECT_COLUMN expressions. +*/ +SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* List to which to append. Might be NULL */ + IdList *pColumns, /* List of names of LHS of the assignment */ + Expr *pExpr /* Vector expression to be appended. Might be NULL */ +){ + sqlite3 *db = pParse->db; + int n; + int i; + int iFirst = pList ? pList->nExpr : 0; + /* pColumns can only be NULL due to an OOM but an OOM will cause an + ** exit prior to this routine being invoked */ + if( NEVER(pColumns==0) ) goto vector_append_error; + if( pExpr==0 ) goto vector_append_error; + + /* If the RHS is a vector, then we can immediately check to see that + ** the size of the RHS and LHS match. But if the RHS is a SELECT, + ** wildcards ("*") in the result set of the SELECT must be expanded before + ** we can do the size check, so defer the size check until code generation. + */ + if( pExpr->op!=TK_SELECT && pColumns->nId!=(n=sqlite3ExprVectorSize(pExpr)) ){ + sqlite3ErrorMsg(pParse, "%d columns assigned %d values", + pColumns->nId, n); + goto vector_append_error; + } + + for(i=0; i<pColumns->nId; i++){ + Expr *pSubExpr = sqlite3ExprForVectorField(pParse, pExpr, i, pColumns->nId); + assert( pSubExpr!=0 || db->mallocFailed ); + if( pSubExpr==0 ) continue; + pList = sqlite3ExprListAppend(pParse, pList, pSubExpr); + if( pList ){ + assert( pList->nExpr==iFirst+i+1 ); + pList->a[pList->nExpr-1].zEName = pColumns->a[i].zName; + pColumns->a[i].zName = 0; + } + } + + if( !db->mallocFailed && pExpr->op==TK_SELECT && ALWAYS(pList!=0) ){ + Expr *pFirst = pList->a[iFirst].pExpr; + assert( pFirst!=0 ); + assert( pFirst->op==TK_SELECT_COLUMN ); + + /* Store the SELECT statement in pRight so it will be deleted when + ** sqlite3ExprListDelete() is called */ + pFirst->pRight = pExpr; + pExpr = 0; + + /* Remember the size of the LHS in iTable so that we can check that + ** the RHS and LHS sizes match during code generation. */ + pFirst->iTable = pColumns->nId; + } + +vector_append_error: + sqlite3ExprUnmapAndDelete(pParse, pExpr); + sqlite3IdListDelete(db, pColumns); + return pList; +} + +/* +** Set the sort order for the last element on the given ExprList. +*/ +SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder, int eNulls){ + struct ExprList_item *pItem; + if( p==0 ) return; + assert( p->nExpr>0 ); + + assert( SQLITE_SO_UNDEFINED<0 && SQLITE_SO_ASC==0 && SQLITE_SO_DESC>0 ); + assert( iSortOrder==SQLITE_SO_UNDEFINED + || iSortOrder==SQLITE_SO_ASC + || iSortOrder==SQLITE_SO_DESC + ); + assert( eNulls==SQLITE_SO_UNDEFINED + || eNulls==SQLITE_SO_ASC + || eNulls==SQLITE_SO_DESC + ); + + pItem = &p->a[p->nExpr-1]; + assert( pItem->fg.bNulls==0 ); + if( iSortOrder==SQLITE_SO_UNDEFINED ){ + iSortOrder = SQLITE_SO_ASC; + } + pItem->fg.sortFlags = (u8)iSortOrder; + + if( eNulls!=SQLITE_SO_UNDEFINED ){ + pItem->fg.bNulls = 1; + if( iSortOrder!=eNulls ){ + pItem->fg.sortFlags |= KEYINFO_ORDER_BIGNULL; + } + } +} + +/* +** Set the ExprList.a[].zEName element of the most recently added item +** on the expression list. +** +** pList might be NULL following an OOM error. But pName should never be +** NULL. If a memory allocation fails, the pParse->db->mallocFailed flag +** is set. +*/ +SQLITE_PRIVATE void sqlite3ExprListSetName( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* List to which to add the span. */ + const Token *pName, /* Name to be added */ + int dequote /* True to cause the name to be dequoted */ +){ + assert( pList!=0 || pParse->db->mallocFailed!=0 ); + assert( pParse->eParseMode!=PARSE_MODE_UNMAP || dequote==0 ); + if( pList ){ + struct ExprList_item *pItem; + assert( pList->nExpr>0 ); + pItem = &pList->a[pList->nExpr-1]; + assert( pItem->zEName==0 ); + assert( pItem->fg.eEName==ENAME_NAME ); + pItem->zEName = sqlite3DbStrNDup(pParse->db, pName->z, pName->n); + if( dequote ){ + /* If dequote==0, then pName->z does not point to part of a DDL + ** statement handled by the parser. And so no token need be added + ** to the token-map. */ + sqlite3Dequote(pItem->zEName); + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, (const void*)pItem->zEName, pName); + } + } + } +} + +/* +** Set the ExprList.a[].zSpan element of the most recently added item +** on the expression list. +** +** pList might be NULL following an OOM error. But pSpan should never be +** NULL. If a memory allocation fails, the pParse->db->mallocFailed flag +** is set. +*/ +SQLITE_PRIVATE void sqlite3ExprListSetSpan( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* List to which to add the span. */ + const char *zStart, /* Start of the span */ + const char *zEnd /* End of the span */ +){ + sqlite3 *db = pParse->db; + assert( pList!=0 || db->mallocFailed!=0 ); + if( pList ){ + struct ExprList_item *pItem = &pList->a[pList->nExpr-1]; + assert( pList->nExpr>0 ); + if( pItem->zEName==0 ){ + pItem->zEName = sqlite3DbSpanDup(db, zStart, zEnd); + pItem->fg.eEName = ENAME_SPAN; + } + } +} + +/* +** If the expression list pEList contains more than iLimit elements, +** leave an error message in pParse. +*/ +SQLITE_PRIVATE void sqlite3ExprListCheckLength( + Parse *pParse, + ExprList *pEList, + const char *zObject +){ + int mx = pParse->db->aLimit[SQLITE_LIMIT_COLUMN]; + testcase( pEList && pEList->nExpr==mx ); + testcase( pEList && pEList->nExpr==mx+1 ); + if( pEList && pEList->nExpr>mx ){ + sqlite3ErrorMsg(pParse, "too many columns in %s", zObject); + } +} + +/* +** Delete an entire expression list. +*/ +static SQLITE_NOINLINE void exprListDeleteNN(sqlite3 *db, ExprList *pList){ + int i = pList->nExpr; + struct ExprList_item *pItem = pList->a; + assert( pList->nExpr>0 ); + assert( db!=0 ); + do{ + sqlite3ExprDelete(db, pItem->pExpr); + if( pItem->zEName ) sqlite3DbNNFreeNN(db, pItem->zEName); + pItem++; + }while( --i>0 ); + sqlite3DbNNFreeNN(db, pList); +} +SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){ + if( pList ) exprListDeleteNN(db, pList); +} + +/* +** Return the bitwise-OR of all Expr.flags fields in the given +** ExprList. +*/ +SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList *pList){ + int i; + u32 m = 0; + assert( pList!=0 ); + for(i=0; i<pList->nExpr; i++){ + Expr *pExpr = pList->a[i].pExpr; + assert( pExpr!=0 ); + m |= pExpr->flags; + } + return m; +} + +/* +** This is a SELECT-node callback for the expression walker that +** always "fails". By "fail" in this case, we mean set +** pWalker->eCode to zero and abort. +** +** This callback is used by multiple expression walkers. +*/ +SQLITE_PRIVATE int sqlite3SelectWalkFail(Walker *pWalker, Select *NotUsed){ + UNUSED_PARAMETER(NotUsed); + pWalker->eCode = 0; + return WRC_Abort; +} + +/* +** Check the input string to see if it is "true" or "false" (in any case). +** +** If the string is.... Return +** "true" EP_IsTrue +** "false" EP_IsFalse +** anything else 0 +*/ +SQLITE_PRIVATE u32 sqlite3IsTrueOrFalse(const char *zIn){ + if( sqlite3StrICmp(zIn, "true")==0 ) return EP_IsTrue; + if( sqlite3StrICmp(zIn, "false")==0 ) return EP_IsFalse; + return 0; +} + + +/* +** If the input expression is an ID with the name "true" or "false" +** then convert it into an TK_TRUEFALSE term. Return non-zero if +** the conversion happened, and zero if the expression is unaltered. +*/ +SQLITE_PRIVATE int sqlite3ExprIdToTrueFalse(Expr *pExpr){ + u32 v; + assert( pExpr->op==TK_ID || pExpr->op==TK_STRING ); + if( !ExprHasProperty(pExpr, EP_Quoted|EP_IntValue) + && (v = sqlite3IsTrueOrFalse(pExpr->u.zToken))!=0 + ){ + pExpr->op = TK_TRUEFALSE; + ExprSetProperty(pExpr, v); + return 1; + } + return 0; +} + +/* +** The argument must be a TK_TRUEFALSE Expr node. Return 1 if it is TRUE +** and 0 if it is FALSE. +*/ +SQLITE_PRIVATE int sqlite3ExprTruthValue(const Expr *pExpr){ + pExpr = sqlite3ExprSkipCollateAndLikely((Expr*)pExpr); + assert( pExpr->op==TK_TRUEFALSE ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + assert( sqlite3StrICmp(pExpr->u.zToken,"true")==0 + || sqlite3StrICmp(pExpr->u.zToken,"false")==0 ); + return pExpr->u.zToken[4]==0; +} + +/* +** If pExpr is an AND or OR expression, try to simplify it by eliminating +** terms that are always true or false. Return the simplified expression. +** Or return the original expression if no simplification is possible. +** +** Examples: +** +** (x<10) AND true => (x<10) +** (x<10) AND false => false +** (x<10) AND (y=22 OR false) => (x<10) AND (y=22) +** (x<10) AND (y=22 OR true) => (x<10) +** (y=22) OR true => true +*/ +SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){ + assert( pExpr!=0 ); + if( pExpr->op==TK_AND || pExpr->op==TK_OR ){ + Expr *pRight = sqlite3ExprSimplifiedAndOr(pExpr->pRight); + Expr *pLeft = sqlite3ExprSimplifiedAndOr(pExpr->pLeft); + if( ExprAlwaysTrue(pLeft) || ExprAlwaysFalse(pRight) ){ + pExpr = pExpr->op==TK_AND ? pRight : pLeft; + }else if( ExprAlwaysTrue(pRight) || ExprAlwaysFalse(pLeft) ){ + pExpr = pExpr->op==TK_AND ? pLeft : pRight; + } + } + return pExpr; +} + + +/* +** These routines are Walker callbacks used to check expressions to +** see if they are "constant" for some definition of constant. The +** Walker.eCode value determines the type of "constant" we are looking +** for. +** +** These callback routines are used to implement the following: +** +** sqlite3ExprIsConstant() pWalker->eCode==1 +** sqlite3ExprIsConstantNotJoin() pWalker->eCode==2 +** sqlite3ExprIsTableConstant() pWalker->eCode==3 +** sqlite3ExprIsConstantOrFunction() pWalker->eCode==4 or 5 +** +** In all cases, the callbacks set Walker.eCode=0 and abort if the expression +** is found to not be a constant. +** +** The sqlite3ExprIsConstantOrFunction() is used for evaluating DEFAULT +** expressions in a CREATE TABLE statement. The Walker.eCode value is 5 +** when parsing an existing schema out of the sqlite_schema table and 4 +** when processing a new CREATE TABLE statement. A bound parameter raises +** an error for new statements, but is silently converted +** to NULL for existing schemas. This allows sqlite_schema tables that +** contain a bound parameter because they were generated by older versions +** of SQLite to be parsed by newer versions of SQLite without raising a +** malformed schema error. +*/ +static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ + + /* If pWalker->eCode is 2 then any term of the expression that comes from + ** the ON or USING clauses of an outer join disqualifies the expression + ** from being considered constant. */ + if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_OuterON) ){ + pWalker->eCode = 0; + return WRC_Abort; + } + + switch( pExpr->op ){ + /* Consider functions to be constant if all their arguments are constant + ** and either pWalker->eCode==4 or 5 or the function has the + ** SQLITE_FUNC_CONST flag. */ + case TK_FUNCTION: + if( (pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_ConstFunc)) + && !ExprHasProperty(pExpr, EP_WinFunc) + ){ + if( pWalker->eCode==5 ) ExprSetProperty(pExpr, EP_FromDDL); + return WRC_Continue; + }else{ + pWalker->eCode = 0; + return WRC_Abort; + } + case TK_ID: + /* Convert "true" or "false" in a DEFAULT clause into the + ** appropriate TK_TRUEFALSE operator */ + if( sqlite3ExprIdToTrueFalse(pExpr) ){ + return WRC_Prune; + } + /* no break */ deliberate_fall_through + case TK_COLUMN: + case TK_AGG_FUNCTION: + case TK_AGG_COLUMN: + testcase( pExpr->op==TK_ID ); + testcase( pExpr->op==TK_COLUMN ); + testcase( pExpr->op==TK_AGG_FUNCTION ); + testcase( pExpr->op==TK_AGG_COLUMN ); + if( ExprHasProperty(pExpr, EP_FixedCol) && pWalker->eCode!=2 ){ + return WRC_Continue; + } + if( pWalker->eCode==3 && pExpr->iTable==pWalker->u.iCur ){ + return WRC_Continue; + } + /* no break */ deliberate_fall_through + case TK_IF_NULL_ROW: + case TK_REGISTER: + case TK_DOT: + testcase( pExpr->op==TK_REGISTER ); + testcase( pExpr->op==TK_IF_NULL_ROW ); + testcase( pExpr->op==TK_DOT ); + pWalker->eCode = 0; + return WRC_Abort; + case TK_VARIABLE: + if( pWalker->eCode==5 ){ + /* Silently convert bound parameters that appear inside of CREATE + ** statements into a NULL when parsing the CREATE statement text out + ** of the sqlite_schema table */ + pExpr->op = TK_NULL; + }else if( pWalker->eCode==4 ){ + /* A bound parameter in a CREATE statement that originates from + ** sqlite3_prepare() causes an error */ + pWalker->eCode = 0; + return WRC_Abort; + } + /* no break */ deliberate_fall_through + default: + testcase( pExpr->op==TK_SELECT ); /* sqlite3SelectWalkFail() disallows */ + testcase( pExpr->op==TK_EXISTS ); /* sqlite3SelectWalkFail() disallows */ + return WRC_Continue; + } +} +static int exprIsConst(Expr *p, int initFlag, int iCur){ + Walker w; + w.eCode = initFlag; + w.xExprCallback = exprNodeIsConstant; + w.xSelectCallback = sqlite3SelectWalkFail; +#ifdef SQLITE_DEBUG + w.xSelectCallback2 = sqlite3SelectWalkAssert2; +#endif + w.u.iCur = iCur; + sqlite3WalkExpr(&w, p); + return w.eCode; +} + +/* +** Walk an expression tree. Return non-zero if the expression is constant +** and 0 if it involves variables or function calls. +** +** For the purposes of this function, a double-quoted string (ex: "abc") +** is considered a variable but a single-quoted string (ex: 'abc') is +** a constant. +*/ +SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr *p){ + return exprIsConst(p, 1, 0); +} + +/* +** Walk an expression tree. Return non-zero if +** +** (1) the expression is constant, and +** (2) the expression does originate in the ON or USING clause +** of a LEFT JOIN, and +** (3) the expression does not contain any EP_FixedCol TK_COLUMN +** operands created by the constant propagation optimization. +** +** When this routine returns true, it indicates that the expression +** can be added to the pParse->pConstExpr list and evaluated once when +** the prepared statement starts up. See sqlite3ExprCodeRunJustOnce(). +*/ +SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){ + return exprIsConst(p, 2, 0); +} + +/* +** Walk an expression tree. Return non-zero if the expression is constant +** for any single row of the table with cursor iCur. In other words, the +** expression must not refer to any non-deterministic function nor any +** table other than iCur. +*/ +SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){ + return exprIsConst(p, 3, iCur); +} + +/* +** Check pExpr to see if it is an constraint on the single data source +** pSrc = &pSrcList->a[iSrc]. In other words, check to see if pExpr +** constrains pSrc but does not depend on any other tables or data +** sources anywhere else in the query. Return true (non-zero) if pExpr +** is a constraint on pSrc only. +** +** This is an optimization. False negatives will perhaps cause slower +** queries, but false positives will yield incorrect answers. So when in +** doubt, return 0. +** +** To be an single-source constraint, the following must be true: +** +** (1) pExpr cannot refer to any table other than pSrc->iCursor. +** +** (2) pExpr cannot use subqueries or non-deterministic functions. +** +** (3) pSrc cannot be part of the left operand for a RIGHT JOIN. +** (Is there some way to relax this constraint?) +** +** (4) If pSrc is the right operand of a LEFT JOIN, then... +** (4a) pExpr must come from an ON clause.. +** (4b) and specifically the ON clause associated with the LEFT JOIN. +** +** (5) If pSrc is not the right operand of a LEFT JOIN or the left +** operand of a RIGHT JOIN, then pExpr must be from the WHERE +** clause, not an ON clause. +** +** (6) Either: +** +** (6a) pExpr does not originate in an ON or USING clause, or +** +** (6b) The ON or USING clause from which pExpr is derived is +** not to the left of a RIGHT JOIN (or FULL JOIN). +** +** Without this restriction, accepting pExpr as a single-table +** constraint might move the the ON/USING filter expression +** from the left side of a RIGHT JOIN over to the right side, +** which leads to incorrect answers. See also restriction (9) +** on push-down. +*/ +SQLITE_PRIVATE int sqlite3ExprIsSingleTableConstraint( + Expr *pExpr, /* The constraint */ + const SrcList *pSrcList, /* Complete FROM clause */ + int iSrc /* Which element of pSrcList to use */ +){ + const SrcItem *pSrc = &pSrcList->a[iSrc]; + if( pSrc->fg.jointype & JT_LTORJ ){ + return 0; /* rule (3) */ + } + if( pSrc->fg.jointype & JT_LEFT ){ + if( !ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (4a) */ + if( pExpr->w.iJoin!=pSrc->iCursor ) return 0; /* rule (4b) */ + }else{ + if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (5) */ + } + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) /* (6a) */ + && (pSrcList->a[0].fg.jointype & JT_LTORJ)!=0 /* Fast pre-test of (6b) */ + ){ + int jj; + for(jj=0; jj<iSrc; jj++){ + if( pExpr->w.iJoin==pSrcList->a[jj].iCursor ){ + if( (pSrcList->a[jj].fg.jointype & JT_LTORJ)!=0 ){ + return 0; /* restriction (6) */ + } + break; + } + } + } + return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */ +} + + +/* +** sqlite3WalkExpr() callback used by sqlite3ExprIsConstantOrGroupBy(). +*/ +static int exprNodeIsConstantOrGroupBy(Walker *pWalker, Expr *pExpr){ + ExprList *pGroupBy = pWalker->u.pGroupBy; + int i; + + /* Check if pExpr is identical to any GROUP BY term. If so, consider + ** it constant. */ + for(i=0; i<pGroupBy->nExpr; i++){ + Expr *p = pGroupBy->a[i].pExpr; + if( sqlite3ExprCompare(0, pExpr, p, -1)<2 ){ + CollSeq *pColl = sqlite3ExprNNCollSeq(pWalker->pParse, p); + if( sqlite3IsBinary(pColl) ){ + return WRC_Prune; + } + } + } + + /* Check if pExpr is a sub-select. If so, consider it variable. */ + if( ExprUseXSelect(pExpr) ){ + pWalker->eCode = 0; + return WRC_Abort; + } + + return exprNodeIsConstant(pWalker, pExpr); +} + +/* +** Walk the expression tree passed as the first argument. Return non-zero +** if the expression consists entirely of constants or copies of terms +** in pGroupBy that sort with the BINARY collation sequence. +** +** This routine is used to determine if a term of the HAVING clause can +** be promoted into the WHERE clause. In order for such a promotion to work, +** the value of the HAVING clause term must be the same for all members of +** a "group". The requirement that the GROUP BY term must be BINARY +** assumes that no other collating sequence will have a finer-grained +** grouping than binary. In other words (A=B COLLATE binary) implies +** A=B in every other collating sequence. The requirement that the +** GROUP BY be BINARY is stricter than necessary. It would also work +** to promote HAVING clauses that use the same alternative collating +** sequence as the GROUP BY term, but that is much harder to check, +** alternative collating sequences are uncommon, and this is only an +** optimization, so we take the easy way out and simply require the +** GROUP BY to use the BINARY collating sequence. +*/ +SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse *pParse, Expr *p, ExprList *pGroupBy){ + Walker w; + w.eCode = 1; + w.xExprCallback = exprNodeIsConstantOrGroupBy; + w.xSelectCallback = 0; + w.u.pGroupBy = pGroupBy; + w.pParse = pParse; + sqlite3WalkExpr(&w, p); + return w.eCode; +} + +/* +** Walk an expression tree for the DEFAULT field of a column definition +** in a CREATE TABLE statement. Return non-zero if the expression is +** acceptable for use as a DEFAULT. That is to say, return non-zero if +** the expression is constant or a function call with constant arguments. +** Return and 0 if there are any variables. +** +** isInit is true when parsing from sqlite_schema. isInit is false when +** processing a new CREATE TABLE statement. When isInit is true, parameters +** (such as ? or $abc) in the expression are converted into NULL. When +** isInit is false, parameters raise an error. Parameters should not be +** allowed in a CREATE TABLE statement, but some legacy versions of SQLite +** allowed it, so we need to support it when reading sqlite_schema for +** backwards compatibility. +** +** If isInit is true, set EP_FromDDL on every TK_FUNCTION node. +** +** For the purposes of this function, a double-quoted string (ex: "abc") +** is considered a variable but a single-quoted string (ex: 'abc') is +** a constant. +*/ +SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){ + assert( isInit==0 || isInit==1 ); + return exprIsConst(p, 4+isInit, 0); +} + +#ifdef SQLITE_ENABLE_CURSOR_HINTS +/* +** Walk an expression tree. Return 1 if the expression contains a +** subquery of some kind. Return 0 if there are no subqueries. +*/ +SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr *p){ + Walker w; + w.eCode = 1; + w.xExprCallback = sqlite3ExprWalkNoop; + w.xSelectCallback = sqlite3SelectWalkFail; +#ifdef SQLITE_DEBUG + w.xSelectCallback2 = sqlite3SelectWalkAssert2; +#endif + sqlite3WalkExpr(&w, p); + return w.eCode==0; +} +#endif + +/* +** If the expression p codes a constant integer that is small enough +** to fit in a 32-bit integer, return 1 and put the value of the integer +** in *pValue. If the expression is not an integer or if it is too big +** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged. +*/ +SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr *p, int *pValue){ + int rc = 0; + if( NEVER(p==0) ) return 0; /* Used to only happen following on OOM */ + + /* If an expression is an integer literal that fits in a signed 32-bit + ** integer, then the EP_IntValue flag will have already been set */ + assert( p->op!=TK_INTEGER || (p->flags & EP_IntValue)!=0 + || sqlite3GetInt32(p->u.zToken, &rc)==0 ); + + if( p->flags & EP_IntValue ){ + *pValue = p->u.iValue; + return 1; + } + switch( p->op ){ + case TK_UPLUS: { + rc = sqlite3ExprIsInteger(p->pLeft, pValue); + break; + } + case TK_UMINUS: { + int v = 0; + if( sqlite3ExprIsInteger(p->pLeft, &v) ){ + assert( ((unsigned int)v)!=0x80000000 ); + *pValue = -v; + rc = 1; + } + break; + } + default: break; + } + return rc; +} + +/* +** Return FALSE if there is no chance that the expression can be NULL. +** +** If the expression might be NULL or if the expression is too complex +** to tell return TRUE. +** +** This routine is used as an optimization, to skip OP_IsNull opcodes +** when we know that a value cannot be NULL. Hence, a false positive +** (returning TRUE when in fact the expression can never be NULL) might +** be a small performance hit but is otherwise harmless. On the other +** hand, a false negative (returning FALSE when the result could be NULL) +** will likely result in an incorrect answer. So when in doubt, return +** TRUE. +*/ +SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){ + u8 op; + assert( p!=0 ); + while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ + p = p->pLeft; + assert( p!=0 ); + } + op = p->op; + if( op==TK_REGISTER ) op = p->op2; + switch( op ){ + case TK_INTEGER: + case TK_STRING: + case TK_FLOAT: + case TK_BLOB: + return 0; + case TK_COLUMN: + assert( ExprUseYTab(p) ); + return ExprHasProperty(p, EP_CanBeNull) || + p->y.pTab==0 || /* Reference to column of index on expression */ + (p->iColumn>=0 + && p->y.pTab->aCol!=0 /* Possible due to prior error */ + && p->y.pTab->aCol[p->iColumn].notNull==0); + default: + return 1; + } +} + +/* +** Return TRUE if the given expression is a constant which would be +** unchanged by OP_Affinity with the affinity given in the second +** argument. +** +** This routine is used to determine if the OP_Affinity operation +** can be omitted. When in doubt return FALSE. A false negative +** is harmless. A false positive, however, can result in the wrong +** answer. +*/ +SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr *p, char aff){ + u8 op; + int unaryMinus = 0; + if( aff==SQLITE_AFF_BLOB ) return 1; + while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ + if( p->op==TK_UMINUS ) unaryMinus = 1; + p = p->pLeft; + } + op = p->op; + if( op==TK_REGISTER ) op = p->op2; + switch( op ){ + case TK_INTEGER: { + return aff>=SQLITE_AFF_NUMERIC; + } + case TK_FLOAT: { + return aff>=SQLITE_AFF_NUMERIC; + } + case TK_STRING: { + return !unaryMinus && aff==SQLITE_AFF_TEXT; + } + case TK_BLOB: { + return !unaryMinus; + } + case TK_COLUMN: { + assert( p->iTable>=0 ); /* p cannot be part of a CHECK constraint */ + return aff>=SQLITE_AFF_NUMERIC && p->iColumn<0; + } + default: { + return 0; + } + } +} + +/* +** Return TRUE if the given string is a row-id column name. +*/ +SQLITE_PRIVATE int sqlite3IsRowid(const char *z){ + if( sqlite3StrICmp(z, "_ROWID_")==0 ) return 1; + if( sqlite3StrICmp(z, "ROWID")==0 ) return 1; + if( sqlite3StrICmp(z, "OID")==0 ) return 1; + return 0; +} + +/* +** pX is the RHS of an IN operator. If pX is a SELECT statement +** that can be simplified to a direct table access, then return +** a pointer to the SELECT statement. If pX is not a SELECT statement, +** or if the SELECT statement needs to be materialized into a transient +** table, then return NULL. +*/ +#ifndef SQLITE_OMIT_SUBQUERY +static Select *isCandidateForInOpt(const Expr *pX){ + Select *p; + SrcList *pSrc; + ExprList *pEList; + Table *pTab; + int i; + if( !ExprUseXSelect(pX) ) return 0; /* Not a subquery */ + if( ExprHasProperty(pX, EP_VarSelect) ) return 0; /* Correlated subq */ + p = pX->x.pSelect; + if( p->pPrior ) return 0; /* Not a compound SELECT */ + if( p->selFlags & (SF_Distinct|SF_Aggregate) ){ + testcase( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct ); + testcase( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Aggregate ); + return 0; /* No DISTINCT keyword and no aggregate functions */ + } + assert( p->pGroupBy==0 ); /* Has no GROUP BY clause */ + if( p->pLimit ) return 0; /* Has no LIMIT clause */ + if( p->pWhere ) return 0; /* Has no WHERE clause */ + pSrc = p->pSrc; + assert( pSrc!=0 ); + if( pSrc->nSrc!=1 ) return 0; /* Single term in FROM clause */ + if( pSrc->a[0].pSelect ) return 0; /* FROM is not a subquery or view */ + pTab = pSrc->a[0].pTab; + assert( pTab!=0 ); + assert( !IsView(pTab) ); /* FROM clause is not a view */ + if( IsVirtual(pTab) ) return 0; /* FROM clause not a virtual table */ + pEList = p->pEList; + assert( pEList!=0 ); + /* All SELECT results must be columns. */ + for(i=0; i<pEList->nExpr; i++){ + Expr *pRes = pEList->a[i].pExpr; + if( pRes->op!=TK_COLUMN ) return 0; + assert( pRes->iTable==pSrc->a[0].iCursor ); /* Not a correlated subquery */ + } + return p; +} +#endif /* SQLITE_OMIT_SUBQUERY */ + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Generate code that checks the left-most column of index table iCur to see if +** it contains any NULL entries. Cause the register at regHasNull to be set +** to a non-NULL value if iCur contains no NULLs. Cause register regHasNull +** to be set to NULL if iCur contains one or more NULL values. +*/ +static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){ + int addr1; + sqlite3VdbeAddOp2(v, OP_Integer, 0, regHasNull); + addr1 = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_Column, iCur, 0, regHasNull); + sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); + VdbeComment((v, "first_entry_in(%d)", iCur)); + sqlite3VdbeJumpHere(v, addr1); +} +#endif + + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** The argument is an IN operator with a list (not a subquery) on the +** right-hand side. Return TRUE if that list is constant. +*/ +static int sqlite3InRhsIsConstant(Expr *pIn){ + Expr *pLHS; + int res; + assert( !ExprHasProperty(pIn, EP_xIsSelect) ); + pLHS = pIn->pLeft; + pIn->pLeft = 0; + res = sqlite3ExprIsConstant(pIn); + pIn->pLeft = pLHS; + return res; +} +#endif + +/* +** This function is used by the implementation of the IN (...) operator. +** The pX parameter is the expression on the RHS of the IN operator, which +** might be either a list of expressions or a subquery. +** +** The job of this routine is to find or create a b-tree object that can +** be used either to test for membership in the RHS set or to iterate through +** all members of the RHS set, skipping duplicates. +** +** A cursor is opened on the b-tree object that is the RHS of the IN operator +** and the *piTab parameter is set to the index of that cursor. +** +** The returned value of this function indicates the b-tree type, as follows: +** +** IN_INDEX_ROWID - The cursor was opened on a database table. +** IN_INDEX_INDEX_ASC - The cursor was opened on an ascending index. +** IN_INDEX_INDEX_DESC - The cursor was opened on a descending index. +** IN_INDEX_EPH - The cursor was opened on a specially created and +** populated ephemeral table. +** IN_INDEX_NOOP - No cursor was allocated. The IN operator must be +** implemented as a sequence of comparisons. +** +** An existing b-tree might be used if the RHS expression pX is a simple +** subquery such as: +** +** SELECT <column1>, <column2>... FROM <table> +** +** If the RHS of the IN operator is a list or a more complex subquery, then +** an ephemeral table might need to be generated from the RHS and then +** pX->iTable made to point to the ephemeral table instead of an +** existing table. In this case, the creation and initialization of the +** ephemeral table might be put inside of a subroutine, the EP_Subrtn flag +** will be set on pX and the pX->y.sub fields will be set to show where +** the subroutine is coded. +** +** The inFlags parameter must contain, at a minimum, one of the bits +** IN_INDEX_MEMBERSHIP or IN_INDEX_LOOP but not both. If inFlags contains +** IN_INDEX_MEMBERSHIP, then the generated table will be used for a fast +** membership test. When the IN_INDEX_LOOP bit is set, the IN index will +** be used to loop over all values of the RHS of the IN operator. +** +** When IN_INDEX_LOOP is used (and the b-tree will be used to iterate +** through the set members) then the b-tree must not contain duplicates. +** An ephemeral table will be created unless the selected columns are guaranteed +** to be unique - either because it is an INTEGER PRIMARY KEY or due to +** a UNIQUE constraint or index. +** +** When IN_INDEX_MEMBERSHIP is used (and the b-tree will be used +** for fast set membership tests) then an ephemeral table must +** be used unless <columns> is a single INTEGER PRIMARY KEY column or an +** index can be found with the specified <columns> as its left-most. +** +** If the IN_INDEX_NOOP_OK and IN_INDEX_MEMBERSHIP are both set and +** if the RHS of the IN operator is a list (not a subquery) then this +** routine might decide that creating an ephemeral b-tree for membership +** testing is too expensive and return IN_INDEX_NOOP. In that case, the +** calling routine should implement the IN operator using a sequence +** of Eq or Ne comparison operations. +** +** When the b-tree is being used for membership tests, the calling function +** might need to know whether or not the RHS side of the IN operator +** contains a NULL. If prRhsHasNull is not a NULL pointer and +** if there is any chance that the (...) might contain a NULL value at +** runtime, then a register is allocated and the register number written +** to *prRhsHasNull. If there is no chance that the (...) contains a +** NULL value, then *prRhsHasNull is left unchanged. +** +** If a register is allocated and its location stored in *prRhsHasNull, then +** the value in that register will be NULL if the b-tree contains one or more +** NULL values, and it will be some non-NULL value if the b-tree contains no +** NULL values. +** +** If the aiMap parameter is not NULL, it must point to an array containing +** one element for each column returned by the SELECT statement on the RHS +** of the IN(...) operator. The i'th entry of the array is populated with the +** offset of the index column that matches the i'th column returned by the +** SELECT. For example, if the expression and selected index are: +** +** (?,?,?) IN (SELECT a, b, c FROM t1) +** CREATE INDEX i1 ON t1(b, c, a); +** +** then aiMap[] is populated with {2, 0, 1}. +*/ +#ifndef SQLITE_OMIT_SUBQUERY +SQLITE_PRIVATE int sqlite3FindInIndex( + Parse *pParse, /* Parsing context */ + Expr *pX, /* The IN expression */ + u32 inFlags, /* IN_INDEX_LOOP, _MEMBERSHIP, and/or _NOOP_OK */ + int *prRhsHasNull, /* Register holding NULL status. See notes */ + int *aiMap, /* Mapping from Index fields to RHS fields */ + int *piTab /* OUT: index to use */ +){ + Select *p; /* SELECT to the right of IN operator */ + int eType = 0; /* Type of RHS table. IN_INDEX_* */ + int iTab; /* Cursor of the RHS table */ + int mustBeUnique; /* True if RHS must be unique */ + Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ + + assert( pX->op==TK_IN ); + mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0; + iTab = pParse->nTab++; + + /* If the RHS of this IN(...) operator is a SELECT, and if it matters + ** whether or not the SELECT result contains NULL values, check whether + ** or not NULL is actually possible (it may not be, for example, due + ** to NOT NULL constraints in the schema). If no NULL values are possible, + ** set prRhsHasNull to 0 before continuing. */ + if( prRhsHasNull && ExprUseXSelect(pX) ){ + int i; + ExprList *pEList = pX->x.pSelect->pEList; + for(i=0; i<pEList->nExpr; i++){ + if( sqlite3ExprCanBeNull(pEList->a[i].pExpr) ) break; + } + if( i==pEList->nExpr ){ + prRhsHasNull = 0; + } + } + + /* Check to see if an existing table or index can be used to + ** satisfy the query. This is preferable to generating a new + ** ephemeral table. */ + if( pParse->nErr==0 && (p = isCandidateForInOpt(pX))!=0 ){ + sqlite3 *db = pParse->db; /* Database connection */ + Table *pTab; /* Table <table>. */ + int iDb; /* Database idx for pTab */ + ExprList *pEList = p->pEList; + int nExpr = pEList->nExpr; + + assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */ + assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ + assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ + pTab = p->pSrc->a[0].pTab; + + /* Code an OP_Transaction and OP_TableLock for <table>. */ + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb>=0 && iDb<SQLITE_MAX_DB ); + sqlite3CodeVerifySchema(pParse, iDb); + sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); + + assert(v); /* sqlite3GetVdbe() has always been previously called */ + if( nExpr==1 && pEList->a[0].pExpr->iColumn<0 ){ + /* The "x IN (SELECT rowid FROM table)" case */ + int iAddr = sqlite3VdbeAddOp0(v, OP_Once); + VdbeCoverage(v); + + sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); + eType = IN_INDEX_ROWID; + ExplainQueryPlan((pParse, 0, + "USING ROWID SEARCH ON TABLE %s FOR IN-OPERATOR",pTab->zName)); + sqlite3VdbeJumpHere(v, iAddr); + }else{ + Index *pIdx; /* Iterator variable */ + int affinity_ok = 1; + int i; + + /* Check that the affinity that will be used to perform each + ** comparison is the same as the affinity of each column in table + ** on the RHS of the IN operator. If it not, it is not possible to + ** use any index of the RHS table. */ + for(i=0; i<nExpr && affinity_ok; i++){ + Expr *pLhs = sqlite3VectorFieldSubexpr(pX->pLeft, i); + int iCol = pEList->a[i].pExpr->iColumn; + char idxaff = sqlite3TableColumnAffinity(pTab,iCol); /* RHS table */ + char cmpaff = sqlite3CompareAffinity(pLhs, idxaff); + testcase( cmpaff==SQLITE_AFF_BLOB ); + testcase( cmpaff==SQLITE_AFF_TEXT ); + switch( cmpaff ){ + case SQLITE_AFF_BLOB: + break; + case SQLITE_AFF_TEXT: + /* sqlite3CompareAffinity() only returns TEXT if one side or the + ** other has no affinity and the other side is TEXT. Hence, + ** the only way for cmpaff to be TEXT is for idxaff to be TEXT + ** and for the term on the LHS of the IN to have no affinity. */ + assert( idxaff==SQLITE_AFF_TEXT ); + break; + default: + affinity_ok = sqlite3IsNumericAffinity(idxaff); + } + } + + if( affinity_ok ){ + /* Search for an existing index that will work for this IN operator */ + for(pIdx=pTab->pIndex; pIdx && eType==0; pIdx=pIdx->pNext){ + Bitmask colUsed; /* Columns of the index used */ + Bitmask mCol; /* Mask for the current column */ + if( pIdx->nColumn<nExpr ) continue; + if( pIdx->pPartIdxWhere!=0 ) continue; + /* Maximum nColumn is BMS-2, not BMS-1, so that we can compute + ** BITMASK(nExpr) without overflowing */ + testcase( pIdx->nColumn==BMS-2 ); + testcase( pIdx->nColumn==BMS-1 ); + if( pIdx->nColumn>=BMS-1 ) continue; + if( mustBeUnique ){ + if( pIdx->nKeyCol>nExpr + ||(pIdx->nColumn>nExpr && !IsUniqueIndex(pIdx)) + ){ + continue; /* This index is not unique over the IN RHS columns */ + } + } + + colUsed = 0; /* Columns of index used so far */ + for(i=0; i<nExpr; i++){ + Expr *pLhs = sqlite3VectorFieldSubexpr(pX->pLeft, i); + Expr *pRhs = pEList->a[i].pExpr; + CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); + int j; + + for(j=0; j<nExpr; j++){ + if( pIdx->aiColumn[j]!=pRhs->iColumn ) continue; + assert( pIdx->azColl[j] ); + if( pReq!=0 && sqlite3StrICmp(pReq->zName, pIdx->azColl[j])!=0 ){ + continue; + } + break; + } + if( j==nExpr ) break; + mCol = MASKBIT(j); + if( mCol & colUsed ) break; /* Each column used only once */ + colUsed |= mCol; + if( aiMap ) aiMap[i] = j; + } + + assert( i==nExpr || colUsed!=(MASKBIT(nExpr)-1) ); + if( colUsed==(MASKBIT(nExpr)-1) ){ + /* If we reach this point, that means the index pIdx is usable */ + int iAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + ExplainQueryPlan((pParse, 0, + "USING INDEX %s FOR IN-OPERATOR",pIdx->zName)); + sqlite3VdbeAddOp3(v, OP_OpenRead, iTab, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + VdbeComment((v, "%s", pIdx->zName)); + assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 ); + eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0]; + + if( prRhsHasNull ){ +#ifdef SQLITE_ENABLE_COLUMN_USED_MASK + i64 mask = (1<<nExpr)-1; + sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, + iTab, 0, 0, (u8*)&mask, P4_INT64); +#endif + *prRhsHasNull = ++pParse->nMem; + if( nExpr==1 ){ + sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull); + } + } + sqlite3VdbeJumpHere(v, iAddr); + } + } /* End loop over indexes */ + } /* End if( affinity_ok ) */ + } /* End if not an rowid index */ + } /* End attempt to optimize using an index */ + + /* If no preexisting index is available for the IN clause + ** and IN_INDEX_NOOP is an allowed reply + ** and the RHS of the IN operator is a list, not a subquery + ** and the RHS is not constant or has two or fewer terms, + ** then it is not worth creating an ephemeral table to evaluate + ** the IN operator so return IN_INDEX_NOOP. + */ + if( eType==0 + && (inFlags & IN_INDEX_NOOP_OK) + && ExprUseXList(pX) + && (!sqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2) + ){ + pParse->nTab--; /* Back out the allocation of the unused cursor */ + iTab = -1; /* Cursor is not allocated */ + eType = IN_INDEX_NOOP; + } + + if( eType==0 ){ + /* Could not find an existing table or index to use as the RHS b-tree. + ** We will have to generate an ephemeral table to do the job. + */ + u32 savedNQueryLoop = pParse->nQueryLoop; + int rMayHaveNull = 0; + eType = IN_INDEX_EPH; + if( inFlags & IN_INDEX_LOOP ){ + pParse->nQueryLoop = 0; + }else if( prRhsHasNull ){ + *prRhsHasNull = rMayHaveNull = ++pParse->nMem; + } + assert( pX->op==TK_IN ); + sqlite3CodeRhsOfIN(pParse, pX, iTab); + if( rMayHaveNull ){ + sqlite3SetHasNullFlag(v, iTab, rMayHaveNull); + } + pParse->nQueryLoop = savedNQueryLoop; + } + + if( aiMap && eType!=IN_INDEX_INDEX_ASC && eType!=IN_INDEX_INDEX_DESC ){ + int i, n; + n = sqlite3ExprVectorSize(pX->pLeft); + for(i=0; i<n; i++) aiMap[i] = i; + } + *piTab = iTab; + return eType; +} +#endif + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Argument pExpr is an (?, ?...) IN(...) expression. This +** function allocates and returns a nul-terminated string containing +** the affinities to be used for each column of the comparison. +** +** It is the responsibility of the caller to ensure that the returned +** string is eventually freed using sqlite3DbFree(). +*/ +static char *exprINAffinity(Parse *pParse, const Expr *pExpr){ + Expr *pLeft = pExpr->pLeft; + int nVal = sqlite3ExprVectorSize(pLeft); + Select *pSelect = ExprUseXSelect(pExpr) ? pExpr->x.pSelect : 0; + char *zRet; + + assert( pExpr->op==TK_IN ); + zRet = sqlite3DbMallocRaw(pParse->db, nVal+1); + if( zRet ){ + int i; + for(i=0; i<nVal; i++){ + Expr *pA = sqlite3VectorFieldSubexpr(pLeft, i); + char a = sqlite3ExprAffinity(pA); + if( pSelect ){ + zRet[i] = sqlite3CompareAffinity(pSelect->pEList->a[i].pExpr, a); + }else{ + zRet[i] = a; + } + } + zRet[nVal] = '\0'; + } + return zRet; +} +#endif + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Load the Parse object passed as the first argument with an error +** message of the form: +** +** "sub-select returns N columns - expected M" +*/ +SQLITE_PRIVATE void sqlite3SubselectError(Parse *pParse, int nActual, int nExpect){ + if( pParse->nErr==0 ){ + const char *zFmt = "sub-select returns %d columns - expected %d"; + sqlite3ErrorMsg(pParse, zFmt, nActual, nExpect); + } +} +#endif + +/* +** Expression pExpr is a vector that has been used in a context where +** it is not permitted. If pExpr is a sub-select vector, this routine +** loads the Parse object with a message of the form: +** +** "sub-select returns N columns - expected 1" +** +** Or, if it is a regular scalar vector: +** +** "row value misused" +*/ +SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ +#ifndef SQLITE_OMIT_SUBQUERY + if( ExprUseXSelect(pExpr) ){ + sqlite3SubselectError(pParse, pExpr->x.pSelect->pEList->nExpr, 1); + }else +#endif + { + sqlite3ErrorMsg(pParse, "row value misused"); + } +} + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Generate code that will construct an ephemeral table containing all terms +** in the RHS of an IN operator. The IN operator can be in either of two +** forms: +** +** x IN (4,5,11) -- IN operator with list on right-hand side +** x IN (SELECT a FROM b) -- IN operator with subquery on the right +** +** The pExpr parameter is the IN operator. The cursor number for the +** constructed ephemeral table is returned. The first time the ephemeral +** table is computed, the cursor number is also stored in pExpr->iTable, +** however the cursor number returned might not be the same, as it might +** have been duplicated using OP_OpenDup. +** +** If the LHS expression ("x" in the examples) is a column value, or +** the SELECT statement returns a column value, then the affinity of that +** column is used to build the index keys. If both 'x' and the +** SELECT... statement are columns, then numeric affinity is used +** if either column has NUMERIC or INTEGER affinity. If neither +** 'x' nor the SELECT... statement are columns, then numeric affinity +** is used. +*/ +SQLITE_PRIVATE void sqlite3CodeRhsOfIN( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* The IN operator */ + int iTab /* Use this cursor number */ +){ + int addrOnce = 0; /* Address of the OP_Once instruction at top */ + int addr; /* Address of OP_OpenEphemeral instruction */ + Expr *pLeft; /* the LHS of the IN operator */ + KeyInfo *pKeyInfo = 0; /* Key information */ + int nVal; /* Size of vector pLeft */ + Vdbe *v; /* The prepared statement under construction */ + + v = pParse->pVdbe; + assert( v!=0 ); + + /* The evaluation of the IN must be repeated every time it + ** is encountered if any of the following is true: + ** + ** * The right-hand side is a correlated subquery + ** * The right-hand side is an expression list containing variables + ** * We are inside a trigger + ** + ** If all of the above are false, then we can compute the RHS just once + ** and reuse it many names. + */ + if( !ExprHasProperty(pExpr, EP_VarSelect) && pParse->iSelfTab==0 ){ + /* Reuse of the RHS is allowed */ + /* If this routine has already been coded, but the previous code + ** might not have been invoked yet, so invoke it now as a subroutine. + */ + if( ExprHasProperty(pExpr, EP_Subrtn) ){ + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + if( ExprUseXSelect(pExpr) ){ + ExplainQueryPlan((pParse, 0, "REUSE LIST SUBQUERY %d", + pExpr->x.pSelect->selId)); + } + assert( ExprUseYSub(pExpr) ); + sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr); + assert( iTab!=pExpr->iTable ); + sqlite3VdbeAddOp2(v, OP_OpenDup, iTab, pExpr->iTable); + sqlite3VdbeJumpHere(v, addrOnce); + return; + } + + /* Begin coding the subroutine */ + assert( !ExprUseYWin(pExpr) ); + ExprSetProperty(pExpr, EP_Subrtn); + assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); + pExpr->y.sub.regReturn = ++pParse->nMem; + pExpr->y.sub.iAddr = + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1; + + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + } + + /* Check to see if this is a vector IN operator */ + pLeft = pExpr->pLeft; + nVal = sqlite3ExprVectorSize(pLeft); + + /* Construct the ephemeral table that will contain the content of + ** RHS of the IN operator. + */ + pExpr->iTable = iTab; + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, nVal); +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + if( ExprUseXSelect(pExpr) ){ + VdbeComment((v, "Result of SELECT %u", pExpr->x.pSelect->selId)); + }else{ + VdbeComment((v, "RHS of IN operator")); + } +#endif + pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nVal, 1); + + if( ExprUseXSelect(pExpr) ){ + /* Case 1: expr IN (SELECT ...) + ** + ** Generate code to write the results of the select into the temporary + ** table allocated and opened above. + */ + Select *pSelect = pExpr->x.pSelect; + ExprList *pEList = pSelect->pEList; + + ExplainQueryPlan((pParse, 1, "%sLIST SUBQUERY %d", + addrOnce?"":"CORRELATED ", pSelect->selId + )); + /* If the LHS and RHS of the IN operator do not match, that + ** error will have been caught long before we reach this point. */ + if( ALWAYS(pEList->nExpr==nVal) ){ + Select *pCopy; + SelectDest dest; + int i; + int rc; + sqlite3SelectDestInit(&dest, SRT_Set, iTab); + dest.zAffSdst = exprINAffinity(pParse, pExpr); + pSelect->iLimit = 0; + testcase( pSelect->selFlags & SF_Distinct ); + testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ + pCopy = sqlite3SelectDup(pParse->db, pSelect, 0); + rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest); + sqlite3SelectDelete(pParse->db, pCopy); + sqlite3DbFree(pParse->db, dest.zAffSdst); + if( rc ){ + sqlite3KeyInfoUnref(pKeyInfo); + return; + } + assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ + assert( pEList!=0 ); + assert( pEList->nExpr>0 ); + assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); + for(i=0; i<nVal; i++){ + Expr *p = sqlite3VectorFieldSubexpr(pLeft, i); + pKeyInfo->aColl[i] = sqlite3BinaryCompareCollSeq( + pParse, p, pEList->a[i].pExpr + ); + } + } + }else if( ALWAYS(pExpr->x.pList!=0) ){ + /* Case 2: expr IN (exprlist) + ** + ** For each expression, build an index key from the evaluation and + ** store it in the temporary table. If <expr> is a column, then use + ** that columns affinity when building index keys. If <expr> is not + ** a column, use numeric affinity. + */ + char affinity; /* Affinity of the LHS of the IN */ + int i; + ExprList *pList = pExpr->x.pList; + struct ExprList_item *pItem; + int r1, r2; + affinity = sqlite3ExprAffinity(pLeft); + if( affinity<=SQLITE_AFF_NONE ){ + affinity = SQLITE_AFF_BLOB; + }else if( affinity==SQLITE_AFF_REAL ){ + affinity = SQLITE_AFF_NUMERIC; + } + if( pKeyInfo ){ + assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); + pKeyInfo->aColl[0] = sqlite3ExprCollSeq(pParse, pExpr->pLeft); + } + + /* Loop through each expression in <exprlist>. */ + r1 = sqlite3GetTempReg(pParse); + r2 = sqlite3GetTempReg(pParse); + for(i=pList->nExpr, pItem=pList->a; i>0; i--, pItem++){ + Expr *pE2 = pItem->pExpr; + + /* If the expression is not constant then we will need to + ** disable the test that was generated above that makes sure + ** this code only executes once. Because for a non-constant + ** expression we need to rerun this code each time. + */ + if( addrOnce && !sqlite3ExprIsConstant(pE2) ){ + sqlite3VdbeChangeToNoop(v, addrOnce-1); + sqlite3VdbeChangeToNoop(v, addrOnce); + ExprClearProperty(pExpr, EP_Subrtn); + addrOnce = 0; + } + + /* Evaluate the expression and insert it into the temp table */ + sqlite3ExprCode(pParse, pE2, r1); + sqlite3VdbeAddOp4(v, OP_MakeRecord, r1, 1, r2, &affinity, 1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r2, r1, 1); + } + sqlite3ReleaseTempReg(pParse, r1); + sqlite3ReleaseTempReg(pParse, r2); + } + if( pKeyInfo ){ + sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO); + } + if( addrOnce ){ + sqlite3VdbeAddOp1(v, OP_NullRow, iTab); + sqlite3VdbeJumpHere(v, addrOnce); + /* Subroutine return */ + assert( ExprUseYSub(pExpr) ); + assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn + || pParse->nErr ); + sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr, 1); + VdbeCoverage(v); + sqlite3ClearTempRegCache(pParse); + } +} +#endif /* SQLITE_OMIT_SUBQUERY */ + +/* +** Generate code for scalar subqueries used as a subquery expression +** or EXISTS operator: +** +** (SELECT a FROM b) -- subquery +** EXISTS (SELECT a FROM b) -- EXISTS subquery +** +** The pExpr parameter is the SELECT or EXISTS operator to be coded. +** +** Return the register that holds the result. For a multi-column SELECT, +** the result is stored in a contiguous array of registers and the +** return value is the register of the left-most result column. +** Return 0 if an error occurs. +*/ +#ifndef SQLITE_OMIT_SUBQUERY +SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ + int addrOnce = 0; /* Address of OP_Once at top of subroutine */ + int rReg = 0; /* Register storing resulting */ + Select *pSel; /* SELECT statement to encode */ + SelectDest dest; /* How to deal with SELECT result */ + int nReg; /* Registers to allocate */ + Expr *pLimit; /* New limit expression */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExplain; /* Address of OP_Explain instruction */ +#endif + + Vdbe *v = pParse->pVdbe; + assert( v!=0 ); + if( pParse->nErr ) return 0; + testcase( pExpr->op==TK_EXISTS ); + testcase( pExpr->op==TK_SELECT ); + assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT ); + assert( ExprUseXSelect(pExpr) ); + pSel = pExpr->x.pSelect; + + /* If this routine has already been coded, then invoke it as a + ** subroutine. */ + if( ExprHasProperty(pExpr, EP_Subrtn) ){ + ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId)); + assert( ExprUseYSub(pExpr) ); + sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr); + return pExpr->iTable; + } + + /* Begin coding the subroutine */ + assert( !ExprUseYWin(pExpr) ); + assert( !ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) ); + ExprSetProperty(pExpr, EP_Subrtn); + pExpr->y.sub.regReturn = ++pParse->nMem; + pExpr->y.sub.iAddr = + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1; + + /* The evaluation of the EXISTS/SELECT must be repeated every time it + ** is encountered if any of the following is true: + ** + ** * The right-hand side is a correlated subquery + ** * The right-hand side is an expression list containing variables + ** * We are inside a trigger + ** + ** If all of the above are false, then we can run this code just once + ** save the results, and reuse the same result on subsequent invocations. + */ + if( !ExprHasProperty(pExpr, EP_VarSelect) ){ + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + } + + /* For a SELECT, generate code to put the values for all columns of + ** the first row into an array of registers and return the index of + ** the first register. + ** + ** If this is an EXISTS, write an integer 0 (not exists) or 1 (exists) + ** into a register and return that register number. + ** + ** In both cases, the query is augmented with "LIMIT 1". Any + ** preexisting limit is discarded in place of the new LIMIT 1. + */ + ExplainQueryPlan2(addrExplain, (pParse, 1, "%sSCALAR SUBQUERY %d", + addrOnce?"":"CORRELATED ", pSel->selId)); + sqlite3VdbeScanStatusCounters(v, addrExplain, addrExplain, -1); + nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1; + sqlite3SelectDestInit(&dest, 0, pParse->nMem+1); + pParse->nMem += nReg; + if( pExpr->op==TK_SELECT ){ + dest.eDest = SRT_Mem; + dest.iSdst = dest.iSDParm; + dest.nSdst = nReg; + sqlite3VdbeAddOp3(v, OP_Null, 0, dest.iSDParm, dest.iSDParm+nReg-1); + VdbeComment((v, "Init subquery result")); + }else{ + dest.eDest = SRT_Exists; + sqlite3VdbeAddOp2(v, OP_Integer, 0, dest.iSDParm); + VdbeComment((v, "Init EXISTS result")); + } + if( pSel->pLimit ){ + /* The subquery already has a limit. If the pre-existing limit is X + ** then make the new limit X<>0 so that the new limit is either 1 or 0 */ + sqlite3 *db = pParse->db; + pLimit = sqlite3Expr(db, TK_INTEGER, "0"); + if( pLimit ){ + pLimit->affExpr = SQLITE_AFF_NUMERIC; + pLimit = sqlite3PExpr(pParse, TK_NE, + sqlite3ExprDup(db, pSel->pLimit->pLeft, 0), pLimit); + } + sqlite3ExprDeferredDelete(pParse, pSel->pLimit->pLeft); + pSel->pLimit->pLeft = pLimit; + }else{ + /* If there is no pre-existing limit add a limit of 1 */ + pLimit = sqlite3Expr(pParse->db, TK_INTEGER, "1"); + pSel->pLimit = sqlite3PExpr(pParse, TK_LIMIT, pLimit, 0); + } + pSel->iLimit = 0; + if( sqlite3Select(pParse, pSel, &dest) ){ + pExpr->op2 = pExpr->op; + pExpr->op = TK_ERROR; + return 0; + } + pExpr->iTable = rReg = dest.iSDParm; + ExprSetVVAProperty(pExpr, EP_NoReduce); + if( addrOnce ){ + sqlite3VdbeJumpHere(v, addrOnce); + } + sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); + + /* Subroutine return */ + assert( ExprUseYSub(pExpr) ); + assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn + || pParse->nErr ); + sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr, 1); + VdbeCoverage(v); + sqlite3ClearTempRegCache(pParse); + return rReg; +} +#endif /* SQLITE_OMIT_SUBQUERY */ + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Expr pIn is an IN(...) expression. This function checks that the +** sub-select on the RHS of the IN() operator has the same number of +** columns as the vector on the LHS. Or, if the RHS of the IN() is not +** a sub-query, that the LHS is a vector of size 1. +*/ +SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ + int nVector = sqlite3ExprVectorSize(pIn->pLeft); + if( ExprUseXSelect(pIn) && !pParse->db->mallocFailed ){ + if( nVector!=pIn->x.pSelect->pEList->nExpr ){ + sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector); + return 1; + } + }else if( nVector!=1 ){ + sqlite3VectorErrorMsg(pParse, pIn->pLeft); + return 1; + } + return 0; +} +#endif + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Generate code for an IN expression. +** +** x IN (SELECT ...) +** x IN (value, value, ...) +** +** The left-hand side (LHS) is a scalar or vector expression. The +** right-hand side (RHS) is an array of zero or more scalar values, or a +** subquery. If the RHS is a subquery, the number of result columns must +** match the number of columns in the vector on the LHS. If the RHS is +** a list of values, the LHS must be a scalar. +** +** The IN operator is true if the LHS value is contained within the RHS. +** The result is false if the LHS is definitely not in the RHS. The +** result is NULL if the presence of the LHS in the RHS cannot be +** determined due to NULLs. +** +** This routine generates code that jumps to destIfFalse if the LHS is not +** contained within the RHS. If due to NULLs we cannot determine if the LHS +** is contained in the RHS then jump to destIfNull. If the LHS is contained +** within the RHS then fall through. +** +** See the separate in-operator.md documentation file in the canonical +** SQLite source tree for additional information. +*/ +static void sqlite3ExprCodeIN( + Parse *pParse, /* Parsing and code generating context */ + Expr *pExpr, /* The IN expression */ + int destIfFalse, /* Jump here if LHS is not contained in the RHS */ + int destIfNull /* Jump here if the results are unknown due to NULLs */ +){ + int rRhsHasNull = 0; /* Register that is true if RHS contains NULL values */ + int eType; /* Type of the RHS */ + int rLhs; /* Register(s) holding the LHS values */ + int rLhsOrig; /* LHS values prior to reordering by aiMap[] */ + Vdbe *v; /* Statement under construction */ + int *aiMap = 0; /* Map from vector field to index column */ + char *zAff = 0; /* Affinity string for comparisons */ + int nVector; /* Size of vectors for this IN operator */ + int iDummy; /* Dummy parameter to exprCodeVector() */ + Expr *pLeft; /* The LHS of the IN operator */ + int i; /* loop counter */ + int destStep2; /* Where to jump when NULLs seen in step 2 */ + int destStep6 = 0; /* Start of code for Step 6 */ + int addrTruthOp; /* Address of opcode that determines the IN is true */ + int destNotNull; /* Jump here if a comparison is not true in step 6 */ + int addrTop; /* Top of the step-6 loop */ + int iTab = 0; /* Index to use */ + u8 okConstFactor = pParse->okConstFactor; + + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); + pLeft = pExpr->pLeft; + if( sqlite3ExprCheckIN(pParse, pExpr) ) return; + zAff = exprINAffinity(pParse, pExpr); + nVector = sqlite3ExprVectorSize(pExpr->pLeft); + aiMap = (int*)sqlite3DbMallocZero( + pParse->db, nVector*(sizeof(int) + sizeof(char)) + 1 + ); + if( pParse->db->mallocFailed ) goto sqlite3ExprCodeIN_oom_error; + + /* Attempt to compute the RHS. After this step, if anything other than + ** IN_INDEX_NOOP is returned, the table opened with cursor iTab + ** contains the values that make up the RHS. If IN_INDEX_NOOP is returned, + ** the RHS has not yet been coded. */ + v = pParse->pVdbe; + assert( v!=0 ); /* OOM detected prior to this routine */ + VdbeNoopComment((v, "begin IN expr")); + eType = sqlite3FindInIndex(pParse, pExpr, + IN_INDEX_MEMBERSHIP | IN_INDEX_NOOP_OK, + destIfFalse==destIfNull ? 0 : &rRhsHasNull, + aiMap, &iTab); + + assert( pParse->nErr || nVector==1 || eType==IN_INDEX_EPH + || eType==IN_INDEX_INDEX_ASC || eType==IN_INDEX_INDEX_DESC + ); +#ifdef SQLITE_DEBUG + /* Confirm that aiMap[] contains nVector integer values between 0 and + ** nVector-1. */ + for(i=0; i<nVector; i++){ + int j, cnt; + for(cnt=j=0; j<nVector; j++) if( aiMap[j]==i ) cnt++; + assert( cnt==1 ); + } +#endif + + /* Code the LHS, the <expr> from "<expr> IN (...)". If the LHS is a + ** vector, then it is stored in an array of nVector registers starting + ** at r1. + ** + ** sqlite3FindInIndex() might have reordered the fields of the LHS vector + ** so that the fields are in the same order as an existing index. The + ** aiMap[] array contains a mapping from the original LHS field order to + ** the field order that matches the RHS index. + ** + ** Avoid factoring the LHS of the IN(...) expression out of the loop, + ** even if it is constant, as OP_Affinity may be used on the register + ** by code generated below. */ + assert( pParse->okConstFactor==okConstFactor ); + pParse->okConstFactor = 0; + rLhsOrig = exprCodeVector(pParse, pLeft, &iDummy); + pParse->okConstFactor = okConstFactor; + for(i=0; i<nVector && aiMap[i]==i; i++){} /* Are LHS fields reordered? */ + if( i==nVector ){ + /* LHS fields are not reordered */ + rLhs = rLhsOrig; + }else{ + /* Need to reorder the LHS fields according to aiMap */ + rLhs = sqlite3GetTempRange(pParse, nVector); + for(i=0; i<nVector; i++){ + sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0); + } + } + + /* If sqlite3FindInIndex() did not find or create an index that is + ** suitable for evaluating the IN operator, then evaluate using a + ** sequence of comparisons. + ** + ** This is step (1) in the in-operator.md optimized algorithm. + */ + if( eType==IN_INDEX_NOOP ){ + ExprList *pList; + CollSeq *pColl; + int labelOk = sqlite3VdbeMakeLabel(pParse); + int r2, regToFree; + int regCkNull = 0; + int ii; + assert( ExprUseXList(pExpr) ); + pList = pExpr->x.pList; + pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); + if( destIfNull!=destIfFalse ){ + regCkNull = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_BitAnd, rLhs, rLhs, regCkNull); + } + for(ii=0; ii<pList->nExpr; ii++){ + r2 = sqlite3ExprCodeTemp(pParse, pList->a[ii].pExpr, &regToFree); + if( regCkNull && sqlite3ExprCanBeNull(pList->a[ii].pExpr) ){ + sqlite3VdbeAddOp3(v, OP_BitAnd, regCkNull, r2, regCkNull); + } + sqlite3ReleaseTempReg(pParse, regToFree); + if( ii<pList->nExpr-1 || destIfNull!=destIfFalse ){ + int op = rLhs!=r2 ? OP_Eq : OP_NotNull; + sqlite3VdbeAddOp4(v, op, rLhs, labelOk, r2, + (void*)pColl, P4_COLLSEQ); + VdbeCoverageIf(v, ii<pList->nExpr-1 && op==OP_Eq); + VdbeCoverageIf(v, ii==pList->nExpr-1 && op==OP_Eq); + VdbeCoverageIf(v, ii<pList->nExpr-1 && op==OP_NotNull); + VdbeCoverageIf(v, ii==pList->nExpr-1 && op==OP_NotNull); + sqlite3VdbeChangeP5(v, zAff[0]); + }else{ + int op = rLhs!=r2 ? OP_Ne : OP_IsNull; + assert( destIfNull==destIfFalse ); + sqlite3VdbeAddOp4(v, op, rLhs, destIfFalse, r2, + (void*)pColl, P4_COLLSEQ); + VdbeCoverageIf(v, op==OP_Ne); + VdbeCoverageIf(v, op==OP_IsNull); + sqlite3VdbeChangeP5(v, zAff[0] | SQLITE_JUMPIFNULL); + } + } + if( regCkNull ){ + sqlite3VdbeAddOp2(v, OP_IsNull, regCkNull, destIfNull); VdbeCoverage(v); + sqlite3VdbeGoto(v, destIfFalse); + } + sqlite3VdbeResolveLabel(v, labelOk); + sqlite3ReleaseTempReg(pParse, regCkNull); + goto sqlite3ExprCodeIN_finished; + } + + /* Step 2: Check to see if the LHS contains any NULL columns. If the + ** LHS does contain NULLs then the result must be either FALSE or NULL. + ** We will then skip the binary search of the RHS. + */ + if( destIfNull==destIfFalse ){ + destStep2 = destIfFalse; + }else{ + destStep2 = destStep6 = sqlite3VdbeMakeLabel(pParse); + } + for(i=0; i<nVector; i++){ + Expr *p = sqlite3VectorFieldSubexpr(pExpr->pLeft, i); + if( pParse->nErr ) goto sqlite3ExprCodeIN_oom_error; + if( sqlite3ExprCanBeNull(p) ){ + sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2); + VdbeCoverage(v); + } + } + + /* Step 3. The LHS is now known to be non-NULL. Do the binary search + ** of the RHS using the LHS as a probe. If found, the result is + ** true. + */ + if( eType==IN_INDEX_ROWID ){ + /* In this case, the RHS is the ROWID of table b-tree and so we also + ** know that the RHS is non-NULL. Hence, we combine steps 3 and 4 + ** into a single opcode. */ + sqlite3VdbeAddOp3(v, OP_SeekRowid, iTab, destIfFalse, rLhs); + VdbeCoverage(v); + addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */ + }else{ + sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); + if( destIfFalse==destIfNull ){ + /* Combine Step 3 and Step 5 into a single opcode */ + sqlite3VdbeAddOp4Int(v, OP_NotFound, iTab, destIfFalse, + rLhs, nVector); VdbeCoverage(v); + goto sqlite3ExprCodeIN_finished; + } + /* Ordinary Step 3, for the case where FALSE and NULL are distinct */ + addrTruthOp = sqlite3VdbeAddOp4Int(v, OP_Found, iTab, 0, + rLhs, nVector); VdbeCoverage(v); + } + + /* Step 4. If the RHS is known to be non-NULL and we did not find + ** an match on the search above, then the result must be FALSE. + */ + if( rRhsHasNull && nVector==1 ){ + sqlite3VdbeAddOp2(v, OP_NotNull, rRhsHasNull, destIfFalse); + VdbeCoverage(v); + } + + /* Step 5. If we do not care about the difference between NULL and + ** FALSE, then just return false. + */ + if( destIfFalse==destIfNull ) sqlite3VdbeGoto(v, destIfFalse); + + /* Step 6: Loop through rows of the RHS. Compare each row to the LHS. + ** If any comparison is NULL, then the result is NULL. If all + ** comparisons are FALSE then the final result is FALSE. + ** + ** For a scalar LHS, it is sufficient to check just the first row + ** of the RHS. + */ + if( destStep6 ) sqlite3VdbeResolveLabel(v, destStep6); + addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, destIfFalse); + VdbeCoverage(v); + if( nVector>1 ){ + destNotNull = sqlite3VdbeMakeLabel(pParse); + }else{ + /* For nVector==1, combine steps 6 and 7 by immediately returning + ** FALSE if the first comparison is not NULL */ + destNotNull = destIfFalse; + } + for(i=0; i<nVector; i++){ + Expr *p; + CollSeq *pColl; + int r3 = sqlite3GetTempReg(pParse); + p = sqlite3VectorFieldSubexpr(pLeft, i); + pColl = sqlite3ExprCollSeq(pParse, p); + sqlite3VdbeAddOp3(v, OP_Column, iTab, i, r3); + sqlite3VdbeAddOp4(v, OP_Ne, rLhs+i, destNotNull, r3, + (void*)pColl, P4_COLLSEQ); + VdbeCoverage(v); + sqlite3ReleaseTempReg(pParse, r3); + } + sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfNull); + if( nVector>1 ){ + sqlite3VdbeResolveLabel(v, destNotNull); + sqlite3VdbeAddOp2(v, OP_Next, iTab, addrTop+1); + VdbeCoverage(v); + + /* Step 7: If we reach this point, we know that the result must + ** be false. */ + sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse); + } + + /* Jumps here in order to return true. */ + sqlite3VdbeJumpHere(v, addrTruthOp); + +sqlite3ExprCodeIN_finished: + if( rLhs!=rLhsOrig ) sqlite3ReleaseTempReg(pParse, rLhs); + VdbeComment((v, "end IN expr")); +sqlite3ExprCodeIN_oom_error: + sqlite3DbFree(pParse->db, aiMap); + sqlite3DbFree(pParse->db, zAff); +} +#endif /* SQLITE_OMIT_SUBQUERY */ + +#ifndef SQLITE_OMIT_FLOATING_POINT +/* +** Generate an instruction that will put the floating point +** value described by z[0..n-1] into register iMem. +** +** The z[] string will probably not be zero-terminated. But the +** z[n] character is guaranteed to be something that does not look +** like the continuation of the number. +*/ +static void codeReal(Vdbe *v, const char *z, int negateFlag, int iMem){ + if( ALWAYS(z!=0) ){ + double value; + sqlite3AtoF(z, &value, sqlite3Strlen30(z), SQLITE_UTF8); + assert( !sqlite3IsNaN(value) ); /* The new AtoF never returns NaN */ + if( negateFlag ) value = -value; + sqlite3VdbeAddOp4Dup8(v, OP_Real, 0, iMem, 0, (u8*)&value, P4_REAL); + } +} +#endif + + +/* +** Generate an instruction that will put the integer describe by +** text z[0..n-1] into register iMem. +** +** Expr.u.zToken is always UTF8 and zero-terminated. +*/ +static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){ + Vdbe *v = pParse->pVdbe; + if( pExpr->flags & EP_IntValue ){ + int i = pExpr->u.iValue; + assert( i>=0 ); + if( negFlag ) i = -i; + sqlite3VdbeAddOp2(v, OP_Integer, i, iMem); + }else{ + int c; + i64 value; + const char *z = pExpr->u.zToken; + assert( z!=0 ); + c = sqlite3DecOrHexToI64(z, &value); + if( (c==3 && !negFlag) || (c==2) || (negFlag && value==SMALLEST_INT64)){ +#ifdef SQLITE_OMIT_FLOATING_POINT + sqlite3ErrorMsg(pParse, "oversized integer: %s%#T", negFlag?"-":"",pExpr); +#else +#ifndef SQLITE_OMIT_HEX_INTEGER + if( sqlite3_strnicmp(z,"0x",2)==0 ){ + sqlite3ErrorMsg(pParse, "hex literal too big: %s%#T", + negFlag?"-":"",pExpr); + }else +#endif + { + codeReal(v, z, negFlag, iMem); + } +#endif + }else{ + if( negFlag ){ value = c==3 ? SMALLEST_INT64 : -value; } + sqlite3VdbeAddOp4Dup8(v, OP_Int64, 0, iMem, 0, (u8*)&value, P4_INT64); + } + } +} + + +/* Generate code that will load into register regOut a value that is +** appropriate for the iIdxCol-th column of index pIdx. +*/ +SQLITE_PRIVATE void sqlite3ExprCodeLoadIndexColumn( + Parse *pParse, /* The parsing context */ + Index *pIdx, /* The index whose column is to be loaded */ + int iTabCur, /* Cursor pointing to a table row */ + int iIdxCol, /* The column of the index to be loaded */ + int regOut /* Store the index column value in this register */ +){ + i16 iTabCol = pIdx->aiColumn[iIdxCol]; + if( iTabCol==XN_EXPR ){ + assert( pIdx->aColExpr ); + assert( pIdx->aColExpr->nExpr>iIdxCol ); + pParse->iSelfTab = iTabCur + 1; + sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[iIdxCol].pExpr, regOut); + pParse->iSelfTab = 0; + }else{ + sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable, iTabCur, + iTabCol, regOut); + } +} + +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +/* +** Generate code that will compute the value of generated column pCol +** and store the result in register regOut +*/ +SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn( + Parse *pParse, /* Parsing context */ + Table *pTab, /* Table containing the generated column */ + Column *pCol, /* The generated column */ + int regOut /* Put the result in this register */ +){ + int iAddr; + Vdbe *v = pParse->pVdbe; + int nErr = pParse->nErr; + assert( v!=0 ); + assert( pParse->iSelfTab!=0 ); + if( pParse->iSelfTab>0 ){ + iAddr = sqlite3VdbeAddOp3(v, OP_IfNullRow, pParse->iSelfTab-1, 0, regOut); + }else{ + iAddr = 0; + } + sqlite3ExprCodeCopy(pParse, sqlite3ColumnExpr(pTab,pCol), regOut); + if( pCol->affinity>=SQLITE_AFF_TEXT ){ + sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1); + } + if( iAddr ) sqlite3VdbeJumpHere(v, iAddr); + if( pParse->nErr>nErr ) pParse->db->errByteOffset = -1; +} +#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ + +/* +** Generate code to extract the value of the iCol-th column of a table. +*/ +SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable( + Vdbe *v, /* Parsing context */ + Table *pTab, /* The table containing the value */ + int iTabCur, /* The table cursor. Or the PK cursor for WITHOUT ROWID */ + int iCol, /* Index of the column to extract */ + int regOut /* Extract the value into this register */ +){ + Column *pCol; + assert( v!=0 ); + assert( pTab!=0 ); + assert( iCol!=XN_EXPR ); + if( iCol<0 || iCol==pTab->iPKey ){ + sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut); + VdbeComment((v, "%s.rowid", pTab->zName)); + }else{ + int op; + int x; + if( IsVirtual(pTab) ){ + op = OP_VColumn; + x = iCol; +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + }else if( (pCol = &pTab->aCol[iCol])->colFlags & COLFLAG_VIRTUAL ){ + Parse *pParse = sqlite3VdbeParser(v); + if( pCol->colFlags & COLFLAG_BUSY ){ + sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", + pCol->zCnName); + }else{ + int savedSelfTab = pParse->iSelfTab; + pCol->colFlags |= COLFLAG_BUSY; + pParse->iSelfTab = iTabCur+1; + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, regOut); + pParse->iSelfTab = savedSelfTab; + pCol->colFlags &= ~COLFLAG_BUSY; + } + return; +#endif + }else if( !HasRowid(pTab) ){ + testcase( iCol!=sqlite3TableColumnToStorage(pTab, iCol) ); + x = sqlite3TableColumnToIndex(sqlite3PrimaryKeyIndex(pTab), iCol); + op = OP_Column; + }else{ + x = sqlite3TableColumnToStorage(pTab,iCol); + testcase( x!=iCol ); + op = OP_Column; + } + sqlite3VdbeAddOp3(v, op, iTabCur, x, regOut); + sqlite3ColumnDefault(v, pTab, iCol, regOut); + } +} + +/* +** Generate code that will extract the iColumn-th column from +** table pTab and store the column value in register iReg. +** +** There must be an open cursor to pTab in iTable when this routine +** is called. If iColumn<0 then code is generated that extracts the rowid. +*/ +SQLITE_PRIVATE int sqlite3ExprCodeGetColumn( + Parse *pParse, /* Parsing and code generating context */ + Table *pTab, /* Description of the table we are reading from */ + int iColumn, /* Index of the table column */ + int iTable, /* The cursor pointing to the table */ + int iReg, /* Store results here */ + u8 p5 /* P5 value for OP_Column + FLAGS */ +){ + assert( pParse->pVdbe!=0 ); + assert( (p5 & (OPFLAG_NOCHNG|OPFLAG_TYPEOFARG|OPFLAG_LENGTHARG))==p5 ); + assert( IsVirtual(pTab) || (p5 & OPFLAG_NOCHNG)==0 ); + sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pTab, iTable, iColumn, iReg); + if( p5 ){ + VdbeOp *pOp = sqlite3VdbeGetLastOp(pParse->pVdbe); + if( pOp->opcode==OP_Column ) pOp->p5 = p5; + if( pOp->opcode==OP_VColumn ) pOp->p5 = (p5 & OPFLAG_NOCHNG); + } + return iReg; +} + +/* +** Generate code to move content from registers iFrom...iFrom+nReg-1 +** over to iTo..iTo+nReg-1. +*/ +SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse *pParse, int iFrom, int iTo, int nReg){ + sqlite3VdbeAddOp3(pParse->pVdbe, OP_Move, iFrom, iTo, nReg); +} + +/* +** Convert a scalar expression node to a TK_REGISTER referencing +** register iReg. The caller must ensure that iReg already contains +** the correct value for the expression. +*/ +static void exprToRegister(Expr *pExpr, int iReg){ + Expr *p = sqlite3ExprSkipCollateAndLikely(pExpr); + if( NEVER(p==0) ) return; + p->op2 = p->op; + p->op = TK_REGISTER; + p->iTable = iReg; + ExprClearProperty(p, EP_Skip); +} + +/* +** Evaluate an expression (either a vector or a scalar expression) and store +** the result in contiguous temporary registers. Return the index of +** the first register used to store the result. +** +** If the returned result register is a temporary scalar, then also write +** that register number into *piFreeable. If the returned result register +** is not a temporary or if the expression is a vector set *piFreeable +** to 0. +*/ +static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ + int iResult; + int nResult = sqlite3ExprVectorSize(p); + if( nResult==1 ){ + iResult = sqlite3ExprCodeTemp(pParse, p, piFreeable); + }else{ + *piFreeable = 0; + if( p->op==TK_SELECT ){ +#if SQLITE_OMIT_SUBQUERY + iResult = 0; +#else + iResult = sqlite3CodeSubselect(pParse, p); +#endif + }else{ + int i; + iResult = pParse->nMem+1; + pParse->nMem += nResult; + assert( ExprUseXList(p) ); + for(i=0; i<nResult; i++){ + sqlite3ExprCodeFactorable(pParse, p->x.pList->a[i].pExpr, i+iResult); + } + } + } + return iResult; +} + +/* +** If the last opcode is a OP_Copy, then set the do-not-merge flag (p5) +** so that a subsequent copy will not be merged into this one. +*/ +static void setDoNotMergeFlagOnCopy(Vdbe *v){ + if( sqlite3VdbeGetLastOp(v)->opcode==OP_Copy ){ + sqlite3VdbeChangeP5(v, 1); /* Tag trailing OP_Copy as not mergeable */ + } +} + +/* +** Generate code to implement special SQL functions that are implemented +** in-line rather than by using the usual callbacks. +*/ +static int exprCodeInlineFunction( + Parse *pParse, /* Parsing context */ + ExprList *pFarg, /* List of function arguments */ + int iFuncId, /* Function ID. One of the INTFUNC_... values */ + int target /* Store function result in this register */ +){ + int nFarg; + Vdbe *v = pParse->pVdbe; + assert( v!=0 ); + assert( pFarg!=0 ); + nFarg = pFarg->nExpr; + assert( nFarg>0 ); /* All in-line functions have at least one argument */ + switch( iFuncId ){ + case INLINEFUNC_coalesce: { + /* Attempt a direct implementation of the built-in COALESCE() and + ** IFNULL() functions. This avoids unnecessary evaluation of + ** arguments past the first non-NULL argument. + */ + int endCoalesce = sqlite3VdbeMakeLabel(pParse); + int i; + assert( nFarg>=2 ); + sqlite3ExprCode(pParse, pFarg->a[0].pExpr, target); + for(i=1; i<nFarg; i++){ + sqlite3VdbeAddOp2(v, OP_NotNull, target, endCoalesce); + VdbeCoverage(v); + sqlite3ExprCode(pParse, pFarg->a[i].pExpr, target); + } + setDoNotMergeFlagOnCopy(v); + sqlite3VdbeResolveLabel(v, endCoalesce); + break; + } + case INLINEFUNC_iif: { + Expr caseExpr; + memset(&caseExpr, 0, sizeof(caseExpr)); + caseExpr.op = TK_CASE; + caseExpr.x.pList = pFarg; + return sqlite3ExprCodeTarget(pParse, &caseExpr, target); + } +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + case INLINEFUNC_sqlite_offset: { + Expr *pArg = pFarg->a[0].pExpr; + if( pArg->op==TK_COLUMN && pArg->iTable>=0 ){ + sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + } + break; + } +#endif + default: { + /* The UNLIKELY() function is a no-op. The result is the value + ** of the first argument. + */ + assert( nFarg==1 || nFarg==2 ); + target = sqlite3ExprCodeTarget(pParse, pFarg->a[0].pExpr, target); + break; + } + + /*********************************************************************** + ** Test-only SQL functions that are only usable if enabled + ** via SQLITE_TESTCTRL_INTERNAL_FUNCTIONS + */ +#if !defined(SQLITE_UNTESTABLE) + case INLINEFUNC_expr_compare: { + /* Compare two expressions using sqlite3ExprCompare() */ + assert( nFarg==2 ); + sqlite3VdbeAddOp2(v, OP_Integer, + sqlite3ExprCompare(0,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1), + target); + break; + } + + case INLINEFUNC_expr_implies_expr: { + /* Compare two expressions using sqlite3ExprImpliesExpr() */ + assert( nFarg==2 ); + sqlite3VdbeAddOp2(v, OP_Integer, + sqlite3ExprImpliesExpr(pParse,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1), + target); + break; + } + + case INLINEFUNC_implies_nonnull_row: { + /* Result of sqlite3ExprImpliesNonNullRow() */ + Expr *pA1; + assert( nFarg==2 ); + pA1 = pFarg->a[1].pExpr; + if( pA1->op==TK_COLUMN ){ + sqlite3VdbeAddOp2(v, OP_Integer, + sqlite3ExprImpliesNonNullRow(pFarg->a[0].pExpr,pA1->iTable,1), + target); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + } + break; + } + + case INLINEFUNC_affinity: { + /* The AFFINITY() function evaluates to a string that describes + ** the type affinity of the argument. This is used for testing of + ** the SQLite type logic. + */ + const char *azAff[] = { "blob", "text", "numeric", "integer", + "real", "flexnum" }; + char aff; + assert( nFarg==1 ); + aff = sqlite3ExprAffinity(pFarg->a[0].pExpr); + assert( aff<=SQLITE_AFF_NONE + || (aff>=SQLITE_AFF_BLOB && aff<=SQLITE_AFF_FLEXNUM) ); + sqlite3VdbeLoadString(v, target, + (aff<=SQLITE_AFF_NONE) ? "none" : azAff[aff-SQLITE_AFF_BLOB]); + break; + } +#endif /* !defined(SQLITE_UNTESTABLE) */ + } + return target; +} + +/* +** Check to see if pExpr is one of the indexed expressions on pParse->pIdxEpr. +** If it is, then resolve the expression by reading from the index and +** return the register into which the value has been read. If pExpr is +** not an indexed expression, then return negative. +*/ +static SQLITE_NOINLINE int sqlite3IndexedExprLookup( + Parse *pParse, /* The parsing context */ + Expr *pExpr, /* The expression to potentially bypass */ + int target /* Where to store the result of the expression */ +){ + IndexedExpr *p; + Vdbe *v; + for(p=pParse->pIdxEpr; p; p=p->pIENext){ + u8 exprAff; + int iDataCur = p->iDataCur; + if( iDataCur<0 ) continue; + if( pParse->iSelfTab ){ + if( p->iDataCur!=pParse->iSelfTab-1 ) continue; + iDataCur = -1; + } + if( sqlite3ExprCompare(0, pExpr, p->pExpr, iDataCur)!=0 ) continue; + assert( p->aff>=SQLITE_AFF_BLOB && p->aff<=SQLITE_AFF_NUMERIC ); + exprAff = sqlite3ExprAffinity(pExpr); + if( (exprAff<=SQLITE_AFF_BLOB && p->aff!=SQLITE_AFF_BLOB) + || (exprAff==SQLITE_AFF_TEXT && p->aff!=SQLITE_AFF_TEXT) + || (exprAff>=SQLITE_AFF_NUMERIC && p->aff!=SQLITE_AFF_NUMERIC) + ){ + /* Affinity mismatch on a generated column */ + continue; + } + + v = pParse->pVdbe; + assert( v!=0 ); + if( p->bMaybeNullRow ){ + /* If the index is on a NULL row due to an outer join, then we + ** cannot extract the value from the index. The value must be + ** computed using the original expression. */ + int addr = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp3(v, OP_IfNullRow, p->iIdxCur, addr+3, target); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target); + VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol)); + sqlite3VdbeGoto(v, 0); + p = pParse->pIdxEpr; + pParse->pIdxEpr = 0; + sqlite3ExprCode(pParse, pExpr, target); + pParse->pIdxEpr = p; + sqlite3VdbeJumpHere(v, addr+2); + }else{ + sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target); + VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol)); + } + return target; + } + return -1; /* Not found */ +} + + +/* +** Generate code into the current Vdbe to evaluate the given +** expression. Attempt to store the results in register "target". +** Return the register where results are stored. +** +** With this routine, there is no guarantee that results will +** be stored in target. The result might be stored in some other +** register if it is convenient to do so. The calling function +** must check the return code and move the results to the desired +** register. +*/ +SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ + Vdbe *v = pParse->pVdbe; /* The VM under construction */ + int op; /* The opcode being coded */ + int inReg = target; /* Results stored in register inReg */ + int regFree1 = 0; /* If non-zero free this temporary register */ + int regFree2 = 0; /* If non-zero free this temporary register */ + int r1, r2; /* Various register numbers */ + Expr tempX; /* Temporary expression node */ + int p5 = 0; + + assert( target>0 && target<=pParse->nMem ); + assert( v!=0 ); + +expr_code_doover: + if( pExpr==0 ){ + op = TK_NULL; + }else if( pParse->pIdxEpr!=0 + && !ExprHasProperty(pExpr, EP_Leaf) + && (r1 = sqlite3IndexedExprLookup(pParse, pExpr, target))>=0 + ){ + return r1; + }else{ + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); + op = pExpr->op; + } + switch( op ){ + case TK_AGG_COLUMN: { + AggInfo *pAggInfo = pExpr->pAggInfo; + struct AggInfo_col *pCol; + assert( pAggInfo!=0 ); + assert( pExpr->iAgg>=0 ); + if( pExpr->iAgg>=pAggInfo->nColumn ){ + /* Happens when the left table of a RIGHT JOIN is null and + ** is using an expression index */ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); +#ifdef SQLITE_VDBE_COVERAGE + /* Verify that the OP_Null above is exercised by tests + ** tag-20230325-2 */ + sqlite3VdbeAddOp2(v, OP_NotNull, target, 1); + VdbeCoverageNeverTaken(v); +#endif + break; + } + pCol = &pAggInfo->aCol[pExpr->iAgg]; + if( !pAggInfo->directMode ){ + return AggInfoColumnReg(pAggInfo, pExpr->iAgg); + }else if( pAggInfo->useSortingIdx ){ + Table *pTab = pCol->pTab; + sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, + pCol->iSorterColumn, target); + if( pTab==0 ){ + /* No comment added */ + }else if( pCol->iColumn<0 ){ + VdbeComment((v,"%s.rowid",pTab->zName)); + }else{ + VdbeComment((v,"%s.%s", + pTab->zName, pTab->aCol[pCol->iColumn].zCnName)); + if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, target); + } + } + return target; + }else if( pExpr->y.pTab==0 ){ + /* This case happens when the argument to an aggregate function + ** is rewritten by aggregateConvertIndexedExprRefToColumn() */ + sqlite3VdbeAddOp3(v, OP_Column, pExpr->iTable, pExpr->iColumn, target); + return target; + } + /* Otherwise, fall thru into the TK_COLUMN case */ + /* no break */ deliberate_fall_through + } + case TK_COLUMN: { + int iTab = pExpr->iTable; + int iReg; + if( ExprHasProperty(pExpr, EP_FixedCol) ){ + /* This COLUMN expression is really a constant due to WHERE clause + ** constraints, and that constant is coded by the pExpr->pLeft + ** expression. However, make sure the constant has the correct + ** datatype by applying the Affinity of the table column to the + ** constant. + */ + int aff; + iReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft,target); + assert( ExprUseYTab(pExpr) ); + assert( pExpr->y.pTab!=0 ); + aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); + if( aff>SQLITE_AFF_BLOB ){ + static const char zAff[] = "B\000C\000D\000E\000F"; + assert( SQLITE_AFF_BLOB=='A' ); + assert( SQLITE_AFF_TEXT=='B' ); + sqlite3VdbeAddOp4(v, OP_Affinity, iReg, 1, 0, + &zAff[(aff-'B')*2], P4_STATIC); + } + return iReg; + } + if( iTab<0 ){ + if( pParse->iSelfTab<0 ){ + /* Other columns in the same row for CHECK constraints or + ** generated columns or for inserting into partial index. + ** The row is unpacked into registers beginning at + ** 0-(pParse->iSelfTab). The rowid (if any) is in a register + ** immediately prior to the first column. + */ + Column *pCol; + Table *pTab; + int iSrc; + int iCol = pExpr->iColumn; + assert( ExprUseYTab(pExpr) ); + pTab = pExpr->y.pTab; + assert( pTab!=0 ); + assert( iCol>=XN_ROWID ); + assert( iCol<pTab->nCol ); + if( iCol<0 ){ + return -1-pParse->iSelfTab; + } + pCol = pTab->aCol + iCol; + testcase( iCol!=sqlite3TableColumnToStorage(pTab,iCol) ); + iSrc = sqlite3TableColumnToStorage(pTab, iCol) - pParse->iSelfTab; +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( pCol->colFlags & COLFLAG_GENERATED ){ + if( pCol->colFlags & COLFLAG_BUSY ){ + sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", + pCol->zCnName); + return 0; + } + pCol->colFlags |= COLFLAG_BUSY; + if( pCol->colFlags & COLFLAG_NOTAVAIL ){ + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, iSrc); + } + pCol->colFlags &= ~(COLFLAG_BUSY|COLFLAG_NOTAVAIL); + return iSrc; + }else +#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ + if( pCol->affinity==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp2(v, OP_SCopy, iSrc, target); + sqlite3VdbeAddOp1(v, OP_RealAffinity, target); + return target; + }else{ + return iSrc; + } + }else{ + /* Coding an expression that is part of an index where column names + ** in the index refer to the table to which the index belongs */ + iTab = pParse->iSelfTab - 1; + } + } + assert( ExprUseYTab(pExpr) ); + assert( pExpr->y.pTab!=0 ); + iReg = sqlite3ExprCodeGetColumn(pParse, pExpr->y.pTab, + pExpr->iColumn, iTab, target, + pExpr->op2); + return iReg; + } + case TK_INTEGER: { + codeInteger(pParse, pExpr, 0, target); + return target; + } + case TK_TRUEFALSE: { + sqlite3VdbeAddOp2(v, OP_Integer, sqlite3ExprTruthValue(pExpr), target); + return target; + } +#ifndef SQLITE_OMIT_FLOATING_POINT + case TK_FLOAT: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + codeReal(v, pExpr->u.zToken, 0, target); + return target; + } +#endif + case TK_STRING: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3VdbeLoadString(v, target, pExpr->u.zToken); + return target; + } + default: { + /* Make NULL the default case so that if a bug causes an illegal + ** Expr node to be passed into this function, it will be handled + ** sanely and not crash. But keep the assert() to bring the problem + ** to the attention of the developers. */ + assert( op==TK_NULL || op==TK_ERROR || pParse->db->mallocFailed ); + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + return target; + } +#ifndef SQLITE_OMIT_BLOB_LITERAL + case TK_BLOB: { + int n; + const char *z; + char *zBlob; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + assert( pExpr->u.zToken[0]=='x' || pExpr->u.zToken[0]=='X' ); + assert( pExpr->u.zToken[1]=='\'' ); + z = &pExpr->u.zToken[2]; + n = sqlite3Strlen30(z) - 1; + assert( z[n]=='\'' ); + zBlob = sqlite3HexToBlob(sqlite3VdbeDb(v), z, n); + sqlite3VdbeAddOp4(v, OP_Blob, n/2, target, 0, zBlob, P4_DYNAMIC); + return target; + } +#endif + case TK_VARIABLE: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + assert( pExpr->u.zToken!=0 ); + assert( pExpr->u.zToken[0]!=0 ); + sqlite3VdbeAddOp2(v, OP_Variable, pExpr->iColumn, target); + if( pExpr->u.zToken[1]!=0 ){ + const char *z = sqlite3VListNumToName(pParse->pVList, pExpr->iColumn); + assert( pExpr->u.zToken[0]=='?' || (z && !strcmp(pExpr->u.zToken, z)) ); + pParse->pVList[0] = 0; /* Indicate VList may no longer be enlarged */ + sqlite3VdbeAppendP4(v, (char*)z, P4_STATIC); + } + return target; + } + case TK_REGISTER: { + return pExpr->iTable; + } +#ifndef SQLITE_OMIT_CAST + case TK_CAST: { + /* Expressions of the form: CAST(pLeft AS token) */ + sqlite3ExprCode(pParse, pExpr->pLeft, target); + assert( inReg==target ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3VdbeAddOp2(v, OP_Cast, target, + sqlite3AffinityType(pExpr->u.zToken, 0)); + return inReg; + } +#endif /* SQLITE_OMIT_CAST */ + case TK_IS: + case TK_ISNOT: + op = (op==TK_IS) ? TK_EQ : TK_NE; + p5 = SQLITE_NULLEQ; + /* fall-through */ + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: { + Expr *pLeft = pExpr->pLeft; + if( sqlite3ExprIsVector(pLeft) ){ + codeVectorCompare(pParse, pExpr, target, op, p5); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pLeft, &regFree1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2); + sqlite3VdbeAddOp2(v, OP_Integer, 1, inReg); + codeCompare(pParse, pLeft, pExpr->pRight, op, r1, r2, + sqlite3VdbeCurrentAddr(v)+2, p5, + ExprHasProperty(pExpr,EP_Commuted)); + assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); + if( p5==SQLITE_NULLEQ ){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, inReg); + }else{ + sqlite3VdbeAddOp3(v, OP_ZeroOrNull, r1, inReg, r2); + } + testcase( regFree1==0 ); + testcase( regFree2==0 ); + } + break; + } + case TK_AND: + case TK_OR: + case TK_PLUS: + case TK_STAR: + case TK_MINUS: + case TK_REM: + case TK_BITAND: + case TK_BITOR: + case TK_SLASH: + case TK_LSHIFT: + case TK_RSHIFT: + case TK_CONCAT: { + assert( TK_AND==OP_And ); testcase( op==TK_AND ); + assert( TK_OR==OP_Or ); testcase( op==TK_OR ); + assert( TK_PLUS==OP_Add ); testcase( op==TK_PLUS ); + assert( TK_MINUS==OP_Subtract ); testcase( op==TK_MINUS ); + assert( TK_REM==OP_Remainder ); testcase( op==TK_REM ); + assert( TK_BITAND==OP_BitAnd ); testcase( op==TK_BITAND ); + assert( TK_BITOR==OP_BitOr ); testcase( op==TK_BITOR ); + assert( TK_SLASH==OP_Divide ); testcase( op==TK_SLASH ); + assert( TK_LSHIFT==OP_ShiftLeft ); testcase( op==TK_LSHIFT ); + assert( TK_RSHIFT==OP_ShiftRight ); testcase( op==TK_RSHIFT ); + assert( TK_CONCAT==OP_Concat ); testcase( op==TK_CONCAT ); + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2); + sqlite3VdbeAddOp3(v, op, r2, r1, target); + testcase( regFree1==0 ); + testcase( regFree2==0 ); + break; + } + case TK_UMINUS: { + Expr *pLeft = pExpr->pLeft; + assert( pLeft ); + if( pLeft->op==TK_INTEGER ){ + codeInteger(pParse, pLeft, 1, target); + return target; +#ifndef SQLITE_OMIT_FLOATING_POINT + }else if( pLeft->op==TK_FLOAT ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + codeReal(v, pLeft->u.zToken, 1, target); + return target; +#endif + }else{ + tempX.op = TK_INTEGER; + tempX.flags = EP_IntValue|EP_TokenOnly; + tempX.u.iValue = 0; + ExprClearVVAProperties(&tempX); + r1 = sqlite3ExprCodeTemp(pParse, &tempX, &regFree1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree2); + sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target); + testcase( regFree2==0 ); + } + break; + } + case TK_BITNOT: + case TK_NOT: { + assert( TK_BITNOT==OP_BitNot ); testcase( op==TK_BITNOT ); + assert( TK_NOT==OP_Not ); testcase( op==TK_NOT ); + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1); + testcase( regFree1==0 ); + sqlite3VdbeAddOp2(v, op, r1, inReg); + break; + } + case TK_TRUTH: { + int isTrue; /* IS TRUE or IS NOT TRUE */ + int bNormal; /* IS TRUE or IS FALSE */ + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1); + testcase( regFree1==0 ); + isTrue = sqlite3ExprTruthValue(pExpr->pRight); + bNormal = pExpr->op2==TK_IS; + testcase( isTrue && bNormal); + testcase( !isTrue && bNormal); + sqlite3VdbeAddOp4Int(v, OP_IsTrue, r1, inReg, !isTrue, isTrue ^ bNormal); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + int addr; + assert( TK_ISNULL==OP_IsNull ); testcase( op==TK_ISNULL ); + assert( TK_NOTNULL==OP_NotNull ); testcase( op==TK_NOTNULL ); + sqlite3VdbeAddOp2(v, OP_Integer, 1, target); + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1); + testcase( regFree1==0 ); + addr = sqlite3VdbeAddOp1(v, op, r1); + VdbeCoverageIf(v, op==TK_ISNULL); + VdbeCoverageIf(v, op==TK_NOTNULL); + sqlite3VdbeAddOp2(v, OP_Integer, 0, target); + sqlite3VdbeJumpHere(v, addr); + break; + } + case TK_AGG_FUNCTION: { + AggInfo *pInfo = pExpr->pAggInfo; + if( pInfo==0 + || NEVER(pExpr->iAgg<0) + || NEVER(pExpr->iAgg>=pInfo->nFunc) + ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3ErrorMsg(pParse, "misuse of aggregate: %#T()", pExpr); + }else{ + return AggInfoFuncReg(pInfo, pExpr->iAgg); + } + break; + } + case TK_FUNCTION: { + ExprList *pFarg; /* List of function arguments */ + int nFarg; /* Number of function arguments */ + FuncDef *pDef; /* The function definition object */ + const char *zId; /* The function name */ + u32 constMask = 0; /* Mask of function arguments that are constant */ + int i; /* Loop counter */ + sqlite3 *db = pParse->db; /* The database connection */ + u8 enc = ENC(db); /* The text encoding used by this database */ + CollSeq *pColl = 0; /* A collating sequence */ + +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + return pExpr->y.pWin->regResult; + } +#endif + + if( ConstFactorOk(pParse) && sqlite3ExprIsConstantNotJoin(pExpr) ){ + /* SQL functions can be expensive. So try to avoid running them + ** multiple times if we know they always give the same result */ + return sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); + } + assert( !ExprHasProperty(pExpr, EP_TokenOnly) ); + assert( ExprUseXList(pExpr) ); + pFarg = pExpr->x.pList; + nFarg = pFarg ? pFarg->nExpr : 0; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + zId = pExpr->u.zToken; + pDef = sqlite3FindFunction(db, zId, nFarg, enc, 0); +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + if( pDef==0 && pParse->explain ){ + pDef = sqlite3FindFunction(db, "unknown", nFarg, enc, 0); + } +#endif + if( pDef==0 || pDef->xFinalize!=0 ){ + sqlite3ErrorMsg(pParse, "unknown function: %#T()", pExpr); + break; + } + if( (pDef->funcFlags & SQLITE_FUNC_INLINE)!=0 && ALWAYS(pFarg!=0) ){ + assert( (pDef->funcFlags & SQLITE_FUNC_UNSAFE)==0 ); + assert( (pDef->funcFlags & SQLITE_FUNC_DIRECT)==0 ); + return exprCodeInlineFunction(pParse, pFarg, + SQLITE_PTR_TO_INT(pDef->pUserData), target); + }else if( pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE) ){ + sqlite3ExprFunctionUsable(pParse, pExpr, pDef); + } + + for(i=0; i<nFarg; i++){ + if( i<32 && sqlite3ExprIsConstant(pFarg->a[i].pExpr) ){ + testcase( i==31 ); + constMask |= MASKBIT32(i); + } + if( (pDef->funcFlags & SQLITE_FUNC_NEEDCOLL)!=0 && !pColl ){ + pColl = sqlite3ExprCollSeq(pParse, pFarg->a[i].pExpr); + } + } + if( pFarg ){ + if( constMask ){ + r1 = pParse->nMem+1; + pParse->nMem += nFarg; + }else{ + r1 = sqlite3GetTempRange(pParse, nFarg); + } + + /* For length() and typeof() and octet_length() functions, + ** set the P5 parameter to the OP_Column opcode to OPFLAG_LENGTHARG + ** or OPFLAG_TYPEOFARG or OPFLAG_BYTELENARG respectively, to avoid + ** unnecessary data loading. + */ + if( (pDef->funcFlags & (SQLITE_FUNC_LENGTH|SQLITE_FUNC_TYPEOF))!=0 ){ + u8 exprOp; + assert( nFarg==1 ); + assert( pFarg->a[0].pExpr!=0 ); + exprOp = pFarg->a[0].pExpr->op; + if( exprOp==TK_COLUMN || exprOp==TK_AGG_COLUMN ){ + assert( SQLITE_FUNC_LENGTH==OPFLAG_LENGTHARG ); + assert( SQLITE_FUNC_TYPEOF==OPFLAG_TYPEOFARG ); + assert( SQLITE_FUNC_BYTELEN==OPFLAG_BYTELENARG ); + assert( (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG)==OPFLAG_BYTELENARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_LENGTHARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_TYPEOFARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_BYTELENARG); + pFarg->a[0].pExpr->op2 = pDef->funcFlags & OPFLAG_BYTELENARG; + } + } + + sqlite3ExprCodeExprList(pParse, pFarg, r1, 0, SQLITE_ECEL_FACTOR); + }else{ + r1 = 0; + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* Possibly overload the function if the first argument is + ** a virtual table column. + ** + ** For infix functions (LIKE, GLOB, REGEXP, and MATCH) use the + ** second argument, not the first, as the argument to test to + ** see if it is a column in a virtual table. This is done because + ** the left operand of infix functions (the operand we want to + ** control overloading) ends up as the second argument to the + ** function. The expression "A glob B" is equivalent to + ** "glob(B,A). We want to use the A in "A glob B" to test + ** for function overloading. But we use the B term in "glob(B,A)". + */ + if( nFarg>=2 && ExprHasProperty(pExpr, EP_InfixFunc) ){ + pDef = sqlite3VtabOverloadFunction(db, pDef, nFarg, pFarg->a[1].pExpr); + }else if( nFarg>0 ){ + pDef = sqlite3VtabOverloadFunction(db, pDef, nFarg, pFarg->a[0].pExpr); + } +#endif + if( pDef->funcFlags & SQLITE_FUNC_NEEDCOLL ){ + if( !pColl ) pColl = db->pDfltColl; + sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); + } + sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg, + pDef, pExpr->op2); + if( nFarg ){ + if( constMask==0 ){ + sqlite3ReleaseTempRange(pParse, r1, nFarg); + }else{ + sqlite3VdbeReleaseRegisters(pParse, r1, nFarg, constMask, 1); + } + } + return target; + } +#ifndef SQLITE_OMIT_SUBQUERY + case TK_EXISTS: + case TK_SELECT: { + int nCol; + testcase( op==TK_EXISTS ); + testcase( op==TK_SELECT ); + if( pParse->db->mallocFailed ){ + return 0; + }else if( op==TK_SELECT + && ALWAYS( ExprUseXSelect(pExpr) ) + && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 + ){ + sqlite3SubselectError(pParse, nCol, 1); + }else{ + return sqlite3CodeSubselect(pParse, pExpr); + } + break; + } + case TK_SELECT_COLUMN: { + int n; + Expr *pLeft = pExpr->pLeft; + if( pLeft->iTable==0 || pParse->withinRJSubrtn > pLeft->op2 ){ + pLeft->iTable = sqlite3CodeSubselect(pParse, pLeft); + pLeft->op2 = pParse->withinRJSubrtn; + } + assert( pLeft->op==TK_SELECT || pLeft->op==TK_ERROR ); + n = sqlite3ExprVectorSize(pLeft); + if( pExpr->iTable!=n ){ + sqlite3ErrorMsg(pParse, "%d columns assigned %d values", + pExpr->iTable, n); + } + return pLeft->iTable + pExpr->iColumn; + } + case TK_IN: { + int destIfFalse = sqlite3VdbeMakeLabel(pParse); + int destIfNull = sqlite3VdbeMakeLabel(pParse); + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + sqlite3ExprCodeIN(pParse, pExpr, destIfFalse, destIfNull); + sqlite3VdbeAddOp2(v, OP_Integer, 1, target); + sqlite3VdbeResolveLabel(v, destIfFalse); + sqlite3VdbeAddOp2(v, OP_AddImm, target, 0); + sqlite3VdbeResolveLabel(v, destIfNull); + return target; + } +#endif /* SQLITE_OMIT_SUBQUERY */ + + + /* + ** x BETWEEN y AND z + ** + ** This is equivalent to + ** + ** x>=y AND x<=z + ** + ** X is stored in pExpr->pLeft. + ** Y is stored in pExpr->pList->a[0].pExpr. + ** Z is stored in pExpr->pList->a[1].pExpr. + */ + case TK_BETWEEN: { + exprCodeBetween(pParse, pExpr, target, 0, 0); + return target; + } + case TK_COLLATE: { + if( !ExprHasProperty(pExpr, EP_Collate) ){ + /* A TK_COLLATE Expr node without the EP_Collate tag is a so-called + ** "SOFT-COLLATE" that is added to constraints that are pushed down + ** from outer queries into sub-queries by the push-down optimization. + ** Clear subtypes as subtypes may not cross a subquery boundary. + */ + assert( pExpr->pLeft ); + sqlite3ExprCode(pParse, pExpr->pLeft, target); + sqlite3VdbeAddOp1(v, OP_ClrSubtype, target); + return target; + }else{ + pExpr = pExpr->pLeft; + goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. */ + } + } + case TK_SPAN: + case TK_UPLUS: { + pExpr = pExpr->pLeft; + goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. OSSFuzz. */ + } + + case TK_TRIGGER: { + /* If the opcode is TK_TRIGGER, then the expression is a reference + ** to a column in the new.* or old.* pseudo-tables available to + ** trigger programs. In this case Expr.iTable is set to 1 for the + ** new.* pseudo-table, or 0 for the old.* pseudo-table. Expr.iColumn + ** is set to the column of the pseudo-table to read, or to -1 to + ** read the rowid field. + ** + ** The expression is implemented using an OP_Param opcode. The p1 + ** parameter is set to 0 for an old.rowid reference, or to (i+1) + ** to reference another column of the old.* pseudo-table, where + ** i is the index of the column. For a new.rowid reference, p1 is + ** set to (n+1), where n is the number of columns in each pseudo-table. + ** For a reference to any other column in the new.* pseudo-table, p1 + ** is set to (n+2+i), where n and i are as defined previously. For + ** example, if the table on which triggers are being fired is + ** declared as: + ** + ** CREATE TABLE t1(a, b); + ** + ** Then p1 is interpreted as follows: + ** + ** p1==0 -> old.rowid p1==3 -> new.rowid + ** p1==1 -> old.a p1==4 -> new.a + ** p1==2 -> old.b p1==5 -> new.b + */ + Table *pTab; + int iCol; + int p1; + + assert( ExprUseYTab(pExpr) ); + pTab = pExpr->y.pTab; + iCol = pExpr->iColumn; + p1 = pExpr->iTable * (pTab->nCol+1) + 1 + + sqlite3TableColumnToStorage(pTab, iCol); + + assert( pExpr->iTable==0 || pExpr->iTable==1 ); + assert( iCol>=-1 && iCol<pTab->nCol ); + assert( pTab->iPKey<0 || iCol!=pTab->iPKey ); + assert( p1>=0 && p1<(pTab->nCol*2+2) ); + + sqlite3VdbeAddOp2(v, OP_Param, p1, target); + VdbeComment((v, "r[%d]=%s.%s", target, + (pExpr->iTable ? "new" : "old"), + (pExpr->iColumn<0 ? "rowid" : pExpr->y.pTab->aCol[iCol].zCnName) + )); + +#ifndef SQLITE_OMIT_FLOATING_POINT + /* If the column has REAL affinity, it may currently be stored as an + ** integer. Use OP_RealAffinity to make sure it is really real. + ** + ** EVIDENCE-OF: R-60985-57662 SQLite will convert the value back to + ** floating point when extracting it from the record. */ + if( iCol>=0 && pTab->aCol[iCol].affinity==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, target); + } +#endif + break; + } + + case TK_VECTOR: { + sqlite3ErrorMsg(pParse, "row value misused"); + break; + } + + /* TK_IF_NULL_ROW Expr nodes are inserted ahead of expressions + ** that derive from the right-hand table of a LEFT JOIN. The + ** Expr.iTable value is the table number for the right-hand table. + ** The expression is only evaluated if that table is not currently + ** on a LEFT JOIN NULL row. + */ + case TK_IF_NULL_ROW: { + int addrINR; + u8 okConstFactor = pParse->okConstFactor; + AggInfo *pAggInfo = pExpr->pAggInfo; + if( pAggInfo ){ + assert( pExpr->iAgg>=0 && pExpr->iAgg<pAggInfo->nColumn ); + if( !pAggInfo->directMode ){ + inReg = AggInfoColumnReg(pAggInfo, pExpr->iAgg); + break; + } + if( pExpr->pAggInfo->useSortingIdx ){ + sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, + pAggInfo->aCol[pExpr->iAgg].iSorterColumn, + target); + inReg = target; + break; + } + } + addrINR = sqlite3VdbeAddOp3(v, OP_IfNullRow, pExpr->iTable, 0, target); + /* The OP_IfNullRow opcode above can overwrite the result register with + ** NULL. So we have to ensure that the result register is not a value + ** that is suppose to be a constant. Two defenses are needed: + ** (1) Temporarily disable factoring of constant expressions + ** (2) Make sure the computed value really is stored in register + ** "target" and not someplace else. + */ + pParse->okConstFactor = 0; /* note (1) above */ + sqlite3ExprCode(pParse, pExpr->pLeft, target); + assert( target==inReg ); + pParse->okConstFactor = okConstFactor; + sqlite3VdbeJumpHere(v, addrINR); + break; + } + + /* + ** Form A: + ** CASE x WHEN e1 THEN r1 WHEN e2 THEN r2 ... WHEN eN THEN rN ELSE y END + ** + ** Form B: + ** CASE WHEN e1 THEN r1 WHEN e2 THEN r2 ... WHEN eN THEN rN ELSE y END + ** + ** Form A is can be transformed into the equivalent form B as follows: + ** CASE WHEN x=e1 THEN r1 WHEN x=e2 THEN r2 ... + ** WHEN x=eN THEN rN ELSE y END + ** + ** X (if it exists) is in pExpr->pLeft. + ** Y is in the last element of pExpr->x.pList if pExpr->x.pList->nExpr is + ** odd. The Y is also optional. If the number of elements in x.pList + ** is even, then Y is omitted and the "otherwise" result is NULL. + ** Ei is in pExpr->pList->a[i*2] and Ri is pExpr->pList->a[i*2+1]. + ** + ** The result of the expression is the Ri for the first matching Ei, + ** or if there is no matching Ei, the ELSE term Y, or if there is + ** no ELSE term, NULL. + */ + case TK_CASE: { + int endLabel; /* GOTO label for end of CASE stmt */ + int nextCase; /* GOTO label for next WHEN clause */ + int nExpr; /* 2x number of WHEN terms */ + int i; /* Loop counter */ + ExprList *pEList; /* List of WHEN terms */ + struct ExprList_item *aListelem; /* Array of WHEN terms */ + Expr opCompare; /* The X==Ei expression */ + Expr *pX; /* The X expression */ + Expr *pTest = 0; /* X==Ei (form A) or just Ei (form B) */ + Expr *pDel = 0; + sqlite3 *db = pParse->db; + + assert( ExprUseXList(pExpr) && pExpr->x.pList!=0 ); + assert(pExpr->x.pList->nExpr > 0); + pEList = pExpr->x.pList; + aListelem = pEList->a; + nExpr = pEList->nExpr; + endLabel = sqlite3VdbeMakeLabel(pParse); + if( (pX = pExpr->pLeft)!=0 ){ + pDel = sqlite3ExprDup(db, pX, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDel); + break; + } + testcase( pX->op==TK_COLUMN ); + exprToRegister(pDel, exprCodeVector(pParse, pDel, &regFree1)); + testcase( regFree1==0 ); + memset(&opCompare, 0, sizeof(opCompare)); + opCompare.op = TK_EQ; + opCompare.pLeft = pDel; + pTest = &opCompare; + /* Ticket b351d95f9cd5ef17e9d9dbae18f5ca8611190001: + ** The value in regFree1 might get SCopy-ed into the file result. + ** So make sure that the regFree1 register is not reused for other + ** purposes and possibly overwritten. */ + regFree1 = 0; + } + for(i=0; i<nExpr-1; i=i+2){ + if( pX ){ + assert( pTest!=0 ); + opCompare.pRight = aListelem[i].pExpr; + }else{ + pTest = aListelem[i].pExpr; + } + nextCase = sqlite3VdbeMakeLabel(pParse); + testcase( pTest->op==TK_COLUMN ); + sqlite3ExprIfFalse(pParse, pTest, nextCase, SQLITE_JUMPIFNULL); + testcase( aListelem[i+1].pExpr->op==TK_COLUMN ); + sqlite3ExprCode(pParse, aListelem[i+1].pExpr, target); + sqlite3VdbeGoto(v, endLabel); + sqlite3VdbeResolveLabel(v, nextCase); + } + if( (nExpr&1)!=0 ){ + sqlite3ExprCode(pParse, pEList->a[nExpr-1].pExpr, target); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + } + sqlite3ExprDelete(db, pDel); + setDoNotMergeFlagOnCopy(v); + sqlite3VdbeResolveLabel(v, endLabel); + break; + } +#ifndef SQLITE_OMIT_TRIGGER + case TK_RAISE: { + assert( pExpr->affExpr==OE_Rollback + || pExpr->affExpr==OE_Abort + || pExpr->affExpr==OE_Fail + || pExpr->affExpr==OE_Ignore + ); + if( !pParse->pTriggerTab && !pParse->nested ){ + sqlite3ErrorMsg(pParse, + "RAISE() may only be used within a trigger-program"); + return 0; + } + if( pExpr->affExpr==OE_Abort ){ + sqlite3MayAbort(pParse); + } + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + if( pExpr->affExpr==OE_Ignore ){ + sqlite3VdbeAddOp4( + v, OP_Halt, SQLITE_OK, OE_Ignore, 0, pExpr->u.zToken,0); + VdbeCoverage(v); + }else{ + sqlite3HaltConstraint(pParse, + pParse->pTriggerTab ? SQLITE_CONSTRAINT_TRIGGER : SQLITE_ERROR, + pExpr->affExpr, pExpr->u.zToken, 0, 0); + } + + break; + } +#endif + } + sqlite3ReleaseTempReg(pParse, regFree1); + sqlite3ReleaseTempReg(pParse, regFree2); + return inReg; +} + +/* +** Generate code that will evaluate expression pExpr just one time +** per prepared statement execution. +** +** If the expression uses functions (that might throw an exception) then +** guard them with an OP_Once opcode to ensure that the code is only executed +** once. If no functions are involved, then factor the code out and put it at +** the end of the prepared statement in the initialization section. +** +** If regDest>0 then the result is always stored in that register and the +** result is not reusable. If regDest<0 then this routine is free to +** store the value wherever it wants. The register where the expression +** is stored is returned. When regDest<0, two identical expressions might +** code to the same register, if they do not contain function calls and hence +** are factored out into the initialization section at the end of the +** prepared statement. +*/ +SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* The expression to code when the VDBE initializes */ + int regDest /* Store the value in this register */ +){ + ExprList *p; + assert( ConstFactorOk(pParse) ); + assert( regDest!=0 ); + p = pParse->pConstExpr; + if( regDest<0 && p ){ + struct ExprList_item *pItem; + int i; + for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){ + if( pItem->fg.reusable + && sqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0 + ){ + return pItem->u.iConstExprReg; + } + } + } + pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); + if( pExpr!=0 && ExprHasProperty(pExpr, EP_HasFunc) ){ + Vdbe *v = pParse->pVdbe; + int addr; + assert( v ); + addr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + pParse->okConstFactor = 0; + if( !pParse->db->mallocFailed ){ + if( regDest<0 ) regDest = ++pParse->nMem; + sqlite3ExprCode(pParse, pExpr, regDest); + } + pParse->okConstFactor = 1; + sqlite3ExprDelete(pParse->db, pExpr); + sqlite3VdbeJumpHere(v, addr); + }else{ + p = sqlite3ExprListAppend(pParse, p, pExpr); + if( p ){ + struct ExprList_item *pItem = &p->a[p->nExpr-1]; + pItem->fg.reusable = regDest<0; + if( regDest<0 ) regDest = ++pParse->nMem; + pItem->u.iConstExprReg = regDest; + } + pParse->pConstExpr = p; + } + return regDest; +} + +/* +** Generate code to evaluate an expression and store the results +** into a register. Return the register number where the results +** are stored. +** +** If the register is a temporary register that can be deallocated, +** then write its number into *pReg. If the result register is not +** a temporary, then set *pReg to zero. +** +** If pExpr is a constant, then this routine might generate this +** code to fill the register in the initialization section of the +** VDBE program, in order to factor it out of the evaluation loop. +*/ +SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){ + int r2; + pExpr = sqlite3ExprSkipCollateAndLikely(pExpr); + if( ConstFactorOk(pParse) + && ALWAYS(pExpr!=0) + && pExpr->op!=TK_REGISTER + && sqlite3ExprIsConstantNotJoin(pExpr) + ){ + *pReg = 0; + r2 = sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); + }else{ + int r1 = sqlite3GetTempReg(pParse); + r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1); + if( r2==r1 ){ + *pReg = r1; + }else{ + sqlite3ReleaseTempReg(pParse, r1); + *pReg = 0; + } + } + return r2; +} + +/* +** Generate code that will evaluate expression pExpr and store the +** results in register target. The results are guaranteed to appear +** in register target. +*/ +SQLITE_PRIVATE void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){ + int inReg; + + assert( pExpr==0 || !ExprHasVVAProperty(pExpr,EP_Immutable) ); + assert( target>0 && target<=pParse->nMem ); + assert( pParse->pVdbe!=0 || pParse->db->mallocFailed ); + if( pParse->pVdbe==0 ) return; + inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); + if( inReg!=target ){ + u8 op; + if( ALWAYS(pExpr) + && (ExprHasProperty(pExpr,EP_Subquery) || pExpr->op==TK_REGISTER) + ){ + op = OP_Copy; + }else{ + op = OP_SCopy; + } + sqlite3VdbeAddOp2(pParse->pVdbe, op, inReg, target); + } +} + +/* +** Make a transient copy of expression pExpr and then code it using +** sqlite3ExprCode(). This routine works just like sqlite3ExprCode() +** except that the input expression is guaranteed to be unchanged. +*/ +SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse *pParse, Expr *pExpr, int target){ + sqlite3 *db = pParse->db; + pExpr = sqlite3ExprDup(db, pExpr, 0); + if( !db->mallocFailed ) sqlite3ExprCode(pParse, pExpr, target); + sqlite3ExprDelete(db, pExpr); +} + +/* +** Generate code that will evaluate expression pExpr and store the +** results in register target. The results are guaranteed to appear +** in register target. If the expression is constant, then this routine +** might choose to code the expression at initialization time. +*/ +SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){ + if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pExpr) ){ + sqlite3ExprCodeRunJustOnce(pParse, pExpr, target); + }else{ + sqlite3ExprCodeCopy(pParse, pExpr, target); + } +} + +/* +** Generate code that pushes the value of every element of the given +** expression list into a sequence of registers beginning at target. +** +** Return the number of elements evaluated. The number returned will +** usually be pList->nExpr but might be reduced if SQLITE_ECEL_OMITREF +** is defined. +** +** The SQLITE_ECEL_DUP flag prevents the arguments from being +** filled using OP_SCopy. OP_Copy must be used instead. +** +** The SQLITE_ECEL_FACTOR argument allows constant arguments to be +** factored out into initialization code. +** +** The SQLITE_ECEL_REF flag means that expressions in the list with +** ExprList.a[].u.x.iOrderByCol>0 have already been evaluated and stored +** in registers at srcReg, and so the value can be copied from there. +** If SQLITE_ECEL_OMITREF is also set, then the values with u.x.iOrderByCol>0 +** are simply omitted rather than being copied from srcReg. +*/ +SQLITE_PRIVATE int sqlite3ExprCodeExprList( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* The expression list to be coded */ + int target, /* Where to write results */ + int srcReg, /* Source registers if SQLITE_ECEL_REF */ + u8 flags /* SQLITE_ECEL_* flags */ +){ + struct ExprList_item *pItem; + int i, j, n; + u8 copyOp = (flags & SQLITE_ECEL_DUP) ? OP_Copy : OP_SCopy; + Vdbe *v = pParse->pVdbe; + assert( pList!=0 ); + assert( target>0 ); + assert( pParse->pVdbe!=0 ); /* Never gets this far otherwise */ + n = pList->nExpr; + if( !ConstFactorOk(pParse) ) flags &= ~SQLITE_ECEL_FACTOR; + for(pItem=pList->a, i=0; i<n; i++, pItem++){ + Expr *pExpr = pItem->pExpr; +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + if( pItem->fg.bSorterRef ){ + i--; + n--; + }else +#endif + if( (flags & SQLITE_ECEL_REF)!=0 && (j = pItem->u.x.iOrderByCol)>0 ){ + if( flags & SQLITE_ECEL_OMITREF ){ + i--; + n--; + }else{ + sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i); + } + }else if( (flags & SQLITE_ECEL_FACTOR)!=0 + && sqlite3ExprIsConstantNotJoin(pExpr) + ){ + sqlite3ExprCodeRunJustOnce(pParse, pExpr, target+i); + }else{ + int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i); + if( inReg!=target+i ){ + VdbeOp *pOp; + if( copyOp==OP_Copy + && (pOp=sqlite3VdbeGetLastOp(v))->opcode==OP_Copy + && pOp->p1+pOp->p3+1==inReg + && pOp->p2+pOp->p3+1==target+i + && pOp->p5==0 /* The do-not-merge flag must be clear */ + ){ + pOp->p3++; + }else{ + sqlite3VdbeAddOp2(v, copyOp, inReg, target+i); + } + } + } + } + return n; +} + +/* +** Generate code for a BETWEEN operator. +** +** x BETWEEN y AND z +** +** The above is equivalent to +** +** x>=y AND x<=z +** +** Code it as such, taking care to do the common subexpression +** elimination of x. +** +** The xJumpIf parameter determines details: +** +** NULL: Store the boolean result in reg[dest] +** sqlite3ExprIfTrue: Jump to dest if true +** sqlite3ExprIfFalse: Jump to dest if false +** +** The jumpIfNull parameter is ignored if xJumpIf is NULL. +*/ +static void exprCodeBetween( + Parse *pParse, /* Parsing and code generating context */ + Expr *pExpr, /* The BETWEEN expression */ + int dest, /* Jump destination or storage location */ + void (*xJump)(Parse*,Expr*,int,int), /* Action to take */ + int jumpIfNull /* Take the jump if the BETWEEN is NULL */ +){ + Expr exprAnd; /* The AND operator in x>=y AND x<=z */ + Expr compLeft; /* The x>=y term */ + Expr compRight; /* The x<=z term */ + int regFree1 = 0; /* Temporary use register */ + Expr *pDel = 0; + sqlite3 *db = pParse->db; + + memset(&compLeft, 0, sizeof(Expr)); + memset(&compRight, 0, sizeof(Expr)); + memset(&exprAnd, 0, sizeof(Expr)); + + assert( ExprUseXList(pExpr) ); + pDel = sqlite3ExprDup(db, pExpr->pLeft, 0); + if( db->mallocFailed==0 ){ + exprAnd.op = TK_AND; + exprAnd.pLeft = &compLeft; + exprAnd.pRight = &compRight; + compLeft.op = TK_GE; + compLeft.pLeft = pDel; + compLeft.pRight = pExpr->x.pList->a[0].pExpr; + compRight.op = TK_LE; + compRight.pLeft = pDel; + compRight.pRight = pExpr->x.pList->a[1].pExpr; + exprToRegister(pDel, exprCodeVector(pParse, pDel, &regFree1)); + if( xJump ){ + xJump(pParse, &exprAnd, dest, jumpIfNull); + }else{ + /* Mark the expression is being from the ON or USING clause of a join + ** so that the sqlite3ExprCodeTarget() routine will not attempt to move + ** it into the Parse.pConstExpr list. We should use a new bit for this, + ** for clarity, but we are out of bits in the Expr.flags field so we + ** have to reuse the EP_OuterON bit. Bummer. */ + pDel->flags |= EP_OuterON; + sqlite3ExprCodeTarget(pParse, &exprAnd, dest); + } + sqlite3ReleaseTempReg(pParse, regFree1); + } + sqlite3ExprDelete(db, pDel); + + /* Ensure adequate test coverage */ + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull==0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull==0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull!=0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull!=0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull==0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull==0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull!=0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull!=0 && regFree1!=0 ); + testcase( xJump==0 ); +} + +/* +** Generate code for a boolean expression such that a jump is made +** to the label "dest" if the expression is true but execution +** continues straight thru if the expression is false. +** +** If the expression evaluates to NULL (neither true nor false), then +** take the jump if the jumpIfNull flag is SQLITE_JUMPIFNULL. +** +** This code depends on the fact that certain token values (ex: TK_EQ) +** are the same as opcode values (ex: OP_Eq) that implement the corresponding +** operation. Special comments in vdbe.c and the mkopcodeh.awk script in +** the make process cause these values to align. Assert()s in the code +** below verify that the numbers are aligned correctly. +*/ +SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ + Vdbe *v = pParse->pVdbe; + int op = 0; + int regFree1 = 0; + int regFree2 = 0; + int r1, r2; + + assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 ); + if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */ + if( NEVER(pExpr==0) ) return; /* No way this can happen */ + assert( !ExprHasVVAProperty(pExpr, EP_Immutable) ); + op = pExpr->op; + switch( op ){ + case TK_AND: + case TK_OR: { + Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr); + if( pAlt!=pExpr ){ + sqlite3ExprIfTrue(pParse, pAlt, dest, jumpIfNull); + }else if( op==TK_AND ){ + int d2 = sqlite3VdbeMakeLabel(pParse); + testcase( jumpIfNull==0 ); + sqlite3ExprIfFalse(pParse, pExpr->pLeft, d2, + jumpIfNull^SQLITE_JUMPIFNULL); + sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); + sqlite3VdbeResolveLabel(v, d2); + }else{ + testcase( jumpIfNull==0 ); + sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); + sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); + } + break; + } + case TK_NOT: { + testcase( jumpIfNull==0 ); + sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); + break; + } + case TK_TRUTH: { + int isNot; /* IS NOT TRUE or IS NOT FALSE */ + int isTrue; /* IS TRUE or IS NOT TRUE */ + testcase( jumpIfNull==0 ); + isNot = pExpr->op2==TK_ISNOT; + isTrue = sqlite3ExprTruthValue(pExpr->pRight); + testcase( isTrue && isNot ); + testcase( !isTrue && isNot ); + if( isTrue ^ isNot ){ + sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, + isNot ? SQLITE_JUMPIFNULL : 0); + }else{ + sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, + isNot ? SQLITE_JUMPIFNULL : 0); + } + break; + } + case TK_IS: + case TK_ISNOT: + testcase( op==TK_IS ); + testcase( op==TK_ISNOT ); + op = (op==TK_IS) ? TK_EQ : TK_NE; + jumpIfNull = SQLITE_NULLEQ; + /* no break */ deliberate_fall_through + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: { + if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr; + testcase( jumpIfNull==0 ); + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2); + codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, + r1, r2, dest, jumpIfNull, ExprHasProperty(pExpr,EP_Commuted)); + assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); + VdbeCoverageIf(v, op==OP_Eq && jumpIfNull==SQLITE_NULLEQ); + VdbeCoverageIf(v, op==OP_Eq && jumpIfNull!=SQLITE_NULLEQ); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); + VdbeCoverageIf(v, op==OP_Ne && jumpIfNull==SQLITE_NULLEQ); + VdbeCoverageIf(v, op==OP_Ne && jumpIfNull!=SQLITE_NULLEQ); + testcase( regFree1==0 ); + testcase( regFree2==0 ); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + assert( TK_ISNULL==OP_IsNull ); testcase( op==TK_ISNULL ); + assert( TK_NOTNULL==OP_NotNull ); testcase( op==TK_NOTNULL ); + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1); + sqlite3VdbeTypeofColumn(v, r1); + sqlite3VdbeAddOp2(v, op, r1, dest); + VdbeCoverageIf(v, op==TK_ISNULL); + VdbeCoverageIf(v, op==TK_NOTNULL); + testcase( regFree1==0 ); + break; + } + case TK_BETWEEN: { + testcase( jumpIfNull==0 ); + exprCodeBetween(pParse, pExpr, dest, sqlite3ExprIfTrue, jumpIfNull); + break; + } +#ifndef SQLITE_OMIT_SUBQUERY + case TK_IN: { + int destIfFalse = sqlite3VdbeMakeLabel(pParse); + int destIfNull = jumpIfNull ? dest : destIfFalse; + sqlite3ExprCodeIN(pParse, pExpr, destIfFalse, destIfNull); + sqlite3VdbeGoto(v, dest); + sqlite3VdbeResolveLabel(v, destIfFalse); + break; + } +#endif + default: { + default_expr: + if( ExprAlwaysTrue(pExpr) ){ + sqlite3VdbeGoto(v, dest); + }else if( ExprAlwaysFalse(pExpr) ){ + /* No-op */ + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr, &regFree1); + sqlite3VdbeAddOp3(v, OP_If, r1, dest, jumpIfNull!=0); + VdbeCoverage(v); + testcase( regFree1==0 ); + testcase( jumpIfNull==0 ); + } + break; + } + } + sqlite3ReleaseTempReg(pParse, regFree1); + sqlite3ReleaseTempReg(pParse, regFree2); +} + +/* +** Generate code for a boolean expression such that a jump is made +** to the label "dest" if the expression is false but execution +** continues straight thru if the expression is true. +** +** If the expression evaluates to NULL (neither true nor false) then +** jump if jumpIfNull is SQLITE_JUMPIFNULL or fall through if jumpIfNull +** is 0. +*/ +SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ + Vdbe *v = pParse->pVdbe; + int op = 0; + int regFree1 = 0; + int regFree2 = 0; + int r1, r2; + + assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 ); + if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */ + if( pExpr==0 ) return; + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); + + /* The value of pExpr->op and op are related as follows: + ** + ** pExpr->op op + ** --------- ---------- + ** TK_ISNULL OP_NotNull + ** TK_NOTNULL OP_IsNull + ** TK_NE OP_Eq + ** TK_EQ OP_Ne + ** TK_GT OP_Le + ** TK_LE OP_Gt + ** TK_GE OP_Lt + ** TK_LT OP_Ge + ** + ** For other values of pExpr->op, op is undefined and unused. + ** The value of TK_ and OP_ constants are arranged such that we + ** can compute the mapping above using the following expression. + ** Assert()s verify that the computation is correct. + */ + op = ((pExpr->op+(TK_ISNULL&1))^1)-(TK_ISNULL&1); + + /* Verify correct alignment of TK_ and OP_ constants + */ + assert( pExpr->op!=TK_ISNULL || op==OP_NotNull ); + assert( pExpr->op!=TK_NOTNULL || op==OP_IsNull ); + assert( pExpr->op!=TK_NE || op==OP_Eq ); + assert( pExpr->op!=TK_EQ || op==OP_Ne ); + assert( pExpr->op!=TK_LT || op==OP_Ge ); + assert( pExpr->op!=TK_LE || op==OP_Gt ); + assert( pExpr->op!=TK_GT || op==OP_Le ); + assert( pExpr->op!=TK_GE || op==OP_Lt ); + + switch( pExpr->op ){ + case TK_AND: + case TK_OR: { + Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr); + if( pAlt!=pExpr ){ + sqlite3ExprIfFalse(pParse, pAlt, dest, jumpIfNull); + }else if( pExpr->op==TK_AND ){ + testcase( jumpIfNull==0 ); + sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); + sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); + }else{ + int d2 = sqlite3VdbeMakeLabel(pParse); + testcase( jumpIfNull==0 ); + sqlite3ExprIfTrue(pParse, pExpr->pLeft, d2, + jumpIfNull^SQLITE_JUMPIFNULL); + sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); + sqlite3VdbeResolveLabel(v, d2); + } + break; + } + case TK_NOT: { + testcase( jumpIfNull==0 ); + sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); + break; + } + case TK_TRUTH: { + int isNot; /* IS NOT TRUE or IS NOT FALSE */ + int isTrue; /* IS TRUE or IS NOT TRUE */ + testcase( jumpIfNull==0 ); + isNot = pExpr->op2==TK_ISNOT; + isTrue = sqlite3ExprTruthValue(pExpr->pRight); + testcase( isTrue && isNot ); + testcase( !isTrue && isNot ); + if( isTrue ^ isNot ){ + /* IS TRUE and IS NOT FALSE */ + sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, + isNot ? 0 : SQLITE_JUMPIFNULL); + + }else{ + /* IS FALSE and IS NOT TRUE */ + sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, + isNot ? 0 : SQLITE_JUMPIFNULL); + } + break; + } + case TK_IS: + case TK_ISNOT: + testcase( pExpr->op==TK_IS ); + testcase( pExpr->op==TK_ISNOT ); + op = (pExpr->op==TK_IS) ? TK_NE : TK_EQ; + jumpIfNull = SQLITE_NULLEQ; + /* no break */ deliberate_fall_through + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: { + if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr; + testcase( jumpIfNull==0 ); + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2); + codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, + r1, r2, dest, jumpIfNull,ExprHasProperty(pExpr,EP_Commuted)); + assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); + VdbeCoverageIf(v, op==OP_Eq && jumpIfNull!=SQLITE_NULLEQ); + VdbeCoverageIf(v, op==OP_Eq && jumpIfNull==SQLITE_NULLEQ); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); + VdbeCoverageIf(v, op==OP_Ne && jumpIfNull!=SQLITE_NULLEQ); + VdbeCoverageIf(v, op==OP_Ne && jumpIfNull==SQLITE_NULLEQ); + testcase( regFree1==0 ); + testcase( regFree2==0 ); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1); + sqlite3VdbeTypeofColumn(v, r1); + sqlite3VdbeAddOp2(v, op, r1, dest); + testcase( op==TK_ISNULL ); VdbeCoverageIf(v, op==TK_ISNULL); + testcase( op==TK_NOTNULL ); VdbeCoverageIf(v, op==TK_NOTNULL); + testcase( regFree1==0 ); + break; + } + case TK_BETWEEN: { + testcase( jumpIfNull==0 ); + exprCodeBetween(pParse, pExpr, dest, sqlite3ExprIfFalse, jumpIfNull); + break; + } +#ifndef SQLITE_OMIT_SUBQUERY + case TK_IN: { + if( jumpIfNull ){ + sqlite3ExprCodeIN(pParse, pExpr, dest, dest); + }else{ + int destIfNull = sqlite3VdbeMakeLabel(pParse); + sqlite3ExprCodeIN(pParse, pExpr, dest, destIfNull); + sqlite3VdbeResolveLabel(v, destIfNull); + } + break; + } +#endif + default: { + default_expr: + if( ExprAlwaysFalse(pExpr) ){ + sqlite3VdbeGoto(v, dest); + }else if( ExprAlwaysTrue(pExpr) ){ + /* no-op */ + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr, &regFree1); + sqlite3VdbeAddOp3(v, OP_IfNot, r1, dest, jumpIfNull!=0); + VdbeCoverage(v); + testcase( regFree1==0 ); + testcase( jumpIfNull==0 ); + } + break; + } + } + sqlite3ReleaseTempReg(pParse, regFree1); + sqlite3ReleaseTempReg(pParse, regFree2); +} + +/* +** Like sqlite3ExprIfFalse() except that a copy is made of pExpr before +** code generation, and that copy is deleted after code generation. This +** ensures that the original pExpr is unchanged. +*/ +SQLITE_PRIVATE void sqlite3ExprIfFalseDup(Parse *pParse, Expr *pExpr, int dest,int jumpIfNull){ + sqlite3 *db = pParse->db; + Expr *pCopy = sqlite3ExprDup(db, pExpr, 0); + if( db->mallocFailed==0 ){ + sqlite3ExprIfFalse(pParse, pCopy, dest, jumpIfNull); + } + sqlite3ExprDelete(db, pCopy); +} + +/* +** Expression pVar is guaranteed to be an SQL variable. pExpr may be any +** type of expression. +** +** If pExpr is a simple SQL value - an integer, real, string, blob +** or NULL value - then the VDBE currently being prepared is configured +** to re-prepare each time a new value is bound to variable pVar. +** +** Additionally, if pExpr is a simple SQL value and the value is the +** same as that currently bound to variable pVar, non-zero is returned. +** Otherwise, if the values are not the same or if pExpr is not a simple +** SQL value, zero is returned. +*/ +static int exprCompareVariable( + const Parse *pParse, + const Expr *pVar, + const Expr *pExpr +){ + int res = 0; + int iVar; + sqlite3_value *pL, *pR = 0; + + sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR); + if( pR ){ + iVar = pVar->iColumn; + sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); + pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB); + if( pL ){ + if( sqlite3_value_type(pL)==SQLITE_TEXT ){ + sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */ + } + res = 0==sqlite3MemCompare(pL, pR, 0); + } + sqlite3ValueFree(pR); + sqlite3ValueFree(pL); + } + + return res; +} + +/* +** Do a deep comparison of two expression trees. Return 0 if the two +** expressions are completely identical. Return 1 if they differ only +** by a COLLATE operator at the top level. Return 2 if there are differences +** other than the top-level COLLATE operator. +** +** If any subelement of pB has Expr.iTable==(-1) then it is allowed +** to compare equal to an equivalent element in pA with Expr.iTable==iTab. +** +** The pA side might be using TK_REGISTER. If that is the case and pB is +** not using TK_REGISTER but is otherwise equivalent, then still return 0. +** +** Sometimes this routine will return 2 even if the two expressions +** really are equivalent. If we cannot prove that the expressions are +** identical, we return 2 just to be safe. So if this routine +** returns 2, then you do not really know for certain if the two +** expressions are the same. But if you get a 0 or 1 return, then you +** can be sure the expressions are the same. In the places where +** this routine is used, it does not hurt to get an extra 2 - that +** just might result in some slightly slower code. But returning +** an incorrect 0 or 1 could lead to a malfunction. +** +** If pParse is not NULL then TK_VARIABLE terms in pA with bindings in +** pParse->pReprepare can be matched against literals in pB. The +** pParse->pVdbe->expmask bitmask is updated for each variable referenced. +** If pParse is NULL (the normal case) then any TK_VARIABLE term in +** Argument pParse should normally be NULL. If it is not NULL and pA or +** pB causes a return value of 2. +*/ +SQLITE_PRIVATE int sqlite3ExprCompare( + const Parse *pParse, + const Expr *pA, + const Expr *pB, + int iTab +){ + u32 combinedFlags; + if( pA==0 || pB==0 ){ + return pB==pA ? 0 : 2; + } + if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){ + return 0; + } + combinedFlags = pA->flags | pB->flags; + if( combinedFlags & EP_IntValue ){ + if( (pA->flags&pB->flags&EP_IntValue)!=0 && pA->u.iValue==pB->u.iValue ){ + return 0; + } + return 2; + } + if( pA->op!=pB->op || pA->op==TK_RAISE ){ + if( pA->op==TK_COLLATE && sqlite3ExprCompare(pParse, pA->pLeft,pB,iTab)<2 ){ + return 1; + } + if( pB->op==TK_COLLATE && sqlite3ExprCompare(pParse, pA,pB->pLeft,iTab)<2 ){ + return 1; + } + if( pA->op==TK_AGG_COLUMN && pB->op==TK_COLUMN + && pB->iTable<0 && pA->iTable==iTab + ){ + /* fall through */ + }else{ + return 2; + } + } + assert( !ExprHasProperty(pA, EP_IntValue) ); + assert( !ExprHasProperty(pB, EP_IntValue) ); + if( pA->u.zToken ){ + if( pA->op==TK_FUNCTION || pA->op==TK_AGG_FUNCTION ){ + if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2; +#ifndef SQLITE_OMIT_WINDOWFUNC + assert( pA->op==pB->op ); + if( ExprHasProperty(pA,EP_WinFunc)!=ExprHasProperty(pB,EP_WinFunc) ){ + return 2; + } + if( ExprHasProperty(pA,EP_WinFunc) ){ + if( sqlite3WindowCompare(pParse, pA->y.pWin, pB->y.pWin, 1)!=0 ){ + return 2; + } + } +#endif + }else if( pA->op==TK_NULL ){ + return 0; + }else if( pA->op==TK_COLLATE ){ + if( sqlite3_stricmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2; + }else + if( pB->u.zToken!=0 + && pA->op!=TK_COLUMN + && pA->op!=TK_AGG_COLUMN + && strcmp(pA->u.zToken,pB->u.zToken)!=0 + ){ + return 2; + } + } + if( (pA->flags & (EP_Distinct|EP_Commuted)) + != (pB->flags & (EP_Distinct|EP_Commuted)) ) return 2; + if( ALWAYS((combinedFlags & EP_TokenOnly)==0) ){ + if( combinedFlags & EP_xIsSelect ) return 2; + if( (combinedFlags & EP_FixedCol)==0 + && sqlite3ExprCompare(pParse, pA->pLeft, pB->pLeft, iTab) ) return 2; + if( sqlite3ExprCompare(pParse, pA->pRight, pB->pRight, iTab) ) return 2; + if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2; + if( pA->op!=TK_STRING + && pA->op!=TK_TRUEFALSE + && ALWAYS((combinedFlags & EP_Reduced)==0) + ){ + if( pA->iColumn!=pB->iColumn ) return 2; + if( pA->op2!=pB->op2 && pA->op==TK_TRUTH ) return 2; + if( pA->op!=TK_IN && pA->iTable!=pB->iTable && pA->iTable!=iTab ){ + return 2; + } + } + } + return 0; +} + +/* +** Compare two ExprList objects. Return 0 if they are identical, 1 +** if they are certainly different, or 2 if it is not possible to +** determine if they are identical or not. +** +** If any subelement of pB has Expr.iTable==(-1) then it is allowed +** to compare equal to an equivalent element in pA with Expr.iTable==iTab. +** +** This routine might return non-zero for equivalent ExprLists. The +** only consequence will be disabled optimizations. But this routine +** must never return 0 if the two ExprList objects are different, or +** a malfunction will result. +** +** Two NULL pointers are considered to be the same. But a NULL pointer +** always differs from a non-NULL pointer. +*/ +SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList *pA, const ExprList *pB, int iTab){ + int i; + if( pA==0 && pB==0 ) return 0; + if( pA==0 || pB==0 ) return 1; + if( pA->nExpr!=pB->nExpr ) return 1; + for(i=0; i<pA->nExpr; i++){ + int res; + Expr *pExprA = pA->a[i].pExpr; + Expr *pExprB = pB->a[i].pExpr; + if( pA->a[i].fg.sortFlags!=pB->a[i].fg.sortFlags ) return 1; + if( (res = sqlite3ExprCompare(0, pExprA, pExprB, iTab)) ) return res; + } + return 0; +} + +/* +** Like sqlite3ExprCompare() except COLLATE operators at the top-level +** are ignored. +*/ +SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA,Expr *pB, int iTab){ + return sqlite3ExprCompare(0, + sqlite3ExprSkipCollateAndLikely(pA), + sqlite3ExprSkipCollateAndLikely(pB), + iTab); +} + +/* +** Return non-zero if Expr p can only be true if pNN is not NULL. +** +** Or if seenNot is true, return non-zero if Expr p can only be +** non-NULL if pNN is not NULL +*/ +static int exprImpliesNotNull( + const Parse *pParse,/* Parsing context */ + const Expr *p, /* The expression to be checked */ + const Expr *pNN, /* The expression that is NOT NULL */ + int iTab, /* Table being evaluated */ + int seenNot /* Return true only if p can be any non-NULL value */ +){ + assert( p ); + assert( pNN ); + if( sqlite3ExprCompare(pParse, p, pNN, iTab)==0 ){ + return pNN->op!=TK_NULL; + } + switch( p->op ){ + case TK_IN: { + if( seenNot && ExprHasProperty(p, EP_xIsSelect) ) return 0; + assert( ExprUseXSelect(p) || (p->x.pList!=0 && p->x.pList->nExpr>0) ); + return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); + } + case TK_BETWEEN: { + ExprList *pList; + assert( ExprUseXList(p) ); + pList = p->x.pList; + assert( pList!=0 ); + assert( pList->nExpr==2 ); + if( seenNot ) return 0; + if( exprImpliesNotNull(pParse, pList->a[0].pExpr, pNN, iTab, 1) + || exprImpliesNotNull(pParse, pList->a[1].pExpr, pNN, iTab, 1) + ){ + return 1; + } + return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); + } + case TK_EQ: + case TK_NE: + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_PLUS: + case TK_MINUS: + case TK_BITOR: + case TK_LSHIFT: + case TK_RSHIFT: + case TK_CONCAT: + seenNot = 1; + /* no break */ deliberate_fall_through + case TK_STAR: + case TK_REM: + case TK_BITAND: + case TK_SLASH: { + if( exprImpliesNotNull(pParse, p->pRight, pNN, iTab, seenNot) ) return 1; + /* no break */ deliberate_fall_through + } + case TK_SPAN: + case TK_COLLATE: + case TK_UPLUS: + case TK_UMINUS: { + return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, seenNot); + } + case TK_TRUTH: { + if( seenNot ) return 0; + if( p->op2!=TK_IS ) return 0; + return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); + } + case TK_BITNOT: + case TK_NOT: { + return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); + } + } + return 0; +} + +/* +** Return true if we can prove the pE2 will always be true if pE1 is +** true. Return false if we cannot complete the proof or if pE2 might +** be false. Examples: +** +** pE1: x==5 pE2: x==5 Result: true +** pE1: x>0 pE2: x==5 Result: false +** pE1: x=21 pE2: x=21 OR y=43 Result: true +** pE1: x!=123 pE2: x IS NOT NULL Result: true +** pE1: x!=?1 pE2: x IS NOT NULL Result: true +** pE1: x IS NULL pE2: x IS NOT NULL Result: false +** pE1: x IS ?2 pE2: x IS NOT NULL Result: false +** +** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has +** Expr.iTable<0 then assume a table number given by iTab. +** +** If pParse is not NULL, then the values of bound variables in pE1 are +** compared against literal values in pE2 and pParse->pVdbe->expmask is +** modified to record which bound variables are referenced. If pParse +** is NULL, then false will be returned if pE1 contains any bound variables. +** +** When in doubt, return false. Returning true might give a performance +** improvement. Returning false might cause a performance reduction, but +** it will always give the correct answer and is hence always safe. +*/ +SQLITE_PRIVATE int sqlite3ExprImpliesExpr( + const Parse *pParse, + const Expr *pE1, + const Expr *pE2, + int iTab +){ + if( sqlite3ExprCompare(pParse, pE1, pE2, iTab)==0 ){ + return 1; + } + if( pE2->op==TK_OR + && (sqlite3ExprImpliesExpr(pParse, pE1, pE2->pLeft, iTab) + || sqlite3ExprImpliesExpr(pParse, pE1, pE2->pRight, iTab) ) + ){ + return 1; + } + if( pE2->op==TK_NOTNULL + && exprImpliesNotNull(pParse, pE1, pE2->pLeft, iTab, 0) + ){ + return 1; + } + return 0; +} + +/* This is a helper function to impliesNotNullRow(). In this routine, +** set pWalker->eCode to one only if *both* of the input expressions +** separately have the implies-not-null-row property. +*/ +static void bothImplyNotNullRow(Walker *pWalker, Expr *pE1, Expr *pE2){ + if( pWalker->eCode==0 ){ + sqlite3WalkExpr(pWalker, pE1); + if( pWalker->eCode ){ + pWalker->eCode = 0; + sqlite3WalkExpr(pWalker, pE2); + } + } +} + +/* +** This is the Expr node callback for sqlite3ExprImpliesNonNullRow(). +** If the expression node requires that the table at pWalker->iCur +** have one or more non-NULL column, then set pWalker->eCode to 1 and abort. +** +** pWalker->mWFlags is non-zero if this inquiry is being undertaking on +** behalf of a RIGHT JOIN (or FULL JOIN). That makes a difference when +** evaluating terms in the ON clause of an inner join. +** +** This routine controls an optimization. False positives (setting +** pWalker->eCode to 1 when it should not be) are deadly, but false-negatives +** (never setting pWalker->eCode) is a harmless missed optimization. +*/ +static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ + testcase( pExpr->op==TK_AGG_COLUMN ); + testcase( pExpr->op==TK_AGG_FUNCTION ); + if( ExprHasProperty(pExpr, EP_OuterON) ) return WRC_Prune; + if( ExprHasProperty(pExpr, EP_InnerON) && pWalker->mWFlags ){ + /* If iCur is used in an inner-join ON clause to the left of a + ** RIGHT JOIN, that does *not* mean that the table must be non-null. + ** But it is difficult to check for that condition precisely. + ** To keep things simple, any use of iCur from any inner-join is + ** ignored while attempting to simplify a RIGHT JOIN. */ + return WRC_Prune; + } + switch( pExpr->op ){ + case TK_ISNOT: + case TK_ISNULL: + case TK_NOTNULL: + case TK_IS: + case TK_VECTOR: + case TK_FUNCTION: + case TK_TRUTH: + case TK_CASE: + testcase( pExpr->op==TK_ISNOT ); + testcase( pExpr->op==TK_ISNULL ); + testcase( pExpr->op==TK_NOTNULL ); + testcase( pExpr->op==TK_IS ); + testcase( pExpr->op==TK_VECTOR ); + testcase( pExpr->op==TK_FUNCTION ); + testcase( pExpr->op==TK_TRUTH ); + testcase( pExpr->op==TK_CASE ); + return WRC_Prune; + + case TK_COLUMN: + if( pWalker->u.iCur==pExpr->iTable ){ + pWalker->eCode = 1; + return WRC_Abort; + } + return WRC_Prune; + + case TK_OR: + case TK_AND: + /* Both sides of an AND or OR must separately imply non-null-row. + ** Consider these cases: + ** 1. NOT (x AND y) + ** 2. x OR y + ** If only one of x or y is non-null-row, then the overall expression + ** can be true if the other arm is false (case 1) or true (case 2). + */ + testcase( pExpr->op==TK_OR ); + testcase( pExpr->op==TK_AND ); + bothImplyNotNullRow(pWalker, pExpr->pLeft, pExpr->pRight); + return WRC_Prune; + + case TK_IN: + /* Beware of "x NOT IN ()" and "x NOT IN (SELECT 1 WHERE false)", + ** both of which can be true. But apart from these cases, if + ** the left-hand side of the IN is NULL then the IN itself will be + ** NULL. */ + if( ExprUseXList(pExpr) && ALWAYS(pExpr->x.pList->nExpr>0) ){ + sqlite3WalkExpr(pWalker, pExpr->pLeft); + } + return WRC_Prune; + + case TK_BETWEEN: + /* In "x NOT BETWEEN y AND z" either x must be non-null-row or else + ** both y and z must be non-null row */ + assert( ExprUseXList(pExpr) ); + assert( pExpr->x.pList->nExpr==2 ); + sqlite3WalkExpr(pWalker, pExpr->pLeft); + bothImplyNotNullRow(pWalker, pExpr->x.pList->a[0].pExpr, + pExpr->x.pList->a[1].pExpr); + return WRC_Prune; + + /* Virtual tables are allowed to use constraints like x=NULL. So + ** a term of the form x=y does not prove that y is not null if x + ** is the column of a virtual table */ + case TK_EQ: + case TK_NE: + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: { + Expr *pLeft = pExpr->pLeft; + Expr *pRight = pExpr->pRight; + testcase( pExpr->op==TK_EQ ); + testcase( pExpr->op==TK_NE ); + testcase( pExpr->op==TK_LT ); + testcase( pExpr->op==TK_LE ); + testcase( pExpr->op==TK_GT ); + testcase( pExpr->op==TK_GE ); + /* The y.pTab=0 assignment in wherecode.c always happens after the + ** impliesNotNullRow() test */ + assert( pLeft->op!=TK_COLUMN || ExprUseYTab(pLeft) ); + assert( pRight->op!=TK_COLUMN || ExprUseYTab(pRight) ); + if( (pLeft->op==TK_COLUMN + && ALWAYS(pLeft->y.pTab!=0) + && IsVirtual(pLeft->y.pTab)) + || (pRight->op==TK_COLUMN + && ALWAYS(pRight->y.pTab!=0) + && IsVirtual(pRight->y.pTab)) + ){ + return WRC_Prune; + } + /* no break */ deliberate_fall_through + } + default: + return WRC_Continue; + } +} + +/* +** Return true (non-zero) if expression p can only be true if at least +** one column of table iTab is non-null. In other words, return true +** if expression p will always be NULL or false if every column of iTab +** is NULL. +** +** False negatives are acceptable. In other words, it is ok to return +** zero even if expression p will never be true of every column of iTab +** is NULL. A false negative is merely a missed optimization opportunity. +** +** False positives are not allowed, however. A false positive may result +** in an incorrect answer. +** +** Terms of p that are marked with EP_OuterON (and hence that come from +** the ON or USING clauses of OUTER JOINS) are excluded from the analysis. +** +** This routine is used to check if a LEFT JOIN can be converted into +** an ordinary JOIN. The p argument is the WHERE clause. If the WHERE +** clause requires that some column of the right table of the LEFT JOIN +** be non-NULL, then the LEFT JOIN can be safely converted into an +** ordinary join. +*/ +SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab, int isRJ){ + Walker w; + p = sqlite3ExprSkipCollateAndLikely(p); + if( p==0 ) return 0; + if( p->op==TK_NOTNULL ){ + p = p->pLeft; + }else{ + while( p->op==TK_AND ){ + if( sqlite3ExprImpliesNonNullRow(p->pLeft, iTab, isRJ) ) return 1; + p = p->pRight; + } + } + w.xExprCallback = impliesNotNullRow; + w.xSelectCallback = 0; + w.xSelectCallback2 = 0; + w.eCode = 0; + w.mWFlags = isRJ!=0; + w.u.iCur = iTab; + sqlite3WalkExpr(&w, p); + return w.eCode; +} + +/* +** An instance of the following structure is used by the tree walker +** to determine if an expression can be evaluated by reference to the +** index only, without having to do a search for the corresponding +** table entry. The IdxCover.pIdx field is the index. IdxCover.iCur +** is the cursor for the table. +*/ +struct IdxCover { + Index *pIdx; /* The index to be tested for coverage */ + int iCur; /* Cursor number for the table corresponding to the index */ +}; + +/* +** Check to see if there are references to columns in table +** pWalker->u.pIdxCover->iCur can be satisfied using the index +** pWalker->u.pIdxCover->pIdx. +*/ +static int exprIdxCover(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN + && pExpr->iTable==pWalker->u.pIdxCover->iCur + && sqlite3TableColumnToIndex(pWalker->u.pIdxCover->pIdx, pExpr->iColumn)<0 + ){ + pWalker->eCode = 1; + return WRC_Abort; + } + return WRC_Continue; +} + +/* +** Determine if an index pIdx on table with cursor iCur contains will +** the expression pExpr. Return true if the index does cover the +** expression and false if the pExpr expression references table columns +** that are not found in the index pIdx. +** +** An index covering an expression means that the expression can be +** evaluated using only the index and without having to lookup the +** corresponding table entry. +*/ +SQLITE_PRIVATE int sqlite3ExprCoveredByIndex( + Expr *pExpr, /* The index to be tested */ + int iCur, /* The cursor number for the corresponding table */ + Index *pIdx /* The index that might be used for coverage */ +){ + Walker w; + struct IdxCover xcov; + memset(&w, 0, sizeof(w)); + xcov.iCur = iCur; + xcov.pIdx = pIdx; + w.xExprCallback = exprIdxCover; + w.u.pIdxCover = &xcov; + sqlite3WalkExpr(&w, pExpr); + return !w.eCode; +} + + +/* Structure used to pass information throughout the Walker in order to +** implement sqlite3ReferencesSrcList(). +*/ +struct RefSrcList { + sqlite3 *db; /* Database connection used for sqlite3DbRealloc() */ + SrcList *pRef; /* Looking for references to these tables */ + i64 nExclude; /* Number of tables to exclude from the search */ + int *aiExclude; /* Cursor IDs for tables to exclude from the search */ +}; + +/* +** Walker SELECT callbacks for sqlite3ReferencesSrcList(). +** +** When entering a new subquery on the pExpr argument, add all FROM clause +** entries for that subquery to the exclude list. +** +** When leaving the subquery, remove those entries from the exclude list. +*/ +static int selectRefEnter(Walker *pWalker, Select *pSelect){ + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = pSelect->pSrc; + i64 i, j; + int *piNew; + if( pSrc->nSrc==0 ) return WRC_Continue; + j = p->nExclude; + p->nExclude += pSrc->nSrc; + piNew = sqlite3DbRealloc(p->db, p->aiExclude, p->nExclude*sizeof(int)); + if( piNew==0 ){ + p->nExclude = 0; + return WRC_Abort; + }else{ + p->aiExclude = piNew; + } + for(i=0; i<pSrc->nSrc; i++, j++){ + p->aiExclude[j] = pSrc->a[i].iCursor; + } + return WRC_Continue; +} +static void selectRefLeave(Walker *pWalker, Select *pSelect){ + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = pSelect->pSrc; + if( p->nExclude ){ + assert( p->nExclude>=pSrc->nSrc ); + p->nExclude -= pSrc->nSrc; + } +} + +/* This is the Walker EXPR callback for sqlite3ReferencesSrcList(). +** +** Set the 0x01 bit of pWalker->eCode if there is a reference to any +** of the tables shown in RefSrcList.pRef. +** +** Set the 0x02 bit of pWalker->eCode if there is a reference to a +** table is in neither RefSrcList.pRef nor RefSrcList.aiExclude. +*/ +static int exprRefToSrcList(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN + || pExpr->op==TK_AGG_COLUMN + ){ + int i; + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = p->pRef; + int nSrc = pSrc ? pSrc->nSrc : 0; + for(i=0; i<nSrc; i++){ + if( pExpr->iTable==pSrc->a[i].iCursor ){ + pWalker->eCode |= 1; + return WRC_Continue; + } + } + for(i=0; i<p->nExclude && p->aiExclude[i]!=pExpr->iTable; i++){} + if( i>=p->nExclude ){ + pWalker->eCode |= 2; + } + } + return WRC_Continue; +} + +/* +** Check to see if pExpr references any tables in pSrcList. +** Possible return values: +** +** 1 pExpr does references a table in pSrcList. +** +** 0 pExpr references some table that is not defined in either +** pSrcList or in subqueries of pExpr itself. +** +** -1 pExpr only references no tables at all, or it only +** references tables defined in subqueries of pExpr itself. +** +** As currently used, pExpr is always an aggregate function call. That +** fact is exploited for efficiency. +*/ +SQLITE_PRIVATE int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList *pSrcList){ + Walker w; + struct RefSrcList x; + assert( pParse->db!=0 ); + memset(&w, 0, sizeof(w)); + memset(&x, 0, sizeof(x)); + w.xExprCallback = exprRefToSrcList; + w.xSelectCallback = selectRefEnter; + w.xSelectCallback2 = selectRefLeave; + w.u.pRefSrcList = &x; + x.db = pParse->db; + x.pRef = pSrcList; + assert( pExpr->op==TK_AGG_FUNCTION ); + assert( ExprUseXList(pExpr) ); + sqlite3WalkExprList(&w, pExpr->x.pList); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + sqlite3WalkExpr(&w, pExpr->y.pWin->pFilter); + } +#endif + if( x.aiExclude ) sqlite3DbNNFreeNN(pParse->db, x.aiExclude); + if( w.eCode & 0x01 ){ + return 1; + }else if( w.eCode ){ + return 0; + }else{ + return -1; + } +} + +/* +** This is a Walker expression node callback. +** +** For Expr nodes that contain pAggInfo pointers, make sure the AggInfo +** object that is referenced does not refer directly to the Expr. If +** it does, make a copy. This is done because the pExpr argument is +** subject to change. +** +** The copy is scheduled for deletion using the sqlite3ExprDeferredDelete() +** which builds on the sqlite3ParserAddCleanup() mechanism. +*/ +static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ + if( ALWAYS(!ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced)) + && pExpr->pAggInfo!=0 + ){ + AggInfo *pAggInfo = pExpr->pAggInfo; + int iAgg = pExpr->iAgg; + Parse *pParse = pWalker->pParse; + sqlite3 *db = pParse->db; + assert( iAgg>=0 ); + if( pExpr->op!=TK_AGG_FUNCTION ){ + if( iAgg<pAggInfo->nColumn + && pAggInfo->aCol[iAgg].pCExpr==pExpr + ){ + pExpr = sqlite3ExprDup(db, pExpr, 0); + if( pExpr ){ + pAggInfo->aCol[iAgg].pCExpr = pExpr; + sqlite3ExprDeferredDelete(pParse, pExpr); + } + } + }else{ + assert( pExpr->op==TK_AGG_FUNCTION ); + if( ALWAYS(iAgg<pAggInfo->nFunc) + && pAggInfo->aFunc[iAgg].pFExpr==pExpr + ){ + pExpr = sqlite3ExprDup(db, pExpr, 0); + if( pExpr ){ + pAggInfo->aFunc[iAgg].pFExpr = pExpr; + sqlite3ExprDeferredDelete(pParse, pExpr); + } + } + } + } + return WRC_Continue; +} + +/* +** Initialize a Walker object so that will persist AggInfo entries referenced +** by the tree that is walked. +*/ +SQLITE_PRIVATE void sqlite3AggInfoPersistWalkerInit(Walker *pWalker, Parse *pParse){ + memset(pWalker, 0, sizeof(*pWalker)); + pWalker->pParse = pParse; + pWalker->xExprCallback = agginfoPersistExprCb; + pWalker->xSelectCallback = sqlite3SelectWalkNoop; +} + +/* +** Add a new element to the pAggInfo->aCol[] array. Return the index of +** the new element. Return a negative number if malloc fails. +*/ +static int addAggInfoColumn(sqlite3 *db, AggInfo *pInfo){ + int i; + pInfo->aCol = sqlite3ArrayAllocate( + db, + pInfo->aCol, + sizeof(pInfo->aCol[0]), + &pInfo->nColumn, + &i + ); + return i; +} + +/* +** Add a new element to the pAggInfo->aFunc[] array. Return the index of +** the new element. Return a negative number if malloc fails. +*/ +static int addAggInfoFunc(sqlite3 *db, AggInfo *pInfo){ + int i; + pInfo->aFunc = sqlite3ArrayAllocate( + db, + pInfo->aFunc, + sizeof(pInfo->aFunc[0]), + &pInfo->nFunc, + &i + ); + return i; +} + +/* +** Search the AggInfo object for an aCol[] entry that has iTable and iColumn. +** Return the index in aCol[] of the entry that describes that column. +** +** If no prior entry is found, create a new one and return -1. The +** new column will have an index of pAggInfo->nColumn-1. +*/ +static void findOrCreateAggInfoColumn( + Parse *pParse, /* Parsing context */ + AggInfo *pAggInfo, /* The AggInfo object to search and/or modify */ + Expr *pExpr /* Expr describing the column to find or insert */ +){ + struct AggInfo_col *pCol; + int k; + + assert( pAggInfo->iFirstReg==0 ); + pCol = pAggInfo->aCol; + for(k=0; k<pAggInfo->nColumn; k++, pCol++){ + if( pCol->pCExpr==pExpr ) return; + if( pCol->iTable==pExpr->iTable + && pCol->iColumn==pExpr->iColumn + && pExpr->op!=TK_IF_NULL_ROW + ){ + goto fix_up_expr; + } + } + k = addAggInfoColumn(pParse->db, pAggInfo); + if( k<0 ){ + /* OOM on resize */ + assert( pParse->db->mallocFailed ); + return; + } + pCol = &pAggInfo->aCol[k]; + assert( ExprUseYTab(pExpr) ); + pCol->pTab = pExpr->y.pTab; + pCol->iTable = pExpr->iTable; + pCol->iColumn = pExpr->iColumn; + pCol->iSorterColumn = -1; + pCol->pCExpr = pExpr; + if( pAggInfo->pGroupBy && pExpr->op!=TK_IF_NULL_ROW ){ + int j, n; + ExprList *pGB = pAggInfo->pGroupBy; + struct ExprList_item *pTerm = pGB->a; + n = pGB->nExpr; + for(j=0; j<n; j++, pTerm++){ + Expr *pE = pTerm->pExpr; + if( pE->op==TK_COLUMN + && pE->iTable==pExpr->iTable + && pE->iColumn==pExpr->iColumn + ){ + pCol->iSorterColumn = j; + break; + } + } + } + if( pCol->iSorterColumn<0 ){ + pCol->iSorterColumn = pAggInfo->nSortingColumn++; + } +fix_up_expr: + ExprSetVVAProperty(pExpr, EP_NoReduce); + assert( pExpr->pAggInfo==0 || pExpr->pAggInfo==pAggInfo ); + pExpr->pAggInfo = pAggInfo; + if( pExpr->op==TK_COLUMN ){ + pExpr->op = TK_AGG_COLUMN; + } + pExpr->iAgg = (i16)k; +} + +/* +** This is the xExprCallback for a tree walker. It is used to +** implement sqlite3ExprAnalyzeAggregates(). See sqlite3ExprAnalyzeAggregates +** for additional information. +*/ +static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ + int i; + NameContext *pNC = pWalker->u.pNC; + Parse *pParse = pNC->pParse; + SrcList *pSrcList = pNC->pSrcList; + AggInfo *pAggInfo = pNC->uNC.pAggInfo; + + assert( pNC->ncFlags & NC_UAggInfo ); + assert( pAggInfo->iFirstReg==0 ); + switch( pExpr->op ){ + default: { + IndexedExpr *pIEpr; + Expr tmp; + assert( pParse->iSelfTab==0 ); + if( (pNC->ncFlags & NC_InAggFunc)==0 ) break; + if( pParse->pIdxEpr==0 ) break; + for(pIEpr=pParse->pIdxEpr; pIEpr; pIEpr=pIEpr->pIENext){ + int iDataCur = pIEpr->iDataCur; + if( iDataCur<0 ) continue; + if( sqlite3ExprCompare(0, pExpr, pIEpr->pExpr, iDataCur)==0 ) break; + } + if( pIEpr==0 ) break; + if( NEVER(!ExprUseYTab(pExpr)) ) break; + for(i=0; i<pSrcList->nSrc; i++){ + if( pSrcList->a[0].iCursor==pIEpr->iDataCur ) break; + } + if( i>=pSrcList->nSrc ) break; + if( NEVER(pExpr->pAggInfo!=0) ) break; /* Resolved by outer context */ + if( pParse->nErr ){ return WRC_Abort; } + + /* If we reach this point, it means that expression pExpr can be + ** translated into a reference to an index column as described by + ** pIEpr. + */ + memset(&tmp, 0, sizeof(tmp)); + tmp.op = TK_AGG_COLUMN; + tmp.iTable = pIEpr->iIdxCur; + tmp.iColumn = pIEpr->iIdxCol; + findOrCreateAggInfoColumn(pParse, pAggInfo, &tmp); + if( pParse->nErr ){ return WRC_Abort; } + assert( pAggInfo->aCol!=0 ); + assert( tmp.iAgg<pAggInfo->nColumn ); + pAggInfo->aCol[tmp.iAgg].pCExpr = pExpr; + pExpr->pAggInfo = pAggInfo; + pExpr->iAgg = tmp.iAgg; + return WRC_Prune; + } + case TK_IF_NULL_ROW: + case TK_AGG_COLUMN: + case TK_COLUMN: { + testcase( pExpr->op==TK_AGG_COLUMN ); + testcase( pExpr->op==TK_COLUMN ); + testcase( pExpr->op==TK_IF_NULL_ROW ); + /* Check to see if the column is in one of the tables in the FROM + ** clause of the aggregate query */ + if( ALWAYS(pSrcList!=0) ){ + SrcItem *pItem = pSrcList->a; + for(i=0; i<pSrcList->nSrc; i++, pItem++){ + assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); + if( pExpr->iTable==pItem->iCursor ){ + findOrCreateAggInfoColumn(pParse, pAggInfo, pExpr); + break; + } /* endif pExpr->iTable==pItem->iCursor */ + } /* end loop over pSrcList */ + } + return WRC_Continue; + } + case TK_AGG_FUNCTION: { + if( (pNC->ncFlags & NC_InAggFunc)==0 + && pWalker->walkerDepth==pExpr->op2 + ){ + /* Check to see if pExpr is a duplicate of another aggregate + ** function that is already in the pAggInfo structure + */ + struct AggInfo_func *pItem = pAggInfo->aFunc; + for(i=0; i<pAggInfo->nFunc; i++, pItem++){ + if( pItem->pFExpr==pExpr ) break; + if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){ + break; + } + } + if( i>=pAggInfo->nFunc ){ + /* pExpr is original. Make a new entry in pAggInfo->aFunc[] + */ + u8 enc = ENC(pParse->db); + i = addAggInfoFunc(pParse->db, pAggInfo); + if( i>=0 ){ + assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + pItem = &pAggInfo->aFunc[i]; + pItem->pFExpr = pExpr; + assert( ExprUseUToken(pExpr) ); + pItem->pFunc = sqlite3FindFunction(pParse->db, + pExpr->u.zToken, + pExpr->x.pList ? pExpr->x.pList->nExpr : 0, enc, 0); + if( pExpr->flags & EP_Distinct ){ + pItem->iDistinct = pParse->nTab++; + }else{ + pItem->iDistinct = -1; + } + } + } + /* Make pExpr point to the appropriate pAggInfo->aFunc[] entry + */ + assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); + ExprSetVVAProperty(pExpr, EP_NoReduce); + pExpr->iAgg = (i16)i; + pExpr->pAggInfo = pAggInfo; + return WRC_Prune; + }else{ + return WRC_Continue; + } + } + } + return WRC_Continue; +} + +/* +** Analyze the pExpr expression looking for aggregate functions and +** for variables that need to be added to AggInfo object that pNC->pAggInfo +** points to. Additional entries are made on the AggInfo object as +** necessary. +** +** This routine should only be called after the expression has been +** analyzed by sqlite3ResolveExprNames(). +*/ +SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext *pNC, Expr *pExpr){ + Walker w; + w.xExprCallback = analyzeAggregate; + w.xSelectCallback = sqlite3WalkerDepthIncrease; + w.xSelectCallback2 = sqlite3WalkerDepthDecrease; + w.walkerDepth = 0; + w.u.pNC = pNC; + w.pParse = 0; + assert( pNC->pSrcList!=0 ); + sqlite3WalkExpr(&w, pExpr); +} + +/* +** Call sqlite3ExprAnalyzeAggregates() for every expression in an +** expression list. Return the number of errors. +** +** If an error is found, the analysis is cut short. +*/ +SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext *pNC, ExprList *pList){ + struct ExprList_item *pItem; + int i; + if( pList ){ + for(pItem=pList->a, i=0; i<pList->nExpr; i++, pItem++){ + sqlite3ExprAnalyzeAggregates(pNC, pItem->pExpr); + } + } +} + +/* +** Allocate a single new register for use to hold some intermediate result. +*/ +SQLITE_PRIVATE int sqlite3GetTempReg(Parse *pParse){ + if( pParse->nTempReg==0 ){ + return ++pParse->nMem; + } + return pParse->aTempReg[--pParse->nTempReg]; +} + +/* +** Deallocate a register, making available for reuse for some other +** purpose. +*/ +SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse *pParse, int iReg){ + if( iReg ){ + sqlite3VdbeReleaseRegisters(pParse, iReg, 1, 0, 0); + if( pParse->nTempReg<ArraySize(pParse->aTempReg) ){ + pParse->aTempReg[pParse->nTempReg++] = iReg; + } + } +} + +/* +** Allocate or deallocate a block of nReg consecutive registers. +*/ +SQLITE_PRIVATE int sqlite3GetTempRange(Parse *pParse, int nReg){ + int i, n; + if( nReg==1 ) return sqlite3GetTempReg(pParse); + i = pParse->iRangeReg; + n = pParse->nRangeReg; + if( nReg<=n ){ + pParse->iRangeReg += nReg; + pParse->nRangeReg -= nReg; + }else{ + i = pParse->nMem+1; + pParse->nMem += nReg; + } + return i; +} +SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse *pParse, int iReg, int nReg){ + if( nReg==1 ){ + sqlite3ReleaseTempReg(pParse, iReg); + return; + } + sqlite3VdbeReleaseRegisters(pParse, iReg, nReg, 0, 0); + if( nReg>pParse->nRangeReg ){ + pParse->nRangeReg = nReg; + pParse->iRangeReg = iReg; + } +} + +/* +** Mark all temporary registers as being unavailable for reuse. +** +** Always invoke this procedure after coding a subroutine or co-routine +** that might be invoked from other parts of the code, to ensure that +** the sub/co-routine does not use registers in common with the code that +** invokes the sub/co-routine. +*/ +SQLITE_PRIVATE void sqlite3ClearTempRegCache(Parse *pParse){ + pParse->nTempReg = 0; + pParse->nRangeReg = 0; +} + +/* +** Make sure sufficient registers have been allocated so that +** iReg is a valid register number. +*/ +SQLITE_PRIVATE void sqlite3TouchRegister(Parse *pParse, int iReg){ + if( pParse->nMem<iReg ) pParse->nMem = iReg; +} + +#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_DEBUG) +/* +** Return the latest reusable register in the set of all registers. +** The value returned is no less than iMin. If any register iMin or +** greater is in permanent use, then return one more than that last +** permanent register. +*/ +SQLITE_PRIVATE int sqlite3FirstAvailableRegister(Parse *pParse, int iMin){ + const ExprList *pList = pParse->pConstExpr; + if( pList ){ + int i; + for(i=0; i<pList->nExpr; i++){ + if( pList->a[i].u.iConstExprReg>=iMin ){ + iMin = pList->a[i].u.iConstExprReg + 1; + } + } + } + pParse->nTempReg = 0; + pParse->nRangeReg = 0; + return iMin; +} +#endif /* SQLITE_ENABLE_STAT4 || SQLITE_DEBUG */ + +/* +** Validate that no temporary register falls within the range of +** iFirst..iLast, inclusive. This routine is only call from within assert() +** statements. +*/ +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){ + int i; + if( pParse->nRangeReg>0 + && pParse->iRangeReg+pParse->nRangeReg > iFirst + && pParse->iRangeReg <= iLast + ){ + return 0; + } + for(i=0; i<pParse->nTempReg; i++){ + if( pParse->aTempReg[i]>=iFirst && pParse->aTempReg[i]<=iLast ){ + return 0; + } + } + if( pParse->pConstExpr ){ + ExprList *pList = pParse->pConstExpr; + for(i=0; i<pList->nExpr; i++){ + int iReg = pList->a[i].u.iConstExprReg; + if( iReg==0 ) continue; + if( iReg>=iFirst && iReg<=iLast ) return 0; + } + } + return 1; +} +#endif /* SQLITE_DEBUG */ + +/************** End of expr.c ************************************************/ +/************** Begin file alter.c *******************************************/ +/* +** 2005 February 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that used to generate VDBE code +** that implements the ALTER TABLE command. +*/ +/* #include "sqliteInt.h" */ + +/* +** The code in this file only exists if we are not omitting the +** ALTER TABLE logic from the build. +*/ +#ifndef SQLITE_OMIT_ALTERTABLE + +/* +** Parameter zName is the name of a table that is about to be altered +** (either with ALTER TABLE ... RENAME TO or ALTER TABLE ... ADD COLUMN). +** If the table is a system table, this function leaves an error message +** in pParse->zErr (system tables may not be altered) and returns non-zero. +** +** Or, if zName is not a system table, zero is returned. +*/ +static int isAlterableTable(Parse *pParse, Table *pTab){ + if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) +#ifndef SQLITE_OMIT_VIRTUALTABLE + || (pTab->tabFlags & TF_Eponymous)!=0 + || ( (pTab->tabFlags & TF_Shadow)!=0 + && sqlite3ReadOnlyShadowTables(pParse->db) + ) +#endif + ){ + sqlite3ErrorMsg(pParse, "table %s may not be altered", pTab->zName); + return 1; + } + return 0; +} + +/* +** Generate code to verify that the schemas of database zDb and, if +** bTemp is not true, database "temp", can still be parsed. This is +** called at the end of the generation of an ALTER TABLE ... RENAME ... +** statement to ensure that the operation has not rendered any schema +** objects unusable. +*/ +static void renameTestSchema( + Parse *pParse, /* Parse context */ + const char *zDb, /* Name of db to verify schema of */ + int bTemp, /* True if this is the temp db */ + const char *zWhen, /* "when" part of error message */ + int bNoDQS /* Do not allow DQS in the schema */ +){ + pParse->colNamesSet = 1; + sqlite3NestedParse(pParse, + "SELECT 1 " + "FROM \"%w\"." LEGACY_SCHEMA_TABLE " " + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + " AND sql NOT LIKE 'create virtual%%'" + " AND sqlite_rename_test(%Q, sql, type, name, %d, %Q, %d)=NULL ", + zDb, + zDb, bTemp, zWhen, bNoDQS + ); + + if( bTemp==0 ){ + sqlite3NestedParse(pParse, + "SELECT 1 " + "FROM temp." LEGACY_SCHEMA_TABLE " " + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + " AND sql NOT LIKE 'create virtual%%'" + " AND sqlite_rename_test(%Q, sql, type, name, 1, %Q, %d)=NULL ", + zDb, zWhen, bNoDQS + ); + } +} + +/* +** Generate VM code to replace any double-quoted strings (but not double-quoted +** identifiers) within the "sql" column of the sqlite_schema table in +** database zDb with their single-quoted equivalents. If argument bTemp is +** not true, similarly update all SQL statements in the sqlite_schema table +** of the temp db. +*/ +static void renameFixQuotes(Parse *pParse, const char *zDb, int bTemp){ + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE + " SET sql = sqlite_rename_quotefix(%Q, sql)" + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + " AND sql NOT LIKE 'create virtual%%'" , zDb, zDb + ); + if( bTemp==0 ){ + sqlite3NestedParse(pParse, + "UPDATE temp." LEGACY_SCHEMA_TABLE + " SET sql = sqlite_rename_quotefix('temp', sql)" + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + " AND sql NOT LIKE 'create virtual%%'" + ); + } +} + +/* +** Generate code to reload the schema for database iDb. And, if iDb!=1, for +** the temp database as well. +*/ +static void renameReloadSchema(Parse *pParse, int iDb, u16 p5){ + Vdbe *v = pParse->pVdbe; + if( v ){ + sqlite3ChangeCookie(pParse, iDb); + sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iDb, 0, p5); + if( iDb!=1 ) sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, 1, 0, p5); + } +} + +/* +** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy" +** command. +*/ +SQLITE_PRIVATE void sqlite3AlterRenameTable( + Parse *pParse, /* Parser context. */ + SrcList *pSrc, /* The table to rename. */ + Token *pName /* The new table name. */ +){ + int iDb; /* Database that contains the table */ + char *zDb; /* Name of database iDb */ + Table *pTab; /* Table being renamed */ + char *zName = 0; /* NULL-terminated version of pName */ + sqlite3 *db = pParse->db; /* Database connection */ + int nTabName; /* Number of UTF-8 characters in zTabName */ + const char *zTabName; /* Original name of the table */ + Vdbe *v; + VTable *pVTab = 0; /* Non-zero if this is a v-tab with an xRename() */ + + if( NEVER(db->mallocFailed) ) goto exit_rename_table; + assert( pSrc->nSrc==1 ); + assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); + + pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); + if( !pTab ) goto exit_rename_table; + iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + zDb = db->aDb[iDb].zDbSName; + + /* Get a NULL terminated version of the new table name. */ + zName = sqlite3NameFromToken(db, pName); + if( !zName ) goto exit_rename_table; + + /* Check that a table or index named 'zName' does not already exist + ** in database iDb. If so, this is an error. + */ + if( sqlite3FindTable(db, zName, zDb) + || sqlite3FindIndex(db, zName, zDb) + || sqlite3IsShadowTableOf(db, pTab, zName) + ){ + sqlite3ErrorMsg(pParse, + "there is already another table or index with this name: %s", zName); + goto exit_rename_table; + } + + /* Make sure it is not a system table being altered, or a reserved name + ** that the table is being renamed to. + */ + if( SQLITE_OK!=isAlterableTable(pParse, pTab) ){ + goto exit_rename_table; + } + if( SQLITE_OK!=sqlite3CheckObjectName(pParse,zName,"table",zName) ){ + goto exit_rename_table; + } + +#ifndef SQLITE_OMIT_VIEW + if( IsView(pTab) ){ + sqlite3ErrorMsg(pParse, "view %s may not be altered", pTab->zName); + goto exit_rename_table; + } +#endif + +#ifndef SQLITE_OMIT_AUTHORIZATION + /* Invoke the authorization callback. */ + if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){ + goto exit_rename_table; + } +#endif + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( sqlite3ViewGetColumnNames(pParse, pTab) ){ + goto exit_rename_table; + } + if( IsVirtual(pTab) ){ + pVTab = sqlite3GetVTable(db, pTab); + if( pVTab->pVtab->pModule->xRename==0 ){ + pVTab = 0; + } + } +#endif + + /* Begin a transaction for database iDb. Then modify the schema cookie + ** (since the ALTER TABLE modifies the schema). Call sqlite3MayAbort(), + ** as the scalar functions (e.g. sqlite_rename_table()) invoked by the + ** nested SQL may raise an exception. */ + v = sqlite3GetVdbe(pParse); + if( v==0 ){ + goto exit_rename_table; + } + sqlite3MayAbort(pParse); + + /* figure out how many UTF-8 characters are in zName */ + zTabName = pTab->zName; + nTabName = sqlite3Utf8CharLen(zTabName, -1); + + /* Rewrite all CREATE TABLE, INDEX, TRIGGER or VIEW statements in + ** the schema to use the new table name. */ + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, %d) " + "WHERE (type!='index' OR tbl_name=%Q COLLATE nocase)" + "AND name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + , zDb, zDb, zTabName, zName, (iDb==1), zTabName + ); + + /* Update the tbl_name and name columns of the sqlite_schema table + ** as required. */ + sqlite3NestedParse(pParse, + "UPDATE %Q." LEGACY_SCHEMA_TABLE " SET " + "tbl_name = %Q, " + "name = CASE " + "WHEN type='table' THEN %Q " + "WHEN name LIKE 'sqliteX_autoindex%%' ESCAPE 'X' " + " AND type='index' THEN " + "'sqlite_autoindex_' || %Q || substr(name,%d+18) " + "ELSE name END " + "WHERE tbl_name=%Q COLLATE nocase AND " + "(type='table' OR type='index' OR type='trigger');", + zDb, + zName, zName, zName, + nTabName, zTabName + ); + +#ifndef SQLITE_OMIT_AUTOINCREMENT + /* If the sqlite_sequence table exists in this database, then update + ** it with the new table name. + */ + if( sqlite3FindTable(db, "sqlite_sequence", zDb) ){ + sqlite3NestedParse(pParse, + "UPDATE \"%w\".sqlite_sequence set name = %Q WHERE name = %Q", + zDb, zName, pTab->zName); + } +#endif + + /* If the table being renamed is not itself part of the temp database, + ** edit view and trigger definitions within the temp database + ** as required. */ + if( iDb!=1 ){ + sqlite3NestedParse(pParse, + "UPDATE sqlite_temp_schema SET " + "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, 1), " + "tbl_name = " + "CASE WHEN tbl_name=%Q COLLATE nocase AND " + " sqlite_rename_test(%Q, sql, type, name, 1, 'after rename', 0) " + "THEN %Q ELSE tbl_name END " + "WHERE type IN ('view', 'trigger')" + , zDb, zTabName, zName, zTabName, zDb, zName); + } + + /* If this is a virtual table, invoke the xRename() function if + ** one is defined. The xRename() callback will modify the names + ** of any resources used by the v-table implementation (including other + ** SQLite tables) that are identified by the name of the virtual table. + */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( pVTab ){ + int i = ++pParse->nMem; + sqlite3VdbeLoadString(v, i, zName); + sqlite3VdbeAddOp4(v, OP_VRename, i, 0, 0,(const char*)pVTab, P4_VTAB); + } +#endif + + renameReloadSchema(pParse, iDb, INITFLAG_AlterRename); + renameTestSchema(pParse, zDb, iDb==1, "after rename", 0); + +exit_rename_table: + sqlite3SrcListDelete(db, pSrc); + sqlite3DbFree(db, zName); +} + +/* +** Write code that will raise an error if the table described by +** zDb and zTab is not empty. +*/ +static void sqlite3ErrorIfNotEmpty( + Parse *pParse, /* Parsing context */ + const char *zDb, /* Schema holding the table */ + const char *zTab, /* Table to check for empty */ + const char *zErr /* Error message text */ +){ + sqlite3NestedParse(pParse, + "SELECT raise(ABORT,%Q) FROM \"%w\".\"%w\"", + zErr, zDb, zTab + ); +} + +/* +** This function is called after an "ALTER TABLE ... ADD" statement +** has been parsed. Argument pColDef contains the text of the new +** column definition. +** +** The Table structure pParse->pNewTable was extended to include +** the new column during parsing. +*/ +SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ + Table *pNew; /* Copy of pParse->pNewTable */ + Table *pTab; /* Table being altered */ + int iDb; /* Database number */ + const char *zDb; /* Database name */ + const char *zTab; /* Table name */ + char *zCol; /* Null-terminated column definition */ + Column *pCol; /* The new column */ + Expr *pDflt; /* Default value for the new column */ + sqlite3 *db; /* The database connection; */ + Vdbe *v; /* The prepared statement under construction */ + int r1; /* Temporary registers */ + + db = pParse->db; + assert( db->pParse==pParse ); + if( pParse->nErr ) return; + assert( db->mallocFailed==0 ); + pNew = pParse->pNewTable; + assert( pNew ); + + assert( sqlite3BtreeHoldsAllMutexes(db) ); + iDb = sqlite3SchemaToIndex(db, pNew->pSchema); + zDb = db->aDb[iDb].zDbSName; + zTab = &pNew->zName[16]; /* Skip the "sqlite_altertab_" prefix on the name */ + pCol = &pNew->aCol[pNew->nCol-1]; + pDflt = sqlite3ColumnExpr(pNew, pCol); + pTab = sqlite3FindTable(db, zTab, zDb); + assert( pTab ); + +#ifndef SQLITE_OMIT_AUTHORIZATION + /* Invoke the authorization callback. */ + if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){ + return; + } +#endif + + + /* Check that the new column is not specified as PRIMARY KEY or UNIQUE. + ** If there is a NOT NULL constraint, then the default value for the + ** column must not be NULL. + */ + if( pCol->colFlags & COLFLAG_PRIMKEY ){ + sqlite3ErrorMsg(pParse, "Cannot add a PRIMARY KEY column"); + return; + } + if( pNew->pIndex ){ + sqlite3ErrorMsg(pParse, + "Cannot add a UNIQUE column"); + return; + } + if( (pCol->colFlags & COLFLAG_GENERATED)==0 ){ + /* If the default value for the new column was specified with a + ** literal NULL, then set pDflt to 0. This simplifies checking + ** for an SQL NULL default below. + */ + assert( pDflt==0 || pDflt->op==TK_SPAN ); + if( pDflt && pDflt->pLeft->op==TK_NULL ){ + pDflt = 0; + } + assert( IsOrdinaryTable(pNew) ); + if( (db->flags&SQLITE_ForeignKeys) && pNew->u.tab.pFKey && pDflt ){ + sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, + "Cannot add a REFERENCES column with non-NULL default value"); + } + if( pCol->notNull && !pDflt ){ + sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, + "Cannot add a NOT NULL column with default value NULL"); + } + + + /* Ensure the default expression is something that sqlite3ValueFromExpr() + ** can handle (i.e. not CURRENT_TIME etc.) + */ + if( pDflt ){ + sqlite3_value *pVal = 0; + int rc; + rc = sqlite3ValueFromExpr(db, pDflt, SQLITE_UTF8, SQLITE_AFF_BLOB, &pVal); + assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); + if( rc!=SQLITE_OK ){ + assert( db->mallocFailed == 1 ); + return; + } + if( !pVal ){ + sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, + "Cannot add a column with non-constant default"); + } + sqlite3ValueFree(pVal); + } + }else if( pCol->colFlags & COLFLAG_STORED ){ + sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, "cannot add a STORED column"); + } + + + /* Modify the CREATE TABLE statement. */ + zCol = sqlite3DbStrNDup(db, (char*)pColDef->z, pColDef->n); + if( zCol ){ + char *zEnd = &zCol[pColDef->n-1]; + while( zEnd>zCol && (*zEnd==';' || sqlite3Isspace(*zEnd)) ){ + *zEnd-- = '\0'; + } + /* substr() operations on characters, but addColOffset is in bytes. So we + ** have to use printf() to translate between these units: */ + assert( IsOrdinaryTable(pTab) ); + assert( IsOrdinaryTable(pNew) ); + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = printf('%%.%ds, ',sql) || %Q" + " || substr(sql,1+length(printf('%%.%ds',sql))) " + "WHERE type = 'table' AND name = %Q", + zDb, pNew->u.tab.addColOffset, zCol, pNew->u.tab.addColOffset, + zTab + ); + sqlite3DbFree(db, zCol); + } + + v = sqlite3GetVdbe(pParse); + if( v ){ + /* Make sure the schema version is at least 3. But do not upgrade + ** from less than 3 to 4, as that will corrupt any preexisting DESC + ** index. + */ + r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, r1, BTREE_FILE_FORMAT); + sqlite3VdbeUsesBtree(v, iDb); + sqlite3VdbeAddOp2(v, OP_AddImm, r1, -2); + sqlite3VdbeAddOp2(v, OP_IfPos, r1, sqlite3VdbeCurrentAddr(v)+2); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, 3); + sqlite3ReleaseTempReg(pParse, r1); + + /* Reload the table definition */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterAdd); + + /* Verify that constraints are still satisfied */ + if( pNew->pCheck!=0 + || (pCol->notNull && (pCol->colFlags & COLFLAG_GENERATED)!=0) + ){ + sqlite3NestedParse(pParse, + "SELECT CASE WHEN quick_check GLOB 'CHECK*'" + " THEN raise(ABORT,'CHECK constraint failed')" + " ELSE raise(ABORT,'NOT NULL constraint failed')" + " END" + " FROM pragma_quick_check(%Q,%Q)" + " WHERE quick_check GLOB 'CHECK*' OR quick_check GLOB 'NULL*'", + zTab, zDb + ); + } + } +} + +/* +** This function is called by the parser after the table-name in +** an "ALTER TABLE <table-name> ADD" statement is parsed. Argument +** pSrc is the full-name of the table being altered. +** +** This routine makes a (partial) copy of the Table structure +** for the table being altered and sets Parse.pNewTable to point +** to it. Routines called by the parser as the column definition +** is parsed (i.e. sqlite3AddColumn()) add the new Column data to +** the copy. The copy of the Table structure is deleted by tokenize.c +** after parsing is finished. +** +** Routine sqlite3AlterFinishAddColumn() will be called to complete +** coding the "ALTER TABLE ... ADD" statement. +*/ +SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ + Table *pNew; + Table *pTab; + int iDb; + int i; + int nAlloc; + sqlite3 *db = pParse->db; + + /* Look up the table being altered. */ + assert( pParse->pNewTable==0 ); + assert( sqlite3BtreeHoldsAllMutexes(db) ); + if( db->mallocFailed ) goto exit_begin_add_column; + pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); + if( !pTab ) goto exit_begin_add_column; + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pTab) ){ + sqlite3ErrorMsg(pParse, "virtual tables may not be altered"); + goto exit_begin_add_column; + } +#endif + + /* Make sure this is not an attempt to ALTER a view. */ + if( IsView(pTab) ){ + sqlite3ErrorMsg(pParse, "Cannot add a column to a view"); + goto exit_begin_add_column; + } + if( SQLITE_OK!=isAlterableTable(pParse, pTab) ){ + goto exit_begin_add_column; + } + + sqlite3MayAbort(pParse); + assert( IsOrdinaryTable(pTab) ); + assert( pTab->u.tab.addColOffset>0 ); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + + /* Put a copy of the Table struct in Parse.pNewTable for the + ** sqlite3AddColumn() function and friends to modify. But modify + ** the name by adding an "sqlite_altertab_" prefix. By adding this + ** prefix, we insure that the name will not collide with an existing + ** table because user table are not allowed to have the "sqlite_" + ** prefix on their name. + */ + pNew = (Table*)sqlite3DbMallocZero(db, sizeof(Table)); + if( !pNew ) goto exit_begin_add_column; + pParse->pNewTable = pNew; + pNew->nTabRef = 1; + pNew->nCol = pTab->nCol; + assert( pNew->nCol>0 ); + nAlloc = (((pNew->nCol-1)/8)*8)+8; + assert( nAlloc>=pNew->nCol && nAlloc%8==0 && nAlloc-pNew->nCol<8 ); + pNew->aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column)*nAlloc); + pNew->zName = sqlite3MPrintf(db, "sqlite_altertab_%s", pTab->zName); + if( !pNew->aCol || !pNew->zName ){ + assert( db->mallocFailed ); + goto exit_begin_add_column; + } + memcpy(pNew->aCol, pTab->aCol, sizeof(Column)*pNew->nCol); + for(i=0; i<pNew->nCol; i++){ + Column *pCol = &pNew->aCol[i]; + pCol->zCnName = sqlite3DbStrDup(db, pCol->zCnName); + pCol->hName = sqlite3StrIHash(pCol->zCnName); + } + assert( IsOrdinaryTable(pNew) ); + pNew->u.tab.pDfltList = sqlite3ExprListDup(db, pTab->u.tab.pDfltList, 0); + pNew->pSchema = db->aDb[iDb].pSchema; + pNew->u.tab.addColOffset = pTab->u.tab.addColOffset; + assert( pNew->nTabRef==1 ); + +exit_begin_add_column: + sqlite3SrcListDelete(db, pSrc); + return; +} + +/* +** Parameter pTab is the subject of an ALTER TABLE ... RENAME COLUMN +** command. This function checks if the table is a view or virtual +** table (columns of views or virtual tables may not be renamed). If so, +** it loads an error message into pParse and returns non-zero. +** +** Or, if pTab is not a view or virtual table, zero is returned. +*/ +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) +static int isRealTable(Parse *pParse, Table *pTab, int bDrop){ + const char *zType = 0; +#ifndef SQLITE_OMIT_VIEW + if( IsView(pTab) ){ + zType = "view"; + } +#endif +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pTab) ){ + zType = "virtual table"; + } +#endif + if( zType ){ + sqlite3ErrorMsg(pParse, "cannot %s %s \"%s\"", + (bDrop ? "drop column from" : "rename columns of"), + zType, pTab->zName + ); + return 1; + } + return 0; +} +#else /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */ +# define isRealTable(x,y,z) (0) +#endif + +/* +** Handles the following parser reduction: +** +** cmd ::= ALTER TABLE pSrc RENAME COLUMN pOld TO pNew +*/ +SQLITE_PRIVATE void sqlite3AlterRenameColumn( + Parse *pParse, /* Parsing context */ + SrcList *pSrc, /* Table being altered. pSrc->nSrc==1 */ + Token *pOld, /* Name of column being changed */ + Token *pNew /* New column name */ +){ + sqlite3 *db = pParse->db; /* Database connection */ + Table *pTab; /* Table being updated */ + int iCol; /* Index of column being renamed */ + char *zOld = 0; /* Old column name */ + char *zNew = 0; /* New column name */ + const char *zDb; /* Name of schema containing the table */ + int iSchema; /* Index of the schema */ + int bQuote; /* True to quote the new name */ + + /* Locate the table to be altered */ + pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); + if( !pTab ) goto exit_rename_column; + + /* Cannot alter a system table */ + if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_rename_column; + if( SQLITE_OK!=isRealTable(pParse, pTab, 0) ) goto exit_rename_column; + + /* Which schema holds the table to be altered */ + iSchema = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iSchema>=0 ); + zDb = db->aDb[iSchema].zDbSName; + +#ifndef SQLITE_OMIT_AUTHORIZATION + /* Invoke the authorization callback. */ + if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){ + goto exit_rename_column; + } +#endif + + /* Make sure the old name really is a column name in the table to be + ** altered. Set iCol to be the index of the column being renamed */ + zOld = sqlite3NameFromToken(db, pOld); + if( !zOld ) goto exit_rename_column; + for(iCol=0; iCol<pTab->nCol; iCol++){ + if( 0==sqlite3StrICmp(pTab->aCol[iCol].zCnName, zOld) ) break; + } + if( iCol==pTab->nCol ){ + sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pOld); + goto exit_rename_column; + } + + /* Ensure the schema contains no double-quoted strings */ + renameTestSchema(pParse, zDb, iSchema==1, "", 0); + renameFixQuotes(pParse, zDb, iSchema==1); + + /* Do the rename operation using a recursive UPDATE statement that + ** uses the sqlite_rename_column() SQL function to compute the new + ** CREATE statement text for the sqlite_schema table. + */ + sqlite3MayAbort(pParse); + zNew = sqlite3NameFromToken(db, pNew); + if( !zNew ) goto exit_rename_column; + assert( pNew->n>0 ); + bQuote = sqlite3Isquote(pNew->z[0]); + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) " + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X' " + " AND (type != 'index' OR tbl_name = %Q)", + zDb, + zDb, pTab->zName, iCol, zNew, bQuote, iSchema==1, + pTab->zName + ); + + sqlite3NestedParse(pParse, + "UPDATE temp." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, 1) " + "WHERE type IN ('trigger', 'view')", + zDb, pTab->zName, iCol, zNew, bQuote + ); + + /* Drop and reload the database schema. */ + renameReloadSchema(pParse, iSchema, INITFLAG_AlterRename); + renameTestSchema(pParse, zDb, iSchema==1, "after rename", 1); + + exit_rename_column: + sqlite3SrcListDelete(db, pSrc); + sqlite3DbFree(db, zOld); + sqlite3DbFree(db, zNew); + return; +} + +/* +** Each RenameToken object maps an element of the parse tree into +** the token that generated that element. The parse tree element +** might be one of: +** +** * A pointer to an Expr that represents an ID +** * The name of a table column in Column.zName +** +** A list of RenameToken objects can be constructed during parsing. +** Each new object is created by sqlite3RenameTokenMap(). +** As the parse tree is transformed, the sqlite3RenameTokenRemap() +** routine is used to keep the mapping current. +** +** After the parse finishes, renameTokenFind() routine can be used +** to look up the actual token value that created some element in +** the parse tree. +*/ +struct RenameToken { + const void *p; /* Parse tree element created by token t */ + Token t; /* The token that created parse tree element p */ + RenameToken *pNext; /* Next is a list of all RenameToken objects */ +}; + +/* +** The context of an ALTER TABLE RENAME COLUMN operation that gets passed +** down into the Walker. +*/ +typedef struct RenameCtx RenameCtx; +struct RenameCtx { + RenameToken *pList; /* List of tokens to overwrite */ + int nList; /* Number of tokens in pList */ + int iCol; /* Index of column being renamed */ + Table *pTab; /* Table being ALTERed */ + const char *zOld; /* Old column name */ +}; + +#ifdef SQLITE_DEBUG +/* +** This function is only for debugging. It performs two tasks: +** +** 1. Checks that pointer pPtr does not already appear in the +** rename-token list. +** +** 2. Dereferences each pointer in the rename-token list. +** +** The second is most effective when debugging under valgrind or +** address-sanitizer or similar. If any of these pointers no longer +** point to valid objects, an exception is raised by the memory-checking +** tool. +** +** The point of this is to prevent comparisons of invalid pointer values. +** Even though this always seems to work, it is undefined according to the +** C standard. Example of undefined comparison: +** +** sqlite3_free(x); +** if( x==y ) ... +** +** Technically, as x no longer points into a valid object or to the byte +** following a valid object, it may not be used in comparison operations. +*/ +static void renameTokenCheckAll(Parse *pParse, const void *pPtr){ + assert( pParse==pParse->db->pParse ); + assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); + if( pParse->nErr==0 ){ + const RenameToken *p; + u32 i = 1; + for(p=pParse->pRename; p; p=p->pNext){ + if( p->p ){ + assert( p->p!=pPtr ); + i += *(u8*)(p->p) | 1; + } + } + assert( i>0 ); + } +} +#else +# define renameTokenCheckAll(x,y) +#endif + +/* +** Remember that the parser tree element pPtr was created using +** the token pToken. +** +** In other words, construct a new RenameToken object and add it +** to the list of RenameToken objects currently being built up +** in pParse->pRename. +** +** The pPtr argument is returned so that this routine can be used +** with tail recursion in tokenExpr() routine, for a small performance +** improvement. +*/ +SQLITE_PRIVATE const void *sqlite3RenameTokenMap( + Parse *pParse, + const void *pPtr, + const Token *pToken +){ + RenameToken *pNew; + assert( pPtr || pParse->db->mallocFailed ); + renameTokenCheckAll(pParse, pPtr); + if( ALWAYS(pParse->eParseMode!=PARSE_MODE_UNMAP) ){ + pNew = sqlite3DbMallocZero(pParse->db, sizeof(RenameToken)); + if( pNew ){ + pNew->p = pPtr; + pNew->t = *pToken; + pNew->pNext = pParse->pRename; + pParse->pRename = pNew; + } + } + + return pPtr; +} + +/* +** It is assumed that there is already a RenameToken object associated +** with parse tree element pFrom. This function remaps the associated token +** to parse tree element pTo. +*/ +SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, const void *pTo, const void *pFrom){ + RenameToken *p; + renameTokenCheckAll(pParse, pTo); + for(p=pParse->pRename; p; p=p->pNext){ + if( p->p==pFrom ){ + p->p = pTo; + break; + } + } +} + +/* +** Walker callback used by sqlite3RenameExprUnmap(). +*/ +static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){ + Parse *pParse = pWalker->pParse; + sqlite3RenameTokenRemap(pParse, 0, (const void*)pExpr); + if( ExprUseYTab(pExpr) ){ + sqlite3RenameTokenRemap(pParse, 0, (const void*)&pExpr->y.pTab); + } + return WRC_Continue; +} + +/* +** Iterate through the Select objects that are part of WITH clauses attached +** to select statement pSelect. +*/ +static void renameWalkWith(Walker *pWalker, Select *pSelect){ + With *pWith = pSelect->pWith; + if( pWith ){ + Parse *pParse = pWalker->pParse; + int i; + With *pCopy = 0; + assert( pWith->nCte>0 ); + if( (pWith->a[0].pSelect->selFlags & SF_Expanded)==0 ){ + /* Push a copy of the With object onto the with-stack. We use a copy + ** here as the original will be expanded and resolved (flags SF_Expanded + ** and SF_Resolved) below. And the parser code that uses the with-stack + ** fails if the Select objects on it have already been expanded and + ** resolved. */ + pCopy = sqlite3WithDup(pParse->db, pWith); + pCopy = sqlite3WithPush(pParse, pCopy, 1); + } + for(i=0; i<pWith->nCte; i++){ + Select *p = pWith->a[i].pSelect; + NameContext sNC; + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = pParse; + if( pCopy ) sqlite3SelectPrep(sNC.pParse, p, &sNC); + if( sNC.pParse->db->mallocFailed ) return; + sqlite3WalkSelect(pWalker, p); + sqlite3RenameExprlistUnmap(pParse, pWith->a[i].pCols); + } + if( pCopy && pParse->pWith==pCopy ){ + pParse->pWith = pCopy->pOuter; + } + } +} + +/* +** Unmap all tokens in the IdList object passed as the second argument. +*/ +static void unmapColumnIdlistNames( + Parse *pParse, + const IdList *pIdList +){ + int ii; + assert( pIdList!=0 ); + for(ii=0; ii<pIdList->nId; ii++){ + sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName); + } +} + +/* +** Walker callback used by sqlite3RenameExprUnmap(). +*/ +static int renameUnmapSelectCb(Walker *pWalker, Select *p){ + Parse *pParse = pWalker->pParse; + int i; + if( pParse->nErr ) return WRC_Abort; + testcase( p->selFlags & SF_View ); + testcase( p->selFlags & SF_CopyCte ); + if( p->selFlags & (SF_View|SF_CopyCte) ){ + return WRC_Prune; + } + if( ALWAYS(p->pEList) ){ + ExprList *pList = p->pEList; + for(i=0; i<pList->nExpr; i++){ + if( pList->a[i].zEName && pList->a[i].fg.eEName==ENAME_NAME ){ + sqlite3RenameTokenRemap(pParse, 0, (void*)pList->a[i].zEName); + } + } + } + if( ALWAYS(p->pSrc) ){ /* Every Select as a SrcList, even if it is empty */ + SrcList *pSrc = p->pSrc; + for(i=0; i<pSrc->nSrc; i++){ + sqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName); + if( pSrc->a[i].fg.isUsing==0 ){ + sqlite3WalkExpr(pWalker, pSrc->a[i].u3.pOn); + }else{ + unmapColumnIdlistNames(pParse, pSrc->a[i].u3.pUsing); + } + } + } + + renameWalkWith(pWalker, p); + return WRC_Continue; +} + +/* +** Remove all nodes that are part of expression pExpr from the rename list. +*/ +SQLITE_PRIVATE void sqlite3RenameExprUnmap(Parse *pParse, Expr *pExpr){ + u8 eMode = pParse->eParseMode; + Walker sWalker; + memset(&sWalker, 0, sizeof(Walker)); + sWalker.pParse = pParse; + sWalker.xExprCallback = renameUnmapExprCb; + sWalker.xSelectCallback = renameUnmapSelectCb; + pParse->eParseMode = PARSE_MODE_UNMAP; + sqlite3WalkExpr(&sWalker, pExpr); + pParse->eParseMode = eMode; +} + +/* +** Remove all nodes that are part of expression-list pEList from the +** rename list. +*/ +SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse *pParse, ExprList *pEList){ + if( pEList ){ + int i; + Walker sWalker; + memset(&sWalker, 0, sizeof(Walker)); + sWalker.pParse = pParse; + sWalker.xExprCallback = renameUnmapExprCb; + sqlite3WalkExprList(&sWalker, pEList); + for(i=0; i<pEList->nExpr; i++){ + if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) ){ + sqlite3RenameTokenRemap(pParse, 0, (void*)pEList->a[i].zEName); + } + } + } +} + +/* +** Free the list of RenameToken objects given in the second argument +*/ +static void renameTokenFree(sqlite3 *db, RenameToken *pToken){ + RenameToken *pNext; + RenameToken *p; + for(p=pToken; p; p=pNext){ + pNext = p->pNext; + sqlite3DbFree(db, p); + } +} + +/* +** Search the Parse object passed as the first argument for a RenameToken +** object associated with parse tree element pPtr. If found, return a pointer +** to it. Otherwise, return NULL. +** +** If the second argument passed to this function is not NULL and a matching +** RenameToken object is found, remove it from the Parse object and add it to +** the list maintained by the RenameCtx object. +*/ +static RenameToken *renameTokenFind( + Parse *pParse, + struct RenameCtx *pCtx, + const void *pPtr +){ + RenameToken **pp; + if( NEVER(pPtr==0) ){ + return 0; + } + for(pp=&pParse->pRename; (*pp); pp=&(*pp)->pNext){ + if( (*pp)->p==pPtr ){ + RenameToken *pToken = *pp; + if( pCtx ){ + *pp = pToken->pNext; + pToken->pNext = pCtx->pList; + pCtx->pList = pToken; + pCtx->nList++; + } + return pToken; + } + } + return 0; +} + +/* +** This is a Walker select callback. It does nothing. It is only required +** because without a dummy callback, sqlite3WalkExpr() and similar do not +** descend into sub-select statements. +*/ +static int renameColumnSelectCb(Walker *pWalker, Select *p){ + if( p->selFlags & (SF_View|SF_CopyCte) ){ + testcase( p->selFlags & SF_View ); + testcase( p->selFlags & SF_CopyCte ); + return WRC_Prune; + } + renameWalkWith(pWalker, p); + return WRC_Continue; +} + +/* +** This is a Walker expression callback. +** +** For every TK_COLUMN node in the expression tree, search to see +** if the column being references is the column being renamed by an +** ALTER TABLE statement. If it is, then attach its associated +** RenameToken object to the list of RenameToken objects being +** constructed in RenameCtx object at pWalker->u.pRename. +*/ +static int renameColumnExprCb(Walker *pWalker, Expr *pExpr){ + RenameCtx *p = pWalker->u.pRename; + if( pExpr->op==TK_TRIGGER + && pExpr->iColumn==p->iCol + && pWalker->pParse->pTriggerTab==p->pTab + ){ + renameTokenFind(pWalker->pParse, p, (void*)pExpr); + }else if( pExpr->op==TK_COLUMN + && pExpr->iColumn==p->iCol + && ALWAYS(ExprUseYTab(pExpr)) + && p->pTab==pExpr->y.pTab + ){ + renameTokenFind(pWalker->pParse, p, (void*)pExpr); + } + return WRC_Continue; +} + +/* +** The RenameCtx contains a list of tokens that reference a column that +** is being renamed by an ALTER TABLE statement. Return the "last" +** RenameToken in the RenameCtx and remove that RenameToken from the +** RenameContext. "Last" means the last RenameToken encountered when +** the input SQL is parsed from left to right. Repeated calls to this routine +** return all column name tokens in the order that they are encountered +** in the SQL statement. +*/ +static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){ + RenameToken *pBest = pCtx->pList; + RenameToken *pToken; + RenameToken **pp; + + for(pToken=pBest->pNext; pToken; pToken=pToken->pNext){ + if( pToken->t.z>pBest->t.z ) pBest = pToken; + } + for(pp=&pCtx->pList; *pp!=pBest; pp=&(*pp)->pNext); + *pp = pBest->pNext; + + return pBest; +} + +/* +** An error occurred while parsing or otherwise processing a database +** object (either pParse->pNewTable, pNewIndex or pNewTrigger) as part of an +** ALTER TABLE RENAME COLUMN program. The error message emitted by the +** sub-routine is currently stored in pParse->zErrMsg. This function +** adds context to the error message and then stores it in pCtx. +*/ +static void renameColumnParseError( + sqlite3_context *pCtx, + const char *zWhen, + sqlite3_value *pType, + sqlite3_value *pObject, + Parse *pParse +){ + const char *zT = (const char*)sqlite3_value_text(pType); + const char *zN = (const char*)sqlite3_value_text(pObject); + char *zErr; + + zErr = sqlite3MPrintf(pParse->db, "error in %s %s%s%s: %s", + zT, zN, (zWhen[0] ? " " : ""), zWhen, + pParse->zErrMsg + ); + sqlite3_result_error(pCtx, zErr, -1); + sqlite3DbFree(pParse->db, zErr); +} + +/* +** For each name in the the expression-list pEList (i.e. each +** pEList->a[i].zName) that matches the string in zOld, extract the +** corresponding rename-token from Parse object pParse and add it +** to the RenameCtx pCtx. +*/ +static void renameColumnElistNames( + Parse *pParse, + RenameCtx *pCtx, + const ExprList *pEList, + const char *zOld +){ + if( pEList ){ + int i; + for(i=0; i<pEList->nExpr; i++){ + const char *zName = pEList->a[i].zEName; + if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) + && ALWAYS(zName!=0) + && 0==sqlite3_stricmp(zName, zOld) + ){ + renameTokenFind(pParse, pCtx, (const void*)zName); + } + } + } +} + +/* +** For each name in the the id-list pIdList (i.e. each pIdList->a[i].zName) +** that matches the string in zOld, extract the corresponding rename-token +** from Parse object pParse and add it to the RenameCtx pCtx. +*/ +static void renameColumnIdlistNames( + Parse *pParse, + RenameCtx *pCtx, + const IdList *pIdList, + const char *zOld +){ + if( pIdList ){ + int i; + for(i=0; i<pIdList->nId; i++){ + const char *zName = pIdList->a[i].zName; + if( 0==sqlite3_stricmp(zName, zOld) ){ + renameTokenFind(pParse, pCtx, (const void*)zName); + } + } + } +} + + +/* +** Parse the SQL statement zSql using Parse object (*p). The Parse object +** is initialized by this function before it is used. +*/ +static int renameParseSql( + Parse *p, /* Memory to use for Parse object */ + const char *zDb, /* Name of schema SQL belongs to */ + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL to parse */ + int bTemp /* True if SQL is from temp schema */ +){ + int rc; + + sqlite3ParseObjectInit(p, db); + if( zSql==0 ){ + return SQLITE_NOMEM; + } + if( sqlite3StrNICmp(zSql,"CREATE ",7)!=0 ){ + return SQLITE_CORRUPT_BKPT; + } + db->init.iDb = bTemp ? 1 : sqlite3FindDbName(db, zDb); + p->eParseMode = PARSE_MODE_RENAME; + p->db = db; + p->nQueryLoop = 1; + rc = sqlite3RunParser(p, zSql); + if( db->mallocFailed ) rc = SQLITE_NOMEM; + if( rc==SQLITE_OK + && NEVER(p->pNewTable==0 && p->pNewIndex==0 && p->pNewTrigger==0) + ){ + rc = SQLITE_CORRUPT_BKPT; + } + +#ifdef SQLITE_DEBUG + /* Ensure that all mappings in the Parse.pRename list really do map to + ** a part of the input string. */ + if( rc==SQLITE_OK ){ + int nSql = sqlite3Strlen30(zSql); + RenameToken *pToken; + for(pToken=p->pRename; pToken; pToken=pToken->pNext){ + assert( pToken->t.z>=zSql && &pToken->t.z[pToken->t.n]<=&zSql[nSql] ); + } + } +#endif + + db->init.iDb = 0; + return rc; +} + +/* +** This function edits SQL statement zSql, replacing each token identified +** by the linked list pRename with the text of zNew. If argument bQuote is +** true, then zNew is always quoted first. If no error occurs, the result +** is loaded into context object pCtx as the result. +** +** Or, if an error occurs (i.e. an OOM condition), an error is left in +** pCtx and an SQLite error code returned. +*/ +static int renameEditSql( + sqlite3_context *pCtx, /* Return result here */ + RenameCtx *pRename, /* Rename context */ + const char *zSql, /* SQL statement to edit */ + const char *zNew, /* New token text */ + int bQuote /* True to always quote token */ +){ + i64 nNew = sqlite3Strlen30(zNew); + i64 nSql = sqlite3Strlen30(zSql); + sqlite3 *db = sqlite3_context_db_handle(pCtx); + int rc = SQLITE_OK; + char *zQuot = 0; + char *zOut; + i64 nQuot = 0; + char *zBuf1 = 0; + char *zBuf2 = 0; + + if( zNew ){ + /* Set zQuot to point to a buffer containing a quoted copy of the + ** identifier zNew. If the corresponding identifier in the original + ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to + ** point to zQuot so that all substitutions are made using the + ** quoted version of the new column name. */ + zQuot = sqlite3MPrintf(db, "\"%w\" ", zNew); + if( zQuot==0 ){ + return SQLITE_NOMEM; + }else{ + nQuot = sqlite3Strlen30(zQuot)-1; + } + + assert( nQuot>=nNew ); + zOut = sqlite3DbMallocZero(db, nSql + pRename->nList*nQuot + 1); + }else{ + zOut = (char*)sqlite3DbMallocZero(db, (nSql*2+1) * 3); + if( zOut ){ + zBuf1 = &zOut[nSql*2+1]; + zBuf2 = &zOut[nSql*4+2]; + } + } + + /* At this point pRename->pList contains a list of RenameToken objects + ** corresponding to all tokens in the input SQL that must be replaced + ** with the new column name, or with single-quoted versions of themselves. + ** All that remains is to construct and return the edited SQL string. */ + if( zOut ){ + int nOut = nSql; + memcpy(zOut, zSql, nSql); + while( pRename->pList ){ + int iOff; /* Offset of token to replace in zOut */ + u32 nReplace; + const char *zReplace; + RenameToken *pBest = renameColumnTokenNext(pRename); + + if( zNew ){ + if( bQuote==0 && sqlite3IsIdChar(*pBest->t.z) ){ + nReplace = nNew; + zReplace = zNew; + }else{ + nReplace = nQuot; + zReplace = zQuot; + if( pBest->t.z[pBest->t.n]=='"' ) nReplace++; + } + }else{ + /* Dequote the double-quoted token. Then requote it again, this time + ** using single quotes. If the character immediately following the + ** original token within the input SQL was a single quote ('), then + ** add another space after the new, single-quoted version of the + ** token. This is so that (SELECT "string"'alias') maps to + ** (SELECT 'string' 'alias'), and not (SELECT 'string''alias'). */ + memcpy(zBuf1, pBest->t.z, pBest->t.n); + zBuf1[pBest->t.n] = 0; + sqlite3Dequote(zBuf1); + sqlite3_snprintf(nSql*2, zBuf2, "%Q%s", zBuf1, + pBest->t.z[pBest->t.n]=='\'' ? " " : "" + ); + zReplace = zBuf2; + nReplace = sqlite3Strlen30(zReplace); + } + + iOff = pBest->t.z - zSql; + if( pBest->t.n!=nReplace ){ + memmove(&zOut[iOff + nReplace], &zOut[iOff + pBest->t.n], + nOut - (iOff + pBest->t.n) + ); + nOut += nReplace - pBest->t.n; + zOut[nOut] = '\0'; + } + memcpy(&zOut[iOff], zReplace, nReplace); + sqlite3DbFree(db, pBest); + } + + sqlite3_result_text(pCtx, zOut, -1, SQLITE_TRANSIENT); + sqlite3DbFree(db, zOut); + }else{ + rc = SQLITE_NOMEM; + } + + sqlite3_free(zQuot); + return rc; +} + +/* +** Set all pEList->a[].fg.eEName fields in the expression-list to val. +*/ +static void renameSetENames(ExprList *pEList, int val){ + if( pEList ){ + int i; + for(i=0; i<pEList->nExpr; i++){ + assert( val==ENAME_NAME || pEList->a[i].fg.eEName==ENAME_NAME ); + pEList->a[i].fg.eEName = val; + } + } +} + +/* +** Resolve all symbols in the trigger at pParse->pNewTrigger, assuming +** it was read from the schema of database zDb. Return SQLITE_OK if +** successful. Otherwise, return an SQLite error code and leave an error +** message in the Parse object. +*/ +static int renameResolveTrigger(Parse *pParse){ + sqlite3 *db = pParse->db; + Trigger *pNew = pParse->pNewTrigger; + TriggerStep *pStep; + NameContext sNC; + int rc = SQLITE_OK; + + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = pParse; + assert( pNew->pTabSchema ); + pParse->pTriggerTab = sqlite3FindTable(db, pNew->table, + db->aDb[sqlite3SchemaToIndex(db, pNew->pTabSchema)].zDbSName + ); + pParse->eTriggerOp = pNew->op; + /* ALWAYS() because if the table of the trigger does not exist, the + ** error would have been hit before this point */ + if( ALWAYS(pParse->pTriggerTab) ){ + rc = sqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab); + } + + /* Resolve symbols in WHEN clause */ + if( rc==SQLITE_OK && pNew->pWhen ){ + rc = sqlite3ResolveExprNames(&sNC, pNew->pWhen); + } + + for(pStep=pNew->step_list; rc==SQLITE_OK && pStep; pStep=pStep->pNext){ + if( pStep->pSelect ){ + sqlite3SelectPrep(pParse, pStep->pSelect, &sNC); + if( pParse->nErr ) rc = pParse->rc; + } + if( rc==SQLITE_OK && pStep->zTarget ){ + SrcList *pSrc = sqlite3TriggerStepSrc(pParse, pStep); + if( pSrc ){ + Select *pSel = sqlite3SelectNew( + pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0 + ); + if( pSel==0 ){ + pStep->pExprList = 0; + pSrc = 0; + rc = SQLITE_NOMEM; + }else{ + /* pStep->pExprList contains an expression-list used for an UPDATE + ** statement. So the a[].zEName values are the RHS of the + ** "<col> = <expr>" clauses of the UPDATE statement. So, before + ** running SelectPrep(), change all the eEName values in + ** pStep->pExprList to ENAME_SPAN (from their current value of + ** ENAME_NAME). This is to prevent any ids in ON() clauses that are + ** part of pSrc from being incorrectly resolved against the + ** a[].zEName values as if they were column aliases. */ + renameSetENames(pStep->pExprList, ENAME_SPAN); + sqlite3SelectPrep(pParse, pSel, 0); + renameSetENames(pStep->pExprList, ENAME_NAME); + rc = pParse->nErr ? SQLITE_ERROR : SQLITE_OK; + assert( pStep->pExprList==0 || pStep->pExprList==pSel->pEList ); + assert( pSrc==pSel->pSrc ); + if( pStep->pExprList ) pSel->pEList = 0; + pSel->pSrc = 0; + sqlite3SelectDelete(db, pSel); + } + if( pStep->pFrom ){ + int i; + for(i=0; i<pStep->pFrom->nSrc && rc==SQLITE_OK; i++){ + SrcItem *p = &pStep->pFrom->a[i]; + if( p->pSelect ){ + sqlite3SelectPrep(pParse, p->pSelect, 0); + } + } + } + + if( db->mallocFailed ){ + rc = SQLITE_NOMEM; + } + sNC.pSrcList = pSrc; + if( rc==SQLITE_OK && pStep->pWhere ){ + rc = sqlite3ResolveExprNames(&sNC, pStep->pWhere); + } + if( rc==SQLITE_OK ){ + rc = sqlite3ResolveExprListNames(&sNC, pStep->pExprList); + } + assert( !pStep->pUpsert || (!pStep->pWhere && !pStep->pExprList) ); + if( pStep->pUpsert && rc==SQLITE_OK ){ + Upsert *pUpsert = pStep->pUpsert; + pUpsert->pUpsertSrc = pSrc; + sNC.uNC.pUpsert = pUpsert; + sNC.ncFlags = NC_UUpsert; + rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); + if( rc==SQLITE_OK ){ + ExprList *pUpsertSet = pUpsert->pUpsertSet; + rc = sqlite3ResolveExprListNames(&sNC, pUpsertSet); + } + if( rc==SQLITE_OK ){ + rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertWhere); + } + if( rc==SQLITE_OK ){ + rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); + } + sNC.ncFlags = 0; + } + sNC.pSrcList = 0; + sqlite3SrcListDelete(db, pSrc); + }else{ + rc = SQLITE_NOMEM; + } + } + } + return rc; +} + +/* +** Invoke sqlite3WalkExpr() or sqlite3WalkSelect() on all Select or Expr +** objects that are part of the trigger passed as the second argument. +*/ +static void renameWalkTrigger(Walker *pWalker, Trigger *pTrigger){ + TriggerStep *pStep; + + /* Find tokens to edit in WHEN clause */ + sqlite3WalkExpr(pWalker, pTrigger->pWhen); + + /* Find tokens to edit in trigger steps */ + for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){ + sqlite3WalkSelect(pWalker, pStep->pSelect); + sqlite3WalkExpr(pWalker, pStep->pWhere); + sqlite3WalkExprList(pWalker, pStep->pExprList); + if( pStep->pUpsert ){ + Upsert *pUpsert = pStep->pUpsert; + sqlite3WalkExprList(pWalker, pUpsert->pUpsertTarget); + sqlite3WalkExprList(pWalker, pUpsert->pUpsertSet); + sqlite3WalkExpr(pWalker, pUpsert->pUpsertWhere); + sqlite3WalkExpr(pWalker, pUpsert->pUpsertTargetWhere); + } + if( pStep->pFrom ){ + int i; + for(i=0; i<pStep->pFrom->nSrc; i++){ + sqlite3WalkSelect(pWalker, pStep->pFrom->a[i].pSelect); + } + } + } +} + +/* +** Free the contents of Parse object (*pParse). Do not free the memory +** occupied by the Parse object itself. +*/ +static void renameParseCleanup(Parse *pParse){ + sqlite3 *db = pParse->db; + Index *pIdx; + if( pParse->pVdbe ){ + sqlite3VdbeFinalize(pParse->pVdbe); + } + sqlite3DeleteTable(db, pParse->pNewTable); + while( (pIdx = pParse->pNewIndex)!=0 ){ + pParse->pNewIndex = pIdx->pNext; + sqlite3FreeIndex(db, pIdx); + } + sqlite3DeleteTrigger(db, pParse->pNewTrigger); + sqlite3DbFree(db, pParse->zErrMsg); + renameTokenFree(db, pParse->pRename); + sqlite3ParseObjectReset(pParse); +} + +/* +** SQL function: +** +** sqlite_rename_column(SQL,TYPE,OBJ,DB,TABLE,COL,NEWNAME,QUOTE,TEMP) +** +** 0. zSql: SQL statement to rewrite +** 1. type: Type of object ("table", "view" etc.) +** 2. object: Name of object +** 3. Database: Database name (e.g. "main") +** 4. Table: Table name +** 5. iCol: Index of column to rename +** 6. zNew: New column name +** 7. bQuote: Non-zero if the new column name should be quoted. +** 8. bTemp: True if zSql comes from temp schema +** +** Do a column rename operation on the CREATE statement given in zSql. +** The iCol-th column (left-most is 0) of table zTable is renamed from zCol +** into zNew. The name should be quoted if bQuote is true. +** +** This function is used internally by the ALTER TABLE RENAME COLUMN command. +** It is only accessible to SQL created using sqlite3NestedParse(). It is +** not reachable from ordinary SQL passed into sqlite3_prepare() unless the +** SQLITE_TESTCTRL_INTERNAL_FUNCTIONS test setting is enabled. +*/ +static void renameColumnFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + RenameCtx sCtx; + const char *zSql = (const char*)sqlite3_value_text(argv[0]); + const char *zDb = (const char*)sqlite3_value_text(argv[3]); + const char *zTable = (const char*)sqlite3_value_text(argv[4]); + int iCol = sqlite3_value_int(argv[5]); + const char *zNew = (const char*)sqlite3_value_text(argv[6]); + int bQuote = sqlite3_value_int(argv[7]); + int bTemp = sqlite3_value_int(argv[8]); + const char *zOld; + int rc; + Parse sParse; + Walker sWalker; + Index *pIdx; + int i; + Table *pTab; +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; +#endif + + UNUSED_PARAMETER(NotUsed); + if( zSql==0 ) return; + if( zTable==0 ) return; + if( zNew==0 ) return; + if( iCol<0 ) return; + sqlite3BtreeEnterAll(db); + pTab = sqlite3FindTable(db, zTable, zDb); + if( pTab==0 || iCol>=pTab->nCol ){ + sqlite3BtreeLeaveAll(db); + return; + } + zOld = pTab->aCol[iCol].zCnName; + memset(&sCtx, 0, sizeof(sCtx)); + sCtx.iCol = ((iCol==pTab->iPKey) ? -1 : iCol); + +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = 0; +#endif + rc = renameParseSql(&sParse, zDb, db, zSql, bTemp); + + /* Find tokens that need to be replaced. */ + memset(&sWalker, 0, sizeof(Walker)); + sWalker.pParse = &sParse; + sWalker.xExprCallback = renameColumnExprCb; + sWalker.xSelectCallback = renameColumnSelectCb; + sWalker.u.pRename = &sCtx; + + sCtx.pTab = pTab; + if( rc!=SQLITE_OK ) goto renameColumnFunc_done; + if( sParse.pNewTable ){ + if( IsView(sParse.pNewTable) ){ + Select *pSelect = sParse.pNewTable->u.view.pSelect; + pSelect->selFlags &= ~SF_View; + sParse.rc = SQLITE_OK; + sqlite3SelectPrep(&sParse, pSelect, 0); + rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc); + if( rc==SQLITE_OK ){ + sqlite3WalkSelect(&sWalker, pSelect); + } + if( rc!=SQLITE_OK ) goto renameColumnFunc_done; + }else if( IsOrdinaryTable(sParse.pNewTable) ){ + /* A regular table */ + int bFKOnly = sqlite3_stricmp(zTable, sParse.pNewTable->zName); + FKey *pFKey; + sCtx.pTab = sParse.pNewTable; + if( bFKOnly==0 ){ + if( iCol<sParse.pNewTable->nCol ){ + renameTokenFind( + &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zCnName + ); + } + if( sCtx.iCol<0 ){ + renameTokenFind(&sParse, &sCtx, (void*)&sParse.pNewTable->iPKey); + } + sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck); + for(pIdx=sParse.pNewTable->pIndex; pIdx; pIdx=pIdx->pNext){ + sqlite3WalkExprList(&sWalker, pIdx->aColExpr); + } + for(pIdx=sParse.pNewIndex; pIdx; pIdx=pIdx->pNext){ + sqlite3WalkExprList(&sWalker, pIdx->aColExpr); + } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + for(i=0; i<sParse.pNewTable->nCol; i++){ + Expr *pExpr = sqlite3ColumnExpr(sParse.pNewTable, + &sParse.pNewTable->aCol[i]); + sqlite3WalkExpr(&sWalker, pExpr); + } +#endif + } + + assert( IsOrdinaryTable(sParse.pNewTable) ); + for(pFKey=sParse.pNewTable->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ + for(i=0; i<pFKey->nCol; i++){ + if( bFKOnly==0 && pFKey->aCol[i].iFrom==iCol ){ + renameTokenFind(&sParse, &sCtx, (void*)&pFKey->aCol[i]); + } + if( 0==sqlite3_stricmp(pFKey->zTo, zTable) + && 0==sqlite3_stricmp(pFKey->aCol[i].zCol, zOld) + ){ + renameTokenFind(&sParse, &sCtx, (void*)pFKey->aCol[i].zCol); + } + } + } + } + }else if( sParse.pNewIndex ){ + sqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr); + sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); + }else{ + /* A trigger */ + TriggerStep *pStep; + rc = renameResolveTrigger(&sParse); + if( rc!=SQLITE_OK ) goto renameColumnFunc_done; + + for(pStep=sParse.pNewTrigger->step_list; pStep; pStep=pStep->pNext){ + if( pStep->zTarget ){ + Table *pTarget = sqlite3LocateTable(&sParse, 0, pStep->zTarget, zDb); + if( pTarget==pTab ){ + if( pStep->pUpsert ){ + ExprList *pUpsertSet = pStep->pUpsert->pUpsertSet; + renameColumnElistNames(&sParse, &sCtx, pUpsertSet, zOld); + } + renameColumnIdlistNames(&sParse, &sCtx, pStep->pIdList, zOld); + renameColumnElistNames(&sParse, &sCtx, pStep->pExprList, zOld); + } + } + } + + + /* Find tokens to edit in UPDATE OF clause */ + if( sParse.pTriggerTab==pTab ){ + renameColumnIdlistNames(&sParse, &sCtx,sParse.pNewTrigger->pColumns,zOld); + } + + /* Find tokens to edit in various expressions and selects */ + renameWalkTrigger(&sWalker, sParse.pNewTrigger); + } + + assert( rc==SQLITE_OK ); + rc = renameEditSql(context, &sCtx, zSql, zNew, bQuote); + +renameColumnFunc_done: + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_ERROR && sqlite3WritableSchema(db) ){ + sqlite3_result_value(context, argv[0]); + }else if( sParse.zErrMsg ){ + renameColumnParseError(context, "", argv[1], argv[2], &sParse); + }else{ + sqlite3_result_error_code(context, rc); + } + } + + renameParseCleanup(&sParse); + renameTokenFree(db, sCtx.pList); +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + sqlite3BtreeLeaveAll(db); +} + +/* +** Walker expression callback used by "RENAME TABLE". +*/ +static int renameTableExprCb(Walker *pWalker, Expr *pExpr){ + RenameCtx *p = pWalker->u.pRename; + if( pExpr->op==TK_COLUMN + && ALWAYS(ExprUseYTab(pExpr)) + && p->pTab==pExpr->y.pTab + ){ + renameTokenFind(pWalker->pParse, p, (void*)&pExpr->y.pTab); + } + return WRC_Continue; +} + +/* +** Walker select callback used by "RENAME TABLE". +*/ +static int renameTableSelectCb(Walker *pWalker, Select *pSelect){ + int i; + RenameCtx *p = pWalker->u.pRename; + SrcList *pSrc = pSelect->pSrc; + if( pSelect->selFlags & (SF_View|SF_CopyCte) ){ + testcase( pSelect->selFlags & SF_View ); + testcase( pSelect->selFlags & SF_CopyCte ); + return WRC_Prune; + } + if( NEVER(pSrc==0) ){ + assert( pWalker->pParse->db->mallocFailed ); + return WRC_Abort; + } + for(i=0; i<pSrc->nSrc; i++){ + SrcItem *pItem = &pSrc->a[i]; + if( pItem->pTab==p->pTab ){ + renameTokenFind(pWalker->pParse, p, pItem->zName); + } + } + renameWalkWith(pWalker, pSelect); + + return WRC_Continue; +} + + +/* +** This C function implements an SQL user function that is used by SQL code +** generated by the ALTER TABLE ... RENAME command to modify the definition +** of any foreign key constraints that use the table being renamed as the +** parent table. It is passed three arguments: +** +** 0: The database containing the table being renamed. +** 1. type: Type of object ("table", "view" etc.) +** 2. object: Name of object +** 3: The complete text of the schema statement being modified, +** 4: The old name of the table being renamed, and +** 5: The new name of the table being renamed. +** 6: True if the schema statement comes from the temp db. +** +** It returns the new schema statement. For example: +** +** sqlite_rename_table('main', 'CREATE TABLE t1(a REFERENCES t2)','t2','t3',0) +** -> 'CREATE TABLE t1(a REFERENCES t3)' +*/ +static void renameTableFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + const char *zDb = (const char*)sqlite3_value_text(argv[0]); + const char *zInput = (const char*)sqlite3_value_text(argv[3]); + const char *zOld = (const char*)sqlite3_value_text(argv[4]); + const char *zNew = (const char*)sqlite3_value_text(argv[5]); + int bTemp = sqlite3_value_int(argv[6]); + UNUSED_PARAMETER(NotUsed); + + if( zInput && zOld && zNew ){ + Parse sParse; + int rc; + int bQuote = 1; + RenameCtx sCtx; + Walker sWalker; + +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; + db->xAuth = 0; +#endif + + sqlite3BtreeEnterAll(db); + + memset(&sCtx, 0, sizeof(RenameCtx)); + sCtx.pTab = sqlite3FindTable(db, zOld, zDb); + memset(&sWalker, 0, sizeof(Walker)); + sWalker.pParse = &sParse; + sWalker.xExprCallback = renameTableExprCb; + sWalker.xSelectCallback = renameTableSelectCb; + sWalker.u.pRename = &sCtx; + + rc = renameParseSql(&sParse, zDb, db, zInput, bTemp); + + if( rc==SQLITE_OK ){ + int isLegacy = (db->flags & SQLITE_LegacyAlter); + if( sParse.pNewTable ){ + Table *pTab = sParse.pNewTable; + + if( IsView(pTab) ){ + if( isLegacy==0 ){ + Select *pSelect = pTab->u.view.pSelect; + NameContext sNC; + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = &sParse; + + assert( pSelect->selFlags & SF_View ); + pSelect->selFlags &= ~SF_View; + sqlite3SelectPrep(&sParse, pTab->u.view.pSelect, &sNC); + if( sParse.nErr ){ + rc = sParse.rc; + }else{ + sqlite3WalkSelect(&sWalker, pTab->u.view.pSelect); + } + } + }else{ + /* Modify any FK definitions to point to the new table. */ +#ifndef SQLITE_OMIT_FOREIGN_KEY + if( (isLegacy==0 || (db->flags & SQLITE_ForeignKeys)) + && !IsVirtual(pTab) + ){ + FKey *pFKey; + assert( IsOrdinaryTable(pTab) ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ + if( sqlite3_stricmp(pFKey->zTo, zOld)==0 ){ + renameTokenFind(&sParse, &sCtx, (void*)pFKey->zTo); + } + } + } +#endif + + /* If this is the table being altered, fix any table refs in CHECK + ** expressions. Also update the name that appears right after the + ** "CREATE [VIRTUAL] TABLE" bit. */ + if( sqlite3_stricmp(zOld, pTab->zName)==0 ){ + sCtx.pTab = pTab; + if( isLegacy==0 ){ + sqlite3WalkExprList(&sWalker, pTab->pCheck); + } + renameTokenFind(&sParse, &sCtx, pTab->zName); + } + } + } + + else if( sParse.pNewIndex ){ + renameTokenFind(&sParse, &sCtx, sParse.pNewIndex->zName); + if( isLegacy==0 ){ + sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); + } + } + +#ifndef SQLITE_OMIT_TRIGGER + else{ + Trigger *pTrigger = sParse.pNewTrigger; + TriggerStep *pStep; + if( 0==sqlite3_stricmp(sParse.pNewTrigger->table, zOld) + && sCtx.pTab->pSchema==pTrigger->pTabSchema + ){ + renameTokenFind(&sParse, &sCtx, sParse.pNewTrigger->table); + } + + if( isLegacy==0 ){ + rc = renameResolveTrigger(&sParse); + if( rc==SQLITE_OK ){ + renameWalkTrigger(&sWalker, pTrigger); + for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){ + if( pStep->zTarget && 0==sqlite3_stricmp(pStep->zTarget, zOld) ){ + renameTokenFind(&sParse, &sCtx, pStep->zTarget); + } + if( pStep->pFrom ){ + int i; + for(i=0; i<pStep->pFrom->nSrc; i++){ + SrcItem *pItem = &pStep->pFrom->a[i]; + if( 0==sqlite3_stricmp(pItem->zName, zOld) ){ + renameTokenFind(&sParse, &sCtx, pItem->zName); + } + } + } + } + } + } + } +#endif + } + + if( rc==SQLITE_OK ){ + rc = renameEditSql(context, &sCtx, zInput, zNew, bQuote); + } + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_ERROR && sqlite3WritableSchema(db) ){ + sqlite3_result_value(context, argv[3]); + }else if( sParse.zErrMsg ){ + renameColumnParseError(context, "", argv[1], argv[2], &sParse); + }else{ + sqlite3_result_error_code(context, rc); + } + } + + renameParseCleanup(&sParse); + renameTokenFree(db, sCtx.pList); + sqlite3BtreeLeaveAll(db); +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + } + + return; +} + +static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_STRING && (pExpr->flags & EP_DblQuoted) ){ + renameTokenFind(pWalker->pParse, pWalker->u.pRename, (const void*)pExpr); + } + return WRC_Continue; +} + +/* SQL function: sqlite_rename_quotefix(DB,SQL) +** +** Rewrite the DDL statement "SQL" so that any string literals that use +** double-quotes use single quotes instead. +** +** Two arguments must be passed: +** +** 0: Database name ("main", "temp" etc.). +** 1: SQL statement to edit. +** +** The returned value is the modified SQL statement. For example, given +** the database schema: +** +** CREATE TABLE t1(a, b, c); +** +** SELECT sqlite_rename_quotefix('main', +** 'CREATE VIEW v1 AS SELECT "a", "string" FROM t1' +** ); +** +** returns the string: +** +** CREATE VIEW v1 AS SELECT "a", 'string' FROM t1 +** +** If there is a error in the input SQL, then raise an error, except +** if PRAGMA writable_schema=ON, then just return the input string +** unmodified following an error. +*/ +static void renameQuotefixFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + char const *zDb = (const char*)sqlite3_value_text(argv[0]); + char const *zInput = (const char*)sqlite3_value_text(argv[1]); + +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; + db->xAuth = 0; +#endif + + sqlite3BtreeEnterAll(db); + + UNUSED_PARAMETER(NotUsed); + if( zDb && zInput ){ + int rc; + Parse sParse; + rc = renameParseSql(&sParse, zDb, db, zInput, 0); + + if( rc==SQLITE_OK ){ + RenameCtx sCtx; + Walker sWalker; + + /* Walker to find tokens that need to be replaced. */ + memset(&sCtx, 0, sizeof(RenameCtx)); + memset(&sWalker, 0, sizeof(Walker)); + sWalker.pParse = &sParse; + sWalker.xExprCallback = renameQuotefixExprCb; + sWalker.xSelectCallback = renameColumnSelectCb; + sWalker.u.pRename = &sCtx; + + if( sParse.pNewTable ){ + if( IsView(sParse.pNewTable) ){ + Select *pSelect = sParse.pNewTable->u.view.pSelect; + pSelect->selFlags &= ~SF_View; + sParse.rc = SQLITE_OK; + sqlite3SelectPrep(&sParse, pSelect, 0); + rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc); + if( rc==SQLITE_OK ){ + sqlite3WalkSelect(&sWalker, pSelect); + } + }else{ + int i; + sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck); +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + for(i=0; i<sParse.pNewTable->nCol; i++){ + sqlite3WalkExpr(&sWalker, + sqlite3ColumnExpr(sParse.pNewTable, + &sParse.pNewTable->aCol[i])); + } +#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ + } + }else if( sParse.pNewIndex ){ + sqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr); + sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); + }else{ +#ifndef SQLITE_OMIT_TRIGGER + rc = renameResolveTrigger(&sParse); + if( rc==SQLITE_OK ){ + renameWalkTrigger(&sWalker, sParse.pNewTrigger); + } +#endif /* SQLITE_OMIT_TRIGGER */ + } + + if( rc==SQLITE_OK ){ + rc = renameEditSql(context, &sCtx, zInput, 0, 0); + } + renameTokenFree(db, sCtx.pList); + } + if( rc!=SQLITE_OK ){ + if( sqlite3WritableSchema(db) && rc==SQLITE_ERROR ){ + sqlite3_result_value(context, argv[1]); + }else{ + sqlite3_result_error_code(context, rc); + } + } + renameParseCleanup(&sParse); + } + +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + + sqlite3BtreeLeaveAll(db); +} + +/* Function: sqlite_rename_test(DB,SQL,TYPE,NAME,ISTEMP,WHEN,DQS) +** +** An SQL user function that checks that there are no parse or symbol +** resolution problems in a CREATE TRIGGER|TABLE|VIEW|INDEX statement. +** After an ALTER TABLE .. RENAME operation is performed and the schema +** reloaded, this function is called on each SQL statement in the schema +** to ensure that it is still usable. +** +** 0: Database name ("main", "temp" etc.). +** 1: SQL statement. +** 2: Object type ("view", "table", "trigger" or "index"). +** 3: Object name. +** 4: True if object is from temp schema. +** 5: "when" part of error message. +** 6: True to disable the DQS quirk when parsing SQL. +** +** The return value is computed as follows: +** +** A. If an error is seen and not in PRAGMA writable_schema=ON mode, +** then raise the error. +** B. Else if a trigger is created and the the table that the trigger is +** attached to is in database zDb, then return 1. +** C. Otherwise return NULL. +*/ +static void renameTableTest( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + char const *zDb = (const char*)sqlite3_value_text(argv[0]); + char const *zInput = (const char*)sqlite3_value_text(argv[1]); + int bTemp = sqlite3_value_int(argv[4]); + int isLegacy = (db->flags & SQLITE_LegacyAlter); + char const *zWhen = (const char*)sqlite3_value_text(argv[5]); + int bNoDQS = sqlite3_value_int(argv[6]); + +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; + db->xAuth = 0; +#endif + + UNUSED_PARAMETER(NotUsed); + + if( zDb && zInput ){ + int rc; + Parse sParse; + int flags = db->flags; + if( bNoDQS ) db->flags &= ~(SQLITE_DqsDML|SQLITE_DqsDDL); + rc = renameParseSql(&sParse, zDb, db, zInput, bTemp); + db->flags |= (flags & (SQLITE_DqsDML|SQLITE_DqsDDL)); + if( rc==SQLITE_OK ){ + if( isLegacy==0 && sParse.pNewTable && IsView(sParse.pNewTable) ){ + NameContext sNC; + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = &sParse; + sqlite3SelectPrep(&sParse, sParse.pNewTable->u.view.pSelect, &sNC); + if( sParse.nErr ) rc = sParse.rc; + } + + else if( sParse.pNewTrigger ){ + if( isLegacy==0 ){ + rc = renameResolveTrigger(&sParse); + } + if( rc==SQLITE_OK ){ + int i1 = sqlite3SchemaToIndex(db, sParse.pNewTrigger->pTabSchema); + int i2 = sqlite3FindDbName(db, zDb); + if( i1==i2 ){ + /* Handle output case B */ + sqlite3_result_int(context, 1); + } + } + } + } + + if( rc!=SQLITE_OK && zWhen && !sqlite3WritableSchema(db) ){ + /* Output case A */ + renameColumnParseError(context, zWhen, argv[2], argv[3],&sParse); + } + renameParseCleanup(&sParse); + } + +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif +} + +/* +** The implementation of internal UDF sqlite_drop_column(). +** +** Arguments: +** +** argv[0]: An integer - the index of the schema containing the table +** argv[1]: CREATE TABLE statement to modify. +** argv[2]: An integer - the index of the column to remove. +** +** The value returned is a string containing the CREATE TABLE statement +** with column argv[2] removed. +*/ +static void dropColumnFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + int iSchema = sqlite3_value_int(argv[0]); + const char *zSql = (const char*)sqlite3_value_text(argv[1]); + int iCol = sqlite3_value_int(argv[2]); + const char *zDb = db->aDb[iSchema].zDbSName; + int rc; + Parse sParse; + RenameToken *pCol; + Table *pTab; + const char *zEnd; + char *zNew = 0; + +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; + db->xAuth = 0; +#endif + + UNUSED_PARAMETER(NotUsed); + rc = renameParseSql(&sParse, zDb, db, zSql, iSchema==1); + if( rc!=SQLITE_OK ) goto drop_column_done; + pTab = sParse.pNewTable; + if( pTab==0 || pTab->nCol==1 || iCol>=pTab->nCol ){ + /* This can happen if the sqlite_schema table is corrupt */ + rc = SQLITE_CORRUPT_BKPT; + goto drop_column_done; + } + + pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); + if( iCol<pTab->nCol-1 ){ + RenameToken *pEnd; + pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zCnName); + zEnd = (const char*)pEnd->t.z; + }else{ + assert( IsOrdinaryTable(pTab) ); + zEnd = (const char*)&zSql[pTab->u.tab.addColOffset]; + while( ALWAYS(pCol->t.z[0]!=0) && pCol->t.z[0]!=',' ) pCol->t.z--; + } + + zNew = sqlite3MPrintf(db, "%.*s%s", pCol->t.z-zSql, zSql, zEnd); + sqlite3_result_text(context, zNew, -1, SQLITE_TRANSIENT); + sqlite3_free(zNew); + +drop_column_done: + renameParseCleanup(&sParse); +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(context, rc); + } +} + +/* +** This function is called by the parser upon parsing an +** +** ALTER TABLE pSrc DROP COLUMN pName +** +** statement. Argument pSrc contains the possibly qualified name of the +** table being edited, and token pName the name of the column to drop. +*/ +SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ + sqlite3 *db = pParse->db; /* Database handle */ + Table *pTab; /* Table to modify */ + int iDb; /* Index of db containing pTab in aDb[] */ + const char *zDb; /* Database containing pTab ("main" etc.) */ + char *zCol = 0; /* Name of column to drop */ + int iCol; /* Index of column zCol in pTab->aCol[] */ + + /* Look up the table being altered. */ + assert( pParse->pNewTable==0 ); + assert( sqlite3BtreeHoldsAllMutexes(db) ); + if( NEVER(db->mallocFailed) ) goto exit_drop_column; + pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); + if( !pTab ) goto exit_drop_column; + + /* Make sure this is not an attempt to ALTER a view, virtual table or + ** system table. */ + if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_drop_column; + if( SQLITE_OK!=isRealTable(pParse, pTab, 1) ) goto exit_drop_column; + + /* Find the index of the column being dropped. */ + zCol = sqlite3NameFromToken(db, pName); + if( zCol==0 ){ + assert( db->mallocFailed ); + goto exit_drop_column; + } + iCol = sqlite3ColumnIndex(pTab, zCol); + if( iCol<0 ){ + sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pName); + goto exit_drop_column; + } + + /* Do not allow the user to drop a PRIMARY KEY column or a column + ** constrained by a UNIQUE constraint. */ + if( pTab->aCol[iCol].colFlags & (COLFLAG_PRIMKEY|COLFLAG_UNIQUE) ){ + sqlite3ErrorMsg(pParse, "cannot drop %s column: \"%s\"", + (pTab->aCol[iCol].colFlags&COLFLAG_PRIMKEY) ? "PRIMARY KEY" : "UNIQUE", + zCol + ); + goto exit_drop_column; + } + + /* Do not allow the number of columns to go to zero */ + if( pTab->nCol<=1 ){ + sqlite3ErrorMsg(pParse, "cannot drop column \"%s\": no other columns exist",zCol); + goto exit_drop_column; + } + + /* Edit the sqlite_schema table */ + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb>=0 ); + zDb = db->aDb[iDb].zDbSName; +#ifndef SQLITE_OMIT_AUTHORIZATION + /* Invoke the authorization callback. */ + if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, zCol) ){ + goto exit_drop_column; + } +#endif + renameTestSchema(pParse, zDb, iDb==1, "", 0); + renameFixQuotes(pParse, zDb, iDb==1); + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_drop_column(%d, sql, %d) " + "WHERE (type=='table' AND tbl_name=%Q COLLATE nocase)" + , zDb, iDb, iCol, pTab->zName + ); + + /* Drop and reload the database schema. */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterDrop); + renameTestSchema(pParse, zDb, iDb==1, "after drop column", 1); + + /* Edit rows of table on disk */ + if( pParse->nErr==0 && (pTab->aCol[iCol].colFlags & COLFLAG_VIRTUAL)==0 ){ + int i; + int addr; + int reg; + int regRec; + Index *pPk = 0; + int nField = 0; /* Number of non-virtual columns after drop */ + int iCur; + Vdbe *v = sqlite3GetVdbe(pParse); + iCur = pParse->nTab++; + sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenWrite); + addr = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); + reg = ++pParse->nMem; + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_Rowid, iCur, reg); + pParse->nMem += pTab->nCol; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + pParse->nMem += pPk->nColumn; + for(i=0; i<pPk->nKeyCol; i++){ + sqlite3VdbeAddOp3(v, OP_Column, iCur, i, reg+i+1); + } + nField = pPk->nKeyCol; + } + regRec = ++pParse->nMem; + for(i=0; i<pTab->nCol; i++){ + if( i!=iCol && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){ + int regOut; + if( pPk ){ + int iPos = sqlite3TableColumnToIndex(pPk, i); + int iColPos = sqlite3TableColumnToIndex(pPk, iCol); + if( iPos<pPk->nKeyCol ) continue; + regOut = reg+1+iPos-(iPos>iColPos); + }else{ + regOut = reg+1+nField; + } + if( i==pTab->iPKey ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regOut); + }else{ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOut); + } + nField++; + } + } + if( nField==0 ){ + /* dbsqlfuzz 5f09e7bcc78b4954d06bf9f2400d7715f48d1fef */ + pParse->nMem++; + sqlite3VdbeAddOp2(v, OP_Null, 0, reg+1); + nField = 1; + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, reg+1, nField, regRec); + if( pPk ){ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iCur, regRec, reg+1, pPk->nKeyCol); + }else{ + sqlite3VdbeAddOp3(v, OP_Insert, iCur, regRec, reg); + } + sqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION); + + sqlite3VdbeAddOp2(v, OP_Next, iCur, addr+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addr); + } + +exit_drop_column: + sqlite3DbFree(db, zCol); + sqlite3SrcListDelete(db, pSrc); +} + +/* +** Register built-in functions used to help implement ALTER TABLE +*/ +SQLITE_PRIVATE void sqlite3AlterFunctions(void){ + static FuncDef aAlterTableFuncs[] = { + INTERNAL_FUNCTION(sqlite_rename_column, 9, renameColumnFunc), + INTERNAL_FUNCTION(sqlite_rename_table, 7, renameTableFunc), + INTERNAL_FUNCTION(sqlite_rename_test, 7, renameTableTest), + INTERNAL_FUNCTION(sqlite_drop_column, 3, dropColumnFunc), + INTERNAL_FUNCTION(sqlite_rename_quotefix,2, renameQuotefixFunc), + }; + sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs)); +} +#endif /* SQLITE_ALTER_TABLE */ + +/************** End of alter.c ***********************************************/ +/************** Begin file analyze.c *****************************************/ +/* +** 2005-07-08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code associated with the ANALYZE command. +** +** The ANALYZE command gather statistics about the content of tables +** and indices. These statistics are made available to the query planner +** to help it make better decisions about how to perform queries. +** +** The following system tables are or have been supported: +** +** CREATE TABLE sqlite_stat1(tbl, idx, stat); +** CREATE TABLE sqlite_stat2(tbl, idx, sampleno, sample); +** CREATE TABLE sqlite_stat3(tbl, idx, nEq, nLt, nDLt, sample); +** CREATE TABLE sqlite_stat4(tbl, idx, nEq, nLt, nDLt, sample); +** +** Additional tables might be added in future releases of SQLite. +** The sqlite_stat2 table is not created or used unless the SQLite version +** is between 3.6.18 and 3.7.8, inclusive, and unless SQLite is compiled +** with SQLITE_ENABLE_STAT2. The sqlite_stat2 table is deprecated. +** The sqlite_stat2 table is superseded by sqlite_stat3, which is only +** created and used by SQLite versions 3.7.9 through 3.29.0 when +** SQLITE_ENABLE_STAT3 defined. The functionality of sqlite_stat3 +** is a superset of sqlite_stat2 and is also now deprecated. The +** sqlite_stat4 is an enhanced version of sqlite_stat3 and is only +** available when compiled with SQLITE_ENABLE_STAT4 and in SQLite +** versions 3.8.1 and later. STAT4 is the only variant that is still +** supported. +** +** For most applications, sqlite_stat1 provides all the statistics required +** for the query planner to make good choices. +** +** Format of sqlite_stat1: +** +** There is normally one row per index, with the index identified by the +** name in the idx column. The tbl column is the name of the table to +** which the index belongs. In each such row, the stat column will be +** a string consisting of a list of integers. The first integer in this +** list is the number of rows in the index. (This is the same as the +** number of rows in the table, except for partial indices.) The second +** integer is the average number of rows in the index that have the same +** value in the first column of the index. The third integer is the average +** number of rows in the index that have the same value for the first two +** columns. The N-th integer (for N>1) is the average number of rows in +** the index which have the same value for the first N-1 columns. For +** a K-column index, there will be K+1 integers in the stat column. If +** the index is unique, then the last integer will be 1. +** +** The list of integers in the stat column can optionally be followed +** by the keyword "unordered". The "unordered" keyword, if it is present, +** must be separated from the last integer by a single space. If the +** "unordered" keyword is present, then the query planner assumes that +** the index is unordered and will not use the index for a range query. +** +** If the sqlite_stat1.idx column is NULL, then the sqlite_stat1.stat +** column contains a single integer which is the (estimated) number of +** rows in the table identified by sqlite_stat1.tbl. +** +** Format of sqlite_stat2: +** +** The sqlite_stat2 is only created and is only used if SQLite is compiled +** with SQLITE_ENABLE_STAT2 and if the SQLite version number is between +** 3.6.18 and 3.7.8. The "stat2" table contains additional information +** about the distribution of keys within an index. The index is identified by +** the "idx" column and the "tbl" column is the name of the table to which +** the index belongs. There are usually 10 rows in the sqlite_stat2 +** table for each index. +** +** The sqlite_stat2 entries for an index that have sampleno between 0 and 9 +** inclusive are samples of the left-most key value in the index taken at +** evenly spaced points along the index. Let the number of samples be S +** (10 in the standard build) and let C be the number of rows in the index. +** Then the sampled rows are given by: +** +** rownumber = (i*C*2 + C)/(S*2) +** +** For i between 0 and S-1. Conceptually, the index space is divided into +** S uniform buckets and the samples are the middle row from each bucket. +** +** The format for sqlite_stat2 is recorded here for legacy reference. This +** version of SQLite does not support sqlite_stat2. It neither reads nor +** writes the sqlite_stat2 table. This version of SQLite only supports +** sqlite_stat3. +** +** Format for sqlite_stat3: +** +** The sqlite_stat3 format is a subset of sqlite_stat4. Hence, the +** sqlite_stat4 format will be described first. Further information +** about sqlite_stat3 follows the sqlite_stat4 description. +** +** Format for sqlite_stat4: +** +** As with sqlite_stat2, the sqlite_stat4 table contains histogram data +** to aid the query planner in choosing good indices based on the values +** that indexed columns are compared against in the WHERE clauses of +** queries. +** +** The sqlite_stat4 table contains multiple entries for each index. +** The idx column names the index and the tbl column is the table of the +** index. If the idx and tbl columns are the same, then the sample is +** of the INTEGER PRIMARY KEY. The sample column is a blob which is the +** binary encoding of a key from the index. The nEq column is a +** list of integers. The first integer is the approximate number +** of entries in the index whose left-most column exactly matches +** the left-most column of the sample. The second integer in nEq +** is the approximate number of entries in the index where the +** first two columns match the first two columns of the sample. +** And so forth. nLt is another list of integers that show the approximate +** number of entries that are strictly less than the sample. The first +** integer in nLt contains the number of entries in the index where the +** left-most column is less than the left-most column of the sample. +** The K-th integer in the nLt entry is the number of index entries +** where the first K columns are less than the first K columns of the +** sample. The nDLt column is like nLt except that it contains the +** number of distinct entries in the index that are less than the +** sample. +** +** There can be an arbitrary number of sqlite_stat4 entries per index. +** The ANALYZE command will typically generate sqlite_stat4 tables +** that contain between 10 and 40 samples which are distributed across +** the key space, though not uniformly, and which include samples with +** large nEq values. +** +** Format for sqlite_stat3 redux: +** +** The sqlite_stat3 table is like sqlite_stat4 except that it only +** looks at the left-most column of the index. The sqlite_stat3.sample +** column contains the actual value of the left-most column instead +** of a blob encoding of the complete index key as is found in +** sqlite_stat4.sample. The nEq, nLt, and nDLt entries of sqlite_stat3 +** all contain just a single integer which is the same as the first +** integer in the equivalent columns in sqlite_stat4. +*/ +#ifndef SQLITE_OMIT_ANALYZE +/* #include "sqliteInt.h" */ + +#if defined(SQLITE_ENABLE_STAT4) +# define IsStat4 1 +#else +# define IsStat4 0 +# undef SQLITE_STAT4_SAMPLES +# define SQLITE_STAT4_SAMPLES 1 +#endif + +/* +** This routine generates code that opens the sqlite_statN tables. +** The sqlite_stat1 table is always relevant. sqlite_stat2 is now +** obsolete. sqlite_stat3 and sqlite_stat4 are only opened when +** appropriate compile-time options are provided. +** +** If the sqlite_statN tables do not previously exist, it is created. +** +** Argument zWhere may be a pointer to a buffer containing a table name, +** or it may be a NULL pointer. If it is not NULL, then all entries in +** the sqlite_statN tables associated with the named table are deleted. +** If zWhere==0, then code is generated to delete all stat table entries. +*/ +static void openStatTable( + Parse *pParse, /* Parsing context */ + int iDb, /* The database we are looking in */ + int iStatCur, /* Open the sqlite_stat1 table on this cursor */ + const char *zWhere, /* Delete entries for this table or index */ + const char *zWhereType /* Either "tbl" or "idx" */ +){ + static const struct { + const char *zName; + const char *zCols; + } aTable[] = { + { "sqlite_stat1", "tbl,idx,stat" }, +#if defined(SQLITE_ENABLE_STAT4) + { "sqlite_stat4", "tbl,idx,neq,nlt,ndlt,sample" }, +#else + { "sqlite_stat4", 0 }, +#endif + { "sqlite_stat3", 0 }, + }; + int i; + sqlite3 *db = pParse->db; + Db *pDb; + Vdbe *v = sqlite3GetVdbe(pParse); + u32 aRoot[ArraySize(aTable)]; + u8 aCreateTbl[ArraySize(aTable)]; +#ifdef SQLITE_ENABLE_STAT4 + const int nToOpen = OptimizationEnabled(db,SQLITE_Stat4) ? 2 : 1; +#else + const int nToOpen = 1; +#endif + + if( v==0 ) return; + assert( sqlite3BtreeHoldsAllMutexes(db) ); + assert( sqlite3VdbeDb(v)==db ); + pDb = &db->aDb[iDb]; + + /* Create new statistic tables if they do not exist, or clear them + ** if they do already exist. + */ + for(i=0; i<ArraySize(aTable); i++){ + const char *zTab = aTable[i].zName; + Table *pStat; + aCreateTbl[i] = 0; + if( (pStat = sqlite3FindTable(db, zTab, pDb->zDbSName))==0 ){ + if( i<nToOpen ){ + /* The sqlite_statN table does not exist. Create it. Note that a + ** side-effect of the CREATE TABLE statement is to leave the rootpage + ** of the new table in register pParse->regRoot. This is important + ** because the OpenWrite opcode below will be needing it. */ + sqlite3NestedParse(pParse, + "CREATE TABLE %Q.%s(%s)", pDb->zDbSName, zTab, aTable[i].zCols + ); + aRoot[i] = (u32)pParse->regRoot; + aCreateTbl[i] = OPFLAG_P2ISREG; + } + }else{ + /* The table already exists. If zWhere is not NULL, delete all entries + ** associated with the table zWhere. If zWhere is NULL, delete the + ** entire contents of the table. */ + aRoot[i] = pStat->tnum; + sqlite3TableLock(pParse, iDb, aRoot[i], 1, zTab); + if( zWhere ){ + sqlite3NestedParse(pParse, + "DELETE FROM %Q.%s WHERE %s=%Q", + pDb->zDbSName, zTab, zWhereType, zWhere + ); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + }else if( db->xPreUpdateCallback ){ + sqlite3NestedParse(pParse, "DELETE FROM %Q.%s", pDb->zDbSName, zTab); +#endif + }else{ + /* The sqlite_stat[134] table already exists. Delete all rows. */ + sqlite3VdbeAddOp2(v, OP_Clear, (int)aRoot[i], iDb); + } + } + } + + /* Open the sqlite_stat[134] tables for writing. */ + for(i=0; i<nToOpen; i++){ + assert( i<ArraySize(aTable) ); + sqlite3VdbeAddOp4Int(v, OP_OpenWrite, iStatCur+i, (int)aRoot[i], iDb, 3); + sqlite3VdbeChangeP5(v, aCreateTbl[i]); + VdbeComment((v, aTable[i].zName)); + } +} + +/* +** Recommended number of samples for sqlite_stat4 +*/ +#ifndef SQLITE_STAT4_SAMPLES +# define SQLITE_STAT4_SAMPLES 24 +#endif + +/* +** Three SQL functions - stat_init(), stat_push(), and stat_get() - +** share an instance of the following structure to hold their state +** information. +*/ +typedef struct StatAccum StatAccum; +typedef struct StatSample StatSample; +struct StatSample { + tRowcnt *anEq; /* sqlite_stat4.nEq */ + tRowcnt *anDLt; /* sqlite_stat4.nDLt */ +#ifdef SQLITE_ENABLE_STAT4 + tRowcnt *anLt; /* sqlite_stat4.nLt */ + union { + i64 iRowid; /* Rowid in main table of the key */ + u8 *aRowid; /* Key for WITHOUT ROWID tables */ + } u; + u32 nRowid; /* Sizeof aRowid[] */ + u8 isPSample; /* True if a periodic sample */ + int iCol; /* If !isPSample, the reason for inclusion */ + u32 iHash; /* Tiebreaker hash */ +#endif +}; +struct StatAccum { + sqlite3 *db; /* Database connection, for malloc() */ + tRowcnt nEst; /* Estimated number of rows */ + tRowcnt nRow; /* Number of rows visited so far */ + int nLimit; /* Analysis row-scan limit */ + int nCol; /* Number of columns in index + pk/rowid */ + int nKeyCol; /* Number of index columns w/o the pk/rowid */ + u8 nSkipAhead; /* Number of times of skip-ahead */ + StatSample current; /* Current row as a StatSample */ +#ifdef SQLITE_ENABLE_STAT4 + tRowcnt nPSample; /* How often to do a periodic sample */ + int mxSample; /* Maximum number of samples to accumulate */ + u32 iPrn; /* Pseudo-random number used for sampling */ + StatSample *aBest; /* Array of nCol best samples */ + int iMin; /* Index in a[] of entry with minimum score */ + int nSample; /* Current number of samples */ + int nMaxEqZero; /* Max leading 0 in anEq[] for any a[] entry */ + int iGet; /* Index of current sample accessed by stat_get() */ + StatSample *a; /* Array of mxSample StatSample objects */ +#endif +}; + +/* Reclaim memory used by a StatSample +*/ +#ifdef SQLITE_ENABLE_STAT4 +static void sampleClear(sqlite3 *db, StatSample *p){ + assert( db!=0 ); + if( p->nRowid ){ + sqlite3DbFree(db, p->u.aRowid); + p->nRowid = 0; + } +} +#endif + +/* Initialize the BLOB value of a ROWID +*/ +#ifdef SQLITE_ENABLE_STAT4 +static void sampleSetRowid(sqlite3 *db, StatSample *p, int n, const u8 *pData){ + assert( db!=0 ); + if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); + p->u.aRowid = sqlite3DbMallocRawNN(db, n); + if( p->u.aRowid ){ + p->nRowid = n; + memcpy(p->u.aRowid, pData, n); + }else{ + p->nRowid = 0; + } +} +#endif + +/* Initialize the INTEGER value of a ROWID. +*/ +#ifdef SQLITE_ENABLE_STAT4 +static void sampleSetRowidInt64(sqlite3 *db, StatSample *p, i64 iRowid){ + assert( db!=0 ); + if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); + p->nRowid = 0; + p->u.iRowid = iRowid; +} +#endif + + +/* +** Copy the contents of object (*pFrom) into (*pTo). +*/ +#ifdef SQLITE_ENABLE_STAT4 +static void sampleCopy(StatAccum *p, StatSample *pTo, StatSample *pFrom){ + pTo->isPSample = pFrom->isPSample; + pTo->iCol = pFrom->iCol; + pTo->iHash = pFrom->iHash; + memcpy(pTo->anEq, pFrom->anEq, sizeof(tRowcnt)*p->nCol); + memcpy(pTo->anLt, pFrom->anLt, sizeof(tRowcnt)*p->nCol); + memcpy(pTo->anDLt, pFrom->anDLt, sizeof(tRowcnt)*p->nCol); + if( pFrom->nRowid ){ + sampleSetRowid(p->db, pTo, pFrom->nRowid, pFrom->u.aRowid); + }else{ + sampleSetRowidInt64(p->db, pTo, pFrom->u.iRowid); + } +} +#endif + +/* +** Reclaim all memory of a StatAccum structure. +*/ +static void statAccumDestructor(void *pOld){ + StatAccum *p = (StatAccum*)pOld; +#ifdef SQLITE_ENABLE_STAT4 + if( p->mxSample ){ + int i; + for(i=0; i<p->nCol; i++) sampleClear(p->db, p->aBest+i); + for(i=0; i<p->mxSample; i++) sampleClear(p->db, p->a+i); + sampleClear(p->db, &p->current); + } +#endif + sqlite3DbFree(p->db, p); +} + +/* +** Implementation of the stat_init(N,K,C,L) SQL function. The four parameters +** are: +** N: The number of columns in the index including the rowid/pk (note 1) +** K: The number of columns in the index excluding the rowid/pk. +** C: Estimated number of rows in the index +** L: A limit on the number of rows to scan, or 0 for no-limit +** +** Note 1: In the special case of the covering index that implements a +** WITHOUT ROWID table, N is the number of PRIMARY KEY columns, not the +** total number of columns in the table. +** +** For indexes on ordinary rowid tables, N==K+1. But for indexes on +** WITHOUT ROWID tables, N=K+P where P is the number of columns in the +** PRIMARY KEY of the table. The covering index that implements the +** original WITHOUT ROWID table as N==K as a special case. +** +** This routine allocates the StatAccum object in heap memory. The return +** value is a pointer to the StatAccum object. The datatype of the +** return value is BLOB, but it is really just a pointer to the StatAccum +** object. +*/ +static void statInit( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + StatAccum *p; + int nCol; /* Number of columns in index being sampled */ + int nKeyCol; /* Number of key columns */ + int nColUp; /* nCol rounded up for alignment */ + int n; /* Bytes of space to allocate */ + sqlite3 *db = sqlite3_context_db_handle(context); /* Database connection */ +#ifdef SQLITE_ENABLE_STAT4 + /* Maximum number of samples. 0 if STAT4 data is not collected */ + int mxSample = OptimizationEnabled(db,SQLITE_Stat4) ?SQLITE_STAT4_SAMPLES :0; +#endif + + /* Decode the three function arguments */ + UNUSED_PARAMETER(argc); + nCol = sqlite3_value_int(argv[0]); + assert( nCol>0 ); + nColUp = sizeof(tRowcnt)<8 ? (nCol+1)&~1 : nCol; + nKeyCol = sqlite3_value_int(argv[1]); + assert( nKeyCol<=nCol ); + assert( nKeyCol>0 ); + + /* Allocate the space required for the StatAccum object */ + n = sizeof(*p) + + sizeof(tRowcnt)*nColUp /* StatAccum.anEq */ + + sizeof(tRowcnt)*nColUp; /* StatAccum.anDLt */ +#ifdef SQLITE_ENABLE_STAT4 + if( mxSample ){ + n += sizeof(tRowcnt)*nColUp /* StatAccum.anLt */ + + sizeof(StatSample)*(nCol+mxSample) /* StatAccum.aBest[], a[] */ + + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample); + } +#endif + p = sqlite3DbMallocZero(db, n); + if( p==0 ){ + sqlite3_result_error_nomem(context); + return; + } + + p->db = db; + p->nEst = sqlite3_value_int64(argv[2]); + p->nRow = 0; + p->nLimit = sqlite3_value_int64(argv[3]); + p->nCol = nCol; + p->nKeyCol = nKeyCol; + p->nSkipAhead = 0; + p->current.anDLt = (tRowcnt*)&p[1]; + p->current.anEq = &p->current.anDLt[nColUp]; + +#ifdef SQLITE_ENABLE_STAT4 + p->mxSample = p->nLimit==0 ? mxSample : 0; + if( mxSample ){ + u8 *pSpace; /* Allocated space not yet assigned */ + int i; /* Used to iterate through p->aSample[] */ + + p->iGet = -1; + p->nPSample = (tRowcnt)(p->nEst/(mxSample/3+1) + 1); + p->current.anLt = &p->current.anEq[nColUp]; + p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)sqlite3_value_int(argv[2]); + + /* Set up the StatAccum.a[] and aBest[] arrays */ + p->a = (struct StatSample*)&p->current.anLt[nColUp]; + p->aBest = &p->a[mxSample]; + pSpace = (u8*)(&p->a[mxSample+nCol]); + for(i=0; i<(mxSample+nCol); i++){ + p->a[i].anEq = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp); + p->a[i].anLt = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp); + p->a[i].anDLt = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp); + } + assert( (pSpace - (u8*)p)==n ); + + for(i=0; i<nCol; i++){ + p->aBest[i].iCol = i; + } + } +#endif + + /* Return a pointer to the allocated object to the caller. Note that + ** only the pointer (the 2nd parameter) matters. The size of the object + ** (given by the 3rd parameter) is never used and can be any positive + ** value. */ + sqlite3_result_blob(context, p, sizeof(*p), statAccumDestructor); +} +static const FuncDef statInitFuncdef = { + 4, /* nArg */ + SQLITE_UTF8, /* funcFlags */ + 0, /* pUserData */ + 0, /* pNext */ + statInit, /* xSFunc */ + 0, /* xFinalize */ + 0, 0, /* xValue, xInverse */ + "stat_init", /* zName */ + {0} +}; + +#ifdef SQLITE_ENABLE_STAT4 +/* +** pNew and pOld are both candidate non-periodic samples selected for +** the same column (pNew->iCol==pOld->iCol). Ignoring this column and +** considering only any trailing columns and the sample hash value, this +** function returns true if sample pNew is to be preferred over pOld. +** In other words, if we assume that the cardinalities of the selected +** column for pNew and pOld are equal, is pNew to be preferred over pOld. +** +** This function assumes that for each argument sample, the contents of +** the anEq[] array from pSample->anEq[pSample->iCol+1] onwards are valid. +*/ +static int sampleIsBetterPost( + StatAccum *pAccum, + StatSample *pNew, + StatSample *pOld +){ + int nCol = pAccum->nCol; + int i; + assert( pNew->iCol==pOld->iCol ); + for(i=pNew->iCol+1; i<nCol; i++){ + if( pNew->anEq[i]>pOld->anEq[i] ) return 1; + if( pNew->anEq[i]<pOld->anEq[i] ) return 0; + } + if( pNew->iHash>pOld->iHash ) return 1; + return 0; +} +#endif + +#ifdef SQLITE_ENABLE_STAT4 +/* +** Return true if pNew is to be preferred over pOld. +** +** This function assumes that for each argument sample, the contents of +** the anEq[] array from pSample->anEq[pSample->iCol] onwards are valid. +*/ +static int sampleIsBetter( + StatAccum *pAccum, + StatSample *pNew, + StatSample *pOld +){ + tRowcnt nEqNew = pNew->anEq[pNew->iCol]; + tRowcnt nEqOld = pOld->anEq[pOld->iCol]; + + assert( pOld->isPSample==0 && pNew->isPSample==0 ); + assert( IsStat4 || (pNew->iCol==0 && pOld->iCol==0) ); + + if( (nEqNew>nEqOld) ) return 1; + if( nEqNew==nEqOld ){ + if( pNew->iCol<pOld->iCol ) return 1; + return (pNew->iCol==pOld->iCol && sampleIsBetterPost(pAccum, pNew, pOld)); + } + return 0; +} + +/* +** Copy the contents of sample *pNew into the p->a[] array. If necessary, +** remove the least desirable sample from p->a[] to make room. +*/ +static void sampleInsert(StatAccum *p, StatSample *pNew, int nEqZero){ + StatSample *pSample = 0; + int i; + + assert( IsStat4 || nEqZero==0 ); + + /* StatAccum.nMaxEqZero is set to the maximum number of leading 0 + ** values in the anEq[] array of any sample in StatAccum.a[]. In + ** other words, if nMaxEqZero is n, then it is guaranteed that there + ** are no samples with StatSample.anEq[m]==0 for (m>=n). */ + if( nEqZero>p->nMaxEqZero ){ + p->nMaxEqZero = nEqZero; + } + if( pNew->isPSample==0 ){ + StatSample *pUpgrade = 0; + assert( pNew->anEq[pNew->iCol]>0 ); + + /* This sample is being added because the prefix that ends in column + ** iCol occurs many times in the table. However, if we have already + ** added a sample that shares this prefix, there is no need to add + ** this one. Instead, upgrade the priority of the highest priority + ** existing sample that shares this prefix. */ + for(i=p->nSample-1; i>=0; i--){ + StatSample *pOld = &p->a[i]; + if( pOld->anEq[pNew->iCol]==0 ){ + if( pOld->isPSample ) return; + assert( pOld->iCol>pNew->iCol ); + assert( sampleIsBetter(p, pNew, pOld) ); + if( pUpgrade==0 || sampleIsBetter(p, pOld, pUpgrade) ){ + pUpgrade = pOld; + } + } + } + if( pUpgrade ){ + pUpgrade->iCol = pNew->iCol; + pUpgrade->anEq[pUpgrade->iCol] = pNew->anEq[pUpgrade->iCol]; + goto find_new_min; + } + } + + /* If necessary, remove sample iMin to make room for the new sample. */ + if( p->nSample>=p->mxSample ){ + StatSample *pMin = &p->a[p->iMin]; + tRowcnt *anEq = pMin->anEq; + tRowcnt *anLt = pMin->anLt; + tRowcnt *anDLt = pMin->anDLt; + sampleClear(p->db, pMin); + memmove(pMin, &pMin[1], sizeof(p->a[0])*(p->nSample-p->iMin-1)); + pSample = &p->a[p->nSample-1]; + pSample->nRowid = 0; + pSample->anEq = anEq; + pSample->anDLt = anDLt; + pSample->anLt = anLt; + p->nSample = p->mxSample-1; + } + + /* The "rows less-than" for the rowid column must be greater than that + ** for the last sample in the p->a[] array. Otherwise, the samples would + ** be out of order. */ + assert( p->nSample==0 + || pNew->anLt[p->nCol-1] > p->a[p->nSample-1].anLt[p->nCol-1] ); + + /* Insert the new sample */ + pSample = &p->a[p->nSample]; + sampleCopy(p, pSample, pNew); + p->nSample++; + + /* Zero the first nEqZero entries in the anEq[] array. */ + memset(pSample->anEq, 0, sizeof(tRowcnt)*nEqZero); + +find_new_min: + if( p->nSample>=p->mxSample ){ + int iMin = -1; + for(i=0; i<p->mxSample; i++){ + if( p->a[i].isPSample ) continue; + if( iMin<0 || sampleIsBetter(p, &p->a[iMin], &p->a[i]) ){ + iMin = i; + } + } + assert( iMin>=0 ); + p->iMin = iMin; + } +} +#endif /* SQLITE_ENABLE_STAT4 */ + +#ifdef SQLITE_ENABLE_STAT4 +/* +** Field iChng of the index being scanned has changed. So at this point +** p->current contains a sample that reflects the previous row of the +** index. The value of anEq[iChng] and subsequent anEq[] elements are +** correct at this point. +*/ +static void samplePushPrevious(StatAccum *p, int iChng){ + int i; + + /* Check if any samples from the aBest[] array should be pushed + ** into IndexSample.a[] at this point. */ + for(i=(p->nCol-2); i>=iChng; i--){ + StatSample *pBest = &p->aBest[i]; + pBest->anEq[i] = p->current.anEq[i]; + if( p->nSample<p->mxSample || sampleIsBetter(p, pBest, &p->a[p->iMin]) ){ + sampleInsert(p, pBest, i); + } + } + + /* Check that no sample contains an anEq[] entry with an index of + ** p->nMaxEqZero or greater set to zero. */ + for(i=p->nSample-1; i>=0; i--){ + int j; + for(j=p->nMaxEqZero; j<p->nCol; j++) assert( p->a[i].anEq[j]>0 ); + } + + /* Update the anEq[] fields of any samples already collected. */ + if( iChng<p->nMaxEqZero ){ + for(i=p->nSample-1; i>=0; i--){ + int j; + for(j=iChng; j<p->nCol; j++){ + if( p->a[i].anEq[j]==0 ) p->a[i].anEq[j] = p->current.anEq[j]; + } + } + p->nMaxEqZero = iChng; + } +} +#endif /* SQLITE_ENABLE_STAT4 */ + +/* +** Implementation of the stat_push SQL function: stat_push(P,C,R) +** Arguments: +** +** P Pointer to the StatAccum object created by stat_init() +** C Index of left-most column to differ from previous row +** R Rowid for the current row. Might be a key record for +** WITHOUT ROWID tables. +** +** The purpose of this routine is to collect statistical data and/or +** samples from the index being analyzed into the StatAccum object. +** The stat_get() SQL function will be used afterwards to +** retrieve the information gathered. +** +** This SQL function usually returns NULL, but might return an integer +** if it wants the byte-code to do special processing. +** +** The R parameter is only used for STAT4 +*/ +static void statPush( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int i; + + /* The three function arguments */ + StatAccum *p = (StatAccum*)sqlite3_value_blob(argv[0]); + int iChng = sqlite3_value_int(argv[1]); + + UNUSED_PARAMETER( argc ); + UNUSED_PARAMETER( context ); + assert( p->nCol>0 ); + assert( iChng<p->nCol ); + + if( p->nRow==0 ){ + /* This is the first call to this function. Do initialization. */ + for(i=0; i<p->nCol; i++) p->current.anEq[i] = 1; + }else{ + /* Second and subsequent calls get processed here */ +#ifdef SQLITE_ENABLE_STAT4 + if( p->mxSample ) samplePushPrevious(p, iChng); +#endif + + /* Update anDLt[], anLt[] and anEq[] to reflect the values that apply + ** to the current row of the index. */ + for(i=0; i<iChng; i++){ + p->current.anEq[i]++; + } + for(i=iChng; i<p->nCol; i++){ + p->current.anDLt[i]++; +#ifdef SQLITE_ENABLE_STAT4 + if( p->mxSample ) p->current.anLt[i] += p->current.anEq[i]; +#endif + p->current.anEq[i] = 1; + } + } + + p->nRow++; +#ifdef SQLITE_ENABLE_STAT4 + if( p->mxSample ){ + tRowcnt nLt; + if( sqlite3_value_type(argv[2])==SQLITE_INTEGER ){ + sampleSetRowidInt64(p->db, &p->current, sqlite3_value_int64(argv[2])); + }else{ + sampleSetRowid(p->db, &p->current, sqlite3_value_bytes(argv[2]), + sqlite3_value_blob(argv[2])); + } + p->current.iHash = p->iPrn = p->iPrn*1103515245 + 12345; + + nLt = p->current.anLt[p->nCol-1]; + /* Check if this is to be a periodic sample. If so, add it. */ + if( (nLt/p->nPSample)!=(nLt+1)/p->nPSample ){ + p->current.isPSample = 1; + p->current.iCol = 0; + sampleInsert(p, &p->current, p->nCol-1); + p->current.isPSample = 0; + } + + /* Update the aBest[] array. */ + for(i=0; i<(p->nCol-1); i++){ + p->current.iCol = i; + if( i>=iChng || sampleIsBetterPost(p, &p->current, &p->aBest[i]) ){ + sampleCopy(p, &p->aBest[i], &p->current); + } + } + }else +#endif + if( p->nLimit && p->nRow>(tRowcnt)p->nLimit*(p->nSkipAhead+1) ){ + p->nSkipAhead++; + sqlite3_result_int(context, p->current.anDLt[0]>0); + } +} + +static const FuncDef statPushFuncdef = { + 2+IsStat4, /* nArg */ + SQLITE_UTF8, /* funcFlags */ + 0, /* pUserData */ + 0, /* pNext */ + statPush, /* xSFunc */ + 0, /* xFinalize */ + 0, 0, /* xValue, xInverse */ + "stat_push", /* zName */ + {0} +}; + +#define STAT_GET_STAT1 0 /* "stat" column of stat1 table */ +#define STAT_GET_ROWID 1 /* "rowid" column of stat[34] entry */ +#define STAT_GET_NEQ 2 /* "neq" column of stat[34] entry */ +#define STAT_GET_NLT 3 /* "nlt" column of stat[34] entry */ +#define STAT_GET_NDLT 4 /* "ndlt" column of stat[34] entry */ + +/* +** Implementation of the stat_get(P,J) SQL function. This routine is +** used to query statistical information that has been gathered into +** the StatAccum object by prior calls to stat_push(). The P parameter +** has type BLOB but it is really just a pointer to the StatAccum object. +** The content to returned is determined by the parameter J +** which is one of the STAT_GET_xxxx values defined above. +** +** The stat_get(P,J) function is not available to generic SQL. It is +** inserted as part of a manually constructed bytecode program. (See +** the callStatGet() routine below.) It is guaranteed that the P +** parameter will always be a pointer to a StatAccum object, never a +** NULL. +** +** If STAT4 is not enabled, then J is always +** STAT_GET_STAT1 and is hence omitted and this routine becomes +** a one-parameter function, stat_get(P), that always returns the +** stat1 table entry information. +*/ +static void statGet( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + StatAccum *p = (StatAccum*)sqlite3_value_blob(argv[0]); +#ifdef SQLITE_ENABLE_STAT4 + /* STAT4 has a parameter on this routine. */ + int eCall = sqlite3_value_int(argv[1]); + assert( argc==2 ); + assert( eCall==STAT_GET_STAT1 || eCall==STAT_GET_NEQ + || eCall==STAT_GET_ROWID || eCall==STAT_GET_NLT + || eCall==STAT_GET_NDLT + ); + assert( eCall==STAT_GET_STAT1 || p->mxSample ); + if( eCall==STAT_GET_STAT1 ) +#else + assert( argc==1 ); +#endif + { + /* Return the value to store in the "stat" column of the sqlite_stat1 + ** table for this index. + ** + ** The value is a string composed of a list of integers describing + ** the index. The first integer in the list is the total number of + ** entries in the index. There is one additional integer in the list + ** for each indexed column. This additional integer is an estimate of + ** the number of rows matched by a equality query on the index using + ** a key with the corresponding number of fields. In other words, + ** if the index is on columns (a,b) and the sqlite_stat1 value is + ** "100 10 2", then SQLite estimates that: + ** + ** * the index contains 100 rows, + ** * "WHERE a=?" matches 10 rows, and + ** * "WHERE a=? AND b=?" matches 2 rows. + ** + ** If D is the count of distinct values and K is the total number of + ** rows, then each estimate is usually computed as: + ** + ** I = (K+D-1)/D + ** + ** In other words, I is K/D rounded up to the next whole integer. + ** However, if I is between 1.0 and 1.1 (in other words if I is + ** close to 1.0 but just a little larger) then do not round up but + ** instead keep the I value at 1.0. + */ + sqlite3_str sStat; /* Text of the constructed "stat" line */ + int i; /* Loop counter */ + + sqlite3StrAccumInit(&sStat, 0, 0, 0, (p->nKeyCol+1)*100); + sqlite3_str_appendf(&sStat, "%llu", + p->nSkipAhead ? (u64)p->nEst : (u64)p->nRow); + for(i=0; i<p->nKeyCol; i++){ + u64 nDistinct = p->current.anDLt[i] + 1; + u64 iVal = (p->nRow + nDistinct - 1) / nDistinct; + if( iVal==2 && p->nRow*10 <= nDistinct*11 ) iVal = 1; + sqlite3_str_appendf(&sStat, " %llu", iVal); + assert( p->current.anEq[i] ); + } + sqlite3ResultStrAccum(context, &sStat); + } +#ifdef SQLITE_ENABLE_STAT4 + else if( eCall==STAT_GET_ROWID ){ + if( p->iGet<0 ){ + samplePushPrevious(p, 0); + p->iGet = 0; + } + if( p->iGet<p->nSample ){ + StatSample *pS = p->a + p->iGet; + if( pS->nRowid==0 ){ + sqlite3_result_int64(context, pS->u.iRowid); + }else{ + sqlite3_result_blob(context, pS->u.aRowid, pS->nRowid, + SQLITE_TRANSIENT); + } + } + }else{ + tRowcnt *aCnt = 0; + sqlite3_str sStat; + int i; + + assert( p->iGet<p->nSample ); + switch( eCall ){ + case STAT_GET_NEQ: aCnt = p->a[p->iGet].anEq; break; + case STAT_GET_NLT: aCnt = p->a[p->iGet].anLt; break; + default: { + aCnt = p->a[p->iGet].anDLt; + p->iGet++; + break; + } + } + sqlite3StrAccumInit(&sStat, 0, 0, 0, p->nCol*100); + for(i=0; i<p->nCol; i++){ + sqlite3_str_appendf(&sStat, "%llu ", (u64)aCnt[i]); + } + if( sStat.nChar ) sStat.nChar--; + sqlite3ResultStrAccum(context, &sStat); + } +#endif /* SQLITE_ENABLE_STAT4 */ +#ifndef SQLITE_DEBUG + UNUSED_PARAMETER( argc ); +#endif +} +static const FuncDef statGetFuncdef = { + 1+IsStat4, /* nArg */ + SQLITE_UTF8, /* funcFlags */ + 0, /* pUserData */ + 0, /* pNext */ + statGet, /* xSFunc */ + 0, /* xFinalize */ + 0, 0, /* xValue, xInverse */ + "stat_get", /* zName */ + {0} +}; + +static void callStatGet(Parse *pParse, int regStat, int iParam, int regOut){ +#ifdef SQLITE_ENABLE_STAT4 + sqlite3VdbeAddOp2(pParse->pVdbe, OP_Integer, iParam, regStat+1); +#elif SQLITE_DEBUG + assert( iParam==STAT_GET_STAT1 ); +#else + UNUSED_PARAMETER( iParam ); +#endif + assert( regOut!=regStat && regOut!=regStat+1 ); + sqlite3VdbeAddFunctionCall(pParse, 0, regStat, regOut, 1+IsStat4, + &statGetFuncdef, 0); +} + +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS +/* Add a comment to the most recent VDBE opcode that is the name +** of the k-th column of the pIdx index. +*/ +static void analyzeVdbeCommentIndexWithColumnName( + Vdbe *v, /* Prepared statement under construction */ + Index *pIdx, /* Index whose column is being loaded */ + int k /* Which column index */ +){ + int i; /* Index of column in the table */ + assert( k>=0 && k<pIdx->nColumn ); + i = pIdx->aiColumn[k]; + if( NEVER(i==XN_ROWID) ){ + VdbeComment((v,"%s.rowid",pIdx->zName)); + }else if( i==XN_EXPR ){ + assert( pIdx->bHasExpr ); + VdbeComment((v,"%s.expr(%d)",pIdx->zName, k)); + }else{ + VdbeComment((v,"%s.%s", pIdx->zName, pIdx->pTable->aCol[i].zCnName)); + } +} +#else +# define analyzeVdbeCommentIndexWithColumnName(a,b,c) +#endif /* SQLITE_DEBUG */ + +/* +** Generate code to do an analysis of all indices associated with +** a single table. +*/ +static void analyzeOneTable( + Parse *pParse, /* Parser context */ + Table *pTab, /* Table whose indices are to be analyzed */ + Index *pOnlyIdx, /* If not NULL, only analyze this one index */ + int iStatCur, /* Index of VdbeCursor that writes the sqlite_stat1 table */ + int iMem, /* Available memory locations begin here */ + int iTab /* Next available cursor */ +){ + sqlite3 *db = pParse->db; /* Database handle */ + Index *pIdx; /* An index to being analyzed */ + int iIdxCur; /* Cursor open on index being analyzed */ + int iTabCur; /* Table cursor */ + Vdbe *v; /* The virtual machine being built up */ + int i; /* Loop counter */ + int jZeroRows = -1; /* Jump from here if number of rows is zero */ + int iDb; /* Index of database containing pTab */ + u8 needTableCnt = 1; /* True to count the table */ + int regNewRowid = iMem++; /* Rowid for the inserted record */ + int regStat = iMem++; /* Register to hold StatAccum object */ + int regChng = iMem++; /* Index of changed index field */ + int regRowid = iMem++; /* Rowid argument passed to stat_push() */ + int regTemp = iMem++; /* Temporary use register */ + int regTemp2 = iMem++; /* Second temporary use register */ + int regTabname = iMem++; /* Register containing table name */ + int regIdxname = iMem++; /* Register containing index name */ + int regStat1 = iMem++; /* Value for the stat column of sqlite_stat1 */ + int regPrev = iMem; /* MUST BE LAST (see below) */ +#ifdef SQLITE_ENABLE_STAT4 + int doOnce = 1; /* Flag for a one-time computation */ +#endif +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + Table *pStat1 = 0; +#endif + + sqlite3TouchRegister(pParse, iMem); + assert( sqlite3NoTempsInRange(pParse, regNewRowid, iMem) ); + v = sqlite3GetVdbe(pParse); + if( v==0 || NEVER(pTab==0) ){ + return; + } + if( !IsOrdinaryTable(pTab) ){ + /* Do not gather statistics on views or virtual tables */ + return; + } + if( sqlite3_strlike("sqlite\\_%", pTab->zName, '\\')==0 ){ + /* Do not gather statistics on system tables */ + return; + } + assert( sqlite3BtreeHoldsAllMutexes(db) ); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb>=0 ); + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); +#ifndef SQLITE_OMIT_AUTHORIZATION + if( sqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0, + db->aDb[iDb].zDbSName ) ){ + return; + } +#endif + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( db->xPreUpdateCallback ){ + pStat1 = (Table*)sqlite3DbMallocZero(db, sizeof(Table) + 13); + if( pStat1==0 ) return; + pStat1->zName = (char*)&pStat1[1]; + memcpy(pStat1->zName, "sqlite_stat1", 13); + pStat1->nCol = 3; + pStat1->iPKey = -1; + sqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNAMIC); + } +#endif + + /* Establish a read-lock on the table at the shared-cache level. + ** Open a read-only cursor on the table. Also allocate a cursor number + ** to use for scanning indexes (iIdxCur). No index cursor is opened at + ** this time though. */ + sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); + iTabCur = iTab++; + iIdxCur = iTab++; + pParse->nTab = MAX(pParse->nTab, iTab); + sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); + sqlite3VdbeLoadString(v, regTabname, pTab->zName); + + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int nCol; /* Number of columns in pIdx. "N" */ + int addrRewind; /* Address of "OP_Rewind iIdxCur" */ + int addrNextRow; /* Address of "next_row:" */ + const char *zIdxName; /* Name of the index */ + int nColTest; /* Number of columns to test for changes */ + + if( pOnlyIdx && pOnlyIdx!=pIdx ) continue; + if( pIdx->pPartIdxWhere==0 ) needTableCnt = 0; + if( !HasRowid(pTab) && IsPrimaryKeyIndex(pIdx) ){ + nCol = pIdx->nKeyCol; + zIdxName = pTab->zName; + nColTest = nCol - 1; + }else{ + nCol = pIdx->nColumn; + zIdxName = pIdx->zName; + nColTest = pIdx->uniqNotNull ? pIdx->nKeyCol-1 : nCol-1; + } + + /* Populate the register containing the index name. */ + sqlite3VdbeLoadString(v, regIdxname, zIdxName); + VdbeComment((v, "Analysis for %s.%s", pTab->zName, zIdxName)); + + /* + ** Pseudo-code for loop that calls stat_push(): + ** + ** Rewind csr + ** if eof(csr) goto end_of_scan; + ** regChng = 0 + ** goto chng_addr_0; + ** + ** next_row: + ** regChng = 0 + ** if( idx(0) != regPrev(0) ) goto chng_addr_0 + ** regChng = 1 + ** if( idx(1) != regPrev(1) ) goto chng_addr_1 + ** ... + ** regChng = N + ** goto chng_addr_N + ** + ** chng_addr_0: + ** regPrev(0) = idx(0) + ** chng_addr_1: + ** regPrev(1) = idx(1) + ** ... + ** + ** endDistinctTest: + ** regRowid = idx(rowid) + ** stat_push(P, regChng, regRowid) + ** Next csr + ** if !eof(csr) goto next_row; + ** + ** end_of_scan: + */ + + /* Make sure there are enough memory cells allocated to accommodate + ** the regPrev array and a trailing rowid (the rowid slot is required + ** when building a record to insert into the sample column of + ** the sqlite_stat4 table. */ + sqlite3TouchRegister(pParse, regPrev+nColTest); + + /* Open a read-only cursor on the index being analyzed. */ + assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) ); + sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + VdbeComment((v, "%s", pIdx->zName)); + + /* Invoke the stat_init() function. The arguments are: + ** + ** (1) the number of columns in the index including the rowid + ** (or for a WITHOUT ROWID table, the number of PK columns), + ** (2) the number of columns in the key without the rowid/pk + ** (3) estimated number of rows in the index, + */ + sqlite3VdbeAddOp2(v, OP_Integer, nCol, regStat+1); + assert( regRowid==regStat+2 ); + sqlite3VdbeAddOp2(v, OP_Integer, pIdx->nKeyCol, regRowid); +#ifdef SQLITE_ENABLE_STAT4 + if( OptimizationEnabled(db, SQLITE_Stat4) ){ + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regTemp); + addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur); + VdbeCoverage(v); + }else +#endif + { + addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_Count, iIdxCur, regTemp, 1); + } + assert( regTemp2==regStat+4 ); + sqlite3VdbeAddOp2(v, OP_Integer, db->nAnalysisLimit, regTemp2); + sqlite3VdbeAddFunctionCall(pParse, 0, regStat+1, regStat, 4, + &statInitFuncdef, 0); + + /* Implementation of the following: + ** + ** Rewind csr + ** if eof(csr) goto end_of_scan; + ** regChng = 0 + ** goto next_push_0; + ** + */ + sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng); + addrNextRow = sqlite3VdbeCurrentAddr(v); + + if( nColTest>0 ){ + int endDistinctTest = sqlite3VdbeMakeLabel(pParse); + int *aGotoChng; /* Array of jump instruction addresses */ + aGotoChng = sqlite3DbMallocRawNN(db, sizeof(int)*nColTest); + if( aGotoChng==0 ) continue; + + /* + ** next_row: + ** regChng = 0 + ** if( idx(0) != regPrev(0) ) goto chng_addr_0 + ** regChng = 1 + ** if( idx(1) != regPrev(1) ) goto chng_addr_1 + ** ... + ** regChng = N + ** goto endDistinctTest + */ + sqlite3VdbeAddOp0(v, OP_Goto); + addrNextRow = sqlite3VdbeCurrentAddr(v); + if( nColTest==1 && pIdx->nKeyCol==1 && IsUniqueIndex(pIdx) ){ + /* For a single-column UNIQUE index, once we have found a non-NULL + ** row, we know that all the rest will be distinct, so skip + ** subsequent distinctness tests. */ + sqlite3VdbeAddOp2(v, OP_NotNull, regPrev, endDistinctTest); + VdbeCoverage(v); + } + for(i=0; i<nColTest; i++){ + char *pColl = (char*)sqlite3LocateCollSeq(pParse, pIdx->azColl[i]); + sqlite3VdbeAddOp2(v, OP_Integer, i, regChng); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regTemp); + analyzeVdbeCommentIndexWithColumnName(v,pIdx,i); + aGotoChng[i] = + sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0, regPrev+i, pColl, P4_COLLSEQ); + sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); + VdbeCoverage(v); + } + sqlite3VdbeAddOp2(v, OP_Integer, nColTest, regChng); + sqlite3VdbeGoto(v, endDistinctTest); + + + /* + ** chng_addr_0: + ** regPrev(0) = idx(0) + ** chng_addr_1: + ** regPrev(1) = idx(1) + ** ... + */ + sqlite3VdbeJumpHere(v, addrNextRow-1); + for(i=0; i<nColTest; i++){ + sqlite3VdbeJumpHere(v, aGotoChng[i]); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regPrev+i); + analyzeVdbeCommentIndexWithColumnName(v,pIdx,i); + } + sqlite3VdbeResolveLabel(v, endDistinctTest); + sqlite3DbFree(db, aGotoChng); + } + + /* + ** chng_addr_N: + ** regRowid = idx(rowid) // STAT4 only + ** stat_push(P, regChng, regRowid) // 3rd parameter STAT4 only + ** Next csr + ** if !eof(csr) goto next_row; + */ +#ifdef SQLITE_ENABLE_STAT4 + if( OptimizationEnabled(db, SQLITE_Stat4) ){ + assert( regRowid==(regStat+2) ); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, regRowid); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable); + int j, k, regKey; + regKey = sqlite3GetTempRange(pParse, pPk->nKeyCol); + for(j=0; j<pPk->nKeyCol; j++){ + k = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[j]); + assert( k>=0 && k<pIdx->nColumn ); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, regKey+j); + analyzeVdbeCommentIndexWithColumnName(v,pIdx,k); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regKey, pPk->nKeyCol, regRowid); + sqlite3ReleaseTempRange(pParse, regKey, pPk->nKeyCol); + } + } +#endif + assert( regChng==(regStat+1) ); + { + sqlite3VdbeAddFunctionCall(pParse, 1, regStat, regTemp, 2+IsStat4, + &statPushFuncdef, 0); + if( db->nAnalysisLimit ){ + int j1, j2, j3; + j1 = sqlite3VdbeAddOp1(v, OP_IsNull, regTemp); VdbeCoverage(v); + j2 = sqlite3VdbeAddOp1(v, OP_If, regTemp); VdbeCoverage(v); + j3 = sqlite3VdbeAddOp4Int(v, OP_SeekGT, iIdxCur, 0, regPrev, 1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, j1); + sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, j2); + sqlite3VdbeJumpHere(v, j3); + }else{ + sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow); VdbeCoverage(v); + } + } + + /* Add the entry to the stat1 table. */ + callStatGet(pParse, regStat, STAT_GET_STAT1, regStat1); + assert( "BBB"[0]==SQLITE_AFF_TEXT ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0); + sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + sqlite3VdbeChangeP4(v, -1, (char*)pStat1, P4_TABLE); +#endif + sqlite3VdbeChangeP5(v, OPFLAG_APPEND); + + /* Add the entries to the stat4 table. */ +#ifdef SQLITE_ENABLE_STAT4 + if( OptimizationEnabled(db, SQLITE_Stat4) && db->nAnalysisLimit==0 ){ + int regEq = regStat1; + int regLt = regStat1+1; + int regDLt = regStat1+2; + int regSample = regStat1+3; + int regCol = regStat1+4; + int regSampleRowid = regCol + nCol; + int addrNext; + int addrIsNull; + u8 seekOp = HasRowid(pTab) ? OP_NotExists : OP_NotFound; + + if( doOnce ){ + int mxCol = nCol; + Index *pX; + + /* Compute the maximum number of columns in any index */ + for(pX=pTab->pIndex; pX; pX=pX->pNext){ + int nColX; /* Number of columns in pX */ + if( !HasRowid(pTab) && IsPrimaryKeyIndex(pX) ){ + nColX = pX->nKeyCol; + }else{ + nColX = pX->nColumn; + } + if( nColX>mxCol ) mxCol = nColX; + } + + /* Allocate space to compute results for the largest index */ + sqlite3TouchRegister(pParse, regCol+mxCol); + doOnce = 0; +#ifdef SQLITE_DEBUG + /* Verify that the call to sqlite3ClearTempRegCache() below + ** really is needed. + ** https://sqlite.org/forum/forumpost/83cb4a95a0 (2023-03-25) + */ + testcase( !sqlite3NoTempsInRange(pParse, regEq, regCol+mxCol) ); +#endif + sqlite3ClearTempRegCache(pParse); /* tag-20230325-1 */ + assert( sqlite3NoTempsInRange(pParse, regEq, regCol+mxCol) ); + } + assert( sqlite3NoTempsInRange(pParse, regEq, regCol+nCol) ); + + addrNext = sqlite3VdbeCurrentAddr(v); + callStatGet(pParse, regStat, STAT_GET_ROWID, regSampleRowid); + addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, regSampleRowid); + VdbeCoverage(v); + callStatGet(pParse, regStat, STAT_GET_NEQ, regEq); + callStatGet(pParse, regStat, STAT_GET_NLT, regLt); + callStatGet(pParse, regStat, STAT_GET_NDLT, regDLt); + sqlite3VdbeAddOp4Int(v, seekOp, iTabCur, addrNext, regSampleRowid, 0); + VdbeCoverage(v); + for(i=0; i<nCol; i++){ + sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iTabCur, i, regCol+i); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, nCol, regSample); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regTabname, 6, regTemp); + sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur+1, regNewRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iStatCur+1, regTemp, regNewRowid); + sqlite3VdbeAddOp2(v, OP_Goto, 1, addrNext); /* P1==1 for end-of-loop */ + sqlite3VdbeJumpHere(v, addrIsNull); + } +#endif /* SQLITE_ENABLE_STAT4 */ + + /* End of analysis */ + sqlite3VdbeJumpHere(v, addrRewind); + } + + + /* Create a single sqlite_stat1 entry containing NULL as the index + ** name and the row count as the content. + */ + if( pOnlyIdx==0 && needTableCnt ){ + VdbeComment((v, "%s", pTab->zName)); + sqlite3VdbeAddOp2(v, OP_Count, iTabCur, regStat1); + jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname); + assert( "BBB"[0]==SQLITE_AFF_TEXT ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0); + sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid); + sqlite3VdbeChangeP5(v, OPFLAG_APPEND); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + sqlite3VdbeChangeP4(v, -1, (char*)pStat1, P4_TABLE); +#endif + sqlite3VdbeJumpHere(v, jZeroRows); + } +} + + +/* +** Generate code that will cause the most recent index analysis to +** be loaded into internal hash tables where is can be used. +*/ +static void loadAnalysis(Parse *pParse, int iDb){ + Vdbe *v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3VdbeAddOp1(v, OP_LoadAnalysis, iDb); + } +} + +/* +** Generate code that will do an analysis of an entire database +*/ +static void analyzeDatabase(Parse *pParse, int iDb){ + sqlite3 *db = pParse->db; + Schema *pSchema = db->aDb[iDb].pSchema; /* Schema of database iDb */ + HashElem *k; + int iStatCur; + int iMem; + int iTab; + + sqlite3BeginWriteOperation(pParse, 0, iDb); + iStatCur = pParse->nTab; + pParse->nTab += 3; + openStatTable(pParse, iDb, iStatCur, 0, 0); + iMem = pParse->nMem+1; + iTab = pParse->nTab; + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ + Table *pTab = (Table*)sqliteHashData(k); + analyzeOneTable(pParse, pTab, 0, iStatCur, iMem, iTab); +#ifdef SQLITE_ENABLE_STAT4 + iMem = sqlite3FirstAvailableRegister(pParse, iMem); +#else + assert( iMem==sqlite3FirstAvailableRegister(pParse,iMem) ); +#endif + } + loadAnalysis(pParse, iDb); +} + +/* +** Generate code that will do an analysis of a single table in +** a database. If pOnlyIdx is not NULL then it is a single index +** in pTab that should be analyzed. +*/ +static void analyzeTable(Parse *pParse, Table *pTab, Index *pOnlyIdx){ + int iDb; + int iStatCur; + + assert( pTab!=0 ); + assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); + iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + sqlite3BeginWriteOperation(pParse, 0, iDb); + iStatCur = pParse->nTab; + pParse->nTab += 3; + if( pOnlyIdx ){ + openStatTable(pParse, iDb, iStatCur, pOnlyIdx->zName, "idx"); + }else{ + openStatTable(pParse, iDb, iStatCur, pTab->zName, "tbl"); + } + analyzeOneTable(pParse, pTab, pOnlyIdx, iStatCur,pParse->nMem+1,pParse->nTab); + loadAnalysis(pParse, iDb); +} + +/* +** Generate code for the ANALYZE command. The parser calls this routine +** when it recognizes an ANALYZE command. +** +** ANALYZE -- 1 +** ANALYZE <database> -- 2 +** ANALYZE ?<database>.?<tablename> -- 3 +** +** Form 1 causes all indices in all attached databases to be analyzed. +** Form 2 analyzes all indices the single database named. +** Form 3 analyzes all indices associated with the named table. +*/ +SQLITE_PRIVATE void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){ + sqlite3 *db = pParse->db; + int iDb; + int i; + char *z, *zDb; + Table *pTab; + Index *pIdx; + Token *pTableName; + Vdbe *v; + + /* Read the database schema. If an error occurs, leave an error message + ** and code in pParse and return NULL. */ + assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ + return; + } + + assert( pName2!=0 || pName1==0 ); + if( pName1==0 ){ + /* Form 1: Analyze everything */ + for(i=0; i<db->nDb; i++){ + if( i==1 ) continue; /* Do not analyze the TEMP database */ + analyzeDatabase(pParse, i); + } + }else if( pName2->n==0 && (iDb = sqlite3FindDb(db, pName1))>=0 ){ + /* Analyze the schema named as the argument */ + analyzeDatabase(pParse, iDb); + }else{ + /* Form 3: Analyze the table or index named as an argument */ + iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pTableName); + if( iDb>=0 ){ + zDb = pName2->n ? db->aDb[iDb].zDbSName : 0; + z = sqlite3NameFromToken(db, pTableName); + if( z ){ + if( (pIdx = sqlite3FindIndex(db, z, zDb))!=0 ){ + analyzeTable(pParse, pIdx->pTable, pIdx); + }else if( (pTab = sqlite3LocateTable(pParse, 0, z, zDb))!=0 ){ + analyzeTable(pParse, pTab, 0); + } + sqlite3DbFree(db, z); + } + } + } + if( db->nSqlExec==0 && (v = sqlite3GetVdbe(pParse))!=0 ){ + sqlite3VdbeAddOp0(v, OP_Expire); + } +} + +/* +** Used to pass information from the analyzer reader through to the +** callback routine. +*/ +typedef struct analysisInfo analysisInfo; +struct analysisInfo { + sqlite3 *db; + const char *zDatabase; +}; + +/* +** The first argument points to a nul-terminated string containing a +** list of space separated integers. Read the first nOut of these into +** the array aOut[]. +*/ +static void decodeIntArray( + char *zIntArray, /* String containing int array to decode */ + int nOut, /* Number of slots in aOut[] */ + tRowcnt *aOut, /* Store integers here */ + LogEst *aLog, /* Or, if aOut==0, here */ + Index *pIndex /* Handle extra flags for this index, if not NULL */ +){ + char *z = zIntArray; + int c; + int i; + tRowcnt v; + +#ifdef SQLITE_ENABLE_STAT4 + if( z==0 ) z = ""; +#else + assert( z!=0 ); +#endif + for(i=0; *z && i<nOut; i++){ + v = 0; + while( (c=z[0])>='0' && c<='9' ){ + v = v*10 + c - '0'; + z++; + } +#ifdef SQLITE_ENABLE_STAT4 + if( aOut ) aOut[i] = v; + if( aLog ) aLog[i] = sqlite3LogEst(v); +#else + assert( aOut==0 ); + UNUSED_PARAMETER(aOut); + assert( aLog!=0 ); + aLog[i] = sqlite3LogEst(v); +#endif + if( *z==' ' ) z++; + } +#ifndef SQLITE_ENABLE_STAT4 + assert( pIndex!=0 ); { +#else + if( pIndex ){ +#endif + pIndex->bUnordered = 0; + pIndex->noSkipScan = 0; + while( z[0] ){ + if( sqlite3_strglob("unordered*", z)==0 ){ + pIndex->bUnordered = 1; + }else if( sqlite3_strglob("sz=[0-9]*", z)==0 ){ + int sz = sqlite3Atoi(z+3); + if( sz<2 ) sz = 2; + pIndex->szIdxRow = sqlite3LogEst(sz); + }else if( sqlite3_strglob("noskipscan*", z)==0 ){ + pIndex->noSkipScan = 1; + } +#ifdef SQLITE_ENABLE_COSTMULT + else if( sqlite3_strglob("costmult=[0-9]*",z)==0 ){ + pIndex->pTable->costMult = sqlite3LogEst(sqlite3Atoi(z+9)); + } +#endif + while( z[0]!=0 && z[0]!=' ' ) z++; + while( z[0]==' ' ) z++; + } + } +} + +/* +** This callback is invoked once for each index when reading the +** sqlite_stat1 table. +** +** argv[0] = name of the table +** argv[1] = name of the index (might be NULL) +** argv[2] = results of analysis - on integer for each column +** +** Entries for which argv[1]==NULL simply record the number of rows in +** the table. +*/ +static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){ + analysisInfo *pInfo = (analysisInfo*)pData; + Index *pIndex; + Table *pTable; + const char *z; + + assert( argc==3 ); + UNUSED_PARAMETER2(NotUsed, argc); + + if( argv==0 || argv[0]==0 || argv[2]==0 ){ + return 0; + } + pTable = sqlite3FindTable(pInfo->db, argv[0], pInfo->zDatabase); + if( pTable==0 ){ + return 0; + } + if( argv[1]==0 ){ + pIndex = 0; + }else if( sqlite3_stricmp(argv[0],argv[1])==0 ){ + pIndex = sqlite3PrimaryKeyIndex(pTable); + }else{ + pIndex = sqlite3FindIndex(pInfo->db, argv[1], pInfo->zDatabase); + } + z = argv[2]; + + if( pIndex ){ + tRowcnt *aiRowEst = 0; + int nCol = pIndex->nKeyCol+1; +#ifdef SQLITE_ENABLE_STAT4 + /* Index.aiRowEst may already be set here if there are duplicate + ** sqlite_stat1 entries for this index. In that case just clobber + ** the old data with the new instead of allocating a new array. */ + if( pIndex->aiRowEst==0 ){ + pIndex->aiRowEst = (tRowcnt*)sqlite3MallocZero(sizeof(tRowcnt) * nCol); + if( pIndex->aiRowEst==0 ) sqlite3OomFault(pInfo->db); + } + aiRowEst = pIndex->aiRowEst; +#endif + pIndex->bUnordered = 0; + decodeIntArray((char*)z, nCol, aiRowEst, pIndex->aiRowLogEst, pIndex); + pIndex->hasStat1 = 1; + if( pIndex->pPartIdxWhere==0 ){ + pTable->nRowLogEst = pIndex->aiRowLogEst[0]; + pTable->tabFlags |= TF_HasStat1; + } + }else{ + Index fakeIdx; + fakeIdx.szIdxRow = pTable->szTabRow; +#ifdef SQLITE_ENABLE_COSTMULT + fakeIdx.pTable = pTable; +#endif + decodeIntArray((char*)z, 1, 0, &pTable->nRowLogEst, &fakeIdx); + pTable->szTabRow = fakeIdx.szIdxRow; + pTable->tabFlags |= TF_HasStat1; + } + + return 0; +} + +/* +** If the Index.aSample variable is not NULL, delete the aSample[] array +** and its contents. +*/ +SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){ + assert( db!=0 ); + assert( pIdx!=0 ); +#ifdef SQLITE_ENABLE_STAT4 + if( pIdx->aSample ){ + int j; + for(j=0; j<pIdx->nSample; j++){ + IndexSample *p = &pIdx->aSample[j]; + sqlite3DbFree(db, p->p); + } + sqlite3DbFree(db, pIdx->aSample); + } + if( db->pnBytesFreed==0 ){ + pIdx->nSample = 0; + pIdx->aSample = 0; + } +#else + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(pIdx); +#endif /* SQLITE_ENABLE_STAT4 */ +} + +#ifdef SQLITE_ENABLE_STAT4 +/* +** Populate the pIdx->aAvgEq[] array based on the samples currently +** stored in pIdx->aSample[]. +*/ +static void initAvgEq(Index *pIdx){ + if( pIdx ){ + IndexSample *aSample = pIdx->aSample; + IndexSample *pFinal = &aSample[pIdx->nSample-1]; + int iCol; + int nCol = 1; + if( pIdx->nSampleCol>1 ){ + /* If this is stat4 data, then calculate aAvgEq[] values for all + ** sample columns except the last. The last is always set to 1, as + ** once the trailing PK fields are considered all index keys are + ** unique. */ + nCol = pIdx->nSampleCol-1; + pIdx->aAvgEq[nCol] = 1; + } + for(iCol=0; iCol<nCol; iCol++){ + int nSample = pIdx->nSample; + int i; /* Used to iterate through samples */ + tRowcnt sumEq = 0; /* Sum of the nEq values */ + tRowcnt avgEq = 0; + tRowcnt nRow; /* Number of rows in index */ + i64 nSum100 = 0; /* Number of terms contributing to sumEq */ + i64 nDist100; /* Number of distinct values in index */ + + if( !pIdx->aiRowEst || iCol>=pIdx->nKeyCol || pIdx->aiRowEst[iCol+1]==0 ){ + nRow = pFinal->anLt[iCol]; + nDist100 = (i64)100 * pFinal->anDLt[iCol]; + nSample--; + }else{ + nRow = pIdx->aiRowEst[0]; + nDist100 = ((i64)100 * pIdx->aiRowEst[0]) / pIdx->aiRowEst[iCol+1]; + } + pIdx->nRowEst0 = nRow; + + /* Set nSum to the number of distinct (iCol+1) field prefixes that + ** occur in the stat4 table for this index. Set sumEq to the sum of + ** the nEq values for column iCol for the same set (adding the value + ** only once where there exist duplicate prefixes). */ + for(i=0; i<nSample; i++){ + if( i==(pIdx->nSample-1) + || aSample[i].anDLt[iCol]!=aSample[i+1].anDLt[iCol] + ){ + sumEq += aSample[i].anEq[iCol]; + nSum100 += 100; + } + } + + if( nDist100>nSum100 && sumEq<nRow ){ + avgEq = ((i64)100 * (nRow - sumEq))/(nDist100 - nSum100); + } + if( avgEq==0 ) avgEq = 1; + pIdx->aAvgEq[iCol] = avgEq; + } + } +} + +/* +** Look up an index by name. Or, if the name of a WITHOUT ROWID table +** is supplied instead, find the PRIMARY KEY index for that table. +*/ +static Index *findIndexOrPrimaryKey( + sqlite3 *db, + const char *zName, + const char *zDb +){ + Index *pIdx = sqlite3FindIndex(db, zName, zDb); + if( pIdx==0 ){ + Table *pTab = sqlite3FindTable(db, zName, zDb); + if( pTab && !HasRowid(pTab) ) pIdx = sqlite3PrimaryKeyIndex(pTab); + } + return pIdx; +} + +/* +** Load the content from either the sqlite_stat4 +** into the relevant Index.aSample[] arrays. +** +** Arguments zSql1 and zSql2 must point to SQL statements that return +** data equivalent to the following: +** +** zSql1: SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx +** zSql2: SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4 +** +** where %Q is replaced with the database name before the SQL is executed. +*/ +static int loadStatTbl( + sqlite3 *db, /* Database handle */ + const char *zSql1, /* SQL statement 1 (see above) */ + const char *zSql2, /* SQL statement 2 (see above) */ + const char *zDb /* Database name (e.g. "main") */ +){ + int rc; /* Result codes from subroutines */ + sqlite3_stmt *pStmt = 0; /* An SQL statement being run */ + char *zSql; /* Text of the SQL statement */ + Index *pPrevIdx = 0; /* Previous index in the loop */ + IndexSample *pSample; /* A slot in pIdx->aSample[] */ + + assert( db->lookaside.bDisable ); + zSql = sqlite3MPrintf(db, zSql1, zDb); + if( !zSql ){ + return SQLITE_NOMEM_BKPT; + } + rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + sqlite3DbFree(db, zSql); + if( rc ) return rc; + + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + int nIdxCol = 1; /* Number of columns in stat4 records */ + + char *zIndex; /* Index name */ + Index *pIdx; /* Pointer to the index object */ + int nSample; /* Number of samples */ + int nByte; /* Bytes of space required */ + int i; /* Bytes of space required */ + tRowcnt *pSpace; + + zIndex = (char *)sqlite3_column_text(pStmt, 0); + if( zIndex==0 ) continue; + nSample = sqlite3_column_int(pStmt, 1); + pIdx = findIndexOrPrimaryKey(db, zIndex, zDb); + assert( pIdx==0 || pIdx->nSample==0 ); + if( pIdx==0 ) continue; + if( pIdx->aSample!=0 ){ + /* The same index appears in sqlite_stat4 under multiple names */ + continue; + } + assert( !HasRowid(pIdx->pTable) || pIdx->nColumn==pIdx->nKeyCol+1 ); + if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){ + nIdxCol = pIdx->nKeyCol; + }else{ + nIdxCol = pIdx->nColumn; + } + pIdx->nSampleCol = nIdxCol; + pIdx->mxSample = nSample; + nByte = sizeof(IndexSample) * nSample; + nByte += sizeof(tRowcnt) * nIdxCol * 3 * nSample; + nByte += nIdxCol * sizeof(tRowcnt); /* Space for Index.aAvgEq[] */ + + pIdx->aSample = sqlite3DbMallocZero(db, nByte); + if( pIdx->aSample==0 ){ + sqlite3_finalize(pStmt); + return SQLITE_NOMEM_BKPT; + } + pSpace = (tRowcnt*)&pIdx->aSample[nSample]; + pIdx->aAvgEq = pSpace; pSpace += nIdxCol; + pIdx->pTable->tabFlags |= TF_HasStat4; + for(i=0; i<nSample; i++){ + pIdx->aSample[i].anEq = pSpace; pSpace += nIdxCol; + pIdx->aSample[i].anLt = pSpace; pSpace += nIdxCol; + pIdx->aSample[i].anDLt = pSpace; pSpace += nIdxCol; + } + assert( ((u8*)pSpace)-nByte==(u8*)(pIdx->aSample) ); + } + rc = sqlite3_finalize(pStmt); + if( rc ) return rc; + + zSql = sqlite3MPrintf(db, zSql2, zDb); + if( !zSql ){ + return SQLITE_NOMEM_BKPT; + } + rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + sqlite3DbFree(db, zSql); + if( rc ) return rc; + + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + char *zIndex; /* Index name */ + Index *pIdx; /* Pointer to the index object */ + int nCol = 1; /* Number of columns in index */ + + zIndex = (char *)sqlite3_column_text(pStmt, 0); + if( zIndex==0 ) continue; + pIdx = findIndexOrPrimaryKey(db, zIndex, zDb); + if( pIdx==0 ) continue; + if( pIdx->nSample>=pIdx->mxSample ){ + /* Too many slots used because the same index appears in + ** sqlite_stat4 using multiple names */ + continue; + } + /* This next condition is true if data has already been loaded from + ** the sqlite_stat4 table. */ + nCol = pIdx->nSampleCol; + if( pIdx!=pPrevIdx ){ + initAvgEq(pPrevIdx); + pPrevIdx = pIdx; + } + pSample = &pIdx->aSample[pIdx->nSample]; + decodeIntArray((char*)sqlite3_column_text(pStmt,1),nCol,pSample->anEq,0,0); + decodeIntArray((char*)sqlite3_column_text(pStmt,2),nCol,pSample->anLt,0,0); + decodeIntArray((char*)sqlite3_column_text(pStmt,3),nCol,pSample->anDLt,0,0); + + /* Take a copy of the sample. Add 8 extra 0x00 bytes the end of the buffer. + ** This is in case the sample record is corrupted. In that case, the + ** sqlite3VdbeRecordCompare() may read up to two varints past the + ** end of the allocated buffer before it realizes it is dealing with + ** a corrupt record. Or it might try to read a large integer from the + ** buffer. In any case, eight 0x00 bytes prevents this from causing + ** a buffer overread. */ + pSample->n = sqlite3_column_bytes(pStmt, 4); + pSample->p = sqlite3DbMallocZero(db, pSample->n + 8); + if( pSample->p==0 ){ + sqlite3_finalize(pStmt); + return SQLITE_NOMEM_BKPT; + } + if( pSample->n ){ + memcpy(pSample->p, sqlite3_column_blob(pStmt, 4), pSample->n); + } + pIdx->nSample++; + } + rc = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ) initAvgEq(pPrevIdx); + return rc; +} + +/* +** Load content from the sqlite_stat4 table into +** the Index.aSample[] arrays of all indices. +*/ +static int loadStat4(sqlite3 *db, const char *zDb){ + int rc = SQLITE_OK; /* Result codes from subroutines */ + const Table *pStat4; + + assert( db->lookaside.bDisable ); + if( OptimizationEnabled(db, SQLITE_Stat4) + && (pStat4 = sqlite3FindTable(db, "sqlite_stat4", zDb))!=0 + && IsOrdinaryTable(pStat4) + ){ + rc = loadStatTbl(db, + "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx COLLATE nocase", + "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4", + zDb + ); + } + return rc; +} +#endif /* SQLITE_ENABLE_STAT4 */ + +/* +** Load the content of the sqlite_stat1 and sqlite_stat4 tables. The +** contents of sqlite_stat1 are used to populate the Index.aiRowEst[] +** arrays. The contents of sqlite_stat4 are used to populate the +** Index.aSample[] arrays. +** +** If the sqlite_stat1 table is not present in the database, SQLITE_ERROR +** is returned. In this case, even if SQLITE_ENABLE_STAT4 was defined +** during compilation and the sqlite_stat4 table is present, no data is +** read from it. +** +** If SQLITE_ENABLE_STAT4 was defined during compilation and the +** sqlite_stat4 table is not present in the database, SQLITE_ERROR is +** returned. However, in this case, data is read from the sqlite_stat1 +** table (if it is present) before returning. +** +** If an OOM error occurs, this function always sets db->mallocFailed. +** This means if the caller does not care about other errors, the return +** code may be ignored. +*/ +SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ + analysisInfo sInfo; + HashElem *i; + char *zSql; + int rc = SQLITE_OK; + Schema *pSchema = db->aDb[iDb].pSchema; + const Table *pStat1; + + assert( iDb>=0 && iDb<db->nDb ); + assert( db->aDb[iDb].pBt!=0 ); + + /* Clear any prior statistics */ + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + for(i=sqliteHashFirst(&pSchema->tblHash); i; i=sqliteHashNext(i)){ + Table *pTab = sqliteHashData(i); + pTab->tabFlags &= ~TF_HasStat1; + } + for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){ + Index *pIdx = sqliteHashData(i); + pIdx->hasStat1 = 0; +#ifdef SQLITE_ENABLE_STAT4 + sqlite3DeleteIndexSamples(db, pIdx); + pIdx->aSample = 0; +#endif + } + + /* Load new statistics out of the sqlite_stat1 table */ + sInfo.db = db; + sInfo.zDatabase = db->aDb[iDb].zDbSName; + if( (pStat1 = sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)) + && IsOrdinaryTable(pStat1) + ){ + zSql = sqlite3MPrintf(db, + "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase); + if( zSql==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + rc = sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0); + sqlite3DbFree(db, zSql); + } + } + + /* Set appropriate defaults on all indexes not in the sqlite_stat1 table */ + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){ + Index *pIdx = sqliteHashData(i); + if( !pIdx->hasStat1 ) sqlite3DefaultRowEst(pIdx); + } + + /* Load the statistics from the sqlite_stat4 table. */ +#ifdef SQLITE_ENABLE_STAT4 + if( rc==SQLITE_OK ){ + DisableLookaside; + rc = loadStat4(db, sInfo.zDatabase); + EnableLookaside; + } + for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){ + Index *pIdx = sqliteHashData(i); + sqlite3_free(pIdx->aiRowEst); + pIdx->aiRowEst = 0; + } +#endif + + if( rc==SQLITE_NOMEM ){ + sqlite3OomFault(db); + } + return rc; +} + + +#endif /* SQLITE_OMIT_ANALYZE */ + +/************** End of analyze.c *********************************************/ +/************** Begin file attach.c ******************************************/ +/* +** 2003 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the ATTACH and DETACH commands. +*/ +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_OMIT_ATTACH +/* +** Resolve an expression that was part of an ATTACH or DETACH statement. This +** is slightly different from resolving a normal SQL expression, because simple +** identifiers are treated as strings, not possible column names or aliases. +** +** i.e. if the parser sees: +** +** ATTACH DATABASE abc AS def +** +** it treats the two expressions as literal strings 'abc' and 'def' instead of +** looking for columns of the same name. +** +** This only applies to the root node of pExpr, so the statement: +** +** ATTACH DATABASE abc||def AS 'db2' +** +** will fail because neither abc or def can be resolved. +*/ +static int resolveAttachExpr(NameContext *pName, Expr *pExpr) +{ + int rc = SQLITE_OK; + if( pExpr ){ + if( pExpr->op!=TK_ID ){ + rc = sqlite3ResolveExprNames(pName, pExpr); + }else{ + pExpr->op = TK_STRING; + } + } + return rc; +} + +/* +** Return true if zName points to a name that may be used to refer to +** database iDb attached to handle db. +*/ +SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName){ + return ( + sqlite3StrICmp(db->aDb[iDb].zDbSName, zName)==0 + || (iDb==0 && sqlite3StrICmp("main", zName)==0) + ); +} + +/* +** An SQL user-function registered to do the work of an ATTACH statement. The +** three arguments to the function come directly from an attach statement: +** +** ATTACH DATABASE x AS y KEY z +** +** SELECT sqlite_attach(x, y, z) +** +** If the optional "KEY z" syntax is omitted, an SQL NULL is passed as the +** third argument. +** +** If the db->init.reopenMemdb flags is set, then instead of attaching a +** new database, close the database on db->init.iDb and reopen it as an +** empty MemDB. +*/ +static void attachFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + int i; + int rc = 0; + sqlite3 *db = sqlite3_context_db_handle(context); + const char *zName; + const char *zFile; + char *zPath = 0; + char *zErr = 0; + unsigned int flags; + Db *aNew; /* New array of Db pointers */ + Db *pNew = 0; /* Db object for the newly attached database */ + char *zErrDyn = 0; + sqlite3_vfs *pVfs; + + UNUSED_PARAMETER(NotUsed); + zFile = (const char *)sqlite3_value_text(argv[0]); + zName = (const char *)sqlite3_value_text(argv[1]); + if( zFile==0 ) zFile = ""; + if( zName==0 ) zName = ""; + +#ifndef SQLITE_OMIT_DESERIALIZE +# define REOPEN_AS_MEMDB(db) (db->init.reopenMemdb) +#else +# define REOPEN_AS_MEMDB(db) (0) +#endif + + if( REOPEN_AS_MEMDB(db) ){ + /* This is not a real ATTACH. Instead, this routine is being called + ** from sqlite3_deserialize() to close database db->init.iDb and + ** reopen it as a MemDB */ + Btree *pNewBt = 0; + pVfs = sqlite3_vfs_find("memdb"); + if( pVfs==0 ) return; + rc = sqlite3BtreeOpen(pVfs, "x\0", db, &pNewBt, 0, SQLITE_OPEN_MAIN_DB); + if( rc==SQLITE_OK ){ + Schema *pNewSchema = sqlite3SchemaGet(db, pNewBt); + if( pNewSchema ){ + /* Both the Btree and the new Schema were allocated successfully. + ** Close the old db and update the aDb[] slot with the new memdb + ** values. */ + pNew = &db->aDb[db->init.iDb]; + if( ALWAYS(pNew->pBt) ) sqlite3BtreeClose(pNew->pBt); + pNew->pBt = pNewBt; + pNew->pSchema = pNewSchema; + }else{ + sqlite3BtreeClose(pNewBt); + rc = SQLITE_NOMEM; + } + } + if( rc ) goto attach_error; + }else{ + /* This is a real ATTACH + ** + ** Check for the following errors: + ** + ** * Too many attached databases, + ** * Transaction currently open + ** * Specified database name already being used. + */ + if( db->nDb>=db->aLimit[SQLITE_LIMIT_ATTACHED]+2 ){ + zErrDyn = sqlite3MPrintf(db, "too many attached databases - max %d", + db->aLimit[SQLITE_LIMIT_ATTACHED] + ); + goto attach_error; + } + for(i=0; i<db->nDb; i++){ + assert( zName ); + if( sqlite3DbIsNamed(db, i, zName) ){ + zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName); + goto attach_error; + } + } + + /* Allocate the new entry in the db->aDb[] array and initialize the schema + ** hash tables. + */ + if( db->aDb==db->aDbStatic ){ + aNew = sqlite3DbMallocRawNN(db, sizeof(db->aDb[0])*3 ); + if( aNew==0 ) return; + memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2); + }else{ + aNew = sqlite3DbRealloc(db, db->aDb, sizeof(db->aDb[0])*(db->nDb+1) ); + if( aNew==0 ) return; + } + db->aDb = aNew; + pNew = &db->aDb[db->nDb]; + memset(pNew, 0, sizeof(*pNew)); + + /* Open the database file. If the btree is successfully opened, use + ** it to obtain the database schema. At this point the schema may + ** or may not be initialized. + */ + flags = db->openFlags; + rc = sqlite3ParseUri(db->pVfs->zName, zFile, &flags, &pVfs, &zPath, &zErr); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_NOMEM ) sqlite3OomFault(db); + sqlite3_result_error(context, zErr, -1); + sqlite3_free(zErr); + return; + } + assert( pVfs ); + flags |= SQLITE_OPEN_MAIN_DB; + rc = sqlite3BtreeOpen(pVfs, zPath, db, &pNew->pBt, 0, flags); + db->nDb++; + pNew->zDbSName = sqlite3DbStrDup(db, zName); + } + db->noSharedCache = 0; + if( rc==SQLITE_CONSTRAINT ){ + rc = SQLITE_ERROR; + zErrDyn = sqlite3MPrintf(db, "database is already attached"); + }else if( rc==SQLITE_OK ){ + Pager *pPager; + pNew->pSchema = sqlite3SchemaGet(db, pNew->pBt); + if( !pNew->pSchema ){ + rc = SQLITE_NOMEM_BKPT; + }else if( pNew->pSchema->file_format && pNew->pSchema->enc!=ENC(db) ){ + zErrDyn = sqlite3MPrintf(db, + "attached databases must use the same text encoding as main database"); + rc = SQLITE_ERROR; + } + sqlite3BtreeEnter(pNew->pBt); + pPager = sqlite3BtreePager(pNew->pBt); + sqlite3PagerLockingMode(pPager, db->dfltLockMode); + sqlite3BtreeSecureDelete(pNew->pBt, + sqlite3BtreeSecureDelete(db->aDb[0].pBt,-1) ); +#ifndef SQLITE_OMIT_PAGER_PRAGMAS + sqlite3BtreeSetPagerFlags(pNew->pBt, + PAGER_SYNCHRONOUS_FULL | (db->flags & PAGER_FLAGS_MASK)); +#endif + sqlite3BtreeLeave(pNew->pBt); + } + pNew->safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; + if( rc==SQLITE_OK && pNew->zDbSName==0 ){ + rc = SQLITE_NOMEM_BKPT; + } + sqlite3_free_filename( zPath ); + + /* If the file was opened successfully, read the schema for the new database. + ** If this fails, or if opening the file failed, then close the file and + ** remove the entry from the db->aDb[] array. i.e. put everything back the + ** way we found it. + */ + if( rc==SQLITE_OK ){ + sqlite3BtreeEnterAll(db); + db->init.iDb = 0; + db->mDbFlags &= ~(DBFLAG_SchemaKnownOk); + if( !REOPEN_AS_MEMDB(db) ){ + rc = sqlite3Init(db, &zErrDyn); + } + sqlite3BtreeLeaveAll(db); + assert( zErrDyn==0 || rc!=SQLITE_OK ); + } +#ifdef SQLITE_USER_AUTHENTICATION + if( rc==SQLITE_OK && !REOPEN_AS_MEMDB(db) ){ + u8 newAuth = 0; + rc = sqlite3UserAuthCheckLogin(db, zName, &newAuth); + if( newAuth<db->auth.authLevel ){ + rc = SQLITE_AUTH_USER; + } + } +#endif + if( rc ){ + if( ALWAYS(!REOPEN_AS_MEMDB(db)) ){ + int iDb = db->nDb - 1; + assert( iDb>=2 ); + if( db->aDb[iDb].pBt ){ + sqlite3BtreeClose(db->aDb[iDb].pBt); + db->aDb[iDb].pBt = 0; + db->aDb[iDb].pSchema = 0; + } + sqlite3ResetAllSchemasOfConnection(db); + db->nDb = iDb; + if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){ + sqlite3OomFault(db); + sqlite3DbFree(db, zErrDyn); + zErrDyn = sqlite3MPrintf(db, "out of memory"); + }else if( zErrDyn==0 ){ + zErrDyn = sqlite3MPrintf(db, "unable to open database: %s", zFile); + } + } + goto attach_error; + } + + return; + +attach_error: + /* Return an error if we get here */ + if( zErrDyn ){ + sqlite3_result_error(context, zErrDyn, -1); + sqlite3DbFree(db, zErrDyn); + } + if( rc ) sqlite3_result_error_code(context, rc); +} + +/* +** An SQL user-function registered to do the work of an DETACH statement. The +** three arguments to the function come directly from a detach statement: +** +** DETACH DATABASE x +** +** SELECT sqlite_detach(x) +*/ +static void detachFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + const char *zName = (const char *)sqlite3_value_text(argv[0]); + sqlite3 *db = sqlite3_context_db_handle(context); + int i; + Db *pDb = 0; + HashElem *pEntry; + char zErr[128]; + + UNUSED_PARAMETER(NotUsed); + + if( zName==0 ) zName = ""; + for(i=0; i<db->nDb; i++){ + pDb = &db->aDb[i]; + if( pDb->pBt==0 ) continue; + if( sqlite3DbIsNamed(db, i, zName) ) break; + } + + if( i>=db->nDb ){ + sqlite3_snprintf(sizeof(zErr),zErr, "no such database: %s", zName); + goto detach_error; + } + if( i<2 ){ + sqlite3_snprintf(sizeof(zErr),zErr, "cannot detach database %s", zName); + goto detach_error; + } + if( sqlite3BtreeTxnState(pDb->pBt)!=SQLITE_TXN_NONE + || sqlite3BtreeIsInBackup(pDb->pBt) + ){ + sqlite3_snprintf(sizeof(zErr),zErr, "database %s is locked", zName); + goto detach_error; + } + + /* If any TEMP triggers reference the schema being detached, move those + ** triggers to reference the TEMP schema itself. */ + assert( db->aDb[1].pSchema ); + pEntry = sqliteHashFirst(&db->aDb[1].pSchema->trigHash); + while( pEntry ){ + Trigger *pTrig = (Trigger*)sqliteHashData(pEntry); + if( pTrig->pTabSchema==pDb->pSchema ){ + pTrig->pTabSchema = pTrig->pSchema; + } + pEntry = sqliteHashNext(pEntry); + } + + sqlite3BtreeClose(pDb->pBt); + pDb->pBt = 0; + pDb->pSchema = 0; + sqlite3CollapseDatabaseArray(db); + return; + +detach_error: + sqlite3_result_error(context, zErr, -1); +} + +/* +** This procedure generates VDBE code for a single invocation of either the +** sqlite_detach() or sqlite_attach() SQL user functions. +*/ +static void codeAttach( + Parse *pParse, /* The parser context */ + int type, /* Either SQLITE_ATTACH or SQLITE_DETACH */ + FuncDef const *pFunc,/* FuncDef wrapper for detachFunc() or attachFunc() */ + Expr *pAuthArg, /* Expression to pass to authorization callback */ + Expr *pFilename, /* Name of database file */ + Expr *pDbname, /* Name of the database to use internally */ + Expr *pKey /* Database key for encryption extension */ +){ + int rc; + NameContext sName; + Vdbe *v; + sqlite3* db = pParse->db; + int regArgs; + + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) goto attach_end; + + if( pParse->nErr ) goto attach_end; + memset(&sName, 0, sizeof(NameContext)); + sName.pParse = pParse; + + if( + SQLITE_OK!=resolveAttachExpr(&sName, pFilename) || + SQLITE_OK!=resolveAttachExpr(&sName, pDbname) || + SQLITE_OK!=resolveAttachExpr(&sName, pKey) + ){ + goto attach_end; + } + +#ifndef SQLITE_OMIT_AUTHORIZATION + if( ALWAYS(pAuthArg) ){ + char *zAuthArg; + if( pAuthArg->op==TK_STRING ){ + assert( !ExprHasProperty(pAuthArg, EP_IntValue) ); + zAuthArg = pAuthArg->u.zToken; + }else{ + zAuthArg = 0; + } + rc = sqlite3AuthCheck(pParse, type, zAuthArg, 0, 0); + if(rc!=SQLITE_OK ){ + goto attach_end; + } + } +#endif /* SQLITE_OMIT_AUTHORIZATION */ + + + v = sqlite3GetVdbe(pParse); + regArgs = sqlite3GetTempRange(pParse, 4); + sqlite3ExprCode(pParse, pFilename, regArgs); + sqlite3ExprCode(pParse, pDbname, regArgs+1); + sqlite3ExprCode(pParse, pKey, regArgs+2); + + assert( v || db->mallocFailed ); + if( v ){ + sqlite3VdbeAddFunctionCall(pParse, 0, regArgs+3-pFunc->nArg, regArgs+3, + pFunc->nArg, pFunc, 0); + /* Code an OP_Expire. For an ATTACH statement, set P1 to true (expire this + ** statement only). For DETACH, set it to false (expire all existing + ** statements). + */ + sqlite3VdbeAddOp1(v, OP_Expire, (type==SQLITE_ATTACH)); + } + +attach_end: + sqlite3ExprDelete(db, pFilename); + sqlite3ExprDelete(db, pDbname); + sqlite3ExprDelete(db, pKey); +} + +/* +** Called by the parser to compile a DETACH statement. +** +** DETACH pDbname +*/ +SQLITE_PRIVATE void sqlite3Detach(Parse *pParse, Expr *pDbname){ + static const FuncDef detach_func = { + 1, /* nArg */ + SQLITE_UTF8, /* funcFlags */ + 0, /* pUserData */ + 0, /* pNext */ + detachFunc, /* xSFunc */ + 0, /* xFinalize */ + 0, 0, /* xValue, xInverse */ + "sqlite_detach", /* zName */ + {0} + }; + codeAttach(pParse, SQLITE_DETACH, &detach_func, pDbname, 0, 0, pDbname); +} + +/* +** Called by the parser to compile an ATTACH statement. +** +** ATTACH p AS pDbname KEY pKey +*/ +SQLITE_PRIVATE void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *pKey){ + static const FuncDef attach_func = { + 3, /* nArg */ + SQLITE_UTF8, /* funcFlags */ + 0, /* pUserData */ + 0, /* pNext */ + attachFunc, /* xSFunc */ + 0, /* xFinalize */ + 0, 0, /* xValue, xInverse */ + "sqlite_attach", /* zName */ + {0} + }; + codeAttach(pParse, SQLITE_ATTACH, &attach_func, p, p, pDbname, pKey); +} +#endif /* SQLITE_OMIT_ATTACH */ + +/* +** Expression callback used by sqlite3FixAAAA() routines. +*/ +static int fixExprCb(Walker *p, Expr *pExpr){ + DbFixer *pFix = p->u.pFix; + if( !pFix->bTemp ) ExprSetProperty(pExpr, EP_FromDDL); + if( pExpr->op==TK_VARIABLE ){ + if( pFix->pParse->db->init.busy ){ + pExpr->op = TK_NULL; + }else{ + sqlite3ErrorMsg(pFix->pParse, "%s cannot use variables", pFix->zType); + return WRC_Abort; + } + } + return WRC_Continue; +} + +/* +** Select callback used by sqlite3FixAAAA() routines. +*/ +static int fixSelectCb(Walker *p, Select *pSelect){ + DbFixer *pFix = p->u.pFix; + int i; + SrcItem *pItem; + sqlite3 *db = pFix->pParse->db; + int iDb = sqlite3FindDbName(db, pFix->zDb); + SrcList *pList = pSelect->pSrc; + + if( NEVER(pList==0) ) return WRC_Continue; + for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){ + if( pFix->bTemp==0 ){ + if( pItem->zDatabase ){ + if( iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){ + sqlite3ErrorMsg(pFix->pParse, + "%s %T cannot reference objects in database %s", + pFix->zType, pFix->pName, pItem->zDatabase); + return WRC_Abort; + } + sqlite3DbFree(db, pItem->zDatabase); + pItem->zDatabase = 0; + pItem->fg.notCte = 1; + } + pItem->pSchema = pFix->pSchema; + pItem->fg.fromDDL = 1; + } +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) + if( pList->a[i].fg.isUsing==0 + && sqlite3WalkExpr(&pFix->w, pList->a[i].u3.pOn) + ){ + return WRC_Abort; + } +#endif + } + if( pSelect->pWith ){ + for(i=0; i<pSelect->pWith->nCte; i++){ + if( sqlite3WalkSelect(p, pSelect->pWith->a[i].pSelect) ){ + return WRC_Abort; + } + } + } + return WRC_Continue; +} + +/* +** Initialize a DbFixer structure. This routine must be called prior +** to passing the structure to one of the sqliteFixAAAA() routines below. +*/ +SQLITE_PRIVATE void sqlite3FixInit( + DbFixer *pFix, /* The fixer to be initialized */ + Parse *pParse, /* Error messages will be written here */ + int iDb, /* This is the database that must be used */ + const char *zType, /* "view", "trigger", or "index" */ + const Token *pName /* Name of the view, trigger, or index */ +){ + sqlite3 *db = pParse->db; + assert( db->nDb>iDb ); + pFix->pParse = pParse; + pFix->zDb = db->aDb[iDb].zDbSName; + pFix->pSchema = db->aDb[iDb].pSchema; + pFix->zType = zType; + pFix->pName = pName; + pFix->bTemp = (iDb==1); + pFix->w.pParse = pParse; + pFix->w.xExprCallback = fixExprCb; + pFix->w.xSelectCallback = fixSelectCb; + pFix->w.xSelectCallback2 = sqlite3WalkWinDefnDummyCallback; + pFix->w.walkerDepth = 0; + pFix->w.eCode = 0; + pFix->w.u.pFix = pFix; +} + +/* +** The following set of routines walk through the parse tree and assign +** a specific database to all table references where the database name +** was left unspecified in the original SQL statement. The pFix structure +** must have been initialized by a prior call to sqlite3FixInit(). +** +** These routines are used to make sure that an index, trigger, or +** view in one database does not refer to objects in a different database. +** (Exception: indices, triggers, and views in the TEMP database are +** allowed to refer to anything.) If a reference is explicitly made +** to an object in a different database, an error message is added to +** pParse->zErrMsg and these routines return non-zero. If everything +** checks out, these routines return 0. +*/ +SQLITE_PRIVATE int sqlite3FixSrcList( + DbFixer *pFix, /* Context of the fixation */ + SrcList *pList /* The Source list to check and modify */ +){ + int res = 0; + if( pList ){ + Select s; + memset(&s, 0, sizeof(s)); + s.pSrc = pList; + res = sqlite3WalkSelect(&pFix->w, &s); + } + return res; +} +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) +SQLITE_PRIVATE int sqlite3FixSelect( + DbFixer *pFix, /* Context of the fixation */ + Select *pSelect /* The SELECT statement to be fixed to one database */ +){ + return sqlite3WalkSelect(&pFix->w, pSelect); +} +SQLITE_PRIVATE int sqlite3FixExpr( + DbFixer *pFix, /* Context of the fixation */ + Expr *pExpr /* The expression to be fixed to one database */ +){ + return sqlite3WalkExpr(&pFix->w, pExpr); +} +#endif + +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE int sqlite3FixTriggerStep( + DbFixer *pFix, /* Context of the fixation */ + TriggerStep *pStep /* The trigger step be fixed to one database */ +){ + while( pStep ){ + if( sqlite3WalkSelect(&pFix->w, pStep->pSelect) + || sqlite3WalkExpr(&pFix->w, pStep->pWhere) + || sqlite3WalkExprList(&pFix->w, pStep->pExprList) + || sqlite3FixSrcList(pFix, pStep->pFrom) + ){ + return 1; + } +#ifndef SQLITE_OMIT_UPSERT + { + Upsert *pUp; + for(pUp=pStep->pUpsert; pUp; pUp=pUp->pNextUpsert){ + if( sqlite3WalkExprList(&pFix->w, pUp->pUpsertTarget) + || sqlite3WalkExpr(&pFix->w, pUp->pUpsertTargetWhere) + || sqlite3WalkExprList(&pFix->w, pUp->pUpsertSet) + || sqlite3WalkExpr(&pFix->w, pUp->pUpsertWhere) + ){ + return 1; + } + } + } +#endif + pStep = pStep->pNext; + } + + return 0; +} +#endif + +/************** End of attach.c **********************************************/ +/************** Begin file auth.c ********************************************/ +/* +** 2003 January 11 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the sqlite3_set_authorizer() +** API. This facility is an optional feature of the library. Embedded +** systems that do not need this facility may omit it by recompiling +** the library with -DSQLITE_OMIT_AUTHORIZATION=1 +*/ +/* #include "sqliteInt.h" */ + +/* +** All of the code in this file may be omitted by defining a single +** macro. +*/ +#ifndef SQLITE_OMIT_AUTHORIZATION + +/* +** Set or clear the access authorization function. +** +** The access authorization function is be called during the compilation +** phase to verify that the user has read and/or write access permission on +** various fields of the database. The first argument to the auth function +** is a copy of the 3rd argument to this routine. The second argument +** to the auth function is one of these constants: +** +** SQLITE_CREATE_INDEX +** SQLITE_CREATE_TABLE +** SQLITE_CREATE_TEMP_INDEX +** SQLITE_CREATE_TEMP_TABLE +** SQLITE_CREATE_TEMP_TRIGGER +** SQLITE_CREATE_TEMP_VIEW +** SQLITE_CREATE_TRIGGER +** SQLITE_CREATE_VIEW +** SQLITE_DELETE +** SQLITE_DROP_INDEX +** SQLITE_DROP_TABLE +** SQLITE_DROP_TEMP_INDEX +** SQLITE_DROP_TEMP_TABLE +** SQLITE_DROP_TEMP_TRIGGER +** SQLITE_DROP_TEMP_VIEW +** SQLITE_DROP_TRIGGER +** SQLITE_DROP_VIEW +** SQLITE_INSERT +** SQLITE_PRAGMA +** SQLITE_READ +** SQLITE_SELECT +** SQLITE_TRANSACTION +** SQLITE_UPDATE +** +** The third and fourth arguments to the auth function are the name of +** the table and the column that are being accessed. The auth function +** should return either SQLITE_OK, SQLITE_DENY, or SQLITE_IGNORE. If +** SQLITE_OK is returned, it means that access is allowed. SQLITE_DENY +** means that the SQL statement will never-run - the sqlite3_exec() call +** will return with an error. SQLITE_IGNORE means that the SQL statement +** should run but attempts to read the specified column will return NULL +** and attempts to write the column will be ignored. +** +** Setting the auth function to NULL disables this hook. The default +** setting of the auth function is NULL. +*/ +SQLITE_API int sqlite3_set_authorizer( + sqlite3 *db, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pArg +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + db->xAuth = (sqlite3_xauth)xAuth; + db->pAuthArg = pArg; + if( db->xAuth ) sqlite3ExpirePreparedStatements(db, 1); + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + +/* +** Write an error message into pParse->zErrMsg that explains that the +** user-supplied authorization function returned an illegal value. +*/ +static void sqliteAuthBadReturnCode(Parse *pParse){ + sqlite3ErrorMsg(pParse, "authorizer malfunction"); + pParse->rc = SQLITE_ERROR; +} + +/* +** Invoke the authorization callback for permission to read column zCol from +** table zTab in database zDb. This function assumes that an authorization +** callback has been registered (i.e. that sqlite3.xAuth is not NULL). +** +** If SQLITE_IGNORE is returned and pExpr is not NULL, then pExpr is changed +** to an SQL NULL expression. Otherwise, if pExpr is NULL, then SQLITE_IGNORE +** is treated as SQLITE_DENY. In this case an error is left in pParse. +*/ +SQLITE_PRIVATE int sqlite3AuthReadCol( + Parse *pParse, /* The parser context */ + const char *zTab, /* Table name */ + const char *zCol, /* Column name */ + int iDb /* Index of containing database. */ +){ + sqlite3 *db = pParse->db; /* Database handle */ + char *zDb = db->aDb[iDb].zDbSName; /* Schema name of attached database */ + int rc; /* Auth callback return code */ + + if( db->init.busy ) return SQLITE_OK; + rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext +#ifdef SQLITE_USER_AUTHENTICATION + ,db->auth.zAuthUser +#endif + ); + if( rc==SQLITE_DENY ){ + char *z = sqlite3_mprintf("%s.%s", zTab, zCol); + if( db->nDb>2 || iDb!=0 ) z = sqlite3_mprintf("%s.%z", zDb, z); + sqlite3ErrorMsg(pParse, "access to %z is prohibited", z); + pParse->rc = SQLITE_AUTH; + }else if( rc!=SQLITE_IGNORE && rc!=SQLITE_OK ){ + sqliteAuthBadReturnCode(pParse); + } + return rc; +} + +/* +** The pExpr should be a TK_COLUMN expression. The table referred to +** is in pTabList or else it is the NEW or OLD table of a trigger. +** Check to see if it is OK to read this particular column. +** +** If the auth function returns SQLITE_IGNORE, change the TK_COLUMN +** instruction into a TK_NULL. If the auth function returns SQLITE_DENY, +** then generate an error. +*/ +SQLITE_PRIVATE void sqlite3AuthRead( + Parse *pParse, /* The parser context */ + Expr *pExpr, /* The expression to check authorization on */ + Schema *pSchema, /* The schema of the expression */ + SrcList *pTabList /* All table that pExpr might refer to */ +){ + Table *pTab = 0; /* The table being read */ + const char *zCol; /* Name of the column of the table */ + int iSrc; /* Index in pTabList->a[] of table being read */ + int iDb; /* The index of the database the expression refers to */ + int iCol; /* Index of column in table */ + + assert( pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER ); + assert( !IN_RENAME_OBJECT ); + assert( pParse->db->xAuth!=0 ); + iDb = sqlite3SchemaToIndex(pParse->db, pSchema); + if( iDb<0 ){ + /* An attempt to read a column out of a subquery or other + ** temporary table. */ + return; + } + + if( pExpr->op==TK_TRIGGER ){ + pTab = pParse->pTriggerTab; + }else{ + assert( pTabList ); + for(iSrc=0; iSrc<pTabList->nSrc; iSrc++){ + if( pExpr->iTable==pTabList->a[iSrc].iCursor ){ + pTab = pTabList->a[iSrc].pTab; + break; + } + } + } + iCol = pExpr->iColumn; + if( pTab==0 ) return; + + if( iCol>=0 ){ + assert( iCol<pTab->nCol ); + zCol = pTab->aCol[iCol].zCnName; + }else if( pTab->iPKey>=0 ){ + assert( pTab->iPKey<pTab->nCol ); + zCol = pTab->aCol[pTab->iPKey].zCnName; + }else{ + zCol = "ROWID"; + } + assert( iDb>=0 && iDb<pParse->db->nDb ); + if( SQLITE_IGNORE==sqlite3AuthReadCol(pParse, pTab->zName, zCol, iDb) ){ + pExpr->op = TK_NULL; + } +} + +/* +** Do an authorization check using the code and arguments given. Return +** either SQLITE_OK (zero) or SQLITE_IGNORE or SQLITE_DENY. If SQLITE_DENY +** is returned, then the error count and error message in pParse are +** modified appropriately. +*/ +SQLITE_PRIVATE int sqlite3AuthCheck( + Parse *pParse, + int code, + const char *zArg1, + const char *zArg2, + const char *zArg3 +){ + sqlite3 *db = pParse->db; + int rc; + + /* Don't do any authorization checks if the database is initializing + ** or if the parser is being invoked from within sqlite3_declare_vtab. + */ + assert( !IN_RENAME_OBJECT || db->xAuth==0 ); + if( db->xAuth==0 || db->init.busy || IN_SPECIAL_PARSE ){ + return SQLITE_OK; + } + + /* EVIDENCE-OF: R-43249-19882 The third through sixth parameters to the + ** callback are either NULL pointers or zero-terminated strings that + ** contain additional details about the action to be authorized. + ** + ** The following testcase() macros show that any of the 3rd through 6th + ** parameters can be either NULL or a string. */ + testcase( zArg1==0 ); + testcase( zArg2==0 ); + testcase( zArg3==0 ); + testcase( pParse->zAuthContext==0 ); + + rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext +#ifdef SQLITE_USER_AUTHENTICATION + ,db->auth.zAuthUser +#endif + ); + if( rc==SQLITE_DENY ){ + sqlite3ErrorMsg(pParse, "not authorized"); + pParse->rc = SQLITE_AUTH; + }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){ + rc = SQLITE_DENY; + sqliteAuthBadReturnCode(pParse); + } + return rc; +} + +/* +** Push an authorization context. After this routine is called, the +** zArg3 argument to authorization callbacks will be zContext until +** popped. Or if pParse==0, this routine is a no-op. +*/ +SQLITE_PRIVATE void sqlite3AuthContextPush( + Parse *pParse, + AuthContext *pContext, + const char *zContext +){ + assert( pParse ); + pContext->pParse = pParse; + pContext->zAuthContext = pParse->zAuthContext; + pParse->zAuthContext = zContext; +} + +/* +** Pop an authorization context that was previously pushed +** by sqlite3AuthContextPush +*/ +SQLITE_PRIVATE void sqlite3AuthContextPop(AuthContext *pContext){ + if( pContext->pParse ){ + pContext->pParse->zAuthContext = pContext->zAuthContext; + pContext->pParse = 0; + } +} + +#endif /* SQLITE_OMIT_AUTHORIZATION */ + +/************** End of auth.c ************************************************/ +/************** Begin file build.c *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the SQLite parser +** when syntax rules are reduced. The routines in this file handle the +** following kinds of SQL syntax: +** +** CREATE TABLE +** DROP TABLE +** CREATE INDEX +** DROP INDEX +** creating ID lists +** BEGIN TRANSACTION +** COMMIT +** ROLLBACK +*/ +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_OMIT_SHARED_CACHE +/* +** The TableLock structure is only used by the sqlite3TableLock() and +** codeTableLocks() functions. +*/ +struct TableLock { + int iDb; /* The database containing the table to be locked */ + Pgno iTab; /* The root page of the table to be locked */ + u8 isWriteLock; /* True for write lock. False for a read lock */ + const char *zLockName; /* Name of the table */ +}; + +/* +** Record the fact that we want to lock a table at run-time. +** +** The table to be locked has root page iTab and is found in database iDb. +** A read or a write lock can be taken depending on isWritelock. +** +** This routine just records the fact that the lock is desired. The +** code to make the lock occur is generated by a later call to +** codeTableLocks() which occurs during sqlite3FinishCoding(). +*/ +static SQLITE_NOINLINE void lockTable( + Parse *pParse, /* Parsing context */ + int iDb, /* Index of the database containing the table to lock */ + Pgno iTab, /* Root page number of the table to be locked */ + u8 isWriteLock, /* True for a write lock */ + const char *zName /* Name of the table to be locked */ +){ + Parse *pToplevel; + int i; + int nBytes; + TableLock *p; + assert( iDb>=0 ); + + pToplevel = sqlite3ParseToplevel(pParse); + for(i=0; i<pToplevel->nTableLock; i++){ + p = &pToplevel->aTableLock[i]; + if( p->iDb==iDb && p->iTab==iTab ){ + p->isWriteLock = (p->isWriteLock || isWriteLock); + return; + } + } + + nBytes = sizeof(TableLock) * (pToplevel->nTableLock+1); + pToplevel->aTableLock = + sqlite3DbReallocOrFree(pToplevel->db, pToplevel->aTableLock, nBytes); + if( pToplevel->aTableLock ){ + p = &pToplevel->aTableLock[pToplevel->nTableLock++]; + p->iDb = iDb; + p->iTab = iTab; + p->isWriteLock = isWriteLock; + p->zLockName = zName; + }else{ + pToplevel->nTableLock = 0; + sqlite3OomFault(pToplevel->db); + } +} +SQLITE_PRIVATE void sqlite3TableLock( + Parse *pParse, /* Parsing context */ + int iDb, /* Index of the database containing the table to lock */ + Pgno iTab, /* Root page number of the table to be locked */ + u8 isWriteLock, /* True for a write lock */ + const char *zName /* Name of the table to be locked */ +){ + if( iDb==1 ) return; + if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return; + lockTable(pParse, iDb, iTab, isWriteLock, zName); +} + +/* +** Code an OP_TableLock instruction for each table locked by the +** statement (configured by calls to sqlite3TableLock()). +*/ +static void codeTableLocks(Parse *pParse){ + int i; + Vdbe *pVdbe = pParse->pVdbe; + assert( pVdbe!=0 ); + + for(i=0; i<pParse->nTableLock; i++){ + TableLock *p = &pParse->aTableLock[i]; + int p1 = p->iDb; + sqlite3VdbeAddOp4(pVdbe, OP_TableLock, p1, p->iTab, p->isWriteLock, + p->zLockName, P4_STATIC); + } +} +#else + #define codeTableLocks(x) +#endif + +/* +** Return TRUE if the given yDbMask object is empty - if it contains no +** 1 bits. This routine is used by the DbMaskAllZero() and DbMaskNotZero() +** macros when SQLITE_MAX_ATTACHED is greater than 30. +*/ +#if SQLITE_MAX_ATTACHED>30 +SQLITE_PRIVATE int sqlite3DbMaskAllZero(yDbMask m){ + int i; + for(i=0; i<sizeof(yDbMask); i++) if( m[i] ) return 0; + return 1; +} +#endif + +/* +** This routine is called after a single SQL statement has been +** parsed and a VDBE program to execute that statement has been +** prepared. This routine puts the finishing touches on the +** VDBE program and resets the pParse structure for the next +** parse. +** +** Note that if an error occurred, it might be the case that +** no VDBE code was generated. +*/ +SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ + sqlite3 *db; + Vdbe *v; + int iDb, i; + + assert( pParse->pToplevel==0 ); + db = pParse->db; + assert( db->pParse==pParse ); + if( pParse->nested ) return; + if( pParse->nErr ){ + if( db->mallocFailed ) pParse->rc = SQLITE_NOMEM; + return; + } + assert( db->mallocFailed==0 ); + + /* Begin by generating some termination code at the end of the + ** vdbe program + */ + v = pParse->pVdbe; + if( v==0 ){ + if( db->init.busy ){ + pParse->rc = SQLITE_DONE; + return; + } + v = sqlite3GetVdbe(pParse); + if( v==0 ) pParse->rc = SQLITE_ERROR; + } + assert( !pParse->isMultiWrite + || sqlite3VdbeAssertMayAbort(v, pParse->mayAbort)); + if( v ){ + if( pParse->bReturning ){ + Returning *pReturning = pParse->u1.pReturning; + int addrRewind; + int reg; + + if( pReturning->nRetCol ){ + sqlite3VdbeAddOp0(v, OP_FkCheck); + addrRewind = + sqlite3VdbeAddOp1(v, OP_Rewind, pReturning->iRetCur); + VdbeCoverage(v); + reg = pReturning->iRetReg; + for(i=0; i<pReturning->nRetCol; i++){ + sqlite3VdbeAddOp3(v, OP_Column, pReturning->iRetCur, i, reg+i); + } + sqlite3VdbeAddOp2(v, OP_ResultRow, reg, i); + sqlite3VdbeAddOp2(v, OP_Next, pReturning->iRetCur, addrRewind+1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrRewind); + } + } + sqlite3VdbeAddOp0(v, OP_Halt); + +#if SQLITE_USER_AUTHENTICATION + if( pParse->nTableLock>0 && db->init.busy==0 ){ + sqlite3UserAuthInit(db); + if( db->auth.authLevel<UAUTH_User ){ + sqlite3ErrorMsg(pParse, "user not authenticated"); + pParse->rc = SQLITE_AUTH_USER; + return; + } + } +#endif + + /* The cookie mask contains one bit for each database file open. + ** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are + ** set for each database that is used. Generate code to start a + ** transaction on each used database and to verify the schema cookie + ** on each used database. + */ + assert( pParse->nErr>0 || sqlite3VdbeGetOp(v, 0)->opcode==OP_Init ); + sqlite3VdbeJumpHere(v, 0); + assert( db->nDb>0 ); + iDb = 0; + do{ + Schema *pSchema; + if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue; + sqlite3VdbeUsesBtree(v, iDb); + pSchema = db->aDb[iDb].pSchema; + sqlite3VdbeAddOp4Int(v, + OP_Transaction, /* Opcode */ + iDb, /* P1 */ + DbMaskTest(pParse->writeMask,iDb), /* P2 */ + pSchema->schema_cookie, /* P3 */ + pSchema->iGeneration /* P4 */ + ); + if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); + VdbeComment((v, + "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite)); + }while( ++iDb<db->nDb ); +#ifndef SQLITE_OMIT_VIRTUALTABLE + for(i=0; i<pParse->nVtabLock; i++){ + char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]); + sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB); + } + pParse->nVtabLock = 0; +#endif + +#ifndef SQLITE_OMIT_SHARED_CACHE + /* Once all the cookies have been verified and transactions opened, + ** obtain the required table-locks. This is a no-op unless the + ** shared-cache feature is enabled. + */ + if( pParse->nTableLock ) codeTableLocks(pParse); +#endif + + /* Initialize any AUTOINCREMENT data structures required. + */ + if( pParse->pAinc ) sqlite3AutoincrementBegin(pParse); + + /* Code constant expressions that were factored out of inner loops. + */ + if( pParse->pConstExpr ){ + ExprList *pEL = pParse->pConstExpr; + pParse->okConstFactor = 0; + for(i=0; i<pEL->nExpr; i++){ + assert( pEL->a[i].u.iConstExprReg>0 ); + sqlite3ExprCode(pParse, pEL->a[i].pExpr, pEL->a[i].u.iConstExprReg); + } + } + + if( pParse->bReturning ){ + Returning *pRet = pParse->u1.pReturning; + if( pRet->nRetCol ){ + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol); + } + } + + /* Finally, jump back to the beginning of the executable code. */ + sqlite3VdbeGoto(v, 1); + } + + /* Get the VDBE program ready for execution + */ + assert( v!=0 || pParse->nErr ); + assert( db->mallocFailed==0 || pParse->nErr ); + if( pParse->nErr==0 ){ + /* A minimum of one cursor is required if autoincrement is used + * See ticket [a696379c1f08866] */ + assert( pParse->pAinc==0 || pParse->nTab>0 ); + sqlite3VdbeMakeReady(v, pParse); + pParse->rc = SQLITE_DONE; + }else{ + pParse->rc = SQLITE_ERROR; + } +} + +/* +** Run the parser and code generator recursively in order to generate +** code for the SQL statement given onto the end of the pParse context +** currently under construction. Notes: +** +** * The final OP_Halt is not appended and other initialization +** and finalization steps are omitted because those are handling by the +** outermost parser. +** +** * Built-in SQL functions always take precedence over application-defined +** SQL functions. In other words, it is not possible to override a +** built-in function. +*/ +SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ + va_list ap; + char *zSql; + sqlite3 *db = pParse->db; + u32 savedDbFlags = db->mDbFlags; + char saveBuf[PARSE_TAIL_SZ]; + + if( pParse->nErr ) return; + if( pParse->eParseMode ) return; + assert( pParse->nested<10 ); /* Nesting should only be of limited depth */ + va_start(ap, zFormat); + zSql = sqlite3VMPrintf(db, zFormat, ap); + va_end(ap); + if( zSql==0 ){ + /* This can result either from an OOM or because the formatted string + ** exceeds SQLITE_LIMIT_LENGTH. In the latter case, we need to set + ** an error */ + if( !db->mallocFailed ) pParse->rc = SQLITE_TOOBIG; + pParse->nErr++; + return; + } + pParse->nested++; + memcpy(saveBuf, PARSE_TAIL(pParse), PARSE_TAIL_SZ); + memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); + db->mDbFlags |= DBFLAG_PreferBuiltin; + sqlite3RunParser(pParse, zSql); + db->mDbFlags = savedDbFlags; + sqlite3DbFree(db, zSql); + memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); + pParse->nested--; +} + +#if SQLITE_USER_AUTHENTICATION +/* +** Return TRUE if zTable is the name of the system table that stores the +** list of users and their access credentials. +*/ +SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){ + return sqlite3_stricmp(zTable, "sqlite_user")==0; +} +#endif + +/* +** Locate the in-memory structure that describes a particular database +** table given the name of that table and (optionally) the name of the +** database containing the table. Return NULL if not found. +** +** If zDatabase is 0, all databases are searched for the table and the +** first matching table is returned. (No checking for duplicate table +** names is done.) The search order is TEMP first, then MAIN, then any +** auxiliary databases added using the ATTACH command. +** +** See also sqlite3LocateTable(). +*/ +SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){ + Table *p = 0; + int i; + + /* All mutexes are required for schema access. Make sure we hold them. */ + assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) ); +#if SQLITE_USER_AUTHENTICATION + /* Only the admin user is allowed to know that the sqlite_user table + ** exists */ + if( db->auth.authLevel<UAUTH_Admin && sqlite3UserAuthTable(zName)!=0 ){ + return 0; + } +#endif + if( zDatabase ){ + for(i=0; i<db->nDb; i++){ + if( sqlite3StrICmp(zDatabase, db->aDb[i].zDbSName)==0 ) break; + } + if( i>=db->nDb ){ + /* No match against the official names. But always match "main" + ** to schema 0 as a legacy fallback. */ + if( sqlite3StrICmp(zDatabase,"main")==0 ){ + i = 0; + }else{ + return 0; + } + } + p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, zName); + if( p==0 && sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ + if( i==1 ){ + if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 + || sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 + || sqlite3StrICmp(zName+7, &LEGACY_SCHEMA_TABLE[7])==0 + ){ + p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, + LEGACY_TEMP_SCHEMA_TABLE); + } + }else{ + if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ + p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, + LEGACY_SCHEMA_TABLE); + } + } + } + }else{ + /* Match against TEMP first */ + p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, zName); + if( p ) return p; + /* The main database is second */ + p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, zName); + if( p ) return p; + /* Attached databases are in order of attachment */ + for(i=2; i<db->nDb; i++){ + assert( sqlite3SchemaMutexHeld(db, i, 0) ); + p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, zName); + if( p ) break; + } + if( p==0 && sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ + if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ + p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, LEGACY_SCHEMA_TABLE); + }else if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 ){ + p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, + LEGACY_TEMP_SCHEMA_TABLE); + } + } + } + return p; +} + +/* +** Locate the in-memory structure that describes a particular database +** table given the name of that table and (optionally) the name of the +** database containing the table. Return NULL if not found. Also leave an +** error message in pParse->zErrMsg. +** +** The difference between this routine and sqlite3FindTable() is that this +** routine leaves an error message in pParse->zErrMsg where +** sqlite3FindTable() does not. +*/ +SQLITE_PRIVATE Table *sqlite3LocateTable( + Parse *pParse, /* context in which to report errors */ + u32 flags, /* LOCATE_VIEW or LOCATE_NOERR */ + const char *zName, /* Name of the table we are looking for */ + const char *zDbase /* Name of the database. Might be NULL */ +){ + Table *p; + sqlite3 *db = pParse->db; + + /* Read the database schema. If an error occurs, leave an error message + ** and code in pParse and return NULL. */ + if( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 + && SQLITE_OK!=sqlite3ReadSchema(pParse) + ){ + return 0; + } + + p = sqlite3FindTable(db, zName, zDbase); + if( p==0 ){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* If zName is the not the name of a table in the schema created using + ** CREATE, then check to see if it is the name of an virtual table that + ** can be an eponymous virtual table. */ + if( (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)==0 && db->init.busy==0 ){ + Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName); + if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ + pMod = sqlite3PragmaVtabRegister(db, zName); + } + if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ + testcase( pMod->pEpoTab==0 ); + return pMod->pEpoTab; + } + } +#endif + if( flags & LOCATE_NOERR ) return 0; + pParse->checkSchema = 1; + }else if( IsVirtual(p) && (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)!=0 ){ + p = 0; + } + + if( p==0 ){ + const char *zMsg = flags & LOCATE_VIEW ? "no such view" : "no such table"; + if( zDbase ){ + sqlite3ErrorMsg(pParse, "%s: %s.%s", zMsg, zDbase, zName); + }else{ + sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName); + } + }else{ + assert( HasRowid(p) || p->iPKey<0 ); + } + + return p; +} + +/* +** Locate the table identified by *p. +** +** This is a wrapper around sqlite3LocateTable(). The difference between +** sqlite3LocateTable() and this function is that this function restricts +** the search to schema (p->pSchema) if it is not NULL. p->pSchema may be +** non-NULL if it is part of a view or trigger program definition. See +** sqlite3FixSrcList() for details. +*/ +SQLITE_PRIVATE Table *sqlite3LocateTableItem( + Parse *pParse, + u32 flags, + SrcItem *p +){ + const char *zDb; + assert( p->pSchema==0 || p->zDatabase==0 ); + if( p->pSchema ){ + int iDb = sqlite3SchemaToIndex(pParse->db, p->pSchema); + zDb = pParse->db->aDb[iDb].zDbSName; + }else{ + zDb = p->zDatabase; + } + return sqlite3LocateTable(pParse, flags, p->zName, zDb); +} + +/* +** Return the preferred table name for system tables. Translate legacy +** names into the new preferred names, as appropriate. +*/ +SQLITE_PRIVATE const char *sqlite3PreferredTableName(const char *zName){ + if( sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ + if( sqlite3StrICmp(zName+7, &LEGACY_SCHEMA_TABLE[7])==0 ){ + return PREFERRED_SCHEMA_TABLE; + } + if( sqlite3StrICmp(zName+7, &LEGACY_TEMP_SCHEMA_TABLE[7])==0 ){ + return PREFERRED_TEMP_SCHEMA_TABLE; + } + } + return zName; +} + +/* +** Locate the in-memory structure that describes +** a particular index given the name of that index +** and the name of the database that contains the index. +** Return NULL if not found. +** +** If zDatabase is 0, all databases are searched for the +** table and the first matching index is returned. (No checking +** for duplicate index names is done.) The search order is +** TEMP first, then MAIN, then any auxiliary databases added +** using the ATTACH command. +*/ +SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const char *zDb){ + Index *p = 0; + int i; + /* All mutexes are required for schema access. Make sure we hold them. */ + assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) ); + for(i=OMIT_TEMPDB; i<db->nDb; i++){ + int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ + Schema *pSchema = db->aDb[j].pSchema; + assert( pSchema ); + if( zDb && sqlite3DbIsNamed(db, j, zDb)==0 ) continue; + assert( sqlite3SchemaMutexHeld(db, j, 0) ); + p = sqlite3HashFind(&pSchema->idxHash, zName); + if( p ) break; + } + return p; +} + +/* +** Reclaim the memory used by an index +*/ +SQLITE_PRIVATE void sqlite3FreeIndex(sqlite3 *db, Index *p){ +#ifndef SQLITE_OMIT_ANALYZE + sqlite3DeleteIndexSamples(db, p); +#endif + sqlite3ExprDelete(db, p->pPartIdxWhere); + sqlite3ExprListDelete(db, p->aColExpr); + sqlite3DbFree(db, p->zColAff); + if( p->isResized ) sqlite3DbFree(db, (void *)p->azColl); +#ifdef SQLITE_ENABLE_STAT4 + sqlite3_free(p->aiRowEst); +#endif + sqlite3DbFree(db, p); +} + +/* +** For the index called zIdxName which is found in the database iDb, +** unlike that index from its Table then remove the index from +** the index hash table and free all memory structures associated +** with the index. +*/ +SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char *zIdxName){ + Index *pIndex; + Hash *pHash; + + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + pHash = &db->aDb[iDb].pSchema->idxHash; + pIndex = sqlite3HashInsert(pHash, zIdxName, 0); + if( ALWAYS(pIndex) ){ + if( pIndex->pTable->pIndex==pIndex ){ + pIndex->pTable->pIndex = pIndex->pNext; + }else{ + Index *p; + /* Justification of ALWAYS(); The index must be on the list of + ** indices. */ + p = pIndex->pTable->pIndex; + while( ALWAYS(p) && p->pNext!=pIndex ){ p = p->pNext; } + if( ALWAYS(p && p->pNext==pIndex) ){ + p->pNext = pIndex->pNext; + } + } + sqlite3FreeIndex(db, pIndex); + } + db->mDbFlags |= DBFLAG_SchemaChange; +} + +/* +** Look through the list of open database files in db->aDb[] and if +** any have been closed, remove them from the list. Reallocate the +** db->aDb[] structure to a smaller size, if possible. +** +** Entry 0 (the "main" database) and entry 1 (the "temp" database) +** are never candidates for being collapsed. +*/ +SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3 *db){ + int i, j; + for(i=j=2; i<db->nDb; i++){ + struct Db *pDb = &db->aDb[i]; + if( pDb->pBt==0 ){ + sqlite3DbFree(db, pDb->zDbSName); + pDb->zDbSName = 0; + continue; + } + if( j<i ){ + db->aDb[j] = db->aDb[i]; + } + j++; + } + db->nDb = j; + if( db->nDb<=2 && db->aDb!=db->aDbStatic ){ + memcpy(db->aDbStatic, db->aDb, 2*sizeof(db->aDb[0])); + sqlite3DbFree(db, db->aDb); + db->aDb = db->aDbStatic; + } +} + +/* +** Reset the schema for the database at index iDb. Also reset the +** TEMP schema. The reset is deferred if db->nSchemaLock is not zero. +** Deferred resets may be run by calling with iDb<0. +*/ +SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3 *db, int iDb){ + int i; + assert( iDb<db->nDb ); + + if( iDb>=0 ){ + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + DbSetProperty(db, iDb, DB_ResetWanted); + DbSetProperty(db, 1, DB_ResetWanted); + db->mDbFlags &= ~DBFLAG_SchemaKnownOk; + } + + if( db->nSchemaLock==0 ){ + for(i=0; i<db->nDb; i++){ + if( DbHasProperty(db, i, DB_ResetWanted) ){ + sqlite3SchemaClear(db->aDb[i].pSchema); + } + } + } +} + +/* +** Erase all schema information from all attached databases (including +** "main" and "temp") for a single database connection. +*/ +SQLITE_PRIVATE void sqlite3ResetAllSchemasOfConnection(sqlite3 *db){ + int i; + sqlite3BtreeEnterAll(db); + for(i=0; i<db->nDb; i++){ + Db *pDb = &db->aDb[i]; + if( pDb->pSchema ){ + if( db->nSchemaLock==0 ){ + sqlite3SchemaClear(pDb->pSchema); + }else{ + DbSetProperty(db, i, DB_ResetWanted); + } + } + } + db->mDbFlags &= ~(DBFLAG_SchemaChange|DBFLAG_SchemaKnownOk); + sqlite3VtabUnlockList(db); + sqlite3BtreeLeaveAll(db); + if( db->nSchemaLock==0 ){ + sqlite3CollapseDatabaseArray(db); + } +} + +/* +** This routine is called when a commit occurs. +*/ +SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3 *db){ + db->mDbFlags &= ~DBFLAG_SchemaChange; +} + +/* +** Set the expression associated with a column. This is usually +** the DEFAULT value, but might also be the expression that computes +** the value for a generated column. +*/ +SQLITE_PRIVATE void sqlite3ColumnSetExpr( + Parse *pParse, /* Parsing context */ + Table *pTab, /* The table containing the column */ + Column *pCol, /* The column to receive the new DEFAULT expression */ + Expr *pExpr /* The new default expression */ +){ + ExprList *pList; + assert( IsOrdinaryTable(pTab) ); + pList = pTab->u.tab.pDfltList; + if( pCol->iDflt==0 + || NEVER(pList==0) + || NEVER(pList->nExpr<pCol->iDflt) + ){ + pCol->iDflt = pList==0 ? 1 : pList->nExpr+1; + pTab->u.tab.pDfltList = sqlite3ExprListAppend(pParse, pList, pExpr); + }else{ + sqlite3ExprDelete(pParse->db, pList->a[pCol->iDflt-1].pExpr); + pList->a[pCol->iDflt-1].pExpr = pExpr; + } +} + +/* +** Return the expression associated with a column. The expression might be +** the DEFAULT clause or the AS clause of a generated column. +** Return NULL if the column has no associated expression. +*/ +SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table *pTab, Column *pCol){ + if( pCol->iDflt==0 ) return 0; + if( NEVER(!IsOrdinaryTable(pTab)) ) return 0; + if( NEVER(pTab->u.tab.pDfltList==0) ) return 0; + if( NEVER(pTab->u.tab.pDfltList->nExpr<pCol->iDflt) ) return 0; + return pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; +} + +/* +** Set the collating sequence name for a column. +*/ +SQLITE_PRIVATE void sqlite3ColumnSetColl( + sqlite3 *db, + Column *pCol, + const char *zColl +){ + i64 nColl; + i64 n; + char *zNew; + assert( zColl!=0 ); + n = sqlite3Strlen30(pCol->zCnName) + 1; + if( pCol->colFlags & COLFLAG_HASTYPE ){ + n += sqlite3Strlen30(pCol->zCnName+n) + 1; + } + nColl = sqlite3Strlen30(zColl) + 1; + zNew = sqlite3DbRealloc(db, pCol->zCnName, nColl+n); + if( zNew ){ + pCol->zCnName = zNew; + memcpy(pCol->zCnName + n, zColl, nColl); + pCol->colFlags |= COLFLAG_HASCOLL; + } +} + +/* +** Return the collating sequence name for a column +*/ +SQLITE_PRIVATE const char *sqlite3ColumnColl(Column *pCol){ + const char *z; + if( (pCol->colFlags & COLFLAG_HASCOLL)==0 ) return 0; + z = pCol->zCnName; + while( *z ){ z++; } + if( pCol->colFlags & COLFLAG_HASTYPE ){ + do{ z++; }while( *z ); + } + return z+1; +} + +/* +** Delete memory allocated for the column names of a table or view (the +** Table.aCol[] array). +*/ +SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){ + int i; + Column *pCol; + assert( pTable!=0 ); + assert( db!=0 ); + if( (pCol = pTable->aCol)!=0 ){ + for(i=0; i<pTable->nCol; i++, pCol++){ + assert( pCol->zCnName==0 || pCol->hName==sqlite3StrIHash(pCol->zCnName) ); + sqlite3DbFree(db, pCol->zCnName); + } + sqlite3DbNNFreeNN(db, pTable->aCol); + if( IsOrdinaryTable(pTable) ){ + sqlite3ExprListDelete(db, pTable->u.tab.pDfltList); + } + if( db->pnBytesFreed==0 ){ + pTable->aCol = 0; + pTable->nCol = 0; + if( IsOrdinaryTable(pTable) ){ + pTable->u.tab.pDfltList = 0; + } + } + } +} + +/* +** Remove the memory data structures associated with the given +** Table. No changes are made to disk by this routine. +** +** This routine just deletes the data structure. It does not unlink +** the table data structure from the hash table. But it does destroy +** memory structures of the indices and foreign keys associated with +** the table. +** +** The db parameter is optional. It is needed if the Table object +** contains lookaside memory. (Table objects in the schema do not use +** lookaside memory, but some ephemeral Table objects do.) Or the +** db parameter can be used with db->pnBytesFreed to measure the memory +** used by the Table object. +*/ +static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ + Index *pIndex, *pNext; + +#ifdef SQLITE_DEBUG + /* Record the number of outstanding lookaside allocations in schema Tables + ** prior to doing any free() operations. Since schema Tables do not use + ** lookaside, this number should not change. + ** + ** If malloc has already failed, it may be that it failed while allocating + ** a Table object that was going to be marked ephemeral. So do not check + ** that no lookaside memory is used in this case either. */ + int nLookaside = 0; + assert( db!=0 ); + if( !db->mallocFailed && (pTable->tabFlags & TF_Ephemeral)==0 ){ + nLookaside = sqlite3LookasideUsed(db, 0); + } +#endif + + /* Delete all indices associated with this table. */ + for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){ + pNext = pIndex->pNext; + assert( pIndex->pSchema==pTable->pSchema + || (IsVirtual(pTable) && pIndex->idxType!=SQLITE_IDXTYPE_APPDEF) ); + if( db->pnBytesFreed==0 && !IsVirtual(pTable) ){ + char *zName = pIndex->zName; + TESTONLY ( Index *pOld = ) sqlite3HashInsert( + &pIndex->pSchema->idxHash, zName, 0 + ); + assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) ); + assert( pOld==pIndex || pOld==0 ); + } + sqlite3FreeIndex(db, pIndex); + } + + if( IsOrdinaryTable(pTable) ){ + sqlite3FkDelete(db, pTable); + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + else if( IsVirtual(pTable) ){ + sqlite3VtabClear(db, pTable); + } +#endif + else{ + assert( IsView(pTable) ); + sqlite3SelectDelete(db, pTable->u.view.pSelect); + } + + /* Delete the Table structure itself. + */ + sqlite3DeleteColumnNames(db, pTable); + sqlite3DbFree(db, pTable->zName); + sqlite3DbFree(db, pTable->zColAff); + sqlite3ExprListDelete(db, pTable->pCheck); + sqlite3DbFree(db, pTable); + + /* Verify that no lookaside memory was used by schema tables */ + assert( nLookaside==0 || nLookaside==sqlite3LookasideUsed(db,0) ); +} +SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3 *db, Table *pTable){ + /* Do not delete the table until the reference count reaches zero. */ + assert( db!=0 ); + if( !pTable ) return; + if( db->pnBytesFreed==0 && (--pTable->nTabRef)>0 ) return; + deleteTable(db, pTable); +} + + +/* +** Unlink the given table from the hash tables and the delete the +** table structure with all its indices and foreign keys. +*/ +SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3 *db, int iDb, const char *zTabName){ + Table *p; + Db *pDb; + + assert( db!=0 ); + assert( iDb>=0 && iDb<db->nDb ); + assert( zTabName ); + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + testcase( zTabName[0]==0 ); /* Zero-length table names are allowed */ + pDb = &db->aDb[iDb]; + p = sqlite3HashInsert(&pDb->pSchema->tblHash, zTabName, 0); + sqlite3DeleteTable(db, p); + db->mDbFlags |= DBFLAG_SchemaChange; +} + +/* +** Given a token, return a string that consists of the text of that +** token. Space to hold the returned string +** is obtained from sqliteMalloc() and must be freed by the calling +** function. +** +** Any quotation marks (ex: "name", 'name', [name], or `name`) that +** surround the body of the token are removed. +** +** Tokens are often just pointers into the original SQL text and so +** are not \000 terminated and are not persistent. The returned string +** is \000 terminated and is persistent. +*/ +SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, const Token *pName){ + char *zName; + if( pName ){ + zName = sqlite3DbStrNDup(db, (const char*)pName->z, pName->n); + sqlite3Dequote(zName); + }else{ + zName = 0; + } + return zName; +} + +/* +** Open the sqlite_schema table stored in database number iDb for +** writing. The table is opened using cursor 0. +*/ +SQLITE_PRIVATE void sqlite3OpenSchemaTable(Parse *p, int iDb){ + Vdbe *v = sqlite3GetVdbe(p); + sqlite3TableLock(p, iDb, SCHEMA_ROOT, 1, LEGACY_SCHEMA_TABLE); + sqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, SCHEMA_ROOT, iDb, 5); + if( p->nTab==0 ){ + p->nTab = 1; + } +} + +/* +** Parameter zName points to a nul-terminated buffer containing the name +** of a database ("main", "temp" or the name of an attached db). This +** function returns the index of the named database in db->aDb[], or +** -1 if the named db cannot be found. +*/ +SQLITE_PRIVATE int sqlite3FindDbName(sqlite3 *db, const char *zName){ + int i = -1; /* Database number */ + if( zName ){ + Db *pDb; + for(i=(db->nDb-1), pDb=&db->aDb[i]; i>=0; i--, pDb--){ + if( 0==sqlite3_stricmp(pDb->zDbSName, zName) ) break; + /* "main" is always an acceptable alias for the primary database + ** even if it has been renamed using SQLITE_DBCONFIG_MAINDBNAME. */ + if( i==0 && 0==sqlite3_stricmp("main", zName) ) break; + } + } + return i; +} + +/* +** The token *pName contains the name of a database (either "main" or +** "temp" or the name of an attached db). This routine returns the +** index of the named database in db->aDb[], or -1 if the named db +** does not exist. +*/ +SQLITE_PRIVATE int sqlite3FindDb(sqlite3 *db, Token *pName){ + int i; /* Database number */ + char *zName; /* Name we are searching for */ + zName = sqlite3NameFromToken(db, pName); + i = sqlite3FindDbName(db, zName); + sqlite3DbFree(db, zName); + return i; +} + +/* The table or view or trigger name is passed to this routine via tokens +** pName1 and pName2. If the table name was fully qualified, for example: +** +** CREATE TABLE xxx.yyy (...); +** +** Then pName1 is set to "xxx" and pName2 "yyy". On the other hand if +** the table name is not fully qualified, i.e.: +** +** CREATE TABLE yyy(...); +** +** Then pName1 is set to "yyy" and pName2 is "". +** +** This routine sets the *ppUnqual pointer to point at the token (pName1 or +** pName2) that stores the unqualified table name. The index of the +** database "xxx" is returned. +*/ +SQLITE_PRIVATE int sqlite3TwoPartName( + Parse *pParse, /* Parsing and code generating context */ + Token *pName1, /* The "xxx" in the name "xxx.yyy" or "xxx" */ + Token *pName2, /* The "yyy" in the name "xxx.yyy" */ + Token **pUnqual /* Write the unqualified object name here */ +){ + int iDb; /* Database holding the object */ + sqlite3 *db = pParse->db; + + assert( pName2!=0 ); + if( pName2->n>0 ){ + if( db->init.busy ) { + sqlite3ErrorMsg(pParse, "corrupt database"); + return -1; + } + *pUnqual = pName2; + iDb = sqlite3FindDb(db, pName1); + if( iDb<0 ){ + sqlite3ErrorMsg(pParse, "unknown database %T", pName1); + return -1; + } + }else{ + assert( db->init.iDb==0 || db->init.busy || IN_SPECIAL_PARSE + || (db->mDbFlags & DBFLAG_Vacuum)!=0); + iDb = db->init.iDb; + *pUnqual = pName1; + } + return iDb; +} + +/* +** True if PRAGMA writable_schema is ON +*/ +SQLITE_PRIVATE int sqlite3WritableSchema(sqlite3 *db){ + testcase( (db->flags&(SQLITE_WriteSchema|SQLITE_Defensive))==0 ); + testcase( (db->flags&(SQLITE_WriteSchema|SQLITE_Defensive))== + SQLITE_WriteSchema ); + testcase( (db->flags&(SQLITE_WriteSchema|SQLITE_Defensive))== + SQLITE_Defensive ); + testcase( (db->flags&(SQLITE_WriteSchema|SQLITE_Defensive))== + (SQLITE_WriteSchema|SQLITE_Defensive) ); + return (db->flags&(SQLITE_WriteSchema|SQLITE_Defensive))==SQLITE_WriteSchema; +} + +/* +** This routine is used to check if the UTF-8 string zName is a legal +** unqualified name for a new schema object (table, index, view or +** trigger). All names are legal except those that begin with the string +** "sqlite_" (in upper, lower or mixed case). This portion of the namespace +** is reserved for internal use. +** +** When parsing the sqlite_schema table, this routine also checks to +** make sure the "type", "name", and "tbl_name" columns are consistent +** with the SQL. +*/ +SQLITE_PRIVATE int sqlite3CheckObjectName( + Parse *pParse, /* Parsing context */ + const char *zName, /* Name of the object to check */ + const char *zType, /* Type of this object */ + const char *zTblName /* Parent table name for triggers and indexes */ +){ + sqlite3 *db = pParse->db; + if( sqlite3WritableSchema(db) + || db->init.imposterTable + || !sqlite3Config.bExtraSchemaChecks + ){ + /* Skip these error checks for writable_schema=ON */ + return SQLITE_OK; + } + if( db->init.busy ){ + if( sqlite3_stricmp(zType, db->init.azInit[0]) + || sqlite3_stricmp(zName, db->init.azInit[1]) + || sqlite3_stricmp(zTblName, db->init.azInit[2]) + ){ + sqlite3ErrorMsg(pParse, ""); /* corruptSchema() will supply the error */ + return SQLITE_ERROR; + } + }else{ + if( (pParse->nested==0 && 0==sqlite3StrNICmp(zName, "sqlite_", 7)) + || (sqlite3ReadOnlyShadowTables(db) && sqlite3ShadowTableName(db, zName)) + ){ + sqlite3ErrorMsg(pParse, "object name reserved for internal use: %s", + zName); + return SQLITE_ERROR; + } + + } + return SQLITE_OK; +} + +/* +** Return the PRIMARY KEY index of a table +*/ +SQLITE_PRIVATE Index *sqlite3PrimaryKeyIndex(Table *pTab){ + Index *p; + for(p=pTab->pIndex; p && !IsPrimaryKeyIndex(p); p=p->pNext){} + return p; +} + +/* +** Convert an table column number into a index column number. That is, +** for the column iCol in the table (as defined by the CREATE TABLE statement) +** find the (first) offset of that column in index pIdx. Or return -1 +** if column iCol is not used in index pIdx. +*/ +SQLITE_PRIVATE i16 sqlite3TableColumnToIndex(Index *pIdx, i16 iCol){ + int i; + for(i=0; i<pIdx->nColumn; i++){ + if( iCol==pIdx->aiColumn[i] ) return i; + } + return -1; +} + +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +/* Convert a storage column number into a table column number. +** +** The storage column number (0,1,2,....) is the index of the value +** as it appears in the record on disk. The true column number +** is the index (0,1,2,...) of the column in the CREATE TABLE statement. +** +** The storage column number is less than the table column number if +** and only there are VIRTUAL columns to the left. +** +** If SQLITE_OMIT_GENERATED_COLUMNS, this routine is a no-op macro. +*/ +SQLITE_PRIVATE i16 sqlite3StorageColumnToTable(Table *pTab, i16 iCol){ + if( pTab->tabFlags & TF_HasVirtual ){ + int i; + for(i=0; i<=iCol; i++){ + if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) iCol++; + } + } + return iCol; +} +#endif + +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +/* Convert a table column number into a storage column number. +** +** The storage column number (0,1,2,....) is the index of the value +** as it appears in the record on disk. Or, if the input column is +** the N-th virtual column (zero-based) then the storage number is +** the number of non-virtual columns in the table plus N. +** +** The true column number is the index (0,1,2,...) of the column in +** the CREATE TABLE statement. +** +** If the input column is a VIRTUAL column, then it should not appear +** in storage. But the value sometimes is cached in registers that +** follow the range of registers used to construct storage. This +** avoids computing the same VIRTUAL column multiple times, and provides +** values for use by OP_Param opcodes in triggers. Hence, if the +** input column is a VIRTUAL table, put it after all the other columns. +** +** In the following, N means "normal column", S means STORED, and +** V means VIRTUAL. Suppose the CREATE TABLE has columns like this: +** +** CREATE TABLE ex(N,S,V,N,S,V,N,S,V); +** -- 0 1 2 3 4 5 6 7 8 +** +** Then the mapping from this function is as follows: +** +** INPUTS: 0 1 2 3 4 5 6 7 8 +** OUTPUTS: 0 1 6 2 3 7 4 5 8 +** +** So, in other words, this routine shifts all the virtual columns to +** the end. +** +** If SQLITE_OMIT_GENERATED_COLUMNS then there are no virtual columns and +** this routine is a no-op macro. If the pTab does not have any virtual +** columns, then this routine is no-op that always return iCol. If iCol +** is negative (indicating the ROWID column) then this routine return iCol. +*/ +SQLITE_PRIVATE i16 sqlite3TableColumnToStorage(Table *pTab, i16 iCol){ + int i; + i16 n; + assert( iCol<pTab->nCol ); + if( (pTab->tabFlags & TF_HasVirtual)==0 || iCol<0 ) return iCol; + for(i=0, n=0; i<iCol; i++){ + if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ) n++; + } + if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ){ + /* iCol is a virtual column itself */ + return pTab->nNVCol + i - n; + }else{ + /* iCol is a normal or stored column */ + return n; + } +} +#endif + +/* +** Insert a single OP_JournalMode query opcode in order to force the +** prepared statement to return false for sqlite3_stmt_readonly(). This +** is used by CREATE TABLE IF NOT EXISTS and similar if the table already +** exists, so that the prepared statement for CREATE TABLE IF NOT EXISTS +** will return false for sqlite3_stmt_readonly() even if that statement +** is a read-only no-op. +*/ +static void sqlite3ForceNotReadOnly(Parse *pParse){ + int iReg = ++pParse->nMem; + Vdbe *v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3VdbeAddOp3(v, OP_JournalMode, 0, iReg, PAGER_JOURNALMODE_QUERY); + sqlite3VdbeUsesBtree(v, 0); + } +} + +/* +** Begin constructing a new table representation in memory. This is +** the first of several action routines that get called in response +** to a CREATE TABLE statement. In particular, this routine is called +** after seeing tokens "CREATE" and "TABLE" and the table name. The isTemp +** flag is true if the table should be stored in the auxiliary database +** file instead of in the main database file. This is normally the case +** when the "TEMP" or "TEMPORARY" keyword occurs in between +** CREATE and TABLE. +** +** The new table record is initialized and put in pParse->pNewTable. +** As more of the CREATE TABLE statement is parsed, additional action +** routines will be called to add more information to this record. +** At the end of the CREATE TABLE statement, the sqlite3EndTable() routine +** is called to complete the construction of the new table record. +*/ +SQLITE_PRIVATE void sqlite3StartTable( + Parse *pParse, /* Parser context */ + Token *pName1, /* First part of the name of the table or view */ + Token *pName2, /* Second part of the name of the table or view */ + int isTemp, /* True if this is a TEMP table */ + int isView, /* True if this is a VIEW */ + int isVirtual, /* True if this is a VIRTUAL table */ + int noErr /* Do nothing if table already exists */ +){ + Table *pTable; + char *zName = 0; /* The name of the new table */ + sqlite3 *db = pParse->db; + Vdbe *v; + int iDb; /* Database number to create the table in */ + Token *pName; /* Unqualified name of the table to create */ + + if( db->init.busy && db->init.newTnum==1 ){ + /* Special case: Parsing the sqlite_schema or sqlite_temp_schema schema */ + iDb = db->init.iDb; + zName = sqlite3DbStrDup(db, SCHEMA_TABLE(iDb)); + pName = pName1; + }else{ + /* The common case */ + iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); + if( iDb<0 ) return; + if( !OMIT_TEMPDB && isTemp && pName2->n>0 && iDb!=1 ){ + /* If creating a temp table, the name may not be qualified. Unless + ** the database name is "temp" anyway. */ + sqlite3ErrorMsg(pParse, "temporary table name must be unqualified"); + return; + } + if( !OMIT_TEMPDB && isTemp ) iDb = 1; + zName = sqlite3NameFromToken(db, pName); + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, (void*)zName, pName); + } + } + pParse->sNameToken = *pName; + if( zName==0 ) return; + if( sqlite3CheckObjectName(pParse, zName, isView?"view":"table", zName) ){ + goto begin_table_error; + } + if( db->init.iDb==1 ) isTemp = 1; +#ifndef SQLITE_OMIT_AUTHORIZATION + assert( isTemp==0 || isTemp==1 ); + assert( isView==0 || isView==1 ); + { + static const u8 aCode[] = { + SQLITE_CREATE_TABLE, + SQLITE_CREATE_TEMP_TABLE, + SQLITE_CREATE_VIEW, + SQLITE_CREATE_TEMP_VIEW + }; + char *zDb = db->aDb[iDb].zDbSName; + if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){ + goto begin_table_error; + } + if( !isVirtual && sqlite3AuthCheck(pParse, (int)aCode[isTemp+2*isView], + zName, 0, zDb) ){ + goto begin_table_error; + } + } +#endif + + /* Make sure the new table name does not collide with an existing + ** index or table name in the same database. Issue an error message if + ** it does. The exception is if the statement being parsed was passed + ** to an sqlite3_declare_vtab() call. In that case only the column names + ** and types will be used, so there is no need to test for namespace + ** collisions. + */ + if( !IN_SPECIAL_PARSE ){ + char *zDb = db->aDb[iDb].zDbSName; + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ + goto begin_table_error; + } + pTable = sqlite3FindTable(db, zName, zDb); + if( pTable ){ + if( !noErr ){ + sqlite3ErrorMsg(pParse, "%s %T already exists", + (IsView(pTable)? "view" : "table"), pName); + }else{ + assert( !db->init.busy || CORRUPT_DB ); + sqlite3CodeVerifySchema(pParse, iDb); + sqlite3ForceNotReadOnly(pParse); + } + goto begin_table_error; + } + if( sqlite3FindIndex(db, zName, zDb)!=0 ){ + sqlite3ErrorMsg(pParse, "there is already an index named %s", zName); + goto begin_table_error; + } + } + + pTable = sqlite3DbMallocZero(db, sizeof(Table)); + if( pTable==0 ){ + assert( db->mallocFailed ); + pParse->rc = SQLITE_NOMEM_BKPT; + pParse->nErr++; + goto begin_table_error; + } + pTable->zName = zName; + pTable->iPKey = -1; + pTable->pSchema = db->aDb[iDb].pSchema; + pTable->nTabRef = 1; +#ifdef SQLITE_DEFAULT_ROWEST + pTable->nRowLogEst = sqlite3LogEst(SQLITE_DEFAULT_ROWEST); +#else + pTable->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); +#endif + assert( pParse->pNewTable==0 ); + pParse->pNewTable = pTable; + + /* Begin generating the code that will insert the table record into + ** the schema table. Note in particular that we must go ahead + ** and allocate the record number for the table entry now. Before any + ** PRIMARY KEY or UNIQUE keywords are parsed. Those keywords will cause + ** indices to be created and the table record must come before the + ** indices. Hence, the record number for the table must be allocated + ** now. + */ + if( !db->init.busy && (v = sqlite3GetVdbe(pParse))!=0 ){ + int addr1; + int fileFormat; + int reg1, reg2, reg3; + /* nullRow[] is an OP_Record encoding of a row containing 5 NULLs */ + static const char nullRow[] = { 6, 0, 0, 0, 0, 0 }; + sqlite3BeginWriteOperation(pParse, 1, iDb); + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( isVirtual ){ + sqlite3VdbeAddOp0(v, OP_VBegin); + } +#endif + + /* If the file format and encoding in the database have not been set, + ** set them now. + */ + reg1 = pParse->regRowid = ++pParse->nMem; + reg2 = pParse->regRoot = ++pParse->nMem; + reg3 = ++pParse->nMem; + sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, reg3, BTREE_FILE_FORMAT); + sqlite3VdbeUsesBtree(v, iDb); + addr1 = sqlite3VdbeAddOp1(v, OP_If, reg3); VdbeCoverage(v); + fileFormat = (db->flags & SQLITE_LegacyFileFmt)!=0 ? + 1 : SQLITE_MAX_FILE_FORMAT; + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, fileFormat); + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_TEXT_ENCODING, ENC(db)); + sqlite3VdbeJumpHere(v, addr1); + + /* This just creates a place-holder record in the sqlite_schema table. + ** The record created does not contain anything yet. It will be replaced + ** by the real entry in code generated at sqlite3EndTable(). + ** + ** The rowid for the new entry is left in register pParse->regRowid. + ** The root page number of the new table is left in reg pParse->regRoot. + ** The rowid and root page number values are needed by the code that + ** sqlite3EndTable will generate. + */ +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) + if( isView || isVirtual ){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, reg2); + }else +#endif + { + assert( !pParse->bReturning ); + pParse->u1.addrCrTab = + sqlite3VdbeAddOp3(v, OP_CreateBtree, iDb, reg2, BTREE_INTKEY); + } + sqlite3OpenSchemaTable(pParse, iDb); + sqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1); + sqlite3VdbeAddOp4(v, OP_Blob, 6, reg3, 0, nullRow, P4_STATIC); + sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1); + sqlite3VdbeChangeP5(v, OPFLAG_APPEND); + sqlite3VdbeAddOp0(v, OP_Close); + } + + /* Normal (non-error) return. */ + return; + + /* If an error occurs, we jump here */ +begin_table_error: + pParse->checkSchema = 1; + sqlite3DbFree(db, zName); + return; +} + +/* Set properties of a table column based on the (magical) +** name of the column. +*/ +#if SQLITE_ENABLE_HIDDEN_COLUMNS +SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){ + if( sqlite3_strnicmp(pCol->zCnName, "__hidden__", 10)==0 ){ + pCol->colFlags |= COLFLAG_HIDDEN; + if( pTab ) pTab->tabFlags |= TF_HasHidden; + }else if( pTab && pCol!=pTab->aCol && (pCol[-1].colFlags & COLFLAG_HIDDEN) ){ + pTab->tabFlags |= TF_OOOHidden; + } +} +#endif + +/* +** Name of the special TEMP trigger used to implement RETURNING. The +** name begins with "sqlite_" so that it is guaranteed not to collide +** with any application-generated triggers. +*/ +#define RETURNING_TRIGGER_NAME "sqlite_returning" + +/* +** Clean up the data structures associated with the RETURNING clause. +*/ +static void sqlite3DeleteReturning(sqlite3 *db, Returning *pRet){ + Hash *pHash; + pHash = &(db->aDb[1].pSchema->trigHash); + sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, 0); + sqlite3ExprListDelete(db, pRet->pReturnEL); + sqlite3DbFree(db, pRet); +} + +/* +** Add the RETURNING clause to the parse currently underway. +** +** This routine creates a special TEMP trigger that will fire for each row +** of the DML statement. That TEMP trigger contains a single SELECT +** statement with a result set that is the argument of the RETURNING clause. +** The trigger has the Trigger.bReturning flag and an opcode of +** TK_RETURNING instead of TK_SELECT, so that the trigger code generator +** knows to handle it specially. The TEMP trigger is automatically +** removed at the end of the parse. +** +** When this routine is called, we do not yet know if the RETURNING clause +** is attached to a DELETE, INSERT, or UPDATE, so construct it as a +** RETURNING trigger instead. It will then be converted into the appropriate +** type on the first call to sqlite3TriggersExist(). +*/ +SQLITE_PRIVATE void sqlite3AddReturning(Parse *pParse, ExprList *pList){ + Returning *pRet; + Hash *pHash; + sqlite3 *db = pParse->db; + if( pParse->pNewTrigger ){ + sqlite3ErrorMsg(pParse, "cannot use RETURNING in a trigger"); + }else{ + assert( pParse->bReturning==0 || pParse->ifNotExists ); + } + pParse->bReturning = 1; + pRet = sqlite3DbMallocZero(db, sizeof(*pRet)); + if( pRet==0 ){ + sqlite3ExprListDelete(db, pList); + return; + } + pParse->u1.pReturning = pRet; + pRet->pParse = pParse; + pRet->pReturnEL = pList; + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3DeleteReturning, pRet); + testcase( pParse->earlyCleanup ); + if( db->mallocFailed ) return; + pRet->retTrig.zName = RETURNING_TRIGGER_NAME; + pRet->retTrig.op = TK_RETURNING; + pRet->retTrig.tr_tm = TRIGGER_AFTER; + pRet->retTrig.bReturning = 1; + pRet->retTrig.pSchema = db->aDb[1].pSchema; + pRet->retTrig.pTabSchema = db->aDb[1].pSchema; + pRet->retTrig.step_list = &pRet->retTStep; + pRet->retTStep.op = TK_RETURNING; + pRet->retTStep.pTrig = &pRet->retTrig; + pRet->retTStep.pExprList = pList; + pHash = &(db->aDb[1].pSchema->trigHash); + assert( sqlite3HashFind(pHash, RETURNING_TRIGGER_NAME)==0 + || pParse->nErr || pParse->ifNotExists ); + if( sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, &pRet->retTrig) + ==&pRet->retTrig ){ + sqlite3OomFault(db); + } +} + +/* +** Add a new column to the table currently being constructed. +** +** The parser calls this routine once for each column declaration +** in a CREATE TABLE statement. sqlite3StartTable() gets called +** first to get things going. Then this routine is called for each +** column. +*/ +SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token sName, Token sType){ + Table *p; + int i; + char *z; + char *zType; + Column *pCol; + sqlite3 *db = pParse->db; + u8 hName; + Column *aNew; + u8 eType = COLTYPE_CUSTOM; + u8 szEst = 1; + char affinity = SQLITE_AFF_BLOB; + + if( (p = pParse->pNewTable)==0 ) return; + if( p->nCol+1>db->aLimit[SQLITE_LIMIT_COLUMN] ){ + sqlite3ErrorMsg(pParse, "too many columns on %s", p->zName); + return; + } + if( !IN_RENAME_OBJECT ) sqlite3DequoteToken(&sName); + + /* Because keywords GENERATE ALWAYS can be converted into identifiers + ** by the parser, we can sometimes end up with a typename that ends + ** with "generated always". Check for this case and omit the surplus + ** text. */ + if( sType.n>=16 + && sqlite3_strnicmp(sType.z+(sType.n-6),"always",6)==0 + ){ + sType.n -= 6; + while( ALWAYS(sType.n>0) && sqlite3Isspace(sType.z[sType.n-1]) ) sType.n--; + if( sType.n>=9 + && sqlite3_strnicmp(sType.z+(sType.n-9),"generated",9)==0 + ){ + sType.n -= 9; + while( sType.n>0 && sqlite3Isspace(sType.z[sType.n-1]) ) sType.n--; + } + } + + /* Check for standard typenames. For standard typenames we will + ** set the Column.eType field rather than storing the typename after + ** the column name, in order to save space. */ + if( sType.n>=3 ){ + sqlite3DequoteToken(&sType); + for(i=0; i<SQLITE_N_STDTYPE; i++){ + if( sType.n==sqlite3StdTypeLen[i] + && sqlite3_strnicmp(sType.z, sqlite3StdType[i], sType.n)==0 + ){ + sType.n = 0; + eType = i+1; + affinity = sqlite3StdTypeAffinity[i]; + if( affinity<=SQLITE_AFF_TEXT ) szEst = 5; + break; + } + } + } + + z = sqlite3DbMallocRaw(db, (i64)sName.n + 1 + (i64)sType.n + (sType.n>0) ); + if( z==0 ) return; + if( IN_RENAME_OBJECT ) sqlite3RenameTokenMap(pParse, (void*)z, &sName); + memcpy(z, sName.z, sName.n); + z[sName.n] = 0; + sqlite3Dequote(z); + hName = sqlite3StrIHash(z); + for(i=0; i<p->nCol; i++){ + if( p->aCol[i].hName==hName && sqlite3StrICmp(z, p->aCol[i].zCnName)==0 ){ + sqlite3ErrorMsg(pParse, "duplicate column name: %s", z); + sqlite3DbFree(db, z); + return; + } + } + aNew = sqlite3DbRealloc(db,p->aCol,((i64)p->nCol+1)*sizeof(p->aCol[0])); + if( aNew==0 ){ + sqlite3DbFree(db, z); + return; + } + p->aCol = aNew; + pCol = &p->aCol[p->nCol]; + memset(pCol, 0, sizeof(p->aCol[0])); + pCol->zCnName = z; + pCol->hName = hName; + sqlite3ColumnPropertiesFromName(p, pCol); + + if( sType.n==0 ){ + /* If there is no type specified, columns have the default affinity + ** 'BLOB' with a default size of 4 bytes. */ + pCol->affinity = affinity; + pCol->eCType = eType; + pCol->szEst = szEst; +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + if( affinity==SQLITE_AFF_BLOB ){ + if( 4>=sqlite3GlobalConfig.szSorterRef ){ + pCol->colFlags |= COLFLAG_SORTERREF; + } + } +#endif + }else{ + zType = z + sqlite3Strlen30(z) + 1; + memcpy(zType, sType.z, sType.n); + zType[sType.n] = 0; + sqlite3Dequote(zType); + pCol->affinity = sqlite3AffinityType(zType, pCol); + pCol->colFlags |= COLFLAG_HASTYPE; + } + p->nCol++; + p->nNVCol++; + pParse->constraintName.n = 0; +} + +/* +** This routine is called by the parser while in the middle of +** parsing a CREATE TABLE statement. A "NOT NULL" constraint has +** been seen on a column. This routine sets the notNull flag on +** the column currently under construction. +*/ +SQLITE_PRIVATE void sqlite3AddNotNull(Parse *pParse, int onError){ + Table *p; + Column *pCol; + p = pParse->pNewTable; + if( p==0 || NEVER(p->nCol<1) ) return; + pCol = &p->aCol[p->nCol-1]; + pCol->notNull = (u8)onError; + p->tabFlags |= TF_HasNotNull; + + /* Set the uniqNotNull flag on any UNIQUE or PK indexes already created + ** on this column. */ + if( pCol->colFlags & COLFLAG_UNIQUE ){ + Index *pIdx; + for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ + assert( pIdx->nKeyCol==1 && pIdx->onError!=OE_None ); + if( pIdx->aiColumn[0]==p->nCol-1 ){ + pIdx->uniqNotNull = 1; + } + } + } +} + +/* +** Scan the column type name zType (length nType) and return the +** associated affinity type. +** +** This routine does a case-independent search of zType for the +** substrings in the following table. If one of the substrings is +** found, the corresponding affinity is returned. If zType contains +** more than one of the substrings, entries toward the top of +** the table take priority. For example, if zType is 'BLOBINT', +** SQLITE_AFF_INTEGER is returned. +** +** Substring | Affinity +** -------------------------------- +** 'INT' | SQLITE_AFF_INTEGER +** 'CHAR' | SQLITE_AFF_TEXT +** 'CLOB' | SQLITE_AFF_TEXT +** 'TEXT' | SQLITE_AFF_TEXT +** 'BLOB' | SQLITE_AFF_BLOB +** 'REAL' | SQLITE_AFF_REAL +** 'FLOA' | SQLITE_AFF_REAL +** 'DOUB' | SQLITE_AFF_REAL +** +** If none of the substrings in the above table are found, +** SQLITE_AFF_NUMERIC is returned. +*/ +SQLITE_PRIVATE char sqlite3AffinityType(const char *zIn, Column *pCol){ + u32 h = 0; + char aff = SQLITE_AFF_NUMERIC; + const char *zChar = 0; + + assert( zIn!=0 ); + while( zIn[0] ){ + h = (h<<8) + sqlite3UpperToLower[(*zIn)&0xff]; + zIn++; + if( h==(('c'<<24)+('h'<<16)+('a'<<8)+'r') ){ /* CHAR */ + aff = SQLITE_AFF_TEXT; + zChar = zIn; + }else if( h==(('c'<<24)+('l'<<16)+('o'<<8)+'b') ){ /* CLOB */ + aff = SQLITE_AFF_TEXT; + }else if( h==(('t'<<24)+('e'<<16)+('x'<<8)+'t') ){ /* TEXT */ + aff = SQLITE_AFF_TEXT; + }else if( h==(('b'<<24)+('l'<<16)+('o'<<8)+'b') /* BLOB */ + && (aff==SQLITE_AFF_NUMERIC || aff==SQLITE_AFF_REAL) ){ + aff = SQLITE_AFF_BLOB; + if( zIn[0]=='(' ) zChar = zIn; +#ifndef SQLITE_OMIT_FLOATING_POINT + }else if( h==(('r'<<24)+('e'<<16)+('a'<<8)+'l') /* REAL */ + && aff==SQLITE_AFF_NUMERIC ){ + aff = SQLITE_AFF_REAL; + }else if( h==(('f'<<24)+('l'<<16)+('o'<<8)+'a') /* FLOA */ + && aff==SQLITE_AFF_NUMERIC ){ + aff = SQLITE_AFF_REAL; + }else if( h==(('d'<<24)+('o'<<16)+('u'<<8)+'b') /* DOUB */ + && aff==SQLITE_AFF_NUMERIC ){ + aff = SQLITE_AFF_REAL; +#endif + }else if( (h&0x00FFFFFF)==(('i'<<16)+('n'<<8)+'t') ){ /* INT */ + aff = SQLITE_AFF_INTEGER; + break; + } + } + + /* If pCol is not NULL, store an estimate of the field size. The + ** estimate is scaled so that the size of an integer is 1. */ + if( pCol ){ + int v = 0; /* default size is approx 4 bytes */ + if( aff<SQLITE_AFF_NUMERIC ){ + if( zChar ){ + while( zChar[0] ){ + if( sqlite3Isdigit(zChar[0]) ){ + /* BLOB(k), VARCHAR(k), CHAR(k) -> r=(k/4+1) */ + sqlite3GetInt32(zChar, &v); + break; + } + zChar++; + } + }else{ + v = 16; /* BLOB, TEXT, CLOB -> r=5 (approx 20 bytes)*/ + } + } +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + if( v>=sqlite3GlobalConfig.szSorterRef ){ + pCol->colFlags |= COLFLAG_SORTERREF; + } +#endif + v = v/4 + 1; + if( v>255 ) v = 255; + pCol->szEst = v; + } + return aff; +} + +/* +** The expression is the default value for the most recently added column +** of the table currently under construction. +** +** Default value expressions must be constant. Raise an exception if this +** is not the case. +** +** This routine is called by the parser while in the middle of +** parsing a CREATE TABLE statement. +*/ +SQLITE_PRIVATE void sqlite3AddDefaultValue( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* The parsed expression of the default value */ + const char *zStart, /* Start of the default value text */ + const char *zEnd /* First character past end of default value text */ +){ + Table *p; + Column *pCol; + sqlite3 *db = pParse->db; + p = pParse->pNewTable; + if( p!=0 ){ + int isInit = db->init.busy && db->init.iDb!=1; + pCol = &(p->aCol[p->nCol-1]); + if( !sqlite3ExprIsConstantOrFunction(pExpr, isInit) ){ + sqlite3ErrorMsg(pParse, "default value of column [%s] is not constant", + pCol->zCnName); +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + }else if( pCol->colFlags & COLFLAG_GENERATED ){ + testcase( pCol->colFlags & COLFLAG_VIRTUAL ); + testcase( pCol->colFlags & COLFLAG_STORED ); + sqlite3ErrorMsg(pParse, "cannot use DEFAULT on a generated column"); +#endif + }else{ + /* A copy of pExpr is used instead of the original, as pExpr contains + ** tokens that point to volatile memory. + */ + Expr x, *pDfltExpr; + memset(&x, 0, sizeof(x)); + x.op = TK_SPAN; + x.u.zToken = sqlite3DbSpanDup(db, zStart, zEnd); + x.pLeft = pExpr; + x.flags = EP_Skip; + pDfltExpr = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE); + sqlite3DbFree(db, x.u.zToken); + sqlite3ColumnSetExpr(pParse, p, pCol, pDfltExpr); + } + } + if( IN_RENAME_OBJECT ){ + sqlite3RenameExprUnmap(pParse, pExpr); + } + sqlite3ExprDelete(db, pExpr); +} + +/* +** Backwards Compatibility Hack: +** +** Historical versions of SQLite accepted strings as column names in +** indexes and PRIMARY KEY constraints and in UNIQUE constraints. Example: +** +** CREATE TABLE xyz(a,b,c,d,e,PRIMARY KEY('a'),UNIQUE('b','c' COLLATE trim) +** CREATE INDEX abc ON xyz('c','d' DESC,'e' COLLATE nocase DESC); +** +** This is goofy. But to preserve backwards compatibility we continue to +** accept it. This routine does the necessary conversion. It converts +** the expression given in its argument from a TK_STRING into a TK_ID +** if the expression is just a TK_STRING with an optional COLLATE clause. +** If the expression is anything other than TK_STRING, the expression is +** unchanged. +*/ +static void sqlite3StringToId(Expr *p){ + if( p->op==TK_STRING ){ + p->op = TK_ID; + }else if( p->op==TK_COLLATE && p->pLeft->op==TK_STRING ){ + p->pLeft->op = TK_ID; + } +} + +/* +** Tag the given column as being part of the PRIMARY KEY +*/ +static void makeColumnPartOfPrimaryKey(Parse *pParse, Column *pCol){ + pCol->colFlags |= COLFLAG_PRIMKEY; +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( pCol->colFlags & COLFLAG_GENERATED ){ + testcase( pCol->colFlags & COLFLAG_VIRTUAL ); + testcase( pCol->colFlags & COLFLAG_STORED ); + sqlite3ErrorMsg(pParse, + "generated columns cannot be part of the PRIMARY KEY"); + } +#endif +} + +/* +** Designate the PRIMARY KEY for the table. pList is a list of names +** of columns that form the primary key. If pList is NULL, then the +** most recently added column of the table is the primary key. +** +** A table can have at most one primary key. If the table already has +** a primary key (and this is the second primary key) then create an +** error. +** +** If the PRIMARY KEY is on a single column whose datatype is INTEGER, +** then we will try to use that column as the rowid. Set the Table.iPKey +** field of the table under construction to be the index of the +** INTEGER PRIMARY KEY column. Table.iPKey is set to -1 if there is +** no INTEGER PRIMARY KEY. +** +** If the key is not an INTEGER PRIMARY KEY, then create a unique +** index for the key. No index is created for INTEGER PRIMARY KEYs. +*/ +SQLITE_PRIVATE void sqlite3AddPrimaryKey( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* List of field names to be indexed */ + int onError, /* What to do with a uniqueness conflict */ + int autoInc, /* True if the AUTOINCREMENT keyword is present */ + int sortOrder /* SQLITE_SO_ASC or SQLITE_SO_DESC */ +){ + Table *pTab = pParse->pNewTable; + Column *pCol = 0; + int iCol = -1, i; + int nTerm; + if( pTab==0 ) goto primary_key_exit; + if( pTab->tabFlags & TF_HasPrimaryKey ){ + sqlite3ErrorMsg(pParse, + "table \"%s\" has more than one primary key", pTab->zName); + goto primary_key_exit; + } + pTab->tabFlags |= TF_HasPrimaryKey; + if( pList==0 ){ + iCol = pTab->nCol - 1; + pCol = &pTab->aCol[iCol]; + makeColumnPartOfPrimaryKey(pParse, pCol); + nTerm = 1; + }else{ + nTerm = pList->nExpr; + for(i=0; i<nTerm; i++){ + Expr *pCExpr = sqlite3ExprSkipCollate(pList->a[i].pExpr); + assert( pCExpr!=0 ); + sqlite3StringToId(pCExpr); + if( pCExpr->op==TK_ID ){ + const char *zCName; + assert( !ExprHasProperty(pCExpr, EP_IntValue) ); + zCName = pCExpr->u.zToken; + for(iCol=0; iCol<pTab->nCol; iCol++){ + if( sqlite3StrICmp(zCName, pTab->aCol[iCol].zCnName)==0 ){ + pCol = &pTab->aCol[iCol]; + makeColumnPartOfPrimaryKey(pParse, pCol); + break; + } + } + } + } + } + if( nTerm==1 + && pCol + && pCol->eCType==COLTYPE_INTEGER + && sortOrder!=SQLITE_SO_DESC + ){ + if( IN_RENAME_OBJECT && pList ){ + Expr *pCExpr = sqlite3ExprSkipCollate(pList->a[0].pExpr); + sqlite3RenameTokenRemap(pParse, &pTab->iPKey, pCExpr); + } + pTab->iPKey = iCol; + pTab->keyConf = (u8)onError; + assert( autoInc==0 || autoInc==1 ); + pTab->tabFlags |= autoInc*TF_Autoincrement; + if( pList ) pParse->iPkSortOrder = pList->a[0].fg.sortFlags; + (void)sqlite3HasExplicitNulls(pParse, pList); + }else if( autoInc ){ +#ifndef SQLITE_OMIT_AUTOINCREMENT + sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an " + "INTEGER PRIMARY KEY"); +#endif + }else{ + sqlite3CreateIndex(pParse, 0, 0, 0, pList, onError, 0, + 0, sortOrder, 0, SQLITE_IDXTYPE_PRIMARYKEY); + pList = 0; + } + +primary_key_exit: + sqlite3ExprListDelete(pParse->db, pList); + return; +} + +/* +** Add a new CHECK constraint to the table currently under construction. +*/ +SQLITE_PRIVATE void sqlite3AddCheckConstraint( + Parse *pParse, /* Parsing context */ + Expr *pCheckExpr, /* The check expression */ + const char *zStart, /* Opening "(" */ + const char *zEnd /* Closing ")" */ +){ +#ifndef SQLITE_OMIT_CHECK + Table *pTab = pParse->pNewTable; + sqlite3 *db = pParse->db; + if( pTab && !IN_DECLARE_VTAB + && !sqlite3BtreeIsReadonly(db->aDb[db->init.iDb].pBt) + ){ + pTab->pCheck = sqlite3ExprListAppend(pParse, pTab->pCheck, pCheckExpr); + if( pParse->constraintName.n ){ + sqlite3ExprListSetName(pParse, pTab->pCheck, &pParse->constraintName, 1); + }else{ + Token t; + for(zStart++; sqlite3Isspace(zStart[0]); zStart++){} + while( sqlite3Isspace(zEnd[-1]) ){ zEnd--; } + t.z = zStart; + t.n = (int)(zEnd - t.z); + sqlite3ExprListSetName(pParse, pTab->pCheck, &t, 1); + } + }else +#endif + { + sqlite3ExprDelete(pParse->db, pCheckExpr); + } +} + +/* +** Set the collation function of the most recently parsed table column +** to the CollSeq given. +*/ +SQLITE_PRIVATE void sqlite3AddCollateType(Parse *pParse, Token *pToken){ + Table *p; + int i; + char *zColl; /* Dequoted name of collation sequence */ + sqlite3 *db; + + if( (p = pParse->pNewTable)==0 || IN_RENAME_OBJECT ) return; + i = p->nCol-1; + db = pParse->db; + zColl = sqlite3NameFromToken(db, pToken); + if( !zColl ) return; + + if( sqlite3LocateCollSeq(pParse, zColl) ){ + Index *pIdx; + sqlite3ColumnSetColl(db, &p->aCol[i], zColl); + + /* If the column is declared as "<name> PRIMARY KEY COLLATE <type>", + ** then an index may have been created on this column before the + ** collation type was added. Correct this if it is the case. + */ + for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ + assert( pIdx->nKeyCol==1 ); + if( pIdx->aiColumn[0]==i ){ + pIdx->azColl[0] = sqlite3ColumnColl(&p->aCol[i]); + } + } + } + sqlite3DbFree(db, zColl); +} + +/* Change the most recently parsed column to be a GENERATED ALWAYS AS +** column. +*/ +SQLITE_PRIVATE void sqlite3AddGenerated(Parse *pParse, Expr *pExpr, Token *pType){ +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + u8 eType = COLFLAG_VIRTUAL; + Table *pTab = pParse->pNewTable; + Column *pCol; + if( pTab==0 ){ + /* generated column in an CREATE TABLE IF NOT EXISTS that already exists */ + goto generated_done; + } + pCol = &(pTab->aCol[pTab->nCol-1]); + if( IN_DECLARE_VTAB ){ + sqlite3ErrorMsg(pParse, "virtual tables cannot use computed columns"); + goto generated_done; + } + if( pCol->iDflt>0 ) goto generated_error; + if( pType ){ + if( pType->n==7 && sqlite3StrNICmp("virtual",pType->z,7)==0 ){ + /* no-op */ + }else if( pType->n==6 && sqlite3StrNICmp("stored",pType->z,6)==0 ){ + eType = COLFLAG_STORED; + }else{ + goto generated_error; + } + } + if( eType==COLFLAG_VIRTUAL ) pTab->nNVCol--; + pCol->colFlags |= eType; + assert( TF_HasVirtual==COLFLAG_VIRTUAL ); + assert( TF_HasStored==COLFLAG_STORED ); + pTab->tabFlags |= eType; + if( pCol->colFlags & COLFLAG_PRIMKEY ){ + makeColumnPartOfPrimaryKey(pParse, pCol); /* For the error message */ + } + if( ALWAYS(pExpr) && pExpr->op==TK_ID ){ + /* The value of a generated column needs to be a real expression, not + ** just a reference to another column, in order for covering index + ** optimizations to work correctly. So if the value is not an expression, + ** turn it into one by adding a unary "+" operator. */ + pExpr = sqlite3PExpr(pParse, TK_UPLUS, pExpr, 0); + } + if( pExpr && pExpr->op!=TK_RAISE ) pExpr->affExpr = pCol->affinity; + sqlite3ColumnSetExpr(pParse, pTab, pCol, pExpr); + pExpr = 0; + goto generated_done; + +generated_error: + sqlite3ErrorMsg(pParse, "error in generated column \"%s\"", + pCol->zCnName); +generated_done: + sqlite3ExprDelete(pParse->db, pExpr); +#else + /* Throw and error for the GENERATED ALWAYS AS clause if the + ** SQLITE_OMIT_GENERATED_COLUMNS compile-time option is used. */ + sqlite3ErrorMsg(pParse, "generated columns not supported"); + sqlite3ExprDelete(pParse->db, pExpr); +#endif +} + +/* +** Generate code that will increment the schema cookie. +** +** The schema cookie is used to determine when the schema for the +** database changes. After each schema change, the cookie value +** changes. When a process first reads the schema it records the +** cookie. Thereafter, whenever it goes to access the database, +** it checks the cookie to make sure the schema has not changed +** since it was last read. +** +** This plan is not completely bullet-proof. It is possible for +** the schema to change multiple times and for the cookie to be +** set back to prior value. But schema changes are infrequent +** and the probability of hitting the same cookie value is only +** 1 chance in 2^32. So we're safe enough. +** +** IMPLEMENTATION-OF: R-34230-56049 SQLite automatically increments +** the schema-version whenever the schema changes. +*/ +SQLITE_PRIVATE void sqlite3ChangeCookie(Parse *pParse, int iDb){ + sqlite3 *db = pParse->db; + Vdbe *v = pParse->pVdbe; + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_SCHEMA_VERSION, + (int)(1+(unsigned)db->aDb[iDb].pSchema->schema_cookie)); +} + +/* +** Measure the number of characters needed to output the given +** identifier. The number returned includes any quotes used +** but does not include the null terminator. +** +** The estimate is conservative. It might be larger that what is +** really needed. +*/ +static int identLength(const char *z){ + int n; + for(n=0; *z; n++, z++){ + if( *z=='"' ){ n++; } + } + return n + 2; +} + +/* +** The first parameter is a pointer to an output buffer. The second +** parameter is a pointer to an integer that contains the offset at +** which to write into the output buffer. This function copies the +** nul-terminated string pointed to by the third parameter, zSignedIdent, +** to the specified offset in the buffer and updates *pIdx to refer +** to the first byte after the last byte written before returning. +** +** If the string zSignedIdent consists entirely of alphanumeric +** characters, does not begin with a digit and is not an SQL keyword, +** then it is copied to the output buffer exactly as it is. Otherwise, +** it is quoted using double-quotes. +*/ +static void identPut(char *z, int *pIdx, char *zSignedIdent){ + unsigned char *zIdent = (unsigned char*)zSignedIdent; + int i, j, needQuote; + i = *pIdx; + + for(j=0; zIdent[j]; j++){ + if( !sqlite3Isalnum(zIdent[j]) && zIdent[j]!='_' ) break; + } + needQuote = sqlite3Isdigit(zIdent[0]) + || sqlite3KeywordCode(zIdent, j)!=TK_ID + || zIdent[j]!=0 + || j==0; + + if( needQuote ) z[i++] = '"'; + for(j=0; zIdent[j]; j++){ + z[i++] = zIdent[j]; + if( zIdent[j]=='"' ) z[i++] = '"'; + } + if( needQuote ) z[i++] = '"'; + z[i] = 0; + *pIdx = i; +} + +/* +** Generate a CREATE TABLE statement appropriate for the given +** table. Memory to hold the text of the statement is obtained +** from sqliteMalloc() and must be freed by the calling function. +*/ +static char *createTableStmt(sqlite3 *db, Table *p){ + int i, k, n; + char *zStmt; + char *zSep, *zSep2, *zEnd; + Column *pCol; + n = 0; + for(pCol = p->aCol, i=0; i<p->nCol; i++, pCol++){ + n += identLength(pCol->zCnName) + 5; + } + n += identLength(p->zName); + if( n<50 ){ + zSep = ""; + zSep2 = ","; + zEnd = ")"; + }else{ + zSep = "\n "; + zSep2 = ",\n "; + zEnd = "\n)"; + } + n += 35 + 6*p->nCol; + zStmt = sqlite3DbMallocRaw(0, n); + if( zStmt==0 ){ + sqlite3OomFault(db); + return 0; + } + sqlite3_snprintf(n, zStmt, "CREATE TABLE "); + k = sqlite3Strlen30(zStmt); + identPut(zStmt, &k, p->zName); + zStmt[k++] = '('; + for(pCol=p->aCol, i=0; i<p->nCol; i++, pCol++){ + static const char * const azType[] = { + /* SQLITE_AFF_BLOB */ "", + /* SQLITE_AFF_TEXT */ " TEXT", + /* SQLITE_AFF_NUMERIC */ " NUM", + /* SQLITE_AFF_INTEGER */ " INT", + /* SQLITE_AFF_REAL */ " REAL", + /* SQLITE_AFF_FLEXNUM */ " NUM", + }; + int len; + const char *zType; + + sqlite3_snprintf(n-k, &zStmt[k], zSep); + k += sqlite3Strlen30(&zStmt[k]); + zSep = zSep2; + identPut(zStmt, &k, pCol->zCnName); + assert( pCol->affinity-SQLITE_AFF_BLOB >= 0 ); + assert( pCol->affinity-SQLITE_AFF_BLOB < ArraySize(azType) ); + testcase( pCol->affinity==SQLITE_AFF_BLOB ); + testcase( pCol->affinity==SQLITE_AFF_TEXT ); + testcase( pCol->affinity==SQLITE_AFF_NUMERIC ); + testcase( pCol->affinity==SQLITE_AFF_INTEGER ); + testcase( pCol->affinity==SQLITE_AFF_REAL ); + testcase( pCol->affinity==SQLITE_AFF_FLEXNUM ); + + zType = azType[pCol->affinity - SQLITE_AFF_BLOB]; + len = sqlite3Strlen30(zType); + assert( pCol->affinity==SQLITE_AFF_BLOB + || pCol->affinity==SQLITE_AFF_FLEXNUM + || pCol->affinity==sqlite3AffinityType(zType, 0) ); + memcpy(&zStmt[k], zType, len); + k += len; + assert( k<=n ); + } + sqlite3_snprintf(n-k, &zStmt[k], "%s", zEnd); + return zStmt; +} + +/* +** Resize an Index object to hold N columns total. Return SQLITE_OK +** on success and SQLITE_NOMEM on an OOM error. +*/ +static int resizeIndexObject(sqlite3 *db, Index *pIdx, int N){ + char *zExtra; + int nByte; + if( pIdx->nColumn>=N ) return SQLITE_OK; + assert( pIdx->isResized==0 ); + nByte = (sizeof(char*) + sizeof(LogEst) + sizeof(i16) + 1)*N; + zExtra = sqlite3DbMallocZero(db, nByte); + if( zExtra==0 ) return SQLITE_NOMEM_BKPT; + memcpy(zExtra, pIdx->azColl, sizeof(char*)*pIdx->nColumn); + pIdx->azColl = (const char**)zExtra; + zExtra += sizeof(char*)*N; + memcpy(zExtra, pIdx->aiRowLogEst, sizeof(LogEst)*(pIdx->nKeyCol+1)); + pIdx->aiRowLogEst = (LogEst*)zExtra; + zExtra += sizeof(LogEst)*N; + memcpy(zExtra, pIdx->aiColumn, sizeof(i16)*pIdx->nColumn); + pIdx->aiColumn = (i16*)zExtra; + zExtra += sizeof(i16)*N; + memcpy(zExtra, pIdx->aSortOrder, pIdx->nColumn); + pIdx->aSortOrder = (u8*)zExtra; + pIdx->nColumn = N; + pIdx->isResized = 1; + return SQLITE_OK; +} + +/* +** Estimate the total row width for a table. +*/ +static void estimateTableWidth(Table *pTab){ + unsigned wTable = 0; + const Column *pTabCol; + int i; + for(i=pTab->nCol, pTabCol=pTab->aCol; i>0; i--, pTabCol++){ + wTable += pTabCol->szEst; + } + if( pTab->iPKey<0 ) wTable++; + pTab->szTabRow = sqlite3LogEst(wTable*4); +} + +/* +** Estimate the average size of a row for an index. +*/ +static void estimateIndexWidth(Index *pIdx){ + unsigned wIndex = 0; + int i; + const Column *aCol = pIdx->pTable->aCol; + for(i=0; i<pIdx->nColumn; i++){ + i16 x = pIdx->aiColumn[i]; + assert( x<pIdx->pTable->nCol ); + wIndex += x<0 ? 1 : aCol[x].szEst; + } + pIdx->szIdxRow = sqlite3LogEst(wIndex*4); +} + +/* Return true if column number x is any of the first nCol entries of aiCol[]. +** This is used to determine if the column number x appears in any of the +** first nCol entries of an index. +*/ +static int hasColumn(const i16 *aiCol, int nCol, int x){ + while( nCol-- > 0 ){ + if( x==*(aiCol++) ){ + return 1; + } + } + return 0; +} + +/* +** Return true if any of the first nKey entries of index pIdx exactly +** match the iCol-th entry of pPk. pPk is always a WITHOUT ROWID +** PRIMARY KEY index. pIdx is an index on the same table. pIdx may +** or may not be the same index as pPk. +** +** The first nKey entries of pIdx are guaranteed to be ordinary columns, +** not a rowid or expression. +** +** This routine differs from hasColumn() in that both the column and the +** collating sequence must match for this routine, but for hasColumn() only +** the column name must match. +*/ +static int isDupColumn(Index *pIdx, int nKey, Index *pPk, int iCol){ + int i, j; + assert( nKey<=pIdx->nColumn ); + assert( iCol<MAX(pPk->nColumn,pPk->nKeyCol) ); + assert( pPk->idxType==SQLITE_IDXTYPE_PRIMARYKEY ); + assert( pPk->pTable->tabFlags & TF_WithoutRowid ); + assert( pPk->pTable==pIdx->pTable ); + testcase( pPk==pIdx ); + j = pPk->aiColumn[iCol]; + assert( j!=XN_ROWID && j!=XN_EXPR ); + for(i=0; i<nKey; i++){ + assert( pIdx->aiColumn[i]>=0 || j>=0 ); + if( pIdx->aiColumn[i]==j + && sqlite3StrICmp(pIdx->azColl[i], pPk->azColl[iCol])==0 + ){ + return 1; + } + } + return 0; +} + +/* Recompute the colNotIdxed field of the Index. +** +** colNotIdxed is a bitmask that has a 0 bit representing each indexed +** columns that are within the first 63 columns of the table and a 1 for +** all other bits (all columns that are not in the index). The +** high-order bit of colNotIdxed is always 1. All unindexed columns +** of the table have a 1. +** +** 2019-10-24: For the purpose of this computation, virtual columns are +** not considered to be covered by the index, even if they are in the +** index, because we do not trust the logic in whereIndexExprTrans() to be +** able to find all instances of a reference to the indexed table column +** and convert them into references to the index. Hence we always want +** the actual table at hand in order to recompute the virtual column, if +** necessary. +** +** The colNotIdxed mask is AND-ed with the SrcList.a[].colUsed mask +** to determine if the index is covering index. +*/ +static void recomputeColumnsNotIndexed(Index *pIdx){ + Bitmask m = 0; + int j; + Table *pTab = pIdx->pTable; + for(j=pIdx->nColumn-1; j>=0; j--){ + int x = pIdx->aiColumn[j]; + if( x>=0 && (pTab->aCol[x].colFlags & COLFLAG_VIRTUAL)==0 ){ + testcase( x==BMS-1 ); + testcase( x==BMS-2 ); + if( x<BMS-1 ) m |= MASKBIT(x); + } + } + pIdx->colNotIdxed = ~m; + assert( (pIdx->colNotIdxed>>63)==1 ); /* See note-20221022-a */ +} + +/* +** This routine runs at the end of parsing a CREATE TABLE statement that +** has a WITHOUT ROWID clause. The job of this routine is to convert both +** internal schema data structures and the generated VDBE code so that they +** are appropriate for a WITHOUT ROWID table instead of a rowid table. +** Changes include: +** +** (1) Set all columns of the PRIMARY KEY schema object to be NOT NULL. +** (2) Convert P3 parameter of the OP_CreateBtree from BTREE_INTKEY +** into BTREE_BLOBKEY. +** (3) Bypass the creation of the sqlite_schema table entry +** for the PRIMARY KEY as the primary key index is now +** identified by the sqlite_schema table entry of the table itself. +** (4) Set the Index.tnum of the PRIMARY KEY Index object in the +** schema to the rootpage from the main table. +** (5) Add all table columns to the PRIMARY KEY Index object +** so that the PRIMARY KEY is a covering index. The surplus +** columns are part of KeyInfo.nAllField and are not used for +** sorting or lookup or uniqueness checks. +** (6) Replace the rowid tail on all automatically generated UNIQUE +** indices with the PRIMARY KEY columns. +** +** For virtual tables, only (1) is performed. +*/ +static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ + Index *pIdx; + Index *pPk; + int nPk; + int nExtra; + int i, j; + sqlite3 *db = pParse->db; + Vdbe *v = pParse->pVdbe; + + /* Mark every PRIMARY KEY column as NOT NULL (except for imposter tables) + */ + if( !db->init.imposterTable ){ + for(i=0; i<pTab->nCol; i++){ + if( (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 + && (pTab->aCol[i].notNull==OE_None) + ){ + pTab->aCol[i].notNull = OE_Abort; + } + } + pTab->tabFlags |= TF_HasNotNull; + } + + /* Convert the P3 operand of the OP_CreateBtree opcode from BTREE_INTKEY + ** into BTREE_BLOBKEY. + */ + assert( !pParse->bReturning ); + if( pParse->u1.addrCrTab ){ + assert( v ); + sqlite3VdbeChangeP3(v, pParse->u1.addrCrTab, BTREE_BLOBKEY); + } + + /* Locate the PRIMARY KEY index. Or, if this table was originally + ** an INTEGER PRIMARY KEY table, create a new PRIMARY KEY index. + */ + if( pTab->iPKey>=0 ){ + ExprList *pList; + Token ipkToken; + sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zCnName); + pList = sqlite3ExprListAppend(pParse, 0, + sqlite3ExprAlloc(db, TK_ID, &ipkToken, 0)); + if( pList==0 ){ + pTab->tabFlags &= ~TF_WithoutRowid; + return; + } + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenRemap(pParse, pList->a[0].pExpr, &pTab->iPKey); + } + pList->a[0].fg.sortFlags = pParse->iPkSortOrder; + assert( pParse->pNewTable==pTab ); + pTab->iPKey = -1; + sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0, + SQLITE_IDXTYPE_PRIMARYKEY); + if( pParse->nErr ){ + pTab->tabFlags &= ~TF_WithoutRowid; + return; + } + assert( db->mallocFailed==0 ); + pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk->nKeyCol==1 ); + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + + /* + ** Remove all redundant columns from the PRIMARY KEY. For example, change + ** "PRIMARY KEY(a,b,a,b,c,b,c,d)" into just "PRIMARY KEY(a,b,c,d)". Later + ** code assumes the PRIMARY KEY contains no repeated columns. + */ + for(i=j=1; i<pPk->nKeyCol; i++){ + if( isDupColumn(pPk, j, pPk, i) ){ + pPk->nColumn--; + }else{ + testcase( hasColumn(pPk->aiColumn, j, pPk->aiColumn[i]) ); + pPk->azColl[j] = pPk->azColl[i]; + pPk->aSortOrder[j] = pPk->aSortOrder[i]; + pPk->aiColumn[j++] = pPk->aiColumn[i]; + } + } + pPk->nKeyCol = j; + } + assert( pPk!=0 ); + pPk->isCovering = 1; + if( !db->init.imposterTable ) pPk->uniqNotNull = 1; + nPk = pPk->nColumn = pPk->nKeyCol; + + /* Bypass the creation of the PRIMARY KEY btree and the sqlite_schema + ** table entry. This is only required if currently generating VDBE + ** code for a CREATE TABLE (not when parsing one as part of reading + ** a database schema). */ + if( v && pPk->tnum>0 ){ + assert( db->init.busy==0 ); + sqlite3VdbeChangeOpcode(v, (int)pPk->tnum, OP_Goto); + } + + /* The root page of the PRIMARY KEY is the table root page */ + pPk->tnum = pTab->tnum; + + /* Update the in-memory representation of all UNIQUE indices by converting + ** the final rowid column into one or more columns of the PRIMARY KEY. + */ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int n; + if( IsPrimaryKeyIndex(pIdx) ) continue; + for(i=n=0; i<nPk; i++){ + if( !isDupColumn(pIdx, pIdx->nKeyCol, pPk, i) ){ + testcase( hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ); + n++; + } + } + if( n==0 ){ + /* This index is a superset of the primary key */ + pIdx->nColumn = pIdx->nKeyCol; + continue; + } + if( resizeIndexObject(db, pIdx, pIdx->nKeyCol+n) ) return; + for(i=0, j=pIdx->nKeyCol; i<nPk; i++){ + if( !isDupColumn(pIdx, pIdx->nKeyCol, pPk, i) ){ + testcase( hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ); + pIdx->aiColumn[j] = pPk->aiColumn[i]; + pIdx->azColl[j] = pPk->azColl[i]; + if( pPk->aSortOrder[i] ){ + /* See ticket https://www.sqlite.org/src/info/bba7b69f9849b5bf */ + pIdx->bAscKeyBug = 1; + } + j++; + } + } + assert( pIdx->nColumn>=pIdx->nKeyCol+n ); + assert( pIdx->nColumn>=j ); + } + + /* Add all table columns to the PRIMARY KEY index + */ + nExtra = 0; + for(i=0; i<pTab->nCol; i++){ + if( !hasColumn(pPk->aiColumn, nPk, i) + && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ) nExtra++; + } + if( resizeIndexObject(db, pPk, nPk+nExtra) ) return; + for(i=0, j=nPk; i<pTab->nCol; i++){ + if( !hasColumn(pPk->aiColumn, j, i) + && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 + ){ + assert( j<pPk->nColumn ); + pPk->aiColumn[j] = i; + pPk->azColl[j] = sqlite3StrBINARY; + j++; + } + } + assert( pPk->nColumn==j ); + assert( pTab->nNVCol<=j ); + recomputeColumnsNotIndexed(pPk); +} + + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Return true if pTab is a virtual table and zName is a shadow table name +** for that virtual table. +*/ +SQLITE_PRIVATE int sqlite3IsShadowTableOf(sqlite3 *db, Table *pTab, const char *zName){ + int nName; /* Length of zName */ + Module *pMod; /* Module for the virtual table */ + + if( !IsVirtual(pTab) ) return 0; + nName = sqlite3Strlen30(pTab->zName); + if( sqlite3_strnicmp(zName, pTab->zName, nName)!=0 ) return 0; + if( zName[nName]!='_' ) return 0; + pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->u.vtab.azArg[0]); + if( pMod==0 ) return 0; + if( pMod->pModule->iVersion<3 ) return 0; + if( pMod->pModule->xShadowName==0 ) return 0; + return pMod->pModule->xShadowName(zName+nName+1); +} +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Table pTab is a virtual table. If it the virtual table implementation +** exists and has an xShadowName method, then loop over all other ordinary +** tables within the same schema looking for shadow tables of pTab, and mark +** any shadow tables seen using the TF_Shadow flag. +*/ +SQLITE_PRIVATE void sqlite3MarkAllShadowTablesOf(sqlite3 *db, Table *pTab){ + int nName; /* Length of pTab->zName */ + Module *pMod; /* Module for the virtual table */ + HashElem *k; /* For looping through the symbol table */ + + assert( IsVirtual(pTab) ); + pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->u.vtab.azArg[0]); + if( pMod==0 ) return; + if( NEVER(pMod->pModule==0) ) return; + if( pMod->pModule->iVersion<3 ) return; + if( pMod->pModule->xShadowName==0 ) return; + assert( pTab->zName!=0 ); + nName = sqlite3Strlen30(pTab->zName); + for(k=sqliteHashFirst(&pTab->pSchema->tblHash); k; k=sqliteHashNext(k)){ + Table *pOther = sqliteHashData(k); + assert( pOther->zName!=0 ); + if( !IsOrdinaryTable(pOther) ) continue; + if( pOther->tabFlags & TF_Shadow ) continue; + if( sqlite3StrNICmp(pOther->zName, pTab->zName, nName)==0 + && pOther->zName[nName]=='_' + && pMod->pModule->xShadowName(pOther->zName+nName+1) + ){ + pOther->tabFlags |= TF_Shadow; + } + } +} +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Return true if zName is a shadow table name in the current database +** connection. +** +** zName is temporarily modified while this routine is running, but is +** restored to its original value prior to this routine returning. +*/ +SQLITE_PRIVATE int sqlite3ShadowTableName(sqlite3 *db, const char *zName){ + char *zTail; /* Pointer to the last "_" in zName */ + Table *pTab; /* Table that zName is a shadow of */ + zTail = strrchr(zName, '_'); + if( zTail==0 ) return 0; + *zTail = 0; + pTab = sqlite3FindTable(db, zName, 0); + *zTail = '_'; + if( pTab==0 ) return 0; + if( !IsVirtual(pTab) ) return 0; + return sqlite3IsShadowTableOf(db, pTab, zName); +} +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + + +#ifdef SQLITE_DEBUG +/* +** Mark all nodes of an expression as EP_Immutable, indicating that +** they should not be changed. Expressions attached to a table or +** index definition are tagged this way to help ensure that we do +** not pass them into code generator routines by mistake. +*/ +static int markImmutableExprStep(Walker *pWalker, Expr *pExpr){ + (void)pWalker; + ExprSetVVAProperty(pExpr, EP_Immutable); + return WRC_Continue; +} +static void markExprListImmutable(ExprList *pList){ + if( pList ){ + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = markImmutableExprStep; + w.xSelectCallback = sqlite3SelectWalkNoop; + w.xSelectCallback2 = 0; + sqlite3WalkExprList(&w, pList); + } +} +#else +#define markExprListImmutable(X) /* no-op */ +#endif /* SQLITE_DEBUG */ + + +/* +** This routine is called to report the final ")" that terminates +** a CREATE TABLE statement. +** +** The table structure that other action routines have been building +** is added to the internal hash tables, assuming no errors have +** occurred. +** +** An entry for the table is made in the schema table on disk, unless +** this is a temporary table or db->init.busy==1. When db->init.busy==1 +** it means we are reading the sqlite_schema table because we just +** connected to the database or because the sqlite_schema table has +** recently changed, so the entry for this table already exists in +** the sqlite_schema table. We do not want to create it again. +** +** If the pSelect argument is not NULL, it means that this routine +** was called to create a table generated from a +** "CREATE TABLE ... AS SELECT ..." statement. The column names of +** the new table will match the result set of the SELECT. +*/ +SQLITE_PRIVATE void sqlite3EndTable( + Parse *pParse, /* Parse context */ + Token *pCons, /* The ',' token after the last column defn. */ + Token *pEnd, /* The ')' before options in the CREATE TABLE */ + u32 tabOpts, /* Extra table options. Usually 0. */ + Select *pSelect /* Select from a "CREATE ... AS SELECT" */ +){ + Table *p; /* The new table */ + sqlite3 *db = pParse->db; /* The database connection */ + int iDb; /* Database in which the table lives */ + Index *pIdx; /* An implied index of the table */ + + if( pEnd==0 && pSelect==0 ){ + return; + } + p = pParse->pNewTable; + if( p==0 ) return; + + if( pSelect==0 && sqlite3ShadowTableName(db, p->zName) ){ + p->tabFlags |= TF_Shadow; + } + + /* If the db->init.busy is 1 it means we are reading the SQL off the + ** "sqlite_schema" or "sqlite_temp_schema" table on the disk. + ** So do not write to the disk again. Extract the root page number + ** for the table from the db->init.newTnum field. (The page number + ** should have been put there by the sqliteOpenCb routine.) + ** + ** If the root page number is 1, that means this is the sqlite_schema + ** table itself. So mark it read-only. + */ + if( db->init.busy ){ + if( pSelect || (!IsOrdinaryTable(p) && db->init.newTnum) ){ + sqlite3ErrorMsg(pParse, ""); + return; + } + p->tnum = db->init.newTnum; + if( p->tnum==1 ) p->tabFlags |= TF_Readonly; + } + + /* Special processing for tables that include the STRICT keyword: + ** + ** * Do not allow custom column datatypes. Every column must have + ** a datatype that is one of INT, INTEGER, REAL, TEXT, or BLOB. + ** + ** * If a PRIMARY KEY is defined, other than the INTEGER PRIMARY KEY, + ** then all columns of the PRIMARY KEY must have a NOT NULL + ** constraint. + */ + if( tabOpts & TF_Strict ){ + int ii; + p->tabFlags |= TF_Strict; + for(ii=0; ii<p->nCol; ii++){ + Column *pCol = &p->aCol[ii]; + if( pCol->eCType==COLTYPE_CUSTOM ){ + if( pCol->colFlags & COLFLAG_HASTYPE ){ + sqlite3ErrorMsg(pParse, + "unknown datatype for %s.%s: \"%s\"", + p->zName, pCol->zCnName, sqlite3ColumnType(pCol, "") + ); + }else{ + sqlite3ErrorMsg(pParse, "missing datatype for %s.%s", + p->zName, pCol->zCnName); + } + return; + }else if( pCol->eCType==COLTYPE_ANY ){ + pCol->affinity = SQLITE_AFF_BLOB; + } + if( (pCol->colFlags & COLFLAG_PRIMKEY)!=0 + && p->iPKey!=ii + && pCol->notNull == OE_None + ){ + pCol->notNull = OE_Abort; + p->tabFlags |= TF_HasNotNull; + } + } + } + + assert( (p->tabFlags & TF_HasPrimaryKey)==0 + || p->iPKey>=0 || sqlite3PrimaryKeyIndex(p)!=0 ); + assert( (p->tabFlags & TF_HasPrimaryKey)!=0 + || (p->iPKey<0 && sqlite3PrimaryKeyIndex(p)==0) ); + + /* Special processing for WITHOUT ROWID Tables */ + if( tabOpts & TF_WithoutRowid ){ + if( (p->tabFlags & TF_Autoincrement) ){ + sqlite3ErrorMsg(pParse, + "AUTOINCREMENT not allowed on WITHOUT ROWID tables"); + return; + } + if( (p->tabFlags & TF_HasPrimaryKey)==0 ){ + sqlite3ErrorMsg(pParse, "PRIMARY KEY missing on table %s", p->zName); + return; + } + p->tabFlags |= TF_WithoutRowid | TF_NoVisibleRowid; + convertToWithoutRowidTable(pParse, p); + } + iDb = sqlite3SchemaToIndex(db, p->pSchema); + +#ifndef SQLITE_OMIT_CHECK + /* Resolve names in all CHECK constraint expressions. + */ + if( p->pCheck ){ + sqlite3ResolveSelfReference(pParse, p, NC_IsCheck, 0, p->pCheck); + if( pParse->nErr ){ + /* If errors are seen, delete the CHECK constraints now, else they might + ** actually be used if PRAGMA writable_schema=ON is set. */ + sqlite3ExprListDelete(db, p->pCheck); + p->pCheck = 0; + }else{ + markExprListImmutable(p->pCheck); + } + } +#endif /* !defined(SQLITE_OMIT_CHECK) */ +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( p->tabFlags & TF_HasGenerated ){ + int ii, nNG = 0; + testcase( p->tabFlags & TF_HasVirtual ); + testcase( p->tabFlags & TF_HasStored ); + for(ii=0; ii<p->nCol; ii++){ + u32 colFlags = p->aCol[ii].colFlags; + if( (colFlags & COLFLAG_GENERATED)!=0 ){ + Expr *pX = sqlite3ColumnExpr(p, &p->aCol[ii]); + testcase( colFlags & COLFLAG_VIRTUAL ); + testcase( colFlags & COLFLAG_STORED ); + if( sqlite3ResolveSelfReference(pParse, p, NC_GenCol, pX, 0) ){ + /* If there are errors in resolving the expression, change the + ** expression to a NULL. This prevents code generators that operate + ** on the expression from inserting extra parts into the expression + ** tree that have been allocated from lookaside memory, which is + ** illegal in a schema and will lead to errors or heap corruption + ** when the database connection closes. */ + sqlite3ColumnSetExpr(pParse, p, &p->aCol[ii], + sqlite3ExprAlloc(db, TK_NULL, 0, 0)); + } + }else{ + nNG++; + } + } + if( nNG==0 ){ + sqlite3ErrorMsg(pParse, "must have at least one non-generated column"); + return; + } + } +#endif + + /* Estimate the average row size for the table and for all implied indices */ + estimateTableWidth(p); + for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ + estimateIndexWidth(pIdx); + } + + /* If not initializing, then create a record for the new table + ** in the schema table of the database. + ** + ** If this is a TEMPORARY table, write the entry into the auxiliary + ** file instead of into the main database file. + */ + if( !db->init.busy ){ + int n; + Vdbe *v; + char *zType; /* "view" or "table" */ + char *zType2; /* "VIEW" or "TABLE" */ + char *zStmt; /* Text of the CREATE TABLE or CREATE VIEW statement */ + + v = sqlite3GetVdbe(pParse); + if( NEVER(v==0) ) return; + + sqlite3VdbeAddOp1(v, OP_Close, 0); + + /* + ** Initialize zType for the new view or table. + */ + if( IsOrdinaryTable(p) ){ + /* A regular table */ + zType = "table"; + zType2 = "TABLE"; +#ifndef SQLITE_OMIT_VIEW + }else{ + /* A view */ + zType = "view"; + zType2 = "VIEW"; +#endif + } + + /* If this is a CREATE TABLE xx AS SELECT ..., execute the SELECT + ** statement to populate the new table. The root-page number for the + ** new table is in register pParse->regRoot. + ** + ** Once the SELECT has been coded by sqlite3Select(), it is in a + ** suitable state to query for the column names and types to be used + ** by the new table. + ** + ** A shared-cache write-lock is not required to write to the new table, + ** as a schema-lock must have already been obtained to create it. Since + ** a schema-lock excludes all other database users, the write-lock would + ** be redundant. + */ + if( pSelect ){ + SelectDest dest; /* Where the SELECT should store results */ + int regYield; /* Register holding co-routine entry-point */ + int addrTop; /* Top of the co-routine */ + int regRec; /* A record to be insert into the new table */ + int regRowid; /* Rowid of the next row to insert */ + int addrInsLoop; /* Top of the loop for inserting rows */ + Table *pSelTab; /* A table that describes the SELECT results */ + + if( IN_SPECIAL_PARSE ){ + pParse->rc = SQLITE_ERROR; + pParse->nErr++; + return; + } + regYield = ++pParse->nMem; + regRec = ++pParse->nMem; + regRowid = ++pParse->nMem; + assert(pParse->nTab==1); + sqlite3MayAbort(pParse); + sqlite3VdbeAddOp3(v, OP_OpenWrite, 1, pParse->regRoot, iDb); + sqlite3VdbeChangeP5(v, OPFLAG_P2ISREG); + pParse->nTab = 2; + addrTop = sqlite3VdbeCurrentAddr(v) + 1; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); + if( pParse->nErr ) return; + pSelTab = sqlite3ResultSetOfSelect(pParse, pSelect, SQLITE_AFF_BLOB); + if( pSelTab==0 ) return; + assert( p->aCol==0 ); + p->nCol = p->nNVCol = pSelTab->nCol; + p->aCol = pSelTab->aCol; + pSelTab->nCol = 0; + pSelTab->aCol = 0; + sqlite3DeleteTable(db, pSelTab); + sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); + sqlite3Select(pParse, pSelect, &dest); + if( pParse->nErr ) return; + sqlite3VdbeEndCoroutine(v, regYield); + sqlite3VdbeJumpHere(v, addrTop - 1); + addrInsLoop = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_MakeRecord, dest.iSdst, dest.nSdst, regRec); + sqlite3TableAffinity(v, p, 0); + sqlite3VdbeAddOp2(v, OP_NewRowid, 1, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, 1, regRec, regRowid); + sqlite3VdbeGoto(v, addrInsLoop); + sqlite3VdbeJumpHere(v, addrInsLoop); + sqlite3VdbeAddOp1(v, OP_Close, 1); + } + + /* Compute the complete text of the CREATE statement */ + if( pSelect ){ + zStmt = createTableStmt(db, p); + }else{ + Token *pEnd2 = tabOpts ? &pParse->sLastToken : pEnd; + n = (int)(pEnd2->z - pParse->sNameToken.z); + if( pEnd2->z[0]!=';' ) n += pEnd2->n; + zStmt = sqlite3MPrintf(db, + "CREATE %s %.*s", zType2, n, pParse->sNameToken.z + ); + } + + /* A slot for the record has already been allocated in the + ** schema table. We just need to update that slot with all + ** the information we've collected. + */ + sqlite3NestedParse(pParse, + "UPDATE %Q." LEGACY_SCHEMA_TABLE + " SET type='%s', name=%Q, tbl_name=%Q, rootpage=#%d, sql=%Q" + " WHERE rowid=#%d", + db->aDb[iDb].zDbSName, + zType, + p->zName, + p->zName, + pParse->regRoot, + zStmt, + pParse->regRowid + ); + sqlite3DbFree(db, zStmt); + sqlite3ChangeCookie(pParse, iDb); + +#ifndef SQLITE_OMIT_AUTOINCREMENT + /* Check to see if we need to create an sqlite_sequence table for + ** keeping track of autoincrement keys. + */ + if( (p->tabFlags & TF_Autoincrement)!=0 && !IN_SPECIAL_PARSE ){ + Db *pDb = &db->aDb[iDb]; + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( pDb->pSchema->pSeqTab==0 ){ + sqlite3NestedParse(pParse, + "CREATE TABLE %Q.sqlite_sequence(name,seq)", + pDb->zDbSName + ); + } + } +#endif + + /* Reparse everything to update our internal data structures */ + sqlite3VdbeAddParseSchemaOp(v, iDb, + sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName),0); + } + + /* Add the table to the in-memory representation of the database. + */ + if( db->init.busy ){ + Table *pOld; + Schema *pSchema = p->pSchema; + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + assert( HasRowid(p) || p->iPKey<0 ); + pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, p); + if( pOld ){ + assert( p==pOld ); /* Malloc must have failed inside HashInsert() */ + sqlite3OomFault(db); + return; + } + pParse->pNewTable = 0; + db->mDbFlags |= DBFLAG_SchemaChange; + + /* If this is the magic sqlite_sequence table used by autoincrement, + ** then record a pointer to this table in the main database structure + ** so that INSERT can find the table easily. */ + assert( !pParse->nested ); +#ifndef SQLITE_OMIT_AUTOINCREMENT + if( strcmp(p->zName, "sqlite_sequence")==0 ){ + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + p->pSchema->pSeqTab = p; + } +#endif + } + +#ifndef SQLITE_OMIT_ALTERTABLE + if( !pSelect && IsOrdinaryTable(p) ){ + assert( pCons && pEnd ); + if( pCons->z==0 ){ + pCons = pEnd; + } + p->u.tab.addColOffset = 13 + (int)(pCons->z - pParse->sNameToken.z); + } +#endif +} + +#ifndef SQLITE_OMIT_VIEW +/* +** The parser calls this routine in order to create a new VIEW +*/ +SQLITE_PRIVATE void sqlite3CreateView( + Parse *pParse, /* The parsing context */ + Token *pBegin, /* The CREATE token that begins the statement */ + Token *pName1, /* The token that holds the name of the view */ + Token *pName2, /* The token that holds the name of the view */ + ExprList *pCNames, /* Optional list of view column names */ + Select *pSelect, /* A SELECT statement that will become the new view */ + int isTemp, /* TRUE for a TEMPORARY view */ + int noErr /* Suppress error messages if VIEW already exists */ +){ + Table *p; + int n; + const char *z; + Token sEnd; + DbFixer sFix; + Token *pName = 0; + int iDb; + sqlite3 *db = pParse->db; + + if( pParse->nVar>0 ){ + sqlite3ErrorMsg(pParse, "parameters are not allowed in views"); + goto create_view_fail; + } + sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr); + p = pParse->pNewTable; + if( p==0 || pParse->nErr ) goto create_view_fail; + + /* Legacy versions of SQLite allowed the use of the magic "rowid" column + ** on a view, even though views do not have rowids. The following flag + ** setting fixes this problem. But the fix can be disabled by compiling + ** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that + ** depend upon the old buggy behavior. */ +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW + p->tabFlags |= TF_NoVisibleRowid; +#endif + + sqlite3TwoPartName(pParse, pName1, pName2, &pName); + iDb = sqlite3SchemaToIndex(db, p->pSchema); + sqlite3FixInit(&sFix, pParse, iDb, "view", pName); + if( sqlite3FixSelect(&sFix, pSelect) ) goto create_view_fail; + + /* Make a copy of the entire SELECT statement that defines the view. + ** This will force all the Expr.token.z values to be dynamically + ** allocated rather than point to the input string - which means that + ** they will persist after the current sqlite3_exec() call returns. + */ + pSelect->selFlags |= SF_View; + if( IN_RENAME_OBJECT ){ + p->u.view.pSelect = pSelect; + pSelect = 0; + }else{ + p->u.view.pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); + } + p->pCheck = sqlite3ExprListDup(db, pCNames, EXPRDUP_REDUCE); + p->eTabType = TABTYP_VIEW; + if( db->mallocFailed ) goto create_view_fail; + + /* Locate the end of the CREATE VIEW statement. Make sEnd point to + ** the end. + */ + sEnd = pParse->sLastToken; + assert( sEnd.z[0]!=0 || sEnd.n==0 ); + if( sEnd.z[0]!=';' ){ + sEnd.z += sEnd.n; + } + sEnd.n = 0; + n = (int)(sEnd.z - pBegin->z); + assert( n>0 ); + z = pBegin->z; + while( sqlite3Isspace(z[n-1]) ){ n--; } + sEnd.z = &z[n-1]; + sEnd.n = 1; + + /* Use sqlite3EndTable() to add the view to the schema table */ + sqlite3EndTable(pParse, 0, &sEnd, 0, 0); + +create_view_fail: + sqlite3SelectDelete(db, pSelect); + if( IN_RENAME_OBJECT ){ + sqlite3RenameExprlistUnmap(pParse, pCNames); + } + sqlite3ExprListDelete(db, pCNames); + return; +} +#endif /* SQLITE_OMIT_VIEW */ + +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) +/* +** The Table structure pTable is really a VIEW. Fill in the names of +** the columns of the view in the pTable structure. Return the number +** of errors. If an error is seen leave an error message in pParse->zErrMsg. +*/ +static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){ + Table *pSelTab; /* A fake table from which we get the result set */ + Select *pSel; /* Copy of the SELECT that implements the view */ + int nErr = 0; /* Number of errors encountered */ + sqlite3 *db = pParse->db; /* Database connection for malloc errors */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + int rc; +#endif +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth; /* Saved xAuth pointer */ +#endif + + assert( pTable ); + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pTable) ){ + db->nSchemaLock++; + rc = sqlite3VtabCallConnect(pParse, pTable); + db->nSchemaLock--; + return rc; + } +#endif + +#ifndef SQLITE_OMIT_VIEW + /* A positive nCol means the columns names for this view are + ** already known. This routine is not called unless either the + ** table is virtual or nCol is zero. + */ + assert( pTable->nCol<=0 ); + + /* A negative nCol is a special marker meaning that we are currently + ** trying to compute the column names. If we enter this routine with + ** a negative nCol, it means two or more views form a loop, like this: + ** + ** CREATE VIEW one AS SELECT * FROM two; + ** CREATE VIEW two AS SELECT * FROM one; + ** + ** Actually, the error above is now caught prior to reaching this point. + ** But the following test is still important as it does come up + ** in the following: + ** + ** CREATE TABLE main.ex1(a); + ** CREATE TEMP VIEW ex1 AS SELECT a FROM ex1; + ** SELECT * FROM temp.ex1; + */ + if( pTable->nCol<0 ){ + sqlite3ErrorMsg(pParse, "view %s is circularly defined", pTable->zName); + return 1; + } + assert( pTable->nCol>=0 ); + + /* If we get this far, it means we need to compute the table names. + ** Note that the call to sqlite3ResultSetOfSelect() will expand any + ** "*" elements in the results set of the view and will assign cursors + ** to the elements of the FROM clause. But we do not want these changes + ** to be permanent. So the computation is done on a copy of the SELECT + ** statement that defines the view. + */ + assert( IsView(pTable) ); + pSel = sqlite3SelectDup(db, pTable->u.view.pSelect, 0); + if( pSel ){ + u8 eParseMode = pParse->eParseMode; + int nTab = pParse->nTab; + int nSelect = pParse->nSelect; + pParse->eParseMode = PARSE_MODE_NORMAL; + sqlite3SrcListAssignCursors(pParse, pSel->pSrc); + pTable->nCol = -1; + DisableLookaside; +#ifndef SQLITE_OMIT_AUTHORIZATION + xAuth = db->xAuth; + db->xAuth = 0; + pSelTab = sqlite3ResultSetOfSelect(pParse, pSel, SQLITE_AFF_NONE); + db->xAuth = xAuth; +#else + pSelTab = sqlite3ResultSetOfSelect(pParse, pSel, SQLITE_AFF_NONE); +#endif + pParse->nTab = nTab; + pParse->nSelect = nSelect; + if( pSelTab==0 ){ + pTable->nCol = 0; + nErr++; + }else if( pTable->pCheck ){ + /* CREATE VIEW name(arglist) AS ... + ** The names of the columns in the table are taken from + ** arglist which is stored in pTable->pCheck. The pCheck field + ** normally holds CHECK constraints on an ordinary table, but for + ** a VIEW it holds the list of column names. + */ + sqlite3ColumnsFromExprList(pParse, pTable->pCheck, + &pTable->nCol, &pTable->aCol); + if( pParse->nErr==0 + && pTable->nCol==pSel->pEList->nExpr + ){ + assert( db->mallocFailed==0 ); + sqlite3SubqueryColumnTypes(pParse, pTable, pSel, SQLITE_AFF_NONE); + } + }else{ + /* CREATE VIEW name AS... without an argument list. Construct + ** the column names from the SELECT statement that defines the view. + */ + assert( pTable->aCol==0 ); + pTable->nCol = pSelTab->nCol; + pTable->aCol = pSelTab->aCol; + pTable->tabFlags |= (pSelTab->tabFlags & COLFLAG_NOINSERT); + pSelTab->nCol = 0; + pSelTab->aCol = 0; + assert( sqlite3SchemaMutexHeld(db, 0, pTable->pSchema) ); + } + pTable->nNVCol = pTable->nCol; + sqlite3DeleteTable(db, pSelTab); + sqlite3SelectDelete(db, pSel); + EnableLookaside; + pParse->eParseMode = eParseMode; + } else { + nErr++; + } + pTable->pSchema->schemaFlags |= DB_UnresetViews; + if( db->mallocFailed ){ + sqlite3DeleteColumnNames(db, pTable); + } +#endif /* SQLITE_OMIT_VIEW */ + return nErr; +} +SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ + assert( pTable!=0 ); + if( !IsVirtual(pTable) && pTable->nCol>0 ) return 0; + return viewGetColumnNames(pParse, pTable); +} +#endif /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */ + +#ifndef SQLITE_OMIT_VIEW +/* +** Clear the column names from every VIEW in database idx. +*/ +static void sqliteViewResetAll(sqlite3 *db, int idx){ + HashElem *i; + assert( sqlite3SchemaMutexHeld(db, idx, 0) ); + if( !DbHasProperty(db, idx, DB_UnresetViews) ) return; + for(i=sqliteHashFirst(&db->aDb[idx].pSchema->tblHash); i;i=sqliteHashNext(i)){ + Table *pTab = sqliteHashData(i); + if( IsView(pTab) ){ + sqlite3DeleteColumnNames(db, pTab); + } + } + DbClearProperty(db, idx, DB_UnresetViews); +} +#else +# define sqliteViewResetAll(A,B) +#endif /* SQLITE_OMIT_VIEW */ + +/* +** This function is called by the VDBE to adjust the internal schema +** used by SQLite when the btree layer moves a table root page. The +** root-page of a table or index in database iDb has changed from iFrom +** to iTo. +** +** Ticket #1728: The symbol table might still contain information +** on tables and/or indices that are the process of being deleted. +** If you are unlucky, one of those deleted indices or tables might +** have the same rootpage number as the real table or index that is +** being moved. So we cannot stop searching after the first match +** because the first match might be for one of the deleted indices +** or tables and not the table/index that is actually being moved. +** We must continue looping until all tables and indices with +** rootpage==iFrom have been converted to have a rootpage of iTo +** in order to be certain that we got the right one. +*/ +#ifndef SQLITE_OMIT_AUTOVACUUM +SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3 *db, int iDb, Pgno iFrom, Pgno iTo){ + HashElem *pElem; + Hash *pHash; + Db *pDb; + + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + pDb = &db->aDb[iDb]; + pHash = &pDb->pSchema->tblHash; + for(pElem=sqliteHashFirst(pHash); pElem; pElem=sqliteHashNext(pElem)){ + Table *pTab = sqliteHashData(pElem); + if( pTab->tnum==iFrom ){ + pTab->tnum = iTo; + } + } + pHash = &pDb->pSchema->idxHash; + for(pElem=sqliteHashFirst(pHash); pElem; pElem=sqliteHashNext(pElem)){ + Index *pIdx = sqliteHashData(pElem); + if( pIdx->tnum==iFrom ){ + pIdx->tnum = iTo; + } + } +} +#endif + +/* +** Write code to erase the table with root-page iTable from database iDb. +** Also write code to modify the sqlite_schema table and internal schema +** if a root-page of another table is moved by the btree-layer whilst +** erasing iTable (this can happen with an auto-vacuum database). +*/ +static void destroyRootPage(Parse *pParse, int iTable, int iDb){ + Vdbe *v = sqlite3GetVdbe(pParse); + int r1 = sqlite3GetTempReg(pParse); + if( iTable<2 ) sqlite3ErrorMsg(pParse, "corrupt schema"); + sqlite3VdbeAddOp3(v, OP_Destroy, iTable, r1, iDb); + sqlite3MayAbort(pParse); +#ifndef SQLITE_OMIT_AUTOVACUUM + /* OP_Destroy stores an in integer r1. If this integer + ** is non-zero, then it is the root page number of a table moved to + ** location iTable. The following code modifies the sqlite_schema table to + ** reflect this. + ** + ** The "#NNN" in the SQL is a special constant that means whatever value + ** is in register NNN. See grammar rules associated with the TK_REGISTER + ** token for additional information. + */ + sqlite3NestedParse(pParse, + "UPDATE %Q." LEGACY_SCHEMA_TABLE + " SET rootpage=%d WHERE #%d AND rootpage=#%d", + pParse->db->aDb[iDb].zDbSName, iTable, r1, r1); +#endif + sqlite3ReleaseTempReg(pParse, r1); +} + +/* +** Write VDBE code to erase table pTab and all associated indices on disk. +** Code to update the sqlite_schema tables and internal schema definitions +** in case a root-page belonging to another table is moved by the btree layer +** is also added (this can happen with an auto-vacuum database). +*/ +static void destroyTable(Parse *pParse, Table *pTab){ + /* If the database may be auto-vacuum capable (if SQLITE_OMIT_AUTOVACUUM + ** is not defined), then it is important to call OP_Destroy on the + ** table and index root-pages in order, starting with the numerically + ** largest root-page number. This guarantees that none of the root-pages + ** to be destroyed is relocated by an earlier OP_Destroy. i.e. if the + ** following were coded: + ** + ** OP_Destroy 4 0 + ** ... + ** OP_Destroy 5 0 + ** + ** and root page 5 happened to be the largest root-page number in the + ** database, then root page 5 would be moved to page 4 by the + ** "OP_Destroy 4 0" opcode. The subsequent "OP_Destroy 5 0" would hit + ** a free-list page. + */ + Pgno iTab = pTab->tnum; + Pgno iDestroyed = 0; + + while( 1 ){ + Index *pIdx; + Pgno iLargest = 0; + + if( iDestroyed==0 || iTab<iDestroyed ){ + iLargest = iTab; + } + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + Pgno iIdx = pIdx->tnum; + assert( pIdx->pSchema==pTab->pSchema ); + if( (iDestroyed==0 || (iIdx<iDestroyed)) && iIdx>iLargest ){ + iLargest = iIdx; + } + } + if( iLargest==0 ){ + return; + }else{ + int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + assert( iDb>=0 && iDb<pParse->db->nDb ); + destroyRootPage(pParse, iLargest, iDb); + iDestroyed = iLargest; + } + } +} + +/* +** Remove entries from the sqlite_statN tables (for N in (1,2,3)) +** after a DROP INDEX or DROP TABLE command. +*/ +static void sqlite3ClearStatTables( + Parse *pParse, /* The parsing context */ + int iDb, /* The database number */ + const char *zType, /* "idx" or "tbl" */ + const char *zName /* Name of index or table */ +){ + int i; + const char *zDbName = pParse->db->aDb[iDb].zDbSName; + for(i=1; i<=4; i++){ + char zTab[24]; + sqlite3_snprintf(sizeof(zTab),zTab,"sqlite_stat%d",i); + if( sqlite3FindTable(pParse->db, zTab, zDbName) ){ + sqlite3NestedParse(pParse, + "DELETE FROM %Q.%s WHERE %s=%Q", + zDbName, zTab, zType, zName + ); + } + } +} + +/* +** Generate code to drop a table. +*/ +SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){ + Vdbe *v; + sqlite3 *db = pParse->db; + Trigger *pTrigger; + Db *pDb = &db->aDb[iDb]; + + v = sqlite3GetVdbe(pParse); + assert( v!=0 ); + sqlite3BeginWriteOperation(pParse, 1, iDb); + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pTab) ){ + sqlite3VdbeAddOp0(v, OP_VBegin); + } +#endif + + /* Drop all triggers associated with the table being dropped. Code + ** is generated to remove entries from sqlite_schema and/or + ** sqlite_temp_schema if required. + */ + pTrigger = sqlite3TriggerList(pParse, pTab); + while( pTrigger ){ + assert( pTrigger->pSchema==pTab->pSchema || + pTrigger->pSchema==db->aDb[1].pSchema ); + sqlite3DropTriggerPtr(pParse, pTrigger); + pTrigger = pTrigger->pNext; + } + +#ifndef SQLITE_OMIT_AUTOINCREMENT + /* Remove any entries of the sqlite_sequence table associated with + ** the table being dropped. This is done before the table is dropped + ** at the btree level, in case the sqlite_sequence table needs to + ** move as a result of the drop (can happen in auto-vacuum mode). + */ + if( pTab->tabFlags & TF_Autoincrement ){ + sqlite3NestedParse(pParse, + "DELETE FROM %Q.sqlite_sequence WHERE name=%Q", + pDb->zDbSName, pTab->zName + ); + } +#endif + + /* Drop all entries in the schema table that refer to the + ** table. The program name loops through the schema table and deletes + ** every row that refers to a table of the same name as the one being + ** dropped. Triggers are handled separately because a trigger can be + ** created in the temp database that refers to a table in another + ** database. + */ + sqlite3NestedParse(pParse, + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE + " WHERE tbl_name=%Q and type!='trigger'", + pDb->zDbSName, pTab->zName); + if( !isView && !IsVirtual(pTab) ){ + destroyTable(pParse, pTab); + } + + /* Remove the table entry from SQLite's internal schema and modify + ** the schema cookie. + */ + if( IsVirtual(pTab) ){ + sqlite3VdbeAddOp4(v, OP_VDestroy, iDb, 0, 0, pTab->zName, 0); + sqlite3MayAbort(pParse); + } + sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0); + sqlite3ChangeCookie(pParse, iDb); + sqliteViewResetAll(db, iDb); +} + +/* +** Return TRUE if shadow tables should be read-only in the current +** context. +*/ +SQLITE_PRIVATE int sqlite3ReadOnlyShadowTables(sqlite3 *db){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( (db->flags & SQLITE_Defensive)!=0 + && db->pVtabCtx==0 + && db->nVdbeExec==0 + && !sqlite3VtabInSync(db) + ){ + return 1; + } +#endif + return 0; +} + +/* +** Return true if it is not allowed to drop the given table +*/ +static int tableMayNotBeDropped(sqlite3 *db, Table *pTab){ + if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){ + if( sqlite3StrNICmp(pTab->zName+7, "stat", 4)==0 ) return 0; + if( sqlite3StrNICmp(pTab->zName+7, "parameters", 10)==0 ) return 0; + return 1; + } + if( (pTab->tabFlags & TF_Shadow)!=0 && sqlite3ReadOnlyShadowTables(db) ){ + return 1; + } + if( pTab->tabFlags & TF_Eponymous ){ + return 1; + } + return 0; +} + +/* +** This routine is called to do the work of a DROP TABLE statement. +** pName is the name of the table to be dropped. +*/ +SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){ + Table *pTab; + Vdbe *v; + sqlite3 *db = pParse->db; + int iDb; + + if( db->mallocFailed ){ + goto exit_drop_table; + } + assert( pParse->nErr==0 ); + assert( pName->nSrc==1 ); + if( sqlite3ReadSchema(pParse) ) goto exit_drop_table; + if( noErr ) db->suppressErr++; + assert( isView==0 || isView==LOCATE_VIEW ); + pTab = sqlite3LocateTableItem(pParse, isView, &pName->a[0]); + if( noErr ) db->suppressErr--; + + if( pTab==0 ){ + if( noErr ){ + sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); + sqlite3ForceNotReadOnly(pParse); + } + goto exit_drop_table; + } + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb>=0 && iDb<db->nDb ); + + /* If pTab is a virtual table, call ViewGetColumnNames() to ensure + ** it is initialized. + */ + if( IsVirtual(pTab) && sqlite3ViewGetColumnNames(pParse, pTab) ){ + goto exit_drop_table; + } +#ifndef SQLITE_OMIT_AUTHORIZATION + { + int code; + const char *zTab = SCHEMA_TABLE(iDb); + const char *zDb = db->aDb[iDb].zDbSName; + const char *zArg2 = 0; + if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb)){ + goto exit_drop_table; + } + if( isView ){ + if( !OMIT_TEMPDB && iDb==1 ){ + code = SQLITE_DROP_TEMP_VIEW; + }else{ + code = SQLITE_DROP_VIEW; + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + }else if( IsVirtual(pTab) ){ + code = SQLITE_DROP_VTABLE; + zArg2 = sqlite3GetVTable(db, pTab)->pMod->zName; +#endif + }else{ + if( !OMIT_TEMPDB && iDb==1 ){ + code = SQLITE_DROP_TEMP_TABLE; + }else{ + code = SQLITE_DROP_TABLE; + } + } + if( sqlite3AuthCheck(pParse, code, pTab->zName, zArg2, zDb) ){ + goto exit_drop_table; + } + if( sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){ + goto exit_drop_table; + } + } +#endif + if( tableMayNotBeDropped(db, pTab) ){ + sqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName); + goto exit_drop_table; + } + +#ifndef SQLITE_OMIT_VIEW + /* Ensure DROP TABLE is not used on a view, and DROP VIEW is not used + ** on a table. + */ + if( isView && !IsView(pTab) ){ + sqlite3ErrorMsg(pParse, "use DROP TABLE to delete table %s", pTab->zName); + goto exit_drop_table; + } + if( !isView && IsView(pTab) ){ + sqlite3ErrorMsg(pParse, "use DROP VIEW to delete view %s", pTab->zName); + goto exit_drop_table; + } +#endif + + /* Generate code to remove the table from the schema table + ** on disk. + */ + v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3BeginWriteOperation(pParse, 1, iDb); + if( !isView ){ + sqlite3ClearStatTables(pParse, iDb, "tbl", pTab->zName); + sqlite3FkDropTable(pParse, pName, pTab); + } + sqlite3CodeDropTable(pParse, pTab, iDb, isView); + } + +exit_drop_table: + sqlite3SrcListDelete(db, pName); +} + +/* +** This routine is called to create a new foreign key on the table +** currently under construction. pFromCol determines which columns +** in the current table point to the foreign key. If pFromCol==0 then +** connect the key to the last column inserted. pTo is the name of +** the table referred to (a.k.a the "parent" table). pToCol is a list +** of tables in the parent pTo table. flags contains all +** information about the conflict resolution algorithms specified +** in the ON DELETE, ON UPDATE and ON INSERT clauses. +** +** An FKey structure is created and added to the table currently +** under construction in the pParse->pNewTable field. +** +** The foreign key is set for IMMEDIATE processing. A subsequent call +** to sqlite3DeferForeignKey() might change this to DEFERRED. +*/ +SQLITE_PRIVATE void sqlite3CreateForeignKey( + Parse *pParse, /* Parsing context */ + ExprList *pFromCol, /* Columns in this table that point to other table */ + Token *pTo, /* Name of the other table */ + ExprList *pToCol, /* Columns in the other table */ + int flags /* Conflict resolution algorithms. */ +){ + sqlite3 *db = pParse->db; +#ifndef SQLITE_OMIT_FOREIGN_KEY + FKey *pFKey = 0; + FKey *pNextTo; + Table *p = pParse->pNewTable; + i64 nByte; + int i; + int nCol; + char *z; + + assert( pTo!=0 ); + if( p==0 || IN_DECLARE_VTAB ) goto fk_end; + if( pFromCol==0 ){ + int iCol = p->nCol-1; + if( NEVER(iCol<0) ) goto fk_end; + if( pToCol && pToCol->nExpr!=1 ){ + sqlite3ErrorMsg(pParse, "foreign key on %s" + " should reference only one column of table %T", + p->aCol[iCol].zCnName, pTo); + goto fk_end; + } + nCol = 1; + }else if( pToCol && pToCol->nExpr!=pFromCol->nExpr ){ + sqlite3ErrorMsg(pParse, + "number of columns in foreign key does not match the number of " + "columns in the referenced table"); + goto fk_end; + }else{ + nCol = pFromCol->nExpr; + } + nByte = sizeof(*pFKey) + (nCol-1)*sizeof(pFKey->aCol[0]) + pTo->n + 1; + if( pToCol ){ + for(i=0; i<pToCol->nExpr; i++){ + nByte += sqlite3Strlen30(pToCol->a[i].zEName) + 1; + } + } + pFKey = sqlite3DbMallocZero(db, nByte ); + if( pFKey==0 ){ + goto fk_end; + } + pFKey->pFrom = p; + assert( IsOrdinaryTable(p) ); + pFKey->pNextFrom = p->u.tab.pFKey; + z = (char*)&pFKey->aCol[nCol]; + pFKey->zTo = z; + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, (void*)z, pTo); + } + memcpy(z, pTo->z, pTo->n); + z[pTo->n] = 0; + sqlite3Dequote(z); + z += pTo->n+1; + pFKey->nCol = nCol; + if( pFromCol==0 ){ + pFKey->aCol[0].iFrom = p->nCol-1; + }else{ + for(i=0; i<nCol; i++){ + int j; + for(j=0; j<p->nCol; j++){ + if( sqlite3StrICmp(p->aCol[j].zCnName, pFromCol->a[i].zEName)==0 ){ + pFKey->aCol[i].iFrom = j; + break; + } + } + if( j>=p->nCol ){ + sqlite3ErrorMsg(pParse, + "unknown column \"%s\" in foreign key definition", + pFromCol->a[i].zEName); + goto fk_end; + } + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenRemap(pParse, &pFKey->aCol[i], pFromCol->a[i].zEName); + } + } + } + if( pToCol ){ + for(i=0; i<nCol; i++){ + int n = sqlite3Strlen30(pToCol->a[i].zEName); + pFKey->aCol[i].zCol = z; + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenRemap(pParse, z, pToCol->a[i].zEName); + } + memcpy(z, pToCol->a[i].zEName, n); + z[n] = 0; + z += n+1; + } + } + pFKey->isDeferred = 0; + pFKey->aAction[0] = (u8)(flags & 0xff); /* ON DELETE action */ + pFKey->aAction[1] = (u8)((flags >> 8 ) & 0xff); /* ON UPDATE action */ + + assert( sqlite3SchemaMutexHeld(db, 0, p->pSchema) ); + pNextTo = (FKey *)sqlite3HashInsert(&p->pSchema->fkeyHash, + pFKey->zTo, (void *)pFKey + ); + if( pNextTo==pFKey ){ + sqlite3OomFault(db); + goto fk_end; + } + if( pNextTo ){ + assert( pNextTo->pPrevTo==0 ); + pFKey->pNextTo = pNextTo; + pNextTo->pPrevTo = pFKey; + } + + /* Link the foreign key to the table as the last step. + */ + assert( IsOrdinaryTable(p) ); + p->u.tab.pFKey = pFKey; + pFKey = 0; + +fk_end: + sqlite3DbFree(db, pFKey); +#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */ + sqlite3ExprListDelete(db, pFromCol); + sqlite3ExprListDelete(db, pToCol); +} + +/* +** This routine is called when an INITIALLY IMMEDIATE or INITIALLY DEFERRED +** clause is seen as part of a foreign key definition. The isDeferred +** parameter is 1 for INITIALLY DEFERRED and 0 for INITIALLY IMMEDIATE. +** The behavior of the most recently created foreign key is adjusted +** accordingly. +*/ +SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse *pParse, int isDeferred){ +#ifndef SQLITE_OMIT_FOREIGN_KEY + Table *pTab; + FKey *pFKey; + if( (pTab = pParse->pNewTable)==0 ) return; + if( NEVER(!IsOrdinaryTable(pTab)) ) return; + if( (pFKey = pTab->u.tab.pFKey)==0 ) return; + assert( isDeferred==0 || isDeferred==1 ); /* EV: R-30323-21917 */ + pFKey->isDeferred = (u8)isDeferred; +#endif +} + +/* +** Generate code that will erase and refill index *pIdx. This is +** used to initialize a newly created index or to recompute the +** content of an index in response to a REINDEX command. +** +** if memRootPage is not negative, it means that the index is newly +** created. The register specified by memRootPage contains the +** root page number of the index. If memRootPage is negative, then +** the index already exists and must be cleared before being refilled and +** the root page number of the index is taken from pIndex->tnum. +*/ +static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ + Table *pTab = pIndex->pTable; /* The table that is indexed */ + int iTab = pParse->nTab++; /* Btree cursor used for pTab */ + int iIdx = pParse->nTab++; /* Btree cursor used for pIndex */ + int iSorter; /* Cursor opened by OpenSorter (if in use) */ + int addr1; /* Address of top of loop */ + int addr2; /* Address to jump to for next iteration */ + Pgno tnum; /* Root page of index */ + int iPartIdxLabel; /* Jump to this label to skip a row */ + Vdbe *v; /* Generate code into this virtual machine */ + KeyInfo *pKey; /* KeyInfo for index */ + int regRecord; /* Register holding assembled index record */ + sqlite3 *db = pParse->db; /* The database connection */ + int iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); + +#ifndef SQLITE_OMIT_AUTHORIZATION + if( sqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0, + db->aDb[iDb].zDbSName ) ){ + return; + } +#endif + + /* Require a write-lock on the table to perform this operation */ + sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName); + + v = sqlite3GetVdbe(pParse); + if( v==0 ) return; + if( memRootPage>=0 ){ + tnum = (Pgno)memRootPage; + }else{ + tnum = pIndex->tnum; + } + pKey = sqlite3KeyInfoOfIndex(pParse, pIndex); + assert( pKey!=0 || pParse->nErr ); + + /* Open the sorter cursor if we are to use one. */ + iSorter = pParse->nTab++; + sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, pIndex->nKeyCol, (char*) + sqlite3KeyInfoRef(pKey), P4_KEYINFO); + + /* Open the table. Loop through all rows of the table, inserting index + ** records into the sorter. */ + sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); + addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0); VdbeCoverage(v); + regRecord = sqlite3GetTempReg(pParse); + sqlite3MultiWrite(pParse); + + sqlite3GenerateIndexKey(pParse,pIndex,iTab,regRecord,0,&iPartIdxLabel,0,0); + sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord); + sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel); + sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addr1); + if( memRootPage<0 ) sqlite3VdbeAddOp2(v, OP_Clear, tnum, iDb); + sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, (int)tnum, iDb, + (char *)pKey, P4_KEYINFO); + sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR|((memRootPage>=0)?OPFLAG_P2ISREG:0)); + + addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0); VdbeCoverage(v); + if( IsUniqueIndex(pIndex) ){ + int j2 = sqlite3VdbeGoto(v, 1); + addr2 = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeVerifyAbortable(v, OE_Abort); + sqlite3VdbeAddOp4Int(v, OP_SorterCompare, iSorter, j2, regRecord, + pIndex->nKeyCol); VdbeCoverage(v); + sqlite3UniqueConstraint(pParse, OE_Abort, pIndex); + sqlite3VdbeJumpHere(v, j2); + }else{ + /* Most CREATE INDEX and REINDEX statements that are not UNIQUE can not + ** abort. The exception is if one of the indexed expressions contains a + ** user function that throws an exception when it is evaluated. But the + ** overhead of adding a statement journal to a CREATE INDEX statement is + ** very small (since most of the pages written do not contain content that + ** needs to be restored if the statement aborts), so we call + ** sqlite3MayAbort() for all CREATE INDEX statements. */ + sqlite3MayAbort(pParse); + addr2 = sqlite3VdbeCurrentAddr(v); + } + sqlite3VdbeAddOp3(v, OP_SorterData, iSorter, regRecord, iIdx); + if( !pIndex->bAscKeyBug ){ + /* This OP_SeekEnd opcode makes index insert for a REINDEX go much + ** faster by avoiding unnecessary seeks. But the optimization does + ** not work for UNIQUE constraint indexes on WITHOUT ROWID tables + ** with DESC primary keys, since those indexes have there keys in + ** a different order from the main table. + ** See ticket: https://www.sqlite.org/src/info/bba7b69f9849b5bf + */ + sqlite3VdbeAddOp1(v, OP_SeekEnd, iIdx); + } + sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdx, regRecord); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3ReleaseTempReg(pParse, regRecord); + sqlite3VdbeAddOp2(v, OP_SorterNext, iSorter, addr2); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addr1); + + sqlite3VdbeAddOp1(v, OP_Close, iTab); + sqlite3VdbeAddOp1(v, OP_Close, iIdx); + sqlite3VdbeAddOp1(v, OP_Close, iSorter); +} + +/* +** Allocate heap space to hold an Index object with nCol columns. +** +** Increase the allocation size to provide an extra nExtra bytes +** of 8-byte aligned space after the Index object and return a +** pointer to this extra space in *ppExtra. +*/ +SQLITE_PRIVATE Index *sqlite3AllocateIndexObject( + sqlite3 *db, /* Database connection */ + i16 nCol, /* Total number of columns in the index */ + int nExtra, /* Number of bytes of extra space to alloc */ + char **ppExtra /* Pointer to the "extra" space */ +){ + Index *p; /* Allocated index object */ + int nByte; /* Bytes of space for Index object + arrays */ + + nByte = ROUND8(sizeof(Index)) + /* Index structure */ + ROUND8(sizeof(char*)*nCol) + /* Index.azColl */ + ROUND8(sizeof(LogEst)*(nCol+1) + /* Index.aiRowLogEst */ + sizeof(i16)*nCol + /* Index.aiColumn */ + sizeof(u8)*nCol); /* Index.aSortOrder */ + p = sqlite3DbMallocZero(db, nByte + nExtra); + if( p ){ + char *pExtra = ((char*)p)+ROUND8(sizeof(Index)); + p->azColl = (const char**)pExtra; pExtra += ROUND8(sizeof(char*)*nCol); + p->aiRowLogEst = (LogEst*)pExtra; pExtra += sizeof(LogEst)*(nCol+1); + p->aiColumn = (i16*)pExtra; pExtra += sizeof(i16)*nCol; + p->aSortOrder = (u8*)pExtra; + p->nColumn = nCol; + p->nKeyCol = nCol - 1; + *ppExtra = ((char*)p) + nByte; + } + return p; +} + +/* +** If expression list pList contains an expression that was parsed with +** an explicit "NULLS FIRST" or "NULLS LAST" clause, leave an error in +** pParse and return non-zero. Otherwise, return zero. +*/ +SQLITE_PRIVATE int sqlite3HasExplicitNulls(Parse *pParse, ExprList *pList){ + if( pList ){ + int i; + for(i=0; i<pList->nExpr; i++){ + if( pList->a[i].fg.bNulls ){ + u8 sf = pList->a[i].fg.sortFlags; + sqlite3ErrorMsg(pParse, "unsupported use of NULLS %s", + (sf==0 || sf==3) ? "FIRST" : "LAST" + ); + return 1; + } + } + } + return 0; +} + +/* +** Create a new index for an SQL table. pName1.pName2 is the name of the index +** and pTblList is the name of the table that is to be indexed. Both will +** be NULL for a primary key or an index that is created to satisfy a +** UNIQUE constraint. If pTable and pIndex are NULL, use pParse->pNewTable +** as the table to be indexed. pParse->pNewTable is a table that is +** currently being constructed by a CREATE TABLE statement. +** +** pList is a list of columns to be indexed. pList will be NULL if this +** is a primary key or unique-constraint on the most recent column added +** to the table currently under construction. +*/ +SQLITE_PRIVATE void sqlite3CreateIndex( + Parse *pParse, /* All information about this parse */ + Token *pName1, /* First part of index name. May be NULL */ + Token *pName2, /* Second part of index name. May be NULL */ + SrcList *pTblName, /* Table to index. Use pParse->pNewTable if 0 */ + ExprList *pList, /* A list of columns to be indexed */ + int onError, /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ + Token *pStart, /* The CREATE token that begins this statement */ + Expr *pPIWhere, /* WHERE clause for partial indices */ + int sortOrder, /* Sort order of primary key when pList==NULL */ + int ifNotExist, /* Omit error if index already exists */ + u8 idxType /* The index type */ +){ + Table *pTab = 0; /* Table to be indexed */ + Index *pIndex = 0; /* The index to be created */ + char *zName = 0; /* Name of the index */ + int nName; /* Number of characters in zName */ + int i, j; + DbFixer sFix; /* For assigning database names to pTable */ + int sortOrderMask; /* 1 to honor DESC in index. 0 to ignore. */ + sqlite3 *db = pParse->db; + Db *pDb; /* The specific table containing the indexed database */ + int iDb; /* Index of the database that is being written */ + Token *pName = 0; /* Unqualified name of the index to create */ + struct ExprList_item *pListItem; /* For looping over pList */ + int nExtra = 0; /* Space allocated for zExtra[] */ + int nExtraCol; /* Number of extra columns needed */ + char *zExtra = 0; /* Extra space after the Index object */ + Index *pPk = 0; /* PRIMARY KEY index for WITHOUT ROWID tables */ + + assert( db->pParse==pParse ); + if( pParse->nErr ){ + goto exit_create_index; + } + assert( db->mallocFailed==0 ); + if( IN_DECLARE_VTAB && idxType!=SQLITE_IDXTYPE_PRIMARYKEY ){ + goto exit_create_index; + } + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ + goto exit_create_index; + } + if( sqlite3HasExplicitNulls(pParse, pList) ){ + goto exit_create_index; + } + + /* + ** Find the table that is to be indexed. Return early if not found. + */ + if( pTblName!=0 ){ + + /* Use the two-part index name to determine the database + ** to search for the table. 'Fix' the table name to this db + ** before looking up the table. + */ + assert( pName1 && pName2 ); + iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); + if( iDb<0 ) goto exit_create_index; + assert( pName && pName->z ); + +#ifndef SQLITE_OMIT_TEMPDB + /* If the index name was unqualified, check if the table + ** is a temp table. If so, set the database to 1. Do not do this + ** if initializing a database schema. + */ + if( !db->init.busy ){ + pTab = sqlite3SrcListLookup(pParse, pTblName); + if( pName2->n==0 && pTab && pTab->pSchema==db->aDb[1].pSchema ){ + iDb = 1; + } + } +#endif + + sqlite3FixInit(&sFix, pParse, iDb, "index", pName); + if( sqlite3FixSrcList(&sFix, pTblName) ){ + /* Because the parser constructs pTblName from a single identifier, + ** sqlite3FixSrcList can never fail. */ + assert(0); + } + pTab = sqlite3LocateTableItem(pParse, 0, &pTblName->a[0]); + assert( db->mallocFailed==0 || pTab==0 ); + if( pTab==0 ) goto exit_create_index; + if( iDb==1 && db->aDb[iDb].pSchema!=pTab->pSchema ){ + sqlite3ErrorMsg(pParse, + "cannot create a TEMP index on non-TEMP table \"%s\"", + pTab->zName); + goto exit_create_index; + } + if( !HasRowid(pTab) ) pPk = sqlite3PrimaryKeyIndex(pTab); + }else{ + assert( pName==0 ); + assert( pStart==0 ); + pTab = pParse->pNewTable; + if( !pTab ) goto exit_create_index; + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + } + pDb = &db->aDb[iDb]; + + assert( pTab!=0 ); + if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 + && db->init.busy==0 + && pTblName!=0 +#if SQLITE_USER_AUTHENTICATION + && sqlite3UserAuthTable(pTab->zName)==0 +#endif + ){ + sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName); + goto exit_create_index; + } +#ifndef SQLITE_OMIT_VIEW + if( IsView(pTab) ){ + sqlite3ErrorMsg(pParse, "views may not be indexed"); + goto exit_create_index; + } +#endif +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pTab) ){ + sqlite3ErrorMsg(pParse, "virtual tables may not be indexed"); + goto exit_create_index; + } +#endif + + /* + ** Find the name of the index. Make sure there is not already another + ** index or table with the same name. + ** + ** Exception: If we are reading the names of permanent indices from the + ** sqlite_schema table (because some other process changed the schema) and + ** one of the index names collides with the name of a temporary table or + ** index, then we will continue to process this index. + ** + ** If pName==0 it means that we are + ** dealing with a primary key or UNIQUE constraint. We have to invent our + ** own name. + */ + if( pName ){ + zName = sqlite3NameFromToken(db, pName); + if( zName==0 ) goto exit_create_index; + assert( pName->z!=0 ); + if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName,"index",pTab->zName) ){ + goto exit_create_index; + } + if( !IN_RENAME_OBJECT ){ + if( !db->init.busy ){ + if( sqlite3FindTable(db, zName, pDb->zDbSName)!=0 ){ + sqlite3ErrorMsg(pParse, "there is already a table named %s", zName); + goto exit_create_index; + } + } + if( sqlite3FindIndex(db, zName, pDb->zDbSName)!=0 ){ + if( !ifNotExist ){ + sqlite3ErrorMsg(pParse, "index %s already exists", zName); + }else{ + assert( !db->init.busy ); + sqlite3CodeVerifySchema(pParse, iDb); + sqlite3ForceNotReadOnly(pParse); + } + goto exit_create_index; + } + } + }else{ + int n; + Index *pLoop; + for(pLoop=pTab->pIndex, n=1; pLoop; pLoop=pLoop->pNext, n++){} + zName = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->zName, n); + if( zName==0 ){ + goto exit_create_index; + } + + /* Automatic index names generated from within sqlite3_declare_vtab() + ** must have names that are distinct from normal automatic index names. + ** The following statement converts "sqlite3_autoindex..." into + ** "sqlite3_butoindex..." in order to make the names distinct. + ** The "vtab_err.test" test demonstrates the need of this statement. */ + if( IN_SPECIAL_PARSE ) zName[7]++; + } + + /* Check for authorization to create an index. + */ +#ifndef SQLITE_OMIT_AUTHORIZATION + if( !IN_RENAME_OBJECT ){ + const char *zDb = pDb->zDbSName; + if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iDb), 0, zDb) ){ + goto exit_create_index; + } + i = SQLITE_CREATE_INDEX; + if( !OMIT_TEMPDB && iDb==1 ) i = SQLITE_CREATE_TEMP_INDEX; + if( sqlite3AuthCheck(pParse, i, zName, pTab->zName, zDb) ){ + goto exit_create_index; + } + } +#endif + + /* If pList==0, it means this routine was called to make a primary + ** key out of the last column added to the table under construction. + ** So create a fake list to simulate this. + */ + if( pList==0 ){ + Token prevCol; + Column *pCol = &pTab->aCol[pTab->nCol-1]; + pCol->colFlags |= COLFLAG_UNIQUE; + sqlite3TokenInit(&prevCol, pCol->zCnName); + pList = sqlite3ExprListAppend(pParse, 0, + sqlite3ExprAlloc(db, TK_ID, &prevCol, 0)); + if( pList==0 ) goto exit_create_index; + assert( pList->nExpr==1 ); + sqlite3ExprListSetSortOrder(pList, sortOrder, SQLITE_SO_UNDEFINED); + }else{ + sqlite3ExprListCheckLength(pParse, pList, "index"); + if( pParse->nErr ) goto exit_create_index; + } + + /* Figure out how many bytes of space are required to store explicitly + ** specified collation sequence names. + */ + for(i=0; i<pList->nExpr; i++){ + Expr *pExpr = pList->a[i].pExpr; + assert( pExpr!=0 ); + if( pExpr->op==TK_COLLATE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + nExtra += (1 + sqlite3Strlen30(pExpr->u.zToken)); + } + } + + /* + ** Allocate the index structure. + */ + nName = sqlite3Strlen30(zName); + nExtraCol = pPk ? pPk->nKeyCol : 1; + assert( pList->nExpr + nExtraCol <= 32767 /* Fits in i16 */ ); + pIndex = sqlite3AllocateIndexObject(db, pList->nExpr + nExtraCol, + nName + nExtra + 1, &zExtra); + if( db->mallocFailed ){ + goto exit_create_index; + } + assert( EIGHT_BYTE_ALIGNMENT(pIndex->aiRowLogEst) ); + assert( EIGHT_BYTE_ALIGNMENT(pIndex->azColl) ); + pIndex->zName = zExtra; + zExtra += nName + 1; + memcpy(pIndex->zName, zName, nName+1); + pIndex->pTable = pTab; + pIndex->onError = (u8)onError; + pIndex->uniqNotNull = onError!=OE_None; + pIndex->idxType = idxType; + pIndex->pSchema = db->aDb[iDb].pSchema; + pIndex->nKeyCol = pList->nExpr; + if( pPIWhere ){ + sqlite3ResolveSelfReference(pParse, pTab, NC_PartIdx, pPIWhere, 0); + pIndex->pPartIdxWhere = pPIWhere; + pPIWhere = 0; + } + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + + /* Check to see if we should honor DESC requests on index columns + */ + if( pDb->pSchema->file_format>=4 ){ + sortOrderMask = -1; /* Honor DESC */ + }else{ + sortOrderMask = 0; /* Ignore DESC */ + } + + /* Analyze the list of expressions that form the terms of the index and + ** report any errors. In the common case where the expression is exactly + ** a table column, store that column in aiColumn[]. For general expressions, + ** populate pIndex->aColExpr and store XN_EXPR (-2) in aiColumn[]. + ** + ** TODO: Issue a warning if two or more columns of the index are identical. + ** TODO: Issue a warning if the table primary key is used as part of the + ** index key. + */ + pListItem = pList->a; + if( IN_RENAME_OBJECT ){ + pIndex->aColExpr = pList; + pList = 0; + } + for(i=0; i<pIndex->nKeyCol; i++, pListItem++){ + Expr *pCExpr; /* The i-th index expression */ + int requestedSortOrder; /* ASC or DESC on the i-th expression */ + const char *zColl; /* Collation sequence name */ + + sqlite3StringToId(pListItem->pExpr); + sqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr, pListItem->pExpr, 0); + if( pParse->nErr ) goto exit_create_index; + pCExpr = sqlite3ExprSkipCollate(pListItem->pExpr); + if( pCExpr->op!=TK_COLUMN ){ + if( pTab==pParse->pNewTable ){ + sqlite3ErrorMsg(pParse, "expressions prohibited in PRIMARY KEY and " + "UNIQUE constraints"); + goto exit_create_index; + } + if( pIndex->aColExpr==0 ){ + pIndex->aColExpr = pList; + pList = 0; + } + j = XN_EXPR; + pIndex->aiColumn[i] = XN_EXPR; + pIndex->uniqNotNull = 0; + pIndex->bHasExpr = 1; + }else{ + j = pCExpr->iColumn; + assert( j<=0x7fff ); + if( j<0 ){ + j = pTab->iPKey; + }else{ + if( pTab->aCol[j].notNull==0 ){ + pIndex->uniqNotNull = 0; + } + if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){ + pIndex->bHasVCol = 1; + pIndex->bHasExpr = 1; + } + } + pIndex->aiColumn[i] = (i16)j; + } + zColl = 0; + if( pListItem->pExpr->op==TK_COLLATE ){ + int nColl; + assert( !ExprHasProperty(pListItem->pExpr, EP_IntValue) ); + zColl = pListItem->pExpr->u.zToken; + nColl = sqlite3Strlen30(zColl) + 1; + assert( nExtra>=nColl ); + memcpy(zExtra, zColl, nColl); + zColl = zExtra; + zExtra += nColl; + nExtra -= nColl; + }else if( j>=0 ){ + zColl = sqlite3ColumnColl(&pTab->aCol[j]); + } + if( !zColl ) zColl = sqlite3StrBINARY; + if( !db->init.busy && !sqlite3LocateCollSeq(pParse, zColl) ){ + goto exit_create_index; + } + pIndex->azColl[i] = zColl; + requestedSortOrder = pListItem->fg.sortFlags & sortOrderMask; + pIndex->aSortOrder[i] = (u8)requestedSortOrder; + } + + /* Append the table key to the end of the index. For WITHOUT ROWID + ** tables (when pPk!=0) this will be the declared PRIMARY KEY. For + ** normal tables (when pPk==0) this will be the rowid. + */ + if( pPk ){ + for(j=0; j<pPk->nKeyCol; j++){ + int x = pPk->aiColumn[j]; + assert( x>=0 ); + if( isDupColumn(pIndex, pIndex->nKeyCol, pPk, j) ){ + pIndex->nColumn--; + }else{ + testcase( hasColumn(pIndex->aiColumn,pIndex->nKeyCol,x) ); + pIndex->aiColumn[i] = x; + pIndex->azColl[i] = pPk->azColl[j]; + pIndex->aSortOrder[i] = pPk->aSortOrder[j]; + i++; + } + } + assert( i==pIndex->nColumn ); + }else{ + pIndex->aiColumn[i] = XN_ROWID; + pIndex->azColl[i] = sqlite3StrBINARY; + } + sqlite3DefaultRowEst(pIndex); + if( pParse->pNewTable==0 ) estimateIndexWidth(pIndex); + + /* If this index contains every column of its table, then mark + ** it as a covering index */ + assert( HasRowid(pTab) + || pTab->iPKey<0 || sqlite3TableColumnToIndex(pIndex, pTab->iPKey)>=0 ); + recomputeColumnsNotIndexed(pIndex); + if( pTblName!=0 && pIndex->nColumn>=pTab->nCol ){ + pIndex->isCovering = 1; + for(j=0; j<pTab->nCol; j++){ + if( j==pTab->iPKey ) continue; + if( sqlite3TableColumnToIndex(pIndex,j)>=0 ) continue; + pIndex->isCovering = 0; + break; + } + } + + if( pTab==pParse->pNewTable ){ + /* This routine has been called to create an automatic index as a + ** result of a PRIMARY KEY or UNIQUE clause on a column definition, or + ** a PRIMARY KEY or UNIQUE clause following the column definitions. + ** i.e. one of: + ** + ** CREATE TABLE t(x PRIMARY KEY, y); + ** CREATE TABLE t(x, y, UNIQUE(x, y)); + ** + ** Either way, check to see if the table already has such an index. If + ** so, don't bother creating this one. This only applies to + ** automatically created indices. Users can do as they wish with + ** explicit indices. + ** + ** Two UNIQUE or PRIMARY KEY constraints are considered equivalent + ** (and thus suppressing the second one) even if they have different + ** sort orders. + ** + ** If there are different collating sequences or if the columns of + ** the constraint occur in different orders, then the constraints are + ** considered distinct and both result in separate indices. + */ + Index *pIdx; + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int k; + assert( IsUniqueIndex(pIdx) ); + assert( pIdx->idxType!=SQLITE_IDXTYPE_APPDEF ); + assert( IsUniqueIndex(pIndex) ); + + if( pIdx->nKeyCol!=pIndex->nKeyCol ) continue; + for(k=0; k<pIdx->nKeyCol; k++){ + const char *z1; + const char *z2; + assert( pIdx->aiColumn[k]>=0 ); + if( pIdx->aiColumn[k]!=pIndex->aiColumn[k] ) break; + z1 = pIdx->azColl[k]; + z2 = pIndex->azColl[k]; + if( sqlite3StrICmp(z1, z2) ) break; + } + if( k==pIdx->nKeyCol ){ + if( pIdx->onError!=pIndex->onError ){ + /* This constraint creates the same index as a previous + ** constraint specified somewhere in the CREATE TABLE statement. + ** However the ON CONFLICT clauses are different. If both this + ** constraint and the previous equivalent constraint have explicit + ** ON CONFLICT clauses this is an error. Otherwise, use the + ** explicitly specified behavior for the index. + */ + if( !(pIdx->onError==OE_Default || pIndex->onError==OE_Default) ){ + sqlite3ErrorMsg(pParse, + "conflicting ON CONFLICT clauses specified", 0); + } + if( pIdx->onError==OE_Default ){ + pIdx->onError = pIndex->onError; + } + } + if( idxType==SQLITE_IDXTYPE_PRIMARYKEY ) pIdx->idxType = idxType; + if( IN_RENAME_OBJECT ){ + pIndex->pNext = pParse->pNewIndex; + pParse->pNewIndex = pIndex; + pIndex = 0; + } + goto exit_create_index; + } + } + } + + if( !IN_RENAME_OBJECT ){ + + /* Link the new Index structure to its table and to the other + ** in-memory database structures. + */ + assert( pParse->nErr==0 ); + if( db->init.busy ){ + Index *p; + assert( !IN_SPECIAL_PARSE ); + assert( sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) ); + if( pTblName!=0 ){ + pIndex->tnum = db->init.newTnum; + if( sqlite3IndexHasDuplicateRootPage(pIndex) ){ + sqlite3ErrorMsg(pParse, "invalid rootpage"); + pParse->rc = SQLITE_CORRUPT_BKPT; + goto exit_create_index; + } + } + p = sqlite3HashInsert(&pIndex->pSchema->idxHash, + pIndex->zName, pIndex); + if( p ){ + assert( p==pIndex ); /* Malloc must have failed */ + sqlite3OomFault(db); + goto exit_create_index; + } + db->mDbFlags |= DBFLAG_SchemaChange; + } + + /* If this is the initial CREATE INDEX statement (or CREATE TABLE if the + ** index is an implied index for a UNIQUE or PRIMARY KEY constraint) then + ** emit code to allocate the index rootpage on disk and make an entry for + ** the index in the sqlite_schema table and populate the index with + ** content. But, do not do this if we are simply reading the sqlite_schema + ** table to parse the schema, or if this index is the PRIMARY KEY index + ** of a WITHOUT ROWID table. + ** + ** If pTblName==0 it means this index is generated as an implied PRIMARY KEY + ** or UNIQUE index in a CREATE TABLE statement. Since the table + ** has just been created, it contains no data and the index initialization + ** step can be skipped. + */ + else if( HasRowid(pTab) || pTblName!=0 ){ + Vdbe *v; + char *zStmt; + int iMem = ++pParse->nMem; + + v = sqlite3GetVdbe(pParse); + if( v==0 ) goto exit_create_index; + + sqlite3BeginWriteOperation(pParse, 1, iDb); + + /* Create the rootpage for the index using CreateIndex. But before + ** doing so, code a Noop instruction and store its address in + ** Index.tnum. This is required in case this index is actually a + ** PRIMARY KEY and the table is actually a WITHOUT ROWID table. In + ** that case the convertToWithoutRowidTable() routine will replace + ** the Noop with a Goto to jump over the VDBE code generated below. */ + pIndex->tnum = (Pgno)sqlite3VdbeAddOp0(v, OP_Noop); + sqlite3VdbeAddOp3(v, OP_CreateBtree, iDb, iMem, BTREE_BLOBKEY); + + /* Gather the complete text of the CREATE INDEX statement into + ** the zStmt variable + */ + assert( pName!=0 || pStart==0 ); + if( pStart ){ + int n = (int)(pParse->sLastToken.z - pName->z) + pParse->sLastToken.n; + if( pName->z[n-1]==';' ) n--; + /* A named index with an explicit CREATE INDEX statement */ + zStmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s", + onError==OE_None ? "" : " UNIQUE", n, pName->z); + }else{ + /* An automatic index created by a PRIMARY KEY or UNIQUE constraint */ + /* zStmt = sqlite3MPrintf(""); */ + zStmt = 0; + } + + /* Add an entry in sqlite_schema for this index + */ + sqlite3NestedParse(pParse, + "INSERT INTO %Q." LEGACY_SCHEMA_TABLE " VALUES('index',%Q,%Q,#%d,%Q);", + db->aDb[iDb].zDbSName, + pIndex->zName, + pTab->zName, + iMem, + zStmt + ); + sqlite3DbFree(db, zStmt); + + /* Fill the index with data and reparse the schema. Code an OP_Expire + ** to invalidate all pre-compiled statements. + */ + if( pTblName ){ + sqlite3RefillIndex(pParse, pIndex, iMem); + sqlite3ChangeCookie(pParse, iDb); + sqlite3VdbeAddParseSchemaOp(v, iDb, + sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName), 0); + sqlite3VdbeAddOp2(v, OP_Expire, 0, 1); + } + + sqlite3VdbeJumpHere(v, (int)pIndex->tnum); + } + } + if( db->init.busy || pTblName==0 ){ + pIndex->pNext = pTab->pIndex; + pTab->pIndex = pIndex; + pIndex = 0; + } + else if( IN_RENAME_OBJECT ){ + assert( pParse->pNewIndex==0 ); + pParse->pNewIndex = pIndex; + pIndex = 0; + } + + /* Clean up before exiting */ +exit_create_index: + if( pIndex ) sqlite3FreeIndex(db, pIndex); + if( pTab ){ + /* Ensure all REPLACE indexes on pTab are at the end of the pIndex list. + ** The list was already ordered when this routine was entered, so at this + ** point at most a single index (the newly added index) will be out of + ** order. So we have to reorder at most one index. */ + Index **ppFrom; + Index *pThis; + for(ppFrom=&pTab->pIndex; (pThis = *ppFrom)!=0; ppFrom=&pThis->pNext){ + Index *pNext; + if( pThis->onError!=OE_Replace ) continue; + while( (pNext = pThis->pNext)!=0 && pNext->onError!=OE_Replace ){ + *ppFrom = pNext; + pThis->pNext = pNext->pNext; + pNext->pNext = pThis; + ppFrom = &pNext->pNext; + } + break; + } +#ifdef SQLITE_DEBUG + /* Verify that all REPLACE indexes really are now at the end + ** of the index list. In other words, no other index type ever + ** comes after a REPLACE index on the list. */ + for(pThis = pTab->pIndex; pThis; pThis=pThis->pNext){ + assert( pThis->onError!=OE_Replace + || pThis->pNext==0 + || pThis->pNext->onError==OE_Replace ); + } +#endif + } + sqlite3ExprDelete(db, pPIWhere); + sqlite3ExprListDelete(db, pList); + sqlite3SrcListDelete(db, pTblName); + sqlite3DbFree(db, zName); +} + +/* +** Fill the Index.aiRowEst[] array with default information - information +** to be used when we have not run the ANALYZE command. +** +** aiRowEst[0] is supposed to contain the number of elements in the index. +** Since we do not know, guess 1 million. aiRowEst[1] is an estimate of the +** number of rows in the table that match any particular value of the +** first column of the index. aiRowEst[2] is an estimate of the number +** of rows that match any particular combination of the first 2 columns +** of the index. And so forth. It must always be the case that +* +** aiRowEst[N]<=aiRowEst[N-1] +** aiRowEst[N]>=1 +** +** Apart from that, we have little to go on besides intuition as to +** how aiRowEst[] should be initialized. The numbers generated here +** are based on typical values found in actual indices. +*/ +SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){ + /* 10, 9, 8, 7, 6 */ + static const LogEst aVal[] = { 33, 32, 30, 28, 26 }; + LogEst *a = pIdx->aiRowLogEst; + LogEst x; + int nCopy = MIN(ArraySize(aVal), pIdx->nKeyCol); + int i; + + /* Indexes with default row estimates should not have stat1 data */ + assert( !pIdx->hasStat1 ); + + /* Set the first entry (number of rows in the index) to the estimated + ** number of rows in the table, or half the number of rows in the table + ** for a partial index. + ** + ** 2020-05-27: If some of the stat data is coming from the sqlite_stat1 + ** table but other parts we are having to guess at, then do not let the + ** estimated number of rows in the table be less than 1000 (LogEst 99). + ** Failure to do this can cause the indexes for which we do not have + ** stat1 data to be ignored by the query planner. + */ + x = pIdx->pTable->nRowLogEst; + assert( 99==sqlite3LogEst(1000) ); + if( x<99 ){ + pIdx->pTable->nRowLogEst = x = 99; + } + if( pIdx->pPartIdxWhere!=0 ){ x -= 10; assert( 10==sqlite3LogEst(2) ); } + a[0] = x; + + /* Estimate that a[1] is 10, a[2] is 9, a[3] is 8, a[4] is 7, a[5] is + ** 6 and each subsequent value (if any) is 5. */ + memcpy(&a[1], aVal, nCopy*sizeof(LogEst)); + for(i=nCopy+1; i<=pIdx->nKeyCol; i++){ + a[i] = 23; assert( 23==sqlite3LogEst(5) ); + } + + assert( 0==sqlite3LogEst(1) ); + if( IsUniqueIndex(pIdx) ) a[pIdx->nKeyCol] = 0; +} + +/* +** This routine will drop an existing named index. This routine +** implements the DROP INDEX statement. +*/ +SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){ + Index *pIndex; + Vdbe *v; + sqlite3 *db = pParse->db; + int iDb; + + if( db->mallocFailed ){ + goto exit_drop_index; + } + assert( pParse->nErr==0 ); /* Never called with prior non-OOM errors */ + assert( pName->nSrc==1 ); + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ + goto exit_drop_index; + } + pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase); + if( pIndex==0 ){ + if( !ifExists ){ + sqlite3ErrorMsg(pParse, "no such index: %S", pName->a); + }else{ + sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); + sqlite3ForceNotReadOnly(pParse); + } + pParse->checkSchema = 1; + goto exit_drop_index; + } + if( pIndex->idxType!=SQLITE_IDXTYPE_APPDEF ){ + sqlite3ErrorMsg(pParse, "index associated with UNIQUE " + "or PRIMARY KEY constraint cannot be dropped", 0); + goto exit_drop_index; + } + iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); +#ifndef SQLITE_OMIT_AUTHORIZATION + { + int code = SQLITE_DROP_INDEX; + Table *pTab = pIndex->pTable; + const char *zDb = db->aDb[iDb].zDbSName; + const char *zTab = SCHEMA_TABLE(iDb); + if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){ + goto exit_drop_index; + } + if( !OMIT_TEMPDB && iDb==1 ) code = SQLITE_DROP_TEMP_INDEX; + if( sqlite3AuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){ + goto exit_drop_index; + } + } +#endif + + /* Generate code to remove the index and from the schema table */ + v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3BeginWriteOperation(pParse, 1, iDb); + sqlite3NestedParse(pParse, + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE name=%Q AND type='index'", + db->aDb[iDb].zDbSName, pIndex->zName + ); + sqlite3ClearStatTables(pParse, iDb, "idx", pIndex->zName); + sqlite3ChangeCookie(pParse, iDb); + destroyRootPage(pParse, pIndex->tnum, iDb); + sqlite3VdbeAddOp4(v, OP_DropIndex, iDb, 0, 0, pIndex->zName, 0); + } + +exit_drop_index: + sqlite3SrcListDelete(db, pName); +} + +/* +** pArray is a pointer to an array of objects. Each object in the +** array is szEntry bytes in size. This routine uses sqlite3DbRealloc() +** to extend the array so that there is space for a new object at the end. +** +** When this function is called, *pnEntry contains the current size of +** the array (in entries - so the allocation is ((*pnEntry) * szEntry) bytes +** in total). +** +** If the realloc() is successful (i.e. if no OOM condition occurs), the +** space allocated for the new object is zeroed, *pnEntry updated to +** reflect the new size of the array and a pointer to the new allocation +** returned. *pIdx is set to the index of the new array entry in this case. +** +** Otherwise, if the realloc() fails, *pIdx is set to -1, *pnEntry remains +** unchanged and a copy of pArray returned. +*/ +SQLITE_PRIVATE void *sqlite3ArrayAllocate( + sqlite3 *db, /* Connection to notify of malloc failures */ + void *pArray, /* Array of objects. Might be reallocated */ + int szEntry, /* Size of each object in the array */ + int *pnEntry, /* Number of objects currently in use */ + int *pIdx /* Write the index of a new slot here */ +){ + char *z; + sqlite3_int64 n = *pIdx = *pnEntry; + if( (n & (n-1))==0 ){ + sqlite3_int64 sz = (n==0) ? 1 : 2*n; + void *pNew = sqlite3DbRealloc(db, pArray, sz*szEntry); + if( pNew==0 ){ + *pIdx = -1; + return pArray; + } + pArray = pNew; + } + z = (char*)pArray; + memset(&z[n * szEntry], 0, szEntry); + ++*pnEntry; + return pArray; +} + +/* +** Append a new element to the given IdList. Create a new IdList if +** need be. +** +** A new IdList is returned, or NULL if malloc() fails. +*/ +SQLITE_PRIVATE IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token *pToken){ + sqlite3 *db = pParse->db; + int i; + if( pList==0 ){ + pList = sqlite3DbMallocZero(db, sizeof(IdList) ); + if( pList==0 ) return 0; + }else{ + IdList *pNew; + pNew = sqlite3DbRealloc(db, pList, + sizeof(IdList) + pList->nId*sizeof(pList->a)); + if( pNew==0 ){ + sqlite3IdListDelete(db, pList); + return 0; + } + pList = pNew; + } + i = pList->nId++; + pList->a[i].zName = sqlite3NameFromToken(db, pToken); + if( IN_RENAME_OBJECT && pList->a[i].zName ){ + sqlite3RenameTokenMap(pParse, (void*)pList->a[i].zName, pToken); + } + return pList; +} + +/* +** Delete an IdList. +*/ +SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3 *db, IdList *pList){ + int i; + assert( db!=0 ); + if( pList==0 ) return; + assert( pList->eU4!=EU4_EXPR ); /* EU4_EXPR mode is not currently used */ + for(i=0; i<pList->nId; i++){ + sqlite3DbFree(db, pList->a[i].zName); + } + sqlite3DbNNFreeNN(db, pList); +} + +/* +** Return the index in pList of the identifier named zId. Return -1 +** if not found. +*/ +SQLITE_PRIVATE int sqlite3IdListIndex(IdList *pList, const char *zName){ + int i; + assert( pList!=0 ); + for(i=0; i<pList->nId; i++){ + if( sqlite3StrICmp(pList->a[i].zName, zName)==0 ) return i; + } + return -1; +} + +/* +** Maximum size of a SrcList object. +** The SrcList object is used to represent the FROM clause of a +** SELECT statement, and the query planner cannot deal with more +** than 64 tables in a join. So any value larger than 64 here +** is sufficient for most uses. Smaller values, like say 10, are +** appropriate for small and memory-limited applications. +*/ +#ifndef SQLITE_MAX_SRCLIST +# define SQLITE_MAX_SRCLIST 200 +#endif + +/* +** Expand the space allocated for the given SrcList object by +** creating nExtra new slots beginning at iStart. iStart is zero based. +** New slots are zeroed. +** +** For example, suppose a SrcList initially contains two entries: A,B. +** To append 3 new entries onto the end, do this: +** +** sqlite3SrcListEnlarge(db, pSrclist, 3, 2); +** +** After the call above it would contain: A, B, nil, nil, nil. +** If the iStart argument had been 1 instead of 2, then the result +** would have been: A, nil, nil, nil, B. To prepend the new slots, +** the iStart value would be 0. The result then would +** be: nil, nil, nil, A, B. +** +** If a memory allocation fails or the SrcList becomes too large, leave +** the original SrcList unchanged, return NULL, and leave an error message +** in pParse. +*/ +SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge( + Parse *pParse, /* Parsing context into which errors are reported */ + SrcList *pSrc, /* The SrcList to be enlarged */ + int nExtra, /* Number of new slots to add to pSrc->a[] */ + int iStart /* Index in pSrc->a[] of first new slot */ +){ + int i; + + /* Sanity checking on calling parameters */ + assert( iStart>=0 ); + assert( nExtra>=1 ); + assert( pSrc!=0 ); + assert( iStart<=pSrc->nSrc ); + + /* Allocate additional space if needed */ + if( (u32)pSrc->nSrc+nExtra>pSrc->nAlloc ){ + SrcList *pNew; + sqlite3_int64 nAlloc = 2*(sqlite3_int64)pSrc->nSrc+nExtra; + sqlite3 *db = pParse->db; + + if( pSrc->nSrc+nExtra>=SQLITE_MAX_SRCLIST ){ + sqlite3ErrorMsg(pParse, "too many FROM clause terms, max: %d", + SQLITE_MAX_SRCLIST); + return 0; + } + if( nAlloc>SQLITE_MAX_SRCLIST ) nAlloc = SQLITE_MAX_SRCLIST; + pNew = sqlite3DbRealloc(db, pSrc, + sizeof(*pSrc) + (nAlloc-1)*sizeof(pSrc->a[0]) ); + if( pNew==0 ){ + assert( db->mallocFailed ); + return 0; + } + pSrc = pNew; + pSrc->nAlloc = nAlloc; + } + + /* Move existing slots that come after the newly inserted slots + ** out of the way */ + for(i=pSrc->nSrc-1; i>=iStart; i--){ + pSrc->a[i+nExtra] = pSrc->a[i]; + } + pSrc->nSrc += nExtra; + + /* Zero the newly allocated slots */ + memset(&pSrc->a[iStart], 0, sizeof(pSrc->a[0])*nExtra); + for(i=iStart; i<iStart+nExtra; i++){ + pSrc->a[i].iCursor = -1; + } + + /* Return a pointer to the enlarged SrcList */ + return pSrc; +} + + +/* +** Append a new table name to the given SrcList. Create a new SrcList if +** need be. A new entry is created in the SrcList even if pTable is NULL. +** +** A SrcList is returned, or NULL if there is an OOM error or if the +** SrcList grows to large. The returned +** SrcList might be the same as the SrcList that was input or it might be +** a new one. If an OOM error does occurs, then the prior value of pList +** that is input to this routine is automatically freed. +** +** If pDatabase is not null, it means that the table has an optional +** database name prefix. Like this: "database.table". The pDatabase +** points to the table name and the pTable points to the database name. +** The SrcList.a[].zName field is filled with the table name which might +** come from pTable (if pDatabase is NULL) or from pDatabase. +** SrcList.a[].zDatabase is filled with the database name from pTable, +** or with NULL if no database is specified. +** +** In other words, if call like this: +** +** sqlite3SrcListAppend(D,A,B,0); +** +** Then B is a table name and the database name is unspecified. If called +** like this: +** +** sqlite3SrcListAppend(D,A,B,C); +** +** Then C is the table name and B is the database name. If C is defined +** then so is B. In other words, we never have a case where: +** +** sqlite3SrcListAppend(D,A,0,C); +** +** Both pTable and pDatabase are assumed to be quoted. They are dequoted +** before being added to the SrcList. +*/ +SQLITE_PRIVATE SrcList *sqlite3SrcListAppend( + Parse *pParse, /* Parsing context, in which errors are reported */ + SrcList *pList, /* Append to this SrcList. NULL creates a new SrcList */ + Token *pTable, /* Table to append */ + Token *pDatabase /* Database of the table */ +){ + SrcItem *pItem; + sqlite3 *db; + assert( pDatabase==0 || pTable!=0 ); /* Cannot have C without B */ + assert( pParse!=0 ); + assert( pParse->db!=0 ); + db = pParse->db; + if( pList==0 ){ + pList = sqlite3DbMallocRawNN(pParse->db, sizeof(SrcList) ); + if( pList==0 ) return 0; + pList->nAlloc = 1; + pList->nSrc = 1; + memset(&pList->a[0], 0, sizeof(pList->a[0])); + pList->a[0].iCursor = -1; + }else{ + SrcList *pNew = sqlite3SrcListEnlarge(pParse, pList, 1, pList->nSrc); + if( pNew==0 ){ + sqlite3SrcListDelete(db, pList); + return 0; + }else{ + pList = pNew; + } + } + pItem = &pList->a[pList->nSrc-1]; + if( pDatabase && pDatabase->z==0 ){ + pDatabase = 0; + } + if( pDatabase ){ + pItem->zName = sqlite3NameFromToken(db, pDatabase); + pItem->zDatabase = sqlite3NameFromToken(db, pTable); + }else{ + pItem->zName = sqlite3NameFromToken(db, pTable); + pItem->zDatabase = 0; + } + return pList; +} + +/* +** Assign VdbeCursor index numbers to all tables in a SrcList +*/ +SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){ + int i; + SrcItem *pItem; + assert( pList || pParse->db->mallocFailed ); + if( ALWAYS(pList) ){ + for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){ + if( pItem->iCursor>=0 ) continue; + pItem->iCursor = pParse->nTab++; + if( pItem->pSelect ){ + sqlite3SrcListAssignCursors(pParse, pItem->pSelect->pSrc); + } + } + } +} + +/* +** Delete an entire SrcList including all its substructure. +*/ +SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){ + int i; + SrcItem *pItem; + assert( db!=0 ); + if( pList==0 ) return; + for(pItem=pList->a, i=0; i<pList->nSrc; i++, pItem++){ + if( pItem->zDatabase ) sqlite3DbNNFreeNN(db, pItem->zDatabase); + if( pItem->zName ) sqlite3DbNNFreeNN(db, pItem->zName); + if( pItem->zAlias ) sqlite3DbNNFreeNN(db, pItem->zAlias); + if( pItem->fg.isIndexedBy ) sqlite3DbFree(db, pItem->u1.zIndexedBy); + if( pItem->fg.isTabFunc ) sqlite3ExprListDelete(db, pItem->u1.pFuncArg); + sqlite3DeleteTable(db, pItem->pTab); + if( pItem->pSelect ) sqlite3SelectDelete(db, pItem->pSelect); + if( pItem->fg.isUsing ){ + sqlite3IdListDelete(db, pItem->u3.pUsing); + }else if( pItem->u3.pOn ){ + sqlite3ExprDelete(db, pItem->u3.pOn); + } + } + sqlite3DbNNFreeNN(db, pList); +} + +/* +** This routine is called by the parser to add a new term to the +** end of a growing FROM clause. The "p" parameter is the part of +** the FROM clause that has already been constructed. "p" is NULL +** if this is the first term of the FROM clause. pTable and pDatabase +** are the name of the table and database named in the FROM clause term. +** pDatabase is NULL if the database name qualifier is missing - the +** usual case. If the term has an alias, then pAlias points to the +** alias token. If the term is a subquery, then pSubquery is the +** SELECT statement that the subquery encodes. The pTable and +** pDatabase parameters are NULL for subqueries. The pOn and pUsing +** parameters are the content of the ON and USING clauses. +** +** Return a new SrcList which encodes is the FROM with the new +** term added. +*/ +SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm( + Parse *pParse, /* Parsing context */ + SrcList *p, /* The left part of the FROM clause already seen */ + Token *pTable, /* Name of the table to add to the FROM clause */ + Token *pDatabase, /* Name of the database containing pTable */ + Token *pAlias, /* The right-hand side of the AS subexpression */ + Select *pSubquery, /* A subquery used in place of a table name */ + OnOrUsing *pOnUsing /* Either the ON clause or the USING clause */ +){ + SrcItem *pItem; + sqlite3 *db = pParse->db; + if( !p && pOnUsing!=0 && (pOnUsing->pOn || pOnUsing->pUsing) ){ + sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s", + (pOnUsing->pOn ? "ON" : "USING") + ); + goto append_from_error; + } + p = sqlite3SrcListAppend(pParse, p, pTable, pDatabase); + if( p==0 ){ + goto append_from_error; + } + assert( p->nSrc>0 ); + pItem = &p->a[p->nSrc-1]; + assert( (pTable==0)==(pDatabase==0) ); + assert( pItem->zName==0 || pDatabase!=0 ); + if( IN_RENAME_OBJECT && pItem->zName ){ + Token *pToken = (ALWAYS(pDatabase) && pDatabase->z) ? pDatabase : pTable; + sqlite3RenameTokenMap(pParse, pItem->zName, pToken); + } + assert( pAlias!=0 ); + if( pAlias->n ){ + pItem->zAlias = sqlite3NameFromToken(db, pAlias); + } + if( pSubquery ){ + pItem->pSelect = pSubquery; + if( pSubquery->selFlags & SF_NestedFrom ){ + pItem->fg.isNestedFrom = 1; + } + } + assert( pOnUsing==0 || pOnUsing->pOn==0 || pOnUsing->pUsing==0 ); + assert( pItem->fg.isUsing==0 ); + if( pOnUsing==0 ){ + pItem->u3.pOn = 0; + }else if( pOnUsing->pUsing ){ + pItem->fg.isUsing = 1; + pItem->u3.pUsing = pOnUsing->pUsing; + }else{ + pItem->u3.pOn = pOnUsing->pOn; + } + return p; + +append_from_error: + assert( p==0 ); + sqlite3ClearOnOrUsing(db, pOnUsing); + sqlite3SelectDelete(db, pSubquery); + return 0; +} + +/* +** Add an INDEXED BY or NOT INDEXED clause to the most recently added +** element of the source-list passed as the second argument. +*/ +SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){ + assert( pIndexedBy!=0 ); + if( p && pIndexedBy->n>0 ){ + SrcItem *pItem; + assert( p->nSrc>0 ); + pItem = &p->a[p->nSrc-1]; + assert( pItem->fg.notIndexed==0 ); + assert( pItem->fg.isIndexedBy==0 ); + assert( pItem->fg.isTabFunc==0 ); + if( pIndexedBy->n==1 && !pIndexedBy->z ){ + /* A "NOT INDEXED" clause was supplied. See parse.y + ** construct "indexed_opt" for details. */ + pItem->fg.notIndexed = 1; + }else{ + pItem->u1.zIndexedBy = sqlite3NameFromToken(pParse->db, pIndexedBy); + pItem->fg.isIndexedBy = 1; + assert( pItem->fg.isCte==0 ); /* No collision on union u2 */ + } + } +} + +/* +** Append the contents of SrcList p2 to SrcList p1 and return the resulting +** SrcList. Or, if an error occurs, return NULL. In all cases, p1 and p2 +** are deleted by this function. +*/ +SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){ + assert( p1 && p1->nSrc==1 ); + if( p2 ){ + SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, 1); + if( pNew==0 ){ + sqlite3SrcListDelete(pParse->db, p2); + }else{ + p1 = pNew; + memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(SrcItem)); + sqlite3DbFree(pParse->db, p2); + p1->a[0].fg.jointype |= (JT_LTORJ & p1->a[1].fg.jointype); + } + } + return p1; +} + +/* +** Add the list of function arguments to the SrcList entry for a +** table-valued-function. +*/ +SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *pList){ + if( p ){ + SrcItem *pItem = &p->a[p->nSrc-1]; + assert( pItem->fg.notIndexed==0 ); + assert( pItem->fg.isIndexedBy==0 ); + assert( pItem->fg.isTabFunc==0 ); + pItem->u1.pFuncArg = pList; + pItem->fg.isTabFunc = 1; + }else{ + sqlite3ExprListDelete(pParse->db, pList); + } +} + +/* +** When building up a FROM clause in the parser, the join operator +** is initially attached to the left operand. But the code generator +** expects the join operator to be on the right operand. This routine +** Shifts all join operators from left to right for an entire FROM +** clause. +** +** Example: Suppose the join is like this: +** +** A natural cross join B +** +** The operator is "natural cross join". The A and B operands are stored +** in p->a[0] and p->a[1], respectively. The parser initially stores the +** operator with A. This routine shifts that operator over to B. +** +** Additional changes: +** +** * All tables to the left of the right-most RIGHT JOIN are tagged with +** JT_LTORJ (mnemonic: Left Table Of Right Join) so that the +** code generator can easily tell that the table is part of +** the left operand of at least one RIGHT JOIN. +*/ +SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(Parse *pParse, SrcList *p){ + (void)pParse; + if( p && p->nSrc>1 ){ + int i = p->nSrc-1; + u8 allFlags = 0; + do{ + allFlags |= p->a[i].fg.jointype = p->a[i-1].fg.jointype; + }while( (--i)>0 ); + p->a[0].fg.jointype = 0; + + /* All terms to the left of a RIGHT JOIN should be tagged with the + ** JT_LTORJ flags */ + if( allFlags & JT_RIGHT ){ + for(i=p->nSrc-1; ALWAYS(i>0) && (p->a[i].fg.jointype&JT_RIGHT)==0; i--){} + i--; + assert( i>=0 ); + do{ + p->a[i].fg.jointype |= JT_LTORJ; + }while( (--i)>=0 ); + } + } +} + +/* +** Generate VDBE code for a BEGIN statement. +*/ +SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){ + sqlite3 *db; + Vdbe *v; + int i; + + assert( pParse!=0 ); + db = pParse->db; + assert( db!=0 ); + if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "BEGIN", 0, 0) ){ + return; + } + v = sqlite3GetVdbe(pParse); + if( !v ) return; + if( type!=TK_DEFERRED ){ + for(i=0; i<db->nDb; i++){ + int eTxnType; + Btree *pBt = db->aDb[i].pBt; + if( pBt && sqlite3BtreeIsReadonly(pBt) ){ + eTxnType = 0; /* Read txn */ + }else if( type==TK_EXCLUSIVE ){ + eTxnType = 2; /* Exclusive txn */ + }else{ + eTxnType = 1; /* Write txn */ + } + sqlite3VdbeAddOp2(v, OP_Transaction, i, eTxnType); + sqlite3VdbeUsesBtree(v, i); + } + } + sqlite3VdbeAddOp0(v, OP_AutoCommit); +} + +/* +** Generate VDBE code for a COMMIT or ROLLBACK statement. +** Code for ROLLBACK is generated if eType==TK_ROLLBACK. Otherwise +** code is generated for a COMMIT. +*/ +SQLITE_PRIVATE void sqlite3EndTransaction(Parse *pParse, int eType){ + Vdbe *v; + int isRollback; + + assert( pParse!=0 ); + assert( pParse->db!=0 ); + assert( eType==TK_COMMIT || eType==TK_END || eType==TK_ROLLBACK ); + isRollback = eType==TK_ROLLBACK; + if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, + isRollback ? "ROLLBACK" : "COMMIT", 0, 0) ){ + return; + } + v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3VdbeAddOp2(v, OP_AutoCommit, 1, isRollback); + } +} + +/* +** This function is called by the parser when it parses a command to create, +** release or rollback an SQL savepoint. +*/ +SQLITE_PRIVATE void sqlite3Savepoint(Parse *pParse, int op, Token *pName){ + char *zName = sqlite3NameFromToken(pParse->db, pName); + if( zName ){ + Vdbe *v = sqlite3GetVdbe(pParse); +#ifndef SQLITE_OMIT_AUTHORIZATION + static const char * const az[] = { "BEGIN", "RELEASE", "ROLLBACK" }; + assert( !SAVEPOINT_BEGIN && SAVEPOINT_RELEASE==1 && SAVEPOINT_ROLLBACK==2 ); +#endif + if( !v || sqlite3AuthCheck(pParse, SQLITE_SAVEPOINT, az[op], zName, 0) ){ + sqlite3DbFree(pParse->db, zName); + return; + } + sqlite3VdbeAddOp4(v, OP_Savepoint, op, 0, 0, zName, P4_DYNAMIC); + } +} + +/* +** Make sure the TEMP database is open and available for use. Return +** the number of errors. Leave any error messages in the pParse structure. +*/ +SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){ + sqlite3 *db = pParse->db; + if( db->aDb[1].pBt==0 && !pParse->explain ){ + int rc; + Btree *pBt; + static const int flags = + SQLITE_OPEN_READWRITE | + SQLITE_OPEN_CREATE | + SQLITE_OPEN_EXCLUSIVE | + SQLITE_OPEN_DELETEONCLOSE | + SQLITE_OPEN_TEMP_DB; + + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pBt, 0, flags); + if( rc!=SQLITE_OK ){ + sqlite3ErrorMsg(pParse, "unable to open a temporary database " + "file for storing temporary tables"); + pParse->rc = rc; + return 1; + } + db->aDb[1].pBt = pBt; + assert( db->aDb[1].pSchema ); + if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize, 0, 0) ){ + sqlite3OomFault(db); + return 1; + } + } + return 0; +} + +/* +** Record the fact that the schema cookie will need to be verified +** for database iDb. The code to actually verify the schema cookie +** will occur at the end of the top-level VDBE and will be generated +** later, by sqlite3FinishCoding(). +*/ +static void sqlite3CodeVerifySchemaAtToplevel(Parse *pToplevel, int iDb){ + assert( iDb>=0 && iDb<pToplevel->db->nDb ); + assert( pToplevel->db->aDb[iDb].pBt!=0 || iDb==1 ); + assert( iDb<SQLITE_MAX_DB ); + assert( sqlite3SchemaMutexHeld(pToplevel->db, iDb, 0) ); + if( DbMaskTest(pToplevel->cookieMask, iDb)==0 ){ + DbMaskSet(pToplevel->cookieMask, iDb); + if( !OMIT_TEMPDB && iDb==1 ){ + sqlite3OpenTempDatabase(pToplevel); + } + } +} +SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ + sqlite3CodeVerifySchemaAtToplevel(sqlite3ParseToplevel(pParse), iDb); +} + + +/* +** If argument zDb is NULL, then call sqlite3CodeVerifySchema() for each +** attached database. Otherwise, invoke it for the database named zDb only. +*/ +SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb){ + sqlite3 *db = pParse->db; + int i; + for(i=0; i<db->nDb; i++){ + Db *pDb = &db->aDb[i]; + if( pDb->pBt && (!zDb || 0==sqlite3StrICmp(zDb, pDb->zDbSName)) ){ + sqlite3CodeVerifySchema(pParse, i); + } + } +} + +/* +** Generate VDBE code that prepares for doing an operation that +** might change the database. +** +** This routine starts a new transaction if we are not already within +** a transaction. If we are already within a transaction, then a checkpoint +** is set if the setStatement parameter is true. A checkpoint should +** be set for operations that might fail (due to a constraint) part of +** the way through and which will need to undo some writes without having to +** rollback the whole transaction. For operations where all constraints +** can be checked before any changes are made to the database, it is never +** necessary to undo a write and the checkpoint should not be set. +*/ +SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){ + Parse *pToplevel = sqlite3ParseToplevel(pParse); + sqlite3CodeVerifySchemaAtToplevel(pToplevel, iDb); + DbMaskSet(pToplevel->writeMask, iDb); + pToplevel->isMultiWrite |= setStatement; +} + +/* +** Indicate that the statement currently under construction might write +** more than one entry (example: deleting one row then inserting another, +** inserting multiple rows in a table, or inserting a row and index entries.) +** If an abort occurs after some of these writes have completed, then it will +** be necessary to undo the completed writes. +*/ +SQLITE_PRIVATE void sqlite3MultiWrite(Parse *pParse){ + Parse *pToplevel = sqlite3ParseToplevel(pParse); + pToplevel->isMultiWrite = 1; +} + +/* +** The code generator calls this routine if is discovers that it is +** possible to abort a statement prior to completion. In order to +** perform this abort without corrupting the database, we need to make +** sure that the statement is protected by a statement transaction. +** +** Technically, we only need to set the mayAbort flag if the +** isMultiWrite flag was previously set. There is a time dependency +** such that the abort must occur after the multiwrite. This makes +** some statements involving the REPLACE conflict resolution algorithm +** go a little faster. But taking advantage of this time dependency +** makes it more difficult to prove that the code is correct (in +** particular, it prevents us from writing an effective +** implementation of sqlite3AssertMayAbort()) and so we have chosen +** to take the safe route and skip the optimization. +*/ +SQLITE_PRIVATE void sqlite3MayAbort(Parse *pParse){ + Parse *pToplevel = sqlite3ParseToplevel(pParse); + pToplevel->mayAbort = 1; +} + +/* +** Code an OP_Halt that causes the vdbe to return an SQLITE_CONSTRAINT +** error. The onError parameter determines which (if any) of the statement +** and/or current transaction is rolled back. +*/ +SQLITE_PRIVATE void sqlite3HaltConstraint( + Parse *pParse, /* Parsing context */ + int errCode, /* extended error code */ + int onError, /* Constraint type */ + char *p4, /* Error message */ + i8 p4type, /* P4_STATIC or P4_TRANSIENT */ + u8 p5Errmsg /* P5_ErrMsg type */ +){ + Vdbe *v; + assert( pParse->pVdbe!=0 ); + v = sqlite3GetVdbe(pParse); + assert( (errCode&0xff)==SQLITE_CONSTRAINT || pParse->nested ); + if( onError==OE_Abort ){ + sqlite3MayAbort(pParse); + } + sqlite3VdbeAddOp4(v, OP_Halt, errCode, onError, 0, p4, p4type); + sqlite3VdbeChangeP5(v, p5Errmsg); +} + +/* +** Code an OP_Halt due to UNIQUE or PRIMARY KEY constraint violation. +*/ +SQLITE_PRIVATE void sqlite3UniqueConstraint( + Parse *pParse, /* Parsing context */ + int onError, /* Constraint type */ + Index *pIdx /* The index that triggers the constraint */ +){ + char *zErr; + int j; + StrAccum errMsg; + Table *pTab = pIdx->pTable; + + sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, + pParse->db->aLimit[SQLITE_LIMIT_LENGTH]); + if( pIdx->aColExpr ){ + sqlite3_str_appendf(&errMsg, "index '%q'", pIdx->zName); + }else{ + for(j=0; j<pIdx->nKeyCol; j++){ + char *zCol; + assert( pIdx->aiColumn[j]>=0 ); + zCol = pTab->aCol[pIdx->aiColumn[j]].zCnName; + if( j ) sqlite3_str_append(&errMsg, ", ", 2); + sqlite3_str_appendall(&errMsg, pTab->zName); + sqlite3_str_append(&errMsg, ".", 1); + sqlite3_str_appendall(&errMsg, zCol); + } + } + zErr = sqlite3StrAccumFinish(&errMsg); + sqlite3HaltConstraint(pParse, + IsPrimaryKeyIndex(pIdx) ? SQLITE_CONSTRAINT_PRIMARYKEY + : SQLITE_CONSTRAINT_UNIQUE, + onError, zErr, P4_DYNAMIC, P5_ConstraintUnique); +} + + +/* +** Code an OP_Halt due to non-unique rowid. +*/ +SQLITE_PRIVATE void sqlite3RowidConstraint( + Parse *pParse, /* Parsing context */ + int onError, /* Conflict resolution algorithm */ + Table *pTab /* The table with the non-unique rowid */ +){ + char *zMsg; + int rc; + if( pTab->iPKey>=0 ){ + zMsg = sqlite3MPrintf(pParse->db, "%s.%s", pTab->zName, + pTab->aCol[pTab->iPKey].zCnName); + rc = SQLITE_CONSTRAINT_PRIMARYKEY; + }else{ + zMsg = sqlite3MPrintf(pParse->db, "%s.rowid", pTab->zName); + rc = SQLITE_CONSTRAINT_ROWID; + } + sqlite3HaltConstraint(pParse, rc, onError, zMsg, P4_DYNAMIC, + P5_ConstraintUnique); +} + +/* +** Check to see if pIndex uses the collating sequence pColl. Return +** true if it does and false if it does not. +*/ +#ifndef SQLITE_OMIT_REINDEX +static int collationMatch(const char *zColl, Index *pIndex){ + int i; + assert( zColl!=0 ); + for(i=0; i<pIndex->nColumn; i++){ + const char *z = pIndex->azColl[i]; + assert( z!=0 || pIndex->aiColumn[i]<0 ); + if( pIndex->aiColumn[i]>=0 && 0==sqlite3StrICmp(z, zColl) ){ + return 1; + } + } + return 0; +} +#endif + +/* +** Recompute all indices of pTab that use the collating sequence pColl. +** If pColl==0 then recompute all indices of pTab. +*/ +#ifndef SQLITE_OMIT_REINDEX +static void reindexTable(Parse *pParse, Table *pTab, char const *zColl){ + if( !IsVirtual(pTab) ){ + Index *pIndex; /* An index associated with pTab */ + + for(pIndex=pTab->pIndex; pIndex; pIndex=pIndex->pNext){ + if( zColl==0 || collationMatch(zColl, pIndex) ){ + int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + sqlite3BeginWriteOperation(pParse, 0, iDb); + sqlite3RefillIndex(pParse, pIndex, -1); + } + } + } +} +#endif + +/* +** Recompute all indices of all tables in all databases where the +** indices use the collating sequence pColl. If pColl==0 then recompute +** all indices everywhere. +*/ +#ifndef SQLITE_OMIT_REINDEX +static void reindexDatabases(Parse *pParse, char const *zColl){ + Db *pDb; /* A single database */ + int iDb; /* The database index number */ + sqlite3 *db = pParse->db; /* The database connection */ + HashElem *k; /* For looping over tables in pDb */ + Table *pTab; /* A table in the database */ + + assert( sqlite3BtreeHoldsAllMutexes(db) ); /* Needed for schema access */ + for(iDb=0, pDb=db->aDb; iDb<db->nDb; iDb++, pDb++){ + assert( pDb!=0 ); + for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){ + pTab = (Table*)sqliteHashData(k); + reindexTable(pParse, pTab, zColl); + } + } +} +#endif + +/* +** Generate code for the REINDEX command. +** +** REINDEX -- 1 +** REINDEX <collation> -- 2 +** REINDEX ?<database>.?<tablename> -- 3 +** REINDEX ?<database>.?<indexname> -- 4 +** +** Form 1 causes all indices in all attached databases to be rebuilt. +** Form 2 rebuilds all indices in all databases that use the named +** collating function. Forms 3 and 4 rebuild the named index or all +** indices associated with the named table. +*/ +#ifndef SQLITE_OMIT_REINDEX +SQLITE_PRIVATE void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){ + CollSeq *pColl; /* Collating sequence to be reindexed, or NULL */ + char *z; /* Name of a table or index */ + const char *zDb; /* Name of the database */ + Table *pTab; /* A table in the database */ + Index *pIndex; /* An index associated with pTab */ + int iDb; /* The database index number */ + sqlite3 *db = pParse->db; /* The database connection */ + Token *pObjName; /* Name of the table or index to be reindexed */ + + /* Read the database schema. If an error occurs, leave an error message + ** and code in pParse and return NULL. */ + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ + return; + } + + if( pName1==0 ){ + reindexDatabases(pParse, 0); + return; + }else if( NEVER(pName2==0) || pName2->z==0 ){ + char *zColl; + assert( pName1->z ); + zColl = sqlite3NameFromToken(pParse->db, pName1); + if( !zColl ) return; + pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); + if( pColl ){ + reindexDatabases(pParse, zColl); + sqlite3DbFree(db, zColl); + return; + } + sqlite3DbFree(db, zColl); + } + iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pObjName); + if( iDb<0 ) return; + z = sqlite3NameFromToken(db, pObjName); + if( z==0 ) return; + zDb = db->aDb[iDb].zDbSName; + pTab = sqlite3FindTable(db, z, zDb); + if( pTab ){ + reindexTable(pParse, pTab, 0); + sqlite3DbFree(db, z); + return; + } + pIndex = sqlite3FindIndex(db, z, zDb); + sqlite3DbFree(db, z); + if( pIndex ){ + sqlite3BeginWriteOperation(pParse, 0, iDb); + sqlite3RefillIndex(pParse, pIndex, -1); + return; + } + sqlite3ErrorMsg(pParse, "unable to identify the object to be reindexed"); +} +#endif + +/* +** Return a KeyInfo structure that is appropriate for the given Index. +** +** The caller should invoke sqlite3KeyInfoUnref() on the returned object +** when it has finished using it. +*/ +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ + int i; + int nCol = pIdx->nColumn; + int nKey = pIdx->nKeyCol; + KeyInfo *pKey; + if( pParse->nErr ) return 0; + if( pIdx->uniqNotNull ){ + pKey = sqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey); + }else{ + pKey = sqlite3KeyInfoAlloc(pParse->db, nCol, 0); + } + if( pKey ){ + assert( sqlite3KeyInfoIsWriteable(pKey) ); + for(i=0; i<nCol; i++){ + const char *zColl = pIdx->azColl[i]; + pKey->aColl[i] = zColl==sqlite3StrBINARY ? 0 : + sqlite3LocateCollSeq(pParse, zColl); + pKey->aSortFlags[i] = pIdx->aSortOrder[i]; + assert( 0==(pKey->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) ); + } + if( pParse->nErr ){ + assert( pParse->rc==SQLITE_ERROR_MISSING_COLLSEQ ); + if( pIdx->bNoQuery==0 ){ + /* Deactivate the index because it contains an unknown collating + ** sequence. The only way to reactive the index is to reload the + ** schema. Adding the missing collating sequence later does not + ** reactive the index. The application had the chance to register + ** the missing index using the collation-needed callback. For + ** simplicity, SQLite will not give the application a second chance. + */ + pIdx->bNoQuery = 1; + pParse->rc = SQLITE_ERROR_RETRY; + } + sqlite3KeyInfoUnref(pKey); + pKey = 0; + } + } + return pKey; +} + +#ifndef SQLITE_OMIT_CTE +/* +** Create a new CTE object +*/ +SQLITE_PRIVATE Cte *sqlite3CteNew( + Parse *pParse, /* Parsing context */ + Token *pName, /* Name of the common-table */ + ExprList *pArglist, /* Optional column name list for the table */ + Select *pQuery, /* Query used to initialize the table */ + u8 eM10d /* The MATERIALIZED flag */ +){ + Cte *pNew; + sqlite3 *db = pParse->db; + + pNew = sqlite3DbMallocZero(db, sizeof(*pNew)); + assert( pNew!=0 || db->mallocFailed ); + + if( db->mallocFailed ){ + sqlite3ExprListDelete(db, pArglist); + sqlite3SelectDelete(db, pQuery); + }else{ + pNew->pSelect = pQuery; + pNew->pCols = pArglist; + pNew->zName = sqlite3NameFromToken(pParse->db, pName); + pNew->eM10d = eM10d; + } + return pNew; +} + +/* +** Clear information from a Cte object, but do not deallocate storage +** for the object itself. +*/ +static void cteClear(sqlite3 *db, Cte *pCte){ + assert( pCte!=0 ); + sqlite3ExprListDelete(db, pCte->pCols); + sqlite3SelectDelete(db, pCte->pSelect); + sqlite3DbFree(db, pCte->zName); +} + +/* +** Free the contents of the CTE object passed as the second argument. +*/ +SQLITE_PRIVATE void sqlite3CteDelete(sqlite3 *db, Cte *pCte){ + assert( pCte!=0 ); + cteClear(db, pCte); + sqlite3DbFree(db, pCte); +} + +/* +** This routine is invoked once per CTE by the parser while parsing a +** WITH clause. The CTE described by the third argument is added to +** the WITH clause of the second argument. If the second argument is +** NULL, then a new WITH argument is created. +*/ +SQLITE_PRIVATE With *sqlite3WithAdd( + Parse *pParse, /* Parsing context */ + With *pWith, /* Existing WITH clause, or NULL */ + Cte *pCte /* CTE to add to the WITH clause */ +){ + sqlite3 *db = pParse->db; + With *pNew; + char *zName; + + if( pCte==0 ){ + return pWith; + } + + /* Check that the CTE name is unique within this WITH clause. If + ** not, store an error in the Parse structure. */ + zName = pCte->zName; + if( zName && pWith ){ + int i; + for(i=0; i<pWith->nCte; i++){ + if( sqlite3StrICmp(zName, pWith->a[i].zName)==0 ){ + sqlite3ErrorMsg(pParse, "duplicate WITH table name: %s", zName); + } + } + } + + if( pWith ){ + sqlite3_int64 nByte = sizeof(*pWith) + (sizeof(pWith->a[1]) * pWith->nCte); + pNew = sqlite3DbRealloc(db, pWith, nByte); + }else{ + pNew = sqlite3DbMallocZero(db, sizeof(*pWith)); + } + assert( (pNew!=0 && zName!=0) || db->mallocFailed ); + + if( db->mallocFailed ){ + sqlite3CteDelete(db, pCte); + pNew = pWith; + }else{ + pNew->a[pNew->nCte++] = *pCte; + sqlite3DbFree(db, pCte); + } + + return pNew; +} + +/* +** Free the contents of the With object passed as the second argument. +*/ +SQLITE_PRIVATE void sqlite3WithDelete(sqlite3 *db, With *pWith){ + if( pWith ){ + int i; + for(i=0; i<pWith->nCte; i++){ + cteClear(db, &pWith->a[i]); + } + sqlite3DbFree(db, pWith); + } +} +#endif /* !defined(SQLITE_OMIT_CTE) */ + +/************** End of build.c ***********************************************/ +/************** Begin file callback.c ****************************************/ +/* +** 2005 May 23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains functions used to access the internal hash tables +** of user defined functions and collation sequences. +*/ + +/* #include "sqliteInt.h" */ + +/* +** Invoke the 'collation needed' callback to request a collation sequence +** in the encoding enc of name zName, length nName. +*/ +static void callCollNeeded(sqlite3 *db, int enc, const char *zName){ + assert( !db->xCollNeeded || !db->xCollNeeded16 ); + if( db->xCollNeeded ){ + char *zExternal = sqlite3DbStrDup(db, zName); + if( !zExternal ) return; + db->xCollNeeded(db->pCollNeededArg, db, enc, zExternal); + sqlite3DbFree(db, zExternal); + } +#ifndef SQLITE_OMIT_UTF16 + if( db->xCollNeeded16 ){ + char const *zExternal; + sqlite3_value *pTmp = sqlite3ValueNew(db); + sqlite3ValueSetStr(pTmp, -1, zName, SQLITE_UTF8, SQLITE_STATIC); + zExternal = sqlite3ValueText(pTmp, SQLITE_UTF16NATIVE); + if( zExternal ){ + db->xCollNeeded16(db->pCollNeededArg, db, (int)ENC(db), zExternal); + } + sqlite3ValueFree(pTmp); + } +#endif +} + +/* +** This routine is called if the collation factory fails to deliver a +** collation function in the best encoding but there may be other versions +** of this collation function (for other text encodings) available. Use one +** of these instead if they exist. Avoid a UTF-8 <-> UTF-16 conversion if +** possible. +*/ +static int synthCollSeq(sqlite3 *db, CollSeq *pColl){ + CollSeq *pColl2; + char *z = pColl->zName; + int i; + static const u8 aEnc[] = { SQLITE_UTF16BE, SQLITE_UTF16LE, SQLITE_UTF8 }; + for(i=0; i<3; i++){ + pColl2 = sqlite3FindCollSeq(db, aEnc[i], z, 0); + if( pColl2->xCmp!=0 ){ + memcpy(pColl, pColl2, sizeof(CollSeq)); + pColl->xDel = 0; /* Do not copy the destructor */ + return SQLITE_OK; + } + } + return SQLITE_ERROR; +} + +/* +** This routine is called on a collation sequence before it is used to +** check that it is defined. An undefined collation sequence exists when +** a database is loaded that contains references to collation sequences +** that have not been defined by sqlite3_create_collation() etc. +** +** If required, this routine calls the 'collation needed' callback to +** request a definition of the collating sequence. If this doesn't work, +** an equivalent collating sequence that uses a text encoding different +** from the main database is substituted, if one is available. +*/ +SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *pParse, CollSeq *pColl){ + if( pColl && pColl->xCmp==0 ){ + const char *zName = pColl->zName; + sqlite3 *db = pParse->db; + CollSeq *p = sqlite3GetCollSeq(pParse, ENC(db), pColl, zName); + if( !p ){ + return SQLITE_ERROR; + } + assert( p==pColl ); + } + return SQLITE_OK; +} + + + +/* +** Locate and return an entry from the db.aCollSeq hash table. If the entry +** specified by zName and nName is not found and parameter 'create' is +** true, then create a new entry. Otherwise return NULL. +** +** Each pointer stored in the sqlite3.aCollSeq hash table contains an +** array of three CollSeq structures. The first is the collation sequence +** preferred for UTF-8, the second UTF-16le, and the third UTF-16be. +** +** Stored immediately after the three collation sequences is a copy of +** the collation sequence name. A pointer to this string is stored in +** each collation sequence structure. +*/ +static CollSeq *findCollSeqEntry( + sqlite3 *db, /* Database connection */ + const char *zName, /* Name of the collating sequence */ + int create /* Create a new entry if true */ +){ + CollSeq *pColl; + pColl = sqlite3HashFind(&db->aCollSeq, zName); + + if( 0==pColl && create ){ + int nName = sqlite3Strlen30(zName) + 1; + pColl = sqlite3DbMallocZero(db, 3*sizeof(*pColl) + nName); + if( pColl ){ + CollSeq *pDel = 0; + pColl[0].zName = (char*)&pColl[3]; + pColl[0].enc = SQLITE_UTF8; + pColl[1].zName = (char*)&pColl[3]; + pColl[1].enc = SQLITE_UTF16LE; + pColl[2].zName = (char*)&pColl[3]; + pColl[2].enc = SQLITE_UTF16BE; + memcpy(pColl[0].zName, zName, nName); + pDel = sqlite3HashInsert(&db->aCollSeq, pColl[0].zName, pColl); + + /* If a malloc() failure occurred in sqlite3HashInsert(), it will + ** return the pColl pointer to be deleted (because it wasn't added + ** to the hash table). + */ + assert( pDel==0 || pDel==pColl ); + if( pDel!=0 ){ + sqlite3OomFault(db); + sqlite3DbFree(db, pDel); + pColl = 0; + } + } + } + return pColl; +} + +/* +** Parameter zName points to a UTF-8 encoded string nName bytes long. +** Return the CollSeq* pointer for the collation sequence named zName +** for the encoding 'enc' from the database 'db'. +** +** If the entry specified is not found and 'create' is true, then create a +** new entry. Otherwise return NULL. +** +** A separate function sqlite3LocateCollSeq() is a wrapper around +** this routine. sqlite3LocateCollSeq() invokes the collation factory +** if necessary and generates an error message if the collating sequence +** cannot be found. +** +** See also: sqlite3LocateCollSeq(), sqlite3GetCollSeq() +*/ +SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq( + sqlite3 *db, /* Database connection to search */ + u8 enc, /* Desired text encoding */ + const char *zName, /* Name of the collating sequence. Might be NULL */ + int create /* True to create CollSeq if doesn't already exist */ +){ + CollSeq *pColl; + assert( SQLITE_UTF8==1 && SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); + assert( enc>=SQLITE_UTF8 && enc<=SQLITE_UTF16BE ); + if( zName ){ + pColl = findCollSeqEntry(db, zName, create); + if( pColl ) pColl += enc-1; + }else{ + pColl = db->pDfltColl; + } + return pColl; +} + +/* +** Change the text encoding for a database connection. This means that +** the pDfltColl must change as well. +*/ +SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8 enc){ + assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); + db->enc = enc; + /* EVIDENCE-OF: R-08308-17224 The default collating function for all + ** strings is BINARY. + */ + db->pDfltColl = sqlite3FindCollSeq(db, enc, sqlite3StrBINARY, 0); + sqlite3ExpirePreparedStatements(db, 1); +} + +/* +** This function is responsible for invoking the collation factory callback +** or substituting a collation sequence of a different encoding when the +** requested collation sequence is not available in the desired encoding. +** +** If it is not NULL, then pColl must point to the database native encoding +** collation sequence with name zName, length nName. +** +** The return value is either the collation sequence to be used in database +** db for collation type name zName, length nName, or NULL, if no collation +** sequence can be found. If no collation is found, leave an error message. +** +** See also: sqlite3LocateCollSeq(), sqlite3FindCollSeq() +*/ +SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq( + Parse *pParse, /* Parsing context */ + u8 enc, /* The desired encoding for the collating sequence */ + CollSeq *pColl, /* Collating sequence with native encoding, or NULL */ + const char *zName /* Collating sequence name */ +){ + CollSeq *p; + sqlite3 *db = pParse->db; + + p = pColl; + if( !p ){ + p = sqlite3FindCollSeq(db, enc, zName, 0); + } + if( !p || !p->xCmp ){ + /* No collation sequence of this type for this encoding is registered. + ** Call the collation factory to see if it can supply us with one. + */ + callCollNeeded(db, enc, zName); + p = sqlite3FindCollSeq(db, enc, zName, 0); + } + if( p && !p->xCmp && synthCollSeq(db, p) ){ + p = 0; + } + assert( !p || p->xCmp ); + if( p==0 ){ + sqlite3ErrorMsg(pParse, "no such collation sequence: %s", zName); + pParse->rc = SQLITE_ERROR_MISSING_COLLSEQ; + } + return p; +} + +/* +** This function returns the collation sequence for database native text +** encoding identified by the string zName. +** +** If the requested collation sequence is not available, or not available +** in the database native encoding, the collation factory is invoked to +** request it. If the collation factory does not supply such a sequence, +** and the sequence is available in another text encoding, then that is +** returned instead. +** +** If no versions of the requested collations sequence are available, or +** another error occurs, NULL is returned and an error message written into +** pParse. +** +** This routine is a wrapper around sqlite3FindCollSeq(). This routine +** invokes the collation factory if the named collation cannot be found +** and generates an error message. +** +** See also: sqlite3FindCollSeq(), sqlite3GetCollSeq() +*/ +SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName){ + sqlite3 *db = pParse->db; + u8 enc = ENC(db); + u8 initbusy = db->init.busy; + CollSeq *pColl; + + pColl = sqlite3FindCollSeq(db, enc, zName, initbusy); + if( !initbusy && (!pColl || !pColl->xCmp) ){ + pColl = sqlite3GetCollSeq(pParse, enc, pColl, zName); + } + + return pColl; +} + +/* During the search for the best function definition, this procedure +** is called to test how well the function passed as the first argument +** matches the request for a function with nArg arguments in a system +** that uses encoding enc. The value returned indicates how well the +** request is matched. A higher value indicates a better match. +** +** If nArg is -1 that means to only return a match (non-zero) if p->nArg +** is also -1. In other words, we are searching for a function that +** takes a variable number of arguments. +** +** If nArg is -2 that means that we are searching for any function +** regardless of the number of arguments it uses, so return a positive +** match score for any +** +** The returned value is always between 0 and 6, as follows: +** +** 0: Not a match. +** 1: UTF8/16 conversion required and function takes any number of arguments. +** 2: UTF16 byte order change required and function takes any number of args. +** 3: encoding matches and function takes any number of arguments +** 4: UTF8/16 conversion required - argument count matches exactly +** 5: UTF16 byte order conversion required - argument count matches exactly +** 6: Perfect match: encoding and argument count match exactly. +** +** If nArg==(-2) then any function with a non-null xSFunc is +** a perfect match and any function with xSFunc NULL is +** a non-match. +*/ +#define FUNC_PERFECT_MATCH 6 /* The score for a perfect match */ +static int matchQuality( + FuncDef *p, /* The function we are evaluating for match quality */ + int nArg, /* Desired number of arguments. (-1)==any */ + u8 enc /* Desired text encoding */ +){ + int match; + assert( p->nArg>=-1 ); + + /* Wrong number of arguments means "no match" */ + if( p->nArg!=nArg ){ + if( nArg==(-2) ) return (p->xSFunc==0) ? 0 : FUNC_PERFECT_MATCH; + if( p->nArg>=0 ) return 0; + } + + /* Give a better score to a function with a specific number of arguments + ** than to function that accepts any number of arguments. */ + if( p->nArg==nArg ){ + match = 4; + }else{ + match = 1; + } + + /* Bonus points if the text encoding matches */ + if( enc==(p->funcFlags & SQLITE_FUNC_ENCMASK) ){ + match += 2; /* Exact encoding match */ + }else if( (enc & p->funcFlags & 2)!=0 ){ + match += 1; /* Both are UTF16, but with different byte orders */ + } + + return match; +} + +/* +** Search a FuncDefHash for a function with the given name. Return +** a pointer to the matching FuncDef if found, or 0 if there is no match. +*/ +SQLITE_PRIVATE FuncDef *sqlite3FunctionSearch( + int h, /* Hash of the name */ + const char *zFunc /* Name of function */ +){ + FuncDef *p; + for(p=sqlite3BuiltinFunctions.a[h]; p; p=p->u.pHash){ + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); + if( sqlite3StrICmp(p->zName, zFunc)==0 ){ + return p; + } + } + return 0; +} + +/* +** Insert a new FuncDef into a FuncDefHash hash table. +*/ +SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs( + FuncDef *aDef, /* List of global functions to be inserted */ + int nDef /* Length of the apDef[] list */ +){ + int i; + for(i=0; i<nDef; i++){ + FuncDef *pOther; + const char *zName = aDef[i].zName; + int nName = sqlite3Strlen30(zName); + int h = SQLITE_FUNC_HASH(zName[0], nName); + assert( aDef[i].funcFlags & SQLITE_FUNC_BUILTIN ); + pOther = sqlite3FunctionSearch(h, zName); + if( pOther ){ + assert( pOther!=&aDef[i] && pOther->pNext!=&aDef[i] ); + aDef[i].pNext = pOther->pNext; + pOther->pNext = &aDef[i]; + }else{ + aDef[i].pNext = 0; + aDef[i].u.pHash = sqlite3BuiltinFunctions.a[h]; + sqlite3BuiltinFunctions.a[h] = &aDef[i]; + } + } +} + + + +/* +** Locate a user function given a name, a number of arguments and a flag +** indicating whether the function prefers UTF-16 over UTF-8. Return a +** pointer to the FuncDef structure that defines that function, or return +** NULL if the function does not exist. +** +** If the createFlag argument is true, then a new (blank) FuncDef +** structure is created and liked into the "db" structure if a +** no matching function previously existed. +** +** If nArg is -2, then the first valid function found is returned. A +** function is valid if xSFunc is non-zero. The nArg==(-2) +** case is used to see if zName is a valid function name for some number +** of arguments. If nArg is -2, then createFlag must be 0. +** +** If createFlag is false, then a function with the required name and +** number of arguments may be returned even if the eTextRep flag does not +** match that requested. +*/ +SQLITE_PRIVATE FuncDef *sqlite3FindFunction( + sqlite3 *db, /* An open database */ + const char *zName, /* Name of the function. zero-terminated */ + int nArg, /* Number of arguments. -1 means any number */ + u8 enc, /* Preferred text encoding */ + u8 createFlag /* Create new entry if true and does not otherwise exist */ +){ + FuncDef *p; /* Iterator variable */ + FuncDef *pBest = 0; /* Best match found so far */ + int bestScore = 0; /* Score of best match */ + int h; /* Hash value */ + int nName; /* Length of the name */ + + assert( nArg>=(-2) ); + assert( nArg>=(-1) || createFlag==0 ); + nName = sqlite3Strlen30(zName); + + /* First search for a match amongst the application-defined functions. + */ + p = (FuncDef*)sqlite3HashFind(&db->aFunc, zName); + while( p ){ + int score = matchQuality(p, nArg, enc); + if( score>bestScore ){ + pBest = p; + bestScore = score; + } + p = p->pNext; + } + + /* If no match is found, search the built-in functions. + ** + ** If the DBFLAG_PreferBuiltin flag is set, then search the built-in + ** functions even if a prior app-defined function was found. And give + ** priority to built-in functions. + ** + ** Except, if createFlag is true, that means that we are trying to + ** install a new function. Whatever FuncDef structure is returned it will + ** have fields overwritten with new information appropriate for the + ** new function. But the FuncDefs for built-in functions are read-only. + ** So we must not search for built-ins when creating a new function. + */ + if( !createFlag && (pBest==0 || (db->mDbFlags & DBFLAG_PreferBuiltin)!=0) ){ + bestScore = 0; + h = SQLITE_FUNC_HASH(sqlite3UpperToLower[(u8)zName[0]], nName); + p = sqlite3FunctionSearch(h, zName); + while( p ){ + int score = matchQuality(p, nArg, enc); + if( score>bestScore ){ + pBest = p; + bestScore = score; + } + p = p->pNext; + } + } + + /* If the createFlag parameter is true and the search did not reveal an + ** exact match for the name, number of arguments and encoding, then add a + ** new entry to the hash table and return it. + */ + if( createFlag && bestScore<FUNC_PERFECT_MATCH && + (pBest = sqlite3DbMallocZero(db, sizeof(*pBest)+nName+1))!=0 ){ + FuncDef *pOther; + u8 *z; + pBest->zName = (const char*)&pBest[1]; + pBest->nArg = (u16)nArg; + pBest->funcFlags = enc; + memcpy((char*)&pBest[1], zName, nName+1); + for(z=(u8*)pBest->zName; *z; z++) *z = sqlite3UpperToLower[*z]; + pOther = (FuncDef*)sqlite3HashInsert(&db->aFunc, pBest->zName, pBest); + if( pOther==pBest ){ + sqlite3DbFree(db, pBest); + sqlite3OomFault(db); + return 0; + }else{ + pBest->pNext = pOther; + } + } + + if( pBest && (pBest->xSFunc || createFlag) ){ + return pBest; + } + return 0; +} + +/* +** Free all resources held by the schema structure. The void* argument points +** at a Schema struct. This function does not call sqlite3DbFree(db, ) on the +** pointer itself, it just cleans up subsidiary resources (i.e. the contents +** of the schema hash tables). +** +** The Schema.cache_size variable is not cleared. +*/ +SQLITE_PRIVATE void sqlite3SchemaClear(void *p){ + Hash temp1; + Hash temp2; + HashElem *pElem; + Schema *pSchema = (Schema *)p; + sqlite3 xdb; + + memset(&xdb, 0, sizeof(xdb)); + temp1 = pSchema->tblHash; + temp2 = pSchema->trigHash; + sqlite3HashInit(&pSchema->trigHash); + sqlite3HashClear(&pSchema->idxHash); + for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){ + sqlite3DeleteTrigger(&xdb, (Trigger*)sqliteHashData(pElem)); + } + sqlite3HashClear(&temp2); + sqlite3HashInit(&pSchema->tblHash); + for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){ + Table *pTab = sqliteHashData(pElem); + sqlite3DeleteTable(&xdb, pTab); + } + sqlite3HashClear(&temp1); + sqlite3HashClear(&pSchema->fkeyHash); + pSchema->pSeqTab = 0; + if( pSchema->schemaFlags & DB_SchemaLoaded ){ + pSchema->iGeneration++; + } + pSchema->schemaFlags &= ~(DB_SchemaLoaded|DB_ResetWanted); +} + +/* +** Find and return the schema associated with a BTree. Create +** a new one if necessary. +*/ +SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){ + Schema * p; + if( pBt ){ + p = (Schema *)sqlite3BtreeSchema(pBt, sizeof(Schema), sqlite3SchemaClear); + }else{ + p = (Schema *)sqlite3DbMallocZero(0, sizeof(Schema)); + } + if( !p ){ + sqlite3OomFault(db); + }else if ( 0==p->file_format ){ + sqlite3HashInit(&p->tblHash); + sqlite3HashInit(&p->idxHash); + sqlite3HashInit(&p->trigHash); + sqlite3HashInit(&p->fkeyHash); + p->enc = SQLITE_UTF8; + } + return p; +} + +/************** End of callback.c ********************************************/ +/************** Begin file delete.c ******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the parser +** in order to generate code for DELETE FROM statements. +*/ +/* #include "sqliteInt.h" */ + +/* +** While a SrcList can in general represent multiple tables and subqueries +** (as in the FROM clause of a SELECT statement) in this case it contains +** the name of a single table, as one might find in an INSERT, DELETE, +** or UPDATE statement. Look up that table in the symbol table and +** return a pointer. Set an error message and return NULL if the table +** name is not found or if any other error occurs. +** +** The following fields are initialized appropriate in pSrc: +** +** pSrc->a[0].pTab Pointer to the Table object +** pSrc->a[0].pIndex Pointer to the INDEXED BY index, if there is one +** +*/ +SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ + SrcItem *pItem = pSrc->a; + Table *pTab; + assert( pItem && pSrc->nSrc>=1 ); + pTab = sqlite3LocateTableItem(pParse, 0, pItem); + if( pItem->pTab ) sqlite3DeleteTable(pParse->db, pItem->pTab); + pItem->pTab = pTab; + pItem->fg.notCte = 1; + if( pTab ){ + pTab->nTabRef++; + if( pItem->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pItem) ){ + pTab = 0; + } + } + return pTab; +} + +/* Generate byte-code that will report the number of rows modified +** by a DELETE, INSERT, or UPDATE statement. +*/ +SQLITE_PRIVATE void sqlite3CodeChangeCount(Vdbe *v, int regCounter, const char *zColName){ + sqlite3VdbeAddOp0(v, OP_FkCheck); + sqlite3VdbeAddOp2(v, OP_ResultRow, regCounter, 1); + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zColName, SQLITE_STATIC); +} + +/* Return true if table pTab is read-only. +** +** A table is read-only if any of the following are true: +** +** 1) It is a virtual table and no implementation of the xUpdate method +** has been provided +** +** 2) A trigger is currently being coded and the table is a virtual table +** that is SQLITE_VTAB_DIRECTONLY or if PRAGMA trusted_schema=OFF and +** the table is not SQLITE_VTAB_INNOCUOUS. +** +** 3) It is a system table (i.e. sqlite_schema), this call is not +** part of a nested parse and writable_schema pragma has not +** been specified +** +** 4) The table is a shadow table, the database connection is in +** defensive mode, and the current sqlite3_prepare() +** is for a top-level SQL statement. +*/ +static int vtabIsReadOnly(Parse *pParse, Table *pTab){ + if( sqlite3GetVTable(pParse->db, pTab)->pMod->pModule->xUpdate==0 ){ + return 1; + } + + /* Within triggers: + ** * Do not allow DELETE, INSERT, or UPDATE of SQLITE_VTAB_DIRECTONLY + ** virtual tables + ** * Only allow DELETE, INSERT, or UPDATE of non-SQLITE_VTAB_INNOCUOUS + ** virtual tables if PRAGMA trusted_schema=ON. + */ + if( pParse->pToplevel!=0 + && pTab->u.vtab.p->eVtabRisk > + ((pParse->db->flags & SQLITE_TrustedSchema)!=0) + ){ + sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"", + pTab->zName); + } + return 0; +} +static int tabIsReadOnly(Parse *pParse, Table *pTab){ + sqlite3 *db; + if( IsVirtual(pTab) ){ + return vtabIsReadOnly(pParse, pTab); + } + if( (pTab->tabFlags & (TF_Readonly|TF_Shadow))==0 ) return 0; + db = pParse->db; + if( (pTab->tabFlags & TF_Readonly)!=0 ){ + return sqlite3WritableSchema(db)==0 && pParse->nested==0; + } + assert( pTab->tabFlags & TF_Shadow ); + return sqlite3ReadOnlyShadowTables(db); +} + +/* +** Check to make sure the given table is writable. +** +** If pTab is not writable -> generate an error message and return 1. +** If pTab is writable but other errors have occurred -> return 1. +** If pTab is writable and no prior errors -> return 0; +*/ +SQLITE_PRIVATE int sqlite3IsReadOnly(Parse *pParse, Table *pTab, Trigger *pTrigger){ + if( tabIsReadOnly(pParse, pTab) ){ + sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab->zName); + return 1; + } +#ifndef SQLITE_OMIT_VIEW + if( IsView(pTab) + && (pTrigger==0 || (pTrigger->bReturning && pTrigger->pNext==0)) + ){ + sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName); + return 1; + } +#endif + return 0; +} + + +#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) +/* +** Evaluate a view and store its result in an ephemeral table. The +** pWhere argument is an optional WHERE clause that restricts the +** set of rows in the view that are to be added to the ephemeral table. +*/ +SQLITE_PRIVATE void sqlite3MaterializeView( + Parse *pParse, /* Parsing context */ + Table *pView, /* View definition */ + Expr *pWhere, /* Optional WHERE clause to be added */ + ExprList *pOrderBy, /* Optional ORDER BY clause */ + Expr *pLimit, /* Optional LIMIT clause */ + int iCur /* Cursor number for ephemeral table */ +){ + SelectDest dest; + Select *pSel; + SrcList *pFrom; + sqlite3 *db = pParse->db; + int iDb = sqlite3SchemaToIndex(db, pView->pSchema); + pWhere = sqlite3ExprDup(db, pWhere, 0); + pFrom = sqlite3SrcListAppend(pParse, 0, 0, 0); + if( pFrom ){ + assert( pFrom->nSrc==1 ); + pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName); + pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName); + assert( pFrom->a[0].fg.isUsing==0 ); + assert( pFrom->a[0].u3.pOn==0 ); + } + pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy, + SF_IncludeHidden, pLimit); + sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur); + sqlite3Select(pParse, pSel, &dest); + sqlite3SelectDelete(db, pSel); +} +#endif /* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */ + +#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) +/* +** Generate an expression tree to implement the WHERE, ORDER BY, +** and LIMIT/OFFSET portion of DELETE and UPDATE statements. +** +** DELETE FROM table_wxyz WHERE a<5 ORDER BY a LIMIT 1; +** \__________________________/ +** pLimitWhere (pInClause) +*/ +SQLITE_PRIVATE Expr *sqlite3LimitWhere( + Parse *pParse, /* The parser context */ + SrcList *pSrc, /* the FROM clause -- which tables to scan */ + Expr *pWhere, /* The WHERE clause. May be null */ + ExprList *pOrderBy, /* The ORDER BY clause. May be null */ + Expr *pLimit, /* The LIMIT clause. May be null */ + char *zStmtType /* Either DELETE or UPDATE. For err msgs. */ +){ + sqlite3 *db = pParse->db; + Expr *pLhs = NULL; /* LHS of IN(SELECT...) operator */ + Expr *pInClause = NULL; /* WHERE rowid IN ( select ) */ + ExprList *pEList = NULL; /* Expression list containing only pSelectRowid*/ + SrcList *pSelectSrc = NULL; /* SELECT rowid FROM x ... (dup of pSrc) */ + Select *pSelect = NULL; /* Complete SELECT tree */ + Table *pTab; + + /* Check that there isn't an ORDER BY without a LIMIT clause. + */ + if( pOrderBy && pLimit==0 ) { + sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", zStmtType); + sqlite3ExprDelete(pParse->db, pWhere); + sqlite3ExprListDelete(pParse->db, pOrderBy); + return 0; + } + + /* We only need to generate a select expression if there + ** is a limit/offset term to enforce. + */ + if( pLimit == 0 ) { + return pWhere; + } + + /* Generate a select expression tree to enforce the limit/offset + ** term for the DELETE or UPDATE statement. For example: + ** DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1 + ** becomes: + ** DELETE FROM table_a WHERE rowid IN ( + ** SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1 + ** ); + */ + + pTab = pSrc->a[0].pTab; + if( HasRowid(pTab) ){ + pLhs = sqlite3PExpr(pParse, TK_ROW, 0, 0); + pEList = sqlite3ExprListAppend( + pParse, 0, sqlite3PExpr(pParse, TK_ROW, 0, 0) + ); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->nKeyCol>=1 ); + if( pPk->nKeyCol==1 ){ + const char *zName; + assert( pPk->aiColumn[0]>=0 && pPk->aiColumn[0]<pTab->nCol ); + zName = pTab->aCol[pPk->aiColumn[0]].zCnName; + pLhs = sqlite3Expr(db, TK_ID, zName); + pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ID, zName)); + }else{ + int i; + for(i=0; i<pPk->nKeyCol; i++){ + Expr *p; + assert( pPk->aiColumn[i]>=0 && pPk->aiColumn[i]<pTab->nCol ); + p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zCnName); + pEList = sqlite3ExprListAppend(pParse, pEList, p); + } + pLhs = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); + if( pLhs ){ + pLhs->x.pList = sqlite3ExprListDup(db, pEList, 0); + } + } + } + + /* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree + ** and the SELECT subtree. */ + pSrc->a[0].pTab = 0; + pSelectSrc = sqlite3SrcListDup(db, pSrc, 0); + pSrc->a[0].pTab = pTab; + if( pSrc->a[0].fg.isIndexedBy ){ + assert( pSrc->a[0].fg.isCte==0 ); + pSrc->a[0].u2.pIBIndex = 0; + pSrc->a[0].fg.isIndexedBy = 0; + sqlite3DbFree(db, pSrc->a[0].u1.zIndexedBy); + }else if( pSrc->a[0].fg.isCte ){ + pSrc->a[0].u2.pCteUse->nUse++; + } + + /* generate the SELECT expression tree. */ + pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0, + pOrderBy,0,pLimit + ); + + /* now generate the new WHERE rowid IN clause for the DELETE/UPDATE */ + pInClause = sqlite3PExpr(pParse, TK_IN, pLhs, 0); + sqlite3PExprAddSelect(pParse, pInClause, pSelect); + return pInClause; +} +#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */ + /* && !defined(SQLITE_OMIT_SUBQUERY) */ + +/* +** Generate code for a DELETE FROM statement. +** +** DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL; +** \________/ \________________/ +** pTabList pWhere +*/ +SQLITE_PRIVATE void sqlite3DeleteFrom( + Parse *pParse, /* The parser context */ + SrcList *pTabList, /* The table from which we should delete things */ + Expr *pWhere, /* The WHERE clause. May be null */ + ExprList *pOrderBy, /* ORDER BY clause. May be null */ + Expr *pLimit /* LIMIT clause. May be null */ +){ + Vdbe *v; /* The virtual database engine */ + Table *pTab; /* The table from which records will be deleted */ + int i; /* Loop counter */ + WhereInfo *pWInfo; /* Information about the WHERE clause */ + Index *pIdx; /* For looping over indices of the table */ + int iTabCur; /* Cursor number for the table */ + int iDataCur = 0; /* VDBE cursor for the canonical data source */ + int iIdxCur = 0; /* Cursor number of the first index */ + int nIdx; /* Number of indices */ + sqlite3 *db; /* Main database structure */ + AuthContext sContext; /* Authorization context */ + NameContext sNC; /* Name context to resolve expressions in */ + int iDb; /* Database number */ + int memCnt = 0; /* Memory cell used for change counting */ + int rcauth; /* Value returned by authorization callback */ + int eOnePass; /* ONEPASS_OFF or _SINGLE or _MULTI */ + int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */ + u8 *aToOpen = 0; /* Open cursor iTabCur+j if aToOpen[j] is true */ + Index *pPk; /* The PRIMARY KEY index on the table */ + int iPk = 0; /* First of nPk registers holding PRIMARY KEY value */ + i16 nPk = 1; /* Number of columns in the PRIMARY KEY */ + int iKey; /* Memory cell holding key of row to be deleted */ + i16 nKey; /* Number of memory cells in the row key */ + int iEphCur = 0; /* Ephemeral table holding all primary key values */ + int iRowSet = 0; /* Register for rowset of rows to delete */ + int addrBypass = 0; /* Address of jump over the delete logic */ + int addrLoop = 0; /* Top of the delete loop */ + int addrEphOpen = 0; /* Instruction to open the Ephemeral table */ + int bComplex; /* True if there are triggers or FKs or + ** subqueries in the WHERE clause */ + +#ifndef SQLITE_OMIT_TRIGGER + int isView; /* True if attempting to delete from a view */ + Trigger *pTrigger; /* List of table triggers, if required */ +#endif + + memset(&sContext, 0, sizeof(sContext)); + db = pParse->db; + assert( db->pParse==pParse ); + if( pParse->nErr ){ + goto delete_from_cleanup; + } + assert( db->mallocFailed==0 ); + assert( pTabList->nSrc==1 ); + + /* Locate the table which we want to delete. This table has to be + ** put in an SrcList structure because some of the subroutines we + ** will be calling are designed to work with multiple tables and expect + ** an SrcList* parameter instead of just a Table* parameter. + */ + pTab = sqlite3SrcListLookup(pParse, pTabList); + if( pTab==0 ) goto delete_from_cleanup; + + /* Figure out if we have any triggers and if the table being + ** deleted from is a view + */ +#ifndef SQLITE_OMIT_TRIGGER + pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); + isView = IsView(pTab); +#else +# define pTrigger 0 +# define isView 0 +#endif + bComplex = pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0); +#ifdef SQLITE_OMIT_VIEW +# undef isView +# define isView 0 +#endif + +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Delete() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewDelete(pParse->pWith, pTabList, pWhere, + pOrderBy, pLimit, pTrigger); + } +#endif + +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + if( !isView ){ + pWhere = sqlite3LimitWhere( + pParse, pTabList, pWhere, pOrderBy, pLimit, "DELETE" + ); + pOrderBy = 0; + pLimit = 0; + } +#endif + + /* If pTab is really a view, make sure it has been initialized. + */ + if( sqlite3ViewGetColumnNames(pParse, pTab) ){ + goto delete_from_cleanup; + } + + if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){ + goto delete_from_cleanup; + } + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb<db->nDb ); + rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, + db->aDb[iDb].zDbSName); + assert( rcauth==SQLITE_OK || rcauth==SQLITE_DENY || rcauth==SQLITE_IGNORE ); + if( rcauth==SQLITE_DENY ){ + goto delete_from_cleanup; + } + assert(!isView || pTrigger); + + /* Assign cursor numbers to the table and all its indices. + */ + assert( pTabList->nSrc==1 ); + iTabCur = pTabList->a[0].iCursor = pParse->nTab++; + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ + pParse->nTab++; + } + + /* Start the view context + */ + if( isView ){ + sqlite3AuthContextPush(pParse, &sContext, pTab->zName); + } + + /* Begin generating code. + */ + v = sqlite3GetVdbe(pParse); + if( v==0 ){ + goto delete_from_cleanup; + } + if( pParse->nested==0 ) sqlite3VdbeCountChanges(v); + sqlite3BeginWriteOperation(pParse, bComplex, iDb); + + /* If we are trying to delete from a view, realize that view into + ** an ephemeral table. + */ +#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) + if( isView ){ + sqlite3MaterializeView(pParse, pTab, + pWhere, pOrderBy, pLimit, iTabCur + ); + iDataCur = iIdxCur = iTabCur; + pOrderBy = 0; + pLimit = 0; + } +#endif + + /* Resolve the column names in the WHERE clause. + */ + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = pParse; + sNC.pSrcList = pTabList; + if( sqlite3ResolveExprNames(&sNC, pWhere) ){ + goto delete_from_cleanup; + } + + /* Initialize the counter of the number of rows deleted, if + ** we are counting rows. + */ + if( (db->flags & SQLITE_CountRows)!=0 + && !pParse->nested + && !pParse->pTriggerTab + && !pParse->bReturning + ){ + memCnt = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt); + } + +#ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION + /* Special case: A DELETE without a WHERE clause deletes everything. + ** It is easier just to erase the whole table. Prior to version 3.6.5, + ** this optimization caused the row change count (the value returned by + ** API function sqlite3_count_changes) to be set incorrectly. + ** + ** The "rcauth==SQLITE_OK" terms is the + ** IMPLEMENTATION-OF: R-17228-37124 If the action code is SQLITE_DELETE and + ** the callback returns SQLITE_IGNORE then the DELETE operation proceeds but + ** the truncate optimization is disabled and all rows are deleted + ** individually. + */ + if( rcauth==SQLITE_OK + && pWhere==0 + && !bComplex + && !IsVirtual(pTab) +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + && db->xPreUpdateCallback==0 +#endif + ){ + assert( !isView ); + sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt ? memCnt : -1, + pTab->zName, P4_STATIC); + } + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + assert( pIdx->pSchema==pTab->pSchema ); + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ + sqlite3VdbeAddOp3(v, OP_Clear, pIdx->tnum, iDb, memCnt ? memCnt : -1); + }else{ + sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb); + } + } + }else +#endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */ + { + u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK; + if( sNC.ncFlags & NC_Subquery ) bComplex = 1; + wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW); + if( HasRowid(pTab) ){ + /* For a rowid table, initialize the RowSet to an empty set */ + pPk = 0; + assert( nPk==1 ); + iRowSet = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet); + }else{ + /* For a WITHOUT ROWID table, create an ephemeral table used to + ** hold all primary keys for rows to be deleted. */ + pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + nPk = pPk->nKeyCol; + iPk = pParse->nMem+1; + pParse->nMem += nPk; + iEphCur = pParse->nTab++; + addrEphOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEphCur, nPk); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + } + + /* Construct a query to find the rowid or primary key for every row + ** to be deleted, based on the WHERE clause. Set variable eOnePass + ** to indicate the strategy used to implement this delete: + ** + ** ONEPASS_OFF: Two-pass approach - use a FIFO for rowids/PK values. + ** ONEPASS_SINGLE: One-pass approach - at most one row deleted. + ** ONEPASS_MULTI: One-pass approach - any number of rows may be deleted. + */ + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,0,wcf,iTabCur+1); + if( pWInfo==0 ) goto delete_from_cleanup; + eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); + assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI ); + assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF + || OptimizationDisabled(db, SQLITE_OnePass) ); + if( eOnePass!=ONEPASS_SINGLE ) sqlite3MultiWrite(pParse); + if( sqlite3WhereUsesDeferredSeek(pWInfo) ){ + sqlite3VdbeAddOp1(v, OP_FinishSeek, iTabCur); + } + + /* Keep track of the number of rows to be deleted */ + if( memCnt ){ + sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1); + } + + /* Extract the rowid or primary key for the current row */ + if( pPk ){ + for(i=0; i<nPk; i++){ + assert( pPk->aiColumn[i]>=0 ); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, + pPk->aiColumn[i], iPk+i); + } + iKey = iPk; + }else{ + iKey = ++pParse->nMem; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, -1, iKey); + } + + if( eOnePass!=ONEPASS_OFF ){ + /* For ONEPASS, no need to store the rowid/primary-key. There is only + ** one, so just keep it in its register(s) and fall through to the + ** delete code. */ + nKey = nPk; /* OP_Found will use an unpacked key */ + aToOpen = sqlite3DbMallocRawNN(db, nIdx+2); + if( aToOpen==0 ){ + sqlite3WhereEnd(pWInfo); + goto delete_from_cleanup; + } + memset(aToOpen, 1, nIdx+1); + aToOpen[nIdx+1] = 0; + if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iTabCur] = 0; + if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iTabCur] = 0; + if( addrEphOpen ) sqlite3VdbeChangeToNoop(v, addrEphOpen); + addrBypass = sqlite3VdbeMakeLabel(pParse); + }else{ + if( pPk ){ + /* Add the PK key for this row to the temporary table */ + iKey = ++pParse->nMem; + nKey = 0; /* Zero tells OP_Found to use a composite key */ + sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, + sqlite3IndexAffinityStr(pParse->db, pPk), nPk); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEphCur, iKey, iPk, nPk); + }else{ + /* Add the rowid of the row to be deleted to the RowSet */ + nKey = 1; /* OP_DeferredSeek always uses a single rowid */ + sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey); + } + sqlite3WhereEnd(pWInfo); + } + + /* Unless this is a view, open cursors for the table we are + ** deleting from and all its indices. If this is a view, then the + ** only effect this statement has is to fire the INSTEAD OF + ** triggers. + */ + if( !isView ){ + int iAddrOnce = 0; + if( eOnePass==ONEPASS_MULTI ){ + iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + } + testcase( IsVirtual(pTab) ); + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, OPFLAG_FORDELETE, + iTabCur, aToOpen, &iDataCur, &iIdxCur); + assert( pPk || IsVirtual(pTab) || iDataCur==iTabCur ); + assert( pPk || IsVirtual(pTab) || iIdxCur==iDataCur+1 ); + if( eOnePass==ONEPASS_MULTI ){ + sqlite3VdbeJumpHereOrPopInst(v, iAddrOnce); + } + } + + /* Set up a loop over the rowids/primary-keys that were found in the + ** where-clause loop above. + */ + if( eOnePass!=ONEPASS_OFF ){ + assert( nKey==nPk ); /* OP_Found will use an unpacked key */ + if( !IsVirtual(pTab) && aToOpen[iDataCur-iTabCur] ){ + assert( pPk!=0 || IsView(pTab) ); + sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey); + VdbeCoverage(v); + } + }else if( pPk ){ + addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur); VdbeCoverage(v); + if( IsVirtual(pTab) ){ + sqlite3VdbeAddOp3(v, OP_Column, iEphCur, 0, iKey); + }else{ + sqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey); + } + assert( nKey==0 ); /* OP_Found will use a composite key */ + }else{ + addrLoop = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, 0, iKey); + VdbeCoverage(v); + assert( nKey==1 ); + } + + /* Delete the row */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pTab) ){ + const char *pVTab = (const char *)sqlite3GetVTable(db, pTab); + sqlite3VtabMakeWritable(pParse, pTab); + assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE ); + sqlite3MayAbort(pParse); + if( eOnePass==ONEPASS_SINGLE ){ + sqlite3VdbeAddOp1(v, OP_Close, iTabCur); + if( sqlite3IsToplevel(pParse) ){ + pParse->isMultiWrite = 0; + } + } + sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iKey, pVTab, P4_VTAB); + sqlite3VdbeChangeP5(v, OE_Abort); + }else +#endif + { + int count = (pParse->nested==0); /* True to count changes */ + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + iKey, nKey, count, OE_Default, eOnePass, aiCurOnePass[1]); + } + + /* End of the loop over all rowids/primary-keys. */ + if( eOnePass!=ONEPASS_OFF ){ + sqlite3VdbeResolveLabel(v, addrBypass); + sqlite3WhereEnd(pWInfo); + }else if( pPk ){ + sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrLoop); + }else{ + sqlite3VdbeGoto(v, addrLoop); + sqlite3VdbeJumpHere(v, addrLoop); + } + } /* End non-truncate path */ + + /* Update the sqlite_sequence table by storing the content of the + ** maximum rowid counter values recorded while inserting into + ** autoincrement tables. + */ + if( pParse->nested==0 && pParse->pTriggerTab==0 ){ + sqlite3AutoincrementEnd(pParse); + } + + /* Return the number of rows that were deleted. If this routine is + ** generating code because of a call to sqlite3NestedParse(), do not + ** invoke the callback function. + */ + if( memCnt ){ + sqlite3CodeChangeCount(v, memCnt, "rows deleted"); + } + +delete_from_cleanup: + sqlite3AuthContextPop(&sContext); + sqlite3SrcListDelete(db, pTabList); + sqlite3ExprDelete(db, pWhere); +#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) + sqlite3ExprListDelete(db, pOrderBy); + sqlite3ExprDelete(db, pLimit); +#endif + if( aToOpen ) sqlite3DbNNFreeNN(db, aToOpen); + return; +} +/* Make sure "isView" and other macros defined above are undefined. Otherwise +** they may interfere with compilation of other functions in this file +** (or in another file, if this file becomes part of the amalgamation). */ +#ifdef isView + #undef isView +#endif +#ifdef pTrigger + #undef pTrigger +#endif + +/* +** This routine generates VDBE code that causes a single row of a +** single table to be deleted. Both the original table entry and +** all indices are removed. +** +** Preconditions: +** +** 1. iDataCur is an open cursor on the btree that is the canonical data +** store for the table. (This will be either the table itself, +** in the case of a rowid table, or the PRIMARY KEY index in the case +** of a WITHOUT ROWID table.) +** +** 2. Read/write cursors for all indices of pTab must be open as +** cursor number iIdxCur+i for the i-th index. +** +** 3. The primary key for the row to be deleted must be stored in a +** sequence of nPk memory cells starting at iPk. If nPk==0 that means +** that a search record formed from OP_MakeRecord is contained in the +** single memory location iPk. +** +** eMode: +** Parameter eMode may be passed either ONEPASS_OFF (0), ONEPASS_SINGLE, or +** ONEPASS_MULTI. If eMode is not ONEPASS_OFF, then the cursor +** iDataCur already points to the row to delete. If eMode is ONEPASS_OFF +** then this function must seek iDataCur to the entry identified by iPk +** and nPk before reading from it. +** +** If eMode is ONEPASS_MULTI, then this call is being made as part +** of a ONEPASS delete that affects multiple rows. In this case, if +** iIdxNoSeek is a valid cursor number (>=0) and is not the same as +** iDataCur, then its position should be preserved following the delete +** operation. Or, if iIdxNoSeek is not a valid cursor number, the +** position of iDataCur should be preserved instead. +** +** iIdxNoSeek: +** If iIdxNoSeek is a valid cursor number (>=0) not equal to iDataCur, +** then it identifies an index cursor (from within array of cursors +** starting at iIdxCur) that already points to the index entry to be deleted. +** Except, this optimization is disabled if there are BEFORE triggers since +** the trigger body might have moved the cursor. +*/ +SQLITE_PRIVATE void sqlite3GenerateRowDelete( + Parse *pParse, /* Parsing context */ + Table *pTab, /* Table containing the row to be deleted */ + Trigger *pTrigger, /* List of triggers to (potentially) fire */ + int iDataCur, /* Cursor from which column data is extracted */ + int iIdxCur, /* First index cursor */ + int iPk, /* First memory cell containing the PRIMARY KEY */ + i16 nPk, /* Number of PRIMARY KEY memory cells */ + u8 count, /* If non-zero, increment the row change counter */ + u8 onconf, /* Default ON CONFLICT policy for triggers */ + u8 eMode, /* ONEPASS_OFF, _SINGLE, or _MULTI. See above */ + int iIdxNoSeek /* Cursor number of cursor that does not need seeking */ +){ + Vdbe *v = pParse->pVdbe; /* Vdbe */ + int iOld = 0; /* First register in OLD.* array */ + int iLabel; /* Label resolved to end of generated code */ + u8 opSeek; /* Seek opcode */ + + /* Vdbe is guaranteed to have been allocated by this stage. */ + assert( v ); + VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)", + iDataCur, iIdxCur, iPk, (int)nPk)); + + /* Seek cursor iCur to the row to delete. If this row no longer exists + ** (this can happen if a trigger program has already deleted it), do + ** not attempt to delete it or fire any DELETE triggers. */ + iLabel = sqlite3VdbeMakeLabel(pParse); + opSeek = HasRowid(pTab) ? OP_NotExists : OP_NotFound; + if( eMode==ONEPASS_OFF ){ + sqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk); + VdbeCoverageIf(v, opSeek==OP_NotExists); + VdbeCoverageIf(v, opSeek==OP_NotFound); + } + + /* If there are any triggers to fire, allocate a range of registers to + ** use for the old.* references in the triggers. */ + if( sqlite3FkRequired(pParse, pTab, 0, 0) || pTrigger ){ + u32 mask; /* Mask of OLD.* columns in use */ + int iCol; /* Iterator used while populating OLD.* */ + int addrStart; /* Start of BEFORE trigger programs */ + + /* TODO: Could use temporary registers here. Also could attempt to + ** avoid copying the contents of the rowid register. */ + mask = sqlite3TriggerColmask( + pParse, pTrigger, 0, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onconf + ); + mask |= sqlite3FkOldmask(pParse, pTab); + iOld = pParse->nMem+1; + pParse->nMem += (1 + pTab->nCol); + + /* Populate the OLD.* pseudo-table register array. These values will be + ** used by any BEFORE and AFTER triggers that exist. */ + sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld); + for(iCol=0; iCol<pTab->nCol; iCol++){ + testcase( mask!=0xffffffff && iCol==31 ); + testcase( mask!=0xffffffff && iCol==32 ); + if( mask==0xffffffff || (iCol<=31 && (mask & MASKBIT32(iCol))!=0) ){ + int kk = sqlite3TableColumnToStorage(pTab, iCol); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, iCol, iOld+kk+1); + } + } + + /* Invoke BEFORE DELETE trigger programs. */ + addrStart = sqlite3VdbeCurrentAddr(v); + sqlite3CodeRowTrigger(pParse, pTrigger, + TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld, onconf, iLabel + ); + + /* If any BEFORE triggers were coded, then seek the cursor to the + ** row to be deleted again. It may be that the BEFORE triggers moved + ** the cursor or already deleted the row that the cursor was + ** pointing to. + ** + ** Also disable the iIdxNoSeek optimization since the BEFORE trigger + ** may have moved that cursor. + */ + if( addrStart<sqlite3VdbeCurrentAddr(v) ){ + sqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk); + VdbeCoverageIf(v, opSeek==OP_NotExists); + VdbeCoverageIf(v, opSeek==OP_NotFound); + testcase( iIdxNoSeek>=0 ); + iIdxNoSeek = -1; + } + + /* Do FK processing. This call checks that any FK constraints that + ** refer to this table (i.e. constraints attached to other tables) + ** are not violated by deleting this row. */ + sqlite3FkCheck(pParse, pTab, iOld, 0, 0, 0); + } + + /* Delete the index and table entries. Skip this step if pTab is really + ** a view (in which case the only effect of the DELETE statement is to + ** fire the INSTEAD OF triggers). + ** + ** If variable 'count' is non-zero, then this OP_Delete instruction should + ** invoke the update-hook. The pre-update-hook, on the other hand should + ** be invoked unless table pTab is a system table. The difference is that + ** the update-hook is not invoked for rows removed by REPLACE, but the + ** pre-update-hook is. + */ + if( !IsView(pTab) ){ + u8 p5 = 0; + sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); + sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); + if( pParse->nested==0 || 0==sqlite3_stricmp(pTab->zName, "sqlite_stat1") ){ + sqlite3VdbeAppendP4(v, (char*)pTab, P4_TABLE); + } + if( eMode!=ONEPASS_OFF ){ + sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE); + } + if( iIdxNoSeek>=0 && iIdxNoSeek!=iDataCur ){ + sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek); + } + if( eMode==ONEPASS_MULTI ) p5 |= OPFLAG_SAVEPOSITION; + sqlite3VdbeChangeP5(v, p5); + } + + /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to + ** handle rows (possibly in other tables) that refer via a foreign key + ** to the row just deleted. */ + sqlite3FkActions(pParse, pTab, 0, iOld, 0, 0); + + /* Invoke AFTER DELETE trigger programs. */ + if( pTrigger ){ + sqlite3CodeRowTrigger(pParse, pTrigger, + TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf, iLabel + ); + } + + /* Jump here if the row had already been deleted before any BEFORE + ** trigger programs were invoked. Or if a trigger program throws a + ** RAISE(IGNORE) exception. */ + sqlite3VdbeResolveLabel(v, iLabel); + VdbeModuleComment((v, "END: GenRowDel()")); +} + +/* +** This routine generates VDBE code that causes the deletion of all +** index entries associated with a single row of a single table, pTab +** +** Preconditions: +** +** 1. A read/write cursor "iDataCur" must be open on the canonical storage +** btree for the table pTab. (This will be either the table itself +** for rowid tables or to the primary key index for WITHOUT ROWID +** tables.) +** +** 2. Read/write cursors for all indices of pTab must be open as +** cursor number iIdxCur+i for the i-th index. (The pTab->pIndex +** index is the 0-th index.) +** +** 3. The "iDataCur" cursor must be already be positioned on the row +** that is to be deleted. +*/ +SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete( + Parse *pParse, /* Parsing and code generating context */ + Table *pTab, /* Table containing the row to be deleted */ + int iDataCur, /* Cursor of table holding data. */ + int iIdxCur, /* First index cursor */ + int *aRegIdx, /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */ + int iIdxNoSeek /* Do not delete from this cursor */ +){ + int i; /* Index loop counter */ + int r1 = -1; /* Register holding an index key */ + int iPartIdxLabel; /* Jump destination for skipping partial index entries */ + Index *pIdx; /* Current index */ + Index *pPrior = 0; /* Prior index */ + Vdbe *v; /* The prepared statement under construction */ + Index *pPk; /* PRIMARY KEY index, or NULL for rowid tables */ + + v = pParse->pVdbe; + pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); + for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + assert( iIdxCur+i!=iDataCur || pPk==pIdx ); + if( aRegIdx!=0 && aRegIdx[i]==0 ) continue; + if( pIdx==pPk ) continue; + if( iIdxCur+i==iIdxNoSeek ) continue; + VdbeModuleComment((v, "GenRowIdxDel for %s", pIdx->zName)); + r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 1, + &iPartIdxLabel, pPrior, r1); + sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1, + pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn); + sqlite3VdbeChangeP5(v, 1); /* Cause IdxDelete to error if no entry found */ + sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel); + pPrior = pIdx; + } +} + +/* +** Generate code that will assemble an index key and stores it in register +** regOut. The key with be for index pIdx which is an index on pTab. +** iCur is the index of a cursor open on the pTab table and pointing to +** the entry that needs indexing. If pTab is a WITHOUT ROWID table, then +** iCur must be the cursor of the PRIMARY KEY index. +** +** Return a register number which is the first in a block of +** registers that holds the elements of the index key. The +** block of registers has already been deallocated by the time +** this routine returns. +** +** If *piPartIdxLabel is not NULL, fill it in with a label and jump +** to that label if pIdx is a partial index that should be skipped. +** The label should be resolved using sqlite3ResolvePartIdxLabel(). +** A partial index should be skipped if its WHERE clause evaluates +** to false or null. If pIdx is not a partial index, *piPartIdxLabel +** will be set to zero which is an empty label that is ignored by +** sqlite3ResolvePartIdxLabel(). +** +** The pPrior and regPrior parameters are used to implement a cache to +** avoid unnecessary register loads. If pPrior is not NULL, then it is +** a pointer to a different index for which an index key has just been +** computed into register regPrior. If the current pIdx index is generating +** its key into the same sequence of registers and if pPrior and pIdx share +** a column in common, then the register corresponding to that column already +** holds the correct value and the loading of that register is skipped. +** This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK +** on a table with multiple indices, and especially with the ROWID or +** PRIMARY KEY columns of the index. +*/ +SQLITE_PRIVATE int sqlite3GenerateIndexKey( + Parse *pParse, /* Parsing context */ + Index *pIdx, /* The index for which to generate a key */ + int iDataCur, /* Cursor number from which to take column data */ + int regOut, /* Put the new key into this register if not 0 */ + int prefixOnly, /* Compute only a unique prefix of the key */ + int *piPartIdxLabel, /* OUT: Jump to this label to skip partial index */ + Index *pPrior, /* Previously generated index key */ + int regPrior /* Register holding previous generated key */ +){ + Vdbe *v = pParse->pVdbe; + int j; + int regBase; + int nCol; + + if( piPartIdxLabel ){ + if( pIdx->pPartIdxWhere ){ + *piPartIdxLabel = sqlite3VdbeMakeLabel(pParse); + pParse->iSelfTab = iDataCur + 1; + sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel, + SQLITE_JUMPIFNULL); + pParse->iSelfTab = 0; + pPrior = 0; /* Ticket a9efb42811fa41ee 2019-11-02; + ** pPartIdxWhere may have corrupted regPrior registers */ + }else{ + *piPartIdxLabel = 0; + } + } + nCol = (prefixOnly && pIdx->uniqNotNull) ? pIdx->nKeyCol : pIdx->nColumn; + regBase = sqlite3GetTempRange(pParse, nCol); + if( pPrior && (regBase!=regPrior || pPrior->pPartIdxWhere) ) pPrior = 0; + for(j=0; j<nCol; j++){ + if( pPrior + && pPrior->aiColumn[j]==pIdx->aiColumn[j] + && pPrior->aiColumn[j]!=XN_EXPR + ){ + /* This column was already computed by the previous index */ + continue; + } + sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j, regBase+j); + if( pIdx->aiColumn[j]>=0 ){ + /* If the column affinity is REAL but the number is an integer, then it + ** might be stored in the table as an integer (using a compact + ** representation) then converted to REAL by an OP_RealAffinity opcode. + ** But we are getting ready to store this value back into an index, where + ** it should be converted by to INTEGER again. So omit the + ** OP_RealAffinity opcode if it is present */ + sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity); + } + } + if( regOut ){ + sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut); + } + sqlite3ReleaseTempRange(pParse, regBase, nCol); + return regBase; +} + +/* +** If a prior call to sqlite3GenerateIndexKey() generated a jump-over label +** because it was a partial index, then this routine should be called to +** resolve that label. +*/ +SQLITE_PRIVATE void sqlite3ResolvePartIdxLabel(Parse *pParse, int iLabel){ + if( iLabel ){ + sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel); + } +} + +/************** End of delete.c **********************************************/ +/************** Begin file func.c ********************************************/ +/* +** 2002 February 23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C-language implementations for many of the SQL +** functions of SQLite. (Some function, and in particular the date and +** time functions, are implemented separately.) +*/ +/* #include "sqliteInt.h" */ +/* #include <stdlib.h> */ +/* #include <assert.h> */ +#ifndef SQLITE_OMIT_FLOATING_POINT +/* #include <math.h> */ +#endif +/* #include "vdbeInt.h" */ + +/* +** Return the collating function associated with a function. +*/ +static CollSeq *sqlite3GetFuncCollSeq(sqlite3_context *context){ + VdbeOp *pOp; + assert( context->pVdbe!=0 ); + pOp = &context->pVdbe->aOp[context->iOp-1]; + assert( pOp->opcode==OP_CollSeq ); + assert( pOp->p4type==P4_COLLSEQ ); + return pOp->p4.pColl; +} + +/* +** Indicate that the accumulator load should be skipped on this +** iteration of the aggregate loop. +*/ +static void sqlite3SkipAccumulatorLoad(sqlite3_context *context){ + assert( context->isError<=0 ); + context->isError = -1; + context->skipFlag = 1; +} + +/* +** Implementation of the non-aggregate min() and max() functions +*/ +static void minmaxFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int i; + int mask; /* 0 for min() or 0xffffffff for max() */ + int iBest; + CollSeq *pColl; + + assert( argc>1 ); + mask = sqlite3_user_data(context)==0 ? 0 : -1; + pColl = sqlite3GetFuncCollSeq(context); + assert( pColl ); + assert( mask==-1 || mask==0 ); + iBest = 0; + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + for(i=1; i<argc; i++){ + if( sqlite3_value_type(argv[i])==SQLITE_NULL ) return; + if( (sqlite3MemCompare(argv[iBest], argv[i], pColl)^mask)>=0 ){ + testcase( mask==0 ); + iBest = i; + } + } + sqlite3_result_value(context, argv[iBest]); +} + +/* +** Return the type of the argument. +*/ +static void typeofFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + static const char *azType[] = { "integer", "real", "text", "blob", "null" }; + int i = sqlite3_value_type(argv[0]) - 1; + UNUSED_PARAMETER(NotUsed); + assert( i>=0 && i<ArraySize(azType) ); + assert( SQLITE_INTEGER==1 ); + assert( SQLITE_FLOAT==2 ); + assert( SQLITE_TEXT==3 ); + assert( SQLITE_BLOB==4 ); + assert( SQLITE_NULL==5 ); + /* EVIDENCE-OF: R-01470-60482 The sqlite3_value_type(V) interface returns + ** the datatype code for the initial datatype of the sqlite3_value object + ** V. The returned value is one of SQLITE_INTEGER, SQLITE_FLOAT, + ** SQLITE_TEXT, SQLITE_BLOB, or SQLITE_NULL. */ + sqlite3_result_text(context, azType[i], -1, SQLITE_STATIC); +} + +/* subtype(X) +** +** Return the subtype of X +*/ +static void subtypeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + sqlite3_result_int(context, sqlite3_value_subtype(argv[0])); +} + +/* +** Implementation of the length() function +*/ +static void lengthFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + UNUSED_PARAMETER(argc); + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_BLOB: + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + sqlite3_result_int(context, sqlite3_value_bytes(argv[0])); + break; + } + case SQLITE_TEXT: { + const unsigned char *z = sqlite3_value_text(argv[0]); + const unsigned char *z0; + unsigned char c; + if( z==0 ) return; + z0 = z; + while( (c = *z)!=0 ){ + z++; + if( c>=0xc0 ){ + while( (*z & 0xc0)==0x80 ){ z++; z0++; } + } + } + sqlite3_result_int(context, (int)(z-z0)); + break; + } + default: { + sqlite3_result_null(context); + break; + } + } +} + +/* +** Implementation of the octet_length() function +*/ +static void bytelengthFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + UNUSED_PARAMETER(argc); + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_BLOB: { + sqlite3_result_int(context, sqlite3_value_bytes(argv[0])); + break; + } + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + i64 m = sqlite3_context_db_handle(context)->enc<=SQLITE_UTF8 ? 1 : 2; + sqlite3_result_int64(context, sqlite3_value_bytes(argv[0])*m); + break; + } + case SQLITE_TEXT: { + if( sqlite3_value_encoding(argv[0])<=SQLITE_UTF8 ){ + sqlite3_result_int(context, sqlite3_value_bytes(argv[0])); + }else{ + sqlite3_result_int(context, sqlite3_value_bytes16(argv[0])); + } + break; + } + default: { + sqlite3_result_null(context); + break; + } + } +} + +/* +** Implementation of the abs() function. +** +** IMP: R-23979-26855 The abs(X) function returns the absolute value of +** the numeric argument X. +*/ +static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + assert( argc==1 ); + UNUSED_PARAMETER(argc); + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_INTEGER: { + i64 iVal = sqlite3_value_int64(argv[0]); + if( iVal<0 ){ + if( iVal==SMALLEST_INT64 ){ + /* IMP: R-31676-45509 If X is the integer -9223372036854775808 + ** then abs(X) throws an integer overflow error since there is no + ** equivalent positive 64-bit two complement value. */ + sqlite3_result_error(context, "integer overflow", -1); + return; + } + iVal = -iVal; + } + sqlite3_result_int64(context, iVal); + break; + } + case SQLITE_NULL: { + /* IMP: R-37434-19929 Abs(X) returns NULL if X is NULL. */ + sqlite3_result_null(context); + break; + } + default: { + /* Because sqlite3_value_double() returns 0.0 if the argument is not + ** something that can be converted into a number, we have: + ** IMP: R-01992-00519 Abs(X) returns 0.0 if X is a string or blob + ** that cannot be converted to a numeric value. + */ + double rVal = sqlite3_value_double(argv[0]); + if( rVal<0 ) rVal = -rVal; + sqlite3_result_double(context, rVal); + break; + } + } +} + +/* +** Implementation of the instr() function. +** +** instr(haystack,needle) finds the first occurrence of needle +** in haystack and returns the number of previous characters plus 1, +** or 0 if needle does not occur within haystack. +** +** If both haystack and needle are BLOBs, then the result is one more than +** the number of bytes in haystack prior to the first occurrence of needle, +** or 0 if needle never occurs in haystack. +*/ +static void instrFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *zHaystack; + const unsigned char *zNeedle; + int nHaystack; + int nNeedle; + int typeHaystack, typeNeedle; + int N = 1; + int isText; + unsigned char firstChar; + sqlite3_value *pC1 = 0; + sqlite3_value *pC2 = 0; + + UNUSED_PARAMETER(argc); + typeHaystack = sqlite3_value_type(argv[0]); + typeNeedle = sqlite3_value_type(argv[1]); + if( typeHaystack==SQLITE_NULL || typeNeedle==SQLITE_NULL ) return; + nHaystack = sqlite3_value_bytes(argv[0]); + nNeedle = sqlite3_value_bytes(argv[1]); + if( nNeedle>0 ){ + if( typeHaystack==SQLITE_BLOB && typeNeedle==SQLITE_BLOB ){ + zHaystack = sqlite3_value_blob(argv[0]); + zNeedle = sqlite3_value_blob(argv[1]); + isText = 0; + }else if( typeHaystack!=SQLITE_BLOB && typeNeedle!=SQLITE_BLOB ){ + zHaystack = sqlite3_value_text(argv[0]); + zNeedle = sqlite3_value_text(argv[1]); + isText = 1; + }else{ + pC1 = sqlite3_value_dup(argv[0]); + zHaystack = sqlite3_value_text(pC1); + if( zHaystack==0 ) goto endInstrOOM; + nHaystack = sqlite3_value_bytes(pC1); + pC2 = sqlite3_value_dup(argv[1]); + zNeedle = sqlite3_value_text(pC2); + if( zNeedle==0 ) goto endInstrOOM; + nNeedle = sqlite3_value_bytes(pC2); + isText = 1; + } + if( zNeedle==0 || (nHaystack && zHaystack==0) ) goto endInstrOOM; + firstChar = zNeedle[0]; + while( nNeedle<=nHaystack + && (zHaystack[0]!=firstChar || memcmp(zHaystack, zNeedle, nNeedle)!=0) + ){ + N++; + do{ + nHaystack--; + zHaystack++; + }while( isText && (zHaystack[0]&0xc0)==0x80 ); + } + if( nNeedle>nHaystack ) N = 0; + } + sqlite3_result_int(context, N); +endInstr: + sqlite3_value_free(pC1); + sqlite3_value_free(pC2); + return; +endInstrOOM: + sqlite3_result_error_nomem(context); + goto endInstr; +} + +/* +** Implementation of the printf() (a.k.a. format()) SQL function. +*/ +static void printfFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + PrintfArguments x; + StrAccum str; + const char *zFormat; + int n; + sqlite3 *db = sqlite3_context_db_handle(context); + + if( argc>=1 && (zFormat = (const char*)sqlite3_value_text(argv[0]))!=0 ){ + x.nArg = argc-1; + x.nUsed = 0; + x.apArg = argv+1; + sqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); + str.printfFlags = SQLITE_PRINTF_SQLFUNC; + sqlite3_str_appendf(&str, zFormat, &x); + n = str.nChar; + sqlite3_result_text(context, sqlite3StrAccumFinish(&str), n, + SQLITE_DYNAMIC); + } +} + +/* +** Implementation of the substr() function. +** +** substr(x,p1,p2) returns p2 characters of x[] beginning with p1. +** p1 is 1-indexed. So substr(x,1,1) returns the first character +** of x. If x is text, then we actually count UTF-8 characters. +** If x is a blob, then we count bytes. +** +** If p1 is negative, then we begin abs(p1) from the end of x[]. +** +** If p2 is negative, return the p2 characters preceding p1. +*/ +static void substrFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *z; + const unsigned char *z2; + int len; + int p0type; + i64 p1, p2; + int negP2 = 0; + + assert( argc==3 || argc==2 ); + if( sqlite3_value_type(argv[1])==SQLITE_NULL + || (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL) + ){ + return; + } + p0type = sqlite3_value_type(argv[0]); + p1 = sqlite3_value_int(argv[1]); + if( p0type==SQLITE_BLOB ){ + len = sqlite3_value_bytes(argv[0]); + z = sqlite3_value_blob(argv[0]); + if( z==0 ) return; + assert( len==sqlite3_value_bytes(argv[0]) ); + }else{ + z = sqlite3_value_text(argv[0]); + if( z==0 ) return; + len = 0; + if( p1<0 ){ + for(z2=z; *z2; len++){ + SQLITE_SKIP_UTF8(z2); + } + } + } +#ifdef SQLITE_SUBSTR_COMPATIBILITY + /* If SUBSTR_COMPATIBILITY is defined then substr(X,0,N) work the same as + ** as substr(X,1,N) - it returns the first N characters of X. This + ** is essentially a back-out of the bug-fix in check-in [5fc125d362df4b8] + ** from 2009-02-02 for compatibility of applications that exploited the + ** old buggy behavior. */ + if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */ +#endif + if( argc==3 ){ + p2 = sqlite3_value_int(argv[2]); + if( p2<0 ){ + p2 = -p2; + negP2 = 1; + } + }else{ + p2 = sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH]; + } + if( p1<0 ){ + p1 += len; + if( p1<0 ){ + p2 += p1; + if( p2<0 ) p2 = 0; + p1 = 0; + } + }else if( p1>0 ){ + p1--; + }else if( p2>0 ){ + p2--; + } + if( negP2 ){ + p1 -= p2; + if( p1<0 ){ + p2 += p1; + p1 = 0; + } + } + assert( p1>=0 && p2>=0 ); + if( p0type!=SQLITE_BLOB ){ + while( *z && p1 ){ + SQLITE_SKIP_UTF8(z); + p1--; + } + for(z2=z; *z2 && p2; p2--){ + SQLITE_SKIP_UTF8(z2); + } + sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT, + SQLITE_UTF8); + }else{ + if( p1+p2>len ){ + p2 = len-p1; + if( p2<0 ) p2 = 0; + } + sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT); + } +} + +/* +** Implementation of the round() function +*/ +#ifndef SQLITE_OMIT_FLOATING_POINT +static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + int n = 0; + double r; + char *zBuf; + assert( argc==1 || argc==2 ); + if( argc==2 ){ + if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return; + n = sqlite3_value_int(argv[1]); + if( n>30 ) n = 30; + if( n<0 ) n = 0; + } + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + r = sqlite3_value_double(argv[0]); + /* If Y==0 and X will fit in a 64-bit int, + ** handle the rounding directly, + ** otherwise use printf. + */ + if( r<-4503599627370496.0 || r>+4503599627370496.0 ){ + /* The value has no fractional part so there is nothing to round */ + }else if( n==0 ){ + r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5))); + }else{ + zBuf = sqlite3_mprintf("%!.*f",n,r); + if( zBuf==0 ){ + sqlite3_result_error_nomem(context); + return; + } + sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8); + sqlite3_free(zBuf); + } + sqlite3_result_double(context, r); +} +#endif + +/* +** Allocate nByte bytes of space using sqlite3Malloc(). If the +** allocation fails, call sqlite3_result_error_nomem() to notify +** the database handle that malloc() has failed and return NULL. +** If nByte is larger than the maximum string or blob length, then +** raise an SQLITE_TOOBIG exception and return NULL. +*/ +static void *contextMalloc(sqlite3_context *context, i64 nByte){ + char *z; + sqlite3 *db = sqlite3_context_db_handle(context); + assert( nByte>0 ); + testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH] ); + testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH]+1 ); + if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ + sqlite3_result_error_toobig(context); + z = 0; + }else{ + z = sqlite3Malloc(nByte); + if( !z ){ + sqlite3_result_error_nomem(context); + } + } + return z; +} + +/* +** Implementation of the upper() and lower() SQL functions. +*/ +static void upperFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + char *z1; + const char *z2; + int i, n; + UNUSED_PARAMETER(argc); + z2 = (char*)sqlite3_value_text(argv[0]); + n = sqlite3_value_bytes(argv[0]); + /* Verify that the call to _bytes() does not invalidate the _text() pointer */ + assert( z2==(char*)sqlite3_value_text(argv[0]) ); + if( z2 ){ + z1 = contextMalloc(context, ((i64)n)+1); + if( z1 ){ + for(i=0; i<n; i++){ + z1[i] = (char)sqlite3Toupper(z2[i]); + } + sqlite3_result_text(context, z1, n, sqlite3_free); + } + } +} +static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + char *z1; + const char *z2; + int i, n; + UNUSED_PARAMETER(argc); + z2 = (char*)sqlite3_value_text(argv[0]); + n = sqlite3_value_bytes(argv[0]); + /* Verify that the call to _bytes() does not invalidate the _text() pointer */ + assert( z2==(char*)sqlite3_value_text(argv[0]) ); + if( z2 ){ + z1 = contextMalloc(context, ((i64)n)+1); + if( z1 ){ + for(i=0; i<n; i++){ + z1[i] = sqlite3Tolower(z2[i]); + } + sqlite3_result_text(context, z1, n, sqlite3_free); + } + } +} + +/* +** Some functions like COALESCE() and IFNULL() and UNLIKELY() are implemented +** as VDBE code so that unused argument values do not have to be computed. +** However, we still need some kind of function implementation for this +** routines in the function table. The noopFunc macro provides this. +** noopFunc will never be called so it doesn't matter what the implementation +** is. We might as well use the "version()" function as a substitute. +*/ +#define noopFunc versionFunc /* Substitute function - never called */ + +/* +** Implementation of random(). Return a random integer. +*/ +static void randomFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **NotUsed2 +){ + sqlite_int64 r; + UNUSED_PARAMETER2(NotUsed, NotUsed2); + sqlite3_randomness(sizeof(r), &r); + if( r<0 ){ + /* We need to prevent a random number of 0x8000000000000000 + ** (or -9223372036854775808) since when you do abs() of that + ** number of you get the same value back again. To do this + ** in a way that is testable, mask the sign bit off of negative + ** values, resulting in a positive value. Then take the + ** 2s complement of that positive value. The end result can + ** therefore be no less than -9223372036854775807. + */ + r = -(r & LARGEST_INT64); + } + sqlite3_result_int64(context, r); +} + +/* +** Implementation of randomblob(N). Return a random blob +** that is N bytes long. +*/ +static void randomBlob( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3_int64 n; + unsigned char *p; + assert( argc==1 ); + UNUSED_PARAMETER(argc); + n = sqlite3_value_int64(argv[0]); + if( n<1 ){ + n = 1; + } + p = contextMalloc(context, n); + if( p ){ + sqlite3_randomness(n, p); + sqlite3_result_blob(context, (char*)p, n, sqlite3_free); + } +} + +/* +** Implementation of the last_insert_rowid() SQL function. The return +** value is the same as the sqlite3_last_insert_rowid() API function. +*/ +static void last_insert_rowid( + sqlite3_context *context, + int NotUsed, + sqlite3_value **NotUsed2 +){ + sqlite3 *db = sqlite3_context_db_handle(context); + UNUSED_PARAMETER2(NotUsed, NotUsed2); + /* IMP: R-51513-12026 The last_insert_rowid() SQL function is a + ** wrapper around the sqlite3_last_insert_rowid() C/C++ interface + ** function. */ + sqlite3_result_int64(context, sqlite3_last_insert_rowid(db)); +} + +/* +** Implementation of the changes() SQL function. +** +** IMP: R-32760-32347 The changes() SQL function is a wrapper +** around the sqlite3_changes64() C/C++ function and hence follows the +** same rules for counting changes. +*/ +static void changes( + sqlite3_context *context, + int NotUsed, + sqlite3_value **NotUsed2 +){ + sqlite3 *db = sqlite3_context_db_handle(context); + UNUSED_PARAMETER2(NotUsed, NotUsed2); + sqlite3_result_int64(context, sqlite3_changes64(db)); +} + +/* +** Implementation of the total_changes() SQL function. The return value is +** the same as the sqlite3_total_changes64() API function. +*/ +static void total_changes( + sqlite3_context *context, + int NotUsed, + sqlite3_value **NotUsed2 +){ + sqlite3 *db = sqlite3_context_db_handle(context); + UNUSED_PARAMETER2(NotUsed, NotUsed2); + /* IMP: R-11217-42568 This function is a wrapper around the + ** sqlite3_total_changes64() C/C++ interface. */ + sqlite3_result_int64(context, sqlite3_total_changes64(db)); +} + +/* +** A structure defining how to do GLOB-style comparisons. +*/ +struct compareInfo { + u8 matchAll; /* "*" or "%" */ + u8 matchOne; /* "?" or "_" */ + u8 matchSet; /* "[" or 0 */ + u8 noCase; /* true to ignore case differences */ +}; + +/* +** For LIKE and GLOB matching on EBCDIC machines, assume that every +** character is exactly one byte in size. Also, provide the Utf8Read() +** macro for fast reading of the next character in the common case where +** the next character is ASCII. +*/ +#if defined(SQLITE_EBCDIC) +# define sqlite3Utf8Read(A) (*((*A)++)) +# define Utf8Read(A) (*(A++)) +#else +# define Utf8Read(A) (A[0]<0x80?*(A++):sqlite3Utf8Read(&A)) +#endif + +static const struct compareInfo globInfo = { '*', '?', '[', 0 }; +/* The correct SQL-92 behavior is for the LIKE operator to ignore +** case. Thus 'a' LIKE 'A' would be true. */ +static const struct compareInfo likeInfoNorm = { '%', '_', 0, 1 }; +/* If SQLITE_CASE_SENSITIVE_LIKE is defined, then the LIKE operator +** is case sensitive causing 'a' LIKE 'A' to be false */ +static const struct compareInfo likeInfoAlt = { '%', '_', 0, 0 }; + +/* +** Possible error returns from patternMatch() +*/ +#define SQLITE_MATCH 0 +#define SQLITE_NOMATCH 1 +#define SQLITE_NOWILDCARDMATCH 2 + +/* +** Compare two UTF-8 strings for equality where the first string is +** a GLOB or LIKE expression. Return values: +** +** SQLITE_MATCH: Match +** SQLITE_NOMATCH: No match +** SQLITE_NOWILDCARDMATCH: No match in spite of having * or % wildcards. +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** With the [...] and [^...] matching, a ']' character can be included +** in the list by making it the first character after '[' or '^'. A +** range of characters can be specified using '-'. Example: +** "[a-z]" matches any single lower-case letter. To match a '-', make +** it the last character in the list. +** +** Like matching rules: +** +** '%' Matches any sequence of zero or more characters +** +*** '_' Matches any one character +** +** Ec Where E is the "esc" character and c is any other +** character, including '%', '_', and esc, match exactly c. +** +** The comments within this routine usually assume glob matching. +** +** This routine is usually quick, but can be N**2 in the worst case. +*/ +static int patternCompare( + const u8 *zPattern, /* The glob pattern */ + const u8 *zString, /* The string to compare against the glob */ + const struct compareInfo *pInfo, /* Information about how to do the compare */ + u32 matchOther /* The escape char (LIKE) or '[' (GLOB) */ +){ + u32 c, c2; /* Next pattern and input string chars */ + u32 matchOne = pInfo->matchOne; /* "?" or "_" */ + u32 matchAll = pInfo->matchAll; /* "*" or "%" */ + u8 noCase = pInfo->noCase; /* True if uppercase==lowercase */ + const u8 *zEscaped = 0; /* One past the last escaped input char */ + + while( (c = Utf8Read(zPattern))!=0 ){ + if( c==matchAll ){ /* Match "*" */ + /* Skip over multiple "*" characters in the pattern. If there + ** are also "?" characters, skip those as well, but consume a + ** single character of the input string for each "?" skipped */ + while( (c=Utf8Read(zPattern)) == matchAll + || (c == matchOne && matchOne!=0) ){ + if( c==matchOne && sqlite3Utf8Read(&zString)==0 ){ + return SQLITE_NOWILDCARDMATCH; + } + } + if( c==0 ){ + return SQLITE_MATCH; /* "*" at the end of the pattern matches */ + }else if( c==matchOther ){ + if( pInfo->matchSet==0 ){ + c = sqlite3Utf8Read(&zPattern); + if( c==0 ) return SQLITE_NOWILDCARDMATCH; + }else{ + /* "[...]" immediately follows the "*". We have to do a slow + ** recursive search in this case, but it is an unusual case. */ + assert( matchOther<0x80 ); /* '[' is a single-byte character */ + while( *zString ){ + int bMatch = patternCompare(&zPattern[-1],zString,pInfo,matchOther); + if( bMatch!=SQLITE_NOMATCH ) return bMatch; + SQLITE_SKIP_UTF8(zString); + } + return SQLITE_NOWILDCARDMATCH; + } + } + + /* At this point variable c contains the first character of the + ** pattern string past the "*". Search in the input string for the + ** first matching character and recursively continue the match from + ** that point. + ** + ** For a case-insensitive search, set variable cx to be the same as + ** c but in the other case and search the input string for either + ** c or cx. + */ + if( c<0x80 ){ + char zStop[3]; + int bMatch; + if( noCase ){ + zStop[0] = sqlite3Toupper(c); + zStop[1] = sqlite3Tolower(c); + zStop[2] = 0; + }else{ + zStop[0] = c; + zStop[1] = 0; + } + while(1){ + zString += strcspn((const char*)zString, zStop); + if( zString[0]==0 ) break; + zString++; + bMatch = patternCompare(zPattern,zString,pInfo,matchOther); + if( bMatch!=SQLITE_NOMATCH ) return bMatch; + } + }else{ + int bMatch; + while( (c2 = Utf8Read(zString))!=0 ){ + if( c2!=c ) continue; + bMatch = patternCompare(zPattern,zString,pInfo,matchOther); + if( bMatch!=SQLITE_NOMATCH ) return bMatch; + } + } + return SQLITE_NOWILDCARDMATCH; + } + if( c==matchOther ){ + if( pInfo->matchSet==0 ){ + c = sqlite3Utf8Read(&zPattern); + if( c==0 ) return SQLITE_NOMATCH; + zEscaped = zPattern; + }else{ + u32 prior_c = 0; + int seen = 0; + int invert = 0; + c = sqlite3Utf8Read(&zString); + if( c==0 ) return SQLITE_NOMATCH; + c2 = sqlite3Utf8Read(&zPattern); + if( c2=='^' ){ + invert = 1; + c2 = sqlite3Utf8Read(&zPattern); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = sqlite3Utf8Read(&zPattern); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){ + c2 = sqlite3Utf8Read(&zPattern); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = sqlite3Utf8Read(&zPattern); + } + if( c2==0 || (seen ^ invert)==0 ){ + return SQLITE_NOMATCH; + } + continue; + } + } + c2 = Utf8Read(zString); + if( c==c2 ) continue; + if( noCase && sqlite3Tolower(c)==sqlite3Tolower(c2) && c<0x80 && c2<0x80 ){ + continue; + } + if( c==matchOne && zPattern!=zEscaped && c2!=0 ) continue; + return SQLITE_NOMATCH; + } + return *zString==0 ? SQLITE_MATCH : SQLITE_NOMATCH; +} + +/* +** The sqlite3_strglob() interface. Return 0 on a match (like strcmp()) and +** non-zero if there is no match. +*/ +SQLITE_API int sqlite3_strglob(const char *zGlobPattern, const char *zString){ + if( zString==0 ){ + return zGlobPattern!=0; + }else if( zGlobPattern==0 ){ + return 1; + }else { + return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '['); + } +} + +/* +** The sqlite3_strlike() interface. Return 0 on a match and non-zero for +** a miss - like strcmp(). +*/ +SQLITE_API int sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc){ + if( zStr==0 ){ + return zPattern!=0; + }else if( zPattern==0 ){ + return 1; + }else{ + return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc); + } +} + +/* +** Count the number of times that the LIKE operator (or GLOB which is +** just a variation of LIKE) gets called. This is used for testing +** only. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_like_count = 0; +#endif + + +/* +** Implementation of the like() SQL function. This function implements +** the built-in LIKE operator. The first argument to the function is the +** pattern and the second argument is the string. So, the SQL statements: +** +** A LIKE B +** +** is implemented as like(B,A). +** +** This same function (with a different compareInfo structure) computes +** the GLOB operator. +*/ +static void likeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *zA, *zB; + u32 escape; + int nPat; + sqlite3 *db = sqlite3_context_db_handle(context); + struct compareInfo *pInfo = sqlite3_user_data(context); + struct compareInfo backupInfo; + +#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS + if( sqlite3_value_type(argv[0])==SQLITE_BLOB + || sqlite3_value_type(argv[1])==SQLITE_BLOB + ){ +#ifdef SQLITE_TEST + sqlite3_like_count++; +#endif + sqlite3_result_int(context, 0); + return; + } +#endif + + /* Limit the length of the LIKE or GLOB pattern to avoid problems + ** of deep recursion and N*N behavior in patternCompare(). + */ + nPat = sqlite3_value_bytes(argv[0]); + testcase( nPat==db->aLimit[SQLITE_LIMIT_LIKE_PATTERN_LENGTH] ); + testcase( nPat==db->aLimit[SQLITE_LIMIT_LIKE_PATTERN_LENGTH]+1 ); + if( nPat > db->aLimit[SQLITE_LIMIT_LIKE_PATTERN_LENGTH] ){ + sqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1); + return; + } + if( argc==3 ){ + /* The escape character string must consist of a single UTF-8 character. + ** Otherwise, return an error. + */ + const unsigned char *zEsc = sqlite3_value_text(argv[2]); + if( zEsc==0 ) return; + if( sqlite3Utf8CharLen((char*)zEsc, -1)!=1 ){ + sqlite3_result_error(context, + "ESCAPE expression must be a single character", -1); + return; + } + escape = sqlite3Utf8Read(&zEsc); + if( escape==pInfo->matchAll || escape==pInfo->matchOne ){ + memcpy(&backupInfo, pInfo, sizeof(backupInfo)); + pInfo = &backupInfo; + if( escape==pInfo->matchAll ) pInfo->matchAll = 0; + if( escape==pInfo->matchOne ) pInfo->matchOne = 0; + } + }else{ + escape = pInfo->matchSet; + } + zB = sqlite3_value_text(argv[0]); + zA = sqlite3_value_text(argv[1]); + if( zA && zB ){ +#ifdef SQLITE_TEST + sqlite3_like_count++; +#endif + sqlite3_result_int(context, + patternCompare(zB, zA, pInfo, escape)==SQLITE_MATCH); + } +} + +/* +** Implementation of the NULLIF(x,y) function. The result is the first +** argument if the arguments are different. The result is NULL if the +** arguments are equal to each other. +*/ +static void nullifFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + CollSeq *pColl = sqlite3GetFuncCollSeq(context); + UNUSED_PARAMETER(NotUsed); + if( sqlite3MemCompare(argv[0], argv[1], pColl)!=0 ){ + sqlite3_result_value(context, argv[0]); + } +} + +/* +** Implementation of the sqlite_version() function. The result is the version +** of the SQLite library that is running. +*/ +static void versionFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **NotUsed2 +){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + /* IMP: R-48699-48617 This function is an SQL wrapper around the + ** sqlite3_libversion() C-interface. */ + sqlite3_result_text(context, sqlite3_libversion(), -1, SQLITE_STATIC); +} + +/* +** Implementation of the sqlite_source_id() function. The result is a string +** that identifies the particular version of the source code used to build +** SQLite. +*/ +static void sourceidFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **NotUsed2 +){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + /* IMP: R-24470-31136 This function is an SQL wrapper around the + ** sqlite3_sourceid() C interface. */ + sqlite3_result_text(context, sqlite3_sourceid(), -1, SQLITE_STATIC); +} + +/* +** Implementation of the sqlite_log() function. This is a wrapper around +** sqlite3_log(). The return value is NULL. The function exists purely for +** its side-effects. +*/ +static void errlogFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(context); + sqlite3_log(sqlite3_value_int(argv[0]), "%s", sqlite3_value_text(argv[1])); +} + +/* +** Implementation of the sqlite_compileoption_used() function. +** The result is an integer that identifies if the compiler option +** was used to build SQLite. +*/ +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS +static void compileoptionusedFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zOptName; + assert( argc==1 ); + UNUSED_PARAMETER(argc); + /* IMP: R-39564-36305 The sqlite_compileoption_used() SQL + ** function is a wrapper around the sqlite3_compileoption_used() C/C++ + ** function. + */ + if( (zOptName = (const char*)sqlite3_value_text(argv[0]))!=0 ){ + sqlite3_result_int(context, sqlite3_compileoption_used(zOptName)); + } +} +#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ + +/* +** Implementation of the sqlite_compileoption_get() function. +** The result is a string that identifies the compiler options +** used to build SQLite. +*/ +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS +static void compileoptiongetFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int n; + assert( argc==1 ); + UNUSED_PARAMETER(argc); + /* IMP: R-04922-24076 The sqlite_compileoption_get() SQL function + ** is a wrapper around the sqlite3_compileoption_get() C/C++ function. + */ + n = sqlite3_value_int(argv[0]); + sqlite3_result_text(context, sqlite3_compileoption_get(n), -1, SQLITE_STATIC); +} +#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ + +/* Array for converting from half-bytes (nybbles) into ASCII hex +** digits. */ +static const char hexdigits[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +/* +** Append to pStr text that is the SQL literal representation of the +** value contained in pValue. +*/ +SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue){ + /* As currently implemented, the string must be initially empty. + ** we might relax this requirement in the future, but that will + ** require enhancements to the implementation. */ + assert( pStr!=0 && pStr->nChar==0 ); + + switch( sqlite3_value_type(pValue) ){ + case SQLITE_FLOAT: { + double r1, r2; + const char *zVal; + r1 = sqlite3_value_double(pValue); + sqlite3_str_appendf(pStr, "%!.15g", r1); + zVal = sqlite3_str_value(pStr); + if( zVal ){ + sqlite3AtoF(zVal, &r2, pStr->nChar, SQLITE_UTF8); + if( r1!=r2 ){ + sqlite3_str_reset(pStr); + sqlite3_str_appendf(pStr, "%!.20e", r1); + } + } + break; + } + case SQLITE_INTEGER: { + sqlite3_str_appendf(pStr, "%lld", sqlite3_value_int64(pValue)); + break; + } + case SQLITE_BLOB: { + char const *zBlob = sqlite3_value_blob(pValue); + i64 nBlob = sqlite3_value_bytes(pValue); + assert( zBlob==sqlite3_value_blob(pValue) ); /* No encoding change */ + sqlite3StrAccumEnlarge(pStr, nBlob*2 + 4); + if( pStr->accError==0 ){ + char *zText = pStr->zText; + int i; + for(i=0; i<nBlob; i++){ + zText[(i*2)+2] = hexdigits[(zBlob[i]>>4)&0x0F]; + zText[(i*2)+3] = hexdigits[(zBlob[i])&0x0F]; + } + zText[(nBlob*2)+2] = '\''; + zText[(nBlob*2)+3] = '\0'; + zText[0] = 'X'; + zText[1] = '\''; + pStr->nChar = nBlob*2 + 3; + } + break; + } + case SQLITE_TEXT: { + const unsigned char *zArg = sqlite3_value_text(pValue); + sqlite3_str_appendf(pStr, "%Q", zArg); + break; + } + default: { + assert( sqlite3_value_type(pValue)==SQLITE_NULL ); + sqlite3_str_append(pStr, "NULL", 4); + break; + } + } +} + +/* +** Implementation of the QUOTE() function. +** +** The quote(X) function returns the text of an SQL literal which is the +** value of its argument suitable for inclusion into an SQL statement. +** Strings are surrounded by single-quotes with escapes on interior quotes +** as needed. BLOBs are encoded as hexadecimal literals. Strings with +** embedded NUL characters cannot be represented as string literals in SQL +** and hence the returned string literal is truncated prior to the first NUL. +*/ +static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + sqlite3_str str; + sqlite3 *db = sqlite3_context_db_handle(context); + assert( argc==1 ); + UNUSED_PARAMETER(argc); + sqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); + sqlite3QuoteValue(&str,argv[0]); + sqlite3_result_text(context, sqlite3StrAccumFinish(&str), str.nChar, + SQLITE_DYNAMIC); + if( str.accError!=SQLITE_OK ){ + sqlite3_result_null(context); + sqlite3_result_error_code(context, str.accError); + } +} + +/* +** The unicode() function. Return the integer unicode code-point value +** for the first character of the input string. +*/ +static void unicodeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *z = sqlite3_value_text(argv[0]); + (void)argc; + if( z && z[0] ) sqlite3_result_int(context, sqlite3Utf8Read(&z)); +} + +/* +** The char() function takes zero or more arguments, each of which is +** an integer. It constructs a string where each character of the string +** is the unicode character for the corresponding integer argument. +*/ +static void charFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + unsigned char *z, *zOut; + int i; + zOut = z = sqlite3_malloc64( argc*4+1 ); + if( z==0 ){ + sqlite3_result_error_nomem(context); + return; + } + for(i=0; i<argc; i++){ + sqlite3_int64 x; + unsigned c; + x = sqlite3_value_int64(argv[i]); + if( x<0 || x>0x10ffff ) x = 0xfffd; + c = (unsigned)(x & 0x1fffff); + if( c<0x00080 ){ + *zOut++ = (u8)(c&0xFF); + }else if( c<0x00800 ){ + *zOut++ = 0xC0 + (u8)((c>>6)&0x1F); + *zOut++ = 0x80 + (u8)(c & 0x3F); + }else if( c<0x10000 ){ + *zOut++ = 0xE0 + (u8)((c>>12)&0x0F); + *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); + *zOut++ = 0x80 + (u8)(c & 0x3F); + }else{ + *zOut++ = 0xF0 + (u8)((c>>18) & 0x07); + *zOut++ = 0x80 + (u8)((c>>12) & 0x3F); + *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); + *zOut++ = 0x80 + (u8)(c & 0x3F); + } \ + } + *zOut = 0; + sqlite3_result_text64(context, (char*)z, zOut-z, sqlite3_free, SQLITE_UTF8); +} + +/* +** The hex() function. Interpret the argument as a blob. Return +** a hexadecimal rendering as text. +*/ +static void hexFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int i, n; + const unsigned char *pBlob; + char *zHex, *z; + assert( argc==1 ); + UNUSED_PARAMETER(argc); + pBlob = sqlite3_value_blob(argv[0]); + n = sqlite3_value_bytes(argv[0]); + assert( pBlob==sqlite3_value_blob(argv[0]) ); /* No encoding change */ + z = zHex = contextMalloc(context, ((i64)n)*2 + 1); + if( zHex ){ + for(i=0; i<n; i++, pBlob++){ + unsigned char c = *pBlob; + *(z++) = hexdigits[(c>>4)&0xf]; + *(z++) = hexdigits[c&0xf]; + } + *z = 0; + sqlite3_result_text(context, zHex, n*2, sqlite3_free); + } +} + +/* +** Buffer zStr contains nStr bytes of utf-8 encoded text. Return 1 if zStr +** contains character ch, or 0 if it does not. +*/ +static int strContainsChar(const u8 *zStr, int nStr, u32 ch){ + const u8 *zEnd = &zStr[nStr]; + const u8 *z = zStr; + while( z<zEnd ){ + u32 tst = Utf8Read(z); + if( tst==ch ) return 1; + } + return 0; +} + +/* +** The unhex() function. This function may be invoked with either one or +** two arguments. In both cases the first argument is interpreted as text +** a text value containing a set of pairs of hexadecimal digits which are +** decoded and returned as a blob. +** +** If there is only a single argument, then it must consist only of an +** even number of hexadecimal digits. Otherwise, return NULL. +** +** Or, if there is a second argument, then any character that appears in +** the second argument is also allowed to appear between pairs of hexadecimal +** digits in the first argument. If any other character appears in the +** first argument, or if one of the allowed characters appears between +** two hexadecimal digits that make up a single byte, NULL is returned. +** +** The following expressions are all true: +** +** unhex('ABCD') IS x'ABCD' +** unhex('AB CD') IS NULL +** unhex('AB CD', ' ') IS x'ABCD' +** unhex('A BCD', ' ') IS NULL +*/ +static void unhexFunc( + sqlite3_context *pCtx, + int argc, + sqlite3_value **argv +){ + const u8 *zPass = (const u8*)""; + int nPass = 0; + const u8 *zHex = sqlite3_value_text(argv[0]); + int nHex = sqlite3_value_bytes(argv[0]); +#ifdef SQLITE_DEBUG + const u8 *zEnd = zHex ? &zHex[nHex] : 0; +#endif + u8 *pBlob = 0; + u8 *p = 0; + + assert( argc==1 || argc==2 ); + if( argc==2 ){ + zPass = sqlite3_value_text(argv[1]); + nPass = sqlite3_value_bytes(argv[1]); + } + if( !zHex || !zPass ) return; + + p = pBlob = contextMalloc(pCtx, (nHex/2)+1); + if( pBlob ){ + u8 c; /* Most significant digit of next byte */ + u8 d; /* Least significant digit of next byte */ + + while( (c = *zHex)!=0x00 ){ + while( !sqlite3Isxdigit(c) ){ + u32 ch = Utf8Read(zHex); + assert( zHex<=zEnd ); + if( !strContainsChar(zPass, nPass, ch) ) goto unhex_null; + c = *zHex; + if( c==0x00 ) goto unhex_done; + } + zHex++; + assert( *zEnd==0x00 ); + assert( zHex<=zEnd ); + d = *(zHex++); + if( !sqlite3Isxdigit(d) ) goto unhex_null; + *(p++) = (sqlite3HexToInt(c)<<4) | sqlite3HexToInt(d); + } + } + + unhex_done: + sqlite3_result_blob(pCtx, pBlob, (p - pBlob), sqlite3_free); + return; + + unhex_null: + sqlite3_free(pBlob); + return; +} + + +/* +** The zeroblob(N) function returns a zero-filled blob of size N bytes. +*/ +static void zeroblobFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + i64 n; + int rc; + assert( argc==1 ); + UNUSED_PARAMETER(argc); + n = sqlite3_value_int64(argv[0]); + if( n<0 ) n = 0; + rc = sqlite3_result_zeroblob64(context, n); /* IMP: R-00293-64994 */ + if( rc ){ + sqlite3_result_error_code(context, rc); + } +} + +/* +** The replace() function. Three arguments are all strings: call +** them A, B, and C. The result is also a string which is derived +** from A by replacing every occurrence of B with C. The match +** must be exact. Collating sequences are not used. +*/ +static void replaceFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *zStr; /* The input string A */ + const unsigned char *zPattern; /* The pattern string B */ + const unsigned char *zRep; /* The replacement string C */ + unsigned char *zOut; /* The output */ + int nStr; /* Size of zStr */ + int nPattern; /* Size of zPattern */ + int nRep; /* Size of zRep */ + i64 nOut; /* Maximum size of zOut */ + int loopLimit; /* Last zStr[] that might match zPattern[] */ + int i, j; /* Loop counters */ + unsigned cntExpand; /* Number zOut expansions */ + sqlite3 *db = sqlite3_context_db_handle(context); + + assert( argc==3 ); + UNUSED_PARAMETER(argc); + zStr = sqlite3_value_text(argv[0]); + if( zStr==0 ) return; + nStr = sqlite3_value_bytes(argv[0]); + assert( zStr==sqlite3_value_text(argv[0]) ); /* No encoding change */ + zPattern = sqlite3_value_text(argv[1]); + if( zPattern==0 ){ + assert( sqlite3_value_type(argv[1])==SQLITE_NULL + || sqlite3_context_db_handle(context)->mallocFailed ); + return; + } + if( zPattern[0]==0 ){ + assert( sqlite3_value_type(argv[1])!=SQLITE_NULL ); + sqlite3_result_value(context, argv[0]); + return; + } + nPattern = sqlite3_value_bytes(argv[1]); + assert( zPattern==sqlite3_value_text(argv[1]) ); /* No encoding change */ + zRep = sqlite3_value_text(argv[2]); + if( zRep==0 ) return; + nRep = sqlite3_value_bytes(argv[2]); + assert( zRep==sqlite3_value_text(argv[2]) ); + nOut = nStr + 1; + assert( nOut<SQLITE_MAX_LENGTH ); + zOut = contextMalloc(context, (i64)nOut); + if( zOut==0 ){ + return; + } + loopLimit = nStr - nPattern; + cntExpand = 0; + for(i=j=0; i<=loopLimit; i++){ + if( zStr[i]!=zPattern[0] || memcmp(&zStr[i], zPattern, nPattern) ){ + zOut[j++] = zStr[i]; + }else{ + if( nRep>nPattern ){ + nOut += nRep - nPattern; + testcase( nOut-1==db->aLimit[SQLITE_LIMIT_LENGTH] ); + testcase( nOut-2==db->aLimit[SQLITE_LIMIT_LENGTH] ); + if( nOut-1>db->aLimit[SQLITE_LIMIT_LENGTH] ){ + sqlite3_result_error_toobig(context); + sqlite3_free(zOut); + return; + } + cntExpand++; + if( (cntExpand&(cntExpand-1))==0 ){ + /* Grow the size of the output buffer only on substitutions + ** whose index is a power of two: 1, 2, 4, 8, 16, 32, ... */ + u8 *zOld; + zOld = zOut; + zOut = sqlite3Realloc(zOut, (int)nOut + (nOut - nStr - 1)); + if( zOut==0 ){ + sqlite3_result_error_nomem(context); + sqlite3_free(zOld); + return; + } + } + } + memcpy(&zOut[j], zRep, nRep); + j += nRep; + i += nPattern-1; + } + } + assert( j+nStr-i+1<=nOut ); + memcpy(&zOut[j], &zStr[i], nStr-i); + j += nStr - i; + assert( j<=nOut ); + zOut[j] = 0; + sqlite3_result_text(context, (char*)zOut, j, sqlite3_free); +} + +/* +** Implementation of the TRIM(), LTRIM(), and RTRIM() functions. +** The userdata is 0x1 for left trim, 0x2 for right trim, 0x3 for both. +*/ +static void trimFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *zIn; /* Input string */ + const unsigned char *zCharSet; /* Set of characters to trim */ + unsigned int nIn; /* Number of bytes in input */ + int flags; /* 1: trimleft 2: trimright 3: trim */ + int i; /* Loop counter */ + unsigned int *aLen = 0; /* Length of each character in zCharSet */ + unsigned char **azChar = 0; /* Individual characters in zCharSet */ + int nChar; /* Number of characters in zCharSet */ + + if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ + return; + } + zIn = sqlite3_value_text(argv[0]); + if( zIn==0 ) return; + nIn = (unsigned)sqlite3_value_bytes(argv[0]); + assert( zIn==sqlite3_value_text(argv[0]) ); + if( argc==1 ){ + static const unsigned lenOne[] = { 1 }; + static unsigned char * const azOne[] = { (u8*)" " }; + nChar = 1; + aLen = (unsigned*)lenOne; + azChar = (unsigned char **)azOne; + zCharSet = 0; + }else if( (zCharSet = sqlite3_value_text(argv[1]))==0 ){ + return; + }else{ + const unsigned char *z; + for(z=zCharSet, nChar=0; *z; nChar++){ + SQLITE_SKIP_UTF8(z); + } + if( nChar>0 ){ + azChar = contextMalloc(context, + ((i64)nChar)*(sizeof(char*)+sizeof(unsigned))); + if( azChar==0 ){ + return; + } + aLen = (unsigned*)&azChar[nChar]; + for(z=zCharSet, nChar=0; *z; nChar++){ + azChar[nChar] = (unsigned char *)z; + SQLITE_SKIP_UTF8(z); + aLen[nChar] = (unsigned)(z - azChar[nChar]); + } + } + } + if( nChar>0 ){ + flags = SQLITE_PTR_TO_INT(sqlite3_user_data(context)); + if( flags & 1 ){ + while( nIn>0 ){ + unsigned int len = 0; + for(i=0; i<nChar; i++){ + len = aLen[i]; + if( len<=nIn && memcmp(zIn, azChar[i], len)==0 ) break; + } + if( i>=nChar ) break; + zIn += len; + nIn -= len; + } + } + if( flags & 2 ){ + while( nIn>0 ){ + unsigned int len = 0; + for(i=0; i<nChar; i++){ + len = aLen[i]; + if( len<=nIn && memcmp(&zIn[nIn-len],azChar[i],len)==0 ) break; + } + if( i>=nChar ) break; + nIn -= len; + } + } + if( zCharSet ){ + sqlite3_free(azChar); + } + } + sqlite3_result_text(context, (char*)zIn, nIn, SQLITE_TRANSIENT); +} + +/* The core implementation of the CONCAT(...) and CONCAT_WS(SEP,...) +** functions. +** +** Return a string value that is the concatenation of all non-null +** entries in argv[]. Use zSep as the separator. +*/ +static void concatFuncCore( + sqlite3_context *context, + int argc, + sqlite3_value **argv, + int nSep, + const char *zSep +){ + i64 j, k, n = 0; + int i; + char *z; + for(i=0; i<argc; i++){ + n += sqlite3_value_bytes(argv[i]); + } + n += (argc-1)*nSep; + z = sqlite3_malloc64(n+1); + if( z==0 ){ + sqlite3_result_error_nomem(context); + return; + } + j = 0; + for(i=0; i<argc; i++){ + k = sqlite3_value_bytes(argv[i]); + if( k>0 ){ + const char *v = (const char*)sqlite3_value_text(argv[i]); + if( ALWAYS(v!=0) ){ + if( j>0 && nSep>0 ){ + memcpy(&z[j], zSep, nSep); + j += nSep; + } + memcpy(&z[j], v, k); + j += k; + } + } + } + z[j] = 0; + assert( j<=n ); + sqlite3_result_text64(context, z, n, sqlite3_free, SQLITE_UTF8); +} + +/* +** The CONCAT(...) function. Generate a string result that is the +** concatentation of all non-null arguments. +*/ +static void concatFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + concatFuncCore(context, argc, argv, 0, ""); +} + +/* +** The CONCAT_WS(separator, ...) function. +** +** Generate a string that is the concatenation of 2nd through the Nth +** argument. Use the first argument (which must be non-NULL) as the +** separator. +*/ +static void concatwsFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int nSep = sqlite3_value_bytes(argv[0]); + const char *zSep = (const char*)sqlite3_value_text(argv[0]); + if( zSep==0 ) return; + concatFuncCore(context, argc-1, argv+1, nSep, zSep); +} + + +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +/* +** The "unknown" function is automatically substituted in place of +** any unrecognized function name when doing an EXPLAIN or EXPLAIN QUERY PLAN +** when the SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION compile-time option is used. +** When the "sqlite3" command-line shell is built using this functionality, +** that allows an EXPLAIN or EXPLAIN QUERY PLAN for complex queries +** involving application-defined functions to be examined in a generic +** sqlite3 shell. +*/ +static void unknownFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + /* no-op */ + (void)context; + (void)argc; + (void)argv; +} +#endif /*SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION*/ + + +/* IMP: R-25361-16150 This function is omitted from SQLite by default. It +** is only available if the SQLITE_SOUNDEX compile-time option is used +** when SQLite is built. +*/ +#ifdef SQLITE_SOUNDEX +/* +** Compute the soundex encoding of a word. +** +** IMP: R-59782-00072 The soundex(X) function returns a string that is the +** soundex encoding of the string X. +*/ +static void soundexFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + char zResult[8]; + const u8 *zIn; + int i, j; + static const unsigned char iCode[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, + 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, + 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, + }; + assert( argc==1 ); + zIn = (u8*)sqlite3_value_text(argv[0]); + if( zIn==0 ) zIn = (u8*)""; + for(i=0; zIn[i] && !sqlite3Isalpha(zIn[i]); i++){} + if( zIn[i] ){ + u8 prevcode = iCode[zIn[i]&0x7f]; + zResult[0] = sqlite3Toupper(zIn[i]); + for(j=1; j<4 && zIn[i]; i++){ + int code = iCode[zIn[i]&0x7f]; + if( code>0 ){ + if( code!=prevcode ){ + prevcode = code; + zResult[j++] = code + '0'; + } + }else{ + prevcode = 0; + } + } + while( j<4 ){ + zResult[j++] = '0'; + } + zResult[j] = 0; + sqlite3_result_text(context, zResult, 4, SQLITE_TRANSIENT); + }else{ + /* IMP: R-64894-50321 The string "?000" is returned if the argument + ** is NULL or contains no ASCII alphabetic characters. */ + sqlite3_result_text(context, "?000", 4, SQLITE_STATIC); + } +} +#endif /* SQLITE_SOUNDEX */ + +#ifndef SQLITE_OMIT_LOAD_EXTENSION +/* +** A function that loads a shared-library extension then returns NULL. +*/ +static void loadExt(sqlite3_context *context, int argc, sqlite3_value **argv){ + const char *zFile = (const char *)sqlite3_value_text(argv[0]); + const char *zProc; + sqlite3 *db = sqlite3_context_db_handle(context); + char *zErrMsg = 0; + + /* Disallow the load_extension() SQL function unless the SQLITE_LoadExtFunc + ** flag is set. See the sqlite3_enable_load_extension() API. + */ + if( (db->flags & SQLITE_LoadExtFunc)==0 ){ + sqlite3_result_error(context, "not authorized", -1); + return; + } + + if( argc==2 ){ + zProc = (const char *)sqlite3_value_text(argv[1]); + }else{ + zProc = 0; + } + if( zFile && sqlite3_load_extension(db, zFile, zProc, &zErrMsg) ){ + sqlite3_result_error(context, zErrMsg, -1); + sqlite3_free(zErrMsg); + } +} +#endif + + +/* +** An instance of the following structure holds the context of a +** sum() or avg() aggregate computation. +*/ +typedef struct SumCtx SumCtx; +struct SumCtx { + double rSum; /* Running sum as as a double */ + double rErr; /* Error term for Kahan-Babushka-Neumaier summation */ + i64 iSum; /* Running sum as a signed integer */ + i64 cnt; /* Number of elements summed */ + u8 approx; /* True if any non-integer value was input to the sum */ + u8 ovrfl; /* Integer overflow seen */ +}; + +/* +** Do one step of the Kahan-Babushka-Neumaier summation. +** +** https://en.wikipedia.org/wiki/Kahan_summation_algorithm +** +** Variables are marked "volatile" to defeat c89 x86 floating point +** optimizations can mess up this algorithm. +*/ +static void kahanBabuskaNeumaierStep( + volatile SumCtx *pSum, + volatile double r +){ + volatile double s = pSum->rSum; + volatile double t = s + r; + if( fabs(s) > fabs(r) ){ + pSum->rErr += (s - t) + r; + }else{ + pSum->rErr += (r - t) + s; + } + pSum->rSum = t; +} + +/* +** Add a (possibly large) integer to the running sum. +*/ +static void kahanBabuskaNeumaierStepInt64(volatile SumCtx *pSum, i64 iVal){ + if( iVal<=-4503599627370496LL || iVal>=+4503599627370496LL ){ + i64 iBig, iSm; + iSm = iVal % 16384; + iBig = iVal - iSm; + kahanBabuskaNeumaierStep(pSum, iBig); + kahanBabuskaNeumaierStep(pSum, iSm); + }else{ + kahanBabuskaNeumaierStep(pSum, (double)iVal); + } +} + +/* +** Initialize the Kahan-Babaska-Neumaier sum from a 64-bit integer +*/ +static void kahanBabuskaNeumaierInit( + volatile SumCtx *p, + i64 iVal +){ + if( iVal<=-4503599627370496LL || iVal>=+4503599627370496LL ){ + i64 iSm = iVal % 16384; + p->rSum = (double)(iVal - iSm); + p->rErr = (double)iSm; + }else{ + p->rSum = (double)iVal; + p->rErr = 0.0; + } +} + +/* +** Routines used to compute the sum, average, and total. +** +** The SUM() function follows the (broken) SQL standard which means +** that it returns NULL if it sums over no inputs. TOTAL returns +** 0.0 in that case. In addition, TOTAL always returns a float where +** SUM might return an integer if it never encounters a floating point +** value. TOTAL never fails, but SUM might through an exception if +** it overflows an integer. +*/ +static void sumStep(sqlite3_context *context, int argc, sqlite3_value **argv){ + SumCtx *p; + int type; + assert( argc==1 ); + UNUSED_PARAMETER(argc); + p = sqlite3_aggregate_context(context, sizeof(*p)); + type = sqlite3_value_numeric_type(argv[0]); + if( p && type!=SQLITE_NULL ){ + p->cnt++; + if( p->approx==0 ){ + if( type!=SQLITE_INTEGER ){ + kahanBabuskaNeumaierInit(p, p->iSum); + p->approx = 1; + kahanBabuskaNeumaierStep(p, sqlite3_value_double(argv[0])); + }else{ + i64 x = p->iSum; + if( sqlite3AddInt64(&x, sqlite3_value_int64(argv[0]))==0 ){ + p->iSum = x; + }else{ + p->ovrfl = 1; + kahanBabuskaNeumaierInit(p, p->iSum); + p->approx = 1; + kahanBabuskaNeumaierStepInt64(p, sqlite3_value_int64(argv[0])); + } + } + }else{ + if( type==SQLITE_INTEGER ){ + kahanBabuskaNeumaierStepInt64(p, sqlite3_value_int64(argv[0])); + }else{ + p->ovrfl = 0; + kahanBabuskaNeumaierStep(p, sqlite3_value_double(argv[0])); + } + } + } +} +#ifndef SQLITE_OMIT_WINDOWFUNC +static void sumInverse(sqlite3_context *context, int argc, sqlite3_value**argv){ + SumCtx *p; + int type; + assert( argc==1 ); + UNUSED_PARAMETER(argc); + p = sqlite3_aggregate_context(context, sizeof(*p)); + type = sqlite3_value_numeric_type(argv[0]); + /* p is always non-NULL because sumStep() will have been called first + ** to initialize it */ + if( ALWAYS(p) && type!=SQLITE_NULL ){ + assert( p->cnt>0 ); + p->cnt--; + if( !p->approx ){ + p->iSum -= sqlite3_value_int64(argv[0]); + }else if( type==SQLITE_INTEGER ){ + i64 iVal = sqlite3_value_int64(argv[0]); + if( iVal!=SMALLEST_INT64 ){ + kahanBabuskaNeumaierStepInt64(p, -iVal); + }else{ + kahanBabuskaNeumaierStepInt64(p, LARGEST_INT64); + kahanBabuskaNeumaierStepInt64(p, 1); + } + }else{ + kahanBabuskaNeumaierStep(p, -sqlite3_value_double(argv[0])); + } + } +} +#else +# define sumInverse 0 +#endif /* SQLITE_OMIT_WINDOWFUNC */ +static void sumFinalize(sqlite3_context *context){ + SumCtx *p; + p = sqlite3_aggregate_context(context, 0); + if( p && p->cnt>0 ){ + if( p->approx ){ + if( p->ovrfl ){ + sqlite3_result_error(context,"integer overflow",-1); + }else if( !sqlite3IsNaN(p->rErr) ){ + sqlite3_result_double(context, p->rSum+p->rErr); + }else{ + sqlite3_result_double(context, p->rSum); + } + }else{ + sqlite3_result_int64(context, p->iSum); + } + } +} +static void avgFinalize(sqlite3_context *context){ + SumCtx *p; + p = sqlite3_aggregate_context(context, 0); + if( p && p->cnt>0 ){ + double r; + if( p->approx ){ + r = p->rSum; + if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; + }else{ + r = (double)(p->iSum); + } + sqlite3_result_double(context, r/(double)p->cnt); + } +} +static void totalFinalize(sqlite3_context *context){ + SumCtx *p; + double r = 0.0; + p = sqlite3_aggregate_context(context, 0); + if( p ){ + if( p->approx ){ + r = p->rSum; + if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; + }else{ + r = (double)(p->iSum); + } + } + sqlite3_result_double(context, r); +} + +/* +** The following structure keeps track of state information for the +** count() aggregate function. +*/ +typedef struct CountCtx CountCtx; +struct CountCtx { + i64 n; +#ifdef SQLITE_DEBUG + int bInverse; /* True if xInverse() ever called */ +#endif +}; + +/* +** Routines to implement the count() aggregate function. +*/ +static void countStep(sqlite3_context *context, int argc, sqlite3_value **argv){ + CountCtx *p; + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( (argc==0 || SQLITE_NULL!=sqlite3_value_type(argv[0])) && p ){ + p->n++; + } + +#ifndef SQLITE_OMIT_DEPRECATED + /* The sqlite3_aggregate_count() function is deprecated. But just to make + ** sure it still operates correctly, verify that its count agrees with our + ** internal count when using count(*) and when the total count can be + ** expressed as a 32-bit integer. */ + assert( argc==1 || p==0 || p->n>0x7fffffff || p->bInverse + || p->n==sqlite3_aggregate_count(context) ); +#endif +} +static void countFinalize(sqlite3_context *context){ + CountCtx *p; + p = sqlite3_aggregate_context(context, 0); + sqlite3_result_int64(context, p ? p->n : 0); +} +#ifndef SQLITE_OMIT_WINDOWFUNC +static void countInverse(sqlite3_context *ctx, int argc, sqlite3_value **argv){ + CountCtx *p; + p = sqlite3_aggregate_context(ctx, sizeof(*p)); + /* p is always non-NULL since countStep() will have been called first */ + if( (argc==0 || SQLITE_NULL!=sqlite3_value_type(argv[0])) && ALWAYS(p) ){ + p->n--; +#ifdef SQLITE_DEBUG + p->bInverse = 1; +#endif + } +} +#else +# define countInverse 0 +#endif /* SQLITE_OMIT_WINDOWFUNC */ + +/* +** Routines to implement min() and max() aggregate functions. +*/ +static void minmaxStep( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + Mem *pArg = (Mem *)argv[0]; + Mem *pBest; + UNUSED_PARAMETER(NotUsed); + + pBest = (Mem *)sqlite3_aggregate_context(context, sizeof(*pBest)); + if( !pBest ) return; + + if( sqlite3_value_type(pArg)==SQLITE_NULL ){ + if( pBest->flags ) sqlite3SkipAccumulatorLoad(context); + }else if( pBest->flags ){ + int max; + int cmp; + CollSeq *pColl = sqlite3GetFuncCollSeq(context); + /* This step function is used for both the min() and max() aggregates, + ** the only difference between the two being that the sense of the + ** comparison is inverted. For the max() aggregate, the + ** sqlite3_user_data() function returns (void *)-1. For min() it + ** returns (void *)db, where db is the sqlite3* database pointer. + ** Therefore the next statement sets variable 'max' to 1 for the max() + ** aggregate, or 0 for min(). + */ + max = sqlite3_user_data(context)!=0; + cmp = sqlite3MemCompare(pBest, pArg, pColl); + if( (max && cmp<0) || (!max && cmp>0) ){ + sqlite3VdbeMemCopy(pBest, pArg); + }else{ + sqlite3SkipAccumulatorLoad(context); + } + }else{ + pBest->db = sqlite3_context_db_handle(context); + sqlite3VdbeMemCopy(pBest, pArg); + } +} +static void minMaxValueFinalize(sqlite3_context *context, int bValue){ + sqlite3_value *pRes; + pRes = (sqlite3_value *)sqlite3_aggregate_context(context, 0); + if( pRes ){ + if( pRes->flags ){ + sqlite3_result_value(context, pRes); + } + if( bValue==0 ) sqlite3VdbeMemRelease(pRes); + } +} +#ifndef SQLITE_OMIT_WINDOWFUNC +static void minMaxValue(sqlite3_context *context){ + minMaxValueFinalize(context, 1); +} +#else +# define minMaxValue 0 +#endif /* SQLITE_OMIT_WINDOWFUNC */ +static void minMaxFinalize(sqlite3_context *context){ + minMaxValueFinalize(context, 0); +} + +/* +** group_concat(EXPR, ?SEPARATOR?) +** +** The SEPARATOR goes before the EXPR string. This is tragic. The +** groupConcatInverse() implementation would have been easier if the +** SEPARATOR were appended after EXPR. And the order is undocumented, +** so we could change it, in theory. But the old behavior has been +** around for so long that we dare not, for fear of breaking something. +*/ +typedef struct { + StrAccum str; /* The accumulated concatenation */ +#ifndef SQLITE_OMIT_WINDOWFUNC + int nAccum; /* Number of strings presently concatenated */ + int nFirstSepLength; /* Used to detect separator length change */ + /* If pnSepLengths!=0, refs an array of inter-string separator lengths, + ** stored as actually incorporated into presently accumulated result. + ** (Hence, its slots in use number nAccum-1 between method calls.) + ** If pnSepLengths==0, nFirstSepLength is the length used throughout. + */ + int *pnSepLengths; +#endif +} GroupConcatCtx; + +static void groupConcatStep( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zVal; + GroupConcatCtx *pGCC; + const char *zSep; + int nVal, nSep; + assert( argc==1 || argc==2 ); + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC)); + if( pGCC ){ + sqlite3 *db = sqlite3_context_db_handle(context); + int firstTerm = pGCC->str.mxAlloc==0; + pGCC->str.mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH]; + if( argc==1 ){ + if( !firstTerm ){ + sqlite3_str_appendchar(&pGCC->str, 1, ','); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + pGCC->nFirstSepLength = 1; + } +#endif + }else if( !firstTerm ){ + zSep = (char*)sqlite3_value_text(argv[1]); + nSep = sqlite3_value_bytes(argv[1]); + if( zSep ){ + sqlite3_str_append(&pGCC->str, zSep, nSep); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + nSep = 0; + } + if( nSep != pGCC->nFirstSepLength || pGCC->pnSepLengths != 0 ){ + int *pnsl = pGCC->pnSepLengths; + if( pnsl == 0 ){ + /* First separator length variation seen, start tracking them. */ + pnsl = (int*)sqlite3_malloc64((pGCC->nAccum+1) * sizeof(int)); + if( pnsl!=0 ){ + int i = 0, nA = pGCC->nAccum-1; + while( i<nA ) pnsl[i++] = pGCC->nFirstSepLength; + } + }else{ + pnsl = (int*)sqlite3_realloc64(pnsl, pGCC->nAccum * sizeof(int)); + } + if( pnsl!=0 ){ + if( ALWAYS(pGCC->nAccum>0) ){ + pnsl[pGCC->nAccum-1] = nSep; + } + pGCC->pnSepLengths = pnsl; + }else{ + sqlite3StrAccumSetError(&pGCC->str, SQLITE_NOMEM); + } + } +#endif + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + pGCC->nFirstSepLength = sqlite3_value_bytes(argv[1]); + } + pGCC->nAccum += 1; +#endif + zVal = (char*)sqlite3_value_text(argv[0]); + nVal = sqlite3_value_bytes(argv[0]); + if( zVal ) sqlite3_str_append(&pGCC->str, zVal, nVal); + } +} + +#ifndef SQLITE_OMIT_WINDOWFUNC +static void groupConcatInverse( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GroupConcatCtx *pGCC; + assert( argc==1 || argc==2 ); + (void)argc; /* Suppress unused parameter warning */ + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC)); + /* pGCC is always non-NULL since groupConcatStep() will have always + ** run first to initialize it */ + if( ALWAYS(pGCC) ){ + int nVS; + /* Must call sqlite3_value_text() to convert the argument into text prior + ** to invoking sqlite3_value_bytes(), in case the text encoding is UTF16 */ + (void)sqlite3_value_text(argv[0]); + nVS = sqlite3_value_bytes(argv[0]); + pGCC->nAccum -= 1; + if( pGCC->pnSepLengths!=0 ){ + assert(pGCC->nAccum >= 0); + if( pGCC->nAccum>0 ){ + nVS += *pGCC->pnSepLengths; + memmove(pGCC->pnSepLengths, pGCC->pnSepLengths+1, + (pGCC->nAccum-1)*sizeof(int)); + } + }else{ + /* If removing single accumulated string, harmlessly over-do. */ + nVS += pGCC->nFirstSepLength; + } + if( nVS>=(int)pGCC->str.nChar ){ + pGCC->str.nChar = 0; + }else{ + pGCC->str.nChar -= nVS; + memmove(pGCC->str.zText, &pGCC->str.zText[nVS], pGCC->str.nChar); + } + if( pGCC->str.nChar==0 ){ + pGCC->str.mxAlloc = 0; + sqlite3_free(pGCC->pnSepLengths); + pGCC->pnSepLengths = 0; + } + } +} +#else +# define groupConcatInverse 0 +#endif /* SQLITE_OMIT_WINDOWFUNC */ +static void groupConcatFinalize(sqlite3_context *context){ + GroupConcatCtx *pGCC + = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0); + if( pGCC ){ + sqlite3ResultStrAccum(context, &pGCC->str); +#ifndef SQLITE_OMIT_WINDOWFUNC + sqlite3_free(pGCC->pnSepLengths); +#endif + } +} +#ifndef SQLITE_OMIT_WINDOWFUNC +static void groupConcatValue(sqlite3_context *context){ + GroupConcatCtx *pGCC + = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0); + if( pGCC ){ + StrAccum *pAccum = &pGCC->str; + if( pAccum->accError==SQLITE_TOOBIG ){ + sqlite3_result_error_toobig(context); + }else if( pAccum->accError==SQLITE_NOMEM ){ + sqlite3_result_error_nomem(context); + }else{ + const char *zText = sqlite3_str_value(pAccum); + sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT); + } + } +} +#else +# define groupConcatValue 0 +#endif /* SQLITE_OMIT_WINDOWFUNC */ + +/* +** This routine does per-connection function registration. Most +** of the built-in functions above are part of the global function set. +** This routine only deals with those that are not global. +*/ +SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3 *db){ + int rc = sqlite3_overload_function(db, "MATCH", 2); + assert( rc==SQLITE_NOMEM || rc==SQLITE_OK ); + if( rc==SQLITE_NOMEM ){ + sqlite3OomFault(db); + } +} + +/* +** Re-register the built-in LIKE functions. The caseSensitive +** parameter determines whether or not the LIKE operator is case +** sensitive. +*/ +SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){ + FuncDef *pDef; + struct compareInfo *pInfo; + int flags; + int nArg; + if( caseSensitive ){ + pInfo = (struct compareInfo*)&likeInfoAlt; + flags = SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE; + }else{ + pInfo = (struct compareInfo*)&likeInfoNorm; + flags = SQLITE_FUNC_LIKE; + } + for(nArg=2; nArg<=3; nArg++){ + sqlite3CreateFunc(db, "like", nArg, SQLITE_UTF8, pInfo, likeFunc, + 0, 0, 0, 0, 0); + pDef = sqlite3FindFunction(db, "like", nArg, SQLITE_UTF8, 0); + pDef->funcFlags |= flags; + pDef->funcFlags &= ~SQLITE_FUNC_UNSAFE; + } +} + +/* +** pExpr points to an expression which implements a function. If +** it is appropriate to apply the LIKE optimization to that function +** then set aWc[0] through aWc[2] to the wildcard characters and the +** escape character and then return TRUE. If the function is not a +** LIKE-style function then return FALSE. +** +** The expression "a LIKE b ESCAPE c" is only considered a valid LIKE +** operator if c is a string literal that is exactly one byte in length. +** That one byte is stored in aWc[3]. aWc[3] is set to zero if there is +** no ESCAPE clause. +** +** *pIsNocase is set to true if uppercase and lowercase are equivalent for +** the function (default for LIKE). If the function makes the distinction +** between uppercase and lowercase (as does GLOB) then *pIsNocase is set to +** false. +*/ +SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){ + FuncDef *pDef; + int nExpr; + assert( pExpr!=0 ); + assert( pExpr->op==TK_FUNCTION ); + assert( ExprUseXList(pExpr) ); + if( !pExpr->x.pList ){ + return 0; + } + nExpr = pExpr->x.pList->nExpr; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + pDef = sqlite3FindFunction(db, pExpr->u.zToken, nExpr, SQLITE_UTF8, 0); +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + if( pDef==0 ) return 0; +#endif + if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_FUNC_LIKE)==0 ){ + return 0; + } + + /* The memcpy() statement assumes that the wildcard characters are + ** the first three statements in the compareInfo structure. The + ** asserts() that follow verify that assumption + */ + memcpy(aWc, pDef->pUserData, 3); + assert( (char*)&likeInfoAlt == (char*)&likeInfoAlt.matchAll ); + assert( &((char*)&likeInfoAlt)[1] == (char*)&likeInfoAlt.matchOne ); + assert( &((char*)&likeInfoAlt)[2] == (char*)&likeInfoAlt.matchSet ); + + if( nExpr<3 ){ + aWc[3] = 0; + }else{ + Expr *pEscape = pExpr->x.pList->a[2].pExpr; + char *zEscape; + if( pEscape->op!=TK_STRING ) return 0; + assert( !ExprHasProperty(pEscape, EP_IntValue) ); + zEscape = pEscape->u.zToken; + if( zEscape[0]==0 || zEscape[1]!=0 ) return 0; + if( zEscape[0]==aWc[0] ) return 0; + if( zEscape[0]==aWc[1] ) return 0; + aWc[3] = zEscape[0]; + } + + *pIsNocase = (pDef->funcFlags & SQLITE_FUNC_CASE)==0; + return 1; +} + +/* Mathematical Constants */ +#ifndef M_PI +# define M_PI 3.141592653589793238462643383279502884 +#endif +#ifndef M_LN10 +# define M_LN10 2.302585092994045684017991454684364208 +#endif +#ifndef M_LN2 +# define M_LN2 0.693147180559945309417232121458176568 +#endif + + +/* Extra math functions that require linking with -lm +*/ +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS +/* +** Implementation SQL functions: +** +** ceil(X) +** ceiling(X) +** floor(X) +** +** The sqlite3_user_data() pointer is a pointer to the libm implementation +** of the underlying C function. +*/ +static void ceilingFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + switch( sqlite3_value_numeric_type(argv[0]) ){ + case SQLITE_INTEGER: { + sqlite3_result_int64(context, sqlite3_value_int64(argv[0])); + break; + } + case SQLITE_FLOAT: { + double (*x)(double) = (double(*)(double))sqlite3_user_data(context); + sqlite3_result_double(context, x(sqlite3_value_double(argv[0]))); + break; + } + default: { + break; + } + } +} + +/* +** On some systems, ceil() and floor() are intrinsic function. You are +** unable to take a pointer to these functions. Hence, we here wrap them +** in our own actual functions. +*/ +static double xCeil(double x){ return ceil(x); } +static double xFloor(double x){ return floor(x); } + +/* +** Some systems do not have log2() and log10() in their standard math +** libraries. +*/ +#if defined(HAVE_LOG10) && HAVE_LOG10==0 +# define log10(X) (0.4342944819032517867*log(X)) +#endif +#if defined(HAVE_LOG2) && HAVE_LOG2==0 +# define log2(X) (1.442695040888963456*log(X)) +#endif + + +/* +** Implementation of SQL functions: +** +** ln(X) - natural logarithm +** log(X) - log X base 10 +** log10(X) - log X base 10 +** log(B,X) - log X base B +*/ +static void logFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + double x, b, ans; + assert( argc==1 || argc==2 ); + switch( sqlite3_value_numeric_type(argv[0]) ){ + case SQLITE_INTEGER: + case SQLITE_FLOAT: + x = sqlite3_value_double(argv[0]); + if( x<=0.0 ) return; + break; + default: + return; + } + if( argc==2 ){ + switch( sqlite3_value_numeric_type(argv[0]) ){ + case SQLITE_INTEGER: + case SQLITE_FLOAT: + b = log(x); + if( b<=0.0 ) return; + x = sqlite3_value_double(argv[1]); + if( x<=0.0 ) return; + break; + default: + return; + } + ans = log(x)/b; + }else{ + switch( SQLITE_PTR_TO_INT(sqlite3_user_data(context)) ){ + case 1: + ans = log10(x); + break; + case 2: + ans = log2(x); + break; + default: + ans = log(x); + break; + } + } + sqlite3_result_double(context, ans); +} + +/* +** Functions to converts degrees to radians and radians to degrees. +*/ +static double degToRad(double x){ return x*(M_PI/180.0); } +static double radToDeg(double x){ return x*(180.0/M_PI); } + +/* +** Implementation of 1-argument SQL math functions: +** +** exp(X) - Compute e to the X-th power +*/ +static void math1Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int type0; + double v0, ans; + double (*x)(double); + assert( argc==1 ); + type0 = sqlite3_value_numeric_type(argv[0]); + if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; + v0 = sqlite3_value_double(argv[0]); + x = (double(*)(double))sqlite3_user_data(context); + ans = x(v0); + sqlite3_result_double(context, ans); +} + +/* +** Implementation of 2-argument SQL math functions: +** +** power(X,Y) - Compute X to the Y-th power +*/ +static void math2Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int type0, type1; + double v0, v1, ans; + double (*x)(double,double); + assert( argc==2 ); + type0 = sqlite3_value_numeric_type(argv[0]); + if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; + type1 = sqlite3_value_numeric_type(argv[1]); + if( type1!=SQLITE_INTEGER && type1!=SQLITE_FLOAT ) return; + v0 = sqlite3_value_double(argv[0]); + v1 = sqlite3_value_double(argv[1]); + x = (double(*)(double,double))sqlite3_user_data(context); + ans = x(v0, v1); + sqlite3_result_double(context, ans); +} + +/* +** Implementation of 0-argument pi() function. +*/ +static void piFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==0 ); + (void)argv; + sqlite3_result_double(context, M_PI); +} + +#endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ + +/* +** Implementation of sign(X) function. +*/ +static void signFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int type0; + double x; + UNUSED_PARAMETER(argc); + assert( argc==1 ); + type0 = sqlite3_value_numeric_type(argv[0]); + if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; + x = sqlite3_value_double(argv[0]); + sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0); +} + +#ifdef SQLITE_DEBUG +/* +** Implementation of fpdecode(x,y,z) function. +** +** x is a real number that is to be decoded. y is the precision. +** z is the maximum real precision. +*/ +static void fpdecodeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + FpDecode s; + double x; + int y, z; + char zBuf[100]; + UNUSED_PARAMETER(argc); + assert( argc==3 ); + x = sqlite3_value_double(argv[0]); + y = sqlite3_value_int(argv[1]); + z = sqlite3_value_int(argv[2]); + sqlite3FpDecode(&s, x, y, z); + if( s.isSpecial==2 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, "NaN"); + }else{ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%c%.*s/%d", s.sign, s.n, s.z, s.iDP); + } + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); +} +#endif /* SQLITE_DEBUG */ + +/* +** All of the FuncDef structures in the aBuiltinFunc[] array above +** to the global function hash table. This occurs at start-time (as +** a consequence of calling sqlite3_initialize()). +** +** After this routine runs +*/ +SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ + /* + ** The following array holds FuncDef structures for all of the functions + ** defined in this file. + ** + ** The array cannot be constant since changes are made to the + ** FuncDef.pHash elements at start-time. The elements of this array + ** are read-only after initialization is complete. + ** + ** For peak efficiency, put the most frequently used function last. + */ + static FuncDef aBuiltinFunc[] = { +/***** Functions only available with SQLITE_TESTCTRL_INTERNAL_FUNCTIONS *****/ +#if !defined(SQLITE_UNTESTABLE) + TEST_FUNC(implies_nonnull_row, 2, INLINEFUNC_implies_nonnull_row, 0), + TEST_FUNC(expr_compare, 2, INLINEFUNC_expr_compare, 0), + TEST_FUNC(expr_implies_expr, 2, INLINEFUNC_expr_implies_expr, 0), + TEST_FUNC(affinity, 1, INLINEFUNC_affinity, 0), +#endif /* !defined(SQLITE_UNTESTABLE) */ +/***** Regular functions *****/ +#ifdef SQLITE_SOUNDEX + FUNCTION(soundex, 1, 0, 0, soundexFunc ), +#endif +#ifndef SQLITE_OMIT_LOAD_EXTENSION + SFUNCTION(load_extension, 1, 0, 0, loadExt ), + SFUNCTION(load_extension, 2, 0, 0, loadExt ), +#endif +#if SQLITE_USER_AUTHENTICATION + FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ), +#endif +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS + DFUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ), + DFUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ), +#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ + INLINE_FUNC(unlikely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY), + INLINE_FUNC(likelihood, 2, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY), + INLINE_FUNC(likely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY), +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + INLINE_FUNC(sqlite_offset, 1, INLINEFUNC_sqlite_offset, 0 ), +#endif + FUNCTION(ltrim, 1, 1, 0, trimFunc ), + FUNCTION(ltrim, 2, 1, 0, trimFunc ), + FUNCTION(rtrim, 1, 2, 0, trimFunc ), + FUNCTION(rtrim, 2, 2, 0, trimFunc ), + FUNCTION(trim, 1, 3, 0, trimFunc ), + FUNCTION(trim, 2, 3, 0, trimFunc ), + FUNCTION(min, -1, 0, 1, minmaxFunc ), + FUNCTION(min, 0, 0, 1, 0 ), + WAGGREGATE(min, 1, 0, 1, minmaxStep, minMaxFinalize, minMaxValue, 0, + SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER ), + FUNCTION(max, -1, 1, 1, minmaxFunc ), + FUNCTION(max, 0, 1, 1, 0 ), + WAGGREGATE(max, 1, 1, 1, minmaxStep, minMaxFinalize, minMaxValue, 0, + SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER ), + FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQLITE_FUNC_TYPEOF), + FUNCTION2(subtype, 1, 0, 0, subtypeFunc, SQLITE_FUNC_TYPEOF), + FUNCTION2(length, 1, 0, 0, lengthFunc, SQLITE_FUNC_LENGTH), + FUNCTION2(octet_length, 1, 0, 0, bytelengthFunc,SQLITE_FUNC_BYTELEN), + FUNCTION(instr, 2, 0, 0, instrFunc ), + FUNCTION(printf, -1, 0, 0, printfFunc ), + FUNCTION(format, -1, 0, 0, printfFunc ), + FUNCTION(unicode, 1, 0, 0, unicodeFunc ), + FUNCTION(char, -1, 0, 0, charFunc ), + FUNCTION(abs, 1, 0, 0, absFunc ), +#ifdef SQLITE_DEBUG + FUNCTION(fpdecode, 3, 0, 0, fpdecodeFunc ), +#endif +#ifndef SQLITE_OMIT_FLOATING_POINT + FUNCTION(round, 1, 0, 0, roundFunc ), + FUNCTION(round, 2, 0, 0, roundFunc ), +#endif + FUNCTION(upper, 1, 0, 0, upperFunc ), + FUNCTION(lower, 1, 0, 0, lowerFunc ), + FUNCTION(hex, 1, 0, 0, hexFunc ), + FUNCTION(unhex, 1, 0, 0, unhexFunc ), + FUNCTION(unhex, 2, 0, 0, unhexFunc ), + FUNCTION(concat, -1, 0, 0, concatFunc ), + FUNCTION(concat, 0, 0, 0, 0 ), + FUNCTION(concat_ws, -1, 0, 0, concatwsFunc ), + FUNCTION(concat_ws, 0, 0, 0, 0 ), + FUNCTION(concat_ws, 1, 0, 0, 0 ), + INLINE_FUNC(ifnull, 2, INLINEFUNC_coalesce, 0 ), + VFUNCTION(random, 0, 0, 0, randomFunc ), + VFUNCTION(randomblob, 1, 0, 0, randomBlob ), + FUNCTION(nullif, 2, 0, 1, nullifFunc ), + DFUNCTION(sqlite_version, 0, 0, 0, versionFunc ), + DFUNCTION(sqlite_source_id, 0, 0, 0, sourceidFunc ), + FUNCTION(sqlite_log, 2, 0, 0, errlogFunc ), + FUNCTION(quote, 1, 0, 0, quoteFunc ), + VFUNCTION(last_insert_rowid, 0, 0, 0, last_insert_rowid), + VFUNCTION(changes, 0, 0, 0, changes ), + VFUNCTION(total_changes, 0, 0, 0, total_changes ), + FUNCTION(replace, 3, 0, 0, replaceFunc ), + FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc ), + FUNCTION(substr, 2, 0, 0, substrFunc ), + FUNCTION(substr, 3, 0, 0, substrFunc ), + FUNCTION(substring, 2, 0, 0, substrFunc ), + FUNCTION(substring, 3, 0, 0, substrFunc ), + WAGGREGATE(sum, 1,0,0, sumStep, sumFinalize, sumFinalize, sumInverse, 0), + WAGGREGATE(total, 1,0,0, sumStep,totalFinalize,totalFinalize,sumInverse, 0), + WAGGREGATE(avg, 1,0,0, sumStep, avgFinalize, avgFinalize, sumInverse, 0), + WAGGREGATE(count, 0,0,0, countStep, + countFinalize, countFinalize, countInverse, + SQLITE_FUNC_COUNT|SQLITE_FUNC_ANYORDER ), + WAGGREGATE(count, 1,0,0, countStep, + countFinalize, countFinalize, countInverse, SQLITE_FUNC_ANYORDER ), + WAGGREGATE(group_concat, 1, 0, 0, groupConcatStep, + groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), + WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep, + groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), + + LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), +#ifdef SQLITE_CASE_SENSITIVE_LIKE + LIKEFUNC(like, 2, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), + LIKEFUNC(like, 3, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), +#else + LIKEFUNC(like, 2, &likeInfoNorm, SQLITE_FUNC_LIKE), + LIKEFUNC(like, 3, &likeInfoNorm, SQLITE_FUNC_LIKE), +#endif +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + FUNCTION(unknown, -1, 0, 0, unknownFunc ), +#endif + FUNCTION(coalesce, 1, 0, 0, 0 ), + FUNCTION(coalesce, 0, 0, 0, 0 ), +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS + MFUNCTION(ceil, 1, xCeil, ceilingFunc ), + MFUNCTION(ceiling, 1, xCeil, ceilingFunc ), + MFUNCTION(floor, 1, xFloor, ceilingFunc ), +#if SQLITE_HAVE_C99_MATH_FUNCS + MFUNCTION(trunc, 1, trunc, ceilingFunc ), +#endif + FUNCTION(ln, 1, 0, 0, logFunc ), + FUNCTION(log, 1, 1, 0, logFunc ), + FUNCTION(log10, 1, 1, 0, logFunc ), + FUNCTION(log2, 1, 2, 0, logFunc ), + FUNCTION(log, 2, 0, 0, logFunc ), + MFUNCTION(exp, 1, exp, math1Func ), + MFUNCTION(pow, 2, pow, math2Func ), + MFUNCTION(power, 2, pow, math2Func ), + MFUNCTION(mod, 2, fmod, math2Func ), + MFUNCTION(acos, 1, acos, math1Func ), + MFUNCTION(asin, 1, asin, math1Func ), + MFUNCTION(atan, 1, atan, math1Func ), + MFUNCTION(atan2, 2, atan2, math2Func ), + MFUNCTION(cos, 1, cos, math1Func ), + MFUNCTION(sin, 1, sin, math1Func ), + MFUNCTION(tan, 1, tan, math1Func ), + MFUNCTION(cosh, 1, cosh, math1Func ), + MFUNCTION(sinh, 1, sinh, math1Func ), + MFUNCTION(tanh, 1, tanh, math1Func ), +#if SQLITE_HAVE_C99_MATH_FUNCS + MFUNCTION(acosh, 1, acosh, math1Func ), + MFUNCTION(asinh, 1, asinh, math1Func ), + MFUNCTION(atanh, 1, atanh, math1Func ), +#endif + MFUNCTION(sqrt, 1, sqrt, math1Func ), + MFUNCTION(radians, 1, degToRad, math1Func ), + MFUNCTION(degrees, 1, radToDeg, math1Func ), + FUNCTION(pi, 0, 0, 0, piFunc ), +#endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ + FUNCTION(sign, 1, 0, 0, signFunc ), + INLINE_FUNC(coalesce, -1, INLINEFUNC_coalesce, 0 ), + INLINE_FUNC(iif, 3, INLINEFUNC_iif, 0 ), + }; +#ifndef SQLITE_OMIT_ALTERTABLE + sqlite3AlterFunctions(); +#endif + sqlite3WindowFunctions(); + sqlite3RegisterDateTimeFunctions(); + sqlite3RegisterJsonFunctions(); + sqlite3InsertBuiltinFuncs(aBuiltinFunc, ArraySize(aBuiltinFunc)); + +#if 0 /* Enable to print out how the built-in functions are hashed */ + { + int i; + FuncDef *p; + for(i=0; i<SQLITE_FUNC_HASH_SZ; i++){ + printf("FUNC-HASH %02d:", i); + for(p=sqlite3BuiltinFunctions.a[i]; p; p=p->u.pHash){ + int n = sqlite3Strlen30(p->zName); + int h = p->zName[0] + n; + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); + printf(" %s(%d)", p->zName, h); + } + printf("\n"); + } + } +#endif +} + +/************** End of func.c ************************************************/ +/************** Begin file fkey.c ********************************************/ +/* +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used by the compiler to add foreign key +** support to compiled SQL statements. +*/ +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_OMIT_FOREIGN_KEY +#ifndef SQLITE_OMIT_TRIGGER + +/* +** Deferred and Immediate FKs +** -------------------------- +** +** Foreign keys in SQLite come in two flavours: deferred and immediate. +** If an immediate foreign key constraint is violated, +** SQLITE_CONSTRAINT_FOREIGNKEY is returned and the current +** statement transaction rolled back. If a +** deferred foreign key constraint is violated, no action is taken +** immediately. However if the application attempts to commit the +** transaction before fixing the constraint violation, the attempt fails. +** +** Deferred constraints are implemented using a simple counter associated +** with the database handle. The counter is set to zero each time a +** database transaction is opened. Each time a statement is executed +** that causes a foreign key violation, the counter is incremented. Each +** time a statement is executed that removes an existing violation from +** the database, the counter is decremented. When the transaction is +** committed, the commit fails if the current value of the counter is +** greater than zero. This scheme has two big drawbacks: +** +** * When a commit fails due to a deferred foreign key constraint, +** there is no way to tell which foreign constraint is not satisfied, +** or which row it is not satisfied for. +** +** * If the database contains foreign key violations when the +** transaction is opened, this may cause the mechanism to malfunction. +** +** Despite these problems, this approach is adopted as it seems simpler +** than the alternatives. +** +** INSERT operations: +** +** I.1) For each FK for which the table is the child table, search +** the parent table for a match. If none is found increment the +** constraint counter. +** +** I.2) For each FK for which the table is the parent table, +** search the child table for rows that correspond to the new +** row in the parent table. Decrement the counter for each row +** found (as the constraint is now satisfied). +** +** DELETE operations: +** +** D.1) For each FK for which the table is the child table, +** search the parent table for a row that corresponds to the +** deleted row in the child table. If such a row is not found, +** decrement the counter. +** +** D.2) For each FK for which the table is the parent table, search +** the child table for rows that correspond to the deleted row +** in the parent table. For each found increment the counter. +** +** UPDATE operations: +** +** An UPDATE command requires that all 4 steps above are taken, but only +** for FK constraints for which the affected columns are actually +** modified (values must be compared at runtime). +** +** Note that I.1 and D.1 are very similar operations, as are I.2 and D.2. +** This simplifies the implementation a bit. +** +** For the purposes of immediate FK constraints, the OR REPLACE conflict +** resolution is considered to delete rows before the new row is inserted. +** If a delete caused by OR REPLACE violates an FK constraint, an exception +** is thrown, even if the FK constraint would be satisfied after the new +** row is inserted. +** +** Immediate constraints are usually handled similarly. The only difference +** is that the counter used is stored as part of each individual statement +** object (struct Vdbe). If, after the statement has run, its immediate +** constraint counter is greater than zero, +** it returns SQLITE_CONSTRAINT_FOREIGNKEY +** and the statement transaction is rolled back. An exception is an INSERT +** statement that inserts a single row only (no triggers). In this case, +** instead of using a counter, an exception is thrown immediately if the +** INSERT violates a foreign key constraint. This is necessary as such +** an INSERT does not open a statement transaction. +** +** TODO: How should dropping a table be handled? How should renaming a +** table be handled? +** +** +** Query API Notes +** --------------- +** +** Before coding an UPDATE or DELETE row operation, the code-generator +** for those two operations needs to know whether or not the operation +** requires any FK processing and, if so, which columns of the original +** row are required by the FK processing VDBE code (i.e. if FKs were +** implemented using triggers, which of the old.* columns would be +** accessed). No information is required by the code-generator before +** coding an INSERT operation. The functions used by the UPDATE/DELETE +** generation code to query for this information are: +** +** sqlite3FkRequired() - Test to see if FK processing is required. +** sqlite3FkOldmask() - Query for the set of required old.* columns. +** +** +** Externally accessible module functions +** -------------------------------------- +** +** sqlite3FkCheck() - Check for foreign key violations. +** sqlite3FkActions() - Code triggers for ON UPDATE/ON DELETE actions. +** sqlite3FkDelete() - Delete an FKey structure. +*/ + +/* +** VDBE Calling Convention +** ----------------------- +** +** Example: +** +** For the following INSERT statement: +** +** CREATE TABLE t1(a, b INTEGER PRIMARY KEY, c); +** INSERT INTO t1 VALUES(1, 2, 3.1); +** +** Register (x): 2 (type integer) +** Register (x+1): 1 (type integer) +** Register (x+2): NULL (type NULL) +** Register (x+3): 3.1 (type real) +*/ + +/* +** A foreign key constraint requires that the key columns in the parent +** table are collectively subject to a UNIQUE or PRIMARY KEY constraint. +** Given that pParent is the parent table for foreign key constraint pFKey, +** search the schema for a unique index on the parent key columns. +** +** If successful, zero is returned. If the parent key is an INTEGER PRIMARY +** KEY column, then output variable *ppIdx is set to NULL. Otherwise, *ppIdx +** is set to point to the unique index. +** +** If the parent key consists of a single column (the foreign key constraint +** is not a composite foreign key), output variable *paiCol is set to NULL. +** Otherwise, it is set to point to an allocated array of size N, where +** N is the number of columns in the parent key. The first element of the +** array is the index of the child table column that is mapped by the FK +** constraint to the parent table column stored in the left-most column +** of index *ppIdx. The second element of the array is the index of the +** child table column that corresponds to the second left-most column of +** *ppIdx, and so on. +** +** If the required index cannot be found, either because: +** +** 1) The named parent key columns do not exist, or +** +** 2) The named parent key columns do exist, but are not subject to a +** UNIQUE or PRIMARY KEY constraint, or +** +** 3) No parent key columns were provided explicitly as part of the +** foreign key definition, and the parent table does not have a +** PRIMARY KEY, or +** +** 4) No parent key columns were provided explicitly as part of the +** foreign key definition, and the PRIMARY KEY of the parent table +** consists of a different number of columns to the child key in +** the child table. +** +** then non-zero is returned, and a "foreign key mismatch" error loaded +** into pParse. If an OOM error occurs, non-zero is returned and the +** pParse->db->mallocFailed flag is set. +*/ +SQLITE_PRIVATE int sqlite3FkLocateIndex( + Parse *pParse, /* Parse context to store any error in */ + Table *pParent, /* Parent table of FK constraint pFKey */ + FKey *pFKey, /* Foreign key to find index for */ + Index **ppIdx, /* OUT: Unique index on parent table */ + int **paiCol /* OUT: Map of index columns in pFKey */ +){ + Index *pIdx = 0; /* Value to return via *ppIdx */ + int *aiCol = 0; /* Value to return via *paiCol */ + int nCol = pFKey->nCol; /* Number of columns in parent key */ + char *zKey = pFKey->aCol[0].zCol; /* Name of left-most parent key column */ + + /* The caller is responsible for zeroing output parameters. */ + assert( ppIdx && *ppIdx==0 ); + assert( !paiCol || *paiCol==0 ); + assert( pParse ); + + /* If this is a non-composite (single column) foreign key, check if it + ** maps to the INTEGER PRIMARY KEY of table pParent. If so, leave *ppIdx + ** and *paiCol set to zero and return early. + ** + ** Otherwise, for a composite foreign key (more than one column), allocate + ** space for the aiCol array (returned via output parameter *paiCol). + ** Non-composite foreign keys do not require the aiCol array. + */ + if( nCol==1 ){ + /* The FK maps to the IPK if any of the following are true: + ** + ** 1) There is an INTEGER PRIMARY KEY column and the FK is implicitly + ** mapped to the primary key of table pParent, or + ** 2) The FK is explicitly mapped to a column declared as INTEGER + ** PRIMARY KEY. + */ + if( pParent->iPKey>=0 ){ + if( !zKey ) return 0; + if( !sqlite3StrICmp(pParent->aCol[pParent->iPKey].zCnName, zKey) ){ + return 0; + } + } + }else if( paiCol ){ + assert( nCol>1 ); + aiCol = (int *)sqlite3DbMallocRawNN(pParse->db, nCol*sizeof(int)); + if( !aiCol ) return 1; + *paiCol = aiCol; + } + + for(pIdx=pParent->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) && pIdx->pPartIdxWhere==0 ){ + /* pIdx is a UNIQUE index (or a PRIMARY KEY) and has the right number + ** of columns. If each indexed column corresponds to a foreign key + ** column of pFKey, then this index is a winner. */ + + if( zKey==0 ){ + /* If zKey is NULL, then this foreign key is implicitly mapped to + ** the PRIMARY KEY of table pParent. The PRIMARY KEY index may be + ** identified by the test. */ + if( IsPrimaryKeyIndex(pIdx) ){ + if( aiCol ){ + int i; + for(i=0; i<nCol; i++) aiCol[i] = pFKey->aCol[i].iFrom; + } + break; + } + }else{ + /* If zKey is non-NULL, then this foreign key was declared to + ** map to an explicit list of columns in table pParent. Check if this + ** index matches those columns. Also, check that the index uses + ** the default collation sequences for each column. */ + int i, j; + for(i=0; i<nCol; i++){ + i16 iCol = pIdx->aiColumn[i]; /* Index of column in parent tbl */ + const char *zDfltColl; /* Def. collation for column */ + char *zIdxCol; /* Name of indexed column */ + + if( iCol<0 ) break; /* No foreign keys against expression indexes */ + + /* If the index uses a collation sequence that is different from + ** the default collation sequence for the column, this index is + ** unusable. Bail out early in this case. */ + zDfltColl = sqlite3ColumnColl(&pParent->aCol[iCol]); + if( !zDfltColl ) zDfltColl = sqlite3StrBINARY; + if( sqlite3StrICmp(pIdx->azColl[i], zDfltColl) ) break; + + zIdxCol = pParent->aCol[iCol].zCnName; + for(j=0; j<nCol; j++){ + if( sqlite3StrICmp(pFKey->aCol[j].zCol, zIdxCol)==0 ){ + if( aiCol ) aiCol[i] = pFKey->aCol[j].iFrom; + break; + } + } + if( j==nCol ) break; + } + if( i==nCol ) break; /* pIdx is usable */ + } + } + } + + if( !pIdx ){ + if( !pParse->disableTriggers ){ + sqlite3ErrorMsg(pParse, + "foreign key mismatch - \"%w\" referencing \"%w\"", + pFKey->pFrom->zName, pFKey->zTo); + } + sqlite3DbFree(pParse->db, aiCol); + return 1; + } + + *ppIdx = pIdx; + return 0; +} + +/* +** This function is called when a row is inserted into or deleted from the +** child table of foreign key constraint pFKey. If an SQL UPDATE is executed +** on the child table of pFKey, this function is invoked twice for each row +** affected - once to "delete" the old row, and then again to "insert" the +** new row. +** +** Each time it is called, this function generates VDBE code to locate the +** row in the parent table that corresponds to the row being inserted into +** or deleted from the child table. If the parent row can be found, no +** special action is taken. Otherwise, if the parent row can *not* be +** found in the parent table: +** +** Operation | FK type | Action taken +** -------------------------------------------------------------------------- +** INSERT immediate Increment the "immediate constraint counter". +** +** DELETE immediate Decrement the "immediate constraint counter". +** +** INSERT deferred Increment the "deferred constraint counter". +** +** DELETE deferred Decrement the "deferred constraint counter". +** +** These operations are identified in the comment at the top of this file +** (fkey.c) as "I.1" and "D.1". +*/ +static void fkLookupParent( + Parse *pParse, /* Parse context */ + int iDb, /* Index of database housing pTab */ + Table *pTab, /* Parent table of FK pFKey */ + Index *pIdx, /* Unique index on parent key columns in pTab */ + FKey *pFKey, /* Foreign key constraint */ + int *aiCol, /* Map from parent key columns to child table columns */ + int regData, /* Address of array containing child table row */ + int nIncr, /* Increment constraint counter by this */ + int isIgnore /* If true, pretend pTab contains all NULL values */ +){ + int i; /* Iterator variable */ + Vdbe *v = sqlite3GetVdbe(pParse); /* Vdbe to add code to */ + int iCur = pParse->nTab - 1; /* Cursor number to use */ + int iOk = sqlite3VdbeMakeLabel(pParse); /* jump here if parent key found */ + + sqlite3VdbeVerifyAbortable(v, + (!pFKey->isDeferred + && !(pParse->db->flags & SQLITE_DeferFKs) + && !pParse->pToplevel + && !pParse->isMultiWrite) ? OE_Abort : OE_Ignore); + + /* If nIncr is less than zero, then check at runtime if there are any + ** outstanding constraints to resolve. If there are not, there is no need + ** to check if deleting this row resolves any outstanding violations. + ** + ** Check if any of the key columns in the child table row are NULL. If + ** any are, then the constraint is considered satisfied. No need to + ** search for a matching row in the parent table. */ + if( nIncr<0 ){ + sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, iOk); + VdbeCoverage(v); + } + for(i=0; i<pFKey->nCol; i++){ + int iReg = sqlite3TableColumnToStorage(pFKey->pFrom,aiCol[i]) + regData + 1; + sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iOk); VdbeCoverage(v); + } + + if( isIgnore==0 ){ + if( pIdx==0 ){ + /* If pIdx is NULL, then the parent key is the INTEGER PRIMARY KEY + ** column of the parent table (table pTab). */ + int iMustBeInt; /* Address of MustBeInt instruction */ + int regTemp = sqlite3GetTempReg(pParse); + + /* Invoke MustBeInt to coerce the child key value to an integer (i.e. + ** apply the affinity of the parent key). If this fails, then there + ** is no matching parent key. Before using MustBeInt, make a copy of + ** the value. Otherwise, the value inserted into the child key column + ** will have INTEGER affinity applied to it, which may not be correct. */ + sqlite3VdbeAddOp2(v, OP_SCopy, + sqlite3TableColumnToStorage(pFKey->pFrom,aiCol[0])+1+regData, regTemp); + iMustBeInt = sqlite3VdbeAddOp2(v, OP_MustBeInt, regTemp, 0); + VdbeCoverage(v); + + /* If the parent table is the same as the child table, and we are about + ** to increment the constraint-counter (i.e. this is an INSERT operation), + ** then check if the row being inserted matches itself. If so, do not + ** increment the constraint-counter. */ + if( pTab==pFKey->pFrom && nIncr==1 ){ + sqlite3VdbeAddOp3(v, OP_Eq, regData, iOk, regTemp); VdbeCoverage(v); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + } + + sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead); + sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regTemp); VdbeCoverage(v); + sqlite3VdbeGoto(v, iOk); + sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2); + sqlite3VdbeJumpHere(v, iMustBeInt); + sqlite3ReleaseTempReg(pParse, regTemp); + }else{ + int nCol = pFKey->nCol; + int regTemp = sqlite3GetTempRange(pParse, nCol); + + sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + for(i=0; i<nCol; i++){ + sqlite3VdbeAddOp2(v, OP_Copy, + sqlite3TableColumnToStorage(pFKey->pFrom, aiCol[i])+1+regData, + regTemp+i); + } + + /* If the parent table is the same as the child table, and we are about + ** to increment the constraint-counter (i.e. this is an INSERT operation), + ** then check if the row being inserted matches itself. If so, do not + ** increment the constraint-counter. + ** + ** If any of the parent-key values are NULL, then the row cannot match + ** itself. So set JUMPIFNULL to make sure we do the OP_Found if any + ** of the parent-key values are NULL (at this point it is known that + ** none of the child key values are). + */ + if( pTab==pFKey->pFrom && nIncr==1 ){ + int iJump = sqlite3VdbeCurrentAddr(v) + nCol + 1; + for(i=0; i<nCol; i++){ + int iChild = sqlite3TableColumnToStorage(pFKey->pFrom,aiCol[i]) + +1+regData; + int iParent = 1+regData; + iParent += sqlite3TableColumnToStorage(pIdx->pTable, + pIdx->aiColumn[i]); + assert( pIdx->aiColumn[i]>=0 ); + assert( aiCol[i]!=pTab->iPKey ); + if( pIdx->aiColumn[i]==pTab->iPKey ){ + /* The parent key is a composite key that includes the IPK column */ + iParent = regData; + } + sqlite3VdbeAddOp3(v, OP_Ne, iChild, iJump, iParent); VdbeCoverage(v); + sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL); + } + sqlite3VdbeGoto(v, iOk); + } + + sqlite3VdbeAddOp4(v, OP_Affinity, regTemp, nCol, 0, + sqlite3IndexAffinityStr(pParse->db,pIdx), nCol); + sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regTemp, nCol); + VdbeCoverage(v); + sqlite3ReleaseTempRange(pParse, regTemp, nCol); + } + } + + if( !pFKey->isDeferred && !(pParse->db->flags & SQLITE_DeferFKs) + && !pParse->pToplevel + && !pParse->isMultiWrite + ){ + /* Special case: If this is an INSERT statement that will insert exactly + ** one row into the table, raise a constraint immediately instead of + ** incrementing a counter. This is necessary as the VM code is being + ** generated for will not open a statement transaction. */ + assert( nIncr==1 ); + sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY, + OE_Abort, 0, P4_STATIC, P5_ConstraintFK); + }else{ + if( nIncr>0 && pFKey->isDeferred==0 ){ + sqlite3MayAbort(pParse); + } + sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); + } + + sqlite3VdbeResolveLabel(v, iOk); + sqlite3VdbeAddOp1(v, OP_Close, iCur); +} + + +/* +** Return an Expr object that refers to a memory register corresponding +** to column iCol of table pTab. +** +** regBase is the first of an array of register that contains the data +** for pTab. regBase itself holds the rowid. regBase+1 holds the first +** column. regBase+2 holds the second column, and so forth. +*/ +static Expr *exprTableRegister( + Parse *pParse, /* Parsing and code generating context */ + Table *pTab, /* The table whose content is at r[regBase]... */ + int regBase, /* Contents of table pTab */ + i16 iCol /* Which column of pTab is desired */ +){ + Expr *pExpr; + Column *pCol; + const char *zColl; + sqlite3 *db = pParse->db; + + pExpr = sqlite3Expr(db, TK_REGISTER, 0); + if( pExpr ){ + if( iCol>=0 && iCol!=pTab->iPKey ){ + pCol = &pTab->aCol[iCol]; + pExpr->iTable = regBase + sqlite3TableColumnToStorage(pTab,iCol) + 1; + pExpr->affExpr = pCol->affinity; + zColl = sqlite3ColumnColl(pCol); + if( zColl==0 ) zColl = db->pDfltColl->zName; + pExpr = sqlite3ExprAddCollateString(pParse, pExpr, zColl); + }else{ + pExpr->iTable = regBase; + pExpr->affExpr = SQLITE_AFF_INTEGER; + } + } + return pExpr; +} + +/* +** Return an Expr object that refers to column iCol of table pTab which +** has cursor iCur. +*/ +static Expr *exprTableColumn( + sqlite3 *db, /* The database connection */ + Table *pTab, /* The table whose column is desired */ + int iCursor, /* The open cursor on the table */ + i16 iCol /* The column that is wanted */ +){ + Expr *pExpr = sqlite3Expr(db, TK_COLUMN, 0); + if( pExpr ){ + assert( ExprUseYTab(pExpr) ); + pExpr->y.pTab = pTab; + pExpr->iTable = iCursor; + pExpr->iColumn = iCol; + } + return pExpr; +} + +/* +** This function is called to generate code executed when a row is deleted +** from the parent table of foreign key constraint pFKey and, if pFKey is +** deferred, when a row is inserted into the same table. When generating +** code for an SQL UPDATE operation, this function may be called twice - +** once to "delete" the old row and once to "insert" the new row. +** +** Parameter nIncr is passed -1 when inserting a row (as this may decrease +** the number of FK violations in the db) or +1 when deleting one (as this +** may increase the number of FK constraint problems). +** +** The code generated by this function scans through the rows in the child +** table that correspond to the parent table row being deleted or inserted. +** For each child row found, one of the following actions is taken: +** +** Operation | FK type | Action taken +** -------------------------------------------------------------------------- +** DELETE immediate Increment the "immediate constraint counter". +** +** INSERT immediate Decrement the "immediate constraint counter". +** +** DELETE deferred Increment the "deferred constraint counter". +** +** INSERT deferred Decrement the "deferred constraint counter". +** +** These operations are identified in the comment at the top of this file +** (fkey.c) as "I.2" and "D.2". +*/ +static void fkScanChildren( + Parse *pParse, /* Parse context */ + SrcList *pSrc, /* The child table to be scanned */ + Table *pTab, /* The parent table */ + Index *pIdx, /* Index on parent covering the foreign key */ + FKey *pFKey, /* The foreign key linking pSrc to pTab */ + int *aiCol, /* Map from pIdx cols to child table cols */ + int regData, /* Parent row data starts here */ + int nIncr /* Amount to increment deferred counter by */ +){ + sqlite3 *db = pParse->db; /* Database handle */ + int i; /* Iterator variable */ + Expr *pWhere = 0; /* WHERE clause to scan with */ + NameContext sNameContext; /* Context used to resolve WHERE clause */ + WhereInfo *pWInfo; /* Context used by sqlite3WhereXXX() */ + int iFkIfZero = 0; /* Address of OP_FkIfZero */ + Vdbe *v = sqlite3GetVdbe(pParse); + + assert( pIdx==0 || pIdx->pTable==pTab ); + assert( pIdx==0 || pIdx->nKeyCol==pFKey->nCol ); + assert( pIdx!=0 || pFKey->nCol==1 ); + assert( pIdx!=0 || HasRowid(pTab) ); + + if( nIncr<0 ){ + iFkIfZero = sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, 0); + VdbeCoverage(v); + } + + /* Create an Expr object representing an SQL expression like: + ** + ** <parent-key1> = <child-key1> AND <parent-key2> = <child-key2> ... + ** + ** The collation sequence used for the comparison should be that of + ** the parent key columns. The affinity of the parent key column should + ** be applied to each child key value before the comparison takes place. + */ + for(i=0; i<pFKey->nCol; i++){ + Expr *pLeft; /* Value from parent table row */ + Expr *pRight; /* Column ref to child table */ + Expr *pEq; /* Expression (pLeft = pRight) */ + i16 iCol; /* Index of column in child table */ + const char *zCol; /* Name of column in child table */ + + iCol = pIdx ? pIdx->aiColumn[i] : -1; + pLeft = exprTableRegister(pParse, pTab, regData, iCol); + iCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom; + assert( iCol>=0 ); + zCol = pFKey->pFrom->aCol[iCol].zCnName; + pRight = sqlite3Expr(db, TK_ID, zCol); + pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight); + pWhere = sqlite3ExprAnd(pParse, pWhere, pEq); + } + + /* If the child table is the same as the parent table, then add terms + ** to the WHERE clause that prevent this entry from being scanned. + ** The added WHERE clause terms are like this: + ** + ** $current_rowid!=rowid + ** NOT( $current_a==a AND $current_b==b AND ... ) + ** + ** The first form is used for rowid tables. The second form is used + ** for WITHOUT ROWID tables. In the second form, the *parent* key is + ** (a,b,...). Either the parent or primary key could be used to + ** uniquely identify the current row, but the parent key is more convenient + ** as the required values have already been loaded into registers + ** by the caller. + */ + if( pTab==pFKey->pFrom && nIncr>0 ){ + Expr *pNe; /* Expression (pLeft != pRight) */ + Expr *pLeft; /* Value from parent table row */ + Expr *pRight; /* Column ref to child table */ + if( HasRowid(pTab) ){ + pLeft = exprTableRegister(pParse, pTab, regData, -1); + pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, -1); + pNe = sqlite3PExpr(pParse, TK_NE, pLeft, pRight); + }else{ + Expr *pEq, *pAll = 0; + assert( pIdx!=0 ); + for(i=0; i<pIdx->nKeyCol; i++){ + i16 iCol = pIdx->aiColumn[i]; + assert( iCol>=0 ); + pLeft = exprTableRegister(pParse, pTab, regData, iCol); + pRight = sqlite3Expr(db, TK_ID, pTab->aCol[iCol].zCnName); + pEq = sqlite3PExpr(pParse, TK_IS, pLeft, pRight); + pAll = sqlite3ExprAnd(pParse, pAll, pEq); + } + pNe = sqlite3PExpr(pParse, TK_NOT, pAll, 0); + } + pWhere = sqlite3ExprAnd(pParse, pWhere, pNe); + } + + /* Resolve the references in the WHERE clause. */ + memset(&sNameContext, 0, sizeof(NameContext)); + sNameContext.pSrcList = pSrc; + sNameContext.pParse = pParse; + sqlite3ResolveExprNames(&sNameContext, pWhere); + + /* Create VDBE to loop through the entries in pSrc that match the WHERE + ** clause. For each row found, increment either the deferred or immediate + ** foreign key constraint counter. */ + if( pParse->nErr==0 ){ + pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0, 0); + sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); + if( pWInfo ){ + sqlite3WhereEnd(pWInfo); + } + } + + /* Clean up the WHERE clause constructed above. */ + sqlite3ExprDelete(db, pWhere); + if( iFkIfZero ){ + sqlite3VdbeJumpHereOrPopInst(v, iFkIfZero); + } +} + +/* +** This function returns a linked list of FKey objects (connected by +** FKey.pNextTo) holding all children of table pTab. For example, +** given the following schema: +** +** CREATE TABLE t1(a PRIMARY KEY); +** CREATE TABLE t2(b REFERENCES t1(a); +** +** Calling this function with table "t1" as an argument returns a pointer +** to the FKey structure representing the foreign key constraint on table +** "t2". Calling this function with "t2" as the argument would return a +** NULL pointer (as there are no FK constraints for which t2 is the parent +** table). +*/ +SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *pTab){ + return (FKey *)sqlite3HashFind(&pTab->pSchema->fkeyHash, pTab->zName); +} + +/* +** The second argument is a Trigger structure allocated by the +** fkActionTrigger() routine. This function deletes the Trigger structure +** and all of its sub-components. +** +** The Trigger structure or any of its sub-components may be allocated from +** the lookaside buffer belonging to database handle dbMem. +*/ +static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ + if( p ){ + TriggerStep *pStep = p->step_list; + sqlite3ExprDelete(dbMem, pStep->pWhere); + sqlite3ExprListDelete(dbMem, pStep->pExprList); + sqlite3SelectDelete(dbMem, pStep->pSelect); + sqlite3ExprDelete(dbMem, p->pWhen); + sqlite3DbFree(dbMem, p); + } +} + +/* +** Clear the apTrigger[] cache of CASCADE triggers for all foreign keys +** in a particular database. This needs to happen when the schema +** changes. +*/ +SQLITE_PRIVATE void sqlite3FkClearTriggerCache(sqlite3 *db, int iDb){ + HashElem *k; + Hash *pHash = &db->aDb[iDb].pSchema->tblHash; + for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k)){ + Table *pTab = sqliteHashData(k); + FKey *pFKey; + if( !IsOrdinaryTable(pTab) ) continue; + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ + fkTriggerDelete(db, pFKey->apTrigger[0]); pFKey->apTrigger[0] = 0; + fkTriggerDelete(db, pFKey->apTrigger[1]); pFKey->apTrigger[1] = 0; + } + } +} + +/* +** This function is called to generate code that runs when table pTab is +** being dropped from the database. The SrcList passed as the second argument +** to this function contains a single entry guaranteed to resolve to +** table pTab. +** +** Normally, no code is required. However, if either +** +** (a) The table is the parent table of a FK constraint, or +** (b) The table is the child table of a deferred FK constraint and it is +** determined at runtime that there are outstanding deferred FK +** constraint violations in the database, +** +** then the equivalent of "DELETE FROM <tbl>" is executed before dropping +** the table from the database. Triggers are disabled while running this +** DELETE, but foreign key actions are not. +*/ +SQLITE_PRIVATE void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){ + sqlite3 *db = pParse->db; + if( (db->flags&SQLITE_ForeignKeys) && IsOrdinaryTable(pTab) ){ + int iSkip = 0; + Vdbe *v = sqlite3GetVdbe(pParse); + + assert( v ); /* VDBE has already been allocated */ + assert( IsOrdinaryTable(pTab) ); + if( sqlite3FkReferences(pTab)==0 ){ + /* Search for a deferred foreign key constraint for which this table + ** is the child table. If one cannot be found, return without + ** generating any VDBE code. If one can be found, then jump over + ** the entire DELETE if there are no outstanding deferred constraints + ** when this statement is run. */ + FKey *p; + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ + if( p->isDeferred || (db->flags & SQLITE_DeferFKs) ) break; + } + if( !p ) return; + iSkip = sqlite3VdbeMakeLabel(pParse); + sqlite3VdbeAddOp2(v, OP_FkIfZero, 1, iSkip); VdbeCoverage(v); + } + + pParse->disableTriggers = 1; + sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0, 0, 0); + pParse->disableTriggers = 0; + + /* If the DELETE has generated immediate foreign key constraint + ** violations, halt the VDBE and return an error at this point, before + ** any modifications to the schema are made. This is because statement + ** transactions are not able to rollback schema changes. + ** + ** If the SQLITE_DeferFKs flag is set, then this is not required, as + ** the statement transaction will not be rolled back even if FK + ** constraints are violated. + */ + if( (db->flags & SQLITE_DeferFKs)==0 ){ + sqlite3VdbeVerifyAbortable(v, OE_Abort); + sqlite3VdbeAddOp2(v, OP_FkIfZero, 0, sqlite3VdbeCurrentAddr(v)+2); + VdbeCoverage(v); + sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY, + OE_Abort, 0, P4_STATIC, P5_ConstraintFK); + } + + if( iSkip ){ + sqlite3VdbeResolveLabel(v, iSkip); + } + } +} + + +/* +** The second argument points to an FKey object representing a foreign key +** for which pTab is the child table. An UPDATE statement against pTab +** is currently being processed. For each column of the table that is +** actually updated, the corresponding element in the aChange[] array +** is zero or greater (if a column is unmodified the corresponding element +** is set to -1). If the rowid column is modified by the UPDATE statement +** the bChngRowid argument is non-zero. +** +** This function returns true if any of the columns that are part of the +** child key for FK constraint *p are modified. +*/ +static int fkChildIsModified( + Table *pTab, /* Table being updated */ + FKey *p, /* Foreign key for which pTab is the child */ + int *aChange, /* Array indicating modified columns */ + int bChngRowid /* True if rowid is modified by this update */ +){ + int i; + for(i=0; i<p->nCol; i++){ + int iChildKey = p->aCol[i].iFrom; + if( aChange[iChildKey]>=0 ) return 1; + if( iChildKey==pTab->iPKey && bChngRowid ) return 1; + } + return 0; +} + +/* +** The second argument points to an FKey object representing a foreign key +** for which pTab is the parent table. An UPDATE statement against pTab +** is currently being processed. For each column of the table that is +** actually updated, the corresponding element in the aChange[] array +** is zero or greater (if a column is unmodified the corresponding element +** is set to -1). If the rowid column is modified by the UPDATE statement +** the bChngRowid argument is non-zero. +** +** This function returns true if any of the columns that are part of the +** parent key for FK constraint *p are modified. +*/ +static int fkParentIsModified( + Table *pTab, + FKey *p, + int *aChange, + int bChngRowid +){ + int i; + for(i=0; i<p->nCol; i++){ + char *zKey = p->aCol[i].zCol; + int iKey; + for(iKey=0; iKey<pTab->nCol; iKey++){ + if( aChange[iKey]>=0 || (iKey==pTab->iPKey && bChngRowid) ){ + Column *pCol = &pTab->aCol[iKey]; + if( zKey ){ + if( 0==sqlite3StrICmp(pCol->zCnName, zKey) ) return 1; + }else if( pCol->colFlags & COLFLAG_PRIMKEY ){ + return 1; + } + } + } + } + return 0; +} + +/* +** Return true if the parser passed as the first argument is being +** used to code a trigger that is really a "SET NULL" action belonging +** to trigger pFKey. +*/ +static int isSetNullAction(Parse *pParse, FKey *pFKey){ + Parse *pTop = sqlite3ParseToplevel(pParse); + if( pTop->pTriggerPrg ){ + Trigger *p = pTop->pTriggerPrg->pTrigger; + if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull) + || (p==pFKey->apTrigger[1] && pFKey->aAction[1]==OE_SetNull) + ){ + return 1; + } + } + return 0; +} + +/* +** This function is called when inserting, deleting or updating a row of +** table pTab to generate VDBE code to perform foreign key constraint +** processing for the operation. +** +** For a DELETE operation, parameter regOld is passed the index of the +** first register in an array of (pTab->nCol+1) registers containing the +** rowid of the row being deleted, followed by each of the column values +** of the row being deleted, from left to right. Parameter regNew is passed +** zero in this case. +** +** For an INSERT operation, regOld is passed zero and regNew is passed the +** first register of an array of (pTab->nCol+1) registers containing the new +** row data. +** +** For an UPDATE operation, this function is called twice. Once before +** the original record is deleted from the table using the calling convention +** described for DELETE. Then again after the original record is deleted +** but before the new record is inserted using the INSERT convention. +*/ +SQLITE_PRIVATE void sqlite3FkCheck( + Parse *pParse, /* Parse context */ + Table *pTab, /* Row is being deleted from this table */ + int regOld, /* Previous row data is stored here */ + int regNew, /* New row data is stored here */ + int *aChange, /* Array indicating UPDATEd columns (or 0) */ + int bChngRowid /* True if rowid is UPDATEd */ +){ + sqlite3 *db = pParse->db; /* Database handle */ + FKey *pFKey; /* Used to iterate through FKs */ + int iDb; /* Index of database containing pTab */ + const char *zDb; /* Name of database containing pTab */ + int isIgnoreErrors = pParse->disableTriggers; + + /* Exactly one of regOld and regNew should be non-zero. */ + assert( (regOld==0)!=(regNew==0) ); + + /* If foreign-keys are disabled, this function is a no-op. */ + if( (db->flags&SQLITE_ForeignKeys)==0 ) return; + if( !IsOrdinaryTable(pTab) ) return; + + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + zDb = db->aDb[iDb].zDbSName; + + /* Loop through all the foreign key constraints for which pTab is the + ** child table (the table that the foreign key definition is part of). */ + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ + Table *pTo; /* Parent table of foreign key pFKey */ + Index *pIdx = 0; /* Index on key columns in pTo */ + int *aiFree = 0; + int *aiCol; + int iCol; + int i; + int bIgnore = 0; + + if( aChange + && sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0 + && fkChildIsModified(pTab, pFKey, aChange, bChngRowid)==0 + ){ + continue; + } + + /* Find the parent table of this foreign key. Also find a unique index + ** on the parent key columns in the parent table. If either of these + ** schema items cannot be located, set an error in pParse and return + ** early. */ + if( pParse->disableTriggers ){ + pTo = sqlite3FindTable(db, pFKey->zTo, zDb); + }else{ + pTo = sqlite3LocateTable(pParse, 0, pFKey->zTo, zDb); + } + if( !pTo || sqlite3FkLocateIndex(pParse, pTo, pFKey, &pIdx, &aiFree) ){ + assert( isIgnoreErrors==0 || (regOld!=0 && regNew==0) ); + if( !isIgnoreErrors || db->mallocFailed ) return; + if( pTo==0 ){ + /* If isIgnoreErrors is true, then a table is being dropped. In this + ** case SQLite runs a "DELETE FROM xxx" on the table being dropped + ** before actually dropping it in order to check FK constraints. + ** If the parent table of an FK constraint on the current table is + ** missing, behave as if it is empty. i.e. decrement the relevant + ** FK counter for each row of the current table with non-NULL keys. + */ + Vdbe *v = sqlite3GetVdbe(pParse); + int iJump = sqlite3VdbeCurrentAddr(v) + pFKey->nCol + 1; + for(i=0; i<pFKey->nCol; i++){ + int iFromCol, iReg; + iFromCol = pFKey->aCol[i].iFrom; + iReg = sqlite3TableColumnToStorage(pFKey->pFrom,iFromCol) + regOld+1; + sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iJump); VdbeCoverage(v); + } + sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, -1); + } + continue; + } + assert( pFKey->nCol==1 || (aiFree && pIdx) ); + + if( aiFree ){ + aiCol = aiFree; + }else{ + iCol = pFKey->aCol[0].iFrom; + aiCol = &iCol; + } + for(i=0; i<pFKey->nCol; i++){ + if( aiCol[i]==pTab->iPKey ){ + aiCol[i] = -1; + } + assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); +#ifndef SQLITE_OMIT_AUTHORIZATION + /* Request permission to read the parent key columns. If the + ** authorization callback returns SQLITE_IGNORE, behave as if any + ** values read from the parent table are NULL. */ + if( db->xAuth ){ + int rcauth; + char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zCnName; + rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb); + bIgnore = (rcauth==SQLITE_IGNORE); + } +#endif + } + + /* Take a shared-cache advisory read-lock on the parent table. Allocate + ** a cursor to use to search the unique index on the parent key columns + ** in the parent table. */ + sqlite3TableLock(pParse, iDb, pTo->tnum, 0, pTo->zName); + pParse->nTab++; + + if( regOld!=0 ){ + /* A row is being removed from the child table. Search for the parent. + ** If the parent does not exist, removing the child row resolves an + ** outstanding foreign key constraint violation. */ + fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1, bIgnore); + } + if( regNew!=0 && !isSetNullAction(pParse, pFKey) ){ + /* A row is being added to the child table. If a parent row cannot + ** be found, adding the child row has violated the FK constraint. + ** + ** If this operation is being performed as part of a trigger program + ** that is actually a "SET NULL" action belonging to this very + ** foreign key, then omit this scan altogether. As all child key + ** values are guaranteed to be NULL, it is not possible for adding + ** this row to cause an FK violation. */ + fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1, bIgnore); + } + + sqlite3DbFree(db, aiFree); + } + + /* Loop through all the foreign key constraints that refer to this table. + ** (the "child" constraints) */ + for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){ + Index *pIdx = 0; /* Foreign key index for pFKey */ + SrcList *pSrc; + int *aiCol = 0; + + if( aChange && fkParentIsModified(pTab, pFKey, aChange, bChngRowid)==0 ){ + continue; + } + + if( !pFKey->isDeferred && !(db->flags & SQLITE_DeferFKs) + && !pParse->pToplevel && !pParse->isMultiWrite + ){ + assert( regOld==0 && regNew!=0 ); + /* Inserting a single row into a parent table cannot cause (or fix) + ** an immediate foreign key violation. So do nothing in this case. */ + continue; + } + + if( sqlite3FkLocateIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ){ + if( !isIgnoreErrors || db->mallocFailed ) return; + continue; + } + assert( aiCol || pFKey->nCol==1 ); + + /* Create a SrcList structure containing the child table. We need the + ** child table as a SrcList for sqlite3WhereBegin() */ + pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + if( pSrc ){ + SrcItem *pItem = pSrc->a; + pItem->pTab = pFKey->pFrom; + pItem->zName = pFKey->pFrom->zName; + pItem->pTab->nTabRef++; + pItem->iCursor = pParse->nTab++; + + if( regNew!=0 ){ + fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regNew, -1); + } + if( regOld!=0 ){ + int eAction = pFKey->aAction[aChange!=0]; + fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1); + /* If this is a deferred FK constraint, or a CASCADE or SET NULL + ** action applies, then any foreign key violations caused by + ** removing the parent key will be rectified by the action trigger. + ** So do not set the "may-abort" flag in this case. + ** + ** Note 1: If the FK is declared "ON UPDATE CASCADE", then the + ** may-abort flag will eventually be set on this statement anyway + ** (when this function is called as part of processing the UPDATE + ** within the action trigger). + ** + ** Note 2: At first glance it may seem like SQLite could simply omit + ** all OP_FkCounter related scans when either CASCADE or SET NULL + ** applies. The trouble starts if the CASCADE or SET NULL action + ** trigger causes other triggers or action rules attached to the + ** child table to fire. In these cases the fk constraint counters + ** might be set incorrectly if any OP_FkCounter related scans are + ** omitted. */ + if( !pFKey->isDeferred && eAction!=OE_Cascade && eAction!=OE_SetNull ){ + sqlite3MayAbort(pParse); + } + } + pItem->zName = 0; + sqlite3SrcListDelete(db, pSrc); + } + sqlite3DbFree(db, aiCol); + } +} + +#define COLUMN_MASK(x) (((x)>31) ? 0xffffffff : ((u32)1<<(x))) + +/* +** This function is called before generating code to update or delete a +** row contained in table pTab. +*/ +SQLITE_PRIVATE u32 sqlite3FkOldmask( + Parse *pParse, /* Parse context */ + Table *pTab /* Table being modified */ +){ + u32 mask = 0; + if( pParse->db->flags&SQLITE_ForeignKeys && IsOrdinaryTable(pTab) ){ + FKey *p; + int i; + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ + for(i=0; i<p->nCol; i++) mask |= COLUMN_MASK(p->aCol[i].iFrom); + } + for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ + Index *pIdx = 0; + sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0); + if( pIdx ){ + for(i=0; i<pIdx->nKeyCol; i++){ + assert( pIdx->aiColumn[i]>=0 ); + mask |= COLUMN_MASK(pIdx->aiColumn[i]); + } + } + } + } + return mask; +} + + +/* +** This function is called before generating code to update or delete a +** row contained in table pTab. If the operation is a DELETE, then +** parameter aChange is passed a NULL value. For an UPDATE, aChange points +** to an array of size N, where N is the number of columns in table pTab. +** If the i'th column is not modified by the UPDATE, then the corresponding +** entry in the aChange[] array is set to -1. If the column is modified, +** the value is 0 or greater. Parameter chngRowid is set to true if the +** UPDATE statement modifies the rowid fields of the table. +** +** If any foreign key processing will be required, this function returns +** non-zero. If there is no foreign key related processing, this function +** returns zero. +** +** For an UPDATE, this function returns 2 if: +** +** * There are any FKs for which pTab is the child and the parent table +** and any FK processing at all is required (even of a different FK), or +** +** * the UPDATE modifies one or more parent keys for which the action is +** not "NO ACTION" (i.e. is CASCADE, SET DEFAULT or SET NULL). +** +** Or, assuming some other foreign key processing is required, 1. +*/ +SQLITE_PRIVATE int sqlite3FkRequired( + Parse *pParse, /* Parse context */ + Table *pTab, /* Table being modified */ + int *aChange, /* Non-NULL for UPDATE operations */ + int chngRowid /* True for UPDATE that affects rowid */ +){ + int eRet = 1; /* Value to return if bHaveFK is true */ + int bHaveFK = 0; /* If FK processing is required */ + if( pParse->db->flags&SQLITE_ForeignKeys && IsOrdinaryTable(pTab) ){ + if( !aChange ){ + /* A DELETE operation. Foreign key processing is required if the + ** table in question is either the child or parent table for any + ** foreign key constraint. */ + bHaveFK = (sqlite3FkReferences(pTab) || pTab->u.tab.pFKey); + }else{ + /* This is an UPDATE. Foreign key processing is only required if the + ** operation modifies one or more child or parent key columns. */ + FKey *p; + + /* Check if any child key columns are being modified. */ + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ + if( fkChildIsModified(pTab, p, aChange, chngRowid) ){ + if( 0==sqlite3_stricmp(pTab->zName, p->zTo) ) eRet = 2; + bHaveFK = 1; + } + } + + /* Check if any parent key columns are being modified. */ + for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ + if( fkParentIsModified(pTab, p, aChange, chngRowid) ){ + if( p->aAction[1]!=OE_None ) return 2; + bHaveFK = 1; + } + } + } + } + return bHaveFK ? eRet : 0; +} + +/* +** This function is called when an UPDATE or DELETE operation is being +** compiled on table pTab, which is the parent table of foreign-key pFKey. +** If the current operation is an UPDATE, then the pChanges parameter is +** passed a pointer to the list of columns being modified. If it is a +** DELETE, pChanges is passed a NULL pointer. +** +** It returns a pointer to a Trigger structure containing a trigger +** equivalent to the ON UPDATE or ON DELETE action specified by pFKey. +** If the action is "NO ACTION" then a NULL pointer is returned (these actions +** require no special handling by the triggers sub-system, code for them is +** created by fkScanChildren()). +** +** For example, if pFKey is the foreign key and pTab is table "p" in +** the following schema: +** +** CREATE TABLE p(pk PRIMARY KEY); +** CREATE TABLE c(ck REFERENCES p ON DELETE CASCADE); +** +** then the returned trigger structure is equivalent to: +** +** CREATE TRIGGER ... DELETE ON p BEGIN +** DELETE FROM c WHERE ck = old.pk; +** END; +** +** The returned pointer is cached as part of the foreign key object. It +** is eventually freed along with the rest of the foreign key object by +** sqlite3FkDelete(). +*/ +static Trigger *fkActionTrigger( + Parse *pParse, /* Parse context */ + Table *pTab, /* Table being updated or deleted from */ + FKey *pFKey, /* Foreign key to get action for */ + ExprList *pChanges /* Change-list for UPDATE, NULL for DELETE */ +){ + sqlite3 *db = pParse->db; /* Database handle */ + int action; /* One of OE_None, OE_Cascade etc. */ + Trigger *pTrigger; /* Trigger definition to return */ + int iAction = (pChanges!=0); /* 1 for UPDATE, 0 for DELETE */ + + action = pFKey->aAction[iAction]; + if( action==OE_Restrict && (db->flags & SQLITE_DeferFKs) ){ + return 0; + } + pTrigger = pFKey->apTrigger[iAction]; + + if( action!=OE_None && !pTrigger ){ + char const *zFrom; /* Name of child table */ + int nFrom; /* Length in bytes of zFrom */ + Index *pIdx = 0; /* Parent key index for this FK */ + int *aiCol = 0; /* child table cols -> parent key cols */ + TriggerStep *pStep = 0; /* First (only) step of trigger program */ + Expr *pWhere = 0; /* WHERE clause of trigger step */ + ExprList *pList = 0; /* Changes list if ON UPDATE CASCADE */ + Select *pSelect = 0; /* If RESTRICT, "SELECT RAISE(...)" */ + int i; /* Iterator variable */ + Expr *pWhen = 0; /* WHEN clause for the trigger */ + + if( sqlite3FkLocateIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ) return 0; + assert( aiCol || pFKey->nCol==1 ); + + for(i=0; i<pFKey->nCol; i++){ + Token tOld = { "old", 3 }; /* Literal "old" token */ + Token tNew = { "new", 3 }; /* Literal "new" token */ + Token tFromCol; /* Name of column in child table */ + Token tToCol; /* Name of column in parent table */ + int iFromCol; /* Idx of column in child table */ + Expr *pEq; /* tFromCol = OLD.tToCol */ + + iFromCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom; + assert( iFromCol>=0 ); + assert( pIdx!=0 || (pTab->iPKey>=0 && pTab->iPKey<pTab->nCol) ); + assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); + sqlite3TokenInit(&tToCol, + pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zCnName); + sqlite3TokenInit(&tFromCol, pFKey->pFrom->aCol[iFromCol].zCnName); + + /* Create the expression "OLD.zToCol = zFromCol". It is important + ** that the "OLD.zToCol" term is on the LHS of the = operator, so + ** that the affinity and collation sequence associated with the + ** parent table are used for the comparison. */ + pEq = sqlite3PExpr(pParse, TK_EQ, + sqlite3PExpr(pParse, TK_DOT, + sqlite3ExprAlloc(db, TK_ID, &tOld, 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)), + sqlite3ExprAlloc(db, TK_ID, &tFromCol, 0) + ); + pWhere = sqlite3ExprAnd(pParse, pWhere, pEq); + + /* For ON UPDATE, construct the next term of the WHEN clause. + ** The final WHEN clause will be like this: + ** + ** WHEN NOT(old.col1 IS new.col1 AND ... AND old.colN IS new.colN) + */ + if( pChanges ){ + pEq = sqlite3PExpr(pParse, TK_IS, + sqlite3PExpr(pParse, TK_DOT, + sqlite3ExprAlloc(db, TK_ID, &tOld, 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)), + sqlite3PExpr(pParse, TK_DOT, + sqlite3ExprAlloc(db, TK_ID, &tNew, 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)) + ); + pWhen = sqlite3ExprAnd(pParse, pWhen, pEq); + } + + if( action!=OE_Restrict && (action!=OE_Cascade || pChanges) ){ + Expr *pNew; + if( action==OE_Cascade ){ + pNew = sqlite3PExpr(pParse, TK_DOT, + sqlite3ExprAlloc(db, TK_ID, &tNew, 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)); + }else if( action==OE_SetDflt ){ + Column *pCol = pFKey->pFrom->aCol + iFromCol; + Expr *pDflt; + if( pCol->colFlags & COLFLAG_GENERATED ){ + testcase( pCol->colFlags & COLFLAG_VIRTUAL ); + testcase( pCol->colFlags & COLFLAG_STORED ); + pDflt = 0; + }else{ + pDflt = sqlite3ColumnExpr(pFKey->pFrom, pCol); + } + if( pDflt ){ + pNew = sqlite3ExprDup(db, pDflt, 0); + }else{ + pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0); + } + }else{ + pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0); + } + pList = sqlite3ExprListAppend(pParse, pList, pNew); + sqlite3ExprListSetName(pParse, pList, &tFromCol, 0); + } + } + sqlite3DbFree(db, aiCol); + + zFrom = pFKey->pFrom->zName; + nFrom = sqlite3Strlen30(zFrom); + + if( action==OE_Restrict ){ + int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + SrcList *pSrc; + Expr *pRaise; + + pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed"); + if( pRaise ){ + pRaise->affExpr = OE_Abort; + } + pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + if( pSrc ){ + assert( pSrc->nSrc==1 ); + pSrc->a[0].zName = sqlite3DbStrDup(db, zFrom); + pSrc->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName); + } + pSelect = sqlite3SelectNew(pParse, + sqlite3ExprListAppend(pParse, 0, pRaise), + pSrc, + pWhere, + 0, 0, 0, 0, 0 + ); + pWhere = 0; + } + + /* Disable lookaside memory allocation */ + DisableLookaside; + + pTrigger = (Trigger *)sqlite3DbMallocZero(db, + sizeof(Trigger) + /* struct Trigger */ + sizeof(TriggerStep) + /* Single step in trigger program */ + nFrom + 1 /* Space for pStep->zTarget */ + ); + if( pTrigger ){ + pStep = pTrigger->step_list = (TriggerStep *)&pTrigger[1]; + pStep->zTarget = (char *)&pStep[1]; + memcpy((char *)pStep->zTarget, zFrom, nFrom); + + pStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); + pStep->pExprList = sqlite3ExprListDup(db, pList, EXPRDUP_REDUCE); + pStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); + if( pWhen ){ + pWhen = sqlite3PExpr(pParse, TK_NOT, pWhen, 0); + pTrigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE); + } + } + + /* Re-enable the lookaside buffer, if it was disabled earlier. */ + EnableLookaside; + + sqlite3ExprDelete(db, pWhere); + sqlite3ExprDelete(db, pWhen); + sqlite3ExprListDelete(db, pList); + sqlite3SelectDelete(db, pSelect); + if( db->mallocFailed==1 ){ + fkTriggerDelete(db, pTrigger); + return 0; + } + assert( pStep!=0 ); + assert( pTrigger!=0 ); + + switch( action ){ + case OE_Restrict: + pStep->op = TK_SELECT; + break; + case OE_Cascade: + if( !pChanges ){ + pStep->op = TK_DELETE; + break; + } + /* no break */ deliberate_fall_through + default: + pStep->op = TK_UPDATE; + } + pStep->pTrig = pTrigger; + pTrigger->pSchema = pTab->pSchema; + pTrigger->pTabSchema = pTab->pSchema; + pFKey->apTrigger[iAction] = pTrigger; + pTrigger->op = (pChanges ? TK_UPDATE : TK_DELETE); + } + + return pTrigger; +} + +/* +** This function is called when deleting or updating a row to implement +** any required CASCADE, SET NULL or SET DEFAULT actions. +*/ +SQLITE_PRIVATE void sqlite3FkActions( + Parse *pParse, /* Parse context */ + Table *pTab, /* Table being updated or deleted from */ + ExprList *pChanges, /* Change-list for UPDATE, NULL for DELETE */ + int regOld, /* Address of array containing old row */ + int *aChange, /* Array indicating UPDATEd columns (or 0) */ + int bChngRowid /* True if rowid is UPDATEd */ +){ + /* If foreign-key support is enabled, iterate through all FKs that + ** refer to table pTab. If there is an action associated with the FK + ** for this operation (either update or delete), invoke the associated + ** trigger sub-program. */ + if( pParse->db->flags&SQLITE_ForeignKeys ){ + FKey *pFKey; /* Iterator variable */ + for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){ + if( aChange==0 || fkParentIsModified(pTab, pFKey, aChange, bChngRowid) ){ + Trigger *pAct = fkActionTrigger(pParse, pTab, pFKey, pChanges); + if( pAct ){ + sqlite3CodeRowTriggerDirect(pParse, pAct, pTab, regOld, OE_Abort, 0); + } + } + } + } +} + +#endif /* ifndef SQLITE_OMIT_TRIGGER */ + +/* +** Free all memory associated with foreign key definitions attached to +** table pTab. Remove the deleted foreign keys from the Schema.fkeyHash +** hash table. +*/ +SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *db, Table *pTab){ + FKey *pFKey; /* Iterator variable */ + FKey *pNext; /* Copy of pFKey->pNextFrom */ + + assert( IsOrdinaryTable(pTab) ); + assert( db!=0 ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pNext){ + assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) ); + + /* Remove the FK from the fkeyHash hash table. */ + if( db->pnBytesFreed==0 ){ + if( pFKey->pPrevTo ){ + pFKey->pPrevTo->pNextTo = pFKey->pNextTo; + }else{ + const char *z = (pFKey->pNextTo ? pFKey->pNextTo->zTo : pFKey->zTo); + sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, pFKey->pNextTo); + } + if( pFKey->pNextTo ){ + pFKey->pNextTo->pPrevTo = pFKey->pPrevTo; + } + } + + /* EV: R-30323-21917 Each foreign key constraint in SQLite is + ** classified as either immediate or deferred. + */ + assert( pFKey->isDeferred==0 || pFKey->isDeferred==1 ); + + /* Delete any triggers created to implement actions for this FK. */ +#ifndef SQLITE_OMIT_TRIGGER + fkTriggerDelete(db, pFKey->apTrigger[0]); + fkTriggerDelete(db, pFKey->apTrigger[1]); +#endif + + pNext = pFKey->pNextFrom; + sqlite3DbFree(db, pFKey); + } +} +#endif /* ifndef SQLITE_OMIT_FOREIGN_KEY */ + +/************** End of fkey.c ************************************************/ +/************** Begin file insert.c ******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle INSERT statements in SQLite. +*/ +/* #include "sqliteInt.h" */ + +/* +** Generate code that will +** +** (1) acquire a lock for table pTab then +** (2) open pTab as cursor iCur. +** +** If pTab is a WITHOUT ROWID table, then it is the PRIMARY KEY index +** for that table that is actually opened. +*/ +SQLITE_PRIVATE void sqlite3OpenTable( + Parse *pParse, /* Generate code into this VDBE */ + int iCur, /* The cursor number of the table */ + int iDb, /* The database index in sqlite3.aDb[] */ + Table *pTab, /* The table to be opened */ + int opcode /* OP_OpenRead or OP_OpenWrite */ +){ + Vdbe *v; + assert( !IsVirtual(pTab) ); + assert( pParse->pVdbe!=0 ); + v = pParse->pVdbe; + assert( opcode==OP_OpenWrite || opcode==OP_OpenRead ); + if( !pParse->db->noSharedCache ){ + sqlite3TableLock(pParse, iDb, pTab->tnum, + (opcode==OP_OpenWrite)?1:0, pTab->zName); + } + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp4Int(v, opcode, iCur, pTab->tnum, iDb, pTab->nNVCol); + VdbeComment((v, "%s", pTab->zName)); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->tnum==pTab->tnum || CORRUPT_DB ); + sqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + VdbeComment((v, "%s", pTab->zName)); + } +} + +/* +** Return a pointer to the column affinity string associated with index +** pIdx. A column affinity string has one character for each column in +** the table, according to the affinity of the column: +** +** Character Column affinity +** ------------------------------ +** 'A' BLOB +** 'B' TEXT +** 'C' NUMERIC +** 'D' INTEGER +** 'F' REAL +** +** An extra 'D' is appended to the end of the string to cover the +** rowid that appears as the last column in every index. +** +** Memory for the buffer containing the column index affinity string +** is managed along with the rest of the Index structure. It will be +** released when sqlite3DeleteIndex() is called. +*/ +static SQLITE_NOINLINE const char *computeIndexAffStr(sqlite3 *db, Index *pIdx){ + /* The first time a column affinity string for a particular index is + ** required, it is allocated and populated here. It is then stored as + ** a member of the Index structure for subsequent use. + ** + ** The column affinity string will eventually be deleted by + ** sqliteDeleteIndex() when the Index structure itself is cleaned + ** up. + */ + int n; + Table *pTab = pIdx->pTable; + pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+1); + if( !pIdx->zColAff ){ + sqlite3OomFault(db); + return 0; + } + for(n=0; n<pIdx->nColumn; n++){ + i16 x = pIdx->aiColumn[n]; + char aff; + if( x>=0 ){ + aff = pTab->aCol[x].affinity; + }else if( x==XN_ROWID ){ + aff = SQLITE_AFF_INTEGER; + }else{ + assert( x==XN_EXPR ); + assert( pIdx->bHasExpr ); + assert( pIdx->aColExpr!=0 ); + aff = sqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr); + } + if( aff<SQLITE_AFF_BLOB ) aff = SQLITE_AFF_BLOB; + if( aff>SQLITE_AFF_NUMERIC) aff = SQLITE_AFF_NUMERIC; + pIdx->zColAff[n] = aff; + } + pIdx->zColAff[n] = 0; + return pIdx->zColAff; +} +SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ + if( !pIdx->zColAff ) return computeIndexAffStr(db, pIdx); + return pIdx->zColAff; +} + + +/* +** Compute an affinity string for a table. Space is obtained +** from sqlite3DbMalloc(). The caller is responsible for freeing +** the space when done. +*/ +SQLITE_PRIVATE char *sqlite3TableAffinityStr(sqlite3 *db, const Table *pTab){ + char *zColAff; + zColAff = (char *)sqlite3DbMallocRaw(db, pTab->nCol+1); + if( zColAff ){ + int i, j; + for(i=j=0; i<pTab->nCol; i++){ + if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){ + zColAff[j++] = pTab->aCol[i].affinity; + } + } + do{ + zColAff[j--] = 0; + }while( j>=0 && zColAff[j]<=SQLITE_AFF_BLOB ); + } + return zColAff; +} + +/* +** Make changes to the evolving bytecode to do affinity transformations +** of values that are about to be gathered into a row for table pTab. +** +** For ordinary (legacy, non-strict) tables: +** ----------------------------------------- +** +** Compute the affinity string for table pTab, if it has not already been +** computed. As an optimization, omit trailing SQLITE_AFF_BLOB affinities. +** +** If the affinity string is empty (because it was all SQLITE_AFF_BLOB entries +** which were then optimized out) then this routine becomes a no-op. +** +** Otherwise if iReg>0 then code an OP_Affinity opcode that will set the +** affinities for register iReg and following. Or if iReg==0, +** then just set the P4 operand of the previous opcode (which should be +** an OP_MakeRecord) to the affinity string. +** +** A column affinity string has one character per column: +** +** Character Column affinity +** --------- --------------- +** 'A' BLOB +** 'B' TEXT +** 'C' NUMERIC +** 'D' INTEGER +** 'E' REAL +** +** For STRICT tables: +** ------------------ +** +** Generate an appropriate OP_TypeCheck opcode that will verify the +** datatypes against the column definitions in pTab. If iReg==0, that +** means an OP_MakeRecord opcode has already been generated and should be +** the last opcode generated. The new OP_TypeCheck needs to be inserted +** before the OP_MakeRecord. The new OP_TypeCheck should use the same +** register set as the OP_MakeRecord. If iReg>0 then register iReg is +** the first of a series of registers that will form the new record. +** Apply the type checking to that array of registers. +*/ +SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ + int i; + char *zColAff; + if( pTab->tabFlags & TF_Strict ){ + if( iReg==0 ){ + /* Move the previous opcode (which should be OP_MakeRecord) forward + ** by one slot and insert a new OP_TypeCheck where the current + ** OP_MakeRecord is found */ + VdbeOp *pPrev; + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + pPrev = sqlite3VdbeGetLastOp(v); + assert( pPrev!=0 ); + assert( pPrev->opcode==OP_MakeRecord || sqlite3VdbeDb(v)->mallocFailed ); + pPrev->opcode = OP_TypeCheck; + sqlite3VdbeAddOp3(v, OP_MakeRecord, pPrev->p1, pPrev->p2, pPrev->p3); + }else{ + /* Insert an isolated OP_Typecheck */ + sqlite3VdbeAddOp2(v, OP_TypeCheck, iReg, pTab->nNVCol); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + } + return; + } + zColAff = pTab->zColAff; + if( zColAff==0 ){ + zColAff = sqlite3TableAffinityStr(0, pTab); + if( !zColAff ){ + sqlite3OomFault(sqlite3VdbeDb(v)); + return; + } + pTab->zColAff = zColAff; + } + assert( zColAff!=0 ); + i = sqlite3Strlen30NN(zColAff); + if( i ){ + if( iReg ){ + sqlite3VdbeAddOp4(v, OP_Affinity, iReg, i, 0, zColAff, i); + }else{ + assert( sqlite3VdbeGetLastOp(v)->opcode==OP_MakeRecord + || sqlite3VdbeDb(v)->mallocFailed ); + sqlite3VdbeChangeP4(v, -1, zColAff, i); + } + } +} + +/* +** Return non-zero if the table pTab in database iDb or any of its indices +** have been opened at any point in the VDBE program. This is used to see if +** a statement of the form "INSERT INTO <iDb, pTab> SELECT ..." can +** run without using a temporary table for the results of the SELECT. +*/ +static int readsTable(Parse *p, int iDb, Table *pTab){ + Vdbe *v = sqlite3GetVdbe(p); + int i; + int iEnd = sqlite3VdbeCurrentAddr(v); +#ifndef SQLITE_OMIT_VIRTUALTABLE + VTable *pVTab = IsVirtual(pTab) ? sqlite3GetVTable(p->db, pTab) : 0; +#endif + + for(i=1; i<iEnd; i++){ + VdbeOp *pOp = sqlite3VdbeGetOp(v, i); + assert( pOp!=0 ); + if( pOp->opcode==OP_OpenRead && pOp->p3==iDb ){ + Index *pIndex; + Pgno tnum = pOp->p2; + if( tnum==pTab->tnum ){ + return 1; + } + for(pIndex=pTab->pIndex; pIndex; pIndex=pIndex->pNext){ + if( tnum==pIndex->tnum ){ + return 1; + } + } + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( pOp->opcode==OP_VOpen && pOp->p4.pVtab==pVTab ){ + assert( pOp->p4.pVtab!=0 ); + assert( pOp->p4type==P4_VTAB ); + return 1; + } +#endif + } + return 0; +} + +/* This walker callback will compute the union of colFlags flags for all +** referenced columns in a CHECK constraint or generated column expression. +*/ +static int exprColumnFlagUnion(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN && pExpr->iColumn>=0 ){ + assert( pExpr->iColumn < pWalker->u.pTab->nCol ); + pWalker->eCode |= pWalker->u.pTab->aCol[pExpr->iColumn].colFlags; + } + return WRC_Continue; +} + +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +/* +** All regular columns for table pTab have been puts into registers +** starting with iRegStore. The registers that correspond to STORED +** or VIRTUAL columns have not yet been initialized. This routine goes +** back and computes the values for those columns based on the previously +** computed normal columns. +*/ +SQLITE_PRIVATE void sqlite3ComputeGeneratedColumns( + Parse *pParse, /* Parsing context */ + int iRegStore, /* Register holding the first column */ + Table *pTab /* The table */ +){ + int i; + Walker w; + Column *pRedo; + int eProgress; + VdbeOp *pOp; + + assert( pTab->tabFlags & TF_HasGenerated ); + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + + /* Before computing generated columns, first go through and make sure + ** that appropriate affinity has been applied to the regular columns + */ + sqlite3TableAffinity(pParse->pVdbe, pTab, iRegStore); + if( (pTab->tabFlags & TF_HasStored)!=0 ){ + pOp = sqlite3VdbeGetLastOp(pParse->pVdbe); + if( pOp->opcode==OP_Affinity ){ + /* Change the OP_Affinity argument to '@' (NONE) for all stored + ** columns. '@' is the no-op affinity and those columns have not + ** yet been computed. */ + int ii, jj; + char *zP4 = pOp->p4.z; + assert( zP4!=0 ); + assert( pOp->p4type==P4_DYNAMIC ); + for(ii=jj=0; zP4[jj]; ii++){ + if( pTab->aCol[ii].colFlags & COLFLAG_VIRTUAL ){ + continue; + } + if( pTab->aCol[ii].colFlags & COLFLAG_STORED ){ + zP4[jj] = SQLITE_AFF_NONE; + } + jj++; + } + }else if( pOp->opcode==OP_TypeCheck ){ + /* If an OP_TypeCheck was generated because the table is STRICT, + ** then set the P3 operand to indicate that generated columns should + ** not be checked */ + pOp->p3 = 1; + } + } + + /* Because there can be multiple generated columns that refer to one another, + ** this is a two-pass algorithm. On the first pass, mark all generated + ** columns as "not available". + */ + for(i=0; i<pTab->nCol; i++){ + if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){ + testcase( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ); + testcase( pTab->aCol[i].colFlags & COLFLAG_STORED ); + pTab->aCol[i].colFlags |= COLFLAG_NOTAVAIL; + } + } + + w.u.pTab = pTab; + w.xExprCallback = exprColumnFlagUnion; + w.xSelectCallback = 0; + w.xSelectCallback2 = 0; + + /* On the second pass, compute the value of each NOT-AVAILABLE column. + ** Companion code in the TK_COLUMN case of sqlite3ExprCodeTarget() will + ** compute dependencies and mark remove the COLSPAN_NOTAVAIL mark, as + ** they are needed. + */ + pParse->iSelfTab = -iRegStore; + do{ + eProgress = 0; + pRedo = 0; + for(i=0; i<pTab->nCol; i++){ + Column *pCol = pTab->aCol + i; + if( (pCol->colFlags & COLFLAG_NOTAVAIL)!=0 ){ + int x; + pCol->colFlags |= COLFLAG_BUSY; + w.eCode = 0; + sqlite3WalkExpr(&w, sqlite3ColumnExpr(pTab, pCol)); + pCol->colFlags &= ~COLFLAG_BUSY; + if( w.eCode & COLFLAG_NOTAVAIL ){ + pRedo = pCol; + continue; + } + eProgress = 1; + assert( pCol->colFlags & COLFLAG_GENERATED ); + x = sqlite3TableColumnToStorage(pTab, i) + iRegStore; + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, x); + pCol->colFlags &= ~COLFLAG_NOTAVAIL; + } + } + }while( pRedo && eProgress ); + if( pRedo ){ + sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", pRedo->zCnName); + } + pParse->iSelfTab = 0; +} +#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ + + +#ifndef SQLITE_OMIT_AUTOINCREMENT +/* +** Locate or create an AutoincInfo structure associated with table pTab +** which is in database iDb. Return the register number for the register +** that holds the maximum rowid. Return zero if pTab is not an AUTOINCREMENT +** table. (Also return zero when doing a VACUUM since we do not want to +** update the AUTOINCREMENT counters during a VACUUM.) +** +** There is at most one AutoincInfo structure per table even if the +** same table is autoincremented multiple times due to inserts within +** triggers. A new AutoincInfo structure is created if this is the +** first use of table pTab. On 2nd and subsequent uses, the original +** AutoincInfo structure is used. +** +** Four consecutive registers are allocated: +** +** (1) The name of the pTab table. +** (2) The maximum ROWID of pTab. +** (3) The rowid in sqlite_sequence of pTab +** (4) The original value of the max ROWID in pTab, or NULL if none +** +** The 2nd register is the one that is returned. That is all the +** insert routine needs to know about. +*/ +static int autoIncBegin( + Parse *pParse, /* Parsing context */ + int iDb, /* Index of the database holding pTab */ + Table *pTab /* The table we are writing to */ +){ + int memId = 0; /* Register holding maximum rowid */ + assert( pParse->db->aDb[iDb].pSchema!=0 ); + if( (pTab->tabFlags & TF_Autoincrement)!=0 + && (pParse->db->mDbFlags & DBFLAG_Vacuum)==0 + ){ + Parse *pToplevel = sqlite3ParseToplevel(pParse); + AutoincInfo *pInfo; + Table *pSeqTab = pParse->db->aDb[iDb].pSchema->pSeqTab; + + /* Verify that the sqlite_sequence table exists and is an ordinary + ** rowid table with exactly two columns. + ** Ticket d8dc2b3a58cd5dc2918a1d4acb 2018-05-23 */ + if( pSeqTab==0 + || !HasRowid(pSeqTab) + || NEVER(IsVirtual(pSeqTab)) + || pSeqTab->nCol!=2 + ){ + pParse->nErr++; + pParse->rc = SQLITE_CORRUPT_SEQUENCE; + return 0; + } + + pInfo = pToplevel->pAinc; + while( pInfo && pInfo->pTab!=pTab ){ pInfo = pInfo->pNext; } + if( pInfo==0 ){ + pInfo = sqlite3DbMallocRawNN(pParse->db, sizeof(*pInfo)); + sqlite3ParserAddCleanup(pToplevel, sqlite3DbFree, pInfo); + testcase( pParse->earlyCleanup ); + if( pParse->db->mallocFailed ) return 0; + pInfo->pNext = pToplevel->pAinc; + pToplevel->pAinc = pInfo; + pInfo->pTab = pTab; + pInfo->iDb = iDb; + pToplevel->nMem++; /* Register to hold name of table */ + pInfo->regCtr = ++pToplevel->nMem; /* Max rowid register */ + pToplevel->nMem +=2; /* Rowid in sqlite_sequence + orig max val */ + } + memId = pInfo->regCtr; + } + return memId; +} + +/* +** This routine generates code that will initialize all of the +** register used by the autoincrement tracker. +*/ +SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse){ + AutoincInfo *p; /* Information about an AUTOINCREMENT */ + sqlite3 *db = pParse->db; /* The database connection */ + Db *pDb; /* Database only autoinc table */ + int memId; /* Register holding max rowid */ + Vdbe *v = pParse->pVdbe; /* VDBE under construction */ + + /* This routine is never called during trigger-generation. It is + ** only called from the top-level */ + assert( pParse->pTriggerTab==0 ); + assert( sqlite3IsToplevel(pParse) ); + + assert( v ); /* We failed long ago if this is not so */ + for(p = pParse->pAinc; p; p = p->pNext){ + static const int iLn = VDBE_OFFSET_LINENO(2); + static const VdbeOpList autoInc[] = { + /* 0 */ {OP_Null, 0, 0, 0}, + /* 1 */ {OP_Rewind, 0, 10, 0}, + /* 2 */ {OP_Column, 0, 0, 0}, + /* 3 */ {OP_Ne, 0, 9, 0}, + /* 4 */ {OP_Rowid, 0, 0, 0}, + /* 5 */ {OP_Column, 0, 1, 0}, + /* 6 */ {OP_AddImm, 0, 0, 0}, + /* 7 */ {OP_Copy, 0, 0, 0}, + /* 8 */ {OP_Goto, 0, 11, 0}, + /* 9 */ {OP_Next, 0, 2, 0}, + /* 10 */ {OP_Integer, 0, 0, 0}, + /* 11 */ {OP_Close, 0, 0, 0} + }; + VdbeOp *aOp; + pDb = &db->aDb[p->iDb]; + memId = p->regCtr; + assert( sqlite3SchemaMutexHeld(db, 0, pDb->pSchema) ); + sqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenRead); + sqlite3VdbeLoadString(v, memId-1, p->pTab->zName); + aOp = sqlite3VdbeAddOpList(v, ArraySize(autoInc), autoInc, iLn); + if( aOp==0 ) break; + aOp[0].p2 = memId; + aOp[0].p3 = memId+2; + aOp[2].p3 = memId; + aOp[3].p1 = memId-1; + aOp[3].p3 = memId; + aOp[3].p5 = SQLITE_JUMPIFNULL; + aOp[4].p2 = memId+1; + aOp[5].p3 = memId; + aOp[6].p1 = memId; + aOp[7].p2 = memId+2; + aOp[7].p1 = memId; + aOp[10].p2 = memId; + if( pParse->nTab==0 ) pParse->nTab = 1; + } +} + +/* +** Update the maximum rowid for an autoincrement calculation. +** +** This routine should be called when the regRowid register holds a +** new rowid that is about to be inserted. If that new rowid is +** larger than the maximum rowid in the memId memory cell, then the +** memory cell is updated. +*/ +static void autoIncStep(Parse *pParse, int memId, int regRowid){ + if( memId>0 ){ + sqlite3VdbeAddOp2(pParse->pVdbe, OP_MemMax, memId, regRowid); + } +} + +/* +** This routine generates the code needed to write autoincrement +** maximum rowid values back into the sqlite_sequence register. +** Every statement that might do an INSERT into an autoincrement +** table (either directly or through triggers) needs to call this +** routine just before the "exit" code. +*/ +static SQLITE_NOINLINE void autoIncrementEnd(Parse *pParse){ + AutoincInfo *p; + Vdbe *v = pParse->pVdbe; + sqlite3 *db = pParse->db; + + assert( v ); + for(p = pParse->pAinc; p; p = p->pNext){ + static const int iLn = VDBE_OFFSET_LINENO(2); + static const VdbeOpList autoIncEnd[] = { + /* 0 */ {OP_NotNull, 0, 2, 0}, + /* 1 */ {OP_NewRowid, 0, 0, 0}, + /* 2 */ {OP_MakeRecord, 0, 2, 0}, + /* 3 */ {OP_Insert, 0, 0, 0}, + /* 4 */ {OP_Close, 0, 0, 0} + }; + VdbeOp *aOp; + Db *pDb = &db->aDb[p->iDb]; + int iRec; + int memId = p->regCtr; + + iRec = sqlite3GetTempReg(pParse); + assert( sqlite3SchemaMutexHeld(db, 0, pDb->pSchema) ); + sqlite3VdbeAddOp3(v, OP_Le, memId+2, sqlite3VdbeCurrentAddr(v)+7, memId); + VdbeCoverage(v); + sqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenWrite); + aOp = sqlite3VdbeAddOpList(v, ArraySize(autoIncEnd), autoIncEnd, iLn); + if( aOp==0 ) break; + aOp[0].p1 = memId+1; + aOp[1].p2 = memId+1; + aOp[2].p1 = memId-1; + aOp[2].p3 = iRec; + aOp[3].p2 = iRec; + aOp[3].p3 = memId+1; + aOp[3].p5 = OPFLAG_APPEND; + sqlite3ReleaseTempReg(pParse, iRec); + } +} +SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse){ + if( pParse->pAinc ) autoIncrementEnd(pParse); +} +#else +/* +** If SQLITE_OMIT_AUTOINCREMENT is defined, then the three routines +** above are all no-ops +*/ +# define autoIncBegin(A,B,C) (0) +# define autoIncStep(A,B,C) +#endif /* SQLITE_OMIT_AUTOINCREMENT */ + + +/* Forward declaration */ +static int xferOptimization( + Parse *pParse, /* Parser context */ + Table *pDest, /* The table we are inserting into */ + Select *pSelect, /* A SELECT statement to use as the data source */ + int onError, /* How to handle constraint errors */ + int iDbDest /* The database of pDest */ +); + +/* +** This routine is called to handle SQL of the following forms: +** +** insert into TABLE (IDLIST) values(EXPRLIST),(EXPRLIST),... +** insert into TABLE (IDLIST) select +** insert into TABLE (IDLIST) default values +** +** The IDLIST following the table name is always optional. If omitted, +** then a list of all (non-hidden) columns for the table is substituted. +** The IDLIST appears in the pColumn parameter. pColumn is NULL if IDLIST +** is omitted. +** +** For the pSelect parameter holds the values to be inserted for the +** first two forms shown above. A VALUES clause is really just short-hand +** for a SELECT statement that omits the FROM clause and everything else +** that follows. If the pSelect parameter is NULL, that means that the +** DEFAULT VALUES form of the INSERT statement is intended. +** +** The code generated follows one of four templates. For a simple +** insert with data coming from a single-row VALUES clause, the code executes +** once straight down through. Pseudo-code follows (we call this +** the "1st template"): +** +** open write cursor to <table> and its indices +** put VALUES clause expressions into registers +** write the resulting record into <table> +** cleanup +** +** The three remaining templates assume the statement is of the form +** +** INSERT INTO <table> SELECT ... +** +** If the SELECT clause is of the restricted form "SELECT * FROM <table2>" - +** in other words if the SELECT pulls all columns from a single table +** and there is no WHERE or LIMIT or GROUP BY or ORDER BY clauses, and +** if <table2> and <table1> are distinct tables but have identical +** schemas, including all the same indices, then a special optimization +** is invoked that copies raw records from <table2> over to <table1>. +** See the xferOptimization() function for the implementation of this +** template. This is the 2nd template. +** +** open a write cursor to <table> +** open read cursor on <table2> +** transfer all records in <table2> over to <table> +** close cursors +** foreach index on <table> +** open a write cursor on the <table> index +** open a read cursor on the corresponding <table2> index +** transfer all records from the read to the write cursors +** close cursors +** end foreach +** +** The 3rd template is for when the second template does not apply +** and the SELECT clause does not read from <table> at any time. +** The generated code follows this template: +** +** X <- A +** goto B +** A: setup for the SELECT +** loop over the rows in the SELECT +** load values into registers R..R+n +** yield X +** end loop +** cleanup after the SELECT +** end-coroutine X +** B: open write cursor to <table> and its indices +** C: yield X, at EOF goto D +** insert the select result into <table> from R..R+n +** goto C +** D: cleanup +** +** The 4th template is used if the insert statement takes its +** values from a SELECT but the data is being inserted into a table +** that is also read as part of the SELECT. In the third form, +** we have to use an intermediate table to store the results of +** the select. The template is like this: +** +** X <- A +** goto B +** A: setup for the SELECT +** loop over the tables in the SELECT +** load value into register R..R+n +** yield X +** end loop +** cleanup after the SELECT +** end co-routine R +** B: open temp table +** L: yield X, at EOF goto M +** insert row from R..R+n into temp table +** goto L +** M: open write cursor to <table> and its indices +** rewind temp table +** C: loop over rows of intermediate table +** transfer values form intermediate table into <table> +** end loop +** D: cleanup +*/ +SQLITE_PRIVATE void sqlite3Insert( + Parse *pParse, /* Parser context */ + SrcList *pTabList, /* Name of table into which we are inserting */ + Select *pSelect, /* A SELECT statement to use as the data source */ + IdList *pColumn, /* Column names corresponding to IDLIST, or NULL. */ + int onError, /* How to handle constraint errors */ + Upsert *pUpsert /* ON CONFLICT clauses for upsert, or NULL */ +){ + sqlite3 *db; /* The main database structure */ + Table *pTab; /* The table to insert into. aka TABLE */ + int i, j; /* Loop counters */ + Vdbe *v; /* Generate code into this virtual machine */ + Index *pIdx; /* For looping over indices of the table */ + int nColumn; /* Number of columns in the data */ + int nHidden = 0; /* Number of hidden columns if TABLE is virtual */ + int iDataCur = 0; /* VDBE cursor that is the main data repository */ + int iIdxCur = 0; /* First index cursor */ + int ipkColumn = -1; /* Column that is the INTEGER PRIMARY KEY */ + int endOfLoop; /* Label for the end of the insertion loop */ + int srcTab = 0; /* Data comes from this temporary cursor if >=0 */ + int addrInsTop = 0; /* Jump to label "D" */ + int addrCont = 0; /* Top of insert loop. Label "C" in templates 3 and 4 */ + SelectDest dest; /* Destination for SELECT on rhs of INSERT */ + int iDb; /* Index of database holding TABLE */ + u8 useTempTable = 0; /* Store SELECT results in intermediate table */ + u8 appendFlag = 0; /* True if the insert is likely to be an append */ + u8 withoutRowid; /* 0 for normal table. 1 for WITHOUT ROWID table */ + u8 bIdListInOrder; /* True if IDLIST is in table order */ + ExprList *pList = 0; /* List of VALUES() to be inserted */ + int iRegStore; /* Register in which to store next column */ + + /* Register allocations */ + int regFromSelect = 0;/* Base register for data coming from SELECT */ + int regAutoinc = 0; /* Register holding the AUTOINCREMENT counter */ + int regRowCount = 0; /* Memory cell used for the row counter */ + int regIns; /* Block of regs holding rowid+data being inserted */ + int regRowid; /* registers holding insert rowid */ + int regData; /* register holding first column to insert */ + int *aRegIdx = 0; /* One register allocated to each index */ + +#ifndef SQLITE_OMIT_TRIGGER + int isView; /* True if attempting to insert into a view */ + Trigger *pTrigger; /* List of triggers on pTab, if required */ + int tmask; /* Mask of trigger times */ +#endif + + db = pParse->db; + assert( db->pParse==pParse ); + if( pParse->nErr ){ + goto insert_cleanup; + } + assert( db->mallocFailed==0 ); + dest.iSDParm = 0; /* Suppress a harmless compiler warning */ + + /* If the Select object is really just a simple VALUES() list with a + ** single row (the common case) then keep that one row of values + ** and discard the other (unused) parts of the pSelect object + */ + if( pSelect && (pSelect->selFlags & SF_Values)!=0 && pSelect->pPrior==0 ){ + pList = pSelect->pEList; + pSelect->pEList = 0; + sqlite3SelectDelete(db, pSelect); + pSelect = 0; + } + + /* Locate the table into which we will be inserting new information. + */ + assert( pTabList->nSrc==1 ); + pTab = sqlite3SrcListLookup(pParse, pTabList); + if( pTab==0 ){ + goto insert_cleanup; + } + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb<db->nDb ); + if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, + db->aDb[iDb].zDbSName) ){ + goto insert_cleanup; + } + withoutRowid = !HasRowid(pTab); + + /* Figure out if we have any triggers and if the table being + ** inserted into is a view + */ +#ifndef SQLITE_OMIT_TRIGGER + pTrigger = sqlite3TriggersExist(pParse, pTab, TK_INSERT, 0, &tmask); + isView = IsView(pTab); +#else +# define pTrigger 0 +# define tmask 0 +# define isView 0 +#endif +#ifdef SQLITE_OMIT_VIEW +# undef isView +# define isView 0 +#endif + assert( (pTrigger && tmask) || (pTrigger==0 && tmask==0) ); + +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Insert() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewInsert(pParse->pWith, pTabList, pColumn, pSelect, pList, + onError, pUpsert, pTrigger); + } +#endif + + /* If pTab is really a view, make sure it has been initialized. + ** ViewGetColumnNames() is a no-op if pTab is not a view. + */ + if( sqlite3ViewGetColumnNames(pParse, pTab) ){ + goto insert_cleanup; + } + + /* Cannot insert into a read-only table. + */ + if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){ + goto insert_cleanup; + } + + /* Allocate a VDBE + */ + v = sqlite3GetVdbe(pParse); + if( v==0 ) goto insert_cleanup; + if( pParse->nested==0 ) sqlite3VdbeCountChanges(v); + sqlite3BeginWriteOperation(pParse, pSelect || pTrigger, iDb); + +#ifndef SQLITE_OMIT_XFER_OPT + /* If the statement is of the form + ** + ** INSERT INTO <table1> SELECT * FROM <table2>; + ** + ** Then special optimizations can be applied that make the transfer + ** very fast and which reduce fragmentation of indices. + ** + ** This is the 2nd template. + */ + if( pColumn==0 + && pSelect!=0 + && pTrigger==0 + && xferOptimization(pParse, pTab, pSelect, onError, iDb) + ){ + assert( !pTrigger ); + assert( pList==0 ); + goto insert_end; + } +#endif /* SQLITE_OMIT_XFER_OPT */ + + /* If this is an AUTOINCREMENT table, look up the sequence number in the + ** sqlite_sequence table and store it in memory cell regAutoinc. + */ + regAutoinc = autoIncBegin(pParse, iDb, pTab); + + /* Allocate a block registers to hold the rowid and the values + ** for all columns of the new row. + */ + regRowid = regIns = pParse->nMem+1; + pParse->nMem += pTab->nCol + 1; + if( IsVirtual(pTab) ){ + regRowid++; + pParse->nMem++; + } + regData = regRowid+1; + + /* If the INSERT statement included an IDLIST term, then make sure + ** all elements of the IDLIST really are columns of the table and + ** remember the column indices. + ** + ** If the table has an INTEGER PRIMARY KEY column and that column + ** is named in the IDLIST, then record in the ipkColumn variable + ** the index into IDLIST of the primary key column. ipkColumn is + ** the index of the primary key as it appears in IDLIST, not as + ** is appears in the original table. (The index of the INTEGER + ** PRIMARY KEY in the original table is pTab->iPKey.) After this + ** loop, if ipkColumn==(-1), that means that integer primary key + ** is unspecified, and hence the table is either WITHOUT ROWID or + ** it will automatically generated an integer primary key. + ** + ** bIdListInOrder is true if the columns in IDLIST are in storage + ** order. This enables an optimization that avoids shuffling the + ** columns into storage order. False negatives are harmless, + ** but false positives will cause database corruption. + */ + bIdListInOrder = (pTab->tabFlags & (TF_OOOHidden|TF_HasStored))==0; + if( pColumn ){ + assert( pColumn->eU4!=EU4_EXPR ); + pColumn->eU4 = EU4_IDX; + for(i=0; i<pColumn->nId; i++){ + pColumn->a[i].u4.idx = -1; + } + for(i=0; i<pColumn->nId; i++){ + for(j=0; j<pTab->nCol; j++){ + if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zCnName)==0 ){ + pColumn->a[i].u4.idx = j; + if( i!=j ) bIdListInOrder = 0; + if( j==pTab->iPKey ){ + ipkColumn = i; assert( !withoutRowid ); + } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( pTab->aCol[j].colFlags & (COLFLAG_STORED|COLFLAG_VIRTUAL) ){ + sqlite3ErrorMsg(pParse, + "cannot INSERT into generated column \"%s\"", + pTab->aCol[j].zCnName); + goto insert_cleanup; + } +#endif + break; + } + } + if( j>=pTab->nCol ){ + if( sqlite3IsRowid(pColumn->a[i].zName) && !withoutRowid ){ + ipkColumn = i; + bIdListInOrder = 0; + }else{ + sqlite3ErrorMsg(pParse, "table %S has no column named %s", + pTabList->a, pColumn->a[i].zName); + pParse->checkSchema = 1; + goto insert_cleanup; + } + } + } + } + + /* Figure out how many columns of data are supplied. If the data + ** is coming from a SELECT statement, then generate a co-routine that + ** produces a single row of the SELECT on each invocation. The + ** co-routine is the common header to the 3rd and 4th templates. + */ + if( pSelect ){ + /* Data is coming from a SELECT or from a multi-row VALUES clause. + ** Generate a co-routine to run the SELECT. */ + int regYield; /* Register holding co-routine entry-point */ + int addrTop; /* Top of the co-routine */ + int rc; /* Result code */ + + regYield = ++pParse->nMem; + addrTop = sqlite3VdbeCurrentAddr(v) + 1; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); + sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); + dest.iSdst = bIdListInOrder ? regData : 0; + dest.nSdst = pTab->nCol; + rc = sqlite3Select(pParse, pSelect, &dest); + regFromSelect = dest.iSdst; + assert( db->pParse==pParse ); + if( rc || pParse->nErr ) goto insert_cleanup; + assert( db->mallocFailed==0 ); + sqlite3VdbeEndCoroutine(v, regYield); + sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */ + assert( pSelect->pEList ); + nColumn = pSelect->pEList->nExpr; + + /* Set useTempTable to TRUE if the result of the SELECT statement + ** should be written into a temporary table (template 4). Set to + ** FALSE if each output row of the SELECT can be written directly into + ** the destination table (template 3). + ** + ** A temp table must be used if the table being updated is also one + ** of the tables being read by the SELECT statement. Also use a + ** temp table in the case of row triggers. + */ + if( pTrigger || readsTable(pParse, iDb, pTab) ){ + useTempTable = 1; + } + + if( useTempTable ){ + /* Invoke the coroutine to extract information from the SELECT + ** and add it to a transient table srcTab. The code generated + ** here is from the 4th template: + ** + ** B: open temp table + ** L: yield X, goto M at EOF + ** insert row from R..R+n into temp table + ** goto L + ** M: ... + */ + int regRec; /* Register to hold packed record */ + int regTempRowid; /* Register to hold temp table ROWID */ + int addrL; /* Label "L" */ + + srcTab = pParse->nTab++; + regRec = sqlite3GetTempReg(pParse); + regTempRowid = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, srcTab, nColumn); + addrL = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regFromSelect, nColumn, regRec); + sqlite3VdbeAddOp2(v, OP_NewRowid, srcTab, regTempRowid); + sqlite3VdbeAddOp3(v, OP_Insert, srcTab, regRec, regTempRowid); + sqlite3VdbeGoto(v, addrL); + sqlite3VdbeJumpHere(v, addrL); + sqlite3ReleaseTempReg(pParse, regRec); + sqlite3ReleaseTempReg(pParse, regTempRowid); + } + }else{ + /* This is the case if the data for the INSERT is coming from a + ** single-row VALUES clause + */ + NameContext sNC; + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = pParse; + srcTab = -1; + assert( useTempTable==0 ); + if( pList ){ + nColumn = pList->nExpr; + if( sqlite3ResolveExprListNames(&sNC, pList) ){ + goto insert_cleanup; + } + }else{ + nColumn = 0; + } + } + + /* If there is no IDLIST term but the table has an integer primary + ** key, the set the ipkColumn variable to the integer primary key + ** column index in the original table definition. + */ + if( pColumn==0 && nColumn>0 ){ + ipkColumn = pTab->iPKey; +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( ipkColumn>=0 && (pTab->tabFlags & TF_HasGenerated)!=0 ){ + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + for(i=ipkColumn-1; i>=0; i--){ + if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){ + testcase( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ); + testcase( pTab->aCol[i].colFlags & COLFLAG_STORED ); + ipkColumn--; + } + } + } +#endif + + /* Make sure the number of columns in the source data matches the number + ** of columns to be inserted into the table. + */ + assert( TF_HasHidden==COLFLAG_HIDDEN ); + assert( TF_HasGenerated==COLFLAG_GENERATED ); + assert( COLFLAG_NOINSERT==(COLFLAG_GENERATED|COLFLAG_HIDDEN) ); + if( (pTab->tabFlags & (TF_HasGenerated|TF_HasHidden))!=0 ){ + for(i=0; i<pTab->nCol; i++){ + if( pTab->aCol[i].colFlags & COLFLAG_NOINSERT ) nHidden++; + } + } + if( nColumn!=(pTab->nCol-nHidden) ){ + sqlite3ErrorMsg(pParse, + "table %S has %d columns but %d values were supplied", + pTabList->a, pTab->nCol-nHidden, nColumn); + goto insert_cleanup; + } + } + if( pColumn!=0 && nColumn!=pColumn->nId ){ + sqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId); + goto insert_cleanup; + } + + /* Initialize the count of rows to be inserted + */ + if( (db->flags & SQLITE_CountRows)!=0 + && !pParse->nested + && !pParse->pTriggerTab + && !pParse->bReturning + ){ + regRowCount = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount); + } + + /* If this is not a view, open the table and and all indices */ + if( !isView ){ + int nIdx; + nIdx = sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, -1, 0, + &iDataCur, &iIdxCur); + aRegIdx = sqlite3DbMallocRawNN(db, sizeof(int)*(nIdx+2)); + if( aRegIdx==0 ){ + goto insert_cleanup; + } + for(i=0, pIdx=pTab->pIndex; i<nIdx; pIdx=pIdx->pNext, i++){ + assert( pIdx ); + aRegIdx[i] = ++pParse->nMem; + pParse->nMem += pIdx->nColumn; + } + aRegIdx[i] = ++pParse->nMem; /* Register to store the table record */ + } +#ifndef SQLITE_OMIT_UPSERT + if( pUpsert ){ + Upsert *pNx; + if( IsVirtual(pTab) ){ + sqlite3ErrorMsg(pParse, "UPSERT not implemented for virtual table \"%s\"", + pTab->zName); + goto insert_cleanup; + } + if( IsView(pTab) ){ + sqlite3ErrorMsg(pParse, "cannot UPSERT a view"); + goto insert_cleanup; + } + if( sqlite3HasExplicitNulls(pParse, pUpsert->pUpsertTarget) ){ + goto insert_cleanup; + } + pTabList->a[0].iCursor = iDataCur; + pNx = pUpsert; + do{ + pNx->pUpsertSrc = pTabList; + pNx->regData = regData; + pNx->iDataCur = iDataCur; + pNx->iIdxCur = iIdxCur; + if( pNx->pUpsertTarget ){ + if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx) ){ + goto insert_cleanup; + } + } + pNx = pNx->pNextUpsert; + }while( pNx!=0 ); + } +#endif + + + /* This is the top of the main insertion loop */ + if( useTempTable ){ + /* This block codes the top of loop only. The complete loop is the + ** following pseudocode (template 4): + ** + ** rewind temp table, if empty goto D + ** C: loop over rows of intermediate table + ** transfer values form intermediate table into <table> + ** end loop + ** D: ... + */ + addrInsTop = sqlite3VdbeAddOp1(v, OP_Rewind, srcTab); VdbeCoverage(v); + addrCont = sqlite3VdbeCurrentAddr(v); + }else if( pSelect ){ + /* This block codes the top of loop only. The complete loop is the + ** following pseudocode (template 3): + ** + ** C: yield X, at EOF goto D + ** insert the select result into <table> from R..R+n + ** goto C + ** D: ... + */ + sqlite3VdbeReleaseRegisters(pParse, regData, pTab->nCol, 0, 0); + addrInsTop = addrCont = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); + VdbeCoverage(v); + if( ipkColumn>=0 ){ + /* tag-20191021-001: If the INTEGER PRIMARY KEY is being generated by the + ** SELECT, go ahead and copy the value into the rowid slot now, so that + ** the value does not get overwritten by a NULL at tag-20191021-002. */ + sqlite3VdbeAddOp2(v, OP_Copy, regFromSelect+ipkColumn, regRowid); + } + } + + /* Compute data for ordinary columns of the new entry. Values + ** are written in storage order into registers starting with regData. + ** Only ordinary columns are computed in this loop. The rowid + ** (if there is one) is computed later and generated columns are + ** computed after the rowid since they might depend on the value + ** of the rowid. + */ + nHidden = 0; + iRegStore = regData; assert( regData==regRowid+1 ); + for(i=0; i<pTab->nCol; i++, iRegStore++){ + int k; + u32 colFlags; + assert( i>=nHidden ); + if( i==pTab->iPKey ){ + /* tag-20191021-002: References to the INTEGER PRIMARY KEY are filled + ** using the rowid. So put a NULL in the IPK slot of the record to avoid + ** using excess space. The file format definition requires this extra + ** NULL - we cannot optimize further by skipping the column completely */ + sqlite3VdbeAddOp1(v, OP_SoftNull, iRegStore); + continue; + } + if( ((colFlags = pTab->aCol[i].colFlags) & COLFLAG_NOINSERT)!=0 ){ + nHidden++; + if( (colFlags & COLFLAG_VIRTUAL)!=0 ){ + /* Virtual columns do not participate in OP_MakeRecord. So back up + ** iRegStore by one slot to compensate for the iRegStore++ in the + ** outer for() loop */ + iRegStore--; + continue; + }else if( (colFlags & COLFLAG_STORED)!=0 ){ + /* Stored columns are computed later. But if there are BEFORE + ** triggers, the slots used for stored columns will be OP_Copy-ed + ** to a second block of registers, so the register needs to be + ** initialized to NULL to avoid an uninitialized register read */ + if( tmask & TRIGGER_BEFORE ){ + sqlite3VdbeAddOp1(v, OP_SoftNull, iRegStore); + } + continue; + }else if( pColumn==0 ){ + /* Hidden columns that are not explicitly named in the INSERT + ** get there default value */ + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); + continue; + } + } + if( pColumn ){ + assert( pColumn->eU4==EU4_IDX ); + for(j=0; j<pColumn->nId && pColumn->a[j].u4.idx!=i; j++){} + if( j>=pColumn->nId ){ + /* A column not named in the insert column list gets its + ** default value */ + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); + continue; + } + k = j; + }else if( nColumn==0 ){ + /* This is INSERT INTO ... DEFAULT VALUES. Load the default value. */ + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); + continue; + }else{ + k = i - nHidden; + } + + if( useTempTable ){ + sqlite3VdbeAddOp3(v, OP_Column, srcTab, k, iRegStore); + }else if( pSelect ){ + if( regFromSelect!=regData ){ + sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+k, iRegStore); + } + }else{ + Expr *pX = pList->a[k].pExpr; + int y = sqlite3ExprCodeTarget(pParse, pX, iRegStore); + if( y!=iRegStore ){ + sqlite3VdbeAddOp2(v, + ExprHasProperty(pX, EP_Subquery) ? OP_Copy : OP_SCopy, y, iRegStore); + } + } + } + + + /* Run the BEFORE and INSTEAD OF triggers, if there are any + */ + endOfLoop = sqlite3VdbeMakeLabel(pParse); + if( tmask & TRIGGER_BEFORE ){ + int regCols = sqlite3GetTempRange(pParse, pTab->nCol+1); + + /* build the NEW.* reference row. Note that if there is an INTEGER + ** PRIMARY KEY into which a NULL is being inserted, that NULL will be + ** translated into a unique ID for the row. But on a BEFORE trigger, + ** we do not know what the unique ID will be (because the insert has + ** not happened yet) so we substitute a rowid of -1 + */ + if( ipkColumn<0 ){ + sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols); + }else{ + int addr1; + assert( !withoutRowid ); + if( useTempTable ){ + sqlite3VdbeAddOp3(v, OP_Column, srcTab, ipkColumn, regCols); + }else{ + assert( pSelect==0 ); /* Otherwise useTempTable is true */ + sqlite3ExprCode(pParse, pList->a[ipkColumn].pExpr, regCols); + } + addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, regCols); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols); + sqlite3VdbeJumpHere(v, addr1); + sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); VdbeCoverage(v); + } + + /* Copy the new data already generated. */ + assert( pTab->nNVCol>0 || pParse->nErr>0 ); + sqlite3VdbeAddOp3(v, OP_Copy, regRowid+1, regCols+1, pTab->nNVCol-1); + +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + /* Compute the new value for generated columns after all other + ** columns have already been computed. This must be done after + ** computing the ROWID in case one of the generated columns + ** refers to the ROWID. */ + if( pTab->tabFlags & TF_HasGenerated ){ + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + sqlite3ComputeGeneratedColumns(pParse, regCols+1, pTab); + } +#endif + + /* If this is an INSERT on a view with an INSTEAD OF INSERT trigger, + ** do not attempt any conversions before assembling the record. + ** If this is a real table, attempt conversions as required by the + ** table column affinities. + */ + if( !isView ){ + sqlite3TableAffinity(v, pTab, regCols+1); + } + + /* Fire BEFORE or INSTEAD OF triggers */ + sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE, + pTab, regCols-pTab->nCol-1, onError, endOfLoop); + + sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol+1); + } + + if( !isView ){ + if( IsVirtual(pTab) ){ + /* The row that the VUpdate opcode will delete: none */ + sqlite3VdbeAddOp2(v, OP_Null, 0, regIns); + } + if( ipkColumn>=0 ){ + /* Compute the new rowid */ + if( useTempTable ){ + sqlite3VdbeAddOp3(v, OP_Column, srcTab, ipkColumn, regRowid); + }else if( pSelect ){ + /* Rowid already initialized at tag-20191021-001 */ + }else{ + Expr *pIpk = pList->a[ipkColumn].pExpr; + if( pIpk->op==TK_NULL && !IsVirtual(pTab) ){ + sqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc); + appendFlag = 1; + }else{ + sqlite3ExprCode(pParse, pList->a[ipkColumn].pExpr, regRowid); + } + } + /* If the PRIMARY KEY expression is NULL, then use OP_NewRowid + ** to generate a unique primary key value. + */ + if( !appendFlag ){ + int addr1; + if( !IsVirtual(pTab) ){ + addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, regRowid); VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc); + sqlite3VdbeJumpHere(v, addr1); + }else{ + addr1 = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp2(v, OP_IsNull, regRowid, addr1+2); VdbeCoverage(v); + } + sqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid); VdbeCoverage(v); + } + }else if( IsVirtual(pTab) || withoutRowid ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regRowid); + }else{ + sqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc); + appendFlag = 1; + } + autoIncStep(pParse, regAutoinc, regRowid); + +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + /* Compute the new value for generated columns after all other + ** columns have already been computed. This must be done after + ** computing the ROWID in case one of the generated columns + ** is derived from the INTEGER PRIMARY KEY. */ + if( pTab->tabFlags & TF_HasGenerated ){ + sqlite3ComputeGeneratedColumns(pParse, regRowid+1, pTab); + } +#endif + + /* Generate code to check constraints and generate index keys and + ** do the insertion. + */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pTab) ){ + const char *pVTab = (const char *)sqlite3GetVTable(db, pTab); + sqlite3VtabMakeWritable(pParse, pTab); + sqlite3VdbeAddOp4(v, OP_VUpdate, 1, pTab->nCol+2, regIns, pVTab, P4_VTAB); + sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError); + sqlite3MayAbort(pParse); + }else +#endif + { + int isReplace = 0;/* Set to true if constraints may cause a replace */ + int bUseSeek; /* True to use OPFLAG_SEEKRESULT */ + sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, + regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert + ); + if( db->flags & SQLITE_ForeignKeys ){ + sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); + } + + /* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE + ** constraints or (b) there are no triggers and this table is not a + ** parent table in a foreign key constraint. It is safe to set the + ** flag in the second case as if any REPLACE constraint is hit, an + ** OP_Delete or OP_IdxDelete instruction will be executed on each + ** cursor that is disturbed. And these instructions both clear the + ** VdbeCursor.seekResult variable, disabling the OPFLAG_USESEEKRESULT + ** functionality. */ + bUseSeek = (isReplace==0 || !sqlite3VdbeHasSubProgram(v)); + sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur, + regIns, aRegIdx, 0, appendFlag, bUseSeek + ); + } +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + }else if( pParse->bReturning ){ + /* If there is a RETURNING clause, populate the rowid register with + ** constant value -1, in case one or more of the returned expressions + ** refer to the "rowid" of the view. */ + sqlite3VdbeAddOp2(v, OP_Integer, -1, regRowid); +#endif + } + + /* Update the count of rows that are inserted + */ + if( regRowCount ){ + sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); + } + + if( pTrigger ){ + /* Code AFTER triggers */ + sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER, + pTab, regData-2-pTab->nCol, onError, endOfLoop); + } + + /* The bottom of the main insertion loop, if the data source + ** is a SELECT statement. + */ + sqlite3VdbeResolveLabel(v, endOfLoop); + if( useTempTable ){ + sqlite3VdbeAddOp2(v, OP_Next, srcTab, addrCont); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrInsTop); + sqlite3VdbeAddOp1(v, OP_Close, srcTab); + }else if( pSelect ){ + sqlite3VdbeGoto(v, addrCont); +#ifdef SQLITE_DEBUG + /* If we are jumping back to an OP_Yield that is preceded by an + ** OP_ReleaseReg, set the p5 flag on the OP_Goto so that the + ** OP_ReleaseReg will be included in the loop. */ + if( sqlite3VdbeGetOp(v, addrCont-1)->opcode==OP_ReleaseReg ){ + assert( sqlite3VdbeGetOp(v, addrCont)->opcode==OP_Yield ); + sqlite3VdbeChangeP5(v, 1); + } +#endif + sqlite3VdbeJumpHere(v, addrInsTop); + } + +#ifndef SQLITE_OMIT_XFER_OPT +insert_end: +#endif /* SQLITE_OMIT_XFER_OPT */ + /* Update the sqlite_sequence table by storing the content of the + ** maximum rowid counter values recorded while inserting into + ** autoincrement tables. + */ + if( pParse->nested==0 && pParse->pTriggerTab==0 ){ + sqlite3AutoincrementEnd(pParse); + } + + /* + ** Return the number of rows inserted. If this routine is + ** generating code because of a call to sqlite3NestedParse(), do not + ** invoke the callback function. + */ + if( regRowCount ){ + sqlite3CodeChangeCount(v, regRowCount, "rows inserted"); + } + +insert_cleanup: + sqlite3SrcListDelete(db, pTabList); + sqlite3ExprListDelete(db, pList); + sqlite3UpsertDelete(db, pUpsert); + sqlite3SelectDelete(db, pSelect); + sqlite3IdListDelete(db, pColumn); + if( aRegIdx ) sqlite3DbNNFreeNN(db, aRegIdx); +} + +/* Make sure "isView" and other macros defined above are undefined. Otherwise +** they may interfere with compilation of other functions in this file +** (or in another file, if this file becomes part of the amalgamation). */ +#ifdef isView + #undef isView +#endif +#ifdef pTrigger + #undef pTrigger +#endif +#ifdef tmask + #undef tmask +#endif + +/* +** Meanings of bits in of pWalker->eCode for +** sqlite3ExprReferencesUpdatedColumn() +*/ +#define CKCNSTRNT_COLUMN 0x01 /* CHECK constraint uses a changing column */ +#define CKCNSTRNT_ROWID 0x02 /* CHECK constraint references the ROWID */ + +/* This is the Walker callback from sqlite3ExprReferencesUpdatedColumn(). +* Set bit 0x01 of pWalker->eCode if pWalker->eCode to 0 and if this +** expression node references any of the +** columns that are being modified by an UPDATE statement. +*/ +static int checkConstraintExprNode(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN ){ + assert( pExpr->iColumn>=0 || pExpr->iColumn==-1 ); + if( pExpr->iColumn>=0 ){ + if( pWalker->u.aiCol[pExpr->iColumn]>=0 ){ + pWalker->eCode |= CKCNSTRNT_COLUMN; + } + }else{ + pWalker->eCode |= CKCNSTRNT_ROWID; + } + } + return WRC_Continue; +} + +/* +** pExpr is a CHECK constraint on a row that is being UPDATE-ed. The +** only columns that are modified by the UPDATE are those for which +** aiChng[i]>=0, and also the ROWID is modified if chngRowid is true. +** +** Return true if CHECK constraint pExpr uses any of the +** changing columns (or the rowid if it is changing). In other words, +** return true if this CHECK constraint must be validated for +** the new row in the UPDATE statement. +** +** 2018-09-15: pExpr might also be an expression for an index-on-expressions. +** The operation of this routine is the same - return true if an only if +** the expression uses one or more of columns identified by the second and +** third arguments. +*/ +SQLITE_PRIVATE int sqlite3ExprReferencesUpdatedColumn( + Expr *pExpr, /* The expression to be checked */ + int *aiChng, /* aiChng[x]>=0 if column x changed by the UPDATE */ + int chngRowid /* True if UPDATE changes the rowid */ +){ + Walker w; + memset(&w, 0, sizeof(w)); + w.eCode = 0; + w.xExprCallback = checkConstraintExprNode; + w.u.aiCol = aiChng; + sqlite3WalkExpr(&w, pExpr); + if( !chngRowid ){ + testcase( (w.eCode & CKCNSTRNT_ROWID)!=0 ); + w.eCode &= ~CKCNSTRNT_ROWID; + } + testcase( w.eCode==0 ); + testcase( w.eCode==CKCNSTRNT_COLUMN ); + testcase( w.eCode==CKCNSTRNT_ROWID ); + testcase( w.eCode==(CKCNSTRNT_ROWID|CKCNSTRNT_COLUMN) ); + return w.eCode!=0; +} + +/* +** The sqlite3GenerateConstraintChecks() routine usually wants to visit +** the indexes of a table in the order provided in the Table->pIndex list. +** However, sometimes (rarely - when there is an upsert) it wants to visit +** the indexes in a different order. The following data structures accomplish +** this. +** +** The IndexIterator object is used to walk through all of the indexes +** of a table in either Index.pNext order, or in some other order established +** by an array of IndexListTerm objects. +*/ +typedef struct IndexListTerm IndexListTerm; +typedef struct IndexIterator IndexIterator; +struct IndexIterator { + int eType; /* 0 for Index.pNext list. 1 for an array of IndexListTerm */ + int i; /* Index of the current item from the list */ + union { + struct { /* Use this object for eType==0: A Index.pNext list */ + Index *pIdx; /* The current Index */ + } lx; + struct { /* Use this object for eType==1; Array of IndexListTerm */ + int nIdx; /* Size of the array */ + IndexListTerm *aIdx; /* Array of IndexListTerms */ + } ax; + } u; +}; + +/* When IndexIterator.eType==1, then each index is an array of instances +** of the following object +*/ +struct IndexListTerm { + Index *p; /* The index */ + int ix; /* Which entry in the original Table.pIndex list is this index*/ +}; + +/* Return the first index on the list */ +static Index *indexIteratorFirst(IndexIterator *pIter, int *pIx){ + assert( pIter->i==0 ); + if( pIter->eType ){ + *pIx = pIter->u.ax.aIdx[0].ix; + return pIter->u.ax.aIdx[0].p; + }else{ + *pIx = 0; + return pIter->u.lx.pIdx; + } +} + +/* Return the next index from the list. Return NULL when out of indexes */ +static Index *indexIteratorNext(IndexIterator *pIter, int *pIx){ + if( pIter->eType ){ + int i = ++pIter->i; + if( i>=pIter->u.ax.nIdx ){ + *pIx = i; + return 0; + } + *pIx = pIter->u.ax.aIdx[i].ix; + return pIter->u.ax.aIdx[i].p; + }else{ + ++(*pIx); + pIter->u.lx.pIdx = pIter->u.lx.pIdx->pNext; + return pIter->u.lx.pIdx; + } +} + +/* +** Generate code to do constraint checks prior to an INSERT or an UPDATE +** on table pTab. +** +** The regNewData parameter is the first register in a range that contains +** the data to be inserted or the data after the update. There will be +** pTab->nCol+1 registers in this range. The first register (the one +** that regNewData points to) will contain the new rowid, or NULL in the +** case of a WITHOUT ROWID table. The second register in the range will +** contain the content of the first table column. The third register will +** contain the content of the second table column. And so forth. +** +** The regOldData parameter is similar to regNewData except that it contains +** the data prior to an UPDATE rather than afterwards. regOldData is zero +** for an INSERT. This routine can distinguish between UPDATE and INSERT by +** checking regOldData for zero. +** +** For an UPDATE, the pkChng boolean is true if the true primary key (the +** rowid for a normal table or the PRIMARY KEY for a WITHOUT ROWID table) +** might be modified by the UPDATE. If pkChng is false, then the key of +** the iDataCur content table is guaranteed to be unchanged by the UPDATE. +** +** For an INSERT, the pkChng boolean indicates whether or not the rowid +** was explicitly specified as part of the INSERT statement. If pkChng +** is zero, it means that the either rowid is computed automatically or +** that the table is a WITHOUT ROWID table and has no rowid. On an INSERT, +** pkChng will only be true if the INSERT statement provides an integer +** value for either the rowid column or its INTEGER PRIMARY KEY alias. +** +** The code generated by this routine will store new index entries into +** registers identified by aRegIdx[]. No index entry is created for +** indices where aRegIdx[i]==0. The order of indices in aRegIdx[] is +** the same as the order of indices on the linked list of indices +** at pTab->pIndex. +** +** (2019-05-07) The generated code also creates a new record for the +** main table, if pTab is a rowid table, and stores that record in the +** register identified by aRegIdx[nIdx] - in other words in the first +** entry of aRegIdx[] past the last index. It is important that the +** record be generated during constraint checks to avoid affinity changes +** to the register content that occur after constraint checks but before +** the new record is inserted. +** +** The caller must have already opened writeable cursors on the main +** table and all applicable indices (that is to say, all indices for which +** aRegIdx[] is not zero). iDataCur is the cursor for the main table when +** inserting or updating a rowid table, or the cursor for the PRIMARY KEY +** index when operating on a WITHOUT ROWID table. iIdxCur is the cursor +** for the first index in the pTab->pIndex list. Cursors for other indices +** are at iIdxCur+N for the N-th element of the pTab->pIndex list. +** +** This routine also generates code to check constraints. NOT NULL, +** CHECK, and UNIQUE constraints are all checked. If a constraint fails, +** then the appropriate action is performed. There are five possible +** actions: ROLLBACK, ABORT, FAIL, REPLACE, and IGNORE. +** +** Constraint type Action What Happens +** --------------- ---------- ---------------------------------------- +** any ROLLBACK The current transaction is rolled back and +** sqlite3_step() returns immediately with a +** return code of SQLITE_CONSTRAINT. +** +** any ABORT Back out changes from the current command +** only (do not do a complete rollback) then +** cause sqlite3_step() to return immediately +** with SQLITE_CONSTRAINT. +** +** any FAIL Sqlite3_step() returns immediately with a +** return code of SQLITE_CONSTRAINT. The +** transaction is not rolled back and any +** changes to prior rows are retained. +** +** any IGNORE The attempt in insert or update the current +** row is skipped, without throwing an error. +** Processing continues with the next row. +** (There is an immediate jump to ignoreDest.) +** +** NOT NULL REPLACE The NULL value is replace by the default +** value for that column. If the default value +** is NULL, the action is the same as ABORT. +** +** UNIQUE REPLACE The other row that conflicts with the row +** being inserted is removed. +** +** CHECK REPLACE Illegal. The results in an exception. +** +** Which action to take is determined by the overrideError parameter. +** Or if overrideError==OE_Default, then the pParse->onError parameter +** is used. Or if pParse->onError==OE_Default then the onError value +** for the constraint is used. +*/ +SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( + Parse *pParse, /* The parser context */ + Table *pTab, /* The table being inserted or updated */ + int *aRegIdx, /* Use register aRegIdx[i] for index i. 0 for unused */ + int iDataCur, /* Canonical data cursor (main table or PK index) */ + int iIdxCur, /* First index cursor */ + int regNewData, /* First register in a range holding values to insert */ + int regOldData, /* Previous content. 0 for INSERTs */ + u8 pkChng, /* Non-zero if the rowid or PRIMARY KEY changed */ + u8 overrideError, /* Override onError to this if not OE_Default */ + int ignoreDest, /* Jump to this label on an OE_Ignore resolution */ + int *pbMayReplace, /* OUT: Set to true if constraint may cause a replace */ + int *aiChng, /* column i is unchanged if aiChng[i]<0 */ + Upsert *pUpsert /* ON CONFLICT clauses, if any. NULL otherwise */ +){ + Vdbe *v; /* VDBE under construction */ + Index *pIdx; /* Pointer to one of the indices */ + Index *pPk = 0; /* The PRIMARY KEY index for WITHOUT ROWID tables */ + sqlite3 *db; /* Database connection */ + int i; /* loop counter */ + int ix; /* Index loop counter */ + int nCol; /* Number of columns */ + int onError; /* Conflict resolution strategy */ + int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */ + int nPkField; /* Number of fields in PRIMARY KEY. 1 for ROWID tables */ + Upsert *pUpsertClause = 0; /* The specific ON CONFLICT clause for pIdx */ + u8 isUpdate; /* True if this is an UPDATE operation */ + u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */ + int upsertIpkReturn = 0; /* Address of Goto at end of IPK uniqueness check */ + int upsertIpkDelay = 0; /* Address of Goto to bypass initial IPK check */ + int ipkTop = 0; /* Top of the IPK uniqueness check */ + int ipkBottom = 0; /* OP_Goto at the end of the IPK uniqueness check */ + /* Variables associated with retesting uniqueness constraints after + ** replace triggers fire have run */ + int regTrigCnt; /* Register used to count replace trigger invocations */ + int addrRecheck = 0; /* Jump here to recheck all uniqueness constraints */ + int lblRecheckOk = 0; /* Each recheck jumps to this label if it passes */ + Trigger *pTrigger; /* List of DELETE triggers on the table pTab */ + int nReplaceTrig = 0; /* Number of replace triggers coded */ + IndexIterator sIdxIter; /* Index iterator */ + + isUpdate = regOldData!=0; + db = pParse->db; + v = pParse->pVdbe; + assert( v!=0 ); + assert( !IsView(pTab) ); /* This table is not a VIEW */ + nCol = pTab->nCol; + + /* pPk is the PRIMARY KEY index for WITHOUT ROWID tables and NULL for + ** normal rowid tables. nPkField is the number of key fields in the + ** pPk index or 1 for a rowid table. In other words, nPkField is the + ** number of fields in the true primary key of the table. */ + if( HasRowid(pTab) ){ + pPk = 0; + nPkField = 1; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + nPkField = pPk->nKeyCol; + } + + /* Record that this module has started */ + VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)", + iDataCur, iIdxCur, regNewData, regOldData, pkChng)); + + /* Test all NOT NULL constraints. + */ + if( pTab->tabFlags & TF_HasNotNull ){ + int b2ndPass = 0; /* True if currently running 2nd pass */ + int nSeenReplace = 0; /* Number of ON CONFLICT REPLACE operations */ + int nGenerated = 0; /* Number of generated columns with NOT NULL */ + while(1){ /* Make 2 passes over columns. Exit loop via "break" */ + for(i=0; i<nCol; i++){ + int iReg; /* Register holding column value */ + Column *pCol = &pTab->aCol[i]; /* The column to check for NOT NULL */ + int isGenerated; /* non-zero if column is generated */ + onError = pCol->notNull; + if( onError==OE_None ) continue; /* No NOT NULL on this column */ + if( i==pTab->iPKey ){ + continue; /* ROWID is never NULL */ + } + isGenerated = pCol->colFlags & COLFLAG_GENERATED; + if( isGenerated && !b2ndPass ){ + nGenerated++; + continue; /* Generated columns processed on 2nd pass */ + } + if( aiChng && aiChng[i]<0 && !isGenerated ){ + /* Do not check NOT NULL on columns that do not change */ + continue; + } + if( overrideError!=OE_Default ){ + onError = overrideError; + }else if( onError==OE_Default ){ + onError = OE_Abort; + } + if( onError==OE_Replace ){ + if( b2ndPass /* REPLACE becomes ABORT on the 2nd pass */ + || pCol->iDflt==0 /* REPLACE is ABORT if no DEFAULT value */ + ){ + testcase( pCol->colFlags & COLFLAG_VIRTUAL ); + testcase( pCol->colFlags & COLFLAG_STORED ); + testcase( pCol->colFlags & COLFLAG_GENERATED ); + onError = OE_Abort; + }else{ + assert( !isGenerated ); + } + }else if( b2ndPass && !isGenerated ){ + continue; + } + assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail + || onError==OE_Ignore || onError==OE_Replace ); + testcase( i!=sqlite3TableColumnToStorage(pTab, i) ); + iReg = sqlite3TableColumnToStorage(pTab, i) + regNewData + 1; + switch( onError ){ + case OE_Replace: { + int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, iReg); + VdbeCoverage(v); + assert( (pCol->colFlags & COLFLAG_GENERATED)==0 ); + nSeenReplace++; + sqlite3ExprCodeCopy(pParse, + sqlite3ColumnExpr(pTab, pCol), iReg); + sqlite3VdbeJumpHere(v, addr1); + break; + } + case OE_Abort: + sqlite3MayAbort(pParse); + /* no break */ deliberate_fall_through + case OE_Rollback: + case OE_Fail: { + char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, + pCol->zCnName); + testcase( zMsg==0 && db->mallocFailed==0 ); + sqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, + onError, iReg); + sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC); + sqlite3VdbeChangeP5(v, P5_ConstraintNotNull); + VdbeCoverage(v); + break; + } + default: { + assert( onError==OE_Ignore ); + sqlite3VdbeAddOp2(v, OP_IsNull, iReg, ignoreDest); + VdbeCoverage(v); + break; + } + } /* end switch(onError) */ + } /* end loop i over columns */ + if( nGenerated==0 && nSeenReplace==0 ){ + /* If there are no generated columns with NOT NULL constraints + ** and no NOT NULL ON CONFLICT REPLACE constraints, then a single + ** pass is sufficient */ + break; + } + if( b2ndPass ) break; /* Never need more than 2 passes */ + b2ndPass = 1; +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( nSeenReplace>0 && (pTab->tabFlags & TF_HasGenerated)!=0 ){ + /* If any NOT NULL ON CONFLICT REPLACE constraints fired on the + ** first pass, recomputed values for all generated columns, as + ** those values might depend on columns affected by the REPLACE. + */ + sqlite3ComputeGeneratedColumns(pParse, regNewData+1, pTab); + } +#endif + } /* end of 2-pass loop */ + } /* end if( has-not-null-constraints ) */ + + /* Test all CHECK constraints + */ +#ifndef SQLITE_OMIT_CHECK + if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ + ExprList *pCheck = pTab->pCheck; + pParse->iSelfTab = -(regNewData+1); + onError = overrideError!=OE_Default ? overrideError : OE_Abort; + for(i=0; i<pCheck->nExpr; i++){ + int allOk; + Expr *pCopy; + Expr *pExpr = pCheck->a[i].pExpr; + if( aiChng + && !sqlite3ExprReferencesUpdatedColumn(pExpr, aiChng, pkChng) + ){ + /* The check constraints do not reference any of the columns being + ** updated so there is no point it verifying the check constraint */ + continue; + } + if( bAffinityDone==0 ){ + sqlite3TableAffinity(v, pTab, regNewData+1); + bAffinityDone = 1; + } + allOk = sqlite3VdbeMakeLabel(pParse); + sqlite3VdbeVerifyAbortable(v, onError); + pCopy = sqlite3ExprDup(db, pExpr, 0); + if( !db->mallocFailed ){ + sqlite3ExprIfTrue(pParse, pCopy, allOk, SQLITE_JUMPIFNULL); + } + sqlite3ExprDelete(db, pCopy); + if( onError==OE_Ignore ){ + sqlite3VdbeGoto(v, ignoreDest); + }else{ + char *zName = pCheck->a[i].zEName; + assert( zName!=0 || pParse->db->mallocFailed ); + if( onError==OE_Replace ) onError = OE_Abort; /* IMP: R-26383-51744 */ + sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_CHECK, + onError, zName, P4_TRANSIENT, + P5_ConstraintCheck); + } + sqlite3VdbeResolveLabel(v, allOk); + } + pParse->iSelfTab = 0; + } +#endif /* !defined(SQLITE_OMIT_CHECK) */ + + /* UNIQUE and PRIMARY KEY constraints should be handled in the following + ** order: + ** + ** (1) OE_Update + ** (2) OE_Abort, OE_Fail, OE_Rollback, OE_Ignore + ** (3) OE_Replace + ** + ** OE_Fail and OE_Ignore must happen before any changes are made. + ** OE_Update guarantees that only a single row will change, so it + ** must happen before OE_Replace. Technically, OE_Abort and OE_Rollback + ** could happen in any order, but they are grouped up front for + ** convenience. + ** + ** 2018-08-14: Ticket https://www.sqlite.org/src/info/908f001483982c43 + ** The order of constraints used to have OE_Update as (2) and OE_Abort + ** and so forth as (1). But apparently PostgreSQL checks the OE_Update + ** constraint before any others, so it had to be moved. + ** + ** Constraint checking code is generated in this order: + ** (A) The rowid constraint + ** (B) Unique index constraints that do not have OE_Replace as their + ** default conflict resolution strategy + ** (C) Unique index that do use OE_Replace by default. + ** + ** The ordering of (2) and (3) is accomplished by making sure the linked + ** list of indexes attached to a table puts all OE_Replace indexes last + ** in the list. See sqlite3CreateIndex() for where that happens. + */ + sIdxIter.eType = 0; + sIdxIter.i = 0; + sIdxIter.u.ax.aIdx = 0; /* Silence harmless compiler warning */ + sIdxIter.u.lx.pIdx = pTab->pIndex; + if( pUpsert ){ + if( pUpsert->pUpsertTarget==0 ){ + /* There is just on ON CONFLICT clause and it has no constraint-target */ + assert( pUpsert->pNextUpsert==0 ); + if( pUpsert->isDoUpdate==0 ){ + /* A single ON CONFLICT DO NOTHING clause, without a constraint-target. + ** Make all unique constraint resolution be OE_Ignore */ + overrideError = OE_Ignore; + pUpsert = 0; + }else{ + /* A single ON CONFLICT DO UPDATE. Make all resolutions OE_Update */ + overrideError = OE_Update; + } + }else if( pTab->pIndex!=0 ){ + /* Otherwise, we'll need to run the IndexListTerm array version of the + ** iterator to ensure that all of the ON CONFLICT conditions are + ** checked first and in order. */ + int nIdx, jj; + u64 nByte; + Upsert *pTerm; + u8 *bUsed; + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ + assert( aRegIdx[nIdx]>0 ); + } + sIdxIter.eType = 1; + sIdxIter.u.ax.nIdx = nIdx; + nByte = (sizeof(IndexListTerm)+1)*nIdx + nIdx; + sIdxIter.u.ax.aIdx = sqlite3DbMallocZero(db, nByte); + if( sIdxIter.u.ax.aIdx==0 ) return; /* OOM */ + bUsed = (u8*)&sIdxIter.u.ax.aIdx[nIdx]; + pUpsert->pToFree = sIdxIter.u.ax.aIdx; + for(i=0, pTerm=pUpsert; pTerm; pTerm=pTerm->pNextUpsert){ + if( pTerm->pUpsertTarget==0 ) break; + if( pTerm->pUpsertIdx==0 ) continue; /* Skip ON CONFLICT for the IPK */ + jj = 0; + pIdx = pTab->pIndex; + while( ALWAYS(pIdx!=0) && pIdx!=pTerm->pUpsertIdx ){ + pIdx = pIdx->pNext; + jj++; + } + if( bUsed[jj] ) continue; /* Duplicate ON CONFLICT clause ignored */ + bUsed[jj] = 1; + sIdxIter.u.ax.aIdx[i].p = pIdx; + sIdxIter.u.ax.aIdx[i].ix = jj; + i++; + } + for(jj=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, jj++){ + if( bUsed[jj] ) continue; + sIdxIter.u.ax.aIdx[i].p = pIdx; + sIdxIter.u.ax.aIdx[i].ix = jj; + i++; + } + assert( i==nIdx ); + } + } + + /* Determine if it is possible that triggers (either explicitly coded + ** triggers or FK resolution actions) might run as a result of deletes + ** that happen when OE_Replace conflict resolution occurs. (Call these + ** "replace triggers".) If any replace triggers run, we will need to + ** recheck all of the uniqueness constraints after they have all run. + ** But on the recheck, the resolution is OE_Abort instead of OE_Replace. + ** + ** If replace triggers are a possibility, then + ** + ** (1) Allocate register regTrigCnt and initialize it to zero. + ** That register will count the number of replace triggers that + ** fire. Constraint recheck only occurs if the number is positive. + ** (2) Initialize pTrigger to the list of all DELETE triggers on pTab. + ** (3) Initialize addrRecheck and lblRecheckOk + ** + ** The uniqueness rechecking code will create a series of tests to run + ** in a second pass. The addrRecheck and lblRecheckOk variables are + ** used to link together these tests which are separated from each other + ** in the generate bytecode. + */ + if( (db->flags & (SQLITE_RecTriggers|SQLITE_ForeignKeys))==0 ){ + /* There are not DELETE triggers nor FK constraints. No constraint + ** rechecks are needed. */ + pTrigger = 0; + regTrigCnt = 0; + }else{ + if( db->flags&SQLITE_RecTriggers ){ + pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); + regTrigCnt = pTrigger!=0 || sqlite3FkRequired(pParse, pTab, 0, 0); + }else{ + pTrigger = 0; + regTrigCnt = sqlite3FkRequired(pParse, pTab, 0, 0); + } + if( regTrigCnt ){ + /* Replace triggers might exist. Allocate the counter and + ** initialize it to zero. */ + regTrigCnt = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 0, regTrigCnt); + VdbeComment((v, "trigger count")); + lblRecheckOk = sqlite3VdbeMakeLabel(pParse); + addrRecheck = lblRecheckOk; + } + } + + /* If rowid is changing, make sure the new rowid does not previously + ** exist in the table. + */ + if( pkChng && pPk==0 ){ + int addrRowidOk = sqlite3VdbeMakeLabel(pParse); + + /* Figure out what action to take in case of a rowid collision */ + onError = pTab->keyConf; + if( overrideError!=OE_Default ){ + onError = overrideError; + }else if( onError==OE_Default ){ + onError = OE_Abort; + } + + /* figure out whether or not upsert applies in this case */ + if( pUpsert ){ + pUpsertClause = sqlite3UpsertOfIndex(pUpsert,0); + if( pUpsertClause!=0 ){ + if( pUpsertClause->isDoUpdate==0 ){ + onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */ + }else{ + onError = OE_Update; /* DO UPDATE */ + } + } + if( pUpsertClause!=pUpsert ){ + /* The first ON CONFLICT clause has a conflict target other than + ** the IPK. We have to jump ahead to that first ON CONFLICT clause + ** and then come back here and deal with the IPK afterwards */ + upsertIpkDelay = sqlite3VdbeAddOp0(v, OP_Goto); + } + } + + /* If the response to a rowid conflict is REPLACE but the response + ** to some other UNIQUE constraint is FAIL or IGNORE, then we need + ** to defer the running of the rowid conflict checking until after + ** the UNIQUE constraints have run. + */ + if( onError==OE_Replace /* IPK rule is REPLACE */ + && onError!=overrideError /* Rules for other constraints are different */ + && pTab->pIndex /* There exist other constraints */ + && !upsertIpkDelay /* IPK check already deferred by UPSERT */ + ){ + ipkTop = sqlite3VdbeAddOp0(v, OP_Goto)+1; + VdbeComment((v, "defer IPK REPLACE until last")); + } + + if( isUpdate ){ + /* pkChng!=0 does not mean that the rowid has changed, only that + ** it might have changed. Skip the conflict logic below if the rowid + ** is unchanged. */ + sqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRowidOk, regOldData); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + VdbeCoverage(v); + } + + /* Check to see if the new rowid already exists in the table. Skip + ** the following conflict logic if it does not. */ + VdbeNoopComment((v, "uniqueness check for ROWID")); + sqlite3VdbeVerifyAbortable(v, onError); + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, addrRowidOk, regNewData); + VdbeCoverage(v); + + switch( onError ){ + default: { + onError = OE_Abort; + /* no break */ deliberate_fall_through + } + case OE_Rollback: + case OE_Abort: + case OE_Fail: { + testcase( onError==OE_Rollback ); + testcase( onError==OE_Abort ); + testcase( onError==OE_Fail ); + sqlite3RowidConstraint(pParse, onError, pTab); + break; + } + case OE_Replace: { + /* If there are DELETE triggers on this table and the + ** recursive-triggers flag is set, call GenerateRowDelete() to + ** remove the conflicting row from the table. This will fire + ** the triggers and remove both the table and index b-tree entries. + ** + ** Otherwise, if there are no triggers or the recursive-triggers + ** flag is not set, but the table has one or more indexes, call + ** GenerateRowIndexDelete(). This removes the index b-tree entries + ** only. The table b-tree entry will be replaced by the new entry + ** when it is inserted. + ** + ** If either GenerateRowDelete() or GenerateRowIndexDelete() is called, + ** also invoke MultiWrite() to indicate that this VDBE may require + ** statement rollback (if the statement is aborted after the delete + ** takes place). Earlier versions called sqlite3MultiWrite() regardless, + ** but being more selective here allows statements like: + ** + ** REPLACE INTO t(rowid) VALUES($newrowid) + ** + ** to run without a statement journal if there are no indexes on the + ** table. + */ + if( regTrigCnt ){ + sqlite3MultiWrite(pParse); + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + regNewData, 1, 0, OE_Replace, 1, -1); + sqlite3VdbeAddOp2(v, OP_AddImm, regTrigCnt, 1); /* incr trigger cnt */ + nReplaceTrig++; + }else{ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + assert( HasRowid(pTab) ); + /* This OP_Delete opcode fires the pre-update-hook only. It does + ** not modify the b-tree. It is more efficient to let the coming + ** OP_Insert replace the existing entry than it is to delete the + ** existing entry and then insert a new one. */ + sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, OPFLAG_ISNOOP); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + if( pTab->pIndex ){ + sqlite3MultiWrite(pParse); + sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,-1); + } + } + seenReplace = 1; + break; + } +#ifndef SQLITE_OMIT_UPSERT + case OE_Update: { + sqlite3UpsertDoUpdate(pParse, pUpsert, pTab, 0, iDataCur); + /* no break */ deliberate_fall_through + } +#endif + case OE_Ignore: { + testcase( onError==OE_Ignore ); + sqlite3VdbeGoto(v, ignoreDest); + break; + } + } + sqlite3VdbeResolveLabel(v, addrRowidOk); + if( pUpsert && pUpsertClause!=pUpsert ){ + upsertIpkReturn = sqlite3VdbeAddOp0(v, OP_Goto); + }else if( ipkTop ){ + ipkBottom = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeJumpHere(v, ipkTop-1); + } + } + + /* Test all UNIQUE constraints by creating entries for each UNIQUE + ** index and making sure that duplicate entries do not already exist. + ** Compute the revised record entries for indices as we go. + ** + ** This loop also handles the case of the PRIMARY KEY index for a + ** WITHOUT ROWID table. + */ + for(pIdx = indexIteratorFirst(&sIdxIter, &ix); + pIdx; + pIdx = indexIteratorNext(&sIdxIter, &ix) + ){ + int regIdx; /* Range of registers holding content for pIdx */ + int regR; /* Range of registers holding conflicting PK */ + int iThisCur; /* Cursor for this UNIQUE index */ + int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */ + int addrConflictCk; /* First opcode in the conflict check logic */ + + if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */ + if( pUpsert ){ + pUpsertClause = sqlite3UpsertOfIndex(pUpsert, pIdx); + if( upsertIpkDelay && pUpsertClause==pUpsert ){ + sqlite3VdbeJumpHere(v, upsertIpkDelay); + } + } + addrUniqueOk = sqlite3VdbeMakeLabel(pParse); + if( bAffinityDone==0 ){ + sqlite3TableAffinity(v, pTab, regNewData+1); + bAffinityDone = 1; + } + VdbeNoopComment((v, "prep index %s", pIdx->zName)); + iThisCur = iIdxCur+ix; + + + /* Skip partial indices for which the WHERE clause is not true */ + if( pIdx->pPartIdxWhere ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, aRegIdx[ix]); + pParse->iSelfTab = -(regNewData+1); + sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, addrUniqueOk, + SQLITE_JUMPIFNULL); + pParse->iSelfTab = 0; + } + + /* Create a record for this index entry as it should appear after + ** the insert or update. Store that record in the aRegIdx[ix] register + */ + regIdx = aRegIdx[ix]+1; + for(i=0; i<pIdx->nColumn; i++){ + int iField = pIdx->aiColumn[i]; + int x; + if( iField==XN_EXPR ){ + pParse->iSelfTab = -(regNewData+1); + sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[i].pExpr, regIdx+i); + pParse->iSelfTab = 0; + VdbeComment((v, "%s column %d", pIdx->zName, i)); + }else if( iField==XN_ROWID || iField==pTab->iPKey ){ + x = regNewData; + sqlite3VdbeAddOp2(v, OP_IntCopy, x, regIdx+i); + VdbeComment((v, "rowid")); + }else{ + testcase( sqlite3TableColumnToStorage(pTab, iField)!=iField ); + x = sqlite3TableColumnToStorage(pTab, iField) + regNewData + 1; + sqlite3VdbeAddOp2(v, OP_SCopy, x, regIdx+i); + VdbeComment((v, "%s", pTab->aCol[iField].zCnName)); + } + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]); + VdbeComment((v, "for %s", pIdx->zName)); +#ifdef SQLITE_ENABLE_NULL_TRIM + if( pIdx->idxType==SQLITE_IDXTYPE_PRIMARYKEY ){ + sqlite3SetMakeRecordP5(v, pIdx->pTable); + } +#endif + sqlite3VdbeReleaseRegisters(pParse, regIdx, pIdx->nColumn, 0, 0); + + /* In an UPDATE operation, if this index is the PRIMARY KEY index + ** of a WITHOUT ROWID table and there has been no change the + ** primary key, then no collision is possible. The collision detection + ** logic below can all be skipped. */ + if( isUpdate && pPk==pIdx && pkChng==0 ){ + sqlite3VdbeResolveLabel(v, addrUniqueOk); + continue; + } + + /* Find out what action to take in case there is a uniqueness conflict */ + onError = pIdx->onError; + if( onError==OE_None ){ + sqlite3VdbeResolveLabel(v, addrUniqueOk); + continue; /* pIdx is not a UNIQUE index */ + } + if( overrideError!=OE_Default ){ + onError = overrideError; + }else if( onError==OE_Default ){ + onError = OE_Abort; + } + + /* Figure out if the upsert clause applies to this index */ + if( pUpsertClause ){ + if( pUpsertClause->isDoUpdate==0 ){ + onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */ + }else{ + onError = OE_Update; /* DO UPDATE */ + } + } + + /* Collision detection may be omitted if all of the following are true: + ** (1) The conflict resolution algorithm is REPLACE + ** (2) The table is a WITHOUT ROWID table + ** (3) There are no secondary indexes on the table + ** (4) No delete triggers need to be fired if there is a conflict + ** (5) No FK constraint counters need to be updated if a conflict occurs. + ** + ** This is not possible for ENABLE_PREUPDATE_HOOK builds, as the row + ** must be explicitly deleted in order to ensure any pre-update hook + ** is invoked. */ + assert( IsOrdinaryTable(pTab) ); +#ifndef SQLITE_ENABLE_PREUPDATE_HOOK + if( (ix==0 && pIdx->pNext==0) /* Condition 3 */ + && pPk==pIdx /* Condition 2 */ + && onError==OE_Replace /* Condition 1 */ + && ( 0==(db->flags&SQLITE_RecTriggers) || /* Condition 4 */ + 0==sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0)) + && ( 0==(db->flags&SQLITE_ForeignKeys) || /* Condition 5 */ + (0==pTab->u.tab.pFKey && 0==sqlite3FkReferences(pTab))) + ){ + sqlite3VdbeResolveLabel(v, addrUniqueOk); + continue; + } +#endif /* ifndef SQLITE_ENABLE_PREUPDATE_HOOK */ + + /* Check to see if the new index entry will be unique */ + sqlite3VdbeVerifyAbortable(v, onError); + addrConflictCk = + sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk, + regIdx, pIdx->nKeyCol); VdbeCoverage(v); + + /* Generate code to handle collisions */ + regR = pIdx==pPk ? regIdx : sqlite3GetTempRange(pParse, nPkField); + if( isUpdate || onError==OE_Replace ){ + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_IdxRowid, iThisCur, regR); + /* Conflict only if the rowid of the existing index entry + ** is different from old-rowid */ + if( isUpdate ){ + sqlite3VdbeAddOp3(v, OP_Eq, regR, addrUniqueOk, regOldData); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + VdbeCoverage(v); + } + }else{ + int x; + /* Extract the PRIMARY KEY from the end of the index entry and + ** store it in registers regR..regR+nPk-1 */ + if( pIdx!=pPk ){ + for(i=0; i<pPk->nKeyCol; i++){ + assert( pPk->aiColumn[i]>=0 ); + x = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[i]); + sqlite3VdbeAddOp3(v, OP_Column, iThisCur, x, regR+i); + VdbeComment((v, "%s.%s", pTab->zName, + pTab->aCol[pPk->aiColumn[i]].zCnName)); + } + } + if( isUpdate ){ + /* If currently processing the PRIMARY KEY of a WITHOUT ROWID + ** table, only conflict if the new PRIMARY KEY values are actually + ** different from the old. See TH3 withoutrowid04.test. + ** + ** For a UNIQUE index, only conflict if the PRIMARY KEY values + ** of the matched index row are different from the original PRIMARY + ** KEY values of this row before the update. */ + int addrJump = sqlite3VdbeCurrentAddr(v)+pPk->nKeyCol; + int op = OP_Ne; + int regCmp = (IsPrimaryKeyIndex(pIdx) ? regIdx : regR); + + for(i=0; i<pPk->nKeyCol; i++){ + char *p4 = (char*)sqlite3LocateCollSeq(pParse, pPk->azColl[i]); + x = pPk->aiColumn[i]; + assert( x>=0 ); + if( i==(pPk->nKeyCol-1) ){ + addrJump = addrUniqueOk; + op = OP_Eq; + } + x = sqlite3TableColumnToStorage(pTab, x); + sqlite3VdbeAddOp4(v, op, + regOldData+1+x, addrJump, regCmp+i, p4, P4_COLLSEQ + ); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + VdbeCoverageIf(v, op==OP_Eq); + VdbeCoverageIf(v, op==OP_Ne); + } + } + } + } + + /* Generate code that executes if the new index entry is not unique */ + assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail + || onError==OE_Ignore || onError==OE_Replace || onError==OE_Update ); + switch( onError ){ + case OE_Rollback: + case OE_Abort: + case OE_Fail: { + testcase( onError==OE_Rollback ); + testcase( onError==OE_Abort ); + testcase( onError==OE_Fail ); + sqlite3UniqueConstraint(pParse, onError, pIdx); + break; + } +#ifndef SQLITE_OMIT_UPSERT + case OE_Update: { + sqlite3UpsertDoUpdate(pParse, pUpsert, pTab, pIdx, iIdxCur+ix); + /* no break */ deliberate_fall_through + } +#endif + case OE_Ignore: { + testcase( onError==OE_Ignore ); + sqlite3VdbeGoto(v, ignoreDest); + break; + } + default: { + int nConflictCk; /* Number of opcodes in conflict check logic */ + + assert( onError==OE_Replace ); + nConflictCk = sqlite3VdbeCurrentAddr(v) - addrConflictCk; + assert( nConflictCk>0 || db->mallocFailed ); + testcase( nConflictCk<=0 ); + testcase( nConflictCk>1 ); + if( regTrigCnt ){ + sqlite3MultiWrite(pParse); + nReplaceTrig++; + } + if( pTrigger && isUpdate ){ + sqlite3VdbeAddOp1(v, OP_CursorLock, iDataCur); + } + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + regR, nPkField, 0, OE_Replace, + (pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), iThisCur); + if( pTrigger && isUpdate ){ + sqlite3VdbeAddOp1(v, OP_CursorUnlock, iDataCur); + } + if( regTrigCnt ){ + int addrBypass; /* Jump destination to bypass recheck logic */ + + sqlite3VdbeAddOp2(v, OP_AddImm, regTrigCnt, 1); /* incr trigger cnt */ + addrBypass = sqlite3VdbeAddOp0(v, OP_Goto); /* Bypass recheck */ + VdbeComment((v, "bypass recheck")); + + /* Here we insert code that will be invoked after all constraint + ** checks have run, if and only if one or more replace triggers + ** fired. */ + sqlite3VdbeResolveLabel(v, lblRecheckOk); + lblRecheckOk = sqlite3VdbeMakeLabel(pParse); + if( pIdx->pPartIdxWhere ){ + /* Bypass the recheck if this partial index is not defined + ** for the current row */ + sqlite3VdbeAddOp2(v, OP_IsNull, regIdx-1, lblRecheckOk); + VdbeCoverage(v); + } + /* Copy the constraint check code from above, except change + ** the constraint-ok jump destination to be the address of + ** the next retest block */ + while( nConflictCk>0 ){ + VdbeOp x; /* Conflict check opcode to copy */ + /* The sqlite3VdbeAddOp4() call might reallocate the opcode array. + ** Hence, make a complete copy of the opcode, rather than using + ** a pointer to the opcode. */ + x = *sqlite3VdbeGetOp(v, addrConflictCk); + if( x.opcode!=OP_IdxRowid ){ + int p2; /* New P2 value for copied conflict check opcode */ + const char *zP4; + if( sqlite3OpcodeProperty[x.opcode]&OPFLG_JUMP ){ + p2 = lblRecheckOk; + }else{ + p2 = x.p2; + } + zP4 = x.p4type==P4_INT32 ? SQLITE_INT_TO_PTR(x.p4.i) : x.p4.z; + sqlite3VdbeAddOp4(v, x.opcode, x.p1, p2, x.p3, zP4, x.p4type); + sqlite3VdbeChangeP5(v, x.p5); + VdbeCoverageIf(v, p2!=x.p2); + } + nConflictCk--; + addrConflictCk++; + } + /* If the retest fails, issue an abort */ + sqlite3UniqueConstraint(pParse, OE_Abort, pIdx); + + sqlite3VdbeJumpHere(v, addrBypass); /* Terminate the recheck bypass */ + } + seenReplace = 1; + break; + } + } + sqlite3VdbeResolveLabel(v, addrUniqueOk); + if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField); + if( pUpsertClause + && upsertIpkReturn + && sqlite3UpsertNextIsIPK(pUpsertClause) + ){ + sqlite3VdbeGoto(v, upsertIpkDelay+1); + sqlite3VdbeJumpHere(v, upsertIpkReturn); + upsertIpkReturn = 0; + } + } + + /* If the IPK constraint is a REPLACE, run it last */ + if( ipkTop ){ + sqlite3VdbeGoto(v, ipkTop); + VdbeComment((v, "Do IPK REPLACE")); + assert( ipkBottom>0 ); + sqlite3VdbeJumpHere(v, ipkBottom); + } + + /* Recheck all uniqueness constraints after replace triggers have run */ + testcase( regTrigCnt!=0 && nReplaceTrig==0 ); + assert( regTrigCnt!=0 || nReplaceTrig==0 ); + if( nReplaceTrig ){ + sqlite3VdbeAddOp2(v, OP_IfNot, regTrigCnt, lblRecheckOk);VdbeCoverage(v); + if( !pPk ){ + if( isUpdate ){ + sqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRecheck, regOldData); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + VdbeCoverage(v); + } + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, addrRecheck, regNewData); + VdbeCoverage(v); + sqlite3RowidConstraint(pParse, OE_Abort, pTab); + }else{ + sqlite3VdbeGoto(v, addrRecheck); + } + sqlite3VdbeResolveLabel(v, lblRecheckOk); + } + + /* Generate the table record */ + if( HasRowid(pTab) ){ + int regRec = aRegIdx[ix]; + sqlite3VdbeAddOp3(v, OP_MakeRecord, regNewData+1, pTab->nNVCol, regRec); + sqlite3SetMakeRecordP5(v, pTab); + if( !bAffinityDone ){ + sqlite3TableAffinity(v, pTab, 0); + } + } + + *pbMayReplace = seenReplace; + VdbeModuleComment((v, "END: GenCnstCks(%d)", seenReplace)); +} + +#ifdef SQLITE_ENABLE_NULL_TRIM +/* +** Change the P5 operand on the last opcode (which should be an OP_MakeRecord) +** to be the number of columns in table pTab that must not be NULL-trimmed. +** +** Or if no columns of pTab may be NULL-trimmed, leave P5 at zero. +*/ +SQLITE_PRIVATE void sqlite3SetMakeRecordP5(Vdbe *v, Table *pTab){ + u16 i; + + /* Records with omitted columns are only allowed for schema format + ** version 2 and later (SQLite version 3.1.4, 2005-02-20). */ + if( pTab->pSchema->file_format<2 ) return; + + for(i=pTab->nCol-1; i>0; i--){ + if( pTab->aCol[i].iDflt!=0 ) break; + if( pTab->aCol[i].colFlags & COLFLAG_PRIMKEY ) break; + } + sqlite3VdbeChangeP5(v, i+1); +} +#endif + +/* +** Table pTab is a WITHOUT ROWID table that is being written to. The cursor +** number is iCur, and register regData contains the new record for the +** PK index. This function adds code to invoke the pre-update hook, +** if one is registered. +*/ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +static void codeWithoutRowidPreupdate( + Parse *pParse, /* Parse context */ + Table *pTab, /* Table being updated */ + int iCur, /* Cursor number for table */ + int regData /* Data containing new record */ +){ + Vdbe *v = pParse->pVdbe; + int r = sqlite3GetTempReg(pParse); + assert( !HasRowid(pTab) ); + assert( 0==(pParse->db->mDbFlags & DBFLAG_Vacuum) || CORRUPT_DB ); + sqlite3VdbeAddOp2(v, OP_Integer, 0, r); + sqlite3VdbeAddOp4(v, OP_Insert, iCur, regData, r, (char*)pTab, P4_TABLE); + sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP); + sqlite3ReleaseTempReg(pParse, r); +} +#else +# define codeWithoutRowidPreupdate(a,b,c,d) +#endif + +/* +** This routine generates code to finish the INSERT or UPDATE operation +** that was started by a prior call to sqlite3GenerateConstraintChecks. +** A consecutive range of registers starting at regNewData contains the +** rowid and the content to be inserted. +** +** The arguments to this routine should be the same as the first six +** arguments to sqlite3GenerateConstraintChecks. +*/ +SQLITE_PRIVATE void sqlite3CompleteInsertion( + Parse *pParse, /* The parser context */ + Table *pTab, /* the table into which we are inserting */ + int iDataCur, /* Cursor of the canonical data source */ + int iIdxCur, /* First index cursor */ + int regNewData, /* Range of content */ + int *aRegIdx, /* Register used by each index. 0 for unused indices */ + int update_flags, /* True for UPDATE, False for INSERT */ + int appendBias, /* True if this is likely to be an append */ + int useSeekResult /* True to set the USESEEKRESULT flag on OP_[Idx]Insert */ +){ + Vdbe *v; /* Prepared statements under construction */ + Index *pIdx; /* An index being inserted or updated */ + u8 pik_flags; /* flag values passed to the btree insert */ + int i; /* Loop counter */ + + assert( update_flags==0 + || update_flags==OPFLAG_ISUPDATE + || update_flags==(OPFLAG_ISUPDATE|OPFLAG_SAVEPOSITION) + ); + + v = pParse->pVdbe; + assert( v!=0 ); + assert( !IsView(pTab) ); /* This table is not a VIEW */ + for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + /* All REPLACE indexes are at the end of the list */ + assert( pIdx->onError!=OE_Replace + || pIdx->pNext==0 + || pIdx->pNext->onError==OE_Replace ); + if( aRegIdx[i]==0 ) continue; + if( pIdx->pPartIdxWhere ){ + sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2); + VdbeCoverage(v); + } + pik_flags = (useSeekResult ? OPFLAG_USESEEKRESULT : 0); + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ + pik_flags |= OPFLAG_NCHANGE; + pik_flags |= (update_flags & OPFLAG_SAVEPOSITION); + if( update_flags==0 ){ + codeWithoutRowidPreupdate(pParse, pTab, iIdxCur+i, aRegIdx[i]); + } + } + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i], + aRegIdx[i]+1, + pIdx->uniqNotNull ? pIdx->nKeyCol: pIdx->nColumn); + sqlite3VdbeChangeP5(v, pik_flags); + } + if( !HasRowid(pTab) ) return; + if( pParse->nested ){ + pik_flags = 0; + }else{ + pik_flags = OPFLAG_NCHANGE; + pik_flags |= (update_flags?update_flags:OPFLAG_LASTROWID); + } + if( appendBias ){ + pik_flags |= OPFLAG_APPEND; + } + if( useSeekResult ){ + pik_flags |= OPFLAG_USESEEKRESULT; + } + sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, aRegIdx[i], regNewData); + if( !pParse->nested ){ + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + } + sqlite3VdbeChangeP5(v, pik_flags); +} + +/* +** Allocate cursors for the pTab table and all its indices and generate +** code to open and initialized those cursors. +** +** The cursor for the object that contains the complete data (normally +** the table itself, but the PRIMARY KEY index in the case of a WITHOUT +** ROWID table) is returned in *piDataCur. The first index cursor is +** returned in *piIdxCur. The number of indices is returned. +** +** Use iBase as the first cursor (either the *piDataCur for rowid tables +** or the first index for WITHOUT ROWID tables) if it is non-negative. +** If iBase is negative, then allocate the next available cursor. +** +** For a rowid table, *piDataCur will be exactly one less than *piIdxCur. +** For a WITHOUT ROWID table, *piDataCur will be somewhere in the range +** of *piIdxCurs, depending on where the PRIMARY KEY index appears on the +** pTab->pIndex list. +** +** If pTab is a virtual table, then this routine is a no-op and the +** *piDataCur and *piIdxCur values are left uninitialized. +*/ +SQLITE_PRIVATE int sqlite3OpenTableAndIndices( + Parse *pParse, /* Parsing context */ + Table *pTab, /* Table to be opened */ + int op, /* OP_OpenRead or OP_OpenWrite */ + u8 p5, /* P5 value for OP_Open* opcodes (except on WITHOUT ROWID) */ + int iBase, /* Use this for the table cursor, if there is one */ + u8 *aToOpen, /* If not NULL: boolean for each table and index */ + int *piDataCur, /* Write the database source cursor number here */ + int *piIdxCur /* Write the first index cursor number here */ +){ + int i; + int iDb; + int iDataCur; + Index *pIdx; + Vdbe *v; + + assert( op==OP_OpenRead || op==OP_OpenWrite ); + assert( op==OP_OpenWrite || p5==0 ); + assert( piDataCur!=0 ); + assert( piIdxCur!=0 ); + if( IsVirtual(pTab) ){ + /* This routine is a no-op for virtual tables. Leave the output + ** variables *piDataCur and *piIdxCur set to illegal cursor numbers + ** for improved error detection. */ + *piDataCur = *piIdxCur = -999; + return 0; + } + iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + v = pParse->pVdbe; + assert( v!=0 ); + if( iBase<0 ) iBase = pParse->nTab; + iDataCur = iBase++; + *piDataCur = iDataCur; + if( HasRowid(pTab) && (aToOpen==0 || aToOpen[0]) ){ + sqlite3OpenTable(pParse, iDataCur, iDb, pTab, op); + }else if( pParse->db->noSharedCache==0 ){ + sqlite3TableLock(pParse, iDb, pTab->tnum, op==OP_OpenWrite, pTab->zName); + } + *piIdxCur = iBase; + for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + int iIdxCur = iBase++; + assert( pIdx->pSchema==pTab->pSchema ); + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ + *piDataCur = iIdxCur; + p5 = 0; + } + if( aToOpen==0 || aToOpen[i+1] ){ + sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + sqlite3VdbeChangeP5(v, p5); + VdbeComment((v, "%s", pIdx->zName)); + } + } + if( iBase>pParse->nTab ) pParse->nTab = iBase; + return i; +} + + +#ifdef SQLITE_TEST +/* +** The following global variable is incremented whenever the +** transfer optimization is used. This is used for testing +** purposes only - to make sure the transfer optimization really +** is happening when it is supposed to. +*/ +SQLITE_API int sqlite3_xferopt_count; +#endif /* SQLITE_TEST */ + + +#ifndef SQLITE_OMIT_XFER_OPT +/* +** Check to see if index pSrc is compatible as a source of data +** for index pDest in an insert transfer optimization. The rules +** for a compatible index: +** +** * The index is over the same set of columns +** * The same DESC and ASC markings occurs on all columns +** * The same onError processing (OE_Abort, OE_Ignore, etc) +** * The same collating sequence on each column +** * The index has the exact same WHERE clause +*/ +static int xferCompatibleIndex(Index *pDest, Index *pSrc){ + int i; + assert( pDest && pSrc ); + assert( pDest->pTable!=pSrc->pTable ); + if( pDest->nKeyCol!=pSrc->nKeyCol || pDest->nColumn!=pSrc->nColumn ){ + return 0; /* Different number of columns */ + } + if( pDest->onError!=pSrc->onError ){ + return 0; /* Different conflict resolution strategies */ + } + for(i=0; i<pSrc->nKeyCol; i++){ + if( pSrc->aiColumn[i]!=pDest->aiColumn[i] ){ + return 0; /* Different columns indexed */ + } + if( pSrc->aiColumn[i]==XN_EXPR ){ + assert( pSrc->aColExpr!=0 && pDest->aColExpr!=0 ); + if( sqlite3ExprCompare(0, pSrc->aColExpr->a[i].pExpr, + pDest->aColExpr->a[i].pExpr, -1)!=0 ){ + return 0; /* Different expressions in the index */ + } + } + if( pSrc->aSortOrder[i]!=pDest->aSortOrder[i] ){ + return 0; /* Different sort orders */ + } + if( sqlite3_stricmp(pSrc->azColl[i],pDest->azColl[i])!=0 ){ + return 0; /* Different collating sequences */ + } + } + if( sqlite3ExprCompare(0, pSrc->pPartIdxWhere, pDest->pPartIdxWhere, -1) ){ + return 0; /* Different WHERE clauses */ + } + + /* If no test above fails then the indices must be compatible */ + return 1; +} + +/* +** Attempt the transfer optimization on INSERTs of the form +** +** INSERT INTO tab1 SELECT * FROM tab2; +** +** The xfer optimization transfers raw records from tab2 over to tab1. +** Columns are not decoded and reassembled, which greatly improves +** performance. Raw index records are transferred in the same way. +** +** The xfer optimization is only attempted if tab1 and tab2 are compatible. +** There are lots of rules for determining compatibility - see comments +** embedded in the code for details. +** +** This routine returns TRUE if the optimization is guaranteed to be used. +** Sometimes the xfer optimization will only work if the destination table +** is empty - a factor that can only be determined at run-time. In that +** case, this routine generates code for the xfer optimization but also +** does a test to see if the destination table is empty and jumps over the +** xfer optimization code if the test fails. In that case, this routine +** returns FALSE so that the caller will know to go ahead and generate +** an unoptimized transfer. This routine also returns FALSE if there +** is no chance that the xfer optimization can be applied. +** +** This optimization is particularly useful at making VACUUM run faster. +*/ +static int xferOptimization( + Parse *pParse, /* Parser context */ + Table *pDest, /* The table we are inserting into */ + Select *pSelect, /* A SELECT statement to use as the data source */ + int onError, /* How to handle constraint errors */ + int iDbDest /* The database of pDest */ +){ + sqlite3 *db = pParse->db; + ExprList *pEList; /* The result set of the SELECT */ + Table *pSrc; /* The table in the FROM clause of SELECT */ + Index *pSrcIdx, *pDestIdx; /* Source and destination indices */ + SrcItem *pItem; /* An element of pSelect->pSrc */ + int i; /* Loop counter */ + int iDbSrc; /* The database of pSrc */ + int iSrc, iDest; /* Cursors from source and destination */ + int addr1, addr2; /* Loop addresses */ + int emptyDestTest = 0; /* Address of test for empty pDest */ + int emptySrcTest = 0; /* Address of test for empty pSrc */ + Vdbe *v; /* The VDBE we are building */ + int regAutoinc; /* Memory register used by AUTOINC */ + int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */ + int regData, regRowid; /* Registers holding data and rowid */ + + assert( pSelect!=0 ); + if( pParse->pWith || pSelect->pWith ){ + /* Do not attempt to process this query if there are an WITH clauses + ** attached to it. Proceeding may generate a false "no such table: xxx" + ** error if pSelect reads from a CTE named "xxx". */ + return 0; + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pDest) ){ + return 0; /* tab1 must not be a virtual table */ + } +#endif + if( onError==OE_Default ){ + if( pDest->iPKey>=0 ) onError = pDest->keyConf; + if( onError==OE_Default ) onError = OE_Abort; + } + assert(pSelect->pSrc); /* allocated even if there is no FROM clause */ + if( pSelect->pSrc->nSrc!=1 ){ + return 0; /* FROM clause must have exactly one term */ + } + if( pSelect->pSrc->a[0].pSelect ){ + return 0; /* FROM clause cannot contain a subquery */ + } + if( pSelect->pWhere ){ + return 0; /* SELECT may not have a WHERE clause */ + } + if( pSelect->pOrderBy ){ + return 0; /* SELECT may not have an ORDER BY clause */ + } + /* Do not need to test for a HAVING clause. If HAVING is present but + ** there is no ORDER BY, we will get an error. */ + if( pSelect->pGroupBy ){ + return 0; /* SELECT may not have a GROUP BY clause */ + } + if( pSelect->pLimit ){ + return 0; /* SELECT may not have a LIMIT clause */ + } + if( pSelect->pPrior ){ + return 0; /* SELECT may not be a compound query */ + } + if( pSelect->selFlags & SF_Distinct ){ + return 0; /* SELECT may not be DISTINCT */ + } + pEList = pSelect->pEList; + assert( pEList!=0 ); + if( pEList->nExpr!=1 ){ + return 0; /* The result set must have exactly one column */ + } + assert( pEList->a[0].pExpr ); + if( pEList->a[0].pExpr->op!=TK_ASTERISK ){ + return 0; /* The result set must be the special operator "*" */ + } + + /* At this point we have established that the statement is of the + ** correct syntactic form to participate in this optimization. Now + ** we have to check the semantics. + */ + pItem = pSelect->pSrc->a; + pSrc = sqlite3LocateTableItem(pParse, 0, pItem); + if( pSrc==0 ){ + return 0; /* FROM clause does not contain a real table */ + } + if( pSrc->tnum==pDest->tnum && pSrc->pSchema==pDest->pSchema ){ + testcase( pSrc!=pDest ); /* Possible due to bad sqlite_schema.rootpage */ + return 0; /* tab1 and tab2 may not be the same table */ + } + if( HasRowid(pDest)!=HasRowid(pSrc) ){ + return 0; /* source and destination must both be WITHOUT ROWID or not */ + } + if( !IsOrdinaryTable(pSrc) ){ + return 0; /* tab2 may not be a view or virtual table */ + } + if( pDest->nCol!=pSrc->nCol ){ + return 0; /* Number of columns must be the same in tab1 and tab2 */ + } + if( pDest->iPKey!=pSrc->iPKey ){ + return 0; /* Both tables must have the same INTEGER PRIMARY KEY */ + } + if( (pDest->tabFlags & TF_Strict)!=0 && (pSrc->tabFlags & TF_Strict)==0 ){ + return 0; /* Cannot feed from a non-strict into a strict table */ + } + for(i=0; i<pDest->nCol; i++){ + Column *pDestCol = &pDest->aCol[i]; + Column *pSrcCol = &pSrc->aCol[i]; +#ifdef SQLITE_ENABLE_HIDDEN_COLUMNS + if( (db->mDbFlags & DBFLAG_Vacuum)==0 + && (pDestCol->colFlags | pSrcCol->colFlags) & COLFLAG_HIDDEN + ){ + return 0; /* Neither table may have __hidden__ columns */ + } +#endif +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + /* Even if tables t1 and t2 have identical schemas, if they contain + ** generated columns, then this statement is semantically incorrect: + ** + ** INSERT INTO t2 SELECT * FROM t1; + ** + ** The reason is that generated column values are returned by the + ** the SELECT statement on the right but the INSERT statement on the + ** left wants them to be omitted. + ** + ** Nevertheless, this is a useful notational shorthand to tell SQLite + ** to do a bulk transfer all of the content from t1 over to t2. + ** + ** We could, in theory, disable this (except for internal use by the + ** VACUUM command where it is actually needed). But why do that? It + ** seems harmless enough, and provides a useful service. + */ + if( (pDestCol->colFlags & COLFLAG_GENERATED) != + (pSrcCol->colFlags & COLFLAG_GENERATED) ){ + return 0; /* Both columns have the same generated-column type */ + } + /* But the transfer is only allowed if both the source and destination + ** tables have the exact same expressions for generated columns. + ** This requirement could be relaxed for VIRTUAL columns, I suppose. + */ + if( (pDestCol->colFlags & COLFLAG_GENERATED)!=0 ){ + if( sqlite3ExprCompare(0, + sqlite3ColumnExpr(pSrc, pSrcCol), + sqlite3ColumnExpr(pDest, pDestCol), -1)!=0 ){ + testcase( pDestCol->colFlags & COLFLAG_VIRTUAL ); + testcase( pDestCol->colFlags & COLFLAG_STORED ); + return 0; /* Different generator expressions */ + } + } +#endif + if( pDestCol->affinity!=pSrcCol->affinity ){ + return 0; /* Affinity must be the same on all columns */ + } + if( sqlite3_stricmp(sqlite3ColumnColl(pDestCol), + sqlite3ColumnColl(pSrcCol))!=0 ){ + return 0; /* Collating sequence must be the same on all columns */ + } + if( pDestCol->notNull && !pSrcCol->notNull ){ + return 0; /* tab2 must be NOT NULL if tab1 is */ + } + /* Default values for second and subsequent columns need to match. */ + if( (pDestCol->colFlags & COLFLAG_GENERATED)==0 && i>0 ){ + Expr *pDestExpr = sqlite3ColumnExpr(pDest, pDestCol); + Expr *pSrcExpr = sqlite3ColumnExpr(pSrc, pSrcCol); + assert( pDestExpr==0 || pDestExpr->op==TK_SPAN ); + assert( pDestExpr==0 || !ExprHasProperty(pDestExpr, EP_IntValue) ); + assert( pSrcExpr==0 || pSrcExpr->op==TK_SPAN ); + assert( pSrcExpr==0 || !ExprHasProperty(pSrcExpr, EP_IntValue) ); + if( (pDestExpr==0)!=(pSrcExpr==0) + || (pDestExpr!=0 && strcmp(pDestExpr->u.zToken, + pSrcExpr->u.zToken)!=0) + ){ + return 0; /* Default values must be the same for all columns */ + } + } + } + for(pDestIdx=pDest->pIndex; pDestIdx; pDestIdx=pDestIdx->pNext){ + if( IsUniqueIndex(pDestIdx) ){ + destHasUniqueIdx = 1; + } + for(pSrcIdx=pSrc->pIndex; pSrcIdx; pSrcIdx=pSrcIdx->pNext){ + if( xferCompatibleIndex(pDestIdx, pSrcIdx) ) break; + } + if( pSrcIdx==0 ){ + return 0; /* pDestIdx has no corresponding index in pSrc */ + } + if( pSrcIdx->tnum==pDestIdx->tnum && pSrc->pSchema==pDest->pSchema + && sqlite3FaultSim(411)==SQLITE_OK ){ + /* The sqlite3FaultSim() call allows this corruption test to be + ** bypassed during testing, in order to exercise other corruption tests + ** further downstream. */ + return 0; /* Corrupt schema - two indexes on the same btree */ + } + } +#ifndef SQLITE_OMIT_CHECK + if( pDest->pCheck && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) ){ + return 0; /* Tables have different CHECK constraints. Ticket #2252 */ + } +#endif +#ifndef SQLITE_OMIT_FOREIGN_KEY + /* Disallow the transfer optimization if the destination table contains + ** any foreign key constraints. This is more restrictive than necessary. + ** But the main beneficiary of the transfer optimization is the VACUUM + ** command, and the VACUUM command disables foreign key constraints. So + ** the extra complication to make this rule less restrictive is probably + ** not worth the effort. Ticket [6284df89debdfa61db8073e062908af0c9b6118e] + */ + assert( IsOrdinaryTable(pDest) ); + if( (db->flags & SQLITE_ForeignKeys)!=0 && pDest->u.tab.pFKey!=0 ){ + return 0; + } +#endif + if( (db->flags & SQLITE_CountRows)!=0 ){ + return 0; /* xfer opt does not play well with PRAGMA count_changes */ + } + + /* If we get this far, it means that the xfer optimization is at + ** least a possibility, though it might only work if the destination + ** table (tab1) is initially empty. + */ +#ifdef SQLITE_TEST + sqlite3_xferopt_count++; +#endif + iDbSrc = sqlite3SchemaToIndex(db, pSrc->pSchema); + v = sqlite3GetVdbe(pParse); + sqlite3CodeVerifySchema(pParse, iDbSrc); + iSrc = pParse->nTab++; + iDest = pParse->nTab++; + regAutoinc = autoIncBegin(pParse, iDbDest, pDest); + regData = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_Null, 0, regData); + regRowid = sqlite3GetTempReg(pParse); + sqlite3OpenTable(pParse, iDest, iDbDest, pDest, OP_OpenWrite); + assert( HasRowid(pDest) || destHasUniqueIdx ); + if( (db->mDbFlags & DBFLAG_Vacuum)==0 && ( + (pDest->iPKey<0 && pDest->pIndex!=0) /* (1) */ + || destHasUniqueIdx /* (2) */ + || (onError!=OE_Abort && onError!=OE_Rollback) /* (3) */ + )){ + /* In some circumstances, we are able to run the xfer optimization + ** only if the destination table is initially empty. Unless the + ** DBFLAG_Vacuum flag is set, this block generates code to make + ** that determination. If DBFLAG_Vacuum is set, then the destination + ** table is always empty. + ** + ** Conditions under which the destination must be empty: + ** + ** (1) There is no INTEGER PRIMARY KEY but there are indices. + ** (If the destination is not initially empty, the rowid fields + ** of index entries might need to change.) + ** + ** (2) The destination has a unique index. (The xfer optimization + ** is unable to test uniqueness.) + ** + ** (3) onError is something other than OE_Abort and OE_Rollback. + */ + addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0); VdbeCoverage(v); + emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeJumpHere(v, addr1); + } + if( HasRowid(pSrc) ){ + u8 insFlags; + sqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OP_OpenRead); + emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); + if( pDest->iPKey>=0 ){ + addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); + if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){ + sqlite3VdbeVerifyAbortable(v, onError); + addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid); + VdbeCoverage(v); + sqlite3RowidConstraint(pParse, onError, pDest); + sqlite3VdbeJumpHere(v, addr2); + } + autoIncStep(pParse, regAutoinc, regRowid); + }else if( pDest->pIndex==0 && !(db->mDbFlags & DBFLAG_VacuumInto) ){ + addr1 = sqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid); + }else{ + addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); + assert( (pDest->tabFlags & TF_Autoincrement)==0 ); + } + + if( db->mDbFlags & DBFLAG_Vacuum ){ + sqlite3VdbeAddOp1(v, OP_SeekEnd, iDest); + insFlags = OPFLAG_APPEND|OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT; + }else{ + insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND|OPFLAG_PREFORMAT; + } +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){ + sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); + insFlags &= ~OPFLAG_PREFORMAT; + }else +#endif + { + sqlite3VdbeAddOp3(v, OP_RowCell, iDest, iSrc, regRowid); + } + sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid); + if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){ + sqlite3VdbeChangeP4(v, -1, (char*)pDest, P4_TABLE); + } + sqlite3VdbeChangeP5(v, insFlags); + + sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); + sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); + }else{ + sqlite3TableLock(pParse, iDbDest, pDest->tnum, 1, pDest->zName); + sqlite3TableLock(pParse, iDbSrc, pSrc->tnum, 0, pSrc->zName); + } + for(pDestIdx=pDest->pIndex; pDestIdx; pDestIdx=pDestIdx->pNext){ + u8 idxInsFlags = 0; + for(pSrcIdx=pSrc->pIndex; ALWAYS(pSrcIdx); pSrcIdx=pSrcIdx->pNext){ + if( xferCompatibleIndex(pDestIdx, pSrcIdx) ) break; + } + assert( pSrcIdx ); + sqlite3VdbeAddOp3(v, OP_OpenRead, iSrc, pSrcIdx->tnum, iDbSrc); + sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx); + VdbeComment((v, "%s", pSrcIdx->zName)); + sqlite3VdbeAddOp3(v, OP_OpenWrite, iDest, pDestIdx->tnum, iDbDest); + sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx); + sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR); + VdbeComment((v, "%s", pDestIdx->zName)); + addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); + if( db->mDbFlags & DBFLAG_Vacuum ){ + /* This INSERT command is part of a VACUUM operation, which guarantees + ** that the destination table is empty. If all indexed columns use + ** collation sequence BINARY, then it can also be assumed that the + ** index will be populated by inserting keys in strictly sorted + ** order. In this case, instead of seeking within the b-tree as part + ** of every OP_IdxInsert opcode, an OP_SeekEnd is added before the + ** OP_IdxInsert to seek to the point within the b-tree where each key + ** should be inserted. This is faster. + ** + ** If any of the indexed columns use a collation sequence other than + ** BINARY, this optimization is disabled. This is because the user + ** might change the definition of a collation sequence and then run + ** a VACUUM command. In that case keys may not be written in strictly + ** sorted order. */ + for(i=0; i<pSrcIdx->nColumn; i++){ + const char *zColl = pSrcIdx->azColl[i]; + if( sqlite3_stricmp(sqlite3StrBINARY, zColl) ) break; + } + if( i==pSrcIdx->nColumn ){ + idxInsFlags = OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT; + sqlite3VdbeAddOp1(v, OP_SeekEnd, iDest); + sqlite3VdbeAddOp2(v, OP_RowCell, iDest, iSrc); + } + }else if( !HasRowid(pSrc) && pDestIdx->idxType==SQLITE_IDXTYPE_PRIMARYKEY ){ + idxInsFlags |= OPFLAG_NCHANGE; + } + if( idxInsFlags!=(OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT) ){ + sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); + if( (db->mDbFlags & DBFLAG_Vacuum)==0 + && !HasRowid(pDest) + && IsPrimaryKeyIndex(pDestIdx) + ){ + codeWithoutRowidPreupdate(pParse, pDest, iDest, regData); + } + } + sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData); + sqlite3VdbeChangeP5(v, idxInsFlags|OPFLAG_APPEND); + sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addr1); + sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); + sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); + } + if( emptySrcTest ) sqlite3VdbeJumpHere(v, emptySrcTest); + sqlite3ReleaseTempReg(pParse, regRowid); + sqlite3ReleaseTempReg(pParse, regData); + if( emptyDestTest ){ + sqlite3AutoincrementEnd(pParse); + sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_OK, 0); + sqlite3VdbeJumpHere(v, emptyDestTest); + sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); + return 0; + }else{ + return 1; + } +} +#endif /* SQLITE_OMIT_XFER_OPT */ + +/************** End of insert.c **********************************************/ +/************** Begin file legacy.c ******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Main file for the SQLite library. The routines in this file +** implement the programmer interface to the library. Routines in +** other files are for internal use by SQLite and should not be +** accessed by users of the library. +*/ + +/* #include "sqliteInt.h" */ + +/* +** Execute SQL code. Return one of the SQLITE_ success/failure +** codes. Also write an error message into memory obtained from +** malloc() and make *pzErrMsg point to that message. +** +** If the SQL is a query, then for each row in the query result +** the xCallback() function is called. pArg becomes the first +** argument to xCallback(). If xCallback=NULL then no callback +** is invoked, even for queries. +*/ +SQLITE_API int sqlite3_exec( + sqlite3 *db, /* The database on which the SQL executes */ + const char *zSql, /* The SQL to be executed */ + sqlite3_callback xCallback, /* Invoke this callback routine */ + void *pArg, /* First argument to xCallback() */ + char **pzErrMsg /* Write error messages here */ +){ + int rc = SQLITE_OK; /* Return code */ + const char *zLeftover; /* Tail of unprocessed SQL */ + sqlite3_stmt *pStmt = 0; /* The current SQL statement */ + char **azCols = 0; /* Names of result columns */ + int callbackIsInit; /* True if callback data is initialized */ + + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; + if( zSql==0 ) zSql = ""; + + sqlite3_mutex_enter(db->mutex); + sqlite3Error(db, SQLITE_OK); + while( rc==SQLITE_OK && zSql[0] ){ + int nCol = 0; + char **azVals = 0; + + pStmt = 0; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); + assert( rc==SQLITE_OK || pStmt==0 ); + if( rc!=SQLITE_OK ){ + continue; + } + if( !pStmt ){ + /* this happens for a comment or white-space */ + zSql = zLeftover; + continue; + } + callbackIsInit = 0; + + while( 1 ){ + int i; + rc = sqlite3_step(pStmt); + + /* Invoke the callback function if required */ + if( xCallback && (SQLITE_ROW==rc || + (SQLITE_DONE==rc && !callbackIsInit + && db->flags&SQLITE_NullCallback)) ){ + if( !callbackIsInit ){ + nCol = sqlite3_column_count(pStmt); + azCols = sqlite3DbMallocRaw(db, (2*nCol+1)*sizeof(const char*)); + if( azCols==0 ){ + goto exec_out; + } + for(i=0; i<nCol; i++){ + azCols[i] = (char *)sqlite3_column_name(pStmt, i); + /* sqlite3VdbeSetColName() installs column names as UTF8 + ** strings so there is no way for sqlite3_column_name() to fail. */ + assert( azCols[i]!=0 ); + } + callbackIsInit = 1; + } + if( rc==SQLITE_ROW ){ + azVals = &azCols[nCol]; + for(i=0; i<nCol; i++){ + azVals[i] = (char *)sqlite3_column_text(pStmt, i); + if( !azVals[i] && sqlite3_column_type(pStmt, i)!=SQLITE_NULL ){ + sqlite3OomFault(db); + goto exec_out; + } + } + azVals[i] = 0; + } + if( xCallback(pArg, nCol, azVals, azCols) ){ + /* EVIDENCE-OF: R-38229-40159 If the callback function to + ** sqlite3_exec() returns non-zero, then sqlite3_exec() will + ** return SQLITE_ABORT. */ + rc = SQLITE_ABORT; + sqlite3VdbeFinalize((Vdbe *)pStmt); + pStmt = 0; + sqlite3Error(db, SQLITE_ABORT); + goto exec_out; + } + } + + if( rc!=SQLITE_ROW ){ + rc = sqlite3VdbeFinalize((Vdbe *)pStmt); + pStmt = 0; + zSql = zLeftover; + while( sqlite3Isspace(zSql[0]) ) zSql++; + break; + } + } + + sqlite3DbFree(db, azCols); + azCols = 0; + } + +exec_out: + if( pStmt ) sqlite3VdbeFinalize((Vdbe *)pStmt); + sqlite3DbFree(db, azCols); + + rc = sqlite3ApiExit(db, rc); + if( rc!=SQLITE_OK && pzErrMsg ){ + *pzErrMsg = sqlite3DbStrDup(0, sqlite3_errmsg(db)); + if( *pzErrMsg==0 ){ + rc = SQLITE_NOMEM_BKPT; + sqlite3Error(db, SQLITE_NOMEM); + } + }else if( pzErrMsg ){ + *pzErrMsg = 0; + } + + assert( (rc&db->errMask)==rc ); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/************** End of legacy.c **********************************************/ +/************** Begin file loadext.c *****************************************/ +/* +** 2006 June 7 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to dynamically load extensions into +** the SQLite library. +*/ + +#ifndef SQLITE_CORE + #define SQLITE_CORE 1 /* Disable the API redefinition in sqlite3ext.h */ +#endif +/************** Include sqlite3ext.h in the middle of loadext.c **************/ +/************** Begin file sqlite3ext.h **************************************/ +/* +** 2006 June 7 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the SQLite interface for use by +** shared libraries that want to be imported as extensions into +** an SQLite instance. Shared libraries that intend to be loaded +** as extensions by SQLite should #include this file instead of +** sqlite3.h. +*/ +#ifndef SQLITE3EXT_H +#define SQLITE3EXT_H +/* #include "sqlite3.h" */ + +/* +** The following structure holds pointers to all of the SQLite API +** routines. +** +** WARNING: In order to maintain backwards compatibility, add new +** interfaces to the end of this structure only. If you insert new +** interfaces in the middle of this structure, then older different +** versions of SQLite will not be able to load each other's shared +** libraries! +*/ +struct sqlite3_api_routines { + void * (*aggregate_context)(sqlite3_context*,int nBytes); + int (*aggregate_count)(sqlite3_context*); + int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*)); + int (*bind_double)(sqlite3_stmt*,int,double); + int (*bind_int)(sqlite3_stmt*,int,int); + int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64); + int (*bind_null)(sqlite3_stmt*,int); + int (*bind_parameter_count)(sqlite3_stmt*); + int (*bind_parameter_index)(sqlite3_stmt*,const char*zName); + const char * (*bind_parameter_name)(sqlite3_stmt*,int); + int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*)); + int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*)); + int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*); + int (*busy_handler)(sqlite3*,int(*)(void*,int),void*); + int (*busy_timeout)(sqlite3*,int ms); + int (*changes)(sqlite3*); + int (*close)(sqlite3*); + int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*, + int eTextRep,const char*)); + int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*, + int eTextRep,const void*)); + const void * (*column_blob)(sqlite3_stmt*,int iCol); + int (*column_bytes)(sqlite3_stmt*,int iCol); + int (*column_bytes16)(sqlite3_stmt*,int iCol); + int (*column_count)(sqlite3_stmt*pStmt); + const char * (*column_database_name)(sqlite3_stmt*,int); + const void * (*column_database_name16)(sqlite3_stmt*,int); + const char * (*column_decltype)(sqlite3_stmt*,int i); + const void * (*column_decltype16)(sqlite3_stmt*,int); + double (*column_double)(sqlite3_stmt*,int iCol); + int (*column_int)(sqlite3_stmt*,int iCol); + sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol); + const char * (*column_name)(sqlite3_stmt*,int); + const void * (*column_name16)(sqlite3_stmt*,int); + const char * (*column_origin_name)(sqlite3_stmt*,int); + const void * (*column_origin_name16)(sqlite3_stmt*,int); + const char * (*column_table_name)(sqlite3_stmt*,int); + const void * (*column_table_name16)(sqlite3_stmt*,int); + const unsigned char * (*column_text)(sqlite3_stmt*,int iCol); + const void * (*column_text16)(sqlite3_stmt*,int iCol); + int (*column_type)(sqlite3_stmt*,int iCol); + sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol); + void * (*commit_hook)(sqlite3*,int(*)(void*),void*); + int (*complete)(const char*sql); + int (*complete16)(const void*sql); + int (*create_collation)(sqlite3*,const char*,int,void*, + int(*)(void*,int,const void*,int,const void*)); + int (*create_collation16)(sqlite3*,const void*,int,void*, + int(*)(void*,int,const void*,int,const void*)); + int (*create_function)(sqlite3*,const char*,int,int,void*, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*)); + int (*create_function16)(sqlite3*,const void*,int,int,void*, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*)); + int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*); + int (*data_count)(sqlite3_stmt*pStmt); + sqlite3 * (*db_handle)(sqlite3_stmt*); + int (*declare_vtab)(sqlite3*,const char*); + int (*enable_shared_cache)(int); + int (*errcode)(sqlite3*db); + const char * (*errmsg)(sqlite3*); + const void * (*errmsg16)(sqlite3*); + int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**); + int (*expired)(sqlite3_stmt*); + int (*finalize)(sqlite3_stmt*pStmt); + void (*free)(void*); + void (*free_table)(char**result); + int (*get_autocommit)(sqlite3*); + void * (*get_auxdata)(sqlite3_context*,int); + int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**); + int (*global_recover)(void); + void (*interruptx)(sqlite3*); + sqlite_int64 (*last_insert_rowid)(sqlite3*); + const char * (*libversion)(void); + int (*libversion_number)(void); + void *(*malloc)(int); + char * (*mprintf)(const char*,...); + int (*open)(const char*,sqlite3**); + int (*open16)(const void*,sqlite3**); + int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**); + int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**); + void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*); + void (*progress_handler)(sqlite3*,int,int(*)(void*),void*); + void *(*realloc)(void*,int); + int (*reset)(sqlite3_stmt*pStmt); + void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_double)(sqlite3_context*,double); + void (*result_error)(sqlite3_context*,const char*,int); + void (*result_error16)(sqlite3_context*,const void*,int); + void (*result_int)(sqlite3_context*,int); + void (*result_int64)(sqlite3_context*,sqlite_int64); + void (*result_null)(sqlite3_context*); + void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*)); + void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_value)(sqlite3_context*,sqlite3_value*); + void * (*rollback_hook)(sqlite3*,void(*)(void*),void*); + int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*, + const char*,const char*),void*); + void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*)); + char * (*xsnprintf)(int,char*,const char*,...); + int (*step)(sqlite3_stmt*); + int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*, + char const**,char const**,int*,int*,int*); + void (*thread_cleanup)(void); + int (*total_changes)(sqlite3*); + void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*); + int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*); + void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*, + sqlite_int64),void*); + void * (*user_data)(sqlite3_context*); + const void * (*value_blob)(sqlite3_value*); + int (*value_bytes)(sqlite3_value*); + int (*value_bytes16)(sqlite3_value*); + double (*value_double)(sqlite3_value*); + int (*value_int)(sqlite3_value*); + sqlite_int64 (*value_int64)(sqlite3_value*); + int (*value_numeric_type)(sqlite3_value*); + const unsigned char * (*value_text)(sqlite3_value*); + const void * (*value_text16)(sqlite3_value*); + const void * (*value_text16be)(sqlite3_value*); + const void * (*value_text16le)(sqlite3_value*); + int (*value_type)(sqlite3_value*); + char *(*vmprintf)(const char*,va_list); + /* Added ??? */ + int (*overload_function)(sqlite3*, const char *zFuncName, int nArg); + /* Added by 3.3.13 */ + int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**); + int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**); + int (*clear_bindings)(sqlite3_stmt*); + /* Added by 3.4.1 */ + int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*, + void (*xDestroy)(void *)); + /* Added by 3.5.0 */ + int (*bind_zeroblob)(sqlite3_stmt*,int,int); + int (*blob_bytes)(sqlite3_blob*); + int (*blob_close)(sqlite3_blob*); + int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64, + int,sqlite3_blob**); + int (*blob_read)(sqlite3_blob*,void*,int,int); + int (*blob_write)(sqlite3_blob*,const void*,int,int); + int (*create_collation_v2)(sqlite3*,const char*,int,void*, + int(*)(void*,int,const void*,int,const void*), + void(*)(void*)); + int (*file_control)(sqlite3*,const char*,int,void*); + sqlite3_int64 (*memory_highwater)(int); + sqlite3_int64 (*memory_used)(void); + sqlite3_mutex *(*mutex_alloc)(int); + void (*mutex_enter)(sqlite3_mutex*); + void (*mutex_free)(sqlite3_mutex*); + void (*mutex_leave)(sqlite3_mutex*); + int (*mutex_try)(sqlite3_mutex*); + int (*open_v2)(const char*,sqlite3**,int,const char*); + int (*release_memory)(int); + void (*result_error_nomem)(sqlite3_context*); + void (*result_error_toobig)(sqlite3_context*); + int (*sleep)(int); + void (*soft_heap_limit)(int); + sqlite3_vfs *(*vfs_find)(const char*); + int (*vfs_register)(sqlite3_vfs*,int); + int (*vfs_unregister)(sqlite3_vfs*); + int (*xthreadsafe)(void); + void (*result_zeroblob)(sqlite3_context*,int); + void (*result_error_code)(sqlite3_context*,int); + int (*test_control)(int, ...); + void (*randomness)(int,void*); + sqlite3 *(*context_db_handle)(sqlite3_context*); + int (*extended_result_codes)(sqlite3*,int); + int (*limit)(sqlite3*,int,int); + sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*); + const char *(*sql)(sqlite3_stmt*); + int (*status)(int,int*,int*,int); + int (*backup_finish)(sqlite3_backup*); + sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*); + int (*backup_pagecount)(sqlite3_backup*); + int (*backup_remaining)(sqlite3_backup*); + int (*backup_step)(sqlite3_backup*,int); + const char *(*compileoption_get)(int); + int (*compileoption_used)(const char*); + int (*create_function_v2)(sqlite3*,const char*,int,int,void*, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void(*xDestroy)(void*)); + int (*db_config)(sqlite3*,int,...); + sqlite3_mutex *(*db_mutex)(sqlite3*); + int (*db_status)(sqlite3*,int,int*,int*,int); + int (*extended_errcode)(sqlite3*); + void (*log)(int,const char*,...); + sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64); + const char *(*sourceid)(void); + int (*stmt_status)(sqlite3_stmt*,int,int); + int (*strnicmp)(const char*,const char*,int); + int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*); + int (*wal_autocheckpoint)(sqlite3*,int); + int (*wal_checkpoint)(sqlite3*,const char*); + void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*); + int (*blob_reopen)(sqlite3_blob*,sqlite3_int64); + int (*vtab_config)(sqlite3*,int op,...); + int (*vtab_on_conflict)(sqlite3*); + /* Version 3.7.16 and later */ + int (*close_v2)(sqlite3*); + const char *(*db_filename)(sqlite3*,const char*); + int (*db_readonly)(sqlite3*,const char*); + int (*db_release_memory)(sqlite3*); + const char *(*errstr)(int); + int (*stmt_busy)(sqlite3_stmt*); + int (*stmt_readonly)(sqlite3_stmt*); + int (*stricmp)(const char*,const char*); + int (*uri_boolean)(const char*,const char*,int); + sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64); + const char *(*uri_parameter)(const char*,const char*); + char *(*xvsnprintf)(int,char*,const char*,va_list); + int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*); + /* Version 3.8.7 and later */ + int (*auto_extension)(void(*)(void)); + int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64, + void(*)(void*)); + int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64, + void(*)(void*),unsigned char); + int (*cancel_auto_extension)(void(*)(void)); + int (*load_extension)(sqlite3*,const char*,const char*,char**); + void *(*malloc64)(sqlite3_uint64); + sqlite3_uint64 (*msize)(void*); + void *(*realloc64)(void*,sqlite3_uint64); + void (*reset_auto_extension)(void); + void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64, + void(*)(void*)); + void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64, + void(*)(void*), unsigned char); + int (*strglob)(const char*,const char*); + /* Version 3.8.11 and later */ + sqlite3_value *(*value_dup)(const sqlite3_value*); + void (*value_free)(sqlite3_value*); + int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64); + int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64); + /* Version 3.9.0 and later */ + unsigned int (*value_subtype)(sqlite3_value*); + void (*result_subtype)(sqlite3_context*,unsigned int); + /* Version 3.10.0 and later */ + int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int); + int (*strlike)(const char*,const char*,unsigned int); + int (*db_cacheflush)(sqlite3*); + /* Version 3.12.0 and later */ + int (*system_errno)(sqlite3*); + /* Version 3.14.0 and later */ + int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*); + char *(*expanded_sql)(sqlite3_stmt*); + /* Version 3.18.0 and later */ + void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64); + /* Version 3.20.0 and later */ + int (*prepare_v3)(sqlite3*,const char*,int,unsigned int, + sqlite3_stmt**,const char**); + int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int, + sqlite3_stmt**,const void**); + int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*)); + void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*)); + void *(*value_pointer)(sqlite3_value*,const char*); + int (*vtab_nochange)(sqlite3_context*); + int (*value_nochange)(sqlite3_value*); + const char *(*vtab_collation)(sqlite3_index_info*,int); + /* Version 3.24.0 and later */ + int (*keyword_count)(void); + int (*keyword_name)(int,const char**,int*); + int (*keyword_check)(const char*,int); + sqlite3_str *(*str_new)(sqlite3*); + char *(*str_finish)(sqlite3_str*); + void (*str_appendf)(sqlite3_str*, const char *zFormat, ...); + void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list); + void (*str_append)(sqlite3_str*, const char *zIn, int N); + void (*str_appendall)(sqlite3_str*, const char *zIn); + void (*str_appendchar)(sqlite3_str*, int N, char C); + void (*str_reset)(sqlite3_str*); + int (*str_errcode)(sqlite3_str*); + int (*str_length)(sqlite3_str*); + char *(*str_value)(sqlite3_str*); + /* Version 3.25.0 and later */ + int (*create_window_function)(sqlite3*,const char*,int,int,void*, + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void (*xValue)(sqlite3_context*), + void (*xInv)(sqlite3_context*,int,sqlite3_value**), + void(*xDestroy)(void*)); + /* Version 3.26.0 and later */ + const char *(*normalized_sql)(sqlite3_stmt*); + /* Version 3.28.0 and later */ + int (*stmt_isexplain)(sqlite3_stmt*); + int (*value_frombind)(sqlite3_value*); + /* Version 3.30.0 and later */ + int (*drop_modules)(sqlite3*,const char**); + /* Version 3.31.0 and later */ + sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64); + const char *(*uri_key)(const char*,int); + const char *(*filename_database)(const char*); + const char *(*filename_journal)(const char*); + const char *(*filename_wal)(const char*); + /* Version 3.32.0 and later */ + const char *(*create_filename)(const char*,const char*,const char*, + int,const char**); + void (*free_filename)(const char*); + sqlite3_file *(*database_file_object)(const char*); + /* Version 3.34.0 and later */ + int (*txn_state)(sqlite3*,const char*); + /* Version 3.36.1 and later */ + sqlite3_int64 (*changes64)(sqlite3*); + sqlite3_int64 (*total_changes64)(sqlite3*); + /* Version 3.37.0 and later */ + int (*autovacuum_pages)(sqlite3*, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, void(*)(void*)); + /* Version 3.38.0 and later */ + int (*error_offset)(sqlite3*); + int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**); + int (*vtab_distinct)(sqlite3_index_info*); + int (*vtab_in)(sqlite3_index_info*,int,int); + int (*vtab_in_first)(sqlite3_value*,sqlite3_value**); + int (*vtab_in_next)(sqlite3_value*,sqlite3_value**); + /* Version 3.39.0 and later */ + int (*deserialize)(sqlite3*,const char*,unsigned char*, + sqlite3_int64,sqlite3_int64,unsigned); + unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*, + unsigned int); + const char *(*db_name)(sqlite3*,int); + /* Version 3.40.0 and later */ + int (*value_encoding)(sqlite3_value*); + /* Version 3.41.0 and later */ + int (*is_interrupted)(sqlite3*); + /* Version 3.43.0 and later */ + int (*stmt_explain)(sqlite3_stmt*,int); +}; + +/* +** This is the function signature used for all extension entry points. It +** is also defined in the file "loadext.c". +*/ +typedef int (*sqlite3_loadext_entry)( + sqlite3 *db, /* Handle to the database. */ + char **pzErrMsg, /* Used to set error string on failure. */ + const sqlite3_api_routines *pThunk /* Extension API function pointers. */ +); + +/* +** The following macros redefine the API routines so that they are +** redirected through the global sqlite3_api structure. +** +** This header file is also used by the loadext.c source file +** (part of the main SQLite library - not an extension) so that +** it can get access to the sqlite3_api_routines structure +** definition. But the main library does not want to redefine +** the API. So the redefinition macros are only valid if the +** SQLITE_CORE macros is undefined. +*/ +#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) +#define sqlite3_aggregate_context sqlite3_api->aggregate_context +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_aggregate_count sqlite3_api->aggregate_count +#endif +#define sqlite3_bind_blob sqlite3_api->bind_blob +#define sqlite3_bind_double sqlite3_api->bind_double +#define sqlite3_bind_int sqlite3_api->bind_int +#define sqlite3_bind_int64 sqlite3_api->bind_int64 +#define sqlite3_bind_null sqlite3_api->bind_null +#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count +#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index +#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name +#define sqlite3_bind_text sqlite3_api->bind_text +#define sqlite3_bind_text16 sqlite3_api->bind_text16 +#define sqlite3_bind_value sqlite3_api->bind_value +#define sqlite3_busy_handler sqlite3_api->busy_handler +#define sqlite3_busy_timeout sqlite3_api->busy_timeout +#define sqlite3_changes sqlite3_api->changes +#define sqlite3_close sqlite3_api->close +#define sqlite3_collation_needed sqlite3_api->collation_needed +#define sqlite3_collation_needed16 sqlite3_api->collation_needed16 +#define sqlite3_column_blob sqlite3_api->column_blob +#define sqlite3_column_bytes sqlite3_api->column_bytes +#define sqlite3_column_bytes16 sqlite3_api->column_bytes16 +#define sqlite3_column_count sqlite3_api->column_count +#define sqlite3_column_database_name sqlite3_api->column_database_name +#define sqlite3_column_database_name16 sqlite3_api->column_database_name16 +#define sqlite3_column_decltype sqlite3_api->column_decltype +#define sqlite3_column_decltype16 sqlite3_api->column_decltype16 +#define sqlite3_column_double sqlite3_api->column_double +#define sqlite3_column_int sqlite3_api->column_int +#define sqlite3_column_int64 sqlite3_api->column_int64 +#define sqlite3_column_name sqlite3_api->column_name +#define sqlite3_column_name16 sqlite3_api->column_name16 +#define sqlite3_column_origin_name sqlite3_api->column_origin_name +#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16 +#define sqlite3_column_table_name sqlite3_api->column_table_name +#define sqlite3_column_table_name16 sqlite3_api->column_table_name16 +#define sqlite3_column_text sqlite3_api->column_text +#define sqlite3_column_text16 sqlite3_api->column_text16 +#define sqlite3_column_type sqlite3_api->column_type +#define sqlite3_column_value sqlite3_api->column_value +#define sqlite3_commit_hook sqlite3_api->commit_hook +#define sqlite3_complete sqlite3_api->complete +#define sqlite3_complete16 sqlite3_api->complete16 +#define sqlite3_create_collation sqlite3_api->create_collation +#define sqlite3_create_collation16 sqlite3_api->create_collation16 +#define sqlite3_create_function sqlite3_api->create_function +#define sqlite3_create_function16 sqlite3_api->create_function16 +#define sqlite3_create_module sqlite3_api->create_module +#define sqlite3_create_module_v2 sqlite3_api->create_module_v2 +#define sqlite3_data_count sqlite3_api->data_count +#define sqlite3_db_handle sqlite3_api->db_handle +#define sqlite3_declare_vtab sqlite3_api->declare_vtab +#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache +#define sqlite3_errcode sqlite3_api->errcode +#define sqlite3_errmsg sqlite3_api->errmsg +#define sqlite3_errmsg16 sqlite3_api->errmsg16 +#define sqlite3_exec sqlite3_api->exec +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_expired sqlite3_api->expired +#endif +#define sqlite3_finalize sqlite3_api->finalize +#define sqlite3_free sqlite3_api->free +#define sqlite3_free_table sqlite3_api->free_table +#define sqlite3_get_autocommit sqlite3_api->get_autocommit +#define sqlite3_get_auxdata sqlite3_api->get_auxdata +#define sqlite3_get_table sqlite3_api->get_table +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_global_recover sqlite3_api->global_recover +#endif +#define sqlite3_interrupt sqlite3_api->interruptx +#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid +#define sqlite3_libversion sqlite3_api->libversion +#define sqlite3_libversion_number sqlite3_api->libversion_number +#define sqlite3_malloc sqlite3_api->malloc +#define sqlite3_mprintf sqlite3_api->mprintf +#define sqlite3_open sqlite3_api->open +#define sqlite3_open16 sqlite3_api->open16 +#define sqlite3_prepare sqlite3_api->prepare +#define sqlite3_prepare16 sqlite3_api->prepare16 +#define sqlite3_prepare_v2 sqlite3_api->prepare_v2 +#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2 +#define sqlite3_profile sqlite3_api->profile +#define sqlite3_progress_handler sqlite3_api->progress_handler +#define sqlite3_realloc sqlite3_api->realloc +#define sqlite3_reset sqlite3_api->reset +#define sqlite3_result_blob sqlite3_api->result_blob +#define sqlite3_result_double sqlite3_api->result_double +#define sqlite3_result_error sqlite3_api->result_error +#define sqlite3_result_error16 sqlite3_api->result_error16 +#define sqlite3_result_int sqlite3_api->result_int +#define sqlite3_result_int64 sqlite3_api->result_int64 +#define sqlite3_result_null sqlite3_api->result_null +#define sqlite3_result_text sqlite3_api->result_text +#define sqlite3_result_text16 sqlite3_api->result_text16 +#define sqlite3_result_text16be sqlite3_api->result_text16be +#define sqlite3_result_text16le sqlite3_api->result_text16le +#define sqlite3_result_value sqlite3_api->result_value +#define sqlite3_rollback_hook sqlite3_api->rollback_hook +#define sqlite3_set_authorizer sqlite3_api->set_authorizer +#define sqlite3_set_auxdata sqlite3_api->set_auxdata +#define sqlite3_snprintf sqlite3_api->xsnprintf +#define sqlite3_step sqlite3_api->step +#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata +#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup +#define sqlite3_total_changes sqlite3_api->total_changes +#define sqlite3_trace sqlite3_api->trace +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings +#endif +#define sqlite3_update_hook sqlite3_api->update_hook +#define sqlite3_user_data sqlite3_api->user_data +#define sqlite3_value_blob sqlite3_api->value_blob +#define sqlite3_value_bytes sqlite3_api->value_bytes +#define sqlite3_value_bytes16 sqlite3_api->value_bytes16 +#define sqlite3_value_double sqlite3_api->value_double +#define sqlite3_value_int sqlite3_api->value_int +#define sqlite3_value_int64 sqlite3_api->value_int64 +#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type +#define sqlite3_value_text sqlite3_api->value_text +#define sqlite3_value_text16 sqlite3_api->value_text16 +#define sqlite3_value_text16be sqlite3_api->value_text16be +#define sqlite3_value_text16le sqlite3_api->value_text16le +#define sqlite3_value_type sqlite3_api->value_type +#define sqlite3_vmprintf sqlite3_api->vmprintf +#define sqlite3_vsnprintf sqlite3_api->xvsnprintf +#define sqlite3_overload_function sqlite3_api->overload_function +#define sqlite3_prepare_v2 sqlite3_api->prepare_v2 +#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2 +#define sqlite3_clear_bindings sqlite3_api->clear_bindings +#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob +#define sqlite3_blob_bytes sqlite3_api->blob_bytes +#define sqlite3_blob_close sqlite3_api->blob_close +#define sqlite3_blob_open sqlite3_api->blob_open +#define sqlite3_blob_read sqlite3_api->blob_read +#define sqlite3_blob_write sqlite3_api->blob_write +#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2 +#define sqlite3_file_control sqlite3_api->file_control +#define sqlite3_memory_highwater sqlite3_api->memory_highwater +#define sqlite3_memory_used sqlite3_api->memory_used +#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc +#define sqlite3_mutex_enter sqlite3_api->mutex_enter +#define sqlite3_mutex_free sqlite3_api->mutex_free +#define sqlite3_mutex_leave sqlite3_api->mutex_leave +#define sqlite3_mutex_try sqlite3_api->mutex_try +#define sqlite3_open_v2 sqlite3_api->open_v2 +#define sqlite3_release_memory sqlite3_api->release_memory +#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem +#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig +#define sqlite3_sleep sqlite3_api->sleep +#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit +#define sqlite3_vfs_find sqlite3_api->vfs_find +#define sqlite3_vfs_register sqlite3_api->vfs_register +#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister +#define sqlite3_threadsafe sqlite3_api->xthreadsafe +#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob +#define sqlite3_result_error_code sqlite3_api->result_error_code +#define sqlite3_test_control sqlite3_api->test_control +#define sqlite3_randomness sqlite3_api->randomness +#define sqlite3_context_db_handle sqlite3_api->context_db_handle +#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes +#define sqlite3_limit sqlite3_api->limit +#define sqlite3_next_stmt sqlite3_api->next_stmt +#define sqlite3_sql sqlite3_api->sql +#define sqlite3_status sqlite3_api->status +#define sqlite3_backup_finish sqlite3_api->backup_finish +#define sqlite3_backup_init sqlite3_api->backup_init +#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount +#define sqlite3_backup_remaining sqlite3_api->backup_remaining +#define sqlite3_backup_step sqlite3_api->backup_step +#define sqlite3_compileoption_get sqlite3_api->compileoption_get +#define sqlite3_compileoption_used sqlite3_api->compileoption_used +#define sqlite3_create_function_v2 sqlite3_api->create_function_v2 +#define sqlite3_db_config sqlite3_api->db_config +#define sqlite3_db_mutex sqlite3_api->db_mutex +#define sqlite3_db_status sqlite3_api->db_status +#define sqlite3_extended_errcode sqlite3_api->extended_errcode +#define sqlite3_log sqlite3_api->log +#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64 +#define sqlite3_sourceid sqlite3_api->sourceid +#define sqlite3_stmt_status sqlite3_api->stmt_status +#define sqlite3_strnicmp sqlite3_api->strnicmp +#define sqlite3_unlock_notify sqlite3_api->unlock_notify +#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint +#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint +#define sqlite3_wal_hook sqlite3_api->wal_hook +#define sqlite3_blob_reopen sqlite3_api->blob_reopen +#define sqlite3_vtab_config sqlite3_api->vtab_config +#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict +/* Version 3.7.16 and later */ +#define sqlite3_close_v2 sqlite3_api->close_v2 +#define sqlite3_db_filename sqlite3_api->db_filename +#define sqlite3_db_readonly sqlite3_api->db_readonly +#define sqlite3_db_release_memory sqlite3_api->db_release_memory +#define sqlite3_errstr sqlite3_api->errstr +#define sqlite3_stmt_busy sqlite3_api->stmt_busy +#define sqlite3_stmt_readonly sqlite3_api->stmt_readonly +#define sqlite3_stricmp sqlite3_api->stricmp +#define sqlite3_uri_boolean sqlite3_api->uri_boolean +#define sqlite3_uri_int64 sqlite3_api->uri_int64 +#define sqlite3_uri_parameter sqlite3_api->uri_parameter +#define sqlite3_uri_vsnprintf sqlite3_api->xvsnprintf +#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2 +/* Version 3.8.7 and later */ +#define sqlite3_auto_extension sqlite3_api->auto_extension +#define sqlite3_bind_blob64 sqlite3_api->bind_blob64 +#define sqlite3_bind_text64 sqlite3_api->bind_text64 +#define sqlite3_cancel_auto_extension sqlite3_api->cancel_auto_extension +#define sqlite3_load_extension sqlite3_api->load_extension +#define sqlite3_malloc64 sqlite3_api->malloc64 +#define sqlite3_msize sqlite3_api->msize +#define sqlite3_realloc64 sqlite3_api->realloc64 +#define sqlite3_reset_auto_extension sqlite3_api->reset_auto_extension +#define sqlite3_result_blob64 sqlite3_api->result_blob64 +#define sqlite3_result_text64 sqlite3_api->result_text64 +#define sqlite3_strglob sqlite3_api->strglob +/* Version 3.8.11 and later */ +#define sqlite3_value_dup sqlite3_api->value_dup +#define sqlite3_value_free sqlite3_api->value_free +#define sqlite3_result_zeroblob64 sqlite3_api->result_zeroblob64 +#define sqlite3_bind_zeroblob64 sqlite3_api->bind_zeroblob64 +/* Version 3.9.0 and later */ +#define sqlite3_value_subtype sqlite3_api->value_subtype +#define sqlite3_result_subtype sqlite3_api->result_subtype +/* Version 3.10.0 and later */ +#define sqlite3_status64 sqlite3_api->status64 +#define sqlite3_strlike sqlite3_api->strlike +#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush +/* Version 3.12.0 and later */ +#define sqlite3_system_errno sqlite3_api->system_errno +/* Version 3.14.0 and later */ +#define sqlite3_trace_v2 sqlite3_api->trace_v2 +#define sqlite3_expanded_sql sqlite3_api->expanded_sql +/* Version 3.18.0 and later */ +#define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid +/* Version 3.20.0 and later */ +#define sqlite3_prepare_v3 sqlite3_api->prepare_v3 +#define sqlite3_prepare16_v3 sqlite3_api->prepare16_v3 +#define sqlite3_bind_pointer sqlite3_api->bind_pointer +#define sqlite3_result_pointer sqlite3_api->result_pointer +#define sqlite3_value_pointer sqlite3_api->value_pointer +/* Version 3.22.0 and later */ +#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange +#define sqlite3_value_nochange sqlite3_api->value_nochange +#define sqlite3_vtab_collation sqlite3_api->vtab_collation +/* Version 3.24.0 and later */ +#define sqlite3_keyword_count sqlite3_api->keyword_count +#define sqlite3_keyword_name sqlite3_api->keyword_name +#define sqlite3_keyword_check sqlite3_api->keyword_check +#define sqlite3_str_new sqlite3_api->str_new +#define sqlite3_str_finish sqlite3_api->str_finish +#define sqlite3_str_appendf sqlite3_api->str_appendf +#define sqlite3_str_vappendf sqlite3_api->str_vappendf +#define sqlite3_str_append sqlite3_api->str_append +#define sqlite3_str_appendall sqlite3_api->str_appendall +#define sqlite3_str_appendchar sqlite3_api->str_appendchar +#define sqlite3_str_reset sqlite3_api->str_reset +#define sqlite3_str_errcode sqlite3_api->str_errcode +#define sqlite3_str_length sqlite3_api->str_length +#define sqlite3_str_value sqlite3_api->str_value +/* Version 3.25.0 and later */ +#define sqlite3_create_window_function sqlite3_api->create_window_function +/* Version 3.26.0 and later */ +#define sqlite3_normalized_sql sqlite3_api->normalized_sql +/* Version 3.28.0 and later */ +#define sqlite3_stmt_isexplain sqlite3_api->stmt_isexplain +#define sqlite3_value_frombind sqlite3_api->value_frombind +/* Version 3.30.0 and later */ +#define sqlite3_drop_modules sqlite3_api->drop_modules +/* Version 3.31.0 and later */ +#define sqlite3_hard_heap_limit64 sqlite3_api->hard_heap_limit64 +#define sqlite3_uri_key sqlite3_api->uri_key +#define sqlite3_filename_database sqlite3_api->filename_database +#define sqlite3_filename_journal sqlite3_api->filename_journal +#define sqlite3_filename_wal sqlite3_api->filename_wal +/* Version 3.32.0 and later */ +#define sqlite3_create_filename sqlite3_api->create_filename +#define sqlite3_free_filename sqlite3_api->free_filename +#define sqlite3_database_file_object sqlite3_api->database_file_object +/* Version 3.34.0 and later */ +#define sqlite3_txn_state sqlite3_api->txn_state +/* Version 3.36.1 and later */ +#define sqlite3_changes64 sqlite3_api->changes64 +#define sqlite3_total_changes64 sqlite3_api->total_changes64 +/* Version 3.37.0 and later */ +#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages +/* Version 3.38.0 and later */ +#define sqlite3_error_offset sqlite3_api->error_offset +#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value +#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct +#define sqlite3_vtab_in sqlite3_api->vtab_in +#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first +#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next +/* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE +#define sqlite3_deserialize sqlite3_api->deserialize +#define sqlite3_serialize sqlite3_api->serialize +#endif +#define sqlite3_db_name sqlite3_api->db_name +/* Version 3.40.0 and later */ +#define sqlite3_value_encoding sqlite3_api->value_encoding +/* Version 3.41.0 and later */ +#define sqlite3_is_interrupted sqlite3_api->is_interrupted +/* Version 3.43.0 and later */ +#define sqlite3_stmt_explain sqlite3_api->stmt_explain +#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ + +#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) + /* This case when the file really is being compiled as a loadable + ** extension */ +# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0; +# define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v; +# define SQLITE_EXTENSION_INIT3 \ + extern const sqlite3_api_routines *sqlite3_api; +#else + /* This case when the file is being statically linked into the + ** application */ +# define SQLITE_EXTENSION_INIT1 /*no-op*/ +# define SQLITE_EXTENSION_INIT2(v) (void)v; /* unused parameter */ +# define SQLITE_EXTENSION_INIT3 /*no-op*/ +#endif + +#endif /* SQLITE3EXT_H */ + +/************** End of sqlite3ext.h ******************************************/ +/************** Continuing where we left off in loadext.c ********************/ +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_OMIT_LOAD_EXTENSION +/* +** Some API routines are omitted when various features are +** excluded from a build of SQLite. Substitute a NULL pointer +** for any missing APIs. +*/ +#ifndef SQLITE_ENABLE_COLUMN_METADATA +# define sqlite3_column_database_name 0 +# define sqlite3_column_database_name16 0 +# define sqlite3_column_table_name 0 +# define sqlite3_column_table_name16 0 +# define sqlite3_column_origin_name 0 +# define sqlite3_column_origin_name16 0 +#endif + +#ifdef SQLITE_OMIT_AUTHORIZATION +# define sqlite3_set_authorizer 0 +#endif + +#ifdef SQLITE_OMIT_UTF16 +# define sqlite3_bind_text16 0 +# define sqlite3_collation_needed16 0 +# define sqlite3_column_decltype16 0 +# define sqlite3_column_name16 0 +# define sqlite3_column_text16 0 +# define sqlite3_complete16 0 +# define sqlite3_create_collation16 0 +# define sqlite3_create_function16 0 +# define sqlite3_errmsg16 0 +# define sqlite3_open16 0 +# define sqlite3_prepare16 0 +# define sqlite3_prepare16_v2 0 +# define sqlite3_prepare16_v3 0 +# define sqlite3_result_error16 0 +# define sqlite3_result_text16 0 +# define sqlite3_result_text16be 0 +# define sqlite3_result_text16le 0 +# define sqlite3_value_text16 0 +# define sqlite3_value_text16be 0 +# define sqlite3_value_text16le 0 +# define sqlite3_column_database_name16 0 +# define sqlite3_column_table_name16 0 +# define sqlite3_column_origin_name16 0 +#endif + +#ifdef SQLITE_OMIT_COMPLETE +# define sqlite3_complete 0 +# define sqlite3_complete16 0 +#endif + +#ifdef SQLITE_OMIT_DECLTYPE +# define sqlite3_column_decltype16 0 +# define sqlite3_column_decltype 0 +#endif + +#ifdef SQLITE_OMIT_PROGRESS_CALLBACK +# define sqlite3_progress_handler 0 +#endif + +#ifdef SQLITE_OMIT_VIRTUALTABLE +# define sqlite3_create_module 0 +# define sqlite3_create_module_v2 0 +# define sqlite3_declare_vtab 0 +# define sqlite3_vtab_config 0 +# define sqlite3_vtab_on_conflict 0 +# define sqlite3_vtab_collation 0 +#endif + +#ifdef SQLITE_OMIT_SHARED_CACHE +# define sqlite3_enable_shared_cache 0 +#endif + +#if defined(SQLITE_OMIT_TRACE) || defined(SQLITE_OMIT_DEPRECATED) +# define sqlite3_profile 0 +# define sqlite3_trace 0 +#endif + +#ifdef SQLITE_OMIT_GET_TABLE +# define sqlite3_free_table 0 +# define sqlite3_get_table 0 +#endif + +#ifdef SQLITE_OMIT_INCRBLOB +#define sqlite3_bind_zeroblob 0 +#define sqlite3_blob_bytes 0 +#define sqlite3_blob_close 0 +#define sqlite3_blob_open 0 +#define sqlite3_blob_read 0 +#define sqlite3_blob_write 0 +#define sqlite3_blob_reopen 0 +#endif + +#if defined(SQLITE_OMIT_TRACE) +# define sqlite3_trace_v2 0 +#endif + +/* +** The following structure contains pointers to all SQLite API routines. +** A pointer to this structure is passed into extensions when they are +** loaded so that the extension can make calls back into the SQLite +** library. +** +** When adding new APIs, add them to the bottom of this structure +** in order to preserve backwards compatibility. +** +** Extensions that use newer APIs should first call the +** sqlite3_libversion_number() to make sure that the API they +** intend to use is supported by the library. Extensions should +** also check to make sure that the pointer to the function is +** not NULL before calling it. +*/ +static const sqlite3_api_routines sqlite3Apis = { + sqlite3_aggregate_context, +#ifndef SQLITE_OMIT_DEPRECATED + sqlite3_aggregate_count, +#else + 0, +#endif + sqlite3_bind_blob, + sqlite3_bind_double, + sqlite3_bind_int, + sqlite3_bind_int64, + sqlite3_bind_null, + sqlite3_bind_parameter_count, + sqlite3_bind_parameter_index, + sqlite3_bind_parameter_name, + sqlite3_bind_text, + sqlite3_bind_text16, + sqlite3_bind_value, + sqlite3_busy_handler, + sqlite3_busy_timeout, + sqlite3_changes, + sqlite3_close, + sqlite3_collation_needed, + sqlite3_collation_needed16, + sqlite3_column_blob, + sqlite3_column_bytes, + sqlite3_column_bytes16, + sqlite3_column_count, + sqlite3_column_database_name, + sqlite3_column_database_name16, + sqlite3_column_decltype, + sqlite3_column_decltype16, + sqlite3_column_double, + sqlite3_column_int, + sqlite3_column_int64, + sqlite3_column_name, + sqlite3_column_name16, + sqlite3_column_origin_name, + sqlite3_column_origin_name16, + sqlite3_column_table_name, + sqlite3_column_table_name16, + sqlite3_column_text, + sqlite3_column_text16, + sqlite3_column_type, + sqlite3_column_value, + sqlite3_commit_hook, + sqlite3_complete, + sqlite3_complete16, + sqlite3_create_collation, + sqlite3_create_collation16, + sqlite3_create_function, + sqlite3_create_function16, + sqlite3_create_module, + sqlite3_data_count, + sqlite3_db_handle, + sqlite3_declare_vtab, + sqlite3_enable_shared_cache, + sqlite3_errcode, + sqlite3_errmsg, + sqlite3_errmsg16, + sqlite3_exec, +#ifndef SQLITE_OMIT_DEPRECATED + sqlite3_expired, +#else + 0, +#endif + sqlite3_finalize, + sqlite3_free, + sqlite3_free_table, + sqlite3_get_autocommit, + sqlite3_get_auxdata, + sqlite3_get_table, + 0, /* Was sqlite3_global_recover(), but that function is deprecated */ + sqlite3_interrupt, + sqlite3_last_insert_rowid, + sqlite3_libversion, + sqlite3_libversion_number, + sqlite3_malloc, + sqlite3_mprintf, + sqlite3_open, + sqlite3_open16, + sqlite3_prepare, + sqlite3_prepare16, + sqlite3_profile, + sqlite3_progress_handler, + sqlite3_realloc, + sqlite3_reset, + sqlite3_result_blob, + sqlite3_result_double, + sqlite3_result_error, + sqlite3_result_error16, + sqlite3_result_int, + sqlite3_result_int64, + sqlite3_result_null, + sqlite3_result_text, + sqlite3_result_text16, + sqlite3_result_text16be, + sqlite3_result_text16le, + sqlite3_result_value, + sqlite3_rollback_hook, + sqlite3_set_authorizer, + sqlite3_set_auxdata, + sqlite3_snprintf, + sqlite3_step, + sqlite3_table_column_metadata, +#ifndef SQLITE_OMIT_DEPRECATED + sqlite3_thread_cleanup, +#else + 0, +#endif + sqlite3_total_changes, + sqlite3_trace, +#ifndef SQLITE_OMIT_DEPRECATED + sqlite3_transfer_bindings, +#else + 0, +#endif + sqlite3_update_hook, + sqlite3_user_data, + sqlite3_value_blob, + sqlite3_value_bytes, + sqlite3_value_bytes16, + sqlite3_value_double, + sqlite3_value_int, + sqlite3_value_int64, + sqlite3_value_numeric_type, + sqlite3_value_text, + sqlite3_value_text16, + sqlite3_value_text16be, + sqlite3_value_text16le, + sqlite3_value_type, + sqlite3_vmprintf, + /* + ** The original API set ends here. All extensions can call any + ** of the APIs above provided that the pointer is not NULL. But + ** before calling APIs that follow, extension should check the + ** sqlite3_libversion_number() to make sure they are dealing with + ** a library that is new enough to support that API. + ************************************************************************* + */ + sqlite3_overload_function, + + /* + ** Added after 3.3.13 + */ + sqlite3_prepare_v2, + sqlite3_prepare16_v2, + sqlite3_clear_bindings, + + /* + ** Added for 3.4.1 + */ + sqlite3_create_module_v2, + + /* + ** Added for 3.5.0 + */ + sqlite3_bind_zeroblob, + sqlite3_blob_bytes, + sqlite3_blob_close, + sqlite3_blob_open, + sqlite3_blob_read, + sqlite3_blob_write, + sqlite3_create_collation_v2, + sqlite3_file_control, + sqlite3_memory_highwater, + sqlite3_memory_used, +#ifdef SQLITE_MUTEX_OMIT + 0, + 0, + 0, + 0, + 0, +#else + sqlite3_mutex_alloc, + sqlite3_mutex_enter, + sqlite3_mutex_free, + sqlite3_mutex_leave, + sqlite3_mutex_try, +#endif + sqlite3_open_v2, + sqlite3_release_memory, + sqlite3_result_error_nomem, + sqlite3_result_error_toobig, + sqlite3_sleep, + sqlite3_soft_heap_limit, + sqlite3_vfs_find, + sqlite3_vfs_register, + sqlite3_vfs_unregister, + + /* + ** Added for 3.5.8 + */ + sqlite3_threadsafe, + sqlite3_result_zeroblob, + sqlite3_result_error_code, + sqlite3_test_control, + sqlite3_randomness, + sqlite3_context_db_handle, + + /* + ** Added for 3.6.0 + */ + sqlite3_extended_result_codes, + sqlite3_limit, + sqlite3_next_stmt, + sqlite3_sql, + sqlite3_status, + + /* + ** Added for 3.7.4 + */ + sqlite3_backup_finish, + sqlite3_backup_init, + sqlite3_backup_pagecount, + sqlite3_backup_remaining, + sqlite3_backup_step, +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS + sqlite3_compileoption_get, + sqlite3_compileoption_used, +#else + 0, + 0, +#endif + sqlite3_create_function_v2, + sqlite3_db_config, + sqlite3_db_mutex, + sqlite3_db_status, + sqlite3_extended_errcode, + sqlite3_log, + sqlite3_soft_heap_limit64, + sqlite3_sourceid, + sqlite3_stmt_status, + sqlite3_strnicmp, +#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY + sqlite3_unlock_notify, +#else + 0, +#endif +#ifndef SQLITE_OMIT_WAL + sqlite3_wal_autocheckpoint, + sqlite3_wal_checkpoint, + sqlite3_wal_hook, +#else + 0, + 0, + 0, +#endif + sqlite3_blob_reopen, + sqlite3_vtab_config, + sqlite3_vtab_on_conflict, + sqlite3_close_v2, + sqlite3_db_filename, + sqlite3_db_readonly, + sqlite3_db_release_memory, + sqlite3_errstr, + sqlite3_stmt_busy, + sqlite3_stmt_readonly, + sqlite3_stricmp, + sqlite3_uri_boolean, + sqlite3_uri_int64, + sqlite3_uri_parameter, + sqlite3_vsnprintf, + sqlite3_wal_checkpoint_v2, + /* Version 3.8.7 and later */ + sqlite3_auto_extension, + sqlite3_bind_blob64, + sqlite3_bind_text64, + sqlite3_cancel_auto_extension, + sqlite3_load_extension, + sqlite3_malloc64, + sqlite3_msize, + sqlite3_realloc64, + sqlite3_reset_auto_extension, + sqlite3_result_blob64, + sqlite3_result_text64, + sqlite3_strglob, + /* Version 3.8.11 and later */ + (sqlite3_value*(*)(const sqlite3_value*))sqlite3_value_dup, + sqlite3_value_free, + sqlite3_result_zeroblob64, + sqlite3_bind_zeroblob64, + /* Version 3.9.0 and later */ + sqlite3_value_subtype, + sqlite3_result_subtype, + /* Version 3.10.0 and later */ + sqlite3_status64, + sqlite3_strlike, + sqlite3_db_cacheflush, + /* Version 3.12.0 and later */ + sqlite3_system_errno, + /* Version 3.14.0 and later */ + sqlite3_trace_v2, + sqlite3_expanded_sql, + /* Version 3.18.0 and later */ + sqlite3_set_last_insert_rowid, + /* Version 3.20.0 and later */ + sqlite3_prepare_v3, + sqlite3_prepare16_v3, + sqlite3_bind_pointer, + sqlite3_result_pointer, + sqlite3_value_pointer, + /* Version 3.22.0 and later */ + sqlite3_vtab_nochange, + sqlite3_value_nochange, + sqlite3_vtab_collation, + /* Version 3.24.0 and later */ + sqlite3_keyword_count, + sqlite3_keyword_name, + sqlite3_keyword_check, + sqlite3_str_new, + sqlite3_str_finish, + sqlite3_str_appendf, + sqlite3_str_vappendf, + sqlite3_str_append, + sqlite3_str_appendall, + sqlite3_str_appendchar, + sqlite3_str_reset, + sqlite3_str_errcode, + sqlite3_str_length, + sqlite3_str_value, + /* Version 3.25.0 and later */ + sqlite3_create_window_function, + /* Version 3.26.0 and later */ +#ifdef SQLITE_ENABLE_NORMALIZE + sqlite3_normalized_sql, +#else + 0, +#endif + /* Version 3.28.0 and later */ + sqlite3_stmt_isexplain, + sqlite3_value_frombind, + /* Version 3.30.0 and later */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3_drop_modules, +#else + 0, +#endif + /* Version 3.31.0 and later */ + sqlite3_hard_heap_limit64, + sqlite3_uri_key, + sqlite3_filename_database, + sqlite3_filename_journal, + sqlite3_filename_wal, + /* Version 3.32.0 and later */ + sqlite3_create_filename, + sqlite3_free_filename, + sqlite3_database_file_object, + /* Version 3.34.0 and later */ + sqlite3_txn_state, + /* Version 3.36.1 and later */ + sqlite3_changes64, + sqlite3_total_changes64, + /* Version 3.37.0 and later */ + sqlite3_autovacuum_pages, + /* Version 3.38.0 and later */ + sqlite3_error_offset, +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3_vtab_rhs_value, + sqlite3_vtab_distinct, + sqlite3_vtab_in, + sqlite3_vtab_in_first, + sqlite3_vtab_in_next, +#else + 0, + 0, + 0, + 0, + 0, +#endif + /* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE + sqlite3_deserialize, + sqlite3_serialize, +#else + 0, + 0, +#endif + sqlite3_db_name, + /* Version 3.40.0 and later */ + sqlite3_value_encoding, + /* Version 3.41.0 and later */ + sqlite3_is_interrupted, + /* Version 3.43.0 and later */ + sqlite3_stmt_explain +}; + +/* True if x is the directory separator character +*/ +#if SQLITE_OS_WIN +# define DirSep(X) ((X)=='/'||(X)=='\\') +#else +# define DirSep(X) ((X)=='/') +#endif + +/* +** Attempt to load an SQLite extension library contained in the file +** zFile. The entry point is zProc. zProc may be 0 in which case a +** default entry point name (sqlite3_extension_init) is used. Use +** of the default name is recommended. +** +** Return SQLITE_OK on success and SQLITE_ERROR if something goes wrong. +** +** If an error occurs and pzErrMsg is not 0, then fill *pzErrMsg with +** error message text. The calling function should free this memory +** by calling sqlite3DbFree(db, ). +*/ +static int sqlite3LoadExtension( + sqlite3 *db, /* Load the extension into this database connection */ + const char *zFile, /* Name of the shared library containing extension */ + const char *zProc, /* Entry point. Use "sqlite3_extension_init" if 0 */ + char **pzErrMsg /* Put error message here if not 0 */ +){ + sqlite3_vfs *pVfs = db->pVfs; + void *handle; + sqlite3_loadext_entry xInit; + char *zErrmsg = 0; + const char *zEntry; + char *zAltEntry = 0; + void **aHandle; + u64 nMsg = strlen(zFile); + int ii; + int rc; + + /* Shared library endings to try if zFile cannot be loaded as written */ + static const char *azEndings[] = { +#if SQLITE_OS_WIN + "dll" +#elif defined(__APPLE__) + "dylib" +#else + "so" +#endif + }; + + + if( pzErrMsg ) *pzErrMsg = 0; + + /* Ticket #1863. To avoid a creating security problems for older + ** applications that relink against newer versions of SQLite, the + ** ability to run load_extension is turned off by default. One + ** must call either sqlite3_enable_load_extension(db) or + ** sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, 0) + ** to turn on extension loading. + */ + if( (db->flags & SQLITE_LoadExtension)==0 ){ + if( pzErrMsg ){ + *pzErrMsg = sqlite3_mprintf("not authorized"); + } + return SQLITE_ERROR; + } + + zEntry = zProc ? zProc : "sqlite3_extension_init"; + + /* tag-20210611-1. Some dlopen() implementations will segfault if given + ** an oversize filename. Most filesystems have a pathname limit of 4K, + ** so limit the extension filename length to about twice that. + ** https://sqlite.org/forum/forumpost/08a0d6d9bf + ** + ** Later (2023-03-25): Save an extra 6 bytes for the filename suffix. + ** See https://sqlite.org/forum/forumpost/24083b579d. + */ + if( nMsg>SQLITE_MAX_PATHLEN ) goto extension_not_found; + + /* Do not allow sqlite3_load_extension() to link to a copy of the + ** running application, by passing in an empty filename. */ + if( nMsg==0 ) goto extension_not_found; + + handle = sqlite3OsDlOpen(pVfs, zFile); +#if SQLITE_OS_UNIX || SQLITE_OS_WIN + for(ii=0; ii<ArraySize(azEndings) && handle==0; ii++){ + char *zAltFile = sqlite3_mprintf("%s.%s", zFile, azEndings[ii]); + if( zAltFile==0 ) return SQLITE_NOMEM_BKPT; + if( nMsg+strlen(azEndings[ii])+1<=SQLITE_MAX_PATHLEN ){ + handle = sqlite3OsDlOpen(pVfs, zAltFile); + } + sqlite3_free(zAltFile); + } +#endif + if( handle==0 ) goto extension_not_found; + xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry); + + /* If no entry point was specified and the default legacy + ** entry point name "sqlite3_extension_init" was not found, then + ** construct an entry point name "sqlite3_X_init" where the X is + ** replaced by the lowercase value of every ASCII alphabetic + ** character in the filename after the last "/" upto the first ".", + ** and eliding the first three characters if they are "lib". + ** Examples: + ** + ** /usr/local/lib/libExample5.4.3.so ==> sqlite3_example_init + ** C:/lib/mathfuncs.dll ==> sqlite3_mathfuncs_init + */ + if( xInit==0 && zProc==0 ){ + int iFile, iEntry, c; + int ncFile = sqlite3Strlen30(zFile); + zAltEntry = sqlite3_malloc64(ncFile+30); + if( zAltEntry==0 ){ + sqlite3OsDlClose(pVfs, handle); + return SQLITE_NOMEM_BKPT; + } + memcpy(zAltEntry, "sqlite3_", 8); + for(iFile=ncFile-1; iFile>=0 && !DirSep(zFile[iFile]); iFile--){} + iFile++; + if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3; + for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){ + if( sqlite3Isalpha(c) ){ + zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c]; + } + } + memcpy(zAltEntry+iEntry, "_init", 6); + zEntry = zAltEntry; + xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry); + } + if( xInit==0 ){ + if( pzErrMsg ){ + nMsg += strlen(zEntry) + 300; + *pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg); + if( zErrmsg ){ + assert( nMsg<0x7fffffff ); /* zErrmsg would be NULL if not so */ + sqlite3_snprintf((int)nMsg, zErrmsg, + "no entry point [%s] in shared library [%s]", zEntry, zFile); + sqlite3OsDlError(pVfs, nMsg-1, zErrmsg); + } + } + sqlite3OsDlClose(pVfs, handle); + sqlite3_free(zAltEntry); + return SQLITE_ERROR; + } + sqlite3_free(zAltEntry); + rc = xInit(db, &zErrmsg, &sqlite3Apis); + if( rc ){ + if( rc==SQLITE_OK_LOAD_PERMANENTLY ) return SQLITE_OK; + if( pzErrMsg ){ + *pzErrMsg = sqlite3_mprintf("error during initialization: %s", zErrmsg); + } + sqlite3_free(zErrmsg); + sqlite3OsDlClose(pVfs, handle); + return SQLITE_ERROR; + } + + /* Append the new shared library handle to the db->aExtension array. */ + aHandle = sqlite3DbMallocZero(db, sizeof(handle)*(db->nExtension+1)); + if( aHandle==0 ){ + return SQLITE_NOMEM_BKPT; + } + if( db->nExtension>0 ){ + memcpy(aHandle, db->aExtension, sizeof(handle)*db->nExtension); + } + sqlite3DbFree(db, db->aExtension); + db->aExtension = aHandle; + + db->aExtension[db->nExtension++] = handle; + return SQLITE_OK; + +extension_not_found: + if( pzErrMsg ){ + nMsg += 300; + *pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg); + if( zErrmsg ){ + assert( nMsg<0x7fffffff ); /* zErrmsg would be NULL if not so */ + sqlite3_snprintf((int)nMsg, zErrmsg, + "unable to open shared library [%.*s]", SQLITE_MAX_PATHLEN, zFile); + sqlite3OsDlError(pVfs, nMsg-1, zErrmsg); + } + } + return SQLITE_ERROR; +} +SQLITE_API int sqlite3_load_extension( + sqlite3 *db, /* Load the extension into this database connection */ + const char *zFile, /* Name of the shared library containing extension */ + const char *zProc, /* Entry point. Use "sqlite3_extension_init" if 0 */ + char **pzErrMsg /* Put error message here if not 0 */ +){ + int rc; + sqlite3_mutex_enter(db->mutex); + rc = sqlite3LoadExtension(db, zFile, zProc, pzErrMsg); + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Call this routine when the database connection is closing in order +** to clean up loaded extensions +*/ +SQLITE_PRIVATE void sqlite3CloseExtensions(sqlite3 *db){ + int i; + assert( sqlite3_mutex_held(db->mutex) ); + for(i=0; i<db->nExtension; i++){ + sqlite3OsDlClose(db->pVfs, db->aExtension[i]); + } + sqlite3DbFree(db, db->aExtension); +} + +/* +** Enable or disable extension loading. Extension loading is disabled by +** default so as not to open security holes in older applications. +*/ +SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff){ + sqlite3_mutex_enter(db->mutex); + if( onoff ){ + db->flags |= SQLITE_LoadExtension|SQLITE_LoadExtFunc; + }else{ + db->flags &= ~(u64)(SQLITE_LoadExtension|SQLITE_LoadExtFunc); + } + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + +#endif /* !defined(SQLITE_OMIT_LOAD_EXTENSION) */ + +/* +** The following object holds the list of automatically loaded +** extensions. +** +** This list is shared across threads. The SQLITE_MUTEX_STATIC_MAIN +** mutex must be held while accessing this list. +*/ +typedef struct sqlite3AutoExtList sqlite3AutoExtList; +static SQLITE_WSD struct sqlite3AutoExtList { + u32 nExt; /* Number of entries in aExt[] */ + void (**aExt)(void); /* Pointers to the extension init functions */ +} sqlite3Autoext = { 0, 0 }; + +/* The "wsdAutoext" macro will resolve to the autoextension +** state vector. If writable static data is unsupported on the target, +** we have to locate the state vector at run-time. In the more common +** case where writable static data is supported, wsdStat can refer directly +** to the "sqlite3Autoext" state vector declared above. +*/ +#ifdef SQLITE_OMIT_WSD +# define wsdAutoextInit \ + sqlite3AutoExtList *x = &GLOBAL(sqlite3AutoExtList,sqlite3Autoext) +# define wsdAutoext x[0] +#else +# define wsdAutoextInit +# define wsdAutoext sqlite3Autoext +#endif + + +/* +** Register a statically linked extension that is automatically +** loaded by every new database connection. +*/ +SQLITE_API int sqlite3_auto_extension( + void (*xInit)(void) +){ + int rc = SQLITE_OK; +#ifndef SQLITE_OMIT_AUTOINIT + rc = sqlite3_initialize(); + if( rc ){ + return rc; + }else +#endif + { + u32 i; +#if SQLITE_THREADSAFE + sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); +#endif + wsdAutoextInit; + sqlite3_mutex_enter(mutex); + for(i=0; i<wsdAutoext.nExt; i++){ + if( wsdAutoext.aExt[i]==xInit ) break; + } + if( i==wsdAutoext.nExt ){ + u64 nByte = (wsdAutoext.nExt+1)*sizeof(wsdAutoext.aExt[0]); + void (**aNew)(void); + aNew = sqlite3_realloc64(wsdAutoext.aExt, nByte); + if( aNew==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + wsdAutoext.aExt = aNew; + wsdAutoext.aExt[wsdAutoext.nExt] = xInit; + wsdAutoext.nExt++; + } + } + sqlite3_mutex_leave(mutex); + assert( (rc&0xff)==rc ); + return rc; + } +} + +/* +** Cancel a prior call to sqlite3_auto_extension. Remove xInit from the +** set of routines that is invoked for each new database connection, if it +** is currently on the list. If xInit is not on the list, then this +** routine is a no-op. +** +** Return 1 if xInit was found on the list and removed. Return 0 if xInit +** was not on the list. +*/ +SQLITE_API int sqlite3_cancel_auto_extension( + void (*xInit)(void) +){ +#if SQLITE_THREADSAFE + sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); +#endif + int i; + int n = 0; + wsdAutoextInit; + sqlite3_mutex_enter(mutex); + for(i=(int)wsdAutoext.nExt-1; i>=0; i--){ + if( wsdAutoext.aExt[i]==xInit ){ + wsdAutoext.nExt--; + wsdAutoext.aExt[i] = wsdAutoext.aExt[wsdAutoext.nExt]; + n++; + break; + } + } + sqlite3_mutex_leave(mutex); + return n; +} + +/* +** Reset the automatic extension loading mechanism. +*/ +SQLITE_API void sqlite3_reset_auto_extension(void){ +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize()==SQLITE_OK ) +#endif + { +#if SQLITE_THREADSAFE + sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); +#endif + wsdAutoextInit; + sqlite3_mutex_enter(mutex); + sqlite3_free(wsdAutoext.aExt); + wsdAutoext.aExt = 0; + wsdAutoext.nExt = 0; + sqlite3_mutex_leave(mutex); + } +} + +/* +** Load all automatic extensions. +** +** If anything goes wrong, set an error in the database connection. +*/ +SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ + u32 i; + int go = 1; + int rc; + sqlite3_loadext_entry xInit; + + wsdAutoextInit; + if( wsdAutoext.nExt==0 ){ + /* Common case: early out without every having to acquire a mutex */ + return; + } + for(i=0; go; i++){ + char *zErrmsg; +#if SQLITE_THREADSAFE + sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); +#endif +#ifdef SQLITE_OMIT_LOAD_EXTENSION + const sqlite3_api_routines *pThunk = 0; +#else + const sqlite3_api_routines *pThunk = &sqlite3Apis; +#endif + sqlite3_mutex_enter(mutex); + if( i>=wsdAutoext.nExt ){ + xInit = 0; + go = 0; + }else{ + xInit = (sqlite3_loadext_entry)wsdAutoext.aExt[i]; + } + sqlite3_mutex_leave(mutex); + zErrmsg = 0; + if( xInit && (rc = xInit(db, &zErrmsg, pThunk))!=0 ){ + sqlite3ErrorWithMsg(db, rc, + "automatic extension loading failed: %s", zErrmsg); + go = 0; + } + sqlite3_free(zErrmsg); + } +} + +/************** End of loadext.c *********************************************/ +/************** Begin file pragma.c ******************************************/ +/* +** 2003 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the PRAGMA command. +*/ +/* #include "sqliteInt.h" */ + +#if !defined(SQLITE_ENABLE_LOCKING_STYLE) +# if defined(__APPLE__) +# define SQLITE_ENABLE_LOCKING_STYLE 1 +# else +# define SQLITE_ENABLE_LOCKING_STYLE 0 +# endif +#endif + +/*************************************************************************** +** The "pragma.h" include file is an automatically generated file that +** that includes the PragType_XXXX macro definitions and the aPragmaName[] +** object. This ensures that the aPragmaName[] table is arranged in +** lexicographical order to facility a binary search of the pragma name. +** Do not edit pragma.h directly. Edit and rerun the script in at +** ../tool/mkpragmatab.tcl. */ +/************** Include pragma.h in the middle of pragma.c *******************/ +/************** Begin file pragma.h ******************************************/ +/* DO NOT EDIT! +** This file is automatically generated by the script at +** ../tool/mkpragmatab.tcl. To update the set of pragmas, edit +** that script and rerun it. +*/ + +/* The various pragma types */ +#define PragTyp_ACTIVATE_EXTENSIONS 0 +#define PragTyp_ANALYSIS_LIMIT 1 +#define PragTyp_HEADER_VALUE 2 +#define PragTyp_AUTO_VACUUM 3 +#define PragTyp_FLAG 4 +#define PragTyp_BUSY_TIMEOUT 5 +#define PragTyp_CACHE_SIZE 6 +#define PragTyp_CACHE_SPILL 7 +#define PragTyp_CASE_SENSITIVE_LIKE 8 +#define PragTyp_COLLATION_LIST 9 +#define PragTyp_COMPILE_OPTIONS 10 +#define PragTyp_DATA_STORE_DIRECTORY 11 +#define PragTyp_DATABASE_LIST 12 +#define PragTyp_DEFAULT_CACHE_SIZE 13 +#define PragTyp_ENCODING 14 +#define PragTyp_FOREIGN_KEY_CHECK 15 +#define PragTyp_FOREIGN_KEY_LIST 16 +#define PragTyp_FUNCTION_LIST 17 +#define PragTyp_HARD_HEAP_LIMIT 18 +#define PragTyp_INCREMENTAL_VACUUM 19 +#define PragTyp_INDEX_INFO 20 +#define PragTyp_INDEX_LIST 21 +#define PragTyp_INTEGRITY_CHECK 22 +#define PragTyp_JOURNAL_MODE 23 +#define PragTyp_JOURNAL_SIZE_LIMIT 24 +#define PragTyp_LOCK_PROXY_FILE 25 +#define PragTyp_LOCKING_MODE 26 +#define PragTyp_PAGE_COUNT 27 +#define PragTyp_MMAP_SIZE 28 +#define PragTyp_MODULE_LIST 29 +#define PragTyp_OPTIMIZE 30 +#define PragTyp_PAGE_SIZE 31 +#define PragTyp_PRAGMA_LIST 32 +#define PragTyp_SECURE_DELETE 33 +#define PragTyp_SHRINK_MEMORY 34 +#define PragTyp_SOFT_HEAP_LIMIT 35 +#define PragTyp_SYNCHRONOUS 36 +#define PragTyp_TABLE_INFO 37 +#define PragTyp_TABLE_LIST 38 +#define PragTyp_TEMP_STORE 39 +#define PragTyp_TEMP_STORE_DIRECTORY 40 +#define PragTyp_THREADS 41 +#define PragTyp_WAL_AUTOCHECKPOINT 42 +#define PragTyp_WAL_CHECKPOINT 43 +#define PragTyp_LOCK_STATUS 44 +#define PragTyp_STATS 45 + +/* Property flags associated with various pragma. */ +#define PragFlg_NeedSchema 0x01 /* Force schema load before running */ +#define PragFlg_NoColumns 0x02 /* OP_ResultRow called with zero columns */ +#define PragFlg_NoColumns1 0x04 /* zero columns if RHS argument is present */ +#define PragFlg_ReadOnly 0x08 /* Read-only HEADER_VALUE */ +#define PragFlg_Result0 0x10 /* Acts as query when no argument */ +#define PragFlg_Result1 0x20 /* Acts as query when has one argument */ +#define PragFlg_SchemaOpt 0x40 /* Schema restricts name search if present */ +#define PragFlg_SchemaReq 0x80 /* Schema required - "main" is default */ + +/* Names of columns for pragmas that return multi-column result +** or that return single-column results where the name of the +** result column is different from the name of the pragma +*/ +static const char *const pragCName[] = { + /* 0 */ "id", /* Used by: foreign_key_list */ + /* 1 */ "seq", + /* 2 */ "table", + /* 3 */ "from", + /* 4 */ "to", + /* 5 */ "on_update", + /* 6 */ "on_delete", + /* 7 */ "match", + /* 8 */ "cid", /* Used by: table_xinfo */ + /* 9 */ "name", + /* 10 */ "type", + /* 11 */ "notnull", + /* 12 */ "dflt_value", + /* 13 */ "pk", + /* 14 */ "hidden", + /* table_info reuses 8 */ + /* 15 */ "schema", /* Used by: table_list */ + /* 16 */ "name", + /* 17 */ "type", + /* 18 */ "ncol", + /* 19 */ "wr", + /* 20 */ "strict", + /* 21 */ "seqno", /* Used by: index_xinfo */ + /* 22 */ "cid", + /* 23 */ "name", + /* 24 */ "desc", + /* 25 */ "coll", + /* 26 */ "key", + /* 27 */ "name", /* Used by: function_list */ + /* 28 */ "builtin", + /* 29 */ "type", + /* 30 */ "enc", + /* 31 */ "narg", + /* 32 */ "flags", + /* 33 */ "tbl", /* Used by: stats */ + /* 34 */ "idx", + /* 35 */ "wdth", + /* 36 */ "hght", + /* 37 */ "flgs", + /* 38 */ "seq", /* Used by: index_list */ + /* 39 */ "name", + /* 40 */ "unique", + /* 41 */ "origin", + /* 42 */ "partial", + /* 43 */ "table", /* Used by: foreign_key_check */ + /* 44 */ "rowid", + /* 45 */ "parent", + /* 46 */ "fkid", + /* index_info reuses 21 */ + /* 47 */ "seq", /* Used by: database_list */ + /* 48 */ "name", + /* 49 */ "file", + /* 50 */ "busy", /* Used by: wal_checkpoint */ + /* 51 */ "log", + /* 52 */ "checkpointed", + /* collation_list reuses 38 */ + /* 53 */ "database", /* Used by: lock_status */ + /* 54 */ "status", + /* 55 */ "cache_size", /* Used by: default_cache_size */ + /* module_list pragma_list reuses 9 */ + /* 56 */ "timeout", /* Used by: busy_timeout */ +}; + +/* Definitions of all built-in pragmas */ +typedef struct PragmaName { + const char *const zName; /* Name of pragma */ + u8 ePragTyp; /* PragTyp_XXX value */ + u8 mPragFlg; /* Zero or more PragFlg_XXX values */ + u8 iPragCName; /* Start of column names in pragCName[] */ + u8 nPragCName; /* Num of col names. 0 means use pragma name */ + u64 iArg; /* Extra argument */ +} PragmaName; +static const PragmaName aPragmaName[] = { +#if defined(SQLITE_ENABLE_CEROD) + {/* zName: */ "activate_extensions", + /* ePragTyp: */ PragTyp_ACTIVATE_EXTENSIONS, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif + {/* zName: */ "analysis_limit", + /* ePragTyp: */ PragTyp_ANALYSIS_LIMIT, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) + {/* zName: */ "application_id", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_APPLICATION_ID }, +#endif +#if !defined(SQLITE_OMIT_AUTOVACUUM) + {/* zName: */ "auto_vacuum", + /* ePragTyp: */ PragTyp_AUTO_VACUUM, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) +#if !defined(SQLITE_OMIT_AUTOMATIC_INDEX) + {/* zName: */ "automatic_index", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_AutoIndex }, +#endif +#endif + {/* zName: */ "busy_timeout", + /* ePragTyp: */ PragTyp_BUSY_TIMEOUT, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 56, 1, + /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + {/* zName: */ "cache_size", + /* ePragTyp: */ PragTyp_CACHE_SIZE, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "cache_spill", + /* ePragTyp: */ PragTyp_CACHE_SPILL, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA) + {/* zName: */ "case_sensitive_like", + /* ePragTyp: */ PragTyp_CASE_SENSITIVE_LIKE, + /* ePragFlg: */ PragFlg_NoColumns, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif + {/* zName: */ "cell_size_check", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_CellSizeCk }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "checkpoint_fullfsync", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_CkptFullFSync }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) + {/* zName: */ "collation_list", + /* ePragTyp: */ PragTyp_COLLATION_LIST, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 38, 2, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS) + {/* zName: */ "compile_options", + /* ePragTyp: */ PragTyp_COMPILE_OPTIONS, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "count_changes", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_CountRows }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_OS_WIN + {/* zName: */ "data_store_directory", + /* ePragTyp: */ PragTyp_DATA_STORE_DIRECTORY, + /* ePragFlg: */ PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) + {/* zName: */ "data_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_ReadOnly|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_DATA_VERSION }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) + {/* zName: */ "database_list", + /* ePragTyp: */ PragTyp_DATABASE_LIST, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 47, 3, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) + {/* zName: */ "default_cache_size", + /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 55, 1, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) +#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) + {/* zName: */ "defer_foreign_keys", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_DeferFKs }, +#endif +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "empty_result_callbacks", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_NullCallback }, +#endif +#if !defined(SQLITE_OMIT_UTF16) + {/* zName: */ "encoding", + /* ePragTyp: */ PragTyp_ENCODING, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) + {/* zName: */ "foreign_key_check", + /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 43, 4, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FOREIGN_KEY) + {/* zName: */ "foreign_key_list", + /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 0, 8, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) +#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) + {/* zName: */ "foreign_keys", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ForeignKeys }, +#endif +#endif +#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) + {/* zName: */ "freelist_count", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_ReadOnly|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_FREE_PAGE_COUNT }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "full_column_names", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_FullColNames }, + {/* zName: */ "fullfsync", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_FullFSync }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) +#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS) + {/* zName: */ "function_list", + /* ePragTyp: */ PragTyp_FUNCTION_LIST, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 27, 6, + /* iArg: */ 0 }, +#endif +#endif + {/* zName: */ "hard_heap_limit", + /* ePragTyp: */ PragTyp_HARD_HEAP_LIMIT, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) +#if !defined(SQLITE_OMIT_CHECK) + {/* zName: */ "ignore_check_constraints", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_IgnoreChecks }, +#endif +#endif +#if !defined(SQLITE_OMIT_AUTOVACUUM) + {/* zName: */ "incremental_vacuum", + /* ePragTyp: */ PragTyp_INCREMENTAL_VACUUM, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_NoColumns, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) + {/* zName: */ "index_info", + /* ePragTyp: */ PragTyp_INDEX_INFO, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 21, 3, + /* iArg: */ 0 }, + {/* zName: */ "index_list", + /* ePragTyp: */ PragTyp_INDEX_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 38, 5, + /* iArg: */ 0 }, + {/* zName: */ "index_xinfo", + /* ePragTyp: */ PragTyp_INDEX_INFO, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 21, 6, + /* iArg: */ 1 }, +#endif +#if !defined(SQLITE_OMIT_INTEGRITY_CHECK) + {/* zName: */ "integrity_check", + /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + {/* zName: */ "journal_mode", + /* ePragTyp: */ PragTyp_JOURNAL_MODE, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "journal_size_limit", + /* ePragTyp: */ PragTyp_JOURNAL_SIZE_LIMIT, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "legacy_alter_table", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_LegacyAlter }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_ENABLE_LOCKING_STYLE + {/* zName: */ "lock_proxy_file", + /* ePragTyp: */ PragTyp_LOCK_PROXY_FILE, + /* ePragFlg: */ PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + {/* zName: */ "lock_status", + /* ePragTyp: */ PragTyp_LOCK_STATUS, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 53, 2, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + {/* zName: */ "locking_mode", + /* ePragTyp: */ PragTyp_LOCKING_MODE, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "max_page_count", + /* ePragTyp: */ PragTyp_PAGE_COUNT, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "mmap_size", + /* ePragTyp: */ PragTyp_MMAP_SIZE, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) +#if !defined(SQLITE_OMIT_VIRTUALTABLE) +#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS) + {/* zName: */ "module_list", + /* ePragTyp: */ PragTyp_MODULE_LIST, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 9, 1, + /* iArg: */ 0 }, +#endif +#endif +#endif + {/* zName: */ "optimize", + /* ePragTyp: */ PragTyp_OPTIMIZE, + /* ePragFlg: */ PragFlg_Result1|PragFlg_NeedSchema, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + {/* zName: */ "page_count", + /* ePragTyp: */ PragTyp_PAGE_COUNT, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "page_size", + /* ePragTyp: */ PragTyp_PAGE_SIZE, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) +#if defined(SQLITE_DEBUG) + {/* zName: */ "parser_trace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ParserTrace }, +#endif +#endif +#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS) + {/* zName: */ "pragma_list", + /* ePragTyp: */ PragTyp_PRAGMA_LIST, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 9, 1, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "query_only", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_QueryOnly }, +#endif +#if !defined(SQLITE_OMIT_INTEGRITY_CHECK) + {/* zName: */ "quick_check", + /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "read_uncommitted", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ReadUncommit }, + {/* zName: */ "recursive_triggers", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_RecTriggers }, + {/* zName: */ "reverse_unordered_selects", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ReverseOrder }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) + {/* zName: */ "schema_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_SCHEMA_VERSION }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + {/* zName: */ "secure_delete", + /* ePragTyp: */ PragTyp_SECURE_DELETE, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "short_column_names", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ShortColNames }, +#endif + {/* zName: */ "shrink_memory", + /* ePragTyp: */ PragTyp_SHRINK_MEMORY, + /* ePragFlg: */ PragFlg_NoColumns, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "soft_heap_limit", + /* ePragTyp: */ PragTyp_SOFT_HEAP_LIMIT, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) +#if defined(SQLITE_DEBUG) + {/* zName: */ "sql_trace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_SqlTrace }, +#endif +#endif +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && defined(SQLITE_DEBUG) + {/* zName: */ "stats", + /* ePragTyp: */ PragTyp_STATS, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 33, 5, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + {/* zName: */ "synchronous", + /* ePragTyp: */ PragTyp_SYNCHRONOUS, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) + {/* zName: */ "table_info", + /* ePragTyp: */ PragTyp_TABLE_INFO, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 8, 6, + /* iArg: */ 0 }, + {/* zName: */ "table_list", + /* ePragTyp: */ PragTyp_TABLE_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1, + /* ColNames: */ 15, 6, + /* iArg: */ 0 }, + {/* zName: */ "table_xinfo", + /* ePragTyp: */ PragTyp_TABLE_INFO, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 8, 7, + /* iArg: */ 1 }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + {/* zName: */ "temp_store", + /* ePragTyp: */ PragTyp_TEMP_STORE, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "temp_store_directory", + /* ePragTyp: */ PragTyp_TEMP_STORE_DIRECTORY, + /* ePragFlg: */ PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif + {/* zName: */ "threads", + /* ePragTyp: */ PragTyp_THREADS, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "trusted_schema", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_TrustedSchema }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) + {/* zName: */ "user_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_USER_VERSION }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) +#if defined(SQLITE_DEBUG) + {/* zName: */ "vdbe_addoptrace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeAddopTrace }, + {/* zName: */ "vdbe_debug", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_SqlTrace|SQLITE_VdbeListing|SQLITE_VdbeTrace }, + {/* zName: */ "vdbe_eqp", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeEQP }, + {/* zName: */ "vdbe_listing", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeListing }, + {/* zName: */ "vdbe_trace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeTrace }, +#endif +#endif +#if !defined(SQLITE_OMIT_WAL) + {/* zName: */ "wal_autocheckpoint", + /* ePragTyp: */ PragTyp_WAL_AUTOCHECKPOINT, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "wal_checkpoint", + /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, + /* ePragFlg: */ PragFlg_NeedSchema, + /* ColNames: */ 50, 3, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "writable_schema", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_WriteSchema|SQLITE_NoSchemaError }, +#endif +}; +/* Number of pragmas: 68 on by default, 78 total. */ + +/************** End of pragma.h **********************************************/ +/************** Continuing where we left off in pragma.c *********************/ + +/* +** Interpret the given string as a safety level. Return 0 for OFF, +** 1 for ON or NORMAL, 2 for FULL, and 3 for EXTRA. Return 1 for an empty or +** unrecognized string argument. The FULL and EXTRA option is disallowed +** if the omitFull parameter it 1. +** +** Note that the values returned are one less that the values that +** should be passed into sqlite3BtreeSetSafetyLevel(). The is done +** to support legacy SQL code. The safety level used to be boolean +** and older scripts may have used numbers 0 for OFF and 1 for ON. +*/ +static u8 getSafetyLevel(const char *z, int omitFull, u8 dflt){ + /* 123456789 123456789 123 */ + static const char zText[] = "onoffalseyestruextrafull"; + static const u8 iOffset[] = {0, 1, 2, 4, 9, 12, 15, 20}; + static const u8 iLength[] = {2, 2, 3, 5, 3, 4, 5, 4}; + static const u8 iValue[] = {1, 0, 0, 0, 1, 1, 3, 2}; + /* on no off false yes true extra full */ + int i, n; + if( sqlite3Isdigit(*z) ){ + return (u8)sqlite3Atoi(z); + } + n = sqlite3Strlen30(z); + for(i=0; i<ArraySize(iLength); i++){ + if( iLength[i]==n && sqlite3StrNICmp(&zText[iOffset[i]],z,n)==0 + && (!omitFull || iValue[i]<=1) + ){ + return iValue[i]; + } + } + return dflt; +} + +/* +** Interpret the given string as a boolean value. +*/ +SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z, u8 dflt){ + return getSafetyLevel(z,1,dflt)!=0; +} + +/* The sqlite3GetBoolean() function is used by other modules but the +** remainder of this file is specific to PRAGMA processing. So omit +** the rest of the file if PRAGMAs are omitted from the build. +*/ +#if !defined(SQLITE_OMIT_PRAGMA) + +/* +** Interpret the given string as a locking mode value. +*/ +static int getLockingMode(const char *z){ + if( z ){ + if( 0==sqlite3StrICmp(z, "exclusive") ) return PAGER_LOCKINGMODE_EXCLUSIVE; + if( 0==sqlite3StrICmp(z, "normal") ) return PAGER_LOCKINGMODE_NORMAL; + } + return PAGER_LOCKINGMODE_QUERY; +} + +#ifndef SQLITE_OMIT_AUTOVACUUM +/* +** Interpret the given string as an auto-vacuum mode value. +** +** The following strings, "none", "full" and "incremental" are +** acceptable, as are their numeric equivalents: 0, 1 and 2 respectively. +*/ +static int getAutoVacuum(const char *z){ + int i; + if( 0==sqlite3StrICmp(z, "none") ) return BTREE_AUTOVACUUM_NONE; + if( 0==sqlite3StrICmp(z, "full") ) return BTREE_AUTOVACUUM_FULL; + if( 0==sqlite3StrICmp(z, "incremental") ) return BTREE_AUTOVACUUM_INCR; + i = sqlite3Atoi(z); + return (u8)((i>=0&&i<=2)?i:0); +} +#endif /* ifndef SQLITE_OMIT_AUTOVACUUM */ + +#ifndef SQLITE_OMIT_PAGER_PRAGMAS +/* +** Interpret the given string as a temp db location. Return 1 for file +** backed temporary databases, 2 for the Red-Black tree in memory database +** and 0 to use the compile-time default. +*/ +static int getTempStore(const char *z){ + if( z[0]>='0' && z[0]<='2' ){ + return z[0] - '0'; + }else if( sqlite3StrICmp(z, "file")==0 ){ + return 1; + }else if( sqlite3StrICmp(z, "memory")==0 ){ + return 2; + }else{ + return 0; + } +} +#endif /* SQLITE_PAGER_PRAGMAS */ + +#ifndef SQLITE_OMIT_PAGER_PRAGMAS +/* +** Invalidate temp storage, either when the temp storage is changed +** from default, or when 'file' and the temp_store_directory has changed +*/ +static int invalidateTempStorage(Parse *pParse){ + sqlite3 *db = pParse->db; + if( db->aDb[1].pBt!=0 ){ + if( !db->autoCommit + || sqlite3BtreeTxnState(db->aDb[1].pBt)!=SQLITE_TXN_NONE + ){ + sqlite3ErrorMsg(pParse, "temporary storage cannot be changed " + "from within a transaction"); + return SQLITE_ERROR; + } + sqlite3BtreeClose(db->aDb[1].pBt); + db->aDb[1].pBt = 0; + sqlite3ResetAllSchemasOfConnection(db); + } + return SQLITE_OK; +} +#endif /* SQLITE_PAGER_PRAGMAS */ + +#ifndef SQLITE_OMIT_PAGER_PRAGMAS +/* +** If the TEMP database is open, close it and mark the database schema +** as needing reloading. This must be done when using the SQLITE_TEMP_STORE +** or DEFAULT_TEMP_STORE pragmas. +*/ +static int changeTempStorage(Parse *pParse, const char *zStorageType){ + int ts = getTempStore(zStorageType); + sqlite3 *db = pParse->db; + if( db->temp_store==ts ) return SQLITE_OK; + if( invalidateTempStorage( pParse ) != SQLITE_OK ){ + return SQLITE_ERROR; + } + db->temp_store = (u8)ts; + return SQLITE_OK; +} +#endif /* SQLITE_PAGER_PRAGMAS */ + +/* +** Set result column names for a pragma. +*/ +static void setPragmaResultColumnNames( + Vdbe *v, /* The query under construction */ + const PragmaName *pPragma /* The pragma */ +){ + u8 n = pPragma->nPragCName; + sqlite3VdbeSetNumCols(v, n==0 ? 1 : n); + if( n==0 ){ + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, pPragma->zName, SQLITE_STATIC); + }else{ + int i, j; + for(i=0, j=pPragma->iPragCName; i<n; i++, j++){ + sqlite3VdbeSetColName(v, i, COLNAME_NAME, pragCName[j], SQLITE_STATIC); + } + } +} + +/* +** Generate code to return a single integer value. +*/ +static void returnSingleInt(Vdbe *v, i64 value){ + sqlite3VdbeAddOp4Dup8(v, OP_Int64, 0, 1, 0, (const u8*)&value, P4_INT64); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); +} + +/* +** Generate code to return a single text value. +*/ +static void returnSingleText( + Vdbe *v, /* Prepared statement under construction */ + const char *zValue /* Value to be returned */ +){ + if( zValue ){ + sqlite3VdbeLoadString(v, 1, (const char*)zValue); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); + } +} + + +/* +** Set the safety_level and pager flags for pager iDb. Or if iDb<0 +** set these values for all pagers. +*/ +#ifndef SQLITE_OMIT_PAGER_PRAGMAS +static void setAllPagerFlags(sqlite3 *db){ + if( db->autoCommit ){ + Db *pDb = db->aDb; + int n = db->nDb; + assert( SQLITE_FullFSync==PAGER_FULLFSYNC ); + assert( SQLITE_CkptFullFSync==PAGER_CKPT_FULLFSYNC ); + assert( SQLITE_CacheSpill==PAGER_CACHESPILL ); + assert( (PAGER_FULLFSYNC | PAGER_CKPT_FULLFSYNC | PAGER_CACHESPILL) + == PAGER_FLAGS_MASK ); + assert( (pDb->safety_level & PAGER_SYNCHRONOUS_MASK)==pDb->safety_level ); + while( (n--) > 0 ){ + if( pDb->pBt ){ + sqlite3BtreeSetPagerFlags(pDb->pBt, + pDb->safety_level | (db->flags & PAGER_FLAGS_MASK) ); + } + pDb++; + } + } +} +#else +# define setAllPagerFlags(X) /* no-op */ +#endif + + +/* +** Return a human-readable name for a constraint resolution action. +*/ +#ifndef SQLITE_OMIT_FOREIGN_KEY +static const char *actionName(u8 action){ + const char *zName; + switch( action ){ + case OE_SetNull: zName = "SET NULL"; break; + case OE_SetDflt: zName = "SET DEFAULT"; break; + case OE_Cascade: zName = "CASCADE"; break; + case OE_Restrict: zName = "RESTRICT"; break; + default: zName = "NO ACTION"; + assert( action==OE_None ); break; + } + return zName; +} +#endif + + +/* +** Parameter eMode must be one of the PAGER_JOURNALMODE_XXX constants +** defined in pager.h. This function returns the associated lowercase +** journal-mode name. +*/ +SQLITE_PRIVATE const char *sqlite3JournalModename(int eMode){ + static char * const azModeName[] = { + "delete", "persist", "off", "truncate", "memory" +#ifndef SQLITE_OMIT_WAL + , "wal" +#endif + }; + assert( PAGER_JOURNALMODE_DELETE==0 ); + assert( PAGER_JOURNALMODE_PERSIST==1 ); + assert( PAGER_JOURNALMODE_OFF==2 ); + assert( PAGER_JOURNALMODE_TRUNCATE==3 ); + assert( PAGER_JOURNALMODE_MEMORY==4 ); + assert( PAGER_JOURNALMODE_WAL==5 ); + assert( eMode>=0 && eMode<=ArraySize(azModeName) ); + + if( eMode==ArraySize(azModeName) ) return 0; + return azModeName[eMode]; +} + +/* +** Locate a pragma in the aPragmaName[] array. +*/ +static const PragmaName *pragmaLocate(const char *zName){ + int upr, lwr, mid = 0, rc; + lwr = 0; + upr = ArraySize(aPragmaName)-1; + while( lwr<=upr ){ + mid = (lwr+upr)/2; + rc = sqlite3_stricmp(zName, aPragmaName[mid].zName); + if( rc==0 ) break; + if( rc<0 ){ + upr = mid - 1; + }else{ + lwr = mid + 1; + } + } + return lwr>upr ? 0 : &aPragmaName[mid]; +} + +/* +** Create zero or more entries in the output for the SQL functions +** defined by FuncDef p. +*/ +static void pragmaFunclistLine( + Vdbe *v, /* The prepared statement being created */ + FuncDef *p, /* A particular function definition */ + int isBuiltin, /* True if this is a built-in function */ + int showInternFuncs /* True if showing internal functions */ +){ + u32 mask = + SQLITE_DETERMINISTIC | + SQLITE_DIRECTONLY | + SQLITE_SUBTYPE | + SQLITE_INNOCUOUS | + SQLITE_FUNC_INTERNAL + ; + if( showInternFuncs ) mask = 0xffffffff; + for(; p; p=p->pNext){ + const char *zType; + static const char *azEnc[] = { 0, "utf8", "utf16le", "utf16be" }; + + assert( SQLITE_FUNC_ENCMASK==0x3 ); + assert( strcmp(azEnc[SQLITE_UTF8],"utf8")==0 ); + assert( strcmp(azEnc[SQLITE_UTF16LE],"utf16le")==0 ); + assert( strcmp(azEnc[SQLITE_UTF16BE],"utf16be")==0 ); + + if( p->xSFunc==0 ) continue; + if( (p->funcFlags & SQLITE_FUNC_INTERNAL)!=0 + && showInternFuncs==0 + ){ + continue; + } + if( p->xValue!=0 ){ + zType = "w"; + }else if( p->xFinalize!=0 ){ + zType = "a"; + }else{ + zType = "s"; + } + sqlite3VdbeMultiLoad(v, 1, "sissii", + p->zName, isBuiltin, + zType, azEnc[p->funcFlags&SQLITE_FUNC_ENCMASK], + p->nArg, + (p->funcFlags & mask) ^ SQLITE_INNOCUOUS + ); + } +} + + +/* +** Helper subroutine for PRAGMA integrity_check: +** +** Generate code to output a single-column result row with a value of the +** string held in register 3. Decrement the result count in register 1 +** and halt if the maximum number of result rows have been issued. +*/ +static int integrityCheckResultRow(Vdbe *v){ + int addr; + sqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1); + addr = sqlite3VdbeAddOp3(v, OP_IfPos, 1, sqlite3VdbeCurrentAddr(v)+2, 1); + VdbeCoverage(v); + sqlite3VdbeAddOp0(v, OP_Halt); + return addr; +} + +/* +** Process a pragma statement. +** +** Pragmas are of this form: +** +** PRAGMA [schema.]id [= value] +** +** The identifier might also be a string. The value is a string, and +** identifier, or a number. If minusFlag is true, then the value is +** a number that was preceded by a minus sign. +** +** If the left side is "database.id" then pId1 is the database name +** and pId2 is the id. If the left side is just "id" then pId1 is the +** id and pId2 is any empty string. +*/ +SQLITE_PRIVATE void sqlite3Pragma( + Parse *pParse, + Token *pId1, /* First part of [schema.]id field */ + Token *pId2, /* Second part of [schema.]id field, or NULL */ + Token *pValue, /* Token for <value>, or NULL */ + int minusFlag /* True if a '-' sign preceded <value> */ +){ + char *zLeft = 0; /* Nul-terminated UTF-8 string <id> */ + char *zRight = 0; /* Nul-terminated UTF-8 string <value>, or NULL */ + const char *zDb = 0; /* The database name */ + Token *pId; /* Pointer to <id> token */ + char *aFcntl[4]; /* Argument to SQLITE_FCNTL_PRAGMA */ + int iDb; /* Database index for <database> */ + int rc; /* return value form SQLITE_FCNTL_PRAGMA */ + sqlite3 *db = pParse->db; /* The database connection */ + Db *pDb; /* The specific database being pragmaed */ + Vdbe *v = sqlite3GetVdbe(pParse); /* Prepared statement */ + const PragmaName *pPragma; /* The pragma */ + + if( v==0 ) return; + sqlite3VdbeRunOnlyOnce(v); + pParse->nMem = 2; + + /* Interpret the [schema.] part of the pragma statement. iDb is the + ** index of the database this pragma is being applied to in db.aDb[]. */ + iDb = sqlite3TwoPartName(pParse, pId1, pId2, &pId); + if( iDb<0 ) return; + pDb = &db->aDb[iDb]; + + /* If the temp database has been explicitly named as part of the + ** pragma, make sure it is open. + */ + if( iDb==1 && sqlite3OpenTempDatabase(pParse) ){ + return; + } + + zLeft = sqlite3NameFromToken(db, pId); + if( !zLeft ) return; + if( minusFlag ){ + zRight = sqlite3MPrintf(db, "-%T", pValue); + }else{ + zRight = sqlite3NameFromToken(db, pValue); + } + + assert( pId2 ); + zDb = pId2->n>0 ? pDb->zDbSName : 0; + if( sqlite3AuthCheck(pParse, SQLITE_PRAGMA, zLeft, zRight, zDb) ){ + goto pragma_out; + } + + /* Send an SQLITE_FCNTL_PRAGMA file-control to the underlying VFS + ** connection. If it returns SQLITE_OK, then assume that the VFS + ** handled the pragma and generate a no-op prepared statement. + ** + ** IMPLEMENTATION-OF: R-12238-55120 Whenever a PRAGMA statement is parsed, + ** an SQLITE_FCNTL_PRAGMA file control is sent to the open sqlite3_file + ** object corresponding to the database file to which the pragma + ** statement refers. + ** + ** IMPLEMENTATION-OF: R-29875-31678 The argument to the SQLITE_FCNTL_PRAGMA + ** file control is an array of pointers to strings (char**) in which the + ** second element of the array is the name of the pragma and the third + ** element is the argument to the pragma or NULL if the pragma has no + ** argument. + */ + aFcntl[0] = 0; + aFcntl[1] = zLeft; + aFcntl[2] = zRight; + aFcntl[3] = 0; + db->busyHandler.nBusy = 0; + rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_PRAGMA, (void*)aFcntl); + if( rc==SQLITE_OK ){ + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, aFcntl[0], SQLITE_TRANSIENT); + returnSingleText(v, aFcntl[0]); + sqlite3_free(aFcntl[0]); + goto pragma_out; + } + if( rc!=SQLITE_NOTFOUND ){ + if( aFcntl[0] ){ + sqlite3ErrorMsg(pParse, "%s", aFcntl[0]); + sqlite3_free(aFcntl[0]); + } + pParse->nErr++; + pParse->rc = rc; + goto pragma_out; + } + + /* Locate the pragma in the lookup table */ + pPragma = pragmaLocate(zLeft); + if( pPragma==0 ){ + /* IMP: R-43042-22504 No error messages are generated if an + ** unknown pragma is issued. */ + goto pragma_out; + } + + /* Make sure the database schema is loaded if the pragma requires that */ + if( (pPragma->mPragFlg & PragFlg_NeedSchema)!=0 ){ + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + } + + /* Register the result column names for pragmas that return results */ + if( (pPragma->mPragFlg & PragFlg_NoColumns)==0 + && ((pPragma->mPragFlg & PragFlg_NoColumns1)==0 || zRight==0) + ){ + setPragmaResultColumnNames(v, pPragma); + } + + /* Jump to the appropriate pragma handler */ + switch( pPragma->ePragTyp ){ + +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) + /* + ** PRAGMA [schema.]default_cache_size + ** PRAGMA [schema.]default_cache_size=N + ** + ** The first form reports the current persistent setting for the + ** page cache size. The value returned is the maximum number of + ** pages in the page cache. The second form sets both the current + ** page cache size value and the persistent page cache size value + ** stored in the database file. + ** + ** Older versions of SQLite would set the default cache size to a + ** negative number to indicate synchronous=OFF. These days, synchronous + ** is always on by default regardless of the sign of the default cache + ** size. But continue to take the absolute value of the default cache + ** size of historical compatibility. + */ + case PragTyp_DEFAULT_CACHE_SIZE: { + static const int iLn = VDBE_OFFSET_LINENO(2); + static const VdbeOpList getCacheSize[] = { + { OP_Transaction, 0, 0, 0}, /* 0 */ + { OP_ReadCookie, 0, 1, BTREE_DEFAULT_CACHE_SIZE}, /* 1 */ + { OP_IfPos, 1, 8, 0}, + { OP_Integer, 0, 2, 0}, + { OP_Subtract, 1, 2, 1}, + { OP_IfPos, 1, 8, 0}, + { OP_Integer, 0, 1, 0}, /* 6 */ + { OP_Noop, 0, 0, 0}, + { OP_ResultRow, 1, 1, 0}, + }; + VdbeOp *aOp; + sqlite3VdbeUsesBtree(v, iDb); + if( !zRight ){ + pParse->nMem += 2; + sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(getCacheSize)); + aOp = sqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize, iLn); + if( ONLY_IF_REALLOC_STRESS(aOp==0) ) break; + aOp[0].p1 = iDb; + aOp[1].p1 = iDb; + aOp[6].p1 = SQLITE_DEFAULT_CACHE_SIZE; + }else{ + int size = sqlite3AbsInt32(sqlite3Atoi(zRight)); + sqlite3BeginWriteOperation(pParse, 0, iDb); + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_DEFAULT_CACHE_SIZE, size); + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + pDb->pSchema->cache_size = size; + sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size); + } + break; + } +#endif /* !SQLITE_OMIT_PAGER_PRAGMAS && !SQLITE_OMIT_DEPRECATED */ + +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + /* + ** PRAGMA [schema.]page_size + ** PRAGMA [schema.]page_size=N + ** + ** The first form reports the current setting for the + ** database page size in bytes. The second form sets the + ** database page size value. The value can only be set if + ** the database has not yet been created. + */ + case PragTyp_PAGE_SIZE: { + Btree *pBt = pDb->pBt; + assert( pBt!=0 ); + if( !zRight ){ + int size = ALWAYS(pBt) ? sqlite3BtreeGetPageSize(pBt) : 0; + returnSingleInt(v, size); + }else{ + /* Malloc may fail when setting the page-size, as there is an internal + ** buffer that the pager module resizes using sqlite3_realloc(). + */ + db->nextPagesize = sqlite3Atoi(zRight); + if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize,0,0) ){ + sqlite3OomFault(db); + } + } + break; + } + + /* + ** PRAGMA [schema.]secure_delete + ** PRAGMA [schema.]secure_delete=ON/OFF/FAST + ** + ** The first form reports the current setting for the + ** secure_delete flag. The second form changes the secure_delete + ** flag setting and reports the new value. + */ + case PragTyp_SECURE_DELETE: { + Btree *pBt = pDb->pBt; + int b = -1; + assert( pBt!=0 ); + if( zRight ){ + if( sqlite3_stricmp(zRight, "fast")==0 ){ + b = 2; + }else{ + b = sqlite3GetBoolean(zRight, 0); + } + } + if( pId2->n==0 && b>=0 ){ + int ii; + for(ii=0; ii<db->nDb; ii++){ + sqlite3BtreeSecureDelete(db->aDb[ii].pBt, b); + } + } + b = sqlite3BtreeSecureDelete(pBt, b); + returnSingleInt(v, b); + break; + } + + /* + ** PRAGMA [schema.]max_page_count + ** PRAGMA [schema.]max_page_count=N + ** + ** The first form reports the current setting for the + ** maximum number of pages in the database file. The + ** second form attempts to change this setting. Both + ** forms return the current setting. + ** + ** The absolute value of N is used. This is undocumented and might + ** change. The only purpose is to provide an easy way to test + ** the sqlite3AbsInt32() function. + ** + ** PRAGMA [schema.]page_count + ** + ** Return the number of pages in the specified database. + */ + case PragTyp_PAGE_COUNT: { + int iReg; + i64 x = 0; + sqlite3CodeVerifySchema(pParse, iDb); + iReg = ++pParse->nMem; + if( sqlite3Tolower(zLeft[0])=='p' ){ + sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg); + }else{ + if( zRight && sqlite3DecOrHexToI64(zRight,&x)==0 ){ + if( x<0 ) x = 0; + else if( x>0xfffffffe ) x = 0xfffffffe; + }else{ + x = 0; + } + sqlite3VdbeAddOp3(v, OP_MaxPgcnt, iDb, iReg, (int)x); + } + sqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1); + break; + } + + /* + ** PRAGMA [schema.]locking_mode + ** PRAGMA [schema.]locking_mode = (normal|exclusive) + */ + case PragTyp_LOCKING_MODE: { + const char *zRet = "normal"; + int eMode = getLockingMode(zRight); + + if( pId2->n==0 && eMode==PAGER_LOCKINGMODE_QUERY ){ + /* Simple "PRAGMA locking_mode;" statement. This is a query for + ** the current default locking mode (which may be different to + ** the locking-mode of the main database). + */ + eMode = db->dfltLockMode; + }else{ + Pager *pPager; + if( pId2->n==0 ){ + /* This indicates that no database name was specified as part + ** of the PRAGMA command. In this case the locking-mode must be + ** set on all attached databases, as well as the main db file. + ** + ** Also, the sqlite3.dfltLockMode variable is set so that + ** any subsequently attached databases also use the specified + ** locking mode. + */ + int ii; + assert(pDb==&db->aDb[0]); + for(ii=2; ii<db->nDb; ii++){ + pPager = sqlite3BtreePager(db->aDb[ii].pBt); + sqlite3PagerLockingMode(pPager, eMode); + } + db->dfltLockMode = (u8)eMode; + } + pPager = sqlite3BtreePager(pDb->pBt); + eMode = sqlite3PagerLockingMode(pPager, eMode); + } + + assert( eMode==PAGER_LOCKINGMODE_NORMAL + || eMode==PAGER_LOCKINGMODE_EXCLUSIVE ); + if( eMode==PAGER_LOCKINGMODE_EXCLUSIVE ){ + zRet = "exclusive"; + } + returnSingleText(v, zRet); + break; + } + + /* + ** PRAGMA [schema.]journal_mode + ** PRAGMA [schema.]journal_mode = + ** (delete|persist|off|truncate|memory|wal|off) + */ + case PragTyp_JOURNAL_MODE: { + int eMode; /* One of the PAGER_JOURNALMODE_XXX symbols */ + int ii; /* Loop counter */ + + if( zRight==0 ){ + /* If there is no "=MODE" part of the pragma, do a query for the + ** current mode */ + eMode = PAGER_JOURNALMODE_QUERY; + }else{ + const char *zMode; + int n = sqlite3Strlen30(zRight); + for(eMode=0; (zMode = sqlite3JournalModename(eMode))!=0; eMode++){ + if( sqlite3StrNICmp(zRight, zMode, n)==0 ) break; + } + if( !zMode ){ + /* If the "=MODE" part does not match any known journal mode, + ** then do a query */ + eMode = PAGER_JOURNALMODE_QUERY; + } + if( eMode==PAGER_JOURNALMODE_OFF && (db->flags & SQLITE_Defensive)!=0 ){ + /* Do not allow journal-mode "OFF" in defensive since the database + ** can become corrupted using ordinary SQL when the journal is off */ + eMode = PAGER_JOURNALMODE_QUERY; + } + } + if( eMode==PAGER_JOURNALMODE_QUERY && pId2->n==0 ){ + /* Convert "PRAGMA journal_mode" into "PRAGMA main.journal_mode" */ + iDb = 0; + pId2->n = 1; + } + for(ii=db->nDb-1; ii>=0; ii--){ + if( db->aDb[ii].pBt && (ii==iDb || pId2->n==0) ){ + sqlite3VdbeUsesBtree(v, ii); + sqlite3VdbeAddOp3(v, OP_JournalMode, ii, 1, eMode); + } + } + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); + break; + } + + /* + ** PRAGMA [schema.]journal_size_limit + ** PRAGMA [schema.]journal_size_limit=N + ** + ** Get or set the size limit on rollback journal files. + */ + case PragTyp_JOURNAL_SIZE_LIMIT: { + Pager *pPager = sqlite3BtreePager(pDb->pBt); + i64 iLimit = -2; + if( zRight ){ + sqlite3DecOrHexToI64(zRight, &iLimit); + if( iLimit<-1 ) iLimit = -1; + } + iLimit = sqlite3PagerJournalSizeLimit(pPager, iLimit); + returnSingleInt(v, iLimit); + break; + } + +#endif /* SQLITE_OMIT_PAGER_PRAGMAS */ + + /* + ** PRAGMA [schema.]auto_vacuum + ** PRAGMA [schema.]auto_vacuum=N + ** + ** Get or set the value of the database 'auto-vacuum' parameter. + ** The value is one of: 0 NONE 1 FULL 2 INCREMENTAL + */ +#ifndef SQLITE_OMIT_AUTOVACUUM + case PragTyp_AUTO_VACUUM: { + Btree *pBt = pDb->pBt; + assert( pBt!=0 ); + if( !zRight ){ + returnSingleInt(v, sqlite3BtreeGetAutoVacuum(pBt)); + }else{ + int eAuto = getAutoVacuum(zRight); + assert( eAuto>=0 && eAuto<=2 ); + db->nextAutovac = (u8)eAuto; + /* Call SetAutoVacuum() to set initialize the internal auto and + ** incr-vacuum flags. This is required in case this connection + ** creates the database file. It is important that it is created + ** as an auto-vacuum capable db. + */ + rc = sqlite3BtreeSetAutoVacuum(pBt, eAuto); + if( rc==SQLITE_OK && (eAuto==1 || eAuto==2) ){ + /* When setting the auto_vacuum mode to either "full" or + ** "incremental", write the value of meta[6] in the database + ** file. Before writing to meta[6], check that meta[3] indicates + ** that this really is an auto-vacuum capable database. + */ + static const int iLn = VDBE_OFFSET_LINENO(2); + static const VdbeOpList setMeta6[] = { + { OP_Transaction, 0, 1, 0}, /* 0 */ + { OP_ReadCookie, 0, 1, BTREE_LARGEST_ROOT_PAGE}, + { OP_If, 1, 0, 0}, /* 2 */ + { OP_Halt, SQLITE_OK, OE_Abort, 0}, /* 3 */ + { OP_SetCookie, 0, BTREE_INCR_VACUUM, 0}, /* 4 */ + }; + VdbeOp *aOp; + int iAddr = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(setMeta6)); + aOp = sqlite3VdbeAddOpList(v, ArraySize(setMeta6), setMeta6, iLn); + if( ONLY_IF_REALLOC_STRESS(aOp==0) ) break; + aOp[0].p1 = iDb; + aOp[1].p1 = iDb; + aOp[2].p2 = iAddr+4; + aOp[4].p1 = iDb; + aOp[4].p3 = eAuto - 1; + sqlite3VdbeUsesBtree(v, iDb); + } + } + break; + } +#endif + + /* + ** PRAGMA [schema.]incremental_vacuum(N) + ** + ** Do N steps of incremental vacuuming on a database. + */ +#ifndef SQLITE_OMIT_AUTOVACUUM + case PragTyp_INCREMENTAL_VACUUM: { + int iLimit = 0, addr; + if( zRight==0 || !sqlite3GetInt32(zRight, &iLimit) || iLimit<=0 ){ + iLimit = 0x7fffffff; + } + sqlite3BeginWriteOperation(pParse, 0, iDb); + sqlite3VdbeAddOp2(v, OP_Integer, iLimit, 1); + addr = sqlite3VdbeAddOp1(v, OP_IncrVacuum, iDb); VdbeCoverage(v); + sqlite3VdbeAddOp1(v, OP_ResultRow, 1); + sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); + sqlite3VdbeAddOp2(v, OP_IfPos, 1, addr); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addr); + break; + } +#endif + +#ifndef SQLITE_OMIT_PAGER_PRAGMAS + /* + ** PRAGMA [schema.]cache_size + ** PRAGMA [schema.]cache_size=N + ** + ** The first form reports the current local setting for the + ** page cache size. The second form sets the local + ** page cache size value. If N is positive then that is the + ** number of pages in the cache. If N is negative, then the + ** number of pages is adjusted so that the cache uses -N kibibytes + ** of memory. + */ + case PragTyp_CACHE_SIZE: { + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( !zRight ){ + returnSingleInt(v, pDb->pSchema->cache_size); + }else{ + int size = sqlite3Atoi(zRight); + pDb->pSchema->cache_size = size; + sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size); + } + break; + } + + /* + ** PRAGMA [schema.]cache_spill + ** PRAGMA cache_spill=BOOLEAN + ** PRAGMA [schema.]cache_spill=N + ** + ** The first form reports the current local setting for the + ** page cache spill size. The second form turns cache spill on + ** or off. When turning cache spill on, the size is set to the + ** current cache_size. The third form sets a spill size that + ** may be different form the cache size. + ** If N is positive then that is the + ** number of pages in the cache. If N is negative, then the + ** number of pages is adjusted so that the cache uses -N kibibytes + ** of memory. + ** + ** If the number of cache_spill pages is less then the number of + ** cache_size pages, no spilling occurs until the page count exceeds + ** the number of cache_size pages. + ** + ** The cache_spill=BOOLEAN setting applies to all attached schemas, + ** not just the schema specified. + */ + case PragTyp_CACHE_SPILL: { + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( !zRight ){ + returnSingleInt(v, + (db->flags & SQLITE_CacheSpill)==0 ? 0 : + sqlite3BtreeSetSpillSize(pDb->pBt,0)); + }else{ + int size = 1; + if( sqlite3GetInt32(zRight, &size) ){ + sqlite3BtreeSetSpillSize(pDb->pBt, size); + } + if( sqlite3GetBoolean(zRight, size!=0) ){ + db->flags |= SQLITE_CacheSpill; + }else{ + db->flags &= ~(u64)SQLITE_CacheSpill; + } + setAllPagerFlags(db); + } + break; + } + + /* + ** PRAGMA [schema.]mmap_size(N) + ** + ** Used to set mapping size limit. The mapping size limit is + ** used to limit the aggregate size of all memory mapped regions of the + ** database file. If this parameter is set to zero, then memory mapping + ** is not used at all. If N is negative, then the default memory map + ** limit determined by sqlite3_config(SQLITE_CONFIG_MMAP_SIZE) is set. + ** The parameter N is measured in bytes. + ** + ** This value is advisory. The underlying VFS is free to memory map + ** as little or as much as it wants. Except, if N is set to 0 then the + ** upper layers will never invoke the xFetch interfaces to the VFS. + */ + case PragTyp_MMAP_SIZE: { + sqlite3_int64 sz; +#if SQLITE_MAX_MMAP_SIZE>0 + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( zRight ){ + int ii; + sqlite3DecOrHexToI64(zRight, &sz); + if( sz<0 ) sz = sqlite3GlobalConfig.szMmap; + if( pId2->n==0 ) db->szMmap = sz; + for(ii=db->nDb-1; ii>=0; ii--){ + if( db->aDb[ii].pBt && (ii==iDb || pId2->n==0) ){ + sqlite3BtreeSetMmapLimit(db->aDb[ii].pBt, sz); + } + } + } + sz = -1; + rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_MMAP_SIZE, &sz); +#else + sz = 0; + rc = SQLITE_OK; +#endif + if( rc==SQLITE_OK ){ + returnSingleInt(v, sz); + }else if( rc!=SQLITE_NOTFOUND ){ + pParse->nErr++; + pParse->rc = rc; + } + break; + } + + /* + ** PRAGMA temp_store + ** PRAGMA temp_store = "default"|"memory"|"file" + ** + ** Return or set the local value of the temp_store flag. Changing + ** the local value does not make changes to the disk file and the default + ** value will be restored the next time the database is opened. + ** + ** Note that it is possible for the library compile-time options to + ** override this setting + */ + case PragTyp_TEMP_STORE: { + if( !zRight ){ + returnSingleInt(v, db->temp_store); + }else{ + changeTempStorage(pParse, zRight); + } + break; + } + + /* + ** PRAGMA temp_store_directory + ** PRAGMA temp_store_directory = ""|"directory_name" + ** + ** Return or set the local value of the temp_store_directory flag. Changing + ** the value sets a specific directory to be used for temporary files. + ** Setting to a null string reverts to the default temporary directory search. + ** If temporary directory is changed, then invalidateTempStorage. + ** + */ + case PragTyp_TEMP_STORE_DIRECTORY: { + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + if( !zRight ){ + returnSingleText(v, sqlite3_temp_directory); + }else{ +#ifndef SQLITE_OMIT_WSD + if( zRight[0] ){ + int res; + rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res); + if( rc!=SQLITE_OK || res==0 ){ + sqlite3ErrorMsg(pParse, "not a writable directory"); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + goto pragma_out; + } + } + if( SQLITE_TEMP_STORE==0 + || (SQLITE_TEMP_STORE==1 && db->temp_store<=1) + || (SQLITE_TEMP_STORE==2 && db->temp_store==1) + ){ + invalidateTempStorage(pParse); + } + sqlite3_free(sqlite3_temp_directory); + if( zRight[0] ){ + sqlite3_temp_directory = sqlite3_mprintf("%s", zRight); + }else{ + sqlite3_temp_directory = 0; + } +#endif /* SQLITE_OMIT_WSD */ + } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + break; + } + +#if SQLITE_OS_WIN + /* + ** PRAGMA data_store_directory + ** PRAGMA data_store_directory = ""|"directory_name" + ** + ** Return or set the local value of the data_store_directory flag. Changing + ** the value sets a specific directory to be used for database files that + ** were specified with a relative pathname. Setting to a null string reverts + ** to the default database directory, which for database files specified with + ** a relative path will probably be based on the current directory for the + ** process. Database file specified with an absolute path are not impacted + ** by this setting, regardless of its value. + ** + */ + case PragTyp_DATA_STORE_DIRECTORY: { + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + if( !zRight ){ + returnSingleText(v, sqlite3_data_directory); + }else{ +#ifndef SQLITE_OMIT_WSD + if( zRight[0] ){ + int res; + rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res); + if( rc!=SQLITE_OK || res==0 ){ + sqlite3ErrorMsg(pParse, "not a writable directory"); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + goto pragma_out; + } + } + sqlite3_free(sqlite3_data_directory); + if( zRight[0] ){ + sqlite3_data_directory = sqlite3_mprintf("%s", zRight); + }else{ + sqlite3_data_directory = 0; + } +#endif /* SQLITE_OMIT_WSD */ + } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + break; + } +#endif + +#if SQLITE_ENABLE_LOCKING_STYLE + /* + ** PRAGMA [schema.]lock_proxy_file + ** PRAGMA [schema.]lock_proxy_file = ":auto:"|"lock_file_path" + ** + ** Return or set the value of the lock_proxy_file flag. Changing + ** the value sets a specific file to be used for database access locks. + ** + */ + case PragTyp_LOCK_PROXY_FILE: { + if( !zRight ){ + Pager *pPager = sqlite3BtreePager(pDb->pBt); + char *proxy_file_path = NULL; + sqlite3_file *pFile = sqlite3PagerFile(pPager); + sqlite3OsFileControlHint(pFile, SQLITE_GET_LOCKPROXYFILE, + &proxy_file_path); + returnSingleText(v, proxy_file_path); + }else{ + Pager *pPager = sqlite3BtreePager(pDb->pBt); + sqlite3_file *pFile = sqlite3PagerFile(pPager); + int res; + if( zRight[0] ){ + res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, + zRight); + } else { + res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, + NULL); + } + if( res!=SQLITE_OK ){ + sqlite3ErrorMsg(pParse, "failed to set lock proxy file"); + goto pragma_out; + } + } + break; + } +#endif /* SQLITE_ENABLE_LOCKING_STYLE */ + + /* + ** PRAGMA [schema.]synchronous + ** PRAGMA [schema.]synchronous=OFF|ON|NORMAL|FULL|EXTRA + ** + ** Return or set the local value of the synchronous flag. Changing + ** the local value does not make changes to the disk file and the + ** default value will be restored the next time the database is + ** opened. + */ + case PragTyp_SYNCHRONOUS: { + if( !zRight ){ + returnSingleInt(v, pDb->safety_level-1); + }else{ + if( !db->autoCommit ){ + sqlite3ErrorMsg(pParse, + "Safety level may not be changed inside a transaction"); + }else if( iDb!=1 ){ + int iLevel = (getSafetyLevel(zRight,0,1)+1) & PAGER_SYNCHRONOUS_MASK; + if( iLevel==0 ) iLevel = 1; + pDb->safety_level = iLevel; + pDb->bSyncSet = 1; + setAllPagerFlags(db); + } + } + break; + } +#endif /* SQLITE_OMIT_PAGER_PRAGMAS */ + +#ifndef SQLITE_OMIT_FLAG_PRAGMAS + case PragTyp_FLAG: { + if( zRight==0 ){ + setPragmaResultColumnNames(v, pPragma); + returnSingleInt(v, (db->flags & pPragma->iArg)!=0 ); + }else{ + u64 mask = pPragma->iArg; /* Mask of bits to set or clear. */ + if( db->autoCommit==0 ){ + /* Foreign key support may not be enabled or disabled while not + ** in auto-commit mode. */ + mask &= ~(SQLITE_ForeignKeys); + } +#if SQLITE_USER_AUTHENTICATION + if( db->auth.authLevel==UAUTH_User ){ + /* Do not allow non-admin users to modify the schema arbitrarily */ + mask &= ~(SQLITE_WriteSchema); + } +#endif + + if( sqlite3GetBoolean(zRight, 0) ){ + db->flags |= mask; + }else{ + db->flags &= ~mask; + if( mask==SQLITE_DeferFKs ) db->nDeferredImmCons = 0; + if( (mask & SQLITE_WriteSchema)!=0 + && sqlite3_stricmp(zRight, "reset")==0 + ){ + /* IMP: R-60817-01178 If the argument is "RESET" then schema + ** writing is disabled (as with "PRAGMA writable_schema=OFF") and, + ** in addition, the schema is reloaded. */ + sqlite3ResetAllSchemasOfConnection(db); + } + } + + /* Many of the flag-pragmas modify the code generated by the SQL + ** compiler (eg. count_changes). So add an opcode to expire all + ** compiled SQL statements after modifying a pragma value. + */ + sqlite3VdbeAddOp0(v, OP_Expire); + setAllPagerFlags(db); + } + break; + } +#endif /* SQLITE_OMIT_FLAG_PRAGMAS */ + +#ifndef SQLITE_OMIT_SCHEMA_PRAGMAS + /* + ** PRAGMA table_info(<table>) + ** + ** Return a single row for each column of the named table. The columns of + ** the returned data set are: + ** + ** cid: Column id (numbered from left to right, starting at 0) + ** name: Column name + ** type: Column declaration type. + ** notnull: True if 'NOT NULL' is part of column declaration + ** dflt_value: The default value for the column, if any. + ** pk: Non-zero for PK fields. + */ + case PragTyp_TABLE_INFO: if( zRight ){ + Table *pTab; + sqlite3CodeVerifyNamedSchema(pParse, zDb); + pTab = sqlite3LocateTable(pParse, LOCATE_NOERR, zRight, zDb); + if( pTab ){ + int i, k; + int nHidden = 0; + Column *pCol; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + pParse->nMem = 7; + sqlite3ViewGetColumnNames(pParse, pTab); + for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){ + int isHidden = 0; + const Expr *pColExpr; + if( pCol->colFlags & COLFLAG_NOINSERT ){ + if( pPragma->iArg==0 ){ + nHidden++; + continue; + } + if( pCol->colFlags & COLFLAG_VIRTUAL ){ + isHidden = 2; /* GENERATED ALWAYS AS ... VIRTUAL */ + }else if( pCol->colFlags & COLFLAG_STORED ){ + isHidden = 3; /* GENERATED ALWAYS AS ... STORED */ + }else{ assert( pCol->colFlags & COLFLAG_HIDDEN ); + isHidden = 1; /* HIDDEN */ + } + } + if( (pCol->colFlags & COLFLAG_PRIMKEY)==0 ){ + k = 0; + }else if( pPk==0 ){ + k = 1; + }else{ + for(k=1; k<=pTab->nCol && pPk->aiColumn[k-1]!=i; k++){} + } + pColExpr = sqlite3ColumnExpr(pTab,pCol); + assert( pColExpr==0 || pColExpr->op==TK_SPAN || isHidden>=2 ); + assert( pColExpr==0 || !ExprHasProperty(pColExpr, EP_IntValue) + || isHidden>=2 ); + sqlite3VdbeMultiLoad(v, 1, pPragma->iArg ? "issisii" : "issisi", + i-nHidden, + pCol->zCnName, + sqlite3ColumnType(pCol,""), + pCol->notNull ? 1 : 0, + (isHidden>=2 || pColExpr==0) ? 0 : pColExpr->u.zToken, + k, + isHidden); + } + } + } + break; + + /* + ** PRAGMA table_list + ** + ** Return a single row for each table, virtual table, or view in the + ** entire schema. + ** + ** schema: Name of attached database hold this table + ** name: Name of the table itself + ** type: "table", "view", "virtual", "shadow" + ** ncol: Number of columns + ** wr: True for a WITHOUT ROWID table + ** strict: True for a STRICT table + */ + case PragTyp_TABLE_LIST: { + int ii; + pParse->nMem = 6; + sqlite3CodeVerifyNamedSchema(pParse, zDb); + for(ii=0; ii<db->nDb; ii++){ + HashElem *k; + Hash *pHash; + int initNCol; + if( zDb && sqlite3_stricmp(zDb, db->aDb[ii].zDbSName)!=0 ) continue; + + /* Ensure that the Table.nCol field is initialized for all views + ** and virtual tables. Each time we initialize a Table.nCol value + ** for a table, that can potentially disrupt the hash table, so restart + ** the initialization scan. + */ + pHash = &db->aDb[ii].pSchema->tblHash; + initNCol = sqliteHashCount(pHash); + while( initNCol-- ){ + for(k=sqliteHashFirst(pHash); 1; k=sqliteHashNext(k) ){ + Table *pTab; + if( k==0 ){ initNCol = 0; break; } + pTab = sqliteHashData(k); + if( pTab->nCol==0 ){ + char *zSql = sqlite3MPrintf(db, "SELECT*FROM\"%w\"", pTab->zName); + if( zSql ){ + sqlite3_stmt *pDummy = 0; + (void)sqlite3_prepare(db, zSql, -1, &pDummy, 0); + (void)sqlite3_finalize(pDummy); + sqlite3DbFree(db, zSql); + } + if( db->mallocFailed ){ + sqlite3ErrorMsg(db->pParse, "out of memory"); + db->pParse->rc = SQLITE_NOMEM_BKPT; + } + pHash = &db->aDb[ii].pSchema->tblHash; + break; + } + } + } + + for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k) ){ + Table *pTab = sqliteHashData(k); + const char *zType; + if( zRight && sqlite3_stricmp(zRight, pTab->zName)!=0 ) continue; + if( IsView(pTab) ){ + zType = "view"; + }else if( IsVirtual(pTab) ){ + zType = "virtual"; + }else if( pTab->tabFlags & TF_Shadow ){ + zType = "shadow"; + }else{ + zType = "table"; + } + sqlite3VdbeMultiLoad(v, 1, "sssiii", + db->aDb[ii].zDbSName, + sqlite3PreferredTableName(pTab->zName), + zType, + pTab->nCol, + (pTab->tabFlags & TF_WithoutRowid)!=0, + (pTab->tabFlags & TF_Strict)!=0 + ); + } + } + } + break; + +#ifdef SQLITE_DEBUG + case PragTyp_STATS: { + Index *pIdx; + HashElem *i; + pParse->nMem = 5; + sqlite3CodeVerifySchema(pParse, iDb); + for(i=sqliteHashFirst(&pDb->pSchema->tblHash); i; i=sqliteHashNext(i)){ + Table *pTab = sqliteHashData(i); + sqlite3VdbeMultiLoad(v, 1, "ssiii", + sqlite3PreferredTableName(pTab->zName), + 0, + pTab->szTabRow, + pTab->nRowLogEst, + pTab->tabFlags); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + sqlite3VdbeMultiLoad(v, 2, "siiiX", + pIdx->zName, + pIdx->szIdxRow, + pIdx->aiRowLogEst[0], + pIdx->hasStat1); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 5); + } + } + } + break; +#endif + + case PragTyp_INDEX_INFO: if( zRight ){ + Index *pIdx; + Table *pTab; + pIdx = sqlite3FindIndex(db, zRight, zDb); + if( pIdx==0 ){ + /* If there is no index named zRight, check to see if there is a + ** WITHOUT ROWID table named zRight, and if there is, show the + ** structure of the PRIMARY KEY index for that table. */ + pTab = sqlite3LocateTable(pParse, LOCATE_NOERR, zRight, zDb); + if( pTab && !HasRowid(pTab) ){ + pIdx = sqlite3PrimaryKeyIndex(pTab); + } + } + if( pIdx ){ + int iIdxDb = sqlite3SchemaToIndex(db, pIdx->pSchema); + int i; + int mx; + if( pPragma->iArg ){ + /* PRAGMA index_xinfo (newer version with more rows and columns) */ + mx = pIdx->nColumn; + pParse->nMem = 6; + }else{ + /* PRAGMA index_info (legacy version) */ + mx = pIdx->nKeyCol; + pParse->nMem = 3; + } + pTab = pIdx->pTable; + sqlite3CodeVerifySchema(pParse, iIdxDb); + assert( pParse->nMem<=pPragma->nPragCName ); + for(i=0; i<mx; i++){ + i16 cnum = pIdx->aiColumn[i]; + sqlite3VdbeMultiLoad(v, 1, "iisX", i, cnum, + cnum<0 ? 0 : pTab->aCol[cnum].zCnName); + if( pPragma->iArg ){ + sqlite3VdbeMultiLoad(v, 4, "isiX", + pIdx->aSortOrder[i], + pIdx->azColl[i], + i<pIdx->nKeyCol); + } + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, pParse->nMem); + } + } + } + break; + + case PragTyp_INDEX_LIST: if( zRight ){ + Index *pIdx; + Table *pTab; + int i; + pTab = sqlite3FindTable(db, zRight, zDb); + if( pTab ){ + int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); + pParse->nMem = 5; + sqlite3CodeVerifySchema(pParse, iTabDb); + for(pIdx=pTab->pIndex, i=0; pIdx; pIdx=pIdx->pNext, i++){ + const char *azOrigin[] = { "c", "u", "pk" }; + sqlite3VdbeMultiLoad(v, 1, "isisi", + i, + pIdx->zName, + IsUniqueIndex(pIdx), + azOrigin[pIdx->idxType], + pIdx->pPartIdxWhere!=0); + } + } + } + break; + + case PragTyp_DATABASE_LIST: { + int i; + pParse->nMem = 3; + for(i=0; i<db->nDb; i++){ + if( db->aDb[i].pBt==0 ) continue; + assert( db->aDb[i].zDbSName!=0 ); + sqlite3VdbeMultiLoad(v, 1, "iss", + i, + db->aDb[i].zDbSName, + sqlite3BtreeGetFilename(db->aDb[i].pBt)); + } + } + break; + + case PragTyp_COLLATION_LIST: { + int i = 0; + HashElem *p; + pParse->nMem = 2; + for(p=sqliteHashFirst(&db->aCollSeq); p; p=sqliteHashNext(p)){ + CollSeq *pColl = (CollSeq *)sqliteHashData(p); + sqlite3VdbeMultiLoad(v, 1, "is", i++, pColl->zName); + } + } + break; + +#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS + case PragTyp_FUNCTION_LIST: { + int i; + HashElem *j; + FuncDef *p; + int showInternFunc = (db->mDbFlags & DBFLAG_InternalFunc)!=0; + pParse->nMem = 6; + for(i=0; i<SQLITE_FUNC_HASH_SZ; i++){ + for(p=sqlite3BuiltinFunctions.a[i]; p; p=p->u.pHash ){ + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); + pragmaFunclistLine(v, p, 1, showInternFunc); + } + } + for(j=sqliteHashFirst(&db->aFunc); j; j=sqliteHashNext(j)){ + p = (FuncDef*)sqliteHashData(j); + assert( (p->funcFlags & SQLITE_FUNC_BUILTIN)==0 ); + pragmaFunclistLine(v, p, 0, showInternFunc); + } + } + break; + +#ifndef SQLITE_OMIT_VIRTUALTABLE + case PragTyp_MODULE_LIST: { + HashElem *j; + pParse->nMem = 1; + for(j=sqliteHashFirst(&db->aModule); j; j=sqliteHashNext(j)){ + Module *pMod = (Module*)sqliteHashData(j); + sqlite3VdbeMultiLoad(v, 1, "s", pMod->zName); + } + } + break; +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + case PragTyp_PRAGMA_LIST: { + int i; + for(i=0; i<ArraySize(aPragmaName); i++){ + sqlite3VdbeMultiLoad(v, 1, "s", aPragmaName[i].zName); + } + } + break; +#endif /* SQLITE_INTROSPECTION_PRAGMAS */ + +#endif /* SQLITE_OMIT_SCHEMA_PRAGMAS */ + +#ifndef SQLITE_OMIT_FOREIGN_KEY + case PragTyp_FOREIGN_KEY_LIST: if( zRight ){ + FKey *pFK; + Table *pTab; + pTab = sqlite3FindTable(db, zRight, zDb); + if( pTab && IsOrdinaryTable(pTab) ){ + pFK = pTab->u.tab.pFKey; + if( pFK ){ + int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); + int i = 0; + pParse->nMem = 8; + sqlite3CodeVerifySchema(pParse, iTabDb); + while(pFK){ + int j; + for(j=0; j<pFK->nCol; j++){ + sqlite3VdbeMultiLoad(v, 1, "iissssss", + i, + j, + pFK->zTo, + pTab->aCol[pFK->aCol[j].iFrom].zCnName, + pFK->aCol[j].zCol, + actionName(pFK->aAction[1]), /* ON UPDATE */ + actionName(pFK->aAction[0]), /* ON DELETE */ + "NONE"); + } + ++i; + pFK = pFK->pNextFrom; + } + } + } + } + break; +#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */ + +#ifndef SQLITE_OMIT_FOREIGN_KEY +#ifndef SQLITE_OMIT_TRIGGER + case PragTyp_FOREIGN_KEY_CHECK: { + FKey *pFK; /* A foreign key constraint */ + Table *pTab; /* Child table contain "REFERENCES" keyword */ + Table *pParent; /* Parent table that child points to */ + Index *pIdx; /* Index in the parent table */ + int i; /* Loop counter: Foreign key number for pTab */ + int j; /* Loop counter: Field of the foreign key */ + HashElem *k; /* Loop counter: Next table in schema */ + int x; /* result variable */ + int regResult; /* 3 registers to hold a result row */ + int regRow; /* Registers to hold a row from pTab */ + int addrTop; /* Top of a loop checking foreign keys */ + int addrOk; /* Jump here if the key is OK */ + int *aiCols; /* child to parent column mapping */ + + regResult = pParse->nMem+1; + pParse->nMem += 4; + regRow = ++pParse->nMem; + k = sqliteHashFirst(&db->aDb[iDb].pSchema->tblHash); + while( k ){ + if( zRight ){ + pTab = sqlite3LocateTable(pParse, 0, zRight, zDb); + k = 0; + }else{ + pTab = (Table*)sqliteHashData(k); + k = sqliteHashNext(k); + } + if( pTab==0 || !IsOrdinaryTable(pTab) || pTab->u.tab.pFKey==0 ) continue; + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + zDb = db->aDb[iDb].zDbSName; + sqlite3CodeVerifySchema(pParse, iDb); + sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); + sqlite3TouchRegister(pParse, pTab->nCol+regRow); + sqlite3OpenTable(pParse, 0, iDb, pTab, OP_OpenRead); + sqlite3VdbeLoadString(v, regResult, pTab->zName); + assert( IsOrdinaryTable(pTab) ); + for(i=1, pFK=pTab->u.tab.pFKey; pFK; i++, pFK=pFK->pNextFrom){ + pParent = sqlite3FindTable(db, pFK->zTo, zDb); + if( pParent==0 ) continue; + pIdx = 0; + sqlite3TableLock(pParse, iDb, pParent->tnum, 0, pParent->zName); + x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, 0); + if( x==0 ){ + if( pIdx==0 ){ + sqlite3OpenTable(pParse, i, iDb, pParent, OP_OpenRead); + }else{ + sqlite3VdbeAddOp3(v, OP_OpenRead, i, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + } + }else{ + k = 0; + break; + } + } + assert( pParse->nErr>0 || pFK==0 ); + if( pFK ) break; + if( pParse->nTab<i ) pParse->nTab = i; + addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, 0); VdbeCoverage(v); + assert( IsOrdinaryTable(pTab) ); + for(i=1, pFK=pTab->u.tab.pFKey; pFK; i++, pFK=pFK->pNextFrom){ + pParent = sqlite3FindTable(db, pFK->zTo, zDb); + pIdx = 0; + aiCols = 0; + if( pParent ){ + x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, &aiCols); + assert( x==0 || db->mallocFailed ); + } + addrOk = sqlite3VdbeMakeLabel(pParse); + + /* Generate code to read the child key values into registers + ** regRow..regRow+n. If any of the child key values are NULL, this + ** row cannot cause an FK violation. Jump directly to addrOk in + ** this case. */ + sqlite3TouchRegister(pParse, regRow + pFK->nCol); + for(j=0; j<pFK->nCol; j++){ + int iCol = aiCols ? aiCols[j] : pFK->aCol[j].iFrom; + sqlite3ExprCodeGetColumnOfTable(v, pTab, 0, iCol, regRow+j); + sqlite3VdbeAddOp2(v, OP_IsNull, regRow+j, addrOk); VdbeCoverage(v); + } + + /* Generate code to query the parent index for a matching parent + ** key. If a match is found, jump to addrOk. */ + if( pIdx ){ + sqlite3VdbeAddOp4(v, OP_Affinity, regRow, pFK->nCol, 0, + sqlite3IndexAffinityStr(db,pIdx), pFK->nCol); + sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regRow, pFK->nCol); + VdbeCoverage(v); + }else if( pParent ){ + int jmp = sqlite3VdbeCurrentAddr(v)+2; + sqlite3VdbeAddOp3(v, OP_SeekRowid, i, jmp, regRow); VdbeCoverage(v); + sqlite3VdbeGoto(v, addrOk); + assert( pFK->nCol==1 || db->mallocFailed ); + } + + /* Generate code to report an FK violation to the caller. */ + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_Rowid, 0, regResult+1); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, regResult+1); + } + sqlite3VdbeMultiLoad(v, regResult+2, "siX", pFK->zTo, i-1); + sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, 4); + sqlite3VdbeResolveLabel(v, addrOk); + sqlite3DbFree(db, aiCols); + } + sqlite3VdbeAddOp2(v, OP_Next, 0, addrTop+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrTop); + } + } + break; +#endif /* !defined(SQLITE_OMIT_TRIGGER) */ +#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */ + +#ifndef SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA + /* Reinstall the LIKE and GLOB functions. The variant of LIKE + ** used will be case sensitive or not depending on the RHS. + */ + case PragTyp_CASE_SENSITIVE_LIKE: { + if( zRight ){ + sqlite3RegisterLikeFunctions(db, sqlite3GetBoolean(zRight, 0)); + } + } + break; +#endif /* SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA */ + +#ifndef SQLITE_INTEGRITY_CHECK_ERROR_MAX +# define SQLITE_INTEGRITY_CHECK_ERROR_MAX 100 +#endif + +#ifndef SQLITE_OMIT_INTEGRITY_CHECK + /* PRAGMA integrity_check + ** PRAGMA integrity_check(N) + ** PRAGMA quick_check + ** PRAGMA quick_check(N) + ** + ** Verify the integrity of the database. + ** + ** The "quick_check" is reduced version of + ** integrity_check designed to detect most database corruption + ** without the overhead of cross-checking indexes. Quick_check + ** is linear time whereas integrity_check is O(NlogN). + ** + ** The maximum number of errors is 100 by default. A different default + ** can be specified using a numeric parameter N. + ** + ** Or, the parameter N can be the name of a table. In that case, only + ** the one table named is verified. The freelist is only verified if + ** the named table is "sqlite_schema" (or one of its aliases). + ** + ** All schemas are checked by default. To check just a single + ** schema, use the form: + ** + ** PRAGMA schema.integrity_check; + */ + case PragTyp_INTEGRITY_CHECK: { + int i, j, addr, mxErr; + Table *pObjTab = 0; /* Check only this one table, if not NULL */ + + int isQuick = (sqlite3Tolower(zLeft[0])=='q'); + + /* If the PRAGMA command was of the form "PRAGMA <db>.integrity_check", + ** then iDb is set to the index of the database identified by <db>. + ** In this case, the integrity of database iDb only is verified by + ** the VDBE created below. + ** + ** Otherwise, if the command was simply "PRAGMA integrity_check" (or + ** "PRAGMA quick_check"), then iDb is set to 0. In this case, set iDb + ** to -1 here, to indicate that the VDBE should verify the integrity + ** of all attached databases. */ + assert( iDb>=0 ); + assert( iDb==0 || pId2->z ); + if( pId2->z==0 ) iDb = -1; + + /* Initialize the VDBE program */ + pParse->nMem = 6; + + /* Set the maximum error count */ + mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; + if( zRight ){ + if( sqlite3GetInt32(zRight, &mxErr) ){ + if( mxErr<=0 ){ + mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; + } + }else{ + pObjTab = sqlite3LocateTable(pParse, 0, zRight, + iDb>=0 ? db->aDb[iDb].zDbSName : 0); + } + } + sqlite3VdbeAddOp2(v, OP_Integer, mxErr-1, 1); /* reg[1] holds errors left */ + + /* Do an integrity check on each database file */ + for(i=0; i<db->nDb; i++){ + HashElem *x; /* For looping over tables in the schema */ + Hash *pTbls; /* Set of all tables in the schema */ + int *aRoot; /* Array of root page numbers of all btrees */ + int cnt = 0; /* Number of entries in aRoot[] */ + int mxIdx = 0; /* Maximum number of indexes for any table */ + + if( OMIT_TEMPDB && i==1 ) continue; + if( iDb>=0 && i!=iDb ) continue; + + sqlite3CodeVerifySchema(pParse, i); + pParse->okConstFactor = 0; /* tag-20230327-1 */ + + /* Do an integrity check of the B-Tree + ** + ** Begin by finding the root pages numbers + ** for all tables and indices in the database. + */ + assert( sqlite3SchemaMutexHeld(db, i, 0) ); + pTbls = &db->aDb[i].pSchema->tblHash; + for(cnt=0, x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + Table *pTab = sqliteHashData(x); /* Current table */ + Index *pIdx; /* An index on pTab */ + int nIdx; /* Number of indexes on pTab */ + if( pObjTab && pObjTab!=pTab ) continue; + if( HasRowid(pTab) ) cnt++; + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ cnt++; } + if( nIdx>mxIdx ) mxIdx = nIdx; + } + if( cnt==0 ) continue; + if( pObjTab ) cnt++; + aRoot = sqlite3DbMallocRawNN(db, sizeof(int)*(cnt+1)); + if( aRoot==0 ) break; + cnt = 0; + if( pObjTab ) aRoot[++cnt] = 0; + for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + Table *pTab = sqliteHashData(x); + Index *pIdx; + if( pObjTab && pObjTab!=pTab ) continue; + if( HasRowid(pTab) ) aRoot[++cnt] = pTab->tnum; + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + aRoot[++cnt] = pIdx->tnum; + } + } + aRoot[0] = cnt; + + /* Make sure sufficient number of registers have been allocated */ + sqlite3TouchRegister(pParse, 8+mxIdx); + sqlite3ClearTempRegCache(pParse); + + /* Do the b-tree integrity checks */ + sqlite3VdbeAddOp4(v, OP_IntegrityCk, 2, cnt, 1, (char*)aRoot,P4_INTARRAY); + sqlite3VdbeChangeP5(v, (u8)i); + addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, + sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName), + P4_DYNAMIC); + sqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, addr); + + /* Make sure all the indices are constructed correctly. + */ + for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + Table *pTab = sqliteHashData(x); + Index *pIdx, *pPk; + Index *pPrior = 0; /* Previous index */ + int loopTop; + int iDataCur, iIdxCur; + int r1 = -1; + int bStrict; /* True for a STRICT table */ + int r2; /* Previous key for WITHOUT ROWID tables */ + int mxCol; /* Maximum non-virtual column number */ + + if( pObjTab && pObjTab!=pTab ) continue; + if( !IsOrdinaryTable(pTab) ){ + sqlite3_vtab *pVTab; + int a1; + if( !IsVirtual(pTab) ) continue; + if( pTab->u.vtab.p==0 ) continue; + pVTab = pTab->u.vtab.p->pVtab; + if( NEVER(pVTab==0) ) continue; + if( NEVER(pVTab->pModule==0) ) continue; + if( pVTab->pModule->iVersion<4 ) continue; + if( pVTab->pModule->xIntegrity==0 ) continue; + sqlite3VdbeAddOp2(v, OP_VCheck, 0, 3); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, a1); + continue; + } + if( isQuick || HasRowid(pTab) ){ + pPk = 0; + r2 = 0; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + r2 = sqlite3GetTempRange(pParse, pPk->nKeyCol); + sqlite3VdbeAddOp3(v, OP_Null, 1, r2, r2+pPk->nKeyCol-1); + } + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0, + 1, 0, &iDataCur, &iIdxCur); + /* reg[7] counts the number of entries in the table. + ** reg[8+i] counts the number of entries in the i-th index + */ + sqlite3VdbeAddOp2(v, OP_Integer, 0, 7); + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, 8+j); /* index entries counter */ + } + assert( pParse->nMem>=8+j ); + assert( sqlite3NoTempsInRange(pParse,1,7+j) ); + sqlite3VdbeAddOp2(v, OP_Rewind, iDataCur, 0); VdbeCoverage(v); + loopTop = sqlite3VdbeAddOp2(v, OP_AddImm, 7, 1); + + /* Fetch the right-most column from the table. This will cause + ** the entire record header to be parsed and sanity checked. It + ** will also prepopulate the cursor column cache that is used + ** by the OP_IsType code, so it is a required step. + */ + assert( !IsVirtual(pTab) ); + if( HasRowid(pTab) ){ + mxCol = -1; + for(j=0; j<pTab->nCol; j++){ + if( (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)==0 ) mxCol++; + } + if( mxCol==pTab->iPKey ) mxCol--; + }else{ + /* COLFLAG_VIRTUAL columns are not included in the WITHOUT ROWID + ** PK index column-count, so there is no need to account for them + ** in this case. */ + mxCol = sqlite3PrimaryKeyIndex(pTab)->nColumn-1; + } + if( mxCol>=0 ){ + sqlite3VdbeAddOp3(v, OP_Column, iDataCur, mxCol, 3); + sqlite3VdbeTypeofColumn(v, 3); + } + + if( !isQuick ){ + if( pPk ){ + /* Verify WITHOUT ROWID keys are in ascending order */ + int a1; + char *zErr; + a1 = sqlite3VdbeAddOp4Int(v, OP_IdxGT, iDataCur, 0,r2,pPk->nKeyCol); + VdbeCoverage(v); + sqlite3VdbeAddOp1(v, OP_IsNull, r2); VdbeCoverage(v); + zErr = sqlite3MPrintf(db, + "row not in PRIMARY KEY order for %s", + pTab->zName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, a1); + sqlite3VdbeJumpHere(v, a1+1); + for(j=0; j<pPk->nKeyCol; j++){ + sqlite3ExprCodeLoadIndexColumn(pParse, pPk, iDataCur, j, r2+j); + } + } + } + /* Verify datatypes for all columns: + ** + ** (1) NOT NULL columns may not contain a NULL + ** (2) Datatype must be exact for non-ANY columns in STRICT tables + ** (3) Datatype for TEXT columns in non-STRICT tables must be + ** NULL, TEXT, or BLOB. + ** (4) Datatype for numeric columns in non-STRICT tables must not + ** be a TEXT value that can be losslessly converted to numeric. + */ + bStrict = (pTab->tabFlags & TF_Strict)!=0; + for(j=0; j<pTab->nCol; j++){ + char *zErr; + Column *pCol = pTab->aCol + j; /* The column to be checked */ + int labelError; /* Jump here to report an error */ + int labelOk; /* Jump here if all looks ok */ + int p1, p3, p4; /* Operands to the OP_IsType opcode */ + int doTypeCheck; /* Check datatypes (besides NOT NULL) */ + + if( j==pTab->iPKey ) continue; + if( bStrict ){ + doTypeCheck = pCol->eCType>COLTYPE_ANY; + }else{ + doTypeCheck = pCol->affinity>SQLITE_AFF_BLOB; + } + if( pCol->notNull==0 && !doTypeCheck ) continue; + + /* Compute the operands that will be needed for OP_IsType */ + p4 = SQLITE_NULL; + if( pCol->colFlags & COLFLAG_VIRTUAL ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); + p1 = -1; + p3 = 3; + }else{ + if( pCol->iDflt ){ + sqlite3_value *pDfltValue = 0; + sqlite3ValueFromExpr(db, sqlite3ColumnExpr(pTab,pCol), ENC(db), + pCol->affinity, &pDfltValue); + if( pDfltValue ){ + p4 = sqlite3_value_type(pDfltValue); + sqlite3ValueFree(pDfltValue); + } + } + p1 = iDataCur; + if( !HasRowid(pTab) ){ + testcase( j!=sqlite3TableColumnToStorage(pTab, j) ); + p3 = sqlite3TableColumnToIndex(sqlite3PrimaryKeyIndex(pTab), j); + }else{ + p3 = sqlite3TableColumnToStorage(pTab,j); + testcase( p3!=j); + } + } + + labelError = sqlite3VdbeMakeLabel(pParse); + labelOk = sqlite3VdbeMakeLabel(pParse); + if( pCol->notNull ){ + /* (1) NOT NULL columns may not contain a NULL */ + int jmp3; + int jmp2 = sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + VdbeCoverage(v); + if( p1<0 ){ + sqlite3VdbeChangeP5(v, 0x0f); /* INT, REAL, TEXT, or BLOB */ + jmp3 = jmp2; + }else{ + sqlite3VdbeChangeP5(v, 0x0d); /* INT, TEXT, or BLOB */ + /* OP_IsType does not detect NaN values in the database file + ** which should be treated as a NULL. So if the header type + ** is REAL, we have to load the actual data using OP_Column + ** to reliably determine if the value is a NULL. */ + sqlite3VdbeAddOp3(v, OP_Column, p1, p3, 3); + jmp3 = sqlite3VdbeAddOp2(v, OP_NotNull, 3, labelOk); + VdbeCoverage(v); + } + zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, + pCol->zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + if( doTypeCheck ){ + sqlite3VdbeGoto(v, labelError); + sqlite3VdbeJumpHere(v, jmp2); + sqlite3VdbeJumpHere(v, jmp3); + }else{ + /* VDBE byte code will fall thru */ + } + } + if( bStrict && doTypeCheck ){ + /* (2) Datatype must be exact for non-ANY columns in STRICT tables*/ + static unsigned char aStdTypeMask[] = { + 0x1f, /* ANY */ + 0x18, /* BLOB */ + 0x11, /* INT */ + 0x11, /* INTEGER */ + 0x13, /* REAL */ + 0x14 /* TEXT */ + }; + sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + assert( pCol->eCType>=1 && pCol->eCType<=sizeof(aStdTypeMask) ); + sqlite3VdbeChangeP5(v, aStdTypeMask[pCol->eCType-1]); + VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "non-%s value in %s.%s", + sqlite3StdType[pCol->eCType-1], + pTab->zName, pTab->aCol[j].zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + }else if( !bStrict && pCol->affinity==SQLITE_AFF_TEXT ){ + /* (3) Datatype for TEXT columns in non-STRICT tables must be + ** NULL, TEXT, or BLOB. */ + sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + sqlite3VdbeChangeP5(v, 0x1c); /* NULL, TEXT, or BLOB */ + VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "NUMERIC value in %s.%s", + pTab->zName, pTab->aCol[j].zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + }else if( !bStrict && pCol->affinity>=SQLITE_AFF_NUMERIC ){ + /* (4) Datatype for numeric columns in non-STRICT tables must not + ** be a TEXT value that can be converted to numeric. */ + sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + sqlite3VdbeChangeP5(v, 0x1b); /* NULL, INT, FLOAT, or BLOB */ + VdbeCoverage(v); + if( p1>=0 ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); + } + sqlite3VdbeAddOp4(v, OP_Affinity, 3, 1, 0, "C", P4_STATIC); + sqlite3VdbeAddOp4Int(v, OP_IsType, -1, labelOk, 3, p4); + sqlite3VdbeChangeP5(v, 0x1c); /* NULL, TEXT, or BLOB */ + VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "TEXT value in %s.%s", + pTab->zName, pTab->aCol[j].zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + } + sqlite3VdbeResolveLabel(v, labelError); + integrityCheckResultRow(v); + sqlite3VdbeResolveLabel(v, labelOk); + } + /* Verify CHECK constraints */ + if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ + ExprList *pCheck = sqlite3ExprListDup(db, pTab->pCheck, 0); + if( db->mallocFailed==0 ){ + int addrCkFault = sqlite3VdbeMakeLabel(pParse); + int addrCkOk = sqlite3VdbeMakeLabel(pParse); + char *zErr; + int k; + pParse->iSelfTab = iDataCur + 1; + for(k=pCheck->nExpr-1; k>0; k--){ + sqlite3ExprIfFalse(pParse, pCheck->a[k].pExpr, addrCkFault, 0); + } + sqlite3ExprIfTrue(pParse, pCheck->a[0].pExpr, addrCkOk, + SQLITE_JUMPIFNULL); + sqlite3VdbeResolveLabel(v, addrCkFault); + pParse->iSelfTab = 0; + zErr = sqlite3MPrintf(db, "CHECK constraint failed in %s", + pTab->zName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + integrityCheckResultRow(v); + sqlite3VdbeResolveLabel(v, addrCkOk); + } + sqlite3ExprListDelete(db, pCheck); + } + if( !isQuick ){ /* Omit the remaining tests for quick_check */ + /* Validate index entries for the current row */ + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + int jmp2, jmp3, jmp4, jmp5, label6; + int kk; + int ckUniq = sqlite3VdbeMakeLabel(pParse); + if( pPk==pIdx ) continue; + r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 0, &jmp3, + pPrior, r1); + pPrior = pIdx; + sqlite3VdbeAddOp2(v, OP_AddImm, 8+j, 1);/* increment entry count */ + /* Verify that an index entry exists for the current table row */ + jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1, + pIdx->nColumn); VdbeCoverage(v); + sqlite3VdbeLoadString(v, 3, "row "); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + sqlite3VdbeLoadString(v, 4, " missing from index "); + sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); + jmp5 = sqlite3VdbeLoadString(v, 4, pIdx->zName); + sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); + jmp4 = integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, jmp2); + + /* The OP_IdxRowid opcode is an optimized version of OP_Column + ** that extracts the rowid off the end of the index record. + ** But it only works correctly if index record does not have + ** any extra bytes at the end. Verify that this is the case. */ + if( HasRowid(pTab) ){ + int jmp7; + sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur+j, 3); + jmp7 = sqlite3VdbeAddOp3(v, OP_Eq, 3, 0, r1+pIdx->nColumn-1); + VdbeCoverageNeverNull(v); + sqlite3VdbeLoadString(v, 3, + "rowid not at end-of-record for row "); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + sqlite3VdbeLoadString(v, 4, " of index "); + sqlite3VdbeGoto(v, jmp5-1); + sqlite3VdbeJumpHere(v, jmp7); + } + + /* Any indexed columns with non-BINARY collations must still hold + ** the exact same text value as the table. */ + label6 = 0; + for(kk=0; kk<pIdx->nKeyCol; kk++){ + if( pIdx->azColl[kk]==sqlite3StrBINARY ) continue; + if( label6==0 ) label6 = sqlite3VdbeMakeLabel(pParse); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur+j, kk, 3); + sqlite3VdbeAddOp3(v, OP_Ne, 3, label6, r1+kk); VdbeCoverage(v); + } + if( label6 ){ + int jmp6 = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeResolveLabel(v, label6); + sqlite3VdbeLoadString(v, 3, "row "); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + sqlite3VdbeLoadString(v, 4, " values differ from index "); + sqlite3VdbeGoto(v, jmp5-1); + sqlite3VdbeJumpHere(v, jmp6); + } + + /* For UNIQUE indexes, verify that only one entry exists with the + ** current key. The entry is unique if (1) any column is NULL + ** or (2) the next entry has a different key */ + if( IsUniqueIndex(pIdx) ){ + int uniqOk = sqlite3VdbeMakeLabel(pParse); + int jmp6; + for(kk=0; kk<pIdx->nKeyCol; kk++){ + int iCol = pIdx->aiColumn[kk]; + assert( iCol!=XN_ROWID && iCol<pTab->nCol ); + if( iCol>=0 && pTab->aCol[iCol].notNull ) continue; + sqlite3VdbeAddOp2(v, OP_IsNull, r1+kk, uniqOk); + VdbeCoverage(v); + } + jmp6 = sqlite3VdbeAddOp1(v, OP_Next, iIdxCur+j); VdbeCoverage(v); + sqlite3VdbeGoto(v, uniqOk); + sqlite3VdbeJumpHere(v, jmp6); + sqlite3VdbeAddOp4Int(v, OP_IdxGT, iIdxCur+j, uniqOk, r1, + pIdx->nKeyCol); VdbeCoverage(v); + sqlite3VdbeLoadString(v, 3, "non-unique entry in index "); + sqlite3VdbeGoto(v, jmp5); + sqlite3VdbeResolveLabel(v, uniqOk); + } + sqlite3VdbeJumpHere(v, jmp4); + sqlite3ResolvePartIdxLabel(pParse, jmp3); + } + } + sqlite3VdbeAddOp2(v, OP_Next, iDataCur, loopTop); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, loopTop-1); + if( !isQuick ){ + sqlite3VdbeLoadString(v, 2, "wrong # of entries in index "); + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + if( pPk==pIdx ) continue; + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur+j, 3); + addr = sqlite3VdbeAddOp3(v, OP_Eq, 8+j, 0, 3); VdbeCoverage(v); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + sqlite3VdbeLoadString(v, 4, pIdx->zName); + sqlite3VdbeAddOp3(v, OP_Concat, 4, 2, 3); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, addr); + } + if( pPk ){ + sqlite3ReleaseTempRange(pParse, r2, pPk->nKeyCol); + } + } + } + } + { + static const int iLn = VDBE_OFFSET_LINENO(2); + static const VdbeOpList endCode[] = { + { OP_AddImm, 1, 0, 0}, /* 0 */ + { OP_IfNotZero, 1, 4, 0}, /* 1 */ + { OP_String8, 0, 3, 0}, /* 2 */ + { OP_ResultRow, 3, 1, 0}, /* 3 */ + { OP_Halt, 0, 0, 0}, /* 4 */ + { OP_String8, 0, 3, 0}, /* 5 */ + { OP_Goto, 0, 3, 0}, /* 6 */ + }; + VdbeOp *aOp; + + aOp = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode, iLn); + if( aOp ){ + aOp[0].p2 = 1-mxErr; + aOp[2].p4type = P4_STATIC; + aOp[2].p4.z = "ok"; + aOp[5].p4type = P4_STATIC; + aOp[5].p4.z = (char*)sqlite3ErrStr(SQLITE_CORRUPT); + } + sqlite3VdbeChangeP3(v, 0, sqlite3VdbeCurrentAddr(v)-2); + } + } + break; +#endif /* SQLITE_OMIT_INTEGRITY_CHECK */ + +#ifndef SQLITE_OMIT_UTF16 + /* + ** PRAGMA encoding + ** PRAGMA encoding = "utf-8"|"utf-16"|"utf-16le"|"utf-16be" + ** + ** In its first form, this pragma returns the encoding of the main + ** database. If the database is not initialized, it is initialized now. + ** + ** The second form of this pragma is a no-op if the main database file + ** has not already been initialized. In this case it sets the default + ** encoding that will be used for the main database file if a new file + ** is created. If an existing main database file is opened, then the + ** default text encoding for the existing database is used. + ** + ** In all cases new databases created using the ATTACH command are + ** created to use the same default text encoding as the main database. If + ** the main database has not been initialized and/or created when ATTACH + ** is executed, this is done before the ATTACH operation. + ** + ** In the second form this pragma sets the text encoding to be used in + ** new database files created using this database handle. It is only + ** useful if invoked immediately after the main database i + */ + case PragTyp_ENCODING: { + static const struct EncName { + char *zName; + u8 enc; + } encnames[] = { + { "UTF8", SQLITE_UTF8 }, + { "UTF-8", SQLITE_UTF8 }, /* Must be element [1] */ + { "UTF-16le", SQLITE_UTF16LE }, /* Must be element [2] */ + { "UTF-16be", SQLITE_UTF16BE }, /* Must be element [3] */ + { "UTF16le", SQLITE_UTF16LE }, + { "UTF16be", SQLITE_UTF16BE }, + { "UTF-16", 0 }, /* SQLITE_UTF16NATIVE */ + { "UTF16", 0 }, /* SQLITE_UTF16NATIVE */ + { 0, 0 } + }; + const struct EncName *pEnc; + if( !zRight ){ /* "PRAGMA encoding" */ + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + assert( encnames[SQLITE_UTF8].enc==SQLITE_UTF8 ); + assert( encnames[SQLITE_UTF16LE].enc==SQLITE_UTF16LE ); + assert( encnames[SQLITE_UTF16BE].enc==SQLITE_UTF16BE ); + returnSingleText(v, encnames[ENC(pParse->db)].zName); + }else{ /* "PRAGMA encoding = XXX" */ + /* Only change the value of sqlite.enc if the database handle is not + ** initialized. If the main database exists, the new sqlite.enc value + ** will be overwritten when the schema is next loaded. If it does not + ** already exists, it will be created to use the new encoding value. + */ + if( (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){ + for(pEnc=&encnames[0]; pEnc->zName; pEnc++){ + if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){ + u8 enc = pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE; + SCHEMA_ENC(db) = enc; + sqlite3SetTextEncoding(db, enc); + break; + } + } + if( !pEnc->zName ){ + sqlite3ErrorMsg(pParse, "unsupported encoding: %s", zRight); + } + } + } + } + break; +#endif /* SQLITE_OMIT_UTF16 */ + +#ifndef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS + /* + ** PRAGMA [schema.]schema_version + ** PRAGMA [schema.]schema_version = <integer> + ** + ** PRAGMA [schema.]user_version + ** PRAGMA [schema.]user_version = <integer> + ** + ** PRAGMA [schema.]freelist_count + ** + ** PRAGMA [schema.]data_version + ** + ** PRAGMA [schema.]application_id + ** PRAGMA [schema.]application_id = <integer> + ** + ** The pragma's schema_version and user_version are used to set or get + ** the value of the schema-version and user-version, respectively. Both + ** the schema-version and the user-version are 32-bit signed integers + ** stored in the database header. + ** + ** The schema-cookie is usually only manipulated internally by SQLite. It + ** is incremented by SQLite whenever the database schema is modified (by + ** creating or dropping a table or index). The schema version is used by + ** SQLite each time a query is executed to ensure that the internal cache + ** of the schema used when compiling the SQL query matches the schema of + ** the database against which the compiled query is actually executed. + ** Subverting this mechanism by using "PRAGMA schema_version" to modify + ** the schema-version is potentially dangerous and may lead to program + ** crashes or database corruption. Use with caution! + ** + ** The user-version is not used internally by SQLite. It may be used by + ** applications for any purpose. + */ + case PragTyp_HEADER_VALUE: { + int iCookie = pPragma->iArg; /* Which cookie to read or write */ + sqlite3VdbeUsesBtree(v, iDb); + if( zRight && (pPragma->mPragFlg & PragFlg_ReadOnly)==0 ){ + /* Write the specified cookie value */ + static const VdbeOpList setCookie[] = { + { OP_Transaction, 0, 1, 0}, /* 0 */ + { OP_SetCookie, 0, 0, 0}, /* 1 */ + }; + VdbeOp *aOp; + sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(setCookie)); + aOp = sqlite3VdbeAddOpList(v, ArraySize(setCookie), setCookie, 0); + if( ONLY_IF_REALLOC_STRESS(aOp==0) ) break; + aOp[0].p1 = iDb; + aOp[1].p1 = iDb; + aOp[1].p2 = iCookie; + aOp[1].p3 = sqlite3Atoi(zRight); + aOp[1].p5 = 1; + if( iCookie==BTREE_SCHEMA_VERSION && (db->flags & SQLITE_Defensive)!=0 ){ + /* Do not allow the use of PRAGMA schema_version=VALUE in defensive + ** mode. Change the OP_SetCookie opcode into a no-op. */ + aOp[1].opcode = OP_Noop; + } + }else{ + /* Read the specified cookie value */ + static const VdbeOpList readCookie[] = { + { OP_Transaction, 0, 0, 0}, /* 0 */ + { OP_ReadCookie, 0, 1, 0}, /* 1 */ + { OP_ResultRow, 1, 1, 0} + }; + VdbeOp *aOp; + sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(readCookie)); + aOp = sqlite3VdbeAddOpList(v, ArraySize(readCookie),readCookie,0); + if( ONLY_IF_REALLOC_STRESS(aOp==0) ) break; + aOp[0].p1 = iDb; + aOp[1].p1 = iDb; + aOp[1].p3 = iCookie; + sqlite3VdbeReusable(v); + } + } + break; +#endif /* SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS */ + +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS + /* + ** PRAGMA compile_options + ** + ** Return the names of all compile-time options used in this build, + ** one option per row. + */ + case PragTyp_COMPILE_OPTIONS: { + int i = 0; + const char *zOpt; + pParse->nMem = 1; + while( (zOpt = sqlite3_compileoption_get(i++))!=0 ){ + sqlite3VdbeLoadString(v, 1, zOpt); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); + } + sqlite3VdbeReusable(v); + } + break; +#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ + +#ifndef SQLITE_OMIT_WAL + /* + ** PRAGMA [schema.]wal_checkpoint = passive|full|restart|truncate + ** + ** Checkpoint the database. + */ + case PragTyp_WAL_CHECKPOINT: { + int iBt = (pId2->z?iDb:SQLITE_MAX_DB); + int eMode = SQLITE_CHECKPOINT_PASSIVE; + if( zRight ){ + if( sqlite3StrICmp(zRight, "full")==0 ){ + eMode = SQLITE_CHECKPOINT_FULL; + }else if( sqlite3StrICmp(zRight, "restart")==0 ){ + eMode = SQLITE_CHECKPOINT_RESTART; + }else if( sqlite3StrICmp(zRight, "truncate")==0 ){ + eMode = SQLITE_CHECKPOINT_TRUNCATE; + } + } + pParse->nMem = 3; + sqlite3VdbeAddOp3(v, OP_Checkpoint, iBt, eMode, 1); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); + } + break; + + /* + ** PRAGMA wal_autocheckpoint + ** PRAGMA wal_autocheckpoint = N + ** + ** Configure a database connection to automatically checkpoint a database + ** after accumulating N frames in the log. Or query for the current value + ** of N. + */ + case PragTyp_WAL_AUTOCHECKPOINT: { + if( zRight ){ + sqlite3_wal_autocheckpoint(db, sqlite3Atoi(zRight)); + } + returnSingleInt(v, + db->xWalCallback==sqlite3WalDefaultHook ? + SQLITE_PTR_TO_INT(db->pWalArg) : 0); + } + break; +#endif + + /* + ** PRAGMA shrink_memory + ** + ** IMPLEMENTATION-OF: R-23445-46109 This pragma causes the database + ** connection on which it is invoked to free up as much memory as it + ** can, by calling sqlite3_db_release_memory(). + */ + case PragTyp_SHRINK_MEMORY: { + sqlite3_db_release_memory(db); + break; + } + + /* + ** PRAGMA optimize + ** PRAGMA optimize(MASK) + ** PRAGMA schema.optimize + ** PRAGMA schema.optimize(MASK) + ** + ** Attempt to optimize the database. All schemas are optimized in the first + ** two forms, and only the specified schema is optimized in the latter two. + ** + ** The details of optimizations performed by this pragma are expected + ** to change and improve over time. Applications should anticipate that + ** this pragma will perform new optimizations in future releases. + ** + ** The optional argument is a bitmask of optimizations to perform: + ** + ** 0x0001 Debugging mode. Do not actually perform any optimizations + ** but instead return one line of text for each optimization + ** that would have been done. Off by default. + ** + ** 0x0002 Run ANALYZE on tables that might benefit. On by default. + ** See below for additional information. + ** + ** 0x0004 (Not yet implemented) Record usage and performance + ** information from the current session in the + ** database file so that it will be available to "optimize" + ** pragmas run by future database connections. + ** + ** 0x0008 (Not yet implemented) Create indexes that might have + ** been helpful to recent queries + ** + ** The default MASK is and always shall be 0xfffe. 0xfffe means perform all + ** of the optimizations listed above except Debug Mode, including new + ** optimizations that have not yet been invented. If new optimizations are + ** ever added that should be off by default, those off-by-default + ** optimizations will have bitmasks of 0x10000 or larger. + ** + ** DETERMINATION OF WHEN TO RUN ANALYZE + ** + ** In the current implementation, a table is analyzed if only if all of + ** the following are true: + ** + ** (1) MASK bit 0x02 is set. + ** + ** (2) The query planner used sqlite_stat1-style statistics for one or + ** more indexes of the table at some point during the lifetime of + ** the current connection. + ** + ** (3) One or more indexes of the table are currently unanalyzed OR + ** the number of rows in the table has increased by 25 times or more + ** since the last time ANALYZE was run. + ** + ** The rules for when tables are analyzed are likely to change in + ** future releases. + */ + case PragTyp_OPTIMIZE: { + int iDbLast; /* Loop termination point for the schema loop */ + int iTabCur; /* Cursor for a table whose size needs checking */ + HashElem *k; /* Loop over tables of a schema */ + Schema *pSchema; /* The current schema */ + Table *pTab; /* A table in the schema */ + Index *pIdx; /* An index of the table */ + LogEst szThreshold; /* Size threshold above which reanalysis needed */ + char *zSubSql; /* SQL statement for the OP_SqlExec opcode */ + u32 opMask; /* Mask of operations to perform */ + + if( zRight ){ + opMask = (u32)sqlite3Atoi(zRight); + if( (opMask & 0x02)==0 ) break; + }else{ + opMask = 0xfffe; + } + iTabCur = pParse->nTab++; + for(iDbLast = zDb?iDb:db->nDb-1; iDb<=iDbLast; iDb++){ + if( iDb==1 ) continue; + sqlite3CodeVerifySchema(pParse, iDb); + pSchema = db->aDb[iDb].pSchema; + for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ + pTab = (Table*)sqliteHashData(k); + + /* If table pTab has not been used in a way that would benefit from + ** having analysis statistics during the current session, then skip it. + ** This also has the effect of skipping virtual tables and views */ + if( (pTab->tabFlags & TF_StatsUsed)==0 ) continue; + + /* Reanalyze if the table is 25 times larger than the last analysis */ + szThreshold = pTab->nRowLogEst + 46; assert( sqlite3LogEst(25)==46 ); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( !pIdx->hasStat1 ){ + szThreshold = 0; /* Always analyze if any index lacks statistics */ + break; + } + } + if( szThreshold ){ + sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); + sqlite3VdbeAddOp3(v, OP_IfSmaller, iTabCur, + sqlite3VdbeCurrentAddr(v)+2+(opMask&1), szThreshold); + VdbeCoverage(v); + } + zSubSql = sqlite3MPrintf(db, "ANALYZE \"%w\".\"%w\"", + db->aDb[iDb].zDbSName, pTab->zName); + if( opMask & 0x01 ){ + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp4(v, OP_String8, 0, r1, 0, zSubSql, P4_DYNAMIC); + sqlite3VdbeAddOp2(v, OP_ResultRow, r1, 1); + }else{ + sqlite3VdbeAddOp4(v, OP_SqlExec, 0, 0, 0, zSubSql, P4_DYNAMIC); + } + } + } + sqlite3VdbeAddOp0(v, OP_Expire); + break; + } + + /* + ** PRAGMA busy_timeout + ** PRAGMA busy_timeout = N + ** + ** Call sqlite3_busy_timeout(db, N). Return the current timeout value + ** if one is set. If no busy handler or a different busy handler is set + ** then 0 is returned. Setting the busy_timeout to 0 or negative + ** disables the timeout. + */ + /*case PragTyp_BUSY_TIMEOUT*/ default: { + assert( pPragma->ePragTyp==PragTyp_BUSY_TIMEOUT ); + if( zRight ){ + sqlite3_busy_timeout(db, sqlite3Atoi(zRight)); + } + returnSingleInt(v, db->busyTimeout); + break; + } + + /* + ** PRAGMA soft_heap_limit + ** PRAGMA soft_heap_limit = N + ** + ** IMPLEMENTATION-OF: R-26343-45930 This pragma invokes the + ** sqlite3_soft_heap_limit64() interface with the argument N, if N is + ** specified and is a non-negative integer. + ** IMPLEMENTATION-OF: R-64451-07163 The soft_heap_limit pragma always + ** returns the same integer that would be returned by the + ** sqlite3_soft_heap_limit64(-1) C-language function. + */ + case PragTyp_SOFT_HEAP_LIMIT: { + sqlite3_int64 N; + if( zRight && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK ){ + sqlite3_soft_heap_limit64(N); + } + returnSingleInt(v, sqlite3_soft_heap_limit64(-1)); + break; + } + + /* + ** PRAGMA hard_heap_limit + ** PRAGMA hard_heap_limit = N + ** + ** Invoke sqlite3_hard_heap_limit64() to query or set the hard heap + ** limit. The hard heap limit can be activated or lowered by this + ** pragma, but not raised or deactivated. Only the + ** sqlite3_hard_heap_limit64() C-language API can raise or deactivate + ** the hard heap limit. This allows an application to set a heap limit + ** constraint that cannot be relaxed by an untrusted SQL script. + */ + case PragTyp_HARD_HEAP_LIMIT: { + sqlite3_int64 N; + if( zRight && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK ){ + sqlite3_int64 iPrior = sqlite3_hard_heap_limit64(-1); + if( N>0 && (iPrior==0 || iPrior>N) ) sqlite3_hard_heap_limit64(N); + } + returnSingleInt(v, sqlite3_hard_heap_limit64(-1)); + break; + } + + /* + ** PRAGMA threads + ** PRAGMA threads = N + ** + ** Configure the maximum number of worker threads. Return the new + ** maximum, which might be less than requested. + */ + case PragTyp_THREADS: { + sqlite3_int64 N; + if( zRight + && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK + && N>=0 + ){ + sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, (int)(N&0x7fffffff)); + } + returnSingleInt(v, sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, -1)); + break; + } + + /* + ** PRAGMA analysis_limit + ** PRAGMA analysis_limit = N + ** + ** Configure the maximum number of rows that ANALYZE will examine + ** in each index that it looks at. Return the new limit. + */ + case PragTyp_ANALYSIS_LIMIT: { + sqlite3_int64 N; + if( zRight + && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK /* IMP: R-40975-20399 */ + && N>=0 + ){ + db->nAnalysisLimit = (int)(N&0x7fffffff); + } + returnSingleInt(v, db->nAnalysisLimit); /* IMP: R-57594-65522 */ + break; + } + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + /* + ** Report the current state of file logs for all databases + */ + case PragTyp_LOCK_STATUS: { + static const char *const azLockName[] = { + "unlocked", "shared", "reserved", "pending", "exclusive" + }; + int i; + pParse->nMem = 2; + for(i=0; i<db->nDb; i++){ + Btree *pBt; + const char *zState = "unknown"; + int j; + if( db->aDb[i].zDbSName==0 ) continue; + pBt = db->aDb[i].pBt; + if( pBt==0 || sqlite3BtreePager(pBt)==0 ){ + zState = "closed"; + }else if( sqlite3_file_control(db, i ? db->aDb[i].zDbSName : 0, + SQLITE_FCNTL_LOCKSTATE, &j)==SQLITE_OK ){ + zState = azLockName[j]; + } + sqlite3VdbeMultiLoad(v, 1, "ss", db->aDb[i].zDbSName, zState); + } + break; + } +#endif + +#if defined(SQLITE_ENABLE_CEROD) + case PragTyp_ACTIVATE_EXTENSIONS: if( zRight ){ + if( sqlite3StrNICmp(zRight, "cerod-", 6)==0 ){ + sqlite3_activate_cerod(&zRight[6]); + } + } + break; +#endif + + } /* End of the PRAGMA switch */ + + /* The following block is a no-op unless SQLITE_DEBUG is defined. Its only + ** purpose is to execute assert() statements to verify that if the + ** PragFlg_NoColumns1 flag is set and the caller specified an argument + ** to the PRAGMA, the implementation has not added any OP_ResultRow + ** instructions to the VM. */ + if( (pPragma->mPragFlg & PragFlg_NoColumns1) && zRight ){ + sqlite3VdbeVerifyNoResultRow(v); + } + +pragma_out: + sqlite3DbFree(db, zLeft); + sqlite3DbFree(db, zRight); +} +#ifndef SQLITE_OMIT_VIRTUALTABLE +/***************************************************************************** +** Implementation of an eponymous virtual table that runs a pragma. +** +*/ +typedef struct PragmaVtab PragmaVtab; +typedef struct PragmaVtabCursor PragmaVtabCursor; +struct PragmaVtab { + sqlite3_vtab base; /* Base class. Must be first */ + sqlite3 *db; /* The database connection to which it belongs */ + const PragmaName *pName; /* Name of the pragma */ + u8 nHidden; /* Number of hidden columns */ + u8 iHidden; /* Index of the first hidden column */ +}; +struct PragmaVtabCursor { + sqlite3_vtab_cursor base; /* Base class. Must be first */ + sqlite3_stmt *pPragma; /* The pragma statement to run */ + sqlite_int64 iRowid; /* Current rowid */ + char *azArg[2]; /* Value of the argument and schema */ +}; + +/* +** Pragma virtual table module xConnect method. +*/ +static int pragmaVtabConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + const PragmaName *pPragma = (const PragmaName*)pAux; + PragmaVtab *pTab = 0; + int rc; + int i, j; + char cSep = '('; + StrAccum acc; + char zBuf[200]; + + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); + sqlite3_str_appendall(&acc, "CREATE TABLE x"); + for(i=0, j=pPragma->iPragCName; i<pPragma->nPragCName; i++, j++){ + sqlite3_str_appendf(&acc, "%c\"%s\"", cSep, pragCName[j]); + cSep = ','; + } + if( i==0 ){ + sqlite3_str_appendf(&acc, "(\"%s\"", pPragma->zName); + i++; + } + j = 0; + if( pPragma->mPragFlg & PragFlg_Result1 ){ + sqlite3_str_appendall(&acc, ",arg HIDDEN"); + j++; + } + if( pPragma->mPragFlg & (PragFlg_SchemaOpt|PragFlg_SchemaReq) ){ + sqlite3_str_appendall(&acc, ",schema HIDDEN"); + j++; + } + sqlite3_str_append(&acc, ")", 1); + sqlite3StrAccumFinish(&acc); + assert( strlen(zBuf) < sizeof(zBuf)-1 ); + rc = sqlite3_declare_vtab(db, zBuf); + if( rc==SQLITE_OK ){ + pTab = (PragmaVtab*)sqlite3_malloc(sizeof(PragmaVtab)); + if( pTab==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pTab, 0, sizeof(PragmaVtab)); + pTab->pName = pPragma; + pTab->db = db; + pTab->iHidden = i; + pTab->nHidden = j; + } + }else{ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + } + + *ppVtab = (sqlite3_vtab*)pTab; + return rc; +} + +/* +** Pragma virtual table module xDisconnect method. +*/ +static int pragmaVtabDisconnect(sqlite3_vtab *pVtab){ + PragmaVtab *pTab = (PragmaVtab*)pVtab; + sqlite3_free(pTab); + return SQLITE_OK; +} + +/* Figure out the best index to use to search a pragma virtual table. +** +** There are not really any index choices. But we want to encourage the +** query planner to give == constraints on as many hidden parameters as +** possible, and especially on the first hidden parameter. So return a +** high cost if hidden parameters are unconstrained. +*/ +static int pragmaVtabBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + PragmaVtab *pTab = (PragmaVtab*)tab; + const struct sqlite3_index_constraint *pConstraint; + int i, j; + int seen[2]; + + pIdxInfo->estimatedCost = (double)1; + if( pTab->nHidden==0 ){ return SQLITE_OK; } + pConstraint = pIdxInfo->aConstraint; + seen[0] = 0; + seen[1] = 0; + for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( pConstraint->iColumn < pTab->iHidden ) continue; + j = pConstraint->iColumn - pTab->iHidden; + assert( j < 2 ); + seen[j] = i+1; + } + if( seen[0]==0 ){ + pIdxInfo->estimatedCost = (double)2147483647; + pIdxInfo->estimatedRows = 2147483647; + return SQLITE_OK; + } + j = seen[0]-1; + pIdxInfo->aConstraintUsage[j].argvIndex = 1; + pIdxInfo->aConstraintUsage[j].omit = 1; + if( seen[1]==0 ) return SQLITE_OK; + pIdxInfo->estimatedCost = (double)20; + pIdxInfo->estimatedRows = 20; + j = seen[1]-1; + pIdxInfo->aConstraintUsage[j].argvIndex = 2; + pIdxInfo->aConstraintUsage[j].omit = 1; + return SQLITE_OK; +} + +/* Create a new cursor for the pragma virtual table */ +static int pragmaVtabOpen(sqlite3_vtab *pVtab, sqlite3_vtab_cursor **ppCursor){ + PragmaVtabCursor *pCsr; + pCsr = (PragmaVtabCursor*)sqlite3_malloc(sizeof(*pCsr)); + if( pCsr==0 ) return SQLITE_NOMEM; + memset(pCsr, 0, sizeof(PragmaVtabCursor)); + pCsr->base.pVtab = pVtab; + *ppCursor = &pCsr->base; + return SQLITE_OK; +} + +/* Clear all content from pragma virtual table cursor. */ +static void pragmaVtabCursorClear(PragmaVtabCursor *pCsr){ + int i; + sqlite3_finalize(pCsr->pPragma); + pCsr->pPragma = 0; + for(i=0; i<ArraySize(pCsr->azArg); i++){ + sqlite3_free(pCsr->azArg[i]); + pCsr->azArg[i] = 0; + } +} + +/* Close a pragma virtual table cursor */ +static int pragmaVtabClose(sqlite3_vtab_cursor *cur){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)cur; + pragmaVtabCursorClear(pCsr); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* Advance the pragma virtual table cursor to the next row */ +static int pragmaVtabNext(sqlite3_vtab_cursor *pVtabCursor){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + int rc = SQLITE_OK; + + /* Increment the xRowid value */ + pCsr->iRowid++; + assert( pCsr->pPragma ); + if( SQLITE_ROW!=sqlite3_step(pCsr->pPragma) ){ + rc = sqlite3_finalize(pCsr->pPragma); + pCsr->pPragma = 0; + pragmaVtabCursorClear(pCsr); + } + return rc; +} + +/* +** Pragma virtual table module xFilter method. +*/ +static int pragmaVtabFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + PragmaVtab *pTab = (PragmaVtab*)(pVtabCursor->pVtab); + int rc; + int i, j; + StrAccum acc; + char *zSql; + + UNUSED_PARAMETER(idxNum); + UNUSED_PARAMETER(idxStr); + pragmaVtabCursorClear(pCsr); + j = (pTab->pName->mPragFlg & PragFlg_Result1)!=0 ? 0 : 1; + for(i=0; i<argc; i++, j++){ + const char *zText = (const char*)sqlite3_value_text(argv[i]); + assert( j<ArraySize(pCsr->azArg) ); + assert( pCsr->azArg[j]==0 ); + if( zText ){ + pCsr->azArg[j] = sqlite3_mprintf("%s", zText); + if( pCsr->azArg[j]==0 ){ + return SQLITE_NOMEM; + } + } + } + sqlite3StrAccumInit(&acc, 0, 0, 0, pTab->db->aLimit[SQLITE_LIMIT_SQL_LENGTH]); + sqlite3_str_appendall(&acc, "PRAGMA "); + if( pCsr->azArg[1] ){ + sqlite3_str_appendf(&acc, "%Q.", pCsr->azArg[1]); + } + sqlite3_str_appendall(&acc, pTab->pName->zName); + if( pCsr->azArg[0] ){ + sqlite3_str_appendf(&acc, "=%Q", pCsr->azArg[0]); + } + zSql = sqlite3StrAccumFinish(&acc); + if( zSql==0 ) return SQLITE_NOMEM; + rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pPragma, 0); + sqlite3_free(zSql); + if( rc!=SQLITE_OK ){ + pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); + return rc; + } + return pragmaVtabNext(pVtabCursor); +} + +/* +** Pragma virtual table module xEof method. +*/ +static int pragmaVtabEof(sqlite3_vtab_cursor *pVtabCursor){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + return (pCsr->pPragma==0); +} + +/* The xColumn method simply returns the corresponding column from +** the PRAGMA. +*/ +static int pragmaVtabColumn( + sqlite3_vtab_cursor *pVtabCursor, + sqlite3_context *ctx, + int i +){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + PragmaVtab *pTab = (PragmaVtab*)(pVtabCursor->pVtab); + if( i<pTab->iHidden ){ + sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pPragma, i)); + }else{ + sqlite3_result_text(ctx, pCsr->azArg[i-pTab->iHidden],-1,SQLITE_TRANSIENT); + } + return SQLITE_OK; +} + +/* +** Pragma virtual table module xRowid method. +*/ +static int pragmaVtabRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *p){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + *p = pCsr->iRowid; + return SQLITE_OK; +} + +/* The pragma virtual table object */ +static const sqlite3_module pragmaVtabModule = { + 0, /* iVersion */ + 0, /* xCreate - create a table */ + pragmaVtabConnect, /* xConnect - connect to an existing table */ + pragmaVtabBestIndex, /* xBestIndex - Determine search strategy */ + pragmaVtabDisconnect, /* xDisconnect - Disconnect from a table */ + 0, /* xDestroy - Drop a table */ + pragmaVtabOpen, /* xOpen - open a cursor */ + pragmaVtabClose, /* xClose - close a cursor */ + pragmaVtabFilter, /* xFilter - configure scan constraints */ + pragmaVtabNext, /* xNext - advance a cursor */ + pragmaVtabEof, /* xEof */ + pragmaVtabColumn, /* xColumn - read data */ + pragmaVtabRowid, /* xRowid - read data */ + 0, /* xUpdate - write data */ + 0, /* xBegin - begin transaction */ + 0, /* xSync - sync transaction */ + 0, /* xCommit - commit transaction */ + 0, /* xRollback - rollback transaction */ + 0, /* xFindFunction - function overloading */ + 0, /* xRename - rename the table */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ +}; + +/* +** Check to see if zTabName is really the name of a pragma. If it is, +** then register an eponymous virtual table for that pragma and return +** a pointer to the Module object for the new virtual table. +*/ +SQLITE_PRIVATE Module *sqlite3PragmaVtabRegister(sqlite3 *db, const char *zName){ + const PragmaName *pName; + assert( sqlite3_strnicmp(zName, "pragma_", 7)==0 ); + pName = pragmaLocate(zName+7); + if( pName==0 ) return 0; + if( (pName->mPragFlg & (PragFlg_Result0|PragFlg_Result1))==0 ) return 0; + assert( sqlite3HashFind(&db->aModule, zName)==0 ); + return sqlite3VtabCreateModule(db, zName, &pragmaVtabModule, (void*)pName, 0); +} + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#endif /* SQLITE_OMIT_PRAGMA */ + +/************** End of pragma.c **********************************************/ +/************** Begin file prepare.c *****************************************/ +/* +** 2005 May 25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the implementation of the sqlite3_prepare() +** interface, and routines that contribute to loading the database schema +** from disk. +*/ +/* #include "sqliteInt.h" */ + +/* +** Fill the InitData structure with an error message that indicates +** that the database is corrupt. +*/ +static void corruptSchema( + InitData *pData, /* Initialization context */ + char **azObj, /* Type and name of object being parsed */ + const char *zExtra /* Error information */ +){ + sqlite3 *db = pData->db; + if( db->mallocFailed ){ + pData->rc = SQLITE_NOMEM_BKPT; + }else if( pData->pzErrMsg[0]!=0 ){ + /* A error message has already been generated. Do not overwrite it */ + }else if( pData->mInitFlags & (INITFLAG_AlterMask) ){ + static const char *azAlterType[] = { + "rename", + "drop column", + "add column" + }; + *pData->pzErrMsg = sqlite3MPrintf(db, + "error in %s %s after %s: %s", azObj[0], azObj[1], + azAlterType[(pData->mInitFlags&INITFLAG_AlterMask)-1], + zExtra + ); + pData->rc = SQLITE_ERROR; + }else if( db->flags & SQLITE_WriteSchema ){ + pData->rc = SQLITE_CORRUPT_BKPT; + }else{ + char *z; + const char *zObj = azObj[1] ? azObj[1] : "?"; + z = sqlite3MPrintf(db, "malformed database schema (%s)", zObj); + if( zExtra && zExtra[0] ) z = sqlite3MPrintf(db, "%z - %s", z, zExtra); + *pData->pzErrMsg = z; + pData->rc = SQLITE_CORRUPT_BKPT; + } +} + +/* +** Check to see if any sibling index (another index on the same table) +** of pIndex has the same root page number, and if it does, return true. +** This would indicate a corrupt schema. +*/ +SQLITE_PRIVATE int sqlite3IndexHasDuplicateRootPage(Index *pIndex){ + Index *p; + for(p=pIndex->pTable->pIndex; p; p=p->pNext){ + if( p->tnum==pIndex->tnum && p!=pIndex ) return 1; + } + return 0; +} + +/* forward declaration */ +static int sqlite3Prepare( + sqlite3 *db, /* Database handle. */ + const char *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ + Vdbe *pReprepare, /* VM being reprepared */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const char **pzTail /* OUT: End of parsed string */ +); + + +/* +** This is the callback routine for the code that initializes the +** database. See sqlite3Init() below for additional information. +** This routine is also called from the OP_ParseSchema opcode of the VDBE. +** +** Each callback contains the following information: +** +** argv[0] = type of object: "table", "index", "trigger", or "view". +** argv[1] = name of thing being created +** argv[2] = associated table if an index or trigger +** argv[3] = root page number for table or index. 0 for trigger or view. +** argv[4] = SQL text for the CREATE statement. +** +*/ +SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ + InitData *pData = (InitData*)pInit; + sqlite3 *db = pData->db; + int iDb = pData->iDb; + + assert( argc==5 ); + UNUSED_PARAMETER2(NotUsed, argc); + assert( sqlite3_mutex_held(db->mutex) ); + db->mDbFlags |= DBFLAG_EncodingFixed; + if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */ + pData->nInitRow++; + if( db->mallocFailed ){ + corruptSchema(pData, argv, 0); + return 1; + } + + assert( iDb>=0 && iDb<db->nDb ); + if( argv[3]==0 ){ + corruptSchema(pData, argv, 0); + }else if( argv[4] + && 'c'==sqlite3UpperToLower[(unsigned char)argv[4][0]] + && 'r'==sqlite3UpperToLower[(unsigned char)argv[4][1]] ){ + /* Call the parser to process a CREATE TABLE, INDEX or VIEW. + ** But because db->init.busy is set to 1, no VDBE code is generated + ** or executed. All the parser does is build the internal data + ** structures that describe the table, index, or view. + ** + ** No other valid SQL statement, other than the variable CREATE statements, + ** can begin with the letters "C" and "R". Thus, it is not possible run + ** any other kind of statement while parsing the schema, even a corrupt + ** schema. + */ + int rc; + u8 saved_iDb = db->init.iDb; + sqlite3_stmt *pStmt; + TESTONLY(int rcp); /* Return code from sqlite3_prepare() */ + + assert( db->init.busy ); + db->init.iDb = iDb; + if( sqlite3GetUInt32(argv[3], &db->init.newTnum)==0 + || (db->init.newTnum>pData->mxPage && pData->mxPage>0) + ){ + if( sqlite3Config.bExtraSchemaChecks ){ + corruptSchema(pData, argv, "invalid rootpage"); + } + } + db->init.orphanTrigger = 0; + db->init.azInit = (const char**)argv; + pStmt = 0; + TESTONLY(rcp = ) sqlite3Prepare(db, argv[4], -1, 0, 0, &pStmt, 0); + rc = db->errCode; + assert( (rc&0xFF)==(rcp&0xFF) ); + db->init.iDb = saved_iDb; + /* assert( saved_iDb==0 || (db->mDbFlags & DBFLAG_Vacuum)!=0 ); */ + if( SQLITE_OK!=rc ){ + if( db->init.orphanTrigger ){ + assert( iDb==1 ); + }else{ + if( rc > pData->rc ) pData->rc = rc; + if( rc==SQLITE_NOMEM ){ + sqlite3OomFault(db); + }else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED ){ + corruptSchema(pData, argv, sqlite3_errmsg(db)); + } + } + } + db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ + sqlite3_finalize(pStmt); + }else if( argv[1]==0 || (argv[4]!=0 && argv[4][0]!=0) ){ + corruptSchema(pData, argv, 0); + }else{ + /* If the SQL column is blank it means this is an index that + ** was created to be the PRIMARY KEY or to fulfill a UNIQUE + ** constraint for a CREATE TABLE. The index should have already + ** been created when we processed the CREATE TABLE. All we have + ** to do here is record the root page number for that index. + */ + Index *pIndex; + pIndex = sqlite3FindIndex(db, argv[1], db->aDb[iDb].zDbSName); + if( pIndex==0 ){ + corruptSchema(pData, argv, "orphan index"); + }else + if( sqlite3GetUInt32(argv[3],&pIndex->tnum)==0 + || pIndex->tnum<2 + || pIndex->tnum>pData->mxPage + || sqlite3IndexHasDuplicateRootPage(pIndex) + ){ + if( sqlite3Config.bExtraSchemaChecks ){ + corruptSchema(pData, argv, "invalid rootpage"); + } + } + } + return 0; +} + +/* +** Attempt to read the database schema and initialize internal +** data structures for a single database file. The index of the +** database file is given by iDb. iDb==0 is used for the main +** database. iDb==1 should never be used. iDb>=2 is used for +** auxiliary databases. Return one of the SQLITE_ error codes to +** indicate success or failure. +*/ +SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFlags){ + int rc; + int i; +#ifndef SQLITE_OMIT_DEPRECATED + int size; +#endif + Db *pDb; + char const *azArg[6]; + int meta[5]; + InitData initData; + const char *zSchemaTabName; + int openedTransaction = 0; + int mask = ((db->mDbFlags & DBFLAG_EncodingFixed) | ~DBFLAG_EncodingFixed); + + assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 ); + assert( iDb>=0 && iDb<db->nDb ); + assert( db->aDb[iDb].pSchema ); + assert( sqlite3_mutex_held(db->mutex) ); + assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) ); + + db->init.busy = 1; + + /* Construct the in-memory representation schema tables (sqlite_schema or + ** sqlite_temp_schema) by invoking the parser directly. The appropriate + ** table name will be inserted automatically by the parser so we can just + ** use the abbreviation "x" here. The parser will also automatically tag + ** the schema table as read-only. */ + azArg[0] = "table"; + azArg[1] = zSchemaTabName = SCHEMA_TABLE(iDb); + azArg[2] = azArg[1]; + azArg[3] = "1"; + azArg[4] = "CREATE TABLE x(type text,name text,tbl_name text," + "rootpage int,sql text)"; + azArg[5] = 0; + initData.db = db; + initData.iDb = iDb; + initData.rc = SQLITE_OK; + initData.pzErrMsg = pzErrMsg; + initData.mInitFlags = mFlags; + initData.nInitRow = 0; + initData.mxPage = 0; + sqlite3InitCallback(&initData, 5, (char **)azArg, 0); + db->mDbFlags &= mask; + if( initData.rc ){ + rc = initData.rc; + goto error_out; + } + + /* Create a cursor to hold the database open + */ + pDb = &db->aDb[iDb]; + if( pDb->pBt==0 ){ + assert( iDb==1 ); + DbSetProperty(db, 1, DB_SchemaLoaded); + rc = SQLITE_OK; + goto error_out; + } + + /* If there is not already a read-only (or read-write) transaction opened + ** on the b-tree database, open one now. If a transaction is opened, it + ** will be closed before this function returns. */ + sqlite3BtreeEnter(pDb->pBt); + if( sqlite3BtreeTxnState(pDb->pBt)==SQLITE_TXN_NONE ){ + rc = sqlite3BtreeBeginTrans(pDb->pBt, 0, 0); + if( rc!=SQLITE_OK ){ + sqlite3SetString(pzErrMsg, db, sqlite3ErrStr(rc)); + goto initone_error_out; + } + openedTransaction = 1; + } + + /* Get the database meta information. + ** + ** Meta values are as follows: + ** meta[0] Schema cookie. Changes with each schema change. + ** meta[1] File format of schema layer. + ** meta[2] Size of the page cache. + ** meta[3] Largest rootpage (auto/incr_vacuum mode) + ** meta[4] Db text encoding. 1:UTF-8 2:UTF-16LE 3:UTF-16BE + ** meta[5] User version + ** meta[6] Incremental vacuum mode + ** meta[7] unused + ** meta[8] unused + ** meta[9] unused + ** + ** Note: The #defined SQLITE_UTF* symbols in sqliteInt.h correspond to + ** the possible values of meta[4]. + */ + for(i=0; i<ArraySize(meta); i++){ + sqlite3BtreeGetMeta(pDb->pBt, i+1, (u32 *)&meta[i]); + } + if( (db->flags & SQLITE_ResetDatabase)!=0 ){ + memset(meta, 0, sizeof(meta)); + } + pDb->pSchema->schema_cookie = meta[BTREE_SCHEMA_VERSION-1]; + + /* If opening a non-empty database, check the text encoding. For the + ** main database, set sqlite3.enc to the encoding of the main database. + ** For an attached db, it is an error if the encoding is not the same + ** as sqlite3.enc. + */ + if( meta[BTREE_TEXT_ENCODING-1] ){ /* text encoding */ + if( iDb==0 && (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){ + u8 encoding; +#ifndef SQLITE_OMIT_UTF16 + /* If opening the main database, set ENC(db). */ + encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3; + if( encoding==0 ) encoding = SQLITE_UTF8; +#else + encoding = SQLITE_UTF8; +#endif + if( db->nVdbeActive>0 && encoding!=ENC(db) + && (db->mDbFlags & DBFLAG_Vacuum)==0 + ){ + rc = SQLITE_LOCKED; + goto initone_error_out; + }else{ + sqlite3SetTextEncoding(db, encoding); + } + }else{ + /* If opening an attached database, the encoding much match ENC(db) */ + if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){ + sqlite3SetString(pzErrMsg, db, "attached databases must use the same" + " text encoding as main database"); + rc = SQLITE_ERROR; + goto initone_error_out; + } + } + } + pDb->pSchema->enc = ENC(db); + + if( pDb->pSchema->cache_size==0 ){ +#ifndef SQLITE_OMIT_DEPRECATED + size = sqlite3AbsInt32(meta[BTREE_DEFAULT_CACHE_SIZE-1]); + if( size==0 ){ size = SQLITE_DEFAULT_CACHE_SIZE; } + pDb->pSchema->cache_size = size; +#else + pDb->pSchema->cache_size = SQLITE_DEFAULT_CACHE_SIZE; +#endif + sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size); + } + + /* + ** file_format==1 Version 3.0.0. + ** file_format==2 Version 3.1.3. // ALTER TABLE ADD COLUMN + ** file_format==3 Version 3.1.4. // ditto but with non-NULL defaults + ** file_format==4 Version 3.3.0. // DESC indices. Boolean constants + */ + pDb->pSchema->file_format = (u8)meta[BTREE_FILE_FORMAT-1]; + if( pDb->pSchema->file_format==0 ){ + pDb->pSchema->file_format = 1; + } + if( pDb->pSchema->file_format>SQLITE_MAX_FILE_FORMAT ){ + sqlite3SetString(pzErrMsg, db, "unsupported file format"); + rc = SQLITE_ERROR; + goto initone_error_out; + } + + /* Ticket #2804: When we open a database in the newer file format, + ** clear the legacy_file_format pragma flag so that a VACUUM will + ** not downgrade the database and thus invalidate any descending + ** indices that the user might have created. + */ + if( iDb==0 && meta[BTREE_FILE_FORMAT-1]>=4 ){ + db->flags &= ~(u64)SQLITE_LegacyFileFmt; + } + + /* Read the schema information out of the schema tables + */ + assert( db->init.busy ); + initData.mxPage = sqlite3BtreeLastPage(pDb->pBt); + { + char *zSql; + zSql = sqlite3MPrintf(db, + "SELECT*FROM\"%w\".%s ORDER BY rowid", + db->aDb[iDb].zDbSName, zSchemaTabName); +#ifndef SQLITE_OMIT_AUTHORIZATION + { + sqlite3_xauth xAuth; + xAuth = db->xAuth; + db->xAuth = 0; +#endif + rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0); +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; + } +#endif + if( rc==SQLITE_OK ) rc = initData.rc; + sqlite3DbFree(db, zSql); +#ifndef SQLITE_OMIT_ANALYZE + if( rc==SQLITE_OK ){ + sqlite3AnalysisLoad(db, iDb); + } +#endif + } + assert( pDb == &(db->aDb[iDb]) ); + if( db->mallocFailed ){ + rc = SQLITE_NOMEM_BKPT; + sqlite3ResetAllSchemasOfConnection(db); + pDb = &db->aDb[iDb]; + }else + if( rc==SQLITE_OK || ((db->flags&SQLITE_NoSchemaError) && rc!=SQLITE_NOMEM)){ + /* Hack: If the SQLITE_NoSchemaError flag is set, then consider + ** the schema loaded, even if errors (other than OOM) occurred. In + ** this situation the current sqlite3_prepare() operation will fail, + ** but the following one will attempt to compile the supplied statement + ** against whatever subset of the schema was loaded before the error + ** occurred. + ** + ** The primary purpose of this is to allow access to the sqlite_schema + ** table even when its contents have been corrupted. + */ + DbSetProperty(db, iDb, DB_SchemaLoaded); + rc = SQLITE_OK; + } + + /* Jump here for an error that occurs after successfully allocating + ** curMain and calling sqlite3BtreeEnter(). For an error that occurs + ** before that point, jump to error_out. + */ +initone_error_out: + if( openedTransaction ){ + sqlite3BtreeCommit(pDb->pBt); + } + sqlite3BtreeLeave(pDb->pBt); + +error_out: + if( rc ){ + if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){ + sqlite3OomFault(db); + } + sqlite3ResetOneSchema(db, iDb); + } + db->init.busy = 0; + return rc; +} + +/* +** Initialize all database files - the main database file, the file +** used to store temporary tables, and any additional database files +** created using ATTACH statements. Return a success code. If an +** error occurs, write an error message into *pzErrMsg. +** +** After a database is initialized, the DB_SchemaLoaded bit is set +** bit is set in the flags field of the Db structure. +*/ +SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){ + int i, rc; + int commit_internal = !(db->mDbFlags&DBFLAG_SchemaChange); + + assert( sqlite3_mutex_held(db->mutex) ); + assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) ); + assert( db->init.busy==0 ); + ENC(db) = SCHEMA_ENC(db); + assert( db->nDb>0 ); + /* Do the main schema first */ + if( !DbHasProperty(db, 0, DB_SchemaLoaded) ){ + rc = sqlite3InitOne(db, 0, pzErrMsg, 0); + if( rc ) return rc; + } + /* All other schemas after the main schema. The "temp" schema must be last */ + for(i=db->nDb-1; i>0; i--){ + assert( i==1 || sqlite3BtreeHoldsMutex(db->aDb[i].pBt) ); + if( !DbHasProperty(db, i, DB_SchemaLoaded) ){ + rc = sqlite3InitOne(db, i, pzErrMsg, 0); + if( rc ) return rc; + } + } + if( commit_internal ){ + sqlite3CommitInternalChanges(db); + } + return SQLITE_OK; +} + +/* +** This routine is a no-op if the database schema is already initialized. +** Otherwise, the schema is loaded. An error code is returned. +*/ +SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse){ + int rc = SQLITE_OK; + sqlite3 *db = pParse->db; + assert( sqlite3_mutex_held(db->mutex) ); + if( !db->init.busy ){ + rc = sqlite3Init(db, &pParse->zErrMsg); + if( rc!=SQLITE_OK ){ + pParse->rc = rc; + pParse->nErr++; + }else if( db->noSharedCache ){ + db->mDbFlags |= DBFLAG_SchemaKnownOk; + } + } + return rc; +} + + +/* +** Check schema cookies in all databases. If any cookie is out +** of date set pParse->rc to SQLITE_SCHEMA. If all schema cookies +** make no changes to pParse->rc. +*/ +static void schemaIsValid(Parse *pParse){ + sqlite3 *db = pParse->db; + int iDb; + int rc; + int cookie; + + assert( pParse->checkSchema ); + assert( sqlite3_mutex_held(db->mutex) ); + for(iDb=0; iDb<db->nDb; iDb++){ + int openedTransaction = 0; /* True if a transaction is opened */ + Btree *pBt = db->aDb[iDb].pBt; /* Btree database to read cookie from */ + if( pBt==0 ) continue; + + /* If there is not already a read-only (or read-write) transaction opened + ** on the b-tree database, open one now. If a transaction is opened, it + ** will be closed immediately after reading the meta-value. */ + if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_NONE ){ + rc = sqlite3BtreeBeginTrans(pBt, 0, 0); + if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){ + sqlite3OomFault(db); + pParse->rc = SQLITE_NOMEM; + } + if( rc!=SQLITE_OK ) return; + openedTransaction = 1; + } + + /* Read the schema cookie from the database. If it does not match the + ** value stored as part of the in-memory schema representation, + ** set Parse.rc to SQLITE_SCHEMA. */ + sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&cookie); + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( cookie!=db->aDb[iDb].pSchema->schema_cookie ){ + if( DbHasProperty(db, iDb, DB_SchemaLoaded) ) pParse->rc = SQLITE_SCHEMA; + sqlite3ResetOneSchema(db, iDb); + } + + /* Close the transaction, if one was opened. */ + if( openedTransaction ){ + sqlite3BtreeCommit(pBt); + } + } +} + +/* +** Convert a schema pointer into the iDb index that indicates +** which database file in db->aDb[] the schema refers to. +** +** If the same database is attached more than once, the first +** attached database is returned. +*/ +SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){ + int i = -32768; + + /* If pSchema is NULL, then return -32768. This happens when code in + ** expr.c is trying to resolve a reference to a transient table (i.e. one + ** created by a sub-select). In this case the return value of this + ** function should never be used. + ** + ** We return -32768 instead of the more usual -1 simply because using + ** -32768 as the incorrect index into db->aDb[] is much + ** more likely to cause a segfault than -1 (of course there are assert() + ** statements too, but it never hurts to play the odds) and + ** -32768 will still fit into a 16-bit signed integer. + */ + assert( sqlite3_mutex_held(db->mutex) ); + if( pSchema ){ + for(i=0; 1; i++){ + assert( i<db->nDb ); + if( db->aDb[i].pSchema==pSchema ){ + break; + } + } + assert( i>=0 && i<db->nDb ); + } + return i; +} + +/* +** Free all memory allocations in the pParse object +*/ +SQLITE_PRIVATE void sqlite3ParseObjectReset(Parse *pParse){ + sqlite3 *db = pParse->db; + assert( db!=0 ); + assert( db->pParse==pParse ); + assert( pParse->nested==0 ); +#ifndef SQLITE_OMIT_SHARED_CACHE + if( pParse->aTableLock ) sqlite3DbNNFreeNN(db, pParse->aTableLock); +#endif + while( pParse->pCleanup ){ + ParseCleanup *pCleanup = pParse->pCleanup; + pParse->pCleanup = pCleanup->pNext; + pCleanup->xCleanup(db, pCleanup->pPtr); + sqlite3DbNNFreeNN(db, pCleanup); + } + if( pParse->aLabel ) sqlite3DbNNFreeNN(db, pParse->aLabel); + if( pParse->pConstExpr ){ + sqlite3ExprListDelete(db, pParse->pConstExpr); + } + assert( db->lookaside.bDisable >= pParse->disableLookaside ); + db->lookaside.bDisable -= pParse->disableLookaside; + db->lookaside.sz = db->lookaside.bDisable ? 0 : db->lookaside.szTrue; + assert( pParse->db->pParse==pParse ); + db->pParse = pParse->pOuterParse; + pParse->db = 0; + pParse->disableLookaside = 0; +} + +/* +** Add a new cleanup operation to a Parser. The cleanup should happen when +** the parser object is destroyed. But, beware: the cleanup might happen +** immediately. +** +** Use this mechanism for uncommon cleanups. There is a higher setup +** cost for this mechanism (an extra malloc), so it should not be used +** for common cleanups that happen on most calls. But for less +** common cleanups, we save a single NULL-pointer comparison in +** sqlite3ParseObjectReset(), which reduces the total CPU cycle count. +** +** If a memory allocation error occurs, then the cleanup happens immediately. +** When either SQLITE_DEBUG or SQLITE_COVERAGE_TEST are defined, the +** pParse->earlyCleanup flag is set in that case. Calling code show verify +** that test cases exist for which this happens, to guard against possible +** use-after-free errors following an OOM. The preferred way to do this is +** to immediately follow the call to this routine with: +** +** testcase( pParse->earlyCleanup ); +** +** This routine returns a copy of its pPtr input (the third parameter) +** except if an early cleanup occurs, in which case it returns NULL. So +** another way to check for early cleanup is to check the return value. +** Or, stop using the pPtr parameter with this call and use only its +** return value thereafter. Something like this: +** +** pObj = sqlite3ParserAddCleanup(pParse, destructor, pObj); +*/ +SQLITE_PRIVATE void *sqlite3ParserAddCleanup( + Parse *pParse, /* Destroy when this Parser finishes */ + void (*xCleanup)(sqlite3*,void*), /* The cleanup routine */ + void *pPtr /* Pointer to object to be cleaned up */ +){ + ParseCleanup *pCleanup = sqlite3DbMallocRaw(pParse->db, sizeof(*pCleanup)); + if( pCleanup ){ + pCleanup->pNext = pParse->pCleanup; + pParse->pCleanup = pCleanup; + pCleanup->pPtr = pPtr; + pCleanup->xCleanup = xCleanup; + }else{ + xCleanup(pParse->db, pPtr); + pPtr = 0; +#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) + pParse->earlyCleanup = 1; +#endif + } + return pPtr; +} + +/* +** Turn bulk memory into a valid Parse object and link that Parse object +** into database connection db. +** +** Call sqlite3ParseObjectReset() to undo this operation. +** +** Caution: Do not confuse this routine with sqlite3ParseObjectInit() which +** is generated by Lemon. +*/ +SQLITE_PRIVATE void sqlite3ParseObjectInit(Parse *pParse, sqlite3 *db){ + memset(PARSE_HDR(pParse), 0, PARSE_HDR_SZ); + memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); + assert( db->pParse!=pParse ); + pParse->pOuterParse = db->pParse; + db->pParse = pParse; + pParse->db = db; + if( db->mallocFailed ) sqlite3ErrorMsg(pParse, "out of memory"); +} + +/* +** Maximum number of times that we will try again to prepare a statement +** that returns SQLITE_ERROR_RETRY. +*/ +#ifndef SQLITE_MAX_PREPARE_RETRY +# define SQLITE_MAX_PREPARE_RETRY 25 +#endif + +/* +** Compile the UTF-8 encoded SQL statement zSql into a statement handle. +*/ +static int sqlite3Prepare( + sqlite3 *db, /* Database handle. */ + const char *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ + Vdbe *pReprepare, /* VM being reprepared */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const char **pzTail /* OUT: End of parsed string */ +){ + int rc = SQLITE_OK; /* Result code */ + int i; /* Loop counter */ + Parse sParse; /* Parsing context */ + + /* sqlite3ParseObjectInit(&sParse, db); // inlined for performance */ + memset(PARSE_HDR(&sParse), 0, PARSE_HDR_SZ); + memset(PARSE_TAIL(&sParse), 0, PARSE_TAIL_SZ); + sParse.pOuterParse = db->pParse; + db->pParse = &sParse; + sParse.db = db; + if( pReprepare ){ + sParse.pReprepare = pReprepare; + sParse.explain = sqlite3_stmt_isexplain((sqlite3_stmt*)pReprepare); + }else{ + assert( sParse.pReprepare==0 ); + } + assert( ppStmt && *ppStmt==0 ); + if( db->mallocFailed ){ + sqlite3ErrorMsg(&sParse, "out of memory"); + db->errCode = rc = SQLITE_NOMEM; + goto end_prepare; + } + assert( sqlite3_mutex_held(db->mutex) ); + + /* For a long-term use prepared statement avoid the use of + ** lookaside memory. + */ + if( prepFlags & SQLITE_PREPARE_PERSISTENT ){ + sParse.disableLookaside++; + DisableLookaside; + } + sParse.prepFlags = prepFlags & 0xff; + + /* Check to verify that it is possible to get a read lock on all + ** database schemas. The inability to get a read lock indicates that + ** some other database connection is holding a write-lock, which in + ** turn means that the other connection has made uncommitted changes + ** to the schema. + ** + ** Were we to proceed and prepare the statement against the uncommitted + ** schema changes and if those schema changes are subsequently rolled + ** back and different changes are made in their place, then when this + ** prepared statement goes to run the schema cookie would fail to detect + ** the schema change. Disaster would follow. + ** + ** This thread is currently holding mutexes on all Btrees (because + ** of the sqlite3BtreeEnterAll() in sqlite3LockAndPrepare()) so it + ** is not possible for another thread to start a new schema change + ** while this routine is running. Hence, we do not need to hold + ** locks on the schema, we just need to make sure nobody else is + ** holding them. + ** + ** Note that setting READ_UNCOMMITTED overrides most lock detection, + ** but it does *not* override schema lock detection, so this all still + ** works even if READ_UNCOMMITTED is set. + */ + if( !db->noSharedCache ){ + for(i=0; i<db->nDb; i++) { + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + assert( sqlite3BtreeHoldsMutex(pBt) ); + rc = sqlite3BtreeSchemaLocked(pBt); + if( rc ){ + const char *zDb = db->aDb[i].zDbSName; + sqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb); + testcase( db->flags & SQLITE_ReadUncommit ); + goto end_prepare; + } + } + } + } + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( db->pDisconnect ) sqlite3VtabUnlockList(db); +#endif + + if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){ + char *zSqlCopy; + int mxLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; + testcase( nBytes==mxLen ); + testcase( nBytes==mxLen+1 ); + if( nBytes>mxLen ){ + sqlite3ErrorWithMsg(db, SQLITE_TOOBIG, "statement too long"); + rc = sqlite3ApiExit(db, SQLITE_TOOBIG); + goto end_prepare; + } + zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes); + if( zSqlCopy ){ + sqlite3RunParser(&sParse, zSqlCopy); + sParse.zTail = &zSql[sParse.zTail-zSqlCopy]; + sqlite3DbFree(db, zSqlCopy); + }else{ + sParse.zTail = &zSql[nBytes]; + } + }else{ + sqlite3RunParser(&sParse, zSql); + } + assert( 0==sParse.nQueryLoop ); + + if( pzTail ){ + *pzTail = sParse.zTail; + } + + if( db->init.busy==0 ){ + sqlite3VdbeSetSql(sParse.pVdbe, zSql, (int)(sParse.zTail-zSql), prepFlags); + } + if( db->mallocFailed ){ + sParse.rc = SQLITE_NOMEM_BKPT; + sParse.checkSchema = 0; + } + if( sParse.rc!=SQLITE_OK && sParse.rc!=SQLITE_DONE ){ + if( sParse.checkSchema && db->init.busy==0 ){ + schemaIsValid(&sParse); + } + if( sParse.pVdbe ){ + sqlite3VdbeFinalize(sParse.pVdbe); + } + assert( 0==(*ppStmt) ); + rc = sParse.rc; + if( sParse.zErrMsg ){ + sqlite3ErrorWithMsg(db, rc, "%s", sParse.zErrMsg); + sqlite3DbFree(db, sParse.zErrMsg); + }else{ + sqlite3Error(db, rc); + } + }else{ + assert( sParse.zErrMsg==0 ); + *ppStmt = (sqlite3_stmt*)sParse.pVdbe; + rc = SQLITE_OK; + sqlite3ErrorClear(db); + } + + + /* Delete any TriggerPrg structures allocated while parsing this statement. */ + while( sParse.pTriggerPrg ){ + TriggerPrg *pT = sParse.pTriggerPrg; + sParse.pTriggerPrg = pT->pNext; + sqlite3DbFree(db, pT); + } + +end_prepare: + + sqlite3ParseObjectReset(&sParse); + return rc; +} +static int sqlite3LockAndPrepare( + sqlite3 *db, /* Database handle. */ + const char *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ + Vdbe *pOld, /* VM being reprepared */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const char **pzTail /* OUT: End of parsed string */ +){ + int rc; + int cnt = 0; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppStmt==0 ) return SQLITE_MISUSE_BKPT; +#endif + *ppStmt = 0; + if( !sqlite3SafetyCheckOk(db)||zSql==0 ){ + return SQLITE_MISUSE_BKPT; + } + sqlite3_mutex_enter(db->mutex); + sqlite3BtreeEnterAll(db); + do{ + /* Make multiple attempts to compile the SQL, until it either succeeds + ** or encounters a permanent error. A schema problem after one schema + ** reset is considered a permanent error. */ + rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); + assert( rc==SQLITE_OK || *ppStmt==0 ); + if( rc==SQLITE_OK || db->mallocFailed ) break; + }while( (rc==SQLITE_ERROR_RETRY && (cnt++)<SQLITE_MAX_PREPARE_RETRY) + || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt++)==0) ); + sqlite3BtreeLeaveAll(db); + rc = sqlite3ApiExit(db, rc); + assert( (rc&db->errMask)==rc ); + db->busyHandler.nBusy = 0; + sqlite3_mutex_leave(db->mutex); + return rc; +} + + +/* +** Rerun the compilation of a statement after a schema change. +** +** If the statement is successfully recompiled, return SQLITE_OK. Otherwise, +** if the statement cannot be recompiled because another connection has +** locked the sqlite3_schema table, return SQLITE_LOCKED. If any other error +** occurs, return SQLITE_SCHEMA. +*/ +SQLITE_PRIVATE int sqlite3Reprepare(Vdbe *p){ + int rc; + sqlite3_stmt *pNew; + const char *zSql; + sqlite3 *db; + u8 prepFlags; + + assert( sqlite3_mutex_held(sqlite3VdbeDb(p)->mutex) ); + zSql = sqlite3_sql((sqlite3_stmt *)p); + assert( zSql!=0 ); /* Reprepare only called for prepare_v2() statements */ + db = sqlite3VdbeDb(p); + assert( sqlite3_mutex_held(db->mutex) ); + prepFlags = sqlite3VdbePrepareFlags(p); + rc = sqlite3LockAndPrepare(db, zSql, -1, prepFlags, p, &pNew, 0); + if( rc ){ + if( rc==SQLITE_NOMEM ){ + sqlite3OomFault(db); + } + assert( pNew==0 ); + return rc; + }else{ + assert( pNew!=0 ); + } + sqlite3VdbeSwap((Vdbe*)pNew, p); + sqlite3TransferBindings(pNew, (sqlite3_stmt*)p); + sqlite3VdbeResetStepResult((Vdbe*)pNew); + sqlite3VdbeFinalize((Vdbe*)pNew); + return SQLITE_OK; +} + + +/* +** Two versions of the official API. Legacy and new use. In the legacy +** version, the original SQL text is not saved in the prepared statement +** and so if a schema change occurs, SQLITE_SCHEMA is returned by +** sqlite3_step(). In the new version, the original SQL text is retained +** and the statement is automatically recompiled if an schema change +** occurs. +*/ +SQLITE_API int sqlite3_prepare( + sqlite3 *db, /* Database handle. */ + const char *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const char **pzTail /* OUT: End of parsed string */ +){ + int rc; + rc = sqlite3LockAndPrepare(db,zSql,nBytes,0,0,ppStmt,pzTail); + assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ + return rc; +} +SQLITE_API int sqlite3_prepare_v2( + sqlite3 *db, /* Database handle. */ + const char *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const char **pzTail /* OUT: End of parsed string */ +){ + int rc; + /* EVIDENCE-OF: R-37923-12173 The sqlite3_prepare_v2() interface works + ** exactly the same as sqlite3_prepare_v3() with a zero prepFlags + ** parameter. + ** + ** Proof in that the 5th parameter to sqlite3LockAndPrepare is 0 */ + rc = sqlite3LockAndPrepare(db,zSql,nBytes,SQLITE_PREPARE_SAVESQL,0, + ppStmt,pzTail); + assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); + return rc; +} +SQLITE_API int sqlite3_prepare_v3( + sqlite3 *db, /* Database handle. */ + const char *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const char **pzTail /* OUT: End of parsed string */ +){ + int rc; + /* EVIDENCE-OF: R-56861-42673 sqlite3_prepare_v3() differs from + ** sqlite3_prepare_v2() only in having the extra prepFlags parameter, + ** which is a bit array consisting of zero or more of the + ** SQLITE_PREPARE_* flags. + ** + ** Proof by comparison to the implementation of sqlite3_prepare_v2() + ** directly above. */ + rc = sqlite3LockAndPrepare(db,zSql,nBytes, + SQLITE_PREPARE_SAVESQL|(prepFlags&SQLITE_PREPARE_MASK), + 0,ppStmt,pzTail); + assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); + return rc; +} + + +#ifndef SQLITE_OMIT_UTF16 +/* +** Compile the UTF-16 encoded SQL statement zSql into a statement handle. +*/ +static int sqlite3Prepare16( + sqlite3 *db, /* Database handle. */ + const void *zSql, /* UTF-16 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const void **pzTail /* OUT: End of parsed string */ +){ + /* This function currently works by first transforming the UTF-16 + ** encoded string to UTF-8, then invoking sqlite3_prepare(). The + ** tricky bit is figuring out the pointer to return in *pzTail. + */ + char *zSql8; + const char *zTail8 = 0; + int rc = SQLITE_OK; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppStmt==0 ) return SQLITE_MISUSE_BKPT; +#endif + *ppStmt = 0; + if( !sqlite3SafetyCheckOk(db)||zSql==0 ){ + return SQLITE_MISUSE_BKPT; + } + if( nBytes>=0 ){ + int sz; + const char *z = (const char*)zSql; + for(sz=0; sz<nBytes && (z[sz]!=0 || z[sz+1]!=0); sz += 2){} + nBytes = sz; + } + sqlite3_mutex_enter(db->mutex); + zSql8 = sqlite3Utf16to8(db, zSql, nBytes, SQLITE_UTF16NATIVE); + if( zSql8 ){ + rc = sqlite3LockAndPrepare(db, zSql8, -1, prepFlags, 0, ppStmt, &zTail8); + } + + if( zTail8 && pzTail ){ + /* If sqlite3_prepare returns a tail pointer, we calculate the + ** equivalent pointer into the UTF-16 string by counting the unicode + ** characters between zSql8 and zTail8, and then returning a pointer + ** the same number of characters into the UTF-16 string. + */ + int chars_parsed = sqlite3Utf8CharLen(zSql8, (int)(zTail8-zSql8)); + *pzTail = (u8 *)zSql + sqlite3Utf16ByteLen(zSql, chars_parsed); + } + sqlite3DbFree(db, zSql8); + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Two versions of the official API. Legacy and new use. In the legacy +** version, the original SQL text is not saved in the prepared statement +** and so if a schema change occurs, SQLITE_SCHEMA is returned by +** sqlite3_step(). In the new version, the original SQL text is retained +** and the statement is automatically recompiled if an schema change +** occurs. +*/ +SQLITE_API int sqlite3_prepare16( + sqlite3 *db, /* Database handle. */ + const void *zSql, /* UTF-16 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const void **pzTail /* OUT: End of parsed string */ +){ + int rc; + rc = sqlite3Prepare16(db,zSql,nBytes,0,ppStmt,pzTail); + assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ + return rc; +} +SQLITE_API int sqlite3_prepare16_v2( + sqlite3 *db, /* Database handle. */ + const void *zSql, /* UTF-16 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const void **pzTail /* OUT: End of parsed string */ +){ + int rc; + rc = sqlite3Prepare16(db,zSql,nBytes,SQLITE_PREPARE_SAVESQL,ppStmt,pzTail); + assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ + return rc; +} +SQLITE_API int sqlite3_prepare16_v3( + sqlite3 *db, /* Database handle. */ + const void *zSql, /* UTF-16 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const void **pzTail /* OUT: End of parsed string */ +){ + int rc; + rc = sqlite3Prepare16(db,zSql,nBytes, + SQLITE_PREPARE_SAVESQL|(prepFlags&SQLITE_PREPARE_MASK), + ppStmt,pzTail); + assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ + return rc; +} + +#endif /* SQLITE_OMIT_UTF16 */ + +/************** End of prepare.c *********************************************/ +/************** Begin file select.c ******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle SELECT statements in SQLite. +*/ +/* #include "sqliteInt.h" */ + +/* +** An instance of the following object is used to record information about +** how to process the DISTINCT keyword, to simplify passing that information +** into the selectInnerLoop() routine. +*/ +typedef struct DistinctCtx DistinctCtx; +struct DistinctCtx { + u8 isTnct; /* 0: Not distinct. 1: DISTICT 2: DISTINCT and ORDER BY */ + u8 eTnctType; /* One of the WHERE_DISTINCT_* operators */ + int tabTnct; /* Ephemeral table used for DISTINCT processing */ + int addrTnct; /* Address of OP_OpenEphemeral opcode for tabTnct */ +}; + +/* +** An instance of the following object is used to record information about +** the ORDER BY (or GROUP BY) clause of query is being coded. +** +** The aDefer[] array is used by the sorter-references optimization. For +** example, assuming there is no index that can be used for the ORDER BY, +** for the query: +** +** SELECT a, bigblob FROM t1 ORDER BY a LIMIT 10; +** +** it may be more efficient to add just the "a" values to the sorter, and +** retrieve the associated "bigblob" values directly from table t1 as the +** 10 smallest "a" values are extracted from the sorter. +** +** When the sorter-reference optimization is used, there is one entry in the +** aDefer[] array for each database table that may be read as values are +** extracted from the sorter. +*/ +typedef struct SortCtx SortCtx; +struct SortCtx { + ExprList *pOrderBy; /* The ORDER BY (or GROUP BY clause) */ + int nOBSat; /* Number of ORDER BY terms satisfied by indices */ + int iECursor; /* Cursor number for the sorter */ + int regReturn; /* Register holding block-output return address */ + int labelBkOut; /* Start label for the block-output subroutine */ + int addrSortIndex; /* Address of the OP_SorterOpen or OP_OpenEphemeral */ + int labelDone; /* Jump here when done, ex: LIMIT reached */ + int labelOBLopt; /* Jump here when sorter is full */ + u8 sortFlags; /* Zero or more SORTFLAG_* bits */ +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + u8 nDefer; /* Number of valid entries in aDefer[] */ + struct DeferredCsr { + Table *pTab; /* Table definition */ + int iCsr; /* Cursor number for table */ + int nKey; /* Number of PK columns for table pTab (>=1) */ + } aDefer[4]; +#endif + struct RowLoadInfo *pDeferredRowLoad; /* Deferred row loading info or NULL */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrPush; /* First instruction to push data into sorter */ + int addrPushEnd; /* Last instruction that pushes data into sorter */ +#endif +}; +#define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */ + +/* +** Delete all the content of a Select structure. Deallocate the structure +** itself depending on the value of bFree +** +** If bFree==1, call sqlite3DbFree() on the p object. +** If bFree==0, Leave the first Select object unfreed +*/ +static void clearSelect(sqlite3 *db, Select *p, int bFree){ + assert( db!=0 ); + while( p ){ + Select *pPrior = p->pPrior; + sqlite3ExprListDelete(db, p->pEList); + sqlite3SrcListDelete(db, p->pSrc); + sqlite3ExprDelete(db, p->pWhere); + sqlite3ExprListDelete(db, p->pGroupBy); + sqlite3ExprDelete(db, p->pHaving); + sqlite3ExprListDelete(db, p->pOrderBy); + sqlite3ExprDelete(db, p->pLimit); + if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( OK_IF_ALWAYS_TRUE(p->pWinDefn) ){ + sqlite3WindowListDelete(db, p->pWinDefn); + } + while( p->pWin ){ + assert( p->pWin->ppThis==&p->pWin ); + sqlite3WindowUnlinkFromSelect(p->pWin); + } +#endif + if( bFree ) sqlite3DbNNFreeNN(db, p); + p = pPrior; + bFree = 1; + } +} + +/* +** Initialize a SelectDest structure. +*/ +SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest *pDest, int eDest, int iParm){ + pDest->eDest = (u8)eDest; + pDest->iSDParm = iParm; + pDest->iSDParm2 = 0; + pDest->zAffSdst = 0; + pDest->iSdst = 0; + pDest->nSdst = 0; +} + + +/* +** Allocate a new Select structure and return a pointer to that +** structure. +*/ +SQLITE_PRIVATE Select *sqlite3SelectNew( + Parse *pParse, /* Parsing context */ + ExprList *pEList, /* which columns to include in the result */ + SrcList *pSrc, /* the FROM clause -- which tables to scan */ + Expr *pWhere, /* the WHERE clause */ + ExprList *pGroupBy, /* the GROUP BY clause */ + Expr *pHaving, /* the HAVING clause */ + ExprList *pOrderBy, /* the ORDER BY clause */ + u32 selFlags, /* Flag parameters, such as SF_Distinct */ + Expr *pLimit /* LIMIT value. NULL means not used */ +){ + Select *pNew, *pAllocated; + Select standin; + pAllocated = pNew = sqlite3DbMallocRawNN(pParse->db, sizeof(*pNew) ); + if( pNew==0 ){ + assert( pParse->db->mallocFailed ); + pNew = &standin; + } + if( pEList==0 ){ + pEList = sqlite3ExprListAppend(pParse, 0, + sqlite3Expr(pParse->db,TK_ASTERISK,0)); + } + pNew->pEList = pEList; + pNew->op = TK_SELECT; + pNew->selFlags = selFlags; + pNew->iLimit = 0; + pNew->iOffset = 0; + pNew->selId = ++pParse->nSelect; + pNew->addrOpenEphm[0] = -1; + pNew->addrOpenEphm[1] = -1; + pNew->nSelectRow = 0; + if( pSrc==0 ) pSrc = sqlite3DbMallocZero(pParse->db, sizeof(*pSrc)); + pNew->pSrc = pSrc; + pNew->pWhere = pWhere; + pNew->pGroupBy = pGroupBy; + pNew->pHaving = pHaving; + pNew->pOrderBy = pOrderBy; + pNew->pPrior = 0; + pNew->pNext = 0; + pNew->pLimit = pLimit; + pNew->pWith = 0; +#ifndef SQLITE_OMIT_WINDOWFUNC + pNew->pWin = 0; + pNew->pWinDefn = 0; +#endif + if( pParse->db->mallocFailed ) { + clearSelect(pParse->db, pNew, pNew!=&standin); + pAllocated = 0; + }else{ + assert( pNew->pSrc!=0 || pParse->nErr>0 ); + } + return pAllocated; +} + + +/* +** Delete the given Select structure and all of its substructures. +*/ +SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3 *db, Select *p){ + if( OK_IF_ALWAYS_TRUE(p) ) clearSelect(db, p, 1); +} + +/* +** Return a pointer to the right-most SELECT statement in a compound. +*/ +static Select *findRightmost(Select *p){ + while( p->pNext ) p = p->pNext; + return p; +} + +/* +** Given 1 to 3 identifiers preceding the JOIN keyword, determine the +** type of join. Return an integer constant that expresses that type +** in terms of the following bit values: +** +** JT_INNER +** JT_CROSS +** JT_OUTER +** JT_NATURAL +** JT_LEFT +** JT_RIGHT +** +** A full outer join is the combination of JT_LEFT and JT_RIGHT. +** +** If an illegal or unsupported join type is seen, then still return +** a join type, but put an error in the pParse structure. +** +** These are the valid join types: +** +** +** pA pB pC Return Value +** ------- ----- ----- ------------ +** CROSS - - JT_CROSS +** INNER - - JT_INNER +** LEFT - - JT_LEFT|JT_OUTER +** LEFT OUTER - JT_LEFT|JT_OUTER +** RIGHT - - JT_RIGHT|JT_OUTER +** RIGHT OUTER - JT_RIGHT|JT_OUTER +** FULL - - JT_LEFT|JT_RIGHT|JT_OUTER +** FULL OUTER - JT_LEFT|JT_RIGHT|JT_OUTER +** NATURAL INNER - JT_NATURAL|JT_INNER +** NATURAL LEFT - JT_NATURAL|JT_LEFT|JT_OUTER +** NATURAL LEFT OUTER JT_NATURAL|JT_LEFT|JT_OUTER +** NATURAL RIGHT - JT_NATURAL|JT_RIGHT|JT_OUTER +** NATURAL RIGHT OUTER JT_NATURAL|JT_RIGHT|JT_OUTER +** NATURAL FULL - JT_NATURAL|JT_LEFT|JT_RIGHT +** NATURAL FULL OUTER JT_NATRUAL|JT_LEFT|JT_RIGHT +** +** To preserve historical compatibly, SQLite also accepts a variety +** of other non-standard and in many cases nonsensical join types. +** This routine makes as much sense at it can from the nonsense join +** type and returns a result. Examples of accepted nonsense join types +** include but are not limited to: +** +** INNER CROSS JOIN -> same as JOIN +** NATURAL CROSS JOIN -> same as NATURAL JOIN +** OUTER LEFT JOIN -> same as LEFT JOIN +** LEFT NATURAL JOIN -> same as NATURAL LEFT JOIN +** LEFT RIGHT JOIN -> same as FULL JOIN +** RIGHT OUTER FULL JOIN -> same as FULL JOIN +** CROSS CROSS CROSS JOIN -> same as JOIN +** +** The only restrictions on the join type name are: +** +** * "INNER" cannot appear together with "OUTER", "LEFT", "RIGHT", +** or "FULL". +** +** * "CROSS" cannot appear together with "OUTER", "LEFT", "RIGHT, +** or "FULL". +** +** * If "OUTER" is present then there must also be one of +** "LEFT", "RIGHT", or "FULL" +*/ +SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){ + int jointype = 0; + Token *apAll[3]; + Token *p; + /* 0123456789 123456789 123456789 123 */ + static const char zKeyText[] = "naturaleftouterightfullinnercross"; + static const struct { + u8 i; /* Beginning of keyword text in zKeyText[] */ + u8 nChar; /* Length of the keyword in characters */ + u8 code; /* Join type mask */ + } aKeyword[] = { + /* (0) natural */ { 0, 7, JT_NATURAL }, + /* (1) left */ { 6, 4, JT_LEFT|JT_OUTER }, + /* (2) outer */ { 10, 5, JT_OUTER }, + /* (3) right */ { 14, 5, JT_RIGHT|JT_OUTER }, + /* (4) full */ { 19, 4, JT_LEFT|JT_RIGHT|JT_OUTER }, + /* (5) inner */ { 23, 5, JT_INNER }, + /* (6) cross */ { 28, 5, JT_INNER|JT_CROSS }, + }; + int i, j; + apAll[0] = pA; + apAll[1] = pB; + apAll[2] = pC; + for(i=0; i<3 && apAll[i]; i++){ + p = apAll[i]; + for(j=0; j<ArraySize(aKeyword); j++){ + if( p->n==aKeyword[j].nChar + && sqlite3StrNICmp((char*)p->z, &zKeyText[aKeyword[j].i], p->n)==0 ){ + jointype |= aKeyword[j].code; + break; + } + } + testcase( j==0 || j==1 || j==2 || j==3 || j==4 || j==5 || j==6 ); + if( j>=ArraySize(aKeyword) ){ + jointype |= JT_ERROR; + break; + } + } + if( + (jointype & (JT_INNER|JT_OUTER))==(JT_INNER|JT_OUTER) || + (jointype & JT_ERROR)!=0 || + (jointype & (JT_OUTER|JT_LEFT|JT_RIGHT))==JT_OUTER + ){ + const char *zSp1 = " "; + const char *zSp2 = " "; + if( pB==0 ){ zSp1++; } + if( pC==0 ){ zSp2++; } + sqlite3ErrorMsg(pParse, "unknown join type: " + "%T%s%T%s%T", pA, zSp1, pB, zSp2, pC); + jointype = JT_INNER; + } + return jointype; +} + +/* +** Return the index of a column in a table. Return -1 if the column +** is not contained in the table. +*/ +SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol){ + int i; + u8 h = sqlite3StrIHash(zCol); + Column *pCol; + for(pCol=pTab->aCol, i=0; i<pTab->nCol; pCol++, i++){ + if( pCol->hName==h && sqlite3StrICmp(pCol->zCnName, zCol)==0 ) return i; + } + return -1; +} + +/* +** Mark a subquery result column as having been used. +*/ +SQLITE_PRIVATE void sqlite3SrcItemColumnUsed(SrcItem *pItem, int iCol){ + assert( pItem!=0 ); + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); + if( pItem->fg.isNestedFrom ){ + ExprList *pResults; + assert( pItem->pSelect!=0 ); + pResults = pItem->pSelect->pEList; + assert( pResults!=0 ); + assert( iCol>=0 && iCol<pResults->nExpr ); + pResults->a[iCol].fg.bUsed = 1; + } +} + +/* +** Search the tables iStart..iEnd (inclusive) in pSrc, looking for a +** table that has a column named zCol. The search is left-to-right. +** The first match found is returned. +** +** When found, set *piTab and *piCol to the table index and column index +** of the matching column and return TRUE. +** +** If not found, return FALSE. +*/ +static int tableAndColumnIndex( + SrcList *pSrc, /* Array of tables to search */ + int iStart, /* First member of pSrc->a[] to check */ + int iEnd, /* Last member of pSrc->a[] to check */ + const char *zCol, /* Name of the column we are looking for */ + int *piTab, /* Write index of pSrc->a[] here */ + int *piCol, /* Write index of pSrc->a[*piTab].pTab->aCol[] here */ + int bIgnoreHidden /* Ignore hidden columns */ +){ + int i; /* For looping over tables in pSrc */ + int iCol; /* Index of column matching zCol */ + + assert( iEnd<pSrc->nSrc ); + assert( iStart>=0 ); + assert( (piTab==0)==(piCol==0) ); /* Both or neither are NULL */ + + for(i=iStart; i<=iEnd; i++){ + iCol = sqlite3ColumnIndex(pSrc->a[i].pTab, zCol); + if( iCol>=0 + && (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pTab->aCol[iCol])==0) + ){ + if( piTab ){ + sqlite3SrcItemColumnUsed(&pSrc->a[i], iCol); + *piTab = i; + *piCol = iCol; + } + return 1; + } + } + return 0; +} + +/* +** Set the EP_OuterON property on all terms of the given expression. +** And set the Expr.w.iJoin to iTable for every term in the +** expression. +** +** The EP_OuterON property is used on terms of an expression to tell +** the OUTER JOIN processing logic that this term is part of the +** join restriction specified in the ON or USING clause and not a part +** of the more general WHERE clause. These terms are moved over to the +** WHERE clause during join processing but we need to remember that they +** originated in the ON or USING clause. +** +** The Expr.w.iJoin tells the WHERE clause processing that the +** expression depends on table w.iJoin even if that table is not +** explicitly mentioned in the expression. That information is needed +** for cases like this: +** +** SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.b AND t1.x=5 +** +** The where clause needs to defer the handling of the t1.x=5 +** term until after the t2 loop of the join. In that way, a +** NULL t2 row will be inserted whenever t1.x!=5. If we do not +** defer the handling of t1.x=5, it will be processed immediately +** after the t1 loop and rows with t1.x!=5 will never appear in +** the output, which is incorrect. +*/ +SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr *p, int iTable, u32 joinFlag){ + assert( joinFlag==EP_OuterON || joinFlag==EP_InnerON ); + while( p ){ + ExprSetProperty(p, joinFlag); + assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); + ExprSetVVAProperty(p, EP_NoReduce); + p->w.iJoin = iTable; + if( p->op==TK_FUNCTION ){ + assert( ExprUseXList(p) ); + if( p->x.pList ){ + int i; + for(i=0; i<p->x.pList->nExpr; i++){ + sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable, joinFlag); + } + } + } + sqlite3SetJoinExpr(p->pLeft, iTable, joinFlag); + p = p->pRight; + } +} + +/* Undo the work of sqlite3SetJoinExpr(). This is used when a LEFT JOIN +** is simplified into an ordinary JOIN, and when an ON expression is +** "pushed down" into the WHERE clause of a subquery. +** +** Convert every term that is marked with EP_OuterON and w.iJoin==iTable into +** an ordinary term that omits the EP_OuterON mark. Or if iTable<0, then +** just clear every EP_OuterON and EP_InnerON mark from the expression tree. +** +** If nullable is true, that means that Expr p might evaluate to NULL even +** if it is a reference to a NOT NULL column. This can happen, for example, +** if the table that p references is on the left side of a RIGHT JOIN. +** If nullable is true, then take care to not remove the EP_CanBeNull bit. +** See forum thread https://sqlite.org/forum/forumpost/b40696f50145d21c +*/ +static void unsetJoinExpr(Expr *p, int iTable, int nullable){ + while( p ){ + if( iTable<0 || (ExprHasProperty(p, EP_OuterON) && p->w.iJoin==iTable) ){ + ExprClearProperty(p, EP_OuterON|EP_InnerON); + if( iTable>=0 ) ExprSetProperty(p, EP_InnerON); + } + if( p->op==TK_COLUMN && p->iTable==iTable && !nullable ){ + ExprClearProperty(p, EP_CanBeNull); + } + if( p->op==TK_FUNCTION ){ + assert( ExprUseXList(p) ); + if( p->x.pList ){ + int i; + for(i=0; i<p->x.pList->nExpr; i++){ + unsetJoinExpr(p->x.pList->a[i].pExpr, iTable, nullable); + } + } + } + unsetJoinExpr(p->pLeft, iTable, nullable); + p = p->pRight; + } +} + +/* +** This routine processes the join information for a SELECT statement. +** +** * A NATURAL join is converted into a USING join. After that, we +** do not need to be concerned with NATURAL joins and we only have +** think about USING joins. +** +** * ON and USING clauses result in extra terms being added to the +** WHERE clause to enforce the specified constraints. The extra +** WHERE clause terms will be tagged with EP_OuterON or +** EP_InnerON so that we know that they originated in ON/USING. +** +** The terms of a FROM clause are contained in the Select.pSrc structure. +** The left most table is the first entry in Select.pSrc. The right-most +** table is the last entry. The join operator is held in the entry to +** the right. Thus entry 1 contains the join operator for the join between +** entries 0 and 1. Any ON or USING clauses associated with the join are +** also attached to the right entry. +** +** This routine returns the number of errors encountered. +*/ +static int sqlite3ProcessJoin(Parse *pParse, Select *p){ + SrcList *pSrc; /* All tables in the FROM clause */ + int i, j; /* Loop counters */ + SrcItem *pLeft; /* Left table being joined */ + SrcItem *pRight; /* Right table being joined */ + + pSrc = p->pSrc; + pLeft = &pSrc->a[0]; + pRight = &pLeft[1]; + for(i=0; i<pSrc->nSrc-1; i++, pRight++, pLeft++){ + Table *pRightTab = pRight->pTab; + u32 joinType; + + if( NEVER(pLeft->pTab==0 || pRightTab==0) ) continue; + joinType = (pRight->fg.jointype & JT_OUTER)!=0 ? EP_OuterON : EP_InnerON; + + /* If this is a NATURAL join, synthesize an appropriate USING clause + ** to specify which columns should be joined. + */ + if( pRight->fg.jointype & JT_NATURAL ){ + IdList *pUsing = 0; + if( pRight->fg.isUsing || pRight->u3.pOn ){ + sqlite3ErrorMsg(pParse, "a NATURAL join may not have " + "an ON or USING clause", 0); + return 1; + } + for(j=0; j<pRightTab->nCol; j++){ + char *zName; /* Name of column in the right table */ + + if( IsHiddenColumn(&pRightTab->aCol[j]) ) continue; + zName = pRightTab->aCol[j].zCnName; + if( tableAndColumnIndex(pSrc, 0, i, zName, 0, 0, 1) ){ + pUsing = sqlite3IdListAppend(pParse, pUsing, 0); + if( pUsing ){ + assert( pUsing->nId>0 ); + assert( pUsing->a[pUsing->nId-1].zName==0 ); + pUsing->a[pUsing->nId-1].zName = sqlite3DbStrDup(pParse->db, zName); + } + } + } + if( pUsing ){ + pRight->fg.isUsing = 1; + pRight->fg.isSynthUsing = 1; + pRight->u3.pUsing = pUsing; + } + if( pParse->nErr ) return 1; + } + + /* Create extra terms on the WHERE clause for each column named + ** in the USING clause. Example: If the two tables to be joined are + ** A and B and the USING clause names X, Y, and Z, then add this + ** to the WHERE clause: A.X=B.X AND A.Y=B.Y AND A.Z=B.Z + ** Report an error if any column mentioned in the USING clause is + ** not contained in both tables to be joined. + */ + if( pRight->fg.isUsing ){ + IdList *pList = pRight->u3.pUsing; + sqlite3 *db = pParse->db; + assert( pList!=0 ); + for(j=0; j<pList->nId; j++){ + char *zName; /* Name of the term in the USING clause */ + int iLeft; /* Table on the left with matching column name */ + int iLeftCol; /* Column number of matching column on the left */ + int iRightCol; /* Column number of matching column on the right */ + Expr *pE1; /* Reference to the column on the LEFT of the join */ + Expr *pE2; /* Reference to the column on the RIGHT of the join */ + Expr *pEq; /* Equality constraint. pE1 == pE2 */ + + zName = pList->a[j].zName; + iRightCol = sqlite3ColumnIndex(pRightTab, zName); + if( iRightCol<0 + || tableAndColumnIndex(pSrc, 0, i, zName, &iLeft, &iLeftCol, + pRight->fg.isSynthUsing)==0 + ){ + sqlite3ErrorMsg(pParse, "cannot join using column %s - column " + "not present in both tables", zName); + return 1; + } + pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol); + sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol); + if( (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + /* This branch runs if the query contains one or more RIGHT or FULL + ** JOINs. If only a single table on the left side of this join + ** contains the zName column, then this branch is a no-op. + ** But if there are two or more tables on the left side + ** of the join, construct a coalesce() function that gathers all + ** such tables. Raise an error if more than one of those references + ** to zName is not also within a prior USING clause. + ** + ** We really ought to raise an error if there are two or more + ** non-USING references to zName on the left of an INNER or LEFT + ** JOIN. But older versions of SQLite do not do that, so we avoid + ** adding a new error so as to not break legacy applications. + */ + ExprList *pFuncArgs = 0; /* Arguments to the coalesce() */ + static const Token tkCoalesce = { "coalesce", 8 }; + while( tableAndColumnIndex(pSrc, iLeft+1, i, zName, &iLeft, &iLeftCol, + pRight->fg.isSynthUsing)!=0 ){ + if( pSrc->a[iLeft].fg.isUsing==0 + || sqlite3IdListIndex(pSrc->a[iLeft].u3.pUsing, zName)<0 + ){ + sqlite3ErrorMsg(pParse, "ambiguous reference to %s in USING()", + zName); + break; + } + pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1); + pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol); + sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol); + } + if( pFuncArgs ){ + pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1); + pE1 = sqlite3ExprFunction(pParse, pFuncArgs, &tkCoalesce, 0); + } + } + pE2 = sqlite3CreateColumnExpr(db, pSrc, i+1, iRightCol); + sqlite3SrcItemColumnUsed(pRight, iRightCol); + pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2); + assert( pE2!=0 || pEq==0 ); + if( pEq ){ + ExprSetProperty(pEq, joinType); + assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); + ExprSetVVAProperty(pEq, EP_NoReduce); + pEq->w.iJoin = pE2->iTable; + } + p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pEq); + } + } + + /* Add the ON clause to the end of the WHERE clause, connected by + ** an AND operator. + */ + else if( pRight->u3.pOn ){ + sqlite3SetJoinExpr(pRight->u3.pOn, pRight->iCursor, joinType); + p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn); + pRight->u3.pOn = 0; + pRight->fg.isOn = 1; + } + } + return 0; +} + +/* +** An instance of this object holds information (beyond pParse and pSelect) +** needed to load the next result row that is to be added to the sorter. +*/ +typedef struct RowLoadInfo RowLoadInfo; +struct RowLoadInfo { + int regResult; /* Store results in array of registers here */ + u8 ecelFlags; /* Flag argument to ExprCodeExprList() */ +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + ExprList *pExtra; /* Extra columns needed by sorter refs */ + int regExtraResult; /* Where to load the extra columns */ +#endif +}; + +/* +** This routine does the work of loading query data into an array of +** registers so that it can be added to the sorter. +*/ +static void innerLoopLoadRow( + Parse *pParse, /* Statement under construction */ + Select *pSelect, /* The query being coded */ + RowLoadInfo *pInfo /* Info needed to complete the row load */ +){ + sqlite3ExprCodeExprList(pParse, pSelect->pEList, pInfo->regResult, + 0, pInfo->ecelFlags); +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + if( pInfo->pExtra ){ + sqlite3ExprCodeExprList(pParse, pInfo->pExtra, pInfo->regExtraResult, 0, 0); + sqlite3ExprListDelete(pParse->db, pInfo->pExtra); + } +#endif +} + +/* +** Code the OP_MakeRecord instruction that generates the entry to be +** added into the sorter. +** +** Return the register in which the result is stored. +*/ +static int makeSorterRecord( + Parse *pParse, + SortCtx *pSort, + Select *pSelect, + int regBase, + int nBase +){ + int nOBSat = pSort->nOBSat; + Vdbe *v = pParse->pVdbe; + int regOut = ++pParse->nMem; + if( pSort->pDeferredRowLoad ){ + innerLoopLoadRow(pParse, pSelect, pSort->pDeferredRowLoad); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase+nOBSat, nBase-nOBSat, regOut); + return regOut; +} + +/* +** Generate code that will push the record in registers regData +** through regData+nData-1 onto the sorter. +*/ +static void pushOntoSorter( + Parse *pParse, /* Parser context */ + SortCtx *pSort, /* Information about the ORDER BY clause */ + Select *pSelect, /* The whole SELECT statement */ + int regData, /* First register holding data to be sorted */ + int regOrigData, /* First register holding data before packing */ + int nData, /* Number of elements in the regData data array */ + int nPrefixReg /* No. of reg prior to regData available for use */ +){ + Vdbe *v = pParse->pVdbe; /* Stmt under construction */ + int bSeq = ((pSort->sortFlags & SORTFLAG_UseSorter)==0); + int nExpr = pSort->pOrderBy->nExpr; /* No. of ORDER BY terms */ + int nBase = nExpr + bSeq + nData; /* Fields in sorter record */ + int regBase; /* Regs for sorter record */ + int regRecord = 0; /* Assembled sorter record */ + int nOBSat = pSort->nOBSat; /* ORDER BY terms to skip */ + int op; /* Opcode to add sorter record to sorter */ + int iLimit; /* LIMIT counter */ + int iSkip = 0; /* End of the sorter insert loop */ + + assert( bSeq==0 || bSeq==1 ); + + /* Three cases: + ** (1) The data to be sorted has already been packed into a Record + ** by a prior OP_MakeRecord. In this case nData==1 and regData + ** will be completely unrelated to regOrigData. + ** (2) All output columns are included in the sort record. In that + ** case regData==regOrigData. + ** (3) Some output columns are omitted from the sort record due to + ** the SQLITE_ENABLE_SORTER_REFERENCES optimization, or due to the + ** SQLITE_ECEL_OMITREF optimization, or due to the + ** SortCtx.pDeferredRowLoad optimization. In any of these cases + ** regOrigData is 0 to prevent this routine from trying to copy + ** values that might not yet exist. + */ + assert( nData==1 || regData==regOrigData || regOrigData==0 ); + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pSort->addrPush = sqlite3VdbeCurrentAddr(v); +#endif + + if( nPrefixReg ){ + assert( nPrefixReg==nExpr+bSeq ); + regBase = regData - nPrefixReg; + }else{ + regBase = pParse->nMem + 1; + pParse->nMem += nBase; + } + assert( pSelect->iOffset==0 || pSelect->iLimit!=0 ); + iLimit = pSelect->iOffset ? pSelect->iOffset+1 : pSelect->iLimit; + pSort->labelDone = sqlite3VdbeMakeLabel(pParse); + sqlite3ExprCodeExprList(pParse, pSort->pOrderBy, regBase, regOrigData, + SQLITE_ECEL_DUP | (regOrigData? SQLITE_ECEL_REF : 0)); + if( bSeq ){ + sqlite3VdbeAddOp2(v, OP_Sequence, pSort->iECursor, regBase+nExpr); + } + if( nPrefixReg==0 && nData>0 ){ + sqlite3ExprCodeMove(pParse, regData, regBase+nExpr+bSeq, nData); + } + if( nOBSat>0 ){ + int regPrevKey; /* The first nOBSat columns of the previous row */ + int addrFirst; /* Address of the OP_IfNot opcode */ + int addrJmp; /* Address of the OP_Jump opcode */ + VdbeOp *pOp; /* Opcode that opens the sorter */ + int nKey; /* Number of sorting key columns, including OP_Sequence */ + KeyInfo *pKI; /* Original KeyInfo on the sorter table */ + + regRecord = makeSorterRecord(pParse, pSort, pSelect, regBase, nBase); + regPrevKey = pParse->nMem+1; + pParse->nMem += pSort->nOBSat; + nKey = nExpr - pSort->nOBSat + bSeq; + if( bSeq ){ + addrFirst = sqlite3VdbeAddOp1(v, OP_IfNot, regBase+nExpr); + }else{ + addrFirst = sqlite3VdbeAddOp1(v, OP_SequenceTest, pSort->iECursor); + } + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_Compare, regPrevKey, regBase, pSort->nOBSat); + pOp = sqlite3VdbeGetOp(v, pSort->addrSortIndex); + if( pParse->db->mallocFailed ) return; + pOp->p2 = nKey + nData; + pKI = pOp->p4.pKeyInfo; + memset(pKI->aSortFlags, 0, pKI->nKeyField); /* Makes OP_Jump testable */ + sqlite3VdbeChangeP4(v, -1, (char*)pKI, P4_KEYINFO); + testcase( pKI->nAllField > pKI->nKeyField+2 ); + pOp->p4.pKeyInfo = sqlite3KeyInfoFromExprList(pParse,pSort->pOrderBy,nOBSat, + pKI->nAllField-pKI->nKeyField-1); + pOp = 0; /* Ensure pOp not used after sqlite3VdbeAddOp3() */ + addrJmp = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp3(v, OP_Jump, addrJmp+1, 0, addrJmp+1); VdbeCoverage(v); + pSort->labelBkOut = sqlite3VdbeMakeLabel(pParse); + pSort->regReturn = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut); + sqlite3VdbeAddOp1(v, OP_ResetSorter, pSort->iECursor); + if( iLimit ){ + sqlite3VdbeAddOp2(v, OP_IfNot, iLimit, pSort->labelDone); + VdbeCoverage(v); + } + sqlite3VdbeJumpHere(v, addrFirst); + sqlite3ExprCodeMove(pParse, regBase, regPrevKey, pSort->nOBSat); + sqlite3VdbeJumpHere(v, addrJmp); + } + if( iLimit ){ + /* At this point the values for the new sorter entry are stored + ** in an array of registers. They need to be composed into a record + ** and inserted into the sorter if either (a) there are currently + ** less than LIMIT+OFFSET items or (b) the new record is smaller than + ** the largest record currently in the sorter. If (b) is true and there + ** are already LIMIT+OFFSET items in the sorter, delete the largest + ** entry before inserting the new one. This way there are never more + ** than LIMIT+OFFSET items in the sorter. + ** + ** If the new record does not need to be inserted into the sorter, + ** jump to the next iteration of the loop. If the pSort->labelOBLopt + ** value is not zero, then it is a label of where to jump. Otherwise, + ** just bypass the row insert logic. See the header comment on the + ** sqlite3WhereOrderByLimitOptLabel() function for additional info. + */ + int iCsr = pSort->iECursor; + sqlite3VdbeAddOp2(v, OP_IfNotZero, iLimit, sqlite3VdbeCurrentAddr(v)+4); + VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Last, iCsr, 0); + iSkip = sqlite3VdbeAddOp4Int(v, OP_IdxLE, + iCsr, 0, regBase+nOBSat, nExpr-nOBSat); + VdbeCoverage(v); + sqlite3VdbeAddOp1(v, OP_Delete, iCsr); + } + if( regRecord==0 ){ + regRecord = makeSorterRecord(pParse, pSort, pSelect, regBase, nBase); + } + if( pSort->sortFlags & SORTFLAG_UseSorter ){ + op = OP_SorterInsert; + }else{ + op = OP_IdxInsert; + } + sqlite3VdbeAddOp4Int(v, op, pSort->iECursor, regRecord, + regBase+nOBSat, nBase-nOBSat); + if( iSkip ){ + sqlite3VdbeChangeP2(v, iSkip, + pSort->labelOBLopt ? pSort->labelOBLopt : sqlite3VdbeCurrentAddr(v)); + } +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pSort->addrPushEnd = sqlite3VdbeCurrentAddr(v)-1; +#endif +} + +/* +** Add code to implement the OFFSET +*/ +static void codeOffset( + Vdbe *v, /* Generate code into this VM */ + int iOffset, /* Register holding the offset counter */ + int iContinue /* Jump here to skip the current record */ +){ + if( iOffset>0 ){ + sqlite3VdbeAddOp3(v, OP_IfPos, iOffset, iContinue, 1); VdbeCoverage(v); + VdbeComment((v, "OFFSET")); + } +} + +/* +** Add code that will check to make sure the array of registers starting at +** iMem form a distinct entry. This is used by both "SELECT DISTINCT ..." and +** distinct aggregates ("SELECT count(DISTINCT <expr>) ..."). Three strategies +** are available. Which is used depends on the value of parameter eTnctType, +** as follows: +** +** WHERE_DISTINCT_UNORDERED/WHERE_DISTINCT_NOOP: +** Build an ephemeral table that contains all entries seen before and +** skip entries which have been seen before. +** +** Parameter iTab is the cursor number of an ephemeral table that must +** be opened before the VM code generated by this routine is executed. +** The ephemeral cursor table is queried for a record identical to the +** record formed by the current array of registers. If one is found, +** jump to VM address addrRepeat. Otherwise, insert a new record into +** the ephemeral cursor and proceed. +** +** The returned value in this case is a copy of parameter iTab. +** +** WHERE_DISTINCT_ORDERED: +** In this case rows are being delivered sorted order. The ephemeral +** table is not required. Instead, the current set of values +** is compared against previous row. If they match, the new row +** is not distinct and control jumps to VM address addrRepeat. Otherwise, +** the VM program proceeds with processing the new row. +** +** The returned value in this case is the register number of the first +** in an array of registers used to store the previous result row so that +** it can be compared to the next. The caller must ensure that this +** register is initialized to NULL. (The fixDistinctOpenEph() routine +** will take care of this initialization.) +** +** WHERE_DISTINCT_UNIQUE: +** In this case it has already been determined that the rows are distinct. +** No special action is required. The return value is zero. +** +** Parameter pEList is the list of expressions used to generated the +** contents of each row. It is used by this routine to determine (a) +** how many elements there are in the array of registers and (b) the +** collation sequences that should be used for the comparisons if +** eTnctType is WHERE_DISTINCT_ORDERED. +*/ +static int codeDistinct( + Parse *pParse, /* Parsing and code generating context */ + int eTnctType, /* WHERE_DISTINCT_* value */ + int iTab, /* A sorting index used to test for distinctness */ + int addrRepeat, /* Jump to here if not distinct */ + ExprList *pEList, /* Expression for each element */ + int regElem /* First element */ +){ + int iRet = 0; + int nResultCol = pEList->nExpr; + Vdbe *v = pParse->pVdbe; + + switch( eTnctType ){ + case WHERE_DISTINCT_ORDERED: { + int i; + int iJump; /* Jump destination */ + int regPrev; /* Previous row content */ + + /* Allocate space for the previous row */ + iRet = regPrev = pParse->nMem+1; + pParse->nMem += nResultCol; + + iJump = sqlite3VdbeCurrentAddr(v) + nResultCol; + for(i=0; i<nResultCol; i++){ + CollSeq *pColl = sqlite3ExprCollSeq(pParse, pEList->a[i].pExpr); + if( i<nResultCol-1 ){ + sqlite3VdbeAddOp3(v, OP_Ne, regElem+i, iJump, regPrev+i); + VdbeCoverage(v); + }else{ + sqlite3VdbeAddOp3(v, OP_Eq, regElem+i, addrRepeat, regPrev+i); + VdbeCoverage(v); + } + sqlite3VdbeChangeP4(v, -1, (const char *)pColl, P4_COLLSEQ); + sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); + } + assert( sqlite3VdbeCurrentAddr(v)==iJump || pParse->db->mallocFailed ); + sqlite3VdbeAddOp3(v, OP_Copy, regElem, regPrev, nResultCol-1); + break; + } + + case WHERE_DISTINCT_UNIQUE: { + /* nothing to do */ + break; + } + + default: { + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, regElem, nResultCol); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regElem, nResultCol, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, regElem, nResultCol); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3ReleaseTempReg(pParse, r1); + iRet = iTab; + break; + } + } + + return iRet; +} + +/* +** This routine runs after codeDistinct(). It makes necessary +** adjustments to the OP_OpenEphemeral opcode that the codeDistinct() +** routine made use of. This processing must be done separately since +** sometimes codeDistinct is called before the OP_OpenEphemeral is actually +** laid down. +** +** WHERE_DISTINCT_NOOP: +** WHERE_DISTINCT_UNORDERED: +** +** No adjustments necessary. This function is a no-op. +** +** WHERE_DISTINCT_UNIQUE: +** +** The ephemeral table is not needed. So change the +** OP_OpenEphemeral opcode into an OP_Noop. +** +** WHERE_DISTINCT_ORDERED: +** +** The ephemeral table is not needed. But we do need register +** iVal to be initialized to NULL. So change the OP_OpenEphemeral +** into an OP_Null on the iVal register. +*/ +static void fixDistinctOpenEph( + Parse *pParse, /* Parsing and code generating context */ + int eTnctType, /* WHERE_DISTINCT_* value */ + int iVal, /* Value returned by codeDistinct() */ + int iOpenEphAddr /* Address of OP_OpenEphemeral instruction for iTab */ +){ + if( pParse->nErr==0 + && (eTnctType==WHERE_DISTINCT_UNIQUE || eTnctType==WHERE_DISTINCT_ORDERED) + ){ + Vdbe *v = pParse->pVdbe; + sqlite3VdbeChangeToNoop(v, iOpenEphAddr); + if( sqlite3VdbeGetOp(v, iOpenEphAddr+1)->opcode==OP_Explain ){ + sqlite3VdbeChangeToNoop(v, iOpenEphAddr+1); + } + if( eTnctType==WHERE_DISTINCT_ORDERED ){ + /* Change the OP_OpenEphemeral to an OP_Null that sets the MEM_Cleared + ** bit on the first register of the previous value. This will cause the + ** OP_Ne added in codeDistinct() to always fail on the first iteration of + ** the loop even if the first row is all NULLs. */ + VdbeOp *pOp = sqlite3VdbeGetOp(v, iOpenEphAddr); + pOp->opcode = OP_Null; + pOp->p1 = 1; + pOp->p2 = iVal; + } + } +} + +#ifdef SQLITE_ENABLE_SORTER_REFERENCES +/* +** This function is called as part of inner-loop generation for a SELECT +** statement with an ORDER BY that is not optimized by an index. It +** determines the expressions, if any, that the sorter-reference +** optimization should be used for. The sorter-reference optimization +** is used for SELECT queries like: +** +** SELECT a, bigblob FROM t1 ORDER BY a LIMIT 10 +** +** If the optimization is used for expression "bigblob", then instead of +** storing values read from that column in the sorter records, the PK of +** the row from table t1 is stored instead. Then, as records are extracted from +** the sorter to return to the user, the required value of bigblob is +** retrieved directly from table t1. If the values are very large, this +** can be more efficient than storing them directly in the sorter records. +** +** The ExprList_item.fg.bSorterRef flag is set for each expression in pEList +** for which the sorter-reference optimization should be enabled. +** Additionally, the pSort->aDefer[] array is populated with entries +** for all cursors required to evaluate all selected expressions. Finally. +** output variable (*ppExtra) is set to an expression list containing +** expressions for all extra PK values that should be stored in the +** sorter records. +*/ +static void selectExprDefer( + Parse *pParse, /* Leave any error here */ + SortCtx *pSort, /* Sorter context */ + ExprList *pEList, /* Expressions destined for sorter */ + ExprList **ppExtra /* Expressions to append to sorter record */ +){ + int i; + int nDefer = 0; + ExprList *pExtra = 0; + for(i=0; i<pEList->nExpr; i++){ + struct ExprList_item *pItem = &pEList->a[i]; + if( pItem->u.x.iOrderByCol==0 ){ + Expr *pExpr = pItem->pExpr; + Table *pTab; + if( pExpr->op==TK_COLUMN + && pExpr->iColumn>=0 + && ALWAYS( ExprUseYTab(pExpr) ) + && (pTab = pExpr->y.pTab)!=0 + && IsOrdinaryTable(pTab) + && (pTab->aCol[pExpr->iColumn].colFlags & COLFLAG_SORTERREF)!=0 + ){ + int j; + for(j=0; j<nDefer; j++){ + if( pSort->aDefer[j].iCsr==pExpr->iTable ) break; + } + if( j==nDefer ){ + if( nDefer==ArraySize(pSort->aDefer) ){ + continue; + }else{ + int nKey = 1; + int k; + Index *pPk = 0; + if( !HasRowid(pTab) ){ + pPk = sqlite3PrimaryKeyIndex(pTab); + nKey = pPk->nKeyCol; + } + for(k=0; k<nKey; k++){ + Expr *pNew = sqlite3PExpr(pParse, TK_COLUMN, 0, 0); + if( pNew ){ + pNew->iTable = pExpr->iTable; + assert( ExprUseYTab(pNew) ); + pNew->y.pTab = pExpr->y.pTab; + pNew->iColumn = pPk ? pPk->aiColumn[k] : -1; + pExtra = sqlite3ExprListAppend(pParse, pExtra, pNew); + } + } + pSort->aDefer[nDefer].pTab = pExpr->y.pTab; + pSort->aDefer[nDefer].iCsr = pExpr->iTable; + pSort->aDefer[nDefer].nKey = nKey; + nDefer++; + } + } + pItem->fg.bSorterRef = 1; + } + } + } + pSort->nDefer = (u8)nDefer; + *ppExtra = pExtra; +} +#endif + +/* +** This routine generates the code for the inside of the inner loop +** of a SELECT. +** +** If srcTab is negative, then the p->pEList expressions +** are evaluated in order to get the data for this row. If srcTab is +** zero or more, then data is pulled from srcTab and p->pEList is used only +** to get the number of columns and the collation sequence for each column. +*/ +static void selectInnerLoop( + Parse *pParse, /* The parser context */ + Select *p, /* The complete select statement being coded */ + int srcTab, /* Pull data from this table if non-negative */ + SortCtx *pSort, /* If not NULL, info on how to process ORDER BY */ + DistinctCtx *pDistinct, /* If not NULL, info on how to process DISTINCT */ + SelectDest *pDest, /* How to dispose of the results */ + int iContinue, /* Jump here to continue with next row */ + int iBreak /* Jump here to break out of the inner loop */ +){ + Vdbe *v = pParse->pVdbe; + int i; + int hasDistinct; /* True if the DISTINCT keyword is present */ + int eDest = pDest->eDest; /* How to dispose of results */ + int iParm = pDest->iSDParm; /* First argument to disposal method */ + int nResultCol; /* Number of result columns */ + int nPrefixReg = 0; /* Number of extra registers before regResult */ + RowLoadInfo sRowLoadInfo; /* Info for deferred row loading */ + + /* Usually, regResult is the first cell in an array of memory cells + ** containing the current result row. In this case regOrig is set to the + ** same value. However, if the results are being sent to the sorter, the + ** values for any expressions that are also part of the sort-key are omitted + ** from this array. In this case regOrig is set to zero. */ + int regResult; /* Start of memory holding current results */ + int regOrig; /* Start of memory holding full result (or 0) */ + + assert( v ); + assert( p->pEList!=0 ); + hasDistinct = pDistinct ? pDistinct->eTnctType : WHERE_DISTINCT_NOOP; + if( pSort && pSort->pOrderBy==0 ) pSort = 0; + if( pSort==0 && !hasDistinct ){ + assert( iContinue!=0 ); + codeOffset(v, p->iOffset, iContinue); + } + + /* Pull the requested columns. + */ + nResultCol = p->pEList->nExpr; + + if( pDest->iSdst==0 ){ + if( pSort ){ + nPrefixReg = pSort->pOrderBy->nExpr; + if( !(pSort->sortFlags & SORTFLAG_UseSorter) ) nPrefixReg++; + pParse->nMem += nPrefixReg; + } + pDest->iSdst = pParse->nMem+1; + pParse->nMem += nResultCol; + }else if( pDest->iSdst+nResultCol > pParse->nMem ){ + /* This is an error condition that can result, for example, when a SELECT + ** on the right-hand side of an INSERT contains more result columns than + ** there are columns in the table on the left. The error will be caught + ** and reported later. But we need to make sure enough memory is allocated + ** to avoid other spurious errors in the meantime. */ + pParse->nMem += nResultCol; + } + pDest->nSdst = nResultCol; + regOrig = regResult = pDest->iSdst; + if( srcTab>=0 ){ + for(i=0; i<nResultCol; i++){ + sqlite3VdbeAddOp3(v, OP_Column, srcTab, i, regResult+i); + VdbeComment((v, "%s", p->pEList->a[i].zEName)); + } + }else if( eDest!=SRT_Exists ){ +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + ExprList *pExtra = 0; +#endif + /* If the destination is an EXISTS(...) expression, the actual + ** values returned by the SELECT are not required. + */ + u8 ecelFlags; /* "ecel" is an abbreviation of "ExprCodeExprList" */ + ExprList *pEList; + if( eDest==SRT_Mem || eDest==SRT_Output || eDest==SRT_Coroutine ){ + ecelFlags = SQLITE_ECEL_DUP; + }else{ + ecelFlags = 0; + } + if( pSort && hasDistinct==0 && eDest!=SRT_EphemTab && eDest!=SRT_Table ){ + /* For each expression in p->pEList that is a copy of an expression in + ** the ORDER BY clause (pSort->pOrderBy), set the associated + ** iOrderByCol value to one more than the index of the ORDER BY + ** expression within the sort-key that pushOntoSorter() will generate. + ** This allows the p->pEList field to be omitted from the sorted record, + ** saving space and CPU cycles. */ + ecelFlags |= (SQLITE_ECEL_OMITREF|SQLITE_ECEL_REF); + + for(i=pSort->nOBSat; i<pSort->pOrderBy->nExpr; i++){ + int j; + if( (j = pSort->pOrderBy->a[i].u.x.iOrderByCol)>0 ){ + p->pEList->a[j-1].u.x.iOrderByCol = i+1-pSort->nOBSat; + } + } +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + selectExprDefer(pParse, pSort, p->pEList, &pExtra); + if( pExtra && pParse->db->mallocFailed==0 ){ + /* If there are any extra PK columns to add to the sorter records, + ** allocate extra memory cells and adjust the OpenEphemeral + ** instruction to account for the larger records. This is only + ** required if there are one or more WITHOUT ROWID tables with + ** composite primary keys in the SortCtx.aDefer[] array. */ + VdbeOp *pOp = sqlite3VdbeGetOp(v, pSort->addrSortIndex); + pOp->p2 += (pExtra->nExpr - pSort->nDefer); + pOp->p4.pKeyInfo->nAllField += (pExtra->nExpr - pSort->nDefer); + pParse->nMem += pExtra->nExpr; + } +#endif + + /* Adjust nResultCol to account for columns that are omitted + ** from the sorter by the optimizations in this branch */ + pEList = p->pEList; + for(i=0; i<pEList->nExpr; i++){ + if( pEList->a[i].u.x.iOrderByCol>0 +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + || pEList->a[i].fg.bSorterRef +#endif + ){ + nResultCol--; + regOrig = 0; + } + } + + testcase( regOrig ); + testcase( eDest==SRT_Set ); + testcase( eDest==SRT_Mem ); + testcase( eDest==SRT_Coroutine ); + testcase( eDest==SRT_Output ); + assert( eDest==SRT_Set || eDest==SRT_Mem + || eDest==SRT_Coroutine || eDest==SRT_Output + || eDest==SRT_Upfrom ); + } + sRowLoadInfo.regResult = regResult; + sRowLoadInfo.ecelFlags = ecelFlags; +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + sRowLoadInfo.pExtra = pExtra; + sRowLoadInfo.regExtraResult = regResult + nResultCol; + if( pExtra ) nResultCol += pExtra->nExpr; +#endif + if( p->iLimit + && (ecelFlags & SQLITE_ECEL_OMITREF)!=0 + && nPrefixReg>0 + ){ + assert( pSort!=0 ); + assert( hasDistinct==0 ); + pSort->pDeferredRowLoad = &sRowLoadInfo; + regOrig = 0; + }else{ + innerLoopLoadRow(pParse, p, &sRowLoadInfo); + } + } + + /* If the DISTINCT keyword was present on the SELECT statement + ** and this row has been seen before, then do not make this row + ** part of the result. + */ + if( hasDistinct ){ + int eType = pDistinct->eTnctType; + int iTab = pDistinct->tabTnct; + assert( nResultCol==p->pEList->nExpr ); + iTab = codeDistinct(pParse, eType, iTab, iContinue, p->pEList, regResult); + fixDistinctOpenEph(pParse, eType, iTab, pDistinct->addrTnct); + if( pSort==0 ){ + codeOffset(v, p->iOffset, iContinue); + } + } + + switch( eDest ){ + /* In this mode, write each query result to the key of the temporary + ** table iParm. + */ +#ifndef SQLITE_OMIT_COMPOUND_SELECT + case SRT_Union: { + int r1; + r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); + sqlite3ReleaseTempReg(pParse, r1); + break; + } + + /* Construct a record from the query result, but instead of + ** saving that record, use it as a key to delete elements from + ** the temporary table iParm. + */ + case SRT_Except: { + sqlite3VdbeAddOp3(v, OP_IdxDelete, iParm, regResult, nResultCol); + break; + } +#endif /* SQLITE_OMIT_COMPOUND_SELECT */ + + /* Store the result as data using a unique key. + */ + case SRT_Fifo: + case SRT_DistFifo: + case SRT_Table: + case SRT_EphemTab: { + int r1 = sqlite3GetTempRange(pParse, nPrefixReg+1); + testcase( eDest==SRT_Table ); + testcase( eDest==SRT_EphemTab ); + testcase( eDest==SRT_Fifo ); + testcase( eDest==SRT_DistFifo ); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg); +#if !defined(SQLITE_ENABLE_NULL_TRIM) && defined(SQLITE_DEBUG) + /* A destination of SRT_Table and a non-zero iSDParm2 parameter means + ** that this is an "UPDATE ... FROM" on a virtual table or view. In this + ** case set the p5 parameter of the OP_MakeRecord to OPFLAG_NOCHNG_MAGIC. + ** This does not affect operation in any way - it just allows MakeRecord + ** to process OPFLAG_NOCHANGE values without an assert() failing. */ + if( eDest==SRT_Table && pDest->iSDParm2 ){ + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); + } +#endif +#ifndef SQLITE_OMIT_CTE + if( eDest==SRT_DistFifo ){ + /* If the destination is DistFifo, then cursor (iParm+1) is open + ** on an ephemeral index. If the current row is already present + ** in the index, do not write it to the output. If not, add the + ** current row to the index and proceed with writing it to the + ** output table as well. */ + int addr = sqlite3VdbeCurrentAddr(v) + 4; + sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, addr, r1, 0); + VdbeCoverage(v); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm+1, r1,regResult,nResultCol); + assert( pSort==0 ); + } +#endif + if( pSort ){ + assert( regResult==regOrig ); + pushOntoSorter(pParse, pSort, p, r1+nPrefixReg, regOrig, 1, nPrefixReg); + }else{ + int r2 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, r2); + sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, r2); + sqlite3VdbeChangeP5(v, OPFLAG_APPEND); + sqlite3ReleaseTempReg(pParse, r2); + } + sqlite3ReleaseTempRange(pParse, r1, nPrefixReg+1); + break; + } + + case SRT_Upfrom: { + if( pSort ){ + pushOntoSorter( + pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); + }else{ + int i2 = pDest->iSDParm2; + int r1 = sqlite3GetTempReg(pParse); + + /* If the UPDATE FROM join is an aggregate that matches no rows, it + ** might still be trying to return one row, because that is what + ** aggregates do. Don't record that empty row in the output table. */ + sqlite3VdbeAddOp2(v, OP_IsNull, regResult, iBreak); VdbeCoverage(v); + + sqlite3VdbeAddOp3(v, OP_MakeRecord, + regResult+(i2<0), nResultCol-(i2<0), r1); + if( i2<0 ){ + sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, regResult); + }else{ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, i2); + } + } + break; + } + +#ifndef SQLITE_OMIT_SUBQUERY + /* If we are creating a set for an "expr IN (SELECT ...)" construct, + ** then there should be a single item on the stack. Write this + ** item into the set table with bogus data. + */ + case SRT_Set: { + if( pSort ){ + /* At first glance you would think we could optimize out the + ** ORDER BY in this case since the order of entries in the set + ** does not matter. But there might be a LIMIT clause, in which + ** case the order does matter */ + pushOntoSorter( + pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); + }else{ + int r1 = sqlite3GetTempReg(pParse); + assert( sqlite3Strlen30(pDest->zAffSdst)==nResultCol ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol, + r1, pDest->zAffSdst, nResultCol); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); + sqlite3ReleaseTempReg(pParse, r1); + } + break; + } + + + /* If any row exist in the result set, record that fact and abort. + */ + case SRT_Exists: { + sqlite3VdbeAddOp2(v, OP_Integer, 1, iParm); + /* The LIMIT clause will terminate the loop for us */ + break; + } + + /* If this is a scalar select that is part of an expression, then + ** store the results in the appropriate memory cell or array of + ** memory cells and break out of the scan loop. + */ + case SRT_Mem: { + if( pSort ){ + assert( nResultCol<=pDest->nSdst ); + pushOntoSorter( + pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); + }else{ + assert( nResultCol==pDest->nSdst ); + assert( regResult==iParm ); + /* The LIMIT clause will jump out of the loop for us */ + } + break; + } +#endif /* #ifndef SQLITE_OMIT_SUBQUERY */ + + case SRT_Coroutine: /* Send data to a co-routine */ + case SRT_Output: { /* Return the results */ + testcase( eDest==SRT_Coroutine ); + testcase( eDest==SRT_Output ); + if( pSort ){ + pushOntoSorter(pParse, pSort, p, regResult, regOrig, nResultCol, + nPrefixReg); + }else if( eDest==SRT_Coroutine ){ + sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm); + }else{ + sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, nResultCol); + } + break; + } + +#ifndef SQLITE_OMIT_CTE + /* Write the results into a priority queue that is order according to + ** pDest->pOrderBy (in pSO). pDest->iSDParm (in iParm) is the cursor for an + ** index with pSO->nExpr+2 columns. Build a key using pSO for the first + ** pSO->nExpr columns, then make sure all keys are unique by adding a + ** final OP_Sequence column. The last column is the record as a blob. + */ + case SRT_DistQueue: + case SRT_Queue: { + int nKey; + int r1, r2, r3; + int addrTest = 0; + ExprList *pSO; + pSO = pDest->pOrderBy; + assert( pSO ); + nKey = pSO->nExpr; + r1 = sqlite3GetTempReg(pParse); + r2 = sqlite3GetTempRange(pParse, nKey+2); + r3 = r2+nKey+1; + if( eDest==SRT_DistQueue ){ + /* If the destination is DistQueue, then cursor (iParm+1) is open + ** on a second ephemeral index that holds all values every previously + ** added to the queue. */ + addrTest = sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, 0, + regResult, nResultCol); + VdbeCoverage(v); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r3); + if( eDest==SRT_DistQueue ){ + sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r3); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + } + for(i=0; i<nKey; i++){ + sqlite3VdbeAddOp2(v, OP_SCopy, + regResult + pSO->a[i].u.x.iOrderByCol - 1, + r2+i); + } + sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey); + sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, r2, nKey+2); + if( addrTest ) sqlite3VdbeJumpHere(v, addrTest); + sqlite3ReleaseTempReg(pParse, r1); + sqlite3ReleaseTempRange(pParse, r2, nKey+2); + break; + } +#endif /* SQLITE_OMIT_CTE */ + + + +#if !defined(SQLITE_OMIT_TRIGGER) + /* Discard the results. This is used for SELECT statements inside + ** the body of a TRIGGER. The purpose of such selects is to call + ** user-defined functions that have side effects. We do not care + ** about the actual results of the select. + */ + default: { + assert( eDest==SRT_Discard ); + break; + } +#endif + } + + /* Jump to the end of the loop if the LIMIT is reached. Except, if + ** there is a sorter, in which case the sorter has already limited + ** the output for us. + */ + if( pSort==0 && p->iLimit ){ + sqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, iBreak); VdbeCoverage(v); + } +} + +/* +** Allocate a KeyInfo object sufficient for an index of N key columns and +** X extra columns. +*/ +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ + int nExtra = (N+X)*(sizeof(CollSeq*)+1) - sizeof(CollSeq*); + KeyInfo *p = sqlite3DbMallocRawNN(db, sizeof(KeyInfo) + nExtra); + if( p ){ + p->aSortFlags = (u8*)&p->aColl[N+X]; + p->nKeyField = (u16)N; + p->nAllField = (u16)(N+X); + p->enc = ENC(db); + p->db = db; + p->nRef = 1; + memset(&p[1], 0, nExtra); + }else{ + return (KeyInfo*)sqlite3OomFault(db); + } + return p; +} + +/* +** Deallocate a KeyInfo object +*/ +SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo *p){ + if( p ){ + assert( p->db!=0 ); + assert( p->nRef>0 ); + p->nRef--; + if( p->nRef==0 ) sqlite3DbNNFreeNN(p->db, p); + } +} + +/* +** Make a new pointer to a KeyInfo object +*/ +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoRef(KeyInfo *p){ + if( p ){ + assert( p->nRef>0 ); + p->nRef++; + } + return p; +} + +#ifdef SQLITE_DEBUG +/* +** Return TRUE if a KeyInfo object can be change. The KeyInfo object +** can only be changed if this is just a single reference to the object. +** +** This routine is used only inside of assert() statements. +*/ +SQLITE_PRIVATE int sqlite3KeyInfoIsWriteable(KeyInfo *p){ return p->nRef==1; } +#endif /* SQLITE_DEBUG */ + +/* +** Given an expression list, generate a KeyInfo structure that records +** the collating sequence for each expression in that expression list. +** +** If the ExprList is an ORDER BY or GROUP BY clause then the resulting +** KeyInfo structure is appropriate for initializing a virtual index to +** implement that clause. If the ExprList is the result set of a SELECT +** then the KeyInfo structure is appropriate for initializing a virtual +** index to implement a DISTINCT test. +** +** Space to hold the KeyInfo structure is obtained from malloc. The calling +** function is responsible for seeing that this structure is eventually +** freed. +*/ +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoFromExprList( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* Form the KeyInfo object from this ExprList */ + int iStart, /* Begin with this column of pList */ + int nExtra /* Add this many extra columns to the end */ +){ + int nExpr; + KeyInfo *pInfo; + struct ExprList_item *pItem; + sqlite3 *db = pParse->db; + int i; + + nExpr = pList->nExpr; + pInfo = sqlite3KeyInfoAlloc(db, nExpr-iStart, nExtra+1); + if( pInfo ){ + assert( sqlite3KeyInfoIsWriteable(pInfo) ); + for(i=iStart, pItem=pList->a+iStart; i<nExpr; i++, pItem++){ + pInfo->aColl[i-iStart] = sqlite3ExprNNCollSeq(pParse, pItem->pExpr); + pInfo->aSortFlags[i-iStart] = pItem->fg.sortFlags; + } + } + return pInfo; +} + +/* +** Name of the connection operator, used for error messages. +*/ +SQLITE_PRIVATE const char *sqlite3SelectOpName(int id){ + char *z; + switch( id ){ + case TK_ALL: z = "UNION ALL"; break; + case TK_INTERSECT: z = "INTERSECT"; break; + case TK_EXCEPT: z = "EXCEPT"; break; + default: z = "UNION"; break; + } + return z; +} + +#ifndef SQLITE_OMIT_EXPLAIN +/* +** Unless an "EXPLAIN QUERY PLAN" command is being processed, this function +** is a no-op. Otherwise, it adds a single row of output to the EQP result, +** where the caption is of the form: +** +** "USE TEMP B-TREE FOR xxx" +** +** where xxx is one of "DISTINCT", "ORDER BY" or "GROUP BY". Exactly which +** is determined by the zUsage argument. +*/ +static void explainTempTable(Parse *pParse, const char *zUsage){ + ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s", zUsage)); +} + +/* +** Assign expression b to lvalue a. A second, no-op, version of this macro +** is provided when SQLITE_OMIT_EXPLAIN is defined. This allows the code +** in sqlite3Select() to assign values to structure member variables that +** only exist if SQLITE_OMIT_EXPLAIN is not defined without polluting the +** code with #ifndef directives. +*/ +# define explainSetInteger(a, b) a = b + +#else +/* No-op versions of the explainXXX() functions and macros. */ +# define explainTempTable(y,z) +# define explainSetInteger(y,z) +#endif + + +/* +** If the inner loop was generated using a non-null pOrderBy argument, +** then the results were placed in a sorter. After the loop is terminated +** we need to run the sorter and output the results. The following +** routine generates the code needed to do that. +*/ +static void generateSortTail( + Parse *pParse, /* Parsing context */ + Select *p, /* The SELECT statement */ + SortCtx *pSort, /* Information on the ORDER BY clause */ + int nColumn, /* Number of columns of data */ + SelectDest *pDest /* Write the sorted results here */ +){ + Vdbe *v = pParse->pVdbe; /* The prepared statement */ + int addrBreak = pSort->labelDone; /* Jump here to exit loop */ + int addrContinue = sqlite3VdbeMakeLabel(pParse);/* Jump here for next cycle */ + int addr; /* Top of output loop. Jump for Next. */ + int addrOnce = 0; + int iTab; + ExprList *pOrderBy = pSort->pOrderBy; + int eDest = pDest->eDest; + int iParm = pDest->iSDParm; + int regRow; + int regRowid; + int iCol; + int nKey; /* Number of key columns in sorter record */ + int iSortTab; /* Sorter cursor to read from */ + int i; + int bSeq; /* True if sorter record includes seq. no. */ + int nRefKey = 0; + struct ExprList_item *aOutEx = p->pEList->a; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExplain; /* Address of OP_Explain instruction */ +#endif + + ExplainQueryPlan2(addrExplain, (pParse, 0, + "USE TEMP B-TREE FOR %sORDER BY", pSort->nOBSat>0?"RIGHT PART OF ":"") + ); + sqlite3VdbeScanStatusRange(v, addrExplain,pSort->addrPush,pSort->addrPushEnd); + sqlite3VdbeScanStatusCounters(v, addrExplain, addrExplain, pSort->addrPush); + + + assert( addrBreak<0 ); + if( pSort->labelBkOut ){ + sqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut); + sqlite3VdbeGoto(v, addrBreak); + sqlite3VdbeResolveLabel(v, pSort->labelBkOut); + } + +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + /* Open any cursors needed for sorter-reference expressions */ + for(i=0; i<pSort->nDefer; i++){ + Table *pTab = pSort->aDefer[i].pTab; + int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + sqlite3OpenTable(pParse, pSort->aDefer[i].iCsr, iDb, pTab, OP_OpenRead); + nRefKey = MAX(nRefKey, pSort->aDefer[i].nKey); + } +#endif + + iTab = pSort->iECursor; + if( eDest==SRT_Output || eDest==SRT_Coroutine || eDest==SRT_Mem ){ + if( eDest==SRT_Mem && p->iOffset ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, pDest->iSdst); + } + regRowid = 0; + regRow = pDest->iSdst; + }else{ + regRowid = sqlite3GetTempReg(pParse); + if( eDest==SRT_EphemTab || eDest==SRT_Table ){ + regRow = sqlite3GetTempReg(pParse); + nColumn = 0; + }else{ + regRow = sqlite3GetTempRange(pParse, nColumn); + } + } + nKey = pOrderBy->nExpr - pSort->nOBSat; + if( pSort->sortFlags & SORTFLAG_UseSorter ){ + int regSortOut = ++pParse->nMem; + iSortTab = pParse->nTab++; + if( pSort->labelBkOut ){ + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + } + sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut, + nKey+1+nColumn+nRefKey); + if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); + addr = 1 + sqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak); + VdbeCoverage(v); + assert( p->iLimit==0 && p->iOffset==0 ); + sqlite3VdbeAddOp3(v, OP_SorterData, iTab, regSortOut, iSortTab); + bSeq = 0; + }else{ + addr = 1 + sqlite3VdbeAddOp2(v, OP_Sort, iTab, addrBreak); VdbeCoverage(v); + codeOffset(v, p->iOffset, addrContinue); + iSortTab = iTab; + bSeq = 1; + if( p->iOffset>0 ){ + sqlite3VdbeAddOp2(v, OP_AddImm, p->iLimit, -1); + } + } + for(i=0, iCol=nKey+bSeq-1; i<nColumn; i++){ +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + if( aOutEx[i].fg.bSorterRef ) continue; +#endif + if( aOutEx[i].u.x.iOrderByCol==0 ) iCol++; + } +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + if( pSort->nDefer ){ + int iKey = iCol+1; + int regKey = sqlite3GetTempRange(pParse, nRefKey); + + for(i=0; i<pSort->nDefer; i++){ + int iCsr = pSort->aDefer[i].iCsr; + Table *pTab = pSort->aDefer[i].pTab; + int nKey = pSort->aDefer[i].nKey; + + sqlite3VdbeAddOp1(v, OP_NullRow, iCsr); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iKey++, regKey); + sqlite3VdbeAddOp3(v, OP_SeekRowid, iCsr, + sqlite3VdbeCurrentAddr(v)+1, regKey); + }else{ + int k; + int iJmp; + assert( sqlite3PrimaryKeyIndex(pTab)->nKeyCol==nKey ); + for(k=0; k<nKey; k++){ + sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iKey++, regKey+k); + } + iJmp = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp4Int(v, OP_SeekGE, iCsr, iJmp+2, regKey, nKey); + sqlite3VdbeAddOp4Int(v, OP_IdxLE, iCsr, iJmp+3, regKey, nKey); + sqlite3VdbeAddOp1(v, OP_NullRow, iCsr); + } + } + sqlite3ReleaseTempRange(pParse, regKey, nRefKey); + } +#endif + for(i=nColumn-1; i>=0; i--){ +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + if( aOutEx[i].fg.bSorterRef ){ + sqlite3ExprCode(pParse, aOutEx[i].pExpr, regRow+i); + }else +#endif + { + int iRead; + if( aOutEx[i].u.x.iOrderByCol ){ + iRead = aOutEx[i].u.x.iOrderByCol-1; + }else{ + iRead = iCol--; + } + sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iRead, regRow+i); + VdbeComment((v, "%s", aOutEx[i].zEName)); + } + } + sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); + switch( eDest ){ + case SRT_Table: + case SRT_EphemTab: { + sqlite3VdbeAddOp3(v, OP_Column, iSortTab, nKey+bSeq, regRow); + sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iParm, regRow, regRowid); + sqlite3VdbeChangeP5(v, OPFLAG_APPEND); + break; + } +#ifndef SQLITE_OMIT_SUBQUERY + case SRT_Set: { + assert( nColumn==sqlite3Strlen30(pDest->zAffSdst) ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, nColumn, regRowid, + pDest->zAffSdst, nColumn); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, regRowid, regRow, nColumn); + break; + } + case SRT_Mem: { + /* The LIMIT clause will terminate the loop for us */ + break; + } +#endif + case SRT_Upfrom: { + int i2 = pDest->iSDParm2; + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_MakeRecord,regRow+(i2<0),nColumn-(i2<0),r1); + if( i2<0 ){ + sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, regRow); + }else{ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regRow, i2); + } + break; + } + default: { + assert( eDest==SRT_Output || eDest==SRT_Coroutine ); + testcase( eDest==SRT_Output ); + testcase( eDest==SRT_Coroutine ); + if( eDest==SRT_Output ){ + sqlite3VdbeAddOp2(v, OP_ResultRow, pDest->iSdst, nColumn); + }else{ + sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm); + } + break; + } + } + if( regRowid ){ + if( eDest==SRT_Set ){ + sqlite3ReleaseTempRange(pParse, regRow, nColumn); + }else{ + sqlite3ReleaseTempReg(pParse, regRow); + } + sqlite3ReleaseTempReg(pParse, regRowid); + } + /* The bottom of the loop + */ + sqlite3VdbeResolveLabel(v, addrContinue); + if( pSort->sortFlags & SORTFLAG_UseSorter ){ + sqlite3VdbeAddOp2(v, OP_SorterNext, iTab, addr); VdbeCoverage(v); + }else{ + sqlite3VdbeAddOp2(v, OP_Next, iTab, addr); VdbeCoverage(v); + } + sqlite3VdbeScanStatusRange(v, addrExplain, sqlite3VdbeCurrentAddr(v)-1, -1); + if( pSort->regReturn ) sqlite3VdbeAddOp1(v, OP_Return, pSort->regReturn); + sqlite3VdbeResolveLabel(v, addrBreak); +} + +/* +** Return a pointer to a string containing the 'declaration type' of the +** expression pExpr. The string may be treated as static by the caller. +** +** The declaration type is the exact datatype definition extracted from the +** original CREATE TABLE statement if the expression is a column. The +** declaration type for a ROWID field is INTEGER. Exactly when an expression +** is considered a column can be complex in the presence of subqueries. The +** result-set expression in all of the following SELECT statements is +** considered a column by this function. +** +** SELECT col FROM tbl; +** SELECT (SELECT col FROM tbl; +** SELECT (SELECT col FROM tbl); +** SELECT abc FROM (SELECT col AS abc FROM tbl); +** +** The declaration type for any expression other than a column is NULL. +** +** This routine has either 3 or 6 parameters depending on whether or not +** the SQLITE_ENABLE_COLUMN_METADATA compile-time option is used. +*/ +#ifdef SQLITE_ENABLE_COLUMN_METADATA +# define columnType(A,B,C,D,E) columnTypeImpl(A,B,C,D,E) +#else /* if !defined(SQLITE_ENABLE_COLUMN_METADATA) */ +# define columnType(A,B,C,D,E) columnTypeImpl(A,B) +#endif +static const char *columnTypeImpl( + NameContext *pNC, +#ifndef SQLITE_ENABLE_COLUMN_METADATA + Expr *pExpr +#else + Expr *pExpr, + const char **pzOrigDb, + const char **pzOrigTab, + const char **pzOrigCol +#endif +){ + char const *zType = 0; + int j; +#ifdef SQLITE_ENABLE_COLUMN_METADATA + char const *zOrigDb = 0; + char const *zOrigTab = 0; + char const *zOrigCol = 0; +#endif + + assert( pExpr!=0 ); + assert( pNC->pSrcList!=0 ); + switch( pExpr->op ){ + case TK_COLUMN: { + /* The expression is a column. Locate the table the column is being + ** extracted from in NameContext.pSrcList. This table may be real + ** database table or a subquery. + */ + Table *pTab = 0; /* Table structure column is extracted from */ + Select *pS = 0; /* Select the column is extracted from */ + int iCol = pExpr->iColumn; /* Index of column in pTab */ + while( pNC && !pTab ){ + SrcList *pTabList = pNC->pSrcList; + for(j=0;j<pTabList->nSrc && pTabList->a[j].iCursor!=pExpr->iTable;j++); + if( j<pTabList->nSrc ){ + pTab = pTabList->a[j].pTab; + pS = pTabList->a[j].pSelect; + }else{ + pNC = pNC->pNext; + } + } + + if( pTab==0 ){ + /* At one time, code such as "SELECT new.x" within a trigger would + ** cause this condition to run. Since then, we have restructured how + ** trigger code is generated and so this condition is no longer + ** possible. However, it can still be true for statements like + ** the following: + ** + ** CREATE TABLE t1(col INTEGER); + ** SELECT (SELECT t1.col) FROM FROM t1; + ** + ** when columnType() is called on the expression "t1.col" in the + ** sub-select. In this case, set the column type to NULL, even + ** though it should really be "INTEGER". + ** + ** This is not a problem, as the column type of "t1.col" is never + ** used. When columnType() is called on the expression + ** "(SELECT t1.col)", the correct type is returned (see the TK_SELECT + ** branch below. */ + break; + } + + assert( pTab && ExprUseYTab(pExpr) && pExpr->y.pTab==pTab ); + if( pS ){ + /* The "table" is actually a sub-select or a view in the FROM clause + ** of the SELECT statement. Return the declaration type and origin + ** data for the result-set column of the sub-select. + */ + if( iCol<pS->pEList->nExpr +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + && iCol>=0 +#else + && ALWAYS(iCol>=0) +#endif + ){ + /* If iCol is less than zero, then the expression requests the + ** rowid of the sub-select or view. This expression is legal (see + ** test case misc2.2.2) - it always evaluates to NULL. + */ + NameContext sNC; + Expr *p = pS->pEList->a[iCol].pExpr; + sNC.pSrcList = pS->pSrc; + sNC.pNext = pNC; + sNC.pParse = pNC->pParse; + zType = columnType(&sNC, p,&zOrigDb,&zOrigTab,&zOrigCol); + } + }else{ + /* A real table or a CTE table */ + assert( !pS ); +#ifdef SQLITE_ENABLE_COLUMN_METADATA + if( iCol<0 ) iCol = pTab->iPKey; + assert( iCol==XN_ROWID || (iCol>=0 && iCol<pTab->nCol) ); + if( iCol<0 ){ + zType = "INTEGER"; + zOrigCol = "rowid"; + }else{ + zOrigCol = pTab->aCol[iCol].zCnName; + zType = sqlite3ColumnType(&pTab->aCol[iCol],0); + } + zOrigTab = pTab->zName; + if( pNC->pParse && pTab->pSchema ){ + int iDb = sqlite3SchemaToIndex(pNC->pParse->db, pTab->pSchema); + zOrigDb = pNC->pParse->db->aDb[iDb].zDbSName; + } +#else + assert( iCol==XN_ROWID || (iCol>=0 && iCol<pTab->nCol) ); + if( iCol<0 ){ + zType = "INTEGER"; + }else{ + zType = sqlite3ColumnType(&pTab->aCol[iCol],0); + } +#endif + } + break; + } +#ifndef SQLITE_OMIT_SUBQUERY + case TK_SELECT: { + /* The expression is a sub-select. Return the declaration type and + ** origin info for the single column in the result set of the SELECT + ** statement. + */ + NameContext sNC; + Select *pS; + Expr *p; + assert( ExprUseXSelect(pExpr) ); + pS = pExpr->x.pSelect; + p = pS->pEList->a[0].pExpr; + sNC.pSrcList = pS->pSrc; + sNC.pNext = pNC; + sNC.pParse = pNC->pParse; + zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); + break; + } +#endif + } + +#ifdef SQLITE_ENABLE_COLUMN_METADATA + if( pzOrigDb ){ + assert( pzOrigTab && pzOrigCol ); + *pzOrigDb = zOrigDb; + *pzOrigTab = zOrigTab; + *pzOrigCol = zOrigCol; + } +#endif + return zType; +} + +/* +** Generate code that will tell the VDBE the declaration types of columns +** in the result set. +*/ +static void generateColumnTypes( + Parse *pParse, /* Parser context */ + SrcList *pTabList, /* List of tables */ + ExprList *pEList /* Expressions defining the result set */ +){ +#ifndef SQLITE_OMIT_DECLTYPE + Vdbe *v = pParse->pVdbe; + int i; + NameContext sNC; + sNC.pSrcList = pTabList; + sNC.pParse = pParse; + sNC.pNext = 0; + for(i=0; i<pEList->nExpr; i++){ + Expr *p = pEList->a[i].pExpr; + const char *zType; +#ifdef SQLITE_ENABLE_COLUMN_METADATA + const char *zOrigDb = 0; + const char *zOrigTab = 0; + const char *zOrigCol = 0; + zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); + + /* The vdbe must make its own copy of the column-type and other + ** column specific strings, in case the schema is reset before this + ** virtual machine is deleted. + */ + sqlite3VdbeSetColName(v, i, COLNAME_DATABASE, zOrigDb, SQLITE_TRANSIENT); + sqlite3VdbeSetColName(v, i, COLNAME_TABLE, zOrigTab, SQLITE_TRANSIENT); + sqlite3VdbeSetColName(v, i, COLNAME_COLUMN, zOrigCol, SQLITE_TRANSIENT); +#else + zType = columnType(&sNC, p, 0, 0, 0); +#endif + sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, zType, SQLITE_TRANSIENT); + } +#endif /* !defined(SQLITE_OMIT_DECLTYPE) */ +} + + +/* +** Compute the column names for a SELECT statement. +** +** The only guarantee that SQLite makes about column names is that if the +** column has an AS clause assigning it a name, that will be the name used. +** That is the only documented guarantee. However, countless applications +** developed over the years have made baseless assumptions about column names +** and will break if those assumptions changes. Hence, use extreme caution +** when modifying this routine to avoid breaking legacy. +** +** See Also: sqlite3ColumnsFromExprList() +** +** The PRAGMA short_column_names and PRAGMA full_column_names settings are +** deprecated. The default setting is short=ON, full=OFF. 99.9% of all +** applications should operate this way. Nevertheless, we need to support the +** other modes for legacy: +** +** short=OFF, full=OFF: Column name is the text of the expression has it +** originally appears in the SELECT statement. In +** other words, the zSpan of the result expression. +** +** short=ON, full=OFF: (This is the default setting). If the result +** refers directly to a table column, then the +** result column name is just the table column +** name: COLUMN. Otherwise use zSpan. +** +** full=ON, short=ANY: If the result refers directly to a table column, +** then the result column name with the table name +** prefix, ex: TABLE.COLUMN. Otherwise use zSpan. +*/ +SQLITE_PRIVATE void sqlite3GenerateColumnNames( + Parse *pParse, /* Parser context */ + Select *pSelect /* Generate column names for this SELECT statement */ +){ + Vdbe *v = pParse->pVdbe; + int i; + Table *pTab; + SrcList *pTabList; + ExprList *pEList; + sqlite3 *db = pParse->db; + int fullName; /* TABLE.COLUMN if no AS clause and is a direct table ref */ + int srcName; /* COLUMN or TABLE.COLUMN if no AS clause and is direct */ + + if( pParse->colNamesSet ) return; + /* Column names are determined by the left-most term of a compound select */ + while( pSelect->pPrior ) pSelect = pSelect->pPrior; + TREETRACE(0x80,pParse,pSelect,("generating column names\n")); + pTabList = pSelect->pSrc; + pEList = pSelect->pEList; + assert( v!=0 ); + assert( pTabList!=0 ); + pParse->colNamesSet = 1; + fullName = (db->flags & SQLITE_FullColNames)!=0; + srcName = (db->flags & SQLITE_ShortColNames)!=0 || fullName; + sqlite3VdbeSetNumCols(v, pEList->nExpr); + for(i=0; i<pEList->nExpr; i++){ + Expr *p = pEList->a[i].pExpr; + + assert( p!=0 ); + assert( p->op!=TK_AGG_COLUMN ); /* Agg processing has not run yet */ + assert( p->op!=TK_COLUMN + || (ExprUseYTab(p) && p->y.pTab!=0) ); /* Covering idx not yet coded */ + if( pEList->a[i].zEName && pEList->a[i].fg.eEName==ENAME_NAME ){ + /* An AS clause always takes first priority */ + char *zName = pEList->a[i].zEName; + sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_TRANSIENT); + }else if( srcName && p->op==TK_COLUMN ){ + char *zCol; + int iCol = p->iColumn; + pTab = p->y.pTab; + assert( pTab!=0 ); + if( iCol<0 ) iCol = pTab->iPKey; + assert( iCol==-1 || (iCol>=0 && iCol<pTab->nCol) ); + if( iCol<0 ){ + zCol = "rowid"; + }else{ + zCol = pTab->aCol[iCol].zCnName; + } + if( fullName ){ + char *zName = 0; + zName = sqlite3MPrintf(db, "%s.%s", pTab->zName, zCol); + sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_DYNAMIC); + }else{ + sqlite3VdbeSetColName(v, i, COLNAME_NAME, zCol, SQLITE_TRANSIENT); + } + }else{ + const char *z = pEList->a[i].zEName; + z = z==0 ? sqlite3MPrintf(db, "column%d", i+1) : sqlite3DbStrDup(db, z); + sqlite3VdbeSetColName(v, i, COLNAME_NAME, z, SQLITE_DYNAMIC); + } + } + generateColumnTypes(pParse, pTabList, pEList); +} + +/* +** Given an expression list (which is really the list of expressions +** that form the result set of a SELECT statement) compute appropriate +** column names for a table that would hold the expression list. +** +** All column names will be unique. +** +** Only the column names are computed. Column.zType, Column.zColl, +** and other fields of Column are zeroed. +** +** Return SQLITE_OK on success. If a memory allocation error occurs, +** store NULL in *paCol and 0 in *pnCol and return SQLITE_NOMEM. +** +** The only guarantee that SQLite makes about column names is that if the +** column has an AS clause assigning it a name, that will be the name used. +** That is the only documented guarantee. However, countless applications +** developed over the years have made baseless assumptions about column names +** and will break if those assumptions changes. Hence, use extreme caution +** when modifying this routine to avoid breaking legacy. +** +** See Also: sqlite3GenerateColumnNames() +*/ +SQLITE_PRIVATE int sqlite3ColumnsFromExprList( + Parse *pParse, /* Parsing context */ + ExprList *pEList, /* Expr list from which to derive column names */ + i16 *pnCol, /* Write the number of columns here */ + Column **paCol /* Write the new column list here */ +){ + sqlite3 *db = pParse->db; /* Database connection */ + int i, j; /* Loop counters */ + u32 cnt; /* Index added to make the name unique */ + Column *aCol, *pCol; /* For looping over result columns */ + int nCol; /* Number of columns in the result set */ + char *zName; /* Column name */ + int nName; /* Size of name in zName[] */ + Hash ht; /* Hash table of column names */ + Table *pTab; + + sqlite3HashInit(&ht); + if( pEList ){ + nCol = pEList->nExpr; + aCol = sqlite3DbMallocZero(db, sizeof(aCol[0])*nCol); + testcase( aCol==0 ); + if( NEVER(nCol>32767) ) nCol = 32767; + }else{ + nCol = 0; + aCol = 0; + } + assert( nCol==(i16)nCol ); + *pnCol = nCol; + *paCol = aCol; + + for(i=0, pCol=aCol; i<nCol && !pParse->nErr; i++, pCol++){ + struct ExprList_item *pX = &pEList->a[i]; + struct ExprList_item *pCollide; + /* Get an appropriate name for the column + */ + if( (zName = pX->zEName)!=0 && pX->fg.eEName==ENAME_NAME ){ + /* If the column contains an "AS <name>" phrase, use <name> as the name */ + }else{ + Expr *pColExpr = sqlite3ExprSkipCollateAndLikely(pX->pExpr); + while( ALWAYS(pColExpr!=0) && pColExpr->op==TK_DOT ){ + pColExpr = pColExpr->pRight; + assert( pColExpr!=0 ); + } + if( pColExpr->op==TK_COLUMN + && ALWAYS( ExprUseYTab(pColExpr) ) + && ALWAYS( pColExpr->y.pTab!=0 ) + ){ + /* For columns use the column name name */ + int iCol = pColExpr->iColumn; + pTab = pColExpr->y.pTab; + if( iCol<0 ) iCol = pTab->iPKey; + zName = iCol>=0 ? pTab->aCol[iCol].zCnName : "rowid"; + }else if( pColExpr->op==TK_ID ){ + assert( !ExprHasProperty(pColExpr, EP_IntValue) ); + zName = pColExpr->u.zToken; + }else{ + /* Use the original text of the column expression as its name */ + assert( zName==pX->zEName ); /* pointer comparison intended */ + } + } + if( zName && !sqlite3IsTrueOrFalse(zName) ){ + zName = sqlite3DbStrDup(db, zName); + }else{ + zName = sqlite3MPrintf(db,"column%d",i+1); + } + + /* Make sure the column name is unique. If the name is not unique, + ** append an integer to the name so that it becomes unique. + */ + cnt = 0; + while( zName && (pCollide = sqlite3HashFind(&ht, zName))!=0 ){ + if( pCollide->fg.bUsingTerm ){ + pCol->colFlags |= COLFLAG_NOEXPAND; + } + nName = sqlite3Strlen30(zName); + if( nName>0 ){ + for(j=nName-1; j>0 && sqlite3Isdigit(zName[j]); j--){} + if( zName[j]==':' ) nName = j; + } + zName = sqlite3MPrintf(db, "%.*z:%u", nName, zName, ++cnt); + sqlite3ProgressCheck(pParse); + if( cnt>3 ){ + sqlite3_randomness(sizeof(cnt), &cnt); + } + } + pCol->zCnName = zName; + pCol->hName = sqlite3StrIHash(zName); + if( pX->fg.bNoExpand ){ + pCol->colFlags |= COLFLAG_NOEXPAND; + } + sqlite3ColumnPropertiesFromName(0, pCol); + if( zName && sqlite3HashInsert(&ht, zName, pX)==pX ){ + sqlite3OomFault(db); + } + } + sqlite3HashClear(&ht); + if( pParse->nErr ){ + for(j=0; j<i; j++){ + sqlite3DbFree(db, aCol[j].zCnName); + } + sqlite3DbFree(db, aCol); + *paCol = 0; + *pnCol = 0; + return pParse->rc; + } + return SQLITE_OK; +} + +/* +** pTab is a transient Table object that represents a subquery of some +** kind (maybe a parenthesized subquery in the FROM clause of a larger +** query, or a VIEW, or a CTE). This routine computes type information +** for that Table object based on the Select object that implements the +** subquery. For the purposes of this routine, "type information" means: +** +** * The datatype name, as it might appear in a CREATE TABLE statement +** * Which collating sequence to use for the column +** * The affinity of the column +*/ +SQLITE_PRIVATE void sqlite3SubqueryColumnTypes( + Parse *pParse, /* Parsing contexts */ + Table *pTab, /* Add column type information to this table */ + Select *pSelect, /* SELECT used to determine types and collations */ + char aff /* Default affinity. */ +){ + sqlite3 *db = pParse->db; + Column *pCol; + CollSeq *pColl; + int i,j; + Expr *p; + struct ExprList_item *a; + NameContext sNC; + + assert( pSelect!=0 ); + assert( (pSelect->selFlags & SF_Resolved)!=0 ); + assert( pTab->nCol==pSelect->pEList->nExpr || pParse->nErr>0 ); + assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB ); + if( db->mallocFailed || IN_RENAME_OBJECT ) return; + while( pSelect->pPrior ) pSelect = pSelect->pPrior; + a = pSelect->pEList->a; + memset(&sNC, 0, sizeof(sNC)); + sNC.pSrcList = pSelect->pSrc; + for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){ + const char *zType; + i64 n; + pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); + p = a[i].pExpr; + /* pCol->szEst = ... // Column size est for SELECT tables never used */ + pCol->affinity = sqlite3ExprAffinity(p); + if( pCol->affinity<=SQLITE_AFF_NONE ){ + pCol->affinity = aff; + } + if( pCol->affinity>=SQLITE_AFF_TEXT && pSelect->pNext ){ + int m = 0; + Select *pS2; + for(m=0, pS2=pSelect->pNext; pS2; pS2=pS2->pNext){ + m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); + } + if( pCol->affinity==SQLITE_AFF_TEXT && (m&0x01)!=0 ){ + pCol->affinity = SQLITE_AFF_BLOB; + }else + if( pCol->affinity>=SQLITE_AFF_NUMERIC && (m&0x02)!=0 ){ + pCol->affinity = SQLITE_AFF_BLOB; + } + if( pCol->affinity>=SQLITE_AFF_NUMERIC && p->op==TK_CAST ){ + pCol->affinity = SQLITE_AFF_FLEXNUM; + } + } + zType = columnType(&sNC, p, 0, 0, 0); + if( zType==0 || pCol->affinity!=sqlite3AffinityType(zType, 0) ){ + if( pCol->affinity==SQLITE_AFF_NUMERIC + || pCol->affinity==SQLITE_AFF_FLEXNUM + ){ + zType = "NUM"; + }else{ + zType = 0; + for(j=1; j<SQLITE_N_STDTYPE; j++){ + if( sqlite3StdTypeAffinity[j]==pCol->affinity ){ + zType = sqlite3StdType[j]; + break; + } + } + } + } + if( zType ){ + i64 m = sqlite3Strlen30(zType); + n = sqlite3Strlen30(pCol->zCnName); + pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); + pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); + if( pCol->zCnName ){ + memcpy(&pCol->zCnName[n+1], zType, m+1); + pCol->colFlags |= COLFLAG_HASTYPE; + } + } + pColl = sqlite3ExprCollSeq(pParse, p); + if( pColl ){ + assert( pTab->pIndex==0 ); + sqlite3ColumnSetColl(db, pCol, pColl->zName); + } + } + pTab->szTabRow = 1; /* Any non-zero value works */ +} + +/* +** Given a SELECT statement, generate a Table structure that describes +** the result set of that SELECT. +*/ +SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect, char aff){ + Table *pTab; + sqlite3 *db = pParse->db; + u64 savedFlags; + + savedFlags = db->flags; + db->flags &= ~(u64)SQLITE_FullColNames; + db->flags |= SQLITE_ShortColNames; + sqlite3SelectPrep(pParse, pSelect, 0); + db->flags = savedFlags; + if( pParse->nErr ) return 0; + while( pSelect->pPrior ) pSelect = pSelect->pPrior; + pTab = sqlite3DbMallocZero(db, sizeof(Table) ); + if( pTab==0 ){ + return 0; + } + pTab->nTabRef = 1; + pTab->zName = 0; + pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); + sqlite3ColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol); + sqlite3SubqueryColumnTypes(pParse, pTab, pSelect, aff); + pTab->iPKey = -1; + if( db->mallocFailed ){ + sqlite3DeleteTable(db, pTab); + return 0; + } + return pTab; +} + +/* +** Get a VDBE for the given parser context. Create a new one if necessary. +** If an error occurs, return NULL and leave a message in pParse. +*/ +SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse *pParse){ + if( pParse->pVdbe ){ + return pParse->pVdbe; + } + if( pParse->pToplevel==0 + && OptimizationEnabled(pParse->db,SQLITE_FactorOutConst) + ){ + pParse->okConstFactor = 1; + } + return sqlite3VdbeCreate(pParse); +} + + +/* +** Compute the iLimit and iOffset fields of the SELECT based on the +** pLimit expressions. pLimit->pLeft and pLimit->pRight hold the expressions +** that appear in the original SQL statement after the LIMIT and OFFSET +** keywords. Or NULL if those keywords are omitted. iLimit and iOffset +** are the integer memory register numbers for counters used to compute +** the limit and offset. If there is no limit and/or offset, then +** iLimit and iOffset are negative. +** +** This routine changes the values of iLimit and iOffset only if +** a limit or offset is defined by pLimit->pLeft and pLimit->pRight. iLimit +** and iOffset should have been preset to appropriate default values (zero) +** prior to calling this routine. +** +** The iOffset register (if it exists) is initialized to the value +** of the OFFSET. The iLimit register is initialized to LIMIT. Register +** iOffset+1 is initialized to LIMIT+OFFSET. +** +** Only if pLimit->pLeft!=0 do the limit registers get +** redefined. The UNION ALL operator uses this property to force +** the reuse of the same limit and offset registers across multiple +** SELECT statements. +*/ +static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ + Vdbe *v = 0; + int iLimit = 0; + int iOffset; + int n; + Expr *pLimit = p->pLimit; + + if( p->iLimit ) return; + + /* + ** "LIMIT -1" always shows all rows. There is some + ** controversy about what the correct behavior should be. + ** The current implementation interprets "LIMIT 0" to mean + ** no rows. + */ + if( pLimit ){ + assert( pLimit->op==TK_LIMIT ); + assert( pLimit->pLeft!=0 ); + p->iLimit = iLimit = ++pParse->nMem; + v = sqlite3GetVdbe(pParse); + assert( v!=0 ); + if( sqlite3ExprIsInteger(pLimit->pLeft, &n) ){ + sqlite3VdbeAddOp2(v, OP_Integer, n, iLimit); + VdbeComment((v, "LIMIT counter")); + if( n==0 ){ + sqlite3VdbeGoto(v, iBreak); + }else if( n>=0 && p->nSelectRow>sqlite3LogEst((u64)n) ){ + p->nSelectRow = sqlite3LogEst((u64)n); + p->selFlags |= SF_FixedLimit; + } + }else{ + sqlite3ExprCode(pParse, pLimit->pLeft, iLimit); + sqlite3VdbeAddOp1(v, OP_MustBeInt, iLimit); VdbeCoverage(v); + VdbeComment((v, "LIMIT counter")); + sqlite3VdbeAddOp2(v, OP_IfNot, iLimit, iBreak); VdbeCoverage(v); + } + if( pLimit->pRight ){ + p->iOffset = iOffset = ++pParse->nMem; + pParse->nMem++; /* Allocate an extra register for limit+offset */ + sqlite3ExprCode(pParse, pLimit->pRight, iOffset); + sqlite3VdbeAddOp1(v, OP_MustBeInt, iOffset); VdbeCoverage(v); + VdbeComment((v, "OFFSET counter")); + sqlite3VdbeAddOp3(v, OP_OffsetLimit, iLimit, iOffset+1, iOffset); + VdbeComment((v, "LIMIT+OFFSET")); + } + } +} + +#ifndef SQLITE_OMIT_COMPOUND_SELECT +/* +** Return the appropriate collating sequence for the iCol-th column of +** the result set for the compound-select statement "p". Return NULL if +** the column has no default collating sequence. +** +** The collating sequence for the compound select is taken from the +** left-most term of the select that has a collating sequence. +*/ +static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){ + CollSeq *pRet; + if( p->pPrior ){ + pRet = multiSelectCollSeq(pParse, p->pPrior, iCol); + }else{ + pRet = 0; + } + assert( iCol>=0 ); + /* iCol must be less than p->pEList->nExpr. Otherwise an error would + ** have been thrown during name resolution and we would not have gotten + ** this far */ + if( pRet==0 && ALWAYS(iCol<p->pEList->nExpr) ){ + pRet = sqlite3ExprCollSeq(pParse, p->pEList->a[iCol].pExpr); + } + return pRet; +} + +/* +** The select statement passed as the second parameter is a compound SELECT +** with an ORDER BY clause. This function allocates and returns a KeyInfo +** structure suitable for implementing the ORDER BY. +** +** Space to hold the KeyInfo structure is obtained from malloc. The calling +** function is responsible for ensuring that this structure is eventually +** freed. +*/ +static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ + ExprList *pOrderBy = p->pOrderBy; + int nOrderBy = ALWAYS(pOrderBy!=0) ? pOrderBy->nExpr : 0; + sqlite3 *db = pParse->db; + KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy+nExtra, 1); + if( pRet ){ + int i; + for(i=0; i<nOrderBy; i++){ + struct ExprList_item *pItem = &pOrderBy->a[i]; + Expr *pTerm = pItem->pExpr; + CollSeq *pColl; + + if( pTerm->flags & EP_Collate ){ + pColl = sqlite3ExprCollSeq(pParse, pTerm); + }else{ + pColl = multiSelectCollSeq(pParse, p, pItem->u.x.iOrderByCol-1); + if( pColl==0 ) pColl = db->pDfltColl; + pOrderBy->a[i].pExpr = + sqlite3ExprAddCollateString(pParse, pTerm, pColl->zName); + } + assert( sqlite3KeyInfoIsWriteable(pRet) ); + pRet->aColl[i] = pColl; + pRet->aSortFlags[i] = pOrderBy->a[i].fg.sortFlags; + } + } + + return pRet; +} + +#ifndef SQLITE_OMIT_CTE +/* +** This routine generates VDBE code to compute the content of a WITH RECURSIVE +** query of the form: +** +** <recursive-table> AS (<setup-query> UNION [ALL] <recursive-query>) +** \___________/ \_______________/ +** p->pPrior p +** +** +** There is exactly one reference to the recursive-table in the FROM clause +** of recursive-query, marked with the SrcList->a[].fg.isRecursive flag. +** +** The setup-query runs once to generate an initial set of rows that go +** into a Queue table. Rows are extracted from the Queue table one by +** one. Each row extracted from Queue is output to pDest. Then the single +** extracted row (now in the iCurrent table) becomes the content of the +** recursive-table for a recursive-query run. The output of the recursive-query +** is added back into the Queue table. Then another row is extracted from Queue +** and the iteration continues until the Queue table is empty. +** +** If the compound query operator is UNION then no duplicate rows are ever +** inserted into the Queue table. The iDistinct table keeps a copy of all rows +** that have ever been inserted into Queue and causes duplicates to be +** discarded. If the operator is UNION ALL, then duplicates are allowed. +** +** If the query has an ORDER BY, then entries in the Queue table are kept in +** ORDER BY order and the first entry is extracted for each cycle. Without +** an ORDER BY, the Queue table is just a FIFO. +** +** If a LIMIT clause is provided, then the iteration stops after LIMIT rows +** have been output to pDest. A LIMIT of zero means to output no rows and a +** negative LIMIT means to output all rows. If there is also an OFFSET clause +** with a positive value, then the first OFFSET outputs are discarded rather +** than being sent to pDest. The LIMIT count does not begin until after OFFSET +** rows have been skipped. +*/ +static void generateWithRecursiveQuery( + Parse *pParse, /* Parsing context */ + Select *p, /* The recursive SELECT to be coded */ + SelectDest *pDest /* What to do with query results */ +){ + SrcList *pSrc = p->pSrc; /* The FROM clause of the recursive query */ + int nCol = p->pEList->nExpr; /* Number of columns in the recursive table */ + Vdbe *v = pParse->pVdbe; /* The prepared statement under construction */ + Select *pSetup; /* The setup query */ + Select *pFirstRec; /* Left-most recursive term */ + int addrTop; /* Top of the loop */ + int addrCont, addrBreak; /* CONTINUE and BREAK addresses */ + int iCurrent = 0; /* The Current table */ + int regCurrent; /* Register holding Current table */ + int iQueue; /* The Queue table */ + int iDistinct = 0; /* To ensure unique results if UNION */ + int eDest = SRT_Fifo; /* How to write to Queue */ + SelectDest destQueue; /* SelectDest targeting the Queue table */ + int i; /* Loop counter */ + int rc; /* Result code */ + ExprList *pOrderBy; /* The ORDER BY clause */ + Expr *pLimit; /* Saved LIMIT and OFFSET */ + int regLimit, regOffset; /* Registers used by LIMIT and OFFSET */ + +#ifndef SQLITE_OMIT_WINDOWFUNC + if( p->pWin ){ + sqlite3ErrorMsg(pParse, "cannot use window functions in recursive queries"); + return; + } +#endif + + /* Obtain authorization to do a recursive query */ + if( sqlite3AuthCheck(pParse, SQLITE_RECURSIVE, 0, 0, 0) ) return; + + /* Process the LIMIT and OFFSET clauses, if they exist */ + addrBreak = sqlite3VdbeMakeLabel(pParse); + p->nSelectRow = 320; /* 4 billion rows */ + computeLimitRegisters(pParse, p, addrBreak); + pLimit = p->pLimit; + regLimit = p->iLimit; + regOffset = p->iOffset; + p->pLimit = 0; + p->iLimit = p->iOffset = 0; + pOrderBy = p->pOrderBy; + + /* Locate the cursor number of the Current table */ + for(i=0; ALWAYS(i<pSrc->nSrc); i++){ + if( pSrc->a[i].fg.isRecursive ){ + iCurrent = pSrc->a[i].iCursor; + break; + } + } + + /* Allocate cursors numbers for Queue and Distinct. The cursor number for + ** the Distinct table must be exactly one greater than Queue in order + ** for the SRT_DistFifo and SRT_DistQueue destinations to work. */ + iQueue = pParse->nTab++; + if( p->op==TK_UNION ){ + eDest = pOrderBy ? SRT_DistQueue : SRT_DistFifo; + iDistinct = pParse->nTab++; + }else{ + eDest = pOrderBy ? SRT_Queue : SRT_Fifo; + } + sqlite3SelectDestInit(&destQueue, eDest, iQueue); + + /* Allocate cursors for Current, Queue, and Distinct. */ + regCurrent = ++pParse->nMem; + sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol); + if( pOrderBy ){ + KeyInfo *pKeyInfo = multiSelectOrderByKeyInfo(pParse, p, 1); + sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iQueue, pOrderBy->nExpr+2, 0, + (char*)pKeyInfo, P4_KEYINFO); + destQueue.pOrderBy = pOrderBy; + }else{ + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iQueue, nCol); + } + VdbeComment((v, "Queue table")); + if( iDistinct ){ + p->addrOpenEphm[0] = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iDistinct, 0); + p->selFlags |= SF_UsesEphemeral; + } + + /* Detach the ORDER BY clause from the compound SELECT */ + p->pOrderBy = 0; + + /* Figure out how many elements of the compound SELECT are part of the + ** recursive query. Make sure no recursive elements use aggregate + ** functions. Mark the recursive elements as UNION ALL even if they + ** are really UNION because the distinctness will be enforced by the + ** iDistinct table. pFirstRec is left pointing to the left-most + ** recursive term of the CTE. + */ + for(pFirstRec=p; ALWAYS(pFirstRec!=0); pFirstRec=pFirstRec->pPrior){ + if( pFirstRec->selFlags & SF_Aggregate ){ + sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported"); + goto end_of_recursive_query; + } + pFirstRec->op = TK_ALL; + if( (pFirstRec->pPrior->selFlags & SF_Recursive)==0 ) break; + } + + /* Store the results of the setup-query in Queue. */ + pSetup = pFirstRec->pPrior; + pSetup->pNext = 0; + ExplainQueryPlan((pParse, 1, "SETUP")); + rc = sqlite3Select(pParse, pSetup, &destQueue); + pSetup->pNext = p; + if( rc ) goto end_of_recursive_query; + + /* Find the next row in the Queue and output that row */ + addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, iQueue, addrBreak); VdbeCoverage(v); + + /* Transfer the next row in Queue over to Current */ + sqlite3VdbeAddOp1(v, OP_NullRow, iCurrent); /* To reset column cache */ + if( pOrderBy ){ + sqlite3VdbeAddOp3(v, OP_Column, iQueue, pOrderBy->nExpr+1, regCurrent); + }else{ + sqlite3VdbeAddOp2(v, OP_RowData, iQueue, regCurrent); + } + sqlite3VdbeAddOp1(v, OP_Delete, iQueue); + + /* Output the single row in Current */ + addrCont = sqlite3VdbeMakeLabel(pParse); + codeOffset(v, regOffset, addrCont); + selectInnerLoop(pParse, p, iCurrent, + 0, 0, pDest, addrCont, addrBreak); + if( regLimit ){ + sqlite3VdbeAddOp2(v, OP_DecrJumpZero, regLimit, addrBreak); + VdbeCoverage(v); + } + sqlite3VdbeResolveLabel(v, addrCont); + + /* Execute the recursive SELECT taking the single row in Current as + ** the value for the recursive-table. Store the results in the Queue. + */ + pFirstRec->pPrior = 0; + ExplainQueryPlan((pParse, 1, "RECURSIVE STEP")); + sqlite3Select(pParse, p, &destQueue); + assert( pFirstRec->pPrior==0 ); + pFirstRec->pPrior = pSetup; + + /* Keep running the loop until the Queue is empty */ + sqlite3VdbeGoto(v, addrTop); + sqlite3VdbeResolveLabel(v, addrBreak); + +end_of_recursive_query: + sqlite3ExprListDelete(pParse->db, p->pOrderBy); + p->pOrderBy = pOrderBy; + p->pLimit = pLimit; + return; +} +#endif /* SQLITE_OMIT_CTE */ + +/* Forward references */ +static int multiSelectOrderBy( + Parse *pParse, /* Parsing context */ + Select *p, /* The right-most of SELECTs to be coded */ + SelectDest *pDest /* What to do with query results */ +); + +/* +** Handle the special case of a compound-select that originates from a +** VALUES clause. By handling this as a special case, we avoid deep +** recursion, and thus do not need to enforce the SQLITE_LIMIT_COMPOUND_SELECT +** on a VALUES clause. +** +** Because the Select object originates from a VALUES clause: +** (1) There is no LIMIT or OFFSET or else there is a LIMIT of exactly 1 +** (2) All terms are UNION ALL +** (3) There is no ORDER BY clause +** +** The "LIMIT of exactly 1" case of condition (1) comes about when a VALUES +** clause occurs within scalar expression (ex: "SELECT (VALUES(1),(2),(3))"). +** The sqlite3CodeSubselect will have added the LIMIT 1 clause in tht case. +** Since the limit is exactly 1, we only need to evaluate the left-most VALUES. +*/ +static int multiSelectValues( + Parse *pParse, /* Parsing context */ + Select *p, /* The right-most of SELECTs to be coded */ + SelectDest *pDest /* What to do with query results */ +){ + int nRow = 1; + int rc = 0; + int bShowAll = p->pLimit==0; + assert( p->selFlags & SF_MultiValue ); + do{ + assert( p->selFlags & SF_Values ); + assert( p->op==TK_ALL || (p->op==TK_SELECT && p->pPrior==0) ); + assert( p->pNext==0 || p->pEList->nExpr==p->pNext->pEList->nExpr ); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( p->pWin ) return -1; +#endif + if( p->pPrior==0 ) break; + assert( p->pPrior->pNext==p ); + p = p->pPrior; + nRow += bShowAll; + }while(1); + ExplainQueryPlan((pParse, 0, "SCAN %d CONSTANT ROW%s", nRow, + nRow==1 ? "" : "S")); + while( p ){ + selectInnerLoop(pParse, p, -1, 0, 0, pDest, 1, 1); + if( !bShowAll ) break; + p->nSelectRow = nRow; + p = p->pNext; + } + return rc; +} + +/* +** Return true if the SELECT statement which is known to be the recursive +** part of a recursive CTE still has its anchor terms attached. If the +** anchor terms have already been removed, then return false. +*/ +static int hasAnchor(Select *p){ + while( p && (p->selFlags & SF_Recursive)!=0 ){ p = p->pPrior; } + return p!=0; +} + +/* +** This routine is called to process a compound query form from +** two or more separate queries using UNION, UNION ALL, EXCEPT, or +** INTERSECT +** +** "p" points to the right-most of the two queries. the query on the +** left is p->pPrior. The left query could also be a compound query +** in which case this routine will be called recursively. +** +** The results of the total query are to be written into a destination +** of type eDest with parameter iParm. +** +** Example 1: Consider a three-way compound SQL statement. +** +** SELECT a FROM t1 UNION SELECT b FROM t2 UNION SELECT c FROM t3 +** +** This statement is parsed up as follows: +** +** SELECT c FROM t3 +** | +** `-----> SELECT b FROM t2 +** | +** `------> SELECT a FROM t1 +** +** The arrows in the diagram above represent the Select.pPrior pointer. +** So if this routine is called with p equal to the t3 query, then +** pPrior will be the t2 query. p->op will be TK_UNION in this case. +** +** Notice that because of the way SQLite parses compound SELECTs, the +** individual selects always group from left to right. +*/ +static int multiSelect( + Parse *pParse, /* Parsing context */ + Select *p, /* The right-most of SELECTs to be coded */ + SelectDest *pDest /* What to do with query results */ +){ + int rc = SQLITE_OK; /* Success code from a subroutine */ + Select *pPrior; /* Another SELECT immediately to our left */ + Vdbe *v; /* Generate code to this VDBE */ + SelectDest dest; /* Alternative data destination */ + Select *pDelete = 0; /* Chain of simple selects to delete */ + sqlite3 *db; /* Database connection */ + + /* Make sure there is no ORDER BY or LIMIT clause on prior SELECTs. Only + ** the last (right-most) SELECT in the series may have an ORDER BY or LIMIT. + */ + assert( p && p->pPrior ); /* Calling function guarantees this much */ + assert( (p->selFlags & SF_Recursive)==0 || p->op==TK_ALL || p->op==TK_UNION ); + assert( p->selFlags & SF_Compound ); + db = pParse->db; + pPrior = p->pPrior; + dest = *pDest; + assert( pPrior->pOrderBy==0 ); + assert( pPrior->pLimit==0 ); + + v = sqlite3GetVdbe(pParse); + assert( v!=0 ); /* The VDBE already created by calling function */ + + /* Create the destination temporary table if necessary + */ + if( dest.eDest==SRT_EphemTab ){ + assert( p->pEList ); + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, dest.iSDParm, p->pEList->nExpr); + dest.eDest = SRT_Table; + } + + /* Special handling for a compound-select that originates as a VALUES clause. + */ + if( p->selFlags & SF_MultiValue ){ + rc = multiSelectValues(pParse, p, &dest); + if( rc>=0 ) goto multi_select_end; + rc = SQLITE_OK; + } + + /* Make sure all SELECTs in the statement have the same number of elements + ** in their result sets. + */ + assert( p->pEList && pPrior->pEList ); + assert( p->pEList->nExpr==pPrior->pEList->nExpr ); + +#ifndef SQLITE_OMIT_CTE + if( (p->selFlags & SF_Recursive)!=0 && hasAnchor(p) ){ + generateWithRecursiveQuery(pParse, p, &dest); + }else +#endif + + /* Compound SELECTs that have an ORDER BY clause are handled separately. + */ + if( p->pOrderBy ){ + return multiSelectOrderBy(pParse, p, pDest); + }else{ + +#ifndef SQLITE_OMIT_EXPLAIN + if( pPrior->pPrior==0 ){ + ExplainQueryPlan((pParse, 1, "COMPOUND QUERY")); + ExplainQueryPlan((pParse, 1, "LEFT-MOST SUBQUERY")); + } +#endif + + /* Generate code for the left and right SELECT statements. + */ + switch( p->op ){ + case TK_ALL: { + int addr = 0; + int nLimit = 0; /* Initialize to suppress harmless compiler warning */ + assert( !pPrior->pLimit ); + pPrior->iLimit = p->iLimit; + pPrior->iOffset = p->iOffset; + pPrior->pLimit = p->pLimit; + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n")); + rc = sqlite3Select(pParse, pPrior, &dest); + pPrior->pLimit = 0; + if( rc ){ + goto multi_select_end; + } + p->pPrior = 0; + p->iLimit = pPrior->iLimit; + p->iOffset = pPrior->iOffset; + if( p->iLimit ){ + addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); + VdbeComment((v, "Jump ahead if LIMIT reached")); + if( p->iOffset ){ + sqlite3VdbeAddOp3(v, OP_OffsetLimit, + p->iLimit, p->iOffset+1, p->iOffset); + } + } + ExplainQueryPlan((pParse, 1, "UNION ALL")); + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n")); + rc = sqlite3Select(pParse, p, &dest); + testcase( rc!=SQLITE_OK ); + pDelete = p->pPrior; + p->pPrior = pPrior; + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); + if( p->pLimit + && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit) + && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) + ){ + p->nSelectRow = sqlite3LogEst((u64)nLimit); + } + if( addr ){ + sqlite3VdbeJumpHere(v, addr); + } + break; + } + case TK_EXCEPT: + case TK_UNION: { + int unionTab; /* Cursor number of the temp table holding result */ + u8 op = 0; /* One of the SRT_ operations to apply to self */ + int priorOp; /* The SRT_ operation to apply to prior selects */ + Expr *pLimit; /* Saved values of p->nLimit */ + int addr; + SelectDest uniondest; + + testcase( p->op==TK_EXCEPT ); + testcase( p->op==TK_UNION ); + priorOp = SRT_Union; + if( dest.eDest==priorOp ){ + /* We can reuse a temporary table generated by a SELECT to our + ** right. + */ + assert( p->pLimit==0 ); /* Not allowed on leftward elements */ + unionTab = dest.iSDParm; + }else{ + /* We will need to create our own temporary table to hold the + ** intermediate results. + */ + unionTab = pParse->nTab++; + assert( p->pOrderBy==0 ); + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0); + assert( p->addrOpenEphm[0] == -1 ); + p->addrOpenEphm[0] = addr; + findRightmost(p)->selFlags |= SF_UsesEphemeral; + assert( p->pEList ); + } + + + /* Code the SELECT statements to our left + */ + assert( !pPrior->pOrderBy ); + sqlite3SelectDestInit(&uniondest, priorOp, unionTab); + TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION left...\n")); + rc = sqlite3Select(pParse, pPrior, &uniondest); + if( rc ){ + goto multi_select_end; + } + + /* Code the current SELECT statement + */ + if( p->op==TK_EXCEPT ){ + op = SRT_Except; + }else{ + assert( p->op==TK_UNION ); + op = SRT_Union; + } + p->pPrior = 0; + pLimit = p->pLimit; + p->pLimit = 0; + uniondest.eDest = op; + ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", + sqlite3SelectOpName(p->op))); + TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION right...\n")); + rc = sqlite3Select(pParse, p, &uniondest); + testcase( rc!=SQLITE_OK ); + assert( p->pOrderBy==0 ); + pDelete = p->pPrior; + p->pPrior = pPrior; + p->pOrderBy = 0; + if( p->op==TK_UNION ){ + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); + } + sqlite3ExprDelete(db, p->pLimit); + p->pLimit = pLimit; + p->iLimit = 0; + p->iOffset = 0; + + /* Convert the data in the temporary table into whatever form + ** it is that we currently need. + */ + assert( unionTab==dest.iSDParm || dest.eDest!=priorOp ); + assert( p->pEList || db->mallocFailed ); + if( dest.eDest!=priorOp && db->mallocFailed==0 ){ + int iCont, iBreak, iStart; + iBreak = sqlite3VdbeMakeLabel(pParse); + iCont = sqlite3VdbeMakeLabel(pParse); + computeLimitRegisters(pParse, p, iBreak); + sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); VdbeCoverage(v); + iStart = sqlite3VdbeCurrentAddr(v); + selectInnerLoop(pParse, p, unionTab, + 0, 0, &dest, iCont, iBreak); + sqlite3VdbeResolveLabel(v, iCont); + sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v); + sqlite3VdbeResolveLabel(v, iBreak); + sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0); + } + break; + } + default: assert( p->op==TK_INTERSECT ); { + int tab1, tab2; + int iCont, iBreak, iStart; + Expr *pLimit; + int addr; + SelectDest intersectdest; + int r1; + + /* INTERSECT is different from the others since it requires + ** two temporary tables. Hence it has its own case. Begin + ** by allocating the tables we will need. + */ + tab1 = pParse->nTab++; + tab2 = pParse->nTab++; + assert( p->pOrderBy==0 ); + + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0); + assert( p->addrOpenEphm[0] == -1 ); + p->addrOpenEphm[0] = addr; + findRightmost(p)->selFlags |= SF_UsesEphemeral; + assert( p->pEList ); + + /* Code the SELECTs to our left into temporary table "tab1". + */ + sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); + TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT left...\n")); + rc = sqlite3Select(pParse, pPrior, &intersectdest); + if( rc ){ + goto multi_select_end; + } + + /* Code the current SELECT into temporary table "tab2" + */ + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0); + assert( p->addrOpenEphm[1] == -1 ); + p->addrOpenEphm[1] = addr; + p->pPrior = 0; + pLimit = p->pLimit; + p->pLimit = 0; + intersectdest.iSDParm = tab2; + ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", + sqlite3SelectOpName(p->op))); + TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT right...\n")); + rc = sqlite3Select(pParse, p, &intersectdest); + testcase( rc!=SQLITE_OK ); + pDelete = p->pPrior; + p->pPrior = pPrior; + if( p->nSelectRow>pPrior->nSelectRow ){ + p->nSelectRow = pPrior->nSelectRow; + } + sqlite3ExprDelete(db, p->pLimit); + p->pLimit = pLimit; + + /* Generate code to take the intersection of the two temporary + ** tables. + */ + if( rc ) break; + assert( p->pEList ); + iBreak = sqlite3VdbeMakeLabel(pParse); + iCont = sqlite3VdbeMakeLabel(pParse); + computeLimitRegisters(pParse, p, iBreak); + sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v); + r1 = sqlite3GetTempReg(pParse); + iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1); + sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); + VdbeCoverage(v); + sqlite3ReleaseTempReg(pParse, r1); + selectInnerLoop(pParse, p, tab1, + 0, 0, &dest, iCont, iBreak); + sqlite3VdbeResolveLabel(v, iCont); + sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v); + sqlite3VdbeResolveLabel(v, iBreak); + sqlite3VdbeAddOp2(v, OP_Close, tab2, 0); + sqlite3VdbeAddOp2(v, OP_Close, tab1, 0); + break; + } + } + + #ifndef SQLITE_OMIT_EXPLAIN + if( p->pNext==0 ){ + ExplainQueryPlanPop(pParse); + } + #endif + } + if( pParse->nErr ) goto multi_select_end; + + /* Compute collating sequences used by + ** temporary tables needed to implement the compound select. + ** Attach the KeyInfo structure to all temporary tables. + ** + ** This section is run by the right-most SELECT statement only. + ** SELECT statements to the left always skip this part. The right-most + ** SELECT might also skip this part if it has no ORDER BY clause and + ** no temp tables are required. + */ + if( p->selFlags & SF_UsesEphemeral ){ + int i; /* Loop counter */ + KeyInfo *pKeyInfo; /* Collating sequence for the result set */ + Select *pLoop; /* For looping through SELECT statements */ + CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ + int nCol; /* Number of columns in result set */ + + assert( p->pNext==0 ); + assert( p->pEList!=0 ); + nCol = p->pEList->nExpr; + pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1); + if( !pKeyInfo ){ + rc = SQLITE_NOMEM_BKPT; + goto multi_select_end; + } + for(i=0, apColl=pKeyInfo->aColl; i<nCol; i++, apColl++){ + *apColl = multiSelectCollSeq(pParse, p, i); + if( 0==*apColl ){ + *apColl = db->pDfltColl; + } + } + + for(pLoop=p; pLoop; pLoop=pLoop->pPrior){ + for(i=0; i<2; i++){ + int addr = pLoop->addrOpenEphm[i]; + if( addr<0 ){ + /* If [0] is unused then [1] is also unused. So we can + ** always safely abort as soon as the first unused slot is found */ + assert( pLoop->addrOpenEphm[1]<0 ); + break; + } + sqlite3VdbeChangeP2(v, addr, nCol); + sqlite3VdbeChangeP4(v, addr, (char*)sqlite3KeyInfoRef(pKeyInfo), + P4_KEYINFO); + pLoop->addrOpenEphm[i] = -1; + } + } + sqlite3KeyInfoUnref(pKeyInfo); + } + +multi_select_end: + pDest->iSdst = dest.iSdst; + pDest->nSdst = dest.nSdst; + if( pDelete ){ + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3SelectDelete, + pDelete); + } + return rc; +} +#endif /* SQLITE_OMIT_COMPOUND_SELECT */ + +/* +** Error message for when two or more terms of a compound select have different +** size result sets. +*/ +SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){ + if( p->selFlags & SF_Values ){ + sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms"); + }else{ + sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s" + " do not have the same number of result columns", + sqlite3SelectOpName(p->op)); + } +} + +/* +** Code an output subroutine for a coroutine implementation of a +** SELECT statement. +** +** The data to be output is contained in pIn->iSdst. There are +** pIn->nSdst columns to be output. pDest is where the output should +** be sent. +** +** regReturn is the number of the register holding the subroutine +** return address. +** +** If regPrev>0 then it is the first register in a vector that +** records the previous output. mem[regPrev] is a flag that is false +** if there has been no previous output. If regPrev>0 then code is +** generated to suppress duplicates. pKeyInfo is used for comparing +** keys. +** +** If the LIMIT found in p->iLimit is reached, jump immediately to +** iBreak. +*/ +static int generateOutputSubroutine( + Parse *pParse, /* Parsing context */ + Select *p, /* The SELECT statement */ + SelectDest *pIn, /* Coroutine supplying data */ + SelectDest *pDest, /* Where to send the data */ + int regReturn, /* The return address register */ + int regPrev, /* Previous result register. No uniqueness if 0 */ + KeyInfo *pKeyInfo, /* For comparing with previous entry */ + int iBreak /* Jump here if we hit the LIMIT */ +){ + Vdbe *v = pParse->pVdbe; + int iContinue; + int addr; + + addr = sqlite3VdbeCurrentAddr(v); + iContinue = sqlite3VdbeMakeLabel(pParse); + + /* Suppress duplicates for UNION, EXCEPT, and INTERSECT + */ + if( regPrev ){ + int addr1, addr2; + addr1 = sqlite3VdbeAddOp1(v, OP_IfNot, regPrev); VdbeCoverage(v); + addr2 = sqlite3VdbeAddOp4(v, OP_Compare, pIn->iSdst, regPrev+1, pIn->nSdst, + (char*)sqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO); + sqlite3VdbeAddOp3(v, OP_Jump, addr2+2, iContinue, addr2+2); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addr1); + sqlite3VdbeAddOp3(v, OP_Copy, pIn->iSdst, regPrev+1, pIn->nSdst-1); + sqlite3VdbeAddOp2(v, OP_Integer, 1, regPrev); + } + if( pParse->db->mallocFailed ) return 0; + + /* Suppress the first OFFSET entries if there is an OFFSET clause + */ + codeOffset(v, p->iOffset, iContinue); + + assert( pDest->eDest!=SRT_Exists ); + assert( pDest->eDest!=SRT_Table ); + switch( pDest->eDest ){ + /* Store the result as data using a unique key. + */ + case SRT_EphemTab: { + int r1 = sqlite3GetTempReg(pParse); + int r2 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r1); + sqlite3VdbeAddOp2(v, OP_NewRowid, pDest->iSDParm, r2); + sqlite3VdbeAddOp3(v, OP_Insert, pDest->iSDParm, r1, r2); + sqlite3VdbeChangeP5(v, OPFLAG_APPEND); + sqlite3ReleaseTempReg(pParse, r2); + sqlite3ReleaseTempReg(pParse, r1); + break; + } + +#ifndef SQLITE_OMIT_SUBQUERY + /* If we are creating a set for an "expr IN (SELECT ...)". + */ + case SRT_Set: { + int r1; + testcase( pIn->nSdst>1 ); + r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, + r1, pDest->zAffSdst, pIn->nSdst); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pDest->iSDParm, r1, + pIn->iSdst, pIn->nSdst); + sqlite3ReleaseTempReg(pParse, r1); + break; + } + + /* If this is a scalar select that is part of an expression, then + ** store the results in the appropriate memory cell and break out + ** of the scan loop. Note that the select might return multiple columns + ** if it is the RHS of a row-value IN operator. + */ + case SRT_Mem: { + testcase( pIn->nSdst>1 ); + sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm, pIn->nSdst); + /* The LIMIT clause will jump out of the loop for us */ + break; + } +#endif /* #ifndef SQLITE_OMIT_SUBQUERY */ + + /* The results are stored in a sequence of registers + ** starting at pDest->iSdst. Then the co-routine yields. + */ + case SRT_Coroutine: { + if( pDest->iSdst==0 ){ + pDest->iSdst = sqlite3GetTempRange(pParse, pIn->nSdst); + pDest->nSdst = pIn->nSdst; + } + sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSdst, pIn->nSdst); + sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm); + break; + } + + /* If none of the above, then the result destination must be + ** SRT_Output. This routine is never called with any other + ** destination other than the ones handled above or SRT_Output. + ** + ** For SRT_Output, results are stored in a sequence of registers. + ** Then the OP_ResultRow opcode is used to cause sqlite3_step() to + ** return the next row of result. + */ + default: { + assert( pDest->eDest==SRT_Output ); + sqlite3VdbeAddOp2(v, OP_ResultRow, pIn->iSdst, pIn->nSdst); + break; + } + } + + /* Jump to the end of the loop if the LIMIT is reached. + */ + if( p->iLimit ){ + sqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, iBreak); VdbeCoverage(v); + } + + /* Generate the subroutine return + */ + sqlite3VdbeResolveLabel(v, iContinue); + sqlite3VdbeAddOp1(v, OP_Return, regReturn); + + return addr; +} + +/* +** Alternative compound select code generator for cases when there +** is an ORDER BY clause. +** +** We assume a query of the following form: +** +** <selectA> <operator> <selectB> ORDER BY <orderbylist> +** +** <operator> is one of UNION ALL, UNION, EXCEPT, or INTERSECT. The idea +** is to code both <selectA> and <selectB> with the ORDER BY clause as +** co-routines. Then run the co-routines in parallel and merge the results +** into the output. In addition to the two coroutines (called selectA and +** selectB) there are 7 subroutines: +** +** outA: Move the output of the selectA coroutine into the output +** of the compound query. +** +** outB: Move the output of the selectB coroutine into the output +** of the compound query. (Only generated for UNION and +** UNION ALL. EXCEPT and INSERTSECT never output a row that +** appears only in B.) +** +** AltB: Called when there is data from both coroutines and A<B. +** +** AeqB: Called when there is data from both coroutines and A==B. +** +** AgtB: Called when there is data from both coroutines and A>B. +** +** EofA: Called when data is exhausted from selectA. +** +** EofB: Called when data is exhausted from selectB. +** +** The implementation of the latter five subroutines depend on which +** <operator> is used: +** +** +** UNION ALL UNION EXCEPT INTERSECT +** ------------- ----------------- -------------- ----------------- +** AltB: outA, nextA outA, nextA outA, nextA nextA +** +** AeqB: outA, nextA nextA nextA outA, nextA +** +** AgtB: outB, nextB outB, nextB nextB nextB +** +** EofA: outB, nextB outB, nextB halt halt +** +** EofB: outA, nextA outA, nextA outA, nextA halt +** +** In the AltB, AeqB, and AgtB subroutines, an EOF on A following nextA +** causes an immediate jump to EofA and an EOF on B following nextB causes +** an immediate jump to EofB. Within EofA and EofB, and EOF on entry or +** following nextX causes a jump to the end of the select processing. +** +** Duplicate removal in the UNION, EXCEPT, and INTERSECT cases is handled +** within the output subroutine. The regPrev register set holds the previously +** output value. A comparison is made against this value and the output +** is skipped if the next results would be the same as the previous. +** +** The implementation plan is to implement the two coroutines and seven +** subroutines first, then put the control logic at the bottom. Like this: +** +** goto Init +** coA: coroutine for left query (A) +** coB: coroutine for right query (B) +** outA: output one row of A +** outB: output one row of B (UNION and UNION ALL only) +** EofA: ... +** EofB: ... +** AltB: ... +** AeqB: ... +** AgtB: ... +** Init: initialize coroutine registers +** yield coA +** if eof(A) goto EofA +** yield coB +** if eof(B) goto EofB +** Cmpr: Compare A, B +** Jump AltB, AeqB, AgtB +** End: ... +** +** We call AltB, AeqB, AgtB, EofA, and EofB "subroutines" but they are not +** actually called using Gosub and they do not Return. EofA and EofB loop +** until all data is exhausted then jump to the "end" label. AltB, AeqB, +** and AgtB jump to either L2 or to one of EofA or EofB. +*/ +#ifndef SQLITE_OMIT_COMPOUND_SELECT +static int multiSelectOrderBy( + Parse *pParse, /* Parsing context */ + Select *p, /* The right-most of SELECTs to be coded */ + SelectDest *pDest /* What to do with query results */ +){ + int i, j; /* Loop counters */ + Select *pPrior; /* Another SELECT immediately to our left */ + Select *pSplit; /* Left-most SELECT in the right-hand group */ + int nSelect; /* Number of SELECT statements in the compound */ + Vdbe *v; /* Generate code to this VDBE */ + SelectDest destA; /* Destination for coroutine A */ + SelectDest destB; /* Destination for coroutine B */ + int regAddrA; /* Address register for select-A coroutine */ + int regAddrB; /* Address register for select-B coroutine */ + int addrSelectA; /* Address of the select-A coroutine */ + int addrSelectB; /* Address of the select-B coroutine */ + int regOutA; /* Address register for the output-A subroutine */ + int regOutB; /* Address register for the output-B subroutine */ + int addrOutA; /* Address of the output-A subroutine */ + int addrOutB = 0; /* Address of the output-B subroutine */ + int addrEofA; /* Address of the select-A-exhausted subroutine */ + int addrEofA_noB; /* Alternate addrEofA if B is uninitialized */ + int addrEofB; /* Address of the select-B-exhausted subroutine */ + int addrAltB; /* Address of the A<B subroutine */ + int addrAeqB; /* Address of the A==B subroutine */ + int addrAgtB; /* Address of the A>B subroutine */ + int regLimitA; /* Limit register for select-A */ + int regLimitB; /* Limit register for select-A */ + int regPrev; /* A range of registers to hold previous output */ + int savedLimit; /* Saved value of p->iLimit */ + int savedOffset; /* Saved value of p->iOffset */ + int labelCmpr; /* Label for the start of the merge algorithm */ + int labelEnd; /* Label for the end of the overall SELECT stmt */ + int addr1; /* Jump instructions that get retargeted */ + int op; /* One of TK_ALL, TK_UNION, TK_EXCEPT, TK_INTERSECT */ + KeyInfo *pKeyDup = 0; /* Comparison information for duplicate removal */ + KeyInfo *pKeyMerge; /* Comparison information for merging rows */ + sqlite3 *db; /* Database connection */ + ExprList *pOrderBy; /* The ORDER BY clause */ + int nOrderBy; /* Number of terms in the ORDER BY clause */ + u32 *aPermute; /* Mapping from ORDER BY terms to result set columns */ + + assert( p->pOrderBy!=0 ); + assert( pKeyDup==0 ); /* "Managed" code needs this. Ticket #3382. */ + db = pParse->db; + v = pParse->pVdbe; + assert( v!=0 ); /* Already thrown the error if VDBE alloc failed */ + labelEnd = sqlite3VdbeMakeLabel(pParse); + labelCmpr = sqlite3VdbeMakeLabel(pParse); + + + /* Patch up the ORDER BY clause + */ + op = p->op; + assert( p->pPrior->pOrderBy==0 ); + pOrderBy = p->pOrderBy; + assert( pOrderBy ); + nOrderBy = pOrderBy->nExpr; + + /* For operators other than UNION ALL we have to make sure that + ** the ORDER BY clause covers every term of the result set. Add + ** terms to the ORDER BY clause as necessary. + */ + if( op!=TK_ALL ){ + for(i=1; db->mallocFailed==0 && i<=p->pEList->nExpr; i++){ + struct ExprList_item *pItem; + for(j=0, pItem=pOrderBy->a; j<nOrderBy; j++, pItem++){ + assert( pItem!=0 ); + assert( pItem->u.x.iOrderByCol>0 ); + if( pItem->u.x.iOrderByCol==i ) break; + } + if( j==nOrderBy ){ + Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); + if( pNew==0 ) return SQLITE_NOMEM_BKPT; + pNew->flags |= EP_IntValue; + pNew->u.iValue = i; + p->pOrderBy = pOrderBy = sqlite3ExprListAppend(pParse, pOrderBy, pNew); + if( pOrderBy ) pOrderBy->a[nOrderBy++].u.x.iOrderByCol = (u16)i; + } + } + } + + /* Compute the comparison permutation and keyinfo that is used with + ** the permutation used to determine if the next + ** row of results comes from selectA or selectB. Also add explicit + ** collations to the ORDER BY clause terms so that when the subqueries + ** to the right and the left are evaluated, they use the correct + ** collation. + */ + aPermute = sqlite3DbMallocRawNN(db, sizeof(u32)*(nOrderBy + 1)); + if( aPermute ){ + struct ExprList_item *pItem; + aPermute[0] = nOrderBy; + for(i=1, pItem=pOrderBy->a; i<=nOrderBy; i++, pItem++){ + assert( pItem!=0 ); + assert( pItem->u.x.iOrderByCol>0 ); + assert( pItem->u.x.iOrderByCol<=p->pEList->nExpr ); + aPermute[i] = pItem->u.x.iOrderByCol - 1; + } + pKeyMerge = multiSelectOrderByKeyInfo(pParse, p, 1); + }else{ + pKeyMerge = 0; + } + + /* Allocate a range of temporary registers and the KeyInfo needed + ** for the logic that removes duplicate result rows when the + ** operator is UNION, EXCEPT, or INTERSECT (but not UNION ALL). + */ + if( op==TK_ALL ){ + regPrev = 0; + }else{ + int nExpr = p->pEList->nExpr; + assert( nOrderBy>=nExpr || db->mallocFailed ); + regPrev = pParse->nMem+1; + pParse->nMem += nExpr+1; + sqlite3VdbeAddOp2(v, OP_Integer, 0, regPrev); + pKeyDup = sqlite3KeyInfoAlloc(db, nExpr, 1); + if( pKeyDup ){ + assert( sqlite3KeyInfoIsWriteable(pKeyDup) ); + for(i=0; i<nExpr; i++){ + pKeyDup->aColl[i] = multiSelectCollSeq(pParse, p, i); + pKeyDup->aSortFlags[i] = 0; + } + } + } + + /* Separate the left and the right query from one another + */ + nSelect = 1; + if( (op==TK_ALL || op==TK_UNION) + && OptimizationEnabled(db, SQLITE_BalancedMerge) + ){ + for(pSplit=p; pSplit->pPrior!=0 && pSplit->op==op; pSplit=pSplit->pPrior){ + nSelect++; + assert( pSplit->pPrior->pNext==pSplit ); + } + } + if( nSelect<=3 ){ + pSplit = p; + }else{ + pSplit = p; + for(i=2; i<nSelect; i+=2){ pSplit = pSplit->pPrior; } + } + pPrior = pSplit->pPrior; + assert( pPrior!=0 ); + pSplit->pPrior = 0; + pPrior->pNext = 0; + assert( p->pOrderBy == pOrderBy ); + assert( pOrderBy!=0 || db->mallocFailed ); + pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0); + sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER"); + sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER"); + + /* Compute the limit registers */ + computeLimitRegisters(pParse, p, labelEnd); + if( p->iLimit && op==TK_ALL ){ + regLimitA = ++pParse->nMem; + regLimitB = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Copy, p->iOffset ? p->iOffset+1 : p->iLimit, + regLimitA); + sqlite3VdbeAddOp2(v, OP_Copy, regLimitA, regLimitB); + }else{ + regLimitA = regLimitB = 0; + } + sqlite3ExprDelete(db, p->pLimit); + p->pLimit = 0; + + regAddrA = ++pParse->nMem; + regAddrB = ++pParse->nMem; + regOutA = ++pParse->nMem; + regOutB = ++pParse->nMem; + sqlite3SelectDestInit(&destA, SRT_Coroutine, regAddrA); + sqlite3SelectDestInit(&destB, SRT_Coroutine, regAddrB); + + ExplainQueryPlan((pParse, 1, "MERGE (%s)", sqlite3SelectOpName(p->op))); + + /* Generate a coroutine to evaluate the SELECT statement to the + ** left of the compound operator - the "A" select. + */ + addrSelectA = sqlite3VdbeCurrentAddr(v) + 1; + addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrA, 0, addrSelectA); + VdbeComment((v, "left SELECT")); + pPrior->iLimit = regLimitA; + ExplainQueryPlan((pParse, 1, "LEFT")); + sqlite3Select(pParse, pPrior, &destA); + sqlite3VdbeEndCoroutine(v, regAddrA); + sqlite3VdbeJumpHere(v, addr1); + + /* Generate a coroutine to evaluate the SELECT statement on + ** the right - the "B" select + */ + addrSelectB = sqlite3VdbeCurrentAddr(v) + 1; + addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrB, 0, addrSelectB); + VdbeComment((v, "right SELECT")); + savedLimit = p->iLimit; + savedOffset = p->iOffset; + p->iLimit = regLimitB; + p->iOffset = 0; + ExplainQueryPlan((pParse, 1, "RIGHT")); + sqlite3Select(pParse, p, &destB); + p->iLimit = savedLimit; + p->iOffset = savedOffset; + sqlite3VdbeEndCoroutine(v, regAddrB); + + /* Generate a subroutine that outputs the current row of the A + ** select as the next output row of the compound select. + */ + VdbeNoopComment((v, "Output routine for A")); + addrOutA = generateOutputSubroutine(pParse, + p, &destA, pDest, regOutA, + regPrev, pKeyDup, labelEnd); + + /* Generate a subroutine that outputs the current row of the B + ** select as the next output row of the compound select. + */ + if( op==TK_ALL || op==TK_UNION ){ + VdbeNoopComment((v, "Output routine for B")); + addrOutB = generateOutputSubroutine(pParse, + p, &destB, pDest, regOutB, + regPrev, pKeyDup, labelEnd); + } + sqlite3KeyInfoUnref(pKeyDup); + + /* Generate a subroutine to run when the results from select A + ** are exhausted and only data in select B remains. + */ + if( op==TK_EXCEPT || op==TK_INTERSECT ){ + addrEofA_noB = addrEofA = labelEnd; + }else{ + VdbeNoopComment((v, "eof-A subroutine")); + addrEofA = sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); + addrEofA_noB = sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, labelEnd); + VdbeCoverage(v); + sqlite3VdbeGoto(v, addrEofA); + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); + } + + /* Generate a subroutine to run when the results from select B + ** are exhausted and only data in select A remains. + */ + if( op==TK_INTERSECT ){ + addrEofB = addrEofA; + if( p->nSelectRow > pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow; + }else{ + VdbeNoopComment((v, "eof-B subroutine")); + addrEofB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, labelEnd); VdbeCoverage(v); + sqlite3VdbeGoto(v, addrEofB); + } + + /* Generate code to handle the case of A<B + */ + VdbeNoopComment((v, "A-lt-B subroutine")); + addrAltB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA); VdbeCoverage(v); + sqlite3VdbeGoto(v, labelCmpr); + + /* Generate code to handle the case of A==B + */ + if( op==TK_ALL ){ + addrAeqB = addrAltB; + }else if( op==TK_INTERSECT ){ + addrAeqB = addrAltB; + addrAltB++; + }else{ + VdbeNoopComment((v, "A-eq-B subroutine")); + addrAeqB = + sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA); VdbeCoverage(v); + sqlite3VdbeGoto(v, labelCmpr); + } + + /* Generate code to handle the case of A>B + */ + VdbeNoopComment((v, "A-gt-B subroutine")); + addrAgtB = sqlite3VdbeCurrentAddr(v); + if( op==TK_ALL || op==TK_UNION ){ + sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); + } + sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); + sqlite3VdbeGoto(v, labelCmpr); + + /* This code runs once to initialize everything. + */ + sqlite3VdbeJumpHere(v, addr1); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA_noB); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); + + /* Implement the main merge loop + */ + sqlite3VdbeResolveLabel(v, labelCmpr); + sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); + sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy, + (char*)pKeyMerge, P4_KEYINFO); + sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); + sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); VdbeCoverage(v); + + /* Jump to the this point in order to terminate the query. + */ + sqlite3VdbeResolveLabel(v, labelEnd); + + /* Make arrangements to free the 2nd and subsequent arms of the compound + ** after the parse has finished */ + if( pSplit->pPrior ){ + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3SelectDelete, pSplit->pPrior); + } + pSplit->pPrior = pPrior; + pPrior->pNext = pSplit; + sqlite3ExprListDelete(db, pPrior->pOrderBy); + pPrior->pOrderBy = 0; + + /*** TBD: Insert subroutine calls to close cursors on incomplete + **** subqueries ****/ + ExplainQueryPlanPop(pParse); + return pParse->nErr!=0; +} +#endif + +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) + +/* An instance of the SubstContext object describes an substitution edit +** to be performed on a parse tree. +** +** All references to columns in table iTable are to be replaced by corresponding +** expressions in pEList. +** +** ## About "isOuterJoin": +** +** The isOuterJoin column indicates that the replacement will occur into a +** position in the parent that NULL-able due to an OUTER JOIN. Either the +** target slot in the parent is the right operand of a LEFT JOIN, or one of +** the left operands of a RIGHT JOIN. In either case, we need to potentially +** bypass the substituted expression with OP_IfNullRow. +** +** Suppose the original expression is an integer constant. Even though the table +** has the nullRow flag set, because the expression is an integer constant, +** it will not be NULLed out. So instead, we insert an OP_IfNullRow opcode +** that checks to see if the nullRow flag is set on the table. If the nullRow +** flag is set, then the value in the register is set to NULL and the original +** expression is bypassed. If the nullRow flag is not set, then the original +** expression runs to populate the register. +** +** Example where this is needed: +** +** CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT); +** CREATE TABLE t2(x INT UNIQUE); +** +** SELECT a,b,m,x FROM t1 LEFT JOIN (SELECT 59 AS m,x FROM t2) ON b=x; +** +** When the subquery on the right side of the LEFT JOIN is flattened, we +** have to add OP_IfNullRow in front of the OP_Integer that implements the +** "m" value of the subquery so that a NULL will be loaded instead of 59 +** when processing a non-matched row of the left. +*/ +typedef struct SubstContext { + Parse *pParse; /* The parsing context */ + int iTable; /* Replace references to this table */ + int iNewTable; /* New table number */ + int isOuterJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */ + ExprList *pEList; /* Replacement expressions */ + ExprList *pCList; /* Collation sequences for replacement expr */ +} SubstContext; + +/* Forward Declarations */ +static void substExprList(SubstContext*, ExprList*); +static void substSelect(SubstContext*, Select*, int); + +/* +** Scan through the expression pExpr. Replace every reference to +** a column in table number iTable with a copy of the iColumn-th +** entry in pEList. (But leave references to the ROWID column +** unchanged.) +** +** This routine is part of the flattening procedure. A subquery +** whose result set is defined by pEList appears as entry in the +** FROM clause of a SELECT such that the VDBE cursor assigned to that +** FORM clause entry is iTable. This routine makes the necessary +** changes to pExpr so that it refers directly to the source table +** of the subquery rather the result set of the subquery. +*/ +static Expr *substExpr( + SubstContext *pSubst, /* Description of the substitution */ + Expr *pExpr /* Expr in which substitution occurs */ +){ + if( pExpr==0 ) return 0; + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) + && pExpr->w.iJoin==pSubst->iTable + ){ + testcase( ExprHasProperty(pExpr, EP_InnerON) ); + pExpr->w.iJoin = pSubst->iNewTable; + } + if( pExpr->op==TK_COLUMN + && pExpr->iTable==pSubst->iTable + && !ExprHasProperty(pExpr, EP_FixedCol) + ){ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( pExpr->iColumn<0 ){ + pExpr->op = TK_NULL; + }else +#endif + { + Expr *pNew; + int iColumn; + Expr *pCopy; + Expr ifNullRow; + iColumn = pExpr->iColumn; + assert( iColumn>=0 ); + assert( pSubst->pEList!=0 && iColumn<pSubst->pEList->nExpr ); + assert( pExpr->pRight==0 ); + pCopy = pSubst->pEList->a[iColumn].pExpr; + if( sqlite3ExprIsVector(pCopy) ){ + sqlite3VectorErrorMsg(pSubst->pParse, pCopy); + }else{ + sqlite3 *db = pSubst->pParse->db; + if( pSubst->isOuterJoin + && (pCopy->op!=TK_COLUMN || pCopy->iTable!=pSubst->iNewTable) + ){ + memset(&ifNullRow, 0, sizeof(ifNullRow)); + ifNullRow.op = TK_IF_NULL_ROW; + ifNullRow.pLeft = pCopy; + ifNullRow.iTable = pSubst->iNewTable; + ifNullRow.iColumn = -99; + ifNullRow.flags = EP_IfNullRow; + pCopy = &ifNullRow; + } + testcase( ExprHasProperty(pCopy, EP_Subquery) ); + pNew = sqlite3ExprDup(db, pCopy, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pNew); + return pExpr; + } + if( pSubst->isOuterJoin ){ + ExprSetProperty(pNew, EP_CanBeNull); + } + if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){ + sqlite3SetJoinExpr(pNew, pExpr->w.iJoin, + pExpr->flags & (EP_OuterON|EP_InnerON)); + } + sqlite3ExprDelete(db, pExpr); + pExpr = pNew; + if( pExpr->op==TK_TRUEFALSE ){ + pExpr->u.iValue = sqlite3ExprTruthValue(pExpr); + pExpr->op = TK_INTEGER; + ExprSetProperty(pExpr, EP_IntValue); + } + + /* Ensure that the expression now has an implicit collation sequence, + ** just as it did when it was a column of a view or sub-query. */ + { + CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pExpr); + CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, + pSubst->pCList->a[iColumn].pExpr + ); + if( pNat!=pColl || (pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE) ){ + pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr, + (pColl ? pColl->zName : "BINARY") + ); + } + } + ExprClearProperty(pExpr, EP_Collate); + } + } + }else{ + if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){ + pExpr->iTable = pSubst->iNewTable; + } + pExpr->pLeft = substExpr(pSubst, pExpr->pLeft); + pExpr->pRight = substExpr(pSubst, pExpr->pRight); + if( ExprUseXSelect(pExpr) ){ + substSelect(pSubst, pExpr->x.pSelect, 1); + }else{ + substExprList(pSubst, pExpr->x.pList); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + Window *pWin = pExpr->y.pWin; + pWin->pFilter = substExpr(pSubst, pWin->pFilter); + substExprList(pSubst, pWin->pPartition); + substExprList(pSubst, pWin->pOrderBy); + } +#endif + } + return pExpr; +} +static void substExprList( + SubstContext *pSubst, /* Description of the substitution */ + ExprList *pList /* List to scan and in which to make substitutes */ +){ + int i; + if( pList==0 ) return; + for(i=0; i<pList->nExpr; i++){ + pList->a[i].pExpr = substExpr(pSubst, pList->a[i].pExpr); + } +} +static void substSelect( + SubstContext *pSubst, /* Description of the substitution */ + Select *p, /* SELECT statement in which to make substitutions */ + int doPrior /* Do substitutes on p->pPrior too */ +){ + SrcList *pSrc; + SrcItem *pItem; + int i; + if( !p ) return; + do{ + substExprList(pSubst, p->pEList); + substExprList(pSubst, p->pGroupBy); + substExprList(pSubst, p->pOrderBy); + p->pHaving = substExpr(pSubst, p->pHaving); + p->pWhere = substExpr(pSubst, p->pWhere); + pSrc = p->pSrc; + assert( pSrc!=0 ); + for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ + substSelect(pSubst, pItem->pSelect, 1); + if( pItem->fg.isTabFunc ){ + substExprList(pSubst, pItem->u1.pFuncArg); + } + } + }while( doPrior && (p = p->pPrior)!=0 ); +} +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) +/* +** pSelect is a SELECT statement and pSrcItem is one item in the FROM +** clause of that SELECT. +** +** This routine scans the entire SELECT statement and recomputes the +** pSrcItem->colUsed mask. +*/ +static int recomputeColumnsUsedExpr(Walker *pWalker, Expr *pExpr){ + SrcItem *pItem; + if( pExpr->op!=TK_COLUMN ) return WRC_Continue; + pItem = pWalker->u.pSrcItem; + if( pItem->iCursor!=pExpr->iTable ) return WRC_Continue; + if( pExpr->iColumn<0 ) return WRC_Continue; + pItem->colUsed |= sqlite3ExprColUsed(pExpr); + return WRC_Continue; +} +static void recomputeColumnsUsed( + Select *pSelect, /* The complete SELECT statement */ + SrcItem *pSrcItem /* Which FROM clause item to recompute */ +){ + Walker w; + if( NEVER(pSrcItem->pTab==0) ) return; + memset(&w, 0, sizeof(w)); + w.xExprCallback = recomputeColumnsUsedExpr; + w.xSelectCallback = sqlite3SelectWalkNoop; + w.u.pSrcItem = pSrcItem; + pSrcItem->colUsed = 0; + sqlite3WalkSelect(&w, pSelect); +} +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) +/* +** Assign new cursor numbers to each of the items in pSrc. For each +** new cursor number assigned, set an entry in the aCsrMap[] array +** to map the old cursor number to the new: +** +** aCsrMap[iOld+1] = iNew; +** +** The array is guaranteed by the caller to be large enough for all +** existing cursor numbers in pSrc. aCsrMap[0] is the array size. +** +** If pSrc contains any sub-selects, call this routine recursively +** on the FROM clause of each such sub-select, with iExcept set to -1. +*/ +static void srclistRenumberCursors( + Parse *pParse, /* Parse context */ + int *aCsrMap, /* Array to store cursor mappings in */ + SrcList *pSrc, /* FROM clause to renumber */ + int iExcept /* FROM clause item to skip */ +){ + int i; + SrcItem *pItem; + for(i=0, pItem=pSrc->a; i<pSrc->nSrc; i++, pItem++){ + if( i!=iExcept ){ + Select *p; + assert( pItem->iCursor < aCsrMap[0] ); + if( !pItem->fg.isRecursive || aCsrMap[pItem->iCursor+1]==0 ){ + aCsrMap[pItem->iCursor+1] = pParse->nTab++; + } + pItem->iCursor = aCsrMap[pItem->iCursor+1]; + for(p=pItem->pSelect; p; p=p->pPrior){ + srclistRenumberCursors(pParse, aCsrMap, p->pSrc, -1); + } + } + } +} + +/* +** *piCursor is a cursor number. Change it if it needs to be mapped. +*/ +static void renumberCursorDoMapping(Walker *pWalker, int *piCursor){ + int *aCsrMap = pWalker->u.aiCol; + int iCsr = *piCursor; + if( iCsr < aCsrMap[0] && aCsrMap[iCsr+1]>0 ){ + *piCursor = aCsrMap[iCsr+1]; + } +} + +/* +** Expression walker callback used by renumberCursors() to update +** Expr objects to match newly assigned cursor numbers. +*/ +static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){ + int op = pExpr->op; + if( op==TK_COLUMN || op==TK_IF_NULL_ROW ){ + renumberCursorDoMapping(pWalker, &pExpr->iTable); + } + if( ExprHasProperty(pExpr, EP_OuterON) ){ + renumberCursorDoMapping(pWalker, &pExpr->w.iJoin); + } + return WRC_Continue; +} + +/* +** Assign a new cursor number to each cursor in the FROM clause (Select.pSrc) +** of the SELECT statement passed as the second argument, and to each +** cursor in the FROM clause of any FROM clause sub-selects, recursively. +** Except, do not assign a new cursor number to the iExcept'th element in +** the FROM clause of (*p). Update all expressions and other references +** to refer to the new cursor numbers. +** +** Argument aCsrMap is an array that may be used for temporary working +** space. Two guarantees are made by the caller: +** +** * the array is larger than the largest cursor number used within the +** select statement passed as an argument, and +** +** * the array entries for all cursor numbers that do *not* appear in +** FROM clauses of the select statement as described above are +** initialized to zero. +*/ +static void renumberCursors( + Parse *pParse, /* Parse context */ + Select *p, /* Select to renumber cursors within */ + int iExcept, /* FROM clause item to skip */ + int *aCsrMap /* Working space */ +){ + Walker w; + srclistRenumberCursors(pParse, aCsrMap, p->pSrc, iExcept); + memset(&w, 0, sizeof(w)); + w.u.aiCol = aCsrMap; + w.xExprCallback = renumberCursorsCb; + w.xSelectCallback = sqlite3SelectWalkNoop; + sqlite3WalkSelect(&w, p); +} +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + +/* +** If pSel is not part of a compound SELECT, return a pointer to its +** expression list. Otherwise, return a pointer to the expression list +** of the leftmost SELECT in the compound. +*/ +static ExprList *findLeftmostExprlist(Select *pSel){ + while( pSel->pPrior ){ + pSel = pSel->pPrior; + } + return pSel->pEList; +} + +/* +** Return true if any of the result-set columns in the compound query +** have incompatible affinities on one or more arms of the compound. +*/ +static int compoundHasDifferentAffinities(Select *p){ + int ii; + ExprList *pList; + assert( p!=0 ); + assert( p->pEList!=0 ); + assert( p->pPrior!=0 ); + pList = p->pEList; + for(ii=0; ii<pList->nExpr; ii++){ + char aff; + Select *pSub1; + assert( pList->a[ii].pExpr!=0 ); + aff = sqlite3ExprAffinity(pList->a[ii].pExpr); + for(pSub1=p->pPrior; pSub1; pSub1=pSub1->pPrior){ + assert( pSub1->pEList!=0 ); + assert( pSub1->pEList->nExpr>ii ); + assert( pSub1->pEList->a[ii].pExpr!=0 ); + if( sqlite3ExprAffinity(pSub1->pEList->a[ii].pExpr)!=aff ){ + return 1; + } + } + } + return 0; +} + +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) +/* +** This routine attempts to flatten subqueries as a performance optimization. +** This routine returns 1 if it makes changes and 0 if no flattening occurs. +** +** To understand the concept of flattening, consider the following +** query: +** +** SELECT a FROM (SELECT x+y AS a FROM t1 WHERE z<100) WHERE a>5 +** +** The default way of implementing this query is to execute the +** subquery first and store the results in a temporary table, then +** run the outer query on that temporary table. This requires two +** passes over the data. Furthermore, because the temporary table +** has no indices, the WHERE clause on the outer query cannot be +** optimized. +** +** This routine attempts to rewrite queries such as the above into +** a single flat select, like this: +** +** SELECT x+y AS a FROM t1 WHERE z<100 AND a>5 +** +** The code generated for this simplification gives the same result +** but only has to scan the data once. And because indices might +** exist on the table t1, a complete scan of the data might be +** avoided. +** +** Flattening is subject to the following constraints: +** +** (**) We no longer attempt to flatten aggregate subqueries. Was: +** The subquery and the outer query cannot both be aggregates. +** +** (**) We no longer attempt to flatten aggregate subqueries. Was: +** (2) If the subquery is an aggregate then +** (2a) the outer query must not be a join and +** (2b) the outer query must not use subqueries +** other than the one FROM-clause subquery that is a candidate +** for flattening. (This is due to ticket [2f7170d73bf9abf80] +** from 2015-02-09.) +** +** (3) If the subquery is the right operand of a LEFT JOIN then +** (3a) the subquery may not be a join and +** (3b) the FROM clause of the subquery may not contain a virtual +** table and +** (**) Was: "The outer query may not have a GROUP BY." This case +** is now managed correctly +** (3d) the outer query may not be DISTINCT. +** See also (26) for restrictions on RIGHT JOIN. +** +** (4) The subquery can not be DISTINCT. +** +** (**) At one point restrictions (4) and (5) defined a subset of DISTINCT +** sub-queries that were excluded from this optimization. Restriction +** (4) has since been expanded to exclude all DISTINCT subqueries. +** +** (**) We no longer attempt to flatten aggregate subqueries. Was: +** If the subquery is aggregate, the outer query may not be DISTINCT. +** +** (7) The subquery must have a FROM clause. TODO: For subqueries without +** A FROM clause, consider adding a FROM clause with the special +** table sqlite_once that consists of a single row containing a +** single NULL. +** +** (8) If the subquery uses LIMIT then the outer query may not be a join. +** +** (9) If the subquery uses LIMIT then the outer query may not be aggregate. +** +** (**) Restriction (10) was removed from the code on 2005-02-05 but we +** accidentally carried the comment forward until 2014-09-15. Original +** constraint: "If the subquery is aggregate then the outer query +** may not use LIMIT." +** +** (11) The subquery and the outer query may not both have ORDER BY clauses. +** +** (**) Not implemented. Subsumed into restriction (3). Was previously +** a separate restriction deriving from ticket #350. +** +** (13) The subquery and outer query may not both use LIMIT. +** +** (14) The subquery may not use OFFSET. +** +** (15) If the outer query is part of a compound select, then the +** subquery may not use LIMIT. +** (See ticket #2339 and ticket [02a8e81d44]). +** +** (16) If the outer query is aggregate, then the subquery may not +** use ORDER BY. (Ticket #2942) This used to not matter +** until we introduced the group_concat() function. +** +** (17) If the subquery is a compound select, then +** (17a) all compound operators must be a UNION ALL, and +** (17b) no terms within the subquery compound may be aggregate +** or DISTINCT, and +** (17c) every term within the subquery compound must have a FROM clause +** (17d) the outer query may not be +** (17d1) aggregate, or +** (17d2) DISTINCT +** (17e) the subquery may not contain window functions, and +** (17f) the subquery must not be the RHS of a LEFT JOIN. +** (17g) either the subquery is the first element of the outer +** query or there are no RIGHT or FULL JOINs in any arm +** of the subquery. (This is a duplicate of condition (27b).) +** (17h) The corresponding result set expressions in all arms of the +** compound must have the same affinity. +** +** The parent and sub-query may contain WHERE clauses. Subject to +** rules (11), (13) and (14), they may also contain ORDER BY, +** LIMIT and OFFSET clauses. The subquery cannot use any compound +** operator other than UNION ALL because all the other compound +** operators have an implied DISTINCT which is disallowed by +** restriction (4). +** +** Also, each component of the sub-query must return the same number +** of result columns. This is actually a requirement for any compound +** SELECT statement, but all the code here does is make sure that no +** such (illegal) sub-query is flattened. The caller will detect the +** syntax error and return a detailed message. +** +** (18) If the sub-query is a compound select, then all terms of the +** ORDER BY clause of the parent must be copies of a term returned +** by the parent query. +** +** (19) If the subquery uses LIMIT then the outer query may not +** have a WHERE clause. +** +** (20) If the sub-query is a compound select, then it must not use +** an ORDER BY clause. Ticket #3773. We could relax this constraint +** somewhat by saying that the terms of the ORDER BY clause must +** appear as unmodified result columns in the outer query. But we +** have other optimizations in mind to deal with that case. +** +** (21) If the subquery uses LIMIT then the outer query may not be +** DISTINCT. (See ticket [752e1646fc]). +** +** (22) The subquery may not be a recursive CTE. +** +** (23) If the outer query is a recursive CTE, then the sub-query may not be +** a compound query. This restriction is because transforming the +** parent to a compound query confuses the code that handles +** recursive queries in multiSelect(). +** +** (**) We no longer attempt to flatten aggregate subqueries. Was: +** The subquery may not be an aggregate that uses the built-in min() or +** or max() functions. (Without this restriction, a query like: +** "SELECT x FROM (SELECT max(y), x FROM t1)" would not necessarily +** return the value X for which Y was maximal.) +** +** (25) If either the subquery or the parent query contains a window +** function in the select list or ORDER BY clause, flattening +** is not attempted. +** +** (26) The subquery may not be the right operand of a RIGHT JOIN. +** See also (3) for restrictions on LEFT JOIN. +** +** (27) The subquery may not contain a FULL or RIGHT JOIN unless it +** is the first element of the parent query. Two subcases: +** (27a) the subquery is not a compound query. +** (27b) the subquery is a compound query and the RIGHT JOIN occurs +** in any arm of the compound query. (See also (17g).) +** +** (28) The subquery is not a MATERIALIZED CTE. (This is handled +** in the caller before ever reaching this routine.) +** +** +** In this routine, the "p" parameter is a pointer to the outer query. +** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query +** uses aggregates. +** +** If flattening is not attempted, this routine is a no-op and returns 0. +** If flattening is attempted this routine returns 1. +** +** All of the expression analysis must occur on both the outer query and +** the subquery before this routine runs. +*/ +static int flattenSubquery( + Parse *pParse, /* Parsing context */ + Select *p, /* The parent or outer SELECT statement */ + int iFrom, /* Index in p->pSrc->a[] of the inner subquery */ + int isAgg /* True if outer SELECT uses aggregate functions */ +){ + const char *zSavedAuthContext = pParse->zAuthContext; + Select *pParent; /* Current UNION ALL term of the other query */ + Select *pSub; /* The inner query or "subquery" */ + Select *pSub1; /* Pointer to the rightmost select in sub-query */ + SrcList *pSrc; /* The FROM clause of the outer query */ + SrcList *pSubSrc; /* The FROM clause of the subquery */ + int iParent; /* VDBE cursor number of the pSub result set temp table */ + int iNewParent = -1;/* Replacement table for iParent */ + int isOuterJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ + int i; /* Loop counter */ + Expr *pWhere; /* The WHERE clause */ + SrcItem *pSubitem; /* The subquery */ + sqlite3 *db = pParse->db; + Walker w; /* Walker to persist agginfo data */ + int *aCsrMap = 0; + + /* Check to see if flattening is permitted. Return 0 if not. + */ + assert( p!=0 ); + assert( p->pPrior==0 ); + if( OptimizationDisabled(db, SQLITE_QueryFlattener) ) return 0; + pSrc = p->pSrc; + assert( pSrc && iFrom>=0 && iFrom<pSrc->nSrc ); + pSubitem = &pSrc->a[iFrom]; + iParent = pSubitem->iCursor; + pSub = pSubitem->pSelect; + assert( pSub!=0 ); + +#ifndef SQLITE_OMIT_WINDOWFUNC + if( p->pWin || pSub->pWin ) return 0; /* Restriction (25) */ +#endif + + pSubSrc = pSub->pSrc; + assert( pSubSrc ); + /* Prior to version 3.1.2, when LIMIT and OFFSET had to be simple constants, + ** not arbitrary expressions, we allowed some combining of LIMIT and OFFSET + ** because they could be computed at compile-time. But when LIMIT and OFFSET + ** became arbitrary expressions, we were forced to add restrictions (13) + ** and (14). */ + if( pSub->pLimit && p->pLimit ) return 0; /* Restriction (13) */ + if( pSub->pLimit && pSub->pLimit->pRight ) return 0; /* Restriction (14) */ + if( (p->selFlags & SF_Compound)!=0 && pSub->pLimit ){ + return 0; /* Restriction (15) */ + } + if( pSubSrc->nSrc==0 ) return 0; /* Restriction (7) */ + if( pSub->selFlags & SF_Distinct ) return 0; /* Restriction (4) */ + if( pSub->pLimit && (pSrc->nSrc>1 || isAgg) ){ + return 0; /* Restrictions (8)(9) */ + } + if( p->pOrderBy && pSub->pOrderBy ){ + return 0; /* Restriction (11) */ + } + if( isAgg && pSub->pOrderBy ) return 0; /* Restriction (16) */ + if( pSub->pLimit && p->pWhere ) return 0; /* Restriction (19) */ + if( pSub->pLimit && (p->selFlags & SF_Distinct)!=0 ){ + return 0; /* Restriction (21) */ + } + if( pSub->selFlags & (SF_Recursive) ){ + return 0; /* Restrictions (22) */ + } + + /* + ** If the subquery is the right operand of a LEFT JOIN, then the + ** subquery may not be a join itself (3a). Example of why this is not + ** allowed: + ** + ** t1 LEFT OUTER JOIN (t2 JOIN t3) + ** + ** If we flatten the above, we would get + ** + ** (t1 LEFT OUTER JOIN t2) JOIN t3 + ** + ** which is not at all the same thing. + ** + ** See also tickets #306, #350, and #3300. + */ + if( (pSubitem->fg.jointype & (JT_OUTER|JT_LTORJ))!=0 ){ + if( pSubSrc->nSrc>1 /* (3a) */ + || IsVirtual(pSubSrc->a[0].pTab) /* (3b) */ + || (p->selFlags & SF_Distinct)!=0 /* (3d) */ + || (pSubitem->fg.jointype & JT_RIGHT)!=0 /* (26) */ + ){ + return 0; + } + isOuterJoin = 1; + } + + assert( pSubSrc->nSrc>0 ); /* True by restriction (7) */ + if( iFrom>0 && (pSubSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + return 0; /* Restriction (27a) */ + } + + /* Condition (28) is blocked by the caller */ + assert( !pSubitem->fg.isCte || pSubitem->u2.pCteUse->eM10d!=M10d_Yes ); + + /* Restriction (17): If the sub-query is a compound SELECT, then it must + ** use only the UNION ALL operator. And none of the simple select queries + ** that make up the compound SELECT are allowed to be aggregate or distinct + ** queries. + */ + if( pSub->pPrior ){ + int ii; + if( pSub->pOrderBy ){ + return 0; /* Restriction (20) */ + } + if( isAgg || (p->selFlags & SF_Distinct)!=0 || isOuterJoin>0 ){ + return 0; /* (17d1), (17d2), or (17f) */ + } + for(pSub1=pSub; pSub1; pSub1=pSub1->pPrior){ + testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct ); + testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Aggregate ); + assert( pSub->pSrc!=0 ); + assert( (pSub->selFlags & SF_Recursive)==0 ); + assert( pSub->pEList->nExpr==pSub1->pEList->nExpr ); + if( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))!=0 /* (17b) */ + || (pSub1->pPrior && pSub1->op!=TK_ALL) /* (17a) */ + || pSub1->pSrc->nSrc<1 /* (17c) */ +#ifndef SQLITE_OMIT_WINDOWFUNC + || pSub1->pWin /* (17e) */ +#endif + ){ + return 0; + } + if( iFrom>0 && (pSub1->pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + /* Without this restriction, the JT_LTORJ flag would end up being + ** omitted on left-hand tables of the right join that is being + ** flattened. */ + return 0; /* Restrictions (17g), (27b) */ + } + testcase( pSub1->pSrc->nSrc>1 ); + } + + /* Restriction (18). */ + if( p->pOrderBy ){ + for(ii=0; ii<p->pOrderBy->nExpr; ii++){ + if( p->pOrderBy->a[ii].u.x.iOrderByCol==0 ) return 0; + } + } + + /* Restriction (23) */ + if( (p->selFlags & SF_Recursive) ) return 0; + + /* Restriction (17h) */ + if( compoundHasDifferentAffinities(pSub) ) return 0; + + if( pSrc->nSrc>1 ){ + if( pParse->nSelect>500 ) return 0; + if( OptimizationDisabled(db, SQLITE_FlttnUnionAll) ) return 0; + aCsrMap = sqlite3DbMallocZero(db, ((i64)pParse->nTab+1)*sizeof(int)); + if( aCsrMap ) aCsrMap[0] = pParse->nTab; + } + } + + /***** If we reach this point, flattening is permitted. *****/ + TREETRACE(0x4,pParse,p,("flatten %u.%p from term %d\n", + pSub->selId, pSub, iFrom)); + + /* Authorize the subquery */ + pParse->zAuthContext = pSubitem->zName; + TESTONLY(i =) sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0); + testcase( i==SQLITE_DENY ); + pParse->zAuthContext = zSavedAuthContext; + + /* Delete the transient structures associated with the subquery */ + pSub1 = pSubitem->pSelect; + sqlite3DbFree(db, pSubitem->zDatabase); + sqlite3DbFree(db, pSubitem->zName); + sqlite3DbFree(db, pSubitem->zAlias); + pSubitem->zDatabase = 0; + pSubitem->zName = 0; + pSubitem->zAlias = 0; + pSubitem->pSelect = 0; + assert( pSubitem->fg.isUsing!=0 || pSubitem->u3.pOn==0 ); + + /* If the sub-query is a compound SELECT statement, then (by restrictions + ** 17 and 18 above) it must be a UNION ALL and the parent query must + ** be of the form: + ** + ** SELECT <expr-list> FROM (<sub-query>) <where-clause> + ** + ** followed by any ORDER BY, LIMIT and/or OFFSET clauses. This block + ** creates N-1 copies of the parent query without any ORDER BY, LIMIT or + ** OFFSET clauses and joins them to the left-hand-side of the original + ** using UNION ALL operators. In this case N is the number of simple + ** select statements in the compound sub-query. + ** + ** Example: + ** + ** SELECT a+1 FROM ( + ** SELECT x FROM tab + ** UNION ALL + ** SELECT y FROM tab + ** UNION ALL + ** SELECT abs(z*2) FROM tab2 + ** ) WHERE a!=5 ORDER BY 1 + ** + ** Transformed into: + ** + ** SELECT x+1 FROM tab WHERE x+1!=5 + ** UNION ALL + ** SELECT y+1 FROM tab WHERE y+1!=5 + ** UNION ALL + ** SELECT abs(z*2)+1 FROM tab2 WHERE abs(z*2)+1!=5 + ** ORDER BY 1 + ** + ** We call this the "compound-subquery flattening". + */ + for(pSub=pSub->pPrior; pSub; pSub=pSub->pPrior){ + Select *pNew; + ExprList *pOrderBy = p->pOrderBy; + Expr *pLimit = p->pLimit; + Select *pPrior = p->pPrior; + Table *pItemTab = pSubitem->pTab; + pSubitem->pTab = 0; + p->pOrderBy = 0; + p->pPrior = 0; + p->pLimit = 0; + pNew = sqlite3SelectDup(db, p, 0); + p->pLimit = pLimit; + p->pOrderBy = pOrderBy; + p->op = TK_ALL; + pSubitem->pTab = pItemTab; + if( pNew==0 ){ + p->pPrior = pPrior; + }else{ + pNew->selId = ++pParse->nSelect; + if( aCsrMap && ALWAYS(db->mallocFailed==0) ){ + renumberCursors(pParse, pNew, iFrom, aCsrMap); + } + pNew->pPrior = pPrior; + if( pPrior ) pPrior->pNext = pNew; + pNew->pNext = p; + p->pPrior = pNew; + TREETRACE(0x4,pParse,p,("compound-subquery flattener" + " creates %u as peer\n",pNew->selId)); + } + assert( pSubitem->pSelect==0 ); + } + sqlite3DbFree(db, aCsrMap); + if( db->mallocFailed ){ + pSubitem->pSelect = pSub1; + return 1; + } + + /* Defer deleting the Table object associated with the + ** subquery until code generation is + ** complete, since there may still exist Expr.pTab entries that + ** refer to the subquery even after flattening. Ticket #3346. + ** + ** pSubitem->pTab is always non-NULL by test restrictions and tests above. + */ + if( ALWAYS(pSubitem->pTab!=0) ){ + Table *pTabToDel = pSubitem->pTab; + if( pTabToDel->nTabRef==1 ){ + Parse *pToplevel = sqlite3ParseToplevel(pParse); + sqlite3ParserAddCleanup(pToplevel, + (void(*)(sqlite3*,void*))sqlite3DeleteTable, + pTabToDel); + testcase( pToplevel->earlyCleanup ); + }else{ + pTabToDel->nTabRef--; + } + pSubitem->pTab = 0; + } + + /* The following loop runs once for each term in a compound-subquery + ** flattening (as described above). If we are doing a different kind + ** of flattening - a flattening other than a compound-subquery flattening - + ** then this loop only runs once. + ** + ** This loop moves all of the FROM elements of the subquery into the + ** the FROM clause of the outer query. Before doing this, remember + ** the cursor number for the original outer query FROM element in + ** iParent. The iParent cursor will never be used. Subsequent code + ** will scan expressions looking for iParent references and replace + ** those references with expressions that resolve to the subquery FROM + ** elements we are now copying in. + */ + pSub = pSub1; + for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){ + int nSubSrc; + u8 jointype = 0; + u8 ltorj = pSrc->a[iFrom].fg.jointype & JT_LTORJ; + assert( pSub!=0 ); + pSubSrc = pSub->pSrc; /* FROM clause of subquery */ + nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */ + pSrc = pParent->pSrc; /* FROM clause of the outer query */ + + if( pParent==p ){ + jointype = pSubitem->fg.jointype; /* First time through the loop */ + } + + /* The subquery uses a single slot of the FROM clause of the outer + ** query. If the subquery has more than one element in its FROM clause, + ** then expand the outer query to make space for it to hold all elements + ** of the subquery. + ** + ** Example: + ** + ** SELECT * FROM tabA, (SELECT * FROM sub1, sub2), tabB; + ** + ** The outer query has 3 slots in its FROM clause. One slot of the + ** outer query (the middle slot) is used by the subquery. The next + ** block of code will expand the outer query FROM clause to 4 slots. + ** The middle slot is expanded to two slots in order to make space + ** for the two elements in the FROM clause of the subquery. + */ + if( nSubSrc>1 ){ + pSrc = sqlite3SrcListEnlarge(pParse, pSrc, nSubSrc-1,iFrom+1); + if( pSrc==0 ) break; + pParent->pSrc = pSrc; + } + + /* Transfer the FROM clause terms from the subquery into the + ** outer query. + */ + for(i=0; i<nSubSrc; i++){ + SrcItem *pItem = &pSrc->a[i+iFrom]; + if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); + assert( pItem->fg.isTabFunc==0 ); + *pItem = pSubSrc->a[i]; + pItem->fg.jointype |= ltorj; + iNewParent = pSubSrc->a[i].iCursor; + memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); + } + pSrc->a[iFrom].fg.jointype &= JT_LTORJ; + pSrc->a[iFrom].fg.jointype |= jointype | ltorj; + + /* Now begin substituting subquery result set expressions for + ** references to the iParent in the outer query. + ** + ** Example: + ** + ** SELECT a+5, b*10 FROM (SELECT x*3 AS a, y+10 AS b FROM t1) WHERE a>b; + ** \ \_____________ subquery __________/ / + ** \_____________________ outer query ______________________________/ + ** + ** We look at every expression in the outer query and every place we see + ** "a" we substitute "x*3" and every place we see "b" we substitute "y+10". + */ + if( pSub->pOrderBy && (pParent->selFlags & SF_NoopOrderBy)==0 ){ + /* At this point, any non-zero iOrderByCol values indicate that the + ** ORDER BY column expression is identical to the iOrderByCol'th + ** expression returned by SELECT statement pSub. Since these values + ** do not necessarily correspond to columns in SELECT statement pParent, + ** zero them before transferring the ORDER BY clause. + ** + ** Not doing this may cause an error if a subsequent call to this + ** function attempts to flatten a compound sub-query into pParent + ** (the only way this can happen is if the compound sub-query is + ** currently part of pSub->pSrc). See ticket [d11a6e908f]. */ + ExprList *pOrderBy = pSub->pOrderBy; + for(i=0; i<pOrderBy->nExpr; i++){ + pOrderBy->a[i].u.x.iOrderByCol = 0; + } + assert( pParent->pOrderBy==0 ); + pParent->pOrderBy = pOrderBy; + pSub->pOrderBy = 0; + } + pWhere = pSub->pWhere; + pSub->pWhere = 0; + if( isOuterJoin>0 ){ + sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON); + } + if( pWhere ){ + if( pParent->pWhere ){ + pParent->pWhere = sqlite3PExpr(pParse, TK_AND, pWhere, pParent->pWhere); + }else{ + pParent->pWhere = pWhere; + } + } + if( db->mallocFailed==0 ){ + SubstContext x; + x.pParse = pParse; + x.iTable = iParent; + x.iNewTable = iNewParent; + x.isOuterJoin = isOuterJoin; + x.pEList = pSub->pEList; + x.pCList = findLeftmostExprlist(pSub); + substSelect(&x, pParent, 0); + } + + /* The flattened query is a compound if either the inner or the + ** outer query is a compound. */ + pParent->selFlags |= pSub->selFlags & SF_Compound; + assert( (pSub->selFlags & SF_Distinct)==0 ); /* restriction (17b) */ + + /* + ** SELECT ... FROM (SELECT ... LIMIT a OFFSET b) LIMIT x OFFSET y; + ** + ** One is tempted to try to add a and b to combine the limits. But this + ** does not work if either limit is negative. + */ + if( pSub->pLimit ){ + pParent->pLimit = pSub->pLimit; + pSub->pLimit = 0; + } + + /* Recompute the SrcItem.colUsed masks for the flattened + ** tables. */ + for(i=0; i<nSubSrc; i++){ + recomputeColumnsUsed(pParent, &pSrc->a[i+iFrom]); + } + } + + /* Finally, delete what is left of the subquery and return success. + */ + sqlite3AggInfoPersistWalkerInit(&w, pParse); + sqlite3WalkSelect(&w,pSub1); + sqlite3SelectDelete(db, pSub1); + +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x4 ){ + TREETRACE(0x4,pParse,p,("After flattening:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + + return 1; +} +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + +/* +** A structure to keep track of all of the column values that are fixed to +** a known value due to WHERE clause constraints of the form COLUMN=VALUE. +*/ +typedef struct WhereConst WhereConst; +struct WhereConst { + Parse *pParse; /* Parsing context */ + u8 *pOomFault; /* Pointer to pParse->db->mallocFailed */ + int nConst; /* Number for COLUMN=CONSTANT terms */ + int nChng; /* Number of times a constant is propagated */ + int bHasAffBlob; /* At least one column in apExpr[] as affinity BLOB */ + u32 mExcludeOn; /* Which ON expressions to exclude from considertion. + ** Either EP_OuterON or EP_InnerON|EP_OuterON */ + Expr **apExpr; /* [i*2] is COLUMN and [i*2+1] is VALUE */ +}; + +/* +** Add a new entry to the pConst object. Except, do not add duplicate +** pColumn entries. Also, do not add if doing so would not be appropriate. +** +** The caller guarantees the pColumn is a column and pValue is a constant. +** This routine has to do some additional checks before completing the +** insert. +*/ +static void constInsert( + WhereConst *pConst, /* The WhereConst into which we are inserting */ + Expr *pColumn, /* The COLUMN part of the constraint */ + Expr *pValue, /* The VALUE part of the constraint */ + Expr *pExpr /* Overall expression: COLUMN=VALUE or VALUE=COLUMN */ +){ + int i; + assert( pColumn->op==TK_COLUMN ); + assert( sqlite3ExprIsConstant(pValue) ); + + if( ExprHasProperty(pColumn, EP_FixedCol) ) return; + if( sqlite3ExprAffinity(pValue)!=0 ) return; + if( !sqlite3IsBinary(sqlite3ExprCompareCollSeq(pConst->pParse,pExpr)) ){ + return; + } + + /* 2018-10-25 ticket [cf5ed20f] + ** Make sure the same pColumn is not inserted more than once */ + for(i=0; i<pConst->nConst; i++){ + const Expr *pE2 = pConst->apExpr[i*2]; + assert( pE2->op==TK_COLUMN ); + if( pE2->iTable==pColumn->iTable + && pE2->iColumn==pColumn->iColumn + ){ + return; /* Already present. Return without doing anything. */ + } + } + if( sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){ + pConst->bHasAffBlob = 1; + } + + pConst->nConst++; + pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr, + pConst->nConst*2*sizeof(Expr*)); + if( pConst->apExpr==0 ){ + pConst->nConst = 0; + }else{ + pConst->apExpr[pConst->nConst*2-2] = pColumn; + pConst->apExpr[pConst->nConst*2-1] = pValue; + } +} + +/* +** Find all terms of COLUMN=VALUE or VALUE=COLUMN in pExpr where VALUE +** is a constant expression and where the term must be true because it +** is part of the AND-connected terms of the expression. For each term +** found, add it to the pConst structure. +*/ +static void findConstInWhere(WhereConst *pConst, Expr *pExpr){ + Expr *pRight, *pLeft; + if( NEVER(pExpr==0) ) return; + if( ExprHasProperty(pExpr, pConst->mExcludeOn) ){ + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + testcase( ExprHasProperty(pExpr, EP_InnerON) ); + return; + } + if( pExpr->op==TK_AND ){ + findConstInWhere(pConst, pExpr->pRight); + findConstInWhere(pConst, pExpr->pLeft); + return; + } + if( pExpr->op!=TK_EQ ) return; + pRight = pExpr->pRight; + pLeft = pExpr->pLeft; + assert( pRight!=0 ); + assert( pLeft!=0 ); + if( pRight->op==TK_COLUMN && sqlite3ExprIsConstant(pLeft) ){ + constInsert(pConst,pRight,pLeft,pExpr); + } + if( pLeft->op==TK_COLUMN && sqlite3ExprIsConstant(pRight) ){ + constInsert(pConst,pLeft,pRight,pExpr); + } +} + +/* +** This is a helper function for Walker callback propagateConstantExprRewrite(). +** +** Argument pExpr is a candidate expression to be replaced by a value. If +** pExpr is equivalent to one of the columns named in pWalker->u.pConst, +** then overwrite it with the corresponding value. Except, do not do so +** if argument bIgnoreAffBlob is non-zero and the affinity of pExpr +** is SQLITE_AFF_BLOB. +*/ +static int propagateConstantExprRewriteOne( + WhereConst *pConst, + Expr *pExpr, + int bIgnoreAffBlob +){ + int i; + if( pConst->pOomFault[0] ) return WRC_Prune; + if( pExpr->op!=TK_COLUMN ) return WRC_Continue; + if( ExprHasProperty(pExpr, EP_FixedCol|pConst->mExcludeOn) ){ + testcase( ExprHasProperty(pExpr, EP_FixedCol) ); + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + testcase( ExprHasProperty(pExpr, EP_InnerON) ); + return WRC_Continue; + } + for(i=0; i<pConst->nConst; i++){ + Expr *pColumn = pConst->apExpr[i*2]; + if( pColumn==pExpr ) continue; + if( pColumn->iTable!=pExpr->iTable ) continue; + if( pColumn->iColumn!=pExpr->iColumn ) continue; + if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){ + break; + } + /* A match is found. Add the EP_FixedCol property */ + pConst->nChng++; + ExprClearProperty(pExpr, EP_Leaf); + ExprSetProperty(pExpr, EP_FixedCol); + assert( pExpr->pLeft==0 ); + pExpr->pLeft = sqlite3ExprDup(pConst->pParse->db, pConst->apExpr[i*2+1], 0); + if( pConst->pParse->db->mallocFailed ) return WRC_Prune; + break; + } + return WRC_Prune; +} + +/* +** This is a Walker expression callback. pExpr is a node from the WHERE +** clause of a SELECT statement. This function examines pExpr to see if +** any substitutions based on the contents of pWalker->u.pConst should +** be made to pExpr or its immediate children. +** +** A substitution is made if: +** +** + pExpr is a column with an affinity other than BLOB that matches +** one of the columns in pWalker->u.pConst, or +** +** + pExpr is a binary comparison operator (=, <=, >=, <, >) that +** uses an affinity other than TEXT and one of its immediate +** children is a column that matches one of the columns in +** pWalker->u.pConst. +*/ +static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ + WhereConst *pConst = pWalker->u.pConst; + assert( TK_GT==TK_EQ+1 ); + assert( TK_LE==TK_EQ+2 ); + assert( TK_LT==TK_EQ+3 ); + assert( TK_GE==TK_EQ+4 ); + if( pConst->bHasAffBlob ){ + if( (pExpr->op>=TK_EQ && pExpr->op<=TK_GE) + || pExpr->op==TK_IS + ){ + propagateConstantExprRewriteOne(pConst, pExpr->pLeft, 0); + if( pConst->pOomFault[0] ) return WRC_Prune; + if( sqlite3ExprAffinity(pExpr->pLeft)!=SQLITE_AFF_TEXT ){ + propagateConstantExprRewriteOne(pConst, pExpr->pRight, 0); + } + } + } + return propagateConstantExprRewriteOne(pConst, pExpr, pConst->bHasAffBlob); +} + +/* +** The WHERE-clause constant propagation optimization. +** +** If the WHERE clause contains terms of the form COLUMN=CONSTANT or +** CONSTANT=COLUMN that are top-level AND-connected terms that are not +** part of a ON clause from a LEFT JOIN, then throughout the query +** replace all other occurrences of COLUMN with CONSTANT. +** +** For example, the query: +** +** SELECT * FROM t1, t2, t3 WHERE t1.a=39 AND t2.b=t1.a AND t3.c=t2.b +** +** Is transformed into +** +** SELECT * FROM t1, t2, t3 WHERE t1.a=39 AND t2.b=39 AND t3.c=39 +** +** Return true if any transformations where made and false if not. +** +** Implementation note: Constant propagation is tricky due to affinity +** and collating sequence interactions. Consider this example: +** +** CREATE TABLE t1(a INT,b TEXT); +** INSERT INTO t1 VALUES(123,'0123'); +** SELECT * FROM t1 WHERE a=123 AND b=a; +** SELECT * FROM t1 WHERE a=123 AND b=123; +** +** The two SELECT statements above should return different answers. b=a +** is always true because the comparison uses numeric affinity, but b=123 +** is false because it uses text affinity and '0123' is not the same as '123'. +** To work around this, the expression tree is not actually changed from +** "b=a" to "b=123" but rather the "a" in "b=a" is tagged with EP_FixedCol +** and the "123" value is hung off of the pLeft pointer. Code generator +** routines know to generate the constant "123" instead of looking up the +** column value. Also, to avoid collation problems, this optimization is +** only attempted if the "a=123" term uses the default BINARY collation. +** +** 2021-05-25 forum post 6a06202608: Another troublesome case is... +** +** CREATE TABLE t1(x); +** INSERT INTO t1 VALUES(10.0); +** SELECT 1 FROM t1 WHERE x=10 AND x LIKE 10; +** +** The query should return no rows, because the t1.x value is '10.0' not '10' +** and '10.0' is not LIKE '10'. But if we are not careful, the first WHERE +** term "x=10" will cause the second WHERE term to become "10 LIKE 10", +** resulting in a false positive. To avoid this, constant propagation for +** columns with BLOB affinity is only allowed if the constant is used with +** operators ==, <=, <, >=, >, or IS in a way that will cause the correct +** type conversions to occur. See logic associated with the bHasAffBlob flag +** for details. +*/ +static int propagateConstants( + Parse *pParse, /* The parsing context */ + Select *p /* The query in which to propagate constants */ +){ + WhereConst x; + Walker w; + int nChng = 0; + x.pParse = pParse; + x.pOomFault = &pParse->db->mallocFailed; + do{ + x.nConst = 0; + x.nChng = 0; + x.apExpr = 0; + x.bHasAffBlob = 0; + if( ALWAYS(p->pSrc!=0) + && p->pSrc->nSrc>0 + && (p->pSrc->a[0].fg.jointype & JT_LTORJ)!=0 + ){ + /* Do not propagate constants on any ON clause if there is a + ** RIGHT JOIN anywhere in the query */ + x.mExcludeOn = EP_InnerON | EP_OuterON; + }else{ + /* Do not propagate constants through the ON clause of a LEFT JOIN */ + x.mExcludeOn = EP_OuterON; + } + findConstInWhere(&x, p->pWhere); + if( x.nConst ){ + memset(&w, 0, sizeof(w)); + w.pParse = pParse; + w.xExprCallback = propagateConstantExprRewrite; + w.xSelectCallback = sqlite3SelectWalkNoop; + w.xSelectCallback2 = 0; + w.walkerDepth = 0; + w.u.pConst = &x; + sqlite3WalkExpr(&w, p->pWhere); + sqlite3DbFree(x.pParse->db, x.apExpr); + nChng += x.nChng; + } + }while( x.nChng ); + return nChng; +} + +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) +# if !defined(SQLITE_OMIT_WINDOWFUNC) +/* +** This function is called to determine whether or not it is safe to +** push WHERE clause expression pExpr down to FROM clause sub-query +** pSubq, which contains at least one window function. Return 1 +** if it is safe and the expression should be pushed down, or 0 +** otherwise. +** +** It is only safe to push the expression down if it consists only +** of constants and copies of expressions that appear in the PARTITION +** BY clause of all window function used by the sub-query. It is safe +** to filter out entire partitions, but not rows within partitions, as +** this may change the results of the window functions. +** +** At the time this function is called it is guaranteed that +** +** * the sub-query uses only one distinct window frame, and +** * that the window frame has a PARTITION BY clause. +*/ +static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ + assert( pSubq->pWin->pPartition ); + assert( (pSubq->selFlags & SF_MultiPart)==0 ); + assert( pSubq->pPrior==0 ); + return sqlite3ExprIsConstantOrGroupBy(pParse, pExpr, pSubq->pWin->pPartition); +} +# endif /* SQLITE_OMIT_WINDOWFUNC */ +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) +/* +** Make copies of relevant WHERE clause terms of the outer query into +** the WHERE clause of subquery. Example: +** +** SELECT * FROM (SELECT a AS x, c-d AS y FROM t1) WHERE x=5 AND y=10; +** +** Transformed into: +** +** SELECT * FROM (SELECT a AS x, c-d AS y FROM t1 WHERE a=5 AND c-d=10) +** WHERE x=5 AND y=10; +** +** The hope is that the terms added to the inner query will make it more +** efficient. +** +** Do not attempt this optimization if: +** +** (1) (** This restriction was removed on 2017-09-29. We used to +** disallow this optimization for aggregate subqueries, but now +** it is allowed by putting the extra terms on the HAVING clause. +** The added HAVING clause is pointless if the subquery lacks +** a GROUP BY clause. But such a HAVING clause is also harmless +** so there does not appear to be any reason to add extra logic +** to suppress it. **) +** +** (2) The inner query is the recursive part of a common table expression. +** +** (3) The inner query has a LIMIT clause (since the changes to the WHERE +** clause would change the meaning of the LIMIT). +** +** (4) The inner query is the right operand of a LEFT JOIN and the +** expression to be pushed down does not come from the ON clause +** on that LEFT JOIN. +** +** (5) The WHERE clause expression originates in the ON or USING clause +** of a LEFT JOIN where iCursor is not the right-hand table of that +** left join. An example: +** +** SELECT * +** FROM (SELECT 1 AS a1 UNION ALL SELECT 2) AS aa +** JOIN (SELECT 1 AS b2 UNION ALL SELECT 2) AS bb ON (a1=b2) +** LEFT JOIN (SELECT 8 AS c3 UNION ALL SELECT 9) AS cc ON (b2=2); +** +** The correct answer is three rows: (1,1,NULL),(2,2,8),(2,2,9). +** But if the (b2=2) term were to be pushed down into the bb subquery, +** then the (1,1,NULL) row would be suppressed. +** +** (6) Window functions make things tricky as changes to the WHERE clause +** of the inner query could change the window over which window +** functions are calculated. Therefore, do not attempt the optimization +** if: +** +** (6a) The inner query uses multiple incompatible window partitions. +** +** (6b) The inner query is a compound and uses window-functions. +** +** (6c) The WHERE clause does not consist entirely of constants and +** copies of expressions found in the PARTITION BY clause of +** all window-functions used by the sub-query. It is safe to +** filter out entire partitions, as this does not change the +** window over which any window-function is calculated. +** +** (7) The inner query is a Common Table Expression (CTE) that should +** be materialized. (This restriction is implemented in the calling +** routine.) +** +** (8) If the subquery is a compound that uses UNION, INTERSECT, +** or EXCEPT, then all of the result set columns for all arms of +** the compound must use the BINARY collating sequence. +** +** (9) All three of the following are true: +** +** (9a) The WHERE clause expression originates in the ON or USING clause +** of a join (either an INNER or an OUTER join), and +** +** (9b) The subquery is to the right of the ON/USING clause +** +** (9c) There is a RIGHT JOIN (or FULL JOIN) in between the ON/USING +** clause and the subquery. +** +** Without this restriction, the push-down optimization might move +** the ON/USING filter expression from the left side of a RIGHT JOIN +** over to the right side, which leads to incorrect answers. See +** also restriction (6) in sqlite3ExprIsSingleTableConstraint(). +** +** (10) The inner query is not the right-hand table of a RIGHT JOIN. +** +** (11) The subquery is not a VALUES clause +** +** Return 0 if no changes are made and non-zero if one or more WHERE clause +** terms are duplicated into the subquery. +*/ +static int pushDownWhereTerms( + Parse *pParse, /* Parse context (for malloc() and error reporting) */ + Select *pSubq, /* The subquery whose WHERE clause is to be augmented */ + Expr *pWhere, /* The WHERE clause of the outer query */ + SrcList *pSrcList, /* The complete from clause of the outer query */ + int iSrc /* Which FROM clause term to try to push into */ +){ + Expr *pNew; + SrcItem *pSrc; /* The subquery FROM term into which WHERE is pushed */ + int nChng = 0; + pSrc = &pSrcList->a[iSrc]; + if( pWhere==0 ) return 0; + if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ){ + return 0; /* restrictions (2) and (11) */ + } + if( pSrc->fg.jointype & (JT_LTORJ|JT_RIGHT) ){ + return 0; /* restrictions (10) */ + } + + if( pSubq->pPrior ){ + Select *pSel; + int notUnionAll = 0; + for(pSel=pSubq; pSel; pSel=pSel->pPrior){ + u8 op = pSel->op; + assert( op==TK_ALL || op==TK_SELECT + || op==TK_UNION || op==TK_INTERSECT || op==TK_EXCEPT ); + if( op!=TK_ALL && op!=TK_SELECT ){ + notUnionAll = 1; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pSel->pWin ) return 0; /* restriction (6b) */ +#endif + } + if( notUnionAll ){ + /* If any of the compound arms are connected using UNION, INTERSECT, + ** or EXCEPT, then we must ensure that none of the columns use a + ** non-BINARY collating sequence. */ + for(pSel=pSubq; pSel; pSel=pSel->pPrior){ + int ii; + const ExprList *pList = pSel->pEList; + assert( pList!=0 ); + for(ii=0; ii<pList->nExpr; ii++){ + CollSeq *pColl = sqlite3ExprCollSeq(pParse, pList->a[ii].pExpr); + if( !sqlite3IsBinary(pColl) ){ + return 0; /* Restriction (8) */ + } + } + } + } + }else{ +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pSubq->pWin && pSubq->pWin->pPartition==0 ) return 0; +#endif + } + +#ifdef SQLITE_DEBUG + /* Only the first term of a compound can have a WITH clause. But make + ** sure no other terms are marked SF_Recursive in case something changes + ** in the future. + */ + { + Select *pX; + for(pX=pSubq; pX; pX=pX->pPrior){ + assert( (pX->selFlags & (SF_Recursive))==0 ); + } + } +#endif + + if( pSubq->pLimit!=0 ){ + return 0; /* restriction (3) */ + } + while( pWhere->op==TK_AND ){ + nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, pSrcList, iSrc); + pWhere = pWhere->pLeft; + } + +#if 0 /* These checks now done by sqlite3ExprIsSingleTableConstraint() */ + if( ExprHasProperty(pWhere, EP_OuterON|EP_InnerON) /* (9a) */ + && (pSrcList->a[0].fg.jointype & JT_LTORJ)!=0 /* Fast pre-test of (9c) */ + ){ + int jj; + for(jj=0; jj<iSrc; jj++){ + if( pWhere->w.iJoin==pSrcList->a[jj].iCursor ){ + /* If we reach this point, both (9a) and (9b) are satisfied. + ** The following loop checks (9c): + */ + for(jj++; jj<iSrc; jj++){ + if( (pSrcList->a[jj].fg.jointype & JT_RIGHT)!=0 ){ + return 0; /* restriction (9) */ + } + } + } + } + } + if( isLeftJoin + && (ExprHasProperty(pWhere,EP_OuterON)==0 + || pWhere->w.iJoin!=iCursor) + ){ + return 0; /* restriction (4) */ + } + if( ExprHasProperty(pWhere,EP_OuterON) + && pWhere->w.iJoin!=iCursor + ){ + return 0; /* restriction (5) */ + } +#endif + + if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc) ){ + nChng++; + pSubq->selFlags |= SF_PushDown; + while( pSubq ){ + SubstContext x; + pNew = sqlite3ExprDup(pParse->db, pWhere, 0); + unsetJoinExpr(pNew, -1, 1); + x.pParse = pParse; + x.iTable = pSrc->iCursor; + x.iNewTable = pSrc->iCursor; + x.isOuterJoin = 0; + x.pEList = pSubq->pEList; + x.pCList = findLeftmostExprlist(pSubq); + pNew = substExpr(&x, pNew); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pSubq->pWin && 0==pushDownWindowCheck(pParse, pSubq, pNew) ){ + /* Restriction 6c has prevented push-down in this case */ + sqlite3ExprDelete(pParse->db, pNew); + nChng--; + break; + } +#endif + if( pSubq->selFlags & SF_Aggregate ){ + pSubq->pHaving = sqlite3ExprAnd(pParse, pSubq->pHaving, pNew); + }else{ + pSubq->pWhere = sqlite3ExprAnd(pParse, pSubq->pWhere, pNew); + } + pSubq = pSubq->pPrior; + } + } + return nChng; +} +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + +/* +** Check to see if a subquery contains result-set columns that are +** never used. If it does, change the value of those result-set columns +** to NULL so that they do not cause unnecessary work to compute. +** +** Return the number of column that were changed to NULL. +*/ +static int disableUnusedSubqueryResultColumns(SrcItem *pItem){ + int nCol; + Select *pSub; /* The subquery to be simplified */ + Select *pX; /* For looping over compound elements of pSub */ + Table *pTab; /* The table that describes the subquery */ + int j; /* Column number */ + int nChng = 0; /* Number of columns converted to NULL */ + Bitmask colUsed; /* Columns that may not be NULLed out */ + + assert( pItem!=0 ); + if( pItem->fg.isCorrelated || pItem->fg.isCte ){ + return 0; + } + assert( pItem->pTab!=0 ); + pTab = pItem->pTab; + assert( pItem->pSelect!=0 ); + pSub = pItem->pSelect; + assert( pSub->pEList->nExpr==pTab->nCol ); + for(pX=pSub; pX; pX=pX->pPrior){ + if( (pX->selFlags & (SF_Distinct|SF_Aggregate))!=0 ){ + testcase( pX->selFlags & SF_Distinct ); + testcase( pX->selFlags & SF_Aggregate ); + return 0; + } + if( pX->pPrior && pX->op!=TK_ALL ){ + /* This optimization does not work for compound subqueries that + ** use UNION, INTERSECT, or EXCEPT. Only UNION ALL is allowed. */ + return 0; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pX->pWin ){ + /* This optimization does not work for subqueries that use window + ** functions. */ + return 0; + } +#endif + } + colUsed = pItem->colUsed; + if( pSub->pOrderBy ){ + ExprList *pList = pSub->pOrderBy; + for(j=0; j<pList->nExpr; j++){ + u16 iCol = pList->a[j].u.x.iOrderByCol; + if( iCol>0 ){ + iCol--; + colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol); + } + } + } + nCol = pTab->nCol; + for(j=0; j<nCol; j++){ + Bitmask m = j<BMS-1 ? MASKBIT(j) : TOPBIT; + if( (m & colUsed)!=0 ) continue; + for(pX=pSub; pX; pX=pX->pPrior) { + Expr *pY = pX->pEList->a[j].pExpr; + if( pY->op==TK_NULL ) continue; + pY->op = TK_NULL; + ExprClearProperty(pY, EP_Skip|EP_Unlikely); + pX->selFlags |= SF_PushDown; + nChng++; + } + } + return nChng; +} + + +/* +** The pFunc is the only aggregate function in the query. Check to see +** if the query is a candidate for the min/max optimization. +** +** If the query is a candidate for the min/max optimization, then set +** *ppMinMax to be an ORDER BY clause to be used for the optimization +** and return either WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX depending on +** whether pFunc is a min() or max() function. +** +** If the query is not a candidate for the min/max optimization, return +** WHERE_ORDERBY_NORMAL (which must be zero). +** +** This routine must be called after aggregate functions have been +** located but before their arguments have been subjected to aggregate +** analysis. +*/ +static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ + int eRet = WHERE_ORDERBY_NORMAL; /* Return value */ + ExprList *pEList; /* Arguments to agg function */ + const char *zFunc; /* Name of aggregate function pFunc */ + ExprList *pOrderBy; + u8 sortFlags = 0; + + assert( *ppMinMax==0 ); + assert( pFunc->op==TK_AGG_FUNCTION ); + assert( !IsWindowFunc(pFunc) ); + assert( ExprUseXList(pFunc) ); + pEList = pFunc->x.pList; + if( pEList==0 + || pEList->nExpr!=1 + || ExprHasProperty(pFunc, EP_WinFunc) + || OptimizationDisabled(db, SQLITE_MinMaxOpt) + ){ + return eRet; + } + assert( !ExprHasProperty(pFunc, EP_IntValue) ); + zFunc = pFunc->u.zToken; + if( sqlite3StrICmp(zFunc, "min")==0 ){ + eRet = WHERE_ORDERBY_MIN; + if( sqlite3ExprCanBeNull(pEList->a[0].pExpr) ){ + sortFlags = KEYINFO_ORDER_BIGNULL; + } + }else if( sqlite3StrICmp(zFunc, "max")==0 ){ + eRet = WHERE_ORDERBY_MAX; + sortFlags = KEYINFO_ORDER_DESC; + }else{ + return eRet; + } + *ppMinMax = pOrderBy = sqlite3ExprListDup(db, pEList, 0); + assert( pOrderBy!=0 || db->mallocFailed ); + if( pOrderBy ) pOrderBy->a[0].fg.sortFlags = sortFlags; + return eRet; +} + +/* +** The select statement passed as the first argument is an aggregate query. +** The second argument is the associated aggregate-info object. This +** function tests if the SELECT is of the form: +** +** SELECT count(*) FROM <tbl> +** +** where table is a database table, not a sub-select or view. If the query +** does match this pattern, then a pointer to the Table object representing +** <tbl> is returned. Otherwise, NULL is returned. +** +** This routine checks to see if it is safe to use the count optimization. +** A correct answer is still obtained (though perhaps more slowly) if +** this routine returns NULL when it could have returned a table pointer. +** But returning the pointer when NULL should have been returned can +** result in incorrect answers and/or crashes. So, when in doubt, return NULL. +*/ +static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ + Table *pTab; + Expr *pExpr; + + assert( !p->pGroupBy ); + + if( p->pWhere + || p->pEList->nExpr!=1 + || p->pSrc->nSrc!=1 + || p->pSrc->a[0].pSelect + || pAggInfo->nFunc!=1 + || p->pHaving + ){ + return 0; + } + pTab = p->pSrc->a[0].pTab; + assert( pTab!=0 ); + assert( !IsView(pTab) ); + if( !IsOrdinaryTable(pTab) ) return 0; + pExpr = p->pEList->a[0].pExpr; + assert( pExpr!=0 ); + if( pExpr->op!=TK_AGG_FUNCTION ) return 0; + if( pExpr->pAggInfo!=pAggInfo ) return 0; + if( (pAggInfo->aFunc[0].pFunc->funcFlags&SQLITE_FUNC_COUNT)==0 ) return 0; + assert( pAggInfo->aFunc[0].pFExpr==pExpr ); + testcase( ExprHasProperty(pExpr, EP_Distinct) ); + testcase( ExprHasProperty(pExpr, EP_WinFunc) ); + if( ExprHasProperty(pExpr, EP_Distinct|EP_WinFunc) ) return 0; + + return pTab; +} + +/* +** If the source-list item passed as an argument was augmented with an +** INDEXED BY clause, then try to locate the specified index. If there +** was such a clause and the named index cannot be found, return +** SQLITE_ERROR and leave an error in pParse. Otherwise, populate +** pFrom->pIndex and return SQLITE_OK. +*/ +SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ + Table *pTab = pFrom->pTab; + char *zIndexedBy = pFrom->u1.zIndexedBy; + Index *pIdx; + assert( pTab!=0 ); + assert( pFrom->fg.isIndexedBy!=0 ); + + for(pIdx=pTab->pIndex; + pIdx && sqlite3StrICmp(pIdx->zName, zIndexedBy); + pIdx=pIdx->pNext + ); + if( !pIdx ){ + sqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy, 0); + pParse->checkSchema = 1; + return SQLITE_ERROR; + } + assert( pFrom->fg.isCte==0 ); + pFrom->u2.pIBIndex = pIdx; + return SQLITE_OK; +} + +/* +** Detect compound SELECT statements that use an ORDER BY clause with +** an alternative collating sequence. +** +** SELECT ... FROM t1 EXCEPT SELECT ... FROM t2 ORDER BY .. COLLATE ... +** +** These are rewritten as a subquery: +** +** SELECT * FROM (SELECT ... FROM t1 EXCEPT SELECT ... FROM t2) +** ORDER BY ... COLLATE ... +** +** This transformation is necessary because the multiSelectOrderBy() routine +** above that generates the code for a compound SELECT with an ORDER BY clause +** uses a merge algorithm that requires the same collating sequence on the +** result columns as on the ORDER BY clause. See ticket +** http://www.sqlite.org/src/info/6709574d2a +** +** This transformation is only needed for EXCEPT, INTERSECT, and UNION. +** The UNION ALL operator works fine with multiSelectOrderBy() even when +** there are COLLATE terms in the ORDER BY. +*/ +static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ + int i; + Select *pNew; + Select *pX; + sqlite3 *db; + struct ExprList_item *a; + SrcList *pNewSrc; + Parse *pParse; + Token dummy; + + if( p->pPrior==0 ) return WRC_Continue; + if( p->pOrderBy==0 ) return WRC_Continue; + for(pX=p; pX && (pX->op==TK_ALL || pX->op==TK_SELECT); pX=pX->pPrior){} + if( pX==0 ) return WRC_Continue; + a = p->pOrderBy->a; +#ifndef SQLITE_OMIT_WINDOWFUNC + /* If iOrderByCol is already non-zero, then it has already been matched + ** to a result column of the SELECT statement. This occurs when the + ** SELECT is rewritten for window-functions processing and then passed + ** to sqlite3SelectPrep() and similar a second time. The rewriting done + ** by this function is not required in this case. */ + if( a[0].u.x.iOrderByCol ) return WRC_Continue; +#endif + for(i=p->pOrderBy->nExpr-1; i>=0; i--){ + if( a[i].pExpr->flags & EP_Collate ) break; + } + if( i<0 ) return WRC_Continue; + + /* If we reach this point, that means the transformation is required. */ + + pParse = pWalker->pParse; + db = pParse->db; + pNew = sqlite3DbMallocZero(db, sizeof(*pNew) ); + if( pNew==0 ) return WRC_Abort; + memset(&dummy, 0, sizeof(dummy)); + pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0); + if( pNewSrc==0 ) return WRC_Abort; + *pNew = *p; + p->pSrc = pNewSrc; + p->pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ASTERISK, 0)); + p->op = TK_SELECT; + p->pWhere = 0; + pNew->pGroupBy = 0; + pNew->pHaving = 0; + pNew->pOrderBy = 0; + p->pPrior = 0; + p->pNext = 0; + p->pWith = 0; +#ifndef SQLITE_OMIT_WINDOWFUNC + p->pWinDefn = 0; +#endif + p->selFlags &= ~SF_Compound; + assert( (p->selFlags & SF_Converted)==0 ); + p->selFlags |= SF_Converted; + assert( pNew->pPrior!=0 ); + pNew->pPrior->pNext = pNew; + pNew->pLimit = 0; + return WRC_Continue; +} + +/* +** Check to see if the FROM clause term pFrom has table-valued function +** arguments. If it does, leave an error message in pParse and return +** non-zero, since pFrom is not allowed to be a table-valued function. +*/ +static int cannotBeFunction(Parse *pParse, SrcItem *pFrom){ + if( pFrom->fg.isTabFunc ){ + sqlite3ErrorMsg(pParse, "'%s' is not a function", pFrom->zName); + return 1; + } + return 0; +} + +#ifndef SQLITE_OMIT_CTE +/* +** Argument pWith (which may be NULL) points to a linked list of nested +** WITH contexts, from inner to outermost. If the table identified by +** FROM clause element pItem is really a common-table-expression (CTE) +** then return a pointer to the CTE definition for that table. Otherwise +** return NULL. +** +** If a non-NULL value is returned, set *ppContext to point to the With +** object that the returned CTE belongs to. +*/ +static struct Cte *searchWith( + With *pWith, /* Current innermost WITH clause */ + SrcItem *pItem, /* FROM clause element to resolve */ + With **ppContext /* OUT: WITH clause return value belongs to */ +){ + const char *zName = pItem->zName; + With *p; + assert( pItem->zDatabase==0 ); + assert( zName!=0 ); + for(p=pWith; p; p=p->pOuter){ + int i; + for(i=0; i<p->nCte; i++){ + if( sqlite3StrICmp(zName, p->a[i].zName)==0 ){ + *ppContext = p; + return &p->a[i]; + } + } + if( p->bView ) break; + } + return 0; +} + +/* The code generator maintains a stack of active WITH clauses +** with the inner-most WITH clause being at the top of the stack. +** +** This routine pushes the WITH clause passed as the second argument +** onto the top of the stack. If argument bFree is true, then this +** WITH clause will never be popped from the stack but should instead +** be freed along with the Parse object. In other cases, when +** bFree==0, the With object will be freed along with the SELECT +** statement with which it is associated. +** +** This routine returns a copy of pWith. Or, if bFree is true and +** the pWith object is destroyed immediately due to an OOM condition, +** then this routine return NULL. +** +** If bFree is true, do not continue to use the pWith pointer after +** calling this routine, Instead, use only the return value. +*/ +SQLITE_PRIVATE With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ + if( pWith ){ + if( bFree ){ + pWith = (With*)sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3WithDelete, + pWith); + if( pWith==0 ) return 0; + } + if( pParse->nErr==0 ){ + assert( pParse->pWith!=pWith ); + pWith->pOuter = pParse->pWith; + pParse->pWith = pWith; + } + } + return pWith; +} + +/* +** This function checks if argument pFrom refers to a CTE declared by +** a WITH clause on the stack currently maintained by the parser (on the +** pParse->pWith linked list). And if currently processing a CTE +** CTE expression, through routine checks to see if the reference is +** a recursive reference to the CTE. +** +** If pFrom matches a CTE according to either of these two above, pFrom->pTab +** and other fields are populated accordingly. +** +** Return 0 if no match is found. +** Return 1 if a match is found. +** Return 2 if an error condition is detected. +*/ +static int resolveFromTermToCte( + Parse *pParse, /* The parsing context */ + Walker *pWalker, /* Current tree walker */ + SrcItem *pFrom /* The FROM clause term to check */ +){ + Cte *pCte; /* Matched CTE (or NULL if no match) */ + With *pWith; /* The matching WITH */ + + assert( pFrom->pTab==0 ); + if( pParse->pWith==0 ){ + /* There are no WITH clauses in the stack. No match is possible */ + return 0; + } + if( pParse->nErr ){ + /* Prior errors might have left pParse->pWith in a goofy state, so + ** go no further. */ + return 0; + } + if( pFrom->zDatabase!=0 ){ + /* The FROM term contains a schema qualifier (ex: main.t1) and so + ** it cannot possibly be a CTE reference. */ + return 0; + } + if( pFrom->fg.notCte ){ + /* The FROM term is specifically excluded from matching a CTE. + ** (1) It is part of a trigger that used to have zDatabase but had + ** zDatabase removed by sqlite3FixTriggerStep(). + ** (2) This is the first term in the FROM clause of an UPDATE. + */ + return 0; + } + pCte = searchWith(pParse->pWith, pFrom, &pWith); + if( pCte ){ + sqlite3 *db = pParse->db; + Table *pTab; + ExprList *pEList; + Select *pSel; + Select *pLeft; /* Left-most SELECT statement */ + Select *pRecTerm; /* Left-most recursive term */ + int bMayRecursive; /* True if compound joined by UNION [ALL] */ + With *pSavedWith; /* Initial value of pParse->pWith */ + int iRecTab = -1; /* Cursor for recursive table */ + CteUse *pCteUse; + + /* If pCte->zCteErr is non-NULL at this point, then this is an illegal + ** recursive reference to CTE pCte. Leave an error in pParse and return + ** early. If pCte->zCteErr is NULL, then this is not a recursive reference. + ** In this case, proceed. */ + if( pCte->zCteErr ){ + sqlite3ErrorMsg(pParse, pCte->zCteErr, pCte->zName); + return 2; + } + if( cannotBeFunction(pParse, pFrom) ) return 2; + + assert( pFrom->pTab==0 ); + pTab = sqlite3DbMallocZero(db, sizeof(Table)); + if( pTab==0 ) return 2; + pCteUse = pCte->pUse; + if( pCteUse==0 ){ + pCte->pUse = pCteUse = sqlite3DbMallocZero(db, sizeof(pCteUse[0])); + if( pCteUse==0 + || sqlite3ParserAddCleanup(pParse,sqlite3DbFree,pCteUse)==0 + ){ + sqlite3DbFree(db, pTab); + return 2; + } + pCteUse->eM10d = pCte->eM10d; + } + pFrom->pTab = pTab; + pTab->nTabRef = 1; + pTab->zName = sqlite3DbStrDup(db, pCte->zName); + pTab->iPKey = -1; + pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); + pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; + pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0); + if( db->mallocFailed ) return 2; + pFrom->pSelect->selFlags |= SF_CopyCte; + assert( pFrom->pSelect ); + if( pFrom->fg.isIndexedBy ){ + sqlite3ErrorMsg(pParse, "no such index: \"%s\"", pFrom->u1.zIndexedBy); + return 2; + } + pFrom->fg.isCte = 1; + pFrom->u2.pCteUse = pCteUse; + pCteUse->nUse++; + + /* Check if this is a recursive CTE. */ + pRecTerm = pSel = pFrom->pSelect; + bMayRecursive = ( pSel->op==TK_ALL || pSel->op==TK_UNION ); + while( bMayRecursive && pRecTerm->op==pSel->op ){ + int i; + SrcList *pSrc = pRecTerm->pSrc; + assert( pRecTerm->pPrior!=0 ); + for(i=0; i<pSrc->nSrc; i++){ + SrcItem *pItem = &pSrc->a[i]; + if( pItem->zDatabase==0 + && pItem->zName!=0 + && 0==sqlite3StrICmp(pItem->zName, pCte->zName) + ){ + pItem->pTab = pTab; + pTab->nTabRef++; + pItem->fg.isRecursive = 1; + if( pRecTerm->selFlags & SF_Recursive ){ + sqlite3ErrorMsg(pParse, + "multiple references to recursive table: %s", pCte->zName + ); + return 2; + } + pRecTerm->selFlags |= SF_Recursive; + if( iRecTab<0 ) iRecTab = pParse->nTab++; + pItem->iCursor = iRecTab; + } + } + if( (pRecTerm->selFlags & SF_Recursive)==0 ) break; + pRecTerm = pRecTerm->pPrior; + } + + pCte->zCteErr = "circular reference: %s"; + pSavedWith = pParse->pWith; + pParse->pWith = pWith; + if( pSel->selFlags & SF_Recursive ){ + int rc; + assert( pRecTerm!=0 ); + assert( (pRecTerm->selFlags & SF_Recursive)==0 ); + assert( pRecTerm->pNext!=0 ); + assert( (pRecTerm->pNext->selFlags & SF_Recursive)!=0 ); + assert( pRecTerm->pWith==0 ); + pRecTerm->pWith = pSel->pWith; + rc = sqlite3WalkSelect(pWalker, pRecTerm); + pRecTerm->pWith = 0; + if( rc ){ + pParse->pWith = pSavedWith; + return 2; + } + }else{ + if( sqlite3WalkSelect(pWalker, pSel) ){ + pParse->pWith = pSavedWith; + return 2; + } + } + pParse->pWith = pWith; + + for(pLeft=pSel; pLeft->pPrior; pLeft=pLeft->pPrior); + pEList = pLeft->pEList; + if( pCte->pCols ){ + if( pEList && pEList->nExpr!=pCte->pCols->nExpr ){ + sqlite3ErrorMsg(pParse, "table %s has %d values for %d columns", + pCte->zName, pEList->nExpr, pCte->pCols->nExpr + ); + pParse->pWith = pSavedWith; + return 2; + } + pEList = pCte->pCols; + } + + sqlite3ColumnsFromExprList(pParse, pEList, &pTab->nCol, &pTab->aCol); + if( bMayRecursive ){ + if( pSel->selFlags & SF_Recursive ){ + pCte->zCteErr = "multiple recursive references: %s"; + }else{ + pCte->zCteErr = "recursive reference in a subquery: %s"; + } + sqlite3WalkSelect(pWalker, pSel); + } + pCte->zCteErr = 0; + pParse->pWith = pSavedWith; + return 1; /* Success */ + } + return 0; /* No match */ +} +#endif + +#ifndef SQLITE_OMIT_CTE +/* +** If the SELECT passed as the second argument has an associated WITH +** clause, pop it from the stack stored as part of the Parse object. +** +** This function is used as the xSelectCallback2() callback by +** sqlite3SelectExpand() when walking a SELECT tree to resolve table +** names and other FROM clause elements. +*/ +SQLITE_PRIVATE void sqlite3SelectPopWith(Walker *pWalker, Select *p){ + Parse *pParse = pWalker->pParse; + if( OK_IF_ALWAYS_TRUE(pParse->pWith) && p->pPrior==0 ){ + With *pWith = findRightmost(p)->pWith; + if( pWith!=0 ){ + assert( pParse->pWith==pWith || pParse->nErr ); + pParse->pWith = pWith->pOuter; + } + } +} +#endif + +/* +** The SrcItem structure passed as the second argument represents a +** sub-query in the FROM clause of a SELECT statement. This function +** allocates and populates the SrcItem.pTab object. If successful, +** SQLITE_OK is returned. Otherwise, if an OOM error is encountered, +** SQLITE_NOMEM. +*/ +SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){ + Select *pSel = pFrom->pSelect; + Table *pTab; + + assert( pSel ); + pFrom->pTab = pTab = sqlite3DbMallocZero(pParse->db, sizeof(Table)); + if( pTab==0 ) return SQLITE_NOMEM; + pTab->nTabRef = 1; + if( pFrom->zAlias ){ + pTab->zName = sqlite3DbStrDup(pParse->db, pFrom->zAlias); + }else{ + pTab->zName = sqlite3MPrintf(pParse->db, "%!S", pFrom); + } + while( pSel->pPrior ){ pSel = pSel->pPrior; } + sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); + pTab->iPKey = -1; + pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW + /* The usual case - do not allow ROWID on a subquery */ + pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; +#else + pTab->tabFlags |= TF_Ephemeral; /* Legacy compatibility mode */ +#endif + return pParse->nErr ? SQLITE_ERROR : SQLITE_OK; +} + + +/* +** Check the N SrcItem objects to the right of pBase. (N might be zero!) +** If any of those SrcItem objects have a USING clause containing zName +** then return true. +** +** If N is zero, or none of the N SrcItem objects to the right of pBase +** contains a USING clause, or if none of the USING clauses contain zName, +** then return false. +*/ +static int inAnyUsingClause( + const char *zName, /* Name we are looking for */ + SrcItem *pBase, /* The base SrcItem. Looking at pBase[1] and following */ + int N /* How many SrcItems to check */ +){ + while( N>0 ){ + N--; + pBase++; + if( pBase->fg.isUsing==0 ) continue; + if( NEVER(pBase->u3.pUsing==0) ) continue; + if( sqlite3IdListIndex(pBase->u3.pUsing, zName)>=0 ) return 1; + } + return 0; +} + + +/* +** This routine is a Walker callback for "expanding" a SELECT statement. +** "Expanding" means to do the following: +** +** (1) Make sure VDBE cursor numbers have been assigned to every +** element of the FROM clause. +** +** (2) Fill in the pTabList->a[].pTab fields in the SrcList that +** defines FROM clause. When views appear in the FROM clause, +** fill pTabList->a[].pSelect with a copy of the SELECT statement +** that implements the view. A copy is made of the view's SELECT +** statement so that we can freely modify or delete that statement +** without worrying about messing up the persistent representation +** of the view. +** +** (3) Add terms to the WHERE clause to accommodate the NATURAL keyword +** on joins and the ON and USING clause of joins. +** +** (4) Scan the list of columns in the result set (pEList) looking +** for instances of the "*" operator or the TABLE.* operator. +** If found, expand each "*" to be every column in every table +** and TABLE.* to be every column in TABLE. +** +*/ +static int selectExpander(Walker *pWalker, Select *p){ + Parse *pParse = pWalker->pParse; + int i, j, k, rc; + SrcList *pTabList; + ExprList *pEList; + SrcItem *pFrom; + sqlite3 *db = pParse->db; + Expr *pE, *pRight, *pExpr; + u16 selFlags = p->selFlags; + u32 elistFlags = 0; + + p->selFlags |= SF_Expanded; + if( db->mallocFailed ){ + return WRC_Abort; + } + assert( p->pSrc!=0 ); + if( (selFlags & SF_Expanded)!=0 ){ + return WRC_Prune; + } + if( pWalker->eCode ){ + /* Renumber selId because it has been copied from a view */ + p->selId = ++pParse->nSelect; + } + pTabList = p->pSrc; + pEList = p->pEList; + if( pParse->pWith && (p->selFlags & SF_View) ){ + if( p->pWith==0 ){ + p->pWith = (With*)sqlite3DbMallocZero(db, sizeof(With)); + if( p->pWith==0 ){ + return WRC_Abort; + } + } + p->pWith->bView = 1; + } + sqlite3WithPush(pParse, p->pWith, 0); + + /* Make sure cursor numbers have been assigned to all entries in + ** the FROM clause of the SELECT statement. + */ + sqlite3SrcListAssignCursors(pParse, pTabList); + + /* Look up every table named in the FROM clause of the select. If + ** an entry of the FROM clause is a subquery instead of a table or view, + ** then create a transient table structure to describe the subquery. + */ + for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){ + Table *pTab; + assert( pFrom->fg.isRecursive==0 || pFrom->pTab!=0 ); + if( pFrom->pTab ) continue; + assert( pFrom->fg.isRecursive==0 ); + if( pFrom->zName==0 ){ +#ifndef SQLITE_OMIT_SUBQUERY + Select *pSel = pFrom->pSelect; + /* A sub-query in the FROM clause of a SELECT */ + assert( pSel!=0 ); + assert( pFrom->pTab==0 ); + if( sqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort; + if( sqlite3ExpandSubquery(pParse, pFrom) ) return WRC_Abort; +#endif +#ifndef SQLITE_OMIT_CTE + }else if( (rc = resolveFromTermToCte(pParse, pWalker, pFrom))!=0 ){ + if( rc>1 ) return WRC_Abort; + pTab = pFrom->pTab; + assert( pTab!=0 ); +#endif + }else{ + /* An ordinary table or view name in the FROM clause */ + assert( pFrom->pTab==0 ); + pFrom->pTab = pTab = sqlite3LocateTableItem(pParse, 0, pFrom); + if( pTab==0 ) return WRC_Abort; + if( pTab->nTabRef>=0xffff ){ + sqlite3ErrorMsg(pParse, "too many references to \"%s\": max 65535", + pTab->zName); + pFrom->pTab = 0; + return WRC_Abort; + } + pTab->nTabRef++; + if( !IsVirtual(pTab) && cannotBeFunction(pParse, pFrom) ){ + return WRC_Abort; + } +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) + if( !IsOrdinaryTable(pTab) ){ + i16 nCol; + u8 eCodeOrig = pWalker->eCode; + if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort; + assert( pFrom->pSelect==0 ); + if( IsView(pTab) ){ + if( (db->flags & SQLITE_EnableView)==0 + && pTab->pSchema!=db->aDb[1].pSchema + ){ + sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited", + pTab->zName); + } + pFrom->pSelect = sqlite3SelectDup(db, pTab->u.view.pSelect, 0); + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + else if( ALWAYS(IsVirtual(pTab)) + && pFrom->fg.fromDDL + && ALWAYS(pTab->u.vtab.p!=0) + && pTab->u.vtab.p->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0) + ){ + sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"", + pTab->zName); + } + assert( SQLITE_VTABRISK_Normal==1 && SQLITE_VTABRISK_High==2 ); +#endif + nCol = pTab->nCol; + pTab->nCol = -1; + pWalker->eCode = 1; /* Turn on Select.selId renumbering */ + sqlite3WalkSelect(pWalker, pFrom->pSelect); + pWalker->eCode = eCodeOrig; + pTab->nCol = nCol; + } +#endif + } + + /* Locate the index named by the INDEXED BY clause, if any. */ + if( pFrom->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pFrom) ){ + return WRC_Abort; + } + } + + /* Process NATURAL keywords, and ON and USING clauses of joins. + */ + assert( db->mallocFailed==0 || pParse->nErr!=0 ); + if( pParse->nErr || sqlite3ProcessJoin(pParse, p) ){ + return WRC_Abort; + } + + /* For every "*" that occurs in the column list, insert the names of + ** all columns in all tables. And for every TABLE.* insert the names + ** of all columns in TABLE. The parser inserted a special expression + ** with the TK_ASTERISK operator for each "*" that it found in the column + ** list. The following code just has to locate the TK_ASTERISK + ** expressions and expand each one to the list of all columns in + ** all tables. + ** + ** The first loop just checks to see if there are any "*" operators + ** that need expanding. + */ + for(k=0; k<pEList->nExpr; k++){ + pE = pEList->a[k].pExpr; + if( pE->op==TK_ASTERISK ) break; + assert( pE->op!=TK_DOT || pE->pRight!=0 ); + assert( pE->op!=TK_DOT || (pE->pLeft!=0 && pE->pLeft->op==TK_ID) ); + if( pE->op==TK_DOT && pE->pRight->op==TK_ASTERISK ) break; + elistFlags |= pE->flags; + } + if( k<pEList->nExpr ){ + /* + ** If we get here it means the result set contains one or more "*" + ** operators that need to be expanded. Loop through each expression + ** in the result set and expand them one by one. + */ + struct ExprList_item *a = pEList->a; + ExprList *pNew = 0; + int flags = pParse->db->flags; + int longNames = (flags & SQLITE_FullColNames)!=0 + && (flags & SQLITE_ShortColNames)==0; + + for(k=0; k<pEList->nExpr; k++){ + pE = a[k].pExpr; + elistFlags |= pE->flags; + pRight = pE->pRight; + assert( pE->op!=TK_DOT || pRight!=0 ); + if( pE->op!=TK_ASTERISK + && (pE->op!=TK_DOT || pRight->op!=TK_ASTERISK) + ){ + /* This particular expression does not need to be expanded. + */ + pNew = sqlite3ExprListAppend(pParse, pNew, a[k].pExpr); + if( pNew ){ + pNew->a[pNew->nExpr-1].zEName = a[k].zEName; + pNew->a[pNew->nExpr-1].fg.eEName = a[k].fg.eEName; + a[k].zEName = 0; + } + a[k].pExpr = 0; + }else{ + /* This expression is a "*" or a "TABLE.*" and needs to be + ** expanded. */ + int tableSeen = 0; /* Set to 1 when TABLE matches */ + char *zTName = 0; /* text of name of TABLE */ + int iErrOfst; + if( pE->op==TK_DOT ){ + assert( pE->pLeft!=0 ); + assert( !ExprHasProperty(pE->pLeft, EP_IntValue) ); + zTName = pE->pLeft->u.zToken; + assert( ExprUseWOfst(pE->pLeft) ); + iErrOfst = pE->pRight->w.iOfst; + }else{ + assert( ExprUseWOfst(pE) ); + iErrOfst = pE->w.iOfst; + } + for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){ + Table *pTab = pFrom->pTab; /* Table for this data source */ + ExprList *pNestedFrom; /* Result-set of a nested FROM clause */ + char *zTabName; /* AS name for this data source */ + const char *zSchemaName = 0; /* Schema name for this data source */ + int iDb; /* Schema index for this data src */ + IdList *pUsing; /* USING clause for pFrom[1] */ + + if( (zTabName = pFrom->zAlias)==0 ){ + zTabName = pTab->zName; + } + if( db->mallocFailed ) break; + assert( (int)pFrom->fg.isNestedFrom == IsNestedFrom(pFrom->pSelect) ); + if( pFrom->fg.isNestedFrom ){ + assert( pFrom->pSelect!=0 ); + pNestedFrom = pFrom->pSelect->pEList; + assert( pNestedFrom!=0 ); + assert( pNestedFrom->nExpr==pTab->nCol ); + }else{ + if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){ + continue; + } + pNestedFrom = 0; + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + zSchemaName = iDb>=0 ? db->aDb[iDb].zDbSName : "*"; + } + if( i+1<pTabList->nSrc + && pFrom[1].fg.isUsing + && (selFlags & SF_NestedFrom)!=0 + ){ + int ii; + pUsing = pFrom[1].u3.pUsing; + for(ii=0; ii<pUsing->nId; ii++){ + const char *zUName = pUsing->a[ii].zName; + pRight = sqlite3Expr(db, TK_ID, zUName); + sqlite3ExprSetErrorOffset(pRight, iErrOfst); + pNew = sqlite3ExprListAppend(pParse, pNew, pRight); + if( pNew ){ + struct ExprList_item *pX = &pNew->a[pNew->nExpr-1]; + assert( pX->zEName==0 ); + pX->zEName = sqlite3MPrintf(db,"..%s", zUName); + pX->fg.eEName = ENAME_TAB; + pX->fg.bUsingTerm = 1; + } + } + }else{ + pUsing = 0; + } + for(j=0; j<pTab->nCol; j++){ + char *zName = pTab->aCol[j].zCnName; + struct ExprList_item *pX; /* Newly added ExprList term */ + + assert( zName ); + if( zTName + && pNestedFrom + && sqlite3MatchEName(&pNestedFrom->a[j], 0, zTName, 0)==0 + ){ + continue; + } + + /* If a column is marked as 'hidden', omit it from the expanded + ** result-set list unless the SELECT has the SF_IncludeHidden + ** bit set. + */ + if( (p->selFlags & SF_IncludeHidden)==0 + && IsHiddenColumn(&pTab->aCol[j]) + ){ + continue; + } + if( (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0 + && zTName==0 + && (selFlags & (SF_NestedFrom))==0 + ){ + continue; + } + tableSeen = 1; + + if( i>0 && zTName==0 && (selFlags & SF_NestedFrom)==0 ){ + if( pFrom->fg.isUsing + && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0 + ){ + /* In a join with a USING clause, omit columns in the + ** using clause from the table on the right. */ + continue; + } + } + pRight = sqlite3Expr(db, TK_ID, zName); + if( (pTabList->nSrc>1 + && ( (pFrom->fg.jointype & JT_LTORJ)==0 + || (selFlags & SF_NestedFrom)!=0 + || !inAnyUsingClause(zName,pFrom,pTabList->nSrc-i-1) + ) + ) + || IN_RENAME_OBJECT + ){ + Expr *pLeft; + pLeft = sqlite3Expr(db, TK_ID, zTabName); + pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); + if( IN_RENAME_OBJECT && pE->pLeft ){ + sqlite3RenameTokenRemap(pParse, pLeft, pE->pLeft); + } + if( zSchemaName ){ + pLeft = sqlite3Expr(db, TK_ID, zSchemaName); + pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pExpr); + } + }else{ + pExpr = pRight; + } + sqlite3ExprSetErrorOffset(pExpr, iErrOfst); + pNew = sqlite3ExprListAppend(pParse, pNew, pExpr); + if( pNew==0 ){ + break; /* OOM */ + } + pX = &pNew->a[pNew->nExpr-1]; + assert( pX->zEName==0 ); + if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){ + if( pNestedFrom ){ + pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName); + testcase( pX->zEName==0 ); + }else{ + pX->zEName = sqlite3MPrintf(db, "%s.%s.%s", + zSchemaName, zTabName, zName); + testcase( pX->zEName==0 ); + } + pX->fg.eEName = ENAME_TAB; + if( (pFrom->fg.isUsing + && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0) + || (pUsing && sqlite3IdListIndex(pUsing, zName)>=0) + || (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0 + ){ + pX->fg.bNoExpand = 1; + } + }else if( longNames ){ + pX->zEName = sqlite3MPrintf(db, "%s.%s", zTabName, zName); + pX->fg.eEName = ENAME_NAME; + }else{ + pX->zEName = sqlite3DbStrDup(db, zName); + pX->fg.eEName = ENAME_NAME; + } + } + } + if( !tableSeen ){ + if( zTName ){ + sqlite3ErrorMsg(pParse, "no such table: %s", zTName); + }else{ + sqlite3ErrorMsg(pParse, "no tables specified"); + } + } + } + } + sqlite3ExprListDelete(db, pEList); + p->pEList = pNew; + } + if( p->pEList ){ + if( p->pEList->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){ + sqlite3ErrorMsg(pParse, "too many columns in result set"); + return WRC_Abort; + } + if( (elistFlags & (EP_HasFunc|EP_Subquery))!=0 ){ + p->selFlags |= SF_ComplexResult; + } + } +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x8 ){ + TREETRACE(0x8,pParse,p,("After result-set wildcard expansion:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + return WRC_Continue; +} + +#if SQLITE_DEBUG +/* +** Always assert. This xSelectCallback2 implementation proves that the +** xSelectCallback2 is never invoked. +*/ +SQLITE_PRIVATE void sqlite3SelectWalkAssert2(Walker *NotUsed, Select *NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + assert( 0 ); +} +#endif +/* +** This routine "expands" a SELECT statement and all of its subqueries. +** For additional information on what it means to "expand" a SELECT +** statement, see the comment on the selectExpand worker callback above. +** +** Expanding a SELECT statement is the first step in processing a +** SELECT statement. The SELECT statement must be expanded before +** name resolution is performed. +** +** If anything goes wrong, an error message is written into pParse. +** The calling function can detect the problem by looking at pParse->nErr +** and/or pParse->db->mallocFailed. +*/ +static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){ + Walker w; + w.xExprCallback = sqlite3ExprWalkNoop; + w.pParse = pParse; + if( OK_IF_ALWAYS_TRUE(pParse->hasCompound) ){ + w.xSelectCallback = convertCompoundSelectToSubquery; + w.xSelectCallback2 = 0; + sqlite3WalkSelect(&w, pSelect); + } + w.xSelectCallback = selectExpander; + w.xSelectCallback2 = sqlite3SelectPopWith; + w.eCode = 0; + sqlite3WalkSelect(&w, pSelect); +} + + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** This is a Walker.xSelectCallback callback for the sqlite3SelectTypeInfo() +** interface. +** +** For each FROM-clause subquery, add Column.zType, Column.zColl, and +** Column.affinity information to the Table structure that represents +** the result set of that subquery. +** +** The Table structure that represents the result set was constructed +** by selectExpander() but the type and collation and affinity information +** was omitted at that point because identifiers had not yet been resolved. +** This routine is called after identifier resolution. +*/ +static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ + Parse *pParse; + int i; + SrcList *pTabList; + SrcItem *pFrom; + + assert( p->selFlags & SF_Resolved ); + if( p->selFlags & SF_HasTypeInfo ) return; + p->selFlags |= SF_HasTypeInfo; + pParse = pWalker->pParse; + pTabList = p->pSrc; + for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){ + Table *pTab = pFrom->pTab; + assert( pTab!=0 ); + if( (pTab->tabFlags & TF_Ephemeral)!=0 ){ + /* A sub-query in the FROM clause of a SELECT */ + Select *pSel = pFrom->pSelect; + if( pSel ){ + sqlite3SubqueryColumnTypes(pParse, pTab, pSel, SQLITE_AFF_NONE); + } + } + } +} +#endif + + +/* +** This routine adds datatype and collating sequence information to +** the Table structures of all FROM-clause subqueries in a +** SELECT statement. +** +** Use this routine after name resolution. +*/ +static void sqlite3SelectAddTypeInfo(Parse *pParse, Select *pSelect){ +#ifndef SQLITE_OMIT_SUBQUERY + Walker w; + w.xSelectCallback = sqlite3SelectWalkNoop; + w.xSelectCallback2 = selectAddSubqueryTypeInfo; + w.xExprCallback = sqlite3ExprWalkNoop; + w.pParse = pParse; + sqlite3WalkSelect(&w, pSelect); +#endif +} + + +/* +** This routine sets up a SELECT statement for processing. The +** following is accomplished: +** +** * VDBE Cursor numbers are assigned to all FROM-clause terms. +** * Ephemeral Table objects are created for all FROM-clause subqueries. +** * ON and USING clauses are shifted into WHERE statements +** * Wildcards "*" and "TABLE.*" in result sets are expanded. +** * Identifiers in expression are matched to tables. +** +** This routine acts recursively on all subqueries within the SELECT. +*/ +SQLITE_PRIVATE void sqlite3SelectPrep( + Parse *pParse, /* The parser context */ + Select *p, /* The SELECT statement being coded. */ + NameContext *pOuterNC /* Name context for container */ +){ + assert( p!=0 || pParse->db->mallocFailed ); + assert( pParse->db->pParse==pParse ); + if( pParse->db->mallocFailed ) return; + if( p->selFlags & SF_HasTypeInfo ) return; + sqlite3SelectExpand(pParse, p); + if( pParse->nErr ) return; + sqlite3ResolveSelectNames(pParse, p, pOuterNC); + if( pParse->nErr ) return; + sqlite3SelectAddTypeInfo(pParse, p); +} + +#if TREETRACE_ENABLED +/* +** Display all information about an AggInfo object +*/ +static void printAggInfo(AggInfo *pAggInfo){ + int ii; + for(ii=0; ii<pAggInfo->nColumn; ii++){ + struct AggInfo_col *pCol = &pAggInfo->aCol[ii]; + sqlite3DebugPrintf( + "agg-column[%d] pTab=%s iTable=%d iColumn=%d iMem=%d" + " iSorterColumn=%d %s\n", + ii, pCol->pTab ? pCol->pTab->zName : "NULL", + pCol->iTable, pCol->iColumn, pAggInfo->iFirstReg+ii, + pCol->iSorterColumn, + ii>=pAggInfo->nAccumulator ? "" : " Accumulator"); + sqlite3TreeViewExpr(0, pAggInfo->aCol[ii].pCExpr, 0); + } + for(ii=0; ii<pAggInfo->nFunc; ii++){ + sqlite3DebugPrintf("agg-func[%d]: iMem=%d\n", + ii, pAggInfo->iFirstReg+pAggInfo->nColumn+ii); + sqlite3TreeViewExpr(0, pAggInfo->aFunc[ii].pFExpr, 0); + } +} +#endif /* TREETRACE_ENABLED */ + +/* +** Analyze the arguments to aggregate functions. Create new pAggInfo->aCol[] +** entries for columns that are arguments to aggregate functions but which +** are not otherwise used. +** +** The aCol[] entries in AggInfo prior to nAccumulator are columns that +** are referenced outside of aggregate functions. These might be columns +** that are part of the GROUP by clause, for example. Other database engines +** would throw an error if there is a column reference that is not in the +** GROUP BY clause and that is not part of an aggregate function argument. +** But SQLite allows this. +** +** The aCol[] entries beginning with the aCol[nAccumulator] and following +** are column references that are used exclusively as arguments to +** aggregate functions. This routine is responsible for computing +** (or recomputing) those aCol[] entries. +*/ +static void analyzeAggFuncArgs( + AggInfo *pAggInfo, + NameContext *pNC +){ + int i; + assert( pAggInfo!=0 ); + assert( pAggInfo->iFirstReg==0 ); + pNC->ncFlags |= NC_InAggFunc; + for(i=0; i<pAggInfo->nFunc; i++){ + Expr *pExpr = pAggInfo->aFunc[i].pFExpr; + assert( ExprUseXList(pExpr) ); + sqlite3ExprAnalyzeAggList(pNC, pExpr->x.pList); +#ifndef SQLITE_OMIT_WINDOWFUNC + assert( !IsWindowFunc(pExpr) ); + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + sqlite3ExprAnalyzeAggregates(pNC, pExpr->y.pWin->pFilter); + } +#endif + } + pNC->ncFlags &= ~NC_InAggFunc; +} + +/* +** An index on expressions is being used in the inner loop of an +** aggregate query with a GROUP BY clause. This routine attempts +** to adjust the AggInfo object to take advantage of index and to +** perhaps use the index as a covering index. +** +*/ +static void optimizeAggregateUseOfIndexedExpr( + Parse *pParse, /* Parsing context */ + Select *pSelect, /* The SELECT statement being processed */ + AggInfo *pAggInfo, /* The aggregate info */ + NameContext *pNC /* Name context used to resolve agg-func args */ +){ + assert( pAggInfo->iFirstReg==0 ); + assert( pSelect!=0 ); + assert( pSelect->pGroupBy!=0 ); + pAggInfo->nColumn = pAggInfo->nAccumulator; + if( ALWAYS(pAggInfo->nSortingColumn>0) ){ + int mx = pSelect->pGroupBy->nExpr - 1; + int j, k; + for(j=0; j<pAggInfo->nColumn; j++){ + k = pAggInfo->aCol[j].iSorterColumn; + if( k>mx ) mx = k; + } + pAggInfo->nSortingColumn = mx+1; + } + analyzeAggFuncArgs(pAggInfo, pNC); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20 ){ + IndexedExpr *pIEpr; + TREETRACE(0x20, pParse, pSelect, + ("AggInfo (possibly) adjusted for Indexed Exprs\n")); + sqlite3TreeViewSelect(0, pSelect, 0); + for(pIEpr=pParse->pIdxEpr; pIEpr; pIEpr=pIEpr->pIENext){ + printf("data-cursor=%d index={%d,%d}\n", + pIEpr->iDataCur, pIEpr->iIdxCur, pIEpr->iIdxCol); + sqlite3TreeViewExpr(0, pIEpr->pExpr, 0); + } + printAggInfo(pAggInfo); + } +#else + UNUSED_PARAMETER(pSelect); + UNUSED_PARAMETER(pParse); +#endif +} + +/* +** Walker callback for aggregateConvertIndexedExprRefToColumn(). +*/ +static int aggregateIdxEprRefToColCallback(Walker *pWalker, Expr *pExpr){ + AggInfo *pAggInfo; + struct AggInfo_col *pCol; + UNUSED_PARAMETER(pWalker); + if( pExpr->pAggInfo==0 ) return WRC_Continue; + if( pExpr->op==TK_AGG_COLUMN ) return WRC_Continue; + if( pExpr->op==TK_AGG_FUNCTION ) return WRC_Continue; + if( pExpr->op==TK_IF_NULL_ROW ) return WRC_Continue; + pAggInfo = pExpr->pAggInfo; + if( NEVER(pExpr->iAgg>=pAggInfo->nColumn) ) return WRC_Continue; + assert( pExpr->iAgg>=0 ); + pCol = &pAggInfo->aCol[pExpr->iAgg]; + pExpr->op = TK_AGG_COLUMN; + pExpr->iTable = pCol->iTable; + pExpr->iColumn = pCol->iColumn; + ExprClearProperty(pExpr, EP_Skip|EP_Collate|EP_Unlikely); + return WRC_Prune; +} + +/* +** Convert every pAggInfo->aFunc[].pExpr such that any node within +** those expressions that has pAppInfo set is changed into a TK_AGG_COLUMN +** opcode. +*/ +static void aggregateConvertIndexedExprRefToColumn(AggInfo *pAggInfo){ + int i; + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = aggregateIdxEprRefToColCallback; + for(i=0; i<pAggInfo->nFunc; i++){ + sqlite3WalkExpr(&w, pAggInfo->aFunc[i].pFExpr); + } +} + + +/* +** Allocate a block of registers so that there is one register for each +** pAggInfo->aCol[] and pAggInfo->aFunc[] entry in pAggInfo. The first +** register in this block is stored in pAggInfo->iFirstReg. +** +** This routine may only be called once for each AggInfo object. Prior +** to calling this routine: +** +** * The aCol[] and aFunc[] arrays may be modified +** * The AggInfoColumnReg() and AggInfoFuncReg() macros may not be used +** +** After calling this routine: +** +** * The aCol[] and aFunc[] arrays are fixed +** * The AggInfoColumnReg() and AggInfoFuncReg() macros may be used +** +*/ +static void assignAggregateRegisters(Parse *pParse, AggInfo *pAggInfo){ + assert( pAggInfo!=0 ); + assert( pAggInfo->iFirstReg==0 ); + pAggInfo->iFirstReg = pParse->nMem + 1; + pParse->nMem += pAggInfo->nColumn + pAggInfo->nFunc; +} + +/* +** Reset the aggregate accumulator. +** +** The aggregate accumulator is a set of memory cells that hold +** intermediate results while calculating an aggregate. This +** routine generates code that stores NULLs in all of those memory +** cells. +*/ +static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ + Vdbe *v = pParse->pVdbe; + int i; + struct AggInfo_func *pFunc; + int nReg = pAggInfo->nFunc + pAggInfo->nColumn; + assert( pAggInfo->iFirstReg>0 ); + assert( pParse->db->pParse==pParse ); + assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); + if( nReg==0 ) return; + if( pParse->nErr ) return; + sqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->iFirstReg, + pAggInfo->iFirstReg+nReg-1); + for(pFunc=pAggInfo->aFunc, i=0; i<pAggInfo->nFunc; i++, pFunc++){ + if( pFunc->iDistinct>=0 ){ + Expr *pE = pFunc->pFExpr; + assert( ExprUseXList(pE) ); + if( pE->x.pList==0 || pE->x.pList->nExpr!=1 ){ + sqlite3ErrorMsg(pParse, "DISTINCT aggregates must have exactly one " + "argument"); + pFunc->iDistinct = -1; + }else{ + KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pE->x.pList,0,0); + pFunc->iDistAddr = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, + pFunc->iDistinct, 0, 0, (char*)pKeyInfo, P4_KEYINFO); + ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s(DISTINCT)", + pFunc->pFunc->zName)); + } + } + } +} + +/* +** Invoke the OP_AggFinalize opcode for every aggregate function +** in the AggInfo structure. +*/ +static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ + Vdbe *v = pParse->pVdbe; + int i; + struct AggInfo_func *pF; + for(i=0, pF=pAggInfo->aFunc; i<pAggInfo->nFunc; i++, pF++){ + ExprList *pList; + assert( ExprUseXList(pF->pFExpr) ); + pList = pF->pFExpr->x.pList; + sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i), + pList ? pList->nExpr : 0); + sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); + } +} + + +/* +** Generate code that will update the accumulator memory cells for an +** aggregate based on the current cursor position. +** +** If regAcc is non-zero and there are no min() or max() aggregates +** in pAggInfo, then only populate the pAggInfo->nAccumulator accumulator +** registers if register regAcc contains 0. The caller will take care +** of setting and clearing regAcc. +*/ +static void updateAccumulator( + Parse *pParse, + int regAcc, + AggInfo *pAggInfo, + int eDistinctType +){ + Vdbe *v = pParse->pVdbe; + int i; + int regHit = 0; + int addrHitTest = 0; + struct AggInfo_func *pF; + struct AggInfo_col *pC; + + assert( pAggInfo->iFirstReg>0 ); + if( pParse->nErr ) return; + pAggInfo->directMode = 1; + for(i=0, pF=pAggInfo->aFunc; i<pAggInfo->nFunc; i++, pF++){ + int nArg; + int addrNext = 0; + int regAgg; + ExprList *pList; + assert( ExprUseXList(pF->pFExpr) ); + assert( !IsWindowFunc(pF->pFExpr) ); + pList = pF->pFExpr->x.pList; + if( ExprHasProperty(pF->pFExpr, EP_WinFunc) ){ + Expr *pFilter = pF->pFExpr->y.pWin->pFilter; + if( pAggInfo->nAccumulator + && (pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) + && regAcc + ){ + /* If regAcc==0, there there exists some min() or max() function + ** without a FILTER clause that will ensure the magnet registers + ** are populated. */ + if( regHit==0 ) regHit = ++pParse->nMem; + /* If this is the first row of the group (regAcc contains 0), clear the + ** "magnet" register regHit so that the accumulator registers + ** are populated if the FILTER clause jumps over the the + ** invocation of min() or max() altogether. Or, if this is not + ** the first row (regAcc contains 1), set the magnet register so that + ** the accumulators are not populated unless the min()/max() is invoked + ** and indicates that they should be. */ + sqlite3VdbeAddOp2(v, OP_Copy, regAcc, regHit); + } + addrNext = sqlite3VdbeMakeLabel(pParse); + sqlite3ExprIfFalse(pParse, pFilter, addrNext, SQLITE_JUMPIFNULL); + } + if( pList ){ + nArg = pList->nExpr; + regAgg = sqlite3GetTempRange(pParse, nArg); + sqlite3ExprCodeExprList(pParse, pList, regAgg, 0, SQLITE_ECEL_DUP); + }else{ + nArg = 0; + regAgg = 0; + } + if( pF->iDistinct>=0 && pList ){ + if( addrNext==0 ){ + addrNext = sqlite3VdbeMakeLabel(pParse); + } + pF->iDistinct = codeDistinct(pParse, eDistinctType, + pF->iDistinct, addrNext, pList, regAgg); + } + if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ + CollSeq *pColl = 0; + struct ExprList_item *pItem; + int j; + assert( pList!=0 ); /* pList!=0 if pF->pFunc has NEEDCOLL */ + for(j=0, pItem=pList->a; !pColl && j<nArg; j++, pItem++){ + pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr); + } + if( !pColl ){ + pColl = pParse->db->pDfltColl; + } + if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem; + sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ); + } + sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); + sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, (u8)nArg); + sqlite3ReleaseTempRange(pParse, regAgg, nArg); + if( addrNext ){ + sqlite3VdbeResolveLabel(v, addrNext); + } + } + if( regHit==0 && pAggInfo->nAccumulator ){ + regHit = regAcc; + } + if( regHit ){ + addrHitTest = sqlite3VdbeAddOp1(v, OP_If, regHit); VdbeCoverage(v); + } + for(i=0, pC=pAggInfo->aCol; i<pAggInfo->nAccumulator; i++, pC++){ + sqlite3ExprCode(pParse, pC->pCExpr, AggInfoColumnReg(pAggInfo,i)); + } + + pAggInfo->directMode = 0; + if( addrHitTest ){ + sqlite3VdbeJumpHereOrPopInst(v, addrHitTest); + } +} + +/* +** Add a single OP_Explain instruction to the VDBE to explain a simple +** count(*) query ("SELECT count(*) FROM pTab"). +*/ +#ifndef SQLITE_OMIT_EXPLAIN +static void explainSimpleCount( + Parse *pParse, /* Parse context */ + Table *pTab, /* Table being queried */ + Index *pIdx /* Index used to optimize scan, or NULL */ +){ + if( pParse->explain==2 ){ + int bCover = (pIdx!=0 && (HasRowid(pTab) || !IsPrimaryKeyIndex(pIdx))); + sqlite3VdbeExplain(pParse, 0, "SCAN %s%s%s", + pTab->zName, + bCover ? " USING COVERING INDEX " : "", + bCover ? pIdx->zName : "" + ); + } +} +#else +# define explainSimpleCount(a,b,c) +#endif + +/* +** sqlite3WalkExpr() callback used by havingToWhere(). +** +** If the node passed to the callback is a TK_AND node, return +** WRC_Continue to tell sqlite3WalkExpr() to iterate through child nodes. +** +** Otherwise, return WRC_Prune. In this case, also check if the +** sub-expression matches the criteria for being moved to the WHERE +** clause. If so, add it to the WHERE clause and replace the sub-expression +** within the HAVING expression with a constant "1". +*/ +static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op!=TK_AND ){ + Select *pS = pWalker->u.pSelect; + /* This routine is called before the HAVING clause of the current + ** SELECT is analyzed for aggregates. So if pExpr->pAggInfo is set + ** here, it indicates that the expression is a correlated reference to a + ** column from an outer aggregate query, or an aggregate function that + ** belongs to an outer query. Do not move the expression to the WHERE + ** clause in this obscure case, as doing so may corrupt the outer Select + ** statements AggInfo structure. */ + if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy) + && ExprAlwaysFalse(pExpr)==0 + && pExpr->pAggInfo==0 + ){ + sqlite3 *db = pWalker->pParse->db; + Expr *pNew = sqlite3Expr(db, TK_INTEGER, "1"); + if( pNew ){ + Expr *pWhere = pS->pWhere; + SWAP(Expr, *pNew, *pExpr); + pNew = sqlite3ExprAnd(pWalker->pParse, pWhere, pNew); + pS->pWhere = pNew; + pWalker->eCode = 1; + } + } + return WRC_Prune; + } + return WRC_Continue; +} + +/* +** Transfer eligible terms from the HAVING clause of a query, which is +** processed after grouping, to the WHERE clause, which is processed before +** grouping. For example, the query: +** +** SELECT * FROM <tables> WHERE a=? GROUP BY b HAVING b=? AND c=? +** +** can be rewritten as: +** +** SELECT * FROM <tables> WHERE a=? AND b=? GROUP BY b HAVING c=? +** +** A term of the HAVING expression is eligible for transfer if it consists +** entirely of constants and expressions that are also GROUP BY terms that +** use the "BINARY" collation sequence. +*/ +static void havingToWhere(Parse *pParse, Select *p){ + Walker sWalker; + memset(&sWalker, 0, sizeof(sWalker)); + sWalker.pParse = pParse; + sWalker.xExprCallback = havingToWhereExprCb; + sWalker.u.pSelect = p; + sqlite3WalkExpr(&sWalker, p->pHaving); +#if TREETRACE_ENABLED + if( sWalker.eCode && (sqlite3TreeTrace & 0x100)!=0 ){ + TREETRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif +} + +/* +** Check to see if the pThis entry of pTabList is a self-join of another view. +** Search FROM-clause entries in the range of iFirst..iEnd, including iFirst +** but stopping before iEnd. +** +** If pThis is a self-join, then return the SrcItem for the first other +** instance of that view found. If pThis is not a self-join then return 0. +*/ +static SrcItem *isSelfJoinView( + SrcList *pTabList, /* Search for self-joins in this FROM clause */ + SrcItem *pThis, /* Search for prior reference to this subquery */ + int iFirst, int iEnd /* Range of FROM-clause entries to search. */ +){ + SrcItem *pItem; + assert( pThis->pSelect!=0 ); + if( pThis->pSelect->selFlags & SF_PushDown ) return 0; + while( iFirst<iEnd ){ + Select *pS1; + pItem = &pTabList->a[iFirst++]; + if( pItem->pSelect==0 ) continue; + if( pItem->fg.viaCoroutine ) continue; + if( pItem->zName==0 ) continue; + assert( pItem->pTab!=0 ); + assert( pThis->pTab!=0 ); + if( pItem->pTab->pSchema!=pThis->pTab->pSchema ) continue; + if( sqlite3_stricmp(pItem->zName, pThis->zName)!=0 ) continue; + pS1 = pItem->pSelect; + if( pItem->pTab->pSchema==0 && pThis->pSelect->selId!=pS1->selId ){ + /* The query flattener left two different CTE tables with identical + ** names in the same FROM clause. */ + continue; + } + if( pItem->pSelect->selFlags & SF_PushDown ){ + /* The view was modified by some other optimization such as + ** pushDownWhereTerms() */ + continue; + } + return pItem; + } + return 0; +} + +/* +** Deallocate a single AggInfo object +*/ +static void agginfoFree(sqlite3 *db, AggInfo *p){ + sqlite3DbFree(db, p->aCol); + sqlite3DbFree(db, p->aFunc); + sqlite3DbFreeNN(db, p); +} + +/* +** Attempt to transform a query of the form +** +** SELECT count(*) FROM (SELECT x FROM t1 UNION ALL SELECT y FROM t2) +** +** Into this: +** +** SELECT (SELECT count(*) FROM t1)+(SELECT count(*) FROM t2) +** +** The transformation only works if all of the following are true: +** +** * The subquery is a UNION ALL of two or more terms +** * The subquery does not have a LIMIT clause +** * There is no WHERE or GROUP BY or HAVING clauses on the subqueries +** * The outer query is a simple count(*) with no WHERE clause or other +** extraneous syntax. +** +** Return TRUE if the optimization is undertaken. +*/ +static int countOfViewOptimization(Parse *pParse, Select *p){ + Select *pSub, *pPrior; + Expr *pExpr; + Expr *pCount; + sqlite3 *db; + if( (p->selFlags & SF_Aggregate)==0 ) return 0; /* This is an aggregate */ + if( p->pEList->nExpr!=1 ) return 0; /* Single result column */ + if( p->pWhere ) return 0; + if( p->pHaving ) return 0; + if( p->pGroupBy ) return 0; + if( p->pOrderBy ) return 0; + pExpr = p->pEList->a[0].pExpr; + if( pExpr->op!=TK_AGG_FUNCTION ) return 0; /* Result is an aggregate */ + assert( ExprUseUToken(pExpr) ); + if( sqlite3_stricmp(pExpr->u.zToken,"count") ) return 0; /* Is count() */ + assert( ExprUseXList(pExpr) ); + if( pExpr->x.pList!=0 ) return 0; /* Must be count(*) */ + if( p->pSrc->nSrc!=1 ) return 0; /* One table in FROM */ + if( ExprHasProperty(pExpr, EP_WinFunc) ) return 0;/* Not a window function */ + pSub = p->pSrc->a[0].pSelect; + if( pSub==0 ) return 0; /* The FROM is a subquery */ + if( pSub->pPrior==0 ) return 0; /* Must be a compound */ + if( pSub->selFlags & SF_CopyCte ) return 0; /* Not a CTE */ + do{ + if( pSub->op!=TK_ALL && pSub->pPrior ) return 0; /* Must be UNION ALL */ + if( pSub->pWhere ) return 0; /* No WHERE clause */ + if( pSub->pLimit ) return 0; /* No LIMIT clause */ + if( pSub->selFlags & SF_Aggregate ) return 0; /* Not an aggregate */ + assert( pSub->pHaving==0 ); /* Due to the previous */ + pSub = pSub->pPrior; /* Repeat over compound */ + }while( pSub ); + + /* If we reach this point then it is OK to perform the transformation */ + + db = pParse->db; + pCount = pExpr; + pExpr = 0; + pSub = p->pSrc->a[0].pSelect; + p->pSrc->a[0].pSelect = 0; + sqlite3SrcListDelete(db, p->pSrc); + p->pSrc = sqlite3DbMallocZero(pParse->db, sizeof(*p->pSrc)); + while( pSub ){ + Expr *pTerm; + pPrior = pSub->pPrior; + pSub->pPrior = 0; + pSub->pNext = 0; + pSub->selFlags |= SF_Aggregate; + pSub->selFlags &= ~SF_Compound; + pSub->nSelectRow = 0; + sqlite3ExprListDelete(db, pSub->pEList); + pTerm = pPrior ? sqlite3ExprDup(db, pCount, 0) : pCount; + pSub->pEList = sqlite3ExprListAppend(pParse, 0, pTerm); + pTerm = sqlite3PExpr(pParse, TK_SELECT, 0, 0); + sqlite3PExprAddSelect(pParse, pTerm, pSub); + if( pExpr==0 ){ + pExpr = pTerm; + }else{ + pExpr = sqlite3PExpr(pParse, TK_PLUS, pTerm, pExpr); + } + pSub = pPrior; + } + p->pEList->a[0].pExpr = pExpr; + p->selFlags &= ~SF_Aggregate; + +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x200 ){ + TREETRACE(0x200,pParse,p,("After count-of-view optimization:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + return 1; +} + +/* +** If any term of pSrc, or any SF_NestedFrom sub-query, is not the same +** as pSrcItem but has the same alias as p0, then return true. +** Otherwise return false. +*/ +static int sameSrcAlias(SrcItem *p0, SrcList *pSrc){ + int i; + for(i=0; i<pSrc->nSrc; i++){ + SrcItem *p1 = &pSrc->a[i]; + if( p1==p0 ) continue; + if( p0->pTab==p1->pTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){ + return 1; + } + if( p1->pSelect + && (p1->pSelect->selFlags & SF_NestedFrom)!=0 + && sameSrcAlias(p0, p1->pSelect->pSrc) + ){ + return 1; + } + } + return 0; +} + +/* +** Return TRUE (non-zero) if the i-th entry in the pTabList SrcList can +** be implemented as a co-routine. The i-th entry is guaranteed to be +** a subquery. +** +** The subquery is implemented as a co-routine if all of the following are +** true: +** +** (1) The subquery will likely be implemented in the outer loop of +** the query. This will be the case if any one of the following +** conditions hold: +** (a) The subquery is the only term in the FROM clause +** (b) The subquery is the left-most term and a CROSS JOIN or similar +** requires it to be the outer loop +** (c) All of the following are true: +** (i) The subquery is the left-most subquery in the FROM clause +** (ii) There is nothing that would prevent the subquery from +** being used as the outer loop if the sqlite3WhereBegin() +** routine nominates it to that position. +** (iii) The query is not a UPDATE ... FROM +** (2) The subquery is not a CTE that should be materialized because +** (a) the AS MATERIALIZED keyword is used, or +** (b) the CTE is used multiple times and does not have the +** NOT MATERIALIZED keyword +** (3) The subquery is not part of a left operand for a RIGHT JOIN +** (4) The SQLITE_Coroutine optimization disable flag is not set +** (5) The subquery is not self-joined +*/ +static int fromClauseTermCanBeCoroutine( + Parse *pParse, /* Parsing context */ + SrcList *pTabList, /* FROM clause */ + int i, /* Which term of the FROM clause holds the subquery */ + int selFlags /* Flags on the SELECT statement */ +){ + SrcItem *pItem = &pTabList->a[i]; + if( pItem->fg.isCte ){ + const CteUse *pCteUse = pItem->u2.pCteUse; + if( pCteUse->eM10d==M10d_Yes ) return 0; /* (2a) */ + if( pCteUse->nUse>=2 && pCteUse->eM10d!=M10d_No ) return 0; /* (2b) */ + } + if( pTabList->a[0].fg.jointype & JT_LTORJ ) return 0; /* (3) */ + if( OptimizationDisabled(pParse->db, SQLITE_Coroutines) ) return 0; /* (4) */ + if( isSelfJoinView(pTabList, pItem, i+1, pTabList->nSrc)!=0 ){ + return 0; /* (5) */ + } + if( i==0 ){ + if( pTabList->nSrc==1 ) return 1; /* (1a) */ + if( pTabList->a[1].fg.jointype & JT_CROSS ) return 1; /* (1b) */ + if( selFlags & SF_UpdateFrom ) return 0; /* (1c-iii) */ + return 1; + } + if( selFlags & SF_UpdateFrom ) return 0; /* (1c-iii) */ + while( 1 /*exit-by-break*/ ){ + if( pItem->fg.jointype & (JT_OUTER|JT_CROSS) ) return 0; /* (1c-ii) */ + if( i==0 ) break; + i--; + pItem--; + if( pItem->pSelect!=0 ) return 0; /* (1c-i) */ + } + return 1; +} + +/* +** Generate code for the SELECT statement given in the p argument. +** +** The results are returned according to the SelectDest structure. +** See comments in sqliteInt.h for further information. +** +** This routine returns the number of errors. If any errors are +** encountered, then an appropriate error message is left in +** pParse->zErrMsg. +** +** This routine does NOT free the Select structure passed in. The +** calling function needs to do that. +*/ +SQLITE_PRIVATE int sqlite3Select( + Parse *pParse, /* The parser context */ + Select *p, /* The SELECT statement being coded. */ + SelectDest *pDest /* What to do with the query results */ +){ + int i, j; /* Loop counters */ + WhereInfo *pWInfo; /* Return from sqlite3WhereBegin() */ + Vdbe *v; /* The virtual machine under construction */ + int isAgg; /* True for select lists like "count(*)" */ + ExprList *pEList = 0; /* List of columns to extract. */ + SrcList *pTabList; /* List of tables to select from */ + Expr *pWhere; /* The WHERE clause. May be NULL */ + ExprList *pGroupBy; /* The GROUP BY clause. May be NULL */ + Expr *pHaving; /* The HAVING clause. May be NULL */ + AggInfo *pAggInfo = 0; /* Aggregate information */ + int rc = 1; /* Value to return from this function */ + DistinctCtx sDistinct; /* Info on how to code the DISTINCT keyword */ + SortCtx sSort; /* Info on how to code the ORDER BY clause */ + int iEnd; /* Address of the end of the query */ + sqlite3 *db; /* The database connection */ + ExprList *pMinMaxOrderBy = 0; /* Added ORDER BY for min/max queries */ + u8 minMaxFlag; /* Flag for min/max queries */ + + db = pParse->db; + assert( pParse==db->pParse ); + v = sqlite3GetVdbe(pParse); + if( p==0 || pParse->nErr ){ + return 1; + } + assert( db->mallocFailed==0 ); + if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; +#if TREETRACE_ENABLED + TREETRACE(0x1,pParse,p, ("begin processing:\n", pParse->addrExplain)); + if( sqlite3TreeTrace & 0x10000 ){ + if( (sqlite3TreeTrace & 0x10001)==0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Select() at %s:%d", + __FILE__, __LINE__); + } + sqlite3ShowSelect(p); + } +#endif + + assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistFifo ); + assert( p->pOrderBy==0 || pDest->eDest!=SRT_Fifo ); + assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistQueue ); + assert( p->pOrderBy==0 || pDest->eDest!=SRT_Queue ); + if( IgnorableDistinct(pDest) ){ + assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union || + pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard || + pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo ); + /* All of these destinations are also able to ignore the ORDER BY clause */ + if( p->pOrderBy ){ +#if TREETRACE_ENABLED + TREETRACE(0x800,pParse,p, ("dropping superfluous ORDER BY:\n")); + if( sqlite3TreeTrace & 0x800 ){ + sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY"); + } +#endif + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3ExprListDelete, + p->pOrderBy); + testcase( pParse->earlyCleanup ); + p->pOrderBy = 0; + } + p->selFlags &= ~SF_Distinct; + p->selFlags |= SF_NoopOrderBy; + } + sqlite3SelectPrep(pParse, p, 0); + if( pParse->nErr ){ + goto select_end; + } + assert( db->mallocFailed==0 ); + assert( p->pEList!=0 ); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10 ){ + TREETRACE(0x10,pParse,p, ("after name resolution:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + + /* If the SF_UFSrcCheck flag is set, then this function is being called + ** as part of populating the temp table for an UPDATE...FROM statement. + ** In this case, it is an error if the target object (pSrc->a[0]) name + ** or alias is duplicated within FROM clause (pSrc->a[1..n]). + ** + ** Postgres disallows this case too. The reason is that some other + ** systems handle this case differently, and not all the same way, + ** which is just confusing. To avoid this, we follow PG's lead and + ** disallow it altogether. */ + if( p->selFlags & SF_UFSrcCheck ){ + SrcItem *p0 = &p->pSrc->a[0]; + if( sameSrcAlias(p0, p->pSrc) ){ + sqlite3ErrorMsg(pParse, + "target object/alias may not appear in FROM clause: %s", + p0->zAlias ? p0->zAlias : p0->pTab->zName + ); + goto select_end; + } + + /* Clear the SF_UFSrcCheck flag. The check has already been performed, + ** and leaving this flag set can cause errors if a compound sub-query + ** in p->pSrc is flattened into this query and this function called + ** again as part of compound SELECT processing. */ + p->selFlags &= ~SF_UFSrcCheck; + } + + if( pDest->eDest==SRT_Output ){ + sqlite3GenerateColumnNames(pParse, p); + } + +#ifndef SQLITE_OMIT_WINDOWFUNC + if( sqlite3WindowRewrite(pParse, p) ){ + assert( pParse->nErr ); + goto select_end; + } +#if TREETRACE_ENABLED + if( p->pWin && (sqlite3TreeTrace & 0x40)!=0 ){ + TREETRACE(0x40,pParse,p, ("after window rewrite:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif +#endif /* SQLITE_OMIT_WINDOWFUNC */ + pTabList = p->pSrc; + isAgg = (p->selFlags & SF_Aggregate)!=0; + memset(&sSort, 0, sizeof(sSort)); + sSort.pOrderBy = p->pOrderBy; + + /* Try to do various optimizations (flattening subqueries, and strength + ** reduction of join operators) in the FROM clause up into the main query + */ +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) + for(i=0; !p->pPrior && i<pTabList->nSrc; i++){ + SrcItem *pItem = &pTabList->a[i]; + Select *pSub = pItem->pSelect; + Table *pTab = pItem->pTab; + + /* The expander should have already created transient Table objects + ** even for FROM clause elements such as subqueries that do not correspond + ** to a real table */ + assert( pTab!=0 ); + + /* Try to simplify joins: + ** + ** LEFT JOIN -> JOIN + ** RIGHT JOIN -> JOIN + ** FULL JOIN -> RIGHT JOIN + ** + ** If terms of the i-th table are used in the WHERE clause in such a + ** way that the i-th table cannot be the NULL row of a join, then + ** perform the appropriate simplification. This is called + ** "OUTER JOIN strength reduction" in the SQLite documentation. + */ + if( (pItem->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 + && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor, + pItem->fg.jointype & JT_LTORJ) + && OptimizationEnabled(db, SQLITE_SimplifyJoin) + ){ + if( pItem->fg.jointype & JT_LEFT ){ + if( pItem->fg.jointype & JT_RIGHT ){ + TREETRACE(0x1000,pParse,p, + ("FULL-JOIN simplifies to RIGHT-JOIN on term %d\n",i)); + pItem->fg.jointype &= ~JT_LEFT; + }else{ + TREETRACE(0x1000,pParse,p, + ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); + pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); + } + } + if( pItem->fg.jointype & JT_LTORJ ){ + for(j=i+1; j<pTabList->nSrc; j++){ + SrcItem *pI2 = &pTabList->a[j]; + if( pI2->fg.jointype & JT_RIGHT ){ + if( pI2->fg.jointype & JT_LEFT ){ + TREETRACE(0x1000,pParse,p, + ("FULL-JOIN simplifies to LEFT-JOIN on term %d\n",j)); + pI2->fg.jointype &= ~JT_RIGHT; + }else{ + TREETRACE(0x1000,pParse,p, + ("RIGHT-JOIN simplifies to JOIN on term %d\n",j)); + pI2->fg.jointype &= ~(JT_RIGHT|JT_OUTER); + } + } + } + for(j=pTabList->nSrc-1; j>=i; j--){ + pTabList->a[j].fg.jointype &= ~JT_LTORJ; + if( pTabList->a[j].fg.jointype & JT_RIGHT ) break; + } + } + assert( pItem->iCursor>=0 ); + unsetJoinExpr(p->pWhere, pItem->iCursor, + pTabList->a[0].fg.jointype & JT_LTORJ); + } + + /* No further action if this term of the FROM clause is not a subquery */ + if( pSub==0 ) continue; + + /* Catch mismatch in the declared columns of a view and the number of + ** columns in the SELECT on the RHS */ + if( pTab->nCol!=pSub->pEList->nExpr ){ + sqlite3ErrorMsg(pParse, "expected %d columns for '%s' but got %d", + pTab->nCol, pTab->zName, pSub->pEList->nExpr); + goto select_end; + } + + /* Do not attempt the usual optimizations (flattening and ORDER BY + ** elimination) on a MATERIALIZED common table expression because + ** a MATERIALIZED common table expression is an optimization fence. + */ + if( pItem->fg.isCte && pItem->u2.pCteUse->eM10d==M10d_Yes ){ + continue; + } + + /* Do not try to flatten an aggregate subquery. + ** + ** Flattening an aggregate subquery is only possible if the outer query + ** is not a join. But if the outer query is not a join, then the subquery + ** will be implemented as a co-routine and there is no advantage to + ** flattening in that case. + */ + if( (pSub->selFlags & SF_Aggregate)!=0 ) continue; + assert( pSub->pGroupBy==0 ); + + /* If a FROM-clause subquery has an ORDER BY clause that is not + ** really doing anything, then delete it now so that it does not + ** interfere with query flattening. See the discussion at + ** https://sqlite.org/forum/forumpost/2d76f2bcf65d256a + ** + ** Beware of these cases where the ORDER BY clause may not be safely + ** omitted: + ** + ** (1) There is also a LIMIT clause + ** (2) The subquery was added to help with window-function + ** processing + ** (3) The subquery is in the FROM clause of an UPDATE + ** (4) The outer query uses an aggregate function other than + ** the built-in count(), min(), or max(). + ** (5) The ORDER BY isn't going to accomplish anything because + ** one of: + ** (a) The outer query has a different ORDER BY clause + ** (b) The subquery is part of a join + ** See forum post 062d576715d277c8 + ** + ** Also retain the ORDER BY if the OmitOrderBy optimization is disabled. + */ + if( pSub->pOrderBy!=0 + && (p->pOrderBy!=0 || pTabList->nSrc>1) /* Condition (5) */ + && pSub->pLimit==0 /* Condition (1) */ + && (pSub->selFlags & SF_OrderByReqd)==0 /* Condition (2) */ + && (p->selFlags & SF_OrderByReqd)==0 /* Condition (3) and (4) */ + && OptimizationEnabled(db, SQLITE_OmitOrderBy) + ){ + TREETRACE(0x800,pParse,p, + ("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1)); + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))sqlite3ExprListDelete, + pSub->pOrderBy); + pSub->pOrderBy = 0; + } + + /* If the outer query contains a "complex" result set (that is, + ** if the result set of the outer query uses functions or subqueries) + ** and if the subquery contains an ORDER BY clause and if + ** it will be implemented as a co-routine, then do not flatten. This + ** restriction allows SQL constructs like this: + ** + ** SELECT expensive_function(x) + ** FROM (SELECT x FROM tab ORDER BY y LIMIT 10); + ** + ** The expensive_function() is only computed on the 10 rows that + ** are output, rather than every row of the table. + ** + ** The requirement that the outer query have a complex result set + ** means that flattening does occur on simpler SQL constraints without + ** the expensive_function() like: + ** + ** SELECT x FROM (SELECT x FROM tab ORDER BY y LIMIT 10); + */ + if( pSub->pOrderBy!=0 + && i==0 + && (p->selFlags & SF_ComplexResult)!=0 + && (pTabList->nSrc==1 + || (pTabList->a[1].fg.jointype&(JT_OUTER|JT_CROSS))!=0) + ){ + continue; + } + + if( flattenSubquery(pParse, p, i, isAgg) ){ + if( pParse->nErr ) goto select_end; + /* This subquery can be absorbed into its parent. */ + i = -1; + } + pTabList = p->pSrc; + if( db->mallocFailed ) goto select_end; + if( !IgnorableOrderby(pDest) ){ + sSort.pOrderBy = p->pOrderBy; + } + } +#endif + +#ifndef SQLITE_OMIT_COMPOUND_SELECT + /* Handle compound SELECT statements using the separate multiSelect() + ** procedure. + */ + if( p->pPrior ){ + rc = multiSelect(pParse, p, pDest); +#if TREETRACE_ENABLED + TREETRACE(0x400,pParse,p,("end compound-select processing\n")); + if( (sqlite3TreeTrace & 0x400)!=0 && ExplainQueryPlanParent(pParse)==0 ){ + sqlite3TreeViewSelect(0, p, 0); + } +#endif + if( p->pNext==0 ) ExplainQueryPlanPop(pParse); + return rc; + } +#endif + + /* Do the WHERE-clause constant propagation optimization if this is + ** a join. No need to speed time on this operation for non-join queries + ** as the equivalent optimization will be handled by query planner in + ** sqlite3WhereBegin(). + */ + if( p->pWhere!=0 + && p->pWhere->op==TK_AND + && OptimizationEnabled(db, SQLITE_PropagateConst) + && propagateConstants(pParse, p) + ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x2000 ){ + TREETRACE(0x2000,pParse,p,("After constant propagation:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + }else{ + TREETRACE(0x2000,pParse,p,("Constant propagation not helpful\n")); + } + + if( OptimizationEnabled(db, SQLITE_QueryFlattener|SQLITE_CountOfView) + && countOfViewOptimization(pParse, p) + ){ + if( db->mallocFailed ) goto select_end; + pTabList = p->pSrc; + } + + /* For each term in the FROM clause, do two things: + ** (1) Authorized unreferenced tables + ** (2) Generate code for all sub-queries + */ + for(i=0; i<pTabList->nSrc; i++){ + SrcItem *pItem = &pTabList->a[i]; + SrcItem *pPrior; + SelectDest dest; + Select *pSub; +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) + const char *zSavedAuthContext; +#endif + + /* Issue SQLITE_READ authorizations with a fake column name for any + ** tables that are referenced but from which no values are extracted. + ** Examples of where these kinds of null SQLITE_READ authorizations + ** would occur: + ** + ** SELECT count(*) FROM t1; -- SQLITE_READ t1."" + ** SELECT t1.* FROM t1, t2; -- SQLITE_READ t2."" + ** + ** The fake column name is an empty string. It is possible for a table to + ** have a column named by the empty string, in which case there is no way to + ** distinguish between an unreferenced table and an actual reference to the + ** "" column. The original design was for the fake column name to be a NULL, + ** which would be unambiguous. But legacy authorization callbacks might + ** assume the column name is non-NULL and segfault. The use of an empty + ** string for the fake column name seems safer. + */ + if( pItem->colUsed==0 && pItem->zName!=0 ){ + sqlite3AuthCheck(pParse, SQLITE_READ, pItem->zName, "", pItem->zDatabase); + } + +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) + /* Generate code for all sub-queries in the FROM clause + */ + pSub = pItem->pSelect; + if( pSub==0 ) continue; + + /* The code for a subquery should only be generated once. */ + assert( pItem->addrFillSub==0 ); + + /* Increment Parse.nHeight by the height of the largest expression + ** tree referred to by this, the parent select. The child select + ** may contain expression trees of at most + ** (SQLITE_MAX_EXPR_DEPTH-Parse.nHeight) height. This is a bit + ** more conservative than necessary, but much easier than enforcing + ** an exact limit. + */ + pParse->nHeight += sqlite3SelectExprHeight(p); + + /* Make copies of constant WHERE-clause terms in the outer query down + ** inside the subquery. This can help the subquery to run more efficiently. + */ + if( OptimizationEnabled(db, SQLITE_PushDown) + && (pItem->fg.isCte==0 + || (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2)) + && pushDownWhereTerms(pParse, pSub, p->pWhere, pTabList, i) + ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x4000 ){ + TREETRACE(0x4000,pParse,p, + ("After WHERE-clause push-down into subquery %d:\n", pSub->selId)); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + assert( pItem->pSelect && (pItem->pSelect->selFlags & SF_PushDown)!=0 ); + }else{ + TREETRACE(0x4000,pParse,p,("Push-down not possible\n")); + } + + /* Convert unused result columns of the subquery into simple NULL + ** expressions, to avoid unneeded searching and computation. + */ + if( OptimizationEnabled(db, SQLITE_NullUnusedCols) + && disableUnusedSubqueryResultColumns(pItem) + ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x4000 ){ + TREETRACE(0x4000,pParse,p, + ("Change unused result columns to NULL for subquery %d:\n", + pSub->selId)); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + } + + zSavedAuthContext = pParse->zAuthContext; + pParse->zAuthContext = pItem->zName; + + /* Generate code to implement the subquery + */ + if( fromClauseTermCanBeCoroutine(pParse, pTabList, i, p->selFlags) ){ + /* Implement a co-routine that will return a single row of the result + ** set on each invocation. + */ + int addrTop = sqlite3VdbeCurrentAddr(v)+1; + + pItem->regReturn = ++pParse->nMem; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop); + VdbeComment((v, "%!S", pItem)); + pItem->addrFillSub = addrTop; + sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn); + ExplainQueryPlan((pParse, 1, "CO-ROUTINE %!S", pItem)); + sqlite3Select(pParse, pSub, &dest); + pItem->pTab->nRowLogEst = pSub->nSelectRow; + pItem->fg.viaCoroutine = 1; + pItem->regResult = dest.iSdst; + sqlite3VdbeEndCoroutine(v, pItem->regReturn); + sqlite3VdbeJumpHere(v, addrTop-1); + sqlite3ClearTempRegCache(pParse); + }else if( pItem->fg.isCte && pItem->u2.pCteUse->addrM9e>0 ){ + /* This is a CTE for which materialization code has already been + ** generated. Invoke the subroutine to compute the materialization, + ** the make the pItem->iCursor be a copy of the ephemeral table that + ** holds the result of the materialization. */ + CteUse *pCteUse = pItem->u2.pCteUse; + sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e); + if( pItem->iCursor!=pCteUse->iCur ){ + sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur); + VdbeComment((v, "%!S", pItem)); + } + pSub->nSelectRow = pCteUse->nRowEst; + }else if( (pPrior = isSelfJoinView(pTabList, pItem, 0, i))!=0 ){ + /* This view has already been materialized by a prior entry in + ** this same FROM clause. Reuse it. */ + if( pPrior->addrFillSub ){ + sqlite3VdbeAddOp2(v, OP_Gosub, pPrior->regReturn, pPrior->addrFillSub); + } + sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor); + pSub->nSelectRow = pPrior->pSelect->nSelectRow; + }else{ + /* Materialize the view. If the view is not correlated, generate a + ** subroutine to do the materialization so that subsequent uses of + ** the same view can reuse the materialization. */ + int topAddr; + int onceAddr = 0; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExplain; +#endif + + pItem->regReturn = ++pParse->nMem; + topAddr = sqlite3VdbeAddOp0(v, OP_Goto); + pItem->addrFillSub = topAddr+1; + pItem->fg.isMaterialized = 1; + if( pItem->fg.isCorrelated==0 ){ + /* If the subquery is not correlated and if we are not inside of + ** a trigger, then we only need to compute the value of the subquery + ** once. */ + onceAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + VdbeComment((v, "materialize %!S", pItem)); + }else{ + VdbeNoopComment((v, "materialize %!S", pItem)); + } + sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); + + ExplainQueryPlan2(addrExplain, (pParse, 1, "MATERIALIZE %!S", pItem)); + sqlite3Select(pParse, pSub, &dest); + pItem->pTab->nRowLogEst = pSub->nSelectRow; + if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); + sqlite3VdbeAddOp2(v, OP_Return, pItem->regReturn, topAddr+1); + VdbeComment((v, "end %!S", pItem)); + sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); + sqlite3VdbeJumpHere(v, topAddr); + sqlite3ClearTempRegCache(pParse); + if( pItem->fg.isCte && pItem->fg.isCorrelated==0 ){ + CteUse *pCteUse = pItem->u2.pCteUse; + pCteUse->addrM9e = pItem->addrFillSub; + pCteUse->regRtn = pItem->regReturn; + pCteUse->iCur = pItem->iCursor; + pCteUse->nRowEst = pSub->nSelectRow; + } + } + if( db->mallocFailed ) goto select_end; + pParse->nHeight -= sqlite3SelectExprHeight(p); + pParse->zAuthContext = zSavedAuthContext; +#endif + } + + /* Various elements of the SELECT copied into local variables for + ** convenience */ + pEList = p->pEList; + pWhere = p->pWhere; + pGroupBy = p->pGroupBy; + pHaving = p->pHaving; + sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0; + +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x8000 ){ + TREETRACE(0x8000,pParse,p,("After all FROM-clause analysis:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + + /* If the query is DISTINCT with an ORDER BY but is not an aggregate, and + ** if the select-list is the same as the ORDER BY list, then this query + ** can be rewritten as a GROUP BY. In other words, this: + ** + ** SELECT DISTINCT xyz FROM ... ORDER BY xyz + ** + ** is transformed to: + ** + ** SELECT xyz FROM ... GROUP BY xyz ORDER BY xyz + ** + ** The second form is preferred as a single index (or temp-table) may be + ** used for both the ORDER BY and DISTINCT processing. As originally + ** written the query must use a temp-table for at least one of the ORDER + ** BY and DISTINCT, and an index or separate temp-table for the other. + */ + if( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct + && sqlite3ExprListCompare(sSort.pOrderBy, pEList, -1)==0 +#ifndef SQLITE_OMIT_WINDOWFUNC + && p->pWin==0 +#endif + ){ + p->selFlags &= ~SF_Distinct; + pGroupBy = p->pGroupBy = sqlite3ExprListDup(db, pEList, 0); + p->selFlags |= SF_Aggregate; + /* Notice that even thought SF_Distinct has been cleared from p->selFlags, + ** the sDistinct.isTnct is still set. Hence, isTnct represents the + ** original setting of the SF_Distinct flag, not the current setting */ + assert( sDistinct.isTnct ); + sDistinct.isTnct = 2; + +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20000 ){ + TREETRACE(0x20000,pParse,p,("Transform DISTINCT into GROUP BY:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + } + + /* If there is an ORDER BY clause, then create an ephemeral index to + ** do the sorting. But this sorting ephemeral index might end up + ** being unused if the data can be extracted in pre-sorted order. + ** If that is the case, then the OP_OpenEphemeral instruction will be + ** changed to an OP_Noop once we figure out that the sorting index is + ** not needed. The sSort.addrSortIndex variable is used to facilitate + ** that change. + */ + if( sSort.pOrderBy ){ + KeyInfo *pKeyInfo; + pKeyInfo = sqlite3KeyInfoFromExprList( + pParse, sSort.pOrderBy, 0, pEList->nExpr); + sSort.iECursor = pParse->nTab++; + sSort.addrSortIndex = + sqlite3VdbeAddOp4(v, OP_OpenEphemeral, + sSort.iECursor, sSort.pOrderBy->nExpr+1+pEList->nExpr, 0, + (char*)pKeyInfo, P4_KEYINFO + ); + }else{ + sSort.addrSortIndex = -1; + } + + /* If the output is destined for a temporary table, open that table. + */ + if( pDest->eDest==SRT_EphemTab ){ + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pDest->iSDParm, pEList->nExpr); + if( p->selFlags & SF_NestedFrom ){ + /* Delete or NULL-out result columns that will never be used */ + int ii; + for(ii=pEList->nExpr-1; ii>0 && pEList->a[ii].fg.bUsed==0; ii--){ + sqlite3ExprDelete(db, pEList->a[ii].pExpr); + sqlite3DbFree(db, pEList->a[ii].zEName); + pEList->nExpr--; + } + for(ii=0; ii<pEList->nExpr; ii++){ + if( pEList->a[ii].fg.bUsed==0 ) pEList->a[ii].pExpr->op = TK_NULL; + } + } + } + + /* Set the limiter. + */ + iEnd = sqlite3VdbeMakeLabel(pParse); + if( (p->selFlags & SF_FixedLimit)==0 ){ + p->nSelectRow = 320; /* 4 billion rows */ + } + if( p->pLimit ) computeLimitRegisters(pParse, p, iEnd); + if( p->iLimit==0 && sSort.addrSortIndex>=0 ){ + sqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen); + sSort.sortFlags |= SORTFLAG_UseSorter; + } + + /* Open an ephemeral index to use for the distinct set. + */ + if( p->selFlags & SF_Distinct ){ + sDistinct.tabTnct = pParse->nTab++; + sDistinct.addrTnct = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, + sDistinct.tabTnct, 0, 0, + (char*)sqlite3KeyInfoFromExprList(pParse, p->pEList,0,0), + P4_KEYINFO); + sqlite3VdbeChangeP5(v, BTREE_UNORDERED); + sDistinct.eTnctType = WHERE_DISTINCT_UNORDERED; + }else{ + sDistinct.eTnctType = WHERE_DISTINCT_NOOP; + } + + if( !isAgg && pGroupBy==0 ){ + /* No aggregate functions and no GROUP BY clause */ + u16 wctrlFlags = (sDistinct.isTnct ? WHERE_WANT_DISTINCT : 0) + | (p->selFlags & SF_FixedLimit); +#ifndef SQLITE_OMIT_WINDOWFUNC + Window *pWin = p->pWin; /* Main window object (or NULL) */ + if( pWin ){ + sqlite3WindowCodeInit(pParse, p); + } +#endif + assert( WHERE_USE_LIMIT==SF_FixedLimit ); + + + /* Begin the database scan. */ + TREETRACE(0x2,pParse,p,("WhereBegin\n")); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, + p->pEList, p, wctrlFlags, p->nSelectRow); + if( pWInfo==0 ) goto select_end; + if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){ + p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo); + } + if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){ + sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo); + } + if( sSort.pOrderBy ){ + sSort.nOBSat = sqlite3WhereIsOrdered(pWInfo); + sSort.labelOBLopt = sqlite3WhereOrderByLimitOptLabel(pWInfo); + if( sSort.nOBSat==sSort.pOrderBy->nExpr ){ + sSort.pOrderBy = 0; + } + } + TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); + + /* If sorting index that was created by a prior OP_OpenEphemeral + ** instruction ended up not being needed, then change the OP_OpenEphemeral + ** into an OP_Noop. + */ + if( sSort.addrSortIndex>=0 && sSort.pOrderBy==0 ){ + sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex); + } + + assert( p->pEList==pEList ); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pWin ){ + int addrGosub = sqlite3VdbeMakeLabel(pParse); + int iCont = sqlite3VdbeMakeLabel(pParse); + int iBreak = sqlite3VdbeMakeLabel(pParse); + int regGosub = ++pParse->nMem; + + sqlite3WindowCodeStep(pParse, p, pWInfo, regGosub, addrGosub); + + sqlite3VdbeAddOp2(v, OP_Goto, 0, iBreak); + sqlite3VdbeResolveLabel(v, addrGosub); + VdbeNoopComment((v, "inner-loop subroutine")); + sSort.labelOBLopt = 0; + selectInnerLoop(pParse, p, -1, &sSort, &sDistinct, pDest, iCont, iBreak); + sqlite3VdbeResolveLabel(v, iCont); + sqlite3VdbeAddOp1(v, OP_Return, regGosub); + VdbeComment((v, "end inner-loop subroutine")); + sqlite3VdbeResolveLabel(v, iBreak); + }else +#endif /* SQLITE_OMIT_WINDOWFUNC */ + { + /* Use the standard inner loop. */ + selectInnerLoop(pParse, p, -1, &sSort, &sDistinct, pDest, + sqlite3WhereContinueLabel(pWInfo), + sqlite3WhereBreakLabel(pWInfo)); + + /* End the database scan loop. + */ + TREETRACE(0x2,pParse,p,("WhereEnd\n")); + sqlite3WhereEnd(pWInfo); + } + }else{ + /* This case when there exist aggregate functions or a GROUP BY clause + ** or both */ + NameContext sNC; /* Name context for processing aggregate information */ + int iAMem; /* First Mem address for storing current GROUP BY */ + int iBMem; /* First Mem address for previous GROUP BY */ + int iUseFlag; /* Mem address holding flag indicating that at least + ** one row of the input to the aggregator has been + ** processed */ + int iAbortFlag; /* Mem address which causes query abort if positive */ + int groupBySort; /* Rows come from source in GROUP BY order */ + int addrEnd; /* End of processing for this SELECT */ + int sortPTab = 0; /* Pseudotable used to decode sorting results */ + int sortOut = 0; /* Output register from the sorter */ + int orderByGrp = 0; /* True if the GROUP BY and ORDER BY are the same */ + + /* Remove any and all aliases between the result set and the + ** GROUP BY clause. + */ + if( pGroupBy ){ + int k; /* Loop counter */ + struct ExprList_item *pItem; /* For looping over expression in a list */ + + for(k=p->pEList->nExpr, pItem=p->pEList->a; k>0; k--, pItem++){ + pItem->u.x.iAlias = 0; + } + for(k=pGroupBy->nExpr, pItem=pGroupBy->a; k>0; k--, pItem++){ + pItem->u.x.iAlias = 0; + } + assert( 66==sqlite3LogEst(100) ); + if( p->nSelectRow>66 ) p->nSelectRow = 66; + + /* If there is both a GROUP BY and an ORDER BY clause and they are + ** identical, then it may be possible to disable the ORDER BY clause + ** on the grounds that the GROUP BY will cause elements to come out + ** in the correct order. It also may not - the GROUP BY might use a + ** database index that causes rows to be grouped together as required + ** but not actually sorted. Either way, record the fact that the + ** ORDER BY and GROUP BY clauses are the same by setting the orderByGrp + ** variable. */ + if( sSort.pOrderBy && pGroupBy->nExpr==sSort.pOrderBy->nExpr ){ + int ii; + /* The GROUP BY processing doesn't care whether rows are delivered in + ** ASC or DESC order - only that each group is returned contiguously. + ** So set the ASC/DESC flags in the GROUP BY to match those in the + ** ORDER BY to maximize the chances of rows being delivered in an + ** order that makes the ORDER BY redundant. */ + for(ii=0; ii<pGroupBy->nExpr; ii++){ + u8 sortFlags; + sortFlags = sSort.pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC; + pGroupBy->a[ii].fg.sortFlags = sortFlags; + } + if( sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){ + orderByGrp = 1; + } + } + }else{ + assert( 0==sqlite3LogEst(1) ); + p->nSelectRow = 0; + } + + /* Create a label to jump to when we want to abort the query */ + addrEnd = sqlite3VdbeMakeLabel(pParse); + + /* Convert TK_COLUMN nodes into TK_AGG_COLUMN and make entries in + ** sAggInfo for all TK_AGG_FUNCTION nodes in expressions of the + ** SELECT statement. + */ + pAggInfo = sqlite3DbMallocZero(db, sizeof(*pAggInfo) ); + if( pAggInfo ){ + sqlite3ParserAddCleanup(pParse, + (void(*)(sqlite3*,void*))agginfoFree, pAggInfo); + testcase( pParse->earlyCleanup ); + } + if( db->mallocFailed ){ + goto select_end; + } + pAggInfo->selId = p->selId; +#ifdef SQLITE_DEBUG + pAggInfo->pSelect = p; +#endif + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = pParse; + sNC.pSrcList = pTabList; + sNC.uNC.pAggInfo = pAggInfo; + VVA_ONLY( sNC.ncFlags = NC_UAggInfo; ) + pAggInfo->nSortingColumn = pGroupBy ? pGroupBy->nExpr : 0; + pAggInfo->pGroupBy = pGroupBy; + sqlite3ExprAnalyzeAggList(&sNC, pEList); + sqlite3ExprAnalyzeAggList(&sNC, sSort.pOrderBy); + if( pHaving ){ + if( pGroupBy ){ + assert( pWhere==p->pWhere ); + assert( pHaving==p->pHaving ); + assert( pGroupBy==p->pGroupBy ); + havingToWhere(pParse, p); + pWhere = p->pWhere; + } + sqlite3ExprAnalyzeAggregates(&sNC, pHaving); + } + pAggInfo->nAccumulator = pAggInfo->nColumn; + if( p->pGroupBy==0 && p->pHaving==0 && pAggInfo->nFunc==1 ){ + minMaxFlag = minMaxQuery(db, pAggInfo->aFunc[0].pFExpr, &pMinMaxOrderBy); + }else{ + minMaxFlag = WHERE_ORDERBY_NORMAL; + } + analyzeAggFuncArgs(pAggInfo, &sNC); + if( db->mallocFailed ) goto select_end; +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20 ){ + TREETRACE(0x20,pParse,p,("After aggregate analysis %p:\n", pAggInfo)); + sqlite3TreeViewSelect(0, p, 0); + if( minMaxFlag ){ + sqlite3DebugPrintf("MIN/MAX Optimization (0x%02x) adds:\n", minMaxFlag); + sqlite3TreeViewExprList(0, pMinMaxOrderBy, 0, "ORDERBY"); + } + printAggInfo(pAggInfo); + } +#endif + + + /* Processing for aggregates with GROUP BY is very different and + ** much more complex than aggregates without a GROUP BY. + */ + if( pGroupBy ){ + KeyInfo *pKeyInfo; /* Keying information for the group by clause */ + int addr1; /* A-vs-B comparison jump */ + int addrOutputRow; /* Start of subroutine that outputs a result row */ + int regOutputRow; /* Return address register for output subroutine */ + int addrSetAbort; /* Set the abort flag and return */ + int addrTopOfLoop; /* Top of the input loop */ + int addrSortingIdx; /* The OP_OpenEphemeral for the sorting index */ + int addrReset; /* Subroutine for resetting the accumulator */ + int regReset; /* Return address register for reset subroutine */ + ExprList *pDistinct = 0; + u16 distFlag = 0; + int eDist = WHERE_DISTINCT_NOOP; + + if( pAggInfo->nFunc==1 + && pAggInfo->aFunc[0].iDistinct>=0 + && ALWAYS(pAggInfo->aFunc[0].pFExpr!=0) + && ALWAYS(ExprUseXList(pAggInfo->aFunc[0].pFExpr)) + && pAggInfo->aFunc[0].pFExpr->x.pList!=0 + ){ + Expr *pExpr = pAggInfo->aFunc[0].pFExpr->x.pList->a[0].pExpr; + pExpr = sqlite3ExprDup(db, pExpr, 0); + pDistinct = sqlite3ExprListDup(db, pGroupBy, 0); + pDistinct = sqlite3ExprListAppend(pParse, pDistinct, pExpr); + distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; + } + + /* If there is a GROUP BY clause we might need a sorting index to + ** implement it. Allocate that sorting index now. If it turns out + ** that we do not need it after all, the OP_SorterOpen instruction + ** will be converted into a Noop. + */ + pAggInfo->sortingIdx = pParse->nTab++; + pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pGroupBy, + 0, pAggInfo->nColumn); + addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen, + pAggInfo->sortingIdx, pAggInfo->nSortingColumn, + 0, (char*)pKeyInfo, P4_KEYINFO); + + /* Initialize memory locations used by GROUP BY aggregate processing + */ + iUseFlag = ++pParse->nMem; + iAbortFlag = ++pParse->nMem; + regOutputRow = ++pParse->nMem; + addrOutputRow = sqlite3VdbeMakeLabel(pParse); + regReset = ++pParse->nMem; + addrReset = sqlite3VdbeMakeLabel(pParse); + iAMem = pParse->nMem + 1; + pParse->nMem += pGroupBy->nExpr; + iBMem = pParse->nMem + 1; + pParse->nMem += pGroupBy->nExpr; + sqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag); + VdbeComment((v, "clear abort flag")); + sqlite3VdbeAddOp3(v, OP_Null, 0, iAMem, iAMem+pGroupBy->nExpr-1); + + /* Begin a loop that will extract all source rows in GROUP BY order. + ** This might involve two separate loops with an OP_Sort in between, or + ** it might be a single loop that uses an index to extract information + ** in the right order to begin with. + */ + sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); + TREETRACE(0x2,pParse,p,("WhereBegin\n")); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct, + p, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY) + | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0 + ); + if( pWInfo==0 ){ + sqlite3ExprListDelete(db, pDistinct); + goto select_end; + } + if( pParse->pIdxEpr ){ + optimizeAggregateUseOfIndexedExpr(pParse, p, pAggInfo, &sNC); + } + assignAggregateRegisters(pParse, pAggInfo); + eDist = sqlite3WhereIsDistinct(pWInfo); + TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); + if( sqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){ + /* The optimizer is able to deliver rows in group by order so + ** we do not have to sort. The OP_OpenEphemeral table will be + ** cancelled later because we still need to use the pKeyInfo + */ + groupBySort = 0; + }else{ + /* Rows are coming out in undetermined order. We have to push + ** each row into a sorting index, terminate the first loop, + ** then loop over the sorting index in order to get the output + ** in sorted order + */ + int regBase; + int regRecord; + int nCol; + int nGroupBy; + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExp; /* Address of OP_Explain instruction */ +#endif + ExplainQueryPlan2(addrExp, (pParse, 0, "USE TEMP B-TREE FOR %s", + (sDistinct.isTnct && (p->selFlags&SF_Distinct)==0) ? + "DISTINCT" : "GROUP BY" + )); + + groupBySort = 1; + nGroupBy = pGroupBy->nExpr; + nCol = nGroupBy; + j = nGroupBy; + for(i=0; i<pAggInfo->nColumn; i++){ + if( pAggInfo->aCol[i].iSorterColumn>=j ){ + nCol++; + j++; + } + } + regBase = sqlite3GetTempRange(pParse, nCol); + sqlite3ExprCodeExprList(pParse, pGroupBy, regBase, 0, 0); + j = nGroupBy; + pAggInfo->directMode = 1; + for(i=0; i<pAggInfo->nColumn; i++){ + struct AggInfo_col *pCol = &pAggInfo->aCol[i]; + if( pCol->iSorterColumn>=j ){ + sqlite3ExprCode(pParse, pCol->pCExpr, j + regBase); + j++; + } + } + pAggInfo->directMode = 0; + regRecord = sqlite3GetTempReg(pParse); + sqlite3VdbeScanStatusCounters(v, addrExp, 0, sqlite3VdbeCurrentAddr(v)); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord); + sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord); + sqlite3VdbeScanStatusRange(v, addrExp, sqlite3VdbeCurrentAddr(v)-2, -1); + sqlite3ReleaseTempReg(pParse, regRecord); + sqlite3ReleaseTempRange(pParse, regBase, nCol); + TREETRACE(0x2,pParse,p,("WhereEnd\n")); + sqlite3WhereEnd(pWInfo); + pAggInfo->sortingIdxPTab = sortPTab = pParse->nTab++; + sortOut = sqlite3GetTempReg(pParse); + sqlite3VdbeScanStatusCounters(v, addrExp, sqlite3VdbeCurrentAddr(v), 0); + sqlite3VdbeAddOp3(v, OP_OpenPseudo, sortPTab, sortOut, nCol); + sqlite3VdbeAddOp2(v, OP_SorterSort, pAggInfo->sortingIdx, addrEnd); + VdbeComment((v, "GROUP BY sort")); VdbeCoverage(v); + pAggInfo->useSortingIdx = 1; + sqlite3VdbeScanStatusRange(v, addrExp, -1, sortPTab); + sqlite3VdbeScanStatusRange(v, addrExp, -1, pAggInfo->sortingIdx); + } + + /* If there are entries in pAgggInfo->aFunc[] that contain subexpressions + ** that are indexed (and that were previously identified and tagged + ** in optimizeAggregateUseOfIndexedExpr()) then those subexpressions + ** must now be converted into a TK_AGG_COLUMN node so that the value + ** is correctly pulled from the index rather than being recomputed. */ + if( pParse->pIdxEpr ){ + aggregateConvertIndexedExprRefToColumn(pAggInfo); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20 ){ + TREETRACE(0x20, pParse, p, + ("AggInfo function expressions converted to reference index\n")); + sqlite3TreeViewSelect(0, p, 0); + printAggInfo(pAggInfo); + } +#endif + } + + /* If the index or temporary table used by the GROUP BY sort + ** will naturally deliver rows in the order required by the ORDER BY + ** clause, cancel the ephemeral table open coded earlier. + ** + ** This is an optimization - the correct answer should result regardless. + ** Use the SQLITE_GroupByOrder flag with SQLITE_TESTCTRL_OPTIMIZER to + ** disable this optimization for testing purposes. */ + if( orderByGrp && OptimizationEnabled(db, SQLITE_GroupByOrder) + && (groupBySort || sqlite3WhereIsSorted(pWInfo)) + ){ + sSort.pOrderBy = 0; + sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex); + } + + /* Evaluate the current GROUP BY terms and store in b0, b1, b2... + ** (b0 is memory location iBMem+0, b1 is iBMem+1, and so forth) + ** Then compare the current GROUP BY terms against the GROUP BY terms + ** from the previous row currently stored in a0, a1, a2... + */ + addrTopOfLoop = sqlite3VdbeCurrentAddr(v); + if( groupBySort ){ + sqlite3VdbeAddOp3(v, OP_SorterData, pAggInfo->sortingIdx, + sortOut, sortPTab); + } + for(j=0; j<pGroupBy->nExpr; j++){ + if( groupBySort ){ + sqlite3VdbeAddOp3(v, OP_Column, sortPTab, j, iBMem+j); + }else{ + pAggInfo->directMode = 1; + sqlite3ExprCode(pParse, pGroupBy->a[j].pExpr, iBMem+j); + } + } + sqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem, pGroupBy->nExpr, + (char*)sqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO); + addr1 = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp3(v, OP_Jump, addr1+1, 0, addr1+1); VdbeCoverage(v); + + /* Generate code that runs whenever the GROUP BY changes. + ** Changes in the GROUP BY are detected by the previous code + ** block. If there were no changes, this block is skipped. + ** + ** This code copies current group by terms in b0,b1,b2,... + ** over to a0,a1,a2. It then calls the output subroutine + ** and resets the aggregate accumulator registers in preparation + ** for the next GROUP BY batch. + */ + sqlite3ExprCodeMove(pParse, iBMem, iAMem, pGroupBy->nExpr); + sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow); + VdbeComment((v, "output one row")); + sqlite3VdbeAddOp2(v, OP_IfPos, iAbortFlag, addrEnd); VdbeCoverage(v); + VdbeComment((v, "check abort flag")); + sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); + VdbeComment((v, "reset accumulator")); + + /* Update the aggregate accumulators based on the content of + ** the current row + */ + sqlite3VdbeJumpHere(v, addr1); + updateAccumulator(pParse, iUseFlag, pAggInfo, eDist); + sqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag); + VdbeComment((v, "indicate data in accumulator")); + + /* End of the loop + */ + if( groupBySort ){ + sqlite3VdbeAddOp2(v, OP_SorterNext, pAggInfo->sortingIdx,addrTopOfLoop); + VdbeCoverage(v); + }else{ + TREETRACE(0x2,pParse,p,("WhereEnd\n")); + sqlite3WhereEnd(pWInfo); + sqlite3VdbeChangeToNoop(v, addrSortingIdx); + } + sqlite3ExprListDelete(db, pDistinct); + + /* Output the final row of result + */ + sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow); + VdbeComment((v, "output final row")); + + /* Jump over the subroutines + */ + sqlite3VdbeGoto(v, addrEnd); + + /* Generate a subroutine that outputs a single row of the result + ** set. This subroutine first looks at the iUseFlag. If iUseFlag + ** is less than or equal to zero, the subroutine is a no-op. If + ** the processing calls for the query to abort, this subroutine + ** increments the iAbortFlag memory location before returning in + ** order to signal the caller to abort. + */ + addrSetAbort = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp2(v, OP_Integer, 1, iAbortFlag); + VdbeComment((v, "set abort flag")); + sqlite3VdbeAddOp1(v, OP_Return, regOutputRow); + sqlite3VdbeResolveLabel(v, addrOutputRow); + addrOutputRow = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp2(v, OP_IfPos, iUseFlag, addrOutputRow+2); + VdbeCoverage(v); + VdbeComment((v, "Groupby result generator entry point")); + sqlite3VdbeAddOp1(v, OP_Return, regOutputRow); + finalizeAggFunctions(pParse, pAggInfo); + sqlite3ExprIfFalse(pParse, pHaving, addrOutputRow+1, SQLITE_JUMPIFNULL); + selectInnerLoop(pParse, p, -1, &sSort, + &sDistinct, pDest, + addrOutputRow+1, addrSetAbort); + sqlite3VdbeAddOp1(v, OP_Return, regOutputRow); + VdbeComment((v, "end groupby result generator")); + + /* Generate a subroutine that will reset the group-by accumulator + */ + sqlite3VdbeResolveLabel(v, addrReset); + resetAccumulator(pParse, pAggInfo); + sqlite3VdbeAddOp2(v, OP_Integer, 0, iUseFlag); + VdbeComment((v, "indicate accumulator empty")); + sqlite3VdbeAddOp1(v, OP_Return, regReset); + + if( distFlag!=0 && eDist!=WHERE_DISTINCT_NOOP ){ + struct AggInfo_func *pF = &pAggInfo->aFunc[0]; + fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); + } + } /* endif pGroupBy. Begin aggregate queries without GROUP BY: */ + else { + Table *pTab; + if( (pTab = isSimpleCount(p, pAggInfo))!=0 ){ + /* If isSimpleCount() returns a pointer to a Table structure, then + ** the SQL statement is of the form: + ** + ** SELECT count(*) FROM <tbl> + ** + ** where the Table structure returned represents table <tbl>. + ** + ** This statement is so common that it is optimized specially. The + ** OP_Count instruction is executed either on the intkey table that + ** contains the data for table <tbl> or on one of its indexes. It + ** is better to execute the op on an index, as indexes are almost + ** always spread across less pages than their corresponding tables. + */ + const int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + const int iCsr = pParse->nTab++; /* Cursor to scan b-tree */ + Index *pIdx; /* Iterator variable */ + KeyInfo *pKeyInfo = 0; /* Keyinfo for scanned index */ + Index *pBest = 0; /* Best index found so far */ + Pgno iRoot = pTab->tnum; /* Root page of scanned b-tree */ + + sqlite3CodeVerifySchema(pParse, iDb); + sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); + + /* Search for the index that has the lowest scan cost. + ** + ** (2011-04-15) Do not do a full scan of an unordered index. + ** + ** (2013-10-03) Do not count the entries in a partial index. + ** + ** In practice the KeyInfo structure will not be used. It is only + ** passed to keep OP_OpenRead happy. + */ + if( !HasRowid(pTab) ) pBest = sqlite3PrimaryKeyIndex(pTab); + if( !p->pSrc->a[0].fg.notIndexed ){ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->bUnordered==0 + && pIdx->szIdxRow<pTab->szTabRow + && pIdx->pPartIdxWhere==0 + && (!pBest || pIdx->szIdxRow<pBest->szIdxRow) + ){ + pBest = pIdx; + } + } + } + if( pBest ){ + iRoot = pBest->tnum; + pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pBest); + } + + /* Open a read-only cursor, execute the OP_Count, close the cursor. */ + sqlite3VdbeAddOp4Int(v, OP_OpenRead, iCsr, (int)iRoot, iDb, 1); + if( pKeyInfo ){ + sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO); + } + assignAggregateRegisters(pParse, pAggInfo); + sqlite3VdbeAddOp2(v, OP_Count, iCsr, AggInfoFuncReg(pAggInfo,0)); + sqlite3VdbeAddOp1(v, OP_Close, iCsr); + explainSimpleCount(pParse, pTab, pBest); + }else{ + int regAcc = 0; /* "populate accumulators" flag */ + ExprList *pDistinct = 0; + u16 distFlag = 0; + int eDist; + + /* If there are accumulator registers but no min() or max() functions + ** without FILTER clauses, allocate register regAcc. Register regAcc + ** will contain 0 the first time the inner loop runs, and 1 thereafter. + ** The code generated by updateAccumulator() uses this to ensure + ** that the accumulator registers are (a) updated only once if + ** there are no min() or max functions or (b) always updated for the + ** first row visited by the aggregate, so that they are updated at + ** least once even if the FILTER clause means the min() or max() + ** function visits zero rows. */ + if( pAggInfo->nAccumulator ){ + for(i=0; i<pAggInfo->nFunc; i++){ + if( ExprHasProperty(pAggInfo->aFunc[i].pFExpr, EP_WinFunc) ){ + continue; + } + if( pAggInfo->aFunc[i].pFunc->funcFlags&SQLITE_FUNC_NEEDCOLL ){ + break; + } + } + if( i==pAggInfo->nFunc ){ + regAcc = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 0, regAcc); + } + }else if( pAggInfo->nFunc==1 && pAggInfo->aFunc[0].iDistinct>=0 ){ + assert( ExprUseXList(pAggInfo->aFunc[0].pFExpr) ); + pDistinct = pAggInfo->aFunc[0].pFExpr->x.pList; + distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; + } + assignAggregateRegisters(pParse, pAggInfo); + + /* This case runs if the aggregate has no GROUP BY clause. The + ** processing is much simpler since there is only a single row + ** of output. + */ + assert( p->pGroupBy==0 ); + resetAccumulator(pParse, pAggInfo); + + /* If this query is a candidate for the min/max optimization, then + ** minMaxFlag will have been previously set to either + ** WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX and pMinMaxOrderBy will + ** be an appropriate ORDER BY expression for the optimization. + */ + assert( minMaxFlag==WHERE_ORDERBY_NORMAL || pMinMaxOrderBy!=0 ); + assert( pMinMaxOrderBy==0 || pMinMaxOrderBy->nExpr==1 ); + + TREETRACE(0x2,pParse,p,("WhereBegin\n")); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy, + pDistinct, p, minMaxFlag|distFlag, 0); + if( pWInfo==0 ){ + goto select_end; + } + TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); + eDist = sqlite3WhereIsDistinct(pWInfo); + updateAccumulator(pParse, regAcc, pAggInfo, eDist); + if( eDist!=WHERE_DISTINCT_NOOP ){ + struct AggInfo_func *pF = pAggInfo->aFunc; + if( pF ){ + fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); + } + } + + if( regAcc ) sqlite3VdbeAddOp2(v, OP_Integer, 1, regAcc); + if( minMaxFlag ){ + sqlite3WhereMinMaxOptEarlyOut(v, pWInfo); + } + TREETRACE(0x2,pParse,p,("WhereEnd\n")); + sqlite3WhereEnd(pWInfo); + finalizeAggFunctions(pParse, pAggInfo); + } + + sSort.pOrderBy = 0; + sqlite3ExprIfFalse(pParse, pHaving, addrEnd, SQLITE_JUMPIFNULL); + selectInnerLoop(pParse, p, -1, 0, 0, + pDest, addrEnd, addrEnd); + } + sqlite3VdbeResolveLabel(v, addrEnd); + + } /* endif aggregate query */ + + if( sDistinct.eTnctType==WHERE_DISTINCT_UNORDERED ){ + explainTempTable(pParse, "DISTINCT"); + } + + /* If there is an ORDER BY clause, then we need to sort the results + ** and send them to the callback one by one. + */ + if( sSort.pOrderBy ){ + assert( p->pEList==pEList ); + generateSortTail(pParse, p, &sSort, pEList->nExpr, pDest); + } + + /* Jump here to skip this query + */ + sqlite3VdbeResolveLabel(v, iEnd); + + /* The SELECT has been coded. If there is an error in the Parse structure, + ** set the return code to 1. Otherwise 0. */ + rc = (pParse->nErr>0); + + /* Control jumps to here if an error is encountered above, or upon + ** successful coding of the SELECT. + */ +select_end: + assert( db->mallocFailed==0 || db->mallocFailed==1 ); + assert( db->mallocFailed==0 || pParse->nErr!=0 ); + sqlite3ExprListDelete(db, pMinMaxOrderBy); +#ifdef SQLITE_DEBUG + if( pAggInfo && !db->mallocFailed ){ + for(i=0; i<pAggInfo->nColumn; i++){ + Expr *pExpr = pAggInfo->aCol[i].pCExpr; + if( pExpr==0 ) continue; + assert( pExpr->pAggInfo==pAggInfo ); + assert( pExpr->iAgg==i ); + } + for(i=0; i<pAggInfo->nFunc; i++){ + Expr *pExpr = pAggInfo->aFunc[i].pFExpr; + assert( pExpr!=0 ); + assert( pExpr->pAggInfo==pAggInfo ); + assert( pExpr->iAgg==i ); + } + } +#endif + +#if TREETRACE_ENABLED + TREETRACE(0x1,pParse,p,("end processing\n")); + if( (sqlite3TreeTrace & 0x40000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ + sqlite3TreeViewSelect(0, p, 0); + } +#endif + ExplainQueryPlanPop(pParse); + return rc; +} + +/************** End of select.c **********************************************/ +/************** Begin file table.c *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the sqlite3_get_table() and sqlite3_free_table() +** interface routines. These are just wrappers around the main +** interface routine of sqlite3_exec(). +** +** These routines are in a separate files so that they will not be linked +** if they are not used. +*/ +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_OMIT_GET_TABLE + +/* +** This structure is used to pass data from sqlite3_get_table() through +** to the callback function is uses to build the result. +*/ +typedef struct TabResult { + char **azResult; /* Accumulated output */ + char *zErrMsg; /* Error message text, if an error occurs */ + u32 nAlloc; /* Slots allocated for azResult[] */ + u32 nRow; /* Number of rows in the result */ + u32 nColumn; /* Number of columns in the result */ + u32 nData; /* Slots used in azResult[]. (nRow+1)*nColumn */ + int rc; /* Return code from sqlite3_exec() */ +} TabResult; + +/* +** This routine is called once for each row in the result table. Its job +** is to fill in the TabResult structure appropriately, allocating new +** memory as necessary. +*/ +static int sqlite3_get_table_cb(void *pArg, int nCol, char **argv, char **colv){ + TabResult *p = (TabResult*)pArg; /* Result accumulator */ + int need; /* Slots needed in p->azResult[] */ + int i; /* Loop counter */ + char *z; /* A single column of result */ + + /* Make sure there is enough space in p->azResult to hold everything + ** we need to remember from this invocation of the callback. + */ + if( p->nRow==0 && argv!=0 ){ + need = nCol*2; + }else{ + need = nCol; + } + if( p->nData + need > p->nAlloc ){ + char **azNew; + p->nAlloc = p->nAlloc*2 + need; + azNew = sqlite3Realloc( p->azResult, sizeof(char*)*p->nAlloc ); + if( azNew==0 ) goto malloc_failed; + p->azResult = azNew; + } + + /* If this is the first row, then generate an extra row containing + ** the names of all columns. + */ + if( p->nRow==0 ){ + p->nColumn = nCol; + for(i=0; i<nCol; i++){ + z = sqlite3_mprintf("%s", colv[i]); + if( z==0 ) goto malloc_failed; + p->azResult[p->nData++] = z; + } + }else if( (int)p->nColumn!=nCol ){ + sqlite3_free(p->zErrMsg); + p->zErrMsg = sqlite3_mprintf( + "sqlite3_get_table() called with two or more incompatible queries" + ); + p->rc = SQLITE_ERROR; + return 1; + } + + /* Copy over the row data + */ + if( argv!=0 ){ + for(i=0; i<nCol; i++){ + if( argv[i]==0 ){ + z = 0; + }else{ + int n = sqlite3Strlen30(argv[i])+1; + z = sqlite3_malloc64( n ); + if( z==0 ) goto malloc_failed; + memcpy(z, argv[i], n); + } + p->azResult[p->nData++] = z; + } + p->nRow++; + } + return 0; + +malloc_failed: + p->rc = SQLITE_NOMEM_BKPT; + return 1; +} + +/* +** Query the database. But instead of invoking a callback for each row, +** malloc() for space to hold the result and return the entire results +** at the conclusion of the call. +** +** The result that is written to ***pazResult is held in memory obtained +** from malloc(). But the caller cannot free this memory directly. +** Instead, the entire table should be passed to sqlite3_free_table() when +** the calling procedure is finished using it. +*/ +SQLITE_API int sqlite3_get_table( + sqlite3 *db, /* The database on which the SQL executes */ + const char *zSql, /* The SQL to be executed */ + char ***pazResult, /* Write the result table here */ + int *pnRow, /* Write the number of rows in the result here */ + int *pnColumn, /* Write the number of columns of result here */ + char **pzErrMsg /* Write error messages here */ +){ + int rc; + TabResult res; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || pazResult==0 ) return SQLITE_MISUSE_BKPT; +#endif + *pazResult = 0; + if( pnColumn ) *pnColumn = 0; + if( pnRow ) *pnRow = 0; + if( pzErrMsg ) *pzErrMsg = 0; + res.zErrMsg = 0; + res.nRow = 0; + res.nColumn = 0; + res.nData = 1; + res.nAlloc = 20; + res.rc = SQLITE_OK; + res.azResult = sqlite3_malloc64(sizeof(char*)*res.nAlloc ); + if( res.azResult==0 ){ + db->errCode = SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; + } + res.azResult[0] = 0; + rc = sqlite3_exec(db, zSql, sqlite3_get_table_cb, &res, pzErrMsg); + assert( sizeof(res.azResult[0])>= sizeof(res.nData) ); + res.azResult[0] = SQLITE_INT_TO_PTR(res.nData); + if( (rc&0xff)==SQLITE_ABORT ){ + sqlite3_free_table(&res.azResult[1]); + if( res.zErrMsg ){ + if( pzErrMsg ){ + sqlite3_free(*pzErrMsg); + *pzErrMsg = sqlite3_mprintf("%s",res.zErrMsg); + } + sqlite3_free(res.zErrMsg); + } + db->errCode = res.rc; /* Assume 32-bit assignment is atomic */ + return res.rc; + } + sqlite3_free(res.zErrMsg); + if( rc!=SQLITE_OK ){ + sqlite3_free_table(&res.azResult[1]); + return rc; + } + if( res.nAlloc>res.nData ){ + char **azNew; + azNew = sqlite3Realloc( res.azResult, sizeof(char*)*res.nData ); + if( azNew==0 ){ + sqlite3_free_table(&res.azResult[1]); + db->errCode = SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; + } + res.azResult = azNew; + } + *pazResult = &res.azResult[1]; + if( pnColumn ) *pnColumn = res.nColumn; + if( pnRow ) *pnRow = res.nRow; + return rc; +} + +/* +** This routine frees the space the sqlite3_get_table() malloced. +*/ +SQLITE_API void sqlite3_free_table( + char **azResult /* Result returned from sqlite3_get_table() */ +){ + if( azResult ){ + int i, n; + azResult--; + assert( azResult!=0 ); + n = SQLITE_PTR_TO_INT(azResult[0]); + for(i=1; i<n; i++){ if( azResult[i] ) sqlite3_free(azResult[i]); } + sqlite3_free(azResult); + } +} + +#endif /* SQLITE_OMIT_GET_TABLE */ + +/************** End of table.c ***********************************************/ +/************** Begin file trigger.c *****************************************/ +/* +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the implementation for TRIGGERs +*/ +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_OMIT_TRIGGER +/* +** Delete a linked list of TriggerStep structures. +*/ +SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){ + while( pTriggerStep ){ + TriggerStep * pTmp = pTriggerStep; + pTriggerStep = pTriggerStep->pNext; + + sqlite3ExprDelete(db, pTmp->pWhere); + sqlite3ExprListDelete(db, pTmp->pExprList); + sqlite3SelectDelete(db, pTmp->pSelect); + sqlite3IdListDelete(db, pTmp->pIdList); + sqlite3UpsertDelete(db, pTmp->pUpsert); + sqlite3SrcListDelete(db, pTmp->pFrom); + sqlite3DbFree(db, pTmp->zSpan); + + sqlite3DbFree(db, pTmp); + } +} + +/* +** Given table pTab, return a list of all the triggers attached to +** the table. The list is connected by Trigger.pNext pointers. +** +** All of the triggers on pTab that are in the same database as pTab +** are already attached to pTab->pTrigger. But there might be additional +** triggers on pTab in the TEMP schema. This routine prepends all +** TEMP triggers on pTab to the beginning of the pTab->pTrigger list +** and returns the combined list. +** +** To state it another way: This routine returns a list of all triggers +** that fire off of pTab. The list will include any TEMP triggers on +** pTab as well as the triggers lised in pTab->pTrigger. +*/ +SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){ + Schema *pTmpSchema; /* Schema of the pTab table */ + Trigger *pList; /* List of triggers to return */ + HashElem *p; /* Loop variable for TEMP triggers */ + + assert( pParse->disableTriggers==0 ); + pTmpSchema = pParse->db->aDb[1].pSchema; + p = sqliteHashFirst(&pTmpSchema->trigHash); + pList = pTab->pTrigger; + while( p ){ + Trigger *pTrig = (Trigger *)sqliteHashData(p); + if( pTrig->pTabSchema==pTab->pSchema + && pTrig->table + && 0==sqlite3StrICmp(pTrig->table, pTab->zName) + && (pTrig->pTabSchema!=pTmpSchema || pTrig->bReturning) + ){ + pTrig->pNext = pList; + pList = pTrig; + }else if( pTrig->op==TK_RETURNING ){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + assert( pParse->db->pVtabCtx==0 ); +#endif + assert( pParse->bReturning ); + assert( &(pParse->u1.pReturning->retTrig) == pTrig ); + pTrig->table = pTab->zName; + pTrig->pTabSchema = pTab->pSchema; + pTrig->pNext = pList; + pList = pTrig; + } + p = sqliteHashNext(p); + } +#if 0 + if( pList ){ + Trigger *pX; + printf("Triggers for %s:", pTab->zName); + for(pX=pList; pX; pX=pX->pNext){ + printf(" %s", pX->zName); + } + printf("\n"); + fflush(stdout); + } +#endif + return pList; +} + +/* +** This is called by the parser when it sees a CREATE TRIGGER statement +** up to the point of the BEGIN before the trigger actions. A Trigger +** structure is generated based on the information available and stored +** in pParse->pNewTrigger. After the trigger actions have been parsed, the +** sqlite3FinishTrigger() function is called to complete the trigger +** construction process. +*/ +SQLITE_PRIVATE void sqlite3BeginTrigger( + Parse *pParse, /* The parse context of the CREATE TRIGGER statement */ + Token *pName1, /* The name of the trigger */ + Token *pName2, /* The name of the trigger */ + int tr_tm, /* One of TK_BEFORE, TK_AFTER, TK_INSTEAD */ + int op, /* One of TK_INSERT, TK_UPDATE, TK_DELETE */ + IdList *pColumns, /* column list if this is an UPDATE OF trigger */ + SrcList *pTableName,/* The name of the table/view the trigger applies to */ + Expr *pWhen, /* WHEN clause */ + int isTemp, /* True if the TEMPORARY keyword is present */ + int noErr /* Suppress errors if the trigger already exists */ +){ + Trigger *pTrigger = 0; /* The new trigger */ + Table *pTab; /* Table that the trigger fires off of */ + char *zName = 0; /* Name of the trigger */ + sqlite3 *db = pParse->db; /* The database connection */ + int iDb; /* The database to store the trigger in */ + Token *pName; /* The unqualified db name */ + DbFixer sFix; /* State vector for the DB fixer */ + + assert( pName1!=0 ); /* pName1->z might be NULL, but not pName1 itself */ + assert( pName2!=0 ); + assert( op==TK_INSERT || op==TK_UPDATE || op==TK_DELETE ); + assert( op>0 && op<0xff ); + if( isTemp ){ + /* If TEMP was specified, then the trigger name may not be qualified. */ + if( pName2->n>0 ){ + sqlite3ErrorMsg(pParse, "temporary trigger may not have qualified name"); + goto trigger_cleanup; + } + iDb = 1; + pName = pName1; + }else{ + /* Figure out the db that the trigger will be created in */ + iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); + if( iDb<0 ){ + goto trigger_cleanup; + } + } + if( !pTableName || db->mallocFailed ){ + goto trigger_cleanup; + } + + /* A long-standing parser bug is that this syntax was allowed: + ** + ** CREATE TRIGGER attached.demo AFTER INSERT ON attached.tab .... + ** ^^^^^^^^ + ** + ** To maintain backwards compatibility, ignore the database + ** name on pTableName if we are reparsing out of the schema table + */ + if( db->init.busy && iDb!=1 ){ + sqlite3DbFree(db, pTableName->a[0].zDatabase); + pTableName->a[0].zDatabase = 0; + } + + /* If the trigger name was unqualified, and the table is a temp table, + ** then set iDb to 1 to create the trigger in the temporary database. + ** If sqlite3SrcListLookup() returns 0, indicating the table does not + ** exist, the error is caught by the block below. + */ + pTab = sqlite3SrcListLookup(pParse, pTableName); + if( db->init.busy==0 && pName2->n==0 && pTab + && pTab->pSchema==db->aDb[1].pSchema ){ + iDb = 1; + } + + /* Ensure the table name matches database name and that the table exists */ + if( db->mallocFailed ) goto trigger_cleanup; + assert( pTableName->nSrc==1 ); + sqlite3FixInit(&sFix, pParse, iDb, "trigger", pName); + if( sqlite3FixSrcList(&sFix, pTableName) ){ + goto trigger_cleanup; + } + pTab = sqlite3SrcListLookup(pParse, pTableName); + if( !pTab ){ + /* The table does not exist. */ + goto trigger_orphan_error; + } + if( IsVirtual(pTab) ){ + sqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables"); + goto trigger_orphan_error; + } + + /* Check that the trigger name is not reserved and that no trigger of the + ** specified name exists */ + zName = sqlite3NameFromToken(db, pName); + if( zName==0 ){ + assert( db->mallocFailed ); + goto trigger_cleanup; + } + if( sqlite3CheckObjectName(pParse, zName, "trigger", pTab->zName) ){ + goto trigger_cleanup; + } + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( !IN_RENAME_OBJECT ){ + if( sqlite3HashFind(&(db->aDb[iDb].pSchema->trigHash),zName) ){ + if( !noErr ){ + sqlite3ErrorMsg(pParse, "trigger %T already exists", pName); + }else{ + assert( !db->init.busy ); + sqlite3CodeVerifySchema(pParse, iDb); + VVA_ONLY( pParse->ifNotExists = 1; ) + } + goto trigger_cleanup; + } + } + + /* Do not create a trigger on a system table */ + if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){ + sqlite3ErrorMsg(pParse, "cannot create trigger on system table"); + goto trigger_cleanup; + } + + /* INSTEAD of triggers are only for views and views only support INSTEAD + ** of triggers. + */ + if( IsView(pTab) && tr_tm!=TK_INSTEAD ){ + sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S", + (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName->a); + goto trigger_orphan_error; + } + if( !IsView(pTab) && tr_tm==TK_INSTEAD ){ + sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF" + " trigger on table: %S", pTableName->a); + goto trigger_orphan_error; + } + +#ifndef SQLITE_OMIT_AUTHORIZATION + if( !IN_RENAME_OBJECT ){ + int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); + int code = SQLITE_CREATE_TRIGGER; + const char *zDb = db->aDb[iTabDb].zDbSName; + const char *zDbTrig = isTemp ? db->aDb[1].zDbSName : zDb; + if( iTabDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER; + if( sqlite3AuthCheck(pParse, code, zName, pTab->zName, zDbTrig) ){ + goto trigger_cleanup; + } + if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iTabDb),0,zDb)){ + goto trigger_cleanup; + } + } +#endif + + /* INSTEAD OF triggers can only appear on views and BEFORE triggers + ** cannot appear on views. So we might as well translate every + ** INSTEAD OF trigger into a BEFORE trigger. It simplifies code + ** elsewhere. + */ + if (tr_tm == TK_INSTEAD){ + tr_tm = TK_BEFORE; + } + + /* Build the Trigger object */ + pTrigger = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger)); + if( pTrigger==0 ) goto trigger_cleanup; + pTrigger->zName = zName; + zName = 0; + pTrigger->table = sqlite3DbStrDup(db, pTableName->a[0].zName); + pTrigger->pSchema = db->aDb[iDb].pSchema; + pTrigger->pTabSchema = pTab->pSchema; + pTrigger->op = (u8)op; + pTrigger->tr_tm = tr_tm==TK_BEFORE ? TRIGGER_BEFORE : TRIGGER_AFTER; + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenRemap(pParse, pTrigger->table, pTableName->a[0].zName); + pTrigger->pWhen = pWhen; + pWhen = 0; + }else{ + pTrigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE); + } + pTrigger->pColumns = pColumns; + pColumns = 0; + assert( pParse->pNewTrigger==0 ); + pParse->pNewTrigger = pTrigger; + +trigger_cleanup: + sqlite3DbFree(db, zName); + sqlite3SrcListDelete(db, pTableName); + sqlite3IdListDelete(db, pColumns); + sqlite3ExprDelete(db, pWhen); + if( !pParse->pNewTrigger ){ + sqlite3DeleteTrigger(db, pTrigger); + }else{ + assert( pParse->pNewTrigger==pTrigger ); + } + return; + +trigger_orphan_error: + if( db->init.iDb==1 ){ + /* Ticket #3810. + ** Normally, whenever a table is dropped, all associated triggers are + ** dropped too. But if a TEMP trigger is created on a non-TEMP table + ** and the table is dropped by a different database connection, the + ** trigger is not visible to the database connection that does the + ** drop so the trigger cannot be dropped. This results in an + ** "orphaned trigger" - a trigger whose associated table is missing. + ** + ** 2020-11-05 see also https://sqlite.org/forum/forumpost/157dc791df + */ + db->init.orphanTrigger = 1; + } + goto trigger_cleanup; +} + +/* +** This routine is called after all of the trigger actions have been parsed +** in order to complete the process of building the trigger. +*/ +SQLITE_PRIVATE void sqlite3FinishTrigger( + Parse *pParse, /* Parser context */ + TriggerStep *pStepList, /* The triggered program */ + Token *pAll /* Token that describes the complete CREATE TRIGGER */ +){ + Trigger *pTrig = pParse->pNewTrigger; /* Trigger being finished */ + char *zName; /* Name of trigger */ + sqlite3 *db = pParse->db; /* The database */ + DbFixer sFix; /* Fixer object */ + int iDb; /* Database containing the trigger */ + Token nameToken; /* Trigger name for error reporting */ + + pParse->pNewTrigger = 0; + if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup; + zName = pTrig->zName; + iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema); + pTrig->step_list = pStepList; + while( pStepList ){ + pStepList->pTrig = pTrig; + pStepList = pStepList->pNext; + } + sqlite3TokenInit(&nameToken, pTrig->zName); + sqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken); + if( sqlite3FixTriggerStep(&sFix, pTrig->step_list) + || sqlite3FixExpr(&sFix, pTrig->pWhen) + ){ + goto triggerfinish_cleanup; + } + +#ifndef SQLITE_OMIT_ALTERTABLE + if( IN_RENAME_OBJECT ){ + assert( !db->init.busy ); + pParse->pNewTrigger = pTrig; + pTrig = 0; + }else +#endif + + /* if we are not initializing, + ** build the sqlite_schema entry + */ + if( !db->init.busy ){ + Vdbe *v; + char *z; + + /* If this is a new CREATE TABLE statement, and if shadow tables + ** are read-only, and the trigger makes a change to a shadow table, + ** then raise an error - do not allow the trigger to be created. */ + if( sqlite3ReadOnlyShadowTables(db) ){ + TriggerStep *pStep; + for(pStep=pTrig->step_list; pStep; pStep=pStep->pNext){ + if( pStep->zTarget!=0 + && sqlite3ShadowTableName(db, pStep->zTarget) + ){ + sqlite3ErrorMsg(pParse, + "trigger \"%s\" may not write to shadow table \"%s\"", + pTrig->zName, pStep->zTarget); + goto triggerfinish_cleanup; + } + } + } + + /* Make an entry in the sqlite_schema table */ + v = sqlite3GetVdbe(pParse); + if( v==0 ) goto triggerfinish_cleanup; + sqlite3BeginWriteOperation(pParse, 0, iDb); + z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n); + testcase( z==0 ); + sqlite3NestedParse(pParse, + "INSERT INTO %Q." LEGACY_SCHEMA_TABLE + " VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", + db->aDb[iDb].zDbSName, zName, + pTrig->table, z); + sqlite3DbFree(db, z); + sqlite3ChangeCookie(pParse, iDb); + sqlite3VdbeAddParseSchemaOp(v, iDb, + sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName), 0); + } + + if( db->init.busy ){ + Trigger *pLink = pTrig; + Hash *pHash = &db->aDb[iDb].pSchema->trigHash; + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + assert( pLink!=0 ); + pTrig = sqlite3HashInsert(pHash, zName, pTrig); + if( pTrig ){ + sqlite3OomFault(db); + }else if( pLink->pSchema==pLink->pTabSchema ){ + Table *pTab; + pTab = sqlite3HashFind(&pLink->pTabSchema->tblHash, pLink->table); + assert( pTab!=0 ); + pLink->pNext = pTab->pTrigger; + pTab->pTrigger = pLink; + } + } + +triggerfinish_cleanup: + sqlite3DeleteTrigger(db, pTrig); + assert( IN_RENAME_OBJECT || !pParse->pNewTrigger ); + sqlite3DeleteTriggerStep(db, pStepList); +} + +/* +** Duplicate a range of text from an SQL statement, then convert all +** whitespace characters into ordinary space characters. +*/ +static char *triggerSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){ + char *z = sqlite3DbSpanDup(db, zStart, zEnd); + int i; + if( z ) for(i=0; z[i]; i++) if( sqlite3Isspace(z[i]) ) z[i] = ' '; + return z; +} + +/* +** Turn a SELECT statement (that the pSelect parameter points to) into +** a trigger step. Return a pointer to a TriggerStep structure. +** +** The parser calls this routine when it finds a SELECT statement in +** body of a TRIGGER. +*/ +SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep( + sqlite3 *db, /* Database connection */ + Select *pSelect, /* The SELECT statement */ + const char *zStart, /* Start of SQL text */ + const char *zEnd /* End of SQL text */ +){ + TriggerStep *pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep)); + if( pTriggerStep==0 ) { + sqlite3SelectDelete(db, pSelect); + return 0; + } + pTriggerStep->op = TK_SELECT; + pTriggerStep->pSelect = pSelect; + pTriggerStep->orconf = OE_Default; + pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); + return pTriggerStep; +} + +/* +** Allocate space to hold a new trigger step. The allocated space +** holds both the TriggerStep object and the TriggerStep.target.z string. +** +** If an OOM error occurs, NULL is returned and db->mallocFailed is set. +*/ +static TriggerStep *triggerStepAllocate( + Parse *pParse, /* Parser context */ + u8 op, /* Trigger opcode */ + Token *pName, /* The target name */ + const char *zStart, /* Start of SQL text */ + const char *zEnd /* End of SQL text */ +){ + sqlite3 *db = pParse->db; + TriggerStep *pTriggerStep; + + if( pParse->nErr ) return 0; + pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1); + if( pTriggerStep ){ + char *z = (char*)&pTriggerStep[1]; + memcpy(z, pName->z, pName->n); + sqlite3Dequote(z); + pTriggerStep->zTarget = z; + pTriggerStep->op = op; + pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, pTriggerStep->zTarget, pName); + } + } + return pTriggerStep; +} + +/* +** Build a trigger step out of an INSERT statement. Return a pointer +** to the new trigger step. +** +** The parser calls this routine when it sees an INSERT inside the +** body of a trigger. +*/ +SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep( + Parse *pParse, /* Parser */ + Token *pTableName, /* Name of the table into which we insert */ + IdList *pColumn, /* List of columns in pTableName to insert into */ + Select *pSelect, /* A SELECT statement that supplies values */ + u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */ + Upsert *pUpsert, /* ON CONFLICT clauses for upsert */ + const char *zStart, /* Start of SQL text */ + const char *zEnd /* End of SQL text */ +){ + sqlite3 *db = pParse->db; + TriggerStep *pTriggerStep; + + assert(pSelect != 0 || db->mallocFailed); + + pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTableName,zStart,zEnd); + if( pTriggerStep ){ + if( IN_RENAME_OBJECT ){ + pTriggerStep->pSelect = pSelect; + pSelect = 0; + }else{ + pTriggerStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); + } + pTriggerStep->pIdList = pColumn; + pTriggerStep->pUpsert = pUpsert; + pTriggerStep->orconf = orconf; + if( pUpsert ){ + sqlite3HasExplicitNulls(pParse, pUpsert->pUpsertTarget); + } + }else{ + testcase( pColumn ); + sqlite3IdListDelete(db, pColumn); + testcase( pUpsert ); + sqlite3UpsertDelete(db, pUpsert); + } + sqlite3SelectDelete(db, pSelect); + + return pTriggerStep; +} + +/* +** Construct a trigger step that implements an UPDATE statement and return +** a pointer to that trigger step. The parser calls this routine when it +** sees an UPDATE statement inside the body of a CREATE TRIGGER. +*/ +SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep( + Parse *pParse, /* Parser */ + Token *pTableName, /* Name of the table to be updated */ + SrcList *pFrom, /* FROM clause for an UPDATE-FROM, or NULL */ + ExprList *pEList, /* The SET clause: list of column and new values */ + Expr *pWhere, /* The WHERE clause */ + u8 orconf, /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */ + const char *zStart, /* Start of SQL text */ + const char *zEnd /* End of SQL text */ +){ + sqlite3 *db = pParse->db; + TriggerStep *pTriggerStep; + + pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTableName,zStart,zEnd); + if( pTriggerStep ){ + if( IN_RENAME_OBJECT ){ + pTriggerStep->pExprList = pEList; + pTriggerStep->pWhere = pWhere; + pTriggerStep->pFrom = pFrom; + pEList = 0; + pWhere = 0; + pFrom = 0; + }else{ + pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE); + pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); + pTriggerStep->pFrom = sqlite3SrcListDup(db, pFrom, EXPRDUP_REDUCE); + } + pTriggerStep->orconf = orconf; + } + sqlite3ExprListDelete(db, pEList); + sqlite3ExprDelete(db, pWhere); + sqlite3SrcListDelete(db, pFrom); + return pTriggerStep; +} + +/* +** Construct a trigger step that implements a DELETE statement and return +** a pointer to that trigger step. The parser calls this routine when it +** sees a DELETE statement inside the body of a CREATE TRIGGER. +*/ +SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep( + Parse *pParse, /* Parser */ + Token *pTableName, /* The table from which rows are deleted */ + Expr *pWhere, /* The WHERE clause */ + const char *zStart, /* Start of SQL text */ + const char *zEnd /* End of SQL text */ +){ + sqlite3 *db = pParse->db; + TriggerStep *pTriggerStep; + + pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTableName,zStart,zEnd); + if( pTriggerStep ){ + if( IN_RENAME_OBJECT ){ + pTriggerStep->pWhere = pWhere; + pWhere = 0; + }else{ + pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); + } + pTriggerStep->orconf = OE_Default; + } + sqlite3ExprDelete(db, pWhere); + return pTriggerStep; +} + +/* +** Recursively delete a Trigger structure +*/ +SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){ + if( pTrigger==0 || pTrigger->bReturning ) return; + sqlite3DeleteTriggerStep(db, pTrigger->step_list); + sqlite3DbFree(db, pTrigger->zName); + sqlite3DbFree(db, pTrigger->table); + sqlite3ExprDelete(db, pTrigger->pWhen); + sqlite3IdListDelete(db, pTrigger->pColumns); + sqlite3DbFree(db, pTrigger); +} + +/* +** This function is called to drop a trigger from the database schema. +** +** This may be called directly from the parser and therefore identifies +** the trigger by name. The sqlite3DropTriggerPtr() routine does the +** same job as this routine except it takes a pointer to the trigger +** instead of the trigger name. +**/ +SQLITE_PRIVATE void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){ + Trigger *pTrigger = 0; + int i; + const char *zDb; + const char *zName; + sqlite3 *db = pParse->db; + + if( db->mallocFailed ) goto drop_trigger_cleanup; + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ + goto drop_trigger_cleanup; + } + + assert( pName->nSrc==1 ); + zDb = pName->a[0].zDatabase; + zName = pName->a[0].zName; + assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) ); + for(i=OMIT_TEMPDB; i<db->nDb; i++){ + int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ + if( zDb && sqlite3DbIsNamed(db, j, zDb)==0 ) continue; + assert( sqlite3SchemaMutexHeld(db, j, 0) ); + pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName); + if( pTrigger ) break; + } + if( !pTrigger ){ + if( !noErr ){ + sqlite3ErrorMsg(pParse, "no such trigger: %S", pName->a); + }else{ + sqlite3CodeVerifyNamedSchema(pParse, zDb); + } + pParse->checkSchema = 1; + goto drop_trigger_cleanup; + } + sqlite3DropTriggerPtr(pParse, pTrigger); + +drop_trigger_cleanup: + sqlite3SrcListDelete(db, pName); +} + +/* +** Return a pointer to the Table structure for the table that a trigger +** is set on. +*/ +static Table *tableOfTrigger(Trigger *pTrigger){ + return sqlite3HashFind(&pTrigger->pTabSchema->tblHash, pTrigger->table); +} + + +/* +** Drop a trigger given a pointer to that trigger. +*/ +SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ + Table *pTable; + Vdbe *v; + sqlite3 *db = pParse->db; + int iDb; + + iDb = sqlite3SchemaToIndex(pParse->db, pTrigger->pSchema); + assert( iDb>=0 && iDb<db->nDb ); + pTable = tableOfTrigger(pTrigger); + assert( (pTable && pTable->pSchema==pTrigger->pSchema) || iDb==1 ); +#ifndef SQLITE_OMIT_AUTHORIZATION + if( pTable ){ + int code = SQLITE_DROP_TRIGGER; + const char *zDb = db->aDb[iDb].zDbSName; + const char *zTab = SCHEMA_TABLE(iDb); + if( iDb==1 ) code = SQLITE_DROP_TEMP_TRIGGER; + if( sqlite3AuthCheck(pParse, code, pTrigger->zName, pTable->zName, zDb) || + sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){ + return; + } + } +#endif + + /* Generate code to destroy the database record of the trigger. + */ + if( (v = sqlite3GetVdbe(pParse))!=0 ){ + sqlite3NestedParse(pParse, + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE name=%Q AND type='trigger'", + db->aDb[iDb].zDbSName, pTrigger->zName + ); + sqlite3ChangeCookie(pParse, iDb); + sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0); + } +} + +/* +** Remove a trigger from the hash tables of the sqlite* pointer. +*/ +SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3 *db, int iDb, const char *zName){ + Trigger *pTrigger; + Hash *pHash; + + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + pHash = &(db->aDb[iDb].pSchema->trigHash); + pTrigger = sqlite3HashInsert(pHash, zName, 0); + if( ALWAYS(pTrigger) ){ + if( pTrigger->pSchema==pTrigger->pTabSchema ){ + Table *pTab = tableOfTrigger(pTrigger); + if( pTab ){ + Trigger **pp; + for(pp=&pTab->pTrigger; *pp; pp=&((*pp)->pNext)){ + if( *pp==pTrigger ){ + *pp = (*pp)->pNext; + break; + } + } + } + } + sqlite3DeleteTrigger(db, pTrigger); + db->mDbFlags |= DBFLAG_SchemaChange; + } +} + +/* +** pEList is the SET clause of an UPDATE statement. Each entry +** in pEList is of the format <id>=<expr>. If any of the entries +** in pEList have an <id> which matches an identifier in pIdList, +** then return TRUE. If pIdList==NULL, then it is considered a +** wildcard that matches anything. Likewise if pEList==NULL then +** it matches anything so always return true. Return false only +** if there is no match. +*/ +static int checkColumnOverlap(IdList *pIdList, ExprList *pEList){ + int e; + if( pIdList==0 || NEVER(pEList==0) ) return 1; + for(e=0; e<pEList->nExpr; e++){ + if( sqlite3IdListIndex(pIdList, pEList->a[e].zEName)>=0 ) return 1; + } + return 0; +} + +/* +** Return true if any TEMP triggers exist +*/ +static int tempTriggersExist(sqlite3 *db){ + if( NEVER(db->aDb[1].pSchema==0) ) return 0; + if( sqliteHashFirst(&db->aDb[1].pSchema->trigHash)==0 ) return 0; + return 1; +} + +/* +** Return a list of all triggers on table pTab if there exists at least +** one trigger that must be fired when an operation of type 'op' is +** performed on the table, and, if that operation is an UPDATE, if at +** least one of the columns in pChanges is being modified. +*/ +static SQLITE_NOINLINE Trigger *triggersReallyExist( + Parse *pParse, /* Parse context */ + Table *pTab, /* The table the contains the triggers */ + int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */ + ExprList *pChanges, /* Columns that change in an UPDATE statement */ + int *pMask /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ +){ + int mask = 0; + Trigger *pList = 0; + Trigger *p; + + pList = sqlite3TriggerList(pParse, pTab); + assert( pList==0 || IsVirtual(pTab)==0 + || (pList->bReturning && pList->pNext==0) ); + if( pList!=0 ){ + p = pList; + if( (pParse->db->flags & SQLITE_EnableTrigger)==0 + && pTab->pTrigger!=0 + ){ + /* The SQLITE_DBCONFIG_ENABLE_TRIGGER setting is off. That means that + ** only TEMP triggers are allowed. Truncate the pList so that it + ** includes only TEMP triggers */ + if( pList==pTab->pTrigger ){ + pList = 0; + goto exit_triggers_exist; + } + while( ALWAYS(p->pNext) && p->pNext!=pTab->pTrigger ) p = p->pNext; + p->pNext = 0; + p = pList; + } + do{ + if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){ + mask |= p->tr_tm; + }else if( p->op==TK_RETURNING ){ + /* The first time a RETURNING trigger is seen, the "op" value tells + ** us what time of trigger it should be. */ + assert( sqlite3IsToplevel(pParse) ); + p->op = op; + if( IsVirtual(pTab) ){ + if( op!=TK_INSERT ){ + sqlite3ErrorMsg(pParse, + "%s RETURNING is not available on virtual tables", + op==TK_DELETE ? "DELETE" : "UPDATE"); + } + p->tr_tm = TRIGGER_BEFORE; + }else{ + p->tr_tm = TRIGGER_AFTER; + } + mask |= p->tr_tm; + }else if( p->bReturning && p->op==TK_INSERT && op==TK_UPDATE + && sqlite3IsToplevel(pParse) ){ + /* Also fire a RETURNING trigger for an UPSERT */ + mask |= p->tr_tm; + } + p = p->pNext; + }while( p ); + } +exit_triggers_exist: + if( pMask ){ + *pMask = mask; + } + return (mask ? pList : 0); +} +SQLITE_PRIVATE Trigger *sqlite3TriggersExist( + Parse *pParse, /* Parse context */ + Table *pTab, /* The table the contains the triggers */ + int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */ + ExprList *pChanges, /* Columns that change in an UPDATE statement */ + int *pMask /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ +){ + assert( pTab!=0 ); + if( (pTab->pTrigger==0 && !tempTriggersExist(pParse->db)) + || pParse->disableTriggers + ){ + if( pMask ) *pMask = 0; + return 0; + } + return triggersReallyExist(pParse,pTab,op,pChanges,pMask); +} + +/* +** Convert the pStep->zTarget string into a SrcList and return a pointer +** to that SrcList. +** +** This routine adds a specific database name, if needed, to the target when +** forming the SrcList. This prevents a trigger in one database from +** referring to a target in another database. An exception is when the +** trigger is in TEMP in which case it can refer to any other database it +** wants. +*/ +SQLITE_PRIVATE SrcList *sqlite3TriggerStepSrc( + Parse *pParse, /* The parsing context */ + TriggerStep *pStep /* The trigger containing the target token */ +){ + sqlite3 *db = pParse->db; + SrcList *pSrc; /* SrcList to be returned */ + char *zName = sqlite3DbStrDup(db, pStep->zTarget); + pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + assert( pSrc==0 || pSrc->nSrc==1 ); + assert( zName || pSrc==0 ); + if( pSrc ){ + Schema *pSchema = pStep->pTrig->pSchema; + pSrc->a[0].zName = zName; + if( pSchema!=db->aDb[1].pSchema ){ + pSrc->a[0].pSchema = pSchema; + } + if( pStep->pFrom ){ + SrcList *pDup = sqlite3SrcListDup(db, pStep->pFrom, 0); + if( pDup && pDup->nSrc>1 && !IN_RENAME_OBJECT ){ + Select *pSubquery; + Token as; + pSubquery = sqlite3SelectNew(pParse,0,pDup,0,0,0,0,SF_NestedFrom,0); + as.n = 0; + as.z = 0; + pDup = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); + } + pSrc = sqlite3SrcListAppendList(pParse, pSrc, pDup); + } + }else{ + sqlite3DbFree(db, zName); + } + return pSrc; +} + +/* +** Return true if the pExpr term from the RETURNING clause argument +** list is of the form "*". Raise an error if the terms if of the +** form "table.*". +*/ +static int isAsteriskTerm( + Parse *pParse, /* Parsing context */ + Expr *pTerm /* A term in the RETURNING clause */ +){ + assert( pTerm!=0 ); + if( pTerm->op==TK_ASTERISK ) return 1; + if( pTerm->op!=TK_DOT ) return 0; + assert( pTerm->pRight!=0 ); + assert( pTerm->pLeft!=0 ); + if( pTerm->pRight->op!=TK_ASTERISK ) return 0; + sqlite3ErrorMsg(pParse, "RETURNING may not use \"TABLE.*\" wildcards"); + return 1; +} + +/* The input list pList is the list of result set terms from a RETURNING +** clause. The table that we are returning from is pTab. +** +** This routine makes a copy of the pList, and at the same time expands +** any "*" wildcards to be the complete set of columns from pTab. +*/ +static ExprList *sqlite3ExpandReturning( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* The arguments to RETURNING */ + Table *pTab /* The table being updated */ +){ + ExprList *pNew = 0; + sqlite3 *db = pParse->db; + int i; + + for(i=0; i<pList->nExpr; i++){ + Expr *pOldExpr = pList->a[i].pExpr; + if( NEVER(pOldExpr==0) ) continue; + if( isAsteriskTerm(pParse, pOldExpr) ){ + int jj; + for(jj=0; jj<pTab->nCol; jj++){ + Expr *pNewExpr; + if( IsHiddenColumn(pTab->aCol+jj) ) continue; + pNewExpr = sqlite3Expr(db, TK_ID, pTab->aCol[jj].zCnName); + pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr); + if( !db->mallocFailed ){ + struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; + pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zCnName); + pItem->fg.eEName = ENAME_NAME; + } + } + }else{ + Expr *pNewExpr = sqlite3ExprDup(db, pOldExpr, 0); + pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr); + if( !db->mallocFailed && ALWAYS(pList->a[i].zEName!=0) ){ + struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; + pItem->zEName = sqlite3DbStrDup(db, pList->a[i].zEName); + pItem->fg.eEName = pList->a[i].fg.eEName; + } + } + } + return pNew; +} + +/* +** Generate code for the RETURNING trigger. Unlike other triggers +** that invoke a subprogram in the bytecode, the code for RETURNING +** is generated in-line. +*/ +static void codeReturningTrigger( + Parse *pParse, /* Parse context */ + Trigger *pTrigger, /* The trigger step that defines the RETURNING */ + Table *pTab, /* The table to code triggers from */ + int regIn /* The first in an array of registers */ +){ + Vdbe *v = pParse->pVdbe; + sqlite3 *db = pParse->db; + ExprList *pNew; + Returning *pReturning; + Select sSelect; + SrcList sFrom; + + assert( v!=0 ); + assert( pParse->bReturning ); + assert( db->pParse==pParse ); + pReturning = pParse->u1.pReturning; + assert( pTrigger == &(pReturning->retTrig) ); + memset(&sSelect, 0, sizeof(sSelect)); + memset(&sFrom, 0, sizeof(sFrom)); + sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); + sSelect.pSrc = &sFrom; + sFrom.nSrc = 1; + sFrom.a[0].pTab = pTab; + sFrom.a[0].iCursor = -1; + sqlite3SelectPrep(pParse, &sSelect, 0); + if( pParse->nErr==0 ){ + assert( db->mallocFailed==0 ); + sqlite3GenerateColumnNames(pParse, &sSelect); + } + sqlite3ExprListDelete(db, sSelect.pEList); + pNew = sqlite3ExpandReturning(pParse, pReturning->pReturnEL, pTab); + if( pParse->nErr==0 ){ + NameContext sNC; + memset(&sNC, 0, sizeof(sNC)); + if( pReturning->nRetCol==0 ){ + pReturning->nRetCol = pNew->nExpr; + pReturning->iRetCur = pParse->nTab++; + } + sNC.pParse = pParse; + sNC.uNC.iBaseReg = regIn; + sNC.ncFlags = NC_UBaseReg; + pParse->eTriggerOp = pTrigger->op; + pParse->pTriggerTab = pTab; + if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK + && ALWAYS(!db->mallocFailed) + ){ + int i; + int nCol = pNew->nExpr; + int reg = pParse->nMem+1; + pParse->nMem += nCol+2; + pReturning->iRetReg = reg; + for(i=0; i<nCol; i++){ + Expr *pCol = pNew->a[i].pExpr; + assert( pCol!=0 ); /* Due to !db->mallocFailed ~9 lines above */ + sqlite3ExprCodeFactorable(pParse, pCol, reg+i); + if( sqlite3ExprAffinity(pCol)==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, reg+i); + } + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, i, reg+i); + sqlite3VdbeAddOp2(v, OP_NewRowid, pReturning->iRetCur, reg+i+1); + sqlite3VdbeAddOp3(v, OP_Insert, pReturning->iRetCur, reg+i, reg+i+1); + } + } + sqlite3ExprListDelete(db, pNew); + pParse->eTriggerOp = 0; + pParse->pTriggerTab = 0; +} + + + +/* +** Generate VDBE code for the statements inside the body of a single +** trigger. +*/ +static int codeTriggerProgram( + Parse *pParse, /* The parser context */ + TriggerStep *pStepList, /* List of statements inside the trigger body */ + int orconf /* Conflict algorithm. (OE_Abort, etc) */ +){ + TriggerStep *pStep; + Vdbe *v = pParse->pVdbe; + sqlite3 *db = pParse->db; + + assert( pParse->pTriggerTab && pParse->pToplevel ); + assert( pStepList ); + assert( v!=0 ); + for(pStep=pStepList; pStep; pStep=pStep->pNext){ + /* Figure out the ON CONFLICT policy that will be used for this step + ** of the trigger program. If the statement that caused this trigger + ** to fire had an explicit ON CONFLICT, then use it. Otherwise, use + ** the ON CONFLICT policy that was specified as part of the trigger + ** step statement. Example: + ** + ** CREATE TRIGGER AFTER INSERT ON t1 BEGIN; + ** INSERT OR REPLACE INTO t2 VALUES(new.a, new.b); + ** END; + ** + ** INSERT INTO t1 ... ; -- insert into t2 uses REPLACE policy + ** INSERT OR IGNORE INTO t1 ... ; -- insert into t2 uses IGNORE policy + */ + pParse->eOrconf = (orconf==OE_Default)?pStep->orconf:(u8)orconf; + assert( pParse->okConstFactor==0 ); + +#ifndef SQLITE_OMIT_TRACE + if( pStep->zSpan ){ + sqlite3VdbeAddOp4(v, OP_Trace, 0x7fffffff, 1, 0, + sqlite3MPrintf(db, "-- %s", pStep->zSpan), + P4_DYNAMIC); + } +#endif + + switch( pStep->op ){ + case TK_UPDATE: { + sqlite3Update(pParse, + sqlite3TriggerStepSrc(pParse, pStep), + sqlite3ExprListDup(db, pStep->pExprList, 0), + sqlite3ExprDup(db, pStep->pWhere, 0), + pParse->eOrconf, 0, 0, 0 + ); + sqlite3VdbeAddOp0(v, OP_ResetCount); + break; + } + case TK_INSERT: { + sqlite3Insert(pParse, + sqlite3TriggerStepSrc(pParse, pStep), + sqlite3SelectDup(db, pStep->pSelect, 0), + sqlite3IdListDup(db, pStep->pIdList), + pParse->eOrconf, + sqlite3UpsertDup(db, pStep->pUpsert) + ); + sqlite3VdbeAddOp0(v, OP_ResetCount); + break; + } + case TK_DELETE: { + sqlite3DeleteFrom(pParse, + sqlite3TriggerStepSrc(pParse, pStep), + sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0 + ); + sqlite3VdbeAddOp0(v, OP_ResetCount); + break; + } + default: assert( pStep->op==TK_SELECT ); { + SelectDest sDest; + Select *pSelect = sqlite3SelectDup(db, pStep->pSelect, 0); + sqlite3SelectDestInit(&sDest, SRT_Discard, 0); + sqlite3Select(pParse, pSelect, &sDest); + sqlite3SelectDelete(db, pSelect); + break; + } + } + } + + return 0; +} + +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS +/* +** This function is used to add VdbeComment() annotations to a VDBE +** program. It is not used in production code, only for debugging. +*/ +static const char *onErrorText(int onError){ + switch( onError ){ + case OE_Abort: return "abort"; + case OE_Rollback: return "rollback"; + case OE_Fail: return "fail"; + case OE_Replace: return "replace"; + case OE_Ignore: return "ignore"; + case OE_Default: return "default"; + } + return "n/a"; +} +#endif + +/* +** Parse context structure pFrom has just been used to create a sub-vdbe +** (trigger program). If an error has occurred, transfer error information +** from pFrom to pTo. +*/ +static void transferParseError(Parse *pTo, Parse *pFrom){ + assert( pFrom->zErrMsg==0 || pFrom->nErr ); + assert( pTo->zErrMsg==0 || pTo->nErr ); + if( pTo->nErr==0 ){ + pTo->zErrMsg = pFrom->zErrMsg; + pTo->nErr = pFrom->nErr; + pTo->rc = pFrom->rc; + }else{ + sqlite3DbFree(pFrom->db, pFrom->zErrMsg); + } +} + +/* +** Create and populate a new TriggerPrg object with a sub-program +** implementing trigger pTrigger with ON CONFLICT policy orconf. +*/ +static TriggerPrg *codeRowTrigger( + Parse *pParse, /* Current parse context */ + Trigger *pTrigger, /* Trigger to code */ + Table *pTab, /* The table pTrigger is attached to */ + int orconf /* ON CONFLICT policy to code trigger program with */ +){ + Parse *pTop = sqlite3ParseToplevel(pParse); + sqlite3 *db = pParse->db; /* Database handle */ + TriggerPrg *pPrg; /* Value to return */ + Expr *pWhen = 0; /* Duplicate of trigger WHEN expression */ + Vdbe *v; /* Temporary VM */ + NameContext sNC; /* Name context for sub-vdbe */ + SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */ + int iEndTrigger = 0; /* Label to jump to if WHEN is false */ + Parse sSubParse; /* Parse context for sub-vdbe */ + + assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) ); + assert( pTop->pVdbe ); + + /* Allocate the TriggerPrg and SubProgram objects. To ensure that they + ** are freed if an error occurs, link them into the Parse.pTriggerPrg + ** list of the top-level Parse object sooner rather than later. */ + pPrg = sqlite3DbMallocZero(db, sizeof(TriggerPrg)); + if( !pPrg ) return 0; + pPrg->pNext = pTop->pTriggerPrg; + pTop->pTriggerPrg = pPrg; + pPrg->pProgram = pProgram = sqlite3DbMallocZero(db, sizeof(SubProgram)); + if( !pProgram ) return 0; + sqlite3VdbeLinkSubProgram(pTop->pVdbe, pProgram); + pPrg->pTrigger = pTrigger; + pPrg->orconf = orconf; + pPrg->aColmask[0] = 0xffffffff; + pPrg->aColmask[1] = 0xffffffff; + + /* Allocate and populate a new Parse context to use for coding the + ** trigger sub-program. */ + sqlite3ParseObjectInit(&sSubParse, db); + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = &sSubParse; + sSubParse.pTriggerTab = pTab; + sSubParse.pToplevel = pTop; + sSubParse.zAuthContext = pTrigger->zName; + sSubParse.eTriggerOp = pTrigger->op; + sSubParse.nQueryLoop = pParse->nQueryLoop; + sSubParse.prepFlags = pParse->prepFlags; + + v = sqlite3GetVdbe(&sSubParse); + if( v ){ + VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)", + pTrigger->zName, onErrorText(orconf), + (pTrigger->tr_tm==TRIGGER_BEFORE ? "BEFORE" : "AFTER"), + (pTrigger->op==TK_UPDATE ? "UPDATE" : ""), + (pTrigger->op==TK_INSERT ? "INSERT" : ""), + (pTrigger->op==TK_DELETE ? "DELETE" : ""), + pTab->zName + )); +#ifndef SQLITE_OMIT_TRACE + if( pTrigger->zName ){ + sqlite3VdbeChangeP4(v, -1, + sqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC + ); + } +#endif + + /* If one was specified, code the WHEN clause. If it evaluates to false + ** (or NULL) the sub-vdbe is immediately halted by jumping to the + ** OP_Halt inserted at the end of the program. */ + if( pTrigger->pWhen ){ + pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0); + if( db->mallocFailed==0 + && SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen) + ){ + iEndTrigger = sqlite3VdbeMakeLabel(&sSubParse); + sqlite3ExprIfFalse(&sSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL); + } + sqlite3ExprDelete(db, pWhen); + } + + /* Code the trigger program into the sub-vdbe. */ + codeTriggerProgram(&sSubParse, pTrigger->step_list, orconf); + + /* Insert an OP_Halt at the end of the sub-program. */ + if( iEndTrigger ){ + sqlite3VdbeResolveLabel(v, iEndTrigger); + } + sqlite3VdbeAddOp0(v, OP_Halt); + VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf))); + transferParseError(pParse, &sSubParse); + + if( pParse->nErr==0 ){ + assert( db->mallocFailed==0 ); + pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg); + } + pProgram->nMem = sSubParse.nMem; + pProgram->nCsr = sSubParse.nTab; + pProgram->token = (void *)pTrigger; + pPrg->aColmask[0] = sSubParse.oldmask; + pPrg->aColmask[1] = sSubParse.newmask; + sqlite3VdbeDelete(v); + }else{ + transferParseError(pParse, &sSubParse); + } + + assert( !sSubParse.pTriggerPrg && !sSubParse.nMaxArg ); + sqlite3ParseObjectReset(&sSubParse); + return pPrg; +} + +/* +** Return a pointer to a TriggerPrg object containing the sub-program for +** trigger pTrigger with default ON CONFLICT algorithm orconf. If no such +** TriggerPrg object exists, a new object is allocated and populated before +** being returned. +*/ +static TriggerPrg *getRowTrigger( + Parse *pParse, /* Current parse context */ + Trigger *pTrigger, /* Trigger to code */ + Table *pTab, /* The table trigger pTrigger is attached to */ + int orconf /* ON CONFLICT algorithm. */ +){ + Parse *pRoot = sqlite3ParseToplevel(pParse); + TriggerPrg *pPrg; + + assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) ); + + /* It may be that this trigger has already been coded (or is in the + ** process of being coded). If this is the case, then an entry with + ** a matching TriggerPrg.pTrigger field will be present somewhere + ** in the Parse.pTriggerPrg list. Search for such an entry. */ + for(pPrg=pRoot->pTriggerPrg; + pPrg && (pPrg->pTrigger!=pTrigger || pPrg->orconf!=orconf); + pPrg=pPrg->pNext + ); + + /* If an existing TriggerPrg could not be located, create a new one. */ + if( !pPrg ){ + pPrg = codeRowTrigger(pParse, pTrigger, pTab, orconf); + pParse->db->errByteOffset = -1; + } + + return pPrg; +} + +/* +** Generate code for the trigger program associated with trigger p on +** table pTab. The reg, orconf and ignoreJump parameters passed to this +** function are the same as those described in the header function for +** sqlite3CodeRowTrigger() +*/ +SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect( + Parse *pParse, /* Parse context */ + Trigger *p, /* Trigger to code */ + Table *pTab, /* The table to code triggers from */ + int reg, /* Reg array containing OLD.* and NEW.* values */ + int orconf, /* ON CONFLICT policy */ + int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */ +){ + Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */ + TriggerPrg *pPrg; + pPrg = getRowTrigger(pParse, p, pTab, orconf); + assert( pPrg || pParse->nErr ); + + /* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program + ** is a pointer to the sub-vdbe containing the trigger program. */ + if( pPrg ){ + int bRecursive = (p->zName && 0==(pParse->db->flags&SQLITE_RecTriggers)); + + sqlite3VdbeAddOp4(v, OP_Program, reg, ignoreJump, ++pParse->nMem, + (const char *)pPrg->pProgram, P4_SUBPROGRAM); + VdbeComment( + (v, "Call: %s.%s", (p->zName?p->zName:"fkey"), onErrorText(orconf))); + + /* Set the P5 operand of the OP_Program instruction to non-zero if + ** recursive invocation of this trigger program is disallowed. Recursive + ** invocation is disallowed if (a) the sub-program is really a trigger, + ** not a foreign key action, and (b) the flag to enable recursive triggers + ** is clear. */ + sqlite3VdbeChangeP5(v, (u8)bRecursive); + } +} + +/* +** This is called to code the required FOR EACH ROW triggers for an operation +** on table pTab. The operation to code triggers for (INSERT, UPDATE or DELETE) +** is given by the op parameter. The tr_tm parameter determines whether the +** BEFORE or AFTER triggers are coded. If the operation is an UPDATE, then +** parameter pChanges is passed the list of columns being modified. +** +** If there are no triggers that fire at the specified time for the specified +** operation on pTab, this function is a no-op. +** +** The reg argument is the address of the first in an array of registers +** that contain the values substituted for the new.* and old.* references +** in the trigger program. If N is the number of columns in table pTab +** (a copy of pTab->nCol), then registers are populated as follows: +** +** Register Contains +** ------------------------------------------------------ +** reg+0 OLD.rowid +** reg+1 OLD.* value of left-most column of pTab +** ... ... +** reg+N OLD.* value of right-most column of pTab +** reg+N+1 NEW.rowid +** reg+N+2 NEW.* value of left-most column of pTab +** ... ... +** reg+N+N+1 NEW.* value of right-most column of pTab +** +** For ON DELETE triggers, the registers containing the NEW.* values will +** never be accessed by the trigger program, so they are not allocated or +** populated by the caller (there is no data to populate them with anyway). +** Similarly, for ON INSERT triggers the values stored in the OLD.* registers +** are never accessed, and so are not allocated by the caller. So, for an +** ON INSERT trigger, the value passed to this function as parameter reg +** is not a readable register, although registers (reg+N) through +** (reg+N+N+1) are. +** +** Parameter orconf is the default conflict resolution algorithm for the +** trigger program to use (REPLACE, IGNORE etc.). Parameter ignoreJump +** is the instruction that control should jump to if a trigger program +** raises an IGNORE exception. +*/ +SQLITE_PRIVATE void sqlite3CodeRowTrigger( + Parse *pParse, /* Parse context */ + Trigger *pTrigger, /* List of triggers on table pTab */ + int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */ + ExprList *pChanges, /* Changes list for any UPDATE OF triggers */ + int tr_tm, /* One of TRIGGER_BEFORE, TRIGGER_AFTER */ + Table *pTab, /* The table to code triggers from */ + int reg, /* The first in an array of registers (see above) */ + int orconf, /* ON CONFLICT policy */ + int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */ +){ + Trigger *p; /* Used to iterate through pTrigger list */ + + assert( op==TK_UPDATE || op==TK_INSERT || op==TK_DELETE ); + assert( tr_tm==TRIGGER_BEFORE || tr_tm==TRIGGER_AFTER ); + assert( (op==TK_UPDATE)==(pChanges!=0) ); + + for(p=pTrigger; p; p=p->pNext){ + + /* Sanity checking: The schema for the trigger and for the table are + ** always defined. The trigger must be in the same schema as the table + ** or else it must be a TEMP trigger. */ + assert( p->pSchema!=0 ); + assert( p->pTabSchema!=0 ); + assert( p->pSchema==p->pTabSchema + || p->pSchema==pParse->db->aDb[1].pSchema ); + + /* Determine whether we should code this trigger. One of two choices: + ** 1. The trigger is an exact match to the current DML statement + ** 2. This is a RETURNING trigger for INSERT but we are currently + ** doing the UPDATE part of an UPSERT. + */ + if( (p->op==op || (p->bReturning && p->op==TK_INSERT && op==TK_UPDATE)) + && p->tr_tm==tr_tm + && checkColumnOverlap(p->pColumns, pChanges) + ){ + if( !p->bReturning ){ + sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump); + }else if( sqlite3IsToplevel(pParse) ){ + codeReturningTrigger(pParse, p, pTab, reg); + } + } + } +} + +/* +** Triggers may access values stored in the old.* or new.* pseudo-table. +** This function returns a 32-bit bitmask indicating which columns of the +** old.* or new.* tables actually are used by triggers. This information +** may be used by the caller, for example, to avoid having to load the entire +** old.* record into memory when executing an UPDATE or DELETE command. +** +** Bit 0 of the returned mask is set if the left-most column of the +** table may be accessed using an [old|new].<col> reference. Bit 1 is set if +** the second leftmost column value is required, and so on. If there +** are more than 32 columns in the table, and at least one of the columns +** with an index greater than 32 may be accessed, 0xffffffff is returned. +** +** It is not possible to determine if the old.rowid or new.rowid column is +** accessed by triggers. The caller must always assume that it is. +** +** Parameter isNew must be either 1 or 0. If it is 0, then the mask returned +** applies to the old.* table. If 1, the new.* table. +** +** Parameter tr_tm must be a mask with one or both of the TRIGGER_BEFORE +** and TRIGGER_AFTER bits set. Values accessed by BEFORE triggers are only +** included in the returned mask if the TRIGGER_BEFORE bit is set in the +** tr_tm parameter. Similarly, values accessed by AFTER triggers are only +** included in the returned mask if the TRIGGER_AFTER bit is set in tr_tm. +*/ +SQLITE_PRIVATE u32 sqlite3TriggerColmask( + Parse *pParse, /* Parse context */ + Trigger *pTrigger, /* List of triggers on table pTab */ + ExprList *pChanges, /* Changes list for any UPDATE OF triggers */ + int isNew, /* 1 for new.* ref mask, 0 for old.* ref mask */ + int tr_tm, /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ + Table *pTab, /* The table to code triggers from */ + int orconf /* Default ON CONFLICT policy for trigger steps */ +){ + const int op = pChanges ? TK_UPDATE : TK_DELETE; + u32 mask = 0; + Trigger *p; + + assert( isNew==1 || isNew==0 ); + if( IsView(pTab) ){ + return 0xffffffff; + } + for(p=pTrigger; p; p=p->pNext){ + if( p->op==op + && (tr_tm&p->tr_tm) + && checkColumnOverlap(p->pColumns,pChanges) + ){ + if( p->bReturning ){ + mask = 0xffffffff; + }else{ + TriggerPrg *pPrg; + pPrg = getRowTrigger(pParse, p, pTab, orconf); + if( pPrg ){ + mask |= pPrg->aColmask[isNew]; + } + } + } + } + + return mask; +} + +#endif /* !defined(SQLITE_OMIT_TRIGGER) */ + +/************** End of trigger.c *********************************************/ +/************** Begin file update.c ******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle UPDATE statements. +*/ +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Forward declaration */ +static void updateVirtualTable( + Parse *pParse, /* The parsing context */ + SrcList *pSrc, /* The virtual table to be modified */ + Table *pTab, /* The virtual table */ + ExprList *pChanges, /* The columns to change in the UPDATE statement */ + Expr *pRowidExpr, /* Expression used to recompute the rowid */ + int *aXRef, /* Mapping from columns of pTab to entries in pChanges */ + Expr *pWhere, /* WHERE clause of the UPDATE statement */ + int onError /* ON CONFLICT strategy */ +); +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +/* +** The most recently coded instruction was an OP_Column to retrieve the +** i-th column of table pTab. This routine sets the P4 parameter of the +** OP_Column to the default value, if any. +** +** The default value of a column is specified by a DEFAULT clause in the +** column definition. This was either supplied by the user when the table +** was created, or added later to the table definition by an ALTER TABLE +** command. If the latter, then the row-records in the table btree on disk +** may not contain a value for the column and the default value, taken +** from the P4 parameter of the OP_Column instruction, is returned instead. +** If the former, then all row-records are guaranteed to include a value +** for the column and the P4 value is not required. +** +** Column definitions created by an ALTER TABLE command may only have +** literal default values specified: a number, null or a string. (If a more +** complicated default expression value was provided, it is evaluated +** when the ALTER TABLE is executed and one of the literal values written +** into the sqlite_schema table.) +** +** Therefore, the P4 parameter is only required if the default value for +** the column is a literal number, string or null. The sqlite3ValueFromExpr() +** function is capable of transforming these types of expressions into +** sqlite3_value objects. +** +** If column as REAL affinity and the table is an ordinary b-tree table +** (not a virtual table) then the value might have been stored as an +** integer. In that case, add an OP_RealAffinity opcode to make sure +** it has been converted into REAL. +*/ +SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){ + Column *pCol; + assert( pTab!=0 ); + assert( pTab->nCol>i ); + pCol = &pTab->aCol[i]; + if( pCol->iDflt ){ + sqlite3_value *pValue = 0; + u8 enc = ENC(sqlite3VdbeDb(v)); + assert( !IsView(pTab) ); + VdbeComment((v, "%s.%s", pTab->zName, pCol->zCnName)); + assert( i<pTab->nCol ); + sqlite3ValueFromExpr(sqlite3VdbeDb(v), + sqlite3ColumnExpr(pTab,pCol), enc, + pCol->affinity, &pValue); + if( pValue ){ + sqlite3VdbeAppendP4(v, pValue, P4_MEM); + } + } +#ifndef SQLITE_OMIT_FLOATING_POINT + if( pCol->affinity==SQLITE_AFF_REAL && !IsVirtual(pTab) ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg); + } +#endif +} + +/* +** Check to see if column iCol of index pIdx references any of the +** columns defined by aXRef and chngRowid. Return true if it does +** and false if not. This is an optimization. False-positives are a +** performance degradation, but false-negatives can result in a corrupt +** index and incorrect answers. +** +** aXRef[j] will be non-negative if column j of the original table is +** being updated. chngRowid will be true if the rowid of the table is +** being updated. +*/ +static int indexColumnIsBeingUpdated( + Index *pIdx, /* The index to check */ + int iCol, /* Which column of the index to check */ + int *aXRef, /* aXRef[j]>=0 if column j is being updated */ + int chngRowid /* true if the rowid is being updated */ +){ + i16 iIdxCol = pIdx->aiColumn[iCol]; + assert( iIdxCol!=XN_ROWID ); /* Cannot index rowid */ + if( iIdxCol>=0 ){ + return aXRef[iIdxCol]>=0; + } + assert( iIdxCol==XN_EXPR ); + assert( pIdx->aColExpr!=0 ); + assert( pIdx->aColExpr->a[iCol].pExpr!=0 ); + return sqlite3ExprReferencesUpdatedColumn(pIdx->aColExpr->a[iCol].pExpr, + aXRef,chngRowid); +} + +/* +** Check to see if index pIdx is a partial index whose conditional +** expression might change values due to an UPDATE. Return true if +** the index is subject to change and false if the index is guaranteed +** to be unchanged. This is an optimization. False-positives are a +** performance degradation, but false-negatives can result in a corrupt +** index and incorrect answers. +** +** aXRef[j] will be non-negative if column j of the original table is +** being updated. chngRowid will be true if the rowid of the table is +** being updated. +*/ +static int indexWhereClauseMightChange( + Index *pIdx, /* The index to check */ + int *aXRef, /* aXRef[j]>=0 if column j is being updated */ + int chngRowid /* true if the rowid is being updated */ +){ + if( pIdx->pPartIdxWhere==0 ) return 0; + return sqlite3ExprReferencesUpdatedColumn(pIdx->pPartIdxWhere, + aXRef, chngRowid); +} + +/* +** Allocate and return a pointer to an expression of type TK_ROW with +** Expr.iColumn set to value (iCol+1). The resolver will modify the +** expression to be a TK_COLUMN reading column iCol of the first +** table in the source-list (pSrc->a[0]). +*/ +static Expr *exprRowColumn(Parse *pParse, int iCol){ + Expr *pRet = sqlite3PExpr(pParse, TK_ROW, 0, 0); + if( pRet ) pRet->iColumn = iCol+1; + return pRet; +} + +/* +** Assuming both the pLimit and pOrderBy parameters are NULL, this function +** generates VM code to run the query: +** +** SELECT <other-columns>, pChanges FROM pTabList WHERE pWhere +** +** and write the results to the ephemeral table already opened as cursor +** iEph. None of pChanges, pTabList or pWhere are modified or consumed by +** this function, they must be deleted by the caller. +** +** Or, if pLimit and pOrderBy are not NULL, and pTab is not a view: +** +** SELECT <other-columns>, pChanges FROM pTabList +** WHERE pWhere +** GROUP BY <other-columns> +** ORDER BY pOrderBy LIMIT pLimit +** +** If pTab is a view, the GROUP BY clause is omitted. +** +** Exactly how results are written to table iEph, and exactly what +** the <other-columns> in the query above are is determined by the type +** of table pTabList->a[0].pTab. +** +** If the table is a WITHOUT ROWID table, then argument pPk must be its +** PRIMARY KEY. In this case <other-columns> are the primary key columns +** of the table, in order. The results of the query are written to ephemeral +** table iEph as index keys, using OP_IdxInsert. +** +** If the table is actually a view, then <other-columns> are all columns of +** the view. The results are written to the ephemeral table iEph as records +** with automatically assigned integer keys. +** +** If the table is a virtual or ordinary intkey table, then <other-columns> +** is its rowid. For a virtual table, the results are written to iEph as +** records with automatically assigned integer keys For intkey tables, the +** rowid value in <other-columns> is used as the integer key, and the +** remaining fields make up the table record. +*/ +static void updateFromSelect( + Parse *pParse, /* Parse context */ + int iEph, /* Cursor for open eph. table */ + Index *pPk, /* PK if table 0 is WITHOUT ROWID */ + ExprList *pChanges, /* List of expressions to return */ + SrcList *pTabList, /* List of tables to select from */ + Expr *pWhere, /* WHERE clause for query */ + ExprList *pOrderBy, /* ORDER BY clause */ + Expr *pLimit /* LIMIT clause */ +){ + int i; + SelectDest dest; + Select *pSelect = 0; + ExprList *pList = 0; + ExprList *pGrp = 0; + Expr *pLimit2 = 0; + ExprList *pOrderBy2 = 0; + sqlite3 *db = pParse->db; + Table *pTab = pTabList->a[0].pTab; + SrcList *pSrc; + Expr *pWhere2; + int eDest; + +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + if( pOrderBy && pLimit==0 ) { + sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on UPDATE"); + return; + } + pOrderBy2 = sqlite3ExprListDup(db, pOrderBy, 0); + pLimit2 = sqlite3ExprDup(db, pLimit, 0); +#else + UNUSED_PARAMETER(pOrderBy); + UNUSED_PARAMETER(pLimit); +#endif + + pSrc = sqlite3SrcListDup(db, pTabList, 0); + pWhere2 = sqlite3ExprDup(db, pWhere, 0); + + assert( pTabList->nSrc>1 ); + if( pSrc ){ + assert( pSrc->a[0].fg.notCte ); + pSrc->a[0].iCursor = -1; + pSrc->a[0].pTab->nTabRef--; + pSrc->a[0].pTab = 0; + } + if( pPk ){ + for(i=0; i<pPk->nKeyCol; i++){ + Expr *pNew = exprRowColumn(pParse, pPk->aiColumn[i]); +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + if( pLimit ){ + pGrp = sqlite3ExprListAppend(pParse, pGrp, sqlite3ExprDup(db, pNew, 0)); + } +#endif + pList = sqlite3ExprListAppend(pParse, pList, pNew); + } + eDest = IsVirtual(pTab) ? SRT_Table : SRT_Upfrom; + }else if( IsView(pTab) ){ + for(i=0; i<pTab->nCol; i++){ + pList = sqlite3ExprListAppend(pParse, pList, exprRowColumn(pParse, i)); + } + eDest = SRT_Table; + }else{ + eDest = IsVirtual(pTab) ? SRT_Table : SRT_Upfrom; + pList = sqlite3ExprListAppend(pParse, 0, sqlite3PExpr(pParse,TK_ROW,0,0)); +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + if( pLimit ){ + pGrp = sqlite3ExprListAppend(pParse, 0, sqlite3PExpr(pParse,TK_ROW,0,0)); + } +#endif + } + assert( pChanges!=0 || pParse->db->mallocFailed ); + if( pChanges ){ + for(i=0; i<pChanges->nExpr; i++){ + pList = sqlite3ExprListAppend(pParse, pList, + sqlite3ExprDup(db, pChanges->a[i].pExpr, 0) + ); + } + } + pSelect = sqlite3SelectNew(pParse, pList, + pSrc, pWhere2, pGrp, 0, pOrderBy2, + SF_UFSrcCheck|SF_IncludeHidden|SF_UpdateFrom, pLimit2 + ); + if( pSelect ) pSelect->selFlags |= SF_OrderByReqd; + sqlite3SelectDestInit(&dest, eDest, iEph); + dest.iSDParm2 = (pPk ? pPk->nKeyCol : -1); + sqlite3Select(pParse, pSelect, &dest); + sqlite3SelectDelete(db, pSelect); +} + +/* +** Process an UPDATE statement. +** +** UPDATE OR IGNORE tbl SET a=b, c=d FROM tbl2... WHERE e<5 AND f NOT NULL; +** \_______/ \_/ \______/ \_____/ \________________/ +** onError | pChanges | pWhere +** \_______________________/ +** pTabList +*/ +SQLITE_PRIVATE void sqlite3Update( + Parse *pParse, /* The parser context */ + SrcList *pTabList, /* The table in which we should change things */ + ExprList *pChanges, /* Things to be changed */ + Expr *pWhere, /* The WHERE clause. May be null */ + int onError, /* How to handle constraint errors */ + ExprList *pOrderBy, /* ORDER BY clause. May be null */ + Expr *pLimit, /* LIMIT clause. May be null */ + Upsert *pUpsert /* ON CONFLICT clause, or null */ +){ + int i, j, k; /* Loop counters */ + Table *pTab; /* The table to be updated */ + int addrTop = 0; /* VDBE instruction address of the start of the loop */ + WhereInfo *pWInfo = 0; /* Information about the WHERE clause */ + Vdbe *v; /* The virtual database engine */ + Index *pIdx; /* For looping over indices */ + Index *pPk; /* The PRIMARY KEY index for WITHOUT ROWID tables */ + int nIdx; /* Number of indices that need updating */ + int nAllIdx; /* Total number of indexes */ + int iBaseCur; /* Base cursor number */ + int iDataCur; /* Cursor for the canonical data btree */ + int iIdxCur; /* Cursor for the first index */ + sqlite3 *db; /* The database structure */ + int *aRegIdx = 0; /* Registers for to each index and the main table */ + int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the + ** an expression for the i-th column of the table. + ** aXRef[i]==-1 if the i-th column is not changed. */ + u8 *aToOpen; /* 1 for tables and indices to be opened */ + u8 chngPk; /* PRIMARY KEY changed in a WITHOUT ROWID table */ + u8 chngRowid; /* Rowid changed in a normal table */ + u8 chngKey; /* Either chngPk or chngRowid */ + Expr *pRowidExpr = 0; /* Expression defining the new record number */ + int iRowidExpr = -1; /* Index of "rowid=" (or IPK) assignment in pChanges */ + AuthContext sContext; /* The authorization context */ + NameContext sNC; /* The name-context to resolve expressions in */ + int iDb; /* Database containing the table being updated */ + int eOnePass; /* ONEPASS_XXX value from where.c */ + int hasFK; /* True if foreign key processing is required */ + int labelBreak; /* Jump here to break out of UPDATE loop */ + int labelContinue; /* Jump here to continue next step of UPDATE loop */ + int flags; /* Flags for sqlite3WhereBegin() */ + +#ifndef SQLITE_OMIT_TRIGGER + int isView; /* True when updating a view (INSTEAD OF trigger) */ + Trigger *pTrigger; /* List of triggers on pTab, if required */ + int tmask; /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ +#endif + int newmask; /* Mask of NEW.* columns accessed by BEFORE triggers */ + int iEph = 0; /* Ephemeral table holding all primary key values */ + int nKey = 0; /* Number of elements in regKey for WITHOUT ROWID */ + int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */ + int addrOpen = 0; /* Address of OP_OpenEphemeral */ + int iPk = 0; /* First of nPk cells holding PRIMARY KEY value */ + i16 nPk = 0; /* Number of components of the PRIMARY KEY */ + int bReplace = 0; /* True if REPLACE conflict resolution might happen */ + int bFinishSeek = 1; /* The OP_FinishSeek opcode is needed */ + int nChangeFrom = 0; /* If there is a FROM, pChanges->nExpr, else 0 */ + + /* Register Allocations */ + int regRowCount = 0; /* A count of rows changed */ + int regOldRowid = 0; /* The old rowid */ + int regNewRowid = 0; /* The new rowid */ + int regNew = 0; /* Content of the NEW.* table in triggers */ + int regOld = 0; /* Content of OLD.* table in triggers */ + int regRowSet = 0; /* Rowset of rows to be updated */ + int regKey = 0; /* composite PRIMARY KEY value */ + + memset(&sContext, 0, sizeof(sContext)); + db = pParse->db; + assert( db->pParse==pParse ); + if( pParse->nErr ){ + goto update_cleanup; + } + assert( db->mallocFailed==0 ); + + /* Locate the table which we want to update. + */ + pTab = sqlite3SrcListLookup(pParse, pTabList); + if( pTab==0 ) goto update_cleanup; + iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + + /* Figure out if we have any triggers and if the table being + ** updated is a view. + */ +#ifndef SQLITE_OMIT_TRIGGER + pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, &tmask); + isView = IsView(pTab); + assert( pTrigger || tmask==0 ); +#else +# define pTrigger 0 +# define isView 0 +# define tmask 0 +#endif +#ifdef SQLITE_OMIT_VIEW +# undef isView +# define isView 0 +#endif + +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Update() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewUpdate(pParse->pWith, pTabList, pChanges, pWhere, + onError, pOrderBy, pLimit, pUpsert, pTrigger); + } +#endif + + /* If there was a FROM clause, set nChangeFrom to the number of expressions + ** in the change-list. Otherwise, set it to 0. There cannot be a FROM + ** clause if this function is being called to generate code for part of + ** an UPSERT statement. */ + nChangeFrom = (pTabList->nSrc>1) ? pChanges->nExpr : 0; + assert( nChangeFrom==0 || pUpsert==0 ); + +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + if( !isView && nChangeFrom==0 ){ + pWhere = sqlite3LimitWhere( + pParse, pTabList, pWhere, pOrderBy, pLimit, "UPDATE" + ); + pOrderBy = 0; + pLimit = 0; + } +#endif + + if( sqlite3ViewGetColumnNames(pParse, pTab) ){ + goto update_cleanup; + } + if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){ + goto update_cleanup; + } + + /* Allocate a cursors for the main database table and for all indices. + ** The index cursors might not be used, but if they are used they + ** need to occur right after the database cursor. So go ahead and + ** allocate enough space, just in case. + */ + iBaseCur = iDataCur = pParse->nTab++; + iIdxCur = iDataCur+1; + pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); + testcase( pPk!=0 && pPk!=pTab->pIndex ); + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ + if( pPk==pIdx ){ + iDataCur = pParse->nTab; + } + pParse->nTab++; + } + if( pUpsert ){ + /* On an UPSERT, reuse the same cursors already opened by INSERT */ + iDataCur = pUpsert->iDataCur; + iIdxCur = pUpsert->iIdxCur; + pParse->nTab = iBaseCur; + } + pTabList->a[0].iCursor = iDataCur; + + /* Allocate space for aXRef[], aRegIdx[], and aToOpen[]. + ** Initialize aXRef[] and aToOpen[] to their default values. + */ + aXRef = sqlite3DbMallocRawNN(db, sizeof(int) * (pTab->nCol+nIdx+1) + nIdx+2 ); + if( aXRef==0 ) goto update_cleanup; + aRegIdx = aXRef+pTab->nCol; + aToOpen = (u8*)(aRegIdx+nIdx+1); + memset(aToOpen, 1, nIdx+1); + aToOpen[nIdx+1] = 0; + for(i=0; i<pTab->nCol; i++) aXRef[i] = -1; + + /* Initialize the name-context */ + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = pParse; + sNC.pSrcList = pTabList; + sNC.uNC.pUpsert = pUpsert; + sNC.ncFlags = NC_UUpsert; + + /* Begin generating code. */ + v = sqlite3GetVdbe(pParse); + if( v==0 ) goto update_cleanup; + + /* Resolve the column names in all the expressions of the + ** of the UPDATE statement. Also find the column index + ** for each column to be updated in the pChanges array. For each + ** column to be updated, make sure we have authorization to change + ** that column. + */ + chngRowid = chngPk = 0; + for(i=0; i<pChanges->nExpr; i++){ + u8 hCol = sqlite3StrIHash(pChanges->a[i].zEName); + /* If this is an UPDATE with a FROM clause, do not resolve expressions + ** here. The call to sqlite3Select() below will do that. */ + if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){ + goto update_cleanup; + } + for(j=0; j<pTab->nCol; j++){ + if( pTab->aCol[j].hName==hCol + && sqlite3StrICmp(pTab->aCol[j].zCnName, pChanges->a[i].zEName)==0 + ){ + if( j==pTab->iPKey ){ + chngRowid = 1; + pRowidExpr = pChanges->a[i].pExpr; + iRowidExpr = i; + }else if( pPk && (pTab->aCol[j].colFlags & COLFLAG_PRIMKEY)!=0 ){ + chngPk = 1; + } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + else if( pTab->aCol[j].colFlags & COLFLAG_GENERATED ){ + testcase( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ); + testcase( pTab->aCol[j].colFlags & COLFLAG_STORED ); + sqlite3ErrorMsg(pParse, + "cannot UPDATE generated column \"%s\"", + pTab->aCol[j].zCnName); + goto update_cleanup; + } +#endif + aXRef[j] = i; + break; + } + } + if( j>=pTab->nCol ){ + if( pPk==0 && sqlite3IsRowid(pChanges->a[i].zEName) ){ + j = -1; + chngRowid = 1; + pRowidExpr = pChanges->a[i].pExpr; + iRowidExpr = i; + }else{ + sqlite3ErrorMsg(pParse, "no such column: %s", pChanges->a[i].zEName); + pParse->checkSchema = 1; + goto update_cleanup; + } + } +#ifndef SQLITE_OMIT_AUTHORIZATION + { + int rc; + rc = sqlite3AuthCheck(pParse, SQLITE_UPDATE, pTab->zName, + j<0 ? "ROWID" : pTab->aCol[j].zCnName, + db->aDb[iDb].zDbSName); + if( rc==SQLITE_DENY ){ + goto update_cleanup; + }else if( rc==SQLITE_IGNORE ){ + aXRef[j] = -1; + } + } +#endif + } + assert( (chngRowid & chngPk)==0 ); + assert( chngRowid==0 || chngRowid==1 ); + assert( chngPk==0 || chngPk==1 ); + chngKey = chngRowid + chngPk; + +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + /* Mark generated columns as changing if their generator expressions + ** reference any changing column. The actual aXRef[] value for + ** generated expressions is not used, other than to check to see that it + ** is non-negative, so the value of aXRef[] for generated columns can be + ** set to any non-negative number. We use 99999 so that the value is + ** obvious when looking at aXRef[] in a symbolic debugger. + */ + if( pTab->tabFlags & TF_HasGenerated ){ + int bProgress; + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + do{ + bProgress = 0; + for(i=0; i<pTab->nCol; i++){ + if( aXRef[i]>=0 ) continue; + if( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 ) continue; + if( sqlite3ExprReferencesUpdatedColumn( + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + aXRef, chngRowid) + ){ + aXRef[i] = 99999; + bProgress = 1; + } + } + }while( bProgress ); + } +#endif + + /* The SET expressions are not actually used inside the WHERE loop. + ** So reset the colUsed mask. Unless this is a virtual table. In that + ** case, set all bits of the colUsed mask (to ensure that the virtual + ** table implementation makes all columns available). + */ + pTabList->a[0].colUsed = IsVirtual(pTab) ? ALLBITS : 0; + + hasFK = sqlite3FkRequired(pParse, pTab, aXRef, chngKey); + + /* There is one entry in the aRegIdx[] array for each index on the table + ** being updated. Fill in aRegIdx[] with a register number that will hold + ** the key for accessing each index. + */ + if( onError==OE_Replace ) bReplace = 1; + for(nAllIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nAllIdx++){ + int reg; + if( chngKey || hasFK>1 || pIdx==pPk + || indexWhereClauseMightChange(pIdx,aXRef,chngRowid) + ){ + reg = ++pParse->nMem; + pParse->nMem += pIdx->nColumn; + }else{ + reg = 0; + for(i=0; i<pIdx->nKeyCol; i++){ + if( indexColumnIsBeingUpdated(pIdx, i, aXRef, chngRowid) ){ + reg = ++pParse->nMem; + pParse->nMem += pIdx->nColumn; + if( onError==OE_Default && pIdx->onError==OE_Replace ){ + bReplace = 1; + } + break; + } + } + } + if( reg==0 ) aToOpen[nAllIdx+1] = 0; + aRegIdx[nAllIdx] = reg; + } + aRegIdx[nAllIdx] = ++pParse->nMem; /* Register storing the table record */ + if( bReplace ){ + /* If REPLACE conflict resolution might be invoked, open cursors on all + ** indexes in case they are needed to delete records. */ + memset(aToOpen, 1, nIdx+1); + } + + if( pParse->nested==0 ) sqlite3VdbeCountChanges(v); + sqlite3BeginWriteOperation(pParse, pTrigger || hasFK, iDb); + + /* Allocate required registers. */ + if( !IsVirtual(pTab) ){ + /* For now, regRowSet and aRegIdx[nAllIdx] share the same register. + ** If regRowSet turns out to be needed, then aRegIdx[nAllIdx] will be + ** reallocated. aRegIdx[nAllIdx] is the register in which the main + ** table record is written. regRowSet holds the RowSet for the + ** two-pass update algorithm. */ + assert( aRegIdx[nAllIdx]==pParse->nMem ); + regRowSet = aRegIdx[nAllIdx]; + regOldRowid = regNewRowid = ++pParse->nMem; + if( chngPk || pTrigger || hasFK ){ + regOld = pParse->nMem + 1; + pParse->nMem += pTab->nCol; + } + if( chngKey || pTrigger || hasFK ){ + regNewRowid = ++pParse->nMem; + } + regNew = pParse->nMem + 1; + pParse->nMem += pTab->nCol; + } + + /* Start the view context. */ + if( isView ){ + sqlite3AuthContextPush(pParse, &sContext, pTab->zName); + } + + /* If we are trying to update a view, realize that view into + ** an ephemeral table. + */ +#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) + if( nChangeFrom==0 && isView ){ + sqlite3MaterializeView(pParse, pTab, + pWhere, pOrderBy, pLimit, iDataCur + ); + pOrderBy = 0; + pLimit = 0; + } +#endif + + /* Resolve the column names in all the expressions in the + ** WHERE clause. + */ + if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pWhere) ){ + goto update_cleanup; + } + +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* Virtual tables must be handled separately */ + if( IsVirtual(pTab) ){ + updateVirtualTable(pParse, pTabList, pTab, pChanges, pRowidExpr, aXRef, + pWhere, onError); + goto update_cleanup; + } +#endif + + /* Jump to labelBreak to abandon further processing of this UPDATE */ + labelContinue = labelBreak = sqlite3VdbeMakeLabel(pParse); + + /* Not an UPSERT. Normal processing. Begin by + ** initialize the count of updated rows */ + if( (db->flags&SQLITE_CountRows)!=0 + && !pParse->pTriggerTab + && !pParse->nested + && !pParse->bReturning + && pUpsert==0 + ){ + regRowCount = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount); + } + + if( nChangeFrom==0 && HasRowid(pTab) ){ + sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid); + iEph = pParse->nTab++; + addrOpen = sqlite3VdbeAddOp3(v, OP_OpenEphemeral, iEph, 0, regRowSet); + }else{ + assert( pPk!=0 || HasRowid(pTab) ); + nPk = pPk ? pPk->nKeyCol : 0; + iPk = pParse->nMem+1; + pParse->nMem += nPk; + pParse->nMem += nChangeFrom; + regKey = ++pParse->nMem; + if( pUpsert==0 ){ + int nEphCol = nPk + nChangeFrom + (isView ? pTab->nCol : 0); + iEph = pParse->nTab++; + if( pPk ) sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1); + addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nEphCol); + if( pPk ){ + KeyInfo *pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pPk); + if( pKeyInfo ){ + pKeyInfo->nAllField = nEphCol; + sqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO); + } + } + if( nChangeFrom ){ + updateFromSelect( + pParse, iEph, pPk, pChanges, pTabList, pWhere, pOrderBy, pLimit + ); +#ifndef SQLITE_OMIT_SUBQUERY + if( isView ) iDataCur = iEph; +#endif + } + } + } + + if( nChangeFrom ){ + sqlite3MultiWrite(pParse); + eOnePass = ONEPASS_OFF; + nKey = nPk; + regKey = iPk; + }else{ + if( pUpsert ){ + /* If this is an UPSERT, then all cursors have already been opened by + ** the outer INSERT and the data cursor should be pointing at the row + ** that is to be updated. So bypass the code that searches for the + ** row(s) to be updated. + */ + pWInfo = 0; + eOnePass = ONEPASS_SINGLE; + sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL); + bFinishSeek = 0; + }else{ + /* Begin the database scan. + ** + ** Do not consider a single-pass strategy for a multi-row update if + ** there is anything that might disrupt the cursor being used to do + ** the UPDATE: + ** (1) This is a nested UPDATE + ** (2) There are triggers + ** (3) There are FOREIGN KEY constraints + ** (4) There are REPLACE conflict handlers + ** (5) There are subqueries in the WHERE clause + */ + flags = WHERE_ONEPASS_DESIRED; + if( !pParse->nested + && !pTrigger + && !hasFK + && !chngKey + && !bReplace + && (pWhere==0 || !ExprHasProperty(pWhere, EP_Subquery)) + ){ + flags |= WHERE_ONEPASS_MULTIROW; + } + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere,0,0,0,flags,iIdxCur); + if( pWInfo==0 ) goto update_cleanup; + + /* A one-pass strategy that might update more than one row may not + ** be used if any column of the index used for the scan is being + ** updated. Otherwise, if there is an index on "b", statements like + ** the following could create an infinite loop: + ** + ** UPDATE t1 SET b=b+1 WHERE b>? + ** + ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI + ** strategy that uses an index for which one or more columns are being + ** updated. */ + eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); + bFinishSeek = sqlite3WhereUsesDeferredSeek(pWInfo); + if( eOnePass!=ONEPASS_SINGLE ){ + sqlite3MultiWrite(pParse); + if( eOnePass==ONEPASS_MULTI ){ + int iCur = aiCurOnePass[1]; + if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){ + eOnePass = ONEPASS_OFF; + } + assert( iCur!=iDataCur || !HasRowid(pTab) ); + } + } + } + + if( HasRowid(pTab) ){ + /* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF + ** mode, write the rowid into the FIFO. In either of the one-pass modes, + ** leave it in register regOldRowid. */ + sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid); + if( eOnePass==ONEPASS_OFF ){ + aRegIdx[nAllIdx] = ++pParse->nMem; + sqlite3VdbeAddOp3(v, OP_Insert, iEph, regRowSet, regOldRowid); + }else{ + if( ALWAYS(addrOpen) ) sqlite3VdbeChangeToNoop(v, addrOpen); + } + }else{ + /* Read the PK of the current row into an array of registers. In + ** ONEPASS_OFF mode, serialize the array into a record and store it in + ** the ephemeral table. Or, in ONEPASS_SINGLE or MULTI mode, change + ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table + ** is not required) and leave the PK fields in the array of registers. */ + for(i=0; i<nPk; i++){ + assert( pPk->aiColumn[i]>=0 ); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, + pPk->aiColumn[i], iPk+i); + } + if( eOnePass ){ + if( addrOpen ) sqlite3VdbeChangeToNoop(v, addrOpen); + nKey = nPk; + regKey = iPk; + }else{ + sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey, + sqlite3IndexAffinityStr(db, pPk), nPk); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk); + } + } + } + + if( pUpsert==0 ){ + if( nChangeFrom==0 && eOnePass!=ONEPASS_MULTI ){ + sqlite3WhereEnd(pWInfo); + } + + if( !isView ){ + int addrOnce = 0; + int iNotUsed1 = 0; + int iNotUsed2 = 0; + + /* Open every index that needs updating. */ + if( eOnePass!=ONEPASS_OFF ){ + if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0; + if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0; + } + + if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){ + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + } + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, + aToOpen, &iNotUsed1, &iNotUsed2); + if( addrOnce ){ + sqlite3VdbeJumpHereOrPopInst(v, addrOnce); + } + } + + /* Top of the update loop */ + if( eOnePass!=ONEPASS_OFF ){ + if( aiCurOnePass[0]!=iDataCur + && aiCurOnePass[1]!=iDataCur +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + && !isView +#endif + ){ + assert( pPk ); + sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey,nKey); + VdbeCoverage(v); + } + if( eOnePass!=ONEPASS_SINGLE ){ + labelContinue = sqlite3VdbeMakeLabel(pParse); + } + sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak); + VdbeCoverageIf(v, pPk==0); + VdbeCoverageIf(v, pPk!=0); + }else if( pPk || nChangeFrom ){ + labelContinue = sqlite3VdbeMakeLabel(pParse); + sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v); + addrTop = sqlite3VdbeCurrentAddr(v); + if( nChangeFrom ){ + if( !isView ){ + if( pPk ){ + for(i=0; i<nPk; i++){ + sqlite3VdbeAddOp3(v, OP_Column, iEph, i, iPk+i); + } + sqlite3VdbeAddOp4Int( + v, OP_NotFound, iDataCur, labelContinue, iPk, nPk + ); VdbeCoverage(v); + }else{ + sqlite3VdbeAddOp2(v, OP_Rowid, iEph, regOldRowid); + sqlite3VdbeAddOp3( + v, OP_NotExists, iDataCur, labelContinue, regOldRowid + ); VdbeCoverage(v); + } + } + }else{ + sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey); + sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey,0); + VdbeCoverage(v); + } + }else{ + sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v); + labelContinue = sqlite3VdbeMakeLabel(pParse); + addrTop = sqlite3VdbeAddOp2(v, OP_Rowid, iEph, regOldRowid); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid); + VdbeCoverage(v); + } + } + + /* If the rowid value will change, set register regNewRowid to + ** contain the new value. If the rowid is not being modified, + ** then regNewRowid is the same register as regOldRowid, which is + ** already populated. */ + assert( chngKey || pTrigger || hasFK || regOldRowid==regNewRowid ); + if( chngRowid ){ + assert( iRowidExpr>=0 ); + if( nChangeFrom==0 ){ + sqlite3ExprCode(pParse, pRowidExpr, regNewRowid); + }else{ + sqlite3VdbeAddOp3(v, OP_Column, iEph, iRowidExpr, regNewRowid); + } + sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid); VdbeCoverage(v); + } + + /* Compute the old pre-UPDATE content of the row being changed, if that + ** information is needed */ + if( chngPk || hasFK || pTrigger ){ + u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0); + oldmask |= sqlite3TriggerColmask(pParse, + pTrigger, pChanges, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onError + ); + for(i=0; i<pTab->nCol; i++){ + u32 colFlags = pTab->aCol[i].colFlags; + k = sqlite3TableColumnToStorage(pTab, i) + regOld; + if( oldmask==0xffffffff + || (i<32 && (oldmask & MASKBIT32(i))!=0) + || (colFlags & COLFLAG_PRIMKEY)!=0 + ){ + testcase( oldmask!=0xffffffff && i==31 ); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, k); + } + } + if( chngRowid==0 && pPk==0 ){ + sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid); + } + } + + /* Populate the array of registers beginning at regNew with the new + ** row data. This array is used to check constants, create the new + ** table and index records, and as the values for any new.* references + ** made by triggers. + ** + ** If there are one or more BEFORE triggers, then do not populate the + ** registers associated with columns that are (a) not modified by + ** this UPDATE statement and (b) not accessed by new.* references. The + ** values for registers not modified by the UPDATE must be reloaded from + ** the database after the BEFORE triggers are fired anyway (as the trigger + ** may have modified them). So not loading those that are not going to + ** be used eliminates some redundant opcodes. + */ + newmask = sqlite3TriggerColmask( + pParse, pTrigger, pChanges, 1, TRIGGER_BEFORE, pTab, onError + ); + for(i=0, k=regNew; i<pTab->nCol; i++, k++){ + if( i==pTab->iPKey ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, k); + }else if( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)!=0 ){ + if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) k--; + }else{ + j = aXRef[i]; + if( j>=0 ){ + if( nChangeFrom ){ + int nOff = (isView ? pTab->nCol : nPk); + assert( eOnePass==ONEPASS_OFF ); + sqlite3VdbeAddOp3(v, OP_Column, iEph, nOff+j, k); + }else{ + sqlite3ExprCode(pParse, pChanges->a[j].pExpr, k); + } + }else if( 0==(tmask&TRIGGER_BEFORE) || i>31 || (newmask & MASKBIT32(i)) ){ + /* This branch loads the value of a column that will not be changed + ** into a register. This is done if there are no BEFORE triggers, or + ** if there are one or more BEFORE triggers that use this value via + ** a new.* reference in a trigger program. + */ + testcase( i==31 ); + testcase( i==32 ); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k); + bFinishSeek = 0; + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, k); + } + } + } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( pTab->tabFlags & TF_HasGenerated ){ + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + sqlite3ComputeGeneratedColumns(pParse, regNew, pTab); + } +#endif + + /* Fire any BEFORE UPDATE triggers. This happens before constraints are + ** verified. One could argue that this is wrong. + */ + if( tmask&TRIGGER_BEFORE ){ + sqlite3TableAffinity(v, pTab, regNew); + sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + TRIGGER_BEFORE, pTab, regOldRowid, onError, labelContinue); + + if( !isView ){ + /* The row-trigger may have deleted the row being updated. In this + ** case, jump to the next row. No updates or AFTER triggers are + ** required. This behavior - what happens when the row being updated + ** is deleted or renamed by a BEFORE trigger - is left undefined in the + ** documentation. + */ + if( pPk ){ + sqlite3VdbeAddOp4Int(v, OP_NotFound,iDataCur,labelContinue,regKey,nKey); + VdbeCoverage(v); + }else{ + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid); + VdbeCoverage(v); + } + + /* After-BEFORE-trigger-reload-loop: + ** If it did not delete it, the BEFORE trigger may still have modified + ** some of the columns of the row being updated. Load the values for + ** all columns not modified by the update statement into their registers + ** in case this has happened. Only unmodified columns are reloaded. + ** The values computed for modified columns use the values before the + ** BEFORE trigger runs. See test case trigger1-18.0 (added 2018-04-26) + ** for an example. + */ + for(i=0, k=regNew; i<pTab->nCol; i++, k++){ + if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){ + if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) k--; + }else if( aXRef[i]<0 && i!=pTab->iPKey ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k); + } + } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( pTab->tabFlags & TF_HasGenerated ){ + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + sqlite3ComputeGeneratedColumns(pParse, regNew, pTab); + } +#endif + } + } + + if( !isView ){ + /* Do constraint checks. */ + assert( regOldRowid>0 ); + sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, + regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace, + aXRef, 0); + + /* If REPLACE conflict handling may have been used, or if the PK of the + ** row is changing, then the GenerateConstraintChecks() above may have + ** moved cursor iDataCur. Reseek it. */ + if( bReplace || chngKey ){ + if( pPk ){ + sqlite3VdbeAddOp4Int(v, OP_NotFound,iDataCur,labelContinue,regKey,nKey); + }else{ + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid); + } + VdbeCoverage(v); + } + + /* Do FK constraint checks. */ + if( hasFK ){ + sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngKey); + } + + /* Delete the index entries associated with the current record. */ + sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx, -1); + + /* We must run the OP_FinishSeek opcode to resolve a prior + ** OP_DeferredSeek if there is any possibility that there have been + ** no OP_Column opcodes since the OP_DeferredSeek was issued. But + ** we want to avoid the OP_FinishSeek if possible, as running it + ** costs CPU cycles. */ + if( bFinishSeek ){ + sqlite3VdbeAddOp1(v, OP_FinishSeek, iDataCur); + } + + /* If changing the rowid value, or if there are foreign key constraints + ** to process, delete the old record. Otherwise, add a noop OP_Delete + ** to invoke the pre-update hook. + ** + ** That (regNew==regnewRowid+1) is true is also important for the + ** pre-update hook. If the caller invokes preupdate_new(), the returned + ** value is copied from memory cell (regNewRowid+1+iCol), where iCol + ** is the column index supplied by the user. + */ + assert( regNew==regNewRowid+1 ); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + sqlite3VdbeAddOp3(v, OP_Delete, iDataCur, + OPFLAG_ISUPDATE | ((hasFK>1 || chngKey) ? 0 : OPFLAG_ISNOOP), + regNewRowid + ); + if( eOnePass==ONEPASS_MULTI ){ + assert( hasFK==0 && chngKey==0 ); + sqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION); + } + if( !pParse->nested ){ + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + } +#else + if( hasFK>1 || chngKey ){ + sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0); + } +#endif + + if( hasFK ){ + sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngKey); + } + + /* Insert the new index entries and the new record. */ + sqlite3CompleteInsertion( + pParse, pTab, iDataCur, iIdxCur, regNewRowid, aRegIdx, + OPFLAG_ISUPDATE | (eOnePass==ONEPASS_MULTI ? OPFLAG_SAVEPOSITION : 0), + 0, 0 + ); + + /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to + ** handle rows (possibly in other tables) that refer via a foreign key + ** to the row just updated. */ + if( hasFK ){ + sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngKey); + } + } + + /* Increment the row counter + */ + if( regRowCount ){ + sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); + } + + if( pTrigger ){ + sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue); + } + + /* Repeat the above with the next record to be updated, until + ** all record selected by the WHERE clause have been updated. + */ + if( eOnePass==ONEPASS_SINGLE ){ + /* Nothing to do at end-of-loop for a single-pass */ + }else if( eOnePass==ONEPASS_MULTI ){ + sqlite3VdbeResolveLabel(v, labelContinue); + sqlite3WhereEnd(pWInfo); + }else{ + sqlite3VdbeResolveLabel(v, labelContinue); + sqlite3VdbeAddOp2(v, OP_Next, iEph, addrTop); VdbeCoverage(v); + } + sqlite3VdbeResolveLabel(v, labelBreak); + + /* Update the sqlite_sequence table by storing the content of the + ** maximum rowid counter values recorded while inserting into + ** autoincrement tables. + */ + if( pParse->nested==0 && pParse->pTriggerTab==0 && pUpsert==0 ){ + sqlite3AutoincrementEnd(pParse); + } + + /* + ** Return the number of rows that were changed, if we are tracking + ** that information. + */ + if( regRowCount ){ + sqlite3CodeChangeCount(v, regRowCount, "rows updated"); + } + +update_cleanup: + sqlite3AuthContextPop(&sContext); + sqlite3DbFree(db, aXRef); /* Also frees aRegIdx[] and aToOpen[] */ + sqlite3SrcListDelete(db, pTabList); + sqlite3ExprListDelete(db, pChanges); + sqlite3ExprDelete(db, pWhere); +#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) + sqlite3ExprListDelete(db, pOrderBy); + sqlite3ExprDelete(db, pLimit); +#endif + return; +} +/* Make sure "isView" and other macros defined above are undefined. Otherwise +** they may interfere with compilation of other functions in this file +** (or in another file, if this file becomes part of the amalgamation). */ +#ifdef isView + #undef isView +#endif +#ifdef pTrigger + #undef pTrigger +#endif + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Generate code for an UPDATE of a virtual table. +** +** There are two possible strategies - the default and the special +** "onepass" strategy. Onepass is only used if the virtual table +** implementation indicates that pWhere may match at most one row. +** +** The default strategy is to create an ephemeral table that contains +** for each row to be changed: +** +** (A) The original rowid of that row. +** (B) The revised rowid for the row. +** (C) The content of every column in the row. +** +** Then loop through the contents of this ephemeral table executing a +** VUpdate for each row. When finished, drop the ephemeral table. +** +** The "onepass" strategy does not use an ephemeral table. Instead, it +** stores the same values (A, B and C above) in a register array and +** makes a single invocation of VUpdate. +*/ +static void updateVirtualTable( + Parse *pParse, /* The parsing context */ + SrcList *pSrc, /* The virtual table to be modified */ + Table *pTab, /* The virtual table */ + ExprList *pChanges, /* The columns to change in the UPDATE statement */ + Expr *pRowid, /* Expression used to recompute the rowid */ + int *aXRef, /* Mapping from columns of pTab to entries in pChanges */ + Expr *pWhere, /* WHERE clause of the UPDATE statement */ + int onError /* ON CONFLICT strategy */ +){ + Vdbe *v = pParse->pVdbe; /* Virtual machine under construction */ + int ephemTab; /* Table holding the result of the SELECT */ + int i; /* Loop counter */ + sqlite3 *db = pParse->db; /* Database connection */ + const char *pVTab = (const char*)sqlite3GetVTable(db, pTab); + WhereInfo *pWInfo = 0; + int nArg = 2 + pTab->nCol; /* Number of arguments to VUpdate */ + int regArg; /* First register in VUpdate arg array */ + int regRec; /* Register in which to assemble record */ + int regRowid; /* Register for ephemeral table rowid */ + int iCsr = pSrc->a[0].iCursor; /* Cursor used for virtual table scan */ + int aDummy[2]; /* Unused arg for sqlite3WhereOkOnePass() */ + int eOnePass; /* True to use onepass strategy */ + int addr; /* Address of OP_OpenEphemeral */ + + /* Allocate nArg registers in which to gather the arguments for VUpdate. Then + ** create and open the ephemeral table in which the records created from + ** these arguments will be temporarily stored. */ + assert( v ); + ephemTab = pParse->nTab++; + addr= sqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, nArg); + regArg = pParse->nMem + 1; + pParse->nMem += nArg; + if( pSrc->nSrc>1 ){ + Index *pPk = 0; + Expr *pRow; + ExprList *pList; + if( HasRowid(pTab) ){ + if( pRowid ){ + pRow = sqlite3ExprDup(db, pRowid, 0); + }else{ + pRow = sqlite3PExpr(pParse, TK_ROW, 0, 0); + } + }else{ + i16 iPk; /* PRIMARY KEY column */ + pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->nKeyCol==1 ); + iPk = pPk->aiColumn[0]; + if( aXRef[iPk]>=0 ){ + pRow = sqlite3ExprDup(db, pChanges->a[aXRef[iPk]].pExpr, 0); + }else{ + pRow = exprRowColumn(pParse, iPk); + } + } + pList = sqlite3ExprListAppend(pParse, 0, pRow); + + for(i=0; i<pTab->nCol; i++){ + if( aXRef[i]>=0 ){ + pList = sqlite3ExprListAppend(pParse, pList, + sqlite3ExprDup(db, pChanges->a[aXRef[i]].pExpr, 0) + ); + }else{ + Expr *pRowExpr = exprRowColumn(pParse, i); + if( pRowExpr ) pRowExpr->op2 = OPFLAG_NOCHNG; + pList = sqlite3ExprListAppend(pParse, pList, pRowExpr); + } + } + + updateFromSelect(pParse, ephemTab, pPk, pList, pSrc, pWhere, 0, 0); + sqlite3ExprListDelete(db, pList); + eOnePass = ONEPASS_OFF; + }else{ + regRec = ++pParse->nMem; + regRowid = ++pParse->nMem; + + /* Start scanning the virtual table */ + pWInfo = sqlite3WhereBegin( + pParse, pSrc, pWhere, 0, 0, 0, WHERE_ONEPASS_DESIRED, 0 + ); + if( pWInfo==0 ) return; + + /* Populate the argument registers. */ + for(i=0; i<pTab->nCol; i++){ + assert( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 ); + if( aXRef[i]>=0 ){ + sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i); + }else{ + sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i); + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG);/* For sqlite3_vtab_nochange() */ + } + } + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg); + if( pRowid ){ + sqlite3ExprCode(pParse, pRowid, regArg+1); + }else{ + sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1); + } + }else{ + Index *pPk; /* PRIMARY KEY index */ + i16 iPk; /* PRIMARY KEY column */ + pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->nKeyCol==1 ); + iPk = pPk->aiColumn[0]; + sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, iPk, regArg); + sqlite3VdbeAddOp2(v, OP_SCopy, regArg+2+iPk, regArg+1); + } + + eOnePass = sqlite3WhereOkOnePass(pWInfo, aDummy); + + /* There is no ONEPASS_MULTI on virtual tables */ + assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE ); + + if( eOnePass ){ + /* If using the onepass strategy, no-op out the OP_OpenEphemeral coded + ** above. */ + sqlite3VdbeChangeToNoop(v, addr); + sqlite3VdbeAddOp1(v, OP_Close, iCsr); + }else{ + /* Create a record from the argument register contents and insert it into + ** the ephemeral table. */ + sqlite3MultiWrite(pParse); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec); +#if defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_NULL_TRIM) + /* Signal an assert() within OP_MakeRecord that it is allowed to + ** accept no-change records with serial_type 10 */ + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); +#endif + sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid); + } + } + + + if( eOnePass==ONEPASS_OFF ){ + /* End the virtual table scan */ + if( pSrc->nSrc==1 ){ + sqlite3WhereEnd(pWInfo); + } + + /* Begin scanning through the ephemeral table. */ + addr = sqlite3VdbeAddOp1(v, OP_Rewind, ephemTab); VdbeCoverage(v); + + /* Extract arguments from the current row of the ephemeral table and + ** invoke the VUpdate method. */ + for(i=0; i<nArg; i++){ + sqlite3VdbeAddOp3(v, OP_Column, ephemTab, i, regArg+i); + } + } + sqlite3VtabMakeWritable(pParse, pTab); + sqlite3VdbeAddOp4(v, OP_VUpdate, 0, nArg, regArg, pVTab, P4_VTAB); + sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError); + sqlite3MayAbort(pParse); + + /* End of the ephemeral table scan. Or, if using the onepass strategy, + ** jump to here if the scan visited zero rows. */ + if( eOnePass==ONEPASS_OFF ){ + sqlite3VdbeAddOp2(v, OP_Next, ephemTab, addr+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addr); + sqlite3VdbeAddOp2(v, OP_Close, ephemTab, 0); + }else{ + sqlite3WhereEnd(pWInfo); + } +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +/************** End of update.c **********************************************/ +/************** Begin file upsert.c ******************************************/ +/* +** 2018-04-12 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement various aspects of UPSERT +** processing and handling of the Upsert object. +*/ +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_OMIT_UPSERT +/* +** Free a list of Upsert objects +*/ +static void SQLITE_NOINLINE upsertDelete(sqlite3 *db, Upsert *p){ + do{ + Upsert *pNext = p->pNextUpsert; + sqlite3ExprListDelete(db, p->pUpsertTarget); + sqlite3ExprDelete(db, p->pUpsertTargetWhere); + sqlite3ExprListDelete(db, p->pUpsertSet); + sqlite3ExprDelete(db, p->pUpsertWhere); + sqlite3DbFree(db, p->pToFree); + sqlite3DbFree(db, p); + p = pNext; + }while( p ); +} +SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){ + if( p ) upsertDelete(db, p); +} + + +/* +** Duplicate an Upsert object. +*/ +SQLITE_PRIVATE Upsert *sqlite3UpsertDup(sqlite3 *db, Upsert *p){ + if( p==0 ) return 0; + return sqlite3UpsertNew(db, + sqlite3ExprListDup(db, p->pUpsertTarget, 0), + sqlite3ExprDup(db, p->pUpsertTargetWhere, 0), + sqlite3ExprListDup(db, p->pUpsertSet, 0), + sqlite3ExprDup(db, p->pUpsertWhere, 0), + sqlite3UpsertDup(db, p->pNextUpsert) + ); +} + +/* +** Create a new Upsert object. +*/ +SQLITE_PRIVATE Upsert *sqlite3UpsertNew( + sqlite3 *db, /* Determines which memory allocator to use */ + ExprList *pTarget, /* Target argument to ON CONFLICT, or NULL */ + Expr *pTargetWhere, /* Optional WHERE clause on the target */ + ExprList *pSet, /* UPDATE columns, or NULL for a DO NOTHING */ + Expr *pWhere, /* WHERE clause for the ON CONFLICT UPDATE */ + Upsert *pNext /* Next ON CONFLICT clause in the list */ +){ + Upsert *pNew; + pNew = sqlite3DbMallocZero(db, sizeof(Upsert)); + if( pNew==0 ){ + sqlite3ExprListDelete(db, pTarget); + sqlite3ExprDelete(db, pTargetWhere); + sqlite3ExprListDelete(db, pSet); + sqlite3ExprDelete(db, pWhere); + sqlite3UpsertDelete(db, pNext); + return 0; + }else{ + pNew->pUpsertTarget = pTarget; + pNew->pUpsertTargetWhere = pTargetWhere; + pNew->pUpsertSet = pSet; + pNew->pUpsertWhere = pWhere; + pNew->isDoUpdate = pSet!=0; + pNew->pNextUpsert = pNext; + } + return pNew; +} + +/* +** Analyze the ON CONFLICT clause described by pUpsert. Resolve all +** symbols in the conflict-target. +** +** Return SQLITE_OK if everything works, or an error code is something +** is wrong. +*/ +SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget( + Parse *pParse, /* The parsing context */ + SrcList *pTabList, /* Table into which we are inserting */ + Upsert *pUpsert /* The ON CONFLICT clauses */ +){ + Table *pTab; /* That table into which we are inserting */ + int rc; /* Result code */ + int iCursor; /* Cursor used by pTab */ + Index *pIdx; /* One of the indexes of pTab */ + ExprList *pTarget; /* The conflict-target clause */ + Expr *pTerm; /* One term of the conflict-target clause */ + NameContext sNC; /* Context for resolving symbolic names */ + Expr sCol[2]; /* Index column converted into an Expr */ + int nClause = 0; /* Counter of ON CONFLICT clauses */ + + assert( pTabList->nSrc==1 ); + assert( pTabList->a[0].pTab!=0 ); + assert( pUpsert!=0 ); + assert( pUpsert->pUpsertTarget!=0 ); + + /* Resolve all symbolic names in the conflict-target clause, which + ** includes both the list of columns and the optional partial-index + ** WHERE clause. + */ + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = pParse; + sNC.pSrcList = pTabList; + for(; pUpsert && pUpsert->pUpsertTarget; + pUpsert=pUpsert->pNextUpsert, nClause++){ + rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); + if( rc ) return rc; + rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); + if( rc ) return rc; + + /* Check to see if the conflict target matches the rowid. */ + pTab = pTabList->a[0].pTab; + pTarget = pUpsert->pUpsertTarget; + iCursor = pTabList->a[0].iCursor; + if( HasRowid(pTab) + && pTarget->nExpr==1 + && (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN + && pTerm->iColumn==XN_ROWID + ){ + /* The conflict-target is the rowid of the primary table */ + assert( pUpsert->pUpsertIdx==0 ); + continue; + } + + /* Initialize sCol[0..1] to be an expression parse tree for a + ** single column of an index. The sCol[0] node will be the TK_COLLATE + ** operator and sCol[1] will be the TK_COLUMN operator. Code below + ** will populate the specific collation and column number values + ** prior to comparing against the conflict-target expression. + */ + memset(sCol, 0, sizeof(sCol)); + sCol[0].op = TK_COLLATE; + sCol[0].pLeft = &sCol[1]; + sCol[1].op = TK_COLUMN; + sCol[1].iTable = pTabList->a[0].iCursor; + + /* Check for matches against other indexes */ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int ii, jj, nn; + if( !IsUniqueIndex(pIdx) ) continue; + if( pTarget->nExpr!=pIdx->nKeyCol ) continue; + if( pIdx->pPartIdxWhere ){ + if( pUpsert->pUpsertTargetWhere==0 ) continue; + if( sqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere, + pIdx->pPartIdxWhere, iCursor)!=0 ){ + continue; + } + } + nn = pIdx->nKeyCol; + for(ii=0; ii<nn; ii++){ + Expr *pExpr; + sCol[0].u.zToken = (char*)pIdx->azColl[ii]; + if( pIdx->aiColumn[ii]==XN_EXPR ){ + assert( pIdx->aColExpr!=0 ); + assert( pIdx->aColExpr->nExpr>ii ); + assert( pIdx->bHasExpr ); + pExpr = pIdx->aColExpr->a[ii].pExpr; + if( pExpr->op!=TK_COLLATE ){ + sCol[0].pLeft = pExpr; + pExpr = &sCol[0]; + } + }else{ + sCol[0].pLeft = &sCol[1]; + sCol[1].iColumn = pIdx->aiColumn[ii]; + pExpr = &sCol[0]; + } + for(jj=0; jj<nn; jj++){ + if( sqlite3ExprCompare(0,pTarget->a[jj].pExpr,pExpr,iCursor)<2 ){ + break; /* Column ii of the index matches column jj of target */ + } + } + if( jj>=nn ){ + /* The target contains no match for column jj of the index */ + break; + } + } + if( ii<nn ){ + /* Column ii of the index did not match any term of the conflict target. + ** Continue the search with the next index. */ + continue; + } + pUpsert->pUpsertIdx = pIdx; + break; + } + if( pUpsert->pUpsertIdx==0 ){ + char zWhich[16]; + if( nClause==0 && pUpsert->pNextUpsert==0 ){ + zWhich[0] = 0; + }else{ + sqlite3_snprintf(sizeof(zWhich),zWhich,"%r ", nClause+1); + } + sqlite3ErrorMsg(pParse, "%sON CONFLICT clause does not match any " + "PRIMARY KEY or UNIQUE constraint", zWhich); + return SQLITE_ERROR; + } + } + return SQLITE_OK; +} + +/* +** Return true if pUpsert is the last ON CONFLICT clause with a +** conflict target, or if pUpsert is followed by another ON CONFLICT +** clause that targets the INTEGER PRIMARY KEY. +*/ +SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert *pUpsert){ + Upsert *pNext; + if( NEVER(pUpsert==0) ) return 0; + pNext = pUpsert->pNextUpsert; + if( pNext==0 ) return 1; + if( pNext->pUpsertTarget==0 ) return 1; + if( pNext->pUpsertIdx==0 ) return 1; + return 0; +} + +/* +** Given the list of ON CONFLICT clauses described by pUpsert, and +** a particular index pIdx, return a pointer to the particular ON CONFLICT +** clause that applies to the index. Or, if the index is not subject to +** any ON CONFLICT clause, return NULL. +*/ +SQLITE_PRIVATE Upsert *sqlite3UpsertOfIndex(Upsert *pUpsert, Index *pIdx){ + while( + pUpsert + && pUpsert->pUpsertTarget!=0 + && pUpsert->pUpsertIdx!=pIdx + ){ + pUpsert = pUpsert->pNextUpsert; + } + return pUpsert; +} + +/* +** Generate bytecode that does an UPDATE as part of an upsert. +** +** If pIdx is NULL, then the UNIQUE constraint that failed was the IPK. +** In this case parameter iCur is a cursor open on the table b-tree that +** currently points to the conflicting table row. Otherwise, if pIdx +** is not NULL, then pIdx is the constraint that failed and iCur is a +** cursor points to the conflicting row. +*/ +SQLITE_PRIVATE void sqlite3UpsertDoUpdate( + Parse *pParse, /* The parsing and code-generating context */ + Upsert *pUpsert, /* The ON CONFLICT clause for the upsert */ + Table *pTab, /* The table being updated */ + Index *pIdx, /* The UNIQUE constraint that failed */ + int iCur /* Cursor for pIdx (or pTab if pIdx==NULL) */ +){ + Vdbe *v = pParse->pVdbe; + sqlite3 *db = pParse->db; + SrcList *pSrc; /* FROM clause for the UPDATE */ + int iDataCur; + int i; + Upsert *pTop = pUpsert; + + assert( v!=0 ); + assert( pUpsert!=0 ); + iDataCur = pUpsert->iDataCur; + pUpsert = sqlite3UpsertOfIndex(pTop, pIdx); + VdbeNoopComment((v, "Begin DO UPDATE of UPSERT")); + if( pIdx && iCur!=iDataCur ){ + if( HasRowid(pTab) ){ + int regRowid = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_IdxRowid, iCur, regRowid); + sqlite3VdbeAddOp3(v, OP_SeekRowid, iDataCur, 0, regRowid); + VdbeCoverage(v); + sqlite3ReleaseTempReg(pParse, regRowid); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + int nPk = pPk->nKeyCol; + int iPk = pParse->nMem+1; + pParse->nMem += nPk; + for(i=0; i<nPk; i++){ + int k; + assert( pPk->aiColumn[i]>=0 ); + k = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[i]); + sqlite3VdbeAddOp3(v, OP_Column, iCur, k, iPk+i); + VdbeComment((v, "%s.%s", pIdx->zName, + pTab->aCol[pPk->aiColumn[i]].zCnName)); + } + sqlite3VdbeVerifyAbortable(v, OE_Abort); + i = sqlite3VdbeAddOp4Int(v, OP_Found, iDataCur, 0, iPk, nPk); + VdbeCoverage(v); + sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CORRUPT, OE_Abort, 0, + "corrupt database", P4_STATIC); + sqlite3MayAbort(pParse); + sqlite3VdbeJumpHere(v, i); + } + } + /* pUpsert does not own pTop->pUpsertSrc - the outer INSERT statement does. + ** So we have to make a copy before passing it down into sqlite3Update() */ + pSrc = sqlite3SrcListDup(db, pTop->pUpsertSrc, 0); + /* excluded.* columns of type REAL need to be converted to a hard real */ + for(i=0; i<pTab->nCol; i++){ + if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, pTop->regData+i); + } + } + sqlite3Update(pParse, pSrc, sqlite3ExprListDup(db,pUpsert->pUpsertSet,0), + sqlite3ExprDup(db,pUpsert->pUpsertWhere,0), OE_Abort, 0, 0, pUpsert); + VdbeNoopComment((v, "End DO UPDATE of UPSERT")); +} + +#endif /* SQLITE_OMIT_UPSERT */ + +/************** End of upsert.c **********************************************/ +/************** Begin file vacuum.c ******************************************/ +/* +** 2003 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the VACUUM command. +** +** Most of the code in this file may be omitted by defining the +** SQLITE_OMIT_VACUUM macro. +*/ +/* #include "sqliteInt.h" */ +/* #include "vdbeInt.h" */ + +#if !defined(SQLITE_OMIT_VACUUM) && !defined(SQLITE_OMIT_ATTACH) + +/* +** Execute zSql on database db. +** +** If zSql returns rows, then each row will have exactly one +** column. (This will only happen if zSql begins with "SELECT".) +** Take each row of result and call execSql() again recursively. +** +** The execSqlF() routine does the same thing, except it accepts +** a format string as its third argument +*/ +static int execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ + sqlite3_stmt *pStmt; + int rc; + + /* printf("SQL: [%s]\n", zSql); fflush(stdout); */ + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; + while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){ + const char *zSubSql = (const char*)sqlite3_column_text(pStmt,0); + assert( sqlite3_strnicmp(zSql,"SELECT",6)==0 ); + /* The secondary SQL must be one of CREATE TABLE, CREATE INDEX, + ** or INSERT. Historically there have been attacks that first + ** corrupt the sqlite_schema.sql field with other kinds of statements + ** then run VACUUM to get those statements to execute at inappropriate + ** times. */ + if( zSubSql + && (strncmp(zSubSql,"CRE",3)==0 || strncmp(zSubSql,"INS",3)==0) + ){ + rc = execSql(db, pzErrMsg, zSubSql); + if( rc!=SQLITE_OK ) break; + } + } + assert( rc!=SQLITE_ROW ); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + if( rc ){ + sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db)); + } + (void)sqlite3_finalize(pStmt); + return rc; +} +static int execSqlF(sqlite3 *db, char **pzErrMsg, const char *zSql, ...){ + char *z; + va_list ap; + int rc; + va_start(ap, zSql); + z = sqlite3VMPrintf(db, zSql, ap); + va_end(ap); + if( z==0 ) return SQLITE_NOMEM; + rc = execSql(db, pzErrMsg, z); + sqlite3DbFree(db, z); + return rc; +} + +/* +** The VACUUM command is used to clean up the database, +** collapse free space, etc. It is modelled after the VACUUM command +** in PostgreSQL. The VACUUM command works as follows: +** +** (1) Create a new transient database file +** (2) Copy all content from the database being vacuumed into +** the new transient database file +** (3) Copy content from the transient database back into the +** original database. +** +** The transient database requires temporary disk space approximately +** equal to the size of the original database. The copy operation of +** step (3) requires additional temporary disk space approximately equal +** to the size of the original database for the rollback journal. +** Hence, temporary disk space that is approximately 2x the size of the +** original database is required. Every page of the database is written +** approximately 3 times: Once for step (2) and twice for step (3). +** Two writes per page are required in step (3) because the original +** database content must be written into the rollback journal prior to +** overwriting the database with the vacuumed content. +** +** Only 1x temporary space and only 1x writes would be required if +** the copy of step (3) were replaced by deleting the original database +** and renaming the transient database as the original. But that will +** not work if other processes are attached to the original database. +** And a power loss in between deleting the original and renaming the +** transient would cause the database file to appear to be deleted +** following reboot. +*/ +SQLITE_PRIVATE void sqlite3Vacuum(Parse *pParse, Token *pNm, Expr *pInto){ + Vdbe *v = sqlite3GetVdbe(pParse); + int iDb = 0; + if( v==0 ) goto build_vacuum_end; + if( pParse->nErr ) goto build_vacuum_end; + if( pNm ){ +#ifndef SQLITE_BUG_COMPATIBLE_20160819 + /* Default behavior: Report an error if the argument to VACUUM is + ** not recognized */ + iDb = sqlite3TwoPartName(pParse, pNm, pNm, &pNm); + if( iDb<0 ) goto build_vacuum_end; +#else + /* When SQLITE_BUG_COMPATIBLE_20160819 is defined, unrecognized arguments + ** to VACUUM are silently ignored. This is a back-out of a bug fix that + ** occurred on 2016-08-19 (https://www.sqlite.org/src/info/083f9e6270). + ** The buggy behavior is required for binary compatibility with some + ** legacy applications. */ + iDb = sqlite3FindDb(pParse->db, pNm); + if( iDb<0 ) iDb = 0; +#endif + } + if( iDb!=1 ){ + int iIntoReg = 0; + if( pInto && sqlite3ResolveSelfReference(pParse,0,0,pInto,0)==0 ){ + iIntoReg = ++pParse->nMem; + sqlite3ExprCode(pParse, pInto, iIntoReg); + } + sqlite3VdbeAddOp2(v, OP_Vacuum, iDb, iIntoReg); + sqlite3VdbeUsesBtree(v, iDb); + } +build_vacuum_end: + sqlite3ExprDelete(pParse->db, pInto); + return; +} + +/* +** This routine implements the OP_Vacuum opcode of the VDBE. +*/ +SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( + char **pzErrMsg, /* Write error message here */ + sqlite3 *db, /* Database connection */ + int iDb, /* Which attached DB to vacuum */ + sqlite3_value *pOut /* Write results here, if not NULL. VACUUM INTO */ +){ + int rc = SQLITE_OK; /* Return code from service routines */ + Btree *pMain; /* The database being vacuumed */ + Btree *pTemp; /* The temporary database we vacuum into */ + u32 saved_mDbFlags; /* Saved value of db->mDbFlags */ + u64 saved_flags; /* Saved value of db->flags */ + i64 saved_nChange; /* Saved value of db->nChange */ + i64 saved_nTotalChange; /* Saved value of db->nTotalChange */ + u32 saved_openFlags; /* Saved value of db->openFlags */ + u8 saved_mTrace; /* Saved trace settings */ + Db *pDb = 0; /* Database to detach at end of vacuum */ + int isMemDb; /* True if vacuuming a :memory: database */ + int nRes; /* Bytes of reserved space at the end of each page */ + int nDb; /* Number of attached databases */ + const char *zDbMain; /* Schema name of database to vacuum */ + const char *zOut; /* Name of output file */ + u32 pgflags = PAGER_SYNCHRONOUS_OFF; /* sync flags for output db */ + + if( !db->autoCommit ){ + sqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction"); + return SQLITE_ERROR; /* IMP: R-12218-18073 */ + } + if( db->nVdbeActive>1 ){ + sqlite3SetString(pzErrMsg, db,"cannot VACUUM - SQL statements in progress"); + return SQLITE_ERROR; /* IMP: R-15610-35227 */ + } + saved_openFlags = db->openFlags; + if( pOut ){ + if( sqlite3_value_type(pOut)!=SQLITE_TEXT ){ + sqlite3SetString(pzErrMsg, db, "non-text filename"); + return SQLITE_ERROR; + } + zOut = (const char*)sqlite3_value_text(pOut); + db->openFlags &= ~SQLITE_OPEN_READONLY; + db->openFlags |= SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE; + }else{ + zOut = ""; + } + + /* Save the current value of the database flags so that it can be + ** restored before returning. Then set the writable-schema flag, and + ** disable CHECK and foreign key constraints. */ + saved_flags = db->flags; + saved_mDbFlags = db->mDbFlags; + saved_nChange = db->nChange; + saved_nTotalChange = db->nTotalChange; + saved_mTrace = db->mTrace; + db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks; + db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum; + db->flags &= ~(u64)(SQLITE_ForeignKeys | SQLITE_ReverseOrder + | SQLITE_Defensive | SQLITE_CountRows); + db->mTrace = 0; + + zDbMain = db->aDb[iDb].zDbSName; + pMain = db->aDb[iDb].pBt; + isMemDb = sqlite3PagerIsMemdb(sqlite3BtreePager(pMain)); + + /* Attach the temporary database as 'vacuum_db'. The synchronous pragma + ** can be set to 'off' for this file, as it is not recovered if a crash + ** occurs anyway. The integrity of the database is maintained by a + ** (possibly synchronous) transaction opened on the main database before + ** sqlite3BtreeCopyFile() is called. + ** + ** An optimization would be to use a non-journaled pager. + ** (Later:) I tried setting "PRAGMA vacuum_db.journal_mode=OFF" but + ** that actually made the VACUUM run slower. Very little journalling + ** actually occurs when doing a vacuum since the vacuum_db is initially + ** empty. Only the journal header is written. Apparently it takes more + ** time to parse and run the PRAGMA to turn journalling off than it does + ** to write the journal header file. + */ + nDb = db->nDb; + rc = execSqlF(db, pzErrMsg, "ATTACH %Q AS vacuum_db", zOut); + db->openFlags = saved_openFlags; + if( rc!=SQLITE_OK ) goto end_of_vacuum; + assert( (db->nDb-1)==nDb ); + pDb = &db->aDb[nDb]; + assert( strcmp(pDb->zDbSName,"vacuum_db")==0 ); + pTemp = pDb->pBt; + if( pOut ){ + sqlite3_file *id = sqlite3PagerFile(sqlite3BtreePager(pTemp)); + i64 sz = 0; + if( id->pMethods!=0 && (sqlite3OsFileSize(id, &sz)!=SQLITE_OK || sz>0) ){ + rc = SQLITE_ERROR; + sqlite3SetString(pzErrMsg, db, "output file already exists"); + goto end_of_vacuum; + } + db->mDbFlags |= DBFLAG_VacuumInto; + + /* For a VACUUM INTO, the pager-flags are set to the same values as + ** they are for the database being vacuumed, except that PAGER_CACHESPILL + ** is always set. */ + pgflags = db->aDb[iDb].safety_level | (db->flags & PAGER_FLAGS_MASK); + } + nRes = sqlite3BtreeGetRequestedReserve(pMain); + + sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size); + sqlite3BtreeSetSpillSize(pTemp, sqlite3BtreeSetSpillSize(pMain,0)); + sqlite3BtreeSetPagerFlags(pTemp, pgflags|PAGER_CACHESPILL); + + /* Begin a transaction and take an exclusive lock on the main database + ** file. This is done before the sqlite3BtreeGetPageSize(pMain) call below, + ** to ensure that we do not try to change the page-size on a WAL database. + */ + rc = execSql(db, pzErrMsg, "BEGIN"); + if( rc!=SQLITE_OK ) goto end_of_vacuum; + rc = sqlite3BtreeBeginTrans(pMain, pOut==0 ? 2 : 0, 0); + if( rc!=SQLITE_OK ) goto end_of_vacuum; + + /* Do not attempt to change the page size for a WAL database */ + if( sqlite3PagerGetJournalMode(sqlite3BtreePager(pMain)) + ==PAGER_JOURNALMODE_WAL + && pOut==0 + ){ + db->nextPagesize = 0; + } + + if( sqlite3BtreeSetPageSize(pTemp, sqlite3BtreeGetPageSize(pMain), nRes, 0) + || (!isMemDb && sqlite3BtreeSetPageSize(pTemp, db->nextPagesize, nRes, 0)) + || NEVER(db->mallocFailed) + ){ + rc = SQLITE_NOMEM_BKPT; + goto end_of_vacuum; + } + +#ifndef SQLITE_OMIT_AUTOVACUUM + sqlite3BtreeSetAutoVacuum(pTemp, db->nextAutovac>=0 ? db->nextAutovac : + sqlite3BtreeGetAutoVacuum(pMain)); +#endif + + /* Query the schema of the main database. Create a mirror schema + ** in the temporary database. + */ + db->init.iDb = nDb; /* force new CREATE statements into vacuum_db */ + rc = execSqlF(db, pzErrMsg, + "SELECT sql FROM \"%w\".sqlite_schema" + " WHERE type='table'AND name<>'sqlite_sequence'" + " AND coalesce(rootpage,1)>0", + zDbMain + ); + if( rc!=SQLITE_OK ) goto end_of_vacuum; + rc = execSqlF(db, pzErrMsg, + "SELECT sql FROM \"%w\".sqlite_schema" + " WHERE type='index'", + zDbMain + ); + if( rc!=SQLITE_OK ) goto end_of_vacuum; + db->init.iDb = 0; + + /* Loop through the tables in the main database. For each, do + ** an "INSERT INTO vacuum_db.xxx SELECT * FROM main.xxx;" to copy + ** the contents to the temporary database. + */ + rc = execSqlF(db, pzErrMsg, + "SELECT'INSERT INTO vacuum_db.'||quote(name)" + "||' SELECT*FROM\"%w\".'||quote(name)" + "FROM vacuum_db.sqlite_schema " + "WHERE type='table'AND coalesce(rootpage,1)>0", + zDbMain + ); + assert( (db->mDbFlags & DBFLAG_Vacuum)!=0 ); + db->mDbFlags &= ~DBFLAG_Vacuum; + if( rc!=SQLITE_OK ) goto end_of_vacuum; + + /* Copy the triggers, views, and virtual tables from the main database + ** over to the temporary database. None of these objects has any + ** associated storage, so all we have to do is copy their entries + ** from the schema table. + */ + rc = execSqlF(db, pzErrMsg, + "INSERT INTO vacuum_db.sqlite_schema" + " SELECT*FROM \"%w\".sqlite_schema" + " WHERE type IN('view','trigger')" + " OR(type='table'AND rootpage=0)", + zDbMain + ); + if( rc ) goto end_of_vacuum; + + /* At this point, there is a write transaction open on both the + ** vacuum database and the main database. Assuming no error occurs, + ** both transactions are closed by this block - the main database + ** transaction by sqlite3BtreeCopyFile() and the other by an explicit + ** call to sqlite3BtreeCommit(). + */ + { + u32 meta; + int i; + + /* This array determines which meta meta values are preserved in the + ** vacuum. Even entries are the meta value number and odd entries + ** are an increment to apply to the meta value after the vacuum. + ** The increment is used to increase the schema cookie so that other + ** connections to the same database will know to reread the schema. + */ + static const unsigned char aCopy[] = { + BTREE_SCHEMA_VERSION, 1, /* Add one to the old schema cookie */ + BTREE_DEFAULT_CACHE_SIZE, 0, /* Preserve the default page cache size */ + BTREE_TEXT_ENCODING, 0, /* Preserve the text encoding */ + BTREE_USER_VERSION, 0, /* Preserve the user version */ + BTREE_APPLICATION_ID, 0, /* Preserve the application id */ + }; + + assert( SQLITE_TXN_WRITE==sqlite3BtreeTxnState(pTemp) ); + assert( pOut!=0 || SQLITE_TXN_WRITE==sqlite3BtreeTxnState(pMain) ); + + /* Copy Btree meta values */ + for(i=0; i<ArraySize(aCopy); i+=2){ + /* GetMeta() and UpdateMeta() cannot fail in this context because + ** we already have page 1 loaded into cache and marked dirty. */ + sqlite3BtreeGetMeta(pMain, aCopy[i], &meta); + rc = sqlite3BtreeUpdateMeta(pTemp, aCopy[i], meta+aCopy[i+1]); + if( NEVER(rc!=SQLITE_OK) ) goto end_of_vacuum; + } + + if( pOut==0 ){ + rc = sqlite3BtreeCopyFile(pMain, pTemp); + } + if( rc!=SQLITE_OK ) goto end_of_vacuum; + rc = sqlite3BtreeCommit(pTemp); + if( rc!=SQLITE_OK ) goto end_of_vacuum; +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pOut==0 ){ + sqlite3BtreeSetAutoVacuum(pMain, sqlite3BtreeGetAutoVacuum(pTemp)); + } +#endif + } + + assert( rc==SQLITE_OK ); + if( pOut==0 ){ + nRes = sqlite3BtreeGetRequestedReserve(pTemp); + rc = sqlite3BtreeSetPageSize(pMain, sqlite3BtreeGetPageSize(pTemp), nRes,1); + } + +end_of_vacuum: + /* Restore the original value of db->flags */ + db->init.iDb = 0; + db->mDbFlags = saved_mDbFlags; + db->flags = saved_flags; + db->nChange = saved_nChange; + db->nTotalChange = saved_nTotalChange; + db->mTrace = saved_mTrace; + sqlite3BtreeSetPageSize(pMain, -1, 0, 1); + + /* Currently there is an SQL level transaction open on the vacuum + ** database. No locks are held on any other files (since the main file + ** was committed at the btree level). So it safe to end the transaction + ** by manually setting the autoCommit flag to true and detaching the + ** vacuum database. The vacuum_db journal file is deleted when the pager + ** is closed by the DETACH. + */ + db->autoCommit = 1; + + if( pDb ){ + sqlite3BtreeClose(pDb->pBt); + pDb->pBt = 0; + pDb->pSchema = 0; + } + + /* This both clears the schemas and reduces the size of the db->aDb[] + ** array. */ + sqlite3ResetAllSchemasOfConnection(db); + + return rc; +} + +#endif /* SQLITE_OMIT_VACUUM && SQLITE_OMIT_ATTACH */ + +/************** End of vacuum.c **********************************************/ +/************** Begin file vtab.c ********************************************/ +/* +** 2006 June 10 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to help implement virtual tables. +*/ +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* #include "sqliteInt.h" */ + +/* +** Before a virtual table xCreate() or xConnect() method is invoked, the +** sqlite3.pVtabCtx member variable is set to point to an instance of +** this struct allocated on the stack. It is used by the implementation of +** the sqlite3_declare_vtab() and sqlite3_vtab_config() APIs, both of which +** are invoked only from within xCreate and xConnect methods. +*/ +struct VtabCtx { + VTable *pVTable; /* The virtual table being constructed */ + Table *pTab; /* The Table object to which the virtual table belongs */ + VtabCtx *pPrior; /* Parent context (if any) */ + int bDeclared; /* True after sqlite3_declare_vtab() is called */ +}; + +/* +** Construct and install a Module object for a virtual table. When this +** routine is called, it is guaranteed that all appropriate locks are held +** and the module is not already part of the connection. +** +** If there already exists a module with zName, replace it with the new one. +** If pModule==0, then delete the module zName if it exists. +*/ +SQLITE_PRIVATE Module *sqlite3VtabCreateModule( + sqlite3 *db, /* Database in which module is registered */ + const char *zName, /* Name assigned to this module */ + const sqlite3_module *pModule, /* The definition of the module */ + void *pAux, /* Context pointer for xCreate/xConnect */ + void (*xDestroy)(void *) /* Module destructor function */ +){ + Module *pMod; + Module *pDel; + char *zCopy; + if( pModule==0 ){ + zCopy = (char*)zName; + pMod = 0; + }else{ + int nName = sqlite3Strlen30(zName); + pMod = (Module *)sqlite3Malloc(sizeof(Module) + nName + 1); + if( pMod==0 ){ + sqlite3OomFault(db); + return 0; + } + zCopy = (char *)(&pMod[1]); + memcpy(zCopy, zName, nName+1); + pMod->zName = zCopy; + pMod->pModule = pModule; + pMod->pAux = pAux; + pMod->xDestroy = xDestroy; + pMod->pEpoTab = 0; + pMod->nRefModule = 1; + } + pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,(void*)pMod); + if( pDel ){ + if( pDel==pMod ){ + sqlite3OomFault(db); + sqlite3DbFree(db, pDel); + pMod = 0; + }else{ + sqlite3VtabEponymousTableClear(db, pDel); + sqlite3VtabModuleUnref(db, pDel); + } + } + return pMod; +} + +/* +** The actual function that does the work of creating a new module. +** This function implements the sqlite3_create_module() and +** sqlite3_create_module_v2() interfaces. +*/ +static int createModule( + sqlite3 *db, /* Database in which module is registered */ + const char *zName, /* Name assigned to this module */ + const sqlite3_module *pModule, /* The definition of the module */ + void *pAux, /* Context pointer for xCreate/xConnect */ + void (*xDestroy)(void *) /* Module destructor function */ +){ + int rc = SQLITE_OK; + + sqlite3_mutex_enter(db->mutex); + (void)sqlite3VtabCreateModule(db, zName, pModule, pAux, xDestroy); + rc = sqlite3ApiExit(db, rc); + if( rc!=SQLITE_OK && xDestroy ) xDestroy(pAux); + sqlite3_mutex_leave(db->mutex); + return rc; +} + + +/* +** External API function used to create a new virtual-table module. +*/ +SQLITE_API int sqlite3_create_module( + sqlite3 *db, /* Database in which module is registered */ + const char *zName, /* Name assigned to this module */ + const sqlite3_module *pModule, /* The definition of the module */ + void *pAux /* Context pointer for xCreate/xConnect */ +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif + return createModule(db, zName, pModule, pAux, 0); +} + +/* +** External API function used to create a new virtual-table module. +*/ +SQLITE_API int sqlite3_create_module_v2( + sqlite3 *db, /* Database in which module is registered */ + const char *zName, /* Name assigned to this module */ + const sqlite3_module *pModule, /* The definition of the module */ + void *pAux, /* Context pointer for xCreate/xConnect */ + void (*xDestroy)(void *) /* Module destructor function */ +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif + return createModule(db, zName, pModule, pAux, xDestroy); +} + +/* +** External API to drop all virtual-table modules, except those named +** on the azNames list. +*/ +SQLITE_API int sqlite3_drop_modules(sqlite3 *db, const char** azNames){ + HashElem *pThis, *pNext; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + for(pThis=sqliteHashFirst(&db->aModule); pThis; pThis=pNext){ + Module *pMod = (Module*)sqliteHashData(pThis); + pNext = sqliteHashNext(pThis); + if( azNames ){ + int ii; + for(ii=0; azNames[ii]!=0 && strcmp(azNames[ii],pMod->zName)!=0; ii++){} + if( azNames[ii]!=0 ) continue; + } + createModule(db, pMod->zName, 0, 0, 0); + } + return SQLITE_OK; +} + +/* +** Decrement the reference count on a Module object. Destroy the +** module when the reference count reaches zero. +*/ +SQLITE_PRIVATE void sqlite3VtabModuleUnref(sqlite3 *db, Module *pMod){ + assert( pMod->nRefModule>0 ); + pMod->nRefModule--; + if( pMod->nRefModule==0 ){ + if( pMod->xDestroy ){ + pMod->xDestroy(pMod->pAux); + } + assert( pMod->pEpoTab==0 ); + sqlite3DbFree(db, pMod); + } +} + +/* +** Lock the virtual table so that it cannot be disconnected. +** Locks nest. Every lock should have a corresponding unlock. +** If an unlock is omitted, resources leaks will occur. +** +** If a disconnect is attempted while a virtual table is locked, +** the disconnect is deferred until all locks have been removed. +*/ +SQLITE_PRIVATE void sqlite3VtabLock(VTable *pVTab){ + pVTab->nRef++; +} + + +/* +** pTab is a pointer to a Table structure representing a virtual-table. +** Return a pointer to the VTable object used by connection db to access +** this virtual-table, if one has been created, or NULL otherwise. +*/ +SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3 *db, Table *pTab){ + VTable *pVtab; + assert( IsVirtual(pTab) ); + for(pVtab=pTab->u.vtab.p; pVtab && pVtab->db!=db; pVtab=pVtab->pNext); + return pVtab; +} + +/* +** Decrement the ref-count on a virtual table object. When the ref-count +** reaches zero, call the xDisconnect() method to delete the object. +*/ +SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *pVTab){ + sqlite3 *db = pVTab->db; + + assert( db ); + assert( pVTab->nRef>0 ); + assert( db->eOpenState==SQLITE_STATE_OPEN + || db->eOpenState==SQLITE_STATE_ZOMBIE ); + + pVTab->nRef--; + if( pVTab->nRef==0 ){ + sqlite3_vtab *p = pVTab->pVtab; + if( p ){ + p->pModule->xDisconnect(p); + } + sqlite3VtabModuleUnref(pVTab->db, pVTab->pMod); + sqlite3DbFree(db, pVTab); + } +} + +/* +** Table p is a virtual table. This function moves all elements in the +** p->u.vtab.p list to the sqlite3.pDisconnect lists of their associated +** database connections to be disconnected at the next opportunity. +** Except, if argument db is not NULL, then the entry associated with +** connection db is left in the p->u.vtab.p list. +*/ +static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){ + VTable *pRet = 0; + VTable *pVTable; + + assert( IsVirtual(p) ); + pVTable = p->u.vtab.p; + p->u.vtab.p = 0; + + /* Assert that the mutex (if any) associated with the BtShared database + ** that contains table p is held by the caller. See header comments + ** above function sqlite3VtabUnlockList() for an explanation of why + ** this makes it safe to access the sqlite3.pDisconnect list of any + ** database connection that may have an entry in the p->u.vtab.p list. + */ + assert( db==0 || sqlite3SchemaMutexHeld(db, 0, p->pSchema) ); + + while( pVTable ){ + sqlite3 *db2 = pVTable->db; + VTable *pNext = pVTable->pNext; + assert( db2 ); + if( db2==db ){ + pRet = pVTable; + p->u.vtab.p = pRet; + pRet->pNext = 0; + }else{ + pVTable->pNext = db2->pDisconnect; + db2->pDisconnect = pVTable; + } + pVTable = pNext; + } + + assert( !db || pRet ); + return pRet; +} + +/* +** Table *p is a virtual table. This function removes the VTable object +** for table *p associated with database connection db from the linked +** list in p->pVTab. It also decrements the VTable ref count. This is +** used when closing database connection db to free all of its VTable +** objects without disturbing the rest of the Schema object (which may +** be being used by other shared-cache connections). +*/ +SQLITE_PRIVATE void sqlite3VtabDisconnect(sqlite3 *db, Table *p){ + VTable **ppVTab; + + assert( IsVirtual(p) ); + assert( sqlite3BtreeHoldsAllMutexes(db) ); + assert( sqlite3_mutex_held(db->mutex) ); + + for(ppVTab=&p->u.vtab.p; *ppVTab; ppVTab=&(*ppVTab)->pNext){ + if( (*ppVTab)->db==db ){ + VTable *pVTab = *ppVTab; + *ppVTab = pVTab->pNext; + sqlite3VtabUnlock(pVTab); + break; + } + } +} + + +/* +** Disconnect all the virtual table objects in the sqlite3.pDisconnect list. +** +** This function may only be called when the mutexes associated with all +** shared b-tree databases opened using connection db are held by the +** caller. This is done to protect the sqlite3.pDisconnect list. The +** sqlite3.pDisconnect list is accessed only as follows: +** +** 1) By this function. In this case, all BtShared mutexes and the mutex +** associated with the database handle itself must be held. +** +** 2) By function vtabDisconnectAll(), when it adds a VTable entry to +** the sqlite3.pDisconnect list. In this case either the BtShared mutex +** associated with the database the virtual table is stored in is held +** or, if the virtual table is stored in a non-sharable database, then +** the database handle mutex is held. +** +** As a result, a sqlite3.pDisconnect cannot be accessed simultaneously +** by multiple threads. It is thread-safe. +*/ +SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3 *db){ + VTable *p = db->pDisconnect; + + assert( sqlite3BtreeHoldsAllMutexes(db) ); + assert( sqlite3_mutex_held(db->mutex) ); + + if( p ){ + db->pDisconnect = 0; + sqlite3ExpirePreparedStatements(db, 0); + do { + VTable *pNext = p->pNext; + sqlite3VtabUnlock(p); + p = pNext; + }while( p ); + } +} + +/* +** Clear any and all virtual-table information from the Table record. +** This routine is called, for example, just before deleting the Table +** record. +** +** Since it is a virtual-table, the Table structure contains a pointer +** to the head of a linked list of VTable structures. Each VTable +** structure is associated with a single sqlite3* user of the schema. +** The reference count of the VTable structure associated with database +** connection db is decremented immediately (which may lead to the +** structure being xDisconnected and free). Any other VTable structures +** in the list are moved to the sqlite3.pDisconnect list of the associated +** database connection. +*/ +SQLITE_PRIVATE void sqlite3VtabClear(sqlite3 *db, Table *p){ + assert( IsVirtual(p) ); + assert( db!=0 ); + if( db->pnBytesFreed==0 ) vtabDisconnectAll(0, p); + if( p->u.vtab.azArg ){ + int i; + for(i=0; i<p->u.vtab.nArg; i++){ + if( i!=1 ) sqlite3DbFree(db, p->u.vtab.azArg[i]); + } + sqlite3DbFree(db, p->u.vtab.azArg); + } +} + +/* +** Add a new module argument to pTable->u.vtab.azArg[]. +** The string is not copied - the pointer is stored. The +** string will be freed automatically when the table is +** deleted. +*/ +static void addModuleArgument(Parse *pParse, Table *pTable, char *zArg){ + sqlite3_int64 nBytes; + char **azModuleArg; + sqlite3 *db = pParse->db; + + assert( IsVirtual(pTable) ); + nBytes = sizeof(char *)*(2+pTable->u.vtab.nArg); + if( pTable->u.vtab.nArg+3>=db->aLimit[SQLITE_LIMIT_COLUMN] ){ + sqlite3ErrorMsg(pParse, "too many columns on %s", pTable->zName); + } + azModuleArg = sqlite3DbRealloc(db, pTable->u.vtab.azArg, nBytes); + if( azModuleArg==0 ){ + sqlite3DbFree(db, zArg); + }else{ + int i = pTable->u.vtab.nArg++; + azModuleArg[i] = zArg; + azModuleArg[i+1] = 0; + pTable->u.vtab.azArg = azModuleArg; + } +} + +/* +** The parser calls this routine when it first sees a CREATE VIRTUAL TABLE +** statement. The module name has been parsed, but the optional list +** of parameters that follow the module name are still pending. +*/ +SQLITE_PRIVATE void sqlite3VtabBeginParse( + Parse *pParse, /* Parsing context */ + Token *pName1, /* Name of new table, or database name */ + Token *pName2, /* Name of new table or NULL */ + Token *pModuleName, /* Name of the module for the virtual table */ + int ifNotExists /* No error if the table already exists */ +){ + Table *pTable; /* The new virtual table */ + sqlite3 *db; /* Database connection */ + + sqlite3StartTable(pParse, pName1, pName2, 0, 0, 1, ifNotExists); + pTable = pParse->pNewTable; + if( pTable==0 ) return; + assert( 0==pTable->pIndex ); + pTable->eTabType = TABTYP_VTAB; + + db = pParse->db; + + assert( pTable->u.vtab.nArg==0 ); + addModuleArgument(pParse, pTable, sqlite3NameFromToken(db, pModuleName)); + addModuleArgument(pParse, pTable, 0); + addModuleArgument(pParse, pTable, sqlite3DbStrDup(db, pTable->zName)); + assert( (pParse->sNameToken.z==pName2->z && pName2->z!=0) + || (pParse->sNameToken.z==pName1->z && pName2->z==0) + ); + pParse->sNameToken.n = (int)( + &pModuleName->z[pModuleName->n] - pParse->sNameToken.z + ); + +#ifndef SQLITE_OMIT_AUTHORIZATION + /* Creating a virtual table invokes the authorization callback twice. + ** The first invocation, to obtain permission to INSERT a row into the + ** sqlite_schema table, has already been made by sqlite3StartTable(). + ** The second call, to obtain permission to create the table, is made now. + */ + if( pTable->u.vtab.azArg ){ + int iDb = sqlite3SchemaToIndex(db, pTable->pSchema); + assert( iDb>=0 ); /* The database the table is being created in */ + sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName, + pTable->u.vtab.azArg[0], pParse->db->aDb[iDb].zDbSName); + } +#endif +} + +/* +** This routine takes the module argument that has been accumulating +** in pParse->zArg[] and appends it to the list of arguments on the +** virtual table currently under construction in pParse->pTable. +*/ +static void addArgumentToVtab(Parse *pParse){ + if( pParse->sArg.z && pParse->pNewTable ){ + const char *z = (const char*)pParse->sArg.z; + int n = pParse->sArg.n; + sqlite3 *db = pParse->db; + addModuleArgument(pParse, pParse->pNewTable, sqlite3DbStrNDup(db, z, n)); + } +} + +/* +** The parser calls this routine after the CREATE VIRTUAL TABLE statement +** has been completely parsed. +*/ +SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ + Table *pTab = pParse->pNewTable; /* The table being constructed */ + sqlite3 *db = pParse->db; /* The database connection */ + + if( pTab==0 ) return; + assert( IsVirtual(pTab) ); + addArgumentToVtab(pParse); + pParse->sArg.z = 0; + if( pTab->u.vtab.nArg<1 ) return; + + /* If the CREATE VIRTUAL TABLE statement is being entered for the + ** first time (in other words if the virtual table is actually being + ** created now instead of just being read out of sqlite_schema) then + ** do additional initialization work and store the statement text + ** in the sqlite_schema table. + */ + if( !db->init.busy ){ + char *zStmt; + char *zWhere; + int iDb; + int iReg; + Vdbe *v; + + sqlite3MayAbort(pParse); + + /* Compute the complete text of the CREATE VIRTUAL TABLE statement */ + if( pEnd ){ + pParse->sNameToken.n = (int)(pEnd->z - pParse->sNameToken.z) + pEnd->n; + } + zStmt = sqlite3MPrintf(db, "CREATE VIRTUAL TABLE %T", &pParse->sNameToken); + + /* A slot for the record has already been allocated in the + ** schema table. We just need to update that slot with all + ** the information we've collected. + ** + ** The VM register number pParse->regRowid holds the rowid of an + ** entry in the sqlite_schema table that was created for this vtab + ** by sqlite3StartTable(). + */ + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + sqlite3NestedParse(pParse, + "UPDATE %Q." LEGACY_SCHEMA_TABLE " " + "SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q " + "WHERE rowid=#%d", + db->aDb[iDb].zDbSName, + pTab->zName, + pTab->zName, + zStmt, + pParse->regRowid + ); + v = sqlite3GetVdbe(pParse); + sqlite3ChangeCookie(pParse, iDb); + + sqlite3VdbeAddOp0(v, OP_Expire); + zWhere = sqlite3MPrintf(db, "name=%Q AND sql=%Q", pTab->zName, zStmt); + sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere, 0); + sqlite3DbFree(db, zStmt); + + iReg = ++pParse->nMem; + sqlite3VdbeLoadString(v, iReg, pTab->zName); + sqlite3VdbeAddOp2(v, OP_VCreate, iDb, iReg); + }else{ + /* If we are rereading the sqlite_schema table create the in-memory + ** record of the table. */ + Table *pOld; + Schema *pSchema = pTab->pSchema; + const char *zName = pTab->zName; + assert( zName!=0 ); + sqlite3MarkAllShadowTablesOf(db, pTab); + pOld = sqlite3HashInsert(&pSchema->tblHash, zName, pTab); + if( pOld ){ + sqlite3OomFault(db); + assert( pTab==pOld ); /* Malloc must have failed inside HashInsert() */ + return; + } + pParse->pNewTable = 0; + } +} + +/* +** The parser calls this routine when it sees the first token +** of an argument to the module name in a CREATE VIRTUAL TABLE statement. +*/ +SQLITE_PRIVATE void sqlite3VtabArgInit(Parse *pParse){ + addArgumentToVtab(pParse); + pParse->sArg.z = 0; + pParse->sArg.n = 0; +} + +/* +** The parser calls this routine for each token after the first token +** in an argument to the module name in a CREATE VIRTUAL TABLE statement. +*/ +SQLITE_PRIVATE void sqlite3VtabArgExtend(Parse *pParse, Token *p){ + Token *pArg = &pParse->sArg; + if( pArg->z==0 ){ + pArg->z = p->z; + pArg->n = p->n; + }else{ + assert(pArg->z <= p->z); + pArg->n = (int)(&p->z[p->n] - pArg->z); + } +} + +/* +** Invoke a virtual table constructor (either xCreate or xConnect). The +** pointer to the function to invoke is passed as the fourth parameter +** to this procedure. +*/ +static int vtabCallConstructor( + sqlite3 *db, + Table *pTab, + Module *pMod, + int (*xConstruct)(sqlite3*,void*,int,const char*const*,sqlite3_vtab**,char**), + char **pzErr +){ + VtabCtx sCtx; + VTable *pVTable; + int rc; + const char *const*azArg; + int nArg = pTab->u.vtab.nArg; + char *zErr = 0; + char *zModuleName; + int iDb; + VtabCtx *pCtx; + + assert( IsVirtual(pTab) ); + azArg = (const char *const*)pTab->u.vtab.azArg; + + /* Check that the virtual-table is not already being initialized */ + for(pCtx=db->pVtabCtx; pCtx; pCtx=pCtx->pPrior){ + if( pCtx->pTab==pTab ){ + *pzErr = sqlite3MPrintf(db, + "vtable constructor called recursively: %s", pTab->zName + ); + return SQLITE_LOCKED; + } + } + + zModuleName = sqlite3DbStrDup(db, pTab->zName); + if( !zModuleName ){ + return SQLITE_NOMEM_BKPT; + } + + pVTable = sqlite3MallocZero(sizeof(VTable)); + if( !pVTable ){ + sqlite3OomFault(db); + sqlite3DbFree(db, zModuleName); + return SQLITE_NOMEM_BKPT; + } + pVTable->db = db; + pVTable->pMod = pMod; + pVTable->eVtabRisk = SQLITE_VTABRISK_Normal; + + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + pTab->u.vtab.azArg[1] = db->aDb[iDb].zDbSName; + + /* Invoke the virtual table constructor */ + assert( &db->pVtabCtx ); + assert( xConstruct ); + sCtx.pTab = pTab; + sCtx.pVTable = pVTable; + sCtx.pPrior = db->pVtabCtx; + sCtx.bDeclared = 0; + db->pVtabCtx = &sCtx; + pTab->nTabRef++; + rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVTable->pVtab, &zErr); + sqlite3DeleteTable(db, pTab); + db->pVtabCtx = sCtx.pPrior; + if( rc==SQLITE_NOMEM ) sqlite3OomFault(db); + assert( sCtx.pTab==pTab ); + + if( SQLITE_OK!=rc ){ + if( zErr==0 ){ + *pzErr = sqlite3MPrintf(db, "vtable constructor failed: %s", zModuleName); + }else { + *pzErr = sqlite3MPrintf(db, "%s", zErr); + sqlite3_free(zErr); + } + sqlite3DbFree(db, pVTable); + }else if( ALWAYS(pVTable->pVtab) ){ + /* Justification of ALWAYS(): A correct vtab constructor must allocate + ** the sqlite3_vtab object if successful. */ + memset(pVTable->pVtab, 0, sizeof(pVTable->pVtab[0])); + pVTable->pVtab->pModule = pMod->pModule; + pMod->nRefModule++; + pVTable->nRef = 1; + if( sCtx.bDeclared==0 ){ + const char *zFormat = "vtable constructor did not declare schema: %s"; + *pzErr = sqlite3MPrintf(db, zFormat, pTab->zName); + sqlite3VtabUnlock(pVTable); + rc = SQLITE_ERROR; + }else{ + int iCol; + u16 oooHidden = 0; + /* If everything went according to plan, link the new VTable structure + ** into the linked list headed by pTab->u.vtab.p. Then loop through the + ** columns of the table to see if any of them contain the token "hidden". + ** If so, set the Column COLFLAG_HIDDEN flag and remove the token from + ** the type string. */ + pVTable->pNext = pTab->u.vtab.p; + pTab->u.vtab.p = pVTable; + + for(iCol=0; iCol<pTab->nCol; iCol++){ + char *zType = sqlite3ColumnType(&pTab->aCol[iCol], ""); + int nType; + int i = 0; + nType = sqlite3Strlen30(zType); + for(i=0; i<nType; i++){ + if( 0==sqlite3StrNICmp("hidden", &zType[i], 6) + && (i==0 || zType[i-1]==' ') + && (zType[i+6]=='\0' || zType[i+6]==' ') + ){ + break; + } + } + if( i<nType ){ + int j; + int nDel = 6 + (zType[i+6] ? 1 : 0); + for(j=i; (j+nDel)<=nType; j++){ + zType[j] = zType[j+nDel]; + } + if( zType[i]=='\0' && i>0 ){ + assert(zType[i-1]==' '); + zType[i-1] = '\0'; + } + pTab->aCol[iCol].colFlags |= COLFLAG_HIDDEN; + pTab->tabFlags |= TF_HasHidden; + oooHidden = TF_OOOHidden; + }else{ + pTab->tabFlags |= oooHidden; + } + } + } + } + + sqlite3DbFree(db, zModuleName); + return rc; +} + +/* +** This function is invoked by the parser to call the xConnect() method +** of the virtual table pTab. If an error occurs, an error code is returned +** and an error left in pParse. +** +** This call is a no-op if table pTab is not a virtual table. +*/ +SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse *pParse, Table *pTab){ + sqlite3 *db = pParse->db; + const char *zMod; + Module *pMod; + int rc; + + assert( pTab ); + assert( IsVirtual(pTab) ); + if( sqlite3GetVTable(db, pTab) ){ + return SQLITE_OK; + } + + /* Locate the required virtual table module */ + zMod = pTab->u.vtab.azArg[0]; + pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); + + if( !pMod ){ + const char *zModule = pTab->u.vtab.azArg[0]; + sqlite3ErrorMsg(pParse, "no such module: %s", zModule); + rc = SQLITE_ERROR; + }else{ + char *zErr = 0; + rc = vtabCallConstructor(db, pTab, pMod, pMod->pModule->xConnect, &zErr); + if( rc!=SQLITE_OK ){ + sqlite3ErrorMsg(pParse, "%s", zErr); + pParse->rc = rc; + } + sqlite3DbFree(db, zErr); + } + + return rc; +} +/* +** Grow the db->aVTrans[] array so that there is room for at least one +** more v-table. Return SQLITE_NOMEM if a malloc fails, or SQLITE_OK otherwise. +*/ +static int growVTrans(sqlite3 *db){ + const int ARRAY_INCR = 5; + + /* Grow the sqlite3.aVTrans array if required */ + if( (db->nVTrans%ARRAY_INCR)==0 ){ + VTable **aVTrans; + sqlite3_int64 nBytes = sizeof(sqlite3_vtab*)* + ((sqlite3_int64)db->nVTrans + ARRAY_INCR); + aVTrans = sqlite3DbRealloc(db, (void *)db->aVTrans, nBytes); + if( !aVTrans ){ + return SQLITE_NOMEM_BKPT; + } + memset(&aVTrans[db->nVTrans], 0, sizeof(sqlite3_vtab *)*ARRAY_INCR); + db->aVTrans = aVTrans; + } + + return SQLITE_OK; +} + +/* +** Add the virtual table pVTab to the array sqlite3.aVTrans[]. Space should +** have already been reserved using growVTrans(). +*/ +static void addToVTrans(sqlite3 *db, VTable *pVTab){ + /* Add pVtab to the end of sqlite3.aVTrans */ + db->aVTrans[db->nVTrans++] = pVTab; + sqlite3VtabLock(pVTab); +} + +/* +** This function is invoked by the vdbe to call the xCreate method +** of the virtual table named zTab in database iDb. +** +** If an error occurs, *pzErr is set to point to an English language +** description of the error and an SQLITE_XXX error code is returned. +** In this case the caller must call sqlite3DbFree(db, ) on *pzErr. +*/ +SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, char **pzErr){ + int rc = SQLITE_OK; + Table *pTab; + Module *pMod; + const char *zMod; + + pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); + assert( pTab && IsVirtual(pTab) && !pTab->u.vtab.p ); + + /* Locate the required virtual table module */ + zMod = pTab->u.vtab.azArg[0]; + pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); + + /* If the module has been registered and includes a Create method, + ** invoke it now. If the module has not been registered, return an + ** error. Otherwise, do nothing. + */ + if( pMod==0 || pMod->pModule->xCreate==0 || pMod->pModule->xDestroy==0 ){ + *pzErr = sqlite3MPrintf(db, "no such module: %s", zMod); + rc = SQLITE_ERROR; + }else{ + rc = vtabCallConstructor(db, pTab, pMod, pMod->pModule->xCreate, pzErr); + } + + /* Justification of ALWAYS(): The xConstructor method is required to + ** create a valid sqlite3_vtab if it returns SQLITE_OK. */ + if( rc==SQLITE_OK && ALWAYS(sqlite3GetVTable(db, pTab)) ){ + rc = growVTrans(db); + if( rc==SQLITE_OK ){ + addToVTrans(db, sqlite3GetVTable(db, pTab)); + } + } + + return rc; +} + +/* +** This function is used to set the schema of a virtual table. It is only +** valid to call this function from within the xCreate() or xConnect() of a +** virtual table module. +*/ +SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ + VtabCtx *pCtx; + int rc = SQLITE_OK; + Table *pTab; + Parse sParse; + int initBusy; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + pCtx = db->pVtabCtx; + if( !pCtx || pCtx->bDeclared ){ + sqlite3Error(db, SQLITE_MISUSE_BKPT); + sqlite3_mutex_leave(db->mutex); + return SQLITE_MISUSE_BKPT; + } + pTab = pCtx->pTab; + assert( IsVirtual(pTab) ); + + sqlite3ParseObjectInit(&sParse, db); + sParse.eParseMode = PARSE_MODE_DECLARE_VTAB; + sParse.disableTriggers = 1; + /* We should never be able to reach this point while loading the + ** schema. Nevertheless, defend against that (turn off db->init.busy) + ** in case a bug arises. */ + assert( db->init.busy==0 ); + initBusy = db->init.busy; + db->init.busy = 0; + sParse.nQueryLoop = 1; + if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable) + && ALWAYS(sParse.pNewTable!=0) + && ALWAYS(!db->mallocFailed) + && IsOrdinaryTable(sParse.pNewTable) + ){ + assert( sParse.zErrMsg==0 ); + if( !pTab->aCol ){ + Table *pNew = sParse.pNewTable; + Index *pIdx; + pTab->aCol = pNew->aCol; + sqlite3ExprListDelete(db, pNew->u.tab.pDfltList); + pTab->nNVCol = pTab->nCol = pNew->nCol; + pTab->tabFlags |= pNew->tabFlags & (TF_WithoutRowid|TF_NoVisibleRowid); + pNew->nCol = 0; + pNew->aCol = 0; + assert( pTab->pIndex==0 ); + assert( HasRowid(pNew) || sqlite3PrimaryKeyIndex(pNew)!=0 ); + if( !HasRowid(pNew) + && pCtx->pVTable->pMod->pModule->xUpdate!=0 + && sqlite3PrimaryKeyIndex(pNew)->nKeyCol!=1 + ){ + /* WITHOUT ROWID virtual tables must either be read-only (xUpdate==0) + ** or else must have a single-column PRIMARY KEY */ + rc = SQLITE_ERROR; + } + pIdx = pNew->pIndex; + if( pIdx ){ + assert( pIdx->pNext==0 ); + pTab->pIndex = pIdx; + pNew->pIndex = 0; + pIdx->pTable = pTab; + } + } + pCtx->bDeclared = 1; + }else{ + sqlite3ErrorWithMsg(db, SQLITE_ERROR, + (sParse.zErrMsg ? "%s" : 0), sParse.zErrMsg); + sqlite3DbFree(db, sParse.zErrMsg); + rc = SQLITE_ERROR; + } + sParse.eParseMode = PARSE_MODE_NORMAL; + + if( sParse.pVdbe ){ + sqlite3VdbeFinalize(sParse.pVdbe); + } + sqlite3DeleteTable(db, sParse.pNewTable); + sqlite3ParseObjectReset(&sParse); + db->init.busy = initBusy; + + assert( (rc&0xff)==rc ); + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** This function is invoked by the vdbe to call the xDestroy method +** of the virtual table named zTab in database iDb. This occurs +** when a DROP TABLE is mentioned. +** +** This call is a no-op if zTab is not a virtual table. +*/ +SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab){ + int rc = SQLITE_OK; + Table *pTab; + + pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); + if( ALWAYS(pTab!=0) + && ALWAYS(IsVirtual(pTab)) + && ALWAYS(pTab->u.vtab.p!=0) + ){ + VTable *p; + int (*xDestroy)(sqlite3_vtab *); + for(p=pTab->u.vtab.p; p; p=p->pNext){ + assert( p->pVtab ); + if( p->pVtab->nRef>0 ){ + return SQLITE_LOCKED; + } + } + p = vtabDisconnectAll(db, pTab); + xDestroy = p->pMod->pModule->xDestroy; + if( xDestroy==0 ) xDestroy = p->pMod->pModule->xDisconnect; + assert( xDestroy!=0 ); + pTab->nTabRef++; + rc = xDestroy(p->pVtab); + /* Remove the sqlite3_vtab* from the aVTrans[] array, if applicable */ + if( rc==SQLITE_OK ){ + assert( pTab->u.vtab.p==p && p->pNext==0 ); + p->pVtab = 0; + pTab->u.vtab.p = 0; + sqlite3VtabUnlock(p); + } + sqlite3DeleteTable(db, pTab); + } + + return rc; +} + +/* +** This function invokes either the xRollback or xCommit method +** of each of the virtual tables in the sqlite3.aVTrans array. The method +** called is identified by the second argument, "offset", which is +** the offset of the method to call in the sqlite3_module structure. +** +** The array is cleared after invoking the callbacks. +*/ +static void callFinaliser(sqlite3 *db, int offset){ + int i; + if( db->aVTrans ){ + VTable **aVTrans = db->aVTrans; + db->aVTrans = 0; + for(i=0; i<db->nVTrans; i++){ + VTable *pVTab = aVTrans[i]; + sqlite3_vtab *p = pVTab->pVtab; + if( p ){ + int (*x)(sqlite3_vtab *); + x = *(int (**)(sqlite3_vtab *))((char *)p->pModule + offset); + if( x ) x(p); + } + pVTab->iSavepoint = 0; + sqlite3VtabUnlock(pVTab); + } + sqlite3DbFree(db, aVTrans); + db->nVTrans = 0; + } +} + +/* +** Invoke the xSync method of all virtual tables in the sqlite3.aVTrans +** array. Return the error code for the first error that occurs, or +** SQLITE_OK if all xSync operations are successful. +** +** If an error message is available, leave it in p->zErrMsg. +*/ +SQLITE_PRIVATE int sqlite3VtabSync(sqlite3 *db, Vdbe *p){ + int i; + int rc = SQLITE_OK; + VTable **aVTrans = db->aVTrans; + + db->aVTrans = 0; + for(i=0; rc==SQLITE_OK && i<db->nVTrans; i++){ + int (*x)(sqlite3_vtab *); + sqlite3_vtab *pVtab = aVTrans[i]->pVtab; + if( pVtab && (x = pVtab->pModule->xSync)!=0 ){ + rc = x(pVtab); + sqlite3VtabImportErrmsg(p, pVtab); + } + } + db->aVTrans = aVTrans; + return rc; +} + +/* +** Invoke the xRollback method of all virtual tables in the +** sqlite3.aVTrans array. Then clear the array itself. +*/ +SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db){ + callFinaliser(db, offsetof(sqlite3_module,xRollback)); + return SQLITE_OK; +} + +/* +** Invoke the xCommit method of all virtual tables in the +** sqlite3.aVTrans array. Then clear the array itself. +*/ +SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db){ + callFinaliser(db, offsetof(sqlite3_module,xCommit)); + return SQLITE_OK; +} + +/* +** If the virtual table pVtab supports the transaction interface +** (xBegin/xRollback/xCommit and optionally xSync) and a transaction is +** not currently open, invoke the xBegin method now. +** +** If the xBegin call is successful, place the sqlite3_vtab pointer +** in the sqlite3.aVTrans array. +*/ +SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){ + int rc = SQLITE_OK; + const sqlite3_module *pModule; + + /* Special case: If db->aVTrans is NULL and db->nVTrans is greater + ** than zero, then this function is being called from within a + ** virtual module xSync() callback. It is illegal to write to + ** virtual module tables in this case, so return SQLITE_LOCKED. + */ + if( sqlite3VtabInSync(db) ){ + return SQLITE_LOCKED; + } + if( !pVTab ){ + return SQLITE_OK; + } + pModule = pVTab->pVtab->pModule; + + if( pModule->xBegin ){ + int i; + + /* If pVtab is already in the aVTrans array, return early */ + for(i=0; i<db->nVTrans; i++){ + if( db->aVTrans[i]==pVTab ){ + return SQLITE_OK; + } + } + + /* Invoke the xBegin method. If successful, add the vtab to the + ** sqlite3.aVTrans[] array. */ + rc = growVTrans(db); + if( rc==SQLITE_OK ){ + rc = pModule->xBegin(pVTab->pVtab); + if( rc==SQLITE_OK ){ + int iSvpt = db->nStatement + db->nSavepoint; + addToVTrans(db, pVTab); + if( iSvpt && pModule->xSavepoint ){ + pVTab->iSavepoint = iSvpt; + rc = pModule->xSavepoint(pVTab->pVtab, iSvpt-1); + } + } + } + } + return rc; +} + +/* +** Invoke either the xSavepoint, xRollbackTo or xRelease method of all +** virtual tables that currently have an open transaction. Pass iSavepoint +** as the second argument to the virtual table method invoked. +** +** If op is SAVEPOINT_BEGIN, the xSavepoint method is invoked. If it is +** SAVEPOINT_ROLLBACK, the xRollbackTo method. Otherwise, if op is +** SAVEPOINT_RELEASE, then the xRelease method of each virtual table with +** an open transaction is invoked. +** +** If any virtual table method returns an error code other than SQLITE_OK, +** processing is abandoned and the error returned to the caller of this +** function immediately. If all calls to virtual table methods are successful, +** SQLITE_OK is returned. +*/ +SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){ + int rc = SQLITE_OK; + + assert( op==SAVEPOINT_RELEASE||op==SAVEPOINT_ROLLBACK||op==SAVEPOINT_BEGIN ); + assert( iSavepoint>=-1 ); + if( db->aVTrans ){ + int i; + for(i=0; rc==SQLITE_OK && i<db->nVTrans; i++){ + VTable *pVTab = db->aVTrans[i]; + const sqlite3_module *pMod = pVTab->pMod->pModule; + if( pVTab->pVtab && pMod->iVersion>=2 ){ + int (*xMethod)(sqlite3_vtab *, int); + sqlite3VtabLock(pVTab); + switch( op ){ + case SAVEPOINT_BEGIN: + xMethod = pMod->xSavepoint; + pVTab->iSavepoint = iSavepoint+1; + break; + case SAVEPOINT_ROLLBACK: + xMethod = pMod->xRollbackTo; + break; + default: + xMethod = pMod->xRelease; + break; + } + if( xMethod && pVTab->iSavepoint>iSavepoint ){ + u64 savedFlags = (db->flags & SQLITE_Defensive); + db->flags &= ~(u64)SQLITE_Defensive; + rc = xMethod(pVTab->pVtab, iSavepoint); + db->flags |= savedFlags; + } + sqlite3VtabUnlock(pVTab); + } + } + } + return rc; +} + +/* +** The first parameter (pDef) is a function implementation. The +** second parameter (pExpr) is the first argument to this function. +** If pExpr is a column in a virtual table, then let the virtual +** table implementation have an opportunity to overload the function. +** +** This routine is used to allow virtual table implementations to +** overload MATCH, LIKE, GLOB, and REGEXP operators. +** +** Return either the pDef argument (indicating no change) or a +** new FuncDef structure that is marked as ephemeral using the +** SQLITE_FUNC_EPHEM flag. +*/ +SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction( + sqlite3 *db, /* Database connection for reporting malloc problems */ + FuncDef *pDef, /* Function to possibly overload */ + int nArg, /* Number of arguments to the function */ + Expr *pExpr /* First argument to the function */ +){ + Table *pTab; + sqlite3_vtab *pVtab; + sqlite3_module *pMod; + void (*xSFunc)(sqlite3_context*,int,sqlite3_value**) = 0; + void *pArg = 0; + FuncDef *pNew; + int rc = 0; + + /* Check to see the left operand is a column in a virtual table */ + if( NEVER(pExpr==0) ) return pDef; + if( pExpr->op!=TK_COLUMN ) return pDef; + assert( ExprUseYTab(pExpr) ); + pTab = pExpr->y.pTab; + if( NEVER(pTab==0) ) return pDef; + if( !IsVirtual(pTab) ) return pDef; + pVtab = sqlite3GetVTable(db, pTab)->pVtab; + assert( pVtab!=0 ); + assert( pVtab->pModule!=0 ); + pMod = (sqlite3_module *)pVtab->pModule; + if( pMod->xFindFunction==0 ) return pDef; + + /* Call the xFindFunction method on the virtual table implementation + ** to see if the implementation wants to overload this function. + ** + ** Though undocumented, we have historically always invoked xFindFunction + ** with an all lower-case function name. Continue in this tradition to + ** avoid any chance of an incompatibility. + */ +#ifdef SQLITE_DEBUG + { + int i; + for(i=0; pDef->zName[i]; i++){ + unsigned char x = (unsigned char)pDef->zName[i]; + assert( x==sqlite3UpperToLower[x] ); + } + } +#endif + rc = pMod->xFindFunction(pVtab, nArg, pDef->zName, &xSFunc, &pArg); + if( rc==0 ){ + return pDef; + } + + /* Create a new ephemeral function definition for the overloaded + ** function */ + pNew = sqlite3DbMallocZero(db, sizeof(*pNew) + + sqlite3Strlen30(pDef->zName) + 1); + if( pNew==0 ){ + return pDef; + } + *pNew = *pDef; + pNew->zName = (const char*)&pNew[1]; + memcpy((char*)&pNew[1], pDef->zName, sqlite3Strlen30(pDef->zName)+1); + pNew->xSFunc = xSFunc; + pNew->pUserData = pArg; + pNew->funcFlags |= SQLITE_FUNC_EPHEM; + return pNew; +} + +/* +** Make sure virtual table pTab is contained in the pParse->apVirtualLock[] +** array so that an OP_VBegin will get generated for it. Add pTab to the +** array if it is missing. If pTab is already in the array, this routine +** is a no-op. +*/ +SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){ + Parse *pToplevel = sqlite3ParseToplevel(pParse); + int i, n; + Table **apVtabLock; + + assert( IsVirtual(pTab) ); + for(i=0; i<pToplevel->nVtabLock; i++){ + if( pTab==pToplevel->apVtabLock[i] ) return; + } + n = (pToplevel->nVtabLock+1)*sizeof(pToplevel->apVtabLock[0]); + apVtabLock = sqlite3Realloc(pToplevel->apVtabLock, n); + if( apVtabLock ){ + pToplevel->apVtabLock = apVtabLock; + pToplevel->apVtabLock[pToplevel->nVtabLock++] = pTab; + }else{ + sqlite3OomFault(pToplevel->db); + } +} + +/* +** Check to see if virtual table module pMod can be have an eponymous +** virtual table instance. If it can, create one if one does not already +** exist. Return non-zero if either the eponymous virtual table instance +** exists when this routine returns or if an attempt to create it failed +** and an error message was left in pParse. +** +** An eponymous virtual table instance is one that is named after its +** module, and more importantly, does not require a CREATE VIRTUAL TABLE +** statement in order to come into existence. Eponymous virtual table +** instances always exist. They cannot be DROP-ed. +** +** Any virtual table module for which xConnect and xCreate are the same +** method can have an eponymous virtual table instance. +*/ +SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ + const sqlite3_module *pModule = pMod->pModule; + Table *pTab; + char *zErr = 0; + int rc; + sqlite3 *db = pParse->db; + if( pMod->pEpoTab ) return 1; + if( pModule->xCreate!=0 && pModule->xCreate!=pModule->xConnect ) return 0; + pTab = sqlite3DbMallocZero(db, sizeof(Table)); + if( pTab==0 ) return 0; + pTab->zName = sqlite3DbStrDup(db, pMod->zName); + if( pTab->zName==0 ){ + sqlite3DbFree(db, pTab); + return 0; + } + pMod->pEpoTab = pTab; + pTab->nTabRef = 1; + pTab->eTabType = TABTYP_VTAB; + pTab->pSchema = db->aDb[0].pSchema; + assert( pTab->u.vtab.nArg==0 ); + pTab->iPKey = -1; + pTab->tabFlags |= TF_Eponymous; + addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); + addModuleArgument(pParse, pTab, 0); + addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); + rc = vtabCallConstructor(db, pTab, pMod, pModule->xConnect, &zErr); + if( rc ){ + sqlite3ErrorMsg(pParse, "%s", zErr); + sqlite3DbFree(db, zErr); + sqlite3VtabEponymousTableClear(db, pMod); + } + return 1; +} + +/* +** Erase the eponymous virtual table instance associated with +** virtual table module pMod, if it exists. +*/ +SQLITE_PRIVATE void sqlite3VtabEponymousTableClear(sqlite3 *db, Module *pMod){ + Table *pTab = pMod->pEpoTab; + if( pTab!=0 ){ + /* Mark the table as Ephemeral prior to deleting it, so that the + ** sqlite3DeleteTable() routine will know that it is not stored in + ** the schema. */ + pTab->tabFlags |= TF_Ephemeral; + sqlite3DeleteTable(db, pTab); + pMod->pEpoTab = 0; + } +} + +/* +** Return the ON CONFLICT resolution mode in effect for the virtual +** table update operation currently in progress. +** +** The results of this routine are undefined unless it is called from +** within an xUpdate method. +*/ +SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *db){ + static const unsigned char aMap[] = { + SQLITE_ROLLBACK, SQLITE_ABORT, SQLITE_FAIL, SQLITE_IGNORE, SQLITE_REPLACE + }; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + assert( OE_Rollback==1 && OE_Abort==2 && OE_Fail==3 ); + assert( OE_Ignore==4 && OE_Replace==5 ); + assert( db->vtabOnConflict>=1 && db->vtabOnConflict<=5 ); + return (int)aMap[db->vtabOnConflict-1]; +} + +/* +** Call from within the xCreate() or xConnect() methods to provide +** the SQLite core with additional information about the behavior +** of the virtual table being implemented. +*/ +SQLITE_API int sqlite3_vtab_config(sqlite3 *db, int op, ...){ + va_list ap; + int rc = SQLITE_OK; + VtabCtx *p; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + p = db->pVtabCtx; + if( !p ){ + rc = SQLITE_MISUSE_BKPT; + }else{ + assert( p->pTab==0 || IsVirtual(p->pTab) ); + va_start(ap, op); + switch( op ){ + case SQLITE_VTAB_CONSTRAINT_SUPPORT: { + p->pVTable->bConstraint = (u8)va_arg(ap, int); + break; + } + case SQLITE_VTAB_INNOCUOUS: { + p->pVTable->eVtabRisk = SQLITE_VTABRISK_Low; + break; + } + case SQLITE_VTAB_DIRECTONLY: { + p->pVTable->eVtabRisk = SQLITE_VTABRISK_High; + break; + } + case SQLITE_VTAB_USES_ALL_SCHEMAS: { + p->pVTable->bAllSchemas = 1; + break; + } + default: { + rc = SQLITE_MISUSE_BKPT; + break; + } + } + va_end(ap); + } + + if( rc!=SQLITE_OK ) sqlite3Error(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +/************** End of vtab.c ************************************************/ +/************** Begin file wherecode.c ***************************************/ +/* +** 2015-06-06 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This module contains C code that generates VDBE code used to process +** the WHERE clause of SQL statements. +** +** This file was split off from where.c on 2015-06-06 in order to reduce the +** size of where.c and make it easier to edit. This file contains the routines +** that actually generate the bulk of the WHERE loop code. The original where.c +** file retains the code that does query planning and analysis. +*/ +/* #include "sqliteInt.h" */ +/************** Include whereInt.h in the middle of wherecode.c **************/ +/************** Begin file whereInt.h ****************************************/ +/* +** 2013-11-12 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains structure and macro definitions for the query +** planner logic in "where.c". These definitions are broken out into +** a separate source file for easier editing. +*/ +#ifndef SQLITE_WHEREINT_H +#define SQLITE_WHEREINT_H + + +/* Forward references +*/ +typedef struct WhereClause WhereClause; +typedef struct WhereMaskSet WhereMaskSet; +typedef struct WhereOrInfo WhereOrInfo; +typedef struct WhereAndInfo WhereAndInfo; +typedef struct WhereLevel WhereLevel; +typedef struct WhereLoop WhereLoop; +typedef struct WherePath WherePath; +typedef struct WhereTerm WhereTerm; +typedef struct WhereLoopBuilder WhereLoopBuilder; +typedef struct WhereScan WhereScan; +typedef struct WhereOrCost WhereOrCost; +typedef struct WhereOrSet WhereOrSet; +typedef struct WhereMemBlock WhereMemBlock; +typedef struct WhereRightJoin WhereRightJoin; + +/* +** This object is a header on a block of allocated memory that will be +** automatically freed when its WInfo object is destructed. +*/ +struct WhereMemBlock { + WhereMemBlock *pNext; /* Next block in the chain */ + u64 sz; /* Bytes of space */ +}; + +/* +** Extra information attached to a WhereLevel that is a RIGHT JOIN. +*/ +struct WhereRightJoin { + int iMatch; /* Cursor used to determine prior matched rows */ + int regBloom; /* Bloom filter for iRJMatch */ + int regReturn; /* Return register for the interior subroutine */ + int addrSubrtn; /* Starting address for the interior subroutine */ + int endSubrtn; /* The last opcode in the interior subroutine */ +}; + +/* +** This object contains information needed to implement a single nested +** loop in WHERE clause. +** +** Contrast this object with WhereLoop. This object describes the +** implementation of the loop. WhereLoop describes the algorithm. +** This object contains a pointer to the WhereLoop algorithm as one of +** its elements. +** +** The WhereInfo object contains a single instance of this object for +** each term in the FROM clause (which is to say, for each of the +** nested loops as implemented). The order of WhereLevel objects determines +** the loop nested order, with WhereInfo.a[0] being the outer loop and +** WhereInfo.a[WhereInfo.nLevel-1] being the inner loop. +*/ +struct WhereLevel { + int iLeftJoin; /* Memory cell used to implement LEFT OUTER JOIN */ + int iTabCur; /* The VDBE cursor used to access the table */ + int iIdxCur; /* The VDBE cursor used to access pIdx */ + int addrBrk; /* Jump here to break out of the loop */ + int addrNxt; /* Jump here to start the next IN combination */ + int addrSkip; /* Jump here for next iteration of skip-scan */ + int addrCont; /* Jump here to continue with the next loop cycle */ + int addrFirst; /* First instruction of interior of the loop */ + int addrBody; /* Beginning of the body of this loop */ + int regBignull; /* big-null flag reg. True if a NULL-scan is needed */ + int addrBignull; /* Jump here for next part of big-null scan */ +#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS + u32 iLikeRepCntr; /* LIKE range processing counter register (times 2) */ + int addrLikeRep; /* LIKE range processing address */ +#endif + int regFilter; /* Bloom filter */ + WhereRightJoin *pRJ; /* Extra information for RIGHT JOIN */ + u8 iFrom; /* Which entry in the FROM clause */ + u8 op, p3, p5; /* Opcode, P3 & P5 of the opcode that ends the loop */ + int p1, p2; /* Operands of the opcode used to end the loop */ + union { /* Information that depends on pWLoop->wsFlags */ + struct { + int nIn; /* Number of entries in aInLoop[] */ + struct InLoop { + int iCur; /* The VDBE cursor used by this IN operator */ + int addrInTop; /* Top of the IN loop */ + int iBase; /* Base register of multi-key index record */ + int nPrefix; /* Number of prior entries in the key */ + u8 eEndLoopOp; /* IN Loop terminator. OP_Next or OP_Prev */ + } *aInLoop; /* Information about each nested IN operator */ + } in; /* Used when pWLoop->wsFlags&WHERE_IN_ABLE */ + Index *pCoveringIdx; /* Possible covering index for WHERE_MULTI_OR */ + } u; + struct WhereLoop *pWLoop; /* The selected WhereLoop object */ + Bitmask notReady; /* FROM entries not usable at this level */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrVisit; /* Address at which row is visited */ +#endif +}; + +/* +** Each instance of this object represents an algorithm for evaluating one +** term of a join. Every term of the FROM clause will have at least +** one corresponding WhereLoop object (unless INDEXED BY constraints +** prevent a query solution - which is an error) and many terms of the +** FROM clause will have multiple WhereLoop objects, each describing a +** potential way of implementing that FROM-clause term, together with +** dependencies and cost estimates for using the chosen algorithm. +** +** Query planning consists of building up a collection of these WhereLoop +** objects, then computing a particular sequence of WhereLoop objects, with +** one WhereLoop object per FROM clause term, that satisfy all dependencies +** and that minimize the overall cost. +*/ +struct WhereLoop { + Bitmask prereq; /* Bitmask of other loops that must run first */ + Bitmask maskSelf; /* Bitmask identifying table iTab */ +#ifdef SQLITE_DEBUG + char cId; /* Symbolic ID of this loop for debugging use */ +#endif + u8 iTab; /* Position in FROM clause of table for this loop */ + u8 iSortIdx; /* Sorting index number. 0==None */ + LogEst rSetup; /* One-time setup cost (ex: create transient index) */ + LogEst rRun; /* Cost of running each loop */ + LogEst nOut; /* Estimated number of output rows */ + union { + struct { /* Information for internal btree tables */ + u16 nEq; /* Number of equality constraints */ + u16 nBtm; /* Size of BTM vector */ + u16 nTop; /* Size of TOP vector */ + u16 nDistinctCol; /* Index columns used to sort for DISTINCT */ + Index *pIndex; /* Index used, or NULL */ + } btree; + struct { /* Information for virtual tables */ + int idxNum; /* Index number */ + u32 needFree : 1; /* True if sqlite3_free(idxStr) is needed */ + u32 bOmitOffset : 1; /* True to let virtual table handle offset */ + i8 isOrdered; /* True if satisfies ORDER BY */ + u16 omitMask; /* Terms that may be omitted */ + char *idxStr; /* Index identifier string */ + u32 mHandleIn; /* Terms to handle as IN(...) instead of == */ + } vtab; + } u; + u32 wsFlags; /* WHERE_* flags describing the plan */ + u16 nLTerm; /* Number of entries in aLTerm[] */ + u16 nSkip; /* Number of NULL aLTerm[] entries */ + /**** whereLoopXfer() copies fields above ***********************/ +# define WHERE_LOOP_XFER_SZ offsetof(WhereLoop,nLSlot) + u16 nLSlot; /* Number of slots allocated for aLTerm[] */ + WhereTerm **aLTerm; /* WhereTerms used */ + WhereLoop *pNextLoop; /* Next WhereLoop object in the WhereClause */ + WhereTerm *aLTermSpace[3]; /* Initial aLTerm[] space */ +}; + +/* This object holds the prerequisites and the cost of running a +** subquery on one operand of an OR operator in the WHERE clause. +** See WhereOrSet for additional information +*/ +struct WhereOrCost { + Bitmask prereq; /* Prerequisites */ + LogEst rRun; /* Cost of running this subquery */ + LogEst nOut; /* Number of outputs for this subquery */ +}; + +/* The WhereOrSet object holds a set of possible WhereOrCosts that +** correspond to the subquery(s) of OR-clause processing. Only the +** best N_OR_COST elements are retained. +*/ +#define N_OR_COST 3 +struct WhereOrSet { + u16 n; /* Number of valid a[] entries */ + WhereOrCost a[N_OR_COST]; /* Set of best costs */ +}; + +/* +** Each instance of this object holds a sequence of WhereLoop objects +** that implement some or all of a query plan. +** +** Think of each WhereLoop object as a node in a graph with arcs +** showing dependencies and costs for travelling between nodes. (That is +** not a completely accurate description because WhereLoop costs are a +** vector, not a scalar, and because dependencies are many-to-one, not +** one-to-one as are graph nodes. But it is a useful visualization aid.) +** Then a WherePath object is a path through the graph that visits some +** or all of the WhereLoop objects once. +** +** The "solver" works by creating the N best WherePath objects of length +** 1. Then using those as a basis to compute the N best WherePath objects +** of length 2. And so forth until the length of WherePaths equals the +** number of nodes in the FROM clause. The best (lowest cost) WherePath +** at the end is the chosen query plan. +*/ +struct WherePath { + Bitmask maskLoop; /* Bitmask of all WhereLoop objects in this path */ + Bitmask revLoop; /* aLoop[]s that should be reversed for ORDER BY */ + LogEst nRow; /* Estimated number of rows generated by this path */ + LogEst rCost; /* Total cost of this path */ + LogEst rUnsorted; /* Total cost of this path ignoring sorting costs */ + i8 isOrdered; /* No. of ORDER BY terms satisfied. -1 for unknown */ + WhereLoop **aLoop; /* Array of WhereLoop objects implementing this path */ +}; + +/* +** The query generator uses an array of instances of this structure to +** help it analyze the subexpressions of the WHERE clause. Each WHERE +** clause subexpression is separated from the others by AND operators, +** usually, or sometimes subexpressions separated by OR. +** +** All WhereTerms are collected into a single WhereClause structure. +** The following identity holds: +** +** WhereTerm.pWC->a[WhereTerm.idx] == WhereTerm +** +** When a term is of the form: +** +** X <op> <expr> +** +** where X is a column name and <op> is one of certain operators, +** then WhereTerm.leftCursor and WhereTerm.u.leftColumn record the +** cursor number and column number for X. WhereTerm.eOperator records +** the <op> using a bitmask encoding defined by WO_xxx below. The +** use of a bitmask encoding for the operator allows us to search +** quickly for terms that match any of several different operators. +** +** A WhereTerm might also be two or more subterms connected by OR: +** +** (t1.X <op> <expr>) OR (t1.Y <op> <expr>) OR .... +** +** In this second case, wtFlag has the TERM_ORINFO bit set and eOperator==WO_OR +** and the WhereTerm.u.pOrInfo field points to auxiliary information that +** is collected about the OR clause. +** +** If a term in the WHERE clause does not match either of the two previous +** categories, then eOperator==0. The WhereTerm.pExpr field is still set +** to the original subexpression content and wtFlags is set up appropriately +** but no other fields in the WhereTerm object are meaningful. +** +** When eOperator!=0, prereqRight and prereqAll record sets of cursor numbers, +** but they do so indirectly. A single WhereMaskSet structure translates +** cursor number into bits and the translated bit is stored in the prereq +** fields. The translation is used in order to maximize the number of +** bits that will fit in a Bitmask. The VDBE cursor numbers might be +** spread out over the non-negative integers. For example, the cursor +** numbers might be 3, 8, 9, 10, 20, 23, 41, and 45. The WhereMaskSet +** translates these sparse cursor numbers into consecutive integers +** beginning with 0 in order to make the best possible use of the available +** bits in the Bitmask. So, in the example above, the cursor numbers +** would be mapped into integers 0 through 7. +** +** The number of terms in a join is limited by the number of bits +** in prereqRight and prereqAll. The default is 64 bits, hence SQLite +** is only able to process joins with 64 or fewer tables. +*/ +struct WhereTerm { + Expr *pExpr; /* Pointer to the subexpression that is this term */ + WhereClause *pWC; /* The clause this term is part of */ + LogEst truthProb; /* Probability of truth for this expression */ + u16 wtFlags; /* TERM_xxx bit flags. See below */ + u16 eOperator; /* A WO_xx value describing <op> */ + u8 nChild; /* Number of children that must disable us */ + u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */ + int iParent; /* Disable pWC->a[iParent] when this term disabled */ + int leftCursor; /* Cursor number of X in "X <op> <expr>" */ + union { + struct { + int leftColumn; /* Column number of X in "X <op> <expr>" */ + int iField; /* Field in (?,?,?) IN (SELECT...) vector */ + } x; /* Opcode other than OP_OR or OP_AND */ + WhereOrInfo *pOrInfo; /* Extra information if (eOperator & WO_OR)!=0 */ + WhereAndInfo *pAndInfo; /* Extra information if (eOperator& WO_AND)!=0 */ + } u; + Bitmask prereqRight; /* Bitmask of tables used by pExpr->pRight */ + Bitmask prereqAll; /* Bitmask of tables referenced by pExpr */ +}; + +/* +** Allowed values of WhereTerm.wtFlags +*/ +#define TERM_DYNAMIC 0x0001 /* Need to call sqlite3ExprDelete(db, pExpr) */ +#define TERM_VIRTUAL 0x0002 /* Added by the optimizer. Do not code */ +#define TERM_CODED 0x0004 /* This term is already coded */ +#define TERM_COPIED 0x0008 /* Has a child */ +#define TERM_ORINFO 0x0010 /* Need to free the WhereTerm.u.pOrInfo object */ +#define TERM_ANDINFO 0x0020 /* Need to free the WhereTerm.u.pAndInfo obj */ +#define TERM_OK 0x0040 /* Used during OR-clause processing */ +#define TERM_VNULL 0x0080 /* Manufactured x>NULL or x<=NULL term */ +#define TERM_LIKEOPT 0x0100 /* Virtual terms from the LIKE optimization */ +#define TERM_LIKECOND 0x0200 /* Conditionally this LIKE operator term */ +#define TERM_LIKE 0x0400 /* The original LIKE operator */ +#define TERM_IS 0x0800 /* Term.pExpr is an IS operator */ +#define TERM_VARSELECT 0x1000 /* Term.pExpr contains a correlated sub-query */ +#define TERM_HEURTRUTH 0x2000 /* Heuristic truthProb used */ +#ifdef SQLITE_ENABLE_STAT4 +# define TERM_HIGHTRUTH 0x4000 /* Term excludes few rows */ +#else +# define TERM_HIGHTRUTH 0 /* Only used with STAT4 */ +#endif +#define TERM_SLICE 0x8000 /* One slice of a row-value/vector comparison */ + +/* +** An instance of the WhereScan object is used as an iterator for locating +** terms in the WHERE clause that are useful to the query planner. +*/ +struct WhereScan { + WhereClause *pOrigWC; /* Original, innermost WhereClause */ + WhereClause *pWC; /* WhereClause currently being scanned */ + const char *zCollName; /* Required collating sequence, if not NULL */ + Expr *pIdxExpr; /* Search for this index expression */ + int k; /* Resume scanning at this->pWC->a[this->k] */ + u32 opMask; /* Acceptable operators */ + char idxaff; /* Must match this affinity, if zCollName!=NULL */ + unsigned char iEquiv; /* Current slot in aiCur[] and aiColumn[] */ + unsigned char nEquiv; /* Number of entries in aiCur[] and aiColumn[] */ + int aiCur[11]; /* Cursors in the equivalence class */ + i16 aiColumn[11]; /* Corresponding column number in the eq-class */ +}; + +/* +** An instance of the following structure holds all information about a +** WHERE clause. Mostly this is a container for one or more WhereTerms. +** +** Explanation of pOuter: For a WHERE clause of the form +** +** a AND ((b AND c) OR (d AND e)) AND f +** +** There are separate WhereClause objects for the whole clause and for +** the subclauses "(b AND c)" and "(d AND e)". The pOuter field of the +** subclauses points to the WhereClause object for the whole clause. +*/ +struct WhereClause { + WhereInfo *pWInfo; /* WHERE clause processing context */ + WhereClause *pOuter; /* Outer conjunction */ + u8 op; /* Split operator. TK_AND or TK_OR */ + u8 hasOr; /* True if any a[].eOperator is WO_OR */ + int nTerm; /* Number of terms */ + int nSlot; /* Number of entries in a[] */ + int nBase; /* Number of terms through the last non-Virtual */ + WhereTerm *a; /* Each a[] describes a term of the WHERE clause */ +#if defined(SQLITE_SMALL_STACK) + WhereTerm aStatic[1]; /* Initial static space for a[] */ +#else + WhereTerm aStatic[8]; /* Initial static space for a[] */ +#endif +}; + +/* +** A WhereTerm with eOperator==WO_OR has its u.pOrInfo pointer set to +** a dynamically allocated instance of the following structure. +*/ +struct WhereOrInfo { + WhereClause wc; /* Decomposition into subterms */ + Bitmask indexable; /* Bitmask of all indexable tables in the clause */ +}; + +/* +** A WhereTerm with eOperator==WO_AND has its u.pAndInfo pointer set to +** a dynamically allocated instance of the following structure. +*/ +struct WhereAndInfo { + WhereClause wc; /* The subexpression broken out */ +}; + +/* +** An instance of the following structure keeps track of a mapping +** between VDBE cursor numbers and bits of the bitmasks in WhereTerm. +** +** The VDBE cursor numbers are small integers contained in +** SrcItem.iCursor and Expr.iTable fields. For any given WHERE +** clause, the cursor numbers might not begin with 0 and they might +** contain gaps in the numbering sequence. But we want to make maximum +** use of the bits in our bitmasks. This structure provides a mapping +** from the sparse cursor numbers into consecutive integers beginning +** with 0. +** +** If WhereMaskSet.ix[A]==B it means that The A-th bit of a Bitmask +** corresponds VDBE cursor number B. The A-th bit of a bitmask is 1<<A. +** +** For example, if the WHERE clause expression used these VDBE +** cursors: 4, 5, 8, 29, 57, 73. Then the WhereMaskSet structure +** would map those cursor numbers into bits 0 through 5. +** +** Note that the mapping is not necessarily ordered. In the example +** above, the mapping might go like this: 4->3, 5->1, 8->2, 29->0, +** 57->5, 73->4. Or one of 719 other combinations might be used. It +** does not really matter. What is important is that sparse cursor +** numbers all get mapped into bit numbers that begin with 0 and contain +** no gaps. +*/ +struct WhereMaskSet { + int bVarSelect; /* Used by sqlite3WhereExprUsage() */ + int n; /* Number of assigned cursor values */ + int ix[BMS]; /* Cursor assigned to each bit */ +}; + +/* +** This object is a convenience wrapper holding all information needed +** to construct WhereLoop objects for a particular query. +*/ +struct WhereLoopBuilder { + WhereInfo *pWInfo; /* Information about this WHERE */ + WhereClause *pWC; /* WHERE clause terms */ + WhereLoop *pNew; /* Template WhereLoop */ + WhereOrSet *pOrSet; /* Record best loops here, if not NULL */ +#ifdef SQLITE_ENABLE_STAT4 + UnpackedRecord *pRec; /* Probe for stat4 (if required) */ + int nRecValid; /* Number of valid fields currently in pRec */ +#endif + unsigned char bldFlags1; /* First set of SQLITE_BLDF_* flags */ + unsigned char bldFlags2; /* Second set of SQLITE_BLDF_* flags */ + unsigned int iPlanLimit; /* Search limiter */ +}; + +/* Allowed values for WhereLoopBuider.bldFlags */ +#define SQLITE_BLDF1_INDEXED 0x0001 /* An index is used */ +#define SQLITE_BLDF1_UNIQUE 0x0002 /* All keys of a UNIQUE index used */ + +#define SQLITE_BLDF2_2NDPASS 0x0004 /* Second builder pass needed */ + +/* The WhereLoopBuilder.iPlanLimit is used to limit the number of +** index+constraint combinations the query planner will consider for a +** particular query. If this parameter is unlimited, then certain +** pathological queries can spend excess time in the sqlite3WhereBegin() +** routine. The limit is high enough that is should not impact real-world +** queries. +** +** SQLITE_QUERY_PLANNER_LIMIT is the baseline limit. The limit is +** increased by SQLITE_QUERY_PLANNER_LIMIT_INCR before each term of the FROM +** clause is processed, so that every table in a join is guaranteed to be +** able to propose a some index+constraint combinations even if the initial +** baseline limit was exhausted by prior tables of the join. +*/ +#ifndef SQLITE_QUERY_PLANNER_LIMIT +# define SQLITE_QUERY_PLANNER_LIMIT 20000 +#endif +#ifndef SQLITE_QUERY_PLANNER_LIMIT_INCR +# define SQLITE_QUERY_PLANNER_LIMIT_INCR 1000 +#endif + +/* +** The WHERE clause processing routine has two halves. The +** first part does the start of the WHERE loop and the second +** half does the tail of the WHERE loop. An instance of +** this structure is returned by the first half and passed +** into the second half to give some continuity. +** +** An instance of this object holds the complete state of the query +** planner. +*/ +struct WhereInfo { + Parse *pParse; /* Parsing and code generating context */ + SrcList *pTabList; /* List of tables in the join */ + ExprList *pOrderBy; /* The ORDER BY clause or NULL */ + ExprList *pResultSet; /* Result set of the query */ +#if WHERETRACE_ENABLED + Expr *pWhere; /* The complete WHERE clause */ +#endif + Select *pSelect; /* The entire SELECT statement containing WHERE */ + int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ + int iContinue; /* Jump here to continue with next record */ + int iBreak; /* Jump here to break out of the loop */ + int savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */ + u16 wctrlFlags; /* Flags originally passed to sqlite3WhereBegin() */ + LogEst iLimit; /* LIMIT if wctrlFlags has WHERE_USE_LIMIT */ + u8 nLevel; /* Number of nested loop */ + i8 nOBSat; /* Number of ORDER BY terms satisfied by indices */ + u8 eOnePass; /* ONEPASS_OFF, or _SINGLE, or _MULTI */ + u8 eDistinct; /* One of the WHERE_DISTINCT_* values */ + unsigned bDeferredSeek :1; /* Uses OP_DeferredSeek */ + unsigned untestedTerms :1; /* Not all WHERE terms resolved by outer loop */ + unsigned bOrderedInnerLoop:1;/* True if only the inner-most loop is ordered */ + unsigned sorted :1; /* True if really sorted (not just grouped) */ + LogEst nRowOut; /* Estimated number of output rows */ + int iTop; /* The very beginning of the WHERE loop */ + int iEndWhere; /* End of the WHERE clause itself */ + WhereLoop *pLoops; /* List of all WhereLoop objects */ + WhereMemBlock *pMemToFree;/* Memory to free when this object destroyed */ + Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ + WhereClause sWC; /* Decomposition of the WHERE clause */ + WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */ + WhereLevel a[1]; /* Information about each nest loop in WHERE */ +}; + +/* +** Private interfaces - callable only by other where.c routines. +** +** where.c: +*/ +SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet*,int); +#ifdef WHERETRACE_ENABLED +SQLITE_PRIVATE void sqlite3WhereClausePrint(WhereClause *pWC); +SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm); +SQLITE_PRIVATE void sqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC); +#endif +SQLITE_PRIVATE WhereTerm *sqlite3WhereFindTerm( + WhereClause *pWC, /* The WHERE clause to be searched */ + int iCur, /* Cursor number of LHS */ + int iColumn, /* Column number of LHS */ + Bitmask notReady, /* RHS must not overlap with this mask */ + u32 op, /* Mask of WO_xx values describing operator */ + Index *pIdx /* Must be compatible with this index, if not NULL */ +); +SQLITE_PRIVATE void *sqlite3WhereMalloc(WhereInfo *pWInfo, u64 nByte); +SQLITE_PRIVATE void *sqlite3WhereRealloc(WhereInfo *pWInfo, void *pOld, u64 nByte); + +/* wherecode.c: */ +#ifndef SQLITE_OMIT_EXPLAIN +SQLITE_PRIVATE int sqlite3WhereExplainOneScan( + Parse *pParse, /* Parse context */ + SrcList *pTabList, /* Table list this loop refers to */ + WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ + u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ +); +SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter( + const Parse *pParse, /* Parse context */ + const WhereInfo *pWInfo, /* WHERE clause */ + const WhereLevel *pLevel /* Bloom filter on this level */ +); +#else +# define sqlite3WhereExplainOneScan(u,v,w,x) 0 +# define sqlite3WhereExplainBloomFilter(u,v,w) 0 +#endif /* SQLITE_OMIT_EXPLAIN */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +SQLITE_PRIVATE void sqlite3WhereAddScanStatus( + Vdbe *v, /* Vdbe to add scanstatus entry to */ + SrcList *pSrclist, /* FROM clause pLvl reads data from */ + WhereLevel *pLvl, /* Level to add scanstatus() entry for */ + int addrExplain /* Address of OP_Explain (or 0) */ +); +#else +# define sqlite3WhereAddScanStatus(a, b, c, d) ((void)d) +#endif +SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( + Parse *pParse, /* Parsing context */ + Vdbe *v, /* Prepared statement under construction */ + WhereInfo *pWInfo, /* Complete information about the WHERE clause */ + int iLevel, /* Which level of pWInfo->a[] should be coded */ + WhereLevel *pLevel, /* The current level pointer */ + Bitmask notReady /* Which tables are currently available */ +); +SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( + WhereInfo *pWInfo, + int iLevel, + WhereLevel *pLevel +); + +/* whereexpr.c: */ +SQLITE_PRIVATE void sqlite3WhereClauseInit(WhereClause*,WhereInfo*); +SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause*); +SQLITE_PRIVATE void sqlite3WhereSplit(WhereClause*,Expr*,u8); +SQLITE_PRIVATE void sqlite3WhereAddLimit(WhereClause*, Select*); +SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet*, Expr*); +SQLITE_PRIVATE Bitmask sqlite3WhereExprUsageNN(WhereMaskSet*, Expr*); +SQLITE_PRIVATE Bitmask sqlite3WhereExprListUsage(WhereMaskSet*, ExprList*); +SQLITE_PRIVATE void sqlite3WhereExprAnalyze(SrcList*, WhereClause*); +SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*); + + + + + +/* +** Bitmasks for the operators on WhereTerm objects. These are all +** operators that are of interest to the query planner. An +** OR-ed combination of these values can be used when searching for +** particular WhereTerms within a WhereClause. +** +** Value constraints: +** WO_EQ == SQLITE_INDEX_CONSTRAINT_EQ +** WO_LT == SQLITE_INDEX_CONSTRAINT_LT +** WO_LE == SQLITE_INDEX_CONSTRAINT_LE +** WO_GT == SQLITE_INDEX_CONSTRAINT_GT +** WO_GE == SQLITE_INDEX_CONSTRAINT_GE +*/ +#define WO_IN 0x0001 +#define WO_EQ 0x0002 +#define WO_LT (WO_EQ<<(TK_LT-TK_EQ)) +#define WO_LE (WO_EQ<<(TK_LE-TK_EQ)) +#define WO_GT (WO_EQ<<(TK_GT-TK_EQ)) +#define WO_GE (WO_EQ<<(TK_GE-TK_EQ)) +#define WO_AUX 0x0040 /* Op useful to virtual tables only */ +#define WO_IS 0x0080 +#define WO_ISNULL 0x0100 +#define WO_OR 0x0200 /* Two or more OR-connected terms */ +#define WO_AND 0x0400 /* Two or more AND-connected terms */ +#define WO_EQUIV 0x0800 /* Of the form A==B, both columns */ +#define WO_NOOP 0x1000 /* This term does not restrict search space */ +#define WO_ROWVAL 0x2000 /* A row-value term */ + +#define WO_ALL 0x3fff /* Mask of all possible WO_* values */ +#define WO_SINGLE 0x01ff /* Mask of all non-compound WO_* values */ + +/* +** These are definitions of bits in the WhereLoop.wsFlags field. +** The particular combination of bits in each WhereLoop help to +** determine the algorithm that WhereLoop represents. +*/ +#define WHERE_COLUMN_EQ 0x00000001 /* x=EXPR */ +#define WHERE_COLUMN_RANGE 0x00000002 /* x<EXPR and/or x>EXPR */ +#define WHERE_COLUMN_IN 0x00000004 /* x IN (...) */ +#define WHERE_COLUMN_NULL 0x00000008 /* x IS NULL */ +#define WHERE_CONSTRAINT 0x0000000f /* Any of the WHERE_COLUMN_xxx values */ +#define WHERE_TOP_LIMIT 0x00000010 /* x<EXPR or x<=EXPR constraint */ +#define WHERE_BTM_LIMIT 0x00000020 /* x>EXPR or x>=EXPR constraint */ +#define WHERE_BOTH_LIMIT 0x00000030 /* Both x>EXPR and x<EXPR */ +#define WHERE_IDX_ONLY 0x00000040 /* Use index only - omit table */ +#define WHERE_IPK 0x00000100 /* x is the INTEGER PRIMARY KEY */ +#define WHERE_INDEXED 0x00000200 /* WhereLoop.u.btree.pIndex is valid */ +#define WHERE_VIRTUALTABLE 0x00000400 /* WhereLoop.u.vtab is valid */ +#define WHERE_IN_ABLE 0x00000800 /* Able to support an IN operator */ +#define WHERE_ONEROW 0x00001000 /* Selects no more than one row */ +#define WHERE_MULTI_OR 0x00002000 /* OR using multiple indices */ +#define WHERE_AUTO_INDEX 0x00004000 /* Uses an ephemeral index */ +#define WHERE_SKIPSCAN 0x00008000 /* Uses the skip-scan algorithm */ +#define WHERE_UNQ_WANTED 0x00010000 /* WHERE_ONEROW would have been helpful*/ +#define WHERE_PARTIALIDX 0x00020000 /* The automatic index is partial */ +#define WHERE_IN_EARLYOUT 0x00040000 /* Perhaps quit IN loops early */ +#define WHERE_BIGNULL_SORT 0x00080000 /* Column nEq of index is BIGNULL */ +#define WHERE_IN_SEEKSCAN 0x00100000 /* Seek-scan optimization for IN */ +#define WHERE_TRANSCONS 0x00200000 /* Uses a transitive constraint */ +#define WHERE_BLOOMFILTER 0x00400000 /* Consider using a Bloom-filter */ +#define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */ +#define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */ +#define WHERE_VIEWSCAN 0x02000000 /* A full-scan of a VIEW or subquery */ +#define WHERE_EXPRIDX 0x04000000 /* Uses an index-on-expressions */ + +#endif /* !defined(SQLITE_WHEREINT_H) */ + +/************** End of whereInt.h ********************************************/ +/************** Continuing where we left off in wherecode.c ******************/ + +#ifndef SQLITE_OMIT_EXPLAIN + +/* +** Return the name of the i-th column of the pIdx index. +*/ +static const char *explainIndexColumnName(Index *pIdx, int i){ + i = pIdx->aiColumn[i]; + if( i==XN_EXPR ) return "<expr>"; + if( i==XN_ROWID ) return "rowid"; + return pIdx->pTable->aCol[i].zCnName; +} + +/* +** This routine is a helper for explainIndexRange() below +** +** pStr holds the text of an expression that we are building up one term +** at a time. This routine adds a new term to the end of the expression. +** Terms are separated by AND so add the "AND" text for second and subsequent +** terms only. +*/ +static void explainAppendTerm( + StrAccum *pStr, /* The text expression being built */ + Index *pIdx, /* Index to read column names from */ + int nTerm, /* Number of terms */ + int iTerm, /* Zero-based index of first term. */ + int bAnd, /* Non-zero to append " AND " */ + const char *zOp /* Name of the operator */ +){ + int i; + + assert( nTerm>=1 ); + if( bAnd ) sqlite3_str_append(pStr, " AND ", 5); + + if( nTerm>1 ) sqlite3_str_append(pStr, "(", 1); + for(i=0; i<nTerm; i++){ + if( i ) sqlite3_str_append(pStr, ",", 1); + sqlite3_str_appendall(pStr, explainIndexColumnName(pIdx, iTerm+i)); + } + if( nTerm>1 ) sqlite3_str_append(pStr, ")", 1); + + sqlite3_str_append(pStr, zOp, 1); + + if( nTerm>1 ) sqlite3_str_append(pStr, "(", 1); + for(i=0; i<nTerm; i++){ + if( i ) sqlite3_str_append(pStr, ",", 1); + sqlite3_str_append(pStr, "?", 1); + } + if( nTerm>1 ) sqlite3_str_append(pStr, ")", 1); +} + +/* +** Argument pLevel describes a strategy for scanning table pTab. This +** function appends text to pStr that describes the subset of table +** rows scanned by the strategy in the form of an SQL expression. +** +** For example, if the query: +** +** SELECT * FROM t1 WHERE a=1 AND b>2; +** +** is run and there is an index on (a, b), then this function returns a +** string similar to: +** +** "a=? AND b>?" +*/ +static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){ + Index *pIndex = pLoop->u.btree.pIndex; + u16 nEq = pLoop->u.btree.nEq; + u16 nSkip = pLoop->nSkip; + int i, j; + + if( nEq==0 && (pLoop->wsFlags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))==0 ) return; + sqlite3_str_append(pStr, " (", 2); + for(i=0; i<nEq; i++){ + const char *z = explainIndexColumnName(pIndex, i); + if( i ) sqlite3_str_append(pStr, " AND ", 5); + sqlite3_str_appendf(pStr, i>=nSkip ? "%s=?" : "ANY(%s)", z); + } + + j = i; + if( pLoop->wsFlags&WHERE_BTM_LIMIT ){ + explainAppendTerm(pStr, pIndex, pLoop->u.btree.nBtm, j, i, ">"); + i = 1; + } + if( pLoop->wsFlags&WHERE_TOP_LIMIT ){ + explainAppendTerm(pStr, pIndex, pLoop->u.btree.nTop, j, i, "<"); + } + sqlite3_str_append(pStr, ")", 1); +} + +/* +** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN +** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG +** was defined at compile-time. If it is not a no-op, a single OP_Explain +** opcode is added to the output to describe the table scan strategy in pLevel. +** +** If an OP_Explain opcode is added to the VM, its address is returned. +** Otherwise, if no OP_Explain is coded, zero is returned. +*/ +SQLITE_PRIVATE int sqlite3WhereExplainOneScan( + Parse *pParse, /* Parse context */ + SrcList *pTabList, /* Table list this loop refers to */ + WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ + u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ +){ + int ret = 0; +#if !defined(SQLITE_DEBUG) + if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) +#endif + { + SrcItem *pItem = &pTabList->a[pLevel->iFrom]; + Vdbe *v = pParse->pVdbe; /* VM being constructed */ + sqlite3 *db = pParse->db; /* Database handle */ + int isSearch; /* True for a SEARCH. False for SCAN. */ + WhereLoop *pLoop; /* The controlling WhereLoop object */ + u32 flags; /* Flags that describe this loop */ + char *zMsg; /* Text to add to EQP output */ + StrAccum str; /* EQP output string */ + char zBuf[100]; /* Initial space for EQP output string */ + + pLoop = pLevel->pWLoop; + flags = pLoop->wsFlags; + if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_OR_SUBCLAUSE) ) return 0; + + isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 + || ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0)) + || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX)); + + sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); + str.printfFlags = SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&str, "%s %S", isSearch ? "SEARCH" : "SCAN", pItem); + if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){ + const char *zFmt = 0; + Index *pIdx; + + assert( pLoop->u.btree.pIndex!=0 ); + pIdx = pLoop->u.btree.pIndex; + assert( !(flags&WHERE_AUTO_INDEX) || (flags&WHERE_IDX_ONLY) ); + if( !HasRowid(pItem->pTab) && IsPrimaryKeyIndex(pIdx) ){ + if( isSearch ){ + zFmt = "PRIMARY KEY"; + } + }else if( flags & WHERE_PARTIALIDX ){ + zFmt = "AUTOMATIC PARTIAL COVERING INDEX"; + }else if( flags & WHERE_AUTO_INDEX ){ + zFmt = "AUTOMATIC COVERING INDEX"; + }else if( flags & WHERE_IDX_ONLY ){ + zFmt = "COVERING INDEX %s"; + }else{ + zFmt = "INDEX %s"; + } + if( zFmt ){ + sqlite3_str_append(&str, " USING ", 7); + sqlite3_str_appendf(&str, zFmt, pIdx->zName); + explainIndexRange(&str, pLoop); + } + }else if( (flags & WHERE_IPK)!=0 && (flags & WHERE_CONSTRAINT)!=0 ){ + char cRangeOp; +#if 0 /* Better output, but breaks many tests */ + const Table *pTab = pItem->pTab; + const char *zRowid = pTab->iPKey>=0 ? pTab->aCol[pTab->iPKey].zCnName: + "rowid"; +#else + const char *zRowid = "rowid"; +#endif + sqlite3_str_appendf(&str, " USING INTEGER PRIMARY KEY (%s", zRowid); + if( flags&(WHERE_COLUMN_EQ|WHERE_COLUMN_IN) ){ + cRangeOp = '='; + }else if( (flags&WHERE_BOTH_LIMIT)==WHERE_BOTH_LIMIT ){ + sqlite3_str_appendf(&str, ">? AND %s", zRowid); + cRangeOp = '<'; + }else if( flags&WHERE_BTM_LIMIT ){ + cRangeOp = '>'; + }else{ + assert( flags&WHERE_TOP_LIMIT); + cRangeOp = '<'; + } + sqlite3_str_appendf(&str, "%c?)", cRangeOp); + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + else if( (flags & WHERE_VIRTUALTABLE)!=0 ){ + sqlite3_str_appendf(&str, " VIRTUAL TABLE INDEX %d:%s", + pLoop->u.vtab.idxNum, pLoop->u.vtab.idxStr); + } +#endif + if( pItem->fg.jointype & JT_LEFT ){ + sqlite3_str_appendf(&str, " LEFT-JOIN"); + } +#ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS + if( pLoop->nOut>=10 ){ + sqlite3_str_appendf(&str, " (~%llu rows)", + sqlite3LogEstToInt(pLoop->nOut)); + }else{ + sqlite3_str_append(&str, " (~1 row)", 9); + } +#endif + zMsg = sqlite3StrAccumFinish(&str); + sqlite3ExplainBreakpoint("",zMsg); + ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), + pParse->addrExplain, 0, zMsg,P4_DYNAMIC); + } + return ret; +} + +/* +** Add a single OP_Explain opcode that describes a Bloom filter. +** +** Or if not processing EXPLAIN QUERY PLAN and not in a SQLITE_DEBUG and/or +** SQLITE_ENABLE_STMT_SCANSTATUS build, then OP_Explain opcodes are not +** required and this routine is a no-op. +** +** If an OP_Explain opcode is added to the VM, its address is returned. +** Otherwise, if no OP_Explain is coded, zero is returned. +*/ +SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter( + const Parse *pParse, /* Parse context */ + const WhereInfo *pWInfo, /* WHERE clause */ + const WhereLevel *pLevel /* Bloom filter on this level */ +){ + int ret = 0; + SrcItem *pItem = &pWInfo->pTabList->a[pLevel->iFrom]; + Vdbe *v = pParse->pVdbe; /* VM being constructed */ + sqlite3 *db = pParse->db; /* Database handle */ + char *zMsg; /* Text to add to EQP output */ + int i; /* Loop counter */ + WhereLoop *pLoop; /* The where loop */ + StrAccum str; /* EQP output string */ + char zBuf[100]; /* Initial space for EQP output string */ + + sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); + str.printfFlags = SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&str, "BLOOM FILTER ON %S (", pItem); + pLoop = pLevel->pWLoop; + if( pLoop->wsFlags & WHERE_IPK ){ + const Table *pTab = pItem->pTab; + if( pTab->iPKey>=0 ){ + sqlite3_str_appendf(&str, "%s=?", pTab->aCol[pTab->iPKey].zCnName); + }else{ + sqlite3_str_appendf(&str, "rowid=?"); + } + }else{ + for(i=pLoop->nSkip; i<pLoop->u.btree.nEq; i++){ + const char *z = explainIndexColumnName(pLoop->u.btree.pIndex, i); + if( i>pLoop->nSkip ) sqlite3_str_append(&str, " AND ", 5); + sqlite3_str_appendf(&str, "%s=?", z); + } + } + sqlite3_str_append(&str, ")", 1); + zMsg = sqlite3StrAccumFinish(&str); + ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), + pParse->addrExplain, 0, zMsg,P4_DYNAMIC); + + sqlite3VdbeScanStatus(v, sqlite3VdbeCurrentAddr(v)-1, 0, 0, 0, 0); + return ret; +} +#endif /* SQLITE_OMIT_EXPLAIN */ + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +/* +** Configure the VM passed as the first argument with an +** sqlite3_stmt_scanstatus() entry corresponding to the scan used to +** implement level pLvl. Argument pSrclist is a pointer to the FROM +** clause that the scan reads data from. +** +** If argument addrExplain is not 0, it must be the address of an +** OP_Explain instruction that describes the same loop. +*/ +SQLITE_PRIVATE void sqlite3WhereAddScanStatus( + Vdbe *v, /* Vdbe to add scanstatus entry to */ + SrcList *pSrclist, /* FROM clause pLvl reads data from */ + WhereLevel *pLvl, /* Level to add scanstatus() entry for */ + int addrExplain /* Address of OP_Explain (or 0) */ +){ + if( IS_STMT_SCANSTATUS( sqlite3VdbeDb(v) ) ){ + const char *zObj = 0; + WhereLoop *pLoop = pLvl->pWLoop; + int wsFlags = pLoop->wsFlags; + int viaCoroutine = 0; + + if( (wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){ + zObj = pLoop->u.btree.pIndex->zName; + }else{ + zObj = pSrclist->a[pLvl->iFrom].zName; + viaCoroutine = pSrclist->a[pLvl->iFrom].fg.viaCoroutine; + } + sqlite3VdbeScanStatus( + v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj + ); + + if( viaCoroutine==0 ){ + if( (wsFlags & (WHERE_MULTI_OR|WHERE_AUTO_INDEX))==0 ){ + sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iTabCur); + } + if( wsFlags & WHERE_INDEXED ){ + sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); + } + }else{ + int addr = pSrclist->a[pLvl->iFrom].addrFillSub; + VdbeOp *pOp = sqlite3VdbeGetOp(v, addr-1); + assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine ); + assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr ); + sqlite3VdbeScanStatusRange(v, addrExplain, addr, pOp->p2-1); + } + } +} +#endif + + +/* +** Disable a term in the WHERE clause. Except, do not disable the term +** if it controls a LEFT OUTER JOIN and it did not originate in the ON +** or USING clause of that join. +** +** Consider the term t2.z='ok' in the following queries: +** +** (1) SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.x WHERE t2.z='ok' +** (2) SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.x AND t2.z='ok' +** (3) SELECT * FROM t1, t2 WHERE t1.a=t2.x AND t2.z='ok' +** +** The t2.z='ok' is disabled in the in (2) because it originates +** in the ON clause. The term is disabled in (3) because it is not part +** of a LEFT OUTER JOIN. In (1), the term is not disabled. +** +** Disabling a term causes that term to not be tested in the inner loop +** of the join. Disabling is an optimization. When terms are satisfied +** by indices, we disable them to prevent redundant tests in the inner +** loop. We would get the correct results if nothing were ever disabled, +** but joins might run a little slower. The trick is to disable as much +** as we can without disabling too much. If we disabled in (1), we'd get +** the wrong answer. See ticket #813. +** +** If all the children of a term are disabled, then that term is also +** automatically disabled. In this way, terms get disabled if derived +** virtual terms are tested first. For example: +** +** x GLOB 'abc*' AND x>='abc' AND x<'acd' +** \___________/ \______/ \_____/ +** parent child1 child2 +** +** Only the parent term was in the original WHERE clause. The child1 +** and child2 terms were added by the LIKE optimization. If both of +** the virtual child terms are valid, then testing of the parent can be +** skipped. +** +** Usually the parent term is marked as TERM_CODED. But if the parent +** term was originally TERM_LIKE, then the parent gets TERM_LIKECOND instead. +** The TERM_LIKECOND marking indicates that the term should be coded inside +** a conditional such that is only evaluated on the second pass of a +** LIKE-optimization loop, when scanning BLOBs instead of strings. +*/ +static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ + int nLoop = 0; + assert( pTerm!=0 ); + while( (pTerm->wtFlags & TERM_CODED)==0 + && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_OuterON)) + && (pLevel->notReady & pTerm->prereqAll)==0 + ){ + if( nLoop && (pTerm->wtFlags & TERM_LIKE)!=0 ){ + pTerm->wtFlags |= TERM_LIKECOND; + }else{ + pTerm->wtFlags |= TERM_CODED; + } +#ifdef WHERETRACE_ENABLED + if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ + sqlite3DebugPrintf("DISABLE-"); + sqlite3WhereTermPrint(pTerm, (int)(pTerm - (pTerm->pWC->a))); + } +#endif + if( pTerm->iParent<0 ) break; + pTerm = &pTerm->pWC->a[pTerm->iParent]; + assert( pTerm!=0 ); + pTerm->nChild--; + if( pTerm->nChild!=0 ) break; + nLoop++; + } +} + +/* +** Code an OP_Affinity opcode to apply the column affinity string zAff +** to the n registers starting at base. +** +** As an optimization, SQLITE_AFF_BLOB and SQLITE_AFF_NONE entries (which +** are no-ops) at the beginning and end of zAff are ignored. If all entries +** in zAff are SQLITE_AFF_BLOB or SQLITE_AFF_NONE, then no code gets generated. +** +** This routine makes its own copy of zAff so that the caller is free +** to modify zAff after this routine returns. +*/ +static void codeApplyAffinity(Parse *pParse, int base, int n, char *zAff){ + Vdbe *v = pParse->pVdbe; + if( zAff==0 ){ + assert( pParse->db->mallocFailed ); + return; + } + assert( v!=0 ); + + /* Adjust base and n to skip over SQLITE_AFF_BLOB and SQLITE_AFF_NONE + ** entries at the beginning and end of the affinity string. + */ + assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB ); + while( n>0 && zAff[0]<=SQLITE_AFF_BLOB ){ + n--; + base++; + zAff++; + } + while( n>1 && zAff[n-1]<=SQLITE_AFF_BLOB ){ + n--; + } + + /* Code the OP_Affinity opcode if there is anything left to do. */ + if( n>0 ){ + sqlite3VdbeAddOp4(v, OP_Affinity, base, n, 0, zAff, n); + } +} + +/* +** Expression pRight, which is the RHS of a comparison operation, is +** either a vector of n elements or, if n==1, a scalar expression. +** Before the comparison operation, affinity zAff is to be applied +** to the pRight values. This function modifies characters within the +** affinity string to SQLITE_AFF_BLOB if either: +** +** * the comparison will be performed with no affinity, or +** * the affinity change in zAff is guaranteed not to change the value. +*/ +static void updateRangeAffinityStr( + Expr *pRight, /* RHS of comparison */ + int n, /* Number of vector elements in comparison */ + char *zAff /* Affinity string to modify */ +){ + int i; + for(i=0; i<n; i++){ + Expr *p = sqlite3VectorFieldSubexpr(pRight, i); + if( sqlite3CompareAffinity(p, zAff[i])==SQLITE_AFF_BLOB + || sqlite3ExprNeedsNoAffinityChange(p, zAff[i]) + ){ + zAff[i] = SQLITE_AFF_BLOB; + } + } +} + + +/* +** pX is an expression of the form: (vector) IN (SELECT ...) +** In other words, it is a vector IN operator with a SELECT clause on the +** LHS. But not all terms in the vector are indexable and the terms might +** not be in the correct order for indexing. +** +** This routine makes a copy of the input pX expression and then adjusts +** the vector on the LHS with corresponding changes to the SELECT so that +** the vector contains only index terms and those terms are in the correct +** order. The modified IN expression is returned. The caller is responsible +** for deleting the returned expression. +** +** Example: +** +** CREATE TABLE t1(a,b,c,d,e,f); +** CREATE INDEX t1x1 ON t1(e,c); +** SELECT * FROM t1 WHERE (a,b,c,d,e) IN (SELECT v,w,x,y,z FROM t2) +** \_______________________________________/ +** The pX expression +** +** Since only columns e and c can be used with the index, in that order, +** the modified IN expression that is returned will be: +** +** (e,c) IN (SELECT z,x FROM t2) +** +** The reduced pX is different from the original (obviously) and thus is +** only used for indexing, to improve performance. The original unaltered +** IN expression must also be run on each output row for correctness. +*/ +static Expr *removeUnindexableInClauseTerms( + Parse *pParse, /* The parsing context */ + int iEq, /* Look at loop terms starting here */ + WhereLoop *pLoop, /* The current loop */ + Expr *pX /* The IN expression to be reduced */ +){ + sqlite3 *db = pParse->db; + Select *pSelect; /* Pointer to the SELECT on the RHS */ + Expr *pNew; + pNew = sqlite3ExprDup(db, pX, 0); + if( db->mallocFailed==0 ){ + for(pSelect=pNew->x.pSelect; pSelect; pSelect=pSelect->pPrior){ + ExprList *pOrigRhs; /* Original unmodified RHS */ + ExprList *pOrigLhs = 0; /* Original unmodified LHS */ + ExprList *pRhs = 0; /* New RHS after modifications */ + ExprList *pLhs = 0; /* New LHS after mods */ + int i; /* Loop counter */ + + assert( ExprUseXSelect(pNew) ); + pOrigRhs = pSelect->pEList; + assert( pNew->pLeft!=0 ); + assert( ExprUseXList(pNew->pLeft) ); + if( pSelect==pNew->x.pSelect ){ + pOrigLhs = pNew->pLeft->x.pList; + } + for(i=iEq; i<pLoop->nLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iField; + assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 ); + iField = pLoop->aLTerm[i]->u.x.iField - 1; + if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */ + pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr); + pOrigRhs->a[iField].pExpr = 0; + if( pOrigLhs ){ + assert( pOrigLhs->a[iField].pExpr!=0 ); + pLhs = sqlite3ExprListAppend(pParse,pLhs,pOrigLhs->a[iField].pExpr); + pOrigLhs->a[iField].pExpr = 0; + } + } + } + sqlite3ExprListDelete(db, pOrigRhs); + if( pOrigLhs ){ + sqlite3ExprListDelete(db, pOrigLhs); + pNew->pLeft->x.pList = pLhs; + } + pSelect->pEList = pRhs; + if( pLhs && pLhs->nExpr==1 ){ + /* Take care here not to generate a TK_VECTOR containing only a + ** single value. Since the parser never creates such a vector, some + ** of the subroutines do not handle this case. */ + Expr *p = pLhs->a[0].pExpr; + pLhs->a[0].pExpr = 0; + sqlite3ExprDelete(db, pNew->pLeft); + pNew->pLeft = p; + } + if( pSelect->pOrderBy ){ + /* If the SELECT statement has an ORDER BY clause, zero the + ** iOrderByCol variables. These are set to non-zero when an + ** ORDER BY term exactly matches one of the terms of the + ** result-set. Since the result-set of the SELECT statement may + ** have been modified or reordered, these variables are no longer + ** set correctly. Since setting them is just an optimization, + ** it's easiest just to zero them here. */ + ExprList *pOrderBy = pSelect->pOrderBy; + for(i=0; i<pOrderBy->nExpr; i++){ + pOrderBy->a[i].u.x.iOrderByCol = 0; + } + } + +#if 0 + printf("For indexing, change the IN expr:\n"); + sqlite3TreeViewExpr(0, pX, 0); + printf("Into:\n"); + sqlite3TreeViewExpr(0, pNew, 0); +#endif + } + } + return pNew; +} + + +/* +** Generate code for a single equality term of the WHERE clause. An equality +** term can be either X=expr or X IN (...). pTerm is the term to be +** coded. +** +** The current value for the constraint is left in a register, the index +** of which is returned. An attempt is made store the result in iTarget but +** this is only guaranteed for TK_ISNULL and TK_IN constraints. If the +** constraint is a TK_EQ or TK_IS, then the current value might be left in +** some other register and it is the caller's responsibility to compensate. +** +** For a constraint of the form X=expr, the expression is evaluated in +** straight-line code. For constraints of the form X IN (...) +** this routine sets up a loop that will iterate over all values of X. +*/ +static int codeEqualityTerm( + Parse *pParse, /* The parsing context */ + WhereTerm *pTerm, /* The term of the WHERE clause to be coded */ + WhereLevel *pLevel, /* The level of the FROM clause we are working on */ + int iEq, /* Index of the equality term within this level */ + int bRev, /* True for reverse-order IN operations */ + int iTarget /* Attempt to leave results in this register */ +){ + Expr *pX = pTerm->pExpr; + Vdbe *v = pParse->pVdbe; + int iReg; /* Register holding results */ + + assert( pLevel->pWLoop->aLTerm[iEq]==pTerm ); + assert( iTarget>0 ); + if( pX->op==TK_EQ || pX->op==TK_IS ){ + iReg = sqlite3ExprCodeTarget(pParse, pX->pRight, iTarget); + }else if( pX->op==TK_ISNULL ){ + iReg = iTarget; + sqlite3VdbeAddOp2(v, OP_Null, 0, iReg); +#ifndef SQLITE_OMIT_SUBQUERY + }else{ + int eType = IN_INDEX_NOOP; + int iTab; + struct InLoop *pIn; + WhereLoop *pLoop = pLevel->pWLoop; + int i; + int nEq = 0; + int *aiMap = 0; + + if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 + && pLoop->u.btree.pIndex!=0 + && pLoop->u.btree.pIndex->aSortOrder[iEq] + ){ + testcase( iEq==0 ); + testcase( bRev ); + bRev = !bRev; + } + assert( pX->op==TK_IN ); + iReg = iTarget; + + for(i=0; i<iEq; i++){ + if( pLoop->aLTerm[i] && pLoop->aLTerm[i]->pExpr==pX ){ + disableTerm(pLevel, pTerm); + return iTarget; + } + } + for(i=iEq;i<pLoop->nLTerm; i++){ + assert( pLoop->aLTerm[i]!=0 ); + if( pLoop->aLTerm[i]->pExpr==pX ) nEq++; + } + + iTab = 0; + if( !ExprUseXSelect(pX) || pX->x.pSelect->pEList->nExpr==1 ){ + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab); + }else{ + Expr *pExpr = pTerm->pExpr; + if( pExpr->iTable==0 || !ExprHasProperty(pExpr, EP_Subrtn) ){ + sqlite3 *db = pParse->db; + pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX); + if( !db->mallocFailed ){ + aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq); + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap,&iTab); + pExpr->iTable = iTab; + } + sqlite3ExprDelete(db, pX); + }else{ + int n = sqlite3ExprVectorSize(pX->pLeft); + aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*MAX(nEq,n)); + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab); + } + pX = pExpr; + } + + if( eType==IN_INDEX_INDEX_DESC ){ + testcase( bRev ); + bRev = !bRev; + } + sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iTab, 0); + VdbeCoverageIf(v, bRev); + VdbeCoverageIf(v, !bRev); + + assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); + pLoop->wsFlags |= WHERE_IN_ABLE; + if( pLevel->u.in.nIn==0 ){ + pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse); + } + if( iEq>0 && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)==0 ){ + pLoop->wsFlags |= WHERE_IN_EARLYOUT; + } + + i = pLevel->u.in.nIn; + pLevel->u.in.nIn += nEq; + pLevel->u.in.aInLoop = + sqlite3WhereRealloc(pTerm->pWC->pWInfo, + pLevel->u.in.aInLoop, + sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn); + pIn = pLevel->u.in.aInLoop; + if( pIn ){ + int iMap = 0; /* Index in aiMap[] */ + pIn += i; + for(i=iEq;i<pLoop->nLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iOut = iReg + i - iEq; + if( eType==IN_INDEX_ROWID ){ + pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iOut); + }else{ + int iCol = aiMap ? aiMap[iMap++] : 0; + pIn->addrInTop = sqlite3VdbeAddOp3(v,OP_Column,iTab, iCol, iOut); + } + sqlite3VdbeAddOp1(v, OP_IsNull, iOut); VdbeCoverage(v); + if( i==iEq ){ + pIn->iCur = iTab; + pIn->eEndLoopOp = bRev ? OP_Prev : OP_Next; + if( iEq>0 ){ + pIn->iBase = iReg - i; + pIn->nPrefix = i; + }else{ + pIn->nPrefix = 0; + } + }else{ + pIn->eEndLoopOp = OP_Noop; + } + pIn++; + } + } + testcase( iEq>0 + && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)==0 + && (pLoop->wsFlags & WHERE_VIRTUALTABLE)!=0 ); + if( iEq>0 + && (pLoop->wsFlags & (WHERE_IN_SEEKSCAN|WHERE_VIRTUALTABLE))==0 + ){ + sqlite3VdbeAddOp3(v, OP_SeekHit, pLevel->iIdxCur, 0, iEq); + } + }else{ + pLevel->u.in.nIn = 0; + } + sqlite3DbFree(pParse->db, aiMap); +#endif + } + + /* As an optimization, try to disable the WHERE clause term that is + ** driving the index as it will always be true. The correct answer is + ** obtained regardless, but we might get the answer with fewer CPU cycles + ** by omitting the term. + ** + ** But do not disable the term unless we are certain that the term is + ** not a transitive constraint. For an example of where that does not + ** work, see https://sqlite.org/forum/forumpost/eb8613976a (2021-05-04) + */ + if( (pLevel->pWLoop->wsFlags & WHERE_TRANSCONS)==0 + || (pTerm->eOperator & WO_EQUIV)==0 + ){ + disableTerm(pLevel, pTerm); + } + + return iReg; +} + +/* +** Generate code that will evaluate all == and IN constraints for an +** index scan. +** +** For example, consider table t1(a,b,c,d,e,f) with index i1(a,b,c). +** Suppose the WHERE clause is this: a==5 AND b IN (1,2,3) AND c>5 AND c<10 +** The index has as many as three equality constraints, but in this +** example, the third "c" value is an inequality. So only two +** constraints are coded. This routine will generate code to evaluate +** a==5 and b IN (1,2,3). The current values for a and b will be stored +** in consecutive registers and the index of the first register is returned. +** +** In the example above nEq==2. But this subroutine works for any value +** of nEq including 0. If nEq==0, this routine is nearly a no-op. +** The only thing it does is allocate the pLevel->iMem memory cell and +** compute the affinity string. +** +** The nExtraReg parameter is 0 or 1. It is 0 if all WHERE clause constraints +** are == or IN and are covered by the nEq. nExtraReg is 1 if there is +** an inequality constraint (such as the "c>=5 AND c<10" in the example) that +** occurs after the nEq quality constraints. +** +** This routine allocates a range of nEq+nExtraReg memory cells and returns +** the index of the first memory cell in that range. The code that +** calls this routine will use that memory range to store keys for +** start and termination conditions of the loop. +** key value of the loop. If one or more IN operators appear, then +** this routine allocates an additional nEq memory cells for internal +** use. +** +** Before returning, *pzAff is set to point to a buffer containing a +** copy of the column affinity string of the index allocated using +** sqlite3DbMalloc(). Except, entries in the copy of the string associated +** with equality constraints that use BLOB or NONE affinity are set to +** SQLITE_AFF_BLOB. This is to deal with SQL such as the following: +** +** CREATE TABLE t1(a TEXT PRIMARY KEY, b); +** SELECT ... FROM t1 AS t2, t1 WHERE t1.a = t2.b; +** +** In the example above, the index on t1(a) has TEXT affinity. But since +** the right hand side of the equality constraint (t2.b) has BLOB/NONE affinity, +** no conversion should be attempted before using a t2.b value as part of +** a key to search the index. Hence the first byte in the returned affinity +** string in this example would be set to SQLITE_AFF_BLOB. +*/ +static int codeAllEqualityTerms( + Parse *pParse, /* Parsing context */ + WhereLevel *pLevel, /* Which nested loop of the FROM we are coding */ + int bRev, /* Reverse the order of IN operators */ + int nExtraReg, /* Number of extra registers to allocate */ + char **pzAff /* OUT: Set to point to affinity string */ +){ + u16 nEq; /* The number of == or IN constraints to code */ + u16 nSkip; /* Number of left-most columns to skip */ + Vdbe *v = pParse->pVdbe; /* The vm under construction */ + Index *pIdx; /* The index being used for this loop */ + WhereTerm *pTerm; /* A single constraint term */ + WhereLoop *pLoop; /* The WhereLoop object */ + int j; /* Loop counter */ + int regBase; /* Base register */ + int nReg; /* Number of registers to allocate */ + char *zAff; /* Affinity string to return */ + + /* This module is only called on query plans that use an index. */ + pLoop = pLevel->pWLoop; + assert( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ); + nEq = pLoop->u.btree.nEq; + nSkip = pLoop->nSkip; + pIdx = pLoop->u.btree.pIndex; + assert( pIdx!=0 ); + + /* Figure out how many memory cells we will need then allocate them. + */ + regBase = pParse->nMem + 1; + nReg = nEq + nExtraReg; + pParse->nMem += nReg; + + zAff = sqlite3DbStrDup(pParse->db,sqlite3IndexAffinityStr(pParse->db,pIdx)); + assert( zAff!=0 || pParse->db->mallocFailed ); + + if( nSkip ){ + int iIdxCur = pLevel->iIdxCur; + sqlite3VdbeAddOp3(v, OP_Null, 0, regBase, regBase+nSkip-1); + sqlite3VdbeAddOp1(v, (bRev?OP_Last:OP_Rewind), iIdxCur); + VdbeCoverageIf(v, bRev==0); + VdbeCoverageIf(v, bRev!=0); + VdbeComment((v, "begin skip-scan on %s", pIdx->zName)); + j = sqlite3VdbeAddOp0(v, OP_Goto); + assert( pLevel->addrSkip==0 ); + pLevel->addrSkip = sqlite3VdbeAddOp4Int(v, (bRev?OP_SeekLT:OP_SeekGT), + iIdxCur, 0, regBase, nSkip); + VdbeCoverageIf(v, bRev==0); + VdbeCoverageIf(v, bRev!=0); + sqlite3VdbeJumpHere(v, j); + for(j=0; j<nSkip; j++){ + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, j, regBase+j); + testcase( pIdx->aiColumn[j]==XN_EXPR ); + VdbeComment((v, "%s", explainIndexColumnName(pIdx, j))); + } + } + + /* Evaluate the equality constraints + */ + assert( zAff==0 || (int)strlen(zAff)>=nEq ); + for(j=nSkip; j<nEq; j++){ + int r1; + pTerm = pLoop->aLTerm[j]; + assert( pTerm!=0 ); + /* The following testcase is true for indices with redundant columns. + ** Ex: CREATE INDEX i1 ON t1(a,b,a); SELECT * FROM t1 WHERE a=0 AND b=0; */ + testcase( (pTerm->wtFlags & TERM_CODED)!=0 ); + testcase( pTerm->wtFlags & TERM_VIRTUAL ); + r1 = codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, regBase+j); + if( r1!=regBase+j ){ + if( nReg==1 ){ + sqlite3ReleaseTempReg(pParse, regBase); + regBase = r1; + }else{ + sqlite3VdbeAddOp2(v, OP_Copy, r1, regBase+j); + } + } + if( pTerm->eOperator & WO_IN ){ + if( pTerm->pExpr->flags & EP_xIsSelect ){ + /* No affinity ever needs to be (or should be) applied to a value + ** from the RHS of an "? IN (SELECT ...)" expression. The + ** sqlite3FindInIndex() routine has already ensured that the + ** affinity of the comparison has been applied to the value. */ + if( zAff ) zAff[j] = SQLITE_AFF_BLOB; + } + }else if( (pTerm->eOperator & WO_ISNULL)==0 ){ + Expr *pRight = pTerm->pExpr->pRight; + if( (pTerm->wtFlags & TERM_IS)==0 && sqlite3ExprCanBeNull(pRight) ){ + sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk); + VdbeCoverage(v); + } + if( pParse->nErr==0 ){ + assert( pParse->db->mallocFailed==0 ); + if( sqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_BLOB ){ + zAff[j] = SQLITE_AFF_BLOB; + } + if( sqlite3ExprNeedsNoAffinityChange(pRight, zAff[j]) ){ + zAff[j] = SQLITE_AFF_BLOB; + } + } + } + } + *pzAff = zAff; + return regBase; +} + +#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS +/* +** If the most recently coded instruction is a constant range constraint +** (a string literal) that originated from the LIKE optimization, then +** set P3 and P5 on the OP_String opcode so that the string will be cast +** to a BLOB at appropriate times. +** +** The LIKE optimization trys to evaluate "x LIKE 'abc%'" as a range +** expression: "x>='ABC' AND x<'abd'". But this requires that the range +** scan loop run twice, once for strings and a second time for BLOBs. +** The OP_String opcodes on the second pass convert the upper and lower +** bound string constants to blobs. This routine makes the necessary changes +** to the OP_String opcodes for that to happen. +** +** Except, of course, if SQLITE_LIKE_DOESNT_MATCH_BLOBS is defined, then +** only the one pass through the string space is required, so this routine +** becomes a no-op. +*/ +static void whereLikeOptimizationStringFixup( + Vdbe *v, /* prepared statement under construction */ + WhereLevel *pLevel, /* The loop that contains the LIKE operator */ + WhereTerm *pTerm /* The upper or lower bound just coded */ +){ + if( pTerm->wtFlags & TERM_LIKEOPT ){ + VdbeOp *pOp; + assert( pLevel->iLikeRepCntr>0 ); + pOp = sqlite3VdbeGetLastOp(v); + assert( pOp!=0 ); + assert( pOp->opcode==OP_String8 + || pTerm->pWC->pWInfo->pParse->db->mallocFailed ); + pOp->p3 = (int)(pLevel->iLikeRepCntr>>1); /* Register holding counter */ + pOp->p5 = (u8)(pLevel->iLikeRepCntr&1); /* ASC or DESC */ + } +} +#else +# define whereLikeOptimizationStringFixup(A,B,C) +#endif + +#ifdef SQLITE_ENABLE_CURSOR_HINTS +/* +** Information is passed from codeCursorHint() down to individual nodes of +** the expression tree (by sqlite3WalkExpr()) using an instance of this +** structure. +*/ +struct CCurHint { + int iTabCur; /* Cursor for the main table */ + int iIdxCur; /* Cursor for the index, if pIdx!=0. Unused otherwise */ + Index *pIdx; /* The index used to access the table */ +}; + +/* +** This function is called for every node of an expression that is a candidate +** for a cursor hint on an index cursor. For TK_COLUMN nodes that reference +** the table CCurHint.iTabCur, verify that the same column can be +** accessed through the index. If it cannot, then set pWalker->eCode to 1. +*/ +static int codeCursorHintCheckExpr(Walker *pWalker, Expr *pExpr){ + struct CCurHint *pHint = pWalker->u.pCCurHint; + assert( pHint->pIdx!=0 ); + if( pExpr->op==TK_COLUMN + && pExpr->iTable==pHint->iTabCur + && sqlite3TableColumnToIndex(pHint->pIdx, pExpr->iColumn)<0 + ){ + pWalker->eCode = 1; + } + return WRC_Continue; +} + +/* +** Test whether or not expression pExpr, which was part of a WHERE clause, +** should be included in the cursor-hint for a table that is on the rhs +** of a LEFT JOIN. Set Walker.eCode to non-zero before returning if the +** expression is not suitable. +** +** An expression is unsuitable if it might evaluate to non NULL even if +** a TK_COLUMN node that does affect the value of the expression is set +** to NULL. For example: +** +** col IS NULL +** col IS NOT NULL +** coalesce(col, 1) +** CASE WHEN col THEN 0 ELSE 1 END +*/ +static int codeCursorHintIsOrFunction(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_IS + || pExpr->op==TK_ISNULL || pExpr->op==TK_ISNOT + || pExpr->op==TK_NOTNULL || pExpr->op==TK_CASE + ){ + pWalker->eCode = 1; + }else if( pExpr->op==TK_FUNCTION ){ + int d1; + char d2[4]; + if( 0==sqlite3IsLikeFunction(pWalker->pParse->db, pExpr, &d1, d2) ){ + pWalker->eCode = 1; + } + } + + return WRC_Continue; +} + + +/* +** This function is called on every node of an expression tree used as an +** argument to the OP_CursorHint instruction. If the node is a TK_COLUMN +** that accesses any table other than the one identified by +** CCurHint.iTabCur, then do the following: +** +** 1) allocate a register and code an OP_Column instruction to read +** the specified column into the new register, and +** +** 2) transform the expression node to a TK_REGISTER node that reads +** from the newly populated register. +** +** Also, if the node is a TK_COLUMN that does access the table identified +** by pCCurHint.iTabCur, and an index is being used (which we will +** know because CCurHint.pIdx!=0) then transform the TK_COLUMN into +** an access of the index rather than the original table. +*/ +static int codeCursorHintFixExpr(Walker *pWalker, Expr *pExpr){ + int rc = WRC_Continue; + int reg; + struct CCurHint *pHint = pWalker->u.pCCurHint; + if( pExpr->op==TK_COLUMN ){ + if( pExpr->iTable!=pHint->iTabCur ){ + reg = ++pWalker->pParse->nMem; /* Register for column value */ + reg = sqlite3ExprCodeTarget(pWalker->pParse, pExpr, reg); + pExpr->op = TK_REGISTER; + pExpr->iTable = reg; + }else if( pHint->pIdx!=0 ){ + pExpr->iTable = pHint->iIdxCur; + pExpr->iColumn = sqlite3TableColumnToIndex(pHint->pIdx, pExpr->iColumn); + assert( pExpr->iColumn>=0 ); + } + }else if( pExpr->pAggInfo ){ + rc = WRC_Prune; + reg = ++pWalker->pParse->nMem; /* Register for column value */ + reg = sqlite3ExprCodeTarget(pWalker->pParse, pExpr, reg); + pExpr->op = TK_REGISTER; + pExpr->iTable = reg; + }else if( pExpr->op==TK_TRUEFALSE ){ + /* Do not walk disabled expressions. tag-20230504-1 */ + return WRC_Prune; + } + return rc; +} + +/* +** Insert an OP_CursorHint instruction if it is appropriate to do so. +*/ +static void codeCursorHint( + SrcItem *pTabItem, /* FROM clause item */ + WhereInfo *pWInfo, /* The where clause */ + WhereLevel *pLevel, /* Which loop to provide hints for */ + WhereTerm *pEndRange /* Hint this end-of-scan boundary term if not NULL */ +){ + Parse *pParse = pWInfo->pParse; + sqlite3 *db = pParse->db; + Vdbe *v = pParse->pVdbe; + Expr *pExpr = 0; + WhereLoop *pLoop = pLevel->pWLoop; + int iCur; + WhereClause *pWC; + WhereTerm *pTerm; + int i, j; + struct CCurHint sHint; + Walker sWalker; + + if( OptimizationDisabled(db, SQLITE_CursorHints) ) return; + iCur = pLevel->iTabCur; + assert( iCur==pWInfo->pTabList->a[pLevel->iFrom].iCursor ); + sHint.iTabCur = iCur; + sHint.iIdxCur = pLevel->iIdxCur; + sHint.pIdx = pLoop->u.btree.pIndex; + memset(&sWalker, 0, sizeof(sWalker)); + sWalker.pParse = pParse; + sWalker.u.pCCurHint = &sHint; + pWC = &pWInfo->sWC; + for(i=0; i<pWC->nBase; i++){ + pTerm = &pWC->a[i]; + if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; + if( pTerm->prereqAll & pLevel->notReady ) continue; + + /* Any terms specified as part of the ON(...) clause for any LEFT + ** JOIN for which the current table is not the rhs are omitted + ** from the cursor-hint. + ** + ** If this table is the rhs of a LEFT JOIN, "IS" or "IS NULL" terms + ** that were specified as part of the WHERE clause must be excluded. + ** This is to address the following: + ** + ** SELECT ... t1 LEFT JOIN t2 ON (t1.a=t2.b) WHERE t2.c IS NULL; + ** + ** Say there is a single row in t2 that matches (t1.a=t2.b), but its + ** t2.c values is not NULL. If the (t2.c IS NULL) constraint is + ** pushed down to the cursor, this row is filtered out, causing + ** SQLite to synthesize a row of NULL values. Which does match the + ** WHERE clause, and so the query returns a row. Which is incorrect. + ** + ** For the same reason, WHERE terms such as: + ** + ** WHERE 1 = (t2.c IS NULL) + ** + ** are also excluded. See codeCursorHintIsOrFunction() for details. + */ + if( pTabItem->fg.jointype & JT_LEFT ){ + Expr *pExpr = pTerm->pExpr; + if( !ExprHasProperty(pExpr, EP_OuterON) + || pExpr->w.iJoin!=pTabItem->iCursor + ){ + sWalker.eCode = 0; + sWalker.xExprCallback = codeCursorHintIsOrFunction; + sqlite3WalkExpr(&sWalker, pTerm->pExpr); + if( sWalker.eCode ) continue; + } + }else{ + if( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) continue; + } + + /* All terms in pWLoop->aLTerm[] except pEndRange are used to initialize + ** the cursor. These terms are not needed as hints for a pure range + ** scan (that has no == terms) so omit them. */ + if( pLoop->u.btree.nEq==0 && pTerm!=pEndRange ){ + for(j=0; j<pLoop->nLTerm && pLoop->aLTerm[j]!=pTerm; j++){} + if( j<pLoop->nLTerm ) continue; + } + + /* No subqueries or non-deterministic functions allowed */ + if( sqlite3ExprContainsSubquery(pTerm->pExpr) ) continue; + + /* For an index scan, make sure referenced columns are actually in + ** the index. */ + if( sHint.pIdx!=0 ){ + sWalker.eCode = 0; + sWalker.xExprCallback = codeCursorHintCheckExpr; + sqlite3WalkExpr(&sWalker, pTerm->pExpr); + if( sWalker.eCode ) continue; + } + + /* If we survive all prior tests, that means this term is worth hinting */ + pExpr = sqlite3ExprAnd(pParse, pExpr, sqlite3ExprDup(db, pTerm->pExpr, 0)); + } + if( pExpr!=0 ){ + sWalker.xExprCallback = codeCursorHintFixExpr; + if( pParse->nErr==0 ) sqlite3WalkExpr(&sWalker, pExpr); + sqlite3VdbeAddOp4(v, OP_CursorHint, + (sHint.pIdx ? sHint.iIdxCur : sHint.iTabCur), 0, 0, + (const char*)pExpr, P4_EXPR); + } +} +#else +# define codeCursorHint(A,B,C,D) /* No-op */ +#endif /* SQLITE_ENABLE_CURSOR_HINTS */ + +/* +** Cursor iCur is open on an intkey b-tree (a table). Register iRowid contains +** a rowid value just read from cursor iIdxCur, open on index pIdx. This +** function generates code to do a deferred seek of cursor iCur to the +** rowid stored in register iRowid. +** +** Normally, this is just: +** +** OP_DeferredSeek $iCur $iRowid +** +** Which causes a seek on $iCur to the row with rowid $iRowid. +** +** However, if the scan currently being coded is a branch of an OR-loop and +** the statement currently being coded is a SELECT, then additional information +** is added that might allow OP_Column to omit the seek and instead do its +** lookup on the index, thus avoiding an expensive seek operation. To +** enable this optimization, the P3 of OP_DeferredSeek is set to iIdxCur +** and P4 is set to an array of integers containing one entry for each column +** in the table. For each table column, if the column is the i'th +** column of the index, then the corresponding array entry is set to (i+1). +** If the column does not appear in the index at all, the array entry is set +** to 0. The OP_Column opcode can check this array to see if the column it +** wants is in the index and if it is, it will substitute the index cursor +** and column number and continue with those new values, rather than seeking +** the table cursor. +*/ +static void codeDeferredSeek( + WhereInfo *pWInfo, /* Where clause context */ + Index *pIdx, /* Index scan is using */ + int iCur, /* Cursor for IPK b-tree */ + int iIdxCur /* Index cursor */ +){ + Parse *pParse = pWInfo->pParse; /* Parse context */ + Vdbe *v = pParse->pVdbe; /* Vdbe to generate code within */ + + assert( iIdxCur>0 ); + assert( pIdx->aiColumn[pIdx->nColumn-1]==-1 ); + + pWInfo->bDeferredSeek = 1; + sqlite3VdbeAddOp3(v, OP_DeferredSeek, iIdxCur, 0, iCur); + if( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN)) + && DbMaskAllZero(sqlite3ParseToplevel(pParse)->writeMask) + ){ + int i; + Table *pTab = pIdx->pTable; + u32 *ai = (u32*)sqlite3DbMallocZero(pParse->db, sizeof(u32)*(pTab->nCol+1)); + if( ai ){ + ai[0] = pTab->nCol; + for(i=0; i<pIdx->nColumn-1; i++){ + int x1, x2; + assert( pIdx->aiColumn[i]<pTab->nCol ); + x1 = pIdx->aiColumn[i]; + x2 = sqlite3TableColumnToStorage(pTab, x1); + testcase( x1!=x2 ); + if( x1>=0 ) ai[x2+1] = i+1; + } + sqlite3VdbeChangeP4(v, -1, (char*)ai, P4_INTARRAY); + } + } +} + +/* +** If the expression passed as the second argument is a vector, generate +** code to write the first nReg elements of the vector into an array +** of registers starting with iReg. +** +** If the expression is not a vector, then nReg must be passed 1. In +** this case, generate code to evaluate the expression and leave the +** result in register iReg. +*/ +static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ + assert( nReg>0 ); + if( p && sqlite3ExprIsVector(p) ){ +#ifndef SQLITE_OMIT_SUBQUERY + if( ExprUseXSelect(p) ){ + Vdbe *v = pParse->pVdbe; + int iSelect; + assert( p->op==TK_SELECT ); + iSelect = sqlite3CodeSubselect(pParse, p); + sqlite3VdbeAddOp3(v, OP_Copy, iSelect, iReg, nReg-1); + }else +#endif + { + int i; + const ExprList *pList; + assert( ExprUseXList(p) ); + pList = p->x.pList; + assert( nReg<=pList->nExpr ); + for(i=0; i<nReg; i++){ + sqlite3ExprCode(pParse, pList->a[i].pExpr, iReg+i); + } + } + }else{ + assert( nReg==1 || pParse->nErr ); + sqlite3ExprCode(pParse, p, iReg); + } +} + +/* +** The pTruth expression is always true because it is the WHERE clause +** a partial index that is driving a query loop. Look through all of the +** WHERE clause terms on the query, and if any of those terms must be +** true because pTruth is true, then mark those WHERE clause terms as +** coded. +*/ +static void whereApplyPartialIndexConstraints( + Expr *pTruth, + int iTabCur, + WhereClause *pWC +){ + int i; + WhereTerm *pTerm; + while( pTruth->op==TK_AND ){ + whereApplyPartialIndexConstraints(pTruth->pLeft, iTabCur, pWC); + pTruth = pTruth->pRight; + } + for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ + Expr *pExpr; + if( pTerm->wtFlags & TERM_CODED ) continue; + pExpr = pTerm->pExpr; + if( sqlite3ExprCompare(0, pExpr, pTruth, iTabCur)==0 ){ + pTerm->wtFlags |= TERM_CODED; + } + } +} + +/* +** This routine is called right after An OP_Filter has been generated and +** before the corresponding index search has been performed. This routine +** checks to see if there are additional Bloom filters in inner loops that +** can be checked prior to doing the index lookup. If there are available +** inner-loop Bloom filters, then evaluate those filters now, before the +** index lookup. The idea is that a Bloom filter check is way faster than +** an index lookup, and the Bloom filter might return false, meaning that +** the index lookup can be skipped. +** +** We know that an inner loop uses a Bloom filter because it has the +** WhereLevel.regFilter set. If an inner-loop Bloom filter is checked, +** then clear the WhereLevel.regFilter value to prevent the Bloom filter +** from being checked a second time when the inner loop is evaluated. +*/ +static SQLITE_NOINLINE void filterPullDown( + Parse *pParse, /* Parsing context */ + WhereInfo *pWInfo, /* Complete information about the WHERE clause */ + int iLevel, /* Which level of pWInfo->a[] should be coded */ + int addrNxt, /* Jump here to bypass inner loops */ + Bitmask notReady /* Loops that are not ready */ +){ + while( ++iLevel < pWInfo->nLevel ){ + WhereLevel *pLevel = &pWInfo->a[iLevel]; + WhereLoop *pLoop = pLevel->pWLoop; + if( pLevel->regFilter==0 ) continue; + if( pLevel->pWLoop->nSkip ) continue; + /* ,--- Because sqlite3ConstructBloomFilter() has will not have set + ** vvvvv--' pLevel->regFilter if this were true. */ + if( NEVER(pLoop->prereq & notReady) ) continue; + assert( pLevel->addrBrk==0 ); + pLevel->addrBrk = addrNxt; + if( pLoop->wsFlags & WHERE_IPK ){ + WhereTerm *pTerm = pLoop->aLTerm[0]; + int regRowid; + assert( pTerm!=0 ); + assert( pTerm->pExpr!=0 ); + testcase( pTerm->wtFlags & TERM_VIRTUAL ); + regRowid = sqlite3GetTempReg(pParse); + regRowid = codeEqualityTerm(pParse, pTerm, pLevel, 0, 0, regRowid); + sqlite3VdbeAddOp2(pParse->pVdbe, OP_MustBeInt, regRowid, addrNxt); + VdbeCoverage(pParse->pVdbe); + sqlite3VdbeAddOp4Int(pParse->pVdbe, OP_Filter, pLevel->regFilter, + addrNxt, regRowid, 1); + VdbeCoverage(pParse->pVdbe); + }else{ + u16 nEq = pLoop->u.btree.nEq; + int r1; + char *zStartAff; + + assert( pLoop->wsFlags & WHERE_INDEXED ); + assert( (pLoop->wsFlags & WHERE_COLUMN_IN)==0 ); + r1 = codeAllEqualityTerms(pParse,pLevel,0,0,&zStartAff); + codeApplyAffinity(pParse, r1, nEq, zStartAff); + sqlite3DbFree(pParse->db, zStartAff); + sqlite3VdbeAddOp4Int(pParse->pVdbe, OP_Filter, pLevel->regFilter, + addrNxt, r1, nEq); + VdbeCoverage(pParse->pVdbe); + } + pLevel->regFilter = 0; + pLevel->addrBrk = 0; + } +} + +/* +** Generate code for the start of the iLevel-th loop in the WHERE clause +** implementation described by pWInfo. +*/ +SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( + Parse *pParse, /* Parsing context */ + Vdbe *v, /* Prepared statement under construction */ + WhereInfo *pWInfo, /* Complete information about the WHERE clause */ + int iLevel, /* Which level of pWInfo->a[] should be coded */ + WhereLevel *pLevel, /* The current level pointer */ + Bitmask notReady /* Which tables are currently available */ +){ + int j, k; /* Loop counters */ + int iCur; /* The VDBE cursor for the table */ + int addrNxt; /* Where to jump to continue with the next IN case */ + int bRev; /* True if we need to scan in reverse order */ + WhereLoop *pLoop; /* The WhereLoop object being coded */ + WhereClause *pWC; /* Decomposition of the entire WHERE clause */ + WhereTerm *pTerm; /* A WHERE clause term */ + sqlite3 *db; /* Database connection */ + SrcItem *pTabItem; /* FROM clause term being coded */ + int addrBrk; /* Jump here to break out of the loop */ + int addrHalt; /* addrBrk for the outermost loop */ + int addrCont; /* Jump here to continue with next cycle */ + int iRowidReg = 0; /* Rowid is stored in this register, if not zero */ + int iReleaseReg = 0; /* Temp register to free before returning */ + Index *pIdx = 0; /* Index used by loop (if any) */ + int iLoop; /* Iteration of constraint generator loop */ + + pWC = &pWInfo->sWC; + db = pParse->db; + pLoop = pLevel->pWLoop; + pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; + iCur = pTabItem->iCursor; + pLevel->notReady = notReady & ~sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur); + bRev = (pWInfo->revMask>>iLevel)&1; + VdbeModuleComment((v, "Begin WHERE-loop%d: %s",iLevel,pTabItem->pTab->zName)); +#if WHERETRACE_ENABLED /* 0x4001 */ + if( sqlite3WhereTrace & 0x1 ){ + sqlite3DebugPrintf("Coding level %d of %d: notReady=%llx iFrom=%d\n", + iLevel, pWInfo->nLevel, (u64)notReady, pLevel->iFrom); + if( sqlite3WhereTrace & 0x1000 ){ + sqlite3WhereLoopPrint(pLoop, pWC); + } + } + if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ + if( iLevel==0 ){ + sqlite3DebugPrintf("WHERE clause being coded:\n"); + sqlite3TreeViewExpr(0, pWInfo->pWhere, 0); + } + sqlite3DebugPrintf("All WHERE-clause terms before coding:\n"); + sqlite3WhereClausePrint(pWC); + } +#endif + + /* Create labels for the "break" and "continue" instructions + ** for the current loop. Jump to addrBrk to break out of a loop. + ** Jump to cont to go immediately to the next iteration of the + ** loop. + ** + ** When there is an IN operator, we also have a "addrNxt" label that + ** means to continue with the next IN value combination. When + ** there are no IN operators in the constraints, the "addrNxt" label + ** is the same as "addrBrk". + */ + addrBrk = pLevel->addrBrk = pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse); + addrCont = pLevel->addrCont = sqlite3VdbeMakeLabel(pParse); + + /* If this is the right table of a LEFT OUTER JOIN, allocate and + ** initialize a memory cell that records if this table matches any + ** row of the left table of the join. + */ + assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN)) + || pLevel->iFrom>0 || (pTabItem[0].fg.jointype & JT_LEFT)==0 + ); + if( pLevel->iFrom>0 && (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){ + pLevel->iLeftJoin = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 0, pLevel->iLeftJoin); + VdbeComment((v, "init LEFT JOIN no-match flag")); + } + + /* Compute a safe address to jump to if we discover that the table for + ** this loop is empty and can never contribute content. */ + for(j=iLevel; j>0; j--){ + if( pWInfo->a[j].iLeftJoin ) break; + if( pWInfo->a[j].pRJ ) break; + } + addrHalt = pWInfo->a[j].addrBrk; + + /* Special case of a FROM clause subquery implemented as a co-routine */ + if( pTabItem->fg.viaCoroutine ){ + int regYield = pTabItem->regReturn; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pTabItem->addrFillSub); + pLevel->p2 = sqlite3VdbeAddOp2(v, OP_Yield, regYield, addrBrk); + VdbeCoverage(v); + VdbeComment((v, "next row of %s", pTabItem->pTab->zName)); + pLevel->op = OP_Goto; + }else + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)!=0 ){ + /* Case 1: The table is a virtual-table. Use the VFilter and VNext + ** to access the data. + */ + int iReg; /* P3 Value for OP_VFilter */ + int addrNotFound; + int nConstraint = pLoop->nLTerm; + + iReg = sqlite3GetTempRange(pParse, nConstraint+2); + addrNotFound = pLevel->addrBrk; + for(j=0; j<nConstraint; j++){ + int iTarget = iReg+j+2; + pTerm = pLoop->aLTerm[j]; + if( NEVER(pTerm==0) ) continue; + if( pTerm->eOperator & WO_IN ){ + if( SMASKBIT32(j) & pLoop->u.vtab.mHandleIn ){ + int iTab = pParse->nTab++; + int iCache = ++pParse->nMem; + sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab); + sqlite3VdbeAddOp3(v, OP_VInitIn, iTab, iTarget, iCache); + }else{ + codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); + addrNotFound = pLevel->addrNxt; + } + }else{ + Expr *pRight = pTerm->pExpr->pRight; + codeExprOrVector(pParse, pRight, iTarget, 1); + if( pTerm->eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET + && pLoop->u.vtab.bOmitOffset + ){ + assert( pTerm->eOperator==WO_AUX ); + assert( pWInfo->pSelect!=0 ); + assert( pWInfo->pSelect->iOffset>0 ); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pWInfo->pSelect->iOffset); + VdbeComment((v,"Zero OFFSET counter")); + } + } + } + sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg); + sqlite3VdbeAddOp2(v, OP_Integer, nConstraint, iReg+1); + sqlite3VdbeAddOp4(v, OP_VFilter, iCur, addrNotFound, iReg, + pLoop->u.vtab.idxStr, + pLoop->u.vtab.needFree ? P4_DYNAMIC : P4_STATIC); + VdbeCoverage(v); + pLoop->u.vtab.needFree = 0; + /* An OOM inside of AddOp4(OP_VFilter) instruction above might have freed + ** the u.vtab.idxStr. NULL it out to prevent a use-after-free */ + if( db->mallocFailed ) pLoop->u.vtab.idxStr = 0; + pLevel->p1 = iCur; + pLevel->op = pWInfo->eOnePass ? OP_Noop : OP_VNext; + pLevel->p2 = sqlite3VdbeCurrentAddr(v); + assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); + + for(j=0; j<nConstraint; j++){ + pTerm = pLoop->aLTerm[j]; + if( j<16 && (pLoop->u.vtab.omitMask>>j)&1 ){ + disableTerm(pLevel, pTerm); + continue; + } + if( (pTerm->eOperator & WO_IN)!=0 + && (SMASKBIT32(j) & pLoop->u.vtab.mHandleIn)==0 + && !db->mallocFailed + ){ + Expr *pCompare; /* The comparison operator */ + Expr *pRight; /* RHS of the comparison */ + VdbeOp *pOp; /* Opcode to access the value of the IN constraint */ + int iIn; /* IN loop corresponding to the j-th constraint */ + + /* Reload the constraint value into reg[iReg+j+2]. The same value + ** was loaded into the same register prior to the OP_VFilter, but + ** the xFilter implementation might have changed the datatype or + ** encoding of the value in the register, so it *must* be reloaded. + */ + for(iIn=0; ALWAYS(iIn<pLevel->u.in.nIn); iIn++){ + pOp = sqlite3VdbeGetOp(v, pLevel->u.in.aInLoop[iIn].addrInTop); + if( (pOp->opcode==OP_Column && pOp->p3==iReg+j+2) + || (pOp->opcode==OP_Rowid && pOp->p2==iReg+j+2) + ){ + testcase( pOp->opcode==OP_Rowid ); + sqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3); + break; + } + } + + /* Generate code that will continue to the next row if + ** the IN constraint is not satisfied + */ + pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0); + if( !db->mallocFailed ){ + int iFld = pTerm->u.x.iField; + Expr *pLeft = pTerm->pExpr->pLeft; + assert( pLeft!=0 ); + if( iFld>0 ){ + assert( pLeft->op==TK_VECTOR ); + assert( ExprUseXList(pLeft) ); + assert( iFld<=pLeft->x.pList->nExpr ); + pCompare->pLeft = pLeft->x.pList->a[iFld-1].pExpr; + }else{ + pCompare->pLeft = pLeft; + } + pCompare->pRight = pRight = sqlite3Expr(db, TK_REGISTER, 0); + if( pRight ){ + pRight->iTable = iReg+j+2; + sqlite3ExprIfFalse( + pParse, pCompare, pLevel->addrCont, SQLITE_JUMPIFNULL + ); + } + pCompare->pLeft = 0; + } + sqlite3ExprDelete(db, pCompare); + } + } + + /* These registers need to be preserved in case there is an IN operator + ** loop. So we could deallocate the registers here (and potentially + ** reuse them later) if (pLoop->wsFlags & WHERE_IN_ABLE)==0. But it seems + ** simpler and safer to simply not reuse the registers. + ** + ** sqlite3ReleaseTempRange(pParse, iReg, nConstraint+2); + */ + }else +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + if( (pLoop->wsFlags & WHERE_IPK)!=0 + && (pLoop->wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_EQ))!=0 + ){ + /* Case 2: We can directly reference a single row using an + ** equality comparison against the ROWID field. Or + ** we reference multiple rows using a "rowid IN (...)" + ** construct. + */ + assert( pLoop->u.btree.nEq==1 ); + pTerm = pLoop->aLTerm[0]; + assert( pTerm!=0 ); + assert( pTerm->pExpr!=0 ); + testcase( pTerm->wtFlags & TERM_VIRTUAL ); + iReleaseReg = ++pParse->nMem; + iRowidReg = codeEqualityTerm(pParse, pTerm, pLevel, 0, bRev, iReleaseReg); + if( iRowidReg!=iReleaseReg ) sqlite3ReleaseTempReg(pParse, iReleaseReg); + addrNxt = pLevel->addrNxt; + if( pLevel->regFilter ){ + sqlite3VdbeAddOp2(v, OP_MustBeInt, iRowidReg, addrNxt); + VdbeCoverage(v); + sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt, + iRowidReg, 1); + VdbeCoverage(v); + filterPullDown(pParse, pWInfo, iLevel, addrNxt, notReady); + } + sqlite3VdbeAddOp3(v, OP_SeekRowid, iCur, addrNxt, iRowidReg); + VdbeCoverage(v); + pLevel->op = OP_Noop; + }else if( (pLoop->wsFlags & WHERE_IPK)!=0 + && (pLoop->wsFlags & WHERE_COLUMN_RANGE)!=0 + ){ + /* Case 3: We have an inequality comparison against the ROWID field. + */ + int testOp = OP_Noop; + int start; + int memEndValue = 0; + WhereTerm *pStart, *pEnd; + + j = 0; + pStart = pEnd = 0; + if( pLoop->wsFlags & WHERE_BTM_LIMIT ) pStart = pLoop->aLTerm[j++]; + if( pLoop->wsFlags & WHERE_TOP_LIMIT ) pEnd = pLoop->aLTerm[j++]; + assert( pStart!=0 || pEnd!=0 ); + if( bRev ){ + pTerm = pStart; + pStart = pEnd; + pEnd = pTerm; + } + codeCursorHint(pTabItem, pWInfo, pLevel, pEnd); + if( pStart ){ + Expr *pX; /* The expression that defines the start bound */ + int r1, rTemp; /* Registers for holding the start boundary */ + int op; /* Cursor seek operation */ + + /* The following constant maps TK_xx codes into corresponding + ** seek opcodes. It depends on a particular ordering of TK_xx + */ + const u8 aMoveOp[] = { + /* TK_GT */ OP_SeekGT, + /* TK_LE */ OP_SeekLE, + /* TK_LT */ OP_SeekLT, + /* TK_GE */ OP_SeekGE + }; + assert( TK_LE==TK_GT+1 ); /* Make sure the ordering.. */ + assert( TK_LT==TK_GT+2 ); /* ... of the TK_xx values... */ + assert( TK_GE==TK_GT+3 ); /* ... is correct. */ + + assert( (pStart->wtFlags & TERM_VNULL)==0 ); + testcase( pStart->wtFlags & TERM_VIRTUAL ); + pX = pStart->pExpr; + assert( pX!=0 ); + testcase( pStart->leftCursor!=iCur ); /* transitive constraints */ + if( sqlite3ExprIsVector(pX->pRight) ){ + r1 = rTemp = sqlite3GetTempReg(pParse); + codeExprOrVector(pParse, pX->pRight, r1, 1); + testcase( pX->op==TK_GT ); + testcase( pX->op==TK_GE ); + testcase( pX->op==TK_LT ); + testcase( pX->op==TK_LE ); + op = aMoveOp[((pX->op - TK_GT - 1) & 0x3) | 0x1]; + assert( pX->op!=TK_GT || op==OP_SeekGE ); + assert( pX->op!=TK_GE || op==OP_SeekGE ); + assert( pX->op!=TK_LT || op==OP_SeekLE ); + assert( pX->op!=TK_LE || op==OP_SeekLE ); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp); + disableTerm(pLevel, pStart); + op = aMoveOp[(pX->op - TK_GT)]; + } + sqlite3VdbeAddOp3(v, op, iCur, addrBrk, r1); + VdbeComment((v, "pk")); + VdbeCoverageIf(v, pX->op==TK_GT); + VdbeCoverageIf(v, pX->op==TK_LE); + VdbeCoverageIf(v, pX->op==TK_LT); + VdbeCoverageIf(v, pX->op==TK_GE); + sqlite3ReleaseTempReg(pParse, rTemp); + }else{ + sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrHalt); + VdbeCoverageIf(v, bRev==0); + VdbeCoverageIf(v, bRev!=0); + } + if( pEnd ){ + Expr *pX; + pX = pEnd->pExpr; + assert( pX!=0 ); + assert( (pEnd->wtFlags & TERM_VNULL)==0 ); + testcase( pEnd->leftCursor!=iCur ); /* Transitive constraints */ + testcase( pEnd->wtFlags & TERM_VIRTUAL ); + memEndValue = ++pParse->nMem; + codeExprOrVector(pParse, pX->pRight, memEndValue, 1); + if( 0==sqlite3ExprIsVector(pX->pRight) + && (pX->op==TK_LT || pX->op==TK_GT) + ){ + testOp = bRev ? OP_Le : OP_Ge; + }else{ + testOp = bRev ? OP_Lt : OP_Gt; + } + if( 0==sqlite3ExprIsVector(pX->pRight) ){ + disableTerm(pLevel, pEnd); + } + } + start = sqlite3VdbeCurrentAddr(v); + pLevel->op = bRev ? OP_Prev : OP_Next; + pLevel->p1 = iCur; + pLevel->p2 = start; + assert( pLevel->p5==0 ); + if( testOp!=OP_Noop ){ + iRowidReg = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Rowid, iCur, iRowidReg); + sqlite3VdbeAddOp3(v, testOp, memEndValue, addrBrk, iRowidReg); + VdbeCoverageIf(v, testOp==OP_Le); + VdbeCoverageIf(v, testOp==OP_Lt); + VdbeCoverageIf(v, testOp==OP_Ge); + VdbeCoverageIf(v, testOp==OP_Gt); + sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC | SQLITE_JUMPIFNULL); + } + }else if( pLoop->wsFlags & WHERE_INDEXED ){ + /* Case 4: A scan using an index. + ** + ** The WHERE clause may contain zero or more equality + ** terms ("==" or "IN" operators) that refer to the N + ** left-most columns of the index. It may also contain + ** inequality constraints (>, <, >= or <=) on the indexed + ** column that immediately follows the N equalities. Only + ** the right-most column can be an inequality - the rest must + ** use the "==" and "IN" operators. For example, if the + ** index is on (x,y,z), then the following clauses are all + ** optimized: + ** + ** x=5 + ** x=5 AND y=10 + ** x=5 AND y<10 + ** x=5 AND y>5 AND y<10 + ** x=5 AND y=5 AND z<=10 + ** + ** The z<10 term of the following cannot be used, only + ** the x=5 term: + ** + ** x=5 AND z<10 + ** + ** N may be zero if there are inequality constraints. + ** If there are no inequality constraints, then N is at + ** least one. + ** + ** This case is also used when there are no WHERE clause + ** constraints but an index is selected anyway, in order + ** to force the output order to conform to an ORDER BY. + */ + static const u8 aStartOp[] = { + 0, + 0, + OP_Rewind, /* 2: (!start_constraints && startEq && !bRev) */ + OP_Last, /* 3: (!start_constraints && startEq && bRev) */ + OP_SeekGT, /* 4: (start_constraints && !startEq && !bRev) */ + OP_SeekLT, /* 5: (start_constraints && !startEq && bRev) */ + OP_SeekGE, /* 6: (start_constraints && startEq && !bRev) */ + OP_SeekLE /* 7: (start_constraints && startEq && bRev) */ + }; + static const u8 aEndOp[] = { + OP_IdxGE, /* 0: (end_constraints && !bRev && !endEq) */ + OP_IdxGT, /* 1: (end_constraints && !bRev && endEq) */ + OP_IdxLE, /* 2: (end_constraints && bRev && !endEq) */ + OP_IdxLT, /* 3: (end_constraints && bRev && endEq) */ + }; + u16 nEq = pLoop->u.btree.nEq; /* Number of == or IN terms */ + u16 nBtm = pLoop->u.btree.nBtm; /* Length of BTM vector */ + u16 nTop = pLoop->u.btree.nTop; /* Length of TOP vector */ + int regBase; /* Base register holding constraint values */ + WhereTerm *pRangeStart = 0; /* Inequality constraint at range start */ + WhereTerm *pRangeEnd = 0; /* Inequality constraint at range end */ + int startEq; /* True if range start uses ==, >= or <= */ + int endEq; /* True if range end uses ==, >= or <= */ + int start_constraints; /* Start of range is constrained */ + int nConstraint; /* Number of constraint terms */ + int iIdxCur; /* The VDBE cursor for the index */ + int nExtraReg = 0; /* Number of extra registers needed */ + int op; /* Instruction opcode */ + char *zStartAff; /* Affinity for start of range constraint */ + char *zEndAff = 0; /* Affinity for end of range constraint */ + u8 bSeekPastNull = 0; /* True to seek past initial nulls */ + u8 bStopAtNull = 0; /* Add condition to terminate at NULLs */ + int omitTable; /* True if we use the index only */ + int regBignull = 0; /* big-null flag register */ + int addrSeekScan = 0; /* Opcode of the OP_SeekScan, if any */ + + pIdx = pLoop->u.btree.pIndex; + iIdxCur = pLevel->iIdxCur; + assert( nEq>=pLoop->nSkip ); + + /* Find any inequality constraint terms for the start and end + ** of the range. + */ + j = nEq; + if( pLoop->wsFlags & WHERE_BTM_LIMIT ){ + pRangeStart = pLoop->aLTerm[j++]; + nExtraReg = MAX(nExtraReg, pLoop->u.btree.nBtm); + /* Like optimization range constraints always occur in pairs */ + assert( (pRangeStart->wtFlags & TERM_LIKEOPT)==0 || + (pLoop->wsFlags & WHERE_TOP_LIMIT)!=0 ); + } + if( pLoop->wsFlags & WHERE_TOP_LIMIT ){ + pRangeEnd = pLoop->aLTerm[j++]; + nExtraReg = MAX(nExtraReg, pLoop->u.btree.nTop); +#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS + if( (pRangeEnd->wtFlags & TERM_LIKEOPT)!=0 ){ + assert( pRangeStart!=0 ); /* LIKE opt constraints */ + assert( pRangeStart->wtFlags & TERM_LIKEOPT ); /* occur in pairs */ + pLevel->iLikeRepCntr = (u32)++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 1, (int)pLevel->iLikeRepCntr); + VdbeComment((v, "LIKE loop counter")); + pLevel->addrLikeRep = sqlite3VdbeCurrentAddr(v); + /* iLikeRepCntr actually stores 2x the counter register number. The + ** bottom bit indicates whether the search order is ASC or DESC. */ + testcase( bRev ); + testcase( pIdx->aSortOrder[nEq]==SQLITE_SO_DESC ); + assert( (bRev & ~1)==0 ); + pLevel->iLikeRepCntr <<=1; + pLevel->iLikeRepCntr |= bRev ^ (pIdx->aSortOrder[nEq]==SQLITE_SO_DESC); + } +#endif + if( pRangeStart==0 ){ + j = pIdx->aiColumn[nEq]; + if( (j>=0 && pIdx->pTable->aCol[j].notNull==0) || j==XN_EXPR ){ + bSeekPastNull = 1; + } + } + } + assert( pRangeEnd==0 || (pRangeEnd->wtFlags & TERM_VNULL)==0 ); + + /* If the WHERE_BIGNULL_SORT flag is set, then index column nEq uses + ** a non-default "big-null" sort (either ASC NULLS LAST or DESC NULLS + ** FIRST). In both cases separate ordered scans are made of those + ** index entries for which the column is null and for those for which + ** it is not. For an ASC sort, the non-NULL entries are scanned first. + ** For DESC, NULL entries are scanned first. + */ + if( (pLoop->wsFlags & (WHERE_TOP_LIMIT|WHERE_BTM_LIMIT))==0 + && (pLoop->wsFlags & WHERE_BIGNULL_SORT)!=0 + ){ + assert( bSeekPastNull==0 && nExtraReg==0 && nBtm==0 && nTop==0 ); + assert( pRangeEnd==0 && pRangeStart==0 ); + testcase( pLoop->nSkip>0 ); + nExtraReg = 1; + bSeekPastNull = 1; + pLevel->regBignull = regBignull = ++pParse->nMem; + if( pLevel->iLeftJoin ){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, regBignull); + } + pLevel->addrBignull = sqlite3VdbeMakeLabel(pParse); + } + + /* If we are doing a reverse order scan on an ascending index, or + ** a forward order scan on a descending index, interchange the + ** start and end terms (pRangeStart and pRangeEnd). + */ + if( (nEq<pIdx->nColumn && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) ){ + SWAP(WhereTerm *, pRangeEnd, pRangeStart); + SWAP(u8, bSeekPastNull, bStopAtNull); + SWAP(u8, nBtm, nTop); + } + + if( iLevel>0 && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)!=0 ){ + /* In case OP_SeekScan is used, ensure that the index cursor does not + ** point to a valid row for the first iteration of this loop. */ + sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur); + } + + /* Generate code to evaluate all constraint terms using == or IN + ** and store the values of those terms in an array of registers + ** starting at regBase. + */ + codeCursorHint(pTabItem, pWInfo, pLevel, pRangeEnd); + regBase = codeAllEqualityTerms(pParse,pLevel,bRev,nExtraReg,&zStartAff); + assert( zStartAff==0 || sqlite3Strlen30(zStartAff)>=nEq ); + if( zStartAff && nTop ){ + zEndAff = sqlite3DbStrDup(db, &zStartAff[nEq]); + } + addrNxt = (regBignull ? pLevel->addrBignull : pLevel->addrNxt); + + testcase( pRangeStart && (pRangeStart->eOperator & WO_LE)!=0 ); + testcase( pRangeStart && (pRangeStart->eOperator & WO_GE)!=0 ); + testcase( pRangeEnd && (pRangeEnd->eOperator & WO_LE)!=0 ); + testcase( pRangeEnd && (pRangeEnd->eOperator & WO_GE)!=0 ); + startEq = !pRangeStart || pRangeStart->eOperator & (WO_LE|WO_GE); + endEq = !pRangeEnd || pRangeEnd->eOperator & (WO_LE|WO_GE); + start_constraints = pRangeStart || nEq>0; + + /* Seek the index cursor to the start of the range. */ + nConstraint = nEq; + if( pRangeStart ){ + Expr *pRight = pRangeStart->pExpr->pRight; + codeExprOrVector(pParse, pRight, regBase+nEq, nBtm); + whereLikeOptimizationStringFixup(v, pLevel, pRangeStart); + if( (pRangeStart->wtFlags & TERM_VNULL)==0 + && sqlite3ExprCanBeNull(pRight) + ){ + sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt); + VdbeCoverage(v); + } + if( zStartAff ){ + updateRangeAffinityStr(pRight, nBtm, &zStartAff[nEq]); + } + nConstraint += nBtm; + testcase( pRangeStart->wtFlags & TERM_VIRTUAL ); + if( sqlite3ExprIsVector(pRight)==0 ){ + disableTerm(pLevel, pRangeStart); + }else{ + startEq = 1; + } + bSeekPastNull = 0; + }else if( bSeekPastNull ){ + startEq = 0; + sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); + start_constraints = 1; + nConstraint++; + }else if( regBignull ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); + start_constraints = 1; + nConstraint++; + } + codeApplyAffinity(pParse, regBase, nConstraint - bSeekPastNull, zStartAff); + if( pLoop->nSkip>0 && nConstraint==pLoop->nSkip ){ + /* The skip-scan logic inside the call to codeAllEqualityConstraints() + ** above has already left the cursor sitting on the correct row, + ** so no further seeking is needed */ + }else{ + if( regBignull ){ + sqlite3VdbeAddOp2(v, OP_Integer, 1, regBignull); + VdbeComment((v, "NULL-scan pass ctr")); + } + if( pLevel->regFilter ){ + sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt, + regBase, nEq); + VdbeCoverage(v); + filterPullDown(pParse, pWInfo, iLevel, addrNxt, notReady); + } + + op = aStartOp[(start_constraints<<2) + (startEq<<1) + bRev]; + assert( op!=0 ); + if( (pLoop->wsFlags & WHERE_IN_SEEKSCAN)!=0 && op==OP_SeekGE ){ + assert( regBignull==0 ); + /* TUNING: The OP_SeekScan opcode seeks to reduce the number + ** of expensive seek operations by replacing a single seek with + ** 1 or more step operations. The question is, how many steps + ** should we try before giving up and going with a seek. The cost + ** of a seek is proportional to the logarithm of the of the number + ** of entries in the tree, so basing the number of steps to try + ** on the estimated number of rows in the btree seems like a good + ** guess. */ + addrSeekScan = sqlite3VdbeAddOp1(v, OP_SeekScan, + (pIdx->aiRowLogEst[0]+9)/10); + if( pRangeStart || pRangeEnd ){ + sqlite3VdbeChangeP5(v, 1); + sqlite3VdbeChangeP2(v, addrSeekScan, sqlite3VdbeCurrentAddr(v)+1); + addrSeekScan = 0; + } + VdbeCoverage(v); + } + sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint); + VdbeCoverage(v); + VdbeCoverageIf(v, op==OP_Rewind); testcase( op==OP_Rewind ); + VdbeCoverageIf(v, op==OP_Last); testcase( op==OP_Last ); + VdbeCoverageIf(v, op==OP_SeekGT); testcase( op==OP_SeekGT ); + VdbeCoverageIf(v, op==OP_SeekGE); testcase( op==OP_SeekGE ); + VdbeCoverageIf(v, op==OP_SeekLE); testcase( op==OP_SeekLE ); + VdbeCoverageIf(v, op==OP_SeekLT); testcase( op==OP_SeekLT ); + + assert( bSeekPastNull==0 || bStopAtNull==0 ); + if( regBignull ){ + assert( bSeekPastNull==1 || bStopAtNull==1 ); + assert( bSeekPastNull==!bStopAtNull ); + assert( bStopAtNull==startEq ); + sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); + op = aStartOp[(nConstraint>1)*4 + 2 + bRev]; + sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, + nConstraint-startEq); + VdbeCoverage(v); + VdbeCoverageIf(v, op==OP_Rewind); testcase( op==OP_Rewind ); + VdbeCoverageIf(v, op==OP_Last); testcase( op==OP_Last ); + VdbeCoverageIf(v, op==OP_SeekGE); testcase( op==OP_SeekGE ); + VdbeCoverageIf(v, op==OP_SeekLE); testcase( op==OP_SeekLE ); + assert( op==OP_Rewind || op==OP_Last || op==OP_SeekGE || op==OP_SeekLE); + } + } + + /* Load the value for the inequality constraint at the end of the + ** range (if any). + */ + nConstraint = nEq; + assert( pLevel->p2==0 ); + if( pRangeEnd ){ + Expr *pRight = pRangeEnd->pExpr->pRight; + assert( addrSeekScan==0 ); + codeExprOrVector(pParse, pRight, regBase+nEq, nTop); + whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd); + if( (pRangeEnd->wtFlags & TERM_VNULL)==0 + && sqlite3ExprCanBeNull(pRight) + ){ + sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt); + VdbeCoverage(v); + } + if( zEndAff ){ + updateRangeAffinityStr(pRight, nTop, zEndAff); + codeApplyAffinity(pParse, regBase+nEq, nTop, zEndAff); + }else{ + assert( pParse->db->mallocFailed ); + } + nConstraint += nTop; + testcase( pRangeEnd->wtFlags & TERM_VIRTUAL ); + + if( sqlite3ExprIsVector(pRight)==0 ){ + disableTerm(pLevel, pRangeEnd); + }else{ + endEq = 1; + } + }else if( bStopAtNull ){ + if( regBignull==0 ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); + endEq = 0; + } + nConstraint++; + } + if( zStartAff ) sqlite3DbNNFreeNN(db, zStartAff); + if( zEndAff ) sqlite3DbNNFreeNN(db, zEndAff); + + /* Top of the loop body */ + pLevel->p2 = sqlite3VdbeCurrentAddr(v); + + /* Check if the index cursor is past the end of the range. */ + if( nConstraint ){ + if( regBignull ){ + /* Except, skip the end-of-range check while doing the NULL-scan */ + sqlite3VdbeAddOp2(v, OP_IfNot, regBignull, sqlite3VdbeCurrentAddr(v)+3); + VdbeComment((v, "If NULL-scan 2nd pass")); + VdbeCoverage(v); + } + op = aEndOp[bRev*2 + endEq]; + sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint); + testcase( op==OP_IdxGT ); VdbeCoverageIf(v, op==OP_IdxGT ); + testcase( op==OP_IdxGE ); VdbeCoverageIf(v, op==OP_IdxGE ); + testcase( op==OP_IdxLT ); VdbeCoverageIf(v, op==OP_IdxLT ); + testcase( op==OP_IdxLE ); VdbeCoverageIf(v, op==OP_IdxLE ); + if( addrSeekScan ) sqlite3VdbeJumpHere(v, addrSeekScan); + } + if( regBignull ){ + /* During a NULL-scan, check to see if we have reached the end of + ** the NULLs */ + assert( bSeekPastNull==!bStopAtNull ); + assert( bSeekPastNull+bStopAtNull==1 ); + assert( nConstraint+bSeekPastNull>0 ); + sqlite3VdbeAddOp2(v, OP_If, regBignull, sqlite3VdbeCurrentAddr(v)+2); + VdbeComment((v, "If NULL-scan 1st pass")); + VdbeCoverage(v); + op = aEndOp[bRev*2 + bSeekPastNull]; + sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, + nConstraint+bSeekPastNull); + testcase( op==OP_IdxGT ); VdbeCoverageIf(v, op==OP_IdxGT ); + testcase( op==OP_IdxGE ); VdbeCoverageIf(v, op==OP_IdxGE ); + testcase( op==OP_IdxLT ); VdbeCoverageIf(v, op==OP_IdxLT ); + testcase( op==OP_IdxLE ); VdbeCoverageIf(v, op==OP_IdxLE ); + } + + if( (pLoop->wsFlags & WHERE_IN_EARLYOUT)!=0 ){ + sqlite3VdbeAddOp3(v, OP_SeekHit, iIdxCur, nEq, nEq); + } + + /* Seek the table cursor, if required */ + omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0 + && (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0; + if( omitTable ){ + /* pIdx is a covering index. No need to access the main table. */ + }else if( HasRowid(pIdx->pTable) ){ + codeDeferredSeek(pWInfo, pIdx, iCur, iIdxCur); + }else if( iCur!=iIdxCur ){ + Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable); + iRowidReg = sqlite3GetTempRange(pParse, pPk->nKeyCol); + for(j=0; j<pPk->nKeyCol; j++){ + k = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[j]); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, iRowidReg+j); + } + sqlite3VdbeAddOp4Int(v, OP_NotFound, iCur, addrCont, + iRowidReg, pPk->nKeyCol); VdbeCoverage(v); + } + + if( pLevel->iLeftJoin==0 ){ + /* If a partial index is driving the loop, try to eliminate WHERE clause + ** terms from the query that must be true due to the WHERE clause of + ** the partial index. + ** + ** 2019-11-02 ticket 623eff57e76d45f6: This optimization does not work + ** for a LEFT JOIN. + */ + if( pIdx->pPartIdxWhere ){ + whereApplyPartialIndexConstraints(pIdx->pPartIdxWhere, iCur, pWC); + } + }else{ + testcase( pIdx->pPartIdxWhere ); + /* The following assert() is not a requirement, merely an observation: + ** The OR-optimization doesn't work for the right hand table of + ** a LEFT JOIN: */ + assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 ); + } + + /* Record the instruction used to terminate the loop. */ + if( pLoop->wsFlags & WHERE_ONEROW ){ + pLevel->op = OP_Noop; + }else if( bRev ){ + pLevel->op = OP_Prev; + }else{ + pLevel->op = OP_Next; + } + pLevel->p1 = iIdxCur; + pLevel->p3 = (pLoop->wsFlags&WHERE_UNQ_WANTED)!=0 ? 1:0; + if( (pLoop->wsFlags & WHERE_CONSTRAINT)==0 ){ + pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP; + }else{ + assert( pLevel->p5==0 ); + } + if( omitTable ) pIdx = 0; + }else + +#ifndef SQLITE_OMIT_OR_OPTIMIZATION + if( pLoop->wsFlags & WHERE_MULTI_OR ){ + /* Case 5: Two or more separately indexed terms connected by OR + ** + ** Example: + ** + ** CREATE TABLE t1(a,b,c,d); + ** CREATE INDEX i1 ON t1(a); + ** CREATE INDEX i2 ON t1(b); + ** CREATE INDEX i3 ON t1(c); + ** + ** SELECT * FROM t1 WHERE a=5 OR b=7 OR (c=11 AND d=13) + ** + ** In the example, there are three indexed terms connected by OR. + ** The top of the loop looks like this: + ** + ** Null 1 # Zero the rowset in reg 1 + ** + ** Then, for each indexed term, the following. The arguments to + ** RowSetTest are such that the rowid of the current row is inserted + ** into the RowSet. If it is already present, control skips the + ** Gosub opcode and jumps straight to the code generated by WhereEnd(). + ** + ** sqlite3WhereBegin(<term>) + ** RowSetTest # Insert rowid into rowset + ** Gosub 2 A + ** sqlite3WhereEnd() + ** + ** Following the above, code to terminate the loop. Label A, the target + ** of the Gosub above, jumps to the instruction right after the Goto. + ** + ** Null 1 # Zero the rowset in reg 1 + ** Goto B # The loop is finished. + ** + ** A: <loop body> # Return data, whatever. + ** + ** Return 2 # Jump back to the Gosub + ** + ** B: <after the loop> + ** + ** Added 2014-05-26: If the table is a WITHOUT ROWID table, then + ** use an ephemeral index instead of a RowSet to record the primary + ** keys of the rows we have already seen. + ** + */ + WhereClause *pOrWc; /* The OR-clause broken out into subterms */ + SrcList *pOrTab; /* Shortened table list or OR-clause generation */ + Index *pCov = 0; /* Potential covering index (or NULL) */ + int iCovCur = pParse->nTab++; /* Cursor used for index scans (if any) */ + + int regReturn = ++pParse->nMem; /* Register used with OP_Gosub */ + int regRowset = 0; /* Register for RowSet object */ + int regRowid = 0; /* Register holding rowid */ + int iLoopBody = sqlite3VdbeMakeLabel(pParse);/* Start of loop body */ + int iRetInit; /* Address of regReturn init */ + int untestedTerms = 0; /* Some terms not completely tested */ + int ii; /* Loop counter */ + Expr *pAndExpr = 0; /* An ".. AND (...)" expression */ + Table *pTab = pTabItem->pTab; + + pTerm = pLoop->aLTerm[0]; + assert( pTerm!=0 ); + assert( pTerm->eOperator & WO_OR ); + assert( (pTerm->wtFlags & TERM_ORINFO)!=0 ); + pOrWc = &pTerm->u.pOrInfo->wc; + pLevel->op = OP_Return; + pLevel->p1 = regReturn; + + /* Set up a new SrcList in pOrTab containing the table being scanned + ** by this loop in the a[0] slot and all notReady tables in a[1..] slots. + ** This becomes the SrcList in the recursive call to sqlite3WhereBegin(). + */ + if( pWInfo->nLevel>1 ){ + int nNotReady; /* The number of notReady tables */ + SrcItem *origSrc; /* Original list of tables */ + nNotReady = pWInfo->nLevel - iLevel - 1; + pOrTab = sqlite3DbMallocRawNN(db, + sizeof(*pOrTab)+ nNotReady*sizeof(pOrTab->a[0])); + if( pOrTab==0 ) return notReady; + pOrTab->nAlloc = (u8)(nNotReady + 1); + pOrTab->nSrc = pOrTab->nAlloc; + memcpy(pOrTab->a, pTabItem, sizeof(*pTabItem)); + origSrc = pWInfo->pTabList->a; + for(k=1; k<=nNotReady; k++){ + memcpy(&pOrTab->a[k], &origSrc[pLevel[k].iFrom], sizeof(pOrTab->a[k])); + } + }else{ + pOrTab = pWInfo->pTabList; + } + + /* Initialize the rowset register to contain NULL. An SQL NULL is + ** equivalent to an empty rowset. Or, create an ephemeral index + ** capable of holding primary keys in the case of a WITHOUT ROWID. + ** + ** Also initialize regReturn to contain the address of the instruction + ** immediately following the OP_Return at the bottom of the loop. This + ** is required in a few obscure LEFT JOIN cases where control jumps + ** over the top of the loop into the body of it. In this case the + ** correct response for the end-of-loop code (the OP_Return) is to + ** fall through to the next instruction, just as an OP_Next does if + ** called on an uninitialized cursor. + */ + if( (pWInfo->wctrlFlags & WHERE_DUPLICATES_OK)==0 ){ + if( HasRowid(pTab) ){ + regRowset = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Null, 0, regRowset); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + regRowset = pParse->nTab++; + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, regRowset, pPk->nKeyCol); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + } + regRowid = ++pParse->nMem; + } + iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn); + + /* If the original WHERE clause is z of the form: (x1 OR x2 OR ...) AND y + ** Then for every term xN, evaluate as the subexpression: xN AND y + ** That way, terms in y that are factored into the disjunction will + ** be picked up by the recursive calls to sqlite3WhereBegin() below. + ** + ** Actually, each subexpression is converted to "xN AND w" where w is + ** the "interesting" terms of z - terms that did not originate in the + ** ON or USING clause of a LEFT JOIN, and terms that are usable as + ** indices. + ** + ** This optimization also only applies if the (x1 OR x2 OR ...) term + ** is not contained in the ON clause of a LEFT JOIN. + ** See ticket http://www.sqlite.org/src/info/f2369304e4 + ** + ** 2022-02-04: Do not push down slices of a row-value comparison. + ** In other words, "w" or "y" may not be a slice of a vector. Otherwise, + ** the initialization of the right-hand operand of the vector comparison + ** might not occur, or might occur only in an OR branch that is not + ** taken. dbsqlfuzz 80a9fade844b4fb43564efc972bcb2c68270f5d1. + ** + ** 2022-03-03: Do not push down expressions that involve subqueries. + ** The subquery might get coded as a subroutine. Any table-references + ** in the subquery might be resolved to index-references for the index on + ** the OR branch in which the subroutine is coded. But if the subroutine + ** is invoked from a different OR branch that uses a different index, such + ** index-references will not work. tag-20220303a + ** https://sqlite.org/forum/forumpost/36937b197273d403 + */ + if( pWC->nTerm>1 ){ + int iTerm; + for(iTerm=0; iTerm<pWC->nTerm; iTerm++){ + Expr *pExpr = pWC->a[iTerm].pExpr; + if( &pWC->a[iTerm] == pTerm ) continue; + testcase( pWC->a[iTerm].wtFlags & TERM_VIRTUAL ); + testcase( pWC->a[iTerm].wtFlags & TERM_CODED ); + testcase( pWC->a[iTerm].wtFlags & TERM_SLICE ); + if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED|TERM_SLICE))!=0 ){ + continue; + } + if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue; + if( ExprHasProperty(pExpr, EP_Subquery) ) continue; /* tag-20220303a */ + pExpr = sqlite3ExprDup(db, pExpr, 0); + pAndExpr = sqlite3ExprAnd(pParse, pAndExpr, pExpr); + } + if( pAndExpr ){ + /* The extra 0x10000 bit on the opcode is masked off and does not + ** become part of the new Expr.op. However, it does make the + ** op==TK_AND comparison inside of sqlite3PExpr() false, and this + ** prevents sqlite3PExpr() from applying the AND short-circuit + ** optimization, which we do not want here. */ + pAndExpr = sqlite3PExpr(pParse, TK_AND|0x10000, 0, pAndExpr); + } + } + + /* Run a separate WHERE clause for each term of the OR clause. After + ** eliminating duplicates from other WHERE clauses, the action for each + ** sub-WHERE clause is to to invoke the main loop body as a subroutine. + */ + ExplainQueryPlan((pParse, 1, "MULTI-INDEX OR")); + for(ii=0; ii<pOrWc->nTerm; ii++){ + WhereTerm *pOrTerm = &pOrWc->a[ii]; + if( pOrTerm->leftCursor==iCur || (pOrTerm->eOperator & WO_AND)!=0 ){ + WhereInfo *pSubWInfo; /* Info for single OR-term scan */ + Expr *pOrExpr = pOrTerm->pExpr; /* Current OR clause term */ + Expr *pDelete; /* Local copy of OR clause term */ + int jmp1 = 0; /* Address of jump operation */ + testcase( (pTabItem[0].fg.jointype & JT_LEFT)!=0 + && !ExprHasProperty(pOrExpr, EP_OuterON) + ); /* See TH3 vtab25.400 and ticket 614b25314c766238 */ + pDelete = pOrExpr = sqlite3ExprDup(db, pOrExpr, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDelete); + continue; + } + if( pAndExpr ){ + pAndExpr->pLeft = pOrExpr; + pOrExpr = pAndExpr; + } + /* Loop through table entries that match term pOrTerm. */ + ExplainQueryPlan((pParse, 1, "INDEX %d", ii+1)); + WHERETRACE(0xffffffff, ("Subplan for OR-clause:\n")); + pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, 0, + WHERE_OR_SUBCLAUSE, iCovCur); + assert( pSubWInfo || pParse->nErr ); + if( pSubWInfo ){ + WhereLoop *pSubLoop; + int addrExplain = sqlite3WhereExplainOneScan( + pParse, pOrTab, &pSubWInfo->a[0], 0 + ); + sqlite3WhereAddScanStatus(v, pOrTab, &pSubWInfo->a[0], addrExplain); + + /* This is the sub-WHERE clause body. First skip over + ** duplicate rows from prior sub-WHERE clauses, and record the + ** rowid (or PRIMARY KEY) for the current row so that the same + ** row will be skipped in subsequent sub-WHERE clauses. + */ + if( (pWInfo->wctrlFlags & WHERE_DUPLICATES_OK)==0 ){ + int iSet = ((ii==pOrWc->nTerm-1)?-1:ii); + if( HasRowid(pTab) ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, -1, regRowid); + jmp1 = sqlite3VdbeAddOp4Int(v, OP_RowSetTest, regRowset, 0, + regRowid, iSet); + VdbeCoverage(v); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + int nPk = pPk->nKeyCol; + int iPk; + int r; + + /* Read the PK into an array of temp registers. */ + r = sqlite3GetTempRange(pParse, nPk); + for(iPk=0; iPk<nPk; iPk++){ + int iCol = pPk->aiColumn[iPk]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+iPk); + } + + /* Check if the temp table already contains this key. If so, + ** the row has already been included in the result set and + ** can be ignored (by jumping past the Gosub below). Otherwise, + ** insert the key into the temp table and proceed with processing + ** the row. + ** + ** Use some of the same optimizations as OP_RowSetTest: If iSet + ** is zero, assume that the key cannot already be present in + ** the temp table. And if iSet is -1, assume that there is no + ** need to insert the key into the temp table, as it will never + ** be tested for. */ + if( iSet ){ + jmp1 = sqlite3VdbeAddOp4Int(v, OP_Found, regRowset, 0, r, nPk); + VdbeCoverage(v); + } + if( iSet>=0 ){ + sqlite3VdbeAddOp3(v, OP_MakeRecord, r, nPk, regRowid); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, regRowset, regRowid, + r, nPk); + if( iSet ) sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + } + + /* Release the array of temp registers */ + sqlite3ReleaseTempRange(pParse, r, nPk); + } + } + + /* Invoke the main loop body as a subroutine */ + sqlite3VdbeAddOp2(v, OP_Gosub, regReturn, iLoopBody); + + /* Jump here (skipping the main loop body subroutine) if the + ** current sub-WHERE row is a duplicate from prior sub-WHEREs. */ + if( jmp1 ) sqlite3VdbeJumpHere(v, jmp1); + + /* The pSubWInfo->untestedTerms flag means that this OR term + ** contained one or more AND term from a notReady table. The + ** terms from the notReady table could not be tested and will + ** need to be tested later. + */ + if( pSubWInfo->untestedTerms ) untestedTerms = 1; + + /* If all of the OR-connected terms are optimized using the same + ** index, and the index is opened using the same cursor number + ** by each call to sqlite3WhereBegin() made by this loop, it may + ** be possible to use that index as a covering index. + ** + ** If the call to sqlite3WhereBegin() above resulted in a scan that + ** uses an index, and this is either the first OR-connected term + ** processed or the index is the same as that used by all previous + ** terms, set pCov to the candidate covering index. Otherwise, set + ** pCov to NULL to indicate that no candidate covering index will + ** be available. + */ + pSubLoop = pSubWInfo->a[0].pWLoop; + assert( (pSubLoop->wsFlags & WHERE_AUTO_INDEX)==0 ); + if( (pSubLoop->wsFlags & WHERE_INDEXED)!=0 + && (ii==0 || pSubLoop->u.btree.pIndex==pCov) + && (HasRowid(pTab) || !IsPrimaryKeyIndex(pSubLoop->u.btree.pIndex)) + ){ + assert( pSubWInfo->a[0].iIdxCur==iCovCur ); + pCov = pSubLoop->u.btree.pIndex; + }else{ + pCov = 0; + } + if( sqlite3WhereUsesDeferredSeek(pSubWInfo) ){ + pWInfo->bDeferredSeek = 1; + } + + /* Finish the loop through table entries that match term pOrTerm. */ + sqlite3WhereEnd(pSubWInfo); + ExplainQueryPlanPop(pParse); + } + sqlite3ExprDelete(db, pDelete); + } + } + ExplainQueryPlanPop(pParse); + assert( pLevel->pWLoop==pLoop ); + assert( (pLoop->wsFlags & WHERE_MULTI_OR)!=0 ); + assert( (pLoop->wsFlags & WHERE_IN_ABLE)==0 ); + pLevel->u.pCoveringIdx = pCov; + if( pCov ) pLevel->iIdxCur = iCovCur; + if( pAndExpr ){ + pAndExpr->pLeft = 0; + sqlite3ExprDelete(db, pAndExpr); + } + sqlite3VdbeChangeP1(v, iRetInit, sqlite3VdbeCurrentAddr(v)); + sqlite3VdbeGoto(v, pLevel->addrBrk); + sqlite3VdbeResolveLabel(v, iLoopBody); + + /* Set the P2 operand of the OP_Return opcode that will end the current + ** loop to point to this spot, which is the top of the next containing + ** loop. The byte-code formatter will use that P2 value as a hint to + ** indent everything in between the this point and the final OP_Return. + ** See tag-20220407a in vdbe.c and shell.c */ + assert( pLevel->op==OP_Return ); + pLevel->p2 = sqlite3VdbeCurrentAddr(v); + + if( pWInfo->nLevel>1 ){ sqlite3DbFreeNN(db, pOrTab); } + if( !untestedTerms ) disableTerm(pLevel, pTerm); + }else +#endif /* SQLITE_OMIT_OR_OPTIMIZATION */ + + { + /* Case 6: There is no usable index. We must do a complete + ** scan of the entire table. + */ + static const u8 aStep[] = { OP_Next, OP_Prev }; + static const u8 aStart[] = { OP_Rewind, OP_Last }; + assert( bRev==0 || bRev==1 ); + if( pTabItem->fg.isRecursive ){ + /* Tables marked isRecursive have only a single row that is stored in + ** a pseudo-cursor. No need to Rewind or Next such cursors. */ + pLevel->op = OP_Noop; + }else{ + codeCursorHint(pTabItem, pWInfo, pLevel, 0); + pLevel->op = aStep[bRev]; + pLevel->p1 = iCur; + pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrHalt); + VdbeCoverageIf(v, bRev==0); + VdbeCoverageIf(v, bRev!=0); + pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP; + } + } + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pLevel->addrVisit = sqlite3VdbeCurrentAddr(v); +#endif + + /* Insert code to test every subexpression that can be completely + ** computed using the current set of tables. + ** + ** This loop may run between one and three times, depending on the + ** constraints to be generated. The value of stack variable iLoop + ** determines the constraints coded by each iteration, as follows: + ** + ** iLoop==1: Code only expressions that are entirely covered by pIdx. + ** iLoop==2: Code remaining expressions that do not contain correlated + ** sub-queries. + ** iLoop==3: Code all remaining expressions. + ** + ** An effort is made to skip unnecessary iterations of the loop. + */ + iLoop = (pIdx ? 1 : 2); + do{ + int iNext = 0; /* Next value for iLoop */ + for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ + Expr *pE; + int skipLikeAddr = 0; + testcase( pTerm->wtFlags & TERM_VIRTUAL ); + testcase( pTerm->wtFlags & TERM_CODED ); + if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; + if( (pTerm->prereqAll & pLevel->notReady)!=0 ){ + testcase( pWInfo->untestedTerms==0 + && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)!=0 ); + pWInfo->untestedTerms = 1; + continue; + } + pE = pTerm->pExpr; + assert( pE!=0 ); + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){ + if( !ExprHasProperty(pE,EP_OuterON|EP_InnerON) ){ + /* Defer processing WHERE clause constraints until after outer + ** join processing. tag-20220513a */ + continue; + }else if( (pTabItem->fg.jointype & JT_LEFT)==JT_LEFT + && !ExprHasProperty(pE,EP_OuterON) ){ + continue; + }else{ + Bitmask m = sqlite3WhereGetMask(&pWInfo->sMaskSet, pE->w.iJoin); + if( m & pLevel->notReady ){ + /* An ON clause that is not ripe */ + continue; + } + } + } + if( iLoop==1 && !sqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){ + iNext = 2; + continue; + } + if( iLoop<3 && (pTerm->wtFlags & TERM_VARSELECT) ){ + if( iNext==0 ) iNext = 3; + continue; + } + + if( (pTerm->wtFlags & TERM_LIKECOND)!=0 ){ + /* If the TERM_LIKECOND flag is set, that means that the range search + ** is sufficient to guarantee that the LIKE operator is true, so we + ** can skip the call to the like(A,B) function. But this only works + ** for strings. So do not skip the call to the function on the pass + ** that compares BLOBs. */ +#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS + continue; +#else + u32 x = pLevel->iLikeRepCntr; + if( x>0 ){ + skipLikeAddr = sqlite3VdbeAddOp1(v, (x&1)?OP_IfNot:OP_If,(int)(x>>1)); + VdbeCoverageIf(v, (x&1)==1); + VdbeCoverageIf(v, (x&1)==0); + } +#endif + } +#ifdef WHERETRACE_ENABLED /* 0xffffffff */ + if( sqlite3WhereTrace ){ + VdbeNoopComment((v, "WhereTerm[%d] (%p) priority=%d", + pWC->nTerm-j, pTerm, iLoop)); + } + if( sqlite3WhereTrace & 0x4000 ){ + sqlite3DebugPrintf("Coding auxiliary constraint:\n"); + sqlite3WhereTermPrint(pTerm, pWC->nTerm-j); + } +#endif + sqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL); + if( skipLikeAddr ) sqlite3VdbeJumpHere(v, skipLikeAddr); + pTerm->wtFlags |= TERM_CODED; + } + iLoop = iNext; + }while( iLoop>0 ); + + /* Insert code to test for implied constraints based on transitivity + ** of the "==" operator. + ** + ** Example: If the WHERE clause contains "t1.a=t2.b" and "t2.b=123" + ** and we are coding the t1 loop and the t2 loop has not yet coded, + ** then we cannot use the "t1.a=t2.b" constraint, but we can code + ** the implied "t1.a=123" constraint. + */ + for(pTerm=pWC->a, j=pWC->nBase; j>0; j--, pTerm++){ + Expr *pE, sEAlt; + WhereTerm *pAlt; + if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; + if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) continue; + if( (pTerm->eOperator & WO_EQUIV)==0 ) continue; + if( pTerm->leftCursor!=iCur ) continue; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ) continue; + pE = pTerm->pExpr; +#ifdef WHERETRACE_ENABLED /* 0x4001 */ + if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ + sqlite3DebugPrintf("Coding transitive constraint:\n"); + sqlite3WhereTermPrint(pTerm, pWC->nTerm-j); + } +#endif + assert( !ExprHasProperty(pE, EP_OuterON) ); + assert( (pTerm->prereqRight & pLevel->notReady)!=0 ); + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + pAlt = sqlite3WhereFindTerm(pWC, iCur, pTerm->u.x.leftColumn, notReady, + WO_EQ|WO_IN|WO_IS, 0); + if( pAlt==0 ) continue; + if( pAlt->wtFlags & (TERM_CODED) ) continue; + if( (pAlt->eOperator & WO_IN) + && ExprUseXSelect(pAlt->pExpr) + && (pAlt->pExpr->x.pSelect->pEList->nExpr>1) + ){ + continue; + } + testcase( pAlt->eOperator & WO_EQ ); + testcase( pAlt->eOperator & WO_IS ); + testcase( pAlt->eOperator & WO_IN ); + VdbeModuleComment((v, "begin transitive constraint")); + sEAlt = *pAlt->pExpr; + sEAlt.pLeft = pE->pLeft; + sqlite3ExprIfFalse(pParse, &sEAlt, addrCont, SQLITE_JUMPIFNULL); + pAlt->wtFlags |= TERM_CODED; + } + + /* For a RIGHT OUTER JOIN, record the fact that the current row has + ** been matched at least once. + */ + if( pLevel->pRJ ){ + Table *pTab; + int nPk; + int r; + int jmp1 = 0; + WhereRightJoin *pRJ = pLevel->pRJ; + + /* pTab is the right-hand table of the RIGHT JOIN. Generate code that + ** will record that the current row of that table has been matched at + ** least once. This is accomplished by storing the PK for the row in + ** both the iMatch index and the regBloom Bloom filter. + */ + pTab = pWInfo->pTabList->a[pLevel->iFrom].pTab; + if( HasRowid(pTab) ){ + r = sqlite3GetTempRange(pParse, 2); + sqlite3ExprCodeGetColumnOfTable(v, pTab, pLevel->iTabCur, -1, r+1); + nPk = 1; + }else{ + int iPk; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + nPk = pPk->nKeyCol; + r = sqlite3GetTempRange(pParse, nPk+1); + for(iPk=0; iPk<nPk; iPk++){ + int iCol = pPk->aiColumn[iPk]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+1+iPk); + } + } + jmp1 = sqlite3VdbeAddOp4Int(v, OP_Found, pRJ->iMatch, 0, r+1, nPk); + VdbeCoverage(v); + VdbeComment((v, "match against %s", pTab->zName)); + sqlite3VdbeAddOp3(v, OP_MakeRecord, r+1, nPk, r); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pRJ->iMatch, r, r+1, nPk); + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pRJ->regBloom, 0, r+1, nPk); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3VdbeJumpHere(v, jmp1); + sqlite3ReleaseTempRange(pParse, r, nPk+1); + } + + /* For a LEFT OUTER JOIN, generate code that will record the fact that + ** at least one row of the right table has matched the left table. + */ + if( pLevel->iLeftJoin ){ + pLevel->addrFirst = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp2(v, OP_Integer, 1, pLevel->iLeftJoin); + VdbeComment((v, "record LEFT JOIN hit")); + if( pLevel->pRJ==0 ){ + goto code_outer_join_constraints; /* WHERE clause constraints */ + } + } + + if( pLevel->pRJ ){ + /* Create a subroutine used to process all interior loops and code + ** of the RIGHT JOIN. During normal operation, the subroutine will + ** be in-line with the rest of the code. But at the end, a separate + ** loop will run that invokes this subroutine for unmatched rows + ** of pTab, with all tables to left begin set to NULL. + */ + WhereRightJoin *pRJ = pLevel->pRJ; + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pRJ->regReturn); + pRJ->addrSubrtn = sqlite3VdbeCurrentAddr(v); + assert( pParse->withinRJSubrtn < 255 ); + pParse->withinRJSubrtn++; + + /* WHERE clause constraints must be deferred until after outer join + ** row elimination has completed, since WHERE clause constraints apply + ** to the results of the OUTER JOIN. The following loop generates the + ** appropriate WHERE clause constraint checks. tag-20220513a. + */ + code_outer_join_constraints: + for(pTerm=pWC->a, j=0; j<pWC->nBase; j++, pTerm++){ + testcase( pTerm->wtFlags & TERM_VIRTUAL ); + testcase( pTerm->wtFlags & TERM_CODED ); + if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; + if( (pTerm->prereqAll & pLevel->notReady)!=0 ){ + assert( pWInfo->untestedTerms ); + continue; + } + if( pTabItem->fg.jointype & JT_LTORJ ) continue; + assert( pTerm->pExpr ); + sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); + pTerm->wtFlags |= TERM_CODED; + } + } + +#if WHERETRACE_ENABLED /* 0x4001 */ + if( sqlite3WhereTrace & 0x4000 ){ + sqlite3DebugPrintf("All WHERE-clause terms after coding level %d:\n", + iLevel); + sqlite3WhereClausePrint(pWC); + } + if( sqlite3WhereTrace & 0x1 ){ + sqlite3DebugPrintf("End Coding level %d: notReady=%llx\n", + iLevel, (u64)pLevel->notReady); + } +#endif + return pLevel->notReady; +} + +/* +** Generate the code for the loop that finds all non-matched terms +** for a RIGHT JOIN. +*/ +SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( + WhereInfo *pWInfo, + int iLevel, + WhereLevel *pLevel +){ + Parse *pParse = pWInfo->pParse; + Vdbe *v = pParse->pVdbe; + WhereRightJoin *pRJ = pLevel->pRJ; + Expr *pSubWhere = 0; + WhereClause *pWC = &pWInfo->sWC; + WhereInfo *pSubWInfo; + WhereLoop *pLoop = pLevel->pWLoop; + SrcItem *pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; + SrcList sFrom; + Bitmask mAll = 0; + int k; + + ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pTab->zName)); + sqlite3VdbeNoJumpsOutsideSubrtn(v, pRJ->addrSubrtn, pRJ->endSubrtn, + pRJ->regReturn); + for(k=0; k<iLevel; k++){ + int iIdxCur; + mAll |= pWInfo->a[k].pWLoop->maskSelf; + sqlite3VdbeAddOp1(v, OP_NullRow, pWInfo->a[k].iTabCur); + iIdxCur = pWInfo->a[k].iIdxCur; + if( iIdxCur ){ + sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur); + } + } + if( (pTabItem->fg.jointype & JT_LTORJ)==0 ){ + mAll |= pLoop->maskSelf; + for(k=0; k<pWC->nTerm; k++){ + WhereTerm *pTerm = &pWC->a[k]; + if( (pTerm->wtFlags & (TERM_VIRTUAL|TERM_SLICE))!=0 + && pTerm->eOperator!=WO_ROWVAL + ){ + break; + } + if( pTerm->prereqAll & ~mAll ) continue; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) ) continue; + pSubWhere = sqlite3ExprAnd(pParse, pSubWhere, + sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); + } + } + sFrom.nSrc = 1; + sFrom.nAlloc = 1; + memcpy(&sFrom.a[0], pTabItem, sizeof(SrcItem)); + sFrom.a[0].fg.jointype = 0; + assert( pParse->withinRJSubrtn < 100 ); + pParse->withinRJSubrtn++; + pSubWInfo = sqlite3WhereBegin(pParse, &sFrom, pSubWhere, 0, 0, 0, + WHERE_RIGHT_JOIN, 0); + if( pSubWInfo ){ + int iCur = pLevel->iTabCur; + int r = ++pParse->nMem; + int nPk; + int jmp; + int addrCont = sqlite3WhereContinueLabel(pSubWInfo); + Table *pTab = pTabItem->pTab; + if( HasRowid(pTab) ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, -1, r); + nPk = 1; + }else{ + int iPk; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + nPk = pPk->nKeyCol; + pParse->nMem += nPk - 1; + for(iPk=0; iPk<nPk; iPk++){ + int iCol = pPk->aiColumn[iPk]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+iPk); + } + } + jmp = sqlite3VdbeAddOp4Int(v, OP_Filter, pRJ->regBloom, 0, r, nPk); + VdbeCoverage(v); + sqlite3VdbeAddOp4Int(v, OP_Found, pRJ->iMatch, addrCont, r, nPk); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, jmp); + sqlite3VdbeAddOp2(v, OP_Gosub, pRJ->regReturn, pRJ->addrSubrtn); + sqlite3WhereEnd(pSubWInfo); + } + sqlite3ExprDelete(pParse->db, pSubWhere); + ExplainQueryPlanPop(pParse); + assert( pParse->withinRJSubrtn>0 ); + pParse->withinRJSubrtn--; +} + +/************** End of wherecode.c *******************************************/ +/************** Begin file whereexpr.c ***************************************/ +/* +** 2015-06-08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This module contains C code that generates VDBE code used to process +** the WHERE clause of SQL statements. +** +** This file was originally part of where.c but was split out to improve +** readability and editability. This file contains utility routines for +** analyzing Expr objects in the WHERE clause. +*/ +/* #include "sqliteInt.h" */ +/* #include "whereInt.h" */ + +/* Forward declarations */ +static void exprAnalyze(SrcList*, WhereClause*, int); + +/* +** Deallocate all memory associated with a WhereOrInfo object. +*/ +static void whereOrInfoDelete(sqlite3 *db, WhereOrInfo *p){ + sqlite3WhereClauseClear(&p->wc); + sqlite3DbFree(db, p); +} + +/* +** Deallocate all memory associated with a WhereAndInfo object. +*/ +static void whereAndInfoDelete(sqlite3 *db, WhereAndInfo *p){ + sqlite3WhereClauseClear(&p->wc); + sqlite3DbFree(db, p); +} + +/* +** Add a single new WhereTerm entry to the WhereClause object pWC. +** The new WhereTerm object is constructed from Expr p and with wtFlags. +** The index in pWC->a[] of the new WhereTerm is returned on success. +** 0 is returned if the new WhereTerm could not be added due to a memory +** allocation error. The memory allocation failure will be recorded in +** the db->mallocFailed flag so that higher-level functions can detect it. +** +** This routine will increase the size of the pWC->a[] array as necessary. +** +** If the wtFlags argument includes TERM_DYNAMIC, then responsibility +** for freeing the expression p is assumed by the WhereClause object pWC. +** This is true even if this routine fails to allocate a new WhereTerm. +** +** WARNING: This routine might reallocate the space used to store +** WhereTerms. All pointers to WhereTerms should be invalidated after +** calling this routine. Such pointers may be reinitialized by referencing +** the pWC->a[] array. +*/ +static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ + WhereTerm *pTerm; + int idx; + testcase( wtFlags & TERM_VIRTUAL ); + if( pWC->nTerm>=pWC->nSlot ){ + WhereTerm *pOld = pWC->a; + sqlite3 *db = pWC->pWInfo->pParse->db; + pWC->a = sqlite3WhereMalloc(pWC->pWInfo, sizeof(pWC->a[0])*pWC->nSlot*2 ); + if( pWC->a==0 ){ + if( wtFlags & TERM_DYNAMIC ){ + sqlite3ExprDelete(db, p); + } + pWC->a = pOld; + return 0; + } + memcpy(pWC->a, pOld, sizeof(pWC->a[0])*pWC->nTerm); + pWC->nSlot = pWC->nSlot*2; + } + pTerm = &pWC->a[idx = pWC->nTerm++]; + if( (wtFlags & TERM_VIRTUAL)==0 ) pWC->nBase = pWC->nTerm; + if( p && ExprHasProperty(p, EP_Unlikely) ){ + pTerm->truthProb = sqlite3LogEst(p->iTable) - 270; + }else{ + pTerm->truthProb = 1; + } + pTerm->pExpr = sqlite3ExprSkipCollateAndLikely(p); + pTerm->wtFlags = wtFlags; + pTerm->pWC = pWC; + pTerm->iParent = -1; + memset(&pTerm->eOperator, 0, + sizeof(WhereTerm) - offsetof(WhereTerm,eOperator)); + return idx; +} + +/* +** Return TRUE if the given operator is one of the operators that is +** allowed for an indexable WHERE clause term. The allowed operators are +** "=", "<", ">", "<=", ">=", "IN", "IS", and "IS NULL" +*/ +static int allowedOp(int op){ + assert( TK_GT>TK_EQ && TK_GT<TK_GE ); + assert( TK_LT>TK_EQ && TK_LT<TK_GE ); + assert( TK_LE>TK_EQ && TK_LE<TK_GE ); + assert( TK_GE==TK_EQ+4 ); + return op==TK_IN || (op>=TK_EQ && op<=TK_GE) || op==TK_ISNULL || op==TK_IS; +} + +/* +** Commute a comparison operator. Expressions of the form "X op Y" +** are converted into "Y op X". +*/ +static u16 exprCommute(Parse *pParse, Expr *pExpr){ + if( pExpr->pLeft->op==TK_VECTOR + || pExpr->pRight->op==TK_VECTOR + || sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pExpr->pRight) != + sqlite3BinaryCompareCollSeq(pParse, pExpr->pRight, pExpr->pLeft) + ){ + pExpr->flags ^= EP_Commuted; + } + SWAP(Expr*,pExpr->pRight,pExpr->pLeft); + if( pExpr->op>=TK_GT ){ + assert( TK_LT==TK_GT+2 ); + assert( TK_GE==TK_LE+2 ); + assert( TK_GT>TK_EQ ); + assert( TK_GT<TK_LE ); + assert( pExpr->op>=TK_GT && pExpr->op<=TK_GE ); + pExpr->op = ((pExpr->op-TK_GT)^2)+TK_GT; + } + return 0; +} + +/* +** Translate from TK_xx operator to WO_xx bitmask. +*/ +static u16 operatorMask(int op){ + u16 c; + assert( allowedOp(op) ); + if( op==TK_IN ){ + c = WO_IN; + }else if( op==TK_ISNULL ){ + c = WO_ISNULL; + }else if( op==TK_IS ){ + c = WO_IS; + }else{ + assert( (WO_EQ<<(op-TK_EQ)) < 0x7fff ); + c = (u16)(WO_EQ<<(op-TK_EQ)); + } + assert( op!=TK_ISNULL || c==WO_ISNULL ); + assert( op!=TK_IN || c==WO_IN ); + assert( op!=TK_EQ || c==WO_EQ ); + assert( op!=TK_LT || c==WO_LT ); + assert( op!=TK_LE || c==WO_LE ); + assert( op!=TK_GT || c==WO_GT ); + assert( op!=TK_GE || c==WO_GE ); + assert( op!=TK_IS || c==WO_IS ); + return c; +} + + +#ifndef SQLITE_OMIT_LIKE_OPTIMIZATION +/* +** Check to see if the given expression is a LIKE or GLOB operator that +** can be optimized using inequality constraints. Return TRUE if it is +** so and false if not. +** +** In order for the operator to be optimizible, the RHS must be a string +** literal that does not begin with a wildcard. The LHS must be a column +** that may only be NULL, a string, or a BLOB, never a number. (This means +** that virtual tables cannot participate in the LIKE optimization.) The +** collating sequence for the column on the LHS must be appropriate for +** the operator. +*/ +static int isLikeOrGlob( + Parse *pParse, /* Parsing and code generating context */ + Expr *pExpr, /* Test this expression */ + Expr **ppPrefix, /* Pointer to TK_STRING expression with pattern prefix */ + int *pisComplete, /* True if the only wildcard is % in the last character */ + int *pnoCase /* True if uppercase is equivalent to lowercase */ +){ + const u8 *z = 0; /* String on RHS of LIKE operator */ + Expr *pRight, *pLeft; /* Right and left size of LIKE operator */ + ExprList *pList; /* List of operands to the LIKE operator */ + u8 c; /* One character in z[] */ + int cnt; /* Number of non-wildcard prefix characters */ + u8 wc[4]; /* Wildcard characters */ + sqlite3 *db = pParse->db; /* Database connection */ + sqlite3_value *pVal = 0; + int op; /* Opcode of pRight */ + int rc; /* Result code to return */ + + if( !sqlite3IsLikeFunction(db, pExpr, pnoCase, (char*)wc) ){ + return 0; + } +#ifdef SQLITE_EBCDIC + if( *pnoCase ) return 0; +#endif + assert( ExprUseXList(pExpr) ); + pList = pExpr->x.pList; + pLeft = pList->a[1].pExpr; + + pRight = sqlite3ExprSkipCollate(pList->a[0].pExpr); + op = pRight->op; + if( op==TK_VARIABLE && (db->flags & SQLITE_EnableQPSG)==0 ){ + Vdbe *pReprepare = pParse->pReprepare; + int iCol = pRight->iColumn; + pVal = sqlite3VdbeGetBoundValue(pReprepare, iCol, SQLITE_AFF_BLOB); + if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){ + z = sqlite3_value_text(pVal); + } + sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); + assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER ); + }else if( op==TK_STRING ){ + assert( !ExprHasProperty(pRight, EP_IntValue) ); + z = (u8*)pRight->u.zToken; + } + if( z ){ + + /* Count the number of prefix characters prior to the first wildcard */ + cnt = 0; + while( (c=z[cnt])!=0 && c!=wc[0] && c!=wc[1] && c!=wc[2] ){ + cnt++; + if( c==wc[3] && z[cnt]!=0 ) cnt++; + } + + /* The optimization is possible only if (1) the pattern does not begin + ** with a wildcard and if (2) the non-wildcard prefix does not end with + ** an (illegal 0xff) character, or (3) the pattern does not consist of + ** a single escape character. The second condition is necessary so + ** that we can increment the prefix key to find an upper bound for the + ** range search. The third is because the caller assumes that the pattern + ** consists of at least one character after all escapes have been + ** removed. */ + if( (cnt>1 || (cnt>0 && z[0]!=wc[3])) && 255!=(u8)z[cnt-1] ){ + Expr *pPrefix; + + /* A "complete" match if the pattern ends with "*" or "%" */ + *pisComplete = c==wc[0] && z[cnt+1]==0; + + /* Get the pattern prefix. Remove all escapes from the prefix. */ + pPrefix = sqlite3Expr(db, TK_STRING, (char*)z); + if( pPrefix ){ + int iFrom, iTo; + char *zNew; + assert( !ExprHasProperty(pPrefix, EP_IntValue) ); + zNew = pPrefix->u.zToken; + zNew[cnt] = 0; + for(iFrom=iTo=0; iFrom<cnt; iFrom++){ + if( zNew[iFrom]==wc[3] ) iFrom++; + zNew[iTo++] = zNew[iFrom]; + } + zNew[iTo] = 0; + assert( iTo>0 ); + + /* If the LHS is not an ordinary column with TEXT affinity, then the + ** pattern prefix boundaries (both the start and end boundaries) must + ** not look like a number. Otherwise the pattern might be treated as + ** a number, which will invalidate the LIKE optimization. + ** + ** Getting this right has been a persistent source of bugs in the + ** LIKE optimization. See, for example: + ** 2018-09-10 https://sqlite.org/src/info/c94369cae9b561b1 + ** 2019-05-02 https://sqlite.org/src/info/b043a54c3de54b28 + ** 2019-06-10 https://sqlite.org/src/info/fd76310a5e843e07 + ** 2019-06-14 https://sqlite.org/src/info/ce8717f0885af975 + ** 2019-09-03 https://sqlite.org/src/info/0f0428096f17252a + */ + if( pLeft->op!=TK_COLUMN + || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT + || (ALWAYS( ExprUseYTab(pLeft) ) + && ALWAYS(pLeft->y.pTab) + && IsVirtual(pLeft->y.pTab)) /* Might be numeric */ + ){ + int isNum; + double rDummy; + isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8); + if( isNum<=0 ){ + if( iTo==1 && zNew[0]=='-' ){ + isNum = +1; + }else{ + zNew[iTo-1]++; + isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8); + zNew[iTo-1]--; + } + } + if( isNum>0 ){ + sqlite3ExprDelete(db, pPrefix); + sqlite3ValueFree(pVal); + return 0; + } + } + } + *ppPrefix = pPrefix; + + /* If the RHS pattern is a bound parameter, make arrangements to + ** reprepare the statement when that parameter is rebound */ + if( op==TK_VARIABLE ){ + Vdbe *v = pParse->pVdbe; + sqlite3VdbeSetVarmask(v, pRight->iColumn); + assert( !ExprHasProperty(pRight, EP_IntValue) ); + if( *pisComplete && pRight->u.zToken[1] ){ + /* If the rhs of the LIKE expression is a variable, and the current + ** value of the variable means there is no need to invoke the LIKE + ** function, then no OP_Variable will be added to the program. + ** This causes problems for the sqlite3_bind_parameter_name() + ** API. To work around them, add a dummy OP_Variable here. + */ + int r1 = sqlite3GetTempReg(pParse); + sqlite3ExprCodeTarget(pParse, pRight, r1); + sqlite3VdbeChangeP3(v, sqlite3VdbeCurrentAddr(v)-1, 0); + sqlite3ReleaseTempReg(pParse, r1); + } + } + }else{ + z = 0; + } + } + + rc = (z!=0); + sqlite3ValueFree(pVal); + return rc; +} +#endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ + + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Check to see if the pExpr expression is a form that needs to be passed +** to the xBestIndex method of virtual tables. Forms of interest include: +** +** Expression Virtual Table Operator +** ----------------------- --------------------------------- +** 1. column MATCH expr SQLITE_INDEX_CONSTRAINT_MATCH +** 2. column GLOB expr SQLITE_INDEX_CONSTRAINT_GLOB +** 3. column LIKE expr SQLITE_INDEX_CONSTRAINT_LIKE +** 4. column REGEXP expr SQLITE_INDEX_CONSTRAINT_REGEXP +** 5. column != expr SQLITE_INDEX_CONSTRAINT_NE +** 6. expr != column SQLITE_INDEX_CONSTRAINT_NE +** 7. column IS NOT expr SQLITE_INDEX_CONSTRAINT_ISNOT +** 8. expr IS NOT column SQLITE_INDEX_CONSTRAINT_ISNOT +** 9. column IS NOT NULL SQLITE_INDEX_CONSTRAINT_ISNOTNULL +** +** In every case, "column" must be a column of a virtual table. If there +** is a match, set *ppLeft to the "column" expression, set *ppRight to the +** "expr" expression (even though in forms (6) and (8) the column is on the +** right and the expression is on the left). Also set *peOp2 to the +** appropriate virtual table operator. The return value is 1 or 2 if there +** is a match. The usual return is 1, but if the RHS is also a column +** of virtual table in forms (5) or (7) then return 2. +** +** If the expression matches none of the patterns above, return 0. +*/ +static int isAuxiliaryVtabOperator( + sqlite3 *db, /* Parsing context */ + Expr *pExpr, /* Test this expression */ + unsigned char *peOp2, /* OUT: 0 for MATCH, or else an op2 value */ + Expr **ppLeft, /* Column expression to left of MATCH/op2 */ + Expr **ppRight /* Expression to left of MATCH/op2 */ +){ + if( pExpr->op==TK_FUNCTION ){ + static const struct Op2 { + const char *zOp; + unsigned char eOp2; + } aOp[] = { + { "match", SQLITE_INDEX_CONSTRAINT_MATCH }, + { "glob", SQLITE_INDEX_CONSTRAINT_GLOB }, + { "like", SQLITE_INDEX_CONSTRAINT_LIKE }, + { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP } + }; + ExprList *pList; + Expr *pCol; /* Column reference */ + int i; + + assert( ExprUseXList(pExpr) ); + pList = pExpr->x.pList; + if( pList==0 || pList->nExpr!=2 ){ + return 0; + } + + /* Built-in operators MATCH, GLOB, LIKE, and REGEXP attach to a + ** virtual table on their second argument, which is the same as + ** the left-hand side operand in their in-fix form. + ** + ** vtab_column MATCH expression + ** MATCH(expression,vtab_column) + */ + pCol = pList->a[1].pExpr; + assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) ); + if( ExprIsVtab(pCol) ){ + for(i=0; i<ArraySize(aOp); i++){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + if( sqlite3StrICmp(pExpr->u.zToken, aOp[i].zOp)==0 ){ + *peOp2 = aOp[i].eOp2; + *ppRight = pList->a[0].pExpr; + *ppLeft = pCol; + return 1; + } + } + } + + /* We can also match against the first column of overloaded + ** functions where xFindFunction returns a value of at least + ** SQLITE_INDEX_CONSTRAINT_FUNCTION. + ** + ** OVERLOADED(vtab_column,expression) + ** + ** Historically, xFindFunction expected to see lower-case function + ** names. But for this use case, xFindFunction is expected to deal + ** with function names in an arbitrary case. + */ + pCol = pList->a[0].pExpr; + assert( pCol->op!=TK_COLUMN || ExprUseYTab(pCol) ); + assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) ); + if( ExprIsVtab(pCol) ){ + sqlite3_vtab *pVtab; + sqlite3_module *pMod; + void (*xNotUsed)(sqlite3_context*,int,sqlite3_value**); + void *pNotUsed; + pVtab = sqlite3GetVTable(db, pCol->y.pTab)->pVtab; + assert( pVtab!=0 ); + assert( pVtab->pModule!=0 ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + pMod = (sqlite3_module *)pVtab->pModule; + if( pMod->xFindFunction!=0 ){ + i = pMod->xFindFunction(pVtab,2, pExpr->u.zToken, &xNotUsed, &pNotUsed); + if( i>=SQLITE_INDEX_CONSTRAINT_FUNCTION ){ + *peOp2 = i; + *ppRight = pList->a[1].pExpr; + *ppLeft = pCol; + return 1; + } + } + } + }else if( pExpr->op==TK_NE || pExpr->op==TK_ISNOT || pExpr->op==TK_NOTNULL ){ + int res = 0; + Expr *pLeft = pExpr->pLeft; + Expr *pRight = pExpr->pRight; + assert( pLeft->op!=TK_COLUMN || (ExprUseYTab(pLeft) && pLeft->y.pTab!=0) ); + if( ExprIsVtab(pLeft) ){ + res++; + } + assert( pRight==0 || pRight->op!=TK_COLUMN + || (ExprUseYTab(pRight) && pRight->y.pTab!=0) ); + if( pRight && ExprIsVtab(pRight) ){ + res++; + SWAP(Expr*, pLeft, pRight); + } + *ppLeft = pLeft; + *ppRight = pRight; + if( pExpr->op==TK_NE ) *peOp2 = SQLITE_INDEX_CONSTRAINT_NE; + if( pExpr->op==TK_ISNOT ) *peOp2 = SQLITE_INDEX_CONSTRAINT_ISNOT; + if( pExpr->op==TK_NOTNULL ) *peOp2 = SQLITE_INDEX_CONSTRAINT_ISNOTNULL; + return res; + } + return 0; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +/* +** If the pBase expression originated in the ON or USING clause of +** a join, then transfer the appropriate markings over to derived. +*/ +static void transferJoinMarkings(Expr *pDerived, Expr *pBase){ + if( pDerived && ExprHasProperty(pBase, EP_OuterON|EP_InnerON) ){ + pDerived->flags |= pBase->flags & (EP_OuterON|EP_InnerON); + pDerived->w.iJoin = pBase->w.iJoin; + } +} + +/* +** Mark term iChild as being a child of term iParent +*/ +static void markTermAsChild(WhereClause *pWC, int iChild, int iParent){ + pWC->a[iChild].iParent = iParent; + pWC->a[iChild].truthProb = pWC->a[iParent].truthProb; + pWC->a[iParent].nChild++; +} + +/* +** Return the N-th AND-connected subterm of pTerm. Or if pTerm is not +** a conjunction, then return just pTerm when N==0. If N is exceeds +** the number of available subterms, return NULL. +*/ +static WhereTerm *whereNthSubterm(WhereTerm *pTerm, int N){ + if( pTerm->eOperator!=WO_AND ){ + return N==0 ? pTerm : 0; + } + if( N<pTerm->u.pAndInfo->wc.nTerm ){ + return &pTerm->u.pAndInfo->wc.a[N]; + } + return 0; +} + +/* +** Subterms pOne and pTwo are contained within WHERE clause pWC. The +** two subterms are in disjunction - they are OR-ed together. +** +** If these two terms are both of the form: "A op B" with the same +** A and B values but different operators and if the operators are +** compatible (if one is = and the other is <, for example) then +** add a new virtual AND term to pWC that is the combination of the +** two. +** +** Some examples: +** +** x<y OR x=y --> x<=y +** x=y OR x=y --> x=y +** x<=y OR x<y --> x<=y +** +** The following is NOT generated: +** +** x<y OR x>y --> x!=y +*/ +static void whereCombineDisjuncts( + SrcList *pSrc, /* the FROM clause */ + WhereClause *pWC, /* The complete WHERE clause */ + WhereTerm *pOne, /* First disjunct */ + WhereTerm *pTwo /* Second disjunct */ +){ + u16 eOp = pOne->eOperator | pTwo->eOperator; + sqlite3 *db; /* Database connection (for malloc) */ + Expr *pNew; /* New virtual expression */ + int op; /* Operator for the combined expression */ + int idxNew; /* Index in pWC of the next virtual term */ + + if( (pOne->wtFlags | pTwo->wtFlags) & TERM_VNULL ) return; + if( (pOne->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; + if( (pTwo->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; + if( (eOp & (WO_EQ|WO_LT|WO_LE))!=eOp + && (eOp & (WO_EQ|WO_GT|WO_GE))!=eOp ) return; + assert( pOne->pExpr->pLeft!=0 && pOne->pExpr->pRight!=0 ); + assert( pTwo->pExpr->pLeft!=0 && pTwo->pExpr->pRight!=0 ); + if( sqlite3ExprCompare(0,pOne->pExpr->pLeft, pTwo->pExpr->pLeft, -1) ) return; + if( sqlite3ExprCompare(0,pOne->pExpr->pRight, pTwo->pExpr->pRight,-1) )return; + /* If we reach this point, it means the two subterms can be combined */ + if( (eOp & (eOp-1))!=0 ){ + if( eOp & (WO_LT|WO_LE) ){ + eOp = WO_LE; + }else{ + assert( eOp & (WO_GT|WO_GE) ); + eOp = WO_GE; + } + } + db = pWC->pWInfo->pParse->db; + pNew = sqlite3ExprDup(db, pOne->pExpr, 0); + if( pNew==0 ) return; + for(op=TK_EQ; eOp!=(WO_EQ<<(op-TK_EQ)); op++){ assert( op<TK_GE ); } + pNew->op = op; + idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC); + exprAnalyze(pSrc, pWC, idxNew); +} + +#if !defined(SQLITE_OMIT_OR_OPTIMIZATION) && !defined(SQLITE_OMIT_SUBQUERY) +/* +** Analyze a term that consists of two or more OR-connected +** subterms. So in: +** +** ... WHERE (a=5) AND (b=7 OR c=9 OR d=13) AND (d=13) +** ^^^^^^^^^^^^^^^^^^^^ +** +** This routine analyzes terms such as the middle term in the above example. +** A WhereOrTerm object is computed and attached to the term under +** analysis, regardless of the outcome of the analysis. Hence: +** +** WhereTerm.wtFlags |= TERM_ORINFO +** WhereTerm.u.pOrInfo = a dynamically allocated WhereOrTerm object +** +** The term being analyzed must have two or more of OR-connected subterms. +** A single subterm might be a set of AND-connected sub-subterms. +** Examples of terms under analysis: +** +** (A) t1.x=t2.y OR t1.x=t2.z OR t1.y=15 OR t1.z=t3.a+5 +** (B) x=expr1 OR expr2=x OR x=expr3 +** (C) t1.x=t2.y OR (t1.x=t2.z AND t1.y=15) +** (D) x=expr1 OR (y>11 AND y<22 AND z LIKE '*hello*') +** (E) (p.a=1 AND q.b=2 AND r.c=3) OR (p.x=4 AND q.y=5 AND r.z=6) +** (F) x>A OR (x=A AND y>=B) +** +** CASE 1: +** +** If all subterms are of the form T.C=expr for some single column of C and +** a single table T (as shown in example B above) then create a new virtual +** term that is an equivalent IN expression. In other words, if the term +** being analyzed is: +** +** x = expr1 OR expr2 = x OR x = expr3 +** +** then create a new virtual term like this: +** +** x IN (expr1,expr2,expr3) +** +** CASE 2: +** +** If there are exactly two disjuncts and one side has x>A and the other side +** has x=A (for the same x and A) then add a new virtual conjunct term to the +** WHERE clause of the form "x>=A". Example: +** +** x>A OR (x=A AND y>B) adds: x>=A +** +** The added conjunct can sometimes be helpful in query planning. +** +** CASE 3: +** +** If all subterms are indexable by a single table T, then set +** +** WhereTerm.eOperator = WO_OR +** WhereTerm.u.pOrInfo->indexable |= the cursor number for table T +** +** A subterm is "indexable" if it is of the form +** "T.C <op> <expr>" where C is any column of table T and +** <op> is one of "=", "<", "<=", ">", ">=", "IS NULL", or "IN". +** A subterm is also indexable if it is an AND of two or more +** subsubterms at least one of which is indexable. Indexable AND +** subterms have their eOperator set to WO_AND and they have +** u.pAndInfo set to a dynamically allocated WhereAndTerm object. +** +** From another point of view, "indexable" means that the subterm could +** potentially be used with an index if an appropriate index exists. +** This analysis does not consider whether or not the index exists; that +** is decided elsewhere. This analysis only looks at whether subterms +** appropriate for indexing exist. +** +** All examples A through E above satisfy case 3. But if a term +** also satisfies case 1 (such as B) we know that the optimizer will +** always prefer case 1, so in that case we pretend that case 3 is not +** satisfied. +** +** It might be the case that multiple tables are indexable. For example, +** (E) above is indexable on tables P, Q, and R. +** +** Terms that satisfy case 3 are candidates for lookup by using +** separate indices to find rowids for each subterm and composing +** the union of all rowids using a RowSet object. This is similar +** to "bitmap indices" in other database engines. +** +** OTHERWISE: +** +** If none of cases 1, 2, or 3 apply, then leave the eOperator set to +** zero. This term is not useful for search. +*/ +static void exprAnalyzeOrTerm( + SrcList *pSrc, /* the FROM clause */ + WhereClause *pWC, /* the complete WHERE clause */ + int idxTerm /* Index of the OR-term to be analyzed */ +){ + WhereInfo *pWInfo = pWC->pWInfo; /* WHERE clause processing context */ + Parse *pParse = pWInfo->pParse; /* Parser context */ + sqlite3 *db = pParse->db; /* Database connection */ + WhereTerm *pTerm = &pWC->a[idxTerm]; /* The term to be analyzed */ + Expr *pExpr = pTerm->pExpr; /* The expression of the term */ + int i; /* Loop counters */ + WhereClause *pOrWc; /* Breakup of pTerm into subterms */ + WhereTerm *pOrTerm; /* A Sub-term within the pOrWc */ + WhereOrInfo *pOrInfo; /* Additional information associated with pTerm */ + Bitmask chngToIN; /* Tables that might satisfy case 1 */ + Bitmask indexable; /* Tables that are indexable, satisfying case 2 */ + + /* + ** Break the OR clause into its separate subterms. The subterms are + ** stored in a WhereClause structure containing within the WhereOrInfo + ** object that is attached to the original OR clause term. + */ + assert( (pTerm->wtFlags & (TERM_DYNAMIC|TERM_ORINFO|TERM_ANDINFO))==0 ); + assert( pExpr->op==TK_OR ); + pTerm->u.pOrInfo = pOrInfo = sqlite3DbMallocZero(db, sizeof(*pOrInfo)); + if( pOrInfo==0 ) return; + pTerm->wtFlags |= TERM_ORINFO; + pOrWc = &pOrInfo->wc; + memset(pOrWc->aStatic, 0, sizeof(pOrWc->aStatic)); + sqlite3WhereClauseInit(pOrWc, pWInfo); + sqlite3WhereSplit(pOrWc, pExpr, TK_OR); + sqlite3WhereExprAnalyze(pSrc, pOrWc); + if( db->mallocFailed ) return; + assert( pOrWc->nTerm>=2 ); + + /* + ** Compute the set of tables that might satisfy cases 1 or 3. + */ + indexable = ~(Bitmask)0; + chngToIN = ~(Bitmask)0; + for(i=pOrWc->nTerm-1, pOrTerm=pOrWc->a; i>=0 && indexable; i--, pOrTerm++){ + if( (pOrTerm->eOperator & WO_SINGLE)==0 ){ + WhereAndInfo *pAndInfo; + assert( (pOrTerm->wtFlags & (TERM_ANDINFO|TERM_ORINFO))==0 ); + chngToIN = 0; + pAndInfo = sqlite3DbMallocRawNN(db, sizeof(*pAndInfo)); + if( pAndInfo ){ + WhereClause *pAndWC; + WhereTerm *pAndTerm; + int j; + Bitmask b = 0; + pOrTerm->u.pAndInfo = pAndInfo; + pOrTerm->wtFlags |= TERM_ANDINFO; + pOrTerm->eOperator = WO_AND; + pOrTerm->leftCursor = -1; + pAndWC = &pAndInfo->wc; + memset(pAndWC->aStatic, 0, sizeof(pAndWC->aStatic)); + sqlite3WhereClauseInit(pAndWC, pWC->pWInfo); + sqlite3WhereSplit(pAndWC, pOrTerm->pExpr, TK_AND); + sqlite3WhereExprAnalyze(pSrc, pAndWC); + pAndWC->pOuter = pWC; + if( !db->mallocFailed ){ + for(j=0, pAndTerm=pAndWC->a; j<pAndWC->nTerm; j++, pAndTerm++){ + assert( pAndTerm->pExpr ); + if( allowedOp(pAndTerm->pExpr->op) + || pAndTerm->eOperator==WO_AUX + ){ + b |= sqlite3WhereGetMask(&pWInfo->sMaskSet, pAndTerm->leftCursor); + } + } + } + indexable &= b; + } + }else if( pOrTerm->wtFlags & TERM_COPIED ){ + /* Skip this term for now. We revisit it when we process the + ** corresponding TERM_VIRTUAL term */ + }else{ + Bitmask b; + b = sqlite3WhereGetMask(&pWInfo->sMaskSet, pOrTerm->leftCursor); + if( pOrTerm->wtFlags & TERM_VIRTUAL ){ + WhereTerm *pOther = &pOrWc->a[pOrTerm->iParent]; + b |= sqlite3WhereGetMask(&pWInfo->sMaskSet, pOther->leftCursor); + } + indexable &= b; + if( (pOrTerm->eOperator & WO_EQ)==0 ){ + chngToIN = 0; + }else{ + chngToIN &= b; + } + } + } + + /* + ** Record the set of tables that satisfy case 3. The set might be + ** empty. + */ + pOrInfo->indexable = indexable; + pTerm->eOperator = WO_OR; + pTerm->leftCursor = -1; + if( indexable ){ + pWC->hasOr = 1; + } + + /* For a two-way OR, attempt to implementation case 2. + */ + if( indexable && pOrWc->nTerm==2 ){ + int iOne = 0; + WhereTerm *pOne; + while( (pOne = whereNthSubterm(&pOrWc->a[0],iOne++))!=0 ){ + int iTwo = 0; + WhereTerm *pTwo; + while( (pTwo = whereNthSubterm(&pOrWc->a[1],iTwo++))!=0 ){ + whereCombineDisjuncts(pSrc, pWC, pOne, pTwo); + } + } + } + + /* + ** chngToIN holds a set of tables that *might* satisfy case 1. But + ** we have to do some additional checking to see if case 1 really + ** is satisfied. + ** + ** chngToIN will hold either 0, 1, or 2 bits. The 0-bit case means + ** that there is no possibility of transforming the OR clause into an + ** IN operator because one or more terms in the OR clause contain + ** something other than == on a column in the single table. The 1-bit + ** case means that every term of the OR clause is of the form + ** "table.column=expr" for some single table. The one bit that is set + ** will correspond to the common table. We still need to check to make + ** sure the same column is used on all terms. The 2-bit case is when + ** the all terms are of the form "table1.column=table2.column". It + ** might be possible to form an IN operator with either table1.column + ** or table2.column as the LHS if either is common to every term of + ** the OR clause. + ** + ** Note that terms of the form "table.column1=table.column2" (the + ** same table on both sizes of the ==) cannot be optimized. + */ + if( chngToIN ){ + int okToChngToIN = 0; /* True if the conversion to IN is valid */ + int iColumn = -1; /* Column index on lhs of IN operator */ + int iCursor = -1; /* Table cursor common to all terms */ + int j = 0; /* Loop counter */ + + /* Search for a table and column that appears on one side or the + ** other of the == operator in every subterm. That table and column + ** will be recorded in iCursor and iColumn. There might not be any + ** such table and column. Set okToChngToIN if an appropriate table + ** and column is found but leave okToChngToIN false if not found. + */ + for(j=0; j<2 && !okToChngToIN; j++){ + Expr *pLeft = 0; + pOrTerm = pOrWc->a; + for(i=pOrWc->nTerm-1; i>=0; i--, pOrTerm++){ + assert( pOrTerm->eOperator & WO_EQ ); + pOrTerm->wtFlags &= ~TERM_OK; + if( pOrTerm->leftCursor==iCursor ){ + /* This is the 2-bit case and we are on the second iteration and + ** current term is from the first iteration. So skip this term. */ + assert( j==1 ); + continue; + } + if( (chngToIN & sqlite3WhereGetMask(&pWInfo->sMaskSet, + pOrTerm->leftCursor))==0 ){ + /* This term must be of the form t1.a==t2.b where t2 is in the + ** chngToIN set but t1 is not. This term will be either preceded + ** or followed by an inverted copy (t2.b==t1.a). Skip this term + ** and use its inversion. */ + testcase( pOrTerm->wtFlags & TERM_COPIED ); + testcase( pOrTerm->wtFlags & TERM_VIRTUAL ); + assert( pOrTerm->wtFlags & (TERM_COPIED|TERM_VIRTUAL) ); + continue; + } + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); + iColumn = pOrTerm->u.x.leftColumn; + iCursor = pOrTerm->leftCursor; + pLeft = pOrTerm->pExpr->pLeft; + break; + } + if( i<0 ){ + /* No candidate table+column was found. This can only occur + ** on the second iteration */ + assert( j==1 ); + assert( IsPowerOfTwo(chngToIN) ); + assert( chngToIN==sqlite3WhereGetMask(&pWInfo->sMaskSet, iCursor) ); + break; + } + testcase( j==1 ); + + /* We have found a candidate table and column. Check to see if that + ** table and column is common to every term in the OR clause */ + okToChngToIN = 1; + for(; i>=0 && okToChngToIN; i--, pOrTerm++){ + assert( pOrTerm->eOperator & WO_EQ ); + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); + if( pOrTerm->leftCursor!=iCursor ){ + pOrTerm->wtFlags &= ~TERM_OK; + }else if( pOrTerm->u.x.leftColumn!=iColumn || (iColumn==XN_EXPR + && sqlite3ExprCompare(pParse, pOrTerm->pExpr->pLeft, pLeft, -1) + )){ + okToChngToIN = 0; + }else{ + int affLeft, affRight; + /* If the right-hand side is also a column, then the affinities + ** of both right and left sides must be such that no type + ** conversions are required on the right. (Ticket #2249) + */ + affRight = sqlite3ExprAffinity(pOrTerm->pExpr->pRight); + affLeft = sqlite3ExprAffinity(pOrTerm->pExpr->pLeft); + if( affRight!=0 && affRight!=affLeft ){ + okToChngToIN = 0; + }else{ + pOrTerm->wtFlags |= TERM_OK; + } + } + } + } + + /* At this point, okToChngToIN is true if original pTerm satisfies + ** case 1. In that case, construct a new virtual term that is + ** pTerm converted into an IN operator. + */ + if( okToChngToIN ){ + Expr *pDup; /* A transient duplicate expression */ + ExprList *pList = 0; /* The RHS of the IN operator */ + Expr *pLeft = 0; /* The LHS of the IN operator */ + Expr *pNew; /* The complete IN operator */ + + for(i=pOrWc->nTerm-1, pOrTerm=pOrWc->a; i>=0; i--, pOrTerm++){ + if( (pOrTerm->wtFlags & TERM_OK)==0 ) continue; + assert( pOrTerm->eOperator & WO_EQ ); + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); + assert( pOrTerm->leftCursor==iCursor ); + assert( pOrTerm->u.x.leftColumn==iColumn ); + pDup = sqlite3ExprDup(db, pOrTerm->pExpr->pRight, 0); + pList = sqlite3ExprListAppend(pWInfo->pParse, pList, pDup); + pLeft = pOrTerm->pExpr->pLeft; + } + assert( pLeft!=0 ); + pDup = sqlite3ExprDup(db, pLeft, 0); + pNew = sqlite3PExpr(pParse, TK_IN, pDup, 0); + if( pNew ){ + int idxNew; + transferJoinMarkings(pNew, pExpr); + assert( ExprUseXList(pNew) ); + pNew->x.pList = pList; + idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC); + testcase( idxNew==0 ); + exprAnalyze(pSrc, pWC, idxNew); + /* pTerm = &pWC->a[idxTerm]; // would be needed if pTerm where reused */ + markTermAsChild(pWC, idxNew, idxTerm); + }else{ + sqlite3ExprListDelete(db, pList); + } + } + } +} +#endif /* !SQLITE_OMIT_OR_OPTIMIZATION && !SQLITE_OMIT_SUBQUERY */ + +/* +** We already know that pExpr is a binary operator where both operands are +** column references. This routine checks to see if pExpr is an equivalence +** relation: +** 1. The SQLITE_Transitive optimization must be enabled +** 2. Must be either an == or an IS operator +** 3. Not originating in the ON clause of an OUTER JOIN +** 4. The affinities of A and B must be compatible +** 5a. Both operands use the same collating sequence OR +** 5b. The overall collating sequence is BINARY +** If this routine returns TRUE, that means that the RHS can be substituted +** for the LHS anyplace else in the WHERE clause where the LHS column occurs. +** This is an optimization. No harm comes from returning 0. But if 1 is +** returned when it should not be, then incorrect answers might result. +*/ +static int termIsEquivalence(Parse *pParse, Expr *pExpr){ + char aff1, aff2; + CollSeq *pColl; + if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0; + if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0; + if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; + aff1 = sqlite3ExprAffinity(pExpr->pLeft); + aff2 = sqlite3ExprAffinity(pExpr->pRight); + if( aff1!=aff2 + && (!sqlite3IsNumericAffinity(aff1) || !sqlite3IsNumericAffinity(aff2)) + ){ + return 0; + } + pColl = sqlite3ExprCompareCollSeq(pParse, pExpr); + if( sqlite3IsBinary(pColl) ) return 1; + return sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight); +} + +/* +** Recursively walk the expressions of a SELECT statement and generate +** a bitmask indicating which tables are used in that expression +** tree. +*/ +static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ + Bitmask mask = 0; + while( pS ){ + SrcList *pSrc = pS->pSrc; + mask |= sqlite3WhereExprListUsage(pMaskSet, pS->pEList); + mask |= sqlite3WhereExprListUsage(pMaskSet, pS->pGroupBy); + mask |= sqlite3WhereExprListUsage(pMaskSet, pS->pOrderBy); + mask |= sqlite3WhereExprUsage(pMaskSet, pS->pWhere); + mask |= sqlite3WhereExprUsage(pMaskSet, pS->pHaving); + if( ALWAYS(pSrc!=0) ){ + int i; + for(i=0; i<pSrc->nSrc; i++){ + mask |= exprSelectUsage(pMaskSet, pSrc->a[i].pSelect); + if( pSrc->a[i].fg.isUsing==0 ){ + mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].u3.pOn); + } + if( pSrc->a[i].fg.isTabFunc ){ + mask |= sqlite3WhereExprListUsage(pMaskSet, pSrc->a[i].u1.pFuncArg); + } + } + } + pS = pS->pPrior; + } + return mask; +} + +/* +** Expression pExpr is one operand of a comparison operator that might +** be useful for indexing. This routine checks to see if pExpr appears +** in any index. Return TRUE (1) if pExpr is an indexed term and return +** FALSE (0) if not. If TRUE is returned, also set aiCurCol[0] to the cursor +** number of the table that is indexed and aiCurCol[1] to the column number +** of the column that is indexed, or XN_EXPR (-2) if an expression is being +** indexed. +** +** If pExpr is a TK_COLUMN column reference, then this routine always returns +** true even if that particular column is not indexed, because the column +** might be added to an automatic index later. +*/ +static SQLITE_NOINLINE int exprMightBeIndexed2( + SrcList *pFrom, /* The FROM clause */ + int *aiCurCol, /* Write the referenced table cursor and column here */ + Expr *pExpr, /* An operand of a comparison operator */ + int j /* Start looking with the j-th pFrom entry */ +){ + Index *pIdx; + int i; + int iCur; + do{ + iCur = pFrom->a[j].iCursor; + for(pIdx=pFrom->a[j].pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->aColExpr==0 ) continue; + for(i=0; i<pIdx->nKeyCol; i++){ + if( pIdx->aiColumn[i]!=XN_EXPR ) continue; + assert( pIdx->bHasExpr ); + if( sqlite3ExprCompareSkip(pExpr,pIdx->aColExpr->a[i].pExpr,iCur)==0 + && pExpr->op!=TK_STRING + ){ + aiCurCol[0] = iCur; + aiCurCol[1] = XN_EXPR; + return 1; + } + } + } + }while( ++j < pFrom->nSrc ); + return 0; +} +static int exprMightBeIndexed( + SrcList *pFrom, /* The FROM clause */ + int *aiCurCol, /* Write the referenced table cursor & column here */ + Expr *pExpr, /* An operand of a comparison operator */ + int op /* The specific comparison operator */ +){ + int i; + + /* If this expression is a vector to the left or right of a + ** inequality constraint (>, <, >= or <=), perform the processing + ** on the first element of the vector. */ + assert( TK_GT+1==TK_LE && TK_GT+2==TK_LT && TK_GT+3==TK_GE ); + assert( TK_IS<TK_GE && TK_ISNULL<TK_GE && TK_IN<TK_GE ); + assert( op<=TK_GE ); + if( pExpr->op==TK_VECTOR && (op>=TK_GT && ALWAYS(op<=TK_GE)) ){ + assert( ExprUseXList(pExpr) ); + pExpr = pExpr->x.pList->a[0].pExpr; + } + + if( pExpr->op==TK_COLUMN ){ + aiCurCol[0] = pExpr->iTable; + aiCurCol[1] = pExpr->iColumn; + return 1; + } + + for(i=0; i<pFrom->nSrc; i++){ + Index *pIdx; + for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->aColExpr ){ + return exprMightBeIndexed2(pFrom,aiCurCol,pExpr,i); + } + } + } + return 0; +} + + +/* +** The input to this routine is an WhereTerm structure with only the +** "pExpr" field filled in. The job of this routine is to analyze the +** subexpression and populate all the other fields of the WhereTerm +** structure. +** +** If the expression is of the form "<expr> <op> X" it gets commuted +** to the standard form of "X <op> <expr>". +** +** If the expression is of the form "X <op> Y" where both X and Y are +** columns, then the original expression is unchanged and a new virtual +** term of the form "Y <op> X" is added to the WHERE clause and +** analyzed separately. The original term is marked with TERM_COPIED +** and the new term is marked with TERM_DYNAMIC (because it's pExpr +** needs to be freed with the WhereClause) and TERM_VIRTUAL (because it +** is a commuted copy of a prior term.) The original term has nChild=1 +** and the copy has idxParent set to the index of the original term. +*/ +static void exprAnalyze( + SrcList *pSrc, /* the FROM clause */ + WhereClause *pWC, /* the WHERE clause */ + int idxTerm /* Index of the term to be analyzed */ +){ + WhereInfo *pWInfo = pWC->pWInfo; /* WHERE clause processing context */ + WhereTerm *pTerm; /* The term to be analyzed */ + WhereMaskSet *pMaskSet; /* Set of table index masks */ + Expr *pExpr; /* The expression to be analyzed */ + Bitmask prereqLeft; /* Prerequisites of the pExpr->pLeft */ + Bitmask prereqAll; /* Prerequisites of pExpr */ + Bitmask extraRight = 0; /* Extra dependencies on LEFT JOIN */ + Expr *pStr1 = 0; /* RHS of LIKE/GLOB operator */ + int isComplete = 0; /* RHS of LIKE/GLOB ends with wildcard */ + int noCase = 0; /* uppercase equivalent to lowercase */ + int op; /* Top-level operator. pExpr->op */ + Parse *pParse = pWInfo->pParse; /* Parsing context */ + sqlite3 *db = pParse->db; /* Database connection */ + unsigned char eOp2 = 0; /* op2 value for LIKE/REGEXP/GLOB */ + int nLeft; /* Number of elements on left side vector */ + + if( db->mallocFailed ){ + return; + } + assert( pWC->nTerm > idxTerm ); + pTerm = &pWC->a[idxTerm]; + pMaskSet = &pWInfo->sMaskSet; + pExpr = pTerm->pExpr; + assert( pExpr!=0 ); /* Because malloc() has not failed */ + assert( pExpr->op!=TK_AS && pExpr->op!=TK_COLLATE ); + pMaskSet->bVarSelect = 0; + prereqLeft = sqlite3WhereExprUsage(pMaskSet, pExpr->pLeft); + op = pExpr->op; + if( op==TK_IN ){ + assert( pExpr->pRight==0 ); + if( sqlite3ExprCheckIN(pParse, pExpr) ) return; + if( ExprUseXSelect(pExpr) ){ + pTerm->prereqRight = exprSelectUsage(pMaskSet, pExpr->x.pSelect); + }else{ + pTerm->prereqRight = sqlite3WhereExprListUsage(pMaskSet, pExpr->x.pList); + } + prereqAll = prereqLeft | pTerm->prereqRight; + }else{ + pTerm->prereqRight = sqlite3WhereExprUsage(pMaskSet, pExpr->pRight); + if( pExpr->pLeft==0 + || ExprHasProperty(pExpr, EP_xIsSelect|EP_IfNullRow) + || pExpr->x.pList!=0 + ){ + prereqAll = sqlite3WhereExprUsageNN(pMaskSet, pExpr); + }else{ + prereqAll = prereqLeft | pTerm->prereqRight; + } + } + if( pMaskSet->bVarSelect ) pTerm->wtFlags |= TERM_VARSELECT; + +#ifdef SQLITE_DEBUG + if( prereqAll!=sqlite3WhereExprUsageNN(pMaskSet, pExpr) ){ + printf("\n*** Incorrect prereqAll computed for:\n"); + sqlite3TreeViewExpr(0,pExpr,0); + assert( 0 ); + } +#endif + + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) ){ + Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iJoin); + if( ExprHasProperty(pExpr, EP_OuterON) ){ + prereqAll |= x; + extraRight = x-1; /* ON clause terms may not be used with an index + ** on left table of a LEFT JOIN. Ticket #3015 */ + if( (prereqAll>>1)>=x ){ + sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); + return; + } + }else if( (prereqAll>>1)>=x ){ + /* The ON clause of an INNER JOIN references a table to its right. + ** Most other SQL database engines raise an error. But SQLite versions + ** 3.0 through 3.38 just put the ON clause constraint into the WHERE + ** clause and carried on. Beginning with 3.39, raise an error only + ** if there is a RIGHT or FULL JOIN in the query. This makes SQLite + ** more like other systems, and also preserves legacy. */ + if( ALWAYS(pSrc->nSrc>0) && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); + return; + } + ExprClearProperty(pExpr, EP_InnerON); + } + } + pTerm->prereqAll = prereqAll; + pTerm->leftCursor = -1; + pTerm->iParent = -1; + pTerm->eOperator = 0; + if( allowedOp(op) ){ + int aiCurCol[2]; + Expr *pLeft = sqlite3ExprSkipCollate(pExpr->pLeft); + Expr *pRight = sqlite3ExprSkipCollate(pExpr->pRight); + u16 opMask = (pTerm->prereqRight & prereqLeft)==0 ? WO_ALL : WO_EQUIV; + + if( pTerm->u.x.iField>0 ){ + assert( op==TK_IN ); + assert( pLeft->op==TK_VECTOR ); + assert( ExprUseXList(pLeft) ); + pLeft = pLeft->x.pList->a[pTerm->u.x.iField-1].pExpr; + } + + if( exprMightBeIndexed(pSrc, aiCurCol, pLeft, op) ){ + pTerm->leftCursor = aiCurCol[0]; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + pTerm->u.x.leftColumn = aiCurCol[1]; + pTerm->eOperator = operatorMask(op) & opMask; + } + if( op==TK_IS ) pTerm->wtFlags |= TERM_IS; + if( pRight + && exprMightBeIndexed(pSrc, aiCurCol, pRight, op) + && !ExprHasProperty(pRight, EP_FixedCol) + ){ + WhereTerm *pNew; + Expr *pDup; + u16 eExtraOp = 0; /* Extra bits for pNew->eOperator */ + assert( pTerm->u.x.iField==0 ); + if( pTerm->leftCursor>=0 ){ + int idxNew; + pDup = sqlite3ExprDup(db, pExpr, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDup); + return; + } + idxNew = whereClauseInsert(pWC, pDup, TERM_VIRTUAL|TERM_DYNAMIC); + if( idxNew==0 ) return; + pNew = &pWC->a[idxNew]; + markTermAsChild(pWC, idxNew, idxTerm); + if( op==TK_IS ) pNew->wtFlags |= TERM_IS; + pTerm = &pWC->a[idxTerm]; + pTerm->wtFlags |= TERM_COPIED; + + if( termIsEquivalence(pParse, pDup) ){ + pTerm->eOperator |= WO_EQUIV; + eExtraOp = WO_EQUIV; + } + }else{ + pDup = pExpr; + pNew = pTerm; + } + pNew->wtFlags |= exprCommute(pParse, pDup); + pNew->leftCursor = aiCurCol[0]; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + pNew->u.x.leftColumn = aiCurCol[1]; + testcase( (prereqLeft | extraRight) != prereqLeft ); + pNew->prereqRight = prereqLeft | extraRight; + pNew->prereqAll = prereqAll; + pNew->eOperator = (operatorMask(pDup->op) + eExtraOp) & opMask; + }else + if( op==TK_ISNULL + && !ExprHasProperty(pExpr,EP_OuterON) + && 0==sqlite3ExprCanBeNull(pLeft) + ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + pExpr->op = TK_TRUEFALSE; /* See tag-20230504-1 */ + pExpr->u.zToken = "false"; + ExprSetProperty(pExpr, EP_IsFalse); + pTerm->prereqAll = 0; + pTerm->eOperator = 0; + } + } + +#ifndef SQLITE_OMIT_BETWEEN_OPTIMIZATION + /* If a term is the BETWEEN operator, create two new virtual terms + ** that define the range that the BETWEEN implements. For example: + ** + ** a BETWEEN b AND c + ** + ** is converted into: + ** + ** (a BETWEEN b AND c) AND (a>=b) AND (a<=c) + ** + ** The two new terms are added onto the end of the WhereClause object. + ** The new terms are "dynamic" and are children of the original BETWEEN + ** term. That means that if the BETWEEN term is coded, the children are + ** skipped. Or, if the children are satisfied by an index, the original + ** BETWEEN term is skipped. + */ + else if( pExpr->op==TK_BETWEEN && pWC->op==TK_AND ){ + ExprList *pList; + int i; + static const u8 ops[] = {TK_GE, TK_LE}; + assert( ExprUseXList(pExpr) ); + pList = pExpr->x.pList; + assert( pList!=0 ); + assert( pList->nExpr==2 ); + for(i=0; i<2; i++){ + Expr *pNewExpr; + int idxNew; + pNewExpr = sqlite3PExpr(pParse, ops[i], + sqlite3ExprDup(db, pExpr->pLeft, 0), + sqlite3ExprDup(db, pList->a[i].pExpr, 0)); + transferJoinMarkings(pNewExpr, pExpr); + idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); + testcase( idxNew==0 ); + exprAnalyze(pSrc, pWC, idxNew); + pTerm = &pWC->a[idxTerm]; + markTermAsChild(pWC, idxNew, idxTerm); + } + } +#endif /* SQLITE_OMIT_BETWEEN_OPTIMIZATION */ + +#if !defined(SQLITE_OMIT_OR_OPTIMIZATION) && !defined(SQLITE_OMIT_SUBQUERY) + /* Analyze a term that is composed of two or more subterms connected by + ** an OR operator. + */ + else if( pExpr->op==TK_OR ){ + assert( pWC->op==TK_AND ); + exprAnalyzeOrTerm(pSrc, pWC, idxTerm); + pTerm = &pWC->a[idxTerm]; + } +#endif /* SQLITE_OMIT_OR_OPTIMIZATION */ + /* The form "x IS NOT NULL" can sometimes be evaluated more efficiently + ** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a + ** virtual term of that form. + ** + ** The virtual term must be tagged with TERM_VNULL. + */ + else if( pExpr->op==TK_NOTNULL ){ + if( pExpr->pLeft->op==TK_COLUMN + && pExpr->pLeft->iColumn>=0 + && !ExprHasProperty(pExpr, EP_OuterON) + ){ + Expr *pNewExpr; + Expr *pLeft = pExpr->pLeft; + int idxNew; + WhereTerm *pNewTerm; + + pNewExpr = sqlite3PExpr(pParse, TK_GT, + sqlite3ExprDup(db, pLeft, 0), + sqlite3ExprAlloc(db, TK_NULL, 0, 0)); + + idxNew = whereClauseInsert(pWC, pNewExpr, + TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL); + if( idxNew ){ + pNewTerm = &pWC->a[idxNew]; + pNewTerm->prereqRight = 0; + pNewTerm->leftCursor = pLeft->iTable; + pNewTerm->u.x.leftColumn = pLeft->iColumn; + pNewTerm->eOperator = WO_GT; + markTermAsChild(pWC, idxNew, idxTerm); + pTerm = &pWC->a[idxTerm]; + pTerm->wtFlags |= TERM_COPIED; + pNewTerm->prereqAll = pTerm->prereqAll; + } + } + } + + +#ifndef SQLITE_OMIT_LIKE_OPTIMIZATION + /* Add constraints to reduce the search space on a LIKE or GLOB + ** operator. + ** + ** A like pattern of the form "x LIKE 'aBc%'" is changed into constraints + ** + ** x>='ABC' AND x<'abd' AND x LIKE 'aBc%' + ** + ** The last character of the prefix "abc" is incremented to form the + ** termination condition "abd". If case is not significant (the default + ** for LIKE) then the lower-bound is made all uppercase and the upper- + ** bound is made all lowercase so that the bounds also work when comparing + ** BLOBs. + */ + else if( pExpr->op==TK_FUNCTION + && pWC->op==TK_AND + && isLikeOrGlob(pParse, pExpr, &pStr1, &isComplete, &noCase) + ){ + Expr *pLeft; /* LHS of LIKE/GLOB operator */ + Expr *pStr2; /* Copy of pStr1 - RHS of LIKE/GLOB operator */ + Expr *pNewExpr1; + Expr *pNewExpr2; + int idxNew1; + int idxNew2; + const char *zCollSeqName; /* Name of collating sequence */ + const u16 wtFlags = TERM_LIKEOPT | TERM_VIRTUAL | TERM_DYNAMIC; + + assert( ExprUseXList(pExpr) ); + pLeft = pExpr->x.pList->a[1].pExpr; + pStr2 = sqlite3ExprDup(db, pStr1, 0); + assert( pStr1==0 || !ExprHasProperty(pStr1, EP_IntValue) ); + assert( pStr2==0 || !ExprHasProperty(pStr2, EP_IntValue) ); + + + /* Convert the lower bound to upper-case and the upper bound to + ** lower-case (upper-case is less than lower-case in ASCII) so that + ** the range constraints also work for BLOBs + */ + if( noCase && !pParse->db->mallocFailed ){ + int i; + char c; + pTerm->wtFlags |= TERM_LIKE; + for(i=0; (c = pStr1->u.zToken[i])!=0; i++){ + pStr1->u.zToken[i] = sqlite3Toupper(c); + pStr2->u.zToken[i] = sqlite3Tolower(c); + } + } + + if( !db->mallocFailed ){ + u8 c, *pC; /* Last character before the first wildcard */ + pC = (u8*)&pStr2->u.zToken[sqlite3Strlen30(pStr2->u.zToken)-1]; + c = *pC; + if( noCase ){ + /* The point is to increment the last character before the first + ** wildcard. But if we increment '@', that will push it into the + ** alphabetic range where case conversions will mess up the + ** inequality. To avoid this, make sure to also run the full + ** LIKE on all candidate expressions by clearing the isComplete flag + */ + if( c=='A'-1 ) isComplete = 0; + c = sqlite3UpperToLower[c]; + } + *pC = c + 1; + } + zCollSeqName = noCase ? "NOCASE" : sqlite3StrBINARY; + pNewExpr1 = sqlite3ExprDup(db, pLeft, 0); + pNewExpr1 = sqlite3PExpr(pParse, TK_GE, + sqlite3ExprAddCollateString(pParse,pNewExpr1,zCollSeqName), + pStr1); + transferJoinMarkings(pNewExpr1, pExpr); + idxNew1 = whereClauseInsert(pWC, pNewExpr1, wtFlags); + testcase( idxNew1==0 ); + pNewExpr2 = sqlite3ExprDup(db, pLeft, 0); + pNewExpr2 = sqlite3PExpr(pParse, TK_LT, + sqlite3ExprAddCollateString(pParse,pNewExpr2,zCollSeqName), + pStr2); + transferJoinMarkings(pNewExpr2, pExpr); + idxNew2 = whereClauseInsert(pWC, pNewExpr2, wtFlags); + testcase( idxNew2==0 ); + exprAnalyze(pSrc, pWC, idxNew1); + exprAnalyze(pSrc, pWC, idxNew2); + pTerm = &pWC->a[idxTerm]; + if( isComplete ){ + markTermAsChild(pWC, idxNew1, idxTerm); + markTermAsChild(pWC, idxNew2, idxTerm); + } + } +#endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ + + /* If there is a vector == or IS term - e.g. "(a, b) == (?, ?)" - create + ** new terms for each component comparison - "a = ?" and "b = ?". The + ** new terms completely replace the original vector comparison, which is + ** no longer used. + ** + ** This is only required if at least one side of the comparison operation + ** is not a sub-select. + ** + ** tag-20220128a + */ + if( (pExpr->op==TK_EQ || pExpr->op==TK_IS) + && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1 + && sqlite3ExprVectorSize(pExpr->pRight)==nLeft + && ( (pExpr->pLeft->flags & EP_xIsSelect)==0 + || (pExpr->pRight->flags & EP_xIsSelect)==0) + && pWC->op==TK_AND + ){ + int i; + for(i=0; i<nLeft; i++){ + int idxNew; + Expr *pNew; + Expr *pLeft = sqlite3ExprForVectorField(pParse, pExpr->pLeft, i, nLeft); + Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i, nLeft); + + pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight); + transferJoinMarkings(pNew, pExpr); + idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC|TERM_SLICE); + exprAnalyze(pSrc, pWC, idxNew); + } + pTerm = &pWC->a[idxTerm]; + pTerm->wtFlags |= TERM_CODED|TERM_VIRTUAL; /* Disable the original */ + pTerm->eOperator = WO_ROWVAL; + } + + /* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create + ** a virtual term for each vector component. The expression object + ** used by each such virtual term is pExpr (the full vector IN(...) + ** expression). The WhereTerm.u.x.iField variable identifies the index within + ** the vector on the LHS that the virtual term represents. + ** + ** This only works if the RHS is a simple SELECT (not a compound) that does + ** not use window functions. + */ + else if( pExpr->op==TK_IN + && pTerm->u.x.iField==0 + && pExpr->pLeft->op==TK_VECTOR + && ALWAYS( ExprUseXSelect(pExpr) ) + && (pExpr->x.pSelect->pPrior==0 || (pExpr->x.pSelect->selFlags & SF_Values)) +#ifndef SQLITE_OMIT_WINDOWFUNC + && pExpr->x.pSelect->pWin==0 +#endif + && pWC->op==TK_AND + ){ + int i; + for(i=0; i<sqlite3ExprVectorSize(pExpr->pLeft); i++){ + int idxNew; + idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL|TERM_SLICE); + pWC->a[idxNew].u.x.iField = i+1; + exprAnalyze(pSrc, pWC, idxNew); + markTermAsChild(pWC, idxNew, idxTerm); + } + } + +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* Add a WO_AUX auxiliary term to the constraint set if the + ** current expression is of the form "column OP expr" where OP + ** is an operator that gets passed into virtual tables but which is + ** not normally optimized for ordinary tables. In other words, OP + ** is one of MATCH, LIKE, GLOB, REGEXP, !=, IS, IS NOT, or NOT NULL. + ** This information is used by the xBestIndex methods of + ** virtual tables. The native query optimizer does not attempt + ** to do anything with MATCH functions. + */ + else if( pWC->op==TK_AND ){ + Expr *pRight = 0, *pLeft = 0; + int res = isAuxiliaryVtabOperator(db, pExpr, &eOp2, &pLeft, &pRight); + while( res-- > 0 ){ + int idxNew; + WhereTerm *pNewTerm; + Bitmask prereqColumn, prereqExpr; + + prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight); + prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft); + if( (prereqExpr & prereqColumn)==0 ){ + Expr *pNewExpr; + pNewExpr = sqlite3PExpr(pParse, TK_MATCH, + 0, sqlite3ExprDup(db, pRight, 0)); + if( ExprHasProperty(pExpr, EP_OuterON) && pNewExpr ){ + ExprSetProperty(pNewExpr, EP_OuterON); + pNewExpr->w.iJoin = pExpr->w.iJoin; + } + idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); + testcase( idxNew==0 ); + pNewTerm = &pWC->a[idxNew]; + pNewTerm->prereqRight = prereqExpr; + pNewTerm->leftCursor = pLeft->iTable; + pNewTerm->u.x.leftColumn = pLeft->iColumn; + pNewTerm->eOperator = WO_AUX; + pNewTerm->eMatchOp = eOp2; + markTermAsChild(pWC, idxNew, idxTerm); + pTerm = &pWC->a[idxTerm]; + pTerm->wtFlags |= TERM_COPIED; + pNewTerm->prereqAll = pTerm->prereqAll; + } + SWAP(Expr*, pLeft, pRight); + } + } +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + /* Prevent ON clause terms of a LEFT JOIN from being used to drive + ** an index for tables to the left of the join. + */ + testcase( pTerm!=&pWC->a[idxTerm] ); + pTerm = &pWC->a[idxTerm]; + pTerm->prereqRight |= extraRight; +} + +/*************************************************************************** +** Routines with file scope above. Interface to the rest of the where.c +** subsystem follows. +***************************************************************************/ + +/* +** This routine identifies subexpressions in the WHERE clause where +** each subexpression is separated by the AND operator or some other +** operator specified in the op parameter. The WhereClause structure +** is filled with pointers to subexpressions. For example: +** +** WHERE a=='hello' AND coalesce(b,11)<10 AND (c+12!=d OR c==22) +** \________/ \_______________/ \________________/ +** slot[0] slot[1] slot[2] +** +** The original WHERE clause in pExpr is unaltered. All this routine +** does is make slot[] entries point to substructure within pExpr. +** +** In the previous sentence and in the diagram, "slot[]" refers to +** the WhereClause.a[] array. The slot[] array grows as needed to contain +** all terms of the WHERE clause. +*/ +SQLITE_PRIVATE void sqlite3WhereSplit(WhereClause *pWC, Expr *pExpr, u8 op){ + Expr *pE2 = sqlite3ExprSkipCollateAndLikely(pExpr); + pWC->op = op; + assert( pE2!=0 || pExpr==0 ); + if( pE2==0 ) return; + if( pE2->op!=op ){ + whereClauseInsert(pWC, pExpr, 0); + }else{ + sqlite3WhereSplit(pWC, pE2->pLeft, op); + sqlite3WhereSplit(pWC, pE2->pRight, op); + } +} + +/* +** Add either a LIMIT (if eMatchOp==SQLITE_INDEX_CONSTRAINT_LIMIT) or +** OFFSET (if eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET) term to the +** where-clause passed as the first argument. The value for the term +** is found in register iReg. +** +** In the common case where the value is a simple integer +** (example: "LIMIT 5 OFFSET 10") then the expression codes as a +** TK_INTEGER so that it will be available to sqlite3_vtab_rhs_value(). +** If not, then it codes as a TK_REGISTER expression. +*/ +static void whereAddLimitExpr( + WhereClause *pWC, /* Add the constraint to this WHERE clause */ + int iReg, /* Register that will hold value of the limit/offset */ + Expr *pExpr, /* Expression that defines the limit/offset */ + int iCsr, /* Cursor to which the constraint applies */ + int eMatchOp /* SQLITE_INDEX_CONSTRAINT_LIMIT or _OFFSET */ +){ + Parse *pParse = pWC->pWInfo->pParse; + sqlite3 *db = pParse->db; + Expr *pNew; + int iVal = 0; + + if( sqlite3ExprIsInteger(pExpr, &iVal) && iVal>=0 ){ + Expr *pVal = sqlite3Expr(db, TK_INTEGER, 0); + if( pVal==0 ) return; + ExprSetProperty(pVal, EP_IntValue); + pVal->u.iValue = iVal; + pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); + }else{ + Expr *pVal = sqlite3Expr(db, TK_REGISTER, 0); + if( pVal==0 ) return; + pVal->iTable = iReg; + pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); + } + if( pNew ){ + WhereTerm *pTerm; + int idx; + idx = whereClauseInsert(pWC, pNew, TERM_DYNAMIC|TERM_VIRTUAL); + pTerm = &pWC->a[idx]; + pTerm->leftCursor = iCsr; + pTerm->eOperator = WO_AUX; + pTerm->eMatchOp = eMatchOp; + } +} + +/* +** Possibly add terms corresponding to the LIMIT and OFFSET clauses of the +** SELECT statement passed as the second argument. These terms are only +** added if: +** +** 1. The SELECT statement has a LIMIT clause, and +** 2. The SELECT statement is not an aggregate or DISTINCT query, and +** 3. The SELECT statement has exactly one object in its from clause, and +** that object is a virtual table, and +** 4. There are no terms in the WHERE clause that will not be passed +** to the virtual table xBestIndex method. +** 5. The ORDER BY clause, if any, will be made available to the xBestIndex +** method. +** +** LIMIT and OFFSET terms are ignored by most of the planner code. They +** exist only so that they may be passed to the xBestIndex method of the +** single virtual table in the FROM clause of the SELECT. +*/ +SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ + assert( p!=0 && p->pLimit!=0 ); /* 1 -- checked by caller */ + if( p->pGroupBy==0 + && (p->selFlags & (SF_Distinct|SF_Aggregate))==0 /* 2 */ + && (p->pSrc->nSrc==1 && IsVirtual(p->pSrc->a[0].pTab)) /* 3 */ + ){ + ExprList *pOrderBy = p->pOrderBy; + int iCsr = p->pSrc->a[0].iCursor; + int ii; + + /* Check condition (4). Return early if it is not met. */ + for(ii=0; ii<pWC->nTerm; ii++){ + if( pWC->a[ii].wtFlags & TERM_CODED ){ + /* This term is a vector operation that has been decomposed into + ** other, subsequent terms. It can be ignored. See tag-20220128a */ + assert( pWC->a[ii].wtFlags & TERM_VIRTUAL ); + assert( pWC->a[ii].eOperator==WO_ROWVAL ); + continue; + } + if( pWC->a[ii].nChild ){ + /* If this term has child terms, then they are also part of the + ** pWC->a[] array. So this term can be ignored, as a LIMIT clause + ** will only be added if each of the child terms passes the + ** (leftCursor==iCsr) test below. */ + continue; + } + if( pWC->a[ii].leftCursor!=iCsr ) return; + } + + /* Check condition (5). Return early if it is not met. */ + if( pOrderBy ){ + for(ii=0; ii<pOrderBy->nExpr; ii++){ + Expr *pExpr = pOrderBy->a[ii].pExpr; + if( pExpr->op!=TK_COLUMN ) return; + if( pExpr->iTable!=iCsr ) return; + if( pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) return; + } + } + + /* All conditions are met. Add the terms to the where-clause object. */ + assert( p->pLimit->op==TK_LIMIT ); + whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft, + iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT); + if( p->iOffset>0 ){ + whereAddLimitExpr(pWC, p->iOffset, p->pLimit->pRight, + iCsr, SQLITE_INDEX_CONSTRAINT_OFFSET); + } + } +} + +/* +** Initialize a preallocated WhereClause structure. +*/ +SQLITE_PRIVATE void sqlite3WhereClauseInit( + WhereClause *pWC, /* The WhereClause to be initialized */ + WhereInfo *pWInfo /* The WHERE processing context */ +){ + pWC->pWInfo = pWInfo; + pWC->hasOr = 0; + pWC->pOuter = 0; + pWC->nTerm = 0; + pWC->nBase = 0; + pWC->nSlot = ArraySize(pWC->aStatic); + pWC->a = pWC->aStatic; +} + +/* +** Deallocate a WhereClause structure. The WhereClause structure +** itself is not freed. This routine is the inverse of +** sqlite3WhereClauseInit(). +*/ +SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){ + sqlite3 *db = pWC->pWInfo->pParse->db; + assert( pWC->nTerm>=pWC->nBase ); + if( pWC->nTerm>0 ){ + WhereTerm *a = pWC->a; + WhereTerm *aLast = &pWC->a[pWC->nTerm-1]; +#ifdef SQLITE_DEBUG + int i; + /* Verify that every term past pWC->nBase is virtual */ + for(i=pWC->nBase; i<pWC->nTerm; i++){ + assert( (pWC->a[i].wtFlags & TERM_VIRTUAL)!=0 ); + } +#endif + while(1){ + assert( a->eMatchOp==0 || a->eOperator==WO_AUX ); + if( a->wtFlags & TERM_DYNAMIC ){ + sqlite3ExprDelete(db, a->pExpr); + } + if( a->wtFlags & (TERM_ORINFO|TERM_ANDINFO) ){ + if( a->wtFlags & TERM_ORINFO ){ + assert( (a->wtFlags & TERM_ANDINFO)==0 ); + whereOrInfoDelete(db, a->u.pOrInfo); + }else{ + assert( (a->wtFlags & TERM_ANDINFO)!=0 ); + whereAndInfoDelete(db, a->u.pAndInfo); + } + } + if( a==aLast ) break; + a++; + } + } +} + + +/* +** These routines walk (recursively) an expression tree and generate +** a bitmask indicating which tables are used in that expression +** tree. +** +** sqlite3WhereExprUsage(MaskSet, Expr) -> +** +** Return a Bitmask of all tables referenced by Expr. Expr can be +** be NULL, in which case 0 is returned. +** +** sqlite3WhereExprUsageNN(MaskSet, Expr) -> +** +** Same as sqlite3WhereExprUsage() except that Expr must not be +** NULL. The "NN" suffix on the name stands for "Not Null". +** +** sqlite3WhereExprListUsage(MaskSet, ExprList) -> +** +** Return a Bitmask of all tables referenced by every expression +** in the expression list ExprList. ExprList can be NULL, in which +** case 0 is returned. +** +** sqlite3WhereExprUsageFull(MaskSet, ExprList) -> +** +** Internal use only. Called only by sqlite3WhereExprUsageNN() for +** complex expressions that require pushing register values onto +** the stack. Many calls to sqlite3WhereExprUsageNN() do not need +** the more complex analysis done by this routine. Hence, the +** computations done by this routine are broken out into a separate +** "no-inline" function to avoid the stack push overhead in the +** common case where it is not needed. +*/ +static SQLITE_NOINLINE Bitmask sqlite3WhereExprUsageFull( + WhereMaskSet *pMaskSet, + Expr *p +){ + Bitmask mask; + mask = (p->op==TK_IF_NULL_ROW) ? sqlite3WhereGetMask(pMaskSet, p->iTable) : 0; + if( p->pLeft ) mask |= sqlite3WhereExprUsageNN(pMaskSet, p->pLeft); + if( p->pRight ){ + mask |= sqlite3WhereExprUsageNN(pMaskSet, p->pRight); + assert( p->x.pList==0 ); + }else if( ExprUseXSelect(p) ){ + if( ExprHasProperty(p, EP_VarSelect) ) pMaskSet->bVarSelect = 1; + mask |= exprSelectUsage(pMaskSet, p->x.pSelect); + }else if( p->x.pList ){ + mask |= sqlite3WhereExprListUsage(pMaskSet, p->x.pList); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( (p->op==TK_FUNCTION || p->op==TK_AGG_FUNCTION) && ExprUseYWin(p) ){ + assert( p->y.pWin!=0 ); + mask |= sqlite3WhereExprListUsage(pMaskSet, p->y.pWin->pPartition); + mask |= sqlite3WhereExprListUsage(pMaskSet, p->y.pWin->pOrderBy); + mask |= sqlite3WhereExprUsage(pMaskSet, p->y.pWin->pFilter); + } +#endif + return mask; +} +SQLITE_PRIVATE Bitmask sqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){ + if( p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ + return sqlite3WhereGetMask(pMaskSet, p->iTable); + }else if( ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ + assert( p->op!=TK_IF_NULL_ROW ); + return 0; + } + return sqlite3WhereExprUsageFull(pMaskSet, p); +} +SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet *pMaskSet, Expr *p){ + return p ? sqlite3WhereExprUsageNN(pMaskSet,p) : 0; +} +SQLITE_PRIVATE Bitmask sqlite3WhereExprListUsage(WhereMaskSet *pMaskSet, ExprList *pList){ + int i; + Bitmask mask = 0; + if( pList ){ + for(i=0; i<pList->nExpr; i++){ + mask |= sqlite3WhereExprUsage(pMaskSet, pList->a[i].pExpr); + } + } + return mask; +} + + +/* +** Call exprAnalyze on all terms in a WHERE clause. +** +** Note that exprAnalyze() might add new virtual terms onto the +** end of the WHERE clause. We do not want to analyze these new +** virtual terms, so start analyzing at the end and work forward +** so that the added virtual terms are never processed. +*/ +SQLITE_PRIVATE void sqlite3WhereExprAnalyze( + SrcList *pTabList, /* the FROM clause */ + WhereClause *pWC /* the WHERE clause to be analyzed */ +){ + int i; + for(i=pWC->nTerm-1; i>=0; i--){ + exprAnalyze(pTabList, pWC, i); + } +} + +/* +** For table-valued-functions, transform the function arguments into +** new WHERE clause terms. +** +** Each function argument translates into an equality constraint against +** a HIDDEN column in the table. +*/ +SQLITE_PRIVATE void sqlite3WhereTabFuncArgs( + Parse *pParse, /* Parsing context */ + SrcItem *pItem, /* The FROM clause term to process */ + WhereClause *pWC /* Xfer function arguments to here */ +){ + Table *pTab; + int j, k; + ExprList *pArgs; + Expr *pColRef; + Expr *pTerm; + if( pItem->fg.isTabFunc==0 ) return; + pTab = pItem->pTab; + assert( pTab!=0 ); + pArgs = pItem->u1.pFuncArg; + if( pArgs==0 ) return; + for(j=k=0; j<pArgs->nExpr; j++){ + Expr *pRhs; + u32 joinType; + while( k<pTab->nCol && (pTab->aCol[k].colFlags & COLFLAG_HIDDEN)==0 ){k++;} + if( k>=pTab->nCol ){ + sqlite3ErrorMsg(pParse, "too many arguments on %s() - max %d", + pTab->zName, j); + return; + } + pColRef = sqlite3ExprAlloc(pParse->db, TK_COLUMN, 0, 0); + if( pColRef==0 ) return; + pColRef->iTable = pItem->iCursor; + pColRef->iColumn = k++; + assert( ExprUseYTab(pColRef) ); + pColRef->y.pTab = pTab; + pItem->colUsed |= sqlite3ExprColUsed(pColRef); + pRhs = sqlite3PExpr(pParse, TK_UPLUS, + sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); + pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, pRhs); + if( pItem->fg.jointype & (JT_LEFT|JT_RIGHT) ){ + testcase( pItem->fg.jointype & JT_LEFT ); /* testtag-20230227a */ + testcase( pItem->fg.jointype & JT_RIGHT ); /* testtag-20230227b */ + joinType = EP_OuterON; + }else{ + testcase( pItem->fg.jointype & JT_LTORJ ); /* testtag-20230227c */ + joinType = EP_InnerON; + } + sqlite3SetJoinExpr(pTerm, pItem->iCursor, joinType); + whereClauseInsert(pWC, pTerm, TERM_DYNAMIC); + } +} + +/************** End of whereexpr.c *******************************************/ +/************** Begin file where.c *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This module contains C code that generates VDBE code used to process +** the WHERE clause of SQL statements. This module is responsible for +** generating the code that loops through a table looking for applicable +** rows. Indices are selected and used to speed the search when doing +** so is applicable. Because this module is responsible for selecting +** indices, you might also think of this module as the "query optimizer". +*/ +/* #include "sqliteInt.h" */ +/* #include "whereInt.h" */ + +/* +** Extra information appended to the end of sqlite3_index_info but not +** visible to the xBestIndex function, at least not directly. The +** sqlite3_vtab_collation() interface knows how to reach it, however. +** +** This object is not an API and can be changed from one release to the +** next. As long as allocateIndexInfo() and sqlite3_vtab_collation() +** agree on the structure, all will be well. +*/ +typedef struct HiddenIndexInfo HiddenIndexInfo; +struct HiddenIndexInfo { + WhereClause *pWC; /* The Where clause being analyzed */ + Parse *pParse; /* The parsing context */ + int eDistinct; /* Value to return from sqlite3_vtab_distinct() */ + u32 mIn; /* Mask of terms that are <col> IN (...) */ + u32 mHandleIn; /* Terms that vtab will handle as <col> IN (...) */ + sqlite3_value *aRhs[1]; /* RHS values for constraints. MUST BE LAST + ** because extra space is allocated to hold up + ** to nTerm such values */ +}; + +/* Forward declaration of methods */ +static int whereLoopResize(sqlite3*, WhereLoop*, int); + +/* +** Return the estimated number of output rows from a WHERE clause +*/ +SQLITE_PRIVATE LogEst sqlite3WhereOutputRowCount(WhereInfo *pWInfo){ + return pWInfo->nRowOut; +} + +/* +** Return one of the WHERE_DISTINCT_xxxxx values to indicate how this +** WHERE clause returns outputs for DISTINCT processing. +*/ +SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo *pWInfo){ + return pWInfo->eDistinct; +} + +/* +** Return the number of ORDER BY terms that are satisfied by the +** WHERE clause. A return of 0 means that the output must be +** completely sorted. A return equal to the number of ORDER BY +** terms means that no sorting is needed at all. A return that +** is positive but less than the number of ORDER BY terms means that +** block sorting is required. +*/ +SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo *pWInfo){ + return pWInfo->nOBSat<0 ? 0 : pWInfo->nOBSat; +} + +/* +** In the ORDER BY LIMIT optimization, if the inner-most loop is known +** to emit rows in increasing order, and if the last row emitted by the +** inner-most loop did not fit within the sorter, then we can skip all +** subsequent rows for the current iteration of the inner loop (because they +** will not fit in the sorter either) and continue with the second inner +** loop - the loop immediately outside the inner-most. +** +** When a row does not fit in the sorter (because the sorter already +** holds LIMIT+OFFSET rows that are smaller), then a jump is made to the +** label returned by this function. +** +** If the ORDER BY LIMIT optimization applies, the jump destination should +** be the continuation for the second-inner-most loop. If the ORDER BY +** LIMIT optimization does not apply, then the jump destination should +** be the continuation for the inner-most loop. +** +** It is always safe for this routine to return the continuation of the +** inner-most loop, in the sense that a correct answer will result. +** Returning the continuation the second inner loop is an optimization +** that might make the code run a little faster, but should not change +** the final answer. +*/ +SQLITE_PRIVATE int sqlite3WhereOrderByLimitOptLabel(WhereInfo *pWInfo){ + WhereLevel *pInner; + if( !pWInfo->bOrderedInnerLoop ){ + /* The ORDER BY LIMIT optimization does not apply. Jump to the + ** continuation of the inner-most loop. */ + return pWInfo->iContinue; + } + pInner = &pWInfo->a[pWInfo->nLevel-1]; + assert( pInner->addrNxt!=0 ); + return pInner->pRJ ? pWInfo->iContinue : pInner->addrNxt; +} + +/* +** While generating code for the min/max optimization, after handling +** the aggregate-step call to min() or max(), check to see if any +** additional looping is required. If the output order is such that +** we are certain that the correct answer has already been found, then +** code an OP_Goto to by pass subsequent processing. +** +** Any extra OP_Goto that is coded here is an optimization. The +** correct answer should be obtained regardless. This OP_Goto just +** makes the answer appear faster. +*/ +SQLITE_PRIVATE void sqlite3WhereMinMaxOptEarlyOut(Vdbe *v, WhereInfo *pWInfo){ + WhereLevel *pInner; + int i; + if( !pWInfo->bOrderedInnerLoop ) return; + if( pWInfo->nOBSat==0 ) return; + for(i=pWInfo->nLevel-1; i>=0; i--){ + pInner = &pWInfo->a[i]; + if( (pInner->pWLoop->wsFlags & WHERE_COLUMN_IN)!=0 ){ + sqlite3VdbeGoto(v, pInner->addrNxt); + return; + } + } + sqlite3VdbeGoto(v, pWInfo->iBreak); +} + +/* +** Return the VDBE address or label to jump to in order to continue +** immediately with the next row of a WHERE clause. +*/ +SQLITE_PRIVATE int sqlite3WhereContinueLabel(WhereInfo *pWInfo){ + assert( pWInfo->iContinue!=0 ); + return pWInfo->iContinue; +} + +/* +** Return the VDBE address or label to jump to in order to break +** out of a WHERE loop. +*/ +SQLITE_PRIVATE int sqlite3WhereBreakLabel(WhereInfo *pWInfo){ + return pWInfo->iBreak; +} + +/* +** Return ONEPASS_OFF (0) if an UPDATE or DELETE statement is unable to +** operate directly on the rowids returned by a WHERE clause. Return +** ONEPASS_SINGLE (1) if the statement can operation directly because only +** a single row is to be changed. Return ONEPASS_MULTI (2) if the one-pass +** optimization can be used on multiple +** +** If the ONEPASS optimization is used (if this routine returns true) +** then also write the indices of open cursors used by ONEPASS +** into aiCur[0] and aiCur[1]. iaCur[0] gets the cursor of the data +** table and iaCur[1] gets the cursor used by an auxiliary index. +** Either value may be -1, indicating that cursor is not used. +** Any cursors returned will have been opened for writing. +** +** aiCur[0] and aiCur[1] both get -1 if the where-clause logic is +** unable to use the ONEPASS optimization. +*/ +SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo *pWInfo, int *aiCur){ + memcpy(aiCur, pWInfo->aiCurOnePass, sizeof(int)*2); +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace && pWInfo->eOnePass!=ONEPASS_OFF ){ + sqlite3DebugPrintf("%s cursors: %d %d\n", + pWInfo->eOnePass==ONEPASS_SINGLE ? "ONEPASS_SINGLE" : "ONEPASS_MULTI", + aiCur[0], aiCur[1]); + } +#endif + return pWInfo->eOnePass; +} + +/* +** Return TRUE if the WHERE loop uses the OP_DeferredSeek opcode to move +** the data cursor to the row selected by the index cursor. +*/ +SQLITE_PRIVATE int sqlite3WhereUsesDeferredSeek(WhereInfo *pWInfo){ + return pWInfo->bDeferredSeek; +} + +/* +** Move the content of pSrc into pDest +*/ +static void whereOrMove(WhereOrSet *pDest, WhereOrSet *pSrc){ + pDest->n = pSrc->n; + memcpy(pDest->a, pSrc->a, pDest->n*sizeof(pDest->a[0])); +} + +/* +** Try to insert a new prerequisite/cost entry into the WhereOrSet pSet. +** +** The new entry might overwrite an existing entry, or it might be +** appended, or it might be discarded. Do whatever is the right thing +** so that pSet keeps the N_OR_COST best entries seen so far. +*/ +static int whereOrInsert( + WhereOrSet *pSet, /* The WhereOrSet to be updated */ + Bitmask prereq, /* Prerequisites of the new entry */ + LogEst rRun, /* Run-cost of the new entry */ + LogEst nOut /* Number of outputs for the new entry */ +){ + u16 i; + WhereOrCost *p; + for(i=pSet->n, p=pSet->a; i>0; i--, p++){ + if( rRun<=p->rRun && (prereq & p->prereq)==prereq ){ + goto whereOrInsert_done; + } + if( p->rRun<=rRun && (p->prereq & prereq)==p->prereq ){ + return 0; + } + } + if( pSet->n<N_OR_COST ){ + p = &pSet->a[pSet->n++]; + p->nOut = nOut; + }else{ + p = pSet->a; + for(i=1; i<pSet->n; i++){ + if( p->rRun>pSet->a[i].rRun ) p = pSet->a + i; + } + if( p->rRun<=rRun ) return 0; + } +whereOrInsert_done: + p->prereq = prereq; + p->rRun = rRun; + if( p->nOut>nOut ) p->nOut = nOut; + return 1; +} + +/* +** Return the bitmask for the given cursor number. Return 0 if +** iCursor is not in the set. +*/ +SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){ + int i; + assert( pMaskSet->n<=(int)sizeof(Bitmask)*8 ); + assert( pMaskSet->n>0 || pMaskSet->ix[0]<0 ); + assert( iCursor>=-1 ); + if( pMaskSet->ix[0]==iCursor ){ + return 1; + } + for(i=1; i<pMaskSet->n; i++){ + if( pMaskSet->ix[i]==iCursor ){ + return MASKBIT(i); + } + } + return 0; +} + +/* Allocate memory that is automatically freed when pWInfo is freed. +*/ +SQLITE_PRIVATE void *sqlite3WhereMalloc(WhereInfo *pWInfo, u64 nByte){ + WhereMemBlock *pBlock; + pBlock = sqlite3DbMallocRawNN(pWInfo->pParse->db, nByte+sizeof(*pBlock)); + if( pBlock ){ + pBlock->pNext = pWInfo->pMemToFree; + pBlock->sz = nByte; + pWInfo->pMemToFree = pBlock; + pBlock++; + } + return (void*)pBlock; +} +SQLITE_PRIVATE void *sqlite3WhereRealloc(WhereInfo *pWInfo, void *pOld, u64 nByte){ + void *pNew = sqlite3WhereMalloc(pWInfo, nByte); + if( pNew && pOld ){ + WhereMemBlock *pOldBlk = (WhereMemBlock*)pOld; + pOldBlk--; + assert( pOldBlk->sz<nByte ); + memcpy(pNew, pOld, pOldBlk->sz); + } + return pNew; +} + +/* +** Create a new mask for cursor iCursor. +** +** There is one cursor per table in the FROM clause. The number of +** tables in the FROM clause is limited by a test early in the +** sqlite3WhereBegin() routine. So we know that the pMaskSet->ix[] +** array will never overflow. +*/ +static void createMask(WhereMaskSet *pMaskSet, int iCursor){ + assert( pMaskSet->n < ArraySize(pMaskSet->ix) ); + pMaskSet->ix[pMaskSet->n++] = iCursor; +} + +/* +** If the right-hand branch of the expression is a TK_COLUMN, then return +** a pointer to the right-hand branch. Otherwise, return NULL. +*/ +static Expr *whereRightSubexprIsColumn(Expr *p){ + p = sqlite3ExprSkipCollateAndLikely(p->pRight); + if( ALWAYS(p!=0) && p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ + return p; + } + return 0; +} + +/* +** Advance to the next WhereTerm that matches according to the criteria +** established when the pScan object was initialized by whereScanInit(). +** Return NULL if there are no more matching WhereTerms. +*/ +static WhereTerm *whereScanNext(WhereScan *pScan){ + int iCur; /* The cursor on the LHS of the term */ + i16 iColumn; /* The column on the LHS of the term. -1 for IPK */ + Expr *pX; /* An expression being tested */ + WhereClause *pWC; /* Shorthand for pScan->pWC */ + WhereTerm *pTerm; /* The term being tested */ + int k = pScan->k; /* Where to start scanning */ + + assert( pScan->iEquiv<=pScan->nEquiv ); + pWC = pScan->pWC; + while(1){ + iColumn = pScan->aiColumn[pScan->iEquiv-1]; + iCur = pScan->aiCur[pScan->iEquiv-1]; + assert( pWC!=0 ); + assert( iCur>=0 ); + do{ + for(pTerm=pWC->a+k; k<pWC->nTerm; k++, pTerm++){ + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 || pTerm->leftCursor<0 ); + if( pTerm->leftCursor==iCur + && pTerm->u.x.leftColumn==iColumn + && (iColumn!=XN_EXPR + || sqlite3ExprCompareSkip(pTerm->pExpr->pLeft, + pScan->pIdxExpr,iCur)==0) + && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_OuterON)) + ){ + if( (pTerm->eOperator & WO_EQUIV)!=0 + && pScan->nEquiv<ArraySize(pScan->aiCur) + && (pX = whereRightSubexprIsColumn(pTerm->pExpr))!=0 + ){ + int j; + for(j=0; j<pScan->nEquiv; j++){ + if( pScan->aiCur[j]==pX->iTable + && pScan->aiColumn[j]==pX->iColumn ){ + break; + } + } + if( j==pScan->nEquiv ){ + pScan->aiCur[j] = pX->iTable; + pScan->aiColumn[j] = pX->iColumn; + pScan->nEquiv++; + } + } + if( (pTerm->eOperator & pScan->opMask)!=0 ){ + /* Verify the affinity and collating sequence match */ + if( pScan->zCollName && (pTerm->eOperator & WO_ISNULL)==0 ){ + CollSeq *pColl; + Parse *pParse = pWC->pWInfo->pParse; + pX = pTerm->pExpr; + if( !sqlite3IndexAffinityOk(pX, pScan->idxaff) ){ + continue; + } + assert(pX->pLeft); + pColl = sqlite3ExprCompareCollSeq(pParse, pX); + if( pColl==0 ) pColl = pParse->db->pDfltColl; + if( sqlite3StrICmp(pColl->zName, pScan->zCollName) ){ + continue; + } + } + if( (pTerm->eOperator & (WO_EQ|WO_IS))!=0 + && (pX = pTerm->pExpr->pRight, ALWAYS(pX!=0)) + && pX->op==TK_COLUMN + && pX->iTable==pScan->aiCur[0] + && pX->iColumn==pScan->aiColumn[0] + ){ + testcase( pTerm->eOperator & WO_IS ); + continue; + } + pScan->pWC = pWC; + pScan->k = k+1; +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x20000 ){ + int ii; + sqlite3DebugPrintf("SCAN-TERM %p: nEquiv=%d", + pTerm, pScan->nEquiv); + for(ii=0; ii<pScan->nEquiv; ii++){ + sqlite3DebugPrintf(" {%d:%d}", + pScan->aiCur[ii], pScan->aiColumn[ii]); + } + sqlite3DebugPrintf("\n"); + } +#endif + return pTerm; + } + } + } + pWC = pWC->pOuter; + k = 0; + }while( pWC!=0 ); + if( pScan->iEquiv>=pScan->nEquiv ) break; + pWC = pScan->pOrigWC; + k = 0; + pScan->iEquiv++; + } + return 0; +} + +/* +** This is whereScanInit() for the case of an index on an expression. +** It is factored out into a separate tail-recursion subroutine so that +** the normal whereScanInit() routine, which is a high-runner, does not +** need to push registers onto the stack as part of its prologue. +*/ +static SQLITE_NOINLINE WhereTerm *whereScanInitIndexExpr(WhereScan *pScan){ + pScan->idxaff = sqlite3ExprAffinity(pScan->pIdxExpr); + return whereScanNext(pScan); +} + +/* +** Initialize a WHERE clause scanner object. Return a pointer to the +** first match. Return NULL if there are no matches. +** +** The scanner will be searching the WHERE clause pWC. It will look +** for terms of the form "X <op> <expr>" where X is column iColumn of table +** iCur. Or if pIdx!=0 then X is column iColumn of index pIdx. pIdx +** must be one of the indexes of table iCur. +** +** The <op> must be one of the operators described by opMask. +** +** If the search is for X and the WHERE clause contains terms of the +** form X=Y then this routine might also return terms of the form +** "Y <op> <expr>". The number of levels of transitivity is limited, +** but is enough to handle most commonly occurring SQL statements. +** +** If X is not the INTEGER PRIMARY KEY then X must be compatible with +** index pIdx. +*/ +static WhereTerm *whereScanInit( + WhereScan *pScan, /* The WhereScan object being initialized */ + WhereClause *pWC, /* The WHERE clause to be scanned */ + int iCur, /* Cursor to scan for */ + int iColumn, /* Column to scan for */ + u32 opMask, /* Operator(s) to scan for */ + Index *pIdx /* Must be compatible with this index */ +){ + pScan->pOrigWC = pWC; + pScan->pWC = pWC; + pScan->pIdxExpr = 0; + pScan->idxaff = 0; + pScan->zCollName = 0; + pScan->opMask = opMask; + pScan->k = 0; + pScan->aiCur[0] = iCur; + pScan->nEquiv = 1; + pScan->iEquiv = 1; + if( pIdx ){ + int j = iColumn; + iColumn = pIdx->aiColumn[j]; + if( iColumn==pIdx->pTable->iPKey ){ + iColumn = XN_ROWID; + }else if( iColumn>=0 ){ + pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; + pScan->zCollName = pIdx->azColl[j]; + }else if( iColumn==XN_EXPR ){ + pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; + pScan->zCollName = pIdx->azColl[j]; + pScan->aiColumn[0] = XN_EXPR; + return whereScanInitIndexExpr(pScan); + } + }else if( iColumn==XN_EXPR ){ + return 0; + } + pScan->aiColumn[0] = iColumn; + return whereScanNext(pScan); +} + +/* +** Search for a term in the WHERE clause that is of the form "X <op> <expr>" +** where X is a reference to the iColumn of table iCur or of index pIdx +** if pIdx!=0 and <op> is one of the WO_xx operator codes specified by +** the op parameter. Return a pointer to the term. Return 0 if not found. +** +** If pIdx!=0 then it must be one of the indexes of table iCur. +** Search for terms matching the iColumn-th column of pIdx +** rather than the iColumn-th column of table iCur. +** +** The term returned might by Y=<expr> if there is another constraint in +** the WHERE clause that specifies that X=Y. Any such constraints will be +** identified by the WO_EQUIV bit in the pTerm->eOperator field. The +** aiCur[]/iaColumn[] arrays hold X and all its equivalents. There are 11 +** slots in aiCur[]/aiColumn[] so that means we can look for X plus up to 10 +** other equivalent values. Hence a search for X will return <expr> if X=A1 +** and A1=A2 and A2=A3 and ... and A9=A10 and A10=<expr>. +** +** If there are multiple terms in the WHERE clause of the form "X <op> <expr>" +** then try for the one with no dependencies on <expr> - in other words where +** <expr> is a constant expression of some kind. Only return entries of +** the form "X <op> Y" where Y is a column in another table if no terms of +** the form "X <op> <const-expr>" exist. If no terms with a constant RHS +** exist, try to return a term that does not use WO_EQUIV. +*/ +SQLITE_PRIVATE WhereTerm *sqlite3WhereFindTerm( + WhereClause *pWC, /* The WHERE clause to be searched */ + int iCur, /* Cursor number of LHS */ + int iColumn, /* Column number of LHS */ + Bitmask notReady, /* RHS must not overlap with this mask */ + u32 op, /* Mask of WO_xx values describing operator */ + Index *pIdx /* Must be compatible with this index, if not NULL */ +){ + WhereTerm *pResult = 0; + WhereTerm *p; + WhereScan scan; + + p = whereScanInit(&scan, pWC, iCur, iColumn, op, pIdx); + op &= WO_EQ|WO_IS; + while( p ){ + if( (p->prereqRight & notReady)==0 ){ + if( p->prereqRight==0 && (p->eOperator&op)!=0 ){ + testcase( p->eOperator & WO_IS ); + return p; + } + if( pResult==0 ) pResult = p; + } + p = whereScanNext(&scan); + } + return pResult; +} + +/* +** This function searches pList for an entry that matches the iCol-th column +** of index pIdx. +** +** If such an expression is found, its index in pList->a[] is returned. If +** no expression is found, -1 is returned. +*/ +static int findIndexCol( + Parse *pParse, /* Parse context */ + ExprList *pList, /* Expression list to search */ + int iBase, /* Cursor for table associated with pIdx */ + Index *pIdx, /* Index to match column of */ + int iCol /* Column of index to match */ +){ + int i; + const char *zColl = pIdx->azColl[iCol]; + + for(i=0; i<pList->nExpr; i++){ + Expr *p = sqlite3ExprSkipCollateAndLikely(pList->a[i].pExpr); + if( ALWAYS(p!=0) + && (p->op==TK_COLUMN || p->op==TK_AGG_COLUMN) + && p->iColumn==pIdx->aiColumn[iCol] + && p->iTable==iBase + ){ + CollSeq *pColl = sqlite3ExprNNCollSeq(pParse, pList->a[i].pExpr); + if( 0==sqlite3StrICmp(pColl->zName, zColl) ){ + return i; + } + } + } + + return -1; +} + +/* +** Return TRUE if the iCol-th column of index pIdx is NOT NULL +*/ +static int indexColumnNotNull(Index *pIdx, int iCol){ + int j; + assert( pIdx!=0 ); + assert( iCol>=0 && iCol<pIdx->nColumn ); + j = pIdx->aiColumn[iCol]; + if( j>=0 ){ + return pIdx->pTable->aCol[j].notNull; + }else if( j==(-1) ){ + return 1; + }else{ + assert( j==(-2) ); + return 0; /* Assume an indexed expression can always yield a NULL */ + + } +} + +/* +** Return true if the DISTINCT expression-list passed as the third argument +** is redundant. +** +** A DISTINCT list is redundant if any subset of the columns in the +** DISTINCT list are collectively unique and individually non-null. +*/ +static int isDistinctRedundant( + Parse *pParse, /* Parsing context */ + SrcList *pTabList, /* The FROM clause */ + WhereClause *pWC, /* The WHERE clause */ + ExprList *pDistinct /* The result set that needs to be DISTINCT */ +){ + Table *pTab; + Index *pIdx; + int i; + int iBase; + + /* If there is more than one table or sub-select in the FROM clause of + ** this query, then it will not be possible to show that the DISTINCT + ** clause is redundant. */ + if( pTabList->nSrc!=1 ) return 0; + iBase = pTabList->a[0].iCursor; + pTab = pTabList->a[0].pTab; + + /* If any of the expressions is an IPK column on table iBase, then return + ** true. Note: The (p->iTable==iBase) part of this test may be false if the + ** current SELECT is a correlated sub-query. + */ + for(i=0; i<pDistinct->nExpr; i++){ + Expr *p = sqlite3ExprSkipCollateAndLikely(pDistinct->a[i].pExpr); + if( NEVER(p==0) ) continue; + if( p->op!=TK_COLUMN && p->op!=TK_AGG_COLUMN ) continue; + if( p->iTable==iBase && p->iColumn<0 ) return 1; + } + + /* Loop through all indices on the table, checking each to see if it makes + ** the DISTINCT qualifier redundant. It does so if: + ** + ** 1. The index is itself UNIQUE, and + ** + ** 2. All of the columns in the index are either part of the pDistinct + ** list, or else the WHERE clause contains a term of the form "col=X", + ** where X is a constant value. The collation sequences of the + ** comparison and select-list expressions must match those of the index. + ** + ** 3. All of those index columns for which the WHERE clause does not + ** contain a "col=X" term are subject to a NOT NULL constraint. + */ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( !IsUniqueIndex(pIdx) ) continue; + if( pIdx->pPartIdxWhere ) continue; + for(i=0; i<pIdx->nKeyCol; i++){ + if( 0==sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask)0, WO_EQ, pIdx) ){ + if( findIndexCol(pParse, pDistinct, iBase, pIdx, i)<0 ) break; + if( indexColumnNotNull(pIdx, i)==0 ) break; + } + } + if( i==pIdx->nKeyCol ){ + /* This index implies that the DISTINCT qualifier is redundant. */ + return 1; + } + } + + return 0; +} + + +/* +** Estimate the logarithm of the input value to base 2. +*/ +static LogEst estLog(LogEst N){ + return N<=10 ? 0 : sqlite3LogEst(N) - 33; +} + +/* +** Convert OP_Column opcodes to OP_Copy in previously generated code. +** +** This routine runs over generated VDBE code and translates OP_Column +** opcodes into OP_Copy when the table is being accessed via co-routine +** instead of via table lookup. +** +** If the iAutoidxCur is not zero, then any OP_Rowid instructions on +** cursor iTabCur are transformed into OP_Sequence opcode for the +** iAutoidxCur cursor, in order to generate unique rowids for the +** automatic index being generated. +*/ +static void translateColumnToCopy( + Parse *pParse, /* Parsing context */ + int iStart, /* Translate from this opcode to the end */ + int iTabCur, /* OP_Column/OP_Rowid references to this table */ + int iRegister, /* The first column is in this register */ + int iAutoidxCur /* If non-zero, cursor of autoindex being generated */ +){ + Vdbe *v = pParse->pVdbe; + VdbeOp *pOp = sqlite3VdbeGetOp(v, iStart); + int iEnd = sqlite3VdbeCurrentAddr(v); + if( pParse->db->mallocFailed ) return; + for(; iStart<iEnd; iStart++, pOp++){ + if( pOp->p1!=iTabCur ) continue; + if( pOp->opcode==OP_Column ){ + pOp->opcode = OP_Copy; + pOp->p1 = pOp->p2 + iRegister; + pOp->p2 = pOp->p3; + pOp->p3 = 0; + pOp->p5 = 2; /* Cause the MEM_Subtype flag to be cleared */ + }else if( pOp->opcode==OP_Rowid ){ + pOp->opcode = OP_Sequence; + pOp->p1 = iAutoidxCur; +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( iAutoidxCur==0 ){ + pOp->opcode = OP_Null; + pOp->p3 = 0; + } +#endif + } + } +} + +/* +** Two routines for printing the content of an sqlite3_index_info +** structure. Used for testing and debugging only. If neither +** SQLITE_TEST or SQLITE_DEBUG are defined, then these routines +** are no-ops. +*/ +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(WHERETRACE_ENABLED) +static void whereTraceIndexInfoInputs(sqlite3_index_info *p){ + int i; + if( (sqlite3WhereTrace & 0x10)==0 ) return; + for(i=0; i<p->nConstraint; i++){ + sqlite3DebugPrintf( + " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", + i, + p->aConstraint[i].iColumn, + p->aConstraint[i].iTermOffset, + p->aConstraint[i].op, + p->aConstraint[i].usable, + sqlite3_vtab_collation(p,i)); + } + for(i=0; i<p->nOrderBy; i++){ + sqlite3DebugPrintf(" orderby[%d]: col=%d desc=%d\n", + i, + p->aOrderBy[i].iColumn, + p->aOrderBy[i].desc); + } +} +static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){ + int i; + if( (sqlite3WhereTrace & 0x10)==0 ) return; + for(i=0; i<p->nConstraint; i++){ + sqlite3DebugPrintf(" usage[%d]: argvIdx=%d omit=%d\n", + i, + p->aConstraintUsage[i].argvIndex, + p->aConstraintUsage[i].omit); + } + sqlite3DebugPrintf(" idxNum=%d\n", p->idxNum); + sqlite3DebugPrintf(" idxStr=%s\n", p->idxStr); + sqlite3DebugPrintf(" orderByConsumed=%d\n", p->orderByConsumed); + sqlite3DebugPrintf(" estimatedCost=%g\n", p->estimatedCost); + sqlite3DebugPrintf(" estimatedRows=%lld\n", p->estimatedRows); +} +#else +#define whereTraceIndexInfoInputs(A) +#define whereTraceIndexInfoOutputs(A) +#endif + +/* +** We know that pSrc is an operand of an outer join. Return true if +** pTerm is a constraint that is compatible with that join. +** +** pTerm must be EP_OuterON if pSrc is the right operand of an +** outer join. pTerm can be either EP_OuterON or EP_InnerON if pSrc +** is the left operand of a RIGHT join. +** +** See https://sqlite.org/forum/forumpost/206d99a16dd9212f +** for an example of a WHERE clause constraints that may not be used on +** the right table of a RIGHT JOIN because the constraint implies a +** not-NULL condition on the left table of the RIGHT JOIN. +*/ +static int constraintCompatibleWithOuterJoin( + const WhereTerm *pTerm, /* WHERE clause term to check */ + const SrcItem *pSrc /* Table we are trying to access */ +){ + assert( (pSrc->fg.jointype&(JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ); /* By caller */ + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); + testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) + testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) + || pTerm->pExpr->w.iJoin != pSrc->iCursor + ){ + return 0; + } + if( (pSrc->fg.jointype & (JT_LEFT|JT_RIGHT))!=0 + && ExprHasProperty(pTerm->pExpr, EP_InnerON) + ){ + return 0; + } + return 1; +} + + + +#ifndef SQLITE_OMIT_AUTOMATIC_INDEX +/* +** Return TRUE if the WHERE clause term pTerm is of a form where it +** could be used with an index to access pSrc, assuming an appropriate +** index existed. +*/ +static int termCanDriveIndex( + const WhereTerm *pTerm, /* WHERE clause term to check */ + const SrcItem *pSrc, /* Table we are trying to access */ + const Bitmask notReady /* Tables in outer loops of the join */ +){ + char aff; + if( pTerm->leftCursor!=pSrc->iCursor ) return 0; + if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) return 0; + assert( (pSrc->fg.jointype & JT_RIGHT)==0 ); + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 + && !constraintCompatibleWithOuterJoin(pTerm,pSrc) + ){ + return 0; /* See https://sqlite.org/forum/forumpost/51e6959f61 */ + } + if( (pTerm->prereqRight & notReady)!=0 ) return 0; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + if( pTerm->u.x.leftColumn<0 ) return 0; + aff = pSrc->pTab->aCol[pTerm->u.x.leftColumn].affinity; + if( !sqlite3IndexAffinityOk(pTerm->pExpr, aff) ) return 0; + testcase( pTerm->pExpr->op==TK_IS ); + return 1; +} +#endif + + +#ifndef SQLITE_OMIT_AUTOMATIC_INDEX + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +/* +** Argument pIdx represents an automatic index that the current statement +** will create and populate. Add an OP_Explain with text of the form: +** +** CREATE AUTOMATIC INDEX ON <table>(<cols>) [WHERE <expr>] +** +** This is only required if sqlite3_stmt_scanstatus() is enabled, to +** associate an SQLITE_SCANSTAT_NCYCLE and SQLITE_SCANSTAT_NLOOP +** values with. In order to avoid breaking legacy code and test cases, +** the OP_Explain is not added if this is an EXPLAIN QUERY PLAN command. +*/ +static void explainAutomaticIndex( + Parse *pParse, + Index *pIdx, /* Automatic index to explain */ + int bPartial, /* True if pIdx is a partial index */ + int *pAddrExplain /* OUT: Address of OP_Explain */ +){ + if( IS_STMT_SCANSTATUS(pParse->db) && pParse->explain!=2 ){ + Table *pTab = pIdx->pTable; + const char *zSep = ""; + char *zText = 0; + int ii = 0; + sqlite3_str *pStr = sqlite3_str_new(pParse->db); + sqlite3_str_appendf(pStr,"CREATE AUTOMATIC INDEX ON %s(", pTab->zName); + assert( pIdx->nColumn>1 ); + assert( pIdx->aiColumn[pIdx->nColumn-1]==XN_ROWID ); + for(ii=0; ii<(pIdx->nColumn-1); ii++){ + const char *zName = 0; + int iCol = pIdx->aiColumn[ii]; + + zName = pTab->aCol[iCol].zCnName; + sqlite3_str_appendf(pStr, "%s%s", zSep, zName); + zSep = ", "; + } + zText = sqlite3_str_finish(pStr); + if( zText==0 ){ + sqlite3OomFault(pParse->db); + }else{ + *pAddrExplain = sqlite3VdbeExplain( + pParse, 0, "%s)%s", zText, (bPartial ? " WHERE <expr>" : "") + ); + sqlite3_free(zText); + } + } +} +#else +# define explainAutomaticIndex(a,b,c,d) +#endif + +/* +** Generate code to construct the Index object for an automatic index +** and to set up the WhereLevel object pLevel so that the code generator +** makes use of the automatic index. +*/ +static SQLITE_NOINLINE void constructAutomaticIndex( + Parse *pParse, /* The parsing context */ + WhereClause *pWC, /* The WHERE clause */ + const Bitmask notReady, /* Mask of cursors that are not available */ + WhereLevel *pLevel /* Write new index here */ +){ + int nKeyCol; /* Number of columns in the constructed index */ + WhereTerm *pTerm; /* A single term of the WHERE clause */ + WhereTerm *pWCEnd; /* End of pWC->a[] */ + Index *pIdx; /* Object describing the transient index */ + Vdbe *v; /* Prepared statement under construction */ + int addrInit; /* Address of the initialization bypass jump */ + Table *pTable; /* The table being indexed */ + int addrTop; /* Top of the index fill loop */ + int regRecord; /* Register holding an index record */ + int n; /* Column counter */ + int i; /* Loop counter */ + int mxBitCol; /* Maximum column in pSrc->colUsed */ + CollSeq *pColl; /* Collating sequence to on a column */ + WhereLoop *pLoop; /* The Loop object */ + char *zNotUsed; /* Extra space on the end of pIdx */ + Bitmask idxCols; /* Bitmap of columns used for indexing */ + Bitmask extraCols; /* Bitmap of additional columns */ + u8 sentWarning = 0; /* True if a warning has been issued */ + u8 useBloomFilter = 0; /* True to also add a Bloom filter */ + Expr *pPartial = 0; /* Partial Index Expression */ + int iContinue = 0; /* Jump here to skip excluded rows */ + SrcList *pTabList; /* The complete FROM clause */ + SrcItem *pSrc; /* The FROM clause term to get the next index */ + int addrCounter = 0; /* Address where integer counter is initialized */ + int regBase; /* Array of registers where record is assembled */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExp = 0; /* Address of OP_Explain */ +#endif + + /* Generate code to skip over the creation and initialization of the + ** transient index on 2nd and subsequent iterations of the loop. */ + v = pParse->pVdbe; + assert( v!=0 ); + addrInit = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + + /* Count the number of columns that will be added to the index + ** and used to match WHERE clause constraints */ + nKeyCol = 0; + pTabList = pWC->pWInfo->pTabList; + pSrc = &pTabList->a[pLevel->iFrom]; + pTable = pSrc->pTab; + pWCEnd = &pWC->a[pWC->nTerm]; + pLoop = pLevel->pWLoop; + idxCols = 0; + for(pTerm=pWC->a; pTerm<pWCEnd; pTerm++){ + Expr *pExpr = pTerm->pExpr; + /* Make the automatic index a partial index if there are terms in the + ** WHERE clause (or the ON clause of a LEFT join) that constrain which + ** rows of the target table (pSrc) that can be used. */ + if( (pTerm->wtFlags & TERM_VIRTUAL)==0 + && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, pLevel->iFrom) + ){ + pPartial = sqlite3ExprAnd(pParse, pPartial, + sqlite3ExprDup(pParse->db, pExpr, 0)); + } + if( termCanDriveIndex(pTerm, pSrc, notReady) ){ + int iCol; + Bitmask cMask; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + iCol = pTerm->u.x.leftColumn; + cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); + testcase( iCol==BMS ); + testcase( iCol==BMS-1 ); + if( !sentWarning ){ + sqlite3_log(SQLITE_WARNING_AUTOINDEX, + "automatic index on %s(%s)", pTable->zName, + pTable->aCol[iCol].zCnName); + sentWarning = 1; + } + if( (idxCols & cMask)==0 ){ + if( whereLoopResize(pParse->db, pLoop, nKeyCol+1) ){ + goto end_auto_index_create; + } + pLoop->aLTerm[nKeyCol++] = pTerm; + idxCols |= cMask; + } + } + } + assert( nKeyCol>0 || pParse->db->mallocFailed ); + pLoop->u.btree.nEq = pLoop->nLTerm = nKeyCol; + pLoop->wsFlags = WHERE_COLUMN_EQ | WHERE_IDX_ONLY | WHERE_INDEXED + | WHERE_AUTO_INDEX; + + /* Count the number of additional columns needed to create a + ** covering index. A "covering index" is an index that contains all + ** columns that are needed by the query. With a covering index, the + ** original table never needs to be accessed. Automatic indices must + ** be a covering index because the index will not be updated if the + ** original table changes and the index and table cannot both be used + ** if they go out of sync. + */ + if( IsView(pTable) ){ + extraCols = ALLBITS; + }else{ + extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); + } + mxBitCol = MIN(BMS-1,pTable->nCol); + testcase( pTable->nCol==BMS-1 ); + testcase( pTable->nCol==BMS-2 ); + for(i=0; i<mxBitCol; i++){ + if( extraCols & MASKBIT(i) ) nKeyCol++; + } + if( pSrc->colUsed & MASKBIT(BMS-1) ){ + nKeyCol += pTable->nCol - BMS + 1; + } + + /* Construct the Index object to describe this index */ + pIdx = sqlite3AllocateIndexObject(pParse->db, nKeyCol+1, 0, &zNotUsed); + if( pIdx==0 ) goto end_auto_index_create; + pLoop->u.btree.pIndex = pIdx; + pIdx->zName = "auto-index"; + pIdx->pTable = pTable; + n = 0; + idxCols = 0; + for(pTerm=pWC->a; pTerm<pWCEnd; pTerm++){ + if( termCanDriveIndex(pTerm, pSrc, notReady) ){ + int iCol; + Bitmask cMask; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + iCol = pTerm->u.x.leftColumn; + cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); + testcase( iCol==BMS-1 ); + testcase( iCol==BMS ); + if( (idxCols & cMask)==0 ){ + Expr *pX = pTerm->pExpr; + idxCols |= cMask; + pIdx->aiColumn[n] = pTerm->u.x.leftColumn; + pColl = sqlite3ExprCompareCollSeq(pParse, pX); + assert( pColl!=0 || pParse->nErr>0 ); /* TH3 collate01.800 */ + pIdx->azColl[n] = pColl ? pColl->zName : sqlite3StrBINARY; + n++; + if( ALWAYS(pX->pLeft!=0) + && sqlite3ExprAffinity(pX->pLeft)!=SQLITE_AFF_TEXT + ){ + /* TUNING: only use a Bloom filter on an automatic index + ** if one or more key columns has the ability to hold numeric + ** values, since strings all have the same hash in the Bloom + ** filter implementation and hence a Bloom filter on a text column + ** is not usually helpful. */ + useBloomFilter = 1; + } + } + } + } + assert( (u32)n==pLoop->u.btree.nEq ); + + /* Add additional columns needed to make the automatic index into + ** a covering index */ + for(i=0; i<mxBitCol; i++){ + if( extraCols & MASKBIT(i) ){ + pIdx->aiColumn[n] = i; + pIdx->azColl[n] = sqlite3StrBINARY; + n++; + } + } + if( pSrc->colUsed & MASKBIT(BMS-1) ){ + for(i=BMS-1; i<pTable->nCol; i++){ + pIdx->aiColumn[n] = i; + pIdx->azColl[n] = sqlite3StrBINARY; + n++; + } + } + assert( n==nKeyCol ); + pIdx->aiColumn[n] = XN_ROWID; + pIdx->azColl[n] = sqlite3StrBINARY; + + /* Create the automatic index */ + explainAutomaticIndex(pParse, pIdx, pPartial!=0, &addrExp); + assert( pLevel->iIdxCur>=0 ); + pLevel->iIdxCur = pParse->nTab++; + sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + VdbeComment((v, "for %s", pTable->zName)); + if( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) && useBloomFilter ){ + sqlite3WhereExplainBloomFilter(pParse, pWC->pWInfo, pLevel); + pLevel->regFilter = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Blob, 10000, pLevel->regFilter); + } + + /* Fill the automatic index with content */ + assert( pSrc == &pWC->pWInfo->pTabList->a[pLevel->iFrom] ); + if( pSrc->fg.viaCoroutine ){ + int regYield = pSrc->regReturn; + addrCounter = sqlite3VdbeAddOp2(v, OP_Integer, 0, 0); + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pSrc->addrFillSub); + addrTop = sqlite3VdbeAddOp1(v, OP_Yield, regYield); + VdbeCoverage(v); + VdbeComment((v, "next row of %s", pSrc->pTab->zName)); + }else{ + addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v); + } + if( pPartial ){ + iContinue = sqlite3VdbeMakeLabel(pParse); + sqlite3ExprIfFalse(pParse, pPartial, iContinue, SQLITE_JUMPIFNULL); + pLoop->wsFlags |= WHERE_PARTIALIDX; + } + regRecord = sqlite3GetTempReg(pParse); + regBase = sqlite3GenerateIndexKey( + pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0, 0, 0 + ); + if( pLevel->regFilter ){ + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, + regBase, pLoop->u.btree.nEq); + } + sqlite3VdbeScanStatusCounters(v, addrExp, addrExp, sqlite3VdbeCurrentAddr(v)); + sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); + if( pSrc->fg.viaCoroutine ){ + sqlite3VdbeChangeP2(v, addrCounter, regBase+n); + testcase( pParse->db->mallocFailed ); + assert( pLevel->iIdxCur>0 ); + translateColumnToCopy(pParse, addrTop, pLevel->iTabCur, + pSrc->regResult, pLevel->iIdxCur); + sqlite3VdbeGoto(v, addrTop); + pSrc->fg.viaCoroutine = 0; + }else{ + sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v); + sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX); + } + sqlite3VdbeJumpHere(v, addrTop); + sqlite3ReleaseTempReg(pParse, regRecord); + + /* Jump here when skipping the initialization */ + sqlite3VdbeJumpHere(v, addrInit); + sqlite3VdbeScanStatusRange(v, addrExp, addrExp, -1); + +end_auto_index_create: + sqlite3ExprDelete(pParse->db, pPartial); +} +#endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ + +/* +** Generate bytecode that will initialize a Bloom filter that is appropriate +** for pLevel. +** +** If there are inner loops within pLevel that have the WHERE_BLOOMFILTER +** flag set, initialize a Bloomfilter for them as well. Except don't do +** this recursive initialization if the SQLITE_BloomPulldown optimization has +** been turned off. +** +** When the Bloom filter is initialized, the WHERE_BLOOMFILTER flag is cleared +** from the loop, but the regFilter value is set to a register that implements +** the Bloom filter. When regFilter is positive, the +** sqlite3WhereCodeOneLoopStart() will generate code to test the Bloom filter +** and skip the subsequence B-Tree seek if the Bloom filter indicates that +** no matching rows exist. +** +** This routine may only be called if it has previously been determined that +** the loop would benefit from a Bloom filter, and the WHERE_BLOOMFILTER bit +** is set. +*/ +static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( + WhereInfo *pWInfo, /* The WHERE clause */ + int iLevel, /* Index in pWInfo->a[] that is pLevel */ + WhereLevel *pLevel, /* Make a Bloom filter for this FROM term */ + Bitmask notReady /* Loops that are not ready */ +){ + int addrOnce; /* Address of opening OP_Once */ + int addrTop; /* Address of OP_Rewind */ + int addrCont; /* Jump here to skip a row */ + const WhereTerm *pTerm; /* For looping over WHERE clause terms */ + const WhereTerm *pWCEnd; /* Last WHERE clause term */ + Parse *pParse = pWInfo->pParse; /* Parsing context */ + Vdbe *v = pParse->pVdbe; /* VDBE under construction */ + WhereLoop *pLoop = pLevel->pWLoop; /* The loop being coded */ + int iCur; /* Cursor for table getting the filter */ + IndexedExpr *saved_pIdxEpr; /* saved copy of Parse.pIdxEpr */ + + saved_pIdxEpr = pParse->pIdxEpr; + pParse->pIdxEpr = 0; + + assert( pLoop!=0 ); + assert( v!=0 ); + assert( pLoop->wsFlags & WHERE_BLOOMFILTER ); + + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + do{ + const SrcList *pTabList; + const SrcItem *pItem; + const Table *pTab; + u64 sz; + int iSrc; + sqlite3WhereExplainBloomFilter(pParse, pWInfo, pLevel); + addrCont = sqlite3VdbeMakeLabel(pParse); + iCur = pLevel->iTabCur; + pLevel->regFilter = ++pParse->nMem; + + /* The Bloom filter is a Blob held in a register. Initialize it + ** to zero-filled blob of at least 80K bits, but maybe more if the + ** estimated size of the table is larger. We could actually + ** measure the size of the table at run-time using OP_Count with + ** P3==1 and use that value to initialize the blob. But that makes + ** testing complicated. By basing the blob size on the value in the + ** sqlite_stat1 table, testing is much easier. + */ + pTabList = pWInfo->pTabList; + iSrc = pLevel->iFrom; + pItem = &pTabList->a[iSrc]; + assert( pItem!=0 ); + pTab = pItem->pTab; + assert( pTab!=0 ); + sz = sqlite3LogEstToInt(pTab->nRowLogEst); + if( sz<10000 ){ + sz = 10000; + }else if( sz>10000000 ){ + sz = 10000000; + } + sqlite3VdbeAddOp2(v, OP_Blob, (int)sz, pLevel->regFilter); + + addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); + pWCEnd = &pWInfo->sWC.a[pWInfo->sWC.nTerm]; + for(pTerm=pWInfo->sWC.a; pTerm<pWCEnd; pTerm++){ + Expr *pExpr = pTerm->pExpr; + if( (pTerm->wtFlags & TERM_VIRTUAL)==0 + && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, iSrc) + ){ + sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); + } + } + if( pLoop->wsFlags & WHERE_IPK ){ + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_Rowid, iCur, r1); + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, 1); + sqlite3ReleaseTempReg(pParse, r1); + }else{ + Index *pIdx = pLoop->u.btree.pIndex; + int n = pLoop->u.btree.nEq; + int r1 = sqlite3GetTempRange(pParse, n); + int jj; + for(jj=0; jj<n; jj++){ + assert( pIdx->pTable==pItem->pTab ); + sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iCur, jj, r1+jj); + } + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, n); + sqlite3ReleaseTempRange(pParse, r1, n); + } + sqlite3VdbeResolveLabel(v, addrCont); + sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrTop); + pLoop->wsFlags &= ~WHERE_BLOOMFILTER; + if( OptimizationDisabled(pParse->db, SQLITE_BloomPulldown) ) break; + while( ++iLevel < pWInfo->nLevel ){ + const SrcItem *pTabItem; + pLevel = &pWInfo->a[iLevel]; + pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ) ) continue; + pLoop = pLevel->pWLoop; + if( NEVER(pLoop==0) ) continue; + if( pLoop->prereq & notReady ) continue; + if( (pLoop->wsFlags & (WHERE_BLOOMFILTER|WHERE_COLUMN_IN)) + ==WHERE_BLOOMFILTER + ){ + /* This is a candidate for bloom-filter pull-down (early evaluation). + ** The test that WHERE_COLUMN_IN is omitted is important, as we are + ** not able to do early evaluation of bloom filters that make use of + ** the IN operator */ + break; + } + } + }while( iLevel < pWInfo->nLevel ); + sqlite3VdbeJumpHere(v, addrOnce); + pParse->pIdxEpr = saved_pIdxEpr; +} + + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Allocate and populate an sqlite3_index_info structure. It is the +** responsibility of the caller to eventually release the structure +** by passing the pointer returned by this function to freeIndexInfo(). +*/ +static sqlite3_index_info *allocateIndexInfo( + WhereInfo *pWInfo, /* The WHERE clause */ + WhereClause *pWC, /* The WHERE clause being analyzed */ + Bitmask mUnusable, /* Ignore terms with these prereqs */ + SrcItem *pSrc, /* The FROM clause term that is the vtab */ + u16 *pmNoOmit /* Mask of terms not to omit */ +){ + int i, j; + int nTerm; + Parse *pParse = pWInfo->pParse; + struct sqlite3_index_constraint *pIdxCons; + struct sqlite3_index_orderby *pIdxOrderBy; + struct sqlite3_index_constraint_usage *pUsage; + struct HiddenIndexInfo *pHidden; + WhereTerm *pTerm; + int nOrderBy; + sqlite3_index_info *pIdxInfo; + u16 mNoOmit = 0; + const Table *pTab; + int eDistinct = 0; + ExprList *pOrderBy = pWInfo->pOrderBy; + + assert( pSrc!=0 ); + pTab = pSrc->pTab; + assert( pTab!=0 ); + assert( IsVirtual(pTab) ); + + /* Find all WHERE clause constraints referring to this virtual table. + ** Mark each term with the TERM_OK flag. Set nTerm to the number of + ** terms found. + */ + for(i=nTerm=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ + pTerm->wtFlags &= ~TERM_OK; + if( pTerm->leftCursor != pSrc->iCursor ) continue; + if( pTerm->prereqRight & mUnusable ) continue; + assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); + testcase( pTerm->eOperator & WO_IN ); + testcase( pTerm->eOperator & WO_ISNULL ); + testcase( pTerm->eOperator & WO_IS ); + testcase( pTerm->eOperator & WO_ALL ); + if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; + if( pTerm->wtFlags & TERM_VNULL ) continue; + + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + assert( pTerm->u.x.leftColumn>=XN_ROWID ); + assert( pTerm->u.x.leftColumn<pTab->nCol ); + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 + && !constraintCompatibleWithOuterJoin(pTerm,pSrc) + ){ + continue; + } + nTerm++; + pTerm->wtFlags |= TERM_OK; + } + + /* If the ORDER BY clause contains only columns in the current + ** virtual table then allocate space for the aOrderBy part of + ** the sqlite3_index_info structure. + */ + nOrderBy = 0; + if( pOrderBy ){ + int n = pOrderBy->nExpr; + for(i=0; i<n; i++){ + Expr *pExpr = pOrderBy->a[i].pExpr; + Expr *pE2; + + /* Skip over constant terms in the ORDER BY clause */ + if( sqlite3ExprIsConstant(pExpr) ){ + continue; + } + + /* Virtual tables are unable to deal with NULLS FIRST */ + if( pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) break; + + /* First case - a direct column references without a COLLATE operator */ + if( pExpr->op==TK_COLUMN && pExpr->iTable==pSrc->iCursor ){ + assert( pExpr->iColumn>=XN_ROWID && pExpr->iColumn<pTab->nCol ); + continue; + } + + /* 2nd case - a column reference with a COLLATE operator. Only match + ** of the COLLATE operator matches the collation of the column. */ + if( pExpr->op==TK_COLLATE + && (pE2 = pExpr->pLeft)->op==TK_COLUMN + && pE2->iTable==pSrc->iCursor + ){ + const char *zColl; /* The collating sequence name */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + assert( pExpr->u.zToken!=0 ); + assert( pE2->iColumn>=XN_ROWID && pE2->iColumn<pTab->nCol ); + pExpr->iColumn = pE2->iColumn; + if( pE2->iColumn<0 ) continue; /* Collseq does not matter for rowid */ + zColl = sqlite3ColumnColl(&pTab->aCol[pE2->iColumn]); + if( zColl==0 ) zColl = sqlite3StrBINARY; + if( sqlite3_stricmp(pExpr->u.zToken, zColl)==0 ) continue; + } + + /* No matches cause a break out of the loop */ + break; + } + if( i==n ){ + nOrderBy = n; + if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) ){ + eDistinct = 2 + ((pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0); + }else if( pWInfo->wctrlFlags & WHERE_GROUPBY ){ + eDistinct = 1; + } + } + } + + /* Allocate the sqlite3_index_info structure + */ + pIdxInfo = sqlite3DbMallocZero(pParse->db, sizeof(*pIdxInfo) + + (sizeof(*pIdxCons) + sizeof(*pUsage))*nTerm + + sizeof(*pIdxOrderBy)*nOrderBy + sizeof(*pHidden) + + sizeof(sqlite3_value*)*nTerm ); + if( pIdxInfo==0 ){ + sqlite3ErrorMsg(pParse, "out of memory"); + return 0; + } + pHidden = (struct HiddenIndexInfo*)&pIdxInfo[1]; + pIdxCons = (struct sqlite3_index_constraint*)&pHidden->aRhs[nTerm]; + pIdxOrderBy = (struct sqlite3_index_orderby*)&pIdxCons[nTerm]; + pUsage = (struct sqlite3_index_constraint_usage*)&pIdxOrderBy[nOrderBy]; + pIdxInfo->aConstraint = pIdxCons; + pIdxInfo->aOrderBy = pIdxOrderBy; + pIdxInfo->aConstraintUsage = pUsage; + pHidden->pWC = pWC; + pHidden->pParse = pParse; + pHidden->eDistinct = eDistinct; + pHidden->mIn = 0; + for(i=j=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ + u16 op; + if( (pTerm->wtFlags & TERM_OK)==0 ) continue; + pIdxCons[j].iColumn = pTerm->u.x.leftColumn; + pIdxCons[j].iTermOffset = i; + op = pTerm->eOperator & WO_ALL; + if( op==WO_IN ){ + if( (pTerm->wtFlags & TERM_SLICE)==0 ){ + pHidden->mIn |= SMASKBIT32(j); + } + op = WO_EQ; + } + if( op==WO_AUX ){ + pIdxCons[j].op = pTerm->eMatchOp; + }else if( op & (WO_ISNULL|WO_IS) ){ + if( op==WO_ISNULL ){ + pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_ISNULL; + }else{ + pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_IS; + } + }else{ + pIdxCons[j].op = (u8)op; + /* The direct assignment in the previous line is possible only because + ** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The + ** following asserts verify this fact. */ + assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ ); + assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT ); + assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE ); + assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT ); + assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE ); + assert( pTerm->eOperator&(WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_AUX) ); + + if( op & (WO_LT|WO_LE|WO_GT|WO_GE) + && sqlite3ExprIsVector(pTerm->pExpr->pRight) + ){ + testcase( j!=i ); + if( j<16 ) mNoOmit |= (1 << j); + if( op==WO_LT ) pIdxCons[j].op = WO_LE; + if( op==WO_GT ) pIdxCons[j].op = WO_GE; + } + } + + j++; + } + assert( j==nTerm ); + pIdxInfo->nConstraint = j; + for(i=j=0; i<nOrderBy; i++){ + Expr *pExpr = pOrderBy->a[i].pExpr; + if( sqlite3ExprIsConstant(pExpr) ) continue; + assert( pExpr->op==TK_COLUMN + || (pExpr->op==TK_COLLATE && pExpr->pLeft->op==TK_COLUMN + && pExpr->iColumn==pExpr->pLeft->iColumn) ); + pIdxOrderBy[j].iColumn = pExpr->iColumn; + pIdxOrderBy[j].desc = pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC; + j++; + } + pIdxInfo->nOrderBy = j; + + *pmNoOmit = mNoOmit; + return pIdxInfo; +} + +/* +** Free an sqlite3_index_info structure allocated by allocateIndexInfo() +** and possibly modified by xBestIndex methods. +*/ +static void freeIndexInfo(sqlite3 *db, sqlite3_index_info *pIdxInfo){ + HiddenIndexInfo *pHidden; + int i; + assert( pIdxInfo!=0 ); + pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + assert( pHidden->pParse!=0 ); + assert( pHidden->pParse->db==db ); + for(i=0; i<pIdxInfo->nConstraint; i++){ + sqlite3ValueFree(pHidden->aRhs[i]); /* IMP: R-14553-25174 */ + pHidden->aRhs[i] = 0; + } + sqlite3DbFree(db, pIdxInfo); +} + +/* +** The table object reference passed as the second argument to this function +** must represent a virtual table. This function invokes the xBestIndex() +** method of the virtual table with the sqlite3_index_info object that +** comes in as the 3rd argument to this function. +** +** If an error occurs, pParse is populated with an error message and an +** appropriate error code is returned. A return of SQLITE_CONSTRAINT from +** xBestIndex is not considered an error. SQLITE_CONSTRAINT indicates that +** the current configuration of "unusable" flags in sqlite3_index_info can +** not result in a valid plan. +** +** Whether or not an error is returned, it is the responsibility of the +** caller to eventually free p->idxStr if p->needToFreeIdxStr indicates +** that this is required. +*/ +static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ + sqlite3_vtab *pVtab = sqlite3GetVTable(pParse->db, pTab)->pVtab; + int rc; + + whereTraceIndexInfoInputs(p); + pParse->db->nSchemaLock++; + rc = pVtab->pModule->xBestIndex(pVtab, p); + pParse->db->nSchemaLock--; + whereTraceIndexInfoOutputs(p); + + if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){ + if( rc==SQLITE_NOMEM ){ + sqlite3OomFault(pParse->db); + }else if( !pVtab->zErrMsg ){ + sqlite3ErrorMsg(pParse, "%s", sqlite3ErrStr(rc)); + }else{ + sqlite3ErrorMsg(pParse, "%s", pVtab->zErrMsg); + } + } + if( pTab->u.vtab.p->bAllSchemas ){ + sqlite3VtabUsesAllSchemas(pParse); + } + sqlite3_free(pVtab->zErrMsg); + pVtab->zErrMsg = 0; + return rc; +} +#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */ + +#ifdef SQLITE_ENABLE_STAT4 +/* +** Estimate the location of a particular key among all keys in an +** index. Store the results in aStat as follows: +** +** aStat[0] Est. number of rows less than pRec +** aStat[1] Est. number of rows equal to pRec +** +** Return the index of the sample that is the smallest sample that +** is greater than or equal to pRec. Note that this index is not an index +** into the aSample[] array - it is an index into a virtual set of samples +** based on the contents of aSample[] and the number of fields in record +** pRec. +*/ +static int whereKeyStats( + Parse *pParse, /* Database connection */ + Index *pIdx, /* Index to consider domain of */ + UnpackedRecord *pRec, /* Vector of values to consider */ + int roundUp, /* Round up if true. Round down if false */ + tRowcnt *aStat /* OUT: stats written here */ +){ + IndexSample *aSample = pIdx->aSample; + int iCol; /* Index of required stats in anEq[] etc. */ + int i; /* Index of first sample >= pRec */ + int iSample; /* Smallest sample larger than or equal to pRec */ + int iMin = 0; /* Smallest sample not yet tested */ + int iTest; /* Next sample to test */ + int res; /* Result of comparison operation */ + int nField; /* Number of fields in pRec */ + tRowcnt iLower = 0; /* anLt[] + anEq[] of largest sample pRec is > */ + +#ifndef SQLITE_DEBUG + UNUSED_PARAMETER( pParse ); +#endif + assert( pRec!=0 ); + assert( pIdx->nSample>0 ); + assert( pRec->nField>0 ); + + + /* Do a binary search to find the first sample greater than or equal + ** to pRec. If pRec contains a single field, the set of samples to search + ** is simply the aSample[] array. If the samples in aSample[] contain more + ** than one fields, all fields following the first are ignored. + ** + ** If pRec contains N fields, where N is more than one, then as well as the + ** samples in aSample[] (truncated to N fields), the search also has to + ** consider prefixes of those samples. For example, if the set of samples + ** in aSample is: + ** + ** aSample[0] = (a, 5) + ** aSample[1] = (a, 10) + ** aSample[2] = (b, 5) + ** aSample[3] = (c, 100) + ** aSample[4] = (c, 105) + ** + ** Then the search space should ideally be the samples above and the + ** unique prefixes [a], [b] and [c]. But since that is hard to organize, + ** the code actually searches this set: + ** + ** 0: (a) + ** 1: (a, 5) + ** 2: (a, 10) + ** 3: (a, 10) + ** 4: (b) + ** 5: (b, 5) + ** 6: (c) + ** 7: (c, 100) + ** 8: (c, 105) + ** 9: (c, 105) + ** + ** For each sample in the aSample[] array, N samples are present in the + ** effective sample array. In the above, samples 0 and 1 are based on + ** sample aSample[0]. Samples 2 and 3 on aSample[1] etc. + ** + ** Often, sample i of each block of N effective samples has (i+1) fields. + ** Except, each sample may be extended to ensure that it is greater than or + ** equal to the previous sample in the array. For example, in the above, + ** sample 2 is the first sample of a block of N samples, so at first it + ** appears that it should be 1 field in size. However, that would make it + ** smaller than sample 1, so the binary search would not work. As a result, + ** it is extended to two fields. The duplicates that this creates do not + ** cause any problems. + */ + if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){ + nField = pIdx->nKeyCol; + }else{ + nField = pIdx->nColumn; + } + nField = MIN(pRec->nField, nField); + iCol = 0; + iSample = pIdx->nSample * nField; + do{ + int iSamp; /* Index in aSample[] of test sample */ + int n; /* Number of fields in test sample */ + + iTest = (iMin+iSample)/2; + iSamp = iTest / nField; + if( iSamp>0 ){ + /* The proposed effective sample is a prefix of sample aSample[iSamp]. + ** Specifically, the shortest prefix of at least (1 + iTest%nField) + ** fields that is greater than the previous effective sample. */ + for(n=(iTest % nField) + 1; n<nField; n++){ + if( aSample[iSamp-1].anLt[n-1]!=aSample[iSamp].anLt[n-1] ) break; + } + }else{ + n = iTest + 1; + } + + pRec->nField = n; + res = sqlite3VdbeRecordCompare(aSample[iSamp].n, aSample[iSamp].p, pRec); + if( res<0 ){ + iLower = aSample[iSamp].anLt[n-1] + aSample[iSamp].anEq[n-1]; + iMin = iTest+1; + }else if( res==0 && n<nField ){ + iLower = aSample[iSamp].anLt[n-1]; + iMin = iTest+1; + res = -1; + }else{ + iSample = iTest; + iCol = n-1; + } + }while( res && iMin<iSample ); + i = iSample / nField; + +#ifdef SQLITE_DEBUG + /* The following assert statements check that the binary search code + ** above found the right answer. This block serves no purpose other + ** than to invoke the asserts. */ + if( pParse->db->mallocFailed==0 ){ + if( res==0 ){ + /* If (res==0) is true, then pRec must be equal to sample i. */ + assert( i<pIdx->nSample ); + assert( iCol==nField-1 ); + pRec->nField = nField; + assert( 0==sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec) + || pParse->db->mallocFailed + ); + }else{ + /* Unless i==pIdx->nSample, indicating that pRec is larger than + ** all samples in the aSample[] array, pRec must be smaller than the + ** (iCol+1) field prefix of sample i. */ + assert( i<=pIdx->nSample && i>=0 ); + pRec->nField = iCol+1; + assert( i==pIdx->nSample + || sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)>0 + || pParse->db->mallocFailed ); + + /* if i==0 and iCol==0, then record pRec is smaller than all samples + ** in the aSample[] array. Otherwise, if (iCol>0) then pRec must + ** be greater than or equal to the (iCol) field prefix of sample i. + ** If (i>0), then pRec must also be greater than sample (i-1). */ + if( iCol>0 ){ + pRec->nField = iCol; + assert( sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)<=0 + || pParse->db->mallocFailed || CORRUPT_DB ); + } + if( i>0 ){ + pRec->nField = nField; + assert( sqlite3VdbeRecordCompare(aSample[i-1].n, aSample[i-1].p, pRec)<0 + || pParse->db->mallocFailed || CORRUPT_DB ); + } + } + } +#endif /* ifdef SQLITE_DEBUG */ + + if( res==0 ){ + /* Record pRec is equal to sample i */ + assert( iCol==nField-1 ); + aStat[0] = aSample[i].anLt[iCol]; + aStat[1] = aSample[i].anEq[iCol]; + }else{ + /* At this point, the (iCol+1) field prefix of aSample[i] is the first + ** sample that is greater than pRec. Or, if i==pIdx->nSample then pRec + ** is larger than all samples in the array. */ + tRowcnt iUpper, iGap; + if( i>=pIdx->nSample ){ + iUpper = pIdx->nRowEst0; + }else{ + iUpper = aSample[i].anLt[iCol]; + } + + if( iLower>=iUpper ){ + iGap = 0; + }else{ + iGap = iUpper - iLower; + } + if( roundUp ){ + iGap = (iGap*2)/3; + }else{ + iGap = iGap/3; + } + aStat[0] = iLower + iGap; + aStat[1] = pIdx->aAvgEq[nField-1]; + } + + /* Restore the pRec->nField value before returning. */ + pRec->nField = nField; + return i; +} +#endif /* SQLITE_ENABLE_STAT4 */ + +/* +** If it is not NULL, pTerm is a term that provides an upper or lower +** bound on a range scan. Without considering pTerm, it is estimated +** that the scan will visit nNew rows. This function returns the number +** estimated to be visited after taking pTerm into account. +** +** If the user explicitly specified a likelihood() value for this term, +** then the return value is the likelihood multiplied by the number of +** input rows. Otherwise, this function assumes that an "IS NOT NULL" term +** has a likelihood of 0.50, and any other term a likelihood of 0.25. +*/ +static LogEst whereRangeAdjust(WhereTerm *pTerm, LogEst nNew){ + LogEst nRet = nNew; + if( pTerm ){ + if( pTerm->truthProb<=0 ){ + nRet += pTerm->truthProb; + }else if( (pTerm->wtFlags & TERM_VNULL)==0 ){ + nRet -= 20; assert( 20==sqlite3LogEst(4) ); + } + } + return nRet; +} + + +#ifdef SQLITE_ENABLE_STAT4 +/* +** Return the affinity for a single column of an index. +*/ +SQLITE_PRIVATE char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){ + assert( iCol>=0 && iCol<pIdx->nColumn ); + if( !pIdx->zColAff ){ + if( sqlite3IndexAffinityStr(db, pIdx)==0 ) return SQLITE_AFF_BLOB; + } + assert( pIdx->zColAff[iCol]!=0 ); + return pIdx->zColAff[iCol]; +} +#endif + + +#ifdef SQLITE_ENABLE_STAT4 +/* +** This function is called to estimate the number of rows visited by a +** range-scan on a skip-scan index. For example: +** +** CREATE INDEX i1 ON t1(a, b, c); +** SELECT * FROM t1 WHERE a=? AND c BETWEEN ? AND ?; +** +** Value pLoop->nOut is currently set to the estimated number of rows +** visited for scanning (a=? AND b=?). This function reduces that estimate +** by some factor to account for the (c BETWEEN ? AND ?) expression based +** on the stat4 data for the index. this scan will be performed multiple +** times (once for each (a,b) combination that matches a=?) is dealt with +** by the caller. +** +** It does this by scanning through all stat4 samples, comparing values +** extracted from pLower and pUpper with the corresponding column in each +** sample. If L and U are the number of samples found to be less than or +** equal to the values extracted from pLower and pUpper respectively, and +** N is the total number of samples, the pLoop->nOut value is adjusted +** as follows: +** +** nOut = nOut * ( min(U - L, 1) / N ) +** +** If pLower is NULL, or a value cannot be extracted from the term, L is +** set to zero. If pUpper is NULL, or a value cannot be extracted from it, +** U is set to N. +** +** Normally, this function sets *pbDone to 1 before returning. However, +** if no value can be extracted from either pLower or pUpper (and so the +** estimate of the number of rows delivered remains unchanged), *pbDone +** is left as is. +** +** If an error occurs, an SQLite error code is returned. Otherwise, +** SQLITE_OK. +*/ +static int whereRangeSkipScanEst( + Parse *pParse, /* Parsing & code generating context */ + WhereTerm *pLower, /* Lower bound on the range. ex: "x>123" Might be NULL */ + WhereTerm *pUpper, /* Upper bound on the range. ex: "x<455" Might be NULL */ + WhereLoop *pLoop, /* Update the .nOut value of this loop */ + int *pbDone /* Set to true if at least one expr. value extracted */ +){ + Index *p = pLoop->u.btree.pIndex; + int nEq = pLoop->u.btree.nEq; + sqlite3 *db = pParse->db; + int nLower = -1; + int nUpper = p->nSample+1; + int rc = SQLITE_OK; + u8 aff = sqlite3IndexColumnAffinity(db, p, nEq); + CollSeq *pColl; + + sqlite3_value *p1 = 0; /* Value extracted from pLower */ + sqlite3_value *p2 = 0; /* Value extracted from pUpper */ + sqlite3_value *pVal = 0; /* Value extracted from record */ + + pColl = sqlite3LocateCollSeq(pParse, p->azColl[nEq]); + if( pLower ){ + rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight, aff, &p1); + nLower = 0; + } + if( pUpper && rc==SQLITE_OK ){ + rc = sqlite3Stat4ValueFromExpr(pParse, pUpper->pExpr->pRight, aff, &p2); + nUpper = p2 ? 0 : p->nSample; + } + + if( p1 || p2 ){ + int i; + int nDiff; + for(i=0; rc==SQLITE_OK && i<p->nSample; i++){ + rc = sqlite3Stat4Column(db, p->aSample[i].p, p->aSample[i].n, nEq, &pVal); + if( rc==SQLITE_OK && p1 ){ + int res = sqlite3MemCompare(p1, pVal, pColl); + if( res>=0 ) nLower++; + } + if( rc==SQLITE_OK && p2 ){ + int res = sqlite3MemCompare(p2, pVal, pColl); + if( res>=0 ) nUpper++; + } + } + nDiff = (nUpper - nLower); + if( nDiff<=0 ) nDiff = 1; + + /* If there is both an upper and lower bound specified, and the + ** comparisons indicate that they are close together, use the fallback + ** method (assume that the scan visits 1/64 of the rows) for estimating + ** the number of rows visited. Otherwise, estimate the number of rows + ** using the method described in the header comment for this function. */ + if( nDiff!=1 || pUpper==0 || pLower==0 ){ + int nAdjust = (sqlite3LogEst(p->nSample) - sqlite3LogEst(nDiff)); + pLoop->nOut -= nAdjust; + *pbDone = 1; + WHERETRACE(0x20, ("range skip-scan regions: %u..%u adjust=%d est=%d\n", + nLower, nUpper, nAdjust*-1, pLoop->nOut)); + } + + }else{ + assert( *pbDone==0 ); + } + + sqlite3ValueFree(p1); + sqlite3ValueFree(p2); + sqlite3ValueFree(pVal); + + return rc; +} +#endif /* SQLITE_ENABLE_STAT4 */ + +/* +** This function is used to estimate the number of rows that will be visited +** by scanning an index for a range of values. The range may have an upper +** bound, a lower bound, or both. The WHERE clause terms that set the upper +** and lower bounds are represented by pLower and pUpper respectively. For +** example, assuming that index p is on t1(a): +** +** ... FROM t1 WHERE a > ? AND a < ? ... +** |_____| |_____| +** | | +** pLower pUpper +** +** If either of the upper or lower bound is not present, then NULL is passed in +** place of the corresponding WhereTerm. +** +** The value in (pBuilder->pNew->u.btree.nEq) is the number of the index +** column subject to the range constraint. Or, equivalently, the number of +** equality constraints optimized by the proposed index scan. For example, +** assuming index p is on t1(a, b), and the SQL query is: +** +** ... FROM t1 WHERE a = ? AND b > ? AND b < ? ... +** +** then nEq is set to 1 (as the range restricted column, b, is the second +** left-most column of the index). Or, if the query is: +** +** ... FROM t1 WHERE a > ? AND a < ? ... +** +** then nEq is set to 0. +** +** When this function is called, *pnOut is set to the sqlite3LogEst() of the +** number of rows that the index scan is expected to visit without +** considering the range constraints. If nEq is 0, then *pnOut is the number of +** rows in the index. Assuming no error occurs, *pnOut is adjusted (reduced) +** to account for the range constraints pLower and pUpper. +** +** In the absence of sqlite_stat4 ANALYZE data, or if such data cannot be +** used, a single range inequality reduces the search space by a factor of 4. +** and a pair of constraints (x>? AND x<?) reduces the expected number of +** rows visited by a factor of 64. +*/ +static int whereRangeScanEst( + Parse *pParse, /* Parsing & code generating context */ + WhereLoopBuilder *pBuilder, + WhereTerm *pLower, /* Lower bound on the range. ex: "x>123" Might be NULL */ + WhereTerm *pUpper, /* Upper bound on the range. ex: "x<455" Might be NULL */ + WhereLoop *pLoop /* Modify the .nOut and maybe .rRun fields */ +){ + int rc = SQLITE_OK; + int nOut = pLoop->nOut; + LogEst nNew; + +#ifdef SQLITE_ENABLE_STAT4 + Index *p = pLoop->u.btree.pIndex; + int nEq = pLoop->u.btree.nEq; + + if( p->nSample>0 && ALWAYS(nEq<p->nSampleCol) + && OptimizationEnabled(pParse->db, SQLITE_Stat4) + ){ + if( nEq==pBuilder->nRecValid ){ + UnpackedRecord *pRec = pBuilder->pRec; + tRowcnt a[2]; + int nBtm = pLoop->u.btree.nBtm; + int nTop = pLoop->u.btree.nTop; + + /* Variable iLower will be set to the estimate of the number of rows in + ** the index that are less than the lower bound of the range query. The + ** lower bound being the concatenation of $P and $L, where $P is the + ** key-prefix formed by the nEq values matched against the nEq left-most + ** columns of the index, and $L is the value in pLower. + ** + ** Or, if pLower is NULL or $L cannot be extracted from it (because it + ** is not a simple variable or literal value), the lower bound of the + ** range is $P. Due to a quirk in the way whereKeyStats() works, even + ** if $L is available, whereKeyStats() is called for both ($P) and + ** ($P:$L) and the larger of the two returned values is used. + ** + ** Similarly, iUpper is to be set to the estimate of the number of rows + ** less than the upper bound of the range query. Where the upper bound + ** is either ($P) or ($P:$U). Again, even if $U is available, both values + ** of iUpper are requested of whereKeyStats() and the smaller used. + ** + ** The number of rows between the two bounds is then just iUpper-iLower. + */ + tRowcnt iLower; /* Rows less than the lower bound */ + tRowcnt iUpper; /* Rows less than the upper bound */ + int iLwrIdx = -2; /* aSample[] for the lower bound */ + int iUprIdx = -1; /* aSample[] for the upper bound */ + + if( pRec ){ + testcase( pRec->nField!=pBuilder->nRecValid ); + pRec->nField = pBuilder->nRecValid; + } + /* Determine iLower and iUpper using ($P) only. */ + if( nEq==0 ){ + iLower = 0; + iUpper = p->nRowEst0; + }else{ + /* Note: this call could be optimized away - since the same values must + ** have been requested when testing key $P in whereEqualScanEst(). */ + whereKeyStats(pParse, p, pRec, 0, a); + iLower = a[0]; + iUpper = a[0] + a[1]; + } + + assert( pLower==0 || (pLower->eOperator & (WO_GT|WO_GE))!=0 ); + assert( pUpper==0 || (pUpper->eOperator & (WO_LT|WO_LE))!=0 ); + assert( p->aSortOrder!=0 ); + if( p->aSortOrder[nEq] ){ + /* The roles of pLower and pUpper are swapped for a DESC index */ + SWAP(WhereTerm*, pLower, pUpper); + SWAP(int, nBtm, nTop); + } + + /* If possible, improve on the iLower estimate using ($P:$L). */ + if( pLower ){ + int n; /* Values extracted from pExpr */ + Expr *pExpr = pLower->pExpr->pRight; + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nBtm, nEq, &n); + if( rc==SQLITE_OK && n ){ + tRowcnt iNew; + u16 mask = WO_GT|WO_LE; + if( sqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT); + iLwrIdx = whereKeyStats(pParse, p, pRec, 0, a); + iNew = a[0] + ((pLower->eOperator & mask) ? a[1] : 0); + if( iNew>iLower ) iLower = iNew; + nOut--; + pLower = 0; + } + } + + /* If possible, improve on the iUpper estimate using ($P:$U). */ + if( pUpper ){ + int n; /* Values extracted from pExpr */ + Expr *pExpr = pUpper->pExpr->pRight; + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nTop, nEq, &n); + if( rc==SQLITE_OK && n ){ + tRowcnt iNew; + u16 mask = WO_GT|WO_LE; + if( sqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT); + iUprIdx = whereKeyStats(pParse, p, pRec, 1, a); + iNew = a[0] + ((pUpper->eOperator & mask) ? a[1] : 0); + if( iNew<iUpper ) iUpper = iNew; + nOut--; + pUpper = 0; + } + } + + pBuilder->pRec = pRec; + if( rc==SQLITE_OK ){ + if( iUpper>iLower ){ + nNew = sqlite3LogEst(iUpper - iLower); + /* TUNING: If both iUpper and iLower are derived from the same + ** sample, then assume they are 4x more selective. This brings + ** the estimated selectivity more in line with what it would be + ** if estimated without the use of STAT4 tables. */ + if( iLwrIdx==iUprIdx ) nNew -= 20; assert( 20==sqlite3LogEst(4) ); + }else{ + nNew = 10; assert( 10==sqlite3LogEst(2) ); + } + if( nNew<nOut ){ + nOut = nNew; + } + WHERETRACE(0x20, ("STAT4 range scan: %u..%u est=%d\n", + (u32)iLower, (u32)iUpper, nOut)); + } + }else{ + int bDone = 0; + rc = whereRangeSkipScanEst(pParse, pLower, pUpper, pLoop, &bDone); + if( bDone ) return rc; + } + } +#else + UNUSED_PARAMETER(pParse); + UNUSED_PARAMETER(pBuilder); + assert( pLower || pUpper ); +#endif + assert( pUpper==0 || (pUpper->wtFlags & TERM_VNULL)==0 || pParse->nErr>0 ); + nNew = whereRangeAdjust(pLower, nOut); + nNew = whereRangeAdjust(pUpper, nNew); + + /* TUNING: If there is both an upper and lower limit and neither limit + ** has an application-defined likelihood(), assume the range is + ** reduced by an additional 75%. This means that, by default, an open-ended + ** range query (e.g. col > ?) is assumed to match 1/4 of the rows in the + ** index. While a closed range (e.g. col BETWEEN ? AND ?) is estimated to + ** match 1/64 of the index. */ + if( pLower && pLower->truthProb>0 && pUpper && pUpper->truthProb>0 ){ + nNew -= 20; + } + + nOut -= (pLower!=0) + (pUpper!=0); + if( nNew<10 ) nNew = 10; + if( nNew<nOut ) nOut = nNew; +#if defined(WHERETRACE_ENABLED) + if( pLoop->nOut>nOut ){ + WHERETRACE(0x20,("Range scan lowers nOut from %d to %d\n", + pLoop->nOut, nOut)); + } +#endif + pLoop->nOut = (LogEst)nOut; + return rc; +} + +#ifdef SQLITE_ENABLE_STAT4 +/* +** Estimate the number of rows that will be returned based on +** an equality constraint x=VALUE and where that VALUE occurs in +** the histogram data. This only works when x is the left-most +** column of an index and sqlite_stat4 histogram data is available +** for that index. When pExpr==NULL that means the constraint is +** "x IS NULL" instead of "x=VALUE". +** +** Write the estimated row count into *pnRow and return SQLITE_OK. +** If unable to make an estimate, leave *pnRow unchanged and return +** non-zero. +** +** This routine can fail if it is unable to load a collating sequence +** required for string comparison, or if unable to allocate memory +** for a UTF conversion required for comparison. The error is stored +** in the pParse structure. +*/ +static int whereEqualScanEst( + Parse *pParse, /* Parsing & code generating context */ + WhereLoopBuilder *pBuilder, + Expr *pExpr, /* Expression for VALUE in the x=VALUE constraint */ + tRowcnt *pnRow /* Write the revised row estimate here */ +){ + Index *p = pBuilder->pNew->u.btree.pIndex; + int nEq = pBuilder->pNew->u.btree.nEq; + UnpackedRecord *pRec = pBuilder->pRec; + int rc; /* Subfunction return code */ + tRowcnt a[2]; /* Statistics */ + int bOk; + + assert( nEq>=1 ); + assert( nEq<=p->nColumn ); + assert( p->aSample!=0 ); + assert( p->nSample>0 ); + assert( pBuilder->nRecValid<nEq ); + + /* If values are not available for all fields of the index to the left + ** of this one, no estimate can be made. Return SQLITE_NOTFOUND. */ + if( pBuilder->nRecValid<(nEq-1) ){ + return SQLITE_NOTFOUND; + } + + /* This is an optimization only. The call to sqlite3Stat4ProbeSetValue() + ** below would return the same value. */ + if( nEq>=p->nColumn ){ + *pnRow = 1; + return SQLITE_OK; + } + + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, 1, nEq-1, &bOk); + pBuilder->pRec = pRec; + if( rc!=SQLITE_OK ) return rc; + if( bOk==0 ) return SQLITE_NOTFOUND; + pBuilder->nRecValid = nEq; + + whereKeyStats(pParse, p, pRec, 0, a); + WHERETRACE(0x20,("equality scan regions %s(%d): %d\n", + p->zName, nEq-1, (int)a[1])); + *pnRow = a[1]; + + return rc; +} +#endif /* SQLITE_ENABLE_STAT4 */ + +#ifdef SQLITE_ENABLE_STAT4 +/* +** Estimate the number of rows that will be returned based on +** an IN constraint where the right-hand side of the IN operator +** is a list of values. Example: +** +** WHERE x IN (1,2,3,4) +** +** Write the estimated row count into *pnRow and return SQLITE_OK. +** If unable to make an estimate, leave *pnRow unchanged and return +** non-zero. +** +** This routine can fail if it is unable to load a collating sequence +** required for string comparison, or if unable to allocate memory +** for a UTF conversion required for comparison. The error is stored +** in the pParse structure. +*/ +static int whereInScanEst( + Parse *pParse, /* Parsing & code generating context */ + WhereLoopBuilder *pBuilder, + ExprList *pList, /* The value list on the RHS of "x IN (v1,v2,v3,...)" */ + tRowcnt *pnRow /* Write the revised row estimate here */ +){ + Index *p = pBuilder->pNew->u.btree.pIndex; + i64 nRow0 = sqlite3LogEstToInt(p->aiRowLogEst[0]); + int nRecValid = pBuilder->nRecValid; + int rc = SQLITE_OK; /* Subfunction return code */ + tRowcnt nEst; /* Number of rows for a single term */ + tRowcnt nRowEst = 0; /* New estimate of the number of rows */ + int i; /* Loop counter */ + + assert( p->aSample!=0 ); + for(i=0; rc==SQLITE_OK && i<pList->nExpr; i++){ + nEst = nRow0; + rc = whereEqualScanEst(pParse, pBuilder, pList->a[i].pExpr, &nEst); + nRowEst += nEst; + pBuilder->nRecValid = nRecValid; + } + + if( rc==SQLITE_OK ){ + if( nRowEst > (tRowcnt)nRow0 ) nRowEst = nRow0; + *pnRow = nRowEst; + WHERETRACE(0x20,("IN row estimate: est=%d\n", nRowEst)); + } + assert( pBuilder->nRecValid==nRecValid ); + return rc; +} +#endif /* SQLITE_ENABLE_STAT4 */ + + +#ifdef WHERETRACE_ENABLED +/* +** Print the content of a WhereTerm object +*/ +SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ + if( pTerm==0 ){ + sqlite3DebugPrintf("TERM-%-3d NULL\n", iTerm); + }else{ + char zType[8]; + char zLeft[50]; + memcpy(zType, "....", 5); + if( pTerm->wtFlags & TERM_VIRTUAL ) zType[0] = 'V'; + if( pTerm->eOperator & WO_EQUIV ) zType[1] = 'E'; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) zType[2] = 'L'; + if( pTerm->wtFlags & TERM_CODED ) zType[3] = 'C'; + if( pTerm->eOperator & WO_SINGLE ){ + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + sqlite3_snprintf(sizeof(zLeft),zLeft,"left={%d:%d}", + pTerm->leftCursor, pTerm->u.x.leftColumn); + }else if( (pTerm->eOperator & WO_OR)!=0 && pTerm->u.pOrInfo!=0 ){ + sqlite3_snprintf(sizeof(zLeft),zLeft,"indexable=0x%llx", + pTerm->u.pOrInfo->indexable); + }else{ + sqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor); + } + sqlite3DebugPrintf( + "TERM-%-3d %p %s %-12s op=%03x wtFlags=%04x", + iTerm, pTerm, zType, zLeft, pTerm->eOperator, pTerm->wtFlags); + /* The 0x10000 .wheretrace flag causes extra information to be + ** shown about each Term */ + if( sqlite3WhereTrace & 0x10000 ){ + sqlite3DebugPrintf(" prob=%-3d prereq=%llx,%llx", + pTerm->truthProb, (u64)pTerm->prereqAll, (u64)pTerm->prereqRight); + } + if( (pTerm->eOperator & (WO_OR|WO_AND))==0 && pTerm->u.x.iField ){ + sqlite3DebugPrintf(" iField=%d", pTerm->u.x.iField); + } + if( pTerm->iParent>=0 ){ + sqlite3DebugPrintf(" iParent=%d", pTerm->iParent); + } + sqlite3DebugPrintf("\n"); + sqlite3TreeViewExpr(0, pTerm->pExpr, 0); + } +} +#endif + +#ifdef WHERETRACE_ENABLED +/* +** Show the complete content of a WhereClause +*/ +SQLITE_PRIVATE void sqlite3WhereClausePrint(WhereClause *pWC){ + int i; + for(i=0; i<pWC->nTerm; i++){ + sqlite3WhereTermPrint(&pWC->a[i], i); + } +} +#endif + +#ifdef WHERETRACE_ENABLED +/* +** Print a WhereLoop object for debugging purposes +*/ +SQLITE_PRIVATE void sqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC){ + WhereInfo *pWInfo = pWC->pWInfo; + int nb = 1+(pWInfo->pTabList->nSrc+3)/4; + SrcItem *pItem = pWInfo->pTabList->a + p->iTab; + Table *pTab = pItem->pTab; + Bitmask mAll = (((Bitmask)1)<<(nb*4)) - 1; + sqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId, + p->iTab, nb, p->maskSelf, nb, p->prereq & mAll); + sqlite3DebugPrintf(" %12s", + pItem->zAlias ? pItem->zAlias : pTab->zName); + if( (p->wsFlags & WHERE_VIRTUALTABLE)==0 ){ + const char *zName; + if( p->u.btree.pIndex && (zName = p->u.btree.pIndex->zName)!=0 ){ + if( strncmp(zName, "sqlite_autoindex_", 17)==0 ){ + int i = sqlite3Strlen30(zName) - 1; + while( zName[i]!='_' ) i--; + zName += i; + } + sqlite3DebugPrintf(".%-16s %2d", zName, p->u.btree.nEq); + }else{ + sqlite3DebugPrintf("%20s",""); + } + }else{ + char *z; + if( p->u.vtab.idxStr ){ + z = sqlite3_mprintf("(%d,\"%s\",%#x)", + p->u.vtab.idxNum, p->u.vtab.idxStr, p->u.vtab.omitMask); + }else{ + z = sqlite3_mprintf("(%d,%x)", p->u.vtab.idxNum, p->u.vtab.omitMask); + } + sqlite3DebugPrintf(" %-19s", z); + sqlite3_free(z); + } + if( p->wsFlags & WHERE_SKIPSCAN ){ + sqlite3DebugPrintf(" f %06x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); + }else{ + sqlite3DebugPrintf(" f %06x N %d", p->wsFlags, p->nLTerm); + } + sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut); + if( p->nLTerm && (sqlite3WhereTrace & 0x4000)!=0 ){ + int i; + for(i=0; i<p->nLTerm; i++){ + sqlite3WhereTermPrint(p->aLTerm[i], i); + } + } +} +#endif + +/* +** Convert bulk memory into a valid WhereLoop that can be passed +** to whereLoopClear harmlessly. +*/ +static void whereLoopInit(WhereLoop *p){ + p->aLTerm = p->aLTermSpace; + p->nLTerm = 0; + p->nLSlot = ArraySize(p->aLTermSpace); + p->wsFlags = 0; +} + +/* +** Clear the WhereLoop.u union. Leave WhereLoop.pLTerm intact. +*/ +static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){ + if( p->wsFlags & (WHERE_VIRTUALTABLE|WHERE_AUTO_INDEX) ){ + if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 && p->u.vtab.needFree ){ + sqlite3_free(p->u.vtab.idxStr); + p->u.vtab.needFree = 0; + p->u.vtab.idxStr = 0; + }else if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 && p->u.btree.pIndex!=0 ){ + sqlite3DbFree(db, p->u.btree.pIndex->zColAff); + sqlite3DbFreeNN(db, p->u.btree.pIndex); + p->u.btree.pIndex = 0; + } + } +} + +/* +** Deallocate internal memory used by a WhereLoop object. Leave the +** object in an initialized state, as if it had been newly allocated. +*/ +static void whereLoopClear(sqlite3 *db, WhereLoop *p){ + if( p->aLTerm!=p->aLTermSpace ){ + sqlite3DbFreeNN(db, p->aLTerm); + p->aLTerm = p->aLTermSpace; + p->nLSlot = ArraySize(p->aLTermSpace); + } + whereLoopClearUnion(db, p); + p->nLTerm = 0; + p->wsFlags = 0; +} + +/* +** Increase the memory allocation for pLoop->aLTerm[] to be at least n. +*/ +static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){ + WhereTerm **paNew; + if( p->nLSlot>=n ) return SQLITE_OK; + n = (n+7)&~7; + paNew = sqlite3DbMallocRawNN(db, sizeof(p->aLTerm[0])*n); + if( paNew==0 ) return SQLITE_NOMEM_BKPT; + memcpy(paNew, p->aLTerm, sizeof(p->aLTerm[0])*p->nLSlot); + if( p->aLTerm!=p->aLTermSpace ) sqlite3DbFreeNN(db, p->aLTerm); + p->aLTerm = paNew; + p->nLSlot = n; + return SQLITE_OK; +} + +/* +** Transfer content from the second pLoop into the first. +*/ +static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){ + whereLoopClearUnion(db, pTo); + if( pFrom->nLTerm > pTo->nLSlot + && whereLoopResize(db, pTo, pFrom->nLTerm) + ){ + memset(pTo, 0, WHERE_LOOP_XFER_SZ); + return SQLITE_NOMEM_BKPT; + } + memcpy(pTo, pFrom, WHERE_LOOP_XFER_SZ); + memcpy(pTo->aLTerm, pFrom->aLTerm, pTo->nLTerm*sizeof(pTo->aLTerm[0])); + if( pFrom->wsFlags & WHERE_VIRTUALTABLE ){ + pFrom->u.vtab.needFree = 0; + }else if( (pFrom->wsFlags & WHERE_AUTO_INDEX)!=0 ){ + pFrom->u.btree.pIndex = 0; + } + return SQLITE_OK; +} + +/* +** Delete a WhereLoop object +*/ +static void whereLoopDelete(sqlite3 *db, WhereLoop *p){ + assert( db!=0 ); + whereLoopClear(db, p); + sqlite3DbNNFreeNN(db, p); +} + +/* +** Free a WhereInfo structure +*/ +static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ + assert( pWInfo!=0 ); + assert( db!=0 ); + sqlite3WhereClauseClear(&pWInfo->sWC); + while( pWInfo->pLoops ){ + WhereLoop *p = pWInfo->pLoops; + pWInfo->pLoops = p->pNextLoop; + whereLoopDelete(db, p); + } + while( pWInfo->pMemToFree ){ + WhereMemBlock *pNext = pWInfo->pMemToFree->pNext; + sqlite3DbNNFreeNN(db, pWInfo->pMemToFree); + pWInfo->pMemToFree = pNext; + } + sqlite3DbNNFreeNN(db, pWInfo); +} + +/* +** Return TRUE if all of the following are true: +** +** (1) X has the same or lower cost, or returns the same or fewer rows, +** than Y. +** (2) X uses fewer WHERE clause terms than Y +** (3) Every WHERE clause term used by X is also used by Y +** (4) X skips at least as many columns as Y +** (5) If X is a covering index, than Y is too +** +** Conditions (2) and (3) mean that X is a "proper subset" of Y. +** If X is a proper subset of Y then Y is a better choice and ought +** to have a lower cost. This routine returns TRUE when that cost +** relationship is inverted and needs to be adjusted. Constraint (4) +** was added because if X uses skip-scan less than Y it still might +** deserve a lower cost even if it is a proper subset of Y. Constraint (5) +** was added because a covering index probably deserves to have a lower cost +** than a non-covering index even if it is a proper subset. +*/ +static int whereLoopCheaperProperSubset( + const WhereLoop *pX, /* First WhereLoop to compare */ + const WhereLoop *pY /* Compare against this WhereLoop */ +){ + int i, j; + if( pX->nLTerm-pX->nSkip >= pY->nLTerm-pY->nSkip ){ + return 0; /* X is not a subset of Y */ + } + if( pX->rRun>pY->rRun && pX->nOut>pY->nOut ) return 0; + if( pY->nSkip > pX->nSkip ) return 0; + for(i=pX->nLTerm-1; i>=0; i--){ + if( pX->aLTerm[i]==0 ) continue; + for(j=pY->nLTerm-1; j>=0; j--){ + if( pY->aLTerm[j]==pX->aLTerm[i] ) break; + } + if( j<0 ) return 0; /* X not a subset of Y since term X[i] not used by Y */ + } + if( (pX->wsFlags&WHERE_IDX_ONLY)!=0 + && (pY->wsFlags&WHERE_IDX_ONLY)==0 ){ + return 0; /* Constraint (5) */ + } + return 1; /* All conditions meet */ +} + +/* +** Try to adjust the cost and number of output rows of WhereLoop pTemplate +** upwards or downwards so that: +** +** (1) pTemplate costs less than any other WhereLoops that are a proper +** subset of pTemplate +** +** (2) pTemplate costs more than any other WhereLoops for which pTemplate +** is a proper subset. +** +** To say "WhereLoop X is a proper subset of Y" means that X uses fewer +** WHERE clause terms than Y and that every WHERE clause term used by X is +** also used by Y. +*/ +static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){ + if( (pTemplate->wsFlags & WHERE_INDEXED)==0 ) return; + for(; p; p=p->pNextLoop){ + if( p->iTab!=pTemplate->iTab ) continue; + if( (p->wsFlags & WHERE_INDEXED)==0 ) continue; + if( whereLoopCheaperProperSubset(p, pTemplate) ){ + /* Adjust pTemplate cost downward so that it is cheaper than its + ** subset p. */ + WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", + pTemplate->rRun, pTemplate->nOut, + MIN(p->rRun, pTemplate->rRun), + MIN(p->nOut - 1, pTemplate->nOut))); + pTemplate->rRun = MIN(p->rRun, pTemplate->rRun); + pTemplate->nOut = MIN(p->nOut - 1, pTemplate->nOut); + }else if( whereLoopCheaperProperSubset(pTemplate, p) ){ + /* Adjust pTemplate cost upward so that it is costlier than p since + ** pTemplate is a proper subset of p */ + WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", + pTemplate->rRun, pTemplate->nOut, + MAX(p->rRun, pTemplate->rRun), + MAX(p->nOut + 1, pTemplate->nOut))); + pTemplate->rRun = MAX(p->rRun, pTemplate->rRun); + pTemplate->nOut = MAX(p->nOut + 1, pTemplate->nOut); + } + } +} + +/* +** Search the list of WhereLoops in *ppPrev looking for one that can be +** replaced by pTemplate. +** +** Return NULL if pTemplate does not belong on the WhereLoop list. +** In other words if pTemplate ought to be dropped from further consideration. +** +** If pX is a WhereLoop that pTemplate can replace, then return the +** link that points to pX. +** +** If pTemplate cannot replace any existing element of the list but needs +** to be added to the list as a new entry, then return a pointer to the +** tail of the list. +*/ +static WhereLoop **whereLoopFindLesser( + WhereLoop **ppPrev, + const WhereLoop *pTemplate +){ + WhereLoop *p; + for(p=(*ppPrev); p; ppPrev=&p->pNextLoop, p=*ppPrev){ + if( p->iTab!=pTemplate->iTab || p->iSortIdx!=pTemplate->iSortIdx ){ + /* If either the iTab or iSortIdx values for two WhereLoop are different + ** then those WhereLoops need to be considered separately. Neither is + ** a candidate to replace the other. */ + continue; + } + /* In the current implementation, the rSetup value is either zero + ** or the cost of building an automatic index (NlogN) and the NlogN + ** is the same for compatible WhereLoops. */ + assert( p->rSetup==0 || pTemplate->rSetup==0 + || p->rSetup==pTemplate->rSetup ); + + /* whereLoopAddBtree() always generates and inserts the automatic index + ** case first. Hence compatible candidate WhereLoops never have a larger + ** rSetup. Call this SETUP-INVARIANT */ + assert( p->rSetup>=pTemplate->rSetup ); + + /* Any loop using an application-defined index (or PRIMARY KEY or + ** UNIQUE constraint) with one or more == constraints is better + ** than an automatic index. Unless it is a skip-scan. */ + if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 + && (pTemplate->nSkip)==0 + && (pTemplate->wsFlags & WHERE_INDEXED)!=0 + && (pTemplate->wsFlags & WHERE_COLUMN_EQ)!=0 + && (p->prereq & pTemplate->prereq)==pTemplate->prereq + ){ + break; + } + + /* If existing WhereLoop p is better than pTemplate, pTemplate can be + ** discarded. WhereLoop p is better if: + ** (1) p has no more dependencies than pTemplate, and + ** (2) p has an equal or lower cost than pTemplate + */ + if( (p->prereq & pTemplate->prereq)==p->prereq /* (1) */ + && p->rSetup<=pTemplate->rSetup /* (2a) */ + && p->rRun<=pTemplate->rRun /* (2b) */ + && p->nOut<=pTemplate->nOut /* (2c) */ + ){ + return 0; /* Discard pTemplate */ + } + + /* If pTemplate is always better than p, then cause p to be overwritten + ** with pTemplate. pTemplate is better than p if: + ** (1) pTemplate has no more dependencies than p, and + ** (2) pTemplate has an equal or lower cost than p. + */ + if( (p->prereq & pTemplate->prereq)==pTemplate->prereq /* (1) */ + && p->rRun>=pTemplate->rRun /* (2a) */ + && p->nOut>=pTemplate->nOut /* (2b) */ + ){ + assert( p->rSetup>=pTemplate->rSetup ); /* SETUP-INVARIANT above */ + break; /* Cause p to be overwritten by pTemplate */ + } + } + return ppPrev; +} + +/* +** Insert or replace a WhereLoop entry using the template supplied. +** +** An existing WhereLoop entry might be overwritten if the new template +** is better and has fewer dependencies. Or the template will be ignored +** and no insert will occur if an existing WhereLoop is faster and has +** fewer dependencies than the template. Otherwise a new WhereLoop is +** added based on the template. +** +** If pBuilder->pOrSet is not NULL then we care about only the +** prerequisites and rRun and nOut costs of the N best loops. That +** information is gathered in the pBuilder->pOrSet object. This special +** processing mode is used only for OR clause processing. +** +** When accumulating multiple loops (when pBuilder->pOrSet is NULL) we +** still might overwrite similar loops with the new template if the +** new template is better. Loops may be overwritten if the following +** conditions are met: +** +** (1) They have the same iTab. +** (2) They have the same iSortIdx. +** (3) The template has same or fewer dependencies than the current loop +** (4) The template has the same or lower cost than the current loop +*/ +static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ + WhereLoop **ppPrev, *p; + WhereInfo *pWInfo = pBuilder->pWInfo; + sqlite3 *db = pWInfo->pParse->db; + int rc; + + /* Stop the search once we hit the query planner search limit */ + if( pBuilder->iPlanLimit==0 ){ + WHERETRACE(0xffffffff,("=== query planner search limit reached ===\n")); + if( pBuilder->pOrSet ) pBuilder->pOrSet->n = 0; + return SQLITE_DONE; + } + pBuilder->iPlanLimit--; + + whereLoopAdjustCost(pWInfo->pLoops, pTemplate); + + /* If pBuilder->pOrSet is defined, then only keep track of the costs + ** and prereqs. + */ + if( pBuilder->pOrSet!=0 ){ + if( pTemplate->nLTerm ){ +#if WHERETRACE_ENABLED + u16 n = pBuilder->pOrSet->n; + int x = +#endif + whereOrInsert(pBuilder->pOrSet, pTemplate->prereq, pTemplate->rRun, + pTemplate->nOut); +#if WHERETRACE_ENABLED /* 0x8 */ + if( sqlite3WhereTrace & 0x8 ){ + sqlite3DebugPrintf(x?" or-%d: ":" or-X: ", n); + sqlite3WhereLoopPrint(pTemplate, pBuilder->pWC); + } +#endif + } + return SQLITE_OK; + } + + /* Look for an existing WhereLoop to replace with pTemplate + */ + ppPrev = whereLoopFindLesser(&pWInfo->pLoops, pTemplate); + + if( ppPrev==0 ){ + /* There already exists a WhereLoop on the list that is better + ** than pTemplate, so just ignore pTemplate */ +#if WHERETRACE_ENABLED /* 0x8 */ + if( sqlite3WhereTrace & 0x8 ){ + sqlite3DebugPrintf(" skip: "); + sqlite3WhereLoopPrint(pTemplate, pBuilder->pWC); + } +#endif + return SQLITE_OK; + }else{ + p = *ppPrev; + } + + /* If we reach this point it means that either p[] should be overwritten + ** with pTemplate[] if p[] exists, or if p==NULL then allocate a new + ** WhereLoop and insert it. + */ +#if WHERETRACE_ENABLED /* 0x8 */ + if( sqlite3WhereTrace & 0x8 ){ + if( p!=0 ){ + sqlite3DebugPrintf("replace: "); + sqlite3WhereLoopPrint(p, pBuilder->pWC); + sqlite3DebugPrintf(" with: "); + }else{ + sqlite3DebugPrintf(" add: "); + } + sqlite3WhereLoopPrint(pTemplate, pBuilder->pWC); + } +#endif + if( p==0 ){ + /* Allocate a new WhereLoop to add to the end of the list */ + *ppPrev = p = sqlite3DbMallocRawNN(db, sizeof(WhereLoop)); + if( p==0 ) return SQLITE_NOMEM_BKPT; + whereLoopInit(p); + p->pNextLoop = 0; + }else{ + /* We will be overwriting WhereLoop p[]. But before we do, first + ** go through the rest of the list and delete any other entries besides + ** p[] that are also supplanted by pTemplate */ + WhereLoop **ppTail = &p->pNextLoop; + WhereLoop *pToDel; + while( *ppTail ){ + ppTail = whereLoopFindLesser(ppTail, pTemplate); + if( ppTail==0 ) break; + pToDel = *ppTail; + if( pToDel==0 ) break; + *ppTail = pToDel->pNextLoop; +#if WHERETRACE_ENABLED /* 0x8 */ + if( sqlite3WhereTrace & 0x8 ){ + sqlite3DebugPrintf(" delete: "); + sqlite3WhereLoopPrint(pToDel, pBuilder->pWC); + } +#endif + whereLoopDelete(db, pToDel); + } + } + rc = whereLoopXfer(db, p, pTemplate); + if( (p->wsFlags & WHERE_VIRTUALTABLE)==0 ){ + Index *pIndex = p->u.btree.pIndex; + if( pIndex && pIndex->idxType==SQLITE_IDXTYPE_IPK ){ + p->u.btree.pIndex = 0; + } + } + return rc; +} + +/* +** Adjust the WhereLoop.nOut value downward to account for terms of the +** WHERE clause that reference the loop but which are not used by an +** index. +* +** For every WHERE clause term that is not used by the index +** and which has a truth probability assigned by one of the likelihood(), +** likely(), or unlikely() SQL functions, reduce the estimated number +** of output rows by the probability specified. +** +** TUNING: For every WHERE clause term that is not used by the index +** and which does not have an assigned truth probability, heuristics +** described below are used to try to estimate the truth probability. +** TODO --> Perhaps this is something that could be improved by better +** table statistics. +** +** Heuristic 1: Estimate the truth probability as 93.75%. The 93.75% +** value corresponds to -1 in LogEst notation, so this means decrement +** the WhereLoop.nOut field for every such WHERE clause term. +** +** Heuristic 2: If there exists one or more WHERE clause terms of the +** form "x==EXPR" and EXPR is not a constant 0 or 1, then make sure the +** final output row estimate is no greater than 1/4 of the total number +** of rows in the table. In other words, assume that x==EXPR will filter +** out at least 3 out of 4 rows. If EXPR is -1 or 0 or 1, then maybe the +** "x" column is boolean or else -1 or 0 or 1 is a common default value +** on the "x" column and so in that case only cap the output row estimate +** at 1/2 instead of 1/4. +*/ +static void whereLoopOutputAdjust( + WhereClause *pWC, /* The WHERE clause */ + WhereLoop *pLoop, /* The loop to adjust downward */ + LogEst nRow /* Number of rows in the entire table */ +){ + WhereTerm *pTerm, *pX; + Bitmask notAllowed = ~(pLoop->prereq|pLoop->maskSelf); + int i, j; + LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */ + + assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 ); + for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){ + assert( pTerm!=0 ); + if( (pTerm->prereqAll & notAllowed)!=0 ) continue; + if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue; + if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) continue; + for(j=pLoop->nLTerm-1; j>=0; j--){ + pX = pLoop->aLTerm[j]; + if( pX==0 ) continue; + if( pX==pTerm ) break; + if( pX->iParent>=0 && (&pWC->a[pX->iParent])==pTerm ) break; + } + if( j<0 ){ + sqlite3ProgressCheck(pWC->pWInfo->pParse); + if( pLoop->maskSelf==pTerm->prereqAll ){ + /* If there are extra terms in the WHERE clause not used by an index + ** that depend only on the table being scanned, and that will tend to + ** cause many rows to be omitted, then mark that table as + ** "self-culling". + ** + ** 2022-03-24: Self-culling only applies if either the extra terms + ** are straight comparison operators that are non-true with NULL + ** operand, or if the loop is not an OUTER JOIN. + */ + if( (pTerm->eOperator & 0x3f)!=0 + || (pWC->pWInfo->pTabList->a[pLoop->iTab].fg.jointype + & (JT_LEFT|JT_LTORJ))==0 + ){ + pLoop->wsFlags |= WHERE_SELFCULL; + } + } + if( pTerm->truthProb<=0 ){ + /* If a truth probability is specified using the likelihood() hints, + ** then use the probability provided by the application. */ + pLoop->nOut += pTerm->truthProb; + }else{ + /* In the absence of explicit truth probabilities, use heuristics to + ** guess a reasonable truth probability. */ + pLoop->nOut--; + if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 + && (pTerm->wtFlags & TERM_HIGHTRUTH)==0 /* tag-20200224-1 */ + ){ + Expr *pRight = pTerm->pExpr->pRight; + int k = 0; + testcase( pTerm->pExpr->op==TK_IS ); + if( sqlite3ExprIsInteger(pRight, &k) && k>=(-1) && k<=1 ){ + k = 10; + }else{ + k = 20; + } + if( iReduce<k ){ + pTerm->wtFlags |= TERM_HEURTRUTH; + iReduce = k; + } + } + } + } + } + if( pLoop->nOut > nRow-iReduce ){ + pLoop->nOut = nRow - iReduce; + } +} + +/* +** Term pTerm is a vector range comparison operation. The first comparison +** in the vector can be optimized using column nEq of the index. This +** function returns the total number of vector elements that can be used +** as part of the range comparison. +** +** For example, if the query is: +** +** WHERE a = ? AND (b, c, d) > (?, ?, ?) +** +** and the index: +** +** CREATE INDEX ... ON (a, b, c, d, e) +** +** then this function would be invoked with nEq=1. The value returned in +** this case is 3. +*/ +static int whereRangeVectorLen( + Parse *pParse, /* Parsing context */ + int iCur, /* Cursor open on pIdx */ + Index *pIdx, /* The index to be used for a inequality constraint */ + int nEq, /* Number of prior equality constraints on same index */ + WhereTerm *pTerm /* The vector inequality constraint */ +){ + int nCmp = sqlite3ExprVectorSize(pTerm->pExpr->pLeft); + int i; + + nCmp = MIN(nCmp, (pIdx->nColumn - nEq)); + for(i=1; i<nCmp; i++){ + /* Test if comparison i of pTerm is compatible with column (i+nEq) + ** of the index. If not, exit the loop. */ + char aff; /* Comparison affinity */ + char idxaff = 0; /* Indexed columns affinity */ + CollSeq *pColl; /* Comparison collation sequence */ + Expr *pLhs, *pRhs; + + assert( ExprUseXList(pTerm->pExpr->pLeft) ); + pLhs = pTerm->pExpr->pLeft->x.pList->a[i].pExpr; + pRhs = pTerm->pExpr->pRight; + if( ExprUseXSelect(pRhs) ){ + pRhs = pRhs->x.pSelect->pEList->a[i].pExpr; + }else{ + pRhs = pRhs->x.pList->a[i].pExpr; + } + + /* Check that the LHS of the comparison is a column reference to + ** the right column of the right source table. And that the sort + ** order of the index column is the same as the sort order of the + ** leftmost index column. */ + if( pLhs->op!=TK_COLUMN + || pLhs->iTable!=iCur + || pLhs->iColumn!=pIdx->aiColumn[i+nEq] + || pIdx->aSortOrder[i+nEq]!=pIdx->aSortOrder[nEq] + ){ + break; + } + + testcase( pLhs->iColumn==XN_ROWID ); + aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs)); + idxaff = sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn); + if( aff!=idxaff ) break; + + pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); + if( pColl==0 ) break; + if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break; + } + return i; +} + +/* +** Adjust the cost C by the costMult factor T. This only occurs if +** compiled with -DSQLITE_ENABLE_COSTMULT +*/ +#ifdef SQLITE_ENABLE_COSTMULT +# define ApplyCostMultiplier(C,T) C += T +#else +# define ApplyCostMultiplier(C,T) +#endif + +/* +** We have so far matched pBuilder->pNew->u.btree.nEq terms of the +** index pIndex. Try to match one more. +** +** When this function is called, pBuilder->pNew->nOut contains the +** number of rows expected to be visited by filtering using the nEq +** terms only. If it is modified, this value is restored before this +** function returns. +** +** If pProbe->idxType==SQLITE_IDXTYPE_IPK, that means pIndex is +** a fake index used for the INTEGER PRIMARY KEY. +*/ +static int whereLoopAddBtreeIndex( + WhereLoopBuilder *pBuilder, /* The WhereLoop factory */ + SrcItem *pSrc, /* FROM clause term being analyzed */ + Index *pProbe, /* An index on pSrc */ + LogEst nInMul /* log(Number of iterations due to IN) */ +){ + WhereInfo *pWInfo = pBuilder->pWInfo; /* WHERE analyze context */ + Parse *pParse = pWInfo->pParse; /* Parsing context */ + sqlite3 *db = pParse->db; /* Database connection malloc context */ + WhereLoop *pNew; /* Template WhereLoop under construction */ + WhereTerm *pTerm; /* A WhereTerm under consideration */ + int opMask; /* Valid operators for constraints */ + WhereScan scan; /* Iterator for WHERE terms */ + Bitmask saved_prereq; /* Original value of pNew->prereq */ + u16 saved_nLTerm; /* Original value of pNew->nLTerm */ + u16 saved_nEq; /* Original value of pNew->u.btree.nEq */ + u16 saved_nBtm; /* Original value of pNew->u.btree.nBtm */ + u16 saved_nTop; /* Original value of pNew->u.btree.nTop */ + u16 saved_nSkip; /* Original value of pNew->nSkip */ + u32 saved_wsFlags; /* Original value of pNew->wsFlags */ + LogEst saved_nOut; /* Original value of pNew->nOut */ + int rc = SQLITE_OK; /* Return code */ + LogEst rSize; /* Number of rows in the table */ + LogEst rLogSize; /* Logarithm of table size */ + WhereTerm *pTop = 0, *pBtm = 0; /* Top and bottom range constraints */ + + pNew = pBuilder->pNew; + assert( db->mallocFailed==0 || pParse->nErr>0 ); + if( pParse->nErr ){ + return pParse->rc; + } + WHERETRACE(0x800, ("BEGIN %s.addBtreeIdx(%s), nEq=%d, nSkip=%d, rRun=%d\n", + pProbe->pTable->zName,pProbe->zName, + pNew->u.btree.nEq, pNew->nSkip, pNew->rRun)); + + assert( (pNew->wsFlags & WHERE_VIRTUALTABLE)==0 ); + assert( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 ); + if( pNew->wsFlags & WHERE_BTM_LIMIT ){ + opMask = WO_LT|WO_LE; + }else{ + assert( pNew->u.btree.nBtm==0 ); + opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE|WO_ISNULL|WO_IS; + } + if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); + + assert( pNew->u.btree.nEq<pProbe->nColumn ); + assert( pNew->u.btree.nEq<pProbe->nKeyCol + || pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY ); + + saved_nEq = pNew->u.btree.nEq; + saved_nBtm = pNew->u.btree.nBtm; + saved_nTop = pNew->u.btree.nTop; + saved_nSkip = pNew->nSkip; + saved_nLTerm = pNew->nLTerm; + saved_wsFlags = pNew->wsFlags; + saved_prereq = pNew->prereq; + saved_nOut = pNew->nOut; + pTerm = whereScanInit(&scan, pBuilder->pWC, pSrc->iCursor, saved_nEq, + opMask, pProbe); + pNew->rSetup = 0; + rSize = pProbe->aiRowLogEst[0]; + rLogSize = estLog(rSize); + for(; rc==SQLITE_OK && pTerm!=0; pTerm = whereScanNext(&scan)){ + u16 eOp = pTerm->eOperator; /* Shorthand for pTerm->eOperator */ + LogEst rCostIdx; + LogEst nOutUnadjusted; /* nOut before IN() and WHERE adjustments */ + int nIn = 0; +#ifdef SQLITE_ENABLE_STAT4 + int nRecValid = pBuilder->nRecValid; +#endif + if( (eOp==WO_ISNULL || (pTerm->wtFlags&TERM_VNULL)!=0) + && indexColumnNotNull(pProbe, saved_nEq) + ){ + continue; /* ignore IS [NOT] NULL constraints on NOT NULL columns */ + } + if( pTerm->prereqRight & pNew->maskSelf ) continue; + + /* Do not allow the upper bound of a LIKE optimization range constraint + ** to mix with a lower range bound from some other source */ + if( pTerm->wtFlags & TERM_LIKEOPT && pTerm->eOperator==WO_LT ) continue; + + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 + && !constraintCompatibleWithOuterJoin(pTerm,pSrc) + ){ + continue; + } + if( IsUniqueIndex(pProbe) && saved_nEq==pProbe->nKeyCol-1 ){ + pBuilder->bldFlags1 |= SQLITE_BLDF1_UNIQUE; + }else{ + pBuilder->bldFlags1 |= SQLITE_BLDF1_INDEXED; + } + pNew->wsFlags = saved_wsFlags; + pNew->u.btree.nEq = saved_nEq; + pNew->u.btree.nBtm = saved_nBtm; + pNew->u.btree.nTop = saved_nTop; + pNew->nLTerm = saved_nLTerm; + if( pNew->nLTerm>=pNew->nLSlot + && whereLoopResize(db, pNew, pNew->nLTerm+1) + ){ + break; /* OOM while trying to enlarge the pNew->aLTerm array */ + } + pNew->aLTerm[pNew->nLTerm++] = pTerm; + pNew->prereq = (saved_prereq | pTerm->prereqRight) & ~pNew->maskSelf; + + assert( nInMul==0 + || (pNew->wsFlags & WHERE_COLUMN_NULL)!=0 + || (pNew->wsFlags & WHERE_COLUMN_IN)!=0 + || (pNew->wsFlags & WHERE_SKIPSCAN)!=0 + ); + + if( eOp & WO_IN ){ + Expr *pExpr = pTerm->pExpr; + if( ExprUseXSelect(pExpr) ){ + /* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */ + int i; + nIn = 46; assert( 46==sqlite3LogEst(25) ); + + /* The expression may actually be of the form (x, y) IN (SELECT...). + ** In this case there is a separate term for each of (x) and (y). + ** However, the nIn multiplier should only be applied once, not once + ** for each such term. The following loop checks that pTerm is the + ** first such term in use, and sets nIn back to 0 if it is not. */ + for(i=0; i<pNew->nLTerm-1; i++){ + if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ) nIn = 0; + } + }else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){ + /* "x IN (value, value, ...)" */ + nIn = sqlite3LogEst(pExpr->x.pList->nExpr); + } + if( pProbe->hasStat1 && rLogSize>=10 ){ + LogEst M, logK, x; + /* Let: + ** N = the total number of rows in the table + ** K = the number of entries on the RHS of the IN operator + ** M = the number of rows in the table that match terms to the + ** to the left in the same index. If the IN operator is on + ** the left-most index column, M==N. + ** + ** Given the definitions above, it is better to omit the IN operator + ** from the index lookup and instead do a scan of the M elements, + ** testing each scanned row against the IN operator separately, if: + ** + ** M*log(K) < K*log(N) + ** + ** Our estimates for M, K, and N might be inaccurate, so we build in + ** a safety margin of 2 (LogEst: 10) that favors using the IN operator + ** with the index, as using an index has better worst-case behavior. + ** If we do not have real sqlite_stat1 data, always prefer to use + ** the index. Do not bother with this optimization on very small + ** tables (less than 2 rows) as it is pointless in that case. + */ + M = pProbe->aiRowLogEst[saved_nEq]; + logK = estLog(nIn); + /* TUNING v----- 10 to bias toward indexed IN */ + x = M + logK + 10 - (nIn + rLogSize); + if( x>=0 ){ + WHERETRACE(0x40, + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d) " + "prefers indexed lookup\n", + saved_nEq, M, logK, nIn, rLogSize, x)); + }else if( nInMul<2 && OptimizationEnabled(db, SQLITE_SeekScan) ){ + WHERETRACE(0x40, + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d" + " nInMul=%d) prefers skip-scan\n", + saved_nEq, M, logK, nIn, rLogSize, x, nInMul)); + pNew->wsFlags |= WHERE_IN_SEEKSCAN; + }else{ + WHERETRACE(0x40, + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d" + " nInMul=%d) prefers normal scan\n", + saved_nEq, M, logK, nIn, rLogSize, x, nInMul)); + continue; + } + } + pNew->wsFlags |= WHERE_COLUMN_IN; + }else if( eOp & (WO_EQ|WO_IS) ){ + int iCol = pProbe->aiColumn[saved_nEq]; + pNew->wsFlags |= WHERE_COLUMN_EQ; + assert( saved_nEq==pNew->u.btree.nEq ); + if( iCol==XN_ROWID + || (iCol>=0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1) + ){ + if( iCol==XN_ROWID || pProbe->uniqNotNull + || (pProbe->nKeyCol==1 && pProbe->onError && eOp==WO_EQ) + ){ + pNew->wsFlags |= WHERE_ONEROW; + }else{ + pNew->wsFlags |= WHERE_UNQ_WANTED; + } + } + if( scan.iEquiv>1 ) pNew->wsFlags |= WHERE_TRANSCONS; + }else if( eOp & WO_ISNULL ){ + pNew->wsFlags |= WHERE_COLUMN_NULL; + }else{ + int nVecLen = whereRangeVectorLen( + pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm + ); + if( eOp & (WO_GT|WO_GE) ){ + testcase( eOp & WO_GT ); + testcase( eOp & WO_GE ); + pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT; + pNew->u.btree.nBtm = nVecLen; + pBtm = pTerm; + pTop = 0; + if( pTerm->wtFlags & TERM_LIKEOPT ){ + /* Range constraints that come from the LIKE optimization are + ** always used in pairs. */ + pTop = &pTerm[1]; + assert( (pTop-(pTerm->pWC->a))<pTerm->pWC->nTerm ); + assert( pTop->wtFlags & TERM_LIKEOPT ); + assert( pTop->eOperator==WO_LT ); + if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ + pNew->aLTerm[pNew->nLTerm++] = pTop; + pNew->wsFlags |= WHERE_TOP_LIMIT; + pNew->u.btree.nTop = 1; + } + }else{ + assert( eOp & (WO_LT|WO_LE) ); + testcase( eOp & WO_LT ); + testcase( eOp & WO_LE ); + pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT; + pNew->u.btree.nTop = nVecLen; + pTop = pTerm; + pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ? + pNew->aLTerm[pNew->nLTerm-2] : 0; + } + } + + /* At this point pNew->nOut is set to the number of rows expected to + ** be visited by the index scan before considering term pTerm, or the + ** values of nIn and nInMul. In other words, assuming that all + ** "x IN(...)" terms are replaced with "x = ?". This block updates + ** the value of pNew->nOut to account for pTerm (but not nIn/nInMul). */ + assert( pNew->nOut==saved_nOut ); + if( pNew->wsFlags & WHERE_COLUMN_RANGE ){ + /* Adjust nOut using stat4 data. Or, if there is no stat4 + ** data, using some other estimate. */ + whereRangeScanEst(pParse, pBuilder, pBtm, pTop, pNew); + }else{ + int nEq = ++pNew->u.btree.nEq; + assert( eOp & (WO_ISNULL|WO_EQ|WO_IN|WO_IS) ); + + assert( pNew->nOut==saved_nOut ); + if( pTerm->truthProb<=0 && pProbe->aiColumn[saved_nEq]>=0 ){ + assert( (eOp & WO_IN) || nIn==0 ); + testcase( eOp & WO_IN ); + pNew->nOut += pTerm->truthProb; + pNew->nOut -= nIn; + }else{ +#ifdef SQLITE_ENABLE_STAT4 + tRowcnt nOut = 0; + if( nInMul==0 + && pProbe->nSample + && ALWAYS(pNew->u.btree.nEq<=pProbe->nSampleCol) + && ((eOp & WO_IN)==0 || ExprUseXList(pTerm->pExpr)) + && OptimizationEnabled(db, SQLITE_Stat4) + ){ + Expr *pExpr = pTerm->pExpr; + if( (eOp & (WO_EQ|WO_ISNULL|WO_IS))!=0 ){ + testcase( eOp & WO_EQ ); + testcase( eOp & WO_IS ); + testcase( eOp & WO_ISNULL ); + rc = whereEqualScanEst(pParse, pBuilder, pExpr->pRight, &nOut); + }else{ + rc = whereInScanEst(pParse, pBuilder, pExpr->x.pList, &nOut); + } + if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; + if( rc!=SQLITE_OK ) break; /* Jump out of the pTerm loop */ + if( nOut ){ + pNew->nOut = sqlite3LogEst(nOut); + if( nEq==1 + /* TUNING: Mark terms as "low selectivity" if they seem likely + ** to be true for half or more of the rows in the table. + ** See tag-202002240-1 */ + && pNew->nOut+10 > pProbe->aiRowLogEst[0] + ){ +#if WHERETRACE_ENABLED /* 0x01 */ + if( sqlite3WhereTrace & 0x20 ){ + sqlite3DebugPrintf( + "STAT4 determines term has low selectivity:\n"); + sqlite3WhereTermPrint(pTerm, 999); + } +#endif + pTerm->wtFlags |= TERM_HIGHTRUTH; + if( pTerm->wtFlags & TERM_HEURTRUTH ){ + /* If the term has previously been used with an assumption of + ** higher selectivity, then set the flag to rerun the + ** loop computations. */ + pBuilder->bldFlags2 |= SQLITE_BLDF2_2NDPASS; + } + } + if( pNew->nOut>saved_nOut ) pNew->nOut = saved_nOut; + pNew->nOut -= nIn; + } + } + if( nOut==0 ) +#endif + { + pNew->nOut += (pProbe->aiRowLogEst[nEq] - pProbe->aiRowLogEst[nEq-1]); + if( eOp & WO_ISNULL ){ + /* TUNING: If there is no likelihood() value, assume that a + ** "col IS NULL" expression matches twice as many rows + ** as (col=?). */ + pNew->nOut += 10; + } + } + } + } + + /* Set rCostIdx to the cost of visiting selected rows in index. Add + ** it to pNew->rRun, which is currently set to the cost of the index + ** seek only. Then, if this is a non-covering index, add the cost of + ** visiting the rows in the main table. */ + assert( pSrc->pTab->szTabRow>0 ); + if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){ + /* The pProbe->szIdxRow is low for an IPK table since the interior + ** pages are small. Thus szIdxRow gives a good estimate of seek cost. + ** But the leaf pages are full-size, so pProbe->szIdxRow would badly + ** under-estimate the scanning cost. */ + rCostIdx = pNew->nOut + 16; + }else{ + rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow; + } + pNew->rRun = sqlite3LogEstAdd(rLogSize, rCostIdx); + if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK|WHERE_EXPRIDX))==0 ){ + pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16); + } + ApplyCostMultiplier(pNew->rRun, pProbe->pTable->costMult); + + nOutUnadjusted = pNew->nOut; + pNew->rRun += nInMul + nIn; + pNew->nOut += nInMul + nIn; + whereLoopOutputAdjust(pBuilder->pWC, pNew, rSize); + rc = whereLoopInsert(pBuilder, pNew); + + if( pNew->wsFlags & WHERE_COLUMN_RANGE ){ + pNew->nOut = saved_nOut; + }else{ + pNew->nOut = nOutUnadjusted; + } + + if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 + && pNew->u.btree.nEq<pProbe->nColumn + && (pNew->u.btree.nEq<pProbe->nKeyCol || + pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY) + ){ + if( pNew->u.btree.nEq>3 ){ + sqlite3ProgressCheck(pParse); + } + whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn); + } + pNew->nOut = saved_nOut; +#ifdef SQLITE_ENABLE_STAT4 + pBuilder->nRecValid = nRecValid; +#endif + } + pNew->prereq = saved_prereq; + pNew->u.btree.nEq = saved_nEq; + pNew->u.btree.nBtm = saved_nBtm; + pNew->u.btree.nTop = saved_nTop; + pNew->nSkip = saved_nSkip; + pNew->wsFlags = saved_wsFlags; + pNew->nOut = saved_nOut; + pNew->nLTerm = saved_nLTerm; + + /* Consider using a skip-scan if there are no WHERE clause constraints + ** available for the left-most terms of the index, and if the average + ** number of repeats in the left-most terms is at least 18. + ** + ** The magic number 18 is selected on the basis that scanning 17 rows + ** is almost always quicker than an index seek (even though if the index + ** contains fewer than 2^17 rows we assume otherwise in other parts of + ** the code). And, even if it is not, it should not be too much slower. + ** On the other hand, the extra seeks could end up being significantly + ** more expensive. */ + assert( 42==sqlite3LogEst(18) ); + if( saved_nEq==saved_nSkip + && saved_nEq+1<pProbe->nKeyCol + && saved_nEq==pNew->nLTerm + && pProbe->noSkipScan==0 + && pProbe->hasStat1!=0 + && OptimizationEnabled(db, SQLITE_SkipScan) + && pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */ + && (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK + ){ + LogEst nIter; + pNew->u.btree.nEq++; + pNew->nSkip++; + pNew->aLTerm[pNew->nLTerm++] = 0; + pNew->wsFlags |= WHERE_SKIPSCAN; + nIter = pProbe->aiRowLogEst[saved_nEq] - pProbe->aiRowLogEst[saved_nEq+1]; + pNew->nOut -= nIter; + /* TUNING: Because uncertainties in the estimates for skip-scan queries, + ** add a 1.375 fudge factor to make skip-scan slightly less likely. */ + nIter += 5; + whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nIter + nInMul); + pNew->nOut = saved_nOut; + pNew->u.btree.nEq = saved_nEq; + pNew->nSkip = saved_nSkip; + pNew->wsFlags = saved_wsFlags; + } + + WHERETRACE(0x800, ("END %s.addBtreeIdx(%s), nEq=%d, rc=%d\n", + pProbe->pTable->zName, pProbe->zName, saved_nEq, rc)); + return rc; +} + +/* +** Return True if it is possible that pIndex might be useful in +** implementing the ORDER BY clause in pBuilder. +** +** Return False if pBuilder does not contain an ORDER BY clause or +** if there is no way for pIndex to be useful in implementing that +** ORDER BY clause. +*/ +static int indexMightHelpWithOrderBy( + WhereLoopBuilder *pBuilder, + Index *pIndex, + int iCursor +){ + ExprList *pOB; + ExprList *aColExpr; + int ii, jj; + + if( pIndex->bUnordered ) return 0; + if( (pOB = pBuilder->pWInfo->pOrderBy)==0 ) return 0; + for(ii=0; ii<pOB->nExpr; ii++){ + Expr *pExpr = sqlite3ExprSkipCollateAndLikely(pOB->a[ii].pExpr); + if( NEVER(pExpr==0) ) continue; + if( pExpr->op==TK_COLUMN && pExpr->iTable==iCursor ){ + if( pExpr->iColumn<0 ) return 1; + for(jj=0; jj<pIndex->nKeyCol; jj++){ + if( pExpr->iColumn==pIndex->aiColumn[jj] ) return 1; + } + }else if( (aColExpr = pIndex->aColExpr)!=0 ){ + for(jj=0; jj<pIndex->nKeyCol; jj++){ + if( pIndex->aiColumn[jj]!=XN_EXPR ) continue; + if( sqlite3ExprCompareSkip(pExpr,aColExpr->a[jj].pExpr,iCursor)==0 ){ + return 1; + } + } + } + } + return 0; +} + +/* Check to see if a partial index with pPartIndexWhere can be used +** in the current query. Return true if it can be and false if not. +*/ +static int whereUsablePartialIndex( + int iTab, /* The table for which we want an index */ + u8 jointype, /* The JT_* flags on the join */ + WhereClause *pWC, /* The WHERE clause of the query */ + Expr *pWhere /* The WHERE clause from the partial index */ +){ + int i; + WhereTerm *pTerm; + Parse *pParse; + + if( jointype & JT_LTORJ ) return 0; + pParse = pWC->pWInfo->pParse; + while( pWhere->op==TK_AND ){ + if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0; + pWhere = pWhere->pRight; + } + if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0; + for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ + Expr *pExpr; + pExpr = pTerm->pExpr; + if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab) + && ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON)) + && sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab) + && (pTerm->wtFlags & TERM_VNULL)==0 + ){ + return 1; + } + } + return 0; +} + +/* +** pIdx is an index containing expressions. Check it see if any of the +** expressions in the index match the pExpr expression. +*/ +static int exprIsCoveredByIndex( + const Expr *pExpr, + const Index *pIdx, + int iTabCur +){ + int i; + for(i=0; i<pIdx->nColumn; i++){ + if( pIdx->aiColumn[i]==XN_EXPR + && sqlite3ExprCompare(0, pExpr, pIdx->aColExpr->a[i].pExpr, iTabCur)==0 + ){ + return 1; + } + } + return 0; +} + +/* +** Structure passed to the whereIsCoveringIndex Walker callback. +*/ +typedef struct CoveringIndexCheck CoveringIndexCheck; +struct CoveringIndexCheck { + Index *pIdx; /* The index */ + int iTabCur; /* Cursor number for the corresponding table */ + u8 bExpr; /* Uses an indexed expression */ + u8 bUnidx; /* Uses an unindexed column not within an indexed expr */ +}; + +/* +** Information passed in is pWalk->u.pCovIdxCk. Call it pCk. +** +** If the Expr node references the table with cursor pCk->iTabCur, then +** make sure that column is covered by the index pCk->pIdx. We know that +** all columns less than 63 (really BMS-1) are covered, so we don't need +** to check them. But we do need to check any column at 63 or greater. +** +** If the index does not cover the column, then set pWalk->eCode to +** non-zero and return WRC_Abort to stop the search. +** +** If this node does not disprove that the index can be a covering index, +** then just return WRC_Continue, to continue the search. +** +** If pCk->pIdx contains indexed expressions and one of those expressions +** matches pExpr, then prune the search. +*/ +static int whereIsCoveringIndexWalkCallback(Walker *pWalk, Expr *pExpr){ + int i; /* Loop counter */ + const Index *pIdx; /* The index of interest */ + const i16 *aiColumn; /* Columns contained in the index */ + u16 nColumn; /* Number of columns in the index */ + CoveringIndexCheck *pCk; /* Info about this search */ + + pCk = pWalk->u.pCovIdxCk; + pIdx = pCk->pIdx; + if( (pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN) ){ + /* if( pExpr->iColumn<(BMS-1) && pIdx->bHasExpr==0 ) return WRC_Continue;*/ + if( pExpr->iTable!=pCk->iTabCur ) return WRC_Continue; + pIdx = pWalk->u.pCovIdxCk->pIdx; + aiColumn = pIdx->aiColumn; + nColumn = pIdx->nColumn; + for(i=0; i<nColumn; i++){ + if( aiColumn[i]==pExpr->iColumn ) return WRC_Continue; + } + pCk->bUnidx = 1; + return WRC_Abort; + }else if( pIdx->bHasExpr + && exprIsCoveredByIndex(pExpr, pIdx, pWalk->u.pCovIdxCk->iTabCur) ){ + pCk->bExpr = 1; + return WRC_Prune; + } + return WRC_Continue; +} + + +/* +** pIdx is an index that covers all of the low-number columns used by +** pWInfo->pSelect (columns from 0 through 62) or an index that has +** expressions terms. Hence, we cannot determine whether or not it is +** a covering index by using the colUsed bitmasks. We have to do a search +** to see if the index is covering. This routine does that search. +** +** The return value is one of these: +** +** 0 The index is definitely not a covering index +** +** WHERE_IDX_ONLY The index is definitely a covering index +** +** WHERE_EXPRIDX The index is likely a covering index, but it is +** difficult to determine precisely because of the +** expressions that are indexed. Score it as a +** covering index, but still keep the main table open +** just in case we need it. +** +** This routine is an optimization. It is always safe to return zero. +** But returning one of the other two values when zero should have been +** returned can lead to incorrect bytecode and assertion faults. +*/ +static SQLITE_NOINLINE u32 whereIsCoveringIndex( + WhereInfo *pWInfo, /* The WHERE clause context */ + Index *pIdx, /* Index that is being tested */ + int iTabCur /* Cursor for the table being indexed */ +){ + int i, rc; + struct CoveringIndexCheck ck; + Walker w; + if( pWInfo->pSelect==0 ){ + /* We don't have access to the full query, so we cannot check to see + ** if pIdx is covering. Assume it is not. */ + return 0; + } + if( pIdx->bHasExpr==0 ){ + for(i=0; i<pIdx->nColumn; i++){ + if( pIdx->aiColumn[i]>=BMS-1 ) break; + } + if( i>=pIdx->nColumn ){ + /* pIdx does not index any columns greater than 62, but we know from + ** colMask that columns greater than 62 are used, so this is not a + ** covering index */ + return 0; + } + } + ck.pIdx = pIdx; + ck.iTabCur = iTabCur; + ck.bExpr = 0; + ck.bUnidx = 0; + memset(&w, 0, sizeof(w)); + w.xExprCallback = whereIsCoveringIndexWalkCallback; + w.xSelectCallback = sqlite3SelectWalkNoop; + w.u.pCovIdxCk = &ck; + sqlite3WalkSelect(&w, pWInfo->pSelect); + if( ck.bUnidx ){ + rc = 0; + }else if( ck.bExpr ){ + rc = WHERE_EXPRIDX; + }else{ + rc = WHERE_IDX_ONLY; + } + return rc; +} + +/* +** Add all WhereLoop objects for a single table of the join where the table +** is identified by pBuilder->pNew->iTab. That table is guaranteed to be +** a b-tree table, not a virtual table. +** +** The costs (WhereLoop.rRun) of the b-tree loops added by this function +** are calculated as follows: +** +** For a full scan, assuming the table (or index) contains nRow rows: +** +** cost = nRow * 3.0 // full-table scan +** cost = nRow * K // scan of covering index +** cost = nRow * (K+3.0) // scan of non-covering index +** +** where K is a value between 1.1 and 3.0 set based on the relative +** estimated average size of the index and table records. +** +** For an index scan, where nVisit is the number of index rows visited +** by the scan, and nSeek is the number of seek operations required on +** the index b-tree: +** +** cost = nSeek * (log(nRow) + K * nVisit) // covering index +** cost = nSeek * (log(nRow) + (K+3.0) * nVisit) // non-covering index +** +** Normally, nSeek is 1. nSeek values greater than 1 come about if the +** WHERE clause includes "x IN (....)" terms used in place of "x=?". Or when +** implicit "x IN (SELECT x FROM tbl)" terms are added for skip-scans. +** +** The estimated values (nRow, nVisit, nSeek) often contain a large amount +** of uncertainty. For this reason, scoring is designed to pick plans that +** "do the least harm" if the estimates are inaccurate. For example, a +** log(nRow) factor is omitted from a non-covering index scan in order to +** bias the scoring in favor of using an index, since the worst-case +** performance of using an index is far better than the worst-case performance +** of a full table scan. +*/ +static int whereLoopAddBtree( + WhereLoopBuilder *pBuilder, /* WHERE clause information */ + Bitmask mPrereq /* Extra prerequisites for using this table */ +){ + WhereInfo *pWInfo; /* WHERE analysis context */ + Index *pProbe; /* An index we are evaluating */ + Index sPk; /* A fake index object for the primary key */ + LogEst aiRowEstPk[2]; /* The aiRowLogEst[] value for the sPk index */ + i16 aiColumnPk = -1; /* The aColumn[] value for the sPk index */ + SrcList *pTabList; /* The FROM clause */ + SrcItem *pSrc; /* The FROM clause btree term to add */ + WhereLoop *pNew; /* Template WhereLoop object */ + int rc = SQLITE_OK; /* Return code */ + int iSortIdx = 1; /* Index number */ + int b; /* A boolean value */ + LogEst rSize; /* number of rows in the table */ + WhereClause *pWC; /* The parsed WHERE clause */ + Table *pTab; /* Table being queried */ + + pNew = pBuilder->pNew; + pWInfo = pBuilder->pWInfo; + pTabList = pWInfo->pTabList; + pSrc = pTabList->a + pNew->iTab; + pTab = pSrc->pTab; + pWC = pBuilder->pWC; + assert( !IsVirtual(pSrc->pTab) ); + + if( pSrc->fg.isIndexedBy ){ + assert( pSrc->fg.isCte==0 ); + /* An INDEXED BY clause specifies a particular index to use */ + pProbe = pSrc->u2.pIBIndex; + }else if( !HasRowid(pTab) ){ + pProbe = pTab->pIndex; + }else{ + /* There is no INDEXED BY clause. Create a fake Index object in local + ** variable sPk to represent the rowid primary key index. Make this + ** fake index the first in a chain of Index objects with all of the real + ** indices to follow */ + Index *pFirst; /* First of real indices on the table */ + memset(&sPk, 0, sizeof(Index)); + sPk.nKeyCol = 1; + sPk.nColumn = 1; + sPk.aiColumn = &aiColumnPk; + sPk.aiRowLogEst = aiRowEstPk; + sPk.onError = OE_Replace; + sPk.pTable = pTab; + sPk.szIdxRow = 3; /* TUNING: Interior rows of IPK table are very small */ + sPk.idxType = SQLITE_IDXTYPE_IPK; + aiRowEstPk[0] = pTab->nRowLogEst; + aiRowEstPk[1] = 0; + pFirst = pSrc->pTab->pIndex; + if( pSrc->fg.notIndexed==0 ){ + /* The real indices of the table are only considered if the + ** NOT INDEXED qualifier is omitted from the FROM clause */ + sPk.pNext = pFirst; + } + pProbe = &sPk; + } + rSize = pTab->nRowLogEst; + +#ifndef SQLITE_OMIT_AUTOMATIC_INDEX + /* Automatic indexes */ + if( !pBuilder->pOrSet /* Not part of an OR optimization */ + && (pWInfo->wctrlFlags & (WHERE_RIGHT_JOIN|WHERE_OR_SUBCLAUSE))==0 + && (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0 + && !pSrc->fg.isIndexedBy /* Has no INDEXED BY clause */ + && !pSrc->fg.notIndexed /* Has no NOT INDEXED clause */ + && HasRowid(pTab) /* Not WITHOUT ROWID table. (FIXME: Why not?) */ + && !pSrc->fg.isCorrelated /* Not a correlated subquery */ + && !pSrc->fg.isRecursive /* Not a recursive common table expression. */ + && (pSrc->fg.jointype & JT_RIGHT)==0 /* Not the right tab of a RIGHT JOIN */ + ){ + /* Generate auto-index WhereLoops */ + LogEst rLogSize; /* Logarithm of the number of rows in the table */ + WhereTerm *pTerm; + WhereTerm *pWCEnd = pWC->a + pWC->nTerm; + rLogSize = estLog(rSize); + for(pTerm=pWC->a; rc==SQLITE_OK && pTerm<pWCEnd; pTerm++){ + if( pTerm->prereqRight & pNew->maskSelf ) continue; + if( termCanDriveIndex(pTerm, pSrc, 0) ){ + pNew->u.btree.nEq = 1; + pNew->nSkip = 0; + pNew->u.btree.pIndex = 0; + pNew->nLTerm = 1; + pNew->aLTerm[0] = pTerm; + /* TUNING: One-time cost for computing the automatic index is + ** estimated to be X*N*log2(N) where N is the number of rows in + ** the table being indexed and where X is 7 (LogEst=28) for normal + ** tables or 0.5 (LogEst=-10) for views and subqueries. The value + ** of X is smaller for views and subqueries so that the query planner + ** will be more aggressive about generating automatic indexes for + ** those objects, since there is no opportunity to add schema + ** indexes on subqueries and views. */ + pNew->rSetup = rLogSize + rSize; + if( !IsView(pTab) && (pTab->tabFlags & TF_Ephemeral)==0 ){ + pNew->rSetup += 28; + }else{ + pNew->rSetup -= 25; /* Greatly reduced setup cost for auto indexes + ** on ephemeral materializations of views */ + } + ApplyCostMultiplier(pNew->rSetup, pTab->costMult); + if( pNew->rSetup<0 ) pNew->rSetup = 0; + /* TUNING: Each index lookup yields 20 rows in the table. This + ** is more than the usual guess of 10 rows, since we have no way + ** of knowing how selective the index will ultimately be. It would + ** not be unreasonable to make this value much larger. */ + pNew->nOut = 43; assert( 43==sqlite3LogEst(20) ); + pNew->rRun = sqlite3LogEstAdd(rLogSize,pNew->nOut); + pNew->wsFlags = WHERE_AUTO_INDEX; + pNew->prereq = mPrereq | pTerm->prereqRight; + rc = whereLoopInsert(pBuilder, pNew); + } + } + } +#endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ + + /* Loop over all indices. If there was an INDEXED BY clause, then only + ** consider index pProbe. */ + for(; rc==SQLITE_OK && pProbe; + pProbe=(pSrc->fg.isIndexedBy ? 0 : pProbe->pNext), iSortIdx++ + ){ + if( pProbe->pPartIdxWhere!=0 + && !whereUsablePartialIndex(pSrc->iCursor, pSrc->fg.jointype, pWC, + pProbe->pPartIdxWhere) + ){ + testcase( pNew->iTab!=pSrc->iCursor ); /* See ticket [98d973b8f5] */ + continue; /* Partial index inappropriate for this query */ + } + if( pProbe->bNoQuery ) continue; + rSize = pProbe->aiRowLogEst[0]; + pNew->u.btree.nEq = 0; + pNew->u.btree.nBtm = 0; + pNew->u.btree.nTop = 0; + pNew->nSkip = 0; + pNew->nLTerm = 0; + pNew->iSortIdx = 0; + pNew->rSetup = 0; + pNew->prereq = mPrereq; + pNew->nOut = rSize; + pNew->u.btree.pIndex = pProbe; + b = indexMightHelpWithOrderBy(pBuilder, pProbe, pSrc->iCursor); + + /* The ONEPASS_DESIRED flags never occurs together with ORDER BY */ + assert( (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 || b==0 ); + if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){ + /* Integer primary key index */ + pNew->wsFlags = WHERE_IPK; + + /* Full table scan */ + pNew->iSortIdx = b ? iSortIdx : 0; + /* TUNING: Cost of full table scan is 3.0*N. The 3.0 factor is an + ** extra cost designed to discourage the use of full table scans, + ** since index lookups have better worst-case performance if our + ** stat guesses are wrong. Reduce the 3.0 penalty slightly + ** (to 2.75) if we have valid STAT4 information for the table. + ** At 2.75, a full table scan is preferred over using an index on + ** a column with just two distinct values where each value has about + ** an equal number of appearances. Without STAT4 data, we still want + ** to use an index in that case, since the constraint might be for + ** the scarcer of the two values, and in that case an index lookup is + ** better. + */ +#ifdef SQLITE_ENABLE_STAT4 + pNew->rRun = rSize + 16 - 2*((pTab->tabFlags & TF_HasStat4)!=0); +#else + pNew->rRun = rSize + 16; +#endif + if( IsView(pTab) || (pTab->tabFlags & TF_Ephemeral)!=0 ){ + pNew->wsFlags |= WHERE_VIEWSCAN; + } + ApplyCostMultiplier(pNew->rRun, pTab->costMult); + whereLoopOutputAdjust(pWC, pNew, rSize); + rc = whereLoopInsert(pBuilder, pNew); + pNew->nOut = rSize; + if( rc ) break; + }else{ + Bitmask m; + if( pProbe->isCovering ){ + m = 0; + pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; + }else{ + m = pSrc->colUsed & pProbe->colNotIdxed; + pNew->wsFlags = WHERE_INDEXED; + if( m==TOPBIT || (pProbe->bHasExpr && !pProbe->bHasVCol && m!=0) ){ + u32 isCov = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor); + if( isCov==0 ){ + WHERETRACE(0x200, + ("-> %s is not a covering index" + " according to whereIsCoveringIndex()\n", pProbe->zName)); + assert( m!=0 ); + }else{ + m = 0; + pNew->wsFlags |= isCov; + if( isCov & WHERE_IDX_ONLY ){ + WHERETRACE(0x200, + ("-> %s is a covering expression index" + " according to whereIsCoveringIndex()\n", pProbe->zName)); + }else{ + assert( isCov==WHERE_EXPRIDX ); + WHERETRACE(0x200, + ("-> %s might be a covering expression index" + " according to whereIsCoveringIndex()\n", pProbe->zName)); + } + } + }else if( m==0 ){ + WHERETRACE(0x200, + ("-> %s a covering index according to bitmasks\n", + pProbe->zName, m==0 ? "is" : "is not")); + pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; + } + } + + /* Full scan via index */ + if( b + || !HasRowid(pTab) + || pProbe->pPartIdxWhere!=0 + || pSrc->fg.isIndexedBy + || ( m==0 + && pProbe->bUnordered==0 + && (pProbe->szIdxRow<pTab->szTabRow) + && (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 + && sqlite3GlobalConfig.bUseCis + && OptimizationEnabled(pWInfo->pParse->db, SQLITE_CoverIdxScan) + ) + ){ + pNew->iSortIdx = b ? iSortIdx : 0; + + /* The cost of visiting the index rows is N*K, where K is + ** between 1.1 and 3.0, depending on the relative sizes of the + ** index and table rows. */ + pNew->rRun = rSize + 1 + (15*pProbe->szIdxRow)/pTab->szTabRow; + if( m!=0 ){ + /* If this is a non-covering index scan, add in the cost of + ** doing table lookups. The cost will be 3x the number of + ** lookups. Take into account WHERE clause terms that can be + ** satisfied using just the index, and that do not require a + ** table lookup. */ + LogEst nLookup = rSize + 16; /* Base cost: N*3 */ + int ii; + int iCur = pSrc->iCursor; + WhereClause *pWC2 = &pWInfo->sWC; + for(ii=0; ii<pWC2->nTerm; ii++){ + WhereTerm *pTerm = &pWC2->a[ii]; + if( !sqlite3ExprCoveredByIndex(pTerm->pExpr, iCur, pProbe) ){ + break; + } + /* pTerm can be evaluated using just the index. So reduce + ** the expected number of table lookups accordingly */ + if( pTerm->truthProb<=0 ){ + nLookup += pTerm->truthProb; + }else{ + nLookup--; + if( pTerm->eOperator & (WO_EQ|WO_IS) ) nLookup -= 19; + } + } + + pNew->rRun = sqlite3LogEstAdd(pNew->rRun, nLookup); + } + ApplyCostMultiplier(pNew->rRun, pTab->costMult); + whereLoopOutputAdjust(pWC, pNew, rSize); + if( (pSrc->fg.jointype & JT_RIGHT)!=0 && pProbe->aColExpr ){ + /* Do not do an SCAN of a index-on-expression in a RIGHT JOIN + ** because the cursor used to access the index might not be + ** positioned to the correct row during the right-join no-match + ** loop. */ + }else{ + rc = whereLoopInsert(pBuilder, pNew); + } + pNew->nOut = rSize; + if( rc ) break; + } + } + + pBuilder->bldFlags1 = 0; + rc = whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, 0); + if( pBuilder->bldFlags1==SQLITE_BLDF1_INDEXED ){ + /* If a non-unique index is used, or if a prefix of the key for + ** unique index is used (making the index functionally non-unique) + ** then the sqlite_stat1 data becomes important for scoring the + ** plan */ + pTab->tabFlags |= TF_StatsUsed; + } +#ifdef SQLITE_ENABLE_STAT4 + sqlite3Stat4ProbeFree(pBuilder->pRec); + pBuilder->nRecValid = 0; + pBuilder->pRec = 0; +#endif + } + return rc; +} + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** Return true if pTerm is a virtual table LIMIT or OFFSET term. +*/ +static int isLimitTerm(WhereTerm *pTerm){ + assert( pTerm->eOperator==WO_AUX || pTerm->eMatchOp==0 ); + return pTerm->eMatchOp>=SQLITE_INDEX_CONSTRAINT_LIMIT + && pTerm->eMatchOp<=SQLITE_INDEX_CONSTRAINT_OFFSET; +} + +/* +** Argument pIdxInfo is already populated with all constraints that may +** be used by the virtual table identified by pBuilder->pNew->iTab. This +** function marks a subset of those constraints usable, invokes the +** xBestIndex method and adds the returned plan to pBuilder. +** +** A constraint is marked usable if: +** +** * Argument mUsable indicates that its prerequisites are available, and +** +** * It is not one of the operators specified in the mExclude mask passed +** as the fourth argument (which in practice is either WO_IN or 0). +** +** Argument mPrereq is a mask of tables that must be scanned before the +** virtual table in question. These are added to the plans prerequisites +** before it is added to pBuilder. +** +** Output parameter *pbIn is set to true if the plan added to pBuilder +** uses one or more WO_IN terms, or false otherwise. +*/ +static int whereLoopAddVirtualOne( + WhereLoopBuilder *pBuilder, + Bitmask mPrereq, /* Mask of tables that must be used. */ + Bitmask mUsable, /* Mask of usable tables */ + u16 mExclude, /* Exclude terms using these operators */ + sqlite3_index_info *pIdxInfo, /* Populated object for xBestIndex */ + u16 mNoOmit, /* Do not omit these constraints */ + int *pbIn, /* OUT: True if plan uses an IN(...) op */ + int *pbRetryLimit /* OUT: Retry without LIMIT/OFFSET */ +){ + WhereClause *pWC = pBuilder->pWC; + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + struct sqlite3_index_constraint *pIdxCons; + struct sqlite3_index_constraint_usage *pUsage = pIdxInfo->aConstraintUsage; + int i; + int mxTerm; + int rc = SQLITE_OK; + WhereLoop *pNew = pBuilder->pNew; + Parse *pParse = pBuilder->pWInfo->pParse; + SrcItem *pSrc = &pBuilder->pWInfo->pTabList->a[pNew->iTab]; + int nConstraint = pIdxInfo->nConstraint; + + assert( (mUsable & mPrereq)==mPrereq ); + *pbIn = 0; + pNew->prereq = mPrereq; + + /* Set the usable flag on the subset of constraints identified by + ** arguments mUsable and mExclude. */ + pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; + for(i=0; i<nConstraint; i++, pIdxCons++){ + WhereTerm *pTerm = &pWC->a[pIdxCons->iTermOffset]; + pIdxCons->usable = 0; + if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight + && (pTerm->eOperator & mExclude)==0 + && (pbRetryLimit || !isLimitTerm(pTerm)) + ){ + pIdxCons->usable = 1; + } + } + + /* Initialize the output fields of the sqlite3_index_info structure */ + memset(pUsage, 0, sizeof(pUsage[0])*nConstraint); + assert( pIdxInfo->needToFreeIdxStr==0 ); + pIdxInfo->idxStr = 0; + pIdxInfo->idxNum = 0; + pIdxInfo->orderByConsumed = 0; + pIdxInfo->estimatedCost = SQLITE_BIG_DBL / (double)2; + pIdxInfo->estimatedRows = 25; + pIdxInfo->idxFlags = 0; + pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed; + pHidden->mHandleIn = 0; + + /* Invoke the virtual table xBestIndex() method */ + rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo); + if( rc ){ + if( rc==SQLITE_CONSTRAINT ){ + /* If the xBestIndex method returns SQLITE_CONSTRAINT, that means + ** that the particular combination of parameters provided is unusable. + ** Make no entries in the loop table. + */ + WHERETRACE(0xffffffff, (" ^^^^--- non-viable plan rejected!\n")); + return SQLITE_OK; + } + return rc; + } + + mxTerm = -1; + assert( pNew->nLSlot>=nConstraint ); + memset(pNew->aLTerm, 0, sizeof(pNew->aLTerm[0])*nConstraint ); + memset(&pNew->u.vtab, 0, sizeof(pNew->u.vtab)); + pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; + for(i=0; i<nConstraint; i++, pIdxCons++){ + int iTerm; + if( (iTerm = pUsage[i].argvIndex - 1)>=0 ){ + WhereTerm *pTerm; + int j = pIdxCons->iTermOffset; + if( iTerm>=nConstraint + || j<0 + || j>=pWC->nTerm + || pNew->aLTerm[iTerm]!=0 + || pIdxCons->usable==0 + ){ + sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName); + testcase( pIdxInfo->needToFreeIdxStr ); + return SQLITE_ERROR; + } + testcase( iTerm==nConstraint-1 ); + testcase( j==0 ); + testcase( j==pWC->nTerm-1 ); + pTerm = &pWC->a[j]; + pNew->prereq |= pTerm->prereqRight; + assert( iTerm<pNew->nLSlot ); + pNew->aLTerm[iTerm] = pTerm; + if( iTerm>mxTerm ) mxTerm = iTerm; + testcase( iTerm==15 ); + testcase( iTerm==16 ); + if( pUsage[i].omit ){ + if( i<16 && ((1<<i)&mNoOmit)==0 ){ + testcase( i!=iTerm ); + pNew->u.vtab.omitMask |= 1<<iTerm; + }else{ + testcase( i!=iTerm ); + } + if( pTerm->eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET ){ + pNew->u.vtab.bOmitOffset = 1; + } + } + if( SMASKBIT32(i) & pHidden->mHandleIn ){ + pNew->u.vtab.mHandleIn |= MASKBIT32(iTerm); + }else if( (pTerm->eOperator & WO_IN)!=0 ){ + /* A virtual table that is constrained by an IN clause may not + ** consume the ORDER BY clause because (1) the order of IN terms + ** is not necessarily related to the order of output terms and + ** (2) Multiple outputs from a single IN value will not merge + ** together. */ + pIdxInfo->orderByConsumed = 0; + pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE; + *pbIn = 1; assert( (mExclude & WO_IN)==0 ); + } + + assert( pbRetryLimit || !isLimitTerm(pTerm) ); + if( isLimitTerm(pTerm) && *pbIn ){ + /* If there is an IN(...) term handled as an == (separate call to + ** xFilter for each value on the RHS of the IN) and a LIMIT or + ** OFFSET term handled as well, the plan is unusable. Set output + ** variable *pbRetryLimit to true to tell the caller to retry with + ** LIMIT and OFFSET disabled. */ + if( pIdxInfo->needToFreeIdxStr ){ + sqlite3_free(pIdxInfo->idxStr); + pIdxInfo->idxStr = 0; + pIdxInfo->needToFreeIdxStr = 0; + } + *pbRetryLimit = 1; + return SQLITE_OK; + } + } + } + + pNew->nLTerm = mxTerm+1; + for(i=0; i<=mxTerm; i++){ + if( pNew->aLTerm[i]==0 ){ + /* The non-zero argvIdx values must be contiguous. Raise an + ** error if they are not */ + sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName); + testcase( pIdxInfo->needToFreeIdxStr ); + return SQLITE_ERROR; + } + } + assert( pNew->nLTerm<=pNew->nLSlot ); + pNew->u.vtab.idxNum = pIdxInfo->idxNum; + pNew->u.vtab.needFree = pIdxInfo->needToFreeIdxStr; + pIdxInfo->needToFreeIdxStr = 0; + pNew->u.vtab.idxStr = pIdxInfo->idxStr; + pNew->u.vtab.isOrdered = (i8)(pIdxInfo->orderByConsumed ? + pIdxInfo->nOrderBy : 0); + pNew->rSetup = 0; + pNew->rRun = sqlite3LogEstFromDouble(pIdxInfo->estimatedCost); + pNew->nOut = sqlite3LogEst(pIdxInfo->estimatedRows); + + /* Set the WHERE_ONEROW flag if the xBestIndex() method indicated + ** that the scan will visit at most one row. Clear it otherwise. */ + if( pIdxInfo->idxFlags & SQLITE_INDEX_SCAN_UNIQUE ){ + pNew->wsFlags |= WHERE_ONEROW; + }else{ + pNew->wsFlags &= ~WHERE_ONEROW; + } + rc = whereLoopInsert(pBuilder, pNew); + if( pNew->u.vtab.needFree ){ + sqlite3_free(pNew->u.vtab.idxStr); + pNew->u.vtab.needFree = 0; + } + WHERETRACE(0xffffffff, (" bIn=%d prereqIn=%04llx prereqOut=%04llx\n", + *pbIn, (sqlite3_uint64)mPrereq, + (sqlite3_uint64)(pNew->prereq & ~mPrereq))); + + return rc; +} + +/* +** Return the collating sequence for a constraint passed into xBestIndex. +** +** pIdxInfo must be an sqlite3_index_info structure passed into xBestIndex. +** This routine depends on there being a HiddenIndexInfo structure immediately +** following the sqlite3_index_info structure. +** +** Return a pointer to the collation name: +** +** 1. If there is an explicit COLLATE operator on the constraint, return it. +** +** 2. Else, if the column has an alternative collation, return that. +** +** 3. Otherwise, return "BINARY". +*/ +SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int iCons){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + const char *zRet = 0; + if( iCons>=0 && iCons<pIdxInfo->nConstraint ){ + CollSeq *pC = 0; + int iTerm = pIdxInfo->aConstraint[iCons].iTermOffset; + Expr *pX = pHidden->pWC->a[iTerm].pExpr; + if( pX->pLeft ){ + pC = sqlite3ExprCompareCollSeq(pHidden->pParse, pX); + } + zRet = (pC ? pC->zName : sqlite3StrBINARY); + } + return zRet; +} + +/* +** Return true if constraint iCons is really an IN(...) constraint, or +** false otherwise. If iCons is an IN(...) constraint, set (if bHandle!=0) +** or clear (if bHandle==0) the flag to handle it using an iterator. +*/ +SQLITE_API int sqlite3_vtab_in(sqlite3_index_info *pIdxInfo, int iCons, int bHandle){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + u32 m = SMASKBIT32(iCons); + if( m & pHidden->mIn ){ + if( bHandle==0 ){ + pHidden->mHandleIn &= ~m; + }else if( bHandle>0 ){ + pHidden->mHandleIn |= m; + } + return 1; + } + return 0; +} + +/* +** This interface is callable from within the xBestIndex callback only. +** +** If possible, set (*ppVal) to point to an object containing the value +** on the right-hand-side of constraint iCons. +*/ +SQLITE_API int sqlite3_vtab_rhs_value( + sqlite3_index_info *pIdxInfo, /* Copy of first argument to xBestIndex */ + int iCons, /* Constraint for which RHS is wanted */ + sqlite3_value **ppVal /* Write value extracted here */ +){ + HiddenIndexInfo *pH = (HiddenIndexInfo*)&pIdxInfo[1]; + sqlite3_value *pVal = 0; + int rc = SQLITE_OK; + if( iCons<0 || iCons>=pIdxInfo->nConstraint ){ + rc = SQLITE_MISUSE_BKPT; /* EV: R-30545-25046 */ + }else{ + if( pH->aRhs[iCons]==0 ){ + WhereTerm *pTerm = &pH->pWC->a[pIdxInfo->aConstraint[iCons].iTermOffset]; + rc = sqlite3ValueFromExpr( + pH->pParse->db, pTerm->pExpr->pRight, ENC(pH->pParse->db), + SQLITE_AFF_BLOB, &pH->aRhs[iCons] + ); + testcase( rc!=SQLITE_OK ); + } + pVal = pH->aRhs[iCons]; + } + *ppVal = pVal; + + if( rc==SQLITE_OK && pVal==0 ){ /* IMP: R-19933-32160 */ + rc = SQLITE_NOTFOUND; /* IMP: R-36424-56542 */ + } + + return rc; +} + +/* +** Return true if ORDER BY clause may be handled as DISTINCT. +*/ +SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + assert( pHidden->eDistinct>=0 && pHidden->eDistinct<=3 ); + return pHidden->eDistinct; +} + +/* +** Cause the prepared statement that is associated with a call to +** xBestIndex to potentially use all schemas. If the statement being +** prepared is read-only, then just start read transactions on all +** schemas. But if this is a write operation, start writes on all +** schemas. +** +** This is used by the (built-in) sqlite_dbpage virtual table. +*/ +SQLITE_PRIVATE void sqlite3VtabUsesAllSchemas(Parse *pParse){ + int nDb = pParse->db->nDb; + int i; + for(i=0; i<nDb; i++){ + sqlite3CodeVerifySchema(pParse, i); + } + if( DbMaskNonZero(pParse->writeMask) ){ + for(i=0; i<nDb; i++){ + sqlite3BeginWriteOperation(pParse, 0, i); + } + } +} + +/* +** Add all WhereLoop objects for a table of the join identified by +** pBuilder->pNew->iTab. That table is guaranteed to be a virtual table. +** +** If there are no LEFT or CROSS JOIN joins in the query, both mPrereq and +** mUnusable are set to 0. Otherwise, mPrereq is a mask of all FROM clause +** entries that occur before the virtual table in the FROM clause and are +** separated from it by at least one LEFT or CROSS JOIN. Similarly, the +** mUnusable mask contains all FROM clause entries that occur after the +** virtual table and are separated from it by at least one LEFT or +** CROSS JOIN. +** +** For example, if the query were: +** +** ... FROM t1, t2 LEFT JOIN t3, t4, vt CROSS JOIN t5, t6; +** +** then mPrereq corresponds to (t1, t2) and mUnusable to (t5, t6). +** +** All the tables in mPrereq must be scanned before the current virtual +** table. So any terms for which all prerequisites are satisfied by +** mPrereq may be specified as "usable" in all calls to xBestIndex. +** Conversely, all tables in mUnusable must be scanned after the current +** virtual table, so any terms for which the prerequisites overlap with +** mUnusable should always be configured as "not-usable" for xBestIndex. +*/ +static int whereLoopAddVirtual( + WhereLoopBuilder *pBuilder, /* WHERE clause information */ + Bitmask mPrereq, /* Tables that must be scanned before this one */ + Bitmask mUnusable /* Tables that must be scanned after this one */ +){ + int rc = SQLITE_OK; /* Return code */ + WhereInfo *pWInfo; /* WHERE analysis context */ + Parse *pParse; /* The parsing context */ + WhereClause *pWC; /* The WHERE clause */ + SrcItem *pSrc; /* The FROM clause term to search */ + sqlite3_index_info *p; /* Object to pass to xBestIndex() */ + int nConstraint; /* Number of constraints in p */ + int bIn; /* True if plan uses IN(...) operator */ + WhereLoop *pNew; + Bitmask mBest; /* Tables used by best possible plan */ + u16 mNoOmit; + int bRetry = 0; /* True to retry with LIMIT/OFFSET disabled */ + + assert( (mPrereq & mUnusable)==0 ); + pWInfo = pBuilder->pWInfo; + pParse = pWInfo->pParse; + pWC = pBuilder->pWC; + pNew = pBuilder->pNew; + pSrc = &pWInfo->pTabList->a[pNew->iTab]; + assert( IsVirtual(pSrc->pTab) ); + p = allocateIndexInfo(pWInfo, pWC, mUnusable, pSrc, &mNoOmit); + if( p==0 ) return SQLITE_NOMEM_BKPT; + pNew->rSetup = 0; + pNew->wsFlags = WHERE_VIRTUALTABLE; + pNew->nLTerm = 0; + pNew->u.vtab.needFree = 0; + nConstraint = p->nConstraint; + if( whereLoopResize(pParse->db, pNew, nConstraint) ){ + freeIndexInfo(pParse->db, p); + return SQLITE_NOMEM_BKPT; + } + + /* First call xBestIndex() with all constraints usable. */ + WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pTab->zName)); + WHERETRACE(0x800, (" VirtualOne: all usable\n")); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, &bRetry + ); + if( bRetry ){ + assert( rc==SQLITE_OK ); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, 0 + ); + } + + /* If the call to xBestIndex() with all terms enabled produced a plan + ** that does not require any source tables (IOW: a plan with mBest==0) + ** and does not use an IN(...) operator, then there is no point in making + ** any further calls to xBestIndex() since they will all return the same + ** result (if the xBestIndex() implementation is sane). */ + if( rc==SQLITE_OK && ((mBest = (pNew->prereq & ~mPrereq))!=0 || bIn) ){ + int seenZero = 0; /* True if a plan with no prereqs seen */ + int seenZeroNoIN = 0; /* Plan with no prereqs and no IN(...) seen */ + Bitmask mPrev = 0; + Bitmask mBestNoIn = 0; + + /* If the plan produced by the earlier call uses an IN(...) term, call + ** xBestIndex again, this time with IN(...) terms disabled. */ + if( bIn ){ + WHERETRACE(0x800, (" VirtualOne: all usable w/o IN\n")); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn, 0); + assert( bIn==0 ); + mBestNoIn = pNew->prereq & ~mPrereq; + if( mBestNoIn==0 ){ + seenZero = 1; + seenZeroNoIN = 1; + } + } + + /* Call xBestIndex once for each distinct value of (prereqRight & ~mPrereq) + ** in the set of terms that apply to the current virtual table. */ + while( rc==SQLITE_OK ){ + int i; + Bitmask mNext = ALLBITS; + assert( mNext>0 ); + for(i=0; i<nConstraint; i++){ + Bitmask mThis = ( + pWC->a[p->aConstraint[i].iTermOffset].prereqRight & ~mPrereq + ); + if( mThis>mPrev && mThis<mNext ) mNext = mThis; + } + mPrev = mNext; + if( mNext==ALLBITS ) break; + if( mNext==mBest || mNext==mBestNoIn ) continue; + WHERETRACE(0x800, (" VirtualOne: mPrev=%04llx mNext=%04llx\n", + (sqlite3_uint64)mPrev, (sqlite3_uint64)mNext)); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn, 0); + if( pNew->prereq==mPrereq ){ + seenZero = 1; + if( bIn==0 ) seenZeroNoIN = 1; + } + } + + /* If the calls to xBestIndex() in the above loop did not find a plan + ** that requires no source tables at all (i.e. one guaranteed to be + ** usable), make a call here with all source tables disabled */ + if( rc==SQLITE_OK && seenZero==0 ){ + WHERETRACE(0x800, (" VirtualOne: all disabled\n")); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn, 0); + if( bIn==0 ) seenZeroNoIN = 1; + } + + /* If the calls to xBestIndex() have so far failed to find a plan + ** that requires no source tables at all and does not use an IN(...) + ** operator, make a final call to obtain one here. */ + if( rc==SQLITE_OK && seenZeroNoIN==0 ){ + WHERETRACE(0x800, (" VirtualOne: all disabled and w/o IN\n")); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn, 0); + } + } + + if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr); + freeIndexInfo(pParse->db, p); + WHERETRACE(0x800, ("END %s.addVirtual(), rc=%d\n", pSrc->pTab->zName, rc)); + return rc; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +/* +** Add WhereLoop entries to handle OR terms. This works for either +** btrees or virtual tables. +*/ +static int whereLoopAddOr( + WhereLoopBuilder *pBuilder, + Bitmask mPrereq, + Bitmask mUnusable +){ + WhereInfo *pWInfo = pBuilder->pWInfo; + WhereClause *pWC; + WhereLoop *pNew; + WhereTerm *pTerm, *pWCEnd; + int rc = SQLITE_OK; + int iCur; + WhereClause tempWC; + WhereLoopBuilder sSubBuild; + WhereOrSet sSum, sCur; + SrcItem *pItem; + + pWC = pBuilder->pWC; + pWCEnd = pWC->a + pWC->nTerm; + pNew = pBuilder->pNew; + memset(&sSum, 0, sizeof(sSum)); + pItem = pWInfo->pTabList->a + pNew->iTab; + iCur = pItem->iCursor; + + /* The multi-index OR optimization does not work for RIGHT and FULL JOIN */ + if( pItem->fg.jointype & JT_RIGHT ) return SQLITE_OK; + + for(pTerm=pWC->a; pTerm<pWCEnd && rc==SQLITE_OK; pTerm++){ + if( (pTerm->eOperator & WO_OR)!=0 + && (pTerm->u.pOrInfo->indexable & pNew->maskSelf)!=0 + ){ + WhereClause * const pOrWC = &pTerm->u.pOrInfo->wc; + WhereTerm * const pOrWCEnd = &pOrWC->a[pOrWC->nTerm]; + WhereTerm *pOrTerm; + int once = 1; + int i, j; + + sSubBuild = *pBuilder; + sSubBuild.pOrSet = &sCur; + + WHERETRACE(0x400, ("Begin processing OR-clause %p\n", pTerm)); + for(pOrTerm=pOrWC->a; pOrTerm<pOrWCEnd; pOrTerm++){ + if( (pOrTerm->eOperator & WO_AND)!=0 ){ + sSubBuild.pWC = &pOrTerm->u.pAndInfo->wc; + }else if( pOrTerm->leftCursor==iCur ){ + tempWC.pWInfo = pWC->pWInfo; + tempWC.pOuter = pWC; + tempWC.op = TK_AND; + tempWC.nTerm = 1; + tempWC.nBase = 1; + tempWC.a = pOrTerm; + sSubBuild.pWC = &tempWC; + }else{ + continue; + } + sCur.n = 0; +#ifdef WHERETRACE_ENABLED + WHERETRACE(0x400, ("OR-term %d of %p has %d subterms:\n", + (int)(pOrTerm-pOrWC->a), pTerm, sSubBuild.pWC->nTerm)); + if( sqlite3WhereTrace & 0x20000 ){ + sqlite3WhereClausePrint(sSubBuild.pWC); + } +#endif +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pItem->pTab) ){ + rc = whereLoopAddVirtual(&sSubBuild, mPrereq, mUnusable); + }else +#endif + { + rc = whereLoopAddBtree(&sSubBuild, mPrereq); + } + if( rc==SQLITE_OK ){ + rc = whereLoopAddOr(&sSubBuild, mPrereq, mUnusable); + } + testcase( rc==SQLITE_NOMEM && sCur.n>0 ); + testcase( rc==SQLITE_DONE ); + if( sCur.n==0 ){ + sSum.n = 0; + break; + }else if( once ){ + whereOrMove(&sSum, &sCur); + once = 0; + }else{ + WhereOrSet sPrev; + whereOrMove(&sPrev, &sSum); + sSum.n = 0; + for(i=0; i<sPrev.n; i++){ + for(j=0; j<sCur.n; j++){ + whereOrInsert(&sSum, sPrev.a[i].prereq | sCur.a[j].prereq, + sqlite3LogEstAdd(sPrev.a[i].rRun, sCur.a[j].rRun), + sqlite3LogEstAdd(sPrev.a[i].nOut, sCur.a[j].nOut)); + } + } + } + } + pNew->nLTerm = 1; + pNew->aLTerm[0] = pTerm; + pNew->wsFlags = WHERE_MULTI_OR; + pNew->rSetup = 0; + pNew->iSortIdx = 0; + memset(&pNew->u, 0, sizeof(pNew->u)); + for(i=0; rc==SQLITE_OK && i<sSum.n; i++){ + /* TUNING: Currently sSum.a[i].rRun is set to the sum of the costs + ** of all sub-scans required by the OR-scan. However, due to rounding + ** errors, it may be that the cost of the OR-scan is equal to its + ** most expensive sub-scan. Add the smallest possible penalty + ** (equivalent to multiplying the cost by 1.07) to ensure that + ** this does not happen. Otherwise, for WHERE clauses such as the + ** following where there is an index on "y": + ** + ** WHERE likelihood(x=?, 0.99) OR y=? + ** + ** the planner may elect to "OR" together a full-table scan and an + ** index lookup. And other similarly odd results. */ + pNew->rRun = sSum.a[i].rRun + 1; + pNew->nOut = sSum.a[i].nOut; + pNew->prereq = sSum.a[i].prereq; + rc = whereLoopInsert(pBuilder, pNew); + } + WHERETRACE(0x400, ("End processing OR-clause %p\n", pTerm)); + } + } + return rc; +} + +/* +** Add all WhereLoop objects for all tables +*/ +static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ + WhereInfo *pWInfo = pBuilder->pWInfo; + Bitmask mPrereq = 0; + Bitmask mPrior = 0; + int iTab; + SrcList *pTabList = pWInfo->pTabList; + SrcItem *pItem; + SrcItem *pEnd = &pTabList->a[pWInfo->nLevel]; + sqlite3 *db = pWInfo->pParse->db; + int rc = SQLITE_OK; + int bFirstPastRJ = 0; + int hasRightJoin = 0; + WhereLoop *pNew; + + + /* Loop over the tables in the join, from left to right */ + pNew = pBuilder->pNew; + + /* Verify that pNew has already been initialized */ + assert( pNew->nLTerm==0 ); + assert( pNew->wsFlags==0 ); + assert( pNew->nLSlot>=ArraySize(pNew->aLTermSpace) ); + assert( pNew->aLTerm!=0 ); + + pBuilder->iPlanLimit = SQLITE_QUERY_PLANNER_LIMIT; + for(iTab=0, pItem=pTabList->a; pItem<pEnd; iTab++, pItem++){ + Bitmask mUnusable = 0; + pNew->iTab = iTab; + pBuilder->iPlanLimit += SQLITE_QUERY_PLANNER_LIMIT_INCR; + pNew->maskSelf = sqlite3WhereGetMask(&pWInfo->sMaskSet, pItem->iCursor); + if( bFirstPastRJ + || (pItem->fg.jointype & (JT_OUTER|JT_CROSS|JT_LTORJ))!=0 + ){ + /* Add prerequisites to prevent reordering of FROM clause terms + ** across CROSS joins and outer joins. The bFirstPastRJ boolean + ** prevents the right operand of a RIGHT JOIN from being swapped with + ** other elements even further to the right. + ** + ** The JT_LTORJ case and the hasRightJoin flag work together to + ** prevent FROM-clause terms from moving from the right side of + ** a LEFT JOIN over to the left side of that join if the LEFT JOIN + ** is itself on the left side of a RIGHT JOIN. + */ + if( pItem->fg.jointype & JT_LTORJ ) hasRightJoin = 1; + mPrereq |= mPrior; + bFirstPastRJ = (pItem->fg.jointype & JT_RIGHT)!=0; + }else if( !hasRightJoin ){ + mPrereq = 0; + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pItem->pTab) ){ + SrcItem *p; + for(p=&pItem[1]; p<pEnd; p++){ + if( mUnusable || (p->fg.jointype & (JT_OUTER|JT_CROSS)) ){ + mUnusable |= sqlite3WhereGetMask(&pWInfo->sMaskSet, p->iCursor); + } + } + rc = whereLoopAddVirtual(pBuilder, mPrereq, mUnusable); + }else +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + { + rc = whereLoopAddBtree(pBuilder, mPrereq); + } + if( rc==SQLITE_OK && pBuilder->pWC->hasOr ){ + rc = whereLoopAddOr(pBuilder, mPrereq, mUnusable); + } + mPrior |= pNew->maskSelf; + if( rc || db->mallocFailed ){ + if( rc==SQLITE_DONE ){ + /* We hit the query planner search limit set by iPlanLimit */ + sqlite3_log(SQLITE_WARNING, "abbreviated query algorithm search"); + rc = SQLITE_OK; + }else{ + break; + } + } + } + + whereLoopClear(db, pNew); + return rc; +} + +/* +** Examine a WherePath (with the addition of the extra WhereLoop of the 6th +** parameters) to see if it outputs rows in the requested ORDER BY +** (or GROUP BY) without requiring a separate sort operation. Return N: +** +** N>0: N terms of the ORDER BY clause are satisfied +** N==0: No terms of the ORDER BY clause are satisfied +** N<0: Unknown yet how many terms of ORDER BY might be satisfied. +** +** Note that processing for WHERE_GROUPBY and WHERE_DISTINCTBY is not as +** strict. With GROUP BY and DISTINCT the only requirement is that +** equivalent rows appear immediately adjacent to one another. GROUP BY +** and DISTINCT do not require rows to appear in any particular order as long +** as equivalent rows are grouped together. Thus for GROUP BY and DISTINCT +** the pOrderBy terms can be matched in any order. With ORDER BY, the +** pOrderBy terms must be matched in strict left-to-right order. +*/ +static i8 wherePathSatisfiesOrderBy( + WhereInfo *pWInfo, /* The WHERE clause */ + ExprList *pOrderBy, /* ORDER BY or GROUP BY or DISTINCT clause to check */ + WherePath *pPath, /* The WherePath to check */ + u16 wctrlFlags, /* WHERE_GROUPBY or _DISTINCTBY or _ORDERBY_LIMIT */ + u16 nLoop, /* Number of entries in pPath->aLoop[] */ + WhereLoop *pLast, /* Add this WhereLoop to the end of pPath->aLoop[] */ + Bitmask *pRevMask /* OUT: Mask of WhereLoops to run in reverse order */ +){ + u8 revSet; /* True if rev is known */ + u8 rev; /* Composite sort order */ + u8 revIdx; /* Index sort order */ + u8 isOrderDistinct; /* All prior WhereLoops are order-distinct */ + u8 distinctColumns; /* True if the loop has UNIQUE NOT NULL columns */ + u8 isMatch; /* iColumn matches a term of the ORDER BY clause */ + u16 eqOpMask; /* Allowed equality operators */ + u16 nKeyCol; /* Number of key columns in pIndex */ + u16 nColumn; /* Total number of ordered columns in the index */ + u16 nOrderBy; /* Number terms in the ORDER BY clause */ + int iLoop; /* Index of WhereLoop in pPath being processed */ + int i, j; /* Loop counters */ + int iCur; /* Cursor number for current WhereLoop */ + int iColumn; /* A column number within table iCur */ + WhereLoop *pLoop = 0; /* Current WhereLoop being processed. */ + WhereTerm *pTerm; /* A single term of the WHERE clause */ + Expr *pOBExpr; /* An expression from the ORDER BY clause */ + CollSeq *pColl; /* COLLATE function from an ORDER BY clause term */ + Index *pIndex; /* The index associated with pLoop */ + sqlite3 *db = pWInfo->pParse->db; /* Database connection */ + Bitmask obSat = 0; /* Mask of ORDER BY terms satisfied so far */ + Bitmask obDone; /* Mask of all ORDER BY terms */ + Bitmask orderDistinctMask; /* Mask of all well-ordered loops */ + Bitmask ready; /* Mask of inner loops */ + + /* + ** We say the WhereLoop is "one-row" if it generates no more than one + ** row of output. A WhereLoop is one-row if all of the following are true: + ** (a) All index columns match with WHERE_COLUMN_EQ. + ** (b) The index is unique + ** Any WhereLoop with an WHERE_COLUMN_EQ constraint on the rowid is one-row. + ** Every one-row WhereLoop will have the WHERE_ONEROW bit set in wsFlags. + ** + ** We say the WhereLoop is "order-distinct" if the set of columns from + ** that WhereLoop that are in the ORDER BY clause are different for every + ** row of the WhereLoop. Every one-row WhereLoop is automatically + ** order-distinct. A WhereLoop that has no columns in the ORDER BY clause + ** is not order-distinct. To be order-distinct is not quite the same as being + ** UNIQUE since a UNIQUE column or index can have multiple rows that + ** are NULL and NULL values are equivalent for the purpose of order-distinct. + ** To be order-distinct, the columns must be UNIQUE and NOT NULL. + ** + ** The rowid for a table is always UNIQUE and NOT NULL so whenever the + ** rowid appears in the ORDER BY clause, the corresponding WhereLoop is + ** automatically order-distinct. + */ + + assert( pOrderBy!=0 ); + if( nLoop && OptimizationDisabled(db, SQLITE_OrderByIdxJoin) ) return 0; + + nOrderBy = pOrderBy->nExpr; + testcase( nOrderBy==BMS-1 ); + if( nOrderBy>BMS-1 ) return 0; /* Cannot optimize overly large ORDER BYs */ + isOrderDistinct = 1; + obDone = MASKBIT(nOrderBy)-1; + orderDistinctMask = 0; + ready = 0; + eqOpMask = WO_EQ | WO_IS | WO_ISNULL; + if( wctrlFlags & (WHERE_ORDERBY_LIMIT|WHERE_ORDERBY_MAX|WHERE_ORDERBY_MIN) ){ + eqOpMask |= WO_IN; + } + for(iLoop=0; isOrderDistinct && obSat<obDone && iLoop<=nLoop; iLoop++){ + if( iLoop>0 ) ready |= pLoop->maskSelf; + if( iLoop<nLoop ){ + pLoop = pPath->aLoop[iLoop]; + if( wctrlFlags & WHERE_ORDERBY_LIMIT ) continue; + }else{ + pLoop = pLast; + } + if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ + if( pLoop->u.vtab.isOrdered + && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) + ){ + obSat = obDone; + } + break; + }else if( wctrlFlags & WHERE_DISTINCTBY ){ + pLoop->u.btree.nDistinctCol = 0; + } + iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor; + + /* Mark off any ORDER BY term X that is a column in the table of + ** the current loop for which there is term in the WHERE + ** clause of the form X IS NULL or X=? that reference only outer + ** loops. + */ + for(i=0; i<nOrderBy; i++){ + if( MASKBIT(i) & obSat ) continue; + pOBExpr = sqlite3ExprSkipCollateAndLikely(pOrderBy->a[i].pExpr); + if( NEVER(pOBExpr==0) ) continue; + if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue; + if( pOBExpr->iTable!=iCur ) continue; + pTerm = sqlite3WhereFindTerm(&pWInfo->sWC, iCur, pOBExpr->iColumn, + ~ready, eqOpMask, 0); + if( pTerm==0 ) continue; + if( pTerm->eOperator==WO_IN ){ + /* IN terms are only valid for sorting in the ORDER BY LIMIT + ** optimization, and then only if they are actually used + ** by the query plan */ + assert( wctrlFlags & + (WHERE_ORDERBY_LIMIT|WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX) ); + for(j=0; j<pLoop->nLTerm && pTerm!=pLoop->aLTerm[j]; j++){} + if( j>=pLoop->nLTerm ) continue; + } + if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && pOBExpr->iColumn>=0 ){ + Parse *pParse = pWInfo->pParse; + CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pOrderBy->a[i].pExpr); + CollSeq *pColl2 = sqlite3ExprCompareCollSeq(pParse, pTerm->pExpr); + assert( pColl1 ); + if( pColl2==0 || sqlite3StrICmp(pColl1->zName, pColl2->zName) ){ + continue; + } + testcase( pTerm->pExpr->op==TK_IS ); + } + obSat |= MASKBIT(i); + } + + if( (pLoop->wsFlags & WHERE_ONEROW)==0 ){ + if( pLoop->wsFlags & WHERE_IPK ){ + pIndex = 0; + nKeyCol = 0; + nColumn = 1; + }else if( (pIndex = pLoop->u.btree.pIndex)==0 || pIndex->bUnordered ){ + return 0; + }else{ + nKeyCol = pIndex->nKeyCol; + nColumn = pIndex->nColumn; + assert( nColumn==nKeyCol+1 || !HasRowid(pIndex->pTable) ); + assert( pIndex->aiColumn[nColumn-1]==XN_ROWID + || !HasRowid(pIndex->pTable)); + /* All relevant terms of the index must also be non-NULL in order + ** for isOrderDistinct to be true. So the isOrderDistint value + ** computed here might be a false positive. Corrections will be + ** made at tag-20210426-1 below */ + isOrderDistinct = IsUniqueIndex(pIndex) + && (pLoop->wsFlags & WHERE_SKIPSCAN)==0; + } + + /* Loop through all columns of the index and deal with the ones + ** that are not constrained by == or IN. + */ + rev = revSet = 0; + distinctColumns = 0; + for(j=0; j<nColumn; j++){ + u8 bOnce = 1; /* True to run the ORDER BY search loop */ + + assert( j>=pLoop->u.btree.nEq + || (pLoop->aLTerm[j]==0)==(j<pLoop->nSkip) + ); + if( j<pLoop->u.btree.nEq && j>=pLoop->nSkip ){ + u16 eOp = pLoop->aLTerm[j]->eOperator; + + /* Skip over == and IS and ISNULL terms. (Also skip IN terms when + ** doing WHERE_ORDERBY_LIMIT processing). Except, IS and ISNULL + ** terms imply that the index is not UNIQUE NOT NULL in which case + ** the loop need to be marked as not order-distinct because it can + ** have repeated NULL rows. + ** + ** If the current term is a column of an ((?,?) IN (SELECT...)) + ** expression for which the SELECT returns more than one column, + ** check that it is the only column used by this loop. Otherwise, + ** if it is one of two or more, none of the columns can be + ** considered to match an ORDER BY term. + */ + if( (eOp & eqOpMask)!=0 ){ + if( eOp & (WO_ISNULL|WO_IS) ){ + testcase( eOp & WO_ISNULL ); + testcase( eOp & WO_IS ); + testcase( isOrderDistinct ); + isOrderDistinct = 0; + } + continue; + }else if( ALWAYS(eOp & WO_IN) ){ + /* ALWAYS() justification: eOp is an equality operator due to the + ** j<pLoop->u.btree.nEq constraint above. Any equality other + ** than WO_IN is captured by the previous "if". So this one + ** always has to be WO_IN. */ + Expr *pX = pLoop->aLTerm[j]->pExpr; + for(i=j+1; i<pLoop->u.btree.nEq; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + assert( (pLoop->aLTerm[i]->eOperator & WO_IN) ); + bOnce = 0; + break; + } + } + } + } + + /* Get the column number in the table (iColumn) and sort order + ** (revIdx) for the j-th column of the index. + */ + if( pIndex ){ + iColumn = pIndex->aiColumn[j]; + revIdx = pIndex->aSortOrder[j] & KEYINFO_ORDER_DESC; + if( iColumn==pIndex->pTable->iPKey ) iColumn = XN_ROWID; + }else{ + iColumn = XN_ROWID; + revIdx = 0; + } + + /* An unconstrained column that might be NULL means that this + ** WhereLoop is not well-ordered. tag-20210426-1 + */ + if( isOrderDistinct ){ + if( iColumn>=0 + && j>=pLoop->u.btree.nEq + && pIndex->pTable->aCol[iColumn].notNull==0 + ){ + isOrderDistinct = 0; + } + if( iColumn==XN_EXPR ){ + isOrderDistinct = 0; + } + } + + /* Find the ORDER BY term that corresponds to the j-th column + ** of the index and mark that ORDER BY term off + */ + isMatch = 0; + for(i=0; bOnce && i<nOrderBy; i++){ + if( MASKBIT(i) & obSat ) continue; + pOBExpr = sqlite3ExprSkipCollateAndLikely(pOrderBy->a[i].pExpr); + testcase( wctrlFlags & WHERE_GROUPBY ); + testcase( wctrlFlags & WHERE_DISTINCTBY ); + if( NEVER(pOBExpr==0) ) continue; + if( (wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY))==0 ) bOnce = 0; + if( iColumn>=XN_ROWID ){ + if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue; + if( pOBExpr->iTable!=iCur ) continue; + if( pOBExpr->iColumn!=iColumn ) continue; + }else{ + Expr *pIxExpr = pIndex->aColExpr->a[j].pExpr; + if( sqlite3ExprCompareSkip(pOBExpr, pIxExpr, iCur) ){ + continue; + } + } + if( iColumn!=XN_ROWID ){ + pColl = sqlite3ExprNNCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr); + if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ) continue; + } + if( wctrlFlags & WHERE_DISTINCTBY ){ + pLoop->u.btree.nDistinctCol = j+1; + } + isMatch = 1; + break; + } + if( isMatch && (wctrlFlags & WHERE_GROUPBY)==0 ){ + /* Make sure the sort order is compatible in an ORDER BY clause. + ** Sort order is irrelevant for a GROUP BY clause. */ + if( revSet ){ + if( (rev ^ revIdx) + != (pOrderBy->a[i].fg.sortFlags&KEYINFO_ORDER_DESC) + ){ + isMatch = 0; + } + }else{ + rev = revIdx ^ (pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC); + if( rev ) *pRevMask |= MASKBIT(iLoop); + revSet = 1; + } + } + if( isMatch && (pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL) ){ + if( j==pLoop->u.btree.nEq ){ + pLoop->wsFlags |= WHERE_BIGNULL_SORT; + }else{ + isMatch = 0; + } + } + if( isMatch ){ + if( iColumn==XN_ROWID ){ + testcase( distinctColumns==0 ); + distinctColumns = 1; + } + obSat |= MASKBIT(i); + }else{ + /* No match found */ + if( j==0 || j<nKeyCol ){ + testcase( isOrderDistinct!=0 ); + isOrderDistinct = 0; + } + break; + } + } /* end Loop over all index columns */ + if( distinctColumns ){ + testcase( isOrderDistinct==0 ); + isOrderDistinct = 1; + } + } /* end-if not one-row */ + + /* Mark off any other ORDER BY terms that reference pLoop */ + if( isOrderDistinct ){ + orderDistinctMask |= pLoop->maskSelf; + for(i=0; i<nOrderBy; i++){ + Expr *p; + Bitmask mTerm; + if( MASKBIT(i) & obSat ) continue; + p = pOrderBy->a[i].pExpr; + mTerm = sqlite3WhereExprUsage(&pWInfo->sMaskSet,p); + if( mTerm==0 && !sqlite3ExprIsConstant(p) ) continue; + if( (mTerm&~orderDistinctMask)==0 ){ + obSat |= MASKBIT(i); + } + } + } + } /* End the loop over all WhereLoops from outer-most down to inner-most */ + if( obSat==obDone ) return (i8)nOrderBy; + if( !isOrderDistinct ){ + for(i=nOrderBy-1; i>0; i--){ + Bitmask m = ALWAYS(i<BMS) ? MASKBIT(i) - 1 : 0; + if( (obSat&m)==m ) return i; + } + return 0; + } + return -1; +} + + +/* +** If the WHERE_GROUPBY flag is set in the mask passed to sqlite3WhereBegin(), +** the planner assumes that the specified pOrderBy list is actually a GROUP +** BY clause - and so any order that groups rows as required satisfies the +** request. +** +** Normally, in this case it is not possible for the caller to determine +** whether or not the rows are really being delivered in sorted order, or +** just in some other order that provides the required grouping. However, +** if the WHERE_SORTBYGROUP flag is also passed to sqlite3WhereBegin(), then +** this function may be called on the returned WhereInfo object. It returns +** true if the rows really will be sorted in the specified order, or false +** otherwise. +** +** For example, assuming: +** +** CREATE INDEX i1 ON t1(x, Y); +** +** then +** +** SELECT * FROM t1 GROUP BY x,y ORDER BY x,y; -- IsSorted()==1 +** SELECT * FROM t1 GROUP BY y,x ORDER BY y,x; -- IsSorted()==0 +*/ +SQLITE_PRIVATE int sqlite3WhereIsSorted(WhereInfo *pWInfo){ + assert( pWInfo->wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY) ); + assert( pWInfo->wctrlFlags & WHERE_SORTBYGROUP ); + return pWInfo->sorted; +} + +#ifdef WHERETRACE_ENABLED +/* For debugging use only: */ +static const char *wherePathName(WherePath *pPath, int nLoop, WhereLoop *pLast){ + static char zName[65]; + int i; + for(i=0; i<nLoop; i++){ zName[i] = pPath->aLoop[i]->cId; } + if( pLast ) zName[i++] = pLast->cId; + zName[i] = 0; + return zName; +} +#endif + +/* +** Return the cost of sorting nRow rows, assuming that the keys have +** nOrderby columns and that the first nSorted columns are already in +** order. +*/ +static LogEst whereSortingCost( + WhereInfo *pWInfo, /* Query planning context */ + LogEst nRow, /* Estimated number of rows to sort */ + int nOrderBy, /* Number of ORDER BY clause terms */ + int nSorted /* Number of initial ORDER BY terms naturally in order */ +){ + /* Estimated cost of a full external sort, where N is + ** the number of rows to sort is: + ** + ** cost = (K * N * log(N)). + ** + ** Or, if the order-by clause has X terms but only the last Y + ** terms are out of order, then block-sorting will reduce the + ** sorting cost to: + ** + ** cost = (K * N * log(N)) * (Y/X) + ** + ** The constant K is at least 2.0 but will be larger if there are a + ** large number of columns to be sorted, as the sorting time is + ** proportional to the amount of content to be sorted. The algorithm + ** does not currently distinguish between fat columns (BLOBs and TEXTs) + ** and skinny columns (INTs). It just uses the number of columns as + ** an approximation for the row width. + ** + ** And extra factor of 2.0 or 3.0 is added to the sorting cost if the sort + ** is built using OP_IdxInsert and OP_Sort rather than with OP_SorterInsert. + */ + LogEst rSortCost, nCol; + assert( pWInfo->pSelect!=0 ); + assert( pWInfo->pSelect->pEList!=0 ); + /* TUNING: sorting cost proportional to the number of output columns: */ + nCol = sqlite3LogEst((pWInfo->pSelect->pEList->nExpr+59)/30); + rSortCost = nRow + nCol; + if( nSorted>0 ){ + /* Scale the result by (Y/X) */ + rSortCost += sqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66; + } + + /* Multiple by log(M) where M is the number of output rows. + ** Use the LIMIT for M if it is smaller. Or if this sort is for + ** a DISTINCT operator, M will be the number of distinct output + ** rows, so fudge it downwards a bit. + */ + if( (pWInfo->wctrlFlags & WHERE_USE_LIMIT)!=0 ){ + rSortCost += 10; /* TUNING: Extra 2.0x if using LIMIT */ + if( nSorted!=0 ){ + rSortCost += 6; /* TUNING: Extra 1.5x if also using partial sort */ + } + if( pWInfo->iLimit<nRow ){ + nRow = pWInfo->iLimit; + } + }else if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT) ){ + /* TUNING: In the sort for a DISTINCT operator, assume that the DISTINCT + ** reduces the number of output rows by a factor of 2 */ + if( nRow>10 ){ nRow -= 10; assert( 10==sqlite3LogEst(2) ); } + } + rSortCost += estLog(nRow); + return rSortCost; +} + +/* +** Given the list of WhereLoop objects at pWInfo->pLoops, this routine +** attempts to find the lowest cost path that visits each WhereLoop +** once. This path is then loaded into the pWInfo->a[].pWLoop fields. +** +** Assume that the total number of output rows that will need to be sorted +** will be nRowEst (in the 10*log2 representation). Or, ignore sorting +** costs if nRowEst==0. +** +** Return SQLITE_OK on success or SQLITE_NOMEM of a memory allocation +** error occurs. +*/ +static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ + int mxChoice; /* Maximum number of simultaneous paths tracked */ + int nLoop; /* Number of terms in the join */ + Parse *pParse; /* Parsing context */ + int iLoop; /* Loop counter over the terms of the join */ + int ii, jj; /* Loop counters */ + int mxI = 0; /* Index of next entry to replace */ + int nOrderBy; /* Number of ORDER BY clause terms */ + LogEst mxCost = 0; /* Maximum cost of a set of paths */ + LogEst mxUnsorted = 0; /* Maximum unsorted cost of a set of path */ + int nTo, nFrom; /* Number of valid entries in aTo[] and aFrom[] */ + WherePath *aFrom; /* All nFrom paths at the previous level */ + WherePath *aTo; /* The nTo best paths at the current level */ + WherePath *pFrom; /* An element of aFrom[] that we are working on */ + WherePath *pTo; /* An element of aTo[] that we are working on */ + WhereLoop *pWLoop; /* One of the WhereLoop objects */ + WhereLoop **pX; /* Used to divy up the pSpace memory */ + LogEst *aSortCost = 0; /* Sorting and partial sorting costs */ + char *pSpace; /* Temporary memory used by this routine */ + int nSpace; /* Bytes of space allocated at pSpace */ + + pParse = pWInfo->pParse; + nLoop = pWInfo->nLevel; + /* TUNING: For simple queries, only the best path is tracked. + ** For 2-way joins, the 5 best paths are followed. + ** For joins of 3 or more tables, track the 10 best paths */ + mxChoice = (nLoop<=1) ? 1 : (nLoop==2 ? 5 : 10); + assert( nLoop<=pWInfo->pTabList->nSrc ); + WHERETRACE(0x002, ("---- begin solver. (nRowEst=%d, nQueryLoop=%d)\n", + nRowEst, pParse->nQueryLoop)); + + /* If nRowEst is zero and there is an ORDER BY clause, ignore it. In this + ** case the purpose of this call is to estimate the number of rows returned + ** by the overall query. Once this estimate has been obtained, the caller + ** will invoke this function a second time, passing the estimate as the + ** nRowEst parameter. */ + if( pWInfo->pOrderBy==0 || nRowEst==0 ){ + nOrderBy = 0; + }else{ + nOrderBy = pWInfo->pOrderBy->nExpr; + } + + /* Allocate and initialize space for aTo, aFrom and aSortCost[] */ + nSpace = (sizeof(WherePath)+sizeof(WhereLoop*)*nLoop)*mxChoice*2; + nSpace += sizeof(LogEst) * nOrderBy; + pSpace = sqlite3StackAllocRawNN(pParse->db, nSpace); + if( pSpace==0 ) return SQLITE_NOMEM_BKPT; + aTo = (WherePath*)pSpace; + aFrom = aTo+mxChoice; + memset(aFrom, 0, sizeof(aFrom[0])); + pX = (WhereLoop**)(aFrom+mxChoice); + for(ii=mxChoice*2, pFrom=aTo; ii>0; ii--, pFrom++, pX += nLoop){ + pFrom->aLoop = pX; + } + if( nOrderBy ){ + /* If there is an ORDER BY clause and it is not being ignored, set up + ** space for the aSortCost[] array. Each element of the aSortCost array + ** is either zero - meaning it has not yet been initialized - or the + ** cost of sorting nRowEst rows of data where the first X terms of + ** the ORDER BY clause are already in order, where X is the array + ** index. */ + aSortCost = (LogEst*)pX; + memset(aSortCost, 0, sizeof(LogEst) * nOrderBy); + } + assert( aSortCost==0 || &pSpace[nSpace]==(char*)&aSortCost[nOrderBy] ); + assert( aSortCost!=0 || &pSpace[nSpace]==(char*)pX ); + + /* Seed the search with a single WherePath containing zero WhereLoops. + ** + ** TUNING: Do not let the number of iterations go above 28. If the cost + ** of computing an automatic index is not paid back within the first 28 + ** rows, then do not use the automatic index. */ + aFrom[0].nRow = MIN(pParse->nQueryLoop, 48); assert( 48==sqlite3LogEst(28) ); + nFrom = 1; + assert( aFrom[0].isOrdered==0 ); + if( nOrderBy ){ + /* If nLoop is zero, then there are no FROM terms in the query. Since + ** in this case the query may return a maximum of one row, the results + ** are already in the requested order. Set isOrdered to nOrderBy to + ** indicate this. Or, if nLoop is greater than zero, set isOrdered to + ** -1, indicating that the result set may or may not be ordered, + ** depending on the loops added to the current plan. */ + aFrom[0].isOrdered = nLoop>0 ? -1 : nOrderBy; + } + + /* Compute successively longer WherePaths using the previous generation + ** of WherePaths as the basis for the next. Keep track of the mxChoice + ** best paths at each generation */ + for(iLoop=0; iLoop<nLoop; iLoop++){ + nTo = 0; + for(ii=0, pFrom=aFrom; ii<nFrom; ii++, pFrom++){ + for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){ + LogEst nOut; /* Rows visited by (pFrom+pWLoop) */ + LogEst rCost; /* Cost of path (pFrom+pWLoop) */ + LogEst rUnsorted; /* Unsorted cost of (pFrom+pWLoop) */ + i8 isOrdered; /* isOrdered for (pFrom+pWLoop) */ + Bitmask maskNew; /* Mask of src visited by (..) */ + Bitmask revMask; /* Mask of rev-order loops for (..) */ + + if( (pWLoop->prereq & ~pFrom->maskLoop)!=0 ) continue; + if( (pWLoop->maskSelf & pFrom->maskLoop)!=0 ) continue; + if( (pWLoop->wsFlags & WHERE_AUTO_INDEX)!=0 && pFrom->nRow<3 ){ + /* Do not use an automatic index if the this loop is expected + ** to run less than 1.25 times. It is tempting to also exclude + ** automatic index usage on an outer loop, but sometimes an automatic + ** index is useful in the outer loop of a correlated subquery. */ + assert( 10==sqlite3LogEst(2) ); + continue; + } + + /* At this point, pWLoop is a candidate to be the next loop. + ** Compute its cost */ + rUnsorted = sqlite3LogEstAdd(pWLoop->rSetup,pWLoop->rRun + pFrom->nRow); + rUnsorted = sqlite3LogEstAdd(rUnsorted, pFrom->rUnsorted); + nOut = pFrom->nRow + pWLoop->nOut; + maskNew = pFrom->maskLoop | pWLoop->maskSelf; + isOrdered = pFrom->isOrdered; + if( isOrdered<0 ){ + revMask = 0; + isOrdered = wherePathSatisfiesOrderBy(pWInfo, + pWInfo->pOrderBy, pFrom, pWInfo->wctrlFlags, + iLoop, pWLoop, &revMask); + }else{ + revMask = pFrom->revLoop; + } + if( isOrdered>=0 && isOrdered<nOrderBy ){ + if( aSortCost[isOrdered]==0 ){ + aSortCost[isOrdered] = whereSortingCost( + pWInfo, nRowEst, nOrderBy, isOrdered + ); + } + /* TUNING: Add a small extra penalty (3) to sorting as an + ** extra encouragement to the query planner to select a plan + ** where the rows emerge in the correct order without any sorting + ** required. */ + rCost = sqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]) + 3; + + WHERETRACE(0x002, + ("---- sort cost=%-3d (%d/%d) increases cost %3d to %-3d\n", + aSortCost[isOrdered], (nOrderBy-isOrdered), nOrderBy, + rUnsorted, rCost)); + }else{ + rCost = rUnsorted; + rUnsorted -= 2; /* TUNING: Slight bias in favor of no-sort plans */ + } + + /* TUNING: A full-scan of a VIEW or subquery in the outer loop + ** is not so bad. */ + if( iLoop==0 && (pWLoop->wsFlags & WHERE_VIEWSCAN)!=0 && nLoop>1 ){ + rCost += -10; + nOut += -30; + WHERETRACE(0x80,("VIEWSCAN cost reduction for %c\n",pWLoop->cId)); + } + + /* Check to see if pWLoop should be added to the set of + ** mxChoice best-so-far paths. + ** + ** First look for an existing path among best-so-far paths + ** that covers the same set of loops and has the same isOrdered + ** setting as the current path candidate. + ** + ** The term "((pTo->isOrdered^isOrdered)&0x80)==0" is equivalent + ** to (pTo->isOrdered==(-1))==(isOrdered==(-1))" for the range + ** of legal values for isOrdered, -1..64. + */ + for(jj=0, pTo=aTo; jj<nTo; jj++, pTo++){ + if( pTo->maskLoop==maskNew + && ((pTo->isOrdered^isOrdered)&0x80)==0 + ){ + testcase( jj==nTo-1 ); + break; + } + } + if( jj>=nTo ){ + /* None of the existing best-so-far paths match the candidate. */ + if( nTo>=mxChoice + && (rCost>mxCost || (rCost==mxCost && rUnsorted>=mxUnsorted)) + ){ + /* The current candidate is no better than any of the mxChoice + ** paths currently in the best-so-far buffer. So discard + ** this candidate as not viable. */ +#ifdef WHERETRACE_ENABLED /* 0x4 */ + if( sqlite3WhereTrace&0x4 ){ + sqlite3DebugPrintf("Skip %s cost=%-3d,%3d,%3d order=%c\n", + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, + isOrdered>=0 ? isOrdered+'0' : '?'); + } +#endif + continue; + } + /* If we reach this points it means that the new candidate path + ** needs to be added to the set of best-so-far paths. */ + if( nTo<mxChoice ){ + /* Increase the size of the aTo set by one */ + jj = nTo++; + }else{ + /* New path replaces the prior worst to keep count below mxChoice */ + jj = mxI; + } + pTo = &aTo[jj]; +#ifdef WHERETRACE_ENABLED /* 0x4 */ + if( sqlite3WhereTrace&0x4 ){ + sqlite3DebugPrintf("New %s cost=%-3d,%3d,%3d order=%c\n", + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, + isOrdered>=0 ? isOrdered+'0' : '?'); + } +#endif + }else{ + /* Control reaches here if best-so-far path pTo=aTo[jj] covers the + ** same set of loops and has the same isOrdered setting as the + ** candidate path. Check to see if the candidate should replace + ** pTo or if the candidate should be skipped. + ** + ** The conditional is an expanded vector comparison equivalent to: + ** (pTo->rCost,pTo->nRow,pTo->rUnsorted) <= (rCost,nOut,rUnsorted) + */ + if( pTo->rCost<rCost + || (pTo->rCost==rCost + && (pTo->nRow<nOut + || (pTo->nRow==nOut && pTo->rUnsorted<=rUnsorted) + ) + ) + ){ +#ifdef WHERETRACE_ENABLED /* 0x4 */ + if( sqlite3WhereTrace&0x4 ){ + sqlite3DebugPrintf( + "Skip %s cost=%-3d,%3d,%3d order=%c", + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, + isOrdered>=0 ? isOrdered+'0' : '?'); + sqlite3DebugPrintf(" vs %s cost=%-3d,%3d,%3d order=%c\n", + wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, + pTo->rUnsorted, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); + } +#endif + /* Discard the candidate path from further consideration */ + testcase( pTo->rCost==rCost ); + continue; + } + testcase( pTo->rCost==rCost+1 ); + /* Control reaches here if the candidate path is better than the + ** pTo path. Replace pTo with the candidate. */ +#ifdef WHERETRACE_ENABLED /* 0x4 */ + if( sqlite3WhereTrace&0x4 ){ + sqlite3DebugPrintf( + "Update %s cost=%-3d,%3d,%3d order=%c", + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, + isOrdered>=0 ? isOrdered+'0' : '?'); + sqlite3DebugPrintf(" was %s cost=%-3d,%3d,%3d order=%c\n", + wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, + pTo->rUnsorted, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); + } +#endif + } + /* pWLoop is a winner. Add it to the set of best so far */ + pTo->maskLoop = pFrom->maskLoop | pWLoop->maskSelf; + pTo->revLoop = revMask; + pTo->nRow = nOut; + pTo->rCost = rCost; + pTo->rUnsorted = rUnsorted; + pTo->isOrdered = isOrdered; + memcpy(pTo->aLoop, pFrom->aLoop, sizeof(WhereLoop*)*iLoop); + pTo->aLoop[iLoop] = pWLoop; + if( nTo>=mxChoice ){ + mxI = 0; + mxCost = aTo[0].rCost; + mxUnsorted = aTo[0].nRow; + for(jj=1, pTo=&aTo[1]; jj<mxChoice; jj++, pTo++){ + if( pTo->rCost>mxCost + || (pTo->rCost==mxCost && pTo->rUnsorted>mxUnsorted) + ){ + mxCost = pTo->rCost; + mxUnsorted = pTo->rUnsorted; + mxI = jj; + } + } + } + } + } + +#ifdef WHERETRACE_ENABLED /* >=2 */ + if( sqlite3WhereTrace & 0x02 ){ + sqlite3DebugPrintf("---- after round %d ----\n", iLoop); + for(ii=0, pTo=aTo; ii<nTo; ii++, pTo++){ + sqlite3DebugPrintf(" %s cost=%-3d nrow=%-3d order=%c", + wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, + pTo->isOrdered>=0 ? (pTo->isOrdered+'0') : '?'); + if( pTo->isOrdered>0 ){ + sqlite3DebugPrintf(" rev=0x%llx\n", pTo->revLoop); + }else{ + sqlite3DebugPrintf("\n"); + } + } + } +#endif + + /* Swap the roles of aFrom and aTo for the next generation */ + pFrom = aTo; + aTo = aFrom; + aFrom = pFrom; + nFrom = nTo; + } + + if( nFrom==0 ){ + sqlite3ErrorMsg(pParse, "no query solution"); + sqlite3StackFreeNN(pParse->db, pSpace); + return SQLITE_ERROR; + } + + /* Find the lowest cost path. pFrom will be left pointing to that path */ + pFrom = aFrom; + for(ii=1; ii<nFrom; ii++){ + if( pFrom->rCost>aFrom[ii].rCost ) pFrom = &aFrom[ii]; + } + assert( pWInfo->nLevel==nLoop ); + /* Load the lowest cost path into pWInfo */ + for(iLoop=0; iLoop<nLoop; iLoop++){ + WhereLevel *pLevel = pWInfo->a + iLoop; + pLevel->pWLoop = pWLoop = pFrom->aLoop[iLoop]; + pLevel->iFrom = pWLoop->iTab; + pLevel->iTabCur = pWInfo->pTabList->a[pLevel->iFrom].iCursor; + } + if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 + && (pWInfo->wctrlFlags & WHERE_DISTINCTBY)==0 + && pWInfo->eDistinct==WHERE_DISTINCT_NOOP + && nRowEst + ){ + Bitmask notUsed; + int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pResultSet, pFrom, + WHERE_DISTINCTBY, nLoop-1, pFrom->aLoop[nLoop-1], &notUsed); + if( rc==pWInfo->pResultSet->nExpr ){ + pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; + } + } + pWInfo->bOrderedInnerLoop = 0; + if( pWInfo->pOrderBy ){ + pWInfo->nOBSat = pFrom->isOrdered; + if( pWInfo->wctrlFlags & WHERE_DISTINCTBY ){ + if( pFrom->isOrdered==pWInfo->pOrderBy->nExpr ){ + pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; + } + if( pWInfo->pSelect->pOrderBy + && pWInfo->nOBSat > pWInfo->pSelect->pOrderBy->nExpr ){ + pWInfo->nOBSat = pWInfo->pSelect->pOrderBy->nExpr; + } + }else{ + pWInfo->revMask = pFrom->revLoop; + if( pWInfo->nOBSat<=0 ){ + pWInfo->nOBSat = 0; + if( nLoop>0 ){ + u32 wsFlags = pFrom->aLoop[nLoop-1]->wsFlags; + if( (wsFlags & WHERE_ONEROW)==0 + && (wsFlags&(WHERE_IPK|WHERE_COLUMN_IN))!=(WHERE_IPK|WHERE_COLUMN_IN) + ){ + Bitmask m = 0; + int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, pFrom, + WHERE_ORDERBY_LIMIT, nLoop-1, pFrom->aLoop[nLoop-1], &m); + testcase( wsFlags & WHERE_IPK ); + testcase( wsFlags & WHERE_COLUMN_IN ); + if( rc==pWInfo->pOrderBy->nExpr ){ + pWInfo->bOrderedInnerLoop = 1; + pWInfo->revMask = m; + } + } + } + }else if( nLoop + && pWInfo->nOBSat==1 + && (pWInfo->wctrlFlags & (WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX))!=0 + ){ + pWInfo->bOrderedInnerLoop = 1; + } + } + if( (pWInfo->wctrlFlags & WHERE_SORTBYGROUP) + && pWInfo->nOBSat==pWInfo->pOrderBy->nExpr && nLoop>0 + ){ + Bitmask revMask = 0; + int nOrder = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, + pFrom, 0, nLoop-1, pFrom->aLoop[nLoop-1], &revMask + ); + assert( pWInfo->sorted==0 ); + if( nOrder==pWInfo->pOrderBy->nExpr ){ + pWInfo->sorted = 1; + pWInfo->revMask = revMask; + } + } + } + + + pWInfo->nRowOut = pFrom->nRow; + + /* Free temporary memory and return success */ + sqlite3StackFreeNN(pParse->db, pSpace); + return SQLITE_OK; +} + +/* +** Most queries use only a single table (they are not joins) and have +** simple == constraints against indexed fields. This routine attempts +** to plan those simple cases using much less ceremony than the +** general-purpose query planner, and thereby yield faster sqlite3_prepare() +** times for the common case. +** +** Return non-zero on success, if this query can be handled by this +** no-frills query planner. Return zero if this query needs the +** general-purpose query planner. +*/ +static int whereShortCut(WhereLoopBuilder *pBuilder){ + WhereInfo *pWInfo; + SrcItem *pItem; + WhereClause *pWC; + WhereTerm *pTerm; + WhereLoop *pLoop; + int iCur; + int j; + Table *pTab; + Index *pIdx; + WhereScan scan; + + pWInfo = pBuilder->pWInfo; + if( pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE ) return 0; + assert( pWInfo->pTabList->nSrc>=1 ); + pItem = pWInfo->pTabList->a; + pTab = pItem->pTab; + if( IsVirtual(pTab) ) return 0; + if( pItem->fg.isIndexedBy || pItem->fg.notIndexed ){ + testcase( pItem->fg.isIndexedBy ); + testcase( pItem->fg.notIndexed ); + return 0; + } + iCur = pItem->iCursor; + pWC = &pWInfo->sWC; + pLoop = pBuilder->pNew; + pLoop->wsFlags = 0; + pLoop->nSkip = 0; + pTerm = whereScanInit(&scan, pWC, iCur, -1, WO_EQ|WO_IS, 0); + while( pTerm && pTerm->prereqRight ) pTerm = whereScanNext(&scan); + if( pTerm ){ + testcase( pTerm->eOperator & WO_IS ); + pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW; + pLoop->aLTerm[0] = pTerm; + pLoop->nLTerm = 1; + pLoop->u.btree.nEq = 1; + /* TUNING: Cost of a rowid lookup is 10 */ + pLoop->rRun = 33; /* 33==sqlite3LogEst(10) */ + }else{ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int opMask; + assert( pLoop->aLTermSpace==pLoop->aLTerm ); + if( !IsUniqueIndex(pIdx) + || pIdx->pPartIdxWhere!=0 + || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace) + ) continue; + opMask = pIdx->uniqNotNull ? (WO_EQ|WO_IS) : WO_EQ; + for(j=0; j<pIdx->nKeyCol; j++){ + pTerm = whereScanInit(&scan, pWC, iCur, j, opMask, pIdx); + while( pTerm && pTerm->prereqRight ) pTerm = whereScanNext(&scan); + if( pTerm==0 ) break; + testcase( pTerm->eOperator & WO_IS ); + pLoop->aLTerm[j] = pTerm; + } + if( j!=pIdx->nKeyCol ) continue; + pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_ONEROW|WHERE_INDEXED; + if( pIdx->isCovering || (pItem->colUsed & pIdx->colNotIdxed)==0 ){ + pLoop->wsFlags |= WHERE_IDX_ONLY; + } + pLoop->nLTerm = j; + pLoop->u.btree.nEq = j; + pLoop->u.btree.pIndex = pIdx; + /* TUNING: Cost of a unique index lookup is 15 */ + pLoop->rRun = 39; /* 39==sqlite3LogEst(15) */ + break; + } + } + if( pLoop->wsFlags ){ + pLoop->nOut = (LogEst)1; + pWInfo->a[0].pWLoop = pLoop; + assert( pWInfo->sMaskSet.n==1 && iCur==pWInfo->sMaskSet.ix[0] ); + pLoop->maskSelf = 1; /* sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur); */ + pWInfo->a[0].iTabCur = iCur; + pWInfo->nRowOut = 1; + if( pWInfo->pOrderBy ) pWInfo->nOBSat = pWInfo->pOrderBy->nExpr; + if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ + pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; + } + if( scan.iEquiv>1 ) pLoop->wsFlags |= WHERE_TRANSCONS; +#ifdef SQLITE_DEBUG + pLoop->cId = '0'; +#endif +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x02 ){ + sqlite3DebugPrintf("whereShortCut() used to compute solution\n"); + } +#endif + return 1; + } + return 0; +} + +/* +** Helper function for exprIsDeterministic(). +*/ +static int exprNodeIsDeterministic(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_FUNCTION && ExprHasProperty(pExpr, EP_ConstFunc)==0 ){ + pWalker->eCode = 0; + return WRC_Abort; + } + return WRC_Continue; +} + +/* +** Return true if the expression contains no non-deterministic SQL +** functions. Do not consider non-deterministic SQL functions that are +** part of sub-select statements. +*/ +static int exprIsDeterministic(Expr *p){ + Walker w; + memset(&w, 0, sizeof(w)); + w.eCode = 1; + w.xExprCallback = exprNodeIsDeterministic; + w.xSelectCallback = sqlite3SelectWalkFail; + sqlite3WalkExpr(&w, p); + return w.eCode; +} + + +#ifdef WHERETRACE_ENABLED +/* +** Display all WhereLoops in pWInfo +*/ +static void showAllWhereLoops(WhereInfo *pWInfo, WhereClause *pWC){ + if( sqlite3WhereTrace ){ /* Display all of the WhereLoop objects */ + WhereLoop *p; + int i; + static const char zLabel[] = "0123456789abcdefghijklmnopqrstuvwyxz" + "ABCDEFGHIJKLMNOPQRSTUVWYXZ"; + for(p=pWInfo->pLoops, i=0; p; p=p->pNextLoop, i++){ + p->cId = zLabel[i%(sizeof(zLabel)-1)]; + sqlite3WhereLoopPrint(p, pWC); + } + } +} +# define WHERETRACE_ALL_LOOPS(W,C) showAllWhereLoops(W,C) +#else +# define WHERETRACE_ALL_LOOPS(W,C) +#endif + +/* Attempt to omit tables from a join that do not affect the result. +** For a table to not affect the result, the following must be true: +** +** 1) The query must not be an aggregate. +** 2) The table must be the RHS of a LEFT JOIN. +** 3) Either the query must be DISTINCT, or else the ON or USING clause +** must contain a constraint that limits the scan of the table to +** at most a single row. +** 4) The table must not be referenced by any part of the query apart +** from its own USING or ON clause. +** 5) The table must not have an inner-join ON or USING clause if there is +** a RIGHT JOIN anywhere in the query. Otherwise the ON/USING clause +** might move from the right side to the left side of the RIGHT JOIN. +** Note: Due to (2), this condition can only arise if the table is +** the right-most table of a subquery that was flattened into the +** main query and that subquery was the right-hand operand of an +** inner join that held an ON or USING clause. +** +** For example, given: +** +** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1); +** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2); +** CREATE TABLE t3(ipk INTEGER PRIMARY KEY, v3); +** +** then table t2 can be omitted from the following: +** +** SELECT v1, v3 FROM t1 +** LEFT JOIN t2 ON (t1.ipk=t2.ipk) +** LEFT JOIN t3 ON (t1.ipk=t3.ipk) +** +** or from: +** +** SELECT DISTINCT v1, v3 FROM t1 +** LEFT JOIN t2 +** LEFT JOIN t3 ON (t1.ipk=t3.ipk) +*/ +static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( + WhereInfo *pWInfo, + Bitmask notReady +){ + int i; + Bitmask tabUsed; + int hasRightJoin; + + /* Preconditions checked by the caller */ + assert( pWInfo->nLevel>=2 ); + assert( OptimizationEnabled(pWInfo->pParse->db, SQLITE_OmitNoopJoin) ); + + /* These two preconditions checked by the caller combine to guarantee + ** condition (1) of the header comment */ + assert( pWInfo->pResultSet!=0 ); + assert( 0==(pWInfo->wctrlFlags & WHERE_AGG_DISTINCT) ); + + tabUsed = sqlite3WhereExprListUsage(&pWInfo->sMaskSet, pWInfo->pResultSet); + if( pWInfo->pOrderBy ){ + tabUsed |= sqlite3WhereExprListUsage(&pWInfo->sMaskSet, pWInfo->pOrderBy); + } + hasRightJoin = (pWInfo->pTabList->a[0].fg.jointype & JT_LTORJ)!=0; + for(i=pWInfo->nLevel-1; i>=1; i--){ + WhereTerm *pTerm, *pEnd; + SrcItem *pItem; + WhereLoop *pLoop; + pLoop = pWInfo->a[i].pWLoop; + pItem = &pWInfo->pTabList->a[pLoop->iTab]; + if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))!=JT_LEFT ) continue; + if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)==0 + && (pLoop->wsFlags & WHERE_ONEROW)==0 + ){ + continue; + } + if( (tabUsed & pLoop->maskSelf)!=0 ) continue; + pEnd = pWInfo->sWC.a + pWInfo->sWC.nTerm; + for(pTerm=pWInfo->sWC.a; pTerm<pEnd; pTerm++){ + if( (pTerm->prereqAll & pLoop->maskSelf)!=0 ){ + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON) + || pTerm->pExpr->w.iJoin!=pItem->iCursor + ){ + break; + } + } + if( hasRightJoin + && ExprHasProperty(pTerm->pExpr, EP_InnerON) + && pTerm->pExpr->w.iJoin==pItem->iCursor + ){ + break; /* restriction (5) */ + } + } + if( pTerm<pEnd ) continue; + WHERETRACE(0xffffffff, ("-> drop loop %c not used\n", pLoop->cId)); + notReady &= ~pLoop->maskSelf; + for(pTerm=pWInfo->sWC.a; pTerm<pEnd; pTerm++){ + if( (pTerm->prereqAll & pLoop->maskSelf)!=0 ){ + pTerm->wtFlags |= TERM_CODED; + } + } + if( i!=pWInfo->nLevel-1 ){ + int nByte = (pWInfo->nLevel-1-i) * sizeof(WhereLevel); + memmove(&pWInfo->a[i], &pWInfo->a[i+1], nByte); + } + pWInfo->nLevel--; + assert( pWInfo->nLevel>0 ); + } + return notReady; +} + +/* +** Check to see if there are any SEARCH loops that might benefit from +** using a Bloom filter. Consider a Bloom filter if: +** +** (1) The SEARCH happens more than N times where N is the number +** of rows in the table that is being considered for the Bloom +** filter. +** (2) Some searches are expected to find zero rows. (This is determined +** by the WHERE_SELFCULL flag on the term.) +** (3) Bloom-filter processing is not disabled. (Checked by the +** caller.) +** (4) The size of the table being searched is known by ANALYZE. +** +** This block of code merely checks to see if a Bloom filter would be +** appropriate, and if so sets the WHERE_BLOOMFILTER flag on the +** WhereLoop. The implementation of the Bloom filter comes further +** down where the code for each WhereLoop is generated. +*/ +static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful( + const WhereInfo *pWInfo +){ + int i; + LogEst nSearch = 0; + + assert( pWInfo->nLevel>=2 ); + assert( OptimizationEnabled(pWInfo->pParse->db, SQLITE_BloomFilter) ); + for(i=0; i<pWInfo->nLevel; i++){ + WhereLoop *pLoop = pWInfo->a[i].pWLoop; + const unsigned int reqFlags = (WHERE_SELFCULL|WHERE_COLUMN_EQ); + SrcItem *pItem = &pWInfo->pTabList->a[pLoop->iTab]; + Table *pTab = pItem->pTab; + if( (pTab->tabFlags & TF_HasStat1)==0 ) break; + pTab->tabFlags |= TF_StatsUsed; + if( i>=1 + && (pLoop->wsFlags & reqFlags)==reqFlags + /* vvvvvv--- Always the case if WHERE_COLUMN_EQ is defined */ + && ALWAYS((pLoop->wsFlags & (WHERE_IPK|WHERE_INDEXED))!=0) + ){ + if( nSearch > pTab->nRowLogEst ){ + testcase( pItem->fg.jointype & JT_LEFT ); + pLoop->wsFlags |= WHERE_BLOOMFILTER; + pLoop->wsFlags &= ~WHERE_IDX_ONLY; + WHERETRACE(0xffffffff, ( + "-> use Bloom-filter on loop %c because there are ~%.1e " + "lookups into %s which has only ~%.1e rows\n", + pLoop->cId, (double)sqlite3LogEstToInt(nSearch), pTab->zName, + (double)sqlite3LogEstToInt(pTab->nRowLogEst))); + } + } + nSearch += pLoop->nOut; + } +} + +/* +** This is an sqlite3ParserAddCleanup() callback that is invoked to +** free the Parse->pIdxEpr list when the Parse object is destroyed. +*/ +static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){ + Parse *pParse = (Parse*)pObject; + while( pParse->pIdxEpr!=0 ){ + IndexedExpr *p = pParse->pIdxEpr; + pParse->pIdxEpr = p->pIENext; + sqlite3ExprDelete(db, p->pExpr); + sqlite3DbFreeNN(db, p); + } +} + +/* +** The index pIdx is used by a query and contains one or more expressions. +** In other words pIdx is an index on an expression. iIdxCur is the cursor +** number for the index and iDataCur is the cursor number for the corresponding +** table. +** +** This routine adds IndexedExpr entries to the Parse->pIdxEpr field for +** each of the expressions in the index so that the expression code generator +** will know to replace occurrences of the indexed expression with +** references to the corresponding column of the index. +*/ +static SQLITE_NOINLINE void whereAddIndexedExpr( + Parse *pParse, /* Add IndexedExpr entries to pParse->pIdxEpr */ + Index *pIdx, /* The index-on-expression that contains the expressions */ + int iIdxCur, /* Cursor number for pIdx */ + SrcItem *pTabItem /* The FROM clause entry for the table */ +){ + int i; + IndexedExpr *p; + Table *pTab; + assert( pIdx->bHasExpr ); + pTab = pIdx->pTable; + for(i=0; i<pIdx->nColumn; i++){ + Expr *pExpr; + int j = pIdx->aiColumn[i]; + int bMaybeNullRow; + if( j==XN_EXPR ){ + pExpr = pIdx->aColExpr->a[i].pExpr; + testcase( pTabItem->fg.jointype & JT_LEFT ); + testcase( pTabItem->fg.jointype & JT_RIGHT ); + testcase( pTabItem->fg.jointype & JT_LTORJ ); + bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0; + }else if( j>=0 && (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)!=0 ){ + pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]); + bMaybeNullRow = 0; + }else{ + continue; + } + if( sqlite3ExprIsConstant(pExpr) ) continue; + p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); + if( p==0 ) break; + p->pIENext = pParse->pIdxEpr; +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x200 ){ + sqlite3DebugPrintf("New pParse->pIdxEpr term {%d,%d}\n", iIdxCur, i); + if( sqlite3WhereTrace & 0x5000 ) sqlite3ShowExpr(pExpr); + } +#endif + p->pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); + p->iDataCur = pTabItem->iCursor; + p->iIdxCur = iIdxCur; + p->iIdxCol = i; + p->bMaybeNullRow = bMaybeNullRow; + if( sqlite3IndexAffinityStr(pParse->db, pIdx) ){ + p->aff = pIdx->zColAff[i]; + } +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + p->zIdxName = pIdx->zName; +#endif + pParse->pIdxEpr = p; + if( p->pIENext==0 ){ + sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse); + } + } +} + +/* +** Set the reverse-scan order mask to one for all tables in the query +** with the exception of MATERIALIZED common table expressions that have +** their own internal ORDER BY clauses. +** +** This implements the PRAGMA reverse_unordered_selects=ON setting. +** (Also SQLITE_DBCONFIG_REVERSE_SCANORDER). +*/ +static SQLITE_NOINLINE void whereReverseScanOrder(WhereInfo *pWInfo){ + int ii; + for(ii=0; ii<pWInfo->pTabList->nSrc; ii++){ + SrcItem *pItem = &pWInfo->pTabList->a[ii]; + if( !pItem->fg.isCte + || pItem->u2.pCteUse->eM10d!=M10d_Yes + || NEVER(pItem->pSelect==0) + || pItem->pSelect->pOrderBy==0 + ){ + pWInfo->revMask |= MASKBIT(ii); + } + } +} + +/* +** Generate the beginning of the loop used for WHERE clause processing. +** The return value is a pointer to an opaque structure that contains +** information needed to terminate the loop. Later, the calling routine +** should invoke sqlite3WhereEnd() with the return value of this function +** in order to complete the WHERE clause processing. +** +** If an error occurs, this routine returns NULL. +** +** The basic idea is to do a nested loop, one loop for each table in +** the FROM clause of a select. (INSERT and UPDATE statements are the +** same as a SELECT with only a single table in the FROM clause.) For +** example, if the SQL is this: +** +** SELECT * FROM t1, t2, t3 WHERE ...; +** +** Then the code generated is conceptually like the following: +** +** foreach row1 in t1 do \ Code generated +** foreach row2 in t2 do |-- by sqlite3WhereBegin() +** foreach row3 in t3 do / +** ... +** end \ Code generated +** end |-- by sqlite3WhereEnd() +** end / +** +** Note that the loops might not be nested in the order in which they +** appear in the FROM clause if a different order is better able to make +** use of indices. Note also that when the IN operator appears in +** the WHERE clause, it might result in additional nested loops for +** scanning through all values on the right-hand side of the IN. +** +** There are Btree cursors associated with each table. t1 uses cursor +** number pTabList->a[0].iCursor. t2 uses the cursor pTabList->a[1].iCursor. +** And so forth. This routine generates code to open those VDBE cursors +** and sqlite3WhereEnd() generates the code to close them. +** +** The code that sqlite3WhereBegin() generates leaves the cursors named +** in pTabList pointing at their appropriate entries. The [...] code +** can use OP_Column and OP_Rowid opcodes on these cursors to extract +** data from the various tables of the loop. +** +** If the WHERE clause is empty, the foreach loops must each scan their +** entire tables. Thus a three-way join is an O(N^3) operation. But if +** the tables have indices and there are terms in the WHERE clause that +** refer to those indices, a complete table scan can be avoided and the +** code will run much faster. Most of the work of this routine is checking +** to see if there are indices that can be used to speed up the loop. +** +** Terms of the WHERE clause are also used to limit which rows actually +** make it to the "..." in the middle of the loop. After each "foreach", +** terms of the WHERE clause that use only terms in that loop and outer +** loops are evaluated and if false a jump is made around all subsequent +** inner loops (or around the "..." if the test occurs within the inner- +** most loop) +** +** OUTER JOINS +** +** An outer join of tables t1 and t2 is conceptually coded as follows: +** +** foreach row1 in t1 do +** flag = 0 +** foreach row2 in t2 do +** start: +** ... +** flag = 1 +** end +** if flag==0 then +** move the row2 cursor to a null row +** goto start +** fi +** end +** +** ORDER BY CLAUSE PROCESSING +** +** pOrderBy is a pointer to the ORDER BY clause (or the GROUP BY clause +** if the WHERE_GROUPBY flag is set in wctrlFlags) of a SELECT statement +** if there is one. If there is no ORDER BY clause or if this routine +** is called from an UPDATE or DELETE statement, then pOrderBy is NULL. +** +** The iIdxCur parameter is the cursor number of an index. If +** WHERE_OR_SUBCLAUSE is set, iIdxCur is the cursor number of an index +** to use for OR clause processing. The WHERE clause should use this +** specific cursor. If WHERE_ONEPASS_DESIRED is set, then iIdxCur is +** the first cursor in an array of cursors for all indices. iIdxCur should +** be used to compute the appropriate cursor depending on which index is +** used. +*/ +SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( + Parse *pParse, /* The parser context */ + SrcList *pTabList, /* FROM clause: A list of all tables to be scanned */ + Expr *pWhere, /* The WHERE clause */ + ExprList *pOrderBy, /* An ORDER BY (or GROUP BY) clause, or NULL */ + ExprList *pResultSet, /* Query result set. Req'd for DISTINCT */ + Select *pSelect, /* The entire SELECT statement */ + u16 wctrlFlags, /* The WHERE_* flags defined in sqliteInt.h */ + int iAuxArg /* If WHERE_OR_SUBCLAUSE is set, index cursor number + ** If WHERE_USE_LIMIT, then the limit amount */ +){ + int nByteWInfo; /* Num. bytes allocated for WhereInfo struct */ + int nTabList; /* Number of elements in pTabList */ + WhereInfo *pWInfo; /* Will become the return value of this function */ + Vdbe *v = pParse->pVdbe; /* The virtual database engine */ + Bitmask notReady; /* Cursors that are not yet positioned */ + WhereLoopBuilder sWLB; /* The WhereLoop builder */ + WhereMaskSet *pMaskSet; /* The expression mask set */ + WhereLevel *pLevel; /* A single level in pWInfo->a[] */ + WhereLoop *pLoop; /* Pointer to a single WhereLoop object */ + int ii; /* Loop counter */ + sqlite3 *db; /* Database connection */ + int rc; /* Return code */ + u8 bFordelete = 0; /* OPFLAG_FORDELETE or zero, as appropriate */ + + assert( (wctrlFlags & WHERE_ONEPASS_MULTIROW)==0 || ( + (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 + && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 + )); + + /* Only one of WHERE_OR_SUBCLAUSE or WHERE_USE_LIMIT */ + assert( (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 + || (wctrlFlags & WHERE_USE_LIMIT)==0 ); + + /* Variable initialization */ + db = pParse->db; + memset(&sWLB, 0, sizeof(sWLB)); + + /* An ORDER/GROUP BY clause of more than 63 terms cannot be optimized */ + testcase( pOrderBy && pOrderBy->nExpr==BMS-1 ); + if( pOrderBy && pOrderBy->nExpr>=BMS ) pOrderBy = 0; + + /* The number of tables in the FROM clause is limited by the number of + ** bits in a Bitmask + */ + testcase( pTabList->nSrc==BMS ); + if( pTabList->nSrc>BMS ){ + sqlite3ErrorMsg(pParse, "at most %d tables in a join", BMS); + return 0; + } + + /* This function normally generates a nested loop for all tables in + ** pTabList. But if the WHERE_OR_SUBCLAUSE flag is set, then we should + ** only generate code for the first table in pTabList and assume that + ** any cursors associated with subsequent tables are uninitialized. + */ + nTabList = (wctrlFlags & WHERE_OR_SUBCLAUSE) ? 1 : pTabList->nSrc; + + /* Allocate and initialize the WhereInfo structure that will become the + ** return value. A single allocation is used to store the WhereInfo + ** struct, the contents of WhereInfo.a[], the WhereClause structure + ** and the WhereMaskSet structure. Since WhereClause contains an 8-byte + ** field (type Bitmask) it must be aligned on an 8-byte boundary on + ** some architectures. Hence the ROUND8() below. + */ + nByteWInfo = ROUND8P(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel)); + pWInfo = sqlite3DbMallocRawNN(db, nByteWInfo + sizeof(WhereLoop)); + if( db->mallocFailed ){ + sqlite3DbFree(db, pWInfo); + pWInfo = 0; + goto whereBeginError; + } + pWInfo->pParse = pParse; + pWInfo->pTabList = pTabList; + pWInfo->pOrderBy = pOrderBy; +#if WHERETRACE_ENABLED + pWInfo->pWhere = pWhere; +#endif + pWInfo->pResultSet = pResultSet; + pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1; + pWInfo->nLevel = nTabList; + pWInfo->iBreak = pWInfo->iContinue = sqlite3VdbeMakeLabel(pParse); + pWInfo->wctrlFlags = wctrlFlags; + pWInfo->iLimit = iAuxArg; + pWInfo->savedNQueryLoop = pParse->nQueryLoop; + pWInfo->pSelect = pSelect; + memset(&pWInfo->nOBSat, 0, + offsetof(WhereInfo,sWC) - offsetof(WhereInfo,nOBSat)); + memset(&pWInfo->a[0], 0, sizeof(WhereLoop)+nTabList*sizeof(WhereLevel)); + assert( pWInfo->eOnePass==ONEPASS_OFF ); /* ONEPASS defaults to OFF */ + pMaskSet = &pWInfo->sMaskSet; + pMaskSet->n = 0; + pMaskSet->ix[0] = -99; /* Initialize ix[0] to a value that can never be + ** a valid cursor number, to avoid an initial + ** test for pMaskSet->n==0 in sqlite3WhereGetMask() */ + sWLB.pWInfo = pWInfo; + sWLB.pWC = &pWInfo->sWC; + sWLB.pNew = (WhereLoop*)(((char*)pWInfo)+nByteWInfo); + assert( EIGHT_BYTE_ALIGNMENT(sWLB.pNew) ); + whereLoopInit(sWLB.pNew); +#ifdef SQLITE_DEBUG + sWLB.pNew->cId = '*'; +#endif + + /* Split the WHERE clause into separate subexpressions where each + ** subexpression is separated by an AND operator. + */ + sqlite3WhereClauseInit(&pWInfo->sWC, pWInfo); + sqlite3WhereSplit(&pWInfo->sWC, pWhere, TK_AND); + + /* Special case: No FROM clause + */ + if( nTabList==0 ){ + if( pOrderBy ) pWInfo->nOBSat = pOrderBy->nExpr; + if( (wctrlFlags & WHERE_WANT_DISTINCT)!=0 + && OptimizationEnabled(db, SQLITE_DistinctOpt) + ){ + pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; + } + ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW")); + }else{ + /* Assign a bit from the bitmask to every term in the FROM clause. + ** + ** The N-th term of the FROM clause is assigned a bitmask of 1<<N. + ** + ** The rule of the previous sentence ensures that if X is the bitmask for + ** a table T, then X-1 is the bitmask for all other tables to the left of T. + ** Knowing the bitmask for all tables to the left of a left join is + ** important. Ticket #3015. + ** + ** Note that bitmasks are created for all pTabList->nSrc tables in + ** pTabList, not just the first nTabList tables. nTabList is normally + ** equal to pTabList->nSrc but might be shortened to 1 if the + ** WHERE_OR_SUBCLAUSE flag is set. + */ + ii = 0; + do{ + createMask(pMaskSet, pTabList->a[ii].iCursor); + sqlite3WhereTabFuncArgs(pParse, &pTabList->a[ii], &pWInfo->sWC); + }while( (++ii)<pTabList->nSrc ); + #ifdef SQLITE_DEBUG + { + Bitmask mx = 0; + for(ii=0; ii<pTabList->nSrc; ii++){ + Bitmask m = sqlite3WhereGetMask(pMaskSet, pTabList->a[ii].iCursor); + assert( m>=mx ); + mx = m; + } + } + #endif + } + + /* Analyze all of the subexpressions. */ + sqlite3WhereExprAnalyze(pTabList, &pWInfo->sWC); + if( pSelect && pSelect->pLimit ){ + sqlite3WhereAddLimit(&pWInfo->sWC, pSelect); + } + if( pParse->nErr ) goto whereBeginError; + + /* The False-WHERE-Term-Bypass optimization: + ** + ** If there are WHERE terms that are false, then no rows will be output, + ** so skip over all of the code generated here. + ** + ** Conditions: + ** + ** (1) The WHERE term must not refer to any tables in the join. + ** (2) The term must not come from an ON clause on the + ** right-hand side of a LEFT or FULL JOIN. + ** (3) The term must not come from an ON clause, or there must be + ** no RIGHT or FULL OUTER joins in pTabList. + ** (4) If the expression contains non-deterministic functions + ** that are not within a sub-select. This is not required + ** for correctness but rather to preserves SQLite's legacy + ** behaviour in the following two cases: + ** + ** WHERE random()>0; -- eval random() once per row + ** WHERE (SELECT random())>0; -- eval random() just once overall + ** + ** Note that the Where term need not be a constant in order for this + ** optimization to apply, though it does need to be constant relative to + ** the current subquery (condition 1). The term might include variables + ** from outer queries so that the value of the term changes from one + ** invocation of the current subquery to the next. + */ + for(ii=0; ii<sWLB.pWC->nBase; ii++){ + WhereTerm *pT = &sWLB.pWC->a[ii]; /* A term of the WHERE clause */ + Expr *pX; /* The expression of pT */ + if( pT->wtFlags & TERM_VIRTUAL ) continue; + pX = pT->pExpr; + assert( pX!=0 ); + assert( pT->prereqAll!=0 || !ExprHasProperty(pX, EP_OuterON) ); + if( pT->prereqAll==0 /* Conditions (1) and (2) */ + && (nTabList==0 || exprIsDeterministic(pX)) /* Condition (4) */ + && !(ExprHasProperty(pX, EP_InnerON) /* Condition (3) */ + && (pTabList->a[0].fg.jointype & JT_LTORJ)!=0 ) + ){ + sqlite3ExprIfFalse(pParse, pX, pWInfo->iBreak, SQLITE_JUMPIFNULL); + pT->wtFlags |= TERM_CODED; + } + } + + if( wctrlFlags & WHERE_WANT_DISTINCT ){ + if( OptimizationDisabled(db, SQLITE_DistinctOpt) ){ + /* Disable the DISTINCT optimization if SQLITE_DistinctOpt is set via + ** sqlite3_test_ctrl(SQLITE_TESTCTRL_OPTIMIZATIONS,...) */ + wctrlFlags &= ~WHERE_WANT_DISTINCT; + pWInfo->wctrlFlags &= ~WHERE_WANT_DISTINCT; + }else if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ + /* The DISTINCT marking is pointless. Ignore it. */ + pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; + }else if( pOrderBy==0 ){ + /* Try to ORDER BY the result set to make distinct processing easier */ + pWInfo->wctrlFlags |= WHERE_DISTINCTBY; + pWInfo->pOrderBy = pResultSet; + } + } + + /* Construct the WhereLoop objects */ +#if defined(WHERETRACE_ENABLED) + if( sqlite3WhereTrace & 0xffffffff ){ + sqlite3DebugPrintf("*** Optimizer Start *** (wctrlFlags: 0x%x",wctrlFlags); + if( wctrlFlags & WHERE_USE_LIMIT ){ + sqlite3DebugPrintf(", limit: %d", iAuxArg); + } + sqlite3DebugPrintf(")\n"); + if( sqlite3WhereTrace & 0x8000 ){ + Select sSelect; + memset(&sSelect, 0, sizeof(sSelect)); + sSelect.selFlags = SF_WhereBegin; + sSelect.pSrc = pTabList; + sSelect.pWhere = pWhere; + sSelect.pOrderBy = pOrderBy; + sSelect.pEList = pResultSet; + sqlite3TreeViewSelect(0, &sSelect, 0); + } + if( sqlite3WhereTrace & 0x4000 ){ /* Display all WHERE clause terms */ + sqlite3DebugPrintf("---- WHERE clause at start of analysis:\n"); + sqlite3WhereClausePrint(sWLB.pWC); + } + } +#endif + + if( nTabList!=1 || whereShortCut(&sWLB)==0 ){ + rc = whereLoopAddAll(&sWLB); + if( rc ) goto whereBeginError; + +#ifdef SQLITE_ENABLE_STAT4 + /* If one or more WhereTerm.truthProb values were used in estimating + ** loop parameters, but then those truthProb values were subsequently + ** changed based on STAT4 information while computing subsequent loops, + ** then we need to rerun the whole loop building process so that all + ** loops will be built using the revised truthProb values. */ + if( sWLB.bldFlags2 & SQLITE_BLDF2_2NDPASS ){ + WHERETRACE_ALL_LOOPS(pWInfo, sWLB.pWC); + WHERETRACE(0xffffffff, + ("**** Redo all loop computations due to" + " TERM_HIGHTRUTH changes ****\n")); + while( pWInfo->pLoops ){ + WhereLoop *p = pWInfo->pLoops; + pWInfo->pLoops = p->pNextLoop; + whereLoopDelete(db, p); + } + rc = whereLoopAddAll(&sWLB); + if( rc ) goto whereBeginError; + } +#endif + WHERETRACE_ALL_LOOPS(pWInfo, sWLB.pWC); + + wherePathSolver(pWInfo, 0); + if( db->mallocFailed ) goto whereBeginError; + if( pWInfo->pOrderBy ){ + wherePathSolver(pWInfo, pWInfo->nRowOut+1); + if( db->mallocFailed ) goto whereBeginError; + } + } + assert( pWInfo->pTabList!=0 ); + if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){ + whereReverseScanOrder(pWInfo); + } + if( pParse->nErr ){ + goto whereBeginError; + } + assert( db->mallocFailed==0 ); +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace ){ + sqlite3DebugPrintf("---- Solution nRow=%d", pWInfo->nRowOut); + if( pWInfo->nOBSat>0 ){ + sqlite3DebugPrintf(" ORDERBY=%d,0x%llx", pWInfo->nOBSat, pWInfo->revMask); + } + switch( pWInfo->eDistinct ){ + case WHERE_DISTINCT_UNIQUE: { + sqlite3DebugPrintf(" DISTINCT=unique"); + break; + } + case WHERE_DISTINCT_ORDERED: { + sqlite3DebugPrintf(" DISTINCT=ordered"); + break; + } + case WHERE_DISTINCT_UNORDERED: { + sqlite3DebugPrintf(" DISTINCT=unordered"); + break; + } + } + sqlite3DebugPrintf("\n"); + for(ii=0; ii<pWInfo->nLevel; ii++){ + sqlite3WhereLoopPrint(pWInfo->a[ii].pWLoop, sWLB.pWC); + } + } +#endif + + /* Attempt to omit tables from a join that do not affect the result. + ** See the comment on whereOmitNoopJoin() for further information. + ** + ** This query optimization is factored out into a separate "no-inline" + ** procedure to keep the sqlite3WhereBegin() procedure from becoming + ** too large. If sqlite3WhereBegin() becomes too large, that prevents + ** some C-compiler optimizers from in-lining the + ** sqlite3WhereCodeOneLoopStart() procedure, and it is important to + ** in-line sqlite3WhereCodeOneLoopStart() for performance reasons. + */ + notReady = ~(Bitmask)0; + if( pWInfo->nLevel>=2 + && pResultSet!=0 /* these two combine to guarantee */ + && 0==(wctrlFlags & WHERE_AGG_DISTINCT) /* condition (1) above */ + && OptimizationEnabled(db, SQLITE_OmitNoopJoin) + ){ + notReady = whereOmitNoopJoin(pWInfo, notReady); + nTabList = pWInfo->nLevel; + assert( nTabList>0 ); + } + + /* Check to see if there are any SEARCH loops that might benefit from + ** using a Bloom filter. + */ + if( pWInfo->nLevel>=2 + && OptimizationEnabled(db, SQLITE_BloomFilter) + ){ + whereCheckIfBloomFilterIsUseful(pWInfo); + } + +#if defined(WHERETRACE_ENABLED) + if( sqlite3WhereTrace & 0x4000 ){ /* Display all terms of the WHERE clause */ + sqlite3DebugPrintf("---- WHERE clause at end of analysis:\n"); + sqlite3WhereClausePrint(sWLB.pWC); + } + WHERETRACE(0xffffffff,("*** Optimizer Finished ***\n")); +#endif + pWInfo->pParse->nQueryLoop += pWInfo->nRowOut; + + /* If the caller is an UPDATE or DELETE statement that is requesting + ** to use a one-pass algorithm, determine if this is appropriate. + ** + ** A one-pass approach can be used if the caller has requested one + ** and either (a) the scan visits at most one row or (b) each + ** of the following are true: + ** + ** * the caller has indicated that a one-pass approach can be used + ** with multiple rows (by setting WHERE_ONEPASS_MULTIROW), and + ** * the table is not a virtual table, and + ** * either the scan does not use the OR optimization or the caller + ** is a DELETE operation (WHERE_DUPLICATES_OK is only specified + ** for DELETE). + ** + ** The last qualification is because an UPDATE statement uses + ** WhereInfo.aiCurOnePass[1] to determine whether or not it really can + ** use a one-pass approach, and this is not set accurately for scans + ** that use the OR optimization. + */ + assert( (wctrlFlags & WHERE_ONEPASS_DESIRED)==0 || pWInfo->nLevel==1 ); + if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 ){ + int wsFlags = pWInfo->a[0].pWLoop->wsFlags; + int bOnerow = (wsFlags & WHERE_ONEROW)!=0; + assert( !(wsFlags & WHERE_VIRTUALTABLE) || IsVirtual(pTabList->a[0].pTab) ); + if( bOnerow || ( + 0!=(wctrlFlags & WHERE_ONEPASS_MULTIROW) + && !IsVirtual(pTabList->a[0].pTab) + && (0==(wsFlags & WHERE_MULTI_OR) || (wctrlFlags & WHERE_DUPLICATES_OK)) + && OptimizationEnabled(db, SQLITE_OnePass) + )){ + pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI; + if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){ + if( wctrlFlags & WHERE_ONEPASS_MULTIROW ){ + bFordelete = OPFLAG_FORDELETE; + } + pWInfo->a[0].pWLoop->wsFlags = (wsFlags & ~WHERE_IDX_ONLY); + } + } + } + + /* Open all tables in the pTabList and any indices selected for + ** searching those tables. + */ + for(ii=0, pLevel=pWInfo->a; ii<nTabList; ii++, pLevel++){ + Table *pTab; /* Table to open */ + int iDb; /* Index of database containing table/index */ + SrcItem *pTabItem; + + pTabItem = &pTabList->a[pLevel->iFrom]; + pTab = pTabItem->pTab; + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + pLoop = pLevel->pWLoop; + if( (pTab->tabFlags & TF_Ephemeral)!=0 || IsView(pTab) ){ + /* Do nothing */ + }else +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)!=0 ){ + const char *pVTab = (const char *)sqlite3GetVTable(db, pTab); + int iCur = pTabItem->iCursor; + sqlite3VdbeAddOp4(v, OP_VOpen, iCur, 0, 0, pVTab, P4_VTAB); + }else if( IsVirtual(pTab) ){ + /* noop */ + }else +#endif + if( ((pLoop->wsFlags & WHERE_IDX_ONLY)==0 + && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0) + || (pTabItem->fg.jointype & (JT_LTORJ|JT_RIGHT))!=0 + ){ + int op = OP_OpenRead; + if( pWInfo->eOnePass!=ONEPASS_OFF ){ + op = OP_OpenWrite; + pWInfo->aiCurOnePass[0] = pTabItem->iCursor; + }; + sqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, op); + assert( pTabItem->iCursor==pLevel->iTabCur ); + testcase( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol==BMS-1 ); + testcase( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol==BMS ); + if( pWInfo->eOnePass==ONEPASS_OFF + && pTab->nCol<BMS + && (pTab->tabFlags & (TF_HasGenerated|TF_WithoutRowid))==0 + && (pLoop->wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))==0 + ){ + /* If we know that only a prefix of the record will be used, + ** it is advantageous to reduce the "column count" field in + ** the P4 operand of the OP_OpenRead/Write opcode. */ + Bitmask b = pTabItem->colUsed; + int n = 0; + for(; b; b=b>>1, n++){} + sqlite3VdbeChangeP4(v, -1, SQLITE_INT_TO_PTR(n), P4_INT32); + assert( n<=pTab->nCol ); + } +#ifdef SQLITE_ENABLE_CURSOR_HINTS + if( pLoop->u.btree.pIndex!=0 && (pTab->tabFlags & TF_WithoutRowid)==0 ){ + sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ|bFordelete); + }else +#endif + { + sqlite3VdbeChangeP5(v, bFordelete); + } +#ifdef SQLITE_ENABLE_COLUMN_USED_MASK + sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, pTabItem->iCursor, 0, 0, + (const u8*)&pTabItem->colUsed, P4_INT64); +#endif + }else{ + sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); + } + if( pLoop->wsFlags & WHERE_INDEXED ){ + Index *pIx = pLoop->u.btree.pIndex; + int iIndexCur; + int op = OP_OpenRead; + /* iAuxArg is always set to a positive value if ONEPASS is possible */ + assert( iAuxArg!=0 || (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 ); + if( !HasRowid(pTab) && IsPrimaryKeyIndex(pIx) + && (wctrlFlags & WHERE_OR_SUBCLAUSE)!=0 + ){ + /* This is one term of an OR-optimization using the PRIMARY KEY of a + ** WITHOUT ROWID table. No need for a separate index */ + iIndexCur = pLevel->iTabCur; + op = 0; + }else if( pWInfo->eOnePass!=ONEPASS_OFF ){ + Index *pJ = pTabItem->pTab->pIndex; + iIndexCur = iAuxArg; + assert( wctrlFlags & WHERE_ONEPASS_DESIRED ); + while( ALWAYS(pJ) && pJ!=pIx ){ + iIndexCur++; + pJ = pJ->pNext; + } + op = OP_OpenWrite; + pWInfo->aiCurOnePass[1] = iIndexCur; + }else if( iAuxArg && (wctrlFlags & WHERE_OR_SUBCLAUSE)!=0 ){ + iIndexCur = iAuxArg; + op = OP_ReopenIdx; + }else{ + iIndexCur = pParse->nTab++; + if( pIx->bHasExpr && OptimizationEnabled(db, SQLITE_IndexedExpr) ){ + whereAddIndexedExpr(pParse, pIx, iIndexCur, pTabItem); + } + } + pLevel->iIdxCur = iIndexCur; + assert( pIx!=0 ); + assert( pIx->pSchema==pTab->pSchema ); + assert( iIndexCur>=0 ); + if( op ){ + sqlite3VdbeAddOp3(v, op, iIndexCur, pIx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIx); + if( (pLoop->wsFlags & WHERE_CONSTRAINT)!=0 + && (pLoop->wsFlags & (WHERE_COLUMN_RANGE|WHERE_SKIPSCAN))==0 + && (pLoop->wsFlags & WHERE_BIGNULL_SORT)==0 + && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)==0 + && (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0 + && pWInfo->eDistinct!=WHERE_DISTINCT_ORDERED + ){ + sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); + } + VdbeComment((v, "%s", pIx->zName)); +#ifdef SQLITE_ENABLE_COLUMN_USED_MASK + { + u64 colUsed = 0; + int ii, jj; + for(ii=0; ii<pIx->nColumn; ii++){ + jj = pIx->aiColumn[ii]; + if( jj<0 ) continue; + if( jj>63 ) jj = 63; + if( (pTabItem->colUsed & MASKBIT(jj))==0 ) continue; + colUsed |= ((u64)1)<<(ii<63 ? ii : 63); + } + sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, iIndexCur, 0, 0, + (u8*)&colUsed, P4_INT64); + } +#endif /* SQLITE_ENABLE_COLUMN_USED_MASK */ + } + } + if( iDb>=0 ) sqlite3CodeVerifySchema(pParse, iDb); + if( (pTabItem->fg.jointype & JT_RIGHT)!=0 + && (pLevel->pRJ = sqlite3WhereMalloc(pWInfo, sizeof(WhereRightJoin)))!=0 + ){ + WhereRightJoin *pRJ = pLevel->pRJ; + pRJ->iMatch = pParse->nTab++; + pRJ->regBloom = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Blob, 65536, pRJ->regBloom); + pRJ->regReturn = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Null, 0, pRJ->regReturn); + assert( pTab==pTabItem->pTab ); + if( HasRowid(pTab) ){ + KeyInfo *pInfo; + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, 1); + pInfo = sqlite3KeyInfoAlloc(pParse->db, 1, 0); + if( pInfo ){ + pInfo->aColl[0] = 0; + pInfo->aSortFlags[0] = 0; + sqlite3VdbeAppendP4(v, pInfo, P4_KEYINFO); + } + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, pPk->nKeyCol); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + } + pLoop->wsFlags &= ~WHERE_IDX_ONLY; + /* The nature of RIGHT JOIN processing is such that it messes up + ** the output order. So omit any ORDER BY/GROUP BY elimination + ** optimizations. We need to do an actual sort for RIGHT JOIN. */ + pWInfo->nOBSat = 0; + pWInfo->eDistinct = WHERE_DISTINCT_UNORDERED; + } + } + pWInfo->iTop = sqlite3VdbeCurrentAddr(v); + if( db->mallocFailed ) goto whereBeginError; + + /* Generate the code to do the search. Each iteration of the for + ** loop below generates code for a single nested loop of the VM + ** program. + */ + for(ii=0; ii<nTabList; ii++){ + int addrExplain; + int wsFlags; + SrcItem *pSrc; + if( pParse->nErr ) goto whereBeginError; + pLevel = &pWInfo->a[ii]; + wsFlags = pLevel->pWLoop->wsFlags; + pSrc = &pTabList->a[pLevel->iFrom]; + if( pSrc->fg.isMaterialized ){ + if( pSrc->fg.isCorrelated ){ + sqlite3VdbeAddOp2(v, OP_Gosub, pSrc->regReturn, pSrc->addrFillSub); + }else{ + int iOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Gosub, pSrc->regReturn, pSrc->addrFillSub); + sqlite3VdbeJumpHere(v, iOnce); + } + } + assert( pTabList == pWInfo->pTabList ); + if( (wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))!=0 ){ + if( (wsFlags & WHERE_AUTO_INDEX)!=0 ){ +#ifndef SQLITE_OMIT_AUTOMATIC_INDEX + constructAutomaticIndex(pParse, &pWInfo->sWC, notReady, pLevel); +#endif + }else{ + sqlite3ConstructBloomFilter(pWInfo, ii, pLevel, notReady); + } + if( db->mallocFailed ) goto whereBeginError; + } + addrExplain = sqlite3WhereExplainOneScan( + pParse, pTabList, pLevel, wctrlFlags + ); + pLevel->addrBody = sqlite3VdbeCurrentAddr(v); + notReady = sqlite3WhereCodeOneLoopStart(pParse,v,pWInfo,ii,pLevel,notReady); + pWInfo->iContinue = pLevel->addrCont; + if( (wsFlags&WHERE_MULTI_OR)==0 && (wctrlFlags&WHERE_OR_SUBCLAUSE)==0 ){ + sqlite3WhereAddScanStatus(v, pTabList, pLevel, addrExplain); + } + } + + /* Done. */ + VdbeModuleComment((v, "Begin WHERE-core")); + pWInfo->iEndWhere = sqlite3VdbeCurrentAddr(v); + return pWInfo; + + /* Jump here if malloc fails */ +whereBeginError: + if( pWInfo ){ + pParse->nQueryLoop = pWInfo->savedNQueryLoop; + whereInfoFree(db, pWInfo); + } + return 0; +} + +/* +** Part of sqlite3WhereEnd() will rewrite opcodes to reference the +** index rather than the main table. In SQLITE_DEBUG mode, we want +** to trace those changes if PRAGMA vdbe_addoptrace=on. This routine +** does that. +*/ +#ifndef SQLITE_DEBUG +# define OpcodeRewriteTrace(D,K,P) /* no-op */ +#else +# define OpcodeRewriteTrace(D,K,P) sqlite3WhereOpcodeRewriteTrace(D,K,P) + static void sqlite3WhereOpcodeRewriteTrace( + sqlite3 *db, + int pc, + VdbeOp *pOp + ){ + if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return; + sqlite3VdbePrintOp(0, pc, pOp); + } +#endif + +#ifdef SQLITE_DEBUG +/* +** Return true if cursor iCur is opened by instruction k of the +** bytecode. Used inside of assert() only. +*/ +static int cursorIsOpen(Vdbe *v, int iCur, int k){ + while( k>=0 ){ + VdbeOp *pOp = sqlite3VdbeGetOp(v,k--); + if( pOp->p1!=iCur ) continue; + if( pOp->opcode==OP_Close ) return 0; + if( pOp->opcode==OP_OpenRead ) return 1; + if( pOp->opcode==OP_OpenWrite ) return 1; + if( pOp->opcode==OP_OpenDup ) return 1; + if( pOp->opcode==OP_OpenAutoindex ) return 1; + if( pOp->opcode==OP_OpenEphemeral ) return 1; + } + return 0; +} +#endif /* SQLITE_DEBUG */ + +/* +** Generate the end of the WHERE loop. See comments on +** sqlite3WhereBegin() for additional information. +*/ +SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ + Parse *pParse = pWInfo->pParse; + Vdbe *v = pParse->pVdbe; + int i; + WhereLevel *pLevel; + WhereLoop *pLoop; + SrcList *pTabList = pWInfo->pTabList; + sqlite3 *db = pParse->db; + int iEnd = sqlite3VdbeCurrentAddr(v); + int nRJ = 0; + + /* Generate loop termination code. + */ + VdbeModuleComment((v, "End WHERE-core")); + for(i=pWInfo->nLevel-1; i>=0; i--){ + int addr; + pLevel = &pWInfo->a[i]; + if( pLevel->pRJ ){ + /* Terminate the subroutine that forms the interior of the loop of + ** the RIGHT JOIN table */ + WhereRightJoin *pRJ = pLevel->pRJ; + sqlite3VdbeResolveLabel(v, pLevel->addrCont); + pLevel->addrCont = 0; + pRJ->endSubrtn = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp3(v, OP_Return, pRJ->regReturn, pRJ->addrSubrtn, 1); + VdbeCoverage(v); + nRJ++; + } + pLoop = pLevel->pWLoop; + if( pLevel->op!=OP_Noop ){ +#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT + int addrSeek = 0; + Index *pIdx; + int n; + if( pWInfo->eDistinct==WHERE_DISTINCT_ORDERED + && i==pWInfo->nLevel-1 /* Ticket [ef9318757b152e3] 2017-10-21 */ + && (pLoop->wsFlags & WHERE_INDEXED)!=0 + && (pIdx = pLoop->u.btree.pIndex)->hasStat1 + && (n = pLoop->u.btree.nDistinctCol)>0 + && pIdx->aiRowLogEst[n]>=36 + ){ + int r1 = pParse->nMem+1; + int j, op; + for(j=0; j<n; j++){ + sqlite3VdbeAddOp3(v, OP_Column, pLevel->iIdxCur, j, r1+j); + } + pParse->nMem += n+1; + op = pLevel->op==OP_Prev ? OP_SeekLT : OP_SeekGT; + addrSeek = sqlite3VdbeAddOp4Int(v, op, pLevel->iIdxCur, 0, r1, n); + VdbeCoverageIf(v, op==OP_SeekLT); + VdbeCoverageIf(v, op==OP_SeekGT); + sqlite3VdbeAddOp2(v, OP_Goto, 1, pLevel->p2); + } +#endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */ + /* The common case: Advance to the next row */ + if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont); + sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3); + sqlite3VdbeChangeP5(v, pLevel->p5); + VdbeCoverage(v); + VdbeCoverageIf(v, pLevel->op==OP_Next); + VdbeCoverageIf(v, pLevel->op==OP_Prev); + VdbeCoverageIf(v, pLevel->op==OP_VNext); + if( pLevel->regBignull ){ + sqlite3VdbeResolveLabel(v, pLevel->addrBignull); + sqlite3VdbeAddOp2(v, OP_DecrJumpZero, pLevel->regBignull, pLevel->p2-1); + VdbeCoverage(v); + } +#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT + if( addrSeek ) sqlite3VdbeJumpHere(v, addrSeek); +#endif + }else if( pLevel->addrCont ){ + sqlite3VdbeResolveLabel(v, pLevel->addrCont); + } + if( (pLoop->wsFlags & WHERE_IN_ABLE)!=0 && pLevel->u.in.nIn>0 ){ + struct InLoop *pIn; + int j; + sqlite3VdbeResolveLabel(v, pLevel->addrNxt); + for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){ + assert( sqlite3VdbeGetOp(v, pIn->addrInTop+1)->opcode==OP_IsNull + || pParse->db->mallocFailed ); + sqlite3VdbeJumpHere(v, pIn->addrInTop+1); + if( pIn->eEndLoopOp!=OP_Noop ){ + if( pIn->nPrefix ){ + int bEarlyOut = + (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 + && (pLoop->wsFlags & WHERE_IN_EARLYOUT)!=0; + if( pLevel->iLeftJoin ){ + /* For LEFT JOIN queries, cursor pIn->iCur may not have been + ** opened yet. This occurs for WHERE clauses such as + ** "a = ? AND b IN (...)", where the index is on (a, b). If + ** the RHS of the (a=?) is NULL, then the "b IN (...)" may + ** never have been coded, but the body of the loop run to + ** return the null-row. So, if the cursor is not open yet, + ** jump over the OP_Next or OP_Prev instruction about to + ** be coded. */ + sqlite3VdbeAddOp2(v, OP_IfNotOpen, pIn->iCur, + sqlite3VdbeCurrentAddr(v) + 2 + bEarlyOut); + VdbeCoverage(v); + } + if( bEarlyOut ){ + sqlite3VdbeAddOp4Int(v, OP_IfNoHope, pLevel->iIdxCur, + sqlite3VdbeCurrentAddr(v)+2, + pIn->iBase, pIn->nPrefix); + VdbeCoverage(v); + /* Retarget the OP_IsNull against the left operand of IN so + ** it jumps past the OP_IfNoHope. This is because the + ** OP_IsNull also bypasses the OP_Affinity opcode that is + ** required by OP_IfNoHope. */ + sqlite3VdbeJumpHere(v, pIn->addrInTop+1); + } + } + sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop); + VdbeCoverage(v); + VdbeCoverageIf(v, pIn->eEndLoopOp==OP_Prev); + VdbeCoverageIf(v, pIn->eEndLoopOp==OP_Next); + } + sqlite3VdbeJumpHere(v, pIn->addrInTop-1); + } + } + sqlite3VdbeResolveLabel(v, pLevel->addrBrk); + if( pLevel->pRJ ){ + sqlite3VdbeAddOp3(v, OP_Return, pLevel->pRJ->regReturn, 0, 1); + VdbeCoverage(v); + } + if( pLevel->addrSkip ){ + sqlite3VdbeGoto(v, pLevel->addrSkip); + VdbeComment((v, "next skip-scan on %s", pLoop->u.btree.pIndex->zName)); + sqlite3VdbeJumpHere(v, pLevel->addrSkip); + sqlite3VdbeJumpHere(v, pLevel->addrSkip-2); + } +#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS + if( pLevel->addrLikeRep ){ + sqlite3VdbeAddOp2(v, OP_DecrJumpZero, (int)(pLevel->iLikeRepCntr>>1), + pLevel->addrLikeRep); + VdbeCoverage(v); + } +#endif + if( pLevel->iLeftJoin ){ + int ws = pLoop->wsFlags; + addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v); + assert( (ws & WHERE_IDX_ONLY)==0 || (ws & WHERE_INDEXED)!=0 ); + if( (ws & WHERE_IDX_ONLY)==0 ){ + assert( pLevel->iTabCur==pTabList->a[pLevel->iFrom].iCursor ); + sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur); + } + if( (ws & WHERE_INDEXED) + || ((ws & WHERE_MULTI_OR) && pLevel->u.pCoveringIdx) + ){ + if( ws & WHERE_MULTI_OR ){ + Index *pIx = pLevel->u.pCoveringIdx; + int iDb = sqlite3SchemaToIndex(db, pIx->pSchema); + sqlite3VdbeAddOp3(v, OP_ReopenIdx, pLevel->iIdxCur, pIx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIx); + } + sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur); + } + if( pLevel->op==OP_Return ){ + sqlite3VdbeAddOp2(v, OP_Gosub, pLevel->p1, pLevel->addrFirst); + }else{ + sqlite3VdbeGoto(v, pLevel->addrFirst); + } + sqlite3VdbeJumpHere(v, addr); + } + VdbeModuleComment((v, "End WHERE-loop%d: %s", i, + pWInfo->pTabList->a[pLevel->iFrom].pTab->zName)); + } + + assert( pWInfo->nLevel<=pTabList->nSrc ); + for(i=0, pLevel=pWInfo->a; i<pWInfo->nLevel; i++, pLevel++){ + int k, last; + VdbeOp *pOp, *pLastOp; + Index *pIdx = 0; + SrcItem *pTabItem = &pTabList->a[pLevel->iFrom]; + Table *pTab = pTabItem->pTab; + assert( pTab!=0 ); + pLoop = pLevel->pWLoop; + + /* Do RIGHT JOIN processing. Generate code that will output the + ** unmatched rows of the right operand of the RIGHT JOIN with + ** all of the columns of the left operand set to NULL. + */ + if( pLevel->pRJ ){ + sqlite3WhereRightJoinLoop(pWInfo, i, pLevel); + continue; + } + + /* For a co-routine, change all OP_Column references to the table of + ** the co-routine into OP_Copy of result contained in a register. + ** OP_Rowid becomes OP_Null. + */ + if( pTabItem->fg.viaCoroutine ){ + testcase( pParse->db->mallocFailed ); + translateColumnToCopy(pParse, pLevel->addrBody, pLevel->iTabCur, + pTabItem->regResult, 0); + continue; + } + + /* If this scan uses an index, make VDBE code substitutions to read data + ** from the index instead of from the table where possible. In some cases + ** this optimization prevents the table from ever being read, which can + ** yield a significant performance boost. + ** + ** Calls to the code generator in between sqlite3WhereBegin and + ** sqlite3WhereEnd will have created code that references the table + ** directly. This loop scans all that code looking for opcodes + ** that reference the table and converts them into opcodes that + ** reference the index. + */ + if( pLoop->wsFlags & (WHERE_INDEXED|WHERE_IDX_ONLY) ){ + pIdx = pLoop->u.btree.pIndex; + }else if( pLoop->wsFlags & WHERE_MULTI_OR ){ + pIdx = pLevel->u.pCoveringIdx; + } + if( pIdx + && !db->mallocFailed + ){ + if( pWInfo->eOnePass==ONEPASS_OFF || !HasRowid(pIdx->pTable) ){ + last = iEnd; + }else{ + last = pWInfo->iEndWhere; + } + if( pIdx->bHasExpr ){ + IndexedExpr *p = pParse->pIdxEpr; + while( p ){ + if( p->iIdxCur==pLevel->iIdxCur ){ +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x200 ){ + sqlite3DebugPrintf("Disable pParse->pIdxEpr term {%d,%d}\n", + p->iIdxCur, p->iIdxCol); + if( sqlite3WhereTrace & 0x5000 ) sqlite3ShowExpr(p->pExpr); + } +#endif + p->iDataCur = -1; + p->iIdxCur = -1; + } + p = p->pIENext; + } + } + k = pLevel->addrBody + 1; +#ifdef SQLITE_DEBUG + if( db->flags & SQLITE_VdbeAddopTrace ){ + printf("TRANSLATE cursor %d->%d in opcode range %d..%d\n", + pLevel->iTabCur, pLevel->iIdxCur, k, last-1); + } + /* Proof that the "+1" on the k value above is safe */ + pOp = sqlite3VdbeGetOp(v, k - 1); + assert( pOp->opcode!=OP_Column || pOp->p1!=pLevel->iTabCur ); + assert( pOp->opcode!=OP_Rowid || pOp->p1!=pLevel->iTabCur ); + assert( pOp->opcode!=OP_IfNullRow || pOp->p1!=pLevel->iTabCur ); +#endif + pOp = sqlite3VdbeGetOp(v, k); + pLastOp = pOp + (last - k); + assert( pOp<=pLastOp ); + do{ + if( pOp->p1!=pLevel->iTabCur ){ + /* no-op */ + }else if( pOp->opcode==OP_Column +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + || pOp->opcode==OP_Offset +#endif + ){ + int x = pOp->p2; + assert( pIdx->pTable==pTab ); +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + if( pOp->opcode==OP_Offset ){ + /* Do not need to translate the column number */ + }else +#endif + if( !HasRowid(pTab) ){ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + x = pPk->aiColumn[x]; + assert( x>=0 ); + }else{ + testcase( x!=sqlite3StorageColumnToTable(pTab,x) ); + x = sqlite3StorageColumnToTable(pTab,x); + } + x = sqlite3TableColumnToIndex(pIdx, x); + if( x>=0 ){ + pOp->p2 = x; + pOp->p1 = pLevel->iIdxCur; + OpcodeRewriteTrace(db, k, pOp); + }else{ + /* Unable to translate the table reference into an index + ** reference. Verify that this is harmless - that the + ** table being referenced really is open. + */ +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 + || cursorIsOpen(v,pOp->p1,k) + || pOp->opcode==OP_Offset + ); +#else + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 + || cursorIsOpen(v,pOp->p1,k) + ); +#endif + } + }else if( pOp->opcode==OP_Rowid ){ + pOp->p1 = pLevel->iIdxCur; + pOp->opcode = OP_IdxRowid; + OpcodeRewriteTrace(db, k, pOp); + }else if( pOp->opcode==OP_IfNullRow ){ + pOp->p1 = pLevel->iIdxCur; + OpcodeRewriteTrace(db, k, pOp); + } +#ifdef SQLITE_DEBUG + k++; +#endif + }while( (++pOp)<pLastOp ); +#ifdef SQLITE_DEBUG + if( db->flags & SQLITE_VdbeAddopTrace ) printf("TRANSLATE complete\n"); +#endif + } + } + + /* The "break" point is here, just past the end of the outer loop. + ** Set it. + */ + sqlite3VdbeResolveLabel(v, pWInfo->iBreak); + + /* Final cleanup + */ + pParse->nQueryLoop = pWInfo->savedNQueryLoop; + whereInfoFree(db, pWInfo); + pParse->withinRJSubrtn -= nRJ; + return; +} + +/************** End of where.c ***********************************************/ +/************** Begin file window.c ******************************************/ +/* +** 2018 May 08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +*/ +/* #include "sqliteInt.h" */ + +#ifndef SQLITE_OMIT_WINDOWFUNC + +/* +** SELECT REWRITING +** +** Any SELECT statement that contains one or more window functions in +** either the select list or ORDER BY clause (the only two places window +** functions may be used) is transformed by function sqlite3WindowRewrite() +** in order to support window function processing. For example, with the +** schema: +** +** CREATE TABLE t1(a, b, c, d, e, f, g); +** +** the statement: +** +** SELECT a+1, max(b) OVER (PARTITION BY c ORDER BY d) FROM t1 ORDER BY e; +** +** is transformed to: +** +** SELECT a+1, max(b) OVER (PARTITION BY c ORDER BY d) FROM ( +** SELECT a, e, c, d, b FROM t1 ORDER BY c, d +** ) ORDER BY e; +** +** The flattening optimization is disabled when processing this transformed +** SELECT statement. This allows the implementation of the window function +** (in this case max()) to process rows sorted in order of (c, d), which +** makes things easier for obvious reasons. More generally: +** +** * FROM, WHERE, GROUP BY and HAVING clauses are all moved to +** the sub-query. +** +** * ORDER BY, LIMIT and OFFSET remain part of the parent query. +** +** * Terminals from each of the expression trees that make up the +** select-list and ORDER BY expressions in the parent query are +** selected by the sub-query. For the purposes of the transformation, +** terminals are column references and aggregate functions. +** +** If there is more than one window function in the SELECT that uses +** the same window declaration (the OVER bit), then a single scan may +** be used to process more than one window function. For example: +** +** SELECT max(b) OVER (PARTITION BY c ORDER BY d), +** min(e) OVER (PARTITION BY c ORDER BY d) +** FROM t1; +** +** is transformed in the same way as the example above. However: +** +** SELECT max(b) OVER (PARTITION BY c ORDER BY d), +** min(e) OVER (PARTITION BY a ORDER BY b) +** FROM t1; +** +** Must be transformed to: +** +** SELECT max(b) OVER (PARTITION BY c ORDER BY d) FROM ( +** SELECT e, min(e) OVER (PARTITION BY a ORDER BY b), c, d, b FROM +** SELECT a, e, c, d, b FROM t1 ORDER BY a, b +** ) ORDER BY c, d +** ) ORDER BY e; +** +** so that both min() and max() may process rows in the order defined by +** their respective window declarations. +** +** INTERFACE WITH SELECT.C +** +** When processing the rewritten SELECT statement, code in select.c calls +** sqlite3WhereBegin() to begin iterating through the results of the +** sub-query, which is always implemented as a co-routine. It then calls +** sqlite3WindowCodeStep() to process rows and finish the scan by calling +** sqlite3WhereEnd(). +** +** sqlite3WindowCodeStep() generates VM code so that, for each row returned +** by the sub-query a sub-routine (OP_Gosub) coded by select.c is invoked. +** When the sub-routine is invoked: +** +** * The results of all window-functions for the row are stored +** in the associated Window.regResult registers. +** +** * The required terminal values are stored in the current row of +** temp table Window.iEphCsr. +** +** In some cases, depending on the window frame and the specific window +** functions invoked, sqlite3WindowCodeStep() caches each entire partition +** in a temp table before returning any rows. In other cases it does not. +** This detail is encapsulated within this file, the code generated by +** select.c is the same in either case. +** +** BUILT-IN WINDOW FUNCTIONS +** +** This implementation features the following built-in window functions: +** +** row_number() +** rank() +** dense_rank() +** percent_rank() +** cume_dist() +** ntile(N) +** lead(expr [, offset [, default]]) +** lag(expr [, offset [, default]]) +** first_value(expr) +** last_value(expr) +** nth_value(expr, N) +** +** These are the same built-in window functions supported by Postgres. +** Although the behaviour of aggregate window functions (functions that +** can be used as either aggregates or window functions) allows them to +** be implemented using an API, built-in window functions are much more +** esoteric. Additionally, some window functions (e.g. nth_value()) +** may only be implemented by caching the entire partition in memory. +** As such, some built-in window functions use the same API as aggregate +** window functions and some are implemented directly using VDBE +** instructions. Additionally, for those functions that use the API, the +** window frame is sometimes modified before the SELECT statement is +** rewritten. For example, regardless of the specified window frame, the +** row_number() function always uses: +** +** ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW +** +** See sqlite3WindowUpdate() for details. +** +** As well as some of the built-in window functions, aggregate window +** functions min() and max() are implemented using VDBE instructions if +** the start of the window frame is declared as anything other than +** UNBOUNDED PRECEDING. +*/ + +/* +** Implementation of built-in window function row_number(). Assumes that the +** window frame has been coerced to: +** +** ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW +*/ +static void row_numberStepFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + i64 *p = (i64*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ) (*p)++; + UNUSED_PARAMETER(nArg); + UNUSED_PARAMETER(apArg); +} +static void row_numberValueFunc(sqlite3_context *pCtx){ + i64 *p = (i64*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + sqlite3_result_int64(pCtx, (p ? *p : 0)); +} + +/* +** Context object type used by rank(), dense_rank(), percent_rank() and +** cume_dist(). +*/ +struct CallCount { + i64 nValue; + i64 nStep; + i64 nTotal; +}; + +/* +** Implementation of built-in window function dense_rank(). Assumes that +** the window frame has been set to: +** +** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW +*/ +static void dense_rankStepFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct CallCount *p; + p = (struct CallCount*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ) p->nStep = 1; + UNUSED_PARAMETER(nArg); + UNUSED_PARAMETER(apArg); +} +static void dense_rankValueFunc(sqlite3_context *pCtx){ + struct CallCount *p; + p = (struct CallCount*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ){ + if( p->nStep ){ + p->nValue++; + p->nStep = 0; + } + sqlite3_result_int64(pCtx, p->nValue); + } +} + +/* +** Implementation of built-in window function nth_value(). This +** implementation is used in "slow mode" only - when the EXCLUDE clause +** is not set to the default value "NO OTHERS". +*/ +struct NthValueCtx { + i64 nStep; + sqlite3_value *pValue; +}; +static void nth_valueStepFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct NthValueCtx *p; + p = (struct NthValueCtx*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ){ + i64 iVal; + switch( sqlite3_value_numeric_type(apArg[1]) ){ + case SQLITE_INTEGER: + iVal = sqlite3_value_int64(apArg[1]); + break; + case SQLITE_FLOAT: { + double fVal = sqlite3_value_double(apArg[1]); + if( ((i64)fVal)!=fVal ) goto error_out; + iVal = (i64)fVal; + break; + } + default: + goto error_out; + } + if( iVal<=0 ) goto error_out; + + p->nStep++; + if( iVal==p->nStep ){ + p->pValue = sqlite3_value_dup(apArg[0]); + if( !p->pValue ){ + sqlite3_result_error_nomem(pCtx); + } + } + } + UNUSED_PARAMETER(nArg); + UNUSED_PARAMETER(apArg); + return; + + error_out: + sqlite3_result_error( + pCtx, "second argument to nth_value must be a positive integer", -1 + ); +} +static void nth_valueFinalizeFunc(sqlite3_context *pCtx){ + struct NthValueCtx *p; + p = (struct NthValueCtx*)sqlite3_aggregate_context(pCtx, 0); + if( p && p->pValue ){ + sqlite3_result_value(pCtx, p->pValue); + sqlite3_value_free(p->pValue); + p->pValue = 0; + } +} +#define nth_valueInvFunc noopStepFunc +#define nth_valueValueFunc noopValueFunc + +static void first_valueStepFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct NthValueCtx *p; + p = (struct NthValueCtx*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p && p->pValue==0 ){ + p->pValue = sqlite3_value_dup(apArg[0]); + if( !p->pValue ){ + sqlite3_result_error_nomem(pCtx); + } + } + UNUSED_PARAMETER(nArg); + UNUSED_PARAMETER(apArg); +} +static void first_valueFinalizeFunc(sqlite3_context *pCtx){ + struct NthValueCtx *p; + p = (struct NthValueCtx*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p && p->pValue ){ + sqlite3_result_value(pCtx, p->pValue); + sqlite3_value_free(p->pValue); + p->pValue = 0; + } +} +#define first_valueInvFunc noopStepFunc +#define first_valueValueFunc noopValueFunc + +/* +** Implementation of built-in window function rank(). Assumes that +** the window frame has been set to: +** +** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW +*/ +static void rankStepFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct CallCount *p; + p = (struct CallCount*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ){ + p->nStep++; + if( p->nValue==0 ){ + p->nValue = p->nStep; + } + } + UNUSED_PARAMETER(nArg); + UNUSED_PARAMETER(apArg); +} +static void rankValueFunc(sqlite3_context *pCtx){ + struct CallCount *p; + p = (struct CallCount*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ){ + sqlite3_result_int64(pCtx, p->nValue); + p->nValue = 0; + } +} + +/* +** Implementation of built-in window function percent_rank(). Assumes that +** the window frame has been set to: +** +** GROUPS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +*/ +static void percent_rankStepFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct CallCount *p; + UNUSED_PARAMETER(nArg); assert( nArg==0 ); + UNUSED_PARAMETER(apArg); + p = (struct CallCount*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ){ + p->nTotal++; + } +} +static void percent_rankInvFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct CallCount *p; + UNUSED_PARAMETER(nArg); assert( nArg==0 ); + UNUSED_PARAMETER(apArg); + p = (struct CallCount*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + p->nStep++; +} +static void percent_rankValueFunc(sqlite3_context *pCtx){ + struct CallCount *p; + p = (struct CallCount*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ){ + p->nValue = p->nStep; + if( p->nTotal>1 ){ + double r = (double)p->nValue / (double)(p->nTotal-1); + sqlite3_result_double(pCtx, r); + }else{ + sqlite3_result_double(pCtx, 0.0); + } + } +} +#define percent_rankFinalizeFunc percent_rankValueFunc + +/* +** Implementation of built-in window function cume_dist(). Assumes that +** the window frame has been set to: +** +** GROUPS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING +*/ +static void cume_distStepFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct CallCount *p; + UNUSED_PARAMETER(nArg); assert( nArg==0 ); + UNUSED_PARAMETER(apArg); + p = (struct CallCount*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ){ + p->nTotal++; + } +} +static void cume_distInvFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct CallCount *p; + UNUSED_PARAMETER(nArg); assert( nArg==0 ); + UNUSED_PARAMETER(apArg); + p = (struct CallCount*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + p->nStep++; +} +static void cume_distValueFunc(sqlite3_context *pCtx){ + struct CallCount *p; + p = (struct CallCount*)sqlite3_aggregate_context(pCtx, 0); + if( p ){ + double r = (double)(p->nStep) / (double)(p->nTotal); + sqlite3_result_double(pCtx, r); + } +} +#define cume_distFinalizeFunc cume_distValueFunc + +/* +** Context object for ntile() window function. +*/ +struct NtileCtx { + i64 nTotal; /* Total rows in partition */ + i64 nParam; /* Parameter passed to ntile(N) */ + i64 iRow; /* Current row */ +}; + +/* +** Implementation of ntile(). This assumes that the window frame has +** been coerced to: +** +** ROWS CURRENT ROW AND UNBOUNDED FOLLOWING +*/ +static void ntileStepFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct NtileCtx *p; + assert( nArg==1 ); UNUSED_PARAMETER(nArg); + p = (struct NtileCtx*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ){ + if( p->nTotal==0 ){ + p->nParam = sqlite3_value_int64(apArg[0]); + if( p->nParam<=0 ){ + sqlite3_result_error( + pCtx, "argument of ntile must be a positive integer", -1 + ); + } + } + p->nTotal++; + } +} +static void ntileInvFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct NtileCtx *p; + assert( nArg==1 ); UNUSED_PARAMETER(nArg); + UNUSED_PARAMETER(apArg); + p = (struct NtileCtx*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + p->iRow++; +} +static void ntileValueFunc(sqlite3_context *pCtx){ + struct NtileCtx *p; + p = (struct NtileCtx*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p && p->nParam>0 ){ + int nSize = (p->nTotal / p->nParam); + if( nSize==0 ){ + sqlite3_result_int64(pCtx, p->iRow+1); + }else{ + i64 nLarge = p->nTotal - p->nParam*nSize; + i64 iSmall = nLarge*(nSize+1); + i64 iRow = p->iRow; + + assert( (nLarge*(nSize+1) + (p->nParam-nLarge)*nSize)==p->nTotal ); + + if( iRow<iSmall ){ + sqlite3_result_int64(pCtx, 1 + iRow/(nSize+1)); + }else{ + sqlite3_result_int64(pCtx, 1 + nLarge + (iRow-iSmall)/nSize); + } + } + } +} +#define ntileFinalizeFunc ntileValueFunc + +/* +** Context object for last_value() window function. +*/ +struct LastValueCtx { + sqlite3_value *pVal; + int nVal; +}; + +/* +** Implementation of last_value(). +*/ +static void last_valueStepFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct LastValueCtx *p; + UNUSED_PARAMETER(nArg); + p = (struct LastValueCtx*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p ){ + sqlite3_value_free(p->pVal); + p->pVal = sqlite3_value_dup(apArg[0]); + if( p->pVal==0 ){ + sqlite3_result_error_nomem(pCtx); + }else{ + p->nVal++; + } + } +} +static void last_valueInvFunc( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **apArg +){ + struct LastValueCtx *p; + UNUSED_PARAMETER(nArg); + UNUSED_PARAMETER(apArg); + p = (struct LastValueCtx*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( ALWAYS(p) ){ + p->nVal--; + if( p->nVal==0 ){ + sqlite3_value_free(p->pVal); + p->pVal = 0; + } + } +} +static void last_valueValueFunc(sqlite3_context *pCtx){ + struct LastValueCtx *p; + p = (struct LastValueCtx*)sqlite3_aggregate_context(pCtx, 0); + if( p && p->pVal ){ + sqlite3_result_value(pCtx, p->pVal); + } +} +static void last_valueFinalizeFunc(sqlite3_context *pCtx){ + struct LastValueCtx *p; + p = (struct LastValueCtx*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p && p->pVal ){ + sqlite3_result_value(pCtx, p->pVal); + sqlite3_value_free(p->pVal); + p->pVal = 0; + } +} + +/* +** Static names for the built-in window function names. These static +** names are used, rather than string literals, so that FuncDef objects +** can be associated with a particular window function by direct +** comparison of the zName pointer. Example: +** +** if( pFuncDef->zName==row_valueName ){ ... } +*/ +static const char row_numberName[] = "row_number"; +static const char dense_rankName[] = "dense_rank"; +static const char rankName[] = "rank"; +static const char percent_rankName[] = "percent_rank"; +static const char cume_distName[] = "cume_dist"; +static const char ntileName[] = "ntile"; +static const char last_valueName[] = "last_value"; +static const char nth_valueName[] = "nth_value"; +static const char first_valueName[] = "first_value"; +static const char leadName[] = "lead"; +static const char lagName[] = "lag"; + +/* +** No-op implementations of xStep() and xFinalize(). Used as place-holders +** for built-in window functions that never call those interfaces. +** +** The noopValueFunc() is called but is expected to do nothing. The +** noopStepFunc() is never called, and so it is marked with NO_TEST to +** let the test coverage routine know not to expect this function to be +** invoked. +*/ +static void noopStepFunc( /*NO_TEST*/ + sqlite3_context *p, /*NO_TEST*/ + int n, /*NO_TEST*/ + sqlite3_value **a /*NO_TEST*/ +){ /*NO_TEST*/ + UNUSED_PARAMETER(p); /*NO_TEST*/ + UNUSED_PARAMETER(n); /*NO_TEST*/ + UNUSED_PARAMETER(a); /*NO_TEST*/ + assert(0); /*NO_TEST*/ +} /*NO_TEST*/ +static void noopValueFunc(sqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ } + +/* Window functions that use all window interfaces: xStep, xFinal, +** xValue, and xInverse */ +#define WINDOWFUNCALL(name,nArg,extra) { \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + name ## StepFunc, name ## FinalizeFunc, name ## ValueFunc, \ + name ## InvFunc, name ## Name, {0} \ +} + +/* Window functions that are implemented using bytecode and thus have +** no-op routines for their methods */ +#define WINDOWFUNCNOOP(name,nArg,extra) { \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + noopStepFunc, noopValueFunc, noopValueFunc, \ + noopStepFunc, name ## Name, {0} \ +} + +/* Window functions that use all window interfaces: xStep, the +** same routine for xFinalize and xValue and which never call +** xInverse. */ +#define WINDOWFUNCX(name,nArg,extra) { \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + name ## StepFunc, name ## ValueFunc, name ## ValueFunc, \ + noopStepFunc, name ## Name, {0} \ +} + + +/* +** Register those built-in window functions that are not also aggregates. +*/ +SQLITE_PRIVATE void sqlite3WindowFunctions(void){ + static FuncDef aWindowFuncs[] = { + WINDOWFUNCX(row_number, 0, 0), + WINDOWFUNCX(dense_rank, 0, 0), + WINDOWFUNCX(rank, 0, 0), + WINDOWFUNCALL(percent_rank, 0, 0), + WINDOWFUNCALL(cume_dist, 0, 0), + WINDOWFUNCALL(ntile, 1, 0), + WINDOWFUNCALL(last_value, 1, 0), + WINDOWFUNCALL(nth_value, 2, 0), + WINDOWFUNCALL(first_value, 1, 0), + WINDOWFUNCNOOP(lead, 1, 0), + WINDOWFUNCNOOP(lead, 2, 0), + WINDOWFUNCNOOP(lead, 3, 0), + WINDOWFUNCNOOP(lag, 1, 0), + WINDOWFUNCNOOP(lag, 2, 0), + WINDOWFUNCNOOP(lag, 3, 0), + }; + sqlite3InsertBuiltinFuncs(aWindowFuncs, ArraySize(aWindowFuncs)); +} + +static Window *windowFind(Parse *pParse, Window *pList, const char *zName){ + Window *p; + for(p=pList; p; p=p->pNextWin){ + if( sqlite3StrICmp(p->zName, zName)==0 ) break; + } + if( p==0 ){ + sqlite3ErrorMsg(pParse, "no such window: %s", zName); + } + return p; +} + +/* +** This function is called immediately after resolving the function name +** for a window function within a SELECT statement. Argument pList is a +** linked list of WINDOW definitions for the current SELECT statement. +** Argument pFunc is the function definition just resolved and pWin +** is the Window object representing the associated OVER clause. This +** function updates the contents of pWin as follows: +** +** * If the OVER clause referred to a named window (as in "max(x) OVER win"), +** search list pList for a matching WINDOW definition, and update pWin +** accordingly. If no such WINDOW clause can be found, leave an error +** in pParse. +** +** * If the function is a built-in window function that requires the +** window to be coerced (see "BUILT-IN WINDOW FUNCTIONS" at the top +** of this file), pWin is updated here. +*/ +SQLITE_PRIVATE void sqlite3WindowUpdate( + Parse *pParse, + Window *pList, /* List of named windows for this SELECT */ + Window *pWin, /* Window frame to update */ + FuncDef *pFunc /* Window function definition */ +){ + if( pWin->zName && pWin->eFrmType==0 ){ + Window *p = windowFind(pParse, pList, pWin->zName); + if( p==0 ) return; + pWin->pPartition = sqlite3ExprListDup(pParse->db, p->pPartition, 0); + pWin->pOrderBy = sqlite3ExprListDup(pParse->db, p->pOrderBy, 0); + pWin->pStart = sqlite3ExprDup(pParse->db, p->pStart, 0); + pWin->pEnd = sqlite3ExprDup(pParse->db, p->pEnd, 0); + pWin->eStart = p->eStart; + pWin->eEnd = p->eEnd; + pWin->eFrmType = p->eFrmType; + pWin->eExclude = p->eExclude; + }else{ + sqlite3WindowChain(pParse, pWin, pList); + } + if( (pWin->eFrmType==TK_RANGE) + && (pWin->pStart || pWin->pEnd) + && (pWin->pOrderBy==0 || pWin->pOrderBy->nExpr!=1) + ){ + sqlite3ErrorMsg(pParse, + "RANGE with offset PRECEDING/FOLLOWING requires one ORDER BY expression" + ); + }else + if( pFunc->funcFlags & SQLITE_FUNC_WINDOW ){ + sqlite3 *db = pParse->db; + if( pWin->pFilter ){ + sqlite3ErrorMsg(pParse, + "FILTER clause may only be used with aggregate window functions" + ); + }else{ + struct WindowUpdate { + const char *zFunc; + int eFrmType; + int eStart; + int eEnd; + } aUp[] = { + { row_numberName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT }, + { dense_rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT }, + { rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT }, + { percent_rankName, TK_GROUPS, TK_CURRENT, TK_UNBOUNDED }, + { cume_distName, TK_GROUPS, TK_FOLLOWING, TK_UNBOUNDED }, + { ntileName, TK_ROWS, TK_CURRENT, TK_UNBOUNDED }, + { leadName, TK_ROWS, TK_UNBOUNDED, TK_UNBOUNDED }, + { lagName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT }, + }; + int i; + for(i=0; i<ArraySize(aUp); i++){ + if( pFunc->zName==aUp[i].zFunc ){ + sqlite3ExprDelete(db, pWin->pStart); + sqlite3ExprDelete(db, pWin->pEnd); + pWin->pEnd = pWin->pStart = 0; + pWin->eFrmType = aUp[i].eFrmType; + pWin->eStart = aUp[i].eStart; + pWin->eEnd = aUp[i].eEnd; + pWin->eExclude = 0; + if( pWin->eStart==TK_FOLLOWING ){ + pWin->pStart = sqlite3Expr(db, TK_INTEGER, "1"); + } + break; + } + } + } + } + pWin->pWFunc = pFunc; +} + +/* +** Context object passed through sqlite3WalkExprList() to +** selectWindowRewriteExprCb() by selectWindowRewriteEList(). +*/ +typedef struct WindowRewrite WindowRewrite; +struct WindowRewrite { + Window *pWin; + SrcList *pSrc; + ExprList *pSub; + Table *pTab; + Select *pSubSelect; /* Current sub-select, if any */ +}; + +/* +** Callback function used by selectWindowRewriteEList(). If necessary, +** this function appends to the output expression-list and updates +** expression (*ppExpr) in place. +*/ +static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){ + struct WindowRewrite *p = pWalker->u.pRewrite; + Parse *pParse = pWalker->pParse; + assert( p!=0 ); + assert( p->pWin!=0 ); + + /* If this function is being called from within a scalar sub-select + ** that used by the SELECT statement being processed, only process + ** TK_COLUMN expressions that refer to it (the outer SELECT). Do + ** not process aggregates or window functions at all, as they belong + ** to the scalar sub-select. */ + if( p->pSubSelect ){ + if( pExpr->op!=TK_COLUMN ){ + return WRC_Continue; + }else{ + int nSrc = p->pSrc->nSrc; + int i; + for(i=0; i<nSrc; i++){ + if( pExpr->iTable==p->pSrc->a[i].iCursor ) break; + } + if( i==nSrc ) return WRC_Continue; + } + } + + switch( pExpr->op ){ + + case TK_FUNCTION: + if( !ExprHasProperty(pExpr, EP_WinFunc) ){ + break; + }else{ + Window *pWin; + for(pWin=p->pWin; pWin; pWin=pWin->pNextWin){ + if( pExpr->y.pWin==pWin ){ + assert( pWin->pOwner==pExpr ); + return WRC_Prune; + } + } + } + /* no break */ deliberate_fall_through + + case TK_IF_NULL_ROW: + case TK_AGG_FUNCTION: + case TK_COLUMN: { + int iCol = -1; + if( pParse->db->mallocFailed ) return WRC_Abort; + if( p->pSub ){ + int i; + for(i=0; i<p->pSub->nExpr; i++){ + if( 0==sqlite3ExprCompare(0, p->pSub->a[i].pExpr, pExpr, -1) ){ + iCol = i; + break; + } + } + } + if( iCol<0 ){ + Expr *pDup = sqlite3ExprDup(pParse->db, pExpr, 0); + if( pDup && pDup->op==TK_AGG_FUNCTION ) pDup->op = TK_FUNCTION; + p->pSub = sqlite3ExprListAppend(pParse, p->pSub, pDup); + } + if( p->pSub ){ + int f = pExpr->flags & EP_Collate; + assert( ExprHasProperty(pExpr, EP_Static)==0 ); + ExprSetProperty(pExpr, EP_Static); + sqlite3ExprDelete(pParse->db, pExpr); + ExprClearProperty(pExpr, EP_Static); + memset(pExpr, 0, sizeof(Expr)); + + pExpr->op = TK_COLUMN; + pExpr->iColumn = (iCol<0 ? p->pSub->nExpr-1: iCol); + pExpr->iTable = p->pWin->iEphCsr; + pExpr->y.pTab = p->pTab; + pExpr->flags = f; + } + if( pParse->db->mallocFailed ) return WRC_Abort; + break; + } + + default: /* no-op */ + break; + } + + return WRC_Continue; +} +static int selectWindowRewriteSelectCb(Walker *pWalker, Select *pSelect){ + struct WindowRewrite *p = pWalker->u.pRewrite; + Select *pSave = p->pSubSelect; + if( pSave==pSelect ){ + return WRC_Continue; + }else{ + p->pSubSelect = pSelect; + sqlite3WalkSelect(pWalker, pSelect); + p->pSubSelect = pSave; + } + return WRC_Prune; +} + + +/* +** Iterate through each expression in expression-list pEList. For each: +** +** * TK_COLUMN, +** * aggregate function, or +** * window function with a Window object that is not a member of the +** Window list passed as the second argument (pWin). +** +** Append the node to output expression-list (*ppSub). And replace it +** with a TK_COLUMN that reads the (N-1)th element of table +** pWin->iEphCsr, where N is the number of elements in (*ppSub) after +** appending the new one. +*/ +static void selectWindowRewriteEList( + Parse *pParse, + Window *pWin, + SrcList *pSrc, + ExprList *pEList, /* Rewrite expressions in this list */ + Table *pTab, + ExprList **ppSub /* IN/OUT: Sub-select expression-list */ +){ + Walker sWalker; + WindowRewrite sRewrite; + + assert( pWin!=0 ); + memset(&sWalker, 0, sizeof(Walker)); + memset(&sRewrite, 0, sizeof(WindowRewrite)); + + sRewrite.pSub = *ppSub; + sRewrite.pWin = pWin; + sRewrite.pSrc = pSrc; + sRewrite.pTab = pTab; + + sWalker.pParse = pParse; + sWalker.xExprCallback = selectWindowRewriteExprCb; + sWalker.xSelectCallback = selectWindowRewriteSelectCb; + sWalker.u.pRewrite = &sRewrite; + + (void)sqlite3WalkExprList(&sWalker, pEList); + + *ppSub = sRewrite.pSub; +} + +/* +** Append a copy of each expression in expression-list pAppend to +** expression list pList. Return a pointer to the result list. +*/ +static ExprList *exprListAppendList( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* List to which to append. Might be NULL */ + ExprList *pAppend, /* List of values to append. Might be NULL */ + int bIntToNull +){ + if( pAppend ){ + int i; + int nInit = pList ? pList->nExpr : 0; + for(i=0; i<pAppend->nExpr; i++){ + sqlite3 *db = pParse->db; + Expr *pDup = sqlite3ExprDup(db, pAppend->a[i].pExpr, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDup); + break; + } + if( bIntToNull ){ + int iDummy; + Expr *pSub; + pSub = sqlite3ExprSkipCollateAndLikely(pDup); + if( sqlite3ExprIsInteger(pSub, &iDummy) ){ + pSub->op = TK_NULL; + pSub->flags &= ~(EP_IntValue|EP_IsTrue|EP_IsFalse); + pSub->u.zToken = 0; + } + } + pList = sqlite3ExprListAppend(pParse, pList, pDup); + if( pList ) pList->a[nInit+i].fg.sortFlags = pAppend->a[i].fg.sortFlags; + } + } + return pList; +} + +/* +** When rewriting a query, if the new subquery in the FROM clause +** contains TK_AGG_FUNCTION nodes that refer to an outer query, +** then we have to increase the Expr->op2 values of those nodes +** due to the extra subquery layer that was added. +** +** See also the incrAggDepth() routine in resolve.c +*/ +static int sqlite3WindowExtraAggFuncDepth(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_AGG_FUNCTION + && pExpr->op2>=pWalker->walkerDepth + ){ + pExpr->op2++; + } + return WRC_Continue; +} + +static int disallowAggregatesInOrderByCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_AGG_FUNCTION && pExpr->pAggInfo==0 ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3ErrorMsg(pWalker->pParse, + "misuse of aggregate: %s()", pExpr->u.zToken); + } + return WRC_Continue; +} + +/* +** If the SELECT statement passed as the second argument does not invoke +** any SQL window functions, this function is a no-op. Otherwise, it +** rewrites the SELECT statement so that window function xStep functions +** are invoked in the correct order as described under "SELECT REWRITING" +** at the top of this file. +*/ +SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ + int rc = SQLITE_OK; + if( p->pWin + && p->pPrior==0 + && ALWAYS((p->selFlags & SF_WinRewrite)==0) + && ALWAYS(!IN_RENAME_OBJECT) + ){ + Vdbe *v = sqlite3GetVdbe(pParse); + sqlite3 *db = pParse->db; + Select *pSub = 0; /* The subquery */ + SrcList *pSrc = p->pSrc; + Expr *pWhere = p->pWhere; + ExprList *pGroupBy = p->pGroupBy; + Expr *pHaving = p->pHaving; + ExprList *pSort = 0; + + ExprList *pSublist = 0; /* Expression list for sub-query */ + Window *pMWin = p->pWin; /* Main window object */ + Window *pWin; /* Window object iterator */ + Table *pTab; + Walker w; + + u32 selFlags = p->selFlags; + + pTab = sqlite3DbMallocZero(db, sizeof(Table)); + if( pTab==0 ){ + return sqlite3ErrorToParser(db, SQLITE_NOMEM); + } + sqlite3AggInfoPersistWalkerInit(&w, pParse); + sqlite3WalkSelect(&w, p); + if( (p->selFlags & SF_Aggregate)==0 ){ + w.xExprCallback = disallowAggregatesInOrderByCb; + w.xSelectCallback = 0; + sqlite3WalkExprList(&w, p->pOrderBy); + } + + p->pSrc = 0; + p->pWhere = 0; + p->pGroupBy = 0; + p->pHaving = 0; + p->selFlags &= ~SF_Aggregate; + p->selFlags |= SF_WinRewrite; + + /* Create the ORDER BY clause for the sub-select. This is the concatenation + ** of the window PARTITION and ORDER BY clauses. Then, if this makes it + ** redundant, remove the ORDER BY from the parent SELECT. */ + pSort = exprListAppendList(pParse, 0, pMWin->pPartition, 1); + pSort = exprListAppendList(pParse, pSort, pMWin->pOrderBy, 1); + if( pSort && p->pOrderBy && p->pOrderBy->nExpr<=pSort->nExpr ){ + int nSave = pSort->nExpr; + pSort->nExpr = p->pOrderBy->nExpr; + if( sqlite3ExprListCompare(pSort, p->pOrderBy, -1)==0 ){ + sqlite3ExprListDelete(db, p->pOrderBy); + p->pOrderBy = 0; + } + pSort->nExpr = nSave; + } + + /* Assign a cursor number for the ephemeral table used to buffer rows. + ** The OpenEphemeral instruction is coded later, after it is known how + ** many columns the table will have. */ + pMWin->iEphCsr = pParse->nTab++; + pParse->nTab += 3; + + selectWindowRewriteEList(pParse, pMWin, pSrc, p->pEList, pTab, &pSublist); + selectWindowRewriteEList(pParse, pMWin, pSrc, p->pOrderBy, pTab, &pSublist); + pMWin->nBufferCol = (pSublist ? pSublist->nExpr : 0); + + /* Append the PARTITION BY and ORDER BY expressions to the to the + ** sub-select expression list. They are required to figure out where + ** boundaries for partitions and sets of peer rows lie. */ + pSublist = exprListAppendList(pParse, pSublist, pMWin->pPartition, 0); + pSublist = exprListAppendList(pParse, pSublist, pMWin->pOrderBy, 0); + + /* Append the arguments passed to each window function to the + ** sub-select expression list. Also allocate two registers for each + ** window function - one for the accumulator, another for interim + ** results. */ + for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ + ExprList *pArgs; + assert( ExprUseXList(pWin->pOwner) ); + assert( pWin->pWFunc!=0 ); + pArgs = pWin->pOwner->x.pList; + if( pWin->pWFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){ + selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist); + pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); + pWin->bExprArgs = 1; + }else{ + pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); + pSublist = exprListAppendList(pParse, pSublist, pArgs, 0); + } + if( pWin->pFilter ){ + Expr *pFilter = sqlite3ExprDup(db, pWin->pFilter, 0); + pSublist = sqlite3ExprListAppend(pParse, pSublist, pFilter); + } + pWin->regAccum = ++pParse->nMem; + pWin->regResult = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); + } + + /* If there is no ORDER BY or PARTITION BY clause, and the window + ** function accepts zero arguments, and there are no other columns + ** selected (e.g. "SELECT row_number() OVER () FROM t1"), it is possible + ** that pSublist is still NULL here. Add a constant expression here to + ** keep everything legal in this case. + */ + if( pSublist==0 ){ + pSublist = sqlite3ExprListAppend(pParse, 0, + sqlite3Expr(db, TK_INTEGER, "0") + ); + } + + pSub = sqlite3SelectNew( + pParse, pSublist, pSrc, pWhere, pGroupBy, pHaving, pSort, 0, 0 + ); + TREETRACE(0x40,pParse,pSub, + ("New window-function subquery in FROM clause of (%u/%p)\n", + p->selId, p)); + p->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + assert( pSub!=0 || p->pSrc==0 ); /* Due to db->mallocFailed test inside + ** of sqlite3DbMallocRawNN() called from + ** sqlite3SrcListAppend() */ + if( p->pSrc ){ + Table *pTab2; + p->pSrc->a[0].pSelect = pSub; + p->pSrc->a[0].fg.isCorrelated = 1; + sqlite3SrcListAssignCursors(pParse, p->pSrc); + pSub->selFlags |= SF_Expanded|SF_OrderByReqd; + pTab2 = sqlite3ResultSetOfSelect(pParse, pSub, SQLITE_AFF_NONE); + pSub->selFlags |= (selFlags & SF_Aggregate); + if( pTab2==0 ){ + /* Might actually be some other kind of error, but in that case + ** pParse->nErr will be set, so if SQLITE_NOMEM is set, we will get + ** the correct error message regardless. */ + rc = SQLITE_NOMEM; + }else{ + memcpy(pTab, pTab2, sizeof(Table)); + pTab->tabFlags |= TF_Ephemeral; + p->pSrc->a[0].pTab = pTab; + pTab = pTab2; + memset(&w, 0, sizeof(w)); + w.xExprCallback = sqlite3WindowExtraAggFuncDepth; + w.xSelectCallback = sqlite3WalkerDepthIncrease; + w.xSelectCallback2 = sqlite3WalkerDepthDecrease; + sqlite3WalkSelect(&w, pSub); + } + }else{ + sqlite3SelectDelete(db, pSub); + } + if( db->mallocFailed ) rc = SQLITE_NOMEM; + + /* Defer deleting the temporary table pTab because if an error occurred, + ** there could still be references to that table embedded in the + ** result-set or ORDER BY clause of the SELECT statement p. */ + sqlite3ParserAddCleanup(pParse, sqlite3DbFree, pTab); + } + + assert( rc==SQLITE_OK || pParse->nErr!=0 ); + return rc; +} + +/* +** Unlink the Window object from the Select to which it is attached, +** if it is attached. +*/ +SQLITE_PRIVATE void sqlite3WindowUnlinkFromSelect(Window *p){ + if( p->ppThis ){ + *p->ppThis = p->pNextWin; + if( p->pNextWin ) p->pNextWin->ppThis = p->ppThis; + p->ppThis = 0; + } +} + +/* +** Free the Window object passed as the second argument. +*/ +SQLITE_PRIVATE void sqlite3WindowDelete(sqlite3 *db, Window *p){ + if( p ){ + sqlite3WindowUnlinkFromSelect(p); + sqlite3ExprDelete(db, p->pFilter); + sqlite3ExprListDelete(db, p->pPartition); + sqlite3ExprListDelete(db, p->pOrderBy); + sqlite3ExprDelete(db, p->pEnd); + sqlite3ExprDelete(db, p->pStart); + sqlite3DbFree(db, p->zName); + sqlite3DbFree(db, p->zBase); + sqlite3DbFree(db, p); + } +} + +/* +** Free the linked list of Window objects starting at the second argument. +*/ +SQLITE_PRIVATE void sqlite3WindowListDelete(sqlite3 *db, Window *p){ + while( p ){ + Window *pNext = p->pNextWin; + sqlite3WindowDelete(db, p); + p = pNext; + } +} + +/* +** The argument expression is an PRECEDING or FOLLOWING offset. The +** value should be a non-negative integer. If the value is not a +** constant, change it to NULL. The fact that it is then a non-negative +** integer will be caught later. But it is important not to leave +** variable values in the expression tree. +*/ +static Expr *sqlite3WindowOffsetExpr(Parse *pParse, Expr *pExpr){ + if( 0==sqlite3ExprIsConstant(pExpr) ){ + if( IN_RENAME_OBJECT ) sqlite3RenameExprUnmap(pParse, pExpr); + sqlite3ExprDelete(pParse->db, pExpr); + pExpr = sqlite3ExprAlloc(pParse->db, TK_NULL, 0, 0); + } + return pExpr; +} + +/* +** Allocate and return a new Window object describing a Window Definition. +*/ +SQLITE_PRIVATE Window *sqlite3WindowAlloc( + Parse *pParse, /* Parsing context */ + int eType, /* Frame type. TK_RANGE, TK_ROWS, TK_GROUPS, or 0 */ + int eStart, /* Start type: CURRENT, PRECEDING, FOLLOWING, UNBOUNDED */ + Expr *pStart, /* Start window size if TK_PRECEDING or FOLLOWING */ + int eEnd, /* End type: CURRENT, FOLLOWING, TK_UNBOUNDED, PRECEDING */ + Expr *pEnd, /* End window size if TK_FOLLOWING or PRECEDING */ + u8 eExclude /* EXCLUDE clause */ +){ + Window *pWin = 0; + int bImplicitFrame = 0; + + /* Parser assures the following: */ + assert( eType==0 || eType==TK_RANGE || eType==TK_ROWS || eType==TK_GROUPS ); + assert( eStart==TK_CURRENT || eStart==TK_PRECEDING + || eStart==TK_UNBOUNDED || eStart==TK_FOLLOWING ); + assert( eEnd==TK_CURRENT || eEnd==TK_FOLLOWING + || eEnd==TK_UNBOUNDED || eEnd==TK_PRECEDING ); + assert( (eStart==TK_PRECEDING || eStart==TK_FOLLOWING)==(pStart!=0) ); + assert( (eEnd==TK_FOLLOWING || eEnd==TK_PRECEDING)==(pEnd!=0) ); + + if( eType==0 ){ + bImplicitFrame = 1; + eType = TK_RANGE; + } + + /* Additionally, the + ** starting boundary type may not occur earlier in the following list than + ** the ending boundary type: + ** + ** UNBOUNDED PRECEDING + ** <expr> PRECEDING + ** CURRENT ROW + ** <expr> FOLLOWING + ** UNBOUNDED FOLLOWING + ** + ** The parser ensures that "UNBOUNDED PRECEDING" cannot be used as an ending + ** boundary, and than "UNBOUNDED FOLLOWING" cannot be used as a starting + ** frame boundary. + */ + if( (eStart==TK_CURRENT && eEnd==TK_PRECEDING) + || (eStart==TK_FOLLOWING && (eEnd==TK_PRECEDING || eEnd==TK_CURRENT)) + ){ + sqlite3ErrorMsg(pParse, "unsupported frame specification"); + goto windowAllocErr; + } + + pWin = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); + if( pWin==0 ) goto windowAllocErr; + pWin->eFrmType = eType; + pWin->eStart = eStart; + pWin->eEnd = eEnd; + if( eExclude==0 && OptimizationDisabled(pParse->db, SQLITE_WindowFunc) ){ + eExclude = TK_NO; + } + pWin->eExclude = eExclude; + pWin->bImplicitFrame = bImplicitFrame; + pWin->pEnd = sqlite3WindowOffsetExpr(pParse, pEnd); + pWin->pStart = sqlite3WindowOffsetExpr(pParse, pStart); + return pWin; + +windowAllocErr: + sqlite3ExprDelete(pParse->db, pEnd); + sqlite3ExprDelete(pParse->db, pStart); + return 0; +} + +/* +** Attach PARTITION and ORDER BY clauses pPartition and pOrderBy to window +** pWin. Also, if parameter pBase is not NULL, set pWin->zBase to the +** equivalent nul-terminated string. +*/ +SQLITE_PRIVATE Window *sqlite3WindowAssemble( + Parse *pParse, + Window *pWin, + ExprList *pPartition, + ExprList *pOrderBy, + Token *pBase +){ + if( pWin ){ + pWin->pPartition = pPartition; + pWin->pOrderBy = pOrderBy; + if( pBase ){ + pWin->zBase = sqlite3DbStrNDup(pParse->db, pBase->z, pBase->n); + } + }else{ + sqlite3ExprListDelete(pParse->db, pPartition); + sqlite3ExprListDelete(pParse->db, pOrderBy); + } + return pWin; +} + +/* +** Window *pWin has just been created from a WINDOW clause. Token pBase +** is the base window. Earlier windows from the same WINDOW clause are +** stored in the linked list starting at pWin->pNextWin. This function +** either updates *pWin according to the base specification, or else +** leaves an error in pParse. +*/ +SQLITE_PRIVATE void sqlite3WindowChain(Parse *pParse, Window *pWin, Window *pList){ + if( pWin->zBase ){ + sqlite3 *db = pParse->db; + Window *pExist = windowFind(pParse, pList, pWin->zBase); + if( pExist ){ + const char *zErr = 0; + /* Check for errors */ + if( pWin->pPartition ){ + zErr = "PARTITION clause"; + }else if( pExist->pOrderBy && pWin->pOrderBy ){ + zErr = "ORDER BY clause"; + }else if( pExist->bImplicitFrame==0 ){ + zErr = "frame specification"; + } + if( zErr ){ + sqlite3ErrorMsg(pParse, + "cannot override %s of window: %s", zErr, pWin->zBase + ); + }else{ + pWin->pPartition = sqlite3ExprListDup(db, pExist->pPartition, 0); + if( pExist->pOrderBy ){ + assert( pWin->pOrderBy==0 ); + pWin->pOrderBy = sqlite3ExprListDup(db, pExist->pOrderBy, 0); + } + sqlite3DbFree(db, pWin->zBase); + pWin->zBase = 0; + } + } + } +} + +/* +** Attach window object pWin to expression p. +*/ +SQLITE_PRIVATE void sqlite3WindowAttach(Parse *pParse, Expr *p, Window *pWin){ + if( p ){ + assert( p->op==TK_FUNCTION ); + assert( pWin ); + p->y.pWin = pWin; + ExprSetProperty(p, EP_WinFunc); + pWin->pOwner = p; + if( (p->flags & EP_Distinct) && pWin->eFrmType!=TK_FILTER ){ + sqlite3ErrorMsg(pParse, + "DISTINCT is not supported for window functions" + ); + } + }else{ + sqlite3WindowDelete(pParse->db, pWin); + } +} + +/* +** Possibly link window pWin into the list at pSel->pWin (window functions +** to be processed as part of SELECT statement pSel). The window is linked +** in if either (a) there are no other windows already linked to this +** SELECT, or (b) the windows already linked use a compatible window frame. +*/ +SQLITE_PRIVATE void sqlite3WindowLink(Select *pSel, Window *pWin){ + if( pSel ){ + if( 0==pSel->pWin || 0==sqlite3WindowCompare(0, pSel->pWin, pWin, 0) ){ + pWin->pNextWin = pSel->pWin; + if( pSel->pWin ){ + pSel->pWin->ppThis = &pWin->pNextWin; + } + pSel->pWin = pWin; + pWin->ppThis = &pSel->pWin; + }else{ + if( sqlite3ExprListCompare(pWin->pPartition, pSel->pWin->pPartition,-1) ){ + pSel->selFlags |= SF_MultiPart; + } + } + } +} + +/* +** Return 0 if the two window objects are identical, 1 if they are +** different, or 2 if it cannot be determined if the objects are identical +** or not. Identical window objects can be processed in a single scan. +*/ +SQLITE_PRIVATE int sqlite3WindowCompare( + const Parse *pParse, + const Window *p1, + const Window *p2, + int bFilter +){ + int res; + if( NEVER(p1==0) || NEVER(p2==0) ) return 1; + if( p1->eFrmType!=p2->eFrmType ) return 1; + if( p1->eStart!=p2->eStart ) return 1; + if( p1->eEnd!=p2->eEnd ) return 1; + if( p1->eExclude!=p2->eExclude ) return 1; + if( sqlite3ExprCompare(pParse, p1->pStart, p2->pStart, -1) ) return 1; + if( sqlite3ExprCompare(pParse, p1->pEnd, p2->pEnd, -1) ) return 1; + if( (res = sqlite3ExprListCompare(p1->pPartition, p2->pPartition, -1)) ){ + return res; + } + if( (res = sqlite3ExprListCompare(p1->pOrderBy, p2->pOrderBy, -1)) ){ + return res; + } + if( bFilter ){ + if( (res = sqlite3ExprCompare(pParse, p1->pFilter, p2->pFilter, -1)) ){ + return res; + } + } + return 0; +} + + +/* +** This is called by code in select.c before it calls sqlite3WhereBegin() +** to begin iterating through the sub-query results. It is used to allocate +** and initialize registers and cursors used by sqlite3WindowCodeStep(). +*/ +SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse *pParse, Select *pSelect){ + int nEphExpr = pSelect->pSrc->a[0].pSelect->pEList->nExpr; + Window *pMWin = pSelect->pWin; + Window *pWin; + Vdbe *v = sqlite3GetVdbe(pParse); + + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pMWin->iEphCsr, nEphExpr); + sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+1, pMWin->iEphCsr); + sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+2, pMWin->iEphCsr); + sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+3, pMWin->iEphCsr); + + /* Allocate registers to use for PARTITION BY values, if any. Initialize + ** said registers to NULL. */ + if( pMWin->pPartition ){ + int nExpr = pMWin->pPartition->nExpr; + pMWin->regPart = pParse->nMem+1; + pParse->nMem += nExpr; + sqlite3VdbeAddOp3(v, OP_Null, 0, pMWin->regPart, pMWin->regPart+nExpr-1); + } + + pMWin->regOne = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 1, pMWin->regOne); + + if( pMWin->eExclude ){ + pMWin->regStartRowid = ++pParse->nMem; + pMWin->regEndRowid = ++pParse->nMem; + pMWin->csrApp = pParse->nTab++; + sqlite3VdbeAddOp2(v, OP_Integer, 1, pMWin->regStartRowid); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pMWin->regEndRowid); + sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->csrApp, pMWin->iEphCsr); + return; + } + + for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ + FuncDef *p = pWin->pWFunc; + if( (p->funcFlags & SQLITE_FUNC_MINMAX) && pWin->eStart!=TK_UNBOUNDED ){ + /* The inline versions of min() and max() require a single ephemeral + ** table and 3 registers. The registers are used as follows: + ** + ** regApp+0: slot to copy min()/max() argument to for MakeRecord + ** regApp+1: integer value used to ensure keys are unique + ** regApp+2: output of MakeRecord + */ + ExprList *pList; + KeyInfo *pKeyInfo; + assert( ExprUseXList(pWin->pOwner) ); + pList = pWin->pOwner->x.pList; + pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pList, 0, 0); + pWin->csrApp = pParse->nTab++; + pWin->regApp = pParse->nMem+1; + pParse->nMem += 3; + if( pKeyInfo && pWin->pWFunc->zName[1]=='i' ){ + assert( pKeyInfo->aSortFlags[0]==0 ); + pKeyInfo->aSortFlags[0] = KEYINFO_ORDER_DESC; + } + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pWin->csrApp, 2); + sqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp+1); + } + else if( p->zName==nth_valueName || p->zName==first_valueName ){ + /* Allocate two registers at pWin->regApp. These will be used to + ** store the start and end index of the current frame. */ + pWin->regApp = pParse->nMem+1; + pWin->csrApp = pParse->nTab++; + pParse->nMem += 2; + sqlite3VdbeAddOp2(v, OP_OpenDup, pWin->csrApp, pMWin->iEphCsr); + } + else if( p->zName==leadName || p->zName==lagName ){ + pWin->csrApp = pParse->nTab++; + sqlite3VdbeAddOp2(v, OP_OpenDup, pWin->csrApp, pMWin->iEphCsr); + } + } +} + +#define WINDOW_STARTING_INT 0 +#define WINDOW_ENDING_INT 1 +#define WINDOW_NTH_VALUE_INT 2 +#define WINDOW_STARTING_NUM 3 +#define WINDOW_ENDING_NUM 4 + +/* +** A "PRECEDING <expr>" (eCond==0) or "FOLLOWING <expr>" (eCond==1) or the +** value of the second argument to nth_value() (eCond==2) has just been +** evaluated and the result left in register reg. This function generates VM +** code to check that the value is a non-negative integer and throws an +** exception if it is not. +*/ +static void windowCheckValue(Parse *pParse, int reg, int eCond){ + static const char *azErr[] = { + "frame starting offset must be a non-negative integer", + "frame ending offset must be a non-negative integer", + "second argument to nth_value must be a positive integer", + "frame starting offset must be a non-negative number", + "frame ending offset must be a non-negative number", + }; + static int aOp[] = { OP_Ge, OP_Ge, OP_Gt, OP_Ge, OP_Ge }; + Vdbe *v = sqlite3GetVdbe(pParse); + int regZero = sqlite3GetTempReg(pParse); + assert( eCond>=0 && eCond<ArraySize(azErr) ); + sqlite3VdbeAddOp2(v, OP_Integer, 0, regZero); + if( eCond>=WINDOW_STARTING_NUM ){ + int regString = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC); + sqlite3VdbeAddOp3(v, OP_Ge, regString, sqlite3VdbeCurrentAddr(v)+2, reg); + sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC|SQLITE_JUMPIFNULL); + VdbeCoverage(v); + assert( eCond==3 || eCond==4 ); + VdbeCoverageIf(v, eCond==3); + VdbeCoverageIf(v, eCond==4); + }else{ + sqlite3VdbeAddOp2(v, OP_MustBeInt, reg, sqlite3VdbeCurrentAddr(v)+2); + VdbeCoverage(v); + assert( eCond==0 || eCond==1 || eCond==2 ); + VdbeCoverageIf(v, eCond==0); + VdbeCoverageIf(v, eCond==1); + VdbeCoverageIf(v, eCond==2); + } + sqlite3VdbeAddOp3(v, aOp[eCond], regZero, sqlite3VdbeCurrentAddr(v)+2, reg); + sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC); + VdbeCoverageNeverNullIf(v, eCond==0); /* NULL case captured by */ + VdbeCoverageNeverNullIf(v, eCond==1); /* the OP_MustBeInt */ + VdbeCoverageNeverNullIf(v, eCond==2); + VdbeCoverageNeverNullIf(v, eCond==3); /* NULL case caught by */ + VdbeCoverageNeverNullIf(v, eCond==4); /* the OP_Ge */ + sqlite3MayAbort(pParse); + sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_ERROR, OE_Abort); + sqlite3VdbeAppendP4(v, (void*)azErr[eCond], P4_STATIC); + sqlite3ReleaseTempReg(pParse, regZero); +} + +/* +** Return the number of arguments passed to the window-function associated +** with the object passed as the only argument to this function. +*/ +static int windowArgCount(Window *pWin){ + const ExprList *pList; + assert( ExprUseXList(pWin->pOwner) ); + pList = pWin->pOwner->x.pList; + return (pList ? pList->nExpr : 0); +} + +typedef struct WindowCodeArg WindowCodeArg; +typedef struct WindowCsrAndReg WindowCsrAndReg; + +/* +** See comments above struct WindowCodeArg. +*/ +struct WindowCsrAndReg { + int csr; /* Cursor number */ + int reg; /* First in array of peer values */ +}; + +/* +** A single instance of this structure is allocated on the stack by +** sqlite3WindowCodeStep() and a pointer to it passed to the various helper +** routines. This is to reduce the number of arguments required by each +** helper function. +** +** regArg: +** Each window function requires an accumulator register (just as an +** ordinary aggregate function does). This variable is set to the first +** in an array of accumulator registers - one for each window function +** in the WindowCodeArg.pMWin list. +** +** eDelete: +** The window functions implementation sometimes caches the input rows +** that it processes in a temporary table. If it is not zero, this +** variable indicates when rows may be removed from the temp table (in +** order to reduce memory requirements - it would always be safe just +** to leave them there). Possible values for eDelete are: +** +** WINDOW_RETURN_ROW: +** An input row can be discarded after it is returned to the caller. +** +** WINDOW_AGGINVERSE: +** An input row can be discarded after the window functions xInverse() +** callbacks have been invoked in it. +** +** WINDOW_AGGSTEP: +** An input row can be discarded after the window functions xStep() +** callbacks have been invoked in it. +** +** start,current,end +** Consider a window-frame similar to the following: +** +** (ORDER BY a, b GROUPS BETWEEN 2 PRECEDING AND 2 FOLLOWING) +** +** The windows functions implementation caches the input rows in a temp +** table, sorted by "a, b" (it actually populates the cache lazily, and +** aggressively removes rows once they are no longer required, but that's +** a mere detail). It keeps three cursors open on the temp table. One +** (current) that points to the next row to return to the query engine +** once its window function values have been calculated. Another (end) +** points to the next row to call the xStep() method of each window function +** on (so that it is 2 groups ahead of current). And a third (start) that +** points to the next row to call the xInverse() method of each window +** function on. +** +** Each cursor (start, current and end) consists of a VDBE cursor +** (WindowCsrAndReg.csr) and an array of registers (starting at +** WindowCodeArg.reg) that always contains a copy of the peer values +** read from the corresponding cursor. +** +** Depending on the window-frame in question, all three cursors may not +** be required. In this case both WindowCodeArg.csr and reg are set to +** 0. +*/ +struct WindowCodeArg { + Parse *pParse; /* Parse context */ + Window *pMWin; /* First in list of functions being processed */ + Vdbe *pVdbe; /* VDBE object */ + int addrGosub; /* OP_Gosub to this address to return one row */ + int regGosub; /* Register used with OP_Gosub(addrGosub) */ + int regArg; /* First in array of accumulator registers */ + int eDelete; /* See above */ + int regRowid; + + WindowCsrAndReg start; + WindowCsrAndReg current; + WindowCsrAndReg end; +}; + +/* +** Generate VM code to read the window frames peer values from cursor csr into +** an array of registers starting at reg. +*/ +static void windowReadPeerValues( + WindowCodeArg *p, + int csr, + int reg +){ + Window *pMWin = p->pMWin; + ExprList *pOrderBy = pMWin->pOrderBy; + if( pOrderBy ){ + Vdbe *v = sqlite3GetVdbe(p->pParse); + ExprList *pPart = pMWin->pPartition; + int iColOff = pMWin->nBufferCol + (pPart ? pPart->nExpr : 0); + int i; + for(i=0; i<pOrderBy->nExpr; i++){ + sqlite3VdbeAddOp3(v, OP_Column, csr, iColOff+i, reg+i); + } + } +} + +/* +** Generate VM code to invoke either xStep() (if bInverse is 0) or +** xInverse (if bInverse is non-zero) for each window function in the +** linked list starting at pMWin. Or, for built-in window functions +** that do not use the standard function API, generate the required +** inline VM code. +** +** If argument csr is greater than or equal to 0, then argument reg is +** the first register in an array of registers guaranteed to be large +** enough to hold the array of arguments for each function. In this case +** the arguments are extracted from the current row of csr into the +** array of registers before invoking OP_AggStep or OP_AggInverse +** +** Or, if csr is less than zero, then the array of registers at reg is +** already populated with all columns from the current row of the sub-query. +** +** If argument regPartSize is non-zero, then it is a register containing the +** number of rows in the current partition. +*/ +static void windowAggStep( + WindowCodeArg *p, + Window *pMWin, /* Linked list of window functions */ + int csr, /* Read arguments from this cursor */ + int bInverse, /* True to invoke xInverse instead of xStep */ + int reg /* Array of registers */ +){ + Parse *pParse = p->pParse; + Vdbe *v = sqlite3GetVdbe(pParse); + Window *pWin; + for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ + FuncDef *pFunc = pWin->pWFunc; + int regArg; + int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin); + int i; + + assert( bInverse==0 || pWin->eStart!=TK_UNBOUNDED ); + + /* All OVER clauses in the same window function aggregate step must + ** be the same. */ + assert( pWin==pMWin || sqlite3WindowCompare(pParse,pWin,pMWin,0)!=1 ); + + for(i=0; i<nArg; i++){ + if( i!=1 || pFunc->zName!=nth_valueName ){ + sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+i, reg+i); + }else{ + sqlite3VdbeAddOp3(v, OP_Column, pMWin->iEphCsr, pWin->iArgCol+i, reg+i); + } + } + regArg = reg; + + if( pMWin->regStartRowid==0 + && (pFunc->funcFlags & SQLITE_FUNC_MINMAX) + && (pWin->eStart!=TK_UNBOUNDED) + ){ + int addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, regArg); + VdbeCoverage(v); + if( bInverse==0 ){ + sqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1, 1); + sqlite3VdbeAddOp2(v, OP_SCopy, regArg, pWin->regApp); + sqlite3VdbeAddOp3(v, OP_MakeRecord, pWin->regApp, 2, pWin->regApp+2); + sqlite3VdbeAddOp2(v, OP_IdxInsert, pWin->csrApp, pWin->regApp+2); + }else{ + sqlite3VdbeAddOp4Int(v, OP_SeekGE, pWin->csrApp, 0, regArg, 1); + VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp1(v, OP_Delete, pWin->csrApp); + sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2); + } + sqlite3VdbeJumpHere(v, addrIsNull); + }else if( pWin->regApp ){ + assert( pFunc->zName==nth_valueName + || pFunc->zName==first_valueName + ); + assert( bInverse==0 || bInverse==1 ); + sqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1-bInverse, 1); + }else if( pFunc->xSFunc!=noopStepFunc ){ + int addrIf = 0; + if( pWin->pFilter ){ + int regTmp; + assert( ExprUseXList(pWin->pOwner) ); + assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr ); + assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 ); + regTmp = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp); + addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1); + VdbeCoverage(v); + sqlite3ReleaseTempReg(pParse, regTmp); + } + + if( pWin->bExprArgs ){ + int iOp = sqlite3VdbeCurrentAddr(v); + int iEnd; + + assert( ExprUseXList(pWin->pOwner) ); + nArg = pWin->pOwner->x.pList->nExpr; + regArg = sqlite3GetTempRange(pParse, nArg); + sqlite3ExprCodeExprList(pParse, pWin->pOwner->x.pList, regArg, 0, 0); + + for(iEnd=sqlite3VdbeCurrentAddr(v); iOp<iEnd; iOp++){ + VdbeOp *pOp = sqlite3VdbeGetOp(v, iOp); + if( pOp->opcode==OP_Column && pOp->p1==pMWin->iEphCsr ){ + pOp->p1 = csr; + } + } + } + if( pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ + CollSeq *pColl; + assert( nArg>0 ); + assert( ExprUseXList(pWin->pOwner) ); + pColl = sqlite3ExprNNCollSeq(pParse, pWin->pOwner->x.pList->a[0].pExpr); + sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ); + } + sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep, + bInverse, regArg, pWin->regAccum); + sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, (u8)nArg); + if( pWin->bExprArgs ){ + sqlite3ReleaseTempRange(pParse, regArg, nArg); + } + if( addrIf ) sqlite3VdbeJumpHere(v, addrIf); + } + } +} + +/* +** Values that may be passed as the second argument to windowCodeOp(). +*/ +#define WINDOW_RETURN_ROW 1 +#define WINDOW_AGGINVERSE 2 +#define WINDOW_AGGSTEP 3 + +/* +** Generate VM code to invoke either xValue() (bFin==0) or xFinalize() +** (bFin==1) for each window function in the linked list starting at +** pMWin. Or, for built-in window-functions that do not use the standard +** API, generate the equivalent VM code. +*/ +static void windowAggFinal(WindowCodeArg *p, int bFin){ + Parse *pParse = p->pParse; + Window *pMWin = p->pMWin; + Vdbe *v = sqlite3GetVdbe(pParse); + Window *pWin; + + for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ + if( pMWin->regStartRowid==0 + && (pWin->pWFunc->funcFlags & SQLITE_FUNC_MINMAX) + && (pWin->eStart!=TK_UNBOUNDED) + ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); + sqlite3VdbeAddOp1(v, OP_Last, pWin->csrApp); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_Column, pWin->csrApp, 0, pWin->regResult); + sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2); + }else if( pWin->regApp ){ + assert( pMWin->regStartRowid==0 ); + }else{ + int nArg = windowArgCount(pWin); + if( bFin ){ + sqlite3VdbeAddOp2(v, OP_AggFinal, pWin->regAccum, nArg); + sqlite3VdbeAppendP4(v, pWin->pWFunc, P4_FUNCDEF); + sqlite3VdbeAddOp2(v, OP_Copy, pWin->regAccum, pWin->regResult); + sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); + }else{ + sqlite3VdbeAddOp3(v, OP_AggValue,pWin->regAccum,nArg,pWin->regResult); + sqlite3VdbeAppendP4(v, pWin->pWFunc, P4_FUNCDEF); + } + } + } +} + +/* +** Generate code to calculate the current values of all window functions in the +** p->pMWin list by doing a full scan of the current window frame. Store the +** results in the Window.regResult registers, ready to return the upper +** layer. +*/ +static void windowFullScan(WindowCodeArg *p){ + Window *pWin; + Parse *pParse = p->pParse; + Window *pMWin = p->pMWin; + Vdbe *v = p->pVdbe; + + int regCRowid = 0; /* Current rowid value */ + int regCPeer = 0; /* Current peer values */ + int regRowid = 0; /* AggStep rowid value */ + int regPeer = 0; /* AggStep peer values */ + + int nPeer; + int lblNext; + int lblBrk; + int addrNext; + int csr; + + VdbeModuleComment((v, "windowFullScan begin")); + + assert( pMWin!=0 ); + csr = pMWin->csrApp; + nPeer = (pMWin->pOrderBy ? pMWin->pOrderBy->nExpr : 0); + + lblNext = sqlite3VdbeMakeLabel(pParse); + lblBrk = sqlite3VdbeMakeLabel(pParse); + + regCRowid = sqlite3GetTempReg(pParse); + regRowid = sqlite3GetTempReg(pParse); + if( nPeer ){ + regCPeer = sqlite3GetTempRange(pParse, nPeer); + regPeer = sqlite3GetTempRange(pParse, nPeer); + } + + sqlite3VdbeAddOp2(v, OP_Rowid, pMWin->iEphCsr, regCRowid); + windowReadPeerValues(p, pMWin->iEphCsr, regCPeer); + + for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ + sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); + } + + sqlite3VdbeAddOp3(v, OP_SeekGE, csr, lblBrk, pMWin->regStartRowid); + VdbeCoverage(v); + addrNext = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp2(v, OP_Rowid, csr, regRowid); + sqlite3VdbeAddOp3(v, OP_Gt, pMWin->regEndRowid, lblBrk, regRowid); + VdbeCoverageNeverNull(v); + + if( pMWin->eExclude==TK_CURRENT ){ + sqlite3VdbeAddOp3(v, OP_Eq, regCRowid, lblNext, regRowid); + VdbeCoverageNeverNull(v); + }else if( pMWin->eExclude!=TK_NO ){ + int addr; + int addrEq = 0; + KeyInfo *pKeyInfo = 0; + + if( pMWin->pOrderBy ){ + pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pMWin->pOrderBy, 0, 0); + } + if( pMWin->eExclude==TK_TIES ){ + addrEq = sqlite3VdbeAddOp3(v, OP_Eq, regCRowid, 0, regRowid); + VdbeCoverageNeverNull(v); + } + if( pKeyInfo ){ + windowReadPeerValues(p, csr, regPeer); + sqlite3VdbeAddOp3(v, OP_Compare, regPeer, regCPeer, nPeer); + sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO); + addr = sqlite3VdbeCurrentAddr(v)+1; + sqlite3VdbeAddOp3(v, OP_Jump, addr, lblNext, addr); + VdbeCoverageEqNe(v); + }else{ + sqlite3VdbeAddOp2(v, OP_Goto, 0, lblNext); + } + if( addrEq ) sqlite3VdbeJumpHere(v, addrEq); + } + + windowAggStep(p, pMWin, csr, 0, p->regArg); + + sqlite3VdbeResolveLabel(v, lblNext); + sqlite3VdbeAddOp2(v, OP_Next, csr, addrNext); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrNext-1); + sqlite3VdbeJumpHere(v, addrNext+1); + sqlite3ReleaseTempReg(pParse, regRowid); + sqlite3ReleaseTempReg(pParse, regCRowid); + if( nPeer ){ + sqlite3ReleaseTempRange(pParse, regPeer, nPeer); + sqlite3ReleaseTempRange(pParse, regCPeer, nPeer); + } + + windowAggFinal(p, 1); + VdbeModuleComment((v, "windowFullScan end")); +} + +/* +** Invoke the sub-routine at regGosub (generated by code in select.c) to +** return the current row of Window.iEphCsr. If all window functions are +** aggregate window functions that use the standard API, a single +** OP_Gosub instruction is all that this routine generates. Extra VM code +** for per-row processing is only generated for the following built-in window +** functions: +** +** nth_value() +** first_value() +** lag() +** lead() +*/ +static void windowReturnOneRow(WindowCodeArg *p){ + Window *pMWin = p->pMWin; + Vdbe *v = p->pVdbe; + + if( pMWin->regStartRowid ){ + windowFullScan(p); + }else{ + Parse *pParse = p->pParse; + Window *pWin; + + for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ + FuncDef *pFunc = pWin->pWFunc; + assert( ExprUseXList(pWin->pOwner) ); + if( pFunc->zName==nth_valueName + || pFunc->zName==first_valueName + ){ + int csr = pWin->csrApp; + int lbl = sqlite3VdbeMakeLabel(pParse); + int tmpReg = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); + + if( pFunc->zName==nth_valueName ){ + sqlite3VdbeAddOp3(v, OP_Column,pMWin->iEphCsr,pWin->iArgCol+1,tmpReg); + windowCheckValue(pParse, tmpReg, 2); + }else{ + sqlite3VdbeAddOp2(v, OP_Integer, 1, tmpReg); + } + sqlite3VdbeAddOp3(v, OP_Add, tmpReg, pWin->regApp, tmpReg); + sqlite3VdbeAddOp3(v, OP_Gt, pWin->regApp+1, lbl, tmpReg); + VdbeCoverageNeverNull(v); + sqlite3VdbeAddOp3(v, OP_SeekRowid, csr, 0, tmpReg); + VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol, pWin->regResult); + sqlite3VdbeResolveLabel(v, lbl); + sqlite3ReleaseTempReg(pParse, tmpReg); + } + else if( pFunc->zName==leadName || pFunc->zName==lagName ){ + int nArg = pWin->pOwner->x.pList->nExpr; + int csr = pWin->csrApp; + int lbl = sqlite3VdbeMakeLabel(pParse); + int tmpReg = sqlite3GetTempReg(pParse); + int iEph = pMWin->iEphCsr; + + if( nArg<3 ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); + }else{ + sqlite3VdbeAddOp3(v, OP_Column, iEph,pWin->iArgCol+2,pWin->regResult); + } + sqlite3VdbeAddOp2(v, OP_Rowid, iEph, tmpReg); + if( nArg<2 ){ + int val = (pFunc->zName==leadName ? 1 : -1); + sqlite3VdbeAddOp2(v, OP_AddImm, tmpReg, val); + }else{ + int op = (pFunc->zName==leadName ? OP_Add : OP_Subtract); + int tmpReg2 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_Column, iEph, pWin->iArgCol+1, tmpReg2); + sqlite3VdbeAddOp3(v, op, tmpReg2, tmpReg, tmpReg); + sqlite3ReleaseTempReg(pParse, tmpReg2); + } + + sqlite3VdbeAddOp3(v, OP_SeekRowid, csr, lbl, tmpReg); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol, pWin->regResult); + sqlite3VdbeResolveLabel(v, lbl); + sqlite3ReleaseTempReg(pParse, tmpReg); + } + } + } + sqlite3VdbeAddOp2(v, OP_Gosub, p->regGosub, p->addrGosub); +} + +/* +** Generate code to set the accumulator register for each window function +** in the linked list passed as the second argument to NULL. And perform +** any equivalent initialization required by any built-in window functions +** in the list. +*/ +static int windowInitAccum(Parse *pParse, Window *pMWin){ + Vdbe *v = sqlite3GetVdbe(pParse); + int regArg; + int nArg = 0; + Window *pWin; + for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ + FuncDef *pFunc = pWin->pWFunc; + assert( pWin->regAccum ); + sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); + nArg = MAX(nArg, windowArgCount(pWin)); + if( pMWin->regStartRowid==0 ){ + if( pFunc->zName==nth_valueName || pFunc->zName==first_valueName ){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp+1); + } + + if( (pFunc->funcFlags & SQLITE_FUNC_MINMAX) && pWin->csrApp ){ + assert( pWin->eStart!=TK_UNBOUNDED ); + sqlite3VdbeAddOp1(v, OP_ResetSorter, pWin->csrApp); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp+1); + } + } + } + regArg = pParse->nMem+1; + pParse->nMem += nArg; + return regArg; +} + +/* +** Return true if the current frame should be cached in the ephemeral table, +** even if there are no xInverse() calls required. +*/ +static int windowCacheFrame(Window *pMWin){ + Window *pWin; + if( pMWin->regStartRowid ) return 1; + for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ + FuncDef *pFunc = pWin->pWFunc; + if( (pFunc->zName==nth_valueName) + || (pFunc->zName==first_valueName) + || (pFunc->zName==leadName) + || (pFunc->zName==lagName) + ){ + return 1; + } + } + return 0; +} + +/* +** regOld and regNew are each the first register in an array of size +** pOrderBy->nExpr. This function generates code to compare the two +** arrays of registers using the collation sequences and other comparison +** parameters specified by pOrderBy. +** +** If the two arrays are not equal, the contents of regNew is copied to +** regOld and control falls through. Otherwise, if the contents of the arrays +** are equal, an OP_Goto is executed. The address of the OP_Goto is returned. +*/ +static void windowIfNewPeer( + Parse *pParse, + ExprList *pOrderBy, + int regNew, /* First in array of new values */ + int regOld, /* First in array of old values */ + int addr /* Jump here */ +){ + Vdbe *v = sqlite3GetVdbe(pParse); + if( pOrderBy ){ + int nVal = pOrderBy->nExpr; + KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pOrderBy, 0, 0); + sqlite3VdbeAddOp3(v, OP_Compare, regOld, regNew, nVal); + sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO); + sqlite3VdbeAddOp3(v, OP_Jump, + sqlite3VdbeCurrentAddr(v)+1, addr, sqlite3VdbeCurrentAddr(v)+1 + ); + VdbeCoverageEqNe(v); + sqlite3VdbeAddOp3(v, OP_Copy, regNew, regOld, nVal-1); + }else{ + sqlite3VdbeAddOp2(v, OP_Goto, 0, addr); + } +} + +/* +** This function is called as part of generating VM programs for RANGE +** offset PRECEDING/FOLLOWING frame boundaries. Assuming "ASC" order for +** the ORDER BY term in the window, and that argument op is OP_Ge, it generates +** code equivalent to: +** +** if( csr1.peerVal + regVal >= csr2.peerVal ) goto lbl; +** +** The value of parameter op may also be OP_Gt or OP_Le. In these cases the +** operator in the above pseudo-code is replaced with ">" or "<=", respectively. +** +** If the sort-order for the ORDER BY term in the window is DESC, then the +** comparison is reversed. Instead of adding regVal to csr1.peerVal, it is +** subtracted. And the comparison operator is inverted to - ">=" becomes "<=", +** ">" becomes "<", and so on. So, with DESC sort order, if the argument op +** is OP_Ge, the generated code is equivalent to: +** +** if( csr1.peerVal - regVal <= csr2.peerVal ) goto lbl; +** +** A special type of arithmetic is used such that if csr1.peerVal is not +** a numeric type (real or integer), then the result of the addition +** or subtraction is a a copy of csr1.peerVal. +*/ +static void windowCodeRangeTest( + WindowCodeArg *p, + int op, /* OP_Ge, OP_Gt, or OP_Le */ + int csr1, /* Cursor number for cursor 1 */ + int regVal, /* Register containing non-negative number */ + int csr2, /* Cursor number for cursor 2 */ + int lbl /* Jump destination if condition is true */ +){ + Parse *pParse = p->pParse; + Vdbe *v = sqlite3GetVdbe(pParse); + ExprList *pOrderBy = p->pMWin->pOrderBy; /* ORDER BY clause for window */ + int reg1 = sqlite3GetTempReg(pParse); /* Reg. for csr1.peerVal+regVal */ + int reg2 = sqlite3GetTempReg(pParse); /* Reg. for csr2.peerVal */ + int regString = ++pParse->nMem; /* Reg. for constant value '' */ + int arith = OP_Add; /* OP_Add or OP_Subtract */ + int addrGe; /* Jump destination */ + int addrDone = sqlite3VdbeMakeLabel(pParse); /* Address past OP_Ge */ + CollSeq *pColl; + + /* Read the peer-value from each cursor into a register */ + windowReadPeerValues(p, csr1, reg1); + windowReadPeerValues(p, csr2, reg2); + + assert( op==OP_Ge || op==OP_Gt || op==OP_Le ); + assert( pOrderBy && pOrderBy->nExpr==1 ); + if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_DESC ){ + switch( op ){ + case OP_Ge: op = OP_Le; break; + case OP_Gt: op = OP_Lt; break; + default: assert( op==OP_Le ); op = OP_Ge; break; + } + arith = OP_Subtract; + } + + VdbeModuleComment((v, "CodeRangeTest: if( R%d %s R%d %s R%d ) goto lbl", + reg1, (arith==OP_Add ? "+" : "-"), regVal, + ((op==OP_Ge) ? ">=" : (op==OP_Le) ? "<=" : (op==OP_Gt) ? ">" : "<"), reg2 + )); + + /* If the BIGNULL flag is set for the ORDER BY, then it is required to + ** consider NULL values to be larger than all other values, instead of + ** the usual smaller. The VDBE opcodes OP_Ge and so on do not handle this + ** (and adding that capability causes a performance regression), so + ** instead if the BIGNULL flag is set then cases where either reg1 or + ** reg2 are NULL are handled separately in the following block. The code + ** generated is equivalent to: + ** + ** if( reg1 IS NULL ){ + ** if( op==OP_Ge ) goto lbl; + ** if( op==OP_Gt && reg2 IS NOT NULL ) goto lbl; + ** if( op==OP_Le && reg2 IS NULL ) goto lbl; + ** }else if( reg2 IS NULL ){ + ** if( op==OP_Le ) goto lbl; + ** } + ** + ** Additionally, if either reg1 or reg2 are NULL but the jump to lbl is + ** not taken, control jumps over the comparison operator coded below this + ** block. */ + if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_BIGNULL ){ + /* This block runs if reg1 contains a NULL. */ + int addr = sqlite3VdbeAddOp1(v, OP_NotNull, reg1); VdbeCoverage(v); + switch( op ){ + case OP_Ge: + sqlite3VdbeAddOp2(v, OP_Goto, 0, lbl); + break; + case OP_Gt: + sqlite3VdbeAddOp2(v, OP_NotNull, reg2, lbl); + VdbeCoverage(v); + break; + case OP_Le: + sqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl); + VdbeCoverage(v); + break; + default: assert( op==OP_Lt ); /* no-op */ break; + } + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrDone); + + /* This block runs if reg1 is not NULL, but reg2 is. */ + sqlite3VdbeJumpHere(v, addr); + sqlite3VdbeAddOp2(v, OP_IsNull, reg2, + (op==OP_Gt || op==OP_Ge) ? addrDone : lbl); + VdbeCoverage(v); + } + + /* Register reg1 currently contains csr1.peerVal (the peer-value from csr1). + ** This block adds (or subtracts for DESC) the numeric value in regVal + ** from it. Or, if reg1 is not numeric (it is a NULL, a text value or a blob), + ** then leave reg1 as it is. In pseudo-code, this is implemented as: + ** + ** if( reg1>='' ) goto addrGe; + ** reg1 = reg1 +/- regVal + ** addrGe: + ** + ** Since all strings and blobs are greater-than-or-equal-to an empty string, + ** the add/subtract is skipped for these, as required. If reg1 is a NULL, + ** then the arithmetic is performed, but since adding or subtracting from + ** NULL is always NULL anyway, this case is handled as required too. */ + sqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC); + addrGe = sqlite3VdbeAddOp3(v, OP_Ge, regString, 0, reg1); + VdbeCoverage(v); + if( (op==OP_Ge && arith==OP_Add) || (op==OP_Le && arith==OP_Subtract) ){ + sqlite3VdbeAddOp3(v, op, reg2, lbl, reg1); VdbeCoverage(v); + } + sqlite3VdbeAddOp3(v, arith, regVal, reg1, reg1); + sqlite3VdbeJumpHere(v, addrGe); + + /* Compare registers reg2 and reg1, taking the jump if required. Note that + ** control skips over this test if the BIGNULL flag is set and either + ** reg1 or reg2 contain a NULL value. */ + sqlite3VdbeAddOp3(v, op, reg2, lbl, reg1); VdbeCoverage(v); + pColl = sqlite3ExprNNCollSeq(pParse, pOrderBy->a[0].pExpr); + sqlite3VdbeAppendP4(v, (void*)pColl, P4_COLLSEQ); + sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); + sqlite3VdbeResolveLabel(v, addrDone); + + assert( op==OP_Ge || op==OP_Gt || op==OP_Lt || op==OP_Le ); + testcase(op==OP_Ge); VdbeCoverageIf(v, op==OP_Ge); + testcase(op==OP_Lt); VdbeCoverageIf(v, op==OP_Lt); + testcase(op==OP_Le); VdbeCoverageIf(v, op==OP_Le); + testcase(op==OP_Gt); VdbeCoverageIf(v, op==OP_Gt); + sqlite3ReleaseTempReg(pParse, reg1); + sqlite3ReleaseTempReg(pParse, reg2); + + VdbeModuleComment((v, "CodeRangeTest: end")); +} + +/* +** Helper function for sqlite3WindowCodeStep(). Each call to this function +** generates VM code for a single RETURN_ROW, AGGSTEP or AGGINVERSE +** operation. Refer to the header comment for sqlite3WindowCodeStep() for +** details. +*/ +static int windowCodeOp( + WindowCodeArg *p, /* Context object */ + int op, /* WINDOW_RETURN_ROW, AGGSTEP or AGGINVERSE */ + int regCountdown, /* Register for OP_IfPos countdown */ + int jumpOnEof /* Jump here if stepped cursor reaches EOF */ +){ + int csr, reg; + Parse *pParse = p->pParse; + Window *pMWin = p->pMWin; + int ret = 0; + Vdbe *v = p->pVdbe; + int addrContinue = 0; + int bPeer = (pMWin->eFrmType!=TK_ROWS); + + int lblDone = sqlite3VdbeMakeLabel(pParse); + int addrNextRange = 0; + + /* Special case - WINDOW_AGGINVERSE is always a no-op if the frame + ** starts with UNBOUNDED PRECEDING. */ + if( op==WINDOW_AGGINVERSE && pMWin->eStart==TK_UNBOUNDED ){ + assert( regCountdown==0 && jumpOnEof==0 ); + return 0; + } + + if( regCountdown>0 ){ + if( pMWin->eFrmType==TK_RANGE ){ + addrNextRange = sqlite3VdbeCurrentAddr(v); + assert( op==WINDOW_AGGINVERSE || op==WINDOW_AGGSTEP ); + if( op==WINDOW_AGGINVERSE ){ + if( pMWin->eStart==TK_FOLLOWING ){ + windowCodeRangeTest( + p, OP_Le, p->current.csr, regCountdown, p->start.csr, lblDone + ); + }else{ + windowCodeRangeTest( + p, OP_Ge, p->start.csr, regCountdown, p->current.csr, lblDone + ); + } + }else{ + windowCodeRangeTest( + p, OP_Gt, p->end.csr, regCountdown, p->current.csr, lblDone + ); + } + }else{ + sqlite3VdbeAddOp3(v, OP_IfPos, regCountdown, lblDone, 1); + VdbeCoverage(v); + } + } + + if( op==WINDOW_RETURN_ROW && pMWin->regStartRowid==0 ){ + windowAggFinal(p, 0); + } + addrContinue = sqlite3VdbeCurrentAddr(v); + + /* If this is a (RANGE BETWEEN a FOLLOWING AND b FOLLOWING) or + ** (RANGE BETWEEN b PRECEDING AND a PRECEDING) frame, ensure the + ** start cursor does not advance past the end cursor within the + ** temporary table. It otherwise might, if (a>b). Also ensure that, + ** if the input cursor is still finding new rows, that the end + ** cursor does not go past it to EOF. */ + if( pMWin->eStart==pMWin->eEnd && regCountdown + && pMWin->eFrmType==TK_RANGE + ){ + int regRowid1 = sqlite3GetTempReg(pParse); + int regRowid2 = sqlite3GetTempReg(pParse); + if( op==WINDOW_AGGINVERSE ){ + sqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1); + sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2); + sqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1); + VdbeCoverage(v); + }else if( p->regRowid ){ + sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid1); + sqlite3VdbeAddOp3(v, OP_Ge, p->regRowid, lblDone, regRowid1); + VdbeCoverageNeverNull(v); + } + sqlite3ReleaseTempReg(pParse, regRowid1); + sqlite3ReleaseTempReg(pParse, regRowid2); + assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING ); + } + + switch( op ){ + case WINDOW_RETURN_ROW: + csr = p->current.csr; + reg = p->current.reg; + windowReturnOneRow(p); + break; + + case WINDOW_AGGINVERSE: + csr = p->start.csr; + reg = p->start.reg; + if( pMWin->regStartRowid ){ + assert( pMWin->regEndRowid ); + sqlite3VdbeAddOp2(v, OP_AddImm, pMWin->regStartRowid, 1); + }else{ + windowAggStep(p, pMWin, csr, 1, p->regArg); + } + break; + + default: + assert( op==WINDOW_AGGSTEP ); + csr = p->end.csr; + reg = p->end.reg; + if( pMWin->regStartRowid ){ + assert( pMWin->regEndRowid ); + sqlite3VdbeAddOp2(v, OP_AddImm, pMWin->regEndRowid, 1); + }else{ + windowAggStep(p, pMWin, csr, 0, p->regArg); + } + break; + } + + if( op==p->eDelete ){ + sqlite3VdbeAddOp1(v, OP_Delete, csr); + sqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION); + } + + if( jumpOnEof ){ + sqlite3VdbeAddOp2(v, OP_Next, csr, sqlite3VdbeCurrentAddr(v)+2); + VdbeCoverage(v); + ret = sqlite3VdbeAddOp0(v, OP_Goto); + }else{ + sqlite3VdbeAddOp2(v, OP_Next, csr, sqlite3VdbeCurrentAddr(v)+1+bPeer); + VdbeCoverage(v); + if( bPeer ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, lblDone); + } + } + + if( bPeer ){ + int nReg = (pMWin->pOrderBy ? pMWin->pOrderBy->nExpr : 0); + int regTmp = (nReg ? sqlite3GetTempRange(pParse, nReg) : 0); + windowReadPeerValues(p, csr, regTmp); + windowIfNewPeer(pParse, pMWin->pOrderBy, regTmp, reg, addrContinue); + sqlite3ReleaseTempRange(pParse, regTmp, nReg); + } + + if( addrNextRange ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrNextRange); + } + sqlite3VdbeResolveLabel(v, lblDone); + return ret; +} + + +/* +** Allocate and return a duplicate of the Window object indicated by the +** third argument. Set the Window.pOwner field of the new object to +** pOwner. +*/ +SQLITE_PRIVATE Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p){ + Window *pNew = 0; + if( ALWAYS(p) ){ + pNew = sqlite3DbMallocZero(db, sizeof(Window)); + if( pNew ){ + pNew->zName = sqlite3DbStrDup(db, p->zName); + pNew->zBase = sqlite3DbStrDup(db, p->zBase); + pNew->pFilter = sqlite3ExprDup(db, p->pFilter, 0); + pNew->pWFunc = p->pWFunc; + pNew->pPartition = sqlite3ExprListDup(db, p->pPartition, 0); + pNew->pOrderBy = sqlite3ExprListDup(db, p->pOrderBy, 0); + pNew->eFrmType = p->eFrmType; + pNew->eEnd = p->eEnd; + pNew->eStart = p->eStart; + pNew->eExclude = p->eExclude; + pNew->regResult = p->regResult; + pNew->regAccum = p->regAccum; + pNew->iArgCol = p->iArgCol; + pNew->iEphCsr = p->iEphCsr; + pNew->bExprArgs = p->bExprArgs; + pNew->pStart = sqlite3ExprDup(db, p->pStart, 0); + pNew->pEnd = sqlite3ExprDup(db, p->pEnd, 0); + pNew->pOwner = pOwner; + pNew->bImplicitFrame = p->bImplicitFrame; + } + } + return pNew; +} + +/* +** Return a copy of the linked list of Window objects passed as the +** second argument. +*/ +SQLITE_PRIVATE Window *sqlite3WindowListDup(sqlite3 *db, Window *p){ + Window *pWin; + Window *pRet = 0; + Window **pp = &pRet; + + for(pWin=p; pWin; pWin=pWin->pNextWin){ + *pp = sqlite3WindowDup(db, 0, pWin); + if( *pp==0 ) break; + pp = &((*pp)->pNextWin); + } + + return pRet; +} + +/* +** Return true if it can be determined at compile time that expression +** pExpr evaluates to a value that, when cast to an integer, is greater +** than zero. False otherwise. +** +** If an OOM error occurs, this function sets the Parse.db.mallocFailed +** flag and returns zero. +*/ +static int windowExprGtZero(Parse *pParse, Expr *pExpr){ + int ret = 0; + sqlite3 *db = pParse->db; + sqlite3_value *pVal = 0; + sqlite3ValueFromExpr(db, pExpr, db->enc, SQLITE_AFF_NUMERIC, &pVal); + if( pVal && sqlite3_value_int(pVal)>0 ){ + ret = 1; + } + sqlite3ValueFree(pVal); + return ret; +} + +/* +** sqlite3WhereBegin() has already been called for the SELECT statement +** passed as the second argument when this function is invoked. It generates +** code to populate the Window.regResult register for each window function +** and invoke the sub-routine at instruction addrGosub once for each row. +** sqlite3WhereEnd() is always called before returning. +** +** This function handles several different types of window frames, which +** require slightly different processing. The following pseudo code is +** used to implement window frames of the form: +** +** ROWS BETWEEN <expr1> PRECEDING AND <expr2> FOLLOWING +** +** Other window frame types use variants of the following: +** +** ... loop started by sqlite3WhereBegin() ... +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** +** if( first row of partition ){ +** // Rewind three cursors, all open on the eph table. +** Rewind(csrEnd); +** Rewind(csrStart); +** Rewind(csrCurrent); +** +** regEnd = <expr2> // FOLLOWING expression +** regStart = <expr1> // PRECEDING expression +** }else{ +** // First time this branch is taken, the eph table contains two +** // rows. The first row in the partition, which all three cursors +** // currently point to, and the following row. +** AGGSTEP +** if( (regEnd--)<=0 ){ +** RETURN_ROW +** if( (regStart--)<=0 ){ +** AGGINVERSE +** } +** } +** } +** } +** flush: +** AGGSTEP +** while( 1 ){ +** RETURN ROW +** if( csrCurrent is EOF ) break; +** if( (regStart--)<=0 ){ +** AggInverse(csrStart) +** Next(csrStart) +** } +** } +** +** The pseudo-code above uses the following shorthand: +** +** AGGSTEP: invoke the aggregate xStep() function for each window function +** with arguments read from the current row of cursor csrEnd, then +** step cursor csrEnd forward one row (i.e. sqlite3BtreeNext()). +** +** RETURN_ROW: return a row to the caller based on the contents of the +** current row of csrCurrent and the current state of all +** aggregates. Then step cursor csrCurrent forward one row. +** +** AGGINVERSE: invoke the aggregate xInverse() function for each window +** functions with arguments read from the current row of cursor +** csrStart. Then step csrStart forward one row. +** +** There are two other ROWS window frames that are handled significantly +** differently from the above - "BETWEEN <expr> PRECEDING AND <expr> PRECEDING" +** and "BETWEEN <expr> FOLLOWING AND <expr> FOLLOWING". These are special +** cases because they change the order in which the three cursors (csrStart, +** csrCurrent and csrEnd) iterate through the ephemeral table. Cases that +** use UNBOUNDED or CURRENT ROW are much simpler variations on one of these +** three. +** +** ROWS BETWEEN <expr1> PRECEDING AND <expr2> PRECEDING +** +** ... loop started by sqlite3WhereBegin() ... +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** if( first row of partition ){ +** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent) +** regEnd = <expr2> +** regStart = <expr1> +** }else{ +** if( (regEnd--)<=0 ){ +** AGGSTEP +** } +** RETURN_ROW +** if( (regStart--)<=0 ){ +** AGGINVERSE +** } +** } +** } +** flush: +** if( (regEnd--)<=0 ){ +** AGGSTEP +** } +** RETURN_ROW +** +** +** ROWS BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING +** +** ... loop started by sqlite3WhereBegin() ... +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** if( first row of partition ){ +** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent) +** regEnd = <expr2> +** regStart = regEnd - <expr1> +** }else{ +** AGGSTEP +** if( (regEnd--)<=0 ){ +** RETURN_ROW +** } +** if( (regStart--)<=0 ){ +** AGGINVERSE +** } +** } +** } +** flush: +** AGGSTEP +** while( 1 ){ +** if( (regEnd--)<=0 ){ +** RETURN_ROW +** if( eof ) break; +** } +** if( (regStart--)<=0 ){ +** AGGINVERSE +** if( eof ) break +** } +** } +** while( !eof csrCurrent ){ +** RETURN_ROW +** } +** +** For the most part, the patterns above are adapted to support UNBOUNDED by +** assuming that it is equivalent to "infinity PRECEDING/FOLLOWING" and +** CURRENT ROW by assuming that it is equivalent to "0 PRECEDING/FOLLOWING". +** This is optimized of course - branches that will never be taken and +** conditions that are always true are omitted from the VM code. The only +** exceptional case is: +** +** ROWS BETWEEN <expr1> FOLLOWING AND UNBOUNDED FOLLOWING +** +** ... loop started by sqlite3WhereBegin() ... +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** if( first row of partition ){ +** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent) +** regStart = <expr1> +** }else{ +** AGGSTEP +** } +** } +** flush: +** AGGSTEP +** while( 1 ){ +** if( (regStart--)<=0 ){ +** AGGINVERSE +** if( eof ) break +** } +** RETURN_ROW +** } +** while( !eof csrCurrent ){ +** RETURN_ROW +** } +** +** Also requiring special handling are the cases: +** +** ROWS BETWEEN <expr1> PRECEDING AND <expr2> PRECEDING +** ROWS BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING +** +** when (expr1 < expr2). This is detected at runtime, not by this function. +** To handle this case, the pseudo-code programs depicted above are modified +** slightly to be: +** +** ... loop started by sqlite3WhereBegin() ... +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** if( first row of partition ){ +** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent) +** regEnd = <expr2> +** regStart = <expr1> +** if( regEnd < regStart ){ +** RETURN_ROW +** delete eph table contents +** continue +** } +** ... +** +** The new "continue" statement in the above jumps to the next iteration +** of the outer loop - the one started by sqlite3WhereBegin(). +** +** The various GROUPS cases are implemented using the same patterns as +** ROWS. The VM code is modified slightly so that: +** +** 1. The else branch in the main loop is only taken if the row just +** added to the ephemeral table is the start of a new group. In +** other words, it becomes: +** +** ... loop started by sqlite3WhereBegin() ... +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** if( first row of partition ){ +** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent) +** regEnd = <expr2> +** regStart = <expr1> +** }else if( new group ){ +** ... +** } +** } +** +** 2. Instead of processing a single row, each RETURN_ROW, AGGSTEP or +** AGGINVERSE step processes the current row of the relevant cursor and +** all subsequent rows belonging to the same group. +** +** RANGE window frames are a little different again. As for GROUPS, the +** main loop runs once per group only. And RETURN_ROW, AGGSTEP and AGGINVERSE +** deal in groups instead of rows. As for ROWS and GROUPS, there are three +** basic cases: +** +** RANGE BETWEEN <expr1> PRECEDING AND <expr2> FOLLOWING +** +** ... loop started by sqlite3WhereBegin() ... +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** if( first row of partition ){ +** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent) +** regEnd = <expr2> +** regStart = <expr1> +** }else{ +** AGGSTEP +** while( (csrCurrent.key + regEnd) < csrEnd.key ){ +** RETURN_ROW +** while( csrStart.key + regStart) < csrCurrent.key ){ +** AGGINVERSE +** } +** } +** } +** } +** flush: +** AGGSTEP +** while( 1 ){ +** RETURN ROW +** if( csrCurrent is EOF ) break; +** while( csrStart.key + regStart) < csrCurrent.key ){ +** AGGINVERSE +** } +** } +** } +** +** In the above notation, "csr.key" means the current value of the ORDER BY +** expression (there is only ever 1 for a RANGE that uses an <expr> FOLLOWING +** or <expr PRECEDING) read from cursor csr. +** +** RANGE BETWEEN <expr1> PRECEDING AND <expr2> PRECEDING +** +** ... loop started by sqlite3WhereBegin() ... +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** if( first row of partition ){ +** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent) +** regEnd = <expr2> +** regStart = <expr1> +** }else{ +** while( (csrEnd.key + regEnd) <= csrCurrent.key ){ +** AGGSTEP +** } +** while( (csrStart.key + regStart) < csrCurrent.key ){ +** AGGINVERSE +** } +** RETURN_ROW +** } +** } +** flush: +** while( (csrEnd.key + regEnd) <= csrCurrent.key ){ +** AGGSTEP +** } +** while( (csrStart.key + regStart) < csrCurrent.key ){ +** AGGINVERSE +** } +** RETURN_ROW +** +** RANGE BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING +** +** ... loop started by sqlite3WhereBegin() ... +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** if( first row of partition ){ +** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent) +** regEnd = <expr2> +** regStart = <expr1> +** }else{ +** AGGSTEP +** while( (csrCurrent.key + regEnd) < csrEnd.key ){ +** while( (csrCurrent.key + regStart) > csrStart.key ){ +** AGGINVERSE +** } +** RETURN_ROW +** } +** } +** } +** flush: +** AGGSTEP +** while( 1 ){ +** while( (csrCurrent.key + regStart) > csrStart.key ){ +** AGGINVERSE +** if( eof ) break "while( 1 )" loop. +** } +** RETURN_ROW +** } +** while( !eof csrCurrent ){ +** RETURN_ROW +** } +** +** The text above leaves out many details. Refer to the code and comments +** below for a more complete picture. +*/ +SQLITE_PRIVATE void sqlite3WindowCodeStep( + Parse *pParse, /* Parse context */ + Select *p, /* Rewritten SELECT statement */ + WhereInfo *pWInfo, /* Context returned by sqlite3WhereBegin() */ + int regGosub, /* Register for OP_Gosub */ + int addrGosub /* OP_Gosub here to return each row */ +){ + Window *pMWin = p->pWin; + ExprList *pOrderBy = pMWin->pOrderBy; + Vdbe *v = sqlite3GetVdbe(pParse); + int csrWrite; /* Cursor used to write to eph. table */ + int csrInput = p->pSrc->a[0].iCursor; /* Cursor of sub-select */ + int nInput = p->pSrc->a[0].pTab->nCol; /* Number of cols returned by sub */ + int iInput; /* To iterate through sub cols */ + int addrNe; /* Address of OP_Ne */ + int addrGosubFlush = 0; /* Address of OP_Gosub to flush: */ + int addrInteger = 0; /* Address of OP_Integer */ + int addrEmpty; /* Address of OP_Rewind in flush: */ + int regNew; /* Array of registers holding new input row */ + int regRecord; /* regNew array in record form */ + int regNewPeer = 0; /* Peer values for new row (part of regNew) */ + int regPeer = 0; /* Peer values for current row */ + int regFlushPart = 0; /* Register for "Gosub flush_partition" */ + WindowCodeArg s; /* Context object for sub-routines */ + int lblWhereEnd; /* Label just before sqlite3WhereEnd() code */ + int regStart = 0; /* Value of <expr> PRECEDING */ + int regEnd = 0; /* Value of <expr> FOLLOWING */ + + assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_CURRENT + || pMWin->eStart==TK_FOLLOWING || pMWin->eStart==TK_UNBOUNDED + ); + assert( pMWin->eEnd==TK_FOLLOWING || pMWin->eEnd==TK_CURRENT + || pMWin->eEnd==TK_UNBOUNDED || pMWin->eEnd==TK_PRECEDING + ); + assert( pMWin->eExclude==0 || pMWin->eExclude==TK_CURRENT + || pMWin->eExclude==TK_GROUP || pMWin->eExclude==TK_TIES + || pMWin->eExclude==TK_NO + ); + + lblWhereEnd = sqlite3VdbeMakeLabel(pParse); + + /* Fill in the context object */ + memset(&s, 0, sizeof(WindowCodeArg)); + s.pParse = pParse; + s.pMWin = pMWin; + s.pVdbe = v; + s.regGosub = regGosub; + s.addrGosub = addrGosub; + s.current.csr = pMWin->iEphCsr; + csrWrite = s.current.csr+1; + s.start.csr = s.current.csr+2; + s.end.csr = s.current.csr+3; + + /* Figure out when rows may be deleted from the ephemeral table. There + ** are four options - they may never be deleted (eDelete==0), they may + ** be deleted as soon as they are no longer part of the window frame + ** (eDelete==WINDOW_AGGINVERSE), they may be deleted as after the row + ** has been returned to the caller (WINDOW_RETURN_ROW), or they may + ** be deleted after they enter the frame (WINDOW_AGGSTEP). */ + switch( pMWin->eStart ){ + case TK_FOLLOWING: + if( pMWin->eFrmType!=TK_RANGE + && windowExprGtZero(pParse, pMWin->pStart) + ){ + s.eDelete = WINDOW_RETURN_ROW; + } + break; + case TK_UNBOUNDED: + if( windowCacheFrame(pMWin)==0 ){ + if( pMWin->eEnd==TK_PRECEDING ){ + if( pMWin->eFrmType!=TK_RANGE + && windowExprGtZero(pParse, pMWin->pEnd) + ){ + s.eDelete = WINDOW_AGGSTEP; + } + }else{ + s.eDelete = WINDOW_RETURN_ROW; + } + } + break; + default: + s.eDelete = WINDOW_AGGINVERSE; + break; + } + + /* Allocate registers for the array of values from the sub-query, the + ** same values in record form, and the rowid used to insert said record + ** into the ephemeral table. */ + regNew = pParse->nMem+1; + pParse->nMem += nInput; + regRecord = ++pParse->nMem; + s.regRowid = ++pParse->nMem; + + /* If the window frame contains an "<expr> PRECEDING" or "<expr> FOLLOWING" + ** clause, allocate registers to store the results of evaluating each + ** <expr>. */ + if( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING ){ + regStart = ++pParse->nMem; + } + if( pMWin->eEnd==TK_PRECEDING || pMWin->eEnd==TK_FOLLOWING ){ + regEnd = ++pParse->nMem; + } + + /* If this is not a "ROWS BETWEEN ..." frame, then allocate arrays of + ** registers to store copies of the ORDER BY expressions (peer values) + ** for the main loop, and for each cursor (start, current and end). */ + if( pMWin->eFrmType!=TK_ROWS ){ + int nPeer = (pOrderBy ? pOrderBy->nExpr : 0); + regNewPeer = regNew + pMWin->nBufferCol; + if( pMWin->pPartition ) regNewPeer += pMWin->pPartition->nExpr; + regPeer = pParse->nMem+1; pParse->nMem += nPeer; + s.start.reg = pParse->nMem+1; pParse->nMem += nPeer; + s.current.reg = pParse->nMem+1; pParse->nMem += nPeer; + s.end.reg = pParse->nMem+1; pParse->nMem += nPeer; + } + + /* Load the column values for the row returned by the sub-select + ** into an array of registers starting at regNew. Assemble them into + ** a record in register regRecord. */ + for(iInput=0; iInput<nInput; iInput++){ + sqlite3VdbeAddOp3(v, OP_Column, csrInput, iInput, regNew+iInput); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regNew, nInput, regRecord); + + /* An input row has just been read into an array of registers starting + ** at regNew. If the window has a PARTITION clause, this block generates + ** VM code to check if the input row is the start of a new partition. + ** If so, it does an OP_Gosub to an address to be filled in later. The + ** address of the OP_Gosub is stored in local variable addrGosubFlush. */ + if( pMWin->pPartition ){ + int addr; + ExprList *pPart = pMWin->pPartition; + int nPart = pPart->nExpr; + int regNewPart = regNew + pMWin->nBufferCol; + KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pPart, 0, 0); + + regFlushPart = ++pParse->nMem; + addr = sqlite3VdbeAddOp3(v, OP_Compare, regNewPart, pMWin->regPart, nPart); + sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO); + sqlite3VdbeAddOp3(v, OP_Jump, addr+2, addr+4, addr+2); + VdbeCoverageEqNe(v); + addrGosubFlush = sqlite3VdbeAddOp1(v, OP_Gosub, regFlushPart); + VdbeComment((v, "call flush_partition")); + sqlite3VdbeAddOp3(v, OP_Copy, regNewPart, pMWin->regPart, nPart-1); + } + + /* Insert the new row into the ephemeral table */ + sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, s.regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, s.regRowid); + addrNe = sqlite3VdbeAddOp3(v, OP_Ne, pMWin->regOne, 0, s.regRowid); + VdbeCoverageNeverNull(v); + + /* This block is run for the first row of each partition */ + s.regArg = windowInitAccum(pParse, pMWin); + + if( regStart ){ + sqlite3ExprCode(pParse, pMWin->pStart, regStart); + windowCheckValue(pParse, regStart, 0 + (pMWin->eFrmType==TK_RANGE?3:0)); + } + if( regEnd ){ + sqlite3ExprCode(pParse, pMWin->pEnd, regEnd); + windowCheckValue(pParse, regEnd, 1 + (pMWin->eFrmType==TK_RANGE?3:0)); + } + + if( pMWin->eFrmType!=TK_RANGE && pMWin->eStart==pMWin->eEnd && regStart ){ + int op = ((pMWin->eStart==TK_FOLLOWING) ? OP_Ge : OP_Le); + int addrGe = sqlite3VdbeAddOp3(v, op, regStart, 0, regEnd); + VdbeCoverageNeverNullIf(v, op==OP_Ge); /* NeverNull because bound <expr> */ + VdbeCoverageNeverNullIf(v, op==OP_Le); /* values previously checked */ + windowAggFinal(&s, 0); + sqlite3VdbeAddOp1(v, OP_Rewind, s.current.csr); + windowReturnOneRow(&s); + sqlite3VdbeAddOp1(v, OP_ResetSorter, s.current.csr); + sqlite3VdbeAddOp2(v, OP_Goto, 0, lblWhereEnd); + sqlite3VdbeJumpHere(v, addrGe); + } + if( pMWin->eStart==TK_FOLLOWING && pMWin->eFrmType!=TK_RANGE && regEnd ){ + assert( pMWin->eEnd==TK_FOLLOWING ); + sqlite3VdbeAddOp3(v, OP_Subtract, regStart, regEnd, regStart); + } + + if( pMWin->eStart!=TK_UNBOUNDED ){ + sqlite3VdbeAddOp1(v, OP_Rewind, s.start.csr); + } + sqlite3VdbeAddOp1(v, OP_Rewind, s.current.csr); + sqlite3VdbeAddOp1(v, OP_Rewind, s.end.csr); + if( regPeer && pOrderBy ){ + sqlite3VdbeAddOp3(v, OP_Copy, regNewPeer, regPeer, pOrderBy->nExpr-1); + sqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.start.reg, pOrderBy->nExpr-1); + sqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.current.reg, pOrderBy->nExpr-1); + sqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.end.reg, pOrderBy->nExpr-1); + } + + sqlite3VdbeAddOp2(v, OP_Goto, 0, lblWhereEnd); + + sqlite3VdbeJumpHere(v, addrNe); + + /* Beginning of the block executed for the second and subsequent rows. */ + if( regPeer ){ + windowIfNewPeer(pParse, pOrderBy, regNewPeer, regPeer, lblWhereEnd); + } + if( pMWin->eStart==TK_FOLLOWING ){ + windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0); + if( pMWin->eEnd!=TK_UNBOUNDED ){ + if( pMWin->eFrmType==TK_RANGE ){ + int lbl = sqlite3VdbeMakeLabel(pParse); + int addrNext = sqlite3VdbeCurrentAddr(v); + windowCodeRangeTest(&s, OP_Ge, s.current.csr, regEnd, s.end.csr, lbl); + windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0); + windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrNext); + sqlite3VdbeResolveLabel(v, lbl); + }else{ + windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 0); + windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0); + } + } + }else + if( pMWin->eEnd==TK_PRECEDING ){ + int bRPS = (pMWin->eStart==TK_PRECEDING && pMWin->eFrmType==TK_RANGE); + windowCodeOp(&s, WINDOW_AGGSTEP, regEnd, 0); + if( bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0); + windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0); + if( !bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0); + }else{ + int addr = 0; + windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0); + if( pMWin->eEnd!=TK_UNBOUNDED ){ + if( pMWin->eFrmType==TK_RANGE ){ + int lbl = 0; + addr = sqlite3VdbeCurrentAddr(v); + if( regEnd ){ + lbl = sqlite3VdbeMakeLabel(pParse); + windowCodeRangeTest(&s, OP_Ge, s.current.csr, regEnd, s.end.csr, lbl); + } + windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0); + windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0); + if( regEnd ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, addr); + sqlite3VdbeResolveLabel(v, lbl); + } + }else{ + if( regEnd ){ + addr = sqlite3VdbeAddOp3(v, OP_IfPos, regEnd, 0, 1); + VdbeCoverage(v); + } + windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0); + windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0); + if( regEnd ) sqlite3VdbeJumpHere(v, addr); + } + } + } + + /* End of the main input loop */ + sqlite3VdbeResolveLabel(v, lblWhereEnd); + sqlite3WhereEnd(pWInfo); + + /* Fall through */ + if( pMWin->pPartition ){ + addrInteger = sqlite3VdbeAddOp2(v, OP_Integer, 0, regFlushPart); + sqlite3VdbeJumpHere(v, addrGosubFlush); + } + + s.regRowid = 0; + addrEmpty = sqlite3VdbeAddOp1(v, OP_Rewind, csrWrite); + VdbeCoverage(v); + if( pMWin->eEnd==TK_PRECEDING ){ + int bRPS = (pMWin->eStart==TK_PRECEDING && pMWin->eFrmType==TK_RANGE); + windowCodeOp(&s, WINDOW_AGGSTEP, regEnd, 0); + if( bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0); + windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0); + }else if( pMWin->eStart==TK_FOLLOWING ){ + int addrStart; + int addrBreak1; + int addrBreak2; + int addrBreak3; + windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0); + if( pMWin->eFrmType==TK_RANGE ){ + addrStart = sqlite3VdbeCurrentAddr(v); + addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1); + addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1); + }else + if( pMWin->eEnd==TK_UNBOUNDED ){ + addrStart = sqlite3VdbeCurrentAddr(v); + addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regStart, 1); + addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, 0, 1); + }else{ + assert( pMWin->eEnd==TK_FOLLOWING ); + addrStart = sqlite3VdbeCurrentAddr(v); + addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 1); + addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1); + } + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart); + sqlite3VdbeJumpHere(v, addrBreak2); + addrStart = sqlite3VdbeCurrentAddr(v); + addrBreak3 = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart); + sqlite3VdbeJumpHere(v, addrBreak1); + sqlite3VdbeJumpHere(v, addrBreak3); + }else{ + int addrBreak; + int addrStart; + windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0); + addrStart = sqlite3VdbeCurrentAddr(v); + addrBreak = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1); + windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart); + sqlite3VdbeJumpHere(v, addrBreak); + } + sqlite3VdbeJumpHere(v, addrEmpty); + + sqlite3VdbeAddOp1(v, OP_ResetSorter, s.current.csr); + if( pMWin->pPartition ){ + if( pMWin->regStartRowid ){ + sqlite3VdbeAddOp2(v, OP_Integer, 1, pMWin->regStartRowid); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pMWin->regEndRowid); + } + sqlite3VdbeChangeP1(v, addrInteger, sqlite3VdbeCurrentAddr(v)); + sqlite3VdbeAddOp1(v, OP_Return, regFlushPart); + } +} + +#endif /* SQLITE_OMIT_WINDOWFUNC */ + +/************** End of window.c **********************************************/ +/************** Begin file parse.c *******************************************/ +/* This file is automatically generated by Lemon from input grammar +** source file "parse.y". +*/ +/* +** 2001-09-15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains SQLite's SQL parser. +** +** The canonical source code to this file ("parse.y") is a Lemon grammar +** file that specifies the input grammar and actions to take while parsing. +** That input file is processed by Lemon to generate a C-language +** implementation of a parser for the given grammar. You might be reading +** this comment as part of the translated C-code. Edits should be made +** to the original parse.y sources. +*/ + +/* #include "sqliteInt.h" */ + +/* +** Disable all error recovery processing in the parser push-down +** automaton. +*/ +#define YYNOERRORRECOVERY 1 + +/* +** Make yytestcase() the same as testcase() +*/ +#define yytestcase(X) testcase(X) + +/* +** Indicate that sqlite3ParserFree() will never be called with a null +** pointer. +*/ +#define YYPARSEFREENEVERNULL 1 + +/* +** In the amalgamation, the parse.c file generated by lemon and the +** tokenize.c file are concatenated. In that case, sqlite3RunParser() +** has access to the the size of the yyParser object and so the parser +** engine can be allocated from stack. In that case, only the +** sqlite3ParserInit() and sqlite3ParserFinalize() routines are invoked +** and the sqlite3ParserAlloc() and sqlite3ParserFree() routines can be +** omitted. +*/ +#ifdef SQLITE_AMALGAMATION +# define sqlite3Parser_ENGINEALWAYSONSTACK 1 +#endif + +/* +** Alternative datatype for the argument to the malloc() routine passed +** into sqlite3ParserAlloc(). The default is size_t. +*/ +#define YYMALLOCARGTYPE u64 + +/* +** An instance of the following structure describes the event of a +** TRIGGER. "a" is the event type, one of TK_UPDATE, TK_INSERT, +** TK_DELETE, or TK_INSTEAD. If the event is of the form +** +** UPDATE ON (a,b,c) +** +** Then the "b" IdList records the list "a,b,c". +*/ +struct TrigEvent { int a; IdList * b; }; + +struct FrameBound { int eType; Expr *pExpr; }; + +/* +** Disable lookaside memory allocation for objects that might be +** shared across database connections. +*/ +static void disableLookaside(Parse *pParse){ + sqlite3 *db = pParse->db; + pParse->disableLookaside++; + DisableLookaside; +} + +#if !defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) \ + && defined(SQLITE_UDL_CAPABLE_PARSER) +/* +** Issue an error message if an ORDER BY or LIMIT clause occurs on an +** UPDATE or DELETE statement. +*/ +static void updateDeleteLimitError( + Parse *pParse, + ExprList *pOrderBy, + Expr *pLimit +){ + if( pOrderBy ){ + sqlite3ErrorMsg(pParse, "syntax error near \"ORDER BY\""); + }else{ + sqlite3ErrorMsg(pParse, "syntax error near \"LIMIT\""); + } + sqlite3ExprListDelete(pParse->db, pOrderBy); + sqlite3ExprDelete(pParse->db, pLimit); +} +#endif /* SQLITE_ENABLE_UPDATE_DELETE_LIMIT */ + + + /* + ** For a compound SELECT statement, make sure p->pPrior->pNext==p for + ** all elements in the list. And make sure list length does not exceed + ** SQLITE_LIMIT_COMPOUND_SELECT. + */ + static void parserDoubleLinkSelect(Parse *pParse, Select *p){ + assert( p!=0 ); + if( p->pPrior ){ + Select *pNext = 0, *pLoop = p; + int mxSelect, cnt = 1; + while(1){ + pLoop->pNext = pNext; + pLoop->selFlags |= SF_Compound; + pNext = pLoop; + pLoop = pLoop->pPrior; + if( pLoop==0 ) break; + cnt++; + if( pLoop->pOrderBy || pLoop->pLimit ){ + sqlite3ErrorMsg(pParse,"%s clause should come after %s not before", + pLoop->pOrderBy!=0 ? "ORDER BY" : "LIMIT", + sqlite3SelectOpName(pNext->op)); + break; + } + } + if( (p->selFlags & SF_MultiValue)==0 && + (mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0 && + cnt>mxSelect + ){ + sqlite3ErrorMsg(pParse, "too many terms in compound SELECT"); + } + } + } + + /* Attach a With object describing the WITH clause to a Select + ** object describing the query for which the WITH clause is a prefix. + */ + static Select *attachWithToSelect(Parse *pParse, Select *pSelect, With *pWith){ + if( pSelect ){ + pSelect->pWith = pWith; + parserDoubleLinkSelect(pParse, pSelect); + }else{ + sqlite3WithDelete(pParse->db, pWith); + } + return pSelect; + } + + + /* Construct a new Expr object from a single token */ + static Expr *tokenExpr(Parse *pParse, int op, Token t){ + Expr *p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)+t.n+1); + if( p ){ + /* memset(p, 0, sizeof(Expr)); */ + p->op = (u8)op; + p->affExpr = 0; + p->flags = EP_Leaf; + ExprClearVVAProperties(p); + /* p->iAgg = -1; // Not required */ + p->pLeft = p->pRight = 0; + p->pAggInfo = 0; + memset(&p->x, 0, sizeof(p->x)); + memset(&p->y, 0, sizeof(p->y)); + p->op2 = 0; + p->iTable = 0; + p->iColumn = 0; + p->u.zToken = (char*)&p[1]; + memcpy(p->u.zToken, t.z, t.n); + p->u.zToken[t.n] = 0; + p->w.iOfst = (int)(t.z - pParse->zTail); + if( sqlite3Isquote(p->u.zToken[0]) ){ + sqlite3DequoteExpr(p); + } +#if SQLITE_MAX_EXPR_DEPTH>0 + p->nHeight = 1; +#endif + if( IN_RENAME_OBJECT ){ + return (Expr*)sqlite3RenameTokenMap(pParse, (void*)p, &t); + } + } + return p; + } + + + /* A routine to convert a binary TK_IS or TK_ISNOT expression into a + ** unary TK_ISNULL or TK_NOTNULL expression. */ + static void binaryToUnaryIfNull(Parse *pParse, Expr *pY, Expr *pA, int op){ + sqlite3 *db = pParse->db; + if( pA && pY && pY->op==TK_NULL && !IN_RENAME_OBJECT ){ + pA->op = (u8)op; + sqlite3ExprDelete(db, pA->pRight); + pA->pRight = 0; + } + } + + /* Add a single new term to an ExprList that is used to store a + ** list of identifiers. Report an error if the ID list contains + ** a COLLATE clause or an ASC or DESC keyword, except ignore the + ** error while parsing a legacy schema. + */ + static ExprList *parserAddExprIdListTerm( + Parse *pParse, + ExprList *pPrior, + Token *pIdToken, + int hasCollate, + int sortOrder + ){ + ExprList *p = sqlite3ExprListAppend(pParse, pPrior, 0); + if( (hasCollate || sortOrder!=SQLITE_SO_UNDEFINED) + && pParse->db->init.busy==0 + ){ + sqlite3ErrorMsg(pParse, "syntax error after column name \"%.*s\"", + pIdToken->n, pIdToken->z); + } + sqlite3ExprListSetName(pParse, p, pIdToken, 1); + return p; + } + +#if TK_SPAN>255 +# error too many tokens in the grammar +#endif +/**************** End of %include directives **********************************/ +/* These constants specify the various numeric values for terminal symbols. +***************** Begin token definitions *************************************/ +#ifndef TK_SEMI +#define TK_SEMI 1 +#define TK_EXPLAIN 2 +#define TK_QUERY 3 +#define TK_PLAN 4 +#define TK_BEGIN 5 +#define TK_TRANSACTION 6 +#define TK_DEFERRED 7 +#define TK_IMMEDIATE 8 +#define TK_EXCLUSIVE 9 +#define TK_COMMIT 10 +#define TK_END 11 +#define TK_ROLLBACK 12 +#define TK_SAVEPOINT 13 +#define TK_RELEASE 14 +#define TK_TO 15 +#define TK_TABLE 16 +#define TK_CREATE 17 +#define TK_IF 18 +#define TK_NOT 19 +#define TK_EXISTS 20 +#define TK_TEMP 21 +#define TK_LP 22 +#define TK_RP 23 +#define TK_AS 24 +#define TK_COMMA 25 +#define TK_WITHOUT 26 +#define TK_ABORT 27 +#define TK_ACTION 28 +#define TK_AFTER 29 +#define TK_ANALYZE 30 +#define TK_ASC 31 +#define TK_ATTACH 32 +#define TK_BEFORE 33 +#define TK_BY 34 +#define TK_CASCADE 35 +#define TK_CAST 36 +#define TK_CONFLICT 37 +#define TK_DATABASE 38 +#define TK_DESC 39 +#define TK_DETACH 40 +#define TK_EACH 41 +#define TK_FAIL 42 +#define TK_OR 43 +#define TK_AND 44 +#define TK_IS 45 +#define TK_MATCH 46 +#define TK_LIKE_KW 47 +#define TK_BETWEEN 48 +#define TK_IN 49 +#define TK_ISNULL 50 +#define TK_NOTNULL 51 +#define TK_NE 52 +#define TK_EQ 53 +#define TK_GT 54 +#define TK_LE 55 +#define TK_LT 56 +#define TK_GE 57 +#define TK_ESCAPE 58 +#define TK_ID 59 +#define TK_COLUMNKW 60 +#define TK_DO 61 +#define TK_FOR 62 +#define TK_IGNORE 63 +#define TK_INITIALLY 64 +#define TK_INSTEAD 65 +#define TK_NO 66 +#define TK_KEY 67 +#define TK_OF 68 +#define TK_OFFSET 69 +#define TK_PRAGMA 70 +#define TK_RAISE 71 +#define TK_RECURSIVE 72 +#define TK_REPLACE 73 +#define TK_RESTRICT 74 +#define TK_ROW 75 +#define TK_ROWS 76 +#define TK_TRIGGER 77 +#define TK_VACUUM 78 +#define TK_VIEW 79 +#define TK_VIRTUAL 80 +#define TK_WITH 81 +#define TK_NULLS 82 +#define TK_FIRST 83 +#define TK_LAST 84 +#define TK_CURRENT 85 +#define TK_FOLLOWING 86 +#define TK_PARTITION 87 +#define TK_PRECEDING 88 +#define TK_RANGE 89 +#define TK_UNBOUNDED 90 +#define TK_EXCLUDE 91 +#define TK_GROUPS 92 +#define TK_OTHERS 93 +#define TK_TIES 94 +#define TK_GENERATED 95 +#define TK_ALWAYS 96 +#define TK_MATERIALIZED 97 +#define TK_REINDEX 98 +#define TK_RENAME 99 +#define TK_CTIME_KW 100 +#define TK_ANY 101 +#define TK_BITAND 102 +#define TK_BITOR 103 +#define TK_LSHIFT 104 +#define TK_RSHIFT 105 +#define TK_PLUS 106 +#define TK_MINUS 107 +#define TK_STAR 108 +#define TK_SLASH 109 +#define TK_REM 110 +#define TK_CONCAT 111 +#define TK_PTR 112 +#define TK_COLLATE 113 +#define TK_BITNOT 114 +#define TK_ON 115 +#define TK_INDEXED 116 +#define TK_STRING 117 +#define TK_JOIN_KW 118 +#define TK_CONSTRAINT 119 +#define TK_DEFAULT 120 +#define TK_NULL 121 +#define TK_PRIMARY 122 +#define TK_UNIQUE 123 +#define TK_CHECK 124 +#define TK_REFERENCES 125 +#define TK_AUTOINCR 126 +#define TK_INSERT 127 +#define TK_DELETE 128 +#define TK_UPDATE 129 +#define TK_SET 130 +#define TK_DEFERRABLE 131 +#define TK_FOREIGN 132 +#define TK_DROP 133 +#define TK_UNION 134 +#define TK_ALL 135 +#define TK_EXCEPT 136 +#define TK_INTERSECT 137 +#define TK_SELECT 138 +#define TK_VALUES 139 +#define TK_DISTINCT 140 +#define TK_DOT 141 +#define TK_FROM 142 +#define TK_JOIN 143 +#define TK_USING 144 +#define TK_ORDER 145 +#define TK_GROUP 146 +#define TK_HAVING 147 +#define TK_LIMIT 148 +#define TK_WHERE 149 +#define TK_RETURNING 150 +#define TK_INTO 151 +#define TK_NOTHING 152 +#define TK_FLOAT 153 +#define TK_BLOB 154 +#define TK_INTEGER 155 +#define TK_VARIABLE 156 +#define TK_CASE 157 +#define TK_WHEN 158 +#define TK_THEN 159 +#define TK_ELSE 160 +#define TK_INDEX 161 +#define TK_ALTER 162 +#define TK_ADD 163 +#define TK_WINDOW 164 +#define TK_OVER 165 +#define TK_FILTER 166 +#define TK_COLUMN 167 +#define TK_AGG_FUNCTION 168 +#define TK_AGG_COLUMN 169 +#define TK_TRUEFALSE 170 +#define TK_ISNOT 171 +#define TK_FUNCTION 172 +#define TK_UMINUS 173 +#define TK_UPLUS 174 +#define TK_TRUTH 175 +#define TK_REGISTER 176 +#define TK_VECTOR 177 +#define TK_SELECT_COLUMN 178 +#define TK_IF_NULL_ROW 179 +#define TK_ASTERISK 180 +#define TK_SPAN 181 +#define TK_ERROR 182 +#define TK_SPACE 183 +#define TK_ILLEGAL 184 +#endif +/**************** End token definitions ***************************************/ + +/* The next sections is a series of control #defines. +** various aspects of the generated parser. +** YYCODETYPE is the data type used to store the integer codes +** that represent terminal and non-terminal symbols. +** "unsigned char" is used if there are fewer than +** 256 symbols. Larger types otherwise. +** YYNOCODE is a number of type YYCODETYPE that is not used for +** any terminal or nonterminal symbol. +** YYFALLBACK If defined, this indicates that one or more tokens +** (also known as: "terminal symbols") have fall-back +** values which should be used if the original symbol +** would not parse. This permits keywords to sometimes +** be used as identifiers, for example. +** YYACTIONTYPE is the data type used for "action codes" - numbers +** that indicate what to do in response to the next +** token. +** sqlite3ParserTOKENTYPE is the data type used for minor type for terminal +** symbols. Background: A "minor type" is a semantic +** value associated with a terminal or non-terminal +** symbols. For example, for an "ID" terminal symbol, +** the minor type might be the name of the identifier. +** Each non-terminal can have a different minor type. +** Terminal symbols all have the same minor type, though. +** This macros defines the minor type for terminal +** symbols. +** YYMINORTYPE is the data type used for all minor types. +** This is typically a union of many types, one of +** which is sqlite3ParserTOKENTYPE. The entry in the union +** for terminal symbols is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** sqlite3ParserARG_SDECL A static variable declaration for the %extra_argument +** sqlite3ParserARG_PDECL A parameter declaration for the %extra_argument +** sqlite3ParserARG_PARAM Code to pass %extra_argument as a subroutine parameter +** sqlite3ParserARG_STORE Code to store %extra_argument into yypParser +** sqlite3ParserARG_FETCH Code to extract %extra_argument from yypParser +** sqlite3ParserCTX_* As sqlite3ParserARG_ except for %extra_context +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYNTOKEN Number of terminal symbols +** YY_MAX_SHIFT Maximum value for shift actions +** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions +** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions +** YY_ERROR_ACTION The yy_action[] code for syntax error +** YY_ACCEPT_ACTION The yy_action[] code for accept +** YY_NO_ACTION The yy_action[] code for no-op +** YY_MIN_REDUCE Minimum value for reduce actions +** YY_MAX_REDUCE Maximum value for reduce actions +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/************* Begin control #defines *****************************************/ +#define YYCODETYPE unsigned short int +#define YYNOCODE 319 +#define YYACTIONTYPE unsigned short int +#define YYWILDCARD 101 +#define sqlite3ParserTOKENTYPE Token +typedef union { + int yyinit; + sqlite3ParserTOKENTYPE yy0; + TriggerStep* yy33; + Window* yy41; + Select* yy47; + SrcList* yy131; + struct TrigEvent yy180; + struct {int value; int mask;} yy231; + IdList* yy254; + u32 yy285; + ExprList* yy322; + Cte* yy385; + int yy394; + Upsert* yy444; + u8 yy516; + With* yy521; + const char* yy522; + Expr* yy528; + OnOrUsing yy561; + struct FrameBound yy595; +} YYMINORTYPE; +#ifndef YYSTACKDEPTH +#define YYSTACKDEPTH 100 +#endif +#define sqlite3ParserARG_SDECL +#define sqlite3ParserARG_PDECL +#define sqlite3ParserARG_PARAM +#define sqlite3ParserARG_FETCH +#define sqlite3ParserARG_STORE +#define sqlite3ParserCTX_SDECL Parse *pParse; +#define sqlite3ParserCTX_PDECL ,Parse *pParse +#define sqlite3ParserCTX_PARAM ,pParse +#define sqlite3ParserCTX_FETCH Parse *pParse=yypParser->pParse; +#define sqlite3ParserCTX_STORE yypParser->pParse=pParse; +#define YYFALLBACK 1 +#define YYNSTATE 575 +#define YYNRULE 403 +#define YYNRULE_WITH_ACTION 338 +#define YYNTOKEN 185 +#define YY_MAX_SHIFT 574 +#define YY_MIN_SHIFTREDUCE 833 +#define YY_MAX_SHIFTREDUCE 1235 +#define YY_ERROR_ACTION 1236 +#define YY_ACCEPT_ACTION 1237 +#define YY_NO_ACTION 1238 +#define YY_MIN_REDUCE 1239 +#define YY_MAX_REDUCE 1641 +/************* End control #defines *******************************************/ +#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0]))) + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N <= YY_MAX_SHIFT Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then +** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE. +** +** N == YY_ERROR_ACTION A syntax error has occurred. +** +** N == YY_ACCEPT_ACTION The parser accepts its input. +** +** N == YY_NO_ACTION No such action. Denotes unused +** slots in the yy_action[] table. +** +** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE +** and YY_MAX_REDUCE +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as either: +** +** (A) N = yy_action[ yy_shift_ofst[S] + X ] +** (B) N = yy_default[S] +** +** The (A) formula is preferred. The B formula is used instead if +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X. +** +** The formulas above are for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +** +*********** Begin parsing tables **********************************************/ +#define YY_ACTTAB_COUNT (2096) +static const YYACTIONTYPE yy_action[] = { + /* 0 */ 568, 208, 568, 118, 115, 229, 568, 118, 115, 229, + /* 10 */ 568, 1310, 377, 1289, 408, 562, 562, 562, 568, 409, + /* 20 */ 378, 1310, 1272, 41, 41, 41, 41, 208, 1520, 71, + /* 30 */ 71, 969, 419, 41, 41, 491, 303, 279, 303, 970, + /* 40 */ 397, 71, 71, 125, 126, 80, 1210, 1210, 1047, 1050, + /* 50 */ 1037, 1037, 123, 123, 124, 124, 124, 124, 476, 409, + /* 60 */ 1237, 1, 1, 574, 2, 1241, 550, 118, 115, 229, + /* 70 */ 317, 480, 146, 480, 524, 118, 115, 229, 529, 1323, + /* 80 */ 417, 523, 142, 125, 126, 80, 1210, 1210, 1047, 1050, + /* 90 */ 1037, 1037, 123, 123, 124, 124, 124, 124, 118, 115, + /* 100 */ 229, 327, 122, 122, 122, 122, 121, 121, 120, 120, + /* 110 */ 120, 119, 116, 444, 284, 284, 284, 284, 442, 442, + /* 120 */ 442, 1559, 376, 1561, 1186, 375, 1157, 565, 1157, 565, + /* 130 */ 409, 1559, 537, 259, 226, 444, 101, 145, 449, 316, + /* 140 */ 559, 240, 122, 122, 122, 122, 121, 121, 120, 120, + /* 150 */ 120, 119, 116, 444, 125, 126, 80, 1210, 1210, 1047, + /* 160 */ 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, 142, + /* 170 */ 294, 1186, 339, 448, 120, 120, 120, 119, 116, 444, + /* 180 */ 127, 1186, 1187, 1186, 148, 441, 440, 568, 119, 116, + /* 190 */ 444, 124, 124, 124, 124, 117, 122, 122, 122, 122, + /* 200 */ 121, 121, 120, 120, 120, 119, 116, 444, 454, 113, + /* 210 */ 13, 13, 546, 122, 122, 122, 122, 121, 121, 120, + /* 220 */ 120, 120, 119, 116, 444, 422, 316, 559, 1186, 1187, + /* 230 */ 1186, 149, 1218, 409, 1218, 124, 124, 124, 124, 122, + /* 240 */ 122, 122, 122, 121, 121, 120, 120, 120, 119, 116, + /* 250 */ 444, 465, 342, 1034, 1034, 1048, 1051, 125, 126, 80, + /* 260 */ 1210, 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, + /* 270 */ 124, 124, 1275, 522, 222, 1186, 568, 409, 224, 514, + /* 280 */ 175, 82, 83, 122, 122, 122, 122, 121, 121, 120, + /* 290 */ 120, 120, 119, 116, 444, 1005, 16, 16, 1186, 133, + /* 300 */ 133, 125, 126, 80, 1210, 1210, 1047, 1050, 1037, 1037, + /* 310 */ 123, 123, 124, 124, 124, 124, 122, 122, 122, 122, + /* 320 */ 121, 121, 120, 120, 120, 119, 116, 444, 1038, 546, + /* 330 */ 1186, 373, 1186, 1187, 1186, 252, 1429, 399, 504, 501, + /* 340 */ 500, 111, 560, 566, 4, 924, 924, 433, 499, 340, + /* 350 */ 460, 328, 360, 394, 1231, 1186, 1187, 1186, 563, 568, + /* 360 */ 122, 122, 122, 122, 121, 121, 120, 120, 120, 119, + /* 370 */ 116, 444, 284, 284, 369, 1572, 1598, 441, 440, 154, + /* 380 */ 409, 445, 71, 71, 1282, 565, 1215, 1186, 1187, 1186, + /* 390 */ 85, 1217, 271, 557, 543, 515, 515, 568, 98, 1216, + /* 400 */ 6, 1274, 472, 142, 125, 126, 80, 1210, 1210, 1047, + /* 410 */ 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, 550, + /* 420 */ 13, 13, 1024, 507, 1218, 1186, 1218, 549, 109, 109, + /* 430 */ 222, 568, 1232, 175, 568, 427, 110, 197, 445, 569, + /* 440 */ 445, 430, 1546, 1014, 325, 551, 1186, 270, 287, 368, + /* 450 */ 510, 363, 509, 257, 71, 71, 543, 71, 71, 359, + /* 460 */ 316, 559, 1604, 122, 122, 122, 122, 121, 121, 120, + /* 470 */ 120, 120, 119, 116, 444, 1014, 1014, 1016, 1017, 27, + /* 480 */ 284, 284, 1186, 1187, 1186, 1152, 568, 1603, 409, 899, + /* 490 */ 190, 550, 356, 565, 550, 935, 533, 517, 1152, 516, + /* 500 */ 413, 1152, 552, 1186, 1187, 1186, 568, 544, 544, 51, + /* 510 */ 51, 214, 125, 126, 80, 1210, 1210, 1047, 1050, 1037, + /* 520 */ 1037, 123, 123, 124, 124, 124, 124, 1186, 474, 135, + /* 530 */ 135, 409, 284, 284, 1484, 505, 121, 121, 120, 120, + /* 540 */ 120, 119, 116, 444, 1005, 565, 518, 217, 541, 541, + /* 550 */ 316, 559, 142, 6, 532, 125, 126, 80, 1210, 1210, + /* 560 */ 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, + /* 570 */ 1548, 122, 122, 122, 122, 121, 121, 120, 120, 120, + /* 580 */ 119, 116, 444, 485, 1186, 1187, 1186, 482, 281, 1263, + /* 590 */ 955, 252, 1186, 373, 504, 501, 500, 1186, 340, 570, + /* 600 */ 1186, 570, 409, 292, 499, 955, 874, 191, 480, 316, + /* 610 */ 559, 384, 290, 380, 122, 122, 122, 122, 121, 121, + /* 620 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1210, + /* 630 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 640 */ 124, 409, 394, 1132, 1186, 867, 100, 284, 284, 1186, + /* 650 */ 1187, 1186, 373, 1089, 1186, 1187, 1186, 1186, 1187, 1186, + /* 660 */ 565, 455, 32, 373, 233, 125, 126, 80, 1210, 1210, + /* 670 */ 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, + /* 680 */ 1428, 957, 568, 228, 956, 122, 122, 122, 122, 121, + /* 690 */ 121, 120, 120, 120, 119, 116, 444, 1152, 228, 1186, + /* 700 */ 157, 1186, 1187, 1186, 1547, 13, 13, 301, 955, 1226, + /* 710 */ 1152, 153, 409, 1152, 373, 1575, 1170, 5, 369, 1572, + /* 720 */ 429, 1232, 3, 955, 122, 122, 122, 122, 121, 121, + /* 730 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1210, + /* 740 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 750 */ 124, 409, 208, 567, 1186, 1025, 1186, 1187, 1186, 1186, + /* 760 */ 388, 850, 155, 1546, 286, 402, 1094, 1094, 488, 568, + /* 770 */ 465, 342, 1315, 1315, 1546, 125, 126, 80, 1210, 1210, + /* 780 */ 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, + /* 790 */ 129, 568, 13, 13, 374, 122, 122, 122, 122, 121, + /* 800 */ 121, 120, 120, 120, 119, 116, 444, 302, 568, 453, + /* 810 */ 528, 1186, 1187, 1186, 13, 13, 1186, 1187, 1186, 1293, + /* 820 */ 463, 1263, 409, 1313, 1313, 1546, 1010, 453, 452, 200, + /* 830 */ 299, 71, 71, 1261, 122, 122, 122, 122, 121, 121, + /* 840 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1210, + /* 850 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 860 */ 124, 409, 227, 1069, 1152, 284, 284, 419, 312, 278, + /* 870 */ 278, 285, 285, 1415, 406, 405, 382, 1152, 565, 568, + /* 880 */ 1152, 1189, 565, 1592, 565, 125, 126, 80, 1210, 1210, + /* 890 */ 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, + /* 900 */ 453, 1476, 13, 13, 1530, 122, 122, 122, 122, 121, + /* 910 */ 121, 120, 120, 120, 119, 116, 444, 201, 568, 354, + /* 920 */ 1578, 574, 2, 1241, 838, 839, 840, 1554, 317, 1205, + /* 930 */ 146, 6, 409, 255, 254, 253, 206, 1323, 9, 1189, + /* 940 */ 262, 71, 71, 424, 122, 122, 122, 122, 121, 121, + /* 950 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1210, + /* 960 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 970 */ 124, 568, 284, 284, 568, 1206, 409, 573, 313, 1241, + /* 980 */ 349, 1292, 352, 419, 317, 565, 146, 491, 525, 1635, + /* 990 */ 395, 371, 491, 1323, 70, 70, 1291, 71, 71, 240, + /* 1000 */ 1321, 104, 80, 1210, 1210, 1047, 1050, 1037, 1037, 123, + /* 1010 */ 123, 124, 124, 124, 124, 122, 122, 122, 122, 121, + /* 1020 */ 121, 120, 120, 120, 119, 116, 444, 1110, 284, 284, + /* 1030 */ 428, 448, 1519, 1206, 439, 284, 284, 1483, 1348, 311, + /* 1040 */ 474, 565, 1111, 969, 491, 491, 217, 1259, 565, 1532, + /* 1050 */ 568, 970, 207, 568, 1024, 240, 383, 1112, 519, 122, + /* 1060 */ 122, 122, 122, 121, 121, 120, 120, 120, 119, 116, + /* 1070 */ 444, 1015, 107, 71, 71, 1014, 13, 13, 910, 568, + /* 1080 */ 1489, 568, 284, 284, 97, 526, 491, 448, 911, 1322, + /* 1090 */ 1318, 545, 409, 284, 284, 565, 151, 209, 1489, 1491, + /* 1100 */ 262, 450, 55, 55, 56, 56, 565, 1014, 1014, 1016, + /* 1110 */ 443, 332, 409, 527, 12, 295, 125, 126, 80, 1210, + /* 1120 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 1130 */ 124, 347, 409, 862, 1528, 1206, 125, 126, 80, 1210, + /* 1140 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 1150 */ 124, 1133, 1633, 474, 1633, 371, 125, 114, 80, 1210, + /* 1160 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 1170 */ 124, 1489, 329, 474, 331, 122, 122, 122, 122, 121, + /* 1180 */ 121, 120, 120, 120, 119, 116, 444, 203, 1415, 568, + /* 1190 */ 1290, 862, 464, 1206, 436, 122, 122, 122, 122, 121, + /* 1200 */ 121, 120, 120, 120, 119, 116, 444, 553, 1133, 1634, + /* 1210 */ 539, 1634, 15, 15, 890, 122, 122, 122, 122, 121, + /* 1220 */ 121, 120, 120, 120, 119, 116, 444, 568, 298, 538, + /* 1230 */ 1131, 1415, 1552, 1553, 1327, 409, 6, 6, 1163, 1264, + /* 1240 */ 415, 320, 284, 284, 1415, 508, 565, 525, 300, 457, + /* 1250 */ 43, 43, 568, 891, 12, 565, 330, 478, 425, 407, + /* 1260 */ 126, 80, 1210, 1210, 1047, 1050, 1037, 1037, 123, 123, + /* 1270 */ 124, 124, 124, 124, 568, 57, 57, 288, 1186, 1415, + /* 1280 */ 496, 458, 392, 392, 391, 273, 389, 1131, 1551, 847, + /* 1290 */ 1163, 407, 6, 568, 321, 1152, 470, 44, 44, 1550, + /* 1300 */ 1110, 426, 234, 6, 323, 256, 540, 256, 1152, 431, + /* 1310 */ 568, 1152, 322, 17, 487, 1111, 58, 58, 122, 122, + /* 1320 */ 122, 122, 121, 121, 120, 120, 120, 119, 116, 444, + /* 1330 */ 1112, 216, 481, 59, 59, 1186, 1187, 1186, 111, 560, + /* 1340 */ 324, 4, 236, 456, 526, 568, 237, 456, 568, 437, + /* 1350 */ 168, 556, 420, 141, 479, 563, 568, 293, 568, 1091, + /* 1360 */ 568, 293, 568, 1091, 531, 568, 870, 8, 60, 60, + /* 1370 */ 235, 61, 61, 568, 414, 568, 414, 568, 445, 62, + /* 1380 */ 62, 45, 45, 46, 46, 47, 47, 199, 49, 49, + /* 1390 */ 557, 568, 359, 568, 100, 486, 50, 50, 63, 63, + /* 1400 */ 64, 64, 561, 415, 535, 410, 568, 1024, 568, 534, + /* 1410 */ 316, 559, 316, 559, 65, 65, 14, 14, 568, 1024, + /* 1420 */ 568, 512, 930, 870, 1015, 109, 109, 929, 1014, 66, + /* 1430 */ 66, 131, 131, 110, 451, 445, 569, 445, 416, 177, + /* 1440 */ 1014, 132, 132, 67, 67, 568, 467, 568, 930, 471, + /* 1450 */ 1360, 283, 226, 929, 315, 1359, 407, 568, 459, 407, + /* 1460 */ 1014, 1014, 1016, 239, 407, 86, 213, 1346, 52, 52, + /* 1470 */ 68, 68, 1014, 1014, 1016, 1017, 27, 1577, 1174, 447, + /* 1480 */ 69, 69, 288, 97, 108, 1535, 106, 392, 392, 391, + /* 1490 */ 273, 389, 568, 877, 847, 881, 568, 111, 560, 466, + /* 1500 */ 4, 568, 152, 30, 38, 568, 1128, 234, 396, 323, + /* 1510 */ 111, 560, 527, 4, 563, 53, 53, 322, 568, 163, + /* 1520 */ 163, 568, 337, 468, 164, 164, 333, 563, 76, 76, + /* 1530 */ 568, 289, 1508, 568, 31, 1507, 568, 445, 338, 483, + /* 1540 */ 100, 54, 54, 344, 72, 72, 296, 236, 1076, 557, + /* 1550 */ 445, 877, 1356, 134, 134, 168, 73, 73, 141, 161, + /* 1560 */ 161, 1566, 557, 535, 568, 319, 568, 348, 536, 1007, + /* 1570 */ 473, 261, 261, 889, 888, 235, 535, 568, 1024, 568, + /* 1580 */ 475, 534, 261, 367, 109, 109, 521, 136, 136, 130, + /* 1590 */ 130, 1024, 110, 366, 445, 569, 445, 109, 109, 1014, + /* 1600 */ 162, 162, 156, 156, 568, 110, 1076, 445, 569, 445, + /* 1610 */ 410, 351, 1014, 568, 353, 316, 559, 568, 343, 568, + /* 1620 */ 100, 497, 357, 258, 100, 896, 897, 140, 140, 355, + /* 1630 */ 1306, 1014, 1014, 1016, 1017, 27, 139, 139, 362, 451, + /* 1640 */ 137, 137, 138, 138, 1014, 1014, 1016, 1017, 27, 1174, + /* 1650 */ 447, 568, 372, 288, 111, 560, 1018, 4, 392, 392, + /* 1660 */ 391, 273, 389, 568, 1137, 847, 568, 1072, 568, 258, + /* 1670 */ 492, 563, 568, 211, 75, 75, 555, 960, 234, 261, + /* 1680 */ 323, 111, 560, 927, 4, 113, 77, 77, 322, 74, + /* 1690 */ 74, 42, 42, 1369, 445, 48, 48, 1414, 563, 972, + /* 1700 */ 973, 1088, 1087, 1088, 1087, 860, 557, 150, 928, 1342, + /* 1710 */ 113, 1354, 554, 1419, 1018, 1271, 1262, 1250, 236, 1249, + /* 1720 */ 1251, 445, 1585, 1339, 308, 276, 168, 309, 11, 141, + /* 1730 */ 393, 310, 232, 557, 1401, 1024, 335, 291, 1396, 219, + /* 1740 */ 336, 109, 109, 934, 297, 1406, 235, 341, 477, 110, + /* 1750 */ 502, 445, 569, 445, 1389, 1405, 1014, 400, 1289, 365, + /* 1760 */ 223, 1480, 1024, 1479, 1351, 1352, 1350, 1349, 109, 109, + /* 1770 */ 204, 1588, 1226, 558, 265, 218, 110, 205, 445, 569, + /* 1780 */ 445, 410, 387, 1014, 1527, 179, 316, 559, 1014, 1014, + /* 1790 */ 1016, 1017, 27, 230, 1525, 1223, 79, 560, 85, 4, + /* 1800 */ 418, 215, 548, 81, 84, 188, 1402, 173, 181, 461, + /* 1810 */ 451, 35, 462, 563, 183, 1014, 1014, 1016, 1017, 27, + /* 1820 */ 184, 1485, 185, 186, 495, 242, 98, 398, 1408, 36, + /* 1830 */ 1407, 484, 91, 469, 401, 1410, 445, 192, 1474, 246, + /* 1840 */ 1496, 490, 346, 277, 248, 196, 493, 511, 557, 350, + /* 1850 */ 1252, 249, 250, 403, 1309, 1308, 111, 560, 432, 4, + /* 1860 */ 1307, 1300, 93, 1602, 881, 1601, 224, 404, 434, 520, + /* 1870 */ 263, 435, 1571, 563, 1279, 1278, 364, 1024, 306, 1277, + /* 1880 */ 264, 1600, 1557, 109, 109, 370, 1299, 307, 1556, 438, + /* 1890 */ 128, 110, 1374, 445, 569, 445, 445, 546, 1014, 10, + /* 1900 */ 1461, 105, 381, 1373, 34, 571, 99, 1332, 557, 314, + /* 1910 */ 1180, 530, 272, 274, 379, 210, 1331, 547, 385, 386, + /* 1920 */ 275, 572, 1247, 1242, 411, 412, 1512, 165, 178, 1513, + /* 1930 */ 1014, 1014, 1016, 1017, 27, 1511, 1510, 1024, 78, 147, + /* 1940 */ 166, 220, 221, 109, 109, 834, 304, 167, 446, 212, + /* 1950 */ 318, 110, 231, 445, 569, 445, 144, 1086, 1014, 1084, + /* 1960 */ 326, 180, 169, 1205, 182, 334, 238, 913, 241, 1100, + /* 1970 */ 187, 170, 171, 421, 87, 88, 423, 189, 89, 90, + /* 1980 */ 172, 1103, 243, 1099, 244, 158, 18, 245, 345, 247, + /* 1990 */ 1014, 1014, 1016, 1017, 27, 261, 1092, 193, 1220, 489, + /* 2000 */ 194, 37, 366, 849, 494, 251, 195, 506, 92, 19, + /* 2010 */ 498, 358, 20, 503, 879, 361, 94, 892, 305, 159, + /* 2020 */ 513, 39, 95, 1168, 160, 1053, 964, 1139, 96, 174, + /* 2030 */ 1138, 225, 280, 282, 198, 958, 113, 1158, 1154, 260, + /* 2040 */ 21, 22, 23, 1156, 1162, 1161, 1143, 24, 33, 25, + /* 2050 */ 202, 542, 26, 100, 1067, 102, 1054, 103, 7, 1052, + /* 2060 */ 1056, 1109, 1057, 1108, 266, 267, 28, 40, 390, 1019, + /* 2070 */ 861, 112, 29, 564, 1176, 1175, 268, 176, 143, 923, + /* 2080 */ 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, + /* 2090 */ 1238, 1238, 1238, 1238, 269, 1593, +}; +static const YYCODETYPE yy_lookahead[] = { + /* 0 */ 193, 193, 193, 274, 275, 276, 193, 274, 275, 276, + /* 10 */ 193, 223, 219, 225, 206, 210, 211, 212, 193, 19, + /* 20 */ 219, 233, 216, 216, 217, 216, 217, 193, 295, 216, + /* 30 */ 217, 31, 193, 216, 217, 193, 228, 213, 230, 39, + /* 40 */ 206, 216, 217, 43, 44, 45, 46, 47, 48, 49, + /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 193, 19, + /* 60 */ 185, 186, 187, 188, 189, 190, 253, 274, 275, 276, + /* 70 */ 195, 193, 197, 193, 261, 274, 275, 276, 253, 204, + /* 80 */ 238, 204, 81, 43, 44, 45, 46, 47, 48, 49, + /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 274, 275, + /* 100 */ 276, 262, 102, 103, 104, 105, 106, 107, 108, 109, + /* 110 */ 110, 111, 112, 113, 239, 240, 239, 240, 210, 211, + /* 120 */ 212, 314, 315, 314, 59, 316, 86, 252, 88, 252, + /* 130 */ 19, 314, 315, 256, 257, 113, 25, 72, 296, 138, + /* 140 */ 139, 266, 102, 103, 104, 105, 106, 107, 108, 109, + /* 150 */ 110, 111, 112, 113, 43, 44, 45, 46, 47, 48, + /* 160 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 81, + /* 170 */ 292, 59, 292, 298, 108, 109, 110, 111, 112, 113, + /* 180 */ 69, 116, 117, 118, 72, 106, 107, 193, 111, 112, + /* 190 */ 113, 54, 55, 56, 57, 58, 102, 103, 104, 105, + /* 200 */ 106, 107, 108, 109, 110, 111, 112, 113, 120, 25, + /* 210 */ 216, 217, 145, 102, 103, 104, 105, 106, 107, 108, + /* 220 */ 109, 110, 111, 112, 113, 231, 138, 139, 116, 117, + /* 230 */ 118, 164, 153, 19, 155, 54, 55, 56, 57, 102, + /* 240 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + /* 250 */ 113, 128, 129, 46, 47, 48, 49, 43, 44, 45, + /* 260 */ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + /* 270 */ 56, 57, 216, 193, 25, 59, 193, 19, 165, 166, + /* 280 */ 193, 67, 24, 102, 103, 104, 105, 106, 107, 108, + /* 290 */ 109, 110, 111, 112, 113, 73, 216, 217, 59, 216, + /* 300 */ 217, 43, 44, 45, 46, 47, 48, 49, 50, 51, + /* 310 */ 52, 53, 54, 55, 56, 57, 102, 103, 104, 105, + /* 320 */ 106, 107, 108, 109, 110, 111, 112, 113, 121, 145, + /* 330 */ 59, 193, 116, 117, 118, 119, 273, 204, 122, 123, + /* 340 */ 124, 19, 20, 134, 22, 136, 137, 19, 132, 127, + /* 350 */ 128, 129, 24, 22, 23, 116, 117, 118, 36, 193, + /* 360 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + /* 370 */ 112, 113, 239, 240, 311, 312, 215, 106, 107, 241, + /* 380 */ 19, 59, 216, 217, 223, 252, 115, 116, 117, 118, + /* 390 */ 151, 120, 26, 71, 193, 308, 309, 193, 149, 128, + /* 400 */ 313, 216, 269, 81, 43, 44, 45, 46, 47, 48, + /* 410 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 253, + /* 420 */ 216, 217, 100, 95, 153, 59, 155, 261, 106, 107, + /* 430 */ 25, 193, 101, 193, 193, 231, 114, 25, 116, 117, + /* 440 */ 118, 113, 304, 121, 193, 204, 59, 119, 120, 121, + /* 450 */ 122, 123, 124, 125, 216, 217, 193, 216, 217, 131, + /* 460 */ 138, 139, 230, 102, 103, 104, 105, 106, 107, 108, + /* 470 */ 109, 110, 111, 112, 113, 153, 154, 155, 156, 157, + /* 480 */ 239, 240, 116, 117, 118, 76, 193, 23, 19, 25, + /* 490 */ 22, 253, 23, 252, 253, 108, 87, 204, 89, 261, + /* 500 */ 198, 92, 261, 116, 117, 118, 193, 306, 307, 216, + /* 510 */ 217, 150, 43, 44, 45, 46, 47, 48, 49, 50, + /* 520 */ 51, 52, 53, 54, 55, 56, 57, 59, 193, 216, + /* 530 */ 217, 19, 239, 240, 283, 23, 106, 107, 108, 109, + /* 540 */ 110, 111, 112, 113, 73, 252, 253, 142, 308, 309, + /* 550 */ 138, 139, 81, 313, 145, 43, 44, 45, 46, 47, + /* 560 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 570 */ 307, 102, 103, 104, 105, 106, 107, 108, 109, 110, + /* 580 */ 111, 112, 113, 281, 116, 117, 118, 285, 23, 193, + /* 590 */ 25, 119, 59, 193, 122, 123, 124, 59, 127, 203, + /* 600 */ 59, 205, 19, 268, 132, 25, 23, 22, 193, 138, + /* 610 */ 139, 249, 204, 251, 102, 103, 104, 105, 106, 107, + /* 620 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46, + /* 630 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 640 */ 57, 19, 22, 23, 59, 23, 25, 239, 240, 116, + /* 650 */ 117, 118, 193, 11, 116, 117, 118, 116, 117, 118, + /* 660 */ 252, 269, 22, 193, 15, 43, 44, 45, 46, 47, + /* 670 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 680 */ 273, 143, 193, 118, 143, 102, 103, 104, 105, 106, + /* 690 */ 107, 108, 109, 110, 111, 112, 113, 76, 118, 59, + /* 700 */ 241, 116, 117, 118, 304, 216, 217, 292, 143, 60, + /* 710 */ 89, 241, 19, 92, 193, 193, 23, 22, 311, 312, + /* 720 */ 231, 101, 22, 143, 102, 103, 104, 105, 106, 107, + /* 730 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46, + /* 740 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 750 */ 57, 19, 193, 193, 59, 23, 116, 117, 118, 59, + /* 760 */ 201, 21, 241, 304, 22, 206, 127, 128, 129, 193, + /* 770 */ 128, 129, 235, 236, 304, 43, 44, 45, 46, 47, + /* 780 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 790 */ 22, 193, 216, 217, 193, 102, 103, 104, 105, 106, + /* 800 */ 107, 108, 109, 110, 111, 112, 113, 231, 193, 193, + /* 810 */ 193, 116, 117, 118, 216, 217, 116, 117, 118, 226, + /* 820 */ 80, 193, 19, 235, 236, 304, 23, 211, 212, 231, + /* 830 */ 204, 216, 217, 205, 102, 103, 104, 105, 106, 107, + /* 840 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46, + /* 850 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 860 */ 57, 19, 193, 123, 76, 239, 240, 193, 253, 239, + /* 870 */ 240, 239, 240, 193, 106, 107, 193, 89, 252, 193, + /* 880 */ 92, 59, 252, 141, 252, 43, 44, 45, 46, 47, + /* 890 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 900 */ 284, 161, 216, 217, 193, 102, 103, 104, 105, 106, + /* 910 */ 107, 108, 109, 110, 111, 112, 113, 231, 193, 16, + /* 920 */ 187, 188, 189, 190, 7, 8, 9, 309, 195, 25, + /* 930 */ 197, 313, 19, 127, 128, 129, 262, 204, 22, 117, + /* 940 */ 24, 216, 217, 263, 102, 103, 104, 105, 106, 107, + /* 950 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46, + /* 960 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 970 */ 57, 193, 239, 240, 193, 59, 19, 188, 253, 190, + /* 980 */ 77, 226, 79, 193, 195, 252, 197, 193, 19, 301, + /* 990 */ 302, 193, 193, 204, 216, 217, 226, 216, 217, 266, + /* 1000 */ 204, 159, 45, 46, 47, 48, 49, 50, 51, 52, + /* 1010 */ 53, 54, 55, 56, 57, 102, 103, 104, 105, 106, + /* 1020 */ 107, 108, 109, 110, 111, 112, 113, 12, 239, 240, + /* 1030 */ 232, 298, 238, 117, 253, 239, 240, 238, 259, 260, + /* 1040 */ 193, 252, 27, 31, 193, 193, 142, 204, 252, 193, + /* 1050 */ 193, 39, 262, 193, 100, 266, 278, 42, 204, 102, + /* 1060 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + /* 1070 */ 113, 117, 159, 216, 217, 121, 216, 217, 63, 193, + /* 1080 */ 193, 193, 239, 240, 115, 116, 193, 298, 73, 238, + /* 1090 */ 238, 231, 19, 239, 240, 252, 22, 24, 211, 212, + /* 1100 */ 24, 193, 216, 217, 216, 217, 252, 153, 154, 155, + /* 1110 */ 253, 16, 19, 144, 213, 268, 43, 44, 45, 46, + /* 1120 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 1130 */ 57, 238, 19, 59, 193, 59, 43, 44, 45, 46, + /* 1140 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 1150 */ 57, 22, 23, 193, 25, 193, 43, 44, 45, 46, + /* 1160 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 1170 */ 57, 284, 77, 193, 79, 102, 103, 104, 105, 106, + /* 1180 */ 107, 108, 109, 110, 111, 112, 113, 286, 193, 193, + /* 1190 */ 193, 117, 291, 117, 232, 102, 103, 104, 105, 106, + /* 1200 */ 107, 108, 109, 110, 111, 112, 113, 204, 22, 23, + /* 1210 */ 66, 25, 216, 217, 35, 102, 103, 104, 105, 106, + /* 1220 */ 107, 108, 109, 110, 111, 112, 113, 193, 268, 85, + /* 1230 */ 101, 193, 309, 309, 240, 19, 313, 313, 94, 208, + /* 1240 */ 209, 193, 239, 240, 193, 66, 252, 19, 268, 244, + /* 1250 */ 216, 217, 193, 74, 213, 252, 161, 19, 263, 254, + /* 1260 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + /* 1270 */ 54, 55, 56, 57, 193, 216, 217, 5, 59, 193, + /* 1280 */ 19, 244, 10, 11, 12, 13, 14, 101, 309, 17, + /* 1290 */ 146, 254, 313, 193, 193, 76, 115, 216, 217, 309, + /* 1300 */ 12, 263, 30, 313, 32, 46, 87, 46, 89, 130, + /* 1310 */ 193, 92, 40, 22, 263, 27, 216, 217, 102, 103, + /* 1320 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + /* 1330 */ 42, 150, 291, 216, 217, 116, 117, 118, 19, 20, + /* 1340 */ 193, 22, 70, 260, 116, 193, 24, 264, 193, 263, + /* 1350 */ 78, 63, 61, 81, 116, 36, 193, 260, 193, 29, + /* 1360 */ 193, 264, 193, 33, 145, 193, 59, 48, 216, 217, + /* 1370 */ 98, 216, 217, 193, 115, 193, 115, 193, 59, 216, + /* 1380 */ 217, 216, 217, 216, 217, 216, 217, 255, 216, 217, + /* 1390 */ 71, 193, 131, 193, 25, 65, 216, 217, 216, 217, + /* 1400 */ 216, 217, 208, 209, 85, 133, 193, 100, 193, 90, + /* 1410 */ 138, 139, 138, 139, 216, 217, 216, 217, 193, 100, + /* 1420 */ 193, 108, 135, 116, 117, 106, 107, 140, 121, 216, + /* 1430 */ 217, 216, 217, 114, 162, 116, 117, 118, 299, 300, + /* 1440 */ 121, 216, 217, 216, 217, 193, 244, 193, 135, 244, + /* 1450 */ 193, 256, 257, 140, 244, 193, 254, 193, 193, 254, + /* 1460 */ 153, 154, 155, 141, 254, 149, 150, 258, 216, 217, + /* 1470 */ 216, 217, 153, 154, 155, 156, 157, 0, 1, 2, + /* 1480 */ 216, 217, 5, 115, 158, 193, 160, 10, 11, 12, + /* 1490 */ 13, 14, 193, 59, 17, 126, 193, 19, 20, 129, + /* 1500 */ 22, 193, 22, 22, 24, 193, 23, 30, 25, 32, + /* 1510 */ 19, 20, 144, 22, 36, 216, 217, 40, 193, 216, + /* 1520 */ 217, 193, 152, 129, 216, 217, 193, 36, 216, 217, + /* 1530 */ 193, 99, 193, 193, 53, 193, 193, 59, 23, 193, + /* 1540 */ 25, 216, 217, 193, 216, 217, 152, 70, 59, 71, + /* 1550 */ 59, 117, 193, 216, 217, 78, 216, 217, 81, 216, + /* 1560 */ 217, 318, 71, 85, 193, 133, 193, 193, 90, 23, + /* 1570 */ 23, 25, 25, 120, 121, 98, 85, 193, 100, 193, + /* 1580 */ 23, 90, 25, 121, 106, 107, 19, 216, 217, 216, + /* 1590 */ 217, 100, 114, 131, 116, 117, 118, 106, 107, 121, + /* 1600 */ 216, 217, 216, 217, 193, 114, 117, 116, 117, 118, + /* 1610 */ 133, 193, 121, 193, 193, 138, 139, 193, 23, 193, + /* 1620 */ 25, 23, 23, 25, 25, 7, 8, 216, 217, 193, + /* 1630 */ 193, 153, 154, 155, 156, 157, 216, 217, 193, 162, + /* 1640 */ 216, 217, 216, 217, 153, 154, 155, 156, 157, 1, + /* 1650 */ 2, 193, 193, 5, 19, 20, 59, 22, 10, 11, + /* 1660 */ 12, 13, 14, 193, 97, 17, 193, 23, 193, 25, + /* 1670 */ 288, 36, 193, 242, 216, 217, 236, 23, 30, 25, + /* 1680 */ 32, 19, 20, 23, 22, 25, 216, 217, 40, 216, + /* 1690 */ 217, 216, 217, 193, 59, 216, 217, 193, 36, 83, + /* 1700 */ 84, 153, 153, 155, 155, 23, 71, 25, 23, 193, + /* 1710 */ 25, 193, 193, 193, 117, 193, 193, 193, 70, 193, + /* 1720 */ 193, 59, 193, 255, 255, 287, 78, 255, 243, 81, + /* 1730 */ 191, 255, 297, 71, 271, 100, 293, 245, 267, 214, + /* 1740 */ 246, 106, 107, 108, 246, 271, 98, 245, 293, 114, + /* 1750 */ 220, 116, 117, 118, 267, 271, 121, 271, 225, 219, + /* 1760 */ 229, 219, 100, 219, 259, 259, 259, 259, 106, 107, + /* 1770 */ 249, 196, 60, 280, 141, 243, 114, 249, 116, 117, + /* 1780 */ 118, 133, 245, 121, 200, 297, 138, 139, 153, 154, + /* 1790 */ 155, 156, 157, 297, 200, 38, 19, 20, 151, 22, + /* 1800 */ 200, 150, 140, 294, 294, 22, 272, 43, 234, 18, + /* 1810 */ 162, 270, 200, 36, 237, 153, 154, 155, 156, 157, + /* 1820 */ 237, 283, 237, 237, 18, 199, 149, 246, 272, 270, + /* 1830 */ 272, 200, 158, 246, 246, 234, 59, 234, 246, 199, + /* 1840 */ 290, 62, 289, 200, 199, 22, 221, 115, 71, 200, + /* 1850 */ 200, 199, 199, 221, 218, 218, 19, 20, 64, 22, + /* 1860 */ 218, 227, 22, 224, 126, 224, 165, 221, 24, 305, + /* 1870 */ 200, 113, 312, 36, 218, 220, 218, 100, 282, 218, + /* 1880 */ 91, 218, 317, 106, 107, 221, 227, 282, 317, 82, + /* 1890 */ 148, 114, 265, 116, 117, 118, 59, 145, 121, 22, + /* 1900 */ 277, 158, 200, 265, 25, 202, 147, 250, 71, 279, + /* 1910 */ 13, 146, 194, 194, 249, 248, 250, 140, 247, 246, + /* 1920 */ 6, 192, 192, 192, 303, 303, 213, 207, 300, 213, + /* 1930 */ 153, 154, 155, 156, 157, 213, 213, 100, 213, 222, + /* 1940 */ 207, 214, 214, 106, 107, 4, 222, 207, 3, 22, + /* 1950 */ 163, 114, 15, 116, 117, 118, 16, 23, 121, 23, + /* 1960 */ 139, 151, 130, 25, 142, 16, 24, 20, 144, 1, + /* 1970 */ 142, 130, 130, 61, 53, 53, 37, 151, 53, 53, + /* 1980 */ 130, 116, 34, 1, 141, 5, 22, 115, 161, 141, + /* 1990 */ 153, 154, 155, 156, 157, 25, 68, 68, 75, 41, + /* 2000 */ 115, 24, 131, 20, 19, 125, 22, 96, 22, 22, + /* 2010 */ 67, 23, 22, 67, 59, 24, 22, 28, 67, 23, + /* 2020 */ 22, 22, 149, 23, 23, 23, 116, 23, 25, 37, + /* 2030 */ 97, 141, 23, 23, 22, 143, 25, 75, 88, 34, + /* 2040 */ 34, 34, 34, 86, 75, 93, 23, 34, 22, 34, + /* 2050 */ 25, 24, 34, 25, 23, 142, 23, 142, 44, 23, + /* 2060 */ 23, 23, 11, 23, 25, 22, 22, 22, 15, 23, + /* 2070 */ 23, 22, 22, 25, 1, 1, 141, 25, 23, 135, + /* 2080 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2090 */ 319, 319, 319, 319, 141, 141, 319, 319, 319, 319, + /* 2100 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2110 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2120 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2130 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2140 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2150 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2160 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2170 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2180 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2190 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2200 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2210 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2220 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2230 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2240 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2250 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2260 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2270 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319, + /* 2280 */ 319, +}; +#define YY_SHIFT_COUNT (574) +#define YY_SHIFT_MIN (0) +#define YY_SHIFT_MAX (2074) +static const unsigned short int yy_shift_ofst[] = { + /* 0 */ 1648, 1477, 1272, 322, 322, 1, 1319, 1478, 1491, 1837, + /* 10 */ 1837, 1837, 471, 0, 0, 214, 1093, 1837, 1837, 1837, + /* 20 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 30 */ 271, 271, 1219, 1219, 216, 88, 1, 1, 1, 1, + /* 40 */ 1, 40, 111, 258, 361, 469, 512, 583, 622, 693, + /* 50 */ 732, 803, 842, 913, 1073, 1093, 1093, 1093, 1093, 1093, + /* 60 */ 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, + /* 70 */ 1093, 1093, 1093, 1113, 1093, 1216, 957, 957, 1635, 1662, + /* 80 */ 1777, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 90 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 100 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 110 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 120 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, + /* 130 */ 137, 181, 181, 181, 181, 181, 181, 181, 94, 430, + /* 140 */ 66, 65, 112, 366, 533, 533, 740, 1261, 533, 533, + /* 150 */ 79, 79, 533, 412, 412, 412, 77, 412, 123, 113, + /* 160 */ 113, 22, 22, 2096, 2096, 328, 328, 328, 239, 468, + /* 170 */ 468, 468, 468, 1015, 1015, 409, 366, 1129, 1186, 533, + /* 180 */ 533, 533, 533, 533, 533, 533, 533, 533, 533, 533, + /* 190 */ 533, 533, 533, 533, 533, 533, 533, 533, 533, 969, + /* 200 */ 621, 621, 533, 642, 788, 788, 1228, 1228, 822, 822, + /* 210 */ 67, 1274, 2096, 2096, 2096, 2096, 2096, 2096, 2096, 1307, + /* 220 */ 954, 954, 585, 472, 640, 387, 695, 538, 541, 700, + /* 230 */ 533, 533, 533, 533, 533, 533, 533, 533, 533, 533, + /* 240 */ 222, 533, 533, 533, 533, 533, 533, 533, 533, 533, + /* 250 */ 533, 533, 533, 1179, 1179, 1179, 533, 533, 533, 565, + /* 260 */ 533, 533, 533, 916, 1144, 533, 533, 1288, 533, 533, + /* 270 */ 533, 533, 533, 533, 533, 533, 639, 1330, 209, 1076, + /* 280 */ 1076, 1076, 1076, 580, 209, 209, 1313, 768, 917, 649, + /* 290 */ 1181, 1316, 405, 1316, 1238, 249, 1181, 1181, 249, 1181, + /* 300 */ 405, 1238, 1369, 464, 1259, 1012, 1012, 1012, 1368, 1368, + /* 310 */ 1368, 1368, 184, 184, 1326, 904, 1287, 1480, 1712, 1712, + /* 320 */ 1633, 1633, 1757, 1757, 1633, 1647, 1651, 1783, 1764, 1791, + /* 330 */ 1791, 1791, 1791, 1633, 1806, 1677, 1651, 1651, 1677, 1783, + /* 340 */ 1764, 1677, 1764, 1677, 1633, 1806, 1674, 1779, 1633, 1806, + /* 350 */ 1823, 1633, 1806, 1633, 1806, 1823, 1732, 1732, 1732, 1794, + /* 360 */ 1840, 1840, 1823, 1732, 1738, 1732, 1794, 1732, 1732, 1701, + /* 370 */ 1844, 1758, 1758, 1823, 1633, 1789, 1789, 1807, 1807, 1742, + /* 380 */ 1752, 1877, 1633, 1743, 1742, 1759, 1765, 1677, 1879, 1897, + /* 390 */ 1897, 1914, 1914, 1914, 2096, 2096, 2096, 2096, 2096, 2096, + /* 400 */ 2096, 2096, 2096, 2096, 2096, 2096, 2096, 2096, 2096, 207, + /* 410 */ 1095, 331, 620, 903, 806, 1074, 1483, 1432, 1481, 1322, + /* 420 */ 1370, 1394, 1515, 1291, 1546, 1547, 1557, 1595, 1598, 1599, + /* 430 */ 1434, 1453, 1618, 1462, 1567, 1489, 1644, 1654, 1616, 1660, + /* 440 */ 1548, 1549, 1682, 1685, 1597, 742, 1941, 1945, 1927, 1787, + /* 450 */ 1937, 1940, 1934, 1936, 1821, 1810, 1832, 1938, 1938, 1942, + /* 460 */ 1822, 1947, 1824, 1949, 1968, 1828, 1841, 1938, 1842, 1912, + /* 470 */ 1939, 1938, 1826, 1921, 1922, 1925, 1926, 1850, 1865, 1948, + /* 480 */ 1843, 1982, 1980, 1964, 1872, 1827, 1928, 1970, 1929, 1923, + /* 490 */ 1958, 1848, 1885, 1977, 1983, 1985, 1871, 1880, 1984, 1943, + /* 500 */ 1986, 1987, 1988, 1990, 1946, 1955, 1991, 1911, 1989, 1994, + /* 510 */ 1951, 1992, 1996, 1873, 1998, 2000, 2001, 2002, 2003, 2004, + /* 520 */ 1999, 1933, 1890, 2009, 2010, 1910, 2005, 2012, 1892, 2011, + /* 530 */ 2006, 2007, 2008, 2013, 1950, 1962, 1957, 2014, 1969, 1952, + /* 540 */ 2015, 2023, 2026, 2027, 2025, 2028, 2018, 1913, 1915, 2031, + /* 550 */ 2011, 2033, 2036, 2037, 2038, 2039, 2040, 2043, 2051, 2044, + /* 560 */ 2045, 2046, 2047, 2049, 2050, 2048, 1944, 1935, 1953, 1954, + /* 570 */ 2052, 2055, 2053, 2073, 2074, +}; +#define YY_REDUCE_COUNT (408) +#define YY_REDUCE_MIN (-271) +#define YY_REDUCE_MAX (1740) +static const short yy_reduce_ofst[] = { + /* 0 */ -125, 733, 789, 241, 293, -123, -193, -191, -183, -187, + /* 10 */ 166, 238, 133, -207, -199, -267, -176, -6, 204, 489, + /* 20 */ 576, -175, 598, 686, 615, 725, 860, 778, 781, 857, + /* 30 */ 616, 887, 87, 240, -192, 408, 626, 796, 843, 854, + /* 40 */ 1003, -271, -271, -271, -271, -271, -271, -271, -271, -271, + /* 50 */ -271, -271, -271, -271, -271, -271, -271, -271, -271, -271, + /* 60 */ -271, -271, -271, -271, -271, -271, -271, -271, -271, -271, + /* 70 */ -271, -271, -271, -271, -271, -271, -271, -271, 80, 83, + /* 80 */ 313, 886, 888, 996, 1034, 1059, 1081, 1100, 1117, 1152, + /* 90 */ 1155, 1163, 1165, 1167, 1169, 1172, 1180, 1182, 1184, 1198, + /* 100 */ 1200, 1213, 1215, 1225, 1227, 1252, 1254, 1264, 1299, 1303, + /* 110 */ 1308, 1312, 1325, 1328, 1337, 1340, 1343, 1371, 1373, 1384, + /* 120 */ 1386, 1411, 1420, 1424, 1426, 1458, 1470, 1473, 1475, 1479, + /* 130 */ -271, -271, -271, -271, -271, -271, -271, -271, -271, -271, + /* 140 */ -271, 138, 459, 396, -158, 470, 302, -212, 521, 201, + /* 150 */ -195, -92, 559, 630, 632, 630, -271, 632, 901, 63, + /* 160 */ 407, -271, -271, -271, -271, 161, 161, 161, 251, 335, + /* 170 */ 847, 960, 980, 537, 588, 618, 628, 688, 688, -166, + /* 180 */ -161, 674, 790, 794, 799, 851, 852, -122, 680, -120, + /* 190 */ 995, 1038, 415, 1051, 893, 798, 962, 400, 1086, 779, + /* 200 */ 923, 924, 263, 1041, 979, 990, 1083, 1097, 1031, 1194, + /* 210 */ 362, 994, 1139, 1005, 1037, 1202, 1205, 1195, 1210, -194, + /* 220 */ 56, 185, -135, 232, 522, 560, 601, 617, 669, 683, + /* 230 */ 711, 856, 908, 941, 1048, 1101, 1147, 1257, 1262, 1265, + /* 240 */ 392, 1292, 1333, 1339, 1342, 1346, 1350, 1359, 1374, 1418, + /* 250 */ 1421, 1436, 1437, 593, 755, 770, 997, 1445, 1459, 1209, + /* 260 */ 1500, 1504, 1516, 1132, 1243, 1518, 1519, 1440, 1520, 560, + /* 270 */ 1522, 1523, 1524, 1526, 1527, 1529, 1382, 1438, 1431, 1468, + /* 280 */ 1469, 1472, 1476, 1209, 1431, 1431, 1485, 1525, 1539, 1435, + /* 290 */ 1463, 1471, 1492, 1487, 1443, 1494, 1474, 1484, 1498, 1486, + /* 300 */ 1502, 1455, 1530, 1531, 1533, 1540, 1542, 1544, 1505, 1506, + /* 310 */ 1507, 1508, 1521, 1528, 1493, 1537, 1532, 1575, 1488, 1496, + /* 320 */ 1584, 1594, 1509, 1510, 1600, 1538, 1534, 1541, 1574, 1577, + /* 330 */ 1583, 1585, 1586, 1612, 1626, 1581, 1556, 1558, 1587, 1559, + /* 340 */ 1601, 1588, 1603, 1592, 1631, 1640, 1550, 1553, 1643, 1645, + /* 350 */ 1625, 1649, 1652, 1650, 1653, 1632, 1636, 1637, 1642, 1634, + /* 360 */ 1639, 1641, 1646, 1656, 1655, 1658, 1659, 1661, 1663, 1560, + /* 370 */ 1564, 1596, 1605, 1664, 1670, 1565, 1571, 1627, 1638, 1657, + /* 380 */ 1665, 1623, 1702, 1630, 1666, 1667, 1671, 1673, 1703, 1718, + /* 390 */ 1719, 1729, 1730, 1731, 1621, 1622, 1628, 1720, 1713, 1716, + /* 400 */ 1722, 1723, 1733, 1717, 1724, 1727, 1728, 1725, 1740, +}; +static const YYACTIONTYPE yy_default[] = { + /* 0 */ 1639, 1639, 1639, 1469, 1236, 1347, 1236, 1236, 1236, 1469, + /* 10 */ 1469, 1469, 1236, 1377, 1377, 1522, 1269, 1236, 1236, 1236, + /* 20 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1468, 1236, 1236, + /* 30 */ 1236, 1236, 1555, 1555, 1236, 1236, 1236, 1236, 1236, 1236, + /* 40 */ 1236, 1236, 1386, 1236, 1393, 1236, 1236, 1236, 1236, 1236, + /* 50 */ 1470, 1471, 1236, 1236, 1236, 1521, 1523, 1486, 1400, 1399, + /* 60 */ 1398, 1397, 1504, 1365, 1391, 1384, 1388, 1465, 1466, 1464, + /* 70 */ 1617, 1471, 1470, 1236, 1387, 1433, 1449, 1432, 1236, 1236, + /* 80 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 90 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 100 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 110 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 120 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 130 */ 1441, 1448, 1447, 1446, 1455, 1445, 1442, 1435, 1434, 1436, + /* 140 */ 1437, 1236, 1236, 1260, 1236, 1236, 1257, 1311, 1236, 1236, + /* 150 */ 1236, 1236, 1236, 1541, 1540, 1236, 1438, 1236, 1269, 1427, + /* 160 */ 1426, 1452, 1439, 1451, 1450, 1529, 1591, 1590, 1487, 1236, + /* 170 */ 1236, 1236, 1236, 1236, 1236, 1555, 1236, 1236, 1236, 1236, + /* 180 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 190 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1367, + /* 200 */ 1555, 1555, 1236, 1269, 1555, 1555, 1368, 1368, 1265, 1265, + /* 210 */ 1371, 1236, 1536, 1338, 1338, 1338, 1338, 1347, 1338, 1236, + /* 220 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 230 */ 1236, 1236, 1236, 1236, 1526, 1524, 1236, 1236, 1236, 1236, + /* 240 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 250 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 260 */ 1236, 1236, 1236, 1343, 1236, 1236, 1236, 1236, 1236, 1236, + /* 270 */ 1236, 1236, 1236, 1236, 1236, 1584, 1236, 1499, 1325, 1343, + /* 280 */ 1343, 1343, 1343, 1345, 1326, 1324, 1337, 1270, 1243, 1631, + /* 290 */ 1403, 1392, 1344, 1392, 1628, 1390, 1403, 1403, 1390, 1403, + /* 300 */ 1344, 1628, 1286, 1606, 1281, 1377, 1377, 1377, 1367, 1367, + /* 310 */ 1367, 1367, 1371, 1371, 1467, 1344, 1337, 1236, 1631, 1631, + /* 320 */ 1353, 1353, 1630, 1630, 1353, 1487, 1614, 1412, 1314, 1320, + /* 330 */ 1320, 1320, 1320, 1353, 1254, 1390, 1614, 1614, 1390, 1412, + /* 340 */ 1314, 1390, 1314, 1390, 1353, 1254, 1503, 1625, 1353, 1254, + /* 350 */ 1477, 1353, 1254, 1353, 1254, 1477, 1312, 1312, 1312, 1301, + /* 360 */ 1236, 1236, 1477, 1312, 1286, 1312, 1301, 1312, 1312, 1573, + /* 370 */ 1236, 1481, 1481, 1477, 1353, 1565, 1565, 1380, 1380, 1385, + /* 380 */ 1371, 1472, 1353, 1236, 1385, 1383, 1381, 1390, 1304, 1587, + /* 390 */ 1587, 1583, 1583, 1583, 1636, 1636, 1536, 1599, 1269, 1269, + /* 400 */ 1269, 1269, 1599, 1288, 1288, 1270, 1270, 1269, 1599, 1236, + /* 410 */ 1236, 1236, 1236, 1236, 1236, 1594, 1236, 1531, 1488, 1357, + /* 420 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 430 */ 1236, 1236, 1236, 1236, 1542, 1236, 1236, 1236, 1236, 1236, + /* 440 */ 1236, 1236, 1236, 1236, 1236, 1417, 1236, 1239, 1533, 1236, + /* 450 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1394, 1395, 1358, + /* 460 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1409, 1236, 1236, + /* 470 */ 1236, 1404, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 480 */ 1627, 1236, 1236, 1236, 1236, 1236, 1236, 1502, 1501, 1236, + /* 490 */ 1236, 1355, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 500 */ 1236, 1236, 1236, 1236, 1236, 1284, 1236, 1236, 1236, 1236, + /* 510 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 520 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1382, + /* 530 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 540 */ 1236, 1236, 1236, 1236, 1570, 1372, 1236, 1236, 1236, 1236, + /* 550 */ 1618, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 560 */ 1236, 1236, 1236, 1236, 1236, 1610, 1328, 1418, 1236, 1421, + /* 570 */ 1258, 1236, 1248, 1236, 1236, +}; +/********** End of lemon-generated parsing tables *****************************/ + +/* The next table maps tokens (terminal symbols) into fallback tokens. +** If a construct like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +** +** This feature can be used, for example, to cause some keywords in a language +** to revert to identifiers if they keyword does not apply in the context where +** it appears. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { + 0, /* $ => nothing */ + 0, /* SEMI => nothing */ + 59, /* EXPLAIN => ID */ + 59, /* QUERY => ID */ + 59, /* PLAN => ID */ + 59, /* BEGIN => ID */ + 0, /* TRANSACTION => nothing */ + 59, /* DEFERRED => ID */ + 59, /* IMMEDIATE => ID */ + 59, /* EXCLUSIVE => ID */ + 0, /* COMMIT => nothing */ + 59, /* END => ID */ + 59, /* ROLLBACK => ID */ + 59, /* SAVEPOINT => ID */ + 59, /* RELEASE => ID */ + 0, /* TO => nothing */ + 0, /* TABLE => nothing */ + 0, /* CREATE => nothing */ + 59, /* IF => ID */ + 0, /* NOT => nothing */ + 0, /* EXISTS => nothing */ + 59, /* TEMP => ID */ + 0, /* LP => nothing */ + 0, /* RP => nothing */ + 0, /* AS => nothing */ + 0, /* COMMA => nothing */ + 59, /* WITHOUT => ID */ + 59, /* ABORT => ID */ + 59, /* ACTION => ID */ + 59, /* AFTER => ID */ + 59, /* ANALYZE => ID */ + 59, /* ASC => ID */ + 59, /* ATTACH => ID */ + 59, /* BEFORE => ID */ + 59, /* BY => ID */ + 59, /* CASCADE => ID */ + 59, /* CAST => ID */ + 59, /* CONFLICT => ID */ + 59, /* DATABASE => ID */ + 59, /* DESC => ID */ + 59, /* DETACH => ID */ + 59, /* EACH => ID */ + 59, /* FAIL => ID */ + 0, /* OR => nothing */ + 0, /* AND => nothing */ + 0, /* IS => nothing */ + 59, /* MATCH => ID */ + 59, /* LIKE_KW => ID */ + 0, /* BETWEEN => nothing */ + 0, /* IN => nothing */ + 0, /* ISNULL => nothing */ + 0, /* NOTNULL => nothing */ + 0, /* NE => nothing */ + 0, /* EQ => nothing */ + 0, /* GT => nothing */ + 0, /* LE => nothing */ + 0, /* LT => nothing */ + 0, /* GE => nothing */ + 0, /* ESCAPE => nothing */ + 0, /* ID => nothing */ + 59, /* COLUMNKW => ID */ + 59, /* DO => ID */ + 59, /* FOR => ID */ + 59, /* IGNORE => ID */ + 59, /* INITIALLY => ID */ + 59, /* INSTEAD => ID */ + 59, /* NO => ID */ + 59, /* KEY => ID */ + 59, /* OF => ID */ + 59, /* OFFSET => ID */ + 59, /* PRAGMA => ID */ + 59, /* RAISE => ID */ + 59, /* RECURSIVE => ID */ + 59, /* REPLACE => ID */ + 59, /* RESTRICT => ID */ + 59, /* ROW => ID */ + 59, /* ROWS => ID */ + 59, /* TRIGGER => ID */ + 59, /* VACUUM => ID */ + 59, /* VIEW => ID */ + 59, /* VIRTUAL => ID */ + 59, /* WITH => ID */ + 59, /* NULLS => ID */ + 59, /* FIRST => ID */ + 59, /* LAST => ID */ + 59, /* CURRENT => ID */ + 59, /* FOLLOWING => ID */ + 59, /* PARTITION => ID */ + 59, /* PRECEDING => ID */ + 59, /* RANGE => ID */ + 59, /* UNBOUNDED => ID */ + 59, /* EXCLUDE => ID */ + 59, /* GROUPS => ID */ + 59, /* OTHERS => ID */ + 59, /* TIES => ID */ + 59, /* GENERATED => ID */ + 59, /* ALWAYS => ID */ + 59, /* MATERIALIZED => ID */ + 59, /* REINDEX => ID */ + 59, /* RENAME => ID */ + 59, /* CTIME_KW => ID */ + 0, /* ANY => nothing */ + 0, /* BITAND => nothing */ + 0, /* BITOR => nothing */ + 0, /* LSHIFT => nothing */ + 0, /* RSHIFT => nothing */ + 0, /* PLUS => nothing */ + 0, /* MINUS => nothing */ + 0, /* STAR => nothing */ + 0, /* SLASH => nothing */ + 0, /* REM => nothing */ + 0, /* CONCAT => nothing */ + 0, /* PTR => nothing */ + 0, /* COLLATE => nothing */ + 0, /* BITNOT => nothing */ + 0, /* ON => nothing */ + 0, /* INDEXED => nothing */ + 0, /* STRING => nothing */ + 0, /* JOIN_KW => nothing */ + 0, /* CONSTRAINT => nothing */ + 0, /* DEFAULT => nothing */ + 0, /* NULL => nothing */ + 0, /* PRIMARY => nothing */ + 0, /* UNIQUE => nothing */ + 0, /* CHECK => nothing */ + 0, /* REFERENCES => nothing */ + 0, /* AUTOINCR => nothing */ + 0, /* INSERT => nothing */ + 0, /* DELETE => nothing */ + 0, /* UPDATE => nothing */ + 0, /* SET => nothing */ + 0, /* DEFERRABLE => nothing */ + 0, /* FOREIGN => nothing */ + 0, /* DROP => nothing */ + 0, /* UNION => nothing */ + 0, /* ALL => nothing */ + 0, /* EXCEPT => nothing */ + 0, /* INTERSECT => nothing */ + 0, /* SELECT => nothing */ + 0, /* VALUES => nothing */ + 0, /* DISTINCT => nothing */ + 0, /* DOT => nothing */ + 0, /* FROM => nothing */ + 0, /* JOIN => nothing */ + 0, /* USING => nothing */ + 0, /* ORDER => nothing */ + 0, /* GROUP => nothing */ + 0, /* HAVING => nothing */ + 0, /* LIMIT => nothing */ + 0, /* WHERE => nothing */ + 0, /* RETURNING => nothing */ + 0, /* INTO => nothing */ + 0, /* NOTHING => nothing */ + 0, /* FLOAT => nothing */ + 0, /* BLOB => nothing */ + 0, /* INTEGER => nothing */ + 0, /* VARIABLE => nothing */ + 0, /* CASE => nothing */ + 0, /* WHEN => nothing */ + 0, /* THEN => nothing */ + 0, /* ELSE => nothing */ + 0, /* INDEX => nothing */ + 0, /* ALTER => nothing */ + 0, /* ADD => nothing */ + 0, /* WINDOW => nothing */ + 0, /* OVER => nothing */ + 0, /* FILTER => nothing */ + 0, /* COLUMN => nothing */ + 0, /* AGG_FUNCTION => nothing */ + 0, /* AGG_COLUMN => nothing */ + 0, /* TRUEFALSE => nothing */ + 0, /* ISNOT => nothing */ + 0, /* FUNCTION => nothing */ + 0, /* UMINUS => nothing */ + 0, /* UPLUS => nothing */ + 0, /* TRUTH => nothing */ + 0, /* REGISTER => nothing */ + 0, /* VECTOR => nothing */ + 0, /* SELECT_COLUMN => nothing */ + 0, /* IF_NULL_ROW => nothing */ + 0, /* ASTERISK => nothing */ + 0, /* SPAN => nothing */ + 0, /* ERROR => nothing */ + 0, /* SPACE => nothing */ + 0, /* ILLEGAL => nothing */ +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +** +** After the "shift" half of a SHIFTREDUCE action, the stateno field +** actually contains the reduce action for the second half of the +** SHIFTREDUCE. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + yyStackEntry *yytos; /* Pointer to top element of the stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyhwm; /* High-water mark of the stack */ +#endif +#ifndef YYNOERRORRECOVERY + int yyerrcnt; /* Shifts left before out of the error */ +#endif + sqlite3ParserARG_SDECL /* A place to hold %extra_argument */ + sqlite3ParserCTX_SDECL /* A place to hold %extra_context */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ + yyStackEntry yystk0; /* First stack entry */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ + yyStackEntry *yystackEnd; /* Last entry in the stack */ +#endif +}; +typedef struct yyParser yyParser; + +/* #include <assert.h> */ +#ifndef NDEBUG +/* #include <stdio.h> */ +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +** <ul> +** <li> A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +** <li> A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +** </ul> +** +** Outputs: +** None. +*/ +SQLITE_PRIVATE void sqlite3ParserTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#if defined(YYCOVERAGE) || !defined(NDEBUG) +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { + /* 0 */ "$", + /* 1 */ "SEMI", + /* 2 */ "EXPLAIN", + /* 3 */ "QUERY", + /* 4 */ "PLAN", + /* 5 */ "BEGIN", + /* 6 */ "TRANSACTION", + /* 7 */ "DEFERRED", + /* 8 */ "IMMEDIATE", + /* 9 */ "EXCLUSIVE", + /* 10 */ "COMMIT", + /* 11 */ "END", + /* 12 */ "ROLLBACK", + /* 13 */ "SAVEPOINT", + /* 14 */ "RELEASE", + /* 15 */ "TO", + /* 16 */ "TABLE", + /* 17 */ "CREATE", + /* 18 */ "IF", + /* 19 */ "NOT", + /* 20 */ "EXISTS", + /* 21 */ "TEMP", + /* 22 */ "LP", + /* 23 */ "RP", + /* 24 */ "AS", + /* 25 */ "COMMA", + /* 26 */ "WITHOUT", + /* 27 */ "ABORT", + /* 28 */ "ACTION", + /* 29 */ "AFTER", + /* 30 */ "ANALYZE", + /* 31 */ "ASC", + /* 32 */ "ATTACH", + /* 33 */ "BEFORE", + /* 34 */ "BY", + /* 35 */ "CASCADE", + /* 36 */ "CAST", + /* 37 */ "CONFLICT", + /* 38 */ "DATABASE", + /* 39 */ "DESC", + /* 40 */ "DETACH", + /* 41 */ "EACH", + /* 42 */ "FAIL", + /* 43 */ "OR", + /* 44 */ "AND", + /* 45 */ "IS", + /* 46 */ "MATCH", + /* 47 */ "LIKE_KW", + /* 48 */ "BETWEEN", + /* 49 */ "IN", + /* 50 */ "ISNULL", + /* 51 */ "NOTNULL", + /* 52 */ "NE", + /* 53 */ "EQ", + /* 54 */ "GT", + /* 55 */ "LE", + /* 56 */ "LT", + /* 57 */ "GE", + /* 58 */ "ESCAPE", + /* 59 */ "ID", + /* 60 */ "COLUMNKW", + /* 61 */ "DO", + /* 62 */ "FOR", + /* 63 */ "IGNORE", + /* 64 */ "INITIALLY", + /* 65 */ "INSTEAD", + /* 66 */ "NO", + /* 67 */ "KEY", + /* 68 */ "OF", + /* 69 */ "OFFSET", + /* 70 */ "PRAGMA", + /* 71 */ "RAISE", + /* 72 */ "RECURSIVE", + /* 73 */ "REPLACE", + /* 74 */ "RESTRICT", + /* 75 */ "ROW", + /* 76 */ "ROWS", + /* 77 */ "TRIGGER", + /* 78 */ "VACUUM", + /* 79 */ "VIEW", + /* 80 */ "VIRTUAL", + /* 81 */ "WITH", + /* 82 */ "NULLS", + /* 83 */ "FIRST", + /* 84 */ "LAST", + /* 85 */ "CURRENT", + /* 86 */ "FOLLOWING", + /* 87 */ "PARTITION", + /* 88 */ "PRECEDING", + /* 89 */ "RANGE", + /* 90 */ "UNBOUNDED", + /* 91 */ "EXCLUDE", + /* 92 */ "GROUPS", + /* 93 */ "OTHERS", + /* 94 */ "TIES", + /* 95 */ "GENERATED", + /* 96 */ "ALWAYS", + /* 97 */ "MATERIALIZED", + /* 98 */ "REINDEX", + /* 99 */ "RENAME", + /* 100 */ "CTIME_KW", + /* 101 */ "ANY", + /* 102 */ "BITAND", + /* 103 */ "BITOR", + /* 104 */ "LSHIFT", + /* 105 */ "RSHIFT", + /* 106 */ "PLUS", + /* 107 */ "MINUS", + /* 108 */ "STAR", + /* 109 */ "SLASH", + /* 110 */ "REM", + /* 111 */ "CONCAT", + /* 112 */ "PTR", + /* 113 */ "COLLATE", + /* 114 */ "BITNOT", + /* 115 */ "ON", + /* 116 */ "INDEXED", + /* 117 */ "STRING", + /* 118 */ "JOIN_KW", + /* 119 */ "CONSTRAINT", + /* 120 */ "DEFAULT", + /* 121 */ "NULL", + /* 122 */ "PRIMARY", + /* 123 */ "UNIQUE", + /* 124 */ "CHECK", + /* 125 */ "REFERENCES", + /* 126 */ "AUTOINCR", + /* 127 */ "INSERT", + /* 128 */ "DELETE", + /* 129 */ "UPDATE", + /* 130 */ "SET", + /* 131 */ "DEFERRABLE", + /* 132 */ "FOREIGN", + /* 133 */ "DROP", + /* 134 */ "UNION", + /* 135 */ "ALL", + /* 136 */ "EXCEPT", + /* 137 */ "INTERSECT", + /* 138 */ "SELECT", + /* 139 */ "VALUES", + /* 140 */ "DISTINCT", + /* 141 */ "DOT", + /* 142 */ "FROM", + /* 143 */ "JOIN", + /* 144 */ "USING", + /* 145 */ "ORDER", + /* 146 */ "GROUP", + /* 147 */ "HAVING", + /* 148 */ "LIMIT", + /* 149 */ "WHERE", + /* 150 */ "RETURNING", + /* 151 */ "INTO", + /* 152 */ "NOTHING", + /* 153 */ "FLOAT", + /* 154 */ "BLOB", + /* 155 */ "INTEGER", + /* 156 */ "VARIABLE", + /* 157 */ "CASE", + /* 158 */ "WHEN", + /* 159 */ "THEN", + /* 160 */ "ELSE", + /* 161 */ "INDEX", + /* 162 */ "ALTER", + /* 163 */ "ADD", + /* 164 */ "WINDOW", + /* 165 */ "OVER", + /* 166 */ "FILTER", + /* 167 */ "COLUMN", + /* 168 */ "AGG_FUNCTION", + /* 169 */ "AGG_COLUMN", + /* 170 */ "TRUEFALSE", + /* 171 */ "ISNOT", + /* 172 */ "FUNCTION", + /* 173 */ "UMINUS", + /* 174 */ "UPLUS", + /* 175 */ "TRUTH", + /* 176 */ "REGISTER", + /* 177 */ "VECTOR", + /* 178 */ "SELECT_COLUMN", + /* 179 */ "IF_NULL_ROW", + /* 180 */ "ASTERISK", + /* 181 */ "SPAN", + /* 182 */ "ERROR", + /* 183 */ "SPACE", + /* 184 */ "ILLEGAL", + /* 185 */ "input", + /* 186 */ "cmdlist", + /* 187 */ "ecmd", + /* 188 */ "cmdx", + /* 189 */ "explain", + /* 190 */ "cmd", + /* 191 */ "transtype", + /* 192 */ "trans_opt", + /* 193 */ "nm", + /* 194 */ "savepoint_opt", + /* 195 */ "create_table", + /* 196 */ "create_table_args", + /* 197 */ "createkw", + /* 198 */ "temp", + /* 199 */ "ifnotexists", + /* 200 */ "dbnm", + /* 201 */ "columnlist", + /* 202 */ "conslist_opt", + /* 203 */ "table_option_set", + /* 204 */ "select", + /* 205 */ "table_option", + /* 206 */ "columnname", + /* 207 */ "carglist", + /* 208 */ "typetoken", + /* 209 */ "typename", + /* 210 */ "signed", + /* 211 */ "plus_num", + /* 212 */ "minus_num", + /* 213 */ "scanpt", + /* 214 */ "scantok", + /* 215 */ "ccons", + /* 216 */ "term", + /* 217 */ "expr", + /* 218 */ "onconf", + /* 219 */ "sortorder", + /* 220 */ "autoinc", + /* 221 */ "eidlist_opt", + /* 222 */ "refargs", + /* 223 */ "defer_subclause", + /* 224 */ "generated", + /* 225 */ "refarg", + /* 226 */ "refact", + /* 227 */ "init_deferred_pred_opt", + /* 228 */ "conslist", + /* 229 */ "tconscomma", + /* 230 */ "tcons", + /* 231 */ "sortlist", + /* 232 */ "eidlist", + /* 233 */ "defer_subclause_opt", + /* 234 */ "orconf", + /* 235 */ "resolvetype", + /* 236 */ "raisetype", + /* 237 */ "ifexists", + /* 238 */ "fullname", + /* 239 */ "selectnowith", + /* 240 */ "oneselect", + /* 241 */ "wqlist", + /* 242 */ "multiselect_op", + /* 243 */ "distinct", + /* 244 */ "selcollist", + /* 245 */ "from", + /* 246 */ "where_opt", + /* 247 */ "groupby_opt", + /* 248 */ "having_opt", + /* 249 */ "orderby_opt", + /* 250 */ "limit_opt", + /* 251 */ "window_clause", + /* 252 */ "values", + /* 253 */ "nexprlist", + /* 254 */ "sclp", + /* 255 */ "as", + /* 256 */ "seltablist", + /* 257 */ "stl_prefix", + /* 258 */ "joinop", + /* 259 */ "on_using", + /* 260 */ "indexed_by", + /* 261 */ "exprlist", + /* 262 */ "xfullname", + /* 263 */ "idlist", + /* 264 */ "indexed_opt", + /* 265 */ "nulls", + /* 266 */ "with", + /* 267 */ "where_opt_ret", + /* 268 */ "setlist", + /* 269 */ "insert_cmd", + /* 270 */ "idlist_opt", + /* 271 */ "upsert", + /* 272 */ "returning", + /* 273 */ "filter_over", + /* 274 */ "likeop", + /* 275 */ "between_op", + /* 276 */ "in_op", + /* 277 */ "paren_exprlist", + /* 278 */ "case_operand", + /* 279 */ "case_exprlist", + /* 280 */ "case_else", + /* 281 */ "uniqueflag", + /* 282 */ "collate", + /* 283 */ "vinto", + /* 284 */ "nmnum", + /* 285 */ "trigger_decl", + /* 286 */ "trigger_cmd_list", + /* 287 */ "trigger_time", + /* 288 */ "trigger_event", + /* 289 */ "foreach_clause", + /* 290 */ "when_clause", + /* 291 */ "trigger_cmd", + /* 292 */ "trnm", + /* 293 */ "tridxby", + /* 294 */ "database_kw_opt", + /* 295 */ "key_opt", + /* 296 */ "add_column_fullname", + /* 297 */ "kwcolumn_opt", + /* 298 */ "create_vtab", + /* 299 */ "vtabarglist", + /* 300 */ "vtabarg", + /* 301 */ "vtabargtoken", + /* 302 */ "lp", + /* 303 */ "anylist", + /* 304 */ "wqitem", + /* 305 */ "wqas", + /* 306 */ "windowdefn_list", + /* 307 */ "windowdefn", + /* 308 */ "window", + /* 309 */ "frame_opt", + /* 310 */ "part_opt", + /* 311 */ "filter_clause", + /* 312 */ "over_clause", + /* 313 */ "range_or_rows", + /* 314 */ "frame_bound", + /* 315 */ "frame_bound_s", + /* 316 */ "frame_bound_e", + /* 317 */ "frame_exclude_opt", + /* 318 */ "frame_exclude", +}; +#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { + /* 0 */ "explain ::= EXPLAIN", + /* 1 */ "explain ::= EXPLAIN QUERY PLAN", + /* 2 */ "cmdx ::= cmd", + /* 3 */ "cmd ::= BEGIN transtype trans_opt", + /* 4 */ "transtype ::=", + /* 5 */ "transtype ::= DEFERRED", + /* 6 */ "transtype ::= IMMEDIATE", + /* 7 */ "transtype ::= EXCLUSIVE", + /* 8 */ "cmd ::= COMMIT|END trans_opt", + /* 9 */ "cmd ::= ROLLBACK trans_opt", + /* 10 */ "cmd ::= SAVEPOINT nm", + /* 11 */ "cmd ::= RELEASE savepoint_opt nm", + /* 12 */ "cmd ::= ROLLBACK trans_opt TO savepoint_opt nm", + /* 13 */ "create_table ::= createkw temp TABLE ifnotexists nm dbnm", + /* 14 */ "createkw ::= CREATE", + /* 15 */ "ifnotexists ::=", + /* 16 */ "ifnotexists ::= IF NOT EXISTS", + /* 17 */ "temp ::= TEMP", + /* 18 */ "temp ::=", + /* 19 */ "create_table_args ::= LP columnlist conslist_opt RP table_option_set", + /* 20 */ "create_table_args ::= AS select", + /* 21 */ "table_option_set ::=", + /* 22 */ "table_option_set ::= table_option_set COMMA table_option", + /* 23 */ "table_option ::= WITHOUT nm", + /* 24 */ "table_option ::= nm", + /* 25 */ "columnname ::= nm typetoken", + /* 26 */ "typetoken ::=", + /* 27 */ "typetoken ::= typename LP signed RP", + /* 28 */ "typetoken ::= typename LP signed COMMA signed RP", + /* 29 */ "typename ::= typename ID|STRING", + /* 30 */ "scanpt ::=", + /* 31 */ "scantok ::=", + /* 32 */ "ccons ::= CONSTRAINT nm", + /* 33 */ "ccons ::= DEFAULT scantok term", + /* 34 */ "ccons ::= DEFAULT LP expr RP", + /* 35 */ "ccons ::= DEFAULT PLUS scantok term", + /* 36 */ "ccons ::= DEFAULT MINUS scantok term", + /* 37 */ "ccons ::= DEFAULT scantok ID|INDEXED", + /* 38 */ "ccons ::= NOT NULL onconf", + /* 39 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", + /* 40 */ "ccons ::= UNIQUE onconf", + /* 41 */ "ccons ::= CHECK LP expr RP", + /* 42 */ "ccons ::= REFERENCES nm eidlist_opt refargs", + /* 43 */ "ccons ::= defer_subclause", + /* 44 */ "ccons ::= COLLATE ID|STRING", + /* 45 */ "generated ::= LP expr RP", + /* 46 */ "generated ::= LP expr RP ID", + /* 47 */ "autoinc ::=", + /* 48 */ "autoinc ::= AUTOINCR", + /* 49 */ "refargs ::=", + /* 50 */ "refargs ::= refargs refarg", + /* 51 */ "refarg ::= MATCH nm", + /* 52 */ "refarg ::= ON INSERT refact", + /* 53 */ "refarg ::= ON DELETE refact", + /* 54 */ "refarg ::= ON UPDATE refact", + /* 55 */ "refact ::= SET NULL", + /* 56 */ "refact ::= SET DEFAULT", + /* 57 */ "refact ::= CASCADE", + /* 58 */ "refact ::= RESTRICT", + /* 59 */ "refact ::= NO ACTION", + /* 60 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 61 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 62 */ "init_deferred_pred_opt ::=", + /* 63 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 64 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 65 */ "conslist_opt ::=", + /* 66 */ "tconscomma ::= COMMA", + /* 67 */ "tcons ::= CONSTRAINT nm", + /* 68 */ "tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf", + /* 69 */ "tcons ::= UNIQUE LP sortlist RP onconf", + /* 70 */ "tcons ::= CHECK LP expr RP onconf", + /* 71 */ "tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt", + /* 72 */ "defer_subclause_opt ::=", + /* 73 */ "onconf ::=", + /* 74 */ "onconf ::= ON CONFLICT resolvetype", + /* 75 */ "orconf ::=", + /* 76 */ "orconf ::= OR resolvetype", + /* 77 */ "resolvetype ::= IGNORE", + /* 78 */ "resolvetype ::= REPLACE", + /* 79 */ "cmd ::= DROP TABLE ifexists fullname", + /* 80 */ "ifexists ::= IF EXISTS", + /* 81 */ "ifexists ::=", + /* 82 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select", + /* 83 */ "cmd ::= DROP VIEW ifexists fullname", + /* 84 */ "cmd ::= select", + /* 85 */ "select ::= WITH wqlist selectnowith", + /* 86 */ "select ::= WITH RECURSIVE wqlist selectnowith", + /* 87 */ "select ::= selectnowith", + /* 88 */ "selectnowith ::= selectnowith multiselect_op oneselect", + /* 89 */ "multiselect_op ::= UNION", + /* 90 */ "multiselect_op ::= UNION ALL", + /* 91 */ "multiselect_op ::= EXCEPT|INTERSECT", + /* 92 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", + /* 93 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt", + /* 94 */ "values ::= VALUES LP nexprlist RP", + /* 95 */ "values ::= values COMMA LP nexprlist RP", + /* 96 */ "distinct ::= DISTINCT", + /* 97 */ "distinct ::= ALL", + /* 98 */ "distinct ::=", + /* 99 */ "sclp ::=", + /* 100 */ "selcollist ::= sclp scanpt expr scanpt as", + /* 101 */ "selcollist ::= sclp scanpt STAR", + /* 102 */ "selcollist ::= sclp scanpt nm DOT STAR", + /* 103 */ "as ::= AS nm", + /* 104 */ "as ::=", + /* 105 */ "from ::=", + /* 106 */ "from ::= FROM seltablist", + /* 107 */ "stl_prefix ::= seltablist joinop", + /* 108 */ "stl_prefix ::=", + /* 109 */ "seltablist ::= stl_prefix nm dbnm as on_using", + /* 110 */ "seltablist ::= stl_prefix nm dbnm as indexed_by on_using", + /* 111 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using", + /* 112 */ "seltablist ::= stl_prefix LP select RP as on_using", + /* 113 */ "seltablist ::= stl_prefix LP seltablist RP as on_using", + /* 114 */ "dbnm ::=", + /* 115 */ "dbnm ::= DOT nm", + /* 116 */ "fullname ::= nm", + /* 117 */ "fullname ::= nm DOT nm", + /* 118 */ "xfullname ::= nm", + /* 119 */ "xfullname ::= nm DOT nm", + /* 120 */ "xfullname ::= nm DOT nm AS nm", + /* 121 */ "xfullname ::= nm AS nm", + /* 122 */ "joinop ::= COMMA|JOIN", + /* 123 */ "joinop ::= JOIN_KW JOIN", + /* 124 */ "joinop ::= JOIN_KW nm JOIN", + /* 125 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 126 */ "on_using ::= ON expr", + /* 127 */ "on_using ::= USING LP idlist RP", + /* 128 */ "on_using ::=", + /* 129 */ "indexed_opt ::=", + /* 130 */ "indexed_by ::= INDEXED BY nm", + /* 131 */ "indexed_by ::= NOT INDEXED", + /* 132 */ "orderby_opt ::=", + /* 133 */ "orderby_opt ::= ORDER BY sortlist", + /* 134 */ "sortlist ::= sortlist COMMA expr sortorder nulls", + /* 135 */ "sortlist ::= expr sortorder nulls", + /* 136 */ "sortorder ::= ASC", + /* 137 */ "sortorder ::= DESC", + /* 138 */ "sortorder ::=", + /* 139 */ "nulls ::= NULLS FIRST", + /* 140 */ "nulls ::= NULLS LAST", + /* 141 */ "nulls ::=", + /* 142 */ "groupby_opt ::=", + /* 143 */ "groupby_opt ::= GROUP BY nexprlist", + /* 144 */ "having_opt ::=", + /* 145 */ "having_opt ::= HAVING expr", + /* 146 */ "limit_opt ::=", + /* 147 */ "limit_opt ::= LIMIT expr", + /* 148 */ "limit_opt ::= LIMIT expr OFFSET expr", + /* 149 */ "limit_opt ::= LIMIT expr COMMA expr", + /* 150 */ "cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret", + /* 151 */ "where_opt ::=", + /* 152 */ "where_opt ::= WHERE expr", + /* 153 */ "where_opt_ret ::=", + /* 154 */ "where_opt_ret ::= WHERE expr", + /* 155 */ "where_opt_ret ::= RETURNING selcollist", + /* 156 */ "where_opt_ret ::= WHERE expr RETURNING selcollist", + /* 157 */ "cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret", + /* 158 */ "setlist ::= setlist COMMA nm EQ expr", + /* 159 */ "setlist ::= setlist COMMA LP idlist RP EQ expr", + /* 160 */ "setlist ::= nm EQ expr", + /* 161 */ "setlist ::= LP idlist RP EQ expr", + /* 162 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert", + /* 163 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning", + /* 164 */ "upsert ::=", + /* 165 */ "upsert ::= RETURNING selcollist", + /* 166 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert", + /* 167 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert", + /* 168 */ "upsert ::= ON CONFLICT DO NOTHING returning", + /* 169 */ "upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning", + /* 170 */ "returning ::= RETURNING selcollist", + /* 171 */ "insert_cmd ::= INSERT orconf", + /* 172 */ "insert_cmd ::= REPLACE", + /* 173 */ "idlist_opt ::=", + /* 174 */ "idlist_opt ::= LP idlist RP", + /* 175 */ "idlist ::= idlist COMMA nm", + /* 176 */ "idlist ::= nm", + /* 177 */ "expr ::= LP expr RP", + /* 178 */ "expr ::= ID|INDEXED|JOIN_KW", + /* 179 */ "expr ::= nm DOT nm", + /* 180 */ "expr ::= nm DOT nm DOT nm", + /* 181 */ "term ::= NULL|FLOAT|BLOB", + /* 182 */ "term ::= STRING", + /* 183 */ "term ::= INTEGER", + /* 184 */ "expr ::= VARIABLE", + /* 185 */ "expr ::= expr COLLATE ID|STRING", + /* 186 */ "expr ::= CAST LP expr AS typetoken RP", + /* 187 */ "expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP", + /* 188 */ "expr ::= ID|INDEXED|JOIN_KW LP STAR RP", + /* 189 */ "expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP filter_over", + /* 190 */ "expr ::= ID|INDEXED|JOIN_KW LP STAR RP filter_over", + /* 191 */ "term ::= CTIME_KW", + /* 192 */ "expr ::= LP nexprlist COMMA expr RP", + /* 193 */ "expr ::= expr AND expr", + /* 194 */ "expr ::= expr OR expr", + /* 195 */ "expr ::= expr LT|GT|GE|LE expr", + /* 196 */ "expr ::= expr EQ|NE expr", + /* 197 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 198 */ "expr ::= expr PLUS|MINUS expr", + /* 199 */ "expr ::= expr STAR|SLASH|REM expr", + /* 200 */ "expr ::= expr CONCAT expr", + /* 201 */ "likeop ::= NOT LIKE_KW|MATCH", + /* 202 */ "expr ::= expr likeop expr", + /* 203 */ "expr ::= expr likeop expr ESCAPE expr", + /* 204 */ "expr ::= expr ISNULL|NOTNULL", + /* 205 */ "expr ::= expr NOT NULL", + /* 206 */ "expr ::= expr IS expr", + /* 207 */ "expr ::= expr IS NOT expr", + /* 208 */ "expr ::= expr IS NOT DISTINCT FROM expr", + /* 209 */ "expr ::= expr IS DISTINCT FROM expr", + /* 210 */ "expr ::= NOT expr", + /* 211 */ "expr ::= BITNOT expr", + /* 212 */ "expr ::= PLUS|MINUS expr", + /* 213 */ "expr ::= expr PTR expr", + /* 214 */ "between_op ::= BETWEEN", + /* 215 */ "between_op ::= NOT BETWEEN", + /* 216 */ "expr ::= expr between_op expr AND expr", + /* 217 */ "in_op ::= IN", + /* 218 */ "in_op ::= NOT IN", + /* 219 */ "expr ::= expr in_op LP exprlist RP", + /* 220 */ "expr ::= LP select RP", + /* 221 */ "expr ::= expr in_op LP select RP", + /* 222 */ "expr ::= expr in_op nm dbnm paren_exprlist", + /* 223 */ "expr ::= EXISTS LP select RP", + /* 224 */ "expr ::= CASE case_operand case_exprlist case_else END", + /* 225 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 226 */ "case_exprlist ::= WHEN expr THEN expr", + /* 227 */ "case_else ::= ELSE expr", + /* 228 */ "case_else ::=", + /* 229 */ "case_operand ::=", + /* 230 */ "exprlist ::=", + /* 231 */ "nexprlist ::= nexprlist COMMA expr", + /* 232 */ "nexprlist ::= expr", + /* 233 */ "paren_exprlist ::=", + /* 234 */ "paren_exprlist ::= LP exprlist RP", + /* 235 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", + /* 236 */ "uniqueflag ::= UNIQUE", + /* 237 */ "uniqueflag ::=", + /* 238 */ "eidlist_opt ::=", + /* 239 */ "eidlist_opt ::= LP eidlist RP", + /* 240 */ "eidlist ::= eidlist COMMA nm collate sortorder", + /* 241 */ "eidlist ::= nm collate sortorder", + /* 242 */ "collate ::=", + /* 243 */ "collate ::= COLLATE ID|STRING", + /* 244 */ "cmd ::= DROP INDEX ifexists fullname", + /* 245 */ "cmd ::= VACUUM vinto", + /* 246 */ "cmd ::= VACUUM nm vinto", + /* 247 */ "vinto ::= INTO expr", + /* 248 */ "vinto ::=", + /* 249 */ "cmd ::= PRAGMA nm dbnm", + /* 250 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", + /* 251 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", + /* 252 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", + /* 253 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", + /* 254 */ "plus_num ::= PLUS INTEGER|FLOAT", + /* 255 */ "minus_num ::= MINUS INTEGER|FLOAT", + /* 256 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", + /* 257 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", + /* 258 */ "trigger_time ::= BEFORE|AFTER", + /* 259 */ "trigger_time ::= INSTEAD OF", + /* 260 */ "trigger_time ::=", + /* 261 */ "trigger_event ::= DELETE|INSERT", + /* 262 */ "trigger_event ::= UPDATE", + /* 263 */ "trigger_event ::= UPDATE OF idlist", + /* 264 */ "when_clause ::=", + /* 265 */ "when_clause ::= WHEN expr", + /* 266 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 267 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 268 */ "trnm ::= nm DOT nm", + /* 269 */ "tridxby ::= INDEXED BY nm", + /* 270 */ "tridxby ::= NOT INDEXED", + /* 271 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt", + /* 272 */ "trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt", + /* 273 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt", + /* 274 */ "trigger_cmd ::= scanpt select scanpt", + /* 275 */ "expr ::= RAISE LP IGNORE RP", + /* 276 */ "expr ::= RAISE LP raisetype COMMA nm RP", + /* 277 */ "raisetype ::= ROLLBACK", + /* 278 */ "raisetype ::= ABORT", + /* 279 */ "raisetype ::= FAIL", + /* 280 */ "cmd ::= DROP TRIGGER ifexists fullname", + /* 281 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", + /* 282 */ "cmd ::= DETACH database_kw_opt expr", + /* 283 */ "key_opt ::=", + /* 284 */ "key_opt ::= KEY expr", + /* 285 */ "cmd ::= REINDEX", + /* 286 */ "cmd ::= REINDEX nm dbnm", + /* 287 */ "cmd ::= ANALYZE", + /* 288 */ "cmd ::= ANALYZE nm dbnm", + /* 289 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", + /* 290 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist", + /* 291 */ "cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm", + /* 292 */ "add_column_fullname ::= fullname", + /* 293 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm", + /* 294 */ "cmd ::= create_vtab", + /* 295 */ "cmd ::= create_vtab LP vtabarglist RP", + /* 296 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", + /* 297 */ "vtabarg ::=", + /* 298 */ "vtabargtoken ::= ANY", + /* 299 */ "vtabargtoken ::= lp anylist RP", + /* 300 */ "lp ::= LP", + /* 301 */ "with ::= WITH wqlist", + /* 302 */ "with ::= WITH RECURSIVE wqlist", + /* 303 */ "wqas ::= AS", + /* 304 */ "wqas ::= AS MATERIALIZED", + /* 305 */ "wqas ::= AS NOT MATERIALIZED", + /* 306 */ "wqitem ::= nm eidlist_opt wqas LP select RP", + /* 307 */ "wqlist ::= wqitem", + /* 308 */ "wqlist ::= wqlist COMMA wqitem", + /* 309 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn", + /* 310 */ "windowdefn ::= nm AS LP window RP", + /* 311 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt", + /* 312 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt", + /* 313 */ "window ::= ORDER BY sortlist frame_opt", + /* 314 */ "window ::= nm ORDER BY sortlist frame_opt", + /* 315 */ "window ::= nm frame_opt", + /* 316 */ "frame_opt ::=", + /* 317 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt", + /* 318 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt", + /* 319 */ "range_or_rows ::= RANGE|ROWS|GROUPS", + /* 320 */ "frame_bound_s ::= frame_bound", + /* 321 */ "frame_bound_s ::= UNBOUNDED PRECEDING", + /* 322 */ "frame_bound_e ::= frame_bound", + /* 323 */ "frame_bound_e ::= UNBOUNDED FOLLOWING", + /* 324 */ "frame_bound ::= expr PRECEDING|FOLLOWING", + /* 325 */ "frame_bound ::= CURRENT ROW", + /* 326 */ "frame_exclude_opt ::=", + /* 327 */ "frame_exclude_opt ::= EXCLUDE frame_exclude", + /* 328 */ "frame_exclude ::= NO OTHERS", + /* 329 */ "frame_exclude ::= CURRENT ROW", + /* 330 */ "frame_exclude ::= GROUP|TIES", + /* 331 */ "window_clause ::= WINDOW windowdefn_list", + /* 332 */ "filter_over ::= filter_clause over_clause", + /* 333 */ "filter_over ::= over_clause", + /* 334 */ "filter_over ::= filter_clause", + /* 335 */ "over_clause ::= OVER LP window RP", + /* 336 */ "over_clause ::= OVER nm", + /* 337 */ "filter_clause ::= FILTER LP WHERE expr RP", + /* 338 */ "input ::= cmdlist", + /* 339 */ "cmdlist ::= cmdlist ecmd", + /* 340 */ "cmdlist ::= ecmd", + /* 341 */ "ecmd ::= SEMI", + /* 342 */ "ecmd ::= cmdx SEMI", + /* 343 */ "ecmd ::= explain cmdx SEMI", + /* 344 */ "trans_opt ::=", + /* 345 */ "trans_opt ::= TRANSACTION", + /* 346 */ "trans_opt ::= TRANSACTION nm", + /* 347 */ "savepoint_opt ::= SAVEPOINT", + /* 348 */ "savepoint_opt ::=", + /* 349 */ "cmd ::= create_table create_table_args", + /* 350 */ "table_option_set ::= table_option", + /* 351 */ "columnlist ::= columnlist COMMA columnname carglist", + /* 352 */ "columnlist ::= columnname carglist", + /* 353 */ "nm ::= ID|INDEXED|JOIN_KW", + /* 354 */ "nm ::= STRING", + /* 355 */ "typetoken ::= typename", + /* 356 */ "typename ::= ID|STRING", + /* 357 */ "signed ::= plus_num", + /* 358 */ "signed ::= minus_num", + /* 359 */ "carglist ::= carglist ccons", + /* 360 */ "carglist ::=", + /* 361 */ "ccons ::= NULL onconf", + /* 362 */ "ccons ::= GENERATED ALWAYS AS generated", + /* 363 */ "ccons ::= AS generated", + /* 364 */ "conslist_opt ::= COMMA conslist", + /* 365 */ "conslist ::= conslist tconscomma tcons", + /* 366 */ "conslist ::= tcons", + /* 367 */ "tconscomma ::=", + /* 368 */ "defer_subclause_opt ::= defer_subclause", + /* 369 */ "resolvetype ::= raisetype", + /* 370 */ "selectnowith ::= oneselect", + /* 371 */ "oneselect ::= values", + /* 372 */ "sclp ::= selcollist COMMA", + /* 373 */ "as ::= ID|STRING", + /* 374 */ "indexed_opt ::= indexed_by", + /* 375 */ "returning ::=", + /* 376 */ "expr ::= term", + /* 377 */ "likeop ::= LIKE_KW|MATCH", + /* 378 */ "case_operand ::= expr", + /* 379 */ "exprlist ::= nexprlist", + /* 380 */ "nmnum ::= plus_num", + /* 381 */ "nmnum ::= nm", + /* 382 */ "nmnum ::= ON", + /* 383 */ "nmnum ::= DELETE", + /* 384 */ "nmnum ::= DEFAULT", + /* 385 */ "plus_num ::= INTEGER|FLOAT", + /* 386 */ "foreach_clause ::=", + /* 387 */ "foreach_clause ::= FOR EACH ROW", + /* 388 */ "trnm ::= nm", + /* 389 */ "tridxby ::=", + /* 390 */ "database_kw_opt ::= DATABASE", + /* 391 */ "database_kw_opt ::=", + /* 392 */ "kwcolumn_opt ::=", + /* 393 */ "kwcolumn_opt ::= COLUMNKW", + /* 394 */ "vtabarglist ::= vtabarg", + /* 395 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 396 */ "vtabarg ::= vtabarg vtabargtoken", + /* 397 */ "anylist ::=", + /* 398 */ "anylist ::= anylist LP anylist RP", + /* 399 */ "anylist ::= anylist ANY", + /* 400 */ "with ::=", + /* 401 */ "windowdefn_list ::= windowdefn", + /* 402 */ "window ::= frame_opt", +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. Return the number +** of errors. Return 0 on success. +*/ +static int yyGrowStack(yyParser *p){ + int newSize; + int idx; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + idx = p->yytos ? (int)(p->yytos - p->yystack) : 0; + if( p->yystack==&p->yystk0 ){ + pNew = malloc(newSize*sizeof(pNew[0])); + if( pNew ) pNew[0] = p->yystk0; + }else{ + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + } + if( pNew ){ + p->yystack = pNew; + p->yytos = &p->yystack[idx]; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n", + yyTracePrompt, p->yystksz, newSize); + } +#endif + p->yystksz = newSize; + } + return pNew==0; +} +#endif + +/* Datatype of the argument to the memory allocated passed as the +** second argument to sqlite3ParserAlloc() below. This can be changed by +** putting an appropriate #define in the %include section of the input +** grammar. +*/ +#ifndef YYMALLOCARGTYPE +# define YYMALLOCARGTYPE size_t +#endif + +/* Initialize a new parser that has already been allocated. +*/ +SQLITE_PRIVATE void sqlite3ParserInit(void *yypRawParser sqlite3ParserCTX_PDECL){ + yyParser *yypParser = (yyParser*)yypRawParser; + sqlite3ParserCTX_STORE +#ifdef YYTRACKMAXSTACKDEPTH + yypParser->yyhwm = 0; +#endif +#if YYSTACKDEPTH<=0 + yypParser->yytos = NULL; + yypParser->yystack = NULL; + yypParser->yystksz = 0; + if( yyGrowStack(yypParser) ){ + yypParser->yystack = &yypParser->yystk0; + yypParser->yystksz = 1; + } +#endif +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + yypParser->yytos = yypParser->yystack; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; +#if YYSTACKDEPTH>0 + yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1]; +#endif +} + +#ifndef sqlite3Parser_ENGINEALWAYSONSTACK +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to sqlite3Parser and sqlite3ParserFree. +*/ +SQLITE_PRIVATE void *sqlite3ParserAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) sqlite3ParserCTX_PDECL){ + yyParser *yypParser; + yypParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) ); + if( yypParser ){ + sqlite3ParserCTX_STORE + sqlite3ParserInit(yypParser sqlite3ParserCTX_PARAM); + } + return (void*)yypParser; +} +#endif /* sqlite3Parser_ENGINEALWAYSONSTACK */ + + +/* The following function deletes the "minor type" or semantic value +** associated with a symbol. The symbol can be either a terminal +** or nonterminal. "yymajor" is the symbol code, and "yypminor" is +** a pointer to the value to be deleted. The code used to do the +** deletions is derived from the %destructor and/or %token_destructor +** directives of the input grammar. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + sqlite3ParserARG_FETCH + sqlite3ParserCTX_FETCH + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are *not* used + ** inside the C code. + */ +/********* Begin destructor definitions ***************************************/ + case 204: /* select */ + case 239: /* selectnowith */ + case 240: /* oneselect */ + case 252: /* values */ +{ +sqlite3SelectDelete(pParse->db, (yypminor->yy47)); +} + break; + case 216: /* term */ + case 217: /* expr */ + case 246: /* where_opt */ + case 248: /* having_opt */ + case 267: /* where_opt_ret */ + case 278: /* case_operand */ + case 280: /* case_else */ + case 283: /* vinto */ + case 290: /* when_clause */ + case 295: /* key_opt */ + case 311: /* filter_clause */ +{ +sqlite3ExprDelete(pParse->db, (yypminor->yy528)); +} + break; + case 221: /* eidlist_opt */ + case 231: /* sortlist */ + case 232: /* eidlist */ + case 244: /* selcollist */ + case 247: /* groupby_opt */ + case 249: /* orderby_opt */ + case 253: /* nexprlist */ + case 254: /* sclp */ + case 261: /* exprlist */ + case 268: /* setlist */ + case 277: /* paren_exprlist */ + case 279: /* case_exprlist */ + case 310: /* part_opt */ +{ +sqlite3ExprListDelete(pParse->db, (yypminor->yy322)); +} + break; + case 238: /* fullname */ + case 245: /* from */ + case 256: /* seltablist */ + case 257: /* stl_prefix */ + case 262: /* xfullname */ +{ +sqlite3SrcListDelete(pParse->db, (yypminor->yy131)); +} + break; + case 241: /* wqlist */ +{ +sqlite3WithDelete(pParse->db, (yypminor->yy521)); +} + break; + case 251: /* window_clause */ + case 306: /* windowdefn_list */ +{ +sqlite3WindowListDelete(pParse->db, (yypminor->yy41)); +} + break; + case 263: /* idlist */ + case 270: /* idlist_opt */ +{ +sqlite3IdListDelete(pParse->db, (yypminor->yy254)); +} + break; + case 273: /* filter_over */ + case 307: /* windowdefn */ + case 308: /* window */ + case 309: /* frame_opt */ + case 312: /* over_clause */ +{ +sqlite3WindowDelete(pParse->db, (yypminor->yy41)); +} + break; + case 286: /* trigger_cmd_list */ + case 291: /* trigger_cmd */ +{ +sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy33)); +} + break; + case 288: /* trigger_event */ +{ +sqlite3IdListDelete(pParse->db, (yypminor->yy180).b); +} + break; + case 314: /* frame_bound */ + case 315: /* frame_bound_s */ + case 316: /* frame_bound_e */ +{ +sqlite3ExprDelete(pParse->db, (yypminor->yy595).pExpr); +} + break; +/********* End destructor definitions *****************************************/ + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +*/ +static void yy_pop_parser_stack(yyParser *pParser){ + yyStackEntry *yytos; + assert( pParser->yytos!=0 ); + assert( pParser->yytos > pParser->yystack ); + yytos = pParser->yytos--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yy_destructor(pParser, yytos->major, &yytos->minor); +} + +/* +** Clear all secondary memory allocations from the parser +*/ +SQLITE_PRIVATE void sqlite3ParserFinalize(void *p){ + yyParser *pParser = (yyParser*)p; + while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack); +#endif +} + +#ifndef sqlite3Parser_ENGINEALWAYSONSTACK +/* +** Deallocate and destroy a parser. Destructors are called for +** all stack elements before shutting the parser down. +** +** If the YYPARSEFREENEVERNULL macro exists (for example because it +** is defined in a %include section of the input grammar) then it is +** assumed that the input pointer is never NULL. +*/ +SQLITE_PRIVATE void sqlite3ParserFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ +#ifndef YYPARSEFREENEVERNULL + if( p==0 ) return; +#endif + sqlite3ParserFinalize(p); + (*freeProc)(p); +} +#endif /* sqlite3Parser_ENGINEALWAYSONSTACK */ + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +SQLITE_PRIVATE int sqlite3ParserStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyhwm; +} +#endif + +/* This array of booleans keeps track of the parser statement +** coverage. The element yycoverage[X][Y] is set when the parser +** is in state X and has a lookahead token Y. In a well-tested +** systems, every element of this matrix should end up being set. +*/ +#if defined(YYCOVERAGE) +static unsigned char yycoverage[YYNSTATE][YYNTOKEN]; +#endif + +/* +** Write into out a description of every state/lookahead combination that +** +** (1) has not been used by the parser, and +** (2) is not a syntax error. +** +** Return the number of missed state/lookahead combinations. +*/ +#if defined(YYCOVERAGE) +SQLITE_PRIVATE int sqlite3ParserCoverage(FILE *out){ + int stateno, iLookAhead, i; + int nMissed = 0; + for(stateno=0; stateno<YYNSTATE; stateno++){ + i = yy_shift_ofst[stateno]; + for(iLookAhead=0; iLookAhead<YYNTOKEN; iLookAhead++){ + if( yy_lookahead[i+iLookAhead]!=iLookAhead ) continue; + if( yycoverage[stateno][iLookAhead]==0 ) nMissed++; + if( out ){ + fprintf(out,"State %d lookahead %s %s\n", stateno, + yyTokenName[iLookAhead], + yycoverage[stateno][iLookAhead] ? "ok" : "missed"); + } + } + } + return nMissed; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +*/ +static YYACTIONTYPE yy_find_shift_action( + YYCODETYPE iLookAhead, /* The look-ahead token */ + YYACTIONTYPE stateno /* Current state number */ +){ + int i; + + if( stateno>YY_MAX_SHIFT ) return stateno; + assert( stateno <= YY_SHIFT_COUNT ); +#if defined(YYCOVERAGE) + yycoverage[stateno][iLookAhead] = 1; +#endif + do{ + i = yy_shift_ofst[stateno]; + assert( i>=0 ); + assert( i<=YY_ACTTAB_COUNT ); + assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD ); + assert( iLookAhead!=YYNOCODE ); + assert( iLookAhead < YYNTOKEN ); + i += iLookAhead; + assert( i<(int)YY_NLOOKAHEAD ); + if( yy_lookahead[i]!=iLookAhead ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + assert( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0]) ); + iFallback = yyFallback[iLookAhead]; + if( iFallback!=0 ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + assert( j<(int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])) ); + if( yy_lookahead[j]==YYWILDCARD && iLookAhead>0 ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], + yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + return yy_default[stateno]; + }else{ + assert( i>=0 && i<(int)(sizeof(yy_action)/sizeof(yy_action[0])) ); + return yy_action[i]; + } + }while(1); +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +*/ +static YYACTIONTYPE yy_find_reduce_action( + YYACTIONTYPE stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && i<YY_ACTTAB_COUNT ); + assert( yy_lookahead[i]==iLookAhead ); +#endif + return yy_action[i]; +} + +/* +** The following routine is called if the stack overflows. +*/ +static void yyStackOverflow(yyParser *yypParser){ + sqlite3ParserARG_FETCH + sqlite3ParserCTX_FETCH +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +/******** Begin %stack_overflow code ******************************************/ + + sqlite3ErrorMsg(pParse, "parser stack overflow"); +/******** End %stack_overflow code ********************************************/ + sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument var */ + sqlite3ParserCTX_STORE +} + +/* +** Print tracing information for a SHIFT action +*/ +#ifndef NDEBUG +static void yyTraceShift(yyParser *yypParser, int yyNewState, const char *zTag){ + if( yyTraceFILE ){ + if( yyNewState<YYNSTATE ){ + fprintf(yyTraceFILE,"%s%s '%s', go to state %d\n", + yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major], + yyNewState); + }else{ + fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n", + yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major], + yyNewState - YY_MIN_REDUCE); + } + } +} +#else +# define yyTraceShift(X,Y,Z) +#endif + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + YYACTIONTYPE yyNewState, /* The new state to shift in */ + YYCODETYPE yyMajor, /* The major token to shift in */ + sqlite3ParserTOKENTYPE yyMinor /* The minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yytos++; +#ifdef YYTRACKMAXSTACKDEPTH + if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) ); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>yypParser->yystackEnd ){ + yypParser->yytos--; + yyStackOverflow(yypParser); + return; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){ + if( yyGrowStack(yypParser) ){ + yypParser->yytos--; + yyStackOverflow(yypParser); + return; + } + } +#endif + if( yyNewState > YY_MAX_SHIFT ){ + yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; + } + yytos = yypParser->yytos; + yytos->stateno = yyNewState; + yytos->major = yyMajor; + yytos->minor.yy0 = yyMinor; + yyTraceShift(yypParser, yyNewState, "Shift"); +} + +/* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side +** of that rule */ +static const YYCODETYPE yyRuleInfoLhs[] = { + 189, /* (0) explain ::= EXPLAIN */ + 189, /* (1) explain ::= EXPLAIN QUERY PLAN */ + 188, /* (2) cmdx ::= cmd */ + 190, /* (3) cmd ::= BEGIN transtype trans_opt */ + 191, /* (4) transtype ::= */ + 191, /* (5) transtype ::= DEFERRED */ + 191, /* (6) transtype ::= IMMEDIATE */ + 191, /* (7) transtype ::= EXCLUSIVE */ + 190, /* (8) cmd ::= COMMIT|END trans_opt */ + 190, /* (9) cmd ::= ROLLBACK trans_opt */ + 190, /* (10) cmd ::= SAVEPOINT nm */ + 190, /* (11) cmd ::= RELEASE savepoint_opt nm */ + 190, /* (12) cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ + 195, /* (13) create_table ::= createkw temp TABLE ifnotexists nm dbnm */ + 197, /* (14) createkw ::= CREATE */ + 199, /* (15) ifnotexists ::= */ + 199, /* (16) ifnotexists ::= IF NOT EXISTS */ + 198, /* (17) temp ::= TEMP */ + 198, /* (18) temp ::= */ + 196, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_option_set */ + 196, /* (20) create_table_args ::= AS select */ + 203, /* (21) table_option_set ::= */ + 203, /* (22) table_option_set ::= table_option_set COMMA table_option */ + 205, /* (23) table_option ::= WITHOUT nm */ + 205, /* (24) table_option ::= nm */ + 206, /* (25) columnname ::= nm typetoken */ + 208, /* (26) typetoken ::= */ + 208, /* (27) typetoken ::= typename LP signed RP */ + 208, /* (28) typetoken ::= typename LP signed COMMA signed RP */ + 209, /* (29) typename ::= typename ID|STRING */ + 213, /* (30) scanpt ::= */ + 214, /* (31) scantok ::= */ + 215, /* (32) ccons ::= CONSTRAINT nm */ + 215, /* (33) ccons ::= DEFAULT scantok term */ + 215, /* (34) ccons ::= DEFAULT LP expr RP */ + 215, /* (35) ccons ::= DEFAULT PLUS scantok term */ + 215, /* (36) ccons ::= DEFAULT MINUS scantok term */ + 215, /* (37) ccons ::= DEFAULT scantok ID|INDEXED */ + 215, /* (38) ccons ::= NOT NULL onconf */ + 215, /* (39) ccons ::= PRIMARY KEY sortorder onconf autoinc */ + 215, /* (40) ccons ::= UNIQUE onconf */ + 215, /* (41) ccons ::= CHECK LP expr RP */ + 215, /* (42) ccons ::= REFERENCES nm eidlist_opt refargs */ + 215, /* (43) ccons ::= defer_subclause */ + 215, /* (44) ccons ::= COLLATE ID|STRING */ + 224, /* (45) generated ::= LP expr RP */ + 224, /* (46) generated ::= LP expr RP ID */ + 220, /* (47) autoinc ::= */ + 220, /* (48) autoinc ::= AUTOINCR */ + 222, /* (49) refargs ::= */ + 222, /* (50) refargs ::= refargs refarg */ + 225, /* (51) refarg ::= MATCH nm */ + 225, /* (52) refarg ::= ON INSERT refact */ + 225, /* (53) refarg ::= ON DELETE refact */ + 225, /* (54) refarg ::= ON UPDATE refact */ + 226, /* (55) refact ::= SET NULL */ + 226, /* (56) refact ::= SET DEFAULT */ + 226, /* (57) refact ::= CASCADE */ + 226, /* (58) refact ::= RESTRICT */ + 226, /* (59) refact ::= NO ACTION */ + 223, /* (60) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ + 223, /* (61) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + 227, /* (62) init_deferred_pred_opt ::= */ + 227, /* (63) init_deferred_pred_opt ::= INITIALLY DEFERRED */ + 227, /* (64) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ + 202, /* (65) conslist_opt ::= */ + 229, /* (66) tconscomma ::= COMMA */ + 230, /* (67) tcons ::= CONSTRAINT nm */ + 230, /* (68) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ + 230, /* (69) tcons ::= UNIQUE LP sortlist RP onconf */ + 230, /* (70) tcons ::= CHECK LP expr RP onconf */ + 230, /* (71) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ + 233, /* (72) defer_subclause_opt ::= */ + 218, /* (73) onconf ::= */ + 218, /* (74) onconf ::= ON CONFLICT resolvetype */ + 234, /* (75) orconf ::= */ + 234, /* (76) orconf ::= OR resolvetype */ + 235, /* (77) resolvetype ::= IGNORE */ + 235, /* (78) resolvetype ::= REPLACE */ + 190, /* (79) cmd ::= DROP TABLE ifexists fullname */ + 237, /* (80) ifexists ::= IF EXISTS */ + 237, /* (81) ifexists ::= */ + 190, /* (82) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ + 190, /* (83) cmd ::= DROP VIEW ifexists fullname */ + 190, /* (84) cmd ::= select */ + 204, /* (85) select ::= WITH wqlist selectnowith */ + 204, /* (86) select ::= WITH RECURSIVE wqlist selectnowith */ + 204, /* (87) select ::= selectnowith */ + 239, /* (88) selectnowith ::= selectnowith multiselect_op oneselect */ + 242, /* (89) multiselect_op ::= UNION */ + 242, /* (90) multiselect_op ::= UNION ALL */ + 242, /* (91) multiselect_op ::= EXCEPT|INTERSECT */ + 240, /* (92) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ + 240, /* (93) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ + 252, /* (94) values ::= VALUES LP nexprlist RP */ + 252, /* (95) values ::= values COMMA LP nexprlist RP */ + 243, /* (96) distinct ::= DISTINCT */ + 243, /* (97) distinct ::= ALL */ + 243, /* (98) distinct ::= */ + 254, /* (99) sclp ::= */ + 244, /* (100) selcollist ::= sclp scanpt expr scanpt as */ + 244, /* (101) selcollist ::= sclp scanpt STAR */ + 244, /* (102) selcollist ::= sclp scanpt nm DOT STAR */ + 255, /* (103) as ::= AS nm */ + 255, /* (104) as ::= */ + 245, /* (105) from ::= */ + 245, /* (106) from ::= FROM seltablist */ + 257, /* (107) stl_prefix ::= seltablist joinop */ + 257, /* (108) stl_prefix ::= */ + 256, /* (109) seltablist ::= stl_prefix nm dbnm as on_using */ + 256, /* (110) seltablist ::= stl_prefix nm dbnm as indexed_by on_using */ + 256, /* (111) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */ + 256, /* (112) seltablist ::= stl_prefix LP select RP as on_using */ + 256, /* (113) seltablist ::= stl_prefix LP seltablist RP as on_using */ + 200, /* (114) dbnm ::= */ + 200, /* (115) dbnm ::= DOT nm */ + 238, /* (116) fullname ::= nm */ + 238, /* (117) fullname ::= nm DOT nm */ + 262, /* (118) xfullname ::= nm */ + 262, /* (119) xfullname ::= nm DOT nm */ + 262, /* (120) xfullname ::= nm DOT nm AS nm */ + 262, /* (121) xfullname ::= nm AS nm */ + 258, /* (122) joinop ::= COMMA|JOIN */ + 258, /* (123) joinop ::= JOIN_KW JOIN */ + 258, /* (124) joinop ::= JOIN_KW nm JOIN */ + 258, /* (125) joinop ::= JOIN_KW nm nm JOIN */ + 259, /* (126) on_using ::= ON expr */ + 259, /* (127) on_using ::= USING LP idlist RP */ + 259, /* (128) on_using ::= */ + 264, /* (129) indexed_opt ::= */ + 260, /* (130) indexed_by ::= INDEXED BY nm */ + 260, /* (131) indexed_by ::= NOT INDEXED */ + 249, /* (132) orderby_opt ::= */ + 249, /* (133) orderby_opt ::= ORDER BY sortlist */ + 231, /* (134) sortlist ::= sortlist COMMA expr sortorder nulls */ + 231, /* (135) sortlist ::= expr sortorder nulls */ + 219, /* (136) sortorder ::= ASC */ + 219, /* (137) sortorder ::= DESC */ + 219, /* (138) sortorder ::= */ + 265, /* (139) nulls ::= NULLS FIRST */ + 265, /* (140) nulls ::= NULLS LAST */ + 265, /* (141) nulls ::= */ + 247, /* (142) groupby_opt ::= */ + 247, /* (143) groupby_opt ::= GROUP BY nexprlist */ + 248, /* (144) having_opt ::= */ + 248, /* (145) having_opt ::= HAVING expr */ + 250, /* (146) limit_opt ::= */ + 250, /* (147) limit_opt ::= LIMIT expr */ + 250, /* (148) limit_opt ::= LIMIT expr OFFSET expr */ + 250, /* (149) limit_opt ::= LIMIT expr COMMA expr */ + 190, /* (150) cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ + 246, /* (151) where_opt ::= */ + 246, /* (152) where_opt ::= WHERE expr */ + 267, /* (153) where_opt_ret ::= */ + 267, /* (154) where_opt_ret ::= WHERE expr */ + 267, /* (155) where_opt_ret ::= RETURNING selcollist */ + 267, /* (156) where_opt_ret ::= WHERE expr RETURNING selcollist */ + 190, /* (157) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ + 268, /* (158) setlist ::= setlist COMMA nm EQ expr */ + 268, /* (159) setlist ::= setlist COMMA LP idlist RP EQ expr */ + 268, /* (160) setlist ::= nm EQ expr */ + 268, /* (161) setlist ::= LP idlist RP EQ expr */ + 190, /* (162) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ + 190, /* (163) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ + 271, /* (164) upsert ::= */ + 271, /* (165) upsert ::= RETURNING selcollist */ + 271, /* (166) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ + 271, /* (167) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ + 271, /* (168) upsert ::= ON CONFLICT DO NOTHING returning */ + 271, /* (169) upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ + 272, /* (170) returning ::= RETURNING selcollist */ + 269, /* (171) insert_cmd ::= INSERT orconf */ + 269, /* (172) insert_cmd ::= REPLACE */ + 270, /* (173) idlist_opt ::= */ + 270, /* (174) idlist_opt ::= LP idlist RP */ + 263, /* (175) idlist ::= idlist COMMA nm */ + 263, /* (176) idlist ::= nm */ + 217, /* (177) expr ::= LP expr RP */ + 217, /* (178) expr ::= ID|INDEXED|JOIN_KW */ + 217, /* (179) expr ::= nm DOT nm */ + 217, /* (180) expr ::= nm DOT nm DOT nm */ + 216, /* (181) term ::= NULL|FLOAT|BLOB */ + 216, /* (182) term ::= STRING */ + 216, /* (183) term ::= INTEGER */ + 217, /* (184) expr ::= VARIABLE */ + 217, /* (185) expr ::= expr COLLATE ID|STRING */ + 217, /* (186) expr ::= CAST LP expr AS typetoken RP */ + 217, /* (187) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP */ + 217, /* (188) expr ::= ID|INDEXED|JOIN_KW LP STAR RP */ + 217, /* (189) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP filter_over */ + 217, /* (190) expr ::= ID|INDEXED|JOIN_KW LP STAR RP filter_over */ + 216, /* (191) term ::= CTIME_KW */ + 217, /* (192) expr ::= LP nexprlist COMMA expr RP */ + 217, /* (193) expr ::= expr AND expr */ + 217, /* (194) expr ::= expr OR expr */ + 217, /* (195) expr ::= expr LT|GT|GE|LE expr */ + 217, /* (196) expr ::= expr EQ|NE expr */ + 217, /* (197) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ + 217, /* (198) expr ::= expr PLUS|MINUS expr */ + 217, /* (199) expr ::= expr STAR|SLASH|REM expr */ + 217, /* (200) expr ::= expr CONCAT expr */ + 274, /* (201) likeop ::= NOT LIKE_KW|MATCH */ + 217, /* (202) expr ::= expr likeop expr */ + 217, /* (203) expr ::= expr likeop expr ESCAPE expr */ + 217, /* (204) expr ::= expr ISNULL|NOTNULL */ + 217, /* (205) expr ::= expr NOT NULL */ + 217, /* (206) expr ::= expr IS expr */ + 217, /* (207) expr ::= expr IS NOT expr */ + 217, /* (208) expr ::= expr IS NOT DISTINCT FROM expr */ + 217, /* (209) expr ::= expr IS DISTINCT FROM expr */ + 217, /* (210) expr ::= NOT expr */ + 217, /* (211) expr ::= BITNOT expr */ + 217, /* (212) expr ::= PLUS|MINUS expr */ + 217, /* (213) expr ::= expr PTR expr */ + 275, /* (214) between_op ::= BETWEEN */ + 275, /* (215) between_op ::= NOT BETWEEN */ + 217, /* (216) expr ::= expr between_op expr AND expr */ + 276, /* (217) in_op ::= IN */ + 276, /* (218) in_op ::= NOT IN */ + 217, /* (219) expr ::= expr in_op LP exprlist RP */ + 217, /* (220) expr ::= LP select RP */ + 217, /* (221) expr ::= expr in_op LP select RP */ + 217, /* (222) expr ::= expr in_op nm dbnm paren_exprlist */ + 217, /* (223) expr ::= EXISTS LP select RP */ + 217, /* (224) expr ::= CASE case_operand case_exprlist case_else END */ + 279, /* (225) case_exprlist ::= case_exprlist WHEN expr THEN expr */ + 279, /* (226) case_exprlist ::= WHEN expr THEN expr */ + 280, /* (227) case_else ::= ELSE expr */ + 280, /* (228) case_else ::= */ + 278, /* (229) case_operand ::= */ + 261, /* (230) exprlist ::= */ + 253, /* (231) nexprlist ::= nexprlist COMMA expr */ + 253, /* (232) nexprlist ::= expr */ + 277, /* (233) paren_exprlist ::= */ + 277, /* (234) paren_exprlist ::= LP exprlist RP */ + 190, /* (235) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + 281, /* (236) uniqueflag ::= UNIQUE */ + 281, /* (237) uniqueflag ::= */ + 221, /* (238) eidlist_opt ::= */ + 221, /* (239) eidlist_opt ::= LP eidlist RP */ + 232, /* (240) eidlist ::= eidlist COMMA nm collate sortorder */ + 232, /* (241) eidlist ::= nm collate sortorder */ + 282, /* (242) collate ::= */ + 282, /* (243) collate ::= COLLATE ID|STRING */ + 190, /* (244) cmd ::= DROP INDEX ifexists fullname */ + 190, /* (245) cmd ::= VACUUM vinto */ + 190, /* (246) cmd ::= VACUUM nm vinto */ + 283, /* (247) vinto ::= INTO expr */ + 283, /* (248) vinto ::= */ + 190, /* (249) cmd ::= PRAGMA nm dbnm */ + 190, /* (250) cmd ::= PRAGMA nm dbnm EQ nmnum */ + 190, /* (251) cmd ::= PRAGMA nm dbnm LP nmnum RP */ + 190, /* (252) cmd ::= PRAGMA nm dbnm EQ minus_num */ + 190, /* (253) cmd ::= PRAGMA nm dbnm LP minus_num RP */ + 211, /* (254) plus_num ::= PLUS INTEGER|FLOAT */ + 212, /* (255) minus_num ::= MINUS INTEGER|FLOAT */ + 190, /* (256) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + 285, /* (257) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + 287, /* (258) trigger_time ::= BEFORE|AFTER */ + 287, /* (259) trigger_time ::= INSTEAD OF */ + 287, /* (260) trigger_time ::= */ + 288, /* (261) trigger_event ::= DELETE|INSERT */ + 288, /* (262) trigger_event ::= UPDATE */ + 288, /* (263) trigger_event ::= UPDATE OF idlist */ + 290, /* (264) when_clause ::= */ + 290, /* (265) when_clause ::= WHEN expr */ + 286, /* (266) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + 286, /* (267) trigger_cmd_list ::= trigger_cmd SEMI */ + 292, /* (268) trnm ::= nm DOT nm */ + 293, /* (269) tridxby ::= INDEXED BY nm */ + 293, /* (270) tridxby ::= NOT INDEXED */ + 291, /* (271) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ + 291, /* (272) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ + 291, /* (273) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ + 291, /* (274) trigger_cmd ::= scanpt select scanpt */ + 217, /* (275) expr ::= RAISE LP IGNORE RP */ + 217, /* (276) expr ::= RAISE LP raisetype COMMA nm RP */ + 236, /* (277) raisetype ::= ROLLBACK */ + 236, /* (278) raisetype ::= ABORT */ + 236, /* (279) raisetype ::= FAIL */ + 190, /* (280) cmd ::= DROP TRIGGER ifexists fullname */ + 190, /* (281) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + 190, /* (282) cmd ::= DETACH database_kw_opt expr */ + 295, /* (283) key_opt ::= */ + 295, /* (284) key_opt ::= KEY expr */ + 190, /* (285) cmd ::= REINDEX */ + 190, /* (286) cmd ::= REINDEX nm dbnm */ + 190, /* (287) cmd ::= ANALYZE */ + 190, /* (288) cmd ::= ANALYZE nm dbnm */ + 190, /* (289) cmd ::= ALTER TABLE fullname RENAME TO nm */ + 190, /* (290) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ + 190, /* (291) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ + 296, /* (292) add_column_fullname ::= fullname */ + 190, /* (293) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ + 190, /* (294) cmd ::= create_vtab */ + 190, /* (295) cmd ::= create_vtab LP vtabarglist RP */ + 298, /* (296) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + 300, /* (297) vtabarg ::= */ + 301, /* (298) vtabargtoken ::= ANY */ + 301, /* (299) vtabargtoken ::= lp anylist RP */ + 302, /* (300) lp ::= LP */ + 266, /* (301) with ::= WITH wqlist */ + 266, /* (302) with ::= WITH RECURSIVE wqlist */ + 305, /* (303) wqas ::= AS */ + 305, /* (304) wqas ::= AS MATERIALIZED */ + 305, /* (305) wqas ::= AS NOT MATERIALIZED */ + 304, /* (306) wqitem ::= nm eidlist_opt wqas LP select RP */ + 241, /* (307) wqlist ::= wqitem */ + 241, /* (308) wqlist ::= wqlist COMMA wqitem */ + 306, /* (309) windowdefn_list ::= windowdefn_list COMMA windowdefn */ + 307, /* (310) windowdefn ::= nm AS LP window RP */ + 308, /* (311) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + 308, /* (312) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + 308, /* (313) window ::= ORDER BY sortlist frame_opt */ + 308, /* (314) window ::= nm ORDER BY sortlist frame_opt */ + 308, /* (315) window ::= nm frame_opt */ + 309, /* (316) frame_opt ::= */ + 309, /* (317) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + 309, /* (318) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + 313, /* (319) range_or_rows ::= RANGE|ROWS|GROUPS */ + 315, /* (320) frame_bound_s ::= frame_bound */ + 315, /* (321) frame_bound_s ::= UNBOUNDED PRECEDING */ + 316, /* (322) frame_bound_e ::= frame_bound */ + 316, /* (323) frame_bound_e ::= UNBOUNDED FOLLOWING */ + 314, /* (324) frame_bound ::= expr PRECEDING|FOLLOWING */ + 314, /* (325) frame_bound ::= CURRENT ROW */ + 317, /* (326) frame_exclude_opt ::= */ + 317, /* (327) frame_exclude_opt ::= EXCLUDE frame_exclude */ + 318, /* (328) frame_exclude ::= NO OTHERS */ + 318, /* (329) frame_exclude ::= CURRENT ROW */ + 318, /* (330) frame_exclude ::= GROUP|TIES */ + 251, /* (331) window_clause ::= WINDOW windowdefn_list */ + 273, /* (332) filter_over ::= filter_clause over_clause */ + 273, /* (333) filter_over ::= over_clause */ + 273, /* (334) filter_over ::= filter_clause */ + 312, /* (335) over_clause ::= OVER LP window RP */ + 312, /* (336) over_clause ::= OVER nm */ + 311, /* (337) filter_clause ::= FILTER LP WHERE expr RP */ + 185, /* (338) input ::= cmdlist */ + 186, /* (339) cmdlist ::= cmdlist ecmd */ + 186, /* (340) cmdlist ::= ecmd */ + 187, /* (341) ecmd ::= SEMI */ + 187, /* (342) ecmd ::= cmdx SEMI */ + 187, /* (343) ecmd ::= explain cmdx SEMI */ + 192, /* (344) trans_opt ::= */ + 192, /* (345) trans_opt ::= TRANSACTION */ + 192, /* (346) trans_opt ::= TRANSACTION nm */ + 194, /* (347) savepoint_opt ::= SAVEPOINT */ + 194, /* (348) savepoint_opt ::= */ + 190, /* (349) cmd ::= create_table create_table_args */ + 203, /* (350) table_option_set ::= table_option */ + 201, /* (351) columnlist ::= columnlist COMMA columnname carglist */ + 201, /* (352) columnlist ::= columnname carglist */ + 193, /* (353) nm ::= ID|INDEXED|JOIN_KW */ + 193, /* (354) nm ::= STRING */ + 208, /* (355) typetoken ::= typename */ + 209, /* (356) typename ::= ID|STRING */ + 210, /* (357) signed ::= plus_num */ + 210, /* (358) signed ::= minus_num */ + 207, /* (359) carglist ::= carglist ccons */ + 207, /* (360) carglist ::= */ + 215, /* (361) ccons ::= NULL onconf */ + 215, /* (362) ccons ::= GENERATED ALWAYS AS generated */ + 215, /* (363) ccons ::= AS generated */ + 202, /* (364) conslist_opt ::= COMMA conslist */ + 228, /* (365) conslist ::= conslist tconscomma tcons */ + 228, /* (366) conslist ::= tcons */ + 229, /* (367) tconscomma ::= */ + 233, /* (368) defer_subclause_opt ::= defer_subclause */ + 235, /* (369) resolvetype ::= raisetype */ + 239, /* (370) selectnowith ::= oneselect */ + 240, /* (371) oneselect ::= values */ + 254, /* (372) sclp ::= selcollist COMMA */ + 255, /* (373) as ::= ID|STRING */ + 264, /* (374) indexed_opt ::= indexed_by */ + 272, /* (375) returning ::= */ + 217, /* (376) expr ::= term */ + 274, /* (377) likeop ::= LIKE_KW|MATCH */ + 278, /* (378) case_operand ::= expr */ + 261, /* (379) exprlist ::= nexprlist */ + 284, /* (380) nmnum ::= plus_num */ + 284, /* (381) nmnum ::= nm */ + 284, /* (382) nmnum ::= ON */ + 284, /* (383) nmnum ::= DELETE */ + 284, /* (384) nmnum ::= DEFAULT */ + 211, /* (385) plus_num ::= INTEGER|FLOAT */ + 289, /* (386) foreach_clause ::= */ + 289, /* (387) foreach_clause ::= FOR EACH ROW */ + 292, /* (388) trnm ::= nm */ + 293, /* (389) tridxby ::= */ + 294, /* (390) database_kw_opt ::= DATABASE */ + 294, /* (391) database_kw_opt ::= */ + 297, /* (392) kwcolumn_opt ::= */ + 297, /* (393) kwcolumn_opt ::= COLUMNKW */ + 299, /* (394) vtabarglist ::= vtabarg */ + 299, /* (395) vtabarglist ::= vtabarglist COMMA vtabarg */ + 300, /* (396) vtabarg ::= vtabarg vtabargtoken */ + 303, /* (397) anylist ::= */ + 303, /* (398) anylist ::= anylist LP anylist RP */ + 303, /* (399) anylist ::= anylist ANY */ + 266, /* (400) with ::= */ + 306, /* (401) windowdefn_list ::= windowdefn */ + 308, /* (402) window ::= frame_opt */ +}; + +/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number +** of symbols on the right-hand side of that rule. */ +static const signed char yyRuleInfoNRhs[] = { + -1, /* (0) explain ::= EXPLAIN */ + -3, /* (1) explain ::= EXPLAIN QUERY PLAN */ + -1, /* (2) cmdx ::= cmd */ + -3, /* (3) cmd ::= BEGIN transtype trans_opt */ + 0, /* (4) transtype ::= */ + -1, /* (5) transtype ::= DEFERRED */ + -1, /* (6) transtype ::= IMMEDIATE */ + -1, /* (7) transtype ::= EXCLUSIVE */ + -2, /* (8) cmd ::= COMMIT|END trans_opt */ + -2, /* (9) cmd ::= ROLLBACK trans_opt */ + -2, /* (10) cmd ::= SAVEPOINT nm */ + -3, /* (11) cmd ::= RELEASE savepoint_opt nm */ + -5, /* (12) cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ + -6, /* (13) create_table ::= createkw temp TABLE ifnotexists nm dbnm */ + -1, /* (14) createkw ::= CREATE */ + 0, /* (15) ifnotexists ::= */ + -3, /* (16) ifnotexists ::= IF NOT EXISTS */ + -1, /* (17) temp ::= TEMP */ + 0, /* (18) temp ::= */ + -5, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_option_set */ + -2, /* (20) create_table_args ::= AS select */ + 0, /* (21) table_option_set ::= */ + -3, /* (22) table_option_set ::= table_option_set COMMA table_option */ + -2, /* (23) table_option ::= WITHOUT nm */ + -1, /* (24) table_option ::= nm */ + -2, /* (25) columnname ::= nm typetoken */ + 0, /* (26) typetoken ::= */ + -4, /* (27) typetoken ::= typename LP signed RP */ + -6, /* (28) typetoken ::= typename LP signed COMMA signed RP */ + -2, /* (29) typename ::= typename ID|STRING */ + 0, /* (30) scanpt ::= */ + 0, /* (31) scantok ::= */ + -2, /* (32) ccons ::= CONSTRAINT nm */ + -3, /* (33) ccons ::= DEFAULT scantok term */ + -4, /* (34) ccons ::= DEFAULT LP expr RP */ + -4, /* (35) ccons ::= DEFAULT PLUS scantok term */ + -4, /* (36) ccons ::= DEFAULT MINUS scantok term */ + -3, /* (37) ccons ::= DEFAULT scantok ID|INDEXED */ + -3, /* (38) ccons ::= NOT NULL onconf */ + -5, /* (39) ccons ::= PRIMARY KEY sortorder onconf autoinc */ + -2, /* (40) ccons ::= UNIQUE onconf */ + -4, /* (41) ccons ::= CHECK LP expr RP */ + -4, /* (42) ccons ::= REFERENCES nm eidlist_opt refargs */ + -1, /* (43) ccons ::= defer_subclause */ + -2, /* (44) ccons ::= COLLATE ID|STRING */ + -3, /* (45) generated ::= LP expr RP */ + -4, /* (46) generated ::= LP expr RP ID */ + 0, /* (47) autoinc ::= */ + -1, /* (48) autoinc ::= AUTOINCR */ + 0, /* (49) refargs ::= */ + -2, /* (50) refargs ::= refargs refarg */ + -2, /* (51) refarg ::= MATCH nm */ + -3, /* (52) refarg ::= ON INSERT refact */ + -3, /* (53) refarg ::= ON DELETE refact */ + -3, /* (54) refarg ::= ON UPDATE refact */ + -2, /* (55) refact ::= SET NULL */ + -2, /* (56) refact ::= SET DEFAULT */ + -1, /* (57) refact ::= CASCADE */ + -1, /* (58) refact ::= RESTRICT */ + -2, /* (59) refact ::= NO ACTION */ + -3, /* (60) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ + -2, /* (61) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + 0, /* (62) init_deferred_pred_opt ::= */ + -2, /* (63) init_deferred_pred_opt ::= INITIALLY DEFERRED */ + -2, /* (64) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ + 0, /* (65) conslist_opt ::= */ + -1, /* (66) tconscomma ::= COMMA */ + -2, /* (67) tcons ::= CONSTRAINT nm */ + -7, /* (68) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ + -5, /* (69) tcons ::= UNIQUE LP sortlist RP onconf */ + -5, /* (70) tcons ::= CHECK LP expr RP onconf */ + -10, /* (71) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ + 0, /* (72) defer_subclause_opt ::= */ + 0, /* (73) onconf ::= */ + -3, /* (74) onconf ::= ON CONFLICT resolvetype */ + 0, /* (75) orconf ::= */ + -2, /* (76) orconf ::= OR resolvetype */ + -1, /* (77) resolvetype ::= IGNORE */ + -1, /* (78) resolvetype ::= REPLACE */ + -4, /* (79) cmd ::= DROP TABLE ifexists fullname */ + -2, /* (80) ifexists ::= IF EXISTS */ + 0, /* (81) ifexists ::= */ + -9, /* (82) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ + -4, /* (83) cmd ::= DROP VIEW ifexists fullname */ + -1, /* (84) cmd ::= select */ + -3, /* (85) select ::= WITH wqlist selectnowith */ + -4, /* (86) select ::= WITH RECURSIVE wqlist selectnowith */ + -1, /* (87) select ::= selectnowith */ + -3, /* (88) selectnowith ::= selectnowith multiselect_op oneselect */ + -1, /* (89) multiselect_op ::= UNION */ + -2, /* (90) multiselect_op ::= UNION ALL */ + -1, /* (91) multiselect_op ::= EXCEPT|INTERSECT */ + -9, /* (92) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ + -10, /* (93) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ + -4, /* (94) values ::= VALUES LP nexprlist RP */ + -5, /* (95) values ::= values COMMA LP nexprlist RP */ + -1, /* (96) distinct ::= DISTINCT */ + -1, /* (97) distinct ::= ALL */ + 0, /* (98) distinct ::= */ + 0, /* (99) sclp ::= */ + -5, /* (100) selcollist ::= sclp scanpt expr scanpt as */ + -3, /* (101) selcollist ::= sclp scanpt STAR */ + -5, /* (102) selcollist ::= sclp scanpt nm DOT STAR */ + -2, /* (103) as ::= AS nm */ + 0, /* (104) as ::= */ + 0, /* (105) from ::= */ + -2, /* (106) from ::= FROM seltablist */ + -2, /* (107) stl_prefix ::= seltablist joinop */ + 0, /* (108) stl_prefix ::= */ + -5, /* (109) seltablist ::= stl_prefix nm dbnm as on_using */ + -6, /* (110) seltablist ::= stl_prefix nm dbnm as indexed_by on_using */ + -8, /* (111) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */ + -6, /* (112) seltablist ::= stl_prefix LP select RP as on_using */ + -6, /* (113) seltablist ::= stl_prefix LP seltablist RP as on_using */ + 0, /* (114) dbnm ::= */ + -2, /* (115) dbnm ::= DOT nm */ + -1, /* (116) fullname ::= nm */ + -3, /* (117) fullname ::= nm DOT nm */ + -1, /* (118) xfullname ::= nm */ + -3, /* (119) xfullname ::= nm DOT nm */ + -5, /* (120) xfullname ::= nm DOT nm AS nm */ + -3, /* (121) xfullname ::= nm AS nm */ + -1, /* (122) joinop ::= COMMA|JOIN */ + -2, /* (123) joinop ::= JOIN_KW JOIN */ + -3, /* (124) joinop ::= JOIN_KW nm JOIN */ + -4, /* (125) joinop ::= JOIN_KW nm nm JOIN */ + -2, /* (126) on_using ::= ON expr */ + -4, /* (127) on_using ::= USING LP idlist RP */ + 0, /* (128) on_using ::= */ + 0, /* (129) indexed_opt ::= */ + -3, /* (130) indexed_by ::= INDEXED BY nm */ + -2, /* (131) indexed_by ::= NOT INDEXED */ + 0, /* (132) orderby_opt ::= */ + -3, /* (133) orderby_opt ::= ORDER BY sortlist */ + -5, /* (134) sortlist ::= sortlist COMMA expr sortorder nulls */ + -3, /* (135) sortlist ::= expr sortorder nulls */ + -1, /* (136) sortorder ::= ASC */ + -1, /* (137) sortorder ::= DESC */ + 0, /* (138) sortorder ::= */ + -2, /* (139) nulls ::= NULLS FIRST */ + -2, /* (140) nulls ::= NULLS LAST */ + 0, /* (141) nulls ::= */ + 0, /* (142) groupby_opt ::= */ + -3, /* (143) groupby_opt ::= GROUP BY nexprlist */ + 0, /* (144) having_opt ::= */ + -2, /* (145) having_opt ::= HAVING expr */ + 0, /* (146) limit_opt ::= */ + -2, /* (147) limit_opt ::= LIMIT expr */ + -4, /* (148) limit_opt ::= LIMIT expr OFFSET expr */ + -4, /* (149) limit_opt ::= LIMIT expr COMMA expr */ + -6, /* (150) cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ + 0, /* (151) where_opt ::= */ + -2, /* (152) where_opt ::= WHERE expr */ + 0, /* (153) where_opt_ret ::= */ + -2, /* (154) where_opt_ret ::= WHERE expr */ + -2, /* (155) where_opt_ret ::= RETURNING selcollist */ + -4, /* (156) where_opt_ret ::= WHERE expr RETURNING selcollist */ + -9, /* (157) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ + -5, /* (158) setlist ::= setlist COMMA nm EQ expr */ + -7, /* (159) setlist ::= setlist COMMA LP idlist RP EQ expr */ + -3, /* (160) setlist ::= nm EQ expr */ + -5, /* (161) setlist ::= LP idlist RP EQ expr */ + -7, /* (162) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ + -8, /* (163) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ + 0, /* (164) upsert ::= */ + -2, /* (165) upsert ::= RETURNING selcollist */ + -12, /* (166) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ + -9, /* (167) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ + -5, /* (168) upsert ::= ON CONFLICT DO NOTHING returning */ + -8, /* (169) upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ + -2, /* (170) returning ::= RETURNING selcollist */ + -2, /* (171) insert_cmd ::= INSERT orconf */ + -1, /* (172) insert_cmd ::= REPLACE */ + 0, /* (173) idlist_opt ::= */ + -3, /* (174) idlist_opt ::= LP idlist RP */ + -3, /* (175) idlist ::= idlist COMMA nm */ + -1, /* (176) idlist ::= nm */ + -3, /* (177) expr ::= LP expr RP */ + -1, /* (178) expr ::= ID|INDEXED|JOIN_KW */ + -3, /* (179) expr ::= nm DOT nm */ + -5, /* (180) expr ::= nm DOT nm DOT nm */ + -1, /* (181) term ::= NULL|FLOAT|BLOB */ + -1, /* (182) term ::= STRING */ + -1, /* (183) term ::= INTEGER */ + -1, /* (184) expr ::= VARIABLE */ + -3, /* (185) expr ::= expr COLLATE ID|STRING */ + -6, /* (186) expr ::= CAST LP expr AS typetoken RP */ + -5, /* (187) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP */ + -4, /* (188) expr ::= ID|INDEXED|JOIN_KW LP STAR RP */ + -6, /* (189) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP filter_over */ + -5, /* (190) expr ::= ID|INDEXED|JOIN_KW LP STAR RP filter_over */ + -1, /* (191) term ::= CTIME_KW */ + -5, /* (192) expr ::= LP nexprlist COMMA expr RP */ + -3, /* (193) expr ::= expr AND expr */ + -3, /* (194) expr ::= expr OR expr */ + -3, /* (195) expr ::= expr LT|GT|GE|LE expr */ + -3, /* (196) expr ::= expr EQ|NE expr */ + -3, /* (197) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ + -3, /* (198) expr ::= expr PLUS|MINUS expr */ + -3, /* (199) expr ::= expr STAR|SLASH|REM expr */ + -3, /* (200) expr ::= expr CONCAT expr */ + -2, /* (201) likeop ::= NOT LIKE_KW|MATCH */ + -3, /* (202) expr ::= expr likeop expr */ + -5, /* (203) expr ::= expr likeop expr ESCAPE expr */ + -2, /* (204) expr ::= expr ISNULL|NOTNULL */ + -3, /* (205) expr ::= expr NOT NULL */ + -3, /* (206) expr ::= expr IS expr */ + -4, /* (207) expr ::= expr IS NOT expr */ + -6, /* (208) expr ::= expr IS NOT DISTINCT FROM expr */ + -5, /* (209) expr ::= expr IS DISTINCT FROM expr */ + -2, /* (210) expr ::= NOT expr */ + -2, /* (211) expr ::= BITNOT expr */ + -2, /* (212) expr ::= PLUS|MINUS expr */ + -3, /* (213) expr ::= expr PTR expr */ + -1, /* (214) between_op ::= BETWEEN */ + -2, /* (215) between_op ::= NOT BETWEEN */ + -5, /* (216) expr ::= expr between_op expr AND expr */ + -1, /* (217) in_op ::= IN */ + -2, /* (218) in_op ::= NOT IN */ + -5, /* (219) expr ::= expr in_op LP exprlist RP */ + -3, /* (220) expr ::= LP select RP */ + -5, /* (221) expr ::= expr in_op LP select RP */ + -5, /* (222) expr ::= expr in_op nm dbnm paren_exprlist */ + -4, /* (223) expr ::= EXISTS LP select RP */ + -5, /* (224) expr ::= CASE case_operand case_exprlist case_else END */ + -5, /* (225) case_exprlist ::= case_exprlist WHEN expr THEN expr */ + -4, /* (226) case_exprlist ::= WHEN expr THEN expr */ + -2, /* (227) case_else ::= ELSE expr */ + 0, /* (228) case_else ::= */ + 0, /* (229) case_operand ::= */ + 0, /* (230) exprlist ::= */ + -3, /* (231) nexprlist ::= nexprlist COMMA expr */ + -1, /* (232) nexprlist ::= expr */ + 0, /* (233) paren_exprlist ::= */ + -3, /* (234) paren_exprlist ::= LP exprlist RP */ + -12, /* (235) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + -1, /* (236) uniqueflag ::= UNIQUE */ + 0, /* (237) uniqueflag ::= */ + 0, /* (238) eidlist_opt ::= */ + -3, /* (239) eidlist_opt ::= LP eidlist RP */ + -5, /* (240) eidlist ::= eidlist COMMA nm collate sortorder */ + -3, /* (241) eidlist ::= nm collate sortorder */ + 0, /* (242) collate ::= */ + -2, /* (243) collate ::= COLLATE ID|STRING */ + -4, /* (244) cmd ::= DROP INDEX ifexists fullname */ + -2, /* (245) cmd ::= VACUUM vinto */ + -3, /* (246) cmd ::= VACUUM nm vinto */ + -2, /* (247) vinto ::= INTO expr */ + 0, /* (248) vinto ::= */ + -3, /* (249) cmd ::= PRAGMA nm dbnm */ + -5, /* (250) cmd ::= PRAGMA nm dbnm EQ nmnum */ + -6, /* (251) cmd ::= PRAGMA nm dbnm LP nmnum RP */ + -5, /* (252) cmd ::= PRAGMA nm dbnm EQ minus_num */ + -6, /* (253) cmd ::= PRAGMA nm dbnm LP minus_num RP */ + -2, /* (254) plus_num ::= PLUS INTEGER|FLOAT */ + -2, /* (255) minus_num ::= MINUS INTEGER|FLOAT */ + -5, /* (256) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + -11, /* (257) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + -1, /* (258) trigger_time ::= BEFORE|AFTER */ + -2, /* (259) trigger_time ::= INSTEAD OF */ + 0, /* (260) trigger_time ::= */ + -1, /* (261) trigger_event ::= DELETE|INSERT */ + -1, /* (262) trigger_event ::= UPDATE */ + -3, /* (263) trigger_event ::= UPDATE OF idlist */ + 0, /* (264) when_clause ::= */ + -2, /* (265) when_clause ::= WHEN expr */ + -3, /* (266) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + -2, /* (267) trigger_cmd_list ::= trigger_cmd SEMI */ + -3, /* (268) trnm ::= nm DOT nm */ + -3, /* (269) tridxby ::= INDEXED BY nm */ + -2, /* (270) tridxby ::= NOT INDEXED */ + -9, /* (271) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ + -8, /* (272) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ + -6, /* (273) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ + -3, /* (274) trigger_cmd ::= scanpt select scanpt */ + -4, /* (275) expr ::= RAISE LP IGNORE RP */ + -6, /* (276) expr ::= RAISE LP raisetype COMMA nm RP */ + -1, /* (277) raisetype ::= ROLLBACK */ + -1, /* (278) raisetype ::= ABORT */ + -1, /* (279) raisetype ::= FAIL */ + -4, /* (280) cmd ::= DROP TRIGGER ifexists fullname */ + -6, /* (281) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + -3, /* (282) cmd ::= DETACH database_kw_opt expr */ + 0, /* (283) key_opt ::= */ + -2, /* (284) key_opt ::= KEY expr */ + -1, /* (285) cmd ::= REINDEX */ + -3, /* (286) cmd ::= REINDEX nm dbnm */ + -1, /* (287) cmd ::= ANALYZE */ + -3, /* (288) cmd ::= ANALYZE nm dbnm */ + -6, /* (289) cmd ::= ALTER TABLE fullname RENAME TO nm */ + -7, /* (290) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ + -6, /* (291) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ + -1, /* (292) add_column_fullname ::= fullname */ + -8, /* (293) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ + -1, /* (294) cmd ::= create_vtab */ + -4, /* (295) cmd ::= create_vtab LP vtabarglist RP */ + -8, /* (296) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + 0, /* (297) vtabarg ::= */ + -1, /* (298) vtabargtoken ::= ANY */ + -3, /* (299) vtabargtoken ::= lp anylist RP */ + -1, /* (300) lp ::= LP */ + -2, /* (301) with ::= WITH wqlist */ + -3, /* (302) with ::= WITH RECURSIVE wqlist */ + -1, /* (303) wqas ::= AS */ + -2, /* (304) wqas ::= AS MATERIALIZED */ + -3, /* (305) wqas ::= AS NOT MATERIALIZED */ + -6, /* (306) wqitem ::= nm eidlist_opt wqas LP select RP */ + -1, /* (307) wqlist ::= wqitem */ + -3, /* (308) wqlist ::= wqlist COMMA wqitem */ + -3, /* (309) windowdefn_list ::= windowdefn_list COMMA windowdefn */ + -5, /* (310) windowdefn ::= nm AS LP window RP */ + -5, /* (311) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + -6, /* (312) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + -4, /* (313) window ::= ORDER BY sortlist frame_opt */ + -5, /* (314) window ::= nm ORDER BY sortlist frame_opt */ + -2, /* (315) window ::= nm frame_opt */ + 0, /* (316) frame_opt ::= */ + -3, /* (317) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + -6, /* (318) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + -1, /* (319) range_or_rows ::= RANGE|ROWS|GROUPS */ + -1, /* (320) frame_bound_s ::= frame_bound */ + -2, /* (321) frame_bound_s ::= UNBOUNDED PRECEDING */ + -1, /* (322) frame_bound_e ::= frame_bound */ + -2, /* (323) frame_bound_e ::= UNBOUNDED FOLLOWING */ + -2, /* (324) frame_bound ::= expr PRECEDING|FOLLOWING */ + -2, /* (325) frame_bound ::= CURRENT ROW */ + 0, /* (326) frame_exclude_opt ::= */ + -2, /* (327) frame_exclude_opt ::= EXCLUDE frame_exclude */ + -2, /* (328) frame_exclude ::= NO OTHERS */ + -2, /* (329) frame_exclude ::= CURRENT ROW */ + -1, /* (330) frame_exclude ::= GROUP|TIES */ + -2, /* (331) window_clause ::= WINDOW windowdefn_list */ + -2, /* (332) filter_over ::= filter_clause over_clause */ + -1, /* (333) filter_over ::= over_clause */ + -1, /* (334) filter_over ::= filter_clause */ + -4, /* (335) over_clause ::= OVER LP window RP */ + -2, /* (336) over_clause ::= OVER nm */ + -5, /* (337) filter_clause ::= FILTER LP WHERE expr RP */ + -1, /* (338) input ::= cmdlist */ + -2, /* (339) cmdlist ::= cmdlist ecmd */ + -1, /* (340) cmdlist ::= ecmd */ + -1, /* (341) ecmd ::= SEMI */ + -2, /* (342) ecmd ::= cmdx SEMI */ + -3, /* (343) ecmd ::= explain cmdx SEMI */ + 0, /* (344) trans_opt ::= */ + -1, /* (345) trans_opt ::= TRANSACTION */ + -2, /* (346) trans_opt ::= TRANSACTION nm */ + -1, /* (347) savepoint_opt ::= SAVEPOINT */ + 0, /* (348) savepoint_opt ::= */ + -2, /* (349) cmd ::= create_table create_table_args */ + -1, /* (350) table_option_set ::= table_option */ + -4, /* (351) columnlist ::= columnlist COMMA columnname carglist */ + -2, /* (352) columnlist ::= columnname carglist */ + -1, /* (353) nm ::= ID|INDEXED|JOIN_KW */ + -1, /* (354) nm ::= STRING */ + -1, /* (355) typetoken ::= typename */ + -1, /* (356) typename ::= ID|STRING */ + -1, /* (357) signed ::= plus_num */ + -1, /* (358) signed ::= minus_num */ + -2, /* (359) carglist ::= carglist ccons */ + 0, /* (360) carglist ::= */ + -2, /* (361) ccons ::= NULL onconf */ + -4, /* (362) ccons ::= GENERATED ALWAYS AS generated */ + -2, /* (363) ccons ::= AS generated */ + -2, /* (364) conslist_opt ::= COMMA conslist */ + -3, /* (365) conslist ::= conslist tconscomma tcons */ + -1, /* (366) conslist ::= tcons */ + 0, /* (367) tconscomma ::= */ + -1, /* (368) defer_subclause_opt ::= defer_subclause */ + -1, /* (369) resolvetype ::= raisetype */ + -1, /* (370) selectnowith ::= oneselect */ + -1, /* (371) oneselect ::= values */ + -2, /* (372) sclp ::= selcollist COMMA */ + -1, /* (373) as ::= ID|STRING */ + -1, /* (374) indexed_opt ::= indexed_by */ + 0, /* (375) returning ::= */ + -1, /* (376) expr ::= term */ + -1, /* (377) likeop ::= LIKE_KW|MATCH */ + -1, /* (378) case_operand ::= expr */ + -1, /* (379) exprlist ::= nexprlist */ + -1, /* (380) nmnum ::= plus_num */ + -1, /* (381) nmnum ::= nm */ + -1, /* (382) nmnum ::= ON */ + -1, /* (383) nmnum ::= DELETE */ + -1, /* (384) nmnum ::= DEFAULT */ + -1, /* (385) plus_num ::= INTEGER|FLOAT */ + 0, /* (386) foreach_clause ::= */ + -3, /* (387) foreach_clause ::= FOR EACH ROW */ + -1, /* (388) trnm ::= nm */ + 0, /* (389) tridxby ::= */ + -1, /* (390) database_kw_opt ::= DATABASE */ + 0, /* (391) database_kw_opt ::= */ + 0, /* (392) kwcolumn_opt ::= */ + -1, /* (393) kwcolumn_opt ::= COLUMNKW */ + -1, /* (394) vtabarglist ::= vtabarg */ + -3, /* (395) vtabarglist ::= vtabarglist COMMA vtabarg */ + -2, /* (396) vtabarg ::= vtabarg vtabargtoken */ + 0, /* (397) anylist ::= */ + -4, /* (398) anylist ::= anylist LP anylist RP */ + -2, /* (399) anylist ::= anylist ANY */ + 0, /* (400) with ::= */ + -1, /* (401) windowdefn_list ::= windowdefn */ + -1, /* (402) window ::= frame_opt */ +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +** +** The yyLookahead and yyLookaheadToken parameters provide reduce actions +** access to the lookahead token (if any). The yyLookahead will be YYNOCODE +** if the lookahead token has already been consumed. As this procedure is +** only called from one place, optimizing compilers will in-line it, which +** means that the extra parameters have no performance impact. +*/ +static YYACTIONTYPE yy_reduce( + yyParser *yypParser, /* The parser */ + unsigned int yyruleno, /* Number of the rule by which to reduce */ + int yyLookahead, /* Lookahead token, or YYNOCODE if none */ + sqlite3ParserTOKENTYPE yyLookaheadToken /* Value of the lookahead token */ + sqlite3ParserCTX_PDECL /* %extra_context */ +){ + int yygoto; /* The next state */ + YYACTIONTYPE yyact; /* The next action */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + sqlite3ParserARG_FETCH + (void)yyLookahead; + (void)yyLookaheadToken; + yymsp = yypParser->yytos; + + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line <lineno> <grammarfile> + ** { ... } // User supplied code + ** #line <lineno> <thisfile> + ** break; + */ +/********** Begin reduce actions **********************************************/ + YYMINORTYPE yylhsminor; + case 0: /* explain ::= EXPLAIN */ +{ if( pParse->pReprepare==0 ) pParse->explain = 1; } + break; + case 1: /* explain ::= EXPLAIN QUERY PLAN */ +{ if( pParse->pReprepare==0 ) pParse->explain = 2; } + break; + case 2: /* cmdx ::= cmd */ +{ sqlite3FinishCoding(pParse); } + break; + case 3: /* cmd ::= BEGIN transtype trans_opt */ +{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy394);} + break; + case 4: /* transtype ::= */ +{yymsp[1].minor.yy394 = TK_DEFERRED;} + break; + case 5: /* transtype ::= DEFERRED */ + case 6: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==6); + case 7: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==7); + case 319: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==319); +{yymsp[0].minor.yy394 = yymsp[0].major; /*A-overwrites-X*/} + break; + case 8: /* cmd ::= COMMIT|END trans_opt */ + case 9: /* cmd ::= ROLLBACK trans_opt */ yytestcase(yyruleno==9); +{sqlite3EndTransaction(pParse,yymsp[-1].major);} + break; + case 10: /* cmd ::= SAVEPOINT nm */ +{ + sqlite3Savepoint(pParse, SAVEPOINT_BEGIN, &yymsp[0].minor.yy0); +} + break; + case 11: /* cmd ::= RELEASE savepoint_opt nm */ +{ + sqlite3Savepoint(pParse, SAVEPOINT_RELEASE, &yymsp[0].minor.yy0); +} + break; + case 12: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ +{ + sqlite3Savepoint(pParse, SAVEPOINT_ROLLBACK, &yymsp[0].minor.yy0); +} + break; + case 13: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */ +{ + sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy394,0,0,yymsp[-2].minor.yy394); +} + break; + case 14: /* createkw ::= CREATE */ +{disableLookaside(pParse);} + break; + case 15: /* ifnotexists ::= */ + case 18: /* temp ::= */ yytestcase(yyruleno==18); + case 47: /* autoinc ::= */ yytestcase(yyruleno==47); + case 62: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==62); + case 72: /* defer_subclause_opt ::= */ yytestcase(yyruleno==72); + case 81: /* ifexists ::= */ yytestcase(yyruleno==81); + case 98: /* distinct ::= */ yytestcase(yyruleno==98); + case 242: /* collate ::= */ yytestcase(yyruleno==242); +{yymsp[1].minor.yy394 = 0;} + break; + case 16: /* ifnotexists ::= IF NOT EXISTS */ +{yymsp[-2].minor.yy394 = 1;} + break; + case 17: /* temp ::= TEMP */ +{yymsp[0].minor.yy394 = pParse->db->init.busy==0;} + break; + case 19: /* create_table_args ::= LP columnlist conslist_opt RP table_option_set */ +{ + sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy285,0); +} + break; + case 20: /* create_table_args ::= AS select */ +{ + sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy47); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy47); +} + break; + case 21: /* table_option_set ::= */ +{yymsp[1].minor.yy285 = 0;} + break; + case 22: /* table_option_set ::= table_option_set COMMA table_option */ +{yylhsminor.yy285 = yymsp[-2].minor.yy285|yymsp[0].minor.yy285;} + yymsp[-2].minor.yy285 = yylhsminor.yy285; + break; + case 23: /* table_option ::= WITHOUT nm */ +{ + if( yymsp[0].minor.yy0.n==5 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"rowid",5)==0 ){ + yymsp[-1].minor.yy285 = TF_WithoutRowid | TF_NoVisibleRowid; + }else{ + yymsp[-1].minor.yy285 = 0; + sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z); + } +} + break; + case 24: /* table_option ::= nm */ +{ + if( yymsp[0].minor.yy0.n==6 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"strict",6)==0 ){ + yylhsminor.yy285 = TF_Strict; + }else{ + yylhsminor.yy285 = 0; + sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z); + } +} + yymsp[0].minor.yy285 = yylhsminor.yy285; + break; + case 25: /* columnname ::= nm typetoken */ +{sqlite3AddColumn(pParse,yymsp[-1].minor.yy0,yymsp[0].minor.yy0);} + break; + case 26: /* typetoken ::= */ + case 65: /* conslist_opt ::= */ yytestcase(yyruleno==65); + case 104: /* as ::= */ yytestcase(yyruleno==104); +{yymsp[1].minor.yy0.n = 0; yymsp[1].minor.yy0.z = 0;} + break; + case 27: /* typetoken ::= typename LP signed RP */ +{ + yymsp[-3].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-3].minor.yy0.z); +} + break; + case 28: /* typetoken ::= typename LP signed COMMA signed RP */ +{ + yymsp[-5].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-5].minor.yy0.z); +} + break; + case 29: /* typename ::= typename ID|STRING */ +{yymsp[-1].minor.yy0.n=yymsp[0].minor.yy0.n+(int)(yymsp[0].minor.yy0.z-yymsp[-1].minor.yy0.z);} + break; + case 30: /* scanpt ::= */ +{ + assert( yyLookahead!=YYNOCODE ); + yymsp[1].minor.yy522 = yyLookaheadToken.z; +} + break; + case 31: /* scantok ::= */ +{ + assert( yyLookahead!=YYNOCODE ); + yymsp[1].minor.yy0 = yyLookaheadToken; +} + break; + case 32: /* ccons ::= CONSTRAINT nm */ + case 67: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==67); +{pParse->constraintName = yymsp[0].minor.yy0;} + break; + case 33: /* ccons ::= DEFAULT scantok term */ +{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy528,yymsp[-1].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} + break; + case 34: /* ccons ::= DEFAULT LP expr RP */ +{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy528,yymsp[-2].minor.yy0.z+1,yymsp[0].minor.yy0.z);} + break; + case 35: /* ccons ::= DEFAULT PLUS scantok term */ +{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy528,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} + break; + case 36: /* ccons ::= DEFAULT MINUS scantok term */ +{ + Expr *p = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy528, 0); + sqlite3AddDefaultValue(pParse,p,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]); +} + break; + case 37: /* ccons ::= DEFAULT scantok ID|INDEXED */ +{ + Expr *p = tokenExpr(pParse, TK_STRING, yymsp[0].minor.yy0); + if( p ){ + sqlite3ExprIdToTrueFalse(p); + testcase( p->op==TK_TRUEFALSE && sqlite3ExprTruthValue(p) ); + } + sqlite3AddDefaultValue(pParse,p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.z+yymsp[0].minor.yy0.n); +} + break; + case 38: /* ccons ::= NOT NULL onconf */ +{sqlite3AddNotNull(pParse, yymsp[0].minor.yy394);} + break; + case 39: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ +{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy394,yymsp[0].minor.yy394,yymsp[-2].minor.yy394);} + break; + case 40: /* ccons ::= UNIQUE onconf */ +{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy394,0,0,0,0, + SQLITE_IDXTYPE_UNIQUE);} + break; + case 41: /* ccons ::= CHECK LP expr RP */ +{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy528,yymsp[-2].minor.yy0.z,yymsp[0].minor.yy0.z);} + break; + case 42: /* ccons ::= REFERENCES nm eidlist_opt refargs */ +{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy322,yymsp[0].minor.yy394);} + break; + case 43: /* ccons ::= defer_subclause */ +{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy394);} + break; + case 44: /* ccons ::= COLLATE ID|STRING */ +{sqlite3AddCollateType(pParse, &yymsp[0].minor.yy0);} + break; + case 45: /* generated ::= LP expr RP */ +{sqlite3AddGenerated(pParse,yymsp[-1].minor.yy528,0);} + break; + case 46: /* generated ::= LP expr RP ID */ +{sqlite3AddGenerated(pParse,yymsp[-2].minor.yy528,&yymsp[0].minor.yy0);} + break; + case 48: /* autoinc ::= AUTOINCR */ +{yymsp[0].minor.yy394 = 1;} + break; + case 49: /* refargs ::= */ +{ yymsp[1].minor.yy394 = OE_None*0x0101; /* EV: R-19803-45884 */} + break; + case 50: /* refargs ::= refargs refarg */ +{ yymsp[-1].minor.yy394 = (yymsp[-1].minor.yy394 & ~yymsp[0].minor.yy231.mask) | yymsp[0].minor.yy231.value; } + break; + case 51: /* refarg ::= MATCH nm */ +{ yymsp[-1].minor.yy231.value = 0; yymsp[-1].minor.yy231.mask = 0x000000; } + break; + case 52: /* refarg ::= ON INSERT refact */ +{ yymsp[-2].minor.yy231.value = 0; yymsp[-2].minor.yy231.mask = 0x000000; } + break; + case 53: /* refarg ::= ON DELETE refact */ +{ yymsp[-2].minor.yy231.value = yymsp[0].minor.yy394; yymsp[-2].minor.yy231.mask = 0x0000ff; } + break; + case 54: /* refarg ::= ON UPDATE refact */ +{ yymsp[-2].minor.yy231.value = yymsp[0].minor.yy394<<8; yymsp[-2].minor.yy231.mask = 0x00ff00; } + break; + case 55: /* refact ::= SET NULL */ +{ yymsp[-1].minor.yy394 = OE_SetNull; /* EV: R-33326-45252 */} + break; + case 56: /* refact ::= SET DEFAULT */ +{ yymsp[-1].minor.yy394 = OE_SetDflt; /* EV: R-33326-45252 */} + break; + case 57: /* refact ::= CASCADE */ +{ yymsp[0].minor.yy394 = OE_Cascade; /* EV: R-33326-45252 */} + break; + case 58: /* refact ::= RESTRICT */ +{ yymsp[0].minor.yy394 = OE_Restrict; /* EV: R-33326-45252 */} + break; + case 59: /* refact ::= NO ACTION */ +{ yymsp[-1].minor.yy394 = OE_None; /* EV: R-33326-45252 */} + break; + case 60: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ +{yymsp[-2].minor.yy394 = 0;} + break; + case 61: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + case 76: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==76); + case 171: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==171); +{yymsp[-1].minor.yy394 = yymsp[0].minor.yy394;} + break; + case 63: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ + case 80: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==80); + case 215: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==215); + case 218: /* in_op ::= NOT IN */ yytestcase(yyruleno==218); + case 243: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==243); +{yymsp[-1].minor.yy394 = 1;} + break; + case 64: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ +{yymsp[-1].minor.yy394 = 0;} + break; + case 66: /* tconscomma ::= COMMA */ +{pParse->constraintName.n = 0;} + break; + case 68: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ +{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy322,yymsp[0].minor.yy394,yymsp[-2].minor.yy394,0);} + break; + case 69: /* tcons ::= UNIQUE LP sortlist RP onconf */ +{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy322,yymsp[0].minor.yy394,0,0,0,0, + SQLITE_IDXTYPE_UNIQUE);} + break; + case 70: /* tcons ::= CHECK LP expr RP onconf */ +{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy528,yymsp[-3].minor.yy0.z,yymsp[-1].minor.yy0.z);} + break; + case 71: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ +{ + sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy322, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy322, yymsp[-1].minor.yy394); + sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy394); +} + break; + case 73: /* onconf ::= */ + case 75: /* orconf ::= */ yytestcase(yyruleno==75); +{yymsp[1].minor.yy394 = OE_Default;} + break; + case 74: /* onconf ::= ON CONFLICT resolvetype */ +{yymsp[-2].minor.yy394 = yymsp[0].minor.yy394;} + break; + case 77: /* resolvetype ::= IGNORE */ +{yymsp[0].minor.yy394 = OE_Ignore;} + break; + case 78: /* resolvetype ::= REPLACE */ + case 172: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==172); +{yymsp[0].minor.yy394 = OE_Replace;} + break; + case 79: /* cmd ::= DROP TABLE ifexists fullname */ +{ + sqlite3DropTable(pParse, yymsp[0].minor.yy131, 0, yymsp[-1].minor.yy394); +} + break; + case 82: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ +{ + sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy322, yymsp[0].minor.yy47, yymsp[-7].minor.yy394, yymsp[-5].minor.yy394); +} + break; + case 83: /* cmd ::= DROP VIEW ifexists fullname */ +{ + sqlite3DropTable(pParse, yymsp[0].minor.yy131, 1, yymsp[-1].minor.yy394); +} + break; + case 84: /* cmd ::= select */ +{ + SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0}; + sqlite3Select(pParse, yymsp[0].minor.yy47, &dest); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy47); +} + break; + case 85: /* select ::= WITH wqlist selectnowith */ +{yymsp[-2].minor.yy47 = attachWithToSelect(pParse,yymsp[0].minor.yy47,yymsp[-1].minor.yy521);} + break; + case 86: /* select ::= WITH RECURSIVE wqlist selectnowith */ +{yymsp[-3].minor.yy47 = attachWithToSelect(pParse,yymsp[0].minor.yy47,yymsp[-1].minor.yy521);} + break; + case 87: /* select ::= selectnowith */ +{ + Select *p = yymsp[0].minor.yy47; + if( p ){ + parserDoubleLinkSelect(pParse, p); + } +} + break; + case 88: /* selectnowith ::= selectnowith multiselect_op oneselect */ +{ + Select *pRhs = yymsp[0].minor.yy47; + Select *pLhs = yymsp[-2].minor.yy47; + if( pRhs && pRhs->pPrior ){ + SrcList *pFrom; + Token x; + x.n = 0; + parserDoubleLinkSelect(pParse, pRhs); + pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0); + pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0); + } + if( pRhs ){ + pRhs->op = (u8)yymsp[-1].minor.yy394; + pRhs->pPrior = pLhs; + if( ALWAYS(pLhs) ) pLhs->selFlags &= ~SF_MultiValue; + pRhs->selFlags &= ~SF_MultiValue; + if( yymsp[-1].minor.yy394!=TK_ALL ) pParse->hasCompound = 1; + }else{ + sqlite3SelectDelete(pParse->db, pLhs); + } + yymsp[-2].minor.yy47 = pRhs; +} + break; + case 89: /* multiselect_op ::= UNION */ + case 91: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==91); +{yymsp[0].minor.yy394 = yymsp[0].major; /*A-overwrites-OP*/} + break; + case 90: /* multiselect_op ::= UNION ALL */ +{yymsp[-1].minor.yy394 = TK_ALL;} + break; + case 92: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ +{ + yymsp[-8].minor.yy47 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy322,yymsp[-5].minor.yy131,yymsp[-4].minor.yy528,yymsp[-3].minor.yy322,yymsp[-2].minor.yy528,yymsp[-1].minor.yy322,yymsp[-7].minor.yy394,yymsp[0].minor.yy528); +} + break; + case 93: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ +{ + yymsp[-9].minor.yy47 = sqlite3SelectNew(pParse,yymsp[-7].minor.yy322,yymsp[-6].minor.yy131,yymsp[-5].minor.yy528,yymsp[-4].minor.yy322,yymsp[-3].minor.yy528,yymsp[-1].minor.yy322,yymsp[-8].minor.yy394,yymsp[0].minor.yy528); + if( yymsp[-9].minor.yy47 ){ + yymsp[-9].minor.yy47->pWinDefn = yymsp[-2].minor.yy41; + }else{ + sqlite3WindowListDelete(pParse->db, yymsp[-2].minor.yy41); + } +} + break; + case 94: /* values ::= VALUES LP nexprlist RP */ +{ + yymsp[-3].minor.yy47 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy322,0,0,0,0,0,SF_Values,0); +} + break; + case 95: /* values ::= values COMMA LP nexprlist RP */ +{ + Select *pRight, *pLeft = yymsp[-4].minor.yy47; + pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy322,0,0,0,0,0,SF_Values|SF_MultiValue,0); + if( ALWAYS(pLeft) ) pLeft->selFlags &= ~SF_MultiValue; + if( pRight ){ + pRight->op = TK_ALL; + pRight->pPrior = pLeft; + yymsp[-4].minor.yy47 = pRight; + }else{ + yymsp[-4].minor.yy47 = pLeft; + } +} + break; + case 96: /* distinct ::= DISTINCT */ +{yymsp[0].minor.yy394 = SF_Distinct;} + break; + case 97: /* distinct ::= ALL */ +{yymsp[0].minor.yy394 = SF_All;} + break; + case 99: /* sclp ::= */ + case 132: /* orderby_opt ::= */ yytestcase(yyruleno==132); + case 142: /* groupby_opt ::= */ yytestcase(yyruleno==142); + case 230: /* exprlist ::= */ yytestcase(yyruleno==230); + case 233: /* paren_exprlist ::= */ yytestcase(yyruleno==233); + case 238: /* eidlist_opt ::= */ yytestcase(yyruleno==238); +{yymsp[1].minor.yy322 = 0;} + break; + case 100: /* selcollist ::= sclp scanpt expr scanpt as */ +{ + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy322, yymsp[-2].minor.yy528); + if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy322, &yymsp[0].minor.yy0, 1); + sqlite3ExprListSetSpan(pParse,yymsp[-4].minor.yy322,yymsp[-3].minor.yy522,yymsp[-1].minor.yy522); +} + break; + case 101: /* selcollist ::= sclp scanpt STAR */ +{ + Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0); + sqlite3ExprSetErrorOffset(p, (int)(yymsp[0].minor.yy0.z - pParse->zTail)); + yymsp[-2].minor.yy322 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy322, p); +} + break; + case 102: /* selcollist ::= sclp scanpt nm DOT STAR */ +{ + Expr *pRight, *pLeft, *pDot; + pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); + sqlite3ExprSetErrorOffset(pRight, (int)(yymsp[0].minor.yy0.z - pParse->zTail)); + pLeft = tokenExpr(pParse, TK_ID, yymsp[-2].minor.yy0); + pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322, pDot); +} + break; + case 103: /* as ::= AS nm */ + case 115: /* dbnm ::= DOT nm */ yytestcase(yyruleno==115); + case 254: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==254); + case 255: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==255); +{yymsp[-1].minor.yy0 = yymsp[0].minor.yy0;} + break; + case 105: /* from ::= */ + case 108: /* stl_prefix ::= */ yytestcase(yyruleno==108); +{yymsp[1].minor.yy131 = 0;} + break; + case 106: /* from ::= FROM seltablist */ +{ + yymsp[-1].minor.yy131 = yymsp[0].minor.yy131; + sqlite3SrcListShiftJoinType(pParse,yymsp[-1].minor.yy131); +} + break; + case 107: /* stl_prefix ::= seltablist joinop */ +{ + if( ALWAYS(yymsp[-1].minor.yy131 && yymsp[-1].minor.yy131->nSrc>0) ) yymsp[-1].minor.yy131->a[yymsp[-1].minor.yy131->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy394; +} + break; + case 109: /* seltablist ::= stl_prefix nm dbnm as on_using */ +{ + yymsp[-4].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-4].minor.yy131,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy561); +} + break; + case 110: /* seltablist ::= stl_prefix nm dbnm as indexed_by on_using */ +{ + yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,0,&yymsp[0].minor.yy561); + sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy131, &yymsp[-1].minor.yy0); +} + break; + case 111: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */ +{ + yymsp[-7].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-7].minor.yy131,&yymsp[-6].minor.yy0,&yymsp[-5].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy561); + sqlite3SrcListFuncArgs(pParse, yymsp[-7].minor.yy131, yymsp[-3].minor.yy322); +} + break; + case 112: /* seltablist ::= stl_prefix LP select RP as on_using */ +{ + yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,0,0,&yymsp[-1].minor.yy0,yymsp[-3].minor.yy47,&yymsp[0].minor.yy561); + } + break; + case 113: /* seltablist ::= stl_prefix LP seltablist RP as on_using */ +{ + if( yymsp[-5].minor.yy131==0 && yymsp[-1].minor.yy0.n==0 && yymsp[0].minor.yy561.pOn==0 && yymsp[0].minor.yy561.pUsing==0 ){ + yymsp[-5].minor.yy131 = yymsp[-3].minor.yy131; + }else if( ALWAYS(yymsp[-3].minor.yy131!=0) && yymsp[-3].minor.yy131->nSrc==1 ){ + yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,0,0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy561); + if( yymsp[-5].minor.yy131 ){ + SrcItem *pNew = &yymsp[-5].minor.yy131->a[yymsp[-5].minor.yy131->nSrc-1]; + SrcItem *pOld = yymsp[-3].minor.yy131->a; + pNew->zName = pOld->zName; + pNew->zDatabase = pOld->zDatabase; + pNew->pSelect = pOld->pSelect; + if( pNew->pSelect && (pNew->pSelect->selFlags & SF_NestedFrom)!=0 ){ + pNew->fg.isNestedFrom = 1; + } + if( pOld->fg.isTabFunc ){ + pNew->u1.pFuncArg = pOld->u1.pFuncArg; + pOld->u1.pFuncArg = 0; + pOld->fg.isTabFunc = 0; + pNew->fg.isTabFunc = 1; + } + pOld->zName = pOld->zDatabase = 0; + pOld->pSelect = 0; + } + sqlite3SrcListDelete(pParse->db, yymsp[-3].minor.yy131); + }else{ + Select *pSubquery; + sqlite3SrcListShiftJoinType(pParse,yymsp[-3].minor.yy131); + pSubquery = sqlite3SelectNew(pParse,0,yymsp[-3].minor.yy131,0,0,0,0,SF_NestedFrom,0); + yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,0,0,&yymsp[-1].minor.yy0,pSubquery,&yymsp[0].minor.yy561); + } + } + break; + case 114: /* dbnm ::= */ + case 129: /* indexed_opt ::= */ yytestcase(yyruleno==129); +{yymsp[1].minor.yy0.z=0; yymsp[1].minor.yy0.n=0;} + break; + case 116: /* fullname ::= nm */ +{ + yylhsminor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); + if( IN_RENAME_OBJECT && yylhsminor.yy131 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy131->a[0].zName, &yymsp[0].minor.yy0); +} + yymsp[0].minor.yy131 = yylhsminor.yy131; + break; + case 117: /* fullname ::= nm DOT nm */ +{ + yylhsminor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); + if( IN_RENAME_OBJECT && yylhsminor.yy131 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy131->a[0].zName, &yymsp[0].minor.yy0); +} + yymsp[-2].minor.yy131 = yylhsminor.yy131; + break; + case 118: /* xfullname ::= nm */ +{yymsp[0].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); /*A-overwrites-X*/} + break; + case 119: /* xfullname ::= nm DOT nm */ +{yymsp[-2].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/} + break; + case 120: /* xfullname ::= nm DOT nm AS nm */ +{ + yymsp[-4].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); /*A-overwrites-X*/ + if( yymsp[-4].minor.yy131 ) yymsp[-4].minor.yy131->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); +} + break; + case 121: /* xfullname ::= nm AS nm */ +{ + yymsp[-2].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); /*A-overwrites-X*/ + if( yymsp[-2].minor.yy131 ) yymsp[-2].minor.yy131->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); +} + break; + case 122: /* joinop ::= COMMA|JOIN */ +{ yymsp[0].minor.yy394 = JT_INNER; } + break; + case 123: /* joinop ::= JOIN_KW JOIN */ +{yymsp[-1].minor.yy394 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/} + break; + case 124: /* joinop ::= JOIN_KW nm JOIN */ +{yymsp[-2].minor.yy394 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/} + break; + case 125: /* joinop ::= JOIN_KW nm nm JOIN */ +{yymsp[-3].minor.yy394 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/} + break; + case 126: /* on_using ::= ON expr */ +{yymsp[-1].minor.yy561.pOn = yymsp[0].minor.yy528; yymsp[-1].minor.yy561.pUsing = 0;} + break; + case 127: /* on_using ::= USING LP idlist RP */ +{yymsp[-3].minor.yy561.pOn = 0; yymsp[-3].minor.yy561.pUsing = yymsp[-1].minor.yy254;} + break; + case 128: /* on_using ::= */ +{yymsp[1].minor.yy561.pOn = 0; yymsp[1].minor.yy561.pUsing = 0;} + break; + case 130: /* indexed_by ::= INDEXED BY nm */ +{yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;} + break; + case 131: /* indexed_by ::= NOT INDEXED */ +{yymsp[-1].minor.yy0.z=0; yymsp[-1].minor.yy0.n=1;} + break; + case 133: /* orderby_opt ::= ORDER BY sortlist */ + case 143: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==143); +{yymsp[-2].minor.yy322 = yymsp[0].minor.yy322;} + break; + case 134: /* sortlist ::= sortlist COMMA expr sortorder nulls */ +{ + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322,yymsp[-2].minor.yy528); + sqlite3ExprListSetSortOrder(yymsp[-4].minor.yy322,yymsp[-1].minor.yy394,yymsp[0].minor.yy394); +} + break; + case 135: /* sortlist ::= expr sortorder nulls */ +{ + yymsp[-2].minor.yy322 = sqlite3ExprListAppend(pParse,0,yymsp[-2].minor.yy528); /*A-overwrites-Y*/ + sqlite3ExprListSetSortOrder(yymsp[-2].minor.yy322,yymsp[-1].minor.yy394,yymsp[0].minor.yy394); +} + break; + case 136: /* sortorder ::= ASC */ +{yymsp[0].minor.yy394 = SQLITE_SO_ASC;} + break; + case 137: /* sortorder ::= DESC */ +{yymsp[0].minor.yy394 = SQLITE_SO_DESC;} + break; + case 138: /* sortorder ::= */ + case 141: /* nulls ::= */ yytestcase(yyruleno==141); +{yymsp[1].minor.yy394 = SQLITE_SO_UNDEFINED;} + break; + case 139: /* nulls ::= NULLS FIRST */ +{yymsp[-1].minor.yy394 = SQLITE_SO_ASC;} + break; + case 140: /* nulls ::= NULLS LAST */ +{yymsp[-1].minor.yy394 = SQLITE_SO_DESC;} + break; + case 144: /* having_opt ::= */ + case 146: /* limit_opt ::= */ yytestcase(yyruleno==146); + case 151: /* where_opt ::= */ yytestcase(yyruleno==151); + case 153: /* where_opt_ret ::= */ yytestcase(yyruleno==153); + case 228: /* case_else ::= */ yytestcase(yyruleno==228); + case 229: /* case_operand ::= */ yytestcase(yyruleno==229); + case 248: /* vinto ::= */ yytestcase(yyruleno==248); +{yymsp[1].minor.yy528 = 0;} + break; + case 145: /* having_opt ::= HAVING expr */ + case 152: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==152); + case 154: /* where_opt_ret ::= WHERE expr */ yytestcase(yyruleno==154); + case 227: /* case_else ::= ELSE expr */ yytestcase(yyruleno==227); + case 247: /* vinto ::= INTO expr */ yytestcase(yyruleno==247); +{yymsp[-1].minor.yy528 = yymsp[0].minor.yy528;} + break; + case 147: /* limit_opt ::= LIMIT expr */ +{yymsp[-1].minor.yy528 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy528,0);} + break; + case 148: /* limit_opt ::= LIMIT expr OFFSET expr */ +{yymsp[-3].minor.yy528 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[-2].minor.yy528,yymsp[0].minor.yy528);} + break; + case 149: /* limit_opt ::= LIMIT expr COMMA expr */ +{yymsp[-3].minor.yy528 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy528,yymsp[-2].minor.yy528);} + break; + case 150: /* cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ +{ + sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy131, &yymsp[-1].minor.yy0); + sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy131,yymsp[0].minor.yy528,0,0); +} + break; + case 155: /* where_opt_ret ::= RETURNING selcollist */ +{sqlite3AddReturning(pParse,yymsp[0].minor.yy322); yymsp[-1].minor.yy528 = 0;} + break; + case 156: /* where_opt_ret ::= WHERE expr RETURNING selcollist */ +{sqlite3AddReturning(pParse,yymsp[0].minor.yy322); yymsp[-3].minor.yy528 = yymsp[-2].minor.yy528;} + break; + case 157: /* cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ +{ + sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy131, &yymsp[-4].minor.yy0); + sqlite3ExprListCheckLength(pParse,yymsp[-2].minor.yy322,"set list"); + if( yymsp[-1].minor.yy131 ){ + SrcList *pFromClause = yymsp[-1].minor.yy131; + if( pFromClause->nSrc>1 ){ + Select *pSubquery; + Token as; + pSubquery = sqlite3SelectNew(pParse,0,pFromClause,0,0,0,0,SF_NestedFrom,0); + as.n = 0; + as.z = 0; + pFromClause = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); + } + yymsp[-5].minor.yy131 = sqlite3SrcListAppendList(pParse, yymsp[-5].minor.yy131, pFromClause); + } + sqlite3Update(pParse,yymsp[-5].minor.yy131,yymsp[-2].minor.yy322,yymsp[0].minor.yy528,yymsp[-6].minor.yy394,0,0,0); +} + break; + case 158: /* setlist ::= setlist COMMA nm EQ expr */ +{ + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy322, yymsp[0].minor.yy528); + sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy322, &yymsp[-2].minor.yy0, 1); +} + break; + case 159: /* setlist ::= setlist COMMA LP idlist RP EQ expr */ +{ + yymsp[-6].minor.yy322 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy322, yymsp[-3].minor.yy254, yymsp[0].minor.yy528); +} + break; + case 160: /* setlist ::= nm EQ expr */ +{ + yylhsminor.yy322 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy528); + sqlite3ExprListSetName(pParse, yylhsminor.yy322, &yymsp[-2].minor.yy0, 1); +} + yymsp[-2].minor.yy322 = yylhsminor.yy322; + break; + case 161: /* setlist ::= LP idlist RP EQ expr */ +{ + yymsp[-4].minor.yy322 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy254, yymsp[0].minor.yy528); +} + break; + case 162: /* cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ +{ + sqlite3Insert(pParse, yymsp[-3].minor.yy131, yymsp[-1].minor.yy47, yymsp[-2].minor.yy254, yymsp[-5].minor.yy394, yymsp[0].minor.yy444); +} + break; + case 163: /* cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ +{ + sqlite3Insert(pParse, yymsp[-4].minor.yy131, 0, yymsp[-3].minor.yy254, yymsp[-6].minor.yy394, 0); +} + break; + case 164: /* upsert ::= */ +{ yymsp[1].minor.yy444 = 0; } + break; + case 165: /* upsert ::= RETURNING selcollist */ +{ yymsp[-1].minor.yy444 = 0; sqlite3AddReturning(pParse,yymsp[0].minor.yy322); } + break; + case 166: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ +{ yymsp[-11].minor.yy444 = sqlite3UpsertNew(pParse->db,yymsp[-8].minor.yy322,yymsp[-6].minor.yy528,yymsp[-2].minor.yy322,yymsp[-1].minor.yy528,yymsp[0].minor.yy444);} + break; + case 167: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ +{ yymsp[-8].minor.yy444 = sqlite3UpsertNew(pParse->db,yymsp[-5].minor.yy322,yymsp[-3].minor.yy528,0,0,yymsp[0].minor.yy444); } + break; + case 168: /* upsert ::= ON CONFLICT DO NOTHING returning */ +{ yymsp[-4].minor.yy444 = sqlite3UpsertNew(pParse->db,0,0,0,0,0); } + break; + case 169: /* upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ +{ yymsp[-7].minor.yy444 = sqlite3UpsertNew(pParse->db,0,0,yymsp[-2].minor.yy322,yymsp[-1].minor.yy528,0);} + break; + case 170: /* returning ::= RETURNING selcollist */ +{sqlite3AddReturning(pParse,yymsp[0].minor.yy322);} + break; + case 173: /* idlist_opt ::= */ +{yymsp[1].minor.yy254 = 0;} + break; + case 174: /* idlist_opt ::= LP idlist RP */ +{yymsp[-2].minor.yy254 = yymsp[-1].minor.yy254;} + break; + case 175: /* idlist ::= idlist COMMA nm */ +{yymsp[-2].minor.yy254 = sqlite3IdListAppend(pParse,yymsp[-2].minor.yy254,&yymsp[0].minor.yy0);} + break; + case 176: /* idlist ::= nm */ +{yymsp[0].minor.yy254 = sqlite3IdListAppend(pParse,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/} + break; + case 177: /* expr ::= LP expr RP */ +{yymsp[-2].minor.yy528 = yymsp[-1].minor.yy528;} + break; + case 178: /* expr ::= ID|INDEXED|JOIN_KW */ +{yymsp[0].minor.yy528=tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/} + break; + case 179: /* expr ::= nm DOT nm */ +{ + Expr *temp1 = tokenExpr(pParse,TK_ID,yymsp[-2].minor.yy0); + Expr *temp2 = tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); + yylhsminor.yy528 = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); +} + yymsp[-2].minor.yy528 = yylhsminor.yy528; + break; + case 180: /* expr ::= nm DOT nm DOT nm */ +{ + Expr *temp1 = tokenExpr(pParse,TK_ID,yymsp[-4].minor.yy0); + Expr *temp2 = tokenExpr(pParse,TK_ID,yymsp[-2].minor.yy0); + Expr *temp3 = tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); + Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3); + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenRemap(pParse, 0, temp1); + } + yylhsminor.yy528 = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); +} + yymsp[-4].minor.yy528 = yylhsminor.yy528; + break; + case 181: /* term ::= NULL|FLOAT|BLOB */ + case 182: /* term ::= STRING */ yytestcase(yyruleno==182); +{yymsp[0].minor.yy528=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/} + break; + case 183: /* term ::= INTEGER */ +{ + yylhsminor.yy528 = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1); + if( yylhsminor.yy528 ) yylhsminor.yy528->w.iOfst = (int)(yymsp[0].minor.yy0.z - pParse->zTail); +} + yymsp[0].minor.yy528 = yylhsminor.yy528; + break; + case 184: /* expr ::= VARIABLE */ +{ + if( !(yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1])) ){ + u32 n = yymsp[0].minor.yy0.n; + yymsp[0].minor.yy528 = tokenExpr(pParse, TK_VARIABLE, yymsp[0].minor.yy0); + sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy528, n); + }else{ + /* When doing a nested parse, one can include terms in an expression + ** that look like this: #1 #2 ... These terms refer to registers + ** in the virtual machine. #N is the N-th register. */ + Token t = yymsp[0].minor.yy0; /*A-overwrites-X*/ + assert( t.n>=2 ); + if( pParse->nested==0 ){ + sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t); + yymsp[0].minor.yy528 = 0; + }else{ + yymsp[0].minor.yy528 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); + if( yymsp[0].minor.yy528 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy528->iTable); + } + } +} + break; + case 185: /* expr ::= expr COLLATE ID|STRING */ +{ + yymsp[-2].minor.yy528 = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy528, &yymsp[0].minor.yy0, 1); +} + break; + case 186: /* expr ::= CAST LP expr AS typetoken RP */ +{ + yymsp[-5].minor.yy528 = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1); + sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy528, yymsp[-3].minor.yy528, 0); +} + break; + case 187: /* expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP */ +{ + yylhsminor.yy528 = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy322, &yymsp[-4].minor.yy0, yymsp[-2].minor.yy394); +} + yymsp[-4].minor.yy528 = yylhsminor.yy528; + break; + case 188: /* expr ::= ID|INDEXED|JOIN_KW LP STAR RP */ +{ + yylhsminor.yy528 = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0, 0); +} + yymsp[-3].minor.yy528 = yylhsminor.yy528; + break; + case 189: /* expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP filter_over */ +{ + yylhsminor.yy528 = sqlite3ExprFunction(pParse, yymsp[-2].minor.yy322, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy394); + sqlite3WindowAttach(pParse, yylhsminor.yy528, yymsp[0].minor.yy41); +} + yymsp[-5].minor.yy528 = yylhsminor.yy528; + break; + case 190: /* expr ::= ID|INDEXED|JOIN_KW LP STAR RP filter_over */ +{ + yylhsminor.yy528 = sqlite3ExprFunction(pParse, 0, &yymsp[-4].minor.yy0, 0); + sqlite3WindowAttach(pParse, yylhsminor.yy528, yymsp[0].minor.yy41); +} + yymsp[-4].minor.yy528 = yylhsminor.yy528; + break; + case 191: /* term ::= CTIME_KW */ +{ + yylhsminor.yy528 = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0, 0); +} + yymsp[0].minor.yy528 = yylhsminor.yy528; + break; + case 192: /* expr ::= LP nexprlist COMMA expr RP */ +{ + ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy322, yymsp[-1].minor.yy528); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); + if( yymsp[-4].minor.yy528 ){ + yymsp[-4].minor.yy528->x.pList = pList; + if( ALWAYS(pList->nExpr) ){ + yymsp[-4].minor.yy528->flags |= pList->a[0].pExpr->flags & EP_Propagate; + } + }else{ + sqlite3ExprListDelete(pParse->db, pList); + } +} + break; + case 193: /* expr ::= expr AND expr */ +{yymsp[-2].minor.yy528=sqlite3ExprAnd(pParse,yymsp[-2].minor.yy528,yymsp[0].minor.yy528);} + break; + case 194: /* expr ::= expr OR expr */ + case 195: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==195); + case 196: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==196); + case 197: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==197); + case 198: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==198); + case 199: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==199); + case 200: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==200); +{yymsp[-2].minor.yy528=sqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy528,yymsp[0].minor.yy528);} + break; + case 201: /* likeop ::= NOT LIKE_KW|MATCH */ +{yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.n|=0x80000000; /*yymsp[-1].minor.yy0-overwrite-yymsp[0].minor.yy0*/} + break; + case 202: /* expr ::= expr likeop expr */ +{ + ExprList *pList; + int bNot = yymsp[-1].minor.yy0.n & 0x80000000; + yymsp[-1].minor.yy0.n &= 0x7fffffff; + pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy528); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy528); + yymsp[-2].minor.yy528 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); + if( bNot ) yymsp[-2].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-2].minor.yy528, 0); + if( yymsp[-2].minor.yy528 ) yymsp[-2].minor.yy528->flags |= EP_InfixFunc; +} + break; + case 203: /* expr ::= expr likeop expr ESCAPE expr */ +{ + ExprList *pList; + int bNot = yymsp[-3].minor.yy0.n & 0x80000000; + yymsp[-3].minor.yy0.n &= 0x7fffffff; + pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy528); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy528); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy528); + yymsp[-4].minor.yy528 = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0, 0); + if( bNot ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0); + if( yymsp[-4].minor.yy528 ) yymsp[-4].minor.yy528->flags |= EP_InfixFunc; +} + break; + case 204: /* expr ::= expr ISNULL|NOTNULL */ +{yymsp[-1].minor.yy528 = sqlite3PExpr(pParse,yymsp[0].major,yymsp[-1].minor.yy528,0);} + break; + case 205: /* expr ::= expr NOT NULL */ +{yymsp[-2].minor.yy528 = sqlite3PExpr(pParse,TK_NOTNULL,yymsp[-2].minor.yy528,0);} + break; + case 206: /* expr ::= expr IS expr */ +{ + yymsp[-2].minor.yy528 = sqlite3PExpr(pParse,TK_IS,yymsp[-2].minor.yy528,yymsp[0].minor.yy528); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-2].minor.yy528, TK_ISNULL); +} + break; + case 207: /* expr ::= expr IS NOT expr */ +{ + yymsp[-3].minor.yy528 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-3].minor.yy528,yymsp[0].minor.yy528); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-3].minor.yy528, TK_NOTNULL); +} + break; + case 208: /* expr ::= expr IS NOT DISTINCT FROM expr */ +{ + yymsp[-5].minor.yy528 = sqlite3PExpr(pParse,TK_IS,yymsp[-5].minor.yy528,yymsp[0].minor.yy528); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-5].minor.yy528, TK_ISNULL); +} + break; + case 209: /* expr ::= expr IS DISTINCT FROM expr */ +{ + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-4].minor.yy528,yymsp[0].minor.yy528); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-4].minor.yy528, TK_NOTNULL); +} + break; + case 210: /* expr ::= NOT expr */ + case 211: /* expr ::= BITNOT expr */ yytestcase(yyruleno==211); +{yymsp[-1].minor.yy528 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy528, 0);/*A-overwrites-B*/} + break; + case 212: /* expr ::= PLUS|MINUS expr */ +{ + yymsp[-1].minor.yy528 = sqlite3PExpr(pParse, yymsp[-1].major==TK_PLUS ? TK_UPLUS : TK_UMINUS, yymsp[0].minor.yy528, 0); + /*A-overwrites-B*/ +} + break; + case 213: /* expr ::= expr PTR expr */ +{ + ExprList *pList = sqlite3ExprListAppend(pParse, 0, yymsp[-2].minor.yy528); + pList = sqlite3ExprListAppend(pParse, pList, yymsp[0].minor.yy528); + yylhsminor.yy528 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); +} + yymsp[-2].minor.yy528 = yylhsminor.yy528; + break; + case 214: /* between_op ::= BETWEEN */ + case 217: /* in_op ::= IN */ yytestcase(yyruleno==217); +{yymsp[0].minor.yy394 = 0;} + break; + case 216: /* expr ::= expr between_op expr AND expr */ +{ + ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy528); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy528); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy528, 0); + if( yymsp[-4].minor.yy528 ){ + yymsp[-4].minor.yy528->x.pList = pList; + }else{ + sqlite3ExprListDelete(pParse->db, pList); + } + if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0); +} + break; + case 219: /* expr ::= expr in_op LP exprlist RP */ +{ + if( yymsp[-1].minor.yy322==0 ){ + /* Expressions of the form + ** + ** expr1 IN () + ** expr1 NOT IN () + ** + ** simplify to constants 0 (false) and 1 (true), respectively, + ** regardless of the value of expr1. + */ + sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy528); + yymsp[-4].minor.yy528 = sqlite3Expr(pParse->db, TK_STRING, yymsp[-3].minor.yy394 ? "true" : "false"); + if( yymsp[-4].minor.yy528 ) sqlite3ExprIdToTrueFalse(yymsp[-4].minor.yy528); + }else{ + Expr *pRHS = yymsp[-1].minor.yy322->a[0].pExpr; + if( yymsp[-1].minor.yy322->nExpr==1 && sqlite3ExprIsConstant(pRHS) && yymsp[-4].minor.yy528->op!=TK_VECTOR ){ + yymsp[-1].minor.yy322->a[0].pExpr = 0; + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy322); + pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_EQ, yymsp[-4].minor.yy528, pRHS); + }else if( yymsp[-1].minor.yy322->nExpr==1 && pRHS->op==TK_SELECT ){ + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy528, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy528, pRHS->x.pSelect); + pRHS->x.pSelect = 0; + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy322); + }else{ + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy528, 0); + if( yymsp[-4].minor.yy528==0 ){ + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy322); + }else if( yymsp[-4].minor.yy528->pLeft->op==TK_VECTOR ){ + int nExpr = yymsp[-4].minor.yy528->pLeft->x.pList->nExpr; + Select *pSelectRHS = sqlite3ExprListToValues(pParse, nExpr, yymsp[-1].minor.yy322); + if( pSelectRHS ){ + parserDoubleLinkSelect(pParse, pSelectRHS); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy528, pSelectRHS); + } + }else{ + yymsp[-4].minor.yy528->x.pList = yymsp[-1].minor.yy322; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy528); + } + } + if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0); + } + } + break; + case 220: /* expr ::= LP select RP */ +{ + yymsp[-2].minor.yy528 = sqlite3PExpr(pParse, TK_SELECT, 0, 0); + sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy528, yymsp[-1].minor.yy47); + } + break; + case 221: /* expr ::= expr in_op LP select RP */ +{ + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy528, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy528, yymsp[-1].minor.yy47); + if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0); + } + break; + case 222: /* expr ::= expr in_op nm dbnm paren_exprlist */ +{ + SrcList *pSrc = sqlite3SrcListAppend(pParse, 0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0); + Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0); + if( yymsp[0].minor.yy322 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy322); + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy528, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy528, pSelect); + if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0); + } + break; + case 223: /* expr ::= EXISTS LP select RP */ +{ + Expr *p; + p = yymsp[-3].minor.yy528 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); + sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy47); + } + break; + case 224: /* expr ::= CASE case_operand case_exprlist case_else END */ +{ + yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy528, 0); + if( yymsp[-4].minor.yy528 ){ + yymsp[-4].minor.yy528->x.pList = yymsp[-1].minor.yy528 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy322,yymsp[-1].minor.yy528) : yymsp[-2].minor.yy322; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy528); + }else{ + sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy322); + sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy528); + } +} + break; + case 225: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ +{ + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322, yymsp[-2].minor.yy528); + yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322, yymsp[0].minor.yy528); +} + break; + case 226: /* case_exprlist ::= WHEN expr THEN expr */ +{ + yymsp[-3].minor.yy322 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy528); + yymsp[-3].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy322, yymsp[0].minor.yy528); +} + break; + case 231: /* nexprlist ::= nexprlist COMMA expr */ +{yymsp[-2].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy322,yymsp[0].minor.yy528);} + break; + case 232: /* nexprlist ::= expr */ +{yymsp[0].minor.yy322 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy528); /*A-overwrites-Y*/} + break; + case 234: /* paren_exprlist ::= LP exprlist RP */ + case 239: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==239); +{yymsp[-2].minor.yy322 = yymsp[-1].minor.yy322;} + break; + case 235: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ +{ + sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, + sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy322, yymsp[-10].minor.yy394, + &yymsp[-11].minor.yy0, yymsp[0].minor.yy528, SQLITE_SO_ASC, yymsp[-8].minor.yy394, SQLITE_IDXTYPE_APPDEF); + if( IN_RENAME_OBJECT && pParse->pNewIndex ){ + sqlite3RenameTokenMap(pParse, pParse->pNewIndex->zName, &yymsp[-4].minor.yy0); + } +} + break; + case 236: /* uniqueflag ::= UNIQUE */ + case 278: /* raisetype ::= ABORT */ yytestcase(yyruleno==278); +{yymsp[0].minor.yy394 = OE_Abort;} + break; + case 237: /* uniqueflag ::= */ +{yymsp[1].minor.yy394 = OE_None;} + break; + case 240: /* eidlist ::= eidlist COMMA nm collate sortorder */ +{ + yymsp[-4].minor.yy322 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy322, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy394, yymsp[0].minor.yy394); +} + break; + case 241: /* eidlist ::= nm collate sortorder */ +{ + yymsp[-2].minor.yy322 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy394, yymsp[0].minor.yy394); /*A-overwrites-Y*/ +} + break; + case 244: /* cmd ::= DROP INDEX ifexists fullname */ +{sqlite3DropIndex(pParse, yymsp[0].minor.yy131, yymsp[-1].minor.yy394);} + break; + case 245: /* cmd ::= VACUUM vinto */ +{sqlite3Vacuum(pParse,0,yymsp[0].minor.yy528);} + break; + case 246: /* cmd ::= VACUUM nm vinto */ +{sqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy528);} + break; + case 249: /* cmd ::= PRAGMA nm dbnm */ +{sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);} + break; + case 250: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ +{sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);} + break; + case 251: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ +{sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);} + break; + case 252: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ +{sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);} + break; + case 253: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ +{sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);} + break; + case 256: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ +{ + Token all; + all.z = yymsp[-3].minor.yy0.z; + all.n = (int)(yymsp[0].minor.yy0.z - yymsp[-3].minor.yy0.z) + yymsp[0].minor.yy0.n; + sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy33, &all); +} + break; + case 257: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ +{ + sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy394, yymsp[-4].minor.yy180.a, yymsp[-4].minor.yy180.b, yymsp[-2].minor.yy131, yymsp[0].minor.yy528, yymsp[-10].minor.yy394, yymsp[-8].minor.yy394); + yymsp[-10].minor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); /*A-overwrites-T*/ +} + break; + case 258: /* trigger_time ::= BEFORE|AFTER */ +{ yymsp[0].minor.yy394 = yymsp[0].major; /*A-overwrites-X*/ } + break; + case 259: /* trigger_time ::= INSTEAD OF */ +{ yymsp[-1].minor.yy394 = TK_INSTEAD;} + break; + case 260: /* trigger_time ::= */ +{ yymsp[1].minor.yy394 = TK_BEFORE; } + break; + case 261: /* trigger_event ::= DELETE|INSERT */ + case 262: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==262); +{yymsp[0].minor.yy180.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy180.b = 0;} + break; + case 263: /* trigger_event ::= UPDATE OF idlist */ +{yymsp[-2].minor.yy180.a = TK_UPDATE; yymsp[-2].minor.yy180.b = yymsp[0].minor.yy254;} + break; + case 264: /* when_clause ::= */ + case 283: /* key_opt ::= */ yytestcase(yyruleno==283); +{ yymsp[1].minor.yy528 = 0; } + break; + case 265: /* when_clause ::= WHEN expr */ + case 284: /* key_opt ::= KEY expr */ yytestcase(yyruleno==284); +{ yymsp[-1].minor.yy528 = yymsp[0].minor.yy528; } + break; + case 266: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ +{ + assert( yymsp[-2].minor.yy33!=0 ); + yymsp[-2].minor.yy33->pLast->pNext = yymsp[-1].minor.yy33; + yymsp[-2].minor.yy33->pLast = yymsp[-1].minor.yy33; +} + break; + case 267: /* trigger_cmd_list ::= trigger_cmd SEMI */ +{ + assert( yymsp[-1].minor.yy33!=0 ); + yymsp[-1].minor.yy33->pLast = yymsp[-1].minor.yy33; +} + break; + case 268: /* trnm ::= nm DOT nm */ +{ + yymsp[-2].minor.yy0 = yymsp[0].minor.yy0; + sqlite3ErrorMsg(pParse, + "qualified table names are not allowed on INSERT, UPDATE, and DELETE " + "statements within triggers"); +} + break; + case 269: /* tridxby ::= INDEXED BY nm */ +{ + sqlite3ErrorMsg(pParse, + "the INDEXED BY clause is not allowed on UPDATE or DELETE statements " + "within triggers"); +} + break; + case 270: /* tridxby ::= NOT INDEXED */ +{ + sqlite3ErrorMsg(pParse, + "the NOT INDEXED clause is not allowed on UPDATE or DELETE statements " + "within triggers"); +} + break; + case 271: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ +{yylhsminor.yy33 = sqlite3TriggerUpdateStep(pParse, &yymsp[-6].minor.yy0, yymsp[-2].minor.yy131, yymsp[-3].minor.yy322, yymsp[-1].minor.yy528, yymsp[-7].minor.yy394, yymsp[-8].minor.yy0.z, yymsp[0].minor.yy522);} + yymsp[-8].minor.yy33 = yylhsminor.yy33; + break; + case 272: /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ +{ + yylhsminor.yy33 = sqlite3TriggerInsertStep(pParse,&yymsp[-4].minor.yy0,yymsp[-3].minor.yy254,yymsp[-2].minor.yy47,yymsp[-6].minor.yy394,yymsp[-1].minor.yy444,yymsp[-7].minor.yy522,yymsp[0].minor.yy522);/*yylhsminor.yy33-overwrites-yymsp[-6].minor.yy394*/ +} + yymsp[-7].minor.yy33 = yylhsminor.yy33; + break; + case 273: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ +{yylhsminor.yy33 = sqlite3TriggerDeleteStep(pParse, &yymsp[-3].minor.yy0, yymsp[-1].minor.yy528, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy522);} + yymsp[-5].minor.yy33 = yylhsminor.yy33; + break; + case 274: /* trigger_cmd ::= scanpt select scanpt */ +{yylhsminor.yy33 = sqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy47, yymsp[-2].minor.yy522, yymsp[0].minor.yy522); /*yylhsminor.yy33-overwrites-yymsp[-1].minor.yy47*/} + yymsp[-2].minor.yy33 = yylhsminor.yy33; + break; + case 275: /* expr ::= RAISE LP IGNORE RP */ +{ + yymsp[-3].minor.yy528 = sqlite3PExpr(pParse, TK_RAISE, 0, 0); + if( yymsp[-3].minor.yy528 ){ + yymsp[-3].minor.yy528->affExpr = OE_Ignore; + } +} + break; + case 276: /* expr ::= RAISE LP raisetype COMMA nm RP */ +{ + yymsp[-5].minor.yy528 = sqlite3ExprAlloc(pParse->db, TK_RAISE, &yymsp[-1].minor.yy0, 1); + if( yymsp[-5].minor.yy528 ) { + yymsp[-5].minor.yy528->affExpr = (char)yymsp[-3].minor.yy394; + } +} + break; + case 277: /* raisetype ::= ROLLBACK */ +{yymsp[0].minor.yy394 = OE_Rollback;} + break; + case 279: /* raisetype ::= FAIL */ +{yymsp[0].minor.yy394 = OE_Fail;} + break; + case 280: /* cmd ::= DROP TRIGGER ifexists fullname */ +{ + sqlite3DropTrigger(pParse,yymsp[0].minor.yy131,yymsp[-1].minor.yy394); +} + break; + case 281: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ +{ + sqlite3Attach(pParse, yymsp[-3].minor.yy528, yymsp[-1].minor.yy528, yymsp[0].minor.yy528); +} + break; + case 282: /* cmd ::= DETACH database_kw_opt expr */ +{ + sqlite3Detach(pParse, yymsp[0].minor.yy528); +} + break; + case 285: /* cmd ::= REINDEX */ +{sqlite3Reindex(pParse, 0, 0);} + break; + case 286: /* cmd ::= REINDEX nm dbnm */ +{sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} + break; + case 287: /* cmd ::= ANALYZE */ +{sqlite3Analyze(pParse, 0, 0);} + break; + case 288: /* cmd ::= ANALYZE nm dbnm */ +{sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} + break; + case 289: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ +{ + sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy131,&yymsp[0].minor.yy0); +} + break; + case 290: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ +{ + yymsp[-1].minor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-1].minor.yy0.z) + pParse->sLastToken.n; + sqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0); +} + break; + case 291: /* cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ +{ + sqlite3AlterDropColumn(pParse, yymsp[-3].minor.yy131, &yymsp[0].minor.yy0); +} + break; + case 292: /* add_column_fullname ::= fullname */ +{ + disableLookaside(pParse); + sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy131); +} + break; + case 293: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ +{ + sqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy131, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0); +} + break; + case 294: /* cmd ::= create_vtab */ +{sqlite3VtabFinishParse(pParse,0);} + break; + case 295: /* cmd ::= create_vtab LP vtabarglist RP */ +{sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);} + break; + case 296: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ +{ + sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy394); +} + break; + case 297: /* vtabarg ::= */ +{sqlite3VtabArgInit(pParse);} + break; + case 298: /* vtabargtoken ::= ANY */ + case 299: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==299); + case 300: /* lp ::= LP */ yytestcase(yyruleno==300); +{sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);} + break; + case 301: /* with ::= WITH wqlist */ + case 302: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==302); +{ sqlite3WithPush(pParse, yymsp[0].minor.yy521, 1); } + break; + case 303: /* wqas ::= AS */ +{yymsp[0].minor.yy516 = M10d_Any;} + break; + case 304: /* wqas ::= AS MATERIALIZED */ +{yymsp[-1].minor.yy516 = M10d_Yes;} + break; + case 305: /* wqas ::= AS NOT MATERIALIZED */ +{yymsp[-2].minor.yy516 = M10d_No;} + break; + case 306: /* wqitem ::= nm eidlist_opt wqas LP select RP */ +{ + yymsp[-5].minor.yy385 = sqlite3CteNew(pParse, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy322, yymsp[-1].minor.yy47, yymsp[-3].minor.yy516); /*A-overwrites-X*/ +} + break; + case 307: /* wqlist ::= wqitem */ +{ + yymsp[0].minor.yy521 = sqlite3WithAdd(pParse, 0, yymsp[0].minor.yy385); /*A-overwrites-X*/ +} + break; + case 308: /* wqlist ::= wqlist COMMA wqitem */ +{ + yymsp[-2].minor.yy521 = sqlite3WithAdd(pParse, yymsp[-2].minor.yy521, yymsp[0].minor.yy385); +} + break; + case 309: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */ +{ + assert( yymsp[0].minor.yy41!=0 ); + sqlite3WindowChain(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy41); + yymsp[0].minor.yy41->pNextWin = yymsp[-2].minor.yy41; + yylhsminor.yy41 = yymsp[0].minor.yy41; +} + yymsp[-2].minor.yy41 = yylhsminor.yy41; + break; + case 310: /* windowdefn ::= nm AS LP window RP */ +{ + if( ALWAYS(yymsp[-1].minor.yy41) ){ + yymsp[-1].minor.yy41->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n); + } + yylhsminor.yy41 = yymsp[-1].minor.yy41; +} + yymsp[-4].minor.yy41 = yylhsminor.yy41; + break; + case 311: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */ +{ + yymsp[-4].minor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy322, yymsp[-1].minor.yy322, 0); +} + break; + case 312: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ +{ + yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy322, yymsp[-1].minor.yy322, &yymsp[-5].minor.yy0); +} + yymsp[-5].minor.yy41 = yylhsminor.yy41; + break; + case 313: /* window ::= ORDER BY sortlist frame_opt */ +{ + yymsp[-3].minor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, yymsp[-1].minor.yy322, 0); +} + break; + case 314: /* window ::= nm ORDER BY sortlist frame_opt */ +{ + yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, yymsp[-1].minor.yy322, &yymsp[-4].minor.yy0); +} + yymsp[-4].minor.yy41 = yylhsminor.yy41; + break; + case 315: /* window ::= nm frame_opt */ +{ + yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, 0, &yymsp[-1].minor.yy0); +} + yymsp[-1].minor.yy41 = yylhsminor.yy41; + break; + case 316: /* frame_opt ::= */ +{ + yymsp[1].minor.yy41 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0); +} + break; + case 317: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ +{ + yylhsminor.yy41 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy394, yymsp[-1].minor.yy595.eType, yymsp[-1].minor.yy595.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy516); +} + yymsp[-2].minor.yy41 = yylhsminor.yy41; + break; + case 318: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ +{ + yylhsminor.yy41 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy394, yymsp[-3].minor.yy595.eType, yymsp[-3].minor.yy595.pExpr, yymsp[-1].minor.yy595.eType, yymsp[-1].minor.yy595.pExpr, yymsp[0].minor.yy516); +} + yymsp[-5].minor.yy41 = yylhsminor.yy41; + break; + case 320: /* frame_bound_s ::= frame_bound */ + case 322: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==322); +{yylhsminor.yy595 = yymsp[0].minor.yy595;} + yymsp[0].minor.yy595 = yylhsminor.yy595; + break; + case 321: /* frame_bound_s ::= UNBOUNDED PRECEDING */ + case 323: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==323); + case 325: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==325); +{yylhsminor.yy595.eType = yymsp[-1].major; yylhsminor.yy595.pExpr = 0;} + yymsp[-1].minor.yy595 = yylhsminor.yy595; + break; + case 324: /* frame_bound ::= expr PRECEDING|FOLLOWING */ +{yylhsminor.yy595.eType = yymsp[0].major; yylhsminor.yy595.pExpr = yymsp[-1].minor.yy528;} + yymsp[-1].minor.yy595 = yylhsminor.yy595; + break; + case 326: /* frame_exclude_opt ::= */ +{yymsp[1].minor.yy516 = 0;} + break; + case 327: /* frame_exclude_opt ::= EXCLUDE frame_exclude */ +{yymsp[-1].minor.yy516 = yymsp[0].minor.yy516;} + break; + case 328: /* frame_exclude ::= NO OTHERS */ + case 329: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==329); +{yymsp[-1].minor.yy516 = yymsp[-1].major; /*A-overwrites-X*/} + break; + case 330: /* frame_exclude ::= GROUP|TIES */ +{yymsp[0].minor.yy516 = yymsp[0].major; /*A-overwrites-X*/} + break; + case 331: /* window_clause ::= WINDOW windowdefn_list */ +{ yymsp[-1].minor.yy41 = yymsp[0].minor.yy41; } + break; + case 332: /* filter_over ::= filter_clause over_clause */ +{ + if( yymsp[0].minor.yy41 ){ + yymsp[0].minor.yy41->pFilter = yymsp[-1].minor.yy528; + }else{ + sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy528); + } + yylhsminor.yy41 = yymsp[0].minor.yy41; +} + yymsp[-1].minor.yy41 = yylhsminor.yy41; + break; + case 333: /* filter_over ::= over_clause */ +{ + yylhsminor.yy41 = yymsp[0].minor.yy41; +} + yymsp[0].minor.yy41 = yylhsminor.yy41; + break; + case 334: /* filter_over ::= filter_clause */ +{ + yylhsminor.yy41 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); + if( yylhsminor.yy41 ){ + yylhsminor.yy41->eFrmType = TK_FILTER; + yylhsminor.yy41->pFilter = yymsp[0].minor.yy528; + }else{ + sqlite3ExprDelete(pParse->db, yymsp[0].minor.yy528); + } +} + yymsp[0].minor.yy41 = yylhsminor.yy41; + break; + case 335: /* over_clause ::= OVER LP window RP */ +{ + yymsp[-3].minor.yy41 = yymsp[-1].minor.yy41; + assert( yymsp[-3].minor.yy41!=0 ); +} + break; + case 336: /* over_clause ::= OVER nm */ +{ + yymsp[-1].minor.yy41 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); + if( yymsp[-1].minor.yy41 ){ + yymsp[-1].minor.yy41->zName = sqlite3DbStrNDup(pParse->db, yymsp[0].minor.yy0.z, yymsp[0].minor.yy0.n); + } +} + break; + case 337: /* filter_clause ::= FILTER LP WHERE expr RP */ +{ yymsp[-4].minor.yy528 = yymsp[-1].minor.yy528; } + break; + default: + /* (338) input ::= cmdlist */ yytestcase(yyruleno==338); + /* (339) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==339); + /* (340) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=340); + /* (341) ecmd ::= SEMI */ yytestcase(yyruleno==341); + /* (342) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==342); + /* (343) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=343); + /* (344) trans_opt ::= */ yytestcase(yyruleno==344); + /* (345) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==345); + /* (346) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==346); + /* (347) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==347); + /* (348) savepoint_opt ::= */ yytestcase(yyruleno==348); + /* (349) cmd ::= create_table create_table_args */ yytestcase(yyruleno==349); + /* (350) table_option_set ::= table_option (OPTIMIZED OUT) */ assert(yyruleno!=350); + /* (351) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==351); + /* (352) columnlist ::= columnname carglist */ yytestcase(yyruleno==352); + /* (353) nm ::= ID|INDEXED|JOIN_KW */ yytestcase(yyruleno==353); + /* (354) nm ::= STRING */ yytestcase(yyruleno==354); + /* (355) typetoken ::= typename */ yytestcase(yyruleno==355); + /* (356) typename ::= ID|STRING */ yytestcase(yyruleno==356); + /* (357) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=357); + /* (358) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=358); + /* (359) carglist ::= carglist ccons */ yytestcase(yyruleno==359); + /* (360) carglist ::= */ yytestcase(yyruleno==360); + /* (361) ccons ::= NULL onconf */ yytestcase(yyruleno==361); + /* (362) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==362); + /* (363) ccons ::= AS generated */ yytestcase(yyruleno==363); + /* (364) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==364); + /* (365) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==365); + /* (366) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=366); + /* (367) tconscomma ::= */ yytestcase(yyruleno==367); + /* (368) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=368); + /* (369) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=369); + /* (370) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=370); + /* (371) oneselect ::= values */ yytestcase(yyruleno==371); + /* (372) sclp ::= selcollist COMMA */ yytestcase(yyruleno==372); + /* (373) as ::= ID|STRING */ yytestcase(yyruleno==373); + /* (374) indexed_opt ::= indexed_by (OPTIMIZED OUT) */ assert(yyruleno!=374); + /* (375) returning ::= */ yytestcase(yyruleno==375); + /* (376) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=376); + /* (377) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==377); + /* (378) case_operand ::= expr */ yytestcase(yyruleno==378); + /* (379) exprlist ::= nexprlist */ yytestcase(yyruleno==379); + /* (380) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=380); + /* (381) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=381); + /* (382) nmnum ::= ON */ yytestcase(yyruleno==382); + /* (383) nmnum ::= DELETE */ yytestcase(yyruleno==383); + /* (384) nmnum ::= DEFAULT */ yytestcase(yyruleno==384); + /* (385) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==385); + /* (386) foreach_clause ::= */ yytestcase(yyruleno==386); + /* (387) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==387); + /* (388) trnm ::= nm */ yytestcase(yyruleno==388); + /* (389) tridxby ::= */ yytestcase(yyruleno==389); + /* (390) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==390); + /* (391) database_kw_opt ::= */ yytestcase(yyruleno==391); + /* (392) kwcolumn_opt ::= */ yytestcase(yyruleno==392); + /* (393) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==393); + /* (394) vtabarglist ::= vtabarg */ yytestcase(yyruleno==394); + /* (395) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==395); + /* (396) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==396); + /* (397) anylist ::= */ yytestcase(yyruleno==397); + /* (398) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==398); + /* (399) anylist ::= anylist ANY */ yytestcase(yyruleno==399); + /* (400) with ::= */ yytestcase(yyruleno==400); + /* (401) windowdefn_list ::= windowdefn (OPTIMIZED OUT) */ assert(yyruleno!=401); + /* (402) window ::= frame_opt (OPTIMIZED OUT) */ assert(yyruleno!=402); + break; +/********** End reduce actions ************************************************/ + }; + assert( yyruleno<sizeof(yyRuleInfoLhs)/sizeof(yyRuleInfoLhs[0]) ); + yygoto = yyRuleInfoLhs[yyruleno]; + yysize = yyRuleInfoNRhs[yyruleno]; + yyact = yy_find_reduce_action(yymsp[yysize].stateno,(YYCODETYPE)yygoto); + + /* There are no SHIFTREDUCE actions on nonterminals because the table + ** generator has simplified them to pure REDUCE actions. */ + assert( !(yyact>YY_MAX_SHIFT && yyact<=YY_MAX_SHIFTREDUCE) ); + + /* It is not possible for a REDUCE to be followed by an error */ + assert( yyact!=YY_ERROR_ACTION ); + + yymsp += yysize+1; + yypParser->yytos = yymsp; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yyTraceShift(yypParser, yyact, "... then shift"); + return yyact; +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + sqlite3ParserARG_FETCH + sqlite3ParserCTX_FETCH +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +/************ Begin %parse_failure code ***************************************/ +/************ End %parse_failure code *****************************************/ + sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */ + sqlite3ParserCTX_STORE +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + sqlite3ParserTOKENTYPE yyminor /* The minor type of the error token */ +){ + sqlite3ParserARG_FETCH + sqlite3ParserCTX_FETCH +#define TOKEN yyminor +/************ Begin %syntax_error code ****************************************/ + + UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */ + if( TOKEN.z[0] ){ + sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN); + }else{ + sqlite3ErrorMsg(pParse, "incomplete input"); + } +/************ End %syntax_error code ******************************************/ + sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */ + sqlite3ParserCTX_STORE +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + sqlite3ParserARG_FETCH + sqlite3ParserCTX_FETCH +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + assert( yypParser->yytos==yypParser->yystack ); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +/*********** Begin %parse_accept code *****************************************/ +/*********** End %parse_accept code *******************************************/ + sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */ + sqlite3ParserCTX_STORE +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "sqlite3ParserAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +** <ul> +** <li> A pointer to the parser (an opaque structure.) +** <li> The major token number. +** <li> The minor token number. +** <li> An option argument of a grammar-specified type. +** </ul> +** +** Outputs: +** None. +*/ +SQLITE_PRIVATE void sqlite3Parser( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + sqlite3ParserTOKENTYPE yyminor /* The value for the token */ + sqlite3ParserARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + YYACTIONTYPE yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser = (yyParser*)yyp; /* The parser */ + sqlite3ParserCTX_FETCH + sqlite3ParserARG_STORE + + assert( yypParser->yytos!=0 ); +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + + yyact = yypParser->yytos->stateno; +#ifndef NDEBUG + if( yyTraceFILE ){ + if( yyact < YY_MIN_REDUCE ){ + fprintf(yyTraceFILE,"%sInput '%s' in state %d\n", + yyTracePrompt,yyTokenName[yymajor],yyact); + }else{ + fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n", + yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE); + } + } +#endif + + while(1){ /* Exit by "break" */ + assert( yypParser->yytos>=yypParser->yystack ); + assert( yyact==yypParser->yytos->stateno ); + yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact); + if( yyact >= YY_MIN_REDUCE ){ + unsigned int yyruleno = yyact - YY_MIN_REDUCE; /* Reduce by this rule */ +#ifndef NDEBUG + assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); + if( yyTraceFILE ){ + int yysize = yyRuleInfoNRhs[yyruleno]; + if( yysize ){ + fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n", + yyTracePrompt, + yyruleno, yyRuleName[yyruleno], + yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action", + yypParser->yytos[yysize].stateno); + }else{ + fprintf(yyTraceFILE, "%sReduce %d [%s]%s.\n", + yyTracePrompt, yyruleno, yyRuleName[yyruleno], + yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action"); + } + } +#endif /* NDEBUG */ + + /* Check that the stack is large enough to grow by a single entry + ** if the RHS of the rule is empty. This ensures that there is room + ** enough on the stack to push the LHS value */ + if( yyRuleInfoNRhs[yyruleno]==0 ){ +#ifdef YYTRACKMAXSTACKDEPTH + if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == + (int)(yypParser->yytos - yypParser->yystack)); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>=yypParser->yystackEnd ){ + yyStackOverflow(yypParser); + break; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){ + if( yyGrowStack(yypParser) ){ + yyStackOverflow(yypParser); + break; + } + } +#endif + } + yyact = yy_reduce(yypParser,yyruleno,yymajor,yyminor sqlite3ParserCTX_PARAM); + }else if( yyact <= YY_MAX_SHIFTREDUCE ){ + yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt--; +#endif + break; + }else if( yyact==YY_ACCEPT_ACTION ){ + yypParser->yytos--; + yy_accept(yypParser); + return; + }else{ + assert( yyact == YY_ERROR_ACTION ); + yyminorunion.yy0 = yyminor; +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminor); + } + yymx = yypParser->yytos->major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); + yymajor = YYNOCODE; + }else{ + while( yypParser->yytos > yypParser->yystack ){ + yyact = yy_find_reduce_action(yypParser->yytos->stateno, + YYERRORSYMBOL); + if( yyact<=YY_MAX_SHIFTREDUCE ) break; + yy_pop_parser_stack(yypParser); + } + if( yypParser->yytos <= yypParser->yystack || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; + if( yymajor==YYNOCODE ) break; + yyact = yypParser->yytos->stateno; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor, yyminor); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + break; +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor, yyminor); + } + yypParser->yyerrcnt = 3; + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + } + break; +#endif + } + } +#ifndef NDEBUG + if( yyTraceFILE ){ + yyStackEntry *i; + char cDiv = '['; + fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt); + for(i=&yypParser->yystack[1]; i<=yypParser->yytos; i++){ + fprintf(yyTraceFILE,"%c%s", cDiv, yyTokenName[i->major]); + cDiv = ' '; + } + fprintf(yyTraceFILE,"]\n"); + } +#endif + return; +} + +/* +** Return the fallback token corresponding to canonical token iToken, or +** 0 if iToken has no fallback. +*/ +SQLITE_PRIVATE int sqlite3ParserFallback(int iToken){ +#ifdef YYFALLBACK + assert( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) ); + return yyFallback[iToken]; +#else + (void)iToken; + return 0; +#endif +} + +/************** End of parse.c ***********************************************/ +/************** Begin file tokenize.c ****************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** An tokenizer for SQL +** +** This file contains C code that splits an SQL input string up into +** individual tokens and sends those tokens one-by-one over to the +** parser for analysis. +*/ +/* #include "sqliteInt.h" */ +/* #include <stdlib.h> */ + +/* Character classes for tokenizing +** +** In the sqlite3GetToken() function, a switch() on aiClass[c] is implemented +** using a lookup table, whereas a switch() directly on c uses a binary search. +** The lookup table is much faster. To maximize speed, and to ensure that +** a lookup table is used, all of the classes need to be small integers and +** all of them need to be used within the switch. +*/ +#define CC_X 0 /* The letter 'x', or start of BLOB literal */ +#define CC_KYWD0 1 /* First letter of a keyword */ +#define CC_KYWD 2 /* Alphabetics or '_'. Usable in a keyword */ +#define CC_DIGIT 3 /* Digits */ +#define CC_DOLLAR 4 /* '$' */ +#define CC_VARALPHA 5 /* '@', '#', ':'. Alphabetic SQL variables */ +#define CC_VARNUM 6 /* '?'. Numeric SQL variables */ +#define CC_SPACE 7 /* Space characters */ +#define CC_QUOTE 8 /* '"', '\'', or '`'. String literals, quoted ids */ +#define CC_QUOTE2 9 /* '['. [...] style quoted ids */ +#define CC_PIPE 10 /* '|'. Bitwise OR or concatenate */ +#define CC_MINUS 11 /* '-'. Minus or SQL-style comment */ +#define CC_LT 12 /* '<'. Part of < or <= or <> */ +#define CC_GT 13 /* '>'. Part of > or >= */ +#define CC_EQ 14 /* '='. Part of = or == */ +#define CC_BANG 15 /* '!'. Part of != */ +#define CC_SLASH 16 /* '/'. / or c-style comment */ +#define CC_LP 17 /* '(' */ +#define CC_RP 18 /* ')' */ +#define CC_SEMI 19 /* ';' */ +#define CC_PLUS 20 /* '+' */ +#define CC_STAR 21 /* '*' */ +#define CC_PERCENT 22 /* '%' */ +#define CC_COMMA 23 /* ',' */ +#define CC_AND 24 /* '&' */ +#define CC_TILDA 25 /* '~' */ +#define CC_DOT 26 /* '.' */ +#define CC_ID 27 /* unicode characters usable in IDs */ +#define CC_ILLEGAL 28 /* Illegal character */ +#define CC_NUL 29 /* 0x00 */ +#define CC_BOM 30 /* First byte of UTF8 BOM: 0xEF 0xBB 0xBF */ + +static const unsigned char aiClass[] = { +#ifdef SQLITE_ASCII +/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ +/* 0x */ 29, 28, 28, 28, 28, 28, 28, 28, 28, 7, 7, 28, 7, 7, 28, 28, +/* 1x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +/* 2x */ 7, 15, 8, 5, 4, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16, +/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 19, 12, 14, 13, 6, +/* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 9, 28, 28, 28, 2, +/* 6x */ 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 28, 10, 28, 25, 28, +/* 8x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* 9x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Ax */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Cx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Dx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Ex */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 30, +/* Fx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27 +#endif +#ifdef SQLITE_EBCDIC +/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ +/* 0x */ 29, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 7, 7, 28, 28, +/* 1x */ 28, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +/* 2x */ 28, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +/* 3x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +/* 4x */ 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 26, 12, 17, 20, 10, +/* 5x */ 24, 28, 28, 28, 28, 28, 28, 28, 28, 28, 15, 4, 21, 18, 19, 28, +/* 6x */ 11, 16, 28, 28, 28, 28, 28, 28, 28, 28, 28, 23, 22, 2, 13, 6, +/* 7x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 8, 5, 5, 5, 8, 14, 8, +/* 8x */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* 9x */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* Ax */ 28, 25, 1, 1, 1, 1, 1, 0, 2, 2, 28, 28, 28, 28, 28, 28, +/* Bx */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 9, 28, 28, 28, 28, 28, +/* Cx */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* Dx */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* Ex */ 28, 28, 1, 1, 1, 1, 1, 0, 2, 2, 28, 28, 28, 28, 28, 28, +/* Fx */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 28, 28, 28, 28, 28, 28, +#endif +}; + +/* +** The charMap() macro maps alphabetic characters (only) into their +** lower-case ASCII equivalent. On ASCII machines, this is just +** an upper-to-lower case map. On EBCDIC machines we also need +** to adjust the encoding. The mapping is only valid for alphabetics +** which are the only characters for which this feature is used. +** +** Used by keywordhash.h +*/ +#ifdef SQLITE_ASCII +# define charMap(X) sqlite3UpperToLower[(unsigned char)X] +#endif +#ifdef SQLITE_EBCDIC +# define charMap(X) ebcdicToAscii[(unsigned char)X] +const unsigned char ebcdicToAscii[] = { +/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 0, 0, /* 6x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7x */ + 0, 97, 98, 99,100,101,102,103,104,105, 0, 0, 0, 0, 0, 0, /* 8x */ + 0,106,107,108,109,110,111,112,113,114, 0, 0, 0, 0, 0, 0, /* 9x */ + 0, 0,115,116,117,118,119,120,121,122, 0, 0, 0, 0, 0, 0, /* Ax */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Bx */ + 0, 97, 98, 99,100,101,102,103,104,105, 0, 0, 0, 0, 0, 0, /* Cx */ + 0,106,107,108,109,110,111,112,113,114, 0, 0, 0, 0, 0, 0, /* Dx */ + 0, 0,115,116,117,118,119,120,121,122, 0, 0, 0, 0, 0, 0, /* Ex */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Fx */ +}; +#endif + +/* +** The sqlite3KeywordCode function looks up an identifier to determine if +** it is a keyword. If it is a keyword, the token code of that keyword is +** returned. If the input is not a keyword, TK_ID is returned. +** +** The implementation of this routine was generated by a program, +** mkkeywordhash.c, located in the tool subdirectory of the distribution. +** The output of the mkkeywordhash.c program is written into a file +** named keywordhash.h and then included into this source file by +** the #include below. +*/ +/************** Include keywordhash.h in the middle of tokenize.c ************/ +/************** Begin file keywordhash.h *************************************/ +/***** This file contains automatically generated code ****** +** +** The code in this file has been automatically generated by +** +** sqlite/tool/mkkeywordhash.c +** +** The code in this file implements a function that determines whether +** or not a given identifier is really an SQL keyword. The same thing +** might be implemented more directly using a hand-written hash table. +** But by using this automatically generated code, the size of the code +** is substantially reduced. This is important for embedded applications +** on platforms with limited memory. +*/ +/* Hash score: 231 */ +/* zKWText[] encodes 1007 bytes of keyword text in 667 bytes */ +/* REINDEXEDESCAPEACHECKEYBEFOREIGNOREGEXPLAINSTEADDATABASELECT */ +/* ABLEFTHENDEFERRABLELSEXCLUDELETEMPORARYISNULLSAVEPOINTERSECT */ +/* IESNOTNULLIKEXCEPTRANSACTIONATURALTERAISEXCLUSIVEXISTS */ +/* CONSTRAINTOFFSETRIGGERANGENERATEDETACHAVINGLOBEGINNEREFERENCES */ +/* UNIQUERYWITHOUTERELEASEATTACHBETWEENOTHINGROUPSCASCADEFAULT */ +/* CASECOLLATECREATECURRENT_DATEIMMEDIATEJOINSERTMATCHPLANALYZE */ +/* PRAGMATERIALIZEDEFERREDISTINCTUPDATEVALUESVIRTUALWAYSWHENWHERE */ +/* CURSIVEABORTAFTERENAMEANDROPARTITIONAUTOINCREMENTCASTCOLUMN */ +/* COMMITCONFLICTCROSSCURRENT_TIMESTAMPRECEDINGFAILASTFILTER */ +/* EPLACEFIRSTFOLLOWINGFROMFULLIMITIFORDERESTRICTOTHERSOVER */ +/* ETURNINGRIGHTROLLBACKROWSUNBOUNDEDUNIONUSINGVACUUMVIEWINDOWBY */ +/* INITIALLYPRIMARY */ +static const char zKWText[666] = { + 'R','E','I','N','D','E','X','E','D','E','S','C','A','P','E','A','C','H', + 'E','C','K','E','Y','B','E','F','O','R','E','I','G','N','O','R','E','G', + 'E','X','P','L','A','I','N','S','T','E','A','D','D','A','T','A','B','A', + 'S','E','L','E','C','T','A','B','L','E','F','T','H','E','N','D','E','F', + 'E','R','R','A','B','L','E','L','S','E','X','C','L','U','D','E','L','E', + 'T','E','M','P','O','R','A','R','Y','I','S','N','U','L','L','S','A','V', + 'E','P','O','I','N','T','E','R','S','E','C','T','I','E','S','N','O','T', + 'N','U','L','L','I','K','E','X','C','E','P','T','R','A','N','S','A','C', + 'T','I','O','N','A','T','U','R','A','L','T','E','R','A','I','S','E','X', + 'C','L','U','S','I','V','E','X','I','S','T','S','C','O','N','S','T','R', + 'A','I','N','T','O','F','F','S','E','T','R','I','G','G','E','R','A','N', + 'G','E','N','E','R','A','T','E','D','E','T','A','C','H','A','V','I','N', + 'G','L','O','B','E','G','I','N','N','E','R','E','F','E','R','E','N','C', + 'E','S','U','N','I','Q','U','E','R','Y','W','I','T','H','O','U','T','E', + 'R','E','L','E','A','S','E','A','T','T','A','C','H','B','E','T','W','E', + 'E','N','O','T','H','I','N','G','R','O','U','P','S','C','A','S','C','A', + 'D','E','F','A','U','L','T','C','A','S','E','C','O','L','L','A','T','E', + 'C','R','E','A','T','E','C','U','R','R','E','N','T','_','D','A','T','E', + 'I','M','M','E','D','I','A','T','E','J','O','I','N','S','E','R','T','M', + 'A','T','C','H','P','L','A','N','A','L','Y','Z','E','P','R','A','G','M', + 'A','T','E','R','I','A','L','I','Z','E','D','E','F','E','R','R','E','D', + 'I','S','T','I','N','C','T','U','P','D','A','T','E','V','A','L','U','E', + 'S','V','I','R','T','U','A','L','W','A','Y','S','W','H','E','N','W','H', + 'E','R','E','C','U','R','S','I','V','E','A','B','O','R','T','A','F','T', + 'E','R','E','N','A','M','E','A','N','D','R','O','P','A','R','T','I','T', + 'I','O','N','A','U','T','O','I','N','C','R','E','M','E','N','T','C','A', + 'S','T','C','O','L','U','M','N','C','O','M','M','I','T','C','O','N','F', + 'L','I','C','T','C','R','O','S','S','C','U','R','R','E','N','T','_','T', + 'I','M','E','S','T','A','M','P','R','E','C','E','D','I','N','G','F','A', + 'I','L','A','S','T','F','I','L','T','E','R','E','P','L','A','C','E','F', + 'I','R','S','T','F','O','L','L','O','W','I','N','G','F','R','O','M','F', + 'U','L','L','I','M','I','T','I','F','O','R','D','E','R','E','S','T','R', + 'I','C','T','O','T','H','E','R','S','O','V','E','R','E','T','U','R','N', + 'I','N','G','R','I','G','H','T','R','O','L','L','B','A','C','K','R','O', + 'W','S','U','N','B','O','U','N','D','E','D','U','N','I','O','N','U','S', + 'I','N','G','V','A','C','U','U','M','V','I','E','W','I','N','D','O','W', + 'B','Y','I','N','I','T','I','A','L','L','Y','P','R','I','M','A','R','Y', +}; +/* aKWHash[i] is the hash value for the i-th keyword */ +static const unsigned char aKWHash[127] = { + 84, 92, 134, 82, 105, 29, 0, 0, 94, 0, 85, 72, 0, + 53, 35, 86, 15, 0, 42, 97, 54, 89, 135, 19, 0, 0, + 140, 0, 40, 129, 0, 22, 107, 0, 9, 0, 0, 123, 80, + 0, 78, 6, 0, 65, 103, 147, 0, 136, 115, 0, 0, 48, + 0, 90, 24, 0, 17, 0, 27, 70, 23, 26, 5, 60, 142, + 110, 122, 0, 73, 91, 71, 145, 61, 120, 74, 0, 49, 0, + 11, 41, 0, 113, 0, 0, 0, 109, 10, 111, 116, 125, 14, + 50, 124, 0, 100, 0, 18, 121, 144, 56, 130, 139, 88, 83, + 37, 30, 126, 0, 0, 108, 51, 131, 128, 0, 34, 0, 0, + 132, 0, 98, 38, 39, 0, 20, 45, 117, 93, +}; +/* aKWNext[] forms the hash collision chain. If aKWHash[i]==0 +** then the i-th keyword has no more hash collisions. Otherwise, +** the next keyword with the same hash is aKWHash[i]-1. */ +static const unsigned char aKWNext[148] = {0, + 0, 0, 0, 0, 4, 0, 43, 0, 0, 106, 114, 0, 0, + 0, 2, 0, 0, 143, 0, 0, 0, 13, 0, 0, 0, 0, + 141, 0, 0, 119, 52, 0, 0, 137, 12, 0, 0, 62, 0, + 138, 0, 133, 0, 0, 36, 0, 0, 28, 77, 0, 0, 0, + 0, 59, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 69, 0, 0, 0, 0, 0, 146, 3, 0, 58, 0, 1, + 75, 0, 0, 0, 31, 0, 0, 0, 0, 0, 127, 0, 104, + 0, 64, 66, 63, 0, 0, 0, 0, 0, 46, 0, 16, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 101, 0, + 112, 21, 7, 67, 0, 79, 96, 118, 0, 0, 68, 0, 0, + 99, 44, 0, 55, 0, 76, 0, 95, 32, 33, 57, 25, 0, + 102, 0, 0, 87, +}; +/* aKWLen[i] is the length (in bytes) of the i-th keyword */ +static const unsigned char aKWLen[148] = {0, + 7, 7, 5, 4, 6, 4, 5, 3, 6, 7, 3, 6, 6, + 7, 7, 3, 8, 2, 6, 5, 4, 4, 3, 10, 4, 7, + 6, 9, 4, 2, 6, 5, 9, 9, 4, 7, 3, 2, 4, + 4, 6, 11, 6, 2, 7, 5, 5, 9, 6, 10, 4, 6, + 2, 3, 7, 5, 9, 6, 6, 4, 5, 5, 10, 6, 5, + 7, 4, 5, 7, 6, 7, 7, 6, 5, 7, 3, 7, 4, + 7, 6, 12, 9, 4, 6, 5, 4, 7, 6, 12, 8, 8, + 2, 6, 6, 7, 6, 4, 5, 9, 5, 5, 6, 3, 4, + 9, 13, 2, 2, 4, 6, 6, 8, 5, 17, 12, 7, 9, + 4, 4, 6, 7, 5, 9, 4, 4, 5, 2, 5, 8, 6, + 4, 9, 5, 8, 4, 3, 9, 5, 5, 6, 4, 6, 2, + 2, 9, 3, 7, +}; +/* aKWOffset[i] is the index into zKWText[] of the start of +** the text for the i-th keyword. */ +static const unsigned short int aKWOffset[148] = {0, + 0, 2, 2, 8, 9, 14, 16, 20, 23, 25, 25, 29, 33, + 36, 41, 46, 48, 53, 54, 59, 62, 65, 67, 69, 78, 81, + 86, 90, 90, 94, 99, 101, 105, 111, 119, 123, 123, 123, 126, + 129, 132, 137, 142, 146, 147, 152, 156, 160, 168, 174, 181, 184, + 184, 187, 189, 195, 198, 206, 211, 216, 219, 222, 226, 236, 239, + 244, 244, 248, 252, 259, 265, 271, 277, 277, 283, 284, 288, 295, + 299, 306, 312, 324, 333, 335, 341, 346, 348, 355, 359, 370, 377, + 378, 385, 391, 397, 402, 408, 412, 415, 424, 429, 433, 439, 441, + 444, 453, 455, 457, 466, 470, 476, 482, 490, 495, 495, 495, 511, + 520, 523, 527, 532, 539, 544, 553, 557, 560, 565, 567, 571, 579, + 585, 588, 597, 602, 610, 610, 614, 623, 628, 633, 639, 642, 645, + 648, 650, 655, 659, +}; +/* aKWCode[i] is the parser symbol code for the i-th keyword */ +static const unsigned char aKWCode[148] = {0, + TK_REINDEX, TK_INDEXED, TK_INDEX, TK_DESC, TK_ESCAPE, + TK_EACH, TK_CHECK, TK_KEY, TK_BEFORE, TK_FOREIGN, + TK_FOR, TK_IGNORE, TK_LIKE_KW, TK_EXPLAIN, TK_INSTEAD, + TK_ADD, TK_DATABASE, TK_AS, TK_SELECT, TK_TABLE, + TK_JOIN_KW, TK_THEN, TK_END, TK_DEFERRABLE, TK_ELSE, + TK_EXCLUDE, TK_DELETE, TK_TEMP, TK_TEMP, TK_OR, + TK_ISNULL, TK_NULLS, TK_SAVEPOINT, TK_INTERSECT, TK_TIES, + TK_NOTNULL, TK_NOT, TK_NO, TK_NULL, TK_LIKE_KW, + TK_EXCEPT, TK_TRANSACTION,TK_ACTION, TK_ON, TK_JOIN_KW, + TK_ALTER, TK_RAISE, TK_EXCLUSIVE, TK_EXISTS, TK_CONSTRAINT, + TK_INTO, TK_OFFSET, TK_OF, TK_SET, TK_TRIGGER, + TK_RANGE, TK_GENERATED, TK_DETACH, TK_HAVING, TK_LIKE_KW, + TK_BEGIN, TK_JOIN_KW, TK_REFERENCES, TK_UNIQUE, TK_QUERY, + TK_WITHOUT, TK_WITH, TK_JOIN_KW, TK_RELEASE, TK_ATTACH, + TK_BETWEEN, TK_NOTHING, TK_GROUPS, TK_GROUP, TK_CASCADE, + TK_ASC, TK_DEFAULT, TK_CASE, TK_COLLATE, TK_CREATE, + TK_CTIME_KW, TK_IMMEDIATE, TK_JOIN, TK_INSERT, TK_MATCH, + TK_PLAN, TK_ANALYZE, TK_PRAGMA, TK_MATERIALIZED, TK_DEFERRED, + TK_DISTINCT, TK_IS, TK_UPDATE, TK_VALUES, TK_VIRTUAL, + TK_ALWAYS, TK_WHEN, TK_WHERE, TK_RECURSIVE, TK_ABORT, + TK_AFTER, TK_RENAME, TK_AND, TK_DROP, TK_PARTITION, + TK_AUTOINCR, TK_TO, TK_IN, TK_CAST, TK_COLUMNKW, + TK_COMMIT, TK_CONFLICT, TK_JOIN_KW, TK_CTIME_KW, TK_CTIME_KW, + TK_CURRENT, TK_PRECEDING, TK_FAIL, TK_LAST, TK_FILTER, + TK_REPLACE, TK_FIRST, TK_FOLLOWING, TK_FROM, TK_JOIN_KW, + TK_LIMIT, TK_IF, TK_ORDER, TK_RESTRICT, TK_OTHERS, + TK_OVER, TK_RETURNING, TK_JOIN_KW, TK_ROLLBACK, TK_ROWS, + TK_ROW, TK_UNBOUNDED, TK_UNION, TK_USING, TK_VACUUM, + TK_VIEW, TK_WINDOW, TK_DO, TK_BY, TK_INITIALLY, + TK_ALL, TK_PRIMARY, +}; +/* Hash table decoded: +** 0: INSERT +** 1: IS +** 2: ROLLBACK TRIGGER +** 3: IMMEDIATE +** 4: PARTITION +** 5: TEMP +** 6: +** 7: +** 8: VALUES WITHOUT +** 9: +** 10: MATCH +** 11: NOTHING +** 12: +** 13: OF +** 14: TIES IGNORE +** 15: PLAN +** 16: INSTEAD INDEXED +** 17: +** 18: TRANSACTION RIGHT +** 19: WHEN +** 20: SET HAVING +** 21: MATERIALIZED IF +** 22: ROWS +** 23: SELECT +** 24: +** 25: +** 26: VACUUM SAVEPOINT +** 27: +** 28: LIKE UNION VIRTUAL REFERENCES +** 29: RESTRICT +** 30: +** 31: THEN REGEXP +** 32: TO +** 33: +** 34: BEFORE +** 35: +** 36: +** 37: FOLLOWING COLLATE CASCADE +** 38: CREATE +** 39: +** 40: CASE REINDEX +** 41: EACH +** 42: +** 43: QUERY +** 44: AND ADD +** 45: PRIMARY ANALYZE +** 46: +** 47: ROW ASC DETACH +** 48: CURRENT_TIME CURRENT_DATE +** 49: +** 50: +** 51: EXCLUSIVE TEMPORARY +** 52: +** 53: DEFERRED +** 54: DEFERRABLE +** 55: +** 56: DATABASE +** 57: +** 58: DELETE VIEW GENERATED +** 59: ATTACH +** 60: END +** 61: EXCLUDE +** 62: ESCAPE DESC +** 63: GLOB +** 64: WINDOW ELSE +** 65: COLUMN +** 66: FIRST +** 67: +** 68: GROUPS ALL +** 69: DISTINCT DROP KEY +** 70: BETWEEN +** 71: INITIALLY +** 72: BEGIN +** 73: FILTER CHECK ACTION +** 74: GROUP INDEX +** 75: +** 76: EXISTS DEFAULT +** 77: +** 78: FOR CURRENT_TIMESTAMP +** 79: EXCEPT +** 80: +** 81: CROSS +** 82: +** 83: +** 84: +** 85: CAST +** 86: FOREIGN AUTOINCREMENT +** 87: COMMIT +** 88: CURRENT AFTER ALTER +** 89: FULL FAIL CONFLICT +** 90: EXPLAIN +** 91: CONSTRAINT +** 92: FROM ALWAYS +** 93: +** 94: ABORT +** 95: +** 96: AS DO +** 97: REPLACE WITH RELEASE +** 98: BY RENAME +** 99: RANGE RAISE +** 100: OTHERS +** 101: USING NULLS +** 102: PRAGMA +** 103: JOIN ISNULL OFFSET +** 104: NOT +** 105: OR LAST LEFT +** 106: LIMIT +** 107: +** 108: +** 109: IN +** 110: INTO +** 111: OVER RECURSIVE +** 112: ORDER OUTER +** 113: +** 114: INTERSECT UNBOUNDED +** 115: +** 116: +** 117: RETURNING ON +** 118: +** 119: WHERE +** 120: NO INNER +** 121: NULL +** 122: +** 123: TABLE +** 124: NATURAL NOTNULL +** 125: PRECEDING +** 126: UPDATE UNIQUE +*/ +/* Check to see if z[0..n-1] is a keyword. If it is, write the +** parser symbol code for that keyword into *pType. Always +** return the integer n (the length of the token). */ +static int keywordCode(const char *z, int n, int *pType){ + int i, j; + const char *zKW; + assert( n>=2 ); + i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n*1) % 127; + for(i=(int)aKWHash[i]; i>0; i=aKWNext[i]){ + if( aKWLen[i]!=n ) continue; + zKW = &zKWText[aKWOffset[i]]; +#ifdef SQLITE_ASCII + if( (z[0]&~0x20)!=zKW[0] ) continue; + if( (z[1]&~0x20)!=zKW[1] ) continue; + j = 2; + while( j<n && (z[j]&~0x20)==zKW[j] ){ j++; } +#endif +#ifdef SQLITE_EBCDIC + if( toupper(z[0])!=zKW[0] ) continue; + if( toupper(z[1])!=zKW[1] ) continue; + j = 2; + while( j<n && toupper(z[j])==zKW[j] ){ j++; } +#endif + if( j<n ) continue; + testcase( i==1 ); /* REINDEX */ + testcase( i==2 ); /* INDEXED */ + testcase( i==3 ); /* INDEX */ + testcase( i==4 ); /* DESC */ + testcase( i==5 ); /* ESCAPE */ + testcase( i==6 ); /* EACH */ + testcase( i==7 ); /* CHECK */ + testcase( i==8 ); /* KEY */ + testcase( i==9 ); /* BEFORE */ + testcase( i==10 ); /* FOREIGN */ + testcase( i==11 ); /* FOR */ + testcase( i==12 ); /* IGNORE */ + testcase( i==13 ); /* REGEXP */ + testcase( i==14 ); /* EXPLAIN */ + testcase( i==15 ); /* INSTEAD */ + testcase( i==16 ); /* ADD */ + testcase( i==17 ); /* DATABASE */ + testcase( i==18 ); /* AS */ + testcase( i==19 ); /* SELECT */ + testcase( i==20 ); /* TABLE */ + testcase( i==21 ); /* LEFT */ + testcase( i==22 ); /* THEN */ + testcase( i==23 ); /* END */ + testcase( i==24 ); /* DEFERRABLE */ + testcase( i==25 ); /* ELSE */ + testcase( i==26 ); /* EXCLUDE */ + testcase( i==27 ); /* DELETE */ + testcase( i==28 ); /* TEMPORARY */ + testcase( i==29 ); /* TEMP */ + testcase( i==30 ); /* OR */ + testcase( i==31 ); /* ISNULL */ + testcase( i==32 ); /* NULLS */ + testcase( i==33 ); /* SAVEPOINT */ + testcase( i==34 ); /* INTERSECT */ + testcase( i==35 ); /* TIES */ + testcase( i==36 ); /* NOTNULL */ + testcase( i==37 ); /* NOT */ + testcase( i==38 ); /* NO */ + testcase( i==39 ); /* NULL */ + testcase( i==40 ); /* LIKE */ + testcase( i==41 ); /* EXCEPT */ + testcase( i==42 ); /* TRANSACTION */ + testcase( i==43 ); /* ACTION */ + testcase( i==44 ); /* ON */ + testcase( i==45 ); /* NATURAL */ + testcase( i==46 ); /* ALTER */ + testcase( i==47 ); /* RAISE */ + testcase( i==48 ); /* EXCLUSIVE */ + testcase( i==49 ); /* EXISTS */ + testcase( i==50 ); /* CONSTRAINT */ + testcase( i==51 ); /* INTO */ + testcase( i==52 ); /* OFFSET */ + testcase( i==53 ); /* OF */ + testcase( i==54 ); /* SET */ + testcase( i==55 ); /* TRIGGER */ + testcase( i==56 ); /* RANGE */ + testcase( i==57 ); /* GENERATED */ + testcase( i==58 ); /* DETACH */ + testcase( i==59 ); /* HAVING */ + testcase( i==60 ); /* GLOB */ + testcase( i==61 ); /* BEGIN */ + testcase( i==62 ); /* INNER */ + testcase( i==63 ); /* REFERENCES */ + testcase( i==64 ); /* UNIQUE */ + testcase( i==65 ); /* QUERY */ + testcase( i==66 ); /* WITHOUT */ + testcase( i==67 ); /* WITH */ + testcase( i==68 ); /* OUTER */ + testcase( i==69 ); /* RELEASE */ + testcase( i==70 ); /* ATTACH */ + testcase( i==71 ); /* BETWEEN */ + testcase( i==72 ); /* NOTHING */ + testcase( i==73 ); /* GROUPS */ + testcase( i==74 ); /* GROUP */ + testcase( i==75 ); /* CASCADE */ + testcase( i==76 ); /* ASC */ + testcase( i==77 ); /* DEFAULT */ + testcase( i==78 ); /* CASE */ + testcase( i==79 ); /* COLLATE */ + testcase( i==80 ); /* CREATE */ + testcase( i==81 ); /* CURRENT_DATE */ + testcase( i==82 ); /* IMMEDIATE */ + testcase( i==83 ); /* JOIN */ + testcase( i==84 ); /* INSERT */ + testcase( i==85 ); /* MATCH */ + testcase( i==86 ); /* PLAN */ + testcase( i==87 ); /* ANALYZE */ + testcase( i==88 ); /* PRAGMA */ + testcase( i==89 ); /* MATERIALIZED */ + testcase( i==90 ); /* DEFERRED */ + testcase( i==91 ); /* DISTINCT */ + testcase( i==92 ); /* IS */ + testcase( i==93 ); /* UPDATE */ + testcase( i==94 ); /* VALUES */ + testcase( i==95 ); /* VIRTUAL */ + testcase( i==96 ); /* ALWAYS */ + testcase( i==97 ); /* WHEN */ + testcase( i==98 ); /* WHERE */ + testcase( i==99 ); /* RECURSIVE */ + testcase( i==100 ); /* ABORT */ + testcase( i==101 ); /* AFTER */ + testcase( i==102 ); /* RENAME */ + testcase( i==103 ); /* AND */ + testcase( i==104 ); /* DROP */ + testcase( i==105 ); /* PARTITION */ + testcase( i==106 ); /* AUTOINCREMENT */ + testcase( i==107 ); /* TO */ + testcase( i==108 ); /* IN */ + testcase( i==109 ); /* CAST */ + testcase( i==110 ); /* COLUMN */ + testcase( i==111 ); /* COMMIT */ + testcase( i==112 ); /* CONFLICT */ + testcase( i==113 ); /* CROSS */ + testcase( i==114 ); /* CURRENT_TIMESTAMP */ + testcase( i==115 ); /* CURRENT_TIME */ + testcase( i==116 ); /* CURRENT */ + testcase( i==117 ); /* PRECEDING */ + testcase( i==118 ); /* FAIL */ + testcase( i==119 ); /* LAST */ + testcase( i==120 ); /* FILTER */ + testcase( i==121 ); /* REPLACE */ + testcase( i==122 ); /* FIRST */ + testcase( i==123 ); /* FOLLOWING */ + testcase( i==124 ); /* FROM */ + testcase( i==125 ); /* FULL */ + testcase( i==126 ); /* LIMIT */ + testcase( i==127 ); /* IF */ + testcase( i==128 ); /* ORDER */ + testcase( i==129 ); /* RESTRICT */ + testcase( i==130 ); /* OTHERS */ + testcase( i==131 ); /* OVER */ + testcase( i==132 ); /* RETURNING */ + testcase( i==133 ); /* RIGHT */ + testcase( i==134 ); /* ROLLBACK */ + testcase( i==135 ); /* ROWS */ + testcase( i==136 ); /* ROW */ + testcase( i==137 ); /* UNBOUNDED */ + testcase( i==138 ); /* UNION */ + testcase( i==139 ); /* USING */ + testcase( i==140 ); /* VACUUM */ + testcase( i==141 ); /* VIEW */ + testcase( i==142 ); /* WINDOW */ + testcase( i==143 ); /* DO */ + testcase( i==144 ); /* BY */ + testcase( i==145 ); /* INITIALLY */ + testcase( i==146 ); /* ALL */ + testcase( i==147 ); /* PRIMARY */ + *pType = aKWCode[i]; + break; + } + return n; +} +SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char *z, int n){ + int id = TK_ID; + if( n>=2 ) keywordCode((char*)z, n, &id); + return id; +} +#define SQLITE_N_KEYWORD 147 +SQLITE_API int sqlite3_keyword_name(int i,const char **pzName,int *pnName){ + if( i<0 || i>=SQLITE_N_KEYWORD ) return SQLITE_ERROR; + i++; + *pzName = zKWText + aKWOffset[i]; + *pnName = aKWLen[i]; + return SQLITE_OK; +} +SQLITE_API int sqlite3_keyword_count(void){ return SQLITE_N_KEYWORD; } +SQLITE_API int sqlite3_keyword_check(const char *zName, int nName){ + return TK_ID!=sqlite3KeywordCode((const u8*)zName, nName); +} + +/************** End of keywordhash.h *****************************************/ +/************** Continuing where we left off in tokenize.c *******************/ + + +/* +** If X is a character that can be used in an identifier then +** IdChar(X) will be true. Otherwise it is false. +** +** For ASCII, any character with the high-order bit set is +** allowed in an identifier. For 7-bit characters, +** sqlite3IsIdChar[X] must be 1. +** +** For EBCDIC, the rules are more complex but have the same +** end result. +** +** Ticket #1066. the SQL standard does not allow '$' in the +** middle of identifiers. But many SQL implementations do. +** SQLite will allow '$' in identifiers for compatibility. +** But the feature is undocumented. +*/ +#ifdef SQLITE_ASCII +#define IdChar(C) ((sqlite3CtypeMap[(unsigned char)C]&0x46)!=0) +#endif +#ifdef SQLITE_EBCDIC +SQLITE_PRIVATE const char sqlite3IsEbcdicIdChar[] = { +/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 4x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, /* 5x */ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, /* 6x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, /* 7x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, /* 8x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, /* 9x */ + 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, /* Ax */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Bx */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, /* Cx */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, /* Dx */ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, /* Ex */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, /* Fx */ +}; +#define IdChar(C) (((c=C)>=0x42 && sqlite3IsEbcdicIdChar[c-0x40])) +#endif + +/* Make the IdChar function accessible from ctime.c and alter.c */ +SQLITE_PRIVATE int sqlite3IsIdChar(u8 c){ return IdChar(c); } + +#ifndef SQLITE_OMIT_WINDOWFUNC +/* +** Return the id of the next token in string (*pz). Before returning, set +** (*pz) to point to the byte following the parsed token. +*/ +static int getToken(const unsigned char **pz){ + const unsigned char *z = *pz; + int t; /* Token type to return */ + do { + z += sqlite3GetToken(z, &t); + }while( t==TK_SPACE ); + if( t==TK_ID + || t==TK_STRING + || t==TK_JOIN_KW + || t==TK_WINDOW + || t==TK_OVER + || sqlite3ParserFallback(t)==TK_ID + ){ + t = TK_ID; + } + *pz = z; + return t; +} + +/* +** The following three functions are called immediately after the tokenizer +** reads the keywords WINDOW, OVER and FILTER, respectively, to determine +** whether the token should be treated as a keyword or an SQL identifier. +** This cannot be handled by the usual lemon %fallback method, due to +** the ambiguity in some constructions. e.g. +** +** SELECT sum(x) OVER ... +** +** In the above, "OVER" might be a keyword, or it might be an alias for the +** sum(x) expression. If a "%fallback ID OVER" directive were added to +** grammar, then SQLite would always treat "OVER" as an alias, making it +** impossible to call a window-function without a FILTER clause. +** +** WINDOW is treated as a keyword if: +** +** * the following token is an identifier, or a keyword that can fallback +** to being an identifier, and +** * the token after than one is TK_AS. +** +** OVER is a keyword if: +** +** * the previous token was TK_RP, and +** * the next token is either TK_LP or an identifier. +** +** FILTER is a keyword if: +** +** * the previous token was TK_RP, and +** * the next token is TK_LP. +*/ +static int analyzeWindowKeyword(const unsigned char *z){ + int t; + t = getToken(&z); + if( t!=TK_ID ) return TK_ID; + t = getToken(&z); + if( t!=TK_AS ) return TK_ID; + return TK_WINDOW; +} +static int analyzeOverKeyword(const unsigned char *z, int lastToken){ + if( lastToken==TK_RP ){ + int t = getToken(&z); + if( t==TK_LP || t==TK_ID ) return TK_OVER; + } + return TK_ID; +} +static int analyzeFilterKeyword(const unsigned char *z, int lastToken){ + if( lastToken==TK_RP && getToken(&z)==TK_LP ){ + return TK_FILTER; + } + return TK_ID; +} +#endif /* SQLITE_OMIT_WINDOWFUNC */ + +/* +** Return the length (in bytes) of the token that begins at z[0]. +** Store the token type in *tokenType before returning. +*/ +SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ + int i, c; + switch( aiClass[*z] ){ /* Switch on the character-class of the first byte + ** of the token. See the comment on the CC_ defines + ** above. */ + case CC_SPACE: { + testcase( z[0]==' ' ); + testcase( z[0]=='\t' ); + testcase( z[0]=='\n' ); + testcase( z[0]=='\f' ); + testcase( z[0]=='\r' ); + for(i=1; sqlite3Isspace(z[i]); i++){} + *tokenType = TK_SPACE; + return i; + } + case CC_MINUS: { + if( z[1]=='-' ){ + for(i=2; (c=z[i])!=0 && c!='\n'; i++){} + *tokenType = TK_SPACE; /* IMP: R-22934-25134 */ + return i; + }else if( z[1]=='>' ){ + *tokenType = TK_PTR; + return 2 + (z[2]=='>'); + } + *tokenType = TK_MINUS; + return 1; + } + case CC_LP: { + *tokenType = TK_LP; + return 1; + } + case CC_RP: { + *tokenType = TK_RP; + return 1; + } + case CC_SEMI: { + *tokenType = TK_SEMI; + return 1; + } + case CC_PLUS: { + *tokenType = TK_PLUS; + return 1; + } + case CC_STAR: { + *tokenType = TK_STAR; + return 1; + } + case CC_SLASH: { + if( z[1]!='*' || z[2]==0 ){ + *tokenType = TK_SLASH; + return 1; + } + for(i=3, c=z[2]; (c!='*' || z[i]!='/') && (c=z[i])!=0; i++){} + if( c ) i++; + *tokenType = TK_SPACE; /* IMP: R-22934-25134 */ + return i; + } + case CC_PERCENT: { + *tokenType = TK_REM; + return 1; + } + case CC_EQ: { + *tokenType = TK_EQ; + return 1 + (z[1]=='='); + } + case CC_LT: { + if( (c=z[1])=='=' ){ + *tokenType = TK_LE; + return 2; + }else if( c=='>' ){ + *tokenType = TK_NE; + return 2; + }else if( c=='<' ){ + *tokenType = TK_LSHIFT; + return 2; + }else{ + *tokenType = TK_LT; + return 1; + } + } + case CC_GT: { + if( (c=z[1])=='=' ){ + *tokenType = TK_GE; + return 2; + }else if( c=='>' ){ + *tokenType = TK_RSHIFT; + return 2; + }else{ + *tokenType = TK_GT; + return 1; + } + } + case CC_BANG: { + if( z[1]!='=' ){ + *tokenType = TK_ILLEGAL; + return 1; + }else{ + *tokenType = TK_NE; + return 2; + } + } + case CC_PIPE: { + if( z[1]!='|' ){ + *tokenType = TK_BITOR; + return 1; + }else{ + *tokenType = TK_CONCAT; + return 2; + } + } + case CC_COMMA: { + *tokenType = TK_COMMA; + return 1; + } + case CC_AND: { + *tokenType = TK_BITAND; + return 1; + } + case CC_TILDA: { + *tokenType = TK_BITNOT; + return 1; + } + case CC_QUOTE: { + int delim = z[0]; + testcase( delim=='`' ); + testcase( delim=='\'' ); + testcase( delim=='"' ); + for(i=1; (c=z[i])!=0; i++){ + if( c==delim ){ + if( z[i+1]==delim ){ + i++; + }else{ + break; + } + } + } + if( c=='\'' ){ + *tokenType = TK_STRING; + return i+1; + }else if( c!=0 ){ + *tokenType = TK_ID; + return i+1; + }else{ + *tokenType = TK_ILLEGAL; + return i; + } + } + case CC_DOT: { +#ifndef SQLITE_OMIT_FLOATING_POINT + if( !sqlite3Isdigit(z[1]) ) +#endif + { + *tokenType = TK_DOT; + return 1; + } + /* If the next character is a digit, this is a floating point + ** number that begins with ".". Fall thru into the next case */ + /* no break */ deliberate_fall_through + } + case CC_DIGIT: { + testcase( z[0]=='0' ); testcase( z[0]=='1' ); testcase( z[0]=='2' ); + testcase( z[0]=='3' ); testcase( z[0]=='4' ); testcase( z[0]=='5' ); + testcase( z[0]=='6' ); testcase( z[0]=='7' ); testcase( z[0]=='8' ); + testcase( z[0]=='9' ); testcase( z[0]=='.' ); + *tokenType = TK_INTEGER; +#ifndef SQLITE_OMIT_HEX_INTEGER + if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && sqlite3Isxdigit(z[2]) ){ + for(i=3; sqlite3Isxdigit(z[i]); i++){} + return i; + } +#endif + for(i=0; sqlite3Isdigit(z[i]); i++){} +#ifndef SQLITE_OMIT_FLOATING_POINT + if( z[i]=='.' ){ + i++; + while( sqlite3Isdigit(z[i]) ){ i++; } + *tokenType = TK_FLOAT; + } + if( (z[i]=='e' || z[i]=='E') && + ( sqlite3Isdigit(z[i+1]) + || ((z[i+1]=='+' || z[i+1]=='-') && sqlite3Isdigit(z[i+2])) + ) + ){ + i += 2; + while( sqlite3Isdigit(z[i]) ){ i++; } + *tokenType = TK_FLOAT; + } +#endif + while( IdChar(z[i]) ){ + *tokenType = TK_ILLEGAL; + i++; + } + return i; + } + case CC_QUOTE2: { + for(i=1, c=z[0]; c!=']' && (c=z[i])!=0; i++){} + *tokenType = c==']' ? TK_ID : TK_ILLEGAL; + return i; + } + case CC_VARNUM: { + *tokenType = TK_VARIABLE; + for(i=1; sqlite3Isdigit(z[i]); i++){} + return i; + } + case CC_DOLLAR: + case CC_VARALPHA: { + int n = 0; + testcase( z[0]=='$' ); testcase( z[0]=='@' ); + testcase( z[0]==':' ); testcase( z[0]=='#' ); + *tokenType = TK_VARIABLE; + for(i=1; (c=z[i])!=0; i++){ + if( IdChar(c) ){ + n++; +#ifndef SQLITE_OMIT_TCL_VARIABLE + }else if( c=='(' && n>0 ){ + do{ + i++; + }while( (c=z[i])!=0 && !sqlite3Isspace(c) && c!=')' ); + if( c==')' ){ + i++; + }else{ + *tokenType = TK_ILLEGAL; + } + break; + }else if( c==':' && z[i+1]==':' ){ + i++; +#endif + }else{ + break; + } + } + if( n==0 ) *tokenType = TK_ILLEGAL; + return i; + } + case CC_KYWD0: { + if( aiClass[z[1]]>CC_KYWD ){ i = 1; break; } + for(i=2; aiClass[z[i]]<=CC_KYWD; i++){} + if( IdChar(z[i]) ){ + /* This token started out using characters that can appear in keywords, + ** but z[i] is a character not allowed within keywords, so this must + ** be an identifier instead */ + i++; + break; + } + *tokenType = TK_ID; + return keywordCode((char*)z, i, tokenType); + } + case CC_X: { +#ifndef SQLITE_OMIT_BLOB_LITERAL + testcase( z[0]=='x' ); testcase( z[0]=='X' ); + if( z[1]=='\'' ){ + *tokenType = TK_BLOB; + for(i=2; sqlite3Isxdigit(z[i]); i++){} + if( z[i]!='\'' || i%2 ){ + *tokenType = TK_ILLEGAL; + while( z[i] && z[i]!='\'' ){ i++; } + } + if( z[i] ) i++; + return i; + } +#endif + /* If it is not a BLOB literal, then it must be an ID, since no + ** SQL keywords start with the letter 'x'. Fall through */ + /* no break */ deliberate_fall_through + } + case CC_KYWD: + case CC_ID: { + i = 1; + break; + } + case CC_BOM: { + if( z[1]==0xbb && z[2]==0xbf ){ + *tokenType = TK_SPACE; + return 3; + } + i = 1; + break; + } + case CC_NUL: { + *tokenType = TK_ILLEGAL; + return 0; + } + default: { + *tokenType = TK_ILLEGAL; + return 1; + } + } + while( IdChar(z[i]) ){ i++; } + *tokenType = TK_ID; + return i; +} + +/* +** Run the parser on the given SQL string. +*/ +SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql){ + int nErr = 0; /* Number of errors encountered */ + void *pEngine; /* The LEMON-generated LALR(1) parser */ + int n = 0; /* Length of the next token token */ + int tokenType; /* type of the next token */ + int lastTokenParsed = -1; /* type of the previous token */ + sqlite3 *db = pParse->db; /* The database connection */ + int mxSqlLen; /* Max length of an SQL string */ + Parse *pParentParse = 0; /* Outer parse context, if any */ +#ifdef sqlite3Parser_ENGINEALWAYSONSTACK + yyParser sEngine; /* Space to hold the Lemon-generated Parser object */ +#endif + VVA_ONLY( u8 startedWithOom = db->mallocFailed ); + + assert( zSql!=0 ); + mxSqlLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; + if( db->nVdbeActive==0 ){ + AtomicStore(&db->u1.isInterrupted, 0); + } + pParse->rc = SQLITE_OK; + pParse->zTail = zSql; +#ifdef SQLITE_DEBUG + if( db->flags & SQLITE_ParserTrace ){ + printf("parser: [[[%s]]]\n", zSql); + sqlite3ParserTrace(stdout, "parser: "); + }else{ + sqlite3ParserTrace(0, 0); + } +#endif +#ifdef sqlite3Parser_ENGINEALWAYSONSTACK + pEngine = &sEngine; + sqlite3ParserInit(pEngine, pParse); +#else + pEngine = sqlite3ParserAlloc(sqlite3Malloc, pParse); + if( pEngine==0 ){ + sqlite3OomFault(db); + return SQLITE_NOMEM_BKPT; + } +#endif + assert( pParse->pNewTable==0 ); + assert( pParse->pNewTrigger==0 ); + assert( pParse->nVar==0 ); + assert( pParse->pVList==0 ); + pParentParse = db->pParse; + db->pParse = pParse; + while( 1 ){ + n = sqlite3GetToken((u8*)zSql, &tokenType); + mxSqlLen -= n; + if( mxSqlLen<0 ){ + pParse->rc = SQLITE_TOOBIG; + pParse->nErr++; + break; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( tokenType>=TK_WINDOW ){ + assert( tokenType==TK_SPACE || tokenType==TK_OVER || tokenType==TK_FILTER + || tokenType==TK_ILLEGAL || tokenType==TK_WINDOW + ); +#else + if( tokenType>=TK_SPACE ){ + assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL ); +#endif /* SQLITE_OMIT_WINDOWFUNC */ + if( AtomicLoad(&db->u1.isInterrupted) ){ + pParse->rc = SQLITE_INTERRUPT; + pParse->nErr++; + break; + } + if( tokenType==TK_SPACE ){ + zSql += n; + continue; + } + if( zSql[0]==0 ){ + /* Upon reaching the end of input, call the parser two more times + ** with tokens TK_SEMI and 0, in that order. */ + if( lastTokenParsed==TK_SEMI ){ + tokenType = 0; + }else if( lastTokenParsed==0 ){ + break; + }else{ + tokenType = TK_SEMI; + } + n = 0; +#ifndef SQLITE_OMIT_WINDOWFUNC + }else if( tokenType==TK_WINDOW ){ + assert( n==6 ); + tokenType = analyzeWindowKeyword((const u8*)&zSql[6]); + }else if( tokenType==TK_OVER ){ + assert( n==4 ); + tokenType = analyzeOverKeyword((const u8*)&zSql[4], lastTokenParsed); + }else if( tokenType==TK_FILTER ){ + assert( n==6 ); + tokenType = analyzeFilterKeyword((const u8*)&zSql[6], lastTokenParsed); +#endif /* SQLITE_OMIT_WINDOWFUNC */ + }else{ + Token x; + x.z = zSql; + x.n = n; + sqlite3ErrorMsg(pParse, "unrecognized token: \"%T\"", &x); + break; + } + } + pParse->sLastToken.z = zSql; + pParse->sLastToken.n = n; + sqlite3Parser(pEngine, tokenType, pParse->sLastToken); + lastTokenParsed = tokenType; + zSql += n; + assert( db->mallocFailed==0 || pParse->rc!=SQLITE_OK || startedWithOom ); + if( pParse->rc!=SQLITE_OK ) break; + } + assert( nErr==0 ); +#ifdef YYTRACKMAXSTACKDEPTH + sqlite3_mutex_enter(sqlite3MallocMutex()); + sqlite3StatusHighwater(SQLITE_STATUS_PARSER_STACK, + sqlite3ParserStackPeak(pEngine) + ); + sqlite3_mutex_leave(sqlite3MallocMutex()); +#endif /* YYDEBUG */ +#ifdef sqlite3Parser_ENGINEALWAYSONSTACK + sqlite3ParserFinalize(pEngine); +#else + sqlite3ParserFree(pEngine, sqlite3_free); +#endif + if( db->mallocFailed ){ + pParse->rc = SQLITE_NOMEM_BKPT; + } + if( pParse->zErrMsg || (pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE) ){ + if( pParse->zErrMsg==0 ){ + pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc)); + } + sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail); + nErr++; + } + pParse->zTail = zSql; +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3_free(pParse->apVtabLock); +#endif + + if( pParse->pNewTable && !IN_SPECIAL_PARSE ){ + /* If the pParse->declareVtab flag is set, do not delete any table + ** structure built up in pParse->pNewTable. The calling code (see vtab.c) + ** will take responsibility for freeing the Table structure. + */ + sqlite3DeleteTable(db, pParse->pNewTable); + } + if( pParse->pNewTrigger && !IN_RENAME_OBJECT ){ + sqlite3DeleteTrigger(db, pParse->pNewTrigger); + } + if( pParse->pVList ) sqlite3DbNNFreeNN(db, pParse->pVList); + db->pParse = pParentParse; + assert( nErr==0 || pParse->rc!=SQLITE_OK ); + return nErr; +} + + +#ifdef SQLITE_ENABLE_NORMALIZE +/* +** Insert a single space character into pStr if the current string +** ends with an identifier +*/ +static void addSpaceSeparator(sqlite3_str *pStr){ + if( pStr->nChar && sqlite3IsIdChar(pStr->zText[pStr->nChar-1]) ){ + sqlite3_str_append(pStr, " ", 1); + } +} + +/* +** Compute a normalization of the SQL given by zSql[0..nSql-1]. Return +** the normalization in space obtained from sqlite3DbMalloc(). Or return +** NULL if anything goes wrong or if zSql is NULL. +*/ +SQLITE_PRIVATE char *sqlite3Normalize( + Vdbe *pVdbe, /* VM being reprepared */ + const char *zSql /* The original SQL string */ +){ + sqlite3 *db; /* The database connection */ + int i; /* Next unread byte of zSql[] */ + int n; /* length of current token */ + int tokenType; /* type of current token */ + int prevType = 0; /* Previous non-whitespace token */ + int nParen; /* Number of nested levels of parentheses */ + int iStartIN; /* Start of RHS of IN operator in z[] */ + int nParenAtIN; /* Value of nParent at start of RHS of IN operator */ + u32 j; /* Bytes of normalized SQL generated so far */ + sqlite3_str *pStr; /* The normalized SQL string under construction */ + + db = sqlite3VdbeDb(pVdbe); + tokenType = -1; + nParen = iStartIN = nParenAtIN = 0; + pStr = sqlite3_str_new(db); + assert( pStr!=0 ); /* sqlite3_str_new() never returns NULL */ + for(i=0; zSql[i] && pStr->accError==0; i+=n){ + if( tokenType!=TK_SPACE ){ + prevType = tokenType; + } + n = sqlite3GetToken((unsigned char*)zSql+i, &tokenType); + if( NEVER(n<=0) ) break; + switch( tokenType ){ + case TK_SPACE: { + break; + } + case TK_NULL: { + if( prevType==TK_IS || prevType==TK_NOT ){ + sqlite3_str_append(pStr, " NULL", 5); + break; + } + /* Fall through */ + } + case TK_STRING: + case TK_INTEGER: + case TK_FLOAT: + case TK_VARIABLE: + case TK_BLOB: { + sqlite3_str_append(pStr, "?", 1); + break; + } + case TK_LP: { + nParen++; + if( prevType==TK_IN ){ + iStartIN = pStr->nChar; + nParenAtIN = nParen; + } + sqlite3_str_append(pStr, "(", 1); + break; + } + case TK_RP: { + if( iStartIN>0 && nParen==nParenAtIN ){ + assert( pStr->nChar>=(u32)iStartIN ); + pStr->nChar = iStartIN+1; + sqlite3_str_append(pStr, "?,?,?", 5); + iStartIN = 0; + } + nParen--; + sqlite3_str_append(pStr, ")", 1); + break; + } + case TK_ID: { + iStartIN = 0; + j = pStr->nChar; + if( sqlite3Isquote(zSql[i]) ){ + char *zId = sqlite3DbStrNDup(db, zSql+i, n); + int nId; + int eType = 0; + if( zId==0 ) break; + sqlite3Dequote(zId); + if( zSql[i]=='"' && sqlite3VdbeUsesDoubleQuotedString(pVdbe, zId) ){ + sqlite3_str_append(pStr, "?", 1); + sqlite3DbFree(db, zId); + break; + } + nId = sqlite3Strlen30(zId); + if( sqlite3GetToken((u8*)zId, &eType)==nId && eType==TK_ID ){ + addSpaceSeparator(pStr); + sqlite3_str_append(pStr, zId, nId); + }else{ + sqlite3_str_appendf(pStr, "\"%w\"", zId); + } + sqlite3DbFree(db, zId); + }else{ + addSpaceSeparator(pStr); + sqlite3_str_append(pStr, zSql+i, n); + } + while( j<pStr->nChar ){ + pStr->zText[j] = sqlite3Tolower(pStr->zText[j]); + j++; + } + break; + } + case TK_SELECT: { + iStartIN = 0; + /* fall through */ + } + default: { + if( sqlite3IsIdChar(zSql[i]) ) addSpaceSeparator(pStr); + j = pStr->nChar; + sqlite3_str_append(pStr, zSql+i, n); + while( j<pStr->nChar ){ + pStr->zText[j] = sqlite3Toupper(pStr->zText[j]); + j++; + } + break; + } + } + } + if( tokenType!=TK_SEMI ) sqlite3_str_append(pStr, ";", 1); + return sqlite3_str_finish(pStr); +} +#endif /* SQLITE_ENABLE_NORMALIZE */ + +/************** End of tokenize.c ********************************************/ +/************** Begin file complete.c ****************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** An tokenizer for SQL +** +** This file contains C code that implements the sqlite3_complete() API. +** This code used to be part of the tokenizer.c source file. But by +** separating it out, the code will be automatically omitted from +** static links that do not use it. +*/ +/* #include "sqliteInt.h" */ +#ifndef SQLITE_OMIT_COMPLETE + +/* +** This is defined in tokenize.c. We just have to import the definition. +*/ +#ifndef SQLITE_AMALGAMATION +#ifdef SQLITE_ASCII +#define IdChar(C) ((sqlite3CtypeMap[(unsigned char)C]&0x46)!=0) +#endif +#ifdef SQLITE_EBCDIC +SQLITE_PRIVATE const char sqlite3IsEbcdicIdChar[]; +#define IdChar(C) (((c=C)>=0x42 && sqlite3IsEbcdicIdChar[c-0x40])) +#endif +#endif /* SQLITE_AMALGAMATION */ + + +/* +** Token types used by the sqlite3_complete() routine. See the header +** comments on that procedure for additional information. +*/ +#define tkSEMI 0 +#define tkWS 1 +#define tkOTHER 2 +#ifndef SQLITE_OMIT_TRIGGER +#define tkEXPLAIN 3 +#define tkCREATE 4 +#define tkTEMP 5 +#define tkTRIGGER 6 +#define tkEND 7 +#endif + +/* +** Return TRUE if the given SQL string ends in a semicolon. +** +** Special handling is require for CREATE TRIGGER statements. +** Whenever the CREATE TRIGGER keywords are seen, the statement +** must end with ";END;". +** +** This implementation uses a state machine with 8 states: +** +** (0) INVALID We have not yet seen a non-whitespace character. +** +** (1) START At the beginning or end of an SQL statement. This routine +** returns 1 if it ends in the START state and 0 if it ends +** in any other state. +** +** (2) NORMAL We are in the middle of statement which ends with a single +** semicolon. +** +** (3) EXPLAIN The keyword EXPLAIN has been seen at the beginning of +** a statement. +** +** (4) CREATE The keyword CREATE has been seen at the beginning of a +** statement, possibly preceded by EXPLAIN and/or followed by +** TEMP or TEMPORARY +** +** (5) TRIGGER We are in the middle of a trigger definition that must be +** ended by a semicolon, the keyword END, and another semicolon. +** +** (6) SEMI We've seen the first semicolon in the ";END;" that occurs at +** the end of a trigger definition. +** +** (7) END We've seen the ";END" of the ";END;" that occurs at the end +** of a trigger definition. +** +** Transitions between states above are determined by tokens extracted +** from the input. The following tokens are significant: +** +** (0) tkSEMI A semicolon. +** (1) tkWS Whitespace. +** (2) tkOTHER Any other SQL token. +** (3) tkEXPLAIN The "explain" keyword. +** (4) tkCREATE The "create" keyword. +** (5) tkTEMP The "temp" or "temporary" keyword. +** (6) tkTRIGGER The "trigger" keyword. +** (7) tkEND The "end" keyword. +** +** Whitespace never causes a state transition and is always ignored. +** This means that a SQL string of all whitespace is invalid. +** +** If we compile with SQLITE_OMIT_TRIGGER, all of the computation needed +** to recognize the end of a trigger can be omitted. All we have to do +** is look for a semicolon that is not part of an string or comment. +*/ +SQLITE_API int sqlite3_complete(const char *zSql){ + u8 state = 0; /* Current state, using numbers defined in header comment */ + u8 token; /* Value of the next token */ + +#ifndef SQLITE_OMIT_TRIGGER + /* A complex statement machine used to detect the end of a CREATE TRIGGER + ** statement. This is the normal case. + */ + static const u8 trans[8][8] = { + /* Token: */ + /* State: ** SEMI WS OTHER EXPLAIN CREATE TEMP TRIGGER END */ + /* 0 INVALID: */ { 1, 0, 2, 3, 4, 2, 2, 2, }, + /* 1 START: */ { 1, 1, 2, 3, 4, 2, 2, 2, }, + /* 2 NORMAL: */ { 1, 2, 2, 2, 2, 2, 2, 2, }, + /* 3 EXPLAIN: */ { 1, 3, 3, 2, 4, 2, 2, 2, }, + /* 4 CREATE: */ { 1, 4, 2, 2, 2, 4, 5, 2, }, + /* 5 TRIGGER: */ { 6, 5, 5, 5, 5, 5, 5, 5, }, + /* 6 SEMI: */ { 6, 6, 5, 5, 5, 5, 5, 7, }, + /* 7 END: */ { 1, 7, 5, 5, 5, 5, 5, 5, }, + }; +#else + /* If triggers are not supported by this compile then the statement machine + ** used to detect the end of a statement is much simpler + */ + static const u8 trans[3][3] = { + /* Token: */ + /* State: ** SEMI WS OTHER */ + /* 0 INVALID: */ { 1, 0, 2, }, + /* 1 START: */ { 1, 1, 2, }, + /* 2 NORMAL: */ { 1, 2, 2, }, + }; +#endif /* SQLITE_OMIT_TRIGGER */ + +#ifdef SQLITE_ENABLE_API_ARMOR + if( zSql==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + + while( *zSql ){ + switch( *zSql ){ + case ';': { /* A semicolon */ + token = tkSEMI; + break; + } + case ' ': + case '\r': + case '\t': + case '\n': + case '\f': { /* White space is ignored */ + token = tkWS; + break; + } + case '/': { /* C-style comments */ + if( zSql[1]!='*' ){ + token = tkOTHER; + break; + } + zSql += 2; + while( zSql[0] && (zSql[0]!='*' || zSql[1]!='/') ){ zSql++; } + if( zSql[0]==0 ) return 0; + zSql++; + token = tkWS; + break; + } + case '-': { /* SQL-style comments from "--" to end of line */ + if( zSql[1]!='-' ){ + token = tkOTHER; + break; + } + while( *zSql && *zSql!='\n' ){ zSql++; } + if( *zSql==0 ) return state==1; + token = tkWS; + break; + } + case '[': { /* Microsoft-style identifiers in [...] */ + zSql++; + while( *zSql && *zSql!=']' ){ zSql++; } + if( *zSql==0 ) return 0; + token = tkOTHER; + break; + } + case '`': /* Grave-accent quoted symbols used by MySQL */ + case '"': /* single- and double-quoted strings */ + case '\'': { + int c = *zSql; + zSql++; + while( *zSql && *zSql!=c ){ zSql++; } + if( *zSql==0 ) return 0; + token = tkOTHER; + break; + } + default: { +#ifdef SQLITE_EBCDIC + unsigned char c; +#endif + if( IdChar((u8)*zSql) ){ + /* Keywords and unquoted identifiers */ + int nId; + for(nId=1; IdChar(zSql[nId]); nId++){} +#ifdef SQLITE_OMIT_TRIGGER + token = tkOTHER; +#else + switch( *zSql ){ + case 'c': case 'C': { + if( nId==6 && sqlite3StrNICmp(zSql, "create", 6)==0 ){ + token = tkCREATE; + }else{ + token = tkOTHER; + } + break; + } + case 't': case 'T': { + if( nId==7 && sqlite3StrNICmp(zSql, "trigger", 7)==0 ){ + token = tkTRIGGER; + }else if( nId==4 && sqlite3StrNICmp(zSql, "temp", 4)==0 ){ + token = tkTEMP; + }else if( nId==9 && sqlite3StrNICmp(zSql, "temporary", 9)==0 ){ + token = tkTEMP; + }else{ + token = tkOTHER; + } + break; + } + case 'e': case 'E': { + if( nId==3 && sqlite3StrNICmp(zSql, "end", 3)==0 ){ + token = tkEND; + }else +#ifndef SQLITE_OMIT_EXPLAIN + if( nId==7 && sqlite3StrNICmp(zSql, "explain", 7)==0 ){ + token = tkEXPLAIN; + }else +#endif + { + token = tkOTHER; + } + break; + } + default: { + token = tkOTHER; + break; + } + } +#endif /* SQLITE_OMIT_TRIGGER */ + zSql += nId-1; + }else{ + /* Operators and special symbols */ + token = tkOTHER; + } + break; + } + } + state = trans[state][token]; + zSql++; + } + return state==1; +} + +#ifndef SQLITE_OMIT_UTF16 +/* +** This routine is the same as the sqlite3_complete() routine described +** above, except that the parameter is required to be UTF-16 encoded, not +** UTF-8. +*/ +SQLITE_API int sqlite3_complete16(const void *zSql){ + sqlite3_value *pVal; + char const *zSql8; + int rc; + +#ifndef SQLITE_OMIT_AUTOINIT + rc = sqlite3_initialize(); + if( rc ) return rc; +#endif + pVal = sqlite3ValueNew(0); + sqlite3ValueSetStr(pVal, -1, zSql, SQLITE_UTF16NATIVE, SQLITE_STATIC); + zSql8 = sqlite3ValueText(pVal, SQLITE_UTF8); + if( zSql8 ){ + rc = sqlite3_complete(zSql8); + }else{ + rc = SQLITE_NOMEM_BKPT; + } + sqlite3ValueFree(pVal); + return rc & 0xff; +} +#endif /* SQLITE_OMIT_UTF16 */ +#endif /* SQLITE_OMIT_COMPLETE */ + +/************** End of complete.c ********************************************/ +/************** Begin file main.c ********************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Main file for the SQLite library. The routines in this file +** implement the programmer interface to the library. Routines in +** other files are for internal use by SQLite and should not be +** accessed by users of the library. +*/ +/* #include "sqliteInt.h" */ + +#ifdef SQLITE_ENABLE_FTS3 +/************** Include fts3.h in the middle of main.c ***********************/ +/************** Begin file fts3.h ********************************************/ +/* +** 2006 Oct 10 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file is used by programs that want to link against the +** FTS3 library. All it does is declare the sqlite3Fts3Init() interface. +*/ +/* #include "sqlite3.h" */ + +#if 0 +extern "C" { +#endif /* __cplusplus */ + +SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db); + +#if 0 +} /* extern "C" */ +#endif /* __cplusplus */ + +/************** End of fts3.h ************************************************/ +/************** Continuing where we left off in main.c ***********************/ +#endif +#ifdef SQLITE_ENABLE_RTREE +/************** Include rtree.h in the middle of main.c **********************/ +/************** Begin file rtree.h *******************************************/ +/* +** 2008 May 26 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file is used by programs that want to link against the +** RTREE library. All it does is declare the sqlite3RtreeInit() interface. +*/ +/* #include "sqlite3.h" */ + +#ifdef SQLITE_OMIT_VIRTUALTABLE +# undef SQLITE_ENABLE_RTREE +#endif + +#if 0 +extern "C" { +#endif /* __cplusplus */ + +SQLITE_PRIVATE int sqlite3RtreeInit(sqlite3 *db); + +#if 0 +} /* extern "C" */ +#endif /* __cplusplus */ + +/************** End of rtree.h ***********************************************/ +/************** Continuing where we left off in main.c ***********************/ +#endif +#if defined(SQLITE_ENABLE_ICU) || defined(SQLITE_ENABLE_ICU_COLLATIONS) +/************** Include sqliteicu.h in the middle of main.c ******************/ +/************** Begin file sqliteicu.h ***************************************/ +/* +** 2008 May 26 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file is used by programs that want to link against the +** ICU extension. All it does is declare the sqlite3IcuInit() interface. +*/ +/* #include "sqlite3.h" */ + +#if 0 +extern "C" { +#endif /* __cplusplus */ + +SQLITE_PRIVATE int sqlite3IcuInit(sqlite3 *db); + +#if 0 +} /* extern "C" */ +#endif /* __cplusplus */ + +/************** End of sqliteicu.h *******************************************/ +/************** Continuing where we left off in main.c ***********************/ +#endif + +/* +** This is an extension initializer that is a no-op and always +** succeeds, except that it fails if the fault-simulation is set +** to 500. +*/ +static int sqlite3TestExtInit(sqlite3 *db){ + (void)db; + return sqlite3FaultSim(500); +} + + +/* +** Forward declarations of external module initializer functions +** for modules that need them. +*/ +#ifdef SQLITE_ENABLE_FTS5 +SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3*); +#endif +#ifdef SQLITE_ENABLE_STMTVTAB +SQLITE_PRIVATE int sqlite3StmtVtabInit(sqlite3*); +#endif +#ifdef SQLITE_EXTRA_AUTOEXT +int SQLITE_EXTRA_AUTOEXT(sqlite3*); +#endif +/* +** An array of pointers to extension initializer functions for +** built-in extensions. +*/ +static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = { +#ifdef SQLITE_ENABLE_FTS3 + sqlite3Fts3Init, +#endif +#ifdef SQLITE_ENABLE_FTS5 + sqlite3Fts5Init, +#endif +#if defined(SQLITE_ENABLE_ICU) || defined(SQLITE_ENABLE_ICU_COLLATIONS) + sqlite3IcuInit, +#endif +#ifdef SQLITE_ENABLE_RTREE + sqlite3RtreeInit, +#endif +#ifdef SQLITE_ENABLE_DBPAGE_VTAB + sqlite3DbpageRegister, +#endif +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + sqlite3DbstatRegister, +#endif + sqlite3TestExtInit, +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) + sqlite3JsonTableFunctions, +#endif +#ifdef SQLITE_ENABLE_STMTVTAB + sqlite3StmtVtabInit, +#endif +#ifdef SQLITE_ENABLE_BYTECODE_VTAB + sqlite3VdbeBytecodeVtabInit, +#endif +#ifdef SQLITE_EXTRA_AUTOEXT + SQLITE_EXTRA_AUTOEXT, +#endif +}; + +#ifndef SQLITE_AMALGAMATION +/* IMPLEMENTATION-OF: R-46656-45156 The sqlite3_version[] string constant +** contains the text of SQLITE_VERSION macro. +*/ +SQLITE_API const char sqlite3_version[] = SQLITE_VERSION; +#endif + +/* IMPLEMENTATION-OF: R-53536-42575 The sqlite3_libversion() function returns +** a pointer to the to the sqlite3_version[] string constant. +*/ +SQLITE_API const char *sqlite3_libversion(void){ return sqlite3_version; } + +/* IMPLEMENTATION-OF: R-25063-23286 The sqlite3_sourceid() function returns a +** pointer to a string constant whose value is the same as the +** SQLITE_SOURCE_ID C preprocessor macro. Except if SQLite is built using +** an edited copy of the amalgamation, then the last four characters of +** the hash might be different from SQLITE_SOURCE_ID. +*/ +/* SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } */ + +/* IMPLEMENTATION-OF: R-35210-63508 The sqlite3_libversion_number() function +** returns an integer equal to SQLITE_VERSION_NUMBER. +*/ +SQLITE_API int sqlite3_libversion_number(void){ return SQLITE_VERSION_NUMBER; } + +/* IMPLEMENTATION-OF: R-20790-14025 The sqlite3_threadsafe() function returns +** zero if and only if SQLite was compiled with mutexing code omitted due to +** the SQLITE_THREADSAFE compile-time option being set to 0. +*/ +SQLITE_API int sqlite3_threadsafe(void){ return SQLITE_THREADSAFE; } + +/* +** When compiling the test fixture or with debugging enabled (on Win32), +** this variable being set to non-zero will cause OSTRACE macros to emit +** extra diagnostic information. +*/ +#ifdef SQLITE_HAVE_OS_TRACE +# ifndef SQLITE_DEBUG_OS_TRACE +# define SQLITE_DEBUG_OS_TRACE 0 +# endif + int sqlite3OSTrace = SQLITE_DEBUG_OS_TRACE; +#endif + +#if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE) +/* +** If the following function pointer is not NULL and if +** SQLITE_ENABLE_IOTRACE is enabled, then messages describing +** I/O active are written using this function. These messages +** are intended for debugging activity only. +*/ +SQLITE_API void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...) = 0; +#endif + +/* +** If the following global variable points to a string which is the +** name of a directory, then that directory will be used to store +** temporary files. +** +** See also the "PRAGMA temp_store_directory" SQL command. +*/ +SQLITE_API char *sqlite3_temp_directory = 0; + +/* +** If the following global variable points to a string which is the +** name of a directory, then that directory will be used to store +** all database files specified with a relative pathname. +** +** See also the "PRAGMA data_store_directory" SQL command. +*/ +SQLITE_API char *sqlite3_data_directory = 0; + +/* +** Initialize SQLite. +** +** This routine must be called to initialize the memory allocation, +** VFS, and mutex subsystems prior to doing any serious work with +** SQLite. But as long as you do not compile with SQLITE_OMIT_AUTOINIT +** this routine will be called automatically by key routines such as +** sqlite3_open(). +** +** This routine is a no-op except on its very first call for the process, +** or for the first call after a call to sqlite3_shutdown. +** +** The first thread to call this routine runs the initialization to +** completion. If subsequent threads call this routine before the first +** thread has finished the initialization process, then the subsequent +** threads must block until the first thread finishes with the initialization. +** +** The first thread might call this routine recursively. Recursive +** calls to this routine should not block, of course. Otherwise the +** initialization process would never complete. +** +** Let X be the first thread to enter this routine. Let Y be some other +** thread. Then while the initial invocation of this routine by X is +** incomplete, it is required that: +** +** * Calls to this routine from Y must block until the outer-most +** call by X completes. +** +** * Recursive calls to this routine from thread X return immediately +** without blocking. +*/ +SQLITE_API int sqlite3_initialize(void){ + MUTEX_LOGIC( sqlite3_mutex *pMainMtx; ) /* The main static mutex */ + int rc; /* Result code */ +#ifdef SQLITE_EXTRA_INIT + int bRunExtraInit = 0; /* Extra initialization needed */ +#endif + +#ifdef SQLITE_OMIT_WSD + rc = sqlite3_wsd_init(4096, 24); + if( rc!=SQLITE_OK ){ + return rc; + } +#endif + + /* If the following assert() fails on some obscure processor/compiler + ** combination, the work-around is to set the correct pointer + ** size at compile-time using -DSQLITE_PTRSIZE=n compile-time option */ + assert( SQLITE_PTRSIZE==sizeof(char*) ); + + /* If SQLite is already completely initialized, then this call + ** to sqlite3_initialize() should be a no-op. But the initialization + ** must be complete. So isInit must not be set until the very end + ** of this routine. + */ + if( sqlite3GlobalConfig.isInit ){ + sqlite3MemoryBarrier(); + return SQLITE_OK; + } + + /* Make sure the mutex subsystem is initialized. If unable to + ** initialize the mutex subsystem, return early with the error. + ** If the system is so sick that we are unable to allocate a mutex, + ** there is not much SQLite is going to be able to do. + ** + ** The mutex subsystem must take care of serializing its own + ** initialization. + */ + rc = sqlite3MutexInit(); + if( rc ) return rc; + + /* Initialize the malloc() system and the recursive pInitMutex mutex. + ** This operation is protected by the STATIC_MAIN mutex. Note that + ** MutexAlloc() is called for a static mutex prior to initializing the + ** malloc subsystem - this implies that the allocation of a static + ** mutex must not require support from the malloc subsystem. + */ + MUTEX_LOGIC( pMainMtx = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); ) + sqlite3_mutex_enter(pMainMtx); + sqlite3GlobalConfig.isMutexInit = 1; + if( !sqlite3GlobalConfig.isMallocInit ){ + rc = sqlite3MallocInit(); + } + if( rc==SQLITE_OK ){ + sqlite3GlobalConfig.isMallocInit = 1; + if( !sqlite3GlobalConfig.pInitMutex ){ + sqlite3GlobalConfig.pInitMutex = + sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE); + if( sqlite3GlobalConfig.bCoreMutex && !sqlite3GlobalConfig.pInitMutex ){ + rc = SQLITE_NOMEM_BKPT; + } + } + } + if( rc==SQLITE_OK ){ + sqlite3GlobalConfig.nRefInitMutex++; + } + sqlite3_mutex_leave(pMainMtx); + + /* If rc is not SQLITE_OK at this point, then either the malloc + ** subsystem could not be initialized or the system failed to allocate + ** the pInitMutex mutex. Return an error in either case. */ + if( rc!=SQLITE_OK ){ + return rc; + } + + /* Do the rest of the initialization under the recursive mutex so + ** that we will be able to handle recursive calls into + ** sqlite3_initialize(). The recursive calls normally come through + ** sqlite3_os_init() when it invokes sqlite3_vfs_register(), but other + ** recursive calls might also be possible. + ** + ** IMPLEMENTATION-OF: R-00140-37445 SQLite automatically serializes calls + ** to the xInit method, so the xInit method need not be threadsafe. + ** + ** The following mutex is what serializes access to the appdef pcache xInit + ** methods. The sqlite3_pcache_methods.xInit() all is embedded in the + ** call to sqlite3PcacheInitialize(). + */ + sqlite3_mutex_enter(sqlite3GlobalConfig.pInitMutex); + if( sqlite3GlobalConfig.isInit==0 && sqlite3GlobalConfig.inProgress==0 ){ + sqlite3GlobalConfig.inProgress = 1; +#ifdef SQLITE_ENABLE_SQLLOG + { + extern void sqlite3_init_sqllog(void); + sqlite3_init_sqllog(); + } +#endif + memset(&sqlite3BuiltinFunctions, 0, sizeof(sqlite3BuiltinFunctions)); + sqlite3RegisterBuiltinFunctions(); + if( sqlite3GlobalConfig.isPCacheInit==0 ){ + rc = sqlite3PcacheInitialize(); + } + if( rc==SQLITE_OK ){ + sqlite3GlobalConfig.isPCacheInit = 1; + rc = sqlite3OsInit(); + } +#ifndef SQLITE_OMIT_DESERIALIZE + if( rc==SQLITE_OK ){ + rc = sqlite3MemdbInit(); + } +#endif + if( rc==SQLITE_OK ){ + sqlite3PCacheBufferSetup( sqlite3GlobalConfig.pPage, + sqlite3GlobalConfig.szPage, sqlite3GlobalConfig.nPage); + sqlite3MemoryBarrier(); + sqlite3GlobalConfig.isInit = 1; +#ifdef SQLITE_EXTRA_INIT + bRunExtraInit = 1; +#endif + } + sqlite3GlobalConfig.inProgress = 0; + } + sqlite3_mutex_leave(sqlite3GlobalConfig.pInitMutex); + + /* Go back under the static mutex and clean up the recursive + ** mutex to prevent a resource leak. + */ + sqlite3_mutex_enter(pMainMtx); + sqlite3GlobalConfig.nRefInitMutex--; + if( sqlite3GlobalConfig.nRefInitMutex<=0 ){ + assert( sqlite3GlobalConfig.nRefInitMutex==0 ); + sqlite3_mutex_free(sqlite3GlobalConfig.pInitMutex); + sqlite3GlobalConfig.pInitMutex = 0; + } + sqlite3_mutex_leave(pMainMtx); + + /* The following is just a sanity check to make sure SQLite has + ** been compiled correctly. It is important to run this code, but + ** we don't want to run it too often and soak up CPU cycles for no + ** reason. So we run it once during initialization. + */ +#ifndef NDEBUG +#ifndef SQLITE_OMIT_FLOATING_POINT + /* This section of code's only "output" is via assert() statements. */ + if( rc==SQLITE_OK ){ + u64 x = (((u64)1)<<63)-1; + double y; + assert(sizeof(x)==8); + assert(sizeof(x)==sizeof(y)); + memcpy(&y, &x, 8); + assert( sqlite3IsNaN(y) ); + } +#endif +#endif + + /* Do extra initialization steps requested by the SQLITE_EXTRA_INIT + ** compile-time option. + */ +#ifdef SQLITE_EXTRA_INIT + if( bRunExtraInit ){ + int SQLITE_EXTRA_INIT(const char*); + rc = SQLITE_EXTRA_INIT(0); + } +#endif + + return rc; +} + +/* +** Undo the effects of sqlite3_initialize(). Must not be called while +** there are outstanding database connections or memory allocations or +** while any part of SQLite is otherwise in use in any thread. This +** routine is not threadsafe. But it is safe to invoke this routine +** on when SQLite is already shut down. If SQLite is already shut down +** when this routine is invoked, then this routine is a harmless no-op. +*/ +SQLITE_API int sqlite3_shutdown(void){ +#ifdef SQLITE_OMIT_WSD + int rc = sqlite3_wsd_init(4096, 24); + if( rc!=SQLITE_OK ){ + return rc; + } +#endif + + if( sqlite3GlobalConfig.isInit ){ +#ifdef SQLITE_EXTRA_SHUTDOWN + void SQLITE_EXTRA_SHUTDOWN(void); + SQLITE_EXTRA_SHUTDOWN(); +#endif + sqlite3_os_end(); + sqlite3_reset_auto_extension(); + sqlite3GlobalConfig.isInit = 0; + } + if( sqlite3GlobalConfig.isPCacheInit ){ + sqlite3PcacheShutdown(); + sqlite3GlobalConfig.isPCacheInit = 0; + } + if( sqlite3GlobalConfig.isMallocInit ){ + sqlite3MallocEnd(); + sqlite3GlobalConfig.isMallocInit = 0; + +#ifndef SQLITE_OMIT_SHUTDOWN_DIRECTORIES + /* The heap subsystem has now been shutdown and these values are supposed + ** to be NULL or point to memory that was obtained from sqlite3_malloc(), + ** which would rely on that heap subsystem; therefore, make sure these + ** values cannot refer to heap memory that was just invalidated when the + ** heap subsystem was shutdown. This is only done if the current call to + ** this function resulted in the heap subsystem actually being shutdown. + */ + sqlite3_data_directory = 0; + sqlite3_temp_directory = 0; +#endif + } + if( sqlite3GlobalConfig.isMutexInit ){ + sqlite3MutexEnd(); + sqlite3GlobalConfig.isMutexInit = 0; + } + + return SQLITE_OK; +} + +/* +** This API allows applications to modify the global configuration of +** the SQLite library at run-time. +** +** This routine should only be called when there are no outstanding +** database connections or memory allocations. This routine is not +** threadsafe. Failure to heed these warnings can lead to unpredictable +** behavior. +*/ +SQLITE_API int sqlite3_config(int op, ...){ + va_list ap; + int rc = SQLITE_OK; + + /* sqlite3_config() normally returns SQLITE_MISUSE if it is invoked while + ** the SQLite library is in use. Except, a few selected opcodes + ** are allowed. + */ + if( sqlite3GlobalConfig.isInit ){ + static const u64 mAnytimeConfigOption = 0 + | MASKBIT64( SQLITE_CONFIG_LOG ) + | MASKBIT64( SQLITE_CONFIG_PCACHE_HDRSZ ) + ; + if( op<0 || op>63 || (MASKBIT64(op) & mAnytimeConfigOption)==0 ){ + return SQLITE_MISUSE_BKPT; + } + testcase( op==SQLITE_CONFIG_LOG ); + testcase( op==SQLITE_CONFIG_PCACHE_HDRSZ ); + } + + va_start(ap, op); + switch( op ){ + + /* Mutex configuration options are only available in a threadsafe + ** compile. + */ +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-54466-46756 */ + case SQLITE_CONFIG_SINGLETHREAD: { + /* EVIDENCE-OF: R-02748-19096 This option sets the threading mode to + ** Single-thread. */ + sqlite3GlobalConfig.bCoreMutex = 0; /* Disable mutex on core */ + sqlite3GlobalConfig.bFullMutex = 0; /* Disable mutex on connections */ + break; + } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-20520-54086 */ + case SQLITE_CONFIG_MULTITHREAD: { + /* EVIDENCE-OF: R-14374-42468 This option sets the threading mode to + ** Multi-thread. */ + sqlite3GlobalConfig.bCoreMutex = 1; /* Enable mutex on core */ + sqlite3GlobalConfig.bFullMutex = 0; /* Disable mutex on connections */ + break; + } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-59593-21810 */ + case SQLITE_CONFIG_SERIALIZED: { + /* EVIDENCE-OF: R-41220-51800 This option sets the threading mode to + ** Serialized. */ + sqlite3GlobalConfig.bCoreMutex = 1; /* Enable mutex on core */ + sqlite3GlobalConfig.bFullMutex = 1; /* Enable mutex on connections */ + break; + } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-63666-48755 */ + case SQLITE_CONFIG_MUTEX: { + /* Specify an alternative mutex implementation */ + sqlite3GlobalConfig.mutex = *va_arg(ap, sqlite3_mutex_methods*); + break; + } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-14450-37597 */ + case SQLITE_CONFIG_GETMUTEX: { + /* Retrieve the current mutex implementation */ + *va_arg(ap, sqlite3_mutex_methods*) = sqlite3GlobalConfig.mutex; + break; + } +#endif + + case SQLITE_CONFIG_MALLOC: { + /* EVIDENCE-OF: R-55594-21030 The SQLITE_CONFIG_MALLOC option takes a + ** single argument which is a pointer to an instance of the + ** sqlite3_mem_methods structure. The argument specifies alternative + ** low-level memory allocation routines to be used in place of the memory + ** allocation routines built into SQLite. */ + sqlite3GlobalConfig.m = *va_arg(ap, sqlite3_mem_methods*); + break; + } + case SQLITE_CONFIG_GETMALLOC: { + /* EVIDENCE-OF: R-51213-46414 The SQLITE_CONFIG_GETMALLOC option takes a + ** single argument which is a pointer to an instance of the + ** sqlite3_mem_methods structure. The sqlite3_mem_methods structure is + ** filled with the currently defined memory allocation routines. */ + if( sqlite3GlobalConfig.m.xMalloc==0 ) sqlite3MemSetDefault(); + *va_arg(ap, sqlite3_mem_methods*) = sqlite3GlobalConfig.m; + break; + } + case SQLITE_CONFIG_MEMSTATUS: { + assert( !sqlite3GlobalConfig.isInit ); /* Cannot change at runtime */ + /* EVIDENCE-OF: R-61275-35157 The SQLITE_CONFIG_MEMSTATUS option takes + ** single argument of type int, interpreted as a boolean, which enables + ** or disables the collection of memory allocation statistics. */ + sqlite3GlobalConfig.bMemstat = va_arg(ap, int); + break; + } + case SQLITE_CONFIG_SMALL_MALLOC: { + sqlite3GlobalConfig.bSmallMalloc = va_arg(ap, int); + break; + } + case SQLITE_CONFIG_PAGECACHE: { + /* EVIDENCE-OF: R-18761-36601 There are three arguments to + ** SQLITE_CONFIG_PAGECACHE: A pointer to 8-byte aligned memory (pMem), + ** the size of each page cache line (sz), and the number of cache lines + ** (N). */ + sqlite3GlobalConfig.pPage = va_arg(ap, void*); + sqlite3GlobalConfig.szPage = va_arg(ap, int); + sqlite3GlobalConfig.nPage = va_arg(ap, int); + break; + } + case SQLITE_CONFIG_PCACHE_HDRSZ: { + /* EVIDENCE-OF: R-39100-27317 The SQLITE_CONFIG_PCACHE_HDRSZ option takes + ** a single parameter which is a pointer to an integer and writes into + ** that integer the number of extra bytes per page required for each page + ** in SQLITE_CONFIG_PAGECACHE. */ + *va_arg(ap, int*) = + sqlite3HeaderSizeBtree() + + sqlite3HeaderSizePcache() + + sqlite3HeaderSizePcache1(); + break; + } + + case SQLITE_CONFIG_PCACHE: { + /* no-op */ + break; + } + case SQLITE_CONFIG_GETPCACHE: { + /* now an error */ + rc = SQLITE_ERROR; + break; + } + + case SQLITE_CONFIG_PCACHE2: { + /* EVIDENCE-OF: R-63325-48378 The SQLITE_CONFIG_PCACHE2 option takes a + ** single argument which is a pointer to an sqlite3_pcache_methods2 + ** object. This object specifies the interface to a custom page cache + ** implementation. */ + sqlite3GlobalConfig.pcache2 = *va_arg(ap, sqlite3_pcache_methods2*); + break; + } + case SQLITE_CONFIG_GETPCACHE2: { + /* EVIDENCE-OF: R-22035-46182 The SQLITE_CONFIG_GETPCACHE2 option takes a + ** single argument which is a pointer to an sqlite3_pcache_methods2 + ** object. SQLite copies of the current page cache implementation into + ** that object. */ + if( sqlite3GlobalConfig.pcache2.xInit==0 ){ + sqlite3PCacheSetDefault(); + } + *va_arg(ap, sqlite3_pcache_methods2*) = sqlite3GlobalConfig.pcache2; + break; + } + +/* EVIDENCE-OF: R-06626-12911 The SQLITE_CONFIG_HEAP option is only +** available if SQLite is compiled with either SQLITE_ENABLE_MEMSYS3 or +** SQLITE_ENABLE_MEMSYS5 and returns SQLITE_ERROR if invoked otherwise. */ +#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) + case SQLITE_CONFIG_HEAP: { + /* EVIDENCE-OF: R-19854-42126 There are three arguments to + ** SQLITE_CONFIG_HEAP: An 8-byte aligned pointer to the memory, the + ** number of bytes in the memory buffer, and the minimum allocation size. + */ + sqlite3GlobalConfig.pHeap = va_arg(ap, void*); + sqlite3GlobalConfig.nHeap = va_arg(ap, int); + sqlite3GlobalConfig.mnReq = va_arg(ap, int); + + if( sqlite3GlobalConfig.mnReq<1 ){ + sqlite3GlobalConfig.mnReq = 1; + }else if( sqlite3GlobalConfig.mnReq>(1<<12) ){ + /* cap min request size at 2^12 */ + sqlite3GlobalConfig.mnReq = (1<<12); + } + + if( sqlite3GlobalConfig.pHeap==0 ){ + /* EVIDENCE-OF: R-49920-60189 If the first pointer (the memory pointer) + ** is NULL, then SQLite reverts to using its default memory allocator + ** (the system malloc() implementation), undoing any prior invocation of + ** SQLITE_CONFIG_MALLOC. + ** + ** Setting sqlite3GlobalConfig.m to all zeros will cause malloc to + ** revert to its default implementation when sqlite3_initialize() is run + */ + memset(&sqlite3GlobalConfig.m, 0, sizeof(sqlite3GlobalConfig.m)); + }else{ + /* EVIDENCE-OF: R-61006-08918 If the memory pointer is not NULL then the + ** alternative memory allocator is engaged to handle all of SQLites + ** memory allocation needs. */ +#ifdef SQLITE_ENABLE_MEMSYS3 + sqlite3GlobalConfig.m = *sqlite3MemGetMemsys3(); +#endif +#ifdef SQLITE_ENABLE_MEMSYS5 + sqlite3GlobalConfig.m = *sqlite3MemGetMemsys5(); +#endif + } + break; + } +#endif + + case SQLITE_CONFIG_LOOKASIDE: { + sqlite3GlobalConfig.szLookaside = va_arg(ap, int); + sqlite3GlobalConfig.nLookaside = va_arg(ap, int); + break; + } + + /* Record a pointer to the logger function and its first argument. + ** The default is NULL. Logging is disabled if the function pointer is + ** NULL. + */ + case SQLITE_CONFIG_LOG: { + /* MSVC is picky about pulling func ptrs from va lists. + ** http://support.microsoft.com/kb/47961 + ** sqlite3GlobalConfig.xLog = va_arg(ap, void(*)(void*,int,const char*)); + */ + typedef void(*LOGFUNC_t)(void*,int,const char*); + LOGFUNC_t xLog = va_arg(ap, LOGFUNC_t); + void *pLogArg = va_arg(ap, void*); + AtomicStore(&sqlite3GlobalConfig.xLog, xLog); + AtomicStore(&sqlite3GlobalConfig.pLogArg, pLogArg); + break; + } + + /* EVIDENCE-OF: R-55548-33817 The compile-time setting for URI filenames + ** can be changed at start-time using the + ** sqlite3_config(SQLITE_CONFIG_URI,1) or + ** sqlite3_config(SQLITE_CONFIG_URI,0) configuration calls. + */ + case SQLITE_CONFIG_URI: { + /* EVIDENCE-OF: R-25451-61125 The SQLITE_CONFIG_URI option takes a single + ** argument of type int. If non-zero, then URI handling is globally + ** enabled. If the parameter is zero, then URI handling is globally + ** disabled. */ + int bOpenUri = va_arg(ap, int); + AtomicStore(&sqlite3GlobalConfig.bOpenUri, bOpenUri); + break; + } + + case SQLITE_CONFIG_COVERING_INDEX_SCAN: { + /* EVIDENCE-OF: R-36592-02772 The SQLITE_CONFIG_COVERING_INDEX_SCAN + ** option takes a single integer argument which is interpreted as a + ** boolean in order to enable or disable the use of covering indices for + ** full table scans in the query optimizer. */ + sqlite3GlobalConfig.bUseCis = va_arg(ap, int); + break; + } + +#ifdef SQLITE_ENABLE_SQLLOG + case SQLITE_CONFIG_SQLLOG: { + typedef void(*SQLLOGFUNC_t)(void*, sqlite3*, const char*, int); + sqlite3GlobalConfig.xSqllog = va_arg(ap, SQLLOGFUNC_t); + sqlite3GlobalConfig.pSqllogArg = va_arg(ap, void *); + break; + } +#endif + + case SQLITE_CONFIG_MMAP_SIZE: { + /* EVIDENCE-OF: R-58063-38258 SQLITE_CONFIG_MMAP_SIZE takes two 64-bit + ** integer (sqlite3_int64) values that are the default mmap size limit + ** (the default setting for PRAGMA mmap_size) and the maximum allowed + ** mmap size limit. */ + sqlite3_int64 szMmap = va_arg(ap, sqlite3_int64); + sqlite3_int64 mxMmap = va_arg(ap, sqlite3_int64); + /* EVIDENCE-OF: R-53367-43190 If either argument to this option is + ** negative, then that argument is changed to its compile-time default. + ** + ** EVIDENCE-OF: R-34993-45031 The maximum allowed mmap size will be + ** silently truncated if necessary so that it does not exceed the + ** compile-time maximum mmap size set by the SQLITE_MAX_MMAP_SIZE + ** compile-time option. + */ + if( mxMmap<0 || mxMmap>SQLITE_MAX_MMAP_SIZE ){ + mxMmap = SQLITE_MAX_MMAP_SIZE; + } + if( szMmap<0 ) szMmap = SQLITE_DEFAULT_MMAP_SIZE; + if( szMmap>mxMmap) szMmap = mxMmap; + sqlite3GlobalConfig.mxMmap = mxMmap; + sqlite3GlobalConfig.szMmap = szMmap; + break; + } + +#if SQLITE_OS_WIN && defined(SQLITE_WIN32_MALLOC) /* IMP: R-04780-55815 */ + case SQLITE_CONFIG_WIN32_HEAPSIZE: { + /* EVIDENCE-OF: R-34926-03360 SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit + ** unsigned integer value that specifies the maximum size of the created + ** heap. */ + sqlite3GlobalConfig.nHeap = va_arg(ap, int); + break; + } +#endif + + case SQLITE_CONFIG_PMASZ: { + sqlite3GlobalConfig.szPma = va_arg(ap, unsigned int); + break; + } + + case SQLITE_CONFIG_STMTJRNL_SPILL: { + sqlite3GlobalConfig.nStmtSpill = va_arg(ap, int); + break; + } + +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + case SQLITE_CONFIG_SORTERREF_SIZE: { + int iVal = va_arg(ap, int); + if( iVal<0 ){ + iVal = SQLITE_DEFAULT_SORTERREF_SIZE; + } + sqlite3GlobalConfig.szSorterRef = (u32)iVal; + break; + } +#endif /* SQLITE_ENABLE_SORTER_REFERENCES */ + +#ifndef SQLITE_OMIT_DESERIALIZE + case SQLITE_CONFIG_MEMDB_MAXSIZE: { + sqlite3GlobalConfig.mxMemdbSize = va_arg(ap, sqlite3_int64); + break; + } +#endif /* SQLITE_OMIT_DESERIALIZE */ + + default: { + rc = SQLITE_ERROR; + break; + } + } + va_end(ap); + return rc; +} + +/* +** Set up the lookaside buffers for a database connection. +** Return SQLITE_OK on success. +** If lookaside is already active, return SQLITE_BUSY. +** +** The sz parameter is the number of bytes in each lookaside slot. +** The cnt parameter is the number of slots. If pStart is NULL the +** space for the lookaside memory is obtained from sqlite3_malloc(). +** If pStart is not NULL then it is sz*cnt bytes of memory to use for +** the lookaside memory. +*/ +static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){ +#ifndef SQLITE_OMIT_LOOKASIDE + void *pStart; + sqlite3_int64 szAlloc = sz*(sqlite3_int64)cnt; + int nBig; /* Number of full-size slots */ + int nSm; /* Number smaller LOOKASIDE_SMALL-byte slots */ + + if( sqlite3LookasideUsed(db,0)>0 ){ + return SQLITE_BUSY; + } + /* Free any existing lookaside buffer for this handle before + ** allocating a new one so we don't have to have space for + ** both at the same time. + */ + if( db->lookaside.bMalloced ){ + sqlite3_free(db->lookaside.pStart); + } + /* The size of a lookaside slot after ROUNDDOWN8 needs to be larger + ** than a pointer to be useful. + */ + sz = ROUNDDOWN8(sz); /* IMP: R-33038-09382 */ + if( sz<=(int)sizeof(LookasideSlot*) ) sz = 0; + if( cnt<0 ) cnt = 0; + if( sz==0 || cnt==0 ){ + sz = 0; + pStart = 0; + }else if( pBuf==0 ){ + sqlite3BeginBenignMalloc(); + pStart = sqlite3Malloc( szAlloc ); /* IMP: R-61949-35727 */ + sqlite3EndBenignMalloc(); + if( pStart ) szAlloc = sqlite3MallocSize(pStart); + }else{ + pStart = pBuf; + } +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( sz>=LOOKASIDE_SMALL*3 ){ + nBig = szAlloc/(3*LOOKASIDE_SMALL+sz); + nSm = (szAlloc - sz*nBig)/LOOKASIDE_SMALL; + }else if( sz>=LOOKASIDE_SMALL*2 ){ + nBig = szAlloc/(LOOKASIDE_SMALL+sz); + nSm = (szAlloc - sz*nBig)/LOOKASIDE_SMALL; + }else +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + if( sz>0 ){ + nBig = szAlloc/sz; + nSm = 0; + }else{ + nBig = nSm = 0; + } + db->lookaside.pStart = pStart; + db->lookaside.pInit = 0; + db->lookaside.pFree = 0; + db->lookaside.sz = (u16)sz; + db->lookaside.szTrue = (u16)sz; + if( pStart ){ + int i; + LookasideSlot *p; + assert( sz > (int)sizeof(LookasideSlot*) ); + p = (LookasideSlot*)pStart; + for(i=0; i<nBig; i++){ + p->pNext = db->lookaside.pInit; + db->lookaside.pInit = p; + p = (LookasideSlot*)&((u8*)p)[sz]; + } +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + db->lookaside.pSmallInit = 0; + db->lookaside.pSmallFree = 0; + db->lookaside.pMiddle = p; + for(i=0; i<nSm; i++){ + p->pNext = db->lookaside.pSmallInit; + db->lookaside.pSmallInit = p; + p = (LookasideSlot*)&((u8*)p)[LOOKASIDE_SMALL]; + } +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + assert( ((uptr)p)<=szAlloc + (uptr)pStart ); + db->lookaside.pEnd = p; + db->lookaside.bDisable = 0; + db->lookaside.bMalloced = pBuf==0 ?1:0; + db->lookaside.nSlot = nBig+nSm; + }else{ + db->lookaside.pStart = 0; +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + db->lookaside.pSmallInit = 0; + db->lookaside.pSmallFree = 0; + db->lookaside.pMiddle = 0; +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + db->lookaside.pEnd = 0; + db->lookaside.bDisable = 1; + db->lookaside.sz = 0; + db->lookaside.bMalloced = 0; + db->lookaside.nSlot = 0; + } + db->lookaside.pTrueEnd = db->lookaside.pEnd; + assert( sqlite3LookasideUsed(db,0)==0 ); +#endif /* SQLITE_OMIT_LOOKASIDE */ + return SQLITE_OK; +} + +/* +** Return the mutex associated with a database connection. +*/ +SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return db->mutex; +} + +/* +** Free up as much memory as we can from the given database +** connection. +*/ +SQLITE_API int sqlite3_db_release_memory(sqlite3 *db){ + int i; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + sqlite3BtreeEnterAll(db); + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + Pager *pPager = sqlite3BtreePager(pBt); + sqlite3PagerShrink(pPager); + } + } + sqlite3BtreeLeaveAll(db); + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + +/* +** Flush any dirty pages in the pager-cache for any attached database +** to disk. +*/ +SQLITE_API int sqlite3_db_cacheflush(sqlite3 *db){ + int i; + int rc = SQLITE_OK; + int bSeenBusy = 0; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + sqlite3BtreeEnterAll(db); + for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt && sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){ + Pager *pPager = sqlite3BtreePager(pBt); + rc = sqlite3PagerFlush(pPager); + if( rc==SQLITE_BUSY ){ + bSeenBusy = 1; + rc = SQLITE_OK; + } + } + } + sqlite3BtreeLeaveAll(db); + sqlite3_mutex_leave(db->mutex); + return ((rc==SQLITE_OK && bSeenBusy) ? SQLITE_BUSY : rc); +} + +/* +** Configuration settings for an individual database connection +*/ +SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ + va_list ap; + int rc; + sqlite3_mutex_enter(db->mutex); + va_start(ap, op); + switch( op ){ + case SQLITE_DBCONFIG_MAINDBNAME: { + /* IMP: R-06824-28531 */ + /* IMP: R-36257-52125 */ + db->aDb[0].zDbSName = va_arg(ap,char*); + rc = SQLITE_OK; + break; + } + case SQLITE_DBCONFIG_LOOKASIDE: { + void *pBuf = va_arg(ap, void*); /* IMP: R-26835-10964 */ + int sz = va_arg(ap, int); /* IMP: R-47871-25994 */ + int cnt = va_arg(ap, int); /* IMP: R-04460-53386 */ + rc = setupLookaside(db, pBuf, sz, cnt); + break; + } + default: { + static const struct { + int op; /* The opcode */ + u32 mask; /* Mask of the bit in sqlite3.flags to set/clear */ + } aFlagOp[] = { + { SQLITE_DBCONFIG_ENABLE_FKEY, SQLITE_ForeignKeys }, + { SQLITE_DBCONFIG_ENABLE_TRIGGER, SQLITE_EnableTrigger }, + { SQLITE_DBCONFIG_ENABLE_VIEW, SQLITE_EnableView }, + { SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, SQLITE_Fts3Tokenizer }, + { SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_LoadExtension }, + { SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, SQLITE_NoCkptOnClose }, + { SQLITE_DBCONFIG_ENABLE_QPSG, SQLITE_EnableQPSG }, + { SQLITE_DBCONFIG_TRIGGER_EQP, SQLITE_TriggerEQP }, + { SQLITE_DBCONFIG_RESET_DATABASE, SQLITE_ResetDatabase }, + { SQLITE_DBCONFIG_DEFENSIVE, SQLITE_Defensive }, + { SQLITE_DBCONFIG_WRITABLE_SCHEMA, SQLITE_WriteSchema| + SQLITE_NoSchemaError }, + { SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, SQLITE_LegacyAlter }, + { SQLITE_DBCONFIG_DQS_DDL, SQLITE_DqsDDL }, + { SQLITE_DBCONFIG_DQS_DML, SQLITE_DqsDML }, + { SQLITE_DBCONFIG_LEGACY_FILE_FORMAT, SQLITE_LegacyFileFmt }, + { SQLITE_DBCONFIG_TRUSTED_SCHEMA, SQLITE_TrustedSchema }, + { SQLITE_DBCONFIG_STMT_SCANSTATUS, SQLITE_StmtScanStatus }, + { SQLITE_DBCONFIG_REVERSE_SCANORDER, SQLITE_ReverseOrder }, + }; + unsigned int i; + rc = SQLITE_ERROR; /* IMP: R-42790-23372 */ + for(i=0; i<ArraySize(aFlagOp); i++){ + if( aFlagOp[i].op==op ){ + int onoff = va_arg(ap, int); + int *pRes = va_arg(ap, int*); + u64 oldFlags = db->flags; + if( onoff>0 ){ + db->flags |= aFlagOp[i].mask; + }else if( onoff==0 ){ + db->flags &= ~(u64)aFlagOp[i].mask; + } + if( oldFlags!=db->flags ){ + sqlite3ExpirePreparedStatements(db, 0); + } + if( pRes ){ + *pRes = (db->flags & aFlagOp[i].mask)!=0; + } + rc = SQLITE_OK; + break; + } + } + break; + } + } + va_end(ap); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** This is the default collating function named "BINARY" which is always +** available. +*/ +static int binCollFunc( + void *NotUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + int rc, n; + UNUSED_PARAMETER(NotUsed); + n = nKey1<nKey2 ? nKey1 : nKey2; + /* EVIDENCE-OF: R-65033-28449 The built-in BINARY collation compares + ** strings byte by byte using the memcmp() function from the standard C + ** library. */ + assert( pKey1 && pKey2 ); + rc = memcmp(pKey1, pKey2, n); + if( rc==0 ){ + rc = nKey1 - nKey2; + } + return rc; +} + +/* +** This is the collating function named "RTRIM" which is always +** available. Ignore trailing spaces. +*/ +static int rtrimCollFunc( + void *pUser, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + const u8 *pK1 = (const u8*)pKey1; + const u8 *pK2 = (const u8*)pKey2; + while( nKey1 && pK1[nKey1-1]==' ' ) nKey1--; + while( nKey2 && pK2[nKey2-1]==' ' ) nKey2--; + return binCollFunc(pUser, nKey1, pKey1, nKey2, pKey2); +} + +/* +** Return true if CollSeq is the default built-in BINARY. +*/ +SQLITE_PRIVATE int sqlite3IsBinary(const CollSeq *p){ + assert( p==0 || p->xCmp!=binCollFunc || strcmp(p->zName,"BINARY")==0 ); + return p==0 || p->xCmp==binCollFunc; +} + +/* +** Another built-in collating sequence: NOCASE. +** +** This collating sequence is intended to be used for "case independent +** comparison". SQLite's knowledge of upper and lower case equivalents +** extends only to the 26 characters used in the English language. +** +** At the moment there is only a UTF-8 implementation. +*/ +static int nocaseCollatingFunc( + void *NotUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + int r = sqlite3StrNICmp( + (const char *)pKey1, (const char *)pKey2, (nKey1<nKey2)?nKey1:nKey2); + UNUSED_PARAMETER(NotUsed); + if( 0==r ){ + r = nKey1-nKey2; + } + return r; +} + +/* +** Return the ROWID of the most recent insert +*/ +SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return db->lastRowid; +} + +/* +** Set the value returned by the sqlite3_last_insert_rowid() API function. +*/ +SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3 *db, sqlite3_int64 iRowid){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return; + } +#endif + sqlite3_mutex_enter(db->mutex); + db->lastRowid = iRowid; + sqlite3_mutex_leave(db->mutex); +} + +/* +** Return the number of changes in the most recent call to sqlite3_exec(). +*/ +SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return db->nChange; +} +SQLITE_API int sqlite3_changes(sqlite3 *db){ + return (int)sqlite3_changes64(db); +} + +/* +** Return the number of changes since the database handle was opened. +*/ +SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return db->nTotalChange; +} +SQLITE_API int sqlite3_total_changes(sqlite3 *db){ + return (int)sqlite3_total_changes64(db); +} + +/* +** Close all open savepoints. This function only manipulates fields of the +** database handle object, it does not close any savepoints that may be open +** at the b-tree/pager level. +*/ +SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *db){ + while( db->pSavepoint ){ + Savepoint *pTmp = db->pSavepoint; + db->pSavepoint = pTmp->pNext; + sqlite3DbFree(db, pTmp); + } + db->nSavepoint = 0; + db->nStatement = 0; + db->isTransactionSavepoint = 0; +} + +/* +** Invoke the destructor function associated with FuncDef p, if any. Except, +** if this is not the last copy of the function, do not invoke it. Multiple +** copies of a single function are created when create_function() is called +** with SQLITE_ANY as the encoding. +*/ +static void functionDestroy(sqlite3 *db, FuncDef *p){ + FuncDestructor *pDestructor; + assert( (p->funcFlags & SQLITE_FUNC_BUILTIN)==0 ); + pDestructor = p->u.pDestructor; + if( pDestructor ){ + pDestructor->nRef--; + if( pDestructor->nRef==0 ){ + pDestructor->xDestroy(pDestructor->pUserData); + sqlite3DbFree(db, pDestructor); + } + } +} + +/* +** Disconnect all sqlite3_vtab objects that belong to database connection +** db. This is called when db is being closed. +*/ +static void disconnectAllVtab(sqlite3 *db){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + int i; + HashElem *p; + sqlite3BtreeEnterAll(db); + for(i=0; i<db->nDb; i++){ + Schema *pSchema = db->aDb[i].pSchema; + if( pSchema ){ + for(p=sqliteHashFirst(&pSchema->tblHash); p; p=sqliteHashNext(p)){ + Table *pTab = (Table *)sqliteHashData(p); + if( IsVirtual(pTab) ) sqlite3VtabDisconnect(db, pTab); + } + } + } + for(p=sqliteHashFirst(&db->aModule); p; p=sqliteHashNext(p)){ + Module *pMod = (Module *)sqliteHashData(p); + if( pMod->pEpoTab ){ + sqlite3VtabDisconnect(db, pMod->pEpoTab); + } + } + sqlite3VtabUnlockList(db); + sqlite3BtreeLeaveAll(db); +#else + UNUSED_PARAMETER(db); +#endif +} + +/* +** Return TRUE if database connection db has unfinalized prepared +** statements or unfinished sqlite3_backup objects. +*/ +static int connectionIsBusy(sqlite3 *db){ + int j; + assert( sqlite3_mutex_held(db->mutex) ); + if( db->pVdbe ) return 1; + for(j=0; j<db->nDb; j++){ + Btree *pBt = db->aDb[j].pBt; + if( pBt && sqlite3BtreeIsInBackup(pBt) ) return 1; + } + return 0; +} + +/* +** Close an existing SQLite database +*/ +static int sqlite3Close(sqlite3 *db, int forceZombie){ + if( !db ){ + /* EVIDENCE-OF: R-63257-11740 Calling sqlite3_close() or + ** sqlite3_close_v2() with a NULL pointer argument is a harmless no-op. */ + return SQLITE_OK; + } + if( !sqlite3SafetyCheckSickOrOk(db) ){ + return SQLITE_MISUSE_BKPT; + } + sqlite3_mutex_enter(db->mutex); + if( db->mTrace & SQLITE_TRACE_CLOSE ){ + db->trace.xV2(SQLITE_TRACE_CLOSE, db->pTraceArg, db, 0); + } + + /* Force xDisconnect calls on all virtual tables */ + disconnectAllVtab(db); + + /* If a transaction is open, the disconnectAllVtab() call above + ** will not have called the xDisconnect() method on any virtual + ** tables in the db->aVTrans[] array. The following sqlite3VtabRollback() + ** call will do so. We need to do this before the check for active + ** SQL statements below, as the v-table implementation may be storing + ** some prepared statements internally. + */ + sqlite3VtabRollback(db); + + /* Legacy behavior (sqlite3_close() behavior) is to return + ** SQLITE_BUSY if the connection can not be closed immediately. + */ + if( !forceZombie && connectionIsBusy(db) ){ + sqlite3ErrorWithMsg(db, SQLITE_BUSY, "unable to close due to unfinalized " + "statements or unfinished backups"); + sqlite3_mutex_leave(db->mutex); + return SQLITE_BUSY; + } + +#ifdef SQLITE_ENABLE_SQLLOG + if( sqlite3GlobalConfig.xSqllog ){ + /* Closing the handle. Fourth parameter is passed the value 2. */ + sqlite3GlobalConfig.xSqllog(sqlite3GlobalConfig.pSqllogArg, db, 0, 2); + } +#endif + + while( db->pDbData ){ + DbClientData *p = db->pDbData; + db->pDbData = p->pNext; + assert( p->pData!=0 ); + if( p->xDestructor ) p->xDestructor(p->pData); + sqlite3_free(p); + } + + /* Convert the connection into a zombie and then close it. + */ + db->eOpenState = SQLITE_STATE_ZOMBIE; + sqlite3LeaveMutexAndCloseZombie(db); + return SQLITE_OK; +} + +/* +** Return the transaction state for a single databse, or the maximum +** transaction state over all attached databases if zSchema is null. +*/ +SQLITE_API int sqlite3_txn_state(sqlite3 *db, const char *zSchema){ + int iDb, nDb; + int iTxn = -1; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return -1; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( zSchema ){ + nDb = iDb = sqlite3FindDbName(db, zSchema); + if( iDb<0 ) nDb--; + }else{ + iDb = 0; + nDb = db->nDb-1; + } + for(; iDb<=nDb; iDb++){ + Btree *pBt = db->aDb[iDb].pBt; + int x = pBt!=0 ? sqlite3BtreeTxnState(pBt) : SQLITE_TXN_NONE; + if( x>iTxn ) iTxn = x; + } + sqlite3_mutex_leave(db->mutex); + return iTxn; +} + +/* +** Two variations on the public interface for closing a database +** connection. The sqlite3_close() version returns SQLITE_BUSY and +** leaves the connection open if there are unfinalized prepared +** statements or unfinished sqlite3_backups. The sqlite3_close_v2() +** version forces the connection to become a zombie if there are +** unclosed resources, and arranges for deallocation when the last +** prepare statement or sqlite3_backup closes. +*/ +SQLITE_API int sqlite3_close(sqlite3 *db){ return sqlite3Close(db,0); } +SQLITE_API int sqlite3_close_v2(sqlite3 *db){ return sqlite3Close(db,1); } + + +/* +** Close the mutex on database connection db. +** +** Furthermore, if database connection db is a zombie (meaning that there +** has been a prior call to sqlite3_close(db) or sqlite3_close_v2(db)) and +** every sqlite3_stmt has now been finalized and every sqlite3_backup has +** finished, then free all resources. +*/ +SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ + HashElem *i; /* Hash table iterator */ + int j; + + /* If there are outstanding sqlite3_stmt or sqlite3_backup objects + ** or if the connection has not yet been closed by sqlite3_close_v2(), + ** then just leave the mutex and return. + */ + if( db->eOpenState!=SQLITE_STATE_ZOMBIE || connectionIsBusy(db) ){ + sqlite3_mutex_leave(db->mutex); + return; + } + + /* If we reach this point, it means that the database connection has + ** closed all sqlite3_stmt and sqlite3_backup objects and has been + ** passed to sqlite3_close (meaning that it is a zombie). Therefore, + ** go ahead and free all resources. + */ + + /* If a transaction is open, roll it back. This also ensures that if + ** any database schemas have been modified by an uncommitted transaction + ** they are reset. And that the required b-tree mutex is held to make + ** the pager rollback and schema reset an atomic operation. */ + sqlite3RollbackAll(db, SQLITE_OK); + + /* Free any outstanding Savepoint structures. */ + sqlite3CloseSavepoints(db); + + /* Close all database connections */ + for(j=0; j<db->nDb; j++){ + struct Db *pDb = &db->aDb[j]; + if( pDb->pBt ){ + sqlite3BtreeClose(pDb->pBt); + pDb->pBt = 0; + if( j!=1 ){ + pDb->pSchema = 0; + } + } + } + /* Clear the TEMP schema separately and last */ + if( db->aDb[1].pSchema ){ + sqlite3SchemaClear(db->aDb[1].pSchema); + } + sqlite3VtabUnlockList(db); + + /* Free up the array of auxiliary databases */ + sqlite3CollapseDatabaseArray(db); + assert( db->nDb<=2 ); + assert( db->aDb==db->aDbStatic ); + + /* Tell the code in notify.c that the connection no longer holds any + ** locks and does not require any further unlock-notify callbacks. + */ + sqlite3ConnectionClosed(db); + + for(i=sqliteHashFirst(&db->aFunc); i; i=sqliteHashNext(i)){ + FuncDef *pNext, *p; + p = sqliteHashData(i); + do{ + functionDestroy(db, p); + pNext = p->pNext; + sqlite3DbFree(db, p); + p = pNext; + }while( p ); + } + sqlite3HashClear(&db->aFunc); + for(i=sqliteHashFirst(&db->aCollSeq); i; i=sqliteHashNext(i)){ + CollSeq *pColl = (CollSeq *)sqliteHashData(i); + /* Invoke any destructors registered for collation sequence user data. */ + for(j=0; j<3; j++){ + if( pColl[j].xDel ){ + pColl[j].xDel(pColl[j].pUser); + } + } + sqlite3DbFree(db, pColl); + } + sqlite3HashClear(&db->aCollSeq); +#ifndef SQLITE_OMIT_VIRTUALTABLE + for(i=sqliteHashFirst(&db->aModule); i; i=sqliteHashNext(i)){ + Module *pMod = (Module *)sqliteHashData(i); + sqlite3VtabEponymousTableClear(db, pMod); + sqlite3VtabModuleUnref(db, pMod); + } + sqlite3HashClear(&db->aModule); +#endif + + sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */ + sqlite3ValueFree(db->pErr); + sqlite3CloseExtensions(db); +#if SQLITE_USER_AUTHENTICATION + sqlite3_free(db->auth.zAuthUser); + sqlite3_free(db->auth.zAuthPW); +#endif + + db->eOpenState = SQLITE_STATE_ERROR; + + /* The temp-database schema is allocated differently from the other schema + ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()). + ** So it needs to be freed here. Todo: Why not roll the temp schema into + ** the same sqliteMalloc() as the one that allocates the database + ** structure? + */ + sqlite3DbFree(db, db->aDb[1].pSchema); + if( db->xAutovacDestr ){ + db->xAutovacDestr(db->pAutovacPagesArg); + } + sqlite3_mutex_leave(db->mutex); + db->eOpenState = SQLITE_STATE_CLOSED; + sqlite3_mutex_free(db->mutex); + assert( sqlite3LookasideUsed(db,0)==0 ); + if( db->lookaside.bMalloced ){ + sqlite3_free(db->lookaside.pStart); + } + sqlite3_free(db); +} + +/* +** Rollback all database files. If tripCode is not SQLITE_OK, then +** any write cursors are invalidated ("tripped" - as in "tripping a circuit +** breaker") and made to return tripCode if there are any further +** attempts to use that cursor. Read cursors remain open and valid +** but are "saved" in case the table pages are moved around. +*/ +SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db, int tripCode){ + int i; + int inTrans = 0; + int schemaChange; + assert( sqlite3_mutex_held(db->mutex) ); + sqlite3BeginBenignMalloc(); + + /* Obtain all b-tree mutexes before making any calls to BtreeRollback(). + ** This is important in case the transaction being rolled back has + ** modified the database schema. If the b-tree mutexes are not taken + ** here, then another shared-cache connection might sneak in between + ** the database rollback and schema reset, which can cause false + ** corruption reports in some cases. */ + sqlite3BtreeEnterAll(db); + schemaChange = (db->mDbFlags & DBFLAG_SchemaChange)!=0 && db->init.busy==0; + + for(i=0; i<db->nDb; i++){ + Btree *p = db->aDb[i].pBt; + if( p ){ + if( sqlite3BtreeTxnState(p)==SQLITE_TXN_WRITE ){ + inTrans = 1; + } + sqlite3BtreeRollback(p, tripCode, !schemaChange); + } + } + sqlite3VtabRollback(db); + sqlite3EndBenignMalloc(); + + if( schemaChange ){ + sqlite3ExpirePreparedStatements(db, 0); + sqlite3ResetAllSchemasOfConnection(db); + } + sqlite3BtreeLeaveAll(db); + + /* Any deferred constraint violations have now been resolved. */ + db->nDeferredCons = 0; + db->nDeferredImmCons = 0; + db->flags &= ~(u64)(SQLITE_DeferFKs|SQLITE_CorruptRdOnly); + + /* If one has been configured, invoke the rollback-hook callback */ + if( db->xRollbackCallback && (inTrans || !db->autoCommit) ){ + db->xRollbackCallback(db->pRollbackArg); + } +} + +/* +** Return a static string containing the name corresponding to the error code +** specified in the argument. +*/ +#if defined(SQLITE_NEED_ERR_NAME) +SQLITE_PRIVATE const char *sqlite3ErrName(int rc){ + const char *zName = 0; + int i, origRc = rc; + for(i=0; i<2 && zName==0; i++, rc &= 0xff){ + switch( rc ){ + case SQLITE_OK: zName = "SQLITE_OK"; break; + case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; + case SQLITE_ERROR_SNAPSHOT: zName = "SQLITE_ERROR_SNAPSHOT"; break; + case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break; + case SQLITE_PERM: zName = "SQLITE_PERM"; break; + case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; + case SQLITE_ABORT_ROLLBACK: zName = "SQLITE_ABORT_ROLLBACK"; break; + case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; + case SQLITE_BUSY_RECOVERY: zName = "SQLITE_BUSY_RECOVERY"; break; + case SQLITE_BUSY_SNAPSHOT: zName = "SQLITE_BUSY_SNAPSHOT"; break; + case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break; + case SQLITE_LOCKED_SHAREDCACHE: zName = "SQLITE_LOCKED_SHAREDCACHE";break; + case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; + case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; + case SQLITE_READONLY_RECOVERY: zName = "SQLITE_READONLY_RECOVERY"; break; + case SQLITE_READONLY_CANTINIT: zName = "SQLITE_READONLY_CANTINIT"; break; + case SQLITE_READONLY_ROLLBACK: zName = "SQLITE_READONLY_ROLLBACK"; break; + case SQLITE_READONLY_DBMOVED: zName = "SQLITE_READONLY_DBMOVED"; break; + case SQLITE_READONLY_DIRECTORY: zName = "SQLITE_READONLY_DIRECTORY";break; + case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; + case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; + case SQLITE_IOERR_READ: zName = "SQLITE_IOERR_READ"; break; + case SQLITE_IOERR_SHORT_READ: zName = "SQLITE_IOERR_SHORT_READ"; break; + case SQLITE_IOERR_WRITE: zName = "SQLITE_IOERR_WRITE"; break; + case SQLITE_IOERR_FSYNC: zName = "SQLITE_IOERR_FSYNC"; break; + case SQLITE_IOERR_DIR_FSYNC: zName = "SQLITE_IOERR_DIR_FSYNC"; break; + case SQLITE_IOERR_TRUNCATE: zName = "SQLITE_IOERR_TRUNCATE"; break; + case SQLITE_IOERR_FSTAT: zName = "SQLITE_IOERR_FSTAT"; break; + case SQLITE_IOERR_UNLOCK: zName = "SQLITE_IOERR_UNLOCK"; break; + case SQLITE_IOERR_RDLOCK: zName = "SQLITE_IOERR_RDLOCK"; break; + case SQLITE_IOERR_DELETE: zName = "SQLITE_IOERR_DELETE"; break; + case SQLITE_IOERR_NOMEM: zName = "SQLITE_IOERR_NOMEM"; break; + case SQLITE_IOERR_ACCESS: zName = "SQLITE_IOERR_ACCESS"; break; + case SQLITE_IOERR_CHECKRESERVEDLOCK: + zName = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break; + case SQLITE_IOERR_LOCK: zName = "SQLITE_IOERR_LOCK"; break; + case SQLITE_IOERR_CLOSE: zName = "SQLITE_IOERR_CLOSE"; break; + case SQLITE_IOERR_DIR_CLOSE: zName = "SQLITE_IOERR_DIR_CLOSE"; break; + case SQLITE_IOERR_SHMOPEN: zName = "SQLITE_IOERR_SHMOPEN"; break; + case SQLITE_IOERR_SHMSIZE: zName = "SQLITE_IOERR_SHMSIZE"; break; + case SQLITE_IOERR_SHMLOCK: zName = "SQLITE_IOERR_SHMLOCK"; break; + case SQLITE_IOERR_SHMMAP: zName = "SQLITE_IOERR_SHMMAP"; break; + case SQLITE_IOERR_SEEK: zName = "SQLITE_IOERR_SEEK"; break; + case SQLITE_IOERR_DELETE_NOENT: zName = "SQLITE_IOERR_DELETE_NOENT";break; + case SQLITE_IOERR_MMAP: zName = "SQLITE_IOERR_MMAP"; break; + case SQLITE_IOERR_GETTEMPPATH: zName = "SQLITE_IOERR_GETTEMPPATH"; break; + case SQLITE_IOERR_CONVPATH: zName = "SQLITE_IOERR_CONVPATH"; break; + case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break; + case SQLITE_CORRUPT_VTAB: zName = "SQLITE_CORRUPT_VTAB"; break; + case SQLITE_NOTFOUND: zName = "SQLITE_NOTFOUND"; break; + case SQLITE_FULL: zName = "SQLITE_FULL"; break; + case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break; + case SQLITE_CANTOPEN_NOTEMPDIR: zName = "SQLITE_CANTOPEN_NOTEMPDIR";break; + case SQLITE_CANTOPEN_ISDIR: zName = "SQLITE_CANTOPEN_ISDIR"; break; + case SQLITE_CANTOPEN_FULLPATH: zName = "SQLITE_CANTOPEN_FULLPATH"; break; + case SQLITE_CANTOPEN_CONVPATH: zName = "SQLITE_CANTOPEN_CONVPATH"; break; + case SQLITE_CANTOPEN_SYMLINK: zName = "SQLITE_CANTOPEN_SYMLINK"; break; + case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; + case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; + case SQLITE_SCHEMA: zName = "SQLITE_SCHEMA"; break; + case SQLITE_TOOBIG: zName = "SQLITE_TOOBIG"; break; + case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT"; break; + case SQLITE_CONSTRAINT_UNIQUE: zName = "SQLITE_CONSTRAINT_UNIQUE"; break; + case SQLITE_CONSTRAINT_TRIGGER: zName = "SQLITE_CONSTRAINT_TRIGGER";break; + case SQLITE_CONSTRAINT_FOREIGNKEY: + zName = "SQLITE_CONSTRAINT_FOREIGNKEY"; break; + case SQLITE_CONSTRAINT_CHECK: zName = "SQLITE_CONSTRAINT_CHECK"; break; + case SQLITE_CONSTRAINT_PRIMARYKEY: + zName = "SQLITE_CONSTRAINT_PRIMARYKEY"; break; + case SQLITE_CONSTRAINT_NOTNULL: zName = "SQLITE_CONSTRAINT_NOTNULL";break; + case SQLITE_CONSTRAINT_COMMITHOOK: + zName = "SQLITE_CONSTRAINT_COMMITHOOK"; break; + case SQLITE_CONSTRAINT_VTAB: zName = "SQLITE_CONSTRAINT_VTAB"; break; + case SQLITE_CONSTRAINT_FUNCTION: + zName = "SQLITE_CONSTRAINT_FUNCTION"; break; + case SQLITE_CONSTRAINT_ROWID: zName = "SQLITE_CONSTRAINT_ROWID"; break; + case SQLITE_MISMATCH: zName = "SQLITE_MISMATCH"; break; + case SQLITE_MISUSE: zName = "SQLITE_MISUSE"; break; + case SQLITE_NOLFS: zName = "SQLITE_NOLFS"; break; + case SQLITE_AUTH: zName = "SQLITE_AUTH"; break; + case SQLITE_FORMAT: zName = "SQLITE_FORMAT"; break; + case SQLITE_RANGE: zName = "SQLITE_RANGE"; break; + case SQLITE_NOTADB: zName = "SQLITE_NOTADB"; break; + case SQLITE_ROW: zName = "SQLITE_ROW"; break; + case SQLITE_NOTICE: zName = "SQLITE_NOTICE"; break; + case SQLITE_NOTICE_RECOVER_WAL: zName = "SQLITE_NOTICE_RECOVER_WAL";break; + case SQLITE_NOTICE_RECOVER_ROLLBACK: + zName = "SQLITE_NOTICE_RECOVER_ROLLBACK"; break; + case SQLITE_NOTICE_RBU: zName = "SQLITE_NOTICE_RBU"; break; + case SQLITE_WARNING: zName = "SQLITE_WARNING"; break; + case SQLITE_WARNING_AUTOINDEX: zName = "SQLITE_WARNING_AUTOINDEX"; break; + case SQLITE_DONE: zName = "SQLITE_DONE"; break; + } + } + if( zName==0 ){ + static char zBuf[50]; + sqlite3_snprintf(sizeof(zBuf), zBuf, "SQLITE_UNKNOWN(%d)", origRc); + zName = zBuf; + } + return zName; +} +#endif + +/* +** Return a static string that describes the kind of error specified in the +** argument. +*/ +SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){ + static const char* const aMsg[] = { + /* SQLITE_OK */ "not an error", + /* SQLITE_ERROR */ "SQL logic error", + /* SQLITE_INTERNAL */ 0, + /* SQLITE_PERM */ "access permission denied", + /* SQLITE_ABORT */ "query aborted", + /* SQLITE_BUSY */ "database is locked", + /* SQLITE_LOCKED */ "database table is locked", + /* SQLITE_NOMEM */ "out of memory", + /* SQLITE_READONLY */ "attempt to write a readonly database", + /* SQLITE_INTERRUPT */ "interrupted", + /* SQLITE_IOERR */ "disk I/O error", + /* SQLITE_CORRUPT */ "database disk image is malformed", + /* SQLITE_NOTFOUND */ "unknown operation", + /* SQLITE_FULL */ "database or disk is full", + /* SQLITE_CANTOPEN */ "unable to open database file", + /* SQLITE_PROTOCOL */ "locking protocol", + /* SQLITE_EMPTY */ 0, + /* SQLITE_SCHEMA */ "database schema has changed", + /* SQLITE_TOOBIG */ "string or blob too big", + /* SQLITE_CONSTRAINT */ "constraint failed", + /* SQLITE_MISMATCH */ "datatype mismatch", + /* SQLITE_MISUSE */ "bad parameter or other API misuse", +#ifdef SQLITE_DISABLE_LFS + /* SQLITE_NOLFS */ "large file support is disabled", +#else + /* SQLITE_NOLFS */ 0, +#endif + /* SQLITE_AUTH */ "authorization denied", + /* SQLITE_FORMAT */ 0, + /* SQLITE_RANGE */ "column index out of range", + /* SQLITE_NOTADB */ "file is not a database", + /* SQLITE_NOTICE */ "notification message", + /* SQLITE_WARNING */ "warning message", + }; + const char *zErr = "unknown error"; + switch( rc ){ + case SQLITE_ABORT_ROLLBACK: { + zErr = "abort due to ROLLBACK"; + break; + } + case SQLITE_ROW: { + zErr = "another row available"; + break; + } + case SQLITE_DONE: { + zErr = "no more rows available"; + break; + } + default: { + rc &= 0xff; + if( ALWAYS(rc>=0) && rc<ArraySize(aMsg) && aMsg[rc]!=0 ){ + zErr = aMsg[rc]; + } + break; + } + } + return zErr; +} + +/* +** This routine implements a busy callback that sleeps and tries +** again until a timeout value is reached. The timeout value is +** an integer number of milliseconds passed in as the first +** argument. +** +** Return non-zero to retry the lock. Return zero to stop trying +** and cause SQLite to return SQLITE_BUSY. +*/ +static int sqliteDefaultBusyCallback( + void *ptr, /* Database connection */ + int count /* Number of times table has been busy */ +){ +#if SQLITE_OS_WIN || !defined(HAVE_NANOSLEEP) || HAVE_NANOSLEEP + /* This case is for systems that have support for sleeping for fractions of + ** a second. Examples: All windows systems, unix systems with nanosleep() */ + static const u8 delays[] = + { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 }; + static const u8 totals[] = + { 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228 }; +# define NDELAY ArraySize(delays) + sqlite3 *db = (sqlite3 *)ptr; + int tmout = db->busyTimeout; + int delay, prior; + + assert( count>=0 ); + if( count < NDELAY ){ + delay = delays[count]; + prior = totals[count]; + }else{ + delay = delays[NDELAY-1]; + prior = totals[NDELAY-1] + delay*(count-(NDELAY-1)); + } + if( prior + delay > tmout ){ + delay = tmout - prior; + if( delay<=0 ) return 0; + } + sqlite3OsSleep(db->pVfs, delay*1000); + return 1; +#else + /* This case for unix systems that lack usleep() support. Sleeping + ** must be done in increments of whole seconds */ + sqlite3 *db = (sqlite3 *)ptr; + int tmout = ((sqlite3 *)ptr)->busyTimeout; + if( (count+1)*1000 > tmout ){ + return 0; + } + sqlite3OsSleep(db->pVfs, 1000000); + return 1; +#endif +} + +/* +** Invoke the given busy handler. +** +** This routine is called when an operation failed to acquire a +** lock on VFS file pFile. +** +** If this routine returns non-zero, the lock is retried. If it +** returns 0, the operation aborts with an SQLITE_BUSY error. +*/ +SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler *p){ + int rc; + if( p->xBusyHandler==0 || p->nBusy<0 ) return 0; + rc = p->xBusyHandler(p->pBusyArg, p->nBusy); + if( rc==0 ){ + p->nBusy = -1; + }else{ + p->nBusy++; + } + return rc; +} + +/* +** This routine sets the busy callback for an Sqlite database to the +** given callback function with the given argument. +*/ +SQLITE_API int sqlite3_busy_handler( + sqlite3 *db, + int (*xBusy)(void*,int), + void *pArg +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + db->busyHandler.xBusyHandler = xBusy; + db->busyHandler.pBusyArg = pArg; + db->busyHandler.nBusy = 0; + db->busyTimeout = 0; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK +/* +** This routine sets the progress callback for an Sqlite database to the +** given callback function with the given argument. The progress callback will +** be invoked every nOps opcodes. +*/ +SQLITE_API void sqlite3_progress_handler( + sqlite3 *db, + int nOps, + int (*xProgress)(void*), + void *pArg +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( nOps>0 ){ + db->xProgress = xProgress; + db->nProgressOps = (unsigned)nOps; + db->pProgressArg = pArg; + }else{ + db->xProgress = 0; + db->nProgressOps = 0; + db->pProgressArg = 0; + } + sqlite3_mutex_leave(db->mutex); +} +#endif + + +/* +** This routine installs a default busy handler that waits for the +** specified number of milliseconds before returning 0. +*/ +SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + if( ms>0 ){ + sqlite3_busy_handler(db, (int(*)(void*,int))sqliteDefaultBusyCallback, + (void*)db); + db->busyTimeout = ms; + }else{ + sqlite3_busy_handler(db, 0, 0); + } + return SQLITE_OK; +} + +/* +** Cause any pending operation to stop at its earliest opportunity. +*/ +SQLITE_API void sqlite3_interrupt(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) + && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) + ){ + (void)SQLITE_MISUSE_BKPT; + return; + } +#endif + AtomicStore(&db->u1.isInterrupted, 1); +} + +/* +** Return true or false depending on whether or not an interrupt is +** pending on connection db. +*/ +SQLITE_API int sqlite3_is_interrupted(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) + && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) + ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return AtomicLoad(&db->u1.isInterrupted)!=0; +} + +/* +** This function is exactly the same as sqlite3_create_function(), except +** that it is designed to be called by internal code. The difference is +** that if a malloc() fails in sqlite3_create_function(), an error code +** is returned and the mallocFailed flag cleared. +*/ +SQLITE_PRIVATE int sqlite3CreateFunc( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int enc, + void *pUserData, + void (*xSFunc)(sqlite3_context*,int,sqlite3_value **), + void (*xStep)(sqlite3_context*,int,sqlite3_value **), + void (*xFinal)(sqlite3_context*), + void (*xValue)(sqlite3_context*), + void (*xInverse)(sqlite3_context*,int,sqlite3_value **), + FuncDestructor *pDestructor +){ + FuncDef *p; + int extraFlags; + + assert( sqlite3_mutex_held(db->mutex) ); + assert( xValue==0 || xSFunc==0 ); + if( zFunctionName==0 /* Must have a valid name */ + || (xSFunc!=0 && xFinal!=0) /* Not both xSFunc and xFinal */ + || ((xFinal==0)!=(xStep==0)) /* Both or neither of xFinal and xStep */ + || ((xValue==0)!=(xInverse==0)) /* Both or neither of xValue, xInverse */ + || (nArg<-1 || nArg>SQLITE_MAX_FUNCTION_ARG) + || (255<sqlite3Strlen30(zFunctionName)) + ){ + return SQLITE_MISUSE_BKPT; + } + + assert( SQLITE_FUNC_CONSTANT==SQLITE_DETERMINISTIC ); + assert( SQLITE_FUNC_DIRECT==SQLITE_DIRECTONLY ); + extraFlags = enc & (SQLITE_DETERMINISTIC|SQLITE_DIRECTONLY| + SQLITE_SUBTYPE|SQLITE_INNOCUOUS); + enc &= (SQLITE_FUNC_ENCMASK|SQLITE_ANY); + + /* The SQLITE_INNOCUOUS flag is the same bit as SQLITE_FUNC_UNSAFE. But + ** the meaning is inverted. So flip the bit. */ + assert( SQLITE_FUNC_UNSAFE==SQLITE_INNOCUOUS ); + extraFlags ^= SQLITE_FUNC_UNSAFE; /* tag-20230109-1 */ + + +#ifndef SQLITE_OMIT_UTF16 + /* If SQLITE_UTF16 is specified as the encoding type, transform this + ** to one of SQLITE_UTF16LE or SQLITE_UTF16BE using the + ** SQLITE_UTF16NATIVE macro. SQLITE_UTF16 is not used internally. + ** + ** If SQLITE_ANY is specified, add three versions of the function + ** to the hash table. + */ + switch( enc ){ + case SQLITE_UTF16: + enc = SQLITE_UTF16NATIVE; + break; + case SQLITE_ANY: { + int rc; + rc = sqlite3CreateFunc(db, zFunctionName, nArg, + (SQLITE_UTF8|extraFlags)^SQLITE_FUNC_UNSAFE, /* tag-20230109-1 */ + pUserData, xSFunc, xStep, xFinal, xValue, xInverse, pDestructor); + if( rc==SQLITE_OK ){ + rc = sqlite3CreateFunc(db, zFunctionName, nArg, + (SQLITE_UTF16LE|extraFlags)^SQLITE_FUNC_UNSAFE, /* tag-20230109-1*/ + pUserData, xSFunc, xStep, xFinal, xValue, xInverse, pDestructor); + } + if( rc!=SQLITE_OK ){ + return rc; + } + enc = SQLITE_UTF16BE; + break; + } + case SQLITE_UTF8: + case SQLITE_UTF16LE: + case SQLITE_UTF16BE: + break; + default: + enc = SQLITE_UTF8; + break; + } +#else + enc = SQLITE_UTF8; +#endif + + /* Check if an existing function is being overridden or deleted. If so, + ** and there are active VMs, then return SQLITE_BUSY. If a function + ** is being overridden/deleted but there are no active VMs, allow the + ** operation to continue but invalidate all precompiled statements. + */ + p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 0); + if( p && (p->funcFlags & SQLITE_FUNC_ENCMASK)==(u32)enc && p->nArg==nArg ){ + if( db->nVdbeActive ){ + sqlite3ErrorWithMsg(db, SQLITE_BUSY, + "unable to delete/modify user-function due to active statements"); + assert( !db->mallocFailed ); + return SQLITE_BUSY; + }else{ + sqlite3ExpirePreparedStatements(db, 0); + } + }else if( xSFunc==0 && xFinal==0 ){ + /* Trying to delete a function that does not exist. This is a no-op. + ** https://sqlite.org/forum/forumpost/726219164b */ + return SQLITE_OK; + } + + p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 1); + assert(p || db->mallocFailed); + if( !p ){ + return SQLITE_NOMEM_BKPT; + } + + /* If an older version of the function with a configured destructor is + ** being replaced invoke the destructor function here. */ + functionDestroy(db, p); + + if( pDestructor ){ + pDestructor->nRef++; + } + p->u.pDestructor = pDestructor; + p->funcFlags = (p->funcFlags & SQLITE_FUNC_ENCMASK) | extraFlags; + testcase( p->funcFlags & SQLITE_DETERMINISTIC ); + testcase( p->funcFlags & SQLITE_DIRECTONLY ); + p->xSFunc = xSFunc ? xSFunc : xStep; + p->xFinalize = xFinal; + p->xValue = xValue; + p->xInverse = xInverse; + p->pUserData = pUserData; + p->nArg = (u16)nArg; + return SQLITE_OK; +} + +/* +** Worker function used by utf-8 APIs that create new functions: +** +** sqlite3_create_function() +** sqlite3_create_function_v2() +** sqlite3_create_window_function() +*/ +static int createFunctionApi( + sqlite3 *db, + const char *zFunc, + int nArg, + int enc, + void *p, + void (*xSFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void (*xValue)(sqlite3_context*), + void (*xInverse)(sqlite3_context*,int,sqlite3_value**), + void(*xDestroy)(void*) +){ + int rc = SQLITE_ERROR; + FuncDestructor *pArg = 0; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( xDestroy ){ + pArg = (FuncDestructor *)sqlite3Malloc(sizeof(FuncDestructor)); + if( !pArg ){ + sqlite3OomFault(db); + xDestroy(p); + goto out; + } + pArg->nRef = 0; + pArg->xDestroy = xDestroy; + pArg->pUserData = p; + } + rc = sqlite3CreateFunc(db, zFunc, nArg, enc, p, + xSFunc, xStep, xFinal, xValue, xInverse, pArg + ); + if( pArg && pArg->nRef==0 ){ + assert( rc!=SQLITE_OK || (xStep==0 && xFinal==0) ); + xDestroy(p); + sqlite3_free(pArg); + } + + out: + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Create new user functions. +*/ +SQLITE_API int sqlite3_create_function( + sqlite3 *db, + const char *zFunc, + int nArg, + int enc, + void *p, + void (*xSFunc)(sqlite3_context*,int,sqlite3_value **), + void (*xStep)(sqlite3_context*,int,sqlite3_value **), + void (*xFinal)(sqlite3_context*) +){ + return createFunctionApi(db, zFunc, nArg, enc, p, xSFunc, xStep, + xFinal, 0, 0, 0); +} +SQLITE_API int sqlite3_create_function_v2( + sqlite3 *db, + const char *zFunc, + int nArg, + int enc, + void *p, + void (*xSFunc)(sqlite3_context*,int,sqlite3_value **), + void (*xStep)(sqlite3_context*,int,sqlite3_value **), + void (*xFinal)(sqlite3_context*), + void (*xDestroy)(void *) +){ + return createFunctionApi(db, zFunc, nArg, enc, p, xSFunc, xStep, + xFinal, 0, 0, xDestroy); +} +SQLITE_API int sqlite3_create_window_function( + sqlite3 *db, + const char *zFunc, + int nArg, + int enc, + void *p, + void (*xStep)(sqlite3_context*,int,sqlite3_value **), + void (*xFinal)(sqlite3_context*), + void (*xValue)(sqlite3_context*), + void (*xInverse)(sqlite3_context*,int,sqlite3_value **), + void (*xDestroy)(void *) +){ + return createFunctionApi(db, zFunc, nArg, enc, p, 0, xStep, + xFinal, xValue, xInverse, xDestroy); +} + +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API int sqlite3_create_function16( + sqlite3 *db, + const void *zFunctionName, + int nArg, + int eTextRep, + void *p, + void (*xSFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +){ + int rc; + char *zFunc8; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zFunctionName==0 ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + assert( !db->mallocFailed ); + zFunc8 = sqlite3Utf16to8(db, zFunctionName, -1, SQLITE_UTF16NATIVE); + rc = sqlite3CreateFunc(db, zFunc8, nArg, eTextRep, p, xSFunc,xStep,xFinal,0,0,0); + sqlite3DbFree(db, zFunc8); + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} +#endif + + +/* +** The following is the implementation of an SQL function that always +** fails with an error message stating that the function is used in the +** wrong context. The sqlite3_overload_function() API might construct +** SQL function that use this routine so that the functions will exist +** for name resolution but are actually overloaded by the xFindFunction +** method of virtual tables. +*/ +static void sqlite3InvalidFunction( + sqlite3_context *context, /* The function calling context */ + int NotUsed, /* Number of arguments to the function */ + sqlite3_value **NotUsed2 /* Value of each argument */ +){ + const char *zName = (const char*)sqlite3_user_data(context); + char *zErr; + UNUSED_PARAMETER2(NotUsed, NotUsed2); + zErr = sqlite3_mprintf( + "unable to use function %s in the requested context", zName); + sqlite3_result_error(context, zErr, -1); + sqlite3_free(zErr); +} + +/* +** Declare that a function has been overloaded by a virtual table. +** +** If the function already exists as a regular global function, then +** this routine is a no-op. If the function does not exist, then create +** a new one that always throws a run-time error. +** +** When virtual tables intend to provide an overloaded function, they +** should call this routine to make sure the global function exists. +** A global function must exist in order for name resolution to work +** properly. +*/ +SQLITE_API int sqlite3_overload_function( + sqlite3 *db, + const char *zName, + int nArg +){ + int rc; + char *zCopy; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 || nArg<-2 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + rc = sqlite3FindFunction(db, zName, nArg, SQLITE_UTF8, 0)!=0; + sqlite3_mutex_leave(db->mutex); + if( rc ) return SQLITE_OK; + zCopy = sqlite3_mprintf("%s", zName); + if( zCopy==0 ) return SQLITE_NOMEM; + return sqlite3_create_function_v2(db, zName, nArg, SQLITE_UTF8, + zCopy, sqlite3InvalidFunction, 0, 0, sqlite3_free); +} + +#ifndef SQLITE_OMIT_TRACE +/* +** Register a trace function. The pArg from the previously registered trace +** is returned. +** +** A NULL trace function means that no tracing is executes. A non-NULL +** trace is a pointer to a function that is invoked at the start of each +** SQL statement. +*/ +#ifndef SQLITE_OMIT_DEPRECATED +SQLITE_API void *sqlite3_trace(sqlite3 *db, void(*xTrace)(void*,const char*), void *pArg){ + void *pOld; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + sqlite3_mutex_enter(db->mutex); + pOld = db->pTraceArg; + db->mTrace = xTrace ? SQLITE_TRACE_LEGACY : 0; + db->trace.xLegacy = xTrace; + db->pTraceArg = pArg; + sqlite3_mutex_leave(db->mutex); + return pOld; +} +#endif /* SQLITE_OMIT_DEPRECATED */ + +/* Register a trace callback using the version-2 interface. +*/ +SQLITE_API int sqlite3_trace_v2( + sqlite3 *db, /* Trace this connection */ + unsigned mTrace, /* Mask of events to be traced */ + int(*xTrace)(unsigned,void*,void*,void*), /* Callback to invoke */ + void *pArg /* Context */ +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( mTrace==0 ) xTrace = 0; + if( xTrace==0 ) mTrace = 0; + db->mTrace = mTrace; + db->trace.xV2 = xTrace; + db->pTraceArg = pArg; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + +#ifndef SQLITE_OMIT_DEPRECATED +/* +** Register a profile function. The pArg from the previously registered +** profile function is returned. +** +** A NULL profile function means that no profiling is executes. A non-NULL +** profile is a pointer to a function that is invoked at the conclusion of +** each SQL statement that is run. +*/ +SQLITE_API void *sqlite3_profile( + sqlite3 *db, + void (*xProfile)(void*,const char*,sqlite_uint64), + void *pArg +){ + void *pOld; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + sqlite3_mutex_enter(db->mutex); + pOld = db->pProfileArg; + db->xProfile = xProfile; + db->pProfileArg = pArg; + db->mTrace &= SQLITE_TRACE_NONLEGACY_MASK; + if( db->xProfile ) db->mTrace |= SQLITE_TRACE_XPROFILE; + sqlite3_mutex_leave(db->mutex); + return pOld; +} +#endif /* SQLITE_OMIT_DEPRECATED */ +#endif /* SQLITE_OMIT_TRACE */ + +/* +** Register a function to be invoked when a transaction commits. +** If the invoked function returns non-zero, then the commit becomes a +** rollback. +*/ +SQLITE_API void *sqlite3_commit_hook( + sqlite3 *db, /* Attach the hook to this database */ + int (*xCallback)(void*), /* Function to invoke on each commit */ + void *pArg /* Argument to the function */ +){ + void *pOld; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + sqlite3_mutex_enter(db->mutex); + pOld = db->pCommitArg; + db->xCommitCallback = xCallback; + db->pCommitArg = pArg; + sqlite3_mutex_leave(db->mutex); + return pOld; +} + +/* +** Register a callback to be invoked each time a row is updated, +** inserted or deleted using this database connection. +*/ +SQLITE_API void *sqlite3_update_hook( + sqlite3 *db, /* Attach the hook to this database */ + void (*xCallback)(void*,int,char const *,char const *,sqlite_int64), + void *pArg /* Argument to the function */ +){ + void *pRet; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + sqlite3_mutex_enter(db->mutex); + pRet = db->pUpdateArg; + db->xUpdateCallback = xCallback; + db->pUpdateArg = pArg; + sqlite3_mutex_leave(db->mutex); + return pRet; +} + +/* +** Register a callback to be invoked each time a transaction is rolled +** back by this database connection. +*/ +SQLITE_API void *sqlite3_rollback_hook( + sqlite3 *db, /* Attach the hook to this database */ + void (*xCallback)(void*), /* Callback function */ + void *pArg /* Argument to the function */ +){ + void *pRet; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + sqlite3_mutex_enter(db->mutex); + pRet = db->pRollbackArg; + db->xRollbackCallback = xCallback; + db->pRollbackArg = pArg; + sqlite3_mutex_leave(db->mutex); + return pRet; +} + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** Register a callback to be invoked each time a row is updated, +** inserted or deleted using this database connection. +*/ +SQLITE_API void *sqlite3_preupdate_hook( + sqlite3 *db, /* Attach the hook to this database */ + void(*xCallback)( /* Callback function */ + void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64), + void *pArg /* First callback argument */ +){ + void *pRet; + sqlite3_mutex_enter(db->mutex); + pRet = db->pPreUpdateArg; + db->xPreUpdateCallback = xCallback; + db->pPreUpdateArg = pArg; + sqlite3_mutex_leave(db->mutex); + return pRet; +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +/* +** Register a function to be invoked prior to each autovacuum that +** determines the number of pages to vacuum. +*/ +SQLITE_API int sqlite3_autovacuum_pages( + sqlite3 *db, /* Attach the hook to this database */ + unsigned int (*xCallback)(void*,const char*,u32,u32,u32), + void *pArg, /* Argument to the function */ + void (*xDestructor)(void*) /* Destructor for pArg */ +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + if( xDestructor ) xDestructor(pArg); + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( db->xAutovacDestr ){ + db->xAutovacDestr(db->pAutovacPagesArg); + } + db->xAutovacPages = xCallback; + db->pAutovacPagesArg = pArg; + db->xAutovacDestr = xDestructor; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + + +#ifndef SQLITE_OMIT_WAL +/* +** The sqlite3_wal_hook() callback registered by sqlite3_wal_autocheckpoint(). +** Invoke sqlite3_wal_checkpoint if the number of frames in the log file +** is greater than sqlite3.pWalArg cast to an integer (the value configured by +** wal_autocheckpoint()). +*/ +SQLITE_PRIVATE int sqlite3WalDefaultHook( + void *pClientData, /* Argument */ + sqlite3 *db, /* Connection */ + const char *zDb, /* Database */ + int nFrame /* Size of WAL */ +){ + if( nFrame>=SQLITE_PTR_TO_INT(pClientData) ){ + sqlite3BeginBenignMalloc(); + sqlite3_wal_checkpoint(db, zDb); + sqlite3EndBenignMalloc(); + } + return SQLITE_OK; +} +#endif /* SQLITE_OMIT_WAL */ + +/* +** Configure an sqlite3_wal_hook() callback to automatically checkpoint +** a database after committing a transaction if there are nFrame or +** more frames in the log file. Passing zero or a negative value as the +** nFrame parameter disables automatic checkpoints entirely. +** +** The callback registered by this function replaces any existing callback +** registered using sqlite3_wal_hook(). Likewise, registering a callback +** using sqlite3_wal_hook() disables the automatic checkpoint mechanism +** configured by this function. +*/ +SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int nFrame){ +#ifdef SQLITE_OMIT_WAL + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(nFrame); +#else +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + if( nFrame>0 ){ + sqlite3_wal_hook(db, sqlite3WalDefaultHook, SQLITE_INT_TO_PTR(nFrame)); + }else{ + sqlite3_wal_hook(db, 0, 0); + } +#endif + return SQLITE_OK; +} + +/* +** Register a callback to be invoked each time a transaction is written +** into the write-ahead-log by this database connection. +*/ +SQLITE_API void *sqlite3_wal_hook( + sqlite3 *db, /* Attach the hook to this db handle */ + int(*xCallback)(void *, sqlite3*, const char*, int), + void *pArg /* First argument passed to xCallback() */ +){ +#ifndef SQLITE_OMIT_WAL + void *pRet; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + sqlite3_mutex_enter(db->mutex); + pRet = db->pWalArg; + db->xWalCallback = xCallback; + db->pWalArg = pArg; + sqlite3_mutex_leave(db->mutex); + return pRet; +#else + return 0; +#endif +} + +/* +** Checkpoint database zDb. +*/ +SQLITE_API int sqlite3_wal_checkpoint_v2( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of attached database (or NULL) */ + int eMode, /* SQLITE_CHECKPOINT_* value */ + int *pnLog, /* OUT: Size of WAL log in frames */ + int *pnCkpt /* OUT: Total number of frames checkpointed */ +){ +#ifdef SQLITE_OMIT_WAL + return SQLITE_OK; +#else + int rc; /* Return code */ + int iDb; /* Schema to checkpoint */ + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + + /* Initialize the output variables to -1 in case an error occurs. */ + if( pnLog ) *pnLog = -1; + if( pnCkpt ) *pnCkpt = -1; + + assert( SQLITE_CHECKPOINT_PASSIVE==0 ); + assert( SQLITE_CHECKPOINT_FULL==1 ); + assert( SQLITE_CHECKPOINT_RESTART==2 ); + assert( SQLITE_CHECKPOINT_TRUNCATE==3 ); + if( eMode<SQLITE_CHECKPOINT_PASSIVE || eMode>SQLITE_CHECKPOINT_TRUNCATE ){ + /* EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint + ** mode: */ + return SQLITE_MISUSE_BKPT; + } + + sqlite3_mutex_enter(db->mutex); + if( zDb && zDb[0] ){ + iDb = sqlite3FindDbName(db, zDb); + }else{ + iDb = SQLITE_MAX_DB; /* This means process all schemas */ + } + if( iDb<0 ){ + rc = SQLITE_ERROR; + sqlite3ErrorWithMsg(db, SQLITE_ERROR, "unknown database: %s", zDb); + }else{ + db->busyHandler.nBusy = 0; + rc = sqlite3Checkpoint(db, iDb, eMode, pnLog, pnCkpt); + sqlite3Error(db, rc); + } + rc = sqlite3ApiExit(db, rc); + + /* If there are no active statements, clear the interrupt flag at this + ** point. */ + if( db->nVdbeActive==0 ){ + AtomicStore(&db->u1.isInterrupted, 0); + } + + sqlite3_mutex_leave(db->mutex); + return rc; +#endif +} + + +/* +** Checkpoint database zDb. If zDb is NULL, or if the buffer zDb points +** to contains a zero-length string, all attached databases are +** checkpointed. +*/ +SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){ + /* EVIDENCE-OF: R-41613-20553 The sqlite3_wal_checkpoint(D,X) is equivalent to + ** sqlite3_wal_checkpoint_v2(D,X,SQLITE_CHECKPOINT_PASSIVE,0,0). */ + return sqlite3_wal_checkpoint_v2(db,zDb,SQLITE_CHECKPOINT_PASSIVE,0,0); +} + +#ifndef SQLITE_OMIT_WAL +/* +** Run a checkpoint on database iDb. This is a no-op if database iDb is +** not currently open in WAL mode. +** +** If a transaction is open on the database being checkpointed, this +** function returns SQLITE_LOCKED and a checkpoint is not attempted. If +** an error occurs while running the checkpoint, an SQLite error code is +** returned (i.e. SQLITE_IOERR). Otherwise, SQLITE_OK. +** +** The mutex on database handle db should be held by the caller. The mutex +** associated with the specific b-tree being checkpointed is taken by +** this function while the checkpoint is running. +** +** If iDb is passed SQLITE_MAX_DB then all attached databases are +** checkpointed. If an error is encountered it is returned immediately - +** no attempt is made to checkpoint any remaining databases. +** +** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL, RESTART +** or TRUNCATE. +*/ +SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3 *db, int iDb, int eMode, int *pnLog, int *pnCkpt){ + int rc = SQLITE_OK; /* Return code */ + int i; /* Used to iterate through attached dbs */ + int bBusy = 0; /* True if SQLITE_BUSY has been encountered */ + + assert( sqlite3_mutex_held(db->mutex) ); + assert( !pnLog || *pnLog==-1 ); + assert( !pnCkpt || *pnCkpt==-1 ); + testcase( iDb==SQLITE_MAX_ATTACHED ); /* See forum post a006d86f72 */ + testcase( iDb==SQLITE_MAX_DB ); + + for(i=0; i<db->nDb && rc==SQLITE_OK; i++){ + if( i==iDb || iDb==SQLITE_MAX_DB ){ + rc = sqlite3BtreeCheckpoint(db->aDb[i].pBt, eMode, pnLog, pnCkpt); + pnLog = 0; + pnCkpt = 0; + if( rc==SQLITE_BUSY ){ + bBusy = 1; + rc = SQLITE_OK; + } + } + } + + return (rc==SQLITE_OK && bBusy) ? SQLITE_BUSY : rc; +} +#endif /* SQLITE_OMIT_WAL */ + +/* +** This function returns true if main-memory should be used instead of +** a temporary file for transient pager files and statement journals. +** The value returned depends on the value of db->temp_store (runtime +** parameter) and the compile time value of SQLITE_TEMP_STORE. The +** following table describes the relationship between these two values +** and this functions return value. +** +** SQLITE_TEMP_STORE db->temp_store Location of temporary database +** ----------------- -------------- ------------------------------ +** 0 any file (return 0) +** 1 1 file (return 0) +** 1 2 memory (return 1) +** 1 0 file (return 0) +** 2 1 file (return 0) +** 2 2 memory (return 1) +** 2 0 memory (return 1) +** 3 any memory (return 1) +*/ +SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3 *db){ +#if SQLITE_TEMP_STORE==1 + return ( db->temp_store==2 ); +#endif +#if SQLITE_TEMP_STORE==2 + return ( db->temp_store!=1 ); +#endif +#if SQLITE_TEMP_STORE==3 + UNUSED_PARAMETER(db); + return 1; +#endif +#if SQLITE_TEMP_STORE<1 || SQLITE_TEMP_STORE>3 + UNUSED_PARAMETER(db); + return 0; +#endif +} + +/* +** Return UTF-8 encoded English language explanation of the most recent +** error. +*/ +SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){ + const char *z; + if( !db ){ + return sqlite3ErrStr(SQLITE_NOMEM_BKPT); + } + if( !sqlite3SafetyCheckSickOrOk(db) ){ + return sqlite3ErrStr(SQLITE_MISUSE_BKPT); + } + sqlite3_mutex_enter(db->mutex); + if( db->mallocFailed ){ + z = sqlite3ErrStr(SQLITE_NOMEM_BKPT); + }else{ + testcase( db->pErr==0 ); + z = db->errCode ? (char*)sqlite3_value_text(db->pErr) : 0; + assert( !db->mallocFailed ); + if( z==0 ){ + z = sqlite3ErrStr(db->errCode); + } + } + sqlite3_mutex_leave(db->mutex); + return z; +} + +/* +** Return the byte offset of the most recent error +*/ +SQLITE_API int sqlite3_error_offset(sqlite3 *db){ + int iOffset = -1; + if( db && sqlite3SafetyCheckSickOrOk(db) && db->errCode ){ + sqlite3_mutex_enter(db->mutex); + iOffset = db->errByteOffset; + sqlite3_mutex_leave(db->mutex); + } + return iOffset; +} + +#ifndef SQLITE_OMIT_UTF16 +/* +** Return UTF-16 encoded English language explanation of the most recent +** error. +*/ +SQLITE_API const void *sqlite3_errmsg16(sqlite3 *db){ + static const u16 outOfMem[] = { + 'o', 'u', 't', ' ', 'o', 'f', ' ', 'm', 'e', 'm', 'o', 'r', 'y', 0 + }; + static const u16 misuse[] = { + 'b', 'a', 'd', ' ', 'p', 'a', 'r', 'a', 'm', 'e', 't', 'e', 'r', ' ', + 'o', 'r', ' ', 'o', 't', 'h', 'e', 'r', ' ', 'A', 'P', 'I', ' ', + 'm', 'i', 's', 'u', 's', 'e', 0 + }; + + const void *z; + if( !db ){ + return (void *)outOfMem; + } + if( !sqlite3SafetyCheckSickOrOk(db) ){ + return (void *)misuse; + } + sqlite3_mutex_enter(db->mutex); + if( db->mallocFailed ){ + z = (void *)outOfMem; + }else{ + z = sqlite3_value_text16(db->pErr); + if( z==0 ){ + sqlite3ErrorWithMsg(db, db->errCode, sqlite3ErrStr(db->errCode)); + z = sqlite3_value_text16(db->pErr); + } + /* A malloc() may have failed within the call to sqlite3_value_text16() + ** above. If this is the case, then the db->mallocFailed flag needs to + ** be cleared before returning. Do this directly, instead of via + ** sqlite3ApiExit(), to avoid setting the database handle error message. + */ + sqlite3OomClear(db); + } + sqlite3_mutex_leave(db->mutex); + return z; +} +#endif /* SQLITE_OMIT_UTF16 */ + +/* +** Return the most recent error code generated by an SQLite routine. If NULL is +** passed to this function, we assume a malloc() failed during sqlite3_open(). +*/ +SQLITE_API int sqlite3_errcode(sqlite3 *db){ + if( db && !sqlite3SafetyCheckSickOrOk(db) ){ + return SQLITE_MISUSE_BKPT; + } + if( !db || db->mallocFailed ){ + return SQLITE_NOMEM_BKPT; + } + return db->errCode & db->errMask; +} +SQLITE_API int sqlite3_extended_errcode(sqlite3 *db){ + if( db && !sqlite3SafetyCheckSickOrOk(db) ){ + return SQLITE_MISUSE_BKPT; + } + if( !db || db->mallocFailed ){ + return SQLITE_NOMEM_BKPT; + } + return db->errCode; +} +SQLITE_API int sqlite3_system_errno(sqlite3 *db){ + return db ? db->iSysErrno : 0; +} + +/* +** Return a string that describes the kind of error specified in the +** argument. For now, this simply calls the internal sqlite3ErrStr() +** function. +*/ +SQLITE_API const char *sqlite3_errstr(int rc){ + return sqlite3ErrStr(rc); +} + +/* +** Create a new collating function for database "db". The name is zName +** and the encoding is enc. +*/ +static int createCollation( + sqlite3* db, + const char *zName, + u8 enc, + void* pCtx, + int(*xCompare)(void*,int,const void*,int,const void*), + void(*xDel)(void*) +){ + CollSeq *pColl; + int enc2; + + assert( sqlite3_mutex_held(db->mutex) ); + + /* If SQLITE_UTF16 is specified as the encoding type, transform this + ** to one of SQLITE_UTF16LE or SQLITE_UTF16BE using the + ** SQLITE_UTF16NATIVE macro. SQLITE_UTF16 is not used internally. + */ + enc2 = enc; + testcase( enc2==SQLITE_UTF16 ); + testcase( enc2==SQLITE_UTF16_ALIGNED ); + if( enc2==SQLITE_UTF16 || enc2==SQLITE_UTF16_ALIGNED ){ + enc2 = SQLITE_UTF16NATIVE; + } + if( enc2<SQLITE_UTF8 || enc2>SQLITE_UTF16BE ){ + return SQLITE_MISUSE_BKPT; + } + + /* Check if this call is removing or replacing an existing collation + ** sequence. If so, and there are active VMs, return busy. If there + ** are no active VMs, invalidate any pre-compiled statements. + */ + pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 0); + if( pColl && pColl->xCmp ){ + if( db->nVdbeActive ){ + sqlite3ErrorWithMsg(db, SQLITE_BUSY, + "unable to delete/modify collation sequence due to active statements"); + return SQLITE_BUSY; + } + sqlite3ExpirePreparedStatements(db, 0); + + /* If collation sequence pColl was created directly by a call to + ** sqlite3_create_collation, and not generated by synthCollSeq(), + ** then any copies made by synthCollSeq() need to be invalidated. + ** Also, collation destructor - CollSeq.xDel() - function may need + ** to be called. + */ + if( (pColl->enc & ~SQLITE_UTF16_ALIGNED)==enc2 ){ + CollSeq *aColl = sqlite3HashFind(&db->aCollSeq, zName); + int j; + for(j=0; j<3; j++){ + CollSeq *p = &aColl[j]; + if( p->enc==pColl->enc ){ + if( p->xDel ){ + p->xDel(p->pUser); + } + p->xCmp = 0; + } + } + } + } + + pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 1); + if( pColl==0 ) return SQLITE_NOMEM_BKPT; + pColl->xCmp = xCompare; + pColl->pUser = pCtx; + pColl->xDel = xDel; + pColl->enc = (u8)(enc2 | (enc & SQLITE_UTF16_ALIGNED)); + sqlite3Error(db, SQLITE_OK); + return SQLITE_OK; +} + + +/* +** This array defines hard upper bounds on limit values. The +** initializer must be kept in sync with the SQLITE_LIMIT_* +** #defines in sqlite3.h. +*/ +static const int aHardLimit[] = { + SQLITE_MAX_LENGTH, + SQLITE_MAX_SQL_LENGTH, + SQLITE_MAX_COLUMN, + SQLITE_MAX_EXPR_DEPTH, + SQLITE_MAX_COMPOUND_SELECT, + SQLITE_MAX_VDBE_OP, + SQLITE_MAX_FUNCTION_ARG, + SQLITE_MAX_ATTACHED, + SQLITE_MAX_LIKE_PATTERN_LENGTH, + SQLITE_MAX_VARIABLE_NUMBER, /* IMP: R-38091-32352 */ + SQLITE_MAX_TRIGGER_DEPTH, + SQLITE_MAX_WORKER_THREADS, +}; + +/* +** Make sure the hard limits are set to reasonable values +*/ +#if SQLITE_MAX_LENGTH<100 +# error SQLITE_MAX_LENGTH must be at least 100 +#endif +#if SQLITE_MAX_SQL_LENGTH<100 +# error SQLITE_MAX_SQL_LENGTH must be at least 100 +#endif +#if SQLITE_MAX_SQL_LENGTH>SQLITE_MAX_LENGTH +# error SQLITE_MAX_SQL_LENGTH must not be greater than SQLITE_MAX_LENGTH +#endif +#if SQLITE_MAX_COMPOUND_SELECT<2 +# error SQLITE_MAX_COMPOUND_SELECT must be at least 2 +#endif +#if SQLITE_MAX_VDBE_OP<40 +# error SQLITE_MAX_VDBE_OP must be at least 40 +#endif +#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>127 +# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 127 +#endif +#if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125 +# error SQLITE_MAX_ATTACHED must be between 0 and 125 +#endif +#if SQLITE_MAX_LIKE_PATTERN_LENGTH<1 +# error SQLITE_MAX_LIKE_PATTERN_LENGTH must be at least 1 +#endif +#if SQLITE_MAX_COLUMN>32767 +# error SQLITE_MAX_COLUMN must not exceed 32767 +#endif +#if SQLITE_MAX_TRIGGER_DEPTH<1 +# error SQLITE_MAX_TRIGGER_DEPTH must be at least 1 +#endif +#if SQLITE_MAX_WORKER_THREADS<0 || SQLITE_MAX_WORKER_THREADS>50 +# error SQLITE_MAX_WORKER_THREADS must be between 0 and 50 +#endif + + +/* +** Change the value of a limit. Report the old value. +** If an invalid limit index is supplied, report -1. +** Make no changes but still report the old value if the +** new limit is negative. +** +** A new lower limit does not shrink existing constructs. +** It merely prevents new constructs that exceed the limit +** from forming. +*/ +SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ + int oldLimit; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return -1; + } +#endif + + /* EVIDENCE-OF: R-30189-54097 For each limit category SQLITE_LIMIT_NAME + ** there is a hard upper bound set at compile-time by a C preprocessor + ** macro called SQLITE_MAX_NAME. (The "_LIMIT_" in the name is changed to + ** "_MAX_".) + */ + assert( aHardLimit[SQLITE_LIMIT_LENGTH]==SQLITE_MAX_LENGTH ); + assert( aHardLimit[SQLITE_LIMIT_SQL_LENGTH]==SQLITE_MAX_SQL_LENGTH ); + assert( aHardLimit[SQLITE_LIMIT_COLUMN]==SQLITE_MAX_COLUMN ); + assert( aHardLimit[SQLITE_LIMIT_EXPR_DEPTH]==SQLITE_MAX_EXPR_DEPTH ); + assert( aHardLimit[SQLITE_LIMIT_COMPOUND_SELECT]==SQLITE_MAX_COMPOUND_SELECT); + assert( aHardLimit[SQLITE_LIMIT_VDBE_OP]==SQLITE_MAX_VDBE_OP ); + assert( aHardLimit[SQLITE_LIMIT_FUNCTION_ARG]==SQLITE_MAX_FUNCTION_ARG ); + assert( aHardLimit[SQLITE_LIMIT_ATTACHED]==SQLITE_MAX_ATTACHED ); + assert( aHardLimit[SQLITE_LIMIT_LIKE_PATTERN_LENGTH]== + SQLITE_MAX_LIKE_PATTERN_LENGTH ); + assert( aHardLimit[SQLITE_LIMIT_VARIABLE_NUMBER]==SQLITE_MAX_VARIABLE_NUMBER); + assert( aHardLimit[SQLITE_LIMIT_TRIGGER_DEPTH]==SQLITE_MAX_TRIGGER_DEPTH ); + assert( aHardLimit[SQLITE_LIMIT_WORKER_THREADS]==SQLITE_MAX_WORKER_THREADS ); + assert( SQLITE_LIMIT_WORKER_THREADS==(SQLITE_N_LIMIT-1) ); + + + if( limitId<0 || limitId>=SQLITE_N_LIMIT ){ + return -1; + } + oldLimit = db->aLimit[limitId]; + if( newLimit>=0 ){ /* IMP: R-52476-28732 */ + if( newLimit>aHardLimit[limitId] ){ + newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */ + }else if( newLimit<1 && limitId==SQLITE_LIMIT_LENGTH ){ + newLimit = 1; + } + db->aLimit[limitId] = newLimit; + } + return oldLimit; /* IMP: R-53341-35419 */ +} + +/* +** This function is used to parse both URIs and non-URI filenames passed by the +** user to API functions sqlite3_open() or sqlite3_open_v2(), and for database +** URIs specified as part of ATTACH statements. +** +** The first argument to this function is the name of the VFS to use (or +** a NULL to signify the default VFS) if the URI does not contain a "vfs=xxx" +** query parameter. The second argument contains the URI (or non-URI filename) +** itself. When this function is called the *pFlags variable should contain +** the default flags to open the database handle with. The value stored in +** *pFlags may be updated before returning if the URI filename contains +** "cache=xxx" or "mode=xxx" query parameters. +** +** If successful, SQLITE_OK is returned. In this case *ppVfs is set to point to +** the VFS that should be used to open the database file. *pzFile is set to +** point to a buffer containing the name of the file to open. The value +** stored in *pzFile is a database name acceptable to sqlite3_uri_parameter() +** and is in the same format as names created using sqlite3_create_filename(). +** The caller must invoke sqlite3_free_filename() (not sqlite3_free()!) on +** the value returned in *pzFile to avoid a memory leak. +** +** If an error occurs, then an SQLite error code is returned and *pzErrMsg +** may be set to point to a buffer containing an English language error +** message. It is the responsibility of the caller to eventually release +** this buffer by calling sqlite3_free(). +*/ +SQLITE_PRIVATE int sqlite3ParseUri( + const char *zDefaultVfs, /* VFS to use if no "vfs=xxx" query option */ + const char *zUri, /* Nul-terminated URI to parse */ + unsigned int *pFlags, /* IN/OUT: SQLITE_OPEN_XXX flags */ + sqlite3_vfs **ppVfs, /* OUT: VFS to use */ + char **pzFile, /* OUT: Filename component of URI */ + char **pzErrMsg /* OUT: Error message (if rc!=SQLITE_OK) */ +){ + int rc = SQLITE_OK; + unsigned int flags = *pFlags; + const char *zVfs = zDefaultVfs; + char *zFile; + char c; + int nUri = sqlite3Strlen30(zUri); + + assert( *pzErrMsg==0 ); + + if( ((flags & SQLITE_OPEN_URI) /* IMP: R-48725-32206 */ + || AtomicLoad(&sqlite3GlobalConfig.bOpenUri)) /* IMP: R-51689-46548 */ + && nUri>=5 && memcmp(zUri, "file:", 5)==0 /* IMP: R-57884-37496 */ + ){ + char *zOpt; + int eState; /* Parser state when parsing URI */ + int iIn; /* Input character index */ + int iOut = 0; /* Output character index */ + u64 nByte = nUri+8; /* Bytes of space to allocate */ + + /* Make sure the SQLITE_OPEN_URI flag is set to indicate to the VFS xOpen + ** method that there may be extra parameters following the file-name. */ + flags |= SQLITE_OPEN_URI; + + for(iIn=0; iIn<nUri; iIn++) nByte += (zUri[iIn]=='&'); + zFile = sqlite3_malloc64(nByte); + if( !zFile ) return SQLITE_NOMEM_BKPT; + + memset(zFile, 0, 4); /* 4-byte of 0x00 is the start of DB name marker */ + zFile += 4; + + iIn = 5; +#ifdef SQLITE_ALLOW_URI_AUTHORITY + if( strncmp(zUri+5, "///", 3)==0 ){ + iIn = 7; + /* The following condition causes URIs with five leading / characters + ** like file://///host/path to be converted into UNCs like //host/path. + ** The correct URI for that UNC has only two or four leading / characters + ** file://host/path or file:////host/path. But 5 leading slashes is a + ** common error, we are told, so we handle it as a special case. */ + if( strncmp(zUri+7, "///", 3)==0 ){ iIn++; } + }else if( strncmp(zUri+5, "//localhost/", 12)==0 ){ + iIn = 16; + } +#else + /* Discard the scheme and authority segments of the URI. */ + if( zUri[5]=='/' && zUri[6]=='/' ){ + iIn = 7; + while( zUri[iIn] && zUri[iIn]!='/' ) iIn++; + if( iIn!=7 && (iIn!=16 || memcmp("localhost", &zUri[7], 9)) ){ + *pzErrMsg = sqlite3_mprintf("invalid uri authority: %.*s", + iIn-7, &zUri[7]); + rc = SQLITE_ERROR; + goto parse_uri_out; + } + } +#endif + + /* Copy the filename and any query parameters into the zFile buffer. + ** Decode %HH escape codes along the way. + ** + ** Within this loop, variable eState may be set to 0, 1 or 2, depending + ** on the parsing context. As follows: + ** + ** 0: Parsing file-name. + ** 1: Parsing name section of a name=value query parameter. + ** 2: Parsing value section of a name=value query parameter. + */ + eState = 0; + while( (c = zUri[iIn])!=0 && c!='#' ){ + iIn++; + if( c=='%' + && sqlite3Isxdigit(zUri[iIn]) + && sqlite3Isxdigit(zUri[iIn+1]) + ){ + int octet = (sqlite3HexToInt(zUri[iIn++]) << 4); + octet += sqlite3HexToInt(zUri[iIn++]); + + assert( octet>=0 && octet<256 ); + if( octet==0 ){ +#ifndef SQLITE_ENABLE_URI_00_ERROR + /* This branch is taken when "%00" appears within the URI. In this + ** case we ignore all text in the remainder of the path, name or + ** value currently being parsed. So ignore the current character + ** and skip to the next "?", "=" or "&", as appropriate. */ + while( (c = zUri[iIn])!=0 && c!='#' + && (eState!=0 || c!='?') + && (eState!=1 || (c!='=' && c!='&')) + && (eState!=2 || c!='&') + ){ + iIn++; + } + continue; +#else + /* If ENABLE_URI_00_ERROR is defined, "%00" in a URI is an error. */ + *pzErrMsg = sqlite3_mprintf("unexpected %%00 in uri"); + rc = SQLITE_ERROR; + goto parse_uri_out; +#endif + } + c = octet; + }else if( eState==1 && (c=='&' || c=='=') ){ + if( zFile[iOut-1]==0 ){ + /* An empty option name. Ignore this option altogether. */ + while( zUri[iIn] && zUri[iIn]!='#' && zUri[iIn-1]!='&' ) iIn++; + continue; + } + if( c=='&' ){ + zFile[iOut++] = '\0'; + }else{ + eState = 2; + } + c = 0; + }else if( (eState==0 && c=='?') || (eState==2 && c=='&') ){ + c = 0; + eState = 1; + } + zFile[iOut++] = c; + } + if( eState==1 ) zFile[iOut++] = '\0'; + memset(zFile+iOut, 0, 4); /* end-of-options + empty journal filenames */ + + /* Check if there were any options specified that should be interpreted + ** here. Options that are interpreted here include "vfs" and those that + ** correspond to flags that may be passed to the sqlite3_open_v2() + ** method. */ + zOpt = &zFile[sqlite3Strlen30(zFile)+1]; + while( zOpt[0] ){ + int nOpt = sqlite3Strlen30(zOpt); + char *zVal = &zOpt[nOpt+1]; + int nVal = sqlite3Strlen30(zVal); + + if( nOpt==3 && memcmp("vfs", zOpt, 3)==0 ){ + zVfs = zVal; + }else{ + struct OpenMode { + const char *z; + int mode; + } *aMode = 0; + char *zModeType = 0; + int mask = 0; + int limit = 0; + + if( nOpt==5 && memcmp("cache", zOpt, 5)==0 ){ + static struct OpenMode aCacheMode[] = { + { "shared", SQLITE_OPEN_SHAREDCACHE }, + { "private", SQLITE_OPEN_PRIVATECACHE }, + { 0, 0 } + }; + + mask = SQLITE_OPEN_SHAREDCACHE|SQLITE_OPEN_PRIVATECACHE; + aMode = aCacheMode; + limit = mask; + zModeType = "cache"; + } + if( nOpt==4 && memcmp("mode", zOpt, 4)==0 ){ + static struct OpenMode aOpenMode[] = { + { "ro", SQLITE_OPEN_READONLY }, + { "rw", SQLITE_OPEN_READWRITE }, + { "rwc", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE }, + { "memory", SQLITE_OPEN_MEMORY }, + { 0, 0 } + }; + + mask = SQLITE_OPEN_READONLY | SQLITE_OPEN_READWRITE + | SQLITE_OPEN_CREATE | SQLITE_OPEN_MEMORY; + aMode = aOpenMode; + limit = mask & flags; + zModeType = "access"; + } + + if( aMode ){ + int i; + int mode = 0; + for(i=0; aMode[i].z; i++){ + const char *z = aMode[i].z; + if( nVal==sqlite3Strlen30(z) && 0==memcmp(zVal, z, nVal) ){ + mode = aMode[i].mode; + break; + } + } + if( mode==0 ){ + *pzErrMsg = sqlite3_mprintf("no such %s mode: %s", zModeType, zVal); + rc = SQLITE_ERROR; + goto parse_uri_out; + } + if( (mode & ~SQLITE_OPEN_MEMORY)>limit ){ + *pzErrMsg = sqlite3_mprintf("%s mode not allowed: %s", + zModeType, zVal); + rc = SQLITE_PERM; + goto parse_uri_out; + } + flags = (flags & ~mask) | mode; + } + } + + zOpt = &zVal[nVal+1]; + } + + }else{ + zFile = sqlite3_malloc64(nUri+8); + if( !zFile ) return SQLITE_NOMEM_BKPT; + memset(zFile, 0, 4); + zFile += 4; + if( nUri ){ + memcpy(zFile, zUri, nUri); + } + memset(zFile+nUri, 0, 4); + flags &= ~SQLITE_OPEN_URI; + } + + *ppVfs = sqlite3_vfs_find(zVfs); + if( *ppVfs==0 ){ + *pzErrMsg = sqlite3_mprintf("no such vfs: %s", zVfs); + rc = SQLITE_ERROR; + } + parse_uri_out: + if( rc!=SQLITE_OK ){ + sqlite3_free_filename(zFile); + zFile = 0; + } + *pFlags = flags; + *pzFile = zFile; + return rc; +} + +/* +** This routine does the core work of extracting URI parameters from a +** database filename for the sqlite3_uri_parameter() interface. +*/ +static const char *uriParameter(const char *zFilename, const char *zParam){ + zFilename += sqlite3Strlen30(zFilename) + 1; + while( ALWAYS(zFilename!=0) && zFilename[0] ){ + int x = strcmp(zFilename, zParam); + zFilename += sqlite3Strlen30(zFilename) + 1; + if( x==0 ) return zFilename; + zFilename += sqlite3Strlen30(zFilename) + 1; + } + return 0; +} + + + +/* +** This routine does the work of opening a database on behalf of +** sqlite3_open() and sqlite3_open16(). The database filename "zFilename" +** is UTF-8 encoded. +*/ +static int openDatabase( + const char *zFilename, /* Database filename UTF-8 encoded */ + sqlite3 **ppDb, /* OUT: Returned database handle */ + unsigned int flags, /* Operational flags */ + const char *zVfs /* Name of the VFS to use */ +){ + sqlite3 *db; /* Store allocated handle here */ + int rc; /* Return code */ + int isThreadsafe; /* True for threadsafe connections */ + char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */ + char *zErrMsg = 0; /* Error message from sqlite3ParseUri() */ + int i; /* Loop counter */ + +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppDb==0 ) return SQLITE_MISUSE_BKPT; +#endif + *ppDb = 0; +#ifndef SQLITE_OMIT_AUTOINIT + rc = sqlite3_initialize(); + if( rc ) return rc; +#endif + + if( sqlite3GlobalConfig.bCoreMutex==0 ){ + isThreadsafe = 0; + }else if( flags & SQLITE_OPEN_NOMUTEX ){ + isThreadsafe = 0; + }else if( flags & SQLITE_OPEN_FULLMUTEX ){ + isThreadsafe = 1; + }else{ + isThreadsafe = sqlite3GlobalConfig.bFullMutex; + } + + if( flags & SQLITE_OPEN_PRIVATECACHE ){ + flags &= ~SQLITE_OPEN_SHAREDCACHE; + }else if( sqlite3GlobalConfig.sharedCacheEnabled ){ + flags |= SQLITE_OPEN_SHAREDCACHE; + } + + /* Remove harmful bits from the flags parameter + ** + ** The SQLITE_OPEN_NOMUTEX and SQLITE_OPEN_FULLMUTEX flags were + ** dealt with in the previous code block. Besides these, the only + ** valid input flags for sqlite3_open_v2() are SQLITE_OPEN_READONLY, + ** SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE, SQLITE_OPEN_SHAREDCACHE, + ** SQLITE_OPEN_PRIVATECACHE, SQLITE_OPEN_EXRESCODE, and some reserved + ** bits. Silently mask off all other flags. + */ + flags &= ~( SQLITE_OPEN_DELETEONCLOSE | + SQLITE_OPEN_EXCLUSIVE | + SQLITE_OPEN_MAIN_DB | + SQLITE_OPEN_TEMP_DB | + SQLITE_OPEN_TRANSIENT_DB | + SQLITE_OPEN_MAIN_JOURNAL | + SQLITE_OPEN_TEMP_JOURNAL | + SQLITE_OPEN_SUBJOURNAL | + SQLITE_OPEN_SUPER_JOURNAL | + SQLITE_OPEN_NOMUTEX | + SQLITE_OPEN_FULLMUTEX | + SQLITE_OPEN_WAL + ); + + /* Allocate the sqlite data structure */ + db = sqlite3MallocZero( sizeof(sqlite3) ); + if( db==0 ) goto opendb_out; + if( isThreadsafe +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS + || sqlite3GlobalConfig.bCoreMutex +#endif + ){ + db->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE); + if( db->mutex==0 ){ + sqlite3_free(db); + db = 0; + goto opendb_out; + } + if( isThreadsafe==0 ){ + sqlite3MutexWarnOnContention(db->mutex); + } + } + sqlite3_mutex_enter(db->mutex); + db->errMask = (flags & SQLITE_OPEN_EXRESCODE)!=0 ? 0xffffffff : 0xff; + db->nDb = 2; + db->eOpenState = SQLITE_STATE_BUSY; + db->aDb = db->aDbStatic; + db->lookaside.bDisable = 1; + db->lookaside.sz = 0; + + assert( sizeof(db->aLimit)==sizeof(aHardLimit) ); + memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit)); + db->aLimit[SQLITE_LIMIT_WORKER_THREADS] = SQLITE_DEFAULT_WORKER_THREADS; + db->autoCommit = 1; + db->nextAutovac = -1; + db->szMmap = sqlite3GlobalConfig.szMmap; + db->nextPagesize = 0; + db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ +#ifdef SQLITE_ENABLE_SORTER_MMAP + /* Beginning with version 3.37.0, using the VFS xFetch() API to memory-map + ** the temporary files used to do external sorts (see code in vdbesort.c) + ** is disabled. It can still be used either by defining + ** SQLITE_ENABLE_SORTER_MMAP at compile time or by using the + ** SQLITE_TESTCTRL_SORTER_MMAP test-control at runtime. */ + db->nMaxSorterMmap = 0x7FFFFFFF; +#endif + db->flags |= SQLITE_ShortColNames + | SQLITE_EnableTrigger + | SQLITE_EnableView + | SQLITE_CacheSpill +#if !defined(SQLITE_TRUSTED_SCHEMA) || SQLITE_TRUSTED_SCHEMA+0!=0 + | SQLITE_TrustedSchema +#endif +/* The SQLITE_DQS compile-time option determines the default settings +** for SQLITE_DBCONFIG_DQS_DDL and SQLITE_DBCONFIG_DQS_DML. +** +** SQLITE_DQS SQLITE_DBCONFIG_DQS_DDL SQLITE_DBCONFIG_DQS_DML +** ---------- ----------------------- ----------------------- +** undefined on on +** 3 on on +** 2 on off +** 1 off on +** 0 off off +** +** Legacy behavior is 3 (double-quoted string literals are allowed anywhere) +** and so that is the default. But developers are encouraged to use +** -DSQLITE_DQS=0 (best) or -DSQLITE_DQS=1 (second choice) if possible. +*/ +#if !defined(SQLITE_DQS) +# define SQLITE_DQS 3 +#endif +#if (SQLITE_DQS&1)==1 + | SQLITE_DqsDML +#endif +#if (SQLITE_DQS&2)==2 + | SQLITE_DqsDDL +#endif + +#if !defined(SQLITE_DEFAULT_AUTOMATIC_INDEX) || SQLITE_DEFAULT_AUTOMATIC_INDEX + | SQLITE_AutoIndex +#endif +#if SQLITE_DEFAULT_CKPTFULLFSYNC + | SQLITE_CkptFullFSync +#endif +#if SQLITE_DEFAULT_FILE_FORMAT<4 + | SQLITE_LegacyFileFmt +#endif +#ifdef SQLITE_ENABLE_LOAD_EXTENSION + | SQLITE_LoadExtension +#endif +#if SQLITE_DEFAULT_RECURSIVE_TRIGGERS + | SQLITE_RecTriggers +#endif +#if defined(SQLITE_DEFAULT_FOREIGN_KEYS) && SQLITE_DEFAULT_FOREIGN_KEYS + | SQLITE_ForeignKeys +#endif +#if defined(SQLITE_REVERSE_UNORDERED_SELECTS) + | SQLITE_ReverseOrder +#endif +#if defined(SQLITE_ENABLE_OVERSIZE_CELL_CHECK) + | SQLITE_CellSizeCk +#endif +#if defined(SQLITE_ENABLE_FTS3_TOKENIZER) + | SQLITE_Fts3Tokenizer +#endif +#if defined(SQLITE_ENABLE_QPSG) + | SQLITE_EnableQPSG +#endif +#if defined(SQLITE_DEFAULT_DEFENSIVE) + | SQLITE_Defensive +#endif +#if defined(SQLITE_DEFAULT_LEGACY_ALTER_TABLE) + | SQLITE_LegacyAlter +#endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) + | SQLITE_StmtScanStatus +#endif + ; + sqlite3HashInit(&db->aCollSeq); +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3HashInit(&db->aModule); +#endif + + /* Add the default collation sequence BINARY. BINARY works for both UTF-8 + ** and UTF-16, so add a version for each to avoid any unnecessary + ** conversions. The only error that can occur here is a malloc() failure. + ** + ** EVIDENCE-OF: R-52786-44878 SQLite defines three built-in collating + ** functions: + */ + createCollation(db, sqlite3StrBINARY, SQLITE_UTF8, 0, binCollFunc, 0); + createCollation(db, sqlite3StrBINARY, SQLITE_UTF16BE, 0, binCollFunc, 0); + createCollation(db, sqlite3StrBINARY, SQLITE_UTF16LE, 0, binCollFunc, 0); + createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0); + createCollation(db, "RTRIM", SQLITE_UTF8, 0, rtrimCollFunc, 0); + if( db->mallocFailed ){ + goto opendb_out; + } + +#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) + /* Process magic filenames ":localStorage:" and ":sessionStorage:" */ + if( zFilename && zFilename[0]==':' ){ + if( strcmp(zFilename, ":localStorage:")==0 ){ + zFilename = "file:local?vfs=kvvfs"; + flags |= SQLITE_OPEN_URI; + }else if( strcmp(zFilename, ":sessionStorage:")==0 ){ + zFilename = "file:session?vfs=kvvfs"; + flags |= SQLITE_OPEN_URI; + } + } +#endif /* SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) */ + + /* Parse the filename/URI argument + ** + ** Only allow sensible combinations of bits in the flags argument. + ** Throw an error if any non-sense combination is used. If we + ** do not block illegal combinations here, it could trigger + ** assert() statements in deeper layers. Sensible combinations + ** are: + ** + ** 1: SQLITE_OPEN_READONLY + ** 2: SQLITE_OPEN_READWRITE + ** 6: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE + */ + db->openFlags = flags; + assert( SQLITE_OPEN_READONLY == 0x01 ); + assert( SQLITE_OPEN_READWRITE == 0x02 ); + assert( SQLITE_OPEN_CREATE == 0x04 ); + testcase( (1<<(flags&7))==0x02 ); /* READONLY */ + testcase( (1<<(flags&7))==0x04 ); /* READWRITE */ + testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */ + if( ((1<<(flags&7)) & 0x46)==0 ){ + rc = SQLITE_MISUSE_BKPT; /* IMP: R-18321-05872 */ + }else{ + rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg); + } + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_NOMEM ) sqlite3OomFault(db); + sqlite3ErrorWithMsg(db, rc, zErrMsg ? "%s" : 0, zErrMsg); + sqlite3_free(zErrMsg); + goto opendb_out; + } + assert( db->pVfs!=0 ); +#if SQLITE_OS_KV || defined(SQLITE_OS_KV_OPTIONAL) + if( sqlite3_stricmp(db->pVfs->zName, "kvvfs")==0 ){ + db->temp_store = 2; + } +#endif + + /* Open the backend database driver */ + rc = sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, 0, + flags | SQLITE_OPEN_MAIN_DB); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_IOERR_NOMEM ){ + rc = SQLITE_NOMEM_BKPT; + } + sqlite3Error(db, rc); + goto opendb_out; + } + sqlite3BtreeEnter(db->aDb[0].pBt); + db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt); + if( !db->mallocFailed ){ + sqlite3SetTextEncoding(db, SCHEMA_ENC(db)); + } + sqlite3BtreeLeave(db->aDb[0].pBt); + db->aDb[1].pSchema = sqlite3SchemaGet(db, 0); + + /* The default safety_level for the main database is FULL; for the temp + ** database it is OFF. This matches the pager layer defaults. + */ + db->aDb[0].zDbSName = "main"; + db->aDb[0].safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; + db->aDb[1].zDbSName = "temp"; + db->aDb[1].safety_level = PAGER_SYNCHRONOUS_OFF; + + db->eOpenState = SQLITE_STATE_OPEN; + if( db->mallocFailed ){ + goto opendb_out; + } + + /* Register all built-in functions, but do not attempt to read the + ** database schema yet. This is delayed until the first time the database + ** is accessed. + */ + sqlite3Error(db, SQLITE_OK); + sqlite3RegisterPerConnectionBuiltinFunctions(db); + rc = sqlite3_errcode(db); + + + /* Load compiled-in extensions */ + for(i=0; rc==SQLITE_OK && i<ArraySize(sqlite3BuiltinExtensions); i++){ + rc = sqlite3BuiltinExtensions[i](db); + } + + /* Load automatic extensions - extensions that have been registered + ** using the sqlite3_automatic_extension() API. + */ + if( rc==SQLITE_OK ){ + sqlite3AutoLoadExtensions(db); + rc = sqlite3_errcode(db); + if( rc!=SQLITE_OK ){ + goto opendb_out; + } + } + +#ifdef SQLITE_ENABLE_INTERNAL_FUNCTIONS + /* Testing use only!!! The -DSQLITE_ENABLE_INTERNAL_FUNCTIONS=1 compile-time + ** option gives access to internal functions by default. + ** Testing use only!!! */ + db->mDbFlags |= DBFLAG_InternalFunc; +#endif + + /* -DSQLITE_DEFAULT_LOCKING_MODE=1 makes EXCLUSIVE the default locking + ** mode. -DSQLITE_DEFAULT_LOCKING_MODE=0 make NORMAL the default locking + ** mode. Doing nothing at all also makes NORMAL the default. + */ +#ifdef SQLITE_DEFAULT_LOCKING_MODE + db->dfltLockMode = SQLITE_DEFAULT_LOCKING_MODE; + sqlite3PagerLockingMode(sqlite3BtreePager(db->aDb[0].pBt), + SQLITE_DEFAULT_LOCKING_MODE); +#endif + + if( rc ) sqlite3Error(db, rc); + + /* Enable the lookaside-malloc subsystem */ + setupLookaside(db, 0, sqlite3GlobalConfig.szLookaside, + sqlite3GlobalConfig.nLookaside); + + sqlite3_wal_autocheckpoint(db, SQLITE_DEFAULT_WAL_AUTOCHECKPOINT); + +opendb_out: + if( db ){ + assert( db->mutex!=0 || isThreadsafe==0 + || sqlite3GlobalConfig.bFullMutex==0 ); + sqlite3_mutex_leave(db->mutex); + } + rc = sqlite3_errcode(db); + assert( db!=0 || (rc&0xff)==SQLITE_NOMEM ); + if( (rc&0xff)==SQLITE_NOMEM ){ + sqlite3_close(db); + db = 0; + }else if( rc!=SQLITE_OK ){ + db->eOpenState = SQLITE_STATE_SICK; + } + *ppDb = db; +#ifdef SQLITE_ENABLE_SQLLOG + if( sqlite3GlobalConfig.xSqllog ){ + /* Opening a db handle. Fourth parameter is passed 0. */ + void *pArg = sqlite3GlobalConfig.pSqllogArg; + sqlite3GlobalConfig.xSqllog(pArg, db, zFilename, 0); + } +#endif + sqlite3_free_filename(zOpen); + return rc; +} + + +/* +** Open a new database handle. +*/ +SQLITE_API int sqlite3_open( + const char *zFilename, + sqlite3 **ppDb +){ + return openDatabase(zFilename, ppDb, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); +} +SQLITE_API int sqlite3_open_v2( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb, /* OUT: SQLite db handle */ + int flags, /* Flags */ + const char *zVfs /* Name of VFS module to use */ +){ + return openDatabase(filename, ppDb, (unsigned int)flags, zVfs); +} + +#ifndef SQLITE_OMIT_UTF16 +/* +** Open a new database handle. +*/ +SQLITE_API int sqlite3_open16( + const void *zFilename, + sqlite3 **ppDb +){ + char const *zFilename8; /* zFilename encoded in UTF-8 instead of UTF-16 */ + sqlite3_value *pVal; + int rc; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppDb==0 ) return SQLITE_MISUSE_BKPT; +#endif + *ppDb = 0; +#ifndef SQLITE_OMIT_AUTOINIT + rc = sqlite3_initialize(); + if( rc ) return rc; +#endif + if( zFilename==0 ) zFilename = "\000\000"; + pVal = sqlite3ValueNew(0); + sqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC); + zFilename8 = sqlite3ValueText(pVal, SQLITE_UTF8); + if( zFilename8 ){ + rc = openDatabase(zFilename8, ppDb, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); + assert( *ppDb || rc==SQLITE_NOMEM ); + if( rc==SQLITE_OK && !DbHasProperty(*ppDb, 0, DB_SchemaLoaded) ){ + SCHEMA_ENC(*ppDb) = ENC(*ppDb) = SQLITE_UTF16NATIVE; + } + }else{ + rc = SQLITE_NOMEM_BKPT; + } + sqlite3ValueFree(pVal); + + return rc & 0xff; +} +#endif /* SQLITE_OMIT_UTF16 */ + +/* +** Register a new collation sequence with the database handle db. +*/ +SQLITE_API int sqlite3_create_collation( + sqlite3* db, + const char *zName, + int enc, + void* pCtx, + int(*xCompare)(void*,int,const void*,int,const void*) +){ + return sqlite3_create_collation_v2(db, zName, enc, pCtx, xCompare, 0); +} + +/* +** Register a new collation sequence with the database handle db. +*/ +SQLITE_API int sqlite3_create_collation_v2( + sqlite3* db, + const char *zName, + int enc, + void* pCtx, + int(*xCompare)(void*,int,const void*,int,const void*), + void(*xDel)(void*) +){ + int rc; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + assert( !db->mallocFailed ); + rc = createCollation(db, zName, (u8)enc, pCtx, xCompare, xDel); + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +#ifndef SQLITE_OMIT_UTF16 +/* +** Register a new collation sequence with the database handle db. +*/ +SQLITE_API int sqlite3_create_collation16( + sqlite3* db, + const void *zName, + int enc, + void* pCtx, + int(*xCompare)(void*,int,const void*,int,const void*) +){ + int rc = SQLITE_OK; + char *zName8; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + assert( !db->mallocFailed ); + zName8 = sqlite3Utf16to8(db, zName, -1, SQLITE_UTF16NATIVE); + if( zName8 ){ + rc = createCollation(db, zName8, (u8)enc, pCtx, xCompare, 0); + sqlite3DbFree(db, zName8); + } + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} +#endif /* SQLITE_OMIT_UTF16 */ + +/* +** Register a collation sequence factory callback with the database handle +** db. Replace any previously installed collation sequence factory. +*/ +SQLITE_API int sqlite3_collation_needed( + sqlite3 *db, + void *pCollNeededArg, + void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*) +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + db->xCollNeeded = xCollNeeded; + db->xCollNeeded16 = 0; + db->pCollNeededArg = pCollNeededArg; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + +#ifndef SQLITE_OMIT_UTF16 +/* +** Register a collation sequence factory callback with the database handle +** db. Replace any previously installed collation sequence factory. +*/ +SQLITE_API int sqlite3_collation_needed16( + sqlite3 *db, + void *pCollNeededArg, + void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*) +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + db->xCollNeeded = 0; + db->xCollNeeded16 = xCollNeeded16; + db->pCollNeededArg = pCollNeededArg; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} +#endif /* SQLITE_OMIT_UTF16 */ + +/* +** Find existing client data. +*/ +SQLITE_API void *sqlite3_get_clientdata(sqlite3 *db, const char *zName){ + DbClientData *p; + sqlite3_mutex_enter(db->mutex); + for(p=db->pDbData; p; p=p->pNext){ + if( strcmp(p->zName, zName)==0 ){ + void *pResult = p->pData; + sqlite3_mutex_leave(db->mutex); + return pResult; + } + } + sqlite3_mutex_leave(db->mutex); + return 0; +} + +/* +** Add new client data to a database connection. +*/ +SQLITE_API int sqlite3_set_clientdata( + sqlite3 *db, /* Attach client data to this connection */ + const char *zName, /* Name of the client data */ + void *pData, /* The client data itself */ + void (*xDestructor)(void*) /* Destructor */ +){ + DbClientData *p, **pp; + sqlite3_mutex_enter(db->mutex); + pp = &db->pDbData; + for(p=db->pDbData; p && strcmp(p->zName,zName); p=p->pNext){ + pp = &p->pNext; + } + if( p ){ + assert( p->pData!=0 ); + if( p->xDestructor ) p->xDestructor(p->pData); + if( pData==0 ){ + *pp = p->pNext; + sqlite3_free(p); + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; + } + }else if( pData==0 ){ + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; + }else{ + size_t n = strlen(zName); + p = sqlite3_malloc64( sizeof(DbClientData)+n+1 ); + if( p==0 ){ + if( xDestructor ) xDestructor(pData); + sqlite3_mutex_leave(db->mutex); + return SQLITE_NOMEM; + } + memcpy(p->zName, zName, n+1); + p->pNext = db->pDbData; + db->pDbData = p; + } + p->pData = pData; + p->xDestructor = xDestructor; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + + +#ifndef SQLITE_OMIT_DEPRECATED +/* +** This function is now an anachronism. It used to be used to recover from a +** malloc() failure, but SQLite now does this automatically. +*/ +SQLITE_API int sqlite3_global_recover(void){ + return SQLITE_OK; +} +#endif + +/* +** Test to see whether or not the database connection is in autocommit +** mode. Return TRUE if it is and FALSE if not. Autocommit mode is on +** by default. Autocommit is disabled by a BEGIN statement and reenabled +** by the next COMMIT or ROLLBACK. +*/ +SQLITE_API int sqlite3_get_autocommit(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return db->autoCommit; +} + +/* +** The following routines are substitutes for constants SQLITE_CORRUPT, +** SQLITE_MISUSE, SQLITE_CANTOPEN, SQLITE_NOMEM and possibly other error +** constants. They serve two purposes: +** +** 1. Serve as a convenient place to set a breakpoint in a debugger +** to detect when version error conditions occurs. +** +** 2. Invoke sqlite3_log() to provide the source code location where +** a low-level error is first detected. +*/ +SQLITE_PRIVATE int sqlite3ReportError(int iErr, int lineno, const char *zType){ + sqlite3_log(iErr, "%s at line %d of [%.10s]", + zType, lineno, 20+sqlite3_sourceid()); + return iErr; +} +SQLITE_PRIVATE int sqlite3CorruptError(int lineno){ + testcase( sqlite3GlobalConfig.xLog!=0 ); + return sqlite3ReportError(SQLITE_CORRUPT, lineno, "database corruption"); +} +SQLITE_PRIVATE int sqlite3MisuseError(int lineno){ + testcase( sqlite3GlobalConfig.xLog!=0 ); + return sqlite3ReportError(SQLITE_MISUSE, lineno, "misuse"); +} +SQLITE_PRIVATE int sqlite3CantopenError(int lineno){ + testcase( sqlite3GlobalConfig.xLog!=0 ); + return sqlite3ReportError(SQLITE_CANTOPEN, lineno, "cannot open file"); +} +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_CORRUPT_PGNO) +SQLITE_PRIVATE int sqlite3CorruptPgnoError(int lineno, Pgno pgno){ + char zMsg[100]; + sqlite3_snprintf(sizeof(zMsg), zMsg, "database corruption page %d", pgno); + testcase( sqlite3GlobalConfig.xLog!=0 ); + return sqlite3ReportError(SQLITE_CORRUPT, lineno, zMsg); +} +#endif +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3NomemError(int lineno){ + testcase( sqlite3GlobalConfig.xLog!=0 ); + return sqlite3ReportError(SQLITE_NOMEM, lineno, "OOM"); +} +SQLITE_PRIVATE int sqlite3IoerrnomemError(int lineno){ + testcase( sqlite3GlobalConfig.xLog!=0 ); + return sqlite3ReportError(SQLITE_IOERR_NOMEM, lineno, "I/O OOM error"); +} +#endif + +#ifndef SQLITE_OMIT_DEPRECATED +/* +** This is a convenience routine that makes sure that all thread-specific +** data for this thread has been deallocated. +** +** SQLite no longer uses thread-specific data so this routine is now a +** no-op. It is retained for historical compatibility. +*/ +SQLITE_API void sqlite3_thread_cleanup(void){ +} +#endif + +/* +** Return meta information about a specific column of a database table. +** See comment in sqlite3.h (sqlite.h.in) for details. +*/ +SQLITE_API int sqlite3_table_column_metadata( + sqlite3 *db, /* Connection handle */ + const char *zDbName, /* Database name or NULL */ + const char *zTableName, /* Table name */ + const char *zColumnName, /* Column name */ + char const **pzDataType, /* OUTPUT: Declared data type */ + char const **pzCollSeq, /* OUTPUT: Collation sequence name */ + int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */ + int *pPrimaryKey, /* OUTPUT: True if column part of PK */ + int *pAutoinc /* OUTPUT: True if column is auto-increment */ +){ + int rc; + char *zErrMsg = 0; + Table *pTab = 0; + Column *pCol = 0; + int iCol = 0; + char const *zDataType = 0; + char const *zCollSeq = 0; + int notnull = 0; + int primarykey = 0; + int autoinc = 0; + + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zTableName==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + + /* Ensure the database schema has been loaded */ + sqlite3_mutex_enter(db->mutex); + sqlite3BtreeEnterAll(db); + rc = sqlite3Init(db, &zErrMsg); + if( SQLITE_OK!=rc ){ + goto error_out; + } + + /* Locate the table in question */ + pTab = sqlite3FindTable(db, zTableName, zDbName); + if( !pTab || IsView(pTab) ){ + pTab = 0; + goto error_out; + } + + /* Find the column for which info is requested */ + if( zColumnName==0 ){ + /* Query for existence of table only */ + }else{ + for(iCol=0; iCol<pTab->nCol; iCol++){ + pCol = &pTab->aCol[iCol]; + if( 0==sqlite3StrICmp(pCol->zCnName, zColumnName) ){ + break; + } + } + if( iCol==pTab->nCol ){ + if( HasRowid(pTab) && sqlite3IsRowid(zColumnName) ){ + iCol = pTab->iPKey; + pCol = iCol>=0 ? &pTab->aCol[iCol] : 0; + }else{ + pTab = 0; + goto error_out; + } + } + } + + /* The following block stores the meta information that will be returned + ** to the caller in local variables zDataType, zCollSeq, notnull, primarykey + ** and autoinc. At this point there are two possibilities: + ** + ** 1. The specified column name was rowid", "oid" or "_rowid_" + ** and there is no explicitly declared IPK column. + ** + ** 2. The table is not a view and the column name identified an + ** explicitly declared column. Copy meta information from *pCol. + */ + if( pCol ){ + zDataType = sqlite3ColumnType(pCol,0); + zCollSeq = sqlite3ColumnColl(pCol); + notnull = pCol->notNull!=0; + primarykey = (pCol->colFlags & COLFLAG_PRIMKEY)!=0; + autoinc = pTab->iPKey==iCol && (pTab->tabFlags & TF_Autoincrement)!=0; + }else{ + zDataType = "INTEGER"; + primarykey = 1; + } + if( !zCollSeq ){ + zCollSeq = sqlite3StrBINARY; + } + +error_out: + sqlite3BtreeLeaveAll(db); + + /* Whether the function call succeeded or failed, set the output parameters + ** to whatever their local counterparts contain. If an error did occur, + ** this has the effect of zeroing all output parameters. + */ + if( pzDataType ) *pzDataType = zDataType; + if( pzCollSeq ) *pzCollSeq = zCollSeq; + if( pNotNull ) *pNotNull = notnull; + if( pPrimaryKey ) *pPrimaryKey = primarykey; + if( pAutoinc ) *pAutoinc = autoinc; + + if( SQLITE_OK==rc && !pTab ){ + sqlite3DbFree(db, zErrMsg); + zErrMsg = sqlite3MPrintf(db, "no such table column: %s.%s", zTableName, + zColumnName); + rc = SQLITE_ERROR; + } + sqlite3ErrorWithMsg(db, rc, (zErrMsg?"%s":0), zErrMsg); + sqlite3DbFree(db, zErrMsg); + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Sleep for a little while. Return the amount of time slept. +*/ +SQLITE_API int sqlite3_sleep(int ms){ + sqlite3_vfs *pVfs; + int rc; + pVfs = sqlite3_vfs_find(0); + if( pVfs==0 ) return 0; + + /* This function works in milliseconds, but the underlying OsSleep() + ** API uses microseconds. Hence the 1000's. + */ + rc = (sqlite3OsSleep(pVfs, ms<0 ? 0 : 1000*ms)/1000); + return rc; +} + +/* +** Enable or disable the extended result codes. +*/ +SQLITE_API int sqlite3_extended_result_codes(sqlite3 *db, int onoff){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + db->errMask = onoff ? 0xffffffff : 0xff; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + +/* +** Invoke the xFileControl method on a particular database. +*/ +SQLITE_API int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){ + int rc = SQLITE_ERROR; + Btree *pBtree; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + pBtree = sqlite3DbNameToBtree(db, zDbName); + if( pBtree ){ + Pager *pPager; + sqlite3_file *fd; + sqlite3BtreeEnter(pBtree); + pPager = sqlite3BtreePager(pBtree); + assert( pPager!=0 ); + fd = sqlite3PagerFile(pPager); + assert( fd!=0 ); + if( op==SQLITE_FCNTL_FILE_POINTER ){ + *(sqlite3_file**)pArg = fd; + rc = SQLITE_OK; + }else if( op==SQLITE_FCNTL_VFS_POINTER ){ + *(sqlite3_vfs**)pArg = sqlite3PagerVfs(pPager); + rc = SQLITE_OK; + }else if( op==SQLITE_FCNTL_JOURNAL_POINTER ){ + *(sqlite3_file**)pArg = sqlite3PagerJrnlFile(pPager); + rc = SQLITE_OK; + }else if( op==SQLITE_FCNTL_DATA_VERSION ){ + *(unsigned int*)pArg = sqlite3PagerDataVersion(pPager); + rc = SQLITE_OK; + }else if( op==SQLITE_FCNTL_RESERVE_BYTES ){ + int iNew = *(int*)pArg; + *(int*)pArg = sqlite3BtreeGetRequestedReserve(pBtree); + if( iNew>=0 && iNew<=255 ){ + sqlite3BtreeSetPageSize(pBtree, 0, iNew, 0); + } + rc = SQLITE_OK; + }else if( op==SQLITE_FCNTL_RESET_CACHE ){ + sqlite3BtreeClearCache(pBtree); + rc = SQLITE_OK; + }else{ + int nSave = db->busyHandler.nBusy; + rc = sqlite3OsFileControl(fd, op, pArg); + db->busyHandler.nBusy = nSave; + } + sqlite3BtreeLeave(pBtree); + } + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Interface to the testing logic. +*/ +SQLITE_API int sqlite3_test_control(int op, ...){ + int rc = 0; +#ifdef SQLITE_UNTESTABLE + UNUSED_PARAMETER(op); +#else + va_list ap; + va_start(ap, op); + switch( op ){ + + /* + ** Save the current state of the PRNG. + */ + case SQLITE_TESTCTRL_PRNG_SAVE: { + sqlite3PrngSaveState(); + break; + } + + /* + ** Restore the state of the PRNG to the last state saved using + ** PRNG_SAVE. If PRNG_SAVE has never before been called, then + ** this verb acts like PRNG_RESET. + */ + case SQLITE_TESTCTRL_PRNG_RESTORE: { + sqlite3PrngRestoreState(); + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_PRNG_SEED, int x, sqlite3 *db); + ** + ** Control the seed for the pseudo-random number generator (PRNG) that + ** is built into SQLite. Cases: + ** + ** x!=0 && db!=0 Seed the PRNG to the current value of the + ** schema cookie in the main database for db, or + ** x if the schema cookie is zero. This case + ** is convenient to use with database fuzzers + ** as it allows the fuzzer some control over the + ** the PRNG seed. + ** + ** x!=0 && db==0 Seed the PRNG to the value of x. + ** + ** x==0 && db==0 Revert to default behavior of using the + ** xRandomness method on the primary VFS. + ** + ** This test-control also resets the PRNG so that the new seed will + ** be used for the next call to sqlite3_randomness(). + */ +#ifndef SQLITE_OMIT_WSD + case SQLITE_TESTCTRL_PRNG_SEED: { + int x = va_arg(ap, int); + int y; + sqlite3 *db = va_arg(ap, sqlite3*); + assert( db==0 || db->aDb[0].pSchema!=0 ); + if( db && (y = db->aDb[0].pSchema->schema_cookie)!=0 ){ x = y; } + sqlite3Config.iPrngSeed = x; + sqlite3_randomness(0,0); + break; + } +#endif + + /* + ** sqlite3_test_control(BITVEC_TEST, size, program) + ** + ** Run a test against a Bitvec object of size. The program argument + ** is an array of integers that defines the test. Return -1 on a + ** memory allocation error, 0 on success, or non-zero for an error. + ** See the sqlite3BitvecBuiltinTest() for additional information. + */ + case SQLITE_TESTCTRL_BITVEC_TEST: { + int sz = va_arg(ap, int); + int *aProg = va_arg(ap, int*); + rc = sqlite3BitvecBuiltinTest(sz, aProg); + break; + } + + /* + ** sqlite3_test_control(FAULT_INSTALL, xCallback) + ** + ** Arrange to invoke xCallback() whenever sqlite3FaultSim() is called, + ** if xCallback is not NULL. + ** + ** As a test of the fault simulator mechanism itself, sqlite3FaultSim(0) + ** is called immediately after installing the new callback and the return + ** value from sqlite3FaultSim(0) becomes the return from + ** sqlite3_test_control(). + */ + case SQLITE_TESTCTRL_FAULT_INSTALL: { + /* A bug in MSVC prevents it from understanding pointers to functions + ** types in the second argument to va_arg(). Work around the problem + ** using a typedef. + ** http://support.microsoft.com/kb/47961 <-- dead hyperlink + ** Search at http://web.archive.org/ to find the 2015-03-16 archive + ** of the link above to see the original text. + ** sqlite3GlobalConfig.xTestCallback = va_arg(ap, int(*)(int)); + */ + typedef int(*sqlite3FaultFuncType)(int); + sqlite3GlobalConfig.xTestCallback = va_arg(ap, sqlite3FaultFuncType); + rc = sqlite3FaultSim(0); + break; + } + + /* + ** sqlite3_test_control(BENIGN_MALLOC_HOOKS, xBegin, xEnd) + ** + ** Register hooks to call to indicate which malloc() failures + ** are benign. + */ + case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: { + typedef void (*void_function)(void); + void_function xBenignBegin; + void_function xBenignEnd; + xBenignBegin = va_arg(ap, void_function); + xBenignEnd = va_arg(ap, void_function); + sqlite3BenignMallocHooks(xBenignBegin, xBenignEnd); + break; + } + + /* + ** sqlite3_test_control(SQLITE_TESTCTRL_PENDING_BYTE, unsigned int X) + ** + ** Set the PENDING byte to the value in the argument, if X>0. + ** Make no changes if X==0. Return the value of the pending byte + ** as it existing before this routine was called. + ** + ** IMPORTANT: Changing the PENDING byte from 0x40000000 results in + ** an incompatible database file format. Changing the PENDING byte + ** while any database connection is open results in undefined and + ** deleterious behavior. + */ + case SQLITE_TESTCTRL_PENDING_BYTE: { + rc = PENDING_BYTE; +#ifndef SQLITE_OMIT_WSD + { + unsigned int newVal = va_arg(ap, unsigned int); + if( newVal ) sqlite3PendingByte = newVal; + } +#endif + break; + } + + /* + ** sqlite3_test_control(SQLITE_TESTCTRL_ASSERT, int X) + ** + ** This action provides a run-time test to see whether or not + ** assert() was enabled at compile-time. If X is true and assert() + ** is enabled, then the return value is true. If X is true and + ** assert() is disabled, then the return value is zero. If X is + ** false and assert() is enabled, then the assertion fires and the + ** process aborts. If X is false and assert() is disabled, then the + ** return value is zero. + */ + case SQLITE_TESTCTRL_ASSERT: { + volatile int x = 0; + assert( /*side-effects-ok*/ (x = va_arg(ap,int))!=0 ); + rc = x; +#if defined(SQLITE_DEBUG) + /* Invoke these debugging routines so that the compiler does not + ** issue "defined but not used" warnings. */ + if( x==9999 ){ + sqlite3ShowExpr(0); + sqlite3ShowExpr(0); + sqlite3ShowExprList(0); + sqlite3ShowIdList(0); + sqlite3ShowSrcList(0); + sqlite3ShowWith(0); + sqlite3ShowUpsert(0); +#ifndef SQLITE_OMIT_TRIGGER + sqlite3ShowTriggerStep(0); + sqlite3ShowTriggerStepList(0); + sqlite3ShowTrigger(0); + sqlite3ShowTriggerList(0); +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC + sqlite3ShowWindow(0); + sqlite3ShowWinFunc(0); +#endif + sqlite3ShowSelect(0); + } +#endif + break; + } + + + /* + ** sqlite3_test_control(SQLITE_TESTCTRL_ALWAYS, int X) + ** + ** This action provides a run-time test to see how the ALWAYS and + ** NEVER macros were defined at compile-time. + ** + ** The return value is ALWAYS(X) if X is true, or 0 if X is false. + ** + ** The recommended test is X==2. If the return value is 2, that means + ** ALWAYS() and NEVER() are both no-op pass-through macros, which is the + ** default setting. If the return value is 1, then ALWAYS() is either + ** hard-coded to true or else it asserts if its argument is false. + ** The first behavior (hard-coded to true) is the case if + ** SQLITE_TESTCTRL_ASSERT shows that assert() is disabled and the second + ** behavior (assert if the argument to ALWAYS() is false) is the case if + ** SQLITE_TESTCTRL_ASSERT shows that assert() is enabled. + ** + ** The run-time test procedure might look something like this: + ** + ** if( sqlite3_test_control(SQLITE_TESTCTRL_ALWAYS, 2)==2 ){ + ** // ALWAYS() and NEVER() are no-op pass-through macros + ** }else if( sqlite3_test_control(SQLITE_TESTCTRL_ASSERT, 1) ){ + ** // ALWAYS(x) asserts that x is true. NEVER(x) asserts x is false. + ** }else{ + ** // ALWAYS(x) is a constant 1. NEVER(x) is a constant 0. + ** } + */ + case SQLITE_TESTCTRL_ALWAYS: { + int x = va_arg(ap,int); + rc = x ? ALWAYS(x) : 0; + break; + } + + /* + ** sqlite3_test_control(SQLITE_TESTCTRL_BYTEORDER); + ** + ** The integer returned reveals the byte-order of the computer on which + ** SQLite is running: + ** + ** 1 big-endian, determined at run-time + ** 10 little-endian, determined at run-time + ** 432101 big-endian, determined at compile-time + ** 123410 little-endian, determined at compile-time + */ + case SQLITE_TESTCTRL_BYTEORDER: { + rc = SQLITE_BYTEORDER*100 + SQLITE_LITTLEENDIAN*10 + SQLITE_BIGENDIAN; + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite3 *db, int N) + ** + ** Enable or disable various optimizations for testing purposes. The + ** argument N is a bitmask of optimizations to be disabled. For normal + ** operation N should be 0. The idea is that a test program (like the + ** SQL Logic Test or SLT test module) can run the same SQL multiple times + ** with various optimizations disabled to verify that the same answer + ** is obtained in every case. + */ + case SQLITE_TESTCTRL_OPTIMIZATIONS: { + sqlite3 *db = va_arg(ap, sqlite3*); + db->dbOptFlags = va_arg(ap, u32); + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_LOCALTIME_FAULT, onoff, xAlt); + ** + ** If parameter onoff is 1, subsequent calls to localtime() fail. + ** If 2, then invoke xAlt() instead of localtime(). If 0, normal + ** processing. + ** + ** xAlt arguments are void pointers, but they really want to be: + ** + ** int xAlt(const time_t*, struct tm*); + ** + ** xAlt should write results in to struct tm object of its 2nd argument + ** and return zero on success, or return non-zero on failure. + */ + case SQLITE_TESTCTRL_LOCALTIME_FAULT: { + sqlite3GlobalConfig.bLocaltimeFault = va_arg(ap, int); + if( sqlite3GlobalConfig.bLocaltimeFault==2 ){ + typedef int(*sqlite3LocaltimeType)(const void*,void*); + sqlite3GlobalConfig.xAltLocaltime = va_arg(ap, sqlite3LocaltimeType); + }else{ + sqlite3GlobalConfig.xAltLocaltime = 0; + } + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_INTERNAL_FUNCTIONS, sqlite3*); + ** + ** Toggle the ability to use internal functions on or off for + ** the database connection given in the argument. + */ + case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: { + sqlite3 *db = va_arg(ap, sqlite3*); + db->mDbFlags ^= DBFLAG_InternalFunc; + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_NEVER_CORRUPT, int); + ** + ** Set or clear a flag that indicates that the database file is always well- + ** formed and never corrupt. This flag is clear by default, indicating that + ** database files might have arbitrary corruption. Setting the flag during + ** testing causes certain assert() statements in the code to be activated + ** that demonstrate invariants on well-formed database files. + */ + case SQLITE_TESTCTRL_NEVER_CORRUPT: { + sqlite3GlobalConfig.neverCorrupt = va_arg(ap, int); + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS, int); + ** + ** Set or clear a flag that causes SQLite to verify that type, name, + ** and tbl_name fields of the sqlite_schema table. This is normally + ** on, but it is sometimes useful to turn it off for testing. + ** + ** 2020-07-22: Disabling EXTRA_SCHEMA_CHECKS also disables the + ** verification of rootpage numbers when parsing the schema. This + ** is useful to make it easier to reach strange internal error states + ** during testing. The EXTRA_SCHEMA_CHECKS setting is always enabled + ** in production. + */ + case SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS: { + sqlite3GlobalConfig.bExtraSchemaChecks = va_arg(ap, int); + break; + } + + /* Set the threshold at which OP_Once counters reset back to zero. + ** By default this is 0x7ffffffe (over 2 billion), but that value is + ** too big to test in a reasonable amount of time, so this control is + ** provided to set a small and easily reachable reset value. + */ + case SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD: { + sqlite3GlobalConfig.iOnceResetThreshold = va_arg(ap, int); + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE, xCallback, ptr); + ** + ** Set the VDBE coverage callback function to xCallback with context + ** pointer ptr. + */ + case SQLITE_TESTCTRL_VDBE_COVERAGE: { +#ifdef SQLITE_VDBE_COVERAGE + typedef void (*branch_callback)(void*,unsigned int, + unsigned char,unsigned char); + sqlite3GlobalConfig.xVdbeBranch = va_arg(ap,branch_callback); + sqlite3GlobalConfig.pVdbeBranchArg = va_arg(ap,void*); +#endif + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_SORTER_MMAP, db, nMax); */ + case SQLITE_TESTCTRL_SORTER_MMAP: { + sqlite3 *db = va_arg(ap, sqlite3*); + db->nMaxSorterMmap = va_arg(ap, int); + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_ISINIT); + ** + ** Return SQLITE_OK if SQLite has been initialized and SQLITE_ERROR if + ** not. + */ + case SQLITE_TESTCTRL_ISINIT: { + if( sqlite3GlobalConfig.isInit==0 ) rc = SQLITE_ERROR; + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum); + ** + ** This test control is used to create imposter tables. "db" is a pointer + ** to the database connection. dbName is the database name (ex: "main" or + ** "temp") which will receive the imposter. "onOff" turns imposter mode on + ** or off. "tnum" is the root page of the b-tree to which the imposter + ** table should connect. + ** + ** Enable imposter mode only when the schema has already been parsed. Then + ** run a single CREATE TABLE statement to construct the imposter table in + ** the parsed schema. Then turn imposter mode back off again. + ** + ** If onOff==0 and tnum>0 then reset the schema for all databases, causing + ** the schema to be reparsed the next time it is needed. This has the + ** effect of erasing all imposter tables. + */ + case SQLITE_TESTCTRL_IMPOSTER: { + sqlite3 *db = va_arg(ap, sqlite3*); + int iDb; + sqlite3_mutex_enter(db->mutex); + iDb = sqlite3FindDbName(db, va_arg(ap,const char*)); + if( iDb>=0 ){ + db->init.iDb = iDb; + db->init.busy = db->init.imposterTable = va_arg(ap,int); + db->init.newTnum = va_arg(ap,int); + if( db->init.busy==0 && db->init.newTnum>0 ){ + sqlite3ResetAllSchemasOfConnection(db); + } + } + sqlite3_mutex_leave(db->mutex); + break; + } + +#if defined(YYCOVERAGE) + /* sqlite3_test_control(SQLITE_TESTCTRL_PARSER_COVERAGE, FILE *out) + ** + ** This test control (only available when SQLite is compiled with + ** -DYYCOVERAGE) writes a report onto "out" that shows all + ** state/lookahead combinations in the parser state machine + ** which are never exercised. If any state is missed, make the + ** return code SQLITE_ERROR. + */ + case SQLITE_TESTCTRL_PARSER_COVERAGE: { + FILE *out = va_arg(ap, FILE*); + if( sqlite3ParserCoverage(out) ) rc = SQLITE_ERROR; + break; + } +#endif /* defined(YYCOVERAGE) */ + + /* sqlite3_test_control(SQLITE_TESTCTRL_RESULT_INTREAL, sqlite3_context*); + ** + ** This test-control causes the most recent sqlite3_result_int64() value + ** to be interpreted as a MEM_IntReal instead of as an MEM_Int. Normally, + ** MEM_IntReal values only arise during an INSERT operation of integer + ** values into a REAL column, so they can be challenging to test. This + ** test-control enables us to write an intreal() SQL function that can + ** inject an intreal() value at arbitrary places in an SQL statement, + ** for testing purposes. + */ + case SQLITE_TESTCTRL_RESULT_INTREAL: { + sqlite3_context *pCtx = va_arg(ap, sqlite3_context*); + sqlite3ResultIntReal(pCtx); + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_SEEK_COUNT, + ** sqlite3 *db, // Database connection + ** u64 *pnSeek // Write seek count here + ** ); + ** + ** This test-control queries the seek-counter on the "main" database + ** file. The seek-counter is written into *pnSeek and is then reset. + ** The seek-count is only available if compiled with SQLITE_DEBUG. + */ + case SQLITE_TESTCTRL_SEEK_COUNT: { + sqlite3 *db = va_arg(ap, sqlite3*); + u64 *pn = va_arg(ap, sqlite3_uint64*); + *pn = sqlite3BtreeSeekCount(db->aDb->pBt); + (void)db; /* Silence harmless unused variable warning */ + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, op, ptr) + ** + ** "ptr" is a pointer to a u32. + ** + ** op==0 Store the current sqlite3TreeTrace in *ptr + ** op==1 Set sqlite3TreeTrace to the value *ptr + ** op==2 Store the current sqlite3WhereTrace in *ptr + ** op==3 Set sqlite3WhereTrace to the value *ptr + */ + case SQLITE_TESTCTRL_TRACEFLAGS: { + int opTrace = va_arg(ap, int); + u32 *ptr = va_arg(ap, u32*); + switch( opTrace ){ + case 0: *ptr = sqlite3TreeTrace; break; + case 1: sqlite3TreeTrace = *ptr; break; + case 2: *ptr = sqlite3WhereTrace; break; + case 3: sqlite3WhereTrace = *ptr; break; + } + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_LOGEST, + ** double fIn, // Input value + ** int *pLogEst, // sqlite3LogEstFromDouble(fIn) + ** u64 *pInt, // sqlite3LogEstToInt(*pLogEst) + ** int *pLogEst2 // sqlite3LogEst(*pInt) + ** ); + ** + ** Test access for the LogEst conversion routines. + */ + case SQLITE_TESTCTRL_LOGEST: { + double rIn = va_arg(ap, double); + LogEst rLogEst = sqlite3LogEstFromDouble(rIn); + int *pI1 = va_arg(ap,int*); + u64 *pU64 = va_arg(ap,u64*); + int *pI2 = va_arg(ap,int*); + *pI1 = rLogEst; + *pU64 = sqlite3LogEstToInt(rLogEst); + *pI2 = sqlite3LogEst(*pU64); + break; + } + +#if !defined(SQLITE_OMIT_WSD) + /* sqlite3_test_control(SQLITE_TESTCTRL_USELONGDOUBLE, int X); + ** + ** X<0 Make no changes to the bUseLongDouble. Just report value. + ** X==0 Disable bUseLongDouble + ** X==1 Enable bUseLongDouble + ** X==2 Set bUseLongDouble to its default value for this platform + */ + case SQLITE_TESTCTRL_USELONGDOUBLE: { + int b = va_arg(ap, int); + if( b==2 ) b = sizeof(LONGDOUBLE_TYPE)>8; + if( b>=0 ) sqlite3Config.bUseLongDouble = b>0; + rc = sqlite3Config.bUseLongDouble!=0; + break; + } +#endif + + +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_WSD) + /* sqlite3_test_control(SQLITE_TESTCTRL_TUNE, id, *piValue) + ** + ** If "id" is an integer between 1 and SQLITE_NTUNE then set the value + ** of the id-th tuning parameter to *piValue. If "id" is between -1 + ** and -SQLITE_NTUNE, then write the current value of the (-id)-th + ** tuning parameter into *piValue. + ** + ** Tuning parameters are for use during transient development builds, + ** to help find the best values for constants in the query planner. + ** Access tuning parameters using the Tuning(ID) macro. Set the + ** parameters in the CLI using ".testctrl tune ID VALUE". + ** + ** Transient use only. Tuning parameters should not be used in + ** checked-in code. + */ + case SQLITE_TESTCTRL_TUNE: { + int id = va_arg(ap, int); + int *piValue = va_arg(ap, int*); + if( id>0 && id<=SQLITE_NTUNE ){ + Tuning(id) = *piValue; + }else if( id<0 && id>=-SQLITE_NTUNE ){ + *piValue = Tuning(-id); + }else{ + rc = SQLITE_NOTFOUND; + } + break; + } +#endif + } + va_end(ap); +#endif /* SQLITE_UNTESTABLE */ + return rc; +} + +/* +** The Pager stores the Database filename, Journal filename, and WAL filename +** consecutively in memory, in that order. The database filename is prefixed +** by four zero bytes. Locate the start of the database filename by searching +** backwards for the first byte following four consecutive zero bytes. +** +** This only works if the filename passed in was obtained from the Pager. +*/ +static const char *databaseName(const char *zName){ + while( zName[-1]!=0 || zName[-2]!=0 || zName[-3]!=0 || zName[-4]!=0 ){ + zName--; + } + return zName; +} + +/* +** Append text z[] to the end of p[]. Return a pointer to the first +** character after then zero terminator on the new text in p[]. +*/ +static char *appendText(char *p, const char *z){ + size_t n = strlen(z); + memcpy(p, z, n+1); + return p+n+1; +} + +/* +** Allocate memory to hold names for a database, journal file, WAL file, +** and query parameters. The pointer returned is valid for use by +** sqlite3_filename_database() and sqlite3_uri_parameter() and related +** functions. +** +** Memory layout must be compatible with that generated by the pager +** and expected by sqlite3_uri_parameter() and databaseName(). +*/ +SQLITE_API const char *sqlite3_create_filename( + const char *zDatabase, + const char *zJournal, + const char *zWal, + int nParam, + const char **azParam +){ + sqlite3_int64 nByte; + int i; + char *pResult, *p; + nByte = strlen(zDatabase) + strlen(zJournal) + strlen(zWal) + 10; + for(i=0; i<nParam*2; i++){ + nByte += strlen(azParam[i])+1; + } + pResult = p = sqlite3_malloc64( nByte ); + if( p==0 ) return 0; + memset(p, 0, 4); + p += 4; + p = appendText(p, zDatabase); + for(i=0; i<nParam*2; i++){ + p = appendText(p, azParam[i]); + } + *(p++) = 0; + p = appendText(p, zJournal); + p = appendText(p, zWal); + *(p++) = 0; + *(p++) = 0; + assert( (sqlite3_int64)(p - pResult)==nByte ); + return pResult + 4; +} + +/* +** Free memory obtained from sqlite3_create_filename(). It is a severe +** error to call this routine with any parameter other than a pointer +** previously obtained from sqlite3_create_filename() or a NULL pointer. +*/ +SQLITE_API void sqlite3_free_filename(const char *p){ + if( p==0 ) return; + p = databaseName(p); + sqlite3_free((char*)p - 4); +} + + +/* +** This is a utility routine, useful to VFS implementations, that checks +** to see if a database file was a URI that contained a specific query +** parameter, and if so obtains the value of the query parameter. +** +** The zFilename argument is the filename pointer passed into the xOpen() +** method of a VFS implementation. The zParam argument is the name of the +** query parameter we seek. This routine returns the value of the zParam +** parameter if it exists. If the parameter does not exist, this routine +** returns a NULL pointer. +*/ +SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam){ + if( zFilename==0 || zParam==0 ) return 0; + zFilename = databaseName(zFilename); + return uriParameter(zFilename, zParam); +} + +/* +** Return a pointer to the name of Nth query parameter of the filename. +*/ +SQLITE_API const char *sqlite3_uri_key(const char *zFilename, int N){ + if( zFilename==0 || N<0 ) return 0; + zFilename = databaseName(zFilename); + zFilename += sqlite3Strlen30(zFilename) + 1; + while( ALWAYS(zFilename) && zFilename[0] && (N--)>0 ){ + zFilename += sqlite3Strlen30(zFilename) + 1; + zFilename += sqlite3Strlen30(zFilename) + 1; + } + return zFilename[0] ? zFilename : 0; +} + +/* +** Return a boolean value for a query parameter. +*/ +SQLITE_API int sqlite3_uri_boolean(const char *zFilename, const char *zParam, int bDflt){ + const char *z = sqlite3_uri_parameter(zFilename, zParam); + bDflt = bDflt!=0; + return z ? sqlite3GetBoolean(z, bDflt) : bDflt; +} + +/* +** Return a 64-bit integer value for a query parameter. +*/ +SQLITE_API sqlite3_int64 sqlite3_uri_int64( + const char *zFilename, /* Filename as passed to xOpen */ + const char *zParam, /* URI parameter sought */ + sqlite3_int64 bDflt /* return if parameter is missing */ +){ + const char *z = sqlite3_uri_parameter(zFilename, zParam); + sqlite3_int64 v; + if( z && sqlite3DecOrHexToI64(z, &v)==0 ){ + bDflt = v; + } + return bDflt; +} + +/* +** Translate a filename that was handed to a VFS routine into the corresponding +** database, journal, or WAL file. +** +** It is an error to pass this routine a filename string that was not +** passed into the VFS from the SQLite core. Doing so is similar to +** passing free() a pointer that was not obtained from malloc() - it is +** an error that we cannot easily detect but that will likely cause memory +** corruption. +*/ +SQLITE_API const char *sqlite3_filename_database(const char *zFilename){ + if( zFilename==0 ) return 0; + return databaseName(zFilename); +} +SQLITE_API const char *sqlite3_filename_journal(const char *zFilename){ + if( zFilename==0 ) return 0; + zFilename = databaseName(zFilename); + zFilename += sqlite3Strlen30(zFilename) + 1; + while( ALWAYS(zFilename) && zFilename[0] ){ + zFilename += sqlite3Strlen30(zFilename) + 1; + zFilename += sqlite3Strlen30(zFilename) + 1; + } + return zFilename + 1; +} +SQLITE_API const char *sqlite3_filename_wal(const char *zFilename){ +#ifdef SQLITE_OMIT_WAL + return 0; +#else + zFilename = sqlite3_filename_journal(zFilename); + if( zFilename ) zFilename += sqlite3Strlen30(zFilename) + 1; + return zFilename; +#endif +} + +/* +** Return the Btree pointer identified by zDbName. Return NULL if not found. +*/ +SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){ + int iDb = zDbName ? sqlite3FindDbName(db, zDbName) : 0; + return iDb<0 ? 0 : db->aDb[iDb].pBt; +} + +/* +** Return the name of the N-th database schema. Return NULL if N is out +** of range. +*/ +SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + if( N<0 || N>=db->nDb ){ + return 0; + }else{ + return db->aDb[N].zDbSName; + } +} + +/* +** Return the filename of the database associated with a database +** connection. +*/ +SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName){ + Btree *pBt; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + pBt = sqlite3DbNameToBtree(db, zDbName); + return pBt ? sqlite3BtreeGetFilename(pBt) : 0; +} + +/* +** Return 1 if database is read-only or 0 if read/write. Return -1 if +** no such database exists. +*/ +SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){ + Btree *pBt; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return -1; + } +#endif + pBt = sqlite3DbNameToBtree(db, zDbName); + return pBt ? sqlite3BtreeIsReadonly(pBt) : -1; +} + +#ifdef SQLITE_ENABLE_SNAPSHOT +/* +** Obtain a snapshot handle for the snapshot of database zDb currently +** being read by handle db. +*/ +SQLITE_API int sqlite3_snapshot_get( + sqlite3 *db, + const char *zDb, + sqlite3_snapshot **ppSnapshot +){ + int rc = SQLITE_ERROR; +#ifndef SQLITE_OMIT_WAL + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + + if( db->autoCommit==0 ){ + int iDb = sqlite3FindDbName(db, zDb); + if( iDb==0 || iDb>1 ){ + Btree *pBt = db->aDb[iDb].pBt; + if( SQLITE_TXN_WRITE!=sqlite3BtreeTxnState(pBt) ){ + rc = sqlite3BtreeBeginTrans(pBt, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot); + } + } + } + } + + sqlite3_mutex_leave(db->mutex); +#endif /* SQLITE_OMIT_WAL */ + return rc; +} + +/* +** Open a read-transaction on the snapshot identified by pSnapshot. +*/ +SQLITE_API int sqlite3_snapshot_open( + sqlite3 *db, + const char *zDb, + sqlite3_snapshot *pSnapshot +){ + int rc = SQLITE_ERROR; +#ifndef SQLITE_OMIT_WAL + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( db->autoCommit==0 ){ + int iDb; + iDb = sqlite3FindDbName(db, zDb); + if( iDb==0 || iDb>1 ){ + Btree *pBt = db->aDb[iDb].pBt; + if( sqlite3BtreeTxnState(pBt)!=SQLITE_TXN_WRITE ){ + Pager *pPager = sqlite3BtreePager(pBt); + int bUnlock = 0; + if( sqlite3BtreeTxnState(pBt)!=SQLITE_TXN_NONE ){ + if( db->nVdbeActive==0 ){ + rc = sqlite3PagerSnapshotCheck(pPager, pSnapshot); + if( rc==SQLITE_OK ){ + bUnlock = 1; + rc = sqlite3BtreeCommit(pBt); + } + } + }else{ + rc = SQLITE_OK; + } + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSnapshotOpen(pPager, pSnapshot); + } + if( rc==SQLITE_OK ){ + rc = sqlite3BtreeBeginTrans(pBt, 0, 0); + sqlite3PagerSnapshotOpen(pPager, 0); + } + if( bUnlock ){ + sqlite3PagerSnapshotUnlock(pPager); + } + } + } + } + + sqlite3_mutex_leave(db->mutex); +#endif /* SQLITE_OMIT_WAL */ + return rc; +} + +/* +** Recover as many snapshots as possible from the wal file associated with +** schema zDb of database db. +*/ +SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){ + int rc = SQLITE_ERROR; +#ifndef SQLITE_OMIT_WAL + int iDb; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + + sqlite3_mutex_enter(db->mutex); + iDb = sqlite3FindDbName(db, zDb); + if( iDb==0 || iDb>1 ){ + Btree *pBt = db->aDb[iDb].pBt; + if( SQLITE_TXN_NONE==sqlite3BtreeTxnState(pBt) ){ + rc = sqlite3BtreeBeginTrans(pBt, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSnapshotRecover(sqlite3BtreePager(pBt)); + sqlite3BtreeCommit(pBt); + } + } + } + sqlite3_mutex_leave(db->mutex); +#endif /* SQLITE_OMIT_WAL */ + return rc; +} + +/* +** Free a snapshot handle obtained from sqlite3_snapshot_get(). +*/ +SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){ + sqlite3_free(pSnapshot); +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ + +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS +/* +** Given the name of a compile-time option, return true if that option +** was used and false if not. +** +** The name can optionally begin with "SQLITE_" but the "SQLITE_" prefix +** is not required for a match. +*/ +SQLITE_API int sqlite3_compileoption_used(const char *zOptName){ + int i, n; + int nOpt; + const char **azCompileOpt; + +#if SQLITE_ENABLE_API_ARMOR + if( zOptName==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + + azCompileOpt = sqlite3CompileOptions(&nOpt); + + if( sqlite3StrNICmp(zOptName, "SQLITE_", 7)==0 ) zOptName += 7; + n = sqlite3Strlen30(zOptName); + + /* Since nOpt is normally in single digits, a linear search is + ** adequate. No need for a binary search. */ + for(i=0; i<nOpt; i++){ + if( sqlite3StrNICmp(zOptName, azCompileOpt[i], n)==0 + && sqlite3IsIdChar((unsigned char)azCompileOpt[i][n])==0 + ){ + return 1; + } + } + return 0; +} + +/* +** Return the N-th compile-time option string. If N is out of range, +** return a NULL pointer. +*/ +SQLITE_API const char *sqlite3_compileoption_get(int N){ + int nOpt; + const char **azCompileOpt; + azCompileOpt = sqlite3CompileOptions(&nOpt); + if( N>=0 && N<nOpt ){ + return azCompileOpt[N]; + } + return 0; +} +#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ + +/************** End of main.c ************************************************/ +/************** Begin file notify.c ******************************************/ +/* +** 2009 March 3 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the implementation of the sqlite3_unlock_notify() +** API method and its associated functionality. +*/ +/* #include "sqliteInt.h" */ +/* #include "btreeInt.h" */ + +/* Omit this entire file if SQLITE_ENABLE_UNLOCK_NOTIFY is not defined. */ +#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY + +/* +** Public interfaces: +** +** sqlite3ConnectionBlocked() +** sqlite3ConnectionUnlocked() +** sqlite3ConnectionClosed() +** sqlite3_unlock_notify() +*/ + +#define assertMutexHeld() \ + assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)) ) + +/* +** Head of a linked list of all sqlite3 objects created by this process +** for which either sqlite3.pBlockingConnection or sqlite3.pUnlockConnection +** is not NULL. This variable may only accessed while the STATIC_MAIN +** mutex is held. +*/ +static sqlite3 *SQLITE_WSD sqlite3BlockedList = 0; + +#ifndef NDEBUG +/* +** This function is a complex assert() that verifies the following +** properties of the blocked connections list: +** +** 1) Each entry in the list has a non-NULL value for either +** pUnlockConnection or pBlockingConnection, or both. +** +** 2) All entries in the list that share a common value for +** xUnlockNotify are grouped together. +** +** 3) If the argument db is not NULL, then none of the entries in the +** blocked connections list have pUnlockConnection or pBlockingConnection +** set to db. This is used when closing connection db. +*/ +static void checkListProperties(sqlite3 *db){ + sqlite3 *p; + for(p=sqlite3BlockedList; p; p=p->pNextBlocked){ + int seen = 0; + sqlite3 *p2; + + /* Verify property (1) */ + assert( p->pUnlockConnection || p->pBlockingConnection ); + + /* Verify property (2) */ + for(p2=sqlite3BlockedList; p2!=p; p2=p2->pNextBlocked){ + if( p2->xUnlockNotify==p->xUnlockNotify ) seen = 1; + assert( p2->xUnlockNotify==p->xUnlockNotify || !seen ); + assert( db==0 || p->pUnlockConnection!=db ); + assert( db==0 || p->pBlockingConnection!=db ); + } + } +} +#else +# define checkListProperties(x) +#endif + +/* +** Remove connection db from the blocked connections list. If connection +** db is not currently a part of the list, this function is a no-op. +*/ +static void removeFromBlockedList(sqlite3 *db){ + sqlite3 **pp; + assertMutexHeld(); + for(pp=&sqlite3BlockedList; *pp; pp = &(*pp)->pNextBlocked){ + if( *pp==db ){ + *pp = (*pp)->pNextBlocked; + break; + } + } +} + +/* +** Add connection db to the blocked connections list. It is assumed +** that it is not already a part of the list. +*/ +static void addToBlockedList(sqlite3 *db){ + sqlite3 **pp; + assertMutexHeld(); + for( + pp=&sqlite3BlockedList; + *pp && (*pp)->xUnlockNotify!=db->xUnlockNotify; + pp=&(*pp)->pNextBlocked + ); + db->pNextBlocked = *pp; + *pp = db; +} + +/* +** Obtain the STATIC_MAIN mutex. +*/ +static void enterMutex(void){ + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)); + checkListProperties(0); +} + +/* +** Release the STATIC_MAIN mutex. +*/ +static void leaveMutex(void){ + assertMutexHeld(); + checkListProperties(0); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)); +} + +/* +** Register an unlock-notify callback. +** +** This is called after connection "db" has attempted some operation +** but has received an SQLITE_LOCKED error because another connection +** (call it pOther) in the same process was busy using the same shared +** cache. pOther is found by looking at db->pBlockingConnection. +** +** If there is no blocking connection, the callback is invoked immediately, +** before this routine returns. +** +** If pOther is already blocked on db, then report SQLITE_LOCKED, to indicate +** a deadlock. +** +** Otherwise, make arrangements to invoke xNotify when pOther drops +** its locks. +** +** Each call to this routine overrides any prior callbacks registered +** on the same "db". If xNotify==0 then any prior callbacks are immediately +** cancelled. +*/ +SQLITE_API int sqlite3_unlock_notify( + sqlite3 *db, + void (*xNotify)(void **, int), + void *pArg +){ + int rc = SQLITE_OK; + + sqlite3_mutex_enter(db->mutex); + enterMutex(); + + if( xNotify==0 ){ + removeFromBlockedList(db); + db->pBlockingConnection = 0; + db->pUnlockConnection = 0; + db->xUnlockNotify = 0; + db->pUnlockArg = 0; + }else if( 0==db->pBlockingConnection ){ + /* The blocking transaction has been concluded. Or there never was a + ** blocking transaction. In either case, invoke the notify callback + ** immediately. + */ + xNotify(&pArg, 1); + }else{ + sqlite3 *p; + + for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection){} + if( p ){ + rc = SQLITE_LOCKED; /* Deadlock detected. */ + }else{ + db->pUnlockConnection = db->pBlockingConnection; + db->xUnlockNotify = xNotify; + db->pUnlockArg = pArg; + removeFromBlockedList(db); + addToBlockedList(db); + } + } + + leaveMutex(); + assert( !db->mallocFailed ); + sqlite3ErrorWithMsg(db, rc, (rc?"database is deadlocked":0)); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** This function is called while stepping or preparing a statement +** associated with connection db. The operation will return SQLITE_LOCKED +** to the user because it requires a lock that will not be available +** until connection pBlocker concludes its current transaction. +*/ +SQLITE_PRIVATE void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){ + enterMutex(); + if( db->pBlockingConnection==0 && db->pUnlockConnection==0 ){ + addToBlockedList(db); + } + db->pBlockingConnection = pBlocker; + leaveMutex(); +} + +/* +** This function is called when +** the transaction opened by database db has just finished. Locks held +** by database connection db have been released. +** +** This function loops through each entry in the blocked connections +** list and does the following: +** +** 1) If the sqlite3.pBlockingConnection member of a list entry is +** set to db, then set pBlockingConnection=0. +** +** 2) If the sqlite3.pUnlockConnection member of a list entry is +** set to db, then invoke the configured unlock-notify callback and +** set pUnlockConnection=0. +** +** 3) If the two steps above mean that pBlockingConnection==0 and +** pUnlockConnection==0, remove the entry from the blocked connections +** list. +*/ +SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){ + void (*xUnlockNotify)(void **, int) = 0; /* Unlock-notify cb to invoke */ + int nArg = 0; /* Number of entries in aArg[] */ + sqlite3 **pp; /* Iterator variable */ + void **aArg; /* Arguments to the unlock callback */ + void **aDyn = 0; /* Dynamically allocated space for aArg[] */ + void *aStatic[16]; /* Starter space for aArg[]. No malloc required */ + + aArg = aStatic; + enterMutex(); /* Enter STATIC_MAIN mutex */ + + /* This loop runs once for each entry in the blocked-connections list. */ + for(pp=&sqlite3BlockedList; *pp; /* no-op */ ){ + sqlite3 *p = *pp; + + /* Step 1. */ + if( p->pBlockingConnection==db ){ + p->pBlockingConnection = 0; + } + + /* Step 2. */ + if( p->pUnlockConnection==db ){ + assert( p->xUnlockNotify ); + if( p->xUnlockNotify!=xUnlockNotify && nArg!=0 ){ + xUnlockNotify(aArg, nArg); + nArg = 0; + } + + sqlite3BeginBenignMalloc(); + assert( aArg==aDyn || (aDyn==0 && aArg==aStatic) ); + assert( nArg<=(int)ArraySize(aStatic) || aArg==aDyn ); + if( (!aDyn && nArg==(int)ArraySize(aStatic)) + || (aDyn && nArg==(int)(sqlite3MallocSize(aDyn)/sizeof(void*))) + ){ + /* The aArg[] array needs to grow. */ + void **pNew = (void **)sqlite3Malloc(nArg*sizeof(void *)*2); + if( pNew ){ + memcpy(pNew, aArg, nArg*sizeof(void *)); + sqlite3_free(aDyn); + aDyn = aArg = pNew; + }else{ + /* This occurs when the array of context pointers that need to + ** be passed to the unlock-notify callback is larger than the + ** aStatic[] array allocated on the stack and the attempt to + ** allocate a larger array from the heap has failed. + ** + ** This is a difficult situation to handle. Returning an error + ** code to the caller is insufficient, as even if an error code + ** is returned the transaction on connection db will still be + ** closed and the unlock-notify callbacks on blocked connections + ** will go unissued. This might cause the application to wait + ** indefinitely for an unlock-notify callback that will never + ** arrive. + ** + ** Instead, invoke the unlock-notify callback with the context + ** array already accumulated. We can then clear the array and + ** begin accumulating any further context pointers without + ** requiring any dynamic allocation. This is sub-optimal because + ** it means that instead of one callback with a large array of + ** context pointers the application will receive two or more + ** callbacks with smaller arrays of context pointers, which will + ** reduce the applications ability to prioritize multiple + ** connections. But it is the best that can be done under the + ** circumstances. + */ + xUnlockNotify(aArg, nArg); + nArg = 0; + } + } + sqlite3EndBenignMalloc(); + + aArg[nArg++] = p->pUnlockArg; + xUnlockNotify = p->xUnlockNotify; + p->pUnlockConnection = 0; + p->xUnlockNotify = 0; + p->pUnlockArg = 0; + } + + /* Step 3. */ + if( p->pBlockingConnection==0 && p->pUnlockConnection==0 ){ + /* Remove connection p from the blocked connections list. */ + *pp = p->pNextBlocked; + p->pNextBlocked = 0; + }else{ + pp = &p->pNextBlocked; + } + } + + if( nArg!=0 ){ + xUnlockNotify(aArg, nArg); + } + sqlite3_free(aDyn); + leaveMutex(); /* Leave STATIC_MAIN mutex */ +} + +/* +** This is called when the database connection passed as an argument is +** being closed. The connection is removed from the blocked list. +*/ +SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){ + sqlite3ConnectionUnlocked(db); + enterMutex(); + removeFromBlockedList(db); + checkListProperties(db); + leaveMutex(); +} +#endif + +/************** End of notify.c **********************************************/ +/************** Begin file fts3.c ********************************************/ +/* +** 2006 Oct 10 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This is an SQLite module implementing full-text search. +*/ + +/* +** The code in this file is only compiled if: +** +** * The FTS3 module is being built as an extension +** (in which case SQLITE_CORE is not defined), or +** +** * The FTS3 module is being built into the core of +** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). +*/ + +/* The full-text index is stored in a series of b+tree (-like) +** structures called segments which map terms to doclists. The +** structures are like b+trees in layout, but are constructed from the +** bottom up in optimal fashion and are not updatable. Since trees +** are built from the bottom up, things will be described from the +** bottom up. +** +** +**** Varints **** +** The basic unit of encoding is a variable-length integer called a +** varint. We encode variable-length integers in little-endian order +** using seven bits * per byte as follows: +** +** KEY: +** A = 0xxxxxxx 7 bits of data and one flag bit +** B = 1xxxxxxx 7 bits of data and one flag bit +** +** 7 bits - A +** 14 bits - BA +** 21 bits - BBA +** and so on. +** +** This is similar in concept to how sqlite encodes "varints" but +** the encoding is not the same. SQLite varints are big-endian +** are are limited to 9 bytes in length whereas FTS3 varints are +** little-endian and can be up to 10 bytes in length (in theory). +** +** Example encodings: +** +** 1: 0x01 +** 127: 0x7f +** 128: 0x81 0x00 +** +** +**** Document lists **** +** A doclist (document list) holds a docid-sorted list of hits for a +** given term. Doclists hold docids and associated token positions. +** A docid is the unique integer identifier for a single document. +** A position is the index of a word within the document. The first +** word of the document has a position of 0. +** +** FTS3 used to optionally store character offsets using a compile-time +** option. But that functionality is no longer supported. +** +** A doclist is stored like this: +** +** array { +** varint docid; (delta from previous doclist) +** array { (position list for column 0) +** varint position; (2 more than the delta from previous position) +** } +** array { +** varint POS_COLUMN; (marks start of position list for new column) +** varint column; (index of new column) +** array { +** varint position; (2 more than the delta from previous position) +** } +** } +** varint POS_END; (marks end of positions for this document. +** } +** +** Here, array { X } means zero or more occurrences of X, adjacent in +** memory. A "position" is an index of a token in the token stream +** generated by the tokenizer. Note that POS_END and POS_COLUMN occur +** in the same logical place as the position element, and act as sentinals +** ending a position list array. POS_END is 0. POS_COLUMN is 1. +** The positions numbers are not stored literally but rather as two more +** than the difference from the prior position, or the just the position plus +** 2 for the first position. Example: +** +** label: A B C D E F G H I J K +** value: 123 5 9 1 1 14 35 0 234 72 0 +** +** The 123 value is the first docid. For column zero in this document +** there are two matches at positions 3 and 10 (5-2 and 9-2+3). The 1 +** at D signals the start of a new column; the 1 at E indicates that the +** new column is column number 1. There are two positions at 12 and 45 +** (14-2 and 35-2+12). The 0 at H indicate the end-of-document. The +** 234 at I is the delta to next docid (357). It has one position 70 +** (72-2) and then terminates with the 0 at K. +** +** A "position-list" is the list of positions for multiple columns for +** a single docid. A "column-list" is the set of positions for a single +** column. Hence, a position-list consists of one or more column-lists, +** a document record consists of a docid followed by a position-list and +** a doclist consists of one or more document records. +** +** A bare doclist omits the position information, becoming an +** array of varint-encoded docids. +** +**** Segment leaf nodes **** +** Segment leaf nodes store terms and doclists, ordered by term. Leaf +** nodes are written using LeafWriter, and read using LeafReader (to +** iterate through a single leaf node's data) and LeavesReader (to +** iterate through a segment's entire leaf layer). Leaf nodes have +** the format: +** +** varint iHeight; (height from leaf level, always 0) +** varint nTerm; (length of first term) +** char pTerm[nTerm]; (content of first term) +** varint nDoclist; (length of term's associated doclist) +** char pDoclist[nDoclist]; (content of doclist) +** array { +** (further terms are delta-encoded) +** varint nPrefix; (length of prefix shared with previous term) +** varint nSuffix; (length of unshared suffix) +** char pTermSuffix[nSuffix];(unshared suffix of next term) +** varint nDoclist; (length of term's associated doclist) +** char pDoclist[nDoclist]; (content of doclist) +** } +** +** Here, array { X } means zero or more occurrences of X, adjacent in +** memory. +** +** Leaf nodes are broken into blocks which are stored contiguously in +** the %_segments table in sorted order. This means that when the end +** of a node is reached, the next term is in the node with the next +** greater node id. +** +** New data is spilled to a new leaf node when the current node +** exceeds LEAF_MAX bytes (default 2048). New data which itself is +** larger than STANDALONE_MIN (default 1024) is placed in a standalone +** node (a leaf node with a single term and doclist). The goal of +** these settings is to pack together groups of small doclists while +** making it efficient to directly access large doclists. The +** assumption is that large doclists represent terms which are more +** likely to be query targets. +** +** TODO(shess) It may be useful for blocking decisions to be more +** dynamic. For instance, it may make more sense to have a 2.5k leaf +** node rather than splitting into 2k and .5k nodes. My intuition is +** that this might extend through 2x or 4x the pagesize. +** +** +**** Segment interior nodes **** +** Segment interior nodes store blockids for subtree nodes and terms +** to describe what data is stored by the each subtree. Interior +** nodes are written using InteriorWriter, and read using +** InteriorReader. InteriorWriters are created as needed when +** SegmentWriter creates new leaf nodes, or when an interior node +** itself grows too big and must be split. The format of interior +** nodes: +** +** varint iHeight; (height from leaf level, always >0) +** varint iBlockid; (block id of node's leftmost subtree) +** optional { +** varint nTerm; (length of first term) +** char pTerm[nTerm]; (content of first term) +** array { +** (further terms are delta-encoded) +** varint nPrefix; (length of shared prefix with previous term) +** varint nSuffix; (length of unshared suffix) +** char pTermSuffix[nSuffix]; (unshared suffix of next term) +** } +** } +** +** Here, optional { X } means an optional element, while array { X } +** means zero or more occurrences of X, adjacent in memory. +** +** An interior node encodes n terms separating n+1 subtrees. The +** subtree blocks are contiguous, so only the first subtree's blockid +** is encoded. The subtree at iBlockid will contain all terms less +** than the first term encoded (or all terms if no term is encoded). +** Otherwise, for terms greater than or equal to pTerm[i] but less +** than pTerm[i+1], the subtree for that term will be rooted at +** iBlockid+i. Interior nodes only store enough term data to +** distinguish adjacent children (if the rightmost term of the left +** child is "something", and the leftmost term of the right child is +** "wicked", only "w" is stored). +** +** New data is spilled to a new interior node at the same height when +** the current node exceeds INTERIOR_MAX bytes (default 2048). +** INTERIOR_MIN_TERMS (default 7) keeps large terms from monopolizing +** interior nodes and making the tree too skinny. The interior nodes +** at a given height are naturally tracked by interior nodes at +** height+1, and so on. +** +** +**** Segment directory **** +** The segment directory in table %_segdir stores meta-information for +** merging and deleting segments, and also the root node of the +** segment's tree. +** +** The root node is the top node of the segment's tree after encoding +** the entire segment, restricted to ROOT_MAX bytes (default 1024). +** This could be either a leaf node or an interior node. If the top +** node requires more than ROOT_MAX bytes, it is flushed to %_segments +** and a new root interior node is generated (which should always fit +** within ROOT_MAX because it only needs space for 2 varints, the +** height and the blockid of the previous root). +** +** The meta-information in the segment directory is: +** level - segment level (see below) +** idx - index within level +** - (level,idx uniquely identify a segment) +** start_block - first leaf node +** leaves_end_block - last leaf node +** end_block - last block (including interior nodes) +** root - contents of root node +** +** If the root node is a leaf node, then start_block, +** leaves_end_block, and end_block are all 0. +** +** +**** Segment merging **** +** To amortize update costs, segments are grouped into levels and +** merged in batches. Each increase in level represents exponentially +** more documents. +** +** New documents (actually, document updates) are tokenized and +** written individually (using LeafWriter) to a level 0 segment, with +** incrementing idx. When idx reaches MERGE_COUNT (default 16), all +** level 0 segments are merged into a single level 1 segment. Level 1 +** is populated like level 0, and eventually MERGE_COUNT level 1 +** segments are merged to a single level 2 segment (representing +** MERGE_COUNT^2 updates), and so on. +** +** A segment merge traverses all segments at a given level in +** parallel, performing a straightforward sorted merge. Since segment +** leaf nodes are written in to the %_segments table in order, this +** merge traverses the underlying sqlite disk structures efficiently. +** After the merge, all segment blocks from the merged level are +** deleted. +** +** MERGE_COUNT controls how often we merge segments. 16 seems to be +** somewhat of a sweet spot for insertion performance. 32 and 64 show +** very similar performance numbers to 16 on insertion, though they're +** a tiny bit slower (perhaps due to more overhead in merge-time +** sorting). 8 is about 20% slower than 16, 4 about 50% slower than +** 16, 2 about 66% slower than 16. +** +** At query time, high MERGE_COUNT increases the number of segments +** which need to be scanned and merged. For instance, with 100k docs +** inserted: +** +** MERGE_COUNT segments +** 16 25 +** 8 12 +** 4 10 +** 2 6 +** +** This appears to have only a moderate impact on queries for very +** frequent terms (which are somewhat dominated by segment merge +** costs), and infrequent and non-existent terms still seem to be fast +** even with many segments. +** +** TODO(shess) That said, it would be nice to have a better query-side +** argument for MERGE_COUNT of 16. Also, it is possible/likely that +** optimizations to things like doclist merging will swing the sweet +** spot around. +** +** +** +**** Handling of deletions and updates **** +** Since we're using a segmented structure, with no docid-oriented +** index into the term index, we clearly cannot simply update the term +** index when a document is deleted or updated. For deletions, we +** write an empty doclist (varint(docid) varint(POS_END)), for updates +** we simply write the new doclist. Segment merges overwrite older +** data for a particular docid with newer data, so deletes or updates +** will eventually overtake the earlier data and knock it out. The +** query logic likewise merges doclists so that newer data knocks out +** older data. +*/ + +/************** Include fts3Int.h in the middle of fts3.c ********************/ +/************** Begin file fts3Int.h *****************************************/ +/* +** 2009 Nov 12 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +*/ +#ifndef _FTSINT_H +#define _FTSINT_H + +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif + +/* FTS3/FTS4 require virtual tables */ +#ifdef SQLITE_OMIT_VIRTUALTABLE +# undef SQLITE_ENABLE_FTS3 +# undef SQLITE_ENABLE_FTS4 +#endif + +/* +** FTS4 is really an extension for FTS3. It is enabled using the +** SQLITE_ENABLE_FTS3 macro. But to avoid confusion we also all +** the SQLITE_ENABLE_FTS4 macro to serve as an alisse for SQLITE_ENABLE_FTS3. +*/ +#if defined(SQLITE_ENABLE_FTS4) && !defined(SQLITE_ENABLE_FTS3) +# define SQLITE_ENABLE_FTS3 +#endif + +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* If not building as part of the core, include sqlite3ext.h. */ +#ifndef SQLITE_CORE +/* # include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT3 +#endif + +/* #include "sqlite3.h" */ +/************** Include fts3_tokenizer.h in the middle of fts3Int.h **********/ +/************** Begin file fts3_tokenizer.h **********************************/ +/* +** 2006 July 10 +** +** The author disclaims copyright to this source code. +** +************************************************************************* +** Defines the interface to tokenizers used by fulltext-search. There +** are three basic components: +** +** sqlite3_tokenizer_module is a singleton defining the tokenizer +** interface functions. This is essentially the class structure for +** tokenizers. +** +** sqlite3_tokenizer is used to define a particular tokenizer, perhaps +** including customization information defined at creation time. +** +** sqlite3_tokenizer_cursor is generated by a tokenizer to generate +** tokens from a particular input. +*/ +#ifndef _FTS3_TOKENIZER_H_ +#define _FTS3_TOKENIZER_H_ + +/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. +** If tokenizers are to be allowed to call sqlite3_*() functions, then +** we will need a way to register the API consistently. +*/ +/* #include "sqlite3.h" */ + +/* +** Structures used by the tokenizer interface. When a new tokenizer +** implementation is registered, the caller provides a pointer to +** an sqlite3_tokenizer_module containing pointers to the callback +** functions that make up an implementation. +** +** When an fts3 table is created, it passes any arguments passed to +** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the +** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer +** implementation. The xCreate() function in turn returns an +** sqlite3_tokenizer structure representing the specific tokenizer to +** be used for the fts3 table (customized by the tokenizer clause arguments). +** +** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen() +** method is called. It returns an sqlite3_tokenizer_cursor object +** that may be used to tokenize a specific input buffer based on +** the tokenization rules supplied by a specific sqlite3_tokenizer +** object. +*/ +typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; +typedef struct sqlite3_tokenizer sqlite3_tokenizer; +typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; + +struct sqlite3_tokenizer_module { + + /* + ** Structure version. Should always be set to 0 or 1. + */ + int iVersion; + + /* + ** Create a new tokenizer. The values in the argv[] array are the + ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL + ** TABLE statement that created the fts3 table. For example, if + ** the following SQL is executed: + ** + ** CREATE .. USING fts3( ... , tokenizer <tokenizer-name> arg1 arg2) + ** + ** then argc is set to 2, and the argv[] array contains pointers + ** to the strings "arg1" and "arg2". + ** + ** This method should return either SQLITE_OK (0), or an SQLite error + ** code. If SQLITE_OK is returned, then *ppTokenizer should be set + ** to point at the newly created tokenizer structure. The generic + ** sqlite3_tokenizer.pModule variable should not be initialized by + ** this callback. The caller will do so. + */ + int (*xCreate)( + int argc, /* Size of argv array */ + const char *const*argv, /* Tokenizer argument strings */ + sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ + ); + + /* + ** Destroy an existing tokenizer. The fts3 module calls this method + ** exactly once for each successful call to xCreate(). + */ + int (*xDestroy)(sqlite3_tokenizer *pTokenizer); + + /* + ** Create a tokenizer cursor to tokenize an input buffer. The caller + ** is responsible for ensuring that the input buffer remains valid + ** until the cursor is closed (using the xClose() method). + */ + int (*xOpen)( + sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ + const char *pInput, int nBytes, /* Input buffer */ + sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ + ); + + /* + ** Destroy an existing tokenizer cursor. The fts3 module calls this + ** method exactly once for each successful call to xOpen(). + */ + int (*xClose)(sqlite3_tokenizer_cursor *pCursor); + + /* + ** Retrieve the next token from the tokenizer cursor pCursor. This + ** method should either return SQLITE_OK and set the values of the + ** "OUT" variables identified below, or SQLITE_DONE to indicate that + ** the end of the buffer has been reached, or an SQLite error code. + ** + ** *ppToken should be set to point at a buffer containing the + ** normalized version of the token (i.e. after any case-folding and/or + ** stemming has been performed). *pnBytes should be set to the length + ** of this buffer in bytes. The input text that generated the token is + ** identified by the byte offsets returned in *piStartOffset and + ** *piEndOffset. *piStartOffset should be set to the index of the first + ** byte of the token in the input buffer. *piEndOffset should be set + ** to the index of the first byte just past the end of the token in + ** the input buffer. + ** + ** The buffer *ppToken is set to point at is managed by the tokenizer + ** implementation. It is only required to be valid until the next call + ** to xNext() or xClose(). + */ + /* TODO(shess) current implementation requires pInput to be + ** nul-terminated. This should either be fixed, or pInput/nBytes + ** should be converted to zInput. + */ + int (*xNext)( + sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ + const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */ + int *piStartOffset, /* OUT: Byte offset of token in input buffer */ + int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ + int *piPosition /* OUT: Number of tokens returned before this one */ + ); + + /*********************************************************************** + ** Methods below this point are only available if iVersion>=1. + */ + + /* + ** Configure the language id of a tokenizer cursor. + */ + int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid); +}; + +struct sqlite3_tokenizer { + const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ + /* Tokenizer implementations will typically add additional fields */ +}; + +struct sqlite3_tokenizer_cursor { + sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ + /* Tokenizer implementations will typically add additional fields */ +}; + +int fts3_global_term_cnt(int iTerm, int iCol); +int fts3_term_cnt(int iTerm, int iCol); + + +#endif /* _FTS3_TOKENIZER_H_ */ + +/************** End of fts3_tokenizer.h **************************************/ +/************** Continuing where we left off in fts3Int.h ********************/ +/************** Include fts3_hash.h in the middle of fts3Int.h ***************/ +/************** Begin file fts3_hash.h ***************************************/ +/* +** 2001 September 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the header file for the generic hash-table implementation +** used in SQLite. We've modified it slightly to serve as a standalone +** hash table implementation for the full-text indexing module. +** +*/ +#ifndef _FTS3_HASH_H_ +#define _FTS3_HASH_H_ + +/* Forward declarations of structures. */ +typedef struct Fts3Hash Fts3Hash; +typedef struct Fts3HashElem Fts3HashElem; + +/* A complete hash table is an instance of the following structure. +** The internals of this structure are intended to be opaque -- client +** code should not attempt to access or modify the fields of this structure +** directly. Change this structure only by using the routines below. +** However, many of the "procedures" and "functions" for modifying and +** accessing this structure are really macros, so we can't really make +** this structure opaque. +*/ +struct Fts3Hash { + char keyClass; /* HASH_INT, _POINTER, _STRING, _BINARY */ + char copyKey; /* True if copy of key made on insert */ + int count; /* Number of entries in this table */ + Fts3HashElem *first; /* The first element of the array */ + int htsize; /* Number of buckets in the hash table */ + struct _fts3ht { /* the hash table */ + int count; /* Number of entries with this hash */ + Fts3HashElem *chain; /* Pointer to first entry with this hash */ + } *ht; +}; + +/* Each element in the hash table is an instance of the following +** structure. All elements are stored on a single doubly-linked list. +** +** Again, this structure is intended to be opaque, but it can't really +** be opaque because it is used by macros. +*/ +struct Fts3HashElem { + Fts3HashElem *next, *prev; /* Next and previous elements in the table */ + void *data; /* Data associated with this element */ + void *pKey; int nKey; /* Key associated with this element */ +}; + +/* +** There are 2 different modes of operation for a hash table: +** +** FTS3_HASH_STRING pKey points to a string that is nKey bytes long +** (including the null-terminator, if any). Case +** is respected in comparisons. +** +** FTS3_HASH_BINARY pKey points to binary data nKey bytes long. +** memcmp() is used to compare keys. +** +** A copy of the key is made if the copyKey parameter to fts3HashInit is 1. +*/ +#define FTS3_HASH_STRING 1 +#define FTS3_HASH_BINARY 2 + +/* +** Access routines. To delete, insert a NULL pointer. +*/ +SQLITE_PRIVATE void sqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copyKey); +SQLITE_PRIVATE void *sqlite3Fts3HashInsert(Fts3Hash*, const void *pKey, int nKey, void *pData); +SQLITE_PRIVATE void *sqlite3Fts3HashFind(const Fts3Hash*, const void *pKey, int nKey); +SQLITE_PRIVATE void sqlite3Fts3HashClear(Fts3Hash*); +SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const void *, int); + +/* +** Shorthand for the functions above +*/ +#define fts3HashInit sqlite3Fts3HashInit +#define fts3HashInsert sqlite3Fts3HashInsert +#define fts3HashFind sqlite3Fts3HashFind +#define fts3HashClear sqlite3Fts3HashClear +#define fts3HashFindElem sqlite3Fts3HashFindElem + +/* +** Macros for looping over all elements of a hash table. The idiom is +** like this: +** +** Fts3Hash h; +** Fts3HashElem *p; +** ... +** for(p=fts3HashFirst(&h); p; p=fts3HashNext(p)){ +** SomeStructure *pData = fts3HashData(p); +** // do something with pData +** } +*/ +#define fts3HashFirst(H) ((H)->first) +#define fts3HashNext(E) ((E)->next) +#define fts3HashData(E) ((E)->data) +#define fts3HashKey(E) ((E)->pKey) +#define fts3HashKeysize(E) ((E)->nKey) + +/* +** Number of entries in a hash table +*/ +#define fts3HashCount(H) ((H)->count) + +#endif /* _FTS3_HASH_H_ */ + +/************** End of fts3_hash.h *******************************************/ +/************** Continuing where we left off in fts3Int.h ********************/ + +/* +** This constant determines the maximum depth of an FTS expression tree +** that the library will create and use. FTS uses recursion to perform +** various operations on the query tree, so the disadvantage of a large +** limit is that it may allow very large queries to use large amounts +** of stack space (perhaps causing a stack overflow). +*/ +#ifndef SQLITE_FTS3_MAX_EXPR_DEPTH +# define SQLITE_FTS3_MAX_EXPR_DEPTH 12 +#endif + + +/* +** This constant controls how often segments are merged. Once there are +** FTS3_MERGE_COUNT segments of level N, they are merged into a single +** segment of level N+1. +*/ +#define FTS3_MERGE_COUNT 16 + +/* +** This is the maximum amount of data (in bytes) to store in the +** Fts3Table.pendingTerms hash table. Normally, the hash table is +** populated as documents are inserted/updated/deleted in a transaction +** and used to create a new segment when the transaction is committed. +** However if this limit is reached midway through a transaction, a new +** segment is created and the hash table cleared immediately. +*/ +#define FTS3_MAX_PENDING_DATA (1*1024*1024) + +/* +** Macro to return the number of elements in an array. SQLite has a +** similar macro called ArraySize(). Use a different name to avoid +** a collision when building an amalgamation with built-in FTS3. +*/ +#define SizeofArray(X) ((int)(sizeof(X)/sizeof(X[0]))) + + +#ifndef MIN +# define MIN(x,y) ((x)<(y)?(x):(y)) +#endif +#ifndef MAX +# define MAX(x,y) ((x)>(y)?(x):(y)) +#endif + +/* +** Maximum length of a varint encoded integer. The varint format is different +** from that used by SQLite, so the maximum length is 10, not 9. +*/ +#define FTS3_VARINT_MAX 10 + +#define FTS3_BUFFER_PADDING 8 + +/* +** FTS4 virtual tables may maintain multiple indexes - one index of all terms +** in the document set and zero or more prefix indexes. All indexes are stored +** as one or more b+-trees in the %_segments and %_segdir tables. +** +** It is possible to determine which index a b+-tree belongs to based on the +** value stored in the "%_segdir.level" column. Given this value L, the index +** that the b+-tree belongs to is (L<<10). In other words, all b+-trees with +** level values between 0 and 1023 (inclusive) belong to index 0, all levels +** between 1024 and 2047 to index 1, and so on. +** +** It is considered impossible for an index to use more than 1024 levels. In +** theory though this may happen, but only after at least +** (FTS3_MERGE_COUNT^1024) separate flushes of the pending-terms tables. +*/ +#define FTS3_SEGDIR_MAXLEVEL 1024 +#define FTS3_SEGDIR_MAXLEVEL_STR "1024" + +/* +** The testcase() macro is only used by the amalgamation. If undefined, +** make it a no-op. +*/ +#ifndef testcase +# define testcase(X) +#endif + +/* +** Terminator values for position-lists and column-lists. +*/ +#define POS_COLUMN (1) /* Column-list terminator */ +#define POS_END (0) /* Position-list terminator */ + +/* +** The assert_fts3_nc() macro is similar to the assert() macro, except that it +** is used for assert() conditions that are true only if it can be +** guranteed that the database is not corrupt. +*/ +#ifdef SQLITE_DEBUG +SQLITE_API extern int sqlite3_fts3_may_be_corrupt; +# define assert_fts3_nc(x) assert(sqlite3_fts3_may_be_corrupt || (x)) +#else +# define assert_fts3_nc(x) assert(x) +#endif + +/* +** This section provides definitions to allow the +** FTS3 extension to be compiled outside of the +** amalgamation. +*/ +#ifndef SQLITE_AMALGAMATION +/* +** Macros indicating that conditional expressions are always true or +** false. +*/ +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif + +/* +** Internal types used by SQLite. +*/ +typedef unsigned char u8; /* 1-byte (or larger) unsigned integer */ +typedef short int i16; /* 2-byte (or larger) signed integer */ +typedef unsigned int u32; /* 4-byte unsigned integer */ +typedef sqlite3_uint64 u64; /* 8-byte unsigned integer */ +typedef sqlite3_int64 i64; /* 8-byte signed integer */ + +/* +** Macro used to suppress compiler warnings for unused parameters. +*/ +#define UNUSED_PARAMETER(x) (void)(x) + +/* +** Activate assert() only if SQLITE_TEST is enabled. +*/ +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif + +/* +** The TESTONLY macro is used to enclose variable declarations or +** other bits of code that are needed to support the arguments +** within testcase() and assert() macros. +*/ +#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) +# define TESTONLY(X) X +#else +# define TESTONLY(X) +#endif + +#define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) +#define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) + +#define deliberate_fall_through + +#endif /* SQLITE_AMALGAMATION */ + +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3Fts3Corrupt(void); +# define FTS_CORRUPT_VTAB sqlite3Fts3Corrupt() +#else +# define FTS_CORRUPT_VTAB SQLITE_CORRUPT_VTAB +#endif + +typedef struct Fts3Table Fts3Table; +typedef struct Fts3Cursor Fts3Cursor; +typedef struct Fts3Expr Fts3Expr; +typedef struct Fts3Phrase Fts3Phrase; +typedef struct Fts3PhraseToken Fts3PhraseToken; + +typedef struct Fts3Doclist Fts3Doclist; +typedef struct Fts3SegFilter Fts3SegFilter; +typedef struct Fts3DeferredToken Fts3DeferredToken; +typedef struct Fts3SegReader Fts3SegReader; +typedef struct Fts3MultiSegReader Fts3MultiSegReader; + +typedef struct MatchinfoBuffer MatchinfoBuffer; + +/* +** A connection to a fulltext index is an instance of the following +** structure. The xCreate and xConnect methods create an instance +** of this structure and xDestroy and xDisconnect free that instance. +** All other methods receive a pointer to the structure as one of their +** arguments. +*/ +struct Fts3Table { + sqlite3_vtab base; /* Base class used by SQLite core */ + sqlite3 *db; /* The database connection */ + const char *zDb; /* logical database name */ + const char *zName; /* virtual table name */ + int nColumn; /* number of named columns in virtual table */ + char **azColumn; /* column names. malloced */ + u8 *abNotindexed; /* True for 'notindexed' columns */ + sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */ + char *zContentTbl; /* content=xxx option, or NULL */ + char *zLanguageid; /* languageid=xxx option, or NULL */ + int nAutoincrmerge; /* Value configured by 'automerge' */ + u32 nLeafAdd; /* Number of leaf blocks added this trans */ + int bLock; /* Used to prevent recursive content= tbls */ + + /* Precompiled statements used by the implementation. Each of these + ** statements is run and reset within a single virtual table API call. + */ + sqlite3_stmt *aStmt[40]; + sqlite3_stmt *pSeekStmt; /* Cache for fts3CursorSeekStmt() */ + + char *zReadExprlist; + char *zWriteExprlist; + + int nNodeSize; /* Soft limit for node size */ + u8 bFts4; /* True for FTS4, false for FTS3 */ + u8 bHasStat; /* True if %_stat table exists (2==unknown) */ + u8 bHasDocsize; /* True if %_docsize table exists */ + u8 bDescIdx; /* True if doclists are in reverse order */ + u8 bIgnoreSavepoint; /* True to ignore xSavepoint invocations */ + int nPgsz; /* Page size for host database */ + char *zSegmentsTbl; /* Name of %_segments table */ + sqlite3_blob *pSegments; /* Blob handle open on %_segments table */ + + /* + ** The following array of hash tables is used to buffer pending index + ** updates during transactions. All pending updates buffered at any one + ** time must share a common language-id (see the FTS4 langid= feature). + ** The current language id is stored in variable iPrevLangid. + ** + ** A single FTS4 table may have multiple full-text indexes. For each index + ** there is an entry in the aIndex[] array. Index 0 is an index of all the + ** terms that appear in the document set. Each subsequent index in aIndex[] + ** is an index of prefixes of a specific length. + ** + ** Variable nPendingData contains an estimate the memory consumed by the + ** pending data structures, including hash table overhead, but not including + ** malloc overhead. When nPendingData exceeds nMaxPendingData, all hash + ** tables are flushed to disk. Variable iPrevDocid is the docid of the most + ** recently inserted record. + */ + int nIndex; /* Size of aIndex[] */ + struct Fts3Index { + int nPrefix; /* Prefix length (0 for main terms index) */ + Fts3Hash hPending; /* Pending terms table for this index */ + } *aIndex; + int nMaxPendingData; /* Max pending data before flush to disk */ + int nPendingData; /* Current bytes of pending data */ + sqlite_int64 iPrevDocid; /* Docid of most recently inserted document */ + int iPrevLangid; /* Langid of recently inserted document */ + int bPrevDelete; /* True if last operation was a delete */ + +#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) + /* State variables used for validating that the transaction control + ** methods of the virtual table are called at appropriate times. These + ** values do not contribute to FTS functionality; they are used for + ** verifying the operation of the SQLite core. + */ + int inTransaction; /* True after xBegin but before xCommit/xRollback */ + int mxSavepoint; /* Largest valid xSavepoint integer */ +#endif + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + /* True to disable the incremental doclist optimization. This is controled + ** by special insert command 'test-no-incr-doclist'. */ + int bNoIncrDoclist; + + /* Number of segments in a level */ + int nMergeCount; +#endif +}; + +/* Macro to find the number of segments to merge */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) +# define MergeCount(P) ((P)->nMergeCount) +#else +# define MergeCount(P) FTS3_MERGE_COUNT +#endif + +/* +** When the core wants to read from the virtual table, it creates a +** virtual table cursor (an instance of the following structure) using +** the xOpen method. Cursors are destroyed using the xClose method. +*/ +struct Fts3Cursor { + sqlite3_vtab_cursor base; /* Base class used by SQLite core */ + i16 eSearch; /* Search strategy (see below) */ + u8 isEof; /* True if at End Of Results */ + u8 isRequireSeek; /* True if must seek pStmt to %_content row */ + u8 bSeekStmt; /* True if pStmt is a seek */ + sqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */ + Fts3Expr *pExpr; /* Parsed MATCH query string */ + int iLangid; /* Language being queried for */ + int nPhrase; /* Number of matchable phrases in query */ + Fts3DeferredToken *pDeferred; /* Deferred search tokens, if any */ + sqlite3_int64 iPrevId; /* Previous id read from aDoclist */ + char *pNextId; /* Pointer into the body of aDoclist */ + char *aDoclist; /* List of docids for full-text queries */ + int nDoclist; /* Size of buffer at aDoclist */ + u8 bDesc; /* True to sort in descending order */ + int eEvalmode; /* An FTS3_EVAL_XX constant */ + int nRowAvg; /* Average size of database rows, in pages */ + sqlite3_int64 nDoc; /* Documents in table */ + i64 iMinDocid; /* Minimum docid to return */ + i64 iMaxDocid; /* Maximum docid to return */ + int isMatchinfoNeeded; /* True when aMatchinfo[] needs filling in */ + MatchinfoBuffer *pMIBuffer; /* Buffer for matchinfo data */ +}; + +#define FTS3_EVAL_FILTER 0 +#define FTS3_EVAL_NEXT 1 +#define FTS3_EVAL_MATCHINFO 2 + +/* +** The Fts3Cursor.eSearch member is always set to one of the following. +** Actualy, Fts3Cursor.eSearch can be greater than or equal to +** FTS3_FULLTEXT_SEARCH. If so, then Fts3Cursor.eSearch - 2 is the index +** of the column to be searched. For example, in +** +** CREATE VIRTUAL TABLE ex1 USING fts3(a,b,c,d); +** SELECT docid FROM ex1 WHERE b MATCH 'one two three'; +** +** Because the LHS of the MATCH operator is 2nd column "b", +** Fts3Cursor.eSearch will be set to FTS3_FULLTEXT_SEARCH+1. (+0 for a, +** +1 for b, +2 for c, +3 for d.) If the LHS of MATCH were "ex1" +** indicating that all columns should be searched, +** then eSearch would be set to FTS3_FULLTEXT_SEARCH+4. +*/ +#define FTS3_FULLSCAN_SEARCH 0 /* Linear scan of %_content table */ +#define FTS3_DOCID_SEARCH 1 /* Lookup by rowid on %_content table */ +#define FTS3_FULLTEXT_SEARCH 2 /* Full-text index search */ + +/* +** The lower 16-bits of the sqlite3_index_info.idxNum value set by +** the xBestIndex() method contains the Fts3Cursor.eSearch value described +** above. The upper 16-bits contain a combination of the following +** bits, used to describe extra constraints on full-text searches. +*/ +#define FTS3_HAVE_LANGID 0x00010000 /* languageid=? */ +#define FTS3_HAVE_DOCID_GE 0x00020000 /* docid>=? */ +#define FTS3_HAVE_DOCID_LE 0x00040000 /* docid<=? */ + +struct Fts3Doclist { + char *aAll; /* Array containing doclist (or NULL) */ + int nAll; /* Size of a[] in bytes */ + char *pNextDocid; /* Pointer to next docid */ + + sqlite3_int64 iDocid; /* Current docid (if pList!=0) */ + int bFreeList; /* True if pList should be sqlite3_free()d */ + char *pList; /* Pointer to position list following iDocid */ + int nList; /* Length of position list */ +}; + +/* +** A "phrase" is a sequence of one or more tokens that must match in +** sequence. A single token is the base case and the most common case. +** For a sequence of tokens contained in double-quotes (i.e. "one two three") +** nToken will be the number of tokens in the string. +*/ +struct Fts3PhraseToken { + char *z; /* Text of the token */ + int n; /* Number of bytes in buffer z */ + int isPrefix; /* True if token ends with a "*" character */ + int bFirst; /* True if token must appear at position 0 */ + + /* Variables above this point are populated when the expression is + ** parsed (by code in fts3_expr.c). Below this point the variables are + ** used when evaluating the expression. */ + Fts3DeferredToken *pDeferred; /* Deferred token object for this token */ + Fts3MultiSegReader *pSegcsr; /* Segment-reader for this token */ +}; + +struct Fts3Phrase { + /* Cache of doclist for this phrase. */ + Fts3Doclist doclist; + int bIncr; /* True if doclist is loaded incrementally */ + int iDoclistToken; + + /* Used by sqlite3Fts3EvalPhrasePoslist() if this is a descendent of an + ** OR condition. */ + char *pOrPoslist; + i64 iOrDocid; + + /* Variables below this point are populated by fts3_expr.c when parsing + ** a MATCH expression. Everything above is part of the evaluation phase. + */ + int nToken; /* Number of tokens in the phrase */ + int iColumn; /* Index of column this phrase must match */ + Fts3PhraseToken aToken[1]; /* One entry for each token in the phrase */ +}; + +/* +** A tree of these objects forms the RHS of a MATCH operator. +** +** If Fts3Expr.eType is FTSQUERY_PHRASE and isLoaded is true, then aDoclist +** points to a malloced buffer, size nDoclist bytes, containing the results +** of this phrase query in FTS3 doclist format. As usual, the initial +** "Length" field found in doclists stored on disk is omitted from this +** buffer. +** +** Variable aMI is used only for FTSQUERY_NEAR nodes to store the global +** matchinfo data. If it is not NULL, it points to an array of size nCol*3, +** where nCol is the number of columns in the queried FTS table. The array +** is populated as follows: +** +** aMI[iCol*3 + 0] = Undefined +** aMI[iCol*3 + 1] = Number of occurrences +** aMI[iCol*3 + 2] = Number of rows containing at least one instance +** +** The aMI array is allocated using sqlite3_malloc(). It should be freed +** when the expression node is. +*/ +struct Fts3Expr { + int eType; /* One of the FTSQUERY_XXX values defined below */ + int nNear; /* Valid if eType==FTSQUERY_NEAR */ + Fts3Expr *pParent; /* pParent->pLeft==this or pParent->pRight==this */ + Fts3Expr *pLeft; /* Left operand */ + Fts3Expr *pRight; /* Right operand */ + Fts3Phrase *pPhrase; /* Valid if eType==FTSQUERY_PHRASE */ + + /* The following are used by the fts3_eval.c module. */ + sqlite3_int64 iDocid; /* Current docid */ + u8 bEof; /* True this expression is at EOF already */ + u8 bStart; /* True if iDocid is valid */ + u8 bDeferred; /* True if this expression is entirely deferred */ + + /* The following are used by the fts3_snippet.c module. */ + int iPhrase; /* Index of this phrase in matchinfo() results */ + u32 *aMI; /* See above */ +}; + +/* +** Candidate values for Fts3Query.eType. Note that the order of the first +** four values is in order of precedence when parsing expressions. For +** example, the following: +** +** "a OR b AND c NOT d NEAR e" +** +** is equivalent to: +** +** "a OR (b AND (c NOT (d NEAR e)))" +*/ +#define FTSQUERY_NEAR 1 +#define FTSQUERY_NOT 2 +#define FTSQUERY_AND 3 +#define FTSQUERY_OR 4 +#define FTSQUERY_PHRASE 5 + + +/* fts3_write.c */ +SQLITE_PRIVATE int sqlite3Fts3UpdateMethod(sqlite3_vtab*,int,sqlite3_value**,sqlite3_int64*); +SQLITE_PRIVATE int sqlite3Fts3PendingTermsFlush(Fts3Table *); +SQLITE_PRIVATE void sqlite3Fts3PendingTermsClear(Fts3Table *); +SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *); +SQLITE_PRIVATE int sqlite3Fts3SegReaderNew(int, int, sqlite3_int64, + sqlite3_int64, sqlite3_int64, const char *, int, Fts3SegReader**); +SQLITE_PRIVATE int sqlite3Fts3SegReaderPending( + Fts3Table*,int,const char*,int,int,Fts3SegReader**); +SQLITE_PRIVATE void sqlite3Fts3SegReaderFree(Fts3SegReader *); +SQLITE_PRIVATE int sqlite3Fts3AllSegdirs(Fts3Table*, int, int, int, sqlite3_stmt **); +SQLITE_PRIVATE int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char **, int*, int*); + +SQLITE_PRIVATE int sqlite3Fts3SelectDoctotal(Fts3Table *, sqlite3_stmt **); +SQLITE_PRIVATE int sqlite3Fts3SelectDocsize(Fts3Table *, sqlite3_int64, sqlite3_stmt **); + +#ifndef SQLITE_DISABLE_FTS4_DEFERRED +SQLITE_PRIVATE void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *); +SQLITE_PRIVATE int sqlite3Fts3DeferToken(Fts3Cursor *, Fts3PhraseToken *, int); +SQLITE_PRIVATE int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *); +SQLITE_PRIVATE void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *); +SQLITE_PRIVATE int sqlite3Fts3DeferredTokenList(Fts3DeferredToken *, char **, int *); +#else +# define sqlite3Fts3FreeDeferredTokens(x) +# define sqlite3Fts3DeferToken(x,y,z) SQLITE_OK +# define sqlite3Fts3CacheDeferredDoclists(x) SQLITE_OK +# define sqlite3Fts3FreeDeferredDoclists(x) +# define sqlite3Fts3DeferredTokenList(x,y,z) SQLITE_OK +#endif + +SQLITE_PRIVATE void sqlite3Fts3SegmentsClose(Fts3Table *); +SQLITE_PRIVATE int sqlite3Fts3MaxLevel(Fts3Table *, int *); + +/* Special values interpreted by sqlite3SegReaderCursor() */ +#define FTS3_SEGCURSOR_PENDING -1 +#define FTS3_SEGCURSOR_ALL -2 + +SQLITE_PRIVATE int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3MultiSegReader*, Fts3SegFilter*); +SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3MultiSegReader *); +SQLITE_PRIVATE void sqlite3Fts3SegReaderFinish(Fts3MultiSegReader *); + +SQLITE_PRIVATE int sqlite3Fts3SegReaderCursor(Fts3Table *, + int, int, int, const char *, int, int, int, Fts3MultiSegReader *); + +/* Flags allowed as part of the 4th argument to SegmentReaderIterate() */ +#define FTS3_SEGMENT_REQUIRE_POS 0x00000001 +#define FTS3_SEGMENT_IGNORE_EMPTY 0x00000002 +#define FTS3_SEGMENT_COLUMN_FILTER 0x00000004 +#define FTS3_SEGMENT_PREFIX 0x00000008 +#define FTS3_SEGMENT_SCAN 0x00000010 +#define FTS3_SEGMENT_FIRST 0x00000020 + +/* Type passed as 4th argument to SegmentReaderIterate() */ +struct Fts3SegFilter { + const char *zTerm; + int nTerm; + int iCol; + int flags; +}; + +struct Fts3MultiSegReader { + /* Used internally by sqlite3Fts3SegReaderXXX() calls */ + Fts3SegReader **apSegment; /* Array of Fts3SegReader objects */ + int nSegment; /* Size of apSegment array */ + int nAdvance; /* How many seg-readers to advance */ + Fts3SegFilter *pFilter; /* Pointer to filter object */ + char *aBuffer; /* Buffer to merge doclists in */ + i64 nBuffer; /* Allocated size of aBuffer[] in bytes */ + + int iColFilter; /* If >=0, filter for this column */ + int bRestart; + + /* Used by fts3.c only. */ + int nCost; /* Cost of running iterator */ + int bLookup; /* True if a lookup of a single entry. */ + + /* Output values. Valid only after Fts3SegReaderStep() returns SQLITE_ROW. */ + char *zTerm; /* Pointer to term buffer */ + int nTerm; /* Size of zTerm in bytes */ + char *aDoclist; /* Pointer to doclist buffer */ + int nDoclist; /* Size of aDoclist[] in bytes */ +}; + +SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table*,int,int); + +#define fts3GetVarint32(p, piVal) ( \ + (*(u8*)(p)&0x80) ? sqlite3Fts3GetVarint32(p, piVal) : (*piVal=*(u8*)(p), 1) \ +) + +/* fts3.c */ +SQLITE_PRIVATE void sqlite3Fts3ErrMsg(char**,const char*,...); +SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *, sqlite3_int64); +SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *, sqlite_int64 *); +SQLITE_PRIVATE int sqlite3Fts3GetVarintU(const char *, sqlite_uint64 *); +SQLITE_PRIVATE int sqlite3Fts3GetVarintBounded(const char*,const char*,sqlite3_int64*); +SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *, int *); +SQLITE_PRIVATE int sqlite3Fts3VarintLen(sqlite3_uint64); +SQLITE_PRIVATE void sqlite3Fts3Dequote(char *); +SQLITE_PRIVATE void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*); +SQLITE_PRIVATE int sqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *); +SQLITE_PRIVATE int sqlite3Fts3FirstFilter(sqlite3_int64, char *, int, char *); +SQLITE_PRIVATE void sqlite3Fts3CreateStatTable(int*, Fts3Table*); +SQLITE_PRIVATE int sqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc); +SQLITE_PRIVATE int sqlite3Fts3ReadInt(const char *z, int *pnOut); + +/* fts3_tokenizer.c */ +SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *, int *); +SQLITE_PRIVATE int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *); +SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *, + sqlite3_tokenizer **, char ** +); +SQLITE_PRIVATE int sqlite3Fts3IsIdChar(char); + +/* fts3_snippet.c */ +SQLITE_PRIVATE void sqlite3Fts3Offsets(sqlite3_context*, Fts3Cursor*); +SQLITE_PRIVATE void sqlite3Fts3Snippet(sqlite3_context *, Fts3Cursor *, const char *, + const char *, const char *, int, int +); +SQLITE_PRIVATE void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *, const char *); +SQLITE_PRIVATE void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p); + +/* fts3_expr.c */ +SQLITE_PRIVATE int sqlite3Fts3ExprParse(sqlite3_tokenizer *, int, + char **, int, int, int, const char *, int, Fts3Expr **, char ** +); +SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *); +#ifdef SQLITE_TEST +SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3 *db, Fts3Hash*); +SQLITE_PRIVATE int sqlite3Fts3InitTerm(sqlite3 *db); +#endif +SQLITE_PRIVATE void *sqlite3Fts3MallocZero(i64 nByte); + +SQLITE_PRIVATE int sqlite3Fts3OpenTokenizer(sqlite3_tokenizer *, int, const char *, int, + sqlite3_tokenizer_cursor ** +); + +/* fts3_aux.c */ +SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db); + +SQLITE_PRIVATE void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *); + +SQLITE_PRIVATE int sqlite3Fts3MsrIncrStart( + Fts3Table*, Fts3MultiSegReader*, int, const char*, int); +SQLITE_PRIVATE int sqlite3Fts3MsrIncrNext( + Fts3Table *, Fts3MultiSegReader *, sqlite3_int64 *, char **, int *); +SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist(Fts3Cursor *, Fts3Expr *, int iCol, char **); +SQLITE_PRIVATE int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *); +SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr); + +/* fts3_tokenize_vtab.c */ +SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3*, Fts3Hash *, void(*xDestroy)(void*)); + +/* fts3_unicode2.c (functions generated by parsing unicode text files) */ +#ifndef SQLITE_DISABLE_FTS3_UNICODE +SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int, int); +SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int); +SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int); +#endif + +SQLITE_PRIVATE int sqlite3Fts3ExprIterate(Fts3Expr*, int (*x)(Fts3Expr*,int,void*), void*); + +#endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */ +#endif /* _FTSINT_H */ + +/************** End of fts3Int.h *********************************************/ +/************** Continuing where we left off in fts3.c ***********************/ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +#if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE) +# define SQLITE_CORE 1 +#endif + +/* #include <assert.h> */ +/* #include <stdlib.h> */ +/* #include <stddef.h> */ +/* #include <stdio.h> */ +/* #include <string.h> */ +/* #include <stdarg.h> */ + +/* #include "fts3.h" */ +#ifndef SQLITE_CORE +/* # include "sqlite3ext.h" */ + SQLITE_EXTENSION_INIT1 +#endif + +typedef struct Fts3HashWrapper Fts3HashWrapper; +struct Fts3HashWrapper { + Fts3Hash hash; /* Hash table */ + int nRef; /* Number of pointers to this object */ +}; + +static int fts3EvalNext(Fts3Cursor *pCsr); +static int fts3EvalStart(Fts3Cursor *pCsr); +static int fts3TermSegReaderCursor( + Fts3Cursor *, const char *, int, int, Fts3MultiSegReader **); + +/* +** This variable is set to false when running tests for which the on disk +** structures should not be corrupt. Otherwise, true. If it is false, extra +** assert() conditions in the fts3 code are activated - conditions that are +** only true if it is guaranteed that the fts3 database is not corrupt. +*/ +#ifdef SQLITE_DEBUG +SQLITE_API int sqlite3_fts3_may_be_corrupt = 1; +#endif + +/* +** Write a 64-bit variable-length integer to memory starting at p[0]. +** The length of data written will be between 1 and FTS3_VARINT_MAX bytes. +** The number of bytes written is returned. +*/ +SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *p, sqlite_int64 v){ + unsigned char *q = (unsigned char *) p; + sqlite_uint64 vu = v; + do{ + *q++ = (unsigned char) ((vu & 0x7f) | 0x80); + vu >>= 7; + }while( vu!=0 ); + q[-1] &= 0x7f; /* turn off high bit in final byte */ + assert( q - (unsigned char *)p <= FTS3_VARINT_MAX ); + return (int) (q - (unsigned char *)p); +} + +#define GETVARINT_STEP(v, ptr, shift, mask1, mask2, var, ret) \ + v = (v & mask1) | ( (*(const unsigned char*)(ptr++)) << shift ); \ + if( (v & mask2)==0 ){ var = v; return ret; } +#define GETVARINT_INIT(v, ptr, shift, mask1, mask2, var, ret) \ + v = (*ptr++); \ + if( (v & mask2)==0 ){ var = v; return ret; } + +SQLITE_PRIVATE int sqlite3Fts3GetVarintU(const char *pBuf, sqlite_uint64 *v){ + const unsigned char *p = (const unsigned char*)pBuf; + const unsigned char *pStart = p; + u32 a; + u64 b; + int shift; + + GETVARINT_INIT(a, p, 0, 0x00, 0x80, *v, 1); + GETVARINT_STEP(a, p, 7, 0x7F, 0x4000, *v, 2); + GETVARINT_STEP(a, p, 14, 0x3FFF, 0x200000, *v, 3); + GETVARINT_STEP(a, p, 21, 0x1FFFFF, 0x10000000, *v, 4); + b = (a & 0x0FFFFFFF ); + + for(shift=28; shift<=63; shift+=7){ + u64 c = *p++; + b += (c&0x7F) << shift; + if( (c & 0x80)==0 ) break; + } + *v = b; + return (int)(p - pStart); +} + +/* +** Read a 64-bit variable-length integer from memory starting at p[0]. +** Return the number of bytes read, or 0 on error. +** The value is stored in *v. +*/ +SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *pBuf, sqlite_int64 *v){ + return sqlite3Fts3GetVarintU(pBuf, (sqlite3_uint64*)v); +} + +/* +** Read a 64-bit variable-length integer from memory starting at p[0] and +** not extending past pEnd[-1]. +** Return the number of bytes read, or 0 on error. +** The value is stored in *v. +*/ +SQLITE_PRIVATE int sqlite3Fts3GetVarintBounded( + const char *pBuf, + const char *pEnd, + sqlite_int64 *v +){ + const unsigned char *p = (const unsigned char*)pBuf; + const unsigned char *pStart = p; + const unsigned char *pX = (const unsigned char*)pEnd; + u64 b = 0; + int shift; + for(shift=0; shift<=63; shift+=7){ + u64 c = p<pX ? *p : 0; + p++; + b += (c&0x7F) << shift; + if( (c & 0x80)==0 ) break; + } + *v = b; + return (int)(p - pStart); +} + +/* +** Similar to sqlite3Fts3GetVarint(), except that the output is truncated to +** a non-negative 32-bit integer before it is returned. +*/ +SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *p, int *pi){ + const unsigned char *ptr = (const unsigned char*)p; + u32 a; + +#ifndef fts3GetVarint32 + GETVARINT_INIT(a, ptr, 0, 0x00, 0x80, *pi, 1); +#else + a = (*ptr++); + assert( a & 0x80 ); +#endif + + GETVARINT_STEP(a, ptr, 7, 0x7F, 0x4000, *pi, 2); + GETVARINT_STEP(a, ptr, 14, 0x3FFF, 0x200000, *pi, 3); + GETVARINT_STEP(a, ptr, 21, 0x1FFFFF, 0x10000000, *pi, 4); + a = (a & 0x0FFFFFFF ); + *pi = (int)(a | ((u32)(*ptr & 0x07) << 28)); + assert( 0==(a & 0x80000000) ); + assert( *pi>=0 ); + return 5; +} + +/* +** Return the number of bytes required to encode v as a varint +*/ +SQLITE_PRIVATE int sqlite3Fts3VarintLen(sqlite3_uint64 v){ + int i = 0; + do{ + i++; + v >>= 7; + }while( v!=0 ); + return i; +} + +/* +** Convert an SQL-style quoted string into a normal string by removing +** the quote characters. The conversion is done in-place. If the +** input does not begin with a quote character, then this routine +** is a no-op. +** +** Examples: +** +** "abc" becomes abc +** 'xyz' becomes xyz +** [pqr] becomes pqr +** `mno` becomes mno +** +*/ +SQLITE_PRIVATE void sqlite3Fts3Dequote(char *z){ + char quote; /* Quote character (if any ) */ + + quote = z[0]; + if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){ + int iIn = 1; /* Index of next byte to read from input */ + int iOut = 0; /* Index of next byte to write to output */ + + /* If the first byte was a '[', then the close-quote character is a ']' */ + if( quote=='[' ) quote = ']'; + + while( z[iIn] ){ + if( z[iIn]==quote ){ + if( z[iIn+1]!=quote ) break; + z[iOut++] = quote; + iIn += 2; + }else{ + z[iOut++] = z[iIn++]; + } + } + z[iOut] = '\0'; + } +} + +/* +** Read a single varint from the doclist at *pp and advance *pp to point +** to the first byte past the end of the varint. Add the value of the varint +** to *pVal. +*/ +static void fts3GetDeltaVarint(char **pp, sqlite3_int64 *pVal){ + sqlite3_int64 iVal; + *pp += sqlite3Fts3GetVarint(*pp, &iVal); + *pVal += iVal; +} + +/* +** When this function is called, *pp points to the first byte following a +** varint that is part of a doclist (or position-list, or any other list +** of varints). This function moves *pp to point to the start of that varint, +** and sets *pVal by the varint value. +** +** Argument pStart points to the first byte of the doclist that the +** varint is part of. +*/ +static void fts3GetReverseVarint( + char **pp, + char *pStart, + sqlite3_int64 *pVal +){ + sqlite3_int64 iVal; + char *p; + + /* Pointer p now points at the first byte past the varint we are + ** interested in. So, unless the doclist is corrupt, the 0x80 bit is + ** clear on character p[-1]. */ + for(p = (*pp)-2; p>=pStart && *p&0x80; p--); + p++; + *pp = p; + + sqlite3Fts3GetVarint(p, &iVal); + *pVal = iVal; +} + +/* +** The xDisconnect() virtual table method. +*/ +static int fts3DisconnectMethod(sqlite3_vtab *pVtab){ + Fts3Table *p = (Fts3Table *)pVtab; + int i; + + assert( p->nPendingData==0 ); + assert( p->pSegments==0 ); + + /* Free any prepared statements held */ + sqlite3_finalize(p->pSeekStmt); + for(i=0; i<SizeofArray(p->aStmt); i++){ + sqlite3_finalize(p->aStmt[i]); + } + sqlite3_free(p->zSegmentsTbl); + sqlite3_free(p->zReadExprlist); + sqlite3_free(p->zWriteExprlist); + sqlite3_free(p->zContentTbl); + sqlite3_free(p->zLanguageid); + + /* Invoke the tokenizer destructor to free the tokenizer. */ + p->pTokenizer->pModule->xDestroy(p->pTokenizer); + + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Write an error message into *pzErr +*/ +SQLITE_PRIVATE void sqlite3Fts3ErrMsg(char **pzErr, const char *zFormat, ...){ + va_list ap; + sqlite3_free(*pzErr); + va_start(ap, zFormat); + *pzErr = sqlite3_vmprintf(zFormat, ap); + va_end(ap); +} + +/* +** Construct one or more SQL statements from the format string given +** and then evaluate those statements. The success code is written +** into *pRc. +** +** If *pRc is initially non-zero then this routine is a no-op. +*/ +static void fts3DbExec( + int *pRc, /* Success code */ + sqlite3 *db, /* Database in which to run SQL */ + const char *zFormat, /* Format string for SQL */ + ... /* Arguments to the format string */ +){ + va_list ap; + char *zSql; + if( *pRc ) return; + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + if( zSql==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + *pRc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + } +} + +/* +** The xDestroy() virtual table method. +*/ +static int fts3DestroyMethod(sqlite3_vtab *pVtab){ + Fts3Table *p = (Fts3Table *)pVtab; + int rc = SQLITE_OK; /* Return code */ + const char *zDb = p->zDb; /* Name of database (e.g. "main", "temp") */ + sqlite3 *db = p->db; /* Database handle */ + + /* Drop the shadow tables */ + fts3DbExec(&rc, db, + "DROP TABLE IF EXISTS %Q.'%q_segments';" + "DROP TABLE IF EXISTS %Q.'%q_segdir';" + "DROP TABLE IF EXISTS %Q.'%q_docsize';" + "DROP TABLE IF EXISTS %Q.'%q_stat';" + "%s DROP TABLE IF EXISTS %Q.'%q_content';", + zDb, p->zName, + zDb, p->zName, + zDb, p->zName, + zDb, p->zName, + (p->zContentTbl ? "--" : ""), zDb,p->zName + ); + + /* If everything has worked, invoke fts3DisconnectMethod() to free the + ** memory associated with the Fts3Table structure and return SQLITE_OK. + ** Otherwise, return an SQLite error code. + */ + return (rc==SQLITE_OK ? fts3DisconnectMethod(pVtab) : rc); +} + + +/* +** Invoke sqlite3_declare_vtab() to declare the schema for the FTS3 table +** passed as the first argument. This is done as part of the xConnect() +** and xCreate() methods. +** +** If *pRc is non-zero when this function is called, it is a no-op. +** Otherwise, if an error occurs, an SQLite error code is stored in *pRc +** before returning. +*/ +static void fts3DeclareVtab(int *pRc, Fts3Table *p){ + if( *pRc==SQLITE_OK ){ + int i; /* Iterator variable */ + int rc; /* Return code */ + char *zSql; /* SQL statement passed to declare_vtab() */ + char *zCols; /* List of user defined columns */ + const char *zLanguageid; + + zLanguageid = (p->zLanguageid ? p->zLanguageid : "__langid"); + sqlite3_vtab_config(p->db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); + + /* Create a list of user columns for the virtual table */ + zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]); + for(i=1; zCols && i<p->nColumn; i++){ + zCols = sqlite3_mprintf("%z%Q, ", zCols, p->azColumn[i]); + } + + /* Create the whole "CREATE TABLE" statement to pass to SQLite */ + zSql = sqlite3_mprintf( + "CREATE TABLE x(%s %Q HIDDEN, docid HIDDEN, %Q HIDDEN)", + zCols, p->zName, zLanguageid + ); + if( !zCols || !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_declare_vtab(p->db, zSql); + } + + sqlite3_free(zSql); + sqlite3_free(zCols); + *pRc = rc; + } +} + +/* +** Create the %_stat table if it does not already exist. +*/ +SQLITE_PRIVATE void sqlite3Fts3CreateStatTable(int *pRc, Fts3Table *p){ + fts3DbExec(pRc, p->db, + "CREATE TABLE IF NOT EXISTS %Q.'%q_stat'" + "(id INTEGER PRIMARY KEY, value BLOB);", + p->zDb, p->zName + ); + if( (*pRc)==SQLITE_OK ) p->bHasStat = 1; +} + +/* +** Create the backing store tables (%_content, %_segments and %_segdir) +** required by the FTS3 table passed as the only argument. This is done +** as part of the vtab xCreate() method. +** +** If the p->bHasDocsize boolean is true (indicating that this is an +** FTS4 table, not an FTS3 table) then also create the %_docsize and +** %_stat tables required by FTS4. +*/ +static int fts3CreateTables(Fts3Table *p){ + int rc = SQLITE_OK; /* Return code */ + int i; /* Iterator variable */ + sqlite3 *db = p->db; /* The database connection */ + + if( p->zContentTbl==0 ){ + const char *zLanguageid = p->zLanguageid; + char *zContentCols; /* Columns of %_content table */ + + /* Create a list of user columns for the content table */ + zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY"); + for(i=0; zContentCols && i<p->nColumn; i++){ + char *z = p->azColumn[i]; + zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z); + } + if( zLanguageid && zContentCols ){ + zContentCols = sqlite3_mprintf("%z, langid", zContentCols, zLanguageid); + } + if( zContentCols==0 ) rc = SQLITE_NOMEM; + + /* Create the content table */ + fts3DbExec(&rc, db, + "CREATE TABLE %Q.'%q_content'(%s)", + p->zDb, p->zName, zContentCols + ); + sqlite3_free(zContentCols); + } + + /* Create other tables */ + fts3DbExec(&rc, db, + "CREATE TABLE %Q.'%q_segments'(blockid INTEGER PRIMARY KEY, block BLOB);", + p->zDb, p->zName + ); + fts3DbExec(&rc, db, + "CREATE TABLE %Q.'%q_segdir'(" + "level INTEGER," + "idx INTEGER," + "start_block INTEGER," + "leaves_end_block INTEGER," + "end_block INTEGER," + "root BLOB," + "PRIMARY KEY(level, idx)" + ");", + p->zDb, p->zName + ); + if( p->bHasDocsize ){ + fts3DbExec(&rc, db, + "CREATE TABLE %Q.'%q_docsize'(docid INTEGER PRIMARY KEY, size BLOB);", + p->zDb, p->zName + ); + } + assert( p->bHasStat==p->bFts4 ); + if( p->bHasStat ){ + sqlite3Fts3CreateStatTable(&rc, p); + } + return rc; +} + +/* +** Store the current database page-size in bytes in p->nPgsz. +** +** If *pRc is non-zero when this function is called, it is a no-op. +** Otherwise, if an error occurs, an SQLite error code is stored in *pRc +** before returning. +*/ +static void fts3DatabasePageSize(int *pRc, Fts3Table *p){ + if( *pRc==SQLITE_OK ){ + int rc; /* Return code */ + char *zSql; /* SQL text "PRAGMA %Q.page_size" */ + sqlite3_stmt *pStmt; /* Compiled "PRAGMA %Q.page_size" statement */ + + zSql = sqlite3_mprintf("PRAGMA %Q.page_size", p->zDb); + if( !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_step(pStmt); + p->nPgsz = sqlite3_column_int(pStmt, 0); + rc = sqlite3_finalize(pStmt); + }else if( rc==SQLITE_AUTH ){ + p->nPgsz = 1024; + rc = SQLITE_OK; + } + } + assert( p->nPgsz>0 || rc!=SQLITE_OK ); + sqlite3_free(zSql); + *pRc = rc; + } +} + +/* +** "Special" FTS4 arguments are column specifications of the following form: +** +** <key> = <value> +** +** There may not be whitespace surrounding the "=" character. The <value> +** term may be quoted, but the <key> may not. +*/ +static int fts3IsSpecialColumn( + const char *z, + int *pnKey, + char **pzValue +){ + char *zValue; + const char *zCsr = z; + + while( *zCsr!='=' ){ + if( *zCsr=='\0' ) return 0; + zCsr++; + } + + *pnKey = (int)(zCsr-z); + zValue = sqlite3_mprintf("%s", &zCsr[1]); + if( zValue ){ + sqlite3Fts3Dequote(zValue); + } + *pzValue = zValue; + return 1; +} + +/* +** Append the output of a printf() style formatting to an existing string. +*/ +static void fts3Appendf( + int *pRc, /* IN/OUT: Error code */ + char **pz, /* IN/OUT: Pointer to string buffer */ + const char *zFormat, /* Printf format string to append */ + ... /* Arguments for printf format string */ +){ + if( *pRc==SQLITE_OK ){ + va_list ap; + char *z; + va_start(ap, zFormat); + z = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + if( z && *pz ){ + char *z2 = sqlite3_mprintf("%s%s", *pz, z); + sqlite3_free(z); + z = z2; + } + if( z==0 ) *pRc = SQLITE_NOMEM; + sqlite3_free(*pz); + *pz = z; + } +} + +/* +** Return a copy of input string zInput enclosed in double-quotes (") and +** with all double quote characters escaped. For example: +** +** fts3QuoteId("un \"zip\"") -> "un \"\"zip\"\"" +** +** The pointer returned points to memory obtained from sqlite3_malloc(). It +** is the callers responsibility to call sqlite3_free() to release this +** memory. +*/ +static char *fts3QuoteId(char const *zInput){ + sqlite3_int64 nRet; + char *zRet; + nRet = 2 + (int)strlen(zInput)*2 + 1; + zRet = sqlite3_malloc64(nRet); + if( zRet ){ + int i; + char *z = zRet; + *(z++) = '"'; + for(i=0; zInput[i]; i++){ + if( zInput[i]=='"' ) *(z++) = '"'; + *(z++) = zInput[i]; + } + *(z++) = '"'; + *(z++) = '\0'; + } + return zRet; +} + +/* +** Return a list of comma separated SQL expressions and a FROM clause that +** could be used in a SELECT statement such as the following: +** +** SELECT <list of expressions> FROM %_content AS x ... +** +** to return the docid, followed by each column of text data in order +** from left to write. If parameter zFunc is not NULL, then instead of +** being returned directly each column of text data is passed to an SQL +** function named zFunc first. For example, if zFunc is "unzip" and the +** table has the three user-defined columns "a", "b", and "c", the following +** string is returned: +** +** "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c') FROM %_content AS x" +** +** The pointer returned points to a buffer allocated by sqlite3_malloc(). It +** is the responsibility of the caller to eventually free it. +** +** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and +** a NULL pointer is returned). Otherwise, if an OOM error is encountered +** by this function, NULL is returned and *pRc is set to SQLITE_NOMEM. If +** no error occurs, *pRc is left unmodified. +*/ +static char *fts3ReadExprList(Fts3Table *p, const char *zFunc, int *pRc){ + char *zRet = 0; + char *zFree = 0; + char *zFunction; + int i; + + if( p->zContentTbl==0 ){ + if( !zFunc ){ + zFunction = ""; + }else{ + zFree = zFunction = fts3QuoteId(zFunc); + } + fts3Appendf(pRc, &zRet, "docid"); + for(i=0; i<p->nColumn; i++){ + fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]); + } + if( p->zLanguageid ){ + fts3Appendf(pRc, &zRet, ", x.%Q", "langid"); + } + sqlite3_free(zFree); + }else{ + fts3Appendf(pRc, &zRet, "rowid"); + for(i=0; i<p->nColumn; i++){ + fts3Appendf(pRc, &zRet, ", x.'%q'", p->azColumn[i]); + } + if( p->zLanguageid ){ + fts3Appendf(pRc, &zRet, ", x.%Q", p->zLanguageid); + } + } + fts3Appendf(pRc, &zRet, " FROM '%q'.'%q%s' AS x", + p->zDb, + (p->zContentTbl ? p->zContentTbl : p->zName), + (p->zContentTbl ? "" : "_content") + ); + return zRet; +} + +/* +** Return a list of N comma separated question marks, where N is the number +** of columns in the %_content table (one for the docid plus one for each +** user-defined text column). +** +** If argument zFunc is not NULL, then all but the first question mark +** is preceded by zFunc and an open bracket, and followed by a closed +** bracket. For example, if zFunc is "zip" and the FTS3 table has three +** user-defined text columns, the following string is returned: +** +** "?, zip(?), zip(?), zip(?)" +** +** The pointer returned points to a buffer allocated by sqlite3_malloc(). It +** is the responsibility of the caller to eventually free it. +** +** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and +** a NULL pointer is returned). Otherwise, if an OOM error is encountered +** by this function, NULL is returned and *pRc is set to SQLITE_NOMEM. If +** no error occurs, *pRc is left unmodified. +*/ +static char *fts3WriteExprList(Fts3Table *p, const char *zFunc, int *pRc){ + char *zRet = 0; + char *zFree = 0; + char *zFunction; + int i; + + if( !zFunc ){ + zFunction = ""; + }else{ + zFree = zFunction = fts3QuoteId(zFunc); + } + fts3Appendf(pRc, &zRet, "?"); + for(i=0; i<p->nColumn; i++){ + fts3Appendf(pRc, &zRet, ",%s(?)", zFunction); + } + if( p->zLanguageid ){ + fts3Appendf(pRc, &zRet, ", ?"); + } + sqlite3_free(zFree); + return zRet; +} + +/* +** Buffer z contains a positive integer value encoded as utf-8 text. +** Decode this value and store it in *pnOut, returning the number of bytes +** consumed. If an overflow error occurs return a negative value. +*/ +SQLITE_PRIVATE int sqlite3Fts3ReadInt(const char *z, int *pnOut){ + u64 iVal = 0; + int i; + for(i=0; z[i]>='0' && z[i]<='9'; i++){ + iVal = iVal*10 + (z[i] - '0'); + if( iVal>0x7FFFFFFF ) return -1; + } + *pnOut = (int)iVal; + return i; +} + +/* +** This function interprets the string at (*pp) as a non-negative integer +** value. It reads the integer and sets *pnOut to the value read, then +** sets *pp to point to the byte immediately following the last byte of +** the integer value. +** +** Only decimal digits ('0'..'9') may be part of an integer value. +** +** If *pp does not being with a decimal digit SQLITE_ERROR is returned and +** the output value undefined. Otherwise SQLITE_OK is returned. +** +** This function is used when parsing the "prefix=" FTS4 parameter. +*/ +static int fts3GobbleInt(const char **pp, int *pnOut){ + const int MAX_NPREFIX = 10000000; + int nInt = 0; /* Output value */ + int nByte; + nByte = sqlite3Fts3ReadInt(*pp, &nInt); + if( nInt>MAX_NPREFIX ){ + nInt = 0; + } + if( nByte==0 ){ + return SQLITE_ERROR; + } + *pnOut = nInt; + *pp += nByte; + return SQLITE_OK; +} + +/* +** This function is called to allocate an array of Fts3Index structures +** representing the indexes maintained by the current FTS table. FTS tables +** always maintain the main "terms" index, but may also maintain one or +** more "prefix" indexes, depending on the value of the "prefix=" parameter +** (if any) specified as part of the CREATE VIRTUAL TABLE statement. +** +** Argument zParam is passed the value of the "prefix=" option if one was +** specified, or NULL otherwise. +** +** If no error occurs, SQLITE_OK is returned and *apIndex set to point to +** the allocated array. *pnIndex is set to the number of elements in the +** array. If an error does occur, an SQLite error code is returned. +** +** Regardless of whether or not an error is returned, it is the responsibility +** of the caller to call sqlite3_free() on the output array to free it. +*/ +static int fts3PrefixParameter( + const char *zParam, /* ABC in prefix=ABC parameter to parse */ + int *pnIndex, /* OUT: size of *apIndex[] array */ + struct Fts3Index **apIndex /* OUT: Array of indexes for this table */ +){ + struct Fts3Index *aIndex; /* Allocated array */ + int nIndex = 1; /* Number of entries in array */ + + if( zParam && zParam[0] ){ + const char *p; + nIndex++; + for(p=zParam; *p; p++){ + if( *p==',' ) nIndex++; + } + } + + aIndex = sqlite3_malloc64(sizeof(struct Fts3Index) * nIndex); + *apIndex = aIndex; + if( !aIndex ){ + return SQLITE_NOMEM; + } + + memset(aIndex, 0, sizeof(struct Fts3Index) * nIndex); + if( zParam ){ + const char *p = zParam; + int i; + for(i=1; i<nIndex; i++){ + int nPrefix = 0; + if( fts3GobbleInt(&p, &nPrefix) ) return SQLITE_ERROR; + assert( nPrefix>=0 ); + if( nPrefix==0 ){ + nIndex--; + i--; + }else{ + aIndex[i].nPrefix = nPrefix; + } + p++; + } + } + + *pnIndex = nIndex; + return SQLITE_OK; +} + +/* +** This function is called when initializing an FTS4 table that uses the +** content=xxx option. It determines the number of and names of the columns +** of the new FTS4 table. +** +** The third argument passed to this function is the value passed to the +** config=xxx option (i.e. "xxx"). This function queries the database for +** a table of that name. If found, the output variables are populated +** as follows: +** +** *pnCol: Set to the number of columns table xxx has, +** +** *pnStr: Set to the total amount of space required to store a copy +** of each columns name, including the nul-terminator. +** +** *pazCol: Set to point to an array of *pnCol strings. Each string is +** the name of the corresponding column in table xxx. The array +** and its contents are allocated using a single allocation. It +** is the responsibility of the caller to free this allocation +** by eventually passing the *pazCol value to sqlite3_free(). +** +** If the table cannot be found, an error code is returned and the output +** variables are undefined. Or, if an OOM is encountered, SQLITE_NOMEM is +** returned (and the output variables are undefined). +*/ +static int fts3ContentColumns( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of db (i.e. "main", "temp" etc.) */ + const char *zTbl, /* Name of content table */ + const char ***pazCol, /* OUT: Malloc'd array of column names */ + int *pnCol, /* OUT: Size of array *pazCol */ + int *pnStr, /* OUT: Bytes of string content */ + char **pzErr /* OUT: error message */ +){ + int rc = SQLITE_OK; /* Return code */ + char *zSql; /* "SELECT *" statement on zTbl */ + sqlite3_stmt *pStmt = 0; /* Compiled version of zSql */ + + zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", zDb, zTbl); + if( !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + sqlite3Fts3ErrMsg(pzErr, "%s", sqlite3_errmsg(db)); + } + } + sqlite3_free(zSql); + + if( rc==SQLITE_OK ){ + const char **azCol; /* Output array */ + sqlite3_int64 nStr = 0; /* Size of all column names (incl. 0x00) */ + int nCol; /* Number of table columns */ + int i; /* Used to iterate through columns */ + + /* Loop through the returned columns. Set nStr to the number of bytes of + ** space required to store a copy of each column name, including the + ** nul-terminator byte. */ + nCol = sqlite3_column_count(pStmt); + for(i=0; i<nCol; i++){ + const char *zCol = sqlite3_column_name(pStmt, i); + nStr += strlen(zCol) + 1; + } + + /* Allocate and populate the array to return. */ + azCol = (const char **)sqlite3_malloc64(sizeof(char *) * nCol + nStr); + if( azCol==0 ){ + rc = SQLITE_NOMEM; + }else{ + char *p = (char *)&azCol[nCol]; + for(i=0; i<nCol; i++){ + const char *zCol = sqlite3_column_name(pStmt, i); + int n = (int)strlen(zCol)+1; + memcpy(p, zCol, n); + azCol[i] = p; + p += n; + } + } + sqlite3_finalize(pStmt); + + /* Set the output variables. */ + *pnCol = nCol; + *pnStr = nStr; + *pazCol = azCol; + } + + return rc; +} + +/* +** This function is the implementation of both the xConnect and xCreate +** methods of the FTS3 virtual table. +** +** The argv[] array contains the following: +** +** argv[0] -> module name ("fts3" or "fts4") +** argv[1] -> database name +** argv[2] -> table name +** argv[...] -> "column name" and other module argument fields. +*/ +static int fts3InitVtab( + int isCreate, /* True for xCreate, false for xConnect */ + sqlite3 *db, /* The SQLite database connection */ + void *pAux, /* Hash table containing tokenizers */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */ + char **pzErr /* Write any error message here */ +){ + Fts3Hash *pHash = &((Fts3HashWrapper*)pAux)->hash; + Fts3Table *p = 0; /* Pointer to allocated vtab */ + int rc = SQLITE_OK; /* Return code */ + int i; /* Iterator variable */ + sqlite3_int64 nByte; /* Size of allocation used for *p */ + int iCol; /* Column index */ + int nString = 0; /* Bytes required to hold all column names */ + int nCol = 0; /* Number of columns in the FTS table */ + char *zCsr; /* Space for holding column names */ + int nDb; /* Bytes required to hold database name */ + int nName; /* Bytes required to hold table name */ + int isFts4 = (argv[0][3]=='4'); /* True for FTS4, false for FTS3 */ + const char **aCol; /* Array of column names */ + sqlite3_tokenizer *pTokenizer = 0; /* Tokenizer for this table */ + + int nIndex = 0; /* Size of aIndex[] array */ + struct Fts3Index *aIndex = 0; /* Array of indexes for this table */ + + /* The results of parsing supported FTS4 key=value options: */ + int bNoDocsize = 0; /* True to omit %_docsize table */ + int bDescIdx = 0; /* True to store descending indexes */ + char *zPrefix = 0; /* Prefix parameter value (or NULL) */ + char *zCompress = 0; /* compress=? parameter (or NULL) */ + char *zUncompress = 0; /* uncompress=? parameter (or NULL) */ + char *zContent = 0; /* content=? parameter (or NULL) */ + char *zLanguageid = 0; /* languageid=? parameter (or NULL) */ + char **azNotindexed = 0; /* The set of notindexed= columns */ + int nNotindexed = 0; /* Size of azNotindexed[] array */ + + assert( strlen(argv[0])==4 ); + assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4) + || (sqlite3_strnicmp(argv[0], "fts3", 4)==0 && !isFts4) + ); + + nDb = (int)strlen(argv[1]) + 1; + nName = (int)strlen(argv[2]) + 1; + + nByte = sizeof(const char *) * (argc-2); + aCol = (const char **)sqlite3_malloc64(nByte); + if( aCol ){ + memset((void*)aCol, 0, nByte); + azNotindexed = (char **)sqlite3_malloc64(nByte); + } + if( azNotindexed ){ + memset(azNotindexed, 0, nByte); + } + if( !aCol || !azNotindexed ){ + rc = SQLITE_NOMEM; + goto fts3_init_out; + } + + /* Loop through all of the arguments passed by the user to the FTS3/4 + ** module (i.e. all the column names and special arguments). This loop + ** does the following: + ** + ** + Figures out the number of columns the FTSX table will have, and + ** the number of bytes of space that must be allocated to store copies + ** of the column names. + ** + ** + If there is a tokenizer specification included in the arguments, + ** initializes the tokenizer pTokenizer. + */ + for(i=3; rc==SQLITE_OK && i<argc; i++){ + char const *z = argv[i]; + int nKey; + char *zVal; + + /* Check if this is a tokenizer specification */ + if( !pTokenizer + && strlen(z)>8 + && 0==sqlite3_strnicmp(z, "tokenize", 8) + && 0==sqlite3Fts3IsIdChar(z[8]) + ){ + rc = sqlite3Fts3InitTokenizer(pHash, &z[9], &pTokenizer, pzErr); + } + + /* Check if it is an FTS4 special argument. */ + else if( isFts4 && fts3IsSpecialColumn(z, &nKey, &zVal) ){ + struct Fts4Option { + const char *zOpt; + int nOpt; + } aFts4Opt[] = { + { "matchinfo", 9 }, /* 0 -> MATCHINFO */ + { "prefix", 6 }, /* 1 -> PREFIX */ + { "compress", 8 }, /* 2 -> COMPRESS */ + { "uncompress", 10 }, /* 3 -> UNCOMPRESS */ + { "order", 5 }, /* 4 -> ORDER */ + { "content", 7 }, /* 5 -> CONTENT */ + { "languageid", 10 }, /* 6 -> LANGUAGEID */ + { "notindexed", 10 } /* 7 -> NOTINDEXED */ + }; + + int iOpt; + if( !zVal ){ + rc = SQLITE_NOMEM; + }else{ + for(iOpt=0; iOpt<SizeofArray(aFts4Opt); iOpt++){ + struct Fts4Option *pOp = &aFts4Opt[iOpt]; + if( nKey==pOp->nOpt && !sqlite3_strnicmp(z, pOp->zOpt, pOp->nOpt) ){ + break; + } + } + switch( iOpt ){ + case 0: /* MATCHINFO */ + if( strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "fts3", 4) ){ + sqlite3Fts3ErrMsg(pzErr, "unrecognized matchinfo: %s", zVal); + rc = SQLITE_ERROR; + } + bNoDocsize = 1; + break; + + case 1: /* PREFIX */ + sqlite3_free(zPrefix); + zPrefix = zVal; + zVal = 0; + break; + + case 2: /* COMPRESS */ + sqlite3_free(zCompress); + zCompress = zVal; + zVal = 0; + break; + + case 3: /* UNCOMPRESS */ + sqlite3_free(zUncompress); + zUncompress = zVal; + zVal = 0; + break; + + case 4: /* ORDER */ + if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3)) + && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 4)) + ){ + sqlite3Fts3ErrMsg(pzErr, "unrecognized order: %s", zVal); + rc = SQLITE_ERROR; + } + bDescIdx = (zVal[0]=='d' || zVal[0]=='D'); + break; + + case 5: /* CONTENT */ + sqlite3_free(zContent); + zContent = zVal; + zVal = 0; + break; + + case 6: /* LANGUAGEID */ + assert( iOpt==6 ); + sqlite3_free(zLanguageid); + zLanguageid = zVal; + zVal = 0; + break; + + case 7: /* NOTINDEXED */ + azNotindexed[nNotindexed++] = zVal; + zVal = 0; + break; + + default: + assert( iOpt==SizeofArray(aFts4Opt) ); + sqlite3Fts3ErrMsg(pzErr, "unrecognized parameter: %s", z); + rc = SQLITE_ERROR; + break; + } + sqlite3_free(zVal); + } + } + + /* Otherwise, the argument is a column name. */ + else { + nString += (int)(strlen(z) + 1); + aCol[nCol++] = z; + } + } + + /* If a content=xxx option was specified, the following: + ** + ** 1. Ignore any compress= and uncompress= options. + ** + ** 2. If no column names were specified as part of the CREATE VIRTUAL + ** TABLE statement, use all columns from the content table. + */ + if( rc==SQLITE_OK && zContent ){ + sqlite3_free(zCompress); + sqlite3_free(zUncompress); + zCompress = 0; + zUncompress = 0; + if( nCol==0 ){ + sqlite3_free((void*)aCol); + aCol = 0; + rc = fts3ContentColumns(db, argv[1], zContent,&aCol,&nCol,&nString,pzErr); + + /* If a languageid= option was specified, remove the language id + ** column from the aCol[] array. */ + if( rc==SQLITE_OK && zLanguageid ){ + int j; + for(j=0; j<nCol; j++){ + if( sqlite3_stricmp(zLanguageid, aCol[j])==0 ){ + int k; + for(k=j; k<nCol; k++) aCol[k] = aCol[k+1]; + nCol--; + break; + } + } + } + } + } + if( rc!=SQLITE_OK ) goto fts3_init_out; + + if( nCol==0 ){ + assert( nString==0 ); + aCol[0] = "content"; + nString = 8; + nCol = 1; + } + + if( pTokenizer==0 ){ + rc = sqlite3Fts3InitTokenizer(pHash, "simple", &pTokenizer, pzErr); + if( rc!=SQLITE_OK ) goto fts3_init_out; + } + assert( pTokenizer ); + + rc = fts3PrefixParameter(zPrefix, &nIndex, &aIndex); + if( rc==SQLITE_ERROR ){ + assert( zPrefix ); + sqlite3Fts3ErrMsg(pzErr, "error parsing prefix parameter: %s", zPrefix); + } + if( rc!=SQLITE_OK ) goto fts3_init_out; + + /* Allocate and populate the Fts3Table structure. */ + nByte = sizeof(Fts3Table) + /* Fts3Table */ + nCol * sizeof(char *) + /* azColumn */ + nIndex * sizeof(struct Fts3Index) + /* aIndex */ + nCol * sizeof(u8) + /* abNotindexed */ + nName + /* zName */ + nDb + /* zDb */ + nString; /* Space for azColumn strings */ + p = (Fts3Table*)sqlite3_malloc64(nByte); + if( p==0 ){ + rc = SQLITE_NOMEM; + goto fts3_init_out; + } + memset(p, 0, nByte); + p->db = db; + p->nColumn = nCol; + p->nPendingData = 0; + p->azColumn = (char **)&p[1]; + p->pTokenizer = pTokenizer; + p->nMaxPendingData = FTS3_MAX_PENDING_DATA; + p->bHasDocsize = (isFts4 && bNoDocsize==0); + p->bHasStat = (u8)isFts4; + p->bFts4 = (u8)isFts4; + p->bDescIdx = (u8)bDescIdx; + p->nAutoincrmerge = 0xff; /* 0xff means setting unknown */ + p->zContentTbl = zContent; + p->zLanguageid = zLanguageid; + zContent = 0; + zLanguageid = 0; + TESTONLY( p->inTransaction = -1 ); + TESTONLY( p->mxSavepoint = -1 ); + + p->aIndex = (struct Fts3Index *)&p->azColumn[nCol]; + memcpy(p->aIndex, aIndex, sizeof(struct Fts3Index) * nIndex); + p->nIndex = nIndex; + for(i=0; i<nIndex; i++){ + fts3HashInit(&p->aIndex[i].hPending, FTS3_HASH_STRING, 1); + } + p->abNotindexed = (u8 *)&p->aIndex[nIndex]; + + /* Fill in the zName and zDb fields of the vtab structure. */ + zCsr = (char *)&p->abNotindexed[nCol]; + p->zName = zCsr; + memcpy(zCsr, argv[2], nName); + zCsr += nName; + p->zDb = zCsr; + memcpy(zCsr, argv[1], nDb); + zCsr += nDb; + + /* Fill in the azColumn array */ + for(iCol=0; iCol<nCol; iCol++){ + char *z; + int n = 0; + z = (char *)sqlite3Fts3NextToken(aCol[iCol], &n); + if( n>0 ){ + memcpy(zCsr, z, n); + } + zCsr[n] = '\0'; + sqlite3Fts3Dequote(zCsr); + p->azColumn[iCol] = zCsr; + zCsr += n+1; + assert( zCsr <= &((char *)p)[nByte] ); + } + + /* Fill in the abNotindexed array */ + for(iCol=0; iCol<nCol; iCol++){ + int n = (int)strlen(p->azColumn[iCol]); + for(i=0; i<nNotindexed; i++){ + char *zNot = azNotindexed[i]; + if( zNot && n==(int)strlen(zNot) + && 0==sqlite3_strnicmp(p->azColumn[iCol], zNot, n) + ){ + p->abNotindexed[iCol] = 1; + sqlite3_free(zNot); + azNotindexed[i] = 0; + } + } + } + for(i=0; i<nNotindexed; i++){ + if( azNotindexed[i] ){ + sqlite3Fts3ErrMsg(pzErr, "no such column: %s", azNotindexed[i]); + rc = SQLITE_ERROR; + } + } + + if( rc==SQLITE_OK && (zCompress==0)!=(zUncompress==0) ){ + char const *zMiss = (zCompress==0 ? "compress" : "uncompress"); + rc = SQLITE_ERROR; + sqlite3Fts3ErrMsg(pzErr, "missing %s parameter in fts4 constructor", zMiss); + } + p->zReadExprlist = fts3ReadExprList(p, zUncompress, &rc); + p->zWriteExprlist = fts3WriteExprList(p, zCompress, &rc); + if( rc!=SQLITE_OK ) goto fts3_init_out; + + /* If this is an xCreate call, create the underlying tables in the + ** database. TODO: For xConnect(), it could verify that said tables exist. + */ + if( isCreate ){ + rc = fts3CreateTables(p); + } + + /* Check to see if a legacy fts3 table has been "upgraded" by the + ** addition of a %_stat table so that it can use incremental merge. + */ + if( !isFts4 && !isCreate ){ + p->bHasStat = 2; + } + + /* Figure out the page-size for the database. This is required in order to + ** estimate the cost of loading large doclists from the database. */ + fts3DatabasePageSize(&rc, p); + p->nNodeSize = p->nPgsz-35; + +#if defined(SQLITE_DEBUG)||defined(SQLITE_TEST) + p->nMergeCount = FTS3_MERGE_COUNT; +#endif + + /* Declare the table schema to SQLite. */ + fts3DeclareVtab(&rc, p); + +fts3_init_out: + sqlite3_free(zPrefix); + sqlite3_free(aIndex); + sqlite3_free(zCompress); + sqlite3_free(zUncompress); + sqlite3_free(zContent); + sqlite3_free(zLanguageid); + for(i=0; i<nNotindexed; i++) sqlite3_free(azNotindexed[i]); + sqlite3_free((void *)aCol); + sqlite3_free((void *)azNotindexed); + if( rc!=SQLITE_OK ){ + if( p ){ + fts3DisconnectMethod((sqlite3_vtab *)p); + }else if( pTokenizer ){ + pTokenizer->pModule->xDestroy(pTokenizer); + } + }else{ + assert( p->pSegments==0 ); + *ppVTab = &p->base; + } + return rc; +} + +/* +** The xConnect() and xCreate() methods for the virtual table. All the +** work is done in function fts3InitVtab(). +*/ +static int fts3ConnectMethod( + sqlite3 *db, /* Database connection */ + void *pAux, /* Pointer to tokenizer hash table */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ + char **pzErr /* OUT: sqlite3_malloc'd error message */ +){ + return fts3InitVtab(0, db, pAux, argc, argv, ppVtab, pzErr); +} +static int fts3CreateMethod( + sqlite3 *db, /* Database connection */ + void *pAux, /* Pointer to tokenizer hash table */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ + char **pzErr /* OUT: sqlite3_malloc'd error message */ +){ + return fts3InitVtab(1, db, pAux, argc, argv, ppVtab, pzErr); +} + +/* +** Set the pIdxInfo->estimatedRows variable to nRow. Unless this +** extension is currently being used by a version of SQLite too old to +** support estimatedRows. In that case this function is a no-op. +*/ +static void fts3SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){ +#if SQLITE_VERSION_NUMBER>=3008002 + if( sqlite3_libversion_number()>=3008002 ){ + pIdxInfo->estimatedRows = nRow; + } +#endif +} + +/* +** Set the SQLITE_INDEX_SCAN_UNIQUE flag in pIdxInfo->flags. Unless this +** extension is currently being used by a version of SQLite too old to +** support index-info flags. In that case this function is a no-op. +*/ +static void fts3SetUniqueFlag(sqlite3_index_info *pIdxInfo){ +#if SQLITE_VERSION_NUMBER>=3008012 + if( sqlite3_libversion_number()>=3008012 ){ + pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE; + } +#endif +} + +/* +** Implementation of the xBestIndex method for FTS3 tables. There +** are three possible strategies, in order of preference: +** +** 1. Direct lookup by rowid or docid. +** 2. Full-text search using a MATCH operator on a non-docid column. +** 3. Linear scan of %_content table. +*/ +static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ + Fts3Table *p = (Fts3Table *)pVTab; + int i; /* Iterator variable */ + int iCons = -1; /* Index of constraint to use */ + + int iLangidCons = -1; /* Index of langid=x constraint, if present */ + int iDocidGe = -1; /* Index of docid>=x constraint, if present */ + int iDocidLe = -1; /* Index of docid<=x constraint, if present */ + int iIdx; + + if( p->bLock ){ + return SQLITE_ERROR; + } + + /* By default use a full table scan. This is an expensive option, + ** so search through the constraints to see if a more efficient + ** strategy is possible. + */ + pInfo->idxNum = FTS3_FULLSCAN_SEARCH; + pInfo->estimatedCost = 5000000; + for(i=0; i<pInfo->nConstraint; i++){ + int bDocid; /* True if this constraint is on docid */ + struct sqlite3_index_constraint *pCons = &pInfo->aConstraint[i]; + if( pCons->usable==0 ){ + if( pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH ){ + /* There exists an unusable MATCH constraint. This means that if + ** the planner does elect to use the results of this call as part + ** of the overall query plan the user will see an "unable to use + ** function MATCH in the requested context" error. To discourage + ** this, return a very high cost here. */ + pInfo->idxNum = FTS3_FULLSCAN_SEARCH; + pInfo->estimatedCost = 1e50; + fts3SetEstimatedRows(pInfo, ((sqlite3_int64)1) << 50); + return SQLITE_OK; + } + continue; + } + + bDocid = (pCons->iColumn<0 || pCons->iColumn==p->nColumn+1); + + /* A direct lookup on the rowid or docid column. Assign a cost of 1.0. */ + if( iCons<0 && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ && bDocid ){ + pInfo->idxNum = FTS3_DOCID_SEARCH; + pInfo->estimatedCost = 1.0; + iCons = i; + } + + /* A MATCH constraint. Use a full-text search. + ** + ** If there is more than one MATCH constraint available, use the first + ** one encountered. If there is both a MATCH constraint and a direct + ** rowid/docid lookup, prefer the MATCH strategy. This is done even + ** though the rowid/docid lookup is faster than a MATCH query, selecting + ** it would lead to an "unable to use function MATCH in the requested + ** context" error. + */ + if( pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH + && pCons->iColumn>=0 && pCons->iColumn<=p->nColumn + ){ + pInfo->idxNum = FTS3_FULLTEXT_SEARCH + pCons->iColumn; + pInfo->estimatedCost = 2.0; + iCons = i; + } + + /* Equality constraint on the langid column */ + if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ + && pCons->iColumn==p->nColumn + 2 + ){ + iLangidCons = i; + } + + if( bDocid ){ + switch( pCons->op ){ + case SQLITE_INDEX_CONSTRAINT_GE: + case SQLITE_INDEX_CONSTRAINT_GT: + iDocidGe = i; + break; + + case SQLITE_INDEX_CONSTRAINT_LE: + case SQLITE_INDEX_CONSTRAINT_LT: + iDocidLe = i; + break; + } + } + } + + /* If using a docid=? or rowid=? strategy, set the UNIQUE flag. */ + if( pInfo->idxNum==FTS3_DOCID_SEARCH ) fts3SetUniqueFlag(pInfo); + + iIdx = 1; + if( iCons>=0 ){ + pInfo->aConstraintUsage[iCons].argvIndex = iIdx++; + pInfo->aConstraintUsage[iCons].omit = 1; + } + if( iLangidCons>=0 ){ + pInfo->idxNum |= FTS3_HAVE_LANGID; + pInfo->aConstraintUsage[iLangidCons].argvIndex = iIdx++; + } + if( iDocidGe>=0 ){ + pInfo->idxNum |= FTS3_HAVE_DOCID_GE; + pInfo->aConstraintUsage[iDocidGe].argvIndex = iIdx++; + } + if( iDocidLe>=0 ){ + pInfo->idxNum |= FTS3_HAVE_DOCID_LE; + pInfo->aConstraintUsage[iDocidLe].argvIndex = iIdx++; + } + + /* Regardless of the strategy selected, FTS can deliver rows in rowid (or + ** docid) order. Both ascending and descending are possible. + */ + if( pInfo->nOrderBy==1 ){ + struct sqlite3_index_orderby *pOrder = &pInfo->aOrderBy[0]; + if( pOrder->iColumn<0 || pOrder->iColumn==p->nColumn+1 ){ + if( pOrder->desc ){ + pInfo->idxStr = "DESC"; + }else{ + pInfo->idxStr = "ASC"; + } + pInfo->orderByConsumed = 1; + } + } + + assert( p->pSegments==0 ); + return SQLITE_OK; +} + +/* +** Implementation of xOpen method. +*/ +static int fts3OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ + sqlite3_vtab_cursor *pCsr; /* Allocated cursor */ + + UNUSED_PARAMETER(pVTab); + + /* Allocate a buffer large enough for an Fts3Cursor structure. If the + ** allocation succeeds, zero it and return SQLITE_OK. Otherwise, + ** if the allocation fails, return SQLITE_NOMEM. + */ + *ppCsr = pCsr = (sqlite3_vtab_cursor *)sqlite3_malloc(sizeof(Fts3Cursor)); + if( !pCsr ){ + return SQLITE_NOMEM; + } + memset(pCsr, 0, sizeof(Fts3Cursor)); + return SQLITE_OK; +} + +/* +** Finalize the statement handle at pCsr->pStmt. +** +** Or, if that statement handle is one created by fts3CursorSeekStmt(), +** and the Fts3Table.pSeekStmt slot is currently NULL, save the statement +** pointer there instead of finalizing it. +*/ +static void fts3CursorFinalizeStmt(Fts3Cursor *pCsr){ + if( pCsr->bSeekStmt ){ + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; + if( p->pSeekStmt==0 ){ + p->pSeekStmt = pCsr->pStmt; + sqlite3_reset(pCsr->pStmt); + pCsr->pStmt = 0; + } + pCsr->bSeekStmt = 0; + } + sqlite3_finalize(pCsr->pStmt); +} + +/* +** Free all resources currently held by the cursor passed as the only +** argument. +*/ +static void fts3ClearCursor(Fts3Cursor *pCsr){ + fts3CursorFinalizeStmt(pCsr); + sqlite3Fts3FreeDeferredTokens(pCsr); + sqlite3_free(pCsr->aDoclist); + sqlite3Fts3MIBufferFree(pCsr->pMIBuffer); + sqlite3Fts3ExprFree(pCsr->pExpr); + memset(&(&pCsr->base)[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor)); +} + +/* +** Close the cursor. For additional information see the documentation +** on the xClose method of the virtual table interface. +*/ +static int fts3CloseMethod(sqlite3_vtab_cursor *pCursor){ + Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; + assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); + fts3ClearCursor(pCsr); + assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** If pCsr->pStmt has not been prepared (i.e. if pCsr->pStmt==0), then +** compose and prepare an SQL statement of the form: +** +** "SELECT <columns> FROM %_content WHERE rowid = ?" +** +** (or the equivalent for a content=xxx table) and set pCsr->pStmt to +** it. If an error occurs, return an SQLite error code. +*/ +static int fts3CursorSeekStmt(Fts3Cursor *pCsr){ + int rc = SQLITE_OK; + if( pCsr->pStmt==0 ){ + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; + char *zSql; + if( p->pSeekStmt ){ + pCsr->pStmt = p->pSeekStmt; + p->pSeekStmt = 0; + }else{ + zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist); + if( !zSql ) return SQLITE_NOMEM; + p->bLock++; + rc = sqlite3_prepare_v3( + p->db, zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0 + ); + p->bLock--; + sqlite3_free(zSql); + } + if( rc==SQLITE_OK ) pCsr->bSeekStmt = 1; + } + return rc; +} + +/* +** Position the pCsr->pStmt statement so that it is on the row +** of the %_content table that contains the last match. Return +** SQLITE_OK on success. +*/ +static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){ + int rc = SQLITE_OK; + if( pCsr->isRequireSeek ){ + rc = fts3CursorSeekStmt(pCsr); + if( rc==SQLITE_OK ){ + Fts3Table *pTab = (Fts3Table*)pCsr->base.pVtab; + pTab->bLock++; + sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId); + pCsr->isRequireSeek = 0; + if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){ + pTab->bLock--; + return SQLITE_OK; + }else{ + pTab->bLock--; + rc = sqlite3_reset(pCsr->pStmt); + if( rc==SQLITE_OK && ((Fts3Table *)pCsr->base.pVtab)->zContentTbl==0 ){ + /* If no row was found and no error has occurred, then the %_content + ** table is missing a row that is present in the full-text index. + ** The data structures are corrupt. */ + rc = FTS_CORRUPT_VTAB; + pCsr->isEof = 1; + } + } + } + } + + if( rc!=SQLITE_OK && pContext ){ + sqlite3_result_error_code(pContext, rc); + } + return rc; +} + +/* +** This function is used to process a single interior node when searching +** a b-tree for a term or term prefix. The node data is passed to this +** function via the zNode/nNode parameters. The term to search for is +** passed in zTerm/nTerm. +** +** If piFirst is not NULL, then this function sets *piFirst to the blockid +** of the child node that heads the sub-tree that may contain the term. +** +** If piLast is not NULL, then *piLast is set to the right-most child node +** that heads a sub-tree that may contain a term for which zTerm/nTerm is +** a prefix. +** +** If an OOM error occurs, SQLITE_NOMEM is returned. Otherwise, SQLITE_OK. +*/ +static int fts3ScanInteriorNode( + const char *zTerm, /* Term to select leaves for */ + int nTerm, /* Size of term zTerm in bytes */ + const char *zNode, /* Buffer containing segment interior node */ + int nNode, /* Size of buffer at zNode */ + sqlite3_int64 *piFirst, /* OUT: Selected child node */ + sqlite3_int64 *piLast /* OUT: Selected child node */ +){ + int rc = SQLITE_OK; /* Return code */ + const char *zCsr = zNode; /* Cursor to iterate through node */ + const char *zEnd = &zCsr[nNode];/* End of interior node buffer */ + char *zBuffer = 0; /* Buffer to load terms into */ + i64 nAlloc = 0; /* Size of allocated buffer */ + int isFirstTerm = 1; /* True when processing first term on page */ + u64 iChild; /* Block id of child node to descend to */ + int nBuffer = 0; /* Total term size */ + + /* Skip over the 'height' varint that occurs at the start of every + ** interior node. Then load the blockid of the left-child of the b-tree + ** node into variable iChild. + ** + ** Even if the data structure on disk is corrupted, this (reading two + ** varints from the buffer) does not risk an overread. If zNode is a + ** root node, then the buffer comes from a SELECT statement. SQLite does + ** not make this guarantee explicitly, but in practice there are always + ** either more than 20 bytes of allocated space following the nNode bytes of + ** contents, or two zero bytes. Or, if the node is read from the %_segments + ** table, then there are always 20 bytes of zeroed padding following the + ** nNode bytes of content (see sqlite3Fts3ReadBlock() for details). + */ + zCsr += sqlite3Fts3GetVarintU(zCsr, &iChild); + zCsr += sqlite3Fts3GetVarintU(zCsr, &iChild); + if( zCsr>zEnd ){ + return FTS_CORRUPT_VTAB; + } + + while( zCsr<zEnd && (piFirst || piLast) ){ + int cmp; /* memcmp() result */ + int nSuffix; /* Size of term suffix */ + int nPrefix = 0; /* Size of term prefix */ + + /* Load the next term on the node into zBuffer. Use realloc() to expand + ** the size of zBuffer if required. */ + if( !isFirstTerm ){ + zCsr += fts3GetVarint32(zCsr, &nPrefix); + if( nPrefix>nBuffer ){ + rc = FTS_CORRUPT_VTAB; + goto finish_scan; + } + } + isFirstTerm = 0; + zCsr += fts3GetVarint32(zCsr, &nSuffix); + + assert( nPrefix>=0 && nSuffix>=0 ); + if( nPrefix>zCsr-zNode || nSuffix>zEnd-zCsr || nSuffix==0 ){ + rc = FTS_CORRUPT_VTAB; + goto finish_scan; + } + if( (i64)nPrefix+nSuffix>nAlloc ){ + char *zNew; + nAlloc = ((i64)nPrefix+nSuffix) * 2; + zNew = (char *)sqlite3_realloc64(zBuffer, nAlloc); + if( !zNew ){ + rc = SQLITE_NOMEM; + goto finish_scan; + } + zBuffer = zNew; + } + assert( zBuffer ); + memcpy(&zBuffer[nPrefix], zCsr, nSuffix); + nBuffer = nPrefix + nSuffix; + zCsr += nSuffix; + + /* Compare the term we are searching for with the term just loaded from + ** the interior node. If the specified term is greater than or equal + ** to the term from the interior node, then all terms on the sub-tree + ** headed by node iChild are smaller than zTerm. No need to search + ** iChild. + ** + ** If the interior node term is larger than the specified term, then + ** the tree headed by iChild may contain the specified term. + */ + cmp = memcmp(zTerm, zBuffer, (nBuffer>nTerm ? nTerm : nBuffer)); + if( piFirst && (cmp<0 || (cmp==0 && nBuffer>nTerm)) ){ + *piFirst = (i64)iChild; + piFirst = 0; + } + + if( piLast && cmp<0 ){ + *piLast = (i64)iChild; + piLast = 0; + } + + iChild++; + }; + + if( piFirst ) *piFirst = (i64)iChild; + if( piLast ) *piLast = (i64)iChild; + + finish_scan: + sqlite3_free(zBuffer); + return rc; +} + + +/* +** The buffer pointed to by argument zNode (size nNode bytes) contains an +** interior node of a b-tree segment. The zTerm buffer (size nTerm bytes) +** contains a term. This function searches the sub-tree headed by the zNode +** node for the range of leaf nodes that may contain the specified term +** or terms for which the specified term is a prefix. +** +** If piLeaf is not NULL, then *piLeaf is set to the blockid of the +** left-most leaf node in the tree that may contain the specified term. +** If piLeaf2 is not NULL, then *piLeaf2 is set to the blockid of the +** right-most leaf node that may contain a term for which the specified +** term is a prefix. +** +** It is possible that the range of returned leaf nodes does not contain +** the specified term or any terms for which it is a prefix. However, if the +** segment does contain any such terms, they are stored within the identified +** range. Because this function only inspects interior segment nodes (and +** never loads leaf nodes into memory), it is not possible to be sure. +** +** If an error occurs, an error code other than SQLITE_OK is returned. +*/ +static int fts3SelectLeaf( + Fts3Table *p, /* Virtual table handle */ + const char *zTerm, /* Term to select leaves for */ + int nTerm, /* Size of term zTerm in bytes */ + const char *zNode, /* Buffer containing segment interior node */ + int nNode, /* Size of buffer at zNode */ + sqlite3_int64 *piLeaf, /* Selected leaf node */ + sqlite3_int64 *piLeaf2 /* Selected leaf node */ +){ + int rc = SQLITE_OK; /* Return code */ + int iHeight; /* Height of this node in tree */ + + assert( piLeaf || piLeaf2 ); + + fts3GetVarint32(zNode, &iHeight); + rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2); + assert_fts3_nc( !piLeaf2 || !piLeaf || rc!=SQLITE_OK || (*piLeaf<=*piLeaf2) ); + + if( rc==SQLITE_OK && iHeight>1 ){ + char *zBlob = 0; /* Blob read from %_segments table */ + int nBlob = 0; /* Size of zBlob in bytes */ + + if( piLeaf && piLeaf2 && (*piLeaf!=*piLeaf2) ){ + rc = sqlite3Fts3ReadBlock(p, *piLeaf, &zBlob, &nBlob, 0); + if( rc==SQLITE_OK ){ + rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, 0); + } + sqlite3_free(zBlob); + piLeaf = 0; + zBlob = 0; + } + + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3ReadBlock(p, piLeaf?*piLeaf:*piLeaf2, &zBlob, &nBlob, 0); + } + if( rc==SQLITE_OK ){ + int iNewHeight = 0; + fts3GetVarint32(zBlob, &iNewHeight); + if( iNewHeight>=iHeight ){ + rc = FTS_CORRUPT_VTAB; + }else{ + rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, piLeaf2); + } + } + sqlite3_free(zBlob); + } + + return rc; +} + +/* +** This function is used to create delta-encoded serialized lists of FTS3 +** varints. Each call to this function appends a single varint to a list. +*/ +static void fts3PutDeltaVarint( + char **pp, /* IN/OUT: Output pointer */ + sqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */ + sqlite3_int64 iVal /* Write this value to the list */ +){ + assert_fts3_nc( iVal-*piPrev > 0 || (*piPrev==0 && iVal==0) ); + *pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev); + *piPrev = iVal; +} + +/* +** When this function is called, *ppPoslist is assumed to point to the +** start of a position-list. After it returns, *ppPoslist points to the +** first byte after the position-list. +** +** A position list is list of positions (delta encoded) and columns for +** a single document record of a doclist. So, in other words, this +** routine advances *ppPoslist so that it points to the next docid in +** the doclist, or to the first byte past the end of the doclist. +** +** If pp is not NULL, then the contents of the position list are copied +** to *pp. *pp is set to point to the first byte past the last byte copied +** before this function returns. +*/ +static void fts3PoslistCopy(char **pp, char **ppPoslist){ + char *pEnd = *ppPoslist; + char c = 0; + + /* The end of a position list is marked by a zero encoded as an FTS3 + ** varint. A single POS_END (0) byte. Except, if the 0 byte is preceded by + ** a byte with the 0x80 bit set, then it is not a varint 0, but the tail + ** of some other, multi-byte, value. + ** + ** The following while-loop moves pEnd to point to the first byte that is not + ** immediately preceded by a byte with the 0x80 bit set. Then increments + ** pEnd once more so that it points to the byte immediately following the + ** last byte in the position-list. + */ + while( *pEnd | c ){ + c = *pEnd++ & 0x80; + testcase( c!=0 && (*pEnd)==0 ); + } + pEnd++; /* Advance past the POS_END terminator byte */ + + if( pp ){ + int n = (int)(pEnd - *ppPoslist); + char *p = *pp; + memcpy(p, *ppPoslist, n); + p += n; + *pp = p; + } + *ppPoslist = pEnd; +} + +/* +** When this function is called, *ppPoslist is assumed to point to the +** start of a column-list. After it returns, *ppPoslist points to the +** to the terminator (POS_COLUMN or POS_END) byte of the column-list. +** +** A column-list is list of delta-encoded positions for a single column +** within a single document within a doclist. +** +** The column-list is terminated either by a POS_COLUMN varint (1) or +** a POS_END varint (0). This routine leaves *ppPoslist pointing to +** the POS_COLUMN or POS_END that terminates the column-list. +** +** If pp is not NULL, then the contents of the column-list are copied +** to *pp. *pp is set to point to the first byte past the last byte copied +** before this function returns. The POS_COLUMN or POS_END terminator +** is not copied into *pp. +*/ +static void fts3ColumnlistCopy(char **pp, char **ppPoslist){ + char *pEnd = *ppPoslist; + char c = 0; + + /* A column-list is terminated by either a 0x01 or 0x00 byte that is + ** not part of a multi-byte varint. + */ + while( 0xFE & (*pEnd | c) ){ + c = *pEnd++ & 0x80; + testcase( c!=0 && ((*pEnd)&0xfe)==0 ); + } + if( pp ){ + int n = (int)(pEnd - *ppPoslist); + char *p = *pp; + memcpy(p, *ppPoslist, n); + p += n; + *pp = p; + } + *ppPoslist = pEnd; +} + +/* +** Value used to signify the end of an position-list. This must be +** as large or larger than any value that might appear on the +** position-list, even a position list that has been corrupted. +*/ +#define POSITION_LIST_END LARGEST_INT64 + +/* +** This function is used to help parse position-lists. When this function is +** called, *pp may point to the start of the next varint in the position-list +** being parsed, or it may point to 1 byte past the end of the position-list +** (in which case **pp will be a terminator bytes POS_END (0) or +** (1)). +** +** If *pp points past the end of the current position-list, set *pi to +** POSITION_LIST_END and return. Otherwise, read the next varint from *pp, +** increment the current value of *pi by the value read, and set *pp to +** point to the next value before returning. +** +** Before calling this routine *pi must be initialized to the value of +** the previous position, or zero if we are reading the first position +** in the position-list. Because positions are delta-encoded, the value +** of the previous position is needed in order to compute the value of +** the next position. +*/ +static void fts3ReadNextPos( + char **pp, /* IN/OUT: Pointer into position-list buffer */ + sqlite3_int64 *pi /* IN/OUT: Value read from position-list */ +){ + if( (**pp)&0xFE ){ + int iVal; + *pp += fts3GetVarint32((*pp), &iVal); + *pi += iVal; + *pi -= 2; + }else{ + *pi = POSITION_LIST_END; + } +} + +/* +** If parameter iCol is not 0, write an POS_COLUMN (1) byte followed by +** the value of iCol encoded as a varint to *pp. This will start a new +** column list. +** +** Set *pp to point to the byte just after the last byte written before +** returning (do not modify it if iCol==0). Return the total number of bytes +** written (0 if iCol==0). +*/ +static int fts3PutColNumber(char **pp, int iCol){ + int n = 0; /* Number of bytes written */ + if( iCol ){ + char *p = *pp; /* Output pointer */ + n = 1 + sqlite3Fts3PutVarint(&p[1], iCol); + *p = 0x01; + *pp = &p[n]; + } + return n; +} + +/* +** Compute the union of two position lists. The output written +** into *pp contains all positions of both *pp1 and *pp2 in sorted +** order and with any duplicates removed. All pointers are +** updated appropriately. The caller is responsible for insuring +** that there is enough space in *pp to hold the complete output. +*/ +static int fts3PoslistMerge( + char **pp, /* Output buffer */ + char **pp1, /* Left input list */ + char **pp2 /* Right input list */ +){ + char *p = *pp; + char *p1 = *pp1; + char *p2 = *pp2; + + while( *p1 || *p2 ){ + int iCol1; /* The current column index in pp1 */ + int iCol2; /* The current column index in pp2 */ + + if( *p1==POS_COLUMN ){ + fts3GetVarint32(&p1[1], &iCol1); + if( iCol1==0 ) return FTS_CORRUPT_VTAB; + } + else if( *p1==POS_END ) iCol1 = 0x7fffffff; + else iCol1 = 0; + + if( *p2==POS_COLUMN ){ + fts3GetVarint32(&p2[1], &iCol2); + if( iCol2==0 ) return FTS_CORRUPT_VTAB; + } + else if( *p2==POS_END ) iCol2 = 0x7fffffff; + else iCol2 = 0; + + if( iCol1==iCol2 ){ + sqlite3_int64 i1 = 0; /* Last position from pp1 */ + sqlite3_int64 i2 = 0; /* Last position from pp2 */ + sqlite3_int64 iPrev = 0; + int n = fts3PutColNumber(&p, iCol1); + p1 += n; + p2 += n; + + /* At this point, both p1 and p2 point to the start of column-lists + ** for the same column (the column with index iCol1 and iCol2). + ** A column-list is a list of non-negative delta-encoded varints, each + ** incremented by 2 before being stored. Each list is terminated by a + ** POS_END (0) or POS_COLUMN (1). The following block merges the two lists + ** and writes the results to buffer p. p is left pointing to the byte + ** after the list written. No terminator (POS_END or POS_COLUMN) is + ** written to the output. + */ + fts3GetDeltaVarint(&p1, &i1); + fts3GetDeltaVarint(&p2, &i2); + if( i1<2 || i2<2 ){ + break; + } + do { + fts3PutDeltaVarint(&p, &iPrev, (i1<i2) ? i1 : i2); + iPrev -= 2; + if( i1==i2 ){ + fts3ReadNextPos(&p1, &i1); + fts3ReadNextPos(&p2, &i2); + }else if( i1<i2 ){ + fts3ReadNextPos(&p1, &i1); + }else{ + fts3ReadNextPos(&p2, &i2); + } + }while( i1!=POSITION_LIST_END || i2!=POSITION_LIST_END ); + }else if( iCol1<iCol2 ){ + p1 += fts3PutColNumber(&p, iCol1); + fts3ColumnlistCopy(&p, &p1); + }else{ + p2 += fts3PutColNumber(&p, iCol2); + fts3ColumnlistCopy(&p, &p2); + } + } + + *p++ = POS_END; + *pp = p; + *pp1 = p1 + 1; + *pp2 = p2 + 1; + return SQLITE_OK; +} + +/* +** This function is used to merge two position lists into one. When it is +** called, *pp1 and *pp2 must both point to position lists. A position-list is +** the part of a doclist that follows each document id. For example, if a row +** contains: +** +** 'a b c'|'x y z'|'a b b a' +** +** Then the position list for this row for token 'b' would consist of: +** +** 0x02 0x01 0x02 0x03 0x03 0x00 +** +** When this function returns, both *pp1 and *pp2 are left pointing to the +** byte following the 0x00 terminator of their respective position lists. +** +** If isSaveLeft is 0, an entry is added to the output position list for +** each position in *pp2 for which there exists one or more positions in +** *pp1 so that (pos(*pp2)>pos(*pp1) && pos(*pp2)-pos(*pp1)<=nToken). i.e. +** when the *pp1 token appears before the *pp2 token, but not more than nToken +** slots before it. +** +** e.g. nToken==1 searches for adjacent positions. +*/ +static int fts3PoslistPhraseMerge( + char **pp, /* IN/OUT: Preallocated output buffer */ + int nToken, /* Maximum difference in token positions */ + int isSaveLeft, /* Save the left position */ + int isExact, /* If *pp1 is exactly nTokens before *pp2 */ + char **pp1, /* IN/OUT: Left input list */ + char **pp2 /* IN/OUT: Right input list */ +){ + char *p = *pp; + char *p1 = *pp1; + char *p2 = *pp2; + int iCol1 = 0; + int iCol2 = 0; + + /* Never set both isSaveLeft and isExact for the same invocation. */ + assert( isSaveLeft==0 || isExact==0 ); + + assert_fts3_nc( p!=0 && *p1!=0 && *p2!=0 ); + if( *p1==POS_COLUMN ){ + p1++; + p1 += fts3GetVarint32(p1, &iCol1); + } + if( *p2==POS_COLUMN ){ + p2++; + p2 += fts3GetVarint32(p2, &iCol2); + } + + while( 1 ){ + if( iCol1==iCol2 ){ + char *pSave = p; + sqlite3_int64 iPrev = 0; + sqlite3_int64 iPos1 = 0; + sqlite3_int64 iPos2 = 0; + + if( iCol1 ){ + *p++ = POS_COLUMN; + p += sqlite3Fts3PutVarint(p, iCol1); + } + + fts3GetDeltaVarint(&p1, &iPos1); iPos1 -= 2; + fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2; + if( iPos1<0 || iPos2<0 ) break; + + while( 1 ){ + if( iPos2==iPos1+nToken + || (isExact==0 && iPos2>iPos1 && iPos2<=iPos1+nToken) + ){ + sqlite3_int64 iSave; + iSave = isSaveLeft ? iPos1 : iPos2; + fts3PutDeltaVarint(&p, &iPrev, iSave+2); iPrev -= 2; + pSave = 0; + assert( p ); + } + if( (!isSaveLeft && iPos2<=(iPos1+nToken)) || iPos2<=iPos1 ){ + if( (*p2&0xFE)==0 ) break; + fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2; + }else{ + if( (*p1&0xFE)==0 ) break; + fts3GetDeltaVarint(&p1, &iPos1); iPos1 -= 2; + } + } + + if( pSave ){ + assert( pp && p ); + p = pSave; + } + + fts3ColumnlistCopy(0, &p1); + fts3ColumnlistCopy(0, &p2); + assert( (*p1&0xFE)==0 && (*p2&0xFE)==0 ); + if( 0==*p1 || 0==*p2 ) break; + + p1++; + p1 += fts3GetVarint32(p1, &iCol1); + p2++; + p2 += fts3GetVarint32(p2, &iCol2); + } + + /* Advance pointer p1 or p2 (whichever corresponds to the smaller of + ** iCol1 and iCol2) so that it points to either the 0x00 that marks the + ** end of the position list, or the 0x01 that precedes the next + ** column-number in the position list. + */ + else if( iCol1<iCol2 ){ + fts3ColumnlistCopy(0, &p1); + if( 0==*p1 ) break; + p1++; + p1 += fts3GetVarint32(p1, &iCol1); + }else{ + fts3ColumnlistCopy(0, &p2); + if( 0==*p2 ) break; + p2++; + p2 += fts3GetVarint32(p2, &iCol2); + } + } + + fts3PoslistCopy(0, &p2); + fts3PoslistCopy(0, &p1); + *pp1 = p1; + *pp2 = p2; + if( *pp==p ){ + return 0; + } + *p++ = 0x00; + *pp = p; + return 1; +} + +/* +** Merge two position-lists as required by the NEAR operator. The argument +** position lists correspond to the left and right phrases of an expression +** like: +** +** "phrase 1" NEAR "phrase number 2" +** +** Position list *pp1 corresponds to the left-hand side of the NEAR +** expression and *pp2 to the right. As usual, the indexes in the position +** lists are the offsets of the last token in each phrase (tokens "1" and "2" +** in the example above). +** +** The output position list - written to *pp - is a copy of *pp2 with those +** entries that are not sufficiently NEAR entries in *pp1 removed. +*/ +static int fts3PoslistNearMerge( + char **pp, /* Output buffer */ + char *aTmp, /* Temporary buffer space */ + int nRight, /* Maximum difference in token positions */ + int nLeft, /* Maximum difference in token positions */ + char **pp1, /* IN/OUT: Left input list */ + char **pp2 /* IN/OUT: Right input list */ +){ + char *p1 = *pp1; + char *p2 = *pp2; + + char *pTmp1 = aTmp; + char *pTmp2; + char *aTmp2; + int res = 1; + + fts3PoslistPhraseMerge(&pTmp1, nRight, 0, 0, pp1, pp2); + aTmp2 = pTmp2 = pTmp1; + *pp1 = p1; + *pp2 = p2; + fts3PoslistPhraseMerge(&pTmp2, nLeft, 1, 0, pp2, pp1); + if( pTmp1!=aTmp && pTmp2!=aTmp2 ){ + fts3PoslistMerge(pp, &aTmp, &aTmp2); + }else if( pTmp1!=aTmp ){ + fts3PoslistCopy(pp, &aTmp); + }else if( pTmp2!=aTmp2 ){ + fts3PoslistCopy(pp, &aTmp2); + }else{ + res = 0; + } + + return res; +} + +/* +** An instance of this function is used to merge together the (potentially +** large number of) doclists for each term that matches a prefix query. +** See function fts3TermSelectMerge() for details. +*/ +typedef struct TermSelect TermSelect; +struct TermSelect { + char *aaOutput[16]; /* Malloc'd output buffers */ + int anOutput[16]; /* Size each output buffer in bytes */ +}; + +/* +** This function is used to read a single varint from a buffer. Parameter +** pEnd points 1 byte past the end of the buffer. When this function is +** called, if *pp points to pEnd or greater, then the end of the buffer +** has been reached. In this case *pp is set to 0 and the function returns. +** +** If *pp does not point to or past pEnd, then a single varint is read +** from *pp. *pp is then set to point 1 byte past the end of the read varint. +** +** If bDescIdx is false, the value read is added to *pVal before returning. +** If it is true, the value read is subtracted from *pVal before this +** function returns. +*/ +static void fts3GetDeltaVarint3( + char **pp, /* IN/OUT: Point to read varint from */ + char *pEnd, /* End of buffer */ + int bDescIdx, /* True if docids are descending */ + sqlite3_int64 *pVal /* IN/OUT: Integer value */ +){ + if( *pp>=pEnd ){ + *pp = 0; + }else{ + u64 iVal; + *pp += sqlite3Fts3GetVarintU(*pp, &iVal); + if( bDescIdx ){ + *pVal = (i64)((u64)*pVal - iVal); + }else{ + *pVal = (i64)((u64)*pVal + iVal); + } + } +} + +/* +** This function is used to write a single varint to a buffer. The varint +** is written to *pp. Before returning, *pp is set to point 1 byte past the +** end of the value written. +** +** If *pbFirst is zero when this function is called, the value written to +** the buffer is that of parameter iVal. +** +** If *pbFirst is non-zero when this function is called, then the value +** written is either (iVal-*piPrev) (if bDescIdx is zero) or (*piPrev-iVal) +** (if bDescIdx is non-zero). +** +** Before returning, this function always sets *pbFirst to 1 and *piPrev +** to the value of parameter iVal. +*/ +static void fts3PutDeltaVarint3( + char **pp, /* IN/OUT: Output pointer */ + int bDescIdx, /* True for descending docids */ + sqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */ + int *pbFirst, /* IN/OUT: True after first int written */ + sqlite3_int64 iVal /* Write this value to the list */ +){ + sqlite3_uint64 iWrite; + if( bDescIdx==0 || *pbFirst==0 ){ + assert_fts3_nc( *pbFirst==0 || iVal>=*piPrev ); + iWrite = (u64)iVal - (u64)*piPrev; + }else{ + assert_fts3_nc( *piPrev>=iVal ); + iWrite = (u64)*piPrev - (u64)iVal; + } + assert( *pbFirst || *piPrev==0 ); + assert_fts3_nc( *pbFirst==0 || iWrite>0 ); + *pp += sqlite3Fts3PutVarint(*pp, iWrite); + *piPrev = iVal; + *pbFirst = 1; +} + + +/* +** This macro is used by various functions that merge doclists. The two +** arguments are 64-bit docid values. If the value of the stack variable +** bDescDoclist is 0 when this macro is invoked, then it returns (i1-i2). +** Otherwise, (i2-i1). +** +** Using this makes it easier to write code that can merge doclists that are +** sorted in either ascending or descending order. +*/ +/* #define DOCID_CMP(i1, i2) ((bDescDoclist?-1:1) * (i64)((u64)i1-i2)) */ +#define DOCID_CMP(i1, i2) ((bDescDoclist?-1:1) * (i1>i2?1:((i1==i2)?0:-1))) + +/* +** This function does an "OR" merge of two doclists (output contains all +** positions contained in either argument doclist). If the docids in the +** input doclists are sorted in ascending order, parameter bDescDoclist +** should be false. If they are sorted in ascending order, it should be +** passed a non-zero value. +** +** If no error occurs, *paOut is set to point at an sqlite3_malloc'd buffer +** containing the output doclist and SQLITE_OK is returned. In this case +** *pnOut is set to the number of bytes in the output doclist. +** +** If an error occurs, an SQLite error code is returned. The output values +** are undefined in this case. +*/ +static int fts3DoclistOrMerge( + int bDescDoclist, /* True if arguments are desc */ + char *a1, int n1, /* First doclist */ + char *a2, int n2, /* Second doclist */ + char **paOut, int *pnOut /* OUT: Malloc'd doclist */ +){ + int rc = SQLITE_OK; + sqlite3_int64 i1 = 0; + sqlite3_int64 i2 = 0; + sqlite3_int64 iPrev = 0; + char *pEnd1 = &a1[n1]; + char *pEnd2 = &a2[n2]; + char *p1 = a1; + char *p2 = a2; + char *p; + char *aOut; + int bFirstOut = 0; + + *paOut = 0; + *pnOut = 0; + + /* Allocate space for the output. Both the input and output doclists + ** are delta encoded. If they are in ascending order (bDescDoclist==0), + ** then the first docid in each list is simply encoded as a varint. For + ** each subsequent docid, the varint stored is the difference between the + ** current and previous docid (a positive number - since the list is in + ** ascending order). + ** + ** The first docid written to the output is therefore encoded using the + ** same number of bytes as it is in whichever of the input lists it is + ** read from. And each subsequent docid read from the same input list + ** consumes either the same or less bytes as it did in the input (since + ** the difference between it and the previous value in the output must + ** be a positive value less than or equal to the delta value read from + ** the input list). The same argument applies to all but the first docid + ** read from the 'other' list. And to the contents of all position lists + ** that will be copied and merged from the input to the output. + ** + ** However, if the first docid copied to the output is a negative number, + ** then the encoding of the first docid from the 'other' input list may + ** be larger in the output than it was in the input (since the delta value + ** may be a larger positive integer than the actual docid). + ** + ** The space required to store the output is therefore the sum of the + ** sizes of the two inputs, plus enough space for exactly one of the input + ** docids to grow. + ** + ** A symetric argument may be made if the doclists are in descending + ** order. + */ + aOut = sqlite3_malloc64((i64)n1+n2+FTS3_VARINT_MAX-1+FTS3_BUFFER_PADDING); + if( !aOut ) return SQLITE_NOMEM; + + p = aOut; + fts3GetDeltaVarint3(&p1, pEnd1, 0, &i1); + fts3GetDeltaVarint3(&p2, pEnd2, 0, &i2); + while( p1 || p2 ){ + sqlite3_int64 iDiff = DOCID_CMP(i1, i2); + + if( p2 && p1 && iDiff==0 ){ + fts3PutDeltaVarint3(&p, bDescDoclist, &iPrev, &bFirstOut, i1); + rc = fts3PoslistMerge(&p, &p1, &p2); + if( rc ) break; + fts3GetDeltaVarint3(&p1, pEnd1, bDescDoclist, &i1); + fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2); + }else if( !p2 || (p1 && iDiff<0) ){ + fts3PutDeltaVarint3(&p, bDescDoclist, &iPrev, &bFirstOut, i1); + fts3PoslistCopy(&p, &p1); + fts3GetDeltaVarint3(&p1, pEnd1, bDescDoclist, &i1); + }else{ + fts3PutDeltaVarint3(&p, bDescDoclist, &iPrev, &bFirstOut, i2); + fts3PoslistCopy(&p, &p2); + fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2); + } + + assert( (p-aOut)<=((p1?(p1-a1):n1)+(p2?(p2-a2):n2)+FTS3_VARINT_MAX-1) ); + } + + if( rc!=SQLITE_OK ){ + sqlite3_free(aOut); + p = aOut = 0; + }else{ + assert( (p-aOut)<=n1+n2+FTS3_VARINT_MAX-1 ); + memset(&aOut[(p-aOut)], 0, FTS3_BUFFER_PADDING); + } + *paOut = aOut; + *pnOut = (int)(p-aOut); + return rc; +} + +/* +** This function does a "phrase" merge of two doclists. In a phrase merge, +** the output contains a copy of each position from the right-hand input +** doclist for which there is a position in the left-hand input doclist +** exactly nDist tokens before it. +** +** If the docids in the input doclists are sorted in ascending order, +** parameter bDescDoclist should be false. If they are sorted in ascending +** order, it should be passed a non-zero value. +** +** The right-hand input doclist is overwritten by this function. +*/ +static int fts3DoclistPhraseMerge( + int bDescDoclist, /* True if arguments are desc */ + int nDist, /* Distance from left to right (1=adjacent) */ + char *aLeft, int nLeft, /* Left doclist */ + char **paRight, int *pnRight /* IN/OUT: Right/output doclist */ +){ + sqlite3_int64 i1 = 0; + sqlite3_int64 i2 = 0; + sqlite3_int64 iPrev = 0; + char *aRight = *paRight; + char *pEnd1 = &aLeft[nLeft]; + char *pEnd2 = &aRight[*pnRight]; + char *p1 = aLeft; + char *p2 = aRight; + char *p; + int bFirstOut = 0; + char *aOut; + + assert( nDist>0 ); + if( bDescDoclist ){ + aOut = sqlite3_malloc64((sqlite3_int64)*pnRight + FTS3_VARINT_MAX); + if( aOut==0 ) return SQLITE_NOMEM; + }else{ + aOut = aRight; + } + p = aOut; + + fts3GetDeltaVarint3(&p1, pEnd1, 0, &i1); + fts3GetDeltaVarint3(&p2, pEnd2, 0, &i2); + + while( p1 && p2 ){ + sqlite3_int64 iDiff = DOCID_CMP(i1, i2); + if( iDiff==0 ){ + char *pSave = p; + sqlite3_int64 iPrevSave = iPrev; + int bFirstOutSave = bFirstOut; + + fts3PutDeltaVarint3(&p, bDescDoclist, &iPrev, &bFirstOut, i1); + if( 0==fts3PoslistPhraseMerge(&p, nDist, 0, 1, &p1, &p2) ){ + p = pSave; + iPrev = iPrevSave; + bFirstOut = bFirstOutSave; + } + fts3GetDeltaVarint3(&p1, pEnd1, bDescDoclist, &i1); + fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2); + }else if( iDiff<0 ){ + fts3PoslistCopy(0, &p1); + fts3GetDeltaVarint3(&p1, pEnd1, bDescDoclist, &i1); + }else{ + fts3PoslistCopy(0, &p2); + fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2); + } + } + + *pnRight = (int)(p - aOut); + if( bDescDoclist ){ + sqlite3_free(aRight); + *paRight = aOut; + } + + return SQLITE_OK; +} + +/* +** Argument pList points to a position list nList bytes in size. This +** function checks to see if the position list contains any entries for +** a token in position 0 (of any column). If so, it writes argument iDelta +** to the output buffer pOut, followed by a position list consisting only +** of the entries from pList at position 0, and terminated by an 0x00 byte. +** The value returned is the number of bytes written to pOut (if any). +*/ +SQLITE_PRIVATE int sqlite3Fts3FirstFilter( + sqlite3_int64 iDelta, /* Varint that may be written to pOut */ + char *pList, /* Position list (no 0x00 term) */ + int nList, /* Size of pList in bytes */ + char *pOut /* Write output here */ +){ + int nOut = 0; + int bWritten = 0; /* True once iDelta has been written */ + char *p = pList; + char *pEnd = &pList[nList]; + + if( *p!=0x01 ){ + if( *p==0x02 ){ + nOut += sqlite3Fts3PutVarint(&pOut[nOut], iDelta); + pOut[nOut++] = 0x02; + bWritten = 1; + } + fts3ColumnlistCopy(0, &p); + } + + while( p<pEnd ){ + sqlite3_int64 iCol; + p++; + p += sqlite3Fts3GetVarint(p, &iCol); + if( *p==0x02 ){ + if( bWritten==0 ){ + nOut += sqlite3Fts3PutVarint(&pOut[nOut], iDelta); + bWritten = 1; + } + pOut[nOut++] = 0x01; + nOut += sqlite3Fts3PutVarint(&pOut[nOut], iCol); + pOut[nOut++] = 0x02; + } + fts3ColumnlistCopy(0, &p); + } + if( bWritten ){ + pOut[nOut++] = 0x00; + } + + return nOut; +} + + +/* +** Merge all doclists in the TermSelect.aaOutput[] array into a single +** doclist stored in TermSelect.aaOutput[0]. If successful, delete all +** other doclists (except the aaOutput[0] one) and return SQLITE_OK. +** +** If an OOM error occurs, return SQLITE_NOMEM. In this case it is +** the responsibility of the caller to free any doclists left in the +** TermSelect.aaOutput[] array. +*/ +static int fts3TermSelectFinishMerge(Fts3Table *p, TermSelect *pTS){ + char *aOut = 0; + int nOut = 0; + int i; + + /* Loop through the doclists in the aaOutput[] array. Merge them all + ** into a single doclist. + */ + for(i=0; i<SizeofArray(pTS->aaOutput); i++){ + if( pTS->aaOutput[i] ){ + if( !aOut ){ + aOut = pTS->aaOutput[i]; + nOut = pTS->anOutput[i]; + pTS->aaOutput[i] = 0; + }else{ + int nNew; + char *aNew; + + int rc = fts3DoclistOrMerge(p->bDescIdx, + pTS->aaOutput[i], pTS->anOutput[i], aOut, nOut, &aNew, &nNew + ); + if( rc!=SQLITE_OK ){ + sqlite3_free(aOut); + return rc; + } + + sqlite3_free(pTS->aaOutput[i]); + sqlite3_free(aOut); + pTS->aaOutput[i] = 0; + aOut = aNew; + nOut = nNew; + } + } + } + + pTS->aaOutput[0] = aOut; + pTS->anOutput[0] = nOut; + return SQLITE_OK; +} + +/* +** Merge the doclist aDoclist/nDoclist into the TermSelect object passed +** as the first argument. The merge is an "OR" merge (see function +** fts3DoclistOrMerge() for details). +** +** This function is called with the doclist for each term that matches +** a queried prefix. It merges all these doclists into one, the doclist +** for the specified prefix. Since there can be a very large number of +** doclists to merge, the merging is done pair-wise using the TermSelect +** object. +** +** This function returns SQLITE_OK if the merge is successful, or an +** SQLite error code (SQLITE_NOMEM) if an error occurs. +*/ +static int fts3TermSelectMerge( + Fts3Table *p, /* FTS table handle */ + TermSelect *pTS, /* TermSelect object to merge into */ + char *aDoclist, /* Pointer to doclist */ + int nDoclist /* Size of aDoclist in bytes */ +){ + if( pTS->aaOutput[0]==0 ){ + /* If this is the first term selected, copy the doclist to the output + ** buffer using memcpy(). + ** + ** Add FTS3_VARINT_MAX bytes of unused space to the end of the + ** allocation. This is so as to ensure that the buffer is big enough + ** to hold the current doclist AND'd with any other doclist. If the + ** doclists are stored in order=ASC order, this padding would not be + ** required (since the size of [doclistA AND doclistB] is always less + ** than or equal to the size of [doclistA] in that case). But this is + ** not true for order=DESC. For example, a doclist containing (1, -1) + ** may be smaller than (-1), as in the first example the -1 may be stored + ** as a single-byte delta, whereas in the second it must be stored as a + ** FTS3_VARINT_MAX byte varint. + ** + ** Similar padding is added in the fts3DoclistOrMerge() function. + */ + pTS->aaOutput[0] = sqlite3_malloc64((i64)nDoclist + FTS3_VARINT_MAX + 1); + pTS->anOutput[0] = nDoclist; + if( pTS->aaOutput[0] ){ + memcpy(pTS->aaOutput[0], aDoclist, nDoclist); + memset(&pTS->aaOutput[0][nDoclist], 0, FTS3_VARINT_MAX); + }else{ + return SQLITE_NOMEM; + } + }else{ + char *aMerge = aDoclist; + int nMerge = nDoclist; + int iOut; + + for(iOut=0; iOut<SizeofArray(pTS->aaOutput); iOut++){ + if( pTS->aaOutput[iOut]==0 ){ + assert( iOut>0 ); + pTS->aaOutput[iOut] = aMerge; + pTS->anOutput[iOut] = nMerge; + break; + }else{ + char *aNew; + int nNew; + + int rc = fts3DoclistOrMerge(p->bDescIdx, aMerge, nMerge, + pTS->aaOutput[iOut], pTS->anOutput[iOut], &aNew, &nNew + ); + if( rc!=SQLITE_OK ){ + if( aMerge!=aDoclist ) sqlite3_free(aMerge); + return rc; + } + + if( aMerge!=aDoclist ) sqlite3_free(aMerge); + sqlite3_free(pTS->aaOutput[iOut]); + pTS->aaOutput[iOut] = 0; + + aMerge = aNew; + nMerge = nNew; + if( (iOut+1)==SizeofArray(pTS->aaOutput) ){ + pTS->aaOutput[iOut] = aMerge; + pTS->anOutput[iOut] = nMerge; + } + } + } + } + return SQLITE_OK; +} + +/* +** Append SegReader object pNew to the end of the pCsr->apSegment[] array. +*/ +static int fts3SegReaderCursorAppend( + Fts3MultiSegReader *pCsr, + Fts3SegReader *pNew +){ + if( (pCsr->nSegment%16)==0 ){ + Fts3SegReader **apNew; + sqlite3_int64 nByte = (pCsr->nSegment + 16)*sizeof(Fts3SegReader*); + apNew = (Fts3SegReader **)sqlite3_realloc64(pCsr->apSegment, nByte); + if( !apNew ){ + sqlite3Fts3SegReaderFree(pNew); + return SQLITE_NOMEM; + } + pCsr->apSegment = apNew; + } + pCsr->apSegment[pCsr->nSegment++] = pNew; + return SQLITE_OK; +} + +/* +** Add seg-reader objects to the Fts3MultiSegReader object passed as the +** 8th argument. +** +** This function returns SQLITE_OK if successful, or an SQLite error code +** otherwise. +*/ +static int fts3SegReaderCursor( + Fts3Table *p, /* FTS3 table handle */ + int iLangid, /* Language id */ + int iIndex, /* Index to search (from 0 to p->nIndex-1) */ + int iLevel, /* Level of segments to scan */ + const char *zTerm, /* Term to query for */ + int nTerm, /* Size of zTerm in bytes */ + int isPrefix, /* True for a prefix search */ + int isScan, /* True to scan from zTerm to EOF */ + Fts3MultiSegReader *pCsr /* Cursor object to populate */ +){ + int rc = SQLITE_OK; /* Error code */ + sqlite3_stmt *pStmt = 0; /* Statement to iterate through segments */ + int rc2; /* Result of sqlite3_reset() */ + + /* If iLevel is less than 0 and this is not a scan, include a seg-reader + ** for the pending-terms. If this is a scan, then this call must be being + ** made by an fts4aux module, not an FTS table. In this case calling + ** Fts3SegReaderPending might segfault, as the data structures used by + ** fts4aux are not completely populated. So it's easiest to filter these + ** calls out here. */ + if( iLevel<0 && p->aIndex && p->iPrevLangid==iLangid ){ + Fts3SegReader *pSeg = 0; + rc = sqlite3Fts3SegReaderPending(p, iIndex, zTerm, nTerm, isPrefix||isScan, &pSeg); + if( rc==SQLITE_OK && pSeg ){ + rc = fts3SegReaderCursorAppend(pCsr, pSeg); + } + } + + if( iLevel!=FTS3_SEGCURSOR_PENDING ){ + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3AllSegdirs(p, iLangid, iIndex, iLevel, &pStmt); + } + + while( rc==SQLITE_OK && SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){ + Fts3SegReader *pSeg = 0; + + /* Read the values returned by the SELECT into local variables. */ + sqlite3_int64 iStartBlock = sqlite3_column_int64(pStmt, 1); + sqlite3_int64 iLeavesEndBlock = sqlite3_column_int64(pStmt, 2); + sqlite3_int64 iEndBlock = sqlite3_column_int64(pStmt, 3); + int nRoot = sqlite3_column_bytes(pStmt, 4); + char const *zRoot = sqlite3_column_blob(pStmt, 4); + + /* If zTerm is not NULL, and this segment is not stored entirely on its + ** root node, the range of leaves scanned can be reduced. Do this. */ + if( iStartBlock && zTerm && zRoot ){ + sqlite3_int64 *pi = (isPrefix ? &iLeavesEndBlock : 0); + rc = fts3SelectLeaf(p, zTerm, nTerm, zRoot, nRoot, &iStartBlock, pi); + if( rc!=SQLITE_OK ) goto finished; + if( isPrefix==0 && isScan==0 ) iLeavesEndBlock = iStartBlock; + } + + rc = sqlite3Fts3SegReaderNew(pCsr->nSegment+1, + (isPrefix==0 && isScan==0), + iStartBlock, iLeavesEndBlock, + iEndBlock, zRoot, nRoot, &pSeg + ); + if( rc!=SQLITE_OK ) goto finished; + rc = fts3SegReaderCursorAppend(pCsr, pSeg); + } + } + + finished: + rc2 = sqlite3_reset(pStmt); + if( rc==SQLITE_DONE ) rc = rc2; + + return rc; +} + +/* +** Set up a cursor object for iterating through a full-text index or a +** single level therein. +*/ +SQLITE_PRIVATE int sqlite3Fts3SegReaderCursor( + Fts3Table *p, /* FTS3 table handle */ + int iLangid, /* Language-id to search */ + int iIndex, /* Index to search (from 0 to p->nIndex-1) */ + int iLevel, /* Level of segments to scan */ + const char *zTerm, /* Term to query for */ + int nTerm, /* Size of zTerm in bytes */ + int isPrefix, /* True for a prefix search */ + int isScan, /* True to scan from zTerm to EOF */ + Fts3MultiSegReader *pCsr /* Cursor object to populate */ +){ + assert( iIndex>=0 && iIndex<p->nIndex ); + assert( iLevel==FTS3_SEGCURSOR_ALL + || iLevel==FTS3_SEGCURSOR_PENDING + || iLevel>=0 + ); + assert( iLevel<FTS3_SEGDIR_MAXLEVEL ); + assert( FTS3_SEGCURSOR_ALL<0 && FTS3_SEGCURSOR_PENDING<0 ); + assert( isPrefix==0 || isScan==0 ); + + memset(pCsr, 0, sizeof(Fts3MultiSegReader)); + return fts3SegReaderCursor( + p, iLangid, iIndex, iLevel, zTerm, nTerm, isPrefix, isScan, pCsr + ); +} + +/* +** In addition to its current configuration, have the Fts3MultiSegReader +** passed as the 4th argument also scan the doclist for term zTerm/nTerm. +** +** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code. +*/ +static int fts3SegReaderCursorAddZero( + Fts3Table *p, /* FTS virtual table handle */ + int iLangid, + const char *zTerm, /* Term to scan doclist of */ + int nTerm, /* Number of bytes in zTerm */ + Fts3MultiSegReader *pCsr /* Fts3MultiSegReader to modify */ +){ + return fts3SegReaderCursor(p, + iLangid, 0, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 0, 0,pCsr + ); +} + +/* +** Open an Fts3MultiSegReader to scan the doclist for term zTerm/nTerm. Or, +** if isPrefix is true, to scan the doclist for all terms for which +** zTerm/nTerm is a prefix. If successful, return SQLITE_OK and write +** a pointer to the new Fts3MultiSegReader to *ppSegcsr. Otherwise, return +** an SQLite error code. +** +** It is the responsibility of the caller to free this object by eventually +** passing it to fts3SegReaderCursorFree() +** +** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code. +** Output parameter *ppSegcsr is set to 0 if an error occurs. +*/ +static int fts3TermSegReaderCursor( + Fts3Cursor *pCsr, /* Virtual table cursor handle */ + const char *zTerm, /* Term to query for */ + int nTerm, /* Size of zTerm in bytes */ + int isPrefix, /* True for a prefix search */ + Fts3MultiSegReader **ppSegcsr /* OUT: Allocated seg-reader cursor */ +){ + Fts3MultiSegReader *pSegcsr; /* Object to allocate and return */ + int rc = SQLITE_NOMEM; /* Return code */ + + pSegcsr = sqlite3_malloc(sizeof(Fts3MultiSegReader)); + if( pSegcsr ){ + int i; + int bFound = 0; /* True once an index has been found */ + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; + + if( isPrefix ){ + for(i=1; bFound==0 && i<p->nIndex; i++){ + if( p->aIndex[i].nPrefix==nTerm ){ + bFound = 1; + rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, + i, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 0, 0, pSegcsr + ); + pSegcsr->bLookup = 1; + } + } + + for(i=1; bFound==0 && i<p->nIndex; i++){ + if( p->aIndex[i].nPrefix==nTerm+1 ){ + bFound = 1; + rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, + i, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 1, 0, pSegcsr + ); + if( rc==SQLITE_OK ){ + rc = fts3SegReaderCursorAddZero( + p, pCsr->iLangid, zTerm, nTerm, pSegcsr + ); + } + } + } + } + + if( bFound==0 ){ + rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, + 0, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, 0, pSegcsr + ); + pSegcsr->bLookup = !isPrefix; + } + } + + *ppSegcsr = pSegcsr; + return rc; +} + +/* +** Free an Fts3MultiSegReader allocated by fts3TermSegReaderCursor(). +*/ +static void fts3SegReaderCursorFree(Fts3MultiSegReader *pSegcsr){ + sqlite3Fts3SegReaderFinish(pSegcsr); + sqlite3_free(pSegcsr); +} + +/* +** This function retrieves the doclist for the specified term (or term +** prefix) from the database. +*/ +static int fts3TermSelect( + Fts3Table *p, /* Virtual table handle */ + Fts3PhraseToken *pTok, /* Token to query for */ + int iColumn, /* Column to query (or -ve for all columns) */ + int *pnOut, /* OUT: Size of buffer at *ppOut */ + char **ppOut /* OUT: Malloced result buffer */ +){ + int rc; /* Return code */ + Fts3MultiSegReader *pSegcsr; /* Seg-reader cursor for this term */ + TermSelect tsc; /* Object for pair-wise doclist merging */ + Fts3SegFilter filter; /* Segment term filter configuration */ + + pSegcsr = pTok->pSegcsr; + memset(&tsc, 0, sizeof(TermSelect)); + + filter.flags = FTS3_SEGMENT_IGNORE_EMPTY | FTS3_SEGMENT_REQUIRE_POS + | (pTok->isPrefix ? FTS3_SEGMENT_PREFIX : 0) + | (pTok->bFirst ? FTS3_SEGMENT_FIRST : 0) + | (iColumn<p->nColumn ? FTS3_SEGMENT_COLUMN_FILTER : 0); + filter.iCol = iColumn; + filter.zTerm = pTok->z; + filter.nTerm = pTok->n; + + rc = sqlite3Fts3SegReaderStart(p, pSegcsr, &filter); + while( SQLITE_OK==rc + && SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, pSegcsr)) + ){ + rc = fts3TermSelectMerge(p, &tsc, pSegcsr->aDoclist, pSegcsr->nDoclist); + } + + if( rc==SQLITE_OK ){ + rc = fts3TermSelectFinishMerge(p, &tsc); + } + if( rc==SQLITE_OK ){ + *ppOut = tsc.aaOutput[0]; + *pnOut = tsc.anOutput[0]; + }else{ + int i; + for(i=0; i<SizeofArray(tsc.aaOutput); i++){ + sqlite3_free(tsc.aaOutput[i]); + } + } + + fts3SegReaderCursorFree(pSegcsr); + pTok->pSegcsr = 0; + return rc; +} + +/* +** This function counts the total number of docids in the doclist stored +** in buffer aList[], size nList bytes. +** +** If the isPoslist argument is true, then it is assumed that the doclist +** contains a position-list following each docid. Otherwise, it is assumed +** that the doclist is simply a list of docids stored as delta encoded +** varints. +*/ +static int fts3DoclistCountDocids(char *aList, int nList){ + int nDoc = 0; /* Return value */ + if( aList ){ + char *aEnd = &aList[nList]; /* Pointer to one byte after EOF */ + char *p = aList; /* Cursor */ + while( p<aEnd ){ + nDoc++; + while( (*p++)&0x80 ); /* Skip docid varint */ + fts3PoslistCopy(0, &p); /* Skip over position list */ + } + } + + return nDoc; +} + +/* +** Advance the cursor to the next row in the %_content table that +** matches the search criteria. For a MATCH search, this will be +** the next row that matches. For a full-table scan, this will be +** simply the next row in the %_content table. For a docid lookup, +** this routine simply sets the EOF flag. +** +** Return SQLITE_OK if nothing goes wrong. SQLITE_OK is returned +** even if we reach end-of-file. The fts3EofMethod() will be called +** subsequently to determine whether or not an EOF was hit. +*/ +static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){ + int rc; + Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; + if( pCsr->eSearch==FTS3_DOCID_SEARCH || pCsr->eSearch==FTS3_FULLSCAN_SEARCH ){ + Fts3Table *pTab = (Fts3Table*)pCursor->pVtab; + pTab->bLock++; + if( SQLITE_ROW!=sqlite3_step(pCsr->pStmt) ){ + pCsr->isEof = 1; + rc = sqlite3_reset(pCsr->pStmt); + }else{ + pCsr->iPrevId = sqlite3_column_int64(pCsr->pStmt, 0); + rc = SQLITE_OK; + } + pTab->bLock--; + }else{ + rc = fts3EvalNext((Fts3Cursor *)pCursor); + } + assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); + return rc; +} + +/* +** If the numeric type of argument pVal is "integer", then return it +** converted to a 64-bit signed integer. Otherwise, return a copy of +** the second parameter, iDefault. +*/ +static sqlite3_int64 fts3DocidRange(sqlite3_value *pVal, i64 iDefault){ + if( pVal ){ + int eType = sqlite3_value_numeric_type(pVal); + if( eType==SQLITE_INTEGER ){ + return sqlite3_value_int64(pVal); + } + } + return iDefault; +} + +/* +** This is the xFilter interface for the virtual table. See +** the virtual table xFilter method documentation for additional +** information. +** +** If idxNum==FTS3_FULLSCAN_SEARCH then do a full table scan against +** the %_content table. +** +** If idxNum==FTS3_DOCID_SEARCH then do a docid lookup for a single entry +** in the %_content table. +** +** If idxNum>=FTS3_FULLTEXT_SEARCH then use the full text index. The +** column on the left-hand side of the MATCH operator is column +** number idxNum-FTS3_FULLTEXT_SEARCH, 0 indexed. argv[0] is the right-hand +** side of the MATCH operator. +*/ +static int fts3FilterMethod( + sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ + int idxNum, /* Strategy index */ + const char *idxStr, /* Unused */ + int nVal, /* Number of elements in apVal */ + sqlite3_value **apVal /* Arguments for the indexing scheme */ +){ + int rc = SQLITE_OK; + char *zSql; /* SQL statement used to access %_content */ + int eSearch; + Fts3Table *p = (Fts3Table *)pCursor->pVtab; + Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; + + sqlite3_value *pCons = 0; /* The MATCH or rowid constraint, if any */ + sqlite3_value *pLangid = 0; /* The "langid = ?" constraint, if any */ + sqlite3_value *pDocidGe = 0; /* The "docid >= ?" constraint, if any */ + sqlite3_value *pDocidLe = 0; /* The "docid <= ?" constraint, if any */ + int iIdx; + + UNUSED_PARAMETER(idxStr); + UNUSED_PARAMETER(nVal); + + if( p->bLock ){ + return SQLITE_ERROR; + } + + eSearch = (idxNum & 0x0000FFFF); + assert( eSearch>=0 && eSearch<=(FTS3_FULLTEXT_SEARCH+p->nColumn) ); + assert( p->pSegments==0 ); + + /* Collect arguments into local variables */ + iIdx = 0; + if( eSearch!=FTS3_FULLSCAN_SEARCH ) pCons = apVal[iIdx++]; + if( idxNum & FTS3_HAVE_LANGID ) pLangid = apVal[iIdx++]; + if( idxNum & FTS3_HAVE_DOCID_GE ) pDocidGe = apVal[iIdx++]; + if( idxNum & FTS3_HAVE_DOCID_LE ) pDocidLe = apVal[iIdx++]; + assert( iIdx==nVal ); + + /* In case the cursor has been used before, clear it now. */ + fts3ClearCursor(pCsr); + + /* Set the lower and upper bounds on docids to return */ + pCsr->iMinDocid = fts3DocidRange(pDocidGe, SMALLEST_INT64); + pCsr->iMaxDocid = fts3DocidRange(pDocidLe, LARGEST_INT64); + + if( idxStr ){ + pCsr->bDesc = (idxStr[0]=='D'); + }else{ + pCsr->bDesc = p->bDescIdx; + } + pCsr->eSearch = (i16)eSearch; + + if( eSearch!=FTS3_DOCID_SEARCH && eSearch!=FTS3_FULLSCAN_SEARCH ){ + int iCol = eSearch-FTS3_FULLTEXT_SEARCH; + const char *zQuery = (const char *)sqlite3_value_text(pCons); + + if( zQuery==0 && sqlite3_value_type(pCons)!=SQLITE_NULL ){ + return SQLITE_NOMEM; + } + + pCsr->iLangid = 0; + if( pLangid ) pCsr->iLangid = sqlite3_value_int(pLangid); + + assert( p->base.zErrMsg==0 ); + rc = sqlite3Fts3ExprParse(p->pTokenizer, pCsr->iLangid, + p->azColumn, p->bFts4, p->nColumn, iCol, zQuery, -1, &pCsr->pExpr, + &p->base.zErrMsg + ); + if( rc!=SQLITE_OK ){ + return rc; + } + + rc = fts3EvalStart(pCsr); + sqlite3Fts3SegmentsClose(p); + if( rc!=SQLITE_OK ) return rc; + pCsr->pNextId = pCsr->aDoclist; + pCsr->iPrevId = 0; + } + + /* Compile a SELECT statement for this cursor. For a full-table-scan, the + ** statement loops through all rows of the %_content table. For a + ** full-text query or docid lookup, the statement retrieves a single + ** row by docid. + */ + if( eSearch==FTS3_FULLSCAN_SEARCH ){ + if( pDocidGe || pDocidLe ){ + zSql = sqlite3_mprintf( + "SELECT %s WHERE rowid BETWEEN %lld AND %lld ORDER BY rowid %s", + p->zReadExprlist, pCsr->iMinDocid, pCsr->iMaxDocid, + (pCsr->bDesc ? "DESC" : "ASC") + ); + }else{ + zSql = sqlite3_mprintf("SELECT %s ORDER BY rowid %s", + p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC") + ); + } + if( zSql ){ + p->bLock++; + rc = sqlite3_prepare_v3( + p->db,zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0 + ); + p->bLock--; + sqlite3_free(zSql); + }else{ + rc = SQLITE_NOMEM; + } + }else if( eSearch==FTS3_DOCID_SEARCH ){ + rc = fts3CursorSeekStmt(pCsr); + if( rc==SQLITE_OK ){ + rc = sqlite3_bind_value(pCsr->pStmt, 1, pCons); + } + } + if( rc!=SQLITE_OK ) return rc; + + return fts3NextMethod(pCursor); +} + +/* +** This is the xEof method of the virtual table. SQLite calls this +** routine to find out if it has reached the end of a result set. +*/ +static int fts3EofMethod(sqlite3_vtab_cursor *pCursor){ + Fts3Cursor *pCsr = (Fts3Cursor*)pCursor; + if( pCsr->isEof ){ + fts3ClearCursor(pCsr); + pCsr->isEof = 1; + } + return pCsr->isEof; +} + +/* +** This is the xRowid method. The SQLite core calls this routine to +** retrieve the rowid for the current row of the result set. fts3 +** exposes %_content.docid as the rowid for the virtual table. The +** rowid should be written to *pRowid. +*/ +static int fts3RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ + Fts3Cursor *pCsr = (Fts3Cursor *) pCursor; + *pRowid = pCsr->iPrevId; + return SQLITE_OK; +} + +/* +** This is the xColumn method, called by SQLite to request a value from +** the row that the supplied cursor currently points to. +** +** If: +** +** (iCol < p->nColumn) -> The value of the iCol'th user column. +** (iCol == p->nColumn) -> Magic column with the same name as the table. +** (iCol == p->nColumn+1) -> Docid column +** (iCol == p->nColumn+2) -> Langid column +*/ +static int fts3ColumnMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ + int iCol /* Index of column to read value from */ +){ + int rc = SQLITE_OK; /* Return Code */ + Fts3Cursor *pCsr = (Fts3Cursor *) pCursor; + Fts3Table *p = (Fts3Table *)pCursor->pVtab; + + /* The column value supplied by SQLite must be in range. */ + assert( iCol>=0 && iCol<=p->nColumn+2 ); + + switch( iCol-p->nColumn ){ + case 0: + /* The special 'table-name' column */ + sqlite3_result_pointer(pCtx, pCsr, "fts3cursor", 0); + break; + + case 1: + /* The docid column */ + sqlite3_result_int64(pCtx, pCsr->iPrevId); + break; + + case 2: + if( pCsr->pExpr ){ + sqlite3_result_int64(pCtx, pCsr->iLangid); + break; + }else if( p->zLanguageid==0 ){ + sqlite3_result_int(pCtx, 0); + break; + }else{ + iCol = p->nColumn; + /* no break */ deliberate_fall_through + } + + default: + /* A user column. Or, if this is a full-table scan, possibly the + ** language-id column. Seek the cursor. */ + rc = fts3CursorSeek(0, pCsr); + if( rc==SQLITE_OK && sqlite3_data_count(pCsr->pStmt)-1>iCol ){ + sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); + } + break; + } + + assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); + return rc; +} + +/* +** This function is the implementation of the xUpdate callback used by +** FTS3 virtual tables. It is invoked by SQLite each time a row is to be +** inserted, updated or deleted. +*/ +static int fts3UpdateMethod( + sqlite3_vtab *pVtab, /* Virtual table handle */ + int nArg, /* Size of argument array */ + sqlite3_value **apVal, /* Array of arguments */ + sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */ +){ + return sqlite3Fts3UpdateMethod(pVtab, nArg, apVal, pRowid); +} + +/* +** Implementation of xSync() method. Flush the contents of the pending-terms +** hash-table to the database. +*/ +static int fts3SyncMethod(sqlite3_vtab *pVtab){ + + /* Following an incremental-merge operation, assuming that the input + ** segments are not completely consumed (the usual case), they are updated + ** in place to remove the entries that have already been merged. This + ** involves updating the leaf block that contains the smallest unmerged + ** entry and each block (if any) between the leaf and the root node. So + ** if the height of the input segment b-trees is N, and input segments + ** are merged eight at a time, updating the input segments at the end + ** of an incremental-merge requires writing (8*(1+N)) blocks. N is usually + ** small - often between 0 and 2. So the overhead of the incremental + ** merge is somewhere between 8 and 24 blocks. To avoid this overhead + ** dwarfing the actual productive work accomplished, the incremental merge + ** is only attempted if it will write at least 64 leaf blocks. Hence + ** nMinMerge. + ** + ** Of course, updating the input segments also involves deleting a bunch + ** of blocks from the segments table. But this is not considered overhead + ** as it would also be required by a crisis-merge that used the same input + ** segments. + */ + const u32 nMinMerge = 64; /* Minimum amount of incr-merge work to do */ + + Fts3Table *p = (Fts3Table*)pVtab; + int rc; + i64 iLastRowid = sqlite3_last_insert_rowid(p->db); + + rc = sqlite3Fts3PendingTermsFlush(p); + if( rc==SQLITE_OK + && p->nLeafAdd>(nMinMerge/16) + && p->nAutoincrmerge && p->nAutoincrmerge!=0xff + ){ + int mxLevel = 0; /* Maximum relative level value in db */ + int A; /* Incr-merge parameter A */ + + rc = sqlite3Fts3MaxLevel(p, &mxLevel); + assert( rc==SQLITE_OK || mxLevel==0 ); + A = p->nLeafAdd * mxLevel; + A += (A/2); + if( A>(int)nMinMerge ) rc = sqlite3Fts3Incrmerge(p, A, p->nAutoincrmerge); + } + sqlite3Fts3SegmentsClose(p); + sqlite3_set_last_insert_rowid(p->db, iLastRowid); + return rc; +} + +/* +** If it is currently unknown whether or not the FTS table has an %_stat +** table (if p->bHasStat==2), attempt to determine this (set p->bHasStat +** to 0 or 1). Return SQLITE_OK if successful, or an SQLite error code +** if an error occurs. +*/ +static int fts3SetHasStat(Fts3Table *p){ + int rc = SQLITE_OK; + if( p->bHasStat==2 ){ + char *zTbl = sqlite3_mprintf("%s_stat", p->zName); + if( zTbl ){ + int res = sqlite3_table_column_metadata(p->db, p->zDb, zTbl, 0,0,0,0,0,0); + sqlite3_free(zTbl); + p->bHasStat = (res==SQLITE_OK); + }else{ + rc = SQLITE_NOMEM; + } + } + return rc; +} + +/* +** Implementation of xBegin() method. +*/ +static int fts3BeginMethod(sqlite3_vtab *pVtab){ + Fts3Table *p = (Fts3Table*)pVtab; + int rc; + UNUSED_PARAMETER(pVtab); + assert( p->pSegments==0 ); + assert( p->nPendingData==0 ); + assert( p->inTransaction!=1 ); + p->nLeafAdd = 0; + rc = fts3SetHasStat(p); +#ifdef SQLITE_DEBUG + if( rc==SQLITE_OK ){ + p->inTransaction = 1; + p->mxSavepoint = -1; + } +#endif + return rc; +} + +/* +** Implementation of xCommit() method. This is a no-op. The contents of +** the pending-terms hash-table have already been flushed into the database +** by fts3SyncMethod(). +*/ +static int fts3CommitMethod(sqlite3_vtab *pVtab){ + TESTONLY( Fts3Table *p = (Fts3Table*)pVtab ); + UNUSED_PARAMETER(pVtab); + assert( p->nPendingData==0 ); + assert( p->inTransaction!=0 ); + assert( p->pSegments==0 ); + TESTONLY( p->inTransaction = 0 ); + TESTONLY( p->mxSavepoint = -1; ); + return SQLITE_OK; +} + +/* +** Implementation of xRollback(). Discard the contents of the pending-terms +** hash-table. Any changes made to the database are reverted by SQLite. +*/ +static int fts3RollbackMethod(sqlite3_vtab *pVtab){ + Fts3Table *p = (Fts3Table*)pVtab; + sqlite3Fts3PendingTermsClear(p); + assert( p->inTransaction!=0 ); + TESTONLY( p->inTransaction = 0 ); + TESTONLY( p->mxSavepoint = -1; ); + return SQLITE_OK; +} + +/* +** When called, *ppPoslist must point to the byte immediately following the +** end of a position-list. i.e. ( (*ppPoslist)[-1]==POS_END ). This function +** moves *ppPoslist so that it instead points to the first byte of the +** same position list. +*/ +static void fts3ReversePoslist(char *pStart, char **ppPoslist){ + char *p = &(*ppPoslist)[-2]; + char c = 0; + + /* Skip backwards passed any trailing 0x00 bytes added by NearTrim() */ + while( p>pStart && (c=*p--)==0 ); + + /* Search backwards for a varint with value zero (the end of the previous + ** poslist). This is an 0x00 byte preceded by some byte that does not + ** have the 0x80 bit set. */ + while( p>pStart && (*p & 0x80) | c ){ + c = *p--; + } + assert( p==pStart || c==0 ); + + /* At this point p points to that preceding byte without the 0x80 bit + ** set. So to find the start of the poslist, skip forward 2 bytes then + ** over a varint. + ** + ** Normally. The other case is that p==pStart and the poslist to return + ** is the first in the doclist. In this case do not skip forward 2 bytes. + ** The second part of the if condition (c==0 && *ppPoslist>&p[2]) + ** is required for cases where the first byte of a doclist and the + ** doclist is empty. For example, if the first docid is 10, a doclist + ** that begins with: + ** + ** 0x0A 0x00 <next docid delta varint> + */ + if( p>pStart || (c==0 && *ppPoslist>&p[2]) ){ p = &p[2]; } + while( *p++&0x80 ); + *ppPoslist = p; +} + +/* +** Helper function used by the implementation of the overloaded snippet(), +** offsets() and optimize() SQL functions. +** +** If the value passed as the third argument is a blob of size +** sizeof(Fts3Cursor*), then the blob contents are copied to the +** output variable *ppCsr and SQLITE_OK is returned. Otherwise, an error +** message is written to context pContext and SQLITE_ERROR returned. The +** string passed via zFunc is used as part of the error message. +*/ +static int fts3FunctionArg( + sqlite3_context *pContext, /* SQL function call context */ + const char *zFunc, /* Function name */ + sqlite3_value *pVal, /* argv[0] passed to function */ + Fts3Cursor **ppCsr /* OUT: Store cursor handle here */ +){ + int rc; + *ppCsr = (Fts3Cursor*)sqlite3_value_pointer(pVal, "fts3cursor"); + if( (*ppCsr)!=0 ){ + rc = SQLITE_OK; + }else{ + char *zErr = sqlite3_mprintf("illegal first argument to %s", zFunc); + sqlite3_result_error(pContext, zErr, -1); + sqlite3_free(zErr); + rc = SQLITE_ERROR; + } + return rc; +} + +/* +** Implementation of the snippet() function for FTS3 +*/ +static void fts3SnippetFunc( + sqlite3_context *pContext, /* SQLite function call context */ + int nVal, /* Size of apVal[] array */ + sqlite3_value **apVal /* Array of arguments */ +){ + Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */ + const char *zStart = "<b>"; + const char *zEnd = "</b>"; + const char *zEllipsis = "<b>...</b>"; + int iCol = -1; + int nToken = 15; /* Default number of tokens in snippet */ + + /* There must be at least one argument passed to this function (otherwise + ** the non-overloaded version would have been called instead of this one). + */ + assert( nVal>=1 ); + + if( nVal>6 ){ + sqlite3_result_error(pContext, + "wrong number of arguments to function snippet()", -1); + return; + } + if( fts3FunctionArg(pContext, "snippet", apVal[0], &pCsr) ) return; + + switch( nVal ){ + case 6: nToken = sqlite3_value_int(apVal[5]); + /* no break */ deliberate_fall_through + case 5: iCol = sqlite3_value_int(apVal[4]); + /* no break */ deliberate_fall_through + case 4: zEllipsis = (const char*)sqlite3_value_text(apVal[3]); + /* no break */ deliberate_fall_through + case 3: zEnd = (const char*)sqlite3_value_text(apVal[2]); + /* no break */ deliberate_fall_through + case 2: zStart = (const char*)sqlite3_value_text(apVal[1]); + } + if( !zEllipsis || !zEnd || !zStart ){ + sqlite3_result_error_nomem(pContext); + }else if( nToken==0 ){ + sqlite3_result_text(pContext, "", -1, SQLITE_STATIC); + }else if( SQLITE_OK==fts3CursorSeek(pContext, pCsr) ){ + sqlite3Fts3Snippet(pContext, pCsr, zStart, zEnd, zEllipsis, iCol, nToken); + } +} + +/* +** Implementation of the offsets() function for FTS3 +*/ +static void fts3OffsetsFunc( + sqlite3_context *pContext, /* SQLite function call context */ + int nVal, /* Size of argument array */ + sqlite3_value **apVal /* Array of arguments */ +){ + Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */ + + UNUSED_PARAMETER(nVal); + + assert( nVal==1 ); + if( fts3FunctionArg(pContext, "offsets", apVal[0], &pCsr) ) return; + assert( pCsr ); + if( SQLITE_OK==fts3CursorSeek(pContext, pCsr) ){ + sqlite3Fts3Offsets(pContext, pCsr); + } +} + +/* +** Implementation of the special optimize() function for FTS3. This +** function merges all segments in the database to a single segment. +** Example usage is: +** +** SELECT optimize(t) FROM t LIMIT 1; +** +** where 't' is the name of an FTS3 table. +*/ +static void fts3OptimizeFunc( + sqlite3_context *pContext, /* SQLite function call context */ + int nVal, /* Size of argument array */ + sqlite3_value **apVal /* Array of arguments */ +){ + int rc; /* Return code */ + Fts3Table *p; /* Virtual table handle */ + Fts3Cursor *pCursor; /* Cursor handle passed through apVal[0] */ + + UNUSED_PARAMETER(nVal); + + assert( nVal==1 ); + if( fts3FunctionArg(pContext, "optimize", apVal[0], &pCursor) ) return; + p = (Fts3Table *)pCursor->base.pVtab; + assert( p ); + + rc = sqlite3Fts3Optimize(p); + + switch( rc ){ + case SQLITE_OK: + sqlite3_result_text(pContext, "Index optimized", -1, SQLITE_STATIC); + break; + case SQLITE_DONE: + sqlite3_result_text(pContext, "Index already optimal", -1, SQLITE_STATIC); + break; + default: + sqlite3_result_error_code(pContext, rc); + break; + } +} + +/* +** Implementation of the matchinfo() function for FTS3 +*/ +static void fts3MatchinfoFunc( + sqlite3_context *pContext, /* SQLite function call context */ + int nVal, /* Size of argument array */ + sqlite3_value **apVal /* Array of arguments */ +){ + Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */ + assert( nVal==1 || nVal==2 ); + if( SQLITE_OK==fts3FunctionArg(pContext, "matchinfo", apVal[0], &pCsr) ){ + const char *zArg = 0; + if( nVal>1 ){ + zArg = (const char *)sqlite3_value_text(apVal[1]); + } + sqlite3Fts3Matchinfo(pContext, pCsr, zArg); + } +} + +/* +** This routine implements the xFindFunction method for the FTS3 +** virtual table. +*/ +static int fts3FindFunctionMethod( + sqlite3_vtab *pVtab, /* Virtual table handle */ + int nArg, /* Number of SQL function arguments */ + const char *zName, /* Name of SQL function */ + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */ + void **ppArg /* Unused */ +){ + struct Overloaded { + const char *zName; + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } aOverload[] = { + { "snippet", fts3SnippetFunc }, + { "offsets", fts3OffsetsFunc }, + { "optimize", fts3OptimizeFunc }, + { "matchinfo", fts3MatchinfoFunc }, + }; + int i; /* Iterator variable */ + + UNUSED_PARAMETER(pVtab); + UNUSED_PARAMETER(nArg); + UNUSED_PARAMETER(ppArg); + + for(i=0; i<SizeofArray(aOverload); i++){ + if( strcmp(zName, aOverload[i].zName)==0 ){ + *pxFunc = aOverload[i].xFunc; + return 1; + } + } + + /* No function of the specified name was found. Return 0. */ + return 0; +} + +/* +** Implementation of FTS3 xRename method. Rename an fts3 table. +*/ +static int fts3RenameMethod( + sqlite3_vtab *pVtab, /* Virtual table handle */ + const char *zName /* New name of table */ +){ + Fts3Table *p = (Fts3Table *)pVtab; + sqlite3 *db = p->db; /* Database connection */ + int rc; /* Return Code */ + + /* At this point it must be known if the %_stat table exists or not. + ** So bHasStat may not be 2. */ + rc = fts3SetHasStat(p); + + /* As it happens, the pending terms table is always empty here. This is + ** because an "ALTER TABLE RENAME TABLE" statement inside a transaction + ** always opens a savepoint transaction. And the xSavepoint() method + ** flushes the pending terms table. But leave the (no-op) call to + ** PendingTermsFlush() in in case that changes. + */ + assert( p->nPendingData==0 ); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3PendingTermsFlush(p); + } + + if( p->zContentTbl==0 ){ + fts3DbExec(&rc, db, + "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';", + p->zDb, p->zName, zName + ); + } + + if( p->bHasDocsize ){ + fts3DbExec(&rc, db, + "ALTER TABLE %Q.'%q_docsize' RENAME TO '%q_docsize';", + p->zDb, p->zName, zName + ); + } + if( p->bHasStat ){ + fts3DbExec(&rc, db, + "ALTER TABLE %Q.'%q_stat' RENAME TO '%q_stat';", + p->zDb, p->zName, zName + ); + } + fts3DbExec(&rc, db, + "ALTER TABLE %Q.'%q_segments' RENAME TO '%q_segments';", + p->zDb, p->zName, zName + ); + fts3DbExec(&rc, db, + "ALTER TABLE %Q.'%q_segdir' RENAME TO '%q_segdir';", + p->zDb, p->zName, zName + ); + return rc; +} + +/* +** The xSavepoint() method. +** +** Flush the contents of the pending-terms table to disk. +*/ +static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ + int rc = SQLITE_OK; + UNUSED_PARAMETER(iSavepoint); + assert( ((Fts3Table *)pVtab)->inTransaction ); + assert( ((Fts3Table *)pVtab)->mxSavepoint <= iSavepoint ); + TESTONLY( ((Fts3Table *)pVtab)->mxSavepoint = iSavepoint ); + if( ((Fts3Table *)pVtab)->bIgnoreSavepoint==0 ){ + rc = fts3SyncMethod(pVtab); + } + return rc; +} + +/* +** The xRelease() method. +** +** This is a no-op. +*/ +static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ + TESTONLY( Fts3Table *p = (Fts3Table*)pVtab ); + UNUSED_PARAMETER(iSavepoint); + UNUSED_PARAMETER(pVtab); + assert( p->inTransaction ); + assert( p->mxSavepoint >= iSavepoint ); + TESTONLY( p->mxSavepoint = iSavepoint-1 ); + return SQLITE_OK; +} + +/* +** The xRollbackTo() method. +** +** Discard the contents of the pending terms table. +*/ +static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ + Fts3Table *p = (Fts3Table*)pVtab; + UNUSED_PARAMETER(iSavepoint); + assert( p->inTransaction ); + TESTONLY( p->mxSavepoint = iSavepoint ); + sqlite3Fts3PendingTermsClear(p); + return SQLITE_OK; +} + +/* +** Return true if zName is the extension on one of the shadow tables used +** by this module. +*/ +static int fts3ShadowName(const char *zName){ + static const char *azName[] = { + "content", "docsize", "segdir", "segments", "stat", + }; + unsigned int i; + for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){ + if( sqlite3_stricmp(zName, azName[i])==0 ) return 1; + } + return 0; +} + +/* +** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual +** table. +*/ +static int fts3Integrity(sqlite3_vtab *pVtab, char **pzErr){ + Fts3Table *p = (Fts3Table*)pVtab; + char *zSql; + int rc; + + zSql = sqlite3_mprintf( + "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');", + p->zDb, p->zName, p->zName); + rc = sqlite3_exec(p->db, zSql, 0, 0, 0); + sqlite3_free(zSql); + if( (rc&0xff)==SQLITE_CORRUPT ){ + *pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s", + p->bFts4 ? 4 : 3, p->zDb, p->zName); + rc = SQLITE_OK; + } + return rc; +} + + + +static const sqlite3_module fts3Module = { + /* iVersion */ 4, + /* xCreate */ fts3CreateMethod, + /* xConnect */ fts3ConnectMethod, + /* xBestIndex */ fts3BestIndexMethod, + /* xDisconnect */ fts3DisconnectMethod, + /* xDestroy */ fts3DestroyMethod, + /* xOpen */ fts3OpenMethod, + /* xClose */ fts3CloseMethod, + /* xFilter */ fts3FilterMethod, + /* xNext */ fts3NextMethod, + /* xEof */ fts3EofMethod, + /* xColumn */ fts3ColumnMethod, + /* xRowid */ fts3RowidMethod, + /* xUpdate */ fts3UpdateMethod, + /* xBegin */ fts3BeginMethod, + /* xSync */ fts3SyncMethod, + /* xCommit */ fts3CommitMethod, + /* xRollback */ fts3RollbackMethod, + /* xFindFunction */ fts3FindFunctionMethod, + /* xRename */ fts3RenameMethod, + /* xSavepoint */ fts3SavepointMethod, + /* xRelease */ fts3ReleaseMethod, + /* xRollbackTo */ fts3RollbackToMethod, + /* xShadowName */ fts3ShadowName, + /* xIntegrity */ fts3Integrity, +}; + +/* +** This function is registered as the module destructor (called when an +** FTS3 enabled database connection is closed). It frees the memory +** allocated for the tokenizer hash table. +*/ +static void hashDestroy(void *p){ + Fts3HashWrapper *pHash = (Fts3HashWrapper *)p; + pHash->nRef--; + if( pHash->nRef<=0 ){ + sqlite3Fts3HashClear(&pHash->hash); + sqlite3_free(pHash); + } +} + +/* +** The fts3 built-in tokenizers - "simple", "porter" and "icu"- are +** implemented in files fts3_tokenizer1.c, fts3_porter.c and fts3_icu.c +** respectively. The following three forward declarations are for functions +** declared in these files used to retrieve the respective implementations. +** +** Calling sqlite3Fts3SimpleTokenizerModule() sets the value pointed +** to by the argument to point to the "simple" tokenizer implementation. +** And so on. +*/ +SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule); +SQLITE_PRIVATE void sqlite3Fts3PorterTokenizerModule(sqlite3_tokenizer_module const**ppModule); +#ifndef SQLITE_DISABLE_FTS3_UNICODE +SQLITE_PRIVATE void sqlite3Fts3UnicodeTokenizer(sqlite3_tokenizer_module const**ppModule); +#endif +#ifdef SQLITE_ENABLE_ICU +SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(sqlite3_tokenizer_module const**ppModule); +#endif + +/* +** Initialize the fts3 extension. If this extension is built as part +** of the sqlite library, then this function is called directly by +** SQLite. If fts3 is built as a dynamically loadable extension, this +** function is called by the sqlite3_extension_init() entry point. +*/ +SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ + int rc = SQLITE_OK; + Fts3HashWrapper *pHash = 0; + const sqlite3_tokenizer_module *pSimple = 0; + const sqlite3_tokenizer_module *pPorter = 0; +#ifndef SQLITE_DISABLE_FTS3_UNICODE + const sqlite3_tokenizer_module *pUnicode = 0; +#endif + +#ifdef SQLITE_ENABLE_ICU + const sqlite3_tokenizer_module *pIcu = 0; + sqlite3Fts3IcuTokenizerModule(&pIcu); +#endif + +#ifndef SQLITE_DISABLE_FTS3_UNICODE + sqlite3Fts3UnicodeTokenizer(&pUnicode); +#endif + +#ifdef SQLITE_TEST + rc = sqlite3Fts3InitTerm(db); + if( rc!=SQLITE_OK ) return rc; +#endif + + rc = sqlite3Fts3InitAux(db); + if( rc!=SQLITE_OK ) return rc; + + sqlite3Fts3SimpleTokenizerModule(&pSimple); + sqlite3Fts3PorterTokenizerModule(&pPorter); + + /* Allocate and initialize the hash-table used to store tokenizers. */ + pHash = sqlite3_malloc(sizeof(Fts3HashWrapper)); + if( !pHash ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3Fts3HashInit(&pHash->hash, FTS3_HASH_STRING, 1); + pHash->nRef = 0; + } + + /* Load the built-in tokenizers into the hash table */ + if( rc==SQLITE_OK ){ + if( sqlite3Fts3HashInsert(&pHash->hash, "simple", 7, (void *)pSimple) + || sqlite3Fts3HashInsert(&pHash->hash, "porter", 7, (void *)pPorter) + +#ifndef SQLITE_DISABLE_FTS3_UNICODE + || sqlite3Fts3HashInsert(&pHash->hash, "unicode61", 10, (void *)pUnicode) +#endif +#ifdef SQLITE_ENABLE_ICU + || (pIcu && sqlite3Fts3HashInsert(&pHash->hash, "icu", 4, (void *)pIcu)) +#endif + ){ + rc = SQLITE_NOMEM; + } + } + +#ifdef SQLITE_TEST + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3ExprInitTestInterface(db, &pHash->hash); + } +#endif + + /* Create the virtual table wrapper around the hash-table and overload + ** the four scalar functions. If this is successful, register the + ** module with sqlite. + */ + if( SQLITE_OK==rc + && SQLITE_OK==(rc=sqlite3Fts3InitHashTable(db,&pHash->hash,"fts3_tokenizer")) + && SQLITE_OK==(rc = sqlite3_overload_function(db, "snippet", -1)) + && SQLITE_OK==(rc = sqlite3_overload_function(db, "offsets", 1)) + && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 1)) + && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 2)) + && SQLITE_OK==(rc = sqlite3_overload_function(db, "optimize", 1)) + ){ + pHash->nRef++; + rc = sqlite3_create_module_v2( + db, "fts3", &fts3Module, (void *)pHash, hashDestroy + ); + if( rc==SQLITE_OK ){ + pHash->nRef++; + rc = sqlite3_create_module_v2( + db, "fts4", &fts3Module, (void *)pHash, hashDestroy + ); + } + if( rc==SQLITE_OK ){ + pHash->nRef++; + rc = sqlite3Fts3InitTok(db, (void *)pHash, hashDestroy); + } + return rc; + } + + + /* An error has occurred. Delete the hash table and return the error code. */ + assert( rc!=SQLITE_OK ); + if( pHash ){ + sqlite3Fts3HashClear(&pHash->hash); + sqlite3_free(pHash); + } + return rc; +} + +/* +** Allocate an Fts3MultiSegReader for each token in the expression headed +** by pExpr. +** +** An Fts3SegReader object is a cursor that can seek or scan a range of +** entries within a single segment b-tree. An Fts3MultiSegReader uses multiple +** Fts3SegReader objects internally to provide an interface to seek or scan +** within the union of all segments of a b-tree. Hence the name. +** +** If the allocated Fts3MultiSegReader just seeks to a single entry in a +** segment b-tree (if the term is not a prefix or it is a prefix for which +** there exists prefix b-tree of the right length) then it may be traversed +** and merged incrementally. Otherwise, it has to be merged into an in-memory +** doclist and then traversed. +*/ +static void fts3EvalAllocateReaders( + Fts3Cursor *pCsr, /* FTS cursor handle */ + Fts3Expr *pExpr, /* Allocate readers for this expression */ + int *pnToken, /* OUT: Total number of tokens in phrase. */ + int *pnOr, /* OUT: Total number of OR nodes in expr. */ + int *pRc /* IN/OUT: Error code */ +){ + if( pExpr && SQLITE_OK==*pRc ){ + if( pExpr->eType==FTSQUERY_PHRASE ){ + int i; + int nToken = pExpr->pPhrase->nToken; + *pnToken += nToken; + for(i=0; i<nToken; i++){ + Fts3PhraseToken *pToken = &pExpr->pPhrase->aToken[i]; + int rc = fts3TermSegReaderCursor(pCsr, + pToken->z, pToken->n, pToken->isPrefix, &pToken->pSegcsr + ); + if( rc!=SQLITE_OK ){ + *pRc = rc; + return; + } + } + assert( pExpr->pPhrase->iDoclistToken==0 ); + pExpr->pPhrase->iDoclistToken = -1; + }else{ + *pnOr += (pExpr->eType==FTSQUERY_OR); + fts3EvalAllocateReaders(pCsr, pExpr->pLeft, pnToken, pnOr, pRc); + fts3EvalAllocateReaders(pCsr, pExpr->pRight, pnToken, pnOr, pRc); + } + } +} + +/* +** Arguments pList/nList contain the doclist for token iToken of phrase p. +** It is merged into the main doclist stored in p->doclist.aAll/nAll. +** +** This function assumes that pList points to a buffer allocated using +** sqlite3_malloc(). This function takes responsibility for eventually +** freeing the buffer. +** +** SQLITE_OK is returned if successful, or SQLITE_NOMEM if an error occurs. +*/ +static int fts3EvalPhraseMergeToken( + Fts3Table *pTab, /* FTS Table pointer */ + Fts3Phrase *p, /* Phrase to merge pList/nList into */ + int iToken, /* Token pList/nList corresponds to */ + char *pList, /* Pointer to doclist */ + int nList /* Number of bytes in pList */ +){ + int rc = SQLITE_OK; + assert( iToken!=p->iDoclistToken ); + + if( pList==0 ){ + sqlite3_free(p->doclist.aAll); + p->doclist.aAll = 0; + p->doclist.nAll = 0; + } + + else if( p->iDoclistToken<0 ){ + p->doclist.aAll = pList; + p->doclist.nAll = nList; + } + + else if( p->doclist.aAll==0 ){ + sqlite3_free(pList); + } + + else { + char *pLeft; + char *pRight; + int nLeft; + int nRight; + int nDiff; + + if( p->iDoclistToken<iToken ){ + pLeft = p->doclist.aAll; + nLeft = p->doclist.nAll; + pRight = pList; + nRight = nList; + nDiff = iToken - p->iDoclistToken; + }else{ + pRight = p->doclist.aAll; + nRight = p->doclist.nAll; + pLeft = pList; + nLeft = nList; + nDiff = p->iDoclistToken - iToken; + } + + rc = fts3DoclistPhraseMerge( + pTab->bDescIdx, nDiff, pLeft, nLeft, &pRight, &nRight + ); + sqlite3_free(pLeft); + p->doclist.aAll = pRight; + p->doclist.nAll = nRight; + } + + if( iToken>p->iDoclistToken ) p->iDoclistToken = iToken; + return rc; +} + +/* +** Load the doclist for phrase p into p->doclist.aAll/nAll. The loaded doclist +** does not take deferred tokens into account. +** +** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code. +*/ +static int fts3EvalPhraseLoad( + Fts3Cursor *pCsr, /* FTS Cursor handle */ + Fts3Phrase *p /* Phrase object */ +){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + int iToken; + int rc = SQLITE_OK; + + for(iToken=0; rc==SQLITE_OK && iToken<p->nToken; iToken++){ + Fts3PhraseToken *pToken = &p->aToken[iToken]; + assert( pToken->pDeferred==0 || pToken->pSegcsr==0 ); + + if( pToken->pSegcsr ){ + int nThis = 0; + char *pThis = 0; + rc = fts3TermSelect(pTab, pToken, p->iColumn, &nThis, &pThis); + if( rc==SQLITE_OK ){ + rc = fts3EvalPhraseMergeToken(pTab, p, iToken, pThis, nThis); + } + } + assert( pToken->pSegcsr==0 ); + } + + return rc; +} + +#ifndef SQLITE_DISABLE_FTS4_DEFERRED +/* +** This function is called on each phrase after the position lists for +** any deferred tokens have been loaded into memory. It updates the phrases +** current position list to include only those positions that are really +** instances of the phrase (after considering deferred tokens). If this +** means that the phrase does not appear in the current row, doclist.pList +** and doclist.nList are both zeroed. +** +** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code. +*/ +static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ + int iToken; /* Used to iterate through phrase tokens */ + char *aPoslist = 0; /* Position list for deferred tokens */ + int nPoslist = 0; /* Number of bytes in aPoslist */ + int iPrev = -1; /* Token number of previous deferred token */ + char *aFree = (pPhrase->doclist.bFreeList ? pPhrase->doclist.pList : 0); + + for(iToken=0; iToken<pPhrase->nToken; iToken++){ + Fts3PhraseToken *pToken = &pPhrase->aToken[iToken]; + Fts3DeferredToken *pDeferred = pToken->pDeferred; + + if( pDeferred ){ + char *pList; + int nList; + int rc = sqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList); + if( rc!=SQLITE_OK ) return rc; + + if( pList==0 ){ + sqlite3_free(aPoslist); + sqlite3_free(aFree); + pPhrase->doclist.pList = 0; + pPhrase->doclist.nList = 0; + return SQLITE_OK; + + }else if( aPoslist==0 ){ + aPoslist = pList; + nPoslist = nList; + + }else{ + char *aOut = pList; + char *p1 = aPoslist; + char *p2 = aOut; + + assert( iPrev>=0 ); + fts3PoslistPhraseMerge(&aOut, iToken-iPrev, 0, 1, &p1, &p2); + sqlite3_free(aPoslist); + aPoslist = pList; + nPoslist = (int)(aOut - aPoslist); + if( nPoslist==0 ){ + sqlite3_free(aPoslist); + sqlite3_free(aFree); + pPhrase->doclist.pList = 0; + pPhrase->doclist.nList = 0; + return SQLITE_OK; + } + } + iPrev = iToken; + } + } + + if( iPrev>=0 ){ + int nMaxUndeferred = pPhrase->iDoclistToken; + if( nMaxUndeferred<0 ){ + pPhrase->doclist.pList = aPoslist; + pPhrase->doclist.nList = nPoslist; + pPhrase->doclist.iDocid = pCsr->iPrevId; + pPhrase->doclist.bFreeList = 1; + }else{ + int nDistance; + char *p1; + char *p2; + char *aOut; + + if( nMaxUndeferred>iPrev ){ + p1 = aPoslist; + p2 = pPhrase->doclist.pList; + nDistance = nMaxUndeferred - iPrev; + }else{ + p1 = pPhrase->doclist.pList; + p2 = aPoslist; + nDistance = iPrev - nMaxUndeferred; + } + + aOut = (char *)sqlite3Fts3MallocZero(nPoslist+FTS3_BUFFER_PADDING); + if( !aOut ){ + sqlite3_free(aPoslist); + return SQLITE_NOMEM; + } + + pPhrase->doclist.pList = aOut; + assert( p1 && p2 ); + if( fts3PoslistPhraseMerge(&aOut, nDistance, 0, 1, &p1, &p2) ){ + pPhrase->doclist.bFreeList = 1; + pPhrase->doclist.nList = (int)(aOut - pPhrase->doclist.pList); + }else{ + sqlite3_free(aOut); + pPhrase->doclist.pList = 0; + pPhrase->doclist.nList = 0; + } + sqlite3_free(aPoslist); + } + } + + if( pPhrase->doclist.pList!=aFree ) sqlite3_free(aFree); + return SQLITE_OK; +} +#endif /* SQLITE_DISABLE_FTS4_DEFERRED */ + +/* +** Maximum number of tokens a phrase may have to be considered for the +** incremental doclists strategy. +*/ +#define MAX_INCR_PHRASE_TOKENS 4 + +/* +** This function is called for each Fts3Phrase in a full-text query +** expression to initialize the mechanism for returning rows. Once this +** function has been called successfully on an Fts3Phrase, it may be +** used with fts3EvalPhraseNext() to iterate through the matching docids. +** +** If parameter bOptOk is true, then the phrase may (or may not) use the +** incremental loading strategy. Otherwise, the entire doclist is loaded into +** memory within this call. +** +** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code. +*/ +static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + int rc = SQLITE_OK; /* Error code */ + int i; + + /* Determine if doclists may be loaded from disk incrementally. This is + ** possible if the bOptOk argument is true, the FTS doclists will be + ** scanned in forward order, and the phrase consists of + ** MAX_INCR_PHRASE_TOKENS or fewer tokens, none of which are are "^first" + ** tokens or prefix tokens that cannot use a prefix-index. */ + int bHaveIncr = 0; + int bIncrOk = (bOptOk + && pCsr->bDesc==pTab->bDescIdx + && p->nToken<=MAX_INCR_PHRASE_TOKENS && p->nToken>0 +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + && pTab->bNoIncrDoclist==0 +#endif + ); + for(i=0; bIncrOk==1 && i<p->nToken; i++){ + Fts3PhraseToken *pToken = &p->aToken[i]; + if( pToken->bFirst || (pToken->pSegcsr!=0 && !pToken->pSegcsr->bLookup) ){ + bIncrOk = 0; + } + if( pToken->pSegcsr ) bHaveIncr = 1; + } + + if( bIncrOk && bHaveIncr ){ + /* Use the incremental approach. */ + int iCol = (p->iColumn >= pTab->nColumn ? -1 : p->iColumn); + for(i=0; rc==SQLITE_OK && i<p->nToken; i++){ + Fts3PhraseToken *pToken = &p->aToken[i]; + Fts3MultiSegReader *pSegcsr = pToken->pSegcsr; + if( pSegcsr ){ + rc = sqlite3Fts3MsrIncrStart(pTab, pSegcsr, iCol, pToken->z, pToken->n); + } + } + p->bIncr = 1; + }else{ + /* Load the full doclist for the phrase into memory. */ + rc = fts3EvalPhraseLoad(pCsr, p); + p->bIncr = 0; + } + + assert( rc!=SQLITE_OK || p->nToken<1 || p->aToken[0].pSegcsr==0 || p->bIncr ); + return rc; +} + +/* +** This function is used to iterate backwards (from the end to start) +** through doclists. It is used by this module to iterate through phrase +** doclists in reverse and by the fts3_write.c module to iterate through +** pending-terms lists when writing to databases with "order=desc". +** +** The doclist may be sorted in ascending (parameter bDescIdx==0) or +** descending (parameter bDescIdx==1) order of docid. Regardless, this +** function iterates from the end of the doclist to the beginning. +*/ +SQLITE_PRIVATE void sqlite3Fts3DoclistPrev( + int bDescIdx, /* True if the doclist is desc */ + char *aDoclist, /* Pointer to entire doclist */ + int nDoclist, /* Length of aDoclist in bytes */ + char **ppIter, /* IN/OUT: Iterator pointer */ + sqlite3_int64 *piDocid, /* IN/OUT: Docid pointer */ + int *pnList, /* OUT: List length pointer */ + u8 *pbEof /* OUT: End-of-file flag */ +){ + char *p = *ppIter; + + assert( nDoclist>0 ); + assert( *pbEof==0 ); + assert_fts3_nc( p || *piDocid==0 ); + assert( !p || (p>aDoclist && p<&aDoclist[nDoclist]) ); + + if( p==0 ){ + sqlite3_int64 iDocid = 0; + char *pNext = 0; + char *pDocid = aDoclist; + char *pEnd = &aDoclist[nDoclist]; + int iMul = 1; + + while( pDocid<pEnd ){ + sqlite3_int64 iDelta; + pDocid += sqlite3Fts3GetVarint(pDocid, &iDelta); + iDocid += (iMul * iDelta); + pNext = pDocid; + fts3PoslistCopy(0, &pDocid); + while( pDocid<pEnd && *pDocid==0 ) pDocid++; + iMul = (bDescIdx ? -1 : 1); + } + + *pnList = (int)(pEnd - pNext); + *ppIter = pNext; + *piDocid = iDocid; + }else{ + int iMul = (bDescIdx ? -1 : 1); + sqlite3_int64 iDelta; + fts3GetReverseVarint(&p, aDoclist, &iDelta); + *piDocid -= (iMul * iDelta); + + if( p==aDoclist ){ + *pbEof = 1; + }else{ + char *pSave = p; + fts3ReversePoslist(aDoclist, &p); + *pnList = (int)(pSave - p); + } + *ppIter = p; + } +} + +/* +** Iterate forwards through a doclist. +*/ +SQLITE_PRIVATE void sqlite3Fts3DoclistNext( + int bDescIdx, /* True if the doclist is desc */ + char *aDoclist, /* Pointer to entire doclist */ + int nDoclist, /* Length of aDoclist in bytes */ + char **ppIter, /* IN/OUT: Iterator pointer */ + sqlite3_int64 *piDocid, /* IN/OUT: Docid pointer */ + u8 *pbEof /* OUT: End-of-file flag */ +){ + char *p = *ppIter; + + assert( nDoclist>0 ); + assert( *pbEof==0 ); + assert_fts3_nc( p || *piDocid==0 ); + assert( !p || (p>=aDoclist && p<=&aDoclist[nDoclist]) ); + + if( p==0 ){ + p = aDoclist; + p += sqlite3Fts3GetVarint(p, piDocid); + }else{ + fts3PoslistCopy(0, &p); + while( p<&aDoclist[nDoclist] && *p==0 ) p++; + if( p>=&aDoclist[nDoclist] ){ + *pbEof = 1; + }else{ + sqlite3_int64 iVar; + p += sqlite3Fts3GetVarint(p, &iVar); + *piDocid += ((bDescIdx ? -1 : 1) * iVar); + } + } + + *ppIter = p; +} + +/* +** Advance the iterator pDL to the next entry in pDL->aAll/nAll. Set *pbEof +** to true if EOF is reached. +*/ +static void fts3EvalDlPhraseNext( + Fts3Table *pTab, + Fts3Doclist *pDL, + u8 *pbEof +){ + char *pIter; /* Used to iterate through aAll */ + char *pEnd; /* 1 byte past end of aAll */ + + if( pDL->pNextDocid ){ + pIter = pDL->pNextDocid; + assert( pDL->aAll!=0 || pIter==0 ); + }else{ + pIter = pDL->aAll; + } + + if( pIter==0 || pIter>=(pEnd = pDL->aAll + pDL->nAll) ){ + /* We have already reached the end of this doclist. EOF. */ + *pbEof = 1; + }else{ + sqlite3_int64 iDelta; + pIter += sqlite3Fts3GetVarint(pIter, &iDelta); + if( pTab->bDescIdx==0 || pDL->pNextDocid==0 ){ + pDL->iDocid += iDelta; + }else{ + pDL->iDocid -= iDelta; + } + pDL->pList = pIter; + fts3PoslistCopy(0, &pIter); + pDL->nList = (int)(pIter - pDL->pList); + + /* pIter now points just past the 0x00 that terminates the position- + ** list for document pDL->iDocid. However, if this position-list was + ** edited in place by fts3EvalNearTrim(), then pIter may not actually + ** point to the start of the next docid value. The following line deals + ** with this case by advancing pIter past the zero-padding added by + ** fts3EvalNearTrim(). */ + while( pIter<pEnd && *pIter==0 ) pIter++; + + pDL->pNextDocid = pIter; + assert( pIter>=&pDL->aAll[pDL->nAll] || *pIter ); + *pbEof = 0; + } +} + +/* +** Helper type used by fts3EvalIncrPhraseNext() and incrPhraseTokenNext(). +*/ +typedef struct TokenDoclist TokenDoclist; +struct TokenDoclist { + int bIgnore; + sqlite3_int64 iDocid; + char *pList; + int nList; +}; + +/* +** Token pToken is an incrementally loaded token that is part of a +** multi-token phrase. Advance it to the next matching document in the +** database and populate output variable *p with the details of the new +** entry. Or, if the iterator has reached EOF, set *pbEof to true. +** +** If an error occurs, return an SQLite error code. Otherwise, return +** SQLITE_OK. +*/ +static int incrPhraseTokenNext( + Fts3Table *pTab, /* Virtual table handle */ + Fts3Phrase *pPhrase, /* Phrase to advance token of */ + int iToken, /* Specific token to advance */ + TokenDoclist *p, /* OUT: Docid and doclist for new entry */ + u8 *pbEof /* OUT: True if iterator is at EOF */ +){ + int rc = SQLITE_OK; + + if( pPhrase->iDoclistToken==iToken ){ + assert( p->bIgnore==0 ); + assert( pPhrase->aToken[iToken].pSegcsr==0 ); + fts3EvalDlPhraseNext(pTab, &pPhrase->doclist, pbEof); + p->pList = pPhrase->doclist.pList; + p->nList = pPhrase->doclist.nList; + p->iDocid = pPhrase->doclist.iDocid; + }else{ + Fts3PhraseToken *pToken = &pPhrase->aToken[iToken]; + assert( pToken->pDeferred==0 ); + assert( pToken->pSegcsr || pPhrase->iDoclistToken>=0 ); + if( pToken->pSegcsr ){ + assert( p->bIgnore==0 ); + rc = sqlite3Fts3MsrIncrNext( + pTab, pToken->pSegcsr, &p->iDocid, &p->pList, &p->nList + ); + if( p->pList==0 ) *pbEof = 1; + }else{ + p->bIgnore = 1; + } + } + + return rc; +} + + +/* +** The phrase iterator passed as the second argument: +** +** * features at least one token that uses an incremental doclist, and +** +** * does not contain any deferred tokens. +** +** Advance it to the next matching documnent in the database and populate +** the Fts3Doclist.pList and nList fields. +** +** If there is no "next" entry and no error occurs, then *pbEof is set to +** 1 before returning. Otherwise, if no error occurs and the iterator is +** successfully advanced, *pbEof is set to 0. +** +** If an error occurs, return an SQLite error code. Otherwise, return +** SQLITE_OK. +*/ +static int fts3EvalIncrPhraseNext( + Fts3Cursor *pCsr, /* FTS Cursor handle */ + Fts3Phrase *p, /* Phrase object to advance to next docid */ + u8 *pbEof /* OUT: Set to 1 if EOF */ +){ + int rc = SQLITE_OK; + Fts3Doclist *pDL = &p->doclist; + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + u8 bEof = 0; + + /* This is only called if it is guaranteed that the phrase has at least + ** one incremental token. In which case the bIncr flag is set. */ + assert( p->bIncr==1 ); + + if( p->nToken==1 ){ + rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr, + &pDL->iDocid, &pDL->pList, &pDL->nList + ); + if( pDL->pList==0 ) bEof = 1; + }else{ + int bDescDoclist = pCsr->bDesc; + struct TokenDoclist a[MAX_INCR_PHRASE_TOKENS]; + + memset(a, 0, sizeof(a)); + assert( p->nToken<=MAX_INCR_PHRASE_TOKENS ); + assert( p->iDoclistToken<MAX_INCR_PHRASE_TOKENS ); + + while( bEof==0 ){ + int bMaxSet = 0; + sqlite3_int64 iMax = 0; /* Largest docid for all iterators */ + int i; /* Used to iterate through tokens */ + + /* Advance the iterator for each token in the phrase once. */ + for(i=0; rc==SQLITE_OK && i<p->nToken && bEof==0; i++){ + rc = incrPhraseTokenNext(pTab, p, i, &a[i], &bEof); + if( a[i].bIgnore==0 && (bMaxSet==0 || DOCID_CMP(iMax, a[i].iDocid)<0) ){ + iMax = a[i].iDocid; + bMaxSet = 1; + } + } + assert( rc!=SQLITE_OK || (p->nToken>=1 && a[p->nToken-1].bIgnore==0) ); + assert( rc!=SQLITE_OK || bMaxSet ); + + /* Keep advancing iterators until they all point to the same document */ + for(i=0; i<p->nToken; i++){ + while( rc==SQLITE_OK && bEof==0 + && a[i].bIgnore==0 && DOCID_CMP(a[i].iDocid, iMax)<0 + ){ + rc = incrPhraseTokenNext(pTab, p, i, &a[i], &bEof); + if( DOCID_CMP(a[i].iDocid, iMax)>0 ){ + iMax = a[i].iDocid; + i = 0; + } + } + } + + /* Check if the current entries really are a phrase match */ + if( bEof==0 ){ + int nList = 0; + int nByte = a[p->nToken-1].nList; + char *aDoclist = sqlite3_malloc64((i64)nByte+FTS3_BUFFER_PADDING); + if( !aDoclist ) return SQLITE_NOMEM; + memcpy(aDoclist, a[p->nToken-1].pList, nByte+1); + memset(&aDoclist[nByte], 0, FTS3_BUFFER_PADDING); + + for(i=0; i<(p->nToken-1); i++){ + if( a[i].bIgnore==0 ){ + char *pL = a[i].pList; + char *pR = aDoclist; + char *pOut = aDoclist; + int nDist = p->nToken-1-i; + int res = fts3PoslistPhraseMerge(&pOut, nDist, 0, 1, &pL, &pR); + if( res==0 ) break; + nList = (int)(pOut - aDoclist); + } + } + if( i==(p->nToken-1) ){ + pDL->iDocid = iMax; + pDL->pList = aDoclist; + pDL->nList = nList; + pDL->bFreeList = 1; + break; + } + sqlite3_free(aDoclist); + } + } + } + + *pbEof = bEof; + return rc; +} + +/* +** Attempt to move the phrase iterator to point to the next matching docid. +** If an error occurs, return an SQLite error code. Otherwise, return +** SQLITE_OK. +** +** If there is no "next" entry and no error occurs, then *pbEof is set to +** 1 before returning. Otherwise, if no error occurs and the iterator is +** successfully advanced, *pbEof is set to 0. +*/ +static int fts3EvalPhraseNext( + Fts3Cursor *pCsr, /* FTS Cursor handle */ + Fts3Phrase *p, /* Phrase object to advance to next docid */ + u8 *pbEof /* OUT: Set to 1 if EOF */ +){ + int rc = SQLITE_OK; + Fts3Doclist *pDL = &p->doclist; + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + + if( p->bIncr ){ + rc = fts3EvalIncrPhraseNext(pCsr, p, pbEof); + }else if( pCsr->bDesc!=pTab->bDescIdx && pDL->nAll ){ + sqlite3Fts3DoclistPrev(pTab->bDescIdx, pDL->aAll, pDL->nAll, + &pDL->pNextDocid, &pDL->iDocid, &pDL->nList, pbEof + ); + pDL->pList = pDL->pNextDocid; + }else{ + fts3EvalDlPhraseNext(pTab, pDL, pbEof); + } + + return rc; +} + +/* +** +** If *pRc is not SQLITE_OK when this function is called, it is a no-op. +** Otherwise, fts3EvalPhraseStart() is called on all phrases within the +** expression. Also the Fts3Expr.bDeferred variable is set to true for any +** expressions for which all descendent tokens are deferred. +** +** If parameter bOptOk is zero, then it is guaranteed that the +** Fts3Phrase.doclist.aAll/nAll variables contain the entire doclist for +** each phrase in the expression (subject to deferred token processing). +** Or, if bOptOk is non-zero, then one or more tokens within the expression +** may be loaded incrementally, meaning doclist.aAll/nAll is not available. +** +** If an error occurs within this function, *pRc is set to an SQLite error +** code before returning. +*/ +static void fts3EvalStartReaders( + Fts3Cursor *pCsr, /* FTS Cursor handle */ + Fts3Expr *pExpr, /* Expression to initialize phrases in */ + int *pRc /* IN/OUT: Error code */ +){ + if( pExpr && SQLITE_OK==*pRc ){ + if( pExpr->eType==FTSQUERY_PHRASE ){ + int nToken = pExpr->pPhrase->nToken; + if( nToken ){ + int i; + for(i=0; i<nToken; i++){ + if( pExpr->pPhrase->aToken[i].pDeferred==0 ) break; + } + pExpr->bDeferred = (i==nToken); + } + *pRc = fts3EvalPhraseStart(pCsr, 1, pExpr->pPhrase); + }else{ + fts3EvalStartReaders(pCsr, pExpr->pLeft, pRc); + fts3EvalStartReaders(pCsr, pExpr->pRight, pRc); + pExpr->bDeferred = (pExpr->pLeft->bDeferred && pExpr->pRight->bDeferred); + } + } +} + +/* +** An array of the following structures is assembled as part of the process +** of selecting tokens to defer before the query starts executing (as part +** of the xFilter() method). There is one element in the array for each +** token in the FTS expression. +** +** Tokens are divided into AND/NEAR clusters. All tokens in a cluster belong +** to phrases that are connected only by AND and NEAR operators (not OR or +** NOT). When determining tokens to defer, each AND/NEAR cluster is considered +** separately. The root of a tokens AND/NEAR cluster is stored in +** Fts3TokenAndCost.pRoot. +*/ +typedef struct Fts3TokenAndCost Fts3TokenAndCost; +struct Fts3TokenAndCost { + Fts3Phrase *pPhrase; /* The phrase the token belongs to */ + int iToken; /* Position of token in phrase */ + Fts3PhraseToken *pToken; /* The token itself */ + Fts3Expr *pRoot; /* Root of NEAR/AND cluster */ + int nOvfl; /* Number of overflow pages to load doclist */ + int iCol; /* The column the token must match */ +}; + +/* +** This function is used to populate an allocated Fts3TokenAndCost array. +** +** If *pRc is not SQLITE_OK when this function is called, it is a no-op. +** Otherwise, if an error occurs during execution, *pRc is set to an +** SQLite error code. +*/ +static void fts3EvalTokenCosts( + Fts3Cursor *pCsr, /* FTS Cursor handle */ + Fts3Expr *pRoot, /* Root of current AND/NEAR cluster */ + Fts3Expr *pExpr, /* Expression to consider */ + Fts3TokenAndCost **ppTC, /* Write new entries to *(*ppTC)++ */ + Fts3Expr ***ppOr, /* Write new OR root to *(*ppOr)++ */ + int *pRc /* IN/OUT: Error code */ +){ + if( *pRc==SQLITE_OK ){ + if( pExpr->eType==FTSQUERY_PHRASE ){ + Fts3Phrase *pPhrase = pExpr->pPhrase; + int i; + for(i=0; *pRc==SQLITE_OK && i<pPhrase->nToken; i++){ + Fts3TokenAndCost *pTC = (*ppTC)++; + pTC->pPhrase = pPhrase; + pTC->iToken = i; + pTC->pRoot = pRoot; + pTC->pToken = &pPhrase->aToken[i]; + pTC->iCol = pPhrase->iColumn; + *pRc = sqlite3Fts3MsrOvfl(pCsr, pTC->pToken->pSegcsr, &pTC->nOvfl); + } + }else if( pExpr->eType!=FTSQUERY_NOT ){ + assert( pExpr->eType==FTSQUERY_OR + || pExpr->eType==FTSQUERY_AND + || pExpr->eType==FTSQUERY_NEAR + ); + assert( pExpr->pLeft && pExpr->pRight ); + if( pExpr->eType==FTSQUERY_OR ){ + pRoot = pExpr->pLeft; + **ppOr = pRoot; + (*ppOr)++; + } + fts3EvalTokenCosts(pCsr, pRoot, pExpr->pLeft, ppTC, ppOr, pRc); + if( pExpr->eType==FTSQUERY_OR ){ + pRoot = pExpr->pRight; + **ppOr = pRoot; + (*ppOr)++; + } + fts3EvalTokenCosts(pCsr, pRoot, pExpr->pRight, ppTC, ppOr, pRc); + } + } +} + +/* +** Determine the average document (row) size in pages. If successful, +** write this value to *pnPage and return SQLITE_OK. Otherwise, return +** an SQLite error code. +** +** The average document size in pages is calculated by first calculating +** determining the average size in bytes, B. If B is less than the amount +** of data that will fit on a single leaf page of an intkey table in +** this database, then the average docsize is 1. Otherwise, it is 1 plus +** the number of overflow pages consumed by a record B bytes in size. +*/ +static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){ + int rc = SQLITE_OK; + if( pCsr->nRowAvg==0 ){ + /* The average document size, which is required to calculate the cost + ** of each doclist, has not yet been determined. Read the required + ** data from the %_stat table to calculate it. + ** + ** Entry 0 of the %_stat table is a blob containing (nCol+1) FTS3 + ** varints, where nCol is the number of columns in the FTS3 table. + ** The first varint is the number of documents currently stored in + ** the table. The following nCol varints contain the total amount of + ** data stored in all rows of each column of the table, from left + ** to right. + */ + Fts3Table *p = (Fts3Table*)pCsr->base.pVtab; + sqlite3_stmt *pStmt; + sqlite3_int64 nDoc = 0; + sqlite3_int64 nByte = 0; + const char *pEnd; + const char *a; + + rc = sqlite3Fts3SelectDoctotal(p, &pStmt); + if( rc!=SQLITE_OK ) return rc; + a = sqlite3_column_blob(pStmt, 0); + testcase( a==0 ); /* If %_stat.value set to X'' */ + if( a ){ + pEnd = &a[sqlite3_column_bytes(pStmt, 0)]; + a += sqlite3Fts3GetVarintBounded(a, pEnd, &nDoc); + while( a<pEnd ){ + a += sqlite3Fts3GetVarintBounded(a, pEnd, &nByte); + } + } + if( nDoc==0 || nByte==0 ){ + sqlite3_reset(pStmt); + return FTS_CORRUPT_VTAB; + } + + pCsr->nDoc = nDoc; + pCsr->nRowAvg = (int)(((nByte / nDoc) + p->nPgsz) / p->nPgsz); + assert( pCsr->nRowAvg>0 ); + rc = sqlite3_reset(pStmt); + } + + *pnPage = pCsr->nRowAvg; + return rc; +} + +/* +** This function is called to select the tokens (if any) that will be +** deferred. The array aTC[] has already been populated when this is +** called. +** +** This function is called once for each AND/NEAR cluster in the +** expression. Each invocation determines which tokens to defer within +** the cluster with root node pRoot. See comments above the definition +** of struct Fts3TokenAndCost for more details. +** +** If no error occurs, SQLITE_OK is returned and sqlite3Fts3DeferToken() +** called on each token to defer. Otherwise, an SQLite error code is +** returned. +*/ +static int fts3EvalSelectDeferred( + Fts3Cursor *pCsr, /* FTS Cursor handle */ + Fts3Expr *pRoot, /* Consider tokens with this root node */ + Fts3TokenAndCost *aTC, /* Array of expression tokens and costs */ + int nTC /* Number of entries in aTC[] */ +){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + int nDocSize = 0; /* Number of pages per doc loaded */ + int rc = SQLITE_OK; /* Return code */ + int ii; /* Iterator variable for various purposes */ + int nOvfl = 0; /* Total overflow pages used by doclists */ + int nToken = 0; /* Total number of tokens in cluster */ + + int nMinEst = 0; /* The minimum count for any phrase so far. */ + int nLoad4 = 1; /* (Phrases that will be loaded)^4. */ + + /* Tokens are never deferred for FTS tables created using the content=xxx + ** option. The reason being that it is not guaranteed that the content + ** table actually contains the same data as the index. To prevent this from + ** causing any problems, the deferred token optimization is completely + ** disabled for content=xxx tables. */ + if( pTab->zContentTbl ){ + return SQLITE_OK; + } + + /* Count the tokens in this AND/NEAR cluster. If none of the doclists + ** associated with the tokens spill onto overflow pages, or if there is + ** only 1 token, exit early. No tokens to defer in this case. */ + for(ii=0; ii<nTC; ii++){ + if( aTC[ii].pRoot==pRoot ){ + nOvfl += aTC[ii].nOvfl; + nToken++; + } + } + if( nOvfl==0 || nToken<2 ) return SQLITE_OK; + + /* Obtain the average docsize (in pages). */ + rc = fts3EvalAverageDocsize(pCsr, &nDocSize); + assert( rc!=SQLITE_OK || nDocSize>0 ); + + + /* Iterate through all tokens in this AND/NEAR cluster, in ascending order + ** of the number of overflow pages that will be loaded by the pager layer + ** to retrieve the entire doclist for the token from the full-text index. + ** Load the doclists for tokens that are either: + ** + ** a. The cheapest token in the entire query (i.e. the one visited by the + ** first iteration of this loop), or + ** + ** b. Part of a multi-token phrase. + ** + ** After each token doclist is loaded, merge it with the others from the + ** same phrase and count the number of documents that the merged doclist + ** contains. Set variable "nMinEst" to the smallest number of documents in + ** any phrase doclist for which 1 or more token doclists have been loaded. + ** Let nOther be the number of other phrases for which it is certain that + ** one or more tokens will not be deferred. + ** + ** Then, for each token, defer it if loading the doclist would result in + ** loading N or more overflow pages into memory, where N is computed as: + ** + ** (nMinEst + 4^nOther - 1) / (4^nOther) + */ + for(ii=0; ii<nToken && rc==SQLITE_OK; ii++){ + int iTC; /* Used to iterate through aTC[] array. */ + Fts3TokenAndCost *pTC = 0; /* Set to cheapest remaining token. */ + + /* Set pTC to point to the cheapest remaining token. */ + for(iTC=0; iTC<nTC; iTC++){ + if( aTC[iTC].pToken && aTC[iTC].pRoot==pRoot + && (!pTC || aTC[iTC].nOvfl<pTC->nOvfl) + ){ + pTC = &aTC[iTC]; + } + } + assert( pTC ); + + if( ii && pTC->nOvfl>=((nMinEst+(nLoad4/4)-1)/(nLoad4/4))*nDocSize ){ + /* The number of overflow pages to load for this (and therefore all + ** subsequent) tokens is greater than the estimated number of pages + ** that will be loaded if all subsequent tokens are deferred. + */ + Fts3PhraseToken *pToken = pTC->pToken; + rc = sqlite3Fts3DeferToken(pCsr, pToken, pTC->iCol); + fts3SegReaderCursorFree(pToken->pSegcsr); + pToken->pSegcsr = 0; + }else{ + /* Set nLoad4 to the value of (4^nOther) for the next iteration of the + ** for-loop. Except, limit the value to 2^24 to prevent it from + ** overflowing the 32-bit integer it is stored in. */ + if( ii<12 ) nLoad4 = nLoad4*4; + + if( ii==0 || (pTC->pPhrase->nToken>1 && ii!=nToken-1) ){ + /* Either this is the cheapest token in the entire query, or it is + ** part of a multi-token phrase. Either way, the entire doclist will + ** (eventually) be loaded into memory. It may as well be now. */ + Fts3PhraseToken *pToken = pTC->pToken; + int nList = 0; + char *pList = 0; + rc = fts3TermSelect(pTab, pToken, pTC->iCol, &nList, &pList); + assert( rc==SQLITE_OK || pList==0 ); + if( rc==SQLITE_OK ){ + rc = fts3EvalPhraseMergeToken( + pTab, pTC->pPhrase, pTC->iToken,pList,nList + ); + } + if( rc==SQLITE_OK ){ + int nCount; + nCount = fts3DoclistCountDocids( + pTC->pPhrase->doclist.aAll, pTC->pPhrase->doclist.nAll + ); + if( ii==0 || nCount<nMinEst ) nMinEst = nCount; + } + } + } + pTC->pToken = 0; + } + + return rc; +} + +/* +** This function is called from within the xFilter method. It initializes +** the full-text query currently stored in pCsr->pExpr. To iterate through +** the results of a query, the caller does: +** +** fts3EvalStart(pCsr); +** while( 1 ){ +** fts3EvalNext(pCsr); +** if( pCsr->bEof ) break; +** ... return row pCsr->iPrevId to the caller ... +** } +*/ +static int fts3EvalStart(Fts3Cursor *pCsr){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + int rc = SQLITE_OK; + int nToken = 0; + int nOr = 0; + + /* Allocate a MultiSegReader for each token in the expression. */ + fts3EvalAllocateReaders(pCsr, pCsr->pExpr, &nToken, &nOr, &rc); + + /* Determine which, if any, tokens in the expression should be deferred. */ +#ifndef SQLITE_DISABLE_FTS4_DEFERRED + if( rc==SQLITE_OK && nToken>1 && pTab->bFts4 ){ + Fts3TokenAndCost *aTC; + aTC = (Fts3TokenAndCost *)sqlite3_malloc64( + sizeof(Fts3TokenAndCost) * nToken + + sizeof(Fts3Expr *) * nOr * 2 + ); + + if( !aTC ){ + rc = SQLITE_NOMEM; + }else{ + Fts3Expr **apOr = (Fts3Expr **)&aTC[nToken]; + int ii; + Fts3TokenAndCost *pTC = aTC; + Fts3Expr **ppOr = apOr; + + fts3EvalTokenCosts(pCsr, 0, pCsr->pExpr, &pTC, &ppOr, &rc); + nToken = (int)(pTC-aTC); + nOr = (int)(ppOr-apOr); + + if( rc==SQLITE_OK ){ + rc = fts3EvalSelectDeferred(pCsr, 0, aTC, nToken); + for(ii=0; rc==SQLITE_OK && ii<nOr; ii++){ + rc = fts3EvalSelectDeferred(pCsr, apOr[ii], aTC, nToken); + } + } + + sqlite3_free(aTC); + } + } +#endif + + fts3EvalStartReaders(pCsr, pCsr->pExpr, &rc); + return rc; +} + +/* +** Invalidate the current position list for phrase pPhrase. +*/ +static void fts3EvalInvalidatePoslist(Fts3Phrase *pPhrase){ + if( pPhrase->doclist.bFreeList ){ + sqlite3_free(pPhrase->doclist.pList); + } + pPhrase->doclist.pList = 0; + pPhrase->doclist.nList = 0; + pPhrase->doclist.bFreeList = 0; +} + +/* +** This function is called to edit the position list associated with +** the phrase object passed as the fifth argument according to a NEAR +** condition. For example: +** +** abc NEAR/5 "def ghi" +** +** Parameter nNear is passed the NEAR distance of the expression (5 in +** the example above). When this function is called, *paPoslist points to +** the position list, and *pnToken is the number of phrase tokens in the +** phrase on the other side of the NEAR operator to pPhrase. For example, +** if pPhrase refers to the "def ghi" phrase, then *paPoslist points to +** the position list associated with phrase "abc". +** +** All positions in the pPhrase position list that are not sufficiently +** close to a position in the *paPoslist position list are removed. If this +** leaves 0 positions, zero is returned. Otherwise, non-zero. +** +** Before returning, *paPoslist is set to point to the position lsit +** associated with pPhrase. And *pnToken is set to the number of tokens in +** pPhrase. +*/ +static int fts3EvalNearTrim( + int nNear, /* NEAR distance. As in "NEAR/nNear". */ + char *aTmp, /* Temporary space to use */ + char **paPoslist, /* IN/OUT: Position list */ + int *pnToken, /* IN/OUT: Tokens in phrase of *paPoslist */ + Fts3Phrase *pPhrase /* The phrase object to trim the doclist of */ +){ + int nParam1 = nNear + pPhrase->nToken; + int nParam2 = nNear + *pnToken; + int nNew; + char *p2; + char *pOut; + int res; + + assert( pPhrase->doclist.pList ); + + p2 = pOut = pPhrase->doclist.pList; + res = fts3PoslistNearMerge( + &pOut, aTmp, nParam1, nParam2, paPoslist, &p2 + ); + if( res ){ + nNew = (int)(pOut - pPhrase->doclist.pList) - 1; + assert_fts3_nc( nNew<=pPhrase->doclist.nList && nNew>0 ); + if( nNew>=0 && nNew<=pPhrase->doclist.nList ){ + assert( pPhrase->doclist.pList[nNew]=='\0' ); + memset(&pPhrase->doclist.pList[nNew], 0, pPhrase->doclist.nList - nNew); + pPhrase->doclist.nList = nNew; + } + *paPoslist = pPhrase->doclist.pList; + *pnToken = pPhrase->nToken; + } + + return res; +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is called. +** Otherwise, it advances the expression passed as the second argument to +** point to the next matching row in the database. Expressions iterate through +** matching rows in docid order. Ascending order if Fts3Cursor.bDesc is zero, +** or descending if it is non-zero. +** +** If an error occurs, *pRc is set to an SQLite error code. Otherwise, if +** successful, the following variables in pExpr are set: +** +** Fts3Expr.bEof (non-zero if EOF - there is no next row) +** Fts3Expr.iDocid (valid if bEof==0. The docid of the next row) +** +** If the expression is of type FTSQUERY_PHRASE, and the expression is not +** at EOF, then the following variables are populated with the position list +** for the phrase for the visited row: +** +** FTs3Expr.pPhrase->doclist.nList (length of pList in bytes) +** FTs3Expr.pPhrase->doclist.pList (pointer to position list) +** +** It says above that this function advances the expression to the next +** matching row. This is usually true, but there are the following exceptions: +** +** 1. Deferred tokens are not taken into account. If a phrase consists +** entirely of deferred tokens, it is assumed to match every row in +** the db. In this case the position-list is not populated at all. +** +** Or, if a phrase contains one or more deferred tokens and one or +** more non-deferred tokens, then the expression is advanced to the +** next possible match, considering only non-deferred tokens. In other +** words, if the phrase is "A B C", and "B" is deferred, the expression +** is advanced to the next row that contains an instance of "A * C", +** where "*" may match any single token. The position list in this case +** is populated as for "A * C" before returning. +** +** 2. NEAR is treated as AND. If the expression is "x NEAR y", it is +** advanced to point to the next row that matches "x AND y". +** +** See sqlite3Fts3EvalTestDeferred() for details on testing if a row is +** really a match, taking into account deferred tokens and NEAR operators. +*/ +static void fts3EvalNextRow( + Fts3Cursor *pCsr, /* FTS Cursor handle */ + Fts3Expr *pExpr, /* Expr. to advance to next matching row */ + int *pRc /* IN/OUT: Error code */ +){ + if( *pRc==SQLITE_OK && pExpr->bEof==0 ){ + int bDescDoclist = pCsr->bDesc; /* Used by DOCID_CMP() macro */ + pExpr->bStart = 1; + + switch( pExpr->eType ){ + case FTSQUERY_NEAR: + case FTSQUERY_AND: { + Fts3Expr *pLeft = pExpr->pLeft; + Fts3Expr *pRight = pExpr->pRight; + assert( !pLeft->bDeferred || !pRight->bDeferred ); + + if( pLeft->bDeferred ){ + /* LHS is entirely deferred. So we assume it matches every row. + ** Advance the RHS iterator to find the next row visited. */ + fts3EvalNextRow(pCsr, pRight, pRc); + pExpr->iDocid = pRight->iDocid; + pExpr->bEof = pRight->bEof; + }else if( pRight->bDeferred ){ + /* RHS is entirely deferred. So we assume it matches every row. + ** Advance the LHS iterator to find the next row visited. */ + fts3EvalNextRow(pCsr, pLeft, pRc); + pExpr->iDocid = pLeft->iDocid; + pExpr->bEof = pLeft->bEof; + }else{ + /* Neither the RHS or LHS are deferred. */ + fts3EvalNextRow(pCsr, pLeft, pRc); + fts3EvalNextRow(pCsr, pRight, pRc); + while( !pLeft->bEof && !pRight->bEof && *pRc==SQLITE_OK ){ + sqlite3_int64 iDiff = DOCID_CMP(pLeft->iDocid, pRight->iDocid); + if( iDiff==0 ) break; + if( iDiff<0 ){ + fts3EvalNextRow(pCsr, pLeft, pRc); + }else{ + fts3EvalNextRow(pCsr, pRight, pRc); + } + } + pExpr->iDocid = pLeft->iDocid; + pExpr->bEof = (pLeft->bEof || pRight->bEof); + if( pExpr->eType==FTSQUERY_NEAR && pExpr->bEof ){ + assert( pRight->eType==FTSQUERY_PHRASE ); + if( pRight->pPhrase->doclist.aAll ){ + Fts3Doclist *pDl = &pRight->pPhrase->doclist; + while( *pRc==SQLITE_OK && pRight->bEof==0 ){ + memset(pDl->pList, 0, pDl->nList); + fts3EvalNextRow(pCsr, pRight, pRc); + } + } + if( pLeft->pPhrase && pLeft->pPhrase->doclist.aAll ){ + Fts3Doclist *pDl = &pLeft->pPhrase->doclist; + while( *pRc==SQLITE_OK && pLeft->bEof==0 ){ + memset(pDl->pList, 0, pDl->nList); + fts3EvalNextRow(pCsr, pLeft, pRc); + } + } + pRight->bEof = pLeft->bEof = 1; + } + } + break; + } + + case FTSQUERY_OR: { + Fts3Expr *pLeft = pExpr->pLeft; + Fts3Expr *pRight = pExpr->pRight; + sqlite3_int64 iCmp = DOCID_CMP(pLeft->iDocid, pRight->iDocid); + + assert_fts3_nc( pLeft->bStart || pLeft->iDocid==pRight->iDocid ); + assert_fts3_nc( pRight->bStart || pLeft->iDocid==pRight->iDocid ); + + if( pRight->bEof || (pLeft->bEof==0 && iCmp<0) ){ + fts3EvalNextRow(pCsr, pLeft, pRc); + }else if( pLeft->bEof || iCmp>0 ){ + fts3EvalNextRow(pCsr, pRight, pRc); + }else{ + fts3EvalNextRow(pCsr, pLeft, pRc); + fts3EvalNextRow(pCsr, pRight, pRc); + } + + pExpr->bEof = (pLeft->bEof && pRight->bEof); + iCmp = DOCID_CMP(pLeft->iDocid, pRight->iDocid); + if( pRight->bEof || (pLeft->bEof==0 && iCmp<0) ){ + pExpr->iDocid = pLeft->iDocid; + }else{ + pExpr->iDocid = pRight->iDocid; + } + + break; + } + + case FTSQUERY_NOT: { + Fts3Expr *pLeft = pExpr->pLeft; + Fts3Expr *pRight = pExpr->pRight; + + if( pRight->bStart==0 ){ + fts3EvalNextRow(pCsr, pRight, pRc); + assert( *pRc!=SQLITE_OK || pRight->bStart ); + } + + fts3EvalNextRow(pCsr, pLeft, pRc); + if( pLeft->bEof==0 ){ + while( !*pRc + && !pRight->bEof + && DOCID_CMP(pLeft->iDocid, pRight->iDocid)>0 + ){ + fts3EvalNextRow(pCsr, pRight, pRc); + } + } + pExpr->iDocid = pLeft->iDocid; + pExpr->bEof = pLeft->bEof; + break; + } + + default: { + Fts3Phrase *pPhrase = pExpr->pPhrase; + fts3EvalInvalidatePoslist(pPhrase); + *pRc = fts3EvalPhraseNext(pCsr, pPhrase, &pExpr->bEof); + pExpr->iDocid = pPhrase->doclist.iDocid; + break; + } + } + } +} + +/* +** If *pRc is not SQLITE_OK, or if pExpr is not the root node of a NEAR +** cluster, then this function returns 1 immediately. +** +** Otherwise, it checks if the current row really does match the NEAR +** expression, using the data currently stored in the position lists +** (Fts3Expr->pPhrase.doclist.pList/nList) for each phrase in the expression. +** +** If the current row is a match, the position list associated with each +** phrase in the NEAR expression is edited in place to contain only those +** phrase instances sufficiently close to their peers to satisfy all NEAR +** constraints. In this case it returns 1. If the NEAR expression does not +** match the current row, 0 is returned. The position lists may or may not +** be edited if 0 is returned. +*/ +static int fts3EvalNearTest(Fts3Expr *pExpr, int *pRc){ + int res = 1; + + /* The following block runs if pExpr is the root of a NEAR query. + ** For example, the query: + ** + ** "w" NEAR "x" NEAR "y" NEAR "z" + ** + ** which is represented in tree form as: + ** + ** | + ** +--NEAR--+ <-- root of NEAR query + ** | | + ** +--NEAR--+ "z" + ** | | + ** +--NEAR--+ "y" + ** | | + ** "w" "x" + ** + ** The right-hand child of a NEAR node is always a phrase. The + ** left-hand child may be either a phrase or a NEAR node. There are + ** no exceptions to this - it's the way the parser in fts3_expr.c works. + */ + if( *pRc==SQLITE_OK + && pExpr->eType==FTSQUERY_NEAR + && (pExpr->pParent==0 || pExpr->pParent->eType!=FTSQUERY_NEAR) + ){ + Fts3Expr *p; + sqlite3_int64 nTmp = 0; /* Bytes of temp space */ + char *aTmp; /* Temp space for PoslistNearMerge() */ + + /* Allocate temporary working space. */ + for(p=pExpr; p->pLeft; p=p->pLeft){ + assert( p->pRight->pPhrase->doclist.nList>0 ); + nTmp += p->pRight->pPhrase->doclist.nList; + } + nTmp += p->pPhrase->doclist.nList; + aTmp = sqlite3_malloc64(nTmp*2); + if( !aTmp ){ + *pRc = SQLITE_NOMEM; + res = 0; + }else{ + char *aPoslist = p->pPhrase->doclist.pList; + int nToken = p->pPhrase->nToken; + + for(p=p->pParent;res && p && p->eType==FTSQUERY_NEAR; p=p->pParent){ + Fts3Phrase *pPhrase = p->pRight->pPhrase; + int nNear = p->nNear; + res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); + } + + aPoslist = pExpr->pRight->pPhrase->doclist.pList; + nToken = pExpr->pRight->pPhrase->nToken; + for(p=pExpr->pLeft; p && res; p=p->pLeft){ + int nNear; + Fts3Phrase *pPhrase; + assert( p->pParent && p->pParent->pLeft==p ); + nNear = p->pParent->nNear; + pPhrase = ( + p->eType==FTSQUERY_NEAR ? p->pRight->pPhrase : p->pPhrase + ); + res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); + } + } + + sqlite3_free(aTmp); + } + + return res; +} + +/* +** This function is a helper function for sqlite3Fts3EvalTestDeferred(). +** Assuming no error occurs or has occurred, It returns non-zero if the +** expression passed as the second argument matches the row that pCsr +** currently points to, or zero if it does not. +** +** If *pRc is not SQLITE_OK when this function is called, it is a no-op. +** If an error occurs during execution of this function, *pRc is set to +** the appropriate SQLite error code. In this case the returned value is +** undefined. +*/ +static int fts3EvalTestExpr( + Fts3Cursor *pCsr, /* FTS cursor handle */ + Fts3Expr *pExpr, /* Expr to test. May or may not be root. */ + int *pRc /* IN/OUT: Error code */ +){ + int bHit = 1; /* Return value */ + if( *pRc==SQLITE_OK ){ + switch( pExpr->eType ){ + case FTSQUERY_NEAR: + case FTSQUERY_AND: + bHit = ( + fts3EvalTestExpr(pCsr, pExpr->pLeft, pRc) + && fts3EvalTestExpr(pCsr, pExpr->pRight, pRc) + && fts3EvalNearTest(pExpr, pRc) + ); + + /* If the NEAR expression does not match any rows, zero the doclist for + ** all phrases involved in the NEAR. This is because the snippet(), + ** offsets() and matchinfo() functions are not supposed to recognize + ** any instances of phrases that are part of unmatched NEAR queries. + ** For example if this expression: + ** + ** ... MATCH 'a OR (b NEAR c)' + ** + ** is matched against a row containing: + ** + ** 'a b d e' + ** + ** then any snippet() should ony highlight the "a" term, not the "b" + ** (as "b" is part of a non-matching NEAR clause). + */ + if( bHit==0 + && pExpr->eType==FTSQUERY_NEAR + && (pExpr->pParent==0 || pExpr->pParent->eType!=FTSQUERY_NEAR) + ){ + Fts3Expr *p; + for(p=pExpr; p->pPhrase==0; p=p->pLeft){ + if( p->pRight->iDocid==pCsr->iPrevId ){ + fts3EvalInvalidatePoslist(p->pRight->pPhrase); + } + } + if( p->iDocid==pCsr->iPrevId ){ + fts3EvalInvalidatePoslist(p->pPhrase); + } + } + + break; + + case FTSQUERY_OR: { + int bHit1 = fts3EvalTestExpr(pCsr, pExpr->pLeft, pRc); + int bHit2 = fts3EvalTestExpr(pCsr, pExpr->pRight, pRc); + bHit = bHit1 || bHit2; + break; + } + + case FTSQUERY_NOT: + bHit = ( + fts3EvalTestExpr(pCsr, pExpr->pLeft, pRc) + && !fts3EvalTestExpr(pCsr, pExpr->pRight, pRc) + ); + break; + + default: { +#ifndef SQLITE_DISABLE_FTS4_DEFERRED + if( pCsr->pDeferred && (pExpr->bDeferred || ( + pExpr->iDocid==pCsr->iPrevId && pExpr->pPhrase->doclist.pList + ))){ + Fts3Phrase *pPhrase = pExpr->pPhrase; + if( pExpr->bDeferred ){ + fts3EvalInvalidatePoslist(pPhrase); + } + *pRc = fts3EvalDeferredPhrase(pCsr, pPhrase); + bHit = (pPhrase->doclist.pList!=0); + pExpr->iDocid = pCsr->iPrevId; + }else +#endif + { + bHit = ( + pExpr->bEof==0 && pExpr->iDocid==pCsr->iPrevId + && pExpr->pPhrase->doclist.nList>0 + ); + } + break; + } + } + } + return bHit; +} + +/* +** This function is called as the second part of each xNext operation when +** iterating through the results of a full-text query. At this point the +** cursor points to a row that matches the query expression, with the +** following caveats: +** +** * Up until this point, "NEAR" operators in the expression have been +** treated as "AND". +** +** * Deferred tokens have not yet been considered. +** +** If *pRc is not SQLITE_OK when this function is called, it immediately +** returns 0. Otherwise, it tests whether or not after considering NEAR +** operators and deferred tokens the current row is still a match for the +** expression. It returns 1 if both of the following are true: +** +** 1. *pRc is SQLITE_OK when this function returns, and +** +** 2. After scanning the current FTS table row for the deferred tokens, +** it is determined that the row does *not* match the query. +** +** Or, if no error occurs and it seems the current row does match the FTS +** query, return 0. +*/ +SQLITE_PRIVATE int sqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc){ + int rc = *pRc; + int bMiss = 0; + if( rc==SQLITE_OK ){ + + /* If there are one or more deferred tokens, load the current row into + ** memory and scan it to determine the position list for each deferred + ** token. Then, see if this row is really a match, considering deferred + ** tokens and NEAR operators (neither of which were taken into account + ** earlier, by fts3EvalNextRow()). + */ + if( pCsr->pDeferred ){ + rc = fts3CursorSeek(0, pCsr); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3CacheDeferredDoclists(pCsr); + } + } + bMiss = (0==fts3EvalTestExpr(pCsr, pCsr->pExpr, &rc)); + + /* Free the position-lists accumulated for each deferred token above. */ + sqlite3Fts3FreeDeferredDoclists(pCsr); + *pRc = rc; + } + return (rc==SQLITE_OK && bMiss); +} + +/* +** Advance to the next document that matches the FTS expression in +** Fts3Cursor.pExpr. +*/ +static int fts3EvalNext(Fts3Cursor *pCsr){ + int rc = SQLITE_OK; /* Return Code */ + Fts3Expr *pExpr = pCsr->pExpr; + assert( pCsr->isEof==0 ); + if( pExpr==0 ){ + pCsr->isEof = 1; + }else{ + do { + if( pCsr->isRequireSeek==0 ){ + sqlite3_reset(pCsr->pStmt); + } + assert( sqlite3_data_count(pCsr->pStmt)==0 ); + fts3EvalNextRow(pCsr, pExpr, &rc); + pCsr->isEof = pExpr->bEof; + pCsr->isRequireSeek = 1; + pCsr->isMatchinfoNeeded = 1; + pCsr->iPrevId = pExpr->iDocid; + }while( pCsr->isEof==0 && sqlite3Fts3EvalTestDeferred(pCsr, &rc) ); + } + + /* Check if the cursor is past the end of the docid range specified + ** by Fts3Cursor.iMinDocid/iMaxDocid. If so, set the EOF flag. */ + if( rc==SQLITE_OK && ( + (pCsr->bDesc==0 && pCsr->iPrevId>pCsr->iMaxDocid) + || (pCsr->bDesc!=0 && pCsr->iPrevId<pCsr->iMinDocid) + )){ + pCsr->isEof = 1; + } + + return rc; +} + +/* +** Restart interation for expression pExpr so that the next call to +** fts3EvalNext() visits the first row. Do not allow incremental +** loading or merging of phrase doclists for this iteration. +** +** If *pRc is other than SQLITE_OK when this function is called, it is +** a no-op. If an error occurs within this function, *pRc is set to an +** SQLite error code before returning. +*/ +static void fts3EvalRestart( + Fts3Cursor *pCsr, + Fts3Expr *pExpr, + int *pRc +){ + if( pExpr && *pRc==SQLITE_OK ){ + Fts3Phrase *pPhrase = pExpr->pPhrase; + + if( pPhrase ){ + fts3EvalInvalidatePoslist(pPhrase); + if( pPhrase->bIncr ){ + int i; + for(i=0; i<pPhrase->nToken; i++){ + Fts3PhraseToken *pToken = &pPhrase->aToken[i]; + assert( pToken->pDeferred==0 ); + if( pToken->pSegcsr ){ + sqlite3Fts3MsrIncrRestart(pToken->pSegcsr); + } + } + *pRc = fts3EvalPhraseStart(pCsr, 0, pPhrase); + } + pPhrase->doclist.pNextDocid = 0; + pPhrase->doclist.iDocid = 0; + pPhrase->pOrPoslist = 0; + } + + pExpr->iDocid = 0; + pExpr->bEof = 0; + pExpr->bStart = 0; + + fts3EvalRestart(pCsr, pExpr->pLeft, pRc); + fts3EvalRestart(pCsr, pExpr->pRight, pRc); + } +} + +/* +** After allocating the Fts3Expr.aMI[] array for each phrase in the +** expression rooted at pExpr, the cursor iterates through all rows matched +** by pExpr, calling this function for each row. This function increments +** the values in Fts3Expr.aMI[] according to the position-list currently +** found in Fts3Expr.pPhrase->doclist.pList for each of the phrase +** expression nodes. +*/ +static void fts3EvalUpdateCounts(Fts3Expr *pExpr, int nCol){ + if( pExpr ){ + Fts3Phrase *pPhrase = pExpr->pPhrase; + if( pPhrase && pPhrase->doclist.pList ){ + int iCol = 0; + char *p = pPhrase->doclist.pList; + + do{ + u8 c = 0; + int iCnt = 0; + while( 0xFE & (*p | c) ){ + if( (c&0x80)==0 ) iCnt++; + c = *p++ & 0x80; + } + + /* aMI[iCol*3 + 1] = Number of occurrences + ** aMI[iCol*3 + 2] = Number of rows containing at least one instance + */ + pExpr->aMI[iCol*3 + 1] += iCnt; + pExpr->aMI[iCol*3 + 2] += (iCnt>0); + if( *p==0x00 ) break; + p++; + p += fts3GetVarint32(p, &iCol); + }while( iCol<nCol ); + } + + fts3EvalUpdateCounts(pExpr->pLeft, nCol); + fts3EvalUpdateCounts(pExpr->pRight, nCol); + } +} + +/* +** This is an sqlite3Fts3ExprIterate() callback. If the Fts3Expr.aMI[] array +** has not yet been allocated, allocate and zero it. Otherwise, just zero +** it. +*/ +static int fts3AllocateMSI(Fts3Expr *pExpr, int iPhrase, void *pCtx){ + Fts3Table *pTab = (Fts3Table*)pCtx; + UNUSED_PARAMETER(iPhrase); + if( pExpr->aMI==0 ){ + pExpr->aMI = (u32 *)sqlite3_malloc64(pTab->nColumn * 3 * sizeof(u32)); + if( pExpr->aMI==0 ) return SQLITE_NOMEM; + } + memset(pExpr->aMI, 0, pTab->nColumn * 3 * sizeof(u32)); + return SQLITE_OK; +} + +/* +** Expression pExpr must be of type FTSQUERY_PHRASE. +** +** If it is not already allocated and populated, this function allocates and +** populates the Fts3Expr.aMI[] array for expression pExpr. If pExpr is part +** of a NEAR expression, then it also allocates and populates the same array +** for all other phrases that are part of the NEAR expression. +** +** SQLITE_OK is returned if the aMI[] array is successfully allocated and +** populated. Otherwise, if an error occurs, an SQLite error code is returned. +*/ +static int fts3EvalGatherStats( + Fts3Cursor *pCsr, /* Cursor object */ + Fts3Expr *pExpr /* FTSQUERY_PHRASE expression */ +){ + int rc = SQLITE_OK; /* Return code */ + + assert( pExpr->eType==FTSQUERY_PHRASE ); + if( pExpr->aMI==0 ){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + Fts3Expr *pRoot; /* Root of NEAR expression */ + + sqlite3_int64 iPrevId = pCsr->iPrevId; + sqlite3_int64 iDocid; + u8 bEof; + + /* Find the root of the NEAR expression */ + pRoot = pExpr; + while( pRoot->pParent + && (pRoot->pParent->eType==FTSQUERY_NEAR || pRoot->bDeferred) + ){ + pRoot = pRoot->pParent; + } + iDocid = pRoot->iDocid; + bEof = pRoot->bEof; + assert( pRoot->bStart ); + + /* Allocate space for the aMSI[] array of each FTSQUERY_PHRASE node */ + rc = sqlite3Fts3ExprIterate(pRoot, fts3AllocateMSI, (void*)pTab); + if( rc!=SQLITE_OK ) return rc; + fts3EvalRestart(pCsr, pRoot, &rc); + + while( pCsr->isEof==0 && rc==SQLITE_OK ){ + + do { + /* Ensure the %_content statement is reset. */ + if( pCsr->isRequireSeek==0 ) sqlite3_reset(pCsr->pStmt); + assert( sqlite3_data_count(pCsr->pStmt)==0 ); + + /* Advance to the next document */ + fts3EvalNextRow(pCsr, pRoot, &rc); + pCsr->isEof = pRoot->bEof; + pCsr->isRequireSeek = 1; + pCsr->isMatchinfoNeeded = 1; + pCsr->iPrevId = pRoot->iDocid; + }while( pCsr->isEof==0 + && pRoot->eType==FTSQUERY_NEAR + && sqlite3Fts3EvalTestDeferred(pCsr, &rc) + ); + + if( rc==SQLITE_OK && pCsr->isEof==0 ){ + fts3EvalUpdateCounts(pRoot, pTab->nColumn); + } + } + + pCsr->isEof = 0; + pCsr->iPrevId = iPrevId; + + if( bEof ){ + pRoot->bEof = bEof; + }else{ + /* Caution: pRoot may iterate through docids in ascending or descending + ** order. For this reason, even though it seems more defensive, the + ** do loop can not be written: + ** + ** do {...} while( pRoot->iDocid<iDocid && rc==SQLITE_OK ); + */ + fts3EvalRestart(pCsr, pRoot, &rc); + do { + fts3EvalNextRow(pCsr, pRoot, &rc); + assert_fts3_nc( pRoot->bEof==0 ); + if( pRoot->bEof ) rc = FTS_CORRUPT_VTAB; + }while( pRoot->iDocid!=iDocid && rc==SQLITE_OK ); + } + } + return rc; +} + +/* +** This function is used by the matchinfo() module to query a phrase +** expression node for the following information: +** +** 1. The total number of occurrences of the phrase in each column of +** the FTS table (considering all rows), and +** +** 2. For each column, the number of rows in the table for which the +** column contains at least one instance of the phrase. +** +** If no error occurs, SQLITE_OK is returned and the values for each column +** written into the array aiOut as follows: +** +** aiOut[iCol*3 + 1] = Number of occurrences +** aiOut[iCol*3 + 2] = Number of rows containing at least one instance +** +** Caveats: +** +** * If a phrase consists entirely of deferred tokens, then all output +** values are set to the number of documents in the table. In other +** words we assume that very common tokens occur exactly once in each +** column of each row of the table. +** +** * If a phrase contains some deferred tokens (and some non-deferred +** tokens), count the potential occurrence identified by considering +** the non-deferred tokens instead of actual phrase occurrences. +** +** * If the phrase is part of a NEAR expression, then only phrase instances +** that meet the NEAR constraint are included in the counts. +*/ +SQLITE_PRIVATE int sqlite3Fts3EvalPhraseStats( + Fts3Cursor *pCsr, /* FTS cursor handle */ + Fts3Expr *pExpr, /* Phrase expression */ + u32 *aiOut /* Array to write results into (see above) */ +){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + int rc = SQLITE_OK; + int iCol; + + if( pExpr->bDeferred && pExpr->pParent->eType!=FTSQUERY_NEAR ){ + assert( pCsr->nDoc>0 ); + for(iCol=0; iCol<pTab->nColumn; iCol++){ + aiOut[iCol*3 + 1] = (u32)pCsr->nDoc; + aiOut[iCol*3 + 2] = (u32)pCsr->nDoc; + } + }else{ + rc = fts3EvalGatherStats(pCsr, pExpr); + if( rc==SQLITE_OK ){ + assert( pExpr->aMI ); + for(iCol=0; iCol<pTab->nColumn; iCol++){ + aiOut[iCol*3 + 1] = pExpr->aMI[iCol*3 + 1]; + aiOut[iCol*3 + 2] = pExpr->aMI[iCol*3 + 2]; + } + } + } + + return rc; +} + +/* +** The expression pExpr passed as the second argument to this function +** must be of type FTSQUERY_PHRASE. +** +** The returned value is either NULL or a pointer to a buffer containing +** a position-list indicating the occurrences of the phrase in column iCol +** of the current row. +** +** More specifically, the returned buffer contains 1 varint for each +** occurrence of the phrase in the column, stored using the normal (delta+2) +** compression and is terminated by either an 0x01 or 0x00 byte. For example, +** if the requested column contains "a b X c d X X" and the position-list +** for 'X' is requested, the buffer returned may contain: +** +** 0x04 0x05 0x03 0x01 or 0x04 0x05 0x03 0x00 +** +** This function works regardless of whether or not the phrase is deferred, +** incremental, or neither. +*/ +SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist( + Fts3Cursor *pCsr, /* FTS3 cursor object */ + Fts3Expr *pExpr, /* Phrase to return doclist for */ + int iCol, /* Column to return position list for */ + char **ppOut /* OUT: Pointer to position list */ +){ + Fts3Phrase *pPhrase = pExpr->pPhrase; + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + char *pIter; + int iThis; + sqlite3_int64 iDocid; + + /* If this phrase is applies specifically to some column other than + ** column iCol, return a NULL pointer. */ + *ppOut = 0; + assert( iCol>=0 && iCol<pTab->nColumn ); + if( (pPhrase->iColumn<pTab->nColumn && pPhrase->iColumn!=iCol) ){ + return SQLITE_OK; + } + + iDocid = pExpr->iDocid; + pIter = pPhrase->doclist.pList; + if( iDocid!=pCsr->iPrevId || pExpr->bEof ){ + int rc = SQLITE_OK; + int bDescDoclist = pTab->bDescIdx; /* For DOCID_CMP macro */ + int bOr = 0; + u8 bTreeEof = 0; + Fts3Expr *p; /* Used to iterate from pExpr to root */ + Fts3Expr *pNear; /* Most senior NEAR ancestor (or pExpr) */ + Fts3Expr *pRun; /* Closest non-deferred ancestor of pNear */ + int bMatch; + + /* Check if this phrase descends from an OR expression node. If not, + ** return NULL. Otherwise, the entry that corresponds to docid + ** pCsr->iPrevId may lie earlier in the doclist buffer. Or, if the + ** tree that the node is part of has been marked as EOF, but the node + ** itself is not EOF, then it may point to an earlier entry. */ + pNear = pExpr; + for(p=pExpr->pParent; p; p=p->pParent){ + if( p->eType==FTSQUERY_OR ) bOr = 1; + if( p->eType==FTSQUERY_NEAR ) pNear = p; + if( p->bEof ) bTreeEof = 1; + } + if( bOr==0 ) return SQLITE_OK; + pRun = pNear; + while( pRun->bDeferred ){ + assert( pRun->pParent ); + pRun = pRun->pParent; + } + + /* This is the descendent of an OR node. In this case we cannot use + ** an incremental phrase. Load the entire doclist for the phrase + ** into memory in this case. */ + if( pPhrase->bIncr ){ + int bEofSave = pRun->bEof; + fts3EvalRestart(pCsr, pRun, &rc); + while( rc==SQLITE_OK && !pRun->bEof ){ + fts3EvalNextRow(pCsr, pRun, &rc); + if( bEofSave==0 && pRun->iDocid==iDocid ) break; + } + assert( rc!=SQLITE_OK || pPhrase->bIncr==0 ); + if( rc==SQLITE_OK && pRun->bEof!=bEofSave ){ + rc = FTS_CORRUPT_VTAB; + } + } + if( bTreeEof ){ + while( rc==SQLITE_OK && !pRun->bEof ){ + fts3EvalNextRow(pCsr, pRun, &rc); + } + } + if( rc!=SQLITE_OK ) return rc; + + bMatch = 1; + for(p=pNear; p; p=p->pLeft){ + u8 bEof = 0; + Fts3Expr *pTest = p; + Fts3Phrase *pPh; + assert( pTest->eType==FTSQUERY_NEAR || pTest->eType==FTSQUERY_PHRASE ); + if( pTest->eType==FTSQUERY_NEAR ) pTest = pTest->pRight; + assert( pTest->eType==FTSQUERY_PHRASE ); + pPh = pTest->pPhrase; + + pIter = pPh->pOrPoslist; + iDocid = pPh->iOrDocid; + if( pCsr->bDesc==bDescDoclist ){ + bEof = !pPh->doclist.nAll || + (pIter >= (pPh->doclist.aAll + pPh->doclist.nAll)); + while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)<0 ) && bEof==0 ){ + sqlite3Fts3DoclistNext( + bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll, + &pIter, &iDocid, &bEof + ); + } + }else{ + bEof = !pPh->doclist.nAll || (pIter && pIter<=pPh->doclist.aAll); + while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)>0 ) && bEof==0 ){ + int dummy; + sqlite3Fts3DoclistPrev( + bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll, + &pIter, &iDocid, &dummy, &bEof + ); + } + } + pPh->pOrPoslist = pIter; + pPh->iOrDocid = iDocid; + if( bEof || iDocid!=pCsr->iPrevId ) bMatch = 0; + } + + if( bMatch ){ + pIter = pPhrase->pOrPoslist; + }else{ + pIter = 0; + } + } + if( pIter==0 ) return SQLITE_OK; + + if( *pIter==0x01 ){ + pIter++; + pIter += fts3GetVarint32(pIter, &iThis); + }else{ + iThis = 0; + } + while( iThis<iCol ){ + fts3ColumnlistCopy(0, &pIter); + if( *pIter==0x00 ) return SQLITE_OK; + pIter++; + pIter += fts3GetVarint32(pIter, &iThis); + } + if( *pIter==0x00 ){ + pIter = 0; + } + + *ppOut = ((iCol==iThis)?pIter:0); + return SQLITE_OK; +} + +/* +** Free all components of the Fts3Phrase structure that were allocated by +** the eval module. Specifically, this means to free: +** +** * the contents of pPhrase->doclist, and +** * any Fts3MultiSegReader objects held by phrase tokens. +*/ +SQLITE_PRIVATE void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *pPhrase){ + if( pPhrase ){ + int i; + sqlite3_free(pPhrase->doclist.aAll); + fts3EvalInvalidatePoslist(pPhrase); + memset(&pPhrase->doclist, 0, sizeof(Fts3Doclist)); + for(i=0; i<pPhrase->nToken; i++){ + fts3SegReaderCursorFree(pPhrase->aToken[i].pSegcsr); + pPhrase->aToken[i].pSegcsr = 0; + } + } +} + + +/* +** Return SQLITE_CORRUPT_VTAB. +*/ +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3Fts3Corrupt(){ + return SQLITE_CORRUPT_VTAB; +} +#endif + +#if !SQLITE_CORE +/* +** Initialize API pointer table, if required. +*/ +#ifdef _WIN32 +__declspec(dllexport) +#endif +SQLITE_API int sqlite3_fts3_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi) + return sqlite3Fts3Init(db); +} +#endif + +#endif + +/************** End of fts3.c ************************************************/ +/************** Begin file fts3_aux.c ****************************************/ +/* +** 2011 Jan 27 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +*/ +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* #include <string.h> */ +/* #include <assert.h> */ + +typedef struct Fts3auxTable Fts3auxTable; +typedef struct Fts3auxCursor Fts3auxCursor; + +struct Fts3auxTable { + sqlite3_vtab base; /* Base class used by SQLite core */ + Fts3Table *pFts3Tab; +}; + +struct Fts3auxCursor { + sqlite3_vtab_cursor base; /* Base class used by SQLite core */ + Fts3MultiSegReader csr; /* Must be right after "base" */ + Fts3SegFilter filter; + char *zStop; + int nStop; /* Byte-length of string zStop */ + int iLangid; /* Language id to query */ + int isEof; /* True if cursor is at EOF */ + sqlite3_int64 iRowid; /* Current rowid */ + + int iCol; /* Current value of 'col' column */ + int nStat; /* Size of aStat[] array */ + struct Fts3auxColstats { + sqlite3_int64 nDoc; /* 'documents' values for current csr row */ + sqlite3_int64 nOcc; /* 'occurrences' values for current csr row */ + } *aStat; +}; + +/* +** Schema of the terms table. +*/ +#define FTS3_AUX_SCHEMA \ + "CREATE TABLE x(term, col, documents, occurrences, languageid HIDDEN)" + +/* +** This function does all the work for both the xConnect and xCreate methods. +** These tables have no persistent representation of their own, so xConnect +** and xCreate are identical operations. +*/ +static int fts3auxConnectMethod( + sqlite3 *db, /* Database connection */ + void *pUnused, /* Unused */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ + char **pzErr /* OUT: sqlite3_malloc'd error message */ +){ + char const *zDb; /* Name of database (e.g. "main") */ + char const *zFts3; /* Name of fts3 table */ + int nDb; /* Result of strlen(zDb) */ + int nFts3; /* Result of strlen(zFts3) */ + sqlite3_int64 nByte; /* Bytes of space to allocate here */ + int rc; /* value returned by declare_vtab() */ + Fts3auxTable *p; /* Virtual table object to return */ + + UNUSED_PARAMETER(pUnused); + + /* The user should invoke this in one of two forms: + ** + ** CREATE VIRTUAL TABLE xxx USING fts4aux(fts4-table); + ** CREATE VIRTUAL TABLE xxx USING fts4aux(fts4-table-db, fts4-table); + */ + if( argc!=4 && argc!=5 ) goto bad_args; + + zDb = argv[1]; + nDb = (int)strlen(zDb); + if( argc==5 ){ + if( nDb==4 && 0==sqlite3_strnicmp("temp", zDb, 4) ){ + zDb = argv[3]; + nDb = (int)strlen(zDb); + zFts3 = argv[4]; + }else{ + goto bad_args; + } + }else{ + zFts3 = argv[3]; + } + nFts3 = (int)strlen(zFts3); + + rc = sqlite3_declare_vtab(db, FTS3_AUX_SCHEMA); + if( rc!=SQLITE_OK ) return rc; + + nByte = sizeof(Fts3auxTable) + sizeof(Fts3Table) + nDb + nFts3 + 2; + p = (Fts3auxTable *)sqlite3_malloc64(nByte); + if( !p ) return SQLITE_NOMEM; + memset(p, 0, nByte); + + p->pFts3Tab = (Fts3Table *)&p[1]; + p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1]; + p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1]; + p->pFts3Tab->db = db; + p->pFts3Tab->nIndex = 1; + + memcpy((char *)p->pFts3Tab->zDb, zDb, nDb); + memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3); + sqlite3Fts3Dequote((char *)p->pFts3Tab->zName); + + *ppVtab = (sqlite3_vtab *)p; + return SQLITE_OK; + + bad_args: + sqlite3Fts3ErrMsg(pzErr, "invalid arguments to fts4aux constructor"); + return SQLITE_ERROR; +} + +/* +** This function does the work for both the xDisconnect and xDestroy methods. +** These tables have no persistent representation of their own, so xDisconnect +** and xDestroy are identical operations. +*/ +static int fts3auxDisconnectMethod(sqlite3_vtab *pVtab){ + Fts3auxTable *p = (Fts3auxTable *)pVtab; + Fts3Table *pFts3 = p->pFts3Tab; + int i; + + /* Free any prepared statements held */ + for(i=0; i<SizeofArray(pFts3->aStmt); i++){ + sqlite3_finalize(pFts3->aStmt[i]); + } + sqlite3_free(pFts3->zSegmentsTbl); + sqlite3_free(p); + return SQLITE_OK; +} + +#define FTS4AUX_EQ_CONSTRAINT 1 +#define FTS4AUX_GE_CONSTRAINT 2 +#define FTS4AUX_LE_CONSTRAINT 4 + +/* +** xBestIndex - Analyze a WHERE and ORDER BY clause. +*/ +static int fts3auxBestIndexMethod( + sqlite3_vtab *pVTab, + sqlite3_index_info *pInfo +){ + int i; + int iEq = -1; + int iGe = -1; + int iLe = -1; + int iLangid = -1; + int iNext = 1; /* Next free argvIndex value */ + + UNUSED_PARAMETER(pVTab); + + /* This vtab delivers always results in "ORDER BY term ASC" order. */ + if( pInfo->nOrderBy==1 + && pInfo->aOrderBy[0].iColumn==0 + && pInfo->aOrderBy[0].desc==0 + ){ + pInfo->orderByConsumed = 1; + } + + /* Search for equality and range constraints on the "term" column. + ** And equality constraints on the hidden "languageid" column. */ + for(i=0; i<pInfo->nConstraint; i++){ + if( pInfo->aConstraint[i].usable ){ + int op = pInfo->aConstraint[i].op; + int iCol = pInfo->aConstraint[i].iColumn; + + if( iCol==0 ){ + if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iEq = i; + if( op==SQLITE_INDEX_CONSTRAINT_LT ) iLe = i; + if( op==SQLITE_INDEX_CONSTRAINT_LE ) iLe = i; + if( op==SQLITE_INDEX_CONSTRAINT_GT ) iGe = i; + if( op==SQLITE_INDEX_CONSTRAINT_GE ) iGe = i; + } + if( iCol==4 ){ + if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iLangid = i; + } + } + } + + if( iEq>=0 ){ + pInfo->idxNum = FTS4AUX_EQ_CONSTRAINT; + pInfo->aConstraintUsage[iEq].argvIndex = iNext++; + pInfo->estimatedCost = 5; + }else{ + pInfo->idxNum = 0; + pInfo->estimatedCost = 20000; + if( iGe>=0 ){ + pInfo->idxNum += FTS4AUX_GE_CONSTRAINT; + pInfo->aConstraintUsage[iGe].argvIndex = iNext++; + pInfo->estimatedCost /= 2; + } + if( iLe>=0 ){ + pInfo->idxNum += FTS4AUX_LE_CONSTRAINT; + pInfo->aConstraintUsage[iLe].argvIndex = iNext++; + pInfo->estimatedCost /= 2; + } + } + if( iLangid>=0 ){ + pInfo->aConstraintUsage[iLangid].argvIndex = iNext++; + pInfo->estimatedCost--; + } + + return SQLITE_OK; +} + +/* +** xOpen - Open a cursor. +*/ +static int fts3auxOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ + Fts3auxCursor *pCsr; /* Pointer to cursor object to return */ + + UNUSED_PARAMETER(pVTab); + + pCsr = (Fts3auxCursor *)sqlite3_malloc(sizeof(Fts3auxCursor)); + if( !pCsr ) return SQLITE_NOMEM; + memset(pCsr, 0, sizeof(Fts3auxCursor)); + + *ppCsr = (sqlite3_vtab_cursor *)pCsr; + return SQLITE_OK; +} + +/* +** xClose - Close a cursor. +*/ +static int fts3auxCloseMethod(sqlite3_vtab_cursor *pCursor){ + Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab; + Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; + + sqlite3Fts3SegmentsClose(pFts3); + sqlite3Fts3SegReaderFinish(&pCsr->csr); + sqlite3_free((void *)pCsr->filter.zTerm); + sqlite3_free(pCsr->zStop); + sqlite3_free(pCsr->aStat); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +static int fts3auxGrowStatArray(Fts3auxCursor *pCsr, int nSize){ + if( nSize>pCsr->nStat ){ + struct Fts3auxColstats *aNew; + aNew = (struct Fts3auxColstats *)sqlite3_realloc64(pCsr->aStat, + sizeof(struct Fts3auxColstats) * nSize + ); + if( aNew==0 ) return SQLITE_NOMEM; + memset(&aNew[pCsr->nStat], 0, + sizeof(struct Fts3auxColstats) * (nSize - pCsr->nStat) + ); + pCsr->aStat = aNew; + pCsr->nStat = nSize; + } + return SQLITE_OK; +} + +/* +** xNext - Advance the cursor to the next row, if any. +*/ +static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ + Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; + Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab; + int rc; + + /* Increment our pretend rowid value. */ + pCsr->iRowid++; + + for(pCsr->iCol++; pCsr->iCol<pCsr->nStat; pCsr->iCol++){ + if( pCsr->aStat[pCsr->iCol].nDoc>0 ) return SQLITE_OK; + } + + rc = sqlite3Fts3SegReaderStep(pFts3, &pCsr->csr); + if( rc==SQLITE_ROW ){ + int i = 0; + int nDoclist = pCsr->csr.nDoclist; + char *aDoclist = pCsr->csr.aDoclist; + int iCol; + + int eState = 0; + + if( pCsr->zStop ){ + int n = (pCsr->nStop<pCsr->csr.nTerm) ? pCsr->nStop : pCsr->csr.nTerm; + int mc = memcmp(pCsr->zStop, pCsr->csr.zTerm, n); + if( mc<0 || (mc==0 && pCsr->csr.nTerm>pCsr->nStop) ){ + pCsr->isEof = 1; + return SQLITE_OK; + } + } + + if( fts3auxGrowStatArray(pCsr, 2) ) return SQLITE_NOMEM; + memset(pCsr->aStat, 0, sizeof(struct Fts3auxColstats) * pCsr->nStat); + iCol = 0; + rc = SQLITE_OK; + + while( i<nDoclist ){ + sqlite3_int64 v = 0; + + i += sqlite3Fts3GetVarint(&aDoclist[i], &v); + switch( eState ){ + /* State 0. In this state the integer just read was a docid. */ + case 0: + pCsr->aStat[0].nDoc++; + eState = 1; + iCol = 0; + break; + + /* State 1. In this state we are expecting either a 1, indicating + ** that the following integer will be a column number, or the + ** start of a position list for column 0. + ** + ** The only difference between state 1 and state 2 is that if the + ** integer encountered in state 1 is not 0 or 1, then we need to + ** increment the column 0 "nDoc" count for this term. + */ + case 1: + assert( iCol==0 ); + if( v>1 ){ + pCsr->aStat[1].nDoc++; + } + eState = 2; + /* fall through */ + + case 2: + if( v==0 ){ /* 0x00. Next integer will be a docid. */ + eState = 0; + }else if( v==1 ){ /* 0x01. Next integer will be a column number. */ + eState = 3; + }else{ /* 2 or greater. A position. */ + pCsr->aStat[iCol+1].nOcc++; + pCsr->aStat[0].nOcc++; + } + break; + + /* State 3. The integer just read is a column number. */ + default: assert( eState==3 ); + iCol = (int)v; + if( iCol<1 ){ + rc = SQLITE_CORRUPT_VTAB; + break; + } + if( fts3auxGrowStatArray(pCsr, iCol+2) ) return SQLITE_NOMEM; + pCsr->aStat[iCol+1].nDoc++; + eState = 2; + break; + } + } + + pCsr->iCol = 0; + }else{ + pCsr->isEof = 1; + } + return rc; +} + +/* +** xFilter - Initialize a cursor to point at the start of its data. +*/ +static int fts3auxFilterMethod( + sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ + int idxNum, /* Strategy index */ + const char *idxStr, /* Unused */ + int nVal, /* Number of elements in apVal */ + sqlite3_value **apVal /* Arguments for the indexing scheme */ +){ + Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; + Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab; + int rc; + int isScan = 0; + int iLangVal = 0; /* Language id to query */ + + int iEq = -1; /* Index of term=? value in apVal */ + int iGe = -1; /* Index of term>=? value in apVal */ + int iLe = -1; /* Index of term<=? value in apVal */ + int iLangid = -1; /* Index of languageid=? value in apVal */ + int iNext = 0; + + UNUSED_PARAMETER(nVal); + UNUSED_PARAMETER(idxStr); + + assert( idxStr==0 ); + assert( idxNum==FTS4AUX_EQ_CONSTRAINT || idxNum==0 + || idxNum==FTS4AUX_LE_CONSTRAINT || idxNum==FTS4AUX_GE_CONSTRAINT + || idxNum==(FTS4AUX_LE_CONSTRAINT|FTS4AUX_GE_CONSTRAINT) + ); + + if( idxNum==FTS4AUX_EQ_CONSTRAINT ){ + iEq = iNext++; + }else{ + isScan = 1; + if( idxNum & FTS4AUX_GE_CONSTRAINT ){ + iGe = iNext++; + } + if( idxNum & FTS4AUX_LE_CONSTRAINT ){ + iLe = iNext++; + } + } + if( iNext<nVal ){ + iLangid = iNext++; + } + + /* In case this cursor is being reused, close and zero it. */ + testcase(pCsr->filter.zTerm); + sqlite3Fts3SegReaderFinish(&pCsr->csr); + sqlite3_free((void *)pCsr->filter.zTerm); + sqlite3_free(pCsr->aStat); + sqlite3_free(pCsr->zStop); + memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr); + + pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY; + if( isScan ) pCsr->filter.flags |= FTS3_SEGMENT_SCAN; + + if( iEq>=0 || iGe>=0 ){ + const unsigned char *zStr = sqlite3_value_text(apVal[0]); + assert( (iEq==0 && iGe==-1) || (iEq==-1 && iGe==0) ); + if( zStr ){ + pCsr->filter.zTerm = sqlite3_mprintf("%s", zStr); + if( pCsr->filter.zTerm==0 ) return SQLITE_NOMEM; + pCsr->filter.nTerm = (int)strlen(pCsr->filter.zTerm); + } + } + + if( iLe>=0 ){ + pCsr->zStop = sqlite3_mprintf("%s", sqlite3_value_text(apVal[iLe])); + if( pCsr->zStop==0 ) return SQLITE_NOMEM; + pCsr->nStop = (int)strlen(pCsr->zStop); + } + + if( iLangid>=0 ){ + iLangVal = sqlite3_value_int(apVal[iLangid]); + + /* If the user specified a negative value for the languageid, use zero + ** instead. This works, as the "languageid=?" constraint will also + ** be tested by the VDBE layer. The test will always be false (since + ** this module will not return a row with a negative languageid), and + ** so the overall query will return zero rows. */ + if( iLangVal<0 ) iLangVal = 0; + } + pCsr->iLangid = iLangVal; + + rc = sqlite3Fts3SegReaderCursor(pFts3, iLangVal, 0, FTS3_SEGCURSOR_ALL, + pCsr->filter.zTerm, pCsr->filter.nTerm, 0, isScan, &pCsr->csr + ); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter); + } + + if( rc==SQLITE_OK ) rc = fts3auxNextMethod(pCursor); + return rc; +} + +/* +** xEof - Return true if the cursor is at EOF, or false otherwise. +*/ +static int fts3auxEofMethod(sqlite3_vtab_cursor *pCursor){ + Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; + return pCsr->isEof; +} + +/* +** xColumn - Return a column value. +*/ +static int fts3auxColumnMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ + int iCol /* Index of column to read value from */ +){ + Fts3auxCursor *p = (Fts3auxCursor *)pCursor; + + assert( p->isEof==0 ); + switch( iCol ){ + case 0: /* term */ + sqlite3_result_text(pCtx, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT); + break; + + case 1: /* col */ + if( p->iCol ){ + sqlite3_result_int(pCtx, p->iCol-1); + }else{ + sqlite3_result_text(pCtx, "*", -1, SQLITE_STATIC); + } + break; + + case 2: /* documents */ + sqlite3_result_int64(pCtx, p->aStat[p->iCol].nDoc); + break; + + case 3: /* occurrences */ + sqlite3_result_int64(pCtx, p->aStat[p->iCol].nOcc); + break; + + default: /* languageid */ + assert( iCol==4 ); + sqlite3_result_int(pCtx, p->iLangid); + break; + } + + return SQLITE_OK; +} + +/* +** xRowid - Return the current rowid for the cursor. +*/ +static int fts3auxRowidMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite_int64 *pRowid /* OUT: Rowid value */ +){ + Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; + *pRowid = pCsr->iRowid; + return SQLITE_OK; +} + +/* +** Register the fts3aux module with database connection db. Return SQLITE_OK +** if successful or an error code if sqlite3_create_module() fails. +*/ +SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db){ + static const sqlite3_module fts3aux_module = { + 0, /* iVersion */ + fts3auxConnectMethod, /* xCreate */ + fts3auxConnectMethod, /* xConnect */ + fts3auxBestIndexMethod, /* xBestIndex */ + fts3auxDisconnectMethod, /* xDisconnect */ + fts3auxDisconnectMethod, /* xDestroy */ + fts3auxOpenMethod, /* xOpen */ + fts3auxCloseMethod, /* xClose */ + fts3auxFilterMethod, /* xFilter */ + fts3auxNextMethod, /* xNext */ + fts3auxEofMethod, /* xEof */ + fts3auxColumnMethod, /* xColumn */ + fts3auxRowidMethod, /* xRowid */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindFunction */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ + }; + int rc; /* Return code */ + + rc = sqlite3_create_module(db, "fts4aux", &fts3aux_module, 0); + return rc; +} + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ + +/************** End of fts3_aux.c ********************************************/ +/************** Begin file fts3_expr.c ***************************************/ +/* +** 2008 Nov 28 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This module contains code that implements a parser for fts3 query strings +** (the right-hand argument to the MATCH operator). Because the supported +** syntax is relatively simple, the whole tokenizer/parser system is +** hand-coded. +*/ +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* +** By default, this module parses the legacy syntax that has been +** traditionally used by fts3. Or, if SQLITE_ENABLE_FTS3_PARENTHESIS +** is defined, then it uses the new syntax. The differences between +** the new and the old syntaxes are: +** +** a) The new syntax supports parenthesis. The old does not. +** +** b) The new syntax supports the AND and NOT operators. The old does not. +** +** c) The old syntax supports the "-" token qualifier. This is not +** supported by the new syntax (it is replaced by the NOT operator). +** +** d) When using the old syntax, the OR operator has a greater precedence +** than an implicit AND. When using the new, both implicity and explicit +** AND operators have a higher precedence than OR. +** +** If compiled with SQLITE_TEST defined, then this module exports the +** symbol "int sqlite3_fts3_enable_parentheses". Setting this variable +** to zero causes the module to use the old syntax. If it is set to +** non-zero the new syntax is activated. This is so both syntaxes can +** be tested using a single build of testfixture. +** +** The following describes the syntax supported by the fts3 MATCH +** operator in a similar format to that used by the lemon parser +** generator. This module does not use actually lemon, it uses a +** custom parser. +** +** query ::= andexpr (OR andexpr)*. +** +** andexpr ::= notexpr (AND? notexpr)*. +** +** notexpr ::= nearexpr (NOT nearexpr|-TOKEN)*. +** notexpr ::= LP query RP. +** +** nearexpr ::= phrase (NEAR distance_opt nearexpr)*. +** +** distance_opt ::= . +** distance_opt ::= / INTEGER. +** +** phrase ::= TOKEN. +** phrase ::= COLUMN:TOKEN. +** phrase ::= "TOKEN TOKEN TOKEN...". +*/ + +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_fts3_enable_parentheses = 0; +#else +# ifdef SQLITE_ENABLE_FTS3_PARENTHESIS +# define sqlite3_fts3_enable_parentheses 1 +# else +# define sqlite3_fts3_enable_parentheses 0 +# endif +#endif + +/* +** Default span for NEAR operators. +*/ +#define SQLITE_FTS3_DEFAULT_NEAR_PARAM 10 + +/* #include <string.h> */ +/* #include <assert.h> */ + +/* +** isNot: +** This variable is used by function getNextNode(). When getNextNode() is +** called, it sets ParseContext.isNot to true if the 'next node' is a +** FTSQUERY_PHRASE with a unary "-" attached to it. i.e. "mysql" in the +** FTS3 query "sqlite -mysql". Otherwise, ParseContext.isNot is set to +** zero. +*/ +typedef struct ParseContext ParseContext; +struct ParseContext { + sqlite3_tokenizer *pTokenizer; /* Tokenizer module */ + int iLangid; /* Language id used with tokenizer */ + const char **azCol; /* Array of column names for fts3 table */ + int bFts4; /* True to allow FTS4-only syntax */ + int nCol; /* Number of entries in azCol[] */ + int iDefaultCol; /* Default column to query */ + int isNot; /* True if getNextNode() sees a unary - */ + sqlite3_context *pCtx; /* Write error message here */ + int nNest; /* Number of nested brackets */ +}; + +/* +** This function is equivalent to the standard isspace() function. +** +** The standard isspace() can be awkward to use safely, because although it +** is defined to accept an argument of type int, its behavior when passed +** an integer that falls outside of the range of the unsigned char type +** is undefined (and sometimes, "undefined" means segfault). This wrapper +** is defined to accept an argument of type char, and always returns 0 for +** any values that fall outside of the range of the unsigned char type (i.e. +** negative values). +*/ +static int fts3isspace(char c){ + return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f'; +} + +/* +** Allocate nByte bytes of memory using sqlite3_malloc(). If successful, +** zero the memory before returning a pointer to it. If unsuccessful, +** return NULL. +*/ +SQLITE_PRIVATE void *sqlite3Fts3MallocZero(sqlite3_int64 nByte){ + void *pRet = sqlite3_malloc64(nByte); + if( pRet ) memset(pRet, 0, nByte); + return pRet; +} + +SQLITE_PRIVATE int sqlite3Fts3OpenTokenizer( + sqlite3_tokenizer *pTokenizer, + int iLangid, + const char *z, + int n, + sqlite3_tokenizer_cursor **ppCsr +){ + sqlite3_tokenizer_module const *pModule = pTokenizer->pModule; + sqlite3_tokenizer_cursor *pCsr = 0; + int rc; + + rc = pModule->xOpen(pTokenizer, z, n, &pCsr); + assert( rc==SQLITE_OK || pCsr==0 ); + if( rc==SQLITE_OK ){ + pCsr->pTokenizer = pTokenizer; + if( pModule->iVersion>=1 ){ + rc = pModule->xLanguageid(pCsr, iLangid); + if( rc!=SQLITE_OK ){ + pModule->xClose(pCsr); + pCsr = 0; + } + } + } + *ppCsr = pCsr; + return rc; +} + +/* +** Function getNextNode(), which is called by fts3ExprParse(), may itself +** call fts3ExprParse(). So this forward declaration is required. +*/ +static int fts3ExprParse(ParseContext *, const char *, int, Fts3Expr **, int *); + +/* +** Extract the next token from buffer z (length n) using the tokenizer +** and other information (column names etc.) in pParse. Create an Fts3Expr +** structure of type FTSQUERY_PHRASE containing a phrase consisting of this +** single token and set *ppExpr to point to it. If the end of the buffer is +** reached before a token is found, set *ppExpr to zero. It is the +** responsibility of the caller to eventually deallocate the allocated +** Fts3Expr structure (if any) by passing it to sqlite3_free(). +** +** Return SQLITE_OK if successful, or SQLITE_NOMEM if a memory allocation +** fails. +*/ +static int getNextToken( + ParseContext *pParse, /* fts3 query parse context */ + int iCol, /* Value for Fts3Phrase.iColumn */ + const char *z, int n, /* Input string */ + Fts3Expr **ppExpr, /* OUT: expression */ + int *pnConsumed /* OUT: Number of bytes consumed */ +){ + sqlite3_tokenizer *pTokenizer = pParse->pTokenizer; + sqlite3_tokenizer_module const *pModule = pTokenizer->pModule; + int rc; + sqlite3_tokenizer_cursor *pCursor; + Fts3Expr *pRet = 0; + int i = 0; + + /* Set variable i to the maximum number of bytes of input to tokenize. */ + for(i=0; i<n; i++){ + if( sqlite3_fts3_enable_parentheses && (z[i]=='(' || z[i]==')') ) break; + if( z[i]=='"' ) break; + } + + *pnConsumed = i; + rc = sqlite3Fts3OpenTokenizer(pTokenizer, pParse->iLangid, z, i, &pCursor); + if( rc==SQLITE_OK ){ + const char *zToken; + int nToken = 0, iStart = 0, iEnd = 0, iPosition = 0; + sqlite3_int64 nByte; /* total space to allocate */ + + rc = pModule->xNext(pCursor, &zToken, &nToken, &iStart, &iEnd, &iPosition); + if( rc==SQLITE_OK ){ + nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase) + nToken; + pRet = (Fts3Expr *)sqlite3Fts3MallocZero(nByte); + if( !pRet ){ + rc = SQLITE_NOMEM; + }else{ + pRet->eType = FTSQUERY_PHRASE; + pRet->pPhrase = (Fts3Phrase *)&pRet[1]; + pRet->pPhrase->nToken = 1; + pRet->pPhrase->iColumn = iCol; + pRet->pPhrase->aToken[0].n = nToken; + pRet->pPhrase->aToken[0].z = (char *)&pRet->pPhrase[1]; + memcpy(pRet->pPhrase->aToken[0].z, zToken, nToken); + + if( iEnd<n && z[iEnd]=='*' ){ + pRet->pPhrase->aToken[0].isPrefix = 1; + iEnd++; + } + + while( 1 ){ + if( !sqlite3_fts3_enable_parentheses + && iStart>0 && z[iStart-1]=='-' + ){ + pParse->isNot = 1; + iStart--; + }else if( pParse->bFts4 && iStart>0 && z[iStart-1]=='^' ){ + pRet->pPhrase->aToken[0].bFirst = 1; + iStart--; + }else{ + break; + } + } + + } + *pnConsumed = iEnd; + }else if( i && rc==SQLITE_DONE ){ + rc = SQLITE_OK; + } + + pModule->xClose(pCursor); + } + + *ppExpr = pRet; + return rc; +} + + +/* +** Enlarge a memory allocation. If an out-of-memory allocation occurs, +** then free the old allocation. +*/ +static void *fts3ReallocOrFree(void *pOrig, sqlite3_int64 nNew){ + void *pRet = sqlite3_realloc64(pOrig, nNew); + if( !pRet ){ + sqlite3_free(pOrig); + } + return pRet; +} + +/* +** Buffer zInput, length nInput, contains the contents of a quoted string +** that appeared as part of an fts3 query expression. Neither quote character +** is included in the buffer. This function attempts to tokenize the entire +** input buffer and create an Fts3Expr structure of type FTSQUERY_PHRASE +** containing the results. +** +** If successful, SQLITE_OK is returned and *ppExpr set to point at the +** allocated Fts3Expr structure. Otherwise, either SQLITE_NOMEM (out of memory +** error) or SQLITE_ERROR (tokenization error) is returned and *ppExpr set +** to 0. +*/ +static int getNextString( + ParseContext *pParse, /* fts3 query parse context */ + const char *zInput, int nInput, /* Input string */ + Fts3Expr **ppExpr /* OUT: expression */ +){ + sqlite3_tokenizer *pTokenizer = pParse->pTokenizer; + sqlite3_tokenizer_module const *pModule = pTokenizer->pModule; + int rc; + Fts3Expr *p = 0; + sqlite3_tokenizer_cursor *pCursor = 0; + char *zTemp = 0; + int nTemp = 0; + + const int nSpace = sizeof(Fts3Expr) + sizeof(Fts3Phrase); + int nToken = 0; + + /* The final Fts3Expr data structure, including the Fts3Phrase, + ** Fts3PhraseToken structures token buffers are all stored as a single + ** allocation so that the expression can be freed with a single call to + ** sqlite3_free(). Setting this up requires a two pass approach. + ** + ** The first pass, in the block below, uses a tokenizer cursor to iterate + ** through the tokens in the expression. This pass uses fts3ReallocOrFree() + ** to assemble data in two dynamic buffers: + ** + ** Buffer p: Points to the Fts3Expr structure, followed by the Fts3Phrase + ** structure, followed by the array of Fts3PhraseToken + ** structures. This pass only populates the Fts3PhraseToken array. + ** + ** Buffer zTemp: Contains copies of all tokens. + ** + ** The second pass, in the block that begins "if( rc==SQLITE_DONE )" below, + ** appends buffer zTemp to buffer p, and fills in the Fts3Expr and Fts3Phrase + ** structures. + */ + rc = sqlite3Fts3OpenTokenizer( + pTokenizer, pParse->iLangid, zInput, nInput, &pCursor); + if( rc==SQLITE_OK ){ + int ii; + for(ii=0; rc==SQLITE_OK; ii++){ + const char *zByte; + int nByte = 0, iBegin = 0, iEnd = 0, iPos = 0; + rc = pModule->xNext(pCursor, &zByte, &nByte, &iBegin, &iEnd, &iPos); + if( rc==SQLITE_OK ){ + Fts3PhraseToken *pToken; + + p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken)); + if( !p ) goto no_mem; + + zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte); + if( !zTemp ) goto no_mem; + + assert( nToken==ii ); + pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii]; + memset(pToken, 0, sizeof(Fts3PhraseToken)); + + memcpy(&zTemp[nTemp], zByte, nByte); + nTemp += nByte; + + pToken->n = nByte; + pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*'); + pToken->bFirst = (iBegin>0 && zInput[iBegin-1]=='^'); + nToken = ii+1; + } + } + + pModule->xClose(pCursor); + pCursor = 0; + } + + if( rc==SQLITE_DONE ){ + int jj; + char *zBuf = 0; + + p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp); + if( !p ) goto no_mem; + memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p); + p->eType = FTSQUERY_PHRASE; + p->pPhrase = (Fts3Phrase *)&p[1]; + p->pPhrase->iColumn = pParse->iDefaultCol; + p->pPhrase->nToken = nToken; + + zBuf = (char *)&p->pPhrase->aToken[nToken]; + if( zTemp ){ + memcpy(zBuf, zTemp, nTemp); + sqlite3_free(zTemp); + }else{ + assert( nTemp==0 ); + } + + for(jj=0; jj<p->pPhrase->nToken; jj++){ + p->pPhrase->aToken[jj].z = zBuf; + zBuf += p->pPhrase->aToken[jj].n; + } + rc = SQLITE_OK; + } + + *ppExpr = p; + return rc; +no_mem: + + if( pCursor ){ + pModule->xClose(pCursor); + } + sqlite3_free(zTemp); + sqlite3_free(p); + *ppExpr = 0; + return SQLITE_NOMEM; +} + +/* +** The output variable *ppExpr is populated with an allocated Fts3Expr +** structure, or set to 0 if the end of the input buffer is reached. +** +** Returns an SQLite error code. SQLITE_OK if everything works, SQLITE_NOMEM +** if a malloc failure occurs, or SQLITE_ERROR if a parse error is encountered. +** If SQLITE_ERROR is returned, pContext is populated with an error message. +*/ +static int getNextNode( + ParseContext *pParse, /* fts3 query parse context */ + const char *z, int n, /* Input string */ + Fts3Expr **ppExpr, /* OUT: expression */ + int *pnConsumed /* OUT: Number of bytes consumed */ +){ + static const struct Fts3Keyword { + char *z; /* Keyword text */ + unsigned char n; /* Length of the keyword */ + unsigned char parenOnly; /* Only valid in paren mode */ + unsigned char eType; /* Keyword code */ + } aKeyword[] = { + { "OR" , 2, 0, FTSQUERY_OR }, + { "AND", 3, 1, FTSQUERY_AND }, + { "NOT", 3, 1, FTSQUERY_NOT }, + { "NEAR", 4, 0, FTSQUERY_NEAR } + }; + int ii; + int iCol; + int iColLen; + int rc; + Fts3Expr *pRet = 0; + + const char *zInput = z; + int nInput = n; + + pParse->isNot = 0; + + /* Skip over any whitespace before checking for a keyword, an open or + ** close bracket, or a quoted string. + */ + while( nInput>0 && fts3isspace(*zInput) ){ + nInput--; + zInput++; + } + if( nInput==0 ){ + return SQLITE_DONE; + } + + /* See if we are dealing with a keyword. */ + for(ii=0; ii<(int)(sizeof(aKeyword)/sizeof(struct Fts3Keyword)); ii++){ + const struct Fts3Keyword *pKey = &aKeyword[ii]; + + if( (pKey->parenOnly & ~sqlite3_fts3_enable_parentheses)!=0 ){ + continue; + } + + if( nInput>=pKey->n && 0==memcmp(zInput, pKey->z, pKey->n) ){ + int nNear = SQLITE_FTS3_DEFAULT_NEAR_PARAM; + int nKey = pKey->n; + char cNext; + + /* If this is a "NEAR" keyword, check for an explicit nearness. */ + if( pKey->eType==FTSQUERY_NEAR ){ + assert( nKey==4 ); + if( zInput[4]=='/' && zInput[5]>='0' && zInput[5]<='9' ){ + nKey += 1+sqlite3Fts3ReadInt(&zInput[nKey+1], &nNear); + } + } + + /* At this point this is probably a keyword. But for that to be true, + ** the next byte must contain either whitespace, an open or close + ** parenthesis, a quote character, or EOF. + */ + cNext = zInput[nKey]; + if( fts3isspace(cNext) + || cNext=='"' || cNext=='(' || cNext==')' || cNext==0 + ){ + pRet = (Fts3Expr *)sqlite3Fts3MallocZero(sizeof(Fts3Expr)); + if( !pRet ){ + return SQLITE_NOMEM; + } + pRet->eType = pKey->eType; + pRet->nNear = nNear; + *ppExpr = pRet; + *pnConsumed = (int)((zInput - z) + nKey); + return SQLITE_OK; + } + + /* Turns out that wasn't a keyword after all. This happens if the + ** user has supplied a token such as "ORacle". Continue. + */ + } + } + + /* See if we are dealing with a quoted phrase. If this is the case, then + ** search for the closing quote and pass the whole string to getNextString() + ** for processing. This is easy to do, as fts3 has no syntax for escaping + ** a quote character embedded in a string. + */ + if( *zInput=='"' ){ + for(ii=1; ii<nInput && zInput[ii]!='"'; ii++); + *pnConsumed = (int)((zInput - z) + ii + 1); + if( ii==nInput ){ + return SQLITE_ERROR; + } + return getNextString(pParse, &zInput[1], ii-1, ppExpr); + } + + if( sqlite3_fts3_enable_parentheses ){ + if( *zInput=='(' ){ + int nConsumed = 0; + pParse->nNest++; +#if !defined(SQLITE_MAX_EXPR_DEPTH) + if( pParse->nNest>1000 ) return SQLITE_ERROR; +#elif SQLITE_MAX_EXPR_DEPTH>0 + if( pParse->nNest>SQLITE_MAX_EXPR_DEPTH ) return SQLITE_ERROR; +#endif + rc = fts3ExprParse(pParse, zInput+1, nInput-1, ppExpr, &nConsumed); + *pnConsumed = (int)(zInput - z) + 1 + nConsumed; + return rc; + }else if( *zInput==')' ){ + pParse->nNest--; + *pnConsumed = (int)((zInput - z) + 1); + *ppExpr = 0; + return SQLITE_DONE; + } + } + + /* If control flows to this point, this must be a regular token, or + ** the end of the input. Read a regular token using the sqlite3_tokenizer + ** interface. Before doing so, figure out if there is an explicit + ** column specifier for the token. + ** + ** TODO: Strangely, it is not possible to associate a column specifier + ** with a quoted phrase, only with a single token. Not sure if this was + ** an implementation artifact or an intentional decision when fts3 was + ** first implemented. Whichever it was, this module duplicates the + ** limitation. + */ + iCol = pParse->iDefaultCol; + iColLen = 0; + for(ii=0; ii<pParse->nCol; ii++){ + const char *zStr = pParse->azCol[ii]; + int nStr = (int)strlen(zStr); + if( nInput>nStr && zInput[nStr]==':' + && sqlite3_strnicmp(zStr, zInput, nStr)==0 + ){ + iCol = ii; + iColLen = (int)((zInput - z) + nStr + 1); + break; + } + } + rc = getNextToken(pParse, iCol, &z[iColLen], n-iColLen, ppExpr, pnConsumed); + *pnConsumed += iColLen; + return rc; +} + +/* +** The argument is an Fts3Expr structure for a binary operator (any type +** except an FTSQUERY_PHRASE). Return an integer value representing the +** precedence of the operator. Lower values have a higher precedence (i.e. +** group more tightly). For example, in the C language, the == operator +** groups more tightly than ||, and would therefore have a higher precedence. +** +** When using the new fts3 query syntax (when SQLITE_ENABLE_FTS3_PARENTHESIS +** is defined), the order of the operators in precedence from highest to +** lowest is: +** +** NEAR +** NOT +** AND (including implicit ANDs) +** OR +** +** Note that when using the old query syntax, the OR operator has a higher +** precedence than the AND operator. +*/ +static int opPrecedence(Fts3Expr *p){ + assert( p->eType!=FTSQUERY_PHRASE ); + if( sqlite3_fts3_enable_parentheses ){ + return p->eType; + }else if( p->eType==FTSQUERY_NEAR ){ + return 1; + }else if( p->eType==FTSQUERY_OR ){ + return 2; + } + assert( p->eType==FTSQUERY_AND ); + return 3; +} + +/* +** Argument ppHead contains a pointer to the current head of a query +** expression tree being parsed. pPrev is the expression node most recently +** inserted into the tree. This function adds pNew, which is always a binary +** operator node, into the expression tree based on the relative precedence +** of pNew and the existing nodes of the tree. This may result in the head +** of the tree changing, in which case *ppHead is set to the new root node. +*/ +static void insertBinaryOperator( + Fts3Expr **ppHead, /* Pointer to the root node of a tree */ + Fts3Expr *pPrev, /* Node most recently inserted into the tree */ + Fts3Expr *pNew /* New binary node to insert into expression tree */ +){ + Fts3Expr *pSplit = pPrev; + while( pSplit->pParent && opPrecedence(pSplit->pParent)<=opPrecedence(pNew) ){ + pSplit = pSplit->pParent; + } + + if( pSplit->pParent ){ + assert( pSplit->pParent->pRight==pSplit ); + pSplit->pParent->pRight = pNew; + pNew->pParent = pSplit->pParent; + }else{ + *ppHead = pNew; + } + pNew->pLeft = pSplit; + pSplit->pParent = pNew; +} + +/* +** Parse the fts3 query expression found in buffer z, length n. This function +** returns either when the end of the buffer is reached or an unmatched +** closing bracket - ')' - is encountered. +** +** If successful, SQLITE_OK is returned, *ppExpr is set to point to the +** parsed form of the expression and *pnConsumed is set to the number of +** bytes read from buffer z. Otherwise, *ppExpr is set to 0 and SQLITE_NOMEM +** (out of memory error) or SQLITE_ERROR (parse error) is returned. +*/ +static int fts3ExprParse( + ParseContext *pParse, /* fts3 query parse context */ + const char *z, int n, /* Text of MATCH query */ + Fts3Expr **ppExpr, /* OUT: Parsed query structure */ + int *pnConsumed /* OUT: Number of bytes consumed */ +){ + Fts3Expr *pRet = 0; + Fts3Expr *pPrev = 0; + Fts3Expr *pNotBranch = 0; /* Only used in legacy parse mode */ + int nIn = n; + const char *zIn = z; + int rc = SQLITE_OK; + int isRequirePhrase = 1; + + while( rc==SQLITE_OK ){ + Fts3Expr *p = 0; + int nByte = 0; + + rc = getNextNode(pParse, zIn, nIn, &p, &nByte); + assert( nByte>0 || (rc!=SQLITE_OK && p==0) ); + if( rc==SQLITE_OK ){ + if( p ){ + int isPhrase; + + if( !sqlite3_fts3_enable_parentheses + && p->eType==FTSQUERY_PHRASE && pParse->isNot + ){ + /* Create an implicit NOT operator. */ + Fts3Expr *pNot = sqlite3Fts3MallocZero(sizeof(Fts3Expr)); + if( !pNot ){ + sqlite3Fts3ExprFree(p); + rc = SQLITE_NOMEM; + goto exprparse_out; + } + pNot->eType = FTSQUERY_NOT; + pNot->pRight = p; + p->pParent = pNot; + if( pNotBranch ){ + pNot->pLeft = pNotBranch; + pNotBranch->pParent = pNot; + } + pNotBranch = pNot; + p = pPrev; + }else{ + int eType = p->eType; + isPhrase = (eType==FTSQUERY_PHRASE || p->pLeft); + + /* The isRequirePhrase variable is set to true if a phrase or + ** an expression contained in parenthesis is required. If a + ** binary operator (AND, OR, NOT or NEAR) is encounted when + ** isRequirePhrase is set, this is a syntax error. + */ + if( !isPhrase && isRequirePhrase ){ + sqlite3Fts3ExprFree(p); + rc = SQLITE_ERROR; + goto exprparse_out; + } + + if( isPhrase && !isRequirePhrase ){ + /* Insert an implicit AND operator. */ + Fts3Expr *pAnd; + assert( pRet && pPrev ); + pAnd = sqlite3Fts3MallocZero(sizeof(Fts3Expr)); + if( !pAnd ){ + sqlite3Fts3ExprFree(p); + rc = SQLITE_NOMEM; + goto exprparse_out; + } + pAnd->eType = FTSQUERY_AND; + insertBinaryOperator(&pRet, pPrev, pAnd); + pPrev = pAnd; + } + + /* This test catches attempts to make either operand of a NEAR + ** operator something other than a phrase. For example, either of + ** the following: + ** + ** (bracketed expression) NEAR phrase + ** phrase NEAR (bracketed expression) + ** + ** Return an error in either case. + */ + if( pPrev && ( + (eType==FTSQUERY_NEAR && !isPhrase && pPrev->eType!=FTSQUERY_PHRASE) + || (eType!=FTSQUERY_PHRASE && isPhrase && pPrev->eType==FTSQUERY_NEAR) + )){ + sqlite3Fts3ExprFree(p); + rc = SQLITE_ERROR; + goto exprparse_out; + } + + if( isPhrase ){ + if( pRet ){ + assert( pPrev && pPrev->pLeft && pPrev->pRight==0 ); + pPrev->pRight = p; + p->pParent = pPrev; + }else{ + pRet = p; + } + }else{ + insertBinaryOperator(&pRet, pPrev, p); + } + isRequirePhrase = !isPhrase; + } + pPrev = p; + } + assert( nByte>0 ); + } + assert( rc!=SQLITE_OK || (nByte>0 && nByte<=nIn) ); + nIn -= nByte; + zIn += nByte; + } + + if( rc==SQLITE_DONE && pRet && isRequirePhrase ){ + rc = SQLITE_ERROR; + } + + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + if( !sqlite3_fts3_enable_parentheses && pNotBranch ){ + if( !pRet ){ + rc = SQLITE_ERROR; + }else{ + Fts3Expr *pIter = pNotBranch; + while( pIter->pLeft ){ + pIter = pIter->pLeft; + } + pIter->pLeft = pRet; + pRet->pParent = pIter; + pRet = pNotBranch; + } + } + } + *pnConsumed = n - nIn; + +exprparse_out: + if( rc!=SQLITE_OK ){ + sqlite3Fts3ExprFree(pRet); + sqlite3Fts3ExprFree(pNotBranch); + pRet = 0; + } + *ppExpr = pRet; + return rc; +} + +/* +** Return SQLITE_ERROR if the maximum depth of the expression tree passed +** as the only argument is more than nMaxDepth. +*/ +static int fts3ExprCheckDepth(Fts3Expr *p, int nMaxDepth){ + int rc = SQLITE_OK; + if( p ){ + if( nMaxDepth<0 ){ + rc = SQLITE_TOOBIG; + }else{ + rc = fts3ExprCheckDepth(p->pLeft, nMaxDepth-1); + if( rc==SQLITE_OK ){ + rc = fts3ExprCheckDepth(p->pRight, nMaxDepth-1); + } + } + } + return rc; +} + +/* +** This function attempts to transform the expression tree at (*pp) to +** an equivalent but more balanced form. The tree is modified in place. +** If successful, SQLITE_OK is returned and (*pp) set to point to the +** new root expression node. +** +** nMaxDepth is the maximum allowable depth of the balanced sub-tree. +** +** Otherwise, if an error occurs, an SQLite error code is returned and +** expression (*pp) freed. +*/ +static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){ + int rc = SQLITE_OK; /* Return code */ + Fts3Expr *pRoot = *pp; /* Initial root node */ + Fts3Expr *pFree = 0; /* List of free nodes. Linked by pParent. */ + int eType = pRoot->eType; /* Type of node in this tree */ + + if( nMaxDepth==0 ){ + rc = SQLITE_ERROR; + } + + if( rc==SQLITE_OK ){ + if( (eType==FTSQUERY_AND || eType==FTSQUERY_OR) ){ + Fts3Expr **apLeaf; + apLeaf = (Fts3Expr **)sqlite3_malloc64(sizeof(Fts3Expr *) * nMaxDepth); + if( 0==apLeaf ){ + rc = SQLITE_NOMEM; + }else{ + memset(apLeaf, 0, sizeof(Fts3Expr *) * nMaxDepth); + } + + if( rc==SQLITE_OK ){ + int i; + Fts3Expr *p; + + /* Set $p to point to the left-most leaf in the tree of eType nodes. */ + for(p=pRoot; p->eType==eType; p=p->pLeft){ + assert( p->pParent==0 || p->pParent->pLeft==p ); + assert( p->pLeft && p->pRight ); + } + + /* This loop runs once for each leaf in the tree of eType nodes. */ + while( 1 ){ + int iLvl; + Fts3Expr *pParent = p->pParent; /* Current parent of p */ + + assert( pParent==0 || pParent->pLeft==p ); + p->pParent = 0; + if( pParent ){ + pParent->pLeft = 0; + }else{ + pRoot = 0; + } + rc = fts3ExprBalance(&p, nMaxDepth-1); + if( rc!=SQLITE_OK ) break; + + for(iLvl=0; p && iLvl<nMaxDepth; iLvl++){ + if( apLeaf[iLvl]==0 ){ + apLeaf[iLvl] = p; + p = 0; + }else{ + assert( pFree ); + pFree->pLeft = apLeaf[iLvl]; + pFree->pRight = p; + pFree->pLeft->pParent = pFree; + pFree->pRight->pParent = pFree; + + p = pFree; + pFree = pFree->pParent; + p->pParent = 0; + apLeaf[iLvl] = 0; + } + } + if( p ){ + sqlite3Fts3ExprFree(p); + rc = SQLITE_TOOBIG; + break; + } + + /* If that was the last leaf node, break out of the loop */ + if( pParent==0 ) break; + + /* Set $p to point to the next leaf in the tree of eType nodes */ + for(p=pParent->pRight; p->eType==eType; p=p->pLeft); + + /* Remove pParent from the original tree. */ + assert( pParent->pParent==0 || pParent->pParent->pLeft==pParent ); + pParent->pRight->pParent = pParent->pParent; + if( pParent->pParent ){ + pParent->pParent->pLeft = pParent->pRight; + }else{ + assert( pParent==pRoot ); + pRoot = pParent->pRight; + } + + /* Link pParent into the free node list. It will be used as an + ** internal node of the new tree. */ + pParent->pParent = pFree; + pFree = pParent; + } + + if( rc==SQLITE_OK ){ + p = 0; + for(i=0; i<nMaxDepth; i++){ + if( apLeaf[i] ){ + if( p==0 ){ + p = apLeaf[i]; + p->pParent = 0; + }else{ + assert( pFree!=0 ); + pFree->pRight = p; + pFree->pLeft = apLeaf[i]; + pFree->pLeft->pParent = pFree; + pFree->pRight->pParent = pFree; + + p = pFree; + pFree = pFree->pParent; + p->pParent = 0; + } + } + } + pRoot = p; + }else{ + /* An error occurred. Delete the contents of the apLeaf[] array + ** and pFree list. Everything else is cleaned up by the call to + ** sqlite3Fts3ExprFree(pRoot) below. */ + Fts3Expr *pDel; + for(i=0; i<nMaxDepth; i++){ + sqlite3Fts3ExprFree(apLeaf[i]); + } + while( (pDel=pFree)!=0 ){ + pFree = pDel->pParent; + sqlite3_free(pDel); + } + } + + assert( pFree==0 ); + sqlite3_free( apLeaf ); + } + }else if( eType==FTSQUERY_NOT ){ + Fts3Expr *pLeft = pRoot->pLeft; + Fts3Expr *pRight = pRoot->pRight; + + pRoot->pLeft = 0; + pRoot->pRight = 0; + pLeft->pParent = 0; + pRight->pParent = 0; + + rc = fts3ExprBalance(&pLeft, nMaxDepth-1); + if( rc==SQLITE_OK ){ + rc = fts3ExprBalance(&pRight, nMaxDepth-1); + } + + if( rc!=SQLITE_OK ){ + sqlite3Fts3ExprFree(pRight); + sqlite3Fts3ExprFree(pLeft); + }else{ + assert( pLeft && pRight ); + pRoot->pLeft = pLeft; + pLeft->pParent = pRoot; + pRoot->pRight = pRight; + pRight->pParent = pRoot; + } + } + } + + if( rc!=SQLITE_OK ){ + sqlite3Fts3ExprFree(pRoot); + pRoot = 0; + } + *pp = pRoot; + return rc; +} + +/* +** This function is similar to sqlite3Fts3ExprParse(), with the following +** differences: +** +** 1. It does not do expression rebalancing. +** 2. It does not check that the expression does not exceed the +** maximum allowable depth. +** 3. Even if it fails, *ppExpr may still be set to point to an +** expression tree. It should be deleted using sqlite3Fts3ExprFree() +** in this case. +*/ +static int fts3ExprParseUnbalanced( + sqlite3_tokenizer *pTokenizer, /* Tokenizer module */ + int iLangid, /* Language id for tokenizer */ + char **azCol, /* Array of column names for fts3 table */ + int bFts4, /* True to allow FTS4-only syntax */ + int nCol, /* Number of entries in azCol[] */ + int iDefaultCol, /* Default column to query */ + const char *z, int n, /* Text of MATCH query */ + Fts3Expr **ppExpr /* OUT: Parsed query structure */ +){ + int nParsed; + int rc; + ParseContext sParse; + + memset(&sParse, 0, sizeof(ParseContext)); + sParse.pTokenizer = pTokenizer; + sParse.iLangid = iLangid; + sParse.azCol = (const char **)azCol; + sParse.nCol = nCol; + sParse.iDefaultCol = iDefaultCol; + sParse.bFts4 = bFts4; + if( z==0 ){ + *ppExpr = 0; + return SQLITE_OK; + } + if( n<0 ){ + n = (int)strlen(z); + } + rc = fts3ExprParse(&sParse, z, n, ppExpr, &nParsed); + assert( rc==SQLITE_OK || *ppExpr==0 ); + + /* Check for mismatched parenthesis */ + if( rc==SQLITE_OK && sParse.nNest ){ + rc = SQLITE_ERROR; + } + + return rc; +} + +/* +** Parameters z and n contain a pointer to and length of a buffer containing +** an fts3 query expression, respectively. This function attempts to parse the +** query expression and create a tree of Fts3Expr structures representing the +** parsed expression. If successful, *ppExpr is set to point to the head +** of the parsed expression tree and SQLITE_OK is returned. If an error +** occurs, either SQLITE_NOMEM (out-of-memory error) or SQLITE_ERROR (parse +** error) is returned and *ppExpr is set to 0. +** +** If parameter n is a negative number, then z is assumed to point to a +** nul-terminated string and the length is determined using strlen(). +** +** The first parameter, pTokenizer, is passed the fts3 tokenizer module to +** use to normalize query tokens while parsing the expression. The azCol[] +** array, which is assumed to contain nCol entries, should contain the names +** of each column in the target fts3 table, in order from left to right. +** Column names must be nul-terminated strings. +** +** The iDefaultCol parameter should be passed the index of the table column +** that appears on the left-hand-side of the MATCH operator (the default +** column to match against for tokens for which a column name is not explicitly +** specified as part of the query string), or -1 if tokens may by default +** match any table column. +*/ +SQLITE_PRIVATE int sqlite3Fts3ExprParse( + sqlite3_tokenizer *pTokenizer, /* Tokenizer module */ + int iLangid, /* Language id for tokenizer */ + char **azCol, /* Array of column names for fts3 table */ + int bFts4, /* True to allow FTS4-only syntax */ + int nCol, /* Number of entries in azCol[] */ + int iDefaultCol, /* Default column to query */ + const char *z, int n, /* Text of MATCH query */ + Fts3Expr **ppExpr, /* OUT: Parsed query structure */ + char **pzErr /* OUT: Error message (sqlite3_malloc) */ +){ + int rc = fts3ExprParseUnbalanced( + pTokenizer, iLangid, azCol, bFts4, nCol, iDefaultCol, z, n, ppExpr + ); + + /* Rebalance the expression. And check that its depth does not exceed + ** SQLITE_FTS3_MAX_EXPR_DEPTH. */ + if( rc==SQLITE_OK && *ppExpr ){ + rc = fts3ExprBalance(ppExpr, SQLITE_FTS3_MAX_EXPR_DEPTH); + if( rc==SQLITE_OK ){ + rc = fts3ExprCheckDepth(*ppExpr, SQLITE_FTS3_MAX_EXPR_DEPTH); + } + } + + if( rc!=SQLITE_OK ){ + sqlite3Fts3ExprFree(*ppExpr); + *ppExpr = 0; + if( rc==SQLITE_TOOBIG ){ + sqlite3Fts3ErrMsg(pzErr, + "FTS expression tree is too large (maximum depth %d)", + SQLITE_FTS3_MAX_EXPR_DEPTH + ); + rc = SQLITE_ERROR; + }else if( rc==SQLITE_ERROR ){ + sqlite3Fts3ErrMsg(pzErr, "malformed MATCH expression: [%s]", z); + } + } + + return rc; +} + +/* +** Free a single node of an expression tree. +*/ +static void fts3FreeExprNode(Fts3Expr *p){ + assert( p->eType==FTSQUERY_PHRASE || p->pPhrase==0 ); + sqlite3Fts3EvalPhraseCleanup(p->pPhrase); + sqlite3_free(p->aMI); + sqlite3_free(p); +} + +/* +** Free a parsed fts3 query expression allocated by sqlite3Fts3ExprParse(). +** +** This function would be simpler if it recursively called itself. But +** that would mean passing a sufficiently large expression to ExprParse() +** could cause a stack overflow. +*/ +SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *pDel){ + Fts3Expr *p; + assert( pDel==0 || pDel->pParent==0 ); + for(p=pDel; p && (p->pLeft||p->pRight); p=(p->pLeft ? p->pLeft : p->pRight)){ + assert( p->pParent==0 || p==p->pParent->pRight || p==p->pParent->pLeft ); + } + while( p ){ + Fts3Expr *pParent = p->pParent; + fts3FreeExprNode(p); + if( pParent && p==pParent->pLeft && pParent->pRight ){ + p = pParent->pRight; + while( p && (p->pLeft || p->pRight) ){ + assert( p==p->pParent->pRight || p==p->pParent->pLeft ); + p = (p->pLeft ? p->pLeft : p->pRight); + } + }else{ + p = pParent; + } + } +} + +/**************************************************************************** +***************************************************************************** +** Everything after this point is just test code. +*/ + +#ifdef SQLITE_TEST + +/* #include <stdio.h> */ + +/* +** Return a pointer to a buffer containing a text representation of the +** expression passed as the first argument. The buffer is obtained from +** sqlite3_malloc(). It is the responsibility of the caller to use +** sqlite3_free() to release the memory. If an OOM condition is encountered, +** NULL is returned. +** +** If the second argument is not NULL, then its contents are prepended to +** the returned expression text and then freed using sqlite3_free(). +*/ +static char *exprToString(Fts3Expr *pExpr, char *zBuf){ + if( pExpr==0 ){ + return sqlite3_mprintf(""); + } + switch( pExpr->eType ){ + case FTSQUERY_PHRASE: { + Fts3Phrase *pPhrase = pExpr->pPhrase; + int i; + zBuf = sqlite3_mprintf( + "%zPHRASE %d 0", zBuf, pPhrase->iColumn); + for(i=0; zBuf && i<pPhrase->nToken; i++){ + zBuf = sqlite3_mprintf("%z %.*s%s", zBuf, + pPhrase->aToken[i].n, pPhrase->aToken[i].z, + (pPhrase->aToken[i].isPrefix?"+":"") + ); + } + return zBuf; + } + + case FTSQUERY_NEAR: + zBuf = sqlite3_mprintf("%zNEAR/%d ", zBuf, pExpr->nNear); + break; + case FTSQUERY_NOT: + zBuf = sqlite3_mprintf("%zNOT ", zBuf); + break; + case FTSQUERY_AND: + zBuf = sqlite3_mprintf("%zAND ", zBuf); + break; + case FTSQUERY_OR: + zBuf = sqlite3_mprintf("%zOR ", zBuf); + break; + } + + if( zBuf ) zBuf = sqlite3_mprintf("%z{", zBuf); + if( zBuf ) zBuf = exprToString(pExpr->pLeft, zBuf); + if( zBuf ) zBuf = sqlite3_mprintf("%z} {", zBuf); + + if( zBuf ) zBuf = exprToString(pExpr->pRight, zBuf); + if( zBuf ) zBuf = sqlite3_mprintf("%z}", zBuf); + + return zBuf; +} + +/* +** This is the implementation of a scalar SQL function used to test the +** expression parser. It should be called as follows: +** +** fts3_exprtest(<tokenizer>, <expr>, <column 1>, ...); +** +** The first argument, <tokenizer>, is the name of the fts3 tokenizer used +** to parse the query expression (see README.tokenizers). The second argument +** is the query expression to parse. Each subsequent argument is the name +** of a column of the fts3 table that the query expression may refer to. +** For example: +** +** SELECT fts3_exprtest('simple', 'Bill col2:Bloggs', 'col1', 'col2'); +*/ +static void fts3ExprTestCommon( + int bRebalance, + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3_tokenizer *pTokenizer = 0; + int rc; + char **azCol = 0; + const char *zExpr; + int nExpr; + int nCol; + int ii; + Fts3Expr *pExpr; + char *zBuf = 0; + Fts3Hash *pHash = (Fts3Hash*)sqlite3_user_data(context); + const char *zTokenizer = 0; + char *zErr = 0; + + if( argc<3 ){ + sqlite3_result_error(context, + "Usage: fts3_exprtest(tokenizer, expr, col1, ...", -1 + ); + return; + } + + zTokenizer = (const char*)sqlite3_value_text(argv[0]); + rc = sqlite3Fts3InitTokenizer(pHash, zTokenizer, &pTokenizer, &zErr); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_NOMEM ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_result_error(context, zErr, -1); + } + sqlite3_free(zErr); + return; + } + + zExpr = (const char *)sqlite3_value_text(argv[1]); + nExpr = sqlite3_value_bytes(argv[1]); + nCol = argc-2; + azCol = (char **)sqlite3_malloc64(nCol*sizeof(char *)); + if( !azCol ){ + sqlite3_result_error_nomem(context); + goto exprtest_out; + } + for(ii=0; ii<nCol; ii++){ + azCol[ii] = (char *)sqlite3_value_text(argv[ii+2]); + } + + if( bRebalance ){ + char *zDummy = 0; + rc = sqlite3Fts3ExprParse( + pTokenizer, 0, azCol, 0, nCol, nCol, zExpr, nExpr, &pExpr, &zDummy + ); + assert( rc==SQLITE_OK || pExpr==0 ); + sqlite3_free(zDummy); + }else{ + rc = fts3ExprParseUnbalanced( + pTokenizer, 0, azCol, 0, nCol, nCol, zExpr, nExpr, &pExpr + ); + } + + if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM ){ + sqlite3Fts3ExprFree(pExpr); + sqlite3_result_error(context, "Error parsing expression", -1); + }else if( rc==SQLITE_NOMEM || !(zBuf = exprToString(pExpr, 0)) ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + sqlite3_free(zBuf); + } + + sqlite3Fts3ExprFree(pExpr); + +exprtest_out: + if( pTokenizer ){ + rc = pTokenizer->pModule->xDestroy(pTokenizer); + } + sqlite3_free(azCol); +} + +static void fts3ExprTest( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + fts3ExprTestCommon(0, context, argc, argv); +} +static void fts3ExprTestRebalance( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + fts3ExprTestCommon(1, context, argc, argv); +} + +/* +** Register the query expression parser test function fts3_exprtest() +** with database connection db. +*/ +SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3 *db, Fts3Hash *pHash){ + int rc = sqlite3_create_function( + db, "fts3_exprtest", -1, SQLITE_UTF8, (void*)pHash, fts3ExprTest, 0, 0 + ); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "fts3_exprtest_rebalance", + -1, SQLITE_UTF8, (void*)pHash, fts3ExprTestRebalance, 0, 0 + ); + } + return rc; +} + +#endif +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ + +/************** End of fts3_expr.c *******************************************/ +/************** Begin file fts3_hash.c ***************************************/ +/* +** 2001 September 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the implementation of generic hash-tables used in SQLite. +** We've modified it slightly to serve as a standalone hash table +** implementation for the full-text indexing module. +*/ + +/* +** The code in this file is only compiled if: +** +** * The FTS3 module is being built as an extension +** (in which case SQLITE_CORE is not defined), or +** +** * The FTS3 module is being built into the core of +** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). +*/ +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* #include <assert.h> */ +/* #include <stdlib.h> */ +/* #include <string.h> */ + +/* #include "fts3_hash.h" */ + +/* +** Malloc and Free functions +*/ +static void *fts3HashMalloc(sqlite3_int64 n){ + void *p = sqlite3_malloc64(n); + if( p ){ + memset(p, 0, n); + } + return p; +} +static void fts3HashFree(void *p){ + sqlite3_free(p); +} + +/* Turn bulk memory into a hash table object by initializing the +** fields of the Hash structure. +** +** "pNew" is a pointer to the hash table that is to be initialized. +** keyClass is one of the constants +** FTS3_HASH_BINARY or FTS3_HASH_STRING. The value of keyClass +** determines what kind of key the hash table will use. "copyKey" is +** true if the hash table should make its own private copy of keys and +** false if it should just use the supplied pointer. +*/ +SQLITE_PRIVATE void sqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copyKey){ + assert( pNew!=0 ); + assert( keyClass>=FTS3_HASH_STRING && keyClass<=FTS3_HASH_BINARY ); + pNew->keyClass = keyClass; + pNew->copyKey = copyKey; + pNew->first = 0; + pNew->count = 0; + pNew->htsize = 0; + pNew->ht = 0; +} + +/* Remove all entries from a hash table. Reclaim all memory. +** Call this routine to delete a hash table or to reset a hash table +** to the empty state. +*/ +SQLITE_PRIVATE void sqlite3Fts3HashClear(Fts3Hash *pH){ + Fts3HashElem *elem; /* For looping over all elements of the table */ + + assert( pH!=0 ); + elem = pH->first; + pH->first = 0; + fts3HashFree(pH->ht); + pH->ht = 0; + pH->htsize = 0; + while( elem ){ + Fts3HashElem *next_elem = elem->next; + if( pH->copyKey && elem->pKey ){ + fts3HashFree(elem->pKey); + } + fts3HashFree(elem); + elem = next_elem; + } + pH->count = 0; +} + +/* +** Hash and comparison functions when the mode is FTS3_HASH_STRING +*/ +static int fts3StrHash(const void *pKey, int nKey){ + const char *z = (const char *)pKey; + unsigned h = 0; + if( nKey<=0 ) nKey = (int) strlen(z); + while( nKey > 0 ){ + h = (h<<3) ^ h ^ *z++; + nKey--; + } + return (int)(h & 0x7fffffff); +} +static int fts3StrCompare(const void *pKey1, int n1, const void *pKey2, int n2){ + if( n1!=n2 ) return 1; + return strncmp((const char*)pKey1,(const char*)pKey2,n1); +} + +/* +** Hash and comparison functions when the mode is FTS3_HASH_BINARY +*/ +static int fts3BinHash(const void *pKey, int nKey){ + int h = 0; + const char *z = (const char *)pKey; + while( nKey-- > 0 ){ + h = (h<<3) ^ h ^ *(z++); + } + return h & 0x7fffffff; +} +static int fts3BinCompare(const void *pKey1, int n1, const void *pKey2, int n2){ + if( n1!=n2 ) return 1; + return memcmp(pKey1,pKey2,n1); +} + +/* +** Return a pointer to the appropriate hash function given the key class. +** +** The C syntax in this function definition may be unfamilar to some +** programmers, so we provide the following additional explanation: +** +** The name of the function is "ftsHashFunction". The function takes a +** single parameter "keyClass". The return value of ftsHashFunction() +** is a pointer to another function. Specifically, the return value +** of ftsHashFunction() is a pointer to a function that takes two parameters +** with types "const void*" and "int" and returns an "int". +*/ +static int (*ftsHashFunction(int keyClass))(const void*,int){ + if( keyClass==FTS3_HASH_STRING ){ + return &fts3StrHash; + }else{ + assert( keyClass==FTS3_HASH_BINARY ); + return &fts3BinHash; + } +} + +/* +** Return a pointer to the appropriate hash function given the key class. +** +** For help in interpreted the obscure C code in the function definition, +** see the header comment on the previous function. +*/ +static int (*ftsCompareFunction(int keyClass))(const void*,int,const void*,int){ + if( keyClass==FTS3_HASH_STRING ){ + return &fts3StrCompare; + }else{ + assert( keyClass==FTS3_HASH_BINARY ); + return &fts3BinCompare; + } +} + +/* Link an element into the hash table +*/ +static void fts3HashInsertElement( + Fts3Hash *pH, /* The complete hash table */ + struct _fts3ht *pEntry, /* The entry into which pNew is inserted */ + Fts3HashElem *pNew /* The element to be inserted */ +){ + Fts3HashElem *pHead; /* First element already in pEntry */ + pHead = pEntry->chain; + if( pHead ){ + pNew->next = pHead; + pNew->prev = pHead->prev; + if( pHead->prev ){ pHead->prev->next = pNew; } + else { pH->first = pNew; } + pHead->prev = pNew; + }else{ + pNew->next = pH->first; + if( pH->first ){ pH->first->prev = pNew; } + pNew->prev = 0; + pH->first = pNew; + } + pEntry->count++; + pEntry->chain = pNew; +} + + +/* Resize the hash table so that it cantains "new_size" buckets. +** "new_size" must be a power of 2. The hash table might fail +** to resize if sqliteMalloc() fails. +** +** Return non-zero if a memory allocation error occurs. +*/ +static int fts3Rehash(Fts3Hash *pH, int new_size){ + struct _fts3ht *new_ht; /* The new hash table */ + Fts3HashElem *elem, *next_elem; /* For looping over existing elements */ + int (*xHash)(const void*,int); /* The hash function */ + + assert( (new_size & (new_size-1))==0 ); + new_ht = (struct _fts3ht *)fts3HashMalloc( new_size*sizeof(struct _fts3ht) ); + if( new_ht==0 ) return 1; + fts3HashFree(pH->ht); + pH->ht = new_ht; + pH->htsize = new_size; + xHash = ftsHashFunction(pH->keyClass); + for(elem=pH->first, pH->first=0; elem; elem = next_elem){ + int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1); + next_elem = elem->next; + fts3HashInsertElement(pH, &new_ht[h], elem); + } + return 0; +} + +/* This function (for internal use only) locates an element in an +** hash table that matches the given key. The hash for this key has +** already been computed and is passed as the 4th parameter. +*/ +static Fts3HashElem *fts3FindElementByHash( + const Fts3Hash *pH, /* The pH to be searched */ + const void *pKey, /* The key we are searching for */ + int nKey, + int h /* The hash for this key. */ +){ + Fts3HashElem *elem; /* Used to loop thru the element list */ + int count; /* Number of elements left to test */ + int (*xCompare)(const void*,int,const void*,int); /* comparison function */ + + if( pH->ht ){ + struct _fts3ht *pEntry = &pH->ht[h]; + elem = pEntry->chain; + count = pEntry->count; + xCompare = ftsCompareFunction(pH->keyClass); + while( count-- && elem ){ + if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){ + return elem; + } + elem = elem->next; + } + } + return 0; +} + +/* Remove a single entry from the hash table given a pointer to that +** element and a hash on the element's key. +*/ +static void fts3RemoveElementByHash( + Fts3Hash *pH, /* The pH containing "elem" */ + Fts3HashElem* elem, /* The element to be removed from the pH */ + int h /* Hash value for the element */ +){ + struct _fts3ht *pEntry; + if( elem->prev ){ + elem->prev->next = elem->next; + }else{ + pH->first = elem->next; + } + if( elem->next ){ + elem->next->prev = elem->prev; + } + pEntry = &pH->ht[h]; + if( pEntry->chain==elem ){ + pEntry->chain = elem->next; + } + pEntry->count--; + if( pEntry->count<=0 ){ + pEntry->chain = 0; + } + if( pH->copyKey && elem->pKey ){ + fts3HashFree(elem->pKey); + } + fts3HashFree( elem ); + pH->count--; + if( pH->count<=0 ){ + assert( pH->first==0 ); + assert( pH->count==0 ); + fts3HashClear(pH); + } +} + +SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem( + const Fts3Hash *pH, + const void *pKey, + int nKey +){ + int h; /* A hash on key */ + int (*xHash)(const void*,int); /* The hash function */ + + if( pH==0 || pH->ht==0 ) return 0; + xHash = ftsHashFunction(pH->keyClass); + assert( xHash!=0 ); + h = (*xHash)(pKey,nKey); + assert( (pH->htsize & (pH->htsize-1))==0 ); + return fts3FindElementByHash(pH,pKey,nKey, h & (pH->htsize-1)); +} + +/* +** Attempt to locate an element of the hash table pH with a key +** that matches pKey,nKey. Return the data for this element if it is +** found, or NULL if there is no match. +*/ +SQLITE_PRIVATE void *sqlite3Fts3HashFind(const Fts3Hash *pH, const void *pKey, int nKey){ + Fts3HashElem *pElem; /* The element that matches key (if any) */ + + pElem = sqlite3Fts3HashFindElem(pH, pKey, nKey); + return pElem ? pElem->data : 0; +} + +/* Insert an element into the hash table pH. The key is pKey,nKey +** and the data is "data". +** +** If no element exists with a matching key, then a new +** element is created. A copy of the key is made if the copyKey +** flag is set. NULL is returned. +** +** If another element already exists with the same key, then the +** new data replaces the old data and the old data is returned. +** The key is not copied in this instance. If a malloc fails, then +** the new data is returned and the hash table is unchanged. +** +** If the "data" parameter to this function is NULL, then the +** element corresponding to "key" is removed from the hash table. +*/ +SQLITE_PRIVATE void *sqlite3Fts3HashInsert( + Fts3Hash *pH, /* The hash table to insert into */ + const void *pKey, /* The key */ + int nKey, /* Number of bytes in the key */ + void *data /* The data */ +){ + int hraw; /* Raw hash value of the key */ + int h; /* the hash of the key modulo hash table size */ + Fts3HashElem *elem; /* Used to loop thru the element list */ + Fts3HashElem *new_elem; /* New element added to the pH */ + int (*xHash)(const void*,int); /* The hash function */ + + assert( pH!=0 ); + xHash = ftsHashFunction(pH->keyClass); + assert( xHash!=0 ); + hraw = (*xHash)(pKey, nKey); + assert( (pH->htsize & (pH->htsize-1))==0 ); + h = hraw & (pH->htsize-1); + elem = fts3FindElementByHash(pH,pKey,nKey,h); + if( elem ){ + void *old_data = elem->data; + if( data==0 ){ + fts3RemoveElementByHash(pH,elem,h); + }else{ + elem->data = data; + } + return old_data; + } + if( data==0 ) return 0; + if( (pH->htsize==0 && fts3Rehash(pH,8)) + || (pH->count>=pH->htsize && fts3Rehash(pH, pH->htsize*2)) + ){ + pH->count = 0; + return data; + } + assert( pH->htsize>0 ); + new_elem = (Fts3HashElem*)fts3HashMalloc( sizeof(Fts3HashElem) ); + if( new_elem==0 ) return data; + if( pH->copyKey && pKey!=0 ){ + new_elem->pKey = fts3HashMalloc( nKey ); + if( new_elem->pKey==0 ){ + fts3HashFree(new_elem); + return data; + } + memcpy((void*)new_elem->pKey, pKey, nKey); + }else{ + new_elem->pKey = (void*)pKey; + } + new_elem->nKey = nKey; + pH->count++; + assert( pH->htsize>0 ); + assert( (pH->htsize & (pH->htsize-1))==0 ); + h = hraw & (pH->htsize-1); + fts3HashInsertElement(pH, &pH->ht[h], new_elem); + new_elem->data = data; + return 0; +} + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ + +/************** End of fts3_hash.c *******************************************/ +/************** Begin file fts3_porter.c *************************************/ +/* +** 2006 September 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Implementation of the full-text-search tokenizer that implements +** a Porter stemmer. +*/ + +/* +** The code in this file is only compiled if: +** +** * The FTS3 module is being built as an extension +** (in which case SQLITE_CORE is not defined), or +** +** * The FTS3 module is being built into the core of +** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). +*/ +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* #include <assert.h> */ +/* #include <stdlib.h> */ +/* #include <stdio.h> */ +/* #include <string.h> */ + +/* #include "fts3_tokenizer.h" */ + +/* +** Class derived from sqlite3_tokenizer +*/ +typedef struct porter_tokenizer { + sqlite3_tokenizer base; /* Base class */ +} porter_tokenizer; + +/* +** Class derived from sqlite3_tokenizer_cursor +*/ +typedef struct porter_tokenizer_cursor { + sqlite3_tokenizer_cursor base; + const char *zInput; /* input we are tokenizing */ + int nInput; /* size of the input */ + int iOffset; /* current position in zInput */ + int iToken; /* index of next token to be returned */ + char *zToken; /* storage for current token */ + int nAllocated; /* space allocated to zToken buffer */ +} porter_tokenizer_cursor; + + +/* +** Create a new tokenizer instance. +*/ +static int porterCreate( + int argc, const char * const *argv, + sqlite3_tokenizer **ppTokenizer +){ + porter_tokenizer *t; + + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + + t = (porter_tokenizer *) sqlite3_malloc(sizeof(*t)); + if( t==NULL ) return SQLITE_NOMEM; + memset(t, 0, sizeof(*t)); + *ppTokenizer = &t->base; + return SQLITE_OK; +} + +/* +** Destroy a tokenizer +*/ +static int porterDestroy(sqlite3_tokenizer *pTokenizer){ + sqlite3_free(pTokenizer); + return SQLITE_OK; +} + +/* +** Prepare to begin tokenizing a particular string. The input +** string to be tokenized is zInput[0..nInput-1]. A cursor +** used to incrementally tokenize this string is returned in +** *ppCursor. +*/ +static int porterOpen( + sqlite3_tokenizer *pTokenizer, /* The tokenizer */ + const char *zInput, int nInput, /* String to be tokenized */ + sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ +){ + porter_tokenizer_cursor *c; + + UNUSED_PARAMETER(pTokenizer); + + c = (porter_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); + if( c==NULL ) return SQLITE_NOMEM; + + c->zInput = zInput; + if( zInput==0 ){ + c->nInput = 0; + }else if( nInput<0 ){ + c->nInput = (int)strlen(zInput); + }else{ + c->nInput = nInput; + } + c->iOffset = 0; /* start tokenizing at the beginning */ + c->iToken = 0; + c->zToken = NULL; /* no space allocated, yet. */ + c->nAllocated = 0; + + *ppCursor = &c->base; + return SQLITE_OK; +} + +/* +** Close a tokenization cursor previously opened by a call to +** porterOpen() above. +*/ +static int porterClose(sqlite3_tokenizer_cursor *pCursor){ + porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; + sqlite3_free(c->zToken); + sqlite3_free(c); + return SQLITE_OK; +} +/* +** Vowel or consonant +*/ +static const char cType[] = { + 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 2, 1 +}; + +/* +** isConsonant() and isVowel() determine if their first character in +** the string they point to is a consonant or a vowel, according +** to Porter ruls. +** +** A consonate is any letter other than 'a', 'e', 'i', 'o', or 'u'. +** 'Y' is a consonant unless it follows another consonant, +** in which case it is a vowel. +** +** In these routine, the letters are in reverse order. So the 'y' rule +** is that 'y' is a consonant unless it is followed by another +** consonent. +*/ +static int isVowel(const char*); +static int isConsonant(const char *z){ + int j; + char x = *z; + if( x==0 ) return 0; + assert( x>='a' && x<='z' ); + j = cType[x-'a']; + if( j<2 ) return j; + return z[1]==0 || isVowel(z + 1); +} +static int isVowel(const char *z){ + int j; + char x = *z; + if( x==0 ) return 0; + assert( x>='a' && x<='z' ); + j = cType[x-'a']; + if( j<2 ) return 1-j; + return isConsonant(z + 1); +} + +/* +** Let any sequence of one or more vowels be represented by V and let +** C be sequence of one or more consonants. Then every word can be +** represented as: +** +** [C] (VC){m} [V] +** +** In prose: A word is an optional consonant followed by zero or +** vowel-consonant pairs followed by an optional vowel. "m" is the +** number of vowel consonant pairs. This routine computes the value +** of m for the first i bytes of a word. +** +** Return true if the m-value for z is 1 or more. In other words, +** return true if z contains at least one vowel that is followed +** by a consonant. +** +** In this routine z[] is in reverse order. So we are really looking +** for an instance of a consonant followed by a vowel. +*/ +static int m_gt_0(const char *z){ + while( isVowel(z) ){ z++; } + if( *z==0 ) return 0; + while( isConsonant(z) ){ z++; } + return *z!=0; +} + +/* Like mgt0 above except we are looking for a value of m which is +** exactly 1 +*/ +static int m_eq_1(const char *z){ + while( isVowel(z) ){ z++; } + if( *z==0 ) return 0; + while( isConsonant(z) ){ z++; } + if( *z==0 ) return 0; + while( isVowel(z) ){ z++; } + if( *z==0 ) return 1; + while( isConsonant(z) ){ z++; } + return *z==0; +} + +/* Like mgt0 above except we are looking for a value of m>1 instead +** or m>0 +*/ +static int m_gt_1(const char *z){ + while( isVowel(z) ){ z++; } + if( *z==0 ) return 0; + while( isConsonant(z) ){ z++; } + if( *z==0 ) return 0; + while( isVowel(z) ){ z++; } + if( *z==0 ) return 0; + while( isConsonant(z) ){ z++; } + return *z!=0; +} + +/* +** Return TRUE if there is a vowel anywhere within z[0..n-1] +*/ +static int hasVowel(const char *z){ + while( isConsonant(z) ){ z++; } + return *z!=0; +} + +/* +** Return TRUE if the word ends in a double consonant. +** +** The text is reversed here. So we are really looking at +** the first two characters of z[]. +*/ +static int doubleConsonant(const char *z){ + return isConsonant(z) && z[0]==z[1]; +} + +/* +** Return TRUE if the word ends with three letters which +** are consonant-vowel-consonent and where the final consonant +** is not 'w', 'x', or 'y'. +** +** The word is reversed here. So we are really checking the +** first three letters and the first one cannot be in [wxy]. +*/ +static int star_oh(const char *z){ + return + isConsonant(z) && + z[0]!='w' && z[0]!='x' && z[0]!='y' && + isVowel(z+1) && + isConsonant(z+2); +} + +/* +** If the word ends with zFrom and xCond() is true for the stem +** of the word that preceeds the zFrom ending, then change the +** ending to zTo. +** +** The input word *pz and zFrom are both in reverse order. zTo +** is in normal order. +** +** Return TRUE if zFrom matches. Return FALSE if zFrom does not +** match. Not that TRUE is returned even if xCond() fails and +** no substitution occurs. +*/ +static int stem( + char **pz, /* The word being stemmed (Reversed) */ + const char *zFrom, /* If the ending matches this... (Reversed) */ + const char *zTo, /* ... change the ending to this (not reversed) */ + int (*xCond)(const char*) /* Condition that must be true */ +){ + char *z = *pz; + while( *zFrom && *zFrom==*z ){ z++; zFrom++; } + if( *zFrom!=0 ) return 0; + if( xCond && !xCond(z) ) return 1; + while( *zTo ){ + *(--z) = *(zTo++); + } + *pz = z; + return 1; +} + +/* +** This is the fallback stemmer used when the porter stemmer is +** inappropriate. The input word is copied into the output with +** US-ASCII case folding. If the input word is too long (more +** than 20 bytes if it contains no digits or more than 6 bytes if +** it contains digits) then word is truncated to 20 or 6 bytes +** by taking 10 or 3 bytes from the beginning and end. +*/ +static void copy_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ + int i, mx, j; + int hasDigit = 0; + for(i=0; i<nIn; i++){ + char c = zIn[i]; + if( c>='A' && c<='Z' ){ + zOut[i] = c - 'A' + 'a'; + }else{ + if( c>='0' && c<='9' ) hasDigit = 1; + zOut[i] = c; + } + } + mx = hasDigit ? 3 : 10; + if( nIn>mx*2 ){ + for(j=mx, i=nIn-mx; i<nIn; i++, j++){ + zOut[j] = zOut[i]; + } + i = j; + } + zOut[i] = 0; + *pnOut = i; +} + + +/* +** Stem the input word zIn[0..nIn-1]. Store the output in zOut. +** zOut is at least big enough to hold nIn bytes. Write the actual +** size of the output word (exclusive of the '\0' terminator) into *pnOut. +** +** Any upper-case characters in the US-ASCII character set ([A-Z]) +** are converted to lower case. Upper-case UTF characters are +** unchanged. +** +** Words that are longer than about 20 bytes are stemmed by retaining +** a few bytes from the beginning and the end of the word. If the +** word contains digits, 3 bytes are taken from the beginning and +** 3 bytes from the end. For long words without digits, 10 bytes +** are taken from each end. US-ASCII case folding still applies. +** +** If the input word contains not digits but does characters not +** in [a-zA-Z] then no stemming is attempted and this routine just +** copies the input into the input into the output with US-ASCII +** case folding. +** +** Stemming never increases the length of the word. So there is +** no chance of overflowing the zOut buffer. +*/ +static void porter_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ + int i, j; + char zReverse[28]; + char *z, *z2; + if( nIn<3 || nIn>=(int)sizeof(zReverse)-7 ){ + /* The word is too big or too small for the porter stemmer. + ** Fallback to the copy stemmer */ + copy_stemmer(zIn, nIn, zOut, pnOut); + return; + } + for(i=0, j=sizeof(zReverse)-6; i<nIn; i++, j--){ + char c = zIn[i]; + if( c>='A' && c<='Z' ){ + zReverse[j] = c + 'a' - 'A'; + }else if( c>='a' && c<='z' ){ + zReverse[j] = c; + }else{ + /* The use of a character not in [a-zA-Z] means that we fallback + ** to the copy stemmer */ + copy_stemmer(zIn, nIn, zOut, pnOut); + return; + } + } + memset(&zReverse[sizeof(zReverse)-5], 0, 5); + z = &zReverse[j+1]; + + + /* Step 1a */ + if( z[0]=='s' ){ + if( + !stem(&z, "sess", "ss", 0) && + !stem(&z, "sei", "i", 0) && + !stem(&z, "ss", "ss", 0) + ){ + z++; + } + } + + /* Step 1b */ + z2 = z; + if( stem(&z, "dee", "ee", m_gt_0) ){ + /* Do nothing. The work was all in the test */ + }else if( + (stem(&z, "gni", "", hasVowel) || stem(&z, "de", "", hasVowel)) + && z!=z2 + ){ + if( stem(&z, "ta", "ate", 0) || + stem(&z, "lb", "ble", 0) || + stem(&z, "zi", "ize", 0) ){ + /* Do nothing. The work was all in the test */ + }else if( doubleConsonant(z) && (*z!='l' && *z!='s' && *z!='z') ){ + z++; + }else if( m_eq_1(z) && star_oh(z) ){ + *(--z) = 'e'; + } + } + + /* Step 1c */ + if( z[0]=='y' && hasVowel(z+1) ){ + z[0] = 'i'; + } + + /* Step 2 */ + switch( z[1] ){ + case 'a': + if( !stem(&z, "lanoita", "ate", m_gt_0) ){ + stem(&z, "lanoit", "tion", m_gt_0); + } + break; + case 'c': + if( !stem(&z, "icne", "ence", m_gt_0) ){ + stem(&z, "icna", "ance", m_gt_0); + } + break; + case 'e': + stem(&z, "rezi", "ize", m_gt_0); + break; + case 'g': + stem(&z, "igol", "log", m_gt_0); + break; + case 'l': + if( !stem(&z, "ilb", "ble", m_gt_0) + && !stem(&z, "illa", "al", m_gt_0) + && !stem(&z, "iltne", "ent", m_gt_0) + && !stem(&z, "ile", "e", m_gt_0) + ){ + stem(&z, "ilsuo", "ous", m_gt_0); + } + break; + case 'o': + if( !stem(&z, "noitazi", "ize", m_gt_0) + && !stem(&z, "noita", "ate", m_gt_0) + ){ + stem(&z, "rota", "ate", m_gt_0); + } + break; + case 's': + if( !stem(&z, "msila", "al", m_gt_0) + && !stem(&z, "ssenevi", "ive", m_gt_0) + && !stem(&z, "ssenluf", "ful", m_gt_0) + ){ + stem(&z, "ssensuo", "ous", m_gt_0); + } + break; + case 't': + if( !stem(&z, "itila", "al", m_gt_0) + && !stem(&z, "itivi", "ive", m_gt_0) + ){ + stem(&z, "itilib", "ble", m_gt_0); + } + break; + } + + /* Step 3 */ + switch( z[0] ){ + case 'e': + if( !stem(&z, "etaci", "ic", m_gt_0) + && !stem(&z, "evita", "", m_gt_0) + ){ + stem(&z, "ezila", "al", m_gt_0); + } + break; + case 'i': + stem(&z, "itici", "ic", m_gt_0); + break; + case 'l': + if( !stem(&z, "laci", "ic", m_gt_0) ){ + stem(&z, "luf", "", m_gt_0); + } + break; + case 's': + stem(&z, "ssen", "", m_gt_0); + break; + } + + /* Step 4 */ + switch( z[1] ){ + case 'a': + if( z[0]=='l' && m_gt_1(z+2) ){ + z += 2; + } + break; + case 'c': + if( z[0]=='e' && z[2]=='n' && (z[3]=='a' || z[3]=='e') && m_gt_1(z+4) ){ + z += 4; + } + break; + case 'e': + if( z[0]=='r' && m_gt_1(z+2) ){ + z += 2; + } + break; + case 'i': + if( z[0]=='c' && m_gt_1(z+2) ){ + z += 2; + } + break; + case 'l': + if( z[0]=='e' && z[2]=='b' && (z[3]=='a' || z[3]=='i') && m_gt_1(z+4) ){ + z += 4; + } + break; + case 'n': + if( z[0]=='t' ){ + if( z[2]=='a' ){ + if( m_gt_1(z+3) ){ + z += 3; + } + }else if( z[2]=='e' ){ + if( !stem(&z, "tneme", "", m_gt_1) + && !stem(&z, "tnem", "", m_gt_1) + ){ + stem(&z, "tne", "", m_gt_1); + } + } + } + break; + case 'o': + if( z[0]=='u' ){ + if( m_gt_1(z+2) ){ + z += 2; + } + }else if( z[3]=='s' || z[3]=='t' ){ + stem(&z, "noi", "", m_gt_1); + } + break; + case 's': + if( z[0]=='m' && z[2]=='i' && m_gt_1(z+3) ){ + z += 3; + } + break; + case 't': + if( !stem(&z, "eta", "", m_gt_1) ){ + stem(&z, "iti", "", m_gt_1); + } + break; + case 'u': + if( z[0]=='s' && z[2]=='o' && m_gt_1(z+3) ){ + z += 3; + } + break; + case 'v': + case 'z': + if( z[0]=='e' && z[2]=='i' && m_gt_1(z+3) ){ + z += 3; + } + break; + } + + /* Step 5a */ + if( z[0]=='e' ){ + if( m_gt_1(z+1) ){ + z++; + }else if( m_eq_1(z+1) && !star_oh(z+1) ){ + z++; + } + } + + /* Step 5b */ + if( m_gt_1(z) && z[0]=='l' && z[1]=='l' ){ + z++; + } + + /* z[] is now the stemmed word in reverse order. Flip it back + ** around into forward order and return. + */ + *pnOut = i = (int)strlen(z); + zOut[i] = 0; + while( *z ){ + zOut[--i] = *(z++); + } +} + +/* +** Characters that can be part of a token. We assume any character +** whose value is greater than 0x80 (any UTF character) can be +** part of a token. In other words, delimiters all must have +** values of 0x7f or lower. +*/ +static const char porterIdChar[] = { +/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ +}; +#define isDelim(C) (((ch=C)&0x80)==0 && (ch<0x30 || !porterIdChar[ch-0x30])) + +/* +** Extract the next token from a tokenization cursor. The cursor must +** have been opened by a prior call to porterOpen(). +*/ +static int porterNext( + sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by porterOpen */ + const char **pzToken, /* OUT: *pzToken is the token text */ + int *pnBytes, /* OUT: Number of bytes in token */ + int *piStartOffset, /* OUT: Starting offset of token */ + int *piEndOffset, /* OUT: Ending offset of token */ + int *piPosition /* OUT: Position integer of token */ +){ + porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; + const char *z = c->zInput; + + while( c->iOffset<c->nInput ){ + int iStartOffset, ch; + + /* Scan past delimiter characters */ + while( c->iOffset<c->nInput && isDelim(z[c->iOffset]) ){ + c->iOffset++; + } + + /* Count non-delimiter characters. */ + iStartOffset = c->iOffset; + while( c->iOffset<c->nInput && !isDelim(z[c->iOffset]) ){ + c->iOffset++; + } + + if( c->iOffset>iStartOffset ){ + int n = c->iOffset-iStartOffset; + if( n>c->nAllocated ){ + char *pNew; + c->nAllocated = n+20; + pNew = sqlite3_realloc64(c->zToken, c->nAllocated); + if( !pNew ) return SQLITE_NOMEM; + c->zToken = pNew; + } + porter_stemmer(&z[iStartOffset], n, c->zToken, pnBytes); + *pzToken = c->zToken; + *piStartOffset = iStartOffset; + *piEndOffset = c->iOffset; + *piPosition = c->iToken++; + return SQLITE_OK; + } + } + return SQLITE_DONE; +} + +/* +** The set of routines that implement the porter-stemmer tokenizer +*/ +static const sqlite3_tokenizer_module porterTokenizerModule = { + 0, + porterCreate, + porterDestroy, + porterOpen, + porterClose, + porterNext, + 0 +}; + +/* +** Allocate a new porter tokenizer. Return a pointer to the new +** tokenizer in *ppModule +*/ +SQLITE_PRIVATE void sqlite3Fts3PorterTokenizerModule( + sqlite3_tokenizer_module const**ppModule +){ + *ppModule = &porterTokenizerModule; +} + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ + +/************** End of fts3_porter.c *****************************************/ +/************** Begin file fts3_tokenizer.c **********************************/ +/* +** 2007 June 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This is part of an SQLite module implementing full-text search. +** This particular file implements the generic tokenizer interface. +*/ + +/* +** The code in this file is only compiled if: +** +** * The FTS3 module is being built as an extension +** (in which case SQLITE_CORE is not defined), or +** +** * The FTS3 module is being built into the core of +** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). +*/ +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* #include <assert.h> */ +/* #include <string.h> */ + +/* +** Return true if the two-argument version of fts3_tokenizer() +** has been activated via a prior call to sqlite3_db_config(db, +** SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0); +*/ +static int fts3TokenizerEnabled(sqlite3_context *context){ + sqlite3 *db = sqlite3_context_db_handle(context); + int isEnabled = 0; + sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,-1,&isEnabled); + return isEnabled; +} + +/* +** Implementation of the SQL scalar function for accessing the underlying +** hash table. This function may be called as follows: +** +** SELECT <function-name>(<key-name>); +** SELECT <function-name>(<key-name>, <pointer>); +** +** where <function-name> is the name passed as the second argument +** to the sqlite3Fts3InitHashTable() function (e.g. 'fts3_tokenizer'). +** +** If the <pointer> argument is specified, it must be a blob value +** containing a pointer to be stored as the hash data corresponding +** to the string <key-name>. If <pointer> is not specified, then +** the string <key-name> must already exist in the has table. Otherwise, +** an error is returned. +** +** Whether or not the <pointer> argument is specified, the value returned +** is a blob containing the pointer stored as the hash data corresponding +** to string <key-name> (after the hash-table is updated, if applicable). +*/ +static void fts3TokenizerFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Fts3Hash *pHash; + void *pPtr = 0; + const unsigned char *zName; + int nName; + + assert( argc==1 || argc==2 ); + + pHash = (Fts3Hash *)sqlite3_user_data(context); + + zName = sqlite3_value_text(argv[0]); + nName = sqlite3_value_bytes(argv[0])+1; + + if( argc==2 ){ + if( fts3TokenizerEnabled(context) || sqlite3_value_frombind(argv[1]) ){ + void *pOld; + int n = sqlite3_value_bytes(argv[1]); + if( zName==0 || n!=sizeof(pPtr) ){ + sqlite3_result_error(context, "argument type mismatch", -1); + return; + } + pPtr = *(void **)sqlite3_value_blob(argv[1]); + pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr); + if( pOld==pPtr ){ + sqlite3_result_error(context, "out of memory", -1); + } + }else{ + sqlite3_result_error(context, "fts3tokenize disabled", -1); + return; + } + }else{ + if( zName ){ + pPtr = sqlite3Fts3HashFind(pHash, zName, nName); + } + if( !pPtr ){ + char *zErr = sqlite3_mprintf("unknown tokenizer: %s", zName); + sqlite3_result_error(context, zErr, -1); + sqlite3_free(zErr); + return; + } + } + if( fts3TokenizerEnabled(context) || sqlite3_value_frombind(argv[0]) ){ + sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT); + } +} + +SQLITE_PRIVATE int sqlite3Fts3IsIdChar(char c){ + static const char isFtsIdChar[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */ + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ + }; + return (c&0x80 || isFtsIdChar[(int)(c)]); +} + +SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *zStr, int *pn){ + const char *z1; + const char *z2 = 0; + + /* Find the start of the next token. */ + z1 = zStr; + while( z2==0 ){ + char c = *z1; + switch( c ){ + case '\0': return 0; /* No more tokens here */ + case '\'': + case '"': + case '`': { + z2 = z1; + while( *++z2 && (*z2!=c || *++z2==c) ); + break; + } + case '[': + z2 = &z1[1]; + while( *z2 && z2[0]!=']' ) z2++; + if( *z2 ) z2++; + break; + + default: + if( sqlite3Fts3IsIdChar(*z1) ){ + z2 = &z1[1]; + while( sqlite3Fts3IsIdChar(*z2) ) z2++; + }else{ + z1++; + } + } + } + + *pn = (int)(z2-z1); + return z1; +} + +SQLITE_PRIVATE int sqlite3Fts3InitTokenizer( + Fts3Hash *pHash, /* Tokenizer hash table */ + const char *zArg, /* Tokenizer name */ + sqlite3_tokenizer **ppTok, /* OUT: Tokenizer (if applicable) */ + char **pzErr /* OUT: Set to malloced error message */ +){ + int rc; + char *z = (char *)zArg; + int n = 0; + char *zCopy; + char *zEnd; /* Pointer to nul-term of zCopy */ + sqlite3_tokenizer_module *m; + + zCopy = sqlite3_mprintf("%s", zArg); + if( !zCopy ) return SQLITE_NOMEM; + zEnd = &zCopy[strlen(zCopy)]; + + z = (char *)sqlite3Fts3NextToken(zCopy, &n); + if( z==0 ){ + assert( n==0 ); + z = zCopy; + } + z[n] = '\0'; + sqlite3Fts3Dequote(z); + + m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash,z,(int)strlen(z)+1); + if( !m ){ + sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer: %s", z); + rc = SQLITE_ERROR; + }else{ + char const **aArg = 0; + int iArg = 0; + z = &z[n+1]; + while( z<zEnd && (NULL!=(z = (char *)sqlite3Fts3NextToken(z, &n))) ){ + sqlite3_int64 nNew = sizeof(char *)*(iArg+1); + char const **aNew = (const char **)sqlite3_realloc64((void *)aArg, nNew); + if( !aNew ){ + sqlite3_free(zCopy); + sqlite3_free((void *)aArg); + return SQLITE_NOMEM; + } + aArg = aNew; + aArg[iArg++] = z; + z[n] = '\0'; + sqlite3Fts3Dequote(z); + z = &z[n+1]; + } + rc = m->xCreate(iArg, aArg, ppTok); + assert( rc!=SQLITE_OK || *ppTok ); + if( rc!=SQLITE_OK ){ + sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer"); + }else{ + (*ppTok)->pModule = m; + } + sqlite3_free((void *)aArg); + } + + sqlite3_free(zCopy); + return rc; +} + + +#ifdef SQLITE_TEST + +#if defined(INCLUDE_SQLITE_TCL_H) +# include "sqlite_tcl.h" +#else +# include "tcl.h" +#endif +/* #include <string.h> */ + +/* +** Implementation of a special SQL scalar function for testing tokenizers +** designed to be used in concert with the Tcl testing framework. This +** function must be called with two or more arguments: +** +** SELECT <function-name>(<key-name>, ..., <input-string>); +** +** where <function-name> is the name passed as the second argument +** to the sqlite3Fts3InitHashTable() function (e.g. 'fts3_tokenizer') +** concatenated with the string '_test' (e.g. 'fts3_tokenizer_test'). +** +** The return value is a string that may be interpreted as a Tcl +** list. For each token in the <input-string>, three elements are +** added to the returned list. The first is the token position, the +** second is the token text (folded, stemmed, etc.) and the third is the +** substring of <input-string> associated with the token. For example, +** using the built-in "simple" tokenizer: +** +** SELECT fts_tokenizer_test('simple', 'I don't see how'); +** +** will return the string: +** +** "{0 i I 1 dont don't 2 see see 3 how how}" +** +*/ +static void testFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Fts3Hash *pHash; + sqlite3_tokenizer_module *p; + sqlite3_tokenizer *pTokenizer = 0; + sqlite3_tokenizer_cursor *pCsr = 0; + + const char *zErr = 0; + + const char *zName; + int nName; + const char *zInput; + int nInput; + + const char *azArg[64]; + + const char *zToken; + int nToken = 0; + int iStart = 0; + int iEnd = 0; + int iPos = 0; + int i; + + Tcl_Obj *pRet; + + if( argc<2 ){ + sqlite3_result_error(context, "insufficient arguments", -1); + return; + } + + nName = sqlite3_value_bytes(argv[0]); + zName = (const char *)sqlite3_value_text(argv[0]); + nInput = sqlite3_value_bytes(argv[argc-1]); + zInput = (const char *)sqlite3_value_text(argv[argc-1]); + + pHash = (Fts3Hash *)sqlite3_user_data(context); + p = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, zName, nName+1); + + if( !p ){ + char *zErr2 = sqlite3_mprintf("unknown tokenizer: %s", zName); + sqlite3_result_error(context, zErr2, -1); + sqlite3_free(zErr2); + return; + } + + pRet = Tcl_NewObj(); + Tcl_IncrRefCount(pRet); + + for(i=1; i<argc-1; i++){ + azArg[i-1] = (const char *)sqlite3_value_text(argv[i]); + } + + if( SQLITE_OK!=p->xCreate(argc-2, azArg, &pTokenizer) ){ + zErr = "error in xCreate()"; + goto finish; + } + pTokenizer->pModule = p; + if( sqlite3Fts3OpenTokenizer(pTokenizer, 0, zInput, nInput, &pCsr) ){ + zErr = "error in xOpen()"; + goto finish; + } + + while( SQLITE_OK==p->xNext(pCsr, &zToken, &nToken, &iStart, &iEnd, &iPos) ){ + Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(iPos)); + Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zToken, nToken)); + zToken = &zInput[iStart]; + nToken = iEnd-iStart; + Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zToken, nToken)); + } + + if( SQLITE_OK!=p->xClose(pCsr) ){ + zErr = "error in xClose()"; + goto finish; + } + if( SQLITE_OK!=p->xDestroy(pTokenizer) ){ + zErr = "error in xDestroy()"; + goto finish; + } + +finish: + if( zErr ){ + sqlite3_result_error(context, zErr, -1); + }else{ + sqlite3_result_text(context, Tcl_GetString(pRet), -1, SQLITE_TRANSIENT); + } + Tcl_DecrRefCount(pRet); +} + +static +int registerTokenizer( + sqlite3 *db, + char *zName, + const sqlite3_tokenizer_module *p +){ + int rc; + sqlite3_stmt *pStmt; + const char zSql[] = "SELECT fts3_tokenizer(?, ?)"; + + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + + sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); + sqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC); + sqlite3_step(pStmt); + + return sqlite3_finalize(pStmt); +} + + +static +int queryTokenizer( + sqlite3 *db, + char *zName, + const sqlite3_tokenizer_module **pp +){ + int rc; + sqlite3_stmt *pStmt; + const char zSql[] = "SELECT fts3_tokenizer(?)"; + + *pp = 0; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + + sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB + && sqlite3_column_bytes(pStmt, 0)==sizeof(*pp) + ){ + memcpy((void *)pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); + } + } + + return sqlite3_finalize(pStmt); +} + +SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule); + +/* +** Implementation of the scalar function fts3_tokenizer_internal_test(). +** This function is used for testing only, it is not included in the +** build unless SQLITE_TEST is defined. +** +** The purpose of this is to test that the fts3_tokenizer() function +** can be used as designed by the C-code in the queryTokenizer and +** registerTokenizer() functions above. These two functions are repeated +** in the README.tokenizer file as an example, so it is important to +** test them. +** +** To run the tests, evaluate the fts3_tokenizer_internal_test() scalar +** function with no arguments. An assert() will fail if a problem is +** detected. i.e.: +** +** SELECT fts3_tokenizer_internal_test(); +** +*/ +static void intTestFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int rc; + const sqlite3_tokenizer_module *p1; + const sqlite3_tokenizer_module *p2; + sqlite3 *db = (sqlite3 *)sqlite3_user_data(context); + + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + + /* Test the query function */ + sqlite3Fts3SimpleTokenizerModule(&p1); + rc = queryTokenizer(db, "simple", &p2); + assert( rc==SQLITE_OK ); + assert( p1==p2 ); + rc = queryTokenizer(db, "nosuchtokenizer", &p2); + assert( rc==SQLITE_ERROR ); + assert( p2==0 ); + assert( 0==strcmp(sqlite3_errmsg(db), "unknown tokenizer: nosuchtokenizer") ); + + /* Test the storage function */ + if( fts3TokenizerEnabled(context) ){ + rc = registerTokenizer(db, "nosuchtokenizer", p1); + assert( rc==SQLITE_OK ); + rc = queryTokenizer(db, "nosuchtokenizer", &p2); + assert( rc==SQLITE_OK ); + assert( p2==p1 ); + } + + sqlite3_result_text(context, "ok", -1, SQLITE_STATIC); +} + +#endif + +/* +** Set up SQL objects in database db used to access the contents of +** the hash table pointed to by argument pHash. The hash table must +** been initialized to use string keys, and to take a private copy +** of the key when a value is inserted. i.e. by a call similar to: +** +** sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1); +** +** This function adds a scalar function (see header comment above +** fts3TokenizerFunc() in this file for details) and, if ENABLE_TABLE is +** defined at compilation time, a temporary virtual table (see header +** comment above struct HashTableVtab) to the database schema. Both +** provide read/write access to the contents of *pHash. +** +** The third argument to this function, zName, is used as the name +** of both the scalar and, if created, the virtual table. +*/ +SQLITE_PRIVATE int sqlite3Fts3InitHashTable( + sqlite3 *db, + Fts3Hash *pHash, + const char *zName +){ + int rc = SQLITE_OK; + void *p = (void *)pHash; + const int any = SQLITE_UTF8|SQLITE_DIRECTONLY; + +#ifdef SQLITE_TEST + char *zTest = 0; + char *zTest2 = 0; + void *pdb = (void *)db; + zTest = sqlite3_mprintf("%s_test", zName); + zTest2 = sqlite3_mprintf("%s_internal_test", zName); + if( !zTest || !zTest2 ){ + rc = SQLITE_NOMEM; + } +#endif + + if( SQLITE_OK==rc ){ + rc = sqlite3_create_function(db, zName, 1, any, p, fts3TokenizerFunc, 0, 0); + } + if( SQLITE_OK==rc ){ + rc = sqlite3_create_function(db, zName, 2, any, p, fts3TokenizerFunc, 0, 0); + } +#ifdef SQLITE_TEST + if( SQLITE_OK==rc ){ + rc = sqlite3_create_function(db, zTest, -1, any, p, testFunc, 0, 0); + } + if( SQLITE_OK==rc ){ + rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0); + } +#endif + +#ifdef SQLITE_TEST + sqlite3_free(zTest); + sqlite3_free(zTest2); +#endif + + return rc; +} + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ + +/************** End of fts3_tokenizer.c **************************************/ +/************** Begin file fts3_tokenizer1.c *********************************/ +/* +** 2006 Oct 10 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** Implementation of the "simple" full-text-search tokenizer. +*/ + +/* +** The code in this file is only compiled if: +** +** * The FTS3 module is being built as an extension +** (in which case SQLITE_CORE is not defined), or +** +** * The FTS3 module is being built into the core of +** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). +*/ +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* #include <assert.h> */ +/* #include <stdlib.h> */ +/* #include <stdio.h> */ +/* #include <string.h> */ + +/* #include "fts3_tokenizer.h" */ + +typedef struct simple_tokenizer { + sqlite3_tokenizer base; + char delim[128]; /* flag ASCII delimiters */ +} simple_tokenizer; + +typedef struct simple_tokenizer_cursor { + sqlite3_tokenizer_cursor base; + const char *pInput; /* input we are tokenizing */ + int nBytes; /* size of the input */ + int iOffset; /* current position in pInput */ + int iToken; /* index of next token to be returned */ + char *pToken; /* storage for current token */ + int nTokenAllocated; /* space allocated to zToken buffer */ +} simple_tokenizer_cursor; + + +static int simpleDelim(simple_tokenizer *t, unsigned char c){ + return c<0x80 && t->delim[c]; +} +static int fts3_isalnum(int x){ + return (x>='0' && x<='9') || (x>='A' && x<='Z') || (x>='a' && x<='z'); +} + +/* +** Create a new tokenizer instance. +*/ +static int simpleCreate( + int argc, const char * const *argv, + sqlite3_tokenizer **ppTokenizer +){ + simple_tokenizer *t; + + t = (simple_tokenizer *) sqlite3_malloc(sizeof(*t)); + if( t==NULL ) return SQLITE_NOMEM; + memset(t, 0, sizeof(*t)); + + /* TODO(shess) Delimiters need to remain the same from run to run, + ** else we need to reindex. One solution would be a meta-table to + ** track such information in the database, then we'd only want this + ** information on the initial create. + */ + if( argc>1 ){ + int i, n = (int)strlen(argv[1]); + for(i=0; i<n; i++){ + unsigned char ch = argv[1][i]; + /* We explicitly don't support UTF-8 delimiters for now. */ + if( ch>=0x80 ){ + sqlite3_free(t); + return SQLITE_ERROR; + } + t->delim[ch] = 1; + } + } else { + /* Mark non-alphanumeric ASCII characters as delimiters */ + int i; + for(i=1; i<0x80; i++){ + t->delim[i] = !fts3_isalnum(i) ? -1 : 0; + } + } + + *ppTokenizer = &t->base; + return SQLITE_OK; +} + +/* +** Destroy a tokenizer +*/ +static int simpleDestroy(sqlite3_tokenizer *pTokenizer){ + sqlite3_free(pTokenizer); + return SQLITE_OK; +} + +/* +** Prepare to begin tokenizing a particular string. The input +** string to be tokenized is pInput[0..nBytes-1]. A cursor +** used to incrementally tokenize this string is returned in +** *ppCursor. +*/ +static int simpleOpen( + sqlite3_tokenizer *pTokenizer, /* The tokenizer */ + const char *pInput, int nBytes, /* String to be tokenized */ + sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ +){ + simple_tokenizer_cursor *c; + + UNUSED_PARAMETER(pTokenizer); + + c = (simple_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); + if( c==NULL ) return SQLITE_NOMEM; + + c->pInput = pInput; + if( pInput==0 ){ + c->nBytes = 0; + }else if( nBytes<0 ){ + c->nBytes = (int)strlen(pInput); + }else{ + c->nBytes = nBytes; + } + c->iOffset = 0; /* start tokenizing at the beginning */ + c->iToken = 0; + c->pToken = NULL; /* no space allocated, yet. */ + c->nTokenAllocated = 0; + + *ppCursor = &c->base; + return SQLITE_OK; +} + +/* +** Close a tokenization cursor previously opened by a call to +** simpleOpen() above. +*/ +static int simpleClose(sqlite3_tokenizer_cursor *pCursor){ + simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor; + sqlite3_free(c->pToken); + sqlite3_free(c); + return SQLITE_OK; +} + +/* +** Extract the next token from a tokenization cursor. The cursor must +** have been opened by a prior call to simpleOpen(). +*/ +static int simpleNext( + sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */ + const char **ppToken, /* OUT: *ppToken is the token text */ + int *pnBytes, /* OUT: Number of bytes in token */ + int *piStartOffset, /* OUT: Starting offset of token */ + int *piEndOffset, /* OUT: Ending offset of token */ + int *piPosition /* OUT: Position integer of token */ +){ + simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor; + simple_tokenizer *t = (simple_tokenizer *) pCursor->pTokenizer; + unsigned char *p = (unsigned char *)c->pInput; + + while( c->iOffset<c->nBytes ){ + int iStartOffset; + + /* Scan past delimiter characters */ + while( c->iOffset<c->nBytes && simpleDelim(t, p[c->iOffset]) ){ + c->iOffset++; + } + + /* Count non-delimiter characters. */ + iStartOffset = c->iOffset; + while( c->iOffset<c->nBytes && !simpleDelim(t, p[c->iOffset]) ){ + c->iOffset++; + } + + if( c->iOffset>iStartOffset ){ + int i, n = c->iOffset-iStartOffset; + if( n>c->nTokenAllocated ){ + char *pNew; + c->nTokenAllocated = n+20; + pNew = sqlite3_realloc64(c->pToken, c->nTokenAllocated); + if( !pNew ) return SQLITE_NOMEM; + c->pToken = pNew; + } + for(i=0; i<n; i++){ + /* TODO(shess) This needs expansion to handle UTF-8 + ** case-insensitivity. + */ + unsigned char ch = p[iStartOffset+i]; + c->pToken[i] = (char)((ch>='A' && ch<='Z') ? ch-'A'+'a' : ch); + } + *ppToken = c->pToken; + *pnBytes = n; + *piStartOffset = iStartOffset; + *piEndOffset = c->iOffset; + *piPosition = c->iToken++; + + return SQLITE_OK; + } + } + return SQLITE_DONE; +} + +/* +** The set of routines that implement the simple tokenizer +*/ +static const sqlite3_tokenizer_module simpleTokenizerModule = { + 0, + simpleCreate, + simpleDestroy, + simpleOpen, + simpleClose, + simpleNext, + 0, +}; + +/* +** Allocate a new simple tokenizer. Return a pointer to the new +** tokenizer in *ppModule +*/ +SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule( + sqlite3_tokenizer_module const**ppModule +){ + *ppModule = &simpleTokenizerModule; +} + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ + +/************** End of fts3_tokenizer1.c *************************************/ +/************** Begin file fts3_tokenize_vtab.c ******************************/ +/* +** 2013 Apr 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code for the "fts3tokenize" virtual table module. +** An fts3tokenize virtual table is created as follows: +** +** CREATE VIRTUAL TABLE <tbl> USING fts3tokenize( +** <tokenizer-name>, <arg-1>, ... +** ); +** +** The table created has the following schema: +** +** CREATE TABLE <tbl>(input, token, start, end, position) +** +** When queried, the query must include a WHERE clause of type: +** +** input = <string> +** +** The virtual table module tokenizes this <string>, using the FTS3 +** tokenizer specified by the arguments to the CREATE VIRTUAL TABLE +** statement and returns one row for each token in the result. With +** fields set as follows: +** +** input: Always set to a copy of <string> +** token: A token from the input. +** start: Byte offset of the token within the input <string>. +** end: Byte offset of the byte immediately following the end of the +** token within the input string. +** pos: Token offset of token within input. +** +*/ +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* #include <string.h> */ +/* #include <assert.h> */ + +typedef struct Fts3tokTable Fts3tokTable; +typedef struct Fts3tokCursor Fts3tokCursor; + +/* +** Virtual table structure. +*/ +struct Fts3tokTable { + sqlite3_vtab base; /* Base class used by SQLite core */ + const sqlite3_tokenizer_module *pMod; + sqlite3_tokenizer *pTok; +}; + +/* +** Virtual table cursor structure. +*/ +struct Fts3tokCursor { + sqlite3_vtab_cursor base; /* Base class used by SQLite core */ + char *zInput; /* Input string */ + sqlite3_tokenizer_cursor *pCsr; /* Cursor to iterate through zInput */ + int iRowid; /* Current 'rowid' value */ + const char *zToken; /* Current 'token' value */ + int nToken; /* Size of zToken in bytes */ + int iStart; /* Current 'start' value */ + int iEnd; /* Current 'end' value */ + int iPos; /* Current 'pos' value */ +}; + +/* +** Query FTS for the tokenizer implementation named zName. +*/ +static int fts3tokQueryTokenizer( + Fts3Hash *pHash, + const char *zName, + const sqlite3_tokenizer_module **pp, + char **pzErr +){ + sqlite3_tokenizer_module *p; + int nName = (int)strlen(zName); + + p = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, zName, nName+1); + if( !p ){ + sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer: %s", zName); + return SQLITE_ERROR; + } + + *pp = p; + return SQLITE_OK; +} + +/* +** The second argument, argv[], is an array of pointers to nul-terminated +** strings. This function makes a copy of the array and strings into a +** single block of memory. It then dequotes any of the strings that appear +** to be quoted. +** +** If successful, output parameter *pazDequote is set to point at the +** array of dequoted strings and SQLITE_OK is returned. The caller is +** responsible for eventually calling sqlite3_free() to free the array +** in this case. Or, if an error occurs, an SQLite error code is returned. +** The final value of *pazDequote is undefined in this case. +*/ +static int fts3tokDequoteArray( + int argc, /* Number of elements in argv[] */ + const char * const *argv, /* Input array */ + char ***pazDequote /* Output array */ +){ + int rc = SQLITE_OK; /* Return code */ + if( argc==0 ){ + *pazDequote = 0; + }else{ + int i; + int nByte = 0; + char **azDequote; + + for(i=0; i<argc; i++){ + nByte += (int)(strlen(argv[i]) + 1); + } + + *pazDequote = azDequote = sqlite3_malloc64(sizeof(char *)*argc + nByte); + if( azDequote==0 ){ + rc = SQLITE_NOMEM; + }else{ + char *pSpace = (char *)&azDequote[argc]; + for(i=0; i<argc; i++){ + int n = (int)strlen(argv[i]); + azDequote[i] = pSpace; + memcpy(pSpace, argv[i], n+1); + sqlite3Fts3Dequote(pSpace); + pSpace += (n+1); + } + } + } + + return rc; +} + +/* +** Schema of the tokenizer table. +*/ +#define FTS3_TOK_SCHEMA "CREATE TABLE x(input, token, start, end, position)" + +/* +** This function does all the work for both the xConnect and xCreate methods. +** These tables have no persistent representation of their own, so xConnect +** and xCreate are identical operations. +** +** argv[0]: module name +** argv[1]: database name +** argv[2]: table name +** argv[3]: first argument (tokenizer name) +*/ +static int fts3tokConnectMethod( + sqlite3 *db, /* Database connection */ + void *pHash, /* Hash table of tokenizers */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ + char **pzErr /* OUT: sqlite3_malloc'd error message */ +){ + Fts3tokTable *pTab = 0; + const sqlite3_tokenizer_module *pMod = 0; + sqlite3_tokenizer *pTok = 0; + int rc; + char **azDequote = 0; + int nDequote; + + rc = sqlite3_declare_vtab(db, FTS3_TOK_SCHEMA); + if( rc!=SQLITE_OK ) return rc; + + nDequote = argc-3; + rc = fts3tokDequoteArray(nDequote, &argv[3], &azDequote); + + if( rc==SQLITE_OK ){ + const char *zModule; + if( nDequote<1 ){ + zModule = "simple"; + }else{ + zModule = azDequote[0]; + } + rc = fts3tokQueryTokenizer((Fts3Hash*)pHash, zModule, &pMod, pzErr); + } + + assert( (rc==SQLITE_OK)==(pMod!=0) ); + if( rc==SQLITE_OK ){ + const char * const *azArg = 0; + if( nDequote>1 ) azArg = (const char * const *)&azDequote[1]; + rc = pMod->xCreate((nDequote>1 ? nDequote-1 : 0), azArg, &pTok); + } + + if( rc==SQLITE_OK ){ + pTab = (Fts3tokTable *)sqlite3_malloc(sizeof(Fts3tokTable)); + if( pTab==0 ){ + rc = SQLITE_NOMEM; + } + } + + if( rc==SQLITE_OK ){ + memset(pTab, 0, sizeof(Fts3tokTable)); + pTab->pMod = pMod; + pTab->pTok = pTok; + *ppVtab = &pTab->base; + }else{ + if( pTok ){ + pMod->xDestroy(pTok); + } + } + + sqlite3_free(azDequote); + return rc; +} + +/* +** This function does the work for both the xDisconnect and xDestroy methods. +** These tables have no persistent representation of their own, so xDisconnect +** and xDestroy are identical operations. +*/ +static int fts3tokDisconnectMethod(sqlite3_vtab *pVtab){ + Fts3tokTable *pTab = (Fts3tokTable *)pVtab; + + pTab->pMod->xDestroy(pTab->pTok); + sqlite3_free(pTab); + return SQLITE_OK; +} + +/* +** xBestIndex - Analyze a WHERE and ORDER BY clause. +*/ +static int fts3tokBestIndexMethod( + sqlite3_vtab *pVTab, + sqlite3_index_info *pInfo +){ + int i; + UNUSED_PARAMETER(pVTab); + + for(i=0; i<pInfo->nConstraint; i++){ + if( pInfo->aConstraint[i].usable + && pInfo->aConstraint[i].iColumn==0 + && pInfo->aConstraint[i].op==SQLITE_INDEX_CONSTRAINT_EQ + ){ + pInfo->idxNum = 1; + pInfo->aConstraintUsage[i].argvIndex = 1; + pInfo->aConstraintUsage[i].omit = 1; + pInfo->estimatedCost = 1; + return SQLITE_OK; + } + } + + pInfo->idxNum = 0; + assert( pInfo->estimatedCost>1000000.0 ); + + return SQLITE_OK; +} + +/* +** xOpen - Open a cursor. +*/ +static int fts3tokOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ + Fts3tokCursor *pCsr; + UNUSED_PARAMETER(pVTab); + + pCsr = (Fts3tokCursor *)sqlite3_malloc(sizeof(Fts3tokCursor)); + if( pCsr==0 ){ + return SQLITE_NOMEM; + } + memset(pCsr, 0, sizeof(Fts3tokCursor)); + + *ppCsr = (sqlite3_vtab_cursor *)pCsr; + return SQLITE_OK; +} + +/* +** Reset the tokenizer cursor passed as the only argument. As if it had +** just been returned by fts3tokOpenMethod(). +*/ +static void fts3tokResetCursor(Fts3tokCursor *pCsr){ + if( pCsr->pCsr ){ + Fts3tokTable *pTab = (Fts3tokTable *)(pCsr->base.pVtab); + pTab->pMod->xClose(pCsr->pCsr); + pCsr->pCsr = 0; + } + sqlite3_free(pCsr->zInput); + pCsr->zInput = 0; + pCsr->zToken = 0; + pCsr->nToken = 0; + pCsr->iStart = 0; + pCsr->iEnd = 0; + pCsr->iPos = 0; + pCsr->iRowid = 0; +} + +/* +** xClose - Close a cursor. +*/ +static int fts3tokCloseMethod(sqlite3_vtab_cursor *pCursor){ + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + + fts3tokResetCursor(pCsr); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** xNext - Advance the cursor to the next row, if any. +*/ +static int fts3tokNextMethod(sqlite3_vtab_cursor *pCursor){ + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + Fts3tokTable *pTab = (Fts3tokTable *)(pCursor->pVtab); + int rc; /* Return code */ + + pCsr->iRowid++; + rc = pTab->pMod->xNext(pCsr->pCsr, + &pCsr->zToken, &pCsr->nToken, + &pCsr->iStart, &pCsr->iEnd, &pCsr->iPos + ); + + if( rc!=SQLITE_OK ){ + fts3tokResetCursor(pCsr); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + } + + return rc; +} + +/* +** xFilter - Initialize a cursor to point at the start of its data. +*/ +static int fts3tokFilterMethod( + sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ + int idxNum, /* Strategy index */ + const char *idxStr, /* Unused */ + int nVal, /* Number of elements in apVal */ + sqlite3_value **apVal /* Arguments for the indexing scheme */ +){ + int rc = SQLITE_ERROR; + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + Fts3tokTable *pTab = (Fts3tokTable *)(pCursor->pVtab); + UNUSED_PARAMETER(idxStr); + UNUSED_PARAMETER(nVal); + + fts3tokResetCursor(pCsr); + if( idxNum==1 ){ + const char *zByte = (const char *)sqlite3_value_text(apVal[0]); + int nByte = sqlite3_value_bytes(apVal[0]); + pCsr->zInput = sqlite3_malloc64(nByte+1); + if( pCsr->zInput==0 ){ + rc = SQLITE_NOMEM; + }else{ + if( nByte>0 ) memcpy(pCsr->zInput, zByte, nByte); + pCsr->zInput[nByte] = 0; + rc = pTab->pMod->xOpen(pTab->pTok, pCsr->zInput, nByte, &pCsr->pCsr); + if( rc==SQLITE_OK ){ + pCsr->pCsr->pTokenizer = pTab->pTok; + } + } + } + + if( rc!=SQLITE_OK ) return rc; + return fts3tokNextMethod(pCursor); +} + +/* +** xEof - Return true if the cursor is at EOF, or false otherwise. +*/ +static int fts3tokEofMethod(sqlite3_vtab_cursor *pCursor){ + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + return (pCsr->zToken==0); +} + +/* +** xColumn - Return a column value. +*/ +static int fts3tokColumnMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ + int iCol /* Index of column to read value from */ +){ + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + + /* CREATE TABLE x(input, token, start, end, position) */ + switch( iCol ){ + case 0: + sqlite3_result_text(pCtx, pCsr->zInput, -1, SQLITE_TRANSIENT); + break; + case 1: + sqlite3_result_text(pCtx, pCsr->zToken, pCsr->nToken, SQLITE_TRANSIENT); + break; + case 2: + sqlite3_result_int(pCtx, pCsr->iStart); + break; + case 3: + sqlite3_result_int(pCtx, pCsr->iEnd); + break; + default: + assert( iCol==4 ); + sqlite3_result_int(pCtx, pCsr->iPos); + break; + } + return SQLITE_OK; +} + +/* +** xRowid - Return the current rowid for the cursor. +*/ +static int fts3tokRowidMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite_int64 *pRowid /* OUT: Rowid value */ +){ + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + *pRowid = (sqlite3_int64)pCsr->iRowid; + return SQLITE_OK; +} + +/* +** Register the fts3tok module with database connection db. Return SQLITE_OK +** if successful or an error code if sqlite3_create_module() fails. +*/ +SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash, void(*xDestroy)(void*)){ + static const sqlite3_module fts3tok_module = { + 0, /* iVersion */ + fts3tokConnectMethod, /* xCreate */ + fts3tokConnectMethod, /* xConnect */ + fts3tokBestIndexMethod, /* xBestIndex */ + fts3tokDisconnectMethod, /* xDisconnect */ + fts3tokDisconnectMethod, /* xDestroy */ + fts3tokOpenMethod, /* xOpen */ + fts3tokCloseMethod, /* xClose */ + fts3tokFilterMethod, /* xFilter */ + fts3tokNextMethod, /* xNext */ + fts3tokEofMethod, /* xEof */ + fts3tokColumnMethod, /* xColumn */ + fts3tokRowidMethod, /* xRowid */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindFunction */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ + }; + int rc; /* Return code */ + + rc = sqlite3_create_module_v2( + db, "fts3tokenize", &fts3tok_module, (void*)pHash, xDestroy + ); + return rc; +} + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ + +/************** End of fts3_tokenize_vtab.c **********************************/ +/************** Begin file fts3_write.c **************************************/ +/* +** 2009 Oct 23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file is part of the SQLite FTS3 extension module. Specifically, +** this file contains code to insert, update and delete rows from FTS3 +** tables. It also contains code to merge FTS3 b-tree segments. Some +** of the sub-routines used to merge segments are also used by the query +** code in fts3.c. +*/ + +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* #include <string.h> */ +/* #include <assert.h> */ +/* #include <stdlib.h> */ +/* #include <stdio.h> */ + +#define FTS_MAX_APPENDABLE_HEIGHT 16 + +/* +** When full-text index nodes are loaded from disk, the buffer that they +** are loaded into has the following number of bytes of padding at the end +** of it. i.e. if a full-text index node is 900 bytes in size, then a buffer +** of 920 bytes is allocated for it. +** +** This means that if we have a pointer into a buffer containing node data, +** it is always safe to read up to two varints from it without risking an +** overread, even if the node data is corrupted. +*/ +#define FTS3_NODE_PADDING (FTS3_VARINT_MAX*2) + +/* +** Under certain circumstances, b-tree nodes (doclists) can be loaded into +** memory incrementally instead of all at once. This can be a big performance +** win (reduced IO and CPU) if SQLite stops calling the virtual table xNext() +** method before retrieving all query results (as may happen, for example, +** if a query has a LIMIT clause). +** +** Incremental loading is used for b-tree nodes FTS3_NODE_CHUNK_THRESHOLD +** bytes and larger. Nodes are loaded in chunks of FTS3_NODE_CHUNKSIZE bytes. +** The code is written so that the hard lower-limit for each of these values +** is 1. Clearly such small values would be inefficient, but can be useful +** for testing purposes. +** +** If this module is built with SQLITE_TEST defined, these constants may +** be overridden at runtime for testing purposes. File fts3_test.c contains +** a Tcl interface to read and write the values. +*/ +#ifdef SQLITE_TEST +int test_fts3_node_chunksize = (4*1024); +int test_fts3_node_chunk_threshold = (4*1024)*4; +# define FTS3_NODE_CHUNKSIZE test_fts3_node_chunksize +# define FTS3_NODE_CHUNK_THRESHOLD test_fts3_node_chunk_threshold +#else +# define FTS3_NODE_CHUNKSIZE (4*1024) +# define FTS3_NODE_CHUNK_THRESHOLD (FTS3_NODE_CHUNKSIZE*4) +#endif + +/* +** The values that may be meaningfully bound to the :1 parameter in +** statements SQL_REPLACE_STAT and SQL_SELECT_STAT. +*/ +#define FTS_STAT_DOCTOTAL 0 +#define FTS_STAT_INCRMERGEHINT 1 +#define FTS_STAT_AUTOINCRMERGE 2 + +/* +** If FTS_LOG_MERGES is defined, call sqlite3_log() to report each automatic +** and incremental merge operation that takes place. This is used for +** debugging FTS only, it should not usually be turned on in production +** systems. +*/ +#ifdef FTS3_LOG_MERGES +static void fts3LogMerge(int nMerge, sqlite3_int64 iAbsLevel){ + sqlite3_log(SQLITE_OK, "%d-way merge from level %d", nMerge, (int)iAbsLevel); +} +#else +#define fts3LogMerge(x, y) +#endif + + +typedef struct PendingList PendingList; +typedef struct SegmentNode SegmentNode; +typedef struct SegmentWriter SegmentWriter; + +/* +** An instance of the following data structure is used to build doclists +** incrementally. See function fts3PendingListAppend() for details. +*/ +struct PendingList { + int nData; + char *aData; + int nSpace; + sqlite3_int64 iLastDocid; + sqlite3_int64 iLastCol; + sqlite3_int64 iLastPos; +}; + + +/* +** Each cursor has a (possibly empty) linked list of the following objects. +*/ +struct Fts3DeferredToken { + Fts3PhraseToken *pToken; /* Pointer to corresponding expr token */ + int iCol; /* Column token must occur in */ + Fts3DeferredToken *pNext; /* Next in list of deferred tokens */ + PendingList *pList; /* Doclist is assembled here */ +}; + +/* +** An instance of this structure is used to iterate through the terms on +** a contiguous set of segment b-tree leaf nodes. Although the details of +** this structure are only manipulated by code in this file, opaque handles +** of type Fts3SegReader* are also used by code in fts3.c to iterate through +** terms when querying the full-text index. See functions: +** +** sqlite3Fts3SegReaderNew() +** sqlite3Fts3SegReaderFree() +** sqlite3Fts3SegReaderIterate() +** +** Methods used to manipulate Fts3SegReader structures: +** +** fts3SegReaderNext() +** fts3SegReaderFirstDocid() +** fts3SegReaderNextDocid() +*/ +struct Fts3SegReader { + int iIdx; /* Index within level, or 0x7FFFFFFF for PT */ + u8 bLookup; /* True for a lookup only */ + u8 rootOnly; /* True for a root-only reader */ + + sqlite3_int64 iStartBlock; /* Rowid of first leaf block to traverse */ + sqlite3_int64 iLeafEndBlock; /* Rowid of final leaf block to traverse */ + sqlite3_int64 iEndBlock; /* Rowid of final block in segment (or 0) */ + sqlite3_int64 iCurrentBlock; /* Current leaf block (or 0) */ + + char *aNode; /* Pointer to node data (or NULL) */ + int nNode; /* Size of buffer at aNode (or 0) */ + int nPopulate; /* If >0, bytes of buffer aNode[] loaded */ + sqlite3_blob *pBlob; /* If not NULL, blob handle to read node */ + + Fts3HashElem **ppNextElem; + + /* Variables set by fts3SegReaderNext(). These may be read directly + ** by the caller. They are valid from the time SegmentReaderNew() returns + ** until SegmentReaderNext() returns something other than SQLITE_OK + ** (i.e. SQLITE_DONE). + */ + int nTerm; /* Number of bytes in current term */ + char *zTerm; /* Pointer to current term */ + int nTermAlloc; /* Allocated size of zTerm buffer */ + char *aDoclist; /* Pointer to doclist of current entry */ + int nDoclist; /* Size of doclist in current entry */ + + /* The following variables are used by fts3SegReaderNextDocid() to iterate + ** through the current doclist (aDoclist/nDoclist). + */ + char *pOffsetList; + int nOffsetList; /* For descending pending seg-readers only */ + sqlite3_int64 iDocid; +}; + +#define fts3SegReaderIsPending(p) ((p)->ppNextElem!=0) +#define fts3SegReaderIsRootOnly(p) ((p)->rootOnly!=0) + +/* +** An instance of this structure is used to create a segment b-tree in the +** database. The internal details of this type are only accessed by the +** following functions: +** +** fts3SegWriterAdd() +** fts3SegWriterFlush() +** fts3SegWriterFree() +*/ +struct SegmentWriter { + SegmentNode *pTree; /* Pointer to interior tree structure */ + sqlite3_int64 iFirst; /* First slot in %_segments written */ + sqlite3_int64 iFree; /* Next free slot in %_segments */ + char *zTerm; /* Pointer to previous term buffer */ + int nTerm; /* Number of bytes in zTerm */ + int nMalloc; /* Size of malloc'd buffer at zMalloc */ + char *zMalloc; /* Malloc'd space (possibly) used for zTerm */ + int nSize; /* Size of allocation at aData */ + int nData; /* Bytes of data in aData */ + char *aData; /* Pointer to block from malloc() */ + i64 nLeafData; /* Number of bytes of leaf data written */ +}; + +/* +** Type SegmentNode is used by the following three functions to create +** the interior part of the segment b+-tree structures (everything except +** the leaf nodes). These functions and type are only ever used by code +** within the fts3SegWriterXXX() family of functions described above. +** +** fts3NodeAddTerm() +** fts3NodeWrite() +** fts3NodeFree() +** +** When a b+tree is written to the database (either as a result of a merge +** or the pending-terms table being flushed), leaves are written into the +** database file as soon as they are completely populated. The interior of +** the tree is assembled in memory and written out only once all leaves have +** been populated and stored. This is Ok, as the b+-tree fanout is usually +** very large, meaning that the interior of the tree consumes relatively +** little memory. +*/ +struct SegmentNode { + SegmentNode *pParent; /* Parent node (or NULL for root node) */ + SegmentNode *pRight; /* Pointer to right-sibling */ + SegmentNode *pLeftmost; /* Pointer to left-most node of this depth */ + int nEntry; /* Number of terms written to node so far */ + char *zTerm; /* Pointer to previous term buffer */ + int nTerm; /* Number of bytes in zTerm */ + int nMalloc; /* Size of malloc'd buffer at zMalloc */ + char *zMalloc; /* Malloc'd space (possibly) used for zTerm */ + int nData; /* Bytes of valid data so far */ + char *aData; /* Node data */ +}; + +/* +** Valid values for the second argument to fts3SqlStmt(). +*/ +#define SQL_DELETE_CONTENT 0 +#define SQL_IS_EMPTY 1 +#define SQL_DELETE_ALL_CONTENT 2 +#define SQL_DELETE_ALL_SEGMENTS 3 +#define SQL_DELETE_ALL_SEGDIR 4 +#define SQL_DELETE_ALL_DOCSIZE 5 +#define SQL_DELETE_ALL_STAT 6 +#define SQL_SELECT_CONTENT_BY_ROWID 7 +#define SQL_NEXT_SEGMENT_INDEX 8 +#define SQL_INSERT_SEGMENTS 9 +#define SQL_NEXT_SEGMENTS_ID 10 +#define SQL_INSERT_SEGDIR 11 +#define SQL_SELECT_LEVEL 12 +#define SQL_SELECT_LEVEL_RANGE 13 +#define SQL_SELECT_LEVEL_COUNT 14 +#define SQL_SELECT_SEGDIR_MAX_LEVEL 15 +#define SQL_DELETE_SEGDIR_LEVEL 16 +#define SQL_DELETE_SEGMENTS_RANGE 17 +#define SQL_CONTENT_INSERT 18 +#define SQL_DELETE_DOCSIZE 19 +#define SQL_REPLACE_DOCSIZE 20 +#define SQL_SELECT_DOCSIZE 21 +#define SQL_SELECT_STAT 22 +#define SQL_REPLACE_STAT 23 + +#define SQL_SELECT_ALL_PREFIX_LEVEL 24 +#define SQL_DELETE_ALL_TERMS_SEGDIR 25 +#define SQL_DELETE_SEGDIR_RANGE 26 +#define SQL_SELECT_ALL_LANGID 27 +#define SQL_FIND_MERGE_LEVEL 28 +#define SQL_MAX_LEAF_NODE_ESTIMATE 29 +#define SQL_DELETE_SEGDIR_ENTRY 30 +#define SQL_SHIFT_SEGDIR_ENTRY 31 +#define SQL_SELECT_SEGDIR 32 +#define SQL_CHOMP_SEGDIR 33 +#define SQL_SEGMENT_IS_APPENDABLE 34 +#define SQL_SELECT_INDEXES 35 +#define SQL_SELECT_MXLEVEL 36 + +#define SQL_SELECT_LEVEL_RANGE2 37 +#define SQL_UPDATE_LEVEL_IDX 38 +#define SQL_UPDATE_LEVEL 39 + +/* +** This function is used to obtain an SQLite prepared statement handle +** for the statement identified by the second argument. If successful, +** *pp is set to the requested statement handle and SQLITE_OK returned. +** Otherwise, an SQLite error code is returned and *pp is set to 0. +** +** If argument apVal is not NULL, then it must point to an array with +** at least as many entries as the requested statement has bound +** parameters. The values are bound to the statements parameters before +** returning. +*/ +static int fts3SqlStmt( + Fts3Table *p, /* Virtual table handle */ + int eStmt, /* One of the SQL_XXX constants above */ + sqlite3_stmt **pp, /* OUT: Statement handle */ + sqlite3_value **apVal /* Values to bind to statement */ +){ + const char *azSql[] = { +/* 0 */ "DELETE FROM %Q.'%q_content' WHERE rowid = ?", +/* 1 */ "SELECT NOT EXISTS(SELECT docid FROM %Q.'%q_content' WHERE rowid!=?)", +/* 2 */ "DELETE FROM %Q.'%q_content'", +/* 3 */ "DELETE FROM %Q.'%q_segments'", +/* 4 */ "DELETE FROM %Q.'%q_segdir'", +/* 5 */ "DELETE FROM %Q.'%q_docsize'", +/* 6 */ "DELETE FROM %Q.'%q_stat'", +/* 7 */ "SELECT %s WHERE rowid=?", +/* 8 */ "SELECT (SELECT max(idx) FROM %Q.'%q_segdir' WHERE level = ?) + 1", +/* 9 */ "REPLACE INTO %Q.'%q_segments'(blockid, block) VALUES(?, ?)", +/* 10 */ "SELECT coalesce((SELECT max(blockid) FROM %Q.'%q_segments') + 1, 1)", +/* 11 */ "REPLACE INTO %Q.'%q_segdir' VALUES(?,?,?,?,?,?)", + + /* Return segments in order from oldest to newest.*/ +/* 12 */ "SELECT idx, start_block, leaves_end_block, end_block, root " + "FROM %Q.'%q_segdir' WHERE level = ? ORDER BY idx ASC", +/* 13 */ "SELECT idx, start_block, leaves_end_block, end_block, root " + "FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ?" + "ORDER BY level DESC, idx ASC", + +/* 14 */ "SELECT count(*) FROM %Q.'%q_segdir' WHERE level = ?", +/* 15 */ "SELECT max(level) FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ?", + +/* 16 */ "DELETE FROM %Q.'%q_segdir' WHERE level = ?", +/* 17 */ "DELETE FROM %Q.'%q_segments' WHERE blockid BETWEEN ? AND ?", +/* 18 */ "INSERT INTO %Q.'%q_content' VALUES(%s)", +/* 19 */ "DELETE FROM %Q.'%q_docsize' WHERE docid = ?", +/* 20 */ "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", +/* 21 */ "SELECT size FROM %Q.'%q_docsize' WHERE docid=?", +/* 22 */ "SELECT value FROM %Q.'%q_stat' WHERE id=?", +/* 23 */ "REPLACE INTO %Q.'%q_stat' VALUES(?,?)", +/* 24 */ "", +/* 25 */ "", + +/* 26 */ "DELETE FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ?", +/* 27 */ "SELECT ? UNION SELECT level / (1024 * ?) FROM %Q.'%q_segdir'", + +/* This statement is used to determine which level to read the input from +** when performing an incremental merge. It returns the absolute level number +** of the oldest level in the db that contains at least ? segments. Or, +** if no level in the FTS index contains more than ? segments, the statement +** returns zero rows. */ +/* 28 */ "SELECT level, count(*) AS cnt FROM %Q.'%q_segdir' " + " GROUP BY level HAVING cnt>=?" + " ORDER BY (level %% 1024) ASC, 2 DESC LIMIT 1", + +/* Estimate the upper limit on the number of leaf nodes in a new segment +** created by merging the oldest :2 segments from absolute level :1. See +** function sqlite3Fts3Incrmerge() for details. */ +/* 29 */ "SELECT 2 * total(1 + leaves_end_block - start_block) " + " FROM (SELECT * FROM %Q.'%q_segdir' " + " WHERE level = ? ORDER BY idx ASC LIMIT ?" + " )", + +/* SQL_DELETE_SEGDIR_ENTRY +** Delete the %_segdir entry on absolute level :1 with index :2. */ +/* 30 */ "DELETE FROM %Q.'%q_segdir' WHERE level = ? AND idx = ?", + +/* SQL_SHIFT_SEGDIR_ENTRY +** Modify the idx value for the segment with idx=:3 on absolute level :2 +** to :1. */ +/* 31 */ "UPDATE %Q.'%q_segdir' SET idx = ? WHERE level=? AND idx=?", + +/* SQL_SELECT_SEGDIR +** Read a single entry from the %_segdir table. The entry from absolute +** level :1 with index value :2. */ +/* 32 */ "SELECT idx, start_block, leaves_end_block, end_block, root " + "FROM %Q.'%q_segdir' WHERE level = ? AND idx = ?", + +/* SQL_CHOMP_SEGDIR +** Update the start_block (:1) and root (:2) fields of the %_segdir +** entry located on absolute level :3 with index :4. */ +/* 33 */ "UPDATE %Q.'%q_segdir' SET start_block = ?, root = ?" + "WHERE level = ? AND idx = ?", + +/* SQL_SEGMENT_IS_APPENDABLE +** Return a single row if the segment with end_block=? is appendable. Or +** no rows otherwise. */ +/* 34 */ "SELECT 1 FROM %Q.'%q_segments' WHERE blockid=? AND block IS NULL", + +/* SQL_SELECT_INDEXES +** Return the list of valid segment indexes for absolute level ? */ +/* 35 */ "SELECT idx FROM %Q.'%q_segdir' WHERE level=? ORDER BY 1 ASC", + +/* SQL_SELECT_MXLEVEL +** Return the largest relative level in the FTS index or indexes. */ +/* 36 */ "SELECT max( level %% 1024 ) FROM %Q.'%q_segdir'", + + /* Return segments in order from oldest to newest.*/ +/* 37 */ "SELECT level, idx, end_block " + "FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ? " + "ORDER BY level DESC, idx ASC", + + /* Update statements used while promoting segments */ +/* 38 */ "UPDATE OR FAIL %Q.'%q_segdir' SET level=-1,idx=? " + "WHERE level=? AND idx=?", +/* 39 */ "UPDATE OR FAIL %Q.'%q_segdir' SET level=? WHERE level=-1" + + }; + int rc = SQLITE_OK; + sqlite3_stmt *pStmt; + + assert( SizeofArray(azSql)==SizeofArray(p->aStmt) ); + assert( eStmt<SizeofArray(azSql) && eStmt>=0 ); + + pStmt = p->aStmt[eStmt]; + if( !pStmt ){ + int f = SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB; + char *zSql; + if( eStmt==SQL_CONTENT_INSERT ){ + zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist); + }else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){ + f &= ~SQLITE_PREPARE_NO_VTAB; + zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist); + }else{ + zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName); + } + if( !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v3(p->db, zSql, -1, f, &pStmt, NULL); + sqlite3_free(zSql); + assert( rc==SQLITE_OK || pStmt==0 ); + p->aStmt[eStmt] = pStmt; + } + } + if( apVal ){ + int i; + int nParam = sqlite3_bind_parameter_count(pStmt); + for(i=0; rc==SQLITE_OK && i<nParam; i++){ + rc = sqlite3_bind_value(pStmt, i+1, apVal[i]); + } + } + *pp = pStmt; + return rc; +} + + +static int fts3SelectDocsize( + Fts3Table *pTab, /* FTS3 table handle */ + sqlite3_int64 iDocid, /* Docid to bind for SQL_SELECT_DOCSIZE */ + sqlite3_stmt **ppStmt /* OUT: Statement handle */ +){ + sqlite3_stmt *pStmt = 0; /* Statement requested from fts3SqlStmt() */ + int rc; /* Return code */ + + rc = fts3SqlStmt(pTab, SQL_SELECT_DOCSIZE, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pStmt, 1, iDocid); + rc = sqlite3_step(pStmt); + if( rc!=SQLITE_ROW || sqlite3_column_type(pStmt, 0)!=SQLITE_BLOB ){ + rc = sqlite3_reset(pStmt); + if( rc==SQLITE_OK ) rc = FTS_CORRUPT_VTAB; + pStmt = 0; + }else{ + rc = SQLITE_OK; + } + } + + *ppStmt = pStmt; + return rc; +} + +SQLITE_PRIVATE int sqlite3Fts3SelectDoctotal( + Fts3Table *pTab, /* Fts3 table handle */ + sqlite3_stmt **ppStmt /* OUT: Statement handle */ +){ + sqlite3_stmt *pStmt = 0; + int rc; + rc = fts3SqlStmt(pTab, SQL_SELECT_STAT, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL); + if( sqlite3_step(pStmt)!=SQLITE_ROW + || sqlite3_column_type(pStmt, 0)!=SQLITE_BLOB + ){ + rc = sqlite3_reset(pStmt); + if( rc==SQLITE_OK ) rc = FTS_CORRUPT_VTAB; + pStmt = 0; + } + } + *ppStmt = pStmt; + return rc; +} + +SQLITE_PRIVATE int sqlite3Fts3SelectDocsize( + Fts3Table *pTab, /* Fts3 table handle */ + sqlite3_int64 iDocid, /* Docid to read size data for */ + sqlite3_stmt **ppStmt /* OUT: Statement handle */ +){ + return fts3SelectDocsize(pTab, iDocid, ppStmt); +} + +/* +** Similar to fts3SqlStmt(). Except, after binding the parameters in +** array apVal[] to the SQL statement identified by eStmt, the statement +** is executed. +** +** Returns SQLITE_OK if the statement is successfully executed, or an +** SQLite error code otherwise. +*/ +static void fts3SqlExec( + int *pRC, /* Result code */ + Fts3Table *p, /* The FTS3 table */ + int eStmt, /* Index of statement to evaluate */ + sqlite3_value **apVal /* Parameters to bind */ +){ + sqlite3_stmt *pStmt; + int rc; + if( *pRC ) return; + rc = fts3SqlStmt(p, eStmt, &pStmt, apVal); + if( rc==SQLITE_OK ){ + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + } + *pRC = rc; +} + + +/* +** This function ensures that the caller has obtained an exclusive +** shared-cache table-lock on the %_segdir table. This is required before +** writing data to the fts3 table. If this lock is not acquired first, then +** the caller may end up attempting to take this lock as part of committing +** a transaction, causing SQLite to return SQLITE_LOCKED or +** LOCKED_SHAREDCACHEto a COMMIT command. +** +** It is best to avoid this because if FTS3 returns any error when +** committing a transaction, the whole transaction will be rolled back. +** And this is not what users expect when they get SQLITE_LOCKED_SHAREDCACHE. +** It can still happen if the user locks the underlying tables directly +** instead of accessing them via FTS. +*/ +static int fts3Writelock(Fts3Table *p){ + int rc = SQLITE_OK; + + if( p->nPendingData==0 ){ + sqlite3_stmt *pStmt; + rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_LEVEL, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_null(pStmt, 1); + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + } + } + + return rc; +} + +/* +** FTS maintains a separate indexes for each language-id (a 32-bit integer). +** Within each language id, a separate index is maintained to store the +** document terms, and each configured prefix size (configured the FTS +** "prefix=" option). And each index consists of multiple levels ("relative +** levels"). +** +** All three of these values (the language id, the specific index and the +** level within the index) are encoded in 64-bit integer values stored +** in the %_segdir table on disk. This function is used to convert three +** separate component values into the single 64-bit integer value that +** can be used to query the %_segdir table. +** +** Specifically, each language-id/index combination is allocated 1024 +** 64-bit integer level values ("absolute levels"). The main terms index +** for language-id 0 is allocate values 0-1023. The first prefix index +** (if any) for language-id 0 is allocated values 1024-2047. And so on. +** Language 1 indexes are allocated immediately following language 0. +** +** So, for a system with nPrefix prefix indexes configured, the block of +** absolute levels that corresponds to language-id iLangid and index +** iIndex starts at absolute level ((iLangid * (nPrefix+1) + iIndex) * 1024). +*/ +static sqlite3_int64 getAbsoluteLevel( + Fts3Table *p, /* FTS3 table handle */ + int iLangid, /* Language id */ + int iIndex, /* Index in p->aIndex[] */ + int iLevel /* Level of segments */ +){ + sqlite3_int64 iBase; /* First absolute level for iLangid/iIndex */ + assert_fts3_nc( iLangid>=0 ); + assert( p->nIndex>0 ); + assert( iIndex>=0 && iIndex<p->nIndex ); + + iBase = ((sqlite3_int64)iLangid * p->nIndex + iIndex) * FTS3_SEGDIR_MAXLEVEL; + return iBase + iLevel; +} + +/* +** Set *ppStmt to a statement handle that may be used to iterate through +** all rows in the %_segdir table, from oldest to newest. If successful, +** return SQLITE_OK. If an error occurs while preparing the statement, +** return an SQLite error code. +** +** There is only ever one instance of this SQL statement compiled for +** each FTS3 table. +** +** The statement returns the following columns from the %_segdir table: +** +** 0: idx +** 1: start_block +** 2: leaves_end_block +** 3: end_block +** 4: root +*/ +SQLITE_PRIVATE int sqlite3Fts3AllSegdirs( + Fts3Table *p, /* FTS3 table */ + int iLangid, /* Language being queried */ + int iIndex, /* Index for p->aIndex[] */ + int iLevel, /* Level to select (relative level) */ + sqlite3_stmt **ppStmt /* OUT: Compiled statement */ +){ + int rc; + sqlite3_stmt *pStmt = 0; + + assert( iLevel==FTS3_SEGCURSOR_ALL || iLevel>=0 ); + assert( iLevel<FTS3_SEGDIR_MAXLEVEL ); + assert( iIndex>=0 && iIndex<p->nIndex ); + + if( iLevel<0 ){ + /* "SELECT * FROM %_segdir WHERE level BETWEEN ? AND ? ORDER BY ..." */ + rc = fts3SqlStmt(p, SQL_SELECT_LEVEL_RANGE, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex, 0)); + sqlite3_bind_int64(pStmt, 2, + getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1) + ); + } + }else{ + /* "SELECT * FROM %_segdir WHERE level = ? ORDER BY ..." */ + rc = fts3SqlStmt(p, SQL_SELECT_LEVEL, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex,iLevel)); + } + } + *ppStmt = pStmt; + return rc; +} + + +/* +** Append a single varint to a PendingList buffer. SQLITE_OK is returned +** if successful, or an SQLite error code otherwise. +** +** This function also serves to allocate the PendingList structure itself. +** For example, to create a new PendingList structure containing two +** varints: +** +** PendingList *p = 0; +** fts3PendingListAppendVarint(&p, 1); +** fts3PendingListAppendVarint(&p, 2); +*/ +static int fts3PendingListAppendVarint( + PendingList **pp, /* IN/OUT: Pointer to PendingList struct */ + sqlite3_int64 i /* Value to append to data */ +){ + PendingList *p = *pp; + + /* Allocate or grow the PendingList as required. */ + if( !p ){ + p = sqlite3_malloc64(sizeof(*p) + 100); + if( !p ){ + return SQLITE_NOMEM; + } + p->nSpace = 100; + p->aData = (char *)&p[1]; + p->nData = 0; + } + else if( p->nData+FTS3_VARINT_MAX+1>p->nSpace ){ + i64 nNew = p->nSpace * 2; + p = sqlite3_realloc64(p, sizeof(*p) + nNew); + if( !p ){ + sqlite3_free(*pp); + *pp = 0; + return SQLITE_NOMEM; + } + p->nSpace = (int)nNew; + p->aData = (char *)&p[1]; + } + + /* Append the new serialized varint to the end of the list. */ + p->nData += sqlite3Fts3PutVarint(&p->aData[p->nData], i); + p->aData[p->nData] = '\0'; + *pp = p; + return SQLITE_OK; +} + +/* +** Add a docid/column/position entry to a PendingList structure. Non-zero +** is returned if the structure is sqlite3_realloced as part of adding +** the entry. Otherwise, zero. +** +** If an OOM error occurs, *pRc is set to SQLITE_NOMEM before returning. +** Zero is always returned in this case. Otherwise, if no OOM error occurs, +** it is set to SQLITE_OK. +*/ +static int fts3PendingListAppend( + PendingList **pp, /* IN/OUT: PendingList structure */ + sqlite3_int64 iDocid, /* Docid for entry to add */ + sqlite3_int64 iCol, /* Column for entry to add */ + sqlite3_int64 iPos, /* Position of term for entry to add */ + int *pRc /* OUT: Return code */ +){ + PendingList *p = *pp; + int rc = SQLITE_OK; + + assert( !p || p->iLastDocid<=iDocid ); + + if( !p || p->iLastDocid!=iDocid ){ + u64 iDelta = (u64)iDocid - (u64)(p ? p->iLastDocid : 0); + if( p ){ + assert( p->nData<p->nSpace ); + assert( p->aData[p->nData]==0 ); + p->nData++; + } + if( SQLITE_OK!=(rc = fts3PendingListAppendVarint(&p, iDelta)) ){ + goto pendinglistappend_out; + } + p->iLastCol = -1; + p->iLastPos = 0; + p->iLastDocid = iDocid; + } + if( iCol>0 && p->iLastCol!=iCol ){ + if( SQLITE_OK!=(rc = fts3PendingListAppendVarint(&p, 1)) + || SQLITE_OK!=(rc = fts3PendingListAppendVarint(&p, iCol)) + ){ + goto pendinglistappend_out; + } + p->iLastCol = iCol; + p->iLastPos = 0; + } + if( iCol>=0 ){ + assert( iPos>p->iLastPos || (iPos==0 && p->iLastPos==0) ); + rc = fts3PendingListAppendVarint(&p, 2+iPos-p->iLastPos); + if( rc==SQLITE_OK ){ + p->iLastPos = iPos; + } + } + + pendinglistappend_out: + *pRc = rc; + if( p!=*pp ){ + *pp = p; + return 1; + } + return 0; +} + +/* +** Free a PendingList object allocated by fts3PendingListAppend(). +*/ +static void fts3PendingListDelete(PendingList *pList){ + sqlite3_free(pList); +} + +/* +** Add an entry to one of the pending-terms hash tables. +*/ +static int fts3PendingTermsAddOne( + Fts3Table *p, + int iCol, + int iPos, + Fts3Hash *pHash, /* Pending terms hash table to add entry to */ + const char *zToken, + int nToken +){ + PendingList *pList; + int rc = SQLITE_OK; + + pList = (PendingList *)fts3HashFind(pHash, zToken, nToken); + if( pList ){ + p->nPendingData -= (pList->nData + nToken + sizeof(Fts3HashElem)); + } + if( fts3PendingListAppend(&pList, p->iPrevDocid, iCol, iPos, &rc) ){ + if( pList==fts3HashInsert(pHash, zToken, nToken, pList) ){ + /* Malloc failed while inserting the new entry. This can only + ** happen if there was no previous entry for this token. + */ + assert( 0==fts3HashFind(pHash, zToken, nToken) ); + sqlite3_free(pList); + rc = SQLITE_NOMEM; + } + } + if( rc==SQLITE_OK ){ + p->nPendingData += (pList->nData + nToken + sizeof(Fts3HashElem)); + } + return rc; +} + +/* +** Tokenize the nul-terminated string zText and add all tokens to the +** pending-terms hash-table. The docid used is that currently stored in +** p->iPrevDocid, and the column is specified by argument iCol. +** +** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. +*/ +static int fts3PendingTermsAdd( + Fts3Table *p, /* Table into which text will be inserted */ + int iLangid, /* Language id to use */ + const char *zText, /* Text of document to be inserted */ + int iCol, /* Column into which text is being inserted */ + u32 *pnWord /* IN/OUT: Incr. by number tokens inserted */ +){ + int rc; + int iStart = 0; + int iEnd = 0; + int iPos = 0; + int nWord = 0; + + char const *zToken; + int nToken = 0; + + sqlite3_tokenizer *pTokenizer = p->pTokenizer; + sqlite3_tokenizer_module const *pModule = pTokenizer->pModule; + sqlite3_tokenizer_cursor *pCsr; + int (*xNext)(sqlite3_tokenizer_cursor *pCursor, + const char**,int*,int*,int*,int*); + + assert( pTokenizer && pModule ); + + /* If the user has inserted a NULL value, this function may be called with + ** zText==0. In this case, add zero token entries to the hash table and + ** return early. */ + if( zText==0 ){ + *pnWord = 0; + return SQLITE_OK; + } + + rc = sqlite3Fts3OpenTokenizer(pTokenizer, iLangid, zText, -1, &pCsr); + if( rc!=SQLITE_OK ){ + return rc; + } + + xNext = pModule->xNext; + while( SQLITE_OK==rc + && SQLITE_OK==(rc = xNext(pCsr, &zToken, &nToken, &iStart, &iEnd, &iPos)) + ){ + int i; + if( iPos>=nWord ) nWord = iPos+1; + + /* Positions cannot be negative; we use -1 as a terminator internally. + ** Tokens must have a non-zero length. + */ + if( iPos<0 || !zToken || nToken<=0 ){ + rc = SQLITE_ERROR; + break; + } + + /* Add the term to the terms index */ + rc = fts3PendingTermsAddOne( + p, iCol, iPos, &p->aIndex[0].hPending, zToken, nToken + ); + + /* Add the term to each of the prefix indexes that it is not too + ** short for. */ + for(i=1; rc==SQLITE_OK && i<p->nIndex; i++){ + struct Fts3Index *pIndex = &p->aIndex[i]; + if( nToken<pIndex->nPrefix ) continue; + rc = fts3PendingTermsAddOne( + p, iCol, iPos, &pIndex->hPending, zToken, pIndex->nPrefix + ); + } + } + + pModule->xClose(pCsr); + *pnWord += nWord; + return (rc==SQLITE_DONE ? SQLITE_OK : rc); +} + +/* +** Calling this function indicates that subsequent calls to +** fts3PendingTermsAdd() are to add term/position-list pairs for the +** contents of the document with docid iDocid. +*/ +static int fts3PendingTermsDocid( + Fts3Table *p, /* Full-text table handle */ + int bDelete, /* True if this op is a delete */ + int iLangid, /* Language id of row being written */ + sqlite_int64 iDocid /* Docid of row being written */ +){ + assert( iLangid>=0 ); + assert( bDelete==1 || bDelete==0 ); + + /* TODO(shess) Explore whether partially flushing the buffer on + ** forced-flush would provide better performance. I suspect that if + ** we ordered the doclists by size and flushed the largest until the + ** buffer was half empty, that would let the less frequent terms + ** generate longer doclists. + */ + if( iDocid<p->iPrevDocid + || (iDocid==p->iPrevDocid && p->bPrevDelete==0) + || p->iPrevLangid!=iLangid + || p->nPendingData>p->nMaxPendingData + ){ + int rc = sqlite3Fts3PendingTermsFlush(p); + if( rc!=SQLITE_OK ) return rc; + } + p->iPrevDocid = iDocid; + p->iPrevLangid = iLangid; + p->bPrevDelete = bDelete; + return SQLITE_OK; +} + +/* +** Discard the contents of the pending-terms hash tables. +*/ +SQLITE_PRIVATE void sqlite3Fts3PendingTermsClear(Fts3Table *p){ + int i; + for(i=0; i<p->nIndex; i++){ + Fts3HashElem *pElem; + Fts3Hash *pHash = &p->aIndex[i].hPending; + for(pElem=fts3HashFirst(pHash); pElem; pElem=fts3HashNext(pElem)){ + PendingList *pList = (PendingList *)fts3HashData(pElem); + fts3PendingListDelete(pList); + } + fts3HashClear(pHash); + } + p->nPendingData = 0; +} + +/* +** This function is called by the xUpdate() method as part of an INSERT +** operation. It adds entries for each term in the new record to the +** pendingTerms hash table. +** +** Argument apVal is the same as the similarly named argument passed to +** fts3InsertData(). Parameter iDocid is the docid of the new row. +*/ +static int fts3InsertTerms( + Fts3Table *p, + int iLangid, + sqlite3_value **apVal, + u32 *aSz +){ + int i; /* Iterator variable */ + for(i=2; i<p->nColumn+2; i++){ + int iCol = i-2; + if( p->abNotindexed[iCol]==0 ){ + const char *zText = (const char *)sqlite3_value_text(apVal[i]); + int rc = fts3PendingTermsAdd(p, iLangid, zText, iCol, &aSz[iCol]); + if( rc!=SQLITE_OK ){ + return rc; + } + aSz[p->nColumn] += sqlite3_value_bytes(apVal[i]); + } + } + return SQLITE_OK; +} + +/* +** This function is called by the xUpdate() method for an INSERT operation. +** The apVal parameter is passed a copy of the apVal argument passed by +** SQLite to the xUpdate() method. i.e: +** +** apVal[0] Not used for INSERT. +** apVal[1] rowid +** apVal[2] Left-most user-defined column +** ... +** apVal[p->nColumn+1] Right-most user-defined column +** apVal[p->nColumn+2] Hidden column with same name as table +** apVal[p->nColumn+3] Hidden "docid" column (alias for rowid) +** apVal[p->nColumn+4] Hidden languageid column +*/ +static int fts3InsertData( + Fts3Table *p, /* Full-text table */ + sqlite3_value **apVal, /* Array of values to insert */ + sqlite3_int64 *piDocid /* OUT: Docid for row just inserted */ +){ + int rc; /* Return code */ + sqlite3_stmt *pContentInsert; /* INSERT INTO %_content VALUES(...) */ + + if( p->zContentTbl ){ + sqlite3_value *pRowid = apVal[p->nColumn+3]; + if( sqlite3_value_type(pRowid)==SQLITE_NULL ){ + pRowid = apVal[1]; + } + if( sqlite3_value_type(pRowid)!=SQLITE_INTEGER ){ + return SQLITE_CONSTRAINT; + } + *piDocid = sqlite3_value_int64(pRowid); + return SQLITE_OK; + } + + /* Locate the statement handle used to insert data into the %_content + ** table. The SQL for this statement is: + ** + ** INSERT INTO %_content VALUES(?, ?, ?, ...) + ** + ** The statement features N '?' variables, where N is the number of user + ** defined columns in the FTS3 table, plus one for the docid field. + */ + rc = fts3SqlStmt(p, SQL_CONTENT_INSERT, &pContentInsert, &apVal[1]); + if( rc==SQLITE_OK && p->zLanguageid ){ + rc = sqlite3_bind_int( + pContentInsert, p->nColumn+2, + sqlite3_value_int(apVal[p->nColumn+4]) + ); + } + if( rc!=SQLITE_OK ) return rc; + + /* There is a quirk here. The users INSERT statement may have specified + ** a value for the "rowid" field, for the "docid" field, or for both. + ** Which is a problem, since "rowid" and "docid" are aliases for the + ** same value. For example: + ** + ** INSERT INTO fts3tbl(rowid, docid) VALUES(1, 2); + ** + ** In FTS3, this is an error. It is an error to specify non-NULL values + ** for both docid and some other rowid alias. + */ + if( SQLITE_NULL!=sqlite3_value_type(apVal[3+p->nColumn]) ){ + if( SQLITE_NULL==sqlite3_value_type(apVal[0]) + && SQLITE_NULL!=sqlite3_value_type(apVal[1]) + ){ + /* A rowid/docid conflict. */ + return SQLITE_ERROR; + } + rc = sqlite3_bind_value(pContentInsert, 1, apVal[3+p->nColumn]); + if( rc!=SQLITE_OK ) return rc; + } + + /* Execute the statement to insert the record. Set *piDocid to the + ** new docid value. + */ + sqlite3_step(pContentInsert); + rc = sqlite3_reset(pContentInsert); + + *piDocid = sqlite3_last_insert_rowid(p->db); + return rc; +} + + + +/* +** Remove all data from the FTS3 table. Clear the hash table containing +** pending terms. +*/ +static int fts3DeleteAll(Fts3Table *p, int bContent){ + int rc = SQLITE_OK; /* Return code */ + + /* Discard the contents of the pending-terms hash table. */ + sqlite3Fts3PendingTermsClear(p); + + /* Delete everything from the shadow tables. Except, leave %_content as + ** is if bContent is false. */ + assert( p->zContentTbl==0 || bContent==0 ); + if( bContent ) fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0); + fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGMENTS, 0); + fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0); + if( p->bHasDocsize ){ + fts3SqlExec(&rc, p, SQL_DELETE_ALL_DOCSIZE, 0); + } + if( p->bHasStat ){ + fts3SqlExec(&rc, p, SQL_DELETE_ALL_STAT, 0); + } + return rc; +} + +/* +** +*/ +static int langidFromSelect(Fts3Table *p, sqlite3_stmt *pSelect){ + int iLangid = 0; + if( p->zLanguageid ) iLangid = sqlite3_column_int(pSelect, p->nColumn+1); + return iLangid; +} + +/* +** The first element in the apVal[] array is assumed to contain the docid +** (an integer) of a row about to be deleted. Remove all terms from the +** full-text index. +*/ +static void fts3DeleteTerms( + int *pRC, /* Result code */ + Fts3Table *p, /* The FTS table to delete from */ + sqlite3_value *pRowid, /* The docid to be deleted */ + u32 *aSz, /* Sizes of deleted document written here */ + int *pbFound /* OUT: Set to true if row really does exist */ +){ + int rc; + sqlite3_stmt *pSelect; + + assert( *pbFound==0 ); + if( *pRC ) return; + rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pSelect, &pRowid); + if( rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pSelect) ){ + int i; + int iLangid = langidFromSelect(p, pSelect); + i64 iDocid = sqlite3_column_int64(pSelect, 0); + rc = fts3PendingTermsDocid(p, 1, iLangid, iDocid); + for(i=1; rc==SQLITE_OK && i<=p->nColumn; i++){ + int iCol = i-1; + if( p->abNotindexed[iCol]==0 ){ + const char *zText = (const char *)sqlite3_column_text(pSelect, i); + rc = fts3PendingTermsAdd(p, iLangid, zText, -1, &aSz[iCol]); + aSz[p->nColumn] += sqlite3_column_bytes(pSelect, i); + } + } + if( rc!=SQLITE_OK ){ + sqlite3_reset(pSelect); + *pRC = rc; + return; + } + *pbFound = 1; + } + rc = sqlite3_reset(pSelect); + }else{ + sqlite3_reset(pSelect); + } + *pRC = rc; +} + +/* +** Forward declaration to account for the circular dependency between +** functions fts3SegmentMerge() and fts3AllocateSegdirIdx(). +*/ +static int fts3SegmentMerge(Fts3Table *, int, int, int); + +/* +** This function allocates a new level iLevel index in the segdir table. +** Usually, indexes are allocated within a level sequentially starting +** with 0, so the allocated index is one greater than the value returned +** by: +** +** SELECT max(idx) FROM %_segdir WHERE level = :iLevel +** +** However, if there are already FTS3_MERGE_COUNT indexes at the requested +** level, they are merged into a single level (iLevel+1) segment and the +** allocated index is 0. +** +** If successful, *piIdx is set to the allocated index slot and SQLITE_OK +** returned. Otherwise, an SQLite error code is returned. +*/ +static int fts3AllocateSegdirIdx( + Fts3Table *p, + int iLangid, /* Language id */ + int iIndex, /* Index for p->aIndex */ + int iLevel, + int *piIdx +){ + int rc; /* Return Code */ + sqlite3_stmt *pNextIdx; /* Query for next idx at level iLevel */ + int iNext = 0; /* Result of query pNextIdx */ + + assert( iLangid>=0 ); + assert( p->nIndex>=1 ); + + /* Set variable iNext to the next available segdir index at level iLevel. */ + rc = fts3SqlStmt(p, SQL_NEXT_SEGMENT_INDEX, &pNextIdx, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64( + pNextIdx, 1, getAbsoluteLevel(p, iLangid, iIndex, iLevel) + ); + if( SQLITE_ROW==sqlite3_step(pNextIdx) ){ + iNext = sqlite3_column_int(pNextIdx, 0); + } + rc = sqlite3_reset(pNextIdx); + } + + if( rc==SQLITE_OK ){ + /* If iNext is FTS3_MERGE_COUNT, indicating that level iLevel is already + ** full, merge all segments in level iLevel into a single iLevel+1 + ** segment and allocate (newly freed) index 0 at level iLevel. Otherwise, + ** if iNext is less than FTS3_MERGE_COUNT, allocate index iNext. + */ + if( iNext>=MergeCount(p) ){ + fts3LogMerge(16, getAbsoluteLevel(p, iLangid, iIndex, iLevel)); + rc = fts3SegmentMerge(p, iLangid, iIndex, iLevel); + *piIdx = 0; + }else{ + *piIdx = iNext; + } + } + + return rc; +} + +/* +** The %_segments table is declared as follows: +** +** CREATE TABLE %_segments(blockid INTEGER PRIMARY KEY, block BLOB) +** +** This function reads data from a single row of the %_segments table. The +** specific row is identified by the iBlockid parameter. If paBlob is not +** NULL, then a buffer is allocated using sqlite3_malloc() and populated +** with the contents of the blob stored in the "block" column of the +** identified table row is. Whether or not paBlob is NULL, *pnBlob is set +** to the size of the blob in bytes before returning. +** +** If an error occurs, or the table does not contain the specified row, +** an SQLite error code is returned. Otherwise, SQLITE_OK is returned. If +** paBlob is non-NULL, then it is the responsibility of the caller to +** eventually free the returned buffer. +** +** This function may leave an open sqlite3_blob* handle in the +** Fts3Table.pSegments variable. This handle is reused by subsequent calls +** to this function. The handle may be closed by calling the +** sqlite3Fts3SegmentsClose() function. Reusing a blob handle is a handy +** performance improvement, but the blob handle should always be closed +** before control is returned to the user (to prevent a lock being held +** on the database file for longer than necessary). Thus, any virtual table +** method (xFilter etc.) that may directly or indirectly call this function +** must call sqlite3Fts3SegmentsClose() before returning. +*/ +SQLITE_PRIVATE int sqlite3Fts3ReadBlock( + Fts3Table *p, /* FTS3 table handle */ + sqlite3_int64 iBlockid, /* Access the row with blockid=$iBlockid */ + char **paBlob, /* OUT: Blob data in malloc'd buffer */ + int *pnBlob, /* OUT: Size of blob data */ + int *pnLoad /* OUT: Bytes actually loaded */ +){ + int rc; /* Return code */ + + /* pnBlob must be non-NULL. paBlob may be NULL or non-NULL. */ + assert( pnBlob ); + + if( p->pSegments ){ + rc = sqlite3_blob_reopen(p->pSegments, iBlockid); + }else{ + if( 0==p->zSegmentsTbl ){ + p->zSegmentsTbl = sqlite3_mprintf("%s_segments", p->zName); + if( 0==p->zSegmentsTbl ) return SQLITE_NOMEM; + } + rc = sqlite3_blob_open( + p->db, p->zDb, p->zSegmentsTbl, "block", iBlockid, 0, &p->pSegments + ); + } + + if( rc==SQLITE_OK ){ + int nByte = sqlite3_blob_bytes(p->pSegments); + *pnBlob = nByte; + if( paBlob ){ + char *aByte = sqlite3_malloc64((i64)nByte + FTS3_NODE_PADDING); + if( !aByte ){ + rc = SQLITE_NOMEM; + }else{ + if( pnLoad && nByte>(FTS3_NODE_CHUNK_THRESHOLD) ){ + nByte = FTS3_NODE_CHUNKSIZE; + *pnLoad = nByte; + } + rc = sqlite3_blob_read(p->pSegments, aByte, nByte, 0); + memset(&aByte[nByte], 0, FTS3_NODE_PADDING); + if( rc!=SQLITE_OK ){ + sqlite3_free(aByte); + aByte = 0; + } + } + *paBlob = aByte; + } + }else if( rc==SQLITE_ERROR ){ + rc = FTS_CORRUPT_VTAB; + } + + return rc; +} + +/* +** Close the blob handle at p->pSegments, if it is open. See comments above +** the sqlite3Fts3ReadBlock() function for details. +*/ +SQLITE_PRIVATE void sqlite3Fts3SegmentsClose(Fts3Table *p){ + sqlite3_blob_close(p->pSegments); + p->pSegments = 0; +} + +static int fts3SegReaderIncrRead(Fts3SegReader *pReader){ + int nRead; /* Number of bytes to read */ + int rc; /* Return code */ + + nRead = MIN(pReader->nNode - pReader->nPopulate, FTS3_NODE_CHUNKSIZE); + rc = sqlite3_blob_read( + pReader->pBlob, + &pReader->aNode[pReader->nPopulate], + nRead, + pReader->nPopulate + ); + + if( rc==SQLITE_OK ){ + pReader->nPopulate += nRead; + memset(&pReader->aNode[pReader->nPopulate], 0, FTS3_NODE_PADDING); + if( pReader->nPopulate==pReader->nNode ){ + sqlite3_blob_close(pReader->pBlob); + pReader->pBlob = 0; + pReader->nPopulate = 0; + } + } + return rc; +} + +static int fts3SegReaderRequire(Fts3SegReader *pReader, char *pFrom, int nByte){ + int rc = SQLITE_OK; + assert( !pReader->pBlob + || (pFrom>=pReader->aNode && pFrom<&pReader->aNode[pReader->nNode]) + ); + while( pReader->pBlob && rc==SQLITE_OK + && (pFrom - pReader->aNode + nByte)>pReader->nPopulate + ){ + rc = fts3SegReaderIncrRead(pReader); + } + return rc; +} + +/* +** Set an Fts3SegReader cursor to point at EOF. +*/ +static void fts3SegReaderSetEof(Fts3SegReader *pSeg){ + if( !fts3SegReaderIsRootOnly(pSeg) ){ + sqlite3_free(pSeg->aNode); + sqlite3_blob_close(pSeg->pBlob); + pSeg->pBlob = 0; + } + pSeg->aNode = 0; +} + +/* +** Move the iterator passed as the first argument to the next term in the +** segment. If successful, SQLITE_OK is returned. If there is no next term, +** SQLITE_DONE. Otherwise, an SQLite error code. +*/ +static int fts3SegReaderNext( + Fts3Table *p, + Fts3SegReader *pReader, + int bIncr +){ + int rc; /* Return code of various sub-routines */ + char *pNext; /* Cursor variable */ + int nPrefix; /* Number of bytes in term prefix */ + int nSuffix; /* Number of bytes in term suffix */ + + if( !pReader->aDoclist ){ + pNext = pReader->aNode; + }else{ + pNext = &pReader->aDoclist[pReader->nDoclist]; + } + + if( !pNext || pNext>=&pReader->aNode[pReader->nNode] ){ + + if( fts3SegReaderIsPending(pReader) ){ + Fts3HashElem *pElem = *(pReader->ppNextElem); + sqlite3_free(pReader->aNode); + pReader->aNode = 0; + if( pElem ){ + char *aCopy; + PendingList *pList = (PendingList *)fts3HashData(pElem); + int nCopy = pList->nData+1; + + int nTerm = fts3HashKeysize(pElem); + if( (nTerm+1)>pReader->nTermAlloc ){ + sqlite3_free(pReader->zTerm); + pReader->zTerm = (char*)sqlite3_malloc64(((i64)nTerm+1)*2); + if( !pReader->zTerm ) return SQLITE_NOMEM; + pReader->nTermAlloc = (nTerm+1)*2; + } + memcpy(pReader->zTerm, fts3HashKey(pElem), nTerm); + pReader->zTerm[nTerm] = '\0'; + pReader->nTerm = nTerm; + + aCopy = (char*)sqlite3_malloc64(nCopy); + if( !aCopy ) return SQLITE_NOMEM; + memcpy(aCopy, pList->aData, nCopy); + pReader->nNode = pReader->nDoclist = nCopy; + pReader->aNode = pReader->aDoclist = aCopy; + pReader->ppNextElem++; + assert( pReader->aNode ); + } + return SQLITE_OK; + } + + fts3SegReaderSetEof(pReader); + + /* If iCurrentBlock>=iLeafEndBlock, this is an EOF condition. All leaf + ** blocks have already been traversed. */ +#ifdef CORRUPT_DB + assert( pReader->iCurrentBlock<=pReader->iLeafEndBlock || CORRUPT_DB ); +#endif + if( pReader->iCurrentBlock>=pReader->iLeafEndBlock ){ + return SQLITE_OK; + } + + rc = sqlite3Fts3ReadBlock( + p, ++pReader->iCurrentBlock, &pReader->aNode, &pReader->nNode, + (bIncr ? &pReader->nPopulate : 0) + ); + if( rc!=SQLITE_OK ) return rc; + assert( pReader->pBlob==0 ); + if( bIncr && pReader->nPopulate<pReader->nNode ){ + pReader->pBlob = p->pSegments; + p->pSegments = 0; + } + pNext = pReader->aNode; + } + + assert( !fts3SegReaderIsPending(pReader) ); + + rc = fts3SegReaderRequire(pReader, pNext, FTS3_VARINT_MAX*2); + if( rc!=SQLITE_OK ) return rc; + + /* Because of the FTS3_NODE_PADDING bytes of padding, the following is + ** safe (no risk of overread) even if the node data is corrupted. */ + pNext += fts3GetVarint32(pNext, &nPrefix); + pNext += fts3GetVarint32(pNext, &nSuffix); + if( nSuffix<=0 + || (&pReader->aNode[pReader->nNode] - pNext)<nSuffix + || nPrefix>pReader->nTerm + ){ + return FTS_CORRUPT_VTAB; + } + + /* Both nPrefix and nSuffix were read by fts3GetVarint32() and so are + ** between 0 and 0x7FFFFFFF. But the sum of the two may cause integer + ** overflow - hence the (i64) casts. */ + if( (i64)nPrefix+nSuffix>(i64)pReader->nTermAlloc ){ + i64 nNew = ((i64)nPrefix+nSuffix)*2; + char *zNew = sqlite3_realloc64(pReader->zTerm, nNew); + if( !zNew ){ + return SQLITE_NOMEM; + } + pReader->zTerm = zNew; + pReader->nTermAlloc = nNew; + } + + rc = fts3SegReaderRequire(pReader, pNext, nSuffix+FTS3_VARINT_MAX); + if( rc!=SQLITE_OK ) return rc; + + memcpy(&pReader->zTerm[nPrefix], pNext, nSuffix); + pReader->nTerm = nPrefix+nSuffix; + pNext += nSuffix; + pNext += fts3GetVarint32(pNext, &pReader->nDoclist); + pReader->aDoclist = pNext; + pReader->pOffsetList = 0; + + /* Check that the doclist does not appear to extend past the end of the + ** b-tree node. And that the final byte of the doclist is 0x00. If either + ** of these statements is untrue, then the data structure is corrupt. + */ + if( pReader->nDoclist > pReader->nNode-(pReader->aDoclist-pReader->aNode) + || (pReader->nPopulate==0 && pReader->aDoclist[pReader->nDoclist-1]) + || pReader->nDoclist==0 + ){ + return FTS_CORRUPT_VTAB; + } + return SQLITE_OK; +} + +/* +** Set the SegReader to point to the first docid in the doclist associated +** with the current term. +*/ +static int fts3SegReaderFirstDocid(Fts3Table *pTab, Fts3SegReader *pReader){ + int rc = SQLITE_OK; + assert( pReader->aDoclist ); + assert( !pReader->pOffsetList ); + if( pTab->bDescIdx && fts3SegReaderIsPending(pReader) ){ + u8 bEof = 0; + pReader->iDocid = 0; + pReader->nOffsetList = 0; + sqlite3Fts3DoclistPrev(0, + pReader->aDoclist, pReader->nDoclist, &pReader->pOffsetList, + &pReader->iDocid, &pReader->nOffsetList, &bEof + ); + }else{ + rc = fts3SegReaderRequire(pReader, pReader->aDoclist, FTS3_VARINT_MAX); + if( rc==SQLITE_OK ){ + int n = sqlite3Fts3GetVarint(pReader->aDoclist, &pReader->iDocid); + pReader->pOffsetList = &pReader->aDoclist[n]; + } + } + return rc; +} + +/* +** Advance the SegReader to point to the next docid in the doclist +** associated with the current term. +** +** If arguments ppOffsetList and pnOffsetList are not NULL, then +** *ppOffsetList is set to point to the first column-offset list +** in the doclist entry (i.e. immediately past the docid varint). +** *pnOffsetList is set to the length of the set of column-offset +** lists, not including the nul-terminator byte. For example: +*/ +static int fts3SegReaderNextDocid( + Fts3Table *pTab, + Fts3SegReader *pReader, /* Reader to advance to next docid */ + char **ppOffsetList, /* OUT: Pointer to current position-list */ + int *pnOffsetList /* OUT: Length of *ppOffsetList in bytes */ +){ + int rc = SQLITE_OK; + char *p = pReader->pOffsetList; + char c = 0; + + assert( p ); + + if( pTab->bDescIdx && fts3SegReaderIsPending(pReader) ){ + /* A pending-terms seg-reader for an FTS4 table that uses order=desc. + ** Pending-terms doclists are always built up in ascending order, so + ** we have to iterate through them backwards here. */ + u8 bEof = 0; + if( ppOffsetList ){ + *ppOffsetList = pReader->pOffsetList; + *pnOffsetList = pReader->nOffsetList - 1; + } + sqlite3Fts3DoclistPrev(0, + pReader->aDoclist, pReader->nDoclist, &p, &pReader->iDocid, + &pReader->nOffsetList, &bEof + ); + if( bEof ){ + pReader->pOffsetList = 0; + }else{ + pReader->pOffsetList = p; + } + }else{ + char *pEnd = &pReader->aDoclist[pReader->nDoclist]; + + /* Pointer p currently points at the first byte of an offset list. The + ** following block advances it to point one byte past the end of + ** the same offset list. */ + while( 1 ){ + + /* The following line of code (and the "p++" below the while() loop) is + ** normally all that is required to move pointer p to the desired + ** position. The exception is if this node is being loaded from disk + ** incrementally and pointer "p" now points to the first byte past + ** the populated part of pReader->aNode[]. + */ + while( *p | c ) c = *p++ & 0x80; + assert( *p==0 ); + + if( pReader->pBlob==0 || p<&pReader->aNode[pReader->nPopulate] ) break; + rc = fts3SegReaderIncrRead(pReader); + if( rc!=SQLITE_OK ) return rc; + } + p++; + + /* If required, populate the output variables with a pointer to and the + ** size of the previous offset-list. + */ + if( ppOffsetList ){ + *ppOffsetList = pReader->pOffsetList; + *pnOffsetList = (int)(p - pReader->pOffsetList - 1); + } + + /* List may have been edited in place by fts3EvalNearTrim() */ + while( p<pEnd && *p==0 ) p++; + + /* If there are no more entries in the doclist, set pOffsetList to + ** NULL. Otherwise, set Fts3SegReader.iDocid to the next docid and + ** Fts3SegReader.pOffsetList to point to the next offset list before + ** returning. + */ + if( p>=pEnd ){ + pReader->pOffsetList = 0; + }else{ + rc = fts3SegReaderRequire(pReader, p, FTS3_VARINT_MAX); + if( rc==SQLITE_OK ){ + u64 iDelta; + pReader->pOffsetList = p + sqlite3Fts3GetVarintU(p, &iDelta); + if( pTab->bDescIdx ){ + pReader->iDocid = (i64)((u64)pReader->iDocid - iDelta); + }else{ + pReader->iDocid = (i64)((u64)pReader->iDocid + iDelta); + } + } + } + } + + return rc; +} + + +SQLITE_PRIVATE int sqlite3Fts3MsrOvfl( + Fts3Cursor *pCsr, + Fts3MultiSegReader *pMsr, + int *pnOvfl +){ + Fts3Table *p = (Fts3Table*)pCsr->base.pVtab; + int nOvfl = 0; + int ii; + int rc = SQLITE_OK; + int pgsz = p->nPgsz; + + assert( p->bFts4 ); + assert( pgsz>0 ); + + for(ii=0; rc==SQLITE_OK && ii<pMsr->nSegment; ii++){ + Fts3SegReader *pReader = pMsr->apSegment[ii]; + if( !fts3SegReaderIsPending(pReader) + && !fts3SegReaderIsRootOnly(pReader) + ){ + sqlite3_int64 jj; + for(jj=pReader->iStartBlock; jj<=pReader->iLeafEndBlock; jj++){ + int nBlob; + rc = sqlite3Fts3ReadBlock(p, jj, 0, &nBlob, 0); + if( rc!=SQLITE_OK ) break; + if( (nBlob+35)>pgsz ){ + nOvfl += (nBlob + 34)/pgsz; + } + } + } + } + *pnOvfl = nOvfl; + return rc; +} + +/* +** Free all allocations associated with the iterator passed as the +** second argument. +*/ +SQLITE_PRIVATE void sqlite3Fts3SegReaderFree(Fts3SegReader *pReader){ + if( pReader ){ + sqlite3_free(pReader->zTerm); + if( !fts3SegReaderIsRootOnly(pReader) ){ + sqlite3_free(pReader->aNode); + } + sqlite3_blob_close(pReader->pBlob); + } + sqlite3_free(pReader); +} + +/* +** Allocate a new SegReader object. +*/ +SQLITE_PRIVATE int sqlite3Fts3SegReaderNew( + int iAge, /* Segment "age". */ + int bLookup, /* True for a lookup only */ + sqlite3_int64 iStartLeaf, /* First leaf to traverse */ + sqlite3_int64 iEndLeaf, /* Final leaf to traverse */ + sqlite3_int64 iEndBlock, /* Final block of segment */ + const char *zRoot, /* Buffer containing root node */ + int nRoot, /* Size of buffer containing root node */ + Fts3SegReader **ppReader /* OUT: Allocated Fts3SegReader */ +){ + Fts3SegReader *pReader; /* Newly allocated SegReader object */ + int nExtra = 0; /* Bytes to allocate segment root node */ + + assert( zRoot!=0 || nRoot==0 ); +#ifdef CORRUPT_DB + assert( zRoot!=0 || CORRUPT_DB ); +#endif + + if( iStartLeaf==0 ){ + if( iEndLeaf!=0 ) return FTS_CORRUPT_VTAB; + nExtra = nRoot + FTS3_NODE_PADDING; + } + + pReader = (Fts3SegReader *)sqlite3_malloc64(sizeof(Fts3SegReader) + nExtra); + if( !pReader ){ + return SQLITE_NOMEM; + } + memset(pReader, 0, sizeof(Fts3SegReader)); + pReader->iIdx = iAge; + pReader->bLookup = bLookup!=0; + pReader->iStartBlock = iStartLeaf; + pReader->iLeafEndBlock = iEndLeaf; + pReader->iEndBlock = iEndBlock; + + if( nExtra ){ + /* The entire segment is stored in the root node. */ + pReader->aNode = (char *)&pReader[1]; + pReader->rootOnly = 1; + pReader->nNode = nRoot; + if( nRoot ) memcpy(pReader->aNode, zRoot, nRoot); + memset(&pReader->aNode[nRoot], 0, FTS3_NODE_PADDING); + }else{ + pReader->iCurrentBlock = iStartLeaf-1; + } + *ppReader = pReader; + return SQLITE_OK; +} + +/* +** This is a comparison function used as a qsort() callback when sorting +** an array of pending terms by term. This occurs as part of flushing +** the contents of the pending-terms hash table to the database. +*/ +static int SQLITE_CDECL fts3CompareElemByTerm( + const void *lhs, + const void *rhs +){ + char *z1 = fts3HashKey(*(Fts3HashElem **)lhs); + char *z2 = fts3HashKey(*(Fts3HashElem **)rhs); + int n1 = fts3HashKeysize(*(Fts3HashElem **)lhs); + int n2 = fts3HashKeysize(*(Fts3HashElem **)rhs); + + int n = (n1<n2 ? n1 : n2); + int c = memcmp(z1, z2, n); + if( c==0 ){ + c = n1 - n2; + } + return c; +} + +/* +** This function is used to allocate an Fts3SegReader that iterates through +** a subset of the terms stored in the Fts3Table.pendingTerms array. +** +** If the isPrefixIter parameter is zero, then the returned SegReader iterates +** through each term in the pending-terms table. Or, if isPrefixIter is +** non-zero, it iterates through each term and its prefixes. For example, if +** the pending terms hash table contains the terms "sqlite", "mysql" and +** "firebird", then the iterator visits the following 'terms' (in the order +** shown): +** +** f fi fir fire fireb firebi firebir firebird +** m my mys mysq mysql +** s sq sql sqli sqlit sqlite +** +** Whereas if isPrefixIter is zero, the terms visited are: +** +** firebird mysql sqlite +*/ +SQLITE_PRIVATE int sqlite3Fts3SegReaderPending( + Fts3Table *p, /* Virtual table handle */ + int iIndex, /* Index for p->aIndex */ + const char *zTerm, /* Term to search for */ + int nTerm, /* Size of buffer zTerm */ + int bPrefix, /* True for a prefix iterator */ + Fts3SegReader **ppReader /* OUT: SegReader for pending-terms */ +){ + Fts3SegReader *pReader = 0; /* Fts3SegReader object to return */ + Fts3HashElem *pE; /* Iterator variable */ + Fts3HashElem **aElem = 0; /* Array of term hash entries to scan */ + int nElem = 0; /* Size of array at aElem */ + int rc = SQLITE_OK; /* Return Code */ + Fts3Hash *pHash; + + pHash = &p->aIndex[iIndex].hPending; + if( bPrefix ){ + int nAlloc = 0; /* Size of allocated array at aElem */ + + for(pE=fts3HashFirst(pHash); pE; pE=fts3HashNext(pE)){ + char *zKey = (char *)fts3HashKey(pE); + int nKey = fts3HashKeysize(pE); + if( nTerm==0 || (nKey>=nTerm && 0==memcmp(zKey, zTerm, nTerm)) ){ + if( nElem==nAlloc ){ + Fts3HashElem **aElem2; + nAlloc += 16; + aElem2 = (Fts3HashElem **)sqlite3_realloc64( + aElem, nAlloc*sizeof(Fts3HashElem *) + ); + if( !aElem2 ){ + rc = SQLITE_NOMEM; + nElem = 0; + break; + } + aElem = aElem2; + } + + aElem[nElem++] = pE; + } + } + + /* If more than one term matches the prefix, sort the Fts3HashElem + ** objects in term order using qsort(). This uses the same comparison + ** callback as is used when flushing terms to disk. + */ + if( nElem>1 ){ + qsort(aElem, nElem, sizeof(Fts3HashElem *), fts3CompareElemByTerm); + } + + }else{ + /* The query is a simple term lookup that matches at most one term in + ** the index. All that is required is a straight hash-lookup. + ** + ** Because the stack address of pE may be accessed via the aElem pointer + ** below, the "Fts3HashElem *pE" must be declared so that it is valid + ** within this entire function, not just this "else{...}" block. + */ + pE = fts3HashFindElem(pHash, zTerm, nTerm); + if( pE ){ + aElem = &pE; + nElem = 1; + } + } + + if( nElem>0 ){ + sqlite3_int64 nByte; + nByte = sizeof(Fts3SegReader) + (nElem+1)*sizeof(Fts3HashElem *); + pReader = (Fts3SegReader *)sqlite3_malloc64(nByte); + if( !pReader ){ + rc = SQLITE_NOMEM; + }else{ + memset(pReader, 0, nByte); + pReader->iIdx = 0x7FFFFFFF; + pReader->ppNextElem = (Fts3HashElem **)&pReader[1]; + memcpy(pReader->ppNextElem, aElem, nElem*sizeof(Fts3HashElem *)); + } + } + + if( bPrefix ){ + sqlite3_free(aElem); + } + *ppReader = pReader; + return rc; +} + +/* +** Compare the entries pointed to by two Fts3SegReader structures. +** Comparison is as follows: +** +** 1) EOF is greater than not EOF. +** +** 2) The current terms (if any) are compared using memcmp(). If one +** term is a prefix of another, the longer term is considered the +** larger. +** +** 3) By segment age. An older segment is considered larger. +*/ +static int fts3SegReaderCmp(Fts3SegReader *pLhs, Fts3SegReader *pRhs){ + int rc; + if( pLhs->aNode && pRhs->aNode ){ + int rc2 = pLhs->nTerm - pRhs->nTerm; + if( rc2<0 ){ + rc = memcmp(pLhs->zTerm, pRhs->zTerm, pLhs->nTerm); + }else{ + rc = memcmp(pLhs->zTerm, pRhs->zTerm, pRhs->nTerm); + } + if( rc==0 ){ + rc = rc2; + } + }else{ + rc = (pLhs->aNode==0) - (pRhs->aNode==0); + } + if( rc==0 ){ + rc = pRhs->iIdx - pLhs->iIdx; + } + assert_fts3_nc( rc!=0 ); + return rc; +} + +/* +** A different comparison function for SegReader structures. In this +** version, it is assumed that each SegReader points to an entry in +** a doclist for identical terms. Comparison is made as follows: +** +** 1) EOF (end of doclist in this case) is greater than not EOF. +** +** 2) By current docid. +** +** 3) By segment age. An older segment is considered larger. +*/ +static int fts3SegReaderDoclistCmp(Fts3SegReader *pLhs, Fts3SegReader *pRhs){ + int rc = (pLhs->pOffsetList==0)-(pRhs->pOffsetList==0); + if( rc==0 ){ + if( pLhs->iDocid==pRhs->iDocid ){ + rc = pRhs->iIdx - pLhs->iIdx; + }else{ + rc = (pLhs->iDocid > pRhs->iDocid) ? 1 : -1; + } + } + assert( pLhs->aNode && pRhs->aNode ); + return rc; +} +static int fts3SegReaderDoclistCmpRev(Fts3SegReader *pLhs, Fts3SegReader *pRhs){ + int rc = (pLhs->pOffsetList==0)-(pRhs->pOffsetList==0); + if( rc==0 ){ + if( pLhs->iDocid==pRhs->iDocid ){ + rc = pRhs->iIdx - pLhs->iIdx; + }else{ + rc = (pLhs->iDocid < pRhs->iDocid) ? 1 : -1; + } + } + assert( pLhs->aNode && pRhs->aNode ); + return rc; +} + +/* +** Compare the term that the Fts3SegReader object passed as the first argument +** points to with the term specified by arguments zTerm and nTerm. +** +** If the pSeg iterator is already at EOF, return 0. Otherwise, return +** -ve if the pSeg term is less than zTerm/nTerm, 0 if the two terms are +** equal, or +ve if the pSeg term is greater than zTerm/nTerm. +*/ +static int fts3SegReaderTermCmp( + Fts3SegReader *pSeg, /* Segment reader object */ + const char *zTerm, /* Term to compare to */ + int nTerm /* Size of term zTerm in bytes */ +){ + int res = 0; + if( pSeg->aNode ){ + if( pSeg->nTerm>nTerm ){ + res = memcmp(pSeg->zTerm, zTerm, nTerm); + }else{ + res = memcmp(pSeg->zTerm, zTerm, pSeg->nTerm); + } + if( res==0 ){ + res = pSeg->nTerm-nTerm; + } + } + return res; +} + +/* +** Argument apSegment is an array of nSegment elements. It is known that +** the final (nSegment-nSuspect) members are already in sorted order +** (according to the comparison function provided). This function shuffles +** the array around until all entries are in sorted order. +*/ +static void fts3SegReaderSort( + Fts3SegReader **apSegment, /* Array to sort entries of */ + int nSegment, /* Size of apSegment array */ + int nSuspect, /* Unsorted entry count */ + int (*xCmp)(Fts3SegReader *, Fts3SegReader *) /* Comparison function */ +){ + int i; /* Iterator variable */ + + assert( nSuspect<=nSegment ); + + if( nSuspect==nSegment ) nSuspect--; + for(i=nSuspect-1; i>=0; i--){ + int j; + for(j=i; j<(nSegment-1); j++){ + Fts3SegReader *pTmp; + if( xCmp(apSegment[j], apSegment[j+1])<0 ) break; + pTmp = apSegment[j+1]; + apSegment[j+1] = apSegment[j]; + apSegment[j] = pTmp; + } + } + +#ifndef NDEBUG + /* Check that the list really is sorted now. */ + for(i=0; i<(nSuspect-1); i++){ + assert( xCmp(apSegment[i], apSegment[i+1])<0 ); + } +#endif +} + +/* +** Insert a record into the %_segments table. +*/ +static int fts3WriteSegment( + Fts3Table *p, /* Virtual table handle */ + sqlite3_int64 iBlock, /* Block id for new block */ + char *z, /* Pointer to buffer containing block data */ + int n /* Size of buffer z in bytes */ +){ + sqlite3_stmt *pStmt; + int rc = fts3SqlStmt(p, SQL_INSERT_SEGMENTS, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pStmt, 1, iBlock); + sqlite3_bind_blob(pStmt, 2, z, n, SQLITE_STATIC); + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + sqlite3_bind_null(pStmt, 2); + } + return rc; +} + +/* +** Find the largest relative level number in the table. If successful, set +** *pnMax to this value and return SQLITE_OK. Otherwise, if an error occurs, +** set *pnMax to zero and return an SQLite error code. +*/ +SQLITE_PRIVATE int sqlite3Fts3MaxLevel(Fts3Table *p, int *pnMax){ + int rc; + int mxLevel = 0; + sqlite3_stmt *pStmt = 0; + + rc = fts3SqlStmt(p, SQL_SELECT_MXLEVEL, &pStmt, 0); + if( rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + mxLevel = sqlite3_column_int(pStmt, 0); + } + rc = sqlite3_reset(pStmt); + } + *pnMax = mxLevel; + return rc; +} + +/* +** Insert a record into the %_segdir table. +*/ +static int fts3WriteSegdir( + Fts3Table *p, /* Virtual table handle */ + sqlite3_int64 iLevel, /* Value for "level" field (absolute level) */ + int iIdx, /* Value for "idx" field */ + sqlite3_int64 iStartBlock, /* Value for "start_block" field */ + sqlite3_int64 iLeafEndBlock, /* Value for "leaves_end_block" field */ + sqlite3_int64 iEndBlock, /* Value for "end_block" field */ + sqlite3_int64 nLeafData, /* Bytes of leaf data in segment */ + char *zRoot, /* Blob value for "root" field */ + int nRoot /* Number of bytes in buffer zRoot */ +){ + sqlite3_stmt *pStmt; + int rc = fts3SqlStmt(p, SQL_INSERT_SEGDIR, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pStmt, 1, iLevel); + sqlite3_bind_int(pStmt, 2, iIdx); + sqlite3_bind_int64(pStmt, 3, iStartBlock); + sqlite3_bind_int64(pStmt, 4, iLeafEndBlock); + if( nLeafData==0 ){ + sqlite3_bind_int64(pStmt, 5, iEndBlock); + }else{ + char *zEnd = sqlite3_mprintf("%lld %lld", iEndBlock, nLeafData); + if( !zEnd ) return SQLITE_NOMEM; + sqlite3_bind_text(pStmt, 5, zEnd, -1, sqlite3_free); + } + sqlite3_bind_blob(pStmt, 6, zRoot, nRoot, SQLITE_STATIC); + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + sqlite3_bind_null(pStmt, 6); + } + return rc; +} + +/* +** Return the size of the common prefix (if any) shared by zPrev and +** zNext, in bytes. For example, +** +** fts3PrefixCompress("abc", 3, "abcdef", 6) // returns 3 +** fts3PrefixCompress("abX", 3, "abcdef", 6) // returns 2 +** fts3PrefixCompress("abX", 3, "Xbcdef", 6) // returns 0 +*/ +static int fts3PrefixCompress( + const char *zPrev, /* Buffer containing previous term */ + int nPrev, /* Size of buffer zPrev in bytes */ + const char *zNext, /* Buffer containing next term */ + int nNext /* Size of buffer zNext in bytes */ +){ + int n; + for(n=0; n<nPrev && n<nNext && zPrev[n]==zNext[n]; n++); + assert_fts3_nc( n<nNext ); + return n; +} + +/* +** Add term zTerm to the SegmentNode. It is guaranteed that zTerm is larger +** (according to memcmp) than the previous term. +*/ +static int fts3NodeAddTerm( + Fts3Table *p, /* Virtual table handle */ + SegmentNode **ppTree, /* IN/OUT: SegmentNode handle */ + int isCopyTerm, /* True if zTerm/nTerm is transient */ + const char *zTerm, /* Pointer to buffer containing term */ + int nTerm /* Size of term in bytes */ +){ + SegmentNode *pTree = *ppTree; + int rc; + SegmentNode *pNew; + + /* First try to append the term to the current node. Return early if + ** this is possible. + */ + if( pTree ){ + int nData = pTree->nData; /* Current size of node in bytes */ + int nReq = nData; /* Required space after adding zTerm */ + int nPrefix; /* Number of bytes of prefix compression */ + int nSuffix; /* Suffix length */ + + nPrefix = fts3PrefixCompress(pTree->zTerm, pTree->nTerm, zTerm, nTerm); + nSuffix = nTerm-nPrefix; + + /* If nSuffix is zero or less, then zTerm/nTerm must be a prefix of + ** pWriter->zTerm/pWriter->nTerm. i.e. must be equal to or less than when + ** compared with BINARY collation. This indicates corruption. */ + if( nSuffix<=0 ) return FTS_CORRUPT_VTAB; + + nReq += sqlite3Fts3VarintLen(nPrefix)+sqlite3Fts3VarintLen(nSuffix)+nSuffix; + if( nReq<=p->nNodeSize || !pTree->zTerm ){ + + if( nReq>p->nNodeSize ){ + /* An unusual case: this is the first term to be added to the node + ** and the static node buffer (p->nNodeSize bytes) is not large + ** enough. Use a separately malloced buffer instead This wastes + ** p->nNodeSize bytes, but since this scenario only comes about when + ** the database contain two terms that share a prefix of almost 2KB, + ** this is not expected to be a serious problem. + */ + assert( pTree->aData==(char *)&pTree[1] ); + pTree->aData = (char *)sqlite3_malloc64(nReq); + if( !pTree->aData ){ + return SQLITE_NOMEM; + } + } + + if( pTree->zTerm ){ + /* There is no prefix-length field for first term in a node */ + nData += sqlite3Fts3PutVarint(&pTree->aData[nData], nPrefix); + } + + nData += sqlite3Fts3PutVarint(&pTree->aData[nData], nSuffix); + memcpy(&pTree->aData[nData], &zTerm[nPrefix], nSuffix); + pTree->nData = nData + nSuffix; + pTree->nEntry++; + + if( isCopyTerm ){ + if( pTree->nMalloc<nTerm ){ + char *zNew = sqlite3_realloc64(pTree->zMalloc, (i64)nTerm*2); + if( !zNew ){ + return SQLITE_NOMEM; + } + pTree->nMalloc = nTerm*2; + pTree->zMalloc = zNew; + } + pTree->zTerm = pTree->zMalloc; + memcpy(pTree->zTerm, zTerm, nTerm); + pTree->nTerm = nTerm; + }else{ + pTree->zTerm = (char *)zTerm; + pTree->nTerm = nTerm; + } + return SQLITE_OK; + } + } + + /* If control flows to here, it was not possible to append zTerm to the + ** current node. Create a new node (a right-sibling of the current node). + ** If this is the first node in the tree, the term is added to it. + ** + ** Otherwise, the term is not added to the new node, it is left empty for + ** now. Instead, the term is inserted into the parent of pTree. If pTree + ** has no parent, one is created here. + */ + pNew = (SegmentNode *)sqlite3_malloc64(sizeof(SegmentNode) + p->nNodeSize); + if( !pNew ){ + return SQLITE_NOMEM; + } + memset(pNew, 0, sizeof(SegmentNode)); + pNew->nData = 1 + FTS3_VARINT_MAX; + pNew->aData = (char *)&pNew[1]; + + if( pTree ){ + SegmentNode *pParent = pTree->pParent; + rc = fts3NodeAddTerm(p, &pParent, isCopyTerm, zTerm, nTerm); + if( pTree->pParent==0 ){ + pTree->pParent = pParent; + } + pTree->pRight = pNew; + pNew->pLeftmost = pTree->pLeftmost; + pNew->pParent = pParent; + pNew->zMalloc = pTree->zMalloc; + pNew->nMalloc = pTree->nMalloc; + pTree->zMalloc = 0; + }else{ + pNew->pLeftmost = pNew; + rc = fts3NodeAddTerm(p, &pNew, isCopyTerm, zTerm, nTerm); + } + + *ppTree = pNew; + return rc; +} + +/* +** Helper function for fts3NodeWrite(). +*/ +static int fts3TreeFinishNode( + SegmentNode *pTree, + int iHeight, + sqlite3_int64 iLeftChild +){ + int nStart; + assert( iHeight>=1 && iHeight<128 ); + nStart = FTS3_VARINT_MAX - sqlite3Fts3VarintLen(iLeftChild); + pTree->aData[nStart] = (char)iHeight; + sqlite3Fts3PutVarint(&pTree->aData[nStart+1], iLeftChild); + return nStart; +} + +/* +** Write the buffer for the segment node pTree and all of its peers to the +** database. Then call this function recursively to write the parent of +** pTree and its peers to the database. +** +** Except, if pTree is a root node, do not write it to the database. Instead, +** set output variables *paRoot and *pnRoot to contain the root node. +** +** If successful, SQLITE_OK is returned and output variable *piLast is +** set to the largest blockid written to the database (or zero if no +** blocks were written to the db). Otherwise, an SQLite error code is +** returned. +*/ +static int fts3NodeWrite( + Fts3Table *p, /* Virtual table handle */ + SegmentNode *pTree, /* SegmentNode handle */ + int iHeight, /* Height of this node in tree */ + sqlite3_int64 iLeaf, /* Block id of first leaf node */ + sqlite3_int64 iFree, /* Block id of next free slot in %_segments */ + sqlite3_int64 *piLast, /* OUT: Block id of last entry written */ + char **paRoot, /* OUT: Data for root node */ + int *pnRoot /* OUT: Size of root node in bytes */ +){ + int rc = SQLITE_OK; + + if( !pTree->pParent ){ + /* Root node of the tree. */ + int nStart = fts3TreeFinishNode(pTree, iHeight, iLeaf); + *piLast = iFree-1; + *pnRoot = pTree->nData - nStart; + *paRoot = &pTree->aData[nStart]; + }else{ + SegmentNode *pIter; + sqlite3_int64 iNextFree = iFree; + sqlite3_int64 iNextLeaf = iLeaf; + for(pIter=pTree->pLeftmost; pIter && rc==SQLITE_OK; pIter=pIter->pRight){ + int nStart = fts3TreeFinishNode(pIter, iHeight, iNextLeaf); + int nWrite = pIter->nData - nStart; + + rc = fts3WriteSegment(p, iNextFree, &pIter->aData[nStart], nWrite); + iNextFree++; + iNextLeaf += (pIter->nEntry+1); + } + if( rc==SQLITE_OK ){ + assert( iNextLeaf==iFree ); + rc = fts3NodeWrite( + p, pTree->pParent, iHeight+1, iFree, iNextFree, piLast, paRoot, pnRoot + ); + } + } + + return rc; +} + +/* +** Free all memory allocations associated with the tree pTree. +*/ +static void fts3NodeFree(SegmentNode *pTree){ + if( pTree ){ + SegmentNode *p = pTree->pLeftmost; + fts3NodeFree(p->pParent); + while( p ){ + SegmentNode *pRight = p->pRight; + if( p->aData!=(char *)&p[1] ){ + sqlite3_free(p->aData); + } + assert( pRight==0 || p->zMalloc==0 ); + sqlite3_free(p->zMalloc); + sqlite3_free(p); + p = pRight; + } + } +} + +/* +** Add a term to the segment being constructed by the SegmentWriter object +** *ppWriter. When adding the first term to a segment, *ppWriter should +** be passed NULL. This function will allocate a new SegmentWriter object +** and return it via the input/output variable *ppWriter in this case. +** +** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. +*/ +static int fts3SegWriterAdd( + Fts3Table *p, /* Virtual table handle */ + SegmentWriter **ppWriter, /* IN/OUT: SegmentWriter handle */ + int isCopyTerm, /* True if buffer zTerm must be copied */ + const char *zTerm, /* Pointer to buffer containing term */ + int nTerm, /* Size of term in bytes */ + const char *aDoclist, /* Pointer to buffer containing doclist */ + int nDoclist /* Size of doclist in bytes */ +){ + int nPrefix; /* Size of term prefix in bytes */ + int nSuffix; /* Size of term suffix in bytes */ + i64 nReq; /* Number of bytes required on leaf page */ + int nData; + SegmentWriter *pWriter = *ppWriter; + + if( !pWriter ){ + int rc; + sqlite3_stmt *pStmt; + + /* Allocate the SegmentWriter structure */ + pWriter = (SegmentWriter *)sqlite3_malloc64(sizeof(SegmentWriter)); + if( !pWriter ) return SQLITE_NOMEM; + memset(pWriter, 0, sizeof(SegmentWriter)); + *ppWriter = pWriter; + + /* Allocate a buffer in which to accumulate data */ + pWriter->aData = (char *)sqlite3_malloc64(p->nNodeSize); + if( !pWriter->aData ) return SQLITE_NOMEM; + pWriter->nSize = p->nNodeSize; + + /* Find the next free blockid in the %_segments table */ + rc = fts3SqlStmt(p, SQL_NEXT_SEGMENTS_ID, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + pWriter->iFree = sqlite3_column_int64(pStmt, 0); + pWriter->iFirst = pWriter->iFree; + } + rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK ) return rc; + } + nData = pWriter->nData; + + nPrefix = fts3PrefixCompress(pWriter->zTerm, pWriter->nTerm, zTerm, nTerm); + nSuffix = nTerm-nPrefix; + + /* If nSuffix is zero or less, then zTerm/nTerm must be a prefix of + ** pWriter->zTerm/pWriter->nTerm. i.e. must be equal to or less than when + ** compared with BINARY collation. This indicates corruption. */ + if( nSuffix<=0 ) return FTS_CORRUPT_VTAB; + + /* Figure out how many bytes are required by this new entry */ + nReq = sqlite3Fts3VarintLen(nPrefix) + /* varint containing prefix size */ + sqlite3Fts3VarintLen(nSuffix) + /* varint containing suffix size */ + nSuffix + /* Term suffix */ + sqlite3Fts3VarintLen(nDoclist) + /* Size of doclist */ + nDoclist; /* Doclist data */ + + if( nData>0 && nData+nReq>p->nNodeSize ){ + int rc; + + /* The current leaf node is full. Write it out to the database. */ + if( pWriter->iFree==LARGEST_INT64 ) return FTS_CORRUPT_VTAB; + rc = fts3WriteSegment(p, pWriter->iFree++, pWriter->aData, nData); + if( rc!=SQLITE_OK ) return rc; + p->nLeafAdd++; + + /* Add the current term to the interior node tree. The term added to + ** the interior tree must: + ** + ** a) be greater than the largest term on the leaf node just written + ** to the database (still available in pWriter->zTerm), and + ** + ** b) be less than or equal to the term about to be added to the new + ** leaf node (zTerm/nTerm). + ** + ** In other words, it must be the prefix of zTerm 1 byte longer than + ** the common prefix (if any) of zTerm and pWriter->zTerm. + */ + assert( nPrefix<nTerm ); + rc = fts3NodeAddTerm(p, &pWriter->pTree, isCopyTerm, zTerm, nPrefix+1); + if( rc!=SQLITE_OK ) return rc; + + nData = 0; + pWriter->nTerm = 0; + + nPrefix = 0; + nSuffix = nTerm; + nReq = 1 + /* varint containing prefix size */ + sqlite3Fts3VarintLen(nTerm) + /* varint containing suffix size */ + nTerm + /* Term suffix */ + sqlite3Fts3VarintLen(nDoclist) + /* Size of doclist */ + nDoclist; /* Doclist data */ + } + + /* Increase the total number of bytes written to account for the new entry. */ + pWriter->nLeafData += nReq; + + /* If the buffer currently allocated is too small for this entry, realloc + ** the buffer to make it large enough. + */ + if( nReq>pWriter->nSize ){ + char *aNew = sqlite3_realloc64(pWriter->aData, nReq); + if( !aNew ) return SQLITE_NOMEM; + pWriter->aData = aNew; + pWriter->nSize = nReq; + } + assert( nData+nReq<=pWriter->nSize ); + + /* Append the prefix-compressed term and doclist to the buffer. */ + nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nPrefix); + nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nSuffix); + assert( nSuffix>0 ); + memcpy(&pWriter->aData[nData], &zTerm[nPrefix], nSuffix); + nData += nSuffix; + nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nDoclist); + assert( nDoclist>0 ); + memcpy(&pWriter->aData[nData], aDoclist, nDoclist); + pWriter->nData = nData + nDoclist; + + /* Save the current term so that it can be used to prefix-compress the next. + ** If the isCopyTerm parameter is true, then the buffer pointed to by + ** zTerm is transient, so take a copy of the term data. Otherwise, just + ** store a copy of the pointer. + */ + if( isCopyTerm ){ + if( nTerm>pWriter->nMalloc ){ + char *zNew = sqlite3_realloc64(pWriter->zMalloc, (i64)nTerm*2); + if( !zNew ){ + return SQLITE_NOMEM; + } + pWriter->nMalloc = nTerm*2; + pWriter->zMalloc = zNew; + pWriter->zTerm = zNew; + } + assert( pWriter->zTerm==pWriter->zMalloc ); + assert( nTerm>0 ); + memcpy(pWriter->zTerm, zTerm, nTerm); + }else{ + pWriter->zTerm = (char *)zTerm; + } + pWriter->nTerm = nTerm; + + return SQLITE_OK; +} + +/* +** Flush all data associated with the SegmentWriter object pWriter to the +** database. This function must be called after all terms have been added +** to the segment using fts3SegWriterAdd(). If successful, SQLITE_OK is +** returned. Otherwise, an SQLite error code. +*/ +static int fts3SegWriterFlush( + Fts3Table *p, /* Virtual table handle */ + SegmentWriter *pWriter, /* SegmentWriter to flush to the db */ + sqlite3_int64 iLevel, /* Value for 'level' column of %_segdir */ + int iIdx /* Value for 'idx' column of %_segdir */ +){ + int rc; /* Return code */ + if( pWriter->pTree ){ + sqlite3_int64 iLast = 0; /* Largest block id written to database */ + sqlite3_int64 iLastLeaf; /* Largest leaf block id written to db */ + char *zRoot = NULL; /* Pointer to buffer containing root node */ + int nRoot = 0; /* Size of buffer zRoot */ + + iLastLeaf = pWriter->iFree; + rc = fts3WriteSegment(p, pWriter->iFree++, pWriter->aData, pWriter->nData); + if( rc==SQLITE_OK ){ + rc = fts3NodeWrite(p, pWriter->pTree, 1, + pWriter->iFirst, pWriter->iFree, &iLast, &zRoot, &nRoot); + } + if( rc==SQLITE_OK ){ + rc = fts3WriteSegdir(p, iLevel, iIdx, + pWriter->iFirst, iLastLeaf, iLast, pWriter->nLeafData, zRoot, nRoot); + } + }else{ + /* The entire tree fits on the root node. Write it to the segdir table. */ + rc = fts3WriteSegdir(p, iLevel, iIdx, + 0, 0, 0, pWriter->nLeafData, pWriter->aData, pWriter->nData); + } + p->nLeafAdd++; + return rc; +} + +/* +** Release all memory held by the SegmentWriter object passed as the +** first argument. +*/ +static void fts3SegWriterFree(SegmentWriter *pWriter){ + if( pWriter ){ + sqlite3_free(pWriter->aData); + sqlite3_free(pWriter->zMalloc); + fts3NodeFree(pWriter->pTree); + sqlite3_free(pWriter); + } +} + +/* +** The first value in the apVal[] array is assumed to contain an integer. +** This function tests if there exist any documents with docid values that +** are different from that integer. i.e. if deleting the document with docid +** pRowid would mean the FTS3 table were empty. +** +** If successful, *pisEmpty is set to true if the table is empty except for +** document pRowid, or false otherwise, and SQLITE_OK is returned. If an +** error occurs, an SQLite error code is returned. +*/ +static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){ + sqlite3_stmt *pStmt; + int rc; + if( p->zContentTbl ){ + /* If using the content=xxx option, assume the table is never empty */ + *pisEmpty = 0; + rc = SQLITE_OK; + }else{ + rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid); + if( rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + *pisEmpty = sqlite3_column_int(pStmt, 0); + } + rc = sqlite3_reset(pStmt); + } + } + return rc; +} + +/* +** Set *pnMax to the largest segment level in the database for the index +** iIndex. +** +** Segment levels are stored in the 'level' column of the %_segdir table. +** +** Return SQLITE_OK if successful, or an SQLite error code if not. +*/ +static int fts3SegmentMaxLevel( + Fts3Table *p, + int iLangid, + int iIndex, + sqlite3_int64 *pnMax +){ + sqlite3_stmt *pStmt; + int rc; + assert( iIndex>=0 && iIndex<p->nIndex ); + + /* Set pStmt to the compiled version of: + ** + ** SELECT max(level) FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ? + ** + ** (1024 is actually the value of macro FTS3_SEGDIR_PREFIXLEVEL_STR). + */ + rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_MAX_LEVEL, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; + sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex, 0)); + sqlite3_bind_int64(pStmt, 2, + getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1) + ); + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + *pnMax = sqlite3_column_int64(pStmt, 0); + } + return sqlite3_reset(pStmt); +} + +/* +** iAbsLevel is an absolute level that may be assumed to exist within +** the database. This function checks if it is the largest level number +** within its index. Assuming no error occurs, *pbMax is set to 1 if +** iAbsLevel is indeed the largest level, or 0 otherwise, and SQLITE_OK +** is returned. If an error occurs, an error code is returned and the +** final value of *pbMax is undefined. +*/ +static int fts3SegmentIsMaxLevel(Fts3Table *p, i64 iAbsLevel, int *pbMax){ + + /* Set pStmt to the compiled version of: + ** + ** SELECT max(level) FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ? + ** + ** (1024 is actually the value of macro FTS3_SEGDIR_PREFIXLEVEL_STR). + */ + sqlite3_stmt *pStmt; + int rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_MAX_LEVEL, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; + sqlite3_bind_int64(pStmt, 1, iAbsLevel+1); + sqlite3_bind_int64(pStmt, 2, + (((u64)iAbsLevel/FTS3_SEGDIR_MAXLEVEL)+1) * FTS3_SEGDIR_MAXLEVEL + ); + + *pbMax = 0; + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + *pbMax = sqlite3_column_type(pStmt, 0)==SQLITE_NULL; + } + return sqlite3_reset(pStmt); +} + +/* +** Delete all entries in the %_segments table associated with the segment +** opened with seg-reader pSeg. This function does not affect the contents +** of the %_segdir table. +*/ +static int fts3DeleteSegment( + Fts3Table *p, /* FTS table handle */ + Fts3SegReader *pSeg /* Segment to delete */ +){ + int rc = SQLITE_OK; /* Return code */ + if( pSeg->iStartBlock ){ + sqlite3_stmt *pDelete; /* SQL statement to delete rows */ + rc = fts3SqlStmt(p, SQL_DELETE_SEGMENTS_RANGE, &pDelete, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pDelete, 1, pSeg->iStartBlock); + sqlite3_bind_int64(pDelete, 2, pSeg->iEndBlock); + sqlite3_step(pDelete); + rc = sqlite3_reset(pDelete); + } + } + return rc; +} + +/* +** This function is used after merging multiple segments into a single large +** segment to delete the old, now redundant, segment b-trees. Specifically, +** it: +** +** 1) Deletes all %_segments entries for the segments associated with +** each of the SegReader objects in the array passed as the third +** argument, and +** +** 2) deletes all %_segdir entries with level iLevel, or all %_segdir +** entries regardless of level if (iLevel<0). +** +** SQLITE_OK is returned if successful, otherwise an SQLite error code. +*/ +static int fts3DeleteSegdir( + Fts3Table *p, /* Virtual table handle */ + int iLangid, /* Language id */ + int iIndex, /* Index for p->aIndex */ + int iLevel, /* Level of %_segdir entries to delete */ + Fts3SegReader **apSegment, /* Array of SegReader objects */ + int nReader /* Size of array apSegment */ +){ + int rc = SQLITE_OK; /* Return Code */ + int i; /* Iterator variable */ + sqlite3_stmt *pDelete = 0; /* SQL statement to delete rows */ + + for(i=0; rc==SQLITE_OK && i<nReader; i++){ + rc = fts3DeleteSegment(p, apSegment[i]); + } + if( rc!=SQLITE_OK ){ + return rc; + } + + assert( iLevel>=0 || iLevel==FTS3_SEGCURSOR_ALL ); + if( iLevel==FTS3_SEGCURSOR_ALL ){ + rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_RANGE, &pDelete, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pDelete, 1, getAbsoluteLevel(p, iLangid, iIndex, 0)); + sqlite3_bind_int64(pDelete, 2, + getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1) + ); + } + }else{ + rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_LEVEL, &pDelete, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64( + pDelete, 1, getAbsoluteLevel(p, iLangid, iIndex, iLevel) + ); + } + } + + if( rc==SQLITE_OK ){ + sqlite3_step(pDelete); + rc = sqlite3_reset(pDelete); + } + + return rc; +} + +/* +** When this function is called, buffer *ppList (size *pnList bytes) contains +** a position list that may (or may not) feature multiple columns. This +** function adjusts the pointer *ppList and the length *pnList so that they +** identify the subset of the position list that corresponds to column iCol. +** +** If there are no entries in the input position list for column iCol, then +** *pnList is set to zero before returning. +** +** If parameter bZero is non-zero, then any part of the input list following +** the end of the output list is zeroed before returning. +*/ +static void fts3ColumnFilter( + int iCol, /* Column to filter on */ + int bZero, /* Zero out anything following *ppList */ + char **ppList, /* IN/OUT: Pointer to position list */ + int *pnList /* IN/OUT: Size of buffer *ppList in bytes */ +){ + char *pList = *ppList; + int nList = *pnList; + char *pEnd = &pList[nList]; + int iCurrent = 0; + char *p = pList; + + assert( iCol>=0 ); + while( 1 ){ + char c = 0; + while( p<pEnd && (c | *p)&0xFE ) c = *p++ & 0x80; + + if( iCol==iCurrent ){ + nList = (int)(p - pList); + break; + } + + nList -= (int)(p - pList); + pList = p; + if( nList<=0 ){ + break; + } + p = &pList[1]; + p += fts3GetVarint32(p, &iCurrent); + } + + if( bZero && (pEnd - &pList[nList])>0){ + memset(&pList[nList], 0, pEnd - &pList[nList]); + } + *ppList = pList; + *pnList = nList; +} + +/* +** Cache data in the Fts3MultiSegReader.aBuffer[] buffer (overwriting any +** existing data). Grow the buffer if required. +** +** If successful, return SQLITE_OK. Otherwise, if an OOM error is encountered +** trying to resize the buffer, return SQLITE_NOMEM. +*/ +static int fts3MsrBufferData( + Fts3MultiSegReader *pMsr, /* Multi-segment-reader handle */ + char *pList, + i64 nList +){ + if( (nList+FTS3_NODE_PADDING)>pMsr->nBuffer ){ + char *pNew; + int nNew = nList*2 + FTS3_NODE_PADDING; + pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, nNew); + if( !pNew ) return SQLITE_NOMEM; + pMsr->aBuffer = pNew; + pMsr->nBuffer = nNew; + } + + assert( nList>0 ); + memcpy(pMsr->aBuffer, pList, nList); + memset(&pMsr->aBuffer[nList], 0, FTS3_NODE_PADDING); + return SQLITE_OK; +} + +SQLITE_PRIVATE int sqlite3Fts3MsrIncrNext( + Fts3Table *p, /* Virtual table handle */ + Fts3MultiSegReader *pMsr, /* Multi-segment-reader handle */ + sqlite3_int64 *piDocid, /* OUT: Docid value */ + char **paPoslist, /* OUT: Pointer to position list */ + int *pnPoslist /* OUT: Size of position list in bytes */ +){ + int nMerge = pMsr->nAdvance; + Fts3SegReader **apSegment = pMsr->apSegment; + int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = ( + p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp + ); + + if( nMerge==0 ){ + *paPoslist = 0; + return SQLITE_OK; + } + + while( 1 ){ + Fts3SegReader *pSeg; + pSeg = pMsr->apSegment[0]; + + if( pSeg->pOffsetList==0 ){ + *paPoslist = 0; + break; + }else{ + int rc; + char *pList; + int nList; + int j; + sqlite3_int64 iDocid = apSegment[0]->iDocid; + + rc = fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList); + j = 1; + while( rc==SQLITE_OK + && j<nMerge + && apSegment[j]->pOffsetList + && apSegment[j]->iDocid==iDocid + ){ + rc = fts3SegReaderNextDocid(p, apSegment[j], 0, 0); + j++; + } + if( rc!=SQLITE_OK ) return rc; + fts3SegReaderSort(pMsr->apSegment, nMerge, j, xCmp); + + if( nList>0 && fts3SegReaderIsPending(apSegment[0]) ){ + rc = fts3MsrBufferData(pMsr, pList, (i64)nList+1); + if( rc!=SQLITE_OK ) return rc; + assert( (pMsr->aBuffer[nList] & 0xFE)==0x00 ); + pList = pMsr->aBuffer; + } + + if( pMsr->iColFilter>=0 ){ + fts3ColumnFilter(pMsr->iColFilter, 1, &pList, &nList); + } + + if( nList>0 ){ + *paPoslist = pList; + *piDocid = iDocid; + *pnPoslist = nList; + break; + } + } + } + + return SQLITE_OK; +} + +static int fts3SegReaderStart( + Fts3Table *p, /* Virtual table handle */ + Fts3MultiSegReader *pCsr, /* Cursor object */ + const char *zTerm, /* Term searched for (or NULL) */ + int nTerm /* Length of zTerm in bytes */ +){ + int i; + int nSeg = pCsr->nSegment; + + /* If the Fts3SegFilter defines a specific term (or term prefix) to search + ** for, then advance each segment iterator until it points to a term of + ** equal or greater value than the specified term. This prevents many + ** unnecessary merge/sort operations for the case where single segment + ** b-tree leaf nodes contain more than one term. + */ + for(i=0; pCsr->bRestart==0 && i<pCsr->nSegment; i++){ + int res = 0; + Fts3SegReader *pSeg = pCsr->apSegment[i]; + do { + int rc = fts3SegReaderNext(p, pSeg, 0); + if( rc!=SQLITE_OK ) return rc; + }while( zTerm && (res = fts3SegReaderTermCmp(pSeg, zTerm, nTerm))<0 ); + + if( pSeg->bLookup && res!=0 ){ + fts3SegReaderSetEof(pSeg); + } + } + fts3SegReaderSort(pCsr->apSegment, nSeg, nSeg, fts3SegReaderCmp); + + return SQLITE_OK; +} + +SQLITE_PRIVATE int sqlite3Fts3SegReaderStart( + Fts3Table *p, /* Virtual table handle */ + Fts3MultiSegReader *pCsr, /* Cursor object */ + Fts3SegFilter *pFilter /* Restrictions on range of iteration */ +){ + pCsr->pFilter = pFilter; + return fts3SegReaderStart(p, pCsr, pFilter->zTerm, pFilter->nTerm); +} + +SQLITE_PRIVATE int sqlite3Fts3MsrIncrStart( + Fts3Table *p, /* Virtual table handle */ + Fts3MultiSegReader *pCsr, /* Cursor object */ + int iCol, /* Column to match on. */ + const char *zTerm, /* Term to iterate through a doclist for */ + int nTerm /* Number of bytes in zTerm */ +){ + int i; + int rc; + int nSegment = pCsr->nSegment; + int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = ( + p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp + ); + + assert( pCsr->pFilter==0 ); + assert( zTerm && nTerm>0 ); + + /* Advance each segment iterator until it points to the term zTerm/nTerm. */ + rc = fts3SegReaderStart(p, pCsr, zTerm, nTerm); + if( rc!=SQLITE_OK ) return rc; + + /* Determine how many of the segments actually point to zTerm/nTerm. */ + for(i=0; i<nSegment; i++){ + Fts3SegReader *pSeg = pCsr->apSegment[i]; + if( !pSeg->aNode || fts3SegReaderTermCmp(pSeg, zTerm, nTerm) ){ + break; + } + } + pCsr->nAdvance = i; + + /* Advance each of the segments to point to the first docid. */ + for(i=0; i<pCsr->nAdvance; i++){ + rc = fts3SegReaderFirstDocid(p, pCsr->apSegment[i]); + if( rc!=SQLITE_OK ) return rc; + } + fts3SegReaderSort(pCsr->apSegment, i, i, xCmp); + + assert( iCol<0 || iCol<p->nColumn ); + pCsr->iColFilter = iCol; + + return SQLITE_OK; +} + +/* +** This function is called on a MultiSegReader that has been started using +** sqlite3Fts3MsrIncrStart(). One or more calls to MsrIncrNext() may also +** have been made. Calling this function puts the MultiSegReader in such +** a state that if the next two calls are: +** +** sqlite3Fts3SegReaderStart() +** sqlite3Fts3SegReaderStep() +** +** then the entire doclist for the term is available in +** MultiSegReader.aDoclist/nDoclist. +*/ +SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr){ + int i; /* Used to iterate through segment-readers */ + + assert( pCsr->zTerm==0 ); + assert( pCsr->nTerm==0 ); + assert( pCsr->aDoclist==0 ); + assert( pCsr->nDoclist==0 ); + + pCsr->nAdvance = 0; + pCsr->bRestart = 1; + for(i=0; i<pCsr->nSegment; i++){ + pCsr->apSegment[i]->pOffsetList = 0; + pCsr->apSegment[i]->nOffsetList = 0; + pCsr->apSegment[i]->iDocid = 0; + } + + return SQLITE_OK; +} + +static int fts3GrowSegReaderBuffer(Fts3MultiSegReader *pCsr, i64 nReq){ + if( nReq>pCsr->nBuffer ){ + char *aNew; + pCsr->nBuffer = nReq*2; + aNew = sqlite3_realloc64(pCsr->aBuffer, pCsr->nBuffer); + if( !aNew ){ + return SQLITE_NOMEM; + } + pCsr->aBuffer = aNew; + } + return SQLITE_OK; +} + + +SQLITE_PRIVATE int sqlite3Fts3SegReaderStep( + Fts3Table *p, /* Virtual table handle */ + Fts3MultiSegReader *pCsr /* Cursor object */ +){ + int rc = SQLITE_OK; + + int isIgnoreEmpty = (pCsr->pFilter->flags & FTS3_SEGMENT_IGNORE_EMPTY); + int isRequirePos = (pCsr->pFilter->flags & FTS3_SEGMENT_REQUIRE_POS); + int isColFilter = (pCsr->pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER); + int isPrefix = (pCsr->pFilter->flags & FTS3_SEGMENT_PREFIX); + int isScan = (pCsr->pFilter->flags & FTS3_SEGMENT_SCAN); + int isFirst = (pCsr->pFilter->flags & FTS3_SEGMENT_FIRST); + + Fts3SegReader **apSegment = pCsr->apSegment; + int nSegment = pCsr->nSegment; + Fts3SegFilter *pFilter = pCsr->pFilter; + int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = ( + p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp + ); + + if( pCsr->nSegment==0 ) return SQLITE_OK; + + do { + int nMerge; + int i; + + /* Advance the first pCsr->nAdvance entries in the apSegment[] array + ** forward. Then sort the list in order of current term again. + */ + for(i=0; i<pCsr->nAdvance; i++){ + Fts3SegReader *pSeg = apSegment[i]; + if( pSeg->bLookup ){ + fts3SegReaderSetEof(pSeg); + }else{ + rc = fts3SegReaderNext(p, pSeg, 0); + } + if( rc!=SQLITE_OK ) return rc; + } + fts3SegReaderSort(apSegment, nSegment, pCsr->nAdvance, fts3SegReaderCmp); + pCsr->nAdvance = 0; + + /* If all the seg-readers are at EOF, we're finished. return SQLITE_OK. */ + assert( rc==SQLITE_OK ); + if( apSegment[0]->aNode==0 ) break; + + pCsr->nTerm = apSegment[0]->nTerm; + pCsr->zTerm = apSegment[0]->zTerm; + + /* If this is a prefix-search, and if the term that apSegment[0] points + ** to does not share a suffix with pFilter->zTerm/nTerm, then all + ** required callbacks have been made. In this case exit early. + ** + ** Similarly, if this is a search for an exact match, and the first term + ** of segment apSegment[0] is not a match, exit early. + */ + if( pFilter->zTerm && !isScan ){ + if( pCsr->nTerm<pFilter->nTerm + || (!isPrefix && pCsr->nTerm>pFilter->nTerm) + || memcmp(pCsr->zTerm, pFilter->zTerm, pFilter->nTerm) + ){ + break; + } + } + + nMerge = 1; + while( nMerge<nSegment + && apSegment[nMerge]->aNode + && apSegment[nMerge]->nTerm==pCsr->nTerm + && 0==memcmp(pCsr->zTerm, apSegment[nMerge]->zTerm, pCsr->nTerm) + ){ + nMerge++; + } + + assert( isIgnoreEmpty || (isRequirePos && !isColFilter) ); + if( nMerge==1 + && !isIgnoreEmpty + && !isFirst + && (p->bDescIdx==0 || fts3SegReaderIsPending(apSegment[0])==0) + ){ + pCsr->nDoclist = apSegment[0]->nDoclist; + if( fts3SegReaderIsPending(apSegment[0]) ){ + rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, + (i64)pCsr->nDoclist); + pCsr->aDoclist = pCsr->aBuffer; + }else{ + pCsr->aDoclist = apSegment[0]->aDoclist; + } + if( rc==SQLITE_OK ) rc = SQLITE_ROW; + }else{ + int nDoclist = 0; /* Size of doclist */ + sqlite3_int64 iPrev = 0; /* Previous docid stored in doclist */ + + /* The current term of the first nMerge entries in the array + ** of Fts3SegReader objects is the same. The doclists must be merged + ** and a single term returned with the merged doclist. + */ + for(i=0; i<nMerge; i++){ + fts3SegReaderFirstDocid(p, apSegment[i]); + } + fts3SegReaderSort(apSegment, nMerge, nMerge, xCmp); + while( apSegment[0]->pOffsetList ){ + int j; /* Number of segments that share a docid */ + char *pList = 0; + int nList = 0; + int nByte; + sqlite3_int64 iDocid = apSegment[0]->iDocid; + fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList); + j = 1; + while( j<nMerge + && apSegment[j]->pOffsetList + && apSegment[j]->iDocid==iDocid + ){ + fts3SegReaderNextDocid(p, apSegment[j], 0, 0); + j++; + } + + if( isColFilter ){ + fts3ColumnFilter(pFilter->iCol, 0, &pList, &nList); + } + + if( !isIgnoreEmpty || nList>0 ){ + + /* Calculate the 'docid' delta value to write into the merged + ** doclist. */ + sqlite3_int64 iDelta; + if( p->bDescIdx && nDoclist>0 ){ + if( iPrev<=iDocid ) return FTS_CORRUPT_VTAB; + iDelta = (i64)((u64)iPrev - (u64)iDocid); + }else{ + if( nDoclist>0 && iPrev>=iDocid ) return FTS_CORRUPT_VTAB; + iDelta = (i64)((u64)iDocid - (u64)iPrev); + } + + nByte = sqlite3Fts3VarintLen(iDelta) + (isRequirePos?nList+1:0); + + rc = fts3GrowSegReaderBuffer(pCsr, + (i64)nByte+nDoclist+FTS3_NODE_PADDING); + if( rc ) return rc; + + if( isFirst ){ + char *a = &pCsr->aBuffer[nDoclist]; + int nWrite; + + nWrite = sqlite3Fts3FirstFilter(iDelta, pList, nList, a); + if( nWrite ){ + iPrev = iDocid; + nDoclist += nWrite; + } + }else{ + nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta); + iPrev = iDocid; + if( isRequirePos ){ + memcpy(&pCsr->aBuffer[nDoclist], pList, nList); + nDoclist += nList; + pCsr->aBuffer[nDoclist++] = '\0'; + } + } + } + + fts3SegReaderSort(apSegment, nMerge, j, xCmp); + } + if( nDoclist>0 ){ + rc = fts3GrowSegReaderBuffer(pCsr, (i64)nDoclist+FTS3_NODE_PADDING); + if( rc ) return rc; + memset(&pCsr->aBuffer[nDoclist], 0, FTS3_NODE_PADDING); + pCsr->aDoclist = pCsr->aBuffer; + pCsr->nDoclist = nDoclist; + rc = SQLITE_ROW; + } + } + pCsr->nAdvance = nMerge; + }while( rc==SQLITE_OK ); + + return rc; +} + + +SQLITE_PRIVATE void sqlite3Fts3SegReaderFinish( + Fts3MultiSegReader *pCsr /* Cursor object */ +){ + if( pCsr ){ + int i; + for(i=0; i<pCsr->nSegment; i++){ + sqlite3Fts3SegReaderFree(pCsr->apSegment[i]); + } + sqlite3_free(pCsr->apSegment); + sqlite3_free(pCsr->aBuffer); + + pCsr->nSegment = 0; + pCsr->apSegment = 0; + pCsr->aBuffer = 0; + } +} + +/* +** Decode the "end_block" field, selected by column iCol of the SELECT +** statement passed as the first argument. +** +** The "end_block" field may contain either an integer, or a text field +** containing the text representation of two non-negative integers separated +** by one or more space (0x20) characters. In the first case, set *piEndBlock +** to the integer value and *pnByte to zero before returning. In the second, +** set *piEndBlock to the first value and *pnByte to the second. +*/ +static void fts3ReadEndBlockField( + sqlite3_stmt *pStmt, + int iCol, + i64 *piEndBlock, + i64 *pnByte +){ + const unsigned char *zText = sqlite3_column_text(pStmt, iCol); + if( zText ){ + int i; + int iMul = 1; + u64 iVal = 0; + for(i=0; zText[i]>='0' && zText[i]<='9'; i++){ + iVal = iVal*10 + (zText[i] - '0'); + } + *piEndBlock = (i64)iVal; + while( zText[i]==' ' ) i++; + iVal = 0; + if( zText[i]=='-' ){ + i++; + iMul = -1; + } + for(/* no-op */; zText[i]>='0' && zText[i]<='9'; i++){ + iVal = iVal*10 + (zText[i] - '0'); + } + *pnByte = ((i64)iVal * (i64)iMul); + } +} + + +/* +** A segment of size nByte bytes has just been written to absolute level +** iAbsLevel. Promote any segments that should be promoted as a result. +*/ +static int fts3PromoteSegments( + Fts3Table *p, /* FTS table handle */ + sqlite3_int64 iAbsLevel, /* Absolute level just updated */ + sqlite3_int64 nByte /* Size of new segment at iAbsLevel */ +){ + int rc = SQLITE_OK; + sqlite3_stmt *pRange; + + rc = fts3SqlStmt(p, SQL_SELECT_LEVEL_RANGE2, &pRange, 0); + + if( rc==SQLITE_OK ){ + int bOk = 0; + i64 iLast = (iAbsLevel/FTS3_SEGDIR_MAXLEVEL + 1) * FTS3_SEGDIR_MAXLEVEL - 1; + i64 nLimit = (nByte*3)/2; + + /* Loop through all entries in the %_segdir table corresponding to + ** segments in this index on levels greater than iAbsLevel. If there is + ** at least one such segment, and it is possible to determine that all + ** such segments are smaller than nLimit bytes in size, they will be + ** promoted to level iAbsLevel. */ + sqlite3_bind_int64(pRange, 1, iAbsLevel+1); + sqlite3_bind_int64(pRange, 2, iLast); + while( SQLITE_ROW==sqlite3_step(pRange) ){ + i64 nSize = 0, dummy; + fts3ReadEndBlockField(pRange, 2, &dummy, &nSize); + if( nSize<=0 || nSize>nLimit ){ + /* If nSize==0, then the %_segdir.end_block field does not not + ** contain a size value. This happens if it was written by an + ** old version of FTS. In this case it is not possible to determine + ** the size of the segment, and so segment promotion does not + ** take place. */ + bOk = 0; + break; + } + bOk = 1; + } + rc = sqlite3_reset(pRange); + + if( bOk ){ + int iIdx = 0; + sqlite3_stmt *pUpdate1 = 0; + sqlite3_stmt *pUpdate2 = 0; + + if( rc==SQLITE_OK ){ + rc = fts3SqlStmt(p, SQL_UPDATE_LEVEL_IDX, &pUpdate1, 0); + } + if( rc==SQLITE_OK ){ + rc = fts3SqlStmt(p, SQL_UPDATE_LEVEL, &pUpdate2, 0); + } + + if( rc==SQLITE_OK ){ + + /* Loop through all %_segdir entries for segments in this index with + ** levels equal to or greater than iAbsLevel. As each entry is visited, + ** updated it to set (level = -1) and (idx = N), where N is 0 for the + ** oldest segment in the range, 1 for the next oldest, and so on. + ** + ** In other words, move all segments being promoted to level -1, + ** setting the "idx" fields as appropriate to keep them in the same + ** order. The contents of level -1 (which is never used, except + ** transiently here), will be moved back to level iAbsLevel below. */ + sqlite3_bind_int64(pRange, 1, iAbsLevel); + while( SQLITE_ROW==sqlite3_step(pRange) ){ + sqlite3_bind_int(pUpdate1, 1, iIdx++); + sqlite3_bind_int(pUpdate1, 2, sqlite3_column_int(pRange, 0)); + sqlite3_bind_int(pUpdate1, 3, sqlite3_column_int(pRange, 1)); + sqlite3_step(pUpdate1); + rc = sqlite3_reset(pUpdate1); + if( rc!=SQLITE_OK ){ + sqlite3_reset(pRange); + break; + } + } + } + if( rc==SQLITE_OK ){ + rc = sqlite3_reset(pRange); + } + + /* Move level -1 to level iAbsLevel */ + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pUpdate2, 1, iAbsLevel); + sqlite3_step(pUpdate2); + rc = sqlite3_reset(pUpdate2); + } + } + } + + + return rc; +} + +/* +** Merge all level iLevel segments in the database into a single +** iLevel+1 segment. Or, if iLevel<0, merge all segments into a +** single segment with a level equal to the numerically largest level +** currently present in the database. +** +** If this function is called with iLevel<0, but there is only one +** segment in the database, SQLITE_DONE is returned immediately. +** Otherwise, if successful, SQLITE_OK is returned. If an error occurs, +** an SQLite error code is returned. +*/ +static int fts3SegmentMerge( + Fts3Table *p, + int iLangid, /* Language id to merge */ + int iIndex, /* Index in p->aIndex[] to merge */ + int iLevel /* Level to merge */ +){ + int rc; /* Return code */ + int iIdx = 0; /* Index of new segment */ + sqlite3_int64 iNewLevel = 0; /* Level/index to create new segment at */ + SegmentWriter *pWriter = 0; /* Used to write the new, merged, segment */ + Fts3SegFilter filter; /* Segment term filter condition */ + Fts3MultiSegReader csr; /* Cursor to iterate through level(s) */ + int bIgnoreEmpty = 0; /* True to ignore empty segments */ + i64 iMaxLevel = 0; /* Max level number for this index/langid */ + + assert( iLevel==FTS3_SEGCURSOR_ALL + || iLevel==FTS3_SEGCURSOR_PENDING + || iLevel>=0 + ); + assert( iLevel<FTS3_SEGDIR_MAXLEVEL ); + assert( iIndex>=0 && iIndex<p->nIndex ); + + rc = sqlite3Fts3SegReaderCursor(p, iLangid, iIndex, iLevel, 0, 0, 1, 0, &csr); + if( rc!=SQLITE_OK || csr.nSegment==0 ) goto finished; + + if( iLevel!=FTS3_SEGCURSOR_PENDING ){ + rc = fts3SegmentMaxLevel(p, iLangid, iIndex, &iMaxLevel); + if( rc!=SQLITE_OK ) goto finished; + } + + if( iLevel==FTS3_SEGCURSOR_ALL ){ + /* This call is to merge all segments in the database to a single + ** segment. The level of the new segment is equal to the numerically + ** greatest segment level currently present in the database for this + ** index. The idx of the new segment is always 0. */ + if( csr.nSegment==1 && 0==fts3SegReaderIsPending(csr.apSegment[0]) ){ + rc = SQLITE_DONE; + goto finished; + } + iNewLevel = iMaxLevel; + bIgnoreEmpty = 1; + + }else{ + /* This call is to merge all segments at level iLevel. find the next + ** available segment index at level iLevel+1. The call to + ** fts3AllocateSegdirIdx() will merge the segments at level iLevel+1 to + ** a single iLevel+2 segment if necessary. */ + assert( FTS3_SEGCURSOR_PENDING==-1 ); + iNewLevel = getAbsoluteLevel(p, iLangid, iIndex, iLevel+1); + rc = fts3AllocateSegdirIdx(p, iLangid, iIndex, iLevel+1, &iIdx); + bIgnoreEmpty = (iLevel!=FTS3_SEGCURSOR_PENDING) && (iNewLevel>iMaxLevel); + } + if( rc!=SQLITE_OK ) goto finished; + + assert( csr.nSegment>0 ); + assert_fts3_nc( iNewLevel>=getAbsoluteLevel(p, iLangid, iIndex, 0) ); + assert_fts3_nc( + iNewLevel<getAbsoluteLevel(p, iLangid, iIndex,FTS3_SEGDIR_MAXLEVEL) + ); + + memset(&filter, 0, sizeof(Fts3SegFilter)); + filter.flags = FTS3_SEGMENT_REQUIRE_POS; + filter.flags |= (bIgnoreEmpty ? FTS3_SEGMENT_IGNORE_EMPTY : 0); + + rc = sqlite3Fts3SegReaderStart(p, &csr, &filter); + while( SQLITE_OK==rc ){ + rc = sqlite3Fts3SegReaderStep(p, &csr); + if( rc!=SQLITE_ROW ) break; + rc = fts3SegWriterAdd(p, &pWriter, 1, + csr.zTerm, csr.nTerm, csr.aDoclist, csr.nDoclist); + } + if( rc!=SQLITE_OK ) goto finished; + assert_fts3_nc( pWriter || bIgnoreEmpty ); + + if( iLevel!=FTS3_SEGCURSOR_PENDING ){ + rc = fts3DeleteSegdir( + p, iLangid, iIndex, iLevel, csr.apSegment, csr.nSegment + ); + if( rc!=SQLITE_OK ) goto finished; + } + if( pWriter ){ + rc = fts3SegWriterFlush(p, pWriter, iNewLevel, iIdx); + if( rc==SQLITE_OK ){ + if( iLevel==FTS3_SEGCURSOR_PENDING || iNewLevel<iMaxLevel ){ + rc = fts3PromoteSegments(p, iNewLevel, pWriter->nLeafData); + } + } + } + + finished: + fts3SegWriterFree(pWriter); + sqlite3Fts3SegReaderFinish(&csr); + return rc; +} + + +/* +** Flush the contents of pendingTerms to level 0 segments. +*/ +SQLITE_PRIVATE int sqlite3Fts3PendingTermsFlush(Fts3Table *p){ + int rc = SQLITE_OK; + int i; + + for(i=0; rc==SQLITE_OK && i<p->nIndex; i++){ + rc = fts3SegmentMerge(p, p->iPrevLangid, i, FTS3_SEGCURSOR_PENDING); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + } + sqlite3Fts3PendingTermsClear(p); + + /* Determine the auto-incr-merge setting if unknown. If enabled, + ** estimate the number of leaf blocks of content to be written + */ + if( rc==SQLITE_OK && p->bHasStat + && p->nAutoincrmerge==0xff && p->nLeafAdd>0 + ){ + sqlite3_stmt *pStmt = 0; + rc = fts3SqlStmt(p, SQL_SELECT_STAT, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pStmt, 1, FTS_STAT_AUTOINCRMERGE); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ + p->nAutoincrmerge = sqlite3_column_int(pStmt, 0); + if( p->nAutoincrmerge==1 ) p->nAutoincrmerge = 8; + }else if( rc==SQLITE_DONE ){ + p->nAutoincrmerge = 0; + } + rc = sqlite3_reset(pStmt); + } + } + return rc; +} + +/* +** Encode N integers as varints into a blob. +*/ +static void fts3EncodeIntArray( + int N, /* The number of integers to encode */ + u32 *a, /* The integer values */ + char *zBuf, /* Write the BLOB here */ + int *pNBuf /* Write number of bytes if zBuf[] used here */ +){ + int i, j; + for(i=j=0; i<N; i++){ + j += sqlite3Fts3PutVarint(&zBuf[j], (sqlite3_int64)a[i]); + } + *pNBuf = j; +} + +/* +** Decode a blob of varints into N integers +*/ +static void fts3DecodeIntArray( + int N, /* The number of integers to decode */ + u32 *a, /* Write the integer values */ + const char *zBuf, /* The BLOB containing the varints */ + int nBuf /* size of the BLOB */ +){ + int i = 0; + if( nBuf && (zBuf[nBuf-1]&0x80)==0 ){ + int j; + for(i=j=0; i<N && j<nBuf; i++){ + sqlite3_int64 x; + j += sqlite3Fts3GetVarint(&zBuf[j], &x); + a[i] = (u32)(x & 0xffffffff); + } + } + while( i<N ) a[i++] = 0; +} + +/* +** Insert the sizes (in tokens) for each column of the document +** with docid equal to p->iPrevDocid. The sizes are encoded as +** a blob of varints. +*/ +static void fts3InsertDocsize( + int *pRC, /* Result code */ + Fts3Table *p, /* Table into which to insert */ + u32 *aSz /* Sizes of each column, in tokens */ +){ + char *pBlob; /* The BLOB encoding of the document size */ + int nBlob; /* Number of bytes in the BLOB */ + sqlite3_stmt *pStmt; /* Statement used to insert the encoding */ + int rc; /* Result code from subfunctions */ + + if( *pRC ) return; + pBlob = sqlite3_malloc64( 10*(sqlite3_int64)p->nColumn ); + if( pBlob==0 ){ + *pRC = SQLITE_NOMEM; + return; + } + fts3EncodeIntArray(p->nColumn, aSz, pBlob, &nBlob); + rc = fts3SqlStmt(p, SQL_REPLACE_DOCSIZE, &pStmt, 0); + if( rc ){ + sqlite3_free(pBlob); + *pRC = rc; + return; + } + sqlite3_bind_int64(pStmt, 1, p->iPrevDocid); + sqlite3_bind_blob(pStmt, 2, pBlob, nBlob, sqlite3_free); + sqlite3_step(pStmt); + *pRC = sqlite3_reset(pStmt); +} + +/* +** Record 0 of the %_stat table contains a blob consisting of N varints, +** where N is the number of user defined columns in the fts3 table plus +** two. If nCol is the number of user defined columns, then values of the +** varints are set as follows: +** +** Varint 0: Total number of rows in the table. +** +** Varint 1..nCol: For each column, the total number of tokens stored in +** the column for all rows of the table. +** +** Varint 1+nCol: The total size, in bytes, of all text values in all +** columns of all rows of the table. +** +*/ +static void fts3UpdateDocTotals( + int *pRC, /* The result code */ + Fts3Table *p, /* Table being updated */ + u32 *aSzIns, /* Size increases */ + u32 *aSzDel, /* Size decreases */ + int nChng /* Change in the number of documents */ +){ + char *pBlob; /* Storage for BLOB written into %_stat */ + int nBlob; /* Size of BLOB written into %_stat */ + u32 *a; /* Array of integers that becomes the BLOB */ + sqlite3_stmt *pStmt; /* Statement for reading and writing */ + int i; /* Loop counter */ + int rc; /* Result code from subfunctions */ + + const int nStat = p->nColumn+2; + + if( *pRC ) return; + a = sqlite3_malloc64( (sizeof(u32)+10)*(sqlite3_int64)nStat ); + if( a==0 ){ + *pRC = SQLITE_NOMEM; + return; + } + pBlob = (char*)&a[nStat]; + rc = fts3SqlStmt(p, SQL_SELECT_STAT, &pStmt, 0); + if( rc ){ + sqlite3_free(a); + *pRC = rc; + return; + } + sqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + fts3DecodeIntArray(nStat, a, + sqlite3_column_blob(pStmt, 0), + sqlite3_column_bytes(pStmt, 0)); + }else{ + memset(a, 0, sizeof(u32)*(nStat) ); + } + rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK ){ + sqlite3_free(a); + *pRC = rc; + return; + } + if( nChng<0 && a[0]<(u32)(-nChng) ){ + a[0] = 0; + }else{ + a[0] += nChng; + } + for(i=0; i<p->nColumn+1; i++){ + u32 x = a[i+1]; + if( x+aSzIns[i] < aSzDel[i] ){ + x = 0; + }else{ + x = x + aSzIns[i] - aSzDel[i]; + } + a[i+1] = x; + } + fts3EncodeIntArray(nStat, a, pBlob, &nBlob); + rc = fts3SqlStmt(p, SQL_REPLACE_STAT, &pStmt, 0); + if( rc ){ + sqlite3_free(a); + *pRC = rc; + return; + } + sqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL); + sqlite3_bind_blob(pStmt, 2, pBlob, nBlob, SQLITE_STATIC); + sqlite3_step(pStmt); + *pRC = sqlite3_reset(pStmt); + sqlite3_bind_null(pStmt, 2); + sqlite3_free(a); +} + +/* +** Merge the entire database so that there is one segment for each +** iIndex/iLangid combination. +*/ +static int fts3DoOptimize(Fts3Table *p, int bReturnDone){ + int bSeenDone = 0; + int rc; + sqlite3_stmt *pAllLangid = 0; + + rc = sqlite3Fts3PendingTermsFlush(p); + if( rc==SQLITE_OK ){ + rc = fts3SqlStmt(p, SQL_SELECT_ALL_LANGID, &pAllLangid, 0); + } + if( rc==SQLITE_OK ){ + int rc2; + sqlite3_bind_int(pAllLangid, 1, p->iPrevLangid); + sqlite3_bind_int(pAllLangid, 2, p->nIndex); + while( sqlite3_step(pAllLangid)==SQLITE_ROW ){ + int i; + int iLangid = sqlite3_column_int(pAllLangid, 0); + for(i=0; rc==SQLITE_OK && i<p->nIndex; i++){ + rc = fts3SegmentMerge(p, iLangid, i, FTS3_SEGCURSOR_ALL); + if( rc==SQLITE_DONE ){ + bSeenDone = 1; + rc = SQLITE_OK; + } + } + } + rc2 = sqlite3_reset(pAllLangid); + if( rc==SQLITE_OK ) rc = rc2; + } + + sqlite3Fts3SegmentsClose(p); + + return (rc==SQLITE_OK && bReturnDone && bSeenDone) ? SQLITE_DONE : rc; +} + +/* +** This function is called when the user executes the following statement: +** +** INSERT INTO <tbl>(<tbl>) VALUES('rebuild'); +** +** The entire FTS index is discarded and rebuilt. If the table is one +** created using the content=xxx option, then the new index is based on +** the current contents of the xxx table. Otherwise, it is rebuilt based +** on the contents of the %_content table. +*/ +static int fts3DoRebuild(Fts3Table *p){ + int rc; /* Return Code */ + + rc = fts3DeleteAll(p, 0); + if( rc==SQLITE_OK ){ + u32 *aSz = 0; + u32 *aSzIns = 0; + u32 *aSzDel = 0; + sqlite3_stmt *pStmt = 0; + int nEntry = 0; + + /* Compose and prepare an SQL statement to loop through the content table */ + char *zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist); + if( !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + } + + if( rc==SQLITE_OK ){ + sqlite3_int64 nByte = sizeof(u32) * ((sqlite3_int64)p->nColumn+1)*3; + aSz = (u32 *)sqlite3_malloc64(nByte); + if( aSz==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(aSz, 0, nByte); + aSzIns = &aSz[p->nColumn+1]; + aSzDel = &aSzIns[p->nColumn+1]; + } + } + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + int iCol; + int iLangid = langidFromSelect(p, pStmt); + rc = fts3PendingTermsDocid(p, 0, iLangid, sqlite3_column_int64(pStmt, 0)); + memset(aSz, 0, sizeof(aSz[0]) * (p->nColumn+1)); + for(iCol=0; rc==SQLITE_OK && iCol<p->nColumn; iCol++){ + if( p->abNotindexed[iCol]==0 ){ + const char *z = (const char *) sqlite3_column_text(pStmt, iCol+1); + rc = fts3PendingTermsAdd(p, iLangid, z, iCol, &aSz[iCol]); + aSz[p->nColumn] += sqlite3_column_bytes(pStmt, iCol+1); + } + } + if( p->bHasDocsize ){ + fts3InsertDocsize(&rc, p, aSz); + } + if( rc!=SQLITE_OK ){ + sqlite3_finalize(pStmt); + pStmt = 0; + }else{ + nEntry++; + for(iCol=0; iCol<=p->nColumn; iCol++){ + aSzIns[iCol] += aSz[iCol]; + } + } + } + if( p->bFts4 ){ + fts3UpdateDocTotals(&rc, p, aSzIns, aSzDel, nEntry); + } + sqlite3_free(aSz); + + if( pStmt ){ + int rc2 = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ){ + rc = rc2; + } + } + } + + return rc; +} + + +/* +** This function opens a cursor used to read the input data for an +** incremental merge operation. Specifically, it opens a cursor to scan +** the oldest nSeg segments (idx=0 through idx=(nSeg-1)) in absolute +** level iAbsLevel. +*/ +static int fts3IncrmergeCsr( + Fts3Table *p, /* FTS3 table handle */ + sqlite3_int64 iAbsLevel, /* Absolute level to open */ + int nSeg, /* Number of segments to merge */ + Fts3MultiSegReader *pCsr /* Cursor object to populate */ +){ + int rc; /* Return Code */ + sqlite3_stmt *pStmt = 0; /* Statement used to read %_segdir entry */ + sqlite3_int64 nByte; /* Bytes allocated at pCsr->apSegment[] */ + + /* Allocate space for the Fts3MultiSegReader.aCsr[] array */ + memset(pCsr, 0, sizeof(*pCsr)); + nByte = sizeof(Fts3SegReader *) * nSeg; + pCsr->apSegment = (Fts3SegReader **)sqlite3_malloc64(nByte); + + if( pCsr->apSegment==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pCsr->apSegment, 0, nByte); + rc = fts3SqlStmt(p, SQL_SELECT_LEVEL, &pStmt, 0); + } + if( rc==SQLITE_OK ){ + int i; + int rc2; + sqlite3_bind_int64(pStmt, 1, iAbsLevel); + assert( pCsr->nSegment==0 ); + for(i=0; rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW && i<nSeg; i++){ + rc = sqlite3Fts3SegReaderNew(i, 0, + sqlite3_column_int64(pStmt, 1), /* segdir.start_block */ + sqlite3_column_int64(pStmt, 2), /* segdir.leaves_end_block */ + sqlite3_column_int64(pStmt, 3), /* segdir.end_block */ + sqlite3_column_blob(pStmt, 4), /* segdir.root */ + sqlite3_column_bytes(pStmt, 4), /* segdir.root */ + &pCsr->apSegment[i] + ); + pCsr->nSegment++; + } + rc2 = sqlite3_reset(pStmt); + if( rc==SQLITE_OK ) rc = rc2; + } + + return rc; +} + +typedef struct IncrmergeWriter IncrmergeWriter; +typedef struct NodeWriter NodeWriter; +typedef struct Blob Blob; +typedef struct NodeReader NodeReader; + +/* +** An instance of the following structure is used as a dynamic buffer +** to build up nodes or other blobs of data in. +** +** The function blobGrowBuffer() is used to extend the allocation. +*/ +struct Blob { + char *a; /* Pointer to allocation */ + int n; /* Number of valid bytes of data in a[] */ + int nAlloc; /* Allocated size of a[] (nAlloc>=n) */ +}; + +/* +** This structure is used to build up buffers containing segment b-tree +** nodes (blocks). +*/ +struct NodeWriter { + sqlite3_int64 iBlock; /* Current block id */ + Blob key; /* Last key written to the current block */ + Blob block; /* Current block image */ +}; + +/* +** An object of this type contains the state required to create or append +** to an appendable b-tree segment. +*/ +struct IncrmergeWriter { + int nLeafEst; /* Space allocated for leaf blocks */ + int nWork; /* Number of leaf pages flushed */ + sqlite3_int64 iAbsLevel; /* Absolute level of input segments */ + int iIdx; /* Index of *output* segment in iAbsLevel+1 */ + sqlite3_int64 iStart; /* Block number of first allocated block */ + sqlite3_int64 iEnd; /* Block number of last allocated block */ + sqlite3_int64 nLeafData; /* Bytes of leaf page data so far */ + u8 bNoLeafData; /* If true, store 0 for segment size */ + NodeWriter aNodeWriter[FTS_MAX_APPENDABLE_HEIGHT]; +}; + +/* +** An object of the following type is used to read data from a single +** FTS segment node. See the following functions: +** +** nodeReaderInit() +** nodeReaderNext() +** nodeReaderRelease() +*/ +struct NodeReader { + const char *aNode; + int nNode; + int iOff; /* Current offset within aNode[] */ + + /* Output variables. Containing the current node entry. */ + sqlite3_int64 iChild; /* Pointer to child node */ + Blob term; /* Current term */ + const char *aDoclist; /* Pointer to doclist */ + int nDoclist; /* Size of doclist in bytes */ +}; + +/* +** If *pRc is not SQLITE_OK when this function is called, it is a no-op. +** Otherwise, if the allocation at pBlob->a is not already at least nMin +** bytes in size, extend (realloc) it to be so. +** +** If an OOM error occurs, set *pRc to SQLITE_NOMEM and leave pBlob->a +** unmodified. Otherwise, if the allocation succeeds, update pBlob->nAlloc +** to reflect the new size of the pBlob->a[] buffer. +*/ +static void blobGrowBuffer(Blob *pBlob, int nMin, int *pRc){ + if( *pRc==SQLITE_OK && nMin>pBlob->nAlloc ){ + int nAlloc = nMin; + char *a = (char *)sqlite3_realloc64(pBlob->a, nAlloc); + if( a ){ + pBlob->nAlloc = nAlloc; + pBlob->a = a; + }else{ + *pRc = SQLITE_NOMEM; + } + } +} + +/* +** Attempt to advance the node-reader object passed as the first argument to +** the next entry on the node. +** +** Return an error code if an error occurs (SQLITE_NOMEM is possible). +** Otherwise return SQLITE_OK. If there is no next entry on the node +** (e.g. because the current entry is the last) set NodeReader->aNode to +** NULL to indicate EOF. Otherwise, populate the NodeReader structure output +** variables for the new entry. +*/ +static int nodeReaderNext(NodeReader *p){ + int bFirst = (p->term.n==0); /* True for first term on the node */ + int nPrefix = 0; /* Bytes to copy from previous term */ + int nSuffix = 0; /* Bytes to append to the prefix */ + int rc = SQLITE_OK; /* Return code */ + + assert( p->aNode ); + if( p->iChild && bFirst==0 ) p->iChild++; + if( p->iOff>=p->nNode ){ + /* EOF */ + p->aNode = 0; + }else{ + if( bFirst==0 ){ + p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &nPrefix); + } + p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &nSuffix); + + if( nPrefix>p->term.n || nSuffix>p->nNode-p->iOff || nSuffix==0 ){ + return FTS_CORRUPT_VTAB; + } + blobGrowBuffer(&p->term, nPrefix+nSuffix, &rc); + if( rc==SQLITE_OK && ALWAYS(p->term.a!=0) ){ + memcpy(&p->term.a[nPrefix], &p->aNode[p->iOff], nSuffix); + p->term.n = nPrefix+nSuffix; + p->iOff += nSuffix; + if( p->iChild==0 ){ + p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &p->nDoclist); + if( (p->nNode-p->iOff)<p->nDoclist ){ + return FTS_CORRUPT_VTAB; + } + p->aDoclist = &p->aNode[p->iOff]; + p->iOff += p->nDoclist; + } + } + } + + assert_fts3_nc( p->iOff<=p->nNode ); + return rc; +} + +/* +** Release all dynamic resources held by node-reader object *p. +*/ +static void nodeReaderRelease(NodeReader *p){ + sqlite3_free(p->term.a); +} + +/* +** Initialize a node-reader object to read the node in buffer aNode/nNode. +** +** If successful, SQLITE_OK is returned and the NodeReader object set to +** point to the first entry on the node (if any). Otherwise, an SQLite +** error code is returned. +*/ +static int nodeReaderInit(NodeReader *p, const char *aNode, int nNode){ + memset(p, 0, sizeof(NodeReader)); + p->aNode = aNode; + p->nNode = nNode; + + /* Figure out if this is a leaf or an internal node. */ + if( aNode && aNode[0] ){ + /* An internal node. */ + p->iOff = 1 + sqlite3Fts3GetVarint(&p->aNode[1], &p->iChild); + }else{ + p->iOff = 1; + } + + return aNode ? nodeReaderNext(p) : SQLITE_OK; +} + +/* +** This function is called while writing an FTS segment each time a leaf o +** node is finished and written to disk. The key (zTerm/nTerm) is guaranteed +** to be greater than the largest key on the node just written, but smaller +** than or equal to the first key that will be written to the next leaf +** node. +** +** The block id of the leaf node just written to disk may be found in +** (pWriter->aNodeWriter[0].iBlock) when this function is called. +*/ +static int fts3IncrmergePush( + Fts3Table *p, /* Fts3 table handle */ + IncrmergeWriter *pWriter, /* Writer object */ + const char *zTerm, /* Term to write to internal node */ + int nTerm /* Bytes at zTerm */ +){ + sqlite3_int64 iPtr = pWriter->aNodeWriter[0].iBlock; + int iLayer; + + assert( nTerm>0 ); + for(iLayer=1; ALWAYS(iLayer<FTS_MAX_APPENDABLE_HEIGHT); iLayer++){ + sqlite3_int64 iNextPtr = 0; + NodeWriter *pNode = &pWriter->aNodeWriter[iLayer]; + int rc = SQLITE_OK; + int nPrefix; + int nSuffix; + int nSpace; + + /* Figure out how much space the key will consume if it is written to + ** the current node of layer iLayer. Due to the prefix compression, + ** the space required changes depending on which node the key is to + ** be added to. */ + nPrefix = fts3PrefixCompress(pNode->key.a, pNode->key.n, zTerm, nTerm); + nSuffix = nTerm - nPrefix; + if(nSuffix<=0 ) return FTS_CORRUPT_VTAB; + nSpace = sqlite3Fts3VarintLen(nPrefix); + nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; + + if( pNode->key.n==0 || (pNode->block.n + nSpace)<=p->nNodeSize ){ + /* If the current node of layer iLayer contains zero keys, or if adding + ** the key to it will not cause it to grow to larger than nNodeSize + ** bytes in size, write the key here. */ + + Blob *pBlk = &pNode->block; + if( pBlk->n==0 ){ + blobGrowBuffer(pBlk, p->nNodeSize, &rc); + if( rc==SQLITE_OK ){ + pBlk->a[0] = (char)iLayer; + pBlk->n = 1 + sqlite3Fts3PutVarint(&pBlk->a[1], iPtr); + } + } + blobGrowBuffer(pBlk, pBlk->n + nSpace, &rc); + blobGrowBuffer(&pNode->key, nTerm, &rc); + + if( rc==SQLITE_OK ){ + if( pNode->key.n ){ + pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nPrefix); + } + pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nSuffix); + assert( nPrefix+nSuffix<=nTerm ); + assert( nPrefix>=0 ); + memcpy(&pBlk->a[pBlk->n], &zTerm[nPrefix], nSuffix); + pBlk->n += nSuffix; + + memcpy(pNode->key.a, zTerm, nTerm); + pNode->key.n = nTerm; + } + }else{ + /* Otherwise, flush the current node of layer iLayer to disk. + ** Then allocate a new, empty sibling node. The key will be written + ** into the parent of this node. */ + rc = fts3WriteSegment(p, pNode->iBlock, pNode->block.a, pNode->block.n); + + assert( pNode->block.nAlloc>=p->nNodeSize ); + pNode->block.a[0] = (char)iLayer; + pNode->block.n = 1 + sqlite3Fts3PutVarint(&pNode->block.a[1], iPtr+1); + + iNextPtr = pNode->iBlock; + pNode->iBlock++; + pNode->key.n = 0; + } + + if( rc!=SQLITE_OK || iNextPtr==0 ) return rc; + iPtr = iNextPtr; + } + + assert( 0 ); + return 0; +} + +/* +** Append a term and (optionally) doclist to the FTS segment node currently +** stored in blob *pNode. The node need not contain any terms, but the +** header must be written before this function is called. +** +** A node header is a single 0x00 byte for a leaf node, or a height varint +** followed by the left-hand-child varint for an internal node. +** +** The term to be appended is passed via arguments zTerm/nTerm. For a +** leaf node, the doclist is passed as aDoclist/nDoclist. For an internal +** node, both aDoclist and nDoclist must be passed 0. +** +** If the size of the value in blob pPrev is zero, then this is the first +** term written to the node. Otherwise, pPrev contains a copy of the +** previous term. Before this function returns, it is updated to contain a +** copy of zTerm/nTerm. +** +** It is assumed that the buffer associated with pNode is already large +** enough to accommodate the new entry. The buffer associated with pPrev +** is extended by this function if requrired. +** +** If an error (i.e. OOM condition) occurs, an SQLite error code is +** returned. Otherwise, SQLITE_OK. +*/ +static int fts3AppendToNode( + Blob *pNode, /* Current node image to append to */ + Blob *pPrev, /* Buffer containing previous term written */ + const char *zTerm, /* New term to write */ + int nTerm, /* Size of zTerm in bytes */ + const char *aDoclist, /* Doclist (or NULL) to write */ + int nDoclist /* Size of aDoclist in bytes */ +){ + int rc = SQLITE_OK; /* Return code */ + int bFirst = (pPrev->n==0); /* True if this is the first term written */ + int nPrefix; /* Size of term prefix in bytes */ + int nSuffix; /* Size of term suffix in bytes */ + + /* Node must have already been started. There must be a doclist for a + ** leaf node, and there must not be a doclist for an internal node. */ + assert( pNode->n>0 ); + assert_fts3_nc( (pNode->a[0]=='\0')==(aDoclist!=0) ); + + blobGrowBuffer(pPrev, nTerm, &rc); + if( rc!=SQLITE_OK ) return rc; + + nPrefix = fts3PrefixCompress(pPrev->a, pPrev->n, zTerm, nTerm); + nSuffix = nTerm - nPrefix; + if( nSuffix<=0 ) return FTS_CORRUPT_VTAB; + memcpy(pPrev->a, zTerm, nTerm); + pPrev->n = nTerm; + + if( bFirst==0 ){ + pNode->n += sqlite3Fts3PutVarint(&pNode->a[pNode->n], nPrefix); + } + pNode->n += sqlite3Fts3PutVarint(&pNode->a[pNode->n], nSuffix); + memcpy(&pNode->a[pNode->n], &zTerm[nPrefix], nSuffix); + pNode->n += nSuffix; + + if( aDoclist ){ + pNode->n += sqlite3Fts3PutVarint(&pNode->a[pNode->n], nDoclist); + memcpy(&pNode->a[pNode->n], aDoclist, nDoclist); + pNode->n += nDoclist; + } + + assert( pNode->n<=pNode->nAlloc ); + + return SQLITE_OK; +} + +/* +** Append the current term and doclist pointed to by cursor pCsr to the +** appendable b-tree segment opened for writing by pWriter. +** +** Return SQLITE_OK if successful, or an SQLite error code otherwise. +*/ +static int fts3IncrmergeAppend( + Fts3Table *p, /* Fts3 table handle */ + IncrmergeWriter *pWriter, /* Writer object */ + Fts3MultiSegReader *pCsr /* Cursor containing term and doclist */ +){ + const char *zTerm = pCsr->zTerm; + int nTerm = pCsr->nTerm; + const char *aDoclist = pCsr->aDoclist; + int nDoclist = pCsr->nDoclist; + int rc = SQLITE_OK; /* Return code */ + int nSpace; /* Total space in bytes required on leaf */ + int nPrefix; /* Size of prefix shared with previous term */ + int nSuffix; /* Size of suffix (nTerm - nPrefix) */ + NodeWriter *pLeaf; /* Object used to write leaf nodes */ + + pLeaf = &pWriter->aNodeWriter[0]; + nPrefix = fts3PrefixCompress(pLeaf->key.a, pLeaf->key.n, zTerm, nTerm); + nSuffix = nTerm - nPrefix; + if(nSuffix<=0 ) return FTS_CORRUPT_VTAB; + + nSpace = sqlite3Fts3VarintLen(nPrefix); + nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; + nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist; + + /* If the current block is not empty, and if adding this term/doclist + ** to the current block would make it larger than Fts3Table.nNodeSize + ** bytes, write this block out to the database. */ + if( pLeaf->block.n>0 && (pLeaf->block.n + nSpace)>p->nNodeSize ){ + rc = fts3WriteSegment(p, pLeaf->iBlock, pLeaf->block.a, pLeaf->block.n); + pWriter->nWork++; + + /* Add the current term to the parent node. The term added to the + ** parent must: + ** + ** a) be greater than the largest term on the leaf node just written + ** to the database (still available in pLeaf->key), and + ** + ** b) be less than or equal to the term about to be added to the new + ** leaf node (zTerm/nTerm). + ** + ** In other words, it must be the prefix of zTerm 1 byte longer than + ** the common prefix (if any) of zTerm and pWriter->zTerm. + */ + if( rc==SQLITE_OK ){ + rc = fts3IncrmergePush(p, pWriter, zTerm, nPrefix+1); + } + + /* Advance to the next output block */ + pLeaf->iBlock++; + pLeaf->key.n = 0; + pLeaf->block.n = 0; + + nSuffix = nTerm; + nSpace = 1; + nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; + nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist; + } + + pWriter->nLeafData += nSpace; + blobGrowBuffer(&pLeaf->block, pLeaf->block.n + nSpace, &rc); + if( rc==SQLITE_OK ){ + if( pLeaf->block.n==0 ){ + pLeaf->block.n = 1; + pLeaf->block.a[0] = '\0'; + } + rc = fts3AppendToNode( + &pLeaf->block, &pLeaf->key, zTerm, nTerm, aDoclist, nDoclist + ); + } + + return rc; +} + +/* +** This function is called to release all dynamic resources held by the +** merge-writer object pWriter, and if no error has occurred, to flush +** all outstanding node buffers held by pWriter to disk. +** +** If *pRc is not SQLITE_OK when this function is called, then no attempt +** is made to write any data to disk. Instead, this function serves only +** to release outstanding resources. +** +** Otherwise, if *pRc is initially SQLITE_OK and an error occurs while +** flushing buffers to disk, *pRc is set to an SQLite error code before +** returning. +*/ +static void fts3IncrmergeRelease( + Fts3Table *p, /* FTS3 table handle */ + IncrmergeWriter *pWriter, /* Merge-writer object */ + int *pRc /* IN/OUT: Error code */ +){ + int i; /* Used to iterate through non-root layers */ + int iRoot; /* Index of root in pWriter->aNodeWriter */ + NodeWriter *pRoot; /* NodeWriter for root node */ + int rc = *pRc; /* Error code */ + + /* Set iRoot to the index in pWriter->aNodeWriter[] of the output segment + ** root node. If the segment fits entirely on a single leaf node, iRoot + ** will be set to 0. If the root node is the parent of the leaves, iRoot + ** will be 1. And so on. */ + for(iRoot=FTS_MAX_APPENDABLE_HEIGHT-1; iRoot>=0; iRoot--){ + NodeWriter *pNode = &pWriter->aNodeWriter[iRoot]; + if( pNode->block.n>0 ) break; + assert( *pRc || pNode->block.nAlloc==0 ); + assert( *pRc || pNode->key.nAlloc==0 ); + sqlite3_free(pNode->block.a); + sqlite3_free(pNode->key.a); + } + + /* Empty output segment. This is a no-op. */ + if( iRoot<0 ) return; + + /* The entire output segment fits on a single node. Normally, this means + ** the node would be stored as a blob in the "root" column of the %_segdir + ** table. However, this is not permitted in this case. The problem is that + ** space has already been reserved in the %_segments table, and so the + ** start_block and end_block fields of the %_segdir table must be populated. + ** And, by design or by accident, released versions of FTS cannot handle + ** segments that fit entirely on the root node with start_block!=0. + ** + ** Instead, create a synthetic root node that contains nothing but a + ** pointer to the single content node. So that the segment consists of a + ** single leaf and a single interior (root) node. + ** + ** Todo: Better might be to defer allocating space in the %_segments + ** table until we are sure it is needed. + */ + if( iRoot==0 ){ + Blob *pBlock = &pWriter->aNodeWriter[1].block; + blobGrowBuffer(pBlock, 1 + FTS3_VARINT_MAX, &rc); + if( rc==SQLITE_OK ){ + pBlock->a[0] = 0x01; + pBlock->n = 1 + sqlite3Fts3PutVarint( + &pBlock->a[1], pWriter->aNodeWriter[0].iBlock + ); + } + iRoot = 1; + } + pRoot = &pWriter->aNodeWriter[iRoot]; + + /* Flush all currently outstanding nodes to disk. */ + for(i=0; i<iRoot; i++){ + NodeWriter *pNode = &pWriter->aNodeWriter[i]; + if( pNode->block.n>0 && rc==SQLITE_OK ){ + rc = fts3WriteSegment(p, pNode->iBlock, pNode->block.a, pNode->block.n); + } + sqlite3_free(pNode->block.a); + sqlite3_free(pNode->key.a); + } + + /* Write the %_segdir record. */ + if( rc==SQLITE_OK ){ + rc = fts3WriteSegdir(p, + pWriter->iAbsLevel+1, /* level */ + pWriter->iIdx, /* idx */ + pWriter->iStart, /* start_block */ + pWriter->aNodeWriter[0].iBlock, /* leaves_end_block */ + pWriter->iEnd, /* end_block */ + (pWriter->bNoLeafData==0 ? pWriter->nLeafData : 0), /* end_block */ + pRoot->block.a, pRoot->block.n /* root */ + ); + } + sqlite3_free(pRoot->block.a); + sqlite3_free(pRoot->key.a); + + *pRc = rc; +} + +/* +** Compare the term in buffer zLhs (size in bytes nLhs) with that in +** zRhs (size in bytes nRhs) using memcmp. If one term is a prefix of +** the other, it is considered to be smaller than the other. +** +** Return -ve if zLhs is smaller than zRhs, 0 if it is equal, or +ve +** if it is greater. +*/ +static int fts3TermCmp( + const char *zLhs, int nLhs, /* LHS of comparison */ + const char *zRhs, int nRhs /* RHS of comparison */ +){ + int nCmp = MIN(nLhs, nRhs); + int res; + + if( nCmp && ALWAYS(zLhs) && ALWAYS(zRhs) ){ + res = memcmp(zLhs, zRhs, nCmp); + }else{ + res = 0; + } + if( res==0 ) res = nLhs - nRhs; + + return res; +} + + +/* +** Query to see if the entry in the %_segments table with blockid iEnd is +** NULL. If no error occurs and the entry is NULL, set *pbRes 1 before +** returning. Otherwise, set *pbRes to 0. +** +** Or, if an error occurs while querying the database, return an SQLite +** error code. The final value of *pbRes is undefined in this case. +** +** This is used to test if a segment is an "appendable" segment. If it +** is, then a NULL entry has been inserted into the %_segments table +** with blockid %_segdir.end_block. +*/ +static int fts3IsAppendable(Fts3Table *p, sqlite3_int64 iEnd, int *pbRes){ + int bRes = 0; /* Result to set *pbRes to */ + sqlite3_stmt *pCheck = 0; /* Statement to query database with */ + int rc; /* Return code */ + + rc = fts3SqlStmt(p, SQL_SEGMENT_IS_APPENDABLE, &pCheck, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pCheck, 1, iEnd); + if( SQLITE_ROW==sqlite3_step(pCheck) ) bRes = 1; + rc = sqlite3_reset(pCheck); + } + + *pbRes = bRes; + return rc; +} + +/* +** This function is called when initializing an incremental-merge operation. +** It checks if the existing segment with index value iIdx at absolute level +** (iAbsLevel+1) can be appended to by the incremental merge. If it can, the +** merge-writer object *pWriter is initialized to write to it. +** +** An existing segment can be appended to by an incremental merge if: +** +** * It was initially created as an appendable segment (with all required +** space pre-allocated), and +** +** * The first key read from the input (arguments zKey and nKey) is +** greater than the largest key currently stored in the potential +** output segment. +*/ +static int fts3IncrmergeLoad( + Fts3Table *p, /* Fts3 table handle */ + sqlite3_int64 iAbsLevel, /* Absolute level of input segments */ + int iIdx, /* Index of candidate output segment */ + const char *zKey, /* First key to write */ + int nKey, /* Number of bytes in nKey */ + IncrmergeWriter *pWriter /* Populate this object */ +){ + int rc; /* Return code */ + sqlite3_stmt *pSelect = 0; /* SELECT to read %_segdir entry */ + + rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR, &pSelect, 0); + if( rc==SQLITE_OK ){ + sqlite3_int64 iStart = 0; /* Value of %_segdir.start_block */ + sqlite3_int64 iLeafEnd = 0; /* Value of %_segdir.leaves_end_block */ + sqlite3_int64 iEnd = 0; /* Value of %_segdir.end_block */ + const char *aRoot = 0; /* Pointer to %_segdir.root buffer */ + int nRoot = 0; /* Size of aRoot[] in bytes */ + int rc2; /* Return code from sqlite3_reset() */ + int bAppendable = 0; /* Set to true if segment is appendable */ + + /* Read the %_segdir entry for index iIdx absolute level (iAbsLevel+1) */ + sqlite3_bind_int64(pSelect, 1, iAbsLevel+1); + sqlite3_bind_int(pSelect, 2, iIdx); + if( sqlite3_step(pSelect)==SQLITE_ROW ){ + iStart = sqlite3_column_int64(pSelect, 1); + iLeafEnd = sqlite3_column_int64(pSelect, 2); + fts3ReadEndBlockField(pSelect, 3, &iEnd, &pWriter->nLeafData); + if( pWriter->nLeafData<0 ){ + pWriter->nLeafData = pWriter->nLeafData * -1; + } + pWriter->bNoLeafData = (pWriter->nLeafData==0); + nRoot = sqlite3_column_bytes(pSelect, 4); + aRoot = sqlite3_column_blob(pSelect, 4); + if( aRoot==0 ){ + sqlite3_reset(pSelect); + return nRoot ? SQLITE_NOMEM : FTS_CORRUPT_VTAB; + } + }else{ + return sqlite3_reset(pSelect); + } + + /* Check for the zero-length marker in the %_segments table */ + rc = fts3IsAppendable(p, iEnd, &bAppendable); + + /* Check that zKey/nKey is larger than the largest key the candidate */ + if( rc==SQLITE_OK && bAppendable ){ + char *aLeaf = 0; + int nLeaf = 0; + + rc = sqlite3Fts3ReadBlock(p, iLeafEnd, &aLeaf, &nLeaf, 0); + if( rc==SQLITE_OK ){ + NodeReader reader; + for(rc = nodeReaderInit(&reader, aLeaf, nLeaf); + rc==SQLITE_OK && reader.aNode; + rc = nodeReaderNext(&reader) + ){ + assert( reader.aNode ); + } + if( fts3TermCmp(zKey, nKey, reader.term.a, reader.term.n)<=0 ){ + bAppendable = 0; + } + nodeReaderRelease(&reader); + } + sqlite3_free(aLeaf); + } + + if( rc==SQLITE_OK && bAppendable ){ + /* It is possible to append to this segment. Set up the IncrmergeWriter + ** object to do so. */ + int i; + int nHeight = (int)aRoot[0]; + NodeWriter *pNode; + if( nHeight<1 || nHeight>=FTS_MAX_APPENDABLE_HEIGHT ){ + sqlite3_reset(pSelect); + return FTS_CORRUPT_VTAB; + } + + pWriter->nLeafEst = (int)((iEnd - iStart) + 1)/FTS_MAX_APPENDABLE_HEIGHT; + pWriter->iStart = iStart; + pWriter->iEnd = iEnd; + pWriter->iAbsLevel = iAbsLevel; + pWriter->iIdx = iIdx; + + for(i=nHeight+1; i<FTS_MAX_APPENDABLE_HEIGHT; i++){ + pWriter->aNodeWriter[i].iBlock = pWriter->iStart + i*pWriter->nLeafEst; + } + + pNode = &pWriter->aNodeWriter[nHeight]; + pNode->iBlock = pWriter->iStart + pWriter->nLeafEst*nHeight; + blobGrowBuffer(&pNode->block, + MAX(nRoot, p->nNodeSize)+FTS3_NODE_PADDING, &rc + ); + if( rc==SQLITE_OK ){ + memcpy(pNode->block.a, aRoot, nRoot); + pNode->block.n = nRoot; + memset(&pNode->block.a[nRoot], 0, FTS3_NODE_PADDING); + } + + for(i=nHeight; i>=0 && rc==SQLITE_OK; i--){ + NodeReader reader; + memset(&reader, 0, sizeof(reader)); + pNode = &pWriter->aNodeWriter[i]; + + if( pNode->block.a){ + rc = nodeReaderInit(&reader, pNode->block.a, pNode->block.n); + while( reader.aNode && rc==SQLITE_OK ) rc = nodeReaderNext(&reader); + blobGrowBuffer(&pNode->key, reader.term.n, &rc); + if( rc==SQLITE_OK ){ + assert_fts3_nc( reader.term.n>0 || reader.aNode==0 ); + if( reader.term.n>0 ){ + memcpy(pNode->key.a, reader.term.a, reader.term.n); + } + pNode->key.n = reader.term.n; + if( i>0 ){ + char *aBlock = 0; + int nBlock = 0; + pNode = &pWriter->aNodeWriter[i-1]; + pNode->iBlock = reader.iChild; + rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock,0); + blobGrowBuffer(&pNode->block, + MAX(nBlock, p->nNodeSize)+FTS3_NODE_PADDING, &rc + ); + if( rc==SQLITE_OK ){ + memcpy(pNode->block.a, aBlock, nBlock); + pNode->block.n = nBlock; + memset(&pNode->block.a[nBlock], 0, FTS3_NODE_PADDING); + } + sqlite3_free(aBlock); + } + } + } + nodeReaderRelease(&reader); + } + } + + rc2 = sqlite3_reset(pSelect); + if( rc==SQLITE_OK ) rc = rc2; + } + + return rc; +} + +/* +** Determine the largest segment index value that exists within absolute +** level iAbsLevel+1. If no error occurs, set *piIdx to this value plus +** one before returning SQLITE_OK. Or, if there are no segments at all +** within level iAbsLevel, set *piIdx to zero. +** +** If an error occurs, return an SQLite error code. The final value of +** *piIdx is undefined in this case. +*/ +static int fts3IncrmergeOutputIdx( + Fts3Table *p, /* FTS Table handle */ + sqlite3_int64 iAbsLevel, /* Absolute index of input segments */ + int *piIdx /* OUT: Next free index at iAbsLevel+1 */ +){ + int rc; + sqlite3_stmt *pOutputIdx = 0; /* SQL used to find output index */ + + rc = fts3SqlStmt(p, SQL_NEXT_SEGMENT_INDEX, &pOutputIdx, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pOutputIdx, 1, iAbsLevel+1); + sqlite3_step(pOutputIdx); + *piIdx = sqlite3_column_int(pOutputIdx, 0); + rc = sqlite3_reset(pOutputIdx); + } + + return rc; +} + +/* +** Allocate an appendable output segment on absolute level iAbsLevel+1 +** with idx value iIdx. +** +** In the %_segdir table, a segment is defined by the values in three +** columns: +** +** start_block +** leaves_end_block +** end_block +** +** When an appendable segment is allocated, it is estimated that the +** maximum number of leaf blocks that may be required is the sum of the +** number of leaf blocks consumed by the input segments, plus the number +** of input segments, multiplied by two. This value is stored in stack +** variable nLeafEst. +** +** A total of 16*nLeafEst blocks are allocated when an appendable segment +** is created ((1 + end_block - start_block)==16*nLeafEst). The contiguous +** array of leaf nodes starts at the first block allocated. The array +** of interior nodes that are parents of the leaf nodes start at block +** (start_block + (1 + end_block - start_block) / 16). And so on. +** +** In the actual code below, the value "16" is replaced with the +** pre-processor macro FTS_MAX_APPENDABLE_HEIGHT. +*/ +static int fts3IncrmergeWriter( + Fts3Table *p, /* Fts3 table handle */ + sqlite3_int64 iAbsLevel, /* Absolute level of input segments */ + int iIdx, /* Index of new output segment */ + Fts3MultiSegReader *pCsr, /* Cursor that data will be read from */ + IncrmergeWriter *pWriter /* Populate this object */ +){ + int rc; /* Return Code */ + int i; /* Iterator variable */ + int nLeafEst = 0; /* Blocks allocated for leaf nodes */ + sqlite3_stmt *pLeafEst = 0; /* SQL used to determine nLeafEst */ + sqlite3_stmt *pFirstBlock = 0; /* SQL used to determine first block */ + + /* Calculate nLeafEst. */ + rc = fts3SqlStmt(p, SQL_MAX_LEAF_NODE_ESTIMATE, &pLeafEst, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pLeafEst, 1, iAbsLevel); + sqlite3_bind_int64(pLeafEst, 2, pCsr->nSegment); + if( SQLITE_ROW==sqlite3_step(pLeafEst) ){ + nLeafEst = sqlite3_column_int(pLeafEst, 0); + } + rc = sqlite3_reset(pLeafEst); + } + if( rc!=SQLITE_OK ) return rc; + + /* Calculate the first block to use in the output segment */ + rc = fts3SqlStmt(p, SQL_NEXT_SEGMENTS_ID, &pFirstBlock, 0); + if( rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pFirstBlock) ){ + pWriter->iStart = sqlite3_column_int64(pFirstBlock, 0); + pWriter->iEnd = pWriter->iStart - 1; + pWriter->iEnd += nLeafEst * FTS_MAX_APPENDABLE_HEIGHT; + } + rc = sqlite3_reset(pFirstBlock); + } + if( rc!=SQLITE_OK ) return rc; + + /* Insert the marker in the %_segments table to make sure nobody tries + ** to steal the space just allocated. This is also used to identify + ** appendable segments. */ + rc = fts3WriteSegment(p, pWriter->iEnd, 0, 0); + if( rc!=SQLITE_OK ) return rc; + + pWriter->iAbsLevel = iAbsLevel; + pWriter->nLeafEst = nLeafEst; + pWriter->iIdx = iIdx; + + /* Set up the array of NodeWriter objects */ + for(i=0; i<FTS_MAX_APPENDABLE_HEIGHT; i++){ + pWriter->aNodeWriter[i].iBlock = pWriter->iStart + i*pWriter->nLeafEst; + } + return SQLITE_OK; +} + +/* +** Remove an entry from the %_segdir table. This involves running the +** following two statements: +** +** DELETE FROM %_segdir WHERE level = :iAbsLevel AND idx = :iIdx +** UPDATE %_segdir SET idx = idx - 1 WHERE level = :iAbsLevel AND idx > :iIdx +** +** The DELETE statement removes the specific %_segdir level. The UPDATE +** statement ensures that the remaining segments have contiguously allocated +** idx values. +*/ +static int fts3RemoveSegdirEntry( + Fts3Table *p, /* FTS3 table handle */ + sqlite3_int64 iAbsLevel, /* Absolute level to delete from */ + int iIdx /* Index of %_segdir entry to delete */ +){ + int rc; /* Return code */ + sqlite3_stmt *pDelete = 0; /* DELETE statement */ + + rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_ENTRY, &pDelete, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pDelete, 1, iAbsLevel); + sqlite3_bind_int(pDelete, 2, iIdx); + sqlite3_step(pDelete); + rc = sqlite3_reset(pDelete); + } + + return rc; +} + +/* +** One or more segments have just been removed from absolute level iAbsLevel. +** Update the 'idx' values of the remaining segments in the level so that +** the idx values are a contiguous sequence starting from 0. +*/ +static int fts3RepackSegdirLevel( + Fts3Table *p, /* FTS3 table handle */ + sqlite3_int64 iAbsLevel /* Absolute level to repack */ +){ + int rc; /* Return code */ + int *aIdx = 0; /* Array of remaining idx values */ + int nIdx = 0; /* Valid entries in aIdx[] */ + int nAlloc = 0; /* Allocated size of aIdx[] */ + int i; /* Iterator variable */ + sqlite3_stmt *pSelect = 0; /* Select statement to read idx values */ + sqlite3_stmt *pUpdate = 0; /* Update statement to modify idx values */ + + rc = fts3SqlStmt(p, SQL_SELECT_INDEXES, &pSelect, 0); + if( rc==SQLITE_OK ){ + int rc2; + sqlite3_bind_int64(pSelect, 1, iAbsLevel); + while( SQLITE_ROW==sqlite3_step(pSelect) ){ + if( nIdx>=nAlloc ){ + int *aNew; + nAlloc += 16; + aNew = sqlite3_realloc64(aIdx, nAlloc*sizeof(int)); + if( !aNew ){ + rc = SQLITE_NOMEM; + break; + } + aIdx = aNew; + } + aIdx[nIdx++] = sqlite3_column_int(pSelect, 0); + } + rc2 = sqlite3_reset(pSelect); + if( rc==SQLITE_OK ) rc = rc2; + } + + if( rc==SQLITE_OK ){ + rc = fts3SqlStmt(p, SQL_SHIFT_SEGDIR_ENTRY, &pUpdate, 0); + } + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pUpdate, 2, iAbsLevel); + } + + assert( p->bIgnoreSavepoint==0 ); + p->bIgnoreSavepoint = 1; + for(i=0; rc==SQLITE_OK && i<nIdx; i++){ + if( aIdx[i]!=i ){ + sqlite3_bind_int(pUpdate, 3, aIdx[i]); + sqlite3_bind_int(pUpdate, 1, i); + sqlite3_step(pUpdate); + rc = sqlite3_reset(pUpdate); + } + } + p->bIgnoreSavepoint = 0; + + sqlite3_free(aIdx); + return rc; +} + +static void fts3StartNode(Blob *pNode, int iHeight, sqlite3_int64 iChild){ + pNode->a[0] = (char)iHeight; + if( iChild ){ + assert( pNode->nAlloc>=1+sqlite3Fts3VarintLen(iChild) ); + pNode->n = 1 + sqlite3Fts3PutVarint(&pNode->a[1], iChild); + }else{ + assert( pNode->nAlloc>=1 ); + pNode->n = 1; + } +} + +/* +** The first two arguments are a pointer to and the size of a segment b-tree +** node. The node may be a leaf or an internal node. +** +** This function creates a new node image in blob object *pNew by copying +** all terms that are greater than or equal to zTerm/nTerm (for leaf nodes) +** or greater than zTerm/nTerm (for internal nodes) from aNode/nNode. +*/ +static int fts3TruncateNode( + const char *aNode, /* Current node image */ + int nNode, /* Size of aNode in bytes */ + Blob *pNew, /* OUT: Write new node image here */ + const char *zTerm, /* Omit all terms smaller than this */ + int nTerm, /* Size of zTerm in bytes */ + sqlite3_int64 *piBlock /* OUT: Block number in next layer down */ +){ + NodeReader reader; /* Reader object */ + Blob prev = {0, 0, 0}; /* Previous term written to new node */ + int rc = SQLITE_OK; /* Return code */ + int bLeaf; /* True for a leaf node */ + + if( nNode<1 ) return FTS_CORRUPT_VTAB; + bLeaf = aNode[0]=='\0'; + + /* Allocate required output space */ + blobGrowBuffer(pNew, nNode, &rc); + if( rc!=SQLITE_OK ) return rc; + pNew->n = 0; + + /* Populate new node buffer */ + for(rc = nodeReaderInit(&reader, aNode, nNode); + rc==SQLITE_OK && reader.aNode; + rc = nodeReaderNext(&reader) + ){ + if( pNew->n==0 ){ + int res = fts3TermCmp(reader.term.a, reader.term.n, zTerm, nTerm); + if( res<0 || (bLeaf==0 && res==0) ) continue; + fts3StartNode(pNew, (int)aNode[0], reader.iChild); + *piBlock = reader.iChild; + } + rc = fts3AppendToNode( + pNew, &prev, reader.term.a, reader.term.n, + reader.aDoclist, reader.nDoclist + ); + if( rc!=SQLITE_OK ) break; + } + if( pNew->n==0 ){ + fts3StartNode(pNew, (int)aNode[0], reader.iChild); + *piBlock = reader.iChild; + } + assert( pNew->n<=pNew->nAlloc ); + + nodeReaderRelease(&reader); + sqlite3_free(prev.a); + return rc; +} + +/* +** Remove all terms smaller than zTerm/nTerm from segment iIdx in absolute +** level iAbsLevel. This may involve deleting entries from the %_segments +** table, and modifying existing entries in both the %_segments and %_segdir +** tables. +** +** SQLITE_OK is returned if the segment is updated successfully. Or an +** SQLite error code otherwise. +*/ +static int fts3TruncateSegment( + Fts3Table *p, /* FTS3 table handle */ + sqlite3_int64 iAbsLevel, /* Absolute level of segment to modify */ + int iIdx, /* Index within level of segment to modify */ + const char *zTerm, /* Remove terms smaller than this */ + int nTerm /* Number of bytes in buffer zTerm */ +){ + int rc = SQLITE_OK; /* Return code */ + Blob root = {0,0,0}; /* New root page image */ + Blob block = {0,0,0}; /* Buffer used for any other block */ + sqlite3_int64 iBlock = 0; /* Block id */ + sqlite3_int64 iNewStart = 0; /* New value for iStartBlock */ + sqlite3_int64 iOldStart = 0; /* Old value for iStartBlock */ + sqlite3_stmt *pFetch = 0; /* Statement used to fetch segdir */ + + rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR, &pFetch, 0); + if( rc==SQLITE_OK ){ + int rc2; /* sqlite3_reset() return code */ + sqlite3_bind_int64(pFetch, 1, iAbsLevel); + sqlite3_bind_int(pFetch, 2, iIdx); + if( SQLITE_ROW==sqlite3_step(pFetch) ){ + const char *aRoot = sqlite3_column_blob(pFetch, 4); + int nRoot = sqlite3_column_bytes(pFetch, 4); + iOldStart = sqlite3_column_int64(pFetch, 1); + rc = fts3TruncateNode(aRoot, nRoot, &root, zTerm, nTerm, &iBlock); + } + rc2 = sqlite3_reset(pFetch); + if( rc==SQLITE_OK ) rc = rc2; + } + + while( rc==SQLITE_OK && iBlock ){ + char *aBlock = 0; + int nBlock = 0; + iNewStart = iBlock; + + rc = sqlite3Fts3ReadBlock(p, iBlock, &aBlock, &nBlock, 0); + if( rc==SQLITE_OK ){ + rc = fts3TruncateNode(aBlock, nBlock, &block, zTerm, nTerm, &iBlock); + } + if( rc==SQLITE_OK ){ + rc = fts3WriteSegment(p, iNewStart, block.a, block.n); + } + sqlite3_free(aBlock); + } + + /* Variable iNewStart now contains the first valid leaf node. */ + if( rc==SQLITE_OK && iNewStart ){ + sqlite3_stmt *pDel = 0; + rc = fts3SqlStmt(p, SQL_DELETE_SEGMENTS_RANGE, &pDel, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pDel, 1, iOldStart); + sqlite3_bind_int64(pDel, 2, iNewStart-1); + sqlite3_step(pDel); + rc = sqlite3_reset(pDel); + } + } + + if( rc==SQLITE_OK ){ + sqlite3_stmt *pChomp = 0; + rc = fts3SqlStmt(p, SQL_CHOMP_SEGDIR, &pChomp, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pChomp, 1, iNewStart); + sqlite3_bind_blob(pChomp, 2, root.a, root.n, SQLITE_STATIC); + sqlite3_bind_int64(pChomp, 3, iAbsLevel); + sqlite3_bind_int(pChomp, 4, iIdx); + sqlite3_step(pChomp); + rc = sqlite3_reset(pChomp); + sqlite3_bind_null(pChomp, 2); + } + } + + sqlite3_free(root.a); + sqlite3_free(block.a); + return rc; +} + +/* +** This function is called after an incrmental-merge operation has run to +** merge (or partially merge) two or more segments from absolute level +** iAbsLevel. +** +** Each input segment is either removed from the db completely (if all of +** its data was copied to the output segment by the incrmerge operation) +** or modified in place so that it no longer contains those entries that +** have been duplicated in the output segment. +*/ +static int fts3IncrmergeChomp( + Fts3Table *p, /* FTS table handle */ + sqlite3_int64 iAbsLevel, /* Absolute level containing segments */ + Fts3MultiSegReader *pCsr, /* Chomp all segments opened by this cursor */ + int *pnRem /* Number of segments not deleted */ +){ + int i; + int nRem = 0; + int rc = SQLITE_OK; + + for(i=pCsr->nSegment-1; i>=0 && rc==SQLITE_OK; i--){ + Fts3SegReader *pSeg = 0; + int j; + + /* Find the Fts3SegReader object with Fts3SegReader.iIdx==i. It is hiding + ** somewhere in the pCsr->apSegment[] array. */ + for(j=0; ALWAYS(j<pCsr->nSegment); j++){ + pSeg = pCsr->apSegment[j]; + if( pSeg->iIdx==i ) break; + } + assert( j<pCsr->nSegment && pSeg->iIdx==i ); + + if( pSeg->aNode==0 ){ + /* Seg-reader is at EOF. Remove the entire input segment. */ + rc = fts3DeleteSegment(p, pSeg); + if( rc==SQLITE_OK ){ + rc = fts3RemoveSegdirEntry(p, iAbsLevel, pSeg->iIdx); + } + *pnRem = 0; + }else{ + /* The incremental merge did not copy all the data from this + ** segment to the upper level. The segment is modified in place + ** so that it contains no keys smaller than zTerm/nTerm. */ + const char *zTerm = pSeg->zTerm; + int nTerm = pSeg->nTerm; + rc = fts3TruncateSegment(p, iAbsLevel, pSeg->iIdx, zTerm, nTerm); + nRem++; + } + } + + if( rc==SQLITE_OK && nRem!=pCsr->nSegment ){ + rc = fts3RepackSegdirLevel(p, iAbsLevel); + } + + *pnRem = nRem; + return rc; +} + +/* +** Store an incr-merge hint in the database. +*/ +static int fts3IncrmergeHintStore(Fts3Table *p, Blob *pHint){ + sqlite3_stmt *pReplace = 0; + int rc; /* Return code */ + + rc = fts3SqlStmt(p, SQL_REPLACE_STAT, &pReplace, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pReplace, 1, FTS_STAT_INCRMERGEHINT); + sqlite3_bind_blob(pReplace, 2, pHint->a, pHint->n, SQLITE_STATIC); + sqlite3_step(pReplace); + rc = sqlite3_reset(pReplace); + sqlite3_bind_null(pReplace, 2); + } + + return rc; +} + +/* +** Load an incr-merge hint from the database. The incr-merge hint, if one +** exists, is stored in the rowid==1 row of the %_stat table. +** +** If successful, populate blob *pHint with the value read from the %_stat +** table and return SQLITE_OK. Otherwise, if an error occurs, return an +** SQLite error code. +*/ +static int fts3IncrmergeHintLoad(Fts3Table *p, Blob *pHint){ + sqlite3_stmt *pSelect = 0; + int rc; + + pHint->n = 0; + rc = fts3SqlStmt(p, SQL_SELECT_STAT, &pSelect, 0); + if( rc==SQLITE_OK ){ + int rc2; + sqlite3_bind_int(pSelect, 1, FTS_STAT_INCRMERGEHINT); + if( SQLITE_ROW==sqlite3_step(pSelect) ){ + const char *aHint = sqlite3_column_blob(pSelect, 0); + int nHint = sqlite3_column_bytes(pSelect, 0); + if( aHint ){ + blobGrowBuffer(pHint, nHint, &rc); + if( rc==SQLITE_OK ){ + if( ALWAYS(pHint->a!=0) ) memcpy(pHint->a, aHint, nHint); + pHint->n = nHint; + } + } + } + rc2 = sqlite3_reset(pSelect); + if( rc==SQLITE_OK ) rc = rc2; + } + + return rc; +} + +/* +** If *pRc is not SQLITE_OK when this function is called, it is a no-op. +** Otherwise, append an entry to the hint stored in blob *pHint. Each entry +** consists of two varints, the absolute level number of the input segments +** and the number of input segments. +** +** If successful, leave *pRc set to SQLITE_OK and return. If an error occurs, +** set *pRc to an SQLite error code before returning. +*/ +static void fts3IncrmergeHintPush( + Blob *pHint, /* Hint blob to append to */ + i64 iAbsLevel, /* First varint to store in hint */ + int nInput, /* Second varint to store in hint */ + int *pRc /* IN/OUT: Error code */ +){ + blobGrowBuffer(pHint, pHint->n + 2*FTS3_VARINT_MAX, pRc); + if( *pRc==SQLITE_OK ){ + pHint->n += sqlite3Fts3PutVarint(&pHint->a[pHint->n], iAbsLevel); + pHint->n += sqlite3Fts3PutVarint(&pHint->a[pHint->n], (i64)nInput); + } +} + +/* +** Read the last entry (most recently pushed) from the hint blob *pHint +** and then remove the entry. Write the two values read to *piAbsLevel and +** *pnInput before returning. +** +** If no error occurs, return SQLITE_OK. If the hint blob in *pHint does +** not contain at least two valid varints, return SQLITE_CORRUPT_VTAB. +*/ +static int fts3IncrmergeHintPop(Blob *pHint, i64 *piAbsLevel, int *pnInput){ + const int nHint = pHint->n; + int i; + + i = pHint->n-1; + if( (pHint->a[i] & 0x80) ) return FTS_CORRUPT_VTAB; + while( i>0 && (pHint->a[i-1] & 0x80) ) i--; + if( i==0 ) return FTS_CORRUPT_VTAB; + i--; + while( i>0 && (pHint->a[i-1] & 0x80) ) i--; + + pHint->n = i; + i += sqlite3Fts3GetVarint(&pHint->a[i], piAbsLevel); + i += fts3GetVarint32(&pHint->a[i], pnInput); + assert( i<=nHint ); + if( i!=nHint ) return FTS_CORRUPT_VTAB; + + return SQLITE_OK; +} + + +/* +** Attempt an incremental merge that writes nMerge leaf blocks. +** +** Incremental merges happen nMin segments at a time. The segments +** to be merged are the nMin oldest segments (the ones with the smallest +** values for the _segdir.idx field) in the highest level that contains +** at least nMin segments. Multiple merges might occur in an attempt to +** write the quota of nMerge leaf blocks. +*/ +SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ + int rc; /* Return code */ + int nRem = nMerge; /* Number of leaf pages yet to be written */ + Fts3MultiSegReader *pCsr; /* Cursor used to read input data */ + Fts3SegFilter *pFilter; /* Filter used with cursor pCsr */ + IncrmergeWriter *pWriter; /* Writer object */ + int nSeg = 0; /* Number of input segments */ + sqlite3_int64 iAbsLevel = 0; /* Absolute level number to work on */ + Blob hint = {0, 0, 0}; /* Hint read from %_stat table */ + int bDirtyHint = 0; /* True if blob 'hint' has been modified */ + + /* Allocate space for the cursor, filter and writer objects */ + const int nAlloc = sizeof(*pCsr) + sizeof(*pFilter) + sizeof(*pWriter); + pWriter = (IncrmergeWriter *)sqlite3_malloc64(nAlloc); + if( !pWriter ) return SQLITE_NOMEM; + pFilter = (Fts3SegFilter *)&pWriter[1]; + pCsr = (Fts3MultiSegReader *)&pFilter[1]; + + rc = fts3IncrmergeHintLoad(p, &hint); + while( rc==SQLITE_OK && nRem>0 ){ + const i64 nMod = FTS3_SEGDIR_MAXLEVEL * p->nIndex; + sqlite3_stmt *pFindLevel = 0; /* SQL used to determine iAbsLevel */ + int bUseHint = 0; /* True if attempting to append */ + int iIdx = 0; /* Largest idx in level (iAbsLevel+1) */ + + /* Search the %_segdir table for the absolute level with the smallest + ** relative level number that contains at least nMin segments, if any. + ** If one is found, set iAbsLevel to the absolute level number and + ** nSeg to nMin. If no level with at least nMin segments can be found, + ** set nSeg to -1. + */ + rc = fts3SqlStmt(p, SQL_FIND_MERGE_LEVEL, &pFindLevel, 0); + sqlite3_bind_int(pFindLevel, 1, MAX(2, nMin)); + if( sqlite3_step(pFindLevel)==SQLITE_ROW ){ + iAbsLevel = sqlite3_column_int64(pFindLevel, 0); + nSeg = sqlite3_column_int(pFindLevel, 1); + assert( nSeg>=2 ); + }else{ + nSeg = -1; + } + rc = sqlite3_reset(pFindLevel); + + /* If the hint read from the %_stat table is not empty, check if the + ** last entry in it specifies a relative level smaller than or equal + ** to the level identified by the block above (if any). If so, this + ** iteration of the loop will work on merging at the hinted level. + */ + if( rc==SQLITE_OK && hint.n ){ + int nHint = hint.n; + sqlite3_int64 iHintAbsLevel = 0; /* Hint level */ + int nHintSeg = 0; /* Hint number of segments */ + + rc = fts3IncrmergeHintPop(&hint, &iHintAbsLevel, &nHintSeg); + if( nSeg<0 || (iAbsLevel % nMod) >= (iHintAbsLevel % nMod) ){ + /* Based on the scan in the block above, it is known that there + ** are no levels with a relative level smaller than that of + ** iAbsLevel with more than nSeg segments, or if nSeg is -1, + ** no levels with more than nMin segments. Use this to limit the + ** value of nHintSeg to avoid a large memory allocation in case the + ** merge-hint is corrupt*/ + iAbsLevel = iHintAbsLevel; + nSeg = MIN(MAX(nMin,nSeg), nHintSeg); + bUseHint = 1; + bDirtyHint = 1; + }else{ + /* This undoes the effect of the HintPop() above - so that no entry + ** is removed from the hint blob. */ + hint.n = nHint; + } + } + + /* If nSeg is less that zero, then there is no level with at least + ** nMin segments and no hint in the %_stat table. No work to do. + ** Exit early in this case. */ + if( nSeg<=0 ) break; + + assert( nMod<=0x7FFFFFFF ); + if( iAbsLevel<0 || iAbsLevel>(nMod<<32) ){ + rc = FTS_CORRUPT_VTAB; + break; + } + + /* Open a cursor to iterate through the contents of the oldest nSeg + ** indexes of absolute level iAbsLevel. If this cursor is opened using + ** the 'hint' parameters, it is possible that there are less than nSeg + ** segments available in level iAbsLevel. In this case, no work is + ** done on iAbsLevel - fall through to the next iteration of the loop + ** to start work on some other level. */ + memset(pWriter, 0, nAlloc); + pFilter->flags = FTS3_SEGMENT_REQUIRE_POS; + + if( rc==SQLITE_OK ){ + rc = fts3IncrmergeOutputIdx(p, iAbsLevel, &iIdx); + assert( bUseHint==1 || bUseHint==0 ); + if( iIdx==0 || (bUseHint && iIdx==1) ){ + int bIgnore = 0; + rc = fts3SegmentIsMaxLevel(p, iAbsLevel+1, &bIgnore); + if( bIgnore ){ + pFilter->flags |= FTS3_SEGMENT_IGNORE_EMPTY; + } + } + } + + if( rc==SQLITE_OK ){ + rc = fts3IncrmergeCsr(p, iAbsLevel, nSeg, pCsr); + } + if( SQLITE_OK==rc && pCsr->nSegment==nSeg + && SQLITE_OK==(rc = sqlite3Fts3SegReaderStart(p, pCsr, pFilter)) + ){ + int bEmpty = 0; + rc = sqlite3Fts3SegReaderStep(p, pCsr); + if( rc==SQLITE_OK ){ + bEmpty = 1; + }else if( rc!=SQLITE_ROW ){ + sqlite3Fts3SegReaderFinish(pCsr); + break; + } + if( bUseHint && iIdx>0 ){ + const char *zKey = pCsr->zTerm; + int nKey = pCsr->nTerm; + rc = fts3IncrmergeLoad(p, iAbsLevel, iIdx-1, zKey, nKey, pWriter); + }else{ + rc = fts3IncrmergeWriter(p, iAbsLevel, iIdx, pCsr, pWriter); + } + + if( rc==SQLITE_OK && pWriter->nLeafEst ){ + fts3LogMerge(nSeg, iAbsLevel); + if( bEmpty==0 ){ + do { + rc = fts3IncrmergeAppend(p, pWriter, pCsr); + if( rc==SQLITE_OK ) rc = sqlite3Fts3SegReaderStep(p, pCsr); + if( pWriter->nWork>=nRem && rc==SQLITE_ROW ) rc = SQLITE_OK; + }while( rc==SQLITE_ROW ); + } + + /* Update or delete the input segments */ + if( rc==SQLITE_OK ){ + nRem -= (1 + pWriter->nWork); + rc = fts3IncrmergeChomp(p, iAbsLevel, pCsr, &nSeg); + if( nSeg!=0 ){ + bDirtyHint = 1; + fts3IncrmergeHintPush(&hint, iAbsLevel, nSeg, &rc); + } + } + } + + if( nSeg!=0 ){ + pWriter->nLeafData = pWriter->nLeafData * -1; + } + fts3IncrmergeRelease(p, pWriter, &rc); + if( nSeg==0 && pWriter->bNoLeafData==0 ){ + fts3PromoteSegments(p, iAbsLevel+1, pWriter->nLeafData); + } + } + + sqlite3Fts3SegReaderFinish(pCsr); + } + + /* Write the hint values into the %_stat table for the next incr-merger */ + if( bDirtyHint && rc==SQLITE_OK ){ + rc = fts3IncrmergeHintStore(p, &hint); + } + + sqlite3_free(pWriter); + sqlite3_free(hint.a); + return rc; +} + +/* +** Convert the text beginning at *pz into an integer and return +** its value. Advance *pz to point to the first character past +** the integer. +** +** This function used for parameters to merge= and incrmerge= +** commands. +*/ +static int fts3Getint(const char **pz){ + const char *z = *pz; + int i = 0; + while( (*z)>='0' && (*z)<='9' && i<214748363 ) i = 10*i + *(z++) - '0'; + *pz = z; + return i; +} + +/* +** Process statements of the form: +** +** INSERT INTO table(table) VALUES('merge=A,B'); +** +** A and B are integers that decode to be the number of leaf pages +** written for the merge, and the minimum number of segments on a level +** before it will be selected for a merge, respectively. +*/ +static int fts3DoIncrmerge( + Fts3Table *p, /* FTS3 table handle */ + const char *zParam /* Nul-terminated string containing "A,B" */ +){ + int rc; + int nMin = (MergeCount(p) / 2); + int nMerge = 0; + const char *z = zParam; + + /* Read the first integer value */ + nMerge = fts3Getint(&z); + + /* If the first integer value is followed by a ',', read the second + ** integer value. */ + if( z[0]==',' && z[1]!='\0' ){ + z++; + nMin = fts3Getint(&z); + } + + if( z[0]!='\0' || nMin<2 ){ + rc = SQLITE_ERROR; + }else{ + rc = SQLITE_OK; + if( !p->bHasStat ){ + assert( p->bFts4==0 ); + sqlite3Fts3CreateStatTable(&rc, p); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3Incrmerge(p, nMerge, nMin); + } + sqlite3Fts3SegmentsClose(p); + } + return rc; +} + +/* +** Process statements of the form: +** +** INSERT INTO table(table) VALUES('automerge=X'); +** +** where X is an integer. X==0 means to turn automerge off. X!=0 means +** turn it on. The setting is persistent. +*/ +static int fts3DoAutoincrmerge( + Fts3Table *p, /* FTS3 table handle */ + const char *zParam /* Nul-terminated string containing boolean */ +){ + int rc = SQLITE_OK; + sqlite3_stmt *pStmt = 0; + p->nAutoincrmerge = fts3Getint(&zParam); + if( p->nAutoincrmerge==1 || p->nAutoincrmerge>MergeCount(p) ){ + p->nAutoincrmerge = 8; + } + if( !p->bHasStat ){ + assert( p->bFts4==0 ); + sqlite3Fts3CreateStatTable(&rc, p); + if( rc ) return rc; + } + rc = fts3SqlStmt(p, SQL_REPLACE_STAT, &pStmt, 0); + if( rc ) return rc; + sqlite3_bind_int(pStmt, 1, FTS_STAT_AUTOINCRMERGE); + sqlite3_bind_int(pStmt, 2, p->nAutoincrmerge); + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + return rc; +} + +/* +** Return a 64-bit checksum for the FTS index entry specified by the +** arguments to this function. +*/ +static u64 fts3ChecksumEntry( + const char *zTerm, /* Pointer to buffer containing term */ + int nTerm, /* Size of zTerm in bytes */ + int iLangid, /* Language id for current row */ + int iIndex, /* Index (0..Fts3Table.nIndex-1) */ + i64 iDocid, /* Docid for current row. */ + int iCol, /* Column number */ + int iPos /* Position */ +){ + int i; + u64 ret = (u64)iDocid; + + ret += (ret<<3) + iLangid; + ret += (ret<<3) + iIndex; + ret += (ret<<3) + iCol; + ret += (ret<<3) + iPos; + for(i=0; i<nTerm; i++) ret += (ret<<3) + zTerm[i]; + + return ret; +} + +/* +** Return a checksum of all entries in the FTS index that correspond to +** language id iLangid. The checksum is calculated by XORing the checksums +** of each individual entry (see fts3ChecksumEntry()) together. +** +** If successful, the checksum value is returned and *pRc set to SQLITE_OK. +** Otherwise, if an error occurs, *pRc is set to an SQLite error code. The +** return value is undefined in this case. +*/ +static u64 fts3ChecksumIndex( + Fts3Table *p, /* FTS3 table handle */ + int iLangid, /* Language id to return cksum for */ + int iIndex, /* Index to cksum (0..p->nIndex-1) */ + int *pRc /* OUT: Return code */ +){ + Fts3SegFilter filter; + Fts3MultiSegReader csr; + int rc; + u64 cksum = 0; + + if( *pRc ) return 0; + + memset(&filter, 0, sizeof(filter)); + memset(&csr, 0, sizeof(csr)); + filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY; + filter.flags |= FTS3_SEGMENT_SCAN; + + rc = sqlite3Fts3SegReaderCursor( + p, iLangid, iIndex, FTS3_SEGCURSOR_ALL, 0, 0, 0, 1,&csr + ); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3SegReaderStart(p, &csr, &filter); + } + + if( rc==SQLITE_OK ){ + while( SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, &csr)) ){ + char *pCsr = csr.aDoclist; + char *pEnd = &pCsr[csr.nDoclist]; + + i64 iDocid = 0; + i64 iCol = 0; + u64 iPos = 0; + + pCsr += sqlite3Fts3GetVarint(pCsr, &iDocid); + while( pCsr<pEnd ){ + u64 iVal = 0; + pCsr += sqlite3Fts3GetVarintU(pCsr, &iVal); + if( pCsr<pEnd ){ + if( iVal==0 || iVal==1 ){ + iCol = 0; + iPos = 0; + if( iVal ){ + pCsr += sqlite3Fts3GetVarint(pCsr, &iCol); + }else{ + pCsr += sqlite3Fts3GetVarintU(pCsr, &iVal); + if( p->bDescIdx ){ + iDocid = (i64)((u64)iDocid - iVal); + }else{ + iDocid = (i64)((u64)iDocid + iVal); + } + } + }else{ + iPos += (iVal - 2); + cksum = cksum ^ fts3ChecksumEntry( + csr.zTerm, csr.nTerm, iLangid, iIndex, iDocid, + (int)iCol, (int)iPos + ); + } + } + } + } + } + sqlite3Fts3SegReaderFinish(&csr); + + *pRc = rc; + return cksum; +} + +/* +** Check if the contents of the FTS index match the current contents of the +** content table. If no error occurs and the contents do match, set *pbOk +** to true and return SQLITE_OK. Or if the contents do not match, set *pbOk +** to false before returning. +** +** If an error occurs (e.g. an OOM or IO error), return an SQLite error +** code. The final value of *pbOk is undefined in this case. +*/ +static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ + int rc = SQLITE_OK; /* Return code */ + u64 cksum1 = 0; /* Checksum based on FTS index contents */ + u64 cksum2 = 0; /* Checksum based on %_content contents */ + sqlite3_stmt *pAllLangid = 0; /* Statement to return all language-ids */ + + /* This block calculates the checksum according to the FTS index. */ + rc = fts3SqlStmt(p, SQL_SELECT_ALL_LANGID, &pAllLangid, 0); + if( rc==SQLITE_OK ){ + int rc2; + sqlite3_bind_int(pAllLangid, 1, p->iPrevLangid); + sqlite3_bind_int(pAllLangid, 2, p->nIndex); + while( rc==SQLITE_OK && sqlite3_step(pAllLangid)==SQLITE_ROW ){ + int iLangid = sqlite3_column_int(pAllLangid, 0); + int i; + for(i=0; i<p->nIndex; i++){ + cksum1 = cksum1 ^ fts3ChecksumIndex(p, iLangid, i, &rc); + } + } + rc2 = sqlite3_reset(pAllLangid); + if( rc==SQLITE_OK ) rc = rc2; + } + + /* This block calculates the checksum according to the %_content table */ + if( rc==SQLITE_OK ){ + sqlite3_tokenizer_module const *pModule = p->pTokenizer->pModule; + sqlite3_stmt *pStmt = 0; + char *zSql; + + zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist); + if( !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + } + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + i64 iDocid = sqlite3_column_int64(pStmt, 0); + int iLang = langidFromSelect(p, pStmt); + int iCol; + + for(iCol=0; rc==SQLITE_OK && iCol<p->nColumn; iCol++){ + if( p->abNotindexed[iCol]==0 ){ + const char *zText = (const char *)sqlite3_column_text(pStmt, iCol+1); + sqlite3_tokenizer_cursor *pT = 0; + + rc = sqlite3Fts3OpenTokenizer(p->pTokenizer, iLang, zText, -1, &pT); + while( rc==SQLITE_OK ){ + char const *zToken; /* Buffer containing token */ + int nToken = 0; /* Number of bytes in token */ + int iDum1 = 0, iDum2 = 0; /* Dummy variables */ + int iPos = 0; /* Position of token in zText */ + + rc = pModule->xNext(pT, &zToken, &nToken, &iDum1, &iDum2, &iPos); + if( rc==SQLITE_OK ){ + int i; + cksum2 = cksum2 ^ fts3ChecksumEntry( + zToken, nToken, iLang, 0, iDocid, iCol, iPos + ); + for(i=1; i<p->nIndex; i++){ + if( p->aIndex[i].nPrefix<=nToken ){ + cksum2 = cksum2 ^ fts3ChecksumEntry( + zToken, p->aIndex[i].nPrefix, iLang, i, iDocid, iCol, iPos + ); + } + } + } + } + if( pT ) pModule->xClose(pT); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + } + } + } + + sqlite3_finalize(pStmt); + } + + *pbOk = (cksum1==cksum2); + return rc; +} + +/* +** Run the integrity-check. If no error occurs and the current contents of +** the FTS index are correct, return SQLITE_OK. Or, if the contents of the +** FTS index are incorrect, return SQLITE_CORRUPT_VTAB. +** +** Or, if an error (e.g. an OOM or IO error) occurs, return an SQLite +** error code. +** +** The integrity-check works as follows. For each token and indexed token +** prefix in the document set, a 64-bit checksum is calculated (by code +** in fts3ChecksumEntry()) based on the following: +** +** + The index number (0 for the main index, 1 for the first prefix +** index etc.), +** + The token (or token prefix) text itself, +** + The language-id of the row it appears in, +** + The docid of the row it appears in, +** + The column it appears in, and +** + The tokens position within that column. +** +** The checksums for all entries in the index are XORed together to create +** a single checksum for the entire index. +** +** The integrity-check code calculates the same checksum in two ways: +** +** 1. By scanning the contents of the FTS index, and +** 2. By scanning and tokenizing the content table. +** +** If the two checksums are identical, the integrity-check is deemed to have +** passed. +*/ +static int fts3DoIntegrityCheck( + Fts3Table *p /* FTS3 table handle */ +){ + int rc; + int bOk = 0; + rc = fts3IntegrityCheck(p, &bOk); + if( rc==SQLITE_OK && bOk==0 ) rc = FTS_CORRUPT_VTAB; + return rc; +} + +/* +** Handle a 'special' INSERT of the form: +** +** "INSERT INTO tbl(tbl) VALUES(<expr>)" +** +** Argument pVal contains the result of <expr>. Currently the only +** meaningful value to insert is the text 'optimize'. +*/ +static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){ + int rc = SQLITE_ERROR; /* Return Code */ + const char *zVal = (const char *)sqlite3_value_text(pVal); + int nVal = sqlite3_value_bytes(pVal); + + if( !zVal ){ + return SQLITE_NOMEM; + }else if( nVal==8 && 0==sqlite3_strnicmp(zVal, "optimize", 8) ){ + rc = fts3DoOptimize(p, 0); + }else if( nVal==7 && 0==sqlite3_strnicmp(zVal, "rebuild", 7) ){ + rc = fts3DoRebuild(p); + }else if( nVal==15 && 0==sqlite3_strnicmp(zVal, "integrity-check", 15) ){ + rc = fts3DoIntegrityCheck(p); + }else if( nVal>6 && 0==sqlite3_strnicmp(zVal, "merge=", 6) ){ + rc = fts3DoIncrmerge(p, &zVal[6]); + }else if( nVal>10 && 0==sqlite3_strnicmp(zVal, "automerge=", 10) ){ + rc = fts3DoAutoincrmerge(p, &zVal[10]); +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + }else{ + int v; + if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){ + v = atoi(&zVal[9]); + if( v>=24 && v<=p->nPgsz-35 ) p->nNodeSize = v; + rc = SQLITE_OK; + }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){ + v = atoi(&zVal[11]); + if( v>=64 && v<=FTS3_MAX_PENDING_DATA ) p->nMaxPendingData = v; + rc = SQLITE_OK; + }else if( nVal>21 && 0==sqlite3_strnicmp(zVal,"test-no-incr-doclist=",21) ){ + p->bNoIncrDoclist = atoi(&zVal[21]); + rc = SQLITE_OK; + }else if( nVal>11 && 0==sqlite3_strnicmp(zVal,"mergecount=",11) ){ + v = atoi(&zVal[11]); + if( v>=4 && v<=FTS3_MERGE_COUNT && (v&1)==0 ) p->nMergeCount = v; + rc = SQLITE_OK; + } +#endif + } + return rc; +} + +#ifndef SQLITE_DISABLE_FTS4_DEFERRED +/* +** Delete all cached deferred doclists. Deferred doclists are cached +** (allocated) by the sqlite3Fts3CacheDeferredDoclists() function. +*/ +SQLITE_PRIVATE void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *pCsr){ + Fts3DeferredToken *pDef; + for(pDef=pCsr->pDeferred; pDef; pDef=pDef->pNext){ + fts3PendingListDelete(pDef->pList); + pDef->pList = 0; + } +} + +/* +** Free all entries in the pCsr->pDeffered list. Entries are added to +** this list using sqlite3Fts3DeferToken(). +*/ +SQLITE_PRIVATE void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *pCsr){ + Fts3DeferredToken *pDef; + Fts3DeferredToken *pNext; + for(pDef=pCsr->pDeferred; pDef; pDef=pNext){ + pNext = pDef->pNext; + fts3PendingListDelete(pDef->pList); + sqlite3_free(pDef); + } + pCsr->pDeferred = 0; +} + +/* +** Generate deferred-doclists for all tokens in the pCsr->pDeferred list +** based on the row that pCsr currently points to. +** +** A deferred-doclist is like any other doclist with position information +** included, except that it only contains entries for a single row of the +** table, not for all rows. +*/ +SQLITE_PRIVATE int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *pCsr){ + int rc = SQLITE_OK; /* Return code */ + if( pCsr->pDeferred ){ + int i; /* Used to iterate through table columns */ + sqlite3_int64 iDocid; /* Docid of the row pCsr points to */ + Fts3DeferredToken *pDef; /* Used to iterate through deferred tokens */ + + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; + sqlite3_tokenizer *pT = p->pTokenizer; + sqlite3_tokenizer_module const *pModule = pT->pModule; + + assert( pCsr->isRequireSeek==0 ); + iDocid = sqlite3_column_int64(pCsr->pStmt, 0); + + for(i=0; i<p->nColumn && rc==SQLITE_OK; i++){ + if( p->abNotindexed[i]==0 ){ + const char *zText = (const char *)sqlite3_column_text(pCsr->pStmt, i+1); + sqlite3_tokenizer_cursor *pTC = 0; + + rc = sqlite3Fts3OpenTokenizer(pT, pCsr->iLangid, zText, -1, &pTC); + while( rc==SQLITE_OK ){ + char const *zToken; /* Buffer containing token */ + int nToken = 0; /* Number of bytes in token */ + int iDum1 = 0, iDum2 = 0; /* Dummy variables */ + int iPos = 0; /* Position of token in zText */ + + rc = pModule->xNext(pTC, &zToken, &nToken, &iDum1, &iDum2, &iPos); + for(pDef=pCsr->pDeferred; pDef && rc==SQLITE_OK; pDef=pDef->pNext){ + Fts3PhraseToken *pPT = pDef->pToken; + if( (pDef->iCol>=p->nColumn || pDef->iCol==i) + && (pPT->bFirst==0 || iPos==0) + && (pPT->n==nToken || (pPT->isPrefix && pPT->n<nToken)) + && (0==memcmp(zToken, pPT->z, pPT->n)) + ){ + fts3PendingListAppend(&pDef->pList, iDocid, i, iPos, &rc); + } + } + } + if( pTC ) pModule->xClose(pTC); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + } + } + + for(pDef=pCsr->pDeferred; pDef && rc==SQLITE_OK; pDef=pDef->pNext){ + if( pDef->pList ){ + rc = fts3PendingListAppendVarint(&pDef->pList, 0); + } + } + } + + return rc; +} + +SQLITE_PRIVATE int sqlite3Fts3DeferredTokenList( + Fts3DeferredToken *p, + char **ppData, + int *pnData +){ + char *pRet; + int nSkip; + sqlite3_int64 dummy; + + *ppData = 0; + *pnData = 0; + + if( p->pList==0 ){ + return SQLITE_OK; + } + + pRet = (char *)sqlite3_malloc64(p->pList->nData); + if( !pRet ) return SQLITE_NOMEM; + + nSkip = sqlite3Fts3GetVarint(p->pList->aData, &dummy); + *pnData = p->pList->nData - nSkip; + *ppData = pRet; + + memcpy(pRet, &p->pList->aData[nSkip], *pnData); + return SQLITE_OK; +} + +/* +** Add an entry for token pToken to the pCsr->pDeferred list. +*/ +SQLITE_PRIVATE int sqlite3Fts3DeferToken( + Fts3Cursor *pCsr, /* Fts3 table cursor */ + Fts3PhraseToken *pToken, /* Token to defer */ + int iCol /* Column that token must appear in (or -1) */ +){ + Fts3DeferredToken *pDeferred; + pDeferred = sqlite3_malloc64(sizeof(*pDeferred)); + if( !pDeferred ){ + return SQLITE_NOMEM; + } + memset(pDeferred, 0, sizeof(*pDeferred)); + pDeferred->pToken = pToken; + pDeferred->pNext = pCsr->pDeferred; + pDeferred->iCol = iCol; + pCsr->pDeferred = pDeferred; + + assert( pToken->pDeferred==0 ); + pToken->pDeferred = pDeferred; + + return SQLITE_OK; +} +#endif + +/* +** SQLite value pRowid contains the rowid of a row that may or may not be +** present in the FTS3 table. If it is, delete it and adjust the contents +** of subsiduary data structures accordingly. +*/ +static int fts3DeleteByRowid( + Fts3Table *p, + sqlite3_value *pRowid, + int *pnChng, /* IN/OUT: Decrement if row is deleted */ + u32 *aSzDel +){ + int rc = SQLITE_OK; /* Return code */ + int bFound = 0; /* True if *pRowid really is in the table */ + + fts3DeleteTerms(&rc, p, pRowid, aSzDel, &bFound); + if( bFound && rc==SQLITE_OK ){ + int isEmpty = 0; /* Deleting *pRowid leaves the table empty */ + rc = fts3IsEmpty(p, pRowid, &isEmpty); + if( rc==SQLITE_OK ){ + if( isEmpty ){ + /* Deleting this row means the whole table is empty. In this case + ** delete the contents of all three tables and throw away any + ** data in the pendingTerms hash table. */ + rc = fts3DeleteAll(p, 1); + *pnChng = 0; + memset(aSzDel, 0, sizeof(u32) * (p->nColumn+1) * 2); + }else{ + *pnChng = *pnChng - 1; + if( p->zContentTbl==0 ){ + fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid); + } + if( p->bHasDocsize ){ + fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, &pRowid); + } + } + } + } + + return rc; +} + +/* +** This function does the work for the xUpdate method of FTS3 virtual +** tables. The schema of the virtual table being: +** +** CREATE TABLE <table name>( +** <user columns>, +** <table name> HIDDEN, +** docid HIDDEN, +** <langid> HIDDEN +** ); +** +** +*/ +SQLITE_PRIVATE int sqlite3Fts3UpdateMethod( + sqlite3_vtab *pVtab, /* FTS3 vtab object */ + int nArg, /* Size of argument array */ + sqlite3_value **apVal, /* Array of arguments */ + sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */ +){ + Fts3Table *p = (Fts3Table *)pVtab; + int rc = SQLITE_OK; /* Return Code */ + u32 *aSzIns = 0; /* Sizes of inserted documents */ + u32 *aSzDel = 0; /* Sizes of deleted documents */ + int nChng = 0; /* Net change in number of documents */ + int bInsertDone = 0; + + /* At this point it must be known if the %_stat table exists or not. + ** So bHasStat may not be 2. */ + assert( p->bHasStat==0 || p->bHasStat==1 ); + + assert( p->pSegments==0 ); + assert( + nArg==1 /* DELETE operations */ + || nArg==(2 + p->nColumn + 3) /* INSERT or UPDATE operations */ + ); + + /* Check for a "special" INSERT operation. One of the form: + ** + ** INSERT INTO xyz(xyz) VALUES('command'); + */ + if( nArg>1 + && sqlite3_value_type(apVal[0])==SQLITE_NULL + && sqlite3_value_type(apVal[p->nColumn+2])!=SQLITE_NULL + ){ + rc = fts3SpecialInsert(p, apVal[p->nColumn+2]); + goto update_out; + } + + if( nArg>1 && sqlite3_value_int(apVal[2 + p->nColumn + 2])<0 ){ + rc = SQLITE_CONSTRAINT; + goto update_out; + } + + /* Allocate space to hold the change in document sizes */ + aSzDel = sqlite3_malloc64(sizeof(aSzDel[0])*((sqlite3_int64)p->nColumn+1)*2); + if( aSzDel==0 ){ + rc = SQLITE_NOMEM; + goto update_out; + } + aSzIns = &aSzDel[p->nColumn+1]; + memset(aSzDel, 0, sizeof(aSzDel[0])*(p->nColumn+1)*2); + + rc = fts3Writelock(p); + if( rc!=SQLITE_OK ) goto update_out; + + /* If this is an INSERT operation, or an UPDATE that modifies the rowid + ** value, then this operation requires constraint handling. + ** + ** If the on-conflict mode is REPLACE, this means that the existing row + ** should be deleted from the database before inserting the new row. Or, + ** if the on-conflict mode is other than REPLACE, then this method must + ** detect the conflict and return SQLITE_CONSTRAINT before beginning to + ** modify the database file. + */ + if( nArg>1 && p->zContentTbl==0 ){ + /* Find the value object that holds the new rowid value. */ + sqlite3_value *pNewRowid = apVal[3+p->nColumn]; + if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){ + pNewRowid = apVal[1]; + } + + if( sqlite3_value_type(pNewRowid)!=SQLITE_NULL && ( + sqlite3_value_type(apVal[0])==SQLITE_NULL + || sqlite3_value_int64(apVal[0])!=sqlite3_value_int64(pNewRowid) + )){ + /* The new rowid is not NULL (in this case the rowid will be + ** automatically assigned and there is no chance of a conflict), and + ** the statement is either an INSERT or an UPDATE that modifies the + ** rowid column. So if the conflict mode is REPLACE, then delete any + ** existing row with rowid=pNewRowid. + ** + ** Or, if the conflict mode is not REPLACE, insert the new record into + ** the %_content table. If we hit the duplicate rowid constraint (or any + ** other error) while doing so, return immediately. + ** + ** This branch may also run if pNewRowid contains a value that cannot + ** be losslessly converted to an integer. In this case, the eventual + ** call to fts3InsertData() (either just below or further on in this + ** function) will return SQLITE_MISMATCH. If fts3DeleteByRowid is + ** invoked, it will delete zero rows (since no row will have + ** docid=$pNewRowid if $pNewRowid is not an integer value). + */ + if( sqlite3_vtab_on_conflict(p->db)==SQLITE_REPLACE ){ + rc = fts3DeleteByRowid(p, pNewRowid, &nChng, aSzDel); + }else{ + rc = fts3InsertData(p, apVal, pRowid); + bInsertDone = 1; + } + } + } + if( rc!=SQLITE_OK ){ + goto update_out; + } + + /* If this is a DELETE or UPDATE operation, remove the old record. */ + if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ + assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER ); + rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel); + } + + /* If this is an INSERT or UPDATE operation, insert the new record. */ + if( nArg>1 && rc==SQLITE_OK ){ + int iLangid = sqlite3_value_int(apVal[2 + p->nColumn + 2]); + if( bInsertDone==0 ){ + rc = fts3InsertData(p, apVal, pRowid); + if( rc==SQLITE_CONSTRAINT && p->zContentTbl==0 ){ + rc = FTS_CORRUPT_VTAB; + } + } + if( rc==SQLITE_OK ){ + rc = fts3PendingTermsDocid(p, 0, iLangid, *pRowid); + } + if( rc==SQLITE_OK ){ + assert( p->iPrevDocid==*pRowid ); + rc = fts3InsertTerms(p, iLangid, apVal, aSzIns); + } + if( p->bHasDocsize ){ + fts3InsertDocsize(&rc, p, aSzIns); + } + nChng++; + } + + if( p->bFts4 ){ + fts3UpdateDocTotals(&rc, p, aSzIns, aSzDel, nChng); + } + + update_out: + sqlite3_free(aSzDel); + sqlite3Fts3SegmentsClose(p); + return rc; +} + +/* +** Flush any data in the pending-terms hash table to disk. If successful, +** merge all segments in the database (including the new segment, if +** there was any data to flush) into a single segment. +*/ +SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *p){ + int rc; + rc = sqlite3_exec(p->db, "SAVEPOINT fts3", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = fts3DoOptimize(p, 1); + if( rc==SQLITE_OK || rc==SQLITE_DONE ){ + int rc2 = sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0); + if( rc2!=SQLITE_OK ) rc = rc2; + }else{ + sqlite3_exec(p->db, "ROLLBACK TO fts3", 0, 0, 0); + sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0); + } + } + sqlite3Fts3SegmentsClose(p); + return rc; +} + +#endif + +/************** End of fts3_write.c ******************************************/ +/************** Begin file fts3_snippet.c ************************************/ +/* +** 2009 Oct 23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +*/ + +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* #include <string.h> */ +/* #include <assert.h> */ + +#ifndef SQLITE_AMALGAMATION +typedef sqlite3_int64 i64; +#endif + +/* +** Characters that may appear in the second argument to matchinfo(). +*/ +#define FTS3_MATCHINFO_NPHRASE 'p' /* 1 value */ +#define FTS3_MATCHINFO_NCOL 'c' /* 1 value */ +#define FTS3_MATCHINFO_NDOC 'n' /* 1 value */ +#define FTS3_MATCHINFO_AVGLENGTH 'a' /* nCol values */ +#define FTS3_MATCHINFO_LENGTH 'l' /* nCol values */ +#define FTS3_MATCHINFO_LCS 's' /* nCol values */ +#define FTS3_MATCHINFO_HITS 'x' /* 3*nCol*nPhrase values */ +#define FTS3_MATCHINFO_LHITS 'y' /* nCol*nPhrase values */ +#define FTS3_MATCHINFO_LHITS_BM 'b' /* nCol*nPhrase values */ + +/* +** The default value for the second argument to matchinfo(). +*/ +#define FTS3_MATCHINFO_DEFAULT "pcx" + + +/* +** Used as an sqlite3Fts3ExprIterate() context when loading phrase doclists to +** Fts3Expr.aDoclist[]/nDoclist. +*/ +typedef struct LoadDoclistCtx LoadDoclistCtx; +struct LoadDoclistCtx { + Fts3Cursor *pCsr; /* FTS3 Cursor */ + int nPhrase; /* Number of phrases seen so far */ + int nToken; /* Number of tokens seen so far */ +}; + +/* +** The following types are used as part of the implementation of the +** fts3BestSnippet() routine. +*/ +typedef struct SnippetIter SnippetIter; +typedef struct SnippetPhrase SnippetPhrase; +typedef struct SnippetFragment SnippetFragment; + +struct SnippetIter { + Fts3Cursor *pCsr; /* Cursor snippet is being generated from */ + int iCol; /* Extract snippet from this column */ + int nSnippet; /* Requested snippet length (in tokens) */ + int nPhrase; /* Number of phrases in query */ + SnippetPhrase *aPhrase; /* Array of size nPhrase */ + int iCurrent; /* First token of current snippet */ +}; + +struct SnippetPhrase { + int nToken; /* Number of tokens in phrase */ + char *pList; /* Pointer to start of phrase position list */ + i64 iHead; /* Next value in position list */ + char *pHead; /* Position list data following iHead */ + i64 iTail; /* Next value in trailing position list */ + char *pTail; /* Position list data following iTail */ +}; + +struct SnippetFragment { + int iCol; /* Column snippet is extracted from */ + int iPos; /* Index of first token in snippet */ + u64 covered; /* Mask of query phrases covered */ + u64 hlmask; /* Mask of snippet terms to highlight */ +}; + +/* +** This type is used as an sqlite3Fts3ExprIterate() context object while +** accumulating the data returned by the matchinfo() function. +*/ +typedef struct MatchInfo MatchInfo; +struct MatchInfo { + Fts3Cursor *pCursor; /* FTS3 Cursor */ + int nCol; /* Number of columns in table */ + int nPhrase; /* Number of matchable phrases in query */ + sqlite3_int64 nDoc; /* Number of docs in database */ + char flag; + u32 *aMatchinfo; /* Pre-allocated buffer */ +}; + +/* +** An instance of this structure is used to manage a pair of buffers, each +** (nElem * sizeof(u32)) bytes in size. See the MatchinfoBuffer code below +** for details. +*/ +struct MatchinfoBuffer { + u8 aRef[3]; + int nElem; + int bGlobal; /* Set if global data is loaded */ + char *zMatchinfo; + u32 aMatchinfo[1]; +}; + + +/* +** The snippet() and offsets() functions both return text values. An instance +** of the following structure is used to accumulate those values while the +** functions are running. See fts3StringAppend() for details. +*/ +typedef struct StrBuffer StrBuffer; +struct StrBuffer { + char *z; /* Pointer to buffer containing string */ + int n; /* Length of z in bytes (excl. nul-term) */ + int nAlloc; /* Allocated size of buffer z in bytes */ +}; + + +/************************************************************************* +** Start of MatchinfoBuffer code. +*/ + +/* +** Allocate a two-slot MatchinfoBuffer object. +*/ +static MatchinfoBuffer *fts3MIBufferNew(size_t nElem, const char *zMatchinfo){ + MatchinfoBuffer *pRet; + sqlite3_int64 nByte = sizeof(u32) * (2*(sqlite3_int64)nElem + 1) + + sizeof(MatchinfoBuffer); + sqlite3_int64 nStr = strlen(zMatchinfo); + + pRet = sqlite3Fts3MallocZero(nByte + nStr+1); + if( pRet ){ + pRet->aMatchinfo[0] = (u8*)(&pRet->aMatchinfo[1]) - (u8*)pRet; + pRet->aMatchinfo[1+nElem] = pRet->aMatchinfo[0] + + sizeof(u32)*((int)nElem+1); + pRet->nElem = (int)nElem; + pRet->zMatchinfo = ((char*)pRet) + nByte; + memcpy(pRet->zMatchinfo, zMatchinfo, nStr+1); + pRet->aRef[0] = 1; + } + + return pRet; +} + +static void fts3MIBufferFree(void *p){ + MatchinfoBuffer *pBuf = (MatchinfoBuffer*)((u8*)p - ((u32*)p)[-1]); + + assert( (u32*)p==&pBuf->aMatchinfo[1] + || (u32*)p==&pBuf->aMatchinfo[pBuf->nElem+2] + ); + if( (u32*)p==&pBuf->aMatchinfo[1] ){ + pBuf->aRef[1] = 0; + }else{ + pBuf->aRef[2] = 0; + } + + if( pBuf->aRef[0]==0 && pBuf->aRef[1]==0 && pBuf->aRef[2]==0 ){ + sqlite3_free(pBuf); + } +} + +static void (*fts3MIBufferAlloc(MatchinfoBuffer *p, u32 **paOut))(void*){ + void (*xRet)(void*) = 0; + u32 *aOut = 0; + + if( p->aRef[1]==0 ){ + p->aRef[1] = 1; + aOut = &p->aMatchinfo[1]; + xRet = fts3MIBufferFree; + } + else if( p->aRef[2]==0 ){ + p->aRef[2] = 1; + aOut = &p->aMatchinfo[p->nElem+2]; + xRet = fts3MIBufferFree; + }else{ + aOut = (u32*)sqlite3_malloc64(p->nElem * sizeof(u32)); + if( aOut ){ + xRet = sqlite3_free; + if( p->bGlobal ) memcpy(aOut, &p->aMatchinfo[1], p->nElem*sizeof(u32)); + } + } + + *paOut = aOut; + return xRet; +} + +static void fts3MIBufferSetGlobal(MatchinfoBuffer *p){ + p->bGlobal = 1; + memcpy(&p->aMatchinfo[2+p->nElem], &p->aMatchinfo[1], p->nElem*sizeof(u32)); +} + +/* +** Free a MatchinfoBuffer object allocated using fts3MIBufferNew() +*/ +SQLITE_PRIVATE void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p){ + if( p ){ + assert( p->aRef[0]==1 ); + p->aRef[0] = 0; + if( p->aRef[0]==0 && p->aRef[1]==0 && p->aRef[2]==0 ){ + sqlite3_free(p); + } + } +} + +/* +** End of MatchinfoBuffer code. +*************************************************************************/ + + +/* +** This function is used to help iterate through a position-list. A position +** list is a list of unique integers, sorted from smallest to largest. Each +** element of the list is represented by an FTS3 varint that takes the value +** of the difference between the current element and the previous one plus +** two. For example, to store the position-list: +** +** 4 9 113 +** +** the three varints: +** +** 6 7 106 +** +** are encoded. +** +** When this function is called, *pp points to the start of an element of +** the list. *piPos contains the value of the previous entry in the list. +** After it returns, *piPos contains the value of the next element of the +** list and *pp is advanced to the following varint. +*/ +static void fts3GetDeltaPosition(char **pp, i64 *piPos){ + int iVal; + *pp += fts3GetVarint32(*pp, &iVal); + *piPos += (iVal-2); +} + +/* +** Helper function for sqlite3Fts3ExprIterate() (see below). +*/ +static int fts3ExprIterate2( + Fts3Expr *pExpr, /* Expression to iterate phrases of */ + int *piPhrase, /* Pointer to phrase counter */ + int (*x)(Fts3Expr*,int,void*), /* Callback function to invoke for phrases */ + void *pCtx /* Second argument to pass to callback */ +){ + int rc; /* Return code */ + int eType = pExpr->eType; /* Type of expression node pExpr */ + + if( eType!=FTSQUERY_PHRASE ){ + assert( pExpr->pLeft && pExpr->pRight ); + rc = fts3ExprIterate2(pExpr->pLeft, piPhrase, x, pCtx); + if( rc==SQLITE_OK && eType!=FTSQUERY_NOT ){ + rc = fts3ExprIterate2(pExpr->pRight, piPhrase, x, pCtx); + } + }else{ + rc = x(pExpr, *piPhrase, pCtx); + (*piPhrase)++; + } + return rc; +} + +/* +** Iterate through all phrase nodes in an FTS3 query, except those that +** are part of a sub-tree that is the right-hand-side of a NOT operator. +** For each phrase node found, the supplied callback function is invoked. +** +** If the callback function returns anything other than SQLITE_OK, +** the iteration is abandoned and the error code returned immediately. +** Otherwise, SQLITE_OK is returned after a callback has been made for +** all eligible phrase nodes. +*/ +SQLITE_PRIVATE int sqlite3Fts3ExprIterate( + Fts3Expr *pExpr, /* Expression to iterate phrases of */ + int (*x)(Fts3Expr*,int,void*), /* Callback function to invoke for phrases */ + void *pCtx /* Second argument to pass to callback */ +){ + int iPhrase = 0; /* Variable used as the phrase counter */ + return fts3ExprIterate2(pExpr, &iPhrase, x, pCtx); +} + +/* +** This is an sqlite3Fts3ExprIterate() callback used while loading the +** doclists for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also +** fts3ExprLoadDoclists(). +*/ +static int fts3ExprLoadDoclistsCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ + int rc = SQLITE_OK; + Fts3Phrase *pPhrase = pExpr->pPhrase; + LoadDoclistCtx *p = (LoadDoclistCtx *)ctx; + + UNUSED_PARAMETER(iPhrase); + + p->nPhrase++; + p->nToken += pPhrase->nToken; + + return rc; +} + +/* +** Load the doclists for each phrase in the query associated with FTS3 cursor +** pCsr. +** +** If pnPhrase is not NULL, then *pnPhrase is set to the number of matchable +** phrases in the expression (all phrases except those directly or +** indirectly descended from the right-hand-side of a NOT operator). If +** pnToken is not NULL, then it is set to the number of tokens in all +** matchable phrases of the expression. +*/ +static int fts3ExprLoadDoclists( + Fts3Cursor *pCsr, /* Fts3 cursor for current query */ + int *pnPhrase, /* OUT: Number of phrases in query */ + int *pnToken /* OUT: Number of tokens in query */ +){ + int rc; /* Return Code */ + LoadDoclistCtx sCtx = {0,0,0}; /* Context for sqlite3Fts3ExprIterate() */ + sCtx.pCsr = pCsr; + rc = sqlite3Fts3ExprIterate(pCsr->pExpr,fts3ExprLoadDoclistsCb,(void*)&sCtx); + if( pnPhrase ) *pnPhrase = sCtx.nPhrase; + if( pnToken ) *pnToken = sCtx.nToken; + return rc; +} + +static int fts3ExprPhraseCountCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ + (*(int *)ctx)++; + pExpr->iPhrase = iPhrase; + return SQLITE_OK; +} +static int fts3ExprPhraseCount(Fts3Expr *pExpr){ + int nPhrase = 0; + (void)sqlite3Fts3ExprIterate(pExpr, fts3ExprPhraseCountCb, (void *)&nPhrase); + return nPhrase; +} + +/* +** Advance the position list iterator specified by the first two +** arguments so that it points to the first element with a value greater +** than or equal to parameter iNext. +*/ +static void fts3SnippetAdvance(char **ppIter, i64 *piIter, int iNext){ + char *pIter = *ppIter; + if( pIter ){ + i64 iIter = *piIter; + + while( iIter<iNext ){ + if( 0==(*pIter & 0xFE) ){ + iIter = -1; + pIter = 0; + break; + } + fts3GetDeltaPosition(&pIter, &iIter); + } + + *piIter = iIter; + *ppIter = pIter; + } +} + +/* +** Advance the snippet iterator to the next candidate snippet. +*/ +static int fts3SnippetNextCandidate(SnippetIter *pIter){ + int i; /* Loop counter */ + + if( pIter->iCurrent<0 ){ + /* The SnippetIter object has just been initialized. The first snippet + ** candidate always starts at offset 0 (even if this candidate has a + ** score of 0.0). + */ + pIter->iCurrent = 0; + + /* Advance the 'head' iterator of each phrase to the first offset that + ** is greater than or equal to (iNext+nSnippet). + */ + for(i=0; i<pIter->nPhrase; i++){ + SnippetPhrase *pPhrase = &pIter->aPhrase[i]; + fts3SnippetAdvance(&pPhrase->pHead, &pPhrase->iHead, pIter->nSnippet); + } + }else{ + int iStart; + int iEnd = 0x7FFFFFFF; + + for(i=0; i<pIter->nPhrase; i++){ + SnippetPhrase *pPhrase = &pIter->aPhrase[i]; + if( pPhrase->pHead && pPhrase->iHead<iEnd ){ + iEnd = pPhrase->iHead; + } + } + if( iEnd==0x7FFFFFFF ){ + return 1; + } + + pIter->iCurrent = iStart = iEnd - pIter->nSnippet + 1; + for(i=0; i<pIter->nPhrase; i++){ + SnippetPhrase *pPhrase = &pIter->aPhrase[i]; + fts3SnippetAdvance(&pPhrase->pHead, &pPhrase->iHead, iEnd+1); + fts3SnippetAdvance(&pPhrase->pTail, &pPhrase->iTail, iStart); + } + } + + return 0; +} + +/* +** Retrieve information about the current candidate snippet of snippet +** iterator pIter. +*/ +static void fts3SnippetDetails( + SnippetIter *pIter, /* Snippet iterator */ + u64 mCovered, /* Bitmask of phrases already covered */ + int *piToken, /* OUT: First token of proposed snippet */ + int *piScore, /* OUT: "Score" for this snippet */ + u64 *pmCover, /* OUT: Bitmask of phrases covered */ + u64 *pmHighlight /* OUT: Bitmask of terms to highlight */ +){ + int iStart = pIter->iCurrent; /* First token of snippet */ + int iScore = 0; /* Score of this snippet */ + int i; /* Loop counter */ + u64 mCover = 0; /* Mask of phrases covered by this snippet */ + u64 mHighlight = 0; /* Mask of tokens to highlight in snippet */ + + for(i=0; i<pIter->nPhrase; i++){ + SnippetPhrase *pPhrase = &pIter->aPhrase[i]; + if( pPhrase->pTail ){ + char *pCsr = pPhrase->pTail; + i64 iCsr = pPhrase->iTail; + + while( iCsr<(iStart+pIter->nSnippet) && iCsr>=iStart ){ + int j; + u64 mPhrase = (u64)1 << (i%64); + u64 mPos = (u64)1 << (iCsr - iStart); + assert( iCsr>=iStart && (iCsr - iStart)<=64 ); + assert( i>=0 ); + if( (mCover|mCovered)&mPhrase ){ + iScore++; + }else{ + iScore += 1000; + } + mCover |= mPhrase; + + for(j=0; j<pPhrase->nToken; j++){ + mHighlight |= (mPos>>j); + } + + if( 0==(*pCsr & 0x0FE) ) break; + fts3GetDeltaPosition(&pCsr, &iCsr); + } + } + } + + /* Set the output variables before returning. */ + *piToken = iStart; + *piScore = iScore; + *pmCover = mCover; + *pmHighlight = mHighlight; +} + +/* +** This function is an sqlite3Fts3ExprIterate() callback used by +** fts3BestSnippet(). Each invocation populates an element of the +** SnippetIter.aPhrase[] array. +*/ +static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){ + SnippetIter *p = (SnippetIter *)ctx; + SnippetPhrase *pPhrase = &p->aPhrase[iPhrase]; + char *pCsr; + int rc; + + pPhrase->nToken = pExpr->pPhrase->nToken; + rc = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pCsr); + assert( rc==SQLITE_OK || pCsr==0 ); + if( pCsr ){ + i64 iFirst = 0; + pPhrase->pList = pCsr; + fts3GetDeltaPosition(&pCsr, &iFirst); + if( iFirst<0 ){ + rc = FTS_CORRUPT_VTAB; + }else{ + pPhrase->pHead = pCsr; + pPhrase->pTail = pCsr; + pPhrase->iHead = iFirst; + pPhrase->iTail = iFirst; + } + }else{ + assert( rc!=SQLITE_OK || ( + pPhrase->pList==0 && pPhrase->pHead==0 && pPhrase->pTail==0 + )); + } + + return rc; +} + +/* +** Select the fragment of text consisting of nFragment contiguous tokens +** from column iCol that represent the "best" snippet. The best snippet +** is the snippet with the highest score, where scores are calculated +** by adding: +** +** (a) +1 point for each occurrence of a matchable phrase in the snippet. +** +** (b) +1000 points for the first occurrence of each matchable phrase in +** the snippet for which the corresponding mCovered bit is not set. +** +** The selected snippet parameters are stored in structure *pFragment before +** returning. The score of the selected snippet is stored in *piScore +** before returning. +*/ +static int fts3BestSnippet( + int nSnippet, /* Desired snippet length */ + Fts3Cursor *pCsr, /* Cursor to create snippet for */ + int iCol, /* Index of column to create snippet from */ + u64 mCovered, /* Mask of phrases already covered */ + u64 *pmSeen, /* IN/OUT: Mask of phrases seen */ + SnippetFragment *pFragment, /* OUT: Best snippet found */ + int *piScore /* OUT: Score of snippet pFragment */ +){ + int rc; /* Return Code */ + int nList; /* Number of phrases in expression */ + SnippetIter sIter; /* Iterates through snippet candidates */ + sqlite3_int64 nByte; /* Number of bytes of space to allocate */ + int iBestScore = -1; /* Best snippet score found so far */ + int i; /* Loop counter */ + + memset(&sIter, 0, sizeof(sIter)); + + /* Iterate through the phrases in the expression to count them. The same + ** callback makes sure the doclists are loaded for each phrase. + */ + rc = fts3ExprLoadDoclists(pCsr, &nList, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + + /* Now that it is known how many phrases there are, allocate and zero + ** the required space using malloc(). + */ + nByte = sizeof(SnippetPhrase) * nList; + sIter.aPhrase = (SnippetPhrase *)sqlite3Fts3MallocZero(nByte); + if( !sIter.aPhrase ){ + return SQLITE_NOMEM; + } + + /* Initialize the contents of the SnippetIter object. Then iterate through + ** the set of phrases in the expression to populate the aPhrase[] array. + */ + sIter.pCsr = pCsr; + sIter.iCol = iCol; + sIter.nSnippet = nSnippet; + sIter.nPhrase = nList; + sIter.iCurrent = -1; + rc = sqlite3Fts3ExprIterate( + pCsr->pExpr, fts3SnippetFindPositions, (void*)&sIter + ); + if( rc==SQLITE_OK ){ + + /* Set the *pmSeen output variable. */ + for(i=0; i<nList; i++){ + if( sIter.aPhrase[i].pHead ){ + *pmSeen |= (u64)1 << (i%64); + } + } + + /* Loop through all candidate snippets. Store the best snippet in + ** *pFragment. Store its associated 'score' in iBestScore. + */ + pFragment->iCol = iCol; + while( !fts3SnippetNextCandidate(&sIter) ){ + int iPos; + int iScore; + u64 mCover; + u64 mHighlite; + fts3SnippetDetails(&sIter, mCovered, &iPos, &iScore, &mCover,&mHighlite); + assert( iScore>=0 ); + if( iScore>iBestScore ){ + pFragment->iPos = iPos; + pFragment->hlmask = mHighlite; + pFragment->covered = mCover; + iBestScore = iScore; + } + } + + *piScore = iBestScore; + } + sqlite3_free(sIter.aPhrase); + return rc; +} + + +/* +** Append a string to the string-buffer passed as the first argument. +** +** If nAppend is negative, then the length of the string zAppend is +** determined using strlen(). +*/ +static int fts3StringAppend( + StrBuffer *pStr, /* Buffer to append to */ + const char *zAppend, /* Pointer to data to append to buffer */ + int nAppend /* Size of zAppend in bytes (or -1) */ +){ + if( nAppend<0 ){ + nAppend = (int)strlen(zAppend); + } + + /* If there is insufficient space allocated at StrBuffer.z, use realloc() + ** to grow the buffer until so that it is big enough to accomadate the + ** appended data. + */ + if( pStr->n+nAppend+1>=pStr->nAlloc ){ + sqlite3_int64 nAlloc = pStr->nAlloc+(sqlite3_int64)nAppend+100; + char *zNew = sqlite3_realloc64(pStr->z, nAlloc); + if( !zNew ){ + return SQLITE_NOMEM; + } + pStr->z = zNew; + pStr->nAlloc = nAlloc; + } + assert( pStr->z!=0 && (pStr->nAlloc >= pStr->n+nAppend+1) ); + + /* Append the data to the string buffer. */ + memcpy(&pStr->z[pStr->n], zAppend, nAppend); + pStr->n += nAppend; + pStr->z[pStr->n] = '\0'; + + return SQLITE_OK; +} + +/* +** The fts3BestSnippet() function often selects snippets that end with a +** query term. That is, the final term of the snippet is always a term +** that requires highlighting. For example, if 'X' is a highlighted term +** and '.' is a non-highlighted term, BestSnippet() may select: +** +** ........X.....X +** +** This function "shifts" the beginning of the snippet forward in the +** document so that there are approximately the same number of +** non-highlighted terms to the right of the final highlighted term as there +** are to the left of the first highlighted term. For example, to this: +** +** ....X.....X.... +** +** This is done as part of extracting the snippet text, not when selecting +** the snippet. Snippet selection is done based on doclists only, so there +** is no way for fts3BestSnippet() to know whether or not the document +** actually contains terms that follow the final highlighted term. +*/ +static int fts3SnippetShift( + Fts3Table *pTab, /* FTS3 table snippet comes from */ + int iLangid, /* Language id to use in tokenizing */ + int nSnippet, /* Number of tokens desired for snippet */ + const char *zDoc, /* Document text to extract snippet from */ + int nDoc, /* Size of buffer zDoc in bytes */ + int *piPos, /* IN/OUT: First token of snippet */ + u64 *pHlmask /* IN/OUT: Mask of tokens to highlight */ +){ + u64 hlmask = *pHlmask; /* Local copy of initial highlight-mask */ + + if( hlmask ){ + int nLeft; /* Tokens to the left of first highlight */ + int nRight; /* Tokens to the right of last highlight */ + int nDesired; /* Ideal number of tokens to shift forward */ + + for(nLeft=0; !(hlmask & ((u64)1 << nLeft)); nLeft++); + for(nRight=0; !(hlmask & ((u64)1 << (nSnippet-1-nRight))); nRight++); + assert( (nSnippet-1-nRight)<=63 && (nSnippet-1-nRight)>=0 ); + nDesired = (nLeft-nRight)/2; + + /* Ideally, the start of the snippet should be pushed forward in the + ** document nDesired tokens. This block checks if there are actually + ** nDesired tokens to the right of the snippet. If so, *piPos and + ** *pHlMask are updated to shift the snippet nDesired tokens to the + ** right. Otherwise, the snippet is shifted by the number of tokens + ** available. + */ + if( nDesired>0 ){ + int nShift; /* Number of tokens to shift snippet by */ + int iCurrent = 0; /* Token counter */ + int rc; /* Return Code */ + sqlite3_tokenizer_module *pMod; + sqlite3_tokenizer_cursor *pC; + pMod = (sqlite3_tokenizer_module *)pTab->pTokenizer->pModule; + + /* Open a cursor on zDoc/nDoc. Check if there are (nSnippet+nDesired) + ** or more tokens in zDoc/nDoc. + */ + rc = sqlite3Fts3OpenTokenizer(pTab->pTokenizer, iLangid, zDoc, nDoc, &pC); + if( rc!=SQLITE_OK ){ + return rc; + } + while( rc==SQLITE_OK && iCurrent<(nSnippet+nDesired) ){ + const char *ZDUMMY; int DUMMY1 = 0, DUMMY2 = 0, DUMMY3 = 0; + rc = pMod->xNext(pC, &ZDUMMY, &DUMMY1, &DUMMY2, &DUMMY3, &iCurrent); + } + pMod->xClose(pC); + if( rc!=SQLITE_OK && rc!=SQLITE_DONE ){ return rc; } + + nShift = (rc==SQLITE_DONE)+iCurrent-nSnippet; + assert( nShift<=nDesired ); + if( nShift>0 ){ + *piPos += nShift; + *pHlmask = hlmask >> nShift; + } + } + } + return SQLITE_OK; +} + +/* +** Extract the snippet text for fragment pFragment from cursor pCsr and +** append it to string buffer pOut. +*/ +static int fts3SnippetText( + Fts3Cursor *pCsr, /* FTS3 Cursor */ + SnippetFragment *pFragment, /* Snippet to extract */ + int iFragment, /* Fragment number */ + int isLast, /* True for final fragment in snippet */ + int nSnippet, /* Number of tokens in extracted snippet */ + const char *zOpen, /* String inserted before highlighted term */ + const char *zClose, /* String inserted after highlighted term */ + const char *zEllipsis, /* String inserted between snippets */ + StrBuffer *pOut /* Write output here */ +){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + int rc; /* Return code */ + const char *zDoc; /* Document text to extract snippet from */ + int nDoc; /* Size of zDoc in bytes */ + int iCurrent = 0; /* Current token number of document */ + int iEnd = 0; /* Byte offset of end of current token */ + int isShiftDone = 0; /* True after snippet is shifted */ + int iPos = pFragment->iPos; /* First token of snippet */ + u64 hlmask = pFragment->hlmask; /* Highlight-mask for snippet */ + int iCol = pFragment->iCol+1; /* Query column to extract text from */ + sqlite3_tokenizer_module *pMod; /* Tokenizer module methods object */ + sqlite3_tokenizer_cursor *pC; /* Tokenizer cursor open on zDoc/nDoc */ + + zDoc = (const char *)sqlite3_column_text(pCsr->pStmt, iCol); + if( zDoc==0 ){ + if( sqlite3_column_type(pCsr->pStmt, iCol)!=SQLITE_NULL ){ + return SQLITE_NOMEM; + } + return SQLITE_OK; + } + nDoc = sqlite3_column_bytes(pCsr->pStmt, iCol); + + /* Open a token cursor on the document. */ + pMod = (sqlite3_tokenizer_module *)pTab->pTokenizer->pModule; + rc = sqlite3Fts3OpenTokenizer(pTab->pTokenizer, pCsr->iLangid, zDoc,nDoc,&pC); + if( rc!=SQLITE_OK ){ + return rc; + } + + while( rc==SQLITE_OK ){ + const char *ZDUMMY; /* Dummy argument used with tokenizer */ + int DUMMY1 = -1; /* Dummy argument used with tokenizer */ + int iBegin = 0; /* Offset in zDoc of start of token */ + int iFin = 0; /* Offset in zDoc of end of token */ + int isHighlight = 0; /* True for highlighted terms */ + + /* Variable DUMMY1 is initialized to a negative value above. Elsewhere + ** in the FTS code the variable that the third argument to xNext points to + ** is initialized to zero before the first (*but not necessarily + ** subsequent*) call to xNext(). This is done for a particular application + ** that needs to know whether or not the tokenizer is being used for + ** snippet generation or for some other purpose. + ** + ** Extreme care is required when writing code to depend on this + ** initialization. It is not a documented part of the tokenizer interface. + ** If a tokenizer is used directly by any code outside of FTS, this + ** convention might not be respected. */ + rc = pMod->xNext(pC, &ZDUMMY, &DUMMY1, &iBegin, &iFin, &iCurrent); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_DONE ){ + /* Special case - the last token of the snippet is also the last token + ** of the column. Append any punctuation that occurred between the end + ** of the previous token and the end of the document to the output. + ** Then break out of the loop. */ + rc = fts3StringAppend(pOut, &zDoc[iEnd], -1); + } + break; + } + if( iCurrent<iPos ){ continue; } + + if( !isShiftDone ){ + int n = nDoc - iBegin; + rc = fts3SnippetShift( + pTab, pCsr->iLangid, nSnippet, &zDoc[iBegin], n, &iPos, &hlmask + ); + isShiftDone = 1; + + /* Now that the shift has been done, check if the initial "..." are + ** required. They are required if (a) this is not the first fragment, + ** or (b) this fragment does not begin at position 0 of its column. + */ + if( rc==SQLITE_OK ){ + if( iPos>0 || iFragment>0 ){ + rc = fts3StringAppend(pOut, zEllipsis, -1); + }else if( iBegin ){ + rc = fts3StringAppend(pOut, zDoc, iBegin); + } + } + if( rc!=SQLITE_OK || iCurrent<iPos ) continue; + } + + if( iCurrent>=(iPos+nSnippet) ){ + if( isLast ){ + rc = fts3StringAppend(pOut, zEllipsis, -1); + } + break; + } + + /* Set isHighlight to true if this term should be highlighted. */ + isHighlight = (hlmask & ((u64)1 << (iCurrent-iPos)))!=0; + + if( iCurrent>iPos ) rc = fts3StringAppend(pOut, &zDoc[iEnd], iBegin-iEnd); + if( rc==SQLITE_OK && isHighlight ) rc = fts3StringAppend(pOut, zOpen, -1); + if( rc==SQLITE_OK ) rc = fts3StringAppend(pOut, &zDoc[iBegin], iFin-iBegin); + if( rc==SQLITE_OK && isHighlight ) rc = fts3StringAppend(pOut, zClose, -1); + + iEnd = iFin; + } + + pMod->xClose(pC); + return rc; +} + + +/* +** This function is used to count the entries in a column-list (a +** delta-encoded list of term offsets within a single column of a single +** row). When this function is called, *ppCollist should point to the +** beginning of the first varint in the column-list (the varint that +** contains the position of the first matching term in the column data). +** Before returning, *ppCollist is set to point to the first byte after +** the last varint in the column-list (either the 0x00 signifying the end +** of the position-list, or the 0x01 that precedes the column number of +** the next column in the position-list). +** +** The number of elements in the column-list is returned. +*/ +static int fts3ColumnlistCount(char **ppCollist){ + char *pEnd = *ppCollist; + char c = 0; + int nEntry = 0; + + /* A column-list is terminated by either a 0x01 or 0x00. */ + while( 0xFE & (*pEnd | c) ){ + c = *pEnd++ & 0x80; + if( !c ) nEntry++; + } + + *ppCollist = pEnd; + return nEntry; +} + +/* +** This function gathers 'y' or 'b' data for a single phrase. +*/ +static int fts3ExprLHits( + Fts3Expr *pExpr, /* Phrase expression node */ + MatchInfo *p /* Matchinfo context */ +){ + Fts3Table *pTab = (Fts3Table *)p->pCursor->base.pVtab; + int iStart; + Fts3Phrase *pPhrase = pExpr->pPhrase; + char *pIter = pPhrase->doclist.pList; + int iCol = 0; + + assert( p->flag==FTS3_MATCHINFO_LHITS_BM || p->flag==FTS3_MATCHINFO_LHITS ); + if( p->flag==FTS3_MATCHINFO_LHITS ){ + iStart = pExpr->iPhrase * p->nCol; + }else{ + iStart = pExpr->iPhrase * ((p->nCol + 31) / 32); + } + + if( pIter ) while( 1 ){ + int nHit = fts3ColumnlistCount(&pIter); + if( (pPhrase->iColumn>=pTab->nColumn || pPhrase->iColumn==iCol) ){ + if( p->flag==FTS3_MATCHINFO_LHITS ){ + p->aMatchinfo[iStart + iCol] = (u32)nHit; + }else if( nHit ){ + p->aMatchinfo[iStart + (iCol+1)/32] |= (1 << (iCol&0x1F)); + } + } + assert( *pIter==0x00 || *pIter==0x01 ); + if( *pIter!=0x01 ) break; + pIter++; + pIter += fts3GetVarint32(pIter, &iCol); + if( iCol>=p->nCol ) return FTS_CORRUPT_VTAB; + } + return SQLITE_OK; +} + +/* +** Gather the results for matchinfo directives 'y' and 'b'. +*/ +static int fts3ExprLHitGather( + Fts3Expr *pExpr, + MatchInfo *p +){ + int rc = SQLITE_OK; + assert( (pExpr->pLeft==0)==(pExpr->pRight==0) ); + if( pExpr->bEof==0 && pExpr->iDocid==p->pCursor->iPrevId ){ + if( pExpr->pLeft ){ + rc = fts3ExprLHitGather(pExpr->pLeft, p); + if( rc==SQLITE_OK ) rc = fts3ExprLHitGather(pExpr->pRight, p); + }else{ + rc = fts3ExprLHits(pExpr, p); + } + } + return rc; +} + +/* +** sqlite3Fts3ExprIterate() callback used to collect the "global" matchinfo +** stats for a single query. +** +** sqlite3Fts3ExprIterate() callback to load the 'global' elements of a +** FTS3_MATCHINFO_HITS matchinfo array. The global stats are those elements +** of the matchinfo array that are constant for all rows returned by the +** current query. +** +** Argument pCtx is actually a pointer to a struct of type MatchInfo. This +** function populates Matchinfo.aMatchinfo[] as follows: +** +** for(iCol=0; iCol<nCol; iCol++){ +** aMatchinfo[3*iPhrase*nCol + 3*iCol + 1] = X; +** aMatchinfo[3*iPhrase*nCol + 3*iCol + 2] = Y; +** } +** +** where X is the number of matches for phrase iPhrase is column iCol of all +** rows of the table. Y is the number of rows for which column iCol contains +** at least one instance of phrase iPhrase. +** +** If the phrase pExpr consists entirely of deferred tokens, then all X and +** Y values are set to nDoc, where nDoc is the number of documents in the +** file system. This is done because the full-text index doclist is required +** to calculate these values properly, and the full-text index doclist is +** not available for deferred tokens. +*/ +static int fts3ExprGlobalHitsCb( + Fts3Expr *pExpr, /* Phrase expression node */ + int iPhrase, /* Phrase number (numbered from zero) */ + void *pCtx /* Pointer to MatchInfo structure */ +){ + MatchInfo *p = (MatchInfo *)pCtx; + return sqlite3Fts3EvalPhraseStats( + p->pCursor, pExpr, &p->aMatchinfo[3*iPhrase*p->nCol] + ); +} + +/* +** sqlite3Fts3ExprIterate() callback used to collect the "local" part of the +** FTS3_MATCHINFO_HITS array. The local stats are those elements of the +** array that are different for each row returned by the query. +*/ +static int fts3ExprLocalHitsCb( + Fts3Expr *pExpr, /* Phrase expression node */ + int iPhrase, /* Phrase number */ + void *pCtx /* Pointer to MatchInfo structure */ +){ + int rc = SQLITE_OK; + MatchInfo *p = (MatchInfo *)pCtx; + int iStart = iPhrase * p->nCol * 3; + int i; + + for(i=0; i<p->nCol && rc==SQLITE_OK; i++){ + char *pCsr; + rc = sqlite3Fts3EvalPhrasePoslist(p->pCursor, pExpr, i, &pCsr); + if( pCsr ){ + p->aMatchinfo[iStart+i*3] = fts3ColumnlistCount(&pCsr); + }else{ + p->aMatchinfo[iStart+i*3] = 0; + } + } + + return rc; +} + +static int fts3MatchinfoCheck( + Fts3Table *pTab, + char cArg, + char **pzErr +){ + if( (cArg==FTS3_MATCHINFO_NPHRASE) + || (cArg==FTS3_MATCHINFO_NCOL) + || (cArg==FTS3_MATCHINFO_NDOC && pTab->bFts4) + || (cArg==FTS3_MATCHINFO_AVGLENGTH && pTab->bFts4) + || (cArg==FTS3_MATCHINFO_LENGTH && pTab->bHasDocsize) + || (cArg==FTS3_MATCHINFO_LCS) + || (cArg==FTS3_MATCHINFO_HITS) + || (cArg==FTS3_MATCHINFO_LHITS) + || (cArg==FTS3_MATCHINFO_LHITS_BM) + ){ + return SQLITE_OK; + } + sqlite3Fts3ErrMsg(pzErr, "unrecognized matchinfo request: %c", cArg); + return SQLITE_ERROR; +} + +static size_t fts3MatchinfoSize(MatchInfo *pInfo, char cArg){ + size_t nVal; /* Number of integers output by cArg */ + + switch( cArg ){ + case FTS3_MATCHINFO_NDOC: + case FTS3_MATCHINFO_NPHRASE: + case FTS3_MATCHINFO_NCOL: + nVal = 1; + break; + + case FTS3_MATCHINFO_AVGLENGTH: + case FTS3_MATCHINFO_LENGTH: + case FTS3_MATCHINFO_LCS: + nVal = pInfo->nCol; + break; + + case FTS3_MATCHINFO_LHITS: + nVal = pInfo->nCol * pInfo->nPhrase; + break; + + case FTS3_MATCHINFO_LHITS_BM: + nVal = pInfo->nPhrase * ((pInfo->nCol + 31) / 32); + break; + + default: + assert( cArg==FTS3_MATCHINFO_HITS ); + nVal = pInfo->nCol * pInfo->nPhrase * 3; + break; + } + + return nVal; +} + +static int fts3MatchinfoSelectDoctotal( + Fts3Table *pTab, + sqlite3_stmt **ppStmt, + sqlite3_int64 *pnDoc, + const char **paLen, + const char **ppEnd +){ + sqlite3_stmt *pStmt; + const char *a; + const char *pEnd; + sqlite3_int64 nDoc; + int n; + + + if( !*ppStmt ){ + int rc = sqlite3Fts3SelectDoctotal(pTab, ppStmt); + if( rc!=SQLITE_OK ) return rc; + } + pStmt = *ppStmt; + assert( sqlite3_data_count(pStmt)==1 ); + + n = sqlite3_column_bytes(pStmt, 0); + a = sqlite3_column_blob(pStmt, 0); + if( a==0 ){ + return FTS_CORRUPT_VTAB; + } + pEnd = a + n; + a += sqlite3Fts3GetVarintBounded(a, pEnd, &nDoc); + if( nDoc<=0 || a>pEnd ){ + return FTS_CORRUPT_VTAB; + } + *pnDoc = nDoc; + + if( paLen ) *paLen = a; + if( ppEnd ) *ppEnd = pEnd; + return SQLITE_OK; +} + +/* +** An instance of the following structure is used to store state while +** iterating through a multi-column position-list corresponding to the +** hits for a single phrase on a single row in order to calculate the +** values for a matchinfo() FTS3_MATCHINFO_LCS request. +*/ +typedef struct LcsIterator LcsIterator; +struct LcsIterator { + Fts3Expr *pExpr; /* Pointer to phrase expression */ + int iPosOffset; /* Tokens count up to end of this phrase */ + char *pRead; /* Cursor used to iterate through aDoclist */ + int iPos; /* Current position */ +}; + +/* +** If LcsIterator.iCol is set to the following value, the iterator has +** finished iterating through all offsets for all columns. +*/ +#define LCS_ITERATOR_FINISHED 0x7FFFFFFF; + +static int fts3MatchinfoLcsCb( + Fts3Expr *pExpr, /* Phrase expression node */ + int iPhrase, /* Phrase number (numbered from zero) */ + void *pCtx /* Pointer to MatchInfo structure */ +){ + LcsIterator *aIter = (LcsIterator *)pCtx; + aIter[iPhrase].pExpr = pExpr; + return SQLITE_OK; +} + +/* +** Advance the iterator passed as an argument to the next position. Return +** 1 if the iterator is at EOF or if it now points to the start of the +** position list for the next column. +*/ +static int fts3LcsIteratorAdvance(LcsIterator *pIter){ + char *pRead; + sqlite3_int64 iRead; + int rc = 0; + + if( NEVER(pIter==0) ) return 1; + pRead = pIter->pRead; + pRead += sqlite3Fts3GetVarint(pRead, &iRead); + if( iRead==0 || iRead==1 ){ + pRead = 0; + rc = 1; + }else{ + pIter->iPos += (int)(iRead-2); + } + + pIter->pRead = pRead; + return rc; +} + +/* +** This function implements the FTS3_MATCHINFO_LCS matchinfo() flag. +** +** If the call is successful, the longest-common-substring lengths for each +** column are written into the first nCol elements of the pInfo->aMatchinfo[] +** array before returning. SQLITE_OK is returned in this case. +** +** Otherwise, if an error occurs, an SQLite error code is returned and the +** data written to the first nCol elements of pInfo->aMatchinfo[] is +** undefined. +*/ +static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){ + LcsIterator *aIter; + int i; + int iCol; + int nToken = 0; + int rc = SQLITE_OK; + + /* Allocate and populate the array of LcsIterator objects. The array + ** contains one element for each matchable phrase in the query. + **/ + aIter = sqlite3Fts3MallocZero(sizeof(LcsIterator) * pCsr->nPhrase); + if( !aIter ) return SQLITE_NOMEM; + (void)sqlite3Fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter); + + for(i=0; i<pInfo->nPhrase; i++){ + LcsIterator *pIter = &aIter[i]; + nToken -= pIter->pExpr->pPhrase->nToken; + pIter->iPosOffset = nToken; + } + + for(iCol=0; iCol<pInfo->nCol; iCol++){ + int nLcs = 0; /* LCS value for this column */ + int nLive = 0; /* Number of iterators in aIter not at EOF */ + + for(i=0; i<pInfo->nPhrase; i++){ + LcsIterator *pIt = &aIter[i]; + rc = sqlite3Fts3EvalPhrasePoslist(pCsr, pIt->pExpr, iCol, &pIt->pRead); + if( rc!=SQLITE_OK ) goto matchinfo_lcs_out; + if( pIt->pRead ){ + pIt->iPos = pIt->iPosOffset; + fts3LcsIteratorAdvance(pIt); + if( pIt->pRead==0 ){ + rc = FTS_CORRUPT_VTAB; + goto matchinfo_lcs_out; + } + nLive++; + } + } + + while( nLive>0 ){ + LcsIterator *pAdv = 0; /* The iterator to advance by one position */ + int nThisLcs = 0; /* LCS for the current iterator positions */ + + for(i=0; i<pInfo->nPhrase; i++){ + LcsIterator *pIter = &aIter[i]; + if( pIter->pRead==0 ){ + /* This iterator is already at EOF for this column. */ + nThisLcs = 0; + }else{ + if( pAdv==0 || pIter->iPos<pAdv->iPos ){ + pAdv = pIter; + } + if( nThisLcs==0 || pIter->iPos==pIter[-1].iPos ){ + nThisLcs++; + }else{ + nThisLcs = 1; + } + if( nThisLcs>nLcs ) nLcs = nThisLcs; + } + } + if( fts3LcsIteratorAdvance(pAdv) ) nLive--; + } + + pInfo->aMatchinfo[iCol] = nLcs; + } + + matchinfo_lcs_out: + sqlite3_free(aIter); + return rc; +} + +/* +** Populate the buffer pInfo->aMatchinfo[] with an array of integers to +** be returned by the matchinfo() function. Argument zArg contains the +** format string passed as the second argument to matchinfo (or the +** default value "pcx" if no second argument was specified). The format +** string has already been validated and the pInfo->aMatchinfo[] array +** is guaranteed to be large enough for the output. +** +** If bGlobal is true, then populate all fields of the matchinfo() output. +** If it is false, then assume that those fields that do not change between +** rows (i.e. FTS3_MATCHINFO_NPHRASE, NCOL, NDOC, AVGLENGTH and part of HITS) +** have already been populated. +** +** Return SQLITE_OK if successful, or an SQLite error code if an error +** occurs. If a value other than SQLITE_OK is returned, the state the +** pInfo->aMatchinfo[] buffer is left in is undefined. +*/ +static int fts3MatchinfoValues( + Fts3Cursor *pCsr, /* FTS3 cursor object */ + int bGlobal, /* True to grab the global stats */ + MatchInfo *pInfo, /* Matchinfo context object */ + const char *zArg /* Matchinfo format string */ +){ + int rc = SQLITE_OK; + int i; + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + sqlite3_stmt *pSelect = 0; + + for(i=0; rc==SQLITE_OK && zArg[i]; i++){ + pInfo->flag = zArg[i]; + switch( zArg[i] ){ + case FTS3_MATCHINFO_NPHRASE: + if( bGlobal ) pInfo->aMatchinfo[0] = pInfo->nPhrase; + break; + + case FTS3_MATCHINFO_NCOL: + if( bGlobal ) pInfo->aMatchinfo[0] = pInfo->nCol; + break; + + case FTS3_MATCHINFO_NDOC: + if( bGlobal ){ + sqlite3_int64 nDoc = 0; + rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, 0, 0); + pInfo->aMatchinfo[0] = (u32)nDoc; + } + break; + + case FTS3_MATCHINFO_AVGLENGTH: + if( bGlobal ){ + sqlite3_int64 nDoc; /* Number of rows in table */ + const char *a; /* Aggregate column length array */ + const char *pEnd; /* First byte past end of length array */ + + rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, &a, &pEnd); + if( rc==SQLITE_OK ){ + int iCol; + for(iCol=0; iCol<pInfo->nCol; iCol++){ + u32 iVal; + sqlite3_int64 nToken; + a += sqlite3Fts3GetVarint(a, &nToken); + if( a>pEnd ){ + rc = SQLITE_CORRUPT_VTAB; + break; + } + iVal = (u32)(((u32)(nToken&0xffffffff)+nDoc/2)/nDoc); + pInfo->aMatchinfo[iCol] = iVal; + } + } + } + break; + + case FTS3_MATCHINFO_LENGTH: { + sqlite3_stmt *pSelectDocsize = 0; + rc = sqlite3Fts3SelectDocsize(pTab, pCsr->iPrevId, &pSelectDocsize); + if( rc==SQLITE_OK ){ + int iCol; + const char *a = sqlite3_column_blob(pSelectDocsize, 0); + const char *pEnd = a + sqlite3_column_bytes(pSelectDocsize, 0); + for(iCol=0; iCol<pInfo->nCol; iCol++){ + sqlite3_int64 nToken; + a += sqlite3Fts3GetVarintBounded(a, pEnd, &nToken); + if( a>pEnd ){ + rc = SQLITE_CORRUPT_VTAB; + break; + } + pInfo->aMatchinfo[iCol] = (u32)nToken; + } + } + sqlite3_reset(pSelectDocsize); + break; + } + + case FTS3_MATCHINFO_LCS: + rc = fts3ExprLoadDoclists(pCsr, 0, 0); + if( rc==SQLITE_OK ){ + rc = fts3MatchinfoLcs(pCsr, pInfo); + } + break; + + case FTS3_MATCHINFO_LHITS_BM: + case FTS3_MATCHINFO_LHITS: { + size_t nZero = fts3MatchinfoSize(pInfo, zArg[i]) * sizeof(u32); + memset(pInfo->aMatchinfo, 0, nZero); + rc = fts3ExprLHitGather(pCsr->pExpr, pInfo); + break; + } + + default: { + Fts3Expr *pExpr; + assert( zArg[i]==FTS3_MATCHINFO_HITS ); + pExpr = pCsr->pExpr; + rc = fts3ExprLoadDoclists(pCsr, 0, 0); + if( rc!=SQLITE_OK ) break; + if( bGlobal ){ + if( pCsr->pDeferred ){ + rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &pInfo->nDoc,0,0); + if( rc!=SQLITE_OK ) break; + } + rc = sqlite3Fts3ExprIterate(pExpr, fts3ExprGlobalHitsCb,(void*)pInfo); + sqlite3Fts3EvalTestDeferred(pCsr, &rc); + if( rc!=SQLITE_OK ) break; + } + (void)sqlite3Fts3ExprIterate(pExpr, fts3ExprLocalHitsCb,(void*)pInfo); + break; + } + } + + pInfo->aMatchinfo += fts3MatchinfoSize(pInfo, zArg[i]); + } + + sqlite3_reset(pSelect); + return rc; +} + + +/* +** Populate pCsr->aMatchinfo[] with data for the current row. The +** 'matchinfo' data is an array of 32-bit unsigned integers (C type u32). +*/ +static void fts3GetMatchinfo( + sqlite3_context *pCtx, /* Return results here */ + Fts3Cursor *pCsr, /* FTS3 Cursor object */ + const char *zArg /* Second argument to matchinfo() function */ +){ + MatchInfo sInfo; + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + int rc = SQLITE_OK; + int bGlobal = 0; /* Collect 'global' stats as well as local */ + + u32 *aOut = 0; + void (*xDestroyOut)(void*) = 0; + + memset(&sInfo, 0, sizeof(MatchInfo)); + sInfo.pCursor = pCsr; + sInfo.nCol = pTab->nColumn; + + /* If there is cached matchinfo() data, but the format string for the + ** cache does not match the format string for this request, discard + ** the cached data. */ + if( pCsr->pMIBuffer && strcmp(pCsr->pMIBuffer->zMatchinfo, zArg) ){ + sqlite3Fts3MIBufferFree(pCsr->pMIBuffer); + pCsr->pMIBuffer = 0; + } + + /* If Fts3Cursor.pMIBuffer is NULL, then this is the first time the + ** matchinfo function has been called for this query. In this case + ** allocate the array used to accumulate the matchinfo data and + ** initialize those elements that are constant for every row. + */ + if( pCsr->pMIBuffer==0 ){ + size_t nMatchinfo = 0; /* Number of u32 elements in match-info */ + int i; /* Used to iterate through zArg */ + + /* Determine the number of phrases in the query */ + pCsr->nPhrase = fts3ExprPhraseCount(pCsr->pExpr); + sInfo.nPhrase = pCsr->nPhrase; + + /* Determine the number of integers in the buffer returned by this call. */ + for(i=0; zArg[i]; i++){ + char *zErr = 0; + if( fts3MatchinfoCheck(pTab, zArg[i], &zErr) ){ + sqlite3_result_error(pCtx, zErr, -1); + sqlite3_free(zErr); + return; + } + nMatchinfo += fts3MatchinfoSize(&sInfo, zArg[i]); + } + + /* Allocate space for Fts3Cursor.aMatchinfo[] and Fts3Cursor.zMatchinfo. */ + pCsr->pMIBuffer = fts3MIBufferNew(nMatchinfo, zArg); + if( !pCsr->pMIBuffer ) rc = SQLITE_NOMEM; + + pCsr->isMatchinfoNeeded = 1; + bGlobal = 1; + } + + if( rc==SQLITE_OK ){ + xDestroyOut = fts3MIBufferAlloc(pCsr->pMIBuffer, &aOut); + if( xDestroyOut==0 ){ + rc = SQLITE_NOMEM; + } + } + + if( rc==SQLITE_OK ){ + sInfo.aMatchinfo = aOut; + sInfo.nPhrase = pCsr->nPhrase; + rc = fts3MatchinfoValues(pCsr, bGlobal, &sInfo, zArg); + if( bGlobal ){ + fts3MIBufferSetGlobal(pCsr->pMIBuffer); + } + } + + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(pCtx, rc); + if( xDestroyOut ) xDestroyOut(aOut); + }else{ + int n = pCsr->pMIBuffer->nElem * sizeof(u32); + sqlite3_result_blob(pCtx, aOut, n, xDestroyOut); + } +} + +/* +** Implementation of snippet() function. +*/ +SQLITE_PRIVATE void sqlite3Fts3Snippet( + sqlite3_context *pCtx, /* SQLite function call context */ + Fts3Cursor *pCsr, /* Cursor object */ + const char *zStart, /* Snippet start text - "<b>" */ + const char *zEnd, /* Snippet end text - "</b>" */ + const char *zEllipsis, /* Snippet ellipsis text - "<b>...</b>" */ + int iCol, /* Extract snippet from this column */ + int nToken /* Approximate number of tokens in snippet */ +){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + int rc = SQLITE_OK; + int i; + StrBuffer res = {0, 0, 0}; + + /* The returned text includes up to four fragments of text extracted from + ** the data in the current row. The first iteration of the for(...) loop + ** below attempts to locate a single fragment of text nToken tokens in + ** size that contains at least one instance of all phrases in the query + ** expression that appear in the current row. If such a fragment of text + ** cannot be found, the second iteration of the loop attempts to locate + ** a pair of fragments, and so on. + */ + int nSnippet = 0; /* Number of fragments in this snippet */ + SnippetFragment aSnippet[4]; /* Maximum of 4 fragments per snippet */ + int nFToken = -1; /* Number of tokens in each fragment */ + + if( !pCsr->pExpr ){ + sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC); + return; + } + + /* Limit the snippet length to 64 tokens. */ + if( nToken<-64 ) nToken = -64; + if( nToken>+64 ) nToken = +64; + + for(nSnippet=1; 1; nSnippet++){ + + int iSnip; /* Loop counter 0..nSnippet-1 */ + u64 mCovered = 0; /* Bitmask of phrases covered by snippet */ + u64 mSeen = 0; /* Bitmask of phrases seen by BestSnippet() */ + + if( nToken>=0 ){ + nFToken = (nToken+nSnippet-1) / nSnippet; + }else{ + nFToken = -1 * nToken; + } + + for(iSnip=0; iSnip<nSnippet; iSnip++){ + int iBestScore = -1; /* Best score of columns checked so far */ + int iRead; /* Used to iterate through columns */ + SnippetFragment *pFragment = &aSnippet[iSnip]; + + memset(pFragment, 0, sizeof(*pFragment)); + + /* Loop through all columns of the table being considered for snippets. + ** If the iCol argument to this function was negative, this means all + ** columns of the FTS3 table. Otherwise, only column iCol is considered. + */ + for(iRead=0; iRead<pTab->nColumn; iRead++){ + SnippetFragment sF = {0, 0, 0, 0}; + int iS = 0; + if( iCol>=0 && iRead!=iCol ) continue; + + /* Find the best snippet of nFToken tokens in column iRead. */ + rc = fts3BestSnippet(nFToken, pCsr, iRead, mCovered, &mSeen, &sF, &iS); + if( rc!=SQLITE_OK ){ + goto snippet_out; + } + if( iS>iBestScore ){ + *pFragment = sF; + iBestScore = iS; + } + } + + mCovered |= pFragment->covered; + } + + /* If all query phrases seen by fts3BestSnippet() are present in at least + ** one of the nSnippet snippet fragments, break out of the loop. + */ + assert( (mCovered&mSeen)==mCovered ); + if( mSeen==mCovered || nSnippet==SizeofArray(aSnippet) ) break; + } + + assert( nFToken>0 ); + + for(i=0; i<nSnippet && rc==SQLITE_OK; i++){ + rc = fts3SnippetText(pCsr, &aSnippet[i], + i, (i==nSnippet-1), nFToken, zStart, zEnd, zEllipsis, &res + ); + } + + snippet_out: + sqlite3Fts3SegmentsClose(pTab); + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(pCtx, rc); + sqlite3_free(res.z); + }else{ + sqlite3_result_text(pCtx, res.z, -1, sqlite3_free); + } +} + + +typedef struct TermOffset TermOffset; +typedef struct TermOffsetCtx TermOffsetCtx; + +struct TermOffset { + char *pList; /* Position-list */ + i64 iPos; /* Position just read from pList */ + i64 iOff; /* Offset of this term from read positions */ +}; + +struct TermOffsetCtx { + Fts3Cursor *pCsr; + int iCol; /* Column of table to populate aTerm for */ + int iTerm; + sqlite3_int64 iDocid; + TermOffset *aTerm; +}; + +/* +** This function is an sqlite3Fts3ExprIterate() callback used by sqlite3Fts3Offsets(). +*/ +static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){ + TermOffsetCtx *p = (TermOffsetCtx *)ctx; + int nTerm; /* Number of tokens in phrase */ + int iTerm; /* For looping through nTerm phrase terms */ + char *pList; /* Pointer to position list for phrase */ + i64 iPos = 0; /* First position in position-list */ + int rc; + + UNUSED_PARAMETER(iPhrase); + rc = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pList); + nTerm = pExpr->pPhrase->nToken; + if( pList ){ + fts3GetDeltaPosition(&pList, &iPos); + assert_fts3_nc( iPos>=0 ); + } + + for(iTerm=0; iTerm<nTerm; iTerm++){ + TermOffset *pT = &p->aTerm[p->iTerm++]; + pT->iOff = nTerm-iTerm-1; + pT->pList = pList; + pT->iPos = iPos; + } + + return rc; +} + +/* +** Implementation of offsets() function. +*/ +SQLITE_PRIVATE void sqlite3Fts3Offsets( + sqlite3_context *pCtx, /* SQLite function call context */ + Fts3Cursor *pCsr /* Cursor object */ +){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + sqlite3_tokenizer_module const *pMod = pTab->pTokenizer->pModule; + int rc; /* Return Code */ + int nToken; /* Number of tokens in query */ + int iCol; /* Column currently being processed */ + StrBuffer res = {0, 0, 0}; /* Result string */ + TermOffsetCtx sCtx; /* Context for fts3ExprTermOffsetInit() */ + + if( !pCsr->pExpr ){ + sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC); + return; + } + + memset(&sCtx, 0, sizeof(sCtx)); + assert( pCsr->isRequireSeek==0 ); + + /* Count the number of terms in the query */ + rc = fts3ExprLoadDoclists(pCsr, 0, &nToken); + if( rc!=SQLITE_OK ) goto offsets_out; + + /* Allocate the array of TermOffset iterators. */ + sCtx.aTerm = (TermOffset *)sqlite3Fts3MallocZero(sizeof(TermOffset)*nToken); + if( 0==sCtx.aTerm ){ + rc = SQLITE_NOMEM; + goto offsets_out; + } + sCtx.iDocid = pCsr->iPrevId; + sCtx.pCsr = pCsr; + + /* Loop through the table columns, appending offset information to + ** string-buffer res for each column. + */ + for(iCol=0; iCol<pTab->nColumn; iCol++){ + sqlite3_tokenizer_cursor *pC; /* Tokenizer cursor */ + const char *ZDUMMY; /* Dummy argument used with xNext() */ + int NDUMMY = 0; /* Dummy argument used with xNext() */ + int iStart = 0; + int iEnd = 0; + int iCurrent = 0; + const char *zDoc; + int nDoc; + + /* Initialize the contents of sCtx.aTerm[] for column iCol. This + ** operation may fail if the database contains corrupt records. + */ + sCtx.iCol = iCol; + sCtx.iTerm = 0; + rc = sqlite3Fts3ExprIterate( + pCsr->pExpr, fts3ExprTermOffsetInit, (void*)&sCtx + ); + if( rc!=SQLITE_OK ) goto offsets_out; + + /* Retreive the text stored in column iCol. If an SQL NULL is stored + ** in column iCol, jump immediately to the next iteration of the loop. + ** If an OOM occurs while retrieving the data (this can happen if SQLite + ** needs to transform the data from utf-16 to utf-8), return SQLITE_NOMEM + ** to the caller. + */ + zDoc = (const char *)sqlite3_column_text(pCsr->pStmt, iCol+1); + nDoc = sqlite3_column_bytes(pCsr->pStmt, iCol+1); + if( zDoc==0 ){ + if( sqlite3_column_type(pCsr->pStmt, iCol+1)==SQLITE_NULL ){ + continue; + } + rc = SQLITE_NOMEM; + goto offsets_out; + } + + /* Initialize a tokenizer iterator to iterate through column iCol. */ + rc = sqlite3Fts3OpenTokenizer(pTab->pTokenizer, pCsr->iLangid, + zDoc, nDoc, &pC + ); + if( rc!=SQLITE_OK ) goto offsets_out; + + rc = pMod->xNext(pC, &ZDUMMY, &NDUMMY, &iStart, &iEnd, &iCurrent); + while( rc==SQLITE_OK ){ + int i; /* Used to loop through terms */ + int iMinPos = 0x7FFFFFFF; /* Position of next token */ + TermOffset *pTerm = 0; /* TermOffset associated with next token */ + + for(i=0; i<nToken; i++){ + TermOffset *pT = &sCtx.aTerm[i]; + if( pT->pList && (pT->iPos-pT->iOff)<iMinPos ){ + iMinPos = pT->iPos-pT->iOff; + pTerm = pT; + } + } + + if( !pTerm ){ + /* All offsets for this column have been gathered. */ + rc = SQLITE_DONE; + }else{ + assert_fts3_nc( iCurrent<=iMinPos ); + if( 0==(0xFE&*pTerm->pList) ){ + pTerm->pList = 0; + }else{ + fts3GetDeltaPosition(&pTerm->pList, &pTerm->iPos); + } + while( rc==SQLITE_OK && iCurrent<iMinPos ){ + rc = pMod->xNext(pC, &ZDUMMY, &NDUMMY, &iStart, &iEnd, &iCurrent); + } + if( rc==SQLITE_OK ){ + char aBuffer[64]; + sqlite3_snprintf(sizeof(aBuffer), aBuffer, + "%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart + ); + rc = fts3StringAppend(&res, aBuffer, -1); + }else if( rc==SQLITE_DONE && pTab->zContentTbl==0 ){ + rc = FTS_CORRUPT_VTAB; + } + } + } + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + } + + pMod->xClose(pC); + if( rc!=SQLITE_OK ) goto offsets_out; + } + + offsets_out: + sqlite3_free(sCtx.aTerm); + assert( rc!=SQLITE_DONE ); + sqlite3Fts3SegmentsClose(pTab); + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(pCtx, rc); + sqlite3_free(res.z); + }else{ + sqlite3_result_text(pCtx, res.z, res.n-1, sqlite3_free); + } + return; +} + +/* +** Implementation of matchinfo() function. +*/ +SQLITE_PRIVATE void sqlite3Fts3Matchinfo( + sqlite3_context *pContext, /* Function call context */ + Fts3Cursor *pCsr, /* FTS3 table cursor */ + const char *zArg /* Second arg to matchinfo() function */ +){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + const char *zFormat; + + if( zArg ){ + zFormat = zArg; + }else{ + zFormat = FTS3_MATCHINFO_DEFAULT; + } + + if( !pCsr->pExpr ){ + sqlite3_result_blob(pContext, "", 0, SQLITE_STATIC); + return; + }else{ + /* Retrieve matchinfo() data. */ + fts3GetMatchinfo(pContext, pCsr, zFormat); + sqlite3Fts3SegmentsClose(pTab); + } +} + +#endif + +/************** End of fts3_snippet.c ****************************************/ +/************** Begin file fts3_unicode.c ************************************/ +/* +** 2012 May 24 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** Implementation of the "unicode" full-text-search tokenizer. +*/ + +#ifndef SQLITE_DISABLE_FTS3_UNICODE + +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +/* #include <assert.h> */ +/* #include <stdlib.h> */ +/* #include <stdio.h> */ +/* #include <string.h> */ + +/* #include "fts3_tokenizer.h" */ + +/* +** The following two macros - READ_UTF8 and WRITE_UTF8 - have been copied +** from the sqlite3 source file utf.c. If this file is compiled as part +** of the amalgamation, they are not required. +*/ +#ifndef SQLITE_AMALGAMATION + +static const unsigned char sqlite3Utf8Trans1[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, +}; + +#define READ_UTF8(zIn, zTerm, c) \ + c = *(zIn++); \ + if( c>=0xc0 ){ \ + c = sqlite3Utf8Trans1[c-0xc0]; \ + while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ + c = (c<<6) + (0x3f & *(zIn++)); \ + } \ + if( c<0x80 \ + || (c&0xFFFFF800)==0xD800 \ + || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ + } + +#define WRITE_UTF8(zOut, c) { \ + if( c<0x00080 ){ \ + *zOut++ = (u8)(c&0xFF); \ + } \ + else if( c<0x00800 ){ \ + *zOut++ = 0xC0 + (u8)((c>>6)&0x1F); \ + *zOut++ = 0x80 + (u8)(c & 0x3F); \ + } \ + else if( c<0x10000 ){ \ + *zOut++ = 0xE0 + (u8)((c>>12)&0x0F); \ + *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); \ + *zOut++ = 0x80 + (u8)(c & 0x3F); \ + }else{ \ + *zOut++ = 0xF0 + (u8)((c>>18) & 0x07); \ + *zOut++ = 0x80 + (u8)((c>>12) & 0x3F); \ + *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); \ + *zOut++ = 0x80 + (u8)(c & 0x3F); \ + } \ +} + +#endif /* ifndef SQLITE_AMALGAMATION */ + +typedef struct unicode_tokenizer unicode_tokenizer; +typedef struct unicode_cursor unicode_cursor; + +struct unicode_tokenizer { + sqlite3_tokenizer base; + int eRemoveDiacritic; + int nException; + int *aiException; +}; + +struct unicode_cursor { + sqlite3_tokenizer_cursor base; + const unsigned char *aInput; /* Input text being tokenized */ + int nInput; /* Size of aInput[] in bytes */ + int iOff; /* Current offset within aInput[] */ + int iToken; /* Index of next token to be returned */ + char *zToken; /* storage for current token */ + int nAlloc; /* space allocated at zToken */ +}; + + +/* +** Destroy a tokenizer allocated by unicodeCreate(). +*/ +static int unicodeDestroy(sqlite3_tokenizer *pTokenizer){ + if( pTokenizer ){ + unicode_tokenizer *p = (unicode_tokenizer *)pTokenizer; + sqlite3_free(p->aiException); + sqlite3_free(p); + } + return SQLITE_OK; +} + +/* +** As part of a tokenchars= or separators= option, the CREATE VIRTUAL TABLE +** statement has specified that the tokenizer for this table shall consider +** all characters in string zIn/nIn to be separators (if bAlnum==0) or +** token characters (if bAlnum==1). +** +** For each codepoint in the zIn/nIn string, this function checks if the +** sqlite3FtsUnicodeIsalnum() function already returns the desired result. +** If so, no action is taken. Otherwise, the codepoint is added to the +** unicode_tokenizer.aiException[] array. For the purposes of tokenization, +** the return value of sqlite3FtsUnicodeIsalnum() is inverted for all +** codepoints in the aiException[] array. +** +** If a standalone diacritic mark (one that sqlite3FtsUnicodeIsdiacritic() +** identifies as a diacritic) occurs in the zIn/nIn string it is ignored. +** It is not possible to change the behavior of the tokenizer with respect +** to these codepoints. +*/ +static int unicodeAddExceptions( + unicode_tokenizer *p, /* Tokenizer to add exceptions to */ + int bAlnum, /* Replace Isalnum() return value with this */ + const char *zIn, /* Array of characters to make exceptions */ + int nIn /* Length of z in bytes */ +){ + const unsigned char *z = (const unsigned char *)zIn; + const unsigned char *zTerm = &z[nIn]; + unsigned int iCode; + int nEntry = 0; + + assert( bAlnum==0 || bAlnum==1 ); + + while( z<zTerm ){ + READ_UTF8(z, zTerm, iCode); + assert( (sqlite3FtsUnicodeIsalnum((int)iCode) & 0xFFFFFFFE)==0 ); + if( sqlite3FtsUnicodeIsalnum((int)iCode)!=bAlnum + && sqlite3FtsUnicodeIsdiacritic((int)iCode)==0 + ){ + nEntry++; + } + } + + if( nEntry ){ + int *aNew; /* New aiException[] array */ + int nNew; /* Number of valid entries in array aNew[] */ + + aNew = sqlite3_realloc64(p->aiException,(p->nException+nEntry)*sizeof(int)); + if( aNew==0 ) return SQLITE_NOMEM; + nNew = p->nException; + + z = (const unsigned char *)zIn; + while( z<zTerm ){ + READ_UTF8(z, zTerm, iCode); + if( sqlite3FtsUnicodeIsalnum((int)iCode)!=bAlnum + && sqlite3FtsUnicodeIsdiacritic((int)iCode)==0 + ){ + int i, j; + for(i=0; i<nNew && aNew[i]<(int)iCode; i++); + for(j=nNew; j>i; j--) aNew[j] = aNew[j-1]; + aNew[i] = (int)iCode; + nNew++; + } + } + p->aiException = aNew; + p->nException = nNew; + } + + return SQLITE_OK; +} + +/* +** Return true if the p->aiException[] array contains the value iCode. +*/ +static int unicodeIsException(unicode_tokenizer *p, int iCode){ + if( p->nException>0 ){ + int *a = p->aiException; + int iLo = 0; + int iHi = p->nException-1; + + while( iHi>=iLo ){ + int iTest = (iHi + iLo) / 2; + if( iCode==a[iTest] ){ + return 1; + }else if( iCode>a[iTest] ){ + iLo = iTest+1; + }else{ + iHi = iTest-1; + } + } + } + + return 0; +} + +/* +** Return true if, for the purposes of tokenization, codepoint iCode is +** considered a token character (not a separator). +*/ +static int unicodeIsAlnum(unicode_tokenizer *p, int iCode){ + assert( (sqlite3FtsUnicodeIsalnum(iCode) & 0xFFFFFFFE)==0 ); + return sqlite3FtsUnicodeIsalnum(iCode) ^ unicodeIsException(p, iCode); +} + +/* +** Create a new tokenizer instance. +*/ +static int unicodeCreate( + int nArg, /* Size of array argv[] */ + const char * const *azArg, /* Tokenizer creation arguments */ + sqlite3_tokenizer **pp /* OUT: New tokenizer handle */ +){ + unicode_tokenizer *pNew; /* New tokenizer object */ + int i; + int rc = SQLITE_OK; + + pNew = (unicode_tokenizer *) sqlite3_malloc(sizeof(unicode_tokenizer)); + if( pNew==NULL ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(unicode_tokenizer)); + pNew->eRemoveDiacritic = 1; + + for(i=0; rc==SQLITE_OK && i<nArg; i++){ + const char *z = azArg[i]; + int n = (int)strlen(z); + + if( n==19 && memcmp("remove_diacritics=1", z, 19)==0 ){ + pNew->eRemoveDiacritic = 1; + } + else if( n==19 && memcmp("remove_diacritics=0", z, 19)==0 ){ + pNew->eRemoveDiacritic = 0; + } + else if( n==19 && memcmp("remove_diacritics=2", z, 19)==0 ){ + pNew->eRemoveDiacritic = 2; + } + else if( n>=11 && memcmp("tokenchars=", z, 11)==0 ){ + rc = unicodeAddExceptions(pNew, 1, &z[11], n-11); + } + else if( n>=11 && memcmp("separators=", z, 11)==0 ){ + rc = unicodeAddExceptions(pNew, 0, &z[11], n-11); + } + else{ + /* Unrecognized argument */ + rc = SQLITE_ERROR; + } + } + + if( rc!=SQLITE_OK ){ + unicodeDestroy((sqlite3_tokenizer *)pNew); + pNew = 0; + } + *pp = (sqlite3_tokenizer *)pNew; + return rc; +} + +/* +** Prepare to begin tokenizing a particular string. The input +** string to be tokenized is pInput[0..nBytes-1]. A cursor +** used to incrementally tokenize this string is returned in +** *ppCursor. +*/ +static int unicodeOpen( + sqlite3_tokenizer *p, /* The tokenizer */ + const char *aInput, /* Input string */ + int nInput, /* Size of string aInput in bytes */ + sqlite3_tokenizer_cursor **pp /* OUT: New cursor object */ +){ + unicode_cursor *pCsr; + + pCsr = (unicode_cursor *)sqlite3_malloc(sizeof(unicode_cursor)); + if( pCsr==0 ){ + return SQLITE_NOMEM; + } + memset(pCsr, 0, sizeof(unicode_cursor)); + + pCsr->aInput = (const unsigned char *)aInput; + if( aInput==0 ){ + pCsr->nInput = 0; + pCsr->aInput = (const unsigned char*)""; + }else if( nInput<0 ){ + pCsr->nInput = (int)strlen(aInput); + }else{ + pCsr->nInput = nInput; + } + + *pp = &pCsr->base; + UNUSED_PARAMETER(p); + return SQLITE_OK; +} + +/* +** Close a tokenization cursor previously opened by a call to +** simpleOpen() above. +*/ +static int unicodeClose(sqlite3_tokenizer_cursor *pCursor){ + unicode_cursor *pCsr = (unicode_cursor *) pCursor; + sqlite3_free(pCsr->zToken); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** Extract the next token from a tokenization cursor. The cursor must +** have been opened by a prior call to simpleOpen(). +*/ +static int unicodeNext( + sqlite3_tokenizer_cursor *pC, /* Cursor returned by simpleOpen */ + const char **paToken, /* OUT: Token text */ + int *pnToken, /* OUT: Number of bytes at *paToken */ + int *piStart, /* OUT: Starting offset of token */ + int *piEnd, /* OUT: Ending offset of token */ + int *piPos /* OUT: Position integer of token */ +){ + unicode_cursor *pCsr = (unicode_cursor *)pC; + unicode_tokenizer *p = ((unicode_tokenizer *)pCsr->base.pTokenizer); + unsigned int iCode = 0; + char *zOut; + const unsigned char *z = &pCsr->aInput[pCsr->iOff]; + const unsigned char *zStart = z; + const unsigned char *zEnd; + const unsigned char *zTerm = &pCsr->aInput[pCsr->nInput]; + + /* Scan past any delimiter characters before the start of the next token. + ** Return SQLITE_DONE early if this takes us all the way to the end of + ** the input. */ + while( z<zTerm ){ + READ_UTF8(z, zTerm, iCode); + if( unicodeIsAlnum(p, (int)iCode) ) break; + zStart = z; + } + if( zStart>=zTerm ) return SQLITE_DONE; + + zOut = pCsr->zToken; + do { + int iOut; + + /* Grow the output buffer if required. */ + if( (zOut-pCsr->zToken)>=(pCsr->nAlloc-4) ){ + char *zNew = sqlite3_realloc64(pCsr->zToken, pCsr->nAlloc+64); + if( !zNew ) return SQLITE_NOMEM; + zOut = &zNew[zOut - pCsr->zToken]; + pCsr->zToken = zNew; + pCsr->nAlloc += 64; + } + + /* Write the folded case of the last character read to the output */ + zEnd = z; + iOut = sqlite3FtsUnicodeFold((int)iCode, p->eRemoveDiacritic); + if( iOut ){ + WRITE_UTF8(zOut, iOut); + } + + /* If the cursor is not at EOF, read the next character */ + if( z>=zTerm ) break; + READ_UTF8(z, zTerm, iCode); + }while( unicodeIsAlnum(p, (int)iCode) + || sqlite3FtsUnicodeIsdiacritic((int)iCode) + ); + + /* Set the output variables and return. */ + pCsr->iOff = (int)(z - pCsr->aInput); + *paToken = pCsr->zToken; + *pnToken = (int)(zOut - pCsr->zToken); + *piStart = (int)(zStart - pCsr->aInput); + *piEnd = (int)(zEnd - pCsr->aInput); + *piPos = pCsr->iToken++; + return SQLITE_OK; +} + +/* +** Set *ppModule to a pointer to the sqlite3_tokenizer_module +** structure for the unicode tokenizer. +*/ +SQLITE_PRIVATE void sqlite3Fts3UnicodeTokenizer(sqlite3_tokenizer_module const **ppModule){ + static const sqlite3_tokenizer_module module = { + 0, + unicodeCreate, + unicodeDestroy, + unicodeOpen, + unicodeClose, + unicodeNext, + 0, + }; + *ppModule = &module; +} + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ +#endif /* ifndef SQLITE_DISABLE_FTS3_UNICODE */ + +/************** End of fts3_unicode.c ****************************************/ +/************** Begin file fts3_unicode2.c ***********************************/ +/* +** 2012-05-25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +*/ + +/* +** DO NOT EDIT THIS MACHINE GENERATED FILE. +*/ + +#ifndef SQLITE_DISABLE_FTS3_UNICODE +#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) + +/* #include <assert.h> */ + +/* +** Return true if the argument corresponds to a unicode codepoint +** classified as either a letter or a number. Otherwise false. +** +** The results are undefined if the value passed to this function +** is less than zero. +*/ +SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int c){ + /* Each unsigned integer in the following array corresponds to a contiguous + ** range of unicode codepoints that are not either letters or numbers (i.e. + ** codepoints for which this function should return 0). + ** + ** The most significant 22 bits in each 32-bit value contain the first + ** codepoint in the range. The least significant 10 bits are used to store + ** the size of the range (always at least 1). In other words, the value + ** ((C<<22) + N) represents a range of N codepoints starting with codepoint + ** C. It is not possible to represent a range larger than 1023 codepoints + ** using this format. + */ + static const unsigned int aEntry[] = { + 0x00000030, 0x0000E807, 0x00016C06, 0x0001EC2F, 0x0002AC07, + 0x0002D001, 0x0002D803, 0x0002EC01, 0x0002FC01, 0x00035C01, + 0x0003DC01, 0x000B0804, 0x000B480E, 0x000B9407, 0x000BB401, + 0x000BBC81, 0x000DD401, 0x000DF801, 0x000E1002, 0x000E1C01, + 0x000FD801, 0x00120808, 0x00156806, 0x00162402, 0x00163C01, + 0x00164437, 0x0017CC02, 0x00180005, 0x00181816, 0x00187802, + 0x00192C15, 0x0019A804, 0x0019C001, 0x001B5001, 0x001B580F, + 0x001B9C07, 0x001BF402, 0x001C000E, 0x001C3C01, 0x001C4401, + 0x001CC01B, 0x001E980B, 0x001FAC09, 0x001FD804, 0x00205804, + 0x00206C09, 0x00209403, 0x0020A405, 0x0020C00F, 0x00216403, + 0x00217801, 0x0023901B, 0x00240004, 0x0024E803, 0x0024F812, + 0x00254407, 0x00258804, 0x0025C001, 0x00260403, 0x0026F001, + 0x0026F807, 0x00271C02, 0x00272C03, 0x00275C01, 0x00278802, + 0x0027C802, 0x0027E802, 0x00280403, 0x0028F001, 0x0028F805, + 0x00291C02, 0x00292C03, 0x00294401, 0x0029C002, 0x0029D401, + 0x002A0403, 0x002AF001, 0x002AF808, 0x002B1C03, 0x002B2C03, + 0x002B8802, 0x002BC002, 0x002C0403, 0x002CF001, 0x002CF807, + 0x002D1C02, 0x002D2C03, 0x002D5802, 0x002D8802, 0x002DC001, + 0x002E0801, 0x002EF805, 0x002F1803, 0x002F2804, 0x002F5C01, + 0x002FCC08, 0x00300403, 0x0030F807, 0x00311803, 0x00312804, + 0x00315402, 0x00318802, 0x0031FC01, 0x00320802, 0x0032F001, + 0x0032F807, 0x00331803, 0x00332804, 0x00335402, 0x00338802, + 0x00340802, 0x0034F807, 0x00351803, 0x00352804, 0x00355C01, + 0x00358802, 0x0035E401, 0x00360802, 0x00372801, 0x00373C06, + 0x00375801, 0x00376008, 0x0037C803, 0x0038C401, 0x0038D007, + 0x0038FC01, 0x00391C09, 0x00396802, 0x003AC401, 0x003AD006, + 0x003AEC02, 0x003B2006, 0x003C041F, 0x003CD00C, 0x003DC417, + 0x003E340B, 0x003E6424, 0x003EF80F, 0x003F380D, 0x0040AC14, + 0x00412806, 0x00415804, 0x00417803, 0x00418803, 0x00419C07, + 0x0041C404, 0x0042080C, 0x00423C01, 0x00426806, 0x0043EC01, + 0x004D740C, 0x004E400A, 0x00500001, 0x0059B402, 0x005A0001, + 0x005A6C02, 0x005BAC03, 0x005C4803, 0x005CC805, 0x005D4802, + 0x005DC802, 0x005ED023, 0x005F6004, 0x005F7401, 0x0060000F, + 0x0062A401, 0x0064800C, 0x0064C00C, 0x00650001, 0x00651002, + 0x0066C011, 0x00672002, 0x00677822, 0x00685C05, 0x00687802, + 0x0069540A, 0x0069801D, 0x0069FC01, 0x006A8007, 0x006AA006, + 0x006C0005, 0x006CD011, 0x006D6823, 0x006E0003, 0x006E840D, + 0x006F980E, 0x006FF004, 0x00709014, 0x0070EC05, 0x0071F802, + 0x00730008, 0x00734019, 0x0073B401, 0x0073C803, 0x00770027, + 0x0077F004, 0x007EF401, 0x007EFC03, 0x007F3403, 0x007F7403, + 0x007FB403, 0x007FF402, 0x00800065, 0x0081A806, 0x0081E805, + 0x00822805, 0x0082801A, 0x00834021, 0x00840002, 0x00840C04, + 0x00842002, 0x00845001, 0x00845803, 0x00847806, 0x00849401, + 0x00849C01, 0x0084A401, 0x0084B801, 0x0084E802, 0x00850005, + 0x00852804, 0x00853C01, 0x00864264, 0x00900027, 0x0091000B, + 0x0092704E, 0x00940200, 0x009C0475, 0x009E53B9, 0x00AD400A, + 0x00B39406, 0x00B3BC03, 0x00B3E404, 0x00B3F802, 0x00B5C001, + 0x00B5FC01, 0x00B7804F, 0x00B8C00C, 0x00BA001A, 0x00BA6C59, + 0x00BC00D6, 0x00BFC00C, 0x00C00005, 0x00C02019, 0x00C0A807, + 0x00C0D802, 0x00C0F403, 0x00C26404, 0x00C28001, 0x00C3EC01, + 0x00C64002, 0x00C6580A, 0x00C70024, 0x00C8001F, 0x00C8A81E, + 0x00C94001, 0x00C98020, 0x00CA2827, 0x00CB003F, 0x00CC0100, + 0x01370040, 0x02924037, 0x0293F802, 0x02983403, 0x0299BC10, + 0x029A7C01, 0x029BC008, 0x029C0017, 0x029C8002, 0x029E2402, + 0x02A00801, 0x02A01801, 0x02A02C01, 0x02A08C09, 0x02A0D804, + 0x02A1D004, 0x02A20002, 0x02A2D011, 0x02A33802, 0x02A38012, + 0x02A3E003, 0x02A4980A, 0x02A51C0D, 0x02A57C01, 0x02A60004, + 0x02A6CC1B, 0x02A77802, 0x02A8A40E, 0x02A90C01, 0x02A93002, + 0x02A97004, 0x02A9DC03, 0x02A9EC01, 0x02AAC001, 0x02AAC803, + 0x02AADC02, 0x02AAF802, 0x02AB0401, 0x02AB7802, 0x02ABAC07, + 0x02ABD402, 0x02AF8C0B, 0x03600001, 0x036DFC02, 0x036FFC02, + 0x037FFC01, 0x03EC7801, 0x03ECA401, 0x03EEC810, 0x03F4F802, + 0x03F7F002, 0x03F8001A, 0x03F88007, 0x03F8C023, 0x03F95013, + 0x03F9A004, 0x03FBFC01, 0x03FC040F, 0x03FC6807, 0x03FCEC06, + 0x03FD6C0B, 0x03FF8007, 0x03FFA007, 0x03FFE405, 0x04040003, + 0x0404DC09, 0x0405E411, 0x0406400C, 0x0407402E, 0x040E7C01, + 0x040F4001, 0x04215C01, 0x04247C01, 0x0424FC01, 0x04280403, + 0x04281402, 0x04283004, 0x0428E003, 0x0428FC01, 0x04294009, + 0x0429FC01, 0x042CE407, 0x04400003, 0x0440E016, 0x04420003, + 0x0442C012, 0x04440003, 0x04449C0E, 0x04450004, 0x04460003, + 0x0446CC0E, 0x04471404, 0x045AAC0D, 0x0491C004, 0x05BD442E, + 0x05BE3C04, 0x074000F6, 0x07440027, 0x0744A4B5, 0x07480046, + 0x074C0057, 0x075B0401, 0x075B6C01, 0x075BEC01, 0x075C5401, + 0x075CD401, 0x075D3C01, 0x075DBC01, 0x075E2401, 0x075EA401, + 0x075F0C01, 0x07BBC002, 0x07C0002C, 0x07C0C064, 0x07C2800F, + 0x07C2C40E, 0x07C3040F, 0x07C3440F, 0x07C4401F, 0x07C4C03C, + 0x07C5C02B, 0x07C7981D, 0x07C8402B, 0x07C90009, 0x07C94002, + 0x07CC0021, 0x07CCC006, 0x07CCDC46, 0x07CE0014, 0x07CE8025, + 0x07CF1805, 0x07CF8011, 0x07D0003F, 0x07D10001, 0x07D108B6, + 0x07D3E404, 0x07D4003E, 0x07D50004, 0x07D54018, 0x07D7EC46, + 0x07D9140B, 0x07DA0046, 0x07DC0074, 0x38000401, 0x38008060, + 0x380400F0, + }; + static const unsigned int aAscii[4] = { + 0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001, + }; + + if( (unsigned int)c<128 ){ + return ( (aAscii[c >> 5] & ((unsigned int)1 << (c & 0x001F)))==0 ); + }else if( (unsigned int)c<(1<<22) ){ + unsigned int key = (((unsigned int)c)<<10) | 0x000003FF; + int iRes = 0; + int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1; + int iLo = 0; + while( iHi>=iLo ){ + int iTest = (iHi + iLo) / 2; + if( key >= aEntry[iTest] ){ + iRes = iTest; + iLo = iTest+1; + }else{ + iHi = iTest-1; + } + } + assert( aEntry[0]<key ); + assert( key>=aEntry[iRes] ); + return (((unsigned int)c) >= ((aEntry[iRes]>>10) + (aEntry[iRes]&0x3FF))); + } + return 1; +} + + +/* +** If the argument is a codepoint corresponding to a lowercase letter +** in the ASCII range with a diacritic added, return the codepoint +** of the ASCII letter only. For example, if passed 235 - "LATIN +** SMALL LETTER E WITH DIAERESIS" - return 65 ("LATIN SMALL LETTER +** E"). The resuls of passing a codepoint that corresponds to an +** uppercase letter are undefined. +*/ +static int remove_diacritic(int c, int bComplex){ + unsigned short aDia[] = { + 0, 1797, 1848, 1859, 1891, 1928, 1940, 1995, + 2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286, + 2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732, + 2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336, + 3456, 3696, 3712, 3728, 3744, 3766, 3832, 3896, + 3912, 3928, 3944, 3968, 4008, 4040, 4056, 4106, + 4138, 4170, 4202, 4234, 4266, 4296, 4312, 4344, + 4408, 4424, 4442, 4472, 4488, 4504, 6148, 6198, + 6264, 6280, 6360, 6429, 6505, 6529, 61448, 61468, + 61512, 61534, 61592, 61610, 61642, 61672, 61688, 61704, + 61726, 61784, 61800, 61816, 61836, 61880, 61896, 61914, + 61948, 61998, 62062, 62122, 62154, 62184, 62200, 62218, + 62252, 62302, 62364, 62410, 62442, 62478, 62536, 62554, + 62584, 62604, 62640, 62648, 62656, 62664, 62730, 62766, + 62830, 62890, 62924, 62974, 63032, 63050, 63082, 63118, + 63182, 63242, 63274, 63310, 63368, 63390, + }; +#define HIBIT ((unsigned char)0x80) + unsigned char aChar[] = { + '\0', 'a', 'c', 'e', 'i', 'n', + 'o', 'u', 'y', 'y', 'a', 'c', + 'd', 'e', 'e', 'g', 'h', 'i', + 'j', 'k', 'l', 'n', 'o', 'r', + 's', 't', 'u', 'u', 'w', 'y', + 'z', 'o', 'u', 'a', 'i', 'o', + 'u', 'u'|HIBIT, 'a'|HIBIT, 'g', 'k', 'o', + 'o'|HIBIT, 'j', 'g', 'n', 'a'|HIBIT, 'a', + 'e', 'i', 'o', 'r', 'u', 's', + 't', 'h', 'a', 'e', 'o'|HIBIT, 'o', + 'o'|HIBIT, 'y', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', 'a', 'b', + 'c'|HIBIT, 'd', 'd', 'e'|HIBIT, 'e', 'e'|HIBIT, + 'f', 'g', 'h', 'h', 'i', 'i'|HIBIT, + 'k', 'l', 'l'|HIBIT, 'l', 'm', 'n', + 'o'|HIBIT, 'p', 'r', 'r'|HIBIT, 'r', 's', + 's'|HIBIT, 't', 'u', 'u'|HIBIT, 'v', 'w', + 'w', 'x', 'y', 'z', 'h', 't', + 'w', 'y', 'a', 'a'|HIBIT, 'a'|HIBIT, 'a'|HIBIT, + 'e', 'e'|HIBIT, 'e'|HIBIT, 'i', 'o', 'o'|HIBIT, + 'o'|HIBIT, 'o'|HIBIT, 'u', 'u'|HIBIT, 'u'|HIBIT, 'y', + }; + + unsigned int key = (((unsigned int)c)<<3) | 0x00000007; + int iRes = 0; + int iHi = sizeof(aDia)/sizeof(aDia[0]) - 1; + int iLo = 0; + while( iHi>=iLo ){ + int iTest = (iHi + iLo) / 2; + if( key >= aDia[iTest] ){ + iRes = iTest; + iLo = iTest+1; + }else{ + iHi = iTest-1; + } + } + assert( key>=aDia[iRes] ); + if( bComplex==0 && (aChar[iRes] & 0x80) ) return c; + return (c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : ((int)aChar[iRes] & 0x7F); +} + + +/* +** Return true if the argument interpreted as a unicode codepoint +** is a diacritical modifier character. +*/ +SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int c){ + unsigned int mask0 = 0x08029FDF; + unsigned int mask1 = 0x000361F8; + if( c<768 || c>817 ) return 0; + return (c < 768+32) ? + (mask0 & ((unsigned int)1 << (c-768))) : + (mask1 & ((unsigned int)1 << (c-768-32))); +} + + +/* +** Interpret the argument as a unicode codepoint. If the codepoint +** is an upper case character that has a lower case equivalent, +** return the codepoint corresponding to the lower case version. +** Otherwise, return a copy of the argument. +** +** The results are undefined if the value passed to this function +** is less than zero. +*/ +SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int eRemoveDiacritic){ + /* Each entry in the following array defines a rule for folding a range + ** of codepoints to lower case. The rule applies to a range of nRange + ** codepoints starting at codepoint iCode. + ** + ** If the least significant bit in flags is clear, then the rule applies + ** to all nRange codepoints (i.e. all nRange codepoints are upper case and + ** need to be folded). Or, if it is set, then the rule only applies to + ** every second codepoint in the range, starting with codepoint C. + ** + ** The 7 most significant bits in flags are an index into the aiOff[] + ** array. If a specific codepoint C does require folding, then its lower + ** case equivalent is ((C + aiOff[flags>>1]) & 0xFFFF). + ** + ** The contents of this array are generated by parsing the CaseFolding.txt + ** file distributed as part of the "Unicode Character Database". See + ** http://www.unicode.org for details. + */ + static const struct TableEntry { + unsigned short iCode; + unsigned char flags; + unsigned char nRange; + } aEntry[] = { + {65, 14, 26}, {181, 64, 1}, {192, 14, 23}, + {216, 14, 7}, {256, 1, 48}, {306, 1, 6}, + {313, 1, 16}, {330, 1, 46}, {376, 116, 1}, + {377, 1, 6}, {383, 104, 1}, {385, 50, 1}, + {386, 1, 4}, {390, 44, 1}, {391, 0, 1}, + {393, 42, 2}, {395, 0, 1}, {398, 32, 1}, + {399, 38, 1}, {400, 40, 1}, {401, 0, 1}, + {403, 42, 1}, {404, 46, 1}, {406, 52, 1}, + {407, 48, 1}, {408, 0, 1}, {412, 52, 1}, + {413, 54, 1}, {415, 56, 1}, {416, 1, 6}, + {422, 60, 1}, {423, 0, 1}, {425, 60, 1}, + {428, 0, 1}, {430, 60, 1}, {431, 0, 1}, + {433, 58, 2}, {435, 1, 4}, {439, 62, 1}, + {440, 0, 1}, {444, 0, 1}, {452, 2, 1}, + {453, 0, 1}, {455, 2, 1}, {456, 0, 1}, + {458, 2, 1}, {459, 1, 18}, {478, 1, 18}, + {497, 2, 1}, {498, 1, 4}, {502, 122, 1}, + {503, 134, 1}, {504, 1, 40}, {544, 110, 1}, + {546, 1, 18}, {570, 70, 1}, {571, 0, 1}, + {573, 108, 1}, {574, 68, 1}, {577, 0, 1}, + {579, 106, 1}, {580, 28, 1}, {581, 30, 1}, + {582, 1, 10}, {837, 36, 1}, {880, 1, 4}, + {886, 0, 1}, {902, 18, 1}, {904, 16, 3}, + {908, 26, 1}, {910, 24, 2}, {913, 14, 17}, + {931, 14, 9}, {962, 0, 1}, {975, 4, 1}, + {976, 140, 1}, {977, 142, 1}, {981, 146, 1}, + {982, 144, 1}, {984, 1, 24}, {1008, 136, 1}, + {1009, 138, 1}, {1012, 130, 1}, {1013, 128, 1}, + {1015, 0, 1}, {1017, 152, 1}, {1018, 0, 1}, + {1021, 110, 3}, {1024, 34, 16}, {1040, 14, 32}, + {1120, 1, 34}, {1162, 1, 54}, {1216, 6, 1}, + {1217, 1, 14}, {1232, 1, 88}, {1329, 22, 38}, + {4256, 66, 38}, {4295, 66, 1}, {4301, 66, 1}, + {7680, 1, 150}, {7835, 132, 1}, {7838, 96, 1}, + {7840, 1, 96}, {7944, 150, 8}, {7960, 150, 6}, + {7976, 150, 8}, {7992, 150, 8}, {8008, 150, 6}, + {8025, 151, 8}, {8040, 150, 8}, {8072, 150, 8}, + {8088, 150, 8}, {8104, 150, 8}, {8120, 150, 2}, + {8122, 126, 2}, {8124, 148, 1}, {8126, 100, 1}, + {8136, 124, 4}, {8140, 148, 1}, {8152, 150, 2}, + {8154, 120, 2}, {8168, 150, 2}, {8170, 118, 2}, + {8172, 152, 1}, {8184, 112, 2}, {8186, 114, 2}, + {8188, 148, 1}, {8486, 98, 1}, {8490, 92, 1}, + {8491, 94, 1}, {8498, 12, 1}, {8544, 8, 16}, + {8579, 0, 1}, {9398, 10, 26}, {11264, 22, 47}, + {11360, 0, 1}, {11362, 88, 1}, {11363, 102, 1}, + {11364, 90, 1}, {11367, 1, 6}, {11373, 84, 1}, + {11374, 86, 1}, {11375, 80, 1}, {11376, 82, 1}, + {11378, 0, 1}, {11381, 0, 1}, {11390, 78, 2}, + {11392, 1, 100}, {11499, 1, 4}, {11506, 0, 1}, + {42560, 1, 46}, {42624, 1, 24}, {42786, 1, 14}, + {42802, 1, 62}, {42873, 1, 4}, {42877, 76, 1}, + {42878, 1, 10}, {42891, 0, 1}, {42893, 74, 1}, + {42896, 1, 4}, {42912, 1, 10}, {42922, 72, 1}, + {65313, 14, 26}, + }; + static const unsigned short aiOff[] = { + 1, 2, 8, 15, 16, 26, 28, 32, + 37, 38, 40, 48, 63, 64, 69, 71, + 79, 80, 116, 202, 203, 205, 206, 207, + 209, 210, 211, 213, 214, 217, 218, 219, + 775, 7264, 10792, 10795, 23228, 23256, 30204, 54721, + 54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274, + 57921, 58019, 58363, 61722, 65268, 65341, 65373, 65406, + 65408, 65410, 65415, 65424, 65436, 65439, 65450, 65462, + 65472, 65476, 65478, 65480, 65482, 65488, 65506, 65511, + 65514, 65521, 65527, 65528, 65529, + }; + + int ret = c; + + assert( sizeof(unsigned short)==2 && sizeof(unsigned char)==1 ); + + if( c<128 ){ + if( c>='A' && c<='Z' ) ret = c + ('a' - 'A'); + }else if( c<65536 ){ + const struct TableEntry *p; + int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1; + int iLo = 0; + int iRes = -1; + + assert( c>aEntry[0].iCode ); + while( iHi>=iLo ){ + int iTest = (iHi + iLo) / 2; + int cmp = (c - aEntry[iTest].iCode); + if( cmp>=0 ){ + iRes = iTest; + iLo = iTest+1; + }else{ + iHi = iTest-1; + } + } + + assert( iRes>=0 && c>=aEntry[iRes].iCode ); + p = &aEntry[iRes]; + if( c<(p->iCode + p->nRange) && 0==(0x01 & p->flags & (p->iCode ^ c)) ){ + ret = (c + (aiOff[p->flags>>1])) & 0x0000FFFF; + assert( ret>0 ); + } + + if( eRemoveDiacritic ){ + ret = remove_diacritic(ret, eRemoveDiacritic==2); + } + } + + else if( c>=66560 && c<66600 ){ + ret = c + 40; + } + + return ret; +} +#endif /* defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) */ +#endif /* !defined(SQLITE_DISABLE_FTS3_UNICODE) */ + +/************** End of fts3_unicode2.c ***************************************/ +/************** Begin file json.c ********************************************/ +/* +** 2015-08-12 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite JSON functions. +** +** This file began as an extension in ext/misc/json1.c in 2015. That +** extension proved so useful that it has now been moved into the core. +** +** For the time being, all JSON is stored as pure text. (We might add +** a JSONB type in the future which stores a binary encoding of JSON in +** a BLOB, but there is no support for JSONB in the current implementation. +** This implementation parses JSON text at 250 MB/s, so it is hard to see +** how JSONB might improve on that.) +*/ +#ifndef SQLITE_OMIT_JSON +/* #include "sqliteInt.h" */ + +/* +** Growing our own isspace() routine this way is twice as fast as +** the library isspace() function, resulting in a 7% overall performance +** increase for the parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os). +*/ +static const char jsonIsSpace[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +#define fast_isspace(x) (jsonIsSpace[(unsigned char)x]) + +/* +** Characters that are special to JSON. Control charaters, +** '"' and '\\'. +*/ +static const char jsonIsOk[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +}; + + +#if !defined(SQLITE_DEBUG) && !defined(SQLITE_COVERAGE_TEST) +# define VVA(X) +#else +# define VVA(X) X +#endif + +/* Objects */ +typedef struct JsonString JsonString; +typedef struct JsonNode JsonNode; +typedef struct JsonParse JsonParse; +typedef struct JsonCleanup JsonCleanup; + +/* An instance of this object represents a JSON string +** under construction. Really, this is a generic string accumulator +** that can be and is used to create strings other than JSON. +*/ +struct JsonString { + sqlite3_context *pCtx; /* Function context - put error messages here */ + char *zBuf; /* Append JSON content here */ + u64 nAlloc; /* Bytes of storage available in zBuf[] */ + u64 nUsed; /* Bytes of zBuf[] currently used */ + u8 bStatic; /* True if zBuf is static space */ + u8 bErr; /* True if an error has been encountered */ + char zSpace[100]; /* Initial static space */ +}; + +/* A deferred cleanup task. A list of JsonCleanup objects might be +** run when the JsonParse object is destroyed. +*/ +struct JsonCleanup { + JsonCleanup *pJCNext; /* Next in a list */ + void (*xOp)(void*); /* Routine to run */ + void *pArg; /* Argument to xOp() */ +}; + +/* JSON type values +*/ +#define JSON_SUBST 0 /* Special edit node. Uses u.iPrev */ +#define JSON_NULL 1 +#define JSON_TRUE 2 +#define JSON_FALSE 3 +#define JSON_INT 4 +#define JSON_REAL 5 +#define JSON_STRING 6 +#define JSON_ARRAY 7 +#define JSON_OBJECT 8 + +/* The "subtype" set for JSON values */ +#define JSON_SUBTYPE 74 /* Ascii for "J" */ + +/* +** Names of the various JSON types: +*/ +static const char * const jsonType[] = { + "subst", + "null", "true", "false", "integer", "real", "text", "array", "object" +}; + +/* Bit values for the JsonNode.jnFlag field +*/ +#define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */ +#define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */ +#define JNODE_REMOVE 0x04 /* Do not output */ +#define JNODE_REPLACE 0x08 /* Target of a JSON_SUBST node */ +#define JNODE_APPEND 0x10 /* More ARRAY/OBJECT entries at u.iAppend */ +#define JNODE_LABEL 0x20 /* Is a label of an object */ +#define JNODE_JSON5 0x40 /* Node contains JSON5 enhancements */ + + +/* A single node of parsed JSON. An array of these nodes describes +** a parse of JSON + edits. +** +** Use the json_parse() SQL function (available when compiled with +** -DSQLITE_DEBUG) to see a dump of complete JsonParse objects, including +** a complete listing and decoding of the array of JsonNodes. +*/ +struct JsonNode { + u8 eType; /* One of the JSON_ type values */ + u8 jnFlags; /* JNODE flags */ + u8 eU; /* Which union element to use */ + u32 n; /* Bytes of content for INT, REAL or STRING + ** Number of sub-nodes for ARRAY and OBJECT + ** Node that SUBST applies to */ + union { + const char *zJContent; /* 1: Content for INT, REAL, and STRING */ + u32 iAppend; /* 2: More terms for ARRAY and OBJECT */ + u32 iKey; /* 3: Key for ARRAY objects in json_tree() */ + u32 iPrev; /* 4: Previous SUBST node, or 0 */ + } u; +}; + + +/* A parsed and possibly edited JSON string. Lifecycle: +** +** 1. JSON comes in and is parsed into an array aNode[]. The original +** JSON text is stored in zJson. +** +** 2. Zero or more changes are made (via json_remove() or json_replace() +** or similar) to the aNode[] array. +** +** 3. A new, edited and mimified JSON string is generated from aNode +** and stored in zAlt. The JsonParse object always owns zAlt. +** +** Step 1 always happens. Step 2 and 3 may or may not happen, depending +** on the operation. +** +** aNode[].u.zJContent entries typically point into zJson. Hence zJson +** must remain valid for the lifespan of the parse. For edits, +** aNode[].u.zJContent might point to malloced space other than zJson. +** Entries in pClup are responsible for freeing that extra malloced space. +** +** When walking the parse tree in aNode[], edits are ignored if useMod is +** false. +*/ +struct JsonParse { + u32 nNode; /* Number of slots of aNode[] used */ + u32 nAlloc; /* Number of slots of aNode[] allocated */ + JsonNode *aNode; /* Array of nodes containing the parse */ + char *zJson; /* Original JSON string (before edits) */ + char *zAlt; /* Revised and/or mimified JSON */ + u32 *aUp; /* Index of parent of each node */ + JsonCleanup *pClup;/* Cleanup operations prior to freeing this object */ + u16 iDepth; /* Nesting depth */ + u8 nErr; /* Number of errors seen */ + u8 oom; /* Set to true if out of memory */ + u8 bJsonIsRCStr; /* True if zJson is an RCStr */ + u8 hasNonstd; /* True if input uses non-standard features like JSON5 */ + u8 useMod; /* Actually use the edits contain inside aNode */ + u8 hasMod; /* aNode contains edits from the original zJson */ + u32 nJPRef; /* Number of references to this object */ + int nJson; /* Length of the zJson string in bytes */ + int nAlt; /* Length of alternative JSON string zAlt, in bytes */ + u32 iErr; /* Error location in zJson[] */ + u32 iSubst; /* Last JSON_SUBST entry in aNode[] */ + u32 iHold; /* Age of this entry in the cache for LRU replacement */ +}; + +/* +** Maximum nesting depth of JSON for this implementation. +** +** This limit is needed to avoid a stack overflow in the recursive +** descent parser. A depth of 1000 is far deeper than any sane JSON +** should go. Historical note: This limit was 2000 prior to version 3.42.0 +*/ +#define JSON_MAX_DEPTH 1000 + +/************************************************************************** +** Utility routines for dealing with JsonString objects +**************************************************************************/ + +/* Set the JsonString object to an empty string +*/ +static void jsonZero(JsonString *p){ + p->zBuf = p->zSpace; + p->nAlloc = sizeof(p->zSpace); + p->nUsed = 0; + p->bStatic = 1; +} + +/* Initialize the JsonString object +*/ +static void jsonInit(JsonString *p, sqlite3_context *pCtx){ + p->pCtx = pCtx; + p->bErr = 0; + jsonZero(p); +} + +/* Free all allocated memory and reset the JsonString object back to its +** initial state. +*/ +static void jsonReset(JsonString *p){ + if( !p->bStatic ) sqlite3RCStrUnref(p->zBuf); + jsonZero(p); +} + +/* Report an out-of-memory (OOM) condition +*/ +static void jsonOom(JsonString *p){ + p->bErr = 1; + sqlite3_result_error_nomem(p->pCtx); + jsonReset(p); +} + +/* Enlarge pJson->zBuf so that it can hold at least N more bytes. +** Return zero on success. Return non-zero on an OOM error +*/ +static int jsonGrow(JsonString *p, u32 N){ + u64 nTotal = N<p->nAlloc ? p->nAlloc*2 : p->nAlloc+N+10; + char *zNew; + if( p->bStatic ){ + if( p->bErr ) return 1; + zNew = sqlite3RCStrNew(nTotal); + if( zNew==0 ){ + jsonOom(p); + return SQLITE_NOMEM; + } + memcpy(zNew, p->zBuf, (size_t)p->nUsed); + p->zBuf = zNew; + p->bStatic = 0; + }else{ + p->zBuf = sqlite3RCStrResize(p->zBuf, nTotal); + if( p->zBuf==0 ){ + p->bErr = 1; + jsonZero(p); + return SQLITE_NOMEM; + } + } + p->nAlloc = nTotal; + return SQLITE_OK; +} + +/* Append N bytes from zIn onto the end of the JsonString string. +*/ +static SQLITE_NOINLINE void jsonAppendExpand( + JsonString *p, + const char *zIn, + u32 N +){ + assert( N>0 ); + if( jsonGrow(p,N) ) return; + memcpy(p->zBuf+p->nUsed, zIn, N); + p->nUsed += N; +} +static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){ + if( N==0 ) return; + if( N+p->nUsed >= p->nAlloc ){ + jsonAppendExpand(p,zIn,N); + }else{ + memcpy(p->zBuf+p->nUsed, zIn, N); + p->nUsed += N; + } +} +static void jsonAppendRawNZ(JsonString *p, const char *zIn, u32 N){ + assert( N>0 ); + if( N+p->nUsed >= p->nAlloc ){ + jsonAppendExpand(p,zIn,N); + }else{ + memcpy(p->zBuf+p->nUsed, zIn, N); + p->nUsed += N; + } +} + + +/* Append formatted text (not to exceed N bytes) to the JsonString. +*/ +static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){ + va_list ap; + if( (p->nUsed + N >= p->nAlloc) && jsonGrow(p, N) ) return; + va_start(ap, zFormat); + sqlite3_vsnprintf(N, p->zBuf+p->nUsed, zFormat, ap); + va_end(ap); + p->nUsed += (int)strlen(p->zBuf+p->nUsed); +} + +/* Append a single character +*/ +static SQLITE_NOINLINE void jsonAppendCharExpand(JsonString *p, char c){ + if( jsonGrow(p,1) ) return; + p->zBuf[p->nUsed++] = c; +} +static void jsonAppendChar(JsonString *p, char c){ + if( p->nUsed>=p->nAlloc ){ + jsonAppendCharExpand(p,c); + }else{ + p->zBuf[p->nUsed++] = c; + } +} + +/* Try to force the string to be a zero-terminated RCStr string. +** +** Return true on success. Return false if an OOM prevents this +** from happening. +*/ +static int jsonForceRCStr(JsonString *p){ + jsonAppendChar(p, 0); + if( p->bErr ) return 0; + p->nUsed--; + if( p->bStatic==0 ) return 1; + p->nAlloc = 0; + p->nUsed++; + jsonGrow(p, p->nUsed); + p->nUsed--; + return p->bStatic==0; +} + + +/* Append a comma separator to the output buffer, if the previous +** character is not '[' or '{'. +*/ +static void jsonAppendSeparator(JsonString *p){ + char c; + if( p->nUsed==0 ) return; + c = p->zBuf[p->nUsed-1]; + if( c=='[' || c=='{' ) return; + jsonAppendChar(p, ','); +} + +/* Append the N-byte string in zIn to the end of the JsonString string +** under construction. Enclose the string in "..." and escape +** any double-quotes or backslash characters contained within the +** string. +*/ +static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ + u32 i; + if( zIn==0 || ((N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0) ) return; + p->zBuf[p->nUsed++] = '"'; + for(i=0; i<N; i++){ + unsigned char c = ((unsigned const char*)zIn)[i]; + if( jsonIsOk[c] ){ + p->zBuf[p->nUsed++] = c; + }else if( c=='"' || c=='\\' ){ + json_simple_escape: + if( (p->nUsed+N+3-i > p->nAlloc) && jsonGrow(p,N+3-i)!=0 ) return; + p->zBuf[p->nUsed++] = '\\'; + p->zBuf[p->nUsed++] = c; + }else if( c=='\'' ){ + p->zBuf[p->nUsed++] = c; + }else{ + static const char aSpecial[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + assert( sizeof(aSpecial)==32 ); + assert( aSpecial['\b']=='b' ); + assert( aSpecial['\f']=='f' ); + assert( aSpecial['\n']=='n' ); + assert( aSpecial['\r']=='r' ); + assert( aSpecial['\t']=='t' ); + assert( c>=0 && c<sizeof(aSpecial) ); + if( aSpecial[c] ){ + c = aSpecial[c]; + goto json_simple_escape; + } + if( (p->nUsed+N+7+i > p->nAlloc) && jsonGrow(p,N+7-i)!=0 ) return; + p->zBuf[p->nUsed++] = '\\'; + p->zBuf[p->nUsed++] = 'u'; + p->zBuf[p->nUsed++] = '0'; + p->zBuf[p->nUsed++] = '0'; + p->zBuf[p->nUsed++] = "0123456789abcdef"[c>>4]; + p->zBuf[p->nUsed++] = "0123456789abcdef"[c&0xf]; + } + } + p->zBuf[p->nUsed++] = '"'; + assert( p->nUsed<p->nAlloc ); +} + +/* +** The zIn[0..N] string is a JSON5 string literal. Append to p a translation +** of the string literal that standard JSON and that omits all JSON5 +** features. +*/ +static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ + u32 i; + jsonAppendChar(p, '"'); + zIn++; + N -= 2; + while( N>0 ){ + for(i=0; i<N && zIn[i]!='\\'; i++){} + if( i>0 ){ + jsonAppendRawNZ(p, zIn, i); + zIn += i; + N -= i; + if( N==0 ) break; + } + assert( zIn[0]=='\\' ); + switch( (u8)zIn[1] ){ + case '\'': + jsonAppendChar(p, '\''); + break; + case 'v': + jsonAppendRawNZ(p, "\\u0009", 6); + break; + case 'x': + jsonAppendRawNZ(p, "\\u00", 4); + jsonAppendRawNZ(p, &zIn[2], 2); + zIn += 2; + N -= 2; + break; + case '0': + jsonAppendRawNZ(p, "\\u0000", 6); + break; + case '\r': + if( zIn[2]=='\n' ){ + zIn++; + N--; + } + break; + case '\n': + break; + case 0xe2: + assert( N>=4 ); + assert( 0x80==(u8)zIn[2] ); + assert( 0xa8==(u8)zIn[3] || 0xa9==(u8)zIn[3] ); + zIn += 2; + N -= 2; + break; + default: + jsonAppendRawNZ(p, zIn, 2); + break; + } + zIn += 2; + N -= 2; + } + jsonAppendChar(p, '"'); +} + +/* +** The zIn[0..N] string is a JSON5 integer literal. Append to p a translation +** of the string literal that standard JSON and that omits all JSON5 +** features. +*/ +static void jsonAppendNormalizedInt(JsonString *p, const char *zIn, u32 N){ + if( zIn[0]=='+' ){ + zIn++; + N--; + }else if( zIn[0]=='-' ){ + jsonAppendChar(p, '-'); + zIn++; + N--; + } + if( zIn[0]=='0' && (zIn[1]=='x' || zIn[1]=='X') ){ + sqlite3_int64 i = 0; + int rc = sqlite3DecOrHexToI64(zIn, &i); + if( rc<=1 ){ + jsonPrintf(100,p,"%lld",i); + }else{ + assert( rc==2 ); + jsonAppendRawNZ(p, "9.0e999", 7); + } + return; + } + assert( N>0 ); + jsonAppendRawNZ(p, zIn, N); +} + +/* +** The zIn[0..N] string is a JSON5 real literal. Append to p a translation +** of the string literal that standard JSON and that omits all JSON5 +** features. +*/ +static void jsonAppendNormalizedReal(JsonString *p, const char *zIn, u32 N){ + u32 i; + if( zIn[0]=='+' ){ + zIn++; + N--; + }else if( zIn[0]=='-' ){ + jsonAppendChar(p, '-'); + zIn++; + N--; + } + if( zIn[0]=='.' ){ + jsonAppendChar(p, '0'); + } + for(i=0; i<N; i++){ + if( zIn[i]=='.' && (i+1==N || !sqlite3Isdigit(zIn[i+1])) ){ + i++; + jsonAppendRaw(p, zIn, i); + zIn += i; + N -= i; + jsonAppendChar(p, '0'); + break; + } + } + if( N>0 ){ + jsonAppendRawNZ(p, zIn, N); + } +} + + + +/* +** Append a function parameter value to the JSON string under +** construction. +*/ +static void jsonAppendValue( + JsonString *p, /* Append to this JSON string */ + sqlite3_value *pValue /* Value to append */ +){ + switch( sqlite3_value_type(pValue) ){ + case SQLITE_NULL: { + jsonAppendRawNZ(p, "null", 4); + break; + } + case SQLITE_FLOAT: { + jsonPrintf(100, p, "%!0.15g", sqlite3_value_double(pValue)); + break; + } + case SQLITE_INTEGER: { + const char *z = (const char*)sqlite3_value_text(pValue); + u32 n = (u32)sqlite3_value_bytes(pValue); + jsonAppendRaw(p, z, n); + break; + } + case SQLITE_TEXT: { + const char *z = (const char*)sqlite3_value_text(pValue); + u32 n = (u32)sqlite3_value_bytes(pValue); + if( sqlite3_value_subtype(pValue)==JSON_SUBTYPE ){ + jsonAppendRaw(p, z, n); + }else{ + jsonAppendString(p, z, n); + } + break; + } + default: { + if( p->bErr==0 ){ + sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1); + p->bErr = 2; + jsonReset(p); + } + break; + } + } +} + + +/* Make the JSON in p the result of the SQL function. +** +** The JSON string is reset. +*/ +static void jsonResult(JsonString *p){ + if( p->bErr==0 ){ + if( p->bStatic ){ + sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, + SQLITE_TRANSIENT, SQLITE_UTF8); + }else if( jsonForceRCStr(p) ){ + sqlite3RCStrRef(p->zBuf); + sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, + (void(*)(void*))sqlite3RCStrUnref, + SQLITE_UTF8); + } + } + if( p->bErr==1 ){ + sqlite3_result_error_nomem(p->pCtx); + } + jsonReset(p); +} + +/************************************************************************** +** Utility routines for dealing with JsonNode and JsonParse objects +**************************************************************************/ + +/* +** Return the number of consecutive JsonNode slots need to represent +** the parsed JSON at pNode. The minimum answer is 1. For ARRAY and +** OBJECT types, the number might be larger. +** +** Appended elements are not counted. The value returned is the number +** by which the JsonNode counter should increment in order to go to the +** next peer value. +*/ +static u32 jsonNodeSize(JsonNode *pNode){ + return pNode->eType>=JSON_ARRAY ? pNode->n+1 : 1; +} + +/* +** Reclaim all memory allocated by a JsonParse object. But do not +** delete the JsonParse object itself. +*/ +static void jsonParseReset(JsonParse *pParse){ + while( pParse->pClup ){ + JsonCleanup *pTask = pParse->pClup; + pParse->pClup = pTask->pJCNext; + pTask->xOp(pTask->pArg); + sqlite3_free(pTask); + } + assert( pParse->nJPRef<=1 ); + if( pParse->aNode ){ + sqlite3_free(pParse->aNode); + pParse->aNode = 0; + } + pParse->nNode = 0; + pParse->nAlloc = 0; + if( pParse->aUp ){ + sqlite3_free(pParse->aUp); + pParse->aUp = 0; + } + if( pParse->bJsonIsRCStr ){ + sqlite3RCStrUnref(pParse->zJson); + pParse->zJson = 0; + pParse->bJsonIsRCStr = 0; + } + if( pParse->zAlt ){ + sqlite3RCStrUnref(pParse->zAlt); + pParse->zAlt = 0; + } +} + +/* +** Free a JsonParse object that was obtained from sqlite3_malloc(). +** +** Note that destroying JsonParse might call sqlite3RCStrUnref() to +** destroy the zJson value. The RCStr object might recursively invoke +** JsonParse to destroy this pParse object again. Take care to ensure +** that this recursive destructor sequence terminates harmlessly. +*/ +static void jsonParseFree(JsonParse *pParse){ + if( pParse->nJPRef>1 ){ + pParse->nJPRef--; + }else{ + jsonParseReset(pParse); + sqlite3_free(pParse); + } +} + +/* +** Add a cleanup task to the JsonParse object. +** +** If an OOM occurs, the cleanup operation happens immediately +** and this function returns SQLITE_NOMEM. +*/ +static int jsonParseAddCleanup( + JsonParse *pParse, /* Add the cleanup task to this parser */ + void(*xOp)(void*), /* The cleanup task */ + void *pArg /* Argument to the cleanup */ +){ + JsonCleanup *pTask = sqlite3_malloc64( sizeof(*pTask) ); + if( pTask==0 ){ + pParse->oom = 1; + xOp(pArg); + return SQLITE_ERROR; + } + pTask->pJCNext = pParse->pClup; + pParse->pClup = pTask; + pTask->xOp = xOp; + pTask->pArg = pArg; + return SQLITE_OK; +} + +/* +** Convert the JsonNode pNode into a pure JSON string and +** append to pOut. Subsubstructure is also included. Return +** the number of JsonNode objects that are encoded. +*/ +static void jsonRenderNode( + JsonParse *pParse, /* the complete parse of the JSON */ + JsonNode *pNode, /* The node to render */ + JsonString *pOut /* Write JSON here */ +){ + assert( pNode!=0 ); + while( (pNode->jnFlags & JNODE_REPLACE)!=0 && pParse->useMod ){ + u32 idx = (u32)(pNode - pParse->aNode); + u32 i = pParse->iSubst; + while( 1 /*exit-by-break*/ ){ + assert( i<pParse->nNode ); + assert( pParse->aNode[i].eType==JSON_SUBST ); + assert( pParse->aNode[i].eU==4 ); + assert( pParse->aNode[i].u.iPrev<i ); + if( pParse->aNode[i].n==idx ){ + pNode = &pParse->aNode[i+1]; + break; + } + i = pParse->aNode[i].u.iPrev; + } + } + switch( pNode->eType ){ + default: { + assert( pNode->eType==JSON_NULL ); + jsonAppendRawNZ(pOut, "null", 4); + break; + } + case JSON_TRUE: { + jsonAppendRawNZ(pOut, "true", 4); + break; + } + case JSON_FALSE: { + jsonAppendRawNZ(pOut, "false", 5); + break; + } + case JSON_STRING: { + assert( pNode->eU==1 ); + if( pNode->jnFlags & JNODE_RAW ){ + if( pNode->jnFlags & JNODE_LABEL ){ + jsonAppendChar(pOut, '"'); + jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + jsonAppendChar(pOut, '"'); + }else{ + jsonAppendString(pOut, pNode->u.zJContent, pNode->n); + } + }else if( pNode->jnFlags & JNODE_JSON5 ){ + jsonAppendNormalizedString(pOut, pNode->u.zJContent, pNode->n); + }else{ + assert( pNode->n>0 ); + jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); + } + break; + } + case JSON_REAL: { + assert( pNode->eU==1 ); + if( pNode->jnFlags & JNODE_JSON5 ){ + jsonAppendNormalizedReal(pOut, pNode->u.zJContent, pNode->n); + }else{ + assert( pNode->n>0 ); + jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); + } + break; + } + case JSON_INT: { + assert( pNode->eU==1 ); + if( pNode->jnFlags & JNODE_JSON5 ){ + jsonAppendNormalizedInt(pOut, pNode->u.zJContent, pNode->n); + }else{ + assert( pNode->n>0 ); + jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); + } + break; + } + case JSON_ARRAY: { + u32 j = 1; + jsonAppendChar(pOut, '['); + for(;;){ + while( j<=pNode->n ){ + if( (pNode[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ + jsonAppendSeparator(pOut); + jsonRenderNode(pParse, &pNode[j], pOut); + } + j += jsonNodeSize(&pNode[j]); + } + if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; + assert( pNode->eU==2 ); + pNode = &pParse->aNode[pNode->u.iAppend]; + j = 1; + } + jsonAppendChar(pOut, ']'); + break; + } + case JSON_OBJECT: { + u32 j = 1; + jsonAppendChar(pOut, '{'); + for(;;){ + while( j<=pNode->n ){ + if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ + jsonAppendSeparator(pOut); + jsonRenderNode(pParse, &pNode[j], pOut); + jsonAppendChar(pOut, ':'); + jsonRenderNode(pParse, &pNode[j+1], pOut); + } + j += 1 + jsonNodeSize(&pNode[j+1]); + } + if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; + assert( pNode->eU==2 ); + pNode = &pParse->aNode[pNode->u.iAppend]; + j = 1; + } + jsonAppendChar(pOut, '}'); + break; + } + } +} + +/* +** Return a JsonNode and all its descendants as a JSON string. +*/ +static void jsonReturnJson( + JsonParse *pParse, /* The complete JSON */ + JsonNode *pNode, /* Node to return */ + sqlite3_context *pCtx, /* Return value for this function */ + int bGenerateAlt /* Also store the rendered text in zAlt */ +){ + JsonString s; + if( pParse->oom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( pParse->nErr==0 ){ + jsonInit(&s, pCtx); + jsonRenderNode(pParse, pNode, &s); + if( bGenerateAlt && pParse->zAlt==0 && jsonForceRCStr(&s) ){ + pParse->zAlt = sqlite3RCStrRef(s.zBuf); + pParse->nAlt = s.nUsed; + } + jsonResult(&s); + sqlite3_result_subtype(pCtx, JSON_SUBTYPE); + } +} + +/* +** Translate a single byte of Hex into an integer. +** This routine only works if h really is a valid hexadecimal +** character: 0..9a..fA..F +*/ +static u8 jsonHexToInt(int h){ + assert( (h>='0' && h<='9') || (h>='a' && h<='f') || (h>='A' && h<='F') ); +#ifdef SQLITE_EBCDIC + h += 9*(1&~(h>>4)); +#else + h += 9*(1&(h>>6)); +#endif + return (u8)(h & 0xf); +} + +/* +** Convert a 4-byte hex string into an integer +*/ +static u32 jsonHexToInt4(const char *z){ + u32 v; + assert( sqlite3Isxdigit(z[0]) ); + assert( sqlite3Isxdigit(z[1]) ); + assert( sqlite3Isxdigit(z[2]) ); + assert( sqlite3Isxdigit(z[3]) ); + v = (jsonHexToInt(z[0])<<12) + + (jsonHexToInt(z[1])<<8) + + (jsonHexToInt(z[2])<<4) + + jsonHexToInt(z[3]); + return v; +} + +/* +** Make the JsonNode the return value of the function. +*/ +static void jsonReturn( + JsonParse *pParse, /* Complete JSON parse tree */ + JsonNode *pNode, /* Node to return */ + sqlite3_context *pCtx /* Return value for this function */ +){ + switch( pNode->eType ){ + default: { + assert( pNode->eType==JSON_NULL ); + sqlite3_result_null(pCtx); + break; + } + case JSON_TRUE: { + sqlite3_result_int(pCtx, 1); + break; + } + case JSON_FALSE: { + sqlite3_result_int(pCtx, 0); + break; + } + case JSON_INT: { + sqlite3_int64 i = 0; + int rc; + int bNeg = 0; + const char *z; + + assert( pNode->eU==1 ); + z = pNode->u.zJContent; + if( z[0]=='-' ){ z++; bNeg = 1; } + else if( z[0]=='+' ){ z++; } + rc = sqlite3DecOrHexToI64(z, &i); + if( rc<=1 ){ + sqlite3_result_int64(pCtx, bNeg ? -i : i); + }else if( rc==3 && bNeg ){ + sqlite3_result_int64(pCtx, SMALLEST_INT64); + }else{ + goto to_double; + } + break; + } + case JSON_REAL: { + double r; + const char *z; + assert( pNode->eU==1 ); + to_double: + z = pNode->u.zJContent; + sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); + sqlite3_result_double(pCtx, r); + break; + } + case JSON_STRING: { + if( pNode->jnFlags & JNODE_RAW ){ + assert( pNode->eU==1 ); + sqlite3_result_text(pCtx, pNode->u.zJContent, pNode->n, + SQLITE_TRANSIENT); + }else if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){ + /* JSON formatted without any backslash-escapes */ + assert( pNode->eU==1 ); + sqlite3_result_text(pCtx, pNode->u.zJContent+1, pNode->n-2, + SQLITE_TRANSIENT); + }else{ + /* Translate JSON formatted string into raw text */ + u32 i; + u32 n = pNode->n; + const char *z; + char *zOut; + u32 j; + u32 nOut = n; + assert( pNode->eU==1 ); + z = pNode->u.zJContent; + zOut = sqlite3_malloc( nOut+1 ); + if( zOut==0 ){ + sqlite3_result_error_nomem(pCtx); + break; + } + for(i=1, j=0; i<n-1; i++){ + char c = z[i]; + if( c=='\\' ){ + c = z[++i]; + if( c=='u' ){ + u32 v = jsonHexToInt4(z+i+1); + i += 4; + if( v==0 ) break; + if( v<=0x7f ){ + zOut[j++] = (char)v; + }else if( v<=0x7ff ){ + zOut[j++] = (char)(0xc0 | (v>>6)); + zOut[j++] = 0x80 | (v&0x3f); + }else{ + u32 vlo; + if( (v&0xfc00)==0xd800 + && i<n-6 + && z[i+1]=='\\' + && z[i+2]=='u' + && ((vlo = jsonHexToInt4(z+i+3))&0xfc00)==0xdc00 + ){ + /* We have a surrogate pair */ + v = ((v&0x3ff)<<10) + (vlo&0x3ff) + 0x10000; + i += 6; + zOut[j++] = 0xf0 | (v>>18); + zOut[j++] = 0x80 | ((v>>12)&0x3f); + zOut[j++] = 0x80 | ((v>>6)&0x3f); + zOut[j++] = 0x80 | (v&0x3f); + }else{ + zOut[j++] = 0xe0 | (v>>12); + zOut[j++] = 0x80 | ((v>>6)&0x3f); + zOut[j++] = 0x80 | (v&0x3f); + } + } + continue; + }else if( c=='b' ){ + c = '\b'; + }else if( c=='f' ){ + c = '\f'; + }else if( c=='n' ){ + c = '\n'; + }else if( c=='r' ){ + c = '\r'; + }else if( c=='t' ){ + c = '\t'; + }else if( c=='v' ){ + c = '\v'; + }else if( c=='\'' || c=='"' || c=='/' || c=='\\' ){ + /* pass through unchanged */ + }else if( c=='0' ){ + c = 0; + }else if( c=='x' ){ + c = (jsonHexToInt(z[i+1])<<4) | jsonHexToInt(z[i+2]); + i += 2; + }else if( c=='\r' && z[i+1]=='\n' ){ + i++; + continue; + }else if( 0xe2==(u8)c ){ + assert( 0x80==(u8)z[i+1] ); + assert( 0xa8==(u8)z[i+2] || 0xa9==(u8)z[i+2] ); + i += 2; + continue; + }else{ + continue; + } + } /* end if( c=='\\' ) */ + zOut[j++] = c; + } /* end for() */ + zOut[j] = 0; + sqlite3_result_text(pCtx, zOut, j, sqlite3_free); + } + break; + } + case JSON_ARRAY: + case JSON_OBJECT: { + jsonReturnJson(pParse, pNode, pCtx, 0); + break; + } + } +} + +/* Forward reference */ +static int jsonParseAddNode(JsonParse*,u32,u32,const char*); + +/* +** A macro to hint to the compiler that a function should not be +** inlined. +*/ +#if defined(__GNUC__) +# define JSON_NOINLINE __attribute__((noinline)) +#elif defined(_MSC_VER) && _MSC_VER>=1310 +# define JSON_NOINLINE __declspec(noinline) +#else +# define JSON_NOINLINE +#endif + + +/* +** Add a single node to pParse->aNode after first expanding the +** size of the aNode array. Return the index of the new node. +** +** If an OOM error occurs, set pParse->oom and return -1. +*/ +static JSON_NOINLINE int jsonParseAddNodeExpand( + JsonParse *pParse, /* Append the node to this object */ + u32 eType, /* Node type */ + u32 n, /* Content size or sub-node count */ + const char *zContent /* Content */ +){ + u32 nNew; + JsonNode *pNew; + assert( pParse->nNode>=pParse->nAlloc ); + if( pParse->oom ) return -1; + nNew = pParse->nAlloc*2 + 10; + pNew = sqlite3_realloc64(pParse->aNode, sizeof(JsonNode)*nNew); + if( pNew==0 ){ + pParse->oom = 1; + return -1; + } + pParse->nAlloc = sqlite3_msize(pNew)/sizeof(JsonNode); + pParse->aNode = pNew; + assert( pParse->nNode<pParse->nAlloc ); + return jsonParseAddNode(pParse, eType, n, zContent); +} + +/* +** Create a new JsonNode instance based on the arguments and append that +** instance to the JsonParse. Return the index in pParse->aNode[] of the +** new node, or -1 if a memory allocation fails. +*/ +static int jsonParseAddNode( + JsonParse *pParse, /* Append the node to this object */ + u32 eType, /* Node type */ + u32 n, /* Content size or sub-node count */ + const char *zContent /* Content */ +){ + JsonNode *p; + assert( pParse->aNode!=0 || pParse->nNode>=pParse->nAlloc ); + if( pParse->nNode>=pParse->nAlloc ){ + return jsonParseAddNodeExpand(pParse, eType, n, zContent); + } + assert( pParse->aNode!=0 ); + p = &pParse->aNode[pParse->nNode]; + assert( p!=0 ); + p->eType = (u8)(eType & 0xff); + p->jnFlags = (u8)(eType >> 8); + VVA( p->eU = zContent ? 1 : 0 ); + p->n = n; + p->u.zJContent = zContent; + return pParse->nNode++; +} + +/* +** Add an array of new nodes to the current pParse->aNode array. +** Return the index of the first node added. +** +** If an OOM error occurs, set pParse->oom. +*/ +static void jsonParseAddNodeArray( + JsonParse *pParse, /* Append the node to this object */ + JsonNode *aNode, /* Array of nodes to add */ + u32 nNode /* Number of elements in aNew */ +){ + assert( aNode!=0 ); + assert( nNode>=1 ); + if( pParse->nNode + nNode > pParse->nAlloc ){ + u32 nNew = pParse->nNode + nNode; + JsonNode *aNew = sqlite3_realloc64(pParse->aNode, nNew*sizeof(JsonNode)); + if( aNew==0 ){ + pParse->oom = 1; + return; + } + pParse->nAlloc = sqlite3_msize(aNew)/sizeof(JsonNode); + pParse->aNode = aNew; + } + memcpy(&pParse->aNode[pParse->nNode], aNode, nNode*sizeof(JsonNode)); + pParse->nNode += nNode; +} + +/* +** Add a new JSON_SUBST node. The node immediately following +** this new node will be the substitute content for iNode. +*/ +static int jsonParseAddSubstNode( + JsonParse *pParse, /* Add the JSON_SUBST here */ + u32 iNode /* References this node */ +){ + int idx = jsonParseAddNode(pParse, JSON_SUBST, iNode, 0); + if( pParse->oom ) return -1; + pParse->aNode[iNode].jnFlags |= JNODE_REPLACE; + pParse->aNode[idx].eU = 4; + pParse->aNode[idx].u.iPrev = pParse->iSubst; + pParse->iSubst = idx; + pParse->hasMod = 1; + pParse->useMod = 1; + return idx; +} + +/* +** Return true if z[] begins with 2 (or more) hexadecimal digits +*/ +static int jsonIs2Hex(const char *z){ + return sqlite3Isxdigit(z[0]) && sqlite3Isxdigit(z[1]); +} + +/* +** Return true if z[] begins with 4 (or more) hexadecimal digits +*/ +static int jsonIs4Hex(const char *z){ + return jsonIs2Hex(z) && jsonIs2Hex(&z[2]); +} + +/* +** Return the number of bytes of JSON5 whitespace at the beginning of +** the input string z[]. +** +** JSON5 whitespace consists of any of the following characters: +** +** Unicode UTF-8 Name +** U+0009 09 horizontal tab +** U+000a 0a line feed +** U+000b 0b vertical tab +** U+000c 0c form feed +** U+000d 0d carriage return +** U+0020 20 space +** U+00a0 c2 a0 non-breaking space +** U+1680 e1 9a 80 ogham space mark +** U+2000 e2 80 80 en quad +** U+2001 e2 80 81 em quad +** U+2002 e2 80 82 en space +** U+2003 e2 80 83 em space +** U+2004 e2 80 84 three-per-em space +** U+2005 e2 80 85 four-per-em space +** U+2006 e2 80 86 six-per-em space +** U+2007 e2 80 87 figure space +** U+2008 e2 80 88 punctuation space +** U+2009 e2 80 89 thin space +** U+200a e2 80 8a hair space +** U+2028 e2 80 a8 line separator +** U+2029 e2 80 a9 paragraph separator +** U+202f e2 80 af narrow no-break space (NNBSP) +** U+205f e2 81 9f medium mathematical space (MMSP) +** U+3000 e3 80 80 ideographical space +** U+FEFF ef bb bf byte order mark +** +** In addition, comments between '/', '*' and '*', '/' and +** from '/', '/' to end-of-line are also considered to be whitespace. +*/ +static int json5Whitespace(const char *zIn){ + int n = 0; + const u8 *z = (u8*)zIn; + while( 1 /*exit by "goto whitespace_done"*/ ){ + switch( z[n] ){ + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x20: { + n++; + break; + } + case '/': { + if( z[n+1]=='*' && z[n+2]!=0 ){ + int j; + for(j=n+3; z[j]!='/' || z[j-1]!='*'; j++){ + if( z[j]==0 ) goto whitespace_done; + } + n = j+1; + break; + }else if( z[n+1]=='/' ){ + int j; + char c; + for(j=n+2; (c = z[j])!=0; j++){ + if( c=='\n' || c=='\r' ) break; + if( 0xe2==(u8)c && 0x80==(u8)z[j+1] + && (0xa8==(u8)z[j+2] || 0xa9==(u8)z[j+2]) + ){ + j += 2; + break; + } + } + n = j; + if( z[n] ) n++; + break; + } + goto whitespace_done; + } + case 0xc2: { + if( z[n+1]==0xa0 ){ + n += 2; + break; + } + goto whitespace_done; + } + case 0xe1: { + if( z[n+1]==0x9a && z[n+2]==0x80 ){ + n += 3; + break; + } + goto whitespace_done; + } + case 0xe2: { + if( z[n+1]==0x80 ){ + u8 c = z[n+2]; + if( c<0x80 ) goto whitespace_done; + if( c<=0x8a || c==0xa8 || c==0xa9 || c==0xaf ){ + n += 3; + break; + } + }else if( z[n+1]==0x81 && z[n+2]==0x9f ){ + n += 3; + break; + } + goto whitespace_done; + } + case 0xe3: { + if( z[n+1]==0x80 && z[n+2]==0x80 ){ + n += 3; + break; + } + goto whitespace_done; + } + case 0xef: { + if( z[n+1]==0xbb && z[n+2]==0xbf ){ + n += 3; + break; + } + goto whitespace_done; + } + default: { + goto whitespace_done; + } + } + } + whitespace_done: + return n; +} + +/* +** Extra floating-point literals to allow in JSON. +*/ +static const struct NanInfName { + char c1; + char c2; + char n; + char eType; + char nRepl; + char *zMatch; + char *zRepl; +} aNanInfName[] = { + { 'i', 'I', 3, JSON_REAL, 7, "inf", "9.0e999" }, + { 'i', 'I', 8, JSON_REAL, 7, "infinity", "9.0e999" }, + { 'n', 'N', 3, JSON_NULL, 4, "NaN", "null" }, + { 'q', 'Q', 4, JSON_NULL, 4, "QNaN", "null" }, + { 's', 'S', 4, JSON_NULL, 4, "SNaN", "null" }, +}; + +/* +** Parse a single JSON value which begins at pParse->zJson[i]. Return the +** index of the first character past the end of the value parsed. +** +** Special return values: +** +** 0 End of input +** -1 Syntax error +** -2 '}' seen +** -3 ']' seen +** -4 ',' seen +** -5 ':' seen +*/ +static int jsonParseValue(JsonParse *pParse, u32 i){ + char c; + u32 j; + int iThis; + int x; + JsonNode *pNode; + const char *z = pParse->zJson; +json_parse_restart: + switch( (u8)z[i] ){ + case '{': { + /* Parse object */ + iThis = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); + if( iThis<0 ) return -1; + if( ++pParse->iDepth > JSON_MAX_DEPTH ){ + pParse->iErr = i; + return -1; + } + for(j=i+1;;j++){ + u32 nNode = pParse->nNode; + x = jsonParseValue(pParse, j); + if( x<=0 ){ + if( x==(-2) ){ + j = pParse->iErr; + if( pParse->nNode!=(u32)iThis+1 ) pParse->hasNonstd = 1; + break; + } + j += json5Whitespace(&z[j]); + if( sqlite3JsonId1(z[j]) + || (z[j]=='\\' && z[j+1]=='u' && jsonIs4Hex(&z[j+2])) + ){ + int k = j+1; + while( (sqlite3JsonId2(z[k]) && json5Whitespace(&z[k])==0) + || (z[k]=='\\' && z[k+1]=='u' && jsonIs4Hex(&z[k+2])) + ){ + k++; + } + jsonParseAddNode(pParse, JSON_STRING | (JNODE_RAW<<8), k-j, &z[j]); + pParse->hasNonstd = 1; + x = k; + }else{ + if( x!=-1 ) pParse->iErr = j; + return -1; + } + } + if( pParse->oom ) return -1; + pNode = &pParse->aNode[nNode]; + if( pNode->eType!=JSON_STRING ){ + pParse->iErr = j; + return -1; + } + pNode->jnFlags |= JNODE_LABEL; + j = x; + if( z[j]==':' ){ + j++; + }else{ + if( fast_isspace(z[j]) ){ + do{ j++; }while( fast_isspace(z[j]) ); + if( z[j]==':' ){ + j++; + goto parse_object_value; + } + } + x = jsonParseValue(pParse, j); + if( x!=(-5) ){ + if( x!=(-1) ) pParse->iErr = j; + return -1; + } + j = pParse->iErr+1; + } + parse_object_value: + x = jsonParseValue(pParse, j); + if( x<=0 ){ + if( x!=(-1) ) pParse->iErr = j; + return -1; + } + j = x; + if( z[j]==',' ){ + continue; + }else if( z[j]=='}' ){ + break; + }else{ + if( fast_isspace(z[j]) ){ + do{ j++; }while( fast_isspace(z[j]) ); + if( z[j]==',' ){ + continue; + }else if( z[j]=='}' ){ + break; + } + } + x = jsonParseValue(pParse, j); + if( x==(-4) ){ + j = pParse->iErr; + continue; + } + if( x==(-2) ){ + j = pParse->iErr; + break; + } + } + pParse->iErr = j; + return -1; + } + pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1; + pParse->iDepth--; + return j+1; + } + case '[': { + /* Parse array */ + iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); + if( iThis<0 ) return -1; + if( ++pParse->iDepth > JSON_MAX_DEPTH ){ + pParse->iErr = i; + return -1; + } + memset(&pParse->aNode[iThis].u, 0, sizeof(pParse->aNode[iThis].u)); + for(j=i+1;;j++){ + x = jsonParseValue(pParse, j); + if( x<=0 ){ + if( x==(-3) ){ + j = pParse->iErr; + if( pParse->nNode!=(u32)iThis+1 ) pParse->hasNonstd = 1; + break; + } + if( x!=(-1) ) pParse->iErr = j; + return -1; + } + j = x; + if( z[j]==',' ){ + continue; + }else if( z[j]==']' ){ + break; + }else{ + if( fast_isspace(z[j]) ){ + do{ j++; }while( fast_isspace(z[j]) ); + if( z[j]==',' ){ + continue; + }else if( z[j]==']' ){ + break; + } + } + x = jsonParseValue(pParse, j); + if( x==(-4) ){ + j = pParse->iErr; + continue; + } + if( x==(-3) ){ + j = pParse->iErr; + break; + } + } + pParse->iErr = j; + return -1; + } + pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1; + pParse->iDepth--; + return j+1; + } + case '\'': { + u8 jnFlags; + char cDelim; + pParse->hasNonstd = 1; + jnFlags = JNODE_JSON5; + goto parse_string; + case '"': + /* Parse string */ + jnFlags = 0; + parse_string: + cDelim = z[i]; + for(j=i+1; 1; j++){ + if( jsonIsOk[(unsigned char)z[j]] ) continue; + c = z[j]; + if( c==cDelim ){ + break; + }else if( c=='\\' ){ + c = z[++j]; + if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f' + || c=='n' || c=='r' || c=='t' + || (c=='u' && jsonIs4Hex(&z[j+1])) ){ + jnFlags |= JNODE_ESCAPE; + }else if( c=='\'' || c=='0' || c=='v' || c=='\n' + || (0xe2==(u8)c && 0x80==(u8)z[j+1] + && (0xa8==(u8)z[j+2] || 0xa9==(u8)z[j+2])) + || (c=='x' && jsonIs2Hex(&z[j+1])) ){ + jnFlags |= (JNODE_ESCAPE|JNODE_JSON5); + pParse->hasNonstd = 1; + }else if( c=='\r' ){ + if( z[j+1]=='\n' ) j++; + jnFlags |= (JNODE_ESCAPE|JNODE_JSON5); + pParse->hasNonstd = 1; + }else{ + pParse->iErr = j; + return -1; + } + }else if( c<=0x1f ){ + /* Control characters are not allowed in strings */ + pParse->iErr = j; + return -1; + } + } + jsonParseAddNode(pParse, JSON_STRING | (jnFlags<<8), j+1-i, &z[i]); + return j+1; + } + case 't': { + if( strncmp(z+i,"true",4)==0 && !sqlite3Isalnum(z[i+4]) ){ + jsonParseAddNode(pParse, JSON_TRUE, 0, 0); + return i+4; + } + pParse->iErr = i; + return -1; + } + case 'f': { + if( strncmp(z+i,"false",5)==0 && !sqlite3Isalnum(z[i+5]) ){ + jsonParseAddNode(pParse, JSON_FALSE, 0, 0); + return i+5; + } + pParse->iErr = i; + return -1; + } + case '+': { + u8 seenDP, seenE, jnFlags; + pParse->hasNonstd = 1; + jnFlags = JNODE_JSON5; + goto parse_number; + case '.': + if( sqlite3Isdigit(z[i+1]) ){ + pParse->hasNonstd = 1; + jnFlags = JNODE_JSON5; + seenE = 0; + seenDP = JSON_REAL; + goto parse_number_2; + } + pParse->iErr = i; + return -1; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + /* Parse number */ + jnFlags = 0; + parse_number: + seenDP = JSON_INT; + seenE = 0; + assert( '-' < '0' ); + assert( '+' < '0' ); + assert( '.' < '0' ); + c = z[i]; + + if( c<='0' ){ + if( c=='0' ){ + if( (z[i+1]=='x' || z[i+1]=='X') && sqlite3Isxdigit(z[i+2]) ){ + assert( seenDP==JSON_INT ); + pParse->hasNonstd = 1; + jnFlags |= JNODE_JSON5; + for(j=i+3; sqlite3Isxdigit(z[j]); j++){} + goto parse_number_finish; + }else if( sqlite3Isdigit(z[i+1]) ){ + pParse->iErr = i+1; + return -1; + } + }else{ + if( !sqlite3Isdigit(z[i+1]) ){ + /* JSON5 allows for "+Infinity" and "-Infinity" using exactly + ** that case. SQLite also allows these in any case and it allows + ** "+inf" and "-inf". */ + if( (z[i+1]=='I' || z[i+1]=='i') + && sqlite3StrNICmp(&z[i+1], "inf",3)==0 + ){ + pParse->hasNonstd = 1; + if( z[i]=='-' ){ + jsonParseAddNode(pParse, JSON_REAL, 8, "-9.0e999"); + }else{ + jsonParseAddNode(pParse, JSON_REAL, 7, "9.0e999"); + } + return i + (sqlite3StrNICmp(&z[i+4],"inity",5)==0 ? 9 : 4); + } + if( z[i+1]=='.' ){ + pParse->hasNonstd = 1; + jnFlags |= JNODE_JSON5; + goto parse_number_2; + } + pParse->iErr = i; + return -1; + } + if( z[i+1]=='0' ){ + if( sqlite3Isdigit(z[i+2]) ){ + pParse->iErr = i+1; + return -1; + }else if( (z[i+2]=='x' || z[i+2]=='X') && sqlite3Isxdigit(z[i+3]) ){ + pParse->hasNonstd = 1; + jnFlags |= JNODE_JSON5; + for(j=i+4; sqlite3Isxdigit(z[j]); j++){} + goto parse_number_finish; + } + } + } + } + parse_number_2: + for(j=i+1;; j++){ + c = z[j]; + if( sqlite3Isdigit(c) ) continue; + if( c=='.' ){ + if( seenDP==JSON_REAL ){ + pParse->iErr = j; + return -1; + } + seenDP = JSON_REAL; + continue; + } + if( c=='e' || c=='E' ){ + if( z[j-1]<'0' ){ + if( ALWAYS(z[j-1]=='.') && ALWAYS(j-2>=i) && sqlite3Isdigit(z[j-2]) ){ + pParse->hasNonstd = 1; + jnFlags |= JNODE_JSON5; + }else{ + pParse->iErr = j; + return -1; + } + } + if( seenE ){ + pParse->iErr = j; + return -1; + } + seenDP = JSON_REAL; + seenE = 1; + c = z[j+1]; + if( c=='+' || c=='-' ){ + j++; + c = z[j+1]; + } + if( c<'0' || c>'9' ){ + pParse->iErr = j; + return -1; + } + continue; + } + break; + } + if( z[j-1]<'0' ){ + if( ALWAYS(z[j-1]=='.') && ALWAYS(j-2>=i) && sqlite3Isdigit(z[j-2]) ){ + pParse->hasNonstd = 1; + jnFlags |= JNODE_JSON5; + }else{ + pParse->iErr = j; + return -1; + } + } + parse_number_finish: + jsonParseAddNode(pParse, seenDP | (jnFlags<<8), j - i, &z[i]); + return j; + } + case '}': { + pParse->iErr = i; + return -2; /* End of {...} */ + } + case ']': { + pParse->iErr = i; + return -3; /* End of [...] */ + } + case ',': { + pParse->iErr = i; + return -4; /* List separator */ + } + case ':': { + pParse->iErr = i; + return -5; /* Object label/value separator */ + } + case 0: { + return 0; /* End of file */ + } + case 0x09: + case 0x0a: + case 0x0d: + case 0x20: { + do{ + i++; + }while( fast_isspace(z[i]) ); + goto json_parse_restart; + } + case 0x0b: + case 0x0c: + case '/': + case 0xc2: + case 0xe1: + case 0xe2: + case 0xe3: + case 0xef: { + j = json5Whitespace(&z[i]); + if( j>0 ){ + i += j; + pParse->hasNonstd = 1; + goto json_parse_restart; + } + pParse->iErr = i; + return -1; + } + case 'n': { + if( strncmp(z+i,"null",4)==0 && !sqlite3Isalnum(z[i+4]) ){ + jsonParseAddNode(pParse, JSON_NULL, 0, 0); + return i+4; + } + /* fall-through into the default case that checks for NaN */ + } + default: { + u32 k; + int nn; + c = z[i]; + for(k=0; k<sizeof(aNanInfName)/sizeof(aNanInfName[0]); k++){ + if( c!=aNanInfName[k].c1 && c!=aNanInfName[k].c2 ) continue; + nn = aNanInfName[k].n; + if( sqlite3StrNICmp(&z[i], aNanInfName[k].zMatch, nn)!=0 ){ + continue; + } + if( sqlite3Isalnum(z[i+nn]) ) continue; + jsonParseAddNode(pParse, aNanInfName[k].eType, + aNanInfName[k].nRepl, aNanInfName[k].zRepl); + pParse->hasNonstd = 1; + return i + nn; + } + pParse->iErr = i; + return -1; /* Syntax error */ + } + } /* End switch(z[i]) */ +} + +/* +** Parse a complete JSON string. Return 0 on success or non-zero if there +** are any errors. If an error occurs, free all memory held by pParse, +** but not pParse itself. +** +** pParse must be initialized to an empty parse object prior to calling +** this routine. +*/ +static int jsonParse( + JsonParse *pParse, /* Initialize and fill this JsonParse object */ + sqlite3_context *pCtx /* Report errors here */ +){ + int i; + const char *zJson = pParse->zJson; + i = jsonParseValue(pParse, 0); + if( pParse->oom ) i = -1; + if( i>0 ){ + assert( pParse->iDepth==0 ); + while( fast_isspace(zJson[i]) ) i++; + if( zJson[i] ){ + i += json5Whitespace(&zJson[i]); + if( zJson[i] ){ + jsonParseReset(pParse); + return 1; + } + pParse->hasNonstd = 1; + } + } + if( i<=0 ){ + if( pCtx!=0 ){ + if( pParse->oom ){ + sqlite3_result_error_nomem(pCtx); + }else{ + sqlite3_result_error(pCtx, "malformed JSON", -1); + } + } + jsonParseReset(pParse); + return 1; + } + return 0; +} + + +/* Mark node i of pParse as being a child of iParent. Call recursively +** to fill in all the descendants of node i. +*/ +static void jsonParseFillInParentage(JsonParse *pParse, u32 i, u32 iParent){ + JsonNode *pNode = &pParse->aNode[i]; + u32 j; + pParse->aUp[i] = iParent; + switch( pNode->eType ){ + case JSON_ARRAY: { + for(j=1; j<=pNode->n; j += jsonNodeSize(pNode+j)){ + jsonParseFillInParentage(pParse, i+j, i); + } + break; + } + case JSON_OBJECT: { + for(j=1; j<=pNode->n; j += jsonNodeSize(pNode+j+1)+1){ + pParse->aUp[i+j] = i; + jsonParseFillInParentage(pParse, i+j+1, i); + } + break; + } + default: { + break; + } + } +} + +/* +** Compute the parentage of all nodes in a completed parse. +*/ +static int jsonParseFindParents(JsonParse *pParse){ + u32 *aUp; + assert( pParse->aUp==0 ); + aUp = pParse->aUp = sqlite3_malloc64( sizeof(u32)*pParse->nNode ); + if( aUp==0 ){ + pParse->oom = 1; + return SQLITE_NOMEM; + } + jsonParseFillInParentage(pParse, 0, 0); + return SQLITE_OK; +} + +/* +** Magic number used for the JSON parse cache in sqlite3_get_auxdata() +*/ +#define JSON_CACHE_ID (-429938) /* First cache entry */ +#define JSON_CACHE_SZ 4 /* Max number of cache entries */ + +/* +** Obtain a complete parse of the JSON found in the pJson argument +** +** Use the sqlite3_get_auxdata() cache to find a preexisting parse +** if it is available. If the cache is not available or if it +** is no longer valid, parse the JSON again and return the new parse. +** Also register the new parse so that it will be available for +** future sqlite3_get_auxdata() calls. +** +** If an error occurs and pErrCtx!=0 then report the error on pErrCtx +** and return NULL. +** +** The returned pointer (if it is not NULL) is owned by the cache in +** most cases, not the caller. The caller does NOT need to invoke +** jsonParseFree(), in most cases. +** +** Except, if an error occurs and pErrCtx==0 then return the JsonParse +** object with JsonParse.nErr non-zero and the caller will own the JsonParse +** object. In that case, it will be the responsibility of the caller to +** invoke jsonParseFree(). To summarize: +** +** pErrCtx!=0 || p->nErr==0 ==> Return value p is owned by the +** cache. Call does not need to +** free it. +** +** pErrCtx==0 && p->nErr!=0 ==> Return value is owned by the caller +** and so the caller must free it. +*/ +static JsonParse *jsonParseCached( + sqlite3_context *pCtx, /* Context to use for cache search */ + sqlite3_value *pJson, /* Function param containing JSON text */ + sqlite3_context *pErrCtx, /* Write parse errors here if not NULL */ + int bUnedited /* No prior edits allowed */ +){ + char *zJson = (char*)sqlite3_value_text(pJson); + int nJson = sqlite3_value_bytes(pJson); + JsonParse *p; + JsonParse *pMatch = 0; + int iKey; + int iMinKey = 0; + u32 iMinHold = 0xffffffff; + u32 iMaxHold = 0; + int bJsonRCStr; + + if( zJson==0 ) return 0; + for(iKey=0; iKey<JSON_CACHE_SZ; iKey++){ + p = (JsonParse*)sqlite3_get_auxdata(pCtx, JSON_CACHE_ID+iKey); + if( p==0 ){ + iMinKey = iKey; + break; + } + if( pMatch==0 + && p->nJson==nJson + && (p->hasMod==0 || bUnedited==0) + && (p->zJson==zJson || memcmp(p->zJson,zJson,nJson)==0) + ){ + p->nErr = 0; + p->useMod = 0; + pMatch = p; + }else + if( pMatch==0 + && p->zAlt!=0 + && bUnedited==0 + && p->nAlt==nJson + && memcmp(p->zAlt, zJson, nJson)==0 + ){ + p->nErr = 0; + p->useMod = 1; + pMatch = p; + }else if( p->iHold<iMinHold ){ + iMinHold = p->iHold; + iMinKey = iKey; + } + if( p->iHold>iMaxHold ){ + iMaxHold = p->iHold; + } + } + if( pMatch ){ + /* The input JSON text was found in the cache. Use the preexisting + ** parse of this JSON */ + pMatch->nErr = 0; + pMatch->iHold = iMaxHold+1; + assert( pMatch->nJPRef>0 ); /* pMatch is owned by the cache */ + return pMatch; + } + + /* The input JSON was not found anywhere in the cache. We will need + ** to parse it ourselves and generate a new JsonParse object. + */ + bJsonRCStr = sqlite3ValueIsOfClass(pJson,(void(*)(void*))sqlite3RCStrUnref); + p = sqlite3_malloc64( sizeof(*p) + (bJsonRCStr ? 0 : nJson+1) ); + if( p==0 ){ + sqlite3_result_error_nomem(pCtx); + return 0; + } + memset(p, 0, sizeof(*p)); + if( bJsonRCStr ){ + p->zJson = sqlite3RCStrRef(zJson); + p->bJsonIsRCStr = 1; + }else{ + p->zJson = (char*)&p[1]; + memcpy(p->zJson, zJson, nJson+1); + } + p->nJPRef = 1; + if( jsonParse(p, pErrCtx) ){ + if( pErrCtx==0 ){ + p->nErr = 1; + assert( p->nJPRef==1 ); /* Caller will own the new JsonParse object p */ + return p; + } + jsonParseFree(p); + return 0; + } + p->nJson = nJson; + p->iHold = iMaxHold+1; + /* Transfer ownership of the new JsonParse to the cache */ + sqlite3_set_auxdata(pCtx, JSON_CACHE_ID+iMinKey, p, + (void(*)(void*))jsonParseFree); + return (JsonParse*)sqlite3_get_auxdata(pCtx, JSON_CACHE_ID+iMinKey); +} + +/* +** Compare the OBJECT label at pNode against zKey,nKey. Return true on +** a match. +*/ +static int jsonLabelCompare(const JsonNode *pNode, const char *zKey, u32 nKey){ + assert( pNode->eU==1 ); + if( pNode->jnFlags & JNODE_RAW ){ + if( pNode->n!=nKey ) return 0; + return strncmp(pNode->u.zJContent, zKey, nKey)==0; + }else{ + if( pNode->n!=nKey+2 ) return 0; + return strncmp(pNode->u.zJContent+1, zKey, nKey)==0; + } +} +static int jsonSameLabel(const JsonNode *p1, const JsonNode *p2){ + if( p1->jnFlags & JNODE_RAW ){ + return jsonLabelCompare(p2, p1->u.zJContent, p1->n); + }else if( p2->jnFlags & JNODE_RAW ){ + return jsonLabelCompare(p1, p2->u.zJContent, p2->n); + }else{ + return p1->n==p2->n && strncmp(p1->u.zJContent,p2->u.zJContent,p1->n)==0; + } +} + +/* forward declaration */ +static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*,const char**); + +/* +** Search along zPath to find the node specified. Return a pointer +** to that node, or NULL if zPath is malformed or if there is no such +** node. +** +** If pApnd!=0, then try to append new nodes to complete zPath if it is +** possible to do so and if no existing node corresponds to zPath. If +** new nodes are appended *pApnd is set to 1. +*/ +static JsonNode *jsonLookupStep( + JsonParse *pParse, /* The JSON to search */ + u32 iRoot, /* Begin the search at this node */ + const char *zPath, /* The path to search */ + int *pApnd, /* Append nodes to complete path if not NULL */ + const char **pzErr /* Make *pzErr point to any syntax error in zPath */ +){ + u32 i, j, nKey; + const char *zKey; + JsonNode *pRoot; + if( pParse->oom ) return 0; + pRoot = &pParse->aNode[iRoot]; + if( pRoot->jnFlags & (JNODE_REPLACE|JNODE_REMOVE) && pParse->useMod ){ + while( (pRoot->jnFlags & JNODE_REPLACE)!=0 ){ + u32 idx = (u32)(pRoot - pParse->aNode); + i = pParse->iSubst; + while( 1 /*exit-by-break*/ ){ + assert( i<pParse->nNode ); + assert( pParse->aNode[i].eType==JSON_SUBST ); + assert( pParse->aNode[i].eU==4 ); + assert( pParse->aNode[i].u.iPrev<i ); + if( pParse->aNode[i].n==idx ){ + pRoot = &pParse->aNode[i+1]; + iRoot = i+1; + break; + } + i = pParse->aNode[i].u.iPrev; + } + } + if( pRoot->jnFlags & JNODE_REMOVE ){ + return 0; + } + } + if( zPath[0]==0 ) return pRoot; + if( zPath[0]=='.' ){ + if( pRoot->eType!=JSON_OBJECT ) return 0; + zPath++; + if( zPath[0]=='"' ){ + zKey = zPath + 1; + for(i=1; zPath[i] && zPath[i]!='"'; i++){} + nKey = i-1; + if( zPath[i] ){ + i++; + }else{ + *pzErr = zPath; + return 0; + } + testcase( nKey==0 ); + }else{ + zKey = zPath; + for(i=0; zPath[i] && zPath[i]!='.' && zPath[i]!='['; i++){} + nKey = i; + if( nKey==0 ){ + *pzErr = zPath; + return 0; + } + } + j = 1; + for(;;){ + while( j<=pRoot->n ){ + if( jsonLabelCompare(pRoot+j, zKey, nKey) ){ + return jsonLookupStep(pParse, iRoot+j+1, &zPath[i], pApnd, pzErr); + } + j++; + j += jsonNodeSize(&pRoot[j]); + } + if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; + assert( pRoot->eU==2 ); + iRoot = pRoot->u.iAppend; + pRoot = &pParse->aNode[iRoot]; + j = 1; + } + if( pApnd ){ + u32 iStart, iLabel; + JsonNode *pNode; + assert( pParse->useMod ); + iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); + iLabel = jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); + zPath += i; + pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); + if( pParse->oom ) return 0; + if( pNode ){ + pRoot = &pParse->aNode[iRoot]; + assert( pRoot->eU==0 ); + pRoot->u.iAppend = iStart; + pRoot->jnFlags |= JNODE_APPEND; + VVA( pRoot->eU = 2 ); + pParse->aNode[iLabel].jnFlags |= JNODE_RAW; + } + return pNode; + } + }else if( zPath[0]=='[' ){ + i = 0; + j = 1; + while( sqlite3Isdigit(zPath[j]) ){ + i = i*10 + zPath[j] - '0'; + j++; + } + if( j<2 || zPath[j]!=']' ){ + if( zPath[1]=='#' ){ + JsonNode *pBase = pRoot; + int iBase = iRoot; + if( pRoot->eType!=JSON_ARRAY ) return 0; + for(;;){ + while( j<=pBase->n ){ + if( (pBase[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i++; + j += jsonNodeSize(&pBase[j]); + } + if( (pBase->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; + assert( pBase->eU==2 ); + iBase = pBase->u.iAppend; + pBase = &pParse->aNode[iBase]; + j = 1; + } + j = 2; + if( zPath[2]=='-' && sqlite3Isdigit(zPath[3]) ){ + unsigned int x = 0; + j = 3; + do{ + x = x*10 + zPath[j] - '0'; + j++; + }while( sqlite3Isdigit(zPath[j]) ); + if( x>i ) return 0; + i -= x; + } + if( zPath[j]!=']' ){ + *pzErr = zPath; + return 0; + } + }else{ + *pzErr = zPath; + return 0; + } + } + if( pRoot->eType!=JSON_ARRAY ) return 0; + zPath += j + 1; + j = 1; + for(;;){ + while( j<=pRoot->n + && (i>0 || ((pRoot[j].jnFlags & JNODE_REMOVE)!=0 && pParse->useMod)) + ){ + if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i--; + j += jsonNodeSize(&pRoot[j]); + } + if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; + assert( pRoot->eU==2 ); + iRoot = pRoot->u.iAppend; + pRoot = &pParse->aNode[iRoot]; + j = 1; + } + if( j<=pRoot->n ){ + return jsonLookupStep(pParse, iRoot+j, zPath, pApnd, pzErr); + } + if( i==0 && pApnd ){ + u32 iStart; + JsonNode *pNode; + assert( pParse->useMod ); + iStart = jsonParseAddNode(pParse, JSON_ARRAY, 1, 0); + pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); + if( pParse->oom ) return 0; + if( pNode ){ + pRoot = &pParse->aNode[iRoot]; + assert( pRoot->eU==0 ); + pRoot->u.iAppend = iStart; + pRoot->jnFlags |= JNODE_APPEND; + VVA( pRoot->eU = 2 ); + } + return pNode; + } + }else{ + *pzErr = zPath; + } + return 0; +} + +/* +** Append content to pParse that will complete zPath. Return a pointer +** to the inserted node, or return NULL if the append fails. +*/ +static JsonNode *jsonLookupAppend( + JsonParse *pParse, /* Append content to the JSON parse */ + const char *zPath, /* Description of content to append */ + int *pApnd, /* Set this flag to 1 */ + const char **pzErr /* Make this point to any syntax error */ +){ + *pApnd = 1; + if( zPath[0]==0 ){ + jsonParseAddNode(pParse, JSON_NULL, 0, 0); + return pParse->oom ? 0 : &pParse->aNode[pParse->nNode-1]; + } + if( zPath[0]=='.' ){ + jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); + }else if( strncmp(zPath,"[0]",3)==0 ){ + jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); + }else{ + return 0; + } + if( pParse->oom ) return 0; + return jsonLookupStep(pParse, pParse->nNode-1, zPath, pApnd, pzErr); +} + +/* +** Return the text of a syntax error message on a JSON path. Space is +** obtained from sqlite3_malloc(). +*/ +static char *jsonPathSyntaxError(const char *zErr){ + return sqlite3_mprintf("JSON path error near '%q'", zErr); +} + +/* +** Do a node lookup using zPath. Return a pointer to the node on success. +** Return NULL if not found or if there is an error. +** +** On an error, write an error message into pCtx and increment the +** pParse->nErr counter. +** +** If pApnd!=NULL then try to append missing nodes and set *pApnd = 1 if +** nodes are appended. +*/ +static JsonNode *jsonLookup( + JsonParse *pParse, /* The JSON to search */ + const char *zPath, /* The path to search */ + int *pApnd, /* Append nodes to complete path if not NULL */ + sqlite3_context *pCtx /* Report errors here, if not NULL */ +){ + const char *zErr = 0; + JsonNode *pNode = 0; + char *zMsg; + + if( zPath==0 ) return 0; + if( zPath[0]!='$' ){ + zErr = zPath; + goto lookup_err; + } + zPath++; + pNode = jsonLookupStep(pParse, 0, zPath, pApnd, &zErr); + if( zErr==0 ) return pNode; + +lookup_err: + pParse->nErr++; + assert( zErr!=0 && pCtx!=0 ); + zMsg = jsonPathSyntaxError(zErr); + if( zMsg ){ + sqlite3_result_error(pCtx, zMsg, -1); + sqlite3_free(zMsg); + }else{ + sqlite3_result_error_nomem(pCtx); + } + return 0; +} + + +/* +** Report the wrong number of arguments for json_insert(), json_replace() +** or json_set(). +*/ +static void jsonWrongNumArgs( + sqlite3_context *pCtx, + const char *zFuncName +){ + char *zMsg = sqlite3_mprintf("json_%s() needs an odd number of arguments", + zFuncName); + sqlite3_result_error(pCtx, zMsg, -1); + sqlite3_free(zMsg); +} + +/* +** Mark all NULL entries in the Object passed in as JNODE_REMOVE. +*/ +static void jsonRemoveAllNulls(JsonNode *pNode){ + int i, n; + assert( pNode->eType==JSON_OBJECT ); + n = pNode->n; + for(i=2; i<=n; i += jsonNodeSize(&pNode[i])+1){ + switch( pNode[i].eType ){ + case JSON_NULL: + pNode[i].jnFlags |= JNODE_REMOVE; + break; + case JSON_OBJECT: + jsonRemoveAllNulls(&pNode[i]); + break; + } + } +} + + +/**************************************************************************** +** SQL functions used for testing and debugging +****************************************************************************/ + +#if SQLITE_DEBUG +/* +** Print N node entries. +*/ +static void jsonDebugPrintNodeEntries( + JsonNode *aNode, /* First node entry to print */ + int N /* Number of node entries to print */ +){ + int i; + for(i=0; i<N; i++){ + const char *zType; + if( aNode[i].jnFlags & JNODE_LABEL ){ + zType = "label"; + }else{ + zType = jsonType[aNode[i].eType]; + } + printf("node %4u: %-7s n=%-5d", i, zType, aNode[i].n); + if( (aNode[i].jnFlags & ~JNODE_LABEL)!=0 ){ + u8 f = aNode[i].jnFlags; + if( f & JNODE_RAW ) printf(" RAW"); + if( f & JNODE_ESCAPE ) printf(" ESCAPE"); + if( f & JNODE_REMOVE ) printf(" REMOVE"); + if( f & JNODE_REPLACE ) printf(" REPLACE"); + if( f & JNODE_APPEND ) printf(" APPEND"); + if( f & JNODE_JSON5 ) printf(" JSON5"); + } + switch( aNode[i].eU ){ + case 1: printf(" zJContent=[%.*s]\n", + aNode[i].n, aNode[i].u.zJContent); break; + case 2: printf(" iAppend=%u\n", aNode[i].u.iAppend); break; + case 3: printf(" iKey=%u\n", aNode[i].u.iKey); break; + case 4: printf(" iPrev=%u\n", aNode[i].u.iPrev); break; + default: printf("\n"); + } + } +} +#endif /* SQLITE_DEBUG */ + + +#if 0 /* 1 for debugging. 0 normally. Requires -DSQLITE_DEBUG too */ +static void jsonDebugPrintParse(JsonParse *p){ + jsonDebugPrintNodeEntries(p->aNode, p->nNode); +} +static void jsonDebugPrintNode(JsonNode *pNode){ + jsonDebugPrintNodeEntries(pNode, jsonNodeSize(pNode)); +} +#else + /* The usual case */ +# define jsonDebugPrintNode(X) +# define jsonDebugPrintParse(X) +#endif + +#ifdef SQLITE_DEBUG +/* +** SQL function: json_parse(JSON) +** +** Parse JSON using jsonParseCached(). Then print a dump of that +** parse on standard output. Return the mimified JSON result, just +** like the json() function. +*/ +static void jsonParseFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *p; /* The parse */ + + assert( argc==1 ); + p = jsonParseCached(ctx, argv[0], ctx, 0); + if( p==0 ) return; + printf("nNode = %u\n", p->nNode); + printf("nAlloc = %u\n", p->nAlloc); + printf("nJson = %d\n", p->nJson); + printf("nAlt = %d\n", p->nAlt); + printf("nErr = %u\n", p->nErr); + printf("oom = %u\n", p->oom); + printf("hasNonstd = %u\n", p->hasNonstd); + printf("useMod = %u\n", p->useMod); + printf("hasMod = %u\n", p->hasMod); + printf("nJPRef = %u\n", p->nJPRef); + printf("iSubst = %u\n", p->iSubst); + printf("iHold = %u\n", p->iHold); + jsonDebugPrintNodeEntries(p->aNode, p->nNode); + jsonReturnJson(p, p->aNode, ctx, 1); +} + +/* +** The json_test1(JSON) function return true (1) if the input is JSON +** text generated by another json function. It returns (0) if the input +** is not known to be JSON. +*/ +static void jsonTest1Func( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + sqlite3_result_int(ctx, sqlite3_value_subtype(argv[0])==JSON_SUBTYPE); +} +#endif /* SQLITE_DEBUG */ + +/**************************************************************************** +** Scalar SQL function implementations +****************************************************************************/ + +/* +** Implementation of the json_QUOTE(VALUE) function. Return a JSON value +** corresponding to the SQL value input. Mostly this means putting +** double-quotes around strings and returning the unquoted string "null" +** when given a NULL input. +*/ +static void jsonQuoteFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonString jx; + UNUSED_PARAMETER(argc); + + jsonInit(&jx, ctx); + jsonAppendValue(&jx, argv[0]); + jsonResult(&jx); + sqlite3_result_subtype(ctx, JSON_SUBTYPE); +} + +/* +** Implementation of the json_array(VALUE,...) function. Return a JSON +** array that contains all values given in arguments. Or if any argument +** is a BLOB, throw an error. +*/ +static void jsonArrayFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + int i; + JsonString jx; + + jsonInit(&jx, ctx); + jsonAppendChar(&jx, '['); + for(i=0; i<argc; i++){ + jsonAppendSeparator(&jx); + jsonAppendValue(&jx, argv[i]); + } + jsonAppendChar(&jx, ']'); + jsonResult(&jx); + sqlite3_result_subtype(ctx, JSON_SUBTYPE); +} + + +/* +** json_array_length(JSON) +** json_array_length(JSON, PATH) +** +** Return the number of elements in the top-level JSON array. +** Return 0 if the input is not a well-formed JSON array. +*/ +static void jsonArrayLengthFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *p; /* The parse */ + sqlite3_int64 n = 0; + u32 i; + JsonNode *pNode; + + p = jsonParseCached(ctx, argv[0], ctx, 0); + if( p==0 ) return; + assert( p->nNode ); + if( argc==2 ){ + const char *zPath = (const char*)sqlite3_value_text(argv[1]); + pNode = jsonLookup(p, zPath, 0, ctx); + }else{ + pNode = p->aNode; + } + if( pNode==0 ){ + return; + } + if( pNode->eType==JSON_ARRAY ){ + while( 1 /*exit-by-break*/ ){ + i = 1; + while( i<=pNode->n ){ + if( (pNode[i].jnFlags & JNODE_REMOVE)==0 ) n++; + i += jsonNodeSize(&pNode[i]); + } + if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + if( p->useMod==0 ) break; + assert( pNode->eU==2 ); + pNode = &p->aNode[pNode->u.iAppend]; + } + } + sqlite3_result_int64(ctx, n); +} + +/* +** Bit values for the flags passed into jsonExtractFunc() or +** jsonSetFunc() via the user-data value. +*/ +#define JSON_JSON 0x01 /* Result is always JSON */ +#define JSON_SQL 0x02 /* Result is always SQL */ +#define JSON_ABPATH 0x03 /* Allow abbreviated JSON path specs */ +#define JSON_ISSET 0x04 /* json_set(), not json_insert() */ + +/* +** json_extract(JSON, PATH, ...) +** "->"(JSON,PATH) +** "->>"(JSON,PATH) +** +** Return the element described by PATH. Return NULL if that PATH element +** is not found. +** +** If JSON_JSON is set or if more that one PATH argument is supplied then +** always return a JSON representation of the result. If JSON_SQL is set, +** then always return an SQL representation of the result. If neither flag +** is present and argc==2, then return JSON for objects and arrays and SQL +** for all other values. +** +** When multiple PATH arguments are supplied, the result is a JSON array +** containing the result of each PATH. +** +** Abbreviated JSON path expressions are allows if JSON_ABPATH, for +** compatibility with PG. +*/ +static void jsonExtractFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *p; /* The parse */ + JsonNode *pNode; + const char *zPath; + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); + JsonString jx; + + if( argc<2 ) return; + p = jsonParseCached(ctx, argv[0], ctx, 0); + if( p==0 ) return; + if( argc==2 ){ + /* With a single PATH argument */ + zPath = (const char*)sqlite3_value_text(argv[1]); + if( zPath==0 ) return; + if( flags & JSON_ABPATH ){ + if( zPath[0]!='$' || (zPath[1]!='.' && zPath[1]!='[' && zPath[1]!=0) ){ + /* The -> and ->> operators accept abbreviated PATH arguments. This + ** is mostly for compatibility with PostgreSQL, but also for + ** convenience. + ** + ** NUMBER ==> $[NUMBER] // PG compatible + ** LABEL ==> $.LABEL // PG compatible + ** [NUMBER] ==> $[NUMBER] // Not PG. Purely for convenience + */ + jsonInit(&jx, ctx); + if( sqlite3Isdigit(zPath[0]) ){ + jsonAppendRawNZ(&jx, "$[", 2); + jsonAppendRaw(&jx, zPath, (int)strlen(zPath)); + jsonAppendRawNZ(&jx, "]", 2); + }else{ + jsonAppendRawNZ(&jx, "$.", 1 + (zPath[0]!='[')); + jsonAppendRaw(&jx, zPath, (int)strlen(zPath)); + jsonAppendChar(&jx, 0); + } + pNode = jx.bErr ? 0 : jsonLookup(p, jx.zBuf, 0, ctx); + jsonReset(&jx); + }else{ + pNode = jsonLookup(p, zPath, 0, ctx); + } + if( pNode ){ + if( flags & JSON_JSON ){ + jsonReturnJson(p, pNode, ctx, 0); + }else{ + jsonReturn(p, pNode, ctx); + sqlite3_result_subtype(ctx, 0); + } + } + }else{ + pNode = jsonLookup(p, zPath, 0, ctx); + if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx); + } + }else{ + /* Two or more PATH arguments results in a JSON array with each + ** element of the array being the value selected by one of the PATHs */ + int i; + jsonInit(&jx, ctx); + jsonAppendChar(&jx, '['); + for(i=1; i<argc; i++){ + zPath = (const char*)sqlite3_value_text(argv[i]); + pNode = jsonLookup(p, zPath, 0, ctx); + if( p->nErr ) break; + jsonAppendSeparator(&jx); + if( pNode ){ + jsonRenderNode(p, pNode, &jx); + }else{ + jsonAppendRawNZ(&jx, "null", 4); + } + } + if( i==argc ){ + jsonAppendChar(&jx, ']'); + jsonResult(&jx); + sqlite3_result_subtype(ctx, JSON_SUBTYPE); + } + jsonReset(&jx); + } +} + +/* This is the RFC 7396 MergePatch algorithm. +*/ +static JsonNode *jsonMergePatch( + JsonParse *pParse, /* The JSON parser that contains the TARGET */ + u32 iTarget, /* Node of the TARGET in pParse */ + JsonNode *pPatch /* The PATCH */ +){ + u32 i, j; + u32 iRoot; + JsonNode *pTarget; + if( pPatch->eType!=JSON_OBJECT ){ + return pPatch; + } + assert( iTarget<pParse->nNode ); + pTarget = &pParse->aNode[iTarget]; + assert( (pPatch->jnFlags & JNODE_APPEND)==0 ); + if( pTarget->eType!=JSON_OBJECT ){ + jsonRemoveAllNulls(pPatch); + return pPatch; + } + iRoot = iTarget; + for(i=1; i<pPatch->n; i += jsonNodeSize(&pPatch[i+1])+1){ + u32 nKey; + const char *zKey; + assert( pPatch[i].eType==JSON_STRING ); + assert( pPatch[i].jnFlags & JNODE_LABEL ); + assert( pPatch[i].eU==1 ); + nKey = pPatch[i].n; + zKey = pPatch[i].u.zJContent; + for(j=1; j<pTarget->n; j += jsonNodeSize(&pTarget[j+1])+1 ){ + assert( pTarget[j].eType==JSON_STRING ); + assert( pTarget[j].jnFlags & JNODE_LABEL ); + if( jsonSameLabel(&pPatch[i], &pTarget[j]) ){ + if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ) break; + if( pPatch[i+1].eType==JSON_NULL ){ + pTarget[j+1].jnFlags |= JNODE_REMOVE; + }else{ + JsonNode *pNew = jsonMergePatch(pParse, iTarget+j+1, &pPatch[i+1]); + if( pNew==0 ) return 0; + if( pNew!=&pParse->aNode[iTarget+j+1] ){ + jsonParseAddSubstNode(pParse, iTarget+j+1); + jsonParseAddNodeArray(pParse, pNew, jsonNodeSize(pNew)); + } + pTarget = &pParse->aNode[iTarget]; + } + break; + } + } + if( j>=pTarget->n && pPatch[i+1].eType!=JSON_NULL ){ + int iStart; + JsonNode *pApnd; + u32 nApnd; + iStart = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); + jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); + pApnd = &pPatch[i+1]; + if( pApnd->eType==JSON_OBJECT ) jsonRemoveAllNulls(pApnd); + nApnd = jsonNodeSize(pApnd); + jsonParseAddNodeArray(pParse, pApnd, jsonNodeSize(pApnd)); + if( pParse->oom ) return 0; + pParse->aNode[iStart].n = 1+nApnd; + pParse->aNode[iRoot].jnFlags |= JNODE_APPEND; + pParse->aNode[iRoot].u.iAppend = iStart; + VVA( pParse->aNode[iRoot].eU = 2 ); + iRoot = iStart; + pTarget = &pParse->aNode[iTarget]; + } + } + return pTarget; +} + +/* +** Implementation of the json_mergepatch(JSON1,JSON2) function. Return a JSON +** object that is the result of running the RFC 7396 MergePatch() algorithm +** on the two arguments. +*/ +static void jsonPatchFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *pX; /* The JSON that is being patched */ + JsonParse *pY; /* The patch */ + JsonNode *pResult; /* The result of the merge */ + + UNUSED_PARAMETER(argc); + pX = jsonParseCached(ctx, argv[0], ctx, 1); + if( pX==0 ) return; + assert( pX->hasMod==0 ); + pX->hasMod = 1; + pY = jsonParseCached(ctx, argv[1], ctx, 1); + if( pY==0 ) return; + pX->useMod = 1; + pY->useMod = 1; + pResult = jsonMergePatch(pX, 0, pY->aNode); + assert( pResult!=0 || pX->oom ); + if( pResult && pX->oom==0 ){ + jsonDebugPrintParse(pX); + jsonDebugPrintNode(pResult); + jsonReturnJson(pX, pResult, ctx, 0); + }else{ + sqlite3_result_error_nomem(ctx); + } +} + + +/* +** Implementation of the json_object(NAME,VALUE,...) function. Return a JSON +** object that contains all name/value given in arguments. Or if any name +** is not a string or if any value is a BLOB, throw an error. +*/ +static void jsonObjectFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + int i; + JsonString jx; + const char *z; + u32 n; + + if( argc&1 ){ + sqlite3_result_error(ctx, "json_object() requires an even number " + "of arguments", -1); + return; + } + jsonInit(&jx, ctx); + jsonAppendChar(&jx, '{'); + for(i=0; i<argc; i+=2){ + if( sqlite3_value_type(argv[i])!=SQLITE_TEXT ){ + sqlite3_result_error(ctx, "json_object() labels must be TEXT", -1); + jsonReset(&jx); + return; + } + jsonAppendSeparator(&jx); + z = (const char*)sqlite3_value_text(argv[i]); + n = (u32)sqlite3_value_bytes(argv[i]); + jsonAppendString(&jx, z, n); + jsonAppendChar(&jx, ':'); + jsonAppendValue(&jx, argv[i+1]); + } + jsonAppendChar(&jx, '}'); + jsonResult(&jx); + sqlite3_result_subtype(ctx, JSON_SUBTYPE); +} + + +/* +** json_remove(JSON, PATH, ...) +** +** Remove the named elements from JSON and return the result. malformed +** JSON or PATH arguments result in an error. +*/ +static void jsonRemoveFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *pParse; /* The parse */ + JsonNode *pNode; + const char *zPath; + u32 i; + + if( argc<1 ) return; + pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); + if( pParse==0 ) return; + for(i=1; i<(u32)argc; i++){ + zPath = (const char*)sqlite3_value_text(argv[i]); + if( zPath==0 ) goto remove_done; + pNode = jsonLookup(pParse, zPath, 0, ctx); + if( pParse->nErr ) goto remove_done; + if( pNode ){ + pNode->jnFlags |= JNODE_REMOVE; + pParse->hasMod = 1; + pParse->useMod = 1; + } + } + if( (pParse->aNode[0].jnFlags & JNODE_REMOVE)==0 ){ + jsonReturnJson(pParse, pParse->aNode, ctx, 1); + } +remove_done: + jsonDebugPrintParse(p); +} + +/* +** Substitute the value at iNode with the pValue parameter. +*/ +static void jsonReplaceNode( + sqlite3_context *pCtx, + JsonParse *p, + int iNode, + sqlite3_value *pValue +){ + int idx = jsonParseAddSubstNode(p, iNode); + if( idx<=0 ){ + assert( p->oom ); + return; + } + switch( sqlite3_value_type(pValue) ){ + case SQLITE_NULL: { + jsonParseAddNode(p, JSON_NULL, 0, 0); + break; + } + case SQLITE_FLOAT: { + char *z = sqlite3_mprintf("%!0.15g", sqlite3_value_double(pValue)); + int n; + if( z==0 ){ + p->oom = 1; + break; + } + n = sqlite3Strlen30(z); + jsonParseAddNode(p, JSON_REAL, n, z); + jsonParseAddCleanup(p, sqlite3_free, z); + break; + } + case SQLITE_INTEGER: { + char *z = sqlite3_mprintf("%lld", sqlite3_value_int64(pValue)); + int n; + if( z==0 ){ + p->oom = 1; + break; + } + n = sqlite3Strlen30(z); + jsonParseAddNode(p, JSON_INT, n, z); + jsonParseAddCleanup(p, sqlite3_free, z); + + break; + } + case SQLITE_TEXT: { + const char *z = (const char*)sqlite3_value_text(pValue); + u32 n = (u32)sqlite3_value_bytes(pValue); + if( z==0 ){ + p->oom = 1; + break; + } + if( sqlite3_value_subtype(pValue)!=JSON_SUBTYPE ){ + char *zCopy = sqlite3DbStrDup(0, z); + int k; + if( zCopy ){ + jsonParseAddCleanup(p, sqlite3_free, zCopy); + }else{ + p->oom = 1; + sqlite3_result_error_nomem(pCtx); + } + k = jsonParseAddNode(p, JSON_STRING, n, zCopy); + assert( k>0 || p->oom ); + if( p->oom==0 ) p->aNode[k].jnFlags |= JNODE_RAW; + }else{ + JsonParse *pPatch = jsonParseCached(pCtx, pValue, pCtx, 1); + if( pPatch==0 ){ + p->oom = 1; + break; + } + jsonParseAddNodeArray(p, pPatch->aNode, pPatch->nNode); + /* The nodes copied out of pPatch and into p likely contain + ** u.zJContent pointers into pPatch->zJson. So preserve the + ** content of pPatch until p is destroyed. */ + assert( pPatch->nJPRef>=1 ); + pPatch->nJPRef++; + jsonParseAddCleanup(p, (void(*)(void*))jsonParseFree, pPatch); + } + break; + } + default: { + jsonParseAddNode(p, JSON_NULL, 0, 0); + sqlite3_result_error(pCtx, "JSON cannot hold BLOB values", -1); + p->nErr++; + break; + } + } +} + +/* +** json_replace(JSON, PATH, VALUE, ...) +** +** Replace the value at PATH with VALUE. If PATH does not already exist, +** this routine is a no-op. If JSON or PATH is malformed, throw an error. +*/ +static void jsonReplaceFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *pParse; /* The parse */ + JsonNode *pNode; + const char *zPath; + u32 i; + + if( argc<1 ) return; + if( (argc&1)==0 ) { + jsonWrongNumArgs(ctx, "replace"); + return; + } + pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); + if( pParse==0 ) return; + for(i=1; i<(u32)argc; i+=2){ + zPath = (const char*)sqlite3_value_text(argv[i]); + pParse->useMod = 1; + pNode = jsonLookup(pParse, zPath, 0, ctx); + if( pParse->nErr ) goto replace_err; + if( pNode ){ + jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); + } + } + jsonReturnJson(pParse, pParse->aNode, ctx, 1); +replace_err: + jsonDebugPrintParse(pParse); +} + + +/* +** json_set(JSON, PATH, VALUE, ...) +** +** Set the value at PATH to VALUE. Create the PATH if it does not already +** exist. Overwrite existing values that do exist. +** If JSON or PATH is malformed, throw an error. +** +** json_insert(JSON, PATH, VALUE, ...) +** +** Create PATH and initialize it to VALUE. If PATH already exists, this +** routine is a no-op. If JSON or PATH is malformed, throw an error. +*/ +static void jsonSetFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *pParse; /* The parse */ + JsonNode *pNode; + const char *zPath; + u32 i; + int bApnd; + int bIsSet = sqlite3_user_data(ctx)!=0; + + if( argc<1 ) return; + if( (argc&1)==0 ) { + jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert"); + return; + } + pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); + if( pParse==0 ) return; + for(i=1; i<(u32)argc; i+=2){ + zPath = (const char*)sqlite3_value_text(argv[i]); + bApnd = 0; + pParse->useMod = 1; + pNode = jsonLookup(pParse, zPath, &bApnd, ctx); + if( pParse->oom ){ + sqlite3_result_error_nomem(ctx); + goto jsonSetDone; + }else if( pParse->nErr ){ + goto jsonSetDone; + }else if( pNode && (bApnd || bIsSet) ){ + jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); + } + } + jsonDebugPrintParse(pParse); + jsonReturnJson(pParse, pParse->aNode, ctx, 1); + +jsonSetDone: + /* no cleanup required */; +} + +/* +** json_type(JSON) +** json_type(JSON, PATH) +** +** Return the top-level "type" of a JSON string. json_type() raises an +** error if either the JSON or PATH inputs are not well-formed. +*/ +static void jsonTypeFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *p; /* The parse */ + const char *zPath; + JsonNode *pNode; + + p = jsonParseCached(ctx, argv[0], ctx, 0); + if( p==0 ) return; + if( argc==2 ){ + zPath = (const char*)sqlite3_value_text(argv[1]); + pNode = jsonLookup(p, zPath, 0, ctx); + }else{ + pNode = p->aNode; + } + if( pNode ){ + sqlite3_result_text(ctx, jsonType[pNode->eType], -1, SQLITE_STATIC); + } +} + +/* +** json_valid(JSON) +** +** Return 1 if JSON is a well-formed canonical JSON string according +** to RFC-7159. Return 0 otherwise. +*/ +static void jsonValidFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *p; /* The parse */ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ +#ifdef SQLITE_LEGACY_JSON_VALID + /* Incorrect legacy behavior was to return FALSE for a NULL input */ + sqlite3_result_int(ctx, 0); +#endif + return; + } + p = jsonParseCached(ctx, argv[0], 0, 0); + if( p==0 || p->oom ){ + sqlite3_result_error_nomem(ctx); + sqlite3_free(p); + }else{ + sqlite3_result_int(ctx, p->nErr==0 && (p->hasNonstd==0 || p->useMod)); + if( p->nErr ) jsonParseFree(p); + } +} + +/* +** json_error_position(JSON) +** +** If the argument is not an interpretable JSON string, then return the 1-based +** character position at which the parser first recognized that the input +** was in error. The left-most character is 1. If the string is valid +** JSON, then return 0. +** +** Note that json_valid() is only true for strictly conforming canonical JSON. +** But this routine returns zero if the input contains extension. Thus: +** +** (1) If the input X is strictly conforming canonical JSON: +** +** json_valid(X) returns true +** json_error_position(X) returns 0 +** +** (2) If the input X is JSON but it includes extension (such as JSON5) that +** are not part of RFC-8259: +** +** json_valid(X) returns false +** json_error_position(X) return 0 +** +** (3) If the input X cannot be interpreted as JSON even taking extensions +** into account: +** +** json_valid(X) return false +** json_error_position(X) returns 1 or more +*/ +static void jsonErrorFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse *p; /* The parse */ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + p = jsonParseCached(ctx, argv[0], 0, 0); + if( p==0 || p->oom ){ + sqlite3_result_error_nomem(ctx); + sqlite3_free(p); + }else if( p->nErr==0 ){ + sqlite3_result_int(ctx, 0); + }else{ + int n = 1; + u32 i; + const char *z = (const char*)sqlite3_value_text(argv[0]); + for(i=0; i<p->iErr && ALWAYS(z[i]); i++){ + if( (z[i]&0xc0)!=0x80 ) n++; + } + sqlite3_result_int(ctx, n); + jsonParseFree(p); + } +} + + +/**************************************************************************** +** Aggregate SQL function implementations +****************************************************************************/ +/* +** json_group_array(VALUE) +** +** Return a JSON array composed of all values in the aggregate. +*/ +static void jsonArrayStep( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonString *pStr; + UNUSED_PARAMETER(argc); + pStr = (JsonString*)sqlite3_aggregate_context(ctx, sizeof(*pStr)); + if( pStr ){ + if( pStr->zBuf==0 ){ + jsonInit(pStr, ctx); + jsonAppendChar(pStr, '['); + }else if( pStr->nUsed>1 ){ + jsonAppendChar(pStr, ','); + } + pStr->pCtx = ctx; + jsonAppendValue(pStr, argv[0]); + } +} +static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ + JsonString *pStr; + pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); + if( pStr ){ + pStr->pCtx = ctx; + jsonAppendChar(pStr, ']'); + if( pStr->bErr ){ + if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); + assert( pStr->bStatic ); + }else if( isFinal ){ + sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, + pStr->bStatic ? SQLITE_TRANSIENT : + (void(*)(void*))sqlite3RCStrUnref); + pStr->bStatic = 1; + }else{ + sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); + pStr->nUsed--; + } + }else{ + sqlite3_result_text(ctx, "[]", 2, SQLITE_STATIC); + } + sqlite3_result_subtype(ctx, JSON_SUBTYPE); +} +static void jsonArrayValue(sqlite3_context *ctx){ + jsonArrayCompute(ctx, 0); +} +static void jsonArrayFinal(sqlite3_context *ctx){ + jsonArrayCompute(ctx, 1); +} + +#ifndef SQLITE_OMIT_WINDOWFUNC +/* +** This method works for both json_group_array() and json_group_object(). +** It works by removing the first element of the group by searching forward +** to the first comma (",") that is not within a string and deleting all +** text through that comma. +*/ +static void jsonGroupInverse( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + unsigned int i; + int inStr = 0; + int nNest = 0; + char *z; + char c; + JsonString *pStr; + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); +#ifdef NEVER + /* pStr is always non-NULL since jsonArrayStep() or jsonObjectStep() will + ** always have been called to initialize it */ + if( NEVER(!pStr) ) return; +#endif + z = pStr->zBuf; + for(i=1; i<pStr->nUsed && ((c = z[i])!=',' || inStr || nNest); i++){ + if( c=='"' ){ + inStr = !inStr; + }else if( c=='\\' ){ + i++; + }else if( !inStr ){ + if( c=='{' || c=='[' ) nNest++; + if( c=='}' || c==']' ) nNest--; + } + } + if( i<pStr->nUsed ){ + pStr->nUsed -= i; + memmove(&z[1], &z[i+1], (size_t)pStr->nUsed-1); + z[pStr->nUsed] = 0; + }else{ + pStr->nUsed = 1; + } +} +#else +# define jsonGroupInverse 0 +#endif + + +/* +** json_group_obj(NAME,VALUE) +** +** Return a JSON object composed of all names and values in the aggregate. +*/ +static void jsonObjectStep( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonString *pStr; + const char *z; + u32 n; + UNUSED_PARAMETER(argc); + pStr = (JsonString*)sqlite3_aggregate_context(ctx, sizeof(*pStr)); + if( pStr ){ + if( pStr->zBuf==0 ){ + jsonInit(pStr, ctx); + jsonAppendChar(pStr, '{'); + }else if( pStr->nUsed>1 ){ + jsonAppendChar(pStr, ','); + } + pStr->pCtx = ctx; + z = (const char*)sqlite3_value_text(argv[0]); + n = (u32)sqlite3_value_bytes(argv[0]); + jsonAppendString(pStr, z, n); + jsonAppendChar(pStr, ':'); + jsonAppendValue(pStr, argv[1]); + } +} +static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ + JsonString *pStr; + pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); + if( pStr ){ + jsonAppendChar(pStr, '}'); + if( pStr->bErr ){ + if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); + assert( pStr->bStatic ); + }else if( isFinal ){ + sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, + pStr->bStatic ? SQLITE_TRANSIENT : + (void(*)(void*))sqlite3RCStrUnref); + pStr->bStatic = 1; + }else{ + sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); + pStr->nUsed--; + } + }else{ + sqlite3_result_text(ctx, "{}", 2, SQLITE_STATIC); + } + sqlite3_result_subtype(ctx, JSON_SUBTYPE); +} +static void jsonObjectValue(sqlite3_context *ctx){ + jsonObjectCompute(ctx, 0); +} +static void jsonObjectFinal(sqlite3_context *ctx){ + jsonObjectCompute(ctx, 1); +} + + + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/**************************************************************************** +** The json_each virtual table +****************************************************************************/ +typedef struct JsonEachCursor JsonEachCursor; +struct JsonEachCursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + u32 iRowid; /* The rowid */ + u32 iBegin; /* The first node of the scan */ + u32 i; /* Index in sParse.aNode[] of current row */ + u32 iEnd; /* EOF when i equals or exceeds this value */ + u8 eType; /* Type of top-level element */ + u8 bRecursive; /* True for json_tree(). False for json_each() */ + char *zJson; /* Input JSON */ + char *zRoot; /* Path by which to filter zJson */ + JsonParse sParse; /* Parse of the input JSON */ +}; + +/* Constructor for the json_each virtual table */ +static int jsonEachConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + sqlite3_vtab *pNew; + int rc; + +/* Column numbers */ +#define JEACH_KEY 0 +#define JEACH_VALUE 1 +#define JEACH_TYPE 2 +#define JEACH_ATOM 3 +#define JEACH_ID 4 +#define JEACH_PARENT 5 +#define JEACH_FULLKEY 6 +#define JEACH_PATH 7 +/* The xBestIndex method assumes that the JSON and ROOT columns are +** the last two columns in the table. Should this ever changes, be +** sure to update the xBestIndex method. */ +#define JEACH_JSON 8 +#define JEACH_ROOT 9 + + UNUSED_PARAMETER(pzErr); + UNUSED_PARAMETER(argv); + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(pAux); + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(key,value,type,atom,id,parent,fullkey,path," + "json HIDDEN,root HIDDEN)"); + if( rc==SQLITE_OK ){ + pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + } + return rc; +} + +/* destructor for json_each virtual table */ +static int jsonEachDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* constructor for a JsonEachCursor object for json_each(). */ +static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + JsonEachCursor *pCur; + + UNUSED_PARAMETER(p); + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* constructor for a JsonEachCursor object for json_tree(). */ +static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + int rc = jsonEachOpenEach(p, ppCursor); + if( rc==SQLITE_OK ){ + JsonEachCursor *pCur = (JsonEachCursor*)*ppCursor; + pCur->bRecursive = 1; + } + return rc; +} + +/* Reset a JsonEachCursor back to its original state. Free any memory +** held. */ +static void jsonEachCursorReset(JsonEachCursor *p){ + sqlite3_free(p->zRoot); + jsonParseReset(&p->sParse); + p->iRowid = 0; + p->i = 0; + p->iEnd = 0; + p->eType = 0; + p->zJson = 0; + p->zRoot = 0; +} + +/* Destructor for a jsonEachCursor object */ +static int jsonEachClose(sqlite3_vtab_cursor *cur){ + JsonEachCursor *p = (JsonEachCursor*)cur; + jsonEachCursorReset(p); + sqlite3_free(cur); + return SQLITE_OK; +} + +/* Return TRUE if the jsonEachCursor object has been advanced off the end +** of the JSON object */ +static int jsonEachEof(sqlite3_vtab_cursor *cur){ + JsonEachCursor *p = (JsonEachCursor*)cur; + return p->i >= p->iEnd; +} + +/* Advance the cursor to the next element for json_tree() */ +static int jsonEachNext(sqlite3_vtab_cursor *cur){ + JsonEachCursor *p = (JsonEachCursor*)cur; + if( p->bRecursive ){ + if( p->sParse.aNode[p->i].jnFlags & JNODE_LABEL ) p->i++; + p->i++; + p->iRowid++; + if( p->i<p->iEnd ){ + u32 iUp = p->sParse.aUp[p->i]; + JsonNode *pUp = &p->sParse.aNode[iUp]; + p->eType = pUp->eType; + if( pUp->eType==JSON_ARRAY ){ + assert( pUp->eU==0 || pUp->eU==3 ); + testcase( pUp->eU==3 ); + VVA( pUp->eU = 3 ); + if( iUp==p->i-1 ){ + pUp->u.iKey = 0; + }else{ + pUp->u.iKey++; + } + } + } + }else{ + switch( p->eType ){ + case JSON_ARRAY: { + p->i += jsonNodeSize(&p->sParse.aNode[p->i]); + p->iRowid++; + break; + } + case JSON_OBJECT: { + p->i += 1 + jsonNodeSize(&p->sParse.aNode[p->i+1]); + p->iRowid++; + break; + } + default: { + p->i = p->iEnd; + break; + } + } + } + return SQLITE_OK; +} + +/* Append an object label to the JSON Path being constructed +** in pStr. +*/ +static void jsonAppendObjectPathElement( + JsonString *pStr, + JsonNode *pNode +){ + int jj, nn; + const char *z; + assert( pNode->eType==JSON_STRING ); + assert( pNode->jnFlags & JNODE_LABEL ); + assert( pNode->eU==1 ); + z = pNode->u.zJContent; + nn = pNode->n; + if( (pNode->jnFlags & JNODE_RAW)==0 ){ + assert( nn>=2 ); + assert( z[0]=='"' || z[0]=='\'' ); + assert( z[nn-1]=='"' || z[0]=='\'' ); + if( nn>2 && sqlite3Isalpha(z[1]) ){ + for(jj=2; jj<nn-1 && sqlite3Isalnum(z[jj]); jj++){} + if( jj==nn-1 ){ + z++; + nn -= 2; + } + } + } + jsonPrintf(nn+2, pStr, ".%.*s", nn, z); +} + +/* Append the name of the path for element i to pStr +*/ +static void jsonEachComputePath( + JsonEachCursor *p, /* The cursor */ + JsonString *pStr, /* Write the path here */ + u32 i /* Path to this element */ +){ + JsonNode *pNode, *pUp; + u32 iUp; + if( i==0 ){ + jsonAppendChar(pStr, '$'); + return; + } + iUp = p->sParse.aUp[i]; + jsonEachComputePath(p, pStr, iUp); + pNode = &p->sParse.aNode[i]; + pUp = &p->sParse.aNode[iUp]; + if( pUp->eType==JSON_ARRAY ){ + assert( pUp->eU==3 || (pUp->eU==0 && pUp->u.iKey==0) ); + testcase( pUp->eU==0 ); + jsonPrintf(30, pStr, "[%d]", pUp->u.iKey); + }else{ + assert( pUp->eType==JSON_OBJECT ); + if( (pNode->jnFlags & JNODE_LABEL)==0 ) pNode--; + jsonAppendObjectPathElement(pStr, pNode); + } +} + +/* Return the value of a column */ +static int jsonEachColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + JsonEachCursor *p = (JsonEachCursor*)cur; + JsonNode *pThis = &p->sParse.aNode[p->i]; + switch( i ){ + case JEACH_KEY: { + if( p->i==0 ) break; + if( p->eType==JSON_OBJECT ){ + jsonReturn(&p->sParse, pThis, ctx); + }else if( p->eType==JSON_ARRAY ){ + u32 iKey; + if( p->bRecursive ){ + if( p->iRowid==0 ) break; + assert( p->sParse.aNode[p->sParse.aUp[p->i]].eU==3 ); + iKey = p->sParse.aNode[p->sParse.aUp[p->i]].u.iKey; + }else{ + iKey = p->iRowid; + } + sqlite3_result_int64(ctx, (sqlite3_int64)iKey); + } + break; + } + case JEACH_VALUE: { + if( pThis->jnFlags & JNODE_LABEL ) pThis++; + jsonReturn(&p->sParse, pThis, ctx); + break; + } + case JEACH_TYPE: { + if( pThis->jnFlags & JNODE_LABEL ) pThis++; + sqlite3_result_text(ctx, jsonType[pThis->eType], -1, SQLITE_STATIC); + break; + } + case JEACH_ATOM: { + if( pThis->jnFlags & JNODE_LABEL ) pThis++; + if( pThis->eType>=JSON_ARRAY ) break; + jsonReturn(&p->sParse, pThis, ctx); + break; + } + case JEACH_ID: { + sqlite3_result_int64(ctx, + (sqlite3_int64)p->i + ((pThis->jnFlags & JNODE_LABEL)!=0)); + break; + } + case JEACH_PARENT: { + if( p->i>p->iBegin && p->bRecursive ){ + sqlite3_result_int64(ctx, (sqlite3_int64)p->sParse.aUp[p->i]); + } + break; + } + case JEACH_FULLKEY: { + JsonString x; + jsonInit(&x, ctx); + if( p->bRecursive ){ + jsonEachComputePath(p, &x, p->i); + }else{ + if( p->zRoot ){ + jsonAppendRaw(&x, p->zRoot, (int)strlen(p->zRoot)); + }else{ + jsonAppendChar(&x, '$'); + } + if( p->eType==JSON_ARRAY ){ + jsonPrintf(30, &x, "[%d]", p->iRowid); + }else if( p->eType==JSON_OBJECT ){ + jsonAppendObjectPathElement(&x, pThis); + } + } + jsonResult(&x); + break; + } + case JEACH_PATH: { + if( p->bRecursive ){ + JsonString x; + jsonInit(&x, ctx); + jsonEachComputePath(p, &x, p->sParse.aUp[p->i]); + jsonResult(&x); + break; + } + /* For json_each() path and root are the same so fall through + ** into the root case */ + /* no break */ deliberate_fall_through + } + default: { + const char *zRoot = p->zRoot; + if( zRoot==0 ) zRoot = "$"; + sqlite3_result_text(ctx, zRoot, -1, SQLITE_STATIC); + break; + } + case JEACH_JSON: { + assert( i==JEACH_JSON ); + sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC); + break; + } + } + return SQLITE_OK; +} + +/* Return the current rowid value */ +static int jsonEachRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + JsonEachCursor *p = (JsonEachCursor*)cur; + *pRowid = p->iRowid; + return SQLITE_OK; +} + +/* The query strategy is to look for an equality constraint on the json +** column. Without such a constraint, the table cannot operate. idxNum is +** 1 if the constraint is found, 3 if the constraint and zRoot are found, +** and 0 otherwise. +*/ +static int jsonEachBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop counter or computed array index */ + int aIdx[2]; /* Index of constraints for JSON and ROOT */ + int unusableMask = 0; /* Mask of unusable JSON and ROOT constraints */ + int idxMask = 0; /* Mask of usable == constraints JSON and ROOT */ + const struct sqlite3_index_constraint *pConstraint; + + /* This implementation assumes that JSON and ROOT are the last two + ** columns in the table */ + assert( JEACH_ROOT == JEACH_JSON+1 ); + UNUSED_PARAMETER(tab); + aIdx[0] = aIdx[1] = -1; + pConstraint = pIdxInfo->aConstraint; + for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ + int iCol; + int iMask; + if( pConstraint->iColumn < JEACH_JSON ) continue; + iCol = pConstraint->iColumn - JEACH_JSON; + assert( iCol==0 || iCol==1 ); + testcase( iCol==0 ); + iMask = 1 << iCol; + if( pConstraint->usable==0 ){ + unusableMask |= iMask; + }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + aIdx[iCol] = i; + idxMask |= iMask; + } + } + if( pIdxInfo->nOrderBy>0 + && pIdxInfo->aOrderBy[0].iColumn<0 + && pIdxInfo->aOrderBy[0].desc==0 + ){ + pIdxInfo->orderByConsumed = 1; + } + + if( (unusableMask & ~idxMask)!=0 ){ + /* If there are any unusable constraints on JSON or ROOT, then reject + ** this entire plan */ + return SQLITE_CONSTRAINT; + } + if( aIdx[0]<0 ){ + /* No JSON input. Leave estimatedCost at the huge value that it was + ** initialized to to discourage the query planner from selecting this + ** plan. */ + pIdxInfo->idxNum = 0; + }else{ + pIdxInfo->estimatedCost = 1.0; + i = aIdx[0]; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + if( aIdx[1]<0 ){ + pIdxInfo->idxNum = 1; /* Only JSON supplied. Plan 1 */ + }else{ + i = aIdx[1]; + pIdxInfo->aConstraintUsage[i].argvIndex = 2; + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->idxNum = 3; /* Both JSON and ROOT are supplied. Plan 3 */ + } + } + return SQLITE_OK; +} + +/* Start a search on a new JSON string */ +static int jsonEachFilter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + JsonEachCursor *p = (JsonEachCursor*)cur; + const char *z; + const char *zRoot = 0; + sqlite3_int64 n; + + UNUSED_PARAMETER(idxStr); + UNUSED_PARAMETER(argc); + jsonEachCursorReset(p); + if( idxNum==0 ) return SQLITE_OK; + z = (const char*)sqlite3_value_text(argv[0]); + if( z==0 ) return SQLITE_OK; + memset(&p->sParse, 0, sizeof(p->sParse)); + p->sParse.nJPRef = 1; + if( sqlite3ValueIsOfClass(argv[0], (void(*)(void*))sqlite3RCStrUnref) ){ + p->sParse.zJson = sqlite3RCStrRef((char*)z); + }else{ + n = sqlite3_value_bytes(argv[0]); + p->sParse.zJson = sqlite3RCStrNew( n+1 ); + if( p->sParse.zJson==0 ) return SQLITE_NOMEM; + memcpy(p->sParse.zJson, z, (size_t)n+1); + } + p->sParse.bJsonIsRCStr = 1; + p->zJson = p->sParse.zJson; + if( jsonParse(&p->sParse, 0) ){ + int rc = SQLITE_NOMEM; + if( p->sParse.oom==0 ){ + sqlite3_free(cur->pVtab->zErrMsg); + cur->pVtab->zErrMsg = sqlite3_mprintf("malformed JSON"); + if( cur->pVtab->zErrMsg ) rc = SQLITE_ERROR; + } + jsonEachCursorReset(p); + return rc; + }else if( p->bRecursive && jsonParseFindParents(&p->sParse) ){ + jsonEachCursorReset(p); + return SQLITE_NOMEM; + }else{ + JsonNode *pNode = 0; + if( idxNum==3 ){ + const char *zErr = 0; + zRoot = (const char*)sqlite3_value_text(argv[1]); + if( zRoot==0 ) return SQLITE_OK; + n = sqlite3_value_bytes(argv[1]); + p->zRoot = sqlite3_malloc64( n+1 ); + if( p->zRoot==0 ) return SQLITE_NOMEM; + memcpy(p->zRoot, zRoot, (size_t)n+1); + if( zRoot[0]!='$' ){ + zErr = zRoot; + }else{ + pNode = jsonLookupStep(&p->sParse, 0, p->zRoot+1, 0, &zErr); + } + if( zErr ){ + sqlite3_free(cur->pVtab->zErrMsg); + cur->pVtab->zErrMsg = jsonPathSyntaxError(zErr); + jsonEachCursorReset(p); + return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; + }else if( pNode==0 ){ + return SQLITE_OK; + } + }else{ + pNode = p->sParse.aNode; + } + p->iBegin = p->i = (int)(pNode - p->sParse.aNode); + p->eType = pNode->eType; + if( p->eType>=JSON_ARRAY ){ + assert( pNode->eU==0 ); + VVA( pNode->eU = 3 ); + pNode->u.iKey = 0; + p->iEnd = p->i + pNode->n + 1; + if( p->bRecursive ){ + p->eType = p->sParse.aNode[p->sParse.aUp[p->i]].eType; + if( p->i>0 && (p->sParse.aNode[p->i-1].jnFlags & JNODE_LABEL)!=0 ){ + p->i--; + } + }else{ + p->i++; + } + }else{ + p->iEnd = p->i+1; + } + } + return SQLITE_OK; +} + +/* The methods of the json_each virtual table */ +static sqlite3_module jsonEachModule = { + 0, /* iVersion */ + 0, /* xCreate */ + jsonEachConnect, /* xConnect */ + jsonEachBestIndex, /* xBestIndex */ + jsonEachDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + jsonEachOpenEach, /* xOpen - open a cursor */ + jsonEachClose, /* xClose - close a cursor */ + jsonEachFilter, /* xFilter - configure scan constraints */ + jsonEachNext, /* xNext - advance a cursor */ + jsonEachEof, /* xEof - check for end of scan */ + jsonEachColumn, /* xColumn - read data */ + jsonEachRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ +}; + +/* The methods of the json_tree virtual table. */ +static sqlite3_module jsonTreeModule = { + 0, /* iVersion */ + 0, /* xCreate */ + jsonEachConnect, /* xConnect */ + jsonEachBestIndex, /* xBestIndex */ + jsonEachDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + jsonEachOpenTree, /* xOpen - open a cursor */ + jsonEachClose, /* xClose - close a cursor */ + jsonEachFilter, /* xFilter - configure scan constraints */ + jsonEachNext, /* xNext - advance a cursor */ + jsonEachEof, /* xEof - check for end of scan */ + jsonEachColumn, /* xColumn - read data */ + jsonEachRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ +}; +#endif /* SQLITE_OMIT_VIRTUALTABLE */ +#endif /* !defined(SQLITE_OMIT_JSON) */ + +/* +** Register JSON functions. +*/ +SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void){ +#ifndef SQLITE_OMIT_JSON + static FuncDef aJsonFunc[] = { + JFUNCTION(json, 1, 0, jsonRemoveFunc), + JFUNCTION(json_array, -1, 0, jsonArrayFunc), + JFUNCTION(json_array_length, 1, 0, jsonArrayLengthFunc), + JFUNCTION(json_array_length, 2, 0, jsonArrayLengthFunc), + JFUNCTION(json_error_position,1, 0, jsonErrorFunc), + JFUNCTION(json_extract, -1, 0, jsonExtractFunc), + JFUNCTION(->, 2, JSON_JSON, jsonExtractFunc), + JFUNCTION(->>, 2, JSON_SQL, jsonExtractFunc), + JFUNCTION(json_insert, -1, 0, jsonSetFunc), + JFUNCTION(json_object, -1, 0, jsonObjectFunc), + JFUNCTION(json_patch, 2, 0, jsonPatchFunc), + JFUNCTION(json_quote, 1, 0, jsonQuoteFunc), + JFUNCTION(json_remove, -1, 0, jsonRemoveFunc), + JFUNCTION(json_replace, -1, 0, jsonReplaceFunc), + JFUNCTION(json_set, -1, JSON_ISSET, jsonSetFunc), + JFUNCTION(json_type, 1, 0, jsonTypeFunc), + JFUNCTION(json_type, 2, 0, jsonTypeFunc), + JFUNCTION(json_valid, 1, 0, jsonValidFunc), +#if SQLITE_DEBUG + JFUNCTION(json_parse, 1, 0, jsonParseFunc), + JFUNCTION(json_test1, 1, 0, jsonTest1Func), +#endif + WAGGREGATE(json_group_array, 1, 0, 0, + jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, + SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), + WAGGREGATE(json_group_object, 2, 0, 0, + jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, + SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC) + }; + sqlite3InsertBuiltinFuncs(aJsonFunc, ArraySize(aJsonFunc)); +#endif +} + +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) +/* +** Register the JSON table-valued functions +*/ +SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3 *db){ + int rc = SQLITE_OK; + static const struct { + const char *zName; + sqlite3_module *pModule; + } aMod[] = { + { "json_each", &jsonEachModule }, + { "json_tree", &jsonTreeModule }, + }; + unsigned int i; + for(i=0; i<sizeof(aMod)/sizeof(aMod[0]) && rc==SQLITE_OK; i++){ + rc = sqlite3_create_module(db, aMod[i].zName, aMod[i].pModule, 0); + } + return rc; +} +#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) */ + +/************** End of json.c ************************************************/ +/************** Begin file rtree.c *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code for implementations of the r-tree and r*-tree +** algorithms packaged as an SQLite virtual table module. +*/ + +/* +** Database Format of R-Tree Tables +** -------------------------------- +** +** The data structure for a single virtual r-tree table is stored in three +** native SQLite tables declared as follows. In each case, the '%' character +** in the table name is replaced with the user-supplied name of the r-tree +** table. +** +** CREATE TABLE %_node(nodeno INTEGER PRIMARY KEY, data BLOB) +** CREATE TABLE %_parent(nodeno INTEGER PRIMARY KEY, parentnode INTEGER) +** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER, ...) +** +** The data for each node of the r-tree structure is stored in the %_node +** table. For each node that is not the root node of the r-tree, there is +** an entry in the %_parent table associating the node with its parent. +** And for each row of data in the table, there is an entry in the %_rowid +** table that maps from the entries rowid to the id of the node that it +** is stored on. If the r-tree contains auxiliary columns, those are stored +** on the end of the %_rowid table. +** +** The root node of an r-tree always exists, even if the r-tree table is +** empty. The nodeno of the root node is always 1. All other nodes in the +** table must be the same size as the root node. The content of each node +** is formatted as follows: +** +** 1. If the node is the root node (node 1), then the first 2 bytes +** of the node contain the tree depth as a big-endian integer. +** For non-root nodes, the first 2 bytes are left unused. +** +** 2. The next 2 bytes contain the number of entries currently +** stored in the node. +** +** 3. The remainder of the node contains the node entries. Each entry +** consists of a single 8-byte integer followed by an even number +** of 4-byte coordinates. For leaf nodes the integer is the rowid +** of a record. For internal nodes it is the node number of a +** child page. +*/ + +#if !defined(SQLITE_CORE) \ + || (defined(SQLITE_ENABLE_RTREE) && !defined(SQLITE_OMIT_VIRTUALTABLE)) + +#ifndef SQLITE_CORE +/* #include "sqlite3ext.h" */ + SQLITE_EXTENSION_INIT1 +#else +/* #include "sqlite3.h" */ +#endif +SQLITE_PRIVATE int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */ + +/* +** If building separately, we will need some setup that is normally +** found in sqliteInt.h +*/ +#if !defined(SQLITE_AMALGAMATION) +#include "sqlite3rtree.h" +typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif +#if defined(NDEBUG) && defined(SQLITE_DEBUG) +# undef NDEBUG +#endif +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif +#endif /* !defined(SQLITE_AMALGAMATION) */ + +/* Macro to check for 4-byte alignment. Only used inside of assert() */ +#ifdef SQLITE_DEBUG +# define FOUR_BYTE_ALIGNED(X) ((((char*)(X) - (char*)0) & 3)==0) +#endif + +/* #include <string.h> */ +/* #include <stdio.h> */ +/* #include <assert.h> */ +/* #include <stdlib.h> */ + +/* The following macro is used to suppress compiler warnings. +*/ +#ifndef UNUSED_PARAMETER +# define UNUSED_PARAMETER(x) (void)(x) +#endif + +typedef struct Rtree Rtree; +typedef struct RtreeCursor RtreeCursor; +typedef struct RtreeNode RtreeNode; +typedef struct RtreeCell RtreeCell; +typedef struct RtreeConstraint RtreeConstraint; +typedef struct RtreeMatchArg RtreeMatchArg; +typedef struct RtreeGeomCallback RtreeGeomCallback; +typedef union RtreeCoord RtreeCoord; +typedef struct RtreeSearchPoint RtreeSearchPoint; + +/* The rtree may have between 1 and RTREE_MAX_DIMENSIONS dimensions. */ +#define RTREE_MAX_DIMENSIONS 5 + +/* Maximum number of auxiliary columns */ +#define RTREE_MAX_AUX_COLUMN 100 + +/* Size of hash table Rtree.aHash. This hash table is not expected to +** ever contain very many entries, so a fixed number of buckets is +** used. +*/ +#define HASHSIZE 97 + +/* The xBestIndex method of this virtual table requires an estimate of +** the number of rows in the virtual table to calculate the costs of +** various strategies. If possible, this estimate is loaded from the +** sqlite_stat1 table (with RTREE_MIN_ROWEST as a hard-coded minimum). +** Otherwise, if no sqlite_stat1 entry is available, use +** RTREE_DEFAULT_ROWEST. +*/ +#define RTREE_DEFAULT_ROWEST 1048576 +#define RTREE_MIN_ROWEST 100 + +/* +** An rtree virtual-table object. +*/ +struct Rtree { + sqlite3_vtab base; /* Base class. Must be first */ + sqlite3 *db; /* Host database connection */ + int iNodeSize; /* Size in bytes of each node in the node table */ + u8 nDim; /* Number of dimensions */ + u8 nDim2; /* Twice the number of dimensions */ + u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */ + u8 nBytesPerCell; /* Bytes consumed per cell */ + u8 inWrTrans; /* True if inside write transaction */ + u8 nAux; /* # of auxiliary columns in %_rowid */ +#ifdef SQLITE_ENABLE_GEOPOLY + u8 nAuxNotNull; /* Number of initial not-null aux columns */ +#endif +#ifdef SQLITE_DEBUG + u8 bCorrupt; /* Shadow table corruption detected */ +#endif + int iDepth; /* Current depth of the r-tree structure */ + char *zDb; /* Name of database containing r-tree table */ + char *zName; /* Name of r-tree table */ + u32 nBusy; /* Current number of users of this structure */ + i64 nRowEst; /* Estimated number of rows in this table */ + u32 nCursor; /* Number of open cursors */ + u32 nNodeRef; /* Number RtreeNodes with positive nRef */ + char *zReadAuxSql; /* SQL for statement to read aux data */ + + /* List of nodes removed during a CondenseTree operation. List is + ** linked together via the pointer normally used for hash chains - + ** RtreeNode.pNext. RtreeNode.iNode stores the depth of the sub-tree + ** headed by the node (leaf nodes have RtreeNode.iNode==0). + */ + RtreeNode *pDeleted; + int iReinsertHeight; /* Height of sub-trees Reinsert() has run on */ + + /* Blob I/O on xxx_node */ + sqlite3_blob *pNodeBlob; + + /* Statements to read/write/delete a record from xxx_node */ + sqlite3_stmt *pWriteNode; + sqlite3_stmt *pDeleteNode; + + /* Statements to read/write/delete a record from xxx_rowid */ + sqlite3_stmt *pReadRowid; + sqlite3_stmt *pWriteRowid; + sqlite3_stmt *pDeleteRowid; + + /* Statements to read/write/delete a record from xxx_parent */ + sqlite3_stmt *pReadParent; + sqlite3_stmt *pWriteParent; + sqlite3_stmt *pDeleteParent; + + /* Statement for writing to the "aux:" fields, if there are any */ + sqlite3_stmt *pWriteAux; + + RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */ +}; + +/* Possible values for Rtree.eCoordType: */ +#define RTREE_COORD_REAL32 0 +#define RTREE_COORD_INT32 1 + +/* +** If SQLITE_RTREE_INT_ONLY is defined, then this virtual table will +** only deal with integer coordinates. No floating point operations +** will be done. +*/ +#ifdef SQLITE_RTREE_INT_ONLY + typedef sqlite3_int64 RtreeDValue; /* High accuracy coordinate */ + typedef int RtreeValue; /* Low accuracy coordinate */ +# define RTREE_ZERO 0 +#else + typedef double RtreeDValue; /* High accuracy coordinate */ + typedef float RtreeValue; /* Low accuracy coordinate */ +# define RTREE_ZERO 0.0 +#endif + +/* +** Set the Rtree.bCorrupt flag +*/ +#ifdef SQLITE_DEBUG +# define RTREE_IS_CORRUPT(X) ((X)->bCorrupt = 1) +#else +# define RTREE_IS_CORRUPT(X) +#endif + +/* +** When doing a search of an r-tree, instances of the following structure +** record intermediate results from the tree walk. +** +** The id is always a node-id. For iLevel>=1 the id is the node-id of +** the node that the RtreeSearchPoint represents. When iLevel==0, however, +** the id is of the parent node and the cell that RtreeSearchPoint +** represents is the iCell-th entry in the parent node. +*/ +struct RtreeSearchPoint { + RtreeDValue rScore; /* The score for this node. Smallest goes first. */ + sqlite3_int64 id; /* Node ID */ + u8 iLevel; /* 0=entries. 1=leaf node. 2+ for higher */ + u8 eWithin; /* PARTLY_WITHIN or FULLY_WITHIN */ + u8 iCell; /* Cell index within the node */ +}; + +/* +** The minimum number of cells allowed for a node is a third of the +** maximum. In Gutman's notation: +** +** m = M/3 +** +** If an R*-tree "Reinsert" operation is required, the same number of +** cells are removed from the overfull node and reinserted into the tree. +*/ +#define RTREE_MINCELLS(p) ((((p)->iNodeSize-4)/(p)->nBytesPerCell)/3) +#define RTREE_REINSERT(p) RTREE_MINCELLS(p) +#define RTREE_MAXCELLS 51 + +/* +** The smallest possible node-size is (512-64)==448 bytes. And the largest +** supported cell size is 48 bytes (8 byte rowid + ten 4 byte coordinates). +** Therefore all non-root nodes must contain at least 3 entries. Since +** 3^40 is greater than 2^64, an r-tree structure always has a depth of +** 40 or less. +*/ +#define RTREE_MAX_DEPTH 40 + + +/* +** Number of entries in the cursor RtreeNode cache. The first entry is +** used to cache the RtreeNode for RtreeCursor.sPoint. The remaining +** entries cache the RtreeNode for the first elements of the priority queue. +*/ +#define RTREE_CACHE_SZ 5 + +/* +** An rtree cursor object. +*/ +struct RtreeCursor { + sqlite3_vtab_cursor base; /* Base class. Must be first */ + u8 atEOF; /* True if at end of search */ + u8 bPoint; /* True if sPoint is valid */ + u8 bAuxValid; /* True if pReadAux is valid */ + int iStrategy; /* Copy of idxNum search parameter */ + int nConstraint; /* Number of entries in aConstraint */ + RtreeConstraint *aConstraint; /* Search constraints. */ + int nPointAlloc; /* Number of slots allocated for aPoint[] */ + int nPoint; /* Number of slots used in aPoint[] */ + int mxLevel; /* iLevel value for root of the tree */ + RtreeSearchPoint *aPoint; /* Priority queue for search points */ + sqlite3_stmt *pReadAux; /* Statement to read aux-data */ + RtreeSearchPoint sPoint; /* Cached next search point */ + RtreeNode *aNode[RTREE_CACHE_SZ]; /* Rtree node cache */ + u32 anQueue[RTREE_MAX_DEPTH+1]; /* Number of queued entries by iLevel */ +}; + +/* Return the Rtree of a RtreeCursor */ +#define RTREE_OF_CURSOR(X) ((Rtree*)((X)->base.pVtab)) + +/* +** A coordinate can be either a floating point number or a integer. All +** coordinates within a single R-Tree are always of the same time. +*/ +union RtreeCoord { + RtreeValue f; /* Floating point value */ + int i; /* Integer value */ + u32 u; /* Unsigned for byte-order conversions */ +}; + +/* +** The argument is an RtreeCoord. Return the value stored within the RtreeCoord +** formatted as a RtreeDValue (double or int64). This macro assumes that local +** variable pRtree points to the Rtree structure associated with the +** RtreeCoord. +*/ +#ifdef SQLITE_RTREE_INT_ONLY +# define DCOORD(coord) ((RtreeDValue)coord.i) +#else +# define DCOORD(coord) ( \ + (pRtree->eCoordType==RTREE_COORD_REAL32) ? \ + ((double)coord.f) : \ + ((double)coord.i) \ + ) +#endif + +/* +** A search constraint. +*/ +struct RtreeConstraint { + int iCoord; /* Index of constrained coordinate */ + int op; /* Constraining operation */ + union { + RtreeDValue rValue; /* Constraint value. */ + int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*); + int (*xQueryFunc)(sqlite3_rtree_query_info*); + } u; + sqlite3_rtree_query_info *pInfo; /* xGeom and xQueryFunc argument */ +}; + +/* Possible values for RtreeConstraint.op */ +#define RTREE_EQ 0x41 /* A */ +#define RTREE_LE 0x42 /* B */ +#define RTREE_LT 0x43 /* C */ +#define RTREE_GE 0x44 /* D */ +#define RTREE_GT 0x45 /* E */ +#define RTREE_MATCH 0x46 /* F: Old-style sqlite3_rtree_geometry_callback() */ +#define RTREE_QUERY 0x47 /* G: New-style sqlite3_rtree_query_callback() */ + +/* Special operators available only on cursors. Needs to be consecutive +** with the normal values above, but must be less than RTREE_MATCH. These +** are used in the cursor for contraints such as x=NULL (RTREE_FALSE) or +** x<'xyz' (RTREE_TRUE) */ +#define RTREE_TRUE 0x3f /* ? */ +#define RTREE_FALSE 0x40 /* @ */ + +/* +** An rtree structure node. +*/ +struct RtreeNode { + RtreeNode *pParent; /* Parent node */ + i64 iNode; /* The node number */ + int nRef; /* Number of references to this node */ + int isDirty; /* True if the node needs to be written to disk */ + u8 *zData; /* Content of the node, as should be on disk */ + RtreeNode *pNext; /* Next node in this hash collision chain */ +}; + +/* Return the number of cells in a node */ +#define NCELL(pNode) readInt16(&(pNode)->zData[2]) + +/* +** A single cell from a node, deserialized +*/ +struct RtreeCell { + i64 iRowid; /* Node or entry ID */ + RtreeCoord aCoord[RTREE_MAX_DIMENSIONS*2]; /* Bounding box coordinates */ +}; + + +/* +** This object becomes the sqlite3_user_data() for the SQL functions +** that are created by sqlite3_rtree_geometry_callback() and +** sqlite3_rtree_query_callback() and which appear on the right of MATCH +** operators in order to constrain a search. +** +** xGeom and xQueryFunc are the callback functions. Exactly one of +** xGeom and xQueryFunc fields is non-NULL, depending on whether the +** SQL function was created using sqlite3_rtree_geometry_callback() or +** sqlite3_rtree_query_callback(). +** +** This object is deleted automatically by the destructor mechanism in +** sqlite3_create_function_v2(). +*/ +struct RtreeGeomCallback { + int (*xGeom)(sqlite3_rtree_geometry*, int, RtreeDValue*, int*); + int (*xQueryFunc)(sqlite3_rtree_query_info*); + void (*xDestructor)(void*); + void *pContext; +}; + +/* +** An instance of this structure (in the form of a BLOB) is returned by +** the SQL functions that sqlite3_rtree_geometry_callback() and +** sqlite3_rtree_query_callback() create, and is read as the right-hand +** operand to the MATCH operator of an R-Tree. +*/ +struct RtreeMatchArg { + u32 iSize; /* Size of this object */ + RtreeGeomCallback cb; /* Info about the callback functions */ + int nParam; /* Number of parameters to the SQL function */ + sqlite3_value **apSqlParam; /* Original SQL parameter values */ + RtreeDValue aParam[1]; /* Values for parameters to the SQL function */ +}; + +#ifndef MAX +# define MAX(x,y) ((x) < (y) ? (y) : (x)) +#endif +#ifndef MIN +# define MIN(x,y) ((x) > (y) ? (y) : (x)) +#endif + +/* What version of GCC is being used. 0 means GCC is not being used . +** Note that the GCC_VERSION macro will also be set correctly when using +** clang, since clang works hard to be gcc compatible. So the gcc +** optimizations will also work when compiling with clang. +*/ +#ifndef GCC_VERSION +#if defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC) +# define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__) +#else +# define GCC_VERSION 0 +#endif +#endif + +/* The testcase() macro should already be defined in the amalgamation. If +** it is not, make it a no-op. +*/ +#ifndef SQLITE_AMALGAMATION +# if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) + unsigned int sqlite3RtreeTestcase = 0; +# define testcase(X) if( X ){ sqlite3RtreeTestcase += __LINE__; } +# else +# define testcase(X) +# endif +#endif + +/* +** Make sure that the compiler intrinsics we desire are enabled when +** compiling with an appropriate version of MSVC unless prevented by +** the SQLITE_DISABLE_INTRINSIC define. +*/ +#if !defined(SQLITE_DISABLE_INTRINSIC) +# if defined(_MSC_VER) && _MSC_VER>=1400 +# if !defined(_WIN32_WCE) +/* # include <intrin.h> */ +# pragma intrinsic(_byteswap_ulong) +# pragma intrinsic(_byteswap_uint64) +# else +/* # include <cmnintrin.h> */ +# endif +# endif +#endif + +/* +** Macros to determine whether the machine is big or little endian, +** and whether or not that determination is run-time or compile-time. +** +** For best performance, an attempt is made to guess at the byte-order +** using C-preprocessor macros. If that is unsuccessful, or if +** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined +** at run-time. +*/ +#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */ +# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__ +# define SQLITE_BYTEORDER 4321 +# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ +# define SQLITE_BYTEORDER 1234 +# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1 +# define SQLITE_BYTEORDER 4321 +# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ + defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64) +# define SQLITE_BYTEORDER 1234 +# elif defined(sparc) || defined(__ARMEB__) || defined(__AARCH64EB__) +# define SQLITE_BYTEORDER 4321 +# else +# define SQLITE_BYTEORDER 0 +# endif +#endif + + +/* What version of MSVC is being used. 0 means MSVC is not being used */ +#ifndef MSVC_VERSION +#if defined(_MSC_VER) && !defined(SQLITE_DISABLE_INTRINSIC) +# define MSVC_VERSION _MSC_VER +#else +# define MSVC_VERSION 0 +#endif +#endif + +/* +** Functions to deserialize a 16 bit integer, 32 bit real number and +** 64 bit integer. The deserialized value is returned. +*/ +static int readInt16(u8 *p){ + return (p[0]<<8) + p[1]; +} +static void readCoord(u8 *p, RtreeCoord *pCoord){ + assert( FOUR_BYTE_ALIGNED(p) ); +#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 + pCoord->u = _byteswap_ulong(*(u32*)p); +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 + pCoord->u = __builtin_bswap32(*(u32*)p); +#elif SQLITE_BYTEORDER==4321 + pCoord->u = *(u32*)p; +#else + pCoord->u = ( + (((u32)p[0]) << 24) + + (((u32)p[1]) << 16) + + (((u32)p[2]) << 8) + + (((u32)p[3]) << 0) + ); +#endif +} +static i64 readInt64(u8 *p){ +#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 + u64 x; + memcpy(&x, p, 8); + return (i64)_byteswap_uint64(x); +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 + u64 x; + memcpy(&x, p, 8); + return (i64)__builtin_bswap64(x); +#elif SQLITE_BYTEORDER==4321 + i64 x; + memcpy(&x, p, 8); + return x; +#else + return (i64)( + (((u64)p[0]) << 56) + + (((u64)p[1]) << 48) + + (((u64)p[2]) << 40) + + (((u64)p[3]) << 32) + + (((u64)p[4]) << 24) + + (((u64)p[5]) << 16) + + (((u64)p[6]) << 8) + + (((u64)p[7]) << 0) + ); +#endif +} + +/* +** Functions to serialize a 16 bit integer, 32 bit real number and +** 64 bit integer. The value returned is the number of bytes written +** to the argument buffer (always 2, 4 and 8 respectively). +*/ +static void writeInt16(u8 *p, int i){ + p[0] = (i>> 8)&0xFF; + p[1] = (i>> 0)&0xFF; +} +static int writeCoord(u8 *p, RtreeCoord *pCoord){ + u32 i; + assert( FOUR_BYTE_ALIGNED(p) ); + assert( sizeof(RtreeCoord)==4 ); + assert( sizeof(u32)==4 ); +#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 + i = __builtin_bswap32(pCoord->u); + memcpy(p, &i, 4); +#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 + i = _byteswap_ulong(pCoord->u); + memcpy(p, &i, 4); +#elif SQLITE_BYTEORDER==4321 + i = pCoord->u; + memcpy(p, &i, 4); +#else + i = pCoord->u; + p[0] = (i>>24)&0xFF; + p[1] = (i>>16)&0xFF; + p[2] = (i>> 8)&0xFF; + p[3] = (i>> 0)&0xFF; +#endif + return 4; +} +static int writeInt64(u8 *p, i64 i){ +#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 + i = (i64)__builtin_bswap64((u64)i); + memcpy(p, &i, 8); +#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 + i = (i64)_byteswap_uint64((u64)i); + memcpy(p, &i, 8); +#elif SQLITE_BYTEORDER==4321 + memcpy(p, &i, 8); +#else + p[0] = (i>>56)&0xFF; + p[1] = (i>>48)&0xFF; + p[2] = (i>>40)&0xFF; + p[3] = (i>>32)&0xFF; + p[4] = (i>>24)&0xFF; + p[5] = (i>>16)&0xFF; + p[6] = (i>> 8)&0xFF; + p[7] = (i>> 0)&0xFF; +#endif + return 8; +} + +/* +** Increment the reference count of node p. +*/ +static void nodeReference(RtreeNode *p){ + if( p ){ + assert( p->nRef>0 ); + p->nRef++; + } +} + +/* +** Clear the content of node p (set all bytes to 0x00). +*/ +static void nodeZero(Rtree *pRtree, RtreeNode *p){ + memset(&p->zData[2], 0, pRtree->iNodeSize-2); + p->isDirty = 1; +} + +/* +** Given a node number iNode, return the corresponding key to use +** in the Rtree.aHash table. +*/ +static unsigned int nodeHash(i64 iNode){ + return ((unsigned)iNode) % HASHSIZE; +} + +/* +** Search the node hash table for node iNode. If found, return a pointer +** to it. Otherwise, return 0. +*/ +static RtreeNode *nodeHashLookup(Rtree *pRtree, i64 iNode){ + RtreeNode *p; + for(p=pRtree->aHash[nodeHash(iNode)]; p && p->iNode!=iNode; p=p->pNext); + return p; +} + +/* +** Add node pNode to the node hash table. +*/ +static void nodeHashInsert(Rtree *pRtree, RtreeNode *pNode){ + int iHash; + assert( pNode->pNext==0 ); + iHash = nodeHash(pNode->iNode); + pNode->pNext = pRtree->aHash[iHash]; + pRtree->aHash[iHash] = pNode; +} + +/* +** Remove node pNode from the node hash table. +*/ +static void nodeHashDelete(Rtree *pRtree, RtreeNode *pNode){ + RtreeNode **pp; + if( pNode->iNode!=0 ){ + pp = &pRtree->aHash[nodeHash(pNode->iNode)]; + for( ; (*pp)!=pNode; pp = &(*pp)->pNext){ assert(*pp); } + *pp = pNode->pNext; + pNode->pNext = 0; + } +} + +/* +** Allocate and return new r-tree node. Initially, (RtreeNode.iNode==0), +** indicating that node has not yet been assigned a node number. It is +** assigned a node number when nodeWrite() is called to write the +** node contents out to the database. +*/ +static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){ + RtreeNode *pNode; + pNode = (RtreeNode *)sqlite3_malloc64(sizeof(RtreeNode) + pRtree->iNodeSize); + if( pNode ){ + memset(pNode, 0, sizeof(RtreeNode) + pRtree->iNodeSize); + pNode->zData = (u8 *)&pNode[1]; + pNode->nRef = 1; + pRtree->nNodeRef++; + pNode->pParent = pParent; + pNode->isDirty = 1; + nodeReference(pParent); + } + return pNode; +} + +/* +** Clear the Rtree.pNodeBlob object +*/ +static void nodeBlobReset(Rtree *pRtree){ + if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){ + sqlite3_blob *pBlob = pRtree->pNodeBlob; + pRtree->pNodeBlob = 0; + sqlite3_blob_close(pBlob); + } +} + +/* +** Obtain a reference to an r-tree node. +*/ +static int nodeAcquire( + Rtree *pRtree, /* R-tree structure */ + i64 iNode, /* Node number to load */ + RtreeNode *pParent, /* Either the parent node or NULL */ + RtreeNode **ppNode /* OUT: Acquired node */ +){ + int rc = SQLITE_OK; + RtreeNode *pNode = 0; + + /* Check if the requested node is already in the hash table. If so, + ** increase its reference count and return it. + */ + if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){ + if( pParent && pParent!=pNode->pParent ){ + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; + } + pNode->nRef++; + *ppNode = pNode; + return SQLITE_OK; + } + + if( pRtree->pNodeBlob ){ + sqlite3_blob *pBlob = pRtree->pNodeBlob; + pRtree->pNodeBlob = 0; + rc = sqlite3_blob_reopen(pBlob, iNode); + pRtree->pNodeBlob = pBlob; + if( rc ){ + nodeBlobReset(pRtree); + if( rc==SQLITE_NOMEM ) return SQLITE_NOMEM; + } + } + if( pRtree->pNodeBlob==0 ){ + char *zTab = sqlite3_mprintf("%s_node", pRtree->zName); + if( zTab==0 ) return SQLITE_NOMEM; + rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0, + &pRtree->pNodeBlob); + sqlite3_free(zTab); + } + if( rc ){ + nodeBlobReset(pRtree); + *ppNode = 0; + /* If unable to open an sqlite3_blob on the desired row, that can only + ** be because the shadow tables hold erroneous data. */ + if( rc==SQLITE_ERROR ){ + rc = SQLITE_CORRUPT_VTAB; + RTREE_IS_CORRUPT(pRtree); + } + }else if( pRtree->iNodeSize==sqlite3_blob_bytes(pRtree->pNodeBlob) ){ + pNode = (RtreeNode *)sqlite3_malloc64(sizeof(RtreeNode)+pRtree->iNodeSize); + if( !pNode ){ + rc = SQLITE_NOMEM; + }else{ + pNode->pParent = pParent; + pNode->zData = (u8 *)&pNode[1]; + pNode->nRef = 1; + pRtree->nNodeRef++; + pNode->iNode = iNode; + pNode->isDirty = 0; + pNode->pNext = 0; + rc = sqlite3_blob_read(pRtree->pNodeBlob, pNode->zData, + pRtree->iNodeSize, 0); + } + } + + /* If the root node was just loaded, set pRtree->iDepth to the height + ** of the r-tree structure. A height of zero means all data is stored on + ** the root node. A height of one means the children of the root node + ** are the leaves, and so on. If the depth as specified on the root node + ** is greater than RTREE_MAX_DEPTH, the r-tree structure must be corrupt. + */ + if( rc==SQLITE_OK && pNode && iNode==1 ){ + pRtree->iDepth = readInt16(pNode->zData); + if( pRtree->iDepth>RTREE_MAX_DEPTH ){ + rc = SQLITE_CORRUPT_VTAB; + RTREE_IS_CORRUPT(pRtree); + } + } + + /* If no error has occurred so far, check if the "number of entries" + ** field on the node is too large. If so, set the return code to + ** SQLITE_CORRUPT_VTAB. + */ + if( pNode && rc==SQLITE_OK ){ + if( NCELL(pNode)>((pRtree->iNodeSize-4)/pRtree->nBytesPerCell) ){ + rc = SQLITE_CORRUPT_VTAB; + RTREE_IS_CORRUPT(pRtree); + } + } + + if( rc==SQLITE_OK ){ + if( pNode!=0 ){ + nodeReference(pParent); + nodeHashInsert(pRtree, pNode); + }else{ + rc = SQLITE_CORRUPT_VTAB; + RTREE_IS_CORRUPT(pRtree); + } + *ppNode = pNode; + }else{ + if( pNode ){ + pRtree->nNodeRef--; + sqlite3_free(pNode); + } + *ppNode = 0; + } + + return rc; +} + +/* +** Overwrite cell iCell of node pNode with the contents of pCell. +*/ +static void nodeOverwriteCell( + Rtree *pRtree, /* The overall R-Tree */ + RtreeNode *pNode, /* The node into which the cell is to be written */ + RtreeCell *pCell, /* The cell to write */ + int iCell /* Index into pNode into which pCell is written */ +){ + int ii; + u8 *p = &pNode->zData[4 + pRtree->nBytesPerCell*iCell]; + p += writeInt64(p, pCell->iRowid); + for(ii=0; ii<pRtree->nDim2; ii++){ + p += writeCoord(p, &pCell->aCoord[ii]); + } + pNode->isDirty = 1; +} + +/* +** Remove the cell with index iCell from node pNode. +*/ +static void nodeDeleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell){ + u8 *pDst = &pNode->zData[4 + pRtree->nBytesPerCell*iCell]; + u8 *pSrc = &pDst[pRtree->nBytesPerCell]; + int nByte = (NCELL(pNode) - iCell - 1) * pRtree->nBytesPerCell; + memmove(pDst, pSrc, nByte); + writeInt16(&pNode->zData[2], NCELL(pNode)-1); + pNode->isDirty = 1; +} + +/* +** Insert the contents of cell pCell into node pNode. If the insert +** is successful, return SQLITE_OK. +** +** If there is not enough free space in pNode, return SQLITE_FULL. +*/ +static int nodeInsertCell( + Rtree *pRtree, /* The overall R-Tree */ + RtreeNode *pNode, /* Write new cell into this node */ + RtreeCell *pCell /* The cell to be inserted */ +){ + int nCell; /* Current number of cells in pNode */ + int nMaxCell; /* Maximum number of cells for pNode */ + + nMaxCell = (pRtree->iNodeSize-4)/pRtree->nBytesPerCell; + nCell = NCELL(pNode); + + assert( nCell<=nMaxCell ); + if( nCell<nMaxCell ){ + nodeOverwriteCell(pRtree, pNode, pCell, nCell); + writeInt16(&pNode->zData[2], nCell+1); + pNode->isDirty = 1; + } + + return (nCell==nMaxCell); +} + +/* +** If the node is dirty, write it out to the database. +*/ +static int nodeWrite(Rtree *pRtree, RtreeNode *pNode){ + int rc = SQLITE_OK; + if( pNode->isDirty ){ + sqlite3_stmt *p = pRtree->pWriteNode; + if( pNode->iNode ){ + sqlite3_bind_int64(p, 1, pNode->iNode); + }else{ + sqlite3_bind_null(p, 1); + } + sqlite3_bind_blob(p, 2, pNode->zData, pRtree->iNodeSize, SQLITE_STATIC); + sqlite3_step(p); + pNode->isDirty = 0; + rc = sqlite3_reset(p); + sqlite3_bind_null(p, 2); + if( pNode->iNode==0 && rc==SQLITE_OK ){ + pNode->iNode = sqlite3_last_insert_rowid(pRtree->db); + nodeHashInsert(pRtree, pNode); + } + } + return rc; +} + +/* +** Release a reference to a node. If the node is dirty and the reference +** count drops to zero, the node data is written to the database. +*/ +static int nodeRelease(Rtree *pRtree, RtreeNode *pNode){ + int rc = SQLITE_OK; + if( pNode ){ + assert( pNode->nRef>0 ); + assert( pRtree->nNodeRef>0 ); + pNode->nRef--; + if( pNode->nRef==0 ){ + pRtree->nNodeRef--; + if( pNode->iNode==1 ){ + pRtree->iDepth = -1; + } + if( pNode->pParent ){ + rc = nodeRelease(pRtree, pNode->pParent); + } + if( rc==SQLITE_OK ){ + rc = nodeWrite(pRtree, pNode); + } + nodeHashDelete(pRtree, pNode); + sqlite3_free(pNode); + } + } + return rc; +} + +/* +** Return the 64-bit integer value associated with cell iCell of +** node pNode. If pNode is a leaf node, this is a rowid. If it is +** an internal node, then the 64-bit integer is a child page number. +*/ +static i64 nodeGetRowid( + Rtree *pRtree, /* The overall R-Tree */ + RtreeNode *pNode, /* The node from which to extract the ID */ + int iCell /* The cell index from which to extract the ID */ +){ + assert( iCell<NCELL(pNode) ); + return readInt64(&pNode->zData[4 + pRtree->nBytesPerCell*iCell]); +} + +/* +** Return coordinate iCoord from cell iCell in node pNode. +*/ +static void nodeGetCoord( + Rtree *pRtree, /* The overall R-Tree */ + RtreeNode *pNode, /* The node from which to extract a coordinate */ + int iCell, /* The index of the cell within the node */ + int iCoord, /* Which coordinate to extract */ + RtreeCoord *pCoord /* OUT: Space to write result to */ +){ + readCoord(&pNode->zData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord); +} + +/* +** Deserialize cell iCell of node pNode. Populate the structure pointed +** to by pCell with the results. +*/ +static void nodeGetCell( + Rtree *pRtree, /* The overall R-Tree */ + RtreeNode *pNode, /* The node containing the cell to be read */ + int iCell, /* Index of the cell within the node */ + RtreeCell *pCell /* OUT: Write the cell contents here */ +){ + u8 *pData; + RtreeCoord *pCoord; + int ii = 0; + pCell->iRowid = nodeGetRowid(pRtree, pNode, iCell); + pData = pNode->zData + (12 + pRtree->nBytesPerCell*iCell); + pCoord = pCell->aCoord; + do{ + readCoord(pData, &pCoord[ii]); + readCoord(pData+4, &pCoord[ii+1]); + pData += 8; + ii += 2; + }while( ii<pRtree->nDim2 ); +} + + +/* Forward declaration for the function that does the work of +** the virtual table module xCreate() and xConnect() methods. +*/ +static int rtreeInit( + sqlite3 *, void *, int, const char *const*, sqlite3_vtab **, char **, int +); + +/* +** Rtree virtual table module xCreate method. +*/ +static int rtreeCreate( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + return rtreeInit(db, pAux, argc, argv, ppVtab, pzErr, 1); +} + +/* +** Rtree virtual table module xConnect method. +*/ +static int rtreeConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + return rtreeInit(db, pAux, argc, argv, ppVtab, pzErr, 0); +} + +/* +** Increment the r-tree reference count. +*/ +static void rtreeReference(Rtree *pRtree){ + pRtree->nBusy++; +} + +/* +** Decrement the r-tree reference count. When the reference count reaches +** zero the structure is deleted. +*/ +static void rtreeRelease(Rtree *pRtree){ + pRtree->nBusy--; + if( pRtree->nBusy==0 ){ + pRtree->inWrTrans = 0; + assert( pRtree->nCursor==0 ); + nodeBlobReset(pRtree); + assert( pRtree->nNodeRef==0 || pRtree->bCorrupt ); + sqlite3_finalize(pRtree->pWriteNode); + sqlite3_finalize(pRtree->pDeleteNode); + sqlite3_finalize(pRtree->pReadRowid); + sqlite3_finalize(pRtree->pWriteRowid); + sqlite3_finalize(pRtree->pDeleteRowid); + sqlite3_finalize(pRtree->pReadParent); + sqlite3_finalize(pRtree->pWriteParent); + sqlite3_finalize(pRtree->pDeleteParent); + sqlite3_finalize(pRtree->pWriteAux); + sqlite3_free(pRtree->zReadAuxSql); + sqlite3_free(pRtree); + } +} + +/* +** Rtree virtual table module xDisconnect method. +*/ +static int rtreeDisconnect(sqlite3_vtab *pVtab){ + rtreeRelease((Rtree *)pVtab); + return SQLITE_OK; +} + +/* +** Rtree virtual table module xDestroy method. +*/ +static int rtreeDestroy(sqlite3_vtab *pVtab){ + Rtree *pRtree = (Rtree *)pVtab; + int rc; + char *zCreate = sqlite3_mprintf( + "DROP TABLE '%q'.'%q_node';" + "DROP TABLE '%q'.'%q_rowid';" + "DROP TABLE '%q'.'%q_parent';", + pRtree->zDb, pRtree->zName, + pRtree->zDb, pRtree->zName, + pRtree->zDb, pRtree->zName + ); + if( !zCreate ){ + rc = SQLITE_NOMEM; + }else{ + nodeBlobReset(pRtree); + rc = sqlite3_exec(pRtree->db, zCreate, 0, 0, 0); + sqlite3_free(zCreate); + } + if( rc==SQLITE_OK ){ + rtreeRelease(pRtree); + } + + return rc; +} + +/* +** Rtree virtual table module xOpen method. +*/ +static int rtreeOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + int rc = SQLITE_NOMEM; + Rtree *pRtree = (Rtree *)pVTab; + RtreeCursor *pCsr; + + pCsr = (RtreeCursor *)sqlite3_malloc64(sizeof(RtreeCursor)); + if( pCsr ){ + memset(pCsr, 0, sizeof(RtreeCursor)); + pCsr->base.pVtab = pVTab; + rc = SQLITE_OK; + pRtree->nCursor++; + } + *ppCursor = (sqlite3_vtab_cursor *)pCsr; + + return rc; +} + + +/* +** Reset a cursor back to its initial state. +*/ +static void resetCursor(RtreeCursor *pCsr){ + Rtree *pRtree = (Rtree *)(pCsr->base.pVtab); + int ii; + sqlite3_stmt *pStmt; + if( pCsr->aConstraint ){ + int i; /* Used to iterate through constraint array */ + for(i=0; i<pCsr->nConstraint; i++){ + sqlite3_rtree_query_info *pInfo = pCsr->aConstraint[i].pInfo; + if( pInfo ){ + if( pInfo->xDelUser ) pInfo->xDelUser(pInfo->pUser); + sqlite3_free(pInfo); + } + } + sqlite3_free(pCsr->aConstraint); + pCsr->aConstraint = 0; + } + for(ii=0; ii<RTREE_CACHE_SZ; ii++) nodeRelease(pRtree, pCsr->aNode[ii]); + sqlite3_free(pCsr->aPoint); + pStmt = pCsr->pReadAux; + memset(pCsr, 0, sizeof(RtreeCursor)); + pCsr->base.pVtab = (sqlite3_vtab*)pRtree; + pCsr->pReadAux = pStmt; + +} + +/* +** Rtree virtual table module xClose method. +*/ +static int rtreeClose(sqlite3_vtab_cursor *cur){ + Rtree *pRtree = (Rtree *)(cur->pVtab); + RtreeCursor *pCsr = (RtreeCursor *)cur; + assert( pRtree->nCursor>0 ); + resetCursor(pCsr); + sqlite3_finalize(pCsr->pReadAux); + sqlite3_free(pCsr); + pRtree->nCursor--; + nodeBlobReset(pRtree); + return SQLITE_OK; +} + +/* +** Rtree virtual table module xEof method. +** +** Return non-zero if the cursor does not currently point to a valid +** record (i.e if the scan has finished), or zero otherwise. +*/ +static int rtreeEof(sqlite3_vtab_cursor *cur){ + RtreeCursor *pCsr = (RtreeCursor *)cur; + return pCsr->atEOF; +} + +/* +** Convert raw bits from the on-disk RTree record into a coordinate value. +** The on-disk format is big-endian and needs to be converted for little- +** endian platforms. The on-disk record stores integer coordinates if +** eInt is true and it stores 32-bit floating point records if eInt is +** false. a[] is the four bytes of the on-disk record to be decoded. +** Store the results in "r". +** +** There are five versions of this macro. The last one is generic. The +** other four are various architectures-specific optimizations. +*/ +#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 +#define RTREE_DECODE_COORD(eInt, a, r) { \ + RtreeCoord c; /* Coordinate decoded */ \ + c.u = _byteswap_ulong(*(u32*)a); \ + r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ +} +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 +#define RTREE_DECODE_COORD(eInt, a, r) { \ + RtreeCoord c; /* Coordinate decoded */ \ + c.u = __builtin_bswap32(*(u32*)a); \ + r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ +} +#elif SQLITE_BYTEORDER==1234 +#define RTREE_DECODE_COORD(eInt, a, r) { \ + RtreeCoord c; /* Coordinate decoded */ \ + memcpy(&c.u,a,4); \ + c.u = ((c.u>>24)&0xff)|((c.u>>8)&0xff00)| \ + ((c.u&0xff)<<24)|((c.u&0xff00)<<8); \ + r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ +} +#elif SQLITE_BYTEORDER==4321 +#define RTREE_DECODE_COORD(eInt, a, r) { \ + RtreeCoord c; /* Coordinate decoded */ \ + memcpy(&c.u,a,4); \ + r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ +} +#else +#define RTREE_DECODE_COORD(eInt, a, r) { \ + RtreeCoord c; /* Coordinate decoded */ \ + c.u = ((u32)a[0]<<24) + ((u32)a[1]<<16) \ + +((u32)a[2]<<8) + a[3]; \ + r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ +} +#endif + +/* +** Check the RTree node or entry given by pCellData and p against the MATCH +** constraint pConstraint. +*/ +static int rtreeCallbackConstraint( + RtreeConstraint *pConstraint, /* The constraint to test */ + int eInt, /* True if RTree holding integer coordinates */ + u8 *pCellData, /* Raw cell content */ + RtreeSearchPoint *pSearch, /* Container of this cell */ + sqlite3_rtree_dbl *prScore, /* OUT: score for the cell */ + int *peWithin /* OUT: visibility of the cell */ +){ + sqlite3_rtree_query_info *pInfo = pConstraint->pInfo; /* Callback info */ + int nCoord = pInfo->nCoord; /* No. of coordinates */ + int rc; /* Callback return code */ + RtreeCoord c; /* Translator union */ + sqlite3_rtree_dbl aCoord[RTREE_MAX_DIMENSIONS*2]; /* Decoded coordinates */ + + assert( pConstraint->op==RTREE_MATCH || pConstraint->op==RTREE_QUERY ); + assert( nCoord==2 || nCoord==4 || nCoord==6 || nCoord==8 || nCoord==10 ); + + if( pConstraint->op==RTREE_QUERY && pSearch->iLevel==1 ){ + pInfo->iRowid = readInt64(pCellData); + } + pCellData += 8; +#ifndef SQLITE_RTREE_INT_ONLY + if( eInt==0 ){ + switch( nCoord ){ + case 10: readCoord(pCellData+36, &c); aCoord[9] = c.f; + readCoord(pCellData+32, &c); aCoord[8] = c.f; + case 8: readCoord(pCellData+28, &c); aCoord[7] = c.f; + readCoord(pCellData+24, &c); aCoord[6] = c.f; + case 6: readCoord(pCellData+20, &c); aCoord[5] = c.f; + readCoord(pCellData+16, &c); aCoord[4] = c.f; + case 4: readCoord(pCellData+12, &c); aCoord[3] = c.f; + readCoord(pCellData+8, &c); aCoord[2] = c.f; + default: readCoord(pCellData+4, &c); aCoord[1] = c.f; + readCoord(pCellData, &c); aCoord[0] = c.f; + } + }else +#endif + { + switch( nCoord ){ + case 10: readCoord(pCellData+36, &c); aCoord[9] = c.i; + readCoord(pCellData+32, &c); aCoord[8] = c.i; + case 8: readCoord(pCellData+28, &c); aCoord[7] = c.i; + readCoord(pCellData+24, &c); aCoord[6] = c.i; + case 6: readCoord(pCellData+20, &c); aCoord[5] = c.i; + readCoord(pCellData+16, &c); aCoord[4] = c.i; + case 4: readCoord(pCellData+12, &c); aCoord[3] = c.i; + readCoord(pCellData+8, &c); aCoord[2] = c.i; + default: readCoord(pCellData+4, &c); aCoord[1] = c.i; + readCoord(pCellData, &c); aCoord[0] = c.i; + } + } + if( pConstraint->op==RTREE_MATCH ){ + int eWithin = 0; + rc = pConstraint->u.xGeom((sqlite3_rtree_geometry*)pInfo, + nCoord, aCoord, &eWithin); + if( eWithin==0 ) *peWithin = NOT_WITHIN; + *prScore = RTREE_ZERO; + }else{ + pInfo->aCoord = aCoord; + pInfo->iLevel = pSearch->iLevel - 1; + pInfo->rScore = pInfo->rParentScore = pSearch->rScore; + pInfo->eWithin = pInfo->eParentWithin = pSearch->eWithin; + rc = pConstraint->u.xQueryFunc(pInfo); + if( pInfo->eWithin<*peWithin ) *peWithin = pInfo->eWithin; + if( pInfo->rScore<*prScore || *prScore<RTREE_ZERO ){ + *prScore = pInfo->rScore; + } + } + return rc; +} + +/* +** Check the internal RTree node given by pCellData against constraint p. +** If this constraint cannot be satisfied by any child within the node, +** set *peWithin to NOT_WITHIN. +*/ +static void rtreeNonleafConstraint( + RtreeConstraint *p, /* The constraint to test */ + int eInt, /* True if RTree holds integer coordinates */ + u8 *pCellData, /* Raw cell content as appears on disk */ + int *peWithin /* Adjust downward, as appropriate */ +){ + sqlite3_rtree_dbl val; /* Coordinate value convert to a double */ + + /* p->iCoord might point to either a lower or upper bound coordinate + ** in a coordinate pair. But make pCellData point to the lower bound. + */ + pCellData += 8 + 4*(p->iCoord&0xfe); + + assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE + || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE + || p->op==RTREE_FALSE ); + assert( FOUR_BYTE_ALIGNED(pCellData) ); + switch( p->op ){ + case RTREE_TRUE: return; /* Always satisfied */ + case RTREE_FALSE: break; /* Never satisfied */ + case RTREE_EQ: + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the lower bound of the coordinate pair */ + if( p->u.rValue>=val ){ + pCellData += 4; + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the upper bound of the coordinate pair */ + if( p->u.rValue<=val ) return; + } + break; + case RTREE_LE: + case RTREE_LT: + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the lower bound of the coordinate pair */ + if( p->u.rValue>=val ) return; + break; + + default: + pCellData += 4; + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the upper bound of the coordinate pair */ + if( p->u.rValue<=val ) return; + break; + } + *peWithin = NOT_WITHIN; +} + +/* +** Check the leaf RTree cell given by pCellData against constraint p. +** If this constraint is not satisfied, set *peWithin to NOT_WITHIN. +** If the constraint is satisfied, leave *peWithin unchanged. +** +** The constraint is of the form: xN op $val +** +** The op is given by p->op. The xN is p->iCoord-th coordinate in +** pCellData. $val is given by p->u.rValue. +*/ +static void rtreeLeafConstraint( + RtreeConstraint *p, /* The constraint to test */ + int eInt, /* True if RTree holds integer coordinates */ + u8 *pCellData, /* Raw cell content as appears on disk */ + int *peWithin /* Adjust downward, as appropriate */ +){ + RtreeDValue xN; /* Coordinate value converted to a double */ + + assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE + || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE + || p->op==RTREE_FALSE ); + pCellData += 8 + p->iCoord*4; + assert( FOUR_BYTE_ALIGNED(pCellData) ); + RTREE_DECODE_COORD(eInt, pCellData, xN); + switch( p->op ){ + case RTREE_TRUE: return; /* Always satisfied */ + case RTREE_FALSE: break; /* Never satisfied */ + case RTREE_LE: if( xN <= p->u.rValue ) return; break; + case RTREE_LT: if( xN < p->u.rValue ) return; break; + case RTREE_GE: if( xN >= p->u.rValue ) return; break; + case RTREE_GT: if( xN > p->u.rValue ) return; break; + default: if( xN == p->u.rValue ) return; break; + } + *peWithin = NOT_WITHIN; +} + +/* +** One of the cells in node pNode is guaranteed to have a 64-bit +** integer value equal to iRowid. Return the index of this cell. +*/ +static int nodeRowidIndex( + Rtree *pRtree, + RtreeNode *pNode, + i64 iRowid, + int *piIndex +){ + int ii; + int nCell = NCELL(pNode); + assert( nCell<200 ); + for(ii=0; ii<nCell; ii++){ + if( nodeGetRowid(pRtree, pNode, ii)==iRowid ){ + *piIndex = ii; + return SQLITE_OK; + } + } + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; +} + +/* +** Return the index of the cell containing a pointer to node pNode +** in its parent. If pNode is the root node, return -1. +*/ +static int nodeParentIndex(Rtree *pRtree, RtreeNode *pNode, int *piIndex){ + RtreeNode *pParent = pNode->pParent; + if( ALWAYS(pParent) ){ + return nodeRowidIndex(pRtree, pParent, pNode->iNode, piIndex); + }else{ + *piIndex = -1; + return SQLITE_OK; + } +} + +/* +** Compare two search points. Return negative, zero, or positive if the first +** is less than, equal to, or greater than the second. +** +** The rScore is the primary key. Smaller rScore values come first. +** If the rScore is a tie, then use iLevel as the tie breaker with smaller +** iLevel values coming first. In this way, if rScore is the same for all +** SearchPoints, then iLevel becomes the deciding factor and the result +** is a depth-first search, which is the desired default behavior. +*/ +static int rtreeSearchPointCompare( + const RtreeSearchPoint *pA, + const RtreeSearchPoint *pB +){ + if( pA->rScore<pB->rScore ) return -1; + if( pA->rScore>pB->rScore ) return +1; + if( pA->iLevel<pB->iLevel ) return -1; + if( pA->iLevel>pB->iLevel ) return +1; + return 0; +} + +/* +** Interchange two search points in a cursor. +*/ +static void rtreeSearchPointSwap(RtreeCursor *p, int i, int j){ + RtreeSearchPoint t = p->aPoint[i]; + assert( i<j ); + p->aPoint[i] = p->aPoint[j]; + p->aPoint[j] = t; + i++; j++; + if( i<RTREE_CACHE_SZ ){ + if( j>=RTREE_CACHE_SZ ){ + nodeRelease(RTREE_OF_CURSOR(p), p->aNode[i]); + p->aNode[i] = 0; + }else{ + RtreeNode *pTemp = p->aNode[i]; + p->aNode[i] = p->aNode[j]; + p->aNode[j] = pTemp; + } + } +} + +/* +** Return the search point with the lowest current score. +*/ +static RtreeSearchPoint *rtreeSearchPointFirst(RtreeCursor *pCur){ + return pCur->bPoint ? &pCur->sPoint : pCur->nPoint ? pCur->aPoint : 0; +} + +/* +** Get the RtreeNode for the search point with the lowest score. +*/ +static RtreeNode *rtreeNodeOfFirstSearchPoint(RtreeCursor *pCur, int *pRC){ + sqlite3_int64 id; + int ii = 1 - pCur->bPoint; + assert( ii==0 || ii==1 ); + assert( pCur->bPoint || pCur->nPoint ); + if( pCur->aNode[ii]==0 ){ + assert( pRC!=0 ); + id = ii ? pCur->aPoint[0].id : pCur->sPoint.id; + *pRC = nodeAcquire(RTREE_OF_CURSOR(pCur), id, 0, &pCur->aNode[ii]); + } + return pCur->aNode[ii]; +} + +/* +** Push a new element onto the priority queue +*/ +static RtreeSearchPoint *rtreeEnqueue( + RtreeCursor *pCur, /* The cursor */ + RtreeDValue rScore, /* Score for the new search point */ + u8 iLevel /* Level for the new search point */ +){ + int i, j; + RtreeSearchPoint *pNew; + if( pCur->nPoint>=pCur->nPointAlloc ){ + int nNew = pCur->nPointAlloc*2 + 8; + pNew = sqlite3_realloc64(pCur->aPoint, nNew*sizeof(pCur->aPoint[0])); + if( pNew==0 ) return 0; + pCur->aPoint = pNew; + pCur->nPointAlloc = nNew; + } + i = pCur->nPoint++; + pNew = pCur->aPoint + i; + pNew->rScore = rScore; + pNew->iLevel = iLevel; + assert( iLevel<=RTREE_MAX_DEPTH ); + while( i>0 ){ + RtreeSearchPoint *pParent; + j = (i-1)/2; + pParent = pCur->aPoint + j; + if( rtreeSearchPointCompare(pNew, pParent)>=0 ) break; + rtreeSearchPointSwap(pCur, j, i); + i = j; + pNew = pParent; + } + return pNew; +} + +/* +** Allocate a new RtreeSearchPoint and return a pointer to it. Return +** NULL if malloc fails. +*/ +static RtreeSearchPoint *rtreeSearchPointNew( + RtreeCursor *pCur, /* The cursor */ + RtreeDValue rScore, /* Score for the new search point */ + u8 iLevel /* Level for the new search point */ +){ + RtreeSearchPoint *pNew, *pFirst; + pFirst = rtreeSearchPointFirst(pCur); + pCur->anQueue[iLevel]++; + if( pFirst==0 + || pFirst->rScore>rScore + || (pFirst->rScore==rScore && pFirst->iLevel>iLevel) + ){ + if( pCur->bPoint ){ + int ii; + pNew = rtreeEnqueue(pCur, rScore, iLevel); + if( pNew==0 ) return 0; + ii = (int)(pNew - pCur->aPoint) + 1; + assert( ii==1 ); + if( ALWAYS(ii<RTREE_CACHE_SZ) ){ + assert( pCur->aNode[ii]==0 ); + pCur->aNode[ii] = pCur->aNode[0]; + }else{ + nodeRelease(RTREE_OF_CURSOR(pCur), pCur->aNode[0]); + } + pCur->aNode[0] = 0; + *pNew = pCur->sPoint; + } + pCur->sPoint.rScore = rScore; + pCur->sPoint.iLevel = iLevel; + pCur->bPoint = 1; + return &pCur->sPoint; + }else{ + return rtreeEnqueue(pCur, rScore, iLevel); + } +} + +#if 0 +/* Tracing routines for the RtreeSearchPoint queue */ +static void tracePoint(RtreeSearchPoint *p, int idx, RtreeCursor *pCur){ + if( idx<0 ){ printf(" s"); }else{ printf("%2d", idx); } + printf(" %d.%05lld.%02d %g %d", + p->iLevel, p->id, p->iCell, p->rScore, p->eWithin + ); + idx++; + if( idx<RTREE_CACHE_SZ ){ + printf(" %p\n", pCur->aNode[idx]); + }else{ + printf("\n"); + } +} +static void traceQueue(RtreeCursor *pCur, const char *zPrefix){ + int ii; + printf("=== %9s ", zPrefix); + if( pCur->bPoint ){ + tracePoint(&pCur->sPoint, -1, pCur); + } + for(ii=0; ii<pCur->nPoint; ii++){ + if( ii>0 || pCur->bPoint ) printf(" "); + tracePoint(&pCur->aPoint[ii], ii, pCur); + } +} +# define RTREE_QUEUE_TRACE(A,B) traceQueue(A,B) +#else +# define RTREE_QUEUE_TRACE(A,B) /* no-op */ +#endif + +/* Remove the search point with the lowest current score. +*/ +static void rtreeSearchPointPop(RtreeCursor *p){ + int i, j, k, n; + i = 1 - p->bPoint; + assert( i==0 || i==1 ); + if( p->aNode[i] ){ + nodeRelease(RTREE_OF_CURSOR(p), p->aNode[i]); + p->aNode[i] = 0; + } + if( p->bPoint ){ + p->anQueue[p->sPoint.iLevel]--; + p->bPoint = 0; + }else if( ALWAYS(p->nPoint) ){ + p->anQueue[p->aPoint[0].iLevel]--; + n = --p->nPoint; + p->aPoint[0] = p->aPoint[n]; + if( n<RTREE_CACHE_SZ-1 ){ + p->aNode[1] = p->aNode[n+1]; + p->aNode[n+1] = 0; + } + i = 0; + while( (j = i*2+1)<n ){ + k = j+1; + if( k<n && rtreeSearchPointCompare(&p->aPoint[k], &p->aPoint[j])<0 ){ + if( rtreeSearchPointCompare(&p->aPoint[k], &p->aPoint[i])<0 ){ + rtreeSearchPointSwap(p, i, k); + i = k; + }else{ + break; + } + }else{ + if( rtreeSearchPointCompare(&p->aPoint[j], &p->aPoint[i])<0 ){ + rtreeSearchPointSwap(p, i, j); + i = j; + }else{ + break; + } + } + } + } +} + + +/* +** Continue the search on cursor pCur until the front of the queue +** contains an entry suitable for returning as a result-set row, +** or until the RtreeSearchPoint queue is empty, indicating that the +** query has completed. +*/ +static int rtreeStepToLeaf(RtreeCursor *pCur){ + RtreeSearchPoint *p; + Rtree *pRtree = RTREE_OF_CURSOR(pCur); + RtreeNode *pNode; + int eWithin; + int rc = SQLITE_OK; + int nCell; + int nConstraint = pCur->nConstraint; + int ii; + int eInt; + RtreeSearchPoint x; + + eInt = pRtree->eCoordType==RTREE_COORD_INT32; + while( (p = rtreeSearchPointFirst(pCur))!=0 && p->iLevel>0 ){ + u8 *pCellData; + pNode = rtreeNodeOfFirstSearchPoint(pCur, &rc); + if( rc ) return rc; + nCell = NCELL(pNode); + assert( nCell<200 ); + pCellData = pNode->zData + (4+pRtree->nBytesPerCell*p->iCell); + while( p->iCell<nCell ){ + sqlite3_rtree_dbl rScore = (sqlite3_rtree_dbl)-1; + eWithin = FULLY_WITHIN; + for(ii=0; ii<nConstraint; ii++){ + RtreeConstraint *pConstraint = pCur->aConstraint + ii; + if( pConstraint->op>=RTREE_MATCH ){ + rc = rtreeCallbackConstraint(pConstraint, eInt, pCellData, p, + &rScore, &eWithin); + if( rc ) return rc; + }else if( p->iLevel==1 ){ + rtreeLeafConstraint(pConstraint, eInt, pCellData, &eWithin); + }else{ + rtreeNonleafConstraint(pConstraint, eInt, pCellData, &eWithin); + } + if( eWithin==NOT_WITHIN ){ + p->iCell++; + pCellData += pRtree->nBytesPerCell; + break; + } + } + if( eWithin==NOT_WITHIN ) continue; + p->iCell++; + x.iLevel = p->iLevel - 1; + if( x.iLevel ){ + x.id = readInt64(pCellData); + for(ii=0; ii<pCur->nPoint; ii++){ + if( pCur->aPoint[ii].id==x.id ){ + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; + } + } + x.iCell = 0; + }else{ + x.id = p->id; + x.iCell = p->iCell - 1; + } + if( p->iCell>=nCell ){ + RTREE_QUEUE_TRACE(pCur, "POP-S:"); + rtreeSearchPointPop(pCur); + } + if( rScore<RTREE_ZERO ) rScore = RTREE_ZERO; + p = rtreeSearchPointNew(pCur, rScore, x.iLevel); + if( p==0 ) return SQLITE_NOMEM; + p->eWithin = (u8)eWithin; + p->id = x.id; + p->iCell = x.iCell; + RTREE_QUEUE_TRACE(pCur, "PUSH-S:"); + break; + } + if( p->iCell>=nCell ){ + RTREE_QUEUE_TRACE(pCur, "POP-Se:"); + rtreeSearchPointPop(pCur); + } + } + pCur->atEOF = p==0; + return SQLITE_OK; +} + +/* +** Rtree virtual table module xNext method. +*/ +static int rtreeNext(sqlite3_vtab_cursor *pVtabCursor){ + RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor; + int rc = SQLITE_OK; + + /* Move to the next entry that matches the configured constraints. */ + RTREE_QUEUE_TRACE(pCsr, "POP-Nx:"); + if( pCsr->bAuxValid ){ + pCsr->bAuxValid = 0; + sqlite3_reset(pCsr->pReadAux); + } + rtreeSearchPointPop(pCsr); + rc = rtreeStepToLeaf(pCsr); + return rc; +} + +/* +** Rtree virtual table module xRowid method. +*/ +static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){ + RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor; + RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr); + int rc = SQLITE_OK; + RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); + if( rc==SQLITE_OK && ALWAYS(p) ){ + *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); + } + return rc; +} + +/* +** Rtree virtual table module xColumn method. +*/ +static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ + Rtree *pRtree = (Rtree *)cur->pVtab; + RtreeCursor *pCsr = (RtreeCursor *)cur; + RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr); + RtreeCoord c; + int rc = SQLITE_OK; + RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); + + if( rc ) return rc; + if( NEVER(p==0) ) return SQLITE_OK; + if( i==0 ){ + sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell)); + }else if( i<=pRtree->nDim2 ){ + nodeGetCoord(pRtree, pNode, p->iCell, i-1, &c); +#ifndef SQLITE_RTREE_INT_ONLY + if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ + sqlite3_result_double(ctx, c.f); + }else +#endif + { + assert( pRtree->eCoordType==RTREE_COORD_INT32 ); + sqlite3_result_int(ctx, c.i); + } + }else{ + if( !pCsr->bAuxValid ){ + if( pCsr->pReadAux==0 ){ + rc = sqlite3_prepare_v3(pRtree->db, pRtree->zReadAuxSql, -1, 0, + &pCsr->pReadAux, 0); + if( rc ) return rc; + } + sqlite3_bind_int64(pCsr->pReadAux, 1, + nodeGetRowid(pRtree, pNode, p->iCell)); + rc = sqlite3_step(pCsr->pReadAux); + if( rc==SQLITE_ROW ){ + pCsr->bAuxValid = 1; + }else{ + sqlite3_reset(pCsr->pReadAux); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + return rc; + } + } + sqlite3_result_value(ctx, + sqlite3_column_value(pCsr->pReadAux, i - pRtree->nDim2 + 1)); + } + return SQLITE_OK; +} + +/* +** Use nodeAcquire() to obtain the leaf node containing the record with +** rowid iRowid. If successful, set *ppLeaf to point to the node and +** return SQLITE_OK. If there is no such record in the table, set +** *ppLeaf to 0 and return SQLITE_OK. If an error occurs, set *ppLeaf +** to zero and return an SQLite error code. +*/ +static int findLeafNode( + Rtree *pRtree, /* RTree to search */ + i64 iRowid, /* The rowid searching for */ + RtreeNode **ppLeaf, /* Write the node here */ + sqlite3_int64 *piNode /* Write the node-id here */ +){ + int rc; + *ppLeaf = 0; + sqlite3_bind_int64(pRtree->pReadRowid, 1, iRowid); + if( sqlite3_step(pRtree->pReadRowid)==SQLITE_ROW ){ + i64 iNode = sqlite3_column_int64(pRtree->pReadRowid, 0); + if( piNode ) *piNode = iNode; + rc = nodeAcquire(pRtree, iNode, 0, ppLeaf); + sqlite3_reset(pRtree->pReadRowid); + }else{ + rc = sqlite3_reset(pRtree->pReadRowid); + } + return rc; +} + +/* +** This function is called to configure the RtreeConstraint object passed +** as the second argument for a MATCH constraint. The value passed as the +** first argument to this function is the right-hand operand to the MATCH +** operator. +*/ +static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){ + RtreeMatchArg *pBlob, *pSrc; /* BLOB returned by geometry function */ + sqlite3_rtree_query_info *pInfo; /* Callback information */ + + pSrc = sqlite3_value_pointer(pValue, "RtreeMatchArg"); + if( pSrc==0 ) return SQLITE_ERROR; + pInfo = (sqlite3_rtree_query_info*) + sqlite3_malloc64( sizeof(*pInfo)+pSrc->iSize ); + if( !pInfo ) return SQLITE_NOMEM; + memset(pInfo, 0, sizeof(*pInfo)); + pBlob = (RtreeMatchArg*)&pInfo[1]; + memcpy(pBlob, pSrc, pSrc->iSize); + pInfo->pContext = pBlob->cb.pContext; + pInfo->nParam = pBlob->nParam; + pInfo->aParam = pBlob->aParam; + pInfo->apSqlParam = pBlob->apSqlParam; + + if( pBlob->cb.xGeom ){ + pCons->u.xGeom = pBlob->cb.xGeom; + }else{ + pCons->op = RTREE_QUERY; + pCons->u.xQueryFunc = pBlob->cb.xQueryFunc; + } + pCons->pInfo = pInfo; + return SQLITE_OK; +} + +/* +** Rtree virtual table module xFilter method. +*/ +static int rtreeFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + Rtree *pRtree = (Rtree *)pVtabCursor->pVtab; + RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor; + RtreeNode *pRoot = 0; + int ii; + int rc = SQLITE_OK; + int iCell = 0; + + rtreeReference(pRtree); + + /* Reset the cursor to the same state as rtreeOpen() leaves it in. */ + resetCursor(pCsr); + + pCsr->iStrategy = idxNum; + if( idxNum==1 ){ + /* Special case - lookup by rowid. */ + RtreeNode *pLeaf; /* Leaf on which the required cell resides */ + RtreeSearchPoint *p; /* Search point for the leaf */ + i64 iRowid = sqlite3_value_int64(argv[0]); + i64 iNode = 0; + int eType = sqlite3_value_numeric_type(argv[0]); + if( eType==SQLITE_INTEGER + || (eType==SQLITE_FLOAT && sqlite3_value_double(argv[0])==iRowid) + ){ + rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); + }else{ + rc = SQLITE_OK; + pLeaf = 0; + } + if( rc==SQLITE_OK && pLeaf!=0 ){ + p = rtreeSearchPointNew(pCsr, RTREE_ZERO, 0); + assert( p!=0 ); /* Always returns pCsr->sPoint */ + pCsr->aNode[0] = pLeaf; + p->id = iNode; + p->eWithin = PARTLY_WITHIN; + rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &iCell); + p->iCell = (u8)iCell; + RTREE_QUEUE_TRACE(pCsr, "PUSH-F1:"); + }else{ + pCsr->atEOF = 1; + } + }else{ + /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array + ** with the configured constraints. + */ + rc = nodeAcquire(pRtree, 1, 0, &pRoot); + if( rc==SQLITE_OK && argc>0 ){ + pCsr->aConstraint = sqlite3_malloc64(sizeof(RtreeConstraint)*argc); + pCsr->nConstraint = argc; + if( !pCsr->aConstraint ){ + rc = SQLITE_NOMEM; + }else{ + memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*argc); + memset(pCsr->anQueue, 0, sizeof(u32)*(pRtree->iDepth + 1)); + assert( (idxStr==0 && argc==0) + || (idxStr && (int)strlen(idxStr)==argc*2) ); + for(ii=0; ii<argc; ii++){ + RtreeConstraint *p = &pCsr->aConstraint[ii]; + int eType = sqlite3_value_numeric_type(argv[ii]); + p->op = idxStr[ii*2]; + p->iCoord = idxStr[ii*2+1]-'0'; + if( p->op>=RTREE_MATCH ){ + /* A MATCH operator. The right-hand-side must be a blob that + ** can be cast into an RtreeMatchArg object. One created using + ** an sqlite3_rtree_geometry_callback() SQL user function. + */ + rc = deserializeGeometry(argv[ii], p); + if( rc!=SQLITE_OK ){ + break; + } + p->pInfo->nCoord = pRtree->nDim2; + p->pInfo->anQueue = pCsr->anQueue; + p->pInfo->mxLevel = pRtree->iDepth + 1; + }else if( eType==SQLITE_INTEGER ){ + sqlite3_int64 iVal = sqlite3_value_int64(argv[ii]); +#ifdef SQLITE_RTREE_INT_ONLY + p->u.rValue = iVal; +#else + p->u.rValue = (double)iVal; + if( iVal>=((sqlite3_int64)1)<<48 + || iVal<=-(((sqlite3_int64)1)<<48) + ){ + if( p->op==RTREE_LT ) p->op = RTREE_LE; + if( p->op==RTREE_GT ) p->op = RTREE_GE; + } +#endif + }else if( eType==SQLITE_FLOAT ){ +#ifdef SQLITE_RTREE_INT_ONLY + p->u.rValue = sqlite3_value_int64(argv[ii]); +#else + p->u.rValue = sqlite3_value_double(argv[ii]); +#endif + }else{ + p->u.rValue = RTREE_ZERO; + if( eType==SQLITE_NULL ){ + p->op = RTREE_FALSE; + }else if( p->op==RTREE_LT || p->op==RTREE_LE ){ + p->op = RTREE_TRUE; + }else{ + p->op = RTREE_FALSE; + } + } + } + } + } + if( rc==SQLITE_OK ){ + RtreeSearchPoint *pNew; + assert( pCsr->bPoint==0 ); /* Due to the resetCursor() call above */ + pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1)); + if( NEVER(pNew==0) ){ /* Because pCsr->bPoint was FALSE */ + return SQLITE_NOMEM; + } + pNew->id = 1; + pNew->iCell = 0; + pNew->eWithin = PARTLY_WITHIN; + assert( pCsr->bPoint==1 ); + pCsr->aNode[0] = pRoot; + pRoot = 0; + RTREE_QUEUE_TRACE(pCsr, "PUSH-Fm:"); + rc = rtreeStepToLeaf(pCsr); + } + } + + nodeRelease(pRtree, pRoot); + rtreeRelease(pRtree); + return rc; +} + +/* +** Rtree virtual table module xBestIndex method. There are three +** table scan strategies to choose from (in order from most to +** least desirable): +** +** idxNum idxStr Strategy +** ------------------------------------------------ +** 1 Unused Direct lookup by rowid. +** 2 See below R-tree query or full-table scan. +** ------------------------------------------------ +** +** If strategy 1 is used, then idxStr is not meaningful. If strategy +** 2 is used, idxStr is formatted to contain 2 bytes for each +** constraint used. The first two bytes of idxStr correspond to +** the constraint in sqlite3_index_info.aConstraintUsage[] with +** (argvIndex==1) etc. +** +** The first of each pair of bytes in idxStr identifies the constraint +** operator as follows: +** +** Operator Byte Value +** ---------------------- +** = 0x41 ('A') +** <= 0x42 ('B') +** < 0x43 ('C') +** >= 0x44 ('D') +** > 0x45 ('E') +** MATCH 0x46 ('F') +** ---------------------- +** +** The second of each pair of bytes identifies the coordinate column +** to which the constraint applies. The leftmost coordinate column +** is 'a', the second from the left 'b' etc. +*/ +static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + Rtree *pRtree = (Rtree*)tab; + int rc = SQLITE_OK; + int ii; + int bMatch = 0; /* True if there exists a MATCH constraint */ + i64 nRow; /* Estimated rows returned by this scan */ + + int iIdx = 0; + char zIdxStr[RTREE_MAX_DIMENSIONS*8+1]; + memset(zIdxStr, 0, sizeof(zIdxStr)); + + /* Check if there exists a MATCH constraint - even an unusable one. If there + ** is, do not consider the lookup-by-rowid plan as using such a plan would + ** require the VDBE to evaluate the MATCH constraint, which is not currently + ** possible. */ + for(ii=0; ii<pIdxInfo->nConstraint; ii++){ + if( pIdxInfo->aConstraint[ii].op==SQLITE_INDEX_CONSTRAINT_MATCH ){ + bMatch = 1; + } + } + + assert( pIdxInfo->idxStr==0 ); + for(ii=0; ii<pIdxInfo->nConstraint && iIdx<(int)(sizeof(zIdxStr)-1); ii++){ + struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; + + if( bMatch==0 && p->usable + && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ + ){ + /* We have an equality constraint on the rowid. Use strategy 1. */ + int jj; + for(jj=0; jj<ii; jj++){ + pIdxInfo->aConstraintUsage[jj].argvIndex = 0; + pIdxInfo->aConstraintUsage[jj].omit = 0; + } + pIdxInfo->idxNum = 1; + pIdxInfo->aConstraintUsage[ii].argvIndex = 1; + pIdxInfo->aConstraintUsage[jj].omit = 1; + + /* This strategy involves a two rowid lookups on an B-Tree structures + ** and then a linear search of an R-Tree node. This should be + ** considered almost as quick as a direct rowid lookup (for which + ** sqlite uses an internal cost of 0.0). It is expected to return + ** a single row. + */ + pIdxInfo->estimatedCost = 30.0; + pIdxInfo->estimatedRows = 1; + pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE; + return SQLITE_OK; + } + + if( p->usable + && ((p->iColumn>0 && p->iColumn<=pRtree->nDim2) + || p->op==SQLITE_INDEX_CONSTRAINT_MATCH) + ){ + u8 op; + u8 doOmit = 1; + switch( p->op ){ + case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; doOmit = 0; break; + case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; doOmit = 0; break; + case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break; + case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; doOmit = 0; break; + case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break; + case SQLITE_INDEX_CONSTRAINT_MATCH: op = RTREE_MATCH; break; + default: op = 0; break; + } + if( op ){ + zIdxStr[iIdx++] = op; + zIdxStr[iIdx++] = (char)(p->iColumn - 1 + '0'); + pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2); + pIdxInfo->aConstraintUsage[ii].omit = doOmit; + } + } + } + + pIdxInfo->idxNum = 2; + pIdxInfo->needToFreeIdxStr = 1; + if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){ + return SQLITE_NOMEM; + } + + nRow = pRtree->nRowEst >> (iIdx/2); + pIdxInfo->estimatedCost = (double)6.0 * (double)nRow; + pIdxInfo->estimatedRows = nRow; + + return rc; +} + +/* +** Return the N-dimensional volumn of the cell stored in *p. +*/ +static RtreeDValue cellArea(Rtree *pRtree, RtreeCell *p){ + RtreeDValue area = (RtreeDValue)1; + assert( pRtree->nDim>=1 && pRtree->nDim<=5 ); +#ifndef SQLITE_RTREE_INT_ONLY + if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ + switch( pRtree->nDim ){ + case 5: area = p->aCoord[9].f - p->aCoord[8].f; + case 4: area *= p->aCoord[7].f - p->aCoord[6].f; + case 3: area *= p->aCoord[5].f - p->aCoord[4].f; + case 2: area *= p->aCoord[3].f - p->aCoord[2].f; + default: area *= p->aCoord[1].f - p->aCoord[0].f; + } + }else +#endif + { + switch( pRtree->nDim ){ + case 5: area = (i64)p->aCoord[9].i - (i64)p->aCoord[8].i; + case 4: area *= (i64)p->aCoord[7].i - (i64)p->aCoord[6].i; + case 3: area *= (i64)p->aCoord[5].i - (i64)p->aCoord[4].i; + case 2: area *= (i64)p->aCoord[3].i - (i64)p->aCoord[2].i; + default: area *= (i64)p->aCoord[1].i - (i64)p->aCoord[0].i; + } + } + return area; +} + +/* +** Return the margin length of cell p. The margin length is the sum +** of the objects size in each dimension. +*/ +static RtreeDValue cellMargin(Rtree *pRtree, RtreeCell *p){ + RtreeDValue margin = 0; + int ii = pRtree->nDim2 - 2; + do{ + margin += (DCOORD(p->aCoord[ii+1]) - DCOORD(p->aCoord[ii])); + ii -= 2; + }while( ii>=0 ); + return margin; +} + +/* +** Store the union of cells p1 and p2 in p1. +*/ +static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){ + int ii = 0; + if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ + do{ + p1->aCoord[ii].f = MIN(p1->aCoord[ii].f, p2->aCoord[ii].f); + p1->aCoord[ii+1].f = MAX(p1->aCoord[ii+1].f, p2->aCoord[ii+1].f); + ii += 2; + }while( ii<pRtree->nDim2 ); + }else{ + do{ + p1->aCoord[ii].i = MIN(p1->aCoord[ii].i, p2->aCoord[ii].i); + p1->aCoord[ii+1].i = MAX(p1->aCoord[ii+1].i, p2->aCoord[ii+1].i); + ii += 2; + }while( ii<pRtree->nDim2 ); + } +} + +/* +** Return true if the area covered by p2 is a subset of the area covered +** by p1. False otherwise. +*/ +static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){ + int ii; + int isInt = (pRtree->eCoordType==RTREE_COORD_INT32); + for(ii=0; ii<pRtree->nDim2; ii+=2){ + RtreeCoord *a1 = &p1->aCoord[ii]; + RtreeCoord *a2 = &p2->aCoord[ii]; + if( (!isInt && (a2[0].f<a1[0].f || a2[1].f>a1[1].f)) + || ( isInt && (a2[0].i<a1[0].i || a2[1].i>a1[1].i)) + ){ + return 0; + } + } + return 1; +} + +/* +** Return the amount cell p would grow by if it were unioned with pCell. +*/ +static RtreeDValue cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){ + RtreeDValue area; + RtreeCell cell; + memcpy(&cell, p, sizeof(RtreeCell)); + area = cellArea(pRtree, &cell); + cellUnion(pRtree, &cell, pCell); + return (cellArea(pRtree, &cell)-area); +} + +static RtreeDValue cellOverlap( + Rtree *pRtree, + RtreeCell *p, + RtreeCell *aCell, + int nCell +){ + int ii; + RtreeDValue overlap = RTREE_ZERO; + for(ii=0; ii<nCell; ii++){ + int jj; + RtreeDValue o = (RtreeDValue)1; + for(jj=0; jj<pRtree->nDim2; jj+=2){ + RtreeDValue x1, x2; + x1 = MAX(DCOORD(p->aCoord[jj]), DCOORD(aCell[ii].aCoord[jj])); + x2 = MIN(DCOORD(p->aCoord[jj+1]), DCOORD(aCell[ii].aCoord[jj+1])); + if( x2<x1 ){ + o = (RtreeDValue)0; + break; + }else{ + o = o * (x2-x1); + } + } + overlap += o; + } + return overlap; +} + + +/* +** This function implements the ChooseLeaf algorithm from Gutman[84]. +** ChooseSubTree in r*tree terminology. +*/ +static int ChooseLeaf( + Rtree *pRtree, /* Rtree table */ + RtreeCell *pCell, /* Cell to insert into rtree */ + int iHeight, /* Height of sub-tree rooted at pCell */ + RtreeNode **ppLeaf /* OUT: Selected leaf page */ +){ + int rc; + int ii; + RtreeNode *pNode = 0; + rc = nodeAcquire(pRtree, 1, 0, &pNode); + + for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){ + int iCell; + sqlite3_int64 iBest = 0; + + RtreeDValue fMinGrowth = RTREE_ZERO; + RtreeDValue fMinArea = RTREE_ZERO; + + int nCell = NCELL(pNode); + RtreeCell cell; + RtreeNode *pChild = 0; + + RtreeCell *aCell = 0; + + /* Select the child node which will be enlarged the least if pCell + ** is inserted into it. Resolve ties by choosing the entry with + ** the smallest area. + */ + for(iCell=0; iCell<nCell; iCell++){ + int bBest = 0; + RtreeDValue growth; + RtreeDValue area; + nodeGetCell(pRtree, pNode, iCell, &cell); + growth = cellGrowth(pRtree, &cell, pCell); + area = cellArea(pRtree, &cell); + if( iCell==0||growth<fMinGrowth||(growth==fMinGrowth && area<fMinArea) ){ + bBest = 1; + } + if( bBest ){ + fMinGrowth = growth; + fMinArea = area; + iBest = cell.iRowid; + } + } + + sqlite3_free(aCell); + rc = nodeAcquire(pRtree, iBest, pNode, &pChild); + nodeRelease(pRtree, pNode); + pNode = pChild; + } + + *ppLeaf = pNode; + return rc; +} + +/* +** A cell with the same content as pCell has just been inserted into +** the node pNode. This function updates the bounding box cells in +** all ancestor elements. +*/ +static int AdjustTree( + Rtree *pRtree, /* Rtree table */ + RtreeNode *pNode, /* Adjust ancestry of this node. */ + RtreeCell *pCell /* This cell was just inserted */ +){ + RtreeNode *p = pNode; + int cnt = 0; + int rc; + while( p->pParent ){ + RtreeNode *pParent = p->pParent; + RtreeCell cell; + int iCell; + + cnt++; + if( NEVER(cnt>100) ){ + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; + } + rc = nodeParentIndex(pRtree, p, &iCell); + if( NEVER(rc!=SQLITE_OK) ){ + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; + } + + nodeGetCell(pRtree, pParent, iCell, &cell); + if( !cellContains(pRtree, &cell, pCell) ){ + cellUnion(pRtree, &cell, pCell); + nodeOverwriteCell(pRtree, pParent, &cell, iCell); + } + + p = pParent; + } + return SQLITE_OK; +} + +/* +** Write mapping (iRowid->iNode) to the <rtree>_rowid table. +*/ +static int rowidWrite(Rtree *pRtree, sqlite3_int64 iRowid, sqlite3_int64 iNode){ + sqlite3_bind_int64(pRtree->pWriteRowid, 1, iRowid); + sqlite3_bind_int64(pRtree->pWriteRowid, 2, iNode); + sqlite3_step(pRtree->pWriteRowid); + return sqlite3_reset(pRtree->pWriteRowid); +} + +/* +** Write mapping (iNode->iPar) to the <rtree>_parent table. +*/ +static int parentWrite(Rtree *pRtree, sqlite3_int64 iNode, sqlite3_int64 iPar){ + sqlite3_bind_int64(pRtree->pWriteParent, 1, iNode); + sqlite3_bind_int64(pRtree->pWriteParent, 2, iPar); + sqlite3_step(pRtree->pWriteParent); + return sqlite3_reset(pRtree->pWriteParent); +} + +static int rtreeInsertCell(Rtree *, RtreeNode *, RtreeCell *, int); + + +/* +** Arguments aIdx, aDistance and aSpare all point to arrays of size +** nIdx. The aIdx array contains the set of integers from 0 to +** (nIdx-1) in no particular order. This function sorts the values +** in aIdx according to the indexed values in aDistance. For +** example, assuming the inputs: +** +** aIdx = { 0, 1, 2, 3 } +** aDistance = { 5.0, 2.0, 7.0, 6.0 } +** +** this function sets the aIdx array to contain: +** +** aIdx = { 0, 1, 2, 3 } +** +** The aSpare array is used as temporary working space by the +** sorting algorithm. +*/ +static void SortByDistance( + int *aIdx, + int nIdx, + RtreeDValue *aDistance, + int *aSpare +){ + if( nIdx>1 ){ + int iLeft = 0; + int iRight = 0; + + int nLeft = nIdx/2; + int nRight = nIdx-nLeft; + int *aLeft = aIdx; + int *aRight = &aIdx[nLeft]; + + SortByDistance(aLeft, nLeft, aDistance, aSpare); + SortByDistance(aRight, nRight, aDistance, aSpare); + + memcpy(aSpare, aLeft, sizeof(int)*nLeft); + aLeft = aSpare; + + while( iLeft<nLeft || iRight<nRight ){ + if( iLeft==nLeft ){ + aIdx[iLeft+iRight] = aRight[iRight]; + iRight++; + }else if( iRight==nRight ){ + aIdx[iLeft+iRight] = aLeft[iLeft]; + iLeft++; + }else{ + RtreeDValue fLeft = aDistance[aLeft[iLeft]]; + RtreeDValue fRight = aDistance[aRight[iRight]]; + if( fLeft<fRight ){ + aIdx[iLeft+iRight] = aLeft[iLeft]; + iLeft++; + }else{ + aIdx[iLeft+iRight] = aRight[iRight]; + iRight++; + } + } + } + +#if 0 + /* Check that the sort worked */ + { + int jj; + for(jj=1; jj<nIdx; jj++){ + RtreeDValue left = aDistance[aIdx[jj-1]]; + RtreeDValue right = aDistance[aIdx[jj]]; + assert( left<=right ); + } + } +#endif + } +} + +/* +** Arguments aIdx, aCell and aSpare all point to arrays of size +** nIdx. The aIdx array contains the set of integers from 0 to +** (nIdx-1) in no particular order. This function sorts the values +** in aIdx according to dimension iDim of the cells in aCell. The +** minimum value of dimension iDim is considered first, the +** maximum used to break ties. +** +** The aSpare array is used as temporary working space by the +** sorting algorithm. +*/ +static void SortByDimension( + Rtree *pRtree, + int *aIdx, + int nIdx, + int iDim, + RtreeCell *aCell, + int *aSpare +){ + if( nIdx>1 ){ + + int iLeft = 0; + int iRight = 0; + + int nLeft = nIdx/2; + int nRight = nIdx-nLeft; + int *aLeft = aIdx; + int *aRight = &aIdx[nLeft]; + + SortByDimension(pRtree, aLeft, nLeft, iDim, aCell, aSpare); + SortByDimension(pRtree, aRight, nRight, iDim, aCell, aSpare); + + memcpy(aSpare, aLeft, sizeof(int)*nLeft); + aLeft = aSpare; + while( iLeft<nLeft || iRight<nRight ){ + RtreeDValue xleft1 = DCOORD(aCell[aLeft[iLeft]].aCoord[iDim*2]); + RtreeDValue xleft2 = DCOORD(aCell[aLeft[iLeft]].aCoord[iDim*2+1]); + RtreeDValue xright1 = DCOORD(aCell[aRight[iRight]].aCoord[iDim*2]); + RtreeDValue xright2 = DCOORD(aCell[aRight[iRight]].aCoord[iDim*2+1]); + if( (iLeft!=nLeft) && ((iRight==nRight) + || (xleft1<xright1) + || (xleft1==xright1 && xleft2<xright2) + )){ + aIdx[iLeft+iRight] = aLeft[iLeft]; + iLeft++; + }else{ + aIdx[iLeft+iRight] = aRight[iRight]; + iRight++; + } + } + +#if 0 + /* Check that the sort worked */ + { + int jj; + for(jj=1; jj<nIdx; jj++){ + RtreeDValue xleft1 = aCell[aIdx[jj-1]].aCoord[iDim*2]; + RtreeDValue xleft2 = aCell[aIdx[jj-1]].aCoord[iDim*2+1]; + RtreeDValue xright1 = aCell[aIdx[jj]].aCoord[iDim*2]; + RtreeDValue xright2 = aCell[aIdx[jj]].aCoord[iDim*2+1]; + assert( xleft1<=xright1 && (xleft1<xright1 || xleft2<=xright2) ); + } + } +#endif + } +} + +/* +** Implementation of the R*-tree variant of SplitNode from Beckman[1990]. +*/ +static int splitNodeStartree( + Rtree *pRtree, + RtreeCell *aCell, + int nCell, + RtreeNode *pLeft, + RtreeNode *pRight, + RtreeCell *pBboxLeft, + RtreeCell *pBboxRight +){ + int **aaSorted; + int *aSpare; + int ii; + + int iBestDim = 0; + int iBestSplit = 0; + RtreeDValue fBestMargin = RTREE_ZERO; + + sqlite3_int64 nByte = (pRtree->nDim+1)*(sizeof(int*)+nCell*sizeof(int)); + + aaSorted = (int **)sqlite3_malloc64(nByte); + if( !aaSorted ){ + return SQLITE_NOMEM; + } + + aSpare = &((int *)&aaSorted[pRtree->nDim])[pRtree->nDim*nCell]; + memset(aaSorted, 0, nByte); + for(ii=0; ii<pRtree->nDim; ii++){ + int jj; + aaSorted[ii] = &((int *)&aaSorted[pRtree->nDim])[ii*nCell]; + for(jj=0; jj<nCell; jj++){ + aaSorted[ii][jj] = jj; + } + SortByDimension(pRtree, aaSorted[ii], nCell, ii, aCell, aSpare); + } + + for(ii=0; ii<pRtree->nDim; ii++){ + RtreeDValue margin = RTREE_ZERO; + RtreeDValue fBestOverlap = RTREE_ZERO; + RtreeDValue fBestArea = RTREE_ZERO; + int iBestLeft = 0; + int nLeft; + + for( + nLeft=RTREE_MINCELLS(pRtree); + nLeft<=(nCell-RTREE_MINCELLS(pRtree)); + nLeft++ + ){ + RtreeCell left; + RtreeCell right; + int kk; + RtreeDValue overlap; + RtreeDValue area; + + memcpy(&left, &aCell[aaSorted[ii][0]], sizeof(RtreeCell)); + memcpy(&right, &aCell[aaSorted[ii][nCell-1]], sizeof(RtreeCell)); + for(kk=1; kk<(nCell-1); kk++){ + if( kk<nLeft ){ + cellUnion(pRtree, &left, &aCell[aaSorted[ii][kk]]); + }else{ + cellUnion(pRtree, &right, &aCell[aaSorted[ii][kk]]); + } + } + margin += cellMargin(pRtree, &left); + margin += cellMargin(pRtree, &right); + overlap = cellOverlap(pRtree, &left, &right, 1); + area = cellArea(pRtree, &left) + cellArea(pRtree, &right); + if( (nLeft==RTREE_MINCELLS(pRtree)) + || (overlap<fBestOverlap) + || (overlap==fBestOverlap && area<fBestArea) + ){ + iBestLeft = nLeft; + fBestOverlap = overlap; + fBestArea = area; + } + } + + if( ii==0 || margin<fBestMargin ){ + iBestDim = ii; + fBestMargin = margin; + iBestSplit = iBestLeft; + } + } + + memcpy(pBboxLeft, &aCell[aaSorted[iBestDim][0]], sizeof(RtreeCell)); + memcpy(pBboxRight, &aCell[aaSorted[iBestDim][iBestSplit]], sizeof(RtreeCell)); + for(ii=0; ii<nCell; ii++){ + RtreeNode *pTarget = (ii<iBestSplit)?pLeft:pRight; + RtreeCell *pBbox = (ii<iBestSplit)?pBboxLeft:pBboxRight; + RtreeCell *pCell = &aCell[aaSorted[iBestDim][ii]]; + nodeInsertCell(pRtree, pTarget, pCell); + cellUnion(pRtree, pBbox, pCell); + } + + sqlite3_free(aaSorted); + return SQLITE_OK; +} + + +static int updateMapping( + Rtree *pRtree, + i64 iRowid, + RtreeNode *pNode, + int iHeight +){ + int (*xSetMapping)(Rtree *, sqlite3_int64, sqlite3_int64); + xSetMapping = ((iHeight==0)?rowidWrite:parentWrite); + if( iHeight>0 ){ + RtreeNode *pChild = nodeHashLookup(pRtree, iRowid); + RtreeNode *p; + for(p=pNode; p; p=p->pParent){ + if( p==pChild ) return SQLITE_CORRUPT_VTAB; + } + if( pChild ){ + nodeRelease(pRtree, pChild->pParent); + nodeReference(pNode); + pChild->pParent = pNode; + } + } + if( NEVER(pNode==0) ) return SQLITE_ERROR; + return xSetMapping(pRtree, iRowid, pNode->iNode); +} + +static int SplitNode( + Rtree *pRtree, + RtreeNode *pNode, + RtreeCell *pCell, + int iHeight +){ + int i; + int newCellIsRight = 0; + + int rc = SQLITE_OK; + int nCell = NCELL(pNode); + RtreeCell *aCell; + int *aiUsed; + + RtreeNode *pLeft = 0; + RtreeNode *pRight = 0; + + RtreeCell leftbbox; + RtreeCell rightbbox; + + /* Allocate an array and populate it with a copy of pCell and + ** all cells from node pLeft. Then zero the original node. + */ + aCell = sqlite3_malloc64((sizeof(RtreeCell)+sizeof(int))*(nCell+1)); + if( !aCell ){ + rc = SQLITE_NOMEM; + goto splitnode_out; + } + aiUsed = (int *)&aCell[nCell+1]; + memset(aiUsed, 0, sizeof(int)*(nCell+1)); + for(i=0; i<nCell; i++){ + nodeGetCell(pRtree, pNode, i, &aCell[i]); + } + nodeZero(pRtree, pNode); + memcpy(&aCell[nCell], pCell, sizeof(RtreeCell)); + nCell++; + + if( pNode->iNode==1 ){ + pRight = nodeNew(pRtree, pNode); + pLeft = nodeNew(pRtree, pNode); + pRtree->iDepth++; + pNode->isDirty = 1; + writeInt16(pNode->zData, pRtree->iDepth); + }else{ + pLeft = pNode; + pRight = nodeNew(pRtree, pLeft->pParent); + pLeft->nRef++; + } + + if( !pLeft || !pRight ){ + rc = SQLITE_NOMEM; + goto splitnode_out; + } + + memset(pLeft->zData, 0, pRtree->iNodeSize); + memset(pRight->zData, 0, pRtree->iNodeSize); + + rc = splitNodeStartree(pRtree, aCell, nCell, pLeft, pRight, + &leftbbox, &rightbbox); + if( rc!=SQLITE_OK ){ + goto splitnode_out; + } + + /* Ensure both child nodes have node numbers assigned to them by calling + ** nodeWrite(). Node pRight always needs a node number, as it was created + ** by nodeNew() above. But node pLeft sometimes already has a node number. + ** In this case avoid the all to nodeWrite(). + */ + if( SQLITE_OK!=(rc = nodeWrite(pRtree, pRight)) + || (0==pLeft->iNode && SQLITE_OK!=(rc = nodeWrite(pRtree, pLeft))) + ){ + goto splitnode_out; + } + + rightbbox.iRowid = pRight->iNode; + leftbbox.iRowid = pLeft->iNode; + + if( pNode->iNode==1 ){ + rc = rtreeInsertCell(pRtree, pLeft->pParent, &leftbbox, iHeight+1); + if( rc!=SQLITE_OK ){ + goto splitnode_out; + } + }else{ + RtreeNode *pParent = pLeft->pParent; + int iCell; + rc = nodeParentIndex(pRtree, pLeft, &iCell); + if( ALWAYS(rc==SQLITE_OK) ){ + nodeOverwriteCell(pRtree, pParent, &leftbbox, iCell); + rc = AdjustTree(pRtree, pParent, &leftbbox); + assert( rc==SQLITE_OK ); + } + if( NEVER(rc!=SQLITE_OK) ){ + goto splitnode_out; + } + } + if( (rc = rtreeInsertCell(pRtree, pRight->pParent, &rightbbox, iHeight+1)) ){ + goto splitnode_out; + } + + for(i=0; i<NCELL(pRight); i++){ + i64 iRowid = nodeGetRowid(pRtree, pRight, i); + rc = updateMapping(pRtree, iRowid, pRight, iHeight); + if( iRowid==pCell->iRowid ){ + newCellIsRight = 1; + } + if( rc!=SQLITE_OK ){ + goto splitnode_out; + } + } + if( pNode->iNode==1 ){ + for(i=0; i<NCELL(pLeft); i++){ + i64 iRowid = nodeGetRowid(pRtree, pLeft, i); + rc = updateMapping(pRtree, iRowid, pLeft, iHeight); + if( rc!=SQLITE_OK ){ + goto splitnode_out; + } + } + }else if( newCellIsRight==0 ){ + rc = updateMapping(pRtree, pCell->iRowid, pLeft, iHeight); + } + + if( rc==SQLITE_OK ){ + rc = nodeRelease(pRtree, pRight); + pRight = 0; + } + if( rc==SQLITE_OK ){ + rc = nodeRelease(pRtree, pLeft); + pLeft = 0; + } + +splitnode_out: + nodeRelease(pRtree, pRight); + nodeRelease(pRtree, pLeft); + sqlite3_free(aCell); + return rc; +} + +/* +** If node pLeaf is not the root of the r-tree and its pParent pointer is +** still NULL, load all ancestor nodes of pLeaf into memory and populate +** the pLeaf->pParent chain all the way up to the root node. +** +** This operation is required when a row is deleted (or updated - an update +** is implemented as a delete followed by an insert). SQLite provides the +** rowid of the row to delete, which can be used to find the leaf on which +** the entry resides (argument pLeaf). Once the leaf is located, this +** function is called to determine its ancestry. +*/ +static int fixLeafParent(Rtree *pRtree, RtreeNode *pLeaf){ + int rc = SQLITE_OK; + RtreeNode *pChild = pLeaf; + while( rc==SQLITE_OK && pChild->iNode!=1 && pChild->pParent==0 ){ + int rc2 = SQLITE_OK; /* sqlite3_reset() return code */ + sqlite3_bind_int64(pRtree->pReadParent, 1, pChild->iNode); + rc = sqlite3_step(pRtree->pReadParent); + if( rc==SQLITE_ROW ){ + RtreeNode *pTest; /* Used to test for reference loops */ + i64 iNode; /* Node number of parent node */ + + /* Before setting pChild->pParent, test that we are not creating a + ** loop of references (as we would if, say, pChild==pParent). We don't + ** want to do this as it leads to a memory leak when trying to delete + ** the referenced counted node structures. + */ + iNode = sqlite3_column_int64(pRtree->pReadParent, 0); + for(pTest=pLeaf; pTest && pTest->iNode!=iNode; pTest=pTest->pParent); + if( pTest==0 ){ + rc2 = nodeAcquire(pRtree, iNode, 0, &pChild->pParent); + } + } + rc = sqlite3_reset(pRtree->pReadParent); + if( rc==SQLITE_OK ) rc = rc2; + if( rc==SQLITE_OK && !pChild->pParent ){ + RTREE_IS_CORRUPT(pRtree); + rc = SQLITE_CORRUPT_VTAB; + } + pChild = pChild->pParent; + } + return rc; +} + +static int deleteCell(Rtree *, RtreeNode *, int, int); + +static int removeNode(Rtree *pRtree, RtreeNode *pNode, int iHeight){ + int rc; + int rc2; + RtreeNode *pParent = 0; + int iCell; + + assert( pNode->nRef==1 ); + + /* Remove the entry in the parent cell. */ + rc = nodeParentIndex(pRtree, pNode, &iCell); + if( rc==SQLITE_OK ){ + pParent = pNode->pParent; + pNode->pParent = 0; + rc = deleteCell(pRtree, pParent, iCell, iHeight+1); + testcase( rc!=SQLITE_OK ); + } + rc2 = nodeRelease(pRtree, pParent); + if( rc==SQLITE_OK ){ + rc = rc2; + } + if( rc!=SQLITE_OK ){ + return rc; + } + + /* Remove the xxx_node entry. */ + sqlite3_bind_int64(pRtree->pDeleteNode, 1, pNode->iNode); + sqlite3_step(pRtree->pDeleteNode); + if( SQLITE_OK!=(rc = sqlite3_reset(pRtree->pDeleteNode)) ){ + return rc; + } + + /* Remove the xxx_parent entry. */ + sqlite3_bind_int64(pRtree->pDeleteParent, 1, pNode->iNode); + sqlite3_step(pRtree->pDeleteParent); + if( SQLITE_OK!=(rc = sqlite3_reset(pRtree->pDeleteParent)) ){ + return rc; + } + + /* Remove the node from the in-memory hash table and link it into + ** the Rtree.pDeleted list. Its contents will be re-inserted later on. + */ + nodeHashDelete(pRtree, pNode); + pNode->iNode = iHeight; + pNode->pNext = pRtree->pDeleted; + pNode->nRef++; + pRtree->pDeleted = pNode; + + return SQLITE_OK; +} + +static int fixBoundingBox(Rtree *pRtree, RtreeNode *pNode){ + RtreeNode *pParent = pNode->pParent; + int rc = SQLITE_OK; + if( pParent ){ + int ii; + int nCell = NCELL(pNode); + RtreeCell box; /* Bounding box for pNode */ + nodeGetCell(pRtree, pNode, 0, &box); + for(ii=1; ii<nCell; ii++){ + RtreeCell cell; + nodeGetCell(pRtree, pNode, ii, &cell); + cellUnion(pRtree, &box, &cell); + } + box.iRowid = pNode->iNode; + rc = nodeParentIndex(pRtree, pNode, &ii); + if( rc==SQLITE_OK ){ + nodeOverwriteCell(pRtree, pParent, &box, ii); + rc = fixBoundingBox(pRtree, pParent); + } + } + return rc; +} + +/* +** Delete the cell at index iCell of node pNode. After removing the +** cell, adjust the r-tree data structure if required. +*/ +static int deleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell, int iHeight){ + RtreeNode *pParent; + int rc; + + if( SQLITE_OK!=(rc = fixLeafParent(pRtree, pNode)) ){ + return rc; + } + + /* Remove the cell from the node. This call just moves bytes around + ** the in-memory node image, so it cannot fail. + */ + nodeDeleteCell(pRtree, pNode, iCell); + + /* If the node is not the tree root and now has less than the minimum + ** number of cells, remove it from the tree. Otherwise, update the + ** cell in the parent node so that it tightly contains the updated + ** node. + */ + pParent = pNode->pParent; + assert( pParent || pNode->iNode==1 ); + if( pParent ){ + if( NCELL(pNode)<RTREE_MINCELLS(pRtree) ){ + rc = removeNode(pRtree, pNode, iHeight); + }else{ + rc = fixBoundingBox(pRtree, pNode); + } + } + + return rc; +} + +static int Reinsert( + Rtree *pRtree, + RtreeNode *pNode, + RtreeCell *pCell, + int iHeight +){ + int *aOrder; + int *aSpare; + RtreeCell *aCell; + RtreeDValue *aDistance; + int nCell; + RtreeDValue aCenterCoord[RTREE_MAX_DIMENSIONS]; + int iDim; + int ii; + int rc = SQLITE_OK; + int n; + + memset(aCenterCoord, 0, sizeof(RtreeDValue)*RTREE_MAX_DIMENSIONS); + + nCell = NCELL(pNode)+1; + n = (nCell+1)&(~1); + + /* Allocate the buffers used by this operation. The allocation is + ** relinquished before this function returns. + */ + aCell = (RtreeCell *)sqlite3_malloc64(n * ( + sizeof(RtreeCell) + /* aCell array */ + sizeof(int) + /* aOrder array */ + sizeof(int) + /* aSpare array */ + sizeof(RtreeDValue) /* aDistance array */ + )); + if( !aCell ){ + return SQLITE_NOMEM; + } + aOrder = (int *)&aCell[n]; + aSpare = (int *)&aOrder[n]; + aDistance = (RtreeDValue *)&aSpare[n]; + + for(ii=0; ii<nCell; ii++){ + if( ii==(nCell-1) ){ + memcpy(&aCell[ii], pCell, sizeof(RtreeCell)); + }else{ + nodeGetCell(pRtree, pNode, ii, &aCell[ii]); + } + aOrder[ii] = ii; + for(iDim=0; iDim<pRtree->nDim; iDim++){ + aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2]); + aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2+1]); + } + } + for(iDim=0; iDim<pRtree->nDim; iDim++){ + aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2)); + } + + for(ii=0; ii<nCell; ii++){ + aDistance[ii] = RTREE_ZERO; + for(iDim=0; iDim<pRtree->nDim; iDim++){ + RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) - + DCOORD(aCell[ii].aCoord[iDim*2])); + aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]); + } + } + + SortByDistance(aOrder, nCell, aDistance, aSpare); + nodeZero(pRtree, pNode); + + for(ii=0; rc==SQLITE_OK && ii<(nCell-(RTREE_MINCELLS(pRtree)+1)); ii++){ + RtreeCell *p = &aCell[aOrder[ii]]; + nodeInsertCell(pRtree, pNode, p); + if( p->iRowid==pCell->iRowid ){ + if( iHeight==0 ){ + rc = rowidWrite(pRtree, p->iRowid, pNode->iNode); + }else{ + rc = parentWrite(pRtree, p->iRowid, pNode->iNode); + } + } + } + if( rc==SQLITE_OK ){ + rc = fixBoundingBox(pRtree, pNode); + } + for(; rc==SQLITE_OK && ii<nCell; ii++){ + /* Find a node to store this cell in. pNode->iNode currently contains + ** the height of the sub-tree headed by the cell. + */ + RtreeNode *pInsert; + RtreeCell *p = &aCell[aOrder[ii]]; + rc = ChooseLeaf(pRtree, p, iHeight, &pInsert); + if( rc==SQLITE_OK ){ + int rc2; + rc = rtreeInsertCell(pRtree, pInsert, p, iHeight); + rc2 = nodeRelease(pRtree, pInsert); + if( rc==SQLITE_OK ){ + rc = rc2; + } + } + } + + sqlite3_free(aCell); + return rc; +} + +/* +** Insert cell pCell into node pNode. Node pNode is the head of a +** subtree iHeight high (leaf nodes have iHeight==0). +*/ +static int rtreeInsertCell( + Rtree *pRtree, + RtreeNode *pNode, + RtreeCell *pCell, + int iHeight +){ + int rc = SQLITE_OK; + if( iHeight>0 ){ + RtreeNode *pChild = nodeHashLookup(pRtree, pCell->iRowid); + if( pChild ){ + nodeRelease(pRtree, pChild->pParent); + nodeReference(pNode); + pChild->pParent = pNode; + } + } + if( nodeInsertCell(pRtree, pNode, pCell) ){ + if( iHeight<=pRtree->iReinsertHeight || pNode->iNode==1){ + rc = SplitNode(pRtree, pNode, pCell, iHeight); + }else{ + pRtree->iReinsertHeight = iHeight; + rc = Reinsert(pRtree, pNode, pCell, iHeight); + } + }else{ + rc = AdjustTree(pRtree, pNode, pCell); + if( ALWAYS(rc==SQLITE_OK) ){ + if( iHeight==0 ){ + rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode); + }else{ + rc = parentWrite(pRtree, pCell->iRowid, pNode->iNode); + } + } + } + return rc; +} + +static int reinsertNodeContent(Rtree *pRtree, RtreeNode *pNode){ + int ii; + int rc = SQLITE_OK; + int nCell = NCELL(pNode); + + for(ii=0; rc==SQLITE_OK && ii<nCell; ii++){ + RtreeNode *pInsert; + RtreeCell cell; + nodeGetCell(pRtree, pNode, ii, &cell); + + /* Find a node to store this cell in. pNode->iNode currently contains + ** the height of the sub-tree headed by the cell. + */ + rc = ChooseLeaf(pRtree, &cell, (int)pNode->iNode, &pInsert); + if( rc==SQLITE_OK ){ + int rc2; + rc = rtreeInsertCell(pRtree, pInsert, &cell, (int)pNode->iNode); + rc2 = nodeRelease(pRtree, pInsert); + if( rc==SQLITE_OK ){ + rc = rc2; + } + } + } + return rc; +} + +/* +** Select a currently unused rowid for a new r-tree record. +*/ +static int rtreeNewRowid(Rtree *pRtree, i64 *piRowid){ + int rc; + sqlite3_bind_null(pRtree->pWriteRowid, 1); + sqlite3_bind_null(pRtree->pWriteRowid, 2); + sqlite3_step(pRtree->pWriteRowid); + rc = sqlite3_reset(pRtree->pWriteRowid); + *piRowid = sqlite3_last_insert_rowid(pRtree->db); + return rc; +} + +/* +** Remove the entry with rowid=iDelete from the r-tree structure. +*/ +static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){ + int rc; /* Return code */ + RtreeNode *pLeaf = 0; /* Leaf node containing record iDelete */ + int iCell; /* Index of iDelete cell in pLeaf */ + RtreeNode *pRoot = 0; /* Root node of rtree structure */ + + + /* Obtain a reference to the root node to initialize Rtree.iDepth */ + rc = nodeAcquire(pRtree, 1, 0, &pRoot); + + /* Obtain a reference to the leaf node that contains the entry + ** about to be deleted. + */ + if( rc==SQLITE_OK ){ + rc = findLeafNode(pRtree, iDelete, &pLeaf, 0); + } + +#ifdef CORRUPT_DB + assert( pLeaf!=0 || rc!=SQLITE_OK || CORRUPT_DB ); +#endif + + /* Delete the cell in question from the leaf node. */ + if( rc==SQLITE_OK && pLeaf ){ + int rc2; + rc = nodeRowidIndex(pRtree, pLeaf, iDelete, &iCell); + if( rc==SQLITE_OK ){ + rc = deleteCell(pRtree, pLeaf, iCell, 0); + } + rc2 = nodeRelease(pRtree, pLeaf); + if( rc==SQLITE_OK ){ + rc = rc2; + } + } + + /* Delete the corresponding entry in the <rtree>_rowid table. */ + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pRtree->pDeleteRowid, 1, iDelete); + sqlite3_step(pRtree->pDeleteRowid); + rc = sqlite3_reset(pRtree->pDeleteRowid); + } + + /* Check if the root node now has exactly one child. If so, remove + ** it, schedule the contents of the child for reinsertion and + ** reduce the tree height by one. + ** + ** This is equivalent to copying the contents of the child into + ** the root node (the operation that Gutman's paper says to perform + ** in this scenario). + */ + if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){ + int rc2; + RtreeNode *pChild = 0; + i64 iChild = nodeGetRowid(pRtree, pRoot, 0); + rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); /* tag-20210916a */ + if( rc==SQLITE_OK ){ + rc = removeNode(pRtree, pChild, pRtree->iDepth-1); + } + rc2 = nodeRelease(pRtree, pChild); + if( rc==SQLITE_OK ) rc = rc2; + if( rc==SQLITE_OK ){ + pRtree->iDepth--; + writeInt16(pRoot->zData, pRtree->iDepth); + pRoot->isDirty = 1; + } + } + + /* Re-insert the contents of any underfull nodes removed from the tree. */ + for(pLeaf=pRtree->pDeleted; pLeaf; pLeaf=pRtree->pDeleted){ + if( rc==SQLITE_OK ){ + rc = reinsertNodeContent(pRtree, pLeaf); + } + pRtree->pDeleted = pLeaf->pNext; + pRtree->nNodeRef--; + sqlite3_free(pLeaf); + } + + /* Release the reference to the root node. */ + if( rc==SQLITE_OK ){ + rc = nodeRelease(pRtree, pRoot); + }else{ + nodeRelease(pRtree, pRoot); + } + + return rc; +} + +/* +** Rounding constants for float->double conversion. +*/ +#define RNDTOWARDS (1.0 - 1.0/8388608.0) /* Round towards zero */ +#define RNDAWAY (1.0 + 1.0/8388608.0) /* Round away from zero */ + +#if !defined(SQLITE_RTREE_INT_ONLY) +/* +** Convert an sqlite3_value into an RtreeValue (presumably a float) +** while taking care to round toward negative or positive, respectively. +*/ +static RtreeValue rtreeValueDown(sqlite3_value *v){ + double d = sqlite3_value_double(v); + float f = (float)d; + if( f>d ){ + f = (float)(d*(d<0 ? RNDAWAY : RNDTOWARDS)); + } + return f; +} +static RtreeValue rtreeValueUp(sqlite3_value *v){ + double d = sqlite3_value_double(v); + float f = (float)d; + if( f<d ){ + f = (float)(d*(d<0 ? RNDTOWARDS : RNDAWAY)); + } + return f; +} +#endif /* !defined(SQLITE_RTREE_INT_ONLY) */ + +/* +** A constraint has failed while inserting a row into an rtree table. +** Assuming no OOM error occurs, this function sets the error message +** (at pRtree->base.zErrMsg) to an appropriate value and returns +** SQLITE_CONSTRAINT. +** +** Parameter iCol is the index of the leftmost column involved in the +** constraint failure. If it is 0, then the constraint that failed is +** the unique constraint on the id column. Otherwise, it is the rtree +** (c1<=c2) constraint on columns iCol and iCol+1 that has failed. +** +** If an OOM occurs, SQLITE_NOMEM is returned instead of SQLITE_CONSTRAINT. +*/ +static int rtreeConstraintError(Rtree *pRtree, int iCol){ + sqlite3_stmt *pStmt = 0; + char *zSql; + int rc; + + assert( iCol==0 || iCol%2 ); + zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", pRtree->zDb, pRtree->zName); + if( zSql ){ + rc = sqlite3_prepare_v2(pRtree->db, zSql, -1, &pStmt, 0); + }else{ + rc = SQLITE_NOMEM; + } + sqlite3_free(zSql); + + if( rc==SQLITE_OK ){ + if( iCol==0 ){ + const char *zCol = sqlite3_column_name(pStmt, 0); + pRtree->base.zErrMsg = sqlite3_mprintf( + "UNIQUE constraint failed: %s.%s", pRtree->zName, zCol + ); + }else{ + const char *zCol1 = sqlite3_column_name(pStmt, iCol); + const char *zCol2 = sqlite3_column_name(pStmt, iCol+1); + pRtree->base.zErrMsg = sqlite3_mprintf( + "rtree constraint failed: %s.(%s<=%s)", pRtree->zName, zCol1, zCol2 + ); + } + } + + sqlite3_finalize(pStmt); + return (rc==SQLITE_OK ? SQLITE_CONSTRAINT : rc); +} + + + +/* +** The xUpdate method for rtree module virtual tables. +*/ +static int rtreeUpdate( + sqlite3_vtab *pVtab, + int nData, + sqlite3_value **aData, + sqlite_int64 *pRowid +){ + Rtree *pRtree = (Rtree *)pVtab; + int rc = SQLITE_OK; + RtreeCell cell; /* New cell to insert if nData>1 */ + int bHaveRowid = 0; /* Set to 1 after new rowid is determined */ + + if( pRtree->nNodeRef ){ + /* Unable to write to the btree while another cursor is reading from it, + ** since the write might do a rebalance which would disrupt the read + ** cursor. */ + return SQLITE_LOCKED_VTAB; + } + rtreeReference(pRtree); + assert(nData>=1); + + memset(&cell, 0, sizeof(cell)); + + /* Constraint handling. A write operation on an r-tree table may return + ** SQLITE_CONSTRAINT for two reasons: + ** + ** 1. A duplicate rowid value, or + ** 2. The supplied data violates the "x2>=x1" constraint. + ** + ** In the first case, if the conflict-handling mode is REPLACE, then + ** the conflicting row can be removed before proceeding. In the second + ** case, SQLITE_CONSTRAINT must be returned regardless of the + ** conflict-handling mode specified by the user. + */ + if( nData>1 ){ + int ii; + int nn = nData - 4; + + if( nn > pRtree->nDim2 ) nn = pRtree->nDim2; + /* Populate the cell.aCoord[] array. The first coordinate is aData[3]. + ** + ** NB: nData can only be less than nDim*2+3 if the rtree is mis-declared + ** with "column" that are interpreted as table constraints. + ** Example: CREATE VIRTUAL TABLE bad USING rtree(x,y,CHECK(y>5)); + ** This problem was discovered after years of use, so we silently ignore + ** these kinds of misdeclared tables to avoid breaking any legacy. + */ + +#ifndef SQLITE_RTREE_INT_ONLY + if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ + for(ii=0; ii<nn; ii+=2){ + cell.aCoord[ii].f = rtreeValueDown(aData[ii+3]); + cell.aCoord[ii+1].f = rtreeValueUp(aData[ii+4]); + if( cell.aCoord[ii].f>cell.aCoord[ii+1].f ){ + rc = rtreeConstraintError(pRtree, ii+1); + goto constraint; + } + } + }else +#endif + { + for(ii=0; ii<nn; ii+=2){ + cell.aCoord[ii].i = sqlite3_value_int(aData[ii+3]); + cell.aCoord[ii+1].i = sqlite3_value_int(aData[ii+4]); + if( cell.aCoord[ii].i>cell.aCoord[ii+1].i ){ + rc = rtreeConstraintError(pRtree, ii+1); + goto constraint; + } + } + } + + /* If a rowid value was supplied, check if it is already present in + ** the table. If so, the constraint has failed. */ + if( sqlite3_value_type(aData[2])!=SQLITE_NULL ){ + cell.iRowid = sqlite3_value_int64(aData[2]); + if( sqlite3_value_type(aData[0])==SQLITE_NULL + || sqlite3_value_int64(aData[0])!=cell.iRowid + ){ + int steprc; + sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid); + steprc = sqlite3_step(pRtree->pReadRowid); + rc = sqlite3_reset(pRtree->pReadRowid); + if( SQLITE_ROW==steprc ){ + if( sqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){ + rc = rtreeDeleteRowid(pRtree, cell.iRowid); + }else{ + rc = rtreeConstraintError(pRtree, 0); + goto constraint; + } + } + } + bHaveRowid = 1; + } + } + + /* If aData[0] is not an SQL NULL value, it is the rowid of a + ** record to delete from the r-tree table. The following block does + ** just that. + */ + if( sqlite3_value_type(aData[0])!=SQLITE_NULL ){ + rc = rtreeDeleteRowid(pRtree, sqlite3_value_int64(aData[0])); + } + + /* If the aData[] array contains more than one element, elements + ** (aData[2]..aData[argc-1]) contain a new record to insert into + ** the r-tree structure. + */ + if( rc==SQLITE_OK && nData>1 ){ + /* Insert the new record into the r-tree */ + RtreeNode *pLeaf = 0; + + /* Figure out the rowid of the new row. */ + if( bHaveRowid==0 ){ + rc = rtreeNewRowid(pRtree, &cell.iRowid); + } + *pRowid = cell.iRowid; + + if( rc==SQLITE_OK ){ + rc = ChooseLeaf(pRtree, &cell, 0, &pLeaf); + } + if( rc==SQLITE_OK ){ + int rc2; + pRtree->iReinsertHeight = -1; + rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0); + rc2 = nodeRelease(pRtree, pLeaf); + if( rc==SQLITE_OK ){ + rc = rc2; + } + } + if( rc==SQLITE_OK && pRtree->nAux ){ + sqlite3_stmt *pUp = pRtree->pWriteAux; + int jj; + sqlite3_bind_int64(pUp, 1, *pRowid); + for(jj=0; jj<pRtree->nAux; jj++){ + sqlite3_bind_value(pUp, jj+2, aData[pRtree->nDim2+3+jj]); + } + sqlite3_step(pUp); + rc = sqlite3_reset(pUp); + } + } + +constraint: + rtreeRelease(pRtree); + return rc; +} + +/* +** Called when a transaction starts. +*/ +static int rtreeBeginTransaction(sqlite3_vtab *pVtab){ + Rtree *pRtree = (Rtree *)pVtab; + assert( pRtree->inWrTrans==0 ); + pRtree->inWrTrans++; + return SQLITE_OK; +} + +/* +** Called when a transaction completes (either by COMMIT or ROLLBACK). +** The sqlite3_blob object should be released at this point. +*/ +static int rtreeEndTransaction(sqlite3_vtab *pVtab){ + Rtree *pRtree = (Rtree *)pVtab; + pRtree->inWrTrans = 0; + nodeBlobReset(pRtree); + return SQLITE_OK; +} + +/* +** The xRename method for rtree module virtual tables. +*/ +static int rtreeRename(sqlite3_vtab *pVtab, const char *zNewName){ + Rtree *pRtree = (Rtree *)pVtab; + int rc = SQLITE_NOMEM; + char *zSql = sqlite3_mprintf( + "ALTER TABLE %Q.'%q_node' RENAME TO \"%w_node\";" + "ALTER TABLE %Q.'%q_parent' RENAME TO \"%w_parent\";" + "ALTER TABLE %Q.'%q_rowid' RENAME TO \"%w_rowid\";" + , pRtree->zDb, pRtree->zName, zNewName + , pRtree->zDb, pRtree->zName, zNewName + , pRtree->zDb, pRtree->zName, zNewName + ); + if( zSql ){ + nodeBlobReset(pRtree); + rc = sqlite3_exec(pRtree->db, zSql, 0, 0, 0); + sqlite3_free(zSql); + } + return rc; +} + +/* +** The xSavepoint method. +** +** This module does not need to do anything to support savepoints. However, +** it uses this hook to close any open blob handle. This is done because a +** DROP TABLE command - which fortunately always opens a savepoint - cannot +** succeed if there are any open blob handles. i.e. if the blob handle were +** not closed here, the following would fail: +** +** BEGIN; +** INSERT INTO rtree... +** DROP TABLE <tablename>; -- Would fail with SQLITE_LOCKED +** COMMIT; +*/ +static int rtreeSavepoint(sqlite3_vtab *pVtab, int iSavepoint){ + Rtree *pRtree = (Rtree *)pVtab; + u8 iwt = pRtree->inWrTrans; + UNUSED_PARAMETER(iSavepoint); + pRtree->inWrTrans = 0; + nodeBlobReset(pRtree); + pRtree->inWrTrans = iwt; + return SQLITE_OK; +} + +/* +** This function populates the pRtree->nRowEst variable with an estimate +** of the number of rows in the virtual table. If possible, this is based +** on sqlite_stat1 data. Otherwise, use RTREE_DEFAULT_ROWEST. +*/ +static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ + const char *zFmt = "SELECT stat FROM %Q.sqlite_stat1 WHERE tbl = '%q_rowid'"; + char *zSql; + sqlite3_stmt *p; + int rc; + i64 nRow = RTREE_MIN_ROWEST; + + rc = sqlite3_table_column_metadata( + db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0 + ); + if( rc!=SQLITE_OK ){ + pRtree->nRowEst = RTREE_DEFAULT_ROWEST; + return rc==SQLITE_ERROR ? SQLITE_OK : rc; + } + zSql = sqlite3_mprintf(zFmt, pRtree->zDb, pRtree->zName); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(db, zSql, -1, &p, 0); + if( rc==SQLITE_OK ){ + if( sqlite3_step(p)==SQLITE_ROW ) nRow = sqlite3_column_int64(p, 0); + rc = sqlite3_finalize(p); + } + sqlite3_free(zSql); + } + pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST); + return rc; +} + + +/* +** Return true if zName is the extension on one of the shadow tables used +** by this module. +*/ +static int rtreeShadowName(const char *zName){ + static const char *azName[] = { + "node", "parent", "rowid" + }; + unsigned int i; + for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){ + if( sqlite3_stricmp(zName, azName[i])==0 ) return 1; + } + return 0; +} + +/* Forward declaration */ +static int rtreeIntegrity(sqlite3_vtab*, char**); + +static sqlite3_module rtreeModule = { + 4, /* iVersion */ + rtreeCreate, /* xCreate - create a table */ + rtreeConnect, /* xConnect - connect to an existing table */ + rtreeBestIndex, /* xBestIndex - Determine search strategy */ + rtreeDisconnect, /* xDisconnect - Disconnect from a table */ + rtreeDestroy, /* xDestroy - Drop a table */ + rtreeOpen, /* xOpen - open a cursor */ + rtreeClose, /* xClose - close a cursor */ + rtreeFilter, /* xFilter - configure scan constraints */ + rtreeNext, /* xNext - advance a cursor */ + rtreeEof, /* xEof */ + rtreeColumn, /* xColumn - read data */ + rtreeRowid, /* xRowid - read data */ + rtreeUpdate, /* xUpdate - write data */ + rtreeBeginTransaction, /* xBegin - begin transaction */ + rtreeEndTransaction, /* xSync - sync transaction */ + rtreeEndTransaction, /* xCommit - commit transaction */ + rtreeEndTransaction, /* xRollback - rollback transaction */ + 0, /* xFindFunction - function overloading */ + rtreeRename, /* xRename - rename the table */ + rtreeSavepoint, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + rtreeShadowName, /* xShadowName */ + rtreeIntegrity /* xIntegrity */ +}; + +static int rtreeSqlInit( + Rtree *pRtree, + sqlite3 *db, + const char *zDb, + const char *zPrefix, + int isCreate +){ + int rc = SQLITE_OK; + + #define N_STATEMENT 8 + static const char *azSql[N_STATEMENT] = { + /* Write the xxx_node table */ + "INSERT OR REPLACE INTO '%q'.'%q_node' VALUES(?1, ?2)", + "DELETE FROM '%q'.'%q_node' WHERE nodeno = ?1", + + /* Read and write the xxx_rowid table */ + "SELECT nodeno FROM '%q'.'%q_rowid' WHERE rowid = ?1", + "INSERT OR REPLACE INTO '%q'.'%q_rowid' VALUES(?1, ?2)", + "DELETE FROM '%q'.'%q_rowid' WHERE rowid = ?1", + + /* Read and write the xxx_parent table */ + "SELECT parentnode FROM '%q'.'%q_parent' WHERE nodeno = ?1", + "INSERT OR REPLACE INTO '%q'.'%q_parent' VALUES(?1, ?2)", + "DELETE FROM '%q'.'%q_parent' WHERE nodeno = ?1" + }; + sqlite3_stmt **appStmt[N_STATEMENT]; + int i; + const int f = SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB; + + pRtree->db = db; + + if( isCreate ){ + char *zCreate; + sqlite3_str *p = sqlite3_str_new(db); + int ii; + sqlite3_str_appendf(p, + "CREATE TABLE \"%w\".\"%w_rowid\"(rowid INTEGER PRIMARY KEY,nodeno", + zDb, zPrefix); + for(ii=0; ii<pRtree->nAux; ii++){ + sqlite3_str_appendf(p,",a%d",ii); + } + sqlite3_str_appendf(p, + ");CREATE TABLE \"%w\".\"%w_node\"(nodeno INTEGER PRIMARY KEY,data);", + zDb, zPrefix); + sqlite3_str_appendf(p, + "CREATE TABLE \"%w\".\"%w_parent\"(nodeno INTEGER PRIMARY KEY,parentnode);", + zDb, zPrefix); + sqlite3_str_appendf(p, + "INSERT INTO \"%w\".\"%w_node\"VALUES(1,zeroblob(%d))", + zDb, zPrefix, pRtree->iNodeSize); + zCreate = sqlite3_str_finish(p); + if( !zCreate ){ + return SQLITE_NOMEM; + } + rc = sqlite3_exec(db, zCreate, 0, 0, 0); + sqlite3_free(zCreate); + if( rc!=SQLITE_OK ){ + return rc; + } + } + + appStmt[0] = &pRtree->pWriteNode; + appStmt[1] = &pRtree->pDeleteNode; + appStmt[2] = &pRtree->pReadRowid; + appStmt[3] = &pRtree->pWriteRowid; + appStmt[4] = &pRtree->pDeleteRowid; + appStmt[5] = &pRtree->pReadParent; + appStmt[6] = &pRtree->pWriteParent; + appStmt[7] = &pRtree->pDeleteParent; + + rc = rtreeQueryStat1(db, pRtree); + for(i=0; i<N_STATEMENT && rc==SQLITE_OK; i++){ + char *zSql; + const char *zFormat; + if( i!=3 || pRtree->nAux==0 ){ + zFormat = azSql[i]; + }else { + /* An UPSERT is very slightly slower than REPLACE, but it is needed + ** if there are auxiliary columns */ + zFormat = "INSERT INTO\"%w\".\"%w_rowid\"(rowid,nodeno)VALUES(?1,?2)" + "ON CONFLICT(rowid)DO UPDATE SET nodeno=excluded.nodeno"; + } + zSql = sqlite3_mprintf(zFormat, zDb, zPrefix); + if( zSql ){ + rc = sqlite3_prepare_v3(db, zSql, -1, f, appStmt[i], 0); + }else{ + rc = SQLITE_NOMEM; + } + sqlite3_free(zSql); + } + if( pRtree->nAux ){ + pRtree->zReadAuxSql = sqlite3_mprintf( + "SELECT * FROM \"%w\".\"%w_rowid\" WHERE rowid=?1", + zDb, zPrefix); + if( pRtree->zReadAuxSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_str *p = sqlite3_str_new(db); + int ii; + char *zSql; + sqlite3_str_appendf(p, "UPDATE \"%w\".\"%w_rowid\"SET ", zDb, zPrefix); + for(ii=0; ii<pRtree->nAux; ii++){ + if( ii ) sqlite3_str_append(p, ",", 1); +#ifdef SQLITE_ENABLE_GEOPOLY + if( ii<pRtree->nAuxNotNull ){ + sqlite3_str_appendf(p,"a%d=coalesce(?%d,a%d)",ii,ii+2,ii); + }else +#endif + { + sqlite3_str_appendf(p,"a%d=?%d",ii,ii+2); + } + } + sqlite3_str_appendf(p, " WHERE rowid=?1"); + zSql = sqlite3_str_finish(p); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v3(db, zSql, -1, f, &pRtree->pWriteAux, 0); + sqlite3_free(zSql); + } + } + } + + return rc; +} + +/* +** The second argument to this function contains the text of an SQL statement +** that returns a single integer value. The statement is compiled and executed +** using database connection db. If successful, the integer value returned +** is written to *piVal and SQLITE_OK returned. Otherwise, an SQLite error +** code is returned and the value of *piVal after returning is not defined. +*/ +static int getIntFromStmt(sqlite3 *db, const char *zSql, int *piVal){ + int rc = SQLITE_NOMEM; + if( zSql ){ + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + *piVal = sqlite3_column_int(pStmt, 0); + } + rc = sqlite3_finalize(pStmt); + } + } + return rc; +} + +/* +** This function is called from within the xConnect() or xCreate() method to +** determine the node-size used by the rtree table being created or connected +** to. If successful, pRtree->iNodeSize is populated and SQLITE_OK returned. +** Otherwise, an SQLite error code is returned. +** +** If this function is being called as part of an xConnect(), then the rtree +** table already exists. In this case the node-size is determined by inspecting +** the root node of the tree. +** +** Otherwise, for an xCreate(), use 64 bytes less than the database page-size. +** This ensures that each node is stored on a single database page. If the +** database page-size is so large that more than RTREE_MAXCELLS entries +** would fit in a single node, use a smaller node-size. +*/ +static int getNodeSize( + sqlite3 *db, /* Database handle */ + Rtree *pRtree, /* Rtree handle */ + int isCreate, /* True for xCreate, false for xConnect */ + char **pzErr /* OUT: Error message, if any */ +){ + int rc; + char *zSql; + if( isCreate ){ + int iPageSize = 0; + zSql = sqlite3_mprintf("PRAGMA %Q.page_size", pRtree->zDb); + rc = getIntFromStmt(db, zSql, &iPageSize); + if( rc==SQLITE_OK ){ + pRtree->iNodeSize = iPageSize-64; + if( (4+pRtree->nBytesPerCell*RTREE_MAXCELLS)<pRtree->iNodeSize ){ + pRtree->iNodeSize = 4+pRtree->nBytesPerCell*RTREE_MAXCELLS; + } + }else{ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + } + }else{ + zSql = sqlite3_mprintf( + "SELECT length(data) FROM '%q'.'%q_node' WHERE nodeno = 1", + pRtree->zDb, pRtree->zName + ); + rc = getIntFromStmt(db, zSql, &pRtree->iNodeSize); + if( rc!=SQLITE_OK ){ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + }else if( pRtree->iNodeSize<(512-64) ){ + rc = SQLITE_CORRUPT_VTAB; + RTREE_IS_CORRUPT(pRtree); + *pzErr = sqlite3_mprintf("undersize RTree blobs in \"%q_node\"", + pRtree->zName); + } + } + + sqlite3_free(zSql); + return rc; +} + +/* +** Return the length of a token +*/ +static int rtreeTokenLength(const char *z){ + int dummy = 0; + return sqlite3GetToken((const unsigned char*)z,&dummy); +} + +/* +** This function is the implementation of both the xConnect and xCreate +** methods of the r-tree virtual table. +** +** argv[0] -> module name +** argv[1] -> database name +** argv[2] -> table name +** argv[...] -> column names... +*/ +static int rtreeInit( + sqlite3 *db, /* Database connection */ + void *pAux, /* One of the RTREE_COORD_* constants */ + int argc, const char *const*argv, /* Parameters to CREATE TABLE statement */ + sqlite3_vtab **ppVtab, /* OUT: New virtual table */ + char **pzErr, /* OUT: Error message, if any */ + int isCreate /* True for xCreate, false for xConnect */ +){ + int rc = SQLITE_OK; + Rtree *pRtree; + int nDb; /* Length of string argv[1] */ + int nName; /* Length of string argv[2] */ + int eCoordType = (pAux ? RTREE_COORD_INT32 : RTREE_COORD_REAL32); + sqlite3_str *pSql; + char *zSql; + int ii = 4; + int iErr; + + const char *aErrMsg[] = { + 0, /* 0 */ + "Wrong number of columns for an rtree table", /* 1 */ + "Too few columns for an rtree table", /* 2 */ + "Too many columns for an rtree table", /* 3 */ + "Auxiliary rtree columns must be last" /* 4 */ + }; + + assert( RTREE_MAX_AUX_COLUMN<256 ); /* Aux columns counted by a u8 */ + if( argc<6 || argc>RTREE_MAX_AUX_COLUMN+3 ){ + *pzErr = sqlite3_mprintf("%s", aErrMsg[2 + (argc>=6)]); + return SQLITE_ERROR; + } + + sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); + + /* Allocate the sqlite3_vtab structure */ + nDb = (int)strlen(argv[1]); + nName = (int)strlen(argv[2]); + pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2); + if( !pRtree ){ + return SQLITE_NOMEM; + } + memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2); + pRtree->nBusy = 1; + pRtree->base.pModule = &rtreeModule; + pRtree->zDb = (char *)&pRtree[1]; + pRtree->zName = &pRtree->zDb[nDb+1]; + pRtree->eCoordType = (u8)eCoordType; + memcpy(pRtree->zDb, argv[1], nDb); + memcpy(pRtree->zName, argv[2], nName); + + + /* Create/Connect to the underlying relational database schema. If + ** that is successful, call sqlite3_declare_vtab() to configure + ** the r-tree table schema. + */ + pSql = sqlite3_str_new(db); + sqlite3_str_appendf(pSql, "CREATE TABLE x(%.*s INT", + rtreeTokenLength(argv[3]), argv[3]); + for(ii=4; ii<argc; ii++){ + const char *zArg = argv[ii]; + if( zArg[0]=='+' ){ + pRtree->nAux++; + sqlite3_str_appendf(pSql, ",%.*s", rtreeTokenLength(zArg+1), zArg+1); + }else if( pRtree->nAux>0 ){ + break; + }else{ + static const char *azFormat[] = {",%.*s REAL", ",%.*s INT"}; + pRtree->nDim2++; + sqlite3_str_appendf(pSql, azFormat[eCoordType], + rtreeTokenLength(zArg), zArg); + } + } + sqlite3_str_appendf(pSql, ");"); + zSql = sqlite3_str_finish(pSql); + if( !zSql ){ + rc = SQLITE_NOMEM; + }else if( ii<argc ){ + *pzErr = sqlite3_mprintf("%s", aErrMsg[4]); + rc = SQLITE_ERROR; + }else if( SQLITE_OK!=(rc = sqlite3_declare_vtab(db, zSql)) ){ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + } + sqlite3_free(zSql); + if( rc ) goto rtreeInit_fail; + pRtree->nDim = pRtree->nDim2/2; + if( pRtree->nDim<1 ){ + iErr = 2; + }else if( pRtree->nDim2>RTREE_MAX_DIMENSIONS*2 ){ + iErr = 3; + }else if( pRtree->nDim2 % 2 ){ + iErr = 1; + }else{ + iErr = 0; + } + if( iErr ){ + *pzErr = sqlite3_mprintf("%s", aErrMsg[iErr]); + goto rtreeInit_fail; + } + pRtree->nBytesPerCell = 8 + pRtree->nDim2*4; + + /* Figure out the node size to use. */ + rc = getNodeSize(db, pRtree, isCreate, pzErr); + if( rc ) goto rtreeInit_fail; + rc = rtreeSqlInit(pRtree, db, argv[1], argv[2], isCreate); + if( rc ){ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + goto rtreeInit_fail; + } + + *ppVtab = (sqlite3_vtab *)pRtree; + return SQLITE_OK; + +rtreeInit_fail: + if( rc==SQLITE_OK ) rc = SQLITE_ERROR; + assert( *ppVtab==0 ); + assert( pRtree->nBusy==1 ); + rtreeRelease(pRtree); + return rc; +} + + +/* +** Implementation of a scalar function that decodes r-tree nodes to +** human readable strings. This can be used for debugging and analysis. +** +** The scalar function takes two arguments: (1) the number of dimensions +** to the rtree (between 1 and 5, inclusive) and (2) a blob of data containing +** an r-tree node. For a two-dimensional r-tree structure called "rt", to +** deserialize all nodes, a statement like: +** +** SELECT rtreenode(2, data) FROM rt_node; +** +** The human readable string takes the form of a Tcl list with one +** entry for each cell in the r-tree node. Each entry is itself a +** list, containing the 8-byte rowid/pageno followed by the +** <num-dimension>*2 coordinates. +*/ +static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ + RtreeNode node; + Rtree tree; + int ii; + int nData; + int errCode; + sqlite3_str *pOut; + + UNUSED_PARAMETER(nArg); + memset(&node, 0, sizeof(RtreeNode)); + memset(&tree, 0, sizeof(Rtree)); + tree.nDim = (u8)sqlite3_value_int(apArg[0]); + if( tree.nDim<1 || tree.nDim>5 ) return; + tree.nDim2 = tree.nDim*2; + tree.nBytesPerCell = 8 + 8 * tree.nDim; + node.zData = (u8 *)sqlite3_value_blob(apArg[1]); + if( node.zData==0 ) return; + nData = sqlite3_value_bytes(apArg[1]); + if( nData<4 ) return; + if( nData<NCELL(&node)*tree.nBytesPerCell ) return; + + pOut = sqlite3_str_new(0); + for(ii=0; ii<NCELL(&node); ii++){ + RtreeCell cell; + int jj; + + nodeGetCell(&tree, &node, ii, &cell); + if( ii>0 ) sqlite3_str_append(pOut, " ", 1); + sqlite3_str_appendf(pOut, "{%lld", cell.iRowid); + for(jj=0; jj<tree.nDim2; jj++){ +#ifndef SQLITE_RTREE_INT_ONLY + sqlite3_str_appendf(pOut, " %g", (double)cell.aCoord[jj].f); +#else + sqlite3_str_appendf(pOut, " %d", cell.aCoord[jj].i); +#endif + } + sqlite3_str_append(pOut, "}", 1); + } + errCode = sqlite3_str_errcode(pOut); + sqlite3_result_text(ctx, sqlite3_str_finish(pOut), -1, sqlite3_free); + sqlite3_result_error_code(ctx, errCode); +} + +/* This routine implements an SQL function that returns the "depth" parameter +** from the front of a blob that is an r-tree node. For example: +** +** SELECT rtreedepth(data) FROM rt_node WHERE nodeno=1; +** +** The depth value is 0 for all nodes other than the root node, and the root +** node always has nodeno=1, so the example above is the primary use for this +** routine. This routine is intended for testing and analysis only. +*/ +static void rtreedepth(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ + UNUSED_PARAMETER(nArg); + if( sqlite3_value_type(apArg[0])!=SQLITE_BLOB + || sqlite3_value_bytes(apArg[0])<2 + + ){ + sqlite3_result_error(ctx, "Invalid argument to rtreedepth()", -1); + }else{ + u8 *zBlob = (u8 *)sqlite3_value_blob(apArg[0]); + if( zBlob ){ + sqlite3_result_int(ctx, readInt16(zBlob)); + }else{ + sqlite3_result_error_nomem(ctx); + } + } +} + +/* +** Context object passed between the various routines that make up the +** implementation of integrity-check function rtreecheck(). +*/ +typedef struct RtreeCheck RtreeCheck; +struct RtreeCheck { + sqlite3 *db; /* Database handle */ + const char *zDb; /* Database containing rtree table */ + const char *zTab; /* Name of rtree table */ + int bInt; /* True for rtree_i32 table */ + int nDim; /* Number of dimensions for this rtree tbl */ + sqlite3_stmt *pGetNode; /* Statement used to retrieve nodes */ + sqlite3_stmt *aCheckMapping[2]; /* Statements to query %_parent/%_rowid */ + int nLeaf; /* Number of leaf cells in table */ + int nNonLeaf; /* Number of non-leaf cells in table */ + int rc; /* Return code */ + char *zReport; /* Message to report */ + int nErr; /* Number of lines in zReport */ +}; + +#define RTREE_CHECK_MAX_ERROR 100 + +/* +** Reset SQL statement pStmt. If the sqlite3_reset() call returns an error, +** and RtreeCheck.rc==SQLITE_OK, set RtreeCheck.rc to the error code. +*/ +static void rtreeCheckReset(RtreeCheck *pCheck, sqlite3_stmt *pStmt){ + int rc = sqlite3_reset(pStmt); + if( pCheck->rc==SQLITE_OK ) pCheck->rc = rc; +} + +/* +** The second and subsequent arguments to this function are a format string +** and printf style arguments. This function formats the string and attempts +** to compile it as an SQL statement. +** +** If successful, a pointer to the new SQL statement is returned. Otherwise, +** NULL is returned and an error code left in RtreeCheck.rc. +*/ +static sqlite3_stmt *rtreeCheckPrepare( + RtreeCheck *pCheck, /* RtreeCheck object */ + const char *zFmt, ... /* Format string and trailing args */ +){ + va_list ap; + char *z; + sqlite3_stmt *pRet = 0; + + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + + if( pCheck->rc==SQLITE_OK ){ + if( z==0 ){ + pCheck->rc = SQLITE_NOMEM; + }else{ + pCheck->rc = sqlite3_prepare_v2(pCheck->db, z, -1, &pRet, 0); + } + } + + sqlite3_free(z); + va_end(ap); + return pRet; +} + +/* +** The second and subsequent arguments to this function are a printf() +** style format string and arguments. This function formats the string and +** appends it to the report being accumuated in pCheck. +*/ +static void rtreeCheckAppendMsg(RtreeCheck *pCheck, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + if( pCheck->rc==SQLITE_OK && pCheck->nErr<RTREE_CHECK_MAX_ERROR ){ + char *z = sqlite3_vmprintf(zFmt, ap); + if( z==0 ){ + pCheck->rc = SQLITE_NOMEM; + }else{ + pCheck->zReport = sqlite3_mprintf("%z%s%z", + pCheck->zReport, (pCheck->zReport ? "\n" : ""), z + ); + if( pCheck->zReport==0 ){ + pCheck->rc = SQLITE_NOMEM; + } + } + pCheck->nErr++; + } + va_end(ap); +} + +/* +** This function is a no-op if there is already an error code stored +** in the RtreeCheck object indicated by the first argument. NULL is +** returned in this case. +** +** Otherwise, the contents of rtree table node iNode are loaded from +** the database and copied into a buffer obtained from sqlite3_malloc(). +** If no error occurs, a pointer to the buffer is returned and (*pnNode) +** is set to the size of the buffer in bytes. +** +** Or, if an error does occur, NULL is returned and an error code left +** in the RtreeCheck object. The final value of *pnNode is undefined in +** this case. +*/ +static u8 *rtreeCheckGetNode(RtreeCheck *pCheck, i64 iNode, int *pnNode){ + u8 *pRet = 0; /* Return value */ + + if( pCheck->rc==SQLITE_OK && pCheck->pGetNode==0 ){ + pCheck->pGetNode = rtreeCheckPrepare(pCheck, + "SELECT data FROM %Q.'%q_node' WHERE nodeno=?", + pCheck->zDb, pCheck->zTab + ); + } + + if( pCheck->rc==SQLITE_OK ){ + sqlite3_bind_int64(pCheck->pGetNode, 1, iNode); + if( sqlite3_step(pCheck->pGetNode)==SQLITE_ROW ){ + int nNode = sqlite3_column_bytes(pCheck->pGetNode, 0); + const u8 *pNode = (const u8*)sqlite3_column_blob(pCheck->pGetNode, 0); + pRet = sqlite3_malloc64(nNode); + if( pRet==0 ){ + pCheck->rc = SQLITE_NOMEM; + }else{ + memcpy(pRet, pNode, nNode); + *pnNode = nNode; + } + } + rtreeCheckReset(pCheck, pCheck->pGetNode); + if( pCheck->rc==SQLITE_OK && pRet==0 ){ + rtreeCheckAppendMsg(pCheck, "Node %lld missing from database", iNode); + } + } + + return pRet; +} + +/* +** This function is used to check that the %_parent (if bLeaf==0) or %_rowid +** (if bLeaf==1) table contains a specified entry. The schemas of the +** two tables are: +** +** CREATE TABLE %_parent(nodeno INTEGER PRIMARY KEY, parentnode INTEGER) +** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER, ...) +** +** In both cases, this function checks that there exists an entry with +** IPK value iKey and the second column set to iVal. +** +*/ +static void rtreeCheckMapping( + RtreeCheck *pCheck, /* RtreeCheck object */ + int bLeaf, /* True for a leaf cell, false for interior */ + i64 iKey, /* Key for mapping */ + i64 iVal /* Expected value for mapping */ +){ + int rc; + sqlite3_stmt *pStmt; + const char *azSql[2] = { + "SELECT parentnode FROM %Q.'%q_parent' WHERE nodeno=?1", + "SELECT nodeno FROM %Q.'%q_rowid' WHERE rowid=?1" + }; + + assert( bLeaf==0 || bLeaf==1 ); + if( pCheck->aCheckMapping[bLeaf]==0 ){ + pCheck->aCheckMapping[bLeaf] = rtreeCheckPrepare(pCheck, + azSql[bLeaf], pCheck->zDb, pCheck->zTab + ); + } + if( pCheck->rc!=SQLITE_OK ) return; + + pStmt = pCheck->aCheckMapping[bLeaf]; + sqlite3_bind_int64(pStmt, 1, iKey); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_DONE ){ + rtreeCheckAppendMsg(pCheck, "Mapping (%lld -> %lld) missing from %s table", + iKey, iVal, (bLeaf ? "%_rowid" : "%_parent") + ); + }else if( rc==SQLITE_ROW ){ + i64 ii = sqlite3_column_int64(pStmt, 0); + if( ii!=iVal ){ + rtreeCheckAppendMsg(pCheck, + "Found (%lld -> %lld) in %s table, expected (%lld -> %lld)", + iKey, ii, (bLeaf ? "%_rowid" : "%_parent"), iKey, iVal + ); + } + } + rtreeCheckReset(pCheck, pStmt); +} + +/* +** Argument pCell points to an array of coordinates stored on an rtree page. +** This function checks that the coordinates are internally consistent (no +** x1>x2 conditions) and adds an error message to the RtreeCheck object +** if they are not. +** +** Additionally, if pParent is not NULL, then it is assumed to point to +** the array of coordinates on the parent page that bound the page +** containing pCell. In this case it is also verified that the two +** sets of coordinates are mutually consistent and an error message added +** to the RtreeCheck object if they are not. +*/ +static void rtreeCheckCellCoord( + RtreeCheck *pCheck, + i64 iNode, /* Node id to use in error messages */ + int iCell, /* Cell number to use in error messages */ + u8 *pCell, /* Pointer to cell coordinates */ + u8 *pParent /* Pointer to parent coordinates */ +){ + RtreeCoord c1, c2; + RtreeCoord p1, p2; + int i; + + for(i=0; i<pCheck->nDim; i++){ + readCoord(&pCell[4*2*i], &c1); + readCoord(&pCell[4*(2*i + 1)], &c2); + + /* printf("%e, %e\n", c1.u.f, c2.u.f); */ + if( pCheck->bInt ? c1.i>c2.i : c1.f>c2.f ){ + rtreeCheckAppendMsg(pCheck, + "Dimension %d of cell %d on node %lld is corrupt", i, iCell, iNode + ); + } + + if( pParent ){ + readCoord(&pParent[4*2*i], &p1); + readCoord(&pParent[4*(2*i + 1)], &p2); + + if( (pCheck->bInt ? c1.i<p1.i : c1.f<p1.f) + || (pCheck->bInt ? c2.i>p2.i : c2.f>p2.f) + ){ + rtreeCheckAppendMsg(pCheck, + "Dimension %d of cell %d on node %lld is corrupt relative to parent" + , i, iCell, iNode + ); + } + } + } +} + +/* +** Run rtreecheck() checks on node iNode, which is at depth iDepth within +** the r-tree structure. Argument aParent points to the array of coordinates +** that bound node iNode on the parent node. +** +** If any problems are discovered, an error message is appended to the +** report accumulated in the RtreeCheck object. +*/ +static void rtreeCheckNode( + RtreeCheck *pCheck, + int iDepth, /* Depth of iNode (0==leaf) */ + u8 *aParent, /* Buffer containing parent coords */ + i64 iNode /* Node to check */ +){ + u8 *aNode = 0; + int nNode = 0; + + assert( iNode==1 || aParent!=0 ); + assert( pCheck->nDim>0 ); + + aNode = rtreeCheckGetNode(pCheck, iNode, &nNode); + if( aNode ){ + if( nNode<4 ){ + rtreeCheckAppendMsg(pCheck, + "Node %lld is too small (%d bytes)", iNode, nNode + ); + }else{ + int nCell; /* Number of cells on page */ + int i; /* Used to iterate through cells */ + if( aParent==0 ){ + iDepth = readInt16(aNode); + if( iDepth>RTREE_MAX_DEPTH ){ + rtreeCheckAppendMsg(pCheck, "Rtree depth out of range (%d)", iDepth); + sqlite3_free(aNode); + return; + } + } + nCell = readInt16(&aNode[2]); + if( (4 + nCell*(8 + pCheck->nDim*2*4))>nNode ){ + rtreeCheckAppendMsg(pCheck, + "Node %lld is too small for cell count of %d (%d bytes)", + iNode, nCell, nNode + ); + }else{ + for(i=0; i<nCell; i++){ + u8 *pCell = &aNode[4 + i*(8 + pCheck->nDim*2*4)]; + i64 iVal = readInt64(pCell); + rtreeCheckCellCoord(pCheck, iNode, i, &pCell[8], aParent); + + if( iDepth>0 ){ + rtreeCheckMapping(pCheck, 0, iVal, iNode); + rtreeCheckNode(pCheck, iDepth-1, &pCell[8], iVal); + pCheck->nNonLeaf++; + }else{ + rtreeCheckMapping(pCheck, 1, iVal, iNode); + pCheck->nLeaf++; + } + } + } + } + sqlite3_free(aNode); + } +} + +/* +** The second argument to this function must be either "_rowid" or +** "_parent". This function checks that the number of entries in the +** %_rowid or %_parent table is exactly nExpect. If not, it adds +** an error message to the report in the RtreeCheck object indicated +** by the first argument. +*/ +static void rtreeCheckCount(RtreeCheck *pCheck, const char *zTbl, i64 nExpect){ + if( pCheck->rc==SQLITE_OK ){ + sqlite3_stmt *pCount; + pCount = rtreeCheckPrepare(pCheck, "SELECT count(*) FROM %Q.'%q%s'", + pCheck->zDb, pCheck->zTab, zTbl + ); + if( pCount ){ + if( sqlite3_step(pCount)==SQLITE_ROW ){ + i64 nActual = sqlite3_column_int64(pCount, 0); + if( nActual!=nExpect ){ + rtreeCheckAppendMsg(pCheck, "Wrong number of entries in %%%s table" + " - expected %lld, actual %lld" , zTbl, nExpect, nActual + ); + } + } + pCheck->rc = sqlite3_finalize(pCount); + } + } +} + +/* +** This function does the bulk of the work for the rtree integrity-check. +** It is called by rtreecheck(), which is the SQL function implementation. +*/ +static int rtreeCheckTable( + sqlite3 *db, /* Database handle to access db through */ + const char *zDb, /* Name of db ("main", "temp" etc.) */ + const char *zTab, /* Name of rtree table to check */ + char **pzReport /* OUT: sqlite3_malloc'd report text */ +){ + RtreeCheck check; /* Common context for various routines */ + sqlite3_stmt *pStmt = 0; /* Used to find column count of rtree table */ + int bEnd = 0; /* True if transaction should be closed */ + int nAux = 0; /* Number of extra columns. */ + + /* Initialize the context object */ + memset(&check, 0, sizeof(check)); + check.db = db; + check.zDb = zDb; + check.zTab = zTab; + + /* If there is not already an open transaction, open one now. This is + ** to ensure that the queries run as part of this integrity-check operate + ** on a consistent snapshot. */ + if( sqlite3_get_autocommit(db) ){ + check.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0); + bEnd = 1; + } + + /* Find the number of auxiliary columns */ + if( check.rc==SQLITE_OK ){ + pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab); + if( pStmt ){ + nAux = sqlite3_column_count(pStmt) - 2; + sqlite3_finalize(pStmt); + }else + if( check.rc!=SQLITE_NOMEM ){ + check.rc = SQLITE_OK; + } + } + + /* Find number of dimensions in the rtree table. */ + pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.%Q", zDb, zTab); + if( pStmt ){ + int rc; + check.nDim = (sqlite3_column_count(pStmt) - 1 - nAux) / 2; + if( check.nDim<1 ){ + rtreeCheckAppendMsg(&check, "Schema corrupt or not an rtree"); + }else if( SQLITE_ROW==sqlite3_step(pStmt) ){ + check.bInt = (sqlite3_column_type(pStmt, 1)==SQLITE_INTEGER); + } + rc = sqlite3_finalize(pStmt); + if( rc!=SQLITE_CORRUPT ) check.rc = rc; + } + + /* Do the actual integrity-check */ + if( check.nDim>=1 ){ + if( check.rc==SQLITE_OK ){ + rtreeCheckNode(&check, 0, 0, 1); + } + rtreeCheckCount(&check, "_rowid", check.nLeaf); + rtreeCheckCount(&check, "_parent", check.nNonLeaf); + } + + /* Finalize SQL statements used by the integrity-check */ + sqlite3_finalize(check.pGetNode); + sqlite3_finalize(check.aCheckMapping[0]); + sqlite3_finalize(check.aCheckMapping[1]); + + /* If one was opened, close the transaction */ + if( bEnd ){ + int rc = sqlite3_exec(db, "END", 0, 0, 0); + if( check.rc==SQLITE_OK ) check.rc = rc; + } + *pzReport = check.zReport; + return check.rc; +} + +/* +** Implementation of the xIntegrity method for Rtree. +*/ +static int rtreeIntegrity(sqlite3_vtab *pVtab, char **pzErr){ + Rtree *pRtree = (Rtree*)pVtab; + int rc; + rc = rtreeCheckTable(pRtree->db, pRtree->zDb, pRtree->zName, pzErr); + if( rc==SQLITE_OK && *pzErr ){ + *pzErr = sqlite3_mprintf("In RTree %s.%s:\n%z", + pRtree->zDb, pRtree->zName, *pzErr); + } + return rc; +} + +/* +** Usage: +** +** rtreecheck(<rtree-table>); +** rtreecheck(<database>, <rtree-table>); +** +** Invoking this SQL function runs an integrity-check on the named rtree +** table. The integrity-check verifies the following: +** +** 1. For each cell in the r-tree structure (%_node table), that: +** +** a) for each dimension, (coord1 <= coord2). +** +** b) unless the cell is on the root node, that the cell is bounded +** by the parent cell on the parent node. +** +** c) for leaf nodes, that there is an entry in the %_rowid +** table corresponding to the cell's rowid value that +** points to the correct node. +** +** d) for cells on non-leaf nodes, that there is an entry in the +** %_parent table mapping from the cell's child node to the +** node that it resides on. +** +** 2. That there are the same number of entries in the %_rowid table +** as there are leaf cells in the r-tree structure, and that there +** is a leaf cell that corresponds to each entry in the %_rowid table. +** +** 3. That there are the same number of entries in the %_parent table +** as there are non-leaf cells in the r-tree structure, and that +** there is a non-leaf cell that corresponds to each entry in the +** %_parent table. +*/ +static void rtreecheck( + sqlite3_context *ctx, + int nArg, + sqlite3_value **apArg +){ + if( nArg!=1 && nArg!=2 ){ + sqlite3_result_error(ctx, + "wrong number of arguments to function rtreecheck()", -1 + ); + }else{ + int rc; + char *zReport = 0; + const char *zDb = (const char*)sqlite3_value_text(apArg[0]); + const char *zTab; + if( nArg==1 ){ + zTab = zDb; + zDb = "main"; + }else{ + zTab = (const char*)sqlite3_value_text(apArg[1]); + } + rc = rtreeCheckTable(sqlite3_context_db_handle(ctx), zDb, zTab, &zReport); + if( rc==SQLITE_OK ){ + sqlite3_result_text(ctx, zReport ? zReport : "ok", -1, SQLITE_TRANSIENT); + }else{ + sqlite3_result_error_code(ctx, rc); + } + sqlite3_free(zReport); + } +} + +/* Conditionally include the geopoly code */ +#ifdef SQLITE_ENABLE_GEOPOLY +/************** Include geopoly.c in the middle of rtree.c *******************/ +/************** Begin file geopoly.c *****************************************/ +/* +** 2018-05-25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file implements an alternative R-Tree virtual table that +** uses polygons to express the boundaries of 2-dimensional objects. +** +** This file is #include-ed onto the end of "rtree.c" so that it has +** access to all of the R-Tree internals. +*/ +/* #include <stdlib.h> */ + +/* Enable -DGEOPOLY_ENABLE_DEBUG for debugging facilities */ +#ifdef GEOPOLY_ENABLE_DEBUG + static int geo_debug = 0; +# define GEODEBUG(X) if(geo_debug)printf X +#else +# define GEODEBUG(X) +#endif + +/* Character class routines */ +#ifdef sqlite3Isdigit + /* Use the SQLite core versions if this routine is part of the + ** SQLite amalgamation */ +# define safe_isdigit(x) sqlite3Isdigit(x) +# define safe_isalnum(x) sqlite3Isalnum(x) +# define safe_isxdigit(x) sqlite3Isxdigit(x) +#else + /* Use the standard library for separate compilation */ +#include <ctype.h> /* amalgamator: keep */ +# define safe_isdigit(x) isdigit((unsigned char)(x)) +# define safe_isalnum(x) isalnum((unsigned char)(x)) +# define safe_isxdigit(x) isxdigit((unsigned char)(x)) +#endif + +#ifndef JSON_NULL /* The following stuff repeats things found in json1 */ +/* +** Growing our own isspace() routine this way is twice as fast as +** the library isspace() function. +*/ +static const char geopolyIsSpace[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +#define fast_isspace(x) (geopolyIsSpace[(unsigned char)x]) +#endif /* JSON NULL - back to original code */ + +/* Compiler and version */ +#ifndef GCC_VERSION +#if defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC) +# define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__) +#else +# define GCC_VERSION 0 +#endif +#endif +#ifndef MSVC_VERSION +#if defined(_MSC_VER) && !defined(SQLITE_DISABLE_INTRINSIC) +# define MSVC_VERSION _MSC_VER +#else +# define MSVC_VERSION 0 +#endif +#endif + +/* Datatype for coordinates +*/ +typedef float GeoCoord; + +/* +** Internal representation of a polygon. +** +** The polygon consists of a sequence of vertexes. There is a line +** segment between each pair of vertexes, and one final segment from +** the last vertex back to the first. (This differs from the GeoJSON +** standard in which the final vertex is a repeat of the first.) +** +** The polygon follows the right-hand rule. The area to the right of +** each segment is "outside" and the area to the left is "inside". +** +** The on-disk representation consists of a 4-byte header followed by +** the values. The 4-byte header is: +** +** encoding (1 byte) 0=big-endian, 1=little-endian +** nvertex (3 bytes) Number of vertexes as a big-endian integer +** +** Enough space is allocated for 4 coordinates, to work around over-zealous +** warnings coming from some compiler (notably, clang). In reality, the size +** of each GeoPoly memory allocate is adjusted as necessary so that the +** GeoPoly.a[] array at the end is the appropriate size. +*/ +typedef struct GeoPoly GeoPoly; +struct GeoPoly { + int nVertex; /* Number of vertexes */ + unsigned char hdr[4]; /* Header for on-disk representation */ + GeoCoord a[8]; /* 2*nVertex values. X (longitude) first, then Y */ +}; + +/* The size of a memory allocation needed for a GeoPoly object sufficient +** to hold N coordinate pairs. +*/ +#define GEOPOLY_SZ(N) (sizeof(GeoPoly) + sizeof(GeoCoord)*2*((N)-4)) + +/* Macros to access coordinates of a GeoPoly. +** We have to use these macros, rather than just say p->a[i] in order +** to silence (incorrect) UBSAN warnings if the array index is too large. +*/ +#define GeoX(P,I) (((GeoCoord*)(P)->a)[(I)*2]) +#define GeoY(P,I) (((GeoCoord*)(P)->a)[(I)*2+1]) + + +/* +** State of a parse of a GeoJSON input. +*/ +typedef struct GeoParse GeoParse; +struct GeoParse { + const unsigned char *z; /* Unparsed input */ + int nVertex; /* Number of vertexes in a[] */ + int nAlloc; /* Space allocated to a[] */ + int nErr; /* Number of errors encountered */ + GeoCoord *a; /* Array of vertexes. From sqlite3_malloc64() */ +}; + +/* Do a 4-byte byte swap */ +static void geopolySwab32(unsigned char *a){ + unsigned char t = a[0]; + a[0] = a[3]; + a[3] = t; + t = a[1]; + a[1] = a[2]; + a[2] = t; +} + +/* Skip whitespace. Return the next non-whitespace character. */ +static char geopolySkipSpace(GeoParse *p){ + while( fast_isspace(p->z[0]) ) p->z++; + return p->z[0]; +} + +/* Parse out a number. Write the value into *pVal if pVal!=0. +** return non-zero on success and zero if the next token is not a number. +*/ +static int geopolyParseNumber(GeoParse *p, GeoCoord *pVal){ + char c = geopolySkipSpace(p); + const unsigned char *z = p->z; + int j = 0; + int seenDP = 0; + int seenE = 0; + if( c=='-' ){ + j = 1; + c = z[j]; + } + if( c=='0' && z[j+1]>='0' && z[j+1]<='9' ) return 0; + for(;; j++){ + c = z[j]; + if( safe_isdigit(c) ) continue; + if( c=='.' ){ + if( z[j-1]=='-' ) return 0; + if( seenDP ) return 0; + seenDP = 1; + continue; + } + if( c=='e' || c=='E' ){ + if( z[j-1]<'0' ) return 0; + if( seenE ) return -1; + seenDP = seenE = 1; + c = z[j+1]; + if( c=='+' || c=='-' ){ + j++; + c = z[j+1]; + } + if( c<'0' || c>'9' ) return 0; + continue; + } + break; + } + if( z[j-1]<'0' ) return 0; + if( pVal ){ +#ifdef SQLITE_AMALGAMATION + /* The sqlite3AtoF() routine is much much faster than atof(), if it + ** is available */ + double r; + (void)sqlite3AtoF((const char*)p->z, &r, j, SQLITE_UTF8); + *pVal = r; +#else + *pVal = (GeoCoord)atof((const char*)p->z); +#endif + } + p->z += j; + return 1; +} + +/* +** If the input is a well-formed JSON array of coordinates with at least +** four coordinates and where each coordinate is itself a two-value array, +** then convert the JSON into a GeoPoly object and return a pointer to +** that object. +** +** If any error occurs, return NULL. +*/ +static GeoPoly *geopolyParseJson(const unsigned char *z, int *pRc){ + GeoParse s; + int rc = SQLITE_OK; + memset(&s, 0, sizeof(s)); + s.z = z; + if( geopolySkipSpace(&s)=='[' ){ + s.z++; + while( geopolySkipSpace(&s)=='[' ){ + int ii = 0; + char c; + s.z++; + if( s.nVertex>=s.nAlloc ){ + GeoCoord *aNew; + s.nAlloc = s.nAlloc*2 + 16; + aNew = sqlite3_realloc64(s.a, s.nAlloc*sizeof(GeoCoord)*2 ); + if( aNew==0 ){ + rc = SQLITE_NOMEM; + s.nErr++; + break; + } + s.a = aNew; + } + while( geopolyParseNumber(&s, ii<=1 ? &s.a[s.nVertex*2+ii] : 0) ){ + ii++; + if( ii==2 ) s.nVertex++; + c = geopolySkipSpace(&s); + s.z++; + if( c==',' ) continue; + if( c==']' && ii>=2 ) break; + s.nErr++; + rc = SQLITE_ERROR; + goto parse_json_err; + } + if( geopolySkipSpace(&s)==',' ){ + s.z++; + continue; + } + break; + } + if( geopolySkipSpace(&s)==']' + && s.nVertex>=4 + && s.a[0]==s.a[s.nVertex*2-2] + && s.a[1]==s.a[s.nVertex*2-1] + && (s.z++, geopolySkipSpace(&s)==0) + ){ + GeoPoly *pOut; + int x = 1; + s.nVertex--; /* Remove the redundant vertex at the end */ + pOut = sqlite3_malloc64( GEOPOLY_SZ((sqlite3_int64)s.nVertex) ); + x = 1; + if( pOut==0 ) goto parse_json_err; + pOut->nVertex = s.nVertex; + memcpy(pOut->a, s.a, s.nVertex*2*sizeof(GeoCoord)); + pOut->hdr[0] = *(unsigned char*)&x; + pOut->hdr[1] = (s.nVertex>>16)&0xff; + pOut->hdr[2] = (s.nVertex>>8)&0xff; + pOut->hdr[3] = s.nVertex&0xff; + sqlite3_free(s.a); + if( pRc ) *pRc = SQLITE_OK; + return pOut; + }else{ + s.nErr++; + rc = SQLITE_ERROR; + } + } +parse_json_err: + if( pRc ) *pRc = rc; + sqlite3_free(s.a); + return 0; +} + +/* +** Given a function parameter, try to interpret it as a polygon, either +** in the binary format or JSON text. Compute a GeoPoly object and +** return a pointer to that object. Or if the input is not a well-formed +** polygon, put an error message in sqlite3_context and return NULL. +*/ +static GeoPoly *geopolyFuncParam( + sqlite3_context *pCtx, /* Context for error messages */ + sqlite3_value *pVal, /* The value to decode */ + int *pRc /* Write error here */ +){ + GeoPoly *p = 0; + int nByte; + testcase( pCtx==0 ); + if( sqlite3_value_type(pVal)==SQLITE_BLOB + && (nByte = sqlite3_value_bytes(pVal))>=(int)(4+6*sizeof(GeoCoord)) + ){ + const unsigned char *a = sqlite3_value_blob(pVal); + int nVertex; + if( a==0 ){ + if( pCtx ) sqlite3_result_error_nomem(pCtx); + return 0; + } + nVertex = (a[1]<<16) + (a[2]<<8) + a[3]; + if( (a[0]==0 || a[0]==1) + && (nVertex*2*sizeof(GeoCoord) + 4)==(unsigned int)nByte + ){ + p = sqlite3_malloc64( sizeof(*p) + (nVertex-1)*2*sizeof(GeoCoord) ); + if( p==0 ){ + if( pRc ) *pRc = SQLITE_NOMEM; + if( pCtx ) sqlite3_result_error_nomem(pCtx); + }else{ + int x = 1; + p->nVertex = nVertex; + memcpy(p->hdr, a, nByte); + if( a[0] != *(unsigned char*)&x ){ + int ii; + for(ii=0; ii<nVertex; ii++){ + geopolySwab32((unsigned char*)&GeoX(p,ii)); + geopolySwab32((unsigned char*)&GeoY(p,ii)); + } + p->hdr[0] ^= 1; + } + } + } + if( pRc ) *pRc = SQLITE_OK; + return p; + }else if( sqlite3_value_type(pVal)==SQLITE_TEXT ){ + const unsigned char *zJson = sqlite3_value_text(pVal); + if( zJson==0 ){ + if( pRc ) *pRc = SQLITE_NOMEM; + return 0; + } + return geopolyParseJson(zJson, pRc); + }else{ + if( pRc ) *pRc = SQLITE_ERROR; + return 0; + } +} + +/* +** Implementation of the geopoly_blob(X) function. +** +** If the input is a well-formed Geopoly BLOB or JSON string +** then return the BLOB representation of the polygon. Otherwise +** return NULL. +*/ +static void geopolyBlobFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; + if( p ){ + sqlite3_result_blob(context, p->hdr, + 4+8*p->nVertex, SQLITE_TRANSIENT); + sqlite3_free(p); + } +} + +/* +** SQL function: geopoly_json(X) +** +** Interpret X as a polygon and render it as a JSON array +** of coordinates. Or, if X is not a valid polygon, return NULL. +*/ +static void geopolyJsonFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; + if( p ){ + sqlite3 *db = sqlite3_context_db_handle(context); + sqlite3_str *x = sqlite3_str_new(db); + int i; + sqlite3_str_append(x, "[", 1); + for(i=0; i<p->nVertex; i++){ + sqlite3_str_appendf(x, "[%!g,%!g],", GeoX(p,i), GeoY(p,i)); + } + sqlite3_str_appendf(x, "[%!g,%!g]]", GeoX(p,0), GeoY(p,0)); + sqlite3_result_text(context, sqlite3_str_finish(x), -1, sqlite3_free); + sqlite3_free(p); + } +} + +/* +** SQL function: geopoly_svg(X, ....) +** +** Interpret X as a polygon and render it as a SVG <polyline>. +** Additional arguments are added as attributes to the <polyline>. +*/ +static void geopolySvgFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GeoPoly *p; + if( argc<1 ) return; + p = geopolyFuncParam(context, argv[0], 0); + if( p ){ + sqlite3 *db = sqlite3_context_db_handle(context); + sqlite3_str *x = sqlite3_str_new(db); + int i; + char cSep = '\''; + sqlite3_str_appendf(x, "<polyline points="); + for(i=0; i<p->nVertex; i++){ + sqlite3_str_appendf(x, "%c%g,%g", cSep, GeoX(p,i), GeoY(p,i)); + cSep = ' '; + } + sqlite3_str_appendf(x, " %g,%g'", GeoX(p,0), GeoY(p,0)); + for(i=1; i<argc; i++){ + const char *z = (const char*)sqlite3_value_text(argv[i]); + if( z && z[0] ){ + sqlite3_str_appendf(x, " %s", z); + } + } + sqlite3_str_appendf(x, "></polyline>"); + sqlite3_result_text(context, sqlite3_str_finish(x), -1, sqlite3_free); + sqlite3_free(p); + } +} + +/* +** SQL Function: geopoly_xform(poly, A, B, C, D, E, F) +** +** Transform and/or translate a polygon as follows: +** +** x1 = A*x0 + B*y0 + E +** y1 = C*x0 + D*y0 + F +** +** For a translation: +** +** geopoly_xform(poly, 1, 0, 0, 1, x-offset, y-offset) +** +** Rotate by R around the point (0,0): +** +** geopoly_xform(poly, cos(R), sin(R), -sin(R), cos(R), 0, 0) +*/ +static void geopolyXformFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + double A = sqlite3_value_double(argv[1]); + double B = sqlite3_value_double(argv[2]); + double C = sqlite3_value_double(argv[3]); + double D = sqlite3_value_double(argv[4]); + double E = sqlite3_value_double(argv[5]); + double F = sqlite3_value_double(argv[6]); + GeoCoord x1, y1, x0, y0; + int ii; + (void)argc; + if( p ){ + for(ii=0; ii<p->nVertex; ii++){ + x0 = GeoX(p,ii); + y0 = GeoY(p,ii); + x1 = (GeoCoord)(A*x0 + B*y0 + E); + y1 = (GeoCoord)(C*x0 + D*y0 + F); + GeoX(p,ii) = x1; + GeoY(p,ii) = y1; + } + sqlite3_result_blob(context, p->hdr, + 4+8*p->nVertex, SQLITE_TRANSIENT); + sqlite3_free(p); + } +} + +/* +** Compute the area enclosed by the polygon. +** +** This routine can also be used to detect polygons that rotate in +** the wrong direction. Polygons are suppose to be counter-clockwise (CCW). +** This routine returns a negative value for clockwise (CW) polygons. +*/ +static double geopolyArea(GeoPoly *p){ + double rArea = 0.0; + int ii; + for(ii=0; ii<p->nVertex-1; ii++){ + rArea += (GeoX(p,ii) - GeoX(p,ii+1)) /* (x0 - x1) */ + * (GeoY(p,ii) + GeoY(p,ii+1)) /* (y0 + y1) */ + * 0.5; + } + rArea += (GeoX(p,ii) - GeoX(p,0)) /* (xN - x0) */ + * (GeoY(p,ii) + GeoY(p,0)) /* (yN + y0) */ + * 0.5; + return rArea; +} + +/* +** Implementation of the geopoly_area(X) function. +** +** If the input is a well-formed Geopoly BLOB then return the area +** enclosed by the polygon. If the polygon circulates clockwise instead +** of counterclockwise (as it should) then return the negative of the +** enclosed area. Otherwise return NULL. +*/ +static void geopolyAreaFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; + if( p ){ + sqlite3_result_double(context, geopolyArea(p)); + sqlite3_free(p); + } +} + +/* +** Implementation of the geopoly_ccw(X) function. +** +** If the rotation of polygon X is clockwise (incorrect) instead of +** counter-clockwise (the correct winding order according to RFC7946) +** then reverse the order of the vertexes in polygon X. +** +** In other words, this routine returns a CCW polygon regardless of the +** winding order of its input. +** +** Use this routine to sanitize historical inputs that that sometimes +** contain polygons that wind in the wrong direction. +*/ +static void geopolyCcwFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; + if( p ){ + if( geopolyArea(p)<0.0 ){ + int ii, jj; + for(ii=1, jj=p->nVertex-1; ii<jj; ii++, jj--){ + GeoCoord t = GeoX(p,ii); + GeoX(p,ii) = GeoX(p,jj); + GeoX(p,jj) = t; + t = GeoY(p,ii); + GeoY(p,ii) = GeoY(p,jj); + GeoY(p,jj) = t; + } + } + sqlite3_result_blob(context, p->hdr, + 4+8*p->nVertex, SQLITE_TRANSIENT); + sqlite3_free(p); + } +} + +#define GEOPOLY_PI 3.1415926535897932385 + +/* Fast approximation for sine(X) for X between -0.5*pi and 2*pi +*/ +static double geopolySine(double r){ + assert( r>=-0.5*GEOPOLY_PI && r<=2.0*GEOPOLY_PI ); + if( r>=1.5*GEOPOLY_PI ){ + r -= 2.0*GEOPOLY_PI; + } + if( r>=0.5*GEOPOLY_PI ){ + return -geopolySine(r-GEOPOLY_PI); + }else{ + double r2 = r*r; + double r3 = r2*r; + double r5 = r3*r2; + return 0.9996949*r - 0.1656700*r3 + 0.0075134*r5; + } +} + +/* +** Function: geopoly_regular(X,Y,R,N) +** +** Construct a simple, convex, regular polygon centered at X, Y +** with circumradius R and with N sides. +*/ +static void geopolyRegularFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + double x = sqlite3_value_double(argv[0]); + double y = sqlite3_value_double(argv[1]); + double r = sqlite3_value_double(argv[2]); + int n = sqlite3_value_int(argv[3]); + int i; + GeoPoly *p; + (void)argc; + + if( n<3 || r<=0.0 ) return; + if( n>1000 ) n = 1000; + p = sqlite3_malloc64( sizeof(*p) + (n-1)*2*sizeof(GeoCoord) ); + if( p==0 ){ + sqlite3_result_error_nomem(context); + return; + } + i = 1; + p->hdr[0] = *(unsigned char*)&i; + p->hdr[1] = 0; + p->hdr[2] = (n>>8)&0xff; + p->hdr[3] = n&0xff; + for(i=0; i<n; i++){ + double rAngle = 2.0*GEOPOLY_PI*i/n; + GeoX(p,i) = x - r*geopolySine(rAngle-0.5*GEOPOLY_PI); + GeoY(p,i) = y + r*geopolySine(rAngle); + } + sqlite3_result_blob(context, p->hdr, 4+8*n, SQLITE_TRANSIENT); + sqlite3_free(p); +} + +/* +** If pPoly is a polygon, compute its bounding box. Then: +** +** (1) if aCoord!=0 store the bounding box in aCoord, returning NULL +** (2) otherwise, compute a GeoPoly for the bounding box and return the +** new GeoPoly +** +** If pPoly is NULL but aCoord is not NULL, then compute a new GeoPoly from +** the bounding box in aCoord and return a pointer to that GeoPoly. +*/ +static GeoPoly *geopolyBBox( + sqlite3_context *context, /* For recording the error */ + sqlite3_value *pPoly, /* The polygon */ + RtreeCoord *aCoord, /* Results here */ + int *pRc /* Error code here */ +){ + GeoPoly *pOut = 0; + GeoPoly *p; + float mnX, mxX, mnY, mxY; + if( pPoly==0 && aCoord!=0 ){ + p = 0; + mnX = aCoord[0].f; + mxX = aCoord[1].f; + mnY = aCoord[2].f; + mxY = aCoord[3].f; + goto geopolyBboxFill; + }else{ + p = geopolyFuncParam(context, pPoly, pRc); + } + if( p ){ + int ii; + mnX = mxX = GeoX(p,0); + mnY = mxY = GeoY(p,0); + for(ii=1; ii<p->nVertex; ii++){ + double r = GeoX(p,ii); + if( r<mnX ) mnX = (float)r; + else if( r>mxX ) mxX = (float)r; + r = GeoY(p,ii); + if( r<mnY ) mnY = (float)r; + else if( r>mxY ) mxY = (float)r; + } + if( pRc ) *pRc = SQLITE_OK; + if( aCoord==0 ){ + geopolyBboxFill: + pOut = sqlite3_realloc64(p, GEOPOLY_SZ(4)); + if( pOut==0 ){ + sqlite3_free(p); + if( context ) sqlite3_result_error_nomem(context); + if( pRc ) *pRc = SQLITE_NOMEM; + return 0; + } + pOut->nVertex = 4; + ii = 1; + pOut->hdr[0] = *(unsigned char*)&ii; + pOut->hdr[1] = 0; + pOut->hdr[2] = 0; + pOut->hdr[3] = 4; + GeoX(pOut,0) = mnX; + GeoY(pOut,0) = mnY; + GeoX(pOut,1) = mxX; + GeoY(pOut,1) = mnY; + GeoX(pOut,2) = mxX; + GeoY(pOut,2) = mxY; + GeoX(pOut,3) = mnX; + GeoY(pOut,3) = mxY; + }else{ + sqlite3_free(p); + aCoord[0].f = mnX; + aCoord[1].f = mxX; + aCoord[2].f = mnY; + aCoord[3].f = mxY; + } + }else if( aCoord ){ + memset(aCoord, 0, sizeof(RtreeCoord)*4); + } + return pOut; +} + +/* +** Implementation of the geopoly_bbox(X) SQL function. +*/ +static void geopolyBBoxFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GeoPoly *p = geopolyBBox(context, argv[0], 0, 0); + (void)argc; + if( p ){ + sqlite3_result_blob(context, p->hdr, + 4+8*p->nVertex, SQLITE_TRANSIENT); + sqlite3_free(p); + } +} + +/* +** State vector for the geopoly_group_bbox() aggregate function. +*/ +typedef struct GeoBBox GeoBBox; +struct GeoBBox { + int isInit; + RtreeCoord a[4]; +}; + + +/* +** Implementation of the geopoly_group_bbox(X) aggregate SQL function. +*/ +static void geopolyBBoxStep( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + RtreeCoord a[4]; + int rc = SQLITE_OK; + (void)argc; + (void)geopolyBBox(context, argv[0], a, &rc); + if( rc==SQLITE_OK ){ + GeoBBox *pBBox; + pBBox = (GeoBBox*)sqlite3_aggregate_context(context, sizeof(*pBBox)); + if( pBBox==0 ) return; + if( pBBox->isInit==0 ){ + pBBox->isInit = 1; + memcpy(pBBox->a, a, sizeof(RtreeCoord)*4); + }else{ + if( a[0].f < pBBox->a[0].f ) pBBox->a[0] = a[0]; + if( a[1].f > pBBox->a[1].f ) pBBox->a[1] = a[1]; + if( a[2].f < pBBox->a[2].f ) pBBox->a[2] = a[2]; + if( a[3].f > pBBox->a[3].f ) pBBox->a[3] = a[3]; + } + } +} +static void geopolyBBoxFinal( + sqlite3_context *context +){ + GeoPoly *p; + GeoBBox *pBBox; + pBBox = (GeoBBox*)sqlite3_aggregate_context(context, 0); + if( pBBox==0 ) return; + p = geopolyBBox(context, 0, pBBox->a, 0); + if( p ){ + sqlite3_result_blob(context, p->hdr, + 4+8*p->nVertex, SQLITE_TRANSIENT); + sqlite3_free(p); + } +} + + +/* +** Determine if point (x0,y0) is beneath line segment (x1,y1)->(x2,y2). +** Returns: +** +** +2 x0,y0 is on the line segement +** +** +1 x0,y0 is beneath line segment +** +** 0 x0,y0 is not on or beneath the line segment or the line segment +** is vertical and x0,y0 is not on the line segment +** +** The left-most coordinate min(x1,x2) is not considered to be part of +** the line segment for the purposes of this analysis. +*/ +static int pointBeneathLine( + double x0, double y0, + double x1, double y1, + double x2, double y2 +){ + double y; + if( x0==x1 && y0==y1 ) return 2; + if( x1<x2 ){ + if( x0<=x1 || x0>x2 ) return 0; + }else if( x1>x2 ){ + if( x0<=x2 || x0>x1 ) return 0; + }else{ + /* Vertical line segment */ + if( x0!=x1 ) return 0; + if( y0<y1 && y0<y2 ) return 0; + if( y0>y1 && y0>y2 ) return 0; + return 2; + } + y = y1 + (y2-y1)*(x0-x1)/(x2-x1); + if( y0==y ) return 2; + if( y0<y ) return 1; + return 0; +} + +/* +** SQL function: geopoly_contains_point(P,X,Y) +** +** Return +2 if point X,Y is within polygon P. +** Return +1 if point X,Y is on the polygon boundary. +** Return 0 if point X,Y is outside the polygon +*/ +static void geopolyContainsPointFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); + double x0 = sqlite3_value_double(argv[1]); + double y0 = sqlite3_value_double(argv[2]); + int v = 0; + int cnt = 0; + int ii; + (void)argc; + + if( p1==0 ) return; + for(ii=0; ii<p1->nVertex-1; ii++){ + v = pointBeneathLine(x0,y0,GeoX(p1,ii), GeoY(p1,ii), + GeoX(p1,ii+1),GeoY(p1,ii+1)); + if( v==2 ) break; + cnt += v; + } + if( v!=2 ){ + v = pointBeneathLine(x0,y0,GeoX(p1,ii), GeoY(p1,ii), + GeoX(p1,0), GeoY(p1,0)); + } + if( v==2 ){ + sqlite3_result_int(context, 1); + }else if( ((v+cnt)&1)==0 ){ + sqlite3_result_int(context, 0); + }else{ + sqlite3_result_int(context, 2); + } + sqlite3_free(p1); +} + +/* Forward declaration */ +static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2); + +/* +** SQL function: geopoly_within(P1,P2) +** +** Return +2 if P1 and P2 are the same polygon +** Return +1 if P2 is contained within P1 +** Return 0 if any part of P2 is on the outside of P1 +** +*/ +static void geopolyWithinFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); + GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0); + (void)argc; + if( p1 && p2 ){ + int x = geopolyOverlap(p1, p2); + if( x<0 ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_result_int(context, x==2 ? 1 : x==4 ? 2 : 0); + } + } + sqlite3_free(p1); + sqlite3_free(p2); +} + +/* Objects used by the overlap algorihm. */ +typedef struct GeoEvent GeoEvent; +typedef struct GeoSegment GeoSegment; +typedef struct GeoOverlap GeoOverlap; +struct GeoEvent { + double x; /* X coordinate at which event occurs */ + int eType; /* 0 for ADD, 1 for REMOVE */ + GeoSegment *pSeg; /* The segment to be added or removed */ + GeoEvent *pNext; /* Next event in the sorted list */ +}; +struct GeoSegment { + double C, B; /* y = C*x + B */ + double y; /* Current y value */ + float y0; /* Initial y value */ + unsigned char side; /* 1 for p1, 2 for p2 */ + unsigned int idx; /* Which segment within the side */ + GeoSegment *pNext; /* Next segment in a list sorted by y */ +}; +struct GeoOverlap { + GeoEvent *aEvent; /* Array of all events */ + GeoSegment *aSegment; /* Array of all segments */ + int nEvent; /* Number of events */ + int nSegment; /* Number of segments */ +}; + +/* +** Add a single segment and its associated events. +*/ +static void geopolyAddOneSegment( + GeoOverlap *p, + GeoCoord x0, + GeoCoord y0, + GeoCoord x1, + GeoCoord y1, + unsigned char side, + unsigned int idx +){ + GeoSegment *pSeg; + GeoEvent *pEvent; + if( x0==x1 ) return; /* Ignore vertical segments */ + if( x0>x1 ){ + GeoCoord t = x0; + x0 = x1; + x1 = t; + t = y0; + y0 = y1; + y1 = t; + } + pSeg = p->aSegment + p->nSegment; + p->nSegment++; + pSeg->C = (y1-y0)/(x1-x0); + pSeg->B = y1 - x1*pSeg->C; + pSeg->y0 = y0; + pSeg->side = side; + pSeg->idx = idx; + pEvent = p->aEvent + p->nEvent; + p->nEvent++; + pEvent->x = x0; + pEvent->eType = 0; + pEvent->pSeg = pSeg; + pEvent = p->aEvent + p->nEvent; + p->nEvent++; + pEvent->x = x1; + pEvent->eType = 1; + pEvent->pSeg = pSeg; +} + + + +/* +** Insert all segments and events for polygon pPoly. +*/ +static void geopolyAddSegments( + GeoOverlap *p, /* Add segments to this Overlap object */ + GeoPoly *pPoly, /* Take all segments from this polygon */ + unsigned char side /* The side of pPoly */ +){ + unsigned int i; + GeoCoord *x; + for(i=0; i<(unsigned)pPoly->nVertex-1; i++){ + x = &GeoX(pPoly,i); + geopolyAddOneSegment(p, x[0], x[1], x[2], x[3], side, i); + } + x = &GeoX(pPoly,i); + geopolyAddOneSegment(p, x[0], x[1], pPoly->a[0], pPoly->a[1], side, i); +} + +/* +** Merge two lists of sorted events by X coordinate +*/ +static GeoEvent *geopolyEventMerge(GeoEvent *pLeft, GeoEvent *pRight){ + GeoEvent head, *pLast; + head.pNext = 0; + pLast = &head; + while( pRight && pLeft ){ + if( pRight->x <= pLeft->x ){ + pLast->pNext = pRight; + pLast = pRight; + pRight = pRight->pNext; + }else{ + pLast->pNext = pLeft; + pLast = pLeft; + pLeft = pLeft->pNext; + } + } + pLast->pNext = pRight ? pRight : pLeft; + return head.pNext; +} + +/* +** Sort an array of nEvent event objects into a list. +*/ +static GeoEvent *geopolySortEventsByX(GeoEvent *aEvent, int nEvent){ + int mx = 0; + int i, j; + GeoEvent *p; + GeoEvent *a[50]; + for(i=0; i<nEvent; i++){ + p = &aEvent[i]; + p->pNext = 0; + for(j=0; j<mx && a[j]; j++){ + p = geopolyEventMerge(a[j], p); + a[j] = 0; + } + a[j] = p; + if( j>=mx ) mx = j+1; + } + p = 0; + for(i=0; i<mx; i++){ + p = geopolyEventMerge(a[i], p); + } + return p; +} + +/* +** Merge two lists of sorted segments by Y, and then by C. +*/ +static GeoSegment *geopolySegmentMerge(GeoSegment *pLeft, GeoSegment *pRight){ + GeoSegment head, *pLast; + head.pNext = 0; + pLast = &head; + while( pRight && pLeft ){ + double r = pRight->y - pLeft->y; + if( r==0.0 ) r = pRight->C - pLeft->C; + if( r<0.0 ){ + pLast->pNext = pRight; + pLast = pRight; + pRight = pRight->pNext; + }else{ + pLast->pNext = pLeft; + pLast = pLeft; + pLeft = pLeft->pNext; + } + } + pLast->pNext = pRight ? pRight : pLeft; + return head.pNext; +} + +/* +** Sort a list of GeoSegments in order of increasing Y and in the event of +** a tie, increasing C (slope). +*/ +static GeoSegment *geopolySortSegmentsByYAndC(GeoSegment *pList){ + int mx = 0; + int i; + GeoSegment *p; + GeoSegment *a[50]; + while( pList ){ + p = pList; + pList = pList->pNext; + p->pNext = 0; + for(i=0; i<mx && a[i]; i++){ + p = geopolySegmentMerge(a[i], p); + a[i] = 0; + } + a[i] = p; + if( i>=mx ) mx = i+1; + } + p = 0; + for(i=0; i<mx; i++){ + p = geopolySegmentMerge(a[i], p); + } + return p; +} + +/* +** Determine the overlap between two polygons +*/ +static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2){ + sqlite3_int64 nVertex = p1->nVertex + p2->nVertex + 2; + GeoOverlap *p; + sqlite3_int64 nByte; + GeoEvent *pThisEvent; + double rX; + int rc = 0; + int needSort = 0; + GeoSegment *pActive = 0; + GeoSegment *pSeg; + unsigned char aOverlap[4]; + + nByte = sizeof(GeoEvent)*nVertex*2 + + sizeof(GeoSegment)*nVertex + + sizeof(GeoOverlap); + p = sqlite3_malloc64( nByte ); + if( p==0 ) return -1; + p->aEvent = (GeoEvent*)&p[1]; + p->aSegment = (GeoSegment*)&p->aEvent[nVertex*2]; + p->nEvent = p->nSegment = 0; + geopolyAddSegments(p, p1, 1); + geopolyAddSegments(p, p2, 2); + pThisEvent = geopolySortEventsByX(p->aEvent, p->nEvent); + rX = pThisEvent && pThisEvent->x==0.0 ? -1.0 : 0.0; + memset(aOverlap, 0, sizeof(aOverlap)); + while( pThisEvent ){ + if( pThisEvent->x!=rX ){ + GeoSegment *pPrev = 0; + int iMask = 0; + GEODEBUG(("Distinct X: %g\n", pThisEvent->x)); + rX = pThisEvent->x; + if( needSort ){ + GEODEBUG(("SORT\n")); + pActive = geopolySortSegmentsByYAndC(pActive); + needSort = 0; + } + for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){ + if( pPrev ){ + if( pPrev->y!=pSeg->y ){ + GEODEBUG(("MASK: %d\n", iMask)); + aOverlap[iMask] = 1; + } + } + iMask ^= pSeg->side; + pPrev = pSeg; + } + pPrev = 0; + for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){ + double y = pSeg->C*rX + pSeg->B; + GEODEBUG(("Segment %d.%d %g->%g\n", pSeg->side, pSeg->idx, pSeg->y, y)); + pSeg->y = y; + if( pPrev ){ + if( pPrev->y>pSeg->y && pPrev->side!=pSeg->side ){ + rc = 1; + GEODEBUG(("Crossing: %d.%d and %d.%d\n", + pPrev->side, pPrev->idx, + pSeg->side, pSeg->idx)); + goto geopolyOverlapDone; + }else if( pPrev->y!=pSeg->y ){ + GEODEBUG(("MASK: %d\n", iMask)); + aOverlap[iMask] = 1; + } + } + iMask ^= pSeg->side; + pPrev = pSeg; + } + } + GEODEBUG(("%s %d.%d C=%g B=%g\n", + pThisEvent->eType ? "RM " : "ADD", + pThisEvent->pSeg->side, pThisEvent->pSeg->idx, + pThisEvent->pSeg->C, + pThisEvent->pSeg->B)); + if( pThisEvent->eType==0 ){ + /* Add a segment */ + pSeg = pThisEvent->pSeg; + pSeg->y = pSeg->y0; + pSeg->pNext = pActive; + pActive = pSeg; + needSort = 1; + }else{ + /* Remove a segment */ + if( pActive==pThisEvent->pSeg ){ + pActive = ALWAYS(pActive) ? pActive->pNext : 0; + }else{ + for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){ + if( pSeg->pNext==pThisEvent->pSeg ){ + pSeg->pNext = ALWAYS(pSeg->pNext) ? pSeg->pNext->pNext : 0; + break; + } + } + } + } + pThisEvent = pThisEvent->pNext; + } + if( aOverlap[3]==0 ){ + rc = 0; + }else if( aOverlap[1]!=0 && aOverlap[2]==0 ){ + rc = 3; + }else if( aOverlap[1]==0 && aOverlap[2]!=0 ){ + rc = 2; + }else if( aOverlap[1]==0 && aOverlap[2]==0 ){ + rc = 4; + }else{ + rc = 1; + } + +geopolyOverlapDone: + sqlite3_free(p); + return rc; +} + +/* +** SQL function: geopoly_overlap(P1,P2) +** +** Determine whether or not P1 and P2 overlap. Return value: +** +** 0 The two polygons are disjoint +** 1 They overlap +** 2 P1 is completely contained within P2 +** 3 P2 is completely contained within P1 +** 4 P1 and P2 are the same polygon +** NULL Either P1 or P2 or both are not valid polygons +*/ +static void geopolyOverlapFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); + GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0); + (void)argc; + if( p1 && p2 ){ + int x = geopolyOverlap(p1, p2); + if( x<0 ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_result_int(context, x); + } + } + sqlite3_free(p1); + sqlite3_free(p2); +} + +/* +** Enable or disable debugging output +*/ +static void geopolyDebugFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + (void)context; + (void)argc; +#ifdef GEOPOLY_ENABLE_DEBUG + geo_debug = sqlite3_value_int(argv[0]); +#else + (void)argv; +#endif +} + +/* +** This function is the implementation of both the xConnect and xCreate +** methods of the geopoly virtual table. +** +** argv[0] -> module name +** argv[1] -> database name +** argv[2] -> table name +** argv[...] -> column names... +*/ +static int geopolyInit( + sqlite3 *db, /* Database connection */ + void *pAux, /* One of the RTREE_COORD_* constants */ + int argc, const char *const*argv, /* Parameters to CREATE TABLE statement */ + sqlite3_vtab **ppVtab, /* OUT: New virtual table */ + char **pzErr, /* OUT: Error message, if any */ + int isCreate /* True for xCreate, false for xConnect */ +){ + int rc = SQLITE_OK; + Rtree *pRtree; + sqlite3_int64 nDb; /* Length of string argv[1] */ + sqlite3_int64 nName; /* Length of string argv[2] */ + sqlite3_str *pSql; + char *zSql; + int ii; + (void)pAux; + + sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); + + /* Allocate the sqlite3_vtab structure */ + nDb = strlen(argv[1]); + nName = strlen(argv[2]); + pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2); + if( !pRtree ){ + return SQLITE_NOMEM; + } + memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2); + pRtree->nBusy = 1; + pRtree->base.pModule = &rtreeModule; + pRtree->zDb = (char *)&pRtree[1]; + pRtree->zName = &pRtree->zDb[nDb+1]; + pRtree->eCoordType = RTREE_COORD_REAL32; + pRtree->nDim = 2; + pRtree->nDim2 = 4; + memcpy(pRtree->zDb, argv[1], nDb); + memcpy(pRtree->zName, argv[2], nName); + + + /* Create/Connect to the underlying relational database schema. If + ** that is successful, call sqlite3_declare_vtab() to configure + ** the r-tree table schema. + */ + pSql = sqlite3_str_new(db); + sqlite3_str_appendf(pSql, "CREATE TABLE x(_shape"); + pRtree->nAux = 1; /* Add one for _shape */ + pRtree->nAuxNotNull = 1; /* The _shape column is always not-null */ + for(ii=3; ii<argc; ii++){ + pRtree->nAux++; + sqlite3_str_appendf(pSql, ",%s", argv[ii]); + } + sqlite3_str_appendf(pSql, ");"); + zSql = sqlite3_str_finish(pSql); + if( !zSql ){ + rc = SQLITE_NOMEM; + }else if( SQLITE_OK!=(rc = sqlite3_declare_vtab(db, zSql)) ){ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + } + sqlite3_free(zSql); + if( rc ) goto geopolyInit_fail; + pRtree->nBytesPerCell = 8 + pRtree->nDim2*4; + + /* Figure out the node size to use. */ + rc = getNodeSize(db, pRtree, isCreate, pzErr); + if( rc ) goto geopolyInit_fail; + rc = rtreeSqlInit(pRtree, db, argv[1], argv[2], isCreate); + if( rc ){ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + goto geopolyInit_fail; + } + + *ppVtab = (sqlite3_vtab *)pRtree; + return SQLITE_OK; + +geopolyInit_fail: + if( rc==SQLITE_OK ) rc = SQLITE_ERROR; + assert( *ppVtab==0 ); + assert( pRtree->nBusy==1 ); + rtreeRelease(pRtree); + return rc; +} + + +/* +** GEOPOLY virtual table module xCreate method. +*/ +static int geopolyCreate( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + return geopolyInit(db, pAux, argc, argv, ppVtab, pzErr, 1); +} + +/* +** GEOPOLY virtual table module xConnect method. +*/ +static int geopolyConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + return geopolyInit(db, pAux, argc, argv, ppVtab, pzErr, 0); +} + + +/* +** GEOPOLY virtual table module xFilter method. +** +** Query plans: +** +** 1 rowid lookup +** 2 search for objects overlapping the same bounding box +** that contains polygon argv[0] +** 3 search for objects overlapping the same bounding box +** that contains polygon argv[0] +** 4 full table scan +*/ +static int geopolyFilter( + sqlite3_vtab_cursor *pVtabCursor, /* The cursor to initialize */ + int idxNum, /* Query plan */ + const char *idxStr, /* Not Used */ + int argc, sqlite3_value **argv /* Parameters to the query plan */ +){ + Rtree *pRtree = (Rtree *)pVtabCursor->pVtab; + RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor; + RtreeNode *pRoot = 0; + int rc = SQLITE_OK; + int iCell = 0; + (void)idxStr; + + rtreeReference(pRtree); + + /* Reset the cursor to the same state as rtreeOpen() leaves it in. */ + resetCursor(pCsr); + + pCsr->iStrategy = idxNum; + if( idxNum==1 ){ + /* Special case - lookup by rowid. */ + RtreeNode *pLeaf; /* Leaf on which the required cell resides */ + RtreeSearchPoint *p; /* Search point for the leaf */ + i64 iRowid = sqlite3_value_int64(argv[0]); + i64 iNode = 0; + rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); + if( rc==SQLITE_OK && pLeaf!=0 ){ + p = rtreeSearchPointNew(pCsr, RTREE_ZERO, 0); + assert( p!=0 ); /* Always returns pCsr->sPoint */ + pCsr->aNode[0] = pLeaf; + p->id = iNode; + p->eWithin = PARTLY_WITHIN; + rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &iCell); + p->iCell = (u8)iCell; + RTREE_QUEUE_TRACE(pCsr, "PUSH-F1:"); + }else{ + pCsr->atEOF = 1; + } + }else{ + /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array + ** with the configured constraints. + */ + rc = nodeAcquire(pRtree, 1, 0, &pRoot); + if( rc==SQLITE_OK && idxNum<=3 ){ + RtreeCoord bbox[4]; + RtreeConstraint *p; + assert( argc==1 ); + assert( argv[0]!=0 ); + geopolyBBox(0, argv[0], bbox, &rc); + if( rc ){ + goto geopoly_filter_end; + } + pCsr->aConstraint = p = sqlite3_malloc(sizeof(RtreeConstraint)*4); + pCsr->nConstraint = 4; + if( p==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*4); + memset(pCsr->anQueue, 0, sizeof(u32)*(pRtree->iDepth + 1)); + if( idxNum==2 ){ + /* Overlap query */ + p->op = 'B'; + p->iCoord = 0; + p->u.rValue = bbox[1].f; + p++; + p->op = 'D'; + p->iCoord = 1; + p->u.rValue = bbox[0].f; + p++; + p->op = 'B'; + p->iCoord = 2; + p->u.rValue = bbox[3].f; + p++; + p->op = 'D'; + p->iCoord = 3; + p->u.rValue = bbox[2].f; + }else{ + /* Within query */ + p->op = 'D'; + p->iCoord = 0; + p->u.rValue = bbox[0].f; + p++; + p->op = 'B'; + p->iCoord = 1; + p->u.rValue = bbox[1].f; + p++; + p->op = 'D'; + p->iCoord = 2; + p->u.rValue = bbox[2].f; + p++; + p->op = 'B'; + p->iCoord = 3; + p->u.rValue = bbox[3].f; + } + } + } + if( rc==SQLITE_OK ){ + RtreeSearchPoint *pNew; + pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1)); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + goto geopoly_filter_end; + } + pNew->id = 1; + pNew->iCell = 0; + pNew->eWithin = PARTLY_WITHIN; + assert( pCsr->bPoint==1 ); + pCsr->aNode[0] = pRoot; + pRoot = 0; + RTREE_QUEUE_TRACE(pCsr, "PUSH-Fm:"); + rc = rtreeStepToLeaf(pCsr); + } + } + +geopoly_filter_end: + nodeRelease(pRtree, pRoot); + rtreeRelease(pRtree); + return rc; +} + +/* +** Rtree virtual table module xBestIndex method. There are three +** table scan strategies to choose from (in order from most to +** least desirable): +** +** idxNum idxStr Strategy +** ------------------------------------------------ +** 1 "rowid" Direct lookup by rowid. +** 2 "rtree" R-tree overlap query using geopoly_overlap() +** 3 "rtree" R-tree within query using geopoly_within() +** 4 "fullscan" full-table scan. +** ------------------------------------------------ +*/ +static int geopolyBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + int ii; + int iRowidTerm = -1; + int iFuncTerm = -1; + int idxNum = 0; + (void)tab; + + for(ii=0; ii<pIdxInfo->nConstraint; ii++){ + struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; + if( !p->usable ) continue; + if( p->iColumn<0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + iRowidTerm = ii; + break; + } + if( p->iColumn==0 && p->op>=SQLITE_INDEX_CONSTRAINT_FUNCTION ){ + /* p->op==SQLITE_INDEX_CONSTRAINT_FUNCTION for geopoly_overlap() + ** p->op==(SQLITE_INDEX_CONTRAINT_FUNCTION+1) for geopoly_within(). + ** See geopolyFindFunction() */ + iFuncTerm = ii; + idxNum = p->op - SQLITE_INDEX_CONSTRAINT_FUNCTION + 2; + } + } + + if( iRowidTerm>=0 ){ + pIdxInfo->idxNum = 1; + pIdxInfo->idxStr = "rowid"; + pIdxInfo->aConstraintUsage[iRowidTerm].argvIndex = 1; + pIdxInfo->aConstraintUsage[iRowidTerm].omit = 1; + pIdxInfo->estimatedCost = 30.0; + pIdxInfo->estimatedRows = 1; + pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE; + return SQLITE_OK; + } + if( iFuncTerm>=0 ){ + pIdxInfo->idxNum = idxNum; + pIdxInfo->idxStr = "rtree"; + pIdxInfo->aConstraintUsage[iFuncTerm].argvIndex = 1; + pIdxInfo->aConstraintUsage[iFuncTerm].omit = 0; + pIdxInfo->estimatedCost = 300.0; + pIdxInfo->estimatedRows = 10; + return SQLITE_OK; + } + pIdxInfo->idxNum = 4; + pIdxInfo->idxStr = "fullscan"; + pIdxInfo->estimatedCost = 3000000.0; + pIdxInfo->estimatedRows = 100000; + return SQLITE_OK; +} + + +/* +** GEOPOLY virtual table module xColumn method. +*/ +static int geopolyColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ + Rtree *pRtree = (Rtree *)cur->pVtab; + RtreeCursor *pCsr = (RtreeCursor *)cur; + RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr); + int rc = SQLITE_OK; + RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); + + if( rc ) return rc; + if( p==0 ) return SQLITE_OK; + if( i==0 && sqlite3_vtab_nochange(ctx) ) return SQLITE_OK; + if( i<=pRtree->nAux ){ + if( !pCsr->bAuxValid ){ + if( pCsr->pReadAux==0 ){ + rc = sqlite3_prepare_v3(pRtree->db, pRtree->zReadAuxSql, -1, 0, + &pCsr->pReadAux, 0); + if( rc ) return rc; + } + sqlite3_bind_int64(pCsr->pReadAux, 1, + nodeGetRowid(pRtree, pNode, p->iCell)); + rc = sqlite3_step(pCsr->pReadAux); + if( rc==SQLITE_ROW ){ + pCsr->bAuxValid = 1; + }else{ + sqlite3_reset(pCsr->pReadAux); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + return rc; + } + } + sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pReadAux, i+2)); + } + return SQLITE_OK; +} + + +/* +** The xUpdate method for GEOPOLY module virtual tables. +** +** For DELETE: +** +** argv[0] = the rowid to be deleted +** +** For INSERT: +** +** argv[0] = SQL NULL +** argv[1] = rowid to insert, or an SQL NULL to select automatically +** argv[2] = _shape column +** argv[3] = first application-defined column.... +** +** For UPDATE: +** +** argv[0] = rowid to modify. Never NULL +** argv[1] = rowid after the change. Never NULL +** argv[2] = new value for _shape +** argv[3] = new value for first application-defined column.... +*/ +static int geopolyUpdate( + sqlite3_vtab *pVtab, + int nData, + sqlite3_value **aData, + sqlite_int64 *pRowid +){ + Rtree *pRtree = (Rtree *)pVtab; + int rc = SQLITE_OK; + RtreeCell cell; /* New cell to insert if nData>1 */ + i64 oldRowid; /* The old rowid */ + int oldRowidValid; /* True if oldRowid is valid */ + i64 newRowid; /* The new rowid */ + int newRowidValid; /* True if newRowid is valid */ + int coordChange = 0; /* Change in coordinates */ + + if( pRtree->nNodeRef ){ + /* Unable to write to the btree while another cursor is reading from it, + ** since the write might do a rebalance which would disrupt the read + ** cursor. */ + return SQLITE_LOCKED_VTAB; + } + rtreeReference(pRtree); + assert(nData>=1); + + oldRowidValid = sqlite3_value_type(aData[0])!=SQLITE_NULL;; + oldRowid = oldRowidValid ? sqlite3_value_int64(aData[0]) : 0; + newRowidValid = nData>1 && sqlite3_value_type(aData[1])!=SQLITE_NULL; + newRowid = newRowidValid ? sqlite3_value_int64(aData[1]) : 0; + cell.iRowid = newRowid; + + if( nData>1 /* not a DELETE */ + && (!oldRowidValid /* INSERT */ + || !sqlite3_value_nochange(aData[2]) /* UPDATE _shape */ + || oldRowid!=newRowid) /* Rowid change */ + ){ + assert( aData[2]!=0 ); + geopolyBBox(0, aData[2], cell.aCoord, &rc); + if( rc ){ + if( rc==SQLITE_ERROR ){ + pVtab->zErrMsg = + sqlite3_mprintf("_shape does not contain a valid polygon"); + } + goto geopoly_update_end; + } + coordChange = 1; + + /* If a rowid value was supplied, check if it is already present in + ** the table. If so, the constraint has failed. */ + if( newRowidValid && (!oldRowidValid || oldRowid!=newRowid) ){ + int steprc; + sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid); + steprc = sqlite3_step(pRtree->pReadRowid); + rc = sqlite3_reset(pRtree->pReadRowid); + if( SQLITE_ROW==steprc ){ + if( sqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){ + rc = rtreeDeleteRowid(pRtree, cell.iRowid); + }else{ + rc = rtreeConstraintError(pRtree, 0); + } + } + } + } + + /* If aData[0] is not an SQL NULL value, it is the rowid of a + ** record to delete from the r-tree table. The following block does + ** just that. + */ + if( rc==SQLITE_OK && (nData==1 || (coordChange && oldRowidValid)) ){ + rc = rtreeDeleteRowid(pRtree, oldRowid); + } + + /* If the aData[] array contains more than one element, elements + ** (aData[2]..aData[argc-1]) contain a new record to insert into + ** the r-tree structure. + */ + if( rc==SQLITE_OK && nData>1 && coordChange ){ + /* Insert the new record into the r-tree */ + RtreeNode *pLeaf = 0; + if( !newRowidValid ){ + rc = rtreeNewRowid(pRtree, &cell.iRowid); + } + *pRowid = cell.iRowid; + if( rc==SQLITE_OK ){ + rc = ChooseLeaf(pRtree, &cell, 0, &pLeaf); + } + if( rc==SQLITE_OK ){ + int rc2; + pRtree->iReinsertHeight = -1; + rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0); + rc2 = nodeRelease(pRtree, pLeaf); + if( rc==SQLITE_OK ){ + rc = rc2; + } + } + } + + /* Change the data */ + if( rc==SQLITE_OK && nData>1 ){ + sqlite3_stmt *pUp = pRtree->pWriteAux; + int jj; + int nChange = 0; + sqlite3_bind_int64(pUp, 1, cell.iRowid); + assert( pRtree->nAux>=1 ); + if( sqlite3_value_nochange(aData[2]) ){ + sqlite3_bind_null(pUp, 2); + }else{ + GeoPoly *p = 0; + if( sqlite3_value_type(aData[2])==SQLITE_TEXT + && (p = geopolyFuncParam(0, aData[2], &rc))!=0 + && rc==SQLITE_OK + ){ + sqlite3_bind_blob(pUp, 2, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); + }else{ + sqlite3_bind_value(pUp, 2, aData[2]); + } + sqlite3_free(p); + nChange = 1; + } + for(jj=1; jj<nData-2; jj++){ + nChange++; + sqlite3_bind_value(pUp, jj+2, aData[jj+2]); + } + if( nChange ){ + sqlite3_step(pUp); + rc = sqlite3_reset(pUp); + } + } + +geopoly_update_end: + rtreeRelease(pRtree); + return rc; +} + +/* +** Report that geopoly_overlap() is an overloaded function suitable +** for use in xBestIndex. +*/ +static int geopolyFindFunction( + sqlite3_vtab *pVtab, + int nArg, + const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg +){ + (void)pVtab; + (void)nArg; + if( sqlite3_stricmp(zName, "geopoly_overlap")==0 ){ + *pxFunc = geopolyOverlapFunc; + *ppArg = 0; + return SQLITE_INDEX_CONSTRAINT_FUNCTION; + } + if( sqlite3_stricmp(zName, "geopoly_within")==0 ){ + *pxFunc = geopolyWithinFunc; + *ppArg = 0; + return SQLITE_INDEX_CONSTRAINT_FUNCTION+1; + } + return 0; +} + + +static sqlite3_module geopolyModule = { + 3, /* iVersion */ + geopolyCreate, /* xCreate - create a table */ + geopolyConnect, /* xConnect - connect to an existing table */ + geopolyBestIndex, /* xBestIndex - Determine search strategy */ + rtreeDisconnect, /* xDisconnect - Disconnect from a table */ + rtreeDestroy, /* xDestroy - Drop a table */ + rtreeOpen, /* xOpen - open a cursor */ + rtreeClose, /* xClose - close a cursor */ + geopolyFilter, /* xFilter - configure scan constraints */ + rtreeNext, /* xNext - advance a cursor */ + rtreeEof, /* xEof */ + geopolyColumn, /* xColumn - read data */ + rtreeRowid, /* xRowid - read data */ + geopolyUpdate, /* xUpdate - write data */ + rtreeBeginTransaction, /* xBegin - begin transaction */ + rtreeEndTransaction, /* xSync - sync transaction */ + rtreeEndTransaction, /* xCommit - commit transaction */ + rtreeEndTransaction, /* xRollback - rollback transaction */ + geopolyFindFunction, /* xFindFunction - function overloading */ + rtreeRename, /* xRename - rename the table */ + rtreeSavepoint, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + rtreeShadowName /* xShadowName */ +}; + +static int sqlite3_geopoly_init(sqlite3 *db){ + int rc = SQLITE_OK; + static const struct { + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + signed char nArg; + unsigned char bPure; + const char *zName; + } aFunc[] = { + { geopolyAreaFunc, 1, 1, "geopoly_area" }, + { geopolyBlobFunc, 1, 1, "geopoly_blob" }, + { geopolyJsonFunc, 1, 1, "geopoly_json" }, + { geopolySvgFunc, -1, 1, "geopoly_svg" }, + { geopolyWithinFunc, 2, 1, "geopoly_within" }, + { geopolyContainsPointFunc, 3, 1, "geopoly_contains_point" }, + { geopolyOverlapFunc, 2, 1, "geopoly_overlap" }, + { geopolyDebugFunc, 1, 0, "geopoly_debug" }, + { geopolyBBoxFunc, 1, 1, "geopoly_bbox" }, + { geopolyXformFunc, 7, 1, "geopoly_xform" }, + { geopolyRegularFunc, 4, 1, "geopoly_regular" }, + { geopolyCcwFunc, 1, 1, "geopoly_ccw" }, + }; + static const struct { + void (*xStep)(sqlite3_context*,int,sqlite3_value**); + void (*xFinal)(sqlite3_context*); + const char *zName; + } aAgg[] = { + { geopolyBBoxStep, geopolyBBoxFinal, "geopoly_group_bbox" }, + }; + unsigned int i; + for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){ + int enc; + if( aFunc[i].bPure ){ + enc = SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS; + }else{ + enc = SQLITE_UTF8|SQLITE_DIRECTONLY; + } + rc = sqlite3_create_function(db, aFunc[i].zName, aFunc[i].nArg, + enc, 0, + aFunc[i].xFunc, 0, 0); + } + for(i=0; i<sizeof(aAgg)/sizeof(aAgg[0]) && rc==SQLITE_OK; i++){ + rc = sqlite3_create_function(db, aAgg[i].zName, 1, + SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, 0, + 0, aAgg[i].xStep, aAgg[i].xFinal); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_module_v2(db, "geopoly", &geopolyModule, 0, 0); + } + return rc; +} + +/************** End of geopoly.c *********************************************/ +/************** Continuing where we left off in rtree.c **********************/ +#endif + +/* +** Register the r-tree module with database handle db. This creates the +** virtual table module "rtree" and the debugging/analysis scalar +** function "rtreenode". +*/ +SQLITE_PRIVATE int sqlite3RtreeInit(sqlite3 *db){ + const int utf8 = SQLITE_UTF8; + int rc; + + rc = sqlite3_create_function(db, "rtreenode", 2, utf8, 0, rtreenode, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "rtreedepth", 1, utf8, 0,rtreedepth, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "rtreecheck", -1, utf8, 0,rtreecheck, 0,0); + } + if( rc==SQLITE_OK ){ +#ifdef SQLITE_RTREE_INT_ONLY + void *c = (void *)RTREE_COORD_INT32; +#else + void *c = (void *)RTREE_COORD_REAL32; +#endif + rc = sqlite3_create_module_v2(db, "rtree", &rtreeModule, c, 0); + } + if( rc==SQLITE_OK ){ + void *c = (void *)RTREE_COORD_INT32; + rc = sqlite3_create_module_v2(db, "rtree_i32", &rtreeModule, c, 0); + } +#ifdef SQLITE_ENABLE_GEOPOLY + if( rc==SQLITE_OK ){ + rc = sqlite3_geopoly_init(db); + } +#endif + + return rc; +} + +/* +** This routine deletes the RtreeGeomCallback object that was attached +** one of the SQL functions create by sqlite3_rtree_geometry_callback() +** or sqlite3_rtree_query_callback(). In other words, this routine is the +** destructor for an RtreeGeomCallback objecct. This routine is called when +** the corresponding SQL function is deleted. +*/ +static void rtreeFreeCallback(void *p){ + RtreeGeomCallback *pInfo = (RtreeGeomCallback*)p; + if( pInfo->xDestructor ) pInfo->xDestructor(pInfo->pContext); + sqlite3_free(p); +} + +/* +** This routine frees the BLOB that is returned by geomCallback(). +*/ +static void rtreeMatchArgFree(void *pArg){ + int i; + RtreeMatchArg *p = (RtreeMatchArg*)pArg; + for(i=0; i<p->nParam; i++){ + sqlite3_value_free(p->apSqlParam[i]); + } + sqlite3_free(p); +} + +/* +** Each call to sqlite3_rtree_geometry_callback() or +** sqlite3_rtree_query_callback() creates an ordinary SQLite +** scalar function that is implemented by this routine. +** +** All this function does is construct an RtreeMatchArg object that +** contains the geometry-checking callback routines and a list of +** parameters to this function, then return that RtreeMatchArg object +** as a BLOB. +** +** The R-Tree MATCH operator will read the returned BLOB, deserialize +** the RtreeMatchArg object, and use the RtreeMatchArg object to figure +** out which elements of the R-Tree should be returned by the query. +*/ +static void geomCallback(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){ + RtreeGeomCallback *pGeomCtx = (RtreeGeomCallback *)sqlite3_user_data(ctx); + RtreeMatchArg *pBlob; + sqlite3_int64 nBlob; + int memErr = 0; + + nBlob = sizeof(RtreeMatchArg) + (nArg-1)*sizeof(RtreeDValue) + + nArg*sizeof(sqlite3_value*); + pBlob = (RtreeMatchArg *)sqlite3_malloc64(nBlob); + if( !pBlob ){ + sqlite3_result_error_nomem(ctx); + }else{ + int i; + pBlob->iSize = nBlob; + pBlob->cb = pGeomCtx[0]; + pBlob->apSqlParam = (sqlite3_value**)&pBlob->aParam[nArg]; + pBlob->nParam = nArg; + for(i=0; i<nArg; i++){ + pBlob->apSqlParam[i] = sqlite3_value_dup(aArg[i]); + if( pBlob->apSqlParam[i]==0 ) memErr = 1; +#ifdef SQLITE_RTREE_INT_ONLY + pBlob->aParam[i] = sqlite3_value_int64(aArg[i]); +#else + pBlob->aParam[i] = sqlite3_value_double(aArg[i]); +#endif + } + if( memErr ){ + sqlite3_result_error_nomem(ctx); + rtreeMatchArgFree(pBlob); + }else{ + sqlite3_result_pointer(ctx, pBlob, "RtreeMatchArg", rtreeMatchArgFree); + } + } +} + +/* +** Register a new geometry function for use with the r-tree MATCH operator. +*/ +SQLITE_API int sqlite3_rtree_geometry_callback( + sqlite3 *db, /* Register SQL function on this connection */ + const char *zGeom, /* Name of the new SQL function */ + int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*), /* Callback */ + void *pContext /* Extra data associated with the callback */ +){ + RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */ + + /* Allocate and populate the context object. */ + pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback)); + if( !pGeomCtx ) return SQLITE_NOMEM; + pGeomCtx->xGeom = xGeom; + pGeomCtx->xQueryFunc = 0; + pGeomCtx->xDestructor = 0; + pGeomCtx->pContext = pContext; + return sqlite3_create_function_v2(db, zGeom, -1, SQLITE_ANY, + (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback + ); +} + +/* +** Register a new 2nd-generation geometry function for use with the +** r-tree MATCH operator. +*/ +SQLITE_API int sqlite3_rtree_query_callback( + sqlite3 *db, /* Register SQL function on this connection */ + const char *zQueryFunc, /* Name of new SQL function */ + int (*xQueryFunc)(sqlite3_rtree_query_info*), /* Callback */ + void *pContext, /* Extra data passed into the callback */ + void (*xDestructor)(void*) /* Destructor for the extra data */ +){ + RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */ + + /* Allocate and populate the context object. */ + pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback)); + if( !pGeomCtx ){ + if( xDestructor ) xDestructor(pContext); + return SQLITE_NOMEM; + } + pGeomCtx->xGeom = 0; + pGeomCtx->xQueryFunc = xQueryFunc; + pGeomCtx->xDestructor = xDestructor; + pGeomCtx->pContext = pContext; + return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY, + (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback + ); +} + +#if !SQLITE_CORE +#ifdef _WIN32 +__declspec(dllexport) +#endif +SQLITE_API int sqlite3_rtree_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi) + return sqlite3RtreeInit(db); +} +#endif + +#endif + +/************** End of rtree.c ***********************************************/ +/************** Begin file icu.c *********************************************/ +/* +** 2007 May 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** $Id: icu.c,v 1.7 2007/12/13 21:54:11 drh Exp $ +** +** This file implements an integration between the ICU library +** ("International Components for Unicode", an open-source library +** for handling unicode data) and SQLite. The integration uses +** ICU to provide the following to SQLite: +** +** * An implementation of the SQL regexp() function (and hence REGEXP +** operator) using the ICU uregex_XX() APIs. +** +** * Implementations of the SQL scalar upper() and lower() functions +** for case mapping. +** +** * Integration of ICU and SQLite collation sequences. +** +** * An implementation of the LIKE operator that uses ICU to +** provide case-independent matching. +*/ + +#if !defined(SQLITE_CORE) \ + || defined(SQLITE_ENABLE_ICU) \ + || defined(SQLITE_ENABLE_ICU_COLLATIONS) + +/* Include ICU headers */ +#include <unicode/utypes.h> +#include <unicode/uregex.h> +#include <unicode/ustring.h> +#include <unicode/ucol.h> + +/* #include <assert.h> */ + +#ifndef SQLITE_CORE +/* #include "sqlite3ext.h" */ + SQLITE_EXTENSION_INIT1 +#else +/* #include "sqlite3.h" */ +#endif + +/* +** This function is called when an ICU function called from within +** the implementation of an SQL scalar function returns an error. +** +** The scalar function context passed as the first argument is +** loaded with an error message based on the following two args. +*/ +static void icuFunctionError( + sqlite3_context *pCtx, /* SQLite scalar function context */ + const char *zName, /* Name of ICU function that failed */ + UErrorCode e /* Error code returned by ICU function */ +){ + char zBuf[128]; + sqlite3_snprintf(128, zBuf, "ICU error: %s(): %s", zName, u_errorName(e)); + zBuf[127] = '\0'; + sqlite3_result_error(pCtx, zBuf, -1); +} + +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) + +/* +** Maximum length (in bytes) of the pattern in a LIKE or GLOB +** operator. +*/ +#ifndef SQLITE_MAX_LIKE_PATTERN_LENGTH +# define SQLITE_MAX_LIKE_PATTERN_LENGTH 50000 +#endif + +/* +** Version of sqlite3_free() that is always a function, never a macro. +*/ +static void xFree(void *p){ + sqlite3_free(p); +} + +/* +** This lookup table is used to help decode the first byte of +** a multi-byte UTF8 character. It is copied here from SQLite source +** code file utf8.c. +*/ +static const unsigned char icuUtf8Trans1[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, +}; + +#define SQLITE_ICU_READ_UTF8(zIn, c) \ + c = *(zIn++); \ + if( c>=0xc0 ){ \ + c = icuUtf8Trans1[c-0xc0]; \ + while( (*zIn & 0xc0)==0x80 ){ \ + c = (c<<6) + (0x3f & *(zIn++)); \ + } \ + } + +#define SQLITE_ICU_SKIP_UTF8(zIn) \ + assert( *zIn ); \ + if( *(zIn++)>=0xc0 ){ \ + while( (*zIn & 0xc0)==0x80 ){zIn++;} \ + } + + +/* +** Compare two UTF-8 strings for equality where the first string is +** a "LIKE" expression. Return true (1) if they are the same and +** false (0) if they are different. +*/ +static int icuLikeCompare( + const uint8_t *zPattern, /* LIKE pattern */ + const uint8_t *zString, /* The UTF-8 string to compare against */ + const UChar32 uEsc /* The escape character */ +){ + static const uint32_t MATCH_ONE = (uint32_t)'_'; + static const uint32_t MATCH_ALL = (uint32_t)'%'; + + int prevEscape = 0; /* True if the previous character was uEsc */ + + while( 1 ){ + + /* Read (and consume) the next character from the input pattern. */ + uint32_t uPattern; + SQLITE_ICU_READ_UTF8(zPattern, uPattern); + if( uPattern==0 ) break; + + /* There are now 4 possibilities: + ** + ** 1. uPattern is an unescaped match-all character "%", + ** 2. uPattern is an unescaped match-one character "_", + ** 3. uPattern is an unescaped escape character, or + ** 4. uPattern is to be handled as an ordinary character + */ + if( uPattern==MATCH_ALL && !prevEscape && uPattern!=(uint32_t)uEsc ){ + /* Case 1. */ + uint8_t c; + + /* Skip any MATCH_ALL or MATCH_ONE characters that follow a + ** MATCH_ALL. For each MATCH_ONE, skip one character in the + ** test string. + */ + while( (c=*zPattern) == MATCH_ALL || c == MATCH_ONE ){ + if( c==MATCH_ONE ){ + if( *zString==0 ) return 0; + SQLITE_ICU_SKIP_UTF8(zString); + } + zPattern++; + } + + if( *zPattern==0 ) return 1; + + while( *zString ){ + if( icuLikeCompare(zPattern, zString, uEsc) ){ + return 1; + } + SQLITE_ICU_SKIP_UTF8(zString); + } + return 0; + + }else if( uPattern==MATCH_ONE && !prevEscape && uPattern!=(uint32_t)uEsc ){ + /* Case 2. */ + if( *zString==0 ) return 0; + SQLITE_ICU_SKIP_UTF8(zString); + + }else if( uPattern==(uint32_t)uEsc && !prevEscape ){ + /* Case 3. */ + prevEscape = 1; + + }else{ + /* Case 4. */ + uint32_t uString; + SQLITE_ICU_READ_UTF8(zString, uString); + uString = (uint32_t)u_foldCase((UChar32)uString, U_FOLD_CASE_DEFAULT); + uPattern = (uint32_t)u_foldCase((UChar32)uPattern, U_FOLD_CASE_DEFAULT); + if( uString!=uPattern ){ + return 0; + } + prevEscape = 0; + } + } + + return *zString==0; +} + +/* +** Implementation of the like() SQL function. This function implements +** the build-in LIKE operator. The first argument to the function is the +** pattern and the second argument is the string. So, the SQL statements: +** +** A LIKE B +** +** is implemented as like(B, A). If there is an escape character E, +** +** A LIKE B ESCAPE E +** +** is mapped to like(B, A, E). +*/ +static void icuLikeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *zA = sqlite3_value_text(argv[0]); + const unsigned char *zB = sqlite3_value_text(argv[1]); + UChar32 uEsc = 0; + + /* Limit the length of the LIKE or GLOB pattern to avoid problems + ** of deep recursion and N*N behavior in patternCompare(). + */ + if( sqlite3_value_bytes(argv[0])>SQLITE_MAX_LIKE_PATTERN_LENGTH ){ + sqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1); + return; + } + + + if( argc==3 ){ + /* The escape character string must consist of a single UTF-8 character. + ** Otherwise, return an error. + */ + int nE= sqlite3_value_bytes(argv[2]); + const unsigned char *zE = sqlite3_value_text(argv[2]); + int i = 0; + if( zE==0 ) return; + U8_NEXT(zE, i, nE, uEsc); + if( i!=nE){ + sqlite3_result_error(context, + "ESCAPE expression must be a single character", -1); + return; + } + } + + if( zA && zB ){ + sqlite3_result_int(context, icuLikeCompare(zA, zB, uEsc)); + } +} + +/* +** Function to delete compiled regexp objects. Registered as +** a destructor function with sqlite3_set_auxdata(). +*/ +static void icuRegexpDelete(void *p){ + URegularExpression *pExpr = (URegularExpression *)p; + uregex_close(pExpr); +} + +/* +** Implementation of SQLite REGEXP operator. This scalar function takes +** two arguments. The first is a regular expression pattern to compile +** the second is a string to match against that pattern. If either +** argument is an SQL NULL, then NULL Is returned. Otherwise, the result +** is 1 if the string matches the pattern, or 0 otherwise. +** +** SQLite maps the regexp() function to the regexp() operator such +** that the following two are equivalent: +** +** zString REGEXP zPattern +** regexp(zPattern, zString) +** +** Uses the following ICU regexp APIs: +** +** uregex_open() +** uregex_matches() +** uregex_close() +*/ +static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){ + UErrorCode status = U_ZERO_ERROR; + URegularExpression *pExpr; + UBool res; + const UChar *zString = sqlite3_value_text16(apArg[1]); + + (void)nArg; /* Unused parameter */ + + /* If the left hand side of the regexp operator is NULL, + ** then the result is also NULL. + */ + if( !zString ){ + return; + } + + pExpr = sqlite3_get_auxdata(p, 0); + if( !pExpr ){ + const UChar *zPattern = sqlite3_value_text16(apArg[0]); + if( !zPattern ){ + return; + } + pExpr = uregex_open(zPattern, -1, 0, 0, &status); + + if( U_SUCCESS(status) ){ + sqlite3_set_auxdata(p, 0, pExpr, icuRegexpDelete); + pExpr = sqlite3_get_auxdata(p, 0); + } + if( !pExpr ){ + icuFunctionError(p, "uregex_open", status); + return; + } + } + + /* Configure the text that the regular expression operates on. */ + uregex_setText(pExpr, zString, -1, &status); + if( !U_SUCCESS(status) ){ + icuFunctionError(p, "uregex_setText", status); + return; + } + + /* Attempt the match */ + res = uregex_matches(pExpr, 0, &status); + if( !U_SUCCESS(status) ){ + icuFunctionError(p, "uregex_matches", status); + return; + } + + /* Set the text that the regular expression operates on to a NULL + ** pointer. This is not really necessary, but it is tidier than + ** leaving the regular expression object configured with an invalid + ** pointer after this function returns. + */ + uregex_setText(pExpr, 0, 0, &status); + + /* Return 1 or 0. */ + sqlite3_result_int(p, res ? 1 : 0); +} + +/* +** Implementations of scalar functions for case mapping - upper() and +** lower(). Function upper() converts its input to upper-case (ABC). +** Function lower() converts to lower-case (abc). +** +** ICU provides two types of case mapping, "general" case mapping and +** "language specific". Refer to ICU documentation for the differences +** between the two. +** +** To utilise "general" case mapping, the upper() or lower() scalar +** functions are invoked with one argument: +** +** upper('ABC') -> 'abc' +** lower('abc') -> 'ABC' +** +** To access ICU "language specific" case mapping, upper() or lower() +** should be invoked with two arguments. The second argument is the name +** of the locale to use. Passing an empty string ("") or SQL NULL value +** as the second argument is the same as invoking the 1 argument version +** of upper() or lower(). +** +** lower('I', 'en_us') -> 'i' +** lower('I', 'tr_tr') -> '\u131' (small dotless i) +** +** http://www.icu-project.org/userguide/posix.html#case_mappings +*/ +static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ + const UChar *zInput; /* Pointer to input string */ + UChar *zOutput = 0; /* Pointer to output buffer */ + int nInput; /* Size of utf-16 input string in bytes */ + int nOut; /* Size of output buffer in bytes */ + int cnt; + int bToUpper; /* True for toupper(), false for tolower() */ + UErrorCode status; + const char *zLocale = 0; + + assert(nArg==1 || nArg==2); + bToUpper = (sqlite3_user_data(p)!=0); + if( nArg==2 ){ + zLocale = (const char *)sqlite3_value_text(apArg[1]); + } + + zInput = sqlite3_value_text16(apArg[0]); + if( !zInput ){ + return; + } + nOut = nInput = sqlite3_value_bytes16(apArg[0]); + if( nOut==0 ){ + sqlite3_result_text16(p, "", 0, SQLITE_STATIC); + return; + } + + for(cnt=0; cnt<2; cnt++){ + UChar *zNew = sqlite3_realloc(zOutput, nOut); + if( zNew==0 ){ + sqlite3_free(zOutput); + sqlite3_result_error_nomem(p); + return; + } + zOutput = zNew; + status = U_ZERO_ERROR; + if( bToUpper ){ + nOut = 2*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); + }else{ + nOut = 2*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); + } + + if( U_SUCCESS(status) ){ + sqlite3_result_text16(p, zOutput, nOut, xFree); + }else if( status==U_BUFFER_OVERFLOW_ERROR ){ + assert( cnt==0 ); + continue; + }else{ + icuFunctionError(p, bToUpper ? "u_strToUpper" : "u_strToLower", status); + } + return; + } + assert( 0 ); /* Unreachable */ +} + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) */ + +/* +** Collation sequence destructor function. The pCtx argument points to +** a UCollator structure previously allocated using ucol_open(). +*/ +static void icuCollationDel(void *pCtx){ + UCollator *p = (UCollator *)pCtx; + ucol_close(p); +} + +/* +** Collation sequence comparison function. The pCtx argument points to +** a UCollator structure previously allocated using ucol_open(). +*/ +static int icuCollationColl( + void *pCtx, + int nLeft, + const void *zLeft, + int nRight, + const void *zRight +){ + UCollationResult res; + UCollator *p = (UCollator *)pCtx; + res = ucol_strcoll(p, (UChar *)zLeft, nLeft/2, (UChar *)zRight, nRight/2); + switch( res ){ + case UCOL_LESS: return -1; + case UCOL_GREATER: return +1; + case UCOL_EQUAL: return 0; + } + assert(!"Unexpected return value from ucol_strcoll()"); + return 0; +} + +/* +** Implementation of the scalar function icu_load_collation(). +** +** This scalar function is used to add ICU collation based collation +** types to an SQLite database connection. It is intended to be called +** as follows: +** +** SELECT icu_load_collation(<locale>, <collation-name>); +** +** Where <locale> is a string containing an ICU locale identifier (i.e. +** "en_AU", "tr_TR" etc.) and <collation-name> is the name of the +** collation sequence to create. +*/ +static void icuLoadCollation( + sqlite3_context *p, + int nArg, + sqlite3_value **apArg +){ + sqlite3 *db = (sqlite3 *)sqlite3_user_data(p); + UErrorCode status = U_ZERO_ERROR; + const char *zLocale; /* Locale identifier - (eg. "jp_JP") */ + const char *zName; /* SQL Collation sequence name (eg. "japanese") */ + UCollator *pUCollator; /* ICU library collation object */ + int rc; /* Return code from sqlite3_create_collation_x() */ + + assert(nArg==2); + (void)nArg; /* Unused parameter */ + zLocale = (const char *)sqlite3_value_text(apArg[0]); + zName = (const char *)sqlite3_value_text(apArg[1]); + + if( !zLocale || !zName ){ + return; + } + + pUCollator = ucol_open(zLocale, &status); + if( !U_SUCCESS(status) ){ + icuFunctionError(p, "ucol_open", status); + return; + } + assert(p); + + rc = sqlite3_create_collation_v2(db, zName, SQLITE_UTF16, (void *)pUCollator, + icuCollationColl, icuCollationDel + ); + if( rc!=SQLITE_OK ){ + ucol_close(pUCollator); + sqlite3_result_error(p, "Error registering collation function", -1); + } +} + +/* +** Register the ICU extension functions with database db. +*/ +SQLITE_PRIVATE int sqlite3IcuInit(sqlite3 *db){ +# define SQLITEICU_EXTRAFLAGS (SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS) + static const struct IcuScalar { + const char *zName; /* Function name */ + unsigned char nArg; /* Number of arguments */ + unsigned int enc; /* Optimal text encoding */ + unsigned char iContext; /* sqlite3_user_data() context */ + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } scalars[] = { + {"icu_load_collation",2,SQLITE_UTF8|SQLITE_DIRECTONLY,1, icuLoadCollation}, +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) + {"regexp", 2, SQLITE_ANY|SQLITEICU_EXTRAFLAGS, 0, icuRegexpFunc}, + {"lower", 1, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16}, + {"lower", 2, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16}, + {"upper", 1, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16}, + {"upper", 2, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16}, + {"lower", 1, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16}, + {"lower", 2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16}, + {"upper", 1, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16}, + {"upper", 2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16}, + {"like", 2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuLikeFunc}, + {"like", 3, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuLikeFunc}, +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) */ + }; + int rc = SQLITE_OK; + int i; + + for(i=0; rc==SQLITE_OK && i<(int)(sizeof(scalars)/sizeof(scalars[0])); i++){ + const struct IcuScalar *p = &scalars[i]; + rc = sqlite3_create_function( + db, p->zName, p->nArg, p->enc, + p->iContext ? (void*)db : (void*)0, + p->xFunc, 0, 0 + ); + } + + return rc; +} + +#if !SQLITE_CORE +#ifdef _WIN32 +__declspec(dllexport) +#endif +SQLITE_API int sqlite3_icu_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi) + return sqlite3IcuInit(db); +} +#endif + +#endif + +/************** End of icu.c *************************************************/ +/************** Begin file fts3_icu.c ****************************************/ +/* +** 2007 June 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file implements a tokenizer for fts3 based on the ICU library. +*/ +/* #include "fts3Int.h" */ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) +#ifdef SQLITE_ENABLE_ICU + +/* #include <assert.h> */ +/* #include <string.h> */ +/* #include "fts3_tokenizer.h" */ + +#include <unicode/ubrk.h> +/* #include <unicode/ucol.h> */ +/* #include <unicode/ustring.h> */ +#include <unicode/utf16.h> + +typedef struct IcuTokenizer IcuTokenizer; +typedef struct IcuCursor IcuCursor; + +struct IcuTokenizer { + sqlite3_tokenizer base; + char *zLocale; +}; + +struct IcuCursor { + sqlite3_tokenizer_cursor base; + + UBreakIterator *pIter; /* ICU break-iterator object */ + int nChar; /* Number of UChar elements in pInput */ + UChar *aChar; /* Copy of input using utf-16 encoding */ + int *aOffset; /* Offsets of each character in utf-8 input */ + + int nBuffer; + char *zBuffer; + + int iToken; +}; + +/* +** Create a new tokenizer instance. +*/ +static int icuCreate( + int argc, /* Number of entries in argv[] */ + const char * const *argv, /* Tokenizer creation arguments */ + sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ +){ + IcuTokenizer *p; + int n = 0; + + if( argc>0 ){ + n = strlen(argv[0])+1; + } + p = (IcuTokenizer *)sqlite3_malloc64(sizeof(IcuTokenizer)+n); + if( !p ){ + return SQLITE_NOMEM; + } + memset(p, 0, sizeof(IcuTokenizer)); + + if( n ){ + p->zLocale = (char *)&p[1]; + memcpy(p->zLocale, argv[0], n); + } + + *ppTokenizer = (sqlite3_tokenizer *)p; + + return SQLITE_OK; +} + +/* +** Destroy a tokenizer +*/ +static int icuDestroy(sqlite3_tokenizer *pTokenizer){ + IcuTokenizer *p = (IcuTokenizer *)pTokenizer; + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Prepare to begin tokenizing a particular string. The input +** string to be tokenized is pInput[0..nBytes-1]. A cursor +** used to incrementally tokenize this string is returned in +** *ppCursor. +*/ +static int icuOpen( + sqlite3_tokenizer *pTokenizer, /* The tokenizer */ + const char *zInput, /* Input string */ + int nInput, /* Length of zInput in bytes */ + sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ +){ + IcuTokenizer *p = (IcuTokenizer *)pTokenizer; + IcuCursor *pCsr; + + const int32_t opt = U_FOLD_CASE_DEFAULT; + UErrorCode status = U_ZERO_ERROR; + int nChar; + + UChar32 c; + int iInput = 0; + int iOut = 0; + + *ppCursor = 0; + + if( zInput==0 ){ + nInput = 0; + zInput = ""; + }else if( nInput<0 ){ + nInput = strlen(zInput); + } + nChar = nInput+1; + pCsr = (IcuCursor *)sqlite3_malloc64( + sizeof(IcuCursor) + /* IcuCursor */ + ((nChar+3)&~3) * sizeof(UChar) + /* IcuCursor.aChar[] */ + (nChar+1) * sizeof(int) /* IcuCursor.aOffset[] */ + ); + if( !pCsr ){ + return SQLITE_NOMEM; + } + memset(pCsr, 0, sizeof(IcuCursor)); + pCsr->aChar = (UChar *)&pCsr[1]; + pCsr->aOffset = (int *)&pCsr->aChar[(nChar+3)&~3]; + + pCsr->aOffset[iOut] = iInput; + U8_NEXT(zInput, iInput, nInput, c); + while( c>0 ){ + int isError = 0; + c = u_foldCase(c, opt); + U16_APPEND(pCsr->aChar, iOut, nChar, c, isError); + if( isError ){ + sqlite3_free(pCsr); + return SQLITE_ERROR; + } + pCsr->aOffset[iOut] = iInput; + + if( iInput<nInput ){ + U8_NEXT(zInput, iInput, nInput, c); + }else{ + c = 0; + } + } + + pCsr->pIter = ubrk_open(UBRK_WORD, p->zLocale, pCsr->aChar, iOut, &status); + if( !U_SUCCESS(status) ){ + sqlite3_free(pCsr); + return SQLITE_ERROR; + } + pCsr->nChar = iOut; + + ubrk_first(pCsr->pIter); + *ppCursor = (sqlite3_tokenizer_cursor *)pCsr; + return SQLITE_OK; +} + +/* +** Close a tokenization cursor previously opened by a call to icuOpen(). +*/ +static int icuClose(sqlite3_tokenizer_cursor *pCursor){ + IcuCursor *pCsr = (IcuCursor *)pCursor; + ubrk_close(pCsr->pIter); + sqlite3_free(pCsr->zBuffer); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** Extract the next token from a tokenization cursor. +*/ +static int icuNext( + sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */ + const char **ppToken, /* OUT: *ppToken is the token text */ + int *pnBytes, /* OUT: Number of bytes in token */ + int *piStartOffset, /* OUT: Starting offset of token */ + int *piEndOffset, /* OUT: Ending offset of token */ + int *piPosition /* OUT: Position integer of token */ +){ + IcuCursor *pCsr = (IcuCursor *)pCursor; + + int iStart = 0; + int iEnd = 0; + int nByte = 0; + + while( iStart==iEnd ){ + UChar32 c; + + iStart = ubrk_current(pCsr->pIter); + iEnd = ubrk_next(pCsr->pIter); + if( iEnd==UBRK_DONE ){ + return SQLITE_DONE; + } + + while( iStart<iEnd ){ + int iWhite = iStart; + U16_NEXT(pCsr->aChar, iWhite, pCsr->nChar, c); + if( u_isspace(c) ){ + iStart = iWhite; + }else{ + break; + } + } + assert(iStart<=iEnd); + } + + do { + UErrorCode status = U_ZERO_ERROR; + if( nByte ){ + char *zNew = sqlite3_realloc(pCsr->zBuffer, nByte); + if( !zNew ){ + return SQLITE_NOMEM; + } + pCsr->zBuffer = zNew; + pCsr->nBuffer = nByte; + } + + u_strToUTF8( + pCsr->zBuffer, pCsr->nBuffer, &nByte, /* Output vars */ + &pCsr->aChar[iStart], iEnd-iStart, /* Input vars */ + &status /* Output success/failure */ + ); + } while( nByte>pCsr->nBuffer ); + + *ppToken = pCsr->zBuffer; + *pnBytes = nByte; + *piStartOffset = pCsr->aOffset[iStart]; + *piEndOffset = pCsr->aOffset[iEnd]; + *piPosition = pCsr->iToken++; + + return SQLITE_OK; +} + +/* +** The set of routines that implement the simple tokenizer +*/ +static const sqlite3_tokenizer_module icuTokenizerModule = { + 0, /* iVersion */ + icuCreate, /* xCreate */ + icuDestroy, /* xCreate */ + icuOpen, /* xOpen */ + icuClose, /* xClose */ + icuNext, /* xNext */ + 0, /* xLanguageid */ +}; + +/* +** Set *ppModule to point at the implementation of the ICU tokenizer. +*/ +SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( + sqlite3_tokenizer_module const**ppModule +){ + *ppModule = &icuTokenizerModule; +} + +#endif /* defined(SQLITE_ENABLE_ICU) */ +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ + +/************** End of fts3_icu.c ********************************************/ +/************** Begin file sqlite3rbu.c **************************************/ +/* +** 2014 August 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** +** OVERVIEW +** +** The RBU extension requires that the RBU update be packaged as an +** SQLite database. The tables it expects to find are described in +** sqlite3rbu.h. Essentially, for each table xyz in the target database +** that the user wishes to write to, a corresponding data_xyz table is +** created in the RBU database and populated with one row for each row to +** update, insert or delete from the target table. +** +** The update proceeds in three stages: +** +** 1) The database is updated. The modified database pages are written +** to a *-oal file. A *-oal file is just like a *-wal file, except +** that it is named "<database>-oal" instead of "<database>-wal". +** Because regular SQLite clients do not look for file named +** "<database>-oal", they go on using the original database in +** rollback mode while the *-oal file is being generated. +** +** During this stage RBU does not update the database by writing +** directly to the target tables. Instead it creates "imposter" +** tables using the SQLITE_TESTCTRL_IMPOSTER interface that it uses +** to update each b-tree individually. All updates required by each +** b-tree are completed before moving on to the next, and all +** updates are done in sorted key order. +** +** 2) The "<database>-oal" file is moved to the equivalent "<database>-wal" +** location using a call to rename(2). Before doing this the RBU +** module takes an EXCLUSIVE lock on the database file, ensuring +** that there are no other active readers. +** +** Once the EXCLUSIVE lock is released, any other database readers +** detect the new *-wal file and read the database in wal mode. At +** this point they see the new version of the database - including +** the updates made as part of the RBU update. +** +** 3) The new *-wal file is checkpointed. This proceeds in the same way +** as a regular database checkpoint, except that a single frame is +** checkpointed each time sqlite3rbu_step() is called. If the RBU +** handle is closed before the entire *-wal file is checkpointed, +** the checkpoint progress is saved in the RBU database and the +** checkpoint can be resumed by another RBU client at some point in +** the future. +** +** POTENTIAL PROBLEMS +** +** The rename() call might not be portable. And RBU is not currently +** syncing the directory after renaming the file. +** +** When state is saved, any commit to the *-oal file and the commit to +** the RBU update database are not atomic. So if the power fails at the +** wrong moment they might get out of sync. As the main database will be +** committed before the RBU update database this will likely either just +** pass unnoticed, or result in SQLITE_CONSTRAINT errors (due to UNIQUE +** constraint violations). +** +** If some client does modify the target database mid RBU update, or some +** other error occurs, the RBU extension will keep throwing errors. It's +** not really clear how to get out of this state. The system could just +** by delete the RBU update database and *-oal file and have the device +** download the update again and start over. +** +** At present, for an UPDATE, both the new.* and old.* records are +** collected in the rbu_xyz table. And for both UPDATEs and DELETEs all +** fields are collected. This means we're probably writing a lot more +** data to disk when saving the state of an ongoing update to the RBU +** update database than is strictly necessary. +** +*/ + +/* #include <assert.h> */ +/* #include <string.h> */ +/* #include <stdio.h> */ + +/* #include "sqlite3.h" */ + +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RBU) +/************** Include sqlite3rbu.h in the middle of sqlite3rbu.c ***********/ +/************** Begin file sqlite3rbu.h **************************************/ +/* +** 2014 August 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the public interface for the RBU extension. +*/ + +/* +** SUMMARY +** +** Writing a transaction containing a large number of operations on +** b-tree indexes that are collectively larger than the available cache +** memory can be very inefficient. +** +** The problem is that in order to update a b-tree, the leaf page (at least) +** containing the entry being inserted or deleted must be modified. If the +** working set of leaves is larger than the available cache memory, then a +** single leaf that is modified more than once as part of the transaction +** may be loaded from or written to the persistent media multiple times. +** Additionally, because the index updates are likely to be applied in +** random order, access to pages within the database is also likely to be in +** random order, which is itself quite inefficient. +** +** One way to improve the situation is to sort the operations on each index +** by index key before applying them to the b-tree. This leads to an IO +** pattern that resembles a single linear scan through the index b-tree, +** and all but guarantees each modified leaf page is loaded and stored +** exactly once. SQLite uses this trick to improve the performance of +** CREATE INDEX commands. This extension allows it to be used to improve +** the performance of large transactions on existing databases. +** +** Additionally, this extension allows the work involved in writing the +** large transaction to be broken down into sub-transactions performed +** sequentially by separate processes. This is useful if the system cannot +** guarantee that a single update process will run for long enough to apply +** the entire update, for example because the update is being applied on a +** mobile device that is frequently rebooted. Even after the writer process +** has committed one or more sub-transactions, other database clients continue +** to read from the original database snapshot. In other words, partially +** applied transactions are not visible to other clients. +** +** "RBU" stands for "Resumable Bulk Update". As in a large database update +** transmitted via a wireless network to a mobile device. A transaction +** applied using this extension is hence refered to as an "RBU update". +** +** +** LIMITATIONS +** +** An "RBU update" transaction is subject to the following limitations: +** +** * The transaction must consist of INSERT, UPDATE and DELETE operations +** only. +** +** * INSERT statements may not use any default values. +** +** * UPDATE and DELETE statements must identify their target rows by +** non-NULL PRIMARY KEY values. Rows with NULL values stored in PRIMARY +** KEY fields may not be updated or deleted. If the table being written +** has no PRIMARY KEY, affected rows must be identified by rowid. +** +** * UPDATE statements may not modify PRIMARY KEY columns. +** +** * No triggers will be fired. +** +** * No foreign key violations are detected or reported. +** +** * CHECK constraints are not enforced. +** +** * No constraint handling mode except for "OR ROLLBACK" is supported. +** +** +** PREPARATION +** +** An "RBU update" is stored as a separate SQLite database. A database +** containing an RBU update is an "RBU database". For each table in the +** target database to be updated, the RBU database should contain a table +** named "data_<target name>" containing the same set of columns as the +** target table, and one more - "rbu_control". The data_% table should +** have no PRIMARY KEY or UNIQUE constraints, but each column should have +** the same type as the corresponding column in the target database. +** The "rbu_control" column should have no type at all. For example, if +** the target database contains: +** +** CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c UNIQUE); +** +** Then the RBU database should contain: +** +** CREATE TABLE data_t1(a INTEGER, b TEXT, c, rbu_control); +** +** The order of the columns in the data_% table does not matter. +** +** Instead of a regular table, the RBU database may also contain virtual +** tables or views named using the data_<target> naming scheme. +** +** Instead of the plain data_<target> naming scheme, RBU database tables +** may also be named data<integer>_<target>, where <integer> is any sequence +** of zero or more numeric characters (0-9). This can be significant because +** tables within the RBU database are always processed in order sorted by +** name. By judicious selection of the <integer> portion of the names +** of the RBU tables the user can therefore control the order in which they +** are processed. This can be useful, for example, to ensure that "external +** content" FTS4 tables are updated before their underlying content tables. +** +** If the target database table is a virtual table or a table that has no +** PRIMARY KEY declaration, the data_% table must also contain a column +** named "rbu_rowid". This column is mapped to the table's implicit primary +** key column - "rowid". Virtual tables for which the "rowid" column does +** not function like a primary key value cannot be updated using RBU. For +** example, if the target db contains either of the following: +** +** CREATE VIRTUAL TABLE x1 USING fts3(a, b); +** CREATE TABLE x1(a, b) +** +** then the RBU database should contain: +** +** CREATE TABLE data_x1(a, b, rbu_rowid, rbu_control); +** +** All non-hidden columns (i.e. all columns matched by "SELECT *") of the +** target table must be present in the input table. For virtual tables, +** hidden columns are optional - they are updated by RBU if present in +** the input table, or not otherwise. For example, to write to an fts4 +** table with a hidden languageid column such as: +** +** CREATE VIRTUAL TABLE ft1 USING fts4(a, b, languageid='langid'); +** +** Either of the following input table schemas may be used: +** +** CREATE TABLE data_ft1(a, b, langid, rbu_rowid, rbu_control); +** CREATE TABLE data_ft1(a, b, rbu_rowid, rbu_control); +** +** For each row to INSERT into the target database as part of the RBU +** update, the corresponding data_% table should contain a single record +** with the "rbu_control" column set to contain integer value 0. The +** other columns should be set to the values that make up the new record +** to insert. +** +** If the target database table has an INTEGER PRIMARY KEY, it is not +** possible to insert a NULL value into the IPK column. Attempting to +** do so results in an SQLITE_MISMATCH error. +** +** For each row to DELETE from the target database as part of the RBU +** update, the corresponding data_% table should contain a single record +** with the "rbu_control" column set to contain integer value 1. The +** real primary key values of the row to delete should be stored in the +** corresponding columns of the data_% table. The values stored in the +** other columns are not used. +** +** For each row to UPDATE from the target database as part of the RBU +** update, the corresponding data_% table should contain a single record +** with the "rbu_control" column set to contain a value of type text. +** The real primary key values identifying the row to update should be +** stored in the corresponding columns of the data_% table row, as should +** the new values of all columns being update. The text value in the +** "rbu_control" column must contain the same number of characters as +** there are columns in the target database table, and must consist entirely +** of 'x' and '.' characters (or in some special cases 'd' - see below). For +** each column that is being updated, the corresponding character is set to +** 'x'. For those that remain as they are, the corresponding character of the +** rbu_control value should be set to '.'. For example, given the tables +** above, the update statement: +** +** UPDATE t1 SET c = 'usa' WHERE a = 4; +** +** is represented by the data_t1 row created by: +** +** INSERT INTO data_t1(a, b, c, rbu_control) VALUES(4, NULL, 'usa', '..x'); +** +** Instead of an 'x' character, characters of the rbu_control value specified +** for UPDATEs may also be set to 'd'. In this case, instead of updating the +** target table with the value stored in the corresponding data_% column, the +** user-defined SQL function "rbu_delta()" is invoked and the result stored in +** the target table column. rbu_delta() is invoked with two arguments - the +** original value currently stored in the target table column and the +** value specified in the data_xxx table. +** +** For example, this row: +** +** INSERT INTO data_t1(a, b, c, rbu_control) VALUES(4, NULL, 'usa', '..d'); +** +** is similar to an UPDATE statement such as: +** +** UPDATE t1 SET c = rbu_delta(c, 'usa') WHERE a = 4; +** +** Finally, if an 'f' character appears in place of a 'd' or 's' in an +** ota_control string, the contents of the data_xxx table column is assumed +** to be a "fossil delta" - a patch to be applied to a blob value in the +** format used by the fossil source-code management system. In this case +** the existing value within the target database table must be of type BLOB. +** It is replaced by the result of applying the specified fossil delta to +** itself. +** +** If the target database table is a virtual table or a table with no PRIMARY +** KEY, the rbu_control value should not include a character corresponding +** to the rbu_rowid value. For example, this: +** +** INSERT INTO data_ft1(a, b, rbu_rowid, rbu_control) +** VALUES(NULL, 'usa', 12, '.x'); +** +** causes a result similar to: +** +** UPDATE ft1 SET b = 'usa' WHERE rowid = 12; +** +** The data_xxx tables themselves should have no PRIMARY KEY declarations. +** However, RBU is more efficient if reading the rows in from each data_xxx +** table in "rowid" order is roughly the same as reading them sorted by +** the PRIMARY KEY of the corresponding target database table. In other +** words, rows should be sorted using the destination table PRIMARY KEY +** fields before they are inserted into the data_xxx tables. +** +** USAGE +** +** The API declared below allows an application to apply an RBU update +** stored on disk to an existing target database. Essentially, the +** application: +** +** 1) Opens an RBU handle using the sqlite3rbu_open() function. +** +** 2) Registers any required virtual table modules with the database +** handle returned by sqlite3rbu_db(). Also, if required, register +** the rbu_delta() implementation. +** +** 3) Calls the sqlite3rbu_step() function one or more times on +** the new handle. Each call to sqlite3rbu_step() performs a single +** b-tree operation, so thousands of calls may be required to apply +** a complete update. +** +** 4) Calls sqlite3rbu_close() to close the RBU update handle. If +** sqlite3rbu_step() has been called enough times to completely +** apply the update to the target database, then the RBU database +** is marked as fully applied. Otherwise, the state of the RBU +** update application is saved in the RBU database for later +** resumption. +** +** See comments below for more detail on APIs. +** +** If an update is only partially applied to the target database by the +** time sqlite3rbu_close() is called, various state information is saved +** within the RBU database. This allows subsequent processes to automatically +** resume the RBU update from where it left off. +** +** To remove all RBU extension state information, returning an RBU database +** to its original contents, it is sufficient to drop all tables that begin +** with the prefix "rbu_" +** +** DATABASE LOCKING +** +** An RBU update may not be applied to a database in WAL mode. Attempting +** to do so is an error (SQLITE_ERROR). +** +** While an RBU handle is open, a SHARED lock may be held on the target +** database file. This means it is possible for other clients to read the +** database, but not to write it. +** +** If an RBU update is started and then suspended before it is completed, +** then an external client writes to the database, then attempting to resume +** the suspended RBU update is also an error (SQLITE_BUSY). +*/ + +#ifndef _SQLITE3RBU_H +#define _SQLITE3RBU_H + +/* #include "sqlite3.h" ** Required for error code definitions ** */ + +#if 0 +extern "C" { +#endif + +typedef struct sqlite3rbu sqlite3rbu; + +/* +** Open an RBU handle. +** +** Argument zTarget is the path to the target database. Argument zRbu is +** the path to the RBU database. Each call to this function must be matched +** by a call to sqlite3rbu_close(). When opening the databases, RBU passes +** the SQLITE_CONFIG_URI flag to sqlite3_open_v2(). So if either zTarget +** or zRbu begin with "file:", it will be interpreted as an SQLite +** database URI, not a regular file name. +** +** If the zState argument is passed a NULL value, the RBU extension stores +** the current state of the update (how many rows have been updated, which +** indexes are yet to be updated etc.) within the RBU database itself. This +** can be convenient, as it means that the RBU application does not need to +** organize removing a separate state file after the update is concluded. +** Or, if zState is non-NULL, it must be a path to a database file in which +** the RBU extension can store the state of the update. +** +** When resuming an RBU update, the zState argument must be passed the same +** value as when the RBU update was started. +** +** Once the RBU update is finished, the RBU extension does not +** automatically remove any zState database file, even if it created it. +** +** By default, RBU uses the default VFS to access the files on disk. To +** use a VFS other than the default, an SQLite "file:" URI containing a +** "vfs=..." option may be passed as the zTarget option. +** +** IMPORTANT NOTE FOR ZIPVFS USERS: The RBU extension works with all of +** SQLite's built-in VFSs, including the multiplexor VFS. However it does +** not work out of the box with zipvfs. Refer to the comment describing +** the zipvfs_create_vfs() API below for details on using RBU with zipvfs. +*/ +SQLITE_API sqlite3rbu *sqlite3rbu_open( + const char *zTarget, + const char *zRbu, + const char *zState +); + +/* +** Open an RBU handle to perform an RBU vacuum on database file zTarget. +** An RBU vacuum is similar to SQLite's built-in VACUUM command, except +** that it can be suspended and resumed like an RBU update. +** +** The second argument to this function identifies a database in which +** to store the state of the RBU vacuum operation if it is suspended. The +** first time sqlite3rbu_vacuum() is called, to start an RBU vacuum +** operation, the state database should either not exist or be empty +** (contain no tables). If an RBU vacuum is suspended by calling +** sqlite3rbu_close() on the RBU handle before sqlite3rbu_step() has +** returned SQLITE_DONE, the vacuum state is stored in the state database. +** The vacuum can be resumed by calling this function to open a new RBU +** handle specifying the same target and state databases. +** +** If the second argument passed to this function is NULL, then the +** name of the state database is "<database>-vacuum", where <database> +** is the name of the target database file. In this case, on UNIX, if the +** state database is not already present in the file-system, it is created +** with the same permissions as the target db is made. +** +** With an RBU vacuum, it is an SQLITE_MISUSE error if the name of the +** state database ends with "-vactmp". This name is reserved for internal +** use. +** +** This function does not delete the state database after an RBU vacuum +** is completed, even if it created it. However, if the call to +** sqlite3rbu_close() returns any value other than SQLITE_OK, the contents +** of the state tables within the state database are zeroed. This way, +** the next call to sqlite3rbu_vacuum() opens a handle that starts a +** new RBU vacuum operation. +** +** As with sqlite3rbu_open(), Zipvfs users should rever to the comment +** describing the sqlite3rbu_create_vfs() API function below for +** a description of the complications associated with using RBU with +** zipvfs databases. +*/ +SQLITE_API sqlite3rbu *sqlite3rbu_vacuum( + const char *zTarget, + const char *zState +); + +/* +** Configure a limit for the amount of temp space that may be used by +** the RBU handle passed as the first argument. The new limit is specified +** in bytes by the second parameter. If it is positive, the limit is updated. +** If the second parameter to this function is passed zero, then the limit +** is removed entirely. If the second parameter is negative, the limit is +** not modified (this is useful for querying the current limit). +** +** In all cases the returned value is the current limit in bytes (zero +** indicates unlimited). +** +** If the temp space limit is exceeded during operation, an SQLITE_FULL +** error is returned. +*/ +SQLITE_API sqlite3_int64 sqlite3rbu_temp_size_limit(sqlite3rbu*, sqlite3_int64); + +/* +** Return the current amount of temp file space, in bytes, currently used by +** the RBU handle passed as the only argument. +*/ +SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu*); + +/* +** Internally, each RBU connection uses a separate SQLite database +** connection to access the target and rbu update databases. This +** API allows the application direct access to these database handles. +** +** The first argument passed to this function must be a valid, open, RBU +** handle. The second argument should be passed zero to access the target +** database handle, or non-zero to access the rbu update database handle. +** Accessing the underlying database handles may be useful in the +** following scenarios: +** +** * If any target tables are virtual tables, it may be necessary to +** call sqlite3_create_module() on the target database handle to +** register the required virtual table implementations. +** +** * If the data_xxx tables in the RBU source database are virtual +** tables, the application may need to call sqlite3_create_module() on +** the rbu update db handle to any required virtual table +** implementations. +** +** * If the application uses the "rbu_delta()" feature described above, +** it must use sqlite3_create_function() or similar to register the +** rbu_delta() implementation with the target database handle. +** +** If an error has occurred, either while opening or stepping the RBU object, +** this function may return NULL. The error code and message may be collected +** when sqlite3rbu_close() is called. +** +** Database handles returned by this function remain valid until the next +** call to any sqlite3rbu_xxx() function other than sqlite3rbu_db(). +*/ +SQLITE_API sqlite3 *sqlite3rbu_db(sqlite3rbu*, int bRbu); + +/* +** Do some work towards applying the RBU update to the target db. +** +** Return SQLITE_DONE if the update has been completely applied, or +** SQLITE_OK if no error occurs but there remains work to do to apply +** the RBU update. If an error does occur, some other error code is +** returned. +** +** Once a call to sqlite3rbu_step() has returned a value other than +** SQLITE_OK, all subsequent calls on the same RBU handle are no-ops +** that immediately return the same value. +*/ +SQLITE_API int sqlite3rbu_step(sqlite3rbu *pRbu); + +/* +** Force RBU to save its state to disk. +** +** If a power failure or application crash occurs during an update, following +** system recovery RBU may resume the update from the point at which the state +** was last saved. In other words, from the most recent successful call to +** sqlite3rbu_close() or this function. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *pRbu); + +/* +** Close an RBU handle. +** +** If the RBU update has been completely applied, mark the RBU database +** as fully applied. Otherwise, assuming no error has occurred, save the +** current state of the RBU update appliation to the RBU database. +** +** If an error has already occurred as part of an sqlite3rbu_step() +** or sqlite3rbu_open() call, or if one occurs within this function, an +** SQLite error code is returned. Additionally, if pzErrmsg is not NULL, +** *pzErrmsg may be set to point to a buffer containing a utf-8 formatted +** English language error message. It is the responsibility of the caller to +** eventually free any such buffer using sqlite3_free(). +** +** Otherwise, if no error occurs, this function returns SQLITE_OK if the +** update has been partially applied, or SQLITE_DONE if it has been +** completely applied. +*/ +SQLITE_API int sqlite3rbu_close(sqlite3rbu *pRbu, char **pzErrmsg); + +/* +** Return the total number of key-value operations (inserts, deletes or +** updates) that have been performed on the target database since the +** current RBU update was started. +*/ +SQLITE_API sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu); + +/* +** Obtain permyriadage (permyriadage is to 10000 as percentage is to 100) +** progress indications for the two stages of an RBU update. This API may +** be useful for driving GUI progress indicators and similar. +** +** An RBU update is divided into two stages: +** +** * Stage 1, in which changes are accumulated in an oal/wal file, and +** * Stage 2, in which the contents of the wal file are copied into the +** main database. +** +** The update is visible to non-RBU clients during stage 2. During stage 1 +** non-RBU reader clients may see the original database. +** +** If this API is called during stage 2 of the update, output variable +** (*pnOne) is set to 10000 to indicate that stage 1 has finished and (*pnTwo) +** to a value between 0 and 10000 to indicate the permyriadage progress of +** stage 2. A value of 5000 indicates that stage 2 is half finished, +** 9000 indicates that it is 90% finished, and so on. +** +** If this API is called during stage 1 of the update, output variable +** (*pnTwo) is set to 0 to indicate that stage 2 has not yet started. The +** value to which (*pnOne) is set depends on whether or not the RBU +** database contains an "rbu_count" table. The rbu_count table, if it +** exists, must contain the same columns as the following: +** +** CREATE TABLE rbu_count(tbl TEXT PRIMARY KEY, cnt INTEGER) WITHOUT ROWID; +** +** There must be one row in the table for each source (data_xxx) table within +** the RBU database. The 'tbl' column should contain the name of the source +** table. The 'cnt' column should contain the number of rows within the +** source table. +** +** If the rbu_count table is present and populated correctly and this +** API is called during stage 1, the *pnOne output variable is set to the +** permyriadage progress of the same stage. If the rbu_count table does +** not exist, then (*pnOne) is set to -1 during stage 1. If the rbu_count +** table exists but is not correctly populated, the value of the *pnOne +** output variable during stage 1 is undefined. +*/ +SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *pRbu, int *pnOne, int*pnTwo); + +/* +** Obtain an indication as to the current stage of an RBU update or vacuum. +** This function always returns one of the SQLITE_RBU_STATE_XXX constants +** defined in this file. Return values should be interpreted as follows: +** +** SQLITE_RBU_STATE_OAL: +** RBU is currently building a *-oal file. The next call to sqlite3rbu_step() +** may either add further data to the *-oal file, or compute data that will +** be added by a subsequent call. +** +** SQLITE_RBU_STATE_MOVE: +** RBU has finished building the *-oal file. The next call to sqlite3rbu_step() +** will move the *-oal file to the equivalent *-wal path. If the current +** operation is an RBU update, then the updated version of the database +** file will become visible to ordinary SQLite clients following the next +** call to sqlite3rbu_step(). +** +** SQLITE_RBU_STATE_CHECKPOINT: +** RBU is currently performing an incremental checkpoint. The next call to +** sqlite3rbu_step() will copy a page of data from the *-wal file into +** the target database file. +** +** SQLITE_RBU_STATE_DONE: +** The RBU operation has finished. Any subsequent calls to sqlite3rbu_step() +** will immediately return SQLITE_DONE. +** +** SQLITE_RBU_STATE_ERROR: +** An error has occurred. Any subsequent calls to sqlite3rbu_step() will +** immediately return the SQLite error code associated with the error. +*/ +#define SQLITE_RBU_STATE_OAL 1 +#define SQLITE_RBU_STATE_MOVE 2 +#define SQLITE_RBU_STATE_CHECKPOINT 3 +#define SQLITE_RBU_STATE_DONE 4 +#define SQLITE_RBU_STATE_ERROR 5 + +SQLITE_API int sqlite3rbu_state(sqlite3rbu *pRbu); + +/* +** As part of applying an RBU update or performing an RBU vacuum operation, +** the system must at one point move the *-oal file to the equivalent *-wal +** path. Normally, it does this by invoking POSIX function rename(2) directly. +** Except on WINCE platforms, where it uses win32 API MoveFileW(). This +** function may be used to register a callback that the RBU module will invoke +** instead of one of these APIs. +** +** If a callback is registered with an RBU handle, it invokes it instead +** of rename(2) when it needs to move a file within the file-system. The +** first argument passed to the xRename() callback is a copy of the second +** argument (pArg) passed to this function. The second is the full path +** to the file to move and the third the full path to which it should be +** moved. The callback function should return SQLITE_OK to indicate +** success. If an error occurs, it should return an SQLite error code. +** In this case the RBU operation will be abandoned and the error returned +** to the RBU user. +** +** Passing a NULL pointer in place of the xRename argument to this function +** restores the default behaviour. +*/ +SQLITE_API void sqlite3rbu_rename_handler( + sqlite3rbu *pRbu, + void *pArg, + int (*xRename)(void *pArg, const char *zOld, const char *zNew) +); + + +/* +** Create an RBU VFS named zName that accesses the underlying file-system +** via existing VFS zParent. Or, if the zParent parameter is passed NULL, +** then the new RBU VFS uses the default system VFS to access the file-system. +** The new object is registered as a non-default VFS with SQLite before +** returning. +** +** Part of the RBU implementation uses a custom VFS object. Usually, this +** object is created and deleted automatically by RBU. +** +** The exception is for applications that also use zipvfs. In this case, +** the custom VFS must be explicitly created by the user before the RBU +** handle is opened. The RBU VFS should be installed so that the zipvfs +** VFS uses the RBU VFS, which in turn uses any other VFS layers in use +** (for example multiplexor) to access the file-system. For example, +** to assemble an RBU enabled VFS stack that uses both zipvfs and +** multiplexor (error checking omitted): +** +** // Create a VFS named "multiplex" (not the default). +** sqlite3_multiplex_initialize(0, 0); +** +** // Create an rbu VFS named "rbu" that uses multiplexor. If the +** // second argument were replaced with NULL, the "rbu" VFS would +** // access the file-system via the system default VFS, bypassing the +** // multiplexor. +** sqlite3rbu_create_vfs("rbu", "multiplex"); +** +** // Create a zipvfs VFS named "zipvfs" that uses rbu. +** zipvfs_create_vfs_v3("zipvfs", "rbu", 0, xCompressorAlgorithmDetector); +** +** // Make zipvfs the default VFS. +** sqlite3_vfs_register(sqlite3_vfs_find("zipvfs"), 1); +** +** Because the default VFS created above includes a RBU functionality, it +** may be used by RBU clients. Attempting to use RBU with a zipvfs VFS stack +** that does not include the RBU layer results in an error. +** +** The overhead of adding the "rbu" VFS to the system is negligible for +** non-RBU users. There is no harm in an application accessing the +** file-system via "rbu" all the time, even if it only uses RBU functionality +** occasionally. +*/ +SQLITE_API int sqlite3rbu_create_vfs(const char *zName, const char *zParent); + +/* +** Deregister and destroy an RBU vfs created by an earlier call to +** sqlite3rbu_create_vfs(). +** +** VFS objects are not reference counted. If a VFS object is destroyed +** before all database handles that use it have been closed, the results +** are undefined. +*/ +SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName); + +#if 0 +} /* end of the 'extern "C"' block */ +#endif + +#endif /* _SQLITE3RBU_H */ + +/************** End of sqlite3rbu.h ******************************************/ +/************** Continuing where we left off in sqlite3rbu.c *****************/ + +#if defined(_WIN32_WCE) +/* #include "windows.h" */ +#endif + +/* Maximum number of prepared UPDATE statements held by this module */ +#define SQLITE_RBU_UPDATE_CACHESIZE 16 + +/* Delta checksums disabled by default. Compile with -DRBU_ENABLE_DELTA_CKSUM +** to enable checksum verification. +*/ +#ifndef RBU_ENABLE_DELTA_CKSUM +# define RBU_ENABLE_DELTA_CKSUM 0 +#endif + +/* +** Swap two objects of type TYPE. +*/ +#if !defined(SQLITE_AMALGAMATION) +# define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;} +#endif + +/* +** Name of the URI option that causes RBU to take an exclusive lock as +** part of the incremental checkpoint operation. +*/ +#define RBU_EXCLUSIVE_CHECKPOINT "rbu_exclusive_checkpoint" + + +/* +** The rbu_state table is used to save the state of a partially applied +** update so that it can be resumed later. The table consists of integer +** keys mapped to values as follows: +** +** RBU_STATE_STAGE: +** May be set to integer values 1, 2, 4 or 5. As follows: +** 1: the *-rbu file is currently under construction. +** 2: the *-rbu file has been constructed, but not yet moved +** to the *-wal path. +** 4: the checkpoint is underway. +** 5: the rbu update has been checkpointed. +** +** RBU_STATE_TBL: +** Only valid if STAGE==1. The target database name of the table +** currently being written. +** +** RBU_STATE_IDX: +** Only valid if STAGE==1. The target database name of the index +** currently being written, or NULL if the main table is currently being +** updated. +** +** RBU_STATE_ROW: +** Only valid if STAGE==1. Number of rows already processed for the current +** table/index. +** +** RBU_STATE_PROGRESS: +** Trbul number of sqlite3rbu_step() calls made so far as part of this +** rbu update. +** +** RBU_STATE_CKPT: +** Valid if STAGE==4. The 64-bit checksum associated with the wal-index +** header created by recovering the *-wal file. This is used to detect +** cases when another client appends frames to the *-wal file in the +** middle of an incremental checkpoint (an incremental checkpoint cannot +** be continued if this happens). +** +** RBU_STATE_COOKIE: +** Valid if STAGE==1. The current change-counter cookie value in the +** target db file. +** +** RBU_STATE_OALSZ: +** Valid if STAGE==1. The size in bytes of the *-oal file. +** +** RBU_STATE_DATATBL: +** Only valid if STAGE==1. The RBU database name of the table +** currently being read. +*/ +#define RBU_STATE_STAGE 1 +#define RBU_STATE_TBL 2 +#define RBU_STATE_IDX 3 +#define RBU_STATE_ROW 4 +#define RBU_STATE_PROGRESS 5 +#define RBU_STATE_CKPT 6 +#define RBU_STATE_COOKIE 7 +#define RBU_STATE_OALSZ 8 +#define RBU_STATE_PHASEONESTEP 9 +#define RBU_STATE_DATATBL 10 + +#define RBU_STAGE_OAL 1 +#define RBU_STAGE_MOVE 2 +#define RBU_STAGE_CAPTURE 3 +#define RBU_STAGE_CKPT 4 +#define RBU_STAGE_DONE 5 + + +#define RBU_CREATE_STATE \ + "CREATE TABLE IF NOT EXISTS %s.rbu_state(k INTEGER PRIMARY KEY, v)" + +typedef struct RbuFrame RbuFrame; +typedef struct RbuObjIter RbuObjIter; +typedef struct RbuState RbuState; +typedef struct RbuSpan RbuSpan; +typedef struct rbu_vfs rbu_vfs; +typedef struct rbu_file rbu_file; +typedef struct RbuUpdateStmt RbuUpdateStmt; + +#if !defined(SQLITE_AMALGAMATION) +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; +typedef sqlite3_int64 i64; +#endif + +/* +** These values must match the values defined in wal.c for the equivalent +** locks. These are not magic numbers as they are part of the SQLite file +** format. +*/ +#define WAL_LOCK_WRITE 0 +#define WAL_LOCK_CKPT 1 +#define WAL_LOCK_READ0 3 + +#define SQLITE_FCNTL_RBUCNT 5149216 + +/* +** A structure to store values read from the rbu_state table in memory. +*/ +struct RbuState { + int eStage; + char *zTbl; + char *zDataTbl; + char *zIdx; + i64 iWalCksum; + int nRow; + i64 nProgress; + u32 iCookie; + i64 iOalSz; + i64 nPhaseOneStep; +}; + +struct RbuUpdateStmt { + char *zMask; /* Copy of update mask used with pUpdate */ + sqlite3_stmt *pUpdate; /* Last update statement (or NULL) */ + RbuUpdateStmt *pNext; +}; + +struct RbuSpan { + const char *zSpan; + int nSpan; +}; + +/* +** An iterator of this type is used to iterate through all objects in +** the target database that require updating. For each such table, the +** iterator visits, in order: +** +** * the table itself, +** * each index of the table (zero or more points to visit), and +** * a special "cleanup table" state. +** +** abIndexed: +** If the table has no indexes on it, abIndexed is set to NULL. Otherwise, +** it points to an array of flags nTblCol elements in size. The flag is +** set for each column that is either a part of the PK or a part of an +** index. Or clear otherwise. +** +** If there are one or more partial indexes on the table, all fields of +** this array set set to 1. This is because in that case, the module has +** no way to tell which fields will be required to add and remove entries +** from the partial indexes. +** +*/ +struct RbuObjIter { + sqlite3_stmt *pTblIter; /* Iterate through tables */ + sqlite3_stmt *pIdxIter; /* Index iterator */ + int nTblCol; /* Size of azTblCol[] array */ + char **azTblCol; /* Array of unquoted target column names */ + char **azTblType; /* Array of target column types */ + int *aiSrcOrder; /* src table col -> target table col */ + u8 *abTblPk; /* Array of flags, set on target PK columns */ + u8 *abNotNull; /* Array of flags, set on NOT NULL columns */ + u8 *abIndexed; /* Array of flags, set on indexed & PK cols */ + int eType; /* Table type - an RBU_PK_XXX value */ + + /* Output variables. zTbl==0 implies EOF. */ + int bCleanup; /* True in "cleanup" state */ + const char *zTbl; /* Name of target db table */ + const char *zDataTbl; /* Name of rbu db table (or null) */ + const char *zIdx; /* Name of target db index (or null) */ + int iTnum; /* Root page of current object */ + int iPkTnum; /* If eType==EXTERNAL, root of PK index */ + int bUnique; /* Current index is unique */ + int nIndex; /* Number of aux. indexes on table zTbl */ + + /* Statements created by rbuObjIterPrepareAll() */ + int nCol; /* Number of columns in current object */ + sqlite3_stmt *pSelect; /* Source data */ + sqlite3_stmt *pInsert; /* Statement for INSERT operations */ + sqlite3_stmt *pDelete; /* Statement for DELETE ops */ + sqlite3_stmt *pTmpInsert; /* Insert into rbu_tmp_$zDataTbl */ + int nIdxCol; + RbuSpan *aIdxCol; + char *zIdxSql; + + /* Last UPDATE used (for PK b-tree updates only), or NULL. */ + RbuUpdateStmt *pRbuUpdate; +}; + +/* +** Values for RbuObjIter.eType +** +** 0: Table does not exist (error) +** 1: Table has an implicit rowid. +** 2: Table has an explicit IPK column. +** 3: Table has an external PK index. +** 4: Table is WITHOUT ROWID. +** 5: Table is a virtual table. +*/ +#define RBU_PK_NOTABLE 0 +#define RBU_PK_NONE 1 +#define RBU_PK_IPK 2 +#define RBU_PK_EXTERNAL 3 +#define RBU_PK_WITHOUT_ROWID 4 +#define RBU_PK_VTAB 5 + + +/* +** Within the RBU_STAGE_OAL stage, each call to sqlite3rbu_step() performs +** one of the following operations. +*/ +#define RBU_INSERT 1 /* Insert on a main table b-tree */ +#define RBU_DELETE 2 /* Delete a row from a main table b-tree */ +#define RBU_REPLACE 3 /* Delete and then insert a row */ +#define RBU_IDX_DELETE 4 /* Delete a row from an aux. index b-tree */ +#define RBU_IDX_INSERT 5 /* Insert on an aux. index b-tree */ + +#define RBU_UPDATE 6 /* Update a row in a main table b-tree */ + +/* +** A single step of an incremental checkpoint - frame iWalFrame of the wal +** file should be copied to page iDbPage of the database file. +*/ +struct RbuFrame { + u32 iDbPage; + u32 iWalFrame; +}; + +/* +** RBU handle. +** +** nPhaseOneStep: +** If the RBU database contains an rbu_count table, this value is set to +** a running estimate of the number of b-tree operations required to +** finish populating the *-oal file. This allows the sqlite3_bp_progress() +** API to calculate the permyriadage progress of populating the *-oal file +** using the formula: +** +** permyriadage = (10000 * nProgress) / nPhaseOneStep +** +** nPhaseOneStep is initialized to the sum of: +** +** nRow * (nIndex + 1) +** +** for all source tables in the RBU database, where nRow is the number +** of rows in the source table and nIndex the number of indexes on the +** corresponding target database table. +** +** This estimate is accurate if the RBU update consists entirely of +** INSERT operations. However, it is inaccurate if: +** +** * the RBU update contains any UPDATE operations. If the PK specified +** for an UPDATE operation does not exist in the target table, then +** no b-tree operations are required on index b-trees. Or if the +** specified PK does exist, then (nIndex*2) such operations are +** required (one delete and one insert on each index b-tree). +** +** * the RBU update contains any DELETE operations for which the specified +** PK does not exist. In this case no operations are required on index +** b-trees. +** +** * the RBU update contains REPLACE operations. These are similar to +** UPDATE operations. +** +** nPhaseOneStep is updated to account for the conditions above during the +** first pass of each source table. The updated nPhaseOneStep value is +** stored in the rbu_state table if the RBU update is suspended. +*/ +struct sqlite3rbu { + int eStage; /* Value of RBU_STATE_STAGE field */ + sqlite3 *dbMain; /* target database handle */ + sqlite3 *dbRbu; /* rbu database handle */ + char *zTarget; /* Path to target db */ + char *zRbu; /* Path to rbu db */ + char *zState; /* Path to state db (or NULL if zRbu) */ + char zStateDb[5]; /* Db name for state ("stat" or "main") */ + int rc; /* Value returned by last rbu_step() call */ + char *zErrmsg; /* Error message if rc!=SQLITE_OK */ + int nStep; /* Rows processed for current object */ + int nProgress; /* Rows processed for all objects */ + RbuObjIter objiter; /* Iterator for skipping through tbl/idx */ + const char *zVfsName; /* Name of automatically created rbu vfs */ + rbu_file *pTargetFd; /* File handle open on target db */ + int nPagePerSector; /* Pages per sector for pTargetFd */ + i64 iOalSz; + i64 nPhaseOneStep; + void *pRenameArg; + int (*xRename)(void*, const char*, const char*); + + /* The following state variables are used as part of the incremental + ** checkpoint stage (eStage==RBU_STAGE_CKPT). See comments surrounding + ** function rbuSetupCheckpoint() for details. */ + u32 iMaxFrame; /* Largest iWalFrame value in aFrame[] */ + u32 mLock; + int nFrame; /* Entries in aFrame[] array */ + int nFrameAlloc; /* Allocated size of aFrame[] array */ + RbuFrame *aFrame; + int pgsz; + u8 *aBuf; + i64 iWalCksum; + i64 szTemp; /* Current size of all temp files in use */ + i64 szTempLimit; /* Total size limit for temp files */ + + /* Used in RBU vacuum mode only */ + int nRbu; /* Number of RBU VFS in the stack */ + rbu_file *pRbuFd; /* Fd for main db of dbRbu */ +}; + +/* +** An rbu VFS is implemented using an instance of this structure. +** +** Variable pRbu is only non-NULL for automatically created RBU VFS objects. +** It is NULL for RBU VFS objects created explicitly using +** sqlite3rbu_create_vfs(). It is used to track the total amount of temp +** space used by the RBU handle. +*/ +struct rbu_vfs { + sqlite3_vfs base; /* rbu VFS shim methods */ + sqlite3_vfs *pRealVfs; /* Underlying VFS */ + sqlite3_mutex *mutex; /* Mutex to protect pMain */ + sqlite3rbu *pRbu; /* Owner RBU object */ + rbu_file *pMain; /* List of main db files */ + rbu_file *pMainRbu; /* List of main db files with pRbu!=0 */ +}; + +/* +** Each file opened by an rbu VFS is represented by an instance of +** the following structure. +** +** If this is a temporary file (pRbu!=0 && flags&DELETE_ON_CLOSE), variable +** "sz" is set to the current size of the database file. +*/ +struct rbu_file { + sqlite3_file base; /* sqlite3_file methods */ + sqlite3_file *pReal; /* Underlying file handle */ + rbu_vfs *pRbuVfs; /* Pointer to the rbu_vfs object */ + sqlite3rbu *pRbu; /* Pointer to rbu object (rbu target only) */ + i64 sz; /* Size of file in bytes (temp only) */ + + int openFlags; /* Flags this file was opened with */ + u32 iCookie; /* Cookie value for main db files */ + u8 iWriteVer; /* "write-version" value for main db files */ + u8 bNolock; /* True to fail EXCLUSIVE locks */ + + int nShm; /* Number of entries in apShm[] array */ + char **apShm; /* Array of mmap'd *-shm regions */ + char *zDel; /* Delete this when closing file */ + + const char *zWal; /* Wal filename for this main db file */ + rbu_file *pWalFd; /* Wal file descriptor for this main db */ + rbu_file *pMainNext; /* Next MAIN_DB file */ + rbu_file *pMainRbuNext; /* Next MAIN_DB file with pRbu!=0 */ +}; + +/* +** True for an RBU vacuum handle, or false otherwise. +*/ +#define rbuIsVacuum(p) ((p)->zTarget==0) + + +/************************************************************************* +** The following three functions, found below: +** +** rbuDeltaGetInt() +** rbuDeltaChecksum() +** rbuDeltaApply() +** +** are lifted from the fossil source code (http://fossil-scm.org). They +** are used to implement the scalar SQL function rbu_fossil_delta(). +*/ + +/* +** Read bytes from *pz and convert them into a positive integer. When +** finished, leave *pz pointing to the first character past the end of +** the integer. The *pLen parameter holds the length of the string +** in *pz and is decremented once for each character in the integer. +*/ +static unsigned int rbuDeltaGetInt(const char **pz, int *pLen){ + static const signed char zValue[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, 36, + -1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, -1, -1, -1, 63, -1, + }; + unsigned int v = 0; + int c; + unsigned char *z = (unsigned char*)*pz; + unsigned char *zStart = z; + while( (c = zValue[0x7f&*(z++)])>=0 ){ + v = (v<<6) + c; + } + z--; + *pLen -= z - zStart; + *pz = (char*)z; + return v; +} + +#if RBU_ENABLE_DELTA_CKSUM +/* +** Compute a 32-bit checksum on the N-byte buffer. Return the result. +*/ +static unsigned int rbuDeltaChecksum(const char *zIn, size_t N){ + const unsigned char *z = (const unsigned char *)zIn; + unsigned sum0 = 0; + unsigned sum1 = 0; + unsigned sum2 = 0; + unsigned sum3 = 0; + while(N >= 16){ + sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]); + sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]); + sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]); + sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]); + z += 16; + N -= 16; + } + while(N >= 4){ + sum0 += z[0]; + sum1 += z[1]; + sum2 += z[2]; + sum3 += z[3]; + z += 4; + N -= 4; + } + sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24); + switch(N){ + case 3: sum3 += (z[2] << 8); + case 2: sum3 += (z[1] << 16); + case 1: sum3 += (z[0] << 24); + default: ; + } + return sum3; +} +#endif + +/* +** Apply a delta. +** +** The output buffer should be big enough to hold the whole output +** file and a NUL terminator at the end. The delta_output_size() +** routine will determine this size for you. +** +** The delta string should be null-terminated. But the delta string +** may contain embedded NUL characters (if the input and output are +** binary files) so we also have to pass in the length of the delta in +** the lenDelta parameter. +** +** This function returns the size of the output file in bytes (excluding +** the final NUL terminator character). Except, if the delta string is +** malformed or intended for use with a source file other than zSrc, +** then this routine returns -1. +** +** Refer to the delta_create() documentation above for a description +** of the delta file format. +*/ +static int rbuDeltaApply( + const char *zSrc, /* The source or pattern file */ + int lenSrc, /* Length of the source file */ + const char *zDelta, /* Delta to apply to the pattern */ + int lenDelta, /* Length of the delta */ + char *zOut /* Write the output into this preallocated buffer */ +){ + unsigned int limit; + unsigned int total = 0; +#if RBU_ENABLE_DELTA_CKSUM + char *zOrigOut = zOut; +#endif + + limit = rbuDeltaGetInt(&zDelta, &lenDelta); + if( *zDelta!='\n' ){ + /* ERROR: size integer not terminated by "\n" */ + return -1; + } + zDelta++; lenDelta--; + while( *zDelta && lenDelta>0 ){ + unsigned int cnt, ofst; + cnt = rbuDeltaGetInt(&zDelta, &lenDelta); + switch( zDelta[0] ){ + case '@': { + zDelta++; lenDelta--; + ofst = rbuDeltaGetInt(&zDelta, &lenDelta); + if( lenDelta>0 && zDelta[0]!=',' ){ + /* ERROR: copy command not terminated by ',' */ + return -1; + } + zDelta++; lenDelta--; + total += cnt; + if( total>limit ){ + /* ERROR: copy exceeds output file size */ + return -1; + } + if( (int)(ofst+cnt) > lenSrc ){ + /* ERROR: copy extends past end of input */ + return -1; + } + memcpy(zOut, &zSrc[ofst], cnt); + zOut += cnt; + break; + } + case ':': { + zDelta++; lenDelta--; + total += cnt; + if( total>limit ){ + /* ERROR: insert command gives an output larger than predicted */ + return -1; + } + if( (int)cnt>lenDelta ){ + /* ERROR: insert count exceeds size of delta */ + return -1; + } + memcpy(zOut, zDelta, cnt); + zOut += cnt; + zDelta += cnt; + lenDelta -= cnt; + break; + } + case ';': { + zDelta++; lenDelta--; + zOut[0] = 0; +#if RBU_ENABLE_DELTA_CKSUM + if( cnt!=rbuDeltaChecksum(zOrigOut, total) ){ + /* ERROR: bad checksum */ + return -1; + } +#endif + if( total!=limit ){ + /* ERROR: generated size does not match predicted size */ + return -1; + } + return total; + } + default: { + /* ERROR: unknown delta operator */ + return -1; + } + } + } + /* ERROR: unterminated delta */ + return -1; +} + +static int rbuDeltaOutputSize(const char *zDelta, int lenDelta){ + int size; + size = rbuDeltaGetInt(&zDelta, &lenDelta); + if( *zDelta!='\n' ){ + /* ERROR: size integer not terminated by "\n" */ + return -1; + } + return size; +} + +/* +** End of code taken from fossil. +*************************************************************************/ + +/* +** Implementation of SQL scalar function rbu_fossil_delta(). +** +** This function applies a fossil delta patch to a blob. Exactly two +** arguments must be passed to this function. The first is the blob to +** patch and the second the patch to apply. If no error occurs, this +** function returns the patched blob. +*/ +static void rbuFossilDeltaFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *aDelta; + int nDelta; + const char *aOrig; + int nOrig; + + int nOut; + int nOut2; + char *aOut; + + assert( argc==2 ); + + nOrig = sqlite3_value_bytes(argv[0]); + aOrig = (const char*)sqlite3_value_blob(argv[0]); + nDelta = sqlite3_value_bytes(argv[1]); + aDelta = (const char*)sqlite3_value_blob(argv[1]); + + /* Figure out the size of the output */ + nOut = rbuDeltaOutputSize(aDelta, nDelta); + if( nOut<0 ){ + sqlite3_result_error(context, "corrupt fossil delta", -1); + return; + } + + aOut = sqlite3_malloc(nOut+1); + if( aOut==0 ){ + sqlite3_result_error_nomem(context); + }else{ + nOut2 = rbuDeltaApply(aOrig, nOrig, aDelta, nDelta, aOut); + if( nOut2!=nOut ){ + sqlite3_free(aOut); + sqlite3_result_error(context, "corrupt fossil delta", -1); + }else{ + sqlite3_result_blob(context, aOut, nOut, sqlite3_free); + } + } +} + + +/* +** Prepare the SQL statement in buffer zSql against database handle db. +** If successful, set *ppStmt to point to the new statement and return +** SQLITE_OK. +** +** Otherwise, if an error does occur, set *ppStmt to NULL and return +** an SQLite error code. Additionally, set output variable *pzErrmsg to +** point to a buffer containing an error message. It is the responsibility +** of the caller to (eventually) free this buffer using sqlite3_free(). +*/ +static int prepareAndCollectError( + sqlite3 *db, + sqlite3_stmt **ppStmt, + char **pzErrmsg, + const char *zSql +){ + int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); + if( rc!=SQLITE_OK ){ + *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + *ppStmt = 0; + } + return rc; +} + +/* +** Reset the SQL statement passed as the first argument. Return a copy +** of the value returned by sqlite3_reset(). +** +** If an error has occurred, then set *pzErrmsg to point to a buffer +** containing an error message. It is the responsibility of the caller +** to eventually free this buffer using sqlite3_free(). +*/ +static int resetAndCollectError(sqlite3_stmt *pStmt, char **pzErrmsg){ + int rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK ){ + *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(sqlite3_db_handle(pStmt))); + } + return rc; +} + +/* +** Unless it is NULL, argument zSql points to a buffer allocated using +** sqlite3_malloc containing an SQL statement. This function prepares the SQL +** statement against database db and frees the buffer. If statement +** compilation is successful, *ppStmt is set to point to the new statement +** handle and SQLITE_OK is returned. +** +** Otherwise, if an error occurs, *ppStmt is set to NULL and an error code +** returned. In this case, *pzErrmsg may also be set to point to an error +** message. It is the responsibility of the caller to free this error message +** buffer using sqlite3_free(). +** +** If argument zSql is NULL, this function assumes that an OOM has occurred. +** In this case SQLITE_NOMEM is returned and *ppStmt set to NULL. +*/ +static int prepareFreeAndCollectError( + sqlite3 *db, + sqlite3_stmt **ppStmt, + char **pzErrmsg, + char *zSql +){ + int rc; + assert( *pzErrmsg==0 ); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + *ppStmt = 0; + }else{ + rc = prepareAndCollectError(db, ppStmt, pzErrmsg, zSql); + sqlite3_free(zSql); + } + return rc; +} + +/* +** Free the RbuObjIter.azTblCol[] and RbuObjIter.abTblPk[] arrays allocated +** by an earlier call to rbuObjIterCacheTableInfo(). +*/ +static void rbuObjIterFreeCols(RbuObjIter *pIter){ + int i; + for(i=0; i<pIter->nTblCol; i++){ + sqlite3_free(pIter->azTblCol[i]); + sqlite3_free(pIter->azTblType[i]); + } + sqlite3_free(pIter->azTblCol); + pIter->azTblCol = 0; + pIter->azTblType = 0; + pIter->aiSrcOrder = 0; + pIter->abTblPk = 0; + pIter->abNotNull = 0; + pIter->nTblCol = 0; + pIter->eType = 0; /* Invalid value */ +} + +/* +** Finalize all statements and free all allocations that are specific to +** the current object (table/index pair). +*/ +static void rbuObjIterClearStatements(RbuObjIter *pIter){ + RbuUpdateStmt *pUp; + + sqlite3_finalize(pIter->pSelect); + sqlite3_finalize(pIter->pInsert); + sqlite3_finalize(pIter->pDelete); + sqlite3_finalize(pIter->pTmpInsert); + pUp = pIter->pRbuUpdate; + while( pUp ){ + RbuUpdateStmt *pTmp = pUp->pNext; + sqlite3_finalize(pUp->pUpdate); + sqlite3_free(pUp); + pUp = pTmp; + } + sqlite3_free(pIter->aIdxCol); + sqlite3_free(pIter->zIdxSql); + + pIter->pSelect = 0; + pIter->pInsert = 0; + pIter->pDelete = 0; + pIter->pRbuUpdate = 0; + pIter->pTmpInsert = 0; + pIter->nCol = 0; + pIter->nIdxCol = 0; + pIter->aIdxCol = 0; + pIter->zIdxSql = 0; +} + +/* +** Clean up any resources allocated as part of the iterator object passed +** as the only argument. +*/ +static void rbuObjIterFinalize(RbuObjIter *pIter){ + rbuObjIterClearStatements(pIter); + sqlite3_finalize(pIter->pTblIter); + sqlite3_finalize(pIter->pIdxIter); + rbuObjIterFreeCols(pIter); + memset(pIter, 0, sizeof(RbuObjIter)); +} + +/* +** Advance the iterator to the next position. +** +** If no error occurs, SQLITE_OK is returned and the iterator is left +** pointing to the next entry. Otherwise, an error code and message is +** left in the RBU handle passed as the first argument. A copy of the +** error code is returned. +*/ +static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){ + int rc = p->rc; + if( rc==SQLITE_OK ){ + + /* Free any SQLite statements used while processing the previous object */ + rbuObjIterClearStatements(pIter); + if( pIter->zIdx==0 ){ + rc = sqlite3_exec(p->dbMain, + "DROP TRIGGER IF EXISTS temp.rbu_insert_tr;" + "DROP TRIGGER IF EXISTS temp.rbu_update1_tr;" + "DROP TRIGGER IF EXISTS temp.rbu_update2_tr;" + "DROP TRIGGER IF EXISTS temp.rbu_delete_tr;" + , 0, 0, &p->zErrmsg + ); + } + + if( rc==SQLITE_OK ){ + if( pIter->bCleanup ){ + rbuObjIterFreeCols(pIter); + pIter->bCleanup = 0; + rc = sqlite3_step(pIter->pTblIter); + if( rc!=SQLITE_ROW ){ + rc = resetAndCollectError(pIter->pTblIter, &p->zErrmsg); + pIter->zTbl = 0; + }else{ + pIter->zTbl = (const char*)sqlite3_column_text(pIter->pTblIter, 0); + pIter->zDataTbl = (const char*)sqlite3_column_text(pIter->pTblIter,1); + rc = (pIter->zDataTbl && pIter->zTbl) ? SQLITE_OK : SQLITE_NOMEM; + } + }else{ + if( pIter->zIdx==0 ){ + sqlite3_stmt *pIdx = pIter->pIdxIter; + rc = sqlite3_bind_text(pIdx, 1, pIter->zTbl, -1, SQLITE_STATIC); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_step(pIter->pIdxIter); + if( rc!=SQLITE_ROW ){ + rc = resetAndCollectError(pIter->pIdxIter, &p->zErrmsg); + pIter->bCleanup = 1; + pIter->zIdx = 0; + }else{ + pIter->zIdx = (const char*)sqlite3_column_text(pIter->pIdxIter, 0); + pIter->iTnum = sqlite3_column_int(pIter->pIdxIter, 1); + pIter->bUnique = sqlite3_column_int(pIter->pIdxIter, 2); + rc = pIter->zIdx ? SQLITE_OK : SQLITE_NOMEM; + } + } + } + } + } + + if( rc!=SQLITE_OK ){ + rbuObjIterFinalize(pIter); + p->rc = rc; + } + return rc; +} + + +/* +** The implementation of the rbu_target_name() SQL function. This function +** accepts one or two arguments. The first argument is the name of a table - +** the name of a table in the RBU database. The second, if it is present, is 1 +** for a view or 0 for a table. +** +** For a non-vacuum RBU handle, if the table name matches the pattern: +** +** data[0-9]_<name> +** +** where <name> is any sequence of 1 or more characters, <name> is returned. +** Otherwise, if the only argument does not match the above pattern, an SQL +** NULL is returned. +** +** "data_t1" -> "t1" +** "data0123_t2" -> "t2" +** "dataAB_t3" -> NULL +** +** For an rbu vacuum handle, a copy of the first argument is returned if +** the second argument is either missing or 0 (not a view). +*/ +static void rbuTargetNameFunc( + sqlite3_context *pCtx, + int argc, + sqlite3_value **argv +){ + sqlite3rbu *p = sqlite3_user_data(pCtx); + const char *zIn; + assert( argc==1 || argc==2 ); + + zIn = (const char*)sqlite3_value_text(argv[0]); + if( zIn ){ + if( rbuIsVacuum(p) ){ + assert( argc==2 || argc==1 ); + if( argc==1 || 0==sqlite3_value_int(argv[1]) ){ + sqlite3_result_text(pCtx, zIn, -1, SQLITE_STATIC); + } + }else{ + if( strlen(zIn)>4 && memcmp("data", zIn, 4)==0 ){ + int i; + for(i=4; zIn[i]>='0' && zIn[i]<='9'; i++); + if( zIn[i]=='_' && zIn[i+1] ){ + sqlite3_result_text(pCtx, &zIn[i+1], -1, SQLITE_STATIC); + } + } + } + } +} + +/* +** Initialize the iterator structure passed as the second argument. +** +** If no error occurs, SQLITE_OK is returned and the iterator is left +** pointing to the first entry. Otherwise, an error code and message is +** left in the RBU handle passed as the first argument. A copy of the +** error code is returned. +*/ +static int rbuObjIterFirst(sqlite3rbu *p, RbuObjIter *pIter){ + int rc; + memset(pIter, 0, sizeof(RbuObjIter)); + + rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pTblIter, &p->zErrmsg, + sqlite3_mprintf( + "SELECT rbu_target_name(name, type='view') AS target, name " + "FROM sqlite_schema " + "WHERE type IN ('table', 'view') AND target IS NOT NULL " + " %s " + "ORDER BY name" + , rbuIsVacuum(p) ? "AND rootpage!=0 AND rootpage IS NOT NULL" : "")); + + if( rc==SQLITE_OK ){ + rc = prepareAndCollectError(p->dbMain, &pIter->pIdxIter, &p->zErrmsg, + "SELECT name, rootpage, sql IS NULL OR substr(8, 6)=='UNIQUE' " + " FROM main.sqlite_schema " + " WHERE type='index' AND tbl_name = ?" + ); + } + + pIter->bCleanup = 1; + p->rc = rc; + return rbuObjIterNext(p, pIter); +} + +/* +** This is a wrapper around "sqlite3_mprintf(zFmt, ...)". If an OOM occurs, +** an error code is stored in the RBU handle passed as the first argument. +** +** If an error has already occurred (p->rc is already set to something other +** than SQLITE_OK), then this function returns NULL without modifying the +** stored error code. In this case it still calls sqlite3_free() on any +** printf() parameters associated with %z conversions. +*/ +static char *rbuMPrintf(sqlite3rbu *p, const char *zFmt, ...){ + char *zSql = 0; + va_list ap; + va_start(ap, zFmt); + zSql = sqlite3_vmprintf(zFmt, ap); + if( p->rc==SQLITE_OK ){ + if( zSql==0 ) p->rc = SQLITE_NOMEM; + }else{ + sqlite3_free(zSql); + zSql = 0; + } + va_end(ap); + return zSql; +} + +/* +** Argument zFmt is a sqlite3_mprintf() style format string. The trailing +** arguments are the usual subsitution values. This function performs +** the printf() style substitutions and executes the result as an SQL +** statement on the RBU handles database. +** +** If an error occurs, an error code and error message is stored in the +** RBU handle. If an error has already occurred when this function is +** called, it is a no-op. +*/ +static int rbuMPrintfExec(sqlite3rbu *p, sqlite3 *db, const char *zFmt, ...){ + va_list ap; + char *zSql; + va_start(ap, zFmt); + zSql = sqlite3_vmprintf(zFmt, ap); + if( p->rc==SQLITE_OK ){ + if( zSql==0 ){ + p->rc = SQLITE_NOMEM; + }else{ + p->rc = sqlite3_exec(db, zSql, 0, 0, &p->zErrmsg); + } + } + sqlite3_free(zSql); + va_end(ap); + return p->rc; +} + +/* +** Attempt to allocate and return a pointer to a zeroed block of nByte +** bytes. +** +** If an error (i.e. an OOM condition) occurs, return NULL and leave an +** error code in the rbu handle passed as the first argument. Or, if an +** error has already occurred when this function is called, return NULL +** immediately without attempting the allocation or modifying the stored +** error code. +*/ +static void *rbuMalloc(sqlite3rbu *p, sqlite3_int64 nByte){ + void *pRet = 0; + if( p->rc==SQLITE_OK ){ + assert( nByte>0 ); + pRet = sqlite3_malloc64(nByte); + if( pRet==0 ){ + p->rc = SQLITE_NOMEM; + }else{ + memset(pRet, 0, nByte); + } + } + return pRet; +} + + +/* +** Allocate and zero the pIter->azTblCol[] and abTblPk[] arrays so that +** there is room for at least nCol elements. If an OOM occurs, store an +** error code in the RBU handle passed as the first argument. +*/ +static void rbuAllocateIterArrays(sqlite3rbu *p, RbuObjIter *pIter, int nCol){ + sqlite3_int64 nByte = (2*sizeof(char*) + sizeof(int) + 3*sizeof(u8)) * nCol; + char **azNew; + + azNew = (char**)rbuMalloc(p, nByte); + if( azNew ){ + pIter->azTblCol = azNew; + pIter->azTblType = &azNew[nCol]; + pIter->aiSrcOrder = (int*)&pIter->azTblType[nCol]; + pIter->abTblPk = (u8*)&pIter->aiSrcOrder[nCol]; + pIter->abNotNull = (u8*)&pIter->abTblPk[nCol]; + pIter->abIndexed = (u8*)&pIter->abNotNull[nCol]; + } +} + +/* +** The first argument must be a nul-terminated string. This function +** returns a copy of the string in memory obtained from sqlite3_malloc(). +** It is the responsibility of the caller to eventually free this memory +** using sqlite3_free(). +** +** If an OOM condition is encountered when attempting to allocate memory, +** output variable (*pRc) is set to SQLITE_NOMEM before returning. Otherwise, +** if the allocation succeeds, (*pRc) is left unchanged. +*/ +static char *rbuStrndup(const char *zStr, int *pRc){ + char *zRet = 0; + + if( *pRc==SQLITE_OK ){ + if( zStr ){ + size_t nCopy = strlen(zStr) + 1; + zRet = (char*)sqlite3_malloc64(nCopy); + if( zRet ){ + memcpy(zRet, zStr, nCopy); + }else{ + *pRc = SQLITE_NOMEM; + } + } + } + + return zRet; +} + +/* +** Finalize the statement passed as the second argument. +** +** If the sqlite3_finalize() call indicates that an error occurs, and the +** rbu handle error code is not already set, set the error code and error +** message accordingly. +*/ +static void rbuFinalize(sqlite3rbu *p, sqlite3_stmt *pStmt){ + sqlite3 *db = sqlite3_db_handle(pStmt); + int rc = sqlite3_finalize(pStmt); + if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){ + p->rc = rc; + p->zErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + } +} + +/* Determine the type of a table. +** +** peType is of type (int*), a pointer to an output parameter of type +** (int). This call sets the output parameter as follows, depending +** on the type of the table specified by parameters dbName and zTbl. +** +** RBU_PK_NOTABLE: No such table. +** RBU_PK_NONE: Table has an implicit rowid. +** RBU_PK_IPK: Table has an explicit IPK column. +** RBU_PK_EXTERNAL: Table has an external PK index. +** RBU_PK_WITHOUT_ROWID: Table is WITHOUT ROWID. +** RBU_PK_VTAB: Table is a virtual table. +** +** Argument *piPk is also of type (int*), and also points to an output +** parameter. Unless the table has an external primary key index +** (i.e. unless *peType is set to 3), then *piPk is set to zero. Or, +** if the table does have an external primary key index, then *piPk +** is set to the root page number of the primary key index before +** returning. +** +** ALGORITHM: +** +** if( no entry exists in sqlite_schema ){ +** return RBU_PK_NOTABLE +** }else if( sql for the entry starts with "CREATE VIRTUAL" ){ +** return RBU_PK_VTAB +** }else if( "PRAGMA index_list()" for the table contains a "pk" index ){ +** if( the index that is the pk exists in sqlite_schema ){ +** *piPK = rootpage of that index. +** return RBU_PK_EXTERNAL +** }else{ +** return RBU_PK_WITHOUT_ROWID +** } +** }else if( "PRAGMA table_info()" lists one or more "pk" columns ){ +** return RBU_PK_IPK +** }else{ +** return RBU_PK_NONE +** } +*/ +static void rbuTableType( + sqlite3rbu *p, + const char *zTab, + int *peType, + int *piTnum, + int *piPk +){ + /* + ** 0) SELECT count(*) FROM sqlite_schema where name=%Q AND IsVirtual(%Q) + ** 1) PRAGMA index_list = ? + ** 2) SELECT count(*) FROM sqlite_schema where name=%Q + ** 3) PRAGMA table_info = ? + */ + sqlite3_stmt *aStmt[4] = {0, 0, 0, 0}; + + *peType = RBU_PK_NOTABLE; + *piPk = 0; + + assert( p->rc==SQLITE_OK ); + p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[0], &p->zErrmsg, + sqlite3_mprintf( + "SELECT " + " (sql COLLATE nocase BETWEEN 'CREATE VIRTUAL' AND 'CREATE VIRTUAM')," + " rootpage" + " FROM sqlite_schema" + " WHERE name=%Q", zTab + )); + if( p->rc!=SQLITE_OK || sqlite3_step(aStmt[0])!=SQLITE_ROW ){ + /* Either an error, or no such table. */ + goto rbuTableType_end; + } + if( sqlite3_column_int(aStmt[0], 0) ){ + *peType = RBU_PK_VTAB; /* virtual table */ + goto rbuTableType_end; + } + *piTnum = sqlite3_column_int(aStmt[0], 1); + + p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[1], &p->zErrmsg, + sqlite3_mprintf("PRAGMA index_list=%Q",zTab) + ); + if( p->rc ) goto rbuTableType_end; + while( sqlite3_step(aStmt[1])==SQLITE_ROW ){ + const u8 *zOrig = sqlite3_column_text(aStmt[1], 3); + const u8 *zIdx = sqlite3_column_text(aStmt[1], 1); + if( zOrig && zIdx && zOrig[0]=='p' ){ + p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[2], &p->zErrmsg, + sqlite3_mprintf( + "SELECT rootpage FROM sqlite_schema WHERE name = %Q", zIdx + )); + if( p->rc==SQLITE_OK ){ + if( sqlite3_step(aStmt[2])==SQLITE_ROW ){ + *piPk = sqlite3_column_int(aStmt[2], 0); + *peType = RBU_PK_EXTERNAL; + }else{ + *peType = RBU_PK_WITHOUT_ROWID; + } + } + goto rbuTableType_end; + } + } + + p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[3], &p->zErrmsg, + sqlite3_mprintf("PRAGMA table_info=%Q",zTab) + ); + if( p->rc==SQLITE_OK ){ + while( sqlite3_step(aStmt[3])==SQLITE_ROW ){ + if( sqlite3_column_int(aStmt[3],5)>0 ){ + *peType = RBU_PK_IPK; /* explicit IPK column */ + goto rbuTableType_end; + } + } + *peType = RBU_PK_NONE; + } + +rbuTableType_end: { + unsigned int i; + for(i=0; i<sizeof(aStmt)/sizeof(aStmt[0]); i++){ + rbuFinalize(p, aStmt[i]); + } + } +} + +/* +** This is a helper function for rbuObjIterCacheTableInfo(). It populates +** the pIter->abIndexed[] array. +*/ +static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){ + sqlite3_stmt *pList = 0; + int bIndex = 0; + + if( p->rc==SQLITE_OK ){ + memcpy(pIter->abIndexed, pIter->abTblPk, sizeof(u8)*pIter->nTblCol); + p->rc = prepareFreeAndCollectError(p->dbMain, &pList, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.index_list = %Q", pIter->zTbl) + ); + } + + pIter->nIndex = 0; + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pList) ){ + const char *zIdx = (const char*)sqlite3_column_text(pList, 1); + int bPartial = sqlite3_column_int(pList, 4); + sqlite3_stmt *pXInfo = 0; + if( zIdx==0 ) break; + if( bPartial ){ + memset(pIter->abIndexed, 0x01, sizeof(u8)*pIter->nTblCol); + } + p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx) + ); + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ + int iCid = sqlite3_column_int(pXInfo, 1); + if( iCid>=0 ) pIter->abIndexed[iCid] = 1; + if( iCid==-2 ){ + memset(pIter->abIndexed, 0x01, sizeof(u8)*pIter->nTblCol); + } + } + rbuFinalize(p, pXInfo); + bIndex = 1; + pIter->nIndex++; + } + + if( pIter->eType==RBU_PK_WITHOUT_ROWID ){ + /* "PRAGMA index_list" includes the main PK b-tree */ + pIter->nIndex--; + } + + rbuFinalize(p, pList); + if( bIndex==0 ) pIter->abIndexed = 0; +} + + +/* +** If they are not already populated, populate the pIter->azTblCol[], +** pIter->abTblPk[], pIter->nTblCol and pIter->bRowid variables according to +** the table (not index) that the iterator currently points to. +** +** Return SQLITE_OK if successful, or an SQLite error code otherwise. If +** an error does occur, an error code and error message are also left in +** the RBU handle. +*/ +static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ + if( pIter->azTblCol==0 ){ + sqlite3_stmt *pStmt = 0; + int nCol = 0; + int i; /* for() loop iterator variable */ + int bRbuRowid = 0; /* If input table has column "rbu_rowid" */ + int iOrder = 0; + int iTnum = 0; + + /* Figure out the type of table this step will deal with. */ + assert( pIter->eType==0 ); + rbuTableType(p, pIter->zTbl, &pIter->eType, &iTnum, &pIter->iPkTnum); + if( p->rc==SQLITE_OK && pIter->eType==RBU_PK_NOTABLE ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("no such table: %s", pIter->zTbl); + } + if( p->rc ) return p->rc; + if( pIter->zIdx==0 ) pIter->iTnum = iTnum; + + assert( pIter->eType==RBU_PK_NONE || pIter->eType==RBU_PK_IPK + || pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_WITHOUT_ROWID + || pIter->eType==RBU_PK_VTAB + ); + + /* Populate the azTblCol[] and nTblCol variables based on the columns + ** of the input table. Ignore any input table columns that begin with + ** "rbu_". */ + p->rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, + sqlite3_mprintf("SELECT * FROM '%q'", pIter->zDataTbl) + ); + if( p->rc==SQLITE_OK ){ + nCol = sqlite3_column_count(pStmt); + rbuAllocateIterArrays(p, pIter, nCol); + } + for(i=0; p->rc==SQLITE_OK && i<nCol; i++){ + const char *zName = (const char*)sqlite3_column_name(pStmt, i); + if( sqlite3_strnicmp("rbu_", zName, 4) ){ + char *zCopy = rbuStrndup(zName, &p->rc); + pIter->aiSrcOrder[pIter->nTblCol] = pIter->nTblCol; + pIter->azTblCol[pIter->nTblCol++] = zCopy; + } + else if( 0==sqlite3_stricmp("rbu_rowid", zName) ){ + bRbuRowid = 1; + } + } + sqlite3_finalize(pStmt); + pStmt = 0; + + if( p->rc==SQLITE_OK + && rbuIsVacuum(p)==0 + && bRbuRowid!=(pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) + ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf( + "table %q %s rbu_rowid column", pIter->zDataTbl, + (bRbuRowid ? "may not have" : "requires") + ); + } + + /* Check that all non-HIDDEN columns in the destination table are also + ** present in the input table. Populate the abTblPk[], azTblType[] and + ** aiTblOrder[] arrays at the same time. */ + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->dbMain, &pStmt, &p->zErrmsg, + sqlite3_mprintf("PRAGMA table_info(%Q)", pIter->zTbl) + ); + } + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zName = (const char*)sqlite3_column_text(pStmt, 1); + if( zName==0 ) break; /* An OOM - finalize() below returns S_NOMEM */ + for(i=iOrder; i<pIter->nTblCol; i++){ + if( 0==strcmp(zName, pIter->azTblCol[i]) ) break; + } + if( i==pIter->nTblCol ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("column missing from %q: %s", + pIter->zDataTbl, zName + ); + }else{ + int iPk = sqlite3_column_int(pStmt, 5); + int bNotNull = sqlite3_column_int(pStmt, 3); + const char *zType = (const char*)sqlite3_column_text(pStmt, 2); + + if( i!=iOrder ){ + SWAP(int, pIter->aiSrcOrder[i], pIter->aiSrcOrder[iOrder]); + SWAP(char*, pIter->azTblCol[i], pIter->azTblCol[iOrder]); + } + + pIter->azTblType[iOrder] = rbuStrndup(zType, &p->rc); + assert( iPk>=0 ); + pIter->abTblPk[iOrder] = (u8)iPk; + pIter->abNotNull[iOrder] = (u8)bNotNull || (iPk!=0); + iOrder++; + } + } + + rbuFinalize(p, pStmt); + rbuObjIterCacheIndexedCols(p, pIter); + assert( pIter->eType!=RBU_PK_VTAB || pIter->abIndexed==0 ); + assert( pIter->eType!=RBU_PK_VTAB || pIter->nIndex==0 ); + } + + return p->rc; +} + +/* +** This function constructs and returns a pointer to a nul-terminated +** string containing some SQL clause or list based on one or more of the +** column names currently stored in the pIter->azTblCol[] array. +*/ +static char *rbuObjIterGetCollist( + sqlite3rbu *p, /* RBU object */ + RbuObjIter *pIter /* Object iterator for column names */ +){ + char *zList = 0; + const char *zSep = ""; + int i; + for(i=0; i<pIter->nTblCol; i++){ + const char *z = pIter->azTblCol[i]; + zList = rbuMPrintf(p, "%z%s\"%w\"", zList, zSep, z); + zSep = ", "; + } + return zList; +} + +/* +** Return a comma separated list of the quoted PRIMARY KEY column names, +** in order, for the current table. Before each column name, add the text +** zPre. After each column name, add the zPost text. Use zSeparator as +** the separator text (usually ", "). +*/ +static char *rbuObjIterGetPkList( + sqlite3rbu *p, /* RBU object */ + RbuObjIter *pIter, /* Object iterator for column names */ + const char *zPre, /* Before each quoted column name */ + const char *zSeparator, /* Separator to use between columns */ + const char *zPost /* After each quoted column name */ +){ + int iPk = 1; + char *zRet = 0; + const char *zSep = ""; + while( 1 ){ + int i; + for(i=0; i<pIter->nTblCol; i++){ + if( (int)pIter->abTblPk[i]==iPk ){ + const char *zCol = pIter->azTblCol[i]; + zRet = rbuMPrintf(p, "%z%s%s\"%w\"%s", zRet, zSep, zPre, zCol, zPost); + zSep = zSeparator; + break; + } + } + if( i==pIter->nTblCol ) break; + iPk++; + } + return zRet; +} + +/* +** This function is called as part of restarting an RBU vacuum within +** stage 1 of the process (while the *-oal file is being built) while +** updating a table (not an index). The table may be a rowid table or +** a WITHOUT ROWID table. It queries the target database to find the +** largest key that has already been written to the target table and +** constructs a WHERE clause that can be used to extract the remaining +** rows from the source table. For a rowid table, the WHERE clause +** is of the form: +** +** "WHERE _rowid_ > ?" +** +** and for WITHOUT ROWID tables: +** +** "WHERE (key1, key2) > (?, ?)" +** +** Instead of "?" placeholders, the actual WHERE clauses created by +** this function contain literal SQL values. +*/ +static char *rbuVacuumTableStart( + sqlite3rbu *p, /* RBU handle */ + RbuObjIter *pIter, /* RBU iterator object */ + int bRowid, /* True for a rowid table */ + const char *zWrite /* Target table name prefix */ +){ + sqlite3_stmt *pMax = 0; + char *zRet = 0; + if( bRowid ){ + p->rc = prepareFreeAndCollectError(p->dbMain, &pMax, &p->zErrmsg, + sqlite3_mprintf( + "SELECT max(_rowid_) FROM \"%s%w\"", zWrite, pIter->zTbl + ) + ); + if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pMax) ){ + sqlite3_int64 iMax = sqlite3_column_int64(pMax, 0); + zRet = rbuMPrintf(p, " WHERE _rowid_ > %lld ", iMax); + } + rbuFinalize(p, pMax); + }else{ + char *zOrder = rbuObjIterGetPkList(p, pIter, "", ", ", " DESC"); + char *zSelect = rbuObjIterGetPkList(p, pIter, "quote(", "||','||", ")"); + char *zList = rbuObjIterGetPkList(p, pIter, "", ", ", ""); + + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->dbMain, &pMax, &p->zErrmsg, + sqlite3_mprintf( + "SELECT %s FROM \"%s%w\" ORDER BY %s LIMIT 1", + zSelect, zWrite, pIter->zTbl, zOrder + ) + ); + if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pMax) ){ + const char *zVal = (const char*)sqlite3_column_text(pMax, 0); + zRet = rbuMPrintf(p, " WHERE (%s) > (%s) ", zList, zVal); + } + rbuFinalize(p, pMax); + } + + sqlite3_free(zOrder); + sqlite3_free(zSelect); + sqlite3_free(zList); + } + return zRet; +} + +/* +** This function is called as part of restating an RBU vacuum when the +** current operation is writing content to an index. If possible, it +** queries the target index b-tree for the largest key already written to +** it, then composes and returns an expression that can be used in a WHERE +** clause to select the remaining required rows from the source table. +** It is only possible to return such an expression if: +** +** * The index contains no DESC columns, and +** * The last key written to the index before the operation was +** suspended does not contain any NULL values. +** +** The expression is of the form: +** +** (index-field1, index-field2, ...) > (?, ?, ...) +** +** except that the "?" placeholders are replaced with literal values. +** +** If the expression cannot be created, NULL is returned. In this case, +** the caller has to use an OFFSET clause to extract only the required +** rows from the sourct table, just as it does for an RBU update operation. +*/ +static char *rbuVacuumIndexStart( + sqlite3rbu *p, /* RBU handle */ + RbuObjIter *pIter /* RBU iterator object */ +){ + char *zOrder = 0; + char *zLhs = 0; + char *zSelect = 0; + char *zVector = 0; + char *zRet = 0; + int bFailed = 0; + const char *zSep = ""; + int iCol = 0; + sqlite3_stmt *pXInfo = 0; + + p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", pIter->zIdx) + ); + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ + int iCid = sqlite3_column_int(pXInfo, 1); + const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4); + const char *zCol; + if( sqlite3_column_int(pXInfo, 3) ){ + bFailed = 1; + break; + } + + if( iCid<0 ){ + if( pIter->eType==RBU_PK_IPK ){ + int i; + for(i=0; pIter->abTblPk[i]==0; i++); + assert( i<pIter->nTblCol ); + zCol = pIter->azTblCol[i]; + }else{ + zCol = "_rowid_"; + } + }else{ + zCol = pIter->azTblCol[iCid]; + } + + zLhs = rbuMPrintf(p, "%z%s \"%w\" COLLATE %Q", + zLhs, zSep, zCol, zCollate + ); + zOrder = rbuMPrintf(p, "%z%s \"rbu_imp_%d%w\" COLLATE %Q DESC", + zOrder, zSep, iCol, zCol, zCollate + ); + zSelect = rbuMPrintf(p, "%z%s quote(\"rbu_imp_%d%w\")", + zSelect, zSep, iCol, zCol + ); + zSep = ", "; + iCol++; + } + rbuFinalize(p, pXInfo); + if( bFailed ) goto index_start_out; + + if( p->rc==SQLITE_OK ){ + sqlite3_stmt *pSel = 0; + + p->rc = prepareFreeAndCollectError(p->dbMain, &pSel, &p->zErrmsg, + sqlite3_mprintf("SELECT %s FROM \"rbu_imp_%w\" ORDER BY %s LIMIT 1", + zSelect, pIter->zTbl, zOrder + ) + ); + if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSel) ){ + zSep = ""; + for(iCol=0; iCol<pIter->nCol; iCol++){ + const char *zQuoted = (const char*)sqlite3_column_text(pSel, iCol); + if( zQuoted==0 ){ + p->rc = SQLITE_NOMEM; + }else if( zQuoted[0]=='N' ){ + bFailed = 1; + break; + } + zVector = rbuMPrintf(p, "%z%s%s", zVector, zSep, zQuoted); + zSep = ", "; + } + + if( !bFailed ){ + zRet = rbuMPrintf(p, "(%s) > (%s)", zLhs, zVector); + } + } + rbuFinalize(p, pSel); + } + + index_start_out: + sqlite3_free(zOrder); + sqlite3_free(zSelect); + sqlite3_free(zVector); + sqlite3_free(zLhs); + return zRet; +} + +/* +** This function is used to create a SELECT list (the list of SQL +** expressions that follows a SELECT keyword) for a SELECT statement +** used to read from an data_xxx or rbu_tmp_xxx table while updating the +** index object currently indicated by the iterator object passed as the +** second argument. A "PRAGMA index_xinfo = <idxname>" statement is used +** to obtain the required information. +** +** If the index is of the following form: +** +** CREATE INDEX i1 ON t1(c, b COLLATE nocase); +** +** and "t1" is a table with an explicit INTEGER PRIMARY KEY column +** "ipk", the returned string is: +** +** "`c` COLLATE 'BINARY', `b` COLLATE 'NOCASE', `ipk` COLLATE 'BINARY'" +** +** As well as the returned string, three other malloc'd strings are +** returned via output parameters. As follows: +** +** pzImposterCols: ... +** pzImposterPk: ... +** pzWhere: ... +*/ +static char *rbuObjIterGetIndexCols( + sqlite3rbu *p, /* RBU object */ + RbuObjIter *pIter, /* Object iterator for column names */ + char **pzImposterCols, /* OUT: Columns for imposter table */ + char **pzImposterPk, /* OUT: Imposter PK clause */ + char **pzWhere, /* OUT: WHERE clause */ + int *pnBind /* OUT: Trbul number of columns */ +){ + int rc = p->rc; /* Error code */ + int rc2; /* sqlite3_finalize() return code */ + char *zRet = 0; /* String to return */ + char *zImpCols = 0; /* String to return via *pzImposterCols */ + char *zImpPK = 0; /* String to return via *pzImposterPK */ + char *zWhere = 0; /* String to return via *pzWhere */ + int nBind = 0; /* Value to return via *pnBind */ + const char *zCom = ""; /* Set to ", " later on */ + const char *zAnd = ""; /* Set to " AND " later on */ + sqlite3_stmt *pXInfo = 0; /* PRAGMA index_xinfo = ? */ + + if( rc==SQLITE_OK ){ + assert( p->zErrmsg==0 ); + rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", pIter->zIdx) + ); + } + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ + int iCid = sqlite3_column_int(pXInfo, 1); + int bDesc = sqlite3_column_int(pXInfo, 3); + const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4); + const char *zCol = 0; + const char *zType; + + if( iCid==-2 ){ + int iSeq = sqlite3_column_int(pXInfo, 0); + zRet = sqlite3_mprintf("%z%s(%.*s) COLLATE %Q", zRet, zCom, + pIter->aIdxCol[iSeq].nSpan, pIter->aIdxCol[iSeq].zSpan, zCollate + ); + zType = ""; + }else { + if( iCid<0 ){ + /* An integer primary key. If the table has an explicit IPK, use + ** its name. Otherwise, use "rbu_rowid". */ + if( pIter->eType==RBU_PK_IPK ){ + int i; + for(i=0; pIter->abTblPk[i]==0; i++); + assert( i<pIter->nTblCol ); + zCol = pIter->azTblCol[i]; + }else if( rbuIsVacuum(p) ){ + zCol = "_rowid_"; + }else{ + zCol = "rbu_rowid"; + } + zType = "INTEGER"; + }else{ + zCol = pIter->azTblCol[iCid]; + zType = pIter->azTblType[iCid]; + } + zRet = sqlite3_mprintf("%z%s\"%w\" COLLATE %Q", zRet, zCom,zCol,zCollate); + } + + if( pIter->bUnique==0 || sqlite3_column_int(pXInfo, 5) ){ + const char *zOrder = (bDesc ? " DESC" : ""); + zImpPK = sqlite3_mprintf("%z%s\"rbu_imp_%d%w\"%s", + zImpPK, zCom, nBind, zCol, zOrder + ); + } + zImpCols = sqlite3_mprintf("%z%s\"rbu_imp_%d%w\" %s COLLATE %Q", + zImpCols, zCom, nBind, zCol, zType, zCollate + ); + zWhere = sqlite3_mprintf( + "%z%s\"rbu_imp_%d%w\" IS ?", zWhere, zAnd, nBind, zCol + ); + if( zRet==0 || zImpPK==0 || zImpCols==0 || zWhere==0 ) rc = SQLITE_NOMEM; + zCom = ", "; + zAnd = " AND "; + nBind++; + } + + rc2 = sqlite3_finalize(pXInfo); + if( rc==SQLITE_OK ) rc = rc2; + + if( rc!=SQLITE_OK ){ + sqlite3_free(zRet); + sqlite3_free(zImpCols); + sqlite3_free(zImpPK); + sqlite3_free(zWhere); + zRet = 0; + zImpCols = 0; + zImpPK = 0; + zWhere = 0; + p->rc = rc; + } + + *pzImposterCols = zImpCols; + *pzImposterPk = zImpPK; + *pzWhere = zWhere; + *pnBind = nBind; + return zRet; +} + +/* +** Assuming the current table columns are "a", "b" and "c", and the zObj +** paramter is passed "old", return a string of the form: +** +** "old.a, old.b, old.b" +** +** With the column names escaped. +** +** For tables with implicit rowids - RBU_PK_EXTERNAL and RBU_PK_NONE, append +** the text ", old._rowid_" to the returned value. +*/ +static char *rbuObjIterGetOldlist( + sqlite3rbu *p, + RbuObjIter *pIter, + const char *zObj +){ + char *zList = 0; + if( p->rc==SQLITE_OK && pIter->abIndexed ){ + const char *zS = ""; + int i; + for(i=0; i<pIter->nTblCol; i++){ + if( pIter->abIndexed[i] ){ + const char *zCol = pIter->azTblCol[i]; + zList = sqlite3_mprintf("%z%s%s.\"%w\"", zList, zS, zObj, zCol); + }else{ + zList = sqlite3_mprintf("%z%sNULL", zList, zS); + } + zS = ", "; + if( zList==0 ){ + p->rc = SQLITE_NOMEM; + break; + } + } + + /* For a table with implicit rowids, append "old._rowid_" to the list. */ + if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ + zList = rbuMPrintf(p, "%z, %s._rowid_", zList, zObj); + } + } + return zList; +} + +/* +** Return an expression that can be used in a WHERE clause to match the +** primary key of the current table. For example, if the table is: +** +** CREATE TABLE t1(a, b, c, PRIMARY KEY(b, c)); +** +** Return the string: +** +** "b = ?1 AND c = ?2" +*/ +static char *rbuObjIterGetWhere( + sqlite3rbu *p, + RbuObjIter *pIter +){ + char *zList = 0; + if( pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE ){ + zList = rbuMPrintf(p, "_rowid_ = ?%d", pIter->nTblCol+1); + }else if( pIter->eType==RBU_PK_EXTERNAL ){ + const char *zSep = ""; + int i; + for(i=0; i<pIter->nTblCol; i++){ + if( pIter->abTblPk[i] ){ + zList = rbuMPrintf(p, "%z%sc%d=?%d", zList, zSep, i, i+1); + zSep = " AND "; + } + } + zList = rbuMPrintf(p, + "_rowid_ = (SELECT id FROM rbu_imposter2 WHERE %z)", zList + ); + + }else{ + const char *zSep = ""; + int i; + for(i=0; i<pIter->nTblCol; i++){ + if( pIter->abTblPk[i] ){ + const char *zCol = pIter->azTblCol[i]; + zList = rbuMPrintf(p, "%z%s\"%w\"=?%d", zList, zSep, zCol, i+1); + zSep = " AND "; + } + } + } + return zList; +} + +/* +** The SELECT statement iterating through the keys for the current object +** (p->objiter.pSelect) currently points to a valid row. However, there +** is something wrong with the rbu_control value in the rbu_control value +** stored in the (p->nCol+1)'th column. Set the error code and error message +** of the RBU handle to something reflecting this. +*/ +static void rbuBadControlError(sqlite3rbu *p){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("invalid rbu_control value"); +} + + +/* +** Return a nul-terminated string containing the comma separated list of +** assignments that should be included following the "SET" keyword of +** an UPDATE statement used to update the table object that the iterator +** passed as the second argument currently points to if the rbu_control +** column of the data_xxx table entry is set to zMask. +** +** The memory for the returned string is obtained from sqlite3_malloc(). +** It is the responsibility of the caller to eventually free it using +** sqlite3_free(). +** +** If an OOM error is encountered when allocating space for the new +** string, an error code is left in the rbu handle passed as the first +** argument and NULL is returned. Or, if an error has already occurred +** when this function is called, NULL is returned immediately, without +** attempting the allocation or modifying the stored error code. +*/ +static char *rbuObjIterGetSetlist( + sqlite3rbu *p, + RbuObjIter *pIter, + const char *zMask +){ + char *zList = 0; + if( p->rc==SQLITE_OK ){ + int i; + + if( (int)strlen(zMask)!=pIter->nTblCol ){ + rbuBadControlError(p); + }else{ + const char *zSep = ""; + for(i=0; i<pIter->nTblCol; i++){ + char c = zMask[pIter->aiSrcOrder[i]]; + if( c=='x' ){ + zList = rbuMPrintf(p, "%z%s\"%w\"=?%d", + zList, zSep, pIter->azTblCol[i], i+1 + ); + zSep = ", "; + } + else if( c=='d' ){ + zList = rbuMPrintf(p, "%z%s\"%w\"=rbu_delta(\"%w\", ?%d)", + zList, zSep, pIter->azTblCol[i], pIter->azTblCol[i], i+1 + ); + zSep = ", "; + } + else if( c=='f' ){ + zList = rbuMPrintf(p, "%z%s\"%w\"=rbu_fossil_delta(\"%w\", ?%d)", + zList, zSep, pIter->azTblCol[i], pIter->azTblCol[i], i+1 + ); + zSep = ", "; + } + } + } + } + return zList; +} + +/* +** Return a nul-terminated string consisting of nByte comma separated +** "?" expressions. For example, if nByte is 3, return a pointer to +** a buffer containing the string "?,?,?". +** +** The memory for the returned string is obtained from sqlite3_malloc(). +** It is the responsibility of the caller to eventually free it using +** sqlite3_free(). +** +** If an OOM error is encountered when allocating space for the new +** string, an error code is left in the rbu handle passed as the first +** argument and NULL is returned. Or, if an error has already occurred +** when this function is called, NULL is returned immediately, without +** attempting the allocation or modifying the stored error code. +*/ +static char *rbuObjIterGetBindlist(sqlite3rbu *p, int nBind){ + char *zRet = 0; + sqlite3_int64 nByte = 2*(sqlite3_int64)nBind + 1; + + zRet = (char*)rbuMalloc(p, nByte); + if( zRet ){ + int i; + for(i=0; i<nBind; i++){ + zRet[i*2] = '?'; + zRet[i*2+1] = (i+1==nBind) ? '\0' : ','; + } + } + return zRet; +} + +/* +** The iterator currently points to a table (not index) of type +** RBU_PK_WITHOUT_ROWID. This function creates the PRIMARY KEY +** declaration for the corresponding imposter table. For example, +** if the iterator points to a table created as: +** +** CREATE TABLE t1(a, b, c, PRIMARY KEY(b, a DESC)) WITHOUT ROWID +** +** this function returns: +** +** PRIMARY KEY("b", "a" DESC) +*/ +static char *rbuWithoutRowidPK(sqlite3rbu *p, RbuObjIter *pIter){ + char *z = 0; + assert( pIter->zIdx==0 ); + if( p->rc==SQLITE_OK ){ + const char *zSep = "PRIMARY KEY("; + sqlite3_stmt *pXList = 0; /* PRAGMA index_list = (pIter->zTbl) */ + sqlite3_stmt *pXInfo = 0; /* PRAGMA index_xinfo = <pk-index> */ + + p->rc = prepareFreeAndCollectError(p->dbMain, &pXList, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.index_list = %Q", pIter->zTbl) + ); + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXList) ){ + const char *zOrig = (const char*)sqlite3_column_text(pXList,3); + if( zOrig && strcmp(zOrig, "pk")==0 ){ + const char *zIdx = (const char*)sqlite3_column_text(pXList,1); + if( zIdx ){ + p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx) + ); + } + break; + } + } + rbuFinalize(p, pXList); + + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ + if( sqlite3_column_int(pXInfo, 5) ){ + /* int iCid = sqlite3_column_int(pXInfo, 0); */ + const char *zCol = (const char*)sqlite3_column_text(pXInfo, 2); + const char *zDesc = sqlite3_column_int(pXInfo, 3) ? " DESC" : ""; + z = rbuMPrintf(p, "%z%s\"%w\"%s", z, zSep, zCol, zDesc); + zSep = ", "; + } + } + z = rbuMPrintf(p, "%z)", z); + rbuFinalize(p, pXInfo); + } + return z; +} + +/* +** This function creates the second imposter table used when writing to +** a table b-tree where the table has an external primary key. If the +** iterator passed as the second argument does not currently point to +** a table (not index) with an external primary key, this function is a +** no-op. +** +** Assuming the iterator does point to a table with an external PK, this +** function creates a WITHOUT ROWID imposter table named "rbu_imposter2" +** used to access that PK index. For example, if the target table is +** declared as follows: +** +** CREATE TABLE t1(a, b TEXT, c REAL, PRIMARY KEY(b, c)); +** +** then the imposter table schema is: +** +** CREATE TABLE rbu_imposter2(c1 TEXT, c2 REAL, id INTEGER) WITHOUT ROWID; +** +*/ +static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){ + if( p->rc==SQLITE_OK && pIter->eType==RBU_PK_EXTERNAL ){ + int tnum = pIter->iPkTnum; /* Root page of PK index */ + sqlite3_stmt *pQuery = 0; /* SELECT name ... WHERE rootpage = $tnum */ + const char *zIdx = 0; /* Name of PK index */ + sqlite3_stmt *pXInfo = 0; /* PRAGMA main.index_xinfo = $zIdx */ + const char *zComma = ""; + char *zCols = 0; /* Used to build up list of table cols */ + char *zPk = 0; /* Used to build up table PK declaration */ + + /* Figure out the name of the primary key index for the current table. + ** This is needed for the argument to "PRAGMA index_xinfo". Set + ** zIdx to point to a nul-terminated string containing this name. */ + p->rc = prepareAndCollectError(p->dbMain, &pQuery, &p->zErrmsg, + "SELECT name FROM sqlite_schema WHERE rootpage = ?" + ); + if( p->rc==SQLITE_OK ){ + sqlite3_bind_int(pQuery, 1, tnum); + if( SQLITE_ROW==sqlite3_step(pQuery) ){ + zIdx = (const char*)sqlite3_column_text(pQuery, 0); + } + } + if( zIdx ){ + p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx) + ); + } + rbuFinalize(p, pQuery); + + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ + int bKey = sqlite3_column_int(pXInfo, 5); + if( bKey ){ + int iCid = sqlite3_column_int(pXInfo, 1); + int bDesc = sqlite3_column_int(pXInfo, 3); + const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4); + zCols = rbuMPrintf(p, "%z%sc%d %s COLLATE %Q", zCols, zComma, + iCid, pIter->azTblType[iCid], zCollate + ); + zPk = rbuMPrintf(p, "%z%sc%d%s", zPk, zComma, iCid, bDesc?" DESC":""); + zComma = ", "; + } + } + zCols = rbuMPrintf(p, "%z, id INTEGER", zCols); + rbuFinalize(p, pXInfo); + + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1, tnum); + rbuMPrintfExec(p, p->dbMain, + "CREATE TABLE rbu_imposter2(%z, PRIMARY KEY(%z)) WITHOUT ROWID", + zCols, zPk + ); + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0); + } +} + +/* +** If an error has already occurred when this function is called, it +** immediately returns zero (without doing any work). Or, if an error +** occurs during the execution of this function, it sets the error code +** in the sqlite3rbu object indicated by the first argument and returns +** zero. +** +** The iterator passed as the second argument is guaranteed to point to +** a table (not an index) when this function is called. This function +** attempts to create any imposter table required to write to the main +** table b-tree of the table before returning. Non-zero is returned if +** an imposter table are created, or zero otherwise. +** +** An imposter table is required in all cases except RBU_PK_VTAB. Only +** virtual tables are written to directly. The imposter table has the +** same schema as the actual target table (less any UNIQUE constraints). +** More precisely, the "same schema" means the same columns, types, +** collation sequences. For tables that do not have an external PRIMARY +** KEY, it also means the same PRIMARY KEY declaration. +*/ +static void rbuCreateImposterTable(sqlite3rbu *p, RbuObjIter *pIter){ + if( p->rc==SQLITE_OK && pIter->eType!=RBU_PK_VTAB ){ + int tnum = pIter->iTnum; + const char *zComma = ""; + char *zSql = 0; + int iCol; + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 1); + + for(iCol=0; p->rc==SQLITE_OK && iCol<pIter->nTblCol; iCol++){ + const char *zPk = ""; + const char *zCol = pIter->azTblCol[iCol]; + const char *zColl = 0; + + p->rc = sqlite3_table_column_metadata( + p->dbMain, "main", pIter->zTbl, zCol, 0, &zColl, 0, 0, 0 + ); + + if( pIter->eType==RBU_PK_IPK && pIter->abTblPk[iCol] ){ + /* If the target table column is an "INTEGER PRIMARY KEY", add + ** "PRIMARY KEY" to the imposter table column declaration. */ + zPk = "PRIMARY KEY "; + } + zSql = rbuMPrintf(p, "%z%s\"%w\" %s %sCOLLATE %Q%s", + zSql, zComma, zCol, pIter->azTblType[iCol], zPk, zColl, + (pIter->abNotNull[iCol] ? " NOT NULL" : "") + ); + zComma = ", "; + } + + if( pIter->eType==RBU_PK_WITHOUT_ROWID ){ + char *zPk = rbuWithoutRowidPK(p, pIter); + if( zPk ){ + zSql = rbuMPrintf(p, "%z, %z", zSql, zPk); + } + } + + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1, tnum); + rbuMPrintfExec(p, p->dbMain, "CREATE TABLE \"rbu_imp_%w\"(%z)%s", + pIter->zTbl, zSql, + (pIter->eType==RBU_PK_WITHOUT_ROWID ? " WITHOUT ROWID" : "") + ); + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0); + } +} + +/* +** Prepare a statement used to insert rows into the "rbu_tmp_xxx" table. +** Specifically a statement of the form: +** +** INSERT INTO rbu_tmp_xxx VALUES(?, ?, ? ...); +** +** The number of bound variables is equal to the number of columns in +** the target table, plus one (for the rbu_control column), plus one more +** (for the rbu_rowid column) if the target table is an implicit IPK or +** virtual table. +*/ +static void rbuObjIterPrepareTmpInsert( + sqlite3rbu *p, + RbuObjIter *pIter, + const char *zCollist, + const char *zRbuRowid +){ + int bRbuRowid = (pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE); + char *zBind = rbuObjIterGetBindlist(p, pIter->nTblCol + 1 + bRbuRowid); + if( zBind ){ + assert( pIter->pTmpInsert==0 ); + p->rc = prepareFreeAndCollectError( + p->dbRbu, &pIter->pTmpInsert, &p->zErrmsg, sqlite3_mprintf( + "INSERT INTO %s.'rbu_tmp_%q'(rbu_control,%s%s) VALUES(%z)", + p->zStateDb, pIter->zDataTbl, zCollist, zRbuRowid, zBind + )); + } +} + +static void rbuTmpInsertFunc( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + sqlite3rbu *p = sqlite3_user_data(pCtx); + int rc = SQLITE_OK; + int i; + + assert( sqlite3_value_int(apVal[0])!=0 + || p->objiter.eType==RBU_PK_EXTERNAL + || p->objiter.eType==RBU_PK_NONE + ); + if( sqlite3_value_int(apVal[0])!=0 ){ + p->nPhaseOneStep += p->objiter.nIndex; + } + + for(i=0; rc==SQLITE_OK && i<nVal; i++){ + rc = sqlite3_bind_value(p->objiter.pTmpInsert, i+1, apVal[i]); + } + if( rc==SQLITE_OK ){ + sqlite3_step(p->objiter.pTmpInsert); + rc = sqlite3_reset(p->objiter.pTmpInsert); + } + + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(pCtx, rc); + } +} + +static char *rbuObjIterGetIndexWhere(sqlite3rbu *p, RbuObjIter *pIter){ + sqlite3_stmt *pStmt = 0; + int rc = p->rc; + char *zRet = 0; + + assert( pIter->zIdxSql==0 && pIter->nIdxCol==0 && pIter->aIdxCol==0 ); + + if( rc==SQLITE_OK ){ + rc = prepareAndCollectError(p->dbMain, &pStmt, &p->zErrmsg, + "SELECT trim(sql) FROM sqlite_schema WHERE type='index' AND name=?" + ); + } + if( rc==SQLITE_OK ){ + int rc2; + rc = sqlite3_bind_text(pStmt, 1, pIter->zIdx, -1, SQLITE_STATIC); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + char *zSql = (char*)sqlite3_column_text(pStmt, 0); + if( zSql ){ + pIter->zIdxSql = zSql = rbuStrndup(zSql, &rc); + } + if( zSql ){ + int nParen = 0; /* Number of open parenthesis */ + int i; + int iIdxCol = 0; + int nIdxAlloc = 0; + for(i=0; zSql[i]; i++){ + char c = zSql[i]; + + /* If necessary, grow the pIter->aIdxCol[] array */ + if( iIdxCol==nIdxAlloc ){ + RbuSpan *aIdxCol = (RbuSpan*)sqlite3_realloc( + pIter->aIdxCol, (nIdxAlloc+16)*sizeof(RbuSpan) + ); + if( aIdxCol==0 ){ + rc = SQLITE_NOMEM; + break; + } + pIter->aIdxCol = aIdxCol; + nIdxAlloc += 16; + } + + if( c=='(' ){ + if( nParen==0 ){ + assert( iIdxCol==0 ); + pIter->aIdxCol[0].zSpan = &zSql[i+1]; + } + nParen++; + } + else if( c==')' ){ + nParen--; + if( nParen==0 ){ + int nSpan = &zSql[i] - pIter->aIdxCol[iIdxCol].zSpan; + pIter->aIdxCol[iIdxCol++].nSpan = nSpan; + i++; + break; + } + }else if( c==',' && nParen==1 ){ + int nSpan = &zSql[i] - pIter->aIdxCol[iIdxCol].zSpan; + pIter->aIdxCol[iIdxCol++].nSpan = nSpan; + pIter->aIdxCol[iIdxCol].zSpan = &zSql[i+1]; + }else if( c=='"' || c=='\'' || c=='`' ){ + for(i++; 1; i++){ + if( zSql[i]==c ){ + if( zSql[i+1]!=c ) break; + i++; + } + } + }else if( c=='[' ){ + for(i++; 1; i++){ + if( zSql[i]==']' ) break; + } + }else if( c=='-' && zSql[i+1]=='-' ){ + for(i=i+2; zSql[i] && zSql[i]!='\n'; i++); + if( zSql[i]=='\0' ) break; + }else if( c=='/' && zSql[i+1]=='*' ){ + for(i=i+2; zSql[i] && (zSql[i]!='*' || zSql[i+1]!='/'); i++); + if( zSql[i]=='\0' ) break; + i++; + } + } + if( zSql[i] ){ + zRet = rbuStrndup(&zSql[i], &rc); + } + pIter->nIdxCol = iIdxCol; + } + } + + rc2 = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ) rc = rc2; + } + + p->rc = rc; + return zRet; +} + +/* +** Ensure that the SQLite statement handles required to update the +** target database object currently indicated by the iterator passed +** as the second argument are available. +*/ +static int rbuObjIterPrepareAll( + sqlite3rbu *p, + RbuObjIter *pIter, + int nOffset /* Add "LIMIT -1 OFFSET $nOffset" to SELECT */ +){ + assert( pIter->bCleanup==0 ); + if( pIter->pSelect==0 && rbuObjIterCacheTableInfo(p, pIter)==SQLITE_OK ){ + const int tnum = pIter->iTnum; + char *zCollist = 0; /* List of indexed columns */ + char **pz = &p->zErrmsg; + const char *zIdx = pIter->zIdx; + char *zLimit = 0; + + if( nOffset ){ + zLimit = sqlite3_mprintf(" LIMIT -1 OFFSET %d", nOffset); + if( !zLimit ) p->rc = SQLITE_NOMEM; + } + + if( zIdx ){ + const char *zTbl = pIter->zTbl; + char *zImposterCols = 0; /* Columns for imposter table */ + char *zImposterPK = 0; /* Primary key declaration for imposter */ + char *zWhere = 0; /* WHERE clause on PK columns */ + char *zBind = 0; + char *zPart = 0; + int nBind = 0; + + assert( pIter->eType!=RBU_PK_VTAB ); + zPart = rbuObjIterGetIndexWhere(p, pIter); + zCollist = rbuObjIterGetIndexCols( + p, pIter, &zImposterCols, &zImposterPK, &zWhere, &nBind + ); + zBind = rbuObjIterGetBindlist(p, nBind); + + /* Create the imposter table used to write to this index. */ + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 1); + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1,tnum); + rbuMPrintfExec(p, p->dbMain, + "CREATE TABLE \"rbu_imp_%w\"( %s, PRIMARY KEY( %s ) ) WITHOUT ROWID", + zTbl, zImposterCols, zImposterPK + ); + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0); + + /* Create the statement to insert index entries */ + pIter->nCol = nBind; + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError( + p->dbMain, &pIter->pInsert, &p->zErrmsg, + sqlite3_mprintf("INSERT INTO \"rbu_imp_%w\" VALUES(%s)", zTbl, zBind) + ); + } + + /* And to delete index entries */ + if( rbuIsVacuum(p)==0 && p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError( + p->dbMain, &pIter->pDelete, &p->zErrmsg, + sqlite3_mprintf("DELETE FROM \"rbu_imp_%w\" WHERE %s", zTbl, zWhere) + ); + } + + /* Create the SELECT statement to read keys in sorted order */ + if( p->rc==SQLITE_OK ){ + char *zSql; + if( rbuIsVacuum(p) ){ + char *zStart = 0; + if( nOffset ){ + zStart = rbuVacuumIndexStart(p, pIter); + if( zStart ){ + sqlite3_free(zLimit); + zLimit = 0; + } + } + + zSql = sqlite3_mprintf( + "SELECT %s, 0 AS rbu_control FROM '%q' %s %s %s ORDER BY %s%s", + zCollist, + pIter->zDataTbl, + zPart, + (zStart ? (zPart ? "AND" : "WHERE") : ""), zStart, + zCollist, zLimit + ); + sqlite3_free(zStart); + }else + + if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ + zSql = sqlite3_mprintf( + "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' %s ORDER BY %s%s", + zCollist, p->zStateDb, pIter->zDataTbl, + zPart, zCollist, zLimit + ); + }else{ + zSql = sqlite3_mprintf( + "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' %s " + "UNION ALL " + "SELECT %s, rbu_control FROM '%q' " + "%s %s typeof(rbu_control)='integer' AND rbu_control!=1 " + "ORDER BY %s%s", + zCollist, p->zStateDb, pIter->zDataTbl, zPart, + zCollist, pIter->zDataTbl, + zPart, + (zPart ? "AND" : "WHERE"), + zCollist, zLimit + ); + } + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->dbRbu,&pIter->pSelect,pz,zSql); + }else{ + sqlite3_free(zSql); + } + } + + sqlite3_free(zImposterCols); + sqlite3_free(zImposterPK); + sqlite3_free(zWhere); + sqlite3_free(zBind); + sqlite3_free(zPart); + }else{ + int bRbuRowid = (pIter->eType==RBU_PK_VTAB) + ||(pIter->eType==RBU_PK_NONE) + ||(pIter->eType==RBU_PK_EXTERNAL && rbuIsVacuum(p)); + const char *zTbl = pIter->zTbl; /* Table this step applies to */ + const char *zWrite; /* Imposter table name */ + + char *zBindings = rbuObjIterGetBindlist(p, pIter->nTblCol + bRbuRowid); + char *zWhere = rbuObjIterGetWhere(p, pIter); + char *zOldlist = rbuObjIterGetOldlist(p, pIter, "old"); + char *zNewlist = rbuObjIterGetOldlist(p, pIter, "new"); + + zCollist = rbuObjIterGetCollist(p, pIter); + pIter->nCol = pIter->nTblCol; + + /* Create the imposter table or tables (if required). */ + rbuCreateImposterTable(p, pIter); + rbuCreateImposterTable2(p, pIter); + zWrite = (pIter->eType==RBU_PK_VTAB ? "" : "rbu_imp_"); + + /* Create the INSERT statement to write to the target PK b-tree */ + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->dbMain, &pIter->pInsert, pz, + sqlite3_mprintf( + "INSERT INTO \"%s%w\"(%s%s) VALUES(%s)", + zWrite, zTbl, zCollist, (bRbuRowid ? ", _rowid_" : ""), zBindings + ) + ); + } + + /* Create the DELETE statement to write to the target PK b-tree. + ** Because it only performs INSERT operations, this is not required for + ** an rbu vacuum handle. */ + if( rbuIsVacuum(p)==0 && p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->dbMain, &pIter->pDelete, pz, + sqlite3_mprintf( + "DELETE FROM \"%s%w\" WHERE %s", zWrite, zTbl, zWhere + ) + ); + } + + if( rbuIsVacuum(p)==0 && pIter->abIndexed ){ + const char *zRbuRowid = ""; + if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ + zRbuRowid = ", rbu_rowid"; + } + + /* Create the rbu_tmp_xxx table and the triggers to populate it. */ + rbuMPrintfExec(p, p->dbRbu, + "CREATE TABLE IF NOT EXISTS %s.'rbu_tmp_%q' AS " + "SELECT *%s FROM '%q' WHERE 0;" + , p->zStateDb, pIter->zDataTbl + , (pIter->eType==RBU_PK_EXTERNAL ? ", 0 AS rbu_rowid" : "") + , pIter->zDataTbl + ); + + rbuMPrintfExec(p, p->dbMain, + "CREATE TEMP TRIGGER rbu_delete_tr BEFORE DELETE ON \"%s%w\" " + "BEGIN " + " SELECT rbu_tmp_insert(3, %s);" + "END;" + + "CREATE TEMP TRIGGER rbu_update1_tr BEFORE UPDATE ON \"%s%w\" " + "BEGIN " + " SELECT rbu_tmp_insert(3, %s);" + "END;" + + "CREATE TEMP TRIGGER rbu_update2_tr AFTER UPDATE ON \"%s%w\" " + "BEGIN " + " SELECT rbu_tmp_insert(4, %s);" + "END;", + zWrite, zTbl, zOldlist, + zWrite, zTbl, zOldlist, + zWrite, zTbl, zNewlist + ); + + if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ + rbuMPrintfExec(p, p->dbMain, + "CREATE TEMP TRIGGER rbu_insert_tr AFTER INSERT ON \"%s%w\" " + "BEGIN " + " SELECT rbu_tmp_insert(0, %s);" + "END;", + zWrite, zTbl, zNewlist + ); + } + + rbuObjIterPrepareTmpInsert(p, pIter, zCollist, zRbuRowid); + } + + /* Create the SELECT statement to read keys from data_xxx */ + if( p->rc==SQLITE_OK ){ + const char *zRbuRowid = ""; + char *zStart = 0; + char *zOrder = 0; + if( bRbuRowid ){ + zRbuRowid = rbuIsVacuum(p) ? ",_rowid_ " : ",rbu_rowid"; + } + + if( rbuIsVacuum(p) ){ + if( nOffset ){ + zStart = rbuVacuumTableStart(p, pIter, bRbuRowid, zWrite); + if( zStart ){ + sqlite3_free(zLimit); + zLimit = 0; + } + } + if( bRbuRowid ){ + zOrder = rbuMPrintf(p, "_rowid_"); + }else{ + zOrder = rbuObjIterGetPkList(p, pIter, "", ", ", ""); + } + } + + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, + sqlite3_mprintf( + "SELECT %s,%s rbu_control%s FROM '%q'%s %s %s %s", + zCollist, + (rbuIsVacuum(p) ? "0 AS " : ""), + zRbuRowid, + pIter->zDataTbl, (zStart ? zStart : ""), + (zOrder ? "ORDER BY" : ""), zOrder, + zLimit + ) + ); + } + sqlite3_free(zStart); + sqlite3_free(zOrder); + } + + sqlite3_free(zWhere); + sqlite3_free(zOldlist); + sqlite3_free(zNewlist); + sqlite3_free(zBindings); + } + sqlite3_free(zCollist); + sqlite3_free(zLimit); + } + + return p->rc; +} + +/* +** Set output variable *ppStmt to point to an UPDATE statement that may +** be used to update the imposter table for the main table b-tree of the +** table object that pIter currently points to, assuming that the +** rbu_control column of the data_xyz table contains zMask. +** +** If the zMask string does not specify any columns to update, then this +** is not an error. Output variable *ppStmt is set to NULL in this case. +*/ +static int rbuGetUpdateStmt( + sqlite3rbu *p, /* RBU handle */ + RbuObjIter *pIter, /* Object iterator */ + const char *zMask, /* rbu_control value ('x.x.') */ + sqlite3_stmt **ppStmt /* OUT: UPDATE statement handle */ +){ + RbuUpdateStmt **pp; + RbuUpdateStmt *pUp = 0; + int nUp = 0; + + /* In case an error occurs */ + *ppStmt = 0; + + /* Search for an existing statement. If one is found, shift it to the front + ** of the LRU queue and return immediately. Otherwise, leave nUp pointing + ** to the number of statements currently in the cache and pUp to the + ** last object in the list. */ + for(pp=&pIter->pRbuUpdate; *pp; pp=&((*pp)->pNext)){ + pUp = *pp; + if( strcmp(pUp->zMask, zMask)==0 ){ + *pp = pUp->pNext; + pUp->pNext = pIter->pRbuUpdate; + pIter->pRbuUpdate = pUp; + *ppStmt = pUp->pUpdate; + return SQLITE_OK; + } + nUp++; + } + assert( pUp==0 || pUp->pNext==0 ); + + if( nUp>=SQLITE_RBU_UPDATE_CACHESIZE ){ + for(pp=&pIter->pRbuUpdate; *pp!=pUp; pp=&((*pp)->pNext)); + *pp = 0; + sqlite3_finalize(pUp->pUpdate); + pUp->pUpdate = 0; + }else{ + pUp = (RbuUpdateStmt*)rbuMalloc(p, sizeof(RbuUpdateStmt)+pIter->nTblCol+1); + } + + if( pUp ){ + char *zWhere = rbuObjIterGetWhere(p, pIter); + char *zSet = rbuObjIterGetSetlist(p, pIter, zMask); + char *zUpdate = 0; + + pUp->zMask = (char*)&pUp[1]; + memcpy(pUp->zMask, zMask, pIter->nTblCol); + pUp->pNext = pIter->pRbuUpdate; + pIter->pRbuUpdate = pUp; + + if( zSet ){ + const char *zPrefix = ""; + + if( pIter->eType!=RBU_PK_VTAB ) zPrefix = "rbu_imp_"; + zUpdate = sqlite3_mprintf("UPDATE \"%s%w\" SET %s WHERE %s", + zPrefix, pIter->zTbl, zSet, zWhere + ); + p->rc = prepareFreeAndCollectError( + p->dbMain, &pUp->pUpdate, &p->zErrmsg, zUpdate + ); + *ppStmt = pUp->pUpdate; + } + sqlite3_free(zWhere); + sqlite3_free(zSet); + } + + return p->rc; +} + +static sqlite3 *rbuOpenDbhandle( + sqlite3rbu *p, + const char *zName, + int bUseVfs +){ + sqlite3 *db = 0; + if( p->rc==SQLITE_OK ){ + const int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_URI; + p->rc = sqlite3_open_v2(zName, &db, flags, bUseVfs ? p->zVfsName : 0); + if( p->rc ){ + p->zErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + sqlite3_close(db); + db = 0; + } + } + return db; +} + +/* +** Free an RbuState object allocated by rbuLoadState(). +*/ +static void rbuFreeState(RbuState *p){ + if( p ){ + sqlite3_free(p->zTbl); + sqlite3_free(p->zDataTbl); + sqlite3_free(p->zIdx); + sqlite3_free(p); + } +} + +/* +** Allocate an RbuState object and load the contents of the rbu_state +** table into it. Return a pointer to the new object. It is the +** responsibility of the caller to eventually free the object using +** sqlite3_free(). +** +** If an error occurs, leave an error code and message in the rbu handle +** and return NULL. +*/ +static RbuState *rbuLoadState(sqlite3rbu *p){ + RbuState *pRet = 0; + sqlite3_stmt *pStmt = 0; + int rc; + int rc2; + + pRet = (RbuState*)rbuMalloc(p, sizeof(RbuState)); + if( pRet==0 ) return 0; + + rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, + sqlite3_mprintf("SELECT k, v FROM %s.rbu_state", p->zStateDb) + ); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + switch( sqlite3_column_int(pStmt, 0) ){ + case RBU_STATE_STAGE: + pRet->eStage = sqlite3_column_int(pStmt, 1); + if( pRet->eStage!=RBU_STAGE_OAL + && pRet->eStage!=RBU_STAGE_MOVE + && pRet->eStage!=RBU_STAGE_CKPT + ){ + p->rc = SQLITE_CORRUPT; + } + break; + + case RBU_STATE_TBL: + pRet->zTbl = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); + break; + + case RBU_STATE_IDX: + pRet->zIdx = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); + break; + + case RBU_STATE_ROW: + pRet->nRow = sqlite3_column_int(pStmt, 1); + break; + + case RBU_STATE_PROGRESS: + pRet->nProgress = sqlite3_column_int64(pStmt, 1); + break; + + case RBU_STATE_CKPT: + pRet->iWalCksum = sqlite3_column_int64(pStmt, 1); + break; + + case RBU_STATE_COOKIE: + pRet->iCookie = (u32)sqlite3_column_int64(pStmt, 1); + break; + + case RBU_STATE_OALSZ: + pRet->iOalSz = sqlite3_column_int64(pStmt, 1); + break; + + case RBU_STATE_PHASEONESTEP: + pRet->nPhaseOneStep = sqlite3_column_int64(pStmt, 1); + break; + + case RBU_STATE_DATATBL: + pRet->zDataTbl = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); + break; + + default: + rc = SQLITE_CORRUPT; + break; + } + } + rc2 = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ) rc = rc2; + + p->rc = rc; + return pRet; +} + + +/* +** Open the database handle and attach the RBU database as "rbu". If an +** error occurs, leave an error code and message in the RBU handle. +** +** If argument dbMain is not NULL, then it is a database handle already +** open on the target database. Use this handle instead of opening a new +** one. +*/ +static void rbuOpenDatabase(sqlite3rbu *p, sqlite3 *dbMain, int *pbRetry){ + assert( p->rc || (p->dbMain==0 && p->dbRbu==0) ); + assert( p->rc || rbuIsVacuum(p) || p->zTarget!=0 ); + assert( dbMain==0 || rbuIsVacuum(p)==0 ); + + /* Open the RBU database */ + p->dbRbu = rbuOpenDbhandle(p, p->zRbu, 1); + p->dbMain = dbMain; + + if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ + sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); + if( p->zState==0 ){ + const char *zFile = sqlite3_db_filename(p->dbRbu, "main"); + p->zState = rbuMPrintf(p, "file:///%s-vacuum?modeof=%s", zFile, zFile); + } + } + + /* If using separate RBU and state databases, attach the state database to + ** the RBU db handle now. */ + if( p->zState ){ + rbuMPrintfExec(p, p->dbRbu, "ATTACH %Q AS stat", p->zState); + memcpy(p->zStateDb, "stat", 4); + }else{ + memcpy(p->zStateDb, "main", 4); + } + +#if 0 + if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ + p->rc = sqlite3_exec(p->dbRbu, "BEGIN", 0, 0, 0); + } +#endif + + /* If it has not already been created, create the rbu_state table */ + rbuMPrintfExec(p, p->dbRbu, RBU_CREATE_STATE, p->zStateDb); + +#if 0 + if( rbuIsVacuum(p) ){ + if( p->rc==SQLITE_OK ){ + int rc2; + int bOk = 0; + sqlite3_stmt *pCnt = 0; + p->rc = prepareAndCollectError(p->dbRbu, &pCnt, &p->zErrmsg, + "SELECT count(*) FROM stat.sqlite_schema" + ); + if( p->rc==SQLITE_OK + && sqlite3_step(pCnt)==SQLITE_ROW + && 1==sqlite3_column_int(pCnt, 0) + ){ + bOk = 1; + } + rc2 = sqlite3_finalize(pCnt); + if( p->rc==SQLITE_OK ) p->rc = rc2; + + if( p->rc==SQLITE_OK && bOk==0 ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("invalid state database"); + } + + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0); + } + } + } +#endif + + if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ + int bOpen = 0; + int rc; + p->nRbu = 0; + p->pRbuFd = 0; + rc = sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); + if( rc!=SQLITE_NOTFOUND ) p->rc = rc; + if( p->eStage>=RBU_STAGE_MOVE ){ + bOpen = 1; + }else{ + RbuState *pState = rbuLoadState(p); + if( pState ){ + bOpen = (pState->eStage>=RBU_STAGE_MOVE); + rbuFreeState(pState); + } + } + if( bOpen ) p->dbMain = rbuOpenDbhandle(p, p->zRbu, p->nRbu<=1); + } + + p->eStage = 0; + if( p->rc==SQLITE_OK && p->dbMain==0 ){ + if( !rbuIsVacuum(p) ){ + p->dbMain = rbuOpenDbhandle(p, p->zTarget, 1); + }else if( p->pRbuFd->pWalFd ){ + if( pbRetry ){ + p->pRbuFd->bNolock = 0; + sqlite3_close(p->dbRbu); + sqlite3_close(p->dbMain); + p->dbMain = 0; + p->dbRbu = 0; + *pbRetry = 1; + return; + } + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("cannot vacuum wal mode database"); + }else{ + char *zTarget; + char *zExtra = 0; + if( strlen(p->zRbu)>=5 && 0==memcmp("file:", p->zRbu, 5) ){ + zExtra = &p->zRbu[5]; + while( *zExtra ){ + if( *zExtra++=='?' ) break; + } + if( *zExtra=='\0' ) zExtra = 0; + } + + zTarget = sqlite3_mprintf("file:%s-vactmp?rbu_memory=1%s%s", + sqlite3_db_filename(p->dbRbu, "main"), + (zExtra==0 ? "" : "&"), (zExtra==0 ? "" : zExtra) + ); + + if( zTarget==0 ){ + p->rc = SQLITE_NOMEM; + return; + } + p->dbMain = rbuOpenDbhandle(p, zTarget, p->nRbu<=1); + sqlite3_free(zTarget); + } + } + + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_create_function(p->dbMain, + "rbu_tmp_insert", -1, SQLITE_UTF8, (void*)p, rbuTmpInsertFunc, 0, 0 + ); + } + + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_create_function(p->dbMain, + "rbu_fossil_delta", 2, SQLITE_UTF8, 0, rbuFossilDeltaFunc, 0, 0 + ); + } + + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_create_function(p->dbRbu, + "rbu_target_name", -1, SQLITE_UTF8, (void*)p, rbuTargetNameFunc, 0, 0 + ); + } + + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_RBU, (void*)p); + } + rbuMPrintfExec(p, p->dbMain, "SELECT * FROM sqlite_schema"); + + /* Mark the database file just opened as an RBU target database. If + ** this call returns SQLITE_NOTFOUND, then the RBU vfs is not in use. + ** This is an error. */ + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_RBU, (void*)p); + } + + if( p->rc==SQLITE_NOTFOUND ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("rbu vfs not found"); + } +} + +/* +** This routine is a copy of the sqlite3FileSuffix3() routine from the core. +** It is a no-op unless SQLITE_ENABLE_8_3_NAMES is defined. +** +** If SQLITE_ENABLE_8_3_NAMES is set at compile-time and if the database +** filename in zBaseFilename is a URI with the "8_3_names=1" parameter and +** if filename in z[] has a suffix (a.k.a. "extension") that is longer than +** three characters, then shorten the suffix on z[] to be the last three +** characters of the original suffix. +** +** If SQLITE_ENABLE_8_3_NAMES is set to 2 at compile-time, then always +** do the suffix shortening regardless of URI parameter. +** +** Examples: +** +** test.db-journal => test.nal +** test.db-wal => test.wal +** test.db-shm => test.shm +** test.db-mj7f3319fa => test.9fa +*/ +static void rbuFileSuffix3(const char *zBase, char *z){ +#ifdef SQLITE_ENABLE_8_3_NAMES +#if SQLITE_ENABLE_8_3_NAMES<2 + if( sqlite3_uri_boolean(zBase, "8_3_names", 0) ) +#endif + { + int i, sz; + sz = (int)strlen(z)&0xffffff; + for(i=sz-1; i>0 && z[i]!='/' && z[i]!='.'; i--){} + if( z[i]=='.' && sz>i+4 ) memmove(&z[i+1], &z[sz-3], 4); + } +#endif +} + +/* +** Return the current wal-index header checksum for the target database +** as a 64-bit integer. +** +** The checksum is store in the first page of xShmMap memory as an 8-byte +** blob starting at byte offset 40. +*/ +static i64 rbuShmChecksum(sqlite3rbu *p){ + i64 iRet = 0; + if( p->rc==SQLITE_OK ){ + sqlite3_file *pDb = p->pTargetFd->pReal; + u32 volatile *ptr; + p->rc = pDb->pMethods->xShmMap(pDb, 0, 32*1024, 0, (void volatile**)&ptr); + if( p->rc==SQLITE_OK ){ + iRet = ((i64)ptr[10] << 32) + ptr[11]; + } + } + return iRet; +} + +/* +** This function is called as part of initializing or reinitializing an +** incremental checkpoint. +** +** It populates the sqlite3rbu.aFrame[] array with the set of +** (wal frame -> db page) copy operations required to checkpoint the +** current wal file, and obtains the set of shm locks required to safely +** perform the copy operations directly on the file-system. +** +** If argument pState is not NULL, then the incremental checkpoint is +** being resumed. In this case, if the checksum of the wal-index-header +** following recovery is not the same as the checksum saved in the RbuState +** object, then the rbu handle is set to DONE state. This occurs if some +** other client appends a transaction to the wal file in the middle of +** an incremental checkpoint. +*/ +static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){ + + /* If pState is NULL, then the wal file may not have been opened and + ** recovered. Running a read-statement here to ensure that doing so + ** does not interfere with the "capture" process below. */ + if( pState==0 ){ + p->eStage = 0; + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_exec(p->dbMain, "SELECT * FROM sqlite_schema", 0, 0, 0); + } + } + + /* Assuming no error has occurred, run a "restart" checkpoint with the + ** sqlite3rbu.eStage variable set to CAPTURE. This turns on the following + ** special behaviour in the rbu VFS: + ** + ** * If the exclusive shm WRITER or READ0 lock cannot be obtained, + ** the checkpoint fails with SQLITE_BUSY (normally SQLite would + ** proceed with running a passive checkpoint instead of failing). + ** + ** * Attempts to read from the *-wal file or write to the database file + ** do not perform any IO. Instead, the frame/page combinations that + ** would be read/written are recorded in the sqlite3rbu.aFrame[] + ** array. + ** + ** * Calls to xShmLock(UNLOCK) to release the exclusive shm WRITER, + ** READ0 and CHECKPOINT locks taken as part of the checkpoint are + ** no-ops. These locks will not be released until the connection + ** is closed. + ** + ** * Attempting to xSync() the database file causes an SQLITE_NOTICE + ** error. + ** + ** As a result, unless an error (i.e. OOM or SQLITE_BUSY) occurs, the + ** checkpoint below fails with SQLITE_NOTICE, and leaves the aFrame[] + ** array populated with a set of (frame -> page) mappings. Because the + ** WRITER, CHECKPOINT and READ0 locks are still held, it is safe to copy + ** data from the wal file into the database file according to the + ** contents of aFrame[]. + */ + if( p->rc==SQLITE_OK ){ + int rc2; + p->eStage = RBU_STAGE_CAPTURE; + rc2 = sqlite3_exec(p->dbMain, "PRAGMA main.wal_checkpoint=restart", 0, 0,0); + if( rc2!=SQLITE_NOTICE ) p->rc = rc2; + } + + if( p->rc==SQLITE_OK && p->nFrame>0 ){ + p->eStage = RBU_STAGE_CKPT; + p->nStep = (pState ? pState->nRow : 0); + p->aBuf = rbuMalloc(p, p->pgsz); + p->iWalCksum = rbuShmChecksum(p); + } + + if( p->rc==SQLITE_OK ){ + if( p->nFrame==0 || (pState && pState->iWalCksum!=p->iWalCksum) ){ + p->rc = SQLITE_DONE; + p->eStage = RBU_STAGE_DONE; + }else{ + int nSectorSize; + sqlite3_file *pDb = p->pTargetFd->pReal; + sqlite3_file *pWal = p->pTargetFd->pWalFd->pReal; + assert( p->nPagePerSector==0 ); + nSectorSize = pDb->pMethods->xSectorSize(pDb); + if( nSectorSize>p->pgsz ){ + p->nPagePerSector = nSectorSize / p->pgsz; + }else{ + p->nPagePerSector = 1; + } + + /* Call xSync() on the wal file. This causes SQLite to sync the + ** directory in which the target database and the wal file reside, in + ** case it has not been synced since the rename() call in + ** rbuMoveOalFile(). */ + p->rc = pWal->pMethods->xSync(pWal, SQLITE_SYNC_NORMAL); + } + } +} + +/* +** Called when iAmt bytes are read from offset iOff of the wal file while +** the rbu object is in capture mode. Record the frame number of the frame +** being read in the aFrame[] array. +*/ +static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){ + const u32 mReq = (1<<WAL_LOCK_WRITE)|(1<<WAL_LOCK_CKPT)|(1<<WAL_LOCK_READ0); + u32 iFrame; + + if( pRbu->mLock!=mReq ){ + pRbu->rc = SQLITE_BUSY; + return SQLITE_NOTICE_RBU; + } + + pRbu->pgsz = iAmt; + if( pRbu->nFrame==pRbu->nFrameAlloc ){ + int nNew = (pRbu->nFrameAlloc ? pRbu->nFrameAlloc : 64) * 2; + RbuFrame *aNew; + aNew = (RbuFrame*)sqlite3_realloc64(pRbu->aFrame, nNew * sizeof(RbuFrame)); + if( aNew==0 ) return SQLITE_NOMEM; + pRbu->aFrame = aNew; + pRbu->nFrameAlloc = nNew; + } + + iFrame = (u32)((iOff-32) / (i64)(iAmt+24)) + 1; + if( pRbu->iMaxFrame<iFrame ) pRbu->iMaxFrame = iFrame; + pRbu->aFrame[pRbu->nFrame].iWalFrame = iFrame; + pRbu->aFrame[pRbu->nFrame].iDbPage = 0; + pRbu->nFrame++; + return SQLITE_OK; +} + +/* +** Called when a page of data is written to offset iOff of the database +** file while the rbu handle is in capture mode. Record the page number +** of the page being written in the aFrame[] array. +*/ +static int rbuCaptureDbWrite(sqlite3rbu *pRbu, i64 iOff){ + pRbu->aFrame[pRbu->nFrame-1].iDbPage = (u32)(iOff / pRbu->pgsz) + 1; + return SQLITE_OK; +} + +/* +** This is called as part of an incremental checkpoint operation. Copy +** a single frame of data from the wal file into the database file, as +** indicated by the RbuFrame object. +*/ +static void rbuCheckpointFrame(sqlite3rbu *p, RbuFrame *pFrame){ + sqlite3_file *pWal = p->pTargetFd->pWalFd->pReal; + sqlite3_file *pDb = p->pTargetFd->pReal; + i64 iOff; + + assert( p->rc==SQLITE_OK ); + iOff = (i64)(pFrame->iWalFrame-1) * (p->pgsz + 24) + 32 + 24; + p->rc = pWal->pMethods->xRead(pWal, p->aBuf, p->pgsz, iOff); + if( p->rc ) return; + + iOff = (i64)(pFrame->iDbPage-1) * p->pgsz; + p->rc = pDb->pMethods->xWrite(pDb, p->aBuf, p->pgsz, iOff); +} + +/* +** This value is copied from the definition of ZIPVFS_CTRL_FILE_POINTER +** in zipvfs.h. +*/ +#define RBU_ZIPVFS_CTRL_FILE_POINTER 230439 + +/* +** Take an EXCLUSIVE lock on the database file. Return SQLITE_OK if +** successful, or an SQLite error code otherwise. +*/ +static int rbuLockDatabase(sqlite3 *db){ + int rc = SQLITE_OK; + sqlite3_file *fd = 0; + + sqlite3_file_control(db, "main", RBU_ZIPVFS_CTRL_FILE_POINTER, &fd); + if( fd ){ + sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd); + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED); + if( rc==SQLITE_OK ){ + rc = fd->pMethods->xUnlock(fd, SQLITE_LOCK_NONE); + } + sqlite3_file_control(db, "main", RBU_ZIPVFS_CTRL_FILE_POINTER, &fd); + }else{ + sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd); + } + + if( rc==SQLITE_OK && fd->pMethods ){ + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED); + if( rc==SQLITE_OK ){ + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_EXCLUSIVE); + } + } + return rc; +} + +/* +** Return true if the database handle passed as the only argument +** was opened with the rbu_exclusive_checkpoint=1 URI parameter +** specified. Or false otherwise. +*/ +static int rbuExclusiveCheckpoint(sqlite3 *db){ + const char *zUri = sqlite3_db_filename(db, 0); + return sqlite3_uri_boolean(zUri, RBU_EXCLUSIVE_CHECKPOINT, 0); +} + +#if defined(_WIN32_WCE) +static LPWSTR rbuWinUtf8ToUnicode(const char *zFilename){ + int nChar; + LPWSTR zWideFilename; + + nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0); + if( nChar==0 ){ + return 0; + } + zWideFilename = sqlite3_malloc64( nChar*sizeof(zWideFilename[0]) ); + if( zWideFilename==0 ){ + return 0; + } + memset(zWideFilename, 0, nChar*sizeof(zWideFilename[0])); + nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, + nChar); + if( nChar==0 ){ + sqlite3_free(zWideFilename); + zWideFilename = 0; + } + return zWideFilename; +} +#endif + +/* +** The RBU handle is currently in RBU_STAGE_OAL state, with a SHARED lock +** on the database file. This proc moves the *-oal file to the *-wal path, +** then reopens the database file (this time in vanilla, non-oal, WAL mode). +** If an error occurs, leave an error code and error message in the rbu +** handle. +*/ +static void rbuMoveOalFile(sqlite3rbu *p){ + const char *zBase = sqlite3_db_filename(p->dbMain, "main"); + const char *zMove = zBase; + char *zOal; + char *zWal; + + if( rbuIsVacuum(p) ){ + zMove = sqlite3_db_filename(p->dbRbu, "main"); + } + zOal = sqlite3_mprintf("%s-oal", zMove); + zWal = sqlite3_mprintf("%s-wal", zMove); + + assert( p->eStage==RBU_STAGE_MOVE ); + assert( p->rc==SQLITE_OK && p->zErrmsg==0 ); + if( zWal==0 || zOal==0 ){ + p->rc = SQLITE_NOMEM; + }else{ + /* Move the *-oal file to *-wal. At this point connection p->db is + ** holding a SHARED lock on the target database file (because it is + ** in WAL mode). So no other connection may be writing the db. + ** + ** In order to ensure that there are no database readers, an EXCLUSIVE + ** lock is obtained here before the *-oal is moved to *-wal. + */ + sqlite3 *dbMain = 0; + rbuFileSuffix3(zBase, zWal); + rbuFileSuffix3(zBase, zOal); + + /* Re-open the databases. */ + rbuObjIterFinalize(&p->objiter); + sqlite3_close(p->dbRbu); + sqlite3_close(p->dbMain); + p->dbMain = 0; + p->dbRbu = 0; + + dbMain = rbuOpenDbhandle(p, p->zTarget, 1); + if( dbMain ){ + assert( p->rc==SQLITE_OK ); + p->rc = rbuLockDatabase(dbMain); + } + + if( p->rc==SQLITE_OK ){ + p->rc = p->xRename(p->pRenameArg, zOal, zWal); + } + + if( p->rc!=SQLITE_OK + || rbuIsVacuum(p) + || rbuExclusiveCheckpoint(dbMain)==0 + ){ + sqlite3_close(dbMain); + dbMain = 0; + } + + if( p->rc==SQLITE_OK ){ + rbuOpenDatabase(p, dbMain, 0); + rbuSetupCheckpoint(p, 0); + } + } + + sqlite3_free(zWal); + sqlite3_free(zOal); +} + +/* +** The SELECT statement iterating through the keys for the current object +** (p->objiter.pSelect) currently points to a valid row. This function +** determines the type of operation requested by this row and returns +** one of the following values to indicate the result: +** +** * RBU_INSERT +** * RBU_DELETE +** * RBU_IDX_DELETE +** * RBU_UPDATE +** +** If RBU_UPDATE is returned, then output variable *pzMask is set to +** point to the text value indicating the columns to update. +** +** If the rbu_control field contains an invalid value, an error code and +** message are left in the RBU handle and zero returned. +*/ +static int rbuStepType(sqlite3rbu *p, const char **pzMask){ + int iCol = p->objiter.nCol; /* Index of rbu_control column */ + int res = 0; /* Return value */ + + switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){ + case SQLITE_INTEGER: { + int iVal = sqlite3_column_int(p->objiter.pSelect, iCol); + switch( iVal ){ + case 0: res = RBU_INSERT; break; + case 1: res = RBU_DELETE; break; + case 2: res = RBU_REPLACE; break; + case 3: res = RBU_IDX_DELETE; break; + case 4: res = RBU_IDX_INSERT; break; + } + break; + } + + case SQLITE_TEXT: { + const unsigned char *z = sqlite3_column_text(p->objiter.pSelect, iCol); + if( z==0 ){ + p->rc = SQLITE_NOMEM; + }else{ + *pzMask = (const char*)z; + } + res = RBU_UPDATE; + + break; + } + + default: + break; + } + + if( res==0 ){ + rbuBadControlError(p); + } + return res; +} + +#ifdef SQLITE_DEBUG +/* +** Assert that column iCol of statement pStmt is named zName. +*/ +static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){ + const char *zCol = sqlite3_column_name(pStmt, iCol); + assert( 0==sqlite3_stricmp(zName, zCol) ); +} +#else +# define assertColumnName(x,y,z) +#endif + +/* +** Argument eType must be one of RBU_INSERT, RBU_DELETE, RBU_IDX_INSERT or +** RBU_IDX_DELETE. This function performs the work of a single +** sqlite3rbu_step() call for the type of operation specified by eType. +*/ +static void rbuStepOneOp(sqlite3rbu *p, int eType){ + RbuObjIter *pIter = &p->objiter; + sqlite3_value *pVal; + sqlite3_stmt *pWriter; + int i; + + assert( p->rc==SQLITE_OK ); + assert( eType!=RBU_DELETE || pIter->zIdx==0 ); + assert( eType==RBU_DELETE || eType==RBU_IDX_DELETE + || eType==RBU_INSERT || eType==RBU_IDX_INSERT + ); + + /* If this is a delete, decrement nPhaseOneStep by nIndex. If the DELETE + ** statement below does actually delete a row, nPhaseOneStep will be + ** incremented by the same amount when SQL function rbu_tmp_insert() + ** is invoked by the trigger. */ + if( eType==RBU_DELETE ){ + p->nPhaseOneStep -= p->objiter.nIndex; + } + + if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){ + pWriter = pIter->pDelete; + }else{ + pWriter = pIter->pInsert; + } + + for(i=0; i<pIter->nCol; i++){ + /* If this is an INSERT into a table b-tree and the table has an + ** explicit INTEGER PRIMARY KEY, check that this is not an attempt + ** to write a NULL into the IPK column. That is not permitted. */ + if( eType==RBU_INSERT + && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i] + && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL + ){ + p->rc = SQLITE_MISMATCH; + p->zErrmsg = sqlite3_mprintf("datatype mismatch"); + return; + } + + if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){ + continue; + } + + pVal = sqlite3_column_value(pIter->pSelect, i); + p->rc = sqlite3_bind_value(pWriter, i+1, pVal); + if( p->rc ) return; + } + if( pIter->zIdx==0 ){ + if( pIter->eType==RBU_PK_VTAB + || pIter->eType==RBU_PK_NONE + || (pIter->eType==RBU_PK_EXTERNAL && rbuIsVacuum(p)) + ){ + /* For a virtual table, or a table with no primary key, the + ** SELECT statement is: + ** + ** SELECT <cols>, rbu_control, rbu_rowid FROM .... + ** + ** Hence column_value(pIter->nCol+1). + */ + assertColumnName(pIter->pSelect, pIter->nCol+1, + rbuIsVacuum(p) ? "rowid" : "rbu_rowid" + ); + pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1); + p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal); + } + } + if( p->rc==SQLITE_OK ){ + sqlite3_step(pWriter); + p->rc = resetAndCollectError(pWriter, &p->zErrmsg); + } +} + +/* +** This function does the work for an sqlite3rbu_step() call. +** +** The object-iterator (p->objiter) currently points to a valid object, +** and the input cursor (p->objiter.pSelect) currently points to a valid +** input row. Perform whatever processing is required and return. +** +** If no error occurs, SQLITE_OK is returned. Otherwise, an error code +** and message is left in the RBU handle and a copy of the error code +** returned. +*/ +static int rbuStep(sqlite3rbu *p){ + RbuObjIter *pIter = &p->objiter; + const char *zMask = 0; + int eType = rbuStepType(p, &zMask); + + if( eType ){ + assert( eType==RBU_INSERT || eType==RBU_DELETE + || eType==RBU_REPLACE || eType==RBU_IDX_DELETE + || eType==RBU_IDX_INSERT || eType==RBU_UPDATE + ); + assert( eType!=RBU_UPDATE || pIter->zIdx==0 ); + + if( pIter->zIdx==0 && (eType==RBU_IDX_DELETE || eType==RBU_IDX_INSERT) ){ + rbuBadControlError(p); + } + else if( eType==RBU_REPLACE ){ + if( pIter->zIdx==0 ){ + p->nPhaseOneStep += p->objiter.nIndex; + rbuStepOneOp(p, RBU_DELETE); + } + if( p->rc==SQLITE_OK ) rbuStepOneOp(p, RBU_INSERT); + } + else if( eType!=RBU_UPDATE ){ + rbuStepOneOp(p, eType); + } + else{ + sqlite3_value *pVal; + sqlite3_stmt *pUpdate = 0; + assert( eType==RBU_UPDATE ); + p->nPhaseOneStep -= p->objiter.nIndex; + rbuGetUpdateStmt(p, pIter, zMask, &pUpdate); + if( pUpdate ){ + int i; + for(i=0; p->rc==SQLITE_OK && i<pIter->nCol; i++){ + char c = zMask[pIter->aiSrcOrder[i]]; + pVal = sqlite3_column_value(pIter->pSelect, i); + if( pIter->abTblPk[i] || c!='.' ){ + p->rc = sqlite3_bind_value(pUpdate, i+1, pVal); + } + } + if( p->rc==SQLITE_OK + && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) + ){ + /* Bind the rbu_rowid value to column _rowid_ */ + assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid"); + pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1); + p->rc = sqlite3_bind_value(pUpdate, pIter->nCol+1, pVal); + } + if( p->rc==SQLITE_OK ){ + sqlite3_step(pUpdate); + p->rc = resetAndCollectError(pUpdate, &p->zErrmsg); + } + } + } + } + return p->rc; +} + +/* +** Increment the schema cookie of the main database opened by p->dbMain. +** +** Or, if this is an RBU vacuum, set the schema cookie of the main db +** opened by p->dbMain to one more than the schema cookie of the main +** db opened by p->dbRbu. +*/ +static void rbuIncrSchemaCookie(sqlite3rbu *p){ + if( p->rc==SQLITE_OK ){ + sqlite3 *dbread = (rbuIsVacuum(p) ? p->dbRbu : p->dbMain); + int iCookie = 1000000; + sqlite3_stmt *pStmt; + + p->rc = prepareAndCollectError(dbread, &pStmt, &p->zErrmsg, + "PRAGMA schema_version" + ); + if( p->rc==SQLITE_OK ){ + /* Coverage: it may be that this sqlite3_step() cannot fail. There + ** is already a transaction open, so the prepared statement cannot + ** throw an SQLITE_SCHEMA exception. The only database page the + ** statement reads is page 1, which is guaranteed to be in the cache. + ** And no memory allocations are required. */ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + iCookie = sqlite3_column_int(pStmt, 0); + } + rbuFinalize(p, pStmt); + } + if( p->rc==SQLITE_OK ){ + rbuMPrintfExec(p, p->dbMain, "PRAGMA schema_version = %d", iCookie+1); + } + } +} + +/* +** Update the contents of the rbu_state table within the rbu database. The +** value stored in the RBU_STATE_STAGE column is eStage. All other values +** are determined by inspecting the rbu handle passed as the first argument. +*/ +static void rbuSaveState(sqlite3rbu *p, int eStage){ + if( p->rc==SQLITE_OK || p->rc==SQLITE_DONE ){ + sqlite3_stmt *pInsert = 0; + rbu_file *pFd = (rbuIsVacuum(p) ? p->pRbuFd : p->pTargetFd); + int rc; + + assert( p->zErrmsg==0 ); + rc = prepareFreeAndCollectError(p->dbRbu, &pInsert, &p->zErrmsg, + sqlite3_mprintf( + "INSERT OR REPLACE INTO %s.rbu_state(k, v) VALUES " + "(%d, %d), " + "(%d, %Q), " + "(%d, %Q), " + "(%d, %d), " + "(%d, %d), " + "(%d, %lld), " + "(%d, %lld), " + "(%d, %lld), " + "(%d, %lld), " + "(%d, %Q) ", + p->zStateDb, + RBU_STATE_STAGE, eStage, + RBU_STATE_TBL, p->objiter.zTbl, + RBU_STATE_IDX, p->objiter.zIdx, + RBU_STATE_ROW, p->nStep, + RBU_STATE_PROGRESS, p->nProgress, + RBU_STATE_CKPT, p->iWalCksum, + RBU_STATE_COOKIE, (i64)pFd->iCookie, + RBU_STATE_OALSZ, p->iOalSz, + RBU_STATE_PHASEONESTEP, p->nPhaseOneStep, + RBU_STATE_DATATBL, p->objiter.zDataTbl + ) + ); + assert( pInsert==0 || rc==SQLITE_OK ); + + if( rc==SQLITE_OK ){ + sqlite3_step(pInsert); + rc = sqlite3_finalize(pInsert); + } + if( rc!=SQLITE_OK ) p->rc = rc; + } +} + + +/* +** The second argument passed to this function is the name of a PRAGMA +** setting - "page_size", "auto_vacuum", "user_version" or "application_id". +** This function executes the following on sqlite3rbu.dbRbu: +** +** "PRAGMA main.$zPragma" +** +** where $zPragma is the string passed as the second argument, then +** on sqlite3rbu.dbMain: +** +** "PRAGMA main.$zPragma = $val" +** +** where $val is the value returned by the first PRAGMA invocation. +** +** In short, it copies the value of the specified PRAGMA setting from +** dbRbu to dbMain. +*/ +static void rbuCopyPragma(sqlite3rbu *p, const char *zPragma){ + if( p->rc==SQLITE_OK ){ + sqlite3_stmt *pPragma = 0; + p->rc = prepareFreeAndCollectError(p->dbRbu, &pPragma, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.%s", zPragma) + ); + if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPragma) ){ + p->rc = rbuMPrintfExec(p, p->dbMain, "PRAGMA main.%s = %d", + zPragma, sqlite3_column_int(pPragma, 0) + ); + } + rbuFinalize(p, pPragma); + } +} + +/* +** The RBU handle passed as the only argument has just been opened and +** the state database is empty. If this RBU handle was opened for an +** RBU vacuum operation, create the schema in the target db. +*/ +static void rbuCreateTargetSchema(sqlite3rbu *p){ + sqlite3_stmt *pSql = 0; + sqlite3_stmt *pInsert = 0; + + assert( rbuIsVacuum(p) ); + p->rc = sqlite3_exec(p->dbMain, "PRAGMA writable_schema=1", 0,0, &p->zErrmsg); + if( p->rc==SQLITE_OK ){ + p->rc = prepareAndCollectError(p->dbRbu, &pSql, &p->zErrmsg, + "SELECT sql FROM sqlite_schema WHERE sql!='' AND rootpage!=0" + " AND name!='sqlite_sequence' " + " ORDER BY type DESC" + ); + } + + while( p->rc==SQLITE_OK && sqlite3_step(pSql)==SQLITE_ROW ){ + const char *zSql = (const char*)sqlite3_column_text(pSql, 0); + p->rc = sqlite3_exec(p->dbMain, zSql, 0, 0, &p->zErrmsg); + } + rbuFinalize(p, pSql); + if( p->rc!=SQLITE_OK ) return; + + if( p->rc==SQLITE_OK ){ + p->rc = prepareAndCollectError(p->dbRbu, &pSql, &p->zErrmsg, + "SELECT * FROM sqlite_schema WHERE rootpage=0 OR rootpage IS NULL" + ); + } + + if( p->rc==SQLITE_OK ){ + p->rc = prepareAndCollectError(p->dbMain, &pInsert, &p->zErrmsg, + "INSERT INTO sqlite_schema VALUES(?,?,?,?,?)" + ); + } + + while( p->rc==SQLITE_OK && sqlite3_step(pSql)==SQLITE_ROW ){ + int i; + for(i=0; i<5; i++){ + sqlite3_bind_value(pInsert, i+1, sqlite3_column_value(pSql, i)); + } + sqlite3_step(pInsert); + p->rc = sqlite3_reset(pInsert); + } + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_exec(p->dbMain, "PRAGMA writable_schema=0",0,0,&p->zErrmsg); + } + + rbuFinalize(p, pSql); + rbuFinalize(p, pInsert); +} + +/* +** Step the RBU object. +*/ +SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){ + if( p ){ + switch( p->eStage ){ + case RBU_STAGE_OAL: { + RbuObjIter *pIter = &p->objiter; + + /* If this is an RBU vacuum operation and the state table was empty + ** when this handle was opened, create the target database schema. */ + if( rbuIsVacuum(p) && p->nProgress==0 && p->rc==SQLITE_OK ){ + rbuCreateTargetSchema(p); + rbuCopyPragma(p, "user_version"); + rbuCopyPragma(p, "application_id"); + } + + while( p->rc==SQLITE_OK && pIter->zTbl ){ + + if( pIter->bCleanup ){ + /* Clean up the rbu_tmp_xxx table for the previous table. It + ** cannot be dropped as there are currently active SQL statements. + ** But the contents can be deleted. */ + if( rbuIsVacuum(p)==0 && pIter->abIndexed ){ + rbuMPrintfExec(p, p->dbRbu, + "DELETE FROM %s.'rbu_tmp_%q'", p->zStateDb, pIter->zDataTbl + ); + } + }else{ + rbuObjIterPrepareAll(p, pIter, 0); + + /* Advance to the next row to process. */ + if( p->rc==SQLITE_OK ){ + int rc = sqlite3_step(pIter->pSelect); + if( rc==SQLITE_ROW ){ + p->nProgress++; + p->nStep++; + return rbuStep(p); + } + p->rc = sqlite3_reset(pIter->pSelect); + p->nStep = 0; + } + } + + rbuObjIterNext(p, pIter); + } + + if( p->rc==SQLITE_OK ){ + assert( pIter->zTbl==0 ); + rbuSaveState(p, RBU_STAGE_MOVE); + rbuIncrSchemaCookie(p); + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_exec(p->dbMain, "COMMIT", 0, 0, &p->zErrmsg); + } + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, &p->zErrmsg); + } + p->eStage = RBU_STAGE_MOVE; + } + break; + } + + case RBU_STAGE_MOVE: { + if( p->rc==SQLITE_OK ){ + rbuMoveOalFile(p); + p->nProgress++; + } + break; + } + + case RBU_STAGE_CKPT: { + if( p->rc==SQLITE_OK ){ + if( p->nStep>=p->nFrame ){ + sqlite3_file *pDb = p->pTargetFd->pReal; + + /* Sync the db file */ + p->rc = pDb->pMethods->xSync(pDb, SQLITE_SYNC_NORMAL); + + /* Update nBackfill */ + if( p->rc==SQLITE_OK ){ + void volatile *ptr; + p->rc = pDb->pMethods->xShmMap(pDb, 0, 32*1024, 0, &ptr); + if( p->rc==SQLITE_OK ){ + ((u32 volatile*)ptr)[24] = p->iMaxFrame; + } + } + + if( p->rc==SQLITE_OK ){ + p->eStage = RBU_STAGE_DONE; + p->rc = SQLITE_DONE; + } + }else{ + /* At one point the following block copied a single frame from the + ** wal file to the database file. So that one call to sqlite3rbu_step() + ** checkpointed a single frame. + ** + ** However, if the sector-size is larger than the page-size, and the + ** application calls sqlite3rbu_savestate() or close() immediately + ** after this step, then rbu_step() again, then a power failure occurs, + ** then the database page written here may be damaged. Work around + ** this by checkpointing frames until the next page in the aFrame[] + ** lies on a different disk sector to the current one. */ + u32 iSector; + do{ + RbuFrame *pFrame = &p->aFrame[p->nStep]; + iSector = (pFrame->iDbPage-1) / p->nPagePerSector; + rbuCheckpointFrame(p, pFrame); + p->nStep++; + }while( p->nStep<p->nFrame + && iSector==((p->aFrame[p->nStep].iDbPage-1) / p->nPagePerSector) + && p->rc==SQLITE_OK + ); + } + p->nProgress++; + } + break; + } + + default: + break; + } + return p->rc; + }else{ + return SQLITE_NOMEM; + } +} + +/* +** Compare strings z1 and z2, returning 0 if they are identical, or non-zero +** otherwise. Either or both argument may be NULL. Two NULL values are +** considered equal, and NULL is considered distinct from all other values. +*/ +static int rbuStrCompare(const char *z1, const char *z2){ + if( z1==0 && z2==0 ) return 0; + if( z1==0 || z2==0 ) return 1; + return (sqlite3_stricmp(z1, z2)!=0); +} + +/* +** This function is called as part of sqlite3rbu_open() when initializing +** an rbu handle in OAL stage. If the rbu update has not started (i.e. +** the rbu_state table was empty) it is a no-op. Otherwise, it arranges +** things so that the next call to sqlite3rbu_step() continues on from +** where the previous rbu handle left off. +** +** If an error occurs, an error code and error message are left in the +** rbu handle passed as the first argument. +*/ +static void rbuSetupOal(sqlite3rbu *p, RbuState *pState){ + assert( p->rc==SQLITE_OK ); + if( pState->zTbl ){ + RbuObjIter *pIter = &p->objiter; + int rc = SQLITE_OK; + + while( rc==SQLITE_OK && pIter->zTbl && (pIter->bCleanup + || rbuStrCompare(pIter->zIdx, pState->zIdx) + || (pState->zDataTbl==0 && rbuStrCompare(pIter->zTbl, pState->zTbl)) + || (pState->zDataTbl && rbuStrCompare(pIter->zDataTbl, pState->zDataTbl)) + )){ + rc = rbuObjIterNext(p, pIter); + } + + if( rc==SQLITE_OK && !pIter->zTbl ){ + rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("rbu_state mismatch error"); + } + + if( rc==SQLITE_OK ){ + p->nStep = pState->nRow; + rc = rbuObjIterPrepareAll(p, &p->objiter, p->nStep); + } + + p->rc = rc; + } +} + +/* +** If there is a "*-oal" file in the file-system corresponding to the +** target database in the file-system, delete it. If an error occurs, +** leave an error code and error message in the rbu handle. +*/ +static void rbuDeleteOalFile(sqlite3rbu *p){ + char *zOal = rbuMPrintf(p, "%s-oal", p->zTarget); + if( zOal ){ + sqlite3_vfs *pVfs = 0; + sqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_VFS_POINTER, &pVfs); + assert( pVfs && p->rc==SQLITE_OK && p->zErrmsg==0 ); + pVfs->xDelete(pVfs, zOal, 0); + sqlite3_free(zOal); + } +} + +/* +** Allocate a private rbu VFS for the rbu handle passed as the only +** argument. This VFS will be used unless the call to sqlite3rbu_open() +** specified a URI with a vfs=? option in place of a target database +** file name. +*/ +static void rbuCreateVfs(sqlite3rbu *p){ + int rnd; + char zRnd[64]; + + assert( p->rc==SQLITE_OK ); + sqlite3_randomness(sizeof(int), (void*)&rnd); + sqlite3_snprintf(sizeof(zRnd), zRnd, "rbu_vfs_%d", rnd); + p->rc = sqlite3rbu_create_vfs(zRnd, 0); + if( p->rc==SQLITE_OK ){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(zRnd); + assert( pVfs ); + p->zVfsName = pVfs->zName; + ((rbu_vfs*)pVfs)->pRbu = p; + } +} + +/* +** Destroy the private VFS created for the rbu handle passed as the only +** argument by an earlier call to rbuCreateVfs(). +*/ +static void rbuDeleteVfs(sqlite3rbu *p){ + if( p->zVfsName ){ + sqlite3rbu_destroy_vfs(p->zVfsName); + p->zVfsName = 0; + } +} + +/* +** This user-defined SQL function is invoked with a single argument - the +** name of a table expected to appear in the target database. It returns +** the number of auxilliary indexes on the table. +*/ +static void rbuIndexCntFunc( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + sqlite3rbu *p = (sqlite3rbu*)sqlite3_user_data(pCtx); + sqlite3_stmt *pStmt = 0; + char *zErrmsg = 0; + int rc; + sqlite3 *db = (rbuIsVacuum(p) ? p->dbRbu : p->dbMain); + + assert( nVal==1 ); + + rc = prepareFreeAndCollectError(db, &pStmt, &zErrmsg, + sqlite3_mprintf("SELECT count(*) FROM sqlite_schema " + "WHERE type='index' AND tbl_name = %Q", sqlite3_value_text(apVal[0])) + ); + if( rc!=SQLITE_OK ){ + sqlite3_result_error(pCtx, zErrmsg, -1); + }else{ + int nIndex = 0; + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + nIndex = sqlite3_column_int(pStmt, 0); + } + rc = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ){ + sqlite3_result_int(pCtx, nIndex); + }else{ + sqlite3_result_error(pCtx, sqlite3_errmsg(db), -1); + } + } + + sqlite3_free(zErrmsg); +} + +/* +** If the RBU database contains the rbu_count table, use it to initialize +** the sqlite3rbu.nPhaseOneStep variable. The schema of the rbu_count table +** is assumed to contain the same columns as: +** +** CREATE TABLE rbu_count(tbl TEXT PRIMARY KEY, cnt INTEGER) WITHOUT ROWID; +** +** There should be one row in the table for each data_xxx table in the +** database. The 'tbl' column should contain the name of a data_xxx table, +** and the cnt column the number of rows it contains. +** +** sqlite3rbu.nPhaseOneStep is initialized to the sum of (1 + nIndex) * cnt +** for all rows in the rbu_count table, where nIndex is the number of +** indexes on the corresponding target database table. +*/ +static void rbuInitPhaseOneSteps(sqlite3rbu *p){ + if( p->rc==SQLITE_OK ){ + sqlite3_stmt *pStmt = 0; + int bExists = 0; /* True if rbu_count exists */ + + p->nPhaseOneStep = -1; + + p->rc = sqlite3_create_function(p->dbRbu, + "rbu_index_cnt", 1, SQLITE_UTF8, (void*)p, rbuIndexCntFunc, 0, 0 + ); + + /* Check for the rbu_count table. If it does not exist, or if an error + ** occurs, nPhaseOneStep will be left set to -1. */ + if( p->rc==SQLITE_OK ){ + p->rc = prepareAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, + "SELECT 1 FROM sqlite_schema WHERE tbl_name = 'rbu_count'" + ); + } + if( p->rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + bExists = 1; + } + p->rc = sqlite3_finalize(pStmt); + } + + if( p->rc==SQLITE_OK && bExists ){ + p->rc = prepareAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, + "SELECT sum(cnt * (1 + rbu_index_cnt(rbu_target_name(tbl))))" + "FROM rbu_count" + ); + if( p->rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + p->nPhaseOneStep = sqlite3_column_int64(pStmt, 0); + } + p->rc = sqlite3_finalize(pStmt); + } + } + } +} + + +static sqlite3rbu *openRbuHandle( + const char *zTarget, + const char *zRbu, + const char *zState +){ + sqlite3rbu *p; + size_t nTarget = zTarget ? strlen(zTarget) : 0; + size_t nRbu = strlen(zRbu); + size_t nByte = sizeof(sqlite3rbu) + nTarget+1 + nRbu+1; + + p = (sqlite3rbu*)sqlite3_malloc64(nByte); + if( p ){ + RbuState *pState = 0; + + /* Create the custom VFS. */ + memset(p, 0, sizeof(sqlite3rbu)); + sqlite3rbu_rename_handler(p, 0, 0); + rbuCreateVfs(p); + + /* Open the target, RBU and state databases */ + if( p->rc==SQLITE_OK ){ + char *pCsr = (char*)&p[1]; + int bRetry = 0; + if( zTarget ){ + p->zTarget = pCsr; + memcpy(p->zTarget, zTarget, nTarget+1); + pCsr += nTarget+1; + } + p->zRbu = pCsr; + memcpy(p->zRbu, zRbu, nRbu+1); + pCsr += nRbu+1; + if( zState ){ + p->zState = rbuMPrintf(p, "%s", zState); + } + + /* If the first attempt to open the database file fails and the bRetry + ** flag it set, this means that the db was not opened because it seemed + ** to be a wal-mode db. But, this may have happened due to an earlier + ** RBU vacuum operation leaving an old wal file in the directory. + ** If this is the case, it will have been checkpointed and deleted + ** when the handle was closed and a second attempt to open the + ** database may succeed. */ + rbuOpenDatabase(p, 0, &bRetry); + if( bRetry ){ + rbuOpenDatabase(p, 0, 0); + } + } + + if( p->rc==SQLITE_OK ){ + pState = rbuLoadState(p); + assert( pState || p->rc!=SQLITE_OK ); + if( p->rc==SQLITE_OK ){ + + if( pState->eStage==0 ){ + rbuDeleteOalFile(p); + rbuInitPhaseOneSteps(p); + p->eStage = RBU_STAGE_OAL; + }else{ + p->eStage = pState->eStage; + p->nPhaseOneStep = pState->nPhaseOneStep; + } + p->nProgress = pState->nProgress; + p->iOalSz = pState->iOalSz; + } + } + assert( p->rc!=SQLITE_OK || p->eStage!=0 ); + + if( p->rc==SQLITE_OK && p->pTargetFd->pWalFd ){ + if( p->eStage==RBU_STAGE_OAL ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("cannot update wal mode database"); + }else if( p->eStage==RBU_STAGE_MOVE ){ + p->eStage = RBU_STAGE_CKPT; + p->nStep = 0; + } + } + + if( p->rc==SQLITE_OK + && (p->eStage==RBU_STAGE_OAL || p->eStage==RBU_STAGE_MOVE) + && pState->eStage!=0 + ){ + rbu_file *pFd = (rbuIsVacuum(p) ? p->pRbuFd : p->pTargetFd); + if( pFd->iCookie!=pState->iCookie ){ + /* At this point (pTargetFd->iCookie) contains the value of the + ** change-counter cookie (the thing that gets incremented when a + ** transaction is committed in rollback mode) currently stored on + ** page 1 of the database file. */ + p->rc = SQLITE_BUSY; + p->zErrmsg = sqlite3_mprintf("database modified during rbu %s", + (rbuIsVacuum(p) ? "vacuum" : "update") + ); + } + } + + if( p->rc==SQLITE_OK ){ + if( p->eStage==RBU_STAGE_OAL ){ + sqlite3 *db = p->dbMain; + p->rc = sqlite3_exec(p->dbRbu, "BEGIN", 0, 0, &p->zErrmsg); + + /* Point the object iterator at the first object */ + if( p->rc==SQLITE_OK ){ + p->rc = rbuObjIterFirst(p, &p->objiter); + } + + /* If the RBU database contains no data_xxx tables, declare the RBU + ** update finished. */ + if( p->rc==SQLITE_OK && p->objiter.zTbl==0 ){ + p->rc = SQLITE_DONE; + p->eStage = RBU_STAGE_DONE; + }else{ + if( p->rc==SQLITE_OK && pState->eStage==0 && rbuIsVacuum(p) ){ + rbuCopyPragma(p, "page_size"); + rbuCopyPragma(p, "auto_vacuum"); + } + + /* Open transactions both databases. The *-oal file is opened or + ** created at this point. */ + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, &p->zErrmsg); + } + + /* Check if the main database is a zipvfs db. If it is, set the upper + ** level pager to use "journal_mode=off". This prevents it from + ** generating a large journal using a temp file. */ + if( p->rc==SQLITE_OK ){ + int frc = sqlite3_file_control(db, "main", SQLITE_FCNTL_ZIPVFS, 0); + if( frc==SQLITE_OK ){ + p->rc = sqlite3_exec( + db, "PRAGMA journal_mode=off",0,0,&p->zErrmsg); + } + } + + if( p->rc==SQLITE_OK ){ + rbuSetupOal(p, pState); + } + } + }else if( p->eStage==RBU_STAGE_MOVE ){ + /* no-op */ + }else if( p->eStage==RBU_STAGE_CKPT ){ + if( !rbuIsVacuum(p) && rbuExclusiveCheckpoint(p->dbMain) ){ + /* If the rbu_exclusive_checkpoint=1 URI parameter was specified + ** and an incremental checkpoint is being resumed, attempt an + ** exclusive lock on the db file. If this fails, so be it. */ + p->eStage = RBU_STAGE_DONE; + rbuLockDatabase(p->dbMain); + p->eStage = RBU_STAGE_CKPT; + } + rbuSetupCheckpoint(p, pState); + }else if( p->eStage==RBU_STAGE_DONE ){ + p->rc = SQLITE_DONE; + }else{ + p->rc = SQLITE_CORRUPT; + } + } + + rbuFreeState(pState); + } + + return p; +} + +/* +** Allocate and return an RBU handle with all fields zeroed except for the +** error code, which is set to SQLITE_MISUSE. +*/ +static sqlite3rbu *rbuMisuseError(void){ + sqlite3rbu *pRet; + pRet = sqlite3_malloc64(sizeof(sqlite3rbu)); + if( pRet ){ + memset(pRet, 0, sizeof(sqlite3rbu)); + pRet->rc = SQLITE_MISUSE; + } + return pRet; +} + +/* +** Open and return a new RBU handle. +*/ +SQLITE_API sqlite3rbu *sqlite3rbu_open( + const char *zTarget, + const char *zRbu, + const char *zState +){ + if( zTarget==0 || zRbu==0 ){ return rbuMisuseError(); } + return openRbuHandle(zTarget, zRbu, zState); +} + +/* +** Open a handle to begin or resume an RBU VACUUM operation. +*/ +SQLITE_API sqlite3rbu *sqlite3rbu_vacuum( + const char *zTarget, + const char *zState +){ + if( zTarget==0 ){ return rbuMisuseError(); } + if( zState ){ + int n = strlen(zState); + if( n>=7 && 0==memcmp("-vactmp", &zState[n-7], 7) ){ + return rbuMisuseError(); + } + } + /* TODO: Check that both arguments are non-NULL */ + return openRbuHandle(0, zTarget, zState); +} + +/* +** Return the database handle used by pRbu. +*/ +SQLITE_API sqlite3 *sqlite3rbu_db(sqlite3rbu *pRbu, int bRbu){ + sqlite3 *db = 0; + if( pRbu ){ + db = (bRbu ? pRbu->dbRbu : pRbu->dbMain); + } + return db; +} + + +/* +** If the error code currently stored in the RBU handle is SQLITE_CONSTRAINT, +** then edit any error message string so as to remove all occurrences of +** the pattern "rbu_imp_[0-9]*". +*/ +static void rbuEditErrmsg(sqlite3rbu *p){ + if( p->rc==SQLITE_CONSTRAINT && p->zErrmsg ){ + unsigned int i; + size_t nErrmsg = strlen(p->zErrmsg); + for(i=0; i<(nErrmsg-8); i++){ + if( memcmp(&p->zErrmsg[i], "rbu_imp_", 8)==0 ){ + int nDel = 8; + while( p->zErrmsg[i+nDel]>='0' && p->zErrmsg[i+nDel]<='9' ) nDel++; + memmove(&p->zErrmsg[i], &p->zErrmsg[i+nDel], nErrmsg + 1 - i - nDel); + nErrmsg -= nDel; + } + } + } +} + +/* +** Close the RBU handle. +*/ +SQLITE_API int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){ + int rc; + if( p ){ + + /* Commit the transaction to the *-oal file. */ + if( p->rc==SQLITE_OK && p->eStage==RBU_STAGE_OAL ){ + p->rc = sqlite3_exec(p->dbMain, "COMMIT", 0, 0, &p->zErrmsg); + } + + /* Sync the db file if currently doing an incremental checkpoint */ + if( p->rc==SQLITE_OK && p->eStage==RBU_STAGE_CKPT ){ + sqlite3_file *pDb = p->pTargetFd->pReal; + p->rc = pDb->pMethods->xSync(pDb, SQLITE_SYNC_NORMAL); + } + + rbuSaveState(p, p->eStage); + + if( p->rc==SQLITE_OK && p->eStage==RBU_STAGE_OAL ){ + p->rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, &p->zErrmsg); + } + + /* Close any open statement handles. */ + rbuObjIterFinalize(&p->objiter); + + /* If this is an RBU vacuum handle and the vacuum has either finished + ** successfully or encountered an error, delete the contents of the + ** state table. This causes the next call to sqlite3rbu_vacuum() + ** specifying the current target and state databases to start a new + ** vacuum from scratch. */ + if( rbuIsVacuum(p) && p->rc!=SQLITE_OK && p->dbRbu ){ + int rc2 = sqlite3_exec(p->dbRbu, "DELETE FROM stat.rbu_state", 0, 0, 0); + if( p->rc==SQLITE_DONE && rc2!=SQLITE_OK ) p->rc = rc2; + } + + /* Close the open database handle and VFS object. */ + sqlite3_close(p->dbRbu); + sqlite3_close(p->dbMain); + assert( p->szTemp==0 ); + rbuDeleteVfs(p); + sqlite3_free(p->aBuf); + sqlite3_free(p->aFrame); + + rbuEditErrmsg(p); + rc = p->rc; + if( pzErrmsg ){ + *pzErrmsg = p->zErrmsg; + }else{ + sqlite3_free(p->zErrmsg); + } + sqlite3_free(p->zState); + sqlite3_free(p); + }else{ + rc = SQLITE_NOMEM; + *pzErrmsg = 0; + } + return rc; +} + +/* +** Return the total number of key-value operations (inserts, deletes or +** updates) that have been performed on the target database since the +** current RBU update was started. +*/ +SQLITE_API sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu){ + return pRbu->nProgress; +} + +/* +** Return permyriadage progress indications for the two main stages of +** an RBU update. +*/ +SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *p, int *pnOne, int *pnTwo){ + const int MAX_PROGRESS = 10000; + switch( p->eStage ){ + case RBU_STAGE_OAL: + if( p->nPhaseOneStep>0 ){ + *pnOne = (int)(MAX_PROGRESS * (i64)p->nProgress/(i64)p->nPhaseOneStep); + }else{ + *pnOne = -1; + } + *pnTwo = 0; + break; + + case RBU_STAGE_MOVE: + *pnOne = MAX_PROGRESS; + *pnTwo = 0; + break; + + case RBU_STAGE_CKPT: + *pnOne = MAX_PROGRESS; + *pnTwo = (int)(MAX_PROGRESS * (i64)p->nStep / (i64)p->nFrame); + break; + + case RBU_STAGE_DONE: + *pnOne = MAX_PROGRESS; + *pnTwo = MAX_PROGRESS; + break; + + default: + assert( 0 ); + } +} + +/* +** Return the current state of the RBU vacuum or update operation. +*/ +SQLITE_API int sqlite3rbu_state(sqlite3rbu *p){ + int aRes[] = { + 0, SQLITE_RBU_STATE_OAL, SQLITE_RBU_STATE_MOVE, + 0, SQLITE_RBU_STATE_CHECKPOINT, SQLITE_RBU_STATE_DONE + }; + + assert( RBU_STAGE_OAL==1 ); + assert( RBU_STAGE_MOVE==2 ); + assert( RBU_STAGE_CKPT==4 ); + assert( RBU_STAGE_DONE==5 ); + assert( aRes[RBU_STAGE_OAL]==SQLITE_RBU_STATE_OAL ); + assert( aRes[RBU_STAGE_MOVE]==SQLITE_RBU_STATE_MOVE ); + assert( aRes[RBU_STAGE_CKPT]==SQLITE_RBU_STATE_CHECKPOINT ); + assert( aRes[RBU_STAGE_DONE]==SQLITE_RBU_STATE_DONE ); + + if( p->rc!=SQLITE_OK && p->rc!=SQLITE_DONE ){ + return SQLITE_RBU_STATE_ERROR; + }else{ + assert( p->rc!=SQLITE_DONE || p->eStage==RBU_STAGE_DONE ); + assert( p->eStage==RBU_STAGE_OAL + || p->eStage==RBU_STAGE_MOVE + || p->eStage==RBU_STAGE_CKPT + || p->eStage==RBU_STAGE_DONE + ); + return aRes[p->eStage]; + } +} + +SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){ + int rc = p->rc; + if( rc==SQLITE_DONE ) return SQLITE_OK; + + assert( p->eStage>=RBU_STAGE_OAL && p->eStage<=RBU_STAGE_DONE ); + if( p->eStage==RBU_STAGE_OAL ){ + assert( rc!=SQLITE_DONE ); + if( rc==SQLITE_OK ) rc = sqlite3_exec(p->dbMain, "COMMIT", 0, 0, 0); + } + + /* Sync the db file */ + if( rc==SQLITE_OK && p->eStage==RBU_STAGE_CKPT ){ + sqlite3_file *pDb = p->pTargetFd->pReal; + rc = pDb->pMethods->xSync(pDb, SQLITE_SYNC_NORMAL); + } + + p->rc = rc; + rbuSaveState(p, p->eStage); + rc = p->rc; + + if( p->eStage==RBU_STAGE_OAL ){ + assert( rc!=SQLITE_DONE ); + if( rc==SQLITE_OK ) rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0); + if( rc==SQLITE_OK ){ + const char *zBegin = rbuIsVacuum(p) ? "BEGIN" : "BEGIN IMMEDIATE"; + rc = sqlite3_exec(p->dbRbu, zBegin, 0, 0, 0); + } + if( rc==SQLITE_OK ) rc = sqlite3_exec(p->dbMain, "BEGIN IMMEDIATE", 0, 0,0); + } + + p->rc = rc; + return rc; +} + +/* +** Default xRename callback for RBU. +*/ +static int xDefaultRename(void *pArg, const char *zOld, const char *zNew){ + int rc = SQLITE_OK; +#if defined(_WIN32_WCE) + { + LPWSTR zWideOld; + LPWSTR zWideNew; + + zWideOld = rbuWinUtf8ToUnicode(zOld); + if( zWideOld ){ + zWideNew = rbuWinUtf8ToUnicode(zNew); + if( zWideNew ){ + if( MoveFileW(zWideOld, zWideNew) ){ + rc = SQLITE_OK; + }else{ + rc = SQLITE_IOERR; + } + sqlite3_free(zWideNew); + }else{ + rc = SQLITE_IOERR_NOMEM; + } + sqlite3_free(zWideOld); + }else{ + rc = SQLITE_IOERR_NOMEM; + } + } +#else + rc = rename(zOld, zNew) ? SQLITE_IOERR : SQLITE_OK; +#endif + return rc; +} + +SQLITE_API void sqlite3rbu_rename_handler( + sqlite3rbu *pRbu, + void *pArg, + int (*xRename)(void *pArg, const char *zOld, const char *zNew) +){ + if( xRename ){ + pRbu->xRename = xRename; + pRbu->pRenameArg = pArg; + }else{ + pRbu->xRename = xDefaultRename; + pRbu->pRenameArg = 0; + } +} + +/************************************************************************** +** Beginning of RBU VFS shim methods. The VFS shim modifies the behaviour +** of a standard VFS in the following ways: +** +** 1. Whenever the first page of a main database file is read or +** written, the value of the change-counter cookie is stored in +** rbu_file.iCookie. Similarly, the value of the "write-version" +** database header field is stored in rbu_file.iWriteVer. This ensures +** that the values are always trustworthy within an open transaction. +** +** 2. Whenever an SQLITE_OPEN_WAL file is opened, the (rbu_file.pWalFd) +** member variable of the associated database file descriptor is set +** to point to the new file. A mutex protected linked list of all main +** db fds opened using a particular RBU VFS is maintained at +** rbu_vfs.pMain to facilitate this. +** +** 3. Using a new file-control "SQLITE_FCNTL_RBU", a main db rbu_file +** object can be marked as the target database of an RBU update. This +** turns on the following extra special behaviour: +** +** 3a. If xAccess() is called to check if there exists a *-wal file +** associated with an RBU target database currently in RBU_STAGE_OAL +** stage (preparing the *-oal file), the following special handling +** applies: +** +** * if the *-wal file does exist, return SQLITE_CANTOPEN. An RBU +** target database may not be in wal mode already. +** +** * if the *-wal file does not exist, set the output parameter to +** non-zero (to tell SQLite that it does exist) anyway. +** +** Then, when xOpen() is called to open the *-wal file associated with +** the RBU target in RBU_STAGE_OAL stage, instead of opening the *-wal +** file, the rbu vfs opens the corresponding *-oal file instead. +** +** 3b. The *-shm pages returned by xShmMap() for a target db file in +** RBU_STAGE_OAL mode are actually stored in heap memory. This is to +** avoid creating a *-shm file on disk. Additionally, xShmLock() calls +** are no-ops on target database files in RBU_STAGE_OAL mode. This is +** because assert() statements in some VFS implementations fail if +** xShmLock() is called before xShmMap(). +** +** 3c. If an EXCLUSIVE lock is attempted on a target database file in any +** mode except RBU_STAGE_DONE (all work completed and checkpointed), it +** fails with an SQLITE_BUSY error. This is to stop RBU connections +** from automatically checkpointing a *-wal (or *-oal) file from within +** sqlite3_close(). +** +** 3d. In RBU_STAGE_CAPTURE mode, all xRead() calls on the wal file, and +** all xWrite() calls on the target database file perform no IO. +** Instead the frame and page numbers that would be read and written +** are recorded. Additionally, successful attempts to obtain exclusive +** xShmLock() WRITER, CHECKPOINTER and READ0 locks on the target +** database file are recorded. xShmLock() calls to unlock the same +** locks are no-ops (so that once obtained, these locks are never +** relinquished). Finally, calls to xSync() on the target database +** file fail with SQLITE_NOTICE errors. +*/ + +static void rbuUnlockShm(rbu_file *p){ + assert( p->openFlags & SQLITE_OPEN_MAIN_DB ); + if( p->pRbu ){ + int (*xShmLock)(sqlite3_file*,int,int,int) = p->pReal->pMethods->xShmLock; + int i; + for(i=0; i<SQLITE_SHM_NLOCK;i++){ + if( (1<<i) & p->pRbu->mLock ){ + xShmLock(p->pReal, i, 1, SQLITE_SHM_UNLOCK|SQLITE_SHM_EXCLUSIVE); + } + } + p->pRbu->mLock = 0; + } +} + +/* +*/ +static int rbuUpdateTempSize(rbu_file *pFd, sqlite3_int64 nNew){ + sqlite3rbu *pRbu = pFd->pRbu; + i64 nDiff = nNew - pFd->sz; + pRbu->szTemp += nDiff; + pFd->sz = nNew; + assert( pRbu->szTemp>=0 ); + if( pRbu->szTempLimit && pRbu->szTemp>pRbu->szTempLimit ) return SQLITE_FULL; + return SQLITE_OK; +} + +/* +** Add an item to the main-db lists, if it is not already present. +** +** There are two main-db lists. One for all file descriptors, and one +** for all file descriptors with rbu_file.pDb!=0. If the argument has +** rbu_file.pDb!=0, then it is assumed to already be present on the +** main list and is only added to the pDb!=0 list. +*/ +static void rbuMainlistAdd(rbu_file *p){ + rbu_vfs *pRbuVfs = p->pRbuVfs; + rbu_file *pIter; + assert( (p->openFlags & SQLITE_OPEN_MAIN_DB) ); + sqlite3_mutex_enter(pRbuVfs->mutex); + if( p->pRbu==0 ){ + for(pIter=pRbuVfs->pMain; pIter; pIter=pIter->pMainNext); + p->pMainNext = pRbuVfs->pMain; + pRbuVfs->pMain = p; + }else{ + for(pIter=pRbuVfs->pMainRbu; pIter && pIter!=p; pIter=pIter->pMainRbuNext){} + if( pIter==0 ){ + p->pMainRbuNext = pRbuVfs->pMainRbu; + pRbuVfs->pMainRbu = p; + } + } + sqlite3_mutex_leave(pRbuVfs->mutex); +} + +/* +** Remove an item from the main-db lists. +*/ +static void rbuMainlistRemove(rbu_file *p){ + rbu_file **pp; + sqlite3_mutex_enter(p->pRbuVfs->mutex); + for(pp=&p->pRbuVfs->pMain; *pp && *pp!=p; pp=&((*pp)->pMainNext)){} + if( *pp ) *pp = p->pMainNext; + p->pMainNext = 0; + for(pp=&p->pRbuVfs->pMainRbu; *pp && *pp!=p; pp=&((*pp)->pMainRbuNext)){} + if( *pp ) *pp = p->pMainRbuNext; + p->pMainRbuNext = 0; + sqlite3_mutex_leave(p->pRbuVfs->mutex); +} + +/* +** Given that zWal points to a buffer containing a wal file name passed to +** either the xOpen() or xAccess() VFS method, search the main-db list for +** a file-handle opened by the same database connection on the corresponding +** database file. +** +** If parameter bRbu is true, only search for file-descriptors with +** rbu_file.pDb!=0. +*/ +static rbu_file *rbuFindMaindb(rbu_vfs *pRbuVfs, const char *zWal, int bRbu){ + rbu_file *pDb; + sqlite3_mutex_enter(pRbuVfs->mutex); + if( bRbu ){ + for(pDb=pRbuVfs->pMainRbu; pDb && pDb->zWal!=zWal; pDb=pDb->pMainRbuNext){} + }else{ + for(pDb=pRbuVfs->pMain; pDb && pDb->zWal!=zWal; pDb=pDb->pMainNext){} + } + sqlite3_mutex_leave(pRbuVfs->mutex); + return pDb; +} + +/* +** Close an rbu file. +*/ +static int rbuVfsClose(sqlite3_file *pFile){ + rbu_file *p = (rbu_file*)pFile; + int rc; + int i; + + /* Free the contents of the apShm[] array. And the array itself. */ + for(i=0; i<p->nShm; i++){ + sqlite3_free(p->apShm[i]); + } + sqlite3_free(p->apShm); + p->apShm = 0; + sqlite3_free(p->zDel); + + if( p->openFlags & SQLITE_OPEN_MAIN_DB ){ + const sqlite3_io_methods *pMeth = p->pReal->pMethods; + rbuMainlistRemove(p); + rbuUnlockShm(p); + if( pMeth->iVersion>1 && pMeth->xShmUnmap ){ + pMeth->xShmUnmap(p->pReal, 0); + } + } + else if( (p->openFlags & SQLITE_OPEN_DELETEONCLOSE) && p->pRbu ){ + rbuUpdateTempSize(p, 0); + } + assert( p->pMainNext==0 && p->pRbuVfs->pMain!=p ); + + /* Close the underlying file handle */ + rc = p->pReal->pMethods->xClose(p->pReal); + return rc; +} + + +/* +** Read and return an unsigned 32-bit big-endian integer from the buffer +** passed as the only argument. +*/ +static u32 rbuGetU32(u8 *aBuf){ + return ((u32)aBuf[0] << 24) + + ((u32)aBuf[1] << 16) + + ((u32)aBuf[2] << 8) + + ((u32)aBuf[3]); +} + +/* +** Write an unsigned 32-bit value in big-endian format to the supplied +** buffer. +*/ +static void rbuPutU32(u8 *aBuf, u32 iVal){ + aBuf[0] = (iVal >> 24) & 0xFF; + aBuf[1] = (iVal >> 16) & 0xFF; + aBuf[2] = (iVal >> 8) & 0xFF; + aBuf[3] = (iVal >> 0) & 0xFF; +} + +static void rbuPutU16(u8 *aBuf, u16 iVal){ + aBuf[0] = (iVal >> 8) & 0xFF; + aBuf[1] = (iVal >> 0) & 0xFF; +} + +/* +** Read data from an rbuVfs-file. +*/ +static int rbuVfsRead( + sqlite3_file *pFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + rbu_file *p = (rbu_file*)pFile; + sqlite3rbu *pRbu = p->pRbu; + int rc; + + if( pRbu && pRbu->eStage==RBU_STAGE_CAPTURE ){ + assert( p->openFlags & SQLITE_OPEN_WAL ); + rc = rbuCaptureWalRead(p->pRbu, iOfst, iAmt); + }else{ + if( pRbu && pRbu->eStage==RBU_STAGE_OAL + && (p->openFlags & SQLITE_OPEN_WAL) + && iOfst>=pRbu->iOalSz + ){ + rc = SQLITE_OK; + memset(zBuf, 0, iAmt); + }else{ + rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); +#if 1 + /* If this is being called to read the first page of the target + ** database as part of an rbu vacuum operation, synthesize the + ** contents of the first page if it does not yet exist. Otherwise, + ** SQLite will not check for a *-wal file. */ + if( pRbu && rbuIsVacuum(pRbu) + && rc==SQLITE_IOERR_SHORT_READ && iOfst==0 + && (p->openFlags & SQLITE_OPEN_MAIN_DB) + && pRbu->rc==SQLITE_OK + ){ + sqlite3_file *pFd = (sqlite3_file*)pRbu->pRbuFd; + rc = pFd->pMethods->xRead(pFd, zBuf, iAmt, iOfst); + if( rc==SQLITE_OK ){ + u8 *aBuf = (u8*)zBuf; + u32 iRoot = rbuGetU32(&aBuf[52]) ? 1 : 0; + rbuPutU32(&aBuf[52], iRoot); /* largest root page number */ + rbuPutU32(&aBuf[36], 0); /* number of free pages */ + rbuPutU32(&aBuf[32], 0); /* first page on free list trunk */ + rbuPutU32(&aBuf[28], 1); /* size of db file in pages */ + rbuPutU32(&aBuf[24], pRbu->pRbuFd->iCookie+1); /* Change counter */ + + if( iAmt>100 ){ + memset(&aBuf[100], 0, iAmt-100); + rbuPutU16(&aBuf[105], iAmt & 0xFFFF); + aBuf[100] = 0x0D; + } + } + } +#endif + } + if( rc==SQLITE_OK && iOfst==0 && (p->openFlags & SQLITE_OPEN_MAIN_DB) ){ + /* These look like magic numbers. But they are stable, as they are part + ** of the definition of the SQLite file format, which may not change. */ + u8 *pBuf = (u8*)zBuf; + p->iCookie = rbuGetU32(&pBuf[24]); + p->iWriteVer = pBuf[19]; + } + } + return rc; +} + +/* +** Write data to an rbuVfs-file. +*/ +static int rbuVfsWrite( + sqlite3_file *pFile, + const void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + rbu_file *p = (rbu_file*)pFile; + sqlite3rbu *pRbu = p->pRbu; + int rc; + + if( pRbu && pRbu->eStage==RBU_STAGE_CAPTURE ){ + assert( p->openFlags & SQLITE_OPEN_MAIN_DB ); + rc = rbuCaptureDbWrite(p->pRbu, iOfst); + }else{ + if( pRbu ){ + if( pRbu->eStage==RBU_STAGE_OAL + && (p->openFlags & SQLITE_OPEN_WAL) + && iOfst>=pRbu->iOalSz + ){ + pRbu->iOalSz = iAmt + iOfst; + }else if( p->openFlags & SQLITE_OPEN_DELETEONCLOSE ){ + i64 szNew = iAmt+iOfst; + if( szNew>p->sz ){ + rc = rbuUpdateTempSize(p, szNew); + if( rc!=SQLITE_OK ) return rc; + } + } + } + rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst); + if( rc==SQLITE_OK && iOfst==0 && (p->openFlags & SQLITE_OPEN_MAIN_DB) ){ + /* These look like magic numbers. But they are stable, as they are part + ** of the definition of the SQLite file format, which may not change. */ + u8 *pBuf = (u8*)zBuf; + p->iCookie = rbuGetU32(&pBuf[24]); + p->iWriteVer = pBuf[19]; + } + } + return rc; +} + +/* +** Truncate an rbuVfs-file. +*/ +static int rbuVfsTruncate(sqlite3_file *pFile, sqlite_int64 size){ + rbu_file *p = (rbu_file*)pFile; + if( (p->openFlags & SQLITE_OPEN_DELETEONCLOSE) && p->pRbu ){ + int rc = rbuUpdateTempSize(p, size); + if( rc!=SQLITE_OK ) return rc; + } + return p->pReal->pMethods->xTruncate(p->pReal, size); +} + +/* +** Sync an rbuVfs-file. +*/ +static int rbuVfsSync(sqlite3_file *pFile, int flags){ + rbu_file *p = (rbu_file *)pFile; + if( p->pRbu && p->pRbu->eStage==RBU_STAGE_CAPTURE ){ + if( p->openFlags & SQLITE_OPEN_MAIN_DB ){ + return SQLITE_NOTICE_RBU; + } + return SQLITE_OK; + } + return p->pReal->pMethods->xSync(p->pReal, flags); +} + +/* +** Return the current file-size of an rbuVfs-file. +*/ +static int rbuVfsFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ + rbu_file *p = (rbu_file *)pFile; + int rc; + rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); + + /* If this is an RBU vacuum operation and this is the target database, + ** pretend that it has at least one page. Otherwise, SQLite will not + ** check for the existance of a *-wal file. rbuVfsRead() contains + ** similar logic. */ + if( rc==SQLITE_OK && *pSize==0 + && p->pRbu && rbuIsVacuum(p->pRbu) + && (p->openFlags & SQLITE_OPEN_MAIN_DB) + ){ + *pSize = 1024; + } + return rc; +} + +/* +** Lock an rbuVfs-file. +*/ +static int rbuVfsLock(sqlite3_file *pFile, int eLock){ + rbu_file *p = (rbu_file*)pFile; + sqlite3rbu *pRbu = p->pRbu; + int rc = SQLITE_OK; + + assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) ); + if( eLock==SQLITE_LOCK_EXCLUSIVE + && (p->bNolock || (pRbu && pRbu->eStage!=RBU_STAGE_DONE)) + ){ + /* Do not allow EXCLUSIVE locks. Preventing SQLite from taking this + ** prevents it from checkpointing the database from sqlite3_close(). */ + rc = SQLITE_BUSY; + }else{ + rc = p->pReal->pMethods->xLock(p->pReal, eLock); + } + + return rc; +} + +/* +** Unlock an rbuVfs-file. +*/ +static int rbuVfsUnlock(sqlite3_file *pFile, int eLock){ + rbu_file *p = (rbu_file *)pFile; + return p->pReal->pMethods->xUnlock(p->pReal, eLock); +} + +/* +** Check if another file-handle holds a RESERVED lock on an rbuVfs-file. +*/ +static int rbuVfsCheckReservedLock(sqlite3_file *pFile, int *pResOut){ + rbu_file *p = (rbu_file *)pFile; + return p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut); +} + +/* +** File control method. For custom operations on an rbuVfs-file. +*/ +static int rbuVfsFileControl(sqlite3_file *pFile, int op, void *pArg){ + rbu_file *p = (rbu_file *)pFile; + int (*xControl)(sqlite3_file*,int,void*) = p->pReal->pMethods->xFileControl; + int rc; + + assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) + || p->openFlags & (SQLITE_OPEN_TRANSIENT_DB|SQLITE_OPEN_TEMP_JOURNAL) + ); + if( op==SQLITE_FCNTL_RBU ){ + sqlite3rbu *pRbu = (sqlite3rbu*)pArg; + + /* First try to find another RBU vfs lower down in the vfs stack. If + ** one is found, this vfs will operate in pass-through mode. The lower + ** level vfs will do the special RBU handling. */ + rc = xControl(p->pReal, op, pArg); + + if( rc==SQLITE_NOTFOUND ){ + /* Now search for a zipvfs instance lower down in the VFS stack. If + ** one is found, this is an error. */ + void *dummy = 0; + rc = xControl(p->pReal, SQLITE_FCNTL_ZIPVFS, &dummy); + if( rc==SQLITE_OK ){ + rc = SQLITE_ERROR; + pRbu->zErrmsg = sqlite3_mprintf("rbu/zipvfs setup error"); + }else if( rc==SQLITE_NOTFOUND ){ + pRbu->pTargetFd = p; + p->pRbu = pRbu; + rbuMainlistAdd(p); + if( p->pWalFd ) p->pWalFd->pRbu = pRbu; + rc = SQLITE_OK; + } + } + return rc; + } + else if( op==SQLITE_FCNTL_RBUCNT ){ + sqlite3rbu *pRbu = (sqlite3rbu*)pArg; + pRbu->nRbu++; + pRbu->pRbuFd = p; + p->bNolock = 1; + } + + rc = xControl(p->pReal, op, pArg); + if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){ + rbu_vfs *pRbuVfs = p->pRbuVfs; + char *zIn = *(char**)pArg; + char *zOut = sqlite3_mprintf("rbu(%s)/%z", pRbuVfs->base.zName, zIn); + *(char**)pArg = zOut; + if( zOut==0 ) rc = SQLITE_NOMEM; + } + + return rc; +} + +/* +** Return the sector-size in bytes for an rbuVfs-file. +*/ +static int rbuVfsSectorSize(sqlite3_file *pFile){ + rbu_file *p = (rbu_file *)pFile; + return p->pReal->pMethods->xSectorSize(p->pReal); +} + +/* +** Return the device characteristic flags supported by an rbuVfs-file. +*/ +static int rbuVfsDeviceCharacteristics(sqlite3_file *pFile){ + rbu_file *p = (rbu_file *)pFile; + return p->pReal->pMethods->xDeviceCharacteristics(p->pReal); +} + +/* +** Take or release a shared-memory lock. +*/ +static int rbuVfsShmLock(sqlite3_file *pFile, int ofst, int n, int flags){ + rbu_file *p = (rbu_file*)pFile; + sqlite3rbu *pRbu = p->pRbu; + int rc = SQLITE_OK; + +#ifdef SQLITE_AMALGAMATION + assert( WAL_CKPT_LOCK==1 ); +#endif + + assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) ); + if( pRbu && ( + pRbu->eStage==RBU_STAGE_OAL + || pRbu->eStage==RBU_STAGE_MOVE + || pRbu->eStage==RBU_STAGE_DONE + )){ + /* Prevent SQLite from taking a shm-lock on the target file when it + ** is supplying heap memory to the upper layer in place of *-shm + ** segments. */ + if( ofst==WAL_LOCK_CKPT && n==1 ) rc = SQLITE_BUSY; + }else{ + int bCapture = 0; + if( pRbu && pRbu->eStage==RBU_STAGE_CAPTURE ){ + bCapture = 1; + } + if( bCapture==0 || 0==(flags & SQLITE_SHM_UNLOCK) ){ + rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags); + if( bCapture && rc==SQLITE_OK ){ + pRbu->mLock |= ((1<<n) - 1) << ofst; + } + } + } + + return rc; +} + +/* +** Obtain a pointer to a mapping of a single 32KiB page of the *-shm file. +*/ +static int rbuVfsShmMap( + sqlite3_file *pFile, + int iRegion, + int szRegion, + int isWrite, + void volatile **pp +){ + rbu_file *p = (rbu_file*)pFile; + int rc = SQLITE_OK; + int eStage = (p->pRbu ? p->pRbu->eStage : 0); + + /* If not in RBU_STAGE_OAL, allow this call to pass through. Or, if this + ** rbu is in the RBU_STAGE_OAL state, use heap memory for *-shm space + ** instead of a file on disk. */ + assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) ); + if( eStage==RBU_STAGE_OAL ){ + sqlite3_int64 nByte = (iRegion+1) * sizeof(char*); + char **apNew = (char**)sqlite3_realloc64(p->apShm, nByte); + + /* This is an RBU connection that uses its own heap memory for the + ** pages of the *-shm file. Since no other process can have run + ** recovery, the connection must request *-shm pages in order + ** from start to finish. */ + assert( iRegion==p->nShm ); + if( apNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(&apNew[p->nShm], 0, sizeof(char*) * (1 + iRegion - p->nShm)); + p->apShm = apNew; + p->nShm = iRegion+1; + } + + if( rc==SQLITE_OK ){ + char *pNew = (char*)sqlite3_malloc64(szRegion); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pNew, 0, szRegion); + p->apShm[iRegion] = pNew; + } + } + + if( rc==SQLITE_OK ){ + *pp = p->apShm[iRegion]; + }else{ + *pp = 0; + } + }else{ + assert( p->apShm==0 ); + rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp); + } + + return rc; +} + +/* +** Memory barrier. +*/ +static void rbuVfsShmBarrier(sqlite3_file *pFile){ + rbu_file *p = (rbu_file *)pFile; + p->pReal->pMethods->xShmBarrier(p->pReal); +} + +/* +** The xShmUnmap method. +*/ +static int rbuVfsShmUnmap(sqlite3_file *pFile, int delFlag){ + rbu_file *p = (rbu_file*)pFile; + int rc = SQLITE_OK; + int eStage = (p->pRbu ? p->pRbu->eStage : 0); + + assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) ); + if( eStage==RBU_STAGE_OAL || eStage==RBU_STAGE_MOVE ){ + /* no-op */ + }else{ + /* Release the checkpointer and writer locks */ + rbuUnlockShm(p); + rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag); + } + return rc; +} + +/* +** Open an rbu file handle. +*/ +static int rbuVfsOpen( + sqlite3_vfs *pVfs, + const char *zName, + sqlite3_file *pFile, + int flags, + int *pOutFlags +){ + static sqlite3_io_methods rbuvfs_io_methods = { + 2, /* iVersion */ + rbuVfsClose, /* xClose */ + rbuVfsRead, /* xRead */ + rbuVfsWrite, /* xWrite */ + rbuVfsTruncate, /* xTruncate */ + rbuVfsSync, /* xSync */ + rbuVfsFileSize, /* xFileSize */ + rbuVfsLock, /* xLock */ + rbuVfsUnlock, /* xUnlock */ + rbuVfsCheckReservedLock, /* xCheckReservedLock */ + rbuVfsFileControl, /* xFileControl */ + rbuVfsSectorSize, /* xSectorSize */ + rbuVfsDeviceCharacteristics, /* xDeviceCharacteristics */ + rbuVfsShmMap, /* xShmMap */ + rbuVfsShmLock, /* xShmLock */ + rbuVfsShmBarrier, /* xShmBarrier */ + rbuVfsShmUnmap, /* xShmUnmap */ + 0, 0 /* xFetch, xUnfetch */ + }; + static sqlite3_io_methods rbuvfs_io_methods1 = { + 1, /* iVersion */ + rbuVfsClose, /* xClose */ + rbuVfsRead, /* xRead */ + rbuVfsWrite, /* xWrite */ + rbuVfsTruncate, /* xTruncate */ + rbuVfsSync, /* xSync */ + rbuVfsFileSize, /* xFileSize */ + rbuVfsLock, /* xLock */ + rbuVfsUnlock, /* xUnlock */ + rbuVfsCheckReservedLock, /* xCheckReservedLock */ + rbuVfsFileControl, /* xFileControl */ + rbuVfsSectorSize, /* xSectorSize */ + rbuVfsDeviceCharacteristics, /* xDeviceCharacteristics */ + 0, 0, 0, 0, 0, 0 + }; + + + + rbu_vfs *pRbuVfs = (rbu_vfs*)pVfs; + sqlite3_vfs *pRealVfs = pRbuVfs->pRealVfs; + rbu_file *pFd = (rbu_file *)pFile; + int rc = SQLITE_OK; + const char *zOpen = zName; + int oflags = flags; + + memset(pFd, 0, sizeof(rbu_file)); + pFd->pReal = (sqlite3_file*)&pFd[1]; + pFd->pRbuVfs = pRbuVfs; + pFd->openFlags = flags; + if( zName ){ + if( flags & SQLITE_OPEN_MAIN_DB ){ + /* A main database has just been opened. The following block sets + ** (pFd->zWal) to point to a buffer owned by SQLite that contains + ** the name of the *-wal file this db connection will use. SQLite + ** happens to pass a pointer to this buffer when using xAccess() + ** or xOpen() to operate on the *-wal file. */ + pFd->zWal = sqlite3_filename_wal(zName); + } + else if( flags & SQLITE_OPEN_WAL ){ + rbu_file *pDb = rbuFindMaindb(pRbuVfs, zName, 0); + if( pDb ){ + if( pDb->pRbu && pDb->pRbu->eStage==RBU_STAGE_OAL ){ + /* This call is to open a *-wal file. Intead, open the *-oal. */ + size_t nOpen; + if( rbuIsVacuum(pDb->pRbu) ){ + zOpen = sqlite3_db_filename(pDb->pRbu->dbRbu, "main"); + zOpen = sqlite3_filename_wal(zOpen); + } + nOpen = strlen(zOpen); + ((char*)zOpen)[nOpen-3] = 'o'; + pFd->pRbu = pDb->pRbu; + } + pDb->pWalFd = pFd; + } + } + }else{ + pFd->pRbu = pRbuVfs->pRbu; + } + + if( oflags & SQLITE_OPEN_MAIN_DB + && sqlite3_uri_boolean(zName, "rbu_memory", 0) + ){ + assert( oflags & SQLITE_OPEN_MAIN_DB ); + oflags = SQLITE_OPEN_TEMP_DB | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | + SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE; + zOpen = 0; + } + + if( rc==SQLITE_OK ){ + rc = pRealVfs->xOpen(pRealVfs, zOpen, pFd->pReal, oflags, pOutFlags); + } + if( pFd->pReal->pMethods ){ + const sqlite3_io_methods *pMeth = pFd->pReal->pMethods; + /* The xOpen() operation has succeeded. Set the sqlite3_file.pMethods + ** pointer and, if the file is a main database file, link it into the + ** mutex protected linked list of all such files. */ + if( pMeth->iVersion<2 || pMeth->xShmLock==0 ){ + pFile->pMethods = &rbuvfs_io_methods1; + }else{ + pFile->pMethods = &rbuvfs_io_methods; + } + if( flags & SQLITE_OPEN_MAIN_DB ){ + rbuMainlistAdd(pFd); + } + }else{ + sqlite3_free(pFd->zDel); + } + + return rc; +} + +/* +** Delete the file located at zPath. +*/ +static int rbuVfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; + return pRealVfs->xDelete(pRealVfs, zPath, dirSync); +} + +/* +** Test for access permissions. Return true if the requested permission +** is available, or false otherwise. +*/ +static int rbuVfsAccess( + sqlite3_vfs *pVfs, + const char *zPath, + int flags, + int *pResOut +){ + rbu_vfs *pRbuVfs = (rbu_vfs*)pVfs; + sqlite3_vfs *pRealVfs = pRbuVfs->pRealVfs; + int rc; + + rc = pRealVfs->xAccess(pRealVfs, zPath, flags, pResOut); + + /* If this call is to check if a *-wal file associated with an RBU target + ** database connection exists, and the RBU update is in RBU_STAGE_OAL, + ** the following special handling is activated: + ** + ** a) if the *-wal file does exist, return SQLITE_CANTOPEN. This + ** ensures that the RBU extension never tries to update a database + ** in wal mode, even if the first page of the database file has + ** been damaged. + ** + ** b) if the *-wal file does not exist, claim that it does anyway, + ** causing SQLite to call xOpen() to open it. This call will also + ** be intercepted (see the rbuVfsOpen() function) and the *-oal + ** file opened instead. + */ + if( rc==SQLITE_OK && flags==SQLITE_ACCESS_EXISTS ){ + rbu_file *pDb = rbuFindMaindb(pRbuVfs, zPath, 1); + if( pDb && pDb->pRbu->eStage==RBU_STAGE_OAL ){ + assert( pDb->pRbu ); + if( *pResOut ){ + rc = SQLITE_CANTOPEN; + }else{ + sqlite3_int64 sz = 0; + rc = rbuVfsFileSize(&pDb->base, &sz); + *pResOut = (sz>0); + } + } + } + + return rc; +} + +/* +** Populate buffer zOut with the full canonical pathname corresponding +** to the pathname in zPath. zOut is guaranteed to point to a buffer +** of at least (DEVSYM_MAX_PATHNAME+1) bytes. +*/ +static int rbuVfsFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, + char *zOut +){ + sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; + return pRealVfs->xFullPathname(pRealVfs, zPath, nOut, zOut); +} + +#ifndef SQLITE_OMIT_LOAD_EXTENSION +/* +** Open the dynamic library located at zPath and return a handle. +*/ +static void *rbuVfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; + return pRealVfs->xDlOpen(pRealVfs, zPath); +} + +/* +** Populate the buffer zErrMsg (size nByte bytes) with a human readable +** utf-8 string describing the most recent error encountered associated +** with dynamic libraries. +*/ +static void rbuVfsDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ + sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; + pRealVfs->xDlError(pRealVfs, nByte, zErrMsg); +} + +/* +** Return a pointer to the symbol zSymbol in the dynamic library pHandle. +*/ +static void (*rbuVfsDlSym( + sqlite3_vfs *pVfs, + void *pArg, + const char *zSym +))(void){ + sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; + return pRealVfs->xDlSym(pRealVfs, pArg, zSym); +} + +/* +** Close the dynamic library handle pHandle. +*/ +static void rbuVfsDlClose(sqlite3_vfs *pVfs, void *pHandle){ + sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; + pRealVfs->xDlClose(pRealVfs, pHandle); +} +#endif /* SQLITE_OMIT_LOAD_EXTENSION */ + +/* +** Populate the buffer pointed to by zBufOut with nByte bytes of +** random data. +*/ +static int rbuVfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ + sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; + return pRealVfs->xRandomness(pRealVfs, nByte, zBufOut); +} + +/* +** Sleep for nMicro microseconds. Return the number of microseconds +** actually slept. +*/ +static int rbuVfsSleep(sqlite3_vfs *pVfs, int nMicro){ + sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; + return pRealVfs->xSleep(pRealVfs, nMicro); +} + +/* +** Return the current time as a Julian Day number in *pTimeOut. +*/ +static int rbuVfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ + sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; + return pRealVfs->xCurrentTime(pRealVfs, pTimeOut); +} + +/* +** No-op. +*/ +static int rbuVfsGetLastError(sqlite3_vfs *pVfs, int a, char *b){ + return 0; +} + +/* +** Deregister and destroy an RBU vfs created by an earlier call to +** sqlite3rbu_create_vfs(). +*/ +SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(zName); + if( pVfs && pVfs->xOpen==rbuVfsOpen ){ + sqlite3_mutex_free(((rbu_vfs*)pVfs)->mutex); + sqlite3_vfs_unregister(pVfs); + sqlite3_free(pVfs); + } +} + +/* +** Create an RBU VFS named zName that accesses the underlying file-system +** via existing VFS zParent. The new object is registered as a non-default +** VFS with SQLite before returning. +*/ +SQLITE_API int sqlite3rbu_create_vfs(const char *zName, const char *zParent){ + + /* Template for VFS */ + static sqlite3_vfs vfs_template = { + 1, /* iVersion */ + 0, /* szOsFile */ + 0, /* mxPathname */ + 0, /* pNext */ + 0, /* zName */ + 0, /* pAppData */ + rbuVfsOpen, /* xOpen */ + rbuVfsDelete, /* xDelete */ + rbuVfsAccess, /* xAccess */ + rbuVfsFullPathname, /* xFullPathname */ + +#ifndef SQLITE_OMIT_LOAD_EXTENSION + rbuVfsDlOpen, /* xDlOpen */ + rbuVfsDlError, /* xDlError */ + rbuVfsDlSym, /* xDlSym */ + rbuVfsDlClose, /* xDlClose */ +#else + 0, 0, 0, 0, +#endif + + rbuVfsRandomness, /* xRandomness */ + rbuVfsSleep, /* xSleep */ + rbuVfsCurrentTime, /* xCurrentTime */ + rbuVfsGetLastError, /* xGetLastError */ + 0, /* xCurrentTimeInt64 (version 2) */ + 0, 0, 0 /* Unimplemented version 3 methods */ + }; + + rbu_vfs *pNew = 0; /* Newly allocated VFS */ + int rc = SQLITE_OK; + size_t nName; + size_t nByte; + + nName = strlen(zName); + nByte = sizeof(rbu_vfs) + nName + 1; + pNew = (rbu_vfs*)sqlite3_malloc64(nByte); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_vfs *pParent; /* Parent VFS */ + memset(pNew, 0, nByte); + pParent = sqlite3_vfs_find(zParent); + if( pParent==0 ){ + rc = SQLITE_NOTFOUND; + }else{ + char *zSpace; + memcpy(&pNew->base, &vfs_template, sizeof(sqlite3_vfs)); + pNew->base.mxPathname = pParent->mxPathname; + pNew->base.szOsFile = sizeof(rbu_file) + pParent->szOsFile; + pNew->pRealVfs = pParent; + pNew->base.zName = (const char*)(zSpace = (char*)&pNew[1]); + memcpy(zSpace, zName, nName); + + /* Allocate the mutex and register the new VFS (not as the default) */ + pNew->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE); + if( pNew->mutex==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_vfs_register(&pNew->base, 0); + } + } + + if( rc!=SQLITE_OK ){ + sqlite3_mutex_free(pNew->mutex); + sqlite3_free(pNew); + } + } + + return rc; +} + +/* +** Configure the aggregate temp file size limit for this RBU handle. +*/ +SQLITE_API sqlite3_int64 sqlite3rbu_temp_size_limit(sqlite3rbu *pRbu, sqlite3_int64 n){ + if( n>=0 ){ + pRbu->szTempLimit = n; + } + return pRbu->szTempLimit; +} + +SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu *pRbu){ + return pRbu->szTemp; +} + + +/**************************************************************************/ + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RBU) */ + +/************** End of sqlite3rbu.c ******************************************/ +/************** Begin file dbstat.c ******************************************/ +/* +** 2010 July 12 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains an implementation of the "dbstat" virtual table. +** +** The dbstat virtual table is used to extract low-level storage +** information from an SQLite database in order to implement the +** "sqlite3_analyzer" utility. See the ../tool/spaceanal.tcl script +** for an example implementation. +** +** Additional information is available on the "dbstat.html" page of the +** official SQLite documentation. +*/ + +/* #include "sqliteInt.h" ** Requires access to internal data structures ** */ +#if (defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)) \ + && !defined(SQLITE_OMIT_VIRTUALTABLE) + +/* +** The pager and btree modules arrange objects in memory so that there are +** always approximately 200 bytes of addressable memory following each page +** buffer. This way small buffer overreads caused by corrupt database pages +** do not cause undefined behaviour. This module pads each page buffer +** by the following number of bytes for the same purpose. +*/ +#define DBSTAT_PAGE_PADDING_BYTES 256 + +/* +** Page paths: +** +** The value of the 'path' column describes the path taken from the +** root-node of the b-tree structure to each page. The value of the +** root-node path is '/'. +** +** The value of the path for the left-most child page of the root of +** a b-tree is '/000/'. (Btrees store content ordered from left to right +** so the pages to the left have smaller keys than the pages to the right.) +** The next to left-most child of the root page is +** '/001', and so on, each sibling page identified by a 3-digit hex +** value. The children of the 451st left-most sibling have paths such +** as '/1c2/000/, '/1c2/001/' etc. +** +** Overflow pages are specified by appending a '+' character and a +** six-digit hexadecimal value to the path to the cell they are linked +** from. For example, the three overflow pages in a chain linked from +** the left-most cell of the 450th child of the root page are identified +** by the paths: +** +** '/1c2/000+000000' // First page in overflow chain +** '/1c2/000+000001' // Second page in overflow chain +** '/1c2/000+000002' // Third page in overflow chain +** +** If the paths are sorted using the BINARY collation sequence, then +** the overflow pages associated with a cell will appear earlier in the +** sort-order than its child page: +** +** '/1c2/000/' // Left-most child of 451st child of root +*/ +static const char zDbstatSchema[] = + "CREATE TABLE x(" + " name TEXT," /* 0 Name of table or index */ + " path TEXT," /* 1 Path to page from root (NULL for agg) */ + " pageno INTEGER," /* 2 Page number (page count for aggregates) */ + " pagetype TEXT," /* 3 'internal', 'leaf', 'overflow', or NULL */ + " ncell INTEGER," /* 4 Cells on page (0 for overflow) */ + " payload INTEGER," /* 5 Bytes of payload on this page */ + " unused INTEGER," /* 6 Bytes of unused space on this page */ + " mx_payload INTEGER," /* 7 Largest payload size of all cells */ + " pgoffset INTEGER," /* 8 Offset of page in file (NULL for agg) */ + " pgsize INTEGER," /* 9 Size of the page (sum for aggregate) */ + " schema TEXT HIDDEN," /* 10 Database schema being analyzed */ + " aggregate BOOLEAN HIDDEN" /* 11 aggregate info for each table */ + ")" +; + +/* Forward reference to data structured used in this module */ +typedef struct StatTable StatTable; +typedef struct StatCursor StatCursor; +typedef struct StatPage StatPage; +typedef struct StatCell StatCell; + +/* Size information for a single cell within a btree page */ +struct StatCell { + int nLocal; /* Bytes of local payload */ + u32 iChildPg; /* Child node (or 0 if this is a leaf) */ + int nOvfl; /* Entries in aOvfl[] */ + u32 *aOvfl; /* Array of overflow page numbers */ + int nLastOvfl; /* Bytes of payload on final overflow page */ + int iOvfl; /* Iterates through aOvfl[] */ +}; + +/* Size information for a single btree page */ +struct StatPage { + u32 iPgno; /* Page number */ + u8 *aPg; /* Page buffer from sqlite3_malloc() */ + int iCell; /* Current cell */ + char *zPath; /* Path to this page */ + + /* Variables populated by statDecodePage(): */ + u8 flags; /* Copy of flags byte */ + int nCell; /* Number of cells on page */ + int nUnused; /* Number of unused bytes on page */ + StatCell *aCell; /* Array of parsed cells */ + u32 iRightChildPg; /* Right-child page number (or 0) */ + int nMxPayload; /* Largest payload of any cell on the page */ +}; + +/* The cursor for scanning the dbstat virtual table */ +struct StatCursor { + sqlite3_vtab_cursor base; /* base class. MUST BE FIRST! */ + sqlite3_stmt *pStmt; /* Iterates through set of root pages */ + u8 isEof; /* After pStmt has returned SQLITE_DONE */ + u8 isAgg; /* Aggregate results for each table */ + int iDb; /* Schema used for this query */ + + StatPage aPage[32]; /* Pages in path to current page */ + int iPage; /* Current entry in aPage[] */ + + /* Values to return. */ + u32 iPageno; /* Value of 'pageno' column */ + char *zName; /* Value of 'name' column */ + char *zPath; /* Value of 'path' column */ + char *zPagetype; /* Value of 'pagetype' column */ + int nPage; /* Number of pages in current btree */ + int nCell; /* Value of 'ncell' column */ + int nMxPayload; /* Value of 'mx_payload' column */ + i64 nUnused; /* Value of 'unused' column */ + i64 nPayload; /* Value of 'payload' column */ + i64 iOffset; /* Value of 'pgOffset' column */ + i64 szPage; /* Value of 'pgSize' column */ +}; + +/* An instance of the DBSTAT virtual table */ +struct StatTable { + sqlite3_vtab base; /* base class. MUST BE FIRST! */ + sqlite3 *db; /* Database connection that owns this vtab */ + int iDb; /* Index of database to analyze */ +}; + +#ifndef get2byte +# define get2byte(x) ((x)[0]<<8 | (x)[1]) +#endif + +/* +** Connect to or create a new DBSTAT virtual table. +*/ +static int statConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + StatTable *pTab = 0; + int rc = SQLITE_OK; + int iDb; + (void)pAux; + + if( argc>=4 ){ + Token nm; + sqlite3TokenInit(&nm, (char*)argv[3]); + iDb = sqlite3FindDb(db, &nm); + if( iDb<0 ){ + *pzErr = sqlite3_mprintf("no such database: %s", argv[3]); + return SQLITE_ERROR; + } + }else{ + iDb = 0; + } + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + rc = sqlite3_declare_vtab(db, zDbstatSchema); + if( rc==SQLITE_OK ){ + pTab = (StatTable *)sqlite3_malloc64(sizeof(StatTable)); + if( pTab==0 ) rc = SQLITE_NOMEM_BKPT; + } + + assert( rc==SQLITE_OK || pTab==0 ); + if( rc==SQLITE_OK ){ + memset(pTab, 0, sizeof(StatTable)); + pTab->db = db; + pTab->iDb = iDb; + } + + *ppVtab = (sqlite3_vtab*)pTab; + return rc; +} + +/* +** Disconnect from or destroy the DBSTAT virtual table. +*/ +static int statDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Compute the best query strategy and return the result in idxNum. +** +** idxNum-Bit Meaning +** ---------- ---------------------------------------------- +** 0x01 There is a schema=? term in the WHERE clause +** 0x02 There is a name=? term in the WHERE clause +** 0x04 There is an aggregate=? term in the WHERE clause +** 0x08 Output should be ordered by name and path +*/ +static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + int i; + int iSchema = -1; + int iName = -1; + int iAgg = -1; + (void)tab; + + /* Look for a valid schema=? constraint. If found, change the idxNum to + ** 1 and request the value of that constraint be sent to xFilter. And + ** lower the cost estimate to encourage the constrained version to be + ** used. + */ + for(i=0; i<pIdxInfo->nConstraint; i++){ + if( pIdxInfo->aConstraint[i].op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( pIdxInfo->aConstraint[i].usable==0 ){ + /* Force DBSTAT table should always be the right-most table in a join */ + return SQLITE_CONSTRAINT; + } + switch( pIdxInfo->aConstraint[i].iColumn ){ + case 0: { /* name */ + iName = i; + break; + } + case 10: { /* schema */ + iSchema = i; + break; + } + case 11: { /* aggregate */ + iAgg = i; + break; + } + } + } + i = 0; + if( iSchema>=0 ){ + pIdxInfo->aConstraintUsage[iSchema].argvIndex = ++i; + pIdxInfo->aConstraintUsage[iSchema].omit = 1; + pIdxInfo->idxNum |= 0x01; + } + if( iName>=0 ){ + pIdxInfo->aConstraintUsage[iName].argvIndex = ++i; + pIdxInfo->idxNum |= 0x02; + } + if( iAgg>=0 ){ + pIdxInfo->aConstraintUsage[iAgg].argvIndex = ++i; + pIdxInfo->idxNum |= 0x04; + } + pIdxInfo->estimatedCost = 1.0; + + /* Records are always returned in ascending order of (name, path). + ** If this will satisfy the client, set the orderByConsumed flag so that + ** SQLite does not do an external sort. + */ + if( ( pIdxInfo->nOrderBy==1 + && pIdxInfo->aOrderBy[0].iColumn==0 + && pIdxInfo->aOrderBy[0].desc==0 + ) || + ( pIdxInfo->nOrderBy==2 + && pIdxInfo->aOrderBy[0].iColumn==0 + && pIdxInfo->aOrderBy[0].desc==0 + && pIdxInfo->aOrderBy[1].iColumn==1 + && pIdxInfo->aOrderBy[1].desc==0 + ) + ){ + pIdxInfo->orderByConsumed = 1; + pIdxInfo->idxNum |= 0x08; + } + + return SQLITE_OK; +} + +/* +** Open a new DBSTAT cursor. +*/ +static int statOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + StatTable *pTab = (StatTable *)pVTab; + StatCursor *pCsr; + + pCsr = (StatCursor *)sqlite3_malloc64(sizeof(StatCursor)); + if( pCsr==0 ){ + return SQLITE_NOMEM_BKPT; + }else{ + memset(pCsr, 0, sizeof(StatCursor)); + pCsr->base.pVtab = pVTab; + pCsr->iDb = pTab->iDb; + } + + *ppCursor = (sqlite3_vtab_cursor *)pCsr; + return SQLITE_OK; +} + +static void statClearCells(StatPage *p){ + int i; + if( p->aCell ){ + for(i=0; i<p->nCell; i++){ + sqlite3_free(p->aCell[i].aOvfl); + } + sqlite3_free(p->aCell); + } + p->nCell = 0; + p->aCell = 0; +} + +static void statClearPage(StatPage *p){ + u8 *aPg = p->aPg; + statClearCells(p); + sqlite3_free(p->zPath); + memset(p, 0, sizeof(StatPage)); + p->aPg = aPg; +} + +static void statResetCsr(StatCursor *pCsr){ + int i; + /* In some circumstances, specifically if an OOM has occurred, the call + ** to sqlite3_reset() may cause the pager to be reset (emptied). It is + ** important that statClearPage() is called to free any page refs before + ** this happens. dbsqlfuzz 9ed3e4e3816219d3509d711636c38542bf3f40b1. */ + for(i=0; i<ArraySize(pCsr->aPage); i++){ + statClearPage(&pCsr->aPage[i]); + sqlite3_free(pCsr->aPage[i].aPg); + pCsr->aPage[i].aPg = 0; + } + sqlite3_reset(pCsr->pStmt); + pCsr->iPage = 0; + sqlite3_free(pCsr->zPath); + pCsr->zPath = 0; + pCsr->isEof = 0; +} + +/* Resize the space-used counters inside of the cursor */ +static void statResetCounts(StatCursor *pCsr){ + pCsr->nCell = 0; + pCsr->nMxPayload = 0; + pCsr->nUnused = 0; + pCsr->nPayload = 0; + pCsr->szPage = 0; + pCsr->nPage = 0; +} + +/* +** Close a DBSTAT cursor. +*/ +static int statClose(sqlite3_vtab_cursor *pCursor){ + StatCursor *pCsr = (StatCursor *)pCursor; + statResetCsr(pCsr); + sqlite3_finalize(pCsr->pStmt); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** For a single cell on a btree page, compute the number of bytes of +** content (payload) stored on that page. That is to say, compute the +** number of bytes of content not found on overflow pages. +*/ +static int getLocalPayload( + int nUsable, /* Usable bytes per page */ + u8 flags, /* Page flags */ + int nTotal /* Total record (payload) size */ +){ + int nLocal; + int nMinLocal; + int nMaxLocal; + + if( flags==0x0D ){ /* Table leaf node */ + nMinLocal = (nUsable - 12) * 32 / 255 - 23; + nMaxLocal = nUsable - 35; + }else{ /* Index interior and leaf nodes */ + nMinLocal = (nUsable - 12) * 32 / 255 - 23; + nMaxLocal = (nUsable - 12) * 64 / 255 - 23; + } + + nLocal = nMinLocal + (nTotal - nMinLocal) % (nUsable - 4); + if( nLocal>nMaxLocal ) nLocal = nMinLocal; + return nLocal; +} + +/* Populate the StatPage object with information about the all +** cells found on the page currently under analysis. +*/ +static int statDecodePage(Btree *pBt, StatPage *p){ + int nUnused; + int iOff; + int nHdr; + int isLeaf; + int szPage; + + u8 *aData = p->aPg; + u8 *aHdr = &aData[p->iPgno==1 ? 100 : 0]; + + p->flags = aHdr[0]; + if( p->flags==0x0A || p->flags==0x0D ){ + isLeaf = 1; + nHdr = 8; + }else if( p->flags==0x05 || p->flags==0x02 ){ + isLeaf = 0; + nHdr = 12; + }else{ + goto statPageIsCorrupt; + } + if( p->iPgno==1 ) nHdr += 100; + p->nCell = get2byte(&aHdr[3]); + p->nMxPayload = 0; + szPage = sqlite3BtreeGetPageSize(pBt); + + nUnused = get2byte(&aHdr[5]) - nHdr - 2*p->nCell; + nUnused += (int)aHdr[7]; + iOff = get2byte(&aHdr[1]); + while( iOff ){ + int iNext; + if( iOff>=szPage ) goto statPageIsCorrupt; + nUnused += get2byte(&aData[iOff+2]); + iNext = get2byte(&aData[iOff]); + if( iNext<iOff+4 && iNext>0 ) goto statPageIsCorrupt; + iOff = iNext; + } + p->nUnused = nUnused; + p->iRightChildPg = isLeaf ? 0 : sqlite3Get4byte(&aHdr[8]); + + if( p->nCell ){ + int i; /* Used to iterate through cells */ + int nUsable; /* Usable bytes per page */ + + sqlite3BtreeEnter(pBt); + nUsable = szPage - sqlite3BtreeGetReserveNoMutex(pBt); + sqlite3BtreeLeave(pBt); + p->aCell = sqlite3_malloc64((p->nCell+1) * sizeof(StatCell)); + if( p->aCell==0 ) return SQLITE_NOMEM_BKPT; + memset(p->aCell, 0, (p->nCell+1) * sizeof(StatCell)); + + for(i=0; i<p->nCell; i++){ + StatCell *pCell = &p->aCell[i]; + + iOff = get2byte(&aData[nHdr+i*2]); + if( iOff<nHdr || iOff>=szPage ) goto statPageIsCorrupt; + if( !isLeaf ){ + pCell->iChildPg = sqlite3Get4byte(&aData[iOff]); + iOff += 4; + } + if( p->flags==0x05 ){ + /* A table interior node. nPayload==0. */ + }else{ + u32 nPayload; /* Bytes of payload total (local+overflow) */ + int nLocal; /* Bytes of payload stored locally */ + iOff += getVarint32(&aData[iOff], nPayload); + if( p->flags==0x0D ){ + u64 dummy; + iOff += sqlite3GetVarint(&aData[iOff], &dummy); + } + if( nPayload>(u32)p->nMxPayload ) p->nMxPayload = nPayload; + nLocal = getLocalPayload(nUsable, p->flags, nPayload); + if( nLocal<0 ) goto statPageIsCorrupt; + pCell->nLocal = nLocal; + assert( nPayload>=(u32)nLocal ); + assert( nLocal<=(nUsable-35) ); + if( nPayload>(u32)nLocal ){ + int j; + int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4); + if( iOff+nLocal+4>nUsable || nPayload>0x7fffffff ){ + goto statPageIsCorrupt; + } + pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4); + pCell->nOvfl = nOvfl; + pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl); + if( pCell->aOvfl==0 ) return SQLITE_NOMEM_BKPT; + pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]); + for(j=1; j<nOvfl; j++){ + int rc; + u32 iPrev = pCell->aOvfl[j-1]; + DbPage *pPg = 0; + rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPrev, &pPg, 0); + if( rc!=SQLITE_OK ){ + assert( pPg==0 ); + return rc; + } + pCell->aOvfl[j] = sqlite3Get4byte(sqlite3PagerGetData(pPg)); + sqlite3PagerUnref(pPg); + } + } + } + } + } + + return SQLITE_OK; + +statPageIsCorrupt: + p->flags = 0; + statClearCells(p); + return SQLITE_OK; +} + +/* +** Populate the pCsr->iOffset and pCsr->szPage member variables. Based on +** the current value of pCsr->iPageno. +*/ +static void statSizeAndOffset(StatCursor *pCsr){ + StatTable *pTab = (StatTable *)((sqlite3_vtab_cursor *)pCsr)->pVtab; + Btree *pBt = pTab->db->aDb[pTab->iDb].pBt; + Pager *pPager = sqlite3BtreePager(pBt); + sqlite3_file *fd; + sqlite3_int64 x[2]; + + /* If connected to a ZIPVFS backend, find the page size and + ** offset from ZIPVFS. + */ + fd = sqlite3PagerFile(pPager); + x[0] = pCsr->iPageno; + if( sqlite3OsFileControl(fd, 230440, &x)==SQLITE_OK ){ + pCsr->iOffset = x[0]; + pCsr->szPage += x[1]; + }else{ + /* Not ZIPVFS: The default page size and offset */ + pCsr->szPage += sqlite3BtreeGetPageSize(pBt); + pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1); + } +} + +/* +** Load a copy of the page data for page iPg into the buffer belonging +** to page object pPg. Allocate the buffer if necessary. Return SQLITE_OK +** if successful, or an SQLite error code otherwise. +*/ +static int statGetPage( + Btree *pBt, /* Load page from this b-tree */ + u32 iPg, /* Page number to load */ + StatPage *pPg /* Load page into this object */ +){ + int pgsz = sqlite3BtreeGetPageSize(pBt); + DbPage *pDbPage = 0; + int rc; + + if( pPg->aPg==0 ){ + pPg->aPg = (u8*)sqlite3_malloc(pgsz + DBSTAT_PAGE_PADDING_BYTES); + if( pPg->aPg==0 ){ + return SQLITE_NOMEM_BKPT; + } + memset(&pPg->aPg[pgsz], 0, DBSTAT_PAGE_PADDING_BYTES); + } + + rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPg, &pDbPage, 0); + if( rc==SQLITE_OK ){ + const u8 *a = sqlite3PagerGetData(pDbPage); + memcpy(pPg->aPg, a, pgsz); + sqlite3PagerUnref(pDbPage); + } + + return rc; +} + +/* +** Move a DBSTAT cursor to the next entry. Normally, the next +** entry will be the next page, but in aggregated mode (pCsr->isAgg!=0), +** the next entry is the next btree. +*/ +static int statNext(sqlite3_vtab_cursor *pCursor){ + int rc; + int nPayload; + char *z; + StatCursor *pCsr = (StatCursor *)pCursor; + StatTable *pTab = (StatTable *)pCursor->pVtab; + Btree *pBt = pTab->db->aDb[pCsr->iDb].pBt; + Pager *pPager = sqlite3BtreePager(pBt); + + sqlite3_free(pCsr->zPath); + pCsr->zPath = 0; + +statNextRestart: + if( pCsr->iPage<0 ){ + /* Start measuring space on the next btree */ + statResetCounts(pCsr); + rc = sqlite3_step(pCsr->pStmt); + if( rc==SQLITE_ROW ){ + int nPage; + u32 iRoot = (u32)sqlite3_column_int64(pCsr->pStmt, 1); + sqlite3PagerPagecount(pPager, &nPage); + if( nPage==0 ){ + pCsr->isEof = 1; + return sqlite3_reset(pCsr->pStmt); + } + rc = statGetPage(pBt, iRoot, &pCsr->aPage[0]); + pCsr->aPage[0].iPgno = iRoot; + pCsr->aPage[0].iCell = 0; + if( !pCsr->isAgg ){ + pCsr->aPage[0].zPath = z = sqlite3_mprintf("/"); + if( z==0 ) rc = SQLITE_NOMEM_BKPT; + } + pCsr->iPage = 0; + pCsr->nPage = 1; + }else{ + pCsr->isEof = 1; + return sqlite3_reset(pCsr->pStmt); + } + }else{ + /* Continue analyzing the btree previously started */ + StatPage *p = &pCsr->aPage[pCsr->iPage]; + if( !pCsr->isAgg ) statResetCounts(pCsr); + while( p->iCell<p->nCell ){ + StatCell *pCell = &p->aCell[p->iCell]; + while( pCell->iOvfl<pCell->nOvfl ){ + int nUsable, iOvfl; + sqlite3BtreeEnter(pBt); + nUsable = sqlite3BtreeGetPageSize(pBt) - + sqlite3BtreeGetReserveNoMutex(pBt); + sqlite3BtreeLeave(pBt); + pCsr->nPage++; + statSizeAndOffset(pCsr); + if( pCell->iOvfl<pCell->nOvfl-1 ){ + pCsr->nPayload += nUsable - 4; + }else{ + pCsr->nPayload += pCell->nLastOvfl; + pCsr->nUnused += nUsable - 4 - pCell->nLastOvfl; + } + iOvfl = pCell->iOvfl; + pCell->iOvfl++; + if( !pCsr->isAgg ){ + pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0); + pCsr->iPageno = pCell->aOvfl[iOvfl]; + pCsr->zPagetype = "overflow"; + pCsr->zPath = z = sqlite3_mprintf( + "%s%.3x+%.6x", p->zPath, p->iCell, iOvfl + ); + return z==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK; + } + } + if( p->iRightChildPg ) break; + p->iCell++; + } + + if( !p->iRightChildPg || p->iCell>p->nCell ){ + statClearPage(p); + pCsr->iPage--; + if( pCsr->isAgg && pCsr->iPage<0 ){ + /* label-statNext-done: When computing aggregate space usage over + ** an entire btree, this is the exit point from this function */ + return SQLITE_OK; + } + goto statNextRestart; /* Tail recursion */ + } + pCsr->iPage++; + if( pCsr->iPage>=ArraySize(pCsr->aPage) ){ + statResetCsr(pCsr); + return SQLITE_CORRUPT_BKPT; + } + assert( p==&pCsr->aPage[pCsr->iPage-1] ); + + if( p->iCell==p->nCell ){ + p[1].iPgno = p->iRightChildPg; + }else{ + p[1].iPgno = p->aCell[p->iCell].iChildPg; + } + rc = statGetPage(pBt, p[1].iPgno, &p[1]); + pCsr->nPage++; + p[1].iCell = 0; + if( !pCsr->isAgg ){ + p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell); + if( z==0 ) rc = SQLITE_NOMEM_BKPT; + } + p->iCell++; + } + + + /* Populate the StatCursor fields with the values to be returned + ** by the xColumn() and xRowid() methods. + */ + if( rc==SQLITE_OK ){ + int i; + StatPage *p = &pCsr->aPage[pCsr->iPage]; + pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0); + pCsr->iPageno = p->iPgno; + + rc = statDecodePage(pBt, p); + if( rc==SQLITE_OK ){ + statSizeAndOffset(pCsr); + + switch( p->flags ){ + case 0x05: /* table internal */ + case 0x02: /* index internal */ + pCsr->zPagetype = "internal"; + break; + case 0x0D: /* table leaf */ + case 0x0A: /* index leaf */ + pCsr->zPagetype = "leaf"; + break; + default: + pCsr->zPagetype = "corrupted"; + break; + } + pCsr->nCell += p->nCell; + pCsr->nUnused += p->nUnused; + if( p->nMxPayload>pCsr->nMxPayload ) pCsr->nMxPayload = p->nMxPayload; + if( !pCsr->isAgg ){ + pCsr->zPath = z = sqlite3_mprintf("%s", p->zPath); + if( z==0 ) rc = SQLITE_NOMEM_BKPT; + } + nPayload = 0; + for(i=0; i<p->nCell; i++){ + nPayload += p->aCell[i].nLocal; + } + pCsr->nPayload += nPayload; + + /* If computing aggregate space usage by btree, continue with the + ** next page. The loop will exit via the return at label-statNext-done + */ + if( pCsr->isAgg ) goto statNextRestart; + } + } + + return rc; +} + +static int statEof(sqlite3_vtab_cursor *pCursor){ + StatCursor *pCsr = (StatCursor *)pCursor; + return pCsr->isEof; +} + +/* Initialize a cursor according to the query plan idxNum using the +** arguments in argv[0]. See statBestIndex() for a description of the +** meaning of the bits in idxNum. +*/ +static int statFilter( + sqlite3_vtab_cursor *pCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + StatCursor *pCsr = (StatCursor *)pCursor; + StatTable *pTab = (StatTable*)(pCursor->pVtab); + sqlite3_str *pSql; /* Query of btrees to analyze */ + char *zSql; /* String value of pSql */ + int iArg = 0; /* Count of argv[] parameters used so far */ + int rc = SQLITE_OK; /* Result of this operation */ + const char *zName = 0; /* Only provide analysis of this table */ + (void)argc; + (void)idxStr; + + statResetCsr(pCsr); + sqlite3_finalize(pCsr->pStmt); + pCsr->pStmt = 0; + if( idxNum & 0x01 ){ + /* schema=? constraint is present. Get its value */ + const char *zDbase = (const char*)sqlite3_value_text(argv[iArg++]); + pCsr->iDb = sqlite3FindDbName(pTab->db, zDbase); + if( pCsr->iDb<0 ){ + pCsr->iDb = 0; + pCsr->isEof = 1; + return SQLITE_OK; + } + }else{ + pCsr->iDb = pTab->iDb; + } + if( idxNum & 0x02 ){ + /* name=? constraint is present */ + zName = (const char*)sqlite3_value_text(argv[iArg++]); + } + if( idxNum & 0x04 ){ + /* aggregate=? constraint is present */ + pCsr->isAgg = sqlite3_value_double(argv[iArg++])!=0.0; + }else{ + pCsr->isAgg = 0; + } + pSql = sqlite3_str_new(pTab->db); + sqlite3_str_appendf(pSql, + "SELECT * FROM (" + "SELECT 'sqlite_schema' AS name,1 AS rootpage,'table' AS type" + " UNION ALL " + "SELECT name,rootpage,type" + " FROM \"%w\".sqlite_schema WHERE rootpage!=0)", + pTab->db->aDb[pCsr->iDb].zDbSName); + if( zName ){ + sqlite3_str_appendf(pSql, "WHERE name=%Q", zName); + } + if( idxNum & 0x08 ){ + sqlite3_str_appendf(pSql, " ORDER BY name"); + } + zSql = sqlite3_str_finish(pSql); + if( zSql==0 ){ + return SQLITE_NOMEM_BKPT; + }else{ + rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0); + sqlite3_free(zSql); + } + + if( rc==SQLITE_OK ){ + pCsr->iPage = -1; + rc = statNext(pCursor); + } + return rc; +} + +static int statColumn( + sqlite3_vtab_cursor *pCursor, + sqlite3_context *ctx, + int i +){ + StatCursor *pCsr = (StatCursor *)pCursor; + switch( i ){ + case 0: /* name */ + sqlite3_result_text(ctx, pCsr->zName, -1, SQLITE_TRANSIENT); + break; + case 1: /* path */ + if( !pCsr->isAgg ){ + sqlite3_result_text(ctx, pCsr->zPath, -1, SQLITE_TRANSIENT); + } + break; + case 2: /* pageno */ + if( pCsr->isAgg ){ + sqlite3_result_int64(ctx, pCsr->nPage); + }else{ + sqlite3_result_int64(ctx, pCsr->iPageno); + } + break; + case 3: /* pagetype */ + if( !pCsr->isAgg ){ + sqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC); + } + break; + case 4: /* ncell */ + sqlite3_result_int64(ctx, pCsr->nCell); + break; + case 5: /* payload */ + sqlite3_result_int64(ctx, pCsr->nPayload); + break; + case 6: /* unused */ + sqlite3_result_int64(ctx, pCsr->nUnused); + break; + case 7: /* mx_payload */ + sqlite3_result_int64(ctx, pCsr->nMxPayload); + break; + case 8: /* pgoffset */ + if( !pCsr->isAgg ){ + sqlite3_result_int64(ctx, pCsr->iOffset); + } + break; + case 9: /* pgsize */ + sqlite3_result_int64(ctx, pCsr->szPage); + break; + case 10: { /* schema */ + sqlite3 *db = sqlite3_context_db_handle(ctx); + int iDb = pCsr->iDb; + sqlite3_result_text(ctx, db->aDb[iDb].zDbSName, -1, SQLITE_STATIC); + break; + } + default: { /* aggregate */ + sqlite3_result_int(ctx, pCsr->isAgg); + break; + } + } + return SQLITE_OK; +} + +static int statRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ + StatCursor *pCsr = (StatCursor *)pCursor; + *pRowid = pCsr->iPageno; + return SQLITE_OK; +} + +/* +** Invoke this routine to register the "dbstat" virtual table module +*/ +SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3 *db){ + static sqlite3_module dbstat_module = { + 0, /* iVersion */ + statConnect, /* xCreate */ + statConnect, /* xConnect */ + statBestIndex, /* xBestIndex */ + statDisconnect, /* xDisconnect */ + statDisconnect, /* xDestroy */ + statOpen, /* xOpen - open a cursor */ + statClose, /* xClose - close a cursor */ + statFilter, /* xFilter - configure scan constraints */ + statNext, /* xNext - advance a cursor */ + statEof, /* xEof - check for end of scan */ + statColumn, /* xColumn - read data */ + statRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ + }; + return sqlite3_create_module(db, "dbstat", &dbstat_module, 0); +} +#elif defined(SQLITE_ENABLE_DBSTAT_VTAB) +SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3 *db){ return SQLITE_OK; } +#endif /* SQLITE_ENABLE_DBSTAT_VTAB */ + +/************** End of dbstat.c **********************************************/ +/************** Begin file dbpage.c ******************************************/ +/* +** 2017-10-11 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains an implementation of the "sqlite_dbpage" virtual table. +** +** The sqlite_dbpage virtual table is used to read or write whole raw +** pages of the database file. The pager interface is used so that +** uncommitted changes and changes recorded in the WAL file are correctly +** retrieved. +** +** Usage example: +** +** SELECT data FROM sqlite_dbpage('aux1') WHERE pgno=123; +** +** This is an eponymous virtual table so it does not need to be created before +** use. The optional argument to the sqlite_dbpage() table name is the +** schema for the database file that is to be read. The default schema is +** "main". +** +** The data field of sqlite_dbpage table can be updated. The new +** value must be a BLOB which is the correct page size, otherwise the +** update fails. Rows may not be deleted or inserted. +*/ + +/* #include "sqliteInt.h" ** Requires access to internal data structures ** */ +#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \ + && !defined(SQLITE_OMIT_VIRTUALTABLE) + +typedef struct DbpageTable DbpageTable; +typedef struct DbpageCursor DbpageCursor; + +struct DbpageCursor { + sqlite3_vtab_cursor base; /* Base class. Must be first */ + int pgno; /* Current page number */ + int mxPgno; /* Last page to visit on this scan */ + Pager *pPager; /* Pager being read/written */ + DbPage *pPage1; /* Page 1 of the database */ + int iDb; /* Index of database to analyze */ + int szPage; /* Size of each page in bytes */ +}; + +struct DbpageTable { + sqlite3_vtab base; /* Base class. Must be first */ + sqlite3 *db; /* The database */ +}; + +/* Columns */ +#define DBPAGE_COLUMN_PGNO 0 +#define DBPAGE_COLUMN_DATA 1 +#define DBPAGE_COLUMN_SCHEMA 2 + + + +/* +** Connect to or create a dbpagevfs virtual table. +*/ +static int dbpageConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + DbpageTable *pTab = 0; + int rc = SQLITE_OK; + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; + + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + sqlite3_vtab_config(db, SQLITE_VTAB_USES_ALL_SCHEMAS); + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(pgno INTEGER PRIMARY KEY, data BLOB, schema HIDDEN)"); + if( rc==SQLITE_OK ){ + pTab = (DbpageTable *)sqlite3_malloc64(sizeof(DbpageTable)); + if( pTab==0 ) rc = SQLITE_NOMEM_BKPT; + } + + assert( rc==SQLITE_OK || pTab==0 ); + if( rc==SQLITE_OK ){ + memset(pTab, 0, sizeof(DbpageTable)); + pTab->db = db; + } + + *ppVtab = (sqlite3_vtab*)pTab; + return rc; +} + +/* +** Disconnect from or destroy a dbpagevfs virtual table. +*/ +static int dbpageDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** idxNum: +** +** 0 schema=main, full table scan +** 1 schema=main, pgno=?1 +** 2 schema=?1, full table scan +** 3 schema=?1, pgno=?2 +*/ +static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + int i; + int iPlan = 0; + (void)tab; + + /* If there is a schema= constraint, it must be honored. Report a + ** ridiculously large estimated cost if the schema= constraint is + ** unavailable + */ + for(i=0; i<pIdxInfo->nConstraint; i++){ + struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; + if( p->iColumn!=DBPAGE_COLUMN_SCHEMA ) continue; + if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( !p->usable ){ + /* No solution. */ + return SQLITE_CONSTRAINT; + } + iPlan = 2; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + break; + } + + /* If we reach this point, it means that either there is no schema= + ** constraint (in which case we use the "main" schema) or else the + ** schema constraint was accepted. Lower the estimated cost accordingly + */ + pIdxInfo->estimatedCost = 1.0e6; + + /* Check for constraints against pgno */ + for(i=0; i<pIdxInfo->nConstraint; i++){ + struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; + if( p->usable && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + pIdxInfo->estimatedRows = 1; + pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE; + pIdxInfo->estimatedCost = 1.0; + pIdxInfo->aConstraintUsage[i].argvIndex = iPlan ? 2 : 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + iPlan |= 1; + break; + } + } + pIdxInfo->idxNum = iPlan; + + if( pIdxInfo->nOrderBy>=1 + && pIdxInfo->aOrderBy[0].iColumn<=0 + && pIdxInfo->aOrderBy[0].desc==0 + ){ + pIdxInfo->orderByConsumed = 1; + } + return SQLITE_OK; +} + +/* +** Open a new dbpagevfs cursor. +*/ +static int dbpageOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + DbpageCursor *pCsr; + + pCsr = (DbpageCursor *)sqlite3_malloc64(sizeof(DbpageCursor)); + if( pCsr==0 ){ + return SQLITE_NOMEM_BKPT; + }else{ + memset(pCsr, 0, sizeof(DbpageCursor)); + pCsr->base.pVtab = pVTab; + pCsr->pgno = -1; + } + + *ppCursor = (sqlite3_vtab_cursor *)pCsr; + return SQLITE_OK; +} + +/* +** Close a dbpagevfs cursor. +*/ +static int dbpageClose(sqlite3_vtab_cursor *pCursor){ + DbpageCursor *pCsr = (DbpageCursor *)pCursor; + if( pCsr->pPage1 ) sqlite3PagerUnrefPageOne(pCsr->pPage1); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** Move a dbpagevfs cursor to the next entry in the file. +*/ +static int dbpageNext(sqlite3_vtab_cursor *pCursor){ + int rc = SQLITE_OK; + DbpageCursor *pCsr = (DbpageCursor *)pCursor; + pCsr->pgno++; + return rc; +} + +static int dbpageEof(sqlite3_vtab_cursor *pCursor){ + DbpageCursor *pCsr = (DbpageCursor *)pCursor; + return pCsr->pgno > pCsr->mxPgno; +} + +/* +** idxNum: +** +** 0 schema=main, full table scan +** 1 schema=main, pgno=?1 +** 2 schema=?1, full table scan +** 3 schema=?1, pgno=?2 +** +** idxStr is not used +*/ +static int dbpageFilter( + sqlite3_vtab_cursor *pCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + DbpageCursor *pCsr = (DbpageCursor *)pCursor; + DbpageTable *pTab = (DbpageTable *)pCursor->pVtab; + int rc; + sqlite3 *db = pTab->db; + Btree *pBt; + + (void)idxStr; + + /* Default setting is no rows of result */ + pCsr->pgno = 1; + pCsr->mxPgno = 0; + + if( idxNum & 2 ){ + const char *zSchema; + assert( argc>=1 ); + zSchema = (const char*)sqlite3_value_text(argv[0]); + pCsr->iDb = sqlite3FindDbName(db, zSchema); + if( pCsr->iDb<0 ) return SQLITE_OK; + }else{ + pCsr->iDb = 0; + } + pBt = db->aDb[pCsr->iDb].pBt; + if( NEVER(pBt==0) ) return SQLITE_OK; + pCsr->pPager = sqlite3BtreePager(pBt); + pCsr->szPage = sqlite3BtreeGetPageSize(pBt); + pCsr->mxPgno = sqlite3BtreeLastPage(pBt); + if( idxNum & 1 ){ + assert( argc>(idxNum>>1) ); + pCsr->pgno = sqlite3_value_int(argv[idxNum>>1]); + if( pCsr->pgno<1 || pCsr->pgno>pCsr->mxPgno ){ + pCsr->pgno = 1; + pCsr->mxPgno = 0; + }else{ + pCsr->mxPgno = pCsr->pgno; + } + }else{ + assert( pCsr->pgno==1 ); + } + if( pCsr->pPage1 ) sqlite3PagerUnrefPageOne(pCsr->pPage1); + rc = sqlite3PagerGet(pCsr->pPager, 1, &pCsr->pPage1, 0); + return rc; +} + +static int dbpageColumn( + sqlite3_vtab_cursor *pCursor, + sqlite3_context *ctx, + int i +){ + DbpageCursor *pCsr = (DbpageCursor *)pCursor; + int rc = SQLITE_OK; + switch( i ){ + case 0: { /* pgno */ + sqlite3_result_int(ctx, pCsr->pgno); + break; + } + case 1: { /* data */ + DbPage *pDbPage = 0; + if( pCsr->pgno==((PENDING_BYTE/pCsr->szPage)+1) ){ + /* The pending byte page. Assume it is zeroed out. Attempting to + ** request this page from the page is an SQLITE_CORRUPT error. */ + sqlite3_result_zeroblob(ctx, pCsr->szPage); + }else{ + rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); + if( rc==SQLITE_OK ){ + sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage, + SQLITE_TRANSIENT); + } + sqlite3PagerUnref(pDbPage); + } + break; + } + default: { /* schema */ + sqlite3 *db = sqlite3_context_db_handle(ctx); + sqlite3_result_text(ctx, db->aDb[pCsr->iDb].zDbSName, -1, SQLITE_STATIC); + break; + } + } + return rc; +} + +static int dbpageRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ + DbpageCursor *pCsr = (DbpageCursor *)pCursor; + *pRowid = pCsr->pgno; + return SQLITE_OK; +} + +static int dbpageUpdate( + sqlite3_vtab *pVtab, + int argc, + sqlite3_value **argv, + sqlite_int64 *pRowid +){ + DbpageTable *pTab = (DbpageTable *)pVtab; + Pgno pgno; + DbPage *pDbPage = 0; + int rc = SQLITE_OK; + char *zErr = 0; + const char *zSchema; + int iDb; + Btree *pBt; + Pager *pPager; + int szPage; + + (void)pRowid; + if( pTab->db->flags & SQLITE_Defensive ){ + zErr = "read-only"; + goto update_fail; + } + if( argc==1 ){ + zErr = "cannot delete"; + goto update_fail; + } + pgno = sqlite3_value_int(argv[0]); + if( sqlite3_value_type(argv[0])==SQLITE_NULL + || (Pgno)sqlite3_value_int(argv[1])!=pgno + ){ + zErr = "cannot insert"; + goto update_fail; + } + zSchema = (const char*)sqlite3_value_text(argv[4]); + iDb = ALWAYS(zSchema) ? sqlite3FindDbName(pTab->db, zSchema) : -1; + if( NEVER(iDb<0) ){ + zErr = "no such schema"; + goto update_fail; + } + pBt = pTab->db->aDb[iDb].pBt; + if( NEVER(pgno<1) || NEVER(pBt==0) || NEVER(pgno>sqlite3BtreeLastPage(pBt)) ){ + zErr = "bad page number"; + goto update_fail; + } + szPage = sqlite3BtreeGetPageSize(pBt); + if( sqlite3_value_type(argv[3])!=SQLITE_BLOB + || sqlite3_value_bytes(argv[3])!=szPage + ){ + zErr = "bad page value"; + goto update_fail; + } + pPager = sqlite3BtreePager(pBt); + rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pDbPage, 0); + if( rc==SQLITE_OK ){ + const void *pData = sqlite3_value_blob(argv[3]); + assert( pData!=0 || pTab->db->mallocFailed ); + if( pData + && (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK + ){ + memcpy(sqlite3PagerGetData(pDbPage), pData, szPage); + } + } + sqlite3PagerUnref(pDbPage); + return rc; + +update_fail: + sqlite3_free(pVtab->zErrMsg); + pVtab->zErrMsg = sqlite3_mprintf("%s", zErr); + return SQLITE_ERROR; +} + +/* Since we do not know in advance which database files will be +** written by the sqlite_dbpage virtual table, start a write transaction +** on them all. +*/ +static int dbpageBegin(sqlite3_vtab *pVtab){ + DbpageTable *pTab = (DbpageTable *)pVtab; + sqlite3 *db = pTab->db; + int i; + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ) (void)sqlite3BtreeBeginTrans(pBt, 1, 0); + } + return SQLITE_OK; +} + + +/* +** Invoke this routine to register the "dbpage" virtual table module +*/ +SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3 *db){ + static sqlite3_module dbpage_module = { + 0, /* iVersion */ + dbpageConnect, /* xCreate */ + dbpageConnect, /* xConnect */ + dbpageBestIndex, /* xBestIndex */ + dbpageDisconnect, /* xDisconnect */ + dbpageDisconnect, /* xDestroy */ + dbpageOpen, /* xOpen - open a cursor */ + dbpageClose, /* xClose - close a cursor */ + dbpageFilter, /* xFilter - configure scan constraints */ + dbpageNext, /* xNext - advance a cursor */ + dbpageEof, /* xEof - check for end of scan */ + dbpageColumn, /* xColumn - read data */ + dbpageRowid, /* xRowid - read data */ + dbpageUpdate, /* xUpdate */ + dbpageBegin, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ + }; + return sqlite3_create_module(db, "sqlite_dbpage", &dbpage_module, 0); +} +#elif defined(SQLITE_ENABLE_DBPAGE_VTAB) +SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; } +#endif /* SQLITE_ENABLE_DBSTAT_VTAB */ + +/************** End of dbpage.c **********************************************/ +/************** Begin file sqlite3session.c **********************************/ + +#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK) +/* #include "sqlite3session.h" */ +/* #include <assert.h> */ +/* #include <string.h> */ + +#ifndef SQLITE_AMALGAMATION +/* # include "sqliteInt.h" */ +/* # include "vdbeInt.h" */ +#endif + +typedef struct SessionTable SessionTable; +typedef struct SessionChange SessionChange; +typedef struct SessionBuffer SessionBuffer; +typedef struct SessionInput SessionInput; + +/* +** Minimum chunk size used by streaming versions of functions. +*/ +#ifndef SESSIONS_STRM_CHUNK_SIZE +# ifdef SQLITE_TEST +# define SESSIONS_STRM_CHUNK_SIZE 64 +# else +# define SESSIONS_STRM_CHUNK_SIZE 1024 +# endif +#endif + +#define SESSIONS_ROWID "_rowid_" + +static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE; + +typedef struct SessionHook SessionHook; +struct SessionHook { + void *pCtx; + int (*xOld)(void*,int,sqlite3_value**); + int (*xNew)(void*,int,sqlite3_value**); + int (*xCount)(void*); + int (*xDepth)(void*); +}; + +/* +** Session handle structure. +*/ +struct sqlite3_session { + sqlite3 *db; /* Database handle session is attached to */ + char *zDb; /* Name of database session is attached to */ + int bEnableSize; /* True if changeset_size() enabled */ + int bEnable; /* True if currently recording */ + int bIndirect; /* True if all changes are indirect */ + int bAutoAttach; /* True to auto-attach tables */ + int bImplicitPK; /* True to handle tables with implicit PK */ + int rc; /* Non-zero if an error has occurred */ + void *pFilterCtx; /* First argument to pass to xTableFilter */ + int (*xTableFilter)(void *pCtx, const char *zTab); + i64 nMalloc; /* Number of bytes of data allocated */ + i64 nMaxChangesetSize; + sqlite3_value *pZeroBlob; /* Value containing X'' */ + sqlite3_session *pNext; /* Next session object on same db. */ + SessionTable *pTable; /* List of attached tables */ + SessionHook hook; /* APIs to grab new and old data with */ +}; + +/* +** Instances of this structure are used to build strings or binary records. +*/ +struct SessionBuffer { + u8 *aBuf; /* Pointer to changeset buffer */ + int nBuf; /* Size of buffer aBuf */ + int nAlloc; /* Size of allocation containing aBuf */ +}; + +/* +** An object of this type is used internally as an abstraction for +** input data. Input data may be supplied either as a single large buffer +** (e.g. sqlite3changeset_start()) or using a stream function (e.g. +** sqlite3changeset_start_strm()). +*/ +struct SessionInput { + int bNoDiscard; /* If true, do not discard in InputBuffer() */ + int iCurrent; /* Offset in aData[] of current change */ + int iNext; /* Offset in aData[] of next change */ + u8 *aData; /* Pointer to buffer containing changeset */ + int nData; /* Number of bytes in aData */ + + SessionBuffer buf; /* Current read buffer */ + int (*xInput)(void*, void*, int*); /* Input stream call (or NULL) */ + void *pIn; /* First argument to xInput */ + int bEof; /* Set to true after xInput finished */ +}; + +/* +** Structure for changeset iterators. +*/ +struct sqlite3_changeset_iter { + SessionInput in; /* Input buffer or stream */ + SessionBuffer tblhdr; /* Buffer to hold apValue/zTab/abPK/ */ + int bPatchset; /* True if this is a patchset */ + int bInvert; /* True to invert changeset */ + int bSkipEmpty; /* Skip noop UPDATE changes */ + int rc; /* Iterator error code */ + sqlite3_stmt *pConflict; /* Points to conflicting row, if any */ + char *zTab; /* Current table */ + int nCol; /* Number of columns in zTab */ + int op; /* Current operation */ + int bIndirect; /* True if current change was indirect */ + u8 *abPK; /* Primary key array */ + sqlite3_value **apValue; /* old.* and new.* values */ +}; + +/* +** Each session object maintains a set of the following structures, one +** for each table the session object is monitoring. The structures are +** stored in a linked list starting at sqlite3_session.pTable. +** +** The keys of the SessionTable.aChange[] hash table are all rows that have +** been modified in any way since the session object was attached to the +** table. +** +** The data associated with each hash-table entry is a structure containing +** a subset of the initial values that the modified row contained at the +** start of the session. Or no initial values if the row was inserted. +*/ +struct SessionTable { + SessionTable *pNext; + char *zName; /* Local name of table */ + int nCol; /* Number of columns in table zName */ + int bStat1; /* True if this is sqlite_stat1 */ + int bRowid; /* True if this table uses rowid for PK */ + const char **azCol; /* Column names */ + u8 *abPK; /* Array of primary key flags */ + int nEntry; /* Total number of entries in hash table */ + int nChange; /* Size of apChange[] array */ + SessionChange **apChange; /* Hash table buckets */ +}; + +/* +** RECORD FORMAT: +** +** The following record format is similar to (but not compatible with) that +** used in SQLite database files. This format is used as part of the +** change-set binary format, and so must be architecture independent. +** +** Unlike the SQLite database record format, each field is self-contained - +** there is no separation of header and data. Each field begins with a +** single byte describing its type, as follows: +** +** 0x00: Undefined value. +** 0x01: Integer value. +** 0x02: Real value. +** 0x03: Text value. +** 0x04: Blob value. +** 0x05: SQL NULL value. +** +** Note that the above match the definitions of SQLITE_INTEGER, SQLITE_TEXT +** and so on in sqlite3.h. For undefined and NULL values, the field consists +** only of the single type byte. For other types of values, the type byte +** is followed by: +** +** Text values: +** A varint containing the number of bytes in the value (encoded using +** UTF-8). Followed by a buffer containing the UTF-8 representation +** of the text value. There is no nul terminator. +** +** Blob values: +** A varint containing the number of bytes in the value, followed by +** a buffer containing the value itself. +** +** Integer values: +** An 8-byte big-endian integer value. +** +** Real values: +** An 8-byte big-endian IEEE 754-2008 real value. +** +** Varint values are encoded in the same way as varints in the SQLite +** record format. +** +** CHANGESET FORMAT: +** +** A changeset is a collection of DELETE, UPDATE and INSERT operations on +** one or more tables. Operations on a single table are grouped together, +** but may occur in any order (i.e. deletes, updates and inserts are all +** mixed together). +** +** Each group of changes begins with a table header: +** +** 1 byte: Constant 0x54 (capital 'T') +** Varint: Number of columns in the table. +** nCol bytes: 0x01 for PK columns, 0x00 otherwise. +** N bytes: Unqualified table name (encoded using UTF-8). Nul-terminated. +** +** Followed by one or more changes to the table. +** +** 1 byte: Either SQLITE_INSERT (0x12), UPDATE (0x17) or DELETE (0x09). +** 1 byte: The "indirect-change" flag. +** old.* record: (delete and update only) +** new.* record: (insert and update only) +** +** The "old.*" and "new.*" records, if present, are N field records in the +** format described above under "RECORD FORMAT", where N is the number of +** columns in the table. The i'th field of each record is associated with +** the i'th column of the table, counting from left to right in the order +** in which columns were declared in the CREATE TABLE statement. +** +** The new.* record that is part of each INSERT change contains the values +** that make up the new row. Similarly, the old.* record that is part of each +** DELETE change contains the values that made up the row that was deleted +** from the database. In the changeset format, the records that are part +** of INSERT or DELETE changes never contain any undefined (type byte 0x00) +** fields. +** +** Within the old.* record associated with an UPDATE change, all fields +** associated with table columns that are not PRIMARY KEY columns and are +** not modified by the UPDATE change are set to "undefined". Other fields +** are set to the values that made up the row before the UPDATE that the +** change records took place. Within the new.* record, fields associated +** with table columns modified by the UPDATE change contain the new +** values. Fields associated with table columns that are not modified +** are set to "undefined". +** +** PATCHSET FORMAT: +** +** A patchset is also a collection of changes. It is similar to a changeset, +** but leaves undefined those fields that are not useful if no conflict +** resolution is required when applying the changeset. +** +** Each group of changes begins with a table header: +** +** 1 byte: Constant 0x50 (capital 'P') +** Varint: Number of columns in the table. +** nCol bytes: 0x01 for PK columns, 0x00 otherwise. +** N bytes: Unqualified table name (encoded using UTF-8). Nul-terminated. +** +** Followed by one or more changes to the table. +** +** 1 byte: Either SQLITE_INSERT (0x12), UPDATE (0x17) or DELETE (0x09). +** 1 byte: The "indirect-change" flag. +** single record: (PK fields for DELETE, PK and modified fields for UPDATE, +** full record for INSERT). +** +** As in the changeset format, each field of the single record that is part +** of a patchset change is associated with the correspondingly positioned +** table column, counting from left to right within the CREATE TABLE +** statement. +** +** For a DELETE change, all fields within the record except those associated +** with PRIMARY KEY columns are omitted. The PRIMARY KEY fields contain the +** values identifying the row to delete. +** +** For an UPDATE change, all fields except those associated with PRIMARY KEY +** columns and columns that are modified by the UPDATE are set to "undefined". +** PRIMARY KEY fields contain the values identifying the table row to update, +** and fields associated with modified columns contain the new column values. +** +** The records associated with INSERT changes are in the same format as for +** changesets. It is not possible for a record associated with an INSERT +** change to contain a field set to "undefined". +** +** REBASE BLOB FORMAT: +** +** A rebase blob may be output by sqlite3changeset_apply_v2() and its +** streaming equivalent for use with the sqlite3_rebaser APIs to rebase +** existing changesets. A rebase blob contains one entry for each conflict +** resolved using either the OMIT or REPLACE strategies within the apply_v2() +** call. +** +** The format used for a rebase blob is very similar to that used for +** changesets. All entries related to a single table are grouped together. +** +** Each group of entries begins with a table header in changeset format: +** +** 1 byte: Constant 0x54 (capital 'T') +** Varint: Number of columns in the table. +** nCol bytes: 0x01 for PK columns, 0x00 otherwise. +** N bytes: Unqualified table name (encoded using UTF-8). Nul-terminated. +** +** Followed by one or more entries associated with the table. +** +** 1 byte: Either SQLITE_INSERT (0x12), DELETE (0x09). +** 1 byte: Flag. 0x01 for REPLACE, 0x00 for OMIT. +** record: (in the record format defined above). +** +** In a rebase blob, the first field is set to SQLITE_INSERT if the change +** that caused the conflict was an INSERT or UPDATE, or to SQLITE_DELETE if +** it was a DELETE. The second field is set to 0x01 if the conflict +** resolution strategy was REPLACE, or 0x00 if it was OMIT. +** +** If the change that caused the conflict was a DELETE, then the single +** record is a copy of the old.* record from the original changeset. If it +** was an INSERT, then the single record is a copy of the new.* record. If +** the conflicting change was an UPDATE, then the single record is a copy +** of the new.* record with the PK fields filled in based on the original +** old.* record. +*/ + +/* +** For each row modified during a session, there exists a single instance of +** this structure stored in a SessionTable.aChange[] hash table. +*/ +struct SessionChange { + u8 op; /* One of UPDATE, DELETE, INSERT */ + u8 bIndirect; /* True if this change is "indirect" */ + int nMaxSize; /* Max size of eventual changeset record */ + int nRecord; /* Number of bytes in buffer aRecord[] */ + u8 *aRecord; /* Buffer containing old.* record */ + SessionChange *pNext; /* For hash-table collisions */ +}; + +/* +** Write a varint with value iVal into the buffer at aBuf. Return the +** number of bytes written. +*/ +static int sessionVarintPut(u8 *aBuf, int iVal){ + return putVarint32(aBuf, iVal); +} + +/* +** Return the number of bytes required to store value iVal as a varint. +*/ +static int sessionVarintLen(int iVal){ + return sqlite3VarintLen(iVal); +} + +/* +** Read a varint value from aBuf[] into *piVal. Return the number of +** bytes read. +*/ +static int sessionVarintGet(u8 *aBuf, int *piVal){ + return getVarint32(aBuf, *piVal); +} + +/* Load an unaligned and unsigned 32-bit integer */ +#define SESSION_UINT32(x) (((u32)(x)[0]<<24)|((x)[1]<<16)|((x)[2]<<8)|(x)[3]) + +/* +** Read a 64-bit big-endian integer value from buffer aRec[]. Return +** the value read. +*/ +static sqlite3_int64 sessionGetI64(u8 *aRec){ + u64 x = SESSION_UINT32(aRec); + u32 y = SESSION_UINT32(aRec+4); + x = (x<<32) + y; + return (sqlite3_int64)x; +} + +/* +** Write a 64-bit big-endian integer value to the buffer aBuf[]. +*/ +static void sessionPutI64(u8 *aBuf, sqlite3_int64 i){ + aBuf[0] = (i>>56) & 0xFF; + aBuf[1] = (i>>48) & 0xFF; + aBuf[2] = (i>>40) & 0xFF; + aBuf[3] = (i>>32) & 0xFF; + aBuf[4] = (i>>24) & 0xFF; + aBuf[5] = (i>>16) & 0xFF; + aBuf[6] = (i>> 8) & 0xFF; + aBuf[7] = (i>> 0) & 0xFF; +} + +/* +** This function is used to serialize the contents of value pValue (see +** comment titled "RECORD FORMAT" above). +** +** If it is non-NULL, the serialized form of the value is written to +** buffer aBuf. *pnWrite is set to the number of bytes written before +** returning. Or, if aBuf is NULL, the only thing this function does is +** set *pnWrite. +** +** If no error occurs, SQLITE_OK is returned. Or, if an OOM error occurs +** within a call to sqlite3_value_text() (may fail if the db is utf-16)) +** SQLITE_NOMEM is returned. +*/ +static int sessionSerializeValue( + u8 *aBuf, /* If non-NULL, write serialized value here */ + sqlite3_value *pValue, /* Value to serialize */ + sqlite3_int64 *pnWrite /* IN/OUT: Increment by bytes written */ +){ + int nByte; /* Size of serialized value in bytes */ + + if( pValue ){ + int eType; /* Value type (SQLITE_NULL, TEXT etc.) */ + + eType = sqlite3_value_type(pValue); + if( aBuf ) aBuf[0] = eType; + + switch( eType ){ + case SQLITE_NULL: + nByte = 1; + break; + + case SQLITE_INTEGER: + case SQLITE_FLOAT: + if( aBuf ){ + /* TODO: SQLite does something special to deal with mixed-endian + ** floating point values (e.g. ARM7). This code probably should + ** too. */ + u64 i; + if( eType==SQLITE_INTEGER ){ + i = (u64)sqlite3_value_int64(pValue); + }else{ + double r; + assert( sizeof(double)==8 && sizeof(u64)==8 ); + r = sqlite3_value_double(pValue); + memcpy(&i, &r, 8); + } + sessionPutI64(&aBuf[1], i); + } + nByte = 9; + break; + + default: { + u8 *z; + int n; + int nVarint; + + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + if( eType==SQLITE_TEXT ){ + z = (u8 *)sqlite3_value_text(pValue); + }else{ + z = (u8 *)sqlite3_value_blob(pValue); + } + n = sqlite3_value_bytes(pValue); + if( z==0 && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM; + nVarint = sessionVarintLen(n); + + if( aBuf ){ + sessionVarintPut(&aBuf[1], n); + if( n>0 ) memcpy(&aBuf[nVarint + 1], z, n); + } + + nByte = 1 + nVarint + n; + break; + } + } + }else{ + nByte = 1; + if( aBuf ) aBuf[0] = '\0'; + } + + if( pnWrite ) *pnWrite += nByte; + return SQLITE_OK; +} + +/* +** Allocate and return a pointer to a buffer nByte bytes in size. If +** pSession is not NULL, increase the sqlite3_session.nMalloc variable +** by the number of bytes allocated. +*/ +static void *sessionMalloc64(sqlite3_session *pSession, i64 nByte){ + void *pRet = sqlite3_malloc64(nByte); + if( pSession ) pSession->nMalloc += sqlite3_msize(pRet); + return pRet; +} + +/* +** Free buffer pFree, which must have been allocated by an earlier +** call to sessionMalloc64(). If pSession is not NULL, decrease the +** sqlite3_session.nMalloc counter by the number of bytes freed. +*/ +static void sessionFree(sqlite3_session *pSession, void *pFree){ + if( pSession ) pSession->nMalloc -= sqlite3_msize(pFree); + sqlite3_free(pFree); +} + +/* +** This macro is used to calculate hash key values for data structures. In +** order to use this macro, the entire data structure must be represented +** as a series of unsigned integers. In order to calculate a hash-key value +** for a data structure represented as three such integers, the macro may +** then be used as follows: +** +** int hash_key_value; +** hash_key_value = HASH_APPEND(0, <value 1>); +** hash_key_value = HASH_APPEND(hash_key_value, <value 2>); +** hash_key_value = HASH_APPEND(hash_key_value, <value 3>); +** +** In practice, the data structures this macro is used for are the primary +** key values of modified rows. +*/ +#define HASH_APPEND(hash, add) ((hash) << 3) ^ (hash) ^ (unsigned int)(add) + +/* +** Append the hash of the 64-bit integer passed as the second argument to the +** hash-key value passed as the first. Return the new hash-key value. +*/ +static unsigned int sessionHashAppendI64(unsigned int h, i64 i){ + h = HASH_APPEND(h, i & 0xFFFFFFFF); + return HASH_APPEND(h, (i>>32)&0xFFFFFFFF); +} + +/* +** Append the hash of the blob passed via the second and third arguments to +** the hash-key value passed as the first. Return the new hash-key value. +*/ +static unsigned int sessionHashAppendBlob(unsigned int h, int n, const u8 *z){ + int i; + for(i=0; i<n; i++) h = HASH_APPEND(h, z[i]); + return h; +} + +/* +** Append the hash of the data type passed as the second argument to the +** hash-key value passed as the first. Return the new hash-key value. +*/ +static unsigned int sessionHashAppendType(unsigned int h, int eType){ + return HASH_APPEND(h, eType); +} + +/* +** This function may only be called from within a pre-update callback. +** It calculates a hash based on the primary key values of the old.* or +** new.* row currently available and, assuming no error occurs, writes it to +** *piHash before returning. If the primary key contains one or more NULL +** values, *pbNullPK is set to true before returning. +** +** If an error occurs, an SQLite error code is returned and the final values +** of *piHash asn *pbNullPK are undefined. Otherwise, SQLITE_OK is returned +** and the output variables are set as described above. +*/ +static int sessionPreupdateHash( + sqlite3_session *pSession, /* Session object that owns pTab */ + i64 iRowid, + SessionTable *pTab, /* Session table handle */ + int bNew, /* True to hash the new.* PK */ + int *piHash, /* OUT: Hash value */ + int *pbNullPK /* OUT: True if there are NULL values in PK */ +){ + unsigned int h = 0; /* Hash value to return */ + int i; /* Used to iterate through columns */ + + if( pTab->bRowid ){ + assert( pTab->nCol-1==pSession->hook.xCount(pSession->hook.pCtx) ); + h = sessionHashAppendI64(h, iRowid); + }else{ + assert( *pbNullPK==0 ); + assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) ); + for(i=0; i<pTab->nCol; i++){ + if( pTab->abPK[i] ){ + int rc; + int eType; + sqlite3_value *pVal; + + if( bNew ){ + rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal); + }else{ + rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal); + } + if( rc!=SQLITE_OK ) return rc; + + eType = sqlite3_value_type(pVal); + h = sessionHashAppendType(h, eType); + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + i64 iVal; + if( eType==SQLITE_INTEGER ){ + iVal = sqlite3_value_int64(pVal); + }else{ + double rVal = sqlite3_value_double(pVal); + assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); + memcpy(&iVal, &rVal, 8); + } + h = sessionHashAppendI64(h, iVal); + }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + const u8 *z; + int n; + if( eType==SQLITE_TEXT ){ + z = (const u8 *)sqlite3_value_text(pVal); + }else{ + z = (const u8 *)sqlite3_value_blob(pVal); + } + n = sqlite3_value_bytes(pVal); + if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM; + h = sessionHashAppendBlob(h, n, z); + }else{ + assert( eType==SQLITE_NULL ); + assert( pTab->bStat1==0 || i!=1 ); + *pbNullPK = 1; + } + } + } + } + + *piHash = (h % pTab->nChange); + return SQLITE_OK; +} + +/* +** The buffer that the argument points to contains a serialized SQL value. +** Return the number of bytes of space occupied by the value (including +** the type byte). +*/ +static int sessionSerialLen(u8 *a){ + int e = *a; + int n; + if( e==0 || e==0xFF ) return 1; + if( e==SQLITE_NULL ) return 1; + if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9; + return sessionVarintGet(&a[1], &n) + 1 + n; +} + +/* +** Based on the primary key values stored in change aRecord, calculate a +** hash key. Assume the has table has nBucket buckets. The hash keys +** calculated by this function are compatible with those calculated by +** sessionPreupdateHash(). +** +** The bPkOnly argument is non-zero if the record at aRecord[] is from +** a patchset DELETE. In this case the non-PK fields are omitted entirely. +*/ +static unsigned int sessionChangeHash( + SessionTable *pTab, /* Table handle */ + int bPkOnly, /* Record consists of PK fields only */ + u8 *aRecord, /* Change record */ + int nBucket /* Assume this many buckets in hash table */ +){ + unsigned int h = 0; /* Value to return */ + int i; /* Used to iterate through columns */ + u8 *a = aRecord; /* Used to iterate through change record */ + + for(i=0; i<pTab->nCol; i++){ + int eType = *a; + int isPK = pTab->abPK[i]; + if( bPkOnly && isPK==0 ) continue; + + /* It is not possible for eType to be SQLITE_NULL here. The session + ** module does not record changes for rows with NULL values stored in + ** primary key columns. */ + assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT + || eType==SQLITE_TEXT || eType==SQLITE_BLOB + || eType==SQLITE_NULL || eType==0 + ); + assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) ); + + if( isPK ){ + a++; + h = sessionHashAppendType(h, eType); + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + h = sessionHashAppendI64(h, sessionGetI64(a)); + a += 8; + }else{ + int n; + a += sessionVarintGet(a, &n); + h = sessionHashAppendBlob(h, n, a); + a += n; + } + }else{ + a += sessionSerialLen(a); + } + } + return (h % nBucket); +} + +/* +** Arguments aLeft and aRight are pointers to change records for table pTab. +** This function returns true if the two records apply to the same row (i.e. +** have the same values stored in the primary key columns), or false +** otherwise. +*/ +static int sessionChangeEqual( + SessionTable *pTab, /* Table used for PK definition */ + int bLeftPkOnly, /* True if aLeft[] contains PK fields only */ + u8 *aLeft, /* Change record */ + int bRightPkOnly, /* True if aRight[] contains PK fields only */ + u8 *aRight /* Change record */ +){ + u8 *a1 = aLeft; /* Cursor to iterate through aLeft */ + u8 *a2 = aRight; /* Cursor to iterate through aRight */ + int iCol; /* Used to iterate through table columns */ + + for(iCol=0; iCol<pTab->nCol; iCol++){ + if( pTab->abPK[iCol] ){ + int n1 = sessionSerialLen(a1); + int n2 = sessionSerialLen(a2); + + if( n1!=n2 || memcmp(a1, a2, n1) ){ + return 0; + } + a1 += n1; + a2 += n2; + }else{ + if( bLeftPkOnly==0 ) a1 += sessionSerialLen(a1); + if( bRightPkOnly==0 ) a2 += sessionSerialLen(a2); + } + } + + return 1; +} + +/* +** Arguments aLeft and aRight both point to buffers containing change +** records with nCol columns. This function "merges" the two records into +** a single records which is written to the buffer at *paOut. *paOut is +** then set to point to one byte after the last byte written before +** returning. +** +** The merging of records is done as follows: For each column, if the +** aRight record contains a value for the column, copy the value from +** their. Otherwise, if aLeft contains a value, copy it. If neither +** record contains a value for a given column, then neither does the +** output record. +*/ +static void sessionMergeRecord( + u8 **paOut, + int nCol, + u8 *aLeft, + u8 *aRight +){ + u8 *a1 = aLeft; /* Cursor used to iterate through aLeft */ + u8 *a2 = aRight; /* Cursor used to iterate through aRight */ + u8 *aOut = *paOut; /* Output cursor */ + int iCol; /* Used to iterate from 0 to nCol */ + + for(iCol=0; iCol<nCol; iCol++){ + int n1 = sessionSerialLen(a1); + int n2 = sessionSerialLen(a2); + if( *a2 ){ + memcpy(aOut, a2, n2); + aOut += n2; + }else{ + memcpy(aOut, a1, n1); + aOut += n1; + } + a1 += n1; + a2 += n2; + } + + *paOut = aOut; +} + +/* +** This is a helper function used by sessionMergeUpdate(). +** +** When this function is called, both *paOne and *paTwo point to a value +** within a change record. Before it returns, both have been advanced so +** as to point to the next value in the record. +** +** If, when this function is called, *paTwo points to a valid value (i.e. +** *paTwo[0] is not 0x00 - the "no value" placeholder), a copy of the *paTwo +** pointer is returned and *pnVal is set to the number of bytes in the +** serialized value. Otherwise, a copy of *paOne is returned and *pnVal +** set to the number of bytes in the value at *paOne. If *paOne points +** to the "no value" placeholder, *pnVal is set to 1. In other words: +** +** if( *paTwo is valid ) return *paTwo; +** return *paOne; +** +*/ +static u8 *sessionMergeValue( + u8 **paOne, /* IN/OUT: Left-hand buffer pointer */ + u8 **paTwo, /* IN/OUT: Right-hand buffer pointer */ + int *pnVal /* OUT: Bytes in returned value */ +){ + u8 *a1 = *paOne; + u8 *a2 = *paTwo; + u8 *pRet = 0; + int n1; + + assert( a1 ); + if( a2 ){ + int n2 = sessionSerialLen(a2); + if( *a2 ){ + *pnVal = n2; + pRet = a2; + } + *paTwo = &a2[n2]; + } + + n1 = sessionSerialLen(a1); + if( pRet==0 ){ + *pnVal = n1; + pRet = a1; + } + *paOne = &a1[n1]; + + return pRet; +} + +/* +** This function is used by changeset_concat() to merge two UPDATE changes +** on the same row. +*/ +static int sessionMergeUpdate( + u8 **paOut, /* IN/OUT: Pointer to output buffer */ + SessionTable *pTab, /* Table change pertains to */ + int bPatchset, /* True if records are patchset records */ + u8 *aOldRecord1, /* old.* record for first change */ + u8 *aOldRecord2, /* old.* record for second change */ + u8 *aNewRecord1, /* new.* record for first change */ + u8 *aNewRecord2 /* new.* record for second change */ +){ + u8 *aOld1 = aOldRecord1; + u8 *aOld2 = aOldRecord2; + u8 *aNew1 = aNewRecord1; + u8 *aNew2 = aNewRecord2; + + u8 *aOut = *paOut; + int i; + + if( bPatchset==0 ){ + int bRequired = 0; + + assert( aOldRecord1 && aNewRecord1 ); + + /* Write the old.* vector first. */ + for(i=0; i<pTab->nCol; i++){ + int nOld; + u8 *aOld; + int nNew; + u8 *aNew; + + aOld = sessionMergeValue(&aOld1, &aOld2, &nOld); + aNew = sessionMergeValue(&aNew1, &aNew2, &nNew); + if( pTab->abPK[i] || nOld!=nNew || memcmp(aOld, aNew, nNew) ){ + if( pTab->abPK[i]==0 ) bRequired = 1; + memcpy(aOut, aOld, nOld); + aOut += nOld; + }else{ + *(aOut++) = '\0'; + } + } + + if( !bRequired ) return 0; + } + + /* Write the new.* vector */ + aOld1 = aOldRecord1; + aOld2 = aOldRecord2; + aNew1 = aNewRecord1; + aNew2 = aNewRecord2; + for(i=0; i<pTab->nCol; i++){ + int nOld; + u8 *aOld; + int nNew; + u8 *aNew; + + aOld = sessionMergeValue(&aOld1, &aOld2, &nOld); + aNew = sessionMergeValue(&aNew1, &aNew2, &nNew); + if( bPatchset==0 + && (pTab->abPK[i] || (nOld==nNew && 0==memcmp(aOld, aNew, nNew))) + ){ + *(aOut++) = '\0'; + }else{ + memcpy(aOut, aNew, nNew); + aOut += nNew; + } + } + + *paOut = aOut; + return 1; +} + +/* +** This function is only called from within a pre-update-hook callback. +** It determines if the current pre-update-hook change affects the same row +** as the change stored in argument pChange. If so, it returns true. Otherwise +** if the pre-update-hook does not affect the same row as pChange, it returns +** false. +*/ +static int sessionPreupdateEqual( + sqlite3_session *pSession, /* Session object that owns SessionTable */ + i64 iRowid, /* Rowid value if pTab->bRowid */ + SessionTable *pTab, /* Table associated with change */ + SessionChange *pChange, /* Change to compare to */ + int op /* Current pre-update operation */ +){ + int iCol; /* Used to iterate through columns */ + u8 *a = pChange->aRecord; /* Cursor used to scan change record */ + + if( pTab->bRowid ){ + if( a[0]!=SQLITE_INTEGER ) return 0; + return sessionGetI64(&a[1])==iRowid; + } + + assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE ); + for(iCol=0; iCol<pTab->nCol; iCol++){ + if( !pTab->abPK[iCol] ){ + a += sessionSerialLen(a); + }else{ + sqlite3_value *pVal; /* Value returned by preupdate_new/old */ + int rc; /* Error code from preupdate_new/old */ + int eType = *a++; /* Type of value from change record */ + + /* The following calls to preupdate_new() and preupdate_old() can not + ** fail. This is because they cache their return values, and by the + ** time control flows to here they have already been called once from + ** within sessionPreupdateHash(). The first two asserts below verify + ** this (that the method has already been called). */ + if( op==SQLITE_INSERT ){ + /* assert( db->pPreUpdate->pNewUnpacked || db->pPreUpdate->aNew ); */ + rc = pSession->hook.xNew(pSession->hook.pCtx, iCol, &pVal); + }else{ + /* assert( db->pPreUpdate->pUnpacked ); */ + rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal); + } + assert( rc==SQLITE_OK ); + (void)rc; /* Suppress warning about unused variable */ + if( sqlite3_value_type(pVal)!=eType ) return 0; + + /* A SessionChange object never has a NULL value in a PK column */ + assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT + || eType==SQLITE_BLOB || eType==SQLITE_TEXT + ); + + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + i64 iVal = sessionGetI64(a); + a += 8; + if( eType==SQLITE_INTEGER ){ + if( sqlite3_value_int64(pVal)!=iVal ) return 0; + }else{ + double rVal; + assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); + memcpy(&rVal, &iVal, 8); + if( sqlite3_value_double(pVal)!=rVal ) return 0; + } + }else{ + int n; + const u8 *z; + a += sessionVarintGet(a, &n); + if( sqlite3_value_bytes(pVal)!=n ) return 0; + if( eType==SQLITE_TEXT ){ + z = sqlite3_value_text(pVal); + }else{ + z = sqlite3_value_blob(pVal); + } + if( n>0 && memcmp(a, z, n) ) return 0; + a += n; + } + } + } + + return 1; +} + +/* +** If required, grow the hash table used to store changes on table pTab +** (part of the session pSession). If a fatal OOM error occurs, set the +** session object to failed and return SQLITE_ERROR. Otherwise, return +** SQLITE_OK. +** +** It is possible that a non-fatal OOM error occurs in this function. In +** that case the hash-table does not grow, but SQLITE_OK is returned anyway. +** Growing the hash table in this case is a performance optimization only, +** it is not required for correct operation. +*/ +static int sessionGrowHash( + sqlite3_session *pSession, /* For memory accounting. May be NULL */ + int bPatchset, + SessionTable *pTab +){ + if( pTab->nChange==0 || pTab->nEntry>=(pTab->nChange/2) ){ + int i; + SessionChange **apNew; + sqlite3_int64 nNew = 2*(sqlite3_int64)(pTab->nChange ? pTab->nChange : 128); + + apNew = (SessionChange**)sessionMalloc64( + pSession, sizeof(SessionChange*) * nNew + ); + if( apNew==0 ){ + if( pTab->nChange==0 ){ + return SQLITE_ERROR; + } + return SQLITE_OK; + } + memset(apNew, 0, sizeof(SessionChange *) * nNew); + + for(i=0; i<pTab->nChange; i++){ + SessionChange *p; + SessionChange *pNext; + for(p=pTab->apChange[i]; p; p=pNext){ + int bPkOnly = (p->op==SQLITE_DELETE && bPatchset); + int iHash = sessionChangeHash(pTab, bPkOnly, p->aRecord, nNew); + pNext = p->pNext; + p->pNext = apNew[iHash]; + apNew[iHash] = p; + } + } + + sessionFree(pSession, pTab->apChange); + pTab->nChange = nNew; + pTab->apChange = apNew; + } + + return SQLITE_OK; +} + +/* +** This function queries the database for the names of the columns of table +** zThis, in schema zDb. +** +** Otherwise, if they are not NULL, variable *pnCol is set to the number +** of columns in the database table and variable *pzTab is set to point to a +** nul-terminated copy of the table name. *pazCol (if not NULL) is set to +** point to an array of pointers to column names. And *pabPK (again, if not +** NULL) is set to point to an array of booleans - true if the corresponding +** column is part of the primary key. +** +** For example, if the table is declared as: +** +** CREATE TABLE tbl1(w, x, y, z, PRIMARY KEY(w, z)); +** +** Then the four output variables are populated as follows: +** +** *pnCol = 4 +** *pzTab = "tbl1" +** *pazCol = {"w", "x", "y", "z"} +** *pabPK = {1, 0, 0, 1} +** +** All returned buffers are part of the same single allocation, which must +** be freed using sqlite3_free() by the caller +*/ +static int sessionTableInfo( + sqlite3_session *pSession, /* For memory accounting. May be NULL */ + sqlite3 *db, /* Database connection */ + const char *zDb, /* Name of attached database (e.g. "main") */ + const char *zThis, /* Table name */ + int *pnCol, /* OUT: number of columns */ + const char **pzTab, /* OUT: Copy of zThis */ + const char ***pazCol, /* OUT: Array of column names for table */ + u8 **pabPK, /* OUT: Array of booleans - true for PK col */ + int *pbRowid /* OUT: True if only PK is a rowid */ +){ + char *zPragma; + sqlite3_stmt *pStmt; + int rc; + sqlite3_int64 nByte; + int nDbCol = 0; + int nThis; + int i; + u8 *pAlloc = 0; + char **azCol = 0; + u8 *abPK = 0; + int bRowid = 0; /* Set to true to use rowid as PK */ + + assert( pazCol && pabPK ); + + nThis = sqlite3Strlen30(zThis); + if( nThis==12 && 0==sqlite3_stricmp("sqlite_stat1", zThis) ){ + rc = sqlite3_table_column_metadata(db, zDb, zThis, 0, 0, 0, 0, 0, 0); + if( rc==SQLITE_OK ){ + /* For sqlite_stat1, pretend that (tbl,idx) is the PRIMARY KEY. */ + zPragma = sqlite3_mprintf( + "SELECT 0, 'tbl', '', 0, '', 1 UNION ALL " + "SELECT 1, 'idx', '', 0, '', 2 UNION ALL " + "SELECT 2, 'stat', '', 0, '', 0" + ); + }else if( rc==SQLITE_ERROR ){ + zPragma = sqlite3_mprintf(""); + }else{ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; + return rc; + } + }else{ + zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis); + } + if( !zPragma ){ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; + return SQLITE_NOMEM; + } + + rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0); + sqlite3_free(zPragma); + if( rc!=SQLITE_OK ){ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; + return rc; + } + + nByte = nThis + 1; + bRowid = (pbRowid!=0); + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + nByte += sqlite3_column_bytes(pStmt, 1); + nDbCol++; + if( sqlite3_column_int(pStmt, 5) ) bRowid = 0; + } + if( nDbCol==0 ) bRowid = 0; + nDbCol += bRowid; + nByte += strlen(SESSIONS_ROWID); + rc = sqlite3_reset(pStmt); + + if( rc==SQLITE_OK ){ + nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1); + pAlloc = sessionMalloc64(pSession, nByte); + if( pAlloc==0 ){ + rc = SQLITE_NOMEM; + } + } + if( rc==SQLITE_OK ){ + azCol = (char **)pAlloc; + pAlloc = (u8 *)&azCol[nDbCol]; + abPK = (u8 *)pAlloc; + pAlloc = &abPK[nDbCol]; + if( pzTab ){ + memcpy(pAlloc, zThis, nThis+1); + *pzTab = (char *)pAlloc; + pAlloc += nThis+1; + } + + i = 0; + if( bRowid ){ + size_t nName = strlen(SESSIONS_ROWID); + memcpy(pAlloc, SESSIONS_ROWID, nName+1); + azCol[i] = (char*)pAlloc; + pAlloc += nName+1; + abPK[i] = 1; + i++; + } + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + int nName = sqlite3_column_bytes(pStmt, 1); + const unsigned char *zName = sqlite3_column_text(pStmt, 1); + if( zName==0 ) break; + memcpy(pAlloc, zName, nName+1); + azCol[i] = (char *)pAlloc; + pAlloc += nName+1; + abPK[i] = sqlite3_column_int(pStmt, 5); + i++; + } + rc = sqlite3_reset(pStmt); + } + + /* If successful, populate the output variables. Otherwise, zero them and + ** free any allocation made. An error code will be returned in this case. + */ + if( rc==SQLITE_OK ){ + *pazCol = (const char **)azCol; + *pabPK = abPK; + *pnCol = nDbCol; + }else{ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; + sessionFree(pSession, azCol); + } + if( pbRowid ) *pbRowid = bRowid; + sqlite3_finalize(pStmt); + return rc; +} + +/* +** This function is only called from within a pre-update handler for a +** write to table pTab, part of session pSession. If this is the first +** write to this table, initalize the SessionTable.nCol, azCol[] and +** abPK[] arrays accordingly. +** +** If an error occurs, an error code is stored in sqlite3_session.rc and +** non-zero returned. Or, if no error occurs but the table has no primary +** key, sqlite3_session.rc is left set to SQLITE_OK and non-zero returned to +** indicate that updates on this table should be ignored. SessionTable.abPK +** is set to NULL in this case. +*/ +static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ + if( pTab->nCol==0 ){ + u8 *abPK; + assert( pTab->azCol==0 || pTab->abPK==0 ); + pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, + pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK, + (pSession->bImplicitPK ? &pTab->bRowid : 0) + ); + if( pSession->rc==SQLITE_OK ){ + int i; + for(i=0; i<pTab->nCol; i++){ + if( abPK[i] ){ + pTab->abPK = abPK; + break; + } + } + if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){ + pTab->bStat1 = 1; + } + + if( pSession->bEnableSize ){ + pSession->nMaxChangesetSize += ( + 1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1 + ); + } + } + } + return (pSession->rc || pTab->abPK==0); +} + +/* +** Versions of the four methods in object SessionHook for use with the +** sqlite_stat1 table. The purpose of this is to substitute a zero-length +** blob each time a NULL value is read from the "idx" column of the +** sqlite_stat1 table. +*/ +typedef struct SessionStat1Ctx SessionStat1Ctx; +struct SessionStat1Ctx { + SessionHook hook; + sqlite3_session *pSession; +}; +static int sessionStat1Old(void *pCtx, int iCol, sqlite3_value **ppVal){ + SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; + sqlite3_value *pVal = 0; + int rc = p->hook.xOld(p->hook.pCtx, iCol, &pVal); + if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){ + pVal = p->pSession->pZeroBlob; + } + *ppVal = pVal; + return rc; +} +static int sessionStat1New(void *pCtx, int iCol, sqlite3_value **ppVal){ + SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; + sqlite3_value *pVal = 0; + int rc = p->hook.xNew(p->hook.pCtx, iCol, &pVal); + if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){ + pVal = p->pSession->pZeroBlob; + } + *ppVal = pVal; + return rc; +} +static int sessionStat1Count(void *pCtx){ + SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; + return p->hook.xCount(p->hook.pCtx); +} +static int sessionStat1Depth(void *pCtx){ + SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; + return p->hook.xDepth(p->hook.pCtx); +} + +static int sessionUpdateMaxSize( + int op, + sqlite3_session *pSession, /* Session object pTab is attached to */ + SessionTable *pTab, /* Table that change applies to */ + SessionChange *pC /* Update pC->nMaxSize */ +){ + i64 nNew = 2; + if( pC->op==SQLITE_INSERT ){ + if( pTab->bRowid ) nNew += 9; + if( op!=SQLITE_DELETE ){ + int ii; + for(ii=0; ii<pTab->nCol; ii++){ + sqlite3_value *p = 0; + pSession->hook.xNew(pSession->hook.pCtx, ii, &p); + sessionSerializeValue(0, p, &nNew); + } + } + }else if( op==SQLITE_DELETE ){ + nNew += pC->nRecord; + if( sqlite3_preupdate_blobwrite(pSession->db)>=0 ){ + nNew += pC->nRecord; + } + }else{ + int ii; + u8 *pCsr = pC->aRecord; + if( pTab->bRowid ){ + nNew += 9 + 1; + pCsr += 9; + } + for(ii=pTab->bRowid; ii<pTab->nCol; ii++){ + int bChanged = 1; + int nOld = 0; + int eType; + sqlite3_value *p = 0; + pSession->hook.xNew(pSession->hook.pCtx, ii-pTab->bRowid, &p); + if( p==0 ){ + return SQLITE_NOMEM; + } + + eType = *pCsr++; + switch( eType ){ + case SQLITE_NULL: + bChanged = sqlite3_value_type(p)!=SQLITE_NULL; + break; + + case SQLITE_FLOAT: + case SQLITE_INTEGER: { + if( eType==sqlite3_value_type(p) ){ + sqlite3_int64 iVal = sessionGetI64(pCsr); + if( eType==SQLITE_INTEGER ){ + bChanged = (iVal!=sqlite3_value_int64(p)); + }else{ + double dVal; + memcpy(&dVal, &iVal, 8); + bChanged = (dVal!=sqlite3_value_double(p)); + } + } + nOld = 8; + pCsr += 8; + break; + } + + default: { + int nByte; + nOld = sessionVarintGet(pCsr, &nByte); + pCsr += nOld; + nOld += nByte; + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + if( eType==sqlite3_value_type(p) + && nByte==sqlite3_value_bytes(p) + && (nByte==0 || 0==memcmp(pCsr, sqlite3_value_blob(p), nByte)) + ){ + bChanged = 0; + } + pCsr += nByte; + break; + } + } + + if( bChanged && pTab->abPK[ii] ){ + nNew = pC->nRecord + 2; + break; + } + + if( bChanged ){ + nNew += 1 + nOld; + sessionSerializeValue(0, p, &nNew); + }else if( pTab->abPK[ii] ){ + nNew += 2 + nOld; + }else{ + nNew += 2; + } + } + } + + if( nNew>pC->nMaxSize ){ + int nIncr = nNew - pC->nMaxSize; + pC->nMaxSize = nNew; + pSession->nMaxChangesetSize += nIncr; + } + return SQLITE_OK; +} + +/* +** This function is only called from with a pre-update-hook reporting a +** change on table pTab (attached to session pSession). The type of change +** (UPDATE, INSERT, DELETE) is specified by the first argument. +** +** Unless one is already present or an error occurs, an entry is added +** to the changed-rows hash table associated with table pTab. +*/ +static void sessionPreupdateOneChange( + int op, /* One of SQLITE_UPDATE, INSERT, DELETE */ + i64 iRowid, + sqlite3_session *pSession, /* Session object pTab is attached to */ + SessionTable *pTab /* Table that change applies to */ +){ + int iHash; + int bNull = 0; + int rc = SQLITE_OK; + SessionStat1Ctx stat1 = {{0,0,0,0,0},0}; + + if( pSession->rc ) return; + + /* Load table details if required */ + if( sessionInitTable(pSession, pTab) ) return; + + /* Check the number of columns in this xPreUpdate call matches the + ** number of columns in the table. */ + if( (pTab->nCol-pTab->bRowid)!=pSession->hook.xCount(pSession->hook.pCtx) ){ + pSession->rc = SQLITE_SCHEMA; + return; + } + + /* Grow the hash table if required */ + if( sessionGrowHash(pSession, 0, pTab) ){ + pSession->rc = SQLITE_NOMEM; + return; + } + + if( pTab->bStat1 ){ + stat1.hook = pSession->hook; + stat1.pSession = pSession; + pSession->hook.pCtx = (void*)&stat1; + pSession->hook.xNew = sessionStat1New; + pSession->hook.xOld = sessionStat1Old; + pSession->hook.xCount = sessionStat1Count; + pSession->hook.xDepth = sessionStat1Depth; + if( pSession->pZeroBlob==0 ){ + sqlite3_value *p = sqlite3ValueNew(0); + if( p==0 ){ + rc = SQLITE_NOMEM; + goto error_out; + } + sqlite3ValueSetStr(p, 0, "", 0, SQLITE_STATIC); + pSession->pZeroBlob = p; + } + } + + /* Calculate the hash-key for this change. If the primary key of the row + ** includes a NULL value, exit early. Such changes are ignored by the + ** session module. */ + rc = sessionPreupdateHash( + pSession, iRowid, pTab, op==SQLITE_INSERT, &iHash, &bNull + ); + if( rc!=SQLITE_OK ) goto error_out; + + if( bNull==0 ){ + /* Search the hash table for an existing record for this row. */ + SessionChange *pC; + for(pC=pTab->apChange[iHash]; pC; pC=pC->pNext){ + if( sessionPreupdateEqual(pSession, iRowid, pTab, pC, op) ) break; + } + + if( pC==0 ){ + /* Create a new change object containing all the old values (if + ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK + ** values (if this is an INSERT). */ + sqlite3_int64 nByte; /* Number of bytes to allocate */ + int i; /* Used to iterate through columns */ + + assert( rc==SQLITE_OK ); + pTab->nEntry++; + + /* Figure out how large an allocation is required */ + nByte = sizeof(SessionChange); + for(i=0; i<(pTab->nCol-pTab->bRowid); i++){ + sqlite3_value *p = 0; + if( op!=SQLITE_INSERT ){ + TESTONLY(int trc = ) pSession->hook.xOld(pSession->hook.pCtx, i, &p); + assert( trc==SQLITE_OK ); + }else if( pTab->abPK[i] ){ + TESTONLY(int trc = ) pSession->hook.xNew(pSession->hook.pCtx, i, &p); + assert( trc==SQLITE_OK ); + } + + /* This may fail if SQLite value p contains a utf-16 string that must + ** be converted to utf-8 and an OOM error occurs while doing so. */ + rc = sessionSerializeValue(0, p, &nByte); + if( rc!=SQLITE_OK ) goto error_out; + } + if( pTab->bRowid ){ + nByte += 9; /* Size of rowid field - an integer */ + } + + /* Allocate the change object */ + pC = (SessionChange *)sessionMalloc64(pSession, nByte); + if( !pC ){ + rc = SQLITE_NOMEM; + goto error_out; + }else{ + memset(pC, 0, sizeof(SessionChange)); + pC->aRecord = (u8 *)&pC[1]; + } + + /* Populate the change object. None of the preupdate_old(), + ** preupdate_new() or SerializeValue() calls below may fail as all + ** required values and encodings have already been cached in memory. + ** It is not possible for an OOM to occur in this block. */ + nByte = 0; + if( pTab->bRowid ){ + pC->aRecord[0] = SQLITE_INTEGER; + sessionPutI64(&pC->aRecord[1], iRowid); + nByte = 9; + } + for(i=0; i<(pTab->nCol-pTab->bRowid); i++){ + sqlite3_value *p = 0; + if( op!=SQLITE_INSERT ){ + pSession->hook.xOld(pSession->hook.pCtx, i, &p); + }else if( pTab->abPK[i] ){ + pSession->hook.xNew(pSession->hook.pCtx, i, &p); + } + sessionSerializeValue(&pC->aRecord[nByte], p, &nByte); + } + + /* Add the change to the hash-table */ + if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){ + pC->bIndirect = 1; + } + pC->nRecord = nByte; + pC->op = op; + pC->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pC; + + }else if( pC->bIndirect ){ + /* If the existing change is considered "indirect", but this current + ** change is "direct", mark the change object as direct. */ + if( pSession->hook.xDepth(pSession->hook.pCtx)==0 + && pSession->bIndirect==0 + ){ + pC->bIndirect = 0; + } + } + + assert( rc==SQLITE_OK ); + if( pSession->bEnableSize ){ + rc = sessionUpdateMaxSize(op, pSession, pTab, pC); + } + } + + + /* If an error has occurred, mark the session object as failed. */ + error_out: + if( pTab->bStat1 ){ + pSession->hook = stat1.hook; + } + if( rc!=SQLITE_OK ){ + pSession->rc = rc; + } +} + +static int sessionFindTable( + sqlite3_session *pSession, + const char *zName, + SessionTable **ppTab +){ + int rc = SQLITE_OK; + int nName = sqlite3Strlen30(zName); + SessionTable *pRet; + + /* Search for an existing table */ + for(pRet=pSession->pTable; pRet; pRet=pRet->pNext){ + if( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) ) break; + } + + if( pRet==0 && pSession->bAutoAttach ){ + /* If there is a table-filter configured, invoke it. If it returns 0, + ** do not automatically add the new table. */ + if( pSession->xTableFilter==0 + || pSession->xTableFilter(pSession->pFilterCtx, zName) + ){ + rc = sqlite3session_attach(pSession, zName); + if( rc==SQLITE_OK ){ + pRet = pSession->pTable; + while( ALWAYS(pRet) && pRet->pNext ){ + pRet = pRet->pNext; + } + assert( pRet!=0 ); + assert( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) ); + } + } + } + + assert( rc==SQLITE_OK || pRet==0 ); + *ppTab = pRet; + return rc; +} + +/* +** The 'pre-update' hook registered by this module with SQLite databases. +*/ +static void xPreUpdate( + void *pCtx, /* Copy of third arg to preupdate_hook() */ + sqlite3 *db, /* Database handle */ + int op, /* SQLITE_UPDATE, DELETE or INSERT */ + char const *zDb, /* Database name */ + char const *zName, /* Table name */ + sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */ + sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */ +){ + sqlite3_session *pSession; + int nDb = sqlite3Strlen30(zDb); + + assert( sqlite3_mutex_held(db->mutex) ); + (void)iKey1; + (void)iKey2; + + for(pSession=(sqlite3_session *)pCtx; pSession; pSession=pSession->pNext){ + SessionTable *pTab; + + /* If this session is attached to a different database ("main", "temp" + ** etc.), or if it is not currently enabled, there is nothing to do. Skip + ** to the next session object attached to this database. */ + if( pSession->bEnable==0 ) continue; + if( pSession->rc ) continue; + if( sqlite3_strnicmp(zDb, pSession->zDb, nDb+1) ) continue; + + pSession->rc = sessionFindTable(pSession, zName, &pTab); + if( pTab ){ + assert( pSession->rc==SQLITE_OK ); + assert( op==SQLITE_UPDATE || iKey1==iKey2 ); + sessionPreupdateOneChange(op, iKey1, pSession, pTab); + if( op==SQLITE_UPDATE ){ + sessionPreupdateOneChange(SQLITE_INSERT, iKey2, pSession, pTab); + } + } + } +} + +/* +** The pre-update hook implementations. +*/ +static int sessionPreupdateOld(void *pCtx, int iVal, sqlite3_value **ppVal){ + return sqlite3_preupdate_old((sqlite3*)pCtx, iVal, ppVal); +} +static int sessionPreupdateNew(void *pCtx, int iVal, sqlite3_value **ppVal){ + return sqlite3_preupdate_new((sqlite3*)pCtx, iVal, ppVal); +} +static int sessionPreupdateCount(void *pCtx){ + return sqlite3_preupdate_count((sqlite3*)pCtx); +} +static int sessionPreupdateDepth(void *pCtx){ + return sqlite3_preupdate_depth((sqlite3*)pCtx); +} + +/* +** Install the pre-update hooks on the session object passed as the only +** argument. +*/ +static void sessionPreupdateHooks( + sqlite3_session *pSession +){ + pSession->hook.pCtx = (void*)pSession->db; + pSession->hook.xOld = sessionPreupdateOld; + pSession->hook.xNew = sessionPreupdateNew; + pSession->hook.xCount = sessionPreupdateCount; + pSession->hook.xDepth = sessionPreupdateDepth; +} + +typedef struct SessionDiffCtx SessionDiffCtx; +struct SessionDiffCtx { + sqlite3_stmt *pStmt; + int bRowid; + int nOldOff; +}; + +/* +** The diff hook implementations. +*/ +static int sessionDiffOld(void *pCtx, int iVal, sqlite3_value **ppVal){ + SessionDiffCtx *p = (SessionDiffCtx*)pCtx; + *ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff+p->bRowid); + return SQLITE_OK; +} +static int sessionDiffNew(void *pCtx, int iVal, sqlite3_value **ppVal){ + SessionDiffCtx *p = (SessionDiffCtx*)pCtx; + *ppVal = sqlite3_column_value(p->pStmt, iVal+p->bRowid); + return SQLITE_OK; +} +static int sessionDiffCount(void *pCtx){ + SessionDiffCtx *p = (SessionDiffCtx*)pCtx; + return (p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt)) - p->bRowid; +} +static int sessionDiffDepth(void *pCtx){ + (void)pCtx; + return 0; +} + +/* +** Install the diff hooks on the session object passed as the only +** argument. +*/ +static void sessionDiffHooks( + sqlite3_session *pSession, + SessionDiffCtx *pDiffCtx +){ + pSession->hook.pCtx = (void*)pDiffCtx; + pSession->hook.xOld = sessionDiffOld; + pSession->hook.xNew = sessionDiffNew; + pSession->hook.xCount = sessionDiffCount; + pSession->hook.xDepth = sessionDiffDepth; +} + +static char *sessionExprComparePK( + int nCol, + const char *zDb1, const char *zDb2, + const char *zTab, + const char **azCol, u8 *abPK +){ + int i; + const char *zSep = ""; + char *zRet = 0; + + for(i=0; i<nCol; i++){ + if( abPK[i] ){ + zRet = sqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"=\"%w\".\"%w\".\"%w\"", + zRet, zSep, zDb1, zTab, azCol[i], zDb2, zTab, azCol[i] + ); + zSep = " AND "; + if( zRet==0 ) break; + } + } + + return zRet; +} + +static char *sessionExprCompareOther( + int nCol, + const char *zDb1, const char *zDb2, + const char *zTab, + const char **azCol, u8 *abPK +){ + int i; + const char *zSep = ""; + char *zRet = 0; + int bHave = 0; + + for(i=0; i<nCol; i++){ + if( abPK[i]==0 ){ + bHave = 1; + zRet = sqlite3_mprintf( + "%z%s\"%w\".\"%w\".\"%w\" IS NOT \"%w\".\"%w\".\"%w\"", + zRet, zSep, zDb1, zTab, azCol[i], zDb2, zTab, azCol[i] + ); + zSep = " OR "; + if( zRet==0 ) break; + } + } + + if( bHave==0 ){ + assert( zRet==0 ); + zRet = sqlite3_mprintf("0"); + } + + return zRet; +} + +static char *sessionSelectFindNew( + const char *zDb1, /* Pick rows in this db only */ + const char *zDb2, /* But not in this one */ + int bRowid, + const char *zTbl, /* Table name */ + const char *zExpr +){ + const char *zSel = (bRowid ? SESSIONS_ROWID ", *" : "*"); + char *zRet = sqlite3_mprintf( + "SELECT %s FROM \"%w\".\"%w\" WHERE NOT EXISTS (" + " SELECT 1 FROM \"%w\".\"%w\" WHERE %s" + ")", + zSel, zDb1, zTbl, zDb2, zTbl, zExpr + ); + return zRet; +} + +static int sessionDiffFindNew( + int op, + sqlite3_session *pSession, + SessionTable *pTab, + const char *zDb1, + const char *zDb2, + char *zExpr +){ + int rc = SQLITE_OK; + char *zStmt = sessionSelectFindNew( + zDb1, zDb2, pTab->bRowid, pTab->zName, zExpr + ); + + if( zStmt==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_stmt *pStmt; + rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx; + pDiffCtx->pStmt = pStmt; + pDiffCtx->nOldOff = 0; + pDiffCtx->bRowid = pTab->bRowid; + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0); + sessionPreupdateOneChange(op, iRowid, pSession, pTab); + } + rc = sqlite3_finalize(pStmt); + } + sqlite3_free(zStmt); + } + + return rc; +} + +/* +** Return a comma-separated list of the fully-qualified (with both database +** and table name) column names from table pTab. e.g. +** +** "main"."t1"."a", "main"."t1"."b", "main"."t1"."c" +*/ +static char *sessionAllCols( + const char *zDb, + SessionTable *pTab +){ + int ii; + char *zRet = 0; + for(ii=0; ii<pTab->nCol; ii++){ + zRet = sqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"", + zRet, (zRet ? ", " : ""), zDb, pTab->zName, pTab->azCol[ii] + ); + if( !zRet ) break; + } + return zRet; +} + +static int sessionDiffFindModified( + sqlite3_session *pSession, + SessionTable *pTab, + const char *zFrom, + const char *zExpr +){ + int rc = SQLITE_OK; + + char *zExpr2 = sessionExprCompareOther(pTab->nCol, + pSession->zDb, zFrom, pTab->zName, pTab->azCol, pTab->abPK + ); + if( zExpr2==0 ){ + rc = SQLITE_NOMEM; + }else{ + char *z1 = sessionAllCols(pSession->zDb, pTab); + char *z2 = sessionAllCols(zFrom, pTab); + char *zStmt = sqlite3_mprintf( + "SELECT %s,%s FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)", + z1, z2, pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2 + ); + if( zStmt==0 || z1==0 || z2==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_stmt *pStmt; + rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0); + + if( rc==SQLITE_OK ){ + SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx; + pDiffCtx->pStmt = pStmt; + pDiffCtx->nOldOff = pTab->nCol; + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0); + sessionPreupdateOneChange(SQLITE_UPDATE, iRowid, pSession, pTab); + } + rc = sqlite3_finalize(pStmt); + } + } + sqlite3_free(zStmt); + sqlite3_free(z1); + sqlite3_free(z2); + } + + return rc; +} + +SQLITE_API int sqlite3session_diff( + sqlite3_session *pSession, + const char *zFrom, + const char *zTbl, + char **pzErrMsg +){ + const char *zDb = pSession->zDb; + int rc = pSession->rc; + SessionDiffCtx d; + + memset(&d, 0, sizeof(d)); + sessionDiffHooks(pSession, &d); + + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); + if( pzErrMsg ) *pzErrMsg = 0; + if( rc==SQLITE_OK ){ + char *zExpr = 0; + sqlite3 *db = pSession->db; + SessionTable *pTo; /* Table zTbl */ + + /* Locate and if necessary initialize the target table object */ + rc = sessionFindTable(pSession, zTbl, &pTo); + if( pTo==0 ) goto diff_out; + if( sessionInitTable(pSession, pTo) ){ + rc = pSession->rc; + goto diff_out; + } + + /* Check the table schemas match */ + if( rc==SQLITE_OK ){ + int bHasPk = 0; + int bMismatch = 0; + int nCol; /* Columns in zFrom.zTbl */ + int bRowid = 0; + u8 *abPK; + const char **azCol = 0; + rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK, + pSession->bImplicitPK ? &bRowid : 0 + ); + if( rc==SQLITE_OK ){ + if( pTo->nCol!=nCol ){ + bMismatch = 1; + }else{ + int i; + for(i=0; i<nCol; i++){ + if( pTo->abPK[i]!=abPK[i] ) bMismatch = 1; + if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1; + if( abPK[i] ) bHasPk = 1; + } + } + } + sqlite3_free((char*)azCol); + if( bMismatch ){ + if( pzErrMsg ){ + *pzErrMsg = sqlite3_mprintf("table schemas do not match"); + } + rc = SQLITE_SCHEMA; + } + if( bHasPk==0 ){ + /* Ignore tables with no primary keys */ + goto diff_out; + } + } + + if( rc==SQLITE_OK ){ + zExpr = sessionExprComparePK(pTo->nCol, + zDb, zFrom, pTo->zName, pTo->azCol, pTo->abPK + ); + } + + /* Find new rows */ + if( rc==SQLITE_OK ){ + rc = sessionDiffFindNew(SQLITE_INSERT, pSession, pTo, zDb, zFrom, zExpr); + } + + /* Find old rows */ + if( rc==SQLITE_OK ){ + rc = sessionDiffFindNew(SQLITE_DELETE, pSession, pTo, zFrom, zDb, zExpr); + } + + /* Find modified rows */ + if( rc==SQLITE_OK ){ + rc = sessionDiffFindModified(pSession, pTo, zFrom, zExpr); + } + + sqlite3_free(zExpr); + } + + diff_out: + sessionPreupdateHooks(pSession); + sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); + return rc; +} + +/* +** Create a session object. This session object will record changes to +** database zDb attached to connection db. +*/ +SQLITE_API int sqlite3session_create( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of db (e.g. "main") */ + sqlite3_session **ppSession /* OUT: New session object */ +){ + sqlite3_session *pNew; /* Newly allocated session object */ + sqlite3_session *pOld; /* Session object already attached to db */ + int nDb = sqlite3Strlen30(zDb); /* Length of zDb in bytes */ + + /* Zero the output value in case an error occurs. */ + *ppSession = 0; + + /* Allocate and populate the new session object. */ + pNew = (sqlite3_session *)sqlite3_malloc64(sizeof(sqlite3_session) + nDb + 1); + if( !pNew ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(sqlite3_session)); + pNew->db = db; + pNew->zDb = (char *)&pNew[1]; + pNew->bEnable = 1; + memcpy(pNew->zDb, zDb, nDb+1); + sessionPreupdateHooks(pNew); + + /* Add the new session object to the linked list of session objects + ** attached to database handle $db. Do this under the cover of the db + ** handle mutex. */ + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + pOld = (sqlite3_session*)sqlite3_preupdate_hook(db, xPreUpdate, (void*)pNew); + pNew->pNext = pOld; + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + + *ppSession = pNew; + return SQLITE_OK; +} + +/* +** Free the list of table objects passed as the first argument. The contents +** of the changed-rows hash tables are also deleted. +*/ +static void sessionDeleteTable(sqlite3_session *pSession, SessionTable *pList){ + SessionTable *pNext; + SessionTable *pTab; + + for(pTab=pList; pTab; pTab=pNext){ + int i; + pNext = pTab->pNext; + for(i=0; i<pTab->nChange; i++){ + SessionChange *p; + SessionChange *pNextChange; + for(p=pTab->apChange[i]; p; p=pNextChange){ + pNextChange = p->pNext; + sessionFree(pSession, p); + } + } + sessionFree(pSession, (char*)pTab->azCol); /* cast works around VC++ bug */ + sessionFree(pSession, pTab->apChange); + sessionFree(pSession, pTab); + } +} + +/* +** Delete a session object previously allocated using sqlite3session_create(). +*/ +SQLITE_API void sqlite3session_delete(sqlite3_session *pSession){ + sqlite3 *db = pSession->db; + sqlite3_session *pHead; + sqlite3_session **pp; + + /* Unlink the session from the linked list of sessions attached to the + ** database handle. Hold the db mutex while doing so. */ + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + pHead = (sqlite3_session*)sqlite3_preupdate_hook(db, 0, 0); + for(pp=&pHead; ALWAYS((*pp)!=0); pp=&((*pp)->pNext)){ + if( (*pp)==pSession ){ + *pp = (*pp)->pNext; + if( pHead ) sqlite3_preupdate_hook(db, xPreUpdate, (void*)pHead); + break; + } + } + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + sqlite3ValueFree(pSession->pZeroBlob); + + /* Delete all attached table objects. And the contents of their + ** associated hash-tables. */ + sessionDeleteTable(pSession, pSession->pTable); + + /* Assert that all allocations have been freed and then free the + ** session object itself. */ + assert( pSession->nMalloc==0 ); + sqlite3_free(pSession); +} + +/* +** Set a table filter on a Session Object. +*/ +SQLITE_API void sqlite3session_table_filter( + sqlite3_session *pSession, + int(*xFilter)(void*, const char*), + void *pCtx /* First argument passed to xFilter */ +){ + pSession->bAutoAttach = 1; + pSession->pFilterCtx = pCtx; + pSession->xTableFilter = xFilter; +} + +/* +** Attach a table to a session. All subsequent changes made to the table +** while the session object is enabled will be recorded. +** +** Only tables that have a PRIMARY KEY defined may be attached. It does +** not matter if the PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) +** or not. +*/ +SQLITE_API int sqlite3session_attach( + sqlite3_session *pSession, /* Session object */ + const char *zName /* Table name */ +){ + int rc = SQLITE_OK; + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); + + if( !zName ){ + pSession->bAutoAttach = 1; + }else{ + SessionTable *pTab; /* New table object (if required) */ + int nName; /* Number of bytes in string zName */ + + /* First search for an existing entry. If one is found, this call is + ** a no-op. Return early. */ + nName = sqlite3Strlen30(zName); + for(pTab=pSession->pTable; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) ) break; + } + + if( !pTab ){ + /* Allocate new SessionTable object. */ + int nByte = sizeof(SessionTable) + nName + 1; + pTab = (SessionTable*)sessionMalloc64(pSession, nByte); + if( !pTab ){ + rc = SQLITE_NOMEM; + }else{ + /* Populate the new SessionTable object and link it into the list. + ** The new object must be linked onto the end of the list, not + ** simply added to the start of it in order to ensure that tables + ** appear in the correct order when a changeset or patchset is + ** eventually generated. */ + SessionTable **ppTab; + memset(pTab, 0, sizeof(SessionTable)); + pTab->zName = (char *)&pTab[1]; + memcpy(pTab->zName, zName, nName+1); + for(ppTab=&pSession->pTable; *ppTab; ppTab=&(*ppTab)->pNext); + *ppTab = pTab; + } + } + } + + sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); + return rc; +} + +/* +** Ensure that there is room in the buffer to append nByte bytes of data. +** If not, use sqlite3_realloc() to grow the buffer so that there is. +** +** If successful, return zero. Otherwise, if an OOM condition is encountered, +** set *pRc to SQLITE_NOMEM and return non-zero. +*/ +static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){ +#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1) + i64 nReq = p->nBuf + nByte; + if( *pRc==SQLITE_OK && nReq>p->nAlloc ){ + u8 *aNew; + i64 nNew = p->nAlloc ? p->nAlloc : 128; + + do { + nNew = nNew*2; + }while( nNew<nReq ); + + /* The value of SESSION_MAX_BUFFER_SZ is copied from the implementation + ** of sqlite3_realloc64(). Allocations greater than this size in bytes + ** always fail. It is used here to ensure that this routine can always + ** allocate up to this limit - instead of up to the largest power of + ** two smaller than the limit. */ + if( nNew>SESSION_MAX_BUFFER_SZ ){ + nNew = SESSION_MAX_BUFFER_SZ; + if( nNew<nReq ){ + *pRc = SQLITE_NOMEM; + return 1; + } + } + + aNew = (u8 *)sqlite3_realloc64(p->aBuf, nNew); + if( 0==aNew ){ + *pRc = SQLITE_NOMEM; + }else{ + p->aBuf = aNew; + p->nAlloc = nNew; + } + } + return (*pRc!=SQLITE_OK); +} + +/* +** Append the value passed as the second argument to the buffer passed +** as the first. +** +** This function is a no-op if *pRc is non-zero when it is called. +** Otherwise, if an error occurs, *pRc is set to an SQLite error code +** before returning. +*/ +static void sessionAppendValue(SessionBuffer *p, sqlite3_value *pVal, int *pRc){ + int rc = *pRc; + if( rc==SQLITE_OK ){ + sqlite3_int64 nByte = 0; + rc = sessionSerializeValue(0, pVal, &nByte); + sessionBufferGrow(p, nByte, &rc); + if( rc==SQLITE_OK ){ + rc = sessionSerializeValue(&p->aBuf[p->nBuf], pVal, 0); + p->nBuf += nByte; + }else{ + *pRc = rc; + } + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a single byte to the buffer. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendByte(SessionBuffer *p, u8 v, int *pRc){ + if( 0==sessionBufferGrow(p, 1, pRc) ){ + p->aBuf[p->nBuf++] = v; + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a single varint to the buffer. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendVarint(SessionBuffer *p, int v, int *pRc){ + if( 0==sessionBufferGrow(p, 9, pRc) ){ + p->nBuf += sessionVarintPut(&p->aBuf[p->nBuf], v); + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a blob of data to the buffer. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendBlob( + SessionBuffer *p, + const u8 *aBlob, + int nBlob, + int *pRc +){ + if( nBlob>0 && 0==sessionBufferGrow(p, nBlob, pRc) ){ + memcpy(&p->aBuf[p->nBuf], aBlob, nBlob); + p->nBuf += nBlob; + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a string to the buffer. All bytes in the string +** up to (but not including) the nul-terminator are written to the buffer. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendStr( + SessionBuffer *p, + const char *zStr, + int *pRc +){ + int nStr = sqlite3Strlen30(zStr); + if( 0==sessionBufferGrow(p, nStr+1, pRc) ){ + memcpy(&p->aBuf[p->nBuf], zStr, nStr); + p->nBuf += nStr; + p->aBuf[p->nBuf] = 0x00; + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append the string representation of integer iVal +** to the buffer. No nul-terminator is written. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendInteger( + SessionBuffer *p, /* Buffer to append to */ + int iVal, /* Value to write the string rep. of */ + int *pRc /* IN/OUT: Error code */ +){ + char aBuf[24]; + sqlite3_snprintf(sizeof(aBuf)-1, aBuf, "%d", iVal); + sessionAppendStr(p, aBuf, pRc); +} + +static void sessionAppendPrintf( + SessionBuffer *p, /* Buffer to append to */ + int *pRc, + const char *zFmt, + ... +){ + if( *pRc==SQLITE_OK ){ + char *zApp = 0; + va_list ap; + va_start(ap, zFmt); + zApp = sqlite3_vmprintf(zFmt, ap); + if( zApp==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + sessionAppendStr(p, zApp, pRc); + } + va_end(ap); + sqlite3_free(zApp); + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append the string zStr enclosed in quotes (") and +** with any embedded quote characters escaped to the buffer. No +** nul-terminator byte is written. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendIdent( + SessionBuffer *p, /* Buffer to a append to */ + const char *zStr, /* String to quote, escape and append */ + int *pRc /* IN/OUT: Error code */ +){ + int nStr = sqlite3Strlen30(zStr)*2 + 2 + 2; + if( 0==sessionBufferGrow(p, nStr, pRc) ){ + char *zOut = (char *)&p->aBuf[p->nBuf]; + const char *zIn = zStr; + *zOut++ = '"'; + while( *zIn ){ + if( *zIn=='"' ) *zOut++ = '"'; + *zOut++ = *(zIn++); + } + *zOut++ = '"'; + p->nBuf = (int)((u8 *)zOut - p->aBuf); + p->aBuf[p->nBuf] = 0x00; + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwse, it appends the serialized version of the value stored +** in column iCol of the row that SQL statement pStmt currently points +** to to the buffer. +*/ +static void sessionAppendCol( + SessionBuffer *p, /* Buffer to append to */ + sqlite3_stmt *pStmt, /* Handle pointing to row containing value */ + int iCol, /* Column to read value from */ + int *pRc /* IN/OUT: Error code */ +){ + if( *pRc==SQLITE_OK ){ + int eType = sqlite3_column_type(pStmt, iCol); + sessionAppendByte(p, (u8)eType, pRc); + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + sqlite3_int64 i; + u8 aBuf[8]; + if( eType==SQLITE_INTEGER ){ + i = sqlite3_column_int64(pStmt, iCol); + }else{ + double r = sqlite3_column_double(pStmt, iCol); + memcpy(&i, &r, 8); + } + sessionPutI64(aBuf, i); + sessionAppendBlob(p, aBuf, 8, pRc); + } + if( eType==SQLITE_BLOB || eType==SQLITE_TEXT ){ + u8 *z; + int nByte; + if( eType==SQLITE_BLOB ){ + z = (u8 *)sqlite3_column_blob(pStmt, iCol); + }else{ + z = (u8 *)sqlite3_column_text(pStmt, iCol); + } + nByte = sqlite3_column_bytes(pStmt, iCol); + if( z || (eType==SQLITE_BLOB && nByte==0) ){ + sessionAppendVarint(p, nByte, pRc); + sessionAppendBlob(p, z, nByte, pRc); + }else{ + *pRc = SQLITE_NOMEM; + } + } + } +} + +/* +** +** This function appends an update change to the buffer (see the comments +** under "CHANGESET FORMAT" at the top of the file). An update change +** consists of: +** +** 1 byte: SQLITE_UPDATE (0x17) +** n bytes: old.* record (see RECORD FORMAT) +** m bytes: new.* record (see RECORD FORMAT) +** +** The SessionChange object passed as the third argument contains the +** values that were stored in the row when the session began (the old.* +** values). The statement handle passed as the second argument points +** at the current version of the row (the new.* values). +** +** If all of the old.* values are equal to their corresponding new.* value +** (i.e. nothing has changed), then no data at all is appended to the buffer. +** +** Otherwise, the old.* record contains all primary key values and the +** original values of any fields that have been modified. The new.* record +** contains the new values of only those fields that have been modified. +*/ +static int sessionAppendUpdate( + SessionBuffer *pBuf, /* Buffer to append to */ + int bPatchset, /* True for "patchset", 0 for "changeset" */ + sqlite3_stmt *pStmt, /* Statement handle pointing at new row */ + SessionChange *p, /* Object containing old values */ + u8 *abPK /* Boolean array - true for PK columns */ +){ + int rc = SQLITE_OK; + SessionBuffer buf2 = {0,0,0}; /* Buffer to accumulate new.* record in */ + int bNoop = 1; /* Set to zero if any values are modified */ + int nRewind = pBuf->nBuf; /* Set to zero if any values are modified */ + int i; /* Used to iterate through columns */ + u8 *pCsr = p->aRecord; /* Used to iterate through old.* values */ + + assert( abPK!=0 ); + sessionAppendByte(pBuf, SQLITE_UPDATE, &rc); + sessionAppendByte(pBuf, p->bIndirect, &rc); + for(i=0; i<sqlite3_column_count(pStmt); i++){ + int bChanged = 0; + int nAdvance; + int eType = *pCsr; + switch( eType ){ + case SQLITE_NULL: + nAdvance = 1; + if( sqlite3_column_type(pStmt, i)!=SQLITE_NULL ){ + bChanged = 1; + } + break; + + case SQLITE_FLOAT: + case SQLITE_INTEGER: { + nAdvance = 9; + if( eType==sqlite3_column_type(pStmt, i) ){ + sqlite3_int64 iVal = sessionGetI64(&pCsr[1]); + if( eType==SQLITE_INTEGER ){ + if( iVal==sqlite3_column_int64(pStmt, i) ) break; + }else{ + double dVal; + memcpy(&dVal, &iVal, 8); + if( dVal==sqlite3_column_double(pStmt, i) ) break; + } + } + bChanged = 1; + break; + } + + default: { + int n; + int nHdr = 1 + sessionVarintGet(&pCsr[1], &n); + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + nAdvance = nHdr + n; + if( eType==sqlite3_column_type(pStmt, i) + && n==sqlite3_column_bytes(pStmt, i) + && (n==0 || 0==memcmp(&pCsr[nHdr], sqlite3_column_blob(pStmt, i), n)) + ){ + break; + } + bChanged = 1; + } + } + + /* If at least one field has been modified, this is not a no-op. */ + if( bChanged ) bNoop = 0; + + /* Add a field to the old.* record. This is omitted if this module is + ** currently generating a patchset. */ + if( bPatchset==0 ){ + if( bChanged || abPK[i] ){ + sessionAppendBlob(pBuf, pCsr, nAdvance, &rc); + }else{ + sessionAppendByte(pBuf, 0, &rc); + } + } + + /* Add a field to the new.* record. Or the only record if currently + ** generating a patchset. */ + if( bChanged || (bPatchset && abPK[i]) ){ + sessionAppendCol(&buf2, pStmt, i, &rc); + }else{ + sessionAppendByte(&buf2, 0, &rc); + } + + pCsr += nAdvance; + } + + if( bNoop ){ + pBuf->nBuf = nRewind; + }else{ + sessionAppendBlob(pBuf, buf2.aBuf, buf2.nBuf, &rc); + } + sqlite3_free(buf2.aBuf); + + return rc; +} + +/* +** Append a DELETE change to the buffer passed as the first argument. Use +** the changeset format if argument bPatchset is zero, or the patchset +** format otherwise. +*/ +static int sessionAppendDelete( + SessionBuffer *pBuf, /* Buffer to append to */ + int bPatchset, /* True for "patchset", 0 for "changeset" */ + SessionChange *p, /* Object containing old values */ + int nCol, /* Number of columns in table */ + u8 *abPK /* Boolean array - true for PK columns */ +){ + int rc = SQLITE_OK; + + sessionAppendByte(pBuf, SQLITE_DELETE, &rc); + sessionAppendByte(pBuf, p->bIndirect, &rc); + + if( bPatchset==0 ){ + sessionAppendBlob(pBuf, p->aRecord, p->nRecord, &rc); + }else{ + int i; + u8 *a = p->aRecord; + for(i=0; i<nCol; i++){ + u8 *pStart = a; + int eType = *a++; + + switch( eType ){ + case 0: + case SQLITE_NULL: + assert( abPK[i]==0 ); + break; + + case SQLITE_FLOAT: + case SQLITE_INTEGER: + a += 8; + break; + + default: { + int n; + a += sessionVarintGet(a, &n); + a += n; + break; + } + } + if( abPK[i] ){ + sessionAppendBlob(pBuf, pStart, (int)(a-pStart), &rc); + } + } + assert( (a - p->aRecord)==p->nRecord ); + } + + return rc; +} + +/* +** Formulate and prepare a SELECT statement to retrieve a row from table +** zTab in database zDb based on its primary key. i.e. +** +** SELECT *, <noop-test> FROM zDb.zTab WHERE (pk1, pk2,...) IS (?1, ?2,...) +** +** where <noop-test> is: +** +** 1 AND (?A OR ?1 IS <column>) AND ... +** +** for each non-pk <column>. +*/ +static int sessionSelectStmt( + sqlite3 *db, /* Database handle */ + int bIgnoreNoop, + const char *zDb, /* Database name */ + const char *zTab, /* Table name */ + int bRowid, + int nCol, /* Number of columns in table */ + const char **azCol, /* Names of table columns */ + u8 *abPK, /* PRIMARY KEY array */ + sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */ +){ + int rc = SQLITE_OK; + char *zSql = 0; + const char *zSep = ""; + const char *zCols = bRowid ? SESSIONS_ROWID ", *" : "*"; + int nSql = -1; + int i; + + SessionBuffer nooptest = {0, 0, 0}; + SessionBuffer pkfield = {0, 0, 0}; + SessionBuffer pkvar = {0, 0, 0}; + + sessionAppendStr(&nooptest, ", 1", &rc); + + if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){ + sessionAppendStr(&nooptest, " AND (?6 OR ?3 IS stat)", &rc); + sessionAppendStr(&pkfield, "tbl, idx", &rc); + sessionAppendStr(&pkvar, + "?1, (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", &rc + ); + zCols = "tbl, ?2, stat"; + }else{ + for(i=0; i<nCol; i++){ + if( abPK[i] ){ + sessionAppendStr(&pkfield, zSep, &rc); + sessionAppendStr(&pkvar, zSep, &rc); + zSep = ", "; + sessionAppendIdent(&pkfield, azCol[i], &rc); + sessionAppendPrintf(&pkvar, &rc, "?%d", i+1); + }else{ + sessionAppendPrintf(&nooptest, &rc, + " AND (?%d OR ?%d IS %w.%w)", i+1+nCol, i+1, zTab, azCol[i] + ); + } + } + } + + if( rc==SQLITE_OK ){ + zSql = sqlite3_mprintf( + "SELECT %s%s FROM %Q.%Q WHERE (%s) IS (%s)", + zCols, (bIgnoreNoop ? (char*)nooptest.aBuf : ""), + zDb, zTab, (char*)pkfield.aBuf, (char*)pkvar.aBuf + ); + if( zSql==0 ) rc = SQLITE_NOMEM; + } + +#if 0 + if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){ + zSql = sqlite3_mprintf( + "SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND " + "idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb + ); + if( zSql==0 ) rc = SQLITE_NOMEM; + }else{ + const char *zSep = ""; + SessionBuffer buf = {0, 0, 0}; + + sessionAppendStr(&buf, "SELECT * FROM ", &rc); + sessionAppendIdent(&buf, zDb, &rc); + sessionAppendStr(&buf, ".", &rc); + sessionAppendIdent(&buf, zTab, &rc); + sessionAppendStr(&buf, " WHERE ", &rc); + for(i=0; i<nCol; i++){ + if( abPK[i] ){ + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, azCol[i], &rc); + sessionAppendStr(&buf, " IS ?", &rc); + sessionAppendInteger(&buf, i+1, &rc); + zSep = " AND "; + } + } + zSql = (char*)buf.aBuf; + nSql = buf.nBuf; + } +#endif + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0); + } + sqlite3_free(zSql); + sqlite3_free(nooptest.aBuf); + sqlite3_free(pkfield.aBuf); + sqlite3_free(pkvar.aBuf); + return rc; +} + +/* +** Bind the PRIMARY KEY values from the change passed in argument pChange +** to the SELECT statement passed as the first argument. The SELECT statement +** is as prepared by function sessionSelectStmt(). +** +** Return SQLITE_OK if all PK values are successfully bound, or an SQLite +** error code (e.g. SQLITE_NOMEM) otherwise. +*/ +static int sessionSelectBind( + sqlite3_stmt *pSelect, /* SELECT from sessionSelectStmt() */ + int nCol, /* Number of columns in table */ + u8 *abPK, /* PRIMARY KEY array */ + SessionChange *pChange /* Change structure */ +){ + int i; + int rc = SQLITE_OK; + u8 *a = pChange->aRecord; + + for(i=0; i<nCol && rc==SQLITE_OK; i++){ + int eType = *a++; + + switch( eType ){ + case 0: + case SQLITE_NULL: + assert( abPK[i]==0 ); + break; + + case SQLITE_INTEGER: { + if( abPK[i] ){ + i64 iVal = sessionGetI64(a); + rc = sqlite3_bind_int64(pSelect, i+1, iVal); + } + a += 8; + break; + } + + case SQLITE_FLOAT: { + if( abPK[i] ){ + double rVal; + i64 iVal = sessionGetI64(a); + memcpy(&rVal, &iVal, 8); + rc = sqlite3_bind_double(pSelect, i+1, rVal); + } + a += 8; + break; + } + + case SQLITE_TEXT: { + int n; + a += sessionVarintGet(a, &n); + if( abPK[i] ){ + rc = sqlite3_bind_text(pSelect, i+1, (char *)a, n, SQLITE_TRANSIENT); + } + a += n; + break; + } + + default: { + int n; + assert( eType==SQLITE_BLOB ); + a += sessionVarintGet(a, &n); + if( abPK[i] ){ + rc = sqlite3_bind_blob(pSelect, i+1, a, n, SQLITE_TRANSIENT); + } + a += n; + break; + } + } + } + + return rc; +} + +/* +** This function is a no-op if *pRc is set to other than SQLITE_OK when it +** is called. Otherwise, append a serialized table header (part of the binary +** changeset format) to buffer *pBuf. If an error occurs, set *pRc to an +** SQLite error code before returning. +*/ +static void sessionAppendTableHdr( + SessionBuffer *pBuf, /* Append header to this buffer */ + int bPatchset, /* Use the patchset format if true */ + SessionTable *pTab, /* Table object to append header for */ + int *pRc /* IN/OUT: Error code */ +){ + /* Write a table header */ + sessionAppendByte(pBuf, (bPatchset ? 'P' : 'T'), pRc); + sessionAppendVarint(pBuf, pTab->nCol, pRc); + sessionAppendBlob(pBuf, pTab->abPK, pTab->nCol, pRc); + sessionAppendBlob(pBuf, (u8 *)pTab->zName, (int)strlen(pTab->zName)+1, pRc); +} + +/* +** Generate either a changeset (if argument bPatchset is zero) or a patchset +** (if it is non-zero) based on the current contents of the session object +** passed as the first argument. +** +** If no error occurs, SQLITE_OK is returned and the new changeset/patchset +** stored in output variables *pnChangeset and *ppChangeset. Or, if an error +** occurs, an SQLite error code is returned and both output variables set +** to 0. +*/ +static int sessionGenerateChangeset( + sqlite3_session *pSession, /* Session object */ + int bPatchset, /* True for patchset, false for changeset */ + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut, /* First argument for xOutput */ + int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ + void **ppChangeset /* OUT: Buffer containing changeset */ +){ + sqlite3 *db = pSession->db; /* Source database handle */ + SessionTable *pTab; /* Used to iterate through attached tables */ + SessionBuffer buf = {0,0,0}; /* Buffer in which to accumlate changeset */ + int rc; /* Return code */ + + assert( xOutput==0 || (pnChangeset==0 && ppChangeset==0) ); + assert( xOutput!=0 || (pnChangeset!=0 && ppChangeset!=0) ); + + /* Zero the output variables in case an error occurs. If this session + ** object is already in the error state (sqlite3_session.rc != SQLITE_OK), + ** this call will be a no-op. */ + if( xOutput==0 ){ + assert( pnChangeset!=0 && ppChangeset!=0 ); + *pnChangeset = 0; + *ppChangeset = 0; + } + + if( pSession->rc ) return pSession->rc; + rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0); + if( rc!=SQLITE_OK ) return rc; + + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + + for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ + if( pTab->nEntry ){ + const char *zName = pTab->zName; + int nCol = 0; /* Number of columns in table */ + u8 *abPK = 0; /* Primary key array */ + const char **azCol = 0; /* Table columns */ + int i; /* Used to iterate through hash buckets */ + sqlite3_stmt *pSel = 0; /* SELECT statement to query table pTab */ + int nRewind = buf.nBuf; /* Initial size of write buffer */ + int nNoop; /* Size of buffer after writing tbl header */ + int bRowid = 0; + + /* Check the table schema is still Ok. */ + rc = sessionTableInfo( + 0, db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK, + (pSession->bImplicitPK ? &bRowid : 0) + ); + if( rc==SQLITE_OK && ( + pTab->nCol!=nCol + || pTab->bRowid!=bRowid + || memcmp(abPK, pTab->abPK, nCol) + )){ + rc = SQLITE_SCHEMA; + } + + /* Write a table header */ + sessionAppendTableHdr(&buf, bPatchset, pTab, &rc); + + /* Build and compile a statement to execute: */ + if( rc==SQLITE_OK ){ + rc = sessionSelectStmt( + db, 0, pSession->zDb, zName, bRowid, nCol, azCol, abPK, &pSel + ); + } + + nNoop = buf.nBuf; + for(i=0; i<pTab->nChange && rc==SQLITE_OK; i++){ + SessionChange *p; /* Used to iterate through changes */ + + for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){ + rc = sessionSelectBind(pSel, nCol, abPK, p); + if( rc!=SQLITE_OK ) continue; + if( sqlite3_step(pSel)==SQLITE_ROW ){ + if( p->op==SQLITE_INSERT ){ + int iCol; + sessionAppendByte(&buf, SQLITE_INSERT, &rc); + sessionAppendByte(&buf, p->bIndirect, &rc); + for(iCol=0; iCol<nCol; iCol++){ + sessionAppendCol(&buf, pSel, iCol, &rc); + } + }else{ + assert( abPK!=0 ); /* Because sessionSelectStmt() returned ok */ + rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK); + } + }else if( p->op!=SQLITE_INSERT ){ + rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_reset(pSel); + } + + /* If the buffer is now larger than sessions_strm_chunk_size, pass + ** its contents to the xOutput() callback. */ + if( xOutput + && rc==SQLITE_OK + && buf.nBuf>nNoop + && buf.nBuf>sessions_strm_chunk_size + ){ + rc = xOutput(pOut, (void*)buf.aBuf, buf.nBuf); + nNoop = -1; + buf.nBuf = 0; + } + + } + } + + sqlite3_finalize(pSel); + if( buf.nBuf==nNoop ){ + buf.nBuf = nRewind; + } + sqlite3_free((char*)azCol); /* cast works around VC++ bug */ + } + } + + if( rc==SQLITE_OK ){ + if( xOutput==0 ){ + *pnChangeset = buf.nBuf; + *ppChangeset = buf.aBuf; + buf.aBuf = 0; + }else if( buf.nBuf>0 ){ + rc = xOutput(pOut, (void*)buf.aBuf, buf.nBuf); + } + } + + sqlite3_free(buf.aBuf); + sqlite3_exec(db, "RELEASE changeset", 0, 0, 0); + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + return rc; +} + +/* +** Obtain a changeset object containing all changes recorded by the +** session object passed as the first argument. +** +** It is the responsibility of the caller to eventually free the buffer +** using sqlite3_free(). +*/ +SQLITE_API int sqlite3session_changeset( + sqlite3_session *pSession, /* Session object */ + int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ + void **ppChangeset /* OUT: Buffer containing changeset */ +){ + int rc; + + if( pnChangeset==0 || ppChangeset==0 ) return SQLITE_MISUSE; + rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset); + assert( rc || pnChangeset==0 + || pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize + ); + return rc; +} + +/* +** Streaming version of sqlite3session_changeset(). +*/ +SQLITE_API int sqlite3session_changeset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + if( xOutput==0 ) return SQLITE_MISUSE; + return sessionGenerateChangeset(pSession, 0, xOutput, pOut, 0, 0); +} + +/* +** Streaming version of sqlite3session_patchset(). +*/ +SQLITE_API int sqlite3session_patchset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + if( xOutput==0 ) return SQLITE_MISUSE; + return sessionGenerateChangeset(pSession, 1, xOutput, pOut, 0, 0); +} + +/* +** Obtain a patchset object containing all changes recorded by the +** session object passed as the first argument. +** +** It is the responsibility of the caller to eventually free the buffer +** using sqlite3_free(). +*/ +SQLITE_API int sqlite3session_patchset( + sqlite3_session *pSession, /* Session object */ + int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */ + void **ppPatchset /* OUT: Buffer containing changeset */ +){ + if( pnPatchset==0 || ppPatchset==0 ) return SQLITE_MISUSE; + return sessionGenerateChangeset(pSession, 1, 0, 0, pnPatchset, ppPatchset); +} + +/* +** Enable or disable the session object passed as the first argument. +*/ +SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable){ + int ret; + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); + if( bEnable>=0 ){ + pSession->bEnable = bEnable; + } + ret = pSession->bEnable; + sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); + return ret; +} + +/* +** Enable or disable the session object passed as the first argument. +*/ +SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect){ + int ret; + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); + if( bIndirect>=0 ){ + pSession->bIndirect = bIndirect; + } + ret = pSession->bIndirect; + sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); + return ret; +} + +/* +** Return true if there have been no changes to monitored tables recorded +** by the session object passed as the only argument. +*/ +SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession){ + int ret = 0; + SessionTable *pTab; + + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); + for(pTab=pSession->pTable; pTab && ret==0; pTab=pTab->pNext){ + ret = (pTab->nEntry>0); + } + sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); + + return (ret==0); +} + +/* +** Return the amount of heap memory in use. +*/ +SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession){ + return pSession->nMalloc; +} + +/* +** Configure the session object passed as the first argument. +*/ +SQLITE_API int sqlite3session_object_config(sqlite3_session *pSession, int op, void *pArg){ + int rc = SQLITE_OK; + switch( op ){ + case SQLITE_SESSION_OBJCONFIG_SIZE: { + int iArg = *(int*)pArg; + if( iArg>=0 ){ + if( pSession->pTable ){ + rc = SQLITE_MISUSE; + }else{ + pSession->bEnableSize = (iArg!=0); + } + } + *(int*)pArg = pSession->bEnableSize; + break; + } + + case SQLITE_SESSION_OBJCONFIG_ROWID: { + int iArg = *(int*)pArg; + if( iArg>=0 ){ + if( pSession->pTable ){ + rc = SQLITE_MISUSE; + }else{ + pSession->bImplicitPK = (iArg!=0); + } + } + *(int*)pArg = pSession->bImplicitPK; + break; + } + + default: + rc = SQLITE_MISUSE; + } + + return rc; +} + +/* +** Return the maximum size of sqlite3session_changeset() output. +*/ +SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession){ + return pSession->nMaxChangesetSize; +} + +/* +** Do the work for either sqlite3changeset_start() or start_strm(). +*/ +static int sessionChangesetStart( + sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int nChangeset, /* Size of buffer pChangeset in bytes */ + void *pChangeset, /* Pointer to buffer containing changeset */ + int bInvert, /* True to invert changeset */ + int bSkipEmpty /* True to skip empty UPDATE changes */ +){ + sqlite3_changeset_iter *pRet; /* Iterator to return */ + int nByte; /* Number of bytes to allocate for iterator */ + + assert( xInput==0 || (pChangeset==0 && nChangeset==0) ); + + /* Zero the output variable in case an error occurs. */ + *pp = 0; + + /* Allocate and initialize the iterator structure. */ + nByte = sizeof(sqlite3_changeset_iter); + pRet = (sqlite3_changeset_iter *)sqlite3_malloc(nByte); + if( !pRet ) return SQLITE_NOMEM; + memset(pRet, 0, sizeof(sqlite3_changeset_iter)); + pRet->in.aData = (u8 *)pChangeset; + pRet->in.nData = nChangeset; + pRet->in.xInput = xInput; + pRet->in.pIn = pIn; + pRet->in.bEof = (xInput ? 0 : 1); + pRet->bInvert = bInvert; + pRet->bSkipEmpty = bSkipEmpty; + + /* Populate the output variable and return success. */ + *pp = pRet; + return SQLITE_OK; +} + +/* +** Create an iterator used to iterate through the contents of a changeset. +*/ +SQLITE_API int sqlite3changeset_start( + sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ + int nChangeset, /* Size of buffer pChangeset in bytes */ + void *pChangeset /* Pointer to buffer containing changeset */ +){ + return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0, 0); +} +SQLITE_API int sqlite3changeset_start_v2( + sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ + int nChangeset, /* Size of buffer pChangeset in bytes */ + void *pChangeset, /* Pointer to buffer containing changeset */ + int flags +){ + int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT); + return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert, 0); +} + +/* +** Streaming version of sqlite3changeset_start(). +*/ +SQLITE_API int sqlite3changeset_start_strm( + sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +){ + return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0, 0); +} +SQLITE_API int sqlite3changeset_start_v2_strm( + sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int flags +){ + int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT); + return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert, 0); +} + +/* +** If the SessionInput object passed as the only argument is a streaming +** object and the buffer is full, discard some data to free up space. +*/ +static void sessionDiscardData(SessionInput *pIn){ + if( pIn->xInput && pIn->iNext>=sessions_strm_chunk_size ){ + int nMove = pIn->buf.nBuf - pIn->iNext; + assert( nMove>=0 ); + if( nMove>0 ){ + memmove(pIn->buf.aBuf, &pIn->buf.aBuf[pIn->iNext], nMove); + } + pIn->buf.nBuf -= pIn->iNext; + pIn->iNext = 0; + pIn->nData = pIn->buf.nBuf; + } +} + +/* +** Ensure that there are at least nByte bytes available in the buffer. Or, +** if there are not nByte bytes remaining in the input, that all available +** data is in the buffer. +** +** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise. +*/ +static int sessionInputBuffer(SessionInput *pIn, int nByte){ + int rc = SQLITE_OK; + if( pIn->xInput ){ + while( !pIn->bEof && (pIn->iNext+nByte)>=pIn->nData && rc==SQLITE_OK ){ + int nNew = sessions_strm_chunk_size; + + if( pIn->bNoDiscard==0 ) sessionDiscardData(pIn); + if( SQLITE_OK==sessionBufferGrow(&pIn->buf, nNew, &rc) ){ + rc = pIn->xInput(pIn->pIn, &pIn->buf.aBuf[pIn->buf.nBuf], &nNew); + if( nNew==0 ){ + pIn->bEof = 1; + }else{ + pIn->buf.nBuf += nNew; + } + } + + pIn->aData = pIn->buf.aBuf; + pIn->nData = pIn->buf.nBuf; + } + } + return rc; +} + +/* +** When this function is called, *ppRec points to the start of a record +** that contains nCol values. This function advances the pointer *ppRec +** until it points to the byte immediately following that record. +*/ +static void sessionSkipRecord( + u8 **ppRec, /* IN/OUT: Record pointer */ + int nCol /* Number of values in record */ +){ + u8 *aRec = *ppRec; + int i; + for(i=0; i<nCol; i++){ + int eType = *aRec++; + if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + int nByte; + aRec += sessionVarintGet((u8*)aRec, &nByte); + aRec += nByte; + }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + aRec += 8; + } + } + + *ppRec = aRec; +} + +/* +** This function sets the value of the sqlite3_value object passed as the +** first argument to a copy of the string or blob held in the aData[] +** buffer. SQLITE_OK is returned if successful, or SQLITE_NOMEM if an OOM +** error occurs. +*/ +static int sessionValueSetStr( + sqlite3_value *pVal, /* Set the value of this object */ + u8 *aData, /* Buffer containing string or blob data */ + int nData, /* Size of buffer aData[] in bytes */ + u8 enc /* String encoding (0 for blobs) */ +){ + /* In theory this code could just pass SQLITE_TRANSIENT as the final + ** argument to sqlite3ValueSetStr() and have the copy created + ** automatically. But doing so makes it difficult to detect any OOM + ** error. Hence the code to create the copy externally. */ + u8 *aCopy = sqlite3_malloc64((sqlite3_int64)nData+1); + if( aCopy==0 ) return SQLITE_NOMEM; + memcpy(aCopy, aData, nData); + sqlite3ValueSetStr(pVal, nData, (char*)aCopy, enc, sqlite3_free); + return SQLITE_OK; +} + +/* +** Deserialize a single record from a buffer in memory. See "RECORD FORMAT" +** for details. +** +** When this function is called, *paChange points to the start of the record +** to deserialize. Assuming no error occurs, *paChange is set to point to +** one byte after the end of the same record before this function returns. +** If the argument abPK is NULL, then the record contains nCol values. Or, +** if abPK is other than NULL, then the record contains only the PK fields +** (in other words, it is a patchset DELETE record). +** +** If successful, each element of the apOut[] array (allocated by the caller) +** is set to point to an sqlite3_value object containing the value read +** from the corresponding position in the record. If that value is not +** included in the record (i.e. because the record is part of an UPDATE change +** and the field was not modified), the corresponding element of apOut[] is +** set to NULL. +** +** It is the responsibility of the caller to free all sqlite_value structures +** using sqlite3_free(). +** +** If an error occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. +** The apOut[] array may have been partially populated in this case. +*/ +static int sessionReadRecord( + SessionInput *pIn, /* Input data */ + int nCol, /* Number of values in record */ + u8 *abPK, /* Array of primary key flags, or NULL */ + sqlite3_value **apOut, /* Write values to this array */ + int *pbEmpty +){ + int i; /* Used to iterate through columns */ + int rc = SQLITE_OK; + + assert( pbEmpty==0 || *pbEmpty==0 ); + if( pbEmpty ) *pbEmpty = 1; + for(i=0; i<nCol && rc==SQLITE_OK; i++){ + int eType = 0; /* Type of value (SQLITE_NULL, TEXT etc.) */ + if( abPK && abPK[i]==0 ) continue; + rc = sessionInputBuffer(pIn, 9); + if( rc==SQLITE_OK ){ + if( pIn->iNext>=pIn->nData ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + eType = pIn->aData[pIn->iNext++]; + assert( apOut[i]==0 ); + if( eType ){ + if( pbEmpty ) *pbEmpty = 0; + apOut[i] = sqlite3ValueNew(0); + if( !apOut[i] ) rc = SQLITE_NOMEM; + } + } + } + + if( rc==SQLITE_OK ){ + u8 *aVal = &pIn->aData[pIn->iNext]; + if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + int nByte; + pIn->iNext += sessionVarintGet(aVal, &nByte); + rc = sessionInputBuffer(pIn, nByte); + if( rc==SQLITE_OK ){ + if( nByte<0 || nByte>pIn->nData-pIn->iNext ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0); + rc = sessionValueSetStr(apOut[i],&pIn->aData[pIn->iNext],nByte,enc); + pIn->iNext += nByte; + } + } + } + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + if( (pIn->nData-pIn->iNext)<8 ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + sqlite3_int64 v = sessionGetI64(aVal); + if( eType==SQLITE_INTEGER ){ + sqlite3VdbeMemSetInt64(apOut[i], v); + }else{ + double d; + memcpy(&d, &v, 8); + sqlite3VdbeMemSetDouble(apOut[i], d); + } + pIn->iNext += 8; + } + } + } + } + + return rc; +} + +/* +** The input pointer currently points to the second byte of a table-header. +** Specifically, to the following: +** +** + number of columns in table (varint) +** + array of PK flags (1 byte per column), +** + table name (nul terminated). +** +** This function ensures that all of the above is present in the input +** buffer (i.e. that it can be accessed without any calls to xInput()). +** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. +** The input pointer is not moved. +*/ +static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){ + int rc = SQLITE_OK; + int nCol = 0; + int nRead = 0; + + rc = sessionInputBuffer(pIn, 9); + if( rc==SQLITE_OK ){ + nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol); + /* The hard upper limit for the number of columns in an SQLite + ** database table is, according to sqliteLimit.h, 32676. So + ** consider any table-header that purports to have more than 65536 + ** columns to be corrupt. This is convenient because otherwise, + ** if the (nCol>65536) condition below were omitted, a sufficiently + ** large value for nCol may cause nRead to wrap around and become + ** negative. Leading to a crash. */ + if( nCol<0 || nCol>65536 ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = sessionInputBuffer(pIn, nRead+nCol+100); + nRead += nCol; + } + } + + while( rc==SQLITE_OK ){ + while( (pIn->iNext + nRead)<pIn->nData && pIn->aData[pIn->iNext + nRead] ){ + nRead++; + } + if( (pIn->iNext + nRead)<pIn->nData ) break; + rc = sessionInputBuffer(pIn, nRead + 100); + } + *pnByte = nRead+1; + return rc; +} + +/* +** The input pointer currently points to the first byte of the first field +** of a record consisting of nCol columns. This function ensures the entire +** record is buffered. It does not move the input pointer. +** +** If successful, SQLITE_OK is returned and *pnByte is set to the size of +** the record in bytes. Otherwise, an SQLite error code is returned. The +** final value of *pnByte is undefined in this case. +*/ +static int sessionChangesetBufferRecord( + SessionInput *pIn, /* Input data */ + int nCol, /* Number of columns in record */ + int *pnByte /* OUT: Size of record in bytes */ +){ + int rc = SQLITE_OK; + int nByte = 0; + int i; + for(i=0; rc==SQLITE_OK && i<nCol; i++){ + int eType; + rc = sessionInputBuffer(pIn, nByte + 10); + if( rc==SQLITE_OK ){ + eType = pIn->aData[pIn->iNext + nByte++]; + if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + int n; + nByte += sessionVarintGet(&pIn->aData[pIn->iNext+nByte], &n); + nByte += n; + rc = sessionInputBuffer(pIn, nByte); + }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + nByte += 8; + } + } + } + *pnByte = nByte; + return rc; +} + +/* +** The input pointer currently points to the second byte of a table-header. +** Specifically, to the following: +** +** + number of columns in table (varint) +** + array of PK flags (1 byte per column), +** + table name (nul terminated). +** +** This function decodes the table-header and populates the p->nCol, +** p->zTab and p->abPK[] variables accordingly. The p->apValue[] array is +** also allocated or resized according to the new value of p->nCol. The +** input pointer is left pointing to the byte following the table header. +** +** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code +** is returned and the final values of the various fields enumerated above +** are undefined. +*/ +static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){ + int rc; + int nCopy; + assert( p->rc==SQLITE_OK ); + + rc = sessionChangesetBufferTblhdr(&p->in, &nCopy); + if( rc==SQLITE_OK ){ + int nByte; + int nVarint; + nVarint = sessionVarintGet(&p->in.aData[p->in.iNext], &p->nCol); + if( p->nCol>0 ){ + nCopy -= nVarint; + p->in.iNext += nVarint; + nByte = p->nCol * sizeof(sqlite3_value*) * 2 + nCopy; + p->tblhdr.nBuf = 0; + sessionBufferGrow(&p->tblhdr, nByte, &rc); + }else{ + rc = SQLITE_CORRUPT_BKPT; + } + } + + if( rc==SQLITE_OK ){ + size_t iPK = sizeof(sqlite3_value*)*p->nCol*2; + memset(p->tblhdr.aBuf, 0, iPK); + memcpy(&p->tblhdr.aBuf[iPK], &p->in.aData[p->in.iNext], nCopy); + p->in.iNext += nCopy; + } + + p->apValue = (sqlite3_value**)p->tblhdr.aBuf; + if( p->apValue==0 ){ + p->abPK = 0; + p->zTab = 0; + }else{ + p->abPK = (u8*)&p->apValue[p->nCol*2]; + p->zTab = p->abPK ? (char*)&p->abPK[p->nCol] : 0; + } + return (p->rc = rc); +} + +/* +** Advance the changeset iterator to the next change. The differences between +** this function and sessionChangesetNext() are that +** +** * If pbEmpty is not NULL and the change is a no-op UPDATE (an UPDATE +** that modifies no columns), this function sets (*pbEmpty) to 1. +** +** * If the iterator is configured to skip no-op UPDATEs, +** sessionChangesetNext() does that. This function does not. +*/ +static int sessionChangesetNextOne( + sqlite3_changeset_iter *p, /* Changeset iterator */ + u8 **paRec, /* If non-NULL, store record pointer here */ + int *pnRec, /* If non-NULL, store size of record here */ + int *pbNew, /* If non-NULL, true if new table */ + int *pbEmpty +){ + int i; + u8 op; + + assert( (paRec==0 && pnRec==0) || (paRec && pnRec) ); + assert( pbEmpty==0 || *pbEmpty==0 ); + + /* If the iterator is in the error-state, return immediately. */ + if( p->rc!=SQLITE_OK ) return p->rc; + + /* Free the current contents of p->apValue[], if any. */ + if( p->apValue ){ + for(i=0; i<p->nCol*2; i++){ + sqlite3ValueFree(p->apValue[i]); + } + memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2); + } + + /* Make sure the buffer contains at least 10 bytes of input data, or all + ** remaining data if there are less than 10 bytes available. This is + ** sufficient either for the 'T' or 'P' byte and the varint that follows + ** it, or for the two single byte values otherwise. */ + p->rc = sessionInputBuffer(&p->in, 2); + if( p->rc!=SQLITE_OK ) return p->rc; + + /* If the iterator is already at the end of the changeset, return DONE. */ + if( p->in.iNext>=p->in.nData ){ + return SQLITE_DONE; + } + + sessionDiscardData(&p->in); + p->in.iCurrent = p->in.iNext; + + op = p->in.aData[p->in.iNext++]; + while( op=='T' || op=='P' ){ + if( pbNew ) *pbNew = 1; + p->bPatchset = (op=='P'); + if( sessionChangesetReadTblhdr(p) ) return p->rc; + if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc; + p->in.iCurrent = p->in.iNext; + if( p->in.iNext>=p->in.nData ) return SQLITE_DONE; + op = p->in.aData[p->in.iNext++]; + } + + if( p->zTab==0 || (p->bPatchset && p->bInvert) ){ + /* The first record in the changeset is not a table header. Must be a + ** corrupt changeset. */ + assert( p->in.iNext==1 || p->zTab ); + return (p->rc = SQLITE_CORRUPT_BKPT); + } + + p->op = op; + p->bIndirect = p->in.aData[p->in.iNext++]; + if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ + return (p->rc = SQLITE_CORRUPT_BKPT); + } + + if( paRec ){ + int nVal; /* Number of values to buffer */ + if( p->bPatchset==0 && op==SQLITE_UPDATE ){ + nVal = p->nCol * 2; + }else if( p->bPatchset && op==SQLITE_DELETE ){ + nVal = 0; + for(i=0; i<p->nCol; i++) if( p->abPK[i] ) nVal++; + }else{ + nVal = p->nCol; + } + p->rc = sessionChangesetBufferRecord(&p->in, nVal, pnRec); + if( p->rc!=SQLITE_OK ) return p->rc; + *paRec = &p->in.aData[p->in.iNext]; + p->in.iNext += *pnRec; + }else{ + sqlite3_value **apOld = (p->bInvert ? &p->apValue[p->nCol] : p->apValue); + sqlite3_value **apNew = (p->bInvert ? p->apValue : &p->apValue[p->nCol]); + + /* If this is an UPDATE or DELETE, read the old.* record. */ + if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){ + u8 *abPK = p->bPatchset ? p->abPK : 0; + p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld, 0); + if( p->rc!=SQLITE_OK ) return p->rc; + } + + /* If this is an INSERT or UPDATE, read the new.* record. */ + if( p->op!=SQLITE_DELETE ){ + p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew, pbEmpty); + if( p->rc!=SQLITE_OK ) return p->rc; + } + + if( (p->bPatchset || p->bInvert) && p->op==SQLITE_UPDATE ){ + /* If this is an UPDATE that is part of a patchset, then all PK and + ** modified fields are present in the new.* record. The old.* record + ** is currently completely empty. This block shifts the PK fields from + ** new.* to old.*, to accommodate the code that reads these arrays. */ + for(i=0; i<p->nCol; i++){ + assert( p->bPatchset==0 || p->apValue[i]==0 ); + if( p->abPK[i] ){ + assert( p->apValue[i]==0 ); + p->apValue[i] = p->apValue[i+p->nCol]; + if( p->apValue[i]==0 ) return (p->rc = SQLITE_CORRUPT_BKPT); + p->apValue[i+p->nCol] = 0; + } + } + }else if( p->bInvert ){ + if( p->op==SQLITE_INSERT ) p->op = SQLITE_DELETE; + else if( p->op==SQLITE_DELETE ) p->op = SQLITE_INSERT; + } + + /* If this is an UPDATE that is part of a changeset, then check that + ** there are no fields in the old.* record that are not (a) PK fields, + ** or (b) also present in the new.* record. + ** + ** Such records are technically corrupt, but the rebaser was at one + ** point generating them. Under most circumstances this is benign, but + ** can cause spurious SQLITE_RANGE errors when applying the changeset. */ + if( p->bPatchset==0 && p->op==SQLITE_UPDATE){ + for(i=0; i<p->nCol; i++){ + if( p->abPK[i]==0 && p->apValue[i+p->nCol]==0 ){ + sqlite3ValueFree(p->apValue[i]); + p->apValue[i] = 0; + } + } + } + } + + return SQLITE_ROW; +} + +/* +** Advance the changeset iterator to the next change. +** +** If both paRec and pnRec are NULL, then this function works like the public +** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the +** sqlite3changeset_new() and old() APIs may be used to query for values. +** +** Otherwise, if paRec and pnRec are not NULL, then a pointer to the change +** record is written to *paRec before returning and the number of bytes in +** the record to *pnRec. +** +** Either way, this function returns SQLITE_ROW if the iterator is +** successfully advanced to the next change in the changeset, an SQLite +** error code if an error occurs, or SQLITE_DONE if there are no further +** changes in the changeset. +*/ +static int sessionChangesetNext( + sqlite3_changeset_iter *p, /* Changeset iterator */ + u8 **paRec, /* If non-NULL, store record pointer here */ + int *pnRec, /* If non-NULL, store size of record here */ + int *pbNew /* If non-NULL, true if new table */ +){ + int bEmpty; + int rc; + do { + bEmpty = 0; + rc = sessionChangesetNextOne(p, paRec, pnRec, pbNew, &bEmpty); + }while( rc==SQLITE_ROW && p->bSkipEmpty && bEmpty); + return rc; +} + +/* +** Advance an iterator created by sqlite3changeset_start() to the next +** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE +** or SQLITE_CORRUPT. +** +** This function may not be called on iterators passed to a conflict handler +** callback by changeset_apply(). +*/ +SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *p){ + return sessionChangesetNext(p, 0, 0, 0); +} + +/* +** The following function extracts information on the current change +** from a changeset iterator. It may only be called after changeset_next() +** has returned SQLITE_ROW. +*/ +SQLITE_API int sqlite3changeset_op( + sqlite3_changeset_iter *pIter, /* Iterator handle */ + const char **pzTab, /* OUT: Pointer to table name */ + int *pnCol, /* OUT: Number of columns in table */ + int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ + int *pbIndirect /* OUT: True if change is indirect */ +){ + *pOp = pIter->op; + *pnCol = pIter->nCol; + *pzTab = pIter->zTab; + if( pbIndirect ) *pbIndirect = pIter->bIndirect; + return SQLITE_OK; +} + +/* +** Return information regarding the PRIMARY KEY and number of columns in +** the database table affected by the change that pIter currently points +** to. This function may only be called after changeset_next() returns +** SQLITE_ROW. +*/ +SQLITE_API int sqlite3changeset_pk( + sqlite3_changeset_iter *pIter, /* Iterator object */ + unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */ + int *pnCol /* OUT: Number of entries in output array */ +){ + *pabPK = pIter->abPK; + if( pnCol ) *pnCol = pIter->nCol; + return SQLITE_OK; +} + +/* +** This function may only be called while the iterator is pointing to an +** SQLITE_UPDATE or SQLITE_DELETE change (see sqlite3changeset_op()). +** Otherwise, SQLITE_MISUSE is returned. +** +** It sets *ppValue to point to an sqlite3_value structure containing the +** iVal'th value in the old.* record. Or, if that particular value is not +** included in the record (because the change is an UPDATE and the field +** was not modified and is not a PK column), set *ppValue to NULL. +** +** If value iVal is out-of-range, SQLITE_RANGE is returned and *ppValue is +** not modified. Otherwise, SQLITE_OK. +*/ +SQLITE_API int sqlite3changeset_old( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Index of old.* value to retrieve */ + sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */ +){ + if( pIter->op!=SQLITE_UPDATE && pIter->op!=SQLITE_DELETE ){ + return SQLITE_MISUSE; + } + if( iVal<0 || iVal>=pIter->nCol ){ + return SQLITE_RANGE; + } + *ppValue = pIter->apValue[iVal]; + return SQLITE_OK; +} + +/* +** This function may only be called while the iterator is pointing to an +** SQLITE_UPDATE or SQLITE_INSERT change (see sqlite3changeset_op()). +** Otherwise, SQLITE_MISUSE is returned. +** +** It sets *ppValue to point to an sqlite3_value structure containing the +** iVal'th value in the new.* record. Or, if that particular value is not +** included in the record (because the change is an UPDATE and the field +** was not modified), set *ppValue to NULL. +** +** If value iVal is out-of-range, SQLITE_RANGE is returned and *ppValue is +** not modified. Otherwise, SQLITE_OK. +*/ +SQLITE_API int sqlite3changeset_new( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Index of new.* value to retrieve */ + sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */ +){ + if( pIter->op!=SQLITE_UPDATE && pIter->op!=SQLITE_INSERT ){ + return SQLITE_MISUSE; + } + if( iVal<0 || iVal>=pIter->nCol ){ + return SQLITE_RANGE; + } + *ppValue = pIter->apValue[pIter->nCol+iVal]; + return SQLITE_OK; +} + +/* +** The following two macros are used internally. They are similar to the +** sqlite3changeset_new() and sqlite3changeset_old() functions, except that +** they omit all error checking and return a pointer to the requested value. +*/ +#define sessionChangesetNew(pIter, iVal) (pIter)->apValue[(pIter)->nCol+(iVal)] +#define sessionChangesetOld(pIter, iVal) (pIter)->apValue[(iVal)] + +/* +** This function may only be called with a changeset iterator that has been +** passed to an SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT +** conflict-handler function. Otherwise, SQLITE_MISUSE is returned. +** +** If successful, *ppValue is set to point to an sqlite3_value structure +** containing the iVal'th value of the conflicting record. +** +** If value iVal is out-of-range or some other error occurs, an SQLite error +** code is returned. Otherwise, SQLITE_OK. +*/ +SQLITE_API int sqlite3changeset_conflict( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Index of conflict record value to fetch */ + sqlite3_value **ppValue /* OUT: Value from conflicting row */ +){ + if( !pIter->pConflict ){ + return SQLITE_MISUSE; + } + if( iVal<0 || iVal>=pIter->nCol ){ + return SQLITE_RANGE; + } + *ppValue = sqlite3_column_value(pIter->pConflict, iVal); + return SQLITE_OK; +} + +/* +** This function may only be called with an iterator passed to an +** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case +** it sets the output variable to the total number of known foreign key +** violations in the destination database and returns SQLITE_OK. +** +** In all other cases this function returns SQLITE_MISUSE. +*/ +SQLITE_API int sqlite3changeset_fk_conflicts( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int *pnOut /* OUT: Number of FK violations */ +){ + if( pIter->pConflict || pIter->apValue ){ + return SQLITE_MISUSE; + } + *pnOut = pIter->nCol; + return SQLITE_OK; +} + + +/* +** Finalize an iterator allocated with sqlite3changeset_start(). +** +** This function may not be called on iterators passed to a conflict handler +** callback by changeset_apply(). +*/ +SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter *p){ + int rc = SQLITE_OK; + if( p ){ + int i; /* Used to iterate through p->apValue[] */ + rc = p->rc; + if( p->apValue ){ + for(i=0; i<p->nCol*2; i++) sqlite3ValueFree(p->apValue[i]); + } + sqlite3_free(p->tblhdr.aBuf); + sqlite3_free(p->in.buf.aBuf); + sqlite3_free(p); + } + return rc; +} + +static int sessionChangesetInvert( + SessionInput *pInput, /* Input changeset */ + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut, + int *pnInverted, /* OUT: Number of bytes in output changeset */ + void **ppInverted /* OUT: Inverse of pChangeset */ +){ + int rc = SQLITE_OK; /* Return value */ + SessionBuffer sOut; /* Output buffer */ + int nCol = 0; /* Number of cols in current table */ + u8 *abPK = 0; /* PK array for current table */ + sqlite3_value **apVal = 0; /* Space for values for UPDATE inversion */ + SessionBuffer sPK = {0, 0, 0}; /* PK array for current table */ + + /* Initialize the output buffer */ + memset(&sOut, 0, sizeof(SessionBuffer)); + + /* Zero the output variables in case an error occurs. */ + if( ppInverted ){ + *ppInverted = 0; + *pnInverted = 0; + } + + while( 1 ){ + u8 eType; + + /* Test for EOF. */ + if( (rc = sessionInputBuffer(pInput, 2)) ) goto finished_invert; + if( pInput->iNext>=pInput->nData ) break; + eType = pInput->aData[pInput->iNext]; + + switch( eType ){ + case 'T': { + /* A 'table' record consists of: + ** + ** * A constant 'T' character, + ** * Number of columns in said table (a varint), + ** * An array of nCol bytes (sPK), + ** * A nul-terminated table name. + */ + int nByte; + int nVar; + pInput->iNext++; + if( (rc = sessionChangesetBufferTblhdr(pInput, &nByte)) ){ + goto finished_invert; + } + nVar = sessionVarintGet(&pInput->aData[pInput->iNext], &nCol); + sPK.nBuf = 0; + sessionAppendBlob(&sPK, &pInput->aData[pInput->iNext+nVar], nCol, &rc); + sessionAppendByte(&sOut, eType, &rc); + sessionAppendBlob(&sOut, &pInput->aData[pInput->iNext], nByte, &rc); + if( rc ) goto finished_invert; + + pInput->iNext += nByte; + sqlite3_free(apVal); + apVal = 0; + abPK = sPK.aBuf; + break; + } + + case SQLITE_INSERT: + case SQLITE_DELETE: { + int nByte; + int bIndirect = pInput->aData[pInput->iNext+1]; + int eType2 = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE); + pInput->iNext += 2; + assert( rc==SQLITE_OK ); + rc = sessionChangesetBufferRecord(pInput, nCol, &nByte); + sessionAppendByte(&sOut, eType2, &rc); + sessionAppendByte(&sOut, bIndirect, &rc); + sessionAppendBlob(&sOut, &pInput->aData[pInput->iNext], nByte, &rc); + pInput->iNext += nByte; + if( rc ) goto finished_invert; + break; + } + + case SQLITE_UPDATE: { + int iCol; + + if( 0==apVal ){ + apVal = (sqlite3_value **)sqlite3_malloc64(sizeof(apVal[0])*nCol*2); + if( 0==apVal ){ + rc = SQLITE_NOMEM; + goto finished_invert; + } + memset(apVal, 0, sizeof(apVal[0])*nCol*2); + } + + /* Write the header for the new UPDATE change. Same as the original. */ + sessionAppendByte(&sOut, eType, &rc); + sessionAppendByte(&sOut, pInput->aData[pInput->iNext+1], &rc); + + /* Read the old.* and new.* records for the update change. */ + pInput->iNext += 2; + rc = sessionReadRecord(pInput, nCol, 0, &apVal[0], 0); + if( rc==SQLITE_OK ){ + rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol], 0); + } + + /* Write the new old.* record. Consists of the PK columns from the + ** original old.* record, and the other values from the original + ** new.* record. */ + for(iCol=0; iCol<nCol; iCol++){ + sqlite3_value *pVal = apVal[iCol + (abPK[iCol] ? 0 : nCol)]; + sessionAppendValue(&sOut, pVal, &rc); + } + + /* Write the new new.* record. Consists of a copy of all values + ** from the original old.* record, except for the PK columns, which + ** are set to "undefined". */ + for(iCol=0; iCol<nCol; iCol++){ + sqlite3_value *pVal = (abPK[iCol] ? 0 : apVal[iCol]); + sessionAppendValue(&sOut, pVal, &rc); + } + + for(iCol=0; iCol<nCol*2; iCol++){ + sqlite3ValueFree(apVal[iCol]); + } + memset(apVal, 0, sizeof(apVal[0])*nCol*2); + if( rc!=SQLITE_OK ){ + goto finished_invert; + } + + break; + } + + default: + rc = SQLITE_CORRUPT_BKPT; + goto finished_invert; + } + + assert( rc==SQLITE_OK ); + if( xOutput && sOut.nBuf>=sessions_strm_chunk_size ){ + rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); + sOut.nBuf = 0; + if( rc!=SQLITE_OK ) goto finished_invert; + } + } + + assert( rc==SQLITE_OK ); + if( pnInverted && ALWAYS(ppInverted) ){ + *pnInverted = sOut.nBuf; + *ppInverted = sOut.aBuf; + sOut.aBuf = 0; + }else if( sOut.nBuf>0 && ALWAYS(xOutput!=0) ){ + rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); + } + + finished_invert: + sqlite3_free(sOut.aBuf); + sqlite3_free(apVal); + sqlite3_free(sPK.aBuf); + return rc; +} + + +/* +** Invert a changeset object. +*/ +SQLITE_API int sqlite3changeset_invert( + int nChangeset, /* Number of bytes in input */ + const void *pChangeset, /* Input changeset */ + int *pnInverted, /* OUT: Number of bytes in output changeset */ + void **ppInverted /* OUT: Inverse of pChangeset */ +){ + SessionInput sInput; + + /* Set up the input stream */ + memset(&sInput, 0, sizeof(SessionInput)); + sInput.nData = nChangeset; + sInput.aData = (u8*)pChangeset; + + return sessionChangesetInvert(&sInput, 0, 0, pnInverted, ppInverted); +} + +/* +** Streaming version of sqlite3changeset_invert(). +*/ +SQLITE_API int sqlite3changeset_invert_strm( + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + SessionInput sInput; + int rc; + + /* Set up the input stream */ + memset(&sInput, 0, sizeof(SessionInput)); + sInput.xInput = xInput; + sInput.pIn = pIn; + + rc = sessionChangesetInvert(&sInput, xOutput, pOut, 0, 0); + sqlite3_free(sInput.buf.aBuf); + return rc; +} + + +typedef struct SessionUpdate SessionUpdate; +struct SessionUpdate { + sqlite3_stmt *pStmt; + u32 *aMask; + SessionUpdate *pNext; +}; + +typedef struct SessionApplyCtx SessionApplyCtx; +struct SessionApplyCtx { + sqlite3 *db; + sqlite3_stmt *pDelete; /* DELETE statement */ + sqlite3_stmt *pInsert; /* INSERT statement */ + sqlite3_stmt *pSelect; /* SELECT statement */ + int nCol; /* Size of azCol[] and abPK[] arrays */ + const char **azCol; /* Array of column names */ + u8 *abPK; /* Boolean array - true if column is in PK */ + u32 *aUpdateMask; /* Used by sessionUpdateFind */ + SessionUpdate *pUp; + int bStat1; /* True if table is sqlite_stat1 */ + int bDeferConstraints; /* True to defer constraints */ + int bInvertConstraints; /* Invert when iterating constraints buffer */ + SessionBuffer constraints; /* Deferred constraints are stored here */ + SessionBuffer rebase; /* Rebase information (if any) here */ + u8 bRebaseStarted; /* If table header is already in rebase */ + u8 bRebase; /* True to collect rebase information */ + u8 bIgnoreNoop; /* True to ignore no-op conflicts */ + int bRowid; +}; + +/* Number of prepared UPDATE statements to cache. */ +#define SESSION_UPDATE_CACHE_SZ 12 + +/* +** Find a prepared UPDATE statement suitable for the UPDATE step currently +** being visited by the iterator. The UPDATE is of the form: +** +** UPDATE tbl SET col = ?, col2 = ? WHERE pk1 IS ? AND pk2 IS ? +*/ +static int sessionUpdateFind( + sqlite3_changeset_iter *pIter, + SessionApplyCtx *p, + int bPatchset, + sqlite3_stmt **ppStmt +){ + int rc = SQLITE_OK; + SessionUpdate *pUp = 0; + int nCol = pIter->nCol; + int nU32 = (pIter->nCol+33)/32; + int ii; + + if( p->aUpdateMask==0 ){ + p->aUpdateMask = sqlite3_malloc(nU32*sizeof(u32)); + if( p->aUpdateMask==0 ){ + rc = SQLITE_NOMEM; + } + } + + if( rc==SQLITE_OK ){ + memset(p->aUpdateMask, 0, nU32*sizeof(u32)); + rc = SQLITE_CORRUPT; + for(ii=0; ii<pIter->nCol; ii++){ + if( sessionChangesetNew(pIter, ii) ){ + p->aUpdateMask[ii/32] |= (1<<(ii%32)); + rc = SQLITE_OK; + } + } + } + + if( rc==SQLITE_OK ){ + if( bPatchset ) p->aUpdateMask[nCol/32] |= (1<<(nCol%32)); + + if( p->pUp ){ + int nUp = 0; + SessionUpdate **pp = &p->pUp; + while( 1 ){ + nUp++; + if( 0==memcmp(p->aUpdateMask, (*pp)->aMask, nU32*sizeof(u32)) ){ + pUp = *pp; + *pp = pUp->pNext; + pUp->pNext = p->pUp; + p->pUp = pUp; + break; + } + + if( (*pp)->pNext ){ + pp = &(*pp)->pNext; + }else{ + if( nUp>=SESSION_UPDATE_CACHE_SZ ){ + sqlite3_finalize((*pp)->pStmt); + sqlite3_free(*pp); + *pp = 0; + } + break; + } + } + } + + if( pUp==0 ){ + int nByte = sizeof(SessionUpdate) * nU32*sizeof(u32); + int bStat1 = (sqlite3_stricmp(pIter->zTab, "sqlite_stat1")==0); + pUp = (SessionUpdate*)sqlite3_malloc(nByte); + if( pUp==0 ){ + rc = SQLITE_NOMEM; + }else{ + const char *zSep = ""; + SessionBuffer buf; + + memset(&buf, 0, sizeof(buf)); + pUp->aMask = (u32*)&pUp[1]; + memcpy(pUp->aMask, p->aUpdateMask, nU32*sizeof(u32)); + + sessionAppendStr(&buf, "UPDATE main.", &rc); + sessionAppendIdent(&buf, pIter->zTab, &rc); + sessionAppendStr(&buf, " SET ", &rc); + + /* Create the assignments part of the UPDATE */ + for(ii=0; ii<pIter->nCol; ii++){ + if( p->abPK[ii]==0 && sessionChangesetNew(pIter, ii) ){ + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, p->azCol[ii], &rc); + sessionAppendStr(&buf, " = ?", &rc); + sessionAppendInteger(&buf, ii*2+1, &rc); + zSep = ", "; + } + } + + /* Create the WHERE clause part of the UPDATE */ + zSep = ""; + sessionAppendStr(&buf, " WHERE ", &rc); + for(ii=0; ii<pIter->nCol; ii++){ + if( p->abPK[ii] || (bPatchset==0 && sessionChangesetOld(pIter, ii)) ){ + sessionAppendStr(&buf, zSep, &rc); + if( bStat1 && ii==1 ){ + assert( sqlite3_stricmp(p->azCol[ii], "idx")==0 ); + sessionAppendStr(&buf, + "idx IS CASE " + "WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL " + "ELSE ?4 END ", &rc + ); + }else{ + sessionAppendIdent(&buf, p->azCol[ii], &rc); + sessionAppendStr(&buf, " IS ?", &rc); + sessionAppendInteger(&buf, ii*2+2, &rc); + } + zSep = " AND "; + } + } + + if( rc==SQLITE_OK ){ + char *zSql = (char*)buf.aBuf; + rc = sqlite3_prepare_v2(p->db, zSql, buf.nBuf, &pUp->pStmt, 0); + } + + if( rc!=SQLITE_OK ){ + sqlite3_free(pUp); + pUp = 0; + }else{ + pUp->pNext = p->pUp; + p->pUp = pUp; + } + sqlite3_free(buf.aBuf); + } + } + } + + assert( (rc==SQLITE_OK)==(pUp!=0) ); + if( pUp ){ + *ppStmt = pUp->pStmt; + }else{ + *ppStmt = 0; + } + return rc; +} + +/* +** Free all cached UPDATE statements. +*/ +static void sessionUpdateFree(SessionApplyCtx *p){ + SessionUpdate *pUp; + SessionUpdate *pNext; + for(pUp=p->pUp; pUp; pUp=pNext){ + pNext = pUp->pNext; + sqlite3_finalize(pUp->pStmt); + sqlite3_free(pUp); + } + p->pUp = 0; + sqlite3_free(p->aUpdateMask); + p->aUpdateMask = 0; +} + +/* +** Formulate a statement to DELETE a row from database db. Assuming a table +** structure like this: +** +** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); +** +** The DELETE statement looks like this: +** +** DELETE FROM x WHERE a = :1 AND c = :3 AND (:5 OR b IS :2 AND d IS :4) +** +** Variable :5 (nCol+1) is a boolean. It should be set to 0 if we require +** matching b and d values, or 1 otherwise. The second case comes up if the +** conflict handler is invoked with NOTFOUND and returns CHANGESET_REPLACE. +** +** If successful, SQLITE_OK is returned and SessionApplyCtx.pDelete is left +** pointing to the prepared version of the SQL statement. +*/ +static int sessionDeleteRow( + sqlite3 *db, /* Database handle */ + const char *zTab, /* Table name */ + SessionApplyCtx *p /* Session changeset-apply context */ +){ + int i; + const char *zSep = ""; + int rc = SQLITE_OK; + SessionBuffer buf = {0, 0, 0}; + int nPk = 0; + + sessionAppendStr(&buf, "DELETE FROM main.", &rc); + sessionAppendIdent(&buf, zTab, &rc); + sessionAppendStr(&buf, " WHERE ", &rc); + + for(i=0; i<p->nCol; i++){ + if( p->abPK[i] ){ + nPk++; + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " = ?", &rc); + sessionAppendInteger(&buf, i+1, &rc); + zSep = " AND "; + } + } + + if( nPk<p->nCol ){ + sessionAppendStr(&buf, " AND (?", &rc); + sessionAppendInteger(&buf, p->nCol+1, &rc); + sessionAppendStr(&buf, " OR ", &rc); + + zSep = ""; + for(i=0; i<p->nCol; i++){ + if( !p->abPK[i] ){ + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " IS ?", &rc); + sessionAppendInteger(&buf, i+1, &rc); + zSep = "AND "; + } + } + sessionAppendStr(&buf, ")", &rc); + } + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0); + } + sqlite3_free(buf.aBuf); + + return rc; +} + +/* +** Formulate and prepare an SQL statement to query table zTab by primary +** key. Assuming the following table structure: +** +** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); +** +** The SELECT statement looks like this: +** +** SELECT * FROM x WHERE a = ?1 AND c = ?3 +** +** If successful, SQLITE_OK is returned and SessionApplyCtx.pSelect is left +** pointing to the prepared version of the SQL statement. +*/ +static int sessionSelectRow( + sqlite3 *db, /* Database handle */ + const char *zTab, /* Table name */ + SessionApplyCtx *p /* Session changeset-apply context */ +){ + /* TODO */ + return sessionSelectStmt(db, p->bIgnoreNoop, + "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect + ); +} + +/* +** Formulate and prepare an INSERT statement to add a record to table zTab. +** For example: +** +** INSERT INTO main."zTab" VALUES(?1, ?2, ?3 ...); +** +** If successful, SQLITE_OK is returned and SessionApplyCtx.pInsert is left +** pointing to the prepared version of the SQL statement. +*/ +static int sessionInsertRow( + sqlite3 *db, /* Database handle */ + const char *zTab, /* Table name */ + SessionApplyCtx *p /* Session changeset-apply context */ +){ + int rc = SQLITE_OK; + int i; + SessionBuffer buf = {0, 0, 0}; + + sessionAppendStr(&buf, "INSERT INTO main.", &rc); + sessionAppendIdent(&buf, zTab, &rc); + sessionAppendStr(&buf, "(", &rc); + for(i=0; i<p->nCol; i++){ + if( i!=0 ) sessionAppendStr(&buf, ", ", &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + } + + sessionAppendStr(&buf, ") VALUES(?", &rc); + for(i=1; i<p->nCol; i++){ + sessionAppendStr(&buf, ", ?", &rc); + } + sessionAppendStr(&buf, ")", &rc); + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0); + } + sqlite3_free(buf.aBuf); + return rc; +} + +static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){ + return sqlite3_prepare_v2(db, zSql, -1, pp, 0); +} + +/* +** Prepare statements for applying changes to the sqlite_stat1 table. +** These are similar to those created by sessionSelectRow(), +** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for +** other tables. +*/ +static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ + int rc = sessionSelectRow(db, "sqlite_stat1", p); + if( rc==SQLITE_OK ){ + rc = sessionPrepare(db, &p->pInsert, + "INSERT INTO main.sqlite_stat1 VALUES(?1, " + "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, " + "?3)" + ); + } + if( rc==SQLITE_OK ){ + rc = sessionPrepare(db, &p->pDelete, + "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS " + "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END " + "AND (?4 OR stat IS ?3)" + ); + } + return rc; +} + +/* +** A wrapper around sqlite3_bind_value() that detects an extra problem. +** See comments in the body of this function for details. +*/ +static int sessionBindValue( + sqlite3_stmt *pStmt, /* Statement to bind value to */ + int i, /* Parameter number to bind to */ + sqlite3_value *pVal /* Value to bind */ +){ + int eType = sqlite3_value_type(pVal); + /* COVERAGE: The (pVal->z==0) branch is never true using current versions + ** of SQLite. If a malloc fails in an sqlite3_value_xxx() function, either + ** the (pVal->z) variable remains as it was or the type of the value is + ** set to SQLITE_NULL. */ + if( (eType==SQLITE_TEXT || eType==SQLITE_BLOB) && pVal->z==0 ){ + /* This condition occurs when an earlier OOM in a call to + ** sqlite3_value_text() or sqlite3_value_blob() (perhaps from within + ** a conflict-handler) has zeroed the pVal->z pointer. Return NOMEM. */ + return SQLITE_NOMEM; + } + return sqlite3_bind_value(pStmt, i, pVal); +} + +/* +** Iterator pIter must point to an SQLITE_INSERT entry. This function +** transfers new.* values from the current iterator entry to statement +** pStmt. The table being inserted into has nCol columns. +** +** New.* value $i from the iterator is bound to variable ($i+1) of +** statement pStmt. If parameter abPK is NULL, all values from 0 to (nCol-1) +** are transfered to the statement. Otherwise, if abPK is not NULL, it points +** to an array nCol elements in size. In this case only those values for +** which abPK[$i] is true are read from the iterator and bound to the +** statement. +** +** An SQLite error code is returned if an error occurs. Otherwise, SQLITE_OK. +*/ +static int sessionBindRow( + sqlite3_changeset_iter *pIter, /* Iterator to read values from */ + int(*xValue)(sqlite3_changeset_iter *, int, sqlite3_value **), + int nCol, /* Number of columns */ + u8 *abPK, /* If not NULL, bind only if true */ + sqlite3_stmt *pStmt /* Bind values to this statement */ +){ + int i; + int rc = SQLITE_OK; + + /* Neither sqlite3changeset_old or sqlite3changeset_new can fail if the + ** argument iterator points to a suitable entry. Make sure that xValue + ** is one of these to guarantee that it is safe to ignore the return + ** in the code below. */ + assert( xValue==sqlite3changeset_old || xValue==sqlite3changeset_new ); + + for(i=0; rc==SQLITE_OK && i<nCol; i++){ + if( !abPK || abPK[i] ){ + sqlite3_value *pVal = 0; + (void)xValue(pIter, i, &pVal); + if( pVal==0 ){ + /* The value in the changeset was "undefined". This indicates a + ** corrupt changeset blob. */ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = sessionBindValue(pStmt, i+1, pVal); + } + } + } + return rc; +} + +/* +** SQL statement pSelect is as generated by the sessionSelectRow() function. +** This function binds the primary key values from the change that changeset +** iterator pIter points to to the SELECT and attempts to seek to the table +** entry. If a row is found, the SELECT statement left pointing at the row +** and SQLITE_ROW is returned. Otherwise, if no row is found and no error +** has occured, the statement is reset and SQLITE_OK is returned. If an +** error occurs, the statement is reset and an SQLite error code is returned. +** +** If this function returns SQLITE_ROW, the caller must eventually reset() +** statement pSelect. If any other value is returned, the statement does +** not require a reset(). +** +** If the iterator currently points to an INSERT record, bind values from the +** new.* record to the SELECT statement. Or, if it points to a DELETE or +** UPDATE, bind values from the old.* record. +*/ +static int sessionSeekToRow( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + SessionApplyCtx *p +){ + sqlite3_stmt *pSelect = p->pSelect; + int rc; /* Return code */ + int nCol; /* Number of columns in table */ + int op; /* Changset operation (SQLITE_UPDATE etc.) */ + const char *zDummy; /* Unused */ + + sqlite3_clear_bindings(pSelect); + sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); + rc = sessionBindRow(pIter, + op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old, + nCol, p->abPK, pSelect + ); + + if( op!=SQLITE_DELETE && p->bIgnoreNoop ){ + int ii; + for(ii=0; rc==SQLITE_OK && ii<nCol; ii++){ + if( p->abPK[ii]==0 ){ + sqlite3_value *pVal = 0; + sqlite3changeset_new(pIter, ii, &pVal); + sqlite3_bind_int(pSelect, ii+1+nCol, (pVal==0)); + if( pVal ) rc = sessionBindValue(pSelect, ii+1, pVal); + } + } + } + + if( rc==SQLITE_OK ){ + rc = sqlite3_step(pSelect); + if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect); + } + + return rc; +} + +/* +** This function is called from within sqlite3changeset_apply_v2() when +** a conflict is encountered and resolved using conflict resolution +** mode eType (either SQLITE_CHANGESET_OMIT or SQLITE_CHANGESET_REPLACE).. +** It adds a conflict resolution record to the buffer in +** SessionApplyCtx.rebase, which will eventually be returned to the caller +** of apply_v2() as the "rebase" buffer. +** +** Return SQLITE_OK if successful, or an SQLite error code otherwise. +*/ +static int sessionRebaseAdd( + SessionApplyCtx *p, /* Apply context */ + int eType, /* Conflict resolution (OMIT or REPLACE) */ + sqlite3_changeset_iter *pIter /* Iterator pointing at current change */ +){ + int rc = SQLITE_OK; + if( p->bRebase ){ + int i; + int eOp = pIter->op; + if( p->bRebaseStarted==0 ){ + /* Append a table-header to the rebase buffer */ + const char *zTab = pIter->zTab; + sessionAppendByte(&p->rebase, 'T', &rc); + sessionAppendVarint(&p->rebase, p->nCol, &rc); + sessionAppendBlob(&p->rebase, p->abPK, p->nCol, &rc); + sessionAppendBlob(&p->rebase, (u8*)zTab, (int)strlen(zTab)+1, &rc); + p->bRebaseStarted = 1; + } + + assert( eType==SQLITE_CHANGESET_REPLACE||eType==SQLITE_CHANGESET_OMIT ); + assert( eOp==SQLITE_DELETE || eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE ); + + sessionAppendByte(&p->rebase, + (eOp==SQLITE_DELETE ? SQLITE_DELETE : SQLITE_INSERT), &rc + ); + sessionAppendByte(&p->rebase, (eType==SQLITE_CHANGESET_REPLACE), &rc); + for(i=0; i<p->nCol; i++){ + sqlite3_value *pVal = 0; + if( eOp==SQLITE_DELETE || (eOp==SQLITE_UPDATE && p->abPK[i]) ){ + sqlite3changeset_old(pIter, i, &pVal); + }else{ + sqlite3changeset_new(pIter, i, &pVal); + } + sessionAppendValue(&p->rebase, pVal, &rc); + } + } + return rc; +} + +/* +** Invoke the conflict handler for the change that the changeset iterator +** currently points to. +** +** Argument eType must be either CHANGESET_DATA or CHANGESET_CONFLICT. +** If argument pbReplace is NULL, then the type of conflict handler invoked +** depends solely on eType, as follows: +** +** eType value Value passed to xConflict +** ------------------------------------------------- +** CHANGESET_DATA CHANGESET_NOTFOUND +** CHANGESET_CONFLICT CHANGESET_CONSTRAINT +** +** Or, if pbReplace is not NULL, then an attempt is made to find an existing +** record with the same primary key as the record about to be deleted, updated +** or inserted. If such a record can be found, it is available to the conflict +** handler as the "conflicting" record. In this case the type of conflict +** handler invoked is as follows: +** +** eType value PK Record found? Value passed to xConflict +** ---------------------------------------------------------------- +** CHANGESET_DATA Yes CHANGESET_DATA +** CHANGESET_DATA No CHANGESET_NOTFOUND +** CHANGESET_CONFLICT Yes CHANGESET_CONFLICT +** CHANGESET_CONFLICT No CHANGESET_CONSTRAINT +** +** If pbReplace is not NULL, and a record with a matching PK is found, and +** the conflict handler function returns SQLITE_CHANGESET_REPLACE, *pbReplace +** is set to non-zero before returning SQLITE_OK. +** +** If the conflict handler returns SQLITE_CHANGESET_ABORT, SQLITE_ABORT is +** returned. Or, if the conflict handler returns an invalid value, +** SQLITE_MISUSE. If the conflict handler returns SQLITE_CHANGESET_OMIT, +** this function returns SQLITE_OK. +*/ +static int sessionConflictHandler( + int eType, /* Either CHANGESET_DATA or CONFLICT */ + SessionApplyCtx *p, /* changeset_apply() context */ + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int(*xConflict)(void *, int, sqlite3_changeset_iter*), + void *pCtx, /* First argument for conflict handler */ + int *pbReplace /* OUT: Set to true if PK row is found */ +){ + int res = 0; /* Value returned by conflict handler */ + int rc; + int nCol; + int op; + const char *zDummy; + + sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); + + assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA ); + assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT ); + assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND ); + + /* Bind the new.* PRIMARY KEY values to the SELECT statement. */ + if( pbReplace ){ + rc = sessionSeekToRow(pIter, p); + }else{ + rc = SQLITE_OK; + } + + if( rc==SQLITE_ROW ){ + /* There exists another row with the new.* primary key. */ + if( p->bIgnoreNoop + && sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) + ){ + res = SQLITE_CHANGESET_OMIT; + }else{ + pIter->pConflict = p->pSelect; + res = xConflict(pCtx, eType, pIter); + pIter->pConflict = 0; + } + rc = sqlite3_reset(p->pSelect); + }else if( rc==SQLITE_OK ){ + if( p->bDeferConstraints && eType==SQLITE_CHANGESET_CONFLICT ){ + /* Instead of invoking the conflict handler, append the change blob + ** to the SessionApplyCtx.constraints buffer. */ + u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent]; + int nBlob = pIter->in.iNext - pIter->in.iCurrent; + sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc); + return SQLITE_OK; + }else{ + /* No other row with the new.* primary key. */ + res = xConflict(pCtx, eType+1, pIter); + if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE; + } + } + + if( rc==SQLITE_OK ){ + switch( res ){ + case SQLITE_CHANGESET_REPLACE: + assert( pbReplace ); + *pbReplace = 1; + break; + + case SQLITE_CHANGESET_OMIT: + break; + + case SQLITE_CHANGESET_ABORT: + rc = SQLITE_ABORT; + break; + + default: + rc = SQLITE_MISUSE; + break; + } + if( rc==SQLITE_OK ){ + rc = sessionRebaseAdd(p, res, pIter); + } + } + + return rc; +} + +/* +** Attempt to apply the change that the iterator passed as the first argument +** currently points to to the database. If a conflict is encountered, invoke +** the conflict handler callback. +** +** If argument pbRetry is NULL, then ignore any CHANGESET_DATA conflict. If +** one is encountered, update or delete the row with the matching primary key +** instead. Or, if pbRetry is not NULL and a CHANGESET_DATA conflict occurs, +** invoke the conflict handler. If it returns CHANGESET_REPLACE, set *pbRetry +** to true before returning. In this case the caller will invoke this function +** again, this time with pbRetry set to NULL. +** +** If argument pbReplace is NULL and a CHANGESET_CONFLICT conflict is +** encountered invoke the conflict handler with CHANGESET_CONSTRAINT instead. +** Or, if pbReplace is not NULL, invoke it with CHANGESET_CONFLICT. If such +** an invocation returns SQLITE_CHANGESET_REPLACE, set *pbReplace to true +** before retrying. In this case the caller attempts to remove the conflicting +** row before invoking this function again, this time with pbReplace set +** to NULL. +** +** If any conflict handler returns SQLITE_CHANGESET_ABORT, this function +** returns SQLITE_ABORT. Otherwise, if no error occurs, SQLITE_OK is +** returned. +*/ +static int sessionApplyOneOp( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + SessionApplyCtx *p, /* changeset_apply() context */ + int(*xConflict)(void *, int, sqlite3_changeset_iter *), + void *pCtx, /* First argument for the conflict handler */ + int *pbReplace, /* OUT: True to remove PK row and retry */ + int *pbRetry /* OUT: True to retry. */ +){ + const char *zDummy; + int op; + int nCol; + int rc = SQLITE_OK; + + assert( p->pDelete && p->pInsert && p->pSelect ); + assert( p->azCol && p->abPK ); + assert( !pbReplace || *pbReplace==0 ); + + sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); + + if( op==SQLITE_DELETE ){ + + /* Bind values to the DELETE statement. If conflict handling is required, + ** bind values for all columns and set bound variable (nCol+1) to true. + ** Or, if conflict handling is not required, bind just the PK column + ** values and, if it exists, set (nCol+1) to false. Conflict handling + ** is not required if: + ** + ** * this is a patchset, or + ** * (pbRetry==0), or + ** * all columns of the table are PK columns (in this case there is + ** no (nCol+1) variable to bind to). + */ + u8 *abPK = (pIter->bPatchset ? p->abPK : 0); + rc = sessionBindRow(pIter, sqlite3changeset_old, nCol, abPK, p->pDelete); + if( rc==SQLITE_OK && sqlite3_bind_parameter_count(p->pDelete)>nCol ){ + rc = sqlite3_bind_int(p->pDelete, nCol+1, (pbRetry==0 || abPK)); + } + if( rc!=SQLITE_OK ) return rc; + + sqlite3_step(p->pDelete); + rc = sqlite3_reset(p->pDelete); + if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 && p->bIgnoreNoop==0 ){ + rc = sessionConflictHandler( + SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry + ); + }else if( (rc&0xff)==SQLITE_CONSTRAINT ){ + rc = sessionConflictHandler( + SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0 + ); + } + + }else if( op==SQLITE_UPDATE ){ + int i; + sqlite3_stmt *pUp = 0; + int bPatchset = (pbRetry==0 || pIter->bPatchset); + + rc = sessionUpdateFind(pIter, p, bPatchset, &pUp); + + /* Bind values to the UPDATE statement. */ + for(i=0; rc==SQLITE_OK && i<nCol; i++){ + sqlite3_value *pOld = sessionChangesetOld(pIter, i); + sqlite3_value *pNew = sessionChangesetNew(pIter, i); + if( p->abPK[i] || (bPatchset==0 && pOld) ){ + rc = sessionBindValue(pUp, i*2+2, pOld); + } + if( rc==SQLITE_OK && pNew ){ + rc = sessionBindValue(pUp, i*2+1, pNew); + } + } + if( rc!=SQLITE_OK ) return rc; + + /* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict, + ** the result will be SQLITE_OK with 0 rows modified. */ + sqlite3_step(pUp); + rc = sqlite3_reset(pUp); + + if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ + /* A NOTFOUND or DATA error. Search the table to see if it contains + ** a row with a matching primary key. If so, this is a DATA conflict. + ** Otherwise, if there is no primary key match, it is a NOTFOUND. */ + + rc = sessionConflictHandler( + SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry + ); + + }else if( (rc&0xff)==SQLITE_CONSTRAINT ){ + /* This is always a CONSTRAINT conflict. */ + rc = sessionConflictHandler( + SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0 + ); + } + + }else{ + assert( op==SQLITE_INSERT ); + if( p->bStat1 ){ + /* Check if there is a conflicting row. For sqlite_stat1, this needs + ** to be done using a SELECT, as there is no PRIMARY KEY in the + ** database schema to throw an exception if a duplicate is inserted. */ + rc = sessionSeekToRow(pIter, p); + if( rc==SQLITE_ROW ){ + rc = SQLITE_CONSTRAINT; + sqlite3_reset(p->pSelect); + } + } + + if( rc==SQLITE_OK ){ + rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert); + if( rc!=SQLITE_OK ) return rc; + + sqlite3_step(p->pInsert); + rc = sqlite3_reset(p->pInsert); + } + + if( (rc&0xff)==SQLITE_CONSTRAINT ){ + rc = sessionConflictHandler( + SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace + ); + } + } + + return rc; +} + +/* +** Attempt to apply the change that the iterator passed as the first argument +** currently points to to the database. If a conflict is encountered, invoke +** the conflict handler callback. +** +** The difference between this function and sessionApplyOne() is that this +** function handles the case where the conflict-handler is invoked and +** returns SQLITE_CHANGESET_REPLACE - indicating that the change should be +** retried in some manner. +*/ +static int sessionApplyOneWithRetry( + sqlite3 *db, /* Apply change to "main" db of this handle */ + sqlite3_changeset_iter *pIter, /* Changeset iterator to read change from */ + SessionApplyCtx *pApply, /* Apply context */ + int(*xConflict)(void*, int, sqlite3_changeset_iter*), + void *pCtx /* First argument passed to xConflict */ +){ + int bReplace = 0; + int bRetry = 0; + int rc; + + rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry); + if( rc==SQLITE_OK ){ + /* If the bRetry flag is set, the change has not been applied due to an + ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and + ** a row with the correct PK is present in the db, but one or more other + ** fields do not contain the expected values) and the conflict handler + ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation, + ** but pass NULL as the final argument so that sessionApplyOneOp() ignores + ** the SQLITE_CHANGESET_DATA problem. */ + if( bRetry ){ + assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE ); + rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0); + } + + /* If the bReplace flag is set, the change is an INSERT that has not + ** been performed because the database already contains a row with the + ** specified primary key and the conflict handler returned + ** SQLITE_CHANGESET_REPLACE. In this case remove the conflicting row + ** before reattempting the INSERT. */ + else if( bReplace ){ + assert( pIter->op==SQLITE_INSERT ); + rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = sessionBindRow(pIter, + sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete); + sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1); + } + if( rc==SQLITE_OK ){ + sqlite3_step(pApply->pDelete); + rc = sqlite3_reset(pApply->pDelete); + } + if( rc==SQLITE_OK ){ + rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0); + } + } + } + + return rc; +} + +/* +** Retry the changes accumulated in the pApply->constraints buffer. +*/ +static int sessionRetryConstraints( + sqlite3 *db, + int bPatchset, + const char *zTab, + SessionApplyCtx *pApply, + int(*xConflict)(void*, int, sqlite3_changeset_iter*), + void *pCtx /* First argument passed to xConflict */ +){ + int rc = SQLITE_OK; + + while( pApply->constraints.nBuf ){ + sqlite3_changeset_iter *pIter2 = 0; + SessionBuffer cons = pApply->constraints; + memset(&pApply->constraints, 0, sizeof(SessionBuffer)); + + rc = sessionChangesetStart( + &pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints, 1 + ); + if( rc==SQLITE_OK ){ + size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*); + int rc2; + pIter2->bPatchset = bPatchset; + pIter2->zTab = (char*)zTab; + pIter2->nCol = pApply->nCol; + pIter2->abPK = pApply->abPK; + sessionBufferGrow(&pIter2->tblhdr, nByte, &rc); + pIter2->apValue = (sqlite3_value**)pIter2->tblhdr.aBuf; + if( rc==SQLITE_OK ) memset(pIter2->apValue, 0, nByte); + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter2) ){ + rc = sessionApplyOneWithRetry(db, pIter2, pApply, xConflict, pCtx); + } + + rc2 = sqlite3changeset_finalize(pIter2); + if( rc==SQLITE_OK ) rc = rc2; + } + assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 ); + + sqlite3_free(cons.aBuf); + if( rc!=SQLITE_OK ) break; + if( pApply->constraints.nBuf>=cons.nBuf ){ + /* No progress was made on the last round. */ + pApply->bDeferConstraints = 0; + } + } + + return rc; +} + +/* +** Argument pIter is a changeset iterator that has been initialized, but +** not yet passed to sqlite3changeset_next(). This function applies the +** changeset to the main database attached to handle "db". The supplied +** conflict handler callback is invoked to resolve any conflicts encountered +** while applying the change. +*/ +static int sessionChangesetApply( + sqlite3 *db, /* Apply change to "main" db of this handle */ + sqlite3_changeset_iter *pIter, /* Changeset to apply */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of fifth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, /* OUT: Rebase information */ + int flags /* SESSION_APPLY_XXX flags */ +){ + int schemaMismatch = 0; + int rc = SQLITE_OK; /* Return code */ + const char *zTab = 0; /* Name of current table */ + int nTab = 0; /* Result of sqlite3Strlen30(zTab) */ + SessionApplyCtx sApply; /* changeset_apply() context object */ + int bPatchset; + + assert( xConflict!=0 ); + + pIter->in.bNoDiscard = 1; + memset(&sApply, 0, sizeof(sApply)); + sApply.bRebase = (ppRebase && pnRebase); + sApply.bInvertConstraints = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); + sApply.bIgnoreNoop = !!(flags & SQLITE_CHANGESETAPPLY_IGNORENOOP); + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){ + rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(db, "PRAGMA defer_foreign_keys = 1", 0, 0, 0); + } + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){ + int nCol; + int op; + const char *zNew; + + sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0); + + if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){ + u8 *abPK; + + rc = sessionRetryConstraints( + db, pIter->bPatchset, zTab, &sApply, xConflict, pCtx + ); + if( rc!=SQLITE_OK ) break; + + sessionUpdateFree(&sApply); + sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ + sqlite3_finalize(sApply.pDelete); + sqlite3_finalize(sApply.pInsert); + sqlite3_finalize(sApply.pSelect); + sApply.db = db; + sApply.pDelete = 0; + sApply.pInsert = 0; + sApply.pSelect = 0; + sApply.nCol = 0; + sApply.azCol = 0; + sApply.abPK = 0; + sApply.bStat1 = 0; + sApply.bDeferConstraints = 1; + sApply.bRebaseStarted = 0; + sApply.bRowid = 0; + memset(&sApply.constraints, 0, sizeof(SessionBuffer)); + + /* If an xFilter() callback was specified, invoke it now. If the + ** xFilter callback returns zero, skip this table. If it returns + ** non-zero, proceed. */ + schemaMismatch = (xFilter && (0==xFilter(pCtx, zNew))); + if( schemaMismatch ){ + zTab = sqlite3_mprintf("%s", zNew); + if( zTab==0 ){ + rc = SQLITE_NOMEM; + break; + } + nTab = (int)strlen(zTab); + sApply.azCol = (const char **)zTab; + }else{ + int nMinCol = 0; + int i; + + sqlite3changeset_pk(pIter, &abPK, 0); + rc = sessionTableInfo(0, db, "main", zNew, + &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK, &sApply.bRowid + ); + if( rc!=SQLITE_OK ) break; + for(i=0; i<sApply.nCol; i++){ + if( sApply.abPK[i] ) nMinCol = i+1; + } + + if( sApply.nCol==0 ){ + schemaMismatch = 1; + sqlite3_log(SQLITE_SCHEMA, + "sqlite3changeset_apply(): no such table: %s", zTab + ); + } + else if( sApply.nCol<nCol ){ + schemaMismatch = 1; + sqlite3_log(SQLITE_SCHEMA, + "sqlite3changeset_apply(): table %s has %d columns, " + "expected %d or more", + zTab, sApply.nCol, nCol + ); + } + else if( nCol<nMinCol || memcmp(sApply.abPK, abPK, nCol)!=0 ){ + schemaMismatch = 1; + sqlite3_log(SQLITE_SCHEMA, "sqlite3changeset_apply(): " + "primary key mismatch for table %s", zTab + ); + } + else{ + sApply.nCol = nCol; + if( 0==sqlite3_stricmp(zTab, "sqlite_stat1") ){ + if( (rc = sessionStat1Sql(db, &sApply) ) ){ + break; + } + sApply.bStat1 = 1; + }else{ + if( (rc = sessionSelectRow(db, zTab, &sApply)) + || (rc = sessionDeleteRow(db, zTab, &sApply)) + || (rc = sessionInsertRow(db, zTab, &sApply)) + ){ + break; + } + sApply.bStat1 = 0; + } + } + nTab = sqlite3Strlen30(zTab); + } + } + + /* If there is a schema mismatch on the current table, proceed to the + ** next change. A log message has already been issued. */ + if( schemaMismatch ) continue; + + rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx); + } + + bPatchset = pIter->bPatchset; + if( rc==SQLITE_OK ){ + rc = sqlite3changeset_finalize(pIter); + }else{ + sqlite3changeset_finalize(pIter); + } + + if( rc==SQLITE_OK ){ + rc = sessionRetryConstraints(db, bPatchset, zTab, &sApply, xConflict, pCtx); + } + + if( rc==SQLITE_OK ){ + int nFk, notUsed; + sqlite3_db_status(db, SQLITE_DBSTATUS_DEFERRED_FKS, &nFk, &notUsed, 0); + if( nFk!=0 ){ + int res = SQLITE_CHANGESET_ABORT; + sqlite3_changeset_iter sIter; + memset(&sIter, 0, sizeof(sIter)); + sIter.nCol = nFk; + res = xConflict(pCtx, SQLITE_CHANGESET_FOREIGN_KEY, &sIter); + if( res!=SQLITE_CHANGESET_OMIT ){ + rc = SQLITE_CONSTRAINT; + } + } + } + sqlite3_exec(db, "PRAGMA defer_foreign_keys = 0", 0, 0, 0); + + if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){ + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0); + }else{ + sqlite3_exec(db, "ROLLBACK TO changeset_apply", 0, 0, 0); + sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0); + } + } + + assert( sApply.bRebase || sApply.rebase.nBuf==0 ); + if( rc==SQLITE_OK && bPatchset==0 && sApply.bRebase ){ + *ppRebase = (void*)sApply.rebase.aBuf; + *pnRebase = sApply.rebase.nBuf; + sApply.rebase.aBuf = 0; + } + sessionUpdateFree(&sApply); + sqlite3_finalize(sApply.pInsert); + sqlite3_finalize(sApply.pDelete); + sqlite3_finalize(sApply.pSelect); + sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ + sqlite3_free((char*)sApply.constraints.aBuf); + sqlite3_free((char*)sApply.rebase.aBuf); + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + return rc; +} + +/* +** Apply the changeset passed via pChangeset/nChangeset to the main +** database attached to handle "db". +*/ +SQLITE_API int sqlite3changeset_apply_v2( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ + int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); + int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1); + if( rc==SQLITE_OK ){ + rc = sessionChangesetApply( + db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags + ); + } + return rc; +} + +/* +** Apply the changeset passed via pChangeset/nChangeset to the main database +** attached to handle "db". Invoke the supplied conflict handler callback +** to resolve any conflicts encountered while applying the change. +*/ +SQLITE_API int sqlite3changeset_apply( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of fifth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +){ + return sqlite3changeset_apply_v2( + db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0, 0 + ); +} + +/* +** Apply the changeset passed via xInput/pIn to the main database +** attached to handle "db". Invoke the supplied conflict handler callback +** to resolve any conflicts encountered while applying the change. +*/ +SQLITE_API int sqlite3changeset_apply_v2_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ + int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); + int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse, 1); + if( rc==SQLITE_OK ){ + rc = sessionChangesetApply( + db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags + ); + } + return rc; +} +SQLITE_API int sqlite3changeset_apply_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +){ + return sqlite3changeset_apply_v2_strm( + db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0, 0 + ); +} + +/* +** sqlite3_changegroup handle. +*/ +struct sqlite3_changegroup { + int rc; /* Error code */ + int bPatch; /* True to accumulate patchsets */ + SessionTable *pList; /* List of tables in current patch */ +}; + +/* +** This function is called to merge two changes to the same row together as +** part of an sqlite3changeset_concat() operation. A new change object is +** allocated and a pointer to it stored in *ppNew. +*/ +static int sessionChangeMerge( + SessionTable *pTab, /* Table structure */ + int bRebase, /* True for a rebase hash-table */ + int bPatchset, /* True for patchsets */ + SessionChange *pExist, /* Existing change */ + int op2, /* Second change operation */ + int bIndirect, /* True if second change is indirect */ + u8 *aRec, /* Second change record */ + int nRec, /* Number of bytes in aRec */ + SessionChange **ppNew /* OUT: Merged change */ +){ + SessionChange *pNew = 0; + int rc = SQLITE_OK; + + if( !pExist ){ + pNew = (SessionChange *)sqlite3_malloc64(sizeof(SessionChange) + nRec); + if( !pNew ){ + return SQLITE_NOMEM; + } + memset(pNew, 0, sizeof(SessionChange)); + pNew->op = op2; + pNew->bIndirect = bIndirect; + pNew->aRecord = (u8*)&pNew[1]; + if( bIndirect==0 || bRebase==0 ){ + pNew->nRecord = nRec; + memcpy(pNew->aRecord, aRec, nRec); + }else{ + int i; + u8 *pIn = aRec; + u8 *pOut = pNew->aRecord; + for(i=0; i<pTab->nCol; i++){ + int nIn = sessionSerialLen(pIn); + if( *pIn==0 ){ + *pOut++ = 0; + }else if( pTab->abPK[i]==0 ){ + *pOut++ = 0xFF; + }else{ + memcpy(pOut, pIn, nIn); + pOut += nIn; + } + pIn += nIn; + } + pNew->nRecord = pOut - pNew->aRecord; + } + }else if( bRebase ){ + if( pExist->op==SQLITE_DELETE && pExist->bIndirect ){ + *ppNew = pExist; + }else{ + sqlite3_int64 nByte = nRec + pExist->nRecord + sizeof(SessionChange); + pNew = (SessionChange*)sqlite3_malloc64(nByte); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + int i; + u8 *a1 = pExist->aRecord; + u8 *a2 = aRec; + u8 *pOut; + + memset(pNew, 0, nByte); + pNew->bIndirect = bIndirect || pExist->bIndirect; + pNew->op = op2; + pOut = pNew->aRecord = (u8*)&pNew[1]; + + for(i=0; i<pTab->nCol; i++){ + int n1 = sessionSerialLen(a1); + int n2 = sessionSerialLen(a2); + if( *a1==0xFF || (pTab->abPK[i]==0 && bIndirect) ){ + *pOut++ = 0xFF; + }else if( *a2==0 ){ + memcpy(pOut, a1, n1); + pOut += n1; + }else{ + memcpy(pOut, a2, n2); + pOut += n2; + } + a1 += n1; + a2 += n2; + } + pNew->nRecord = pOut - pNew->aRecord; + } + sqlite3_free(pExist); + } + }else{ + int op1 = pExist->op; + + /* + ** op1=INSERT, op2=INSERT -> Unsupported. Discard op2. + ** op1=INSERT, op2=UPDATE -> INSERT. + ** op1=INSERT, op2=DELETE -> (none) + ** + ** op1=UPDATE, op2=INSERT -> Unsupported. Discard op2. + ** op1=UPDATE, op2=UPDATE -> UPDATE. + ** op1=UPDATE, op2=DELETE -> DELETE. + ** + ** op1=DELETE, op2=INSERT -> UPDATE. + ** op1=DELETE, op2=UPDATE -> Unsupported. Discard op2. + ** op1=DELETE, op2=DELETE -> Unsupported. Discard op2. + */ + if( (op1==SQLITE_INSERT && op2==SQLITE_INSERT) + || (op1==SQLITE_UPDATE && op2==SQLITE_INSERT) + || (op1==SQLITE_DELETE && op2==SQLITE_UPDATE) + || (op1==SQLITE_DELETE && op2==SQLITE_DELETE) + ){ + pNew = pExist; + }else if( op1==SQLITE_INSERT && op2==SQLITE_DELETE ){ + sqlite3_free(pExist); + assert( pNew==0 ); + }else{ + u8 *aExist = pExist->aRecord; + sqlite3_int64 nByte; + u8 *aCsr; + + /* Allocate a new SessionChange object. Ensure that the aRecord[] + ** buffer of the new object is large enough to hold any record that + ** may be generated by combining the input records. */ + nByte = sizeof(SessionChange) + pExist->nRecord + nRec; + pNew = (SessionChange *)sqlite3_malloc64(nByte); + if( !pNew ){ + sqlite3_free(pExist); + return SQLITE_NOMEM; + } + memset(pNew, 0, sizeof(SessionChange)); + pNew->bIndirect = (bIndirect && pExist->bIndirect); + aCsr = pNew->aRecord = (u8 *)&pNew[1]; + + if( op1==SQLITE_INSERT ){ /* INSERT + UPDATE */ + u8 *a1 = aRec; + assert( op2==SQLITE_UPDATE ); + pNew->op = SQLITE_INSERT; + if( bPatchset==0 ) sessionSkipRecord(&a1, pTab->nCol); + sessionMergeRecord(&aCsr, pTab->nCol, aExist, a1); + }else if( op1==SQLITE_DELETE ){ /* DELETE + INSERT */ + assert( op2==SQLITE_INSERT ); + pNew->op = SQLITE_UPDATE; + if( bPatchset ){ + memcpy(aCsr, aRec, nRec); + aCsr += nRec; + }else{ + if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aExist, 0,aRec,0) ){ + sqlite3_free(pNew); + pNew = 0; + } + } + }else if( op2==SQLITE_UPDATE ){ /* UPDATE + UPDATE */ + u8 *a1 = aExist; + u8 *a2 = aRec; + assert( op1==SQLITE_UPDATE ); + if( bPatchset==0 ){ + sessionSkipRecord(&a1, pTab->nCol); + sessionSkipRecord(&a2, pTab->nCol); + } + pNew->op = SQLITE_UPDATE; + if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aRec, aExist,a1,a2) ){ + sqlite3_free(pNew); + pNew = 0; + } + }else{ /* UPDATE + DELETE */ + assert( op1==SQLITE_UPDATE && op2==SQLITE_DELETE ); + pNew->op = SQLITE_DELETE; + if( bPatchset ){ + memcpy(aCsr, aRec, nRec); + aCsr += nRec; + }else{ + sessionMergeRecord(&aCsr, pTab->nCol, aRec, aExist); + } + } + + if( pNew ){ + pNew->nRecord = (int)(aCsr - pNew->aRecord); + } + sqlite3_free(pExist); + } + } + + *ppNew = pNew; + return rc; +} + +/* +** Add all changes in the changeset traversed by the iterator passed as +** the first argument to the changegroup hash tables. +*/ +static int sessionChangesetToHash( + sqlite3_changeset_iter *pIter, /* Iterator to read from */ + sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */ + int bRebase /* True if hash table is for rebasing */ +){ + u8 *aRec; + int nRec; + int rc = SQLITE_OK; + SessionTable *pTab = 0; + + while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){ + const char *zNew; + int nCol; + int op; + int iHash; + int bIndirect; + SessionChange *pChange; + SessionChange *pExist = 0; + SessionChange **pp; + + if( pGrp->pList==0 ){ + pGrp->bPatch = pIter->bPatchset; + }else if( pIter->bPatchset!=pGrp->bPatch ){ + rc = SQLITE_ERROR; + break; + } + + sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect); + if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){ + /* Search the list for a matching table */ + int nNew = (int)strlen(zNew); + u8 *abPK; + + sqlite3changeset_pk(pIter, &abPK, 0); + for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break; + } + if( !pTab ){ + SessionTable **ppTab; + + pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nNew+1); + if( !pTab ){ + rc = SQLITE_NOMEM; + break; + } + memset(pTab, 0, sizeof(SessionTable)); + pTab->nCol = nCol; + pTab->abPK = (u8*)&pTab[1]; + memcpy(pTab->abPK, abPK, nCol); + pTab->zName = (char*)&pTab->abPK[nCol]; + memcpy(pTab->zName, zNew, nNew+1); + + /* The new object must be linked on to the end of the list, not + ** simply added to the start of it. This is to ensure that the + ** tables within the output of sqlite3changegroup_output() are in + ** the right order. */ + for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext); + *ppTab = pTab; + }else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){ + rc = SQLITE_SCHEMA; + break; + } + } + + if( sessionGrowHash(0, pIter->bPatchset, pTab) ){ + rc = SQLITE_NOMEM; + break; + } + iHash = sessionChangeHash( + pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange + ); + + /* Search for existing entry. If found, remove it from the hash table. + ** Code below may link it back in. + */ + for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){ + int bPkOnly1 = 0; + int bPkOnly2 = 0; + if( pIter->bPatchset ){ + bPkOnly1 = (*pp)->op==SQLITE_DELETE; + bPkOnly2 = op==SQLITE_DELETE; + } + if( sessionChangeEqual(pTab, bPkOnly1, (*pp)->aRecord, bPkOnly2, aRec) ){ + pExist = *pp; + *pp = (*pp)->pNext; + pTab->nEntry--; + break; + } + } + + rc = sessionChangeMerge(pTab, bRebase, + pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange + ); + if( rc ) break; + if( pChange ){ + pChange->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pChange; + pTab->nEntry++; + } + } + + if( rc==SQLITE_OK ) rc = pIter->rc; + return rc; +} + +/* +** Serialize a changeset (or patchset) based on all changesets (or patchsets) +** added to the changegroup object passed as the first argument. +** +** If xOutput is not NULL, then the changeset/patchset is returned to the +** user via one or more calls to xOutput, as with the other streaming +** interfaces. +** +** Or, if xOutput is NULL, then (*ppOut) is populated with a pointer to a +** buffer containing the output changeset before this function returns. In +** this case (*pnOut) is set to the size of the output buffer in bytes. It +** is the responsibility of the caller to free the output buffer using +** sqlite3_free() when it is no longer required. +** +** If successful, SQLITE_OK is returned. Or, if an error occurs, an SQLite +** error code. If an error occurs and xOutput is NULL, (*ppOut) and (*pnOut) +** are both set to 0 before returning. +*/ +static int sessionChangegroupOutput( + sqlite3_changegroup *pGrp, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut, + int *pnOut, + void **ppOut +){ + int rc = SQLITE_OK; + SessionBuffer buf = {0, 0, 0}; + SessionTable *pTab; + assert( xOutput==0 || (ppOut==0 && pnOut==0) ); + + /* Create the serialized output changeset based on the contents of the + ** hash tables attached to the SessionTable objects in list p->pList. + */ + for(pTab=pGrp->pList; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ + int i; + if( pTab->nEntry==0 ) continue; + + sessionAppendTableHdr(&buf, pGrp->bPatch, pTab, &rc); + for(i=0; i<pTab->nChange; i++){ + SessionChange *p; + for(p=pTab->apChange[i]; p; p=p->pNext){ + sessionAppendByte(&buf, p->op, &rc); + sessionAppendByte(&buf, p->bIndirect, &rc); + sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc); + if( rc==SQLITE_OK && xOutput && buf.nBuf>=sessions_strm_chunk_size ){ + rc = xOutput(pOut, buf.aBuf, buf.nBuf); + buf.nBuf = 0; + } + } + } + } + + if( rc==SQLITE_OK ){ + if( xOutput ){ + if( buf.nBuf>0 ) rc = xOutput(pOut, buf.aBuf, buf.nBuf); + }else if( ppOut ){ + *ppOut = buf.aBuf; + if( pnOut ) *pnOut = buf.nBuf; + buf.aBuf = 0; + } + } + sqlite3_free(buf.aBuf); + + return rc; +} + +/* +** Allocate a new, empty, sqlite3_changegroup. +*/ +SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp){ + int rc = SQLITE_OK; /* Return code */ + sqlite3_changegroup *p; /* New object */ + p = (sqlite3_changegroup*)sqlite3_malloc(sizeof(sqlite3_changegroup)); + if( p==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(p, 0, sizeof(sqlite3_changegroup)); + } + *pp = p; + return rc; +} + +/* +** Add the changeset currently stored in buffer pData, size nData bytes, +** to changeset-group p. +*/ +SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){ + sqlite3_changeset_iter *pIter; /* Iterator opened on pData/nData */ + int rc; /* Return code */ + + rc = sqlite3changeset_start(&pIter, nData, pData); + if( rc==SQLITE_OK ){ + rc = sessionChangesetToHash(pIter, pGrp, 0); + } + sqlite3changeset_finalize(pIter); + return rc; +} + +/* +** Obtain a buffer containing a changeset representing the concatenation +** of all changesets added to the group so far. +*/ +SQLITE_API int sqlite3changegroup_output( + sqlite3_changegroup *pGrp, + int *pnData, + void **ppData +){ + return sessionChangegroupOutput(pGrp, 0, 0, pnData, ppData); +} + +/* +** Streaming versions of changegroup_add(). +*/ +SQLITE_API int sqlite3changegroup_add_strm( + sqlite3_changegroup *pGrp, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +){ + sqlite3_changeset_iter *pIter; /* Iterator opened on pData/nData */ + int rc; /* Return code */ + + rc = sqlite3changeset_start_strm(&pIter, xInput, pIn); + if( rc==SQLITE_OK ){ + rc = sessionChangesetToHash(pIter, pGrp, 0); + } + sqlite3changeset_finalize(pIter); + return rc; +} + +/* +** Streaming versions of changegroup_output(). +*/ +SQLITE_API int sqlite3changegroup_output_strm( + sqlite3_changegroup *pGrp, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + return sessionChangegroupOutput(pGrp, xOutput, pOut, 0, 0); +} + +/* +** Delete a changegroup object. +*/ +SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){ + if( pGrp ){ + sessionDeleteTable(0, pGrp->pList); + sqlite3_free(pGrp); + } +} + +/* +** Combine two changesets together. +*/ +SQLITE_API int sqlite3changeset_concat( + int nLeft, /* Number of bytes in lhs input */ + void *pLeft, /* Lhs input changeset */ + int nRight /* Number of bytes in rhs input */, + void *pRight, /* Rhs input changeset */ + int *pnOut, /* OUT: Number of bytes in output changeset */ + void **ppOut /* OUT: changeset (left <concat> right) */ +){ + sqlite3_changegroup *pGrp; + int rc; + + rc = sqlite3changegroup_new(&pGrp); + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_add(pGrp, nLeft, pLeft); + } + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_add(pGrp, nRight, pRight); + } + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_output(pGrp, pnOut, ppOut); + } + sqlite3changegroup_delete(pGrp); + + return rc; +} + +/* +** Streaming version of sqlite3changeset_concat(). +*/ +SQLITE_API int sqlite3changeset_concat_strm( + int (*xInputA)(void *pIn, void *pData, int *pnData), + void *pInA, + int (*xInputB)(void *pIn, void *pData, int *pnData), + void *pInB, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + sqlite3_changegroup *pGrp; + int rc; + + rc = sqlite3changegroup_new(&pGrp); + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_add_strm(pGrp, xInputA, pInA); + } + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_add_strm(pGrp, xInputB, pInB); + } + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_output_strm(pGrp, xOutput, pOut); + } + sqlite3changegroup_delete(pGrp); + + return rc; +} + +/* +** Changeset rebaser handle. +*/ +struct sqlite3_rebaser { + sqlite3_changegroup grp; /* Hash table */ +}; + +/* +** Buffers a1 and a2 must both contain a sessions module record nCol +** fields in size. This function appends an nCol sessions module +** record to buffer pBuf that is a copy of a1, except that for +** each field that is undefined in a1[], swap in the field from a2[]. +*/ +static void sessionAppendRecordMerge( + SessionBuffer *pBuf, /* Buffer to append to */ + int nCol, /* Number of columns in each record */ + u8 *a1, int n1, /* Record 1 */ + u8 *a2, int n2, /* Record 2 */ + int *pRc /* IN/OUT: error code */ +){ + sessionBufferGrow(pBuf, n1+n2, pRc); + if( *pRc==SQLITE_OK ){ + int i; + u8 *pOut = &pBuf->aBuf[pBuf->nBuf]; + for(i=0; i<nCol; i++){ + int nn1 = sessionSerialLen(a1); + int nn2 = sessionSerialLen(a2); + if( *a1==0 || *a1==0xFF ){ + memcpy(pOut, a2, nn2); + pOut += nn2; + }else{ + memcpy(pOut, a1, nn1); + pOut += nn1; + } + a1 += nn1; + a2 += nn2; + } + + pBuf->nBuf = pOut-pBuf->aBuf; + assert( pBuf->nBuf<=pBuf->nAlloc ); + } +} + +/* +** This function is called when rebasing a local UPDATE change against one +** or more remote UPDATE changes. The aRec/nRec buffer contains the current +** old.* and new.* records for the change. The rebase buffer (a single +** record) is in aChange/nChange. The rebased change is appended to buffer +** pBuf. +** +** Rebasing the UPDATE involves: +** +** * Removing any changes to fields for which the corresponding field +** in the rebase buffer is set to "replaced" (type 0xFF). If this +** means the UPDATE change updates no fields, nothing is appended +** to the output buffer. +** +** * For each field modified by the local change for which the +** corresponding field in the rebase buffer is not "undefined" (0x00) +** or "replaced" (0xFF), the old.* value is replaced by the value +** in the rebase buffer. +*/ +static void sessionAppendPartialUpdate( + SessionBuffer *pBuf, /* Append record here */ + sqlite3_changeset_iter *pIter, /* Iterator pointed at local change */ + u8 *aRec, int nRec, /* Local change */ + u8 *aChange, int nChange, /* Record to rebase against */ + int *pRc /* IN/OUT: Return Code */ +){ + sessionBufferGrow(pBuf, 2+nRec+nChange, pRc); + if( *pRc==SQLITE_OK ){ + int bData = 0; + u8 *pOut = &pBuf->aBuf[pBuf->nBuf]; + int i; + u8 *a1 = aRec; + u8 *a2 = aChange; + + *pOut++ = SQLITE_UPDATE; + *pOut++ = pIter->bIndirect; + for(i=0; i<pIter->nCol; i++){ + int n1 = sessionSerialLen(a1); + int n2 = sessionSerialLen(a2); + if( pIter->abPK[i] || a2[0]==0 ){ + if( !pIter->abPK[i] && a1[0] ) bData = 1; + memcpy(pOut, a1, n1); + pOut += n1; + }else if( a2[0]!=0xFF && a1[0] ){ + bData = 1; + memcpy(pOut, a2, n2); + pOut += n2; + }else{ + *pOut++ = '\0'; + } + a1 += n1; + a2 += n2; + } + if( bData ){ + a2 = aChange; + for(i=0; i<pIter->nCol; i++){ + int n1 = sessionSerialLen(a1); + int n2 = sessionSerialLen(a2); + if( pIter->abPK[i] || a2[0]!=0xFF ){ + memcpy(pOut, a1, n1); + pOut += n1; + }else{ + *pOut++ = '\0'; + } + a1 += n1; + a2 += n2; + } + pBuf->nBuf = (pOut - pBuf->aBuf); + } + } +} + +/* +** pIter is configured to iterate through a changeset. This function rebases +** that changeset according to the current configuration of the rebaser +** object passed as the first argument. If no error occurs and argument xOutput +** is not NULL, then the changeset is returned to the caller by invoking +** xOutput zero or more times and SQLITE_OK returned. Or, if xOutput is NULL, +** then (*ppOut) is set to point to a buffer containing the rebased changeset +** before this function returns. In this case (*pnOut) is set to the size of +** the buffer in bytes. It is the responsibility of the caller to eventually +** free the (*ppOut) buffer using sqlite3_free(). +** +** If an error occurs, an SQLite error code is returned. If ppOut and +** pnOut are not NULL, then the two output parameters are set to 0 before +** returning. +*/ +static int sessionRebase( + sqlite3_rebaser *p, /* Rebaser hash table */ + sqlite3_changeset_iter *pIter, /* Input data */ + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut, /* Context for xOutput callback */ + int *pnOut, /* OUT: Number of bytes in output changeset */ + void **ppOut /* OUT: Inverse of pChangeset */ +){ + int rc = SQLITE_OK; + u8 *aRec = 0; + int nRec = 0; + int bNew = 0; + SessionTable *pTab = 0; + SessionBuffer sOut = {0,0,0}; + + while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){ + SessionChange *pChange = 0; + int bDone = 0; + + if( bNew ){ + const char *zTab = pIter->zTab; + for(pTab=p->grp.pList; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_stricmp(pTab->zName, zTab) ) break; + } + bNew = 0; + + /* A patchset may not be rebased */ + if( pIter->bPatchset ){ + rc = SQLITE_ERROR; + } + + /* Append a table header to the output for this new table */ + sessionAppendByte(&sOut, pIter->bPatchset ? 'P' : 'T', &rc); + sessionAppendVarint(&sOut, pIter->nCol, &rc); + sessionAppendBlob(&sOut, pIter->abPK, pIter->nCol, &rc); + sessionAppendBlob(&sOut,(u8*)pIter->zTab,(int)strlen(pIter->zTab)+1,&rc); + } + + if( pTab && rc==SQLITE_OK ){ + int iHash = sessionChangeHash(pTab, 0, aRec, pTab->nChange); + + for(pChange=pTab->apChange[iHash]; pChange; pChange=pChange->pNext){ + if( sessionChangeEqual(pTab, 0, aRec, 0, pChange->aRecord) ){ + break; + } + } + } + + if( pChange ){ + assert( pChange->op==SQLITE_DELETE || pChange->op==SQLITE_INSERT ); + switch( pIter->op ){ + case SQLITE_INSERT: + if( pChange->op==SQLITE_INSERT ){ + bDone = 1; + if( pChange->bIndirect==0 ){ + sessionAppendByte(&sOut, SQLITE_UPDATE, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendBlob(&sOut, pChange->aRecord, pChange->nRecord, &rc); + sessionAppendBlob(&sOut, aRec, nRec, &rc); + } + } + break; + + case SQLITE_UPDATE: + bDone = 1; + if( pChange->op==SQLITE_DELETE ){ + if( pChange->bIndirect==0 ){ + u8 *pCsr = aRec; + sessionSkipRecord(&pCsr, pIter->nCol); + sessionAppendByte(&sOut, SQLITE_INSERT, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendRecordMerge(&sOut, pIter->nCol, + pCsr, nRec-(pCsr-aRec), + pChange->aRecord, pChange->nRecord, &rc + ); + } + }else{ + sessionAppendPartialUpdate(&sOut, pIter, + aRec, nRec, pChange->aRecord, pChange->nRecord, &rc + ); + } + break; + + default: + assert( pIter->op==SQLITE_DELETE ); + bDone = 1; + if( pChange->op==SQLITE_INSERT ){ + sessionAppendByte(&sOut, SQLITE_DELETE, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendRecordMerge(&sOut, pIter->nCol, + pChange->aRecord, pChange->nRecord, aRec, nRec, &rc + ); + } + break; + } + } + + if( bDone==0 ){ + sessionAppendByte(&sOut, pIter->op, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendBlob(&sOut, aRec, nRec, &rc); + } + if( rc==SQLITE_OK && xOutput && sOut.nBuf>sessions_strm_chunk_size ){ + rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); + sOut.nBuf = 0; + } + if( rc ) break; + } + + if( rc!=SQLITE_OK ){ + sqlite3_free(sOut.aBuf); + memset(&sOut, 0, sizeof(sOut)); + } + + if( rc==SQLITE_OK ){ + if( xOutput ){ + if( sOut.nBuf>0 ){ + rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); + } + }else if( ppOut ){ + *ppOut = (void*)sOut.aBuf; + *pnOut = sOut.nBuf; + sOut.aBuf = 0; + } + } + sqlite3_free(sOut.aBuf); + return rc; +} + +/* +** Create a new rebaser object. +*/ +SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser **ppNew){ + int rc = SQLITE_OK; + sqlite3_rebaser *pNew; + + pNew = sqlite3_malloc(sizeof(sqlite3_rebaser)); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pNew, 0, sizeof(sqlite3_rebaser)); + } + *ppNew = pNew; + return rc; +} + +/* +** Call this one or more times to configure a rebaser. +*/ +SQLITE_API int sqlite3rebaser_configure( + sqlite3_rebaser *p, + int nRebase, const void *pRebase +){ + sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */ + int rc; /* Return code */ + rc = sqlite3changeset_start(&pIter, nRebase, (void*)pRebase); + if( rc==SQLITE_OK ){ + rc = sessionChangesetToHash(pIter, &p->grp, 1); + } + sqlite3changeset_finalize(pIter); + return rc; +} + +/* +** Rebase a changeset according to current rebaser configuration +*/ +SQLITE_API int sqlite3rebaser_rebase( + sqlite3_rebaser *p, + int nIn, const void *pIn, + int *pnOut, void **ppOut +){ + sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */ + int rc = sqlite3changeset_start(&pIter, nIn, (void*)pIn); + + if( rc==SQLITE_OK ){ + rc = sessionRebase(p, pIter, 0, 0, pnOut, ppOut); + sqlite3changeset_finalize(pIter); + } + + return rc; +} + +/* +** Rebase a changeset according to current rebaser configuration +*/ +SQLITE_API int sqlite3rebaser_rebase_strm( + sqlite3_rebaser *p, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */ + int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn); + + if( rc==SQLITE_OK ){ + rc = sessionRebase(p, pIter, xOutput, pOut, 0, 0); + sqlite3changeset_finalize(pIter); + } + + return rc; +} + +/* +** Destroy a rebaser object +*/ +SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p){ + if( p ){ + sessionDeleteTable(0, p->grp.pList); + sqlite3_free(p); + } +} + +/* +** Global configuration +*/ +SQLITE_API int sqlite3session_config(int op, void *pArg){ + int rc = SQLITE_OK; + switch( op ){ + case SQLITE_SESSION_CONFIG_STRMSIZE: { + int *pInt = (int*)pArg; + if( *pInt>0 ){ + sessions_strm_chunk_size = *pInt; + } + *pInt = sessions_strm_chunk_size; + break; + } + default: + rc = SQLITE_MISUSE; + break; + } + return rc; +} + +#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */ + +/************** End of sqlite3session.c **************************************/ +/************** Begin file fts5.c ********************************************/ + + +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) + +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif +#if defined(NDEBUG) && defined(SQLITE_DEBUG) +# undef NDEBUG +#endif + +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** Interfaces to extend FTS5. Using the interfaces defined in this file, +** FTS5 may be extended with: +** +** * custom tokenizers, and +** * custom auxiliary functions. +*/ + + +#ifndef _FTS5_H +#define _FTS5_H + +/* #include "sqlite3.h" */ + +#if 0 +extern "C" { +#endif + +/************************************************************************* +** CUSTOM AUXILIARY FUNCTIONS +** +** Virtual table implementations may overload SQL functions by implementing +** the sqlite3_module.xFindFunction() method. +*/ + +typedef struct Fts5ExtensionApi Fts5ExtensionApi; +typedef struct Fts5Context Fts5Context; +typedef struct Fts5PhraseIter Fts5PhraseIter; + +typedef void (*fts5_extension_function)( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + sqlite3_context *pCtx, /* Context for returning result/error */ + int nVal, /* Number of values in apVal[] array */ + sqlite3_value **apVal /* Array of trailing arguments */ +); + +struct Fts5PhraseIter { + const unsigned char *a; + const unsigned char *b; +}; + +/* +** EXTENSION API FUNCTIONS +** +** xUserData(pFts): +** Return a copy of the context pointer the extension function was +** registered with. +** +** xColumnTotalSize(pFts, iCol, pnToken): +** If parameter iCol is less than zero, set output variable *pnToken +** to the total number of tokens in the FTS5 table. Or, if iCol is +** non-negative but less than the number of columns in the table, return +** the total number of tokens in column iCol, considering all rows in +** the FTS5 table. +** +** If parameter iCol is greater than or equal to the number of columns +** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. +** an OOM condition or IO error), an appropriate SQLite error code is +** returned. +** +** xColumnCount(pFts): +** Return the number of columns in the table. +** +** xColumnSize(pFts, iCol, pnToken): +** If parameter iCol is less than zero, set output variable *pnToken +** to the total number of tokens in the current row. Or, if iCol is +** non-negative but less than the number of columns in the table, set +** *pnToken to the number of tokens in column iCol of the current row. +** +** If parameter iCol is greater than or equal to the number of columns +** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. +** an OOM condition or IO error), an appropriate SQLite error code is +** returned. +** +** This function may be quite inefficient if used with an FTS5 table +** created with the "columnsize=0" option. +** +** xColumnText: +** This function attempts to retrieve the text of column iCol of the +** current document. If successful, (*pz) is set to point to a buffer +** containing the text in utf-8 encoding, (*pn) is set to the size in bytes +** (not characters) of the buffer and SQLITE_OK is returned. Otherwise, +** if an error occurs, an SQLite error code is returned and the final values +** of (*pz) and (*pn) are undefined. +** +** xPhraseCount: +** Returns the number of phrases in the current query expression. +** +** xPhraseSize: +** Returns the number of tokens in phrase iPhrase of the query. Phrases +** are numbered starting from zero. +** +** xInstCount: +** Set *pnInst to the total number of occurrences of all phrases within +** the query within the current row. Return SQLITE_OK if successful, or +** an error code (i.e. SQLITE_NOMEM) if an error occurs. +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always returns 0. +** +** xInst: +** Query for the details of phrase match iIdx within the current row. +** Phrase matches are numbered starting from zero, so the iIdx argument +** should be greater than or equal to zero and smaller than the value +** output by xInstCount(). +** +** Usually, output parameter *piPhrase is set to the phrase number, *piCol +** to the column in which it occurs and *piOff the token offset of the +** first token of the phrase. Returns SQLITE_OK if successful, or an error +** code (i.e. SQLITE_NOMEM) if an error occurs. +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. +** +** xRowid: +** Returns the rowid of the current row. +** +** xTokenize: +** Tokenize text using the tokenizer belonging to the FTS5 table. +** +** xQueryPhrase(pFts5, iPhrase, pUserData, xCallback): +** This API function is used to query the FTS table for phrase iPhrase +** of the current query. Specifically, a query equivalent to: +** +** ... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid +** +** with $p set to a phrase equivalent to the phrase iPhrase of the +** current query is executed. Any column filter that applies to +** phrase iPhrase of the current query is included in $p. For each +** row visited, the callback function passed as the fourth argument +** is invoked. The context and API objects passed to the callback +** function may be used to access the properties of each matched row. +** Invoking Api.xUserData() returns a copy of the pointer passed as +** the third argument to pUserData. +** +** If the callback function returns any value other than SQLITE_OK, the +** query is abandoned and the xQueryPhrase function returns immediately. +** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK. +** Otherwise, the error code is propagated upwards. +** +** If the query runs to completion without incident, SQLITE_OK is returned. +** Or, if some error occurs before the query completes or is aborted by +** the callback, an SQLite error code is returned. +** +** +** xSetAuxdata(pFts5, pAux, xDelete) +** +** Save the pointer passed as the second argument as the extension function's +** "auxiliary data". The pointer may then be retrieved by the current or any +** future invocation of the same fts5 extension function made as part of +** the same MATCH query using the xGetAuxdata() API. +** +** Each extension function is allocated a single auxiliary data slot for +** each FTS query (MATCH expression). If the extension function is invoked +** more than once for a single FTS query, then all invocations share a +** single auxiliary data context. +** +** If there is already an auxiliary data pointer when this function is +** invoked, then it is replaced by the new pointer. If an xDelete callback +** was specified along with the original pointer, it is invoked at this +** point. +** +** The xDelete callback, if one is specified, is also invoked on the +** auxiliary data pointer after the FTS5 query has finished. +** +** If an error (e.g. an OOM condition) occurs within this function, +** the auxiliary data is set to NULL and an error code returned. If the +** xDelete parameter was not NULL, it is invoked on the auxiliary data +** pointer before returning. +** +** +** xGetAuxdata(pFts5, bClear) +** +** Returns the current auxiliary data pointer for the fts5 extension +** function. See the xSetAuxdata() method for details. +** +** If the bClear argument is non-zero, then the auxiliary data is cleared +** (set to NULL) before this function returns. In this case the xDelete, +** if any, is not invoked. +** +** +** xRowCount(pFts5, pnRow) +** +** This function is used to retrieve the total number of rows in the table. +** In other words, the same value that would be returned by: +** +** SELECT count(*) FROM ftstable; +** +** xPhraseFirst() +** This function is used, along with type Fts5PhraseIter and the xPhraseNext +** method, to iterate through all instances of a single query phrase within +** the current row. This is the same information as is accessible via the +** xInstCount/xInst APIs. While the xInstCount/xInst APIs are more convenient +** to use, this API may be faster under some circumstances. To iterate +** through instances of phrase iPhrase, use the following code: +** +** Fts5PhraseIter iter; +** int iCol, iOff; +** for(pApi->xPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff); +** iCol>=0; +** pApi->xPhraseNext(pFts, &iter, &iCol, &iOff) +** ){ +** // An instance of phrase iPhrase at offset iOff of column iCol +** } +** +** The Fts5PhraseIter structure is defined above. Applications should not +** modify this structure directly - it should only be used as shown above +** with the xPhraseFirst() and xPhraseNext() API methods (and by +** xPhraseFirstColumn() and xPhraseNextColumn() as illustrated below). +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always iterates +** through an empty set (all calls to xPhraseFirst() set iCol to -1). +** +** xPhraseNext() +** See xPhraseFirst above. +** +** xPhraseFirstColumn() +** This function and xPhraseNextColumn() are similar to the xPhraseFirst() +** and xPhraseNext() APIs described above. The difference is that instead +** of iterating through all instances of a phrase in the current row, these +** APIs are used to iterate through the set of columns in the current row +** that contain one or more instances of a specified phrase. For example: +** +** Fts5PhraseIter iter; +** int iCol; +** for(pApi->xPhraseFirstColumn(pFts, iPhrase, &iter, &iCol); +** iCol>=0; +** pApi->xPhraseNextColumn(pFts, &iter, &iCol) +** ){ +** // Column iCol contains at least one instance of phrase iPhrase +** } +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" option. If the FTS5 table is created with either +** "detail=none" "content=" option (i.e. if it is a contentless table), +** then this API always iterates through an empty set (all calls to +** xPhraseFirstColumn() set iCol to -1). +** +** The information accessed using this API and its companion +** xPhraseFirstColumn() may also be obtained using xPhraseFirst/xPhraseNext +** (or xInst/xInstCount). The chief advantage of this API is that it is +** significantly more efficient than those alternatives when used with +** "detail=column" tables. +** +** xPhraseNextColumn() +** See xPhraseFirstColumn above. +*/ +struct Fts5ExtensionApi { + int iVersion; /* Currently always set to 2 */ + + void *(*xUserData)(Fts5Context*); + + int (*xColumnCount)(Fts5Context*); + int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow); + int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken); + + int (*xTokenize)(Fts5Context*, + const char *pText, int nText, /* Text to tokenize */ + void *pCtx, /* Context passed to xToken() */ + int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ + ); + + int (*xPhraseCount)(Fts5Context*); + int (*xPhraseSize)(Fts5Context*, int iPhrase); + + int (*xInstCount)(Fts5Context*, int *pnInst); + int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff); + + sqlite3_int64 (*xRowid)(Fts5Context*); + int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn); + int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken); + + int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData, + int(*)(const Fts5ExtensionApi*,Fts5Context*,void*) + ); + int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*)); + void *(*xGetAuxdata)(Fts5Context*, int bClear); + + int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); + void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); + + int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); + void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); +}; + +/* +** CUSTOM AUXILIARY FUNCTIONS +*************************************************************************/ + +/************************************************************************* +** CUSTOM TOKENIZERS +** +** Applications may also register custom tokenizer types. A tokenizer +** is registered by providing fts5 with a populated instance of the +** following structure. All structure methods must be defined, setting +** any member of the fts5_tokenizer struct to NULL leads to undefined +** behaviour. The structure methods are expected to function as follows: +** +** xCreate: +** This function is used to allocate and initialize a tokenizer instance. +** A tokenizer instance is required to actually tokenize text. +** +** The first argument passed to this function is a copy of the (void*) +** pointer provided by the application when the fts5_tokenizer object +** was registered with FTS5 (the third argument to xCreateTokenizer()). +** The second and third arguments are an array of nul-terminated strings +** containing the tokenizer arguments, if any, specified following the +** tokenizer name as part of the CREATE VIRTUAL TABLE statement used +** to create the FTS5 table. +** +** The final argument is an output variable. If successful, (*ppOut) +** should be set to point to the new tokenizer handle and SQLITE_OK +** returned. If an error occurs, some value other than SQLITE_OK should +** be returned. In this case, fts5 assumes that the final value of *ppOut +** is undefined. +** +** xDelete: +** This function is invoked to delete a tokenizer handle previously +** allocated using xCreate(). Fts5 guarantees that this function will +** be invoked exactly once for each successful call to xCreate(). +** +** xTokenize: +** This function is expected to tokenize the nText byte string indicated +** by argument pText. pText may or may not be nul-terminated. The first +** argument passed to this function is a pointer to an Fts5Tokenizer object +** returned by an earlier call to xCreate(). +** +** The second argument indicates the reason that FTS5 is requesting +** tokenization of the supplied text. This is always one of the following +** four values: +** +** <ul><li> <b>FTS5_TOKENIZE_DOCUMENT</b> - A document is being inserted into +** or removed from the FTS table. The tokenizer is being invoked to +** determine the set of tokens to add to (or delete from) the +** FTS index. +** +** <li> <b>FTS5_TOKENIZE_QUERY</b> - A MATCH query is being executed +** against the FTS index. The tokenizer is being called to tokenize +** a bareword or quoted string specified as part of the query. +** +** <li> <b>(FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX)</b> - Same as +** FTS5_TOKENIZE_QUERY, except that the bareword or quoted string is +** followed by a "*" character, indicating that the last token +** returned by the tokenizer will be treated as a token prefix. +** +** <li> <b>FTS5_TOKENIZE_AUX</b> - The tokenizer is being invoked to +** satisfy an fts5_api.xTokenize() request made by an auxiliary +** function. Or an fts5_api.xColumnSize() request made by the same +** on a columnsize=0 database. +** </ul> +** +** For each token in the input string, the supplied callback xToken() must +** be invoked. The first argument to it should be a copy of the pointer +** passed as the second argument to xTokenize(). The third and fourth +** arguments are a pointer to a buffer containing the token text, and the +** size of the token in bytes. The 4th and 5th arguments are the byte offsets +** of the first byte of and first byte immediately following the text from +** which the token is derived within the input. +** +** The second argument passed to the xToken() callback ("tflags") should +** normally be set to 0. The exception is if the tokenizer supports +** synonyms. In this case see the discussion below for details. +** +** FTS5 assumes the xToken() callback is invoked for each token in the +** order that they occur within the input text. +** +** If an xToken() callback returns any value other than SQLITE_OK, then +** the tokenization should be abandoned and the xTokenize() method should +** immediately return a copy of the xToken() return value. Or, if the +** input buffer is exhausted, xTokenize() should return SQLITE_OK. Finally, +** if an error occurs with the xTokenize() implementation itself, it +** may abandon the tokenization and return any error code other than +** SQLITE_OK or SQLITE_DONE. +** +** SYNONYM SUPPORT +** +** Custom tokenizers may also support synonyms. Consider a case in which a +** user wishes to query for a phrase such as "first place". Using the +** built-in tokenizers, the FTS5 query 'first + place' will match instances +** of "first place" within the document set, but not alternative forms +** such as "1st place". In some applications, it would be better to match +** all instances of "first place" or "1st place" regardless of which form +** the user specified in the MATCH query text. +** +** There are several ways to approach this in FTS5: +** +** <ol><li> By mapping all synonyms to a single token. In this case, using +** the above example, this means that the tokenizer returns the +** same token for inputs "first" and "1st". Say that token is in +** fact "first", so that when the user inserts the document "I won +** 1st place" entries are added to the index for tokens "i", "won", +** "first" and "place". If the user then queries for '1st + place', +** the tokenizer substitutes "first" for "1st" and the query works +** as expected. +** +** <li> By querying the index for all synonyms of each query term +** separately. In this case, when tokenizing query text, the +** tokenizer may provide multiple synonyms for a single term +** within the document. FTS5 then queries the index for each +** synonym individually. For example, faced with the query: +** +** <codeblock> +** ... MATCH 'first place'</codeblock> +** +** the tokenizer offers both "1st" and "first" as synonyms for the +** first token in the MATCH query and FTS5 effectively runs a query +** similar to: +** +** <codeblock> +** ... MATCH '(first OR 1st) place'</codeblock> +** +** except that, for the purposes of auxiliary functions, the query +** still appears to contain just two phrases - "(first OR 1st)" +** being treated as a single phrase. +** +** <li> By adding multiple synonyms for a single term to the FTS index. +** Using this method, when tokenizing document text, the tokenizer +** provides multiple synonyms for each token. So that when a +** document such as "I won first place" is tokenized, entries are +** added to the FTS index for "i", "won", "first", "1st" and +** "place". +** +** This way, even if the tokenizer does not provide synonyms +** when tokenizing query text (it should not - to do so would be +** inefficient), it doesn't matter if the user queries for +** 'first + place' or '1st + place', as there are entries in the +** FTS index corresponding to both forms of the first token. +** </ol> +** +** Whether it is parsing document or query text, any call to xToken that +** specifies a <i>tflags</i> argument with the FTS5_TOKEN_COLOCATED bit +** is considered to supply a synonym for the previous token. For example, +** when parsing the document "I won first place", a tokenizer that supports +** synonyms would call xToken() 5 times, as follows: +** +** <codeblock> +** xToken(pCtx, 0, "i", 1, 0, 1); +** xToken(pCtx, 0, "won", 3, 2, 5); +** xToken(pCtx, 0, "first", 5, 6, 11); +** xToken(pCtx, FTS5_TOKEN_COLOCATED, "1st", 3, 6, 11); +** xToken(pCtx, 0, "place", 5, 12, 17); +**</codeblock> +** +** It is an error to specify the FTS5_TOKEN_COLOCATED flag the first time +** xToken() is called. Multiple synonyms may be specified for a single token +** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. +** There is no limit to the number of synonyms that may be provided for a +** single token. +** +** In many cases, method (1) above is the best approach. It does not add +** extra data to the FTS index or require FTS5 to query for multiple terms, +** so it is efficient in terms of disk space and query speed. However, it +** does not support prefix queries very well. If, as suggested above, the +** token "first" is substituted for "1st" by the tokenizer, then the query: +** +** <codeblock> +** ... MATCH '1s*'</codeblock> +** +** will not match documents that contain the token "1st" (as the tokenizer +** will probably not map "1s" to any prefix of "first"). +** +** For full prefix support, method (3) may be preferred. In this case, +** because the index contains entries for both "first" and "1st", prefix +** queries such as 'fi*' or '1s*' will match correctly. However, because +** extra entries are added to the FTS index, this method uses more space +** within the database. +** +** Method (2) offers a midpoint between (1) and (3). Using this method, +** a query such as '1s*' will match documents that contain the literal +** token "1st", but not "first" (assuming the tokenizer is not able to +** provide synonyms for prefixes). However, a non-prefix query like '1st' +** will match against "1st" and "first". This method does not require +** extra disk space, as no extra entries are added to the FTS index. +** On the other hand, it may require more CPU cycles to run MATCH queries, +** as separate queries of the FTS index are required for each synonym. +** +** When using methods (2) or (3), it is important that the tokenizer only +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is +** inefficient. +*/ +typedef struct Fts5Tokenizer Fts5Tokenizer; +typedef struct fts5_tokenizer fts5_tokenizer; +struct fts5_tokenizer { + int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + void (*xDelete)(Fts5Tokenizer*); + int (*xTokenize)(Fts5Tokenizer*, + void *pCtx, + int flags, /* Mask of FTS5_TOKENIZE_* flags */ + const char *pText, int nText, + int (*xToken)( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStart, /* Byte offset of token within input text */ + int iEnd /* Byte offset of end of token within input text */ + ) + ); +}; + +/* Flags that may be passed as the third argument to xTokenize() */ +#define FTS5_TOKENIZE_QUERY 0x0001 +#define FTS5_TOKENIZE_PREFIX 0x0002 +#define FTS5_TOKENIZE_DOCUMENT 0x0004 +#define FTS5_TOKENIZE_AUX 0x0008 + +/* Flags that may be passed by the tokenizer implementation back to FTS5 +** as the third argument to the supplied xToken callback. */ +#define FTS5_TOKEN_COLOCATED 0x0001 /* Same position as prev. token */ + +/* +** END OF CUSTOM TOKENIZERS +*************************************************************************/ + +/************************************************************************* +** FTS5 EXTENSION REGISTRATION API +*/ +typedef struct fts5_api fts5_api; +struct fts5_api { + int iVersion; /* Currently always set to 2 */ + + /* Create a new tokenizer */ + int (*xCreateTokenizer)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_tokenizer *pTokenizer, + void (*xDestroy)(void*) + ); + + /* Find an existing tokenizer */ + int (*xFindTokenizer)( + fts5_api *pApi, + const char *zName, + void **ppUserData, + fts5_tokenizer *pTokenizer + ); + + /* Create a new auxiliary function */ + int (*xCreateFunction)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_extension_function xFunction, + void (*xDestroy)(void*) + ); +}; + +/* +** END OF REGISTRATION API +*************************************************************************/ + +#if 0 +} /* end of the 'extern "C"' block */ +#endif + +#endif /* _FTS5_H */ + +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +*/ +#ifndef _FTS5INT_H +#define _FTS5INT_H + +/* #include "fts5.h" */ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 + +/* #include <string.h> */ +/* #include <assert.h> */ + +#ifndef SQLITE_AMALGAMATION + +typedef unsigned char u8; +typedef unsigned int u32; +typedef unsigned short u16; +typedef short i16; +typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; + +#ifndef ArraySize +# define ArraySize(x) ((int)(sizeof(x) / sizeof(x[0]))) +#endif + +#define testcase(x) + +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif + +#define MIN(x,y) (((x) < (y)) ? (x) : (y)) +#define MAX(x,y) (((x) > (y)) ? (x) : (y)) + +/* +** Constants for the largest and smallest possible 64-bit signed integers. +*/ +# define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) +# define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) + +#endif + +/* Truncate very long tokens to this many bytes. Hard limit is +** (65536-1-1-4-9)==65521 bytes. The limiting factor is the 16-bit offset +** field that occurs at the start of each leaf page (see fts5_index.c). */ +#define FTS5_MAX_TOKEN_SIZE 32768 + +/* +** Maximum number of prefix indexes on single FTS5 table. This must be +** less than 32. If it is set to anything large than that, an #error +** directive in fts5_index.c will cause the build to fail. +*/ +#define FTS5_MAX_PREFIX_INDEXES 31 + +/* +** Maximum segments permitted in a single index +*/ +#define FTS5_MAX_SEGMENT 2000 + +#define FTS5_DEFAULT_NEARDIST 10 +#define FTS5_DEFAULT_RANK "bm25" + +/* Name of rank and rowid columns */ +#define FTS5_RANK_NAME "rank" +#define FTS5_ROWID_NAME "rowid" + +#ifdef SQLITE_DEBUG +# define FTS5_CORRUPT sqlite3Fts5Corrupt() +static int sqlite3Fts5Corrupt(void); +#else +# define FTS5_CORRUPT SQLITE_CORRUPT_VTAB +#endif + +/* +** The assert_nc() macro is similar to the assert() macro, except that it +** is used for assert() conditions that are true only if it can be +** guranteed that the database is not corrupt. +*/ +#ifdef SQLITE_DEBUG +SQLITE_API extern int sqlite3_fts5_may_be_corrupt; +# define assert_nc(x) assert(sqlite3_fts5_may_be_corrupt || (x)) +#else +# define assert_nc(x) assert(x) +#endif + +/* +** A version of memcmp() that does not cause asan errors if one of the pointer +** parameters is NULL and the number of bytes to compare is zero. +*/ +#define fts5Memcmp(s1, s2, n) ((n)<=0 ? 0 : memcmp((s1), (s2), (n))) + +/* Mark a function parameter as unused, to suppress nuisance compiler +** warnings. */ +#ifndef UNUSED_PARAM +# define UNUSED_PARAM(X) (void)(X) +#endif + +#ifndef UNUSED_PARAM2 +# define UNUSED_PARAM2(X, Y) (void)(X), (void)(Y) +#endif + +typedef struct Fts5Global Fts5Global; +typedef struct Fts5Colset Fts5Colset; + +/* If a NEAR() clump or phrase may only match a specific set of columns, +** then an object of the following type is used to record the set of columns. +** Each entry in the aiCol[] array is a column that may be matched. +** +** This object is used by fts5_expr.c and fts5_index.c. +*/ +struct Fts5Colset { + int nCol; + int aiCol[1]; +}; + + + +/************************************************************************** +** Interface to code in fts5_config.c. fts5_config.c contains contains code +** to parse the arguments passed to the CREATE VIRTUAL TABLE statement. +*/ + +typedef struct Fts5Config Fts5Config; + +/* +** An instance of the following structure encodes all information that can +** be gleaned from the CREATE VIRTUAL TABLE statement. +** +** And all information loaded from the %_config table. +** +** nAutomerge: +** The minimum number of segments that an auto-merge operation should +** attempt to merge together. A value of 1 sets the object to use the +** compile time default. Zero disables auto-merge altogether. +** +** bContentlessDelete: +** True if the contentless_delete option was present in the CREATE +** VIRTUAL TABLE statement. +** +** zContent: +** +** zContentRowid: +** The value of the content_rowid= option, if one was specified. Or +** the string "rowid" otherwise. This text is not quoted - if it is +** used as part of an SQL statement it needs to be quoted appropriately. +** +** zContentExprlist: +** +** pzErrmsg: +** This exists in order to allow the fts5_index.c module to return a +** decent error message if it encounters a file-format version it does +** not understand. +** +** bColumnsize: +** True if the %_docsize table is created. +** +** bPrefixIndex: +** This is only used for debugging. If set to false, any prefix indexes +** are ignored. This value is configured using: +** +** INSERT INTO tbl(tbl, rank) VALUES('prefix-index', $bPrefixIndex); +** +*/ +struct Fts5Config { + sqlite3 *db; /* Database handle */ + char *zDb; /* Database holding FTS index (e.g. "main") */ + char *zName; /* Name of FTS index */ + int nCol; /* Number of columns */ + char **azCol; /* Column names */ + u8 *abUnindexed; /* True for unindexed columns */ + int nPrefix; /* Number of prefix indexes */ + int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */ + int eContent; /* An FTS5_CONTENT value */ + int bContentlessDelete; /* "contentless_delete=" option (dflt==0) */ + char *zContent; /* content table */ + char *zContentRowid; /* "content_rowid=" option value */ + int bColumnsize; /* "columnsize=" option value (dflt==1) */ + int eDetail; /* FTS5_DETAIL_XXX value */ + char *zContentExprlist; + Fts5Tokenizer *pTok; + fts5_tokenizer *pTokApi; + int bLock; /* True when table is preparing statement */ + int ePattern; /* FTS_PATTERN_XXX constant */ + + /* Values loaded from the %_config table */ + int iVersion; /* fts5 file format 'version' */ + int iCookie; /* Incremented when %_config is modified */ + int pgsz; /* Approximate page size used in %_data */ + int nAutomerge; /* 'automerge' setting */ + int nCrisisMerge; /* Maximum allowed segments per level */ + int nUsermerge; /* 'usermerge' setting */ + int nHashSize; /* Bytes of memory for in-memory hash */ + char *zRank; /* Name of rank function */ + char *zRankArgs; /* Arguments to rank function */ + int bSecureDelete; /* 'secure-delete' */ + int nDeleteMerge; /* 'deletemerge' */ + + /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */ + char **pzErrmsg; + +#ifdef SQLITE_DEBUG + int bPrefixIndex; /* True to use prefix-indexes */ +#endif +}; + +/* Current expected value of %_config table 'version' field. And +** the expected version if the 'secure-delete' option has ever been +** set on the table. */ +#define FTS5_CURRENT_VERSION 4 +#define FTS5_CURRENT_VERSION_SECUREDELETE 5 + +#define FTS5_CONTENT_NORMAL 0 +#define FTS5_CONTENT_NONE 1 +#define FTS5_CONTENT_EXTERNAL 2 + +#define FTS5_DETAIL_FULL 0 +#define FTS5_DETAIL_NONE 1 +#define FTS5_DETAIL_COLUMNS 2 + +#define FTS5_PATTERN_NONE 0 +#define FTS5_PATTERN_LIKE 65 /* matches SQLITE_INDEX_CONSTRAINT_LIKE */ +#define FTS5_PATTERN_GLOB 66 /* matches SQLITE_INDEX_CONSTRAINT_GLOB */ + +static int sqlite3Fts5ConfigParse( + Fts5Global*, sqlite3*, int, const char **, Fts5Config**, char** +); +static void sqlite3Fts5ConfigFree(Fts5Config*); + +static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig); + +static int sqlite3Fts5Tokenize( + Fts5Config *pConfig, /* FTS5 Configuration object */ + int flags, /* FTS5_TOKENIZE_* flags */ + const char *pText, int nText, /* Text to tokenize */ + void *pCtx, /* Context passed to xToken() */ + int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ +); + +static void sqlite3Fts5Dequote(char *z); + +/* Load the contents of the %_config table */ +static int sqlite3Fts5ConfigLoad(Fts5Config*, int); + +/* Set the value of a single config attribute */ +static int sqlite3Fts5ConfigSetValue(Fts5Config*, const char*, sqlite3_value*, int*); + +static int sqlite3Fts5ConfigParseRank(const char*, char**, char**); + +/* +** End of interface to code in fts5_config.c. +**************************************************************************/ + +/************************************************************************** +** Interface to code in fts5_buffer.c. +*/ + +/* +** Buffer object for the incremental building of string data. +*/ +typedef struct Fts5Buffer Fts5Buffer; +struct Fts5Buffer { + u8 *p; + int n; + int nSpace; +}; + +static int sqlite3Fts5BufferSize(int*, Fts5Buffer*, u32); +static void sqlite3Fts5BufferAppendVarint(int*, Fts5Buffer*, i64); +static void sqlite3Fts5BufferAppendBlob(int*, Fts5Buffer*, u32, const u8*); +static void sqlite3Fts5BufferAppendString(int *, Fts5Buffer*, const char*); +static void sqlite3Fts5BufferFree(Fts5Buffer*); +static void sqlite3Fts5BufferZero(Fts5Buffer*); +static void sqlite3Fts5BufferSet(int*, Fts5Buffer*, int, const u8*); +static void sqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...); + +static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...); + +#define fts5BufferZero(x) sqlite3Fts5BufferZero(x) +#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,(i64)c) +#define fts5BufferFree(a) sqlite3Fts5BufferFree(a) +#define fts5BufferAppendBlob(a,b,c,d) sqlite3Fts5BufferAppendBlob(a,b,c,d) +#define fts5BufferSet(a,b,c,d) sqlite3Fts5BufferSet(a,b,c,d) + +#define fts5BufferGrow(pRc,pBuf,nn) ( \ + (u32)((pBuf)->n) + (u32)(nn) <= (u32)((pBuf)->nSpace) ? 0 : \ + sqlite3Fts5BufferSize((pRc),(pBuf),(nn)+(pBuf)->n) \ +) + +/* Write and decode big-endian 32-bit integer values */ +static void sqlite3Fts5Put32(u8*, int); +static int sqlite3Fts5Get32(const u8*); + +#define FTS5_POS2COLUMN(iPos) (int)(iPos >> 32) +#define FTS5_POS2OFFSET(iPos) (int)(iPos & 0x7FFFFFFF) + +typedef struct Fts5PoslistReader Fts5PoslistReader; +struct Fts5PoslistReader { + /* Variables used only by sqlite3Fts5PoslistIterXXX() functions. */ + const u8 *a; /* Position list to iterate through */ + int n; /* Size of buffer at a[] in bytes */ + int i; /* Current offset in a[] */ + + u8 bFlag; /* For client use (any custom purpose) */ + + /* Output variables */ + u8 bEof; /* Set to true at EOF */ + i64 iPos; /* (iCol<<32) + iPos */ +}; +static int sqlite3Fts5PoslistReaderInit( + const u8 *a, int n, /* Poslist buffer to iterate through */ + Fts5PoslistReader *pIter /* Iterator object to initialize */ +); +static int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader*); + +typedef struct Fts5PoslistWriter Fts5PoslistWriter; +struct Fts5PoslistWriter { + i64 iPrev; +}; +static int sqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64); +static void sqlite3Fts5PoslistSafeAppend(Fts5Buffer*, i64*, i64); + +static int sqlite3Fts5PoslistNext64( + const u8 *a, int n, /* Buffer containing poslist */ + int *pi, /* IN/OUT: Offset within a[] */ + i64 *piOff /* IN/OUT: Current offset */ +); + +/* Malloc utility */ +static void *sqlite3Fts5MallocZero(int *pRc, sqlite3_int64 nByte); +static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn); + +/* Character set tests (like isspace(), isalpha() etc.) */ +static int sqlite3Fts5IsBareword(char t); + + +/* Bucket of terms object used by the integrity-check in offsets=0 mode. */ +typedef struct Fts5Termset Fts5Termset; +static int sqlite3Fts5TermsetNew(Fts5Termset**); +static int sqlite3Fts5TermsetAdd(Fts5Termset*, int, const char*, int, int *pbPresent); +static void sqlite3Fts5TermsetFree(Fts5Termset*); + +/* +** End of interface to code in fts5_buffer.c. +**************************************************************************/ + +/************************************************************************** +** Interface to code in fts5_index.c. fts5_index.c contains contains code +** to access the data stored in the %_data table. +*/ + +typedef struct Fts5Index Fts5Index; +typedef struct Fts5IndexIter Fts5IndexIter; + +struct Fts5IndexIter { + i64 iRowid; + const u8 *pData; + int nData; + u8 bEof; +}; + +#define sqlite3Fts5IterEof(x) ((x)->bEof) + +/* +** Values used as part of the flags argument passed to IndexQuery(). +*/ +#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */ +#define FTS5INDEX_QUERY_DESC 0x0002 /* Docs in descending rowid order */ +#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */ +#define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */ + +/* The following are used internally by the fts5_index.c module. They are +** defined here only to make it easier to avoid clashes with the flags +** above. */ +#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010 +#define FTS5INDEX_QUERY_NOOUTPUT 0x0020 +#define FTS5INDEX_QUERY_SKIPHASH 0x0040 + +/* +** Create/destroy an Fts5Index object. +*/ +static int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**); +static int sqlite3Fts5IndexClose(Fts5Index *p); + +/* +** Return a simple checksum value based on the arguments. +*/ +static u64 sqlite3Fts5IndexEntryCksum( + i64 iRowid, + int iCol, + int iPos, + int iIdx, + const char *pTerm, + int nTerm +); + +/* +** Argument p points to a buffer containing utf-8 text that is n bytes in +** size. Return the number of bytes in the nChar character prefix of the +** buffer, or 0 if there are less than nChar characters in total. +*/ +static int sqlite3Fts5IndexCharlenToBytelen( + const char *p, + int nByte, + int nChar +); + +/* +** Open a new iterator to iterate though all rowids that match the +** specified token or token prefix. +*/ +static int sqlite3Fts5IndexQuery( + Fts5Index *p, /* FTS index to query */ + const char *pToken, int nToken, /* Token (or prefix) to query for */ + int flags, /* Mask of FTS5INDEX_QUERY_X flags */ + Fts5Colset *pColset, /* Match these columns only */ + Fts5IndexIter **ppIter /* OUT: New iterator object */ +); + +/* +** The various operations on open token or token prefix iterators opened +** using sqlite3Fts5IndexQuery(). +*/ +static int sqlite3Fts5IterNext(Fts5IndexIter*); +static int sqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch); + +/* +** Close an iterator opened by sqlite3Fts5IndexQuery(). +*/ +static void sqlite3Fts5IterClose(Fts5IndexIter*); + +/* +** Close the reader blob handle, if it is open. +*/ +static void sqlite3Fts5IndexCloseReader(Fts5Index*); + +/* +** This interface is used by the fts5vocab module. +*/ +static const char *sqlite3Fts5IterTerm(Fts5IndexIter*, int*); +static int sqlite3Fts5IterNextScan(Fts5IndexIter*); +static void *sqlite3Fts5StructureRef(Fts5Index*); +static void sqlite3Fts5StructureRelease(void*); +static int sqlite3Fts5StructureTest(Fts5Index*, void*); + + +/* +** Insert or remove data to or from the index. Each time a document is +** added to or removed from the index, this function is called one or more +** times. +** +** For an insert, it must be called once for each token in the new document. +** If the operation is a delete, it must be called (at least) once for each +** unique token in the document with an iCol value less than zero. The iPos +** argument is ignored for a delete. +*/ +static int sqlite3Fts5IndexWrite( + Fts5Index *p, /* Index to write to */ + int iCol, /* Column token appears in (-ve -> delete) */ + int iPos, /* Position of token within column */ + const char *pToken, int nToken /* Token to add or remove to or from index */ +); + +/* +** Indicate that subsequent calls to sqlite3Fts5IndexWrite() pertain to +** document iDocid. +*/ +static int sqlite3Fts5IndexBeginWrite( + Fts5Index *p, /* Index to write to */ + int bDelete, /* True if current operation is a delete */ + i64 iDocid /* Docid to add or remove data from */ +); + +/* +** Flush any data stored in the in-memory hash tables to the database. +** Also close any open blob handles. +*/ +static int sqlite3Fts5IndexSync(Fts5Index *p); + +/* +** Discard any data stored in the in-memory hash tables. Do not write it +** to the database. Additionally, assume that the contents of the %_data +** table may have changed on disk. So any in-memory caches of %_data +** records must be invalidated. +*/ +static int sqlite3Fts5IndexRollback(Fts5Index *p); + +/* +** Get or set the "averages" values. +*/ +static int sqlite3Fts5IndexGetAverages(Fts5Index *p, i64 *pnRow, i64 *anSize); +static int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8*, int); + +/* +** Functions called by the storage module as part of integrity-check. +*/ +static int sqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum, int bUseCksum); + +/* +** Called during virtual module initialization to register UDF +** fts5_decode() with SQLite +*/ +static int sqlite3Fts5IndexInit(sqlite3*); + +static int sqlite3Fts5IndexSetCookie(Fts5Index*, int); + +/* +** Return the total number of entries read from the %_data table by +** this connection since it was created. +*/ +static int sqlite3Fts5IndexReads(Fts5Index *p); + +static int sqlite3Fts5IndexReinit(Fts5Index *p); +static int sqlite3Fts5IndexOptimize(Fts5Index *p); +static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge); +static int sqlite3Fts5IndexReset(Fts5Index *p); + +static int sqlite3Fts5IndexLoadConfig(Fts5Index *p); + +static int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin); +static int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid); + +/* +** End of interface to code in fts5_index.c. +**************************************************************************/ + +/************************************************************************** +** Interface to code in fts5_varint.c. +*/ +static int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v); +static int sqlite3Fts5GetVarintLen(u32 iVal); +static u8 sqlite3Fts5GetVarint(const unsigned char*, u64*); +static int sqlite3Fts5PutVarint(unsigned char *p, u64 v); + +#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&(b)) +#define fts5GetVarint sqlite3Fts5GetVarint + +#define fts5FastGetVarint32(a, iOff, nVal) { \ + nVal = (a)[iOff++]; \ + if( nVal & 0x80 ){ \ + iOff--; \ + iOff += fts5GetVarint32(&(a)[iOff], nVal); \ + } \ +} + + +/* +** End of interface to code in fts5_varint.c. +**************************************************************************/ + + +/************************************************************************** +** Interface to code in fts5_main.c. +*/ + +/* +** Virtual-table object. +*/ +typedef struct Fts5Table Fts5Table; +struct Fts5Table { + sqlite3_vtab base; /* Base class used by SQLite core */ + Fts5Config *pConfig; /* Virtual table configuration */ + Fts5Index *pIndex; /* Full-text index */ +}; + +static int sqlite3Fts5GetTokenizer( + Fts5Global*, + const char **azArg, + int nArg, + Fts5Config*, + char **pzErr +); + +static Fts5Table *sqlite3Fts5TableFromCsrid(Fts5Global*, i64); + +static int sqlite3Fts5FlushToDisk(Fts5Table*); + +/* +** End of interface to code in fts5.c. +**************************************************************************/ + +/************************************************************************** +** Interface to code in fts5_hash.c. +*/ +typedef struct Fts5Hash Fts5Hash; + +/* +** Create a hash table, free a hash table. +*/ +static int sqlite3Fts5HashNew(Fts5Config*, Fts5Hash**, int *pnSize); +static void sqlite3Fts5HashFree(Fts5Hash*); + +static int sqlite3Fts5HashWrite( + Fts5Hash*, + i64 iRowid, /* Rowid for this entry */ + int iCol, /* Column token appears in (-ve -> delete) */ + int iPos, /* Position of token within column */ + char bByte, + const char *pToken, int nToken /* Token to add or remove to or from index */ +); + +/* +** Empty (but do not delete) a hash table. +*/ +static void sqlite3Fts5HashClear(Fts5Hash*); + +/* +** Return true if the hash is empty, false otherwise. +*/ +static int sqlite3Fts5HashIsEmpty(Fts5Hash*); + +static int sqlite3Fts5HashQuery( + Fts5Hash*, /* Hash table to query */ + int nPre, + const char *pTerm, int nTerm, /* Query term */ + void **ppObj, /* OUT: Pointer to doclist for pTerm */ + int *pnDoclist /* OUT: Size of doclist in bytes */ +); + +static int sqlite3Fts5HashScanInit( + Fts5Hash*, /* Hash table to query */ + const char *pTerm, int nTerm /* Query prefix */ +); +static void sqlite3Fts5HashScanNext(Fts5Hash*); +static int sqlite3Fts5HashScanEof(Fts5Hash*); +static void sqlite3Fts5HashScanEntry(Fts5Hash *, + const char **pzTerm, /* OUT: term (nul-terminated) */ + const u8 **ppDoclist, /* OUT: pointer to doclist */ + int *pnDoclist /* OUT: size of doclist in bytes */ +); + + + +/* +** End of interface to code in fts5_hash.c. +**************************************************************************/ + +/************************************************************************** +** Interface to code in fts5_storage.c. fts5_storage.c contains contains +** code to access the data stored in the %_content and %_docsize tables. +*/ + +#define FTS5_STMT_SCAN_ASC 0 /* SELECT rowid, * FROM ... ORDER BY 1 ASC */ +#define FTS5_STMT_SCAN_DESC 1 /* SELECT rowid, * FROM ... ORDER BY 1 DESC */ +#define FTS5_STMT_LOOKUP 2 /* SELECT rowid, * FROM ... WHERE rowid=? */ + +typedef struct Fts5Storage Fts5Storage; + +static int sqlite3Fts5StorageOpen(Fts5Config*, Fts5Index*, int, Fts5Storage**, char**); +static int sqlite3Fts5StorageClose(Fts5Storage *p); +static int sqlite3Fts5StorageRename(Fts5Storage*, const char *zName); + +static int sqlite3Fts5DropAll(Fts5Config*); +static int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **); + +static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**); +static int sqlite3Fts5StorageContentInsert(Fts5Storage *p, sqlite3_value**, i64*); +static int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64); + +static int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg); + +static int sqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, sqlite3_stmt**, char**); +static void sqlite3Fts5StorageStmtRelease(Fts5Storage *p, int eStmt, sqlite3_stmt*); + +static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol); +static int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnAvg); +static int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow); + +static int sqlite3Fts5StorageSync(Fts5Storage *p); +static int sqlite3Fts5StorageRollback(Fts5Storage *p); + +static int sqlite3Fts5StorageConfigValue( + Fts5Storage *p, const char*, sqlite3_value*, int +); + +static int sqlite3Fts5StorageDeleteAll(Fts5Storage *p); +static int sqlite3Fts5StorageRebuild(Fts5Storage *p); +static int sqlite3Fts5StorageOptimize(Fts5Storage *p); +static int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge); +static int sqlite3Fts5StorageReset(Fts5Storage *p); + +/* +** End of interface to code in fts5_storage.c. +**************************************************************************/ + + +/************************************************************************** +** Interface to code in fts5_expr.c. +*/ +typedef struct Fts5Expr Fts5Expr; +typedef struct Fts5ExprNode Fts5ExprNode; +typedef struct Fts5Parse Fts5Parse; +typedef struct Fts5Token Fts5Token; +typedef struct Fts5ExprPhrase Fts5ExprPhrase; +typedef struct Fts5ExprNearset Fts5ExprNearset; + +struct Fts5Token { + const char *p; /* Token text (not NULL terminated) */ + int n; /* Size of buffer p in bytes */ +}; + +/* Parse a MATCH expression. */ +static int sqlite3Fts5ExprNew( + Fts5Config *pConfig, + int bPhraseToAnd, + int iCol, /* Column on LHS of MATCH operator */ + const char *zExpr, + Fts5Expr **ppNew, + char **pzErr +); +static int sqlite3Fts5ExprPattern( + Fts5Config *pConfig, + int bGlob, + int iCol, + const char *zText, + Fts5Expr **pp +); + +/* +** for(rc = sqlite3Fts5ExprFirst(pExpr, pIdx, bDesc); +** rc==SQLITE_OK && 0==sqlite3Fts5ExprEof(pExpr); +** rc = sqlite3Fts5ExprNext(pExpr) +** ){ +** // The document with rowid iRowid matches the expression! +** i64 iRowid = sqlite3Fts5ExprRowid(pExpr); +** } +*/ +static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, int bDesc); +static int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax); +static int sqlite3Fts5ExprEof(Fts5Expr*); +static i64 sqlite3Fts5ExprRowid(Fts5Expr*); + +static void sqlite3Fts5ExprFree(Fts5Expr*); +static int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2); + +/* Called during startup to register a UDF with SQLite */ +static int sqlite3Fts5ExprInit(Fts5Global*, sqlite3*); + +static int sqlite3Fts5ExprPhraseCount(Fts5Expr*); +static int sqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase); +static int sqlite3Fts5ExprPoslist(Fts5Expr*, int, const u8 **); + +typedef struct Fts5PoslistPopulator Fts5PoslistPopulator; +static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr*, int); +static int sqlite3Fts5ExprPopulatePoslists( + Fts5Config*, Fts5Expr*, Fts5PoslistPopulator*, int, const char*, int +); +static void sqlite3Fts5ExprCheckPoslists(Fts5Expr*, i64); + +static int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**); + +static int sqlite3Fts5ExprPhraseCollist(Fts5Expr *, int, const u8 **, int *); + +/******************************************* +** The fts5_expr.c API above this point is used by the other hand-written +** C code in this module. The interfaces below this point are called by +** the parser code in fts5parse.y. */ + +static void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...); + +static Fts5ExprNode *sqlite3Fts5ParseNode( + Fts5Parse *pParse, + int eType, + Fts5ExprNode *pLeft, + Fts5ExprNode *pRight, + Fts5ExprNearset *pNear +); + +static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( + Fts5Parse *pParse, + Fts5ExprNode *pLeft, + Fts5ExprNode *pRight +); + +static Fts5ExprPhrase *sqlite3Fts5ParseTerm( + Fts5Parse *pParse, + Fts5ExprPhrase *pPhrase, + Fts5Token *pToken, + int bPrefix +); + +static void sqlite3Fts5ParseSetCaret(Fts5ExprPhrase*); + +static Fts5ExprNearset *sqlite3Fts5ParseNearset( + Fts5Parse*, + Fts5ExprNearset*, + Fts5ExprPhrase* +); + +static Fts5Colset *sqlite3Fts5ParseColset( + Fts5Parse*, + Fts5Colset*, + Fts5Token * +); + +static void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase*); +static void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset*); +static void sqlite3Fts5ParseNodeFree(Fts5ExprNode*); + +static void sqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*); +static void sqlite3Fts5ParseSetColset(Fts5Parse*, Fts5ExprNode*, Fts5Colset*); +static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse*, Fts5Colset*); +static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p); +static void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*); + +/* +** End of interface to code in fts5_expr.c. +**************************************************************************/ + + + +/************************************************************************** +** Interface to code in fts5_aux.c. +*/ + +static int sqlite3Fts5AuxInit(fts5_api*); +/* +** End of interface to code in fts5_aux.c. +**************************************************************************/ + +/************************************************************************** +** Interface to code in fts5_tokenizer.c. +*/ + +static int sqlite3Fts5TokenizerInit(fts5_api*); +static int sqlite3Fts5TokenizerPattern( + int (*xCreate)(void*, const char**, int, Fts5Tokenizer**), + Fts5Tokenizer *pTok +); +/* +** End of interface to code in fts5_tokenizer.c. +**************************************************************************/ + +/************************************************************************** +** Interface to code in fts5_vocab.c. +*/ + +static int sqlite3Fts5VocabInit(Fts5Global*, sqlite3*); + +/* +** End of interface to code in fts5_vocab.c. +**************************************************************************/ + + +/************************************************************************** +** Interface to automatically generated code in fts5_unicode2.c. +*/ +static int sqlite3Fts5UnicodeIsdiacritic(int c); +static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic); + +static int sqlite3Fts5UnicodeCatParse(const char*, u8*); +static int sqlite3Fts5UnicodeCategory(u32 iCode); +static void sqlite3Fts5UnicodeAscii(u8*, u8*); +/* +** End of interface to code in fts5_unicode2.c. +**************************************************************************/ + +#endif + +#define FTS5_OR 1 +#define FTS5_AND 2 +#define FTS5_NOT 3 +#define FTS5_TERM 4 +#define FTS5_COLON 5 +#define FTS5_MINUS 6 +#define FTS5_LCP 7 +#define FTS5_RCP 8 +#define FTS5_STRING 9 +#define FTS5_LP 10 +#define FTS5_RP 11 +#define FTS5_CARET 12 +#define FTS5_COMMA 13 +#define FTS5_PLUS 14 +#define FTS5_STAR 15 + +/* This file is automatically generated by Lemon from input grammar +** source file "fts5parse.y". +*/ +/* +** 2000-05-29 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Driver template for the LEMON parser generator. +** +** The "lemon" program processes an LALR(1) input grammar file, then uses +** this template to construct a parser. The "lemon" program inserts text +** at each "%%" line. Also, any "P-a-r-s-e" identifer prefix (without the +** interstitial "-" characters) contained in this template is changed into +** the value of the %name directive from the grammar. Otherwise, the content +** of this template is copied straight through into the generate parser +** source file. +** +** The following is the concatenation of all %include directives from the +** input grammar file: +*/ +/************ Begin %include sections from the grammar ************************/ + +/* #include "fts5Int.h" */ +/* #include "fts5parse.h" */ + +/* +** Disable all error recovery processing in the parser push-down +** automaton. +*/ +#define fts5YYNOERRORRECOVERY 1 + +/* +** Make fts5yytestcase() the same as testcase() +*/ +#define fts5yytestcase(X) testcase(X) + +/* +** Indicate that sqlite3ParserFree() will never be called with a null +** pointer. +*/ +#define fts5YYPARSEFREENOTNULL 1 + +/* +** Alternative datatype for the argument to the malloc() routine passed +** into sqlite3ParserAlloc(). The default is size_t. +*/ +#define fts5YYMALLOCARGTYPE u64 + +/**************** End of %include directives **********************************/ +/* These constants specify the various numeric values for terminal symbols. +***************** Begin token definitions *************************************/ +#ifndef FTS5_OR +#define FTS5_OR 1 +#define FTS5_AND 2 +#define FTS5_NOT 3 +#define FTS5_TERM 4 +#define FTS5_COLON 5 +#define FTS5_MINUS 6 +#define FTS5_LCP 7 +#define FTS5_RCP 8 +#define FTS5_STRING 9 +#define FTS5_LP 10 +#define FTS5_RP 11 +#define FTS5_CARET 12 +#define FTS5_COMMA 13 +#define FTS5_PLUS 14 +#define FTS5_STAR 15 +#endif +/**************** End token definitions ***************************************/ + +/* The next sections is a series of control #defines. +** various aspects of the generated parser. +** fts5YYCODETYPE is the data type used to store the integer codes +** that represent terminal and non-terminal symbols. +** "unsigned char" is used if there are fewer than +** 256 symbols. Larger types otherwise. +** fts5YYNOCODE is a number of type fts5YYCODETYPE that is not used for +** any terminal or nonterminal symbol. +** fts5YYFALLBACK If defined, this indicates that one or more tokens +** (also known as: "terminal symbols") have fall-back +** values which should be used if the original symbol +** would not parse. This permits keywords to sometimes +** be used as identifiers, for example. +** fts5YYACTIONTYPE is the data type used for "action codes" - numbers +** that indicate what to do in response to the next +** token. +** sqlite3Fts5ParserFTS5TOKENTYPE is the data type used for minor type for terminal +** symbols. Background: A "minor type" is a semantic +** value associated with a terminal or non-terminal +** symbols. For example, for an "ID" terminal symbol, +** the minor type might be the name of the identifier. +** Each non-terminal can have a different minor type. +** Terminal symbols all have the same minor type, though. +** This macros defines the minor type for terminal +** symbols. +** fts5YYMINORTYPE is the data type used for all minor types. +** This is typically a union of many types, one of +** which is sqlite3Fts5ParserFTS5TOKENTYPE. The entry in the union +** for terminal symbols is called "fts5yy0". +** fts5YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** sqlite3Fts5ParserARG_SDECL A static variable declaration for the %extra_argument +** sqlite3Fts5ParserARG_PDECL A parameter declaration for the %extra_argument +** sqlite3Fts5ParserARG_PARAM Code to pass %extra_argument as a subroutine parameter +** sqlite3Fts5ParserARG_STORE Code to store %extra_argument into fts5yypParser +** sqlite3Fts5ParserARG_FETCH Code to extract %extra_argument from fts5yypParser +** sqlite3Fts5ParserCTX_* As sqlite3Fts5ParserARG_ except for %extra_context +** fts5YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +** fts5YYNSTATE the combined number of states. +** fts5YYNRULE the number of rules in the grammar +** fts5YYNFTS5TOKEN Number of terminal symbols +** fts5YY_MAX_SHIFT Maximum value for shift actions +** fts5YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions +** fts5YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions +** fts5YY_ERROR_ACTION The fts5yy_action[] code for syntax error +** fts5YY_ACCEPT_ACTION The fts5yy_action[] code for accept +** fts5YY_NO_ACTION The fts5yy_action[] code for no-op +** fts5YY_MIN_REDUCE Minimum value for reduce actions +** fts5YY_MAX_REDUCE Maximum value for reduce actions +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/************* Begin control #defines *****************************************/ +#define fts5YYCODETYPE unsigned char +#define fts5YYNOCODE 27 +#define fts5YYACTIONTYPE unsigned char +#define sqlite3Fts5ParserFTS5TOKENTYPE Fts5Token +typedef union { + int fts5yyinit; + sqlite3Fts5ParserFTS5TOKENTYPE fts5yy0; + int fts5yy4; + Fts5Colset* fts5yy11; + Fts5ExprNode* fts5yy24; + Fts5ExprNearset* fts5yy46; + Fts5ExprPhrase* fts5yy53; +} fts5YYMINORTYPE; +#ifndef fts5YYSTACKDEPTH +#define fts5YYSTACKDEPTH 100 +#endif +#define sqlite3Fts5ParserARG_SDECL Fts5Parse *pParse; +#define sqlite3Fts5ParserARG_PDECL ,Fts5Parse *pParse +#define sqlite3Fts5ParserARG_PARAM ,pParse +#define sqlite3Fts5ParserARG_FETCH Fts5Parse *pParse=fts5yypParser->pParse; +#define sqlite3Fts5ParserARG_STORE fts5yypParser->pParse=pParse; +#define sqlite3Fts5ParserCTX_SDECL +#define sqlite3Fts5ParserCTX_PDECL +#define sqlite3Fts5ParserCTX_PARAM +#define sqlite3Fts5ParserCTX_FETCH +#define sqlite3Fts5ParserCTX_STORE +#define fts5YYNSTATE 35 +#define fts5YYNRULE 28 +#define fts5YYNRULE_WITH_ACTION 28 +#define fts5YYNFTS5TOKEN 16 +#define fts5YY_MAX_SHIFT 34 +#define fts5YY_MIN_SHIFTREDUCE 52 +#define fts5YY_MAX_SHIFTREDUCE 79 +#define fts5YY_ERROR_ACTION 80 +#define fts5YY_ACCEPT_ACTION 81 +#define fts5YY_NO_ACTION 82 +#define fts5YY_MIN_REDUCE 83 +#define fts5YY_MAX_REDUCE 110 +/************* End control #defines *******************************************/ +#define fts5YY_NLOOKAHEAD ((int)(sizeof(fts5yy_lookahead)/sizeof(fts5yy_lookahead[0]))) + +/* Define the fts5yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define fts5yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the fts5yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef fts5yytestcase +# define fts5yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N <= fts5YY_MAX_SHIFT Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** N between fts5YY_MIN_SHIFTREDUCE Shift to an arbitrary state then +** and fts5YY_MAX_SHIFTREDUCE reduce by rule N-fts5YY_MIN_SHIFTREDUCE. +** +** N == fts5YY_ERROR_ACTION A syntax error has occurred. +** +** N == fts5YY_ACCEPT_ACTION The parser accepts its input. +** +** N == fts5YY_NO_ACTION No such action. Denotes unused +** slots in the fts5yy_action[] table. +** +** N between fts5YY_MIN_REDUCE Reduce by rule N-fts5YY_MIN_REDUCE +** and fts5YY_MAX_REDUCE +** +** The action table is constructed as a single large table named fts5yy_action[]. +** Given state S and lookahead X, the action is computed as either: +** +** (A) N = fts5yy_action[ fts5yy_shift_ofst[S] + X ] +** (B) N = fts5yy_default[S] +** +** The (A) formula is preferred. The B formula is used instead if +** fts5yy_lookahead[fts5yy_shift_ofst[S]+X] is not equal to X. +** +** The formulas above are for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the fts5yy_reduce_ofst[] array is used in place of +** the fts5yy_shift_ofst[] array. +** +** The following are the tables generated in this section: +** +** fts5yy_action[] A single table containing all actions. +** fts5yy_lookahead[] A table containing the lookahead for each entry in +** fts5yy_action. Used to detect hash collisions. +** fts5yy_shift_ofst[] For each state, the offset into fts5yy_action for +** shifting terminals. +** fts5yy_reduce_ofst[] For each state, the offset into fts5yy_action for +** shifting non-terminals after a reduce. +** fts5yy_default[] Default action for each state. +** +*********** Begin parsing tables **********************************************/ +#define fts5YY_ACTTAB_COUNT (105) +static const fts5YYACTIONTYPE fts5yy_action[] = { + /* 0 */ 81, 20, 96, 6, 28, 99, 98, 26, 26, 18, + /* 10 */ 96, 6, 28, 17, 98, 56, 26, 19, 96, 6, + /* 20 */ 28, 14, 98, 14, 26, 31, 92, 96, 6, 28, + /* 30 */ 108, 98, 25, 26, 21, 96, 6, 28, 78, 98, + /* 40 */ 58, 26, 29, 96, 6, 28, 107, 98, 22, 26, + /* 50 */ 24, 16, 12, 11, 1, 13, 13, 24, 16, 23, + /* 60 */ 11, 33, 34, 13, 97, 8, 27, 32, 98, 7, + /* 70 */ 26, 3, 4, 5, 3, 4, 5, 3, 83, 4, + /* 80 */ 5, 3, 63, 5, 3, 62, 12, 2, 86, 13, + /* 90 */ 9, 30, 10, 10, 54, 57, 75, 78, 78, 53, + /* 100 */ 57, 15, 82, 82, 71, +}; +static const fts5YYCODETYPE fts5yy_lookahead[] = { + /* 0 */ 16, 17, 18, 19, 20, 22, 22, 24, 24, 17, + /* 10 */ 18, 19, 20, 7, 22, 9, 24, 17, 18, 19, + /* 20 */ 20, 9, 22, 9, 24, 13, 17, 18, 19, 20, + /* 30 */ 26, 22, 24, 24, 17, 18, 19, 20, 15, 22, + /* 40 */ 9, 24, 17, 18, 19, 20, 26, 22, 21, 24, + /* 50 */ 6, 7, 9, 9, 10, 12, 12, 6, 7, 21, + /* 60 */ 9, 24, 25, 12, 18, 5, 20, 14, 22, 5, + /* 70 */ 24, 3, 1, 2, 3, 1, 2, 3, 0, 1, + /* 80 */ 2, 3, 11, 2, 3, 11, 9, 10, 5, 12, + /* 90 */ 23, 24, 10, 10, 8, 9, 9, 15, 15, 8, + /* 100 */ 9, 9, 27, 27, 11, 27, 27, 27, 27, 27, + /* 110 */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + /* 120 */ 27, +}; +#define fts5YY_SHIFT_COUNT (34) +#define fts5YY_SHIFT_MIN (0) +#define fts5YY_SHIFT_MAX (93) +static const unsigned char fts5yy_shift_ofst[] = { + /* 0 */ 44, 44, 44, 44, 44, 44, 51, 77, 43, 12, + /* 10 */ 14, 83, 82, 14, 23, 23, 31, 31, 71, 74, + /* 20 */ 78, 81, 86, 91, 6, 53, 53, 60, 64, 68, + /* 30 */ 53, 87, 92, 53, 93, +}; +#define fts5YY_REDUCE_COUNT (17) +#define fts5YY_REDUCE_MIN (-17) +#define fts5YY_REDUCE_MAX (67) +static const signed char fts5yy_reduce_ofst[] = { + /* 0 */ -16, -8, 0, 9, 17, 25, 46, -17, -17, 37, + /* 10 */ 67, 4, 4, 8, 4, 20, 27, 38, +}; +static const fts5YYACTIONTYPE fts5yy_default[] = { + /* 0 */ 80, 80, 80, 80, 80, 80, 95, 80, 80, 105, + /* 10 */ 80, 110, 110, 80, 110, 110, 80, 80, 80, 80, + /* 20 */ 80, 91, 80, 80, 80, 101, 100, 80, 80, 90, + /* 30 */ 103, 80, 80, 104, 80, +}; +/********** End of lemon-generated parsing tables *****************************/ + +/* The next table maps tokens (terminal symbols) into fallback tokens. +** If a construct like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +** +** This feature can be used, for example, to cause some keywords in a language +** to revert to identifiers if they keyword does not apply in the context where +** it appears. +*/ +#ifdef fts5YYFALLBACK +static const fts5YYCODETYPE fts5yyFallback[] = { +}; +#endif /* fts5YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +** +** After the "shift" half of a SHIFTREDUCE action, the stateno field +** actually contains the reduce action for the second half of the +** SHIFTREDUCE. +*/ +struct fts5yyStackEntry { + fts5YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */ + fts5YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + fts5YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct fts5yyStackEntry fts5yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct fts5yyParser { + fts5yyStackEntry *fts5yytos; /* Pointer to top element of the stack */ +#ifdef fts5YYTRACKMAXSTACKDEPTH + int fts5yyhwm; /* High-water mark of the stack */ +#endif +#ifndef fts5YYNOERRORRECOVERY + int fts5yyerrcnt; /* Shifts left before out of the error */ +#endif + sqlite3Fts5ParserARG_SDECL /* A place to hold %extra_argument */ + sqlite3Fts5ParserCTX_SDECL /* A place to hold %extra_context */ +#if fts5YYSTACKDEPTH<=0 + int fts5yystksz; /* Current side of the stack */ + fts5yyStackEntry *fts5yystack; /* The parser's stack */ + fts5yyStackEntry fts5yystk0; /* First stack entry */ +#else + fts5yyStackEntry fts5yystack[fts5YYSTACKDEPTH]; /* The parser's stack */ + fts5yyStackEntry *fts5yystackEnd; /* Last entry in the stack */ +#endif +}; +typedef struct fts5yyParser fts5yyParser; + +/* #include <assert.h> */ +#ifndef NDEBUG +/* #include <stdio.h> */ +static FILE *fts5yyTraceFILE = 0; +static char *fts5yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +** <ul> +** <li> A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +** <li> A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +** </ul> +** +** Outputs: +** None. +*/ +static void sqlite3Fts5ParserTrace(FILE *TraceFILE, char *zTracePrompt){ + fts5yyTraceFILE = TraceFILE; + fts5yyTracePrompt = zTracePrompt; + if( fts5yyTraceFILE==0 ) fts5yyTracePrompt = 0; + else if( fts5yyTracePrompt==0 ) fts5yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#if defined(fts5YYCOVERAGE) || !defined(NDEBUG) +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const fts5yyTokenName[] = { + /* 0 */ "$", + /* 1 */ "OR", + /* 2 */ "AND", + /* 3 */ "NOT", + /* 4 */ "TERM", + /* 5 */ "COLON", + /* 6 */ "MINUS", + /* 7 */ "LCP", + /* 8 */ "RCP", + /* 9 */ "STRING", + /* 10 */ "LP", + /* 11 */ "RP", + /* 12 */ "CARET", + /* 13 */ "COMMA", + /* 14 */ "PLUS", + /* 15 */ "STAR", + /* 16 */ "input", + /* 17 */ "expr", + /* 18 */ "cnearset", + /* 19 */ "exprlist", + /* 20 */ "colset", + /* 21 */ "colsetlist", + /* 22 */ "nearset", + /* 23 */ "nearphrases", + /* 24 */ "phrase", + /* 25 */ "neardist_opt", + /* 26 */ "star_opt", +}; +#endif /* defined(fts5YYCOVERAGE) || !defined(NDEBUG) */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const fts5yyRuleName[] = { + /* 0 */ "input ::= expr", + /* 1 */ "colset ::= MINUS LCP colsetlist RCP", + /* 2 */ "colset ::= LCP colsetlist RCP", + /* 3 */ "colset ::= STRING", + /* 4 */ "colset ::= MINUS STRING", + /* 5 */ "colsetlist ::= colsetlist STRING", + /* 6 */ "colsetlist ::= STRING", + /* 7 */ "expr ::= expr AND expr", + /* 8 */ "expr ::= expr OR expr", + /* 9 */ "expr ::= expr NOT expr", + /* 10 */ "expr ::= colset COLON LP expr RP", + /* 11 */ "expr ::= LP expr RP", + /* 12 */ "expr ::= exprlist", + /* 13 */ "exprlist ::= cnearset", + /* 14 */ "exprlist ::= exprlist cnearset", + /* 15 */ "cnearset ::= nearset", + /* 16 */ "cnearset ::= colset COLON nearset", + /* 17 */ "nearset ::= phrase", + /* 18 */ "nearset ::= CARET phrase", + /* 19 */ "nearset ::= STRING LP nearphrases neardist_opt RP", + /* 20 */ "nearphrases ::= phrase", + /* 21 */ "nearphrases ::= nearphrases phrase", + /* 22 */ "neardist_opt ::=", + /* 23 */ "neardist_opt ::= COMMA STRING", + /* 24 */ "phrase ::= phrase PLUS STRING star_opt", + /* 25 */ "phrase ::= STRING star_opt", + /* 26 */ "star_opt ::= STAR", + /* 27 */ "star_opt ::=", +}; +#endif /* NDEBUG */ + + +#if fts5YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. Return the number +** of errors. Return 0 on success. +*/ +static int fts5yyGrowStack(fts5yyParser *p){ + int newSize; + int idx; + fts5yyStackEntry *pNew; + + newSize = p->fts5yystksz*2 + 100; + idx = p->fts5yytos ? (int)(p->fts5yytos - p->fts5yystack) : 0; + if( p->fts5yystack==&p->fts5yystk0 ){ + pNew = malloc(newSize*sizeof(pNew[0])); + if( pNew ) pNew[0] = p->fts5yystk0; + }else{ + pNew = realloc(p->fts5yystack, newSize*sizeof(pNew[0])); + } + if( pNew ){ + p->fts5yystack = pNew; + p->fts5yytos = &p->fts5yystack[idx]; +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE,"%sStack grows from %d to %d entries.\n", + fts5yyTracePrompt, p->fts5yystksz, newSize); + } +#endif + p->fts5yystksz = newSize; + } + return pNew==0; +} +#endif + +/* Datatype of the argument to the memory allocated passed as the +** second argument to sqlite3Fts5ParserAlloc() below. This can be changed by +** putting an appropriate #define in the %include section of the input +** grammar. +*/ +#ifndef fts5YYMALLOCARGTYPE +# define fts5YYMALLOCARGTYPE size_t +#endif + +/* Initialize a new parser that has already been allocated. +*/ +static void sqlite3Fts5ParserInit(void *fts5yypRawParser sqlite3Fts5ParserCTX_PDECL){ + fts5yyParser *fts5yypParser = (fts5yyParser*)fts5yypRawParser; + sqlite3Fts5ParserCTX_STORE +#ifdef fts5YYTRACKMAXSTACKDEPTH + fts5yypParser->fts5yyhwm = 0; +#endif +#if fts5YYSTACKDEPTH<=0 + fts5yypParser->fts5yytos = NULL; + fts5yypParser->fts5yystack = NULL; + fts5yypParser->fts5yystksz = 0; + if( fts5yyGrowStack(fts5yypParser) ){ + fts5yypParser->fts5yystack = &fts5yypParser->fts5yystk0; + fts5yypParser->fts5yystksz = 1; + } +#endif +#ifndef fts5YYNOERRORRECOVERY + fts5yypParser->fts5yyerrcnt = -1; +#endif + fts5yypParser->fts5yytos = fts5yypParser->fts5yystack; + fts5yypParser->fts5yystack[0].stateno = 0; + fts5yypParser->fts5yystack[0].major = 0; +#if fts5YYSTACKDEPTH>0 + fts5yypParser->fts5yystackEnd = &fts5yypParser->fts5yystack[fts5YYSTACKDEPTH-1]; +#endif +} + +#ifndef sqlite3Fts5Parser_ENGINEALWAYSONSTACK +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to sqlite3Fts5Parser and sqlite3Fts5ParserFree. +*/ +static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(fts5YYMALLOCARGTYPE) sqlite3Fts5ParserCTX_PDECL){ + fts5yyParser *fts5yypParser; + fts5yypParser = (fts5yyParser*)(*mallocProc)( (fts5YYMALLOCARGTYPE)sizeof(fts5yyParser) ); + if( fts5yypParser ){ + sqlite3Fts5ParserCTX_STORE + sqlite3Fts5ParserInit(fts5yypParser sqlite3Fts5ParserCTX_PARAM); + } + return (void*)fts5yypParser; +} +#endif /* sqlite3Fts5Parser_ENGINEALWAYSONSTACK */ + + +/* The following function deletes the "minor type" or semantic value +** associated with a symbol. The symbol can be either a terminal +** or nonterminal. "fts5yymajor" is the symbol code, and "fts5yypminor" is +** a pointer to the value to be deleted. The code used to do the +** deletions is derived from the %destructor and/or %token_destructor +** directives of the input grammar. +*/ +static void fts5yy_destructor( + fts5yyParser *fts5yypParser, /* The parser */ + fts5YYCODETYPE fts5yymajor, /* Type code for object to destroy */ + fts5YYMINORTYPE *fts5yypminor /* The object to be destroyed */ +){ + sqlite3Fts5ParserARG_FETCH + sqlite3Fts5ParserCTX_FETCH + switch( fts5yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are *not* used + ** inside the C code. + */ +/********* Begin destructor definitions ***************************************/ + case 16: /* input */ +{ + (void)pParse; +} + break; + case 17: /* expr */ + case 18: /* cnearset */ + case 19: /* exprlist */ +{ + sqlite3Fts5ParseNodeFree((fts5yypminor->fts5yy24)); +} + break; + case 20: /* colset */ + case 21: /* colsetlist */ +{ + sqlite3_free((fts5yypminor->fts5yy11)); +} + break; + case 22: /* nearset */ + case 23: /* nearphrases */ +{ + sqlite3Fts5ParseNearsetFree((fts5yypminor->fts5yy46)); +} + break; + case 24: /* phrase */ +{ + sqlite3Fts5ParsePhraseFree((fts5yypminor->fts5yy53)); +} + break; +/********* End destructor definitions *****************************************/ + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +*/ +static void fts5yy_pop_parser_stack(fts5yyParser *pParser){ + fts5yyStackEntry *fts5yytos; + assert( pParser->fts5yytos!=0 ); + assert( pParser->fts5yytos > pParser->fts5yystack ); + fts5yytos = pParser->fts5yytos--; +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE,"%sPopping %s\n", + fts5yyTracePrompt, + fts5yyTokenName[fts5yytos->major]); + } +#endif + fts5yy_destructor(pParser, fts5yytos->major, &fts5yytos->minor); +} + +/* +** Clear all secondary memory allocations from the parser +*/ +static void sqlite3Fts5ParserFinalize(void *p){ + fts5yyParser *pParser = (fts5yyParser*)p; + while( pParser->fts5yytos>pParser->fts5yystack ) fts5yy_pop_parser_stack(pParser); +#if fts5YYSTACKDEPTH<=0 + if( pParser->fts5yystack!=&pParser->fts5yystk0 ) free(pParser->fts5yystack); +#endif +} + +#ifndef sqlite3Fts5Parser_ENGINEALWAYSONSTACK +/* +** Deallocate and destroy a parser. Destructors are called for +** all stack elements before shutting the parser down. +** +** If the fts5YYPARSEFREENEVERNULL macro exists (for example because it +** is defined in a %include section of the input grammar) then it is +** assumed that the input pointer is never NULL. +*/ +static void sqlite3Fts5ParserFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ +#ifndef fts5YYPARSEFREENEVERNULL + if( p==0 ) return; +#endif + sqlite3Fts5ParserFinalize(p); + (*freeProc)(p); +} +#endif /* sqlite3Fts5Parser_ENGINEALWAYSONSTACK */ + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef fts5YYTRACKMAXSTACKDEPTH +static int sqlite3Fts5ParserStackPeak(void *p){ + fts5yyParser *pParser = (fts5yyParser*)p; + return pParser->fts5yyhwm; +} +#endif + +/* This array of booleans keeps track of the parser statement +** coverage. The element fts5yycoverage[X][Y] is set when the parser +** is in state X and has a lookahead token Y. In a well-tested +** systems, every element of this matrix should end up being set. +*/ +#if defined(fts5YYCOVERAGE) +static unsigned char fts5yycoverage[fts5YYNSTATE][fts5YYNFTS5TOKEN]; +#endif + +/* +** Write into out a description of every state/lookahead combination that +** +** (1) has not been used by the parser, and +** (2) is not a syntax error. +** +** Return the number of missed state/lookahead combinations. +*/ +#if defined(fts5YYCOVERAGE) +static int sqlite3Fts5ParserCoverage(FILE *out){ + int stateno, iLookAhead, i; + int nMissed = 0; + for(stateno=0; stateno<fts5YYNSTATE; stateno++){ + i = fts5yy_shift_ofst[stateno]; + for(iLookAhead=0; iLookAhead<fts5YYNFTS5TOKEN; iLookAhead++){ + if( fts5yy_lookahead[i+iLookAhead]!=iLookAhead ) continue; + if( fts5yycoverage[stateno][iLookAhead]==0 ) nMissed++; + if( out ){ + fprintf(out,"State %d lookahead %s %s\n", stateno, + fts5yyTokenName[iLookAhead], + fts5yycoverage[stateno][iLookAhead] ? "ok" : "missed"); + } + } + } + return nMissed; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +*/ +static fts5YYACTIONTYPE fts5yy_find_shift_action( + fts5YYCODETYPE iLookAhead, /* The look-ahead token */ + fts5YYACTIONTYPE stateno /* Current state number */ +){ + int i; + + if( stateno>fts5YY_MAX_SHIFT ) return stateno; + assert( stateno <= fts5YY_SHIFT_COUNT ); +#if defined(fts5YYCOVERAGE) + fts5yycoverage[stateno][iLookAhead] = 1; +#endif + do{ + i = fts5yy_shift_ofst[stateno]; + assert( i>=0 ); + assert( i<=fts5YY_ACTTAB_COUNT ); + assert( i+fts5YYNFTS5TOKEN<=(int)fts5YY_NLOOKAHEAD ); + assert( iLookAhead!=fts5YYNOCODE ); + assert( iLookAhead < fts5YYNFTS5TOKEN ); + i += iLookAhead; + assert( i<(int)fts5YY_NLOOKAHEAD ); + if( fts5yy_lookahead[i]!=iLookAhead ){ +#ifdef fts5YYFALLBACK + fts5YYCODETYPE iFallback; /* Fallback token */ + assert( iLookAhead<sizeof(fts5yyFallback)/sizeof(fts5yyFallback[0]) ); + iFallback = fts5yyFallback[iLookAhead]; + if( iFallback!=0 ){ +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE, "%sFALLBACK %s => %s\n", + fts5yyTracePrompt, fts5yyTokenName[iLookAhead], fts5yyTokenName[iFallback]); + } +#endif + assert( fts5yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } +#endif +#ifdef fts5YYWILDCARD + { + int j = i - iLookAhead + fts5YYWILDCARD; + assert( j<(int)(sizeof(fts5yy_lookahead)/sizeof(fts5yy_lookahead[0])) ); + if( fts5yy_lookahead[j]==fts5YYWILDCARD && iLookAhead>0 ){ +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE, "%sWILDCARD %s => %s\n", + fts5yyTracePrompt, fts5yyTokenName[iLookAhead], + fts5yyTokenName[fts5YYWILDCARD]); + } +#endif /* NDEBUG */ + return fts5yy_action[j]; + } + } +#endif /* fts5YYWILDCARD */ + return fts5yy_default[stateno]; + }else{ + assert( i>=0 && i<(int)(sizeof(fts5yy_action)/sizeof(fts5yy_action[0])) ); + return fts5yy_action[i]; + } + }while(1); +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +*/ +static fts5YYACTIONTYPE fts5yy_find_reduce_action( + fts5YYACTIONTYPE stateno, /* Current state number */ + fts5YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef fts5YYERRORSYMBOL + if( stateno>fts5YY_REDUCE_COUNT ){ + return fts5yy_default[stateno]; + } +#else + assert( stateno<=fts5YY_REDUCE_COUNT ); +#endif + i = fts5yy_reduce_ofst[stateno]; + assert( iLookAhead!=fts5YYNOCODE ); + i += iLookAhead; +#ifdef fts5YYERRORSYMBOL + if( i<0 || i>=fts5YY_ACTTAB_COUNT || fts5yy_lookahead[i]!=iLookAhead ){ + return fts5yy_default[stateno]; + } +#else + assert( i>=0 && i<fts5YY_ACTTAB_COUNT ); + assert( fts5yy_lookahead[i]==iLookAhead ); +#endif + return fts5yy_action[i]; +} + +/* +** The following routine is called if the stack overflows. +*/ +static void fts5yyStackOverflow(fts5yyParser *fts5yypParser){ + sqlite3Fts5ParserARG_FETCH + sqlite3Fts5ParserCTX_FETCH +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE,"%sStack Overflow!\n",fts5yyTracePrompt); + } +#endif + while( fts5yypParser->fts5yytos>fts5yypParser->fts5yystack ) fts5yy_pop_parser_stack(fts5yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +/******** Begin %stack_overflow code ******************************************/ + + sqlite3Fts5ParseError(pParse, "fts5: parser stack overflow"); +/******** End %stack_overflow code ********************************************/ + sqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument var */ + sqlite3Fts5ParserCTX_STORE +} + +/* +** Print tracing information for a SHIFT action +*/ +#ifndef NDEBUG +static void fts5yyTraceShift(fts5yyParser *fts5yypParser, int fts5yyNewState, const char *zTag){ + if( fts5yyTraceFILE ){ + if( fts5yyNewState<fts5YYNSTATE ){ + fprintf(fts5yyTraceFILE,"%s%s '%s', go to state %d\n", + fts5yyTracePrompt, zTag, fts5yyTokenName[fts5yypParser->fts5yytos->major], + fts5yyNewState); + }else{ + fprintf(fts5yyTraceFILE,"%s%s '%s', pending reduce %d\n", + fts5yyTracePrompt, zTag, fts5yyTokenName[fts5yypParser->fts5yytos->major], + fts5yyNewState - fts5YY_MIN_REDUCE); + } + } +} +#else +# define fts5yyTraceShift(X,Y,Z) +#endif + +/* +** Perform a shift action. +*/ +static void fts5yy_shift( + fts5yyParser *fts5yypParser, /* The parser to be shifted */ + fts5YYACTIONTYPE fts5yyNewState, /* The new state to shift in */ + fts5YYCODETYPE fts5yyMajor, /* The major token to shift in */ + sqlite3Fts5ParserFTS5TOKENTYPE fts5yyMinor /* The minor token to shift in */ +){ + fts5yyStackEntry *fts5yytos; + fts5yypParser->fts5yytos++; +#ifdef fts5YYTRACKMAXSTACKDEPTH + if( (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)>fts5yypParser->fts5yyhwm ){ + fts5yypParser->fts5yyhwm++; + assert( fts5yypParser->fts5yyhwm == (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack) ); + } +#endif +#if fts5YYSTACKDEPTH>0 + if( fts5yypParser->fts5yytos>fts5yypParser->fts5yystackEnd ){ + fts5yypParser->fts5yytos--; + fts5yyStackOverflow(fts5yypParser); + return; + } +#else + if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5yypParser->fts5yystksz] ){ + if( fts5yyGrowStack(fts5yypParser) ){ + fts5yypParser->fts5yytos--; + fts5yyStackOverflow(fts5yypParser); + return; + } + } +#endif + if( fts5yyNewState > fts5YY_MAX_SHIFT ){ + fts5yyNewState += fts5YY_MIN_REDUCE - fts5YY_MIN_SHIFTREDUCE; + } + fts5yytos = fts5yypParser->fts5yytos; + fts5yytos->stateno = fts5yyNewState; + fts5yytos->major = fts5yyMajor; + fts5yytos->minor.fts5yy0 = fts5yyMinor; + fts5yyTraceShift(fts5yypParser, fts5yyNewState, "Shift"); +} + +/* For rule J, fts5yyRuleInfoLhs[J] contains the symbol on the left-hand side +** of that rule */ +static const fts5YYCODETYPE fts5yyRuleInfoLhs[] = { + 16, /* (0) input ::= expr */ + 20, /* (1) colset ::= MINUS LCP colsetlist RCP */ + 20, /* (2) colset ::= LCP colsetlist RCP */ + 20, /* (3) colset ::= STRING */ + 20, /* (4) colset ::= MINUS STRING */ + 21, /* (5) colsetlist ::= colsetlist STRING */ + 21, /* (6) colsetlist ::= STRING */ + 17, /* (7) expr ::= expr AND expr */ + 17, /* (8) expr ::= expr OR expr */ + 17, /* (9) expr ::= expr NOT expr */ + 17, /* (10) expr ::= colset COLON LP expr RP */ + 17, /* (11) expr ::= LP expr RP */ + 17, /* (12) expr ::= exprlist */ + 19, /* (13) exprlist ::= cnearset */ + 19, /* (14) exprlist ::= exprlist cnearset */ + 18, /* (15) cnearset ::= nearset */ + 18, /* (16) cnearset ::= colset COLON nearset */ + 22, /* (17) nearset ::= phrase */ + 22, /* (18) nearset ::= CARET phrase */ + 22, /* (19) nearset ::= STRING LP nearphrases neardist_opt RP */ + 23, /* (20) nearphrases ::= phrase */ + 23, /* (21) nearphrases ::= nearphrases phrase */ + 25, /* (22) neardist_opt ::= */ + 25, /* (23) neardist_opt ::= COMMA STRING */ + 24, /* (24) phrase ::= phrase PLUS STRING star_opt */ + 24, /* (25) phrase ::= STRING star_opt */ + 26, /* (26) star_opt ::= STAR */ + 26, /* (27) star_opt ::= */ +}; + +/* For rule J, fts5yyRuleInfoNRhs[J] contains the negative of the number +** of symbols on the right-hand side of that rule. */ +static const signed char fts5yyRuleInfoNRhs[] = { + -1, /* (0) input ::= expr */ + -4, /* (1) colset ::= MINUS LCP colsetlist RCP */ + -3, /* (2) colset ::= LCP colsetlist RCP */ + -1, /* (3) colset ::= STRING */ + -2, /* (4) colset ::= MINUS STRING */ + -2, /* (5) colsetlist ::= colsetlist STRING */ + -1, /* (6) colsetlist ::= STRING */ + -3, /* (7) expr ::= expr AND expr */ + -3, /* (8) expr ::= expr OR expr */ + -3, /* (9) expr ::= expr NOT expr */ + -5, /* (10) expr ::= colset COLON LP expr RP */ + -3, /* (11) expr ::= LP expr RP */ + -1, /* (12) expr ::= exprlist */ + -1, /* (13) exprlist ::= cnearset */ + -2, /* (14) exprlist ::= exprlist cnearset */ + -1, /* (15) cnearset ::= nearset */ + -3, /* (16) cnearset ::= colset COLON nearset */ + -1, /* (17) nearset ::= phrase */ + -2, /* (18) nearset ::= CARET phrase */ + -5, /* (19) nearset ::= STRING LP nearphrases neardist_opt RP */ + -1, /* (20) nearphrases ::= phrase */ + -2, /* (21) nearphrases ::= nearphrases phrase */ + 0, /* (22) neardist_opt ::= */ + -2, /* (23) neardist_opt ::= COMMA STRING */ + -4, /* (24) phrase ::= phrase PLUS STRING star_opt */ + -2, /* (25) phrase ::= STRING star_opt */ + -1, /* (26) star_opt ::= STAR */ + 0, /* (27) star_opt ::= */ +}; + +static void fts5yy_accept(fts5yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +** +** The fts5yyLookahead and fts5yyLookaheadToken parameters provide reduce actions +** access to the lookahead token (if any). The fts5yyLookahead will be fts5YYNOCODE +** if the lookahead token has already been consumed. As this procedure is +** only called from one place, optimizing compilers will in-line it, which +** means that the extra parameters have no performance impact. +*/ +static fts5YYACTIONTYPE fts5yy_reduce( + fts5yyParser *fts5yypParser, /* The parser */ + unsigned int fts5yyruleno, /* Number of the rule by which to reduce */ + int fts5yyLookahead, /* Lookahead token, or fts5YYNOCODE if none */ + sqlite3Fts5ParserFTS5TOKENTYPE fts5yyLookaheadToken /* Value of the lookahead token */ + sqlite3Fts5ParserCTX_PDECL /* %extra_context */ +){ + int fts5yygoto; /* The next state */ + fts5YYACTIONTYPE fts5yyact; /* The next action */ + fts5yyStackEntry *fts5yymsp; /* The top of the parser's stack */ + int fts5yysize; /* Amount to pop the stack */ + sqlite3Fts5ParserARG_FETCH + (void)fts5yyLookahead; + (void)fts5yyLookaheadToken; + fts5yymsp = fts5yypParser->fts5yytos; + + switch( fts5yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line <lineno> <grammarfile> + ** { ... } // User supplied code + ** #line <lineno> <thisfile> + ** break; + */ +/********** Begin reduce actions **********************************************/ + fts5YYMINORTYPE fts5yylhsminor; + case 0: /* input ::= expr */ +{ sqlite3Fts5ParseFinished(pParse, fts5yymsp[0].minor.fts5yy24); } + break; + case 1: /* colset ::= MINUS LCP colsetlist RCP */ +{ + fts5yymsp[-3].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11); +} + break; + case 2: /* colset ::= LCP colsetlist RCP */ +{ fts5yymsp[-2].minor.fts5yy11 = fts5yymsp[-1].minor.fts5yy11; } + break; + case 3: /* colset ::= STRING */ +{ + fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); +} + fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11; + break; + case 4: /* colset ::= MINUS STRING */ +{ + fts5yymsp[-1].minor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); + fts5yymsp[-1].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11); +} + break; + case 5: /* colsetlist ::= colsetlist STRING */ +{ + fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, fts5yymsp[-1].minor.fts5yy11, &fts5yymsp[0].minor.fts5yy0); } + fts5yymsp[-1].minor.fts5yy11 = fts5yylhsminor.fts5yy11; + break; + case 6: /* colsetlist ::= STRING */ +{ + fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); +} + fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11; + break; + case 7: /* expr ::= expr AND expr */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); +} + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 8: /* expr ::= expr OR expr */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_OR, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); +} + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 9: /* expr ::= expr NOT expr */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_NOT, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); +} + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 10: /* expr ::= colset COLON LP expr RP */ +{ + sqlite3Fts5ParseSetColset(pParse, fts5yymsp[-1].minor.fts5yy24, fts5yymsp[-4].minor.fts5yy11); + fts5yylhsminor.fts5yy24 = fts5yymsp[-1].minor.fts5yy24; +} + fts5yymsp[-4].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 11: /* expr ::= LP expr RP */ +{fts5yymsp[-2].minor.fts5yy24 = fts5yymsp[-1].minor.fts5yy24;} + break; + case 12: /* expr ::= exprlist */ + case 13: /* exprlist ::= cnearset */ fts5yytestcase(fts5yyruleno==13); +{fts5yylhsminor.fts5yy24 = fts5yymsp[0].minor.fts5yy24;} + fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 14: /* exprlist ::= exprlist cnearset */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseImplicitAnd(pParse, fts5yymsp[-1].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24); +} + fts5yymsp[-1].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 15: /* cnearset ::= nearset */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); +} + fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 16: /* cnearset ::= colset COLON nearset */ +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); + sqlite3Fts5ParseSetColset(pParse, fts5yylhsminor.fts5yy24, fts5yymsp[-2].minor.fts5yy11); +} + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; + break; + case 17: /* nearset ::= phrase */ +{ fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); } + fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46; + break; + case 18: /* nearset ::= CARET phrase */ +{ + sqlite3Fts5ParseSetCaret(fts5yymsp[0].minor.fts5yy53); + fts5yymsp[-1].minor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); +} + break; + case 19: /* nearset ::= STRING LP nearphrases neardist_opt RP */ +{ + sqlite3Fts5ParseNear(pParse, &fts5yymsp[-4].minor.fts5yy0); + sqlite3Fts5ParseSetDistance(pParse, fts5yymsp[-2].minor.fts5yy46, &fts5yymsp[-1].minor.fts5yy0); + fts5yylhsminor.fts5yy46 = fts5yymsp[-2].minor.fts5yy46; +} + fts5yymsp[-4].minor.fts5yy46 = fts5yylhsminor.fts5yy46; + break; + case 20: /* nearphrases ::= phrase */ +{ + fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); +} + fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46; + break; + case 21: /* nearphrases ::= nearphrases phrase */ +{ + fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, fts5yymsp[-1].minor.fts5yy46, fts5yymsp[0].minor.fts5yy53); +} + fts5yymsp[-1].minor.fts5yy46 = fts5yylhsminor.fts5yy46; + break; + case 22: /* neardist_opt ::= */ +{ fts5yymsp[1].minor.fts5yy0.p = 0; fts5yymsp[1].minor.fts5yy0.n = 0; } + break; + case 23: /* neardist_opt ::= COMMA STRING */ +{ fts5yymsp[-1].minor.fts5yy0 = fts5yymsp[0].minor.fts5yy0; } + break; + case 24: /* phrase ::= phrase PLUS STRING star_opt */ +{ + fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, fts5yymsp[-3].minor.fts5yy53, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4); +} + fts5yymsp[-3].minor.fts5yy53 = fts5yylhsminor.fts5yy53; + break; + case 25: /* phrase ::= STRING star_opt */ +{ + fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, 0, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4); +} + fts5yymsp[-1].minor.fts5yy53 = fts5yylhsminor.fts5yy53; + break; + case 26: /* star_opt ::= STAR */ +{ fts5yymsp[0].minor.fts5yy4 = 1; } + break; + case 27: /* star_opt ::= */ +{ fts5yymsp[1].minor.fts5yy4 = 0; } + break; + default: + break; +/********** End reduce actions ************************************************/ + }; + assert( fts5yyruleno<sizeof(fts5yyRuleInfoLhs)/sizeof(fts5yyRuleInfoLhs[0]) ); + fts5yygoto = fts5yyRuleInfoLhs[fts5yyruleno]; + fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno]; + fts5yyact = fts5yy_find_reduce_action(fts5yymsp[fts5yysize].stateno,(fts5YYCODETYPE)fts5yygoto); + + /* There are no SHIFTREDUCE actions on nonterminals because the table + ** generator has simplified them to pure REDUCE actions. */ + assert( !(fts5yyact>fts5YY_MAX_SHIFT && fts5yyact<=fts5YY_MAX_SHIFTREDUCE) ); + + /* It is not possible for a REDUCE to be followed by an error */ + assert( fts5yyact!=fts5YY_ERROR_ACTION ); + + fts5yymsp += fts5yysize+1; + fts5yypParser->fts5yytos = fts5yymsp; + fts5yymsp->stateno = (fts5YYACTIONTYPE)fts5yyact; + fts5yymsp->major = (fts5YYCODETYPE)fts5yygoto; + fts5yyTraceShift(fts5yypParser, fts5yyact, "... then shift"); + return fts5yyact; +} + +/* +** The following code executes when the parse fails +*/ +#ifndef fts5YYNOERRORRECOVERY +static void fts5yy_parse_failed( + fts5yyParser *fts5yypParser /* The parser */ +){ + sqlite3Fts5ParserARG_FETCH + sqlite3Fts5ParserCTX_FETCH +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE,"%sFail!\n",fts5yyTracePrompt); + } +#endif + while( fts5yypParser->fts5yytos>fts5yypParser->fts5yystack ) fts5yy_pop_parser_stack(fts5yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +/************ Begin %parse_failure code ***************************************/ +/************ End %parse_failure code *****************************************/ + sqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument variable */ + sqlite3Fts5ParserCTX_STORE +} +#endif /* fts5YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void fts5yy_syntax_error( + fts5yyParser *fts5yypParser, /* The parser */ + int fts5yymajor, /* The major type of the error token */ + sqlite3Fts5ParserFTS5TOKENTYPE fts5yyminor /* The minor type of the error token */ +){ + sqlite3Fts5ParserARG_FETCH + sqlite3Fts5ParserCTX_FETCH +#define FTS5TOKEN fts5yyminor +/************ Begin %syntax_error code ****************************************/ + + UNUSED_PARAM(fts5yymajor); /* Silence a compiler warning */ + sqlite3Fts5ParseError( + pParse, "fts5: syntax error near \"%.*s\"",FTS5TOKEN.n,FTS5TOKEN.p + ); +/************ End %syntax_error code ******************************************/ + sqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument variable */ + sqlite3Fts5ParserCTX_STORE +} + +/* +** The following is executed when the parser accepts +*/ +static void fts5yy_accept( + fts5yyParser *fts5yypParser /* The parser */ +){ + sqlite3Fts5ParserARG_FETCH + sqlite3Fts5ParserCTX_FETCH +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE,"%sAccept!\n",fts5yyTracePrompt); + } +#endif +#ifndef fts5YYNOERRORRECOVERY + fts5yypParser->fts5yyerrcnt = -1; +#endif + assert( fts5yypParser->fts5yytos==fts5yypParser->fts5yystack ); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +/*********** Begin %parse_accept code *****************************************/ +/*********** End %parse_accept code *******************************************/ + sqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument variable */ + sqlite3Fts5ParserCTX_STORE +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "sqlite3Fts5ParserAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +** <ul> +** <li> A pointer to the parser (an opaque structure.) +** <li> The major token number. +** <li> The minor token number. +** <li> An option argument of a grammar-specified type. +** </ul> +** +** Outputs: +** None. +*/ +static void sqlite3Fts5Parser( + void *fts5yyp, /* The parser */ + int fts5yymajor, /* The major token code number */ + sqlite3Fts5ParserFTS5TOKENTYPE fts5yyminor /* The value for the token */ + sqlite3Fts5ParserARG_PDECL /* Optional %extra_argument parameter */ +){ + fts5YYMINORTYPE fts5yyminorunion; + fts5YYACTIONTYPE fts5yyact; /* The parser action. */ +#if !defined(fts5YYERRORSYMBOL) && !defined(fts5YYNOERRORRECOVERY) + int fts5yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef fts5YYERRORSYMBOL + int fts5yyerrorhit = 0; /* True if fts5yymajor has invoked an error */ +#endif + fts5yyParser *fts5yypParser = (fts5yyParser*)fts5yyp; /* The parser */ + sqlite3Fts5ParserCTX_FETCH + sqlite3Fts5ParserARG_STORE + + assert( fts5yypParser->fts5yytos!=0 ); +#if !defined(fts5YYERRORSYMBOL) && !defined(fts5YYNOERRORRECOVERY) + fts5yyendofinput = (fts5yymajor==0); +#endif + + fts5yyact = fts5yypParser->fts5yytos->stateno; +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + if( fts5yyact < fts5YY_MIN_REDUCE ){ + fprintf(fts5yyTraceFILE,"%sInput '%s' in state %d\n", + fts5yyTracePrompt,fts5yyTokenName[fts5yymajor],fts5yyact); + }else{ + fprintf(fts5yyTraceFILE,"%sInput '%s' with pending reduce %d\n", + fts5yyTracePrompt,fts5yyTokenName[fts5yymajor],fts5yyact-fts5YY_MIN_REDUCE); + } + } +#endif + + while(1){ /* Exit by "break" */ + assert( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystack ); + assert( fts5yyact==fts5yypParser->fts5yytos->stateno ); + fts5yyact = fts5yy_find_shift_action((fts5YYCODETYPE)fts5yymajor,fts5yyact); + if( fts5yyact >= fts5YY_MIN_REDUCE ){ + unsigned int fts5yyruleno = fts5yyact - fts5YY_MIN_REDUCE; /* Reduce by this rule */ +#ifndef NDEBUG + assert( fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) ); + if( fts5yyTraceFILE ){ + int fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno]; + if( fts5yysize ){ + fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n", + fts5yyTracePrompt, + fts5yyruleno, fts5yyRuleName[fts5yyruleno], + fts5yyruleno<fts5YYNRULE_WITH_ACTION ? "" : " without external action", + fts5yypParser->fts5yytos[fts5yysize].stateno); + }else{ + fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s.\n", + fts5yyTracePrompt, fts5yyruleno, fts5yyRuleName[fts5yyruleno], + fts5yyruleno<fts5YYNRULE_WITH_ACTION ? "" : " without external action"); + } + } +#endif /* NDEBUG */ + + /* Check that the stack is large enough to grow by a single entry + ** if the RHS of the rule is empty. This ensures that there is room + ** enough on the stack to push the LHS value */ + if( fts5yyRuleInfoNRhs[fts5yyruleno]==0 ){ +#ifdef fts5YYTRACKMAXSTACKDEPTH + if( (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)>fts5yypParser->fts5yyhwm ){ + fts5yypParser->fts5yyhwm++; + assert( fts5yypParser->fts5yyhwm == + (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)); + } +#endif +#if fts5YYSTACKDEPTH>0 + if( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystackEnd ){ + fts5yyStackOverflow(fts5yypParser); + break; + } +#else + if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5yypParser->fts5yystksz-1] ){ + if( fts5yyGrowStack(fts5yypParser) ){ + fts5yyStackOverflow(fts5yypParser); + break; + } + } +#endif + } + fts5yyact = fts5yy_reduce(fts5yypParser,fts5yyruleno,fts5yymajor,fts5yyminor sqlite3Fts5ParserCTX_PARAM); + }else if( fts5yyact <= fts5YY_MAX_SHIFTREDUCE ){ + fts5yy_shift(fts5yypParser,fts5yyact,(fts5YYCODETYPE)fts5yymajor,fts5yyminor); +#ifndef fts5YYNOERRORRECOVERY + fts5yypParser->fts5yyerrcnt--; +#endif + break; + }else if( fts5yyact==fts5YY_ACCEPT_ACTION ){ + fts5yypParser->fts5yytos--; + fts5yy_accept(fts5yypParser); + return; + }else{ + assert( fts5yyact == fts5YY_ERROR_ACTION ); + fts5yyminorunion.fts5yy0 = fts5yyminor; +#ifdef fts5YYERRORSYMBOL + int fts5yymx; +#endif +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE,"%sSyntax Error!\n",fts5yyTracePrompt); + } +#endif +#ifdef fts5YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( fts5yypParser->fts5yyerrcnt<0 ){ + fts5yy_syntax_error(fts5yypParser,fts5yymajor,fts5yyminor); + } + fts5yymx = fts5yypParser->fts5yytos->major; + if( fts5yymx==fts5YYERRORSYMBOL || fts5yyerrorhit ){ +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE,"%sDiscard input token %s\n", + fts5yyTracePrompt,fts5yyTokenName[fts5yymajor]); + } +#endif + fts5yy_destructor(fts5yypParser, (fts5YYCODETYPE)fts5yymajor, &fts5yyminorunion); + fts5yymajor = fts5YYNOCODE; + }else{ + while( fts5yypParser->fts5yytos > fts5yypParser->fts5yystack ){ + fts5yyact = fts5yy_find_reduce_action(fts5yypParser->fts5yytos->stateno, + fts5YYERRORSYMBOL); + if( fts5yyact<=fts5YY_MAX_SHIFTREDUCE ) break; + fts5yy_pop_parser_stack(fts5yypParser); + } + if( fts5yypParser->fts5yytos <= fts5yypParser->fts5yystack || fts5yymajor==0 ){ + fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion); + fts5yy_parse_failed(fts5yypParser); +#ifndef fts5YYNOERRORRECOVERY + fts5yypParser->fts5yyerrcnt = -1; +#endif + fts5yymajor = fts5YYNOCODE; + }else if( fts5yymx!=fts5YYERRORSYMBOL ){ + fts5yy_shift(fts5yypParser,fts5yyact,fts5YYERRORSYMBOL,fts5yyminor); + } + } + fts5yypParser->fts5yyerrcnt = 3; + fts5yyerrorhit = 1; + if( fts5yymajor==fts5YYNOCODE ) break; + fts5yyact = fts5yypParser->fts5yytos->stateno; +#elif defined(fts5YYNOERRORRECOVERY) + /* If the fts5YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + fts5yy_syntax_error(fts5yypParser,fts5yymajor, fts5yyminor); + fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion); + break; +#else /* fts5YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( fts5yypParser->fts5yyerrcnt<=0 ){ + fts5yy_syntax_error(fts5yypParser,fts5yymajor, fts5yyminor); + } + fts5yypParser->fts5yyerrcnt = 3; + fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion); + if( fts5yyendofinput ){ + fts5yy_parse_failed(fts5yypParser); +#ifndef fts5YYNOERRORRECOVERY + fts5yypParser->fts5yyerrcnt = -1; +#endif + } + break; +#endif + } + } +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fts5yyStackEntry *i; + char cDiv = '['; + fprintf(fts5yyTraceFILE,"%sReturn. Stack=",fts5yyTracePrompt); + for(i=&fts5yypParser->fts5yystack[1]; i<=fts5yypParser->fts5yytos; i++){ + fprintf(fts5yyTraceFILE,"%c%s", cDiv, fts5yyTokenName[i->major]); + cDiv = ' '; + } + fprintf(fts5yyTraceFILE,"]\n"); + } +#endif + return; +} + +/* +** Return the fallback token corresponding to canonical token iToken, or +** 0 if iToken has no fallback. +*/ +static int sqlite3Fts5ParserFallback(int iToken){ +#ifdef fts5YYFALLBACK + assert( iToken<(int)(sizeof(fts5yyFallback)/sizeof(fts5yyFallback[0])) ); + return fts5yyFallback[iToken]; +#else + (void)iToken; + return 0; +#endif +} + +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +*/ + + +/* #include "fts5Int.h" */ +#include <math.h> /* amalgamator: keep */ + +/* +** Object used to iterate through all "coalesced phrase instances" in +** a single column of the current row. If the phrase instances in the +** column being considered do not overlap, this object simply iterates +** through them. Or, if they do overlap (share one or more tokens in +** common), each set of overlapping instances is treated as a single +** match. See documentation for the highlight() auxiliary function for +** details. +** +** Usage is: +** +** for(rc = fts5CInstIterNext(pApi, pFts, iCol, &iter); +** (rc==SQLITE_OK && 0==fts5CInstIterEof(&iter); +** rc = fts5CInstIterNext(&iter) +** ){ +** printf("instance starts at %d, ends at %d\n", iter.iStart, iter.iEnd); +** } +** +*/ +typedef struct CInstIter CInstIter; +struct CInstIter { + const Fts5ExtensionApi *pApi; /* API offered by current FTS version */ + Fts5Context *pFts; /* First arg to pass to pApi functions */ + int iCol; /* Column to search */ + int iInst; /* Next phrase instance index */ + int nInst; /* Total number of phrase instances */ + + /* Output variables */ + int iStart; /* First token in coalesced phrase instance */ + int iEnd; /* Last token in coalesced phrase instance */ +}; + +/* +** Advance the iterator to the next coalesced phrase instance. Return +** an SQLite error code if an error occurs, or SQLITE_OK otherwise. +*/ +static int fts5CInstIterNext(CInstIter *pIter){ + int rc = SQLITE_OK; + pIter->iStart = -1; + pIter->iEnd = -1; + + while( rc==SQLITE_OK && pIter->iInst<pIter->nInst ){ + int ip; int ic; int io; + rc = pIter->pApi->xInst(pIter->pFts, pIter->iInst, &ip, &ic, &io); + if( rc==SQLITE_OK ){ + if( ic==pIter->iCol ){ + int iEnd = io - 1 + pIter->pApi->xPhraseSize(pIter->pFts, ip); + if( pIter->iStart<0 ){ + pIter->iStart = io; + pIter->iEnd = iEnd; + }else if( io<=pIter->iEnd ){ + if( iEnd>pIter->iEnd ) pIter->iEnd = iEnd; + }else{ + break; + } + } + pIter->iInst++; + } + } + + return rc; +} + +/* +** Initialize the iterator object indicated by the final parameter to +** iterate through coalesced phrase instances in column iCol. +*/ +static int fts5CInstIterInit( + const Fts5ExtensionApi *pApi, + Fts5Context *pFts, + int iCol, + CInstIter *pIter +){ + int rc; + + memset(pIter, 0, sizeof(CInstIter)); + pIter->pApi = pApi; + pIter->pFts = pFts; + pIter->iCol = iCol; + rc = pApi->xInstCount(pFts, &pIter->nInst); + + if( rc==SQLITE_OK ){ + rc = fts5CInstIterNext(pIter); + } + + return rc; +} + + + +/************************************************************************* +** Start of highlight() implementation. +*/ +typedef struct HighlightContext HighlightContext; +struct HighlightContext { + CInstIter iter; /* Coalesced Instance Iterator */ + int iPos; /* Current token offset in zIn[] */ + int iRangeStart; /* First token to include */ + int iRangeEnd; /* If non-zero, last token to include */ + const char *zOpen; /* Opening highlight */ + const char *zClose; /* Closing highlight */ + const char *zIn; /* Input text */ + int nIn; /* Size of input text in bytes */ + int iOff; /* Current offset within zIn[] */ + char *zOut; /* Output value */ +}; + +/* +** Append text to the HighlightContext output string - p->zOut. Argument +** z points to a buffer containing n bytes of text to append. If n is +** negative, everything up until the first '\0' is appended to the output. +** +** If *pRc is set to any value other than SQLITE_OK when this function is +** called, it is a no-op. If an error (i.e. an OOM condition) is encountered, +** *pRc is set to an error code before returning. +*/ +static void fts5HighlightAppend( + int *pRc, + HighlightContext *p, + const char *z, int n +){ + if( *pRc==SQLITE_OK && z ){ + if( n<0 ) n = (int)strlen(z); + p->zOut = sqlite3_mprintf("%z%.*s", p->zOut, n, z); + if( p->zOut==0 ) *pRc = SQLITE_NOMEM; + } +} + +/* +** Tokenizer callback used by implementation of highlight() function. +*/ +static int fts5HighlightCb( + void *pContext, /* Pointer to HighlightContext object */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStartOff, /* Start offset of token */ + int iEndOff /* End offset of token */ +){ + HighlightContext *p = (HighlightContext*)pContext; + int rc = SQLITE_OK; + int iPos; + + UNUSED_PARAM2(pToken, nToken); + + if( tflags & FTS5_TOKEN_COLOCATED ) return SQLITE_OK; + iPos = p->iPos++; + + if( p->iRangeEnd>=0 ){ + if( iPos<p->iRangeStart || iPos>p->iRangeEnd ) return SQLITE_OK; + if( p->iRangeStart && iPos==p->iRangeStart ) p->iOff = iStartOff; + } + + if( iPos==p->iter.iStart ){ + fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iStartOff - p->iOff); + fts5HighlightAppend(&rc, p, p->zOpen, -1); + p->iOff = iStartOff; + } + + if( iPos==p->iter.iEnd ){ + if( p->iRangeEnd>=0 && p->iter.iStart<p->iRangeStart ){ + fts5HighlightAppend(&rc, p, p->zOpen, -1); + } + fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); + fts5HighlightAppend(&rc, p, p->zClose, -1); + p->iOff = iEndOff; + if( rc==SQLITE_OK ){ + rc = fts5CInstIterNext(&p->iter); + } + } + + if( p->iRangeEnd>=0 && iPos==p->iRangeEnd ){ + fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); + p->iOff = iEndOff; + if( iPos>=p->iter.iStart && iPos<p->iter.iEnd ){ + fts5HighlightAppend(&rc, p, p->zClose, -1); + } + } + + return rc; +} + +/* +** Implementation of highlight() function. +*/ +static void fts5HighlightFunction( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + sqlite3_context *pCtx, /* Context for returning result/error */ + int nVal, /* Number of values in apVal[] array */ + sqlite3_value **apVal /* Array of trailing arguments */ +){ + HighlightContext ctx; + int rc; + int iCol; + + if( nVal!=3 ){ + const char *zErr = "wrong number of arguments to function highlight()"; + sqlite3_result_error(pCtx, zErr, -1); + return; + } + + iCol = sqlite3_value_int(apVal[0]); + memset(&ctx, 0, sizeof(HighlightContext)); + ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]); + ctx.zClose = (const char*)sqlite3_value_text(apVal[2]); + ctx.iRangeEnd = -1; + rc = pApi->xColumnText(pFts, iCol, &ctx.zIn, &ctx.nIn); + + if( ctx.zIn ){ + if( rc==SQLITE_OK ){ + rc = fts5CInstIterInit(pApi, pFts, iCol, &ctx.iter); + } + + if( rc==SQLITE_OK ){ + rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb); + } + fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff); + + if( rc==SQLITE_OK ){ + sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT); + } + sqlite3_free(ctx.zOut); + } + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(pCtx, rc); + } +} +/* +** End of highlight() implementation. +**************************************************************************/ + +/* +** Context object passed to the fts5SentenceFinderCb() function. +*/ +typedef struct Fts5SFinder Fts5SFinder; +struct Fts5SFinder { + int iPos; /* Current token position */ + int nFirstAlloc; /* Allocated size of aFirst[] */ + int nFirst; /* Number of entries in aFirst[] */ + int *aFirst; /* Array of first token in each sentence */ + const char *zDoc; /* Document being tokenized */ +}; + +/* +** Add an entry to the Fts5SFinder.aFirst[] array. Grow the array if +** necessary. Return SQLITE_OK if successful, or SQLITE_NOMEM if an +** error occurs. +*/ +static int fts5SentenceFinderAdd(Fts5SFinder *p, int iAdd){ + if( p->nFirstAlloc==p->nFirst ){ + int nNew = p->nFirstAlloc ? p->nFirstAlloc*2 : 64; + int *aNew; + + aNew = (int*)sqlite3_realloc64(p->aFirst, nNew*sizeof(int)); + if( aNew==0 ) return SQLITE_NOMEM; + p->aFirst = aNew; + p->nFirstAlloc = nNew; + } + p->aFirst[p->nFirst++] = iAdd; + return SQLITE_OK; +} + +/* +** This function is an xTokenize() callback used by the auxiliary snippet() +** function. Its job is to identify tokens that are the first in a sentence. +** For each such token, an entry is added to the SFinder.aFirst[] array. +*/ +static int fts5SentenceFinderCb( + void *pContext, /* Pointer to HighlightContext object */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStartOff, /* Start offset of token */ + int iEndOff /* End offset of token */ +){ + int rc = SQLITE_OK; + + UNUSED_PARAM2(pToken, nToken); + UNUSED_PARAM(iEndOff); + + if( (tflags & FTS5_TOKEN_COLOCATED)==0 ){ + Fts5SFinder *p = (Fts5SFinder*)pContext; + if( p->iPos>0 ){ + int i; + char c = 0; + for(i=iStartOff-1; i>=0; i--){ + c = p->zDoc[i]; + if( c!=' ' && c!='\t' && c!='\n' && c!='\r' ) break; + } + if( i!=iStartOff-1 && (c=='.' || c==':') ){ + rc = fts5SentenceFinderAdd(p, p->iPos); + } + }else{ + rc = fts5SentenceFinderAdd(p, 0); + } + p->iPos++; + } + return rc; +} + +static int fts5SnippetScore( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + int nDocsize, /* Size of column in tokens */ + unsigned char *aSeen, /* Array with one element per query phrase */ + int iCol, /* Column to score */ + int iPos, /* Starting offset to score */ + int nToken, /* Max tokens per snippet */ + int *pnScore, /* OUT: Score */ + int *piPos /* OUT: Adjusted offset */ +){ + int rc; + int i; + int ip = 0; + int ic = 0; + int iOff = 0; + int iFirst = -1; + int nInst; + int nScore = 0; + int iLast = 0; + sqlite3_int64 iEnd = (sqlite3_int64)iPos + nToken; + + rc = pApi->xInstCount(pFts, &nInst); + for(i=0; i<nInst && rc==SQLITE_OK; i++){ + rc = pApi->xInst(pFts, i, &ip, &ic, &iOff); + if( rc==SQLITE_OK && ic==iCol && iOff>=iPos && iOff<iEnd ){ + nScore += (aSeen[ip] ? 1 : 1000); + aSeen[ip] = 1; + if( iFirst<0 ) iFirst = iOff; + iLast = iOff + pApi->xPhraseSize(pFts, ip); + } + } + + *pnScore = nScore; + if( piPos ){ + sqlite3_int64 iAdj = iFirst - (nToken - (iLast-iFirst)) / 2; + if( (iAdj+nToken)>nDocsize ) iAdj = nDocsize - nToken; + if( iAdj<0 ) iAdj = 0; + *piPos = (int)iAdj; + } + + return rc; +} + +/* +** Return the value in pVal interpreted as utf-8 text. Except, if pVal +** contains a NULL value, return a pointer to a static string zero +** bytes in length instead of a NULL pointer. +*/ +static const char *fts5ValueToText(sqlite3_value *pVal){ + const char *zRet = (const char*)sqlite3_value_text(pVal); + return zRet ? zRet : ""; +} + +/* +** Implementation of snippet() function. +*/ +static void fts5SnippetFunction( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + sqlite3_context *pCtx, /* Context for returning result/error */ + int nVal, /* Number of values in apVal[] array */ + sqlite3_value **apVal /* Array of trailing arguments */ +){ + HighlightContext ctx; + int rc = SQLITE_OK; /* Return code */ + int iCol; /* 1st argument to snippet() */ + const char *zEllips; /* 4th argument to snippet() */ + int nToken; /* 5th argument to snippet() */ + int nInst = 0; /* Number of instance matches this row */ + int i; /* Used to iterate through instances */ + int nPhrase; /* Number of phrases in query */ + unsigned char *aSeen; /* Array of "seen instance" flags */ + int iBestCol; /* Column containing best snippet */ + int iBestStart = 0; /* First token of best snippet */ + int nBestScore = 0; /* Score of best snippet */ + int nColSize = 0; /* Total size of iBestCol in tokens */ + Fts5SFinder sFinder; /* Used to find the beginnings of sentences */ + int nCol; + + if( nVal!=5 ){ + const char *zErr = "wrong number of arguments to function snippet()"; + sqlite3_result_error(pCtx, zErr, -1); + return; + } + + nCol = pApi->xColumnCount(pFts); + memset(&ctx, 0, sizeof(HighlightContext)); + iCol = sqlite3_value_int(apVal[0]); + ctx.zOpen = fts5ValueToText(apVal[1]); + ctx.zClose = fts5ValueToText(apVal[2]); + ctx.iRangeEnd = -1; + zEllips = fts5ValueToText(apVal[3]); + nToken = sqlite3_value_int(apVal[4]); + + iBestCol = (iCol>=0 ? iCol : 0); + nPhrase = pApi->xPhraseCount(pFts); + aSeen = sqlite3_malloc(nPhrase); + if( aSeen==0 ){ + rc = SQLITE_NOMEM; + } + if( rc==SQLITE_OK ){ + rc = pApi->xInstCount(pFts, &nInst); + } + + memset(&sFinder, 0, sizeof(Fts5SFinder)); + for(i=0; i<nCol; i++){ + if( iCol<0 || iCol==i ){ + int nDoc; + int nDocsize; + int ii; + sFinder.iPos = 0; + sFinder.nFirst = 0; + rc = pApi->xColumnText(pFts, i, &sFinder.zDoc, &nDoc); + if( rc!=SQLITE_OK ) break; + rc = pApi->xTokenize(pFts, + sFinder.zDoc, nDoc, (void*)&sFinder,fts5SentenceFinderCb + ); + if( rc!=SQLITE_OK ) break; + rc = pApi->xColumnSize(pFts, i, &nDocsize); + if( rc!=SQLITE_OK ) break; + + for(ii=0; rc==SQLITE_OK && ii<nInst; ii++){ + int ip, ic, io; + int iAdj; + int nScore; + int jj; + + rc = pApi->xInst(pFts, ii, &ip, &ic, &io); + if( ic!=i ) continue; + if( io>nDocsize ) rc = FTS5_CORRUPT; + if( rc!=SQLITE_OK ) continue; + memset(aSeen, 0, nPhrase); + rc = fts5SnippetScore(pApi, pFts, nDocsize, aSeen, i, + io, nToken, &nScore, &iAdj + ); + if( rc==SQLITE_OK && nScore>nBestScore ){ + nBestScore = nScore; + iBestCol = i; + iBestStart = iAdj; + nColSize = nDocsize; + } + + if( rc==SQLITE_OK && sFinder.nFirst && nDocsize>nToken ){ + for(jj=0; jj<(sFinder.nFirst-1); jj++){ + if( sFinder.aFirst[jj+1]>io ) break; + } + + if( sFinder.aFirst[jj]<io ){ + memset(aSeen, 0, nPhrase); + rc = fts5SnippetScore(pApi, pFts, nDocsize, aSeen, i, + sFinder.aFirst[jj], nToken, &nScore, 0 + ); + + nScore += (sFinder.aFirst[jj]==0 ? 120 : 100); + if( rc==SQLITE_OK && nScore>nBestScore ){ + nBestScore = nScore; + iBestCol = i; + iBestStart = sFinder.aFirst[jj]; + nColSize = nDocsize; + } + } + } + } + } + } + + if( rc==SQLITE_OK ){ + rc = pApi->xColumnText(pFts, iBestCol, &ctx.zIn, &ctx.nIn); + } + if( rc==SQLITE_OK && nColSize==0 ){ + rc = pApi->xColumnSize(pFts, iBestCol, &nColSize); + } + if( ctx.zIn ){ + if( rc==SQLITE_OK ){ + rc = fts5CInstIterInit(pApi, pFts, iBestCol, &ctx.iter); + } + + ctx.iRangeStart = iBestStart; + ctx.iRangeEnd = iBestStart + nToken - 1; + + if( iBestStart>0 ){ + fts5HighlightAppend(&rc, &ctx, zEllips, -1); + } + + /* Advance iterator ctx.iter so that it points to the first coalesced + ** phrase instance at or following position iBestStart. */ + while( ctx.iter.iStart>=0 && ctx.iter.iStart<iBestStart && rc==SQLITE_OK ){ + rc = fts5CInstIterNext(&ctx.iter); + } + + if( rc==SQLITE_OK ){ + rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb); + } + if( ctx.iRangeEnd>=(nColSize-1) ){ + fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff); + }else{ + fts5HighlightAppend(&rc, &ctx, zEllips, -1); + } + } + if( rc==SQLITE_OK ){ + sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT); + }else{ + sqlite3_result_error_code(pCtx, rc); + } + sqlite3_free(ctx.zOut); + sqlite3_free(aSeen); + sqlite3_free(sFinder.aFirst); +} + +/************************************************************************/ + +/* +** The first time the bm25() function is called for a query, an instance +** of the following structure is allocated and populated. +*/ +typedef struct Fts5Bm25Data Fts5Bm25Data; +struct Fts5Bm25Data { + int nPhrase; /* Number of phrases in query */ + double avgdl; /* Average number of tokens in each row */ + double *aIDF; /* IDF for each phrase */ + double *aFreq; /* Array used to calculate phrase freq. */ +}; + +/* +** Callback used by fts5Bm25GetData() to count the number of rows in the +** table matched by each individual phrase within the query. +*/ +static int fts5CountCb( + const Fts5ExtensionApi *pApi, + Fts5Context *pFts, + void *pUserData /* Pointer to sqlite3_int64 variable */ +){ + sqlite3_int64 *pn = (sqlite3_int64*)pUserData; + UNUSED_PARAM2(pApi, pFts); + (*pn)++; + return SQLITE_OK; +} + +/* +** Set *ppData to point to the Fts5Bm25Data object for the current query. +** If the object has not already been allocated, allocate and populate it +** now. +*/ +static int fts5Bm25GetData( + const Fts5ExtensionApi *pApi, + Fts5Context *pFts, + Fts5Bm25Data **ppData /* OUT: bm25-data object for this query */ +){ + int rc = SQLITE_OK; /* Return code */ + Fts5Bm25Data *p; /* Object to return */ + + p = (Fts5Bm25Data*)pApi->xGetAuxdata(pFts, 0); + if( p==0 ){ + int nPhrase; /* Number of phrases in query */ + sqlite3_int64 nRow = 0; /* Number of rows in table */ + sqlite3_int64 nToken = 0; /* Number of tokens in table */ + sqlite3_int64 nByte; /* Bytes of space to allocate */ + int i; + + /* Allocate the Fts5Bm25Data object */ + nPhrase = pApi->xPhraseCount(pFts); + nByte = sizeof(Fts5Bm25Data) + nPhrase*2*sizeof(double); + p = (Fts5Bm25Data*)sqlite3_malloc64(nByte); + if( p==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(p, 0, (size_t)nByte); + p->nPhrase = nPhrase; + p->aIDF = (double*)&p[1]; + p->aFreq = &p->aIDF[nPhrase]; + } + + /* Calculate the average document length for this FTS5 table */ + if( rc==SQLITE_OK ) rc = pApi->xRowCount(pFts, &nRow); + assert( rc!=SQLITE_OK || nRow>0 ); + if( rc==SQLITE_OK ) rc = pApi->xColumnTotalSize(pFts, -1, &nToken); + if( rc==SQLITE_OK ) p->avgdl = (double)nToken / (double)nRow; + + /* Calculate an IDF for each phrase in the query */ + for(i=0; rc==SQLITE_OK && i<nPhrase; i++){ + sqlite3_int64 nHit = 0; + rc = pApi->xQueryPhrase(pFts, i, (void*)&nHit, fts5CountCb); + if( rc==SQLITE_OK ){ + /* Calculate the IDF (Inverse Document Frequency) for phrase i. + ** This is done using the standard BM25 formula as found on wikipedia: + ** + ** IDF = log( (N - nHit + 0.5) / (nHit + 0.5) ) + ** + ** where "N" is the total number of documents in the set and nHit + ** is the number that contain at least one instance of the phrase + ** under consideration. + ** + ** The problem with this is that if (N < 2*nHit), the IDF is + ** negative. Which is undesirable. So the mimimum allowable IDF is + ** (1e-6) - roughly the same as a term that appears in just over + ** half of set of 5,000,000 documents. */ + double idf = log( (nRow - nHit + 0.5) / (nHit + 0.5) ); + if( idf<=0.0 ) idf = 1e-6; + p->aIDF[i] = idf; + } + } + + if( rc!=SQLITE_OK ){ + sqlite3_free(p); + }else{ + rc = pApi->xSetAuxdata(pFts, p, sqlite3_free); + } + if( rc!=SQLITE_OK ) p = 0; + } + *ppData = p; + return rc; +} + +/* +** Implementation of bm25() function. +*/ +static void fts5Bm25Function( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + sqlite3_context *pCtx, /* Context for returning result/error */ + int nVal, /* Number of values in apVal[] array */ + sqlite3_value **apVal /* Array of trailing arguments */ +){ + const double k1 = 1.2; /* Constant "k1" from BM25 formula */ + const double b = 0.75; /* Constant "b" from BM25 formula */ + int rc; /* Error code */ + double score = 0.0; /* SQL function return value */ + Fts5Bm25Data *pData; /* Values allocated/calculated once only */ + int i; /* Iterator variable */ + int nInst = 0; /* Value returned by xInstCount() */ + double D = 0.0; /* Total number of tokens in row */ + double *aFreq = 0; /* Array of phrase freq. for current row */ + + /* Calculate the phrase frequency (symbol "f(qi,D)" in the documentation) + ** for each phrase in the query for the current row. */ + rc = fts5Bm25GetData(pApi, pFts, &pData); + if( rc==SQLITE_OK ){ + aFreq = pData->aFreq; + memset(aFreq, 0, sizeof(double) * pData->nPhrase); + rc = pApi->xInstCount(pFts, &nInst); + } + for(i=0; rc==SQLITE_OK && i<nInst; i++){ + int ip; int ic; int io; + rc = pApi->xInst(pFts, i, &ip, &ic, &io); + if( rc==SQLITE_OK ){ + double w = (nVal > ic) ? sqlite3_value_double(apVal[ic]) : 1.0; + aFreq[ip] += w; + } + } + + /* Figure out the total size of the current row in tokens. */ + if( rc==SQLITE_OK ){ + int nTok; + rc = pApi->xColumnSize(pFts, -1, &nTok); + D = (double)nTok; + } + + /* Determine and return the BM25 score for the current row. Or, if an + ** error has occurred, throw an exception. */ + if( rc==SQLITE_OK ){ + for(i=0; i<pData->nPhrase; i++){ + score += pData->aIDF[i] * ( + ( aFreq[i] * (k1 + 1.0) ) / + ( aFreq[i] + k1 * (1 - b + b * D / pData->avgdl) ) + ); + } + sqlite3_result_double(pCtx, -1.0 * score); + }else{ + sqlite3_result_error_code(pCtx, rc); + } +} + +static int sqlite3Fts5AuxInit(fts5_api *pApi){ + struct Builtin { + const char *zFunc; /* Function name (nul-terminated) */ + void *pUserData; /* User-data pointer */ + fts5_extension_function xFunc;/* Callback function */ + void (*xDestroy)(void*); /* Destructor function */ + } aBuiltin [] = { + { "snippet", 0, fts5SnippetFunction, 0 }, + { "highlight", 0, fts5HighlightFunction, 0 }, + { "bm25", 0, fts5Bm25Function, 0 }, + }; + int rc = SQLITE_OK; /* Return code */ + int i; /* To iterate through builtin functions */ + + for(i=0; rc==SQLITE_OK && i<ArraySize(aBuiltin); i++){ + rc = pApi->xCreateFunction(pApi, + aBuiltin[i].zFunc, + aBuiltin[i].pUserData, + aBuiltin[i].xFunc, + aBuiltin[i].xDestroy + ); + } + + return rc; +} + +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +*/ + + + +/* #include "fts5Int.h" */ + +static int sqlite3Fts5BufferSize(int *pRc, Fts5Buffer *pBuf, u32 nByte){ + if( (u32)pBuf->nSpace<nByte ){ + u64 nNew = pBuf->nSpace ? pBuf->nSpace : 64; + u8 *pNew; + while( nNew<nByte ){ + nNew = nNew * 2; + } + pNew = sqlite3_realloc64(pBuf->p, nNew); + if( pNew==0 ){ + *pRc = SQLITE_NOMEM; + return 1; + }else{ + pBuf->nSpace = (int)nNew; + pBuf->p = pNew; + } + } + return 0; +} + + +/* +** Encode value iVal as an SQLite varint and append it to the buffer object +** pBuf. If an OOM error occurs, set the error code in p. +*/ +static void sqlite3Fts5BufferAppendVarint(int *pRc, Fts5Buffer *pBuf, i64 iVal){ + if( fts5BufferGrow(pRc, pBuf, 9) ) return; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iVal); +} + +static void sqlite3Fts5Put32(u8 *aBuf, int iVal){ + aBuf[0] = (iVal>>24) & 0x00FF; + aBuf[1] = (iVal>>16) & 0x00FF; + aBuf[2] = (iVal>> 8) & 0x00FF; + aBuf[3] = (iVal>> 0) & 0x00FF; +} + +static int sqlite3Fts5Get32(const u8 *aBuf){ + return (int)((((u32)aBuf[0])<<24) + (aBuf[1]<<16) + (aBuf[2]<<8) + aBuf[3]); +} + +/* +** Append buffer nData/pData to buffer pBuf. If an OOM error occurs, set +** the error code in p. If an error has already occurred when this function +** is called, it is a no-op. +*/ +static void sqlite3Fts5BufferAppendBlob( + int *pRc, + Fts5Buffer *pBuf, + u32 nData, + const u8 *pData +){ + if( nData ){ + if( fts5BufferGrow(pRc, pBuf, nData) ) return; + memcpy(&pBuf->p[pBuf->n], pData, nData); + pBuf->n += nData; + } +} + +/* +** Append the nul-terminated string zStr to the buffer pBuf. This function +** ensures that the byte following the buffer data is set to 0x00, even +** though this byte is not included in the pBuf->n count. +*/ +static void sqlite3Fts5BufferAppendString( + int *pRc, + Fts5Buffer *pBuf, + const char *zStr +){ + int nStr = (int)strlen(zStr); + sqlite3Fts5BufferAppendBlob(pRc, pBuf, nStr+1, (const u8*)zStr); + pBuf->n--; +} + +/* +** Argument zFmt is a printf() style format string. This function performs +** the printf() style processing, then appends the results to buffer pBuf. +** +** Like sqlite3Fts5BufferAppendString(), this function ensures that the byte +** following the buffer data is set to 0x00, even though this byte is not +** included in the pBuf->n count. +*/ +static void sqlite3Fts5BufferAppendPrintf( + int *pRc, + Fts5Buffer *pBuf, + char *zFmt, ... +){ + if( *pRc==SQLITE_OK ){ + char *zTmp; + va_list ap; + va_start(ap, zFmt); + zTmp = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + + if( zTmp==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + sqlite3Fts5BufferAppendString(pRc, pBuf, zTmp); + sqlite3_free(zTmp); + } + } +} + +static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...){ + char *zRet = 0; + if( *pRc==SQLITE_OK ){ + va_list ap; + va_start(ap, zFmt); + zRet = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( zRet==0 ){ + *pRc = SQLITE_NOMEM; + } + } + return zRet; +} + + +/* +** Free any buffer allocated by pBuf. Zero the structure before returning. +*/ +static void sqlite3Fts5BufferFree(Fts5Buffer *pBuf){ + sqlite3_free(pBuf->p); + memset(pBuf, 0, sizeof(Fts5Buffer)); +} + +/* +** Zero the contents of the buffer object. But do not free the associated +** memory allocation. +*/ +static void sqlite3Fts5BufferZero(Fts5Buffer *pBuf){ + pBuf->n = 0; +} + +/* +** Set the buffer to contain nData/pData. If an OOM error occurs, leave an +** the error code in p. If an error has already occurred when this function +** is called, it is a no-op. +*/ +static void sqlite3Fts5BufferSet( + int *pRc, + Fts5Buffer *pBuf, + int nData, + const u8 *pData +){ + pBuf->n = 0; + sqlite3Fts5BufferAppendBlob(pRc, pBuf, nData, pData); +} + +static int sqlite3Fts5PoslistNext64( + const u8 *a, int n, /* Buffer containing poslist */ + int *pi, /* IN/OUT: Offset within a[] */ + i64 *piOff /* IN/OUT: Current offset */ +){ + int i = *pi; + if( i>=n ){ + /* EOF */ + *piOff = -1; + return 1; + }else{ + i64 iOff = *piOff; + u32 iVal; + fts5FastGetVarint32(a, i, iVal); + if( iVal<=1 ){ + if( iVal==0 ){ + *pi = i; + return 0; + } + fts5FastGetVarint32(a, i, iVal); + iOff = ((i64)iVal) << 32; + assert( iOff>=0 ); + fts5FastGetVarint32(a, i, iVal); + if( iVal<2 ){ + /* This is a corrupt record. So stop parsing it here. */ + *piOff = -1; + return 1; + } + *piOff = iOff + ((iVal-2) & 0x7FFFFFFF); + }else{ + *piOff = (iOff & (i64)0x7FFFFFFF<<32)+((iOff + (iVal-2)) & 0x7FFFFFFF); + } + *pi = i; + assert_nc( *piOff>=iOff ); + return 0; + } +} + + +/* +** Advance the iterator object passed as the only argument. Return true +** if the iterator reaches EOF, or false otherwise. +*/ +static int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader *pIter){ + if( sqlite3Fts5PoslistNext64(pIter->a, pIter->n, &pIter->i, &pIter->iPos) ){ + pIter->bEof = 1; + } + return pIter->bEof; +} + +static int sqlite3Fts5PoslistReaderInit( + const u8 *a, int n, /* Poslist buffer to iterate through */ + Fts5PoslistReader *pIter /* Iterator object to initialize */ +){ + memset(pIter, 0, sizeof(*pIter)); + pIter->a = a; + pIter->n = n; + sqlite3Fts5PoslistReaderNext(pIter); + return pIter->bEof; +} + +/* +** Append position iPos to the position list being accumulated in buffer +** pBuf, which must be already be large enough to hold the new data. +** The previous position written to this list is *piPrev. *piPrev is set +** to iPos before returning. +*/ +static void sqlite3Fts5PoslistSafeAppend( + Fts5Buffer *pBuf, + i64 *piPrev, + i64 iPos +){ + if( iPos>=*piPrev ){ + static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32; + if( (iPos & colmask) != (*piPrev & colmask) ){ + pBuf->p[pBuf->n++] = 1; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32)); + *piPrev = (iPos & colmask); + } + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2); + *piPrev = iPos; + } +} + +static int sqlite3Fts5PoslistWriterAppend( + Fts5Buffer *pBuf, + Fts5PoslistWriter *pWriter, + i64 iPos +){ + int rc = 0; /* Initialized only to suppress erroneous warning from Clang */ + if( fts5BufferGrow(&rc, pBuf, 5+5+5) ) return rc; + sqlite3Fts5PoslistSafeAppend(pBuf, &pWriter->iPrev, iPos); + return SQLITE_OK; +} + +static void *sqlite3Fts5MallocZero(int *pRc, sqlite3_int64 nByte){ + void *pRet = 0; + if( *pRc==SQLITE_OK ){ + pRet = sqlite3_malloc64(nByte); + if( pRet==0 ){ + if( nByte>0 ) *pRc = SQLITE_NOMEM; + }else{ + memset(pRet, 0, (size_t)nByte); + } + } + return pRet; +} + +/* +** Return a nul-terminated copy of the string indicated by pIn. If nIn +** is non-negative, then it is the length of the string in bytes. Otherwise, +** the length of the string is determined using strlen(). +** +** It is the responsibility of the caller to eventually free the returned +** buffer using sqlite3_free(). If an OOM error occurs, NULL is returned. +*/ +static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){ + char *zRet = 0; + if( *pRc==SQLITE_OK ){ + if( nIn<0 ){ + nIn = (int)strlen(pIn); + } + zRet = (char*)sqlite3_malloc(nIn+1); + if( zRet ){ + memcpy(zRet, pIn, nIn); + zRet[nIn] = '\0'; + }else{ + *pRc = SQLITE_NOMEM; + } + } + return zRet; +} + + +/* +** Return true if character 't' may be part of an FTS5 bareword, or false +** otherwise. Characters that may be part of barewords: +** +** * All non-ASCII characters, +** * The 52 upper and lower case ASCII characters, and +** * The 10 integer ASCII characters. +** * The underscore character "_" (0x5F). +** * The unicode "subsitute" character (0x1A). +*/ +static int sqlite3Fts5IsBareword(char t){ + u8 aBareword[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 .. 0x0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, /* 0x10 .. 0x1F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 .. 0x2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 0x30 .. 0x3F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 .. 0x4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 0x50 .. 0x5F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 .. 0x6F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 /* 0x70 .. 0x7F */ + }; + + return (t & 0x80) || aBareword[(int)t]; +} + + +/************************************************************************* +*/ +typedef struct Fts5TermsetEntry Fts5TermsetEntry; +struct Fts5TermsetEntry { + char *pTerm; + int nTerm; + int iIdx; /* Index (main or aPrefix[] entry) */ + Fts5TermsetEntry *pNext; +}; + +struct Fts5Termset { + Fts5TermsetEntry *apHash[512]; +}; + +static int sqlite3Fts5TermsetNew(Fts5Termset **pp){ + int rc = SQLITE_OK; + *pp = sqlite3Fts5MallocZero(&rc, sizeof(Fts5Termset)); + return rc; +} + +static int sqlite3Fts5TermsetAdd( + Fts5Termset *p, + int iIdx, + const char *pTerm, int nTerm, + int *pbPresent +){ + int rc = SQLITE_OK; + *pbPresent = 0; + if( p ){ + int i; + u32 hash = 13; + Fts5TermsetEntry *pEntry; + + /* Calculate a hash value for this term. This is the same hash checksum + ** used by the fts5_hash.c module. This is not important for correct + ** operation of the module, but is necessary to ensure that some tests + ** designed to produce hash table collisions really do work. */ + for(i=nTerm-1; i>=0; i--){ + hash = (hash << 3) ^ hash ^ pTerm[i]; + } + hash = (hash << 3) ^ hash ^ iIdx; + hash = hash % ArraySize(p->apHash); + + for(pEntry=p->apHash[hash]; pEntry; pEntry=pEntry->pNext){ + if( pEntry->iIdx==iIdx + && pEntry->nTerm==nTerm + && memcmp(pEntry->pTerm, pTerm, nTerm)==0 + ){ + *pbPresent = 1; + break; + } + } + + if( pEntry==0 ){ + pEntry = sqlite3Fts5MallocZero(&rc, sizeof(Fts5TermsetEntry) + nTerm); + if( pEntry ){ + pEntry->pTerm = (char*)&pEntry[1]; + pEntry->nTerm = nTerm; + pEntry->iIdx = iIdx; + memcpy(pEntry->pTerm, pTerm, nTerm); + pEntry->pNext = p->apHash[hash]; + p->apHash[hash] = pEntry; + } + } + } + + return rc; +} + +static void sqlite3Fts5TermsetFree(Fts5Termset *p){ + if( p ){ + u32 i; + for(i=0; i<ArraySize(p->apHash); i++){ + Fts5TermsetEntry *pEntry = p->apHash[i]; + while( pEntry ){ + Fts5TermsetEntry *pDel = pEntry; + pEntry = pEntry->pNext; + sqlite3_free(pDel); + } + } + sqlite3_free(p); + } +} + +/* +** 2014 Jun 09 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This is an SQLite module implementing full-text search. +*/ + + +/* #include "fts5Int.h" */ + +#define FTS5_DEFAULT_PAGE_SIZE 4050 +#define FTS5_DEFAULT_AUTOMERGE 4 +#define FTS5_DEFAULT_USERMERGE 4 +#define FTS5_DEFAULT_CRISISMERGE 16 +#define FTS5_DEFAULT_HASHSIZE (1024*1024) + +#define FTS5_DEFAULT_DELETE_AUTOMERGE 10 /* default 10% */ + +/* Maximum allowed page size */ +#define FTS5_MAX_PAGE_SIZE (64*1024) + +static int fts5_iswhitespace(char x){ + return (x==' '); +} + +static int fts5_isopenquote(char x){ + return (x=='"' || x=='\'' || x=='[' || x=='`'); +} + +/* +** Argument pIn points to a character that is part of a nul-terminated +** string. Return a pointer to the first character following *pIn in +** the string that is not a white-space character. +*/ +static const char *fts5ConfigSkipWhitespace(const char *pIn){ + const char *p = pIn; + if( p ){ + while( fts5_iswhitespace(*p) ){ p++; } + } + return p; +} + +/* +** Argument pIn points to a character that is part of a nul-terminated +** string. Return a pointer to the first character following *pIn in +** the string that is not a "bareword" character. +*/ +static const char *fts5ConfigSkipBareword(const char *pIn){ + const char *p = pIn; + while ( sqlite3Fts5IsBareword(*p) ) p++; + if( p==pIn ) p = 0; + return p; +} + +static int fts5_isdigit(char a){ + return (a>='0' && a<='9'); +} + + + +static const char *fts5ConfigSkipLiteral(const char *pIn){ + const char *p = pIn; + switch( *p ){ + case 'n': case 'N': + if( sqlite3_strnicmp("null", p, 4)==0 ){ + p = &p[4]; + }else{ + p = 0; + } + break; + + case 'x': case 'X': + p++; + if( *p=='\'' ){ + p++; + while( (*p>='a' && *p<='f') + || (*p>='A' && *p<='F') + || (*p>='0' && *p<='9') + ){ + p++; + } + if( *p=='\'' && 0==((p-pIn)%2) ){ + p++; + }else{ + p = 0; + } + }else{ + p = 0; + } + break; + + case '\'': + p++; + while( p ){ + if( *p=='\'' ){ + p++; + if( *p!='\'' ) break; + } + p++; + if( *p==0 ) p = 0; + } + break; + + default: + /* maybe a number */ + if( *p=='+' || *p=='-' ) p++; + while( fts5_isdigit(*p) ) p++; + + /* At this point, if the literal was an integer, the parse is + ** finished. Or, if it is a floating point value, it may continue + ** with either a decimal point or an 'E' character. */ + if( *p=='.' && fts5_isdigit(p[1]) ){ + p += 2; + while( fts5_isdigit(*p) ) p++; + } + if( p==pIn ) p = 0; + + break; + } + + return p; +} + +/* +** The first character of the string pointed to by argument z is guaranteed +** to be an open-quote character (see function fts5_isopenquote()). +** +** This function searches for the corresponding close-quote character within +** the string and, if found, dequotes the string in place and adds a new +** nul-terminator byte. +** +** If the close-quote is found, the value returned is the byte offset of +** the character immediately following it. Or, if the close-quote is not +** found, -1 is returned. If -1 is returned, the buffer is left in an +** undefined state. +*/ +static int fts5Dequote(char *z){ + char q; + int iIn = 1; + int iOut = 0; + q = z[0]; + + /* Set stack variable q to the close-quote character */ + assert( q=='[' || q=='\'' || q=='"' || q=='`' ); + if( q=='[' ) q = ']'; + + while( z[iIn] ){ + if( z[iIn]==q ){ + if( z[iIn+1]!=q ){ + /* Character iIn was the close quote. */ + iIn++; + break; + }else{ + /* Character iIn and iIn+1 form an escaped quote character. Skip + ** the input cursor past both and copy a single quote character + ** to the output buffer. */ + iIn += 2; + z[iOut++] = q; + } + }else{ + z[iOut++] = z[iIn++]; + } + } + + z[iOut] = '\0'; + return iIn; +} + +/* +** Convert an SQL-style quoted string into a normal string by removing +** the quote characters. The conversion is done in-place. If the +** input does not begin with a quote character, then this routine +** is a no-op. +** +** Examples: +** +** "abc" becomes abc +** 'xyz' becomes xyz +** [pqr] becomes pqr +** `mno` becomes mno +*/ +static void sqlite3Fts5Dequote(char *z){ + char quote; /* Quote character (if any ) */ + + assert( 0==fts5_iswhitespace(z[0]) ); + quote = z[0]; + if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){ + fts5Dequote(z); + } +} + + +struct Fts5Enum { + const char *zName; + int eVal; +}; +typedef struct Fts5Enum Fts5Enum; + +static int fts5ConfigSetEnum( + const Fts5Enum *aEnum, + const char *zEnum, + int *peVal +){ + int nEnum = (int)strlen(zEnum); + int i; + int iVal = -1; + + for(i=0; aEnum[i].zName; i++){ + if( sqlite3_strnicmp(aEnum[i].zName, zEnum, nEnum)==0 ){ + if( iVal>=0 ) return SQLITE_ERROR; + iVal = aEnum[i].eVal; + } + } + + *peVal = iVal; + return iVal<0 ? SQLITE_ERROR : SQLITE_OK; +} + +/* +** Parse a "special" CREATE VIRTUAL TABLE directive and update +** configuration object pConfig as appropriate. +** +** If successful, object pConfig is updated and SQLITE_OK returned. If +** an error occurs, an SQLite error code is returned and an error message +** may be left in *pzErr. It is the responsibility of the caller to +** eventually free any such error message using sqlite3_free(). +*/ +static int fts5ConfigParseSpecial( + Fts5Global *pGlobal, + Fts5Config *pConfig, /* Configuration object to update */ + const char *zCmd, /* Special command to parse */ + const char *zArg, /* Argument to parse */ + char **pzErr /* OUT: Error message */ +){ + int rc = SQLITE_OK; + int nCmd = (int)strlen(zCmd); + if( sqlite3_strnicmp("prefix", zCmd, nCmd)==0 ){ + const int nByte = sizeof(int) * FTS5_MAX_PREFIX_INDEXES; + const char *p; + int bFirst = 1; + if( pConfig->aPrefix==0 ){ + pConfig->aPrefix = sqlite3Fts5MallocZero(&rc, nByte); + if( rc ) return rc; + } + + p = zArg; + while( 1 ){ + int nPre = 0; + + while( p[0]==' ' ) p++; + if( bFirst==0 && p[0]==',' ){ + p++; + while( p[0]==' ' ) p++; + }else if( p[0]=='\0' ){ + break; + } + if( p[0]<'0' || p[0]>'9' ){ + *pzErr = sqlite3_mprintf("malformed prefix=... directive"); + rc = SQLITE_ERROR; + break; + } + + if( pConfig->nPrefix==FTS5_MAX_PREFIX_INDEXES ){ + *pzErr = sqlite3_mprintf( + "too many prefix indexes (max %d)", FTS5_MAX_PREFIX_INDEXES + ); + rc = SQLITE_ERROR; + break; + } + + while( p[0]>='0' && p[0]<='9' && nPre<1000 ){ + nPre = nPre*10 + (p[0] - '0'); + p++; + } + + if( nPre<=0 || nPre>=1000 ){ + *pzErr = sqlite3_mprintf("prefix length out of range (max 999)"); + rc = SQLITE_ERROR; + break; + } + + pConfig->aPrefix[pConfig->nPrefix] = nPre; + pConfig->nPrefix++; + bFirst = 0; + } + assert( pConfig->nPrefix<=FTS5_MAX_PREFIX_INDEXES ); + return rc; + } + + if( sqlite3_strnicmp("tokenize", zCmd, nCmd)==0 ){ + const char *p = (const char*)zArg; + sqlite3_int64 nArg = strlen(zArg) + 1; + char **azArg = sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg); + char *pDel = sqlite3Fts5MallocZero(&rc, nArg * 2); + char *pSpace = pDel; + + if( azArg && pSpace ){ + if( pConfig->pTok ){ + *pzErr = sqlite3_mprintf("multiple tokenize=... directives"); + rc = SQLITE_ERROR; + }else{ + for(nArg=0; p && *p; nArg++){ + const char *p2 = fts5ConfigSkipWhitespace(p); + if( *p2=='\'' ){ + p = fts5ConfigSkipLiteral(p2); + }else{ + p = fts5ConfigSkipBareword(p2); + } + if( p ){ + memcpy(pSpace, p2, p-p2); + azArg[nArg] = pSpace; + sqlite3Fts5Dequote(pSpace); + pSpace += (p - p2) + 1; + p = fts5ConfigSkipWhitespace(p); + } + } + if( p==0 ){ + *pzErr = sqlite3_mprintf("parse error in tokenize directive"); + rc = SQLITE_ERROR; + }else{ + rc = sqlite3Fts5GetTokenizer(pGlobal, + (const char**)azArg, (int)nArg, pConfig, + pzErr + ); + } + } + } + + sqlite3_free(azArg); + sqlite3_free(pDel); + return rc; + } + + if( sqlite3_strnicmp("content", zCmd, nCmd)==0 ){ + if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){ + *pzErr = sqlite3_mprintf("multiple content=... directives"); + rc = SQLITE_ERROR; + }else{ + if( zArg[0] ){ + pConfig->eContent = FTS5_CONTENT_EXTERNAL; + pConfig->zContent = sqlite3Fts5Mprintf(&rc, "%Q.%Q", pConfig->zDb,zArg); + }else{ + pConfig->eContent = FTS5_CONTENT_NONE; + } + } + return rc; + } + + if( sqlite3_strnicmp("contentless_delete", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed contentless_delete=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bContentlessDelete = (zArg[0]=='1'); + } + return rc; + } + + if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){ + if( pConfig->zContentRowid ){ + *pzErr = sqlite3_mprintf("multiple content_rowid=... directives"); + rc = SQLITE_ERROR; + }else{ + pConfig->zContentRowid = sqlite3Fts5Strndup(&rc, zArg, -1); + } + return rc; + } + + if( sqlite3_strnicmp("columnsize", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed columnsize=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bColumnsize = (zArg[0]=='1'); + } + return rc; + } + + if( sqlite3_strnicmp("detail", zCmd, nCmd)==0 ){ + const Fts5Enum aDetail[] = { + { "none", FTS5_DETAIL_NONE }, + { "full", FTS5_DETAIL_FULL }, + { "columns", FTS5_DETAIL_COLUMNS }, + { 0, 0 } + }; + + if( (rc = fts5ConfigSetEnum(aDetail, zArg, &pConfig->eDetail)) ){ + *pzErr = sqlite3_mprintf("malformed detail=... directive"); + } + return rc; + } + + *pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd); + return SQLITE_ERROR; +} + +/* +** Allocate an instance of the default tokenizer ("simple") at +** Fts5Config.pTokenizer. Return SQLITE_OK if successful, or an SQLite error +** code if an error occurs. +*/ +static int fts5ConfigDefaultTokenizer(Fts5Global *pGlobal, Fts5Config *pConfig){ + assert( pConfig->pTok==0 && pConfig->pTokApi==0 ); + return sqlite3Fts5GetTokenizer(pGlobal, 0, 0, pConfig, 0); +} + +/* +** Gobble up the first bareword or quoted word from the input buffer zIn. +** Return a pointer to the character immediately following the last in +** the gobbled word if successful, or a NULL pointer otherwise (failed +** to find close-quote character). +** +** Before returning, set pzOut to point to a new buffer containing a +** nul-terminated, dequoted copy of the gobbled word. If the word was +** quoted, *pbQuoted is also set to 1 before returning. +** +** If *pRc is other than SQLITE_OK when this function is called, it is +** a no-op (NULL is returned). Otherwise, if an OOM occurs within this +** function, *pRc is set to SQLITE_NOMEM before returning. *pRc is *not* +** set if a parse error (failed to find close quote) occurs. +*/ +static const char *fts5ConfigGobbleWord( + int *pRc, /* IN/OUT: Error code */ + const char *zIn, /* Buffer to gobble string/bareword from */ + char **pzOut, /* OUT: malloc'd buffer containing str/bw */ + int *pbQuoted /* OUT: Set to true if dequoting required */ +){ + const char *zRet = 0; + + sqlite3_int64 nIn = strlen(zIn); + char *zOut = sqlite3_malloc64(nIn+1); + + assert( *pRc==SQLITE_OK ); + *pbQuoted = 0; + *pzOut = 0; + + if( zOut==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + memcpy(zOut, zIn, (size_t)(nIn+1)); + if( fts5_isopenquote(zOut[0]) ){ + int ii = fts5Dequote(zOut); + zRet = &zIn[ii]; + *pbQuoted = 1; + }else{ + zRet = fts5ConfigSkipBareword(zIn); + if( zRet ){ + zOut[zRet-zIn] = '\0'; + } + } + } + + if( zRet==0 ){ + sqlite3_free(zOut); + }else{ + *pzOut = zOut; + } + + return zRet; +} + +static int fts5ConfigParseColumn( + Fts5Config *p, + char *zCol, + char *zArg, + char **pzErr +){ + int rc = SQLITE_OK; + if( 0==sqlite3_stricmp(zCol, FTS5_RANK_NAME) + || 0==sqlite3_stricmp(zCol, FTS5_ROWID_NAME) + ){ + *pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zCol); + rc = SQLITE_ERROR; + }else if( zArg ){ + if( 0==sqlite3_stricmp(zArg, "unindexed") ){ + p->abUnindexed[p->nCol] = 1; + }else{ + *pzErr = sqlite3_mprintf("unrecognized column option: %s", zArg); + rc = SQLITE_ERROR; + } + } + + p->azCol[p->nCol++] = zCol; + return rc; +} + +/* +** Populate the Fts5Config.zContentExprlist string. +*/ +static int fts5ConfigMakeExprlist(Fts5Config *p){ + int i; + int rc = SQLITE_OK; + Fts5Buffer buf = {0, 0, 0}; + + sqlite3Fts5BufferAppendPrintf(&rc, &buf, "T.%Q", p->zContentRowid); + if( p->eContent!=FTS5_CONTENT_NONE ){ + for(i=0; i<p->nCol; i++){ + if( p->eContent==FTS5_CONTENT_EXTERNAL ){ + sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.%Q", p->azCol[i]); + }else{ + sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.c%d", i); + } + } + } + + assert( p->zContentExprlist==0 ); + p->zContentExprlist = (char*)buf.p; + return rc; +} + +/* +** Arguments nArg/azArg contain the string arguments passed to the xCreate +** or xConnect method of the virtual table. This function attempts to +** allocate an instance of Fts5Config containing the results of parsing +** those arguments. +** +** If successful, SQLITE_OK is returned and *ppOut is set to point to the +** new Fts5Config object. If an error occurs, an SQLite error code is +** returned, *ppOut is set to NULL and an error message may be left in +** *pzErr. It is the responsibility of the caller to eventually free any +** such error message using sqlite3_free(). +*/ +static int sqlite3Fts5ConfigParse( + Fts5Global *pGlobal, + sqlite3 *db, + int nArg, /* Number of arguments */ + const char **azArg, /* Array of nArg CREATE VIRTUAL TABLE args */ + Fts5Config **ppOut, /* OUT: Results of parse */ + char **pzErr /* OUT: Error message */ +){ + int rc = SQLITE_OK; /* Return code */ + Fts5Config *pRet; /* New object to return */ + int i; + sqlite3_int64 nByte; + + *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config)); + if( pRet==0 ) return SQLITE_NOMEM; + memset(pRet, 0, sizeof(Fts5Config)); + pRet->db = db; + pRet->iCookie = -1; + + nByte = nArg * (sizeof(char*) + sizeof(u8)); + pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, nByte); + pRet->abUnindexed = pRet->azCol ? (u8*)&pRet->azCol[nArg] : 0; + pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1], -1); + pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2], -1); + pRet->bColumnsize = 1; + pRet->eDetail = FTS5_DETAIL_FULL; +#ifdef SQLITE_DEBUG + pRet->bPrefixIndex = 1; +#endif + if( rc==SQLITE_OK && sqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){ + *pzErr = sqlite3_mprintf("reserved fts5 table name: %s", pRet->zName); + rc = SQLITE_ERROR; + } + + assert( (pRet->abUnindexed && pRet->azCol) || rc!=SQLITE_OK ); + for(i=3; rc==SQLITE_OK && i<nArg; i++){ + const char *zOrig = azArg[i]; + const char *z; + char *zOne = 0; + char *zTwo = 0; + int bOption = 0; + int bMustBeCol = 0; + + z = fts5ConfigGobbleWord(&rc, zOrig, &zOne, &bMustBeCol); + z = fts5ConfigSkipWhitespace(z); + if( z && *z=='=' ){ + bOption = 1; + assert( zOne!=0 ); + z++; + if( bMustBeCol ) z = 0; + } + z = fts5ConfigSkipWhitespace(z); + if( z && z[0] ){ + int bDummy; + z = fts5ConfigGobbleWord(&rc, z, &zTwo, &bDummy); + if( z && z[0] ) z = 0; + } + + if( rc==SQLITE_OK ){ + if( z==0 ){ + *pzErr = sqlite3_mprintf("parse error in \"%s\"", zOrig); + rc = SQLITE_ERROR; + }else{ + if( bOption ){ + rc = fts5ConfigParseSpecial(pGlobal, pRet, + ALWAYS(zOne)?zOne:"", + zTwo?zTwo:"", + pzErr + ); + }else{ + rc = fts5ConfigParseColumn(pRet, zOne, zTwo, pzErr); + zOne = 0; + } + } + } + + sqlite3_free(zOne); + sqlite3_free(zTwo); + } + + /* We only allow contentless_delete=1 if the table is indeed contentless. */ + if( rc==SQLITE_OK + && pRet->bContentlessDelete + && pRet->eContent!=FTS5_CONTENT_NONE + ){ + *pzErr = sqlite3_mprintf( + "contentless_delete=1 requires a contentless table" + ); + rc = SQLITE_ERROR; + } + + /* We only allow contentless_delete=1 if columnsize=0 is not present. + ** + ** This restriction may be removed at some point. + */ + if( rc==SQLITE_OK && pRet->bContentlessDelete && pRet->bColumnsize==0 ){ + *pzErr = sqlite3_mprintf( + "contentless_delete=1 is incompatible with columnsize=0" + ); + rc = SQLITE_ERROR; + } + + /* If a tokenizer= option was successfully parsed, the tokenizer has + ** already been allocated. Otherwise, allocate an instance of the default + ** tokenizer (unicode61) now. */ + if( rc==SQLITE_OK && pRet->pTok==0 ){ + rc = fts5ConfigDefaultTokenizer(pGlobal, pRet); + } + + /* If no zContent option was specified, fill in the default values. */ + if( rc==SQLITE_OK && pRet->zContent==0 ){ + const char *zTail = 0; + assert( pRet->eContent==FTS5_CONTENT_NORMAL + || pRet->eContent==FTS5_CONTENT_NONE + ); + if( pRet->eContent==FTS5_CONTENT_NORMAL ){ + zTail = "content"; + }else if( pRet->bColumnsize ){ + zTail = "docsize"; + } + + if( zTail ){ + pRet->zContent = sqlite3Fts5Mprintf( + &rc, "%Q.'%q_%s'", pRet->zDb, pRet->zName, zTail + ); + } + } + + if( rc==SQLITE_OK && pRet->zContentRowid==0 ){ + pRet->zContentRowid = sqlite3Fts5Strndup(&rc, "rowid", -1); + } + + /* Formulate the zContentExprlist text */ + if( rc==SQLITE_OK ){ + rc = fts5ConfigMakeExprlist(pRet); + } + + if( rc!=SQLITE_OK ){ + sqlite3Fts5ConfigFree(pRet); + *ppOut = 0; + } + return rc; +} + +/* +** Free the configuration object passed as the only argument. +*/ +static void sqlite3Fts5ConfigFree(Fts5Config *pConfig){ + if( pConfig ){ + int i; + if( pConfig->pTok ){ + pConfig->pTokApi->xDelete(pConfig->pTok); + } + sqlite3_free(pConfig->zDb); + sqlite3_free(pConfig->zName); + for(i=0; i<pConfig->nCol; i++){ + sqlite3_free(pConfig->azCol[i]); + } + sqlite3_free(pConfig->azCol); + sqlite3_free(pConfig->aPrefix); + sqlite3_free(pConfig->zRank); + sqlite3_free(pConfig->zRankArgs); + sqlite3_free(pConfig->zContent); + sqlite3_free(pConfig->zContentRowid); + sqlite3_free(pConfig->zContentExprlist); + sqlite3_free(pConfig); + } +} + +/* +** Call sqlite3_declare_vtab() based on the contents of the configuration +** object passed as the only argument. Return SQLITE_OK if successful, or +** an SQLite error code if an error occurs. +*/ +static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){ + int i; + int rc = SQLITE_OK; + char *zSql; + + zSql = sqlite3Fts5Mprintf(&rc, "CREATE TABLE x("); + for(i=0; zSql && i<pConfig->nCol; i++){ + const char *zSep = (i==0?"":", "); + zSql = sqlite3Fts5Mprintf(&rc, "%z%s%Q", zSql, zSep, pConfig->azCol[i]); + } + zSql = sqlite3Fts5Mprintf(&rc, "%z, %Q HIDDEN, %s HIDDEN)", + zSql, pConfig->zName, FTS5_RANK_NAME + ); + + assert( zSql || rc==SQLITE_NOMEM ); + if( zSql ){ + rc = sqlite3_declare_vtab(pConfig->db, zSql); + sqlite3_free(zSql); + } + + return rc; +} + +/* +** Tokenize the text passed via the second and third arguments. +** +** The callback is invoked once for each token in the input text. The +** arguments passed to it are, in order: +** +** void *pCtx // Copy of 4th argument to sqlite3Fts5Tokenize() +** const char *pToken // Pointer to buffer containing token +** int nToken // Size of token in bytes +** int iStart // Byte offset of start of token within input text +** int iEnd // Byte offset of end of token within input text +** int iPos // Position of token in input (first token is 0) +** +** If the callback returns a non-zero value the tokenization is abandoned +** and no further callbacks are issued. +** +** This function returns SQLITE_OK if successful or an SQLite error code +** if an error occurs. If the tokenization was abandoned early because +** the callback returned SQLITE_DONE, this is not an error and this function +** still returns SQLITE_OK. Or, if the tokenization was abandoned early +** because the callback returned another non-zero value, it is assumed +** to be an SQLite error code and returned to the caller. +*/ +static int sqlite3Fts5Tokenize( + Fts5Config *pConfig, /* FTS5 Configuration object */ + int flags, /* FTS5_TOKENIZE_* flags */ + const char *pText, int nText, /* Text to tokenize */ + void *pCtx, /* Context passed to xToken() */ + int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ +){ + if( pText==0 ) return SQLITE_OK; + return pConfig->pTokApi->xTokenize( + pConfig->pTok, pCtx, flags, pText, nText, xToken + ); +} + +/* +** Argument pIn points to the first character in what is expected to be +** a comma-separated list of SQL literals followed by a ')' character. +** If it actually is this, return a pointer to the ')'. Otherwise, return +** NULL to indicate a parse error. +*/ +static const char *fts5ConfigSkipArgs(const char *pIn){ + const char *p = pIn; + + while( 1 ){ + p = fts5ConfigSkipWhitespace(p); + p = fts5ConfigSkipLiteral(p); + p = fts5ConfigSkipWhitespace(p); + if( p==0 || *p==')' ) break; + if( *p!=',' ){ + p = 0; + break; + } + p++; + } + + return p; +} + +/* +** Parameter zIn contains a rank() function specification. The format of +** this is: +** +** + Bareword (function name) +** + Open parenthesis - "(" +** + Zero or more SQL literals in a comma separated list +** + Close parenthesis - ")" +*/ +static int sqlite3Fts5ConfigParseRank( + const char *zIn, /* Input string */ + char **pzRank, /* OUT: Rank function name */ + char **pzRankArgs /* OUT: Rank function arguments */ +){ + const char *p = zIn; + const char *pRank; + char *zRank = 0; + char *zRankArgs = 0; + int rc = SQLITE_OK; + + *pzRank = 0; + *pzRankArgs = 0; + + if( p==0 ){ + rc = SQLITE_ERROR; + }else{ + p = fts5ConfigSkipWhitespace(p); + pRank = p; + p = fts5ConfigSkipBareword(p); + + if( p ){ + zRank = sqlite3Fts5MallocZero(&rc, 1 + p - pRank); + if( zRank ) memcpy(zRank, pRank, p-pRank); + }else{ + rc = SQLITE_ERROR; + } + + if( rc==SQLITE_OK ){ + p = fts5ConfigSkipWhitespace(p); + if( *p!='(' ) rc = SQLITE_ERROR; + p++; + } + if( rc==SQLITE_OK ){ + const char *pArgs; + p = fts5ConfigSkipWhitespace(p); + pArgs = p; + if( *p!=')' ){ + p = fts5ConfigSkipArgs(p); + if( p==0 ){ + rc = SQLITE_ERROR; + }else{ + zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs); + if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs); + } + } + } + } + + if( rc!=SQLITE_OK ){ + sqlite3_free(zRank); + assert( zRankArgs==0 ); + }else{ + *pzRank = zRank; + *pzRankArgs = zRankArgs; + } + return rc; +} + +static int sqlite3Fts5ConfigSetValue( + Fts5Config *pConfig, + const char *zKey, + sqlite3_value *pVal, + int *pbBadkey +){ + int rc = SQLITE_OK; + + if( 0==sqlite3_stricmp(zKey, "pgsz") ){ + int pgsz = 0; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + pgsz = sqlite3_value_int(pVal); + } + if( pgsz<32 || pgsz>FTS5_MAX_PAGE_SIZE ){ + *pbBadkey = 1; + }else{ + pConfig->pgsz = pgsz; + } + } + + else if( 0==sqlite3_stricmp(zKey, "hashsize") ){ + int nHashSize = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nHashSize = sqlite3_value_int(pVal); + } + if( nHashSize<=0 ){ + *pbBadkey = 1; + }else{ + pConfig->nHashSize = nHashSize; + } + } + + else if( 0==sqlite3_stricmp(zKey, "automerge") ){ + int nAutomerge = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nAutomerge = sqlite3_value_int(pVal); + } + if( nAutomerge<0 || nAutomerge>64 ){ + *pbBadkey = 1; + }else{ + if( nAutomerge==1 ) nAutomerge = FTS5_DEFAULT_AUTOMERGE; + pConfig->nAutomerge = nAutomerge; + } + } + + else if( 0==sqlite3_stricmp(zKey, "usermerge") ){ + int nUsermerge = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nUsermerge = sqlite3_value_int(pVal); + } + if( nUsermerge<2 || nUsermerge>16 ){ + *pbBadkey = 1; + }else{ + pConfig->nUsermerge = nUsermerge; + } + } + + else if( 0==sqlite3_stricmp(zKey, "crisismerge") ){ + int nCrisisMerge = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nCrisisMerge = sqlite3_value_int(pVal); + } + if( nCrisisMerge<0 ){ + *pbBadkey = 1; + }else{ + if( nCrisisMerge<=1 ) nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; + if( nCrisisMerge>=FTS5_MAX_SEGMENT ) nCrisisMerge = FTS5_MAX_SEGMENT-1; + pConfig->nCrisisMerge = nCrisisMerge; + } + } + + else if( 0==sqlite3_stricmp(zKey, "deletemerge") ){ + int nVal = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nVal = sqlite3_value_int(pVal); + }else{ + *pbBadkey = 1; + } + if( nVal<0 ) nVal = FTS5_DEFAULT_DELETE_AUTOMERGE; + if( nVal>100 ) nVal = 0; + pConfig->nDeleteMerge = nVal; + } + + else if( 0==sqlite3_stricmp(zKey, "rank") ){ + const char *zIn = (const char*)sqlite3_value_text(pVal); + char *zRank; + char *zRankArgs; + rc = sqlite3Fts5ConfigParseRank(zIn, &zRank, &zRankArgs); + if( rc==SQLITE_OK ){ + sqlite3_free(pConfig->zRank); + sqlite3_free(pConfig->zRankArgs); + pConfig->zRank = zRank; + pConfig->zRankArgs = zRankArgs; + }else if( rc==SQLITE_ERROR ){ + rc = SQLITE_OK; + *pbBadkey = 1; + } + } + + else if( 0==sqlite3_stricmp(zKey, "secure-delete") ){ + int bVal = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + bVal = sqlite3_value_int(pVal); + } + if( bVal<0 ){ + *pbBadkey = 1; + }else{ + pConfig->bSecureDelete = (bVal ? 1 : 0); + } + }else{ + *pbBadkey = 1; + } + return rc; +} + +/* +** Load the contents of the %_config table into memory. +*/ +static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ + const char *zSelect = "SELECT k, v FROM %Q.'%q_config'"; + char *zSql; + sqlite3_stmt *p = 0; + int rc = SQLITE_OK; + int iVersion = 0; + + /* Set default values */ + pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE; + pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE; + pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE; + pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; + pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE; + pConfig->nDeleteMerge = FTS5_DEFAULT_DELETE_AUTOMERGE; + + zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName); + if( zSql ){ + rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p, 0); + sqlite3_free(zSql); + } + + assert( rc==SQLITE_OK || p==0 ); + if( rc==SQLITE_OK ){ + while( SQLITE_ROW==sqlite3_step(p) ){ + const char *zK = (const char*)sqlite3_column_text(p, 0); + sqlite3_value *pVal = sqlite3_column_value(p, 1); + if( 0==sqlite3_stricmp(zK, "version") ){ + iVersion = sqlite3_value_int(pVal); + }else{ + int bDummy = 0; + sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, &bDummy); + } + } + rc = sqlite3_finalize(p); + } + + if( rc==SQLITE_OK + && iVersion!=FTS5_CURRENT_VERSION + && iVersion!=FTS5_CURRENT_VERSION_SECUREDELETE + ){ + rc = SQLITE_ERROR; + if( pConfig->pzErrmsg ){ + assert( 0==*pConfig->pzErrmsg ); + *pConfig->pzErrmsg = sqlite3_mprintf("invalid fts5 file format " + "(found %d, expected %d or %d) - run 'rebuild'", + iVersion, FTS5_CURRENT_VERSION, FTS5_CURRENT_VERSION_SECUREDELETE + ); + } + }else{ + pConfig->iVersion = iVersion; + } + + if( rc==SQLITE_OK ){ + pConfig->iCookie = iCookie; + } + return rc; +} + +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +*/ + + + +/* #include "fts5Int.h" */ +/* #include "fts5parse.h" */ + +#ifndef SQLITE_FTS5_MAX_EXPR_DEPTH +# define SQLITE_FTS5_MAX_EXPR_DEPTH 256 +#endif + +/* +** All token types in the generated fts5parse.h file are greater than 0. +*/ +#define FTS5_EOF 0 + +#define FTS5_LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) + +typedef struct Fts5ExprTerm Fts5ExprTerm; + +/* +** Functions generated by lemon from fts5parse.y. +*/ +static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(u64)); +static void sqlite3Fts5ParserFree(void*, void (*freeProc)(void*)); +static void sqlite3Fts5Parser(void*, int, Fts5Token, Fts5Parse*); +#ifndef NDEBUG +/* #include <stdio.h> */ +static void sqlite3Fts5ParserTrace(FILE*, char*); +#endif +static int sqlite3Fts5ParserFallback(int); + + +struct Fts5Expr { + Fts5Index *pIndex; + Fts5Config *pConfig; + Fts5ExprNode *pRoot; + int bDesc; /* Iterate in descending rowid order */ + int nPhrase; /* Number of phrases in expression */ + Fts5ExprPhrase **apExprPhrase; /* Pointers to phrase objects */ +}; + +/* +** eType: +** Expression node type. Always one of: +** +** FTS5_AND (nChild, apChild valid) +** FTS5_OR (nChild, apChild valid) +** FTS5_NOT (nChild, apChild valid) +** FTS5_STRING (pNear valid) +** FTS5_TERM (pNear valid) +** +** iHeight: +** Distance from this node to furthest leaf. This is always 0 for nodes +** of type FTS5_STRING and FTS5_TERM. For all other nodes it is one +** greater than the largest child value. +*/ +struct Fts5ExprNode { + int eType; /* Node type */ + int bEof; /* True at EOF */ + int bNomatch; /* True if entry is not a match */ + int iHeight; /* Distance to tree leaf nodes */ + + /* Next method for this node. */ + int (*xNext)(Fts5Expr*, Fts5ExprNode*, int, i64); + + i64 iRowid; /* Current rowid */ + Fts5ExprNearset *pNear; /* For FTS5_STRING - cluster of phrases */ + + /* Child nodes. For a NOT node, this array always contains 2 entries. For + ** AND or OR nodes, it contains 2 or more entries. */ + int nChild; /* Number of child nodes */ + Fts5ExprNode *apChild[1]; /* Array of child nodes */ +}; + +#define Fts5NodeIsString(p) ((p)->eType==FTS5_TERM || (p)->eType==FTS5_STRING) + +/* +** Invoke the xNext method of an Fts5ExprNode object. This macro should be +** used as if it has the same signature as the xNext() methods themselves. +*/ +#define fts5ExprNodeNext(a,b,c,d) (b)->xNext((a), (b), (c), (d)) + +/* +** An instance of the following structure represents a single search term +** or term prefix. +*/ +struct Fts5ExprTerm { + u8 bPrefix; /* True for a prefix term */ + u8 bFirst; /* True if token must be first in column */ + char *zTerm; /* nul-terminated term */ + Fts5IndexIter *pIter; /* Iterator for this term */ + Fts5ExprTerm *pSynonym; /* Pointer to first in list of synonyms */ +}; + +/* +** A phrase. One or more terms that must appear in a contiguous sequence +** within a document for it to match. +*/ +struct Fts5ExprPhrase { + Fts5ExprNode *pNode; /* FTS5_STRING node this phrase is part of */ + Fts5Buffer poslist; /* Current position list */ + int nTerm; /* Number of entries in aTerm[] */ + Fts5ExprTerm aTerm[1]; /* Terms that make up this phrase */ +}; + +/* +** One or more phrases that must appear within a certain token distance of +** each other within each matching document. +*/ +struct Fts5ExprNearset { + int nNear; /* NEAR parameter */ + Fts5Colset *pColset; /* Columns to search (NULL -> all columns) */ + int nPhrase; /* Number of entries in aPhrase[] array */ + Fts5ExprPhrase *apPhrase[1]; /* Array of phrase pointers */ +}; + + +/* +** Parse context. +*/ +struct Fts5Parse { + Fts5Config *pConfig; + char *zErr; + int rc; + int nPhrase; /* Size of apPhrase array */ + Fts5ExprPhrase **apPhrase; /* Array of all phrases */ + Fts5ExprNode *pExpr; /* Result of a successful parse */ + int bPhraseToAnd; /* Convert "a+b" to "a AND b" */ +}; + +/* +** Check that the Fts5ExprNode.iHeight variables are set correctly in +** the expression tree passed as the only argument. +*/ +#ifndef NDEBUG +static void assert_expr_depth_ok(int rc, Fts5ExprNode *p){ + if( rc==SQLITE_OK ){ + if( p->eType==FTS5_TERM || p->eType==FTS5_STRING || p->eType==0 ){ + assert( p->iHeight==0 ); + }else{ + int ii; + int iMaxChild = 0; + for(ii=0; ii<p->nChild; ii++){ + Fts5ExprNode *pChild = p->apChild[ii]; + iMaxChild = MAX(iMaxChild, pChild->iHeight); + assert_expr_depth_ok(SQLITE_OK, pChild); + } + assert( p->iHeight==iMaxChild+1 ); + } + } +} +#else +# define assert_expr_depth_ok(rc, p) +#endif + +static void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + if( pParse->rc==SQLITE_OK ){ + assert( pParse->zErr==0 ); + pParse->zErr = sqlite3_vmprintf(zFmt, ap); + pParse->rc = SQLITE_ERROR; + } + va_end(ap); +} + +static int fts5ExprIsspace(char t){ + return t==' ' || t=='\t' || t=='\n' || t=='\r'; +} + +/* +** Read the first token from the nul-terminated string at *pz. +*/ +static int fts5ExprGetToken( + Fts5Parse *pParse, + const char **pz, /* IN/OUT: Pointer into buffer */ + Fts5Token *pToken +){ + const char *z = *pz; + int tok; + + /* Skip past any whitespace */ + while( fts5ExprIsspace(*z) ) z++; + + pToken->p = z; + pToken->n = 1; + switch( *z ){ + case '(': tok = FTS5_LP; break; + case ')': tok = FTS5_RP; break; + case '{': tok = FTS5_LCP; break; + case '}': tok = FTS5_RCP; break; + case ':': tok = FTS5_COLON; break; + case ',': tok = FTS5_COMMA; break; + case '+': tok = FTS5_PLUS; break; + case '*': tok = FTS5_STAR; break; + case '-': tok = FTS5_MINUS; break; + case '^': tok = FTS5_CARET; break; + case '\0': tok = FTS5_EOF; break; + + case '"': { + const char *z2; + tok = FTS5_STRING; + + for(z2=&z[1]; 1; z2++){ + if( z2[0]=='"' ){ + z2++; + if( z2[0]!='"' ) break; + } + if( z2[0]=='\0' ){ + sqlite3Fts5ParseError(pParse, "unterminated string"); + return FTS5_EOF; + } + } + pToken->n = (z2 - z); + break; + } + + default: { + const char *z2; + if( sqlite3Fts5IsBareword(z[0])==0 ){ + sqlite3Fts5ParseError(pParse, "fts5: syntax error near \"%.1s\"", z); + return FTS5_EOF; + } + tok = FTS5_STRING; + for(z2=&z[1]; sqlite3Fts5IsBareword(*z2); z2++); + pToken->n = (z2 - z); + if( pToken->n==2 && memcmp(pToken->p, "OR", 2)==0 ) tok = FTS5_OR; + if( pToken->n==3 && memcmp(pToken->p, "NOT", 3)==0 ) tok = FTS5_NOT; + if( pToken->n==3 && memcmp(pToken->p, "AND", 3)==0 ) tok = FTS5_AND; + break; + } + } + + *pz = &pToken->p[pToken->n]; + return tok; +} + +static void *fts5ParseAlloc(u64 t){ return sqlite3_malloc64((sqlite3_int64)t);} +static void fts5ParseFree(void *p){ sqlite3_free(p); } + +static int sqlite3Fts5ExprNew( + Fts5Config *pConfig, /* FTS5 Configuration */ + int bPhraseToAnd, + int iCol, + const char *zExpr, /* Expression text */ + Fts5Expr **ppNew, + char **pzErr +){ + Fts5Parse sParse; + Fts5Token token; + const char *z = zExpr; + int t; /* Next token type */ + void *pEngine; + Fts5Expr *pNew; + + *ppNew = 0; + *pzErr = 0; + memset(&sParse, 0, sizeof(sParse)); + sParse.bPhraseToAnd = bPhraseToAnd; + pEngine = sqlite3Fts5ParserAlloc(fts5ParseAlloc); + if( pEngine==0 ){ return SQLITE_NOMEM; } + sParse.pConfig = pConfig; + + do { + t = fts5ExprGetToken(&sParse, &z, &token); + sqlite3Fts5Parser(pEngine, t, token, &sParse); + }while( sParse.rc==SQLITE_OK && t!=FTS5_EOF ); + sqlite3Fts5ParserFree(pEngine, fts5ParseFree); + + assert_expr_depth_ok(sParse.rc, sParse.pExpr); + + /* If the LHS of the MATCH expression was a user column, apply the + ** implicit column-filter. */ + if( iCol<pConfig->nCol && sParse.pExpr && sParse.rc==SQLITE_OK ){ + int n = sizeof(Fts5Colset); + Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&sParse.rc, n); + if( pColset ){ + pColset->nCol = 1; + pColset->aiCol[0] = iCol; + sqlite3Fts5ParseSetColset(&sParse, sParse.pExpr, pColset); + } + } + + assert( sParse.rc!=SQLITE_OK || sParse.zErr==0 ); + if( sParse.rc==SQLITE_OK ){ + *ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr)); + if( pNew==0 ){ + sParse.rc = SQLITE_NOMEM; + sqlite3Fts5ParseNodeFree(sParse.pExpr); + }else{ + if( !sParse.pExpr ){ + const int nByte = sizeof(Fts5ExprNode); + pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&sParse.rc, nByte); + if( pNew->pRoot ){ + pNew->pRoot->bEof = 1; + } + }else{ + pNew->pRoot = sParse.pExpr; + } + pNew->pIndex = 0; + pNew->pConfig = pConfig; + pNew->apExprPhrase = sParse.apPhrase; + pNew->nPhrase = sParse.nPhrase; + pNew->bDesc = 0; + sParse.apPhrase = 0; + } + }else{ + sqlite3Fts5ParseNodeFree(sParse.pExpr); + } + + sqlite3_free(sParse.apPhrase); + *pzErr = sParse.zErr; + return sParse.rc; +} + +/* +** Assuming that buffer z is at least nByte bytes in size and contains a +** valid utf-8 string, return the number of characters in the string. +*/ +static int fts5ExprCountChar(const char *z, int nByte){ + int nRet = 0; + int ii; + for(ii=0; ii<nByte; ii++){ + if( (z[ii] & 0xC0)!=0x80 ) nRet++; + } + return nRet; +} + +/* +** This function is only called when using the special 'trigram' tokenizer. +** Argument zText contains the text of a LIKE or GLOB pattern matched +** against column iCol. This function creates and compiles an FTS5 MATCH +** expression that will match a superset of the rows matched by the LIKE or +** GLOB. If successful, SQLITE_OK is returned. Otherwise, an SQLite error +** code. +*/ +static int sqlite3Fts5ExprPattern( + Fts5Config *pConfig, int bGlob, int iCol, const char *zText, Fts5Expr **pp +){ + i64 nText = strlen(zText); + char *zExpr = (char*)sqlite3_malloc64(nText*4 + 1); + int rc = SQLITE_OK; + + if( zExpr==0 ){ + rc = SQLITE_NOMEM; + }else{ + char aSpec[3]; + int iOut = 0; + int i = 0; + int iFirst = 0; + + if( bGlob==0 ){ + aSpec[0] = '_'; + aSpec[1] = '%'; + aSpec[2] = 0; + }else{ + aSpec[0] = '*'; + aSpec[1] = '?'; + aSpec[2] = '['; + } + + while( i<=nText ){ + if( i==nText + || zText[i]==aSpec[0] || zText[i]==aSpec[1] || zText[i]==aSpec[2] + ){ + + if( fts5ExprCountChar(&zText[iFirst], i-iFirst)>=3 ){ + int jj; + zExpr[iOut++] = '"'; + for(jj=iFirst; jj<i; jj++){ + zExpr[iOut++] = zText[jj]; + if( zText[jj]=='"' ) zExpr[iOut++] = '"'; + } + zExpr[iOut++] = '"'; + zExpr[iOut++] = ' '; + } + if( zText[i]==aSpec[2] ){ + i += 2; + if( zText[i-1]=='^' ) i++; + while( i<nText && zText[i]!=']' ) i++; + } + iFirst = i+1; + } + i++; + } + if( iOut>0 ){ + int bAnd = 0; + if( pConfig->eDetail!=FTS5_DETAIL_FULL ){ + bAnd = 1; + if( pConfig->eDetail==FTS5_DETAIL_NONE ){ + iCol = pConfig->nCol; + } + } + zExpr[iOut] = '\0'; + rc = sqlite3Fts5ExprNew(pConfig, bAnd, iCol, zExpr, pp,pConfig->pzErrmsg); + }else{ + *pp = 0; + } + sqlite3_free(zExpr); + } + + return rc; +} + +/* +** Free the expression node object passed as the only argument. +*/ +static void sqlite3Fts5ParseNodeFree(Fts5ExprNode *p){ + if( p ){ + int i; + for(i=0; i<p->nChild; i++){ + sqlite3Fts5ParseNodeFree(p->apChild[i]); + } + sqlite3Fts5ParseNearsetFree(p->pNear); + sqlite3_free(p); + } +} + +/* +** Free the expression object passed as the only argument. +*/ +static void sqlite3Fts5ExprFree(Fts5Expr *p){ + if( p ){ + sqlite3Fts5ParseNodeFree(p->pRoot); + sqlite3_free(p->apExprPhrase); + sqlite3_free(p); + } +} + +static int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){ + Fts5Parse sParse; + memset(&sParse, 0, sizeof(sParse)); + + if( *pp1 && p2 ){ + Fts5Expr *p1 = *pp1; + int nPhrase = p1->nPhrase + p2->nPhrase; + + p1->pRoot = sqlite3Fts5ParseNode(&sParse, FTS5_AND, p1->pRoot, p2->pRoot,0); + p2->pRoot = 0; + + if( sParse.rc==SQLITE_OK ){ + Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc( + p1->apExprPhrase, nPhrase * sizeof(Fts5ExprPhrase*) + ); + if( ap==0 ){ + sParse.rc = SQLITE_NOMEM; + }else{ + int i; + memmove(&ap[p2->nPhrase], ap, p1->nPhrase*sizeof(Fts5ExprPhrase*)); + for(i=0; i<p2->nPhrase; i++){ + ap[i] = p2->apExprPhrase[i]; + } + p1->nPhrase = nPhrase; + p1->apExprPhrase = ap; + } + } + sqlite3_free(p2->apExprPhrase); + sqlite3_free(p2); + }else if( p2 ){ + *pp1 = p2; + } + + return sParse.rc; +} + +/* +** Argument pTerm must be a synonym iterator. Return the current rowid +** that it points to. +*/ +static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){ + i64 iRet = 0; + int bRetValid = 0; + Fts5ExprTerm *p; + + assert( pTerm ); + assert( pTerm->pSynonym ); + assert( bDesc==0 || bDesc==1 ); + for(p=pTerm; p; p=p->pSynonym){ + if( 0==sqlite3Fts5IterEof(p->pIter) ){ + i64 iRowid = p->pIter->iRowid; + if( bRetValid==0 || (bDesc!=(iRowid<iRet)) ){ + iRet = iRowid; + bRetValid = 1; + } + } + } + + if( pbEof && bRetValid==0 ) *pbEof = 1; + return iRet; +} + +/* +** Argument pTerm must be a synonym iterator. +*/ +static int fts5ExprSynonymList( + Fts5ExprTerm *pTerm, + i64 iRowid, + Fts5Buffer *pBuf, /* Use this buffer for space if required */ + u8 **pa, int *pn +){ + Fts5PoslistReader aStatic[4]; + Fts5PoslistReader *aIter = aStatic; + int nIter = 0; + int nAlloc = 4; + int rc = SQLITE_OK; + Fts5ExprTerm *p; + + assert( pTerm->pSynonym ); + for(p=pTerm; p; p=p->pSynonym){ + Fts5IndexIter *pIter = p->pIter; + if( sqlite3Fts5IterEof(pIter)==0 && pIter->iRowid==iRowid ){ + if( pIter->nData==0 ) continue; + if( nIter==nAlloc ){ + sqlite3_int64 nByte = sizeof(Fts5PoslistReader) * nAlloc * 2; + Fts5PoslistReader *aNew = (Fts5PoslistReader*)sqlite3_malloc64(nByte); + if( aNew==0 ){ + rc = SQLITE_NOMEM; + goto synonym_poslist_out; + } + memcpy(aNew, aIter, sizeof(Fts5PoslistReader) * nIter); + nAlloc = nAlloc*2; + if( aIter!=aStatic ) sqlite3_free(aIter); + aIter = aNew; + } + sqlite3Fts5PoslistReaderInit(pIter->pData, pIter->nData, &aIter[nIter]); + assert( aIter[nIter].bEof==0 ); + nIter++; + } + } + + if( nIter==1 ){ + *pa = (u8*)aIter[0].a; + *pn = aIter[0].n; + }else{ + Fts5PoslistWriter writer = {0}; + i64 iPrev = -1; + fts5BufferZero(pBuf); + while( 1 ){ + int i; + i64 iMin = FTS5_LARGEST_INT64; + for(i=0; i<nIter; i++){ + if( aIter[i].bEof==0 ){ + if( aIter[i].iPos==iPrev ){ + if( sqlite3Fts5PoslistReaderNext(&aIter[i]) ) continue; + } + if( aIter[i].iPos<iMin ){ + iMin = aIter[i].iPos; + } + } + } + if( iMin==FTS5_LARGEST_INT64 || rc!=SQLITE_OK ) break; + rc = sqlite3Fts5PoslistWriterAppend(pBuf, &writer, iMin); + iPrev = iMin; + } + if( rc==SQLITE_OK ){ + *pa = pBuf->p; + *pn = pBuf->n; + } + } + + synonym_poslist_out: + if( aIter!=aStatic ) sqlite3_free(aIter); + return rc; +} + + +/* +** All individual term iterators in pPhrase are guaranteed to be valid and +** pointing to the same rowid when this function is called. This function +** checks if the current rowid really is a match, and if so populates +** the pPhrase->poslist buffer accordingly. Output parameter *pbMatch +** is set to true if this is really a match, or false otherwise. +** +** SQLITE_OK is returned if an error occurs, or an SQLite error code +** otherwise. It is not considered an error code if the current rowid is +** not a match. +*/ +static int fts5ExprPhraseIsMatch( + Fts5ExprNode *pNode, /* Node pPhrase belongs to */ + Fts5ExprPhrase *pPhrase, /* Phrase object to initialize */ + int *pbMatch /* OUT: Set to true if really a match */ +){ + Fts5PoslistWriter writer = {0}; + Fts5PoslistReader aStatic[4]; + Fts5PoslistReader *aIter = aStatic; + int i; + int rc = SQLITE_OK; + int bFirst = pPhrase->aTerm[0].bFirst; + + fts5BufferZero(&pPhrase->poslist); + + /* If the aStatic[] array is not large enough, allocate a large array + ** using sqlite3_malloc(). This approach could be improved upon. */ + if( pPhrase->nTerm>ArraySize(aStatic) ){ + sqlite3_int64 nByte = sizeof(Fts5PoslistReader) * pPhrase->nTerm; + aIter = (Fts5PoslistReader*)sqlite3_malloc64(nByte); + if( !aIter ) return SQLITE_NOMEM; + } + memset(aIter, 0, sizeof(Fts5PoslistReader) * pPhrase->nTerm); + + /* Initialize a term iterator for each term in the phrase */ + for(i=0; i<pPhrase->nTerm; i++){ + Fts5ExprTerm *pTerm = &pPhrase->aTerm[i]; + int n = 0; + int bFlag = 0; + u8 *a = 0; + if( pTerm->pSynonym ){ + Fts5Buffer buf = {0, 0, 0}; + rc = fts5ExprSynonymList(pTerm, pNode->iRowid, &buf, &a, &n); + if( rc ){ + sqlite3_free(a); + goto ismatch_out; + } + if( a==buf.p ) bFlag = 1; + }else{ + a = (u8*)pTerm->pIter->pData; + n = pTerm->pIter->nData; + } + sqlite3Fts5PoslistReaderInit(a, n, &aIter[i]); + aIter[i].bFlag = (u8)bFlag; + if( aIter[i].bEof ) goto ismatch_out; + } + + while( 1 ){ + int bMatch; + i64 iPos = aIter[0].iPos; + do { + bMatch = 1; + for(i=0; i<pPhrase->nTerm; i++){ + Fts5PoslistReader *pPos = &aIter[i]; + i64 iAdj = iPos + i; + if( pPos->iPos!=iAdj ){ + bMatch = 0; + while( pPos->iPos<iAdj ){ + if( sqlite3Fts5PoslistReaderNext(pPos) ) goto ismatch_out; + } + if( pPos->iPos>iAdj ) iPos = pPos->iPos-i; + } + } + }while( bMatch==0 ); + + /* Append position iPos to the output */ + if( bFirst==0 || FTS5_POS2OFFSET(iPos)==0 ){ + rc = sqlite3Fts5PoslistWriterAppend(&pPhrase->poslist, &writer, iPos); + if( rc!=SQLITE_OK ) goto ismatch_out; + } + + for(i=0; i<pPhrase->nTerm; i++){ + if( sqlite3Fts5PoslistReaderNext(&aIter[i]) ) goto ismatch_out; + } + } + + ismatch_out: + *pbMatch = (pPhrase->poslist.n>0); + for(i=0; i<pPhrase->nTerm; i++){ + if( aIter[i].bFlag ) sqlite3_free((u8*)aIter[i].a); + } + if( aIter!=aStatic ) sqlite3_free(aIter); + return rc; +} + +typedef struct Fts5LookaheadReader Fts5LookaheadReader; +struct Fts5LookaheadReader { + const u8 *a; /* Buffer containing position list */ + int n; /* Size of buffer a[] in bytes */ + int i; /* Current offset in position list */ + i64 iPos; /* Current position */ + i64 iLookahead; /* Next position */ +}; + +#define FTS5_LOOKAHEAD_EOF (((i64)1) << 62) + +static int fts5LookaheadReaderNext(Fts5LookaheadReader *p){ + p->iPos = p->iLookahead; + if( sqlite3Fts5PoslistNext64(p->a, p->n, &p->i, &p->iLookahead) ){ + p->iLookahead = FTS5_LOOKAHEAD_EOF; + } + return (p->iPos==FTS5_LOOKAHEAD_EOF); +} + +static int fts5LookaheadReaderInit( + const u8 *a, int n, /* Buffer to read position list from */ + Fts5LookaheadReader *p /* Iterator object to initialize */ +){ + memset(p, 0, sizeof(Fts5LookaheadReader)); + p->a = a; + p->n = n; + fts5LookaheadReaderNext(p); + return fts5LookaheadReaderNext(p); +} + +typedef struct Fts5NearTrimmer Fts5NearTrimmer; +struct Fts5NearTrimmer { + Fts5LookaheadReader reader; /* Input iterator */ + Fts5PoslistWriter writer; /* Writer context */ + Fts5Buffer *pOut; /* Output poslist */ +}; + +/* +** The near-set object passed as the first argument contains more than +** one phrase. All phrases currently point to the same row. The +** Fts5ExprPhrase.poslist buffers are populated accordingly. This function +** tests if the current row contains instances of each phrase sufficiently +** close together to meet the NEAR constraint. Non-zero is returned if it +** does, or zero otherwise. +** +** If in/out parameter (*pRc) is set to other than SQLITE_OK when this +** function is called, it is a no-op. Or, if an error (e.g. SQLITE_NOMEM) +** occurs within this function (*pRc) is set accordingly before returning. +** The return value is undefined in both these cases. +** +** If no error occurs and non-zero (a match) is returned, the position-list +** of each phrase object is edited to contain only those entries that +** meet the constraint before returning. +*/ +static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){ + Fts5NearTrimmer aStatic[4]; + Fts5NearTrimmer *a = aStatic; + Fts5ExprPhrase **apPhrase = pNear->apPhrase; + + int i; + int rc = *pRc; + int bMatch; + + assert( pNear->nPhrase>1 ); + + /* If the aStatic[] array is not large enough, allocate a large array + ** using sqlite3_malloc(). This approach could be improved upon. */ + if( pNear->nPhrase>ArraySize(aStatic) ){ + sqlite3_int64 nByte = sizeof(Fts5NearTrimmer) * pNear->nPhrase; + a = (Fts5NearTrimmer*)sqlite3Fts5MallocZero(&rc, nByte); + }else{ + memset(aStatic, 0, sizeof(aStatic)); + } + if( rc!=SQLITE_OK ){ + *pRc = rc; + return 0; + } + + /* Initialize a lookahead iterator for each phrase. After passing the + ** buffer and buffer size to the lookaside-reader init function, zero + ** the phrase poslist buffer. The new poslist for the phrase (containing + ** the same entries as the original with some entries removed on account + ** of the NEAR constraint) is written over the original even as it is + ** being read. This is safe as the entries for the new poslist are a + ** subset of the old, so it is not possible for data yet to be read to + ** be overwritten. */ + for(i=0; i<pNear->nPhrase; i++){ + Fts5Buffer *pPoslist = &apPhrase[i]->poslist; + fts5LookaheadReaderInit(pPoslist->p, pPoslist->n, &a[i].reader); + pPoslist->n = 0; + a[i].pOut = pPoslist; + } + + while( 1 ){ + int iAdv; + i64 iMin; + i64 iMax; + + /* This block advances the phrase iterators until they point to a set of + ** entries that together comprise a match. */ + iMax = a[0].reader.iPos; + do { + bMatch = 1; + for(i=0; i<pNear->nPhrase; i++){ + Fts5LookaheadReader *pPos = &a[i].reader; + iMin = iMax - pNear->apPhrase[i]->nTerm - pNear->nNear; + if( pPos->iPos<iMin || pPos->iPos>iMax ){ + bMatch = 0; + while( pPos->iPos<iMin ){ + if( fts5LookaheadReaderNext(pPos) ) goto ismatch_out; + } + if( pPos->iPos>iMax ) iMax = pPos->iPos; + } + } + }while( bMatch==0 ); + + /* Add an entry to each output position list */ + for(i=0; i<pNear->nPhrase; i++){ + i64 iPos = a[i].reader.iPos; + Fts5PoslistWriter *pWriter = &a[i].writer; + if( a[i].pOut->n==0 || iPos!=pWriter->iPrev ){ + sqlite3Fts5PoslistWriterAppend(a[i].pOut, pWriter, iPos); + } + } + + iAdv = 0; + iMin = a[0].reader.iLookahead; + for(i=0; i<pNear->nPhrase; i++){ + if( a[i].reader.iLookahead < iMin ){ + iMin = a[i].reader.iLookahead; + iAdv = i; + } + } + if( fts5LookaheadReaderNext(&a[iAdv].reader) ) goto ismatch_out; + } + + ismatch_out: { + int bRet = a[0].pOut->n>0; + *pRc = rc; + if( a!=aStatic ) sqlite3_free(a); + return bRet; + } +} + +/* +** Advance iterator pIter until it points to a value equal to or laster +** than the initial value of *piLast. If this means the iterator points +** to a value laster than *piLast, update *piLast to the new lastest value. +** +** If the iterator reaches EOF, set *pbEof to true before returning. If +** an error occurs, set *pRc to an error code. If either *pbEof or *pRc +** are set, return a non-zero value. Otherwise, return zero. +*/ +static int fts5ExprAdvanceto( + Fts5IndexIter *pIter, /* Iterator to advance */ + int bDesc, /* True if iterator is "rowid DESC" */ + i64 *piLast, /* IN/OUT: Lastest rowid seen so far */ + int *pRc, /* OUT: Error code */ + int *pbEof /* OUT: Set to true if EOF */ +){ + i64 iLast = *piLast; + i64 iRowid; + + iRowid = pIter->iRowid; + if( (bDesc==0 && iLast>iRowid) || (bDesc && iLast<iRowid) ){ + int rc = sqlite3Fts5IterNextFrom(pIter, iLast); + if( rc || sqlite3Fts5IterEof(pIter) ){ + *pRc = rc; + *pbEof = 1; + return 1; + } + iRowid = pIter->iRowid; + assert( (bDesc==0 && iRowid>=iLast) || (bDesc==1 && iRowid<=iLast) ); + } + *piLast = iRowid; + + return 0; +} + +static int fts5ExprSynonymAdvanceto( + Fts5ExprTerm *pTerm, /* Term iterator to advance */ + int bDesc, /* True if iterator is "rowid DESC" */ + i64 *piLast, /* IN/OUT: Lastest rowid seen so far */ + int *pRc /* OUT: Error code */ +){ + int rc = SQLITE_OK; + i64 iLast = *piLast; + Fts5ExprTerm *p; + int bEof = 0; + + for(p=pTerm; rc==SQLITE_OK && p; p=p->pSynonym){ + if( sqlite3Fts5IterEof(p->pIter)==0 ){ + i64 iRowid = p->pIter->iRowid; + if( (bDesc==0 && iLast>iRowid) || (bDesc && iLast<iRowid) ){ + rc = sqlite3Fts5IterNextFrom(p->pIter, iLast); + } + } + } + + if( rc!=SQLITE_OK ){ + *pRc = rc; + bEof = 1; + }else{ + *piLast = fts5ExprSynonymRowid(pTerm, bDesc, &bEof); + } + return bEof; +} + + +static int fts5ExprNearTest( + int *pRc, + Fts5Expr *pExpr, /* Expression that pNear is a part of */ + Fts5ExprNode *pNode /* The "NEAR" node (FTS5_STRING) */ +){ + Fts5ExprNearset *pNear = pNode->pNear; + int rc = *pRc; + + if( pExpr->pConfig->eDetail!=FTS5_DETAIL_FULL ){ + Fts5ExprTerm *pTerm; + Fts5ExprPhrase *pPhrase = pNear->apPhrase[0]; + pPhrase->poslist.n = 0; + for(pTerm=&pPhrase->aTerm[0]; pTerm; pTerm=pTerm->pSynonym){ + Fts5IndexIter *pIter = pTerm->pIter; + if( sqlite3Fts5IterEof(pIter)==0 ){ + if( pIter->iRowid==pNode->iRowid && pIter->nData>0 ){ + pPhrase->poslist.n = 1; + } + } + } + return pPhrase->poslist.n; + }else{ + int i; + + /* Check that each phrase in the nearset matches the current row. + ** Populate the pPhrase->poslist buffers at the same time. If any + ** phrase is not a match, break out of the loop early. */ + for(i=0; rc==SQLITE_OK && i<pNear->nPhrase; i++){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; + if( pPhrase->nTerm>1 || pPhrase->aTerm[0].pSynonym + || pNear->pColset || pPhrase->aTerm[0].bFirst + ){ + int bMatch = 0; + rc = fts5ExprPhraseIsMatch(pNode, pPhrase, &bMatch); + if( bMatch==0 ) break; + }else{ + Fts5IndexIter *pIter = pPhrase->aTerm[0].pIter; + fts5BufferSet(&rc, &pPhrase->poslist, pIter->nData, pIter->pData); + } + } + + *pRc = rc; + if( i==pNear->nPhrase && (i==1 || fts5ExprNearIsMatch(pRc, pNear)) ){ + return 1; + } + return 0; + } +} + + +/* +** Initialize all term iterators in the pNear object. If any term is found +** to match no documents at all, return immediately without initializing any +** further iterators. +** +** If an error occurs, return an SQLite error code. Otherwise, return +** SQLITE_OK. It is not considered an error if some term matches zero +** documents. +*/ +static int fts5ExprNearInitAll( + Fts5Expr *pExpr, + Fts5ExprNode *pNode +){ + Fts5ExprNearset *pNear = pNode->pNear; + int i; + + assert( pNode->bNomatch==0 ); + for(i=0; i<pNear->nPhrase; i++){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; + if( pPhrase->nTerm==0 ){ + pNode->bEof = 1; + return SQLITE_OK; + }else{ + int j; + for(j=0; j<pPhrase->nTerm; j++){ + Fts5ExprTerm *pTerm = &pPhrase->aTerm[j]; + Fts5ExprTerm *p; + int bHit = 0; + + for(p=pTerm; p; p=p->pSynonym){ + int rc; + if( p->pIter ){ + sqlite3Fts5IterClose(p->pIter); + p->pIter = 0; + } + rc = sqlite3Fts5IndexQuery( + pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm), + (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | + (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0), + pNear->pColset, + &p->pIter + ); + assert( (rc==SQLITE_OK)==(p->pIter!=0) ); + if( rc!=SQLITE_OK ) return rc; + if( 0==sqlite3Fts5IterEof(p->pIter) ){ + bHit = 1; + } + } + + if( bHit==0 ){ + pNode->bEof = 1; + return SQLITE_OK; + } + } + } + } + + pNode->bEof = 0; + return SQLITE_OK; +} + +/* +** If pExpr is an ASC iterator, this function returns a value with the +** same sign as: +** +** (iLhs - iRhs) +** +** Otherwise, if this is a DESC iterator, the opposite is returned: +** +** (iRhs - iLhs) +*/ +static int fts5RowidCmp( + Fts5Expr *pExpr, + i64 iLhs, + i64 iRhs +){ + assert( pExpr->bDesc==0 || pExpr->bDesc==1 ); + if( pExpr->bDesc==0 ){ + if( iLhs<iRhs ) return -1; + return (iLhs > iRhs); + }else{ + if( iLhs>iRhs ) return -1; + return (iLhs < iRhs); + } +} + +static void fts5ExprSetEof(Fts5ExprNode *pNode){ + int i; + pNode->bEof = 1; + pNode->bNomatch = 0; + for(i=0; i<pNode->nChild; i++){ + fts5ExprSetEof(pNode->apChild[i]); + } +} + +static void fts5ExprNodeZeroPoslist(Fts5ExprNode *pNode){ + if( pNode->eType==FTS5_STRING || pNode->eType==FTS5_TERM ){ + Fts5ExprNearset *pNear = pNode->pNear; + int i; + for(i=0; i<pNear->nPhrase; i++){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; + pPhrase->poslist.n = 0; + } + }else{ + int i; + for(i=0; i<pNode->nChild; i++){ + fts5ExprNodeZeroPoslist(pNode->apChild[i]); + } + } +} + + + +/* +** Compare the values currently indicated by the two nodes as follows: +** +** res = (*p1) - (*p2) +** +** Nodes that point to values that come later in the iteration order are +** considered to be larger. Nodes at EOF are the largest of all. +** +** This means that if the iteration order is ASC, then numerically larger +** rowids are considered larger. Or if it is the default DESC, numerically +** smaller rowids are larger. +*/ +static int fts5NodeCompare( + Fts5Expr *pExpr, + Fts5ExprNode *p1, + Fts5ExprNode *p2 +){ + if( p2->bEof ) return -1; + if( p1->bEof ) return +1; + return fts5RowidCmp(pExpr, p1->iRowid, p2->iRowid); +} + +/* +** All individual term iterators in pNear are guaranteed to be valid when +** this function is called. This function checks if all term iterators +** point to the same rowid, and if not, advances them until they do. +** If an EOF is reached before this happens, *pbEof is set to true before +** returning. +** +** SQLITE_OK is returned if an error occurs, or an SQLite error code +** otherwise. It is not considered an error code if an iterator reaches +** EOF. +*/ +static int fts5ExprNodeTest_STRING( + Fts5Expr *pExpr, /* Expression pPhrase belongs to */ + Fts5ExprNode *pNode +){ + Fts5ExprNearset *pNear = pNode->pNear; + Fts5ExprPhrase *pLeft = pNear->apPhrase[0]; + int rc = SQLITE_OK; + i64 iLast; /* Lastest rowid any iterator points to */ + int i, j; /* Phrase and token index, respectively */ + int bMatch; /* True if all terms are at the same rowid */ + const int bDesc = pExpr->bDesc; + + /* Check that this node should not be FTS5_TERM */ + assert( pNear->nPhrase>1 + || pNear->apPhrase[0]->nTerm>1 + || pNear->apPhrase[0]->aTerm[0].pSynonym + || pNear->apPhrase[0]->aTerm[0].bFirst + ); + + /* Initialize iLast, the "lastest" rowid any iterator points to. If the + ** iterator skips through rowids in the default ascending order, this means + ** the maximum rowid. Or, if the iterator is "ORDER BY rowid DESC", then it + ** means the minimum rowid. */ + if( pLeft->aTerm[0].pSynonym ){ + iLast = fts5ExprSynonymRowid(&pLeft->aTerm[0], bDesc, 0); + }else{ + iLast = pLeft->aTerm[0].pIter->iRowid; + } + + do { + bMatch = 1; + for(i=0; i<pNear->nPhrase; i++){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; + for(j=0; j<pPhrase->nTerm; j++){ + Fts5ExprTerm *pTerm = &pPhrase->aTerm[j]; + if( pTerm->pSynonym ){ + i64 iRowid = fts5ExprSynonymRowid(pTerm, bDesc, 0); + if( iRowid==iLast ) continue; + bMatch = 0; + if( fts5ExprSynonymAdvanceto(pTerm, bDesc, &iLast, &rc) ){ + pNode->bNomatch = 0; + pNode->bEof = 1; + return rc; + } + }else{ + Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter; + if( pIter->iRowid==iLast || pIter->bEof ) continue; + bMatch = 0; + if( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){ + return rc; + } + } + } + } + }while( bMatch==0 ); + + pNode->iRowid = iLast; + pNode->bNomatch = ((0==fts5ExprNearTest(&rc, pExpr, pNode)) && rc==SQLITE_OK); + assert( pNode->bEof==0 || pNode->bNomatch==0 ); + + return rc; +} + +/* +** Advance the first term iterator in the first phrase of pNear. Set output +** variable *pbEof to true if it reaches EOF or if an error occurs. +** +** Return SQLITE_OK if successful, or an SQLite error code if an error +** occurs. +*/ +static int fts5ExprNodeNext_STRING( + Fts5Expr *pExpr, /* Expression pPhrase belongs to */ + Fts5ExprNode *pNode, /* FTS5_STRING or FTS5_TERM node */ + int bFromValid, + i64 iFrom +){ + Fts5ExprTerm *pTerm = &pNode->pNear->apPhrase[0]->aTerm[0]; + int rc = SQLITE_OK; + + pNode->bNomatch = 0; + if( pTerm->pSynonym ){ + int bEof = 1; + Fts5ExprTerm *p; + + /* Find the firstest rowid any synonym points to. */ + i64 iRowid = fts5ExprSynonymRowid(pTerm, pExpr->bDesc, 0); + + /* Advance each iterator that currently points to iRowid. Or, if iFrom + ** is valid - each iterator that points to a rowid before iFrom. */ + for(p=pTerm; p; p=p->pSynonym){ + if( sqlite3Fts5IterEof(p->pIter)==0 ){ + i64 ii = p->pIter->iRowid; + if( ii==iRowid + || (bFromValid && ii!=iFrom && (ii>iFrom)==pExpr->bDesc) + ){ + if( bFromValid ){ + rc = sqlite3Fts5IterNextFrom(p->pIter, iFrom); + }else{ + rc = sqlite3Fts5IterNext(p->pIter); + } + if( rc!=SQLITE_OK ) break; + if( sqlite3Fts5IterEof(p->pIter)==0 ){ + bEof = 0; + } + }else{ + bEof = 0; + } + } + } + + /* Set the EOF flag if either all synonym iterators are at EOF or an + ** error has occurred. */ + pNode->bEof = (rc || bEof); + }else{ + Fts5IndexIter *pIter = pTerm->pIter; + + assert( Fts5NodeIsString(pNode) ); + if( bFromValid ){ + rc = sqlite3Fts5IterNextFrom(pIter, iFrom); + }else{ + rc = sqlite3Fts5IterNext(pIter); + } + + pNode->bEof = (rc || sqlite3Fts5IterEof(pIter)); + } + + if( pNode->bEof==0 ){ + assert( rc==SQLITE_OK ); + rc = fts5ExprNodeTest_STRING(pExpr, pNode); + } + + return rc; +} + + +static int fts5ExprNodeTest_TERM( + Fts5Expr *pExpr, /* Expression that pNear is a part of */ + Fts5ExprNode *pNode /* The "NEAR" node (FTS5_TERM) */ +){ + /* As this "NEAR" object is actually a single phrase that consists + ** of a single term only, grab pointers into the poslist managed by the + ** fts5_index.c iterator object. This is much faster than synthesizing + ** a new poslist the way we have to for more complicated phrase or NEAR + ** expressions. */ + Fts5ExprPhrase *pPhrase = pNode->pNear->apPhrase[0]; + Fts5IndexIter *pIter = pPhrase->aTerm[0].pIter; + + assert( pNode->eType==FTS5_TERM ); + assert( pNode->pNear->nPhrase==1 && pPhrase->nTerm==1 ); + assert( pPhrase->aTerm[0].pSynonym==0 ); + + pPhrase->poslist.n = pIter->nData; + if( pExpr->pConfig->eDetail==FTS5_DETAIL_FULL ){ + pPhrase->poslist.p = (u8*)pIter->pData; + } + pNode->iRowid = pIter->iRowid; + pNode->bNomatch = (pPhrase->poslist.n==0); + return SQLITE_OK; +} + +/* +** xNext() method for a node of type FTS5_TERM. +*/ +static int fts5ExprNodeNext_TERM( + Fts5Expr *pExpr, + Fts5ExprNode *pNode, + int bFromValid, + i64 iFrom +){ + int rc; + Fts5IndexIter *pIter = pNode->pNear->apPhrase[0]->aTerm[0].pIter; + + assert( pNode->bEof==0 ); + if( bFromValid ){ + rc = sqlite3Fts5IterNextFrom(pIter, iFrom); + }else{ + rc = sqlite3Fts5IterNext(pIter); + } + if( rc==SQLITE_OK && sqlite3Fts5IterEof(pIter)==0 ){ + rc = fts5ExprNodeTest_TERM(pExpr, pNode); + }else{ + pNode->bEof = 1; + pNode->bNomatch = 0; + } + return rc; +} + +static void fts5ExprNodeTest_OR( + Fts5Expr *pExpr, /* Expression of which pNode is a part */ + Fts5ExprNode *pNode /* Expression node to test */ +){ + Fts5ExprNode *pNext = pNode->apChild[0]; + int i; + + for(i=1; i<pNode->nChild; i++){ + Fts5ExprNode *pChild = pNode->apChild[i]; + int cmp = fts5NodeCompare(pExpr, pNext, pChild); + if( cmp>0 || (cmp==0 && pChild->bNomatch==0) ){ + pNext = pChild; + } + } + pNode->iRowid = pNext->iRowid; + pNode->bEof = pNext->bEof; + pNode->bNomatch = pNext->bNomatch; +} + +static int fts5ExprNodeNext_OR( + Fts5Expr *pExpr, + Fts5ExprNode *pNode, + int bFromValid, + i64 iFrom +){ + int i; + i64 iLast = pNode->iRowid; + + for(i=0; i<pNode->nChild; i++){ + Fts5ExprNode *p1 = pNode->apChild[i]; + assert( p1->bEof || fts5RowidCmp(pExpr, p1->iRowid, iLast)>=0 ); + if( p1->bEof==0 ){ + if( (p1->iRowid==iLast) + || (bFromValid && fts5RowidCmp(pExpr, p1->iRowid, iFrom)<0) + ){ + int rc = fts5ExprNodeNext(pExpr, p1, bFromValid, iFrom); + if( rc!=SQLITE_OK ){ + pNode->bNomatch = 0; + return rc; + } + } + } + } + + fts5ExprNodeTest_OR(pExpr, pNode); + return SQLITE_OK; +} + +/* +** Argument pNode is an FTS5_AND node. +*/ +static int fts5ExprNodeTest_AND( + Fts5Expr *pExpr, /* Expression pPhrase belongs to */ + Fts5ExprNode *pAnd /* FTS5_AND node to advance */ +){ + int iChild; + i64 iLast = pAnd->iRowid; + int rc = SQLITE_OK; + int bMatch; + + assert( pAnd->bEof==0 ); + do { + pAnd->bNomatch = 0; + bMatch = 1; + for(iChild=0; iChild<pAnd->nChild; iChild++){ + Fts5ExprNode *pChild = pAnd->apChild[iChild]; + int cmp = fts5RowidCmp(pExpr, iLast, pChild->iRowid); + if( cmp>0 ){ + /* Advance pChild until it points to iLast or laster */ + rc = fts5ExprNodeNext(pExpr, pChild, 1, iLast); + if( rc!=SQLITE_OK ){ + pAnd->bNomatch = 0; + return rc; + } + } + + /* If the child node is now at EOF, so is the parent AND node. Otherwise, + ** the child node is guaranteed to have advanced at least as far as + ** rowid iLast. So if it is not at exactly iLast, pChild->iRowid is the + ** new lastest rowid seen so far. */ + assert( pChild->bEof || fts5RowidCmp(pExpr, iLast, pChild->iRowid)<=0 ); + if( pChild->bEof ){ + fts5ExprSetEof(pAnd); + bMatch = 1; + break; + }else if( iLast!=pChild->iRowid ){ + bMatch = 0; + iLast = pChild->iRowid; + } + + if( pChild->bNomatch ){ + pAnd->bNomatch = 1; + } + } + }while( bMatch==0 ); + + if( pAnd->bNomatch && pAnd!=pExpr->pRoot ){ + fts5ExprNodeZeroPoslist(pAnd); + } + pAnd->iRowid = iLast; + return SQLITE_OK; +} + +static int fts5ExprNodeNext_AND( + Fts5Expr *pExpr, + Fts5ExprNode *pNode, + int bFromValid, + i64 iFrom +){ + int rc = fts5ExprNodeNext(pExpr, pNode->apChild[0], bFromValid, iFrom); + if( rc==SQLITE_OK ){ + rc = fts5ExprNodeTest_AND(pExpr, pNode); + }else{ + pNode->bNomatch = 0; + } + return rc; +} + +static int fts5ExprNodeTest_NOT( + Fts5Expr *pExpr, /* Expression pPhrase belongs to */ + Fts5ExprNode *pNode /* FTS5_NOT node to advance */ +){ + int rc = SQLITE_OK; + Fts5ExprNode *p1 = pNode->apChild[0]; + Fts5ExprNode *p2 = pNode->apChild[1]; + assert( pNode->nChild==2 ); + + while( rc==SQLITE_OK && p1->bEof==0 ){ + int cmp = fts5NodeCompare(pExpr, p1, p2); + if( cmp>0 ){ + rc = fts5ExprNodeNext(pExpr, p2, 1, p1->iRowid); + cmp = fts5NodeCompare(pExpr, p1, p2); + } + assert( rc!=SQLITE_OK || cmp<=0 ); + if( cmp || p2->bNomatch ) break; + rc = fts5ExprNodeNext(pExpr, p1, 0, 0); + } + pNode->bEof = p1->bEof; + pNode->bNomatch = p1->bNomatch; + pNode->iRowid = p1->iRowid; + if( p1->bEof ){ + fts5ExprNodeZeroPoslist(p2); + } + return rc; +} + +static int fts5ExprNodeNext_NOT( + Fts5Expr *pExpr, + Fts5ExprNode *pNode, + int bFromValid, + i64 iFrom +){ + int rc = fts5ExprNodeNext(pExpr, pNode->apChild[0], bFromValid, iFrom); + if( rc==SQLITE_OK ){ + rc = fts5ExprNodeTest_NOT(pExpr, pNode); + } + if( rc!=SQLITE_OK ){ + pNode->bNomatch = 0; + } + return rc; +} + +/* +** If pNode currently points to a match, this function returns SQLITE_OK +** without modifying it. Otherwise, pNode is advanced until it does point +** to a match or EOF is reached. +*/ +static int fts5ExprNodeTest( + Fts5Expr *pExpr, /* Expression of which pNode is a part */ + Fts5ExprNode *pNode /* Expression node to test */ +){ + int rc = SQLITE_OK; + if( pNode->bEof==0 ){ + switch( pNode->eType ){ + + case FTS5_STRING: { + rc = fts5ExprNodeTest_STRING(pExpr, pNode); + break; + } + + case FTS5_TERM: { + rc = fts5ExprNodeTest_TERM(pExpr, pNode); + break; + } + + case FTS5_AND: { + rc = fts5ExprNodeTest_AND(pExpr, pNode); + break; + } + + case FTS5_OR: { + fts5ExprNodeTest_OR(pExpr, pNode); + break; + } + + default: assert( pNode->eType==FTS5_NOT ); { + rc = fts5ExprNodeTest_NOT(pExpr, pNode); + break; + } + } + } + return rc; +} + + +/* +** Set node pNode, which is part of expression pExpr, to point to the first +** match. If there are no matches, set the Node.bEof flag to indicate EOF. +** +** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise. +** It is not an error if there are no matches. +*/ +static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){ + int rc = SQLITE_OK; + pNode->bEof = 0; + pNode->bNomatch = 0; + + if( Fts5NodeIsString(pNode) ){ + /* Initialize all term iterators in the NEAR object. */ + rc = fts5ExprNearInitAll(pExpr, pNode); + }else if( pNode->xNext==0 ){ + pNode->bEof = 1; + }else{ + int i; + int nEof = 0; + for(i=0; i<pNode->nChild && rc==SQLITE_OK; i++){ + Fts5ExprNode *pChild = pNode->apChild[i]; + rc = fts5ExprNodeFirst(pExpr, pNode->apChild[i]); + assert( pChild->bEof==0 || pChild->bEof==1 ); + nEof += pChild->bEof; + } + pNode->iRowid = pNode->apChild[0]->iRowid; + + switch( pNode->eType ){ + case FTS5_AND: + if( nEof>0 ) fts5ExprSetEof(pNode); + break; + + case FTS5_OR: + if( pNode->nChild==nEof ) fts5ExprSetEof(pNode); + break; + + default: + assert( pNode->eType==FTS5_NOT ); + pNode->bEof = pNode->apChild[0]->bEof; + break; + } + } + + if( rc==SQLITE_OK ){ + rc = fts5ExprNodeTest(pExpr, pNode); + } + return rc; +} + + +/* +** Begin iterating through the set of documents in index pIdx matched by +** the MATCH expression passed as the first argument. If the "bDesc" +** parameter is passed a non-zero value, iteration is in descending rowid +** order. Or, if it is zero, in ascending order. +** +** If iterating in ascending rowid order (bDesc==0), the first document +** visited is that with the smallest rowid that is larger than or equal +** to parameter iFirst. Or, if iterating in ascending order (bDesc==1), +** then the first document visited must have a rowid smaller than or +** equal to iFirst. +** +** Return SQLITE_OK if successful, or an SQLite error code otherwise. It +** is not considered an error if the query does not match any documents. +*/ +static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){ + Fts5ExprNode *pRoot = p->pRoot; + int rc; /* Return code */ + + p->pIndex = pIdx; + p->bDesc = bDesc; + rc = fts5ExprNodeFirst(p, pRoot); + + /* If not at EOF but the current rowid occurs earlier than iFirst in + ** the iteration order, move to document iFirst or later. */ + if( rc==SQLITE_OK + && 0==pRoot->bEof + && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 + ){ + rc = fts5ExprNodeNext(p, pRoot, 1, iFirst); + } + + /* If the iterator is not at a real match, skip forward until it is. */ + while( pRoot->bNomatch && rc==SQLITE_OK ){ + assert( pRoot->bEof==0 ); + rc = fts5ExprNodeNext(p, pRoot, 0, 0); + } + return rc; +} + +/* +** Move to the next document +** +** Return SQLITE_OK if successful, or an SQLite error code otherwise. It +** is not considered an error if the query does not match any documents. +*/ +static int sqlite3Fts5ExprNext(Fts5Expr *p, i64 iLast){ + int rc; + Fts5ExprNode *pRoot = p->pRoot; + assert( pRoot->bEof==0 && pRoot->bNomatch==0 ); + do { + rc = fts5ExprNodeNext(p, pRoot, 0, 0); + assert( pRoot->bNomatch==0 || (rc==SQLITE_OK && pRoot->bEof==0) ); + }while( pRoot->bNomatch ); + if( fts5RowidCmp(p, pRoot->iRowid, iLast)>0 ){ + pRoot->bEof = 1; + } + return rc; +} + +static int sqlite3Fts5ExprEof(Fts5Expr *p){ + return p->pRoot->bEof; +} + +static i64 sqlite3Fts5ExprRowid(Fts5Expr *p){ + return p->pRoot->iRowid; +} + +static int fts5ParseStringFromToken(Fts5Token *pToken, char **pz){ + int rc = SQLITE_OK; + *pz = sqlite3Fts5Strndup(&rc, pToken->p, pToken->n); + return rc; +} + +/* +** Free the phrase object passed as the only argument. +*/ +static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){ + if( pPhrase ){ + int i; + for(i=0; i<pPhrase->nTerm; i++){ + Fts5ExprTerm *pSyn; + Fts5ExprTerm *pNext; + Fts5ExprTerm *pTerm = &pPhrase->aTerm[i]; + sqlite3_free(pTerm->zTerm); + sqlite3Fts5IterClose(pTerm->pIter); + for(pSyn=pTerm->pSynonym; pSyn; pSyn=pNext){ + pNext = pSyn->pSynonym; + sqlite3Fts5IterClose(pSyn->pIter); + fts5BufferFree((Fts5Buffer*)&pSyn[1]); + sqlite3_free(pSyn); + } + } + if( pPhrase->poslist.nSpace>0 ) fts5BufferFree(&pPhrase->poslist); + sqlite3_free(pPhrase); + } +} + +/* +** Set the "bFirst" flag on the first token of the phrase passed as the +** only argument. +*/ +static void sqlite3Fts5ParseSetCaret(Fts5ExprPhrase *pPhrase){ + if( pPhrase && pPhrase->nTerm ){ + pPhrase->aTerm[0].bFirst = 1; + } +} + +/* +** If argument pNear is NULL, then a new Fts5ExprNearset object is allocated +** and populated with pPhrase. Or, if pNear is not NULL, phrase pPhrase is +** appended to it and the results returned. +** +** If an OOM error occurs, both the pNear and pPhrase objects are freed and +** NULL returned. +*/ +static Fts5ExprNearset *sqlite3Fts5ParseNearset( + Fts5Parse *pParse, /* Parse context */ + Fts5ExprNearset *pNear, /* Existing nearset, or NULL */ + Fts5ExprPhrase *pPhrase /* Recently parsed phrase */ +){ + const int SZALLOC = 8; + Fts5ExprNearset *pRet = 0; + + if( pParse->rc==SQLITE_OK ){ + if( pPhrase==0 ){ + return pNear; + } + if( pNear==0 ){ + sqlite3_int64 nByte; + nByte = sizeof(Fts5ExprNearset) + SZALLOC * sizeof(Fts5ExprPhrase*); + pRet = sqlite3_malloc64(nByte); + if( pRet==0 ){ + pParse->rc = SQLITE_NOMEM; + }else{ + memset(pRet, 0, (size_t)nByte); + } + }else if( (pNear->nPhrase % SZALLOC)==0 ){ + int nNew = pNear->nPhrase + SZALLOC; + sqlite3_int64 nByte; + + nByte = sizeof(Fts5ExprNearset) + nNew * sizeof(Fts5ExprPhrase*); + pRet = (Fts5ExprNearset*)sqlite3_realloc64(pNear, nByte); + if( pRet==0 ){ + pParse->rc = SQLITE_NOMEM; + } + }else{ + pRet = pNear; + } + } + + if( pRet==0 ){ + assert( pParse->rc!=SQLITE_OK ); + sqlite3Fts5ParseNearsetFree(pNear); + sqlite3Fts5ParsePhraseFree(pPhrase); + }else{ + if( pRet->nPhrase>0 ){ + Fts5ExprPhrase *pLast = pRet->apPhrase[pRet->nPhrase-1]; + assert( pParse!=0 ); + assert( pParse->apPhrase!=0 ); + assert( pParse->nPhrase>=2 ); + assert( pLast==pParse->apPhrase[pParse->nPhrase-2] ); + if( pPhrase->nTerm==0 ){ + fts5ExprPhraseFree(pPhrase); + pRet->nPhrase--; + pParse->nPhrase--; + pPhrase = pLast; + }else if( pLast->nTerm==0 ){ + fts5ExprPhraseFree(pLast); + pParse->apPhrase[pParse->nPhrase-2] = pPhrase; + pParse->nPhrase--; + pRet->nPhrase--; + } + } + pRet->apPhrase[pRet->nPhrase++] = pPhrase; + } + return pRet; +} + +typedef struct TokenCtx TokenCtx; +struct TokenCtx { + Fts5ExprPhrase *pPhrase; + int rc; +}; + +/* +** Callback for tokenizing terms used by ParseTerm(). +*/ +static int fts5ParseTokenize( + void *pContext, /* Pointer to Fts5InsertCtx object */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Buffer containing token */ + int nToken, /* Size of token in bytes */ + int iUnused1, /* Start offset of token */ + int iUnused2 /* End offset of token */ +){ + int rc = SQLITE_OK; + const int SZALLOC = 8; + TokenCtx *pCtx = (TokenCtx*)pContext; + Fts5ExprPhrase *pPhrase = pCtx->pPhrase; + + UNUSED_PARAM2(iUnused1, iUnused2); + + /* If an error has already occurred, this is a no-op */ + if( pCtx->rc!=SQLITE_OK ) return pCtx->rc; + if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; + + if( pPhrase && pPhrase->nTerm>0 && (tflags & FTS5_TOKEN_COLOCATED) ){ + Fts5ExprTerm *pSyn; + sqlite3_int64 nByte = sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer) + nToken+1; + pSyn = (Fts5ExprTerm*)sqlite3_malloc64(nByte); + if( pSyn==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pSyn, 0, (size_t)nByte); + pSyn->zTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer); + memcpy(pSyn->zTerm, pToken, nToken); + pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym; + pPhrase->aTerm[pPhrase->nTerm-1].pSynonym = pSyn; + } + }else{ + Fts5ExprTerm *pTerm; + if( pPhrase==0 || (pPhrase->nTerm % SZALLOC)==0 ){ + Fts5ExprPhrase *pNew; + int nNew = SZALLOC + (pPhrase ? pPhrase->nTerm : 0); + + pNew = (Fts5ExprPhrase*)sqlite3_realloc64(pPhrase, + sizeof(Fts5ExprPhrase) + sizeof(Fts5ExprTerm) * nNew + ); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + if( pPhrase==0 ) memset(pNew, 0, sizeof(Fts5ExprPhrase)); + pCtx->pPhrase = pPhrase = pNew; + pNew->nTerm = nNew - SZALLOC; + } + } + + if( rc==SQLITE_OK ){ + pTerm = &pPhrase->aTerm[pPhrase->nTerm++]; + memset(pTerm, 0, sizeof(Fts5ExprTerm)); + pTerm->zTerm = sqlite3Fts5Strndup(&rc, pToken, nToken); + } + } + + pCtx->rc = rc; + return rc; +} + + +/* +** Free the phrase object passed as the only argument. +*/ +static void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase *pPhrase){ + fts5ExprPhraseFree(pPhrase); +} + +/* +** Free the phrase object passed as the second argument. +*/ +static void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset *pNear){ + if( pNear ){ + int i; + for(i=0; i<pNear->nPhrase; i++){ + fts5ExprPhraseFree(pNear->apPhrase[i]); + } + sqlite3_free(pNear->pColset); + sqlite3_free(pNear); + } +} + +static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p){ + assert( pParse->pExpr==0 ); + pParse->pExpr = p; +} + +static int parseGrowPhraseArray(Fts5Parse *pParse){ + if( (pParse->nPhrase % 8)==0 ){ + sqlite3_int64 nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8); + Fts5ExprPhrase **apNew; + apNew = (Fts5ExprPhrase**)sqlite3_realloc64(pParse->apPhrase, nByte); + if( apNew==0 ){ + pParse->rc = SQLITE_NOMEM; + return SQLITE_NOMEM; + } + pParse->apPhrase = apNew; + } + return SQLITE_OK; +} + +/* +** This function is called by the parser to process a string token. The +** string may or may not be quoted. In any case it is tokenized and a +** phrase object consisting of all tokens returned. +*/ +static Fts5ExprPhrase *sqlite3Fts5ParseTerm( + Fts5Parse *pParse, /* Parse context */ + Fts5ExprPhrase *pAppend, /* Phrase to append to */ + Fts5Token *pToken, /* String to tokenize */ + int bPrefix /* True if there is a trailing "*" */ +){ + Fts5Config *pConfig = pParse->pConfig; + TokenCtx sCtx; /* Context object passed to callback */ + int rc; /* Tokenize return code */ + char *z = 0; + + memset(&sCtx, 0, sizeof(TokenCtx)); + sCtx.pPhrase = pAppend; + + rc = fts5ParseStringFromToken(pToken, &z); + if( rc==SQLITE_OK ){ + int flags = FTS5_TOKENIZE_QUERY | (bPrefix ? FTS5_TOKENIZE_PREFIX : 0); + int n; + sqlite3Fts5Dequote(z); + n = (int)strlen(z); + rc = sqlite3Fts5Tokenize(pConfig, flags, z, n, &sCtx, fts5ParseTokenize); + } + sqlite3_free(z); + if( rc || (rc = sCtx.rc) ){ + pParse->rc = rc; + fts5ExprPhraseFree(sCtx.pPhrase); + sCtx.pPhrase = 0; + }else{ + + if( pAppend==0 ){ + if( parseGrowPhraseArray(pParse) ){ + fts5ExprPhraseFree(sCtx.pPhrase); + return 0; + } + pParse->nPhrase++; + } + + if( sCtx.pPhrase==0 ){ + /* This happens when parsing a token or quoted phrase that contains + ** no token characters at all. (e.g ... MATCH '""'). */ + sCtx.pPhrase = sqlite3Fts5MallocZero(&pParse->rc, sizeof(Fts5ExprPhrase)); + }else if( sCtx.pPhrase->nTerm ){ + sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = (u8)bPrefix; + } + pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase; + } + + return sCtx.pPhrase; +} + +/* +** Create a new FTS5 expression by cloning phrase iPhrase of the +** expression passed as the second argument. +*/ +static int sqlite3Fts5ExprClonePhrase( + Fts5Expr *pExpr, + int iPhrase, + Fts5Expr **ppNew +){ + int rc = SQLITE_OK; /* Return code */ + Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */ + Fts5Expr *pNew = 0; /* Expression to return via *ppNew */ + TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */ + + pOrig = pExpr->apExprPhrase[iPhrase]; + pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr)); + if( rc==SQLITE_OK ){ + pNew->apExprPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc, + sizeof(Fts5ExprPhrase*)); + } + if( rc==SQLITE_OK ){ + pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&rc, + sizeof(Fts5ExprNode)); + } + if( rc==SQLITE_OK ){ + pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc, + sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*)); + } + if( rc==SQLITE_OK ){ + Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset; + if( pColsetOrig ){ + sqlite3_int64 nByte; + Fts5Colset *pColset; + nByte = sizeof(Fts5Colset) + (pColsetOrig->nCol-1) * sizeof(int); + pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&rc, nByte); + if( pColset ){ + memcpy(pColset, pColsetOrig, (size_t)nByte); + } + pNew->pRoot->pNear->pColset = pColset; + } + } + + if( pOrig->nTerm ){ + int i; /* Used to iterate through phrase terms */ + for(i=0; rc==SQLITE_OK && i<pOrig->nTerm; i++){ + int tflags = 0; + Fts5ExprTerm *p; + for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ + const char *zTerm = p->zTerm; + rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm), + 0, 0); + tflags = FTS5_TOKEN_COLOCATED; + } + if( rc==SQLITE_OK ){ + sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; + sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst; + } + } + }else{ + /* This happens when parsing a token or quoted phrase that contains + ** no token characters at all. (e.g ... MATCH '""'). */ + sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase)); + } + + if( rc==SQLITE_OK && ALWAYS(sCtx.pPhrase) ){ + /* All the allocations succeeded. Put the expression object together. */ + pNew->pIndex = pExpr->pIndex; + pNew->pConfig = pExpr->pConfig; + pNew->nPhrase = 1; + pNew->apExprPhrase[0] = sCtx.pPhrase; + pNew->pRoot->pNear->apPhrase[0] = sCtx.pPhrase; + pNew->pRoot->pNear->nPhrase = 1; + sCtx.pPhrase->pNode = pNew->pRoot; + + if( pOrig->nTerm==1 + && pOrig->aTerm[0].pSynonym==0 + && pOrig->aTerm[0].bFirst==0 + ){ + pNew->pRoot->eType = FTS5_TERM; + pNew->pRoot->xNext = fts5ExprNodeNext_TERM; + }else{ + pNew->pRoot->eType = FTS5_STRING; + pNew->pRoot->xNext = fts5ExprNodeNext_STRING; + } + }else{ + sqlite3Fts5ExprFree(pNew); + fts5ExprPhraseFree(sCtx.pPhrase); + pNew = 0; + } + + *ppNew = pNew; + return rc; +} + + +/* +** Token pTok has appeared in a MATCH expression where the NEAR operator +** is expected. If token pTok does not contain "NEAR", store an error +** in the pParse object. +*/ +static void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token *pTok){ + if( pTok->n!=4 || memcmp("NEAR", pTok->p, 4) ){ + sqlite3Fts5ParseError( + pParse, "fts5: syntax error near \"%.*s\"", pTok->n, pTok->p + ); + } +} + +static void sqlite3Fts5ParseSetDistance( + Fts5Parse *pParse, + Fts5ExprNearset *pNear, + Fts5Token *p +){ + if( pNear ){ + int nNear = 0; + int i; + if( p->n ){ + for(i=0; i<p->n; i++){ + char c = (char)p->p[i]; + if( c<'0' || c>'9' ){ + sqlite3Fts5ParseError( + pParse, "expected integer, got \"%.*s\"", p->n, p->p + ); + return; + } + nNear = nNear * 10 + (p->p[i] - '0'); + } + }else{ + nNear = FTS5_DEFAULT_NEARDIST; + } + pNear->nNear = nNear; + } +} + +/* +** The second argument passed to this function may be NULL, or it may be +** an existing Fts5Colset object. This function returns a pointer to +** a new colset object containing the contents of (p) with new value column +** number iCol appended. +** +** If an OOM error occurs, store an error code in pParse and return NULL. +** The old colset object (if any) is not freed in this case. +*/ +static Fts5Colset *fts5ParseColset( + Fts5Parse *pParse, /* Store SQLITE_NOMEM here if required */ + Fts5Colset *p, /* Existing colset object */ + int iCol /* New column to add to colset object */ +){ + int nCol = p ? p->nCol : 0; /* Num. columns already in colset object */ + Fts5Colset *pNew; /* New colset object to return */ + + assert( pParse->rc==SQLITE_OK ); + assert( iCol>=0 && iCol<pParse->pConfig->nCol ); + + pNew = sqlite3_realloc64(p, sizeof(Fts5Colset) + sizeof(int)*nCol); + if( pNew==0 ){ + pParse->rc = SQLITE_NOMEM; + }else{ + int *aiCol = pNew->aiCol; + int i, j; + for(i=0; i<nCol; i++){ + if( aiCol[i]==iCol ) return pNew; + if( aiCol[i]>iCol ) break; + } + for(j=nCol; j>i; j--){ + aiCol[j] = aiCol[j-1]; + } + aiCol[i] = iCol; + pNew->nCol = nCol+1; + +#ifndef NDEBUG + /* Check that the array is in order and contains no duplicate entries. */ + for(i=1; i<pNew->nCol; i++) assert( pNew->aiCol[i]>pNew->aiCol[i-1] ); +#endif + } + + return pNew; +} + +/* +** Allocate and return an Fts5Colset object specifying the inverse of +** the colset passed as the second argument. Free the colset passed +** as the second argument before returning. +*/ +static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse *pParse, Fts5Colset *p){ + Fts5Colset *pRet; + int nCol = pParse->pConfig->nCol; + + pRet = (Fts5Colset*)sqlite3Fts5MallocZero(&pParse->rc, + sizeof(Fts5Colset) + sizeof(int)*nCol + ); + if( pRet ){ + int i; + int iOld = 0; + for(i=0; i<nCol; i++){ + if( iOld>=p->nCol || p->aiCol[iOld]!=i ){ + pRet->aiCol[pRet->nCol++] = i; + }else{ + iOld++; + } + } + } + + sqlite3_free(p); + return pRet; +} + +static Fts5Colset *sqlite3Fts5ParseColset( + Fts5Parse *pParse, /* Store SQLITE_NOMEM here if required */ + Fts5Colset *pColset, /* Existing colset object */ + Fts5Token *p +){ + Fts5Colset *pRet = 0; + int iCol; + char *z; /* Dequoted copy of token p */ + + z = sqlite3Fts5Strndup(&pParse->rc, p->p, p->n); + if( pParse->rc==SQLITE_OK ){ + Fts5Config *pConfig = pParse->pConfig; + sqlite3Fts5Dequote(z); + for(iCol=0; iCol<pConfig->nCol; iCol++){ + if( 0==sqlite3_stricmp(pConfig->azCol[iCol], z) ) break; + } + if( iCol==pConfig->nCol ){ + sqlite3Fts5ParseError(pParse, "no such column: %s", z); + }else{ + pRet = fts5ParseColset(pParse, pColset, iCol); + } + sqlite3_free(z); + } + + if( pRet==0 ){ + assert( pParse->rc!=SQLITE_OK ); + sqlite3_free(pColset); + } + + return pRet; +} + +/* +** If argument pOrig is NULL, or if (*pRc) is set to anything other than +** SQLITE_OK when this function is called, NULL is returned. +** +** Otherwise, a copy of (*pOrig) is made into memory obtained from +** sqlite3Fts5MallocZero() and a pointer to it returned. If the allocation +** fails, (*pRc) is set to SQLITE_NOMEM and NULL is returned. +*/ +static Fts5Colset *fts5CloneColset(int *pRc, Fts5Colset *pOrig){ + Fts5Colset *pRet; + if( pOrig ){ + sqlite3_int64 nByte = sizeof(Fts5Colset) + (pOrig->nCol-1) * sizeof(int); + pRet = (Fts5Colset*)sqlite3Fts5MallocZero(pRc, nByte); + if( pRet ){ + memcpy(pRet, pOrig, (size_t)nByte); + } + }else{ + pRet = 0; + } + return pRet; +} + +/* +** Remove from colset pColset any columns that are not also in colset pMerge. +*/ +static void fts5MergeColset(Fts5Colset *pColset, Fts5Colset *pMerge){ + int iIn = 0; /* Next input in pColset */ + int iMerge = 0; /* Next input in pMerge */ + int iOut = 0; /* Next output slot in pColset */ + + while( iIn<pColset->nCol && iMerge<pMerge->nCol ){ + int iDiff = pColset->aiCol[iIn] - pMerge->aiCol[iMerge]; + if( iDiff==0 ){ + pColset->aiCol[iOut++] = pMerge->aiCol[iMerge]; + iMerge++; + iIn++; + }else if( iDiff>0 ){ + iMerge++; + }else{ + iIn++; + } + } + pColset->nCol = iOut; +} + +/* +** Recursively apply colset pColset to expression node pNode and all of +** its decendents. If (*ppFree) is not NULL, it contains a spare copy +** of pColset. This function may use the spare copy and set (*ppFree) to +** zero, or it may create copies of pColset using fts5CloneColset(). +*/ +static void fts5ParseSetColset( + Fts5Parse *pParse, + Fts5ExprNode *pNode, + Fts5Colset *pColset, + Fts5Colset **ppFree +){ + if( pParse->rc==SQLITE_OK ){ + assert( pNode->eType==FTS5_TERM || pNode->eType==FTS5_STRING + || pNode->eType==FTS5_AND || pNode->eType==FTS5_OR + || pNode->eType==FTS5_NOT || pNode->eType==FTS5_EOF + ); + if( pNode->eType==FTS5_STRING || pNode->eType==FTS5_TERM ){ + Fts5ExprNearset *pNear = pNode->pNear; + if( pNear->pColset ){ + fts5MergeColset(pNear->pColset, pColset); + if( pNear->pColset->nCol==0 ){ + pNode->eType = FTS5_EOF; + pNode->xNext = 0; + } + }else if( *ppFree ){ + pNear->pColset = pColset; + *ppFree = 0; + }else{ + pNear->pColset = fts5CloneColset(&pParse->rc, pColset); + } + }else{ + int i; + assert( pNode->eType!=FTS5_EOF || pNode->nChild==0 ); + for(i=0; i<pNode->nChild; i++){ + fts5ParseSetColset(pParse, pNode->apChild[i], pColset, ppFree); + } + } + } +} + +/* +** Apply colset pColset to expression node pExpr and all of its descendents. +*/ +static void sqlite3Fts5ParseSetColset( + Fts5Parse *pParse, + Fts5ExprNode *pExpr, + Fts5Colset *pColset +){ + Fts5Colset *pFree = pColset; + if( pParse->pConfig->eDetail==FTS5_DETAIL_NONE ){ + sqlite3Fts5ParseError(pParse, + "fts5: column queries are not supported (detail=none)" + ); + }else{ + fts5ParseSetColset(pParse, pExpr, pColset, &pFree); + } + sqlite3_free(pFree); +} + +static void fts5ExprAssignXNext(Fts5ExprNode *pNode){ + switch( pNode->eType ){ + case FTS5_STRING: { + Fts5ExprNearset *pNear = pNode->pNear; + if( pNear->nPhrase==1 && pNear->apPhrase[0]->nTerm==1 + && pNear->apPhrase[0]->aTerm[0].pSynonym==0 + && pNear->apPhrase[0]->aTerm[0].bFirst==0 + ){ + pNode->eType = FTS5_TERM; + pNode->xNext = fts5ExprNodeNext_TERM; + }else{ + pNode->xNext = fts5ExprNodeNext_STRING; + } + break; + }; + + case FTS5_OR: { + pNode->xNext = fts5ExprNodeNext_OR; + break; + }; + + case FTS5_AND: { + pNode->xNext = fts5ExprNodeNext_AND; + break; + }; + + default: assert( pNode->eType==FTS5_NOT ); { + pNode->xNext = fts5ExprNodeNext_NOT; + break; + }; + } +} + +static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){ + int ii = p->nChild; + if( p->eType!=FTS5_NOT && pSub->eType==p->eType ){ + int nByte = sizeof(Fts5ExprNode*) * pSub->nChild; + memcpy(&p->apChild[p->nChild], pSub->apChild, nByte); + p->nChild += pSub->nChild; + sqlite3_free(pSub); + }else{ + p->apChild[p->nChild++] = pSub; + } + for( ; ii<p->nChild; ii++){ + p->iHeight = MAX(p->iHeight, p->apChild[ii]->iHeight + 1); + } +} + +/* +** This function is used when parsing LIKE or GLOB patterns against +** trigram indexes that specify either detail=column or detail=none. +** It converts a phrase: +** +** abc + def + ghi +** +** into an AND tree: +** +** abc AND def AND ghi +*/ +static Fts5ExprNode *fts5ParsePhraseToAnd( + Fts5Parse *pParse, + Fts5ExprNearset *pNear +){ + int nTerm = pNear->apPhrase[0]->nTerm; + int ii; + int nByte; + Fts5ExprNode *pRet; + + assert( pNear->nPhrase==1 ); + assert( pParse->bPhraseToAnd ); + + nByte = sizeof(Fts5ExprNode) + nTerm*sizeof(Fts5ExprNode*); + pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte); + if( pRet ){ + pRet->eType = FTS5_AND; + pRet->nChild = nTerm; + pRet->iHeight = 1; + fts5ExprAssignXNext(pRet); + pParse->nPhrase--; + for(ii=0; ii<nTerm; ii++){ + Fts5ExprPhrase *pPhrase = (Fts5ExprPhrase*)sqlite3Fts5MallocZero( + &pParse->rc, sizeof(Fts5ExprPhrase) + ); + if( pPhrase ){ + if( parseGrowPhraseArray(pParse) ){ + fts5ExprPhraseFree(pPhrase); + }else{ + pParse->apPhrase[pParse->nPhrase++] = pPhrase; + pPhrase->nTerm = 1; + pPhrase->aTerm[0].zTerm = sqlite3Fts5Strndup( + &pParse->rc, pNear->apPhrase[0]->aTerm[ii].zTerm, -1 + ); + pRet->apChild[ii] = sqlite3Fts5ParseNode(pParse, FTS5_STRING, + 0, 0, sqlite3Fts5ParseNearset(pParse, 0, pPhrase) + ); + } + } + } + + if( pParse->rc ){ + sqlite3Fts5ParseNodeFree(pRet); + pRet = 0; + }else{ + sqlite3Fts5ParseNearsetFree(pNear); + } + } + + return pRet; +} + +/* +** Allocate and return a new expression object. If anything goes wrong (i.e. +** OOM error), leave an error code in pParse and return NULL. +*/ +static Fts5ExprNode *sqlite3Fts5ParseNode( + Fts5Parse *pParse, /* Parse context */ + int eType, /* FTS5_STRING, AND, OR or NOT */ + Fts5ExprNode *pLeft, /* Left hand child expression */ + Fts5ExprNode *pRight, /* Right hand child expression */ + Fts5ExprNearset *pNear /* For STRING expressions, the near cluster */ +){ + Fts5ExprNode *pRet = 0; + + if( pParse->rc==SQLITE_OK ){ + int nChild = 0; /* Number of children of returned node */ + sqlite3_int64 nByte; /* Bytes of space to allocate for this node */ + + assert( (eType!=FTS5_STRING && !pNear) + || (eType==FTS5_STRING && !pLeft && !pRight) + ); + if( eType==FTS5_STRING && pNear==0 ) return 0; + if( eType!=FTS5_STRING && pLeft==0 ) return pRight; + if( eType!=FTS5_STRING && pRight==0 ) return pLeft; + + if( eType==FTS5_STRING + && pParse->bPhraseToAnd + && pNear->apPhrase[0]->nTerm>1 + ){ + pRet = fts5ParsePhraseToAnd(pParse, pNear); + }else{ + if( eType==FTS5_NOT ){ + nChild = 2; + }else if( eType==FTS5_AND || eType==FTS5_OR ){ + nChild = 2; + if( pLeft->eType==eType ) nChild += pLeft->nChild-1; + if( pRight->eType==eType ) nChild += pRight->nChild-1; + } + + nByte = sizeof(Fts5ExprNode) + sizeof(Fts5ExprNode*)*(nChild-1); + pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte); + + if( pRet ){ + pRet->eType = eType; + pRet->pNear = pNear; + fts5ExprAssignXNext(pRet); + if( eType==FTS5_STRING ){ + int iPhrase; + for(iPhrase=0; iPhrase<pNear->nPhrase; iPhrase++){ + pNear->apPhrase[iPhrase]->pNode = pRet; + if( pNear->apPhrase[iPhrase]->nTerm==0 ){ + pRet->xNext = 0; + pRet->eType = FTS5_EOF; + } + } + + if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL ){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[0]; + if( pNear->nPhrase!=1 + || pPhrase->nTerm>1 + || (pPhrase->nTerm>0 && pPhrase->aTerm[0].bFirst) + ){ + sqlite3Fts5ParseError(pParse, + "fts5: %s queries are not supported (detail!=full)", + pNear->nPhrase==1 ? "phrase": "NEAR" + ); + sqlite3_free(pRet); + pRet = 0; + } + } + }else{ + fts5ExprAddChildren(pRet, pLeft); + fts5ExprAddChildren(pRet, pRight); + if( pRet->iHeight>SQLITE_FTS5_MAX_EXPR_DEPTH ){ + sqlite3Fts5ParseError(pParse, + "fts5 expression tree is too large (maximum depth %d)", + SQLITE_FTS5_MAX_EXPR_DEPTH + ); + sqlite3_free(pRet); + pRet = 0; + } + } + } + } + } + + if( pRet==0 ){ + assert( pParse->rc!=SQLITE_OK ); + sqlite3Fts5ParseNodeFree(pLeft); + sqlite3Fts5ParseNodeFree(pRight); + sqlite3Fts5ParseNearsetFree(pNear); + } + return pRet; +} + +static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( + Fts5Parse *pParse, /* Parse context */ + Fts5ExprNode *pLeft, /* Left hand child expression */ + Fts5ExprNode *pRight /* Right hand child expression */ +){ + Fts5ExprNode *pRet = 0; + Fts5ExprNode *pPrev; + + if( pParse->rc ){ + sqlite3Fts5ParseNodeFree(pLeft); + sqlite3Fts5ParseNodeFree(pRight); + }else{ + + assert( pLeft->eType==FTS5_STRING + || pLeft->eType==FTS5_TERM + || pLeft->eType==FTS5_EOF + || pLeft->eType==FTS5_AND + ); + assert( pRight->eType==FTS5_STRING + || pRight->eType==FTS5_TERM + || pRight->eType==FTS5_EOF + ); + + if( pLeft->eType==FTS5_AND ){ + pPrev = pLeft->apChild[pLeft->nChild-1]; + }else{ + pPrev = pLeft; + } + assert( pPrev->eType==FTS5_STRING + || pPrev->eType==FTS5_TERM + || pPrev->eType==FTS5_EOF + ); + + if( pRight->eType==FTS5_EOF ){ + assert( pParse->apPhrase[pParse->nPhrase-1]==pRight->pNear->apPhrase[0] ); + sqlite3Fts5ParseNodeFree(pRight); + pRet = pLeft; + pParse->nPhrase--; + } + else if( pPrev->eType==FTS5_EOF ){ + Fts5ExprPhrase **ap; + + if( pPrev==pLeft ){ + pRet = pRight; + }else{ + pLeft->apChild[pLeft->nChild-1] = pRight; + pRet = pLeft; + } + + ap = &pParse->apPhrase[pParse->nPhrase-1-pRight->pNear->nPhrase]; + assert( ap[0]==pPrev->pNear->apPhrase[0] ); + memmove(ap, &ap[1], sizeof(Fts5ExprPhrase*)*pRight->pNear->nPhrase); + pParse->nPhrase--; + + sqlite3Fts5ParseNodeFree(pPrev); + } + else{ + pRet = sqlite3Fts5ParseNode(pParse, FTS5_AND, pLeft, pRight, 0); + } + } + + return pRet; +} + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ + sqlite3_int64 nByte = 0; + Fts5ExprTerm *p; + char *zQuoted; + + /* Determine the maximum amount of space required. */ + for(p=pTerm; p; p=p->pSynonym){ + nByte += (int)strlen(pTerm->zTerm) * 2 + 3 + 2; + } + zQuoted = sqlite3_malloc64(nByte); + + if( zQuoted ){ + int i = 0; + for(p=pTerm; p; p=p->pSynonym){ + char *zIn = p->zTerm; + zQuoted[i++] = '"'; + while( *zIn ){ + if( *zIn=='"' ) zQuoted[i++] = '"'; + zQuoted[i++] = *zIn++; + } + zQuoted[i++] = '"'; + if( p->pSynonym ) zQuoted[i++] = '|'; + } + if( pTerm->bPrefix ){ + zQuoted[i++] = ' '; + zQuoted[i++] = '*'; + } + zQuoted[i++] = '\0'; + } + return zQuoted; +} + +static char *fts5PrintfAppend(char *zApp, const char *zFmt, ...){ + char *zNew; + va_list ap; + va_start(ap, zFmt); + zNew = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( zApp && zNew ){ + char *zNew2 = sqlite3_mprintf("%s%s", zApp, zNew); + sqlite3_free(zNew); + zNew = zNew2; + } + sqlite3_free(zApp); + return zNew; +} + +/* +** Compose a tcl-readable representation of expression pExpr. Return a +** pointer to a buffer containing that representation. It is the +** responsibility of the caller to at some point free the buffer using +** sqlite3_free(). +*/ +static char *fts5ExprPrintTcl( + Fts5Config *pConfig, + const char *zNearsetCmd, + Fts5ExprNode *pExpr +){ + char *zRet = 0; + if( pExpr->eType==FTS5_STRING || pExpr->eType==FTS5_TERM ){ + Fts5ExprNearset *pNear = pExpr->pNear; + int i; + int iTerm; + + zRet = fts5PrintfAppend(zRet, "%s ", zNearsetCmd); + if( zRet==0 ) return 0; + if( pNear->pColset ){ + int *aiCol = pNear->pColset->aiCol; + int nCol = pNear->pColset->nCol; + if( nCol==1 ){ + zRet = fts5PrintfAppend(zRet, "-col %d ", aiCol[0]); + }else{ + zRet = fts5PrintfAppend(zRet, "-col {%d", aiCol[0]); + for(i=1; i<pNear->pColset->nCol; i++){ + zRet = fts5PrintfAppend(zRet, " %d", aiCol[i]); + } + zRet = fts5PrintfAppend(zRet, "} "); + } + if( zRet==0 ) return 0; + } + + if( pNear->nPhrase>1 ){ + zRet = fts5PrintfAppend(zRet, "-near %d ", pNear->nNear); + if( zRet==0 ) return 0; + } + + zRet = fts5PrintfAppend(zRet, "--"); + if( zRet==0 ) return 0; + + for(i=0; i<pNear->nPhrase; i++){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; + + zRet = fts5PrintfAppend(zRet, " {"); + for(iTerm=0; zRet && iTerm<pPhrase->nTerm; iTerm++){ + char *zTerm = pPhrase->aTerm[iTerm].zTerm; + zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" ", zTerm); + if( pPhrase->aTerm[iTerm].bPrefix ){ + zRet = fts5PrintfAppend(zRet, "*"); + } + } + + if( zRet ) zRet = fts5PrintfAppend(zRet, "}"); + if( zRet==0 ) return 0; + } + + }else if( pExpr->eType==0 ){ + zRet = sqlite3_mprintf("{}"); + }else{ + char const *zOp = 0; + int i; + switch( pExpr->eType ){ + case FTS5_AND: zOp = "AND"; break; + case FTS5_NOT: zOp = "NOT"; break; + default: + assert( pExpr->eType==FTS5_OR ); + zOp = "OR"; + break; + } + + zRet = sqlite3_mprintf("%s", zOp); + for(i=0; zRet && i<pExpr->nChild; i++){ + char *z = fts5ExprPrintTcl(pConfig, zNearsetCmd, pExpr->apChild[i]); + if( !z ){ + sqlite3_free(zRet); + zRet = 0; + }else{ + zRet = fts5PrintfAppend(zRet, " [%z]", z); + } + } + } + + return zRet; +} + +static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){ + char *zRet = 0; + if( pExpr->eType==0 ){ + return sqlite3_mprintf("\"\""); + }else + if( pExpr->eType==FTS5_STRING || pExpr->eType==FTS5_TERM ){ + Fts5ExprNearset *pNear = pExpr->pNear; + int i; + int iTerm; + + if( pNear->pColset ){ + int ii; + Fts5Colset *pColset = pNear->pColset; + if( pColset->nCol>1 ) zRet = fts5PrintfAppend(zRet, "{"); + for(ii=0; ii<pColset->nCol; ii++){ + zRet = fts5PrintfAppend(zRet, "%s%s", + pConfig->azCol[pColset->aiCol[ii]], ii==pColset->nCol-1 ? "" : " " + ); + } + if( zRet ){ + zRet = fts5PrintfAppend(zRet, "%s : ", pColset->nCol>1 ? "}" : ""); + } + if( zRet==0 ) return 0; + } + + if( pNear->nPhrase>1 ){ + zRet = fts5PrintfAppend(zRet, "NEAR("); + if( zRet==0 ) return 0; + } + + for(i=0; i<pNear->nPhrase; i++){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; + if( i!=0 ){ + zRet = fts5PrintfAppend(zRet, " "); + if( zRet==0 ) return 0; + } + for(iTerm=0; iTerm<pPhrase->nTerm; iTerm++){ + char *zTerm = fts5ExprTermPrint(&pPhrase->aTerm[iTerm]); + if( zTerm ){ + zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" + ", zTerm); + sqlite3_free(zTerm); + } + if( zTerm==0 || zRet==0 ){ + sqlite3_free(zRet); + return 0; + } + } + } + + if( pNear->nPhrase>1 ){ + zRet = fts5PrintfAppend(zRet, ", %d)", pNear->nNear); + if( zRet==0 ) return 0; + } + + }else{ + char const *zOp = 0; + int i; + + switch( pExpr->eType ){ + case FTS5_AND: zOp = " AND "; break; + case FTS5_NOT: zOp = " NOT "; break; + default: + assert( pExpr->eType==FTS5_OR ); + zOp = " OR "; + break; + } + + for(i=0; i<pExpr->nChild; i++){ + char *z = fts5ExprPrint(pConfig, pExpr->apChild[i]); + if( z==0 ){ + sqlite3_free(zRet); + zRet = 0; + }else{ + int e = pExpr->apChild[i]->eType; + int b = (e!=FTS5_STRING && e!=FTS5_TERM && e!=FTS5_EOF); + zRet = fts5PrintfAppend(zRet, "%s%s%z%s", + (i==0 ? "" : zOp), + (b?"(":""), z, (b?")":"") + ); + } + if( zRet==0 ) break; + } + } + + return zRet; +} + +/* +** The implementation of user-defined scalar functions fts5_expr() (bTcl==0) +** and fts5_expr_tcl() (bTcl!=0). +*/ +static void fts5ExprFunction( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args */ + sqlite3_value **apVal, /* Function arguments */ + int bTcl +){ + Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx); + sqlite3 *db = sqlite3_context_db_handle(pCtx); + const char *zExpr = 0; + char *zErr = 0; + Fts5Expr *pExpr = 0; + int rc; + int i; + + const char **azConfig; /* Array of arguments for Fts5Config */ + const char *zNearsetCmd = "nearset"; + int nConfig; /* Size of azConfig[] */ + Fts5Config *pConfig = 0; + int iArg = 1; + + if( nArg<1 ){ + zErr = sqlite3_mprintf("wrong number of arguments to function %s", + bTcl ? "fts5_expr_tcl" : "fts5_expr" + ); + sqlite3_result_error(pCtx, zErr, -1); + sqlite3_free(zErr); + return; + } + + if( bTcl && nArg>1 ){ + zNearsetCmd = (const char*)sqlite3_value_text(apVal[1]); + iArg = 2; + } + + nConfig = 3 + (nArg-iArg); + azConfig = (const char**)sqlite3_malloc64(sizeof(char*) * nConfig); + if( azConfig==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + azConfig[0] = 0; + azConfig[1] = "main"; + azConfig[2] = "tbl"; + for(i=3; iArg<nArg; iArg++){ + const char *z = (const char*)sqlite3_value_text(apVal[iArg]); + azConfig[i++] = (z ? z : ""); + } + + zExpr = (const char*)sqlite3_value_text(apVal[0]); + if( zExpr==0 ) zExpr = ""; + + rc = sqlite3Fts5ConfigParse(pGlobal, db, nConfig, azConfig, &pConfig, &zErr); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ExprNew(pConfig, 0, pConfig->nCol, zExpr, &pExpr, &zErr); + } + if( rc==SQLITE_OK ){ + char *zText; + if( pExpr->pRoot->xNext==0 ){ + zText = sqlite3_mprintf(""); + }else if( bTcl ){ + zText = fts5ExprPrintTcl(pConfig, zNearsetCmd, pExpr->pRoot); + }else{ + zText = fts5ExprPrint(pConfig, pExpr->pRoot); + } + if( zText==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_result_text(pCtx, zText, -1, SQLITE_TRANSIENT); + sqlite3_free(zText); + } + } + + if( rc!=SQLITE_OK ){ + if( zErr ){ + sqlite3_result_error(pCtx, zErr, -1); + sqlite3_free(zErr); + }else{ + sqlite3_result_error_code(pCtx, rc); + } + } + sqlite3_free((void *)azConfig); + sqlite3Fts5ConfigFree(pConfig); + sqlite3Fts5ExprFree(pExpr); +} + +static void fts5ExprFunctionHr( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args */ + sqlite3_value **apVal /* Function arguments */ +){ + fts5ExprFunction(pCtx, nArg, apVal, 0); +} +static void fts5ExprFunctionTcl( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args */ + sqlite3_value **apVal /* Function arguments */ +){ + fts5ExprFunction(pCtx, nArg, apVal, 1); +} + +/* +** The implementation of an SQLite user-defined-function that accepts a +** single integer as an argument. If the integer is an alpha-numeric +** unicode code point, 1 is returned. Otherwise 0. +*/ +static void fts5ExprIsAlnum( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args */ + sqlite3_value **apVal /* Function arguments */ +){ + int iCode; + u8 aArr[32]; + if( nArg!=1 ){ + sqlite3_result_error(pCtx, + "wrong number of arguments to function fts5_isalnum", -1 + ); + return; + } + memset(aArr, 0, sizeof(aArr)); + sqlite3Fts5UnicodeCatParse("L*", aArr); + sqlite3Fts5UnicodeCatParse("N*", aArr); + sqlite3Fts5UnicodeCatParse("Co", aArr); + iCode = sqlite3_value_int(apVal[0]); + sqlite3_result_int(pCtx, aArr[sqlite3Fts5UnicodeCategory((u32)iCode)]); +} + +static void fts5ExprFold( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args */ + sqlite3_value **apVal /* Function arguments */ +){ + if( nArg!=1 && nArg!=2 ){ + sqlite3_result_error(pCtx, + "wrong number of arguments to function fts5_fold", -1 + ); + }else{ + int iCode; + int bRemoveDiacritics = 0; + iCode = sqlite3_value_int(apVal[0]); + if( nArg==2 ) bRemoveDiacritics = sqlite3_value_int(apVal[1]); + sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics)); + } +} +#endif /* if SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +/* +** This is called during initialization to register the fts5_expr() scalar +** UDF with the SQLite handle passed as the only argument. +*/ +static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) + struct Fts5ExprFunc { + const char *z; + void (*x)(sqlite3_context*,int,sqlite3_value**); + } aFunc[] = { + { "fts5_expr", fts5ExprFunctionHr }, + { "fts5_expr_tcl", fts5ExprFunctionTcl }, + { "fts5_isalnum", fts5ExprIsAlnum }, + { "fts5_fold", fts5ExprFold }, + }; + int i; + int rc = SQLITE_OK; + void *pCtx = (void*)pGlobal; + + for(i=0; rc==SQLITE_OK && i<ArraySize(aFunc); i++){ + struct Fts5ExprFunc *p = &aFunc[i]; + rc = sqlite3_create_function(db, p->z, -1, SQLITE_UTF8, pCtx, p->x, 0, 0); + } +#else + int rc = SQLITE_OK; + UNUSED_PARAM2(pGlobal,db); +#endif + + /* Avoid warnings indicating that sqlite3Fts5ParserTrace() and + ** sqlite3Fts5ParserFallback() are unused */ +#ifndef NDEBUG + (void)sqlite3Fts5ParserTrace; +#endif + (void)sqlite3Fts5ParserFallback; + + return rc; +} + +/* +** Return the number of phrases in expression pExpr. +*/ +static int sqlite3Fts5ExprPhraseCount(Fts5Expr *pExpr){ + return (pExpr ? pExpr->nPhrase : 0); +} + +/* +** Return the number of terms in the iPhrase'th phrase in pExpr. +*/ +static int sqlite3Fts5ExprPhraseSize(Fts5Expr *pExpr, int iPhrase){ + if( iPhrase<0 || iPhrase>=pExpr->nPhrase ) return 0; + return pExpr->apExprPhrase[iPhrase]->nTerm; +} + +/* +** This function is used to access the current position list for phrase +** iPhrase. +*/ +static int sqlite3Fts5ExprPoslist(Fts5Expr *pExpr, int iPhrase, const u8 **pa){ + int nRet; + Fts5ExprPhrase *pPhrase = pExpr->apExprPhrase[iPhrase]; + Fts5ExprNode *pNode = pPhrase->pNode; + if( pNode->bEof==0 && pNode->iRowid==pExpr->pRoot->iRowid ){ + *pa = pPhrase->poslist.p; + nRet = pPhrase->poslist.n; + }else{ + *pa = 0; + nRet = 0; + } + return nRet; +} + +struct Fts5PoslistPopulator { + Fts5PoslistWriter writer; + int bOk; /* True if ok to populate */ + int bMiss; +}; + +/* +** Clear the position lists associated with all phrases in the expression +** passed as the first argument. Argument bLive is true if the expression +** might be pointing to a real entry, otherwise it has just been reset. +** +** At present this function is only used for detail=col and detail=none +** fts5 tables. This implies that all phrases must be at most 1 token +** in size, as phrase matches are not supported without detail=full. +*/ +static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int bLive){ + Fts5PoslistPopulator *pRet; + pRet = sqlite3_malloc64(sizeof(Fts5PoslistPopulator)*pExpr->nPhrase); + if( pRet ){ + int i; + memset(pRet, 0, sizeof(Fts5PoslistPopulator)*pExpr->nPhrase); + for(i=0; i<pExpr->nPhrase; i++){ + Fts5Buffer *pBuf = &pExpr->apExprPhrase[i]->poslist; + Fts5ExprNode *pNode = pExpr->apExprPhrase[i]->pNode; + assert( pExpr->apExprPhrase[i]->nTerm<=1 ); + if( bLive && + (pBuf->n==0 || pNode->iRowid!=pExpr->pRoot->iRowid || pNode->bEof) + ){ + pRet[i].bMiss = 1; + }else{ + pBuf->n = 0; + } + } + } + return pRet; +} + +struct Fts5ExprCtx { + Fts5Expr *pExpr; + Fts5PoslistPopulator *aPopulator; + i64 iOff; +}; +typedef struct Fts5ExprCtx Fts5ExprCtx; + +/* +** TODO: Make this more efficient! +*/ +static int fts5ExprColsetTest(Fts5Colset *pColset, int iCol){ + int i; + for(i=0; i<pColset->nCol; i++){ + if( pColset->aiCol[i]==iCol ) return 1; + } + return 0; +} + +static int fts5ExprPopulatePoslistsCb( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iUnused1, /* Byte offset of token within input text */ + int iUnused2 /* Byte offset of end of token within input text */ +){ + Fts5ExprCtx *p = (Fts5ExprCtx*)pCtx; + Fts5Expr *pExpr = p->pExpr; + int i; + + UNUSED_PARAM2(iUnused1, iUnused2); + + if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; + if( (tflags & FTS5_TOKEN_COLOCATED)==0 ) p->iOff++; + for(i=0; i<pExpr->nPhrase; i++){ + Fts5ExprTerm *pTerm; + if( p->aPopulator[i].bOk==0 ) continue; + for(pTerm=&pExpr->apExprPhrase[i]->aTerm[0]; pTerm; pTerm=pTerm->pSynonym){ + int nTerm = (int)strlen(pTerm->zTerm); + if( (nTerm==nToken || (nTerm<nToken && pTerm->bPrefix)) + && memcmp(pTerm->zTerm, pToken, nTerm)==0 + ){ + int rc = sqlite3Fts5PoslistWriterAppend( + &pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff + ); + if( rc ) return rc; + break; + } + } + } + return SQLITE_OK; +} + +static int sqlite3Fts5ExprPopulatePoslists( + Fts5Config *pConfig, + Fts5Expr *pExpr, + Fts5PoslistPopulator *aPopulator, + int iCol, + const char *z, int n +){ + int i; + Fts5ExprCtx sCtx; + sCtx.pExpr = pExpr; + sCtx.aPopulator = aPopulator; + sCtx.iOff = (((i64)iCol) << 32) - 1; + + for(i=0; i<pExpr->nPhrase; i++){ + Fts5ExprNode *pNode = pExpr->apExprPhrase[i]->pNode; + Fts5Colset *pColset = pNode->pNear->pColset; + if( (pColset && 0==fts5ExprColsetTest(pColset, iCol)) + || aPopulator[i].bMiss + ){ + aPopulator[i].bOk = 0; + }else{ + aPopulator[i].bOk = 1; + } + } + + return sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, z, n, (void*)&sCtx, fts5ExprPopulatePoslistsCb + ); +} + +static void fts5ExprClearPoslists(Fts5ExprNode *pNode){ + if( pNode->eType==FTS5_TERM || pNode->eType==FTS5_STRING ){ + pNode->pNear->apPhrase[0]->poslist.n = 0; + }else{ + int i; + for(i=0; i<pNode->nChild; i++){ + fts5ExprClearPoslists(pNode->apChild[i]); + } + } +} + +static int fts5ExprCheckPoslists(Fts5ExprNode *pNode, i64 iRowid){ + pNode->iRowid = iRowid; + pNode->bEof = 0; + switch( pNode->eType ){ + case FTS5_TERM: + case FTS5_STRING: + return (pNode->pNear->apPhrase[0]->poslist.n>0); + + case FTS5_AND: { + int i; + for(i=0; i<pNode->nChild; i++){ + if( fts5ExprCheckPoslists(pNode->apChild[i], iRowid)==0 ){ + fts5ExprClearPoslists(pNode); + return 0; + } + } + break; + } + + case FTS5_OR: { + int i; + int bRet = 0; + for(i=0; i<pNode->nChild; i++){ + if( fts5ExprCheckPoslists(pNode->apChild[i], iRowid) ){ + bRet = 1; + } + } + return bRet; + } + + default: { + assert( pNode->eType==FTS5_NOT ); + if( 0==fts5ExprCheckPoslists(pNode->apChild[0], iRowid) + || 0!=fts5ExprCheckPoslists(pNode->apChild[1], iRowid) + ){ + fts5ExprClearPoslists(pNode); + return 0; + } + break; + } + } + return 1; +} + +static void sqlite3Fts5ExprCheckPoslists(Fts5Expr *pExpr, i64 iRowid){ + fts5ExprCheckPoslists(pExpr->pRoot, iRowid); +} + +/* +** This function is only called for detail=columns tables. +*/ +static int sqlite3Fts5ExprPhraseCollist( + Fts5Expr *pExpr, + int iPhrase, + const u8 **ppCollist, + int *pnCollist +){ + Fts5ExprPhrase *pPhrase = pExpr->apExprPhrase[iPhrase]; + Fts5ExprNode *pNode = pPhrase->pNode; + int rc = SQLITE_OK; + + assert( iPhrase>=0 && iPhrase<pExpr->nPhrase ); + assert( pExpr->pConfig->eDetail==FTS5_DETAIL_COLUMNS ); + + if( pNode->bEof==0 + && pNode->iRowid==pExpr->pRoot->iRowid + && pPhrase->poslist.n>0 + ){ + Fts5ExprTerm *pTerm = &pPhrase->aTerm[0]; + if( pTerm->pSynonym ){ + Fts5Buffer *pBuf = (Fts5Buffer*)&pTerm->pSynonym[1]; + rc = fts5ExprSynonymList( + pTerm, pNode->iRowid, pBuf, (u8**)ppCollist, pnCollist + ); + }else{ + *ppCollist = pPhrase->aTerm[0].pIter->pData; + *pnCollist = pPhrase->aTerm[0].pIter->nData; + } + }else{ + *ppCollist = 0; + *pnCollist = 0; + } + + return rc; +} + +/* +** 2014 August 11 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +*/ + + + +/* #include "fts5Int.h" */ + +typedef struct Fts5HashEntry Fts5HashEntry; + +/* +** This file contains the implementation of an in-memory hash table used +** to accumuluate "term -> doclist" content before it is flused to a level-0 +** segment. +*/ + + +struct Fts5Hash { + int eDetail; /* Copy of Fts5Config.eDetail */ + int *pnByte; /* Pointer to bytes counter */ + int nEntry; /* Number of entries currently in hash */ + int nSlot; /* Size of aSlot[] array */ + Fts5HashEntry *pScan; /* Current ordered scan item */ + Fts5HashEntry **aSlot; /* Array of hash slots */ +}; + +/* +** Each entry in the hash table is represented by an object of the +** following type. Each object, its key (a nul-terminated string) and +** its current data are stored in a single memory allocation. The +** key immediately follows the object in memory. The position list +** data immediately follows the key data in memory. +** +** The data that follows the key is in a similar, but not identical format +** to the doclist data stored in the database. It is: +** +** * Rowid, as a varint +** * Position list, without 0x00 terminator. +** * Size of previous position list and rowid, as a 4 byte +** big-endian integer. +** +** iRowidOff: +** Offset of last rowid written to data area. Relative to first byte of +** structure. +** +** nData: +** Bytes of data written since iRowidOff. +*/ +struct Fts5HashEntry { + Fts5HashEntry *pHashNext; /* Next hash entry with same hash-key */ + Fts5HashEntry *pScanNext; /* Next entry in sorted order */ + + int nAlloc; /* Total size of allocation */ + int iSzPoslist; /* Offset of space for 4-byte poslist size */ + int nData; /* Total bytes of data (incl. structure) */ + int nKey; /* Length of key in bytes */ + u8 bDel; /* Set delete-flag @ iSzPoslist */ + u8 bContent; /* Set content-flag (detail=none mode) */ + i16 iCol; /* Column of last value written */ + int iPos; /* Position of last value written */ + i64 iRowid; /* Rowid of last value written */ +}; + +/* +** Eqivalent to: +** +** char *fts5EntryKey(Fts5HashEntry *pEntry){ return zKey; } +*/ +#define fts5EntryKey(p) ( ((char *)(&(p)[1])) ) + + +/* +** Allocate a new hash table. +*/ +static int sqlite3Fts5HashNew(Fts5Config *pConfig, Fts5Hash **ppNew, int *pnByte){ + int rc = SQLITE_OK; + Fts5Hash *pNew; + + *ppNew = pNew = (Fts5Hash*)sqlite3_malloc(sizeof(Fts5Hash)); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_int64 nByte; + memset(pNew, 0, sizeof(Fts5Hash)); + pNew->pnByte = pnByte; + pNew->eDetail = pConfig->eDetail; + + pNew->nSlot = 1024; + nByte = sizeof(Fts5HashEntry*) * pNew->nSlot; + pNew->aSlot = (Fts5HashEntry**)sqlite3_malloc64(nByte); + if( pNew->aSlot==0 ){ + sqlite3_free(pNew); + *ppNew = 0; + rc = SQLITE_NOMEM; + }else{ + memset(pNew->aSlot, 0, (size_t)nByte); + } + } + return rc; +} + +/* +** Free a hash table object. +*/ +static void sqlite3Fts5HashFree(Fts5Hash *pHash){ + if( pHash ){ + sqlite3Fts5HashClear(pHash); + sqlite3_free(pHash->aSlot); + sqlite3_free(pHash); + } +} + +/* +** Empty (but do not delete) a hash table. +*/ +static void sqlite3Fts5HashClear(Fts5Hash *pHash){ + int i; + for(i=0; i<pHash->nSlot; i++){ + Fts5HashEntry *pNext; + Fts5HashEntry *pSlot; + for(pSlot=pHash->aSlot[i]; pSlot; pSlot=pNext){ + pNext = pSlot->pHashNext; + sqlite3_free(pSlot); + } + } + memset(pHash->aSlot, 0, pHash->nSlot * sizeof(Fts5HashEntry*)); + pHash->nEntry = 0; +} + +static unsigned int fts5HashKey(int nSlot, const u8 *p, int n){ + int i; + unsigned int h = 13; + for(i=n-1; i>=0; i--){ + h = (h << 3) ^ h ^ p[i]; + } + return (h % nSlot); +} + +static unsigned int fts5HashKey2(int nSlot, u8 b, const u8 *p, int n){ + int i; + unsigned int h = 13; + for(i=n-1; i>=0; i--){ + h = (h << 3) ^ h ^ p[i]; + } + h = (h << 3) ^ h ^ b; + return (h % nSlot); +} + +/* +** Resize the hash table by doubling the number of slots. +*/ +static int fts5HashResize(Fts5Hash *pHash){ + int nNew = pHash->nSlot*2; + int i; + Fts5HashEntry **apNew; + Fts5HashEntry **apOld = pHash->aSlot; + + apNew = (Fts5HashEntry**)sqlite3_malloc64(nNew*sizeof(Fts5HashEntry*)); + if( !apNew ) return SQLITE_NOMEM; + memset(apNew, 0, nNew*sizeof(Fts5HashEntry*)); + + for(i=0; i<pHash->nSlot; i++){ + while( apOld[i] ){ + unsigned int iHash; + Fts5HashEntry *p = apOld[i]; + apOld[i] = p->pHashNext; + iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p), + (int)strlen(fts5EntryKey(p))); + p->pHashNext = apNew[iHash]; + apNew[iHash] = p; + } + } + + sqlite3_free(apOld); + pHash->nSlot = nNew; + pHash->aSlot = apNew; + return SQLITE_OK; +} + +static int fts5HashAddPoslistSize( + Fts5Hash *pHash, + Fts5HashEntry *p, + Fts5HashEntry *p2 +){ + int nRet = 0; + if( p->iSzPoslist ){ + u8 *pPtr = p2 ? (u8*)p2 : (u8*)p; + int nData = p->nData; + if( pHash->eDetail==FTS5_DETAIL_NONE ){ + assert( nData==p->iSzPoslist ); + if( p->bDel ){ + pPtr[nData++] = 0x00; + if( p->bContent ){ + pPtr[nData++] = 0x00; + } + } + }else{ + int nSz = (nData - p->iSzPoslist - 1); /* Size in bytes */ + int nPos = nSz*2 + p->bDel; /* Value of nPos field */ + + assert( p->bDel==0 || p->bDel==1 ); + if( nPos<=127 ){ + pPtr[p->iSzPoslist] = (u8)nPos; + }else{ + int nByte = sqlite3Fts5GetVarintLen((u32)nPos); + memmove(&pPtr[p->iSzPoslist + nByte], &pPtr[p->iSzPoslist + 1], nSz); + sqlite3Fts5PutVarint(&pPtr[p->iSzPoslist], nPos); + nData += (nByte-1); + } + } + + nRet = nData - p->nData; + if( p2==0 ){ + p->iSzPoslist = 0; + p->bDel = 0; + p->bContent = 0; + p->nData = nData; + } + } + return nRet; +} + +/* +** Add an entry to the in-memory hash table. The key is the concatenation +** of bByte and (pToken/nToken). The value is (iRowid/iCol/iPos). +** +** (bByte || pToken) -> (iRowid,iCol,iPos) +** +** Or, if iCol is negative, then the value is a delete marker. +*/ +static int sqlite3Fts5HashWrite( + Fts5Hash *pHash, + i64 iRowid, /* Rowid for this entry */ + int iCol, /* Column token appears in (-ve -> delete) */ + int iPos, /* Position of token within column */ + char bByte, /* First byte of token */ + const char *pToken, int nToken /* Token to add or remove to or from index */ +){ + unsigned int iHash; + Fts5HashEntry *p; + u8 *pPtr; + int nIncr = 0; /* Amount to increment (*pHash->pnByte) by */ + int bNew; /* If non-delete entry should be written */ + + bNew = (pHash->eDetail==FTS5_DETAIL_FULL); + + /* Attempt to locate an existing hash entry */ + iHash = fts5HashKey2(pHash->nSlot, (u8)bByte, (const u8*)pToken, nToken); + for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){ + char *zKey = fts5EntryKey(p); + if( zKey[0]==bByte + && p->nKey==nToken + && memcmp(&zKey[1], pToken, nToken)==0 + ){ + break; + } + } + + /* If an existing hash entry cannot be found, create a new one. */ + if( p==0 ){ + /* Figure out how much space to allocate */ + char *zKey; + sqlite3_int64 nByte = sizeof(Fts5HashEntry) + (nToken+1) + 1 + 64; + if( nByte<128 ) nByte = 128; + + /* Grow the Fts5Hash.aSlot[] array if necessary. */ + if( (pHash->nEntry*2)>=pHash->nSlot ){ + int rc = fts5HashResize(pHash); + if( rc!=SQLITE_OK ) return rc; + iHash = fts5HashKey2(pHash->nSlot, (u8)bByte, (const u8*)pToken, nToken); + } + + /* Allocate new Fts5HashEntry and add it to the hash table. */ + p = (Fts5HashEntry*)sqlite3_malloc64(nByte); + if( !p ) return SQLITE_NOMEM; + memset(p, 0, sizeof(Fts5HashEntry)); + p->nAlloc = (int)nByte; + zKey = fts5EntryKey(p); + zKey[0] = bByte; + memcpy(&zKey[1], pToken, nToken); + assert( iHash==fts5HashKey(pHash->nSlot, (u8*)zKey, nToken+1) ); + p->nKey = nToken; + zKey[nToken+1] = '\0'; + p->nData = nToken+1 + 1 + sizeof(Fts5HashEntry); + p->pHashNext = pHash->aSlot[iHash]; + pHash->aSlot[iHash] = p; + pHash->nEntry++; + + /* Add the first rowid field to the hash-entry */ + p->nData += sqlite3Fts5PutVarint(&((u8*)p)[p->nData], iRowid); + p->iRowid = iRowid; + + p->iSzPoslist = p->nData; + if( pHash->eDetail!=FTS5_DETAIL_NONE ){ + p->nData += 1; + p->iCol = (pHash->eDetail==FTS5_DETAIL_FULL ? 0 : -1); + } + + }else{ + + /* Appending to an existing hash-entry. Check that there is enough + ** space to append the largest possible new entry. Worst case scenario + ** is: + ** + ** + 9 bytes for a new rowid, + ** + 4 byte reserved for the "poslist size" varint. + ** + 1 byte for a "new column" byte, + ** + 3 bytes for a new column number (16-bit max) as a varint, + ** + 5 bytes for the new position offset (32-bit max). + */ + if( (p->nAlloc - p->nData) < (9 + 4 + 1 + 3 + 5) ){ + sqlite3_int64 nNew = p->nAlloc * 2; + Fts5HashEntry *pNew; + Fts5HashEntry **pp; + pNew = (Fts5HashEntry*)sqlite3_realloc64(p, nNew); + if( pNew==0 ) return SQLITE_NOMEM; + pNew->nAlloc = (int)nNew; + for(pp=&pHash->aSlot[iHash]; *pp!=p; pp=&(*pp)->pHashNext); + *pp = pNew; + p = pNew; + } + nIncr -= p->nData; + } + assert( (p->nAlloc - p->nData) >= (9 + 4 + 1 + 3 + 5) ); + + pPtr = (u8*)p; + + /* If this is a new rowid, append the 4-byte size field for the previous + ** entry, and the new rowid for this entry. */ + if( iRowid!=p->iRowid ){ + u64 iDiff = (u64)iRowid - (u64)p->iRowid; + fts5HashAddPoslistSize(pHash, p, 0); + p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iDiff); + p->iRowid = iRowid; + bNew = 1; + p->iSzPoslist = p->nData; + if( pHash->eDetail!=FTS5_DETAIL_NONE ){ + p->nData += 1; + p->iCol = (pHash->eDetail==FTS5_DETAIL_FULL ? 0 : -1); + p->iPos = 0; + } + } + + if( iCol>=0 ){ + if( pHash->eDetail==FTS5_DETAIL_NONE ){ + p->bContent = 1; + }else{ + /* Append a new column value, if necessary */ + assert_nc( iCol>=p->iCol ); + if( iCol!=p->iCol ){ + if( pHash->eDetail==FTS5_DETAIL_FULL ){ + pPtr[p->nData++] = 0x01; + p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iCol); + p->iCol = (i16)iCol; + p->iPos = 0; + }else{ + bNew = 1; + p->iCol = (i16)(iPos = iCol); + } + } + + /* Append the new position offset, if necessary */ + if( bNew ){ + p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iPos - p->iPos + 2); + p->iPos = iPos; + } + } + }else{ + /* This is a delete. Set the delete flag. */ + p->bDel = 1; + } + + nIncr += p->nData; + *pHash->pnByte += nIncr; + return SQLITE_OK; +} + + +/* +** Arguments pLeft and pRight point to linked-lists of hash-entry objects, +** each sorted in key order. This function merges the two lists into a +** single list and returns a pointer to its first element. +*/ +static Fts5HashEntry *fts5HashEntryMerge( + Fts5HashEntry *pLeft, + Fts5HashEntry *pRight +){ + Fts5HashEntry *p1 = pLeft; + Fts5HashEntry *p2 = pRight; + Fts5HashEntry *pRet = 0; + Fts5HashEntry **ppOut = &pRet; + + while( p1 || p2 ){ + if( p1==0 ){ + *ppOut = p2; + p2 = 0; + }else if( p2==0 ){ + *ppOut = p1; + p1 = 0; + }else{ + int i = 0; + char *zKey1 = fts5EntryKey(p1); + char *zKey2 = fts5EntryKey(p2); + while( zKey1[i]==zKey2[i] ) i++; + + if( ((u8)zKey1[i])>((u8)zKey2[i]) ){ + /* p2 is smaller */ + *ppOut = p2; + ppOut = &p2->pScanNext; + p2 = p2->pScanNext; + }else{ + /* p1 is smaller */ + *ppOut = p1; + ppOut = &p1->pScanNext; + p1 = p1->pScanNext; + } + *ppOut = 0; + } + } + + return pRet; +} + +/* +** Extract all tokens from hash table iHash and link them into a list +** in sorted order. The hash table is cleared before returning. It is +** the responsibility of the caller to free the elements of the returned +** list. +*/ +static int fts5HashEntrySort( + Fts5Hash *pHash, + const char *pTerm, int nTerm, /* Query prefix, if any */ + Fts5HashEntry **ppSorted +){ + const int nMergeSlot = 32; + Fts5HashEntry **ap; + Fts5HashEntry *pList; + int iSlot; + int i; + + *ppSorted = 0; + ap = sqlite3_malloc64(sizeof(Fts5HashEntry*) * nMergeSlot); + if( !ap ) return SQLITE_NOMEM; + memset(ap, 0, sizeof(Fts5HashEntry*) * nMergeSlot); + + for(iSlot=0; iSlot<pHash->nSlot; iSlot++){ + Fts5HashEntry *pIter; + for(pIter=pHash->aSlot[iSlot]; pIter; pIter=pIter->pHashNext){ + if( pTerm==0 + || (pIter->nKey+1>=nTerm && 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm)) + ){ + Fts5HashEntry *pEntry = pIter; + pEntry->pScanNext = 0; + for(i=0; ap[i]; i++){ + pEntry = fts5HashEntryMerge(pEntry, ap[i]); + ap[i] = 0; + } + ap[i] = pEntry; + } + } + } + + pList = 0; + for(i=0; i<nMergeSlot; i++){ + pList = fts5HashEntryMerge(pList, ap[i]); + } + + sqlite3_free(ap); + *ppSorted = pList; + return SQLITE_OK; +} + +/* +** Query the hash table for a doclist associated with term pTerm/nTerm. +*/ +static int sqlite3Fts5HashQuery( + Fts5Hash *pHash, /* Hash table to query */ + int nPre, + const char *pTerm, int nTerm, /* Query term */ + void **ppOut, /* OUT: Pointer to new object */ + int *pnDoclist /* OUT: Size of doclist in bytes */ +){ + unsigned int iHash = fts5HashKey(pHash->nSlot, (const u8*)pTerm, nTerm); + char *zKey = 0; + Fts5HashEntry *p; + + for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){ + zKey = fts5EntryKey(p); + assert( p->nKey+1==(int)strlen(zKey) ); + if( nTerm==p->nKey+1 && memcmp(zKey, pTerm, nTerm)==0 ) break; + } + + if( p ){ + int nHashPre = sizeof(Fts5HashEntry) + nTerm + 1; + int nList = p->nData - nHashPre; + u8 *pRet = (u8*)(*ppOut = sqlite3_malloc64(nPre + nList + 10)); + if( pRet ){ + Fts5HashEntry *pFaux = (Fts5HashEntry*)&pRet[nPre-nHashPre]; + memcpy(&pRet[nPre], &((u8*)p)[nHashPre], nList); + nList += fts5HashAddPoslistSize(pHash, p, pFaux); + *pnDoclist = nList; + }else{ + *pnDoclist = 0; + return SQLITE_NOMEM; + } + }else{ + *ppOut = 0; + *pnDoclist = 0; + } + + return SQLITE_OK; +} + +static int sqlite3Fts5HashScanInit( + Fts5Hash *p, /* Hash table to query */ + const char *pTerm, int nTerm /* Query prefix */ +){ + return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan); +} + +#ifdef SQLITE_DEBUG +static int fts5HashCount(Fts5Hash *pHash){ + int nEntry = 0; + int ii; + for(ii=0; ii<pHash->nSlot; ii++){ + Fts5HashEntry *p = 0; + for(p=pHash->aSlot[ii]; p; p=p->pHashNext){ + nEntry++; + } + } + return nEntry; +} +#endif + +/* +** Return true if the hash table is empty, false otherwise. +*/ +static int sqlite3Fts5HashIsEmpty(Fts5Hash *pHash){ + assert( pHash->nEntry==fts5HashCount(pHash) ); + return pHash->nEntry==0; +} + +static void sqlite3Fts5HashScanNext(Fts5Hash *p){ + assert( !sqlite3Fts5HashScanEof(p) ); + p->pScan = p->pScan->pScanNext; +} + +static int sqlite3Fts5HashScanEof(Fts5Hash *p){ + return (p->pScan==0); +} + +static void sqlite3Fts5HashScanEntry( + Fts5Hash *pHash, + const char **pzTerm, /* OUT: term (nul-terminated) */ + const u8 **ppDoclist, /* OUT: pointer to doclist */ + int *pnDoclist /* OUT: size of doclist in bytes */ +){ + Fts5HashEntry *p; + if( (p = pHash->pScan) ){ + char *zKey = fts5EntryKey(p); + int nTerm = (int)strlen(zKey); + fts5HashAddPoslistSize(pHash, p, 0); + *pzTerm = zKey; + *ppDoclist = (const u8*)&zKey[nTerm+1]; + *pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm + 1); + }else{ + *pzTerm = 0; + *ppDoclist = 0; + *pnDoclist = 0; + } +} + +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** Low level access to the FTS index stored in the database file. The +** routines in this file file implement all read and write access to the +** %_data table. Other parts of the system access this functionality via +** the interface defined in fts5Int.h. +*/ + + +/* #include "fts5Int.h" */ + +/* +** Overview: +** +** The %_data table contains all the FTS indexes for an FTS5 virtual table. +** As well as the main term index, there may be up to 31 prefix indexes. +** The format is similar to FTS3/4, except that: +** +** * all segment b-tree leaf data is stored in fixed size page records +** (e.g. 1000 bytes). A single doclist may span multiple pages. Care is +** taken to ensure it is possible to iterate in either direction through +** the entries in a doclist, or to seek to a specific entry within a +** doclist, without loading it into memory. +** +** * large doclists that span many pages have associated "doclist index" +** records that contain a copy of the first rowid on each page spanned by +** the doclist. This is used to speed up seek operations, and merges of +** large doclists with very small doclists. +** +** * extra fields in the "structure record" record the state of ongoing +** incremental merge operations. +** +*/ + + +#define FTS5_OPT_WORK_UNIT 1000 /* Number of leaf pages per optimize step */ +#define FTS5_WORK_UNIT 64 /* Number of leaf pages in unit of work */ + +#define FTS5_MIN_DLIDX_SIZE 4 /* Add dlidx if this many empty pages */ + +#define FTS5_MAIN_PREFIX '0' + +#if FTS5_MAX_PREFIX_INDEXES > 31 +# error "FTS5_MAX_PREFIX_INDEXES is too large" +#endif + +#define FTS5_MAX_LEVEL 64 + +/* +** There are two versions of the format used for the structure record: +** +** 1. the legacy format, that may be read by all fts5 versions, and +** +** 2. the V2 format, which is used by contentless_delete=1 databases. +** +** Both begin with a 4-byte "configuration cookie" value. Then, a legacy +** format structure record contains a varint - the number of levels in +** the structure. Whereas a V2 structure record contains the constant +** 4 bytes [0xff 0x00 0x00 0x01]. This is unambiguous as the value of a +** varint has to be at least 16256 to begin with "0xFF". And the default +** maximum number of levels is 64. +** +** See below for more on structure record formats. +*/ +#define FTS5_STRUCTURE_V2 "\xFF\x00\x00\x01" + +/* +** Details: +** +** The %_data table managed by this module, +** +** CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB); +** +** , contains the following 6 types of records. See the comments surrounding +** the FTS5_*_ROWID macros below for a description of how %_data rowids are +** assigned to each fo them. +** +** 1. Structure Records: +** +** The set of segments that make up an index - the index structure - are +** recorded in a single record within the %_data table. The record consists +** of a single 32-bit configuration cookie value followed by a list of +** SQLite varints. +** +** If the structure record is a V2 record, the configuration cookie is +** followed by the following 4 bytes: [0xFF 0x00 0x00 0x01]. +** +** Next, the record continues with three varints: +** +** + number of levels, +** + total number of segments on all levels, +** + value of write counter. +** +** Then, for each level from 0 to nMax: +** +** + number of input segments in ongoing merge. +** + total number of segments in level. +** + for each segment from oldest to newest: +** + segment id (always > 0) +** + first leaf page number (often 1, always greater than 0) +** + final leaf page number +** +** Then, for V2 structures only: +** +** + lower origin counter value, +** + upper origin counter value, +** + the number of tombstone hash pages. +** +** 2. The Averages Record: +** +** A single record within the %_data table. The data is a list of varints. +** The first value is the number of rows in the index. Then, for each column +** from left to right, the total number of tokens in the column for all +** rows of the table. +** +** 3. Segment leaves: +** +** TERM/DOCLIST FORMAT: +** +** Most of each segment leaf is taken up by term/doclist data. The +** general format of term/doclist, starting with the first term +** on the leaf page, is: +** +** varint : size of first term +** blob: first term data +** doclist: first doclist +** zero-or-more { +** varint: number of bytes in common with previous term +** varint: number of bytes of new term data (nNew) +** blob: nNew bytes of new term data +** doclist: next doclist +** } +** +** doclist format: +** +** varint: first rowid +** poslist: first poslist +** zero-or-more { +** varint: rowid delta (always > 0) +** poslist: next poslist +** } +** +** poslist format: +** +** varint: size of poslist in bytes multiplied by 2, not including +** this field. Plus 1 if this entry carries the "delete" flag. +** collist: collist for column 0 +** zero-or-more { +** 0x01 byte +** varint: column number (I) +** collist: collist for column I +** } +** +** collist format: +** +** varint: first offset + 2 +** zero-or-more { +** varint: offset delta + 2 +** } +** +** PAGE FORMAT +** +** Each leaf page begins with a 4-byte header containing 2 16-bit +** unsigned integer fields in big-endian format. They are: +** +** * The byte offset of the first rowid on the page, if it exists +** and occurs before the first term (otherwise 0). +** +** * The byte offset of the start of the page footer. If the page +** footer is 0 bytes in size, then this field is the same as the +** size of the leaf page in bytes. +** +** The page footer consists of a single varint for each term located +** on the page. Each varint is the byte offset of the current term +** within the page, delta-compressed against the previous value. In +** other words, the first varint in the footer is the byte offset of +** the first term, the second is the byte offset of the second less that +** of the first, and so on. +** +** The term/doclist format described above is accurate if the entire +** term/doclist data fits on a single leaf page. If this is not the case, +** the format is changed in two ways: +** +** + if the first rowid on a page occurs before the first term, it +** is stored as a literal value: +** +** varint: first rowid +** +** + the first term on each page is stored in the same way as the +** very first term of the segment: +** +** varint : size of first term +** blob: first term data +** +** 5. Segment doclist indexes: +** +** Doclist indexes are themselves b-trees, however they usually consist of +** a single leaf record only. The format of each doclist index leaf page +** is: +** +** * Flags byte. Bits are: +** 0x01: Clear if leaf is also the root page, otherwise set. +** +** * Page number of fts index leaf page. As a varint. +** +** * First rowid on page indicated by previous field. As a varint. +** +** * A list of varints, one for each subsequent termless page. A +** positive delta if the termless page contains at least one rowid, +** or an 0x00 byte otherwise. +** +** Internal doclist index nodes are: +** +** * Flags byte. Bits are: +** 0x01: Clear for root page, otherwise set. +** +** * Page number of first child page. As a varint. +** +** * Copy of first rowid on page indicated by previous field. As a varint. +** +** * A list of delta-encoded varints - the first rowid on each subsequent +** child page. +** +** 6. Tombstone Hash Page +** +** These records are only ever present in contentless_delete=1 tables. +** There are zero or more of these associated with each segment. They +** are used to store the tombstone rowids for rows contained in the +** associated segments. +** +** The set of nHashPg tombstone hash pages associated with a single +** segment together form a single hash table containing tombstone rowids. +** To find the page of the hash on which a key might be stored: +** +** iPg = (rowid % nHashPg) +** +** Then, within page iPg, which has nSlot slots: +** +** iSlot = (rowid / nHashPg) % nSlot +** +** Each tombstone hash page begins with an 8 byte header: +** +** 1-byte: Key-size (the size in bytes of each slot). Either 4 or 8. +** 1-byte: rowid-0-tombstone flag. This flag is only valid on the +** first tombstone hash page for each segment (iPg=0). If set, +** the hash table contains rowid 0. If clear, it does not. +** Rowid 0 is handled specially. +** 2-bytes: unused. +** 4-bytes: Big-endian integer containing number of entries on page. +** +** Following this are nSlot 4 or 8 byte slots (depending on the key-size +** in the first byte of the page header). The number of slots may be +** determined based on the size of the page record and the key-size: +** +** nSlot = (nByte - 8) / key-size +*/ + +/* +** Rowids for the averages and structure records in the %_data table. +*/ +#define FTS5_AVERAGES_ROWID 1 /* Rowid used for the averages record */ +#define FTS5_STRUCTURE_ROWID 10 /* The structure record */ + +/* +** Macros determining the rowids used by segment leaves and dlidx leaves +** and nodes. All nodes and leaves are stored in the %_data table with large +** positive rowids. +** +** Each segment has a unique non-zero 16-bit id. +** +** The rowid for each segment leaf is found by passing the segment id and +** the leaf page number to the FTS5_SEGMENT_ROWID macro. Leaves are numbered +** sequentially starting from 1. +*/ +#define FTS5_DATA_ID_B 16 /* Max seg id number 65535 */ +#define FTS5_DATA_DLI_B 1 /* Doclist-index flag (1 bit) */ +#define FTS5_DATA_HEIGHT_B 5 /* Max dlidx tree height of 32 */ +#define FTS5_DATA_PAGE_B 31 /* Max page number of 2147483648 */ + +#define fts5_dri(segid, dlidx, height, pgno) ( \ + ((i64)(segid) << (FTS5_DATA_PAGE_B+FTS5_DATA_HEIGHT_B+FTS5_DATA_DLI_B)) + \ + ((i64)(dlidx) << (FTS5_DATA_PAGE_B + FTS5_DATA_HEIGHT_B)) + \ + ((i64)(height) << (FTS5_DATA_PAGE_B)) + \ + ((i64)(pgno)) \ +) + +#define FTS5_SEGMENT_ROWID(segid, pgno) fts5_dri(segid, 0, 0, pgno) +#define FTS5_DLIDX_ROWID(segid, height, pgno) fts5_dri(segid, 1, height, pgno) +#define FTS5_TOMBSTONE_ROWID(segid,ipg) fts5_dri(segid+(1<<16), 0, 0, ipg) + +#ifdef SQLITE_DEBUG +static int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; } +#endif + + +/* +** Each time a blob is read from the %_data table, it is padded with this +** many zero bytes. This makes it easier to decode the various record formats +** without overreading if the records are corrupt. +*/ +#define FTS5_DATA_ZERO_PADDING 8 +#define FTS5_DATA_PADDING 20 + +typedef struct Fts5Data Fts5Data; +typedef struct Fts5DlidxIter Fts5DlidxIter; +typedef struct Fts5DlidxLvl Fts5DlidxLvl; +typedef struct Fts5DlidxWriter Fts5DlidxWriter; +typedef struct Fts5Iter Fts5Iter; +typedef struct Fts5PageWriter Fts5PageWriter; +typedef struct Fts5SegIter Fts5SegIter; +typedef struct Fts5DoclistIter Fts5DoclistIter; +typedef struct Fts5SegWriter Fts5SegWriter; +typedef struct Fts5Structure Fts5Structure; +typedef struct Fts5StructureLevel Fts5StructureLevel; +typedef struct Fts5StructureSegment Fts5StructureSegment; + +struct Fts5Data { + u8 *p; /* Pointer to buffer containing record */ + int nn; /* Size of record in bytes */ + int szLeaf; /* Size of leaf without page-index */ +}; + +/* +** One object per %_data table. +** +** nContentlessDelete: +** The number of contentless delete operations since the most recent +** call to fts5IndexFlush() or fts5IndexDiscardData(). This is tracked +** so that extra auto-merge work can be done by fts5IndexFlush() to +** account for the delete operations. +*/ +struct Fts5Index { + Fts5Config *pConfig; /* Virtual table configuration */ + char *zDataTbl; /* Name of %_data table */ + int nWorkUnit; /* Leaf pages in a "unit" of work */ + + /* + ** Variables related to the accumulation of tokens and doclists within the + ** in-memory hash tables before they are flushed to disk. + */ + Fts5Hash *pHash; /* Hash table for in-memory data */ + int nPendingData; /* Current bytes of pending data */ + i64 iWriteRowid; /* Rowid for current doc being written */ + int bDelete; /* Current write is a delete */ + int nContentlessDelete; /* Number of contentless delete ops */ + int nPendingRow; /* Number of INSERT in hash table */ + + /* Error state. */ + int rc; /* Current error code */ + + /* State used by the fts5DataXXX() functions. */ + sqlite3_blob *pReader; /* RO incr-blob open on %_data table */ + sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */ + sqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */ + sqlite3_stmt *pIdxWriter; /* "INSERT ... %_idx VALUES(?,?,?,?)" */ + sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=?" */ + sqlite3_stmt *pIdxSelect; + int nRead; /* Total number of blocks read */ + + sqlite3_stmt *pDeleteFromIdx; + + sqlite3_stmt *pDataVersion; + i64 iStructVersion; /* data_version when pStruct read */ + Fts5Structure *pStruct; /* Current db structure (or NULL) */ +}; + +struct Fts5DoclistIter { + u8 *aEof; /* Pointer to 1 byte past end of doclist */ + + /* Output variables. aPoslist==0 at EOF */ + i64 iRowid; + u8 *aPoslist; + int nPoslist; + int nSize; +}; + +/* +** The contents of the "structure" record for each index are represented +** using an Fts5Structure record in memory. Which uses instances of the +** other Fts5StructureXXX types as components. +** +** nOriginCntr: +** This value is set to non-zero for structure records created for +** contentlessdelete=1 tables only. In that case it represents the +** origin value to apply to the next top-level segment created. +*/ +struct Fts5StructureSegment { + int iSegid; /* Segment id */ + int pgnoFirst; /* First leaf page number in segment */ + int pgnoLast; /* Last leaf page number in segment */ + + /* contentlessdelete=1 tables only: */ + u64 iOrigin1; + u64 iOrigin2; + int nPgTombstone; /* Number of tombstone hash table pages */ + u64 nEntryTombstone; /* Number of tombstone entries that "count" */ + u64 nEntry; /* Number of rows in this segment */ +}; +struct Fts5StructureLevel { + int nMerge; /* Number of segments in incr-merge */ + int nSeg; /* Total number of segments on level */ + Fts5StructureSegment *aSeg; /* Array of segments. aSeg[0] is oldest. */ +}; +struct Fts5Structure { + int nRef; /* Object reference count */ + u64 nWriteCounter; /* Total leaves written to level 0 */ + u64 nOriginCntr; /* Origin value for next top-level segment */ + int nSegment; /* Total segments in this structure */ + int nLevel; /* Number of levels in this index */ + Fts5StructureLevel aLevel[1]; /* Array of nLevel level objects */ +}; + +/* +** An object of type Fts5SegWriter is used to write to segments. +*/ +struct Fts5PageWriter { + int pgno; /* Page number for this page */ + int iPrevPgidx; /* Previous value written into pgidx */ + Fts5Buffer buf; /* Buffer containing leaf data */ + Fts5Buffer pgidx; /* Buffer containing page-index */ + Fts5Buffer term; /* Buffer containing previous term on page */ +}; +struct Fts5DlidxWriter { + int pgno; /* Page number for this page */ + int bPrevValid; /* True if iPrev is valid */ + i64 iPrev; /* Previous rowid value written to page */ + Fts5Buffer buf; /* Buffer containing page data */ +}; +struct Fts5SegWriter { + int iSegid; /* Segid to write to */ + Fts5PageWriter writer; /* PageWriter object */ + i64 iPrevRowid; /* Previous rowid written to current leaf */ + u8 bFirstRowidInDoclist; /* True if next rowid is first in doclist */ + u8 bFirstRowidInPage; /* True if next rowid is first in page */ + /* TODO1: Can use (writer.pgidx.n==0) instead of bFirstTermInPage */ + u8 bFirstTermInPage; /* True if next term will be first in leaf */ + int nLeafWritten; /* Number of leaf pages written */ + int nEmpty; /* Number of contiguous term-less nodes */ + + int nDlidx; /* Allocated size of aDlidx[] array */ + Fts5DlidxWriter *aDlidx; /* Array of Fts5DlidxWriter objects */ + + /* Values to insert into the %_idx table */ + Fts5Buffer btterm; /* Next term to insert into %_idx table */ + int iBtPage; /* Page number corresponding to btterm */ +}; + +typedef struct Fts5CResult Fts5CResult; +struct Fts5CResult { + u16 iFirst; /* aSeg[] index of firstest iterator */ + u8 bTermEq; /* True if the terms are equal */ +}; + +/* +** Object for iterating through a single segment, visiting each term/rowid +** pair in the segment. +** +** pSeg: +** The segment to iterate through. +** +** iLeafPgno: +** Current leaf page number within segment. +** +** iLeafOffset: +** Byte offset within the current leaf that is the first byte of the +** position list data (one byte passed the position-list size field). +** +** pLeaf: +** Buffer containing current leaf page data. Set to NULL at EOF. +** +** iTermLeafPgno, iTermLeafOffset: +** Leaf page number containing the last term read from the segment. And +** the offset immediately following the term data. +** +** flags: +** Mask of FTS5_SEGITER_XXX values. Interpreted as follows: +** +** FTS5_SEGITER_ONETERM: +** If set, set the iterator to point to EOF after the current doclist +** has been exhausted. Do not proceed to the next term in the segment. +** +** FTS5_SEGITER_REVERSE: +** This flag is only ever set if FTS5_SEGITER_ONETERM is also set. If +** it is set, iterate through rowid in descending order instead of the +** default ascending order. +** +** iRowidOffset/nRowidOffset/aRowidOffset: +** These are used if the FTS5_SEGITER_REVERSE flag is set. +** +** For each rowid on the page corresponding to the current term, the +** corresponding aRowidOffset[] entry is set to the byte offset of the +** start of the "position-list-size" field within the page. +** +** iTermIdx: +** Index of current term on iTermLeafPgno. +** +** apTombstone/nTombstone: +** These are used for contentless_delete=1 tables only. When the cursor +** is first allocated, the apTombstone[] array is allocated so that it +** is large enough for all tombstones hash pages associated with the +** segment. The pages themselves are loaded lazily from the database as +** they are required. +*/ +struct Fts5SegIter { + Fts5StructureSegment *pSeg; /* Segment to iterate through */ + int flags; /* Mask of configuration flags */ + int iLeafPgno; /* Current leaf page number */ + Fts5Data *pLeaf; /* Current leaf data */ + Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ + i64 iLeafOffset; /* Byte offset within current leaf */ + Fts5Data **apTombstone; /* Array of tombstone pages */ + int nTombstone; + + /* Next method */ + void (*xNext)(Fts5Index*, Fts5SegIter*, int*); + + /* The page and offset from which the current term was read. The offset + ** is the offset of the first rowid in the current doclist. */ + int iTermLeafPgno; + int iTermLeafOffset; + + int iPgidxOff; /* Next offset in pgidx */ + int iEndofDoclist; + + /* The following are only used if the FTS5_SEGITER_REVERSE flag is set. */ + int iRowidOffset; /* Current entry in aRowidOffset[] */ + int nRowidOffset; /* Allocated size of aRowidOffset[] array */ + int *aRowidOffset; /* Array of offset to rowid fields */ + + Fts5DlidxIter *pDlidx; /* If there is a doclist-index */ + + /* Variables populated based on current entry. */ + Fts5Buffer term; /* Current term */ + i64 iRowid; /* Current rowid */ + int nPos; /* Number of bytes in current position list */ + u8 bDel; /* True if the delete flag is set */ +}; + +/* +** Argument is a pointer to an Fts5Data structure that contains a +** leaf page. +*/ +#define ASSERT_SZLEAF_OK(x) assert( \ + (x)->szLeaf==(x)->nn || (x)->szLeaf==fts5GetU16(&(x)->p[2]) \ +) + +#define FTS5_SEGITER_ONETERM 0x01 +#define FTS5_SEGITER_REVERSE 0x02 + +/* +** Argument is a pointer to an Fts5Data structure that contains a leaf +** page. This macro evaluates to true if the leaf contains no terms, or +** false if it contains at least one term. +*/ +#define fts5LeafIsTermless(x) ((x)->szLeaf >= (x)->nn) + +#define fts5LeafTermOff(x, i) (fts5GetU16(&(x)->p[(x)->szLeaf + (i)*2])) + +#define fts5LeafFirstRowidOff(x) (fts5GetU16((x)->p)) + +/* +** Object for iterating through the merged results of one or more segments, +** visiting each term/rowid pair in the merged data. +** +** nSeg is always a power of two greater than or equal to the number of +** segments that this object is merging data from. Both the aSeg[] and +** aFirst[] arrays are sized at nSeg entries. The aSeg[] array is padded +** with zeroed objects - these are handled as if they were iterators opened +** on empty segments. +** +** The results of comparing segments aSeg[N] and aSeg[N+1], where N is an +** even number, is stored in aFirst[(nSeg+N)/2]. The "result" of the +** comparison in this context is the index of the iterator that currently +** points to the smaller term/rowid combination. Iterators at EOF are +** considered to be greater than all other iterators. +** +** aFirst[1] contains the index in aSeg[] of the iterator that points to +** the smallest key overall. aFirst[0] is unused. +** +** poslist: +** Used by sqlite3Fts5IterPoslist() when the poslist needs to be buffered. +** There is no way to tell if this is populated or not. +*/ +struct Fts5Iter { + Fts5IndexIter base; /* Base class containing output vars */ + + Fts5Index *pIndex; /* Index that owns this iterator */ + Fts5Buffer poslist; /* Buffer containing current poslist */ + Fts5Colset *pColset; /* Restrict matches to these columns */ + + /* Invoked to set output variables. */ + void (*xSetOutputs)(Fts5Iter*, Fts5SegIter*); + + int nSeg; /* Size of aSeg[] array */ + int bRev; /* True to iterate in reverse order */ + u8 bSkipEmpty; /* True to skip deleted entries */ + + i64 iSwitchRowid; /* Firstest rowid of other than aFirst[1] */ + Fts5CResult *aFirst; /* Current merge state (see above) */ + Fts5SegIter aSeg[1]; /* Array of segment iterators */ +}; + + +/* +** An instance of the following type is used to iterate through the contents +** of a doclist-index record. +** +** pData: +** Record containing the doclist-index data. +** +** bEof: +** Set to true once iterator has reached EOF. +** +** iOff: +** Set to the current offset within record pData. +*/ +struct Fts5DlidxLvl { + Fts5Data *pData; /* Data for current page of this level */ + int iOff; /* Current offset into pData */ + int bEof; /* At EOF already */ + int iFirstOff; /* Used by reverse iterators */ + + /* Output variables */ + int iLeafPgno; /* Page number of current leaf page */ + i64 iRowid; /* First rowid on leaf iLeafPgno */ +}; +struct Fts5DlidxIter { + int nLvl; + int iSegid; + Fts5DlidxLvl aLvl[1]; +}; + +static void fts5PutU16(u8 *aOut, u16 iVal){ + aOut[0] = (iVal>>8); + aOut[1] = (iVal&0xFF); +} + +static u16 fts5GetU16(const u8 *aIn){ + return ((u16)aIn[0] << 8) + aIn[1]; +} + +/* +** The only argument points to a buffer at least 8 bytes in size. This +** function interprets the first 8 bytes of the buffer as a 64-bit big-endian +** unsigned integer and returns the result. +*/ +static u64 fts5GetU64(u8 *a){ + return ((u64)a[0] << 56) + + ((u64)a[1] << 48) + + ((u64)a[2] << 40) + + ((u64)a[3] << 32) + + ((u64)a[4] << 24) + + ((u64)a[5] << 16) + + ((u64)a[6] << 8) + + ((u64)a[7] << 0); +} + +/* +** The only argument points to a buffer at least 4 bytes in size. This +** function interprets the first 4 bytes of the buffer as a 32-bit big-endian +** unsigned integer and returns the result. +*/ +static u32 fts5GetU32(const u8 *a){ + return ((u32)a[0] << 24) + + ((u32)a[1] << 16) + + ((u32)a[2] << 8) + + ((u32)a[3] << 0); +} + +/* +** Write iVal, formated as a 64-bit big-endian unsigned integer, to the +** buffer indicated by the first argument. +*/ +static void fts5PutU64(u8 *a, u64 iVal){ + a[0] = ((iVal >> 56) & 0xFF); + a[1] = ((iVal >> 48) & 0xFF); + a[2] = ((iVal >> 40) & 0xFF); + a[3] = ((iVal >> 32) & 0xFF); + a[4] = ((iVal >> 24) & 0xFF); + a[5] = ((iVal >> 16) & 0xFF); + a[6] = ((iVal >> 8) & 0xFF); + a[7] = ((iVal >> 0) & 0xFF); +} + +/* +** Write iVal, formated as a 32-bit big-endian unsigned integer, to the +** buffer indicated by the first argument. +*/ +static void fts5PutU32(u8 *a, u32 iVal){ + a[0] = ((iVal >> 24) & 0xFF); + a[1] = ((iVal >> 16) & 0xFF); + a[2] = ((iVal >> 8) & 0xFF); + a[3] = ((iVal >> 0) & 0xFF); +} + +/* +** Allocate and return a buffer at least nByte bytes in size. +** +** If an OOM error is encountered, return NULL and set the error code in +** the Fts5Index handle passed as the first argument. +*/ +static void *fts5IdxMalloc(Fts5Index *p, sqlite3_int64 nByte){ + return sqlite3Fts5MallocZero(&p->rc, nByte); +} + +/* +** Compare the contents of the pLeft buffer with the pRight/nRight blob. +** +** Return -ve if pLeft is smaller than pRight, 0 if they are equal or +** +ve if pRight is smaller than pLeft. In other words: +** +** res = *pLeft - *pRight +*/ +#ifdef SQLITE_DEBUG +static int fts5BufferCompareBlob( + Fts5Buffer *pLeft, /* Left hand side of comparison */ + const u8 *pRight, int nRight /* Right hand side of comparison */ +){ + int nCmp = MIN(pLeft->n, nRight); + int res = memcmp(pLeft->p, pRight, nCmp); + return (res==0 ? (pLeft->n - nRight) : res); +} +#endif + +/* +** Compare the contents of the two buffers using memcmp(). If one buffer +** is a prefix of the other, it is considered the lesser. +** +** Return -ve if pLeft is smaller than pRight, 0 if they are equal or +** +ve if pRight is smaller than pLeft. In other words: +** +** res = *pLeft - *pRight +*/ +static int fts5BufferCompare(Fts5Buffer *pLeft, Fts5Buffer *pRight){ + int nCmp, res; + nCmp = MIN(pLeft->n, pRight->n); + assert( nCmp<=0 || pLeft->p!=0 ); + assert( nCmp<=0 || pRight->p!=0 ); + res = fts5Memcmp(pLeft->p, pRight->p, nCmp); + return (res==0 ? (pLeft->n - pRight->n) : res); +} + +static int fts5LeafFirstTermOff(Fts5Data *pLeaf){ + int ret; + fts5GetVarint32(&pLeaf->p[pLeaf->szLeaf], ret); + return ret; +} + +/* +** Close the read-only blob handle, if it is open. +*/ +static void sqlite3Fts5IndexCloseReader(Fts5Index *p){ + if( p->pReader ){ + sqlite3_blob *pReader = p->pReader; + p->pReader = 0; + sqlite3_blob_close(pReader); + } +} + +/* +** Retrieve a record from the %_data table. +** +** If an error occurs, NULL is returned and an error left in the +** Fts5Index object. +*/ +static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ + Fts5Data *pRet = 0; + if( p->rc==SQLITE_OK ){ + int rc = SQLITE_OK; + + if( p->pReader ){ + /* This call may return SQLITE_ABORT if there has been a savepoint + ** rollback since it was last used. In this case a new blob handle + ** is required. */ + sqlite3_blob *pBlob = p->pReader; + p->pReader = 0; + rc = sqlite3_blob_reopen(pBlob, iRowid); + assert( p->pReader==0 ); + p->pReader = pBlob; + if( rc!=SQLITE_OK ){ + sqlite3Fts5IndexCloseReader(p); + } + if( rc==SQLITE_ABORT ) rc = SQLITE_OK; + } + + /* If the blob handle is not open at this point, open it and seek + ** to the requested entry. */ + if( p->pReader==0 && rc==SQLITE_OK ){ + Fts5Config *pConfig = p->pConfig; + rc = sqlite3_blob_open(pConfig->db, + pConfig->zDb, p->zDataTbl, "block", iRowid, 0, &p->pReader + ); + } + + /* If either of the sqlite3_blob_open() or sqlite3_blob_reopen() calls + ** above returned SQLITE_ERROR, return SQLITE_CORRUPT_VTAB instead. + ** All the reasons those functions might return SQLITE_ERROR - missing + ** table, missing row, non-blob/text in block column - indicate + ** backing store corruption. */ + if( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT; + + if( rc==SQLITE_OK ){ + u8 *aOut = 0; /* Read blob data into this buffer */ + int nByte = sqlite3_blob_bytes(p->pReader); + sqlite3_int64 nAlloc = sizeof(Fts5Data) + nByte + FTS5_DATA_PADDING; + pRet = (Fts5Data*)sqlite3_malloc64(nAlloc); + if( pRet ){ + pRet->nn = nByte; + aOut = pRet->p = (u8*)&pRet[1]; + }else{ + rc = SQLITE_NOMEM; + } + + if( rc==SQLITE_OK ){ + rc = sqlite3_blob_read(p->pReader, aOut, nByte, 0); + } + if( rc!=SQLITE_OK ){ + sqlite3_free(pRet); + pRet = 0; + }else{ + /* TODO1: Fix this */ + pRet->p[nByte] = 0x00; + pRet->p[nByte+1] = 0x00; + pRet->szLeaf = fts5GetU16(&pRet->p[2]); + } + } + p->rc = rc; + p->nRead++; + } + + assert( (pRet==0)==(p->rc!=SQLITE_OK) ); + return pRet; +} + + +/* +** Release a reference to data record returned by an earlier call to +** fts5DataRead(). +*/ +static void fts5DataRelease(Fts5Data *pData){ + sqlite3_free(pData); +} + +static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){ + Fts5Data *pRet = fts5DataRead(p, iRowid); + if( pRet ){ + if( pRet->nn<4 || pRet->szLeaf>pRet->nn ){ + p->rc = FTS5_CORRUPT; + fts5DataRelease(pRet); + pRet = 0; + } + } + return pRet; +} + +static int fts5IndexPrepareStmt( + Fts5Index *p, + sqlite3_stmt **ppStmt, + char *zSql +){ + if( p->rc==SQLITE_OK ){ + if( zSql ){ + p->rc = sqlite3_prepare_v3(p->pConfig->db, zSql, -1, + SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB, + ppStmt, 0); + }else{ + p->rc = SQLITE_NOMEM; + } + } + sqlite3_free(zSql); + return p->rc; +} + + +/* +** INSERT OR REPLACE a record into the %_data table. +*/ +static void fts5DataWrite(Fts5Index *p, i64 iRowid, const u8 *pData, int nData){ + if( p->rc!=SQLITE_OK ) return; + + if( p->pWriter==0 ){ + Fts5Config *pConfig = p->pConfig; + fts5IndexPrepareStmt(p, &p->pWriter, sqlite3_mprintf( + "REPLACE INTO '%q'.'%q_data'(id, block) VALUES(?,?)", + pConfig->zDb, pConfig->zName + )); + if( p->rc ) return; + } + + sqlite3_bind_int64(p->pWriter, 1, iRowid); + sqlite3_bind_blob(p->pWriter, 2, pData, nData, SQLITE_STATIC); + sqlite3_step(p->pWriter); + p->rc = sqlite3_reset(p->pWriter); + sqlite3_bind_null(p->pWriter, 2); +} + +/* +** Execute the following SQL: +** +** DELETE FROM %_data WHERE id BETWEEN $iFirst AND $iLast +*/ +static void fts5DataDelete(Fts5Index *p, i64 iFirst, i64 iLast){ + if( p->rc!=SQLITE_OK ) return; + + if( p->pDeleter==0 ){ + Fts5Config *pConfig = p->pConfig; + char *zSql = sqlite3_mprintf( + "DELETE FROM '%q'.'%q_data' WHERE id>=? AND id<=?", + pConfig->zDb, pConfig->zName + ); + if( fts5IndexPrepareStmt(p, &p->pDeleter, zSql) ) return; + } + + sqlite3_bind_int64(p->pDeleter, 1, iFirst); + sqlite3_bind_int64(p->pDeleter, 2, iLast); + sqlite3_step(p->pDeleter); + p->rc = sqlite3_reset(p->pDeleter); +} + +/* +** Remove all records associated with segment iSegid. +*/ +static void fts5DataRemoveSegment(Fts5Index *p, Fts5StructureSegment *pSeg){ + int iSegid = pSeg->iSegid; + i64 iFirst = FTS5_SEGMENT_ROWID(iSegid, 0); + i64 iLast = FTS5_SEGMENT_ROWID(iSegid+1, 0)-1; + fts5DataDelete(p, iFirst, iLast); + + if( pSeg->nPgTombstone ){ + i64 iTomb1 = FTS5_TOMBSTONE_ROWID(iSegid, 0); + i64 iTomb2 = FTS5_TOMBSTONE_ROWID(iSegid, pSeg->nPgTombstone-1); + fts5DataDelete(p, iTomb1, iTomb2); + } + if( p->pIdxDeleter==0 ){ + Fts5Config *pConfig = p->pConfig; + fts5IndexPrepareStmt(p, &p->pIdxDeleter, sqlite3_mprintf( + "DELETE FROM '%q'.'%q_idx' WHERE segid=?", + pConfig->zDb, pConfig->zName + )); + } + if( p->rc==SQLITE_OK ){ + sqlite3_bind_int(p->pIdxDeleter, 1, iSegid); + sqlite3_step(p->pIdxDeleter); + p->rc = sqlite3_reset(p->pIdxDeleter); + } +} + +/* +** Release a reference to an Fts5Structure object returned by an earlier +** call to fts5StructureRead() or fts5StructureDecode(). +*/ +static void fts5StructureRelease(Fts5Structure *pStruct){ + if( pStruct && 0>=(--pStruct->nRef) ){ + int i; + assert( pStruct->nRef==0 ); + for(i=0; i<pStruct->nLevel; i++){ + sqlite3_free(pStruct->aLevel[i].aSeg); + } + sqlite3_free(pStruct); + } +} + +static void fts5StructureRef(Fts5Structure *pStruct){ + pStruct->nRef++; +} + +static void *sqlite3Fts5StructureRef(Fts5Index *p){ + fts5StructureRef(p->pStruct); + return (void*)p->pStruct; +} +static void sqlite3Fts5StructureRelease(void *p){ + if( p ){ + fts5StructureRelease((Fts5Structure*)p); + } +} +static int sqlite3Fts5StructureTest(Fts5Index *p, void *pStruct){ + if( p->pStruct!=(Fts5Structure*)pStruct ){ + return SQLITE_ABORT; + } + return SQLITE_OK; +} + +/* +** Ensure that structure object (*pp) is writable. +** +** This function is a no-op if (*pRc) is not SQLITE_OK when it is called. If +** an error occurs, (*pRc) is set to an SQLite error code before returning. +*/ +static void fts5StructureMakeWritable(int *pRc, Fts5Structure **pp){ + Fts5Structure *p = *pp; + if( *pRc==SQLITE_OK && p->nRef>1 ){ + i64 nByte = sizeof(Fts5Structure)+(p->nLevel-1)*sizeof(Fts5StructureLevel); + Fts5Structure *pNew; + pNew = (Fts5Structure*)sqlite3Fts5MallocZero(pRc, nByte); + if( pNew ){ + int i; + memcpy(pNew, p, nByte); + for(i=0; i<p->nLevel; i++) pNew->aLevel[i].aSeg = 0; + for(i=0; i<p->nLevel; i++){ + Fts5StructureLevel *pLvl = &pNew->aLevel[i]; + nByte = sizeof(Fts5StructureSegment) * pNew->aLevel[i].nSeg; + pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(pRc, nByte); + if( pLvl->aSeg==0 ){ + for(i=0; i<p->nLevel; i++){ + sqlite3_free(pNew->aLevel[i].aSeg); + } + sqlite3_free(pNew); + return; + } + memcpy(pLvl->aSeg, p->aLevel[i].aSeg, nByte); + } + p->nRef--; + pNew->nRef = 1; + } + *pp = pNew; + } +} + +/* +** Deserialize and return the structure record currently stored in serialized +** form within buffer pData/nData. +** +** The Fts5Structure.aLevel[] and each Fts5StructureLevel.aSeg[] array +** are over-allocated by one slot. This allows the structure contents +** to be more easily edited. +** +** If an error occurs, *ppOut is set to NULL and an SQLite error code +** returned. Otherwise, *ppOut is set to point to the new object and +** SQLITE_OK returned. +*/ +static int fts5StructureDecode( + const u8 *pData, /* Buffer containing serialized structure */ + int nData, /* Size of buffer pData in bytes */ + int *piCookie, /* Configuration cookie value */ + Fts5Structure **ppOut /* OUT: Deserialized object */ +){ + int rc = SQLITE_OK; + int i = 0; + int iLvl; + int nLevel = 0; + int nSegment = 0; + sqlite3_int64 nByte; /* Bytes of space to allocate at pRet */ + Fts5Structure *pRet = 0; /* Structure object to return */ + int bStructureV2 = 0; /* True for FTS5_STRUCTURE_V2 */ + u64 nOriginCntr = 0; /* Largest origin value seen so far */ + + /* Grab the cookie value */ + if( piCookie ) *piCookie = sqlite3Fts5Get32(pData); + i = 4; + + /* Check if this is a V2 structure record. Set bStructureV2 if it is. */ + if( 0==memcmp(&pData[i], FTS5_STRUCTURE_V2, 4) ){ + i += 4; + bStructureV2 = 1; + } + + /* Read the total number of levels and segments from the start of the + ** structure record. */ + i += fts5GetVarint32(&pData[i], nLevel); + i += fts5GetVarint32(&pData[i], nSegment); + if( nLevel>FTS5_MAX_SEGMENT || nLevel<0 + || nSegment>FTS5_MAX_SEGMENT || nSegment<0 + ){ + return FTS5_CORRUPT; + } + nByte = ( + sizeof(Fts5Structure) + /* Main structure */ + sizeof(Fts5StructureLevel) * (nLevel-1) /* aLevel[] array */ + ); + pRet = (Fts5Structure*)sqlite3Fts5MallocZero(&rc, nByte); + + if( pRet ){ + pRet->nRef = 1; + pRet->nLevel = nLevel; + pRet->nSegment = nSegment; + i += sqlite3Fts5GetVarint(&pData[i], &pRet->nWriteCounter); + + for(iLvl=0; rc==SQLITE_OK && iLvl<nLevel; iLvl++){ + Fts5StructureLevel *pLvl = &pRet->aLevel[iLvl]; + int nTotal = 0; + int iSeg; + + if( i>=nData ){ + rc = FTS5_CORRUPT; + }else{ + i += fts5GetVarint32(&pData[i], pLvl->nMerge); + i += fts5GetVarint32(&pData[i], nTotal); + if( nTotal<pLvl->nMerge ) rc = FTS5_CORRUPT; + pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc, + nTotal * sizeof(Fts5StructureSegment) + ); + nSegment -= nTotal; + } + + if( rc==SQLITE_OK ){ + pLvl->nSeg = nTotal; + for(iSeg=0; iSeg<nTotal; iSeg++){ + Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; + if( i>=nData ){ + rc = FTS5_CORRUPT; + break; + } + assert( pSeg!=0 ); + i += fts5GetVarint32(&pData[i], pSeg->iSegid); + i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst); + i += fts5GetVarint32(&pData[i], pSeg->pgnoLast); + if( bStructureV2 ){ + i += fts5GetVarint(&pData[i], &pSeg->iOrigin1); + i += fts5GetVarint(&pData[i], &pSeg->iOrigin2); + i += fts5GetVarint32(&pData[i], pSeg->nPgTombstone); + i += fts5GetVarint(&pData[i], &pSeg->nEntryTombstone); + i += fts5GetVarint(&pData[i], &pSeg->nEntry); + nOriginCntr = MAX(nOriginCntr, pSeg->iOrigin2); + } + if( pSeg->pgnoLast<pSeg->pgnoFirst ){ + rc = FTS5_CORRUPT; + break; + } + } + if( iLvl>0 && pLvl[-1].nMerge && nTotal==0 ) rc = FTS5_CORRUPT; + if( iLvl==nLevel-1 && pLvl->nMerge ) rc = FTS5_CORRUPT; + } + } + if( nSegment!=0 && rc==SQLITE_OK ) rc = FTS5_CORRUPT; + if( bStructureV2 ){ + pRet->nOriginCntr = nOriginCntr+1; + } + + if( rc!=SQLITE_OK ){ + fts5StructureRelease(pRet); + pRet = 0; + } + } + + *ppOut = pRet; + return rc; +} + +/* +** Add a level to the Fts5Structure.aLevel[] array of structure object +** (*ppStruct). +*/ +static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){ + fts5StructureMakeWritable(pRc, ppStruct); + assert( (ppStruct!=0 && (*ppStruct)!=0) || (*pRc)!=SQLITE_OK ); + if( *pRc==SQLITE_OK ){ + Fts5Structure *pStruct = *ppStruct; + int nLevel = pStruct->nLevel; + sqlite3_int64 nByte = ( + sizeof(Fts5Structure) + /* Main structure */ + sizeof(Fts5StructureLevel) * (nLevel+1) /* aLevel[] array */ + ); + + pStruct = sqlite3_realloc64(pStruct, nByte); + if( pStruct ){ + memset(&pStruct->aLevel[nLevel], 0, sizeof(Fts5StructureLevel)); + pStruct->nLevel++; + *ppStruct = pStruct; + }else{ + *pRc = SQLITE_NOMEM; + } + } +} + +/* +** Extend level iLvl so that there is room for at least nExtra more +** segments. +*/ +static void fts5StructureExtendLevel( + int *pRc, + Fts5Structure *pStruct, + int iLvl, + int nExtra, + int bInsert +){ + if( *pRc==SQLITE_OK ){ + Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; + Fts5StructureSegment *aNew; + sqlite3_int64 nByte; + + nByte = (pLvl->nSeg + nExtra) * sizeof(Fts5StructureSegment); + aNew = sqlite3_realloc64(pLvl->aSeg, nByte); + if( aNew ){ + if( bInsert==0 ){ + memset(&aNew[pLvl->nSeg], 0, sizeof(Fts5StructureSegment) * nExtra); + }else{ + int nMove = pLvl->nSeg * sizeof(Fts5StructureSegment); + memmove(&aNew[nExtra], aNew, nMove); + memset(aNew, 0, sizeof(Fts5StructureSegment) * nExtra); + } + pLvl->aSeg = aNew; + }else{ + *pRc = SQLITE_NOMEM; + } + } +} + +static Fts5Structure *fts5StructureReadUncached(Fts5Index *p){ + Fts5Structure *pRet = 0; + Fts5Config *pConfig = p->pConfig; + int iCookie; /* Configuration cookie */ + Fts5Data *pData; + + pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID); + if( p->rc==SQLITE_OK ){ + /* TODO: Do we need this if the leaf-index is appended? Probably... */ + memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING); + p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet); + if( p->rc==SQLITE_OK && (pConfig->pgsz==0 || pConfig->iCookie!=iCookie) ){ + p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie); + } + fts5DataRelease(pData); + if( p->rc!=SQLITE_OK ){ + fts5StructureRelease(pRet); + pRet = 0; + } + } + + return pRet; +} + +static i64 fts5IndexDataVersion(Fts5Index *p){ + i64 iVersion = 0; + + if( p->rc==SQLITE_OK ){ + if( p->pDataVersion==0 ){ + p->rc = fts5IndexPrepareStmt(p, &p->pDataVersion, + sqlite3_mprintf("PRAGMA %Q.data_version", p->pConfig->zDb) + ); + if( p->rc ) return 0; + } + + if( SQLITE_ROW==sqlite3_step(p->pDataVersion) ){ + iVersion = sqlite3_column_int64(p->pDataVersion, 0); + } + p->rc = sqlite3_reset(p->pDataVersion); + } + + return iVersion; +} + +/* +** Read, deserialize and return the structure record. +** +** The Fts5Structure.aLevel[] and each Fts5StructureLevel.aSeg[] array +** are over-allocated as described for function fts5StructureDecode() +** above. +** +** If an error occurs, NULL is returned and an error code left in the +** Fts5Index handle. If an error has already occurred when this function +** is called, it is a no-op. +*/ +static Fts5Structure *fts5StructureRead(Fts5Index *p){ + + if( p->pStruct==0 ){ + p->iStructVersion = fts5IndexDataVersion(p); + if( p->rc==SQLITE_OK ){ + p->pStruct = fts5StructureReadUncached(p); + } + } + +#if 0 + else{ + Fts5Structure *pTest = fts5StructureReadUncached(p); + if( pTest ){ + int i, j; + assert_nc( p->pStruct->nSegment==pTest->nSegment ); + assert_nc( p->pStruct->nLevel==pTest->nLevel ); + for(i=0; i<pTest->nLevel; i++){ + assert_nc( p->pStruct->aLevel[i].nMerge==pTest->aLevel[i].nMerge ); + assert_nc( p->pStruct->aLevel[i].nSeg==pTest->aLevel[i].nSeg ); + for(j=0; j<pTest->aLevel[i].nSeg; j++){ + Fts5StructureSegment *p1 = &pTest->aLevel[i].aSeg[j]; + Fts5StructureSegment *p2 = &p->pStruct->aLevel[i].aSeg[j]; + assert_nc( p1->iSegid==p2->iSegid ); + assert_nc( p1->pgnoFirst==p2->pgnoFirst ); + assert_nc( p1->pgnoLast==p2->pgnoLast ); + } + } + fts5StructureRelease(pTest); + } + } +#endif + + if( p->rc!=SQLITE_OK ) return 0; + assert( p->iStructVersion!=0 ); + assert( p->pStruct!=0 ); + fts5StructureRef(p->pStruct); + return p->pStruct; +} + +static void fts5StructureInvalidate(Fts5Index *p){ + if( p->pStruct ){ + fts5StructureRelease(p->pStruct); + p->pStruct = 0; + } +} + +/* +** Return the total number of segments in index structure pStruct. This +** function is only ever used as part of assert() conditions. +*/ +#ifdef SQLITE_DEBUG +static int fts5StructureCountSegments(Fts5Structure *pStruct){ + int nSegment = 0; /* Total number of segments */ + if( pStruct ){ + int iLvl; /* Used to iterate through levels */ + for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ + nSegment += pStruct->aLevel[iLvl].nSeg; + } + } + + return nSegment; +} +#endif + +#define fts5BufferSafeAppendBlob(pBuf, pBlob, nBlob) { \ + assert( (pBuf)->nSpace>=((pBuf)->n+nBlob) ); \ + memcpy(&(pBuf)->p[(pBuf)->n], pBlob, nBlob); \ + (pBuf)->n += nBlob; \ +} + +#define fts5BufferSafeAppendVarint(pBuf, iVal) { \ + (pBuf)->n += sqlite3Fts5PutVarint(&(pBuf)->p[(pBuf)->n], (iVal)); \ + assert( (pBuf)->nSpace>=(pBuf)->n ); \ +} + + +/* +** Serialize and store the "structure" record. +** +** If an error occurs, leave an error code in the Fts5Index object. If an +** error has already occurred, this function is a no-op. +*/ +static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ + if( p->rc==SQLITE_OK ){ + Fts5Buffer buf; /* Buffer to serialize record into */ + int iLvl; /* Used to iterate through levels */ + int iCookie; /* Cookie value to store */ + int nHdr = (pStruct->nOriginCntr>0 ? (4+4+9+9+9) : (4+9+9)); + + assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); + memset(&buf, 0, sizeof(Fts5Buffer)); + + /* Append the current configuration cookie */ + iCookie = p->pConfig->iCookie; + if( iCookie<0 ) iCookie = 0; + + if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, nHdr) ){ + sqlite3Fts5Put32(buf.p, iCookie); + buf.n = 4; + if( pStruct->nOriginCntr>0 ){ + fts5BufferSafeAppendBlob(&buf, FTS5_STRUCTURE_V2, 4); + } + fts5BufferSafeAppendVarint(&buf, pStruct->nLevel); + fts5BufferSafeAppendVarint(&buf, pStruct->nSegment); + fts5BufferSafeAppendVarint(&buf, (i64)pStruct->nWriteCounter); + } + + for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ + int iSeg; /* Used to iterate through segments */ + Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; + fts5BufferAppendVarint(&p->rc, &buf, pLvl->nMerge); + fts5BufferAppendVarint(&p->rc, &buf, pLvl->nSeg); + assert( pLvl->nMerge<=pLvl->nSeg ); + + for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){ + Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iSegid); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoFirst); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoLast); + if( pStruct->nOriginCntr>0 ){ + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin1); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin2); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nPgTombstone); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntryTombstone); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntry); + } + } + } + + fts5DataWrite(p, FTS5_STRUCTURE_ROWID, buf.p, buf.n); + fts5BufferFree(&buf); + } +} + +#if 0 +static void fts5DebugStructure(int*,Fts5Buffer*,Fts5Structure*); +static void fts5PrintStructure(const char *zCaption, Fts5Structure *pStruct){ + int rc = SQLITE_OK; + Fts5Buffer buf; + memset(&buf, 0, sizeof(buf)); + fts5DebugStructure(&rc, &buf, pStruct); + fprintf(stdout, "%s: %s\n", zCaption, buf.p); + fflush(stdout); + fts5BufferFree(&buf); +} +#else +# define fts5PrintStructure(x,y) +#endif + +static int fts5SegmentSize(Fts5StructureSegment *pSeg){ + return 1 + pSeg->pgnoLast - pSeg->pgnoFirst; +} + +/* +** Return a copy of index structure pStruct. Except, promote as many +** segments as possible to level iPromote. If an OOM occurs, NULL is +** returned. +*/ +static void fts5StructurePromoteTo( + Fts5Index *p, + int iPromote, + int szPromote, + Fts5Structure *pStruct +){ + int il, is; + Fts5StructureLevel *pOut = &pStruct->aLevel[iPromote]; + + if( pOut->nMerge==0 ){ + for(il=iPromote+1; il<pStruct->nLevel; il++){ + Fts5StructureLevel *pLvl = &pStruct->aLevel[il]; + if( pLvl->nMerge ) return; + for(is=pLvl->nSeg-1; is>=0; is--){ + int sz = fts5SegmentSize(&pLvl->aSeg[is]); + if( sz>szPromote ) return; + fts5StructureExtendLevel(&p->rc, pStruct, iPromote, 1, 1); + if( p->rc ) return; + memcpy(pOut->aSeg, &pLvl->aSeg[is], sizeof(Fts5StructureSegment)); + pOut->nSeg++; + pLvl->nSeg--; + } + } + } +} + +/* +** A new segment has just been written to level iLvl of index structure +** pStruct. This function determines if any segments should be promoted +** as a result. Segments are promoted in two scenarios: +** +** a) If the segment just written is smaller than one or more segments +** within the previous populated level, it is promoted to the previous +** populated level. +** +** b) If the segment just written is larger than the newest segment on +** the next populated level, then that segment, and any other adjacent +** segments that are also smaller than the one just written, are +** promoted. +** +** If one or more segments are promoted, the structure object is updated +** to reflect this. +*/ +static void fts5StructurePromote( + Fts5Index *p, /* FTS5 backend object */ + int iLvl, /* Index level just updated */ + Fts5Structure *pStruct /* Index structure */ +){ + if( p->rc==SQLITE_OK ){ + int iTst; + int iPromote = -1; + int szPromote = 0; /* Promote anything this size or smaller */ + Fts5StructureSegment *pSeg; /* Segment just written */ + int szSeg; /* Size of segment just written */ + int nSeg = pStruct->aLevel[iLvl].nSeg; + + if( nSeg==0 ) return; + pSeg = &pStruct->aLevel[iLvl].aSeg[pStruct->aLevel[iLvl].nSeg-1]; + szSeg = (1 + pSeg->pgnoLast - pSeg->pgnoFirst); + + /* Check for condition (a) */ + for(iTst=iLvl-1; iTst>=0 && pStruct->aLevel[iTst].nSeg==0; iTst--); + if( iTst>=0 ){ + int i; + int szMax = 0; + Fts5StructureLevel *pTst = &pStruct->aLevel[iTst]; + assert( pTst->nMerge==0 ); + for(i=0; i<pTst->nSeg; i++){ + int sz = pTst->aSeg[i].pgnoLast - pTst->aSeg[i].pgnoFirst + 1; + if( sz>szMax ) szMax = sz; + } + if( szMax>=szSeg ){ + /* Condition (a) is true. Promote the newest segment on level + ** iLvl to level iTst. */ + iPromote = iTst; + szPromote = szMax; + } + } + + /* If condition (a) is not met, assume (b) is true. StructurePromoteTo() + ** is a no-op if it is not. */ + if( iPromote<0 ){ + iPromote = iLvl; + szPromote = szSeg; + } + fts5StructurePromoteTo(p, iPromote, szPromote, pStruct); + } +} + + +/* +** Advance the iterator passed as the only argument. If the end of the +** doclist-index page is reached, return non-zero. +*/ +static int fts5DlidxLvlNext(Fts5DlidxLvl *pLvl){ + Fts5Data *pData = pLvl->pData; + + if( pLvl->iOff==0 ){ + assert( pLvl->bEof==0 ); + pLvl->iOff = 1; + pLvl->iOff += fts5GetVarint32(&pData->p[1], pLvl->iLeafPgno); + pLvl->iOff += fts5GetVarint(&pData->p[pLvl->iOff], (u64*)&pLvl->iRowid); + pLvl->iFirstOff = pLvl->iOff; + }else{ + int iOff; + for(iOff=pLvl->iOff; iOff<pData->nn; iOff++){ + if( pData->p[iOff] ) break; + } + + if( iOff<pData->nn ){ + i64 iVal; + pLvl->iLeafPgno += (iOff - pLvl->iOff) + 1; + iOff += fts5GetVarint(&pData->p[iOff], (u64*)&iVal); + pLvl->iRowid += iVal; + pLvl->iOff = iOff; + }else{ + pLvl->bEof = 1; + } + } + + return pLvl->bEof; +} + +/* +** Advance the iterator passed as the only argument. +*/ +static int fts5DlidxIterNextR(Fts5Index *p, Fts5DlidxIter *pIter, int iLvl){ + Fts5DlidxLvl *pLvl = &pIter->aLvl[iLvl]; + + assert( iLvl<pIter->nLvl ); + if( fts5DlidxLvlNext(pLvl) ){ + if( (iLvl+1) < pIter->nLvl ){ + fts5DlidxIterNextR(p, pIter, iLvl+1); + if( pLvl[1].bEof==0 ){ + fts5DataRelease(pLvl->pData); + memset(pLvl, 0, sizeof(Fts5DlidxLvl)); + pLvl->pData = fts5DataRead(p, + FTS5_DLIDX_ROWID(pIter->iSegid, iLvl, pLvl[1].iLeafPgno) + ); + if( pLvl->pData ) fts5DlidxLvlNext(pLvl); + } + } + } + + return pIter->aLvl[0].bEof; +} +static int fts5DlidxIterNext(Fts5Index *p, Fts5DlidxIter *pIter){ + return fts5DlidxIterNextR(p, pIter, 0); +} + +/* +** The iterator passed as the first argument has the following fields set +** as follows. This function sets up the rest of the iterator so that it +** points to the first rowid in the doclist-index. +** +** pData: +** pointer to doclist-index record, +** +** When this function is called pIter->iLeafPgno is the page number the +** doclist is associated with (the one featuring the term). +*/ +static int fts5DlidxIterFirst(Fts5DlidxIter *pIter){ + int i; + for(i=0; i<pIter->nLvl; i++){ + fts5DlidxLvlNext(&pIter->aLvl[i]); + } + return pIter->aLvl[0].bEof; +} + + +static int fts5DlidxIterEof(Fts5Index *p, Fts5DlidxIter *pIter){ + return p->rc!=SQLITE_OK || pIter->aLvl[0].bEof; +} + +static void fts5DlidxIterLast(Fts5Index *p, Fts5DlidxIter *pIter){ + int i; + + /* Advance each level to the last entry on the last page */ + for(i=pIter->nLvl-1; p->rc==SQLITE_OK && i>=0; i--){ + Fts5DlidxLvl *pLvl = &pIter->aLvl[i]; + while( fts5DlidxLvlNext(pLvl)==0 ); + pLvl->bEof = 0; + + if( i>0 ){ + Fts5DlidxLvl *pChild = &pLvl[-1]; + fts5DataRelease(pChild->pData); + memset(pChild, 0, sizeof(Fts5DlidxLvl)); + pChild->pData = fts5DataRead(p, + FTS5_DLIDX_ROWID(pIter->iSegid, i-1, pLvl->iLeafPgno) + ); + } + } +} + +/* +** Move the iterator passed as the only argument to the previous entry. +*/ +static int fts5DlidxLvlPrev(Fts5DlidxLvl *pLvl){ + int iOff = pLvl->iOff; + + assert( pLvl->bEof==0 ); + if( iOff<=pLvl->iFirstOff ){ + pLvl->bEof = 1; + }else{ + u8 *a = pLvl->pData->p; + + pLvl->iOff = 0; + fts5DlidxLvlNext(pLvl); + while( 1 ){ + int nZero = 0; + int ii = pLvl->iOff; + u64 delta = 0; + + while( a[ii]==0 ){ + nZero++; + ii++; + } + ii += sqlite3Fts5GetVarint(&a[ii], &delta); + + if( ii>=iOff ) break; + pLvl->iLeafPgno += nZero+1; + pLvl->iRowid += delta; + pLvl->iOff = ii; + } + } + + return pLvl->bEof; +} + +static int fts5DlidxIterPrevR(Fts5Index *p, Fts5DlidxIter *pIter, int iLvl){ + Fts5DlidxLvl *pLvl = &pIter->aLvl[iLvl]; + + assert( iLvl<pIter->nLvl ); + if( fts5DlidxLvlPrev(pLvl) ){ + if( (iLvl+1) < pIter->nLvl ){ + fts5DlidxIterPrevR(p, pIter, iLvl+1); + if( pLvl[1].bEof==0 ){ + fts5DataRelease(pLvl->pData); + memset(pLvl, 0, sizeof(Fts5DlidxLvl)); + pLvl->pData = fts5DataRead(p, + FTS5_DLIDX_ROWID(pIter->iSegid, iLvl, pLvl[1].iLeafPgno) + ); + if( pLvl->pData ){ + while( fts5DlidxLvlNext(pLvl)==0 ); + pLvl->bEof = 0; + } + } + } + } + + return pIter->aLvl[0].bEof; +} +static int fts5DlidxIterPrev(Fts5Index *p, Fts5DlidxIter *pIter){ + return fts5DlidxIterPrevR(p, pIter, 0); +} + +/* +** Free a doclist-index iterator object allocated by fts5DlidxIterInit(). +*/ +static void fts5DlidxIterFree(Fts5DlidxIter *pIter){ + if( pIter ){ + int i; + for(i=0; i<pIter->nLvl; i++){ + fts5DataRelease(pIter->aLvl[i].pData); + } + sqlite3_free(pIter); + } +} + +static Fts5DlidxIter *fts5DlidxIterInit( + Fts5Index *p, /* Fts5 Backend to iterate within */ + int bRev, /* True for ORDER BY ASC */ + int iSegid, /* Segment id */ + int iLeafPg /* Leaf page number to load dlidx for */ +){ + Fts5DlidxIter *pIter = 0; + int i; + int bDone = 0; + + for(i=0; p->rc==SQLITE_OK && bDone==0; i++){ + sqlite3_int64 nByte = sizeof(Fts5DlidxIter) + i * sizeof(Fts5DlidxLvl); + Fts5DlidxIter *pNew; + + pNew = (Fts5DlidxIter*)sqlite3_realloc64(pIter, nByte); + if( pNew==0 ){ + p->rc = SQLITE_NOMEM; + }else{ + i64 iRowid = FTS5_DLIDX_ROWID(iSegid, i, iLeafPg); + Fts5DlidxLvl *pLvl = &pNew->aLvl[i]; + pIter = pNew; + memset(pLvl, 0, sizeof(Fts5DlidxLvl)); + pLvl->pData = fts5DataRead(p, iRowid); + if( pLvl->pData && (pLvl->pData->p[0] & 0x0001)==0 ){ + bDone = 1; + } + pIter->nLvl = i+1; + } + } + + if( p->rc==SQLITE_OK ){ + pIter->iSegid = iSegid; + if( bRev==0 ){ + fts5DlidxIterFirst(pIter); + }else{ + fts5DlidxIterLast(p, pIter); + } + } + + if( p->rc!=SQLITE_OK ){ + fts5DlidxIterFree(pIter); + pIter = 0; + } + + return pIter; +} + +static i64 fts5DlidxIterRowid(Fts5DlidxIter *pIter){ + return pIter->aLvl[0].iRowid; +} +static int fts5DlidxIterPgno(Fts5DlidxIter *pIter){ + return pIter->aLvl[0].iLeafPgno; +} + +/* +** Load the next leaf page into the segment iterator. +*/ +static void fts5SegIterNextPage( + Fts5Index *p, /* FTS5 backend object */ + Fts5SegIter *pIter /* Iterator to advance to next page */ +){ + Fts5Data *pLeaf; + Fts5StructureSegment *pSeg = pIter->pSeg; + fts5DataRelease(pIter->pLeaf); + pIter->iLeafPgno++; + if( pIter->pNextLeaf ){ + pIter->pLeaf = pIter->pNextLeaf; + pIter->pNextLeaf = 0; + }else if( pIter->iLeafPgno<=pSeg->pgnoLast ){ + pIter->pLeaf = fts5LeafRead(p, + FTS5_SEGMENT_ROWID(pSeg->iSegid, pIter->iLeafPgno) + ); + }else{ + pIter->pLeaf = 0; + } + pLeaf = pIter->pLeaf; + + if( pLeaf ){ + pIter->iPgidxOff = pLeaf->szLeaf; + if( fts5LeafIsTermless(pLeaf) ){ + pIter->iEndofDoclist = pLeaf->nn+1; + }else{ + pIter->iPgidxOff += fts5GetVarint32(&pLeaf->p[pIter->iPgidxOff], + pIter->iEndofDoclist + ); + } + } +} + +/* +** Argument p points to a buffer containing a varint to be interpreted as a +** position list size field. Read the varint and return the number of bytes +** read. Before returning, set *pnSz to the number of bytes in the position +** list, and *pbDel to true if the delete flag is set, or false otherwise. +*/ +static int fts5GetPoslistSize(const u8 *p, int *pnSz, int *pbDel){ + int nSz; + int n = 0; + fts5FastGetVarint32(p, n, nSz); + assert_nc( nSz>=0 ); + *pnSz = nSz/2; + *pbDel = nSz & 0x0001; + return n; +} + +/* +** Fts5SegIter.iLeafOffset currently points to the first byte of a +** position-list size field. Read the value of the field and store it +** in the following variables: +** +** Fts5SegIter.nPos +** Fts5SegIter.bDel +** +** Leave Fts5SegIter.iLeafOffset pointing to the first byte of the +** position list content (if any). +*/ +static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){ + if( p->rc==SQLITE_OK ){ + int iOff = pIter->iLeafOffset; /* Offset to read at */ + ASSERT_SZLEAF_OK(pIter->pLeaf); + if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ + int iEod = MIN(pIter->iEndofDoclist, pIter->pLeaf->szLeaf); + pIter->bDel = 0; + pIter->nPos = 1; + if( iOff<iEod && pIter->pLeaf->p[iOff]==0 ){ + pIter->bDel = 1; + iOff++; + if( iOff<iEod && pIter->pLeaf->p[iOff]==0 ){ + pIter->nPos = 1; + iOff++; + }else{ + pIter->nPos = 0; + } + } + }else{ + int nSz; + fts5FastGetVarint32(pIter->pLeaf->p, iOff, nSz); + pIter->bDel = (nSz & 0x0001); + pIter->nPos = nSz>>1; + assert_nc( pIter->nPos>=0 ); + } + pIter->iLeafOffset = iOff; + } +} + +static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ + u8 *a = pIter->pLeaf->p; /* Buffer to read data from */ + i64 iOff = pIter->iLeafOffset; + + ASSERT_SZLEAF_OK(pIter->pLeaf); + while( iOff>=pIter->pLeaf->szLeaf ){ + fts5SegIterNextPage(p, pIter); + if( pIter->pLeaf==0 ){ + if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; + return; + } + iOff = 4; + a = pIter->pLeaf->p; + } + iOff += sqlite3Fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid); + pIter->iLeafOffset = iOff; +} + +/* +** Fts5SegIter.iLeafOffset currently points to the first byte of the +** "nSuffix" field of a term. Function parameter nKeep contains the value +** of the "nPrefix" field (if there was one - it is passed 0 if this is +** the first term in the segment). +** +** This function populates: +** +** Fts5SegIter.term +** Fts5SegIter.rowid +** +** accordingly and leaves (Fts5SegIter.iLeafOffset) set to the content of +** the first position list. The position list belonging to document +** (Fts5SegIter.iRowid). +*/ +static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){ + u8 *a = pIter->pLeaf->p; /* Buffer to read data from */ + i64 iOff = pIter->iLeafOffset; /* Offset to read at */ + int nNew; /* Bytes of new data */ + + iOff += fts5GetVarint32(&a[iOff], nNew); + if( iOff+nNew>pIter->pLeaf->szLeaf || nKeep>pIter->term.n || nNew==0 ){ + p->rc = FTS5_CORRUPT; + return; + } + pIter->term.n = nKeep; + fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]); + assert( pIter->term.n<=pIter->term.nSpace ); + iOff += nNew; + pIter->iTermLeafOffset = iOff; + pIter->iTermLeafPgno = pIter->iLeafPgno; + pIter->iLeafOffset = iOff; + + if( pIter->iPgidxOff>=pIter->pLeaf->nn ){ + pIter->iEndofDoclist = pIter->pLeaf->nn+1; + }else{ + int nExtra; + pIter->iPgidxOff += fts5GetVarint32(&a[pIter->iPgidxOff], nExtra); + pIter->iEndofDoclist += nExtra; + } + + fts5SegIterLoadRowid(p, pIter); +} + +static void fts5SegIterNext(Fts5Index*, Fts5SegIter*, int*); +static void fts5SegIterNext_Reverse(Fts5Index*, Fts5SegIter*, int*); +static void fts5SegIterNext_None(Fts5Index*, Fts5SegIter*, int*); + +static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){ + if( pIter->flags & FTS5_SEGITER_REVERSE ){ + pIter->xNext = fts5SegIterNext_Reverse; + }else if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ + pIter->xNext = fts5SegIterNext_None; + }else{ + pIter->xNext = fts5SegIterNext; + } +} + +/* +** Allocate a tombstone hash page array (pIter->apTombstone) for the +** iterator passed as the second argument. If an OOM error occurs, leave +** an error in the Fts5Index object. +*/ +static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){ + const int nTomb = pIter->pSeg->nPgTombstone; + if( nTomb>0 ){ + Fts5Data **apTomb = 0; + apTomb = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data)*nTomb); + if( apTomb ){ + pIter->apTombstone = apTomb; + pIter->nTombstone = nTomb; + } + } +} + +/* +** Initialize the iterator object pIter to iterate through the entries in +** segment pSeg. The iterator is left pointing to the first entry when +** this function returns. +** +** If an error occurs, Fts5Index.rc is set to an appropriate error code. If +** an error has already occurred when this function is called, it is a no-op. +*/ +static void fts5SegIterInit( + Fts5Index *p, /* FTS index object */ + Fts5StructureSegment *pSeg, /* Description of segment */ + Fts5SegIter *pIter /* Object to populate */ +){ + if( pSeg->pgnoFirst==0 ){ + /* This happens if the segment is being used as an input to an incremental + ** merge and all data has already been "trimmed". See function + ** fts5TrimSegments() for details. In this case leave the iterator empty. + ** The caller will see the (pIter->pLeaf==0) and assume the iterator is + ** at EOF already. */ + assert( pIter->pLeaf==0 ); + return; + } + + if( p->rc==SQLITE_OK ){ + memset(pIter, 0, sizeof(*pIter)); + fts5SegIterSetNext(p, pIter); + pIter->pSeg = pSeg; + pIter->iLeafPgno = pSeg->pgnoFirst-1; + do { + fts5SegIterNextPage(p, pIter); + }while( p->rc==SQLITE_OK && pIter->pLeaf && pIter->pLeaf->nn==4 ); + } + + if( p->rc==SQLITE_OK && pIter->pLeaf ){ + pIter->iLeafOffset = 4; + assert( pIter->pLeaf!=0 ); + assert_nc( pIter->pLeaf->nn>4 ); + assert_nc( fts5LeafFirstTermOff(pIter->pLeaf)==4 ); + pIter->iPgidxOff = pIter->pLeaf->szLeaf+1; + fts5SegIterLoadTerm(p, pIter, 0); + fts5SegIterLoadNPos(p, pIter); + fts5SegIterAllocTombstone(p, pIter); + } +} + +/* +** This function is only ever called on iterators created by calls to +** Fts5IndexQuery() with the FTS5INDEX_QUERY_DESC flag set. +** +** The iterator is in an unusual state when this function is called: the +** Fts5SegIter.iLeafOffset variable is set to the offset of the start of +** the position-list size field for the first relevant rowid on the page. +** Fts5SegIter.rowid is set, but nPos and bDel are not. +** +** This function advances the iterator so that it points to the last +** relevant rowid on the page and, if necessary, initializes the +** aRowidOffset[] and iRowidOffset variables. At this point the iterator +** is in its regular state - Fts5SegIter.iLeafOffset points to the first +** byte of the position list content associated with said rowid. +*/ +static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){ + int eDetail = p->pConfig->eDetail; + int n = pIter->pLeaf->szLeaf; + int i = pIter->iLeafOffset; + u8 *a = pIter->pLeaf->p; + int iRowidOffset = 0; + + if( n>pIter->iEndofDoclist ){ + n = pIter->iEndofDoclist; + } + + ASSERT_SZLEAF_OK(pIter->pLeaf); + while( 1 ){ + u64 iDelta = 0; + + if( eDetail==FTS5_DETAIL_NONE ){ + /* todo */ + if( i<n && a[i]==0 ){ + i++; + if( i<n && a[i]==0 ) i++; + } + }else{ + int nPos; + int bDummy; + i += fts5GetPoslistSize(&a[i], &nPos, &bDummy); + i += nPos; + } + if( i>=n ) break; + i += fts5GetVarint(&a[i], &iDelta); + pIter->iRowid += iDelta; + + /* If necessary, grow the pIter->aRowidOffset[] array. */ + if( iRowidOffset>=pIter->nRowidOffset ){ + int nNew = pIter->nRowidOffset + 8; + int *aNew = (int*)sqlite3_realloc64(pIter->aRowidOffset,nNew*sizeof(int)); + if( aNew==0 ){ + p->rc = SQLITE_NOMEM; + break; + } + pIter->aRowidOffset = aNew; + pIter->nRowidOffset = nNew; + } + + pIter->aRowidOffset[iRowidOffset++] = pIter->iLeafOffset; + pIter->iLeafOffset = i; + } + pIter->iRowidOffset = iRowidOffset; + fts5SegIterLoadNPos(p, pIter); +} + +/* +** +*/ +static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ + assert( pIter->flags & FTS5_SEGITER_REVERSE ); + assert( pIter->flags & FTS5_SEGITER_ONETERM ); + + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = 0; + while( p->rc==SQLITE_OK && pIter->iLeafPgno>pIter->iTermLeafPgno ){ + Fts5Data *pNew; + pIter->iLeafPgno--; + pNew = fts5DataRead(p, FTS5_SEGMENT_ROWID( + pIter->pSeg->iSegid, pIter->iLeafPgno + )); + if( pNew ){ + /* iTermLeafOffset may be equal to szLeaf if the term is the last + ** thing on the page - i.e. the first rowid is on the following page. + ** In this case leave pIter->pLeaf==0, this iterator is at EOF. */ + if( pIter->iLeafPgno==pIter->iTermLeafPgno ){ + assert( pIter->pLeaf==0 ); + if( pIter->iTermLeafOffset<pNew->szLeaf ){ + pIter->pLeaf = pNew; + pIter->iLeafOffset = pIter->iTermLeafOffset; + } + }else{ + int iRowidOff; + iRowidOff = fts5LeafFirstRowidOff(pNew); + if( iRowidOff ){ + if( iRowidOff>=pNew->szLeaf ){ + p->rc = FTS5_CORRUPT; + }else{ + pIter->pLeaf = pNew; + pIter->iLeafOffset = iRowidOff; + } + } + } + + if( pIter->pLeaf ){ + u8 *a = &pIter->pLeaf->p[pIter->iLeafOffset]; + pIter->iLeafOffset += fts5GetVarint(a, (u64*)&pIter->iRowid); + break; + }else{ + fts5DataRelease(pNew); + } + } + } + + if( pIter->pLeaf ){ + pIter->iEndofDoclist = pIter->pLeaf->nn+1; + fts5SegIterReverseInitPage(p, pIter); + } +} + +/* +** Return true if the iterator passed as the second argument currently +** points to a delete marker. A delete marker is an entry with a 0 byte +** position-list. +*/ +static int fts5MultiIterIsEmpty(Fts5Index *p, Fts5Iter *pIter){ + Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; + return (p->rc==SQLITE_OK && pSeg->pLeaf && pSeg->nPos==0); +} + +/* +** Advance iterator pIter to the next entry. +** +** This version of fts5SegIterNext() is only used by reverse iterators. +*/ +static void fts5SegIterNext_Reverse( + Fts5Index *p, /* FTS5 backend object */ + Fts5SegIter *pIter, /* Iterator to advance */ + int *pbUnused /* Unused */ +){ + assert( pIter->flags & FTS5_SEGITER_REVERSE ); + assert( pIter->pNextLeaf==0 ); + UNUSED_PARAM(pbUnused); + + if( pIter->iRowidOffset>0 ){ + u8 *a = pIter->pLeaf->p; + int iOff; + u64 iDelta; + + pIter->iRowidOffset--; + pIter->iLeafOffset = pIter->aRowidOffset[pIter->iRowidOffset]; + fts5SegIterLoadNPos(p, pIter); + iOff = pIter->iLeafOffset; + if( p->pConfig->eDetail!=FTS5_DETAIL_NONE ){ + iOff += pIter->nPos; + } + fts5GetVarint(&a[iOff], &iDelta); + pIter->iRowid -= iDelta; + }else{ + fts5SegIterReverseNewPage(p, pIter); + } +} + +/* +** Advance iterator pIter to the next entry. +** +** This version of fts5SegIterNext() is only used if detail=none and the +** iterator is not a reverse direction iterator. +*/ +static void fts5SegIterNext_None( + Fts5Index *p, /* FTS5 backend object */ + Fts5SegIter *pIter, /* Iterator to advance */ + int *pbNewTerm /* OUT: Set for new term */ +){ + int iOff; + + assert( p->rc==SQLITE_OK ); + assert( (pIter->flags & FTS5_SEGITER_REVERSE)==0 ); + assert( p->pConfig->eDetail==FTS5_DETAIL_NONE ); + + ASSERT_SZLEAF_OK(pIter->pLeaf); + iOff = pIter->iLeafOffset; + + /* Next entry is on the next page */ + while( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){ + fts5SegIterNextPage(p, pIter); + if( p->rc || pIter->pLeaf==0 ) return; + pIter->iRowid = 0; + iOff = 4; + } + + if( iOff<pIter->iEndofDoclist ){ + /* Next entry is on the current page */ + i64 iDelta; + iOff += sqlite3Fts5GetVarint(&pIter->pLeaf->p[iOff], (u64*)&iDelta); + pIter->iLeafOffset = iOff; + pIter->iRowid += iDelta; + }else if( (pIter->flags & FTS5_SEGITER_ONETERM)==0 ){ + if( pIter->pSeg ){ + int nKeep = 0; + if( iOff!=fts5LeafFirstTermOff(pIter->pLeaf) ){ + iOff += fts5GetVarint32(&pIter->pLeaf->p[iOff], nKeep); + } + pIter->iLeafOffset = iOff; + fts5SegIterLoadTerm(p, pIter, nKeep); + }else{ + const u8 *pList = 0; + const char *zTerm = 0; + int nList; + sqlite3Fts5HashScanNext(p->pHash); + sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); + if( pList==0 ) goto next_none_eof; + pIter->pLeaf->p = (u8*)pList; + pIter->pLeaf->nn = nList; + pIter->pLeaf->szLeaf = nList; + pIter->iEndofDoclist = nList; + sqlite3Fts5BufferSet(&p->rc,&pIter->term, (int)strlen(zTerm), (u8*)zTerm); + pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); + } + + if( pbNewTerm ) *pbNewTerm = 1; + }else{ + goto next_none_eof; + } + + fts5SegIterLoadNPos(p, pIter); + + return; + next_none_eof: + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = 0; +} + + +/* +** Advance iterator pIter to the next entry. +** +** If an error occurs, Fts5Index.rc is set to an appropriate error code. It +** is not considered an error if the iterator reaches EOF. If an error has +** already occurred when this function is called, it is a no-op. +*/ +static void fts5SegIterNext( + Fts5Index *p, /* FTS5 backend object */ + Fts5SegIter *pIter, /* Iterator to advance */ + int *pbNewTerm /* OUT: Set for new term */ +){ + Fts5Data *pLeaf = pIter->pLeaf; + int iOff; + int bNewTerm = 0; + int nKeep = 0; + u8 *a; + int n; + + assert( pbNewTerm==0 || *pbNewTerm==0 ); + assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE ); + + /* Search for the end of the position list within the current page. */ + a = pLeaf->p; + n = pLeaf->szLeaf; + + ASSERT_SZLEAF_OK(pLeaf); + iOff = pIter->iLeafOffset + pIter->nPos; + + if( iOff<n ){ + /* The next entry is on the current page. */ + assert_nc( iOff<=pIter->iEndofDoclist ); + if( iOff>=pIter->iEndofDoclist ){ + bNewTerm = 1; + if( iOff!=fts5LeafFirstTermOff(pLeaf) ){ + iOff += fts5GetVarint32(&a[iOff], nKeep); + } + }else{ + u64 iDelta; + iOff += sqlite3Fts5GetVarint(&a[iOff], &iDelta); + pIter->iRowid += iDelta; + assert_nc( iDelta>0 ); + } + pIter->iLeafOffset = iOff; + + }else if( pIter->pSeg==0 ){ + const u8 *pList = 0; + const char *zTerm = 0; + int nList = 0; + assert( (pIter->flags & FTS5_SEGITER_ONETERM) || pbNewTerm ); + if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){ + sqlite3Fts5HashScanNext(p->pHash); + sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); + } + if( pList==0 ){ + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = 0; + }else{ + pIter->pLeaf->p = (u8*)pList; + pIter->pLeaf->nn = nList; + pIter->pLeaf->szLeaf = nList; + pIter->iEndofDoclist = nList+1; + sqlite3Fts5BufferSet(&p->rc, &pIter->term, (int)strlen(zTerm), + (u8*)zTerm); + pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); + *pbNewTerm = 1; + } + }else{ + iOff = 0; + /* Next entry is not on the current page */ + while( iOff==0 ){ + fts5SegIterNextPage(p, pIter); + pLeaf = pIter->pLeaf; + if( pLeaf==0 ) break; + ASSERT_SZLEAF_OK(pLeaf); + if( (iOff = fts5LeafFirstRowidOff(pLeaf)) && iOff<pLeaf->szLeaf ){ + iOff += sqlite3Fts5GetVarint(&pLeaf->p[iOff], (u64*)&pIter->iRowid); + pIter->iLeafOffset = iOff; + + if( pLeaf->nn>pLeaf->szLeaf ){ + pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( + &pLeaf->p[pLeaf->szLeaf], pIter->iEndofDoclist + ); + } + } + else if( pLeaf->nn>pLeaf->szLeaf ){ + pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( + &pLeaf->p[pLeaf->szLeaf], iOff + ); + pIter->iLeafOffset = iOff; + pIter->iEndofDoclist = iOff; + bNewTerm = 1; + } + assert_nc( iOff<pLeaf->szLeaf ); + if( iOff>pLeaf->szLeaf ){ + p->rc = FTS5_CORRUPT; + return; + } + } + } + + /* Check if the iterator is now at EOF. If so, return early. */ + if( pIter->pLeaf ){ + if( bNewTerm ){ + if( pIter->flags & FTS5_SEGITER_ONETERM ){ + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = 0; + }else{ + fts5SegIterLoadTerm(p, pIter, nKeep); + fts5SegIterLoadNPos(p, pIter); + if( pbNewTerm ) *pbNewTerm = 1; + } + }else{ + /* The following could be done by calling fts5SegIterLoadNPos(). But + ** this block is particularly performance critical, so equivalent + ** code is inlined. */ + int nSz; + assert_nc( pIter->iLeafOffset<=pIter->pLeaf->nn ); + fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz); + pIter->bDel = (nSz & 0x0001); + pIter->nPos = nSz>>1; + assert_nc( pIter->nPos>=0 ); + } + } +} + +#define SWAPVAL(T, a, b) { T tmp; tmp=a; a=b; b=tmp; } + +#define fts5IndexSkipVarint(a, iOff) { \ + int iEnd = iOff+9; \ + while( (a[iOff++] & 0x80) && iOff<iEnd ); \ +} + +/* +** Iterator pIter currently points to the first rowid in a doclist. This +** function sets the iterator up so that iterates in reverse order through +** the doclist. +*/ +static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ + Fts5DlidxIter *pDlidx = pIter->pDlidx; + Fts5Data *pLast = 0; + int pgnoLast = 0; + + if( pDlidx && p->pConfig->iVersion==FTS5_CURRENT_VERSION ){ + int iSegid = pIter->pSeg->iSegid; + pgnoLast = fts5DlidxIterPgno(pDlidx); + pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast)); + }else{ + Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */ + + /* Currently, Fts5SegIter.iLeafOffset points to the first byte of + ** position-list content for the current rowid. Back it up so that it + ** points to the start of the position-list size field. */ + int iPoslist; + if( pIter->iTermLeafPgno==pIter->iLeafPgno ){ + iPoslist = pIter->iTermLeafOffset; + }else{ + iPoslist = 4; + } + fts5IndexSkipVarint(pLeaf->p, iPoslist); + pIter->iLeafOffset = iPoslist; + + /* If this condition is true then the largest rowid for the current + ** term may not be stored on the current page. So search forward to + ** see where said rowid really is. */ + if( pIter->iEndofDoclist>=pLeaf->szLeaf ){ + int pgno; + Fts5StructureSegment *pSeg = pIter->pSeg; + + /* The last rowid in the doclist may not be on the current page. Search + ** forward to find the page containing the last rowid. */ + for(pgno=pIter->iLeafPgno+1; !p->rc && pgno<=pSeg->pgnoLast; pgno++){ + i64 iAbs = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno); + Fts5Data *pNew = fts5LeafRead(p, iAbs); + if( pNew ){ + int iRowid, bTermless; + iRowid = fts5LeafFirstRowidOff(pNew); + bTermless = fts5LeafIsTermless(pNew); + if( iRowid ){ + SWAPVAL(Fts5Data*, pNew, pLast); + pgnoLast = pgno; + } + fts5DataRelease(pNew); + if( bTermless==0 ) break; + } + } + } + } + + /* If pLast is NULL at this point, then the last rowid for this doclist + ** lies on the page currently indicated by the iterator. In this case + ** pIter->iLeafOffset is already set to point to the position-list size + ** field associated with the first relevant rowid on the page. + ** + ** Or, if pLast is non-NULL, then it is the page that contains the last + ** rowid. In this case configure the iterator so that it points to the + ** first rowid on this page. + */ + if( pLast ){ + int iOff; + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = pLast; + pIter->iLeafPgno = pgnoLast; + iOff = fts5LeafFirstRowidOff(pLast); + if( iOff>pLast->szLeaf ){ + p->rc = FTS5_CORRUPT; + return; + } + iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid); + pIter->iLeafOffset = iOff; + + if( fts5LeafIsTermless(pLast) ){ + pIter->iEndofDoclist = pLast->nn+1; + }else{ + pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast); + } + } + + fts5SegIterReverseInitPage(p, pIter); +} + +/* +** Iterator pIter currently points to the first rowid of a doclist. +** There is a doclist-index associated with the final term on the current +** page. If the current term is the last term on the page, load the +** doclist-index from disk and initialize an iterator at (pIter->pDlidx). +*/ +static void fts5SegIterLoadDlidx(Fts5Index *p, Fts5SegIter *pIter){ + int iSeg = pIter->pSeg->iSegid; + int bRev = (pIter->flags & FTS5_SEGITER_REVERSE); + Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */ + + assert( pIter->flags & FTS5_SEGITER_ONETERM ); + assert( pIter->pDlidx==0 ); + + /* Check if the current doclist ends on this page. If it does, return + ** early without loading the doclist-index (as it belongs to a different + ** term. */ + if( pIter->iTermLeafPgno==pIter->iLeafPgno + && pIter->iEndofDoclist<pLeaf->szLeaf + ){ + return; + } + + pIter->pDlidx = fts5DlidxIterInit(p, bRev, iSeg, pIter->iTermLeafPgno); +} + +/* +** The iterator object passed as the second argument currently contains +** no valid values except for the Fts5SegIter.pLeaf member variable. This +** function searches the leaf page for a term matching (pTerm/nTerm). +** +** If the specified term is found on the page, then the iterator is left +** pointing to it. If argument bGe is zero and the term is not found, +** the iterator is left pointing at EOF. +** +** If bGe is non-zero and the specified term is not found, then the +** iterator is left pointing to the smallest term in the segment that +** is larger than the specified term, even if this term is not on the +** current page. +*/ +static void fts5LeafSeek( + Fts5Index *p, /* Leave any error code here */ + int bGe, /* True for a >= search */ + Fts5SegIter *pIter, /* Iterator to seek */ + const u8 *pTerm, int nTerm /* Term to search for */ +){ + u32 iOff; + const u8 *a = pIter->pLeaf->p; + u32 n = (u32)pIter->pLeaf->nn; + + u32 nMatch = 0; + u32 nKeep = 0; + u32 nNew = 0; + u32 iTermOff; + u32 iPgidx; /* Current offset in pgidx */ + int bEndOfPage = 0; + + assert( p->rc==SQLITE_OK ); + + iPgidx = (u32)pIter->pLeaf->szLeaf; + iPgidx += fts5GetVarint32(&a[iPgidx], iTermOff); + iOff = iTermOff; + if( iOff>n ){ + p->rc = FTS5_CORRUPT; + return; + } + + while( 1 ){ + + /* Figure out how many new bytes are in this term */ + fts5FastGetVarint32(a, iOff, nNew); + if( nKeep<nMatch ){ + goto search_failed; + } + + assert( nKeep>=nMatch ); + if( nKeep==nMatch ){ + u32 nCmp; + u32 i; + nCmp = (u32)MIN(nNew, nTerm-nMatch); + for(i=0; i<nCmp; i++){ + if( a[iOff+i]!=pTerm[nMatch+i] ) break; + } + nMatch += i; + + if( (u32)nTerm==nMatch ){ + if( i==nNew ){ + goto search_success; + }else{ + goto search_failed; + } + }else if( i<nNew && a[iOff+i]>pTerm[nMatch] ){ + goto search_failed; + } + } + + if( iPgidx>=n ){ + bEndOfPage = 1; + break; + } + + iPgidx += fts5GetVarint32(&a[iPgidx], nKeep); + iTermOff += nKeep; + iOff = iTermOff; + + if( iOff>=n ){ + p->rc = FTS5_CORRUPT; + return; + } + + /* Read the nKeep field of the next term. */ + fts5FastGetVarint32(a, iOff, nKeep); + } + + search_failed: + if( bGe==0 ){ + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = 0; + return; + }else if( bEndOfPage ){ + do { + fts5SegIterNextPage(p, pIter); + if( pIter->pLeaf==0 ) return; + a = pIter->pLeaf->p; + if( fts5LeafIsTermless(pIter->pLeaf)==0 ){ + iPgidx = (u32)pIter->pLeaf->szLeaf; + iPgidx += fts5GetVarint32(&pIter->pLeaf->p[iPgidx], iOff); + if( iOff<4 || (i64)iOff>=pIter->pLeaf->szLeaf ){ + p->rc = FTS5_CORRUPT; + return; + }else{ + nKeep = 0; + iTermOff = iOff; + n = (u32)pIter->pLeaf->nn; + iOff += fts5GetVarint32(&a[iOff], nNew); + break; + } + } + }while( 1 ); + } + + search_success: + if( (i64)iOff+nNew>n || nNew<1 ){ + p->rc = FTS5_CORRUPT; + return; + } + pIter->iLeafOffset = iOff + nNew; + pIter->iTermLeafOffset = pIter->iLeafOffset; + pIter->iTermLeafPgno = pIter->iLeafPgno; + + fts5BufferSet(&p->rc, &pIter->term, nKeep, pTerm); + fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]); + + if( iPgidx>=n ){ + pIter->iEndofDoclist = pIter->pLeaf->nn+1; + }else{ + int nExtra; + iPgidx += fts5GetVarint32(&a[iPgidx], nExtra); + pIter->iEndofDoclist = iTermOff + nExtra; + } + pIter->iPgidxOff = iPgidx; + + fts5SegIterLoadRowid(p, pIter); + fts5SegIterLoadNPos(p, pIter); +} + +static sqlite3_stmt *fts5IdxSelectStmt(Fts5Index *p){ + if( p->pIdxSelect==0 ){ + Fts5Config *pConfig = p->pConfig; + fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf( + "SELECT pgno FROM '%q'.'%q_idx' WHERE " + "segid=? AND term<=? ORDER BY term DESC LIMIT 1", + pConfig->zDb, pConfig->zName + )); + } + return p->pIdxSelect; +} + +/* +** Initialize the object pIter to point to term pTerm/nTerm within segment +** pSeg. If there is no such term in the index, the iterator is set to EOF. +** +** If an error occurs, Fts5Index.rc is set to an appropriate error code. If +** an error has already occurred when this function is called, it is a no-op. +*/ +static void fts5SegIterSeekInit( + Fts5Index *p, /* FTS5 backend */ + const u8 *pTerm, int nTerm, /* Term to seek to */ + int flags, /* Mask of FTS5INDEX_XXX flags */ + Fts5StructureSegment *pSeg, /* Description of segment */ + Fts5SegIter *pIter /* Object to populate */ +){ + int iPg = 1; + int bGe = (flags & FTS5INDEX_QUERY_SCAN); + int bDlidx = 0; /* True if there is a doclist-index */ + sqlite3_stmt *pIdxSelect = 0; + + assert( bGe==0 || (flags & FTS5INDEX_QUERY_DESC)==0 ); + assert( pTerm && nTerm ); + memset(pIter, 0, sizeof(*pIter)); + pIter->pSeg = pSeg; + + /* This block sets stack variable iPg to the leaf page number that may + ** contain term (pTerm/nTerm), if it is present in the segment. */ + pIdxSelect = fts5IdxSelectStmt(p); + if( p->rc ) return; + sqlite3_bind_int(pIdxSelect, 1, pSeg->iSegid); + sqlite3_bind_blob(pIdxSelect, 2, pTerm, nTerm, SQLITE_STATIC); + if( SQLITE_ROW==sqlite3_step(pIdxSelect) ){ + i64 val = sqlite3_column_int(pIdxSelect, 0); + iPg = (int)(val>>1); + bDlidx = (val & 0x0001); + } + p->rc = sqlite3_reset(pIdxSelect); + sqlite3_bind_null(pIdxSelect, 2); + + if( iPg<pSeg->pgnoFirst ){ + iPg = pSeg->pgnoFirst; + bDlidx = 0; + } + + pIter->iLeafPgno = iPg - 1; + fts5SegIterNextPage(p, pIter); + + if( pIter->pLeaf ){ + fts5LeafSeek(p, bGe, pIter, pTerm, nTerm); + } + + if( p->rc==SQLITE_OK && bGe==0 ){ + pIter->flags |= FTS5_SEGITER_ONETERM; + if( pIter->pLeaf ){ + if( flags & FTS5INDEX_QUERY_DESC ){ + pIter->flags |= FTS5_SEGITER_REVERSE; + } + if( bDlidx ){ + fts5SegIterLoadDlidx(p, pIter); + } + if( flags & FTS5INDEX_QUERY_DESC ){ + fts5SegIterReverse(p, pIter); + } + } + } + + fts5SegIterSetNext(p, pIter); + fts5SegIterAllocTombstone(p, pIter); + + /* Either: + ** + ** 1) an error has occurred, or + ** 2) the iterator points to EOF, or + ** 3) the iterator points to an entry with term (pTerm/nTerm), or + ** 4) the FTS5INDEX_QUERY_SCAN flag was set and the iterator points + ** to an entry with a term greater than or equal to (pTerm/nTerm). + */ + assert_nc( p->rc!=SQLITE_OK /* 1 */ + || pIter->pLeaf==0 /* 2 */ + || fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)==0 /* 3 */ + || (bGe && fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)>0) /* 4 */ + ); +} + +/* +** Initialize the object pIter to point to term pTerm/nTerm within the +** in-memory hash table. If there is no such term in the hash-table, the +** iterator is set to EOF. +** +** If an error occurs, Fts5Index.rc is set to an appropriate error code. If +** an error has already occurred when this function is called, it is a no-op. +*/ +static void fts5SegIterHashInit( + Fts5Index *p, /* FTS5 backend */ + const u8 *pTerm, int nTerm, /* Term to seek to */ + int flags, /* Mask of FTS5INDEX_XXX flags */ + Fts5SegIter *pIter /* Object to populate */ +){ + int nList = 0; + const u8 *z = 0; + int n = 0; + Fts5Data *pLeaf = 0; + + assert( p->pHash ); + assert( p->rc==SQLITE_OK ); + + if( pTerm==0 || (flags & FTS5INDEX_QUERY_SCAN) ){ + const u8 *pList = 0; + + p->rc = sqlite3Fts5HashScanInit(p->pHash, (const char*)pTerm, nTerm); + sqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &pList, &nList); + n = (z ? (int)strlen((const char*)z) : 0); + if( pList ){ + pLeaf = fts5IdxMalloc(p, sizeof(Fts5Data)); + if( pLeaf ){ + pLeaf->p = (u8*)pList; + } + } + }else{ + p->rc = sqlite3Fts5HashQuery(p->pHash, sizeof(Fts5Data), + (const char*)pTerm, nTerm, (void**)&pLeaf, &nList + ); + if( pLeaf ){ + pLeaf->p = (u8*)&pLeaf[1]; + } + z = pTerm; + n = nTerm; + pIter->flags |= FTS5_SEGITER_ONETERM; + } + + if( pLeaf ){ + sqlite3Fts5BufferSet(&p->rc, &pIter->term, n, z); + pLeaf->nn = pLeaf->szLeaf = nList; + pIter->pLeaf = pLeaf; + pIter->iLeafOffset = fts5GetVarint(pLeaf->p, (u64*)&pIter->iRowid); + pIter->iEndofDoclist = pLeaf->nn; + + if( flags & FTS5INDEX_QUERY_DESC ){ + pIter->flags |= FTS5_SEGITER_REVERSE; + fts5SegIterReverseInitPage(p, pIter); + }else{ + fts5SegIterLoadNPos(p, pIter); + } + } + + fts5SegIterSetNext(p, pIter); +} + +/* +** Array ap[] contains n elements. Release each of these elements using +** fts5DataRelease(). Then free the array itself using sqlite3_free(). +*/ +static void fts5IndexFreeArray(Fts5Data **ap, int n){ + if( ap ){ + int ii; + for(ii=0; ii<n; ii++){ + fts5DataRelease(ap[ii]); + } + sqlite3_free(ap); + } +} + +/* +** Zero the iterator passed as the only argument. +*/ +static void fts5SegIterClear(Fts5SegIter *pIter){ + fts5BufferFree(&pIter->term); + fts5DataRelease(pIter->pLeaf); + fts5DataRelease(pIter->pNextLeaf); + fts5IndexFreeArray(pIter->apTombstone, pIter->nTombstone); + fts5DlidxIterFree(pIter->pDlidx); + sqlite3_free(pIter->aRowidOffset); + memset(pIter, 0, sizeof(Fts5SegIter)); +} + +#ifdef SQLITE_DEBUG + +/* +** This function is used as part of the big assert() procedure implemented by +** fts5AssertMultiIterSetup(). It ensures that the result currently stored +** in *pRes is the correct result of comparing the current positions of the +** two iterators. +*/ +static void fts5AssertComparisonResult( + Fts5Iter *pIter, + Fts5SegIter *p1, + Fts5SegIter *p2, + Fts5CResult *pRes +){ + int i1 = p1 - pIter->aSeg; + int i2 = p2 - pIter->aSeg; + + if( p1->pLeaf || p2->pLeaf ){ + if( p1->pLeaf==0 ){ + assert( pRes->iFirst==i2 ); + }else if( p2->pLeaf==0 ){ + assert( pRes->iFirst==i1 ); + }else{ + int nMin = MIN(p1->term.n, p2->term.n); + int res = fts5Memcmp(p1->term.p, p2->term.p, nMin); + if( res==0 ) res = p1->term.n - p2->term.n; + + if( res==0 ){ + assert( pRes->bTermEq==1 ); + assert( p1->iRowid!=p2->iRowid ); + res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : 1; + }else{ + assert( pRes->bTermEq==0 ); + } + + if( res<0 ){ + assert( pRes->iFirst==i1 ); + }else{ + assert( pRes->iFirst==i2 ); + } + } + } +} + +/* +** This function is a no-op unless SQLITE_DEBUG is defined when this module +** is compiled. In that case, this function is essentially an assert() +** statement used to verify that the contents of the pIter->aFirst[] array +** are correct. +*/ +static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5Iter *pIter){ + if( p->rc==SQLITE_OK ){ + Fts5SegIter *pFirst = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; + int i; + + assert( (pFirst->pLeaf==0)==pIter->base.bEof ); + + /* Check that pIter->iSwitchRowid is set correctly. */ + for(i=0; i<pIter->nSeg; i++){ + Fts5SegIter *p1 = &pIter->aSeg[i]; + assert( p1==pFirst + || p1->pLeaf==0 + || fts5BufferCompare(&pFirst->term, &p1->term) + || p1->iRowid==pIter->iSwitchRowid + || (p1->iRowid<pIter->iSwitchRowid)==pIter->bRev + ); + } + + for(i=0; i<pIter->nSeg; i+=2){ + Fts5SegIter *p1 = &pIter->aSeg[i]; + Fts5SegIter *p2 = &pIter->aSeg[i+1]; + Fts5CResult *pRes = &pIter->aFirst[(pIter->nSeg + i) / 2]; + fts5AssertComparisonResult(pIter, p1, p2, pRes); + } + + for(i=1; i<(pIter->nSeg / 2); i+=2){ + Fts5SegIter *p1 = &pIter->aSeg[ pIter->aFirst[i*2].iFirst ]; + Fts5SegIter *p2 = &pIter->aSeg[ pIter->aFirst[i*2+1].iFirst ]; + Fts5CResult *pRes = &pIter->aFirst[i]; + fts5AssertComparisonResult(pIter, p1, p2, pRes); + } + } +} +#else +# define fts5AssertMultiIterSetup(x,y) +#endif + +/* +** Do the comparison necessary to populate pIter->aFirst[iOut]. +** +** If the returned value is non-zero, then it is the index of an entry +** in the pIter->aSeg[] array that is (a) not at EOF, and (b) pointing +** to a key that is a duplicate of another, higher priority, +** segment-iterator in the pSeg->aSeg[] array. +*/ +static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){ + int i1; /* Index of left-hand Fts5SegIter */ + int i2; /* Index of right-hand Fts5SegIter */ + int iRes; + Fts5SegIter *p1; /* Left-hand Fts5SegIter */ + Fts5SegIter *p2; /* Right-hand Fts5SegIter */ + Fts5CResult *pRes = &pIter->aFirst[iOut]; + + assert( iOut<pIter->nSeg && iOut>0 ); + assert( pIter->bRev==0 || pIter->bRev==1 ); + + if( iOut>=(pIter->nSeg/2) ){ + i1 = (iOut - pIter->nSeg/2) * 2; + i2 = i1 + 1; + }else{ + i1 = pIter->aFirst[iOut*2].iFirst; + i2 = pIter->aFirst[iOut*2+1].iFirst; + } + p1 = &pIter->aSeg[i1]; + p2 = &pIter->aSeg[i2]; + + pRes->bTermEq = 0; + if( p1->pLeaf==0 ){ /* If p1 is at EOF */ + iRes = i2; + }else if( p2->pLeaf==0 ){ /* If p2 is at EOF */ + iRes = i1; + }else{ + int res = fts5BufferCompare(&p1->term, &p2->term); + if( res==0 ){ + assert_nc( i2>i1 ); + assert_nc( i2!=0 ); + pRes->bTermEq = 1; + if( p1->iRowid==p2->iRowid ){ + p1->bDel = p2->bDel; + return i2; + } + res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : +1; + } + assert( res!=0 ); + if( res<0 ){ + iRes = i1; + }else{ + iRes = i2; + } + } + + pRes->iFirst = (u16)iRes; + return 0; +} + +/* +** Move the seg-iter so that it points to the first rowid on page iLeafPgno. +** It is an error if leaf iLeafPgno does not exist. Unless the db is +** a 'secure-delete' db, if it contains no rowids then this is also an error. +*/ +static void fts5SegIterGotoPage( + Fts5Index *p, /* FTS5 backend object */ + Fts5SegIter *pIter, /* Iterator to advance */ + int iLeafPgno +){ + assert( iLeafPgno>pIter->iLeafPgno ); + + if( iLeafPgno>pIter->pSeg->pgnoLast ){ + p->rc = FTS5_CORRUPT; + }else{ + fts5DataRelease(pIter->pNextLeaf); + pIter->pNextLeaf = 0; + pIter->iLeafPgno = iLeafPgno-1; + + while( p->rc==SQLITE_OK ){ + int iOff; + fts5SegIterNextPage(p, pIter); + if( pIter->pLeaf==0 ) break; + iOff = fts5LeafFirstRowidOff(pIter->pLeaf); + if( iOff>0 ){ + u8 *a = pIter->pLeaf->p; + int n = pIter->pLeaf->szLeaf; + if( iOff<4 || iOff>=n ){ + p->rc = FTS5_CORRUPT; + }else{ + iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid); + pIter->iLeafOffset = iOff; + fts5SegIterLoadNPos(p, pIter); + } + break; + } + } + } +} + +/* +** Advance the iterator passed as the second argument until it is at or +** past rowid iFrom. Regardless of the value of iFrom, the iterator is +** always advanced at least once. +*/ +static void fts5SegIterNextFrom( + Fts5Index *p, /* FTS5 backend object */ + Fts5SegIter *pIter, /* Iterator to advance */ + i64 iMatch /* Advance iterator at least this far */ +){ + int bRev = (pIter->flags & FTS5_SEGITER_REVERSE); + Fts5DlidxIter *pDlidx = pIter->pDlidx; + int iLeafPgno = pIter->iLeafPgno; + int bMove = 1; + + assert( pIter->flags & FTS5_SEGITER_ONETERM ); + assert( pIter->pDlidx ); + assert( pIter->pLeaf ); + + if( bRev==0 ){ + while( !fts5DlidxIterEof(p, pDlidx) && iMatch>fts5DlidxIterRowid(pDlidx) ){ + iLeafPgno = fts5DlidxIterPgno(pDlidx); + fts5DlidxIterNext(p, pDlidx); + } + assert_nc( iLeafPgno>=pIter->iLeafPgno || p->rc ); + if( iLeafPgno>pIter->iLeafPgno ){ + fts5SegIterGotoPage(p, pIter, iLeafPgno); + bMove = 0; + } + }else{ + assert( pIter->pNextLeaf==0 ); + assert( iMatch<pIter->iRowid ); + while( !fts5DlidxIterEof(p, pDlidx) && iMatch<fts5DlidxIterRowid(pDlidx) ){ + fts5DlidxIterPrev(p, pDlidx); + } + iLeafPgno = fts5DlidxIterPgno(pDlidx); + + assert( fts5DlidxIterEof(p, pDlidx) || iLeafPgno<=pIter->iLeafPgno ); + + if( iLeafPgno<pIter->iLeafPgno ){ + pIter->iLeafPgno = iLeafPgno+1; + fts5SegIterReverseNewPage(p, pIter); + bMove = 0; + } + } + + do{ + if( bMove && p->rc==SQLITE_OK ) pIter->xNext(p, pIter, 0); + if( pIter->pLeaf==0 ) break; + if( bRev==0 && pIter->iRowid>=iMatch ) break; + if( bRev!=0 && pIter->iRowid<=iMatch ) break; + bMove = 1; + }while( p->rc==SQLITE_OK ); +} + + +/* +** Free the iterator object passed as the second argument. +*/ +static void fts5MultiIterFree(Fts5Iter *pIter){ + if( pIter ){ + int i; + for(i=0; i<pIter->nSeg; i++){ + fts5SegIterClear(&pIter->aSeg[i]); + } + fts5BufferFree(&pIter->poslist); + sqlite3_free(pIter); + } +} + +static void fts5MultiIterAdvanced( + Fts5Index *p, /* FTS5 backend to iterate within */ + Fts5Iter *pIter, /* Iterator to update aFirst[] array for */ + int iChanged, /* Index of sub-iterator just advanced */ + int iMinset /* Minimum entry in aFirst[] to set */ +){ + int i; + for(i=(pIter->nSeg+iChanged)/2; i>=iMinset && p->rc==SQLITE_OK; i=i/2){ + int iEq; + if( (iEq = fts5MultiIterDoCompare(pIter, i)) ){ + Fts5SegIter *pSeg = &pIter->aSeg[iEq]; + assert( p->rc==SQLITE_OK ); + pSeg->xNext(p, pSeg, 0); + i = pIter->nSeg + iEq; + } + } +} + +/* +** Sub-iterator iChanged of iterator pIter has just been advanced. It still +** points to the same term though - just a different rowid. This function +** attempts to update the contents of the pIter->aFirst[] accordingly. +** If it does so successfully, 0 is returned. Otherwise 1. +** +** If non-zero is returned, the caller should call fts5MultiIterAdvanced() +** on the iterator instead. That function does the same as this one, except +** that it deals with more complicated cases as well. +*/ +static int fts5MultiIterAdvanceRowid( + Fts5Iter *pIter, /* Iterator to update aFirst[] array for */ + int iChanged, /* Index of sub-iterator just advanced */ + Fts5SegIter **ppFirst +){ + Fts5SegIter *pNew = &pIter->aSeg[iChanged]; + + if( pNew->iRowid==pIter->iSwitchRowid + || (pNew->iRowid<pIter->iSwitchRowid)==pIter->bRev + ){ + int i; + Fts5SegIter *pOther = &pIter->aSeg[iChanged ^ 0x0001]; + pIter->iSwitchRowid = pIter->bRev ? SMALLEST_INT64 : LARGEST_INT64; + for(i=(pIter->nSeg+iChanged)/2; 1; i=i/2){ + Fts5CResult *pRes = &pIter->aFirst[i]; + + assert( pNew->pLeaf ); + assert( pRes->bTermEq==0 || pOther->pLeaf ); + + if( pRes->bTermEq ){ + if( pNew->iRowid==pOther->iRowid ){ + return 1; + }else if( (pOther->iRowid>pNew->iRowid)==pIter->bRev ){ + pIter->iSwitchRowid = pOther->iRowid; + pNew = pOther; + }else if( (pOther->iRowid>pIter->iSwitchRowid)==pIter->bRev ){ + pIter->iSwitchRowid = pOther->iRowid; + } + } + pRes->iFirst = (u16)(pNew - pIter->aSeg); + if( i==1 ) break; + + pOther = &pIter->aSeg[ pIter->aFirst[i ^ 0x0001].iFirst ]; + } + } + + *ppFirst = pNew; + return 0; +} + +/* +** Set the pIter->bEof variable based on the state of the sub-iterators. +*/ +static void fts5MultiIterSetEof(Fts5Iter *pIter){ + Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; + pIter->base.bEof = pSeg->pLeaf==0; + pIter->iSwitchRowid = pSeg->iRowid; +} + +/* +** The argument to this macro must be an Fts5Data structure containing a +** tombstone hash page. This macro returns the key-size of the hash-page. +*/ +#define TOMBSTONE_KEYSIZE(pPg) (pPg->p[0]==4 ? 4 : 8) + +#define TOMBSTONE_NSLOT(pPg) \ + ((pPg->nn > 16) ? ((pPg->nn-8) / TOMBSTONE_KEYSIZE(pPg)) : 1) + +/* +** Query a single tombstone hash table for rowid iRowid. Return true if +** it is found or false otherwise. The tombstone hash table is one of +** nHashTable tables. +*/ +static int fts5IndexTombstoneQuery( + Fts5Data *pHash, /* Hash table page to query */ + int nHashTable, /* Number of pages attached to segment */ + u64 iRowid /* Rowid to query hash for */ +){ + const int szKey = TOMBSTONE_KEYSIZE(pHash); + const int nSlot = TOMBSTONE_NSLOT(pHash); + int iSlot = (iRowid / nHashTable) % nSlot; + int nCollide = nSlot; + + if( iRowid==0 ){ + return pHash->p[1]; + }else if( szKey==4 ){ + u32 *aSlot = (u32*)&pHash->p[8]; + while( aSlot[iSlot] ){ + if( fts5GetU32((u8*)&aSlot[iSlot])==iRowid ) return 1; + if( nCollide--==0 ) break; + iSlot = (iSlot+1)%nSlot; + } + }else{ + u64 *aSlot = (u64*)&pHash->p[8]; + while( aSlot[iSlot] ){ + if( fts5GetU64((u8*)&aSlot[iSlot])==iRowid ) return 1; + if( nCollide--==0 ) break; + iSlot = (iSlot+1)%nSlot; + } + } + + return 0; +} + +/* +** Return true if the iterator passed as the only argument points +** to an segment entry for which there is a tombstone. Return false +** if there is no tombstone or if the iterator is already at EOF. +*/ +static int fts5MultiIterIsDeleted(Fts5Iter *pIter){ + int iFirst = pIter->aFirst[1].iFirst; + Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; + + if( pSeg->pLeaf && pSeg->nTombstone ){ + /* Figure out which page the rowid might be present on. */ + int iPg = ((u64)pSeg->iRowid) % pSeg->nTombstone; + assert( iPg>=0 ); + + /* If tombstone hash page iPg has not yet been loaded from the + ** database, load it now. */ + if( pSeg->apTombstone[iPg]==0 ){ + pSeg->apTombstone[iPg] = fts5DataRead(pIter->pIndex, + FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg) + ); + if( pSeg->apTombstone[iPg]==0 ) return 0; + } + + return fts5IndexTombstoneQuery( + pSeg->apTombstone[iPg], + pSeg->nTombstone, + pSeg->iRowid + ); + } + + return 0; +} + +/* +** Move the iterator to the next entry. +** +** If an error occurs, an error code is left in Fts5Index.rc. It is not +** considered an error if the iterator reaches EOF, or if it is already at +** EOF when this function is called. +*/ +static void fts5MultiIterNext( + Fts5Index *p, + Fts5Iter *pIter, + int bFrom, /* True if argument iFrom is valid */ + i64 iFrom /* Advance at least as far as this */ +){ + int bUseFrom = bFrom; + assert( pIter->base.bEof==0 ); + while( p->rc==SQLITE_OK ){ + int iFirst = pIter->aFirst[1].iFirst; + int bNewTerm = 0; + Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; + assert( p->rc==SQLITE_OK ); + if( bUseFrom && pSeg->pDlidx ){ + fts5SegIterNextFrom(p, pSeg, iFrom); + }else{ + pSeg->xNext(p, pSeg, &bNewTerm); + } + + if( pSeg->pLeaf==0 || bNewTerm + || fts5MultiIterAdvanceRowid(pIter, iFirst, &pSeg) + ){ + fts5MultiIterAdvanced(p, pIter, iFirst, 1); + fts5MultiIterSetEof(pIter); + pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; + if( pSeg->pLeaf==0 ) return; + } + + fts5AssertMultiIterSetup(p, pIter); + assert( pSeg==&pIter->aSeg[pIter->aFirst[1].iFirst] && pSeg->pLeaf ); + if( (pIter->bSkipEmpty==0 || pSeg->nPos) + && 0==fts5MultiIterIsDeleted(pIter) + ){ + pIter->xSetOutputs(pIter, pSeg); + return; + } + bUseFrom = 0; + } +} + +static void fts5MultiIterNext2( + Fts5Index *p, + Fts5Iter *pIter, + int *pbNewTerm /* OUT: True if *might* be new term */ +){ + assert( pIter->bSkipEmpty ); + if( p->rc==SQLITE_OK ){ + *pbNewTerm = 0; + do{ + int iFirst = pIter->aFirst[1].iFirst; + Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; + int bNewTerm = 0; + + assert( p->rc==SQLITE_OK ); + pSeg->xNext(p, pSeg, &bNewTerm); + if( pSeg->pLeaf==0 || bNewTerm + || fts5MultiIterAdvanceRowid(pIter, iFirst, &pSeg) + ){ + fts5MultiIterAdvanced(p, pIter, iFirst, 1); + fts5MultiIterSetEof(pIter); + *pbNewTerm = 1; + } + fts5AssertMultiIterSetup(p, pIter); + + }while( (fts5MultiIterIsEmpty(p, pIter) || fts5MultiIterIsDeleted(pIter)) + && (p->rc==SQLITE_OK) + ); + } +} + +static void fts5IterSetOutputs_Noop(Fts5Iter *pUnused1, Fts5SegIter *pUnused2){ + UNUSED_PARAM2(pUnused1, pUnused2); +} + +static Fts5Iter *fts5MultiIterAlloc( + Fts5Index *p, /* FTS5 backend to iterate within */ + int nSeg +){ + Fts5Iter *pNew; + i64 nSlot; /* Power of two >= nSeg */ + + for(nSlot=2; nSlot<nSeg; nSlot=nSlot*2); + pNew = fts5IdxMalloc(p, + sizeof(Fts5Iter) + /* pNew */ + sizeof(Fts5SegIter) * (nSlot-1) + /* pNew->aSeg[] */ + sizeof(Fts5CResult) * nSlot /* pNew->aFirst[] */ + ); + if( pNew ){ + pNew->nSeg = nSlot; + pNew->aFirst = (Fts5CResult*)&pNew->aSeg[nSlot]; + pNew->pIndex = p; + pNew->xSetOutputs = fts5IterSetOutputs_Noop; + } + return pNew; +} + +static void fts5PoslistCallback( + Fts5Index *pUnused, + void *pContext, + const u8 *pChunk, int nChunk +){ + UNUSED_PARAM(pUnused); + assert_nc( nChunk>=0 ); + if( nChunk>0 ){ + fts5BufferSafeAppendBlob((Fts5Buffer*)pContext, pChunk, nChunk); + } +} + +typedef struct PoslistCallbackCtx PoslistCallbackCtx; +struct PoslistCallbackCtx { + Fts5Buffer *pBuf; /* Append to this buffer */ + Fts5Colset *pColset; /* Restrict matches to this column */ + int eState; /* See above */ +}; + +typedef struct PoslistOffsetsCtx PoslistOffsetsCtx; +struct PoslistOffsetsCtx { + Fts5Buffer *pBuf; /* Append to this buffer */ + Fts5Colset *pColset; /* Restrict matches to this column */ + int iRead; + int iWrite; +}; + +/* +** TODO: Make this more efficient! +*/ +static int fts5IndexColsetTest(Fts5Colset *pColset, int iCol){ + int i; + for(i=0; i<pColset->nCol; i++){ + if( pColset->aiCol[i]==iCol ) return 1; + } + return 0; +} + +static void fts5PoslistOffsetsCallback( + Fts5Index *pUnused, + void *pContext, + const u8 *pChunk, int nChunk +){ + PoslistOffsetsCtx *pCtx = (PoslistOffsetsCtx*)pContext; + UNUSED_PARAM(pUnused); + assert_nc( nChunk>=0 ); + if( nChunk>0 ){ + int i = 0; + while( i<nChunk ){ + int iVal; + i += fts5GetVarint32(&pChunk[i], iVal); + iVal += pCtx->iRead - 2; + pCtx->iRead = iVal; + if( fts5IndexColsetTest(pCtx->pColset, iVal) ){ + fts5BufferSafeAppendVarint(pCtx->pBuf, iVal + 2 - pCtx->iWrite); + pCtx->iWrite = iVal; + } + } + } +} + +static void fts5PoslistFilterCallback( + Fts5Index *pUnused, + void *pContext, + const u8 *pChunk, int nChunk +){ + PoslistCallbackCtx *pCtx = (PoslistCallbackCtx*)pContext; + UNUSED_PARAM(pUnused); + assert_nc( nChunk>=0 ); + if( nChunk>0 ){ + /* Search through to find the first varint with value 1. This is the + ** start of the next columns hits. */ + int i = 0; + int iStart = 0; + + if( pCtx->eState==2 ){ + int iCol; + fts5FastGetVarint32(pChunk, i, iCol); + if( fts5IndexColsetTest(pCtx->pColset, iCol) ){ + pCtx->eState = 1; + fts5BufferSafeAppendVarint(pCtx->pBuf, 1); + }else{ + pCtx->eState = 0; + } + } + + do { + while( i<nChunk && pChunk[i]!=0x01 ){ + while( pChunk[i] & 0x80 ) i++; + i++; + } + if( pCtx->eState ){ + fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); + } + if( i<nChunk ){ + int iCol; + iStart = i; + i++; + if( i>=nChunk ){ + pCtx->eState = 2; + }else{ + fts5FastGetVarint32(pChunk, i, iCol); + pCtx->eState = fts5IndexColsetTest(pCtx->pColset, iCol); + if( pCtx->eState ){ + fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); + iStart = i; + } + } + } + }while( i<nChunk ); + } +} + +static void fts5ChunkIterate( + Fts5Index *p, /* Index object */ + Fts5SegIter *pSeg, /* Poslist of this iterator */ + void *pCtx, /* Context pointer for xChunk callback */ + void (*xChunk)(Fts5Index*, void*, const u8*, int) +){ + int nRem = pSeg->nPos; /* Number of bytes still to come */ + Fts5Data *pData = 0; + u8 *pChunk = &pSeg->pLeaf->p[pSeg->iLeafOffset]; + int nChunk = MIN(nRem, pSeg->pLeaf->szLeaf - pSeg->iLeafOffset); + int pgno = pSeg->iLeafPgno; + int pgnoSave = 0; + + /* This function does not work with detail=none databases. */ + assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE ); + + if( (pSeg->flags & FTS5_SEGITER_REVERSE)==0 ){ + pgnoSave = pgno+1; + } + + while( 1 ){ + xChunk(p, pCtx, pChunk, nChunk); + nRem -= nChunk; + fts5DataRelease(pData); + if( nRem<=0 ){ + break; + }else if( pSeg->pSeg==0 ){ + p->rc = FTS5_CORRUPT; + return; + }else{ + pgno++; + pData = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno)); + if( pData==0 ) break; + pChunk = &pData->p[4]; + nChunk = MIN(nRem, pData->szLeaf - 4); + if( pgno==pgnoSave ){ + assert( pSeg->pNextLeaf==0 ); + pSeg->pNextLeaf = pData; + pData = 0; + } + } + } +} + +/* +** Iterator pIter currently points to a valid entry (not EOF). This +** function appends the position list data for the current entry to +** buffer pBuf. It does not make a copy of the position-list size +** field. +*/ +static void fts5SegiterPoslist( + Fts5Index *p, + Fts5SegIter *pSeg, + Fts5Colset *pColset, + Fts5Buffer *pBuf +){ + assert( pBuf!=0 ); + assert( pSeg!=0 ); + if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos+FTS5_DATA_ZERO_PADDING) ){ + assert( pBuf->p!=0 ); + assert( pBuf->nSpace >= pBuf->n+pSeg->nPos+FTS5_DATA_ZERO_PADDING ); + memset(&pBuf->p[pBuf->n+pSeg->nPos], 0, FTS5_DATA_ZERO_PADDING); + if( pColset==0 ){ + fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback); + }else{ + if( p->pConfig->eDetail==FTS5_DETAIL_FULL ){ + PoslistCallbackCtx sCtx; + sCtx.pBuf = pBuf; + sCtx.pColset = pColset; + sCtx.eState = fts5IndexColsetTest(pColset, 0); + assert( sCtx.eState==0 || sCtx.eState==1 ); + fts5ChunkIterate(p, pSeg, (void*)&sCtx, fts5PoslistFilterCallback); + }else{ + PoslistOffsetsCtx sCtx; + memset(&sCtx, 0, sizeof(sCtx)); + sCtx.pBuf = pBuf; + sCtx.pColset = pColset; + fts5ChunkIterate(p, pSeg, (void*)&sCtx, fts5PoslistOffsetsCallback); + } + } + } +} + +/* +** Parameter pPos points to a buffer containing a position list, size nPos. +** This function filters it according to pColset (which must be non-NULL) +** and sets pIter->base.pData/nData to point to the new position list. +** If memory is required for the new position list, use buffer pIter->poslist. +** Or, if the new position list is a contiguous subset of the input, set +** pIter->base.pData/nData to point directly to it. +** +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. If an OOM error is encountered, *pRc is set to SQLITE_NOMEM +** before returning. +*/ +static void fts5IndexExtractColset( + int *pRc, + Fts5Colset *pColset, /* Colset to filter on */ + const u8 *pPos, int nPos, /* Position list */ + Fts5Iter *pIter +){ + if( *pRc==SQLITE_OK ){ + const u8 *p = pPos; + const u8 *aCopy = p; + const u8 *pEnd = &p[nPos]; /* One byte past end of position list */ + int i = 0; + int iCurrent = 0; + + if( pColset->nCol>1 && sqlite3Fts5BufferSize(pRc, &pIter->poslist, nPos) ){ + return; + } + + while( 1 ){ + while( pColset->aiCol[i]<iCurrent ){ + i++; + if( i==pColset->nCol ){ + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + return; + } + } + + /* Advance pointer p until it points to pEnd or an 0x01 byte that is + ** not part of a varint */ + while( p<pEnd && *p!=0x01 ){ + while( *p++ & 0x80 ); + } + + if( pColset->aiCol[i]==iCurrent ){ + if( pColset->nCol==1 ){ + pIter->base.pData = aCopy; + pIter->base.nData = p-aCopy; + return; + } + fts5BufferSafeAppendBlob(&pIter->poslist, aCopy, p-aCopy); + } + if( p>=pEnd ){ + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + return; + } + aCopy = p++; + iCurrent = *p++; + if( iCurrent & 0x80 ){ + p--; + p += fts5GetVarint32(p, iCurrent); + } + } + } + +} + +/* +** xSetOutputs callback used by detail=none tables. +*/ +static void fts5IterSetOutputs_None(Fts5Iter *pIter, Fts5SegIter *pSeg){ + assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_NONE ); + pIter->base.iRowid = pSeg->iRowid; + pIter->base.nData = pSeg->nPos; +} + +/* +** xSetOutputs callback used by detail=full and detail=col tables when no +** column filters are specified. +*/ +static void fts5IterSetOutputs_Nocolset(Fts5Iter *pIter, Fts5SegIter *pSeg){ + pIter->base.iRowid = pSeg->iRowid; + pIter->base.nData = pSeg->nPos; + + assert( pIter->pIndex->pConfig->eDetail!=FTS5_DETAIL_NONE ); + assert( pIter->pColset==0 ); + + if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){ + /* All data is stored on the current page. Populate the output + ** variables to point into the body of the page object. */ + pIter->base.pData = &pSeg->pLeaf->p[pSeg->iLeafOffset]; + }else{ + /* The data is distributed over two or more pages. Copy it into the + ** Fts5Iter.poslist buffer and then set the output pointer to point + ** to this buffer. */ + fts5BufferZero(&pIter->poslist); + fts5SegiterPoslist(pIter->pIndex, pSeg, 0, &pIter->poslist); + pIter->base.pData = pIter->poslist.p; + } +} + +/* +** xSetOutputs callback used when the Fts5Colset object has nCol==0 (match +** against no columns at all). +*/ +static void fts5IterSetOutputs_ZeroColset(Fts5Iter *pIter, Fts5SegIter *pSeg){ + UNUSED_PARAM(pSeg); + pIter->base.nData = 0; +} + +/* +** xSetOutputs callback used by detail=col when there is a column filter +** and there are 100 or more columns. Also called as a fallback from +** fts5IterSetOutputs_Col100 if the column-list spans more than one page. +*/ +static void fts5IterSetOutputs_Col(Fts5Iter *pIter, Fts5SegIter *pSeg){ + fts5BufferZero(&pIter->poslist); + fts5SegiterPoslist(pIter->pIndex, pSeg, pIter->pColset, &pIter->poslist); + pIter->base.iRowid = pSeg->iRowid; + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; +} + +/* +** xSetOutputs callback used when: +** +** * detail=col, +** * there is a column filter, and +** * the table contains 100 or fewer columns. +** +** The last point is to ensure all column numbers are stored as +** single-byte varints. +*/ +static void fts5IterSetOutputs_Col100(Fts5Iter *pIter, Fts5SegIter *pSeg){ + + assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_COLUMNS ); + assert( pIter->pColset ); + + if( pSeg->iLeafOffset+pSeg->nPos>pSeg->pLeaf->szLeaf ){ + fts5IterSetOutputs_Col(pIter, pSeg); + }else{ + u8 *a = (u8*)&pSeg->pLeaf->p[pSeg->iLeafOffset]; + u8 *pEnd = (u8*)&a[pSeg->nPos]; + int iPrev = 0; + int *aiCol = pIter->pColset->aiCol; + int *aiColEnd = &aiCol[pIter->pColset->nCol]; + + u8 *aOut = pIter->poslist.p; + int iPrevOut = 0; + + pIter->base.iRowid = pSeg->iRowid; + + while( a<pEnd ){ + iPrev += (int)a++[0] - 2; + while( *aiCol<iPrev ){ + aiCol++; + if( aiCol==aiColEnd ) goto setoutputs_col_out; + } + if( *aiCol==iPrev ){ + *aOut++ = (u8)((iPrev - iPrevOut) + 2); + iPrevOut = iPrev; + } + } + +setoutputs_col_out: + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = aOut - pIter->poslist.p; + } +} + +/* +** xSetOutputs callback used by detail=full when there is a column filter. +*/ +static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){ + Fts5Colset *pColset = pIter->pColset; + pIter->base.iRowid = pSeg->iRowid; + + assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_FULL ); + assert( pColset ); + + if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){ + /* All data is stored on the current page. Populate the output + ** variables to point into the body of the page object. */ + const u8 *a = &pSeg->pLeaf->p[pSeg->iLeafOffset]; + int *pRc = &pIter->pIndex->rc; + fts5BufferZero(&pIter->poslist); + fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, pIter); + }else{ + /* The data is distributed over two or more pages. Copy it into the + ** Fts5Iter.poslist buffer and then set the output pointer to point + ** to this buffer. */ + fts5BufferZero(&pIter->poslist); + fts5SegiterPoslist(pIter->pIndex, pSeg, pColset, &pIter->poslist); + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + } +} + +static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){ + assert( pIter!=0 || (*pRc)!=SQLITE_OK ); + if( *pRc==SQLITE_OK ){ + Fts5Config *pConfig = pIter->pIndex->pConfig; + if( pConfig->eDetail==FTS5_DETAIL_NONE ){ + pIter->xSetOutputs = fts5IterSetOutputs_None; + } + + else if( pIter->pColset==0 ){ + pIter->xSetOutputs = fts5IterSetOutputs_Nocolset; + } + + else if( pIter->pColset->nCol==0 ){ + pIter->xSetOutputs = fts5IterSetOutputs_ZeroColset; + } + + else if( pConfig->eDetail==FTS5_DETAIL_FULL ){ + pIter->xSetOutputs = fts5IterSetOutputs_Full; + } + + else{ + assert( pConfig->eDetail==FTS5_DETAIL_COLUMNS ); + if( pConfig->nCol<=100 ){ + pIter->xSetOutputs = fts5IterSetOutputs_Col100; + sqlite3Fts5BufferSize(pRc, &pIter->poslist, pConfig->nCol); + }else{ + pIter->xSetOutputs = fts5IterSetOutputs_Col; + } + } + } +} + + +/* +** Allocate a new Fts5Iter object. +** +** The new object will be used to iterate through data in structure pStruct. +** If iLevel is -ve, then all data in all segments is merged. Or, if iLevel +** is zero or greater, data from the first nSegment segments on level iLevel +** is merged. +** +** The iterator initially points to the first term/rowid entry in the +** iterated data. +*/ +static void fts5MultiIterNew( + Fts5Index *p, /* FTS5 backend to iterate within */ + Fts5Structure *pStruct, /* Structure of specific index */ + int flags, /* FTS5INDEX_QUERY_XXX flags */ + Fts5Colset *pColset, /* Colset to filter on (or NULL) */ + const u8 *pTerm, int nTerm, /* Term to seek to (or NULL/0) */ + int iLevel, /* Level to iterate (-1 for all) */ + int nSegment, /* Number of segments to merge (iLevel>=0) */ + Fts5Iter **ppOut /* New object */ +){ + int nSeg = 0; /* Number of segment-iters in use */ + int iIter = 0; /* */ + int iSeg; /* Used to iterate through segments */ + Fts5StructureLevel *pLvl; + Fts5Iter *pNew; + + assert( (pTerm==0 && nTerm==0) || iLevel<0 ); + + /* Allocate space for the new multi-seg-iterator. */ + if( p->rc==SQLITE_OK ){ + if( iLevel<0 ){ + assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); + nSeg = pStruct->nSegment; + nSeg += (p->pHash && 0==(flags & FTS5INDEX_QUERY_SKIPHASH)); + }else{ + nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment); + } + } + *ppOut = pNew = fts5MultiIterAlloc(p, nSeg); + if( pNew==0 ){ + assert( p->rc!=SQLITE_OK ); + goto fts5MultiIterNew_post_check; + } + pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC)); + pNew->bSkipEmpty = (0!=(flags & FTS5INDEX_QUERY_SKIPEMPTY)); + pNew->pColset = pColset; + if( (flags & FTS5INDEX_QUERY_NOOUTPUT)==0 ){ + fts5IterSetOutputCb(&p->rc, pNew); + } + + /* Initialize each of the component segment iterators. */ + if( p->rc==SQLITE_OK ){ + if( iLevel<0 ){ + Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel]; + if( p->pHash && 0==(flags & FTS5INDEX_QUERY_SKIPHASH) ){ + /* Add a segment iterator for the current contents of the hash table. */ + Fts5SegIter *pIter = &pNew->aSeg[iIter++]; + fts5SegIterHashInit(p, pTerm, nTerm, flags, pIter); + } + for(pLvl=&pStruct->aLevel[0]; pLvl<pEnd; pLvl++){ + for(iSeg=pLvl->nSeg-1; iSeg>=0; iSeg--){ + Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; + Fts5SegIter *pIter = &pNew->aSeg[iIter++]; + if( pTerm==0 ){ + fts5SegIterInit(p, pSeg, pIter); + }else{ + fts5SegIterSeekInit(p, pTerm, nTerm, flags, pSeg, pIter); + } + } + } + }else{ + pLvl = &pStruct->aLevel[iLevel]; + for(iSeg=nSeg-1; iSeg>=0; iSeg--){ + fts5SegIterInit(p, &pLvl->aSeg[iSeg], &pNew->aSeg[iIter++]); + } + } + assert( iIter==nSeg ); + } + + /* If the above was successful, each component iterators now points + ** to the first entry in its segment. In this case initialize the + ** aFirst[] array. Or, if an error has occurred, free the iterator + ** object and set the output variable to NULL. */ + if( p->rc==SQLITE_OK ){ + for(iIter=pNew->nSeg-1; iIter>0; iIter--){ + int iEq; + if( (iEq = fts5MultiIterDoCompare(pNew, iIter)) ){ + Fts5SegIter *pSeg = &pNew->aSeg[iEq]; + if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); + fts5MultiIterAdvanced(p, pNew, iEq, iIter); + } + } + fts5MultiIterSetEof(pNew); + fts5AssertMultiIterSetup(p, pNew); + + if( (pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew)) + || fts5MultiIterIsDeleted(pNew) + ){ + fts5MultiIterNext(p, pNew, 0, 0); + }else if( pNew->base.bEof==0 ){ + Fts5SegIter *pSeg = &pNew->aSeg[pNew->aFirst[1].iFirst]; + pNew->xSetOutputs(pNew, pSeg); + } + + }else{ + fts5MultiIterFree(pNew); + *ppOut = 0; + } + +fts5MultiIterNew_post_check: + assert( (*ppOut)!=0 || p->rc!=SQLITE_OK ); + return; +} + +/* +** Create an Fts5Iter that iterates through the doclist provided +** as the second argument. +*/ +static void fts5MultiIterNew2( + Fts5Index *p, /* FTS5 backend to iterate within */ + Fts5Data *pData, /* Doclist to iterate through */ + int bDesc, /* True for descending rowid order */ + Fts5Iter **ppOut /* New object */ +){ + Fts5Iter *pNew; + pNew = fts5MultiIterAlloc(p, 2); + if( pNew ){ + Fts5SegIter *pIter = &pNew->aSeg[1]; + + pIter->flags = FTS5_SEGITER_ONETERM; + if( pData->szLeaf>0 ){ + pIter->pLeaf = pData; + pIter->iLeafOffset = fts5GetVarint(pData->p, (u64*)&pIter->iRowid); + pIter->iEndofDoclist = pData->nn; + pNew->aFirst[1].iFirst = 1; + if( bDesc ){ + pNew->bRev = 1; + pIter->flags |= FTS5_SEGITER_REVERSE; + fts5SegIterReverseInitPage(p, pIter); + }else{ + fts5SegIterLoadNPos(p, pIter); + } + pData = 0; + }else{ + pNew->base.bEof = 1; + } + fts5SegIterSetNext(p, pIter); + + *ppOut = pNew; + } + + fts5DataRelease(pData); +} + +/* +** Return true if the iterator is at EOF or if an error has occurred. +** False otherwise. +*/ +static int fts5MultiIterEof(Fts5Index *p, Fts5Iter *pIter){ + assert( pIter!=0 || p->rc!=SQLITE_OK ); + assert( p->rc!=SQLITE_OK + || (pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0)==pIter->base.bEof + ); + return (p->rc || pIter->base.bEof); +} + +/* +** Return the rowid of the entry that the iterator currently points +** to. If the iterator points to EOF when this function is called the +** results are undefined. +*/ +static i64 fts5MultiIterRowid(Fts5Iter *pIter){ + assert( pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf ); + return pIter->aSeg[ pIter->aFirst[1].iFirst ].iRowid; +} + +/* +** Move the iterator to the next entry at or following iMatch. +*/ +static void fts5MultiIterNextFrom( + Fts5Index *p, + Fts5Iter *pIter, + i64 iMatch +){ + while( 1 ){ + i64 iRowid; + fts5MultiIterNext(p, pIter, 1, iMatch); + if( fts5MultiIterEof(p, pIter) ) break; + iRowid = fts5MultiIterRowid(pIter); + if( pIter->bRev==0 && iRowid>=iMatch ) break; + if( pIter->bRev!=0 && iRowid<=iMatch ) break; + } +} + +/* +** Return a pointer to a buffer containing the term associated with the +** entry that the iterator currently points to. +*/ +static const u8 *fts5MultiIterTerm(Fts5Iter *pIter, int *pn){ + Fts5SegIter *p = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; + *pn = p->term.n; + return p->term.p; +} + +/* +** Allocate a new segment-id for the structure pStruct. The new segment +** id must be between 1 and 65335 inclusive, and must not be used by +** any currently existing segment. If a free segment id cannot be found, +** SQLITE_FULL is returned. +** +** If an error has already occurred, this function is a no-op. 0 is +** returned in this case. +*/ +static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){ + int iSegid = 0; + + if( p->rc==SQLITE_OK ){ + if( pStruct->nSegment>=FTS5_MAX_SEGMENT ){ + p->rc = SQLITE_FULL; + }else{ + /* FTS5_MAX_SEGMENT is currently defined as 2000. So the following + ** array is 63 elements, or 252 bytes, in size. */ + u32 aUsed[(FTS5_MAX_SEGMENT+31) / 32]; + int iLvl, iSeg; + int i; + u32 mask; + memset(aUsed, 0, sizeof(aUsed)); + for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ + for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){ + int iId = pStruct->aLevel[iLvl].aSeg[iSeg].iSegid; + if( iId<=FTS5_MAX_SEGMENT && iId>0 ){ + aUsed[(iId-1) / 32] |= (u32)1 << ((iId-1) % 32); + } + } + } + + for(i=0; aUsed[i]==0xFFFFFFFF; i++); + mask = aUsed[i]; + for(iSegid=0; mask & ((u32)1 << iSegid); iSegid++); + iSegid += 1 + i*32; + +#ifdef SQLITE_DEBUG + for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ + for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){ + assert_nc( iSegid!=pStruct->aLevel[iLvl].aSeg[iSeg].iSegid ); + } + } + assert_nc( iSegid>0 && iSegid<=FTS5_MAX_SEGMENT ); + + { + sqlite3_stmt *pIdxSelect = fts5IdxSelectStmt(p); + if( p->rc==SQLITE_OK ){ + u8 aBlob[2] = {0xff, 0xff}; + sqlite3_bind_int(pIdxSelect, 1, iSegid); + sqlite3_bind_blob(pIdxSelect, 2, aBlob, 2, SQLITE_STATIC); + assert_nc( sqlite3_step(pIdxSelect)!=SQLITE_ROW ); + p->rc = sqlite3_reset(pIdxSelect); + sqlite3_bind_null(pIdxSelect, 2); + } + } +#endif + } + } + + return iSegid; +} + +/* +** Discard all data currently cached in the hash-tables. +*/ +static void fts5IndexDiscardData(Fts5Index *p){ + assert( p->pHash || p->nPendingData==0 ); + if( p->pHash ){ + sqlite3Fts5HashClear(p->pHash); + p->nPendingData = 0; + p->nPendingRow = 0; + } + p->nContentlessDelete = 0; +} + +/* +** Return the size of the prefix, in bytes, that buffer +** (pNew/<length-unknown>) shares with buffer (pOld/nOld). +** +** Buffer (pNew/<length-unknown>) is guaranteed to be greater +** than buffer (pOld/nOld). +*/ +static int fts5PrefixCompress(int nOld, const u8 *pOld, const u8 *pNew){ + int i; + for(i=0; i<nOld; i++){ + if( pOld[i]!=pNew[i] ) break; + } + return i; +} + +static void fts5WriteDlidxClear( + Fts5Index *p, + Fts5SegWriter *pWriter, + int bFlush /* If true, write dlidx to disk */ +){ + int i; + assert( bFlush==0 || (pWriter->nDlidx>0 && pWriter->aDlidx[0].buf.n>0) ); + for(i=0; i<pWriter->nDlidx; i++){ + Fts5DlidxWriter *pDlidx = &pWriter->aDlidx[i]; + if( pDlidx->buf.n==0 ) break; + if( bFlush ){ + assert( pDlidx->pgno!=0 ); + fts5DataWrite(p, + FTS5_DLIDX_ROWID(pWriter->iSegid, i, pDlidx->pgno), + pDlidx->buf.p, pDlidx->buf.n + ); + } + sqlite3Fts5BufferZero(&pDlidx->buf); + pDlidx->bPrevValid = 0; + } +} + +/* +** Grow the pWriter->aDlidx[] array to at least nLvl elements in size. +** Any new array elements are zeroed before returning. +*/ +static int fts5WriteDlidxGrow( + Fts5Index *p, + Fts5SegWriter *pWriter, + int nLvl +){ + if( p->rc==SQLITE_OK && nLvl>=pWriter->nDlidx ){ + Fts5DlidxWriter *aDlidx = (Fts5DlidxWriter*)sqlite3_realloc64( + pWriter->aDlidx, sizeof(Fts5DlidxWriter) * nLvl + ); + if( aDlidx==0 ){ + p->rc = SQLITE_NOMEM; + }else{ + size_t nByte = sizeof(Fts5DlidxWriter) * (nLvl - pWriter->nDlidx); + memset(&aDlidx[pWriter->nDlidx], 0, nByte); + pWriter->aDlidx = aDlidx; + pWriter->nDlidx = nLvl; + } + } + return p->rc; +} + +/* +** If the current doclist-index accumulating in pWriter->aDlidx[] is large +** enough, flush it to disk and return 1. Otherwise discard it and return +** zero. +*/ +static int fts5WriteFlushDlidx(Fts5Index *p, Fts5SegWriter *pWriter){ + int bFlag = 0; + + /* If there were FTS5_MIN_DLIDX_SIZE or more empty leaf pages written + ** to the database, also write the doclist-index to disk. */ + if( pWriter->aDlidx[0].buf.n>0 && pWriter->nEmpty>=FTS5_MIN_DLIDX_SIZE ){ + bFlag = 1; + } + fts5WriteDlidxClear(p, pWriter, bFlag); + pWriter->nEmpty = 0; + return bFlag; +} + +/* +** This function is called whenever processing of the doclist for the +** last term on leaf page (pWriter->iBtPage) is completed. +** +** The doclist-index for that term is currently stored in-memory within the +** Fts5SegWriter.aDlidx[] array. If it is large enough, this function +** writes it out to disk. Or, if it is too small to bother with, discards +** it. +** +** Fts5SegWriter.btterm currently contains the first term on page iBtPage. +*/ +static void fts5WriteFlushBtree(Fts5Index *p, Fts5SegWriter *pWriter){ + int bFlag; + + assert( pWriter->iBtPage || pWriter->nEmpty==0 ); + if( pWriter->iBtPage==0 ) return; + bFlag = fts5WriteFlushDlidx(p, pWriter); + + if( p->rc==SQLITE_OK ){ + const char *z = (pWriter->btterm.n>0?(const char*)pWriter->btterm.p:""); + /* The following was already done in fts5WriteInit(): */ + /* sqlite3_bind_int(p->pIdxWriter, 1, pWriter->iSegid); */ + sqlite3_bind_blob(p->pIdxWriter, 2, z, pWriter->btterm.n, SQLITE_STATIC); + sqlite3_bind_int64(p->pIdxWriter, 3, bFlag + ((i64)pWriter->iBtPage<<1)); + sqlite3_step(p->pIdxWriter); + p->rc = sqlite3_reset(p->pIdxWriter); + sqlite3_bind_null(p->pIdxWriter, 2); + } + pWriter->iBtPage = 0; +} + +/* +** This is called once for each leaf page except the first that contains +** at least one term. Argument (nTerm/pTerm) is the split-key - a term that +** is larger than all terms written to earlier leaves, and equal to or +** smaller than the first term on the new leaf. +** +** If an error occurs, an error code is left in Fts5Index.rc. If an error +** has already occurred when this function is called, it is a no-op. +*/ +static void fts5WriteBtreeTerm( + Fts5Index *p, /* FTS5 backend object */ + Fts5SegWriter *pWriter, /* Writer object */ + int nTerm, const u8 *pTerm /* First term on new page */ +){ + fts5WriteFlushBtree(p, pWriter); + if( p->rc==SQLITE_OK ){ + fts5BufferSet(&p->rc, &pWriter->btterm, nTerm, pTerm); + pWriter->iBtPage = pWriter->writer.pgno; + } +} + +/* +** This function is called when flushing a leaf page that contains no +** terms at all to disk. +*/ +static void fts5WriteBtreeNoTerm( + Fts5Index *p, /* FTS5 backend object */ + Fts5SegWriter *pWriter /* Writer object */ +){ + /* If there were no rowids on the leaf page either and the doclist-index + ** has already been started, append an 0x00 byte to it. */ + if( pWriter->bFirstRowidInPage && pWriter->aDlidx[0].buf.n>0 ){ + Fts5DlidxWriter *pDlidx = &pWriter->aDlidx[0]; + assert( pDlidx->bPrevValid ); + sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, 0); + } + + /* Increment the "number of sequential leaves without a term" counter. */ + pWriter->nEmpty++; +} + +static i64 fts5DlidxExtractFirstRowid(Fts5Buffer *pBuf){ + i64 iRowid; + int iOff; + + iOff = 1 + fts5GetVarint(&pBuf->p[1], (u64*)&iRowid); + fts5GetVarint(&pBuf->p[iOff], (u64*)&iRowid); + return iRowid; +} + +/* +** Rowid iRowid has just been appended to the current leaf page. It is the +** first on the page. This function appends an appropriate entry to the current +** doclist-index. +*/ +static void fts5WriteDlidxAppend( + Fts5Index *p, + Fts5SegWriter *pWriter, + i64 iRowid +){ + int i; + int bDone = 0; + + for(i=0; p->rc==SQLITE_OK && bDone==0; i++){ + i64 iVal; + Fts5DlidxWriter *pDlidx = &pWriter->aDlidx[i]; + + if( pDlidx->buf.n>=p->pConfig->pgsz ){ + /* The current doclist-index page is full. Write it to disk and push + ** a copy of iRowid (which will become the first rowid on the next + ** doclist-index leaf page) up into the next level of the b-tree + ** hierarchy. If the node being flushed is currently the root node, + ** also push its first rowid upwards. */ + pDlidx->buf.p[0] = 0x01; /* Not the root node */ + fts5DataWrite(p, + FTS5_DLIDX_ROWID(pWriter->iSegid, i, pDlidx->pgno), + pDlidx->buf.p, pDlidx->buf.n + ); + fts5WriteDlidxGrow(p, pWriter, i+2); + pDlidx = &pWriter->aDlidx[i]; + if( p->rc==SQLITE_OK && pDlidx[1].buf.n==0 ){ + i64 iFirst = fts5DlidxExtractFirstRowid(&pDlidx->buf); + + /* This was the root node. Push its first rowid up to the new root. */ + pDlidx[1].pgno = pDlidx->pgno; + sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1].buf, 0); + sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1].buf, pDlidx->pgno); + sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1].buf, iFirst); + pDlidx[1].bPrevValid = 1; + pDlidx[1].iPrev = iFirst; + } + + sqlite3Fts5BufferZero(&pDlidx->buf); + pDlidx->bPrevValid = 0; + pDlidx->pgno++; + }else{ + bDone = 1; + } + + if( pDlidx->bPrevValid ){ + iVal = iRowid - pDlidx->iPrev; + }else{ + i64 iPgno = (i==0 ? pWriter->writer.pgno : pDlidx[-1].pgno); + assert( pDlidx->buf.n==0 ); + sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, !bDone); + sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, iPgno); + iVal = iRowid; + } + + sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, iVal); + pDlidx->bPrevValid = 1; + pDlidx->iPrev = iRowid; + } +} + +static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){ + static const u8 zero[] = { 0x00, 0x00, 0x00, 0x00 }; + Fts5PageWriter *pPage = &pWriter->writer; + i64 iRowid; + + assert( (pPage->pgidx.n==0)==(pWriter->bFirstTermInPage) ); + + /* Set the szLeaf header field. */ + assert( 0==fts5GetU16(&pPage->buf.p[2]) ); + fts5PutU16(&pPage->buf.p[2], (u16)pPage->buf.n); + + if( pWriter->bFirstTermInPage ){ + /* No term was written to this page. */ + assert( pPage->pgidx.n==0 ); + fts5WriteBtreeNoTerm(p, pWriter); + }else{ + /* Append the pgidx to the page buffer. Set the szLeaf header field. */ + fts5BufferAppendBlob(&p->rc, &pPage->buf, pPage->pgidx.n, pPage->pgidx.p); + } + + /* Write the page out to disk */ + iRowid = FTS5_SEGMENT_ROWID(pWriter->iSegid, pPage->pgno); + fts5DataWrite(p, iRowid, pPage->buf.p, pPage->buf.n); + + /* Initialize the next page. */ + fts5BufferZero(&pPage->buf); + fts5BufferZero(&pPage->pgidx); + fts5BufferAppendBlob(&p->rc, &pPage->buf, 4, zero); + pPage->iPrevPgidx = 0; + pPage->pgno++; + + /* Increase the leaves written counter */ + pWriter->nLeafWritten++; + + /* The new leaf holds no terms or rowids */ + pWriter->bFirstTermInPage = 1; + pWriter->bFirstRowidInPage = 1; +} + +/* +** Append term pTerm/nTerm to the segment being written by the writer passed +** as the second argument. +** +** If an error occurs, set the Fts5Index.rc error code. If an error has +** already occurred, this function is a no-op. +*/ +static void fts5WriteAppendTerm( + Fts5Index *p, + Fts5SegWriter *pWriter, + int nTerm, const u8 *pTerm +){ + int nPrefix; /* Bytes of prefix compression for term */ + Fts5PageWriter *pPage = &pWriter->writer; + Fts5Buffer *pPgidx = &pWriter->writer.pgidx; + int nMin = MIN(pPage->term.n, nTerm); + + assert( p->rc==SQLITE_OK ); + assert( pPage->buf.n>=4 ); + assert( pPage->buf.n>4 || pWriter->bFirstTermInPage ); + + /* If the current leaf page is full, flush it to disk. */ + if( (pPage->buf.n + pPgidx->n + nTerm + 2)>=p->pConfig->pgsz ){ + if( pPage->buf.n>4 ){ + fts5WriteFlushLeaf(p, pWriter); + if( p->rc!=SQLITE_OK ) return; + } + fts5BufferGrow(&p->rc, &pPage->buf, nTerm+FTS5_DATA_PADDING); + } + + /* TODO1: Updating pgidx here. */ + pPgidx->n += sqlite3Fts5PutVarint( + &pPgidx->p[pPgidx->n], pPage->buf.n - pPage->iPrevPgidx + ); + pPage->iPrevPgidx = pPage->buf.n; +#if 0 + fts5PutU16(&pPgidx->p[pPgidx->n], pPage->buf.n); + pPgidx->n += 2; +#endif + + if( pWriter->bFirstTermInPage ){ + nPrefix = 0; + if( pPage->pgno!=1 ){ + /* This is the first term on a leaf that is not the leftmost leaf in + ** the segment b-tree. In this case it is necessary to add a term to + ** the b-tree hierarchy that is (a) larger than the largest term + ** already written to the segment and (b) smaller than or equal to + ** this term. In other words, a prefix of (pTerm/nTerm) that is one + ** byte longer than the longest prefix (pTerm/nTerm) shares with the + ** previous term. + ** + ** Usually, the previous term is available in pPage->term. The exception + ** is if this is the first term written in an incremental-merge step. + ** In this case the previous term is not available, so just write a + ** copy of (pTerm/nTerm) into the parent node. This is slightly + ** inefficient, but still correct. */ + int n = nTerm; + if( pPage->term.n ){ + n = 1 + fts5PrefixCompress(nMin, pPage->term.p, pTerm); + } + fts5WriteBtreeTerm(p, pWriter, n, pTerm); + if( p->rc!=SQLITE_OK ) return; + pPage = &pWriter->writer; + } + }else{ + nPrefix = fts5PrefixCompress(nMin, pPage->term.p, pTerm); + fts5BufferAppendVarint(&p->rc, &pPage->buf, nPrefix); + } + + /* Append the number of bytes of new data, then the term data itself + ** to the page. */ + fts5BufferAppendVarint(&p->rc, &pPage->buf, nTerm - nPrefix); + fts5BufferAppendBlob(&p->rc, &pPage->buf, nTerm - nPrefix, &pTerm[nPrefix]); + + /* Update the Fts5PageWriter.term field. */ + fts5BufferSet(&p->rc, &pPage->term, nTerm, pTerm); + pWriter->bFirstTermInPage = 0; + + pWriter->bFirstRowidInPage = 0; + pWriter->bFirstRowidInDoclist = 1; + + assert( p->rc || (pWriter->nDlidx>0 && pWriter->aDlidx[0].buf.n==0) ); + pWriter->aDlidx[0].pgno = pPage->pgno; +} + +/* +** Append a rowid and position-list size field to the writers output. +*/ +static void fts5WriteAppendRowid( + Fts5Index *p, + Fts5SegWriter *pWriter, + i64 iRowid +){ + if( p->rc==SQLITE_OK ){ + Fts5PageWriter *pPage = &pWriter->writer; + + if( (pPage->buf.n + pPage->pgidx.n)>=p->pConfig->pgsz ){ + fts5WriteFlushLeaf(p, pWriter); + } + + /* If this is to be the first rowid written to the page, set the + ** rowid-pointer in the page-header. Also append a value to the dlidx + ** buffer, in case a doclist-index is required. */ + if( pWriter->bFirstRowidInPage ){ + fts5PutU16(pPage->buf.p, (u16)pPage->buf.n); + fts5WriteDlidxAppend(p, pWriter, iRowid); + } + + /* Write the rowid. */ + if( pWriter->bFirstRowidInDoclist || pWriter->bFirstRowidInPage ){ + fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid); + }else{ + assert_nc( p->rc || iRowid>pWriter->iPrevRowid ); + fts5BufferAppendVarint(&p->rc, &pPage->buf, + (u64)iRowid - (u64)pWriter->iPrevRowid + ); + } + pWriter->iPrevRowid = iRowid; + pWriter->bFirstRowidInDoclist = 0; + pWriter->bFirstRowidInPage = 0; + } +} + +static void fts5WriteAppendPoslistData( + Fts5Index *p, + Fts5SegWriter *pWriter, + const u8 *aData, + int nData +){ + Fts5PageWriter *pPage = &pWriter->writer; + const u8 *a = aData; + int n = nData; + + assert( p->pConfig->pgsz>0 ); + while( p->rc==SQLITE_OK + && (pPage->buf.n + pPage->pgidx.n + n)>=p->pConfig->pgsz + ){ + int nReq = p->pConfig->pgsz - pPage->buf.n - pPage->pgidx.n; + int nCopy = 0; + while( nCopy<nReq ){ + i64 dummy; + nCopy += fts5GetVarint(&a[nCopy], (u64*)&dummy); + } + fts5BufferAppendBlob(&p->rc, &pPage->buf, nCopy, a); + a += nCopy; + n -= nCopy; + fts5WriteFlushLeaf(p, pWriter); + } + if( n>0 ){ + fts5BufferAppendBlob(&p->rc, &pPage->buf, n, a); + } +} + +/* +** Flush any data cached by the writer object to the database. Free any +** allocations associated with the writer. +*/ +static void fts5WriteFinish( + Fts5Index *p, + Fts5SegWriter *pWriter, /* Writer object */ + int *pnLeaf /* OUT: Number of leaf pages in b-tree */ +){ + int i; + Fts5PageWriter *pLeaf = &pWriter->writer; + if( p->rc==SQLITE_OK ){ + assert( pLeaf->pgno>=1 ); + if( pLeaf->buf.n>4 ){ + fts5WriteFlushLeaf(p, pWriter); + } + *pnLeaf = pLeaf->pgno-1; + if( pLeaf->pgno>1 ){ + fts5WriteFlushBtree(p, pWriter); + } + } + fts5BufferFree(&pLeaf->term); + fts5BufferFree(&pLeaf->buf); + fts5BufferFree(&pLeaf->pgidx); + fts5BufferFree(&pWriter->btterm); + + for(i=0; i<pWriter->nDlidx; i++){ + sqlite3Fts5BufferFree(&pWriter->aDlidx[i].buf); + } + sqlite3_free(pWriter->aDlidx); +} + +static void fts5WriteInit( + Fts5Index *p, + Fts5SegWriter *pWriter, + int iSegid +){ + const int nBuffer = p->pConfig->pgsz + FTS5_DATA_PADDING; + + memset(pWriter, 0, sizeof(Fts5SegWriter)); + pWriter->iSegid = iSegid; + + fts5WriteDlidxGrow(p, pWriter, 1); + pWriter->writer.pgno = 1; + pWriter->bFirstTermInPage = 1; + pWriter->iBtPage = 1; + + assert( pWriter->writer.buf.n==0 ); + assert( pWriter->writer.pgidx.n==0 ); + + /* Grow the two buffers to pgsz + padding bytes in size. */ + sqlite3Fts5BufferSize(&p->rc, &pWriter->writer.pgidx, nBuffer); + sqlite3Fts5BufferSize(&p->rc, &pWriter->writer.buf, nBuffer); + + if( p->pIdxWriter==0 ){ + Fts5Config *pConfig = p->pConfig; + fts5IndexPrepareStmt(p, &p->pIdxWriter, sqlite3_mprintf( + "INSERT INTO '%q'.'%q_idx'(segid,term,pgno) VALUES(?,?,?)", + pConfig->zDb, pConfig->zName + )); + } + + if( p->rc==SQLITE_OK ){ + /* Initialize the 4-byte leaf-page header to 0x00. */ + memset(pWriter->writer.buf.p, 0, 4); + pWriter->writer.buf.n = 4; + + /* Bind the current output segment id to the index-writer. This is an + ** optimization over binding the same value over and over as rows are + ** inserted into %_idx by the current writer. */ + sqlite3_bind_int(p->pIdxWriter, 1, pWriter->iSegid); + } +} + +/* +** Iterator pIter was used to iterate through the input segments of on an +** incremental merge operation. This function is called if the incremental +** merge step has finished but the input has not been completely exhausted. +*/ +static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){ + int i; + Fts5Buffer buf; + memset(&buf, 0, sizeof(Fts5Buffer)); + for(i=0; i<pIter->nSeg && p->rc==SQLITE_OK; i++){ + Fts5SegIter *pSeg = &pIter->aSeg[i]; + if( pSeg->pSeg==0 ){ + /* no-op */ + }else if( pSeg->pLeaf==0 ){ + /* All keys from this input segment have been transfered to the output. + ** Set both the first and last page-numbers to 0 to indicate that the + ** segment is now empty. */ + pSeg->pSeg->pgnoLast = 0; + pSeg->pSeg->pgnoFirst = 0; + }else{ + int iOff = pSeg->iTermLeafOffset; /* Offset on new first leaf page */ + i64 iLeafRowid; + Fts5Data *pData; + int iId = pSeg->pSeg->iSegid; + u8 aHdr[4] = {0x00, 0x00, 0x00, 0x00}; + + iLeafRowid = FTS5_SEGMENT_ROWID(iId, pSeg->iTermLeafPgno); + pData = fts5LeafRead(p, iLeafRowid); + if( pData ){ + if( iOff>pData->szLeaf ){ + /* This can occur if the pages that the segments occupy overlap - if + ** a single page has been assigned to more than one segment. In + ** this case a prior iteration of this loop may have corrupted the + ** segment currently being trimmed. */ + p->rc = FTS5_CORRUPT; + }else{ + fts5BufferZero(&buf); + fts5BufferGrow(&p->rc, &buf, pData->nn); + fts5BufferAppendBlob(&p->rc, &buf, sizeof(aHdr), aHdr); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->term.n); + fts5BufferAppendBlob(&p->rc, &buf, pSeg->term.n, pSeg->term.p); + fts5BufferAppendBlob(&p->rc, &buf,pData->szLeaf-iOff,&pData->p[iOff]); + if( p->rc==SQLITE_OK ){ + /* Set the szLeaf field */ + fts5PutU16(&buf.p[2], (u16)buf.n); + } + + /* Set up the new page-index array */ + fts5BufferAppendVarint(&p->rc, &buf, 4); + if( pSeg->iLeafPgno==pSeg->iTermLeafPgno + && pSeg->iEndofDoclist<pData->szLeaf + && pSeg->iPgidxOff<=pData->nn + ){ + int nDiff = pData->szLeaf - pSeg->iEndofDoclist; + fts5BufferAppendVarint(&p->rc, &buf, buf.n - 1 - nDiff - 4); + fts5BufferAppendBlob(&p->rc, &buf, + pData->nn - pSeg->iPgidxOff, &pData->p[pSeg->iPgidxOff] + ); + } + + pSeg->pSeg->pgnoFirst = pSeg->iTermLeafPgno; + fts5DataDelete(p, FTS5_SEGMENT_ROWID(iId, 1), iLeafRowid); + fts5DataWrite(p, iLeafRowid, buf.p, buf.n); + } + fts5DataRelease(pData); + } + } + } + fts5BufferFree(&buf); +} + +static void fts5MergeChunkCallback( + Fts5Index *p, + void *pCtx, + const u8 *pChunk, int nChunk +){ + Fts5SegWriter *pWriter = (Fts5SegWriter*)pCtx; + fts5WriteAppendPoslistData(p, pWriter, pChunk, nChunk); +} + +/* +** +*/ +static void fts5IndexMergeLevel( + Fts5Index *p, /* FTS5 backend object */ + Fts5Structure **ppStruct, /* IN/OUT: Stucture of index */ + int iLvl, /* Level to read input from */ + int *pnRem /* Write up to this many output leaves */ +){ + Fts5Structure *pStruct = *ppStruct; + Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; + Fts5StructureLevel *pLvlOut; + Fts5Iter *pIter = 0; /* Iterator to read input data */ + int nRem = pnRem ? *pnRem : 0; /* Output leaf pages left to write */ + int nInput; /* Number of input segments */ + Fts5SegWriter writer; /* Writer object */ + Fts5StructureSegment *pSeg; /* Output segment */ + Fts5Buffer term; + int bOldest; /* True if the output segment is the oldest */ + int eDetail = p->pConfig->eDetail; + const int flags = FTS5INDEX_QUERY_NOOUTPUT; + int bTermWritten = 0; /* True if current term already output */ + + assert( iLvl<pStruct->nLevel ); + assert( pLvl->nMerge<=pLvl->nSeg ); + + memset(&writer, 0, sizeof(Fts5SegWriter)); + memset(&term, 0, sizeof(Fts5Buffer)); + if( pLvl->nMerge ){ + pLvlOut = &pStruct->aLevel[iLvl+1]; + assert( pLvlOut->nSeg>0 ); + nInput = pLvl->nMerge; + pSeg = &pLvlOut->aSeg[pLvlOut->nSeg-1]; + + fts5WriteInit(p, &writer, pSeg->iSegid); + writer.writer.pgno = pSeg->pgnoLast+1; + writer.iBtPage = 0; + }else{ + int iSegid = fts5AllocateSegid(p, pStruct); + + /* Extend the Fts5Structure object as required to ensure the output + ** segment exists. */ + if( iLvl==pStruct->nLevel-1 ){ + fts5StructureAddLevel(&p->rc, ppStruct); + pStruct = *ppStruct; + } + fts5StructureExtendLevel(&p->rc, pStruct, iLvl+1, 1, 0); + if( p->rc ) return; + pLvl = &pStruct->aLevel[iLvl]; + pLvlOut = &pStruct->aLevel[iLvl+1]; + + fts5WriteInit(p, &writer, iSegid); + + /* Add the new segment to the output level */ + pSeg = &pLvlOut->aSeg[pLvlOut->nSeg]; + pLvlOut->nSeg++; + pSeg->pgnoFirst = 1; + pSeg->iSegid = iSegid; + pStruct->nSegment++; + + /* Read input from all segments in the input level */ + nInput = pLvl->nSeg; + + /* Set the range of origins that will go into the output segment. */ + if( pStruct->nOriginCntr>0 ){ + pSeg->iOrigin1 = pLvl->aSeg[0].iOrigin1; + pSeg->iOrigin2 = pLvl->aSeg[pLvl->nSeg-1].iOrigin2; + } + } + bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2); + + assert( iLvl>=0 ); + for(fts5MultiIterNew(p, pStruct, flags, 0, 0, 0, iLvl, nInput, &pIter); + fts5MultiIterEof(p, pIter)==0; + fts5MultiIterNext(p, pIter, 0, 0) + ){ + Fts5SegIter *pSegIter = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; + int nPos; /* position-list size field value */ + int nTerm; + const u8 *pTerm; + + pTerm = fts5MultiIterTerm(pIter, &nTerm); + if( nTerm!=term.n || fts5Memcmp(pTerm, term.p, nTerm) ){ + if( pnRem && writer.nLeafWritten>nRem ){ + break; + } + fts5BufferSet(&p->rc, &term, nTerm, pTerm); + bTermWritten =0; + } + + /* Check for key annihilation. */ + if( pSegIter->nPos==0 && (bOldest || pSegIter->bDel==0) ) continue; + + if( p->rc==SQLITE_OK && bTermWritten==0 ){ + /* This is a new term. Append a term to the output segment. */ + fts5WriteAppendTerm(p, &writer, nTerm, pTerm); + bTermWritten = 1; + } + + /* Append the rowid to the output */ + /* WRITEPOSLISTSIZE */ + fts5WriteAppendRowid(p, &writer, fts5MultiIterRowid(pIter)); + + if( eDetail==FTS5_DETAIL_NONE ){ + if( pSegIter->bDel ){ + fts5BufferAppendVarint(&p->rc, &writer.writer.buf, 0); + if( pSegIter->nPos>0 ){ + fts5BufferAppendVarint(&p->rc, &writer.writer.buf, 0); + } + } + }else{ + /* Append the position-list data to the output */ + nPos = pSegIter->nPos*2 + pSegIter->bDel; + fts5BufferAppendVarint(&p->rc, &writer.writer.buf, nPos); + fts5ChunkIterate(p, pSegIter, (void*)&writer, fts5MergeChunkCallback); + } + } + + /* Flush the last leaf page to disk. Set the output segment b-tree height + ** and last leaf page number at the same time. */ + fts5WriteFinish(p, &writer, &pSeg->pgnoLast); + + assert( pIter!=0 || p->rc!=SQLITE_OK ); + if( fts5MultiIterEof(p, pIter) ){ + int i; + + /* Remove the redundant segments from the %_data table */ + assert( pSeg->nEntry==0 ); + for(i=0; i<nInput; i++){ + Fts5StructureSegment *pOld = &pLvl->aSeg[i]; + pSeg->nEntry += (pOld->nEntry - pOld->nEntryTombstone); + fts5DataRemoveSegment(p, pOld); + } + + /* Remove the redundant segments from the input level */ + if( pLvl->nSeg!=nInput ){ + int nMove = (pLvl->nSeg - nInput) * sizeof(Fts5StructureSegment); + memmove(pLvl->aSeg, &pLvl->aSeg[nInput], nMove); + } + pStruct->nSegment -= nInput; + pLvl->nSeg -= nInput; + pLvl->nMerge = 0; + if( pSeg->pgnoLast==0 ){ + pLvlOut->nSeg--; + pStruct->nSegment--; + } + }else{ + assert( pSeg->pgnoLast>0 ); + fts5TrimSegments(p, pIter); + pLvl->nMerge = nInput; + } + + fts5MultiIterFree(pIter); + fts5BufferFree(&term); + if( pnRem ) *pnRem -= writer.nLeafWritten; +} + +/* +** If this is not a contentless_delete=1 table, or if the 'deletemerge' +** configuration option is set to 0, then this function always returns -1. +** Otherwise, it searches the structure object passed as the second argument +** for a level suitable for merging due to having a large number of +** tombstones in the tombstone hash. If one is found, its index is returned. +** Otherwise, if there is no suitable level, -1. +*/ +static int fts5IndexFindDeleteMerge(Fts5Index *p, Fts5Structure *pStruct){ + Fts5Config *pConfig = p->pConfig; + int iRet = -1; + if( pConfig->bContentlessDelete && pConfig->nDeleteMerge>0 ){ + int ii; + int nBest = 0; + + for(ii=0; ii<pStruct->nLevel; ii++){ + Fts5StructureLevel *pLvl = &pStruct->aLevel[ii]; + i64 nEntry = 0; + i64 nTomb = 0; + int iSeg; + for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){ + nEntry += pLvl->aSeg[iSeg].nEntry; + nTomb += pLvl->aSeg[iSeg].nEntryTombstone; + } + assert_nc( nEntry>0 || pLvl->nSeg==0 ); + if( nEntry>0 ){ + int nPercent = (nTomb * 100) / nEntry; + if( nPercent>=pConfig->nDeleteMerge && nPercent>nBest ){ + iRet = ii; + nBest = nPercent; + } + } + } + } + return iRet; +} + +/* +** Do up to nPg pages of automerge work on the index. +** +** Return true if any changes were actually made, or false otherwise. +*/ +static int fts5IndexMerge( + Fts5Index *p, /* FTS5 backend object */ + Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */ + int nPg, /* Pages of work to do */ + int nMin /* Minimum number of segments to merge */ +){ + int nRem = nPg; + int bRet = 0; + Fts5Structure *pStruct = *ppStruct; + while( nRem>0 && p->rc==SQLITE_OK ){ + int iLvl; /* To iterate through levels */ + int iBestLvl = 0; /* Level offering the most input segments */ + int nBest = 0; /* Number of input segments on best level */ + + /* Set iBestLvl to the level to read input segments from. Or to -1 if + ** there is no level suitable to merge segments from. */ + assert( pStruct->nLevel>0 ); + for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ + Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; + if( pLvl->nMerge ){ + if( pLvl->nMerge>nBest ){ + iBestLvl = iLvl; + nBest = nMin; + } + break; + } + if( pLvl->nSeg>nBest ){ + nBest = pLvl->nSeg; + iBestLvl = iLvl; + } + } + if( nBest<nMin ){ + iBestLvl = fts5IndexFindDeleteMerge(p, pStruct); + } + + if( iBestLvl<0 ) break; + bRet = 1; + fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem); + if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){ + fts5StructurePromote(p, iBestLvl+1, pStruct); + } + + if( nMin==1 ) nMin = 2; + } + *ppStruct = pStruct; + return bRet; +} + +/* +** A total of nLeaf leaf pages of data has just been flushed to a level-0 +** segment. This function updates the write-counter accordingly and, if +** necessary, performs incremental merge work. +** +** If an error occurs, set the Fts5Index.rc error code. If an error has +** already occurred, this function is a no-op. +*/ +static void fts5IndexAutomerge( + Fts5Index *p, /* FTS5 backend object */ + Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */ + int nLeaf /* Number of output leaves just written */ +){ + if( p->rc==SQLITE_OK && p->pConfig->nAutomerge>0 && ALWAYS((*ppStruct)!=0) ){ + Fts5Structure *pStruct = *ppStruct; + u64 nWrite; /* Initial value of write-counter */ + int nWork; /* Number of work-quanta to perform */ + int nRem; /* Number of leaf pages left to write */ + + /* Update the write-counter. While doing so, set nWork. */ + nWrite = pStruct->nWriteCounter; + nWork = (int)(((nWrite + nLeaf) / p->nWorkUnit) - (nWrite / p->nWorkUnit)); + pStruct->nWriteCounter += nLeaf; + nRem = (int)(p->nWorkUnit * nWork * pStruct->nLevel); + + fts5IndexMerge(p, ppStruct, nRem, p->pConfig->nAutomerge); + } +} + +static void fts5IndexCrisismerge( + Fts5Index *p, /* FTS5 backend object */ + Fts5Structure **ppStruct /* IN/OUT: Current structure of index */ +){ + const int nCrisis = p->pConfig->nCrisisMerge; + Fts5Structure *pStruct = *ppStruct; + if( pStruct && pStruct->nLevel>0 ){ + int iLvl = 0; + while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){ + fts5IndexMergeLevel(p, &pStruct, iLvl, 0); + assert( p->rc!=SQLITE_OK || pStruct->nLevel>(iLvl+1) ); + fts5StructurePromote(p, iLvl+1, pStruct); + iLvl++; + } + *ppStruct = pStruct; + } +} + +static int fts5IndexReturn(Fts5Index *p){ + int rc = p->rc; + p->rc = SQLITE_OK; + return rc; +} + +typedef struct Fts5FlushCtx Fts5FlushCtx; +struct Fts5FlushCtx { + Fts5Index *pIdx; + Fts5SegWriter writer; +}; + +/* +** Buffer aBuf[] contains a list of varints, all small enough to fit +** in a 32-bit integer. Return the size of the largest prefix of this +** list nMax bytes or less in size. +*/ +static int fts5PoslistPrefix(const u8 *aBuf, int nMax){ + int ret; + u32 dummy; + ret = fts5GetVarint32(aBuf, dummy); + if( ret<nMax ){ + while( 1 ){ + int i = fts5GetVarint32(&aBuf[ret], dummy); + if( (ret + i) > nMax ) break; + ret += i; + } + } + return ret; +} + +/* +** Execute the SQL statement: +** +** DELETE FROM %_idx WHERE (segid, (pgno/2)) = ($iSegid, $iPgno); +** +** This is used when a secure-delete operation removes the last term +** from a segment leaf page. In that case the %_idx entry is removed +** too. This is done to ensure that if all instances of a token are +** removed from an fts5 database in secure-delete mode, no trace of +** the token itself remains in the database. +*/ +static void fts5SecureDeleteIdxEntry( + Fts5Index *p, /* FTS5 backend object */ + int iSegid, /* Id of segment to delete entry for */ + int iPgno /* Page number within segment */ +){ + if( iPgno!=1 ){ + assert( p->pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE ); + if( p->pDeleteFromIdx==0 ){ + fts5IndexPrepareStmt(p, &p->pDeleteFromIdx, sqlite3_mprintf( + "DELETE FROM '%q'.'%q_idx' WHERE (segid, (pgno/2)) = (?1, ?2)", + p->pConfig->zDb, p->pConfig->zName + )); + } + if( p->rc==SQLITE_OK ){ + sqlite3_bind_int(p->pDeleteFromIdx, 1, iSegid); + sqlite3_bind_int(p->pDeleteFromIdx, 2, iPgno); + sqlite3_step(p->pDeleteFromIdx); + p->rc = sqlite3_reset(p->pDeleteFromIdx); + } + } +} + +/* +** This is called when a secure-delete operation removes a position-list +** that overflows onto segment page iPgno of segment pSeg. This function +** rewrites node iPgno, and possibly one or more of its right-hand peers, +** to remove this portion of the position list. +** +** Output variable (*pbLastInDoclist) is set to true if the position-list +** removed is followed by a new term or the end-of-segment, or false if +** it is followed by another rowid/position list. +*/ +static void fts5SecureDeleteOverflow( + Fts5Index *p, + Fts5StructureSegment *pSeg, + int iPgno, + int *pbLastInDoclist +){ + const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE); + int pgno; + Fts5Data *pLeaf = 0; + assert( iPgno!=1 ); + + *pbLastInDoclist = 1; + for(pgno=iPgno; p->rc==SQLITE_OK && pgno<=pSeg->pgnoLast; pgno++){ + i64 iRowid = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno); + int iNext = 0; + u8 *aPg = 0; + + pLeaf = fts5DataRead(p, iRowid); + if( pLeaf==0 ) break; + aPg = pLeaf->p; + + iNext = fts5GetU16(&aPg[0]); + if( iNext!=0 ){ + *pbLastInDoclist = 0; + } + if( iNext==0 && pLeaf->szLeaf!=pLeaf->nn ){ + fts5GetVarint32(&aPg[pLeaf->szLeaf], iNext); + } + + if( iNext==0 ){ + /* The page contains no terms or rowids. Replace it with an empty + ** page and move on to the right-hand peer. */ + const u8 aEmpty[] = {0x00, 0x00, 0x00, 0x04}; + assert_nc( bDetailNone==0 || pLeaf->nn==4 ); + if( bDetailNone==0 ) fts5DataWrite(p, iRowid, aEmpty, sizeof(aEmpty)); + fts5DataRelease(pLeaf); + pLeaf = 0; + }else if( bDetailNone ){ + break; + }else if( iNext>=pLeaf->szLeaf || pLeaf->nn<pLeaf->szLeaf || iNext<4 ){ + p->rc = FTS5_CORRUPT; + break; + }else{ + int nShift = iNext - 4; + int nPg; + + int nIdx = 0; + u8 *aIdx = 0; + + /* Unless the current page footer is 0 bytes in size (in which case + ** the new page footer will be as well), allocate and populate a + ** buffer containing the new page footer. Set stack variables aIdx + ** and nIdx accordingly. */ + if( pLeaf->nn>pLeaf->szLeaf ){ + int iFirst = 0; + int i1 = pLeaf->szLeaf; + int i2 = 0; + + i1 += fts5GetVarint32(&aPg[i1], iFirst); + if( iFirst<iNext ){ + p->rc = FTS5_CORRUPT; + break; + } + aIdx = sqlite3Fts5MallocZero(&p->rc, (pLeaf->nn-pLeaf->szLeaf)+2); + if( aIdx==0 ) break; + i2 = sqlite3Fts5PutVarint(aIdx, iFirst-nShift); + if( i1<pLeaf->nn ){ + memcpy(&aIdx[i2], &aPg[i1], pLeaf->nn-i1); + i2 += (pLeaf->nn-i1); + } + nIdx = i2; + } + + /* Modify the contents of buffer aPg[]. Set nPg to the new size + ** in bytes. The new page is always smaller than the old. */ + nPg = pLeaf->szLeaf - nShift; + memmove(&aPg[4], &aPg[4+nShift], nPg-4); + fts5PutU16(&aPg[2], nPg); + if( fts5GetU16(&aPg[0]) ) fts5PutU16(&aPg[0], 4); + if( nIdx>0 ){ + memcpy(&aPg[nPg], aIdx, nIdx); + nPg += nIdx; + } + sqlite3_free(aIdx); + + /* Write the new page to disk and exit the loop */ + assert( nPg>4 || fts5GetU16(aPg)==0 ); + fts5DataWrite(p, iRowid, aPg, nPg); + break; + } + } + fts5DataRelease(pLeaf); +} + +/* +** Completely remove the entry that pSeg currently points to from +** the database. +*/ +static void fts5DoSecureDelete( + Fts5Index *p, + Fts5SegIter *pSeg +){ + const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE); + int iSegid = pSeg->pSeg->iSegid; + u8 *aPg = pSeg->pLeaf->p; + int nPg = pSeg->pLeaf->nn; + int iPgIdx = pSeg->pLeaf->szLeaf; + + u64 iDelta = 0; + int iNextOff = 0; + int iOff = 0; + int nIdx = 0; + u8 *aIdx = 0; + int bLastInDoclist = 0; + int iIdx = 0; + int iStart = 0; + int iDelKeyOff = 0; /* Offset of deleted key, if any */ + + nIdx = nPg-iPgIdx; + aIdx = sqlite3Fts5MallocZero(&p->rc, nIdx+16); + if( p->rc ) return; + memcpy(aIdx, &aPg[iPgIdx], nIdx); + + /* At this point segment iterator pSeg points to the entry + ** this function should remove from the b-tree segment. + ** + ** In detail=full or detail=column mode, pSeg->iLeafOffset is the + ** offset of the first byte in the position-list for the entry to + ** remove. Immediately before this comes two varints that will also + ** need to be removed: + ** + ** + the rowid or delta rowid value for the entry, and + ** + the size of the position list in bytes. + ** + ** Or, in detail=none mode, there is a single varint prior to + ** pSeg->iLeafOffset - the rowid or delta rowid value. + ** + ** This block sets the following variables: + ** + ** iStart: + ** The offset of the first byte of the rowid or delta-rowid + ** value for the doclist entry being removed. + ** + ** iDelta: + ** The value of the rowid or delta-rowid value for the doclist + ** entry being removed. + ** + ** iNextOff: + ** The offset of the next entry following the position list + ** for the one being removed. If the position list for this + ** entry overflows onto the next leaf page, this value will be + ** greater than pLeaf->szLeaf. + */ + { + int iSOP; /* Start-Of-Position-list */ + if( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){ + iStart = pSeg->iTermLeafOffset; + }else{ + iStart = fts5GetU16(&aPg[0]); + } + + iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); + assert_nc( iSOP<=pSeg->iLeafOffset ); + + if( bDetailNone ){ + while( iSOP<pSeg->iLeafOffset ){ + if( aPg[iSOP]==0x00 ) iSOP++; + if( aPg[iSOP]==0x00 ) iSOP++; + iStart = iSOP; + iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); + } + + iNextOff = iSOP; + if( iNextOff<pSeg->iEndofDoclist && aPg[iNextOff]==0x00 ) iNextOff++; + if( iNextOff<pSeg->iEndofDoclist && aPg[iNextOff]==0x00 ) iNextOff++; + + }else{ + int nPos = 0; + iSOP += fts5GetVarint32(&aPg[iSOP], nPos); + while( iSOP<pSeg->iLeafOffset ){ + iStart = iSOP + (nPos/2); + iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); + iSOP += fts5GetVarint32(&aPg[iSOP], nPos); + } + assert_nc( iSOP==pSeg->iLeafOffset ); + iNextOff = pSeg->iLeafOffset + pSeg->nPos; + } + } + + iOff = iStart; + + /* Set variable bLastInDoclist to true if this entry happens to be + ** the last rowid in the doclist for its term. */ + if( iNextOff>=iPgIdx ){ + int pgno = pSeg->iLeafPgno+1; + fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist); + iNextOff = iPgIdx; + }else{ + /* Loop through the page-footer. If iNextOff (offset of the + ** entry following the one we are removing) is equal to the + ** offset of a key on this page, then the entry is the last + ** in its doclist. */ + int iKeyOff = 0; + for(iIdx=0; iIdx<nIdx; /* no-op */){ + u32 iVal = 0; + iIdx += fts5GetVarint32(&aIdx[iIdx], iVal); + iKeyOff += iVal; + if( iKeyOff==iNextOff ){ + bLastInDoclist = 1; + } + } + } + + /* If this is (a) the first rowid on a page and (b) is not followed by + ** another position list on the same page, set the "first-rowid" field + ** of the header to 0. */ + if( fts5GetU16(&aPg[0])==iStart && (bLastInDoclist || iNextOff==iPgIdx) ){ + fts5PutU16(&aPg[0], 0); + } + + if( bLastInDoclist==0 ){ + if( iNextOff!=iPgIdx ){ + u64 iNextDelta = 0; + iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta); + iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta); + } + }else if( + pSeg->iLeafPgno==pSeg->iTermLeafPgno + && iStart==pSeg->iTermLeafOffset + ){ + /* The entry being removed was the only position list in its + ** doclist. Therefore the term needs to be removed as well. */ + int iKey = 0; + int iKeyOff = 0; + + /* Set iKeyOff to the offset of the term that will be removed - the + ** last offset in the footer that is not greater than iStart. */ + for(iIdx=0; iIdx<nIdx; iKey++){ + u32 iVal = 0; + iIdx += fts5GetVarint32(&aIdx[iIdx], iVal); + if( (iKeyOff+iVal)>(u32)iStart ) break; + iKeyOff += iVal; + } + assert_nc( iKey>=1 ); + + /* Set iDelKeyOff to the value of the footer entry to remove from + ** the page. */ + iDelKeyOff = iOff = iKeyOff; + + if( iNextOff!=iPgIdx ){ + /* This is the only position-list associated with the term, and there + ** is another term following it on this page. So the subsequent term + ** needs to be moved to replace the term associated with the entry + ** being removed. */ + int nPrefix = 0; + int nSuffix = 0; + int nPrefix2 = 0; + int nSuffix2 = 0; + + iDelKeyOff = iNextOff; + iNextOff += fts5GetVarint32(&aPg[iNextOff], nPrefix2); + iNextOff += fts5GetVarint32(&aPg[iNextOff], nSuffix2); + + if( iKey!=1 ){ + iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nPrefix); + } + iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nSuffix); + + nPrefix = MIN(nPrefix, nPrefix2); + nSuffix = (nPrefix2 + nSuffix2) - nPrefix; + + if( (iKeyOff+nSuffix)>iPgIdx || (iNextOff+nSuffix2)>iPgIdx ){ + p->rc = FTS5_CORRUPT; + }else{ + if( iKey!=1 ){ + iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix); + } + iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix); + if( nPrefix2>pSeg->term.n ){ + p->rc = FTS5_CORRUPT; + }else if( nPrefix2>nPrefix ){ + memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix); + iOff += (nPrefix2-nPrefix); + } + memmove(&aPg[iOff], &aPg[iNextOff], nSuffix2); + iOff += nSuffix2; + iNextOff += nSuffix2; + } + } + }else if( iStart==4 ){ + int iPgno; + + assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno ); + /* The entry being removed may be the only position list in + ** its doclist. */ + for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){ + Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); + int bEmpty = (pPg && pPg->nn==4); + fts5DataRelease(pPg); + if( bEmpty==0 ) break; + } + + if( iPgno==pSeg->iTermLeafPgno ){ + i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno); + Fts5Data *pTerm = fts5DataRead(p, iId); + if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){ + u8 *aTermIdx = &pTerm->p[pTerm->szLeaf]; + int nTermIdx = pTerm->nn - pTerm->szLeaf; + int iTermIdx = 0; + int iTermOff = 0; + + while( 1 ){ + u32 iVal = 0; + int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal); + iTermOff += iVal; + if( (iTermIdx+nByte)>=nTermIdx ) break; + iTermIdx += nByte; + } + nTermIdx = iTermIdx; + + memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx); + fts5PutU16(&pTerm->p[2], iTermOff); + + fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx); + if( nTermIdx==0 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno); + } + } + fts5DataRelease(pTerm); + } + } + + if( p->rc==SQLITE_OK ){ + const int nMove = nPg - iNextOff; /* Number of bytes to move */ + int nShift = iNextOff - iOff; /* Distance to move them */ + + int iPrevKeyOut = 0; + int iKeyIn = 0; + + memmove(&aPg[iOff], &aPg[iNextOff], nMove); + iPgIdx -= nShift; + nPg = iPgIdx; + fts5PutU16(&aPg[2], iPgIdx); + + for(iIdx=0; iIdx<nIdx; /* no-op */){ + u32 iVal = 0; + iIdx += fts5GetVarint32(&aIdx[iIdx], iVal); + iKeyIn += iVal; + if( iKeyIn!=iDelKeyOff ){ + int iKeyOut = (iKeyIn - (iKeyIn>iOff ? nShift : 0)); + nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut); + iPrevKeyOut = iKeyOut; + } + } + + if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno); + } + + assert_nc( nPg>4 || fts5GetU16(aPg)==0 ); + fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg, nPg); + } + sqlite3_free(aIdx); +} + +/* +** This is called as part of flushing a delete to disk in 'secure-delete' +** mode. It edits the segments within the database described by argument +** pStruct to remove the entries for term zTerm, rowid iRowid. +*/ +static void fts5FlushSecureDelete( + Fts5Index *p, + Fts5Structure *pStruct, + const char *zTerm, + i64 iRowid +){ + const int f = FTS5INDEX_QUERY_SKIPHASH; + int nTerm = (int)strlen(zTerm); + Fts5Iter *pIter = 0; /* Used to find term instance */ + + fts5MultiIterNew(p, pStruct, f, 0, (const u8*)zTerm, nTerm, -1, 0, &pIter); + if( fts5MultiIterEof(p, pIter)==0 ){ + i64 iThis = fts5MultiIterRowid(pIter); + if( iThis<iRowid ){ + fts5MultiIterNextFrom(p, pIter, iRowid); + } + + if( p->rc==SQLITE_OK + && fts5MultiIterEof(p, pIter)==0 + && iRowid==fts5MultiIterRowid(pIter) + ){ + Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; + fts5DoSecureDelete(p, pSeg); + } + } + + fts5MultiIterFree(pIter); +} + + +/* +** Flush the contents of in-memory hash table iHash to a new level-0 +** segment on disk. Also update the corresponding structure record. +** +** If an error occurs, set the Fts5Index.rc error code. If an error has +** already occurred, this function is a no-op. +*/ +static void fts5FlushOneHash(Fts5Index *p){ + Fts5Hash *pHash = p->pHash; + Fts5Structure *pStruct; + int iSegid; + int pgnoLast = 0; /* Last leaf page number in segment */ + + /* Obtain a reference to the index structure and allocate a new segment-id + ** for the new level-0 segment. */ + pStruct = fts5StructureRead(p); + fts5StructureInvalidate(p); + + if( sqlite3Fts5HashIsEmpty(pHash)==0 ){ + iSegid = fts5AllocateSegid(p, pStruct); + if( iSegid ){ + const int pgsz = p->pConfig->pgsz; + int eDetail = p->pConfig->eDetail; + int bSecureDelete = p->pConfig->bSecureDelete; + Fts5StructureSegment *pSeg; /* New segment within pStruct */ + Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */ + Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */ + + Fts5SegWriter writer; + fts5WriteInit(p, &writer, iSegid); + + pBuf = &writer.writer.buf; + pPgidx = &writer.writer.pgidx; + + /* fts5WriteInit() should have initialized the buffers to (most likely) + ** the maximum space required. */ + assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) ); + assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) ); + + /* Begin scanning through hash table entries. This loop runs once for each + ** term/doclist currently stored within the hash table. */ + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0); + } + while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){ + const char *zTerm; /* Buffer containing term */ + int nTerm; /* Size of zTerm in bytes */ + const u8 *pDoclist; /* Pointer to doclist for this term */ + int nDoclist; /* Size of doclist in bytes */ + + /* Get the term and doclist for this entry. */ + sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist); + nTerm = (int)strlen(zTerm); + if( bSecureDelete==0 ){ + fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); + if( p->rc!=SQLITE_OK ) break; + assert( writer.bFirstRowidInPage==0 ); + } + + if( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){ + /* The entire doclist will fit on the current leaf. */ + fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist); + }else{ + int bTermWritten = !bSecureDelete; + i64 iRowid = 0; + i64 iPrev = 0; + int iOff = 0; + + /* The entire doclist will not fit on this leaf. The following + ** loop iterates through the poslists that make up the current + ** doclist. */ + while( p->rc==SQLITE_OK && iOff<nDoclist ){ + u64 iDelta = 0; + iOff += fts5GetVarint(&pDoclist[iOff], &iDelta); + iRowid += iDelta; + + /* If in secure delete mode, and if this entry in the poslist is + ** in fact a delete, then edit the existing segments directly + ** using fts5FlushSecureDelete(). */ + if( bSecureDelete ){ + if( eDetail==FTS5_DETAIL_NONE ){ + if( iOff<nDoclist && pDoclist[iOff]==0x00 ){ + fts5FlushSecureDelete(p, pStruct, zTerm, iRowid); + iOff++; + if( iOff<nDoclist && pDoclist[iOff]==0x00 ){ + iOff++; + nDoclist = 0; + }else{ + continue; + } + } + }else if( (pDoclist[iOff] & 0x01) ){ + fts5FlushSecureDelete(p, pStruct, zTerm, iRowid); + if( p->rc!=SQLITE_OK || pDoclist[iOff]==0x01 ){ + iOff++; + continue; + } + } + } + + if( p->rc==SQLITE_OK && bTermWritten==0 ){ + fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); + bTermWritten = 1; + assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 ); + } + + if( writer.bFirstRowidInPage ){ + fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */ + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid); + writer.bFirstRowidInPage = 0; + fts5WriteDlidxAppend(p, &writer, iRowid); + }else{ + u64 iRowidDelta = (u64)iRowid - (u64)iPrev; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowidDelta); + } + if( p->rc!=SQLITE_OK ) break; + assert( pBuf->n<=pBuf->nSpace ); + iPrev = iRowid; + + if( eDetail==FTS5_DETAIL_NONE ){ + if( iOff<nDoclist && pDoclist[iOff]==0 ){ + pBuf->p[pBuf->n++] = 0; + iOff++; + if( iOff<nDoclist && pDoclist[iOff]==0 ){ + pBuf->p[pBuf->n++] = 0; + iOff++; + } + } + if( (pBuf->n + pPgidx->n)>=pgsz ){ + fts5WriteFlushLeaf(p, &writer); + } + }else{ + int bDummy; + int nPos; + int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); + nCopy += nPos; + if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ + /* The entire poslist will fit on the current leaf. So copy + ** it in one go. */ + fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); + }else{ + /* The entire poslist will not fit on this leaf. So it needs + ** to be broken into sections. The only qualification being + ** that each varint must be stored contiguously. */ + const u8 *pPoslist = &pDoclist[iOff]; + int iPos = 0; + while( p->rc==SQLITE_OK ){ + int nSpace = pgsz - pBuf->n - pPgidx->n; + int n = 0; + if( (nCopy - iPos)<=nSpace ){ + n = nCopy - iPos; + }else{ + n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); + } + assert( n>0 ); + fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); + iPos += n; + if( (pBuf->n + pPgidx->n)>=pgsz ){ + fts5WriteFlushLeaf(p, &writer); + } + if( iPos>=nCopy ) break; + } + } + iOff += nCopy; + } + } + } + + /* TODO2: Doclist terminator written here. */ + /* pBuf->p[pBuf->n++] = '\0'; */ + assert( pBuf->n<=pBuf->nSpace ); + if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash); + } + sqlite3Fts5HashClear(pHash); + fts5WriteFinish(p, &writer, &pgnoLast); + + assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 ); + if( pgnoLast>0 ){ + /* Update the Fts5Structure. It is written back to the database by the + ** fts5StructureRelease() call below. */ + if( pStruct->nLevel==0 ){ + fts5StructureAddLevel(&p->rc, &pStruct); + } + fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0); + if( p->rc==SQLITE_OK ){ + pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ]; + pSeg->iSegid = iSegid; + pSeg->pgnoFirst = 1; + pSeg->pgnoLast = pgnoLast; + if( pStruct->nOriginCntr>0 ){ + pSeg->iOrigin1 = pStruct->nOriginCntr; + pSeg->iOrigin2 = pStruct->nOriginCntr; + pSeg->nEntry = p->nPendingRow; + pStruct->nOriginCntr++; + } + pStruct->nSegment++; + } + fts5StructurePromote(p, 0, pStruct); + } + } + } + + fts5IndexAutomerge(p, &pStruct, pgnoLast + p->nContentlessDelete); + fts5IndexCrisismerge(p, &pStruct); + fts5StructureWrite(p, pStruct); + fts5StructureRelease(pStruct); + p->nContentlessDelete = 0; +} + +/* +** Flush any data stored in the in-memory hash tables to the database. +*/ +static void fts5IndexFlush(Fts5Index *p){ + /* Unless it is empty, flush the hash table to disk */ + if( p->nPendingData || p->nContentlessDelete ){ + assert( p->pHash ); + fts5FlushOneHash(p); + p->nPendingData = 0; + p->nPendingRow = 0; + } +} + +static Fts5Structure *fts5IndexOptimizeStruct( + Fts5Index *p, + Fts5Structure *pStruct +){ + Fts5Structure *pNew = 0; + sqlite3_int64 nByte = sizeof(Fts5Structure); + int nSeg = pStruct->nSegment; + int i; + + /* Figure out if this structure requires optimization. A structure does + ** not require optimization if either: + ** + ** 1. it consists of fewer than two segments, or + ** 2. all segments are on the same level, or + ** 3. all segments except one are currently inputs to a merge operation. + ** + ** In the first case, if there are no tombstone hash pages, return NULL. In + ** the second, increment the ref-count on *pStruct and return a copy of the + ** pointer to it. + */ + if( nSeg==0 ) return 0; + for(i=0; i<pStruct->nLevel; i++){ + int nThis = pStruct->aLevel[i].nSeg; + int nMerge = pStruct->aLevel[i].nMerge; + if( nThis>0 && (nThis==nSeg || (nThis==nSeg-1 && nMerge==nThis)) ){ + if( nSeg==1 && nThis==1 && pStruct->aLevel[i].aSeg[0].nPgTombstone==0 ){ + return 0; + } + fts5StructureRef(pStruct); + return pStruct; + } + assert( pStruct->aLevel[i].nMerge<=nThis ); + } + + nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel); + pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte); + + if( pNew ){ + Fts5StructureLevel *pLvl; + nByte = nSeg * sizeof(Fts5StructureSegment); + pNew->nLevel = MIN(pStruct->nLevel+1, FTS5_MAX_LEVEL); + pNew->nRef = 1; + pNew->nWriteCounter = pStruct->nWriteCounter; + pNew->nOriginCntr = pStruct->nOriginCntr; + pLvl = &pNew->aLevel[pNew->nLevel-1]; + pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte); + if( pLvl->aSeg ){ + int iLvl, iSeg; + int iSegOut = 0; + /* Iterate through all segments, from oldest to newest. Add them to + ** the new Fts5Level object so that pLvl->aSeg[0] is the oldest + ** segment in the data structure. */ + for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){ + for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){ + pLvl->aSeg[iSegOut] = pStruct->aLevel[iLvl].aSeg[iSeg]; + iSegOut++; + } + } + pNew->nSegment = pLvl->nSeg = nSeg; + }else{ + sqlite3_free(pNew); + pNew = 0; + } + } + + return pNew; +} + +static int sqlite3Fts5IndexOptimize(Fts5Index *p){ + Fts5Structure *pStruct; + Fts5Structure *pNew = 0; + + assert( p->rc==SQLITE_OK ); + fts5IndexFlush(p); + assert( p->nContentlessDelete==0 ); + pStruct = fts5StructureRead(p); + fts5StructureInvalidate(p); + + if( pStruct ){ + pNew = fts5IndexOptimizeStruct(p, pStruct); + } + fts5StructureRelease(pStruct); + + assert( pNew==0 || pNew->nSegment>0 ); + if( pNew ){ + int iLvl; + for(iLvl=0; pNew->aLevel[iLvl].nSeg==0; iLvl++){} + while( p->rc==SQLITE_OK && pNew->aLevel[iLvl].nSeg>0 ){ + int nRem = FTS5_OPT_WORK_UNIT; + fts5IndexMergeLevel(p, &pNew, iLvl, &nRem); + } + + fts5StructureWrite(p, pNew); + fts5StructureRelease(pNew); + } + + return fts5IndexReturn(p); +} + +/* +** This is called to implement the special "VALUES('merge', $nMerge)" +** INSERT command. +*/ +static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ + Fts5Structure *pStruct = 0; + + fts5IndexFlush(p); + pStruct = fts5StructureRead(p); + if( pStruct ){ + int nMin = p->pConfig->nUsermerge; + fts5StructureInvalidate(p); + if( nMerge<0 ){ + Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct); + fts5StructureRelease(pStruct); + pStruct = pNew; + nMin = 1; + nMerge = nMerge*-1; + } + if( pStruct && pStruct->nLevel ){ + if( fts5IndexMerge(p, &pStruct, nMerge, nMin) ){ + fts5StructureWrite(p, pStruct); + } + } + fts5StructureRelease(pStruct); + } + return fts5IndexReturn(p); +} + +static void fts5AppendRowid( + Fts5Index *p, + u64 iDelta, + Fts5Iter *pUnused, + Fts5Buffer *pBuf +){ + UNUSED_PARAM(pUnused); + fts5BufferAppendVarint(&p->rc, pBuf, iDelta); +} + +static void fts5AppendPoslist( + Fts5Index *p, + u64 iDelta, + Fts5Iter *pMulti, + Fts5Buffer *pBuf +){ + int nData = pMulti->base.nData; + int nByte = nData + 9 + 9 + FTS5_DATA_ZERO_PADDING; + assert( nData>0 ); + if( p->rc==SQLITE_OK && 0==fts5BufferGrow(&p->rc, pBuf, nByte) ){ + fts5BufferSafeAppendVarint(pBuf, iDelta); + fts5BufferSafeAppendVarint(pBuf, nData*2); + fts5BufferSafeAppendBlob(pBuf, pMulti->base.pData, nData); + memset(&pBuf->p[pBuf->n], 0, FTS5_DATA_ZERO_PADDING); + } +} + + +static void fts5DoclistIterNext(Fts5DoclistIter *pIter){ + u8 *p = pIter->aPoslist + pIter->nSize + pIter->nPoslist; + + assert( pIter->aPoslist || (p==0 && pIter->aPoslist==0) ); + if( p>=pIter->aEof ){ + pIter->aPoslist = 0; + }else{ + i64 iDelta; + + p += fts5GetVarint(p, (u64*)&iDelta); + pIter->iRowid += iDelta; + + /* Read position list size */ + if( p[0] & 0x80 ){ + int nPos; + pIter->nSize = fts5GetVarint32(p, nPos); + pIter->nPoslist = (nPos>>1); + }else{ + pIter->nPoslist = ((int)(p[0])) >> 1; + pIter->nSize = 1; + } + + pIter->aPoslist = p; + if( &pIter->aPoslist[pIter->nPoslist]>pIter->aEof ){ + pIter->aPoslist = 0; + } + } +} + +static void fts5DoclistIterInit( + Fts5Buffer *pBuf, + Fts5DoclistIter *pIter +){ + memset(pIter, 0, sizeof(*pIter)); + if( pBuf->n>0 ){ + pIter->aPoslist = pBuf->p; + pIter->aEof = &pBuf->p[pBuf->n]; + fts5DoclistIterNext(pIter); + } +} + +#if 0 +/* +** Append a doclist to buffer pBuf. +** +** This function assumes that space within the buffer has already been +** allocated. +*/ +static void fts5MergeAppendDocid( + Fts5Buffer *pBuf, /* Buffer to write to */ + i64 *piLastRowid, /* IN/OUT: Previous rowid written (if any) */ + i64 iRowid /* Rowid to append */ +){ + assert( pBuf->n!=0 || (*piLastRowid)==0 ); + fts5BufferSafeAppendVarint(pBuf, iRowid - *piLastRowid); + *piLastRowid = iRowid; +} +#endif + +#define fts5MergeAppendDocid(pBuf, iLastRowid, iRowid) { \ + assert( (pBuf)->n!=0 || (iLastRowid)==0 ); \ + fts5BufferSafeAppendVarint((pBuf), (u64)(iRowid) - (u64)(iLastRowid)); \ + (iLastRowid) = (iRowid); \ +} + +/* +** Swap the contents of buffer *p1 with that of *p2. +*/ +static void fts5BufferSwap(Fts5Buffer *p1, Fts5Buffer *p2){ + Fts5Buffer tmp = *p1; + *p1 = *p2; + *p2 = tmp; +} + +static void fts5NextRowid(Fts5Buffer *pBuf, int *piOff, i64 *piRowid){ + int i = *piOff; + if( i>=pBuf->n ){ + *piOff = -1; + }else{ + u64 iVal; + *piOff = i + sqlite3Fts5GetVarint(&pBuf->p[i], &iVal); + *piRowid += iVal; + } +} + +/* +** This is the equivalent of fts5MergePrefixLists() for detail=none mode. +** In this case the buffers consist of a delta-encoded list of rowids only. +*/ +static void fts5MergeRowidLists( + Fts5Index *p, /* FTS5 backend object */ + Fts5Buffer *p1, /* First list to merge */ + int nBuf, /* Number of entries in apBuf[] */ + Fts5Buffer *aBuf /* Array of other lists to merge into p1 */ +){ + int i1 = 0; + int i2 = 0; + i64 iRowid1 = 0; + i64 iRowid2 = 0; + i64 iOut = 0; + Fts5Buffer *p2 = &aBuf[0]; + Fts5Buffer out; + + (void)nBuf; + memset(&out, 0, sizeof(out)); + assert( nBuf==1 ); + sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n); + if( p->rc ) return; + + fts5NextRowid(p1, &i1, &iRowid1); + fts5NextRowid(p2, &i2, &iRowid2); + while( i1>=0 || i2>=0 ){ + if( i1>=0 && (i2<0 || iRowid1<iRowid2) ){ + assert( iOut==0 || iRowid1>iOut ); + fts5BufferSafeAppendVarint(&out, iRowid1 - iOut); + iOut = iRowid1; + fts5NextRowid(p1, &i1, &iRowid1); + }else{ + assert( iOut==0 || iRowid2>iOut ); + fts5BufferSafeAppendVarint(&out, iRowid2 - iOut); + iOut = iRowid2; + if( i1>=0 && iRowid1==iRowid2 ){ + fts5NextRowid(p1, &i1, &iRowid1); + } + fts5NextRowid(p2, &i2, &iRowid2); + } + } + + fts5BufferSwap(&out, p1); + fts5BufferFree(&out); +} + +typedef struct PrefixMerger PrefixMerger; +struct PrefixMerger { + Fts5DoclistIter iter; /* Doclist iterator */ + i64 iPos; /* For iterating through a position list */ + int iOff; + u8 *aPos; + PrefixMerger *pNext; /* Next in docid/poslist order */ +}; + +static void fts5PrefixMergerInsertByRowid( + PrefixMerger **ppHead, + PrefixMerger *p +){ + if( p->iter.aPoslist ){ + PrefixMerger **pp = ppHead; + while( *pp && p->iter.iRowid>(*pp)->iter.iRowid ){ + pp = &(*pp)->pNext; + } + p->pNext = *pp; + *pp = p; + } +} + +static void fts5PrefixMergerInsertByPosition( + PrefixMerger **ppHead, + PrefixMerger *p +){ + if( p->iPos>=0 ){ + PrefixMerger **pp = ppHead; + while( *pp && p->iPos>(*pp)->iPos ){ + pp = &(*pp)->pNext; + } + p->pNext = *pp; + *pp = p; + } +} + + +/* +** Array aBuf[] contains nBuf doclists. These are all merged in with the +** doclist in buffer p1. +*/ +static void fts5MergePrefixLists( + Fts5Index *p, /* FTS5 backend object */ + Fts5Buffer *p1, /* First list to merge */ + int nBuf, /* Number of buffers in array aBuf[] */ + Fts5Buffer *aBuf /* Other lists to merge in */ +){ +#define fts5PrefixMergerNextPosition(p) \ + sqlite3Fts5PoslistNext64((p)->aPos,(p)->iter.nPoslist,&(p)->iOff,&(p)->iPos) +#define FTS5_MERGE_NLIST 16 + PrefixMerger aMerger[FTS5_MERGE_NLIST]; + PrefixMerger *pHead = 0; + int i; + int nOut = 0; + Fts5Buffer out = {0, 0, 0}; + Fts5Buffer tmp = {0, 0, 0}; + i64 iLastRowid = 0; + + /* Initialize a doclist-iterator for each input buffer. Arrange them in + ** a linked-list starting at pHead in ascending order of rowid. Avoid + ** linking any iterators already at EOF into the linked list at all. */ + assert( nBuf+1<=(int)(sizeof(aMerger)/sizeof(aMerger[0])) ); + memset(aMerger, 0, sizeof(PrefixMerger)*(nBuf+1)); + pHead = &aMerger[nBuf]; + fts5DoclistIterInit(p1, &pHead->iter); + for(i=0; i<nBuf; i++){ + fts5DoclistIterInit(&aBuf[i], &aMerger[i].iter); + fts5PrefixMergerInsertByRowid(&pHead, &aMerger[i]); + nOut += aBuf[i].n; + } + if( nOut==0 ) return; + nOut += p1->n + 9 + 10*nBuf; + + /* The maximum size of the output is equal to the sum of the + ** input sizes + 1 varint (9 bytes). The extra varint is because if the + ** first rowid in one input is a large negative number, and the first in + ** the other a non-negative number, the delta for the non-negative + ** number will be larger on disk than the literal integer value + ** was. + ** + ** Or, if the input position-lists are corrupt, then the output might + ** include up to (nBuf+1) extra 10-byte positions created by interpreting -1 + ** (the value PoslistNext64() uses for EOF) as a position and appending + ** it to the output. This can happen at most once for each input + ** position-list, hence (nBuf+1) 10 byte paddings. */ + if( sqlite3Fts5BufferSize(&p->rc, &out, nOut) ) return; + + while( pHead ){ + fts5MergeAppendDocid(&out, iLastRowid, pHead->iter.iRowid); + + if( pHead->pNext && iLastRowid==pHead->pNext->iter.iRowid ){ + /* Merge data from two or more poslists */ + i64 iPrev = 0; + int nTmp = FTS5_DATA_ZERO_PADDING; + int nMerge = 0; + PrefixMerger *pSave = pHead; + PrefixMerger *pThis = 0; + int nTail = 0; + + pHead = 0; + while( pSave && pSave->iter.iRowid==iLastRowid ){ + PrefixMerger *pNext = pSave->pNext; + pSave->iOff = 0; + pSave->iPos = 0; + pSave->aPos = &pSave->iter.aPoslist[pSave->iter.nSize]; + fts5PrefixMergerNextPosition(pSave); + nTmp += pSave->iter.nPoslist + 10; + nMerge++; + fts5PrefixMergerInsertByPosition(&pHead, pSave); + pSave = pNext; + } + + if( pHead==0 || pHead->pNext==0 ){ + p->rc = FTS5_CORRUPT; + break; + } + + /* See the earlier comment in this function for an explanation of why + ** corrupt input position lists might cause the output to consume + ** at most nMerge*10 bytes of unexpected space. */ + if( sqlite3Fts5BufferSize(&p->rc, &tmp, nTmp+nMerge*10) ){ + break; + } + fts5BufferZero(&tmp); + + pThis = pHead; + pHead = pThis->pNext; + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos); + fts5PrefixMergerNextPosition(pThis); + fts5PrefixMergerInsertByPosition(&pHead, pThis); + + while( pHead->pNext ){ + pThis = pHead; + if( pThis->iPos!=iPrev ){ + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos); + } + fts5PrefixMergerNextPosition(pThis); + pHead = pThis->pNext; + fts5PrefixMergerInsertByPosition(&pHead, pThis); + } + + if( pHead->iPos!=iPrev ){ + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pHead->iPos); + } + nTail = pHead->iter.nPoslist - pHead->iOff; + + /* WRITEPOSLISTSIZE */ + assert_nc( tmp.n+nTail<=nTmp ); + assert( tmp.n+nTail<=nTmp+nMerge*10 ); + if( tmp.n+nTail>nTmp-FTS5_DATA_ZERO_PADDING ){ + if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; + break; + } + fts5BufferSafeAppendVarint(&out, (tmp.n+nTail) * 2); + fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n); + if( nTail>0 ){ + fts5BufferSafeAppendBlob(&out, &pHead->aPos[pHead->iOff], nTail); + } + + pHead = pSave; + for(i=0; i<nBuf+1; i++){ + PrefixMerger *pX = &aMerger[i]; + if( pX->iter.aPoslist && pX->iter.iRowid==iLastRowid ){ + fts5DoclistIterNext(&pX->iter); + fts5PrefixMergerInsertByRowid(&pHead, pX); + } + } + + }else{ + /* Copy poslist from pHead to output */ + PrefixMerger *pThis = pHead; + Fts5DoclistIter *pI = &pThis->iter; + fts5BufferSafeAppendBlob(&out, pI->aPoslist, pI->nPoslist+pI->nSize); + fts5DoclistIterNext(pI); + pHead = pThis->pNext; + fts5PrefixMergerInsertByRowid(&pHead, pThis); + } + } + + fts5BufferFree(p1); + fts5BufferFree(&tmp); + memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING); + *p1 = out; +} + +static void fts5SetupPrefixIter( + Fts5Index *p, /* Index to read from */ + int bDesc, /* True for "ORDER BY rowid DESC" */ + int iIdx, /* Index to scan for data */ + u8 *pToken, /* Buffer containing prefix to match */ + int nToken, /* Size of buffer pToken in bytes */ + Fts5Colset *pColset, /* Restrict matches to these columns */ + Fts5Iter **ppIter /* OUT: New iterator */ +){ + Fts5Structure *pStruct; + Fts5Buffer *aBuf; + int nBuf = 32; + int nMerge = 1; + + void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*); + void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*); + if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ + xMerge = fts5MergeRowidLists; + xAppend = fts5AppendRowid; + }else{ + nMerge = FTS5_MERGE_NLIST-1; + nBuf = nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */ + xMerge = fts5MergePrefixLists; + xAppend = fts5AppendPoslist; + } + + aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf); + pStruct = fts5StructureRead(p); + + if( aBuf && pStruct ){ + const int flags = FTS5INDEX_QUERY_SCAN + | FTS5INDEX_QUERY_SKIPEMPTY + | FTS5INDEX_QUERY_NOOUTPUT; + int i; + i64 iLastRowid = 0; + Fts5Iter *p1 = 0; /* Iterator used to gather data from index */ + Fts5Data *pData; + Fts5Buffer doclist; + int bNewTerm = 1; + + memset(&doclist, 0, sizeof(doclist)); + if( iIdx!=0 ){ + int dummy = 0; + const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT; + pToken[0] = FTS5_MAIN_PREFIX; + fts5MultiIterNew(p, pStruct, f2, pColset, pToken, nToken, -1, 0, &p1); + fts5IterSetOutputCb(&p->rc, p1); + for(; + fts5MultiIterEof(p, p1)==0; + fts5MultiIterNext2(p, p1, &dummy) + ){ + Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; + p1->xSetOutputs(p1, pSeg); + if( p1->base.nData ){ + xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist); + iLastRowid = p1->base.iRowid; + } + } + fts5MultiIterFree(p1); + } + + pToken[0] = FTS5_MAIN_PREFIX + iIdx; + fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); + fts5IterSetOutputCb(&p->rc, p1); + for( /* no-op */ ; + fts5MultiIterEof(p, p1)==0; + fts5MultiIterNext2(p, p1, &bNewTerm) + ){ + Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; + int nTerm = pSeg->term.n; + const u8 *pTerm = pSeg->term.p; + p1->xSetOutputs(p1, pSeg); + + assert_nc( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 ); + if( bNewTerm ){ + if( nTerm<nToken || memcmp(pToken, pTerm, nToken) ) break; + } + + if( p1->base.nData==0 ) continue; + + if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){ + for(i=0; p->rc==SQLITE_OK && doclist.n; i++){ + int i1 = i*nMerge; + int iStore; + assert( i1+nMerge<=nBuf ); + for(iStore=i1; iStore<i1+nMerge; iStore++){ + if( aBuf[iStore].n==0 ){ + fts5BufferSwap(&doclist, &aBuf[iStore]); + fts5BufferZero(&doclist); + break; + } + } + if( iStore==i1+nMerge ){ + xMerge(p, &doclist, nMerge, &aBuf[i1]); + for(iStore=i1; iStore<i1+nMerge; iStore++){ + fts5BufferZero(&aBuf[iStore]); + } + } + } + iLastRowid = 0; + } + + xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist); + iLastRowid = p1->base.iRowid; + } + + assert( (nBuf%nMerge)==0 ); + for(i=0; i<nBuf; i+=nMerge){ + int iFree; + if( p->rc==SQLITE_OK ){ + xMerge(p, &doclist, nMerge, &aBuf[i]); + } + for(iFree=i; iFree<i+nMerge; iFree++){ + fts5BufferFree(&aBuf[iFree]); + } + } + fts5MultiIterFree(p1); + + pData = fts5IdxMalloc(p, sizeof(Fts5Data)+doclist.n+FTS5_DATA_ZERO_PADDING); + if( pData ){ + pData->p = (u8*)&pData[1]; + pData->nn = pData->szLeaf = doclist.n; + if( doclist.n ) memcpy(pData->p, doclist.p, doclist.n); + fts5MultiIterNew2(p, pData, bDesc, ppIter); + } + fts5BufferFree(&doclist); + } + + fts5StructureRelease(pStruct); + sqlite3_free(aBuf); +} + + +/* +** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain +** to the document with rowid iRowid. +*/ +static int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ + assert( p->rc==SQLITE_OK ); + + /* Allocate the hash table if it has not already been allocated */ + if( p->pHash==0 ){ + p->rc = sqlite3Fts5HashNew(p->pConfig, &p->pHash, &p->nPendingData); + } + + /* Flush the hash table to disk if required */ + if( iRowid<p->iWriteRowid + || (iRowid==p->iWriteRowid && p->bDelete==0) + || (p->nPendingData > p->pConfig->nHashSize) + ){ + fts5IndexFlush(p); + } + + p->iWriteRowid = iRowid; + p->bDelete = bDelete; + if( bDelete==0 ){ + p->nPendingRow++; + } + return fts5IndexReturn(p); +} + +/* +** Commit data to disk. +*/ +static int sqlite3Fts5IndexSync(Fts5Index *p){ + assert( p->rc==SQLITE_OK ); + fts5IndexFlush(p); + sqlite3Fts5IndexCloseReader(p); + return fts5IndexReturn(p); +} + +/* +** Discard any data stored in the in-memory hash tables. Do not write it +** to the database. Additionally, assume that the contents of the %_data +** table may have changed on disk. So any in-memory caches of %_data +** records must be invalidated. +*/ +static int sqlite3Fts5IndexRollback(Fts5Index *p){ + sqlite3Fts5IndexCloseReader(p); + fts5IndexDiscardData(p); + fts5StructureInvalidate(p); + /* assert( p->rc==SQLITE_OK ); */ + return SQLITE_OK; +} + +/* +** The %_data table is completely empty when this function is called. This +** function populates it with the initial structure objects for each index, +** and the initial version of the "averages" record (a zero-byte blob). +*/ +static int sqlite3Fts5IndexReinit(Fts5Index *p){ + Fts5Structure s; + fts5StructureInvalidate(p); + fts5IndexDiscardData(p); + memset(&s, 0, sizeof(Fts5Structure)); + if( p->pConfig->bContentlessDelete ){ + s.nOriginCntr = 1; + } + fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); + fts5StructureWrite(p, &s); + return fts5IndexReturn(p); +} + +/* +** Open a new Fts5Index handle. If the bCreate argument is true, create +** and initialize the underlying %_data table. +** +** If successful, set *pp to point to the new object and return SQLITE_OK. +** Otherwise, set *pp to NULL and return an SQLite error code. +*/ +static int sqlite3Fts5IndexOpen( + Fts5Config *pConfig, + int bCreate, + Fts5Index **pp, + char **pzErr +){ + int rc = SQLITE_OK; + Fts5Index *p; /* New object */ + + *pp = p = (Fts5Index*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Index)); + if( rc==SQLITE_OK ){ + p->pConfig = pConfig; + p->nWorkUnit = FTS5_WORK_UNIT; + p->zDataTbl = sqlite3Fts5Mprintf(&rc, "%s_data", pConfig->zName); + if( p->zDataTbl && bCreate ){ + rc = sqlite3Fts5CreateTable( + pConfig, "data", "id INTEGER PRIMARY KEY, block BLOB", 0, pzErr + ); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5CreateTable(pConfig, "idx", + "segid, term, pgno, PRIMARY KEY(segid, term)", + 1, pzErr + ); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexReinit(p); + } + } + } + + assert( rc!=SQLITE_OK || p->rc==SQLITE_OK ); + if( rc ){ + sqlite3Fts5IndexClose(p); + *pp = 0; + } + return rc; +} + +/* +** Close a handle opened by an earlier call to sqlite3Fts5IndexOpen(). +*/ +static int sqlite3Fts5IndexClose(Fts5Index *p){ + int rc = SQLITE_OK; + if( p ){ + assert( p->pReader==0 ); + fts5StructureInvalidate(p); + sqlite3_finalize(p->pWriter); + sqlite3_finalize(p->pDeleter); + sqlite3_finalize(p->pIdxWriter); + sqlite3_finalize(p->pIdxDeleter); + sqlite3_finalize(p->pIdxSelect); + sqlite3_finalize(p->pDataVersion); + sqlite3_finalize(p->pDeleteFromIdx); + sqlite3Fts5HashFree(p->pHash); + sqlite3_free(p->zDataTbl); + sqlite3_free(p); + } + return rc; +} + +/* +** Argument p points to a buffer containing utf-8 text that is n bytes in +** size. Return the number of bytes in the nChar character prefix of the +** buffer, or 0 if there are less than nChar characters in total. +*/ +static int sqlite3Fts5IndexCharlenToBytelen( + const char *p, + int nByte, + int nChar +){ + int n = 0; + int i; + for(i=0; i<nChar; i++){ + if( n>=nByte ) return 0; /* Input contains fewer than nChar chars */ + if( (unsigned char)p[n++]>=0xc0 ){ + if( n>=nByte ) return 0; + while( (p[n] & 0xc0)==0x80 ){ + n++; + if( n>=nByte ){ + if( i+1==nChar ) break; + return 0; + } + } + } + } + return n; +} + +/* +** pIn is a UTF-8 encoded string, nIn bytes in size. Return the number of +** unicode characters in the string. +*/ +static int fts5IndexCharlen(const char *pIn, int nIn){ + int nChar = 0; + int i = 0; + while( i<nIn ){ + if( (unsigned char)pIn[i++]>=0xc0 ){ + while( i<nIn && (pIn[i] & 0xc0)==0x80 ) i++; + } + nChar++; + } + return nChar; +} + +/* +** Insert or remove data to or from the index. Each time a document is +** added to or removed from the index, this function is called one or more +** times. +** +** For an insert, it must be called once for each token in the new document. +** If the operation is a delete, it must be called (at least) once for each +** unique token in the document with an iCol value less than zero. The iPos +** argument is ignored for a delete. +*/ +static int sqlite3Fts5IndexWrite( + Fts5Index *p, /* Index to write to */ + int iCol, /* Column token appears in (-ve -> delete) */ + int iPos, /* Position of token within column */ + const char *pToken, int nToken /* Token to add or remove to or from index */ +){ + int i; /* Used to iterate through indexes */ + int rc = SQLITE_OK; /* Return code */ + Fts5Config *pConfig = p->pConfig; + + assert( p->rc==SQLITE_OK ); + assert( (iCol<0)==p->bDelete ); + + /* Add the entry to the main terms index. */ + rc = sqlite3Fts5HashWrite( + p->pHash, p->iWriteRowid, iCol, iPos, FTS5_MAIN_PREFIX, pToken, nToken + ); + + for(i=0; i<pConfig->nPrefix && rc==SQLITE_OK; i++){ + const int nChar = pConfig->aPrefix[i]; + int nByte = sqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar); + if( nByte ){ + rc = sqlite3Fts5HashWrite(p->pHash, + p->iWriteRowid, iCol, iPos, (char)(FTS5_MAIN_PREFIX+i+1), pToken, + nByte + ); + } + } + + return rc; +} + +/* +** Open a new iterator to iterate though all rowid that match the +** specified token or token prefix. +*/ +static int sqlite3Fts5IndexQuery( + Fts5Index *p, /* FTS index to query */ + const char *pToken, int nToken, /* Token (or prefix) to query for */ + int flags, /* Mask of FTS5INDEX_QUERY_X flags */ + Fts5Colset *pColset, /* Match these columns only */ + Fts5IndexIter **ppIter /* OUT: New iterator object */ +){ + Fts5Config *pConfig = p->pConfig; + Fts5Iter *pRet = 0; + Fts5Buffer buf = {0, 0, 0}; + + /* If the QUERY_SCAN flag is set, all other flags must be clear. */ + assert( (flags & FTS5INDEX_QUERY_SCAN)==0 || flags==FTS5INDEX_QUERY_SCAN ); + + if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ + int iIdx = 0; /* Index to search */ + int iPrefixIdx = 0; /* +1 prefix index */ + if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken); + + /* Figure out which index to search and set iIdx accordingly. If this + ** is a prefix query for which there is no prefix index, set iIdx to + ** greater than pConfig->nPrefix to indicate that the query will be + ** satisfied by scanning multiple terms in the main index. + ** + ** If the QUERY_TEST_NOIDX flag was specified, then this must be a + ** prefix-query. Instead of using a prefix-index (if one exists), + ** evaluate the prefix query using the main FTS index. This is used + ** for internal sanity checking by the integrity-check in debug + ** mode only. */ +#ifdef SQLITE_DEBUG + if( pConfig->bPrefixIndex==0 || (flags & FTS5INDEX_QUERY_TEST_NOIDX) ){ + assert( flags & FTS5INDEX_QUERY_PREFIX ); + iIdx = 1+pConfig->nPrefix; + }else +#endif + if( flags & FTS5INDEX_QUERY_PREFIX ){ + int nChar = fts5IndexCharlen(pToken, nToken); + for(iIdx=1; iIdx<=pConfig->nPrefix; iIdx++){ + int nIdxChar = pConfig->aPrefix[iIdx-1]; + if( nIdxChar==nChar ) break; + if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx; + } + } + + if( iIdx<=pConfig->nPrefix ){ + /* Straight index lookup */ + Fts5Structure *pStruct = fts5StructureRead(p); + buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx); + if( pStruct ){ + fts5MultiIterNew(p, pStruct, flags | FTS5INDEX_QUERY_SKIPEMPTY, + pColset, buf.p, nToken+1, -1, 0, &pRet + ); + fts5StructureRelease(pStruct); + } + }else{ + /* Scan multiple terms in the main index */ + int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0; + fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet); + if( pRet==0 ){ + assert( p->rc!=SQLITE_OK ); + }else{ + assert( pRet->pColset==0 ); + fts5IterSetOutputCb(&p->rc, pRet); + if( p->rc==SQLITE_OK ){ + Fts5SegIter *pSeg = &pRet->aSeg[pRet->aFirst[1].iFirst]; + if( pSeg->pLeaf ) pRet->xSetOutputs(pRet, pSeg); + } + } + } + + if( p->rc ){ + sqlite3Fts5IterClose((Fts5IndexIter*)pRet); + pRet = 0; + sqlite3Fts5IndexCloseReader(p); + } + + *ppIter = (Fts5IndexIter*)pRet; + sqlite3Fts5BufferFree(&buf); + } + return fts5IndexReturn(p); +} + +/* +** Return true if the iterator passed as the only argument is at EOF. +*/ +/* +** Move to the next matching rowid. +*/ +static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + assert( pIter->pIndex->rc==SQLITE_OK ); + fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); + return fts5IndexReturn(pIter->pIndex); +} + +/* +** Move to the next matching term/rowid. Used by the fts5vocab module. +*/ +static int sqlite3Fts5IterNextScan(Fts5IndexIter *pIndexIter){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + Fts5Index *p = pIter->pIndex; + + assert( pIter->pIndex->rc==SQLITE_OK ); + + fts5MultiIterNext(p, pIter, 0, 0); + if( p->rc==SQLITE_OK ){ + Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; + if( pSeg->pLeaf && pSeg->term.p[0]!=FTS5_MAIN_PREFIX ){ + fts5DataRelease(pSeg->pLeaf); + pSeg->pLeaf = 0; + pIter->base.bEof = 1; + } + } + + return fts5IndexReturn(pIter->pIndex); +} + +/* +** Move to the next matching rowid that occurs at or after iMatch. The +** definition of "at or after" depends on whether this iterator iterates +** in ascending or descending rowid order. +*/ +static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); + return fts5IndexReturn(pIter->pIndex); +} + +/* +** Return the current term. +*/ +static const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){ + int n; + const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n); + assert_nc( z || n<=1 ); + *pn = n-1; + return (z ? &z[1] : 0); +} + +/* +** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery(). +*/ +static void sqlite3Fts5IterClose(Fts5IndexIter *pIndexIter){ + if( pIndexIter ){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + Fts5Index *pIndex = pIter->pIndex; + fts5MultiIterFree(pIter); + sqlite3Fts5IndexCloseReader(pIndex); + } +} + +/* +** Read and decode the "averages" record from the database. +** +** Parameter anSize must point to an array of size nCol, where nCol is +** the number of user defined columns in the FTS table. +*/ +static int sqlite3Fts5IndexGetAverages(Fts5Index *p, i64 *pnRow, i64 *anSize){ + int nCol = p->pConfig->nCol; + Fts5Data *pData; + + *pnRow = 0; + memset(anSize, 0, sizeof(i64) * nCol); + pData = fts5DataRead(p, FTS5_AVERAGES_ROWID); + if( p->rc==SQLITE_OK && pData->nn ){ + int i = 0; + int iCol; + i += fts5GetVarint(&pData->p[i], (u64*)pnRow); + for(iCol=0; i<pData->nn && iCol<nCol; iCol++){ + i += fts5GetVarint(&pData->p[i], (u64*)&anSize[iCol]); + } + } + + fts5DataRelease(pData); + return fts5IndexReturn(p); +} + +/* +** Replace the current "averages" record with the contents of the buffer +** supplied as the second argument. +*/ +static int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8 *pData, int nData){ + assert( p->rc==SQLITE_OK ); + fts5DataWrite(p, FTS5_AVERAGES_ROWID, pData, nData); + return fts5IndexReturn(p); +} + +/* +** Return the total number of blocks this module has read from the %_data +** table since it was created. +*/ +static int sqlite3Fts5IndexReads(Fts5Index *p){ + return p->nRead; +} + +/* +** Set the 32-bit cookie value stored at the start of all structure +** records to the value passed as the second argument. +** +** Return SQLITE_OK if successful, or an SQLite error code if an error +** occurs. +*/ +static int sqlite3Fts5IndexSetCookie(Fts5Index *p, int iNew){ + int rc; /* Return code */ + Fts5Config *pConfig = p->pConfig; /* Configuration object */ + u8 aCookie[4]; /* Binary representation of iNew */ + sqlite3_blob *pBlob = 0; + + assert( p->rc==SQLITE_OK ); + sqlite3Fts5Put32(aCookie, iNew); + + rc = sqlite3_blob_open(pConfig->db, pConfig->zDb, p->zDataTbl, + "block", FTS5_STRUCTURE_ROWID, 1, &pBlob + ); + if( rc==SQLITE_OK ){ + sqlite3_blob_write(pBlob, aCookie, 4, 0); + rc = sqlite3_blob_close(pBlob); + } + + return rc; +} + +static int sqlite3Fts5IndexLoadConfig(Fts5Index *p){ + Fts5Structure *pStruct; + pStruct = fts5StructureRead(p); + fts5StructureRelease(pStruct); + return fts5IndexReturn(p); +} + +/* +** Retrieve the origin value that will be used for the segment currently +** being accumulated in the in-memory hash table when it is flushed to +** disk. If successful, SQLITE_OK is returned and (*piOrigin) set to +** the queried value. Or, if an error occurs, an error code is returned +** and the final value of (*piOrigin) is undefined. +*/ +static int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin){ + Fts5Structure *pStruct; + pStruct = fts5StructureRead(p); + if( pStruct ){ + *piOrigin = pStruct->nOriginCntr; + fts5StructureRelease(pStruct); + } + return fts5IndexReturn(p); +} + +/* +** Buffer pPg contains a page of a tombstone hash table - one of nPg pages +** associated with the same segment. This function adds rowid iRowid to +** the hash table. The caller is required to guarantee that there is at +** least one free slot on the page. +** +** If parameter bForce is false and the hash table is deemed to be full +** (more than half of the slots are occupied), then non-zero is returned +** and iRowid not inserted. Or, if bForce is true or if the hash table page +** is not full, iRowid is inserted and zero returned. +*/ +static int fts5IndexTombstoneAddToPage( + Fts5Data *pPg, + int bForce, + int nPg, + u64 iRowid +){ + const int szKey = TOMBSTONE_KEYSIZE(pPg); + const int nSlot = TOMBSTONE_NSLOT(pPg); + const int nElem = fts5GetU32(&pPg->p[4]); + int iSlot = (iRowid / nPg) % nSlot; + int nCollide = nSlot; + + if( szKey==4 && iRowid>0xFFFFFFFF ) return 2; + if( iRowid==0 ){ + pPg->p[1] = 0x01; + return 0; + } + + if( bForce==0 && nElem>=(nSlot/2) ){ + return 1; + } + + fts5PutU32(&pPg->p[4], nElem+1); + if( szKey==4 ){ + u32 *aSlot = (u32*)&pPg->p[8]; + while( aSlot[iSlot] ){ + iSlot = (iSlot + 1) % nSlot; + if( nCollide--==0 ) return 0; + } + fts5PutU32((u8*)&aSlot[iSlot], (u32)iRowid); + }else{ + u64 *aSlot = (u64*)&pPg->p[8]; + while( aSlot[iSlot] ){ + iSlot = (iSlot + 1) % nSlot; + if( nCollide--==0 ) return 0; + } + fts5PutU64((u8*)&aSlot[iSlot], iRowid); + } + + return 0; +} + +/* +** This function attempts to build a new hash containing all the keys +** currently in the tombstone hash table for segment pSeg. The new +** hash will be stored in the nOut buffers passed in array apOut[]. +** All pages of the new hash use key-size szKey (4 or 8). +** +** Return 0 if the hash is successfully rebuilt into the nOut pages. +** Or non-zero if it is not (because one page became overfull). In this +** case the caller should retry with a larger nOut parameter. +** +** Parameter pData1 is page iPg1 of the hash table being rebuilt. +*/ +static int fts5IndexTombstoneRehash( + Fts5Index *p, + Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */ + Fts5Data *pData1, /* One page of current hash - or NULL */ + int iPg1, /* Which page of the current hash is pData1 */ + int szKey, /* 4 or 8, the keysize */ + int nOut, /* Number of output pages */ + Fts5Data **apOut /* Array of output hash pages */ +){ + int ii; + int res = 0; + + /* Initialize the headers of all the output pages */ + for(ii=0; ii<nOut; ii++){ + apOut[ii]->p[0] = szKey; + fts5PutU32(&apOut[ii]->p[4], 0); + } + + /* Loop through the current pages of the hash table. */ + for(ii=0; res==0 && ii<pSeg->nPgTombstone; ii++){ + Fts5Data *pData = 0; /* Page ii of the current hash table */ + Fts5Data *pFree = 0; /* Free this at the end of the loop */ + + if( iPg1==ii ){ + pData = pData1; + }else{ + pFree = pData = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid, ii)); + } + + if( pData ){ + int szKeyIn = TOMBSTONE_KEYSIZE(pData); + int nSlotIn = (pData->nn - 8) / szKeyIn; + int iIn; + for(iIn=0; iIn<nSlotIn; iIn++){ + u64 iVal = 0; + + /* Read the value from slot iIn of the input page into iVal. */ + if( szKeyIn==4 ){ + u32 *aSlot = (u32*)&pData->p[8]; + if( aSlot[iIn] ) iVal = fts5GetU32((u8*)&aSlot[iIn]); + }else{ + u64 *aSlot = (u64*)&pData->p[8]; + if( aSlot[iIn] ) iVal = fts5GetU64((u8*)&aSlot[iIn]); + } + + /* If iVal is not 0 at this point, insert it into the new hash table */ + if( iVal ){ + Fts5Data *pPg = apOut[(iVal % nOut)]; + res = fts5IndexTombstoneAddToPage(pPg, 0, nOut, iVal); + if( res ) break; + } + } + + /* If this is page 0 of the old hash, copy the rowid-0-flag from the + ** old hash to the new. */ + if( ii==0 ){ + apOut[0]->p[1] = pData->p[1]; + } + } + fts5DataRelease(pFree); + } + + return res; +} + +/* +** This is called to rebuild the hash table belonging to segment pSeg. +** If parameter pData1 is not NULL, then one page of the existing hash table +** has already been loaded - pData1, which is page iPg1. The key-size for +** the new hash table is szKey (4 or 8). +** +** If successful, the new hash table is not written to disk. Instead, +** output parameter (*pnOut) is set to the number of pages in the new +** hash table, and (*papOut) to point to an array of buffers containing +** the new page data. +** +** If an error occurs, an error code is left in the Fts5Index object and +** both output parameters set to 0 before returning. +*/ +static void fts5IndexTombstoneRebuild( + Fts5Index *p, + Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */ + Fts5Data *pData1, /* One page of current hash - or NULL */ + int iPg1, /* Which page of the current hash is pData1 */ + int szKey, /* 4 or 8, the keysize */ + int *pnOut, /* OUT: Number of output pages */ + Fts5Data ***papOut /* OUT: Output hash pages */ +){ + const int MINSLOT = 32; + int nSlotPerPage = MAX(MINSLOT, (p->pConfig->pgsz - 8) / szKey); + int nSlot = 0; /* Number of slots in each output page */ + int nOut = 0; + + /* Figure out how many output pages (nOut) and how many slots per + ** page (nSlot). There are three possibilities: + ** + ** 1. The hash table does not yet exist. In this case the new hash + ** table will consist of a single page with MINSLOT slots. + ** + ** 2. The hash table exists but is currently a single page. In this + ** case an attempt is made to grow the page to accommodate the new + ** entry. The page is allowed to grow up to nSlotPerPage (see above) + ** slots. + ** + ** 3. The hash table already consists of more than one page, or of + ** a single page already so large that it cannot be grown. In this + ** case the new hash consists of (nPg*2+1) pages of nSlotPerPage + ** slots each, where nPg is the current number of pages in the + ** hash table. + */ + if( pSeg->nPgTombstone==0 ){ + /* Case 1. */ + nOut = 1; + nSlot = MINSLOT; + }else if( pSeg->nPgTombstone==1 ){ + /* Case 2. */ + int nElem = (int)fts5GetU32(&pData1->p[4]); + assert( pData1 && iPg1==0 ); + nOut = 1; + nSlot = MAX(nElem*4, MINSLOT); + if( nSlot>nSlotPerPage ) nOut = 0; + } + if( nOut==0 ){ + /* Case 3. */ + nOut = (pSeg->nPgTombstone * 2 + 1); + nSlot = nSlotPerPage; + } + + /* Allocate the required array and output pages */ + while( 1 ){ + int res = 0; + int ii = 0; + int szPage = 0; + Fts5Data **apOut = 0; + + /* Allocate space for the new hash table */ + assert( nSlot>=MINSLOT ); + apOut = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data*) * nOut); + szPage = 8 + nSlot*szKey; + for(ii=0; ii<nOut; ii++){ + Fts5Data *pNew = (Fts5Data*)sqlite3Fts5MallocZero(&p->rc, + sizeof(Fts5Data)+szPage + ); + if( pNew ){ + pNew->nn = szPage; + pNew->p = (u8*)&pNew[1]; + apOut[ii] = pNew; + } + } + + /* Rebuild the hash table. */ + if( p->rc==SQLITE_OK ){ + res = fts5IndexTombstoneRehash(p, pSeg, pData1, iPg1, szKey, nOut, apOut); + } + if( res==0 ){ + if( p->rc ){ + fts5IndexFreeArray(apOut, nOut); + apOut = 0; + nOut = 0; + } + *pnOut = nOut; + *papOut = apOut; + break; + } + + /* If control flows to here, it was not possible to rebuild the hash + ** table. Free all buffers and then try again with more pages. */ + assert( p->rc==SQLITE_OK ); + fts5IndexFreeArray(apOut, nOut); + nSlot = nSlotPerPage; + nOut = nOut*2 + 1; + } +} + + +/* +** Add a tombstone for rowid iRowid to segment pSeg. +*/ +static void fts5IndexTombstoneAdd( + Fts5Index *p, + Fts5StructureSegment *pSeg, + u64 iRowid +){ + Fts5Data *pPg = 0; + int iPg = -1; + int szKey = 0; + int nHash = 0; + Fts5Data **apHash = 0; + + p->nContentlessDelete++; + + if( pSeg->nPgTombstone>0 ){ + iPg = iRowid % pSeg->nPgTombstone; + pPg = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg)); + if( pPg==0 ){ + assert( p->rc!=SQLITE_OK ); + return; + } + + if( 0==fts5IndexTombstoneAddToPage(pPg, 0, pSeg->nPgTombstone, iRowid) ){ + fts5DataWrite(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg), pPg->p, pPg->nn); + fts5DataRelease(pPg); + return; + } + } + + /* Have to rebuild the hash table. First figure out the key-size (4 or 8). */ + szKey = pPg ? TOMBSTONE_KEYSIZE(pPg) : 4; + if( iRowid>0xFFFFFFFF ) szKey = 8; + + /* Rebuild the hash table */ + fts5IndexTombstoneRebuild(p, pSeg, pPg, iPg, szKey, &nHash, &apHash); + assert( p->rc==SQLITE_OK || (nHash==0 && apHash==0) ); + + /* If all has succeeded, write the new rowid into one of the new hash + ** table pages, then write them all out to disk. */ + if( nHash ){ + int ii = 0; + fts5IndexTombstoneAddToPage(apHash[iRowid % nHash], 1, nHash, iRowid); + for(ii=0; ii<nHash; ii++){ + i64 iTombstoneRowid = FTS5_TOMBSTONE_ROWID(pSeg->iSegid, ii); + fts5DataWrite(p, iTombstoneRowid, apHash[ii]->p, apHash[ii]->nn); + } + pSeg->nPgTombstone = nHash; + fts5StructureWrite(p, p->pStruct); + } + + fts5DataRelease(pPg); + fts5IndexFreeArray(apHash, nHash); +} + +/* +** Add iRowid to the tombstone list of the segment or segments that contain +** rows from origin iOrigin. Return SQLITE_OK if successful, or an SQLite +** error code otherwise. +*/ +static int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid){ + Fts5Structure *pStruct; + pStruct = fts5StructureRead(p); + if( pStruct ){ + int bFound = 0; /* True after pSeg->nEntryTombstone incr. */ + int iLvl; + for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){ + int iSeg; + for(iSeg=pStruct->aLevel[iLvl].nSeg-1; iSeg>=0; iSeg--){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + if( pSeg->iOrigin1<=(u64)iOrigin && pSeg->iOrigin2>=(u64)iOrigin ){ + if( bFound==0 ){ + pSeg->nEntryTombstone++; + bFound = 1; + } + fts5IndexTombstoneAdd(p, pSeg, iRowid); + } + } + } + fts5StructureRelease(pStruct); + } + return fts5IndexReturn(p); +} + +/************************************************************************* +************************************************************************** +** Below this point is the implementation of the integrity-check +** functionality. +*/ + +/* +** Return a simple checksum value based on the arguments. +*/ +static u64 sqlite3Fts5IndexEntryCksum( + i64 iRowid, + int iCol, + int iPos, + int iIdx, + const char *pTerm, + int nTerm +){ + int i; + u64 ret = iRowid; + ret += (ret<<3) + iCol; + ret += (ret<<3) + iPos; + if( iIdx>=0 ) ret += (ret<<3) + (FTS5_MAIN_PREFIX + iIdx); + for(i=0; i<nTerm; i++) ret += (ret<<3) + pTerm[i]; + return ret; +} + +#ifdef SQLITE_DEBUG +/* +** This function is purely an internal test. It does not contribute to +** FTS functionality, or even the integrity-check, in any way. +** +** Instead, it tests that the same set of pgno/rowid combinations are +** visited regardless of whether the doclist-index identified by parameters +** iSegid/iLeaf is iterated in forwards or reverse order. +*/ +static void fts5TestDlidxReverse( + Fts5Index *p, + int iSegid, /* Segment id to load from */ + int iLeaf /* Load doclist-index for this leaf */ +){ + Fts5DlidxIter *pDlidx = 0; + u64 cksum1 = 13; + u64 cksum2 = 13; + + for(pDlidx=fts5DlidxIterInit(p, 0, iSegid, iLeaf); + fts5DlidxIterEof(p, pDlidx)==0; + fts5DlidxIterNext(p, pDlidx) + ){ + i64 iRowid = fts5DlidxIterRowid(pDlidx); + int pgno = fts5DlidxIterPgno(pDlidx); + assert( pgno>iLeaf ); + cksum1 += iRowid + ((i64)pgno<<32); + } + fts5DlidxIterFree(pDlidx); + pDlidx = 0; + + for(pDlidx=fts5DlidxIterInit(p, 1, iSegid, iLeaf); + fts5DlidxIterEof(p, pDlidx)==0; + fts5DlidxIterPrev(p, pDlidx) + ){ + i64 iRowid = fts5DlidxIterRowid(pDlidx); + int pgno = fts5DlidxIterPgno(pDlidx); + assert( fts5DlidxIterPgno(pDlidx)>iLeaf ); + cksum2 += iRowid + ((i64)pgno<<32); + } + fts5DlidxIterFree(pDlidx); + pDlidx = 0; + + if( p->rc==SQLITE_OK && cksum1!=cksum2 ) p->rc = FTS5_CORRUPT; +} + +static int fts5QueryCksum( + Fts5Index *p, /* Fts5 index object */ + int iIdx, + const char *z, /* Index key to query for */ + int n, /* Size of index key in bytes */ + int flags, /* Flags for Fts5IndexQuery */ + u64 *pCksum /* IN/OUT: Checksum value */ +){ + int eDetail = p->pConfig->eDetail; + u64 cksum = *pCksum; + Fts5IndexIter *pIter = 0; + int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIter); + + while( rc==SQLITE_OK && ALWAYS(pIter!=0) && 0==sqlite3Fts5IterEof(pIter) ){ + i64 rowid = pIter->iRowid; + + if( eDetail==FTS5_DETAIL_NONE ){ + cksum ^= sqlite3Fts5IndexEntryCksum(rowid, 0, 0, iIdx, z, n); + }else{ + Fts5PoslistReader sReader; + for(sqlite3Fts5PoslistReaderInit(pIter->pData, pIter->nData, &sReader); + sReader.bEof==0; + sqlite3Fts5PoslistReaderNext(&sReader) + ){ + int iCol = FTS5_POS2COLUMN(sReader.iPos); + int iOff = FTS5_POS2OFFSET(sReader.iPos); + cksum ^= sqlite3Fts5IndexEntryCksum(rowid, iCol, iOff, iIdx, z, n); + } + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IterNext(pIter); + } + } + sqlite3Fts5IterClose(pIter); + + *pCksum = cksum; + return rc; +} + +/* +** Check if buffer z[], size n bytes, contains as series of valid utf-8 +** encoded codepoints. If so, return 0. Otherwise, if the buffer does not +** contain valid utf-8, return non-zero. +*/ +static int fts5TestUtf8(const char *z, int n){ + int i = 0; + assert_nc( n>0 ); + while( i<n ){ + if( (z[i] & 0x80)==0x00 ){ + i++; + }else + if( (z[i] & 0xE0)==0xC0 ){ + if( i+1>=n || (z[i+1] & 0xC0)!=0x80 ) return 1; + i += 2; + }else + if( (z[i] & 0xF0)==0xE0 ){ + if( i+2>=n || (z[i+1] & 0xC0)!=0x80 || (z[i+2] & 0xC0)!=0x80 ) return 1; + i += 3; + }else + if( (z[i] & 0xF8)==0xF0 ){ + if( i+3>=n || (z[i+1] & 0xC0)!=0x80 || (z[i+2] & 0xC0)!=0x80 ) return 1; + if( (z[i+2] & 0xC0)!=0x80 ) return 1; + i += 3; + }else{ + return 1; + } + } + + return 0; +} + +/* +** This function is also purely an internal test. It does not contribute to +** FTS functionality, or even the integrity-check, in any way. +*/ +static void fts5TestTerm( + Fts5Index *p, + Fts5Buffer *pPrev, /* Previous term */ + const char *z, int n, /* Possibly new term to test */ + u64 expected, + u64 *pCksum +){ + int rc = p->rc; + if( pPrev->n==0 ){ + fts5BufferSet(&rc, pPrev, n, (const u8*)z); + }else + if( rc==SQLITE_OK && (pPrev->n!=n || memcmp(pPrev->p, z, n)) ){ + u64 cksum3 = *pCksum; + const char *zTerm = (const char*)&pPrev->p[1]; /* term sans prefix-byte */ + int nTerm = pPrev->n-1; /* Size of zTerm in bytes */ + int iIdx = (pPrev->p[0] - FTS5_MAIN_PREFIX); + int flags = (iIdx==0 ? 0 : FTS5INDEX_QUERY_PREFIX); + u64 ck1 = 0; + u64 ck2 = 0; + + /* Check that the results returned for ASC and DESC queries are + ** the same. If not, call this corruption. */ + rc = fts5QueryCksum(p, iIdx, zTerm, nTerm, flags, &ck1); + if( rc==SQLITE_OK ){ + int f = flags|FTS5INDEX_QUERY_DESC; + rc = fts5QueryCksum(p, iIdx, zTerm, nTerm, f, &ck2); + } + if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT; + + /* If this is a prefix query, check that the results returned if the + ** the index is disabled are the same. In both ASC and DESC order. + ** + ** This check may only be performed if the hash table is empty. This + ** is because the hash table only supports a single scan query at + ** a time, and the multi-iter loop from which this function is called + ** is already performing such a scan. + ** + ** Also only do this if buffer zTerm contains nTerm bytes of valid + ** utf-8. Otherwise, the last part of the buffer contents might contain + ** a non-utf-8 sequence that happens to be a prefix of a valid utf-8 + ** character stored in the main fts index, which will cause the + ** test to fail. */ + if( p->nPendingData==0 && 0==fts5TestUtf8(zTerm, nTerm) ){ + if( iIdx>0 && rc==SQLITE_OK ){ + int f = flags|FTS5INDEX_QUERY_TEST_NOIDX; + ck2 = 0; + rc = fts5QueryCksum(p, iIdx, zTerm, nTerm, f, &ck2); + if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT; + } + if( iIdx>0 && rc==SQLITE_OK ){ + int f = flags|FTS5INDEX_QUERY_TEST_NOIDX|FTS5INDEX_QUERY_DESC; + ck2 = 0; + rc = fts5QueryCksum(p, iIdx, zTerm, nTerm, f, &ck2); + if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT; + } + } + + cksum3 ^= ck1; + fts5BufferSet(&rc, pPrev, n, (const u8*)z); + + if( rc==SQLITE_OK && cksum3!=expected ){ + rc = FTS5_CORRUPT; + } + *pCksum = cksum3; + } + p->rc = rc; +} + +#else +# define fts5TestDlidxReverse(x,y,z) +# define fts5TestTerm(u,v,w,x,y,z) +#endif + +/* +** Check that: +** +** 1) All leaves of pSeg between iFirst and iLast (inclusive) exist and +** contain zero terms. +** 2) All leaves of pSeg between iNoRowid and iLast (inclusive) exist and +** contain zero rowids. +*/ +static void fts5IndexIntegrityCheckEmpty( + Fts5Index *p, + Fts5StructureSegment *pSeg, /* Segment to check internal consistency */ + int iFirst, + int iNoRowid, + int iLast +){ + int i; + + /* Now check that the iter.nEmpty leaves following the current leaf + ** (a) exist and (b) contain no terms. */ + for(i=iFirst; p->rc==SQLITE_OK && i<=iLast; i++){ + Fts5Data *pLeaf = fts5DataRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i)); + if( pLeaf ){ + if( !fts5LeafIsTermless(pLeaf) ) p->rc = FTS5_CORRUPT; + if( i>=iNoRowid && 0!=fts5LeafFirstRowidOff(pLeaf) ) p->rc = FTS5_CORRUPT; + } + fts5DataRelease(pLeaf); + } +} + +static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ + int iTermOff = 0; + int ii; + + Fts5Buffer buf1 = {0,0,0}; + Fts5Buffer buf2 = {0,0,0}; + + ii = pLeaf->szLeaf; + while( ii<pLeaf->nn && p->rc==SQLITE_OK ){ + int res; + int iOff; + int nIncr; + + ii += fts5GetVarint32(&pLeaf->p[ii], nIncr); + iTermOff += nIncr; + iOff = iTermOff; + + if( iOff>=pLeaf->szLeaf ){ + p->rc = FTS5_CORRUPT; + }else if( iTermOff==nIncr ){ + int nByte; + iOff += fts5GetVarint32(&pLeaf->p[iOff], nByte); + if( (iOff+nByte)>pLeaf->szLeaf ){ + p->rc = FTS5_CORRUPT; + }else{ + fts5BufferSet(&p->rc, &buf1, nByte, &pLeaf->p[iOff]); + } + }else{ + int nKeep, nByte; + iOff += fts5GetVarint32(&pLeaf->p[iOff], nKeep); + iOff += fts5GetVarint32(&pLeaf->p[iOff], nByte); + if( nKeep>buf1.n || (iOff+nByte)>pLeaf->szLeaf ){ + p->rc = FTS5_CORRUPT; + }else{ + buf1.n = nKeep; + fts5BufferAppendBlob(&p->rc, &buf1, nByte, &pLeaf->p[iOff]); + } + + if( p->rc==SQLITE_OK ){ + res = fts5BufferCompare(&buf1, &buf2); + if( res<=0 ) p->rc = FTS5_CORRUPT; + } + } + fts5BufferSet(&p->rc, &buf2, buf1.n, buf1.p); + } + + fts5BufferFree(&buf1); + fts5BufferFree(&buf2); +} + +static void fts5IndexIntegrityCheckSegment( + Fts5Index *p, /* FTS5 backend object */ + Fts5StructureSegment *pSeg /* Segment to check internal consistency */ +){ + Fts5Config *pConfig = p->pConfig; + int bSecureDelete = (pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE); + sqlite3_stmt *pStmt = 0; + int rc2; + int iIdxPrevLeaf = pSeg->pgnoFirst-1; + int iDlidxPrevLeaf = pSeg->pgnoLast; + + if( pSeg->pgnoFirst==0 ) return; + + fts5IndexPrepareStmt(p, &pStmt, sqlite3_mprintf( + "SELECT segid, term, (pgno>>1), (pgno&1) FROM %Q.'%q_idx' WHERE segid=%d " + "ORDER BY 1, 2", + pConfig->zDb, pConfig->zName, pSeg->iSegid + )); + + /* Iterate through the b-tree hierarchy. */ + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + i64 iRow; /* Rowid for this leaf */ + Fts5Data *pLeaf; /* Data for this leaf */ + + const char *zIdxTerm = (const char*)sqlite3_column_blob(pStmt, 1); + int nIdxTerm = sqlite3_column_bytes(pStmt, 1); + int iIdxLeaf = sqlite3_column_int(pStmt, 2); + int bIdxDlidx = sqlite3_column_int(pStmt, 3); + + /* If the leaf in question has already been trimmed from the segment, + ** ignore this b-tree entry. Otherwise, load it into memory. */ + if( iIdxLeaf<pSeg->pgnoFirst ) continue; + iRow = FTS5_SEGMENT_ROWID(pSeg->iSegid, iIdxLeaf); + pLeaf = fts5LeafRead(p, iRow); + if( pLeaf==0 ) break; + + /* Check that the leaf contains at least one term, and that it is equal + ** to or larger than the split-key in zIdxTerm. Also check that if there + ** is also a rowid pointer within the leaf page header, it points to a + ** location before the term. */ + if( pLeaf->nn<=pLeaf->szLeaf ){ + + if( nIdxTerm==0 + && pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE + && pLeaf->nn==pLeaf->szLeaf + && pLeaf->nn==4 + ){ + /* special case - the very first page in a segment keeps its %_idx + ** entry even if all the terms are removed from it by secure-delete + ** operations. */ + }else{ + p->rc = FTS5_CORRUPT; + } + + }else{ + int iOff; /* Offset of first term on leaf */ + int iRowidOff; /* Offset of first rowid on leaf */ + int nTerm; /* Size of term on leaf in bytes */ + int res; /* Comparison of term and split-key */ + + iOff = fts5LeafFirstTermOff(pLeaf); + iRowidOff = fts5LeafFirstRowidOff(pLeaf); + if( iRowidOff>=iOff || iOff>=pLeaf->szLeaf ){ + p->rc = FTS5_CORRUPT; + }else{ + iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm); + res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm)); + if( res==0 ) res = nTerm - nIdxTerm; + if( res<0 ) p->rc = FTS5_CORRUPT; + } + + fts5IntegrityCheckPgidx(p, pLeaf); + } + fts5DataRelease(pLeaf); + if( p->rc ) break; + + /* Now check that the iter.nEmpty leaves following the current leaf + ** (a) exist and (b) contain no terms. */ + fts5IndexIntegrityCheckEmpty( + p, pSeg, iIdxPrevLeaf+1, iDlidxPrevLeaf+1, iIdxLeaf-1 + ); + if( p->rc ) break; + + /* If there is a doclist-index, check that it looks right. */ + if( bIdxDlidx ){ + Fts5DlidxIter *pDlidx = 0; /* For iterating through doclist index */ + int iPrevLeaf = iIdxLeaf; + int iSegid = pSeg->iSegid; + int iPg = 0; + i64 iKey; + + for(pDlidx=fts5DlidxIterInit(p, 0, iSegid, iIdxLeaf); + fts5DlidxIterEof(p, pDlidx)==0; + fts5DlidxIterNext(p, pDlidx) + ){ + + /* Check any rowid-less pages that occur before the current leaf. */ + for(iPg=iPrevLeaf+1; iPg<fts5DlidxIterPgno(pDlidx); iPg++){ + iKey = FTS5_SEGMENT_ROWID(iSegid, iPg); + pLeaf = fts5DataRead(p, iKey); + if( pLeaf ){ + if( fts5LeafFirstRowidOff(pLeaf)!=0 ) p->rc = FTS5_CORRUPT; + fts5DataRelease(pLeaf); + } + } + iPrevLeaf = fts5DlidxIterPgno(pDlidx); + + /* Check that the leaf page indicated by the iterator really does + ** contain the rowid suggested by the same. */ + iKey = FTS5_SEGMENT_ROWID(iSegid, iPrevLeaf); + pLeaf = fts5DataRead(p, iKey); + if( pLeaf ){ + i64 iRowid; + int iRowidOff = fts5LeafFirstRowidOff(pLeaf); + ASSERT_SZLEAF_OK(pLeaf); + if( iRowidOff>=pLeaf->szLeaf ){ + p->rc = FTS5_CORRUPT; + }else if( bSecureDelete==0 || iRowidOff>0 ){ + i64 iDlRowid = fts5DlidxIterRowid(pDlidx); + fts5GetVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid); + if( iRowid<iDlRowid || (bSecureDelete==0 && iRowid!=iDlRowid) ){ + p->rc = FTS5_CORRUPT; + } + } + fts5DataRelease(pLeaf); + } + } + + iDlidxPrevLeaf = iPg; + fts5DlidxIterFree(pDlidx); + fts5TestDlidxReverse(p, iSegid, iIdxLeaf); + }else{ + iDlidxPrevLeaf = pSeg->pgnoLast; + /* TODO: Check there is no doclist index */ + } + + iIdxPrevLeaf = iIdxLeaf; + } + + rc2 = sqlite3_finalize(pStmt); + if( p->rc==SQLITE_OK ) p->rc = rc2; + + /* Page iter.iLeaf must now be the rightmost leaf-page in the segment */ +#if 0 + if( p->rc==SQLITE_OK && iter.iLeaf!=pSeg->pgnoLast ){ + p->rc = FTS5_CORRUPT; + } +#endif +} + + +/* +** Run internal checks to ensure that the FTS index (a) is internally +** consistent and (b) contains entries for which the XOR of the checksums +** as calculated by sqlite3Fts5IndexEntryCksum() is cksum. +** +** Return SQLITE_CORRUPT if any of the internal checks fail, or if the +** checksum does not match. Return SQLITE_OK if all checks pass without +** error, or some other SQLite error code if another error (e.g. OOM) +** occurs. +*/ +static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){ + int eDetail = p->pConfig->eDetail; + u64 cksum2 = 0; /* Checksum based on contents of indexes */ + Fts5Buffer poslist = {0,0,0}; /* Buffer used to hold a poslist */ + Fts5Iter *pIter; /* Used to iterate through entire index */ + Fts5Structure *pStruct; /* Index structure */ + int iLvl, iSeg; + +#ifdef SQLITE_DEBUG + /* Used by extra internal tests only run if NDEBUG is not defined */ + u64 cksum3 = 0; /* Checksum based on contents of indexes */ + Fts5Buffer term = {0,0,0}; /* Buffer used to hold most recent term */ +#endif + const int flags = FTS5INDEX_QUERY_NOOUTPUT; + + /* Load the FTS index structure */ + pStruct = fts5StructureRead(p); + if( pStruct==0 ){ + assert( p->rc!=SQLITE_OK ); + return fts5IndexReturn(p); + } + + /* Check that the internal nodes of each segment match the leaves */ + for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ + for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + fts5IndexIntegrityCheckSegment(p, pSeg); + } + } + + /* The cksum argument passed to this function is a checksum calculated + ** based on all expected entries in the FTS index (including prefix index + ** entries). This block checks that a checksum calculated based on the + ** actual contents of FTS index is identical. + ** + ** Two versions of the same checksum are calculated. The first (stack + ** variable cksum2) based on entries extracted from the full-text index + ** while doing a linear scan of each individual index in turn. + ** + ** As each term visited by the linear scans, a separate query for the + ** same term is performed. cksum3 is calculated based on the entries + ** extracted by these queries. + */ + for(fts5MultiIterNew(p, pStruct, flags, 0, 0, 0, -1, 0, &pIter); + fts5MultiIterEof(p, pIter)==0; + fts5MultiIterNext(p, pIter, 0, 0) + ){ + int n; /* Size of term in bytes */ + i64 iPos = 0; /* Position read from poslist */ + int iOff = 0; /* Offset within poslist */ + i64 iRowid = fts5MultiIterRowid(pIter); + char *z = (char*)fts5MultiIterTerm(pIter, &n); + + /* If this is a new term, query for it. Update cksum3 with the results. */ + fts5TestTerm(p, &term, z, n, cksum2, &cksum3); + if( p->rc ) break; + + if( eDetail==FTS5_DETAIL_NONE ){ + if( 0==fts5MultiIterIsEmpty(p, pIter) ){ + cksum2 ^= sqlite3Fts5IndexEntryCksum(iRowid, 0, 0, -1, z, n); + } + }else{ + poslist.n = 0; + fts5SegiterPoslist(p, &pIter->aSeg[pIter->aFirst[1].iFirst], 0, &poslist); + fts5BufferAppendBlob(&p->rc, &poslist, 4, (const u8*)"\0\0\0\0"); + while( 0==sqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){ + int iCol = FTS5_POS2COLUMN(iPos); + int iTokOff = FTS5_POS2OFFSET(iPos); + cksum2 ^= sqlite3Fts5IndexEntryCksum(iRowid, iCol, iTokOff, -1, z, n); + } + } + } + fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3); + + fts5MultiIterFree(pIter); + if( p->rc==SQLITE_OK && bUseCksum && cksum!=cksum2 ) p->rc = FTS5_CORRUPT; + + fts5StructureRelease(pStruct); +#ifdef SQLITE_DEBUG + fts5BufferFree(&term); +#endif + fts5BufferFree(&poslist); + return fts5IndexReturn(p); +} + +/************************************************************************* +************************************************************************** +** Below this point is the implementation of the fts5_decode() scalar +** function only. +*/ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +/* +** Decode a segment-data rowid from the %_data table. This function is +** the opposite of macro FTS5_SEGMENT_ROWID(). +*/ +static void fts5DecodeRowid( + i64 iRowid, /* Rowid from %_data table */ + int *pbTombstone, /* OUT: Tombstone hash flag */ + int *piSegid, /* OUT: Segment id */ + int *pbDlidx, /* OUT: Dlidx flag */ + int *piHeight, /* OUT: Height */ + int *piPgno /* OUT: Page number */ +){ + *piPgno = (int)(iRowid & (((i64)1 << FTS5_DATA_PAGE_B) - 1)); + iRowid >>= FTS5_DATA_PAGE_B; + + *piHeight = (int)(iRowid & (((i64)1 << FTS5_DATA_HEIGHT_B) - 1)); + iRowid >>= FTS5_DATA_HEIGHT_B; + + *pbDlidx = (int)(iRowid & 0x0001); + iRowid >>= FTS5_DATA_DLI_B; + + *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1)); + iRowid >>= FTS5_DATA_ID_B; + + *pbTombstone = (int)(iRowid & 0x0001); +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ + int iSegid, iHeight, iPgno, bDlidx, bTomb; /* Rowid compenents */ + fts5DecodeRowid(iKey, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno); + + if( iSegid==0 ){ + if( iKey==FTS5_AVERAGES_ROWID ){ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{averages} "); + }else{ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{structure}"); + } + } + else{ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%s%ssegid=%d h=%d pgno=%d}", + bDlidx ? "dlidx " : "", + bTomb ? "tombstone " : "", + iSegid, iHeight, iPgno + ); + } +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +static void fts5DebugStructure( + int *pRc, /* IN/OUT: error code */ + Fts5Buffer *pBuf, + Fts5Structure *p +){ + int iLvl, iSeg; /* Iterate through levels, segments */ + + for(iLvl=0; iLvl<p->nLevel; iLvl++){ + Fts5StructureLevel *pLvl = &p->aLevel[iLvl]; + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, + " {lvl=%d nMerge=%d nSeg=%d", iLvl, pLvl->nMerge, pLvl->nSeg + ); + for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){ + Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d", + pSeg->iSegid, pSeg->pgnoFirst, pSeg->pgnoLast + ); + if( pSeg->iOrigin1>0 ){ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " origin=%lld..%lld", + pSeg->iOrigin1, pSeg->iOrigin2 + ); + } + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); + } + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); + } +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +/* +** This is part of the fts5_decode() debugging aid. +** +** Arguments pBlob/nBlob contain a serialized Fts5Structure object. This +** function appends a human-readable representation of the same object +** to the buffer passed as the second argument. +*/ +static void fts5DecodeStructure( + int *pRc, /* IN/OUT: error code */ + Fts5Buffer *pBuf, + const u8 *pBlob, int nBlob +){ + int rc; /* Return code */ + Fts5Structure *p = 0; /* Decoded structure object */ + + rc = fts5StructureDecode(pBlob, nBlob, 0, &p); + if( rc!=SQLITE_OK ){ + *pRc = rc; + return; + } + + fts5DebugStructure(pRc, pBuf, p); + fts5StructureRelease(p); +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +/* +** This is part of the fts5_decode() debugging aid. +** +** Arguments pBlob/nBlob contain an "averages" record. This function +** appends a human-readable representation of record to the buffer passed +** as the second argument. +*/ +static void fts5DecodeAverages( + int *pRc, /* IN/OUT: error code */ + Fts5Buffer *pBuf, + const u8 *pBlob, int nBlob +){ + int i = 0; + const char *zSpace = ""; + + while( i<nBlob ){ + u64 iVal; + i += sqlite3Fts5GetVarint(&pBlob[i], &iVal); + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "%s%d", zSpace, (int)iVal); + zSpace = " "; + } +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +/* +** Buffer (a/n) is assumed to contain a list of serialized varints. Read +** each varint and append its string representation to buffer pBuf. Return +** after either the input buffer is exhausted or a 0 value is read. +** +** The return value is the number of bytes read from the input buffer. +*/ +static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ + int iOff = 0; + while( iOff<n ){ + int iVal; + iOff += fts5GetVarint32(&a[iOff], iVal); + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %d", iVal); + } + return iOff; +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +/* +** The start of buffer (a/n) contains the start of a doclist. The doclist +** may or may not finish within the buffer. This function appends a text +** representation of the part of the doclist that is present to buffer +** pBuf. +** +** The return value is the number of bytes read from the input buffer. +*/ +static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ + i64 iDocid = 0; + int iOff = 0; + + if( n>0 ){ + iOff = sqlite3Fts5GetVarint(a, (u64*)&iDocid); + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " id=%lld", iDocid); + } + while( iOff<n ){ + int nPos; + int bDel; + iOff += fts5GetPoslistSize(&a[iOff], &nPos, &bDel); + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " nPos=%d%s", nPos, bDel?"*":""); + iOff += fts5DecodePoslist(pRc, pBuf, &a[iOff], MIN(n-iOff, nPos)); + if( iOff<n ){ + i64 iDelta; + iOff += sqlite3Fts5GetVarint(&a[iOff], (u64*)&iDelta); + iDocid += iDelta; + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " id=%lld", iDocid); + } + } + + return iOff; +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +/* +** This function is part of the fts5_decode() debugging function. It is +** only ever used with detail=none tables. +** +** Buffer (pData/nData) contains a doclist in the format used by detail=none +** tables. This function appends a human-readable version of that list to +** buffer pBuf. +** +** If *pRc is other than SQLITE_OK when this function is called, it is a +** no-op. If an OOM or other error occurs within this function, *pRc is +** set to an SQLite error code before returning. The final state of buffer +** pBuf is undefined in this case. +*/ +static void fts5DecodeRowidList( + int *pRc, /* IN/OUT: Error code */ + Fts5Buffer *pBuf, /* Buffer to append text to */ + const u8 *pData, int nData /* Data to decode list-of-rowids from */ +){ + int i = 0; + i64 iRowid = 0; + + while( i<nData ){ + const char *zApp = ""; + u64 iVal; + i += sqlite3Fts5GetVarint(&pData[i], &iVal); + iRowid += iVal; + + if( i<nData && pData[i]==0x00 ){ + i++; + if( i<nData && pData[i]==0x00 ){ + i++; + zApp = "+"; + }else{ + zApp = "*"; + } + } + + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp); + } +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +/* +** The implementation of user-defined scalar function fts5_decode(). +*/ +static void fts5DecodeFunction( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args (always 2) */ + sqlite3_value **apVal /* Function arguments */ +){ + i64 iRowid; /* Rowid for record being decoded */ + int iSegid,iHeight,iPgno,bDlidx;/* Rowid components */ + int bTomb; + const u8 *aBlob; int n; /* Record to decode */ + u8 *a = 0; + Fts5Buffer s; /* Build up text to return here */ + int rc = SQLITE_OK; /* Return code */ + sqlite3_int64 nSpace = 0; + int eDetailNone = (sqlite3_user_data(pCtx)!=0); + + assert( nArg==2 ); + UNUSED_PARAM(nArg); + memset(&s, 0, sizeof(Fts5Buffer)); + iRowid = sqlite3_value_int64(apVal[0]); + + /* Make a copy of the second argument (a blob) in aBlob[]. The aBlob[] + ** copy is followed by FTS5_DATA_ZERO_PADDING 0x00 bytes, which prevents + ** buffer overreads even if the record is corrupt. */ + n = sqlite3_value_bytes(apVal[1]); + aBlob = sqlite3_value_blob(apVal[1]); + nSpace = n + FTS5_DATA_ZERO_PADDING; + a = (u8*)sqlite3Fts5MallocZero(&rc, nSpace); + if( a==0 ) goto decode_out; + if( n>0 ) memcpy(a, aBlob, n); + + fts5DecodeRowid(iRowid, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno); + + fts5DebugRowid(&rc, &s, iRowid); + if( bDlidx ){ + Fts5Data dlidx; + Fts5DlidxLvl lvl; + + dlidx.p = a; + dlidx.nn = n; + + memset(&lvl, 0, sizeof(Fts5DlidxLvl)); + lvl.pData = &dlidx; + lvl.iLeafPgno = iPgno; + + for(fts5DlidxLvlNext(&lvl); lvl.bEof==0; fts5DlidxLvlNext(&lvl)){ + sqlite3Fts5BufferAppendPrintf(&rc, &s, + " %d(%lld)", lvl.iLeafPgno, lvl.iRowid + ); + } + }else if( bTomb ){ + u32 nElem = fts5GetU32(&a[4]); + int szKey = (aBlob[0]==4 || aBlob[0]==8) ? aBlob[0] : 8; + int nSlot = (n - 8) / szKey; + int ii; + sqlite3Fts5BufferAppendPrintf(&rc, &s, " nElem=%d", (int)nElem); + if( aBlob[1] ){ + sqlite3Fts5BufferAppendPrintf(&rc, &s, " 0"); + } + for(ii=0; ii<nSlot; ii++){ + u64 iVal = 0; + if( szKey==4 ){ + u32 *aSlot = (u32*)&aBlob[8]; + if( aSlot[ii] ) iVal = fts5GetU32((u8*)&aSlot[ii]); + }else{ + u64 *aSlot = (u64*)&aBlob[8]; + if( aSlot[ii] ) iVal = fts5GetU64((u8*)&aSlot[ii]); + } + if( iVal!=0 ){ + sqlite3Fts5BufferAppendPrintf(&rc, &s, " %lld", (i64)iVal); + } + } + }else if( iSegid==0 ){ + if( iRowid==FTS5_AVERAGES_ROWID ){ + fts5DecodeAverages(&rc, &s, a, n); + }else{ + fts5DecodeStructure(&rc, &s, a, n); + } + }else if( eDetailNone ){ + Fts5Buffer term; /* Current term read from page */ + int szLeaf; + int iPgidxOff = szLeaf = fts5GetU16(&a[2]); + int iTermOff; + int nKeep = 0; + int iOff; + + memset(&term, 0, sizeof(Fts5Buffer)); + + /* Decode any entries that occur before the first term. */ + if( szLeaf<n ){ + iPgidxOff += fts5GetVarint32(&a[iPgidxOff], iTermOff); + }else{ + iTermOff = szLeaf; + } + fts5DecodeRowidList(&rc, &s, &a[4], iTermOff-4); + + iOff = iTermOff; + while( iOff<szLeaf && rc==SQLITE_OK ){ + int nAppend; + + /* Read the term data for the next term*/ + iOff += fts5GetVarint32(&a[iOff], nAppend); + term.n = nKeep; + fts5BufferAppendBlob(&rc, &term, nAppend, &a[iOff]); + sqlite3Fts5BufferAppendPrintf( + &rc, &s, " term=%.*s", term.n, (const char*)term.p + ); + iOff += nAppend; + + /* Figure out where the doclist for this term ends */ + if( iPgidxOff<n ){ + int nIncr; + iPgidxOff += fts5GetVarint32(&a[iPgidxOff], nIncr); + iTermOff += nIncr; + }else{ + iTermOff = szLeaf; + } + if( iTermOff>szLeaf ){ + rc = FTS5_CORRUPT; + }else{ + fts5DecodeRowidList(&rc, &s, &a[iOff], iTermOff-iOff); + } + iOff = iTermOff; + if( iOff<szLeaf ){ + iOff += fts5GetVarint32(&a[iOff], nKeep); + } + } + + fts5BufferFree(&term); + }else{ + Fts5Buffer term; /* Current term read from page */ + int szLeaf; /* Offset of pgidx in a[] */ + int iPgidxOff; + int iPgidxPrev = 0; /* Previous value read from pgidx */ + int iTermOff = 0; + int iRowidOff = 0; + int iOff; + int nDoclist; + + memset(&term, 0, sizeof(Fts5Buffer)); + + if( n<4 ){ + sqlite3Fts5BufferSet(&rc, &s, 7, (const u8*)"corrupt"); + goto decode_out; + }else{ + iRowidOff = fts5GetU16(&a[0]); + iPgidxOff = szLeaf = fts5GetU16(&a[2]); + if( iPgidxOff<n ){ + fts5GetVarint32(&a[iPgidxOff], iTermOff); + }else if( iPgidxOff>n ){ + rc = FTS5_CORRUPT; + goto decode_out; + } + } + + /* Decode the position list tail at the start of the page */ + if( iRowidOff!=0 ){ + iOff = iRowidOff; + }else if( iTermOff!=0 ){ + iOff = iTermOff; + }else{ + iOff = szLeaf; + } + if( iOff>n ){ + rc = FTS5_CORRUPT; + goto decode_out; + } + fts5DecodePoslist(&rc, &s, &a[4], iOff-4); + + /* Decode any more doclist data that appears on the page before the + ** first term. */ + nDoclist = (iTermOff ? iTermOff : szLeaf) - iOff; + if( nDoclist+iOff>n ){ + rc = FTS5_CORRUPT; + goto decode_out; + } + fts5DecodeDoclist(&rc, &s, &a[iOff], nDoclist); + + while( iPgidxOff<n && rc==SQLITE_OK ){ + int bFirst = (iPgidxOff==szLeaf); /* True for first term on page */ + int nByte; /* Bytes of data */ + int iEnd; + + iPgidxOff += fts5GetVarint32(&a[iPgidxOff], nByte); + iPgidxPrev += nByte; + iOff = iPgidxPrev; + + if( iPgidxOff<n ){ + fts5GetVarint32(&a[iPgidxOff], nByte); + iEnd = iPgidxPrev + nByte; + }else{ + iEnd = szLeaf; + } + if( iEnd>szLeaf ){ + rc = FTS5_CORRUPT; + break; + } + + if( bFirst==0 ){ + iOff += fts5GetVarint32(&a[iOff], nByte); + if( nByte>term.n ){ + rc = FTS5_CORRUPT; + break; + } + term.n = nByte; + } + iOff += fts5GetVarint32(&a[iOff], nByte); + if( iOff+nByte>n ){ + rc = FTS5_CORRUPT; + break; + } + fts5BufferAppendBlob(&rc, &term, nByte, &a[iOff]); + iOff += nByte; + + sqlite3Fts5BufferAppendPrintf( + &rc, &s, " term=%.*s", term.n, (const char*)term.p + ); + iOff += fts5DecodeDoclist(&rc, &s, &a[iOff], iEnd-iOff); + } + + fts5BufferFree(&term); + } + + decode_out: + sqlite3_free(a); + if( rc==SQLITE_OK ){ + sqlite3_result_text(pCtx, (const char*)s.p, s.n, SQLITE_TRANSIENT); + }else{ + sqlite3_result_error_code(pCtx, rc); + } + fts5BufferFree(&s); +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +/* +** The implementation of user-defined scalar function fts5_rowid(). +*/ +static void fts5RowidFunction( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args (always 2) */ + sqlite3_value **apVal /* Function arguments */ +){ + const char *zArg; + if( nArg==0 ){ + sqlite3_result_error(pCtx, "should be: fts5_rowid(subject, ....)", -1); + }else{ + zArg = (const char*)sqlite3_value_text(apVal[0]); + if( 0==sqlite3_stricmp(zArg, "segment") ){ + i64 iRowid; + int segid, pgno; + if( nArg!=3 ){ + sqlite3_result_error(pCtx, + "should be: fts5_rowid('segment', segid, pgno))", -1 + ); + }else{ + segid = sqlite3_value_int(apVal[1]); + pgno = sqlite3_value_int(apVal[2]); + iRowid = FTS5_SEGMENT_ROWID(segid, pgno); + sqlite3_result_int64(pCtx, iRowid); + } + }else{ + sqlite3_result_error(pCtx, + "first arg to fts5_rowid() must be 'segment'" , -1 + ); + } + } +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) + +typedef struct Fts5StructVtab Fts5StructVtab; +struct Fts5StructVtab { + sqlite3_vtab base; +}; + +typedef struct Fts5StructVcsr Fts5StructVcsr; +struct Fts5StructVcsr { + sqlite3_vtab_cursor base; + Fts5Structure *pStruct; + int iLevel; + int iSeg; + int iRowid; +}; + +/* +** Create a new fts5_structure() table-valued function. +*/ +static int fts5structConnectMethod( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + Fts5StructVtab *pNew = 0; + int rc = SQLITE_OK; + + rc = sqlite3_declare_vtab(db, + "CREATE TABLE xyz(" + "level, segment, merge, segid, leaf1, leaf2, loc1, loc2, " + "npgtombstone, nentrytombstone, nentry, struct HIDDEN);" + ); + if( rc==SQLITE_OK ){ + pNew = sqlite3Fts5MallocZero(&rc, sizeof(*pNew)); + } + + *ppVtab = (sqlite3_vtab*)pNew; + return rc; +} + +/* +** We must have a single struct=? constraint that will be passed through +** into the xFilter method. If there is no valid stmt=? constraint, +** then return an SQLITE_CONSTRAINT error. +*/ +static int fts5structBestIndexMethod( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; + int rc = SQLITE_CONSTRAINT; + struct sqlite3_index_constraint *p; + pIdxInfo->estimatedCost = (double)100; + pIdxInfo->estimatedRows = 100; + pIdxInfo->idxNum = 0; + for(i=0, p=pIdxInfo->aConstraint; i<pIdxInfo->nConstraint; i++, p++){ + if( p->usable==0 ) continue; + if( p->op==SQLITE_INDEX_CONSTRAINT_EQ && p->iColumn==11 ){ + rc = SQLITE_OK; + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + break; + } + } + return rc; +} + +/* +** This method is the destructor for bytecodevtab objects. +*/ +static int fts5structDisconnectMethod(sqlite3_vtab *pVtab){ + Fts5StructVtab *p = (Fts5StructVtab*)pVtab; + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Constructor for a new bytecodevtab_cursor object. +*/ +static int fts5structOpenMethod(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){ + int rc = SQLITE_OK; + Fts5StructVcsr *pNew = 0; + + pNew = sqlite3Fts5MallocZero(&rc, sizeof(*pNew)); + *ppCsr = (sqlite3_vtab_cursor*)pNew; + + return SQLITE_OK; +} + +/* +** Destructor for a bytecodevtab_cursor. +*/ +static int fts5structCloseMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + fts5StructureRelease(pCsr->pStruct); + sqlite3_free(pCsr); + return SQLITE_OK; +} + + +/* +** Advance a bytecodevtab_cursor to its next row of output. +*/ +static int fts5structNextMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + Fts5Structure *p = pCsr->pStruct; + + assert( pCsr->pStruct ); + pCsr->iSeg++; + pCsr->iRowid++; + while( pCsr->iLevel<p->nLevel && pCsr->iSeg>=p->aLevel[pCsr->iLevel].nSeg ){ + pCsr->iLevel++; + pCsr->iSeg = 0; + } + if( pCsr->iLevel>=p->nLevel ){ + fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; + } + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int fts5structEofMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + return pCsr->pStruct==0; +} + +static int fts5structRowidMethod( + sqlite3_vtab_cursor *cur, + sqlite_int64 *piRowid +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + *piRowid = pCsr->iRowid; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the bytecodevtab_cursor +** is currently pointing. +*/ +static int fts5structColumnMethod( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + Fts5Structure *p = pCsr->pStruct; + Fts5StructureSegment *pSeg = &p->aLevel[pCsr->iLevel].aSeg[pCsr->iSeg]; + + switch( i ){ + case 0: /* level */ + sqlite3_result_int(ctx, pCsr->iLevel); + break; + case 1: /* segment */ + sqlite3_result_int(ctx, pCsr->iSeg); + break; + case 2: /* merge */ + sqlite3_result_int(ctx, pCsr->iSeg < p->aLevel[pCsr->iLevel].nMerge); + break; + case 3: /* segid */ + sqlite3_result_int(ctx, pSeg->iSegid); + break; + case 4: /* leaf1 */ + sqlite3_result_int(ctx, pSeg->pgnoFirst); + break; + case 5: /* leaf2 */ + sqlite3_result_int(ctx, pSeg->pgnoLast); + break; + case 6: /* origin1 */ + sqlite3_result_int64(ctx, pSeg->iOrigin1); + break; + case 7: /* origin2 */ + sqlite3_result_int64(ctx, pSeg->iOrigin2); + break; + case 8: /* npgtombstone */ + sqlite3_result_int(ctx, pSeg->nPgTombstone); + break; + case 9: /* nentrytombstone */ + sqlite3_result_int64(ctx, pSeg->nEntryTombstone); + break; + case 10: /* nentry */ + sqlite3_result_int64(ctx, pSeg->nEntry); + break; + } + return SQLITE_OK; +} + +/* +** Initialize a cursor. +** +** idxNum==0 means show all subprograms +** idxNum==1 means show only the main bytecode and omit subprograms. +*/ +static int fts5structFilterMethod( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr *)pVtabCursor; + int rc = SQLITE_OK; + + const u8 *aBlob = 0; + int nBlob = 0; + + assert( argc==1 ); + fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; + + nBlob = sqlite3_value_bytes(argv[0]); + aBlob = (const u8*)sqlite3_value_blob(argv[0]); + rc = fts5StructureDecode(aBlob, nBlob, 0, &pCsr->pStruct); + if( rc==SQLITE_OK ){ + pCsr->iLevel = 0; + pCsr->iRowid = 0; + pCsr->iSeg = -1; + rc = fts5structNextMethod(pVtabCursor); + } + + return rc; +} + +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +/* +** This is called as part of registering the FTS5 module with database +** connection db. It registers several user-defined scalar functions useful +** with FTS5. +** +** If successful, SQLITE_OK is returned. If an error occurs, some other +** SQLite error code is returned instead. +*/ +static int sqlite3Fts5IndexInit(sqlite3 *db){ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) + int rc = sqlite3_create_function( + db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0 + ); + + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function( + db, "fts5_decode_none", 2, + SQLITE_UTF8, (void*)db, fts5DecodeFunction, 0, 0 + ); + } + + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function( + db, "fts5_rowid", -1, SQLITE_UTF8, 0, fts5RowidFunction, 0, 0 + ); + } + + if( rc==SQLITE_OK ){ + static const sqlite3_module fts5structure_module = { + 0, /* iVersion */ + 0, /* xCreate */ + fts5structConnectMethod, /* xConnect */ + fts5structBestIndexMethod, /* xBestIndex */ + fts5structDisconnectMethod, /* xDisconnect */ + 0, /* xDestroy */ + fts5structOpenMethod, /* xOpen */ + fts5structCloseMethod, /* xClose */ + fts5structFilterMethod, /* xFilter */ + fts5structNextMethod, /* xNext */ + fts5structEofMethod, /* xEof */ + fts5structColumnMethod, /* xColumn */ + fts5structRowidMethod, /* xRowid */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindFunction */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ + }; + rc = sqlite3_create_module(db, "fts5_structure", &fts5structure_module, 0); + } + return rc; +#else + return SQLITE_OK; + UNUSED_PARAM(db); +#endif +} + + +static int sqlite3Fts5IndexReset(Fts5Index *p){ + assert( p->pStruct==0 || p->iStructVersion!=0 ); + if( fts5IndexDataVersion(p)!=p->iStructVersion ){ + fts5StructureInvalidate(p); + } + return fts5IndexReturn(p); +} + +/* +** 2014 Jun 09 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This is an SQLite module implementing full-text search. +*/ + + +/* #include "fts5Int.h" */ + +/* +** This variable is set to false when running tests for which the on disk +** structures should not be corrupt. Otherwise, true. If it is false, extra +** assert() conditions in the fts5 code are activated - conditions that are +** only true if it is guaranteed that the fts5 database is not corrupt. +*/ +#ifdef SQLITE_DEBUG +SQLITE_API int sqlite3_fts5_may_be_corrupt = 1; +#endif + + +typedef struct Fts5Auxdata Fts5Auxdata; +typedef struct Fts5Auxiliary Fts5Auxiliary; +typedef struct Fts5Cursor Fts5Cursor; +typedef struct Fts5FullTable Fts5FullTable; +typedef struct Fts5Sorter Fts5Sorter; +typedef struct Fts5TokenizerModule Fts5TokenizerModule; + +/* +** NOTES ON TRANSACTIONS: +** +** SQLite invokes the following virtual table methods as transactions are +** opened and closed by the user: +** +** xBegin(): Start of a new transaction. +** xSync(): Initial part of two-phase commit. +** xCommit(): Final part of two-phase commit. +** xRollback(): Rollback the transaction. +** +** Anything that is required as part of a commit that may fail is performed +** in the xSync() callback. Current versions of SQLite ignore any errors +** returned by xCommit(). +** +** And as sub-transactions are opened/closed: +** +** xSavepoint(int S): Open savepoint S. +** xRelease(int S): Commit and close savepoint S. +** xRollbackTo(int S): Rollback to start of savepoint S. +** +** During a write-transaction the fts5_index.c module may cache some data +** in-memory. It is flushed to disk whenever xSync(), xRelease() or +** xSavepoint() is called. And discarded whenever xRollback() or xRollbackTo() +** is called. +** +** Additionally, if SQLITE_DEBUG is defined, an instance of the following +** structure is used to record the current transaction state. This information +** is not required, but it is used in the assert() statements executed by +** function fts5CheckTransactionState() (see below). +*/ +struct Fts5TransactionState { + int eState; /* 0==closed, 1==open, 2==synced */ + int iSavepoint; /* Number of open savepoints (0 -> none) */ +}; + +/* +** A single object of this type is allocated when the FTS5 module is +** registered with a database handle. It is used to store pointers to +** all registered FTS5 extensions - tokenizers and auxiliary functions. +*/ +struct Fts5Global { + fts5_api api; /* User visible part of object (see fts5.h) */ + sqlite3 *db; /* Associated database connection */ + i64 iNextId; /* Used to allocate unique cursor ids */ + Fts5Auxiliary *pAux; /* First in list of all aux. functions */ + Fts5TokenizerModule *pTok; /* First in list of all tokenizer modules */ + Fts5TokenizerModule *pDfltTok; /* Default tokenizer module */ + Fts5Cursor *pCsr; /* First in list of all open cursors */ +}; + +/* +** Each auxiliary function registered with the FTS5 module is represented +** by an object of the following type. All such objects are stored as part +** of the Fts5Global.pAux list. +*/ +struct Fts5Auxiliary { + Fts5Global *pGlobal; /* Global context for this function */ + char *zFunc; /* Function name (nul-terminated) */ + void *pUserData; /* User-data pointer */ + fts5_extension_function xFunc; /* Callback function */ + void (*xDestroy)(void*); /* Destructor function */ + Fts5Auxiliary *pNext; /* Next registered auxiliary function */ +}; + +/* +** Each tokenizer module registered with the FTS5 module is represented +** by an object of the following type. All such objects are stored as part +** of the Fts5Global.pTok list. +*/ +struct Fts5TokenizerModule { + char *zName; /* Name of tokenizer */ + void *pUserData; /* User pointer passed to xCreate() */ + fts5_tokenizer x; /* Tokenizer functions */ + void (*xDestroy)(void*); /* Destructor function */ + Fts5TokenizerModule *pNext; /* Next registered tokenizer module */ +}; + +struct Fts5FullTable { + Fts5Table p; /* Public class members from fts5Int.h */ + Fts5Storage *pStorage; /* Document store */ + Fts5Global *pGlobal; /* Global (connection wide) data */ + Fts5Cursor *pSortCsr; /* Sort data from this cursor */ +#ifdef SQLITE_DEBUG + struct Fts5TransactionState ts; +#endif +}; + +struct Fts5MatchPhrase { + Fts5Buffer *pPoslist; /* Pointer to current poslist */ + int nTerm; /* Size of phrase in terms */ +}; + +/* +** pStmt: +** SELECT rowid, <fts> FROM <fts> ORDER BY +rank; +** +** aIdx[]: +** There is one entry in the aIdx[] array for each phrase in the query, +** the value of which is the offset within aPoslist[] following the last +** byte of the position list for the corresponding phrase. +*/ +struct Fts5Sorter { + sqlite3_stmt *pStmt; + i64 iRowid; /* Current rowid */ + const u8 *aPoslist; /* Position lists for current row */ + int nIdx; /* Number of entries in aIdx[] */ + int aIdx[1]; /* Offsets into aPoslist for current row */ +}; + + +/* +** Virtual-table cursor object. +** +** iSpecial: +** If this is a 'special' query (refer to function fts5SpecialMatch()), +** then this variable contains the result of the query. +** +** iFirstRowid, iLastRowid: +** These variables are only used for FTS5_PLAN_MATCH cursors. Assuming the +** cursor iterates in ascending order of rowids, iFirstRowid is the lower +** limit of rowids to return, and iLastRowid the upper. In other words, the +** WHERE clause in the user's query might have been: +** +** <tbl> MATCH <expr> AND rowid BETWEEN $iFirstRowid AND $iLastRowid +** +** If the cursor iterates in descending order of rowid, iFirstRowid +** is the upper limit (i.e. the "first" rowid visited) and iLastRowid +** the lower. +*/ +struct Fts5Cursor { + sqlite3_vtab_cursor base; /* Base class used by SQLite core */ + Fts5Cursor *pNext; /* Next cursor in Fts5Cursor.pCsr list */ + int *aColumnSize; /* Values for xColumnSize() */ + i64 iCsrId; /* Cursor id */ + + /* Zero from this point onwards on cursor reset */ + int ePlan; /* FTS5_PLAN_XXX value */ + int bDesc; /* True for "ORDER BY rowid DESC" queries */ + i64 iFirstRowid; /* Return no rowids earlier than this */ + i64 iLastRowid; /* Return no rowids later than this */ + sqlite3_stmt *pStmt; /* Statement used to read %_content */ + Fts5Expr *pExpr; /* Expression for MATCH queries */ + Fts5Sorter *pSorter; /* Sorter for "ORDER BY rank" queries */ + int csrflags; /* Mask of cursor flags (see below) */ + i64 iSpecial; /* Result of special query */ + + /* "rank" function. Populated on demand from vtab.xColumn(). */ + char *zRank; /* Custom rank function */ + char *zRankArgs; /* Custom rank function args */ + Fts5Auxiliary *pRank; /* Rank callback (or NULL) */ + int nRankArg; /* Number of trailing arguments for rank() */ + sqlite3_value **apRankArg; /* Array of trailing arguments */ + sqlite3_stmt *pRankArgStmt; /* Origin of objects in apRankArg[] */ + + /* Auxiliary data storage */ + Fts5Auxiliary *pAux; /* Currently executing extension function */ + Fts5Auxdata *pAuxdata; /* First in linked list of saved aux-data */ + + /* Cache used by auxiliary functions xInst() and xInstCount() */ + Fts5PoslistReader *aInstIter; /* One for each phrase */ + int nInstAlloc; /* Size of aInst[] array (entries / 3) */ + int nInstCount; /* Number of phrase instances */ + int *aInst; /* 3 integers per phrase instance */ +}; + +/* +** Bits that make up the "idxNum" parameter passed indirectly by +** xBestIndex() to xFilter(). +*/ +#define FTS5_BI_MATCH 0x0001 /* <tbl> MATCH ? */ +#define FTS5_BI_RANK 0x0002 /* rank MATCH ? */ +#define FTS5_BI_ROWID_EQ 0x0004 /* rowid == ? */ +#define FTS5_BI_ROWID_LE 0x0008 /* rowid <= ? */ +#define FTS5_BI_ROWID_GE 0x0010 /* rowid >= ? */ + +#define FTS5_BI_ORDER_RANK 0x0020 +#define FTS5_BI_ORDER_ROWID 0x0040 +#define FTS5_BI_ORDER_DESC 0x0080 + +/* +** Values for Fts5Cursor.csrflags +*/ +#define FTS5CSR_EOF 0x01 +#define FTS5CSR_REQUIRE_CONTENT 0x02 +#define FTS5CSR_REQUIRE_DOCSIZE 0x04 +#define FTS5CSR_REQUIRE_INST 0x08 +#define FTS5CSR_FREE_ZRANK 0x10 +#define FTS5CSR_REQUIRE_RESEEK 0x20 +#define FTS5CSR_REQUIRE_POSLIST 0x40 + +#define BitFlagAllTest(x,y) (((x) & (y))==(y)) +#define BitFlagTest(x,y) (((x) & (y))!=0) + + +/* +** Macros to Set(), Clear() and Test() cursor flags. +*/ +#define CsrFlagSet(pCsr, flag) ((pCsr)->csrflags |= (flag)) +#define CsrFlagClear(pCsr, flag) ((pCsr)->csrflags &= ~(flag)) +#define CsrFlagTest(pCsr, flag) ((pCsr)->csrflags & (flag)) + +struct Fts5Auxdata { + Fts5Auxiliary *pAux; /* Extension to which this belongs */ + void *pPtr; /* Pointer value */ + void(*xDelete)(void*); /* Destructor */ + Fts5Auxdata *pNext; /* Next object in linked list */ +}; + +#ifdef SQLITE_DEBUG +#define FTS5_BEGIN 1 +#define FTS5_SYNC 2 +#define FTS5_COMMIT 3 +#define FTS5_ROLLBACK 4 +#define FTS5_SAVEPOINT 5 +#define FTS5_RELEASE 6 +#define FTS5_ROLLBACKTO 7 +static void fts5CheckTransactionState(Fts5FullTable *p, int op, int iSavepoint){ + switch( op ){ + case FTS5_BEGIN: + assert( p->ts.eState==0 ); + p->ts.eState = 1; + p->ts.iSavepoint = -1; + break; + + case FTS5_SYNC: + assert( p->ts.eState==1 || p->ts.eState==2 ); + p->ts.eState = 2; + break; + + case FTS5_COMMIT: + assert( p->ts.eState==2 ); + p->ts.eState = 0; + break; + + case FTS5_ROLLBACK: + assert( p->ts.eState==1 || p->ts.eState==2 || p->ts.eState==0 ); + p->ts.eState = 0; + break; + + case FTS5_SAVEPOINT: + assert( p->ts.eState>=1 ); + assert( iSavepoint>=0 ); + assert( iSavepoint>=p->ts.iSavepoint ); + p->ts.iSavepoint = iSavepoint; + break; + + case FTS5_RELEASE: + assert( p->ts.eState>=1 ); + assert( iSavepoint>=0 ); + assert( iSavepoint<=p->ts.iSavepoint ); + p->ts.iSavepoint = iSavepoint-1; + break; + + case FTS5_ROLLBACKTO: + assert( p->ts.eState>=1 ); + assert( iSavepoint>=-1 ); + /* The following assert() can fail if another vtab strikes an error + ** within an xSavepoint() call then SQLite calls xRollbackTo() - without + ** having called xSavepoint() on this vtab. */ + /* assert( iSavepoint<=p->ts.iSavepoint ); */ + p->ts.iSavepoint = iSavepoint; + break; + } +} +#else +# define fts5CheckTransactionState(x,y,z) +#endif + +/* +** Return true if pTab is a contentless table. +*/ +static int fts5IsContentless(Fts5FullTable *pTab){ + return pTab->p.pConfig->eContent==FTS5_CONTENT_NONE; +} + +/* +** Delete a virtual table handle allocated by fts5InitVtab(). +*/ +static void fts5FreeVtab(Fts5FullTable *pTab){ + if( pTab ){ + sqlite3Fts5IndexClose(pTab->p.pIndex); + sqlite3Fts5StorageClose(pTab->pStorage); + sqlite3Fts5ConfigFree(pTab->p.pConfig); + sqlite3_free(pTab); + } +} + +/* +** The xDisconnect() virtual table method. +*/ +static int fts5DisconnectMethod(sqlite3_vtab *pVtab){ + fts5FreeVtab((Fts5FullTable*)pVtab); + return SQLITE_OK; +} + +/* +** The xDestroy() virtual table method. +*/ +static int fts5DestroyMethod(sqlite3_vtab *pVtab){ + Fts5Table *pTab = (Fts5Table*)pVtab; + int rc = sqlite3Fts5DropAll(pTab->pConfig); + if( rc==SQLITE_OK ){ + fts5FreeVtab((Fts5FullTable*)pVtab); + } + return rc; +} + +/* +** This function is the implementation of both the xConnect and xCreate +** methods of the FTS3 virtual table. +** +** The argv[] array contains the following: +** +** argv[0] -> module name ("fts5") +** argv[1] -> database name +** argv[2] -> table name +** argv[...] -> "column name" and other module argument fields. +*/ +static int fts5InitVtab( + int bCreate, /* True for xCreate, false for xConnect */ + sqlite3 *db, /* The SQLite database connection */ + void *pAux, /* Hash table containing tokenizers */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */ + char **pzErr /* Write any error message here */ +){ + Fts5Global *pGlobal = (Fts5Global*)pAux; + const char **azConfig = (const char**)argv; + int rc = SQLITE_OK; /* Return code */ + Fts5Config *pConfig = 0; /* Results of parsing argc/argv */ + Fts5FullTable *pTab = 0; /* New virtual table object */ + + /* Allocate the new vtab object and parse the configuration */ + pTab = (Fts5FullTable*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5FullTable)); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ConfigParse(pGlobal, db, argc, azConfig, &pConfig, pzErr); + assert( (rc==SQLITE_OK && *pzErr==0) || pConfig==0 ); + } + if( rc==SQLITE_OK ){ + pTab->p.pConfig = pConfig; + pTab->pGlobal = pGlobal; + } + + /* Open the index sub-system */ + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexOpen(pConfig, bCreate, &pTab->p.pIndex, pzErr); + } + + /* Open the storage sub-system */ + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageOpen( + pConfig, pTab->p.pIndex, bCreate, &pTab->pStorage, pzErr + ); + } + + /* Call sqlite3_declare_vtab() */ + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ConfigDeclareVtab(pConfig); + } + + /* Load the initial configuration */ + if( rc==SQLITE_OK ){ + assert( pConfig->pzErrmsg==0 ); + pConfig->pzErrmsg = pzErr; + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + sqlite3Fts5IndexRollback(pTab->p.pIndex); + pConfig->pzErrmsg = 0; + } + + if( rc!=SQLITE_OK ){ + fts5FreeVtab(pTab); + pTab = 0; + }else if( bCreate ){ + fts5CheckTransactionState(pTab, FTS5_BEGIN, 0); + } + *ppVTab = (sqlite3_vtab*)pTab; + return rc; +} + +/* +** The xConnect() and xCreate() methods for the virtual table. All the +** work is done in function fts5InitVtab(). +*/ +static int fts5ConnectMethod( + sqlite3 *db, /* Database connection */ + void *pAux, /* Pointer to tokenizer hash table */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ + char **pzErr /* OUT: sqlite3_malloc'd error message */ +){ + return fts5InitVtab(0, db, pAux, argc, argv, ppVtab, pzErr); +} +static int fts5CreateMethod( + sqlite3 *db, /* Database connection */ + void *pAux, /* Pointer to tokenizer hash table */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ + char **pzErr /* OUT: sqlite3_malloc'd error message */ +){ + return fts5InitVtab(1, db, pAux, argc, argv, ppVtab, pzErr); +} + +/* +** The different query plans. +*/ +#define FTS5_PLAN_MATCH 1 /* (<tbl> MATCH ?) */ +#define FTS5_PLAN_SOURCE 2 /* A source cursor for SORTED_MATCH */ +#define FTS5_PLAN_SPECIAL 3 /* An internal query */ +#define FTS5_PLAN_SORTED_MATCH 4 /* (<tbl> MATCH ? ORDER BY rank) */ +#define FTS5_PLAN_SCAN 5 /* No usable constraint */ +#define FTS5_PLAN_ROWID 6 /* (rowid = ?) */ + +/* +** Set the SQLITE_INDEX_SCAN_UNIQUE flag in pIdxInfo->flags. Unless this +** extension is currently being used by a version of SQLite too old to +** support index-info flags. In that case this function is a no-op. +*/ +static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){ +#if SQLITE_VERSION_NUMBER>=3008012 +#ifndef SQLITE_CORE + if( sqlite3_libversion_number()>=3008012 ) +#endif + { + pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE; + } +#endif +} + +static int fts5UsePatternMatch( + Fts5Config *pConfig, + struct sqlite3_index_constraint *p +){ + assert( FTS5_PATTERN_GLOB==SQLITE_INDEX_CONSTRAINT_GLOB ); + assert( FTS5_PATTERN_LIKE==SQLITE_INDEX_CONSTRAINT_LIKE ); + if( pConfig->ePattern==FTS5_PATTERN_GLOB && p->op==FTS5_PATTERN_GLOB ){ + return 1; + } + if( pConfig->ePattern==FTS5_PATTERN_LIKE + && (p->op==FTS5_PATTERN_LIKE || p->op==FTS5_PATTERN_GLOB) + ){ + return 1; + } + return 0; +} + +/* +** Implementation of the xBestIndex method for FTS5 tables. Within the +** WHERE constraint, it searches for the following: +** +** 1. A MATCH constraint against the table column. +** 2. A MATCH constraint against the "rank" column. +** 3. A MATCH constraint against some other column. +** 4. An == constraint against the rowid column. +** 5. A < or <= constraint against the rowid column. +** 6. A > or >= constraint against the rowid column. +** +** Within the ORDER BY, the following are supported: +** +** 5. ORDER BY rank [ASC|DESC] +** 6. ORDER BY rowid [ASC|DESC] +** +** Information for the xFilter call is passed via both the idxNum and +** idxStr variables. Specifically, idxNum is a bitmask of the following +** flags used to encode the ORDER BY clause: +** +** FTS5_BI_ORDER_RANK +** FTS5_BI_ORDER_ROWID +** FTS5_BI_ORDER_DESC +** +** idxStr is used to encode data from the WHERE clause. For each argument +** passed to the xFilter method, the following is appended to idxStr: +** +** Match against table column: "m" +** Match against rank column: "r" +** Match against other column: "M<column-number>" +** LIKE against other column: "L<column-number>" +** GLOB against other column: "G<column-number>" +** Equality constraint against the rowid: "=" +** A < or <= against the rowid: "<" +** A > or >= against the rowid: ">" +** +** This function ensures that there is at most one "r" or "=". And that if +** there exists an "=" then there is no "<" or ">". +** +** Costs are assigned as follows: +** +** a) If an unusable MATCH operator is present in the WHERE clause, the +** cost is unconditionally set to 1e50 (a really big number). +** +** a) If a MATCH operator is present, the cost depends on the other +** constraints also present. As follows: +** +** * No other constraints: cost=1000.0 +** * One rowid range constraint: cost=750.0 +** * Both rowid range constraints: cost=500.0 +** * An == rowid constraint: cost=100.0 +** +** b) Otherwise, if there is no MATCH: +** +** * No other constraints: cost=1000000.0 +** * One rowid range constraint: cost=750000.0 +** * Both rowid range constraints: cost=250000.0 +** * An == rowid constraint: cost=10.0 +** +** Costs are not modified by the ORDER BY clause. +*/ +static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ + Fts5Table *pTab = (Fts5Table*)pVTab; + Fts5Config *pConfig = pTab->pConfig; + const int nCol = pConfig->nCol; + int idxFlags = 0; /* Parameter passed through to xFilter() */ + int i; + + char *idxStr; + int iIdxStr = 0; + int iCons = 0; + + int bSeenEq = 0; + int bSeenGt = 0; + int bSeenLt = 0; + int bSeenMatch = 0; + int bSeenRank = 0; + + + assert( SQLITE_INDEX_CONSTRAINT_EQ<SQLITE_INDEX_CONSTRAINT_MATCH ); + assert( SQLITE_INDEX_CONSTRAINT_GT<SQLITE_INDEX_CONSTRAINT_MATCH ); + assert( SQLITE_INDEX_CONSTRAINT_LE<SQLITE_INDEX_CONSTRAINT_MATCH ); + assert( SQLITE_INDEX_CONSTRAINT_GE<SQLITE_INDEX_CONSTRAINT_MATCH ); + assert( SQLITE_INDEX_CONSTRAINT_LE<SQLITE_INDEX_CONSTRAINT_MATCH ); + + if( pConfig->bLock ){ + pTab->base.zErrMsg = sqlite3_mprintf( + "recursively defined fts5 content table" + ); + return SQLITE_ERROR; + } + + idxStr = (char*)sqlite3_malloc(pInfo->nConstraint * 8 + 1); + if( idxStr==0 ) return SQLITE_NOMEM; + pInfo->idxStr = idxStr; + pInfo->needToFreeIdxStr = 1; + + for(i=0; i<pInfo->nConstraint; i++){ + struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; + int iCol = p->iColumn; + if( p->op==SQLITE_INDEX_CONSTRAINT_MATCH + || (p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol>=nCol) + ){ + /* A MATCH operator or equivalent */ + if( p->usable==0 || iCol<0 ){ + /* As there exists an unusable MATCH constraint this is an + ** unusable plan. Set a prohibitively high cost. */ + pInfo->estimatedCost = 1e50; + assert( iIdxStr < pInfo->nConstraint*6 + 1 ); + idxStr[iIdxStr] = 0; + return SQLITE_OK; + }else{ + if( iCol==nCol+1 ){ + if( bSeenRank ) continue; + idxStr[iIdxStr++] = 'r'; + bSeenRank = 1; + }else if( iCol>=0 ){ + bSeenMatch = 1; + idxStr[iIdxStr++] = 'M'; + sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); + idxStr += strlen(&idxStr[iIdxStr]); + assert( idxStr[iIdxStr]=='\0' ); + } + pInfo->aConstraintUsage[i].argvIndex = ++iCons; + pInfo->aConstraintUsage[i].omit = 1; + } + }else if( p->usable ){ + if( iCol>=0 && iCol<nCol && fts5UsePatternMatch(pConfig, p) ){ + assert( p->op==FTS5_PATTERN_LIKE || p->op==FTS5_PATTERN_GLOB ); + idxStr[iIdxStr++] = p->op==FTS5_PATTERN_LIKE ? 'L' : 'G'; + sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); + idxStr += strlen(&idxStr[iIdxStr]); + pInfo->aConstraintUsage[i].argvIndex = ++iCons; + assert( idxStr[iIdxStr]=='\0' ); + }else if( bSeenEq==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0 ){ + idxStr[iIdxStr++] = '='; + bSeenEq = 1; + pInfo->aConstraintUsage[i].argvIndex = ++iCons; + } + } + } + + if( bSeenEq==0 ){ + for(i=0; i<pInfo->nConstraint; i++){ + struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; + if( p->iColumn<0 && p->usable ){ + int op = p->op; + if( op==SQLITE_INDEX_CONSTRAINT_LT || op==SQLITE_INDEX_CONSTRAINT_LE ){ + if( bSeenLt ) continue; + idxStr[iIdxStr++] = '<'; + pInfo->aConstraintUsage[i].argvIndex = ++iCons; + bSeenLt = 1; + }else + if( op==SQLITE_INDEX_CONSTRAINT_GT || op==SQLITE_INDEX_CONSTRAINT_GE ){ + if( bSeenGt ) continue; + idxStr[iIdxStr++] = '>'; + pInfo->aConstraintUsage[i].argvIndex = ++iCons; + bSeenGt = 1; + } + } + } + } + idxStr[iIdxStr] = '\0'; + + /* Set idxFlags flags for the ORDER BY clause */ + if( pInfo->nOrderBy==1 ){ + int iSort = pInfo->aOrderBy[0].iColumn; + if( iSort==(pConfig->nCol+1) && bSeenMatch ){ + idxFlags |= FTS5_BI_ORDER_RANK; + }else if( iSort==-1 ){ + idxFlags |= FTS5_BI_ORDER_ROWID; + } + if( BitFlagTest(idxFlags, FTS5_BI_ORDER_RANK|FTS5_BI_ORDER_ROWID) ){ + pInfo->orderByConsumed = 1; + if( pInfo->aOrderBy[0].desc ){ + idxFlags |= FTS5_BI_ORDER_DESC; + } + } + } + + /* Calculate the estimated cost based on the flags set in idxFlags. */ + if( bSeenEq ){ + pInfo->estimatedCost = bSeenMatch ? 100.0 : 10.0; + if( bSeenMatch==0 ) fts5SetUniqueFlag(pInfo); + }else if( bSeenLt && bSeenGt ){ + pInfo->estimatedCost = bSeenMatch ? 500.0 : 250000.0; + }else if( bSeenLt || bSeenGt ){ + pInfo->estimatedCost = bSeenMatch ? 750.0 : 750000.0; + }else{ + pInfo->estimatedCost = bSeenMatch ? 1000.0 : 1000000.0; + } + + pInfo->idxNum = idxFlags; + return SQLITE_OK; +} + +static int fts5NewTransaction(Fts5FullTable *pTab){ + Fts5Cursor *pCsr; + for(pCsr=pTab->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){ + if( pCsr->base.pVtab==(sqlite3_vtab*)pTab ) return SQLITE_OK; + } + return sqlite3Fts5StorageReset(pTab->pStorage); +} + +/* +** Implementation of xOpen method. +*/ +static int fts5OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ + Fts5FullTable *pTab = (Fts5FullTable*)pVTab; + Fts5Config *pConfig = pTab->p.pConfig; + Fts5Cursor *pCsr = 0; /* New cursor object */ + sqlite3_int64 nByte; /* Bytes of space to allocate */ + int rc; /* Return code */ + + rc = fts5NewTransaction(pTab); + if( rc==SQLITE_OK ){ + nByte = sizeof(Fts5Cursor) + pConfig->nCol * sizeof(int); + pCsr = (Fts5Cursor*)sqlite3_malloc64(nByte); + if( pCsr ){ + Fts5Global *pGlobal = pTab->pGlobal; + memset(pCsr, 0, (size_t)nByte); + pCsr->aColumnSize = (int*)&pCsr[1]; + pCsr->pNext = pGlobal->pCsr; + pGlobal->pCsr = pCsr; + pCsr->iCsrId = ++pGlobal->iNextId; + }else{ + rc = SQLITE_NOMEM; + } + } + *ppCsr = (sqlite3_vtab_cursor*)pCsr; + return rc; +} + +static int fts5StmtType(Fts5Cursor *pCsr){ + if( pCsr->ePlan==FTS5_PLAN_SCAN ){ + return (pCsr->bDesc) ? FTS5_STMT_SCAN_DESC : FTS5_STMT_SCAN_ASC; + } + return FTS5_STMT_LOOKUP; +} + +/* +** This function is called after the cursor passed as the only argument +** is moved to point at a different row. It clears all cached data +** specific to the previous row stored by the cursor object. +*/ +static void fts5CsrNewrow(Fts5Cursor *pCsr){ + CsrFlagSet(pCsr, + FTS5CSR_REQUIRE_CONTENT + | FTS5CSR_REQUIRE_DOCSIZE + | FTS5CSR_REQUIRE_INST + | FTS5CSR_REQUIRE_POSLIST + ); +} + +static void fts5FreeCursorComponents(Fts5Cursor *pCsr){ + Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); + Fts5Auxdata *pData; + Fts5Auxdata *pNext; + + sqlite3_free(pCsr->aInstIter); + sqlite3_free(pCsr->aInst); + if( pCsr->pStmt ){ + int eStmt = fts5StmtType(pCsr); + sqlite3Fts5StorageStmtRelease(pTab->pStorage, eStmt, pCsr->pStmt); + } + if( pCsr->pSorter ){ + Fts5Sorter *pSorter = pCsr->pSorter; + sqlite3_finalize(pSorter->pStmt); + sqlite3_free(pSorter); + } + + if( pCsr->ePlan!=FTS5_PLAN_SOURCE ){ + sqlite3Fts5ExprFree(pCsr->pExpr); + } + + for(pData=pCsr->pAuxdata; pData; pData=pNext){ + pNext = pData->pNext; + if( pData->xDelete ) pData->xDelete(pData->pPtr); + sqlite3_free(pData); + } + + sqlite3_finalize(pCsr->pRankArgStmt); + sqlite3_free(pCsr->apRankArg); + + if( CsrFlagTest(pCsr, FTS5CSR_FREE_ZRANK) ){ + sqlite3_free(pCsr->zRank); + sqlite3_free(pCsr->zRankArgs); + } + + sqlite3Fts5IndexCloseReader(pTab->p.pIndex); + memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan - (u8*)pCsr)); +} + + +/* +** Close the cursor. For additional information see the documentation +** on the xClose method of the virtual table interface. +*/ +static int fts5CloseMethod(sqlite3_vtab_cursor *pCursor){ + if( pCursor ){ + Fts5FullTable *pTab = (Fts5FullTable*)(pCursor->pVtab); + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; + Fts5Cursor **pp; + + fts5FreeCursorComponents(pCsr); + /* Remove the cursor from the Fts5Global.pCsr list */ + for(pp=&pTab->pGlobal->pCsr; (*pp)!=pCsr; pp=&(*pp)->pNext); + *pp = pCsr->pNext; + + sqlite3_free(pCsr); + } + return SQLITE_OK; +} + +static int fts5SorterNext(Fts5Cursor *pCsr){ + Fts5Sorter *pSorter = pCsr->pSorter; + int rc; + + rc = sqlite3_step(pSorter->pStmt); + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + CsrFlagSet(pCsr, FTS5CSR_EOF|FTS5CSR_REQUIRE_CONTENT); + }else if( rc==SQLITE_ROW ){ + const u8 *a; + const u8 *aBlob; + int nBlob; + int i; + int iOff = 0; + rc = SQLITE_OK; + + pSorter->iRowid = sqlite3_column_int64(pSorter->pStmt, 0); + nBlob = sqlite3_column_bytes(pSorter->pStmt, 1); + aBlob = a = sqlite3_column_blob(pSorter->pStmt, 1); + + /* nBlob==0 in detail=none mode. */ + if( nBlob>0 ){ + for(i=0; i<(pSorter->nIdx-1); i++){ + int iVal; + a += fts5GetVarint32(a, iVal); + iOff += iVal; + pSorter->aIdx[i] = iOff; + } + pSorter->aIdx[i] = &aBlob[nBlob] - a; + pSorter->aPoslist = a; + } + + fts5CsrNewrow(pCsr); + } + + return rc; +} + + +/* +** Set the FTS5CSR_REQUIRE_RESEEK flag on all FTS5_PLAN_MATCH cursors +** open on table pTab. +*/ +static void fts5TripCursors(Fts5FullTable *pTab){ + Fts5Cursor *pCsr; + for(pCsr=pTab->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){ + if( pCsr->ePlan==FTS5_PLAN_MATCH + && pCsr->base.pVtab==(sqlite3_vtab*)pTab + ){ + CsrFlagSet(pCsr, FTS5CSR_REQUIRE_RESEEK); + } + } +} + +/* +** If the REQUIRE_RESEEK flag is set on the cursor passed as the first +** argument, close and reopen all Fts5IndexIter iterators that the cursor +** is using. Then attempt to move the cursor to a rowid equal to or laster +** (in the cursors sort order - ASC or DESC) than the current rowid. +** +** If the new rowid is not equal to the old, set output parameter *pbSkip +** to 1 before returning. Otherwise, leave it unchanged. +** +** Return SQLITE_OK if successful or if no reseek was required, or an +** error code if an error occurred. +*/ +static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){ + int rc = SQLITE_OK; + assert( *pbSkip==0 ); + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_RESEEK) ){ + Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); + int bDesc = pCsr->bDesc; + i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr); + + rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->p.pIndex, iRowid, bDesc); + if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){ + *pbSkip = 1; + } + + CsrFlagClear(pCsr, FTS5CSR_REQUIRE_RESEEK); + fts5CsrNewrow(pCsr); + if( sqlite3Fts5ExprEof(pCsr->pExpr) ){ + CsrFlagSet(pCsr, FTS5CSR_EOF); + *pbSkip = 1; + } + } + return rc; +} + + +/* +** Advance the cursor to the next row in the table that matches the +** search criteria. +** +** Return SQLITE_OK if nothing goes wrong. SQLITE_OK is returned +** even if we reach end-of-file. The fts5EofMethod() will be called +** subsequently to determine whether or not an EOF was hit. +*/ +static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; + int rc; + + assert( (pCsr->ePlan<3)== + (pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SOURCE) + ); + assert( !CsrFlagTest(pCsr, FTS5CSR_EOF) ); + + if( pCsr->ePlan<3 ){ + int bSkip = 0; + if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc; + rc = sqlite3Fts5ExprNext(pCsr->pExpr, pCsr->iLastRowid); + CsrFlagSet(pCsr, sqlite3Fts5ExprEof(pCsr->pExpr)); + fts5CsrNewrow(pCsr); + }else{ + switch( pCsr->ePlan ){ + case FTS5_PLAN_SPECIAL: { + CsrFlagSet(pCsr, FTS5CSR_EOF); + rc = SQLITE_OK; + break; + } + + case FTS5_PLAN_SORTED_MATCH: { + rc = fts5SorterNext(pCsr); + break; + } + + default: { + Fts5Config *pConfig = ((Fts5Table*)pCursor->pVtab)->pConfig; + pConfig->bLock++; + rc = sqlite3_step(pCsr->pStmt); + pConfig->bLock--; + if( rc!=SQLITE_ROW ){ + CsrFlagSet(pCsr, FTS5CSR_EOF); + rc = sqlite3_reset(pCsr->pStmt); + if( rc!=SQLITE_OK ){ + pCursor->pVtab->zErrMsg = sqlite3_mprintf( + "%s", sqlite3_errmsg(pConfig->db) + ); + } + }else{ + rc = SQLITE_OK; + } + break; + } + } + } + + return rc; +} + + +static int fts5PrepareStatement( + sqlite3_stmt **ppStmt, + Fts5Config *pConfig, + const char *zFmt, + ... +){ + sqlite3_stmt *pRet = 0; + int rc; + char *zSql; + va_list ap; + + va_start(ap, zFmt); + zSql = sqlite3_vmprintf(zFmt, ap); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v3(pConfig->db, zSql, -1, + SQLITE_PREPARE_PERSISTENT, &pRet, 0); + if( rc!=SQLITE_OK ){ + *pConfig->pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(pConfig->db)); + } + sqlite3_free(zSql); + } + + va_end(ap); + *ppStmt = pRet; + return rc; +} + +static int fts5CursorFirstSorted( + Fts5FullTable *pTab, + Fts5Cursor *pCsr, + int bDesc +){ + Fts5Config *pConfig = pTab->p.pConfig; + Fts5Sorter *pSorter; + int nPhrase; + sqlite3_int64 nByte; + int rc; + const char *zRank = pCsr->zRank; + const char *zRankArgs = pCsr->zRankArgs; + + nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr); + nByte = sizeof(Fts5Sorter) + sizeof(int) * (nPhrase-1); + pSorter = (Fts5Sorter*)sqlite3_malloc64(nByte); + if( pSorter==0 ) return SQLITE_NOMEM; + memset(pSorter, 0, (size_t)nByte); + pSorter->nIdx = nPhrase; + + /* TODO: It would be better to have some system for reusing statement + ** handles here, rather than preparing a new one for each query. But that + ** is not possible as SQLite reference counts the virtual table objects. + ** And since the statement required here reads from this very virtual + ** table, saving it creates a circular reference. + ** + ** If SQLite a built-in statement cache, this wouldn't be a problem. */ + rc = fts5PrepareStatement(&pSorter->pStmt, pConfig, + "SELECT rowid, rank FROM %Q.%Q ORDER BY %s(\"%w\"%s%s) %s", + pConfig->zDb, pConfig->zName, zRank, pConfig->zName, + (zRankArgs ? ", " : ""), + (zRankArgs ? zRankArgs : ""), + bDesc ? "DESC" : "ASC" + ); + + pCsr->pSorter = pSorter; + if( rc==SQLITE_OK ){ + assert( pTab->pSortCsr==0 ); + pTab->pSortCsr = pCsr; + rc = fts5SorterNext(pCsr); + pTab->pSortCsr = 0; + } + + if( rc!=SQLITE_OK ){ + sqlite3_finalize(pSorter->pStmt); + sqlite3_free(pSorter); + pCsr->pSorter = 0; + } + + return rc; +} + +static int fts5CursorFirst(Fts5FullTable *pTab, Fts5Cursor *pCsr, int bDesc){ + int rc; + Fts5Expr *pExpr = pCsr->pExpr; + rc = sqlite3Fts5ExprFirst(pExpr, pTab->p.pIndex, pCsr->iFirstRowid, bDesc); + if( sqlite3Fts5ExprEof(pExpr) ){ + CsrFlagSet(pCsr, FTS5CSR_EOF); + } + fts5CsrNewrow(pCsr); + return rc; +} + +/* +** Process a "special" query. A special query is identified as one with a +** MATCH expression that begins with a '*' character. The remainder of +** the text passed to the MATCH operator are used as the special query +** parameters. +*/ +static int fts5SpecialMatch( + Fts5FullTable *pTab, + Fts5Cursor *pCsr, + const char *zQuery +){ + int rc = SQLITE_OK; /* Return code */ + const char *z = zQuery; /* Special query text */ + int n; /* Number of bytes in text at z */ + + while( z[0]==' ' ) z++; + for(n=0; z[n] && z[n]!=' '; n++); + + assert( pTab->p.base.zErrMsg==0 ); + pCsr->ePlan = FTS5_PLAN_SPECIAL; + + if( n==5 && 0==sqlite3_strnicmp("reads", z, n) ){ + pCsr->iSpecial = sqlite3Fts5IndexReads(pTab->p.pIndex); + } + else if( n==2 && 0==sqlite3_strnicmp("id", z, n) ){ + pCsr->iSpecial = pCsr->iCsrId; + } + else{ + /* An unrecognized directive. Return an error message. */ + pTab->p.base.zErrMsg = sqlite3_mprintf("unknown special query: %.*s", n, z); + rc = SQLITE_ERROR; + } + + return rc; +} + +/* +** Search for an auxiliary function named zName that can be used with table +** pTab. If one is found, return a pointer to the corresponding Fts5Auxiliary +** structure. Otherwise, if no such function exists, return NULL. +*/ +static Fts5Auxiliary *fts5FindAuxiliary(Fts5FullTable *pTab, const char *zName){ + Fts5Auxiliary *pAux; + + for(pAux=pTab->pGlobal->pAux; pAux; pAux=pAux->pNext){ + if( sqlite3_stricmp(zName, pAux->zFunc)==0 ) return pAux; + } + + /* No function of the specified name was found. Return 0. */ + return 0; +} + + +static int fts5FindRankFunction(Fts5Cursor *pCsr){ + Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); + Fts5Config *pConfig = pTab->p.pConfig; + int rc = SQLITE_OK; + Fts5Auxiliary *pAux = 0; + const char *zRank = pCsr->zRank; + const char *zRankArgs = pCsr->zRankArgs; + + if( zRankArgs ){ + char *zSql = sqlite3Fts5Mprintf(&rc, "SELECT %s", zRankArgs); + if( zSql ){ + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v3(pConfig->db, zSql, -1, + SQLITE_PREPARE_PERSISTENT, &pStmt, 0); + sqlite3_free(zSql); + assert( rc==SQLITE_OK || pCsr->pRankArgStmt==0 ); + if( rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + sqlite3_int64 nByte; + pCsr->nRankArg = sqlite3_column_count(pStmt); + nByte = sizeof(sqlite3_value*)*pCsr->nRankArg; + pCsr->apRankArg = (sqlite3_value**)sqlite3Fts5MallocZero(&rc, nByte); + if( rc==SQLITE_OK ){ + int i; + for(i=0; i<pCsr->nRankArg; i++){ + pCsr->apRankArg[i] = sqlite3_column_value(pStmt, i); + } + } + pCsr->pRankArgStmt = pStmt; + }else{ + rc = sqlite3_finalize(pStmt); + assert( rc!=SQLITE_OK ); + } + } + } + } + + if( rc==SQLITE_OK ){ + pAux = fts5FindAuxiliary(pTab, zRank); + if( pAux==0 ){ + assert( pTab->p.base.zErrMsg==0 ); + pTab->p.base.zErrMsg = sqlite3_mprintf("no such function: %s", zRank); + rc = SQLITE_ERROR; + } + } + + pCsr->pRank = pAux; + return rc; +} + + +static int fts5CursorParseRank( + Fts5Config *pConfig, + Fts5Cursor *pCsr, + sqlite3_value *pRank +){ + int rc = SQLITE_OK; + if( pRank ){ + const char *z = (const char*)sqlite3_value_text(pRank); + char *zRank = 0; + char *zRankArgs = 0; + + if( z==0 ){ + if( sqlite3_value_type(pRank)==SQLITE_NULL ) rc = SQLITE_ERROR; + }else{ + rc = sqlite3Fts5ConfigParseRank(z, &zRank, &zRankArgs); + } + if( rc==SQLITE_OK ){ + pCsr->zRank = zRank; + pCsr->zRankArgs = zRankArgs; + CsrFlagSet(pCsr, FTS5CSR_FREE_ZRANK); + }else if( rc==SQLITE_ERROR ){ + pCsr->base.pVtab->zErrMsg = sqlite3_mprintf( + "parse error in rank function: %s", z + ); + } + }else{ + if( pConfig->zRank ){ + pCsr->zRank = (char*)pConfig->zRank; + pCsr->zRankArgs = (char*)pConfig->zRankArgs; + }else{ + pCsr->zRank = (char*)FTS5_DEFAULT_RANK; + pCsr->zRankArgs = 0; + } + } + return rc; +} + +static i64 fts5GetRowidLimit(sqlite3_value *pVal, i64 iDefault){ + if( pVal ){ + int eType = sqlite3_value_numeric_type(pVal); + if( eType==SQLITE_INTEGER ){ + return sqlite3_value_int64(pVal); + } + } + return iDefault; +} + +/* +** This is the xFilter interface for the virtual table. See +** the virtual table xFilter method documentation for additional +** information. +** +** There are three possible query strategies: +** +** 1. Full-text search using a MATCH operator. +** 2. A by-rowid lookup. +** 3. A full-table scan. +*/ +static int fts5FilterMethod( + sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ + int idxNum, /* Strategy index */ + const char *idxStr, /* Unused */ + int nVal, /* Number of elements in apVal */ + sqlite3_value **apVal /* Arguments for the indexing scheme */ +){ + Fts5FullTable *pTab = (Fts5FullTable*)(pCursor->pVtab); + Fts5Config *pConfig = pTab->p.pConfig; + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; + int rc = SQLITE_OK; /* Error code */ + int bDesc; /* True if ORDER BY [rank|rowid] DESC */ + int bOrderByRank; /* True if ORDER BY rank */ + sqlite3_value *pRank = 0; /* rank MATCH ? expression (or NULL) */ + sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */ + sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */ + sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */ + int iCol; /* Column on LHS of MATCH operator */ + char **pzErrmsg = pConfig->pzErrmsg; + int i; + int iIdxStr = 0; + Fts5Expr *pExpr = 0; + + if( pConfig->bLock ){ + pTab->p.base.zErrMsg = sqlite3_mprintf( + "recursively defined fts5 content table" + ); + return SQLITE_ERROR; + } + + if( pCsr->ePlan ){ + fts5FreeCursorComponents(pCsr); + memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan-(u8*)pCsr)); + } + + assert( pCsr->pStmt==0 ); + assert( pCsr->pExpr==0 ); + assert( pCsr->csrflags==0 ); + assert( pCsr->pRank==0 ); + assert( pCsr->zRank==0 ); + assert( pCsr->zRankArgs==0 ); + assert( pTab->pSortCsr==0 || nVal==0 ); + + assert( pzErrmsg==0 || pzErrmsg==&pTab->p.base.zErrMsg ); + pConfig->pzErrmsg = &pTab->p.base.zErrMsg; + + /* Decode the arguments passed through to this function. */ + for(i=0; i<nVal; i++){ + switch( idxStr[iIdxStr++] ){ + case 'r': + pRank = apVal[i]; + break; + case 'M': { + const char *zText = (const char*)sqlite3_value_text(apVal[i]); + if( zText==0 ) zText = ""; + iCol = 0; + do{ + iCol = iCol*10 + (idxStr[iIdxStr]-'0'); + iIdxStr++; + }while( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' ); + + if( zText[0]=='*' ){ + /* The user has issued a query of the form "MATCH '*...'". This + ** indicates that the MATCH expression is not a full text query, + ** but a request for an internal parameter. */ + rc = fts5SpecialMatch(pTab, pCsr, &zText[1]); + goto filter_out; + }else{ + char **pzErr = &pTab->p.base.zErrMsg; + rc = sqlite3Fts5ExprNew(pConfig, 0, iCol, zText, &pExpr, pzErr); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ExprAnd(&pCsr->pExpr, pExpr); + pExpr = 0; + } + if( rc!=SQLITE_OK ) goto filter_out; + } + + break; + } + case 'L': + case 'G': { + int bGlob = (idxStr[iIdxStr-1]=='G'); + const char *zText = (const char*)sqlite3_value_text(apVal[i]); + iCol = 0; + do{ + iCol = iCol*10 + (idxStr[iIdxStr]-'0'); + iIdxStr++; + }while( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' ); + if( zText ){ + rc = sqlite3Fts5ExprPattern(pConfig, bGlob, iCol, zText, &pExpr); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ExprAnd(&pCsr->pExpr, pExpr); + pExpr = 0; + } + if( rc!=SQLITE_OK ) goto filter_out; + break; + } + case '=': + pRowidEq = apVal[i]; + break; + case '<': + pRowidLe = apVal[i]; + break; + default: assert( idxStr[iIdxStr-1]=='>' ); + pRowidGe = apVal[i]; + break; + } + } + bOrderByRank = ((idxNum & FTS5_BI_ORDER_RANK) ? 1 : 0); + pCsr->bDesc = bDesc = ((idxNum & FTS5_BI_ORDER_DESC) ? 1 : 0); + + /* Set the cursor upper and lower rowid limits. Only some strategies + ** actually use them. This is ok, as the xBestIndex() method leaves the + ** sqlite3_index_constraint.omit flag clear for range constraints + ** on the rowid field. */ + if( pRowidEq ){ + pRowidLe = pRowidGe = pRowidEq; + } + if( bDesc ){ + pCsr->iFirstRowid = fts5GetRowidLimit(pRowidLe, LARGEST_INT64); + pCsr->iLastRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64); + }else{ + pCsr->iLastRowid = fts5GetRowidLimit(pRowidLe, LARGEST_INT64); + pCsr->iFirstRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64); + } + + if( pTab->pSortCsr ){ + /* If pSortCsr is non-NULL, then this call is being made as part of + ** processing for a "... MATCH <expr> ORDER BY rank" query (ePlan is + ** set to FTS5_PLAN_SORTED_MATCH). pSortCsr is the cursor that will + ** return results to the user for this query. The current cursor + ** (pCursor) is used to execute the query issued by function + ** fts5CursorFirstSorted() above. */ + assert( pRowidEq==0 && pRowidLe==0 && pRowidGe==0 && pRank==0 ); + assert( nVal==0 && bOrderByRank==0 && bDesc==0 ); + assert( pCsr->iLastRowid==LARGEST_INT64 ); + assert( pCsr->iFirstRowid==SMALLEST_INT64 ); + if( pTab->pSortCsr->bDesc ){ + pCsr->iLastRowid = pTab->pSortCsr->iFirstRowid; + pCsr->iFirstRowid = pTab->pSortCsr->iLastRowid; + }else{ + pCsr->iLastRowid = pTab->pSortCsr->iLastRowid; + pCsr->iFirstRowid = pTab->pSortCsr->iFirstRowid; + } + pCsr->ePlan = FTS5_PLAN_SOURCE; + pCsr->pExpr = pTab->pSortCsr->pExpr; + rc = fts5CursorFirst(pTab, pCsr, bDesc); + }else if( pCsr->pExpr ){ + rc = fts5CursorParseRank(pConfig, pCsr, pRank); + if( rc==SQLITE_OK ){ + if( bOrderByRank ){ + pCsr->ePlan = FTS5_PLAN_SORTED_MATCH; + rc = fts5CursorFirstSorted(pTab, pCsr, bDesc); + }else{ + pCsr->ePlan = FTS5_PLAN_MATCH; + rc = fts5CursorFirst(pTab, pCsr, bDesc); + } + } + }else if( pConfig->zContent==0 ){ + *pConfig->pzErrmsg = sqlite3_mprintf( + "%s: table does not support scanning", pConfig->zName + ); + rc = SQLITE_ERROR; + }else{ + /* This is either a full-table scan (ePlan==FTS5_PLAN_SCAN) or a lookup + ** by rowid (ePlan==FTS5_PLAN_ROWID). */ + pCsr->ePlan = (pRowidEq ? FTS5_PLAN_ROWID : FTS5_PLAN_SCAN); + rc = sqlite3Fts5StorageStmt( + pTab->pStorage, fts5StmtType(pCsr), &pCsr->pStmt, &pTab->p.base.zErrMsg + ); + if( rc==SQLITE_OK ){ + if( pRowidEq!=0 ){ + assert( pCsr->ePlan==FTS5_PLAN_ROWID ); + sqlite3_bind_value(pCsr->pStmt, 1, pRowidEq); + }else{ + sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iFirstRowid); + sqlite3_bind_int64(pCsr->pStmt, 2, pCsr->iLastRowid); + } + rc = fts5NextMethod(pCursor); + } + } + + filter_out: + sqlite3Fts5ExprFree(pExpr); + pConfig->pzErrmsg = pzErrmsg; + return rc; +} + +/* +** This is the xEof method of the virtual table. SQLite calls this +** routine to find out if it has reached the end of a result set. +*/ +static int fts5EofMethod(sqlite3_vtab_cursor *pCursor){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; + return (CsrFlagTest(pCsr, FTS5CSR_EOF) ? 1 : 0); +} + +/* +** Return the rowid that the cursor currently points to. +*/ +static i64 fts5CursorRowid(Fts5Cursor *pCsr){ + assert( pCsr->ePlan==FTS5_PLAN_MATCH + || pCsr->ePlan==FTS5_PLAN_SORTED_MATCH + || pCsr->ePlan==FTS5_PLAN_SOURCE + ); + if( pCsr->pSorter ){ + return pCsr->pSorter->iRowid; + }else{ + return sqlite3Fts5ExprRowid(pCsr->pExpr); + } +} + +/* +** This is the xRowid method. The SQLite core calls this routine to +** retrieve the rowid for the current row of the result set. fts5 +** exposes %_content.rowid as the rowid for the virtual table. The +** rowid should be written to *pRowid. +*/ +static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; + int ePlan = pCsr->ePlan; + + assert( CsrFlagTest(pCsr, FTS5CSR_EOF)==0 ); + switch( ePlan ){ + case FTS5_PLAN_SPECIAL: + *pRowid = 0; + break; + + case FTS5_PLAN_SOURCE: + case FTS5_PLAN_MATCH: + case FTS5_PLAN_SORTED_MATCH: + *pRowid = fts5CursorRowid(pCsr); + break; + + default: + *pRowid = sqlite3_column_int64(pCsr->pStmt, 0); + break; + } + + return SQLITE_OK; +} + +/* +** If the cursor requires seeking (bSeekRequired flag is set), seek it. +** Return SQLITE_OK if no error occurs, or an SQLite error code otherwise. +** +** If argument bErrormsg is true and an error occurs, an error message may +** be left in sqlite3_vtab.zErrMsg. +*/ +static int fts5SeekCursor(Fts5Cursor *pCsr, int bErrormsg){ + int rc = SQLITE_OK; + + /* If the cursor does not yet have a statement handle, obtain one now. */ + if( pCsr->pStmt==0 ){ + Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); + int eStmt = fts5StmtType(pCsr); + rc = sqlite3Fts5StorageStmt( + pTab->pStorage, eStmt, &pCsr->pStmt, (bErrormsg?&pTab->p.base.zErrMsg:0) + ); + assert( rc!=SQLITE_OK || pTab->p.base.zErrMsg==0 ); + assert( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_CONTENT) ); + } + + if( rc==SQLITE_OK && CsrFlagTest(pCsr, FTS5CSR_REQUIRE_CONTENT) ){ + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab); + assert( pCsr->pExpr ); + sqlite3_reset(pCsr->pStmt); + sqlite3_bind_int64(pCsr->pStmt, 1, fts5CursorRowid(pCsr)); + pTab->pConfig->bLock++; + rc = sqlite3_step(pCsr->pStmt); + pTab->pConfig->bLock--; + if( rc==SQLITE_ROW ){ + rc = SQLITE_OK; + CsrFlagClear(pCsr, FTS5CSR_REQUIRE_CONTENT); + }else{ + rc = sqlite3_reset(pCsr->pStmt); + if( rc==SQLITE_OK ){ + rc = FTS5_CORRUPT; + }else if( pTab->pConfig->pzErrmsg ){ + *pTab->pConfig->pzErrmsg = sqlite3_mprintf( + "%s", sqlite3_errmsg(pTab->pConfig->db) + ); + } + } + } + return rc; +} + +static void fts5SetVtabError(Fts5FullTable *p, const char *zFormat, ...){ + va_list ap; /* ... printf arguments */ + va_start(ap, zFormat); + assert( p->p.base.zErrMsg==0 ); + p->p.base.zErrMsg = sqlite3_vmprintf(zFormat, ap); + va_end(ap); +} + +/* +** This function is called to handle an FTS INSERT command. In other words, +** an INSERT statement of the form: +** +** INSERT INTO fts(fts) VALUES($pCmd) +** INSERT INTO fts(fts, rank) VALUES($pCmd, $pVal) +** +** Argument pVal is the value assigned to column "fts" by the INSERT +** statement. This function returns SQLITE_OK if successful, or an SQLite +** error code if an error occurs. +** +** The commands implemented by this function are documented in the "Special +** INSERT Directives" section of the documentation. It should be updated if +** more commands are added to this function. +*/ +static int fts5SpecialInsert( + Fts5FullTable *pTab, /* Fts5 table object */ + const char *zCmd, /* Text inserted into table-name column */ + sqlite3_value *pVal /* Value inserted into rank column */ +){ + Fts5Config *pConfig = pTab->p.pConfig; + int rc = SQLITE_OK; + int bError = 0; + + if( 0==sqlite3_stricmp("delete-all", zCmd) ){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + fts5SetVtabError(pTab, + "'delete-all' may only be used with a " + "contentless or external content fts5 table" + ); + rc = SQLITE_ERROR; + }else{ + rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage); + } + }else if( 0==sqlite3_stricmp("rebuild", zCmd) ){ + if( pConfig->eContent==FTS5_CONTENT_NONE ){ + fts5SetVtabError(pTab, + "'rebuild' may not be used with a contentless fts5 table" + ); + rc = SQLITE_ERROR; + }else{ + rc = sqlite3Fts5StorageRebuild(pTab->pStorage); + } + }else if( 0==sqlite3_stricmp("optimize", zCmd) ){ + rc = sqlite3Fts5StorageOptimize(pTab->pStorage); + }else if( 0==sqlite3_stricmp("merge", zCmd) ){ + int nMerge = sqlite3_value_int(pVal); + rc = sqlite3Fts5StorageMerge(pTab->pStorage, nMerge); + }else if( 0==sqlite3_stricmp("integrity-check", zCmd) ){ + int iArg = sqlite3_value_int(pVal); + rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, iArg); +#ifdef SQLITE_DEBUG + }else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){ + pConfig->bPrefixIndex = sqlite3_value_int(pVal); +#endif + }else{ + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ConfigSetValue(pTab->p.pConfig, zCmd, pVal, &bError); + } + if( rc==SQLITE_OK ){ + if( bError ){ + rc = SQLITE_ERROR; + }else{ + rc = sqlite3Fts5StorageConfigValue(pTab->pStorage, zCmd, pVal, 0); + } + } + } + return rc; +} + +static int fts5SpecialDelete( + Fts5FullTable *pTab, + sqlite3_value **apVal +){ + int rc = SQLITE_OK; + int eType1 = sqlite3_value_type(apVal[1]); + if( eType1==SQLITE_INTEGER ){ + sqlite3_int64 iDel = sqlite3_value_int64(apVal[1]); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, &apVal[2]); + } + return rc; +} + +static void fts5StorageInsert( + int *pRc, + Fts5FullTable *pTab, + sqlite3_value **apVal, + i64 *piRowid +){ + int rc = *pRc; + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, piRowid); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *piRowid); + } + *pRc = rc; +} + +/* +** This function is the implementation of the xUpdate callback used by +** FTS3 virtual tables. It is invoked by SQLite each time a row is to be +** inserted, updated or deleted. +** +** A delete specifies a single argument - the rowid of the row to remove. +** +** Update and insert operations pass: +** +** 1. The "old" rowid, or NULL. +** 2. The "new" rowid. +** 3. Values for each of the nCol matchable columns. +** 4. Values for the two hidden columns (<tablename> and "rank"). +*/ +static int fts5UpdateMethod( + sqlite3_vtab *pVtab, /* Virtual table handle */ + int nArg, /* Size of argument array */ + sqlite3_value **apVal, /* Array of arguments */ + sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */ +){ + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + Fts5Config *pConfig = pTab->p.pConfig; + int eType0; /* value_type() of apVal[0] */ + int rc = SQLITE_OK; /* Return code */ + int bUpdateOrDelete = 0; + + /* A transaction must be open when this is called. */ + assert( pTab->ts.eState==1 || pTab->ts.eState==2 ); + + assert( pVtab->zErrMsg==0 ); + assert( nArg==1 || nArg==(2+pConfig->nCol+2) ); + assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER + || sqlite3_value_type(apVal[0])==SQLITE_NULL + ); + assert( pTab->p.pConfig->pzErrmsg==0 ); + if( pConfig->pgsz==0 ){ + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + if( rc!=SQLITE_OK ) return rc; + } + + pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg; + + /* Put any active cursors into REQUIRE_SEEK state. */ + fts5TripCursors(pTab); + + eType0 = sqlite3_value_type(apVal[0]); + if( eType0==SQLITE_NULL + && sqlite3_value_type(apVal[2+pConfig->nCol])!=SQLITE_NULL + ){ + /* A "special" INSERT op. These are handled separately. */ + const char *z = (const char*)sqlite3_value_text(apVal[2+pConfig->nCol]); + if( pConfig->eContent!=FTS5_CONTENT_NORMAL + && 0==sqlite3_stricmp("delete", z) + ){ + if( pConfig->bContentlessDelete ){ + fts5SetVtabError(pTab, + "'delete' may not be used with a contentless_delete=1 table" + ); + rc = SQLITE_ERROR; + }else{ + rc = fts5SpecialDelete(pTab, apVal); + } + }else{ + rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]); + } + }else{ + /* A regular INSERT, UPDATE or DELETE statement. The trick here is that + ** any conflict on the rowid value must be detected before any + ** modifications are made to the database file. There are 4 cases: + ** + ** 1) DELETE + ** 2) UPDATE (rowid not modified) + ** 3) UPDATE (rowid modified) + ** 4) INSERT + ** + ** Cases 3 and 4 may violate the rowid constraint. + */ + int eConflict = SQLITE_ABORT; + if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->bContentlessDelete ){ + eConflict = sqlite3_vtab_on_conflict(pConfig->db); + } + + assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL ); + assert( nArg!=1 || eType0==SQLITE_INTEGER ); + + /* Filter out attempts to run UPDATE or DELETE on contentless tables. + ** This is not suported. Except - DELETE is supported if the CREATE + ** VIRTUAL TABLE statement contained "contentless_delete=1". */ + if( eType0==SQLITE_INTEGER + && pConfig->eContent==FTS5_CONTENT_NONE + && pConfig->bContentlessDelete==0 + ){ + pTab->p.base.zErrMsg = sqlite3_mprintf( + "cannot %s contentless fts5 table: %s", + (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName + ); + rc = SQLITE_ERROR; + } + + /* DELETE */ + else if( nArg==1 ){ + i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0); + bUpdateOrDelete = 1; + } + + /* INSERT or UPDATE */ + else{ + int eType1 = sqlite3_value_numeric_type(apVal[1]); + + if( eType1!=SQLITE_INTEGER && eType1!=SQLITE_NULL ){ + rc = SQLITE_MISMATCH; + } + + else if( eType0!=SQLITE_INTEGER ){ + /* If this is a REPLACE, first remove the current entry (if any) */ + if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){ + i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); + bUpdateOrDelete = 1; + } + fts5StorageInsert(&rc, pTab, apVal, pRowid); + } + + /* UPDATE */ + else{ + i64 iOld = sqlite3_value_int64(apVal[0]); /* Old rowid */ + i64 iNew = sqlite3_value_int64(apVal[1]); /* New rowid */ + if( eType1==SQLITE_INTEGER && iOld!=iNew ){ + if( eConflict==SQLITE_REPLACE ){ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); + } + fts5StorageInsert(&rc, pTab, apVal, pRowid); + }else{ + rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal,*pRowid); + } + } + }else{ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); + fts5StorageInsert(&rc, pTab, apVal, pRowid); + } + bUpdateOrDelete = 1; + } + } + } + + if( rc==SQLITE_OK + && bUpdateOrDelete + && pConfig->bSecureDelete + && pConfig->iVersion==FTS5_CURRENT_VERSION + ){ + rc = sqlite3Fts5StorageConfigValue( + pTab->pStorage, "version", 0, FTS5_CURRENT_VERSION_SECUREDELETE + ); + if( rc==SQLITE_OK ){ + pConfig->iVersion = FTS5_CURRENT_VERSION_SECUREDELETE; + } + } + + pTab->p.pConfig->pzErrmsg = 0; + return rc; +} + +/* +** Implementation of xSync() method. +*/ +static int fts5SyncMethod(sqlite3_vtab *pVtab){ + int rc; + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + fts5CheckTransactionState(pTab, FTS5_SYNC, 0); + pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg; + rc = sqlite3Fts5FlushToDisk(&pTab->p); + pTab->p.pConfig->pzErrmsg = 0; + return rc; +} + +/* +** Implementation of xBegin() method. +*/ +static int fts5BeginMethod(sqlite3_vtab *pVtab){ + fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_BEGIN, 0); + fts5NewTransaction((Fts5FullTable*)pVtab); + return SQLITE_OK; +} + +/* +** Implementation of xCommit() method. This is a no-op. The contents of +** the pending-terms hash-table have already been flushed into the database +** by fts5SyncMethod(). +*/ +static int fts5CommitMethod(sqlite3_vtab *pVtab){ + UNUSED_PARAM(pVtab); /* Call below is a no-op for NDEBUG builds */ + fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_COMMIT, 0); + return SQLITE_OK; +} + +/* +** Implementation of xRollback(). Discard the contents of the pending-terms +** hash-table. Any changes made to the database are reverted by SQLite. +*/ +static int fts5RollbackMethod(sqlite3_vtab *pVtab){ + int rc; + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + fts5CheckTransactionState(pTab, FTS5_ROLLBACK, 0); + rc = sqlite3Fts5StorageRollback(pTab->pStorage); + return rc; +} + +static int fts5CsrPoslist(Fts5Cursor*, int, const u8**, int*); + +static void *fts5ApiUserData(Fts5Context *pCtx){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return pCsr->pAux->pUserData; +} + +static int fts5ApiColumnCount(Fts5Context *pCtx){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return ((Fts5Table*)(pCsr->base.pVtab))->pConfig->nCol; +} + +static int fts5ApiColumnTotalSize( + Fts5Context *pCtx, + int iCol, + sqlite3_int64 *pnToken +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); + return sqlite3Fts5StorageSize(pTab->pStorage, iCol, pnToken); +} + +static int fts5ApiRowCount(Fts5Context *pCtx, i64 *pnRow){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); + return sqlite3Fts5StorageRowCount(pTab->pStorage, pnRow); +} + +static int fts5ApiTokenize( + Fts5Context *pCtx, + const char *pText, int nText, + void *pUserData, + int (*xToken)(void*, int, const char*, int, int, int) +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab); + return sqlite3Fts5Tokenize( + pTab->pConfig, FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken + ); +} + +static int fts5ApiPhraseCount(Fts5Context *pCtx){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return sqlite3Fts5ExprPhraseCount(pCsr->pExpr); +} + +static int fts5ApiPhraseSize(Fts5Context *pCtx, int iPhrase){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return sqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase); +} + +static int fts5ApiColumnText( + Fts5Context *pCtx, + int iCol, + const char **pz, + int *pn +){ + int rc = SQLITE_OK; + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab)) + || pCsr->ePlan==FTS5_PLAN_SPECIAL + ){ + *pz = 0; + *pn = 0; + }else{ + rc = fts5SeekCursor(pCsr, 0); + if( rc==SQLITE_OK ){ + *pz = (const char*)sqlite3_column_text(pCsr->pStmt, iCol+1); + *pn = sqlite3_column_bytes(pCsr->pStmt, iCol+1); + } + } + return rc; +} + +static int fts5CsrPoslist( + Fts5Cursor *pCsr, + int iPhrase, + const u8 **pa, + int *pn +){ + Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; + int rc = SQLITE_OK; + int bLive = (pCsr->pSorter==0); + + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){ + + if( pConfig->eDetail!=FTS5_DETAIL_FULL ){ + Fts5PoslistPopulator *aPopulator; + int i; + aPopulator = sqlite3Fts5ExprClearPoslists(pCsr->pExpr, bLive); + if( aPopulator==0 ) rc = SQLITE_NOMEM; + for(i=0; i<pConfig->nCol && rc==SQLITE_OK; i++){ + int n; const char *z; + rc = fts5ApiColumnText((Fts5Context*)pCsr, i, &z, &n); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ExprPopulatePoslists( + pConfig, pCsr->pExpr, aPopulator, i, z, n + ); + } + } + sqlite3_free(aPopulator); + + if( pCsr->pSorter ){ + sqlite3Fts5ExprCheckPoslists(pCsr->pExpr, pCsr->pSorter->iRowid); + } + } + CsrFlagClear(pCsr, FTS5CSR_REQUIRE_POSLIST); + } + + if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){ + Fts5Sorter *pSorter = pCsr->pSorter; + int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); + *pn = pSorter->aIdx[iPhrase] - i1; + *pa = &pSorter->aPoslist[i1]; + }else{ + *pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa); + } + + return rc; +} + +/* +** Ensure that the Fts5Cursor.nInstCount and aInst[] variables are populated +** correctly for the current view. Return SQLITE_OK if successful, or an +** SQLite error code otherwise. +*/ +static int fts5CacheInstArray(Fts5Cursor *pCsr){ + int rc = SQLITE_OK; + Fts5PoslistReader *aIter; /* One iterator for each phrase */ + int nIter; /* Number of iterators/phrases */ + int nCol = ((Fts5Table*)pCsr->base.pVtab)->pConfig->nCol; + + nIter = sqlite3Fts5ExprPhraseCount(pCsr->pExpr); + if( pCsr->aInstIter==0 ){ + sqlite3_int64 nByte = sizeof(Fts5PoslistReader) * nIter; + pCsr->aInstIter = (Fts5PoslistReader*)sqlite3Fts5MallocZero(&rc, nByte); + } + aIter = pCsr->aInstIter; + + if( aIter ){ + int nInst = 0; /* Number instances seen so far */ + int i; + + /* Initialize all iterators */ + for(i=0; i<nIter && rc==SQLITE_OK; i++){ + const u8 *a; + int n; + rc = fts5CsrPoslist(pCsr, i, &a, &n); + if( rc==SQLITE_OK ){ + sqlite3Fts5PoslistReaderInit(a, n, &aIter[i]); + } + } + + if( rc==SQLITE_OK ){ + while( 1 ){ + int *aInst; + int iBest = -1; + for(i=0; i<nIter; i++){ + if( (aIter[i].bEof==0) + && (iBest<0 || aIter[i].iPos<aIter[iBest].iPos) + ){ + iBest = i; + } + } + if( iBest<0 ) break; + + nInst++; + if( nInst>=pCsr->nInstAlloc ){ + int nNewSize = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32; + aInst = (int*)sqlite3_realloc64( + pCsr->aInst, nNewSize*sizeof(int)*3 + ); + if( aInst ){ + pCsr->aInst = aInst; + pCsr->nInstAlloc = nNewSize; + }else{ + nInst--; + rc = SQLITE_NOMEM; + break; + } + } + + aInst = &pCsr->aInst[3 * (nInst-1)]; + aInst[0] = iBest; + aInst[1] = FTS5_POS2COLUMN(aIter[iBest].iPos); + aInst[2] = FTS5_POS2OFFSET(aIter[iBest].iPos); + if( aInst[1]<0 || aInst[1]>=nCol ){ + rc = FTS5_CORRUPT; + break; + } + sqlite3Fts5PoslistReaderNext(&aIter[iBest]); + } + } + + pCsr->nInstCount = nInst; + CsrFlagClear(pCsr, FTS5CSR_REQUIRE_INST); + } + return rc; +} + +static int fts5ApiInstCount(Fts5Context *pCtx, int *pnInst){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + int rc = SQLITE_OK; + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0 + || SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) ){ + *pnInst = pCsr->nInstCount; + } + return rc; +} + +static int fts5ApiInst( + Fts5Context *pCtx, + int iIdx, + int *piPhrase, + int *piCol, + int *piOff +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + int rc = SQLITE_OK; + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0 + || SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) + ){ + if( iIdx<0 || iIdx>=pCsr->nInstCount ){ + rc = SQLITE_RANGE; +#if 0 + }else if( fts5IsOffsetless((Fts5Table*)pCsr->base.pVtab) ){ + *piPhrase = pCsr->aInst[iIdx*3]; + *piCol = pCsr->aInst[iIdx*3 + 2]; + *piOff = -1; +#endif + }else{ + *piPhrase = pCsr->aInst[iIdx*3]; + *piCol = pCsr->aInst[iIdx*3 + 1]; + *piOff = pCsr->aInst[iIdx*3 + 2]; + } + } + return rc; +} + +static sqlite3_int64 fts5ApiRowid(Fts5Context *pCtx){ + return fts5CursorRowid((Fts5Cursor*)pCtx); +} + +static int fts5ColumnSizeCb( + void *pContext, /* Pointer to int */ + int tflags, + const char *pUnused, /* Buffer containing token */ + int nUnused, /* Size of token in bytes */ + int iUnused1, /* Start offset of token */ + int iUnused2 /* End offset of token */ +){ + int *pCnt = (int*)pContext; + UNUSED_PARAM2(pUnused, nUnused); + UNUSED_PARAM2(iUnused1, iUnused2); + if( (tflags & FTS5_TOKEN_COLOCATED)==0 ){ + (*pCnt)++; + } + return SQLITE_OK; +} + +static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); + Fts5Config *pConfig = pTab->p.pConfig; + int rc = SQLITE_OK; + + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_DOCSIZE) ){ + if( pConfig->bColumnsize ){ + i64 iRowid = fts5CursorRowid(pCsr); + rc = sqlite3Fts5StorageDocsize(pTab->pStorage, iRowid, pCsr->aColumnSize); + }else if( pConfig->zContent==0 ){ + int i; + for(i=0; i<pConfig->nCol; i++){ + if( pConfig->abUnindexed[i]==0 ){ + pCsr->aColumnSize[i] = -1; + } + } + }else{ + int i; + for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){ + if( pConfig->abUnindexed[i]==0 ){ + const char *z; int n; + void *p = (void*)(&pCsr->aColumnSize[i]); + pCsr->aColumnSize[i] = 0; + rc = fts5ApiColumnText(pCtx, i, &z, &n); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5Tokenize( + pConfig, FTS5_TOKENIZE_AUX, z, n, p, fts5ColumnSizeCb + ); + } + } + } + } + CsrFlagClear(pCsr, FTS5CSR_REQUIRE_DOCSIZE); + } + if( iCol<0 ){ + int i; + *pnToken = 0; + for(i=0; i<pConfig->nCol; i++){ + *pnToken += pCsr->aColumnSize[i]; + } + }else if( iCol<pConfig->nCol ){ + *pnToken = pCsr->aColumnSize[iCol]; + }else{ + *pnToken = 0; + rc = SQLITE_RANGE; + } + return rc; +} + +/* +** Implementation of the xSetAuxdata() method. +*/ +static int fts5ApiSetAuxdata( + Fts5Context *pCtx, /* Fts5 context */ + void *pPtr, /* Pointer to save as auxdata */ + void(*xDelete)(void*) /* Destructor for pPtr (or NULL) */ +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5Auxdata *pData; + + /* Search through the cursors list of Fts5Auxdata objects for one that + ** corresponds to the currently executing auxiliary function. */ + for(pData=pCsr->pAuxdata; pData; pData=pData->pNext){ + if( pData->pAux==pCsr->pAux ) break; + } + + if( pData ){ + if( pData->xDelete ){ + pData->xDelete(pData->pPtr); + } + }else{ + int rc = SQLITE_OK; + pData = (Fts5Auxdata*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Auxdata)); + if( pData==0 ){ + if( xDelete ) xDelete(pPtr); + return rc; + } + pData->pAux = pCsr->pAux; + pData->pNext = pCsr->pAuxdata; + pCsr->pAuxdata = pData; + } + + pData->xDelete = xDelete; + pData->pPtr = pPtr; + return SQLITE_OK; +} + +static void *fts5ApiGetAuxdata(Fts5Context *pCtx, int bClear){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5Auxdata *pData; + void *pRet = 0; + + for(pData=pCsr->pAuxdata; pData; pData=pData->pNext){ + if( pData->pAux==pCsr->pAux ) break; + } + + if( pData ){ + pRet = pData->pPtr; + if( bClear ){ + pData->pPtr = 0; + pData->xDelete = 0; + } + } + + return pRet; +} + +static void fts5ApiPhraseNext( + Fts5Context *pUnused, + Fts5PhraseIter *pIter, + int *piCol, int *piOff +){ + UNUSED_PARAM(pUnused); + if( pIter->a>=pIter->b ){ + *piCol = -1; + *piOff = -1; + }else{ + int iVal; + pIter->a += fts5GetVarint32(pIter->a, iVal); + if( iVal==1 ){ + pIter->a += fts5GetVarint32(pIter->a, iVal); + *piCol = iVal; + *piOff = 0; + pIter->a += fts5GetVarint32(pIter->a, iVal); + } + *piOff += (iVal-2); + } +} + +static int fts5ApiPhraseFirst( + Fts5Context *pCtx, + int iPhrase, + Fts5PhraseIter *pIter, + int *piCol, int *piOff +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + int n; + int rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n); + if( rc==SQLITE_OK ){ + assert( pIter->a || n==0 ); + pIter->b = (pIter->a ? &pIter->a[n] : 0); + *piCol = 0; + *piOff = 0; + fts5ApiPhraseNext(pCtx, pIter, piCol, piOff); + } + return rc; +} + +static void fts5ApiPhraseNextColumn( + Fts5Context *pCtx, + Fts5PhraseIter *pIter, + int *piCol +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; + + if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + if( pIter->a>=pIter->b ){ + *piCol = -1; + }else{ + int iIncr; + pIter->a += fts5GetVarint32(&pIter->a[0], iIncr); + *piCol += (iIncr-2); + } + }else{ + while( 1 ){ + int dummy; + if( pIter->a>=pIter->b ){ + *piCol = -1; + return; + } + if( pIter->a[0]==0x01 ) break; + pIter->a += fts5GetVarint32(pIter->a, dummy); + } + pIter->a += 1 + fts5GetVarint32(&pIter->a[1], *piCol); + } +} + +static int fts5ApiPhraseFirstColumn( + Fts5Context *pCtx, + int iPhrase, + Fts5PhraseIter *pIter, + int *piCol +){ + int rc = SQLITE_OK; + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; + + if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + Fts5Sorter *pSorter = pCsr->pSorter; + int n; + if( pSorter ){ + int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); + n = pSorter->aIdx[iPhrase] - i1; + pIter->a = &pSorter->aPoslist[i1]; + }else{ + rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n); + } + if( rc==SQLITE_OK ){ + assert( pIter->a || n==0 ); + pIter->b = (pIter->a ? &pIter->a[n] : 0); + *piCol = 0; + fts5ApiPhraseNextColumn(pCtx, pIter, piCol); + } + }else{ + int n; + rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n); + if( rc==SQLITE_OK ){ + assert( pIter->a || n==0 ); + pIter->b = (pIter->a ? &pIter->a[n] : 0); + if( n<=0 ){ + *piCol = -1; + }else if( pIter->a[0]==0x01 ){ + pIter->a += 1 + fts5GetVarint32(&pIter->a[1], *piCol); + }else{ + *piCol = 0; + } + } + } + + return rc; +} + + +static int fts5ApiQueryPhrase(Fts5Context*, int, void*, + int(*)(const Fts5ExtensionApi*, Fts5Context*, void*) +); + +static const Fts5ExtensionApi sFts5Api = { + 2, /* iVersion */ + fts5ApiUserData, + fts5ApiColumnCount, + fts5ApiRowCount, + fts5ApiColumnTotalSize, + fts5ApiTokenize, + fts5ApiPhraseCount, + fts5ApiPhraseSize, + fts5ApiInstCount, + fts5ApiInst, + fts5ApiRowid, + fts5ApiColumnText, + fts5ApiColumnSize, + fts5ApiQueryPhrase, + fts5ApiSetAuxdata, + fts5ApiGetAuxdata, + fts5ApiPhraseFirst, + fts5ApiPhraseNext, + fts5ApiPhraseFirstColumn, + fts5ApiPhraseNextColumn, +}; + +/* +** Implementation of API function xQueryPhrase(). +*/ +static int fts5ApiQueryPhrase( + Fts5Context *pCtx, + int iPhrase, + void *pUserData, + int(*xCallback)(const Fts5ExtensionApi*, Fts5Context*, void*) +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); + int rc; + Fts5Cursor *pNew = 0; + + rc = fts5OpenMethod(pCsr->base.pVtab, (sqlite3_vtab_cursor**)&pNew); + if( rc==SQLITE_OK ){ + pNew->ePlan = FTS5_PLAN_MATCH; + pNew->iFirstRowid = SMALLEST_INT64; + pNew->iLastRowid = LARGEST_INT64; + pNew->base.pVtab = (sqlite3_vtab*)pTab; + rc = sqlite3Fts5ExprClonePhrase(pCsr->pExpr, iPhrase, &pNew->pExpr); + } + + if( rc==SQLITE_OK ){ + for(rc = fts5CursorFirst(pTab, pNew, 0); + rc==SQLITE_OK && CsrFlagTest(pNew, FTS5CSR_EOF)==0; + rc = fts5NextMethod((sqlite3_vtab_cursor*)pNew) + ){ + rc = xCallback(&sFts5Api, (Fts5Context*)pNew, pUserData); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + break; + } + } + } + + fts5CloseMethod((sqlite3_vtab_cursor*)pNew); + return rc; +} + +static void fts5ApiInvoke( + Fts5Auxiliary *pAux, + Fts5Cursor *pCsr, + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( pCsr->pAux==0 ); + pCsr->pAux = pAux; + pAux->xFunc(&sFts5Api, (Fts5Context*)pCsr, context, argc, argv); + pCsr->pAux = 0; +} + +static Fts5Cursor *fts5CursorFromCsrid(Fts5Global *pGlobal, i64 iCsrId){ + Fts5Cursor *pCsr; + for(pCsr=pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){ + if( pCsr->iCsrId==iCsrId ) break; + } + return pCsr; +} + +static void fts5ApiCallback( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + + Fts5Auxiliary *pAux; + Fts5Cursor *pCsr; + i64 iCsrId; + + assert( argc>=1 ); + pAux = (Fts5Auxiliary*)sqlite3_user_data(context); + iCsrId = sqlite3_value_int64(argv[0]); + + pCsr = fts5CursorFromCsrid(pAux->pGlobal, iCsrId); + if( pCsr==0 || pCsr->ePlan==0 ){ + char *zErr = sqlite3_mprintf("no such cursor: %lld", iCsrId); + sqlite3_result_error(context, zErr, -1); + sqlite3_free(zErr); + }else{ + fts5ApiInvoke(pAux, pCsr, context, argc-1, &argv[1]); + } +} + + +/* +** Given cursor id iId, return a pointer to the corresponding Fts5Table +** object. Or NULL If the cursor id does not exist. +*/ +static Fts5Table *sqlite3Fts5TableFromCsrid( + Fts5Global *pGlobal, /* FTS5 global context for db handle */ + i64 iCsrId /* Id of cursor to find */ +){ + Fts5Cursor *pCsr; + pCsr = fts5CursorFromCsrid(pGlobal, iCsrId); + if( pCsr ){ + return (Fts5Table*)pCsr->base.pVtab; + } + return 0; +} + +/* +** Return a "position-list blob" corresponding to the current position of +** cursor pCsr via sqlite3_result_blob(). A position-list blob contains +** the current position-list for each phrase in the query associated with +** cursor pCsr. +** +** A position-list blob begins with (nPhrase-1) varints, where nPhrase is +** the number of phrases in the query. Following the varints are the +** concatenated position lists for each phrase, in order. +** +** The first varint (if it exists) contains the size of the position list +** for phrase 0. The second (same disclaimer) contains the size of position +** list 1. And so on. There is no size field for the final position list, +** as it can be derived from the total size of the blob. +*/ +static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){ + int i; + int rc = SQLITE_OK; + int nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr); + Fts5Buffer val; + + memset(&val, 0, sizeof(Fts5Buffer)); + switch( ((Fts5Table*)(pCsr->base.pVtab))->pConfig->eDetail ){ + case FTS5_DETAIL_FULL: + + /* Append the varints */ + for(i=0; i<(nPhrase-1); i++){ + const u8 *dummy; + int nByte = sqlite3Fts5ExprPoslist(pCsr->pExpr, i, &dummy); + sqlite3Fts5BufferAppendVarint(&rc, &val, nByte); + } + + /* Append the position lists */ + for(i=0; i<nPhrase; i++){ + const u8 *pPoslist; + int nPoslist; + nPoslist = sqlite3Fts5ExprPoslist(pCsr->pExpr, i, &pPoslist); + sqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist); + } + break; + + case FTS5_DETAIL_COLUMNS: + + /* Append the varints */ + for(i=0; rc==SQLITE_OK && i<(nPhrase-1); i++){ + const u8 *dummy; + int nByte; + rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, i, &dummy, &nByte); + sqlite3Fts5BufferAppendVarint(&rc, &val, nByte); + } + + /* Append the position lists */ + for(i=0; rc==SQLITE_OK && i<nPhrase; i++){ + const u8 *pPoslist; + int nPoslist; + rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, i, &pPoslist, &nPoslist); + sqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist); + } + break; + + default: + break; + } + + sqlite3_result_blob(pCtx, val.p, val.n, sqlite3_free); + return rc; +} + +/* +** This is the xColumn method, called by SQLite to request a value from +** the row that the supplied cursor currently points to. +*/ +static int fts5ColumnMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ + int iCol /* Index of column to read value from */ +){ + Fts5FullTable *pTab = (Fts5FullTable*)(pCursor->pVtab); + Fts5Config *pConfig = pTab->p.pConfig; + Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; + int rc = SQLITE_OK; + + assert( CsrFlagTest(pCsr, FTS5CSR_EOF)==0 ); + + if( pCsr->ePlan==FTS5_PLAN_SPECIAL ){ + if( iCol==pConfig->nCol ){ + sqlite3_result_int64(pCtx, pCsr->iSpecial); + } + }else + + if( iCol==pConfig->nCol ){ + /* User is requesting the value of the special column with the same name + ** as the table. Return the cursor integer id number. This value is only + ** useful in that it may be passed as the first argument to an FTS5 + ** auxiliary function. */ + sqlite3_result_int64(pCtx, pCsr->iCsrId); + }else if( iCol==pConfig->nCol+1 ){ + + /* The value of the "rank" column. */ + if( pCsr->ePlan==FTS5_PLAN_SOURCE ){ + fts5PoslistBlob(pCtx, pCsr); + }else if( + pCsr->ePlan==FTS5_PLAN_MATCH + || pCsr->ePlan==FTS5_PLAN_SORTED_MATCH + ){ + if( pCsr->pRank || SQLITE_OK==(rc = fts5FindRankFunction(pCsr)) ){ + fts5ApiInvoke(pCsr->pRank, pCsr, pCtx, pCsr->nRankArg, pCsr->apRankArg); + } + } + }else if( !fts5IsContentless(pTab) ){ + pConfig->pzErrmsg = &pTab->p.base.zErrMsg; + rc = fts5SeekCursor(pCsr, 1); + if( rc==SQLITE_OK ){ + sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); + } + pConfig->pzErrmsg = 0; + }else if( pConfig->bContentlessDelete && sqlite3_vtab_nochange(pCtx) ){ + char *zErr = sqlite3_mprintf("cannot UPDATE a subset of " + "columns on fts5 contentless-delete table: %s", pConfig->zName + ); + sqlite3_result_error(pCtx, zErr, -1); + sqlite3_free(zErr); + } + return rc; +} + + +/* +** This routine implements the xFindFunction method for the FTS3 +** virtual table. +*/ +static int fts5FindFunctionMethod( + sqlite3_vtab *pVtab, /* Virtual table handle */ + int nUnused, /* Number of SQL function arguments */ + const char *zName, /* Name of SQL function */ + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */ + void **ppArg /* OUT: User data for *pxFunc */ +){ + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + Fts5Auxiliary *pAux; + + UNUSED_PARAM(nUnused); + pAux = fts5FindAuxiliary(pTab, zName); + if( pAux ){ + *pxFunc = fts5ApiCallback; + *ppArg = (void*)pAux; + return 1; + } + + /* No function of the specified name was found. Return 0. */ + return 0; +} + +/* +** Implementation of FTS5 xRename method. Rename an fts5 table. +*/ +static int fts5RenameMethod( + sqlite3_vtab *pVtab, /* Virtual table handle */ + const char *zName /* New name of table */ +){ + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + return sqlite3Fts5StorageRename(pTab->pStorage, zName); +} + +static int sqlite3Fts5FlushToDisk(Fts5Table *pTab){ + fts5TripCursors((Fts5FullTable*)pTab); + return sqlite3Fts5StorageSync(((Fts5FullTable*)pTab)->pStorage); +} + +/* +** The xSavepoint() method. +** +** Flush the contents of the pending-terms table to disk. +*/ +static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ + UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ + fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_SAVEPOINT, iSavepoint); + return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab); +} + +/* +** The xRelease() method. +** +** This is a no-op. +*/ +static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ + UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ + fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_RELEASE, iSavepoint); + return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab); +} + +/* +** The xRollbackTo() method. +** +** Discard the contents of the pending terms table. +*/ +static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ + fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint); + fts5TripCursors(pTab); + pTab->p.pConfig->pgsz = 0; + return sqlite3Fts5StorageRollback(pTab->pStorage); +} + +/* +** Register a new auxiliary function with global context pGlobal. +*/ +static int fts5CreateAux( + fts5_api *pApi, /* Global context (one per db handle) */ + const char *zName, /* Name of new function */ + void *pUserData, /* User data for aux. function */ + fts5_extension_function xFunc, /* Aux. function implementation */ + void(*xDestroy)(void*) /* Destructor for pUserData */ +){ + Fts5Global *pGlobal = (Fts5Global*)pApi; + int rc = sqlite3_overload_function(pGlobal->db, zName, -1); + if( rc==SQLITE_OK ){ + Fts5Auxiliary *pAux; + sqlite3_int64 nName; /* Size of zName in bytes, including \0 */ + sqlite3_int64 nByte; /* Bytes of space to allocate */ + + nName = strlen(zName) + 1; + nByte = sizeof(Fts5Auxiliary) + nName; + pAux = (Fts5Auxiliary*)sqlite3_malloc64(nByte); + if( pAux ){ + memset(pAux, 0, (size_t)nByte); + pAux->zFunc = (char*)&pAux[1]; + memcpy(pAux->zFunc, zName, nName); + pAux->pGlobal = pGlobal; + pAux->pUserData = pUserData; + pAux->xFunc = xFunc; + pAux->xDestroy = xDestroy; + pAux->pNext = pGlobal->pAux; + pGlobal->pAux = pAux; + }else{ + rc = SQLITE_NOMEM; + } + } + + return rc; +} + +/* +** Register a new tokenizer. This is the implementation of the +** fts5_api.xCreateTokenizer() method. +*/ +static int fts5CreateTokenizer( + fts5_api *pApi, /* Global context (one per db handle) */ + const char *zName, /* Name of new function */ + void *pUserData, /* User data for aux. function */ + fts5_tokenizer *pTokenizer, /* Tokenizer implementation */ + void(*xDestroy)(void*) /* Destructor for pUserData */ +){ + Fts5Global *pGlobal = (Fts5Global*)pApi; + Fts5TokenizerModule *pNew; + sqlite3_int64 nName; /* Size of zName and its \0 terminator */ + sqlite3_int64 nByte; /* Bytes of space to allocate */ + int rc = SQLITE_OK; + + nName = strlen(zName) + 1; + nByte = sizeof(Fts5TokenizerModule) + nName; + pNew = (Fts5TokenizerModule*)sqlite3_malloc64(nByte); + if( pNew ){ + memset(pNew, 0, (size_t)nByte); + pNew->zName = (char*)&pNew[1]; + memcpy(pNew->zName, zName, nName); + pNew->pUserData = pUserData; + pNew->x = *pTokenizer; + pNew->xDestroy = xDestroy; + pNew->pNext = pGlobal->pTok; + pGlobal->pTok = pNew; + if( pNew->pNext==0 ){ + pGlobal->pDfltTok = pNew; + } + }else{ + rc = SQLITE_NOMEM; + } + + return rc; +} + +static Fts5TokenizerModule *fts5LocateTokenizer( + Fts5Global *pGlobal, + const char *zName +){ + Fts5TokenizerModule *pMod = 0; + + if( zName==0 ){ + pMod = pGlobal->pDfltTok; + }else{ + for(pMod=pGlobal->pTok; pMod; pMod=pMod->pNext){ + if( sqlite3_stricmp(zName, pMod->zName)==0 ) break; + } + } + + return pMod; +} + +/* +** Find a tokenizer. This is the implementation of the +** fts5_api.xFindTokenizer() method. +*/ +static int fts5FindTokenizer( + fts5_api *pApi, /* Global context (one per db handle) */ + const char *zName, /* Name of new function */ + void **ppUserData, + fts5_tokenizer *pTokenizer /* Populate this object */ +){ + int rc = SQLITE_OK; + Fts5TokenizerModule *pMod; + + pMod = fts5LocateTokenizer((Fts5Global*)pApi, zName); + if( pMod ){ + *pTokenizer = pMod->x; + *ppUserData = pMod->pUserData; + }else{ + memset(pTokenizer, 0, sizeof(fts5_tokenizer)); + rc = SQLITE_ERROR; + } + + return rc; +} + +static int sqlite3Fts5GetTokenizer( + Fts5Global *pGlobal, + const char **azArg, + int nArg, + Fts5Config *pConfig, + char **pzErr +){ + Fts5TokenizerModule *pMod; + int rc = SQLITE_OK; + + pMod = fts5LocateTokenizer(pGlobal, nArg==0 ? 0 : azArg[0]); + if( pMod==0 ){ + assert( nArg>0 ); + rc = SQLITE_ERROR; + *pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]); + }else{ + rc = pMod->x.xCreate( + pMod->pUserData, (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->pTok + ); + pConfig->pTokApi = &pMod->x; + if( rc!=SQLITE_OK ){ + if( pzErr ) *pzErr = sqlite3_mprintf("error in tokenizer constructor"); + }else{ + pConfig->ePattern = sqlite3Fts5TokenizerPattern( + pMod->x.xCreate, pConfig->pTok + ); + } + } + + if( rc!=SQLITE_OK ){ + pConfig->pTokApi = 0; + pConfig->pTok = 0; + } + + return rc; +} + +static void fts5ModuleDestroy(void *pCtx){ + Fts5TokenizerModule *pTok, *pNextTok; + Fts5Auxiliary *pAux, *pNextAux; + Fts5Global *pGlobal = (Fts5Global*)pCtx; + + for(pAux=pGlobal->pAux; pAux; pAux=pNextAux){ + pNextAux = pAux->pNext; + if( pAux->xDestroy ) pAux->xDestroy(pAux->pUserData); + sqlite3_free(pAux); + } + + for(pTok=pGlobal->pTok; pTok; pTok=pNextTok){ + pNextTok = pTok->pNext; + if( pTok->xDestroy ) pTok->xDestroy(pTok->pUserData); + sqlite3_free(pTok); + } + + sqlite3_free(pGlobal); +} + +static void fts5Fts5Func( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args */ + sqlite3_value **apArg /* Function arguments */ +){ + Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx); + fts5_api **ppApi; + UNUSED_PARAM(nArg); + assert( nArg==1 ); + ppApi = (fts5_api**)sqlite3_value_pointer(apArg[0], "fts5_api_ptr"); + if( ppApi ) *ppApi = &pGlobal->api; +} + +/* +** Implementation of fts5_source_id() function. +*/ +static void fts5SourceIdFunc( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args */ + sqlite3_value **apUnused /* Function arguments */ +){ + assert( nArg==0 ); + UNUSED_PARAM2(nArg, apUnused); + sqlite3_result_text(pCtx, "fts5: 2023-09-11 15:27:27 3308fdda4b81c110ba4a66d0b325e7653c2f8155e7864aeb78991ed1da061836", -1, SQLITE_TRANSIENT); +} + +/* +** Return true if zName is the extension on one of the shadow tables used +** by this module. +*/ +static int fts5ShadowName(const char *zName){ + static const char *azName[] = { + "config", "content", "data", "docsize", "idx" + }; + unsigned int i; + for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){ + if( sqlite3_stricmp(zName, azName[i])==0 ) return 1; + } + return 0; +} + +/* +** Run an integrity check on the FTS5 data structures. Return a string +** if anything is found amiss. Return a NULL pointer if everything is +** OK. +*/ +static int fts5Integrity(sqlite3_vtab *pVtab, char **pzErr){ + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + Fts5Config *pConfig = pTab->p.pConfig; + char *zSql; + int rc; + zSql = sqlite3_mprintf( + "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');", + pConfig->zDb, pConfig->zName, pConfig->zName); + rc = sqlite3_exec(pConfig->db, zSql, 0, 0, 0); + sqlite3_free(zSql); + if( (rc&0xff)==SQLITE_CORRUPT ){ + *pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s", + pConfig->zDb, pConfig->zName); + rc = SQLITE_OK; + } + return rc; + +} + +static int fts5Init(sqlite3 *db){ + static const sqlite3_module fts5Mod = { + /* iVersion */ 4, + /* xCreate */ fts5CreateMethod, + /* xConnect */ fts5ConnectMethod, + /* xBestIndex */ fts5BestIndexMethod, + /* xDisconnect */ fts5DisconnectMethod, + /* xDestroy */ fts5DestroyMethod, + /* xOpen */ fts5OpenMethod, + /* xClose */ fts5CloseMethod, + /* xFilter */ fts5FilterMethod, + /* xNext */ fts5NextMethod, + /* xEof */ fts5EofMethod, + /* xColumn */ fts5ColumnMethod, + /* xRowid */ fts5RowidMethod, + /* xUpdate */ fts5UpdateMethod, + /* xBegin */ fts5BeginMethod, + /* xSync */ fts5SyncMethod, + /* xCommit */ fts5CommitMethod, + /* xRollback */ fts5RollbackMethod, + /* xFindFunction */ fts5FindFunctionMethod, + /* xRename */ fts5RenameMethod, + /* xSavepoint */ fts5SavepointMethod, + /* xRelease */ fts5ReleaseMethod, + /* xRollbackTo */ fts5RollbackToMethod, + /* xShadowName */ fts5ShadowName, + /* xIntegrity */ fts5Integrity + }; + + int rc; + Fts5Global *pGlobal = 0; + + pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global)); + if( pGlobal==0 ){ + rc = SQLITE_NOMEM; + }else{ + void *p = (void*)pGlobal; + memset(pGlobal, 0, sizeof(Fts5Global)); + pGlobal->db = db; + pGlobal->api.iVersion = 2; + pGlobal->api.xCreateFunction = fts5CreateAux; + pGlobal->api.xCreateTokenizer = fts5CreateTokenizer; + pGlobal->api.xFindTokenizer = fts5FindTokenizer; + rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy); + if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db); + if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(pGlobal, db); + if( rc==SQLITE_OK ) rc = sqlite3Fts5AuxInit(&pGlobal->api); + if( rc==SQLITE_OK ) rc = sqlite3Fts5TokenizerInit(&pGlobal->api); + if( rc==SQLITE_OK ) rc = sqlite3Fts5VocabInit(pGlobal, db); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function( + db, "fts5", 1, SQLITE_UTF8, p, fts5Fts5Func, 0, 0 + ); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function( + db, "fts5_source_id", 0, + SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, + p, fts5SourceIdFunc, 0, 0 + ); + } + } + + /* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file + ** fts5_test_mi.c is compiled and linked into the executable. And call + ** its entry point to enable the matchinfo() demo. */ +#ifdef SQLITE_FTS5_ENABLE_TEST_MI + if( rc==SQLITE_OK ){ + extern int sqlite3Fts5TestRegisterMatchinfo(sqlite3*); + rc = sqlite3Fts5TestRegisterMatchinfo(db); + } +#endif + + return rc; +} + +/* +** The following functions are used to register the module with SQLite. If +** this module is being built as part of the SQLite core (SQLITE_CORE is +** defined), then sqlite3_open() will call sqlite3Fts5Init() directly. +** +** Or, if this module is being built as a loadable extension, +** sqlite3Fts5Init() is omitted and the two standard entry points +** sqlite3_fts_init() and sqlite3_fts5_init() defined instead. +*/ +#ifndef SQLITE_CORE +#ifdef _WIN32 +__declspec(dllexport) +#endif +SQLITE_API int sqlite3_fts_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + return fts5Init(db); +} + +#ifdef _WIN32 +__declspec(dllexport) +#endif +SQLITE_API int sqlite3_fts5_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + return fts5Init(db); +} +#else +SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3 *db){ + return fts5Init(db); +} +#endif + +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +*/ + + + +/* #include "fts5Int.h" */ + +struct Fts5Storage { + Fts5Config *pConfig; + Fts5Index *pIndex; + int bTotalsValid; /* True if nTotalRow/aTotalSize[] are valid */ + i64 nTotalRow; /* Total number of rows in FTS table */ + i64 *aTotalSize; /* Total sizes of each column */ + sqlite3_stmt *aStmt[11]; +}; + + +#if FTS5_STMT_SCAN_ASC!=0 +# error "FTS5_STMT_SCAN_ASC mismatch" +#endif +#if FTS5_STMT_SCAN_DESC!=1 +# error "FTS5_STMT_SCAN_DESC mismatch" +#endif +#if FTS5_STMT_LOOKUP!=2 +# error "FTS5_STMT_LOOKUP mismatch" +#endif + +#define FTS5_STMT_INSERT_CONTENT 3 +#define FTS5_STMT_REPLACE_CONTENT 4 +#define FTS5_STMT_DELETE_CONTENT 5 +#define FTS5_STMT_REPLACE_DOCSIZE 6 +#define FTS5_STMT_DELETE_DOCSIZE 7 +#define FTS5_STMT_LOOKUP_DOCSIZE 8 +#define FTS5_STMT_REPLACE_CONFIG 9 +#define FTS5_STMT_SCAN 10 + +/* +** Prepare the two insert statements - Fts5Storage.pInsertContent and +** Fts5Storage.pInsertDocsize - if they have not already been prepared. +** Return SQLITE_OK if successful, or an SQLite error code if an error +** occurs. +*/ +static int fts5StorageGetStmt( + Fts5Storage *p, /* Storage handle */ + int eStmt, /* FTS5_STMT_XXX constant */ + sqlite3_stmt **ppStmt, /* OUT: Prepared statement handle */ + char **pzErrMsg /* OUT: Error message (if any) */ +){ + int rc = SQLITE_OK; + + /* If there is no %_docsize table, there should be no requests for + ** statements to operate on it. */ + assert( p->pConfig->bColumnsize || ( + eStmt!=FTS5_STMT_REPLACE_DOCSIZE + && eStmt!=FTS5_STMT_DELETE_DOCSIZE + && eStmt!=FTS5_STMT_LOOKUP_DOCSIZE + )); + + assert( eStmt>=0 && eStmt<ArraySize(p->aStmt) ); + if( p->aStmt[eStmt]==0 ){ + const char *azStmt[] = { + "SELECT %s FROM %s T WHERE T.%Q >= ? AND T.%Q <= ? ORDER BY T.%Q ASC", + "SELECT %s FROM %s T WHERE T.%Q <= ? AND T.%Q >= ? ORDER BY T.%Q DESC", + "SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP */ + + "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */ + "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */ + "DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */ + "REPLACE INTO %Q.'%q_docsize' VALUES(?,?%s)", /* REPLACE_DOCSIZE */ + "DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */ + + "SELECT sz%s FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */ + + "REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */ + "SELECT %s FROM %s AS T", /* SCAN */ + }; + Fts5Config *pC = p->pConfig; + char *zSql = 0; + + switch( eStmt ){ + case FTS5_STMT_SCAN: + zSql = sqlite3_mprintf(azStmt[eStmt], + pC->zContentExprlist, pC->zContent + ); + break; + + case FTS5_STMT_SCAN_ASC: + case FTS5_STMT_SCAN_DESC: + zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist, + pC->zContent, pC->zContentRowid, pC->zContentRowid, + pC->zContentRowid + ); + break; + + case FTS5_STMT_LOOKUP: + zSql = sqlite3_mprintf(azStmt[eStmt], + pC->zContentExprlist, pC->zContent, pC->zContentRowid + ); + break; + + case FTS5_STMT_INSERT_CONTENT: + case FTS5_STMT_REPLACE_CONTENT: { + int nCol = pC->nCol + 1; + char *zBind; + int i; + + zBind = sqlite3_malloc64(1 + nCol*2); + if( zBind ){ + for(i=0; i<nCol; i++){ + zBind[i*2] = '?'; + zBind[i*2 + 1] = ','; + } + zBind[i*2-1] = '\0'; + zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind); + sqlite3_free(zBind); + } + break; + } + + case FTS5_STMT_REPLACE_DOCSIZE: + zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, + (pC->bContentlessDelete ? ",?" : "") + ); + break; + + case FTS5_STMT_LOOKUP_DOCSIZE: + zSql = sqlite3_mprintf(azStmt[eStmt], + (pC->bContentlessDelete ? ",origin" : ""), + pC->zDb, pC->zName + ); + break; + + default: + zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName); + break; + } + + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + int f = SQLITE_PREPARE_PERSISTENT; + if( eStmt>FTS5_STMT_LOOKUP ) f |= SQLITE_PREPARE_NO_VTAB; + p->pConfig->bLock++; + rc = sqlite3_prepare_v3(pC->db, zSql, -1, f, &p->aStmt[eStmt], 0); + p->pConfig->bLock--; + sqlite3_free(zSql); + if( rc!=SQLITE_OK && pzErrMsg ){ + *pzErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pC->db)); + } + } + } + + *ppStmt = p->aStmt[eStmt]; + sqlite3_reset(*ppStmt); + return rc; +} + + +static int fts5ExecPrintf( + sqlite3 *db, + char **pzErr, + const char *zFormat, + ... +){ + int rc; + va_list ap; /* ... printf arguments */ + char *zSql; + + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_exec(db, zSql, 0, 0, pzErr); + sqlite3_free(zSql); + } + + va_end(ap); + return rc; +} + +/* +** Drop all shadow tables. Return SQLITE_OK if successful or an SQLite error +** code otherwise. +*/ +static int sqlite3Fts5DropAll(Fts5Config *pConfig){ + int rc = fts5ExecPrintf(pConfig->db, 0, + "DROP TABLE IF EXISTS %Q.'%q_data';" + "DROP TABLE IF EXISTS %Q.'%q_idx';" + "DROP TABLE IF EXISTS %Q.'%q_config';", + pConfig->zDb, pConfig->zName, + pConfig->zDb, pConfig->zName, + pConfig->zDb, pConfig->zName + ); + if( rc==SQLITE_OK && pConfig->bColumnsize ){ + rc = fts5ExecPrintf(pConfig->db, 0, + "DROP TABLE IF EXISTS %Q.'%q_docsize';", + pConfig->zDb, pConfig->zName + ); + } + if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ + rc = fts5ExecPrintf(pConfig->db, 0, + "DROP TABLE IF EXISTS %Q.'%q_content';", + pConfig->zDb, pConfig->zName + ); + } + return rc; +} + +static void fts5StorageRenameOne( + Fts5Config *pConfig, /* Current FTS5 configuration */ + int *pRc, /* IN/OUT: Error code */ + const char *zTail, /* Tail of table name e.g. "data", "config" */ + const char *zName /* New name of FTS5 table */ +){ + if( *pRc==SQLITE_OK ){ + *pRc = fts5ExecPrintf(pConfig->db, 0, + "ALTER TABLE %Q.'%q_%s' RENAME TO '%q_%s';", + pConfig->zDb, pConfig->zName, zTail, zName, zTail + ); + } +} + +static int sqlite3Fts5StorageRename(Fts5Storage *pStorage, const char *zName){ + Fts5Config *pConfig = pStorage->pConfig; + int rc = sqlite3Fts5StorageSync(pStorage); + + fts5StorageRenameOne(pConfig, &rc, "data", zName); + fts5StorageRenameOne(pConfig, &rc, "idx", zName); + fts5StorageRenameOne(pConfig, &rc, "config", zName); + if( pConfig->bColumnsize ){ + fts5StorageRenameOne(pConfig, &rc, "docsize", zName); + } + if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + fts5StorageRenameOne(pConfig, &rc, "content", zName); + } + return rc; +} + +/* +** Create the shadow table named zPost, with definition zDefn. Return +** SQLITE_OK if successful, or an SQLite error code otherwise. +*/ +static int sqlite3Fts5CreateTable( + Fts5Config *pConfig, /* FTS5 configuration */ + const char *zPost, /* Shadow table to create (e.g. "content") */ + const char *zDefn, /* Columns etc. for shadow table */ + int bWithout, /* True for without rowid */ + char **pzErr /* OUT: Error message */ +){ + int rc; + char *zErr = 0; + + rc = fts5ExecPrintf(pConfig->db, &zErr, "CREATE TABLE %Q.'%q_%q'(%s)%s", + pConfig->zDb, pConfig->zName, zPost, zDefn, +#ifndef SQLITE_FTS5_NO_WITHOUT_ROWID + bWithout?" WITHOUT ROWID": +#endif + "" + ); + if( zErr ){ + *pzErr = sqlite3_mprintf( + "fts5: error creating shadow table %q_%s: %s", + pConfig->zName, zPost, zErr + ); + sqlite3_free(zErr); + } + + return rc; +} + +/* +** Open a new Fts5Index handle. If the bCreate argument is true, create +** and initialize the underlying tables +** +** If successful, set *pp to point to the new object and return SQLITE_OK. +** Otherwise, set *pp to NULL and return an SQLite error code. +*/ +static int sqlite3Fts5StorageOpen( + Fts5Config *pConfig, + Fts5Index *pIndex, + int bCreate, + Fts5Storage **pp, + char **pzErr /* OUT: Error message */ +){ + int rc = SQLITE_OK; + Fts5Storage *p; /* New object */ + sqlite3_int64 nByte; /* Bytes of space to allocate */ + + nByte = sizeof(Fts5Storage) /* Fts5Storage object */ + + pConfig->nCol * sizeof(i64); /* Fts5Storage.aTotalSize[] */ + *pp = p = (Fts5Storage*)sqlite3_malloc64(nByte); + if( !p ) return SQLITE_NOMEM; + + memset(p, 0, (size_t)nByte); + p->aTotalSize = (i64*)&p[1]; + p->pConfig = pConfig; + p->pIndex = pIndex; + + if( bCreate ){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + int nDefn = 32 + pConfig->nCol*10; + char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 10); + if( zDefn==0 ){ + rc = SQLITE_NOMEM; + }else{ + int i; + int iOff; + sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY"); + iOff = (int)strlen(zDefn); + for(i=0; i<pConfig->nCol; i++){ + sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i); + iOff += (int)strlen(&zDefn[iOff]); + } + rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr); + } + sqlite3_free(zDefn); + } + + if( rc==SQLITE_OK && pConfig->bColumnsize ){ + const char *zCols = "id INTEGER PRIMARY KEY, sz BLOB"; + if( pConfig->bContentlessDelete ){ + zCols = "id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER"; + } + rc = sqlite3Fts5CreateTable(pConfig, "docsize", zCols, 0, pzErr); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5CreateTable( + pConfig, "config", "k PRIMARY KEY, v", 1, pzErr + ); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION); + } + } + + if( rc ){ + sqlite3Fts5StorageClose(p); + *pp = 0; + } + return rc; +} + +/* +** Close a handle opened by an earlier call to sqlite3Fts5StorageOpen(). +*/ +static int sqlite3Fts5StorageClose(Fts5Storage *p){ + int rc = SQLITE_OK; + if( p ){ + int i; + + /* Finalize all SQL statements */ + for(i=0; i<ArraySize(p->aStmt); i++){ + sqlite3_finalize(p->aStmt[i]); + } + + sqlite3_free(p); + } + return rc; +} + +typedef struct Fts5InsertCtx Fts5InsertCtx; +struct Fts5InsertCtx { + Fts5Storage *pStorage; + int iCol; + int szCol; /* Size of column value in tokens */ +}; + +/* +** Tokenization callback used when inserting tokens into the FTS index. +*/ +static int fts5StorageInsertCallback( + void *pContext, /* Pointer to Fts5InsertCtx object */ + int tflags, + const char *pToken, /* Buffer containing token */ + int nToken, /* Size of token in bytes */ + int iUnused1, /* Start offset of token */ + int iUnused2 /* End offset of token */ +){ + Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext; + Fts5Index *pIdx = pCtx->pStorage->pIndex; + UNUSED_PARAM2(iUnused1, iUnused2); + if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; + if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){ + pCtx->szCol++; + } + return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken); +} + +/* +** If a row with rowid iDel is present in the %_content table, add the +** delete-markers to the FTS index necessary to delete it. Do not actually +** remove the %_content row at this time though. +*/ +static int fts5StorageDeleteFromIndex( + Fts5Storage *p, + i64 iDel, + sqlite3_value **apVal +){ + Fts5Config *pConfig = p->pConfig; + sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */ + int rc = SQLITE_OK; /* Return code */ + int rc2; /* sqlite3_reset() return code */ + int iCol; + Fts5InsertCtx ctx; + + if( apVal==0 ){ + rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek, 0); + if( rc!=SQLITE_OK ) return rc; + sqlite3_bind_int64(pSeek, 1, iDel); + if( sqlite3_step(pSeek)!=SQLITE_ROW ){ + return sqlite3_reset(pSeek); + } + } + + ctx.pStorage = p; + ctx.iCol = -1; + for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ + if( pConfig->abUnindexed[iCol-1]==0 ){ + const char *zText; + int nText; + assert( pSeek==0 || apVal==0 ); + assert( pSeek!=0 || apVal!=0 ); + if( pSeek ){ + zText = (const char*)sqlite3_column_text(pSeek, iCol); + nText = sqlite3_column_bytes(pSeek, iCol); + }else if( ALWAYS(apVal) ){ + zText = (const char*)sqlite3_value_text(apVal[iCol-1]); + nText = sqlite3_value_bytes(apVal[iCol-1]); + }else{ + continue; + } + ctx.szCol = 0; + rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, + zText, nText, (void*)&ctx, fts5StorageInsertCallback + ); + p->aTotalSize[iCol-1] -= (i64)ctx.szCol; + if( p->aTotalSize[iCol-1]<0 ){ + rc = FTS5_CORRUPT; + } + } + } + if( rc==SQLITE_OK && p->nTotalRow<1 ){ + rc = FTS5_CORRUPT; + }else{ + p->nTotalRow--; + } + + rc2 = sqlite3_reset(pSeek); + if( rc==SQLITE_OK ) rc = rc2; + return rc; +} + +/* +** This function is called to process a DELETE on a contentless_delete=1 +** table. It adds the tombstone required to delete the entry with rowid +** iDel. If successful, SQLITE_OK is returned. Or, if an error occurs, +** an SQLite error code. +*/ +static int fts5StorageContentlessDelete(Fts5Storage *p, i64 iDel){ + i64 iOrigin = 0; + sqlite3_stmt *pLookup = 0; + int rc = SQLITE_OK; + + assert( p->pConfig->bContentlessDelete ); + assert( p->pConfig->eContent==FTS5_CONTENT_NONE ); + + /* Look up the origin of the document in the %_docsize table. Store + ** this in stack variable iOrigin. */ + rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pLookup, 1, iDel); + if( SQLITE_ROW==sqlite3_step(pLookup) ){ + iOrigin = sqlite3_column_int64(pLookup, 1); + } + rc = sqlite3_reset(pLookup); + } + + if( rc==SQLITE_OK && iOrigin!=0 ){ + rc = sqlite3Fts5IndexContentlessDelete(p->pIndex, iOrigin, iDel); + } + + return rc; +} + +/* +** Insert a record into the %_docsize table. Specifically, do: +** +** INSERT OR REPLACE INTO %_docsize(id, sz) VALUES(iRowid, pBuf); +** +** If there is no %_docsize table (as happens if the columnsize=0 option +** is specified when the FTS5 table is created), this function is a no-op. +*/ +static int fts5StorageInsertDocsize( + Fts5Storage *p, /* Storage module to write to */ + i64 iRowid, /* id value */ + Fts5Buffer *pBuf /* sz value */ +){ + int rc = SQLITE_OK; + if( p->pConfig->bColumnsize ){ + sqlite3_stmt *pReplace = 0; + rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pReplace, 1, iRowid); + if( p->pConfig->bContentlessDelete ){ + i64 iOrigin = 0; + rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin); + sqlite3_bind_int64(pReplace, 3, iOrigin); + } + if( rc==SQLITE_OK ){ + sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); + sqlite3_step(pReplace); + rc = sqlite3_reset(pReplace); + sqlite3_bind_null(pReplace, 2); + } + } + } + return rc; +} + +/* +** Load the contents of the "averages" record from disk into the +** p->nTotalRow and p->aTotalSize[] variables. If successful, and if +** argument bCache is true, set the p->bTotalsValid flag to indicate +** that the contents of aTotalSize[] and nTotalRow are valid until +** further notice. +** +** Return SQLITE_OK if successful, or an SQLite error code if an error +** occurs. +*/ +static int fts5StorageLoadTotals(Fts5Storage *p, int bCache){ + int rc = SQLITE_OK; + if( p->bTotalsValid==0 ){ + rc = sqlite3Fts5IndexGetAverages(p->pIndex, &p->nTotalRow, p->aTotalSize); + p->bTotalsValid = bCache; + } + return rc; +} + +/* +** Store the current contents of the p->nTotalRow and p->aTotalSize[] +** variables in the "averages" record on disk. +** +** Return SQLITE_OK if successful, or an SQLite error code if an error +** occurs. +*/ +static int fts5StorageSaveTotals(Fts5Storage *p){ + int nCol = p->pConfig->nCol; + int i; + Fts5Buffer buf; + int rc = SQLITE_OK; + memset(&buf, 0, sizeof(buf)); + + sqlite3Fts5BufferAppendVarint(&rc, &buf, p->nTotalRow); + for(i=0; i<nCol; i++){ + sqlite3Fts5BufferAppendVarint(&rc, &buf, p->aTotalSize[i]); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexSetAverages(p->pIndex, buf.p, buf.n); + } + sqlite3_free(buf.p); + + return rc; +} + +/* +** Remove a row from the FTS table. +*/ +static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){ + Fts5Config *pConfig = p->pConfig; + int rc; + sqlite3_stmt *pDel = 0; + + assert( pConfig->eContent!=FTS5_CONTENT_NORMAL || apVal==0 ); + rc = fts5StorageLoadTotals(p, 1); + + /* Delete the index records */ + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); + } + + if( rc==SQLITE_OK ){ + if( p->pConfig->bContentlessDelete ){ + rc = fts5StorageContentlessDelete(p, iDel); + }else{ + rc = fts5StorageDeleteFromIndex(p, iDel, apVal); + } + } + + /* Delete the %_docsize record */ + if( rc==SQLITE_OK && pConfig->bColumnsize ){ + rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pDel, 1, iDel); + sqlite3_step(pDel); + rc = sqlite3_reset(pDel); + } + } + + /* Delete the %_content record */ + if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + if( rc==SQLITE_OK ){ + rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_CONTENT, &pDel, 0); + } + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pDel, 1, iDel); + sqlite3_step(pDel); + rc = sqlite3_reset(pDel); + } + } + + return rc; +} + +/* +** Delete all entries in the FTS5 index. +*/ +static int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){ + Fts5Config *pConfig = p->pConfig; + int rc; + + p->bTotalsValid = 0; + + /* Delete the contents of the %_data and %_docsize tables. */ + rc = fts5ExecPrintf(pConfig->db, 0, + "DELETE FROM %Q.'%q_data';" + "DELETE FROM %Q.'%q_idx';", + pConfig->zDb, pConfig->zName, + pConfig->zDb, pConfig->zName + ); + if( rc==SQLITE_OK && pConfig->bColumnsize ){ + rc = fts5ExecPrintf(pConfig->db, 0, + "DELETE FROM %Q.'%q_docsize';", + pConfig->zDb, pConfig->zName + ); + } + + /* Reinitialize the %_data table. This call creates the initial structure + ** and averages records. */ + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexReinit(p->pIndex); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION); + } + return rc; +} + +static int sqlite3Fts5StorageRebuild(Fts5Storage *p){ + Fts5Buffer buf = {0,0,0}; + Fts5Config *pConfig = p->pConfig; + sqlite3_stmt *pScan = 0; + Fts5InsertCtx ctx; + int rc, rc2; + + memset(&ctx, 0, sizeof(Fts5InsertCtx)); + ctx.pStorage = p; + rc = sqlite3Fts5StorageDeleteAll(p); + if( rc==SQLITE_OK ){ + rc = fts5StorageLoadTotals(p, 1); + } + + if( rc==SQLITE_OK ){ + rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0); + } + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){ + i64 iRowid = sqlite3_column_int64(pScan, 0); + + sqlite3Fts5BufferZero(&buf); + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid); + for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){ + ctx.szCol = 0; + if( pConfig->abUnindexed[ctx.iCol]==0 ){ + const char *zText = (const char*)sqlite3_column_text(pScan, ctx.iCol+1); + int nText = sqlite3_column_bytes(pScan, ctx.iCol+1); + rc = sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, + zText, nText, + (void*)&ctx, + fts5StorageInsertCallback + ); + } + sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol); + p->aTotalSize[ctx.iCol] += (i64)ctx.szCol; + } + p->nTotalRow++; + + if( rc==SQLITE_OK ){ + rc = fts5StorageInsertDocsize(p, iRowid, &buf); + } + } + sqlite3_free(buf.p); + rc2 = sqlite3_reset(pScan); + if( rc==SQLITE_OK ) rc = rc2; + + /* Write the averages record */ + if( rc==SQLITE_OK ){ + rc = fts5StorageSaveTotals(p); + } + return rc; +} + +static int sqlite3Fts5StorageOptimize(Fts5Storage *p){ + return sqlite3Fts5IndexOptimize(p->pIndex); +} + +static int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge){ + return sqlite3Fts5IndexMerge(p->pIndex, nMerge); +} + +static int sqlite3Fts5StorageReset(Fts5Storage *p){ + return sqlite3Fts5IndexReset(p->pIndex); +} + +/* +** Allocate a new rowid. This is used for "external content" tables when +** a NULL value is inserted into the rowid column. The new rowid is allocated +** by inserting a dummy row into the %_docsize table. The dummy will be +** overwritten later. +** +** If the %_docsize table does not exist, SQLITE_MISMATCH is returned. In +** this case the user is required to provide a rowid explicitly. +*/ +static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){ + int rc = SQLITE_MISMATCH; + if( p->pConfig->bColumnsize ){ + sqlite3_stmt *pReplace = 0; + rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_null(pReplace, 1); + sqlite3_bind_null(pReplace, 2); + sqlite3_step(pReplace); + rc = sqlite3_reset(pReplace); + } + if( rc==SQLITE_OK ){ + *piRowid = sqlite3_last_insert_rowid(p->pConfig->db); + } + } + return rc; +} + +/* +** Insert a new row into the FTS content table. +*/ +static int sqlite3Fts5StorageContentInsert( + Fts5Storage *p, + sqlite3_value **apVal, + i64 *piRowid +){ + Fts5Config *pConfig = p->pConfig; + int rc = SQLITE_OK; + + /* Insert the new row into the %_content table. */ + if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){ + if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ + *piRowid = sqlite3_value_int64(apVal[1]); + }else{ + rc = fts5StorageNewRowid(p, piRowid); + } + }else{ + sqlite3_stmt *pInsert = 0; /* Statement to write %_content table */ + int i; /* Counter variable */ + rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0); + for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ + rc = sqlite3_bind_value(pInsert, i, apVal[i]); + } + if( rc==SQLITE_OK ){ + sqlite3_step(pInsert); + rc = sqlite3_reset(pInsert); + } + *piRowid = sqlite3_last_insert_rowid(pConfig->db); + } + + return rc; +} + +/* +** Insert new entries into the FTS index and %_docsize table. +*/ +static int sqlite3Fts5StorageIndexInsert( + Fts5Storage *p, + sqlite3_value **apVal, + i64 iRowid +){ + Fts5Config *pConfig = p->pConfig; + int rc = SQLITE_OK; /* Return code */ + Fts5InsertCtx ctx; /* Tokenization callback context object */ + Fts5Buffer buf; /* Buffer used to build up %_docsize blob */ + + memset(&buf, 0, sizeof(Fts5Buffer)); + ctx.pStorage = p; + rc = fts5StorageLoadTotals(p, 1); + + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid); + } + for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){ + ctx.szCol = 0; + if( pConfig->abUnindexed[ctx.iCol]==0 ){ + const char *zText = (const char*)sqlite3_value_text(apVal[ctx.iCol+2]); + int nText = sqlite3_value_bytes(apVal[ctx.iCol+2]); + rc = sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, + zText, nText, + (void*)&ctx, + fts5StorageInsertCallback + ); + } + sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol); + p->aTotalSize[ctx.iCol] += (i64)ctx.szCol; + } + p->nTotalRow++; + + /* Write the %_docsize record */ + if( rc==SQLITE_OK ){ + rc = fts5StorageInsertDocsize(p, iRowid, &buf); + } + sqlite3_free(buf.p); + + return rc; +} + +static int fts5StorageCount(Fts5Storage *p, const char *zSuffix, i64 *pnRow){ + Fts5Config *pConfig = p->pConfig; + char *zSql; + int rc; + + zSql = sqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'", + pConfig->zDb, pConfig->zName, zSuffix + ); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_stmt *pCnt = 0; + rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pCnt, 0); + if( rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pCnt) ){ + *pnRow = sqlite3_column_int64(pCnt, 0); + } + rc = sqlite3_finalize(pCnt); + } + } + + sqlite3_free(zSql); + return rc; +} + +/* +** Context object used by sqlite3Fts5StorageIntegrity(). +*/ +typedef struct Fts5IntegrityCtx Fts5IntegrityCtx; +struct Fts5IntegrityCtx { + i64 iRowid; + int iCol; + int szCol; + u64 cksum; + Fts5Termset *pTermset; + Fts5Config *pConfig; +}; + + +/* +** Tokenization callback used by integrity check. +*/ +static int fts5StorageIntegrityCallback( + void *pContext, /* Pointer to Fts5IntegrityCtx object */ + int tflags, + const char *pToken, /* Buffer containing token */ + int nToken, /* Size of token in bytes */ + int iUnused1, /* Start offset of token */ + int iUnused2 /* End offset of token */ +){ + Fts5IntegrityCtx *pCtx = (Fts5IntegrityCtx*)pContext; + Fts5Termset *pTermset = pCtx->pTermset; + int bPresent; + int ii; + int rc = SQLITE_OK; + int iPos; + int iCol; + + UNUSED_PARAM2(iUnused1, iUnused2); + if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; + + if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){ + pCtx->szCol++; + } + + switch( pCtx->pConfig->eDetail ){ + case FTS5_DETAIL_FULL: + iPos = pCtx->szCol-1; + iCol = pCtx->iCol; + break; + + case FTS5_DETAIL_COLUMNS: + iPos = pCtx->iCol; + iCol = 0; + break; + + default: + assert( pCtx->pConfig->eDetail==FTS5_DETAIL_NONE ); + iPos = 0; + iCol = 0; + break; + } + + rc = sqlite3Fts5TermsetAdd(pTermset, 0, pToken, nToken, &bPresent); + if( rc==SQLITE_OK && bPresent==0 ){ + pCtx->cksum ^= sqlite3Fts5IndexEntryCksum( + pCtx->iRowid, iCol, iPos, 0, pToken, nToken + ); + } + + for(ii=0; rc==SQLITE_OK && ii<pCtx->pConfig->nPrefix; ii++){ + const int nChar = pCtx->pConfig->aPrefix[ii]; + int nByte = sqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar); + if( nByte ){ + rc = sqlite3Fts5TermsetAdd(pTermset, ii+1, pToken, nByte, &bPresent); + if( bPresent==0 ){ + pCtx->cksum ^= sqlite3Fts5IndexEntryCksum( + pCtx->iRowid, iCol, iPos, ii+1, pToken, nByte + ); + } + } + } + + return rc; +} + +/* +** Check that the contents of the FTS index match that of the %_content +** table. Return SQLITE_OK if they do, or SQLITE_CORRUPT if not. Return +** some other SQLite error code if an error occurs while attempting to +** determine this. +*/ +static int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg){ + Fts5Config *pConfig = p->pConfig; + int rc = SQLITE_OK; /* Return code */ + int *aColSize; /* Array of size pConfig->nCol */ + i64 *aTotalSize; /* Array of size pConfig->nCol */ + Fts5IntegrityCtx ctx; + sqlite3_stmt *pScan; + int bUseCksum; + + memset(&ctx, 0, sizeof(Fts5IntegrityCtx)); + ctx.pConfig = p->pConfig; + aTotalSize = (i64*)sqlite3_malloc64(pConfig->nCol*(sizeof(int)+sizeof(i64))); + if( !aTotalSize ) return SQLITE_NOMEM; + aColSize = (int*)&aTotalSize[pConfig->nCol]; + memset(aTotalSize, 0, sizeof(i64) * pConfig->nCol); + + bUseCksum = (pConfig->eContent==FTS5_CONTENT_NORMAL + || (pConfig->eContent==FTS5_CONTENT_EXTERNAL && iArg) + ); + if( bUseCksum ){ + /* Generate the expected index checksum based on the contents of the + ** %_content table. This block stores the checksum in ctx.cksum. */ + rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0); + if( rc==SQLITE_OK ){ + int rc2; + while( SQLITE_ROW==sqlite3_step(pScan) ){ + int i; + ctx.iRowid = sqlite3_column_int64(pScan, 0); + ctx.szCol = 0; + if( pConfig->bColumnsize ){ + rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize); + } + if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_NONE ){ + rc = sqlite3Fts5TermsetNew(&ctx.pTermset); + } + for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){ + if( pConfig->abUnindexed[i] ) continue; + ctx.iCol = i; + ctx.szCol = 0; + if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + rc = sqlite3Fts5TermsetNew(&ctx.pTermset); + } + if( rc==SQLITE_OK ){ + const char *zText = (const char*)sqlite3_column_text(pScan, i+1); + int nText = sqlite3_column_bytes(pScan, i+1); + rc = sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, + zText, nText, + (void*)&ctx, + fts5StorageIntegrityCallback + ); + } + if( rc==SQLITE_OK && pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){ + rc = FTS5_CORRUPT; + } + aTotalSize[i] += ctx.szCol; + if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + sqlite3Fts5TermsetFree(ctx.pTermset); + ctx.pTermset = 0; + } + } + sqlite3Fts5TermsetFree(ctx.pTermset); + ctx.pTermset = 0; + + if( rc!=SQLITE_OK ) break; + } + rc2 = sqlite3_reset(pScan); + if( rc==SQLITE_OK ) rc = rc2; + } + + /* Test that the "totals" (sometimes called "averages") record looks Ok */ + if( rc==SQLITE_OK ){ + int i; + rc = fts5StorageLoadTotals(p, 0); + for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){ + if( p->aTotalSize[i]!=aTotalSize[i] ) rc = FTS5_CORRUPT; + } + } + + /* Check that the %_docsize and %_content tables contain the expected + ** number of rows. */ + if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ + i64 nRow = 0; + rc = fts5StorageCount(p, "content", &nRow); + if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; + } + if( rc==SQLITE_OK && pConfig->bColumnsize ){ + i64 nRow = 0; + rc = fts5StorageCount(p, "docsize", &nRow); + if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; + } + } + + /* Pass the expected checksum down to the FTS index module. It will + ** verify, amongst other things, that it matches the checksum generated by + ** inspecting the index itself. */ + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum, bUseCksum); + } + + sqlite3_free(aTotalSize); + return rc; +} + +/* +** Obtain an SQLite statement handle that may be used to read data from the +** %_content table. +*/ +static int sqlite3Fts5StorageStmt( + Fts5Storage *p, + int eStmt, + sqlite3_stmt **pp, + char **pzErrMsg +){ + int rc; + assert( eStmt==FTS5_STMT_SCAN_ASC + || eStmt==FTS5_STMT_SCAN_DESC + || eStmt==FTS5_STMT_LOOKUP + ); + rc = fts5StorageGetStmt(p, eStmt, pp, pzErrMsg); + if( rc==SQLITE_OK ){ + assert( p->aStmt[eStmt]==*pp ); + p->aStmt[eStmt] = 0; + } + return rc; +} + +/* +** Release an SQLite statement handle obtained via an earlier call to +** sqlite3Fts5StorageStmt(). The eStmt parameter passed to this function +** must match that passed to the sqlite3Fts5StorageStmt() call. +*/ +static void sqlite3Fts5StorageStmtRelease( + Fts5Storage *p, + int eStmt, + sqlite3_stmt *pStmt +){ + assert( eStmt==FTS5_STMT_SCAN_ASC + || eStmt==FTS5_STMT_SCAN_DESC + || eStmt==FTS5_STMT_LOOKUP + ); + if( p->aStmt[eStmt]==0 ){ + sqlite3_reset(pStmt); + p->aStmt[eStmt] = pStmt; + }else{ + sqlite3_finalize(pStmt); + } +} + +static int fts5StorageDecodeSizeArray( + int *aCol, int nCol, /* Array to populate */ + const u8 *aBlob, int nBlob /* Record to read varints from */ +){ + int i; + int iOff = 0; + for(i=0; i<nCol; i++){ + if( iOff>=nBlob ) return 1; + iOff += fts5GetVarint32(&aBlob[iOff], aCol[i]); + } + return (iOff!=nBlob); +} + +/* +** Argument aCol points to an array of integers containing one entry for +** each table column. This function reads the %_docsize record for the +** specified rowid and populates aCol[] with the results. +** +** An SQLite error code is returned if an error occurs, or SQLITE_OK +** otherwise. +*/ +static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){ + int nCol = p->pConfig->nCol; /* Number of user columns in table */ + sqlite3_stmt *pLookup = 0; /* Statement to query %_docsize */ + int rc; /* Return Code */ + + assert( p->pConfig->bColumnsize ); + rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); + if( pLookup ){ + int bCorrupt = 1; + assert( rc==SQLITE_OK ); + sqlite3_bind_int64(pLookup, 1, iRowid); + if( SQLITE_ROW==sqlite3_step(pLookup) ){ + const u8 *aBlob = sqlite3_column_blob(pLookup, 0); + int nBlob = sqlite3_column_bytes(pLookup, 0); + if( 0==fts5StorageDecodeSizeArray(aCol, nCol, aBlob, nBlob) ){ + bCorrupt = 0; + } + } + rc = sqlite3_reset(pLookup); + if( bCorrupt && rc==SQLITE_OK ){ + rc = FTS5_CORRUPT; + } + }else{ + assert( rc!=SQLITE_OK ); + } + + return rc; +} + +static int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){ + int rc = fts5StorageLoadTotals(p, 0); + if( rc==SQLITE_OK ){ + *pnToken = 0; + if( iCol<0 ){ + int i; + for(i=0; i<p->pConfig->nCol; i++){ + *pnToken += p->aTotalSize[i]; + } + }else if( iCol<p->pConfig->nCol ){ + *pnToken = p->aTotalSize[iCol]; + }else{ + rc = SQLITE_RANGE; + } + } + return rc; +} + +static int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){ + int rc = fts5StorageLoadTotals(p, 0); + if( rc==SQLITE_OK ){ + /* nTotalRow being zero does not necessarily indicate a corrupt + ** database - it might be that the FTS5 table really does contain zero + ** rows. However this function is only called from the xRowCount() API, + ** and there is no way for that API to be invoked if the table contains + ** no rows. Hence the FTS5_CORRUPT return. */ + *pnRow = p->nTotalRow; + if( p->nTotalRow<=0 ) rc = FTS5_CORRUPT; + } + return rc; +} + +/* +** Flush any data currently held in-memory to disk. +*/ +static int sqlite3Fts5StorageSync(Fts5Storage *p){ + int rc = SQLITE_OK; + i64 iLastRowid = sqlite3_last_insert_rowid(p->pConfig->db); + if( p->bTotalsValid ){ + rc = fts5StorageSaveTotals(p); + p->bTotalsValid = 0; + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexSync(p->pIndex); + } + sqlite3_set_last_insert_rowid(p->pConfig->db, iLastRowid); + return rc; +} + +static int sqlite3Fts5StorageRollback(Fts5Storage *p){ + p->bTotalsValid = 0; + return sqlite3Fts5IndexRollback(p->pIndex); +} + +static int sqlite3Fts5StorageConfigValue( + Fts5Storage *p, + const char *z, + sqlite3_value *pVal, + int iVal +){ + sqlite3_stmt *pReplace = 0; + int rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_CONFIG, &pReplace, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_text(pReplace, 1, z, -1, SQLITE_STATIC); + if( pVal ){ + sqlite3_bind_value(pReplace, 2, pVal); + }else{ + sqlite3_bind_int(pReplace, 2, iVal); + } + sqlite3_step(pReplace); + rc = sqlite3_reset(pReplace); + sqlite3_bind_null(pReplace, 1); + } + if( rc==SQLITE_OK && pVal ){ + int iNew = p->pConfig->iCookie + 1; + rc = sqlite3Fts5IndexSetCookie(p->pIndex, iNew); + if( rc==SQLITE_OK ){ + p->pConfig->iCookie = iNew; + } + } + return rc; +} + +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +*/ + + +/* #include "fts5Int.h" */ + +/************************************************************************** +** Start of ascii tokenizer implementation. +*/ + +/* +** For tokenizers with no "unicode" modifier, the set of token characters +** is the same as the set of ASCII range alphanumeric characters. +*/ +static unsigned char aAsciiTokenChar[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00..0x0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10..0x1F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20..0x2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 0x30..0x3F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40..0x4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 0x50..0x5F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60..0x6F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 0x70..0x7F */ +}; + +typedef struct AsciiTokenizer AsciiTokenizer; +struct AsciiTokenizer { + unsigned char aTokenChar[128]; +}; + +static void fts5AsciiAddExceptions( + AsciiTokenizer *p, + const char *zArg, + int bTokenChars +){ + int i; + for(i=0; zArg[i]; i++){ + if( (zArg[i] & 0x80)==0 ){ + p->aTokenChar[(int)zArg[i]] = (unsigned char)bTokenChars; + } + } +} + +/* +** Delete a "ascii" tokenizer. +*/ +static void fts5AsciiDelete(Fts5Tokenizer *p){ + sqlite3_free(p); +} + +/* +** Create an "ascii" tokenizer. +*/ +static int fts5AsciiCreate( + void *pUnused, + const char **azArg, int nArg, + Fts5Tokenizer **ppOut +){ + int rc = SQLITE_OK; + AsciiTokenizer *p = 0; + UNUSED_PARAM(pUnused); + if( nArg%2 ){ + rc = SQLITE_ERROR; + }else{ + p = sqlite3_malloc(sizeof(AsciiTokenizer)); + if( p==0 ){ + rc = SQLITE_NOMEM; + }else{ + int i; + memset(p, 0, sizeof(AsciiTokenizer)); + memcpy(p->aTokenChar, aAsciiTokenChar, sizeof(aAsciiTokenChar)); + for(i=0; rc==SQLITE_OK && i<nArg; i+=2){ + const char *zArg = azArg[i+1]; + if( 0==sqlite3_stricmp(azArg[i], "tokenchars") ){ + fts5AsciiAddExceptions(p, zArg, 1); + }else + if( 0==sqlite3_stricmp(azArg[i], "separators") ){ + fts5AsciiAddExceptions(p, zArg, 0); + }else{ + rc = SQLITE_ERROR; + } + } + if( rc!=SQLITE_OK ){ + fts5AsciiDelete((Fts5Tokenizer*)p); + p = 0; + } + } + } + + *ppOut = (Fts5Tokenizer*)p; + return rc; +} + + +static void asciiFold(char *aOut, const char *aIn, int nByte){ + int i; + for(i=0; i<nByte; i++){ + char c = aIn[i]; + if( c>='A' && c<='Z' ) c += 32; + aOut[i] = c; + } +} + +/* +** Tokenize some text using the ascii tokenizer. +*/ +static int fts5AsciiTokenize( + Fts5Tokenizer *pTokenizer, + void *pCtx, + int iUnused, + const char *pText, int nText, + int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd) +){ + AsciiTokenizer *p = (AsciiTokenizer*)pTokenizer; + int rc = SQLITE_OK; + int ie; + int is = 0; + + char aFold[64]; + int nFold = sizeof(aFold); + char *pFold = aFold; + unsigned char *a = p->aTokenChar; + + UNUSED_PARAM(iUnused); + + while( is<nText && rc==SQLITE_OK ){ + int nByte; + + /* Skip any leading divider characters. */ + while( is<nText && ((pText[is]&0x80)==0 && a[(int)pText[is]]==0) ){ + is++; + } + if( is==nText ) break; + + /* Count the token characters */ + ie = is+1; + while( ie<nText && ((pText[ie]&0x80) || a[(int)pText[ie]] ) ){ + ie++; + } + + /* Fold to lower case */ + nByte = ie-is; + if( nByte>nFold ){ + if( pFold!=aFold ) sqlite3_free(pFold); + pFold = sqlite3_malloc64((sqlite3_int64)nByte*2); + if( pFold==0 ){ + rc = SQLITE_NOMEM; + break; + } + nFold = nByte*2; + } + asciiFold(pFold, &pText[is], nByte); + + /* Invoke the token callback */ + rc = xToken(pCtx, 0, pFold, nByte, is, ie); + is = ie+1; + } + + if( pFold!=aFold ) sqlite3_free(pFold); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + return rc; +} + +/************************************************************************** +** Start of unicode61 tokenizer implementation. +*/ + + +/* +** The following two macros - READ_UTF8 and WRITE_UTF8 - have been copied +** from the sqlite3 source file utf.c. If this file is compiled as part +** of the amalgamation, they are not required. +*/ +#ifndef SQLITE_AMALGAMATION + +static const unsigned char sqlite3Utf8Trans1[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, +}; + +#define READ_UTF8(zIn, zTerm, c) \ + c = *(zIn++); \ + if( c>=0xc0 ){ \ + c = sqlite3Utf8Trans1[c-0xc0]; \ + while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ + c = (c<<6) + (0x3f & *(zIn++)); \ + } \ + if( c<0x80 \ + || (c&0xFFFFF800)==0xD800 \ + || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ + } + + +#define WRITE_UTF8(zOut, c) { \ + if( c<0x00080 ){ \ + *zOut++ = (unsigned char)(c&0xFF); \ + } \ + else if( c<0x00800 ){ \ + *zOut++ = 0xC0 + (unsigned char)((c>>6)&0x1F); \ + *zOut++ = 0x80 + (unsigned char)(c & 0x3F); \ + } \ + else if( c<0x10000 ){ \ + *zOut++ = 0xE0 + (unsigned char)((c>>12)&0x0F); \ + *zOut++ = 0x80 + (unsigned char)((c>>6) & 0x3F); \ + *zOut++ = 0x80 + (unsigned char)(c & 0x3F); \ + }else{ \ + *zOut++ = 0xF0 + (unsigned char)((c>>18) & 0x07); \ + *zOut++ = 0x80 + (unsigned char)((c>>12) & 0x3F); \ + *zOut++ = 0x80 + (unsigned char)((c>>6) & 0x3F); \ + *zOut++ = 0x80 + (unsigned char)(c & 0x3F); \ + } \ +} + +#endif /* ifndef SQLITE_AMALGAMATION */ + +typedef struct Unicode61Tokenizer Unicode61Tokenizer; +struct Unicode61Tokenizer { + unsigned char aTokenChar[128]; /* ASCII range token characters */ + char *aFold; /* Buffer to fold text into */ + int nFold; /* Size of aFold[] in bytes */ + int eRemoveDiacritic; /* True if remove_diacritics=1 is set */ + int nException; + int *aiException; + + unsigned char aCategory[32]; /* True for token char categories */ +}; + +/* Values for eRemoveDiacritic (must match internals of fts5_unicode2.c) */ +#define FTS5_REMOVE_DIACRITICS_NONE 0 +#define FTS5_REMOVE_DIACRITICS_SIMPLE 1 +#define FTS5_REMOVE_DIACRITICS_COMPLEX 2 + +static int fts5UnicodeAddExceptions( + Unicode61Tokenizer *p, /* Tokenizer object */ + const char *z, /* Characters to treat as exceptions */ + int bTokenChars /* 1 for 'tokenchars', 0 for 'separators' */ +){ + int rc = SQLITE_OK; + int n = (int)strlen(z); + int *aNew; + + if( n>0 ){ + aNew = (int*)sqlite3_realloc64(p->aiException, + (n+p->nException)*sizeof(int)); + if( aNew ){ + int nNew = p->nException; + const unsigned char *zCsr = (const unsigned char*)z; + const unsigned char *zTerm = (const unsigned char*)&z[n]; + while( zCsr<zTerm ){ + u32 iCode; + int bToken; + READ_UTF8(zCsr, zTerm, iCode); + if( iCode<128 ){ + p->aTokenChar[iCode] = (unsigned char)bTokenChars; + }else{ + bToken = p->aCategory[sqlite3Fts5UnicodeCategory(iCode)]; + assert( (bToken==0 || bToken==1) ); + assert( (bTokenChars==0 || bTokenChars==1) ); + if( bToken!=bTokenChars && sqlite3Fts5UnicodeIsdiacritic(iCode)==0 ){ + int i; + for(i=0; i<nNew; i++){ + if( (u32)aNew[i]>iCode ) break; + } + memmove(&aNew[i+1], &aNew[i], (nNew-i)*sizeof(int)); + aNew[i] = iCode; + nNew++; + } + } + } + p->aiException = aNew; + p->nException = nNew; + }else{ + rc = SQLITE_NOMEM; + } + } + + return rc; +} + +/* +** Return true if the p->aiException[] array contains the value iCode. +*/ +static int fts5UnicodeIsException(Unicode61Tokenizer *p, int iCode){ + if( p->nException>0 ){ + int *a = p->aiException; + int iLo = 0; + int iHi = p->nException-1; + + while( iHi>=iLo ){ + int iTest = (iHi + iLo) / 2; + if( iCode==a[iTest] ){ + return 1; + }else if( iCode>a[iTest] ){ + iLo = iTest+1; + }else{ + iHi = iTest-1; + } + } + } + + return 0; +} + +/* +** Delete a "unicode61" tokenizer. +*/ +static void fts5UnicodeDelete(Fts5Tokenizer *pTok){ + if( pTok ){ + Unicode61Tokenizer *p = (Unicode61Tokenizer*)pTok; + sqlite3_free(p->aiException); + sqlite3_free(p->aFold); + sqlite3_free(p); + } + return; +} + +static int unicodeSetCategories(Unicode61Tokenizer *p, const char *zCat){ + const char *z = zCat; + + while( *z ){ + while( *z==' ' || *z=='\t' ) z++; + if( *z && sqlite3Fts5UnicodeCatParse(z, p->aCategory) ){ + return SQLITE_ERROR; + } + while( *z!=' ' && *z!='\t' && *z!='\0' ) z++; + } + + sqlite3Fts5UnicodeAscii(p->aCategory, p->aTokenChar); + return SQLITE_OK; +} + +/* +** Create a "unicode61" tokenizer. +*/ +static int fts5UnicodeCreate( + void *pUnused, + const char **azArg, int nArg, + Fts5Tokenizer **ppOut +){ + int rc = SQLITE_OK; /* Return code */ + Unicode61Tokenizer *p = 0; /* New tokenizer object */ + + UNUSED_PARAM(pUnused); + + if( nArg%2 ){ + rc = SQLITE_ERROR; + }else{ + p = (Unicode61Tokenizer*)sqlite3_malloc(sizeof(Unicode61Tokenizer)); + if( p ){ + const char *zCat = "L* N* Co"; + int i; + memset(p, 0, sizeof(Unicode61Tokenizer)); + + p->eRemoveDiacritic = FTS5_REMOVE_DIACRITICS_SIMPLE; + p->nFold = 64; + p->aFold = sqlite3_malloc64(p->nFold * sizeof(char)); + if( p->aFold==0 ){ + rc = SQLITE_NOMEM; + } + + /* Search for a "categories" argument */ + for(i=0; rc==SQLITE_OK && i<nArg; i+=2){ + if( 0==sqlite3_stricmp(azArg[i], "categories") ){ + zCat = azArg[i+1]; + } + } + + if( rc==SQLITE_OK ){ + rc = unicodeSetCategories(p, zCat); + } + + for(i=0; rc==SQLITE_OK && i<nArg; i+=2){ + const char *zArg = azArg[i+1]; + if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){ + if( (zArg[0]!='0' && zArg[0]!='1' && zArg[0]!='2') || zArg[1] ){ + rc = SQLITE_ERROR; + }else{ + p->eRemoveDiacritic = (zArg[0] - '0'); + assert( p->eRemoveDiacritic==FTS5_REMOVE_DIACRITICS_NONE + || p->eRemoveDiacritic==FTS5_REMOVE_DIACRITICS_SIMPLE + || p->eRemoveDiacritic==FTS5_REMOVE_DIACRITICS_COMPLEX + ); + } + }else + if( 0==sqlite3_stricmp(azArg[i], "tokenchars") ){ + rc = fts5UnicodeAddExceptions(p, zArg, 1); + }else + if( 0==sqlite3_stricmp(azArg[i], "separators") ){ + rc = fts5UnicodeAddExceptions(p, zArg, 0); + }else + if( 0==sqlite3_stricmp(azArg[i], "categories") ){ + /* no-op */ + }else{ + rc = SQLITE_ERROR; + } + } + + }else{ + rc = SQLITE_NOMEM; + } + if( rc!=SQLITE_OK ){ + fts5UnicodeDelete((Fts5Tokenizer*)p); + p = 0; + } + *ppOut = (Fts5Tokenizer*)p; + } + return rc; +} + +/* +** Return true if, for the purposes of tokenizing with the tokenizer +** passed as the first argument, codepoint iCode is considered a token +** character (not a separator). +*/ +static int fts5UnicodeIsAlnum(Unicode61Tokenizer *p, int iCode){ + return ( + p->aCategory[sqlite3Fts5UnicodeCategory((u32)iCode)] + ^ fts5UnicodeIsException(p, iCode) + ); +} + +static int fts5UnicodeTokenize( + Fts5Tokenizer *pTokenizer, + void *pCtx, + int iUnused, + const char *pText, int nText, + int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd) +){ + Unicode61Tokenizer *p = (Unicode61Tokenizer*)pTokenizer; + int rc = SQLITE_OK; + unsigned char *a = p->aTokenChar; + + unsigned char *zTerm = (unsigned char*)&pText[nText]; + unsigned char *zCsr = (unsigned char *)pText; + + /* Output buffer */ + char *aFold = p->aFold; + int nFold = p->nFold; + const char *pEnd = &aFold[nFold-6]; + + UNUSED_PARAM(iUnused); + + /* Each iteration of this loop gobbles up a contiguous run of separators, + ** then the next token. */ + while( rc==SQLITE_OK ){ + u32 iCode; /* non-ASCII codepoint read from input */ + char *zOut = aFold; + int is; + int ie; + + /* Skip any separator characters. */ + while( 1 ){ + if( zCsr>=zTerm ) goto tokenize_done; + if( *zCsr & 0x80 ) { + /* A character outside of the ascii range. Skip past it if it is + ** a separator character. Or break out of the loop if it is not. */ + is = zCsr - (unsigned char*)pText; + READ_UTF8(zCsr, zTerm, iCode); + if( fts5UnicodeIsAlnum(p, iCode) ){ + goto non_ascii_tokenchar; + } + }else{ + if( a[*zCsr] ){ + is = zCsr - (unsigned char*)pText; + goto ascii_tokenchar; + } + zCsr++; + } + } + + /* Run through the tokenchars. Fold them into the output buffer along + ** the way. */ + while( zCsr<zTerm ){ + + /* Grow the output buffer so that there is sufficient space to fit the + ** largest possible utf-8 character. */ + if( zOut>pEnd ){ + aFold = sqlite3_malloc64((sqlite3_int64)nFold*2); + if( aFold==0 ){ + rc = SQLITE_NOMEM; + goto tokenize_done; + } + zOut = &aFold[zOut - p->aFold]; + memcpy(aFold, p->aFold, nFold); + sqlite3_free(p->aFold); + p->aFold = aFold; + p->nFold = nFold = nFold*2; + pEnd = &aFold[nFold-6]; + } + + if( *zCsr & 0x80 ){ + /* An non-ascii-range character. Fold it into the output buffer if + ** it is a token character, or break out of the loop if it is not. */ + READ_UTF8(zCsr, zTerm, iCode); + if( fts5UnicodeIsAlnum(p,iCode)||sqlite3Fts5UnicodeIsdiacritic(iCode) ){ + non_ascii_tokenchar: + iCode = sqlite3Fts5UnicodeFold(iCode, p->eRemoveDiacritic); + if( iCode ) WRITE_UTF8(zOut, iCode); + }else{ + break; + } + }else if( a[*zCsr]==0 ){ + /* An ascii-range separator character. End of token. */ + break; + }else{ + ascii_tokenchar: + if( *zCsr>='A' && *zCsr<='Z' ){ + *zOut++ = *zCsr + 32; + }else{ + *zOut++ = *zCsr; + } + zCsr++; + } + ie = zCsr - (unsigned char*)pText; + } + + /* Invoke the token callback */ + rc = xToken(pCtx, 0, aFold, zOut-aFold, is, ie); + } + + tokenize_done: + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + return rc; +} + +/************************************************************************** +** Start of porter stemmer implementation. +*/ + +/* Any tokens larger than this (in bytes) are passed through without +** stemming. */ +#define FTS5_PORTER_MAX_TOKEN 64 + +typedef struct PorterTokenizer PorterTokenizer; +struct PorterTokenizer { + fts5_tokenizer tokenizer; /* Parent tokenizer module */ + Fts5Tokenizer *pTokenizer; /* Parent tokenizer instance */ + char aBuf[FTS5_PORTER_MAX_TOKEN + 64]; +}; + +/* +** Delete a "porter" tokenizer. +*/ +static void fts5PorterDelete(Fts5Tokenizer *pTok){ + if( pTok ){ + PorterTokenizer *p = (PorterTokenizer*)pTok; + if( p->pTokenizer ){ + p->tokenizer.xDelete(p->pTokenizer); + } + sqlite3_free(p); + } +} + +/* +** Create a "porter" tokenizer. +*/ +static int fts5PorterCreate( + void *pCtx, + const char **azArg, int nArg, + Fts5Tokenizer **ppOut +){ + fts5_api *pApi = (fts5_api*)pCtx; + int rc = SQLITE_OK; + PorterTokenizer *pRet; + void *pUserdata = 0; + const char *zBase = "unicode61"; + + if( nArg>0 ){ + zBase = azArg[0]; + } + + pRet = (PorterTokenizer*)sqlite3_malloc(sizeof(PorterTokenizer)); + if( pRet ){ + memset(pRet, 0, sizeof(PorterTokenizer)); + rc = pApi->xFindTokenizer(pApi, zBase, &pUserdata, &pRet->tokenizer); + }else{ + rc = SQLITE_NOMEM; + } + if( rc==SQLITE_OK ){ + int nArg2 = (nArg>0 ? nArg-1 : 0); + const char **azArg2 = (nArg2 ? &azArg[1] : 0); + rc = pRet->tokenizer.xCreate(pUserdata, azArg2, nArg2, &pRet->pTokenizer); + } + + if( rc!=SQLITE_OK ){ + fts5PorterDelete((Fts5Tokenizer*)pRet); + pRet = 0; + } + *ppOut = (Fts5Tokenizer*)pRet; + return rc; +} + +typedef struct PorterContext PorterContext; +struct PorterContext { + void *pCtx; + int (*xToken)(void*, int, const char*, int, int, int); + char *aBuf; +}; + +typedef struct PorterRule PorterRule; +struct PorterRule { + const char *zSuffix; + int nSuffix; + int (*xCond)(char *zStem, int nStem); + const char *zOutput; + int nOutput; +}; + +#if 0 +static int fts5PorterApply(char *aBuf, int *pnBuf, PorterRule *aRule){ + int ret = -1; + int nBuf = *pnBuf; + PorterRule *p; + + for(p=aRule; p->zSuffix; p++){ + assert( strlen(p->zSuffix)==p->nSuffix ); + assert( strlen(p->zOutput)==p->nOutput ); + if( nBuf<p->nSuffix ) continue; + if( 0==memcmp(&aBuf[nBuf - p->nSuffix], p->zSuffix, p->nSuffix) ) break; + } + + if( p->zSuffix ){ + int nStem = nBuf - p->nSuffix; + if( p->xCond==0 || p->xCond(aBuf, nStem) ){ + memcpy(&aBuf[nStem], p->zOutput, p->nOutput); + *pnBuf = nStem + p->nOutput; + ret = p - aRule; + } + } + + return ret; +} +#endif + +static int fts5PorterIsVowel(char c, int bYIsVowel){ + return ( + c=='a' || c=='e' || c=='i' || c=='o' || c=='u' || (bYIsVowel && c=='y') + ); +} + +static int fts5PorterGobbleVC(char *zStem, int nStem, int bPrevCons){ + int i; + int bCons = bPrevCons; + + /* Scan for a vowel */ + for(i=0; i<nStem; i++){ + if( 0==(bCons = !fts5PorterIsVowel(zStem[i], bCons)) ) break; + } + + /* Scan for a consonent */ + for(i++; i<nStem; i++){ + if( (bCons = !fts5PorterIsVowel(zStem[i], bCons)) ) return i+1; + } + return 0; +} + +/* porter rule condition: (m > 0) */ +static int fts5Porter_MGt0(char *zStem, int nStem){ + return !!fts5PorterGobbleVC(zStem, nStem, 0); +} + +/* porter rule condition: (m > 1) */ +static int fts5Porter_MGt1(char *zStem, int nStem){ + int n; + n = fts5PorterGobbleVC(zStem, nStem, 0); + if( n && fts5PorterGobbleVC(&zStem[n], nStem-n, 1) ){ + return 1; + } + return 0; +} + +/* porter rule condition: (m = 1) */ +static int fts5Porter_MEq1(char *zStem, int nStem){ + int n; + n = fts5PorterGobbleVC(zStem, nStem, 0); + if( n && 0==fts5PorterGobbleVC(&zStem[n], nStem-n, 1) ){ + return 1; + } + return 0; +} + +/* porter rule condition: (*o) */ +static int fts5Porter_Ostar(char *zStem, int nStem){ + if( zStem[nStem-1]=='w' || zStem[nStem-1]=='x' || zStem[nStem-1]=='y' ){ + return 0; + }else{ + int i; + int mask = 0; + int bCons = 0; + for(i=0; i<nStem; i++){ + bCons = !fts5PorterIsVowel(zStem[i], bCons); + assert( bCons==0 || bCons==1 ); + mask = (mask << 1) + bCons; + } + return ((mask & 0x0007)==0x0005); + } +} + +/* porter rule condition: (m > 1 and (*S or *T)) */ +static int fts5Porter_MGt1_and_S_or_T(char *zStem, int nStem){ + assert( nStem>0 ); + return (zStem[nStem-1]=='s' || zStem[nStem-1]=='t') + && fts5Porter_MGt1(zStem, nStem); +} + +/* porter rule condition: (*v*) */ +static int fts5Porter_Vowel(char *zStem, int nStem){ + int i; + for(i=0; i<nStem; i++){ + if( fts5PorterIsVowel(zStem[i], i>0) ){ + return 1; + } + } + return 0; +} + + +/************************************************************************** +*************************************************************************** +** GENERATED CODE STARTS HERE (mkportersteps.tcl) +*/ + +static int fts5PorterStep4(char *aBuf, int *pnBuf){ + int ret = 0; + int nBuf = *pnBuf; + switch( aBuf[nBuf-2] ){ + + case 'a': + if( nBuf>2 && 0==memcmp("al", &aBuf[nBuf-2], 2) ){ + if( fts5Porter_MGt1(aBuf, nBuf-2) ){ + *pnBuf = nBuf - 2; + } + } + break; + + case 'c': + if( nBuf>4 && 0==memcmp("ance", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt1(aBuf, nBuf-4) ){ + *pnBuf = nBuf - 4; + } + }else if( nBuf>4 && 0==memcmp("ence", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt1(aBuf, nBuf-4) ){ + *pnBuf = nBuf - 4; + } + } + break; + + case 'e': + if( nBuf>2 && 0==memcmp("er", &aBuf[nBuf-2], 2) ){ + if( fts5Porter_MGt1(aBuf, nBuf-2) ){ + *pnBuf = nBuf - 2; + } + } + break; + + case 'i': + if( nBuf>2 && 0==memcmp("ic", &aBuf[nBuf-2], 2) ){ + if( fts5Porter_MGt1(aBuf, nBuf-2) ){ + *pnBuf = nBuf - 2; + } + } + break; + + case 'l': + if( nBuf>4 && 0==memcmp("able", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt1(aBuf, nBuf-4) ){ + *pnBuf = nBuf - 4; + } + }else if( nBuf>4 && 0==memcmp("ible", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt1(aBuf, nBuf-4) ){ + *pnBuf = nBuf - 4; + } + } + break; + + case 'n': + if( nBuf>3 && 0==memcmp("ant", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt1(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + } + }else if( nBuf>5 && 0==memcmp("ement", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt1(aBuf, nBuf-5) ){ + *pnBuf = nBuf - 5; + } + }else if( nBuf>4 && 0==memcmp("ment", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt1(aBuf, nBuf-4) ){ + *pnBuf = nBuf - 4; + } + }else if( nBuf>3 && 0==memcmp("ent", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt1(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + } + } + break; + + case 'o': + if( nBuf>3 && 0==memcmp("ion", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt1_and_S_or_T(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + } + }else if( nBuf>2 && 0==memcmp("ou", &aBuf[nBuf-2], 2) ){ + if( fts5Porter_MGt1(aBuf, nBuf-2) ){ + *pnBuf = nBuf - 2; + } + } + break; + + case 's': + if( nBuf>3 && 0==memcmp("ism", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt1(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + } + } + break; + + case 't': + if( nBuf>3 && 0==memcmp("ate", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt1(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + } + }else if( nBuf>3 && 0==memcmp("iti", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt1(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + } + } + break; + + case 'u': + if( nBuf>3 && 0==memcmp("ous", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt1(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + } + } + break; + + case 'v': + if( nBuf>3 && 0==memcmp("ive", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt1(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + } + } + break; + + case 'z': + if( nBuf>3 && 0==memcmp("ize", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt1(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + } + } + break; + + } + return ret; +} + + +static int fts5PorterStep1B2(char *aBuf, int *pnBuf){ + int ret = 0; + int nBuf = *pnBuf; + switch( aBuf[nBuf-2] ){ + + case 'a': + if( nBuf>2 && 0==memcmp("at", &aBuf[nBuf-2], 2) ){ + memcpy(&aBuf[nBuf-2], "ate", 3); + *pnBuf = nBuf - 2 + 3; + ret = 1; + } + break; + + case 'b': + if( nBuf>2 && 0==memcmp("bl", &aBuf[nBuf-2], 2) ){ + memcpy(&aBuf[nBuf-2], "ble", 3); + *pnBuf = nBuf - 2 + 3; + ret = 1; + } + break; + + case 'i': + if( nBuf>2 && 0==memcmp("iz", &aBuf[nBuf-2], 2) ){ + memcpy(&aBuf[nBuf-2], "ize", 3); + *pnBuf = nBuf - 2 + 3; + ret = 1; + } + break; + + } + return ret; +} + + +static int fts5PorterStep2(char *aBuf, int *pnBuf){ + int ret = 0; + int nBuf = *pnBuf; + switch( aBuf[nBuf-2] ){ + + case 'a': + if( nBuf>7 && 0==memcmp("ational", &aBuf[nBuf-7], 7) ){ + if( fts5Porter_MGt0(aBuf, nBuf-7) ){ + memcpy(&aBuf[nBuf-7], "ate", 3); + *pnBuf = nBuf - 7 + 3; + } + }else if( nBuf>6 && 0==memcmp("tional", &aBuf[nBuf-6], 6) ){ + if( fts5Porter_MGt0(aBuf, nBuf-6) ){ + memcpy(&aBuf[nBuf-6], "tion", 4); + *pnBuf = nBuf - 6 + 4; + } + } + break; + + case 'c': + if( nBuf>4 && 0==memcmp("enci", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt0(aBuf, nBuf-4) ){ + memcpy(&aBuf[nBuf-4], "ence", 4); + *pnBuf = nBuf - 4 + 4; + } + }else if( nBuf>4 && 0==memcmp("anci", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt0(aBuf, nBuf-4) ){ + memcpy(&aBuf[nBuf-4], "ance", 4); + *pnBuf = nBuf - 4 + 4; + } + } + break; + + case 'e': + if( nBuf>4 && 0==memcmp("izer", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt0(aBuf, nBuf-4) ){ + memcpy(&aBuf[nBuf-4], "ize", 3); + *pnBuf = nBuf - 4 + 3; + } + } + break; + + case 'g': + if( nBuf>4 && 0==memcmp("logi", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt0(aBuf, nBuf-4) ){ + memcpy(&aBuf[nBuf-4], "log", 3); + *pnBuf = nBuf - 4 + 3; + } + } + break; + + case 'l': + if( nBuf>3 && 0==memcmp("bli", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt0(aBuf, nBuf-3) ){ + memcpy(&aBuf[nBuf-3], "ble", 3); + *pnBuf = nBuf - 3 + 3; + } + }else if( nBuf>4 && 0==memcmp("alli", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt0(aBuf, nBuf-4) ){ + memcpy(&aBuf[nBuf-4], "al", 2); + *pnBuf = nBuf - 4 + 2; + } + }else if( nBuf>5 && 0==memcmp("entli", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt0(aBuf, nBuf-5) ){ + memcpy(&aBuf[nBuf-5], "ent", 3); + *pnBuf = nBuf - 5 + 3; + } + }else if( nBuf>3 && 0==memcmp("eli", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt0(aBuf, nBuf-3) ){ + memcpy(&aBuf[nBuf-3], "e", 1); + *pnBuf = nBuf - 3 + 1; + } + }else if( nBuf>5 && 0==memcmp("ousli", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt0(aBuf, nBuf-5) ){ + memcpy(&aBuf[nBuf-5], "ous", 3); + *pnBuf = nBuf - 5 + 3; + } + } + break; + + case 'o': + if( nBuf>7 && 0==memcmp("ization", &aBuf[nBuf-7], 7) ){ + if( fts5Porter_MGt0(aBuf, nBuf-7) ){ + memcpy(&aBuf[nBuf-7], "ize", 3); + *pnBuf = nBuf - 7 + 3; + } + }else if( nBuf>5 && 0==memcmp("ation", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt0(aBuf, nBuf-5) ){ + memcpy(&aBuf[nBuf-5], "ate", 3); + *pnBuf = nBuf - 5 + 3; + } + }else if( nBuf>4 && 0==memcmp("ator", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt0(aBuf, nBuf-4) ){ + memcpy(&aBuf[nBuf-4], "ate", 3); + *pnBuf = nBuf - 4 + 3; + } + } + break; + + case 's': + if( nBuf>5 && 0==memcmp("alism", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt0(aBuf, nBuf-5) ){ + memcpy(&aBuf[nBuf-5], "al", 2); + *pnBuf = nBuf - 5 + 2; + } + }else if( nBuf>7 && 0==memcmp("iveness", &aBuf[nBuf-7], 7) ){ + if( fts5Porter_MGt0(aBuf, nBuf-7) ){ + memcpy(&aBuf[nBuf-7], "ive", 3); + *pnBuf = nBuf - 7 + 3; + } + }else if( nBuf>7 && 0==memcmp("fulness", &aBuf[nBuf-7], 7) ){ + if( fts5Porter_MGt0(aBuf, nBuf-7) ){ + memcpy(&aBuf[nBuf-7], "ful", 3); + *pnBuf = nBuf - 7 + 3; + } + }else if( nBuf>7 && 0==memcmp("ousness", &aBuf[nBuf-7], 7) ){ + if( fts5Porter_MGt0(aBuf, nBuf-7) ){ + memcpy(&aBuf[nBuf-7], "ous", 3); + *pnBuf = nBuf - 7 + 3; + } + } + break; + + case 't': + if( nBuf>5 && 0==memcmp("aliti", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt0(aBuf, nBuf-5) ){ + memcpy(&aBuf[nBuf-5], "al", 2); + *pnBuf = nBuf - 5 + 2; + } + }else if( nBuf>5 && 0==memcmp("iviti", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt0(aBuf, nBuf-5) ){ + memcpy(&aBuf[nBuf-5], "ive", 3); + *pnBuf = nBuf - 5 + 3; + } + }else if( nBuf>6 && 0==memcmp("biliti", &aBuf[nBuf-6], 6) ){ + if( fts5Porter_MGt0(aBuf, nBuf-6) ){ + memcpy(&aBuf[nBuf-6], "ble", 3); + *pnBuf = nBuf - 6 + 3; + } + } + break; + + } + return ret; +} + + +static int fts5PorterStep3(char *aBuf, int *pnBuf){ + int ret = 0; + int nBuf = *pnBuf; + switch( aBuf[nBuf-2] ){ + + case 'a': + if( nBuf>4 && 0==memcmp("ical", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt0(aBuf, nBuf-4) ){ + memcpy(&aBuf[nBuf-4], "ic", 2); + *pnBuf = nBuf - 4 + 2; + } + } + break; + + case 's': + if( nBuf>4 && 0==memcmp("ness", &aBuf[nBuf-4], 4) ){ + if( fts5Porter_MGt0(aBuf, nBuf-4) ){ + *pnBuf = nBuf - 4; + } + } + break; + + case 't': + if( nBuf>5 && 0==memcmp("icate", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt0(aBuf, nBuf-5) ){ + memcpy(&aBuf[nBuf-5], "ic", 2); + *pnBuf = nBuf - 5 + 2; + } + }else if( nBuf>5 && 0==memcmp("iciti", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt0(aBuf, nBuf-5) ){ + memcpy(&aBuf[nBuf-5], "ic", 2); + *pnBuf = nBuf - 5 + 2; + } + } + break; + + case 'u': + if( nBuf>3 && 0==memcmp("ful", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt0(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + } + } + break; + + case 'v': + if( nBuf>5 && 0==memcmp("ative", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt0(aBuf, nBuf-5) ){ + *pnBuf = nBuf - 5; + } + } + break; + + case 'z': + if( nBuf>5 && 0==memcmp("alize", &aBuf[nBuf-5], 5) ){ + if( fts5Porter_MGt0(aBuf, nBuf-5) ){ + memcpy(&aBuf[nBuf-5], "al", 2); + *pnBuf = nBuf - 5 + 2; + } + } + break; + + } + return ret; +} + + +static int fts5PorterStep1B(char *aBuf, int *pnBuf){ + int ret = 0; + int nBuf = *pnBuf; + switch( aBuf[nBuf-2] ){ + + case 'e': + if( nBuf>3 && 0==memcmp("eed", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_MGt0(aBuf, nBuf-3) ){ + memcpy(&aBuf[nBuf-3], "ee", 2); + *pnBuf = nBuf - 3 + 2; + } + }else if( nBuf>2 && 0==memcmp("ed", &aBuf[nBuf-2], 2) ){ + if( fts5Porter_Vowel(aBuf, nBuf-2) ){ + *pnBuf = nBuf - 2; + ret = 1; + } + } + break; + + case 'n': + if( nBuf>3 && 0==memcmp("ing", &aBuf[nBuf-3], 3) ){ + if( fts5Porter_Vowel(aBuf, nBuf-3) ){ + *pnBuf = nBuf - 3; + ret = 1; + } + } + break; + + } + return ret; +} + +/* +** GENERATED CODE ENDS HERE (mkportersteps.tcl) +*************************************************************************** +**************************************************************************/ + +static void fts5PorterStep1A(char *aBuf, int *pnBuf){ + int nBuf = *pnBuf; + if( aBuf[nBuf-1]=='s' ){ + if( aBuf[nBuf-2]=='e' ){ + if( (nBuf>4 && aBuf[nBuf-4]=='s' && aBuf[nBuf-3]=='s') + || (nBuf>3 && aBuf[nBuf-3]=='i' ) + ){ + *pnBuf = nBuf-2; + }else{ + *pnBuf = nBuf-1; + } + } + else if( aBuf[nBuf-2]!='s' ){ + *pnBuf = nBuf-1; + } + } +} + +static int fts5PorterCb( + void *pCtx, + int tflags, + const char *pToken, + int nToken, + int iStart, + int iEnd +){ + PorterContext *p = (PorterContext*)pCtx; + + char *aBuf; + int nBuf; + + if( nToken>FTS5_PORTER_MAX_TOKEN || nToken<3 ) goto pass_through; + aBuf = p->aBuf; + nBuf = nToken; + memcpy(aBuf, pToken, nBuf); + + /* Step 1. */ + fts5PorterStep1A(aBuf, &nBuf); + if( fts5PorterStep1B(aBuf, &nBuf) ){ + if( fts5PorterStep1B2(aBuf, &nBuf)==0 ){ + char c = aBuf[nBuf-1]; + if( fts5PorterIsVowel(c, 0)==0 + && c!='l' && c!='s' && c!='z' && c==aBuf[nBuf-2] + ){ + nBuf--; + }else if( fts5Porter_MEq1(aBuf, nBuf) && fts5Porter_Ostar(aBuf, nBuf) ){ + aBuf[nBuf++] = 'e'; + } + } + } + + /* Step 1C. */ + if( aBuf[nBuf-1]=='y' && fts5Porter_Vowel(aBuf, nBuf-1) ){ + aBuf[nBuf-1] = 'i'; + } + + /* Steps 2 through 4. */ + fts5PorterStep2(aBuf, &nBuf); + fts5PorterStep3(aBuf, &nBuf); + fts5PorterStep4(aBuf, &nBuf); + + /* Step 5a. */ + assert( nBuf>0 ); + if( aBuf[nBuf-1]=='e' ){ + if( fts5Porter_MGt1(aBuf, nBuf-1) + || (fts5Porter_MEq1(aBuf, nBuf-1) && !fts5Porter_Ostar(aBuf, nBuf-1)) + ){ + nBuf--; + } + } + + /* Step 5b. */ + if( nBuf>1 && aBuf[nBuf-1]=='l' + && aBuf[nBuf-2]=='l' && fts5Porter_MGt1(aBuf, nBuf-1) + ){ + nBuf--; + } + + return p->xToken(p->pCtx, tflags, aBuf, nBuf, iStart, iEnd); + + pass_through: + return p->xToken(p->pCtx, tflags, pToken, nToken, iStart, iEnd); +} + +/* +** Tokenize using the porter tokenizer. +*/ +static int fts5PorterTokenize( + Fts5Tokenizer *pTokenizer, + void *pCtx, + int flags, + const char *pText, int nText, + int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd) +){ + PorterTokenizer *p = (PorterTokenizer*)pTokenizer; + PorterContext sCtx; + sCtx.xToken = xToken; + sCtx.pCtx = pCtx; + sCtx.aBuf = p->aBuf; + return p->tokenizer.xTokenize( + p->pTokenizer, (void*)&sCtx, flags, pText, nText, fts5PorterCb + ); +} + +/************************************************************************** +** Start of trigram implementation. +*/ +typedef struct TrigramTokenizer TrigramTokenizer; +struct TrigramTokenizer { + int bFold; /* True to fold to lower-case */ +}; + +/* +** Free a trigram tokenizer. +*/ +static void fts5TriDelete(Fts5Tokenizer *p){ + sqlite3_free(p); +} + +/* +** Allocate a trigram tokenizer. +*/ +static int fts5TriCreate( + void *pUnused, + const char **azArg, + int nArg, + Fts5Tokenizer **ppOut +){ + int rc = SQLITE_OK; + TrigramTokenizer *pNew = (TrigramTokenizer*)sqlite3_malloc(sizeof(*pNew)); + UNUSED_PARAM(pUnused); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + int i; + pNew->bFold = 1; + for(i=0; rc==SQLITE_OK && i<nArg; i+=2){ + const char *zArg = azArg[i+1]; + if( 0==sqlite3_stricmp(azArg[i], "case_sensitive") ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1] ){ + rc = SQLITE_ERROR; + }else{ + pNew->bFold = (zArg[0]=='0'); + } + }else{ + rc = SQLITE_ERROR; + } + } + if( rc!=SQLITE_OK ){ + fts5TriDelete((Fts5Tokenizer*)pNew); + pNew = 0; + } + } + *ppOut = (Fts5Tokenizer*)pNew; + return rc; +} + +/* +** Trigram tokenizer tokenize routine. +*/ +static int fts5TriTokenize( + Fts5Tokenizer *pTok, + void *pCtx, + int unusedFlags, + const char *pText, int nText, + int (*xToken)(void*, int, const char*, int, int, int) +){ + TrigramTokenizer *p = (TrigramTokenizer*)pTok; + int rc = SQLITE_OK; + char aBuf[32]; + const unsigned char *zIn = (const unsigned char*)pText; + const unsigned char *zEof = &zIn[nText]; + u32 iCode; + + UNUSED_PARAM(unusedFlags); + while( 1 ){ + char *zOut = aBuf; + int iStart = zIn - (const unsigned char*)pText; + const unsigned char *zNext; + + READ_UTF8(zIn, zEof, iCode); + if( iCode==0 ) break; + zNext = zIn; + if( zIn<zEof ){ + if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0); + WRITE_UTF8(zOut, iCode); + READ_UTF8(zIn, zEof, iCode); + if( iCode==0 ) break; + }else{ + break; + } + if( zIn<zEof ){ + if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0); + WRITE_UTF8(zOut, iCode); + READ_UTF8(zIn, zEof, iCode); + if( iCode==0 ) break; + if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0); + WRITE_UTF8(zOut, iCode); + }else{ + break; + } + rc = xToken(pCtx, 0, aBuf, zOut-aBuf, iStart, iStart + zOut-aBuf); + if( rc!=SQLITE_OK ) break; + zIn = zNext; + } + + return rc; +} + +/* +** Argument xCreate is a pointer to a constructor function for a tokenizer. +** pTok is a tokenizer previously created using the same method. This function +** returns one of FTS5_PATTERN_NONE, FTS5_PATTERN_LIKE or FTS5_PATTERN_GLOB +** indicating the style of pattern matching that the tokenizer can support. +** In practice, this is: +** +** "trigram" tokenizer, case_sensitive=1 - FTS5_PATTERN_GLOB +** "trigram" tokenizer, case_sensitive=0 (the default) - FTS5_PATTERN_LIKE +** all other tokenizers - FTS5_PATTERN_NONE +*/ +static int sqlite3Fts5TokenizerPattern( + int (*xCreate)(void*, const char**, int, Fts5Tokenizer**), + Fts5Tokenizer *pTok +){ + if( xCreate==fts5TriCreate ){ + TrigramTokenizer *p = (TrigramTokenizer*)pTok; + return p->bFold ? FTS5_PATTERN_LIKE : FTS5_PATTERN_GLOB; + } + return FTS5_PATTERN_NONE; +} + +/* +** Register all built-in tokenizers with FTS5. +*/ +static int sqlite3Fts5TokenizerInit(fts5_api *pApi){ + struct BuiltinTokenizer { + const char *zName; + fts5_tokenizer x; + } aBuiltin[] = { + { "unicode61", {fts5UnicodeCreate, fts5UnicodeDelete, fts5UnicodeTokenize}}, + { "ascii", {fts5AsciiCreate, fts5AsciiDelete, fts5AsciiTokenize }}, + { "porter", {fts5PorterCreate, fts5PorterDelete, fts5PorterTokenize }}, + { "trigram", {fts5TriCreate, fts5TriDelete, fts5TriTokenize}}, + }; + + int rc = SQLITE_OK; /* Return code */ + int i; /* To iterate through builtin functions */ + + for(i=0; rc==SQLITE_OK && i<ArraySize(aBuiltin); i++){ + rc = pApi->xCreateTokenizer(pApi, + aBuiltin[i].zName, + (void*)pApi, + &aBuiltin[i].x, + 0 + ); + } + + return rc; +} + +/* +** 2012-05-25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +*/ + +/* +** DO NOT EDIT THIS MACHINE GENERATED FILE. +*/ + + +/* #include <assert.h> */ + + + +/* +** If the argument is a codepoint corresponding to a lowercase letter +** in the ASCII range with a diacritic added, return the codepoint +** of the ASCII letter only. For example, if passed 235 - "LATIN +** SMALL LETTER E WITH DIAERESIS" - return 65 ("LATIN SMALL LETTER +** E"). The resuls of passing a codepoint that corresponds to an +** uppercase letter are undefined. +*/ +static int fts5_remove_diacritic(int c, int bComplex){ + unsigned short aDia[] = { + 0, 1797, 1848, 1859, 1891, 1928, 1940, 1995, + 2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286, + 2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732, + 2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336, + 3456, 3696, 3712, 3728, 3744, 3766, 3832, 3896, + 3912, 3928, 3944, 3968, 4008, 4040, 4056, 4106, + 4138, 4170, 4202, 4234, 4266, 4296, 4312, 4344, + 4408, 4424, 4442, 4472, 4488, 4504, 6148, 6198, + 6264, 6280, 6360, 6429, 6505, 6529, 61448, 61468, + 61512, 61534, 61592, 61610, 61642, 61672, 61688, 61704, + 61726, 61784, 61800, 61816, 61836, 61880, 61896, 61914, + 61948, 61998, 62062, 62122, 62154, 62184, 62200, 62218, + 62252, 62302, 62364, 62410, 62442, 62478, 62536, 62554, + 62584, 62604, 62640, 62648, 62656, 62664, 62730, 62766, + 62830, 62890, 62924, 62974, 63032, 63050, 63082, 63118, + 63182, 63242, 63274, 63310, 63368, 63390, + }; +#define HIBIT ((unsigned char)0x80) + unsigned char aChar[] = { + '\0', 'a', 'c', 'e', 'i', 'n', + 'o', 'u', 'y', 'y', 'a', 'c', + 'd', 'e', 'e', 'g', 'h', 'i', + 'j', 'k', 'l', 'n', 'o', 'r', + 's', 't', 'u', 'u', 'w', 'y', + 'z', 'o', 'u', 'a', 'i', 'o', + 'u', 'u'|HIBIT, 'a'|HIBIT, 'g', 'k', 'o', + 'o'|HIBIT, 'j', 'g', 'n', 'a'|HIBIT, 'a', + 'e', 'i', 'o', 'r', 'u', 's', + 't', 'h', 'a', 'e', 'o'|HIBIT, 'o', + 'o'|HIBIT, 'y', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', 'a', 'b', + 'c'|HIBIT, 'd', 'd', 'e'|HIBIT, 'e', 'e'|HIBIT, + 'f', 'g', 'h', 'h', 'i', 'i'|HIBIT, + 'k', 'l', 'l'|HIBIT, 'l', 'm', 'n', + 'o'|HIBIT, 'p', 'r', 'r'|HIBIT, 'r', 's', + 's'|HIBIT, 't', 'u', 'u'|HIBIT, 'v', 'w', + 'w', 'x', 'y', 'z', 'h', 't', + 'w', 'y', 'a', 'a'|HIBIT, 'a'|HIBIT, 'a'|HIBIT, + 'e', 'e'|HIBIT, 'e'|HIBIT, 'i', 'o', 'o'|HIBIT, + 'o'|HIBIT, 'o'|HIBIT, 'u', 'u'|HIBIT, 'u'|HIBIT, 'y', + }; + + unsigned int key = (((unsigned int)c)<<3) | 0x00000007; + int iRes = 0; + int iHi = sizeof(aDia)/sizeof(aDia[0]) - 1; + int iLo = 0; + while( iHi>=iLo ){ + int iTest = (iHi + iLo) / 2; + if( key >= aDia[iTest] ){ + iRes = iTest; + iLo = iTest+1; + }else{ + iHi = iTest-1; + } + } + assert( key>=aDia[iRes] ); + if( bComplex==0 && (aChar[iRes] & 0x80) ) return c; + return (c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : ((int)aChar[iRes] & 0x7F); +} + + +/* +** Return true if the argument interpreted as a unicode codepoint +** is a diacritical modifier character. +*/ +static int sqlite3Fts5UnicodeIsdiacritic(int c){ + unsigned int mask0 = 0x08029FDF; + unsigned int mask1 = 0x000361F8; + if( c<768 || c>817 ) return 0; + return (c < 768+32) ? + (mask0 & ((unsigned int)1 << (c-768))) : + (mask1 & ((unsigned int)1 << (c-768-32))); +} + + +/* +** Interpret the argument as a unicode codepoint. If the codepoint +** is an upper case character that has a lower case equivalent, +** return the codepoint corresponding to the lower case version. +** Otherwise, return a copy of the argument. +** +** The results are undefined if the value passed to this function +** is less than zero. +*/ +static int sqlite3Fts5UnicodeFold(int c, int eRemoveDiacritic){ + /* Each entry in the following array defines a rule for folding a range + ** of codepoints to lower case. The rule applies to a range of nRange + ** codepoints starting at codepoint iCode. + ** + ** If the least significant bit in flags is clear, then the rule applies + ** to all nRange codepoints (i.e. all nRange codepoints are upper case and + ** need to be folded). Or, if it is set, then the rule only applies to + ** every second codepoint in the range, starting with codepoint C. + ** + ** The 7 most significant bits in flags are an index into the aiOff[] + ** array. If a specific codepoint C does require folding, then its lower + ** case equivalent is ((C + aiOff[flags>>1]) & 0xFFFF). + ** + ** The contents of this array are generated by parsing the CaseFolding.txt + ** file distributed as part of the "Unicode Character Database". See + ** http://www.unicode.org for details. + */ + static const struct TableEntry { + unsigned short iCode; + unsigned char flags; + unsigned char nRange; + } aEntry[] = { + {65, 14, 26}, {181, 64, 1}, {192, 14, 23}, + {216, 14, 7}, {256, 1, 48}, {306, 1, 6}, + {313, 1, 16}, {330, 1, 46}, {376, 116, 1}, + {377, 1, 6}, {383, 104, 1}, {385, 50, 1}, + {386, 1, 4}, {390, 44, 1}, {391, 0, 1}, + {393, 42, 2}, {395, 0, 1}, {398, 32, 1}, + {399, 38, 1}, {400, 40, 1}, {401, 0, 1}, + {403, 42, 1}, {404, 46, 1}, {406, 52, 1}, + {407, 48, 1}, {408, 0, 1}, {412, 52, 1}, + {413, 54, 1}, {415, 56, 1}, {416, 1, 6}, + {422, 60, 1}, {423, 0, 1}, {425, 60, 1}, + {428, 0, 1}, {430, 60, 1}, {431, 0, 1}, + {433, 58, 2}, {435, 1, 4}, {439, 62, 1}, + {440, 0, 1}, {444, 0, 1}, {452, 2, 1}, + {453, 0, 1}, {455, 2, 1}, {456, 0, 1}, + {458, 2, 1}, {459, 1, 18}, {478, 1, 18}, + {497, 2, 1}, {498, 1, 4}, {502, 122, 1}, + {503, 134, 1}, {504, 1, 40}, {544, 110, 1}, + {546, 1, 18}, {570, 70, 1}, {571, 0, 1}, + {573, 108, 1}, {574, 68, 1}, {577, 0, 1}, + {579, 106, 1}, {580, 28, 1}, {581, 30, 1}, + {582, 1, 10}, {837, 36, 1}, {880, 1, 4}, + {886, 0, 1}, {902, 18, 1}, {904, 16, 3}, + {908, 26, 1}, {910, 24, 2}, {913, 14, 17}, + {931, 14, 9}, {962, 0, 1}, {975, 4, 1}, + {976, 140, 1}, {977, 142, 1}, {981, 146, 1}, + {982, 144, 1}, {984, 1, 24}, {1008, 136, 1}, + {1009, 138, 1}, {1012, 130, 1}, {1013, 128, 1}, + {1015, 0, 1}, {1017, 152, 1}, {1018, 0, 1}, + {1021, 110, 3}, {1024, 34, 16}, {1040, 14, 32}, + {1120, 1, 34}, {1162, 1, 54}, {1216, 6, 1}, + {1217, 1, 14}, {1232, 1, 88}, {1329, 22, 38}, + {4256, 66, 38}, {4295, 66, 1}, {4301, 66, 1}, + {7680, 1, 150}, {7835, 132, 1}, {7838, 96, 1}, + {7840, 1, 96}, {7944, 150, 8}, {7960, 150, 6}, + {7976, 150, 8}, {7992, 150, 8}, {8008, 150, 6}, + {8025, 151, 8}, {8040, 150, 8}, {8072, 150, 8}, + {8088, 150, 8}, {8104, 150, 8}, {8120, 150, 2}, + {8122, 126, 2}, {8124, 148, 1}, {8126, 100, 1}, + {8136, 124, 4}, {8140, 148, 1}, {8152, 150, 2}, + {8154, 120, 2}, {8168, 150, 2}, {8170, 118, 2}, + {8172, 152, 1}, {8184, 112, 2}, {8186, 114, 2}, + {8188, 148, 1}, {8486, 98, 1}, {8490, 92, 1}, + {8491, 94, 1}, {8498, 12, 1}, {8544, 8, 16}, + {8579, 0, 1}, {9398, 10, 26}, {11264, 22, 47}, + {11360, 0, 1}, {11362, 88, 1}, {11363, 102, 1}, + {11364, 90, 1}, {11367, 1, 6}, {11373, 84, 1}, + {11374, 86, 1}, {11375, 80, 1}, {11376, 82, 1}, + {11378, 0, 1}, {11381, 0, 1}, {11390, 78, 2}, + {11392, 1, 100}, {11499, 1, 4}, {11506, 0, 1}, + {42560, 1, 46}, {42624, 1, 24}, {42786, 1, 14}, + {42802, 1, 62}, {42873, 1, 4}, {42877, 76, 1}, + {42878, 1, 10}, {42891, 0, 1}, {42893, 74, 1}, + {42896, 1, 4}, {42912, 1, 10}, {42922, 72, 1}, + {65313, 14, 26}, + }; + static const unsigned short aiOff[] = { + 1, 2, 8, 15, 16, 26, 28, 32, + 37, 38, 40, 48, 63, 64, 69, 71, + 79, 80, 116, 202, 203, 205, 206, 207, + 209, 210, 211, 213, 214, 217, 218, 219, + 775, 7264, 10792, 10795, 23228, 23256, 30204, 54721, + 54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274, + 57921, 58019, 58363, 61722, 65268, 65341, 65373, 65406, + 65408, 65410, 65415, 65424, 65436, 65439, 65450, 65462, + 65472, 65476, 65478, 65480, 65482, 65488, 65506, 65511, + 65514, 65521, 65527, 65528, 65529, + }; + + int ret = c; + + assert( sizeof(unsigned short)==2 && sizeof(unsigned char)==1 ); + + if( c<128 ){ + if( c>='A' && c<='Z' ) ret = c + ('a' - 'A'); + }else if( c<65536 ){ + const struct TableEntry *p; + int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1; + int iLo = 0; + int iRes = -1; + + assert( c>aEntry[0].iCode ); + while( iHi>=iLo ){ + int iTest = (iHi + iLo) / 2; + int cmp = (c - aEntry[iTest].iCode); + if( cmp>=0 ){ + iRes = iTest; + iLo = iTest+1; + }else{ + iHi = iTest-1; + } + } + + assert( iRes>=0 && c>=aEntry[iRes].iCode ); + p = &aEntry[iRes]; + if( c<(p->iCode + p->nRange) && 0==(0x01 & p->flags & (p->iCode ^ c)) ){ + ret = (c + (aiOff[p->flags>>1])) & 0x0000FFFF; + assert( ret>0 ); + } + + if( eRemoveDiacritic ){ + ret = fts5_remove_diacritic(ret, eRemoveDiacritic==2); + } + } + + else if( c>=66560 && c<66600 ){ + ret = c + 40; + } + + return ret; +} + + +static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ + aArray[0] = 1; + switch( zCat[0] ){ + case 'C': + switch( zCat[1] ){ + case 'c': aArray[1] = 1; break; + case 'f': aArray[2] = 1; break; + case 'n': aArray[3] = 1; break; + case 's': aArray[4] = 1; break; + case 'o': aArray[31] = 1; break; + case '*': + aArray[1] = 1; + aArray[2] = 1; + aArray[3] = 1; + aArray[4] = 1; + aArray[31] = 1; + break; + default: return 1; } + break; + + case 'L': + switch( zCat[1] ){ + case 'l': aArray[5] = 1; break; + case 'm': aArray[6] = 1; break; + case 'o': aArray[7] = 1; break; + case 't': aArray[8] = 1; break; + case 'u': aArray[9] = 1; break; + case 'C': aArray[30] = 1; break; + case '*': + aArray[5] = 1; + aArray[6] = 1; + aArray[7] = 1; + aArray[8] = 1; + aArray[9] = 1; + aArray[30] = 1; + break; + default: return 1; } + break; + + case 'M': + switch( zCat[1] ){ + case 'c': aArray[10] = 1; break; + case 'e': aArray[11] = 1; break; + case 'n': aArray[12] = 1; break; + case '*': + aArray[10] = 1; + aArray[11] = 1; + aArray[12] = 1; + break; + default: return 1; } + break; + + case 'N': + switch( zCat[1] ){ + case 'd': aArray[13] = 1; break; + case 'l': aArray[14] = 1; break; + case 'o': aArray[15] = 1; break; + case '*': + aArray[13] = 1; + aArray[14] = 1; + aArray[15] = 1; + break; + default: return 1; } + break; + + case 'P': + switch( zCat[1] ){ + case 'c': aArray[16] = 1; break; + case 'd': aArray[17] = 1; break; + case 'e': aArray[18] = 1; break; + case 'f': aArray[19] = 1; break; + case 'i': aArray[20] = 1; break; + case 'o': aArray[21] = 1; break; + case 's': aArray[22] = 1; break; + case '*': + aArray[16] = 1; + aArray[17] = 1; + aArray[18] = 1; + aArray[19] = 1; + aArray[20] = 1; + aArray[21] = 1; + aArray[22] = 1; + break; + default: return 1; } + break; + + case 'S': + switch( zCat[1] ){ + case 'c': aArray[23] = 1; break; + case 'k': aArray[24] = 1; break; + case 'm': aArray[25] = 1; break; + case 'o': aArray[26] = 1; break; + case '*': + aArray[23] = 1; + aArray[24] = 1; + aArray[25] = 1; + aArray[26] = 1; + break; + default: return 1; } + break; + + case 'Z': + switch( zCat[1] ){ + case 'l': aArray[27] = 1; break; + case 'p': aArray[28] = 1; break; + case 's': aArray[29] = 1; break; + case '*': + aArray[27] = 1; + aArray[28] = 1; + aArray[29] = 1; + break; + default: return 1; } + break; + + } + return 0; +} + +static u16 aFts5UnicodeBlock[] = { + 0, 1471, 1753, 1760, 1760, 1760, 1760, 1760, 1760, 1760, + 1760, 1760, 1760, 1760, 1760, 1763, 1765, + }; +static u16 aFts5UnicodeMap[] = { + 0, 32, 33, 36, 37, 40, 41, 42, 43, 44, + 45, 46, 48, 58, 60, 63, 65, 91, 92, 93, + 94, 95, 96, 97, 123, 124, 125, 126, 127, 160, + 161, 162, 166, 167, 168, 169, 170, 171, 172, 173, + 174, 175, 176, 177, 178, 180, 181, 182, 184, 185, + 186, 187, 188, 191, 192, 215, 216, 223, 247, 248, + 256, 312, 313, 329, 330, 377, 383, 385, 387, 388, + 391, 394, 396, 398, 402, 403, 405, 406, 409, 412, + 414, 415, 417, 418, 423, 427, 428, 431, 434, 436, + 437, 440, 442, 443, 444, 446, 448, 452, 453, 454, + 455, 456, 457, 458, 459, 460, 461, 477, 478, 496, + 497, 498, 499, 500, 503, 505, 506, 564, 570, 572, + 573, 575, 577, 580, 583, 584, 592, 660, 661, 688, + 706, 710, 722, 736, 741, 748, 749, 750, 751, 768, + 880, 884, 885, 886, 890, 891, 894, 900, 902, 903, + 904, 908, 910, 912, 913, 931, 940, 975, 977, 978, + 981, 984, 1008, 1012, 1014, 1015, 1018, 1020, 1021, 1072, + 1120, 1154, 1155, 1160, 1162, 1217, 1231, 1232, 1329, 1369, + 1370, 1377, 1417, 1418, 1423, 1425, 1470, 1471, 1472, 1473, + 1475, 1476, 1478, 1479, 1488, 1520, 1523, 1536, 1542, 1545, + 1547, 1548, 1550, 1552, 1563, 1566, 1568, 1600, 1601, 1611, + 1632, 1642, 1646, 1648, 1649, 1748, 1749, 1750, 1757, 1758, + 1759, 1765, 1767, 1769, 1770, 1774, 1776, 1786, 1789, 1791, + 1792, 1807, 1808, 1809, 1810, 1840, 1869, 1958, 1969, 1984, + 1994, 2027, 2036, 2038, 2039, 2042, 2048, 2070, 2074, 2075, + 2084, 2085, 2088, 2089, 2096, 2112, 2137, 2142, 2208, 2210, + 2276, 2304, 2307, 2308, 2362, 2363, 2364, 2365, 2366, 2369, + 2377, 2381, 2382, 2384, 2385, 2392, 2402, 2404, 2406, 2416, + 2417, 2418, 2425, 2433, 2434, 2437, 2447, 2451, 2474, 2482, + 2486, 2492, 2493, 2494, 2497, 2503, 2507, 2509, 2510, 2519, + 2524, 2527, 2530, 2534, 2544, 2546, 2548, 2554, 2555, 2561, + 2563, 2565, 2575, 2579, 2602, 2610, 2613, 2616, 2620, 2622, + 2625, 2631, 2635, 2641, 2649, 2654, 2662, 2672, 2674, 2677, + 2689, 2691, 2693, 2703, 2707, 2730, 2738, 2741, 2748, 2749, + 2750, 2753, 2759, 2761, 2763, 2765, 2768, 2784, 2786, 2790, + 2800, 2801, 2817, 2818, 2821, 2831, 2835, 2858, 2866, 2869, + 2876, 2877, 2878, 2879, 2880, 2881, 2887, 2891, 2893, 2902, + 2903, 2908, 2911, 2914, 2918, 2928, 2929, 2930, 2946, 2947, + 2949, 2958, 2962, 2969, 2972, 2974, 2979, 2984, 2990, 3006, + 3008, 3009, 3014, 3018, 3021, 3024, 3031, 3046, 3056, 3059, + 3065, 3066, 3073, 3077, 3086, 3090, 3114, 3125, 3133, 3134, + 3137, 3142, 3146, 3157, 3160, 3168, 3170, 3174, 3192, 3199, + 3202, 3205, 3214, 3218, 3242, 3253, 3260, 3261, 3262, 3263, + 3264, 3270, 3271, 3274, 3276, 3285, 3294, 3296, 3298, 3302, + 3313, 3330, 3333, 3342, 3346, 3389, 3390, 3393, 3398, 3402, + 3405, 3406, 3415, 3424, 3426, 3430, 3440, 3449, 3450, 3458, + 3461, 3482, 3507, 3517, 3520, 3530, 3535, 3538, 3542, 3544, + 3570, 3572, 3585, 3633, 3634, 3636, 3647, 3648, 3654, 3655, + 3663, 3664, 3674, 3713, 3716, 3719, 3722, 3725, 3732, 3737, + 3745, 3749, 3751, 3754, 3757, 3761, 3762, 3764, 3771, 3773, + 3776, 3782, 3784, 3792, 3804, 3840, 3841, 3844, 3859, 3860, + 3861, 3864, 3866, 3872, 3882, 3892, 3893, 3894, 3895, 3896, + 3897, 3898, 3899, 3900, 3901, 3902, 3904, 3913, 3953, 3967, + 3968, 3973, 3974, 3976, 3981, 3993, 4030, 4038, 4039, 4046, + 4048, 4053, 4057, 4096, 4139, 4141, 4145, 4146, 4152, 4153, + 4155, 4157, 4159, 4160, 4170, 4176, 4182, 4184, 4186, 4190, + 4193, 4194, 4197, 4199, 4206, 4209, 4213, 4226, 4227, 4229, + 4231, 4237, 4238, 4239, 4240, 4250, 4253, 4254, 4256, 4295, + 4301, 4304, 4347, 4348, 4349, 4682, 4688, 4696, 4698, 4704, + 4746, 4752, 4786, 4792, 4800, 4802, 4808, 4824, 4882, 4888, + 4957, 4960, 4969, 4992, 5008, 5024, 5120, 5121, 5741, 5743, + 5760, 5761, 5787, 5788, 5792, 5867, 5870, 5888, 5902, 5906, + 5920, 5938, 5941, 5952, 5970, 5984, 5998, 6002, 6016, 6068, + 6070, 6071, 6078, 6086, 6087, 6089, 6100, 6103, 6104, 6107, + 6108, 6109, 6112, 6128, 6144, 6150, 6151, 6155, 6158, 6160, + 6176, 6211, 6212, 6272, 6313, 6314, 6320, 6400, 6432, 6435, + 6439, 6441, 6448, 6450, 6451, 6457, 6464, 6468, 6470, 6480, + 6512, 6528, 6576, 6593, 6600, 6608, 6618, 6622, 6656, 6679, + 6681, 6686, 6688, 6741, 6742, 6743, 6744, 6752, 6753, 6754, + 6755, 6757, 6765, 6771, 6783, 6784, 6800, 6816, 6823, 6824, + 6912, 6916, 6917, 6964, 6965, 6966, 6971, 6972, 6973, 6978, + 6979, 6981, 6992, 7002, 7009, 7019, 7028, 7040, 7042, 7043, + 7073, 7074, 7078, 7080, 7082, 7083, 7084, 7086, 7088, 7098, + 7142, 7143, 7144, 7146, 7149, 7150, 7151, 7154, 7164, 7168, + 7204, 7212, 7220, 7222, 7227, 7232, 7245, 7248, 7258, 7288, + 7294, 7360, 7376, 7379, 7380, 7393, 7394, 7401, 7405, 7406, + 7410, 7412, 7413, 7424, 7468, 7531, 7544, 7545, 7579, 7616, + 7676, 7680, 7830, 7838, 7936, 7944, 7952, 7960, 7968, 7976, + 7984, 7992, 8000, 8008, 8016, 8025, 8027, 8029, 8031, 8033, + 8040, 8048, 8064, 8072, 8080, 8088, 8096, 8104, 8112, 8118, + 8120, 8124, 8125, 8126, 8127, 8130, 8134, 8136, 8140, 8141, + 8144, 8150, 8152, 8157, 8160, 8168, 8173, 8178, 8182, 8184, + 8188, 8189, 8192, 8203, 8208, 8214, 8216, 8217, 8218, 8219, + 8221, 8222, 8223, 8224, 8232, 8233, 8234, 8239, 8240, 8249, + 8250, 8251, 8255, 8257, 8260, 8261, 8262, 8263, 8274, 8275, + 8276, 8277, 8287, 8288, 8298, 8304, 8305, 8308, 8314, 8317, + 8318, 8319, 8320, 8330, 8333, 8334, 8336, 8352, 8400, 8413, + 8417, 8418, 8421, 8448, 8450, 8451, 8455, 8456, 8458, 8459, + 8462, 8464, 8467, 8468, 8469, 8470, 8472, 8473, 8478, 8484, + 8485, 8486, 8487, 8488, 8489, 8490, 8494, 8495, 8496, 8500, + 8501, 8505, 8506, 8508, 8510, 8512, 8517, 8519, 8522, 8523, + 8524, 8526, 8527, 8528, 8544, 8579, 8581, 8585, 8592, 8597, + 8602, 8604, 8608, 8609, 8611, 8612, 8614, 8615, 8622, 8623, + 8654, 8656, 8658, 8659, 8660, 8661, 8692, 8960, 8968, 8972, + 8992, 8994, 9001, 9002, 9003, 9084, 9085, 9115, 9140, 9180, + 9186, 9216, 9280, 9312, 9372, 9450, 9472, 9655, 9656, 9665, + 9666, 9720, 9728, 9839, 9840, 9985, 10088, 10089, 10090, 10091, + 10092, 10093, 10094, 10095, 10096, 10097, 10098, 10099, 10100, 10101, + 10102, 10132, 10176, 10181, 10182, 10183, 10214, 10215, 10216, 10217, + 10218, 10219, 10220, 10221, 10222, 10223, 10224, 10240, 10496, 10627, + 10628, 10629, 10630, 10631, 10632, 10633, 10634, 10635, 10636, 10637, + 10638, 10639, 10640, 10641, 10642, 10643, 10644, 10645, 10646, 10647, + 10648, 10649, 10712, 10713, 10714, 10715, 10716, 10748, 10749, 10750, + 11008, 11056, 11077, 11079, 11088, 11264, 11312, 11360, 11363, 11365, + 11367, 11374, 11377, 11378, 11380, 11381, 11383, 11388, 11390, 11393, + 11394, 11492, 11493, 11499, 11503, 11506, 11513, 11517, 11518, 11520, + 11559, 11565, 11568, 11631, 11632, 11647, 11648, 11680, 11688, 11696, + 11704, 11712, 11720, 11728, 11736, 11744, 11776, 11778, 11779, 11780, + 11781, 11782, 11785, 11786, 11787, 11788, 11789, 11790, 11799, 11800, + 11802, 11803, 11804, 11805, 11806, 11808, 11809, 11810, 11811, 11812, + 11813, 11814, 11815, 11816, 11817, 11818, 11823, 11824, 11834, 11904, + 11931, 12032, 12272, 12288, 12289, 12292, 12293, 12294, 12295, 12296, + 12297, 12298, 12299, 12300, 12301, 12302, 12303, 12304, 12305, 12306, + 12308, 12309, 12310, 12311, 12312, 12313, 12314, 12315, 12316, 12317, + 12318, 12320, 12321, 12330, 12334, 12336, 12337, 12342, 12344, 12347, + 12348, 12349, 12350, 12353, 12441, 12443, 12445, 12447, 12448, 12449, + 12539, 12540, 12543, 12549, 12593, 12688, 12690, 12694, 12704, 12736, + 12784, 12800, 12832, 12842, 12872, 12880, 12881, 12896, 12928, 12938, + 12977, 12992, 13056, 13312, 19893, 19904, 19968, 40908, 40960, 40981, + 40982, 42128, 42192, 42232, 42238, 42240, 42508, 42509, 42512, 42528, + 42538, 42560, 42606, 42607, 42608, 42611, 42612, 42622, 42623, 42624, + 42655, 42656, 42726, 42736, 42738, 42752, 42775, 42784, 42786, 42800, + 42802, 42864, 42865, 42873, 42878, 42888, 42889, 42891, 42896, 42912, + 43000, 43002, 43003, 43010, 43011, 43014, 43015, 43019, 43020, 43043, + 43045, 43047, 43048, 43056, 43062, 43064, 43065, 43072, 43124, 43136, + 43138, 43188, 43204, 43214, 43216, 43232, 43250, 43256, 43259, 43264, + 43274, 43302, 43310, 43312, 43335, 43346, 43359, 43360, 43392, 43395, + 43396, 43443, 43444, 43446, 43450, 43452, 43453, 43457, 43471, 43472, + 43486, 43520, 43561, 43567, 43569, 43571, 43573, 43584, 43587, 43588, + 43596, 43597, 43600, 43612, 43616, 43632, 43633, 43639, 43642, 43643, + 43648, 43696, 43697, 43698, 43701, 43703, 43705, 43710, 43712, 43713, + 43714, 43739, 43741, 43742, 43744, 43755, 43756, 43758, 43760, 43762, + 43763, 43765, 43766, 43777, 43785, 43793, 43808, 43816, 43968, 44003, + 44005, 44006, 44008, 44009, 44011, 44012, 44013, 44016, 44032, 55203, + 55216, 55243, 55296, 56191, 56319, 57343, 57344, 63743, 63744, 64112, + 64256, 64275, 64285, 64286, 64287, 64297, 64298, 64312, 64318, 64320, + 64323, 64326, 64434, 64467, 64830, 64831, 64848, 64914, 65008, 65020, + 65021, 65024, 65040, 65047, 65048, 65049, 65056, 65072, 65073, 65075, + 65077, 65078, 65079, 65080, 65081, 65082, 65083, 65084, 65085, 65086, + 65087, 65088, 65089, 65090, 65091, 65092, 65093, 65095, 65096, 65097, + 65101, 65104, 65108, 65112, 65113, 65114, 65115, 65116, 65117, 65118, + 65119, 65122, 65123, 65124, 65128, 65129, 65130, 65136, 65142, 65279, + 65281, 65284, 65285, 65288, 65289, 65290, 65291, 65292, 65293, 65294, + 65296, 65306, 65308, 65311, 65313, 65339, 65340, 65341, 65342, 65343, + 65344, 65345, 65371, 65372, 65373, 65374, 65375, 65376, 65377, 65378, + 65379, 65380, 65382, 65392, 65393, 65438, 65440, 65474, 65482, 65490, + 65498, 65504, 65506, 65507, 65508, 65509, 65512, 65513, 65517, 65529, + 65532, 0, 13, 40, 60, 63, 80, 128, 256, 263, + 311, 320, 373, 377, 394, 400, 464, 509, 640, 672, + 768, 800, 816, 833, 834, 842, 896, 927, 928, 968, + 976, 977, 1024, 1064, 1104, 1184, 2048, 2056, 2058, 2103, + 2108, 2111, 2135, 2136, 2304, 2326, 2335, 2336, 2367, 2432, + 2494, 2560, 2561, 2565, 2572, 2576, 2581, 2585, 2616, 2623, + 2624, 2640, 2656, 2685, 2687, 2816, 2873, 2880, 2904, 2912, + 2936, 3072, 3680, 4096, 4097, 4098, 4099, 4152, 4167, 4178, + 4198, 4224, 4226, 4227, 4272, 4275, 4279, 4281, 4283, 4285, + 4286, 4304, 4336, 4352, 4355, 4391, 4396, 4397, 4406, 4416, + 4480, 4482, 4483, 4531, 4534, 4543, 4545, 4549, 4560, 5760, + 5803, 5804, 5805, 5806, 5808, 5814, 5815, 5824, 8192, 9216, + 9328, 12288, 26624, 28416, 28496, 28497, 28559, 28563, 45056, 53248, + 53504, 53545, 53605, 53607, 53610, 53613, 53619, 53627, 53635, 53637, + 53644, 53674, 53678, 53760, 53826, 53829, 54016, 54112, 54272, 54298, + 54324, 54350, 54358, 54376, 54402, 54428, 54430, 54434, 54437, 54441, + 54446, 54454, 54459, 54461, 54469, 54480, 54506, 54532, 54535, 54541, + 54550, 54558, 54584, 54587, 54592, 54598, 54602, 54610, 54636, 54662, + 54688, 54714, 54740, 54766, 54792, 54818, 54844, 54870, 54896, 54922, + 54952, 54977, 54978, 55003, 55004, 55010, 55035, 55036, 55061, 55062, + 55068, 55093, 55094, 55119, 55120, 55126, 55151, 55152, 55177, 55178, + 55184, 55209, 55210, 55235, 55236, 55242, 55246, 60928, 60933, 60961, + 60964, 60967, 60969, 60980, 60985, 60987, 60994, 60999, 61001, 61003, + 61005, 61009, 61012, 61015, 61017, 61019, 61021, 61023, 61025, 61028, + 61031, 61036, 61044, 61049, 61054, 61056, 61067, 61089, 61093, 61099, + 61168, 61440, 61488, 61600, 61617, 61633, 61649, 61696, 61712, 61744, + 61808, 61926, 61968, 62016, 62032, 62208, 62256, 62263, 62336, 62368, + 62406, 62432, 62464, 62528, 62530, 62713, 62720, 62784, 62800, 62971, + 63045, 63104, 63232, 0, 42710, 42752, 46900, 46912, 47133, 63488, + 1, 32, 256, 0, 65533, + }; +static u16 aFts5UnicodeData[] = { + 1025, 61, 117, 55, 117, 54, 50, 53, 57, 53, + 49, 85, 333, 85, 121, 85, 841, 54, 53, 50, + 56, 48, 56, 837, 54, 57, 50, 57, 1057, 61, + 53, 151, 58, 53, 56, 58, 39, 52, 57, 34, + 58, 56, 58, 57, 79, 56, 37, 85, 56, 47, + 39, 51, 111, 53, 745, 57, 233, 773, 57, 261, + 1822, 37, 542, 37, 1534, 222, 69, 73, 37, 126, + 126, 73, 69, 137, 37, 73, 37, 105, 101, 73, + 37, 73, 37, 190, 158, 37, 126, 126, 73, 37, + 126, 94, 37, 39, 94, 69, 135, 41, 40, 37, + 41, 40, 37, 41, 40, 37, 542, 37, 606, 37, + 41, 40, 37, 126, 73, 37, 1886, 197, 73, 37, + 73, 69, 126, 105, 37, 286, 2181, 39, 869, 582, + 152, 390, 472, 166, 248, 38, 56, 38, 568, 3596, + 158, 38, 56, 94, 38, 101, 53, 88, 41, 53, + 105, 41, 73, 37, 553, 297, 1125, 94, 37, 105, + 101, 798, 133, 94, 57, 126, 94, 37, 1641, 1541, + 1118, 58, 172, 75, 1790, 478, 37, 2846, 1225, 38, + 213, 1253, 53, 49, 55, 1452, 49, 44, 53, 76, + 53, 76, 53, 44, 871, 103, 85, 162, 121, 85, + 55, 85, 90, 364, 53, 85, 1031, 38, 327, 684, + 333, 149, 71, 44, 3175, 53, 39, 236, 34, 58, + 204, 70, 76, 58, 140, 71, 333, 103, 90, 39, + 469, 34, 39, 44, 967, 876, 2855, 364, 39, 333, + 1063, 300, 70, 58, 117, 38, 711, 140, 38, 300, + 38, 108, 38, 172, 501, 807, 108, 53, 39, 359, + 876, 108, 42, 1735, 44, 42, 44, 39, 106, 268, + 138, 44, 74, 39, 236, 327, 76, 85, 333, 53, + 38, 199, 231, 44, 74, 263, 71, 711, 231, 39, + 135, 44, 39, 106, 140, 74, 74, 44, 39, 42, + 71, 103, 76, 333, 71, 87, 207, 58, 55, 76, + 42, 199, 71, 711, 231, 71, 71, 71, 44, 106, + 76, 76, 108, 44, 135, 39, 333, 76, 103, 44, + 76, 42, 295, 103, 711, 231, 71, 167, 44, 39, + 106, 172, 76, 42, 74, 44, 39, 71, 76, 333, + 53, 55, 44, 74, 263, 71, 711, 231, 71, 167, + 44, 39, 42, 44, 42, 140, 74, 74, 44, 44, + 42, 71, 103, 76, 333, 58, 39, 207, 44, 39, + 199, 103, 135, 71, 39, 71, 71, 103, 391, 74, + 44, 74, 106, 106, 44, 39, 42, 333, 111, 218, + 55, 58, 106, 263, 103, 743, 327, 167, 39, 108, + 138, 108, 140, 76, 71, 71, 76, 333, 239, 58, + 74, 263, 103, 743, 327, 167, 44, 39, 42, 44, + 170, 44, 74, 74, 76, 74, 39, 71, 76, 333, + 71, 74, 263, 103, 1319, 39, 106, 140, 106, 106, + 44, 39, 42, 71, 76, 333, 207, 58, 199, 74, + 583, 775, 295, 39, 231, 44, 106, 108, 44, 266, + 74, 53, 1543, 44, 71, 236, 55, 199, 38, 268, + 53, 333, 85, 71, 39, 71, 39, 39, 135, 231, + 103, 39, 39, 71, 135, 44, 71, 204, 76, 39, + 167, 38, 204, 333, 135, 39, 122, 501, 58, 53, + 122, 76, 218, 333, 335, 58, 44, 58, 44, 58, + 44, 54, 50, 54, 50, 74, 263, 1159, 460, 42, + 172, 53, 76, 167, 364, 1164, 282, 44, 218, 90, + 181, 154, 85, 1383, 74, 140, 42, 204, 42, 76, + 74, 76, 39, 333, 213, 199, 74, 76, 135, 108, + 39, 106, 71, 234, 103, 140, 423, 44, 74, 76, + 202, 44, 39, 42, 333, 106, 44, 90, 1225, 41, + 41, 1383, 53, 38, 10631, 135, 231, 39, 135, 1319, + 135, 1063, 135, 231, 39, 135, 487, 1831, 135, 2151, + 108, 309, 655, 519, 346, 2727, 49, 19847, 85, 551, + 61, 839, 54, 50, 2407, 117, 110, 423, 135, 108, + 583, 108, 85, 583, 76, 423, 103, 76, 1671, 76, + 42, 236, 266, 44, 74, 364, 117, 38, 117, 55, + 39, 44, 333, 335, 213, 49, 149, 108, 61, 333, + 1127, 38, 1671, 1319, 44, 39, 2247, 935, 108, 138, + 76, 106, 74, 44, 202, 108, 58, 85, 333, 967, + 167, 1415, 554, 231, 74, 333, 47, 1114, 743, 76, + 106, 85, 1703, 42, 44, 42, 236, 44, 42, 44, + 74, 268, 202, 332, 44, 333, 333, 245, 38, 213, + 140, 42, 1511, 44, 42, 172, 42, 44, 170, 44, + 74, 231, 333, 245, 346, 300, 314, 76, 42, 967, + 42, 140, 74, 76, 42, 44, 74, 71, 333, 1415, + 44, 42, 76, 106, 44, 42, 108, 74, 149, 1159, + 266, 268, 74, 76, 181, 333, 103, 333, 967, 198, + 85, 277, 108, 53, 428, 42, 236, 135, 44, 135, + 74, 44, 71, 1413, 2022, 421, 38, 1093, 1190, 1260, + 140, 4830, 261, 3166, 261, 265, 197, 201, 261, 265, + 261, 265, 197, 201, 261, 41, 41, 41, 94, 229, + 265, 453, 261, 264, 261, 264, 261, 264, 165, 69, + 137, 40, 56, 37, 120, 101, 69, 137, 40, 120, + 133, 69, 137, 120, 261, 169, 120, 101, 69, 137, + 40, 88, 381, 162, 209, 85, 52, 51, 54, 84, + 51, 54, 52, 277, 59, 60, 162, 61, 309, 52, + 51, 149, 80, 117, 57, 54, 50, 373, 57, 53, + 48, 341, 61, 162, 194, 47, 38, 207, 121, 54, + 50, 38, 335, 121, 54, 50, 422, 855, 428, 139, + 44, 107, 396, 90, 41, 154, 41, 90, 37, 105, + 69, 105, 37, 58, 41, 90, 57, 169, 218, 41, + 58, 41, 58, 41, 58, 137, 58, 37, 137, 37, + 135, 37, 90, 69, 73, 185, 94, 101, 58, 57, + 90, 37, 58, 527, 1134, 94, 142, 47, 185, 186, + 89, 154, 57, 90, 57, 90, 57, 250, 57, 1018, + 89, 90, 57, 58, 57, 1018, 8601, 282, 153, 666, + 89, 250, 54, 50, 2618, 57, 986, 825, 1306, 217, + 602, 1274, 378, 1935, 2522, 719, 5882, 57, 314, 57, + 1754, 281, 3578, 57, 4634, 3322, 54, 50, 54, 50, + 54, 50, 54, 50, 54, 50, 54, 50, 54, 50, + 975, 1434, 185, 54, 50, 1017, 54, 50, 54, 50, + 54, 50, 54, 50, 54, 50, 537, 8218, 4217, 54, + 50, 54, 50, 54, 50, 54, 50, 54, 50, 54, + 50, 54, 50, 54, 50, 54, 50, 54, 50, 54, + 50, 2041, 54, 50, 54, 50, 1049, 54, 50, 8281, + 1562, 697, 90, 217, 346, 1513, 1509, 126, 73, 69, + 254, 105, 37, 94, 37, 94, 165, 70, 105, 37, + 3166, 37, 218, 158, 108, 94, 149, 47, 85, 1221, + 37, 37, 1799, 38, 53, 44, 743, 231, 231, 231, + 231, 231, 231, 231, 231, 1036, 85, 52, 51, 52, + 51, 117, 52, 51, 53, 52, 51, 309, 49, 85, + 49, 53, 52, 51, 85, 52, 51, 54, 50, 54, + 50, 54, 50, 54, 50, 181, 38, 341, 81, 858, + 2874, 6874, 410, 61, 117, 58, 38, 39, 46, 54, + 50, 54, 50, 54, 50, 54, 50, 54, 50, 90, + 54, 50, 54, 50, 54, 50, 54, 50, 49, 54, + 82, 58, 302, 140, 74, 49, 166, 90, 110, 38, + 39, 53, 90, 2759, 76, 88, 70, 39, 49, 2887, + 53, 102, 39, 1319, 3015, 90, 143, 346, 871, 1178, + 519, 1018, 335, 986, 271, 58, 495, 1050, 335, 1274, + 495, 2042, 8218, 39, 39, 2074, 39, 39, 679, 38, + 36583, 1786, 1287, 198, 85, 8583, 38, 117, 519, 333, + 71, 1502, 39, 44, 107, 53, 332, 53, 38, 798, + 44, 2247, 334, 76, 213, 760, 294, 88, 478, 69, + 2014, 38, 261, 190, 350, 38, 88, 158, 158, 382, + 70, 37, 231, 44, 103, 44, 135, 44, 743, 74, + 76, 42, 154, 207, 90, 55, 58, 1671, 149, 74, + 1607, 522, 44, 85, 333, 588, 199, 117, 39, 333, + 903, 268, 85, 743, 364, 74, 53, 935, 108, 42, + 1511, 44, 74, 140, 74, 44, 138, 437, 38, 333, + 85, 1319, 204, 74, 76, 74, 76, 103, 44, 263, + 44, 42, 333, 149, 519, 38, 199, 122, 39, 42, + 1543, 44, 39, 108, 71, 76, 167, 76, 39, 44, + 39, 71, 38, 85, 359, 42, 76, 74, 85, 39, + 70, 42, 44, 199, 199, 199, 231, 231, 1127, 74, + 44, 74, 44, 74, 53, 42, 44, 333, 39, 39, + 743, 1575, 36, 68, 68, 36, 63, 63, 11719, 3399, + 229, 165, 39, 44, 327, 57, 423, 167, 39, 71, + 71, 3463, 536, 11623, 54, 50, 2055, 1735, 391, 55, + 58, 524, 245, 54, 50, 53, 236, 53, 81, 80, + 54, 50, 54, 50, 54, 50, 54, 50, 54, 50, + 54, 50, 54, 50, 54, 50, 85, 54, 50, 149, + 112, 117, 149, 49, 54, 50, 54, 50, 54, 50, + 117, 57, 49, 121, 53, 55, 85, 167, 4327, 34, + 117, 55, 117, 54, 50, 53, 57, 53, 49, 85, + 333, 85, 121, 85, 841, 54, 53, 50, 56, 48, + 56, 837, 54, 57, 50, 57, 54, 50, 53, 54, + 50, 85, 327, 38, 1447, 70, 999, 199, 199, 199, + 103, 87, 57, 56, 58, 87, 58, 153, 90, 98, + 90, 391, 839, 615, 71, 487, 455, 3943, 117, 1455, + 314, 1710, 143, 570, 47, 410, 1466, 44, 935, 1575, + 999, 143, 551, 46, 263, 46, 967, 53, 1159, 263, + 53, 174, 1289, 1285, 2503, 333, 199, 39, 1415, 71, + 39, 743, 53, 271, 711, 207, 53, 839, 53, 1799, + 71, 39, 108, 76, 140, 135, 103, 871, 108, 44, + 271, 309, 935, 79, 53, 1735, 245, 711, 271, 615, + 271, 2343, 1007, 42, 44, 42, 1703, 492, 245, 655, + 333, 76, 42, 1447, 106, 140, 74, 76, 85, 34, + 149, 807, 333, 108, 1159, 172, 42, 268, 333, 149, + 76, 42, 1543, 106, 300, 74, 135, 149, 333, 1383, + 44, 42, 44, 74, 204, 42, 44, 333, 28135, 3182, + 149, 34279, 18215, 2215, 39, 1482, 140, 422, 71, 7898, + 1274, 1946, 74, 108, 122, 202, 258, 268, 90, 236, + 986, 140, 1562, 2138, 108, 58, 2810, 591, 841, 837, + 841, 229, 581, 841, 837, 41, 73, 41, 73, 137, + 265, 133, 37, 229, 357, 841, 837, 73, 137, 265, + 233, 837, 73, 137, 169, 41, 233, 837, 841, 837, + 841, 837, 841, 837, 841, 837, 841, 837, 841, 901, + 809, 57, 805, 57, 197, 809, 57, 805, 57, 197, + 809, 57, 805, 57, 197, 809, 57, 805, 57, 197, + 809, 57, 805, 57, 197, 94, 1613, 135, 871, 71, + 39, 39, 327, 135, 39, 39, 39, 39, 39, 39, + 103, 71, 39, 39, 39, 39, 39, 39, 71, 39, + 135, 231, 135, 135, 39, 327, 551, 103, 167, 551, + 89, 1434, 3226, 506, 474, 506, 506, 367, 1018, 1946, + 1402, 954, 1402, 314, 90, 1082, 218, 2266, 666, 1210, + 186, 570, 2042, 58, 5850, 154, 2010, 154, 794, 2266, + 378, 2266, 3738, 39, 39, 39, 39, 39, 39, 17351, + 34, 3074, 7692, 63, 63, + }; + +static int sqlite3Fts5UnicodeCategory(u32 iCode) { + int iRes = -1; + int iHi; + int iLo; + int ret; + u16 iKey; + + if( iCode>=(1<<20) ){ + return 0; + } + iLo = aFts5UnicodeBlock[(iCode>>16)]; + iHi = aFts5UnicodeBlock[1+(iCode>>16)]; + iKey = (iCode & 0xFFFF); + while( iHi>iLo ){ + int iTest = (iHi + iLo) / 2; + assert( iTest>=iLo && iTest<iHi ); + if( iKey>=aFts5UnicodeMap[iTest] ){ + iRes = iTest; + iLo = iTest+1; + }else{ + iHi = iTest; + } + } + + if( iRes<0 ) return 0; + if( iKey>=(aFts5UnicodeMap[iRes]+(aFts5UnicodeData[iRes]>>5)) ) return 0; + ret = aFts5UnicodeData[iRes] & 0x1F; + if( ret!=30 ) return ret; + return ((iKey - aFts5UnicodeMap[iRes]) & 0x01) ? 5 : 9; +} + +static void sqlite3Fts5UnicodeAscii(u8 *aArray, u8 *aAscii){ + int i = 0; + int iTbl = 0; + while( i<128 ){ + int bToken = aArray[ aFts5UnicodeData[iTbl] & 0x1F ]; + int n = (aFts5UnicodeData[iTbl] >> 5) + i; + for(; i<128 && i<n; i++){ + aAscii[i] = (u8)bToken; + } + iTbl++; + } + aAscii[0] = 0; /* 0x00 is never a token character */ +} + + +/* +** 2015 May 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** Routines for varint serialization and deserialization. +*/ + + +/* #include "fts5Int.h" */ + +/* +** This is a copy of the sqlite3GetVarint32() routine from the SQLite core. +** Except, this version does handle the single byte case that the core +** version depends on being handled before its function is called. +*/ +static int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v){ + u32 a,b; + + /* The 1-byte case. Overwhelmingly the most common. */ + a = *p; + /* a: p0 (unmasked) */ + if (!(a&0x80)) + { + /* Values between 0 and 127 */ + *v = a; + return 1; + } + + /* The 2-byte case */ + p++; + b = *p; + /* b: p1 (unmasked) */ + if (!(b&0x80)) + { + /* Values between 128 and 16383 */ + a &= 0x7f; + a = a<<7; + *v = a | b; + return 2; + } + + /* The 3-byte case */ + p++; + a = a<<14; + a |= *p; + /* a: p0<<14 | p2 (unmasked) */ + if (!(a&0x80)) + { + /* Values between 16384 and 2097151 */ + a &= (0x7f<<14)|(0x7f); + b &= 0x7f; + b = b<<7; + *v = a | b; + return 3; + } + + /* A 32-bit varint is used to store size information in btrees. + ** Objects are rarely larger than 2MiB limit of a 3-byte varint. + ** A 3-byte varint is sufficient, for example, to record the size + ** of a 1048569-byte BLOB or string. + ** + ** We only unroll the first 1-, 2-, and 3- byte cases. The very + ** rare larger cases can be handled by the slower 64-bit varint + ** routine. + */ + { + u64 v64; + u8 n; + p -= 2; + n = sqlite3Fts5GetVarint(p, &v64); + *v = ((u32)v64) & 0x7FFFFFFF; + assert( n>3 && n<=9 ); + return n; + } +} + + +/* +** Bitmasks used by sqlite3GetVarint(). These precomputed constants +** are defined here rather than simply putting the constant expressions +** inline in order to work around bugs in the RVT compiler. +** +** SLOT_2_0 A mask for (0x7f<<14) | 0x7f +** +** SLOT_4_2_0 A mask for (0x7f<<28) | SLOT_2_0 +*/ +#define SLOT_2_0 0x001fc07f +#define SLOT_4_2_0 0xf01fc07f + +/* +** Read a 64-bit variable-length integer from memory starting at p[0]. +** Return the number of bytes read. The value is stored in *v. +*/ +static u8 sqlite3Fts5GetVarint(const unsigned char *p, u64 *v){ + u32 a,b,s; + + a = *p; + /* a: p0 (unmasked) */ + if (!(a&0x80)) + { + *v = a; + return 1; + } + + p++; + b = *p; + /* b: p1 (unmasked) */ + if (!(b&0x80)) + { + a &= 0x7f; + a = a<<7; + a |= b; + *v = a; + return 2; + } + + /* Verify that constants are precomputed correctly */ + assert( SLOT_2_0 == ((0x7f<<14) | (0x7f)) ); + assert( SLOT_4_2_0 == ((0xfU<<28) | (0x7f<<14) | (0x7f)) ); + + p++; + a = a<<14; + a |= *p; + /* a: p0<<14 | p2 (unmasked) */ + if (!(a&0x80)) + { + a &= SLOT_2_0; + b &= 0x7f; + b = b<<7; + a |= b; + *v = a; + return 3; + } + + /* CSE1 from below */ + a &= SLOT_2_0; + p++; + b = b<<14; + b |= *p; + /* b: p1<<14 | p3 (unmasked) */ + if (!(b&0x80)) + { + b &= SLOT_2_0; + /* moved CSE1 up */ + /* a &= (0x7f<<14)|(0x7f); */ + a = a<<7; + a |= b; + *v = a; + return 4; + } + + /* a: p0<<14 | p2 (masked) */ + /* b: p1<<14 | p3 (unmasked) */ + /* 1:save off p0<<21 | p1<<14 | p2<<7 | p3 (masked) */ + /* moved CSE1 up */ + /* a &= (0x7f<<14)|(0x7f); */ + b &= SLOT_2_0; + s = a; + /* s: p0<<14 | p2 (masked) */ + + p++; + a = a<<14; + a |= *p; + /* a: p0<<28 | p2<<14 | p4 (unmasked) */ + if (!(a&0x80)) + { + /* we can skip these cause they were (effectively) done above in calc'ing s */ + /* a &= (0x7f<<28)|(0x7f<<14)|(0x7f); */ + /* b &= (0x7f<<14)|(0x7f); */ + b = b<<7; + a |= b; + s = s>>18; + *v = ((u64)s)<<32 | a; + return 5; + } + + /* 2:save off p0<<21 | p1<<14 | p2<<7 | p3 (masked) */ + s = s<<7; + s |= b; + /* s: p0<<21 | p1<<14 | p2<<7 | p3 (masked) */ + + p++; + b = b<<14; + b |= *p; + /* b: p1<<28 | p3<<14 | p5 (unmasked) */ + if (!(b&0x80)) + { + /* we can skip this cause it was (effectively) done above in calc'ing s */ + /* b &= (0x7f<<28)|(0x7f<<14)|(0x7f); */ + a &= SLOT_2_0; + a = a<<7; + a |= b; + s = s>>18; + *v = ((u64)s)<<32 | a; + return 6; + } + + p++; + a = a<<14; + a |= *p; + /* a: p2<<28 | p4<<14 | p6 (unmasked) */ + if (!(a&0x80)) + { + a &= SLOT_4_2_0; + b &= SLOT_2_0; + b = b<<7; + a |= b; + s = s>>11; + *v = ((u64)s)<<32 | a; + return 7; + } + + /* CSE2 from below */ + a &= SLOT_2_0; + p++; + b = b<<14; + b |= *p; + /* b: p3<<28 | p5<<14 | p7 (unmasked) */ + if (!(b&0x80)) + { + b &= SLOT_4_2_0; + /* moved CSE2 up */ + /* a &= (0x7f<<14)|(0x7f); */ + a = a<<7; + a |= b; + s = s>>4; + *v = ((u64)s)<<32 | a; + return 8; + } + + p++; + a = a<<15; + a |= *p; + /* a: p4<<29 | p6<<15 | p8 (unmasked) */ + + /* moved CSE2 up */ + /* a &= (0x7f<<29)|(0x7f<<15)|(0xff); */ + b &= SLOT_2_0; + b = b<<8; + a |= b; + + s = s<<4; + b = p[-4]; + b &= 0x7f; + b = b>>3; + s |= b; + + *v = ((u64)s)<<32 | a; + + return 9; +} + +/* +** The variable-length integer encoding is as follows: +** +** KEY: +** A = 0xxxxxxx 7 bits of data and one flag bit +** B = 1xxxxxxx 7 bits of data and one flag bit +** C = xxxxxxxx 8 bits of data +** +** 7 bits - A +** 14 bits - BA +** 21 bits - BBA +** 28 bits - BBBA +** 35 bits - BBBBA +** 42 bits - BBBBBA +** 49 bits - BBBBBBA +** 56 bits - BBBBBBBA +** 64 bits - BBBBBBBBC +*/ + +#ifdef SQLITE_NOINLINE +# define FTS5_NOINLINE SQLITE_NOINLINE +#else +# define FTS5_NOINLINE +#endif + +/* +** Write a 64-bit variable-length integer to memory starting at p[0]. +** The length of data write will be between 1 and 9 bytes. The number +** of bytes written is returned. +** +** A variable-length integer consists of the lower 7 bits of each byte +** for all bytes that have the 8th bit set and one byte with the 8th +** bit clear. Except, if we get to the 9th byte, it stores the full +** 8 bits and is the last byte. +*/ +static int FTS5_NOINLINE fts5PutVarint64(unsigned char *p, u64 v){ + int i, j, n; + u8 buf[10]; + if( v & (((u64)0xff000000)<<32) ){ + p[8] = (u8)v; + v >>= 8; + for(i=7; i>=0; i--){ + p[i] = (u8)((v & 0x7f) | 0x80); + v >>= 7; + } + return 9; + } + n = 0; + do{ + buf[n++] = (u8)((v & 0x7f) | 0x80); + v >>= 7; + }while( v!=0 ); + buf[0] &= 0x7f; + assert( n<=9 ); + for(i=0, j=n-1; j>=0; j--, i++){ + p[i] = buf[j]; + } + return n; +} + +static int sqlite3Fts5PutVarint(unsigned char *p, u64 v){ + if( v<=0x7f ){ + p[0] = v&0x7f; + return 1; + } + if( v<=0x3fff ){ + p[0] = ((v>>7)&0x7f)|0x80; + p[1] = v&0x7f; + return 2; + } + return fts5PutVarint64(p,v); +} + + +static int sqlite3Fts5GetVarintLen(u32 iVal){ +#if 0 + if( iVal<(1 << 7 ) ) return 1; +#endif + assert( iVal>=(1 << 7) ); + if( iVal<(1 << 14) ) return 2; + if( iVal<(1 << 21) ) return 3; + if( iVal<(1 << 28) ) return 4; + return 5; +} + +/* +** 2015 May 08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This is an SQLite virtual table module implementing direct access to an +** existing FTS5 index. The module may create several different types of +** tables: +** +** col: +** CREATE TABLE vocab(term, col, doc, cnt, PRIMARY KEY(term, col)); +** +** One row for each term/column combination. The value of $doc is set to +** the number of fts5 rows that contain at least one instance of term +** $term within column $col. Field $cnt is set to the total number of +** instances of term $term in column $col (in any row of the fts5 table). +** +** row: +** CREATE TABLE vocab(term, doc, cnt, PRIMARY KEY(term)); +** +** One row for each term in the database. The value of $doc is set to +** the number of fts5 rows that contain at least one instance of term +** $term. Field $cnt is set to the total number of instances of term +** $term in the database. +** +** instance: +** CREATE TABLE vocab(term, doc, col, offset, PRIMARY KEY(<all-fields>)); +** +** One row for each term instance in the database. +*/ + + +/* #include "fts5Int.h" */ + + +typedef struct Fts5VocabTable Fts5VocabTable; +typedef struct Fts5VocabCursor Fts5VocabCursor; + +struct Fts5VocabTable { + sqlite3_vtab base; + char *zFts5Tbl; /* Name of fts5 table */ + char *zFts5Db; /* Db containing fts5 table */ + sqlite3 *db; /* Database handle */ + Fts5Global *pGlobal; /* FTS5 global object for this database */ + int eType; /* FTS5_VOCAB_COL, ROW or INSTANCE */ + unsigned bBusy; /* True if busy */ +}; + +struct Fts5VocabCursor { + sqlite3_vtab_cursor base; + sqlite3_stmt *pStmt; /* Statement holding lock on pIndex */ + Fts5Table *pFts5; /* Associated FTS5 table */ + + int bEof; /* True if this cursor is at EOF */ + Fts5IndexIter *pIter; /* Term/rowid iterator object */ + void *pStruct; /* From sqlite3Fts5StructureRef() */ + + int nLeTerm; /* Size of zLeTerm in bytes */ + char *zLeTerm; /* (term <= $zLeTerm) paramater, or NULL */ + + /* These are used by 'col' tables only */ + int iCol; + i64 *aCnt; + i64 *aDoc; + + /* Output values used by all tables. */ + i64 rowid; /* This table's current rowid value */ + Fts5Buffer term; /* Current value of 'term' column */ + + /* Output values Used by 'instance' tables only */ + i64 iInstPos; + int iInstOff; +}; + +#define FTS5_VOCAB_COL 0 +#define FTS5_VOCAB_ROW 1 +#define FTS5_VOCAB_INSTANCE 2 + +#define FTS5_VOCAB_COL_SCHEMA "term, col, doc, cnt" +#define FTS5_VOCAB_ROW_SCHEMA "term, doc, cnt" +#define FTS5_VOCAB_INST_SCHEMA "term, doc, col, offset" + +/* +** Bits for the mask used as the idxNum value by xBestIndex/xFilter. +*/ +#define FTS5_VOCAB_TERM_EQ 0x01 +#define FTS5_VOCAB_TERM_GE 0x02 +#define FTS5_VOCAB_TERM_LE 0x04 + + +/* +** Translate a string containing an fts5vocab table type to an +** FTS5_VOCAB_XXX constant. If successful, set *peType to the output +** value and return SQLITE_OK. Otherwise, set *pzErr to an error message +** and return SQLITE_ERROR. +*/ +static int fts5VocabTableType(const char *zType, char **pzErr, int *peType){ + int rc = SQLITE_OK; + char *zCopy = sqlite3Fts5Strndup(&rc, zType, -1); + if( rc==SQLITE_OK ){ + sqlite3Fts5Dequote(zCopy); + if( sqlite3_stricmp(zCopy, "col")==0 ){ + *peType = FTS5_VOCAB_COL; + }else + + if( sqlite3_stricmp(zCopy, "row")==0 ){ + *peType = FTS5_VOCAB_ROW; + }else + if( sqlite3_stricmp(zCopy, "instance")==0 ){ + *peType = FTS5_VOCAB_INSTANCE; + }else + { + *pzErr = sqlite3_mprintf("fts5vocab: unknown table type: %Q", zCopy); + rc = SQLITE_ERROR; + } + sqlite3_free(zCopy); + } + + return rc; +} + + +/* +** The xDisconnect() virtual table method. +*/ +static int fts5VocabDisconnectMethod(sqlite3_vtab *pVtab){ + Fts5VocabTable *pTab = (Fts5VocabTable*)pVtab; + sqlite3_free(pTab); + return SQLITE_OK; +} + +/* +** The xDestroy() virtual table method. +*/ +static int fts5VocabDestroyMethod(sqlite3_vtab *pVtab){ + Fts5VocabTable *pTab = (Fts5VocabTable*)pVtab; + sqlite3_free(pTab); + return SQLITE_OK; +} + +/* +** This function is the implementation of both the xConnect and xCreate +** methods of the FTS3 virtual table. +** +** The argv[] array contains the following: +** +** argv[0] -> module name ("fts5vocab") +** argv[1] -> database name +** argv[2] -> table name +** +** then: +** +** argv[3] -> name of fts5 table +** argv[4] -> type of fts5vocab table +** +** or, for tables in the TEMP schema only. +** +** argv[3] -> name of fts5 tables database +** argv[4] -> name of fts5 table +** argv[5] -> type of fts5vocab table +*/ +static int fts5VocabInitVtab( + sqlite3 *db, /* The SQLite database connection */ + void *pAux, /* Pointer to Fts5Global object */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */ + char **pzErr /* Write any error message here */ +){ + const char *azSchema[] = { + "CREATE TABlE vocab(" FTS5_VOCAB_COL_SCHEMA ")", + "CREATE TABlE vocab(" FTS5_VOCAB_ROW_SCHEMA ")", + "CREATE TABlE vocab(" FTS5_VOCAB_INST_SCHEMA ")" + }; + + Fts5VocabTable *pRet = 0; + int rc = SQLITE_OK; /* Return code */ + int bDb; + + bDb = (argc==6 && strlen(argv[1])==4 && memcmp("temp", argv[1], 4)==0); + + if( argc!=5 && bDb==0 ){ + *pzErr = sqlite3_mprintf("wrong number of vtable arguments"); + rc = SQLITE_ERROR; + }else{ + int nByte; /* Bytes of space to allocate */ + const char *zDb = bDb ? argv[3] : argv[1]; + const char *zTab = bDb ? argv[4] : argv[3]; + const char *zType = bDb ? argv[5] : argv[4]; + int nDb = (int)strlen(zDb)+1; + int nTab = (int)strlen(zTab)+1; + int eType = 0; + + rc = fts5VocabTableType(zType, pzErr, &eType); + if( rc==SQLITE_OK ){ + assert( eType>=0 && eType<ArraySize(azSchema) ); + rc = sqlite3_declare_vtab(db, azSchema[eType]); + } + + nByte = sizeof(Fts5VocabTable) + nDb + nTab; + pRet = sqlite3Fts5MallocZero(&rc, nByte); + if( pRet ){ + pRet->pGlobal = (Fts5Global*)pAux; + pRet->eType = eType; + pRet->db = db; + pRet->zFts5Tbl = (char*)&pRet[1]; + pRet->zFts5Db = &pRet->zFts5Tbl[nTab]; + memcpy(pRet->zFts5Tbl, zTab, nTab); + memcpy(pRet->zFts5Db, zDb, nDb); + sqlite3Fts5Dequote(pRet->zFts5Tbl); + sqlite3Fts5Dequote(pRet->zFts5Db); + } + } + + *ppVTab = (sqlite3_vtab*)pRet; + return rc; +} + + +/* +** The xConnect() and xCreate() methods for the virtual table. All the +** work is done in function fts5VocabInitVtab(). +*/ +static int fts5VocabConnectMethod( + sqlite3 *db, /* Database connection */ + void *pAux, /* Pointer to tokenizer hash table */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ + char **pzErr /* OUT: sqlite3_malloc'd error message */ +){ + return fts5VocabInitVtab(db, pAux, argc, argv, ppVtab, pzErr); +} +static int fts5VocabCreateMethod( + sqlite3 *db, /* Database connection */ + void *pAux, /* Pointer to tokenizer hash table */ + int argc, /* Number of elements in argv array */ + const char * const *argv, /* xCreate/xConnect argument array */ + sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ + char **pzErr /* OUT: sqlite3_malloc'd error message */ +){ + return fts5VocabInitVtab(db, pAux, argc, argv, ppVtab, pzErr); +} + +/* +** Implementation of the xBestIndex method. +** +** Only constraints of the form: +** +** term <= ? +** term == ? +** term >= ? +** +** are interpreted. Less-than and less-than-or-equal are treated +** identically, as are greater-than and greater-than-or-equal. +*/ +static int fts5VocabBestIndexMethod( + sqlite3_vtab *pUnused, + sqlite3_index_info *pInfo +){ + int i; + int iTermEq = -1; + int iTermGe = -1; + int iTermLe = -1; + int idxNum = 0; + int nArg = 0; + + UNUSED_PARAM(pUnused); + + for(i=0; i<pInfo->nConstraint; i++){ + struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; + if( p->usable==0 ) continue; + if( p->iColumn==0 ){ /* term column */ + if( p->op==SQLITE_INDEX_CONSTRAINT_EQ ) iTermEq = i; + if( p->op==SQLITE_INDEX_CONSTRAINT_LE ) iTermLe = i; + if( p->op==SQLITE_INDEX_CONSTRAINT_LT ) iTermLe = i; + if( p->op==SQLITE_INDEX_CONSTRAINT_GE ) iTermGe = i; + if( p->op==SQLITE_INDEX_CONSTRAINT_GT ) iTermGe = i; + } + } + + if( iTermEq>=0 ){ + idxNum |= FTS5_VOCAB_TERM_EQ; + pInfo->aConstraintUsage[iTermEq].argvIndex = ++nArg; + pInfo->estimatedCost = 100; + }else{ + pInfo->estimatedCost = 1000000; + if( iTermGe>=0 ){ + idxNum |= FTS5_VOCAB_TERM_GE; + pInfo->aConstraintUsage[iTermGe].argvIndex = ++nArg; + pInfo->estimatedCost = pInfo->estimatedCost / 2; + } + if( iTermLe>=0 ){ + idxNum |= FTS5_VOCAB_TERM_LE; + pInfo->aConstraintUsage[iTermLe].argvIndex = ++nArg; + pInfo->estimatedCost = pInfo->estimatedCost / 2; + } + } + + /* This virtual table always delivers results in ascending order of + ** the "term" column (column 0). So if the user has requested this + ** specifically - "ORDER BY term" or "ORDER BY term ASC" - set the + ** sqlite3_index_info.orderByConsumed flag to tell the core the results + ** are already in sorted order. */ + if( pInfo->nOrderBy==1 + && pInfo->aOrderBy[0].iColumn==0 + && pInfo->aOrderBy[0].desc==0 + ){ + pInfo->orderByConsumed = 1; + } + + pInfo->idxNum = idxNum; + return SQLITE_OK; +} + +/* +** Implementation of xOpen method. +*/ +static int fts5VocabOpenMethod( + sqlite3_vtab *pVTab, + sqlite3_vtab_cursor **ppCsr +){ + Fts5VocabTable *pTab = (Fts5VocabTable*)pVTab; + Fts5Table *pFts5 = 0; + Fts5VocabCursor *pCsr = 0; + int rc = SQLITE_OK; + sqlite3_stmt *pStmt = 0; + char *zSql = 0; + + if( pTab->bBusy ){ + pVTab->zErrMsg = sqlite3_mprintf( + "recursive definition for %s.%s", pTab->zFts5Db, pTab->zFts5Tbl + ); + return SQLITE_ERROR; + } + zSql = sqlite3Fts5Mprintf(&rc, + "SELECT t.%Q FROM %Q.%Q AS t WHERE t.%Q MATCH '*id'", + pTab->zFts5Tbl, pTab->zFts5Db, pTab->zFts5Tbl, pTab->zFts5Tbl + ); + if( zSql ){ + rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pStmt, 0); + } + sqlite3_free(zSql); + assert( rc==SQLITE_OK || pStmt==0 ); + if( rc==SQLITE_ERROR ) rc = SQLITE_OK; + + pTab->bBusy = 1; + if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ + i64 iId = sqlite3_column_int64(pStmt, 0); + pFts5 = sqlite3Fts5TableFromCsrid(pTab->pGlobal, iId); + } + pTab->bBusy = 0; + + if( rc==SQLITE_OK ){ + if( pFts5==0 ){ + rc = sqlite3_finalize(pStmt); + pStmt = 0; + if( rc==SQLITE_OK ){ + pVTab->zErrMsg = sqlite3_mprintf( + "no such fts5 table: %s.%s", pTab->zFts5Db, pTab->zFts5Tbl + ); + rc = SQLITE_ERROR; + } + }else{ + rc = sqlite3Fts5FlushToDisk(pFts5); + } + } + + if( rc==SQLITE_OK ){ + i64 nByte = pFts5->pConfig->nCol * sizeof(i64)*2 + sizeof(Fts5VocabCursor); + pCsr = (Fts5VocabCursor*)sqlite3Fts5MallocZero(&rc, nByte); + } + + if( pCsr ){ + pCsr->pFts5 = pFts5; + pCsr->pStmt = pStmt; + pCsr->aCnt = (i64*)&pCsr[1]; + pCsr->aDoc = &pCsr->aCnt[pFts5->pConfig->nCol]; + }else{ + sqlite3_finalize(pStmt); + } + + *ppCsr = (sqlite3_vtab_cursor*)pCsr; + return rc; +} + +static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){ + pCsr->rowid = 0; + sqlite3Fts5IterClose(pCsr->pIter); + sqlite3Fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; + pCsr->pIter = 0; + sqlite3_free(pCsr->zLeTerm); + pCsr->nLeTerm = -1; + pCsr->zLeTerm = 0; + pCsr->bEof = 0; +} + +/* +** Close the cursor. For additional information see the documentation +** on the xClose method of the virtual table interface. +*/ +static int fts5VocabCloseMethod(sqlite3_vtab_cursor *pCursor){ + Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; + fts5VocabResetCursor(pCsr); + sqlite3Fts5BufferFree(&pCsr->term); + sqlite3_finalize(pCsr->pStmt); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +static int fts5VocabInstanceNewTerm(Fts5VocabCursor *pCsr){ + int rc = SQLITE_OK; + + if( sqlite3Fts5IterEof(pCsr->pIter) ){ + pCsr->bEof = 1; + }else{ + const char *zTerm; + int nTerm; + zTerm = sqlite3Fts5IterTerm(pCsr->pIter, &nTerm); + if( pCsr->nLeTerm>=0 ){ + int nCmp = MIN(nTerm, pCsr->nLeTerm); + int bCmp = memcmp(pCsr->zLeTerm, zTerm, nCmp); + if( bCmp<0 || (bCmp==0 && pCsr->nLeTerm<nTerm) ){ + pCsr->bEof = 1; + } + } + + sqlite3Fts5BufferSet(&rc, &pCsr->term, nTerm, (const u8*)zTerm); + } + return rc; +} + +static int fts5VocabInstanceNext(Fts5VocabCursor *pCsr){ + int eDetail = pCsr->pFts5->pConfig->eDetail; + int rc = SQLITE_OK; + Fts5IndexIter *pIter = pCsr->pIter; + i64 *pp = &pCsr->iInstPos; + int *po = &pCsr->iInstOff; + + assert( sqlite3Fts5IterEof(pIter)==0 ); + assert( pCsr->bEof==0 ); + while( eDetail==FTS5_DETAIL_NONE + || sqlite3Fts5PoslistNext64(pIter->pData, pIter->nData, po, pp) + ){ + pCsr->iInstPos = 0; + pCsr->iInstOff = 0; + + rc = sqlite3Fts5IterNextScan(pCsr->pIter); + if( rc==SQLITE_OK ){ + rc = fts5VocabInstanceNewTerm(pCsr); + if( pCsr->bEof || eDetail==FTS5_DETAIL_NONE ) break; + } + if( rc ){ + pCsr->bEof = 1; + break; + } + } + + return rc; +} + +/* +** Advance the cursor to the next row in the table. +*/ +static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ + Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; + Fts5VocabTable *pTab = (Fts5VocabTable*)pCursor->pVtab; + int nCol = pCsr->pFts5->pConfig->nCol; + int rc; + + rc = sqlite3Fts5StructureTest(pCsr->pFts5->pIndex, pCsr->pStruct); + if( rc!=SQLITE_OK ) return rc; + pCsr->rowid++; + + if( pTab->eType==FTS5_VOCAB_INSTANCE ){ + return fts5VocabInstanceNext(pCsr); + } + + if( pTab->eType==FTS5_VOCAB_COL ){ + for(pCsr->iCol++; pCsr->iCol<nCol; pCsr->iCol++){ + if( pCsr->aDoc[pCsr->iCol] ) break; + } + } + + if( pTab->eType!=FTS5_VOCAB_COL || pCsr->iCol>=nCol ){ + if( sqlite3Fts5IterEof(pCsr->pIter) ){ + pCsr->bEof = 1; + }else{ + const char *zTerm; + int nTerm; + + zTerm = sqlite3Fts5IterTerm(pCsr->pIter, &nTerm); + assert( nTerm>=0 ); + if( pCsr->nLeTerm>=0 ){ + int nCmp = MIN(nTerm, pCsr->nLeTerm); + int bCmp = memcmp(pCsr->zLeTerm, zTerm, nCmp); + if( bCmp<0 || (bCmp==0 && pCsr->nLeTerm<nTerm) ){ + pCsr->bEof = 1; + return SQLITE_OK; + } + } + + sqlite3Fts5BufferSet(&rc, &pCsr->term, nTerm, (const u8*)zTerm); + memset(pCsr->aCnt, 0, nCol * sizeof(i64)); + memset(pCsr->aDoc, 0, nCol * sizeof(i64)); + pCsr->iCol = 0; + + assert( pTab->eType==FTS5_VOCAB_COL || pTab->eType==FTS5_VOCAB_ROW ); + while( rc==SQLITE_OK ){ + int eDetail = pCsr->pFts5->pConfig->eDetail; + const u8 *pPos; int nPos; /* Position list */ + i64 iPos = 0; /* 64-bit position read from poslist */ + int iOff = 0; /* Current offset within position list */ + + pPos = pCsr->pIter->pData; + nPos = pCsr->pIter->nData; + + switch( pTab->eType ){ + case FTS5_VOCAB_ROW: + if( eDetail==FTS5_DETAIL_FULL ){ + while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){ + pCsr->aCnt[0]++; + } + } + pCsr->aDoc[0]++; + break; + + case FTS5_VOCAB_COL: + if( eDetail==FTS5_DETAIL_FULL ){ + int iCol = -1; + while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){ + int ii = FTS5_POS2COLUMN(iPos); + if( iCol!=ii ){ + if( ii>=nCol ){ + rc = FTS5_CORRUPT; + break; + } + pCsr->aDoc[ii]++; + iCol = ii; + } + pCsr->aCnt[ii]++; + } + }else if( eDetail==FTS5_DETAIL_COLUMNS ){ + while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff,&iPos) ){ + assert_nc( iPos>=0 && iPos<nCol ); + if( iPos>=nCol ){ + rc = FTS5_CORRUPT; + break; + } + pCsr->aDoc[iPos]++; + } + }else{ + assert( eDetail==FTS5_DETAIL_NONE ); + pCsr->aDoc[0]++; + } + break; + + default: + assert( pTab->eType==FTS5_VOCAB_INSTANCE ); + break; + } + + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IterNextScan(pCsr->pIter); + } + if( pTab->eType==FTS5_VOCAB_INSTANCE ) break; + + if( rc==SQLITE_OK ){ + zTerm = sqlite3Fts5IterTerm(pCsr->pIter, &nTerm); + if( nTerm!=pCsr->term.n + || (nTerm>0 && memcmp(zTerm, pCsr->term.p, nTerm)) + ){ + break; + } + if( sqlite3Fts5IterEof(pCsr->pIter) ) break; + } + } + } + } + + if( rc==SQLITE_OK && pCsr->bEof==0 && pTab->eType==FTS5_VOCAB_COL ){ + for(/* noop */; pCsr->iCol<nCol && pCsr->aDoc[pCsr->iCol]==0; pCsr->iCol++); + if( pCsr->iCol==nCol ){ + rc = FTS5_CORRUPT; + } + } + return rc; +} + +/* +** This is the xFilter implementation for the virtual table. +*/ +static int fts5VocabFilterMethod( + sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ + int idxNum, /* Strategy index */ + const char *zUnused, /* Unused */ + int nUnused, /* Number of elements in apVal */ + sqlite3_value **apVal /* Arguments for the indexing scheme */ +){ + Fts5VocabTable *pTab = (Fts5VocabTable*)pCursor->pVtab; + Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; + int eType = pTab->eType; + int rc = SQLITE_OK; + + int iVal = 0; + int f = FTS5INDEX_QUERY_SCAN; + const char *zTerm = 0; + int nTerm = 0; + + sqlite3_value *pEq = 0; + sqlite3_value *pGe = 0; + sqlite3_value *pLe = 0; + + UNUSED_PARAM2(zUnused, nUnused); + + fts5VocabResetCursor(pCsr); + if( idxNum & FTS5_VOCAB_TERM_EQ ) pEq = apVal[iVal++]; + if( idxNum & FTS5_VOCAB_TERM_GE ) pGe = apVal[iVal++]; + if( idxNum & FTS5_VOCAB_TERM_LE ) pLe = apVal[iVal++]; + + if( pEq ){ + zTerm = (const char *)sqlite3_value_text(pEq); + nTerm = sqlite3_value_bytes(pEq); + f = 0; + }else{ + if( pGe ){ + zTerm = (const char *)sqlite3_value_text(pGe); + nTerm = sqlite3_value_bytes(pGe); + } + if( pLe ){ + const char *zCopy = (const char *)sqlite3_value_text(pLe); + if( zCopy==0 ) zCopy = ""; + pCsr->nLeTerm = sqlite3_value_bytes(pLe); + pCsr->zLeTerm = sqlite3_malloc(pCsr->nLeTerm+1); + if( pCsr->zLeTerm==0 ){ + rc = SQLITE_NOMEM; + }else{ + memcpy(pCsr->zLeTerm, zCopy, pCsr->nLeTerm+1); + } + } + } + + if( rc==SQLITE_OK ){ + Fts5Index *pIndex = pCsr->pFts5->pIndex; + rc = sqlite3Fts5IndexQuery(pIndex, zTerm, nTerm, f, 0, &pCsr->pIter); + if( rc==SQLITE_OK ){ + pCsr->pStruct = sqlite3Fts5StructureRef(pIndex); + } + } + if( rc==SQLITE_OK && eType==FTS5_VOCAB_INSTANCE ){ + rc = fts5VocabInstanceNewTerm(pCsr); + } + if( rc==SQLITE_OK && !pCsr->bEof + && (eType!=FTS5_VOCAB_INSTANCE + || pCsr->pFts5->pConfig->eDetail!=FTS5_DETAIL_NONE) + ){ + rc = fts5VocabNextMethod(pCursor); + } + + return rc; +} + +/* +** This is the xEof method of the virtual table. SQLite calls this +** routine to find out if it has reached the end of a result set. +*/ +static int fts5VocabEofMethod(sqlite3_vtab_cursor *pCursor){ + Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; + return pCsr->bEof; +} + +static int fts5VocabColumnMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ + int iCol /* Index of column to read value from */ +){ + Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; + int eDetail = pCsr->pFts5->pConfig->eDetail; + int eType = ((Fts5VocabTable*)(pCursor->pVtab))->eType; + i64 iVal = 0; + + if( iCol==0 ){ + sqlite3_result_text( + pCtx, (const char*)pCsr->term.p, pCsr->term.n, SQLITE_TRANSIENT + ); + }else if( eType==FTS5_VOCAB_COL ){ + assert( iCol==1 || iCol==2 || iCol==3 ); + if( iCol==1 ){ + if( eDetail!=FTS5_DETAIL_NONE ){ + const char *z = pCsr->pFts5->pConfig->azCol[pCsr->iCol]; + sqlite3_result_text(pCtx, z, -1, SQLITE_STATIC); + } + }else if( iCol==2 ){ + iVal = pCsr->aDoc[pCsr->iCol]; + }else{ + iVal = pCsr->aCnt[pCsr->iCol]; + } + }else if( eType==FTS5_VOCAB_ROW ){ + assert( iCol==1 || iCol==2 ); + if( iCol==1 ){ + iVal = pCsr->aDoc[0]; + }else{ + iVal = pCsr->aCnt[0]; + } + }else{ + assert( eType==FTS5_VOCAB_INSTANCE ); + switch( iCol ){ + case 1: + sqlite3_result_int64(pCtx, pCsr->pIter->iRowid); + break; + case 2: { + int ii = -1; + if( eDetail==FTS5_DETAIL_FULL ){ + ii = FTS5_POS2COLUMN(pCsr->iInstPos); + }else if( eDetail==FTS5_DETAIL_COLUMNS ){ + ii = (int)pCsr->iInstPos; + } + if( ii>=0 && ii<pCsr->pFts5->pConfig->nCol ){ + const char *z = pCsr->pFts5->pConfig->azCol[ii]; + sqlite3_result_text(pCtx, z, -1, SQLITE_STATIC); + } + break; + } + default: { + assert( iCol==3 ); + if( eDetail==FTS5_DETAIL_FULL ){ + int ii = FTS5_POS2OFFSET(pCsr->iInstPos); + sqlite3_result_int(pCtx, ii); + } + break; + } + } + } + + if( iVal>0 ) sqlite3_result_int64(pCtx, iVal); + return SQLITE_OK; +} + +/* +** This is the xRowid method. The SQLite core calls this routine to +** retrieve the rowid for the current row of the result set. The +** rowid should be written to *pRowid. +*/ +static int fts5VocabRowidMethod( + sqlite3_vtab_cursor *pCursor, + sqlite_int64 *pRowid +){ + Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; + *pRowid = pCsr->rowid; + return SQLITE_OK; +} + +static int sqlite3Fts5VocabInit(Fts5Global *pGlobal, sqlite3 *db){ + static const sqlite3_module fts5Vocab = { + /* iVersion */ 2, + /* xCreate */ fts5VocabCreateMethod, + /* xConnect */ fts5VocabConnectMethod, + /* xBestIndex */ fts5VocabBestIndexMethod, + /* xDisconnect */ fts5VocabDisconnectMethod, + /* xDestroy */ fts5VocabDestroyMethod, + /* xOpen */ fts5VocabOpenMethod, + /* xClose */ fts5VocabCloseMethod, + /* xFilter */ fts5VocabFilterMethod, + /* xNext */ fts5VocabNextMethod, + /* xEof */ fts5VocabEofMethod, + /* xColumn */ fts5VocabColumnMethod, + /* xRowid */ fts5VocabRowidMethod, + /* xUpdate */ 0, + /* xBegin */ 0, + /* xSync */ 0, + /* xCommit */ 0, + /* xRollback */ 0, + /* xFindFunction */ 0, + /* xRename */ 0, + /* xSavepoint */ 0, + /* xRelease */ 0, + /* xRollbackTo */ 0, + /* xShadowName */ 0 + }; + void *p = (void*)pGlobal; + + return sqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0); +} + + + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */ + +/************** End of fts5.c ************************************************/ +/************** Begin file stmt.c ********************************************/ +/* +** 2017-05-31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file demonstrates an eponymous virtual table that returns information +** about all prepared statements for the database connection. +** +** Usage example: +** +** .load ./stmt +** .mode line +** .header on +** SELECT * FROM stmt; +*/ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) +#if !defined(SQLITEINT_H) +/* #include "sqlite3ext.h" */ +#endif +SQLITE_EXTENSION_INIT1 +/* #include <assert.h> */ +/* #include <string.h> */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE + + +#define STMT_NUM_INTEGER_COLUMN 10 +typedef struct StmtRow StmtRow; +struct StmtRow { + sqlite3_int64 iRowid; /* Rowid value */ + char *zSql; /* column "sql" */ + int aCol[STMT_NUM_INTEGER_COLUMN+1]; /* all other column values */ + StmtRow *pNext; /* Next row to return */ +}; + +/* stmt_vtab is a subclass of sqlite3_vtab which will +** serve as the underlying representation of a stmt virtual table +*/ +typedef struct stmt_vtab stmt_vtab; +struct stmt_vtab { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this stmt vtab */ +}; + +/* stmt_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct stmt_cursor stmt_cursor; +struct stmt_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this cursor */ + StmtRow *pRow; /* Current row */ +}; + +/* +** The stmtConnect() method is invoked to create a new +** stmt_vtab that describes the stmt virtual table. +** +** Think of this routine as the constructor for stmt_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the stmt_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against stmt will look like. +*/ +static int stmtConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + stmt_vtab *pNew; + int rc; + +/* Column numbers */ +#define STMT_COLUMN_SQL 0 /* SQL for the statement */ +#define STMT_COLUMN_NCOL 1 /* Number of result columns */ +#define STMT_COLUMN_RO 2 /* True if read-only */ +#define STMT_COLUMN_BUSY 3 /* True if currently busy */ +#define STMT_COLUMN_NSCAN 4 /* SQLITE_STMTSTATUS_FULLSCAN_STEP */ +#define STMT_COLUMN_NSORT 5 /* SQLITE_STMTSTATUS_SORT */ +#define STMT_COLUMN_NAIDX 6 /* SQLITE_STMTSTATUS_AUTOINDEX */ +#define STMT_COLUMN_NSTEP 7 /* SQLITE_STMTSTATUS_VM_STEP */ +#define STMT_COLUMN_REPREP 8 /* SQLITE_STMTSTATUS_REPREPARE */ +#define STMT_COLUMN_RUN 9 /* SQLITE_STMTSTATUS_RUN */ +#define STMT_COLUMN_MEM 10 /* SQLITE_STMTSTATUS_MEMUSED */ + + + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(sql,ncol,ro,busy,nscan,nsort,naidx,nstep," + "reprep,run,mem)"); + if( rc==SQLITE_OK ){ + pNew = sqlite3_malloc64( sizeof(*pNew) ); + *ppVtab = (sqlite3_vtab*)pNew; + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + pNew->db = db; + } + return rc; +} + +/* +** This method is the destructor for stmt_cursor objects. +*/ +static int stmtDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new stmt_cursor object. +*/ +static int stmtOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + stmt_cursor *pCur; + pCur = sqlite3_malloc64( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->db = ((stmt_vtab*)p)->db; + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +static void stmtCsrReset(stmt_cursor *pCur){ + StmtRow *pRow = 0; + StmtRow *pNext = 0; + for(pRow=pCur->pRow; pRow; pRow=pNext){ + pNext = pRow->pNext; + sqlite3_free(pRow); + } + pCur->pRow = 0; +} + +/* +** Destructor for a stmt_cursor. +*/ +static int stmtClose(sqlite3_vtab_cursor *cur){ + stmtCsrReset((stmt_cursor*)cur); + sqlite3_free(cur); + return SQLITE_OK; +} + + +/* +** Advance a stmt_cursor to its next row of output. +*/ +static int stmtNext(sqlite3_vtab_cursor *cur){ + stmt_cursor *pCur = (stmt_cursor*)cur; + StmtRow *pNext = pCur->pRow->pNext; + sqlite3_free(pCur->pRow); + pCur->pRow = pNext; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the stmt_cursor +** is currently pointing. +*/ +static int stmtColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + stmt_cursor *pCur = (stmt_cursor*)cur; + StmtRow *pRow = pCur->pRow; + if( i==STMT_COLUMN_SQL ){ + sqlite3_result_text(ctx, pRow->zSql, -1, SQLITE_TRANSIENT); + }else{ + sqlite3_result_int(ctx, pRow->aCol[i]); + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int stmtRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + stmt_cursor *pCur = (stmt_cursor*)cur; + *pRowid = pCur->pRow->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int stmtEof(sqlite3_vtab_cursor *cur){ + stmt_cursor *pCur = (stmt_cursor*)cur; + return pCur->pRow==0; +} + +/* +** This method is called to "rewind" the stmt_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to stmtColumn() or stmtRowid() or +** stmtEof(). +*/ +static int stmtFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + stmt_cursor *pCur = (stmt_cursor *)pVtabCursor; + sqlite3_stmt *p = 0; + sqlite3_int64 iRowid = 1; + StmtRow **ppRow = 0; + + (void)idxNum; + (void)idxStr; + (void)argc; + (void)argv; + stmtCsrReset(pCur); + ppRow = &pCur->pRow; + for(p=sqlite3_next_stmt(pCur->db, 0); p; p=sqlite3_next_stmt(pCur->db, p)){ + const char *zSql = sqlite3_sql(p); + sqlite3_int64 nSql = zSql ? strlen(zSql)+1 : 0; + StmtRow *pNew = (StmtRow*)sqlite3_malloc64(sizeof(StmtRow) + nSql); + + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(StmtRow)); + if( zSql ){ + pNew->zSql = (char*)&pNew[1]; + memcpy(pNew->zSql, zSql, nSql); + } + pNew->aCol[STMT_COLUMN_NCOL] = sqlite3_column_count(p); + pNew->aCol[STMT_COLUMN_RO] = sqlite3_stmt_readonly(p); + pNew->aCol[STMT_COLUMN_BUSY] = sqlite3_stmt_busy(p); + pNew->aCol[STMT_COLUMN_NSCAN] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_FULLSCAN_STEP, 0 + ); + pNew->aCol[STMT_COLUMN_NSORT] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_SORT, 0 + ); + pNew->aCol[STMT_COLUMN_NAIDX] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_AUTOINDEX, 0 + ); + pNew->aCol[STMT_COLUMN_NSTEP] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_VM_STEP, 0 + ); + pNew->aCol[STMT_COLUMN_REPREP] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_REPREPARE, 0 + ); + pNew->aCol[STMT_COLUMN_RUN] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_RUN, 0 + ); + pNew->aCol[STMT_COLUMN_MEM] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_MEMUSED, 0 + ); + pNew->iRowid = iRowid++; + *ppRow = pNew; + ppRow = &pNew->pNext; + } + + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the stmt virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +*/ +static int stmtBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + (void)tab; + pIdxInfo->estimatedCost = (double)500; + pIdxInfo->estimatedRows = 500; + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** stmt virtual table. +*/ +static sqlite3_module stmtModule = { + 0, /* iVersion */ + 0, /* xCreate */ + stmtConnect, /* xConnect */ + stmtBestIndex, /* xBestIndex */ + stmtDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + stmtOpen, /* xOpen - open a cursor */ + stmtClose, /* xClose - close a cursor */ + stmtFilter, /* xFilter - configure scan constraints */ + stmtNext, /* xNext - advance a cursor */ + stmtEof, /* xEof - check for end of scan */ + stmtColumn, /* xColumn - read data */ + stmtRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ +}; + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +SQLITE_PRIVATE int sqlite3StmtVtabInit(sqlite3 *db){ + int rc = SQLITE_OK; +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3_create_module(db, "sqlite_stmt", &stmtModule, 0); +#endif + return rc; +} + +#ifndef SQLITE_CORE +#ifdef _WIN32 +__declspec(dllexport) +#endif +SQLITE_API int sqlite3_stmt_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3StmtVtabInit(db); +#endif + return rc; +} +#endif /* SQLITE_CORE */ +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */ + +/************** End of stmt.c ************************************************/ +/* Return the source-id for this library */ +SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } +/************************** End of sqlite3.c ******************************/ diff --git a/SQLITE/sqlite3.h b/SQLITE/sqlite3.h new file mode 100644 index 0000000..ebbccfd --- /dev/null +++ b/SQLITE/sqlite3.h @@ -0,0 +1,13181 @@ +/* +** 2001-09-15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the SQLite library +** presents to client programs. If a C-function, structure, datatype, +** or constant definition does not appear in this file, then it is +** not a published API of SQLite, is subject to change without +** notice, and should not be referenced by programs that use SQLite. +** +** Some of the definitions that are in this file are marked as +** "experimental". Experimental interfaces are normally new +** features recently added to SQLite. We do not anticipate changes +** to experimental interfaces but reserve the right to make minor changes +** if experience from use "in the wild" suggest such changes are prudent. +** +** The official C-language API documentation for SQLite is derived +** from comments in this file. This file is the authoritative source +** on how SQLite interfaces are supposed to operate. +** +** The name of this file under configuration management is "sqlite.h.in". +** The makefile makes some minor changes to this file (such as inserting +** the version number) and changes its name to "sqlite3.h" as +** part of the build process. +*/ +#ifndef SQLITE3_H +#define SQLITE3_H +#include <stdarg.h> /* Needed for the definition of va_list */ + +/* +** Make sure we can call this stuff from C++. +*/ +#ifdef __cplusplus +extern "C" { +#endif + + +/* +** Facilitate override of interface linkage and calling conventions. +** Be aware that these macros may not be used within this particular +** translation of the amalgamation and its associated header file. +** +** The SQLITE_EXTERN and SQLITE_API macros are used to instruct the +** compiler that the target identifier should have external linkage. +** +** The SQLITE_CDECL macro is used to set the calling convention for +** public functions that accept a variable number of arguments. +** +** The SQLITE_APICALL macro is used to set the calling convention for +** public functions that accept a fixed number of arguments. +** +** The SQLITE_STDCALL macro is no longer used and is now deprecated. +** +** The SQLITE_CALLBACK macro is used to set the calling convention for +** function pointers. +** +** The SQLITE_SYSAPI macro is used to set the calling convention for +** functions provided by the operating system. +** +** Currently, the SQLITE_CDECL, SQLITE_APICALL, SQLITE_CALLBACK, and +** SQLITE_SYSAPI macros are used only when building for environments +** that require non-default calling conventions. +*/ +#ifndef SQLITE_EXTERN +# define SQLITE_EXTERN extern +#endif +#ifndef SQLITE_API +# define SQLITE_API +#endif +#ifndef SQLITE_CDECL +# define SQLITE_CDECL +#endif +#ifndef SQLITE_APICALL +# define SQLITE_APICALL +#endif +#ifndef SQLITE_STDCALL +# define SQLITE_STDCALL SQLITE_APICALL +#endif +#ifndef SQLITE_CALLBACK +# define SQLITE_CALLBACK +#endif +#ifndef SQLITE_SYSAPI +# define SQLITE_SYSAPI +#endif + +/* +** These no-op macros are used in front of interfaces to mark those +** interfaces as either deprecated or experimental. New applications +** should not use deprecated interfaces - they are supported for backwards +** compatibility only. Application writers should be aware that +** experimental interfaces are subject to change in point releases. +** +** These macros used to resolve to various kinds of compiler magic that +** would generate warning messages when they were used. But that +** compiler magic ended up generating such a flurry of bug reports +** that we have taken it all out and gone back to using simple +** noop macros. +*/ +#define SQLITE_DEPRECATED +#define SQLITE_EXPERIMENTAL + +/* +** Ensure these symbols were not defined by some previous header file. +*/ +#ifdef SQLITE_VERSION +# undef SQLITE_VERSION +#endif +#ifdef SQLITE_VERSION_NUMBER +# undef SQLITE_VERSION_NUMBER +#endif + +/* +** CAPI3REF: Compile-Time Library Version Numbers +** +** ^(The [SQLITE_VERSION] C preprocessor macro in the sqlite3.h header +** evaluates to a string literal that is the SQLite version in the +** format "X.Y.Z" where X is the major version number (always 3 for +** SQLite3) and Y is the minor version number and Z is the release number.)^ +** ^(The [SQLITE_VERSION_NUMBER] C preprocessor macro resolves to an integer +** with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same +** numbers used in [SQLITE_VERSION].)^ +** The SQLITE_VERSION_NUMBER for any given release of SQLite will also +** be larger than the release from which it is derived. Either Y will +** be held constant and Z will be incremented or else Y will be incremented +** and Z will be reset to zero. +** +** Since [version 3.6.18] ([dateof:3.6.18]), +** SQLite source code has been stored in the +** <a href="http://www.fossil-scm.org/">Fossil configuration management +** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to +** a string which identifies a particular check-in of SQLite +** within its configuration management system. ^The SQLITE_SOURCE_ID +** string contains the date and time of the check-in (UTC) and a SHA1 +** or SHA3-256 hash of the entire source tree. If the source code has +** been edited in any way since it was last checked in, then the last +** four hexadecimal digits of the hash may be modified. +** +** See also: [sqlite3_libversion()], +** [sqlite3_libversion_number()], [sqlite3_sourceid()], +** [sqlite_version()] and [sqlite_source_id()]. +*/ +#define SQLITE_VERSION "3.44.0" +#define SQLITE_VERSION_NUMBER 3044000 +#define SQLITE_SOURCE_ID "2023-09-11 15:27:27 3308fdda4b81c110ba4a66d0b325e7653c2f8155e7864aeb78991ed1da061836" + +/* +** CAPI3REF: Run-Time Library Version Numbers +** KEYWORDS: sqlite3_version sqlite3_sourceid +** +** These interfaces provide the same information as the [SQLITE_VERSION], +** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros +** but are associated with the library instead of the header file. ^(Cautious +** programmers might include assert() statements in their application to +** verify that values returned by these interfaces match the macros in +** the header, and thus ensure that the application is +** compiled with matching library and header files. +** +** <blockquote><pre> +** assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER ); +** assert( strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,80)==0 ); +** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 ); +** </pre></blockquote>)^ +** +** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION] +** macro. ^The sqlite3_libversion() function returns a pointer to the +** to the sqlite3_version[] string constant. The sqlite3_libversion() +** function is provided for use in DLLs since DLL users usually do not have +** direct access to string constants within the DLL. ^The +** sqlite3_libversion_number() function returns an integer equal to +** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns +** a pointer to a string constant whose value is the same as the +** [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built +** using an edited copy of [the amalgamation], then the last four characters +** of the hash might be different from [SQLITE_SOURCE_ID].)^ +** +** See also: [sqlite_version()] and [sqlite_source_id()]. +*/ +SQLITE_API SQLITE_EXTERN const char sqlite3_version[]; +SQLITE_API const char *sqlite3_libversion(void); +SQLITE_API const char *sqlite3_sourceid(void); +SQLITE_API int sqlite3_libversion_number(void); + +/* +** CAPI3REF: Run-Time Library Compilation Options Diagnostics +** +** ^The sqlite3_compileoption_used() function returns 0 or 1 +** indicating whether the specified option was defined at +** compile time. ^The SQLITE_ prefix may be omitted from the +** option name passed to sqlite3_compileoption_used(). +** +** ^The sqlite3_compileoption_get() function allows iterating +** over the list of options that were defined at compile time by +** returning the N-th compile time option string. ^If N is out of range, +** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ +** prefix is omitted from any strings returned by +** sqlite3_compileoption_get(). +** +** ^Support for the diagnostic functions sqlite3_compileoption_used() +** and sqlite3_compileoption_get() may be omitted by specifying the +** [SQLITE_OMIT_COMPILEOPTION_DIAGS] option at compile time. +** +** See also: SQL functions [sqlite_compileoption_used()] and +** [sqlite_compileoption_get()] and the [compile_options pragma]. +*/ +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS +SQLITE_API int sqlite3_compileoption_used(const char *zOptName); +SQLITE_API const char *sqlite3_compileoption_get(int N); +#else +# define sqlite3_compileoption_used(X) 0 +# define sqlite3_compileoption_get(X) ((void*)0) +#endif + +/* +** CAPI3REF: Test To See If The Library Is Threadsafe +** +** ^The sqlite3_threadsafe() function returns zero if and only if +** SQLite was compiled with mutexing code omitted due to the +** [SQLITE_THREADSAFE] compile-time option being set to 0. +** +** SQLite can be compiled with or without mutexes. When +** the [SQLITE_THREADSAFE] C preprocessor macro is 1 or 2, mutexes +** are enabled and SQLite is threadsafe. When the +** [SQLITE_THREADSAFE] macro is 0, +** the mutexes are omitted. Without the mutexes, it is not safe +** to use SQLite concurrently from more than one thread. +** +** Enabling mutexes incurs a measurable performance penalty. +** So if speed is of utmost importance, it makes sense to disable +** the mutexes. But for maximum safety, mutexes should be enabled. +** ^The default behavior is for mutexes to be enabled. +** +** This interface can be used by an application to make sure that the +** version of SQLite that it is linking against was compiled with +** the desired setting of the [SQLITE_THREADSAFE] macro. +** +** This interface only reports on the compile-time mutex setting +** of the [SQLITE_THREADSAFE] flag. If SQLite is compiled with +** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but +** can be fully or partially disabled using a call to [sqlite3_config()] +** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD], +** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the +** sqlite3_threadsafe() function shows only the compile-time setting of +** thread safety, not any run-time changes to that setting made by +** sqlite3_config(). In other words, the return value from sqlite3_threadsafe() +** is unchanged by calls to sqlite3_config().)^ +** +** See the [threading mode] documentation for additional information. +*/ +SQLITE_API int sqlite3_threadsafe(void); + +/* +** CAPI3REF: Database Connection Handle +** KEYWORDS: {database connection} {database connections} +** +** Each open SQLite database is represented by a pointer to an instance of +** the opaque structure named "sqlite3". It is useful to think of an sqlite3 +** pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and +** [sqlite3_open_v2()] interfaces are its constructors, and [sqlite3_close()] +** and [sqlite3_close_v2()] are its destructors. There are many other +** interfaces (such as +** [sqlite3_prepare_v2()], [sqlite3_create_function()], and +** [sqlite3_busy_timeout()] to name but three) that are methods on an +** sqlite3 object. +*/ +typedef struct sqlite3 sqlite3; + +/* +** CAPI3REF: 64-Bit Integer Types +** KEYWORDS: sqlite_int64 sqlite_uint64 +** +** Because there is no cross-platform way to specify 64-bit integer types +** SQLite includes typedefs for 64-bit signed and unsigned integers. +** +** The sqlite3_int64 and sqlite3_uint64 are the preferred type definitions. +** The sqlite_int64 and sqlite_uint64 types are supported for backwards +** compatibility only. +** +** ^The sqlite3_int64 and sqlite_int64 types can store integer values +** between -9223372036854775808 and +9223372036854775807 inclusive. ^The +** sqlite3_uint64 and sqlite_uint64 types can store integer values +** between 0 and +18446744073709551615 inclusive. +*/ +#ifdef SQLITE_INT64_TYPE + typedef SQLITE_INT64_TYPE sqlite_int64; +# ifdef SQLITE_UINT64_TYPE + typedef SQLITE_UINT64_TYPE sqlite_uint64; +# else + typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; +# endif +#elif defined(_MSC_VER) || defined(__BORLANDC__) + typedef __int64 sqlite_int64; + typedef unsigned __int64 sqlite_uint64; +#else + typedef long long int sqlite_int64; + typedef unsigned long long int sqlite_uint64; +#endif +typedef sqlite_int64 sqlite3_int64; +typedef sqlite_uint64 sqlite3_uint64; + +/* +** If compiling for a processor that lacks floating point support, +** substitute integer for floating-point. +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# define double sqlite3_int64 +#endif + +/* +** CAPI3REF: Closing A Database Connection +** DESTRUCTOR: sqlite3 +** +** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors +** for the [sqlite3] object. +** ^Calls to sqlite3_close() and sqlite3_close_v2() return [SQLITE_OK] if +** the [sqlite3] object is successfully destroyed and all associated +** resources are deallocated. +** +** Ideally, applications should [sqlite3_finalize | finalize] all +** [prepared statements], [sqlite3_blob_close | close] all [BLOB handles], and +** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated +** with the [sqlite3] object prior to attempting to close the object. +** ^If the database connection is associated with unfinalized prepared +** statements, BLOB handlers, and/or unfinished sqlite3_backup objects then +** sqlite3_close() will leave the database connection open and return +** [SQLITE_BUSY]. ^If sqlite3_close_v2() is called with unfinalized prepared +** statements, unclosed BLOB handlers, and/or unfinished sqlite3_backups, +** it returns [SQLITE_OK] regardless, but instead of deallocating the database +** connection immediately, it marks the database connection as an unusable +** "zombie" and makes arrangements to automatically deallocate the database +** connection after all prepared statements are finalized, all BLOB handles +** are closed, and all backups have finished. The sqlite3_close_v2() interface +** is intended for use with host languages that are garbage collected, and +** where the order in which destructors are called is arbitrary. +** +** ^If an [sqlite3] object is destroyed while a transaction is open, +** the transaction is automatically rolled back. +** +** The C parameter to [sqlite3_close(C)] and [sqlite3_close_v2(C)] +** must be either a NULL +** pointer or an [sqlite3] object pointer obtained +** from [sqlite3_open()], [sqlite3_open16()], or +** [sqlite3_open_v2()], and not previously closed. +** ^Calling sqlite3_close() or sqlite3_close_v2() with a NULL pointer +** argument is a harmless no-op. +*/ +SQLITE_API int sqlite3_close(sqlite3*); +SQLITE_API int sqlite3_close_v2(sqlite3*); + +/* +** The type for a callback function. +** This is legacy and deprecated. It is included for historical +** compatibility and is not documented. +*/ +typedef int (*sqlite3_callback)(void*,int,char**, char**); + +/* +** CAPI3REF: One-Step Query Execution Interface +** METHOD: sqlite3 +** +** The sqlite3_exec() interface is a convenience wrapper around +** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()], +** that allows an application to run multiple statements of SQL +** without having to use a lot of C code. +** +** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded, +** semicolon-separate SQL statements passed into its 2nd argument, +** in the context of the [database connection] passed in as its 1st +** argument. ^If the callback function of the 3rd argument to +** sqlite3_exec() is not NULL, then it is invoked for each result row +** coming out of the evaluated SQL statements. ^The 4th argument to +** sqlite3_exec() is relayed through to the 1st argument of each +** callback invocation. ^If the callback pointer to sqlite3_exec() +** is NULL, then no callback is ever invoked and result rows are +** ignored. +** +** ^If an error occurs while evaluating the SQL statements passed into +** sqlite3_exec(), then execution of the current statement stops and +** subsequent statements are skipped. ^If the 5th parameter to sqlite3_exec() +** is not NULL then any error message is written into memory obtained +** from [sqlite3_malloc()] and passed back through the 5th parameter. +** To avoid memory leaks, the application should invoke [sqlite3_free()] +** on error message strings returned through the 5th parameter of +** sqlite3_exec() after the error message string is no longer needed. +** ^If the 5th parameter to sqlite3_exec() is not NULL and no errors +** occur, then sqlite3_exec() sets the pointer in its 5th parameter to +** NULL before returning. +** +** ^If an sqlite3_exec() callback returns non-zero, the sqlite3_exec() +** routine returns SQLITE_ABORT without invoking the callback again and +** without running any subsequent SQL statements. +** +** ^The 2nd argument to the sqlite3_exec() callback function is the +** number of columns in the result. ^The 3rd argument to the sqlite3_exec() +** callback is an array of pointers to strings obtained as if from +** [sqlite3_column_text()], one for each column. ^If an element of a +** result row is NULL then the corresponding string pointer for the +** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the +** sqlite3_exec() callback is an array of pointers to strings where each +** entry represents the name of corresponding result column as obtained +** from [sqlite3_column_name()]. +** +** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer +** to an empty string, or a pointer that contains only whitespace and/or +** SQL comments, then no SQL statements are evaluated and the database +** is not changed. +** +** Restrictions: +** +** <ul> +** <li> The application must ensure that the 1st parameter to sqlite3_exec() +** is a valid and open [database connection]. +** <li> The application must not close the [database connection] specified by +** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running. +** <li> The application must not modify the SQL statement text passed into +** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running. +** </ul> +*/ +SQLITE_API int sqlite3_exec( + sqlite3*, /* An open database */ + const char *sql, /* SQL to be evaluated */ + int (*callback)(void*,int,char**,char**), /* Callback function */ + void *, /* 1st argument to callback */ + char **errmsg /* Error msg written here */ +); + +/* +** CAPI3REF: Result Codes +** KEYWORDS: {result code definitions} +** +** Many SQLite functions return an integer result code from the set shown +** here in order to indicate success or failure. +** +** New error codes may be added in future versions of SQLite. +** +** See also: [extended result code definitions] +*/ +#define SQLITE_OK 0 /* Successful result */ +/* beginning-of-error-codes */ +#define SQLITE_ERROR 1 /* Generic error */ +#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ +#define SQLITE_PERM 3 /* Access permission denied */ +#define SQLITE_ABORT 4 /* Callback routine requested an abort */ +#define SQLITE_BUSY 5 /* The database file is locked */ +#define SQLITE_LOCKED 6 /* A table in the database is locked */ +#define SQLITE_NOMEM 7 /* A malloc() failed */ +#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ +#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ +#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ +#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ +#define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */ +#define SQLITE_FULL 13 /* Insertion failed because database is full */ +#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ +#define SQLITE_PROTOCOL 15 /* Database lock protocol error */ +#define SQLITE_EMPTY 16 /* Internal use only */ +#define SQLITE_SCHEMA 17 /* The database schema changed */ +#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ +#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ +#define SQLITE_MISMATCH 20 /* Data type mismatch */ +#define SQLITE_MISUSE 21 /* Library used incorrectly */ +#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ +#define SQLITE_AUTH 23 /* Authorization denied */ +#define SQLITE_FORMAT 24 /* Not used */ +#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ +#define SQLITE_NOTADB 26 /* File opened that is not a database file */ +#define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */ +#define SQLITE_WARNING 28 /* Warnings from sqlite3_log() */ +#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ +#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ +/* end-of-error-codes */ + +/* +** CAPI3REF: Extended Result Codes +** KEYWORDS: {extended result code definitions} +** +** In its default configuration, SQLite API routines return one of 30 integer +** [result codes]. However, experience has shown that many of +** these result codes are too coarse-grained. They do not provide as +** much information about problems as programmers might like. In an effort to +** address this, newer versions of SQLite (version 3.3.8 [dateof:3.3.8] +** and later) include +** support for additional result codes that provide more detailed information +** about errors. These [extended result codes] are enabled or disabled +** on a per database connection basis using the +** [sqlite3_extended_result_codes()] API. Or, the extended code for +** the most recent error can be obtained using +** [sqlite3_extended_errcode()]. +*/ +#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) +#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) +#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) +#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) +#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) +#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) +#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) +#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) +#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6<<8)) +#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7<<8)) +#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8<<8)) +#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9<<8)) +#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10<<8)) +#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11<<8)) +#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12<<8)) +#define SQLITE_IOERR_ACCESS (SQLITE_IOERR | (13<<8)) +#define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14<<8)) +#define SQLITE_IOERR_LOCK (SQLITE_IOERR | (15<<8)) +#define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16<<8)) +#define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17<<8)) +#define SQLITE_IOERR_SHMOPEN (SQLITE_IOERR | (18<<8)) +#define SQLITE_IOERR_SHMSIZE (SQLITE_IOERR | (19<<8)) +#define SQLITE_IOERR_SHMLOCK (SQLITE_IOERR | (20<<8)) +#define SQLITE_IOERR_SHMMAP (SQLITE_IOERR | (21<<8)) +#define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22<<8)) +#define SQLITE_IOERR_DELETE_NOENT (SQLITE_IOERR | (23<<8)) +#define SQLITE_IOERR_MMAP (SQLITE_IOERR | (24<<8)) +#define SQLITE_IOERR_GETTEMPPATH (SQLITE_IOERR | (25<<8)) +#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26<<8)) +#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8)) +#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8)) +#define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29<<8)) +#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) +#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) +#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) +#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) +#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) +#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) +#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) +#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) +#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) +#define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) +#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8)) +#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8)) +#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8)) +#define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8)) +#define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5<<8)) /* Not Used */ +#define SQLITE_CANTOPEN_SYMLINK (SQLITE_CANTOPEN | (6<<8)) +#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) +#define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8)) +#define SQLITE_CORRUPT_INDEX (SQLITE_CORRUPT | (3<<8)) +#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) +#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8)) +#define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8)) +#define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4<<8)) +#define SQLITE_READONLY_CANTINIT (SQLITE_READONLY | (5<<8)) +#define SQLITE_READONLY_DIRECTORY (SQLITE_READONLY | (6<<8)) +#define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2<<8)) +#define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1<<8)) +#define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2<<8)) +#define SQLITE_CONSTRAINT_FOREIGNKEY (SQLITE_CONSTRAINT | (3<<8)) +#define SQLITE_CONSTRAINT_FUNCTION (SQLITE_CONSTRAINT | (4<<8)) +#define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5<<8)) +#define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6<<8)) +#define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7<<8)) +#define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) +#define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) +#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) +#define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT |(11<<8)) +#define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8)) +#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) +#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) +#define SQLITE_NOTICE_RBU (SQLITE_NOTICE | (3<<8)) +#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) +#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) +#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) +#define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8)) /* internal use only */ + +/* +** CAPI3REF: Flags For File Open Operations +** +** These bit values are intended for use in the +** 3rd parameter to the [sqlite3_open_v2()] interface and +** in the 4th parameter to the [sqlite3_vfs.xOpen] method. +** +** Only those flags marked as "Ok for sqlite3_open_v2()" may be +** used as the third argument to the [sqlite3_open_v2()] interface. +** The other flags have historically been ignored by sqlite3_open_v2(), +** though future versions of SQLite might change so that an error is +** raised if any of the disallowed bits are passed into sqlite3_open_v2(). +** Applications should not depend on the historical behavior. +** +** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into +** [sqlite3_open_v2()] does *not* cause the underlying database file +** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into +** [sqlite3_open_v2()] has historically be a no-op and might become an +** error in future versions of SQLite. +*/ +#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_DELETEONCLOSE 0x00000008 /* VFS only */ +#define SQLITE_OPEN_EXCLUSIVE 0x00000010 /* VFS only */ +#define SQLITE_OPEN_AUTOPROXY 0x00000020 /* VFS only */ +#define SQLITE_OPEN_URI 0x00000040 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */ +#define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */ +#define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */ +#define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 /* VFS only */ +#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */ +#define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */ +#define SQLITE_OPEN_SUPER_JOURNAL 0x00004000 /* VFS only */ +#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ +#define SQLITE_OPEN_NOFOLLOW 0x01000000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_EXRESCODE 0x02000000 /* Extended result codes */ + +/* Reserved: 0x00F00000 */ +/* Legacy compatibility: */ +#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */ + + +/* +** CAPI3REF: Device Characteristics +** +** The xDeviceCharacteristics method of the [sqlite3_io_methods] +** object returns an integer which is a vector of these +** bit values expressing I/O characteristics of the mass storage +** device that holds the file that the [sqlite3_io_methods] +** refers to. +** +** The SQLITE_IOCAP_ATOMIC property means that all writes of +** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values +** mean that writes of blocks that are nnn bytes in size and +** are aligned to an address which is an integer multiple of +** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means +** that when data is appended to a file, the data is appended +** first then the size of the file is extended, never the other +** way around. The SQLITE_IOCAP_SEQUENTIAL property means that +** information is written to disk in the same order as calls +** to xWrite(). The SQLITE_IOCAP_POWERSAFE_OVERWRITE property means that +** after reboot following a crash or power loss, the only bytes in a +** file that were written at the application level might have changed +** and that adjacent bytes, even bytes within the same sector are +** guaranteed to be unchanged. The SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN +** flag indicates that a file cannot be deleted when open. The +** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on +** read-only media and cannot be changed even by processes with +** elevated privileges. +** +** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying +** filesystem supports doing multiple write operations atomically when those +** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and +** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. +*/ +#define SQLITE_IOCAP_ATOMIC 0x00000001 +#define SQLITE_IOCAP_ATOMIC512 0x00000002 +#define SQLITE_IOCAP_ATOMIC1K 0x00000004 +#define SQLITE_IOCAP_ATOMIC2K 0x00000008 +#define SQLITE_IOCAP_ATOMIC4K 0x00000010 +#define SQLITE_IOCAP_ATOMIC8K 0x00000020 +#define SQLITE_IOCAP_ATOMIC16K 0x00000040 +#define SQLITE_IOCAP_ATOMIC32K 0x00000080 +#define SQLITE_IOCAP_ATOMIC64K 0x00000100 +#define SQLITE_IOCAP_SAFE_APPEND 0x00000200 +#define SQLITE_IOCAP_SEQUENTIAL 0x00000400 +#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 +#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 +#define SQLITE_IOCAP_IMMUTABLE 0x00002000 +#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 + +/* +** CAPI3REF: File Locking Levels +** +** SQLite uses one of these integer values as the second +** argument to calls it makes to the xLock() and xUnlock() methods +** of an [sqlite3_io_methods] object. These values are ordered from +** lest restrictive to most restrictive. +** +** The argument to xLock() is always SHARED or higher. The argument to +** xUnlock is either SHARED or NONE. +*/ +#define SQLITE_LOCK_NONE 0 /* xUnlock() only */ +#define SQLITE_LOCK_SHARED 1 /* xLock() or xUnlock() */ +#define SQLITE_LOCK_RESERVED 2 /* xLock() only */ +#define SQLITE_LOCK_PENDING 3 /* xLock() only */ +#define SQLITE_LOCK_EXCLUSIVE 4 /* xLock() only */ + +/* +** CAPI3REF: Synchronization Type Flags +** +** When SQLite invokes the xSync() method of an +** [sqlite3_io_methods] object it uses a combination of +** these integer values as the second argument. +** +** When the SQLITE_SYNC_DATAONLY flag is used, it means that the +** sync operation only needs to flush data to mass storage. Inode +** information need not be flushed. If the lower four bits of the flag +** equal SQLITE_SYNC_NORMAL, that means to use normal fsync() semantics. +** If the lower four bits equal SQLITE_SYNC_FULL, that means +** to use Mac OS X style fullsync instead of fsync(). +** +** Do not confuse the SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL flags +** with the [PRAGMA synchronous]=NORMAL and [PRAGMA synchronous]=FULL +** settings. The [synchronous pragma] determines when calls to the +** xSync VFS method occur and applies uniformly across all platforms. +** The SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL flags determine how +** energetic or rigorous or forceful the sync operations are and +** only make a difference on Mac OSX for the default SQLite code. +** (Third-party VFS implementations might also make the distinction +** between SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL, but among the +** operating systems natively supported by SQLite, only Mac OSX +** cares about the difference.) +*/ +#define SQLITE_SYNC_NORMAL 0x00002 +#define SQLITE_SYNC_FULL 0x00003 +#define SQLITE_SYNC_DATAONLY 0x00010 + +/* +** CAPI3REF: OS Interface Open File Handle +** +** An [sqlite3_file] object represents an open file in the +** [sqlite3_vfs | OS interface layer]. Individual OS interface +** implementations will +** want to subclass this object by appending additional fields +** for their own use. The pMethods entry is a pointer to an +** [sqlite3_io_methods] object that defines methods for performing +** I/O operations on the open file. +*/ +typedef struct sqlite3_file sqlite3_file; +struct sqlite3_file { + const struct sqlite3_io_methods *pMethods; /* Methods for an open file */ +}; + +/* +** CAPI3REF: OS Interface File Virtual Methods Object +** +** Every file opened by the [sqlite3_vfs.xOpen] method populates an +** [sqlite3_file] object (or, more commonly, a subclass of the +** [sqlite3_file] object) with a pointer to an instance of this object. +** This object defines the methods used to perform various operations +** against the open file represented by the [sqlite3_file] object. +** +** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element +** to a non-NULL pointer, then the sqlite3_io_methods.xClose method +** may be invoked even if the [sqlite3_vfs.xOpen] reported that it failed. The +** only way to prevent a call to xClose following a failed [sqlite3_vfs.xOpen] +** is for the [sqlite3_vfs.xOpen] to set the sqlite3_file.pMethods element +** to NULL. +** +** The flags argument to xSync may be one of [SQLITE_SYNC_NORMAL] or +** [SQLITE_SYNC_FULL]. The first choice is the normal fsync(). +** The second choice is a Mac OS X style fullsync. The [SQLITE_SYNC_DATAONLY] +** flag may be ORed in to indicate that only the data of the file +** and not its inode needs to be synced. +** +** The integer values to xLock() and xUnlock() are one of +** <ul> +** <li> [SQLITE_LOCK_NONE], +** <li> [SQLITE_LOCK_SHARED], +** <li> [SQLITE_LOCK_RESERVED], +** <li> [SQLITE_LOCK_PENDING], or +** <li> [SQLITE_LOCK_EXCLUSIVE]. +** </ul> +** xLock() upgrades the database file lock. In other words, xLock() moves the +** database file lock in the direction NONE toward EXCLUSIVE. The argument to +** xLock() is always on of SHARED, RESERVED, PENDING, or EXCLUSIVE, never +** SQLITE_LOCK_NONE. If the database file lock is already at or above the +** requested lock, then the call to xLock() is a no-op. +** xUnlock() downgrades the database file lock to either SHARED or NONE. +* If the lock is already at or below the requested lock state, then the call +** to xUnlock() is a no-op. +** The xCheckReservedLock() method checks whether any database connection, +** either in this process or in some other process, is holding a RESERVED, +** PENDING, or EXCLUSIVE lock on the file. It returns true +** if such a lock exists and false otherwise. +** +** The xFileControl() method is a generic interface that allows custom +** VFS implementations to directly control an open file using the +** [sqlite3_file_control()] interface. The second "op" argument is an +** integer opcode. The third argument is a generic pointer intended to +** point to a structure that may contain arguments or space in which to +** write return values. Potential uses for xFileControl() might be +** functions to enable blocking locks with timeouts, to change the +** locking strategy (for example to use dot-file locks), to inquire +** about the status of a lock, or to break stale locks. The SQLite +** core reserves all opcodes less than 100 for its own use. +** A [file control opcodes | list of opcodes] less than 100 is available. +** Applications that define a custom xFileControl method should use opcodes +** greater than 100 to avoid conflicts. VFS implementations should +** return [SQLITE_NOTFOUND] for file control opcodes that they do not +** recognize. +** +** The xSectorSize() method returns the sector size of the +** device that underlies the file. The sector size is the +** minimum write that can be performed without disturbing +** other bytes in the file. The xDeviceCharacteristics() +** method returns a bit vector describing behaviors of the +** underlying device: +** +** <ul> +** <li> [SQLITE_IOCAP_ATOMIC] +** <li> [SQLITE_IOCAP_ATOMIC512] +** <li> [SQLITE_IOCAP_ATOMIC1K] +** <li> [SQLITE_IOCAP_ATOMIC2K] +** <li> [SQLITE_IOCAP_ATOMIC4K] +** <li> [SQLITE_IOCAP_ATOMIC8K] +** <li> [SQLITE_IOCAP_ATOMIC16K] +** <li> [SQLITE_IOCAP_ATOMIC32K] +** <li> [SQLITE_IOCAP_ATOMIC64K] +** <li> [SQLITE_IOCAP_SAFE_APPEND] +** <li> [SQLITE_IOCAP_SEQUENTIAL] +** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] +** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE] +** <li> [SQLITE_IOCAP_IMMUTABLE] +** <li> [SQLITE_IOCAP_BATCH_ATOMIC] +** </ul> +** +** The SQLITE_IOCAP_ATOMIC property means that all writes of +** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values +** mean that writes of blocks that are nnn bytes in size and +** are aligned to an address which is an integer multiple of +** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means +** that when data is appended to a file, the data is appended +** first then the size of the file is extended, never the other +** way around. The SQLITE_IOCAP_SEQUENTIAL property means that +** information is written to disk in the same order as calls +** to xWrite(). +** +** If xRead() returns SQLITE_IOERR_SHORT_READ it must also fill +** in the unread portions of the buffer with zeros. A VFS that +** fails to zero-fill short reads might seem to work. However, +** failure to zero-fill short reads will eventually lead to +** database corruption. +*/ +typedef struct sqlite3_io_methods sqlite3_io_methods; +struct sqlite3_io_methods { + int iVersion; + int (*xClose)(sqlite3_file*); + int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); + int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst); + int (*xTruncate)(sqlite3_file*, sqlite3_int64 size); + int (*xSync)(sqlite3_file*, int flags); + int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize); + int (*xLock)(sqlite3_file*, int); + int (*xUnlock)(sqlite3_file*, int); + int (*xCheckReservedLock)(sqlite3_file*, int *pResOut); + int (*xFileControl)(sqlite3_file*, int op, void *pArg); + int (*xSectorSize)(sqlite3_file*); + int (*xDeviceCharacteristics)(sqlite3_file*); + /* Methods above are valid for version 1 */ + int (*xShmMap)(sqlite3_file*, int iPg, int pgsz, int, void volatile**); + int (*xShmLock)(sqlite3_file*, int offset, int n, int flags); + void (*xShmBarrier)(sqlite3_file*); + int (*xShmUnmap)(sqlite3_file*, int deleteFlag); + /* Methods above are valid for version 2 */ + int (*xFetch)(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); + int (*xUnfetch)(sqlite3_file*, sqlite3_int64 iOfst, void *p); + /* Methods above are valid for version 3 */ + /* Additional methods may be added in future releases */ +}; + +/* +** CAPI3REF: Standard File Control Opcodes +** KEYWORDS: {file control opcodes} {file control opcode} +** +** These integer constants are opcodes for the xFileControl method +** of the [sqlite3_io_methods] object and for the [sqlite3_file_control()] +** interface. +** +** <ul> +** <li>[[SQLITE_FCNTL_LOCKSTATE]] +** The [SQLITE_FCNTL_LOCKSTATE] opcode is used for debugging. This +** opcode causes the xFileControl method to write the current state of +** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED], +** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE]) +** into an integer that the pArg argument points to. +** This capability is only available if SQLite is compiled with [SQLITE_DEBUG]. +** +** <li>[[SQLITE_FCNTL_SIZE_HINT]] +** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS +** layer a hint of how large the database file will grow to be during the +** current transaction. This hint is not guaranteed to be accurate but it +** is often close. The underlying VFS might choose to preallocate database +** file space based on this hint in order to help writes to the database +** file run faster. +** +** <li>[[SQLITE_FCNTL_SIZE_LIMIT]] +** The [SQLITE_FCNTL_SIZE_LIMIT] opcode is used by in-memory VFS that +** implements [sqlite3_deserialize()] to set an upper bound on the size +** of the in-memory database. The argument is a pointer to a [sqlite3_int64]. +** If the integer pointed to is negative, then it is filled in with the +** current limit. Otherwise the limit is set to the larger of the value +** of the integer pointed to and the current database size. The integer +** pointed to is set to the new limit. +** +** <li>[[SQLITE_FCNTL_CHUNK_SIZE]] +** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS +** extends and truncates the database file in chunks of a size specified +** by the user. The fourth argument to [sqlite3_file_control()] should +** point to an integer (type int) containing the new chunk-size to use +** for the nominated database. Allocating database file space in large +** chunks (say 1MB at a time), may reduce file-system fragmentation and +** improve performance on some systems. +** +** <li>[[SQLITE_FCNTL_FILE_POINTER]] +** The [SQLITE_FCNTL_FILE_POINTER] opcode is used to obtain a pointer +** to the [sqlite3_file] object associated with a particular database +** connection. See also [SQLITE_FCNTL_JOURNAL_POINTER]. +** +** <li>[[SQLITE_FCNTL_JOURNAL_POINTER]] +** The [SQLITE_FCNTL_JOURNAL_POINTER] opcode is used to obtain a pointer +** to the [sqlite3_file] object associated with the journal file (either +** the [rollback journal] or the [write-ahead log]) for a particular database +** connection. See also [SQLITE_FCNTL_FILE_POINTER]. +** +** <li>[[SQLITE_FCNTL_SYNC_OMITTED]] +** No longer in use. +** +** <li>[[SQLITE_FCNTL_SYNC]] +** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and +** sent to the VFS immediately before the xSync method is invoked on a +** database file descriptor. Or, if the xSync method is not invoked +** because the user has configured SQLite with +** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place +** of the xSync method. In most cases, the pointer argument passed with +** this file-control is NULL. However, if the database file is being synced +** as part of a multi-database commit, the argument points to a nul-terminated +** string containing the transactions super-journal file name. VFSes that +** do not need this signal should silently ignore this opcode. Applications +** should not call [sqlite3_file_control()] with this opcode as doing so may +** disrupt the operation of the specialized VFSes that do require it. +** +** <li>[[SQLITE_FCNTL_COMMIT_PHASETWO]] +** The [SQLITE_FCNTL_COMMIT_PHASETWO] opcode is generated internally by SQLite +** and sent to the VFS after a transaction has been committed immediately +** but before the database is unlocked. VFSes that do not need this signal +** should silently ignore this opcode. Applications should not call +** [sqlite3_file_control()] with this opcode as doing so may disrupt the +** operation of the specialized VFSes that do require it. +** +** <li>[[SQLITE_FCNTL_WIN32_AV_RETRY]] +** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic +** retry counts and intervals for certain disk I/O operations for the +** windows [VFS] in order to provide robustness in the presence of +** anti-virus programs. By default, the windows VFS will retry file read, +** file write, and file delete operations up to 10 times, with a delay +** of 25 milliseconds before the first retry and with the delay increasing +** by an additional 25 milliseconds with each subsequent retry. This +** opcode allows these two values (10 retries and 25 milliseconds of delay) +** to be adjusted. The values are changed for all database connections +** within the same process. The argument is a pointer to an array of two +** integers where the first integer is the new retry count and the second +** integer is the delay. If either integer is negative, then the setting +** is not changed but instead the prior value of that setting is written +** into the array entry, allowing the current retry settings to be +** interrogated. The zDbName parameter is ignored. +** +** <li>[[SQLITE_FCNTL_PERSIST_WAL]] +** ^The [SQLITE_FCNTL_PERSIST_WAL] opcode is used to set or query the +** persistent [WAL | Write Ahead Log] setting. By default, the auxiliary +** write ahead log ([WAL file]) and shared memory +** files used for transaction control +** are automatically deleted when the latest connection to the database +** closes. Setting persistent WAL mode causes those files to persist after +** close. Persisting the files is useful when other processes that do not +** have write permission on the directory containing the database file want +** to read the database file, as the WAL and shared memory files must exist +** in order for the database to be readable. The fourth parameter to +** [sqlite3_file_control()] for this opcode should be a pointer to an integer. +** That integer is 0 to disable persistent WAL mode or 1 to enable persistent +** WAL mode. If the integer is -1, then it is overwritten with the current +** WAL persistence setting. +** +** <li>[[SQLITE_FCNTL_POWERSAFE_OVERWRITE]] +** ^The [SQLITE_FCNTL_POWERSAFE_OVERWRITE] opcode is used to set or query the +** persistent "powersafe-overwrite" or "PSOW" setting. The PSOW setting +** determines the [SQLITE_IOCAP_POWERSAFE_OVERWRITE] bit of the +** xDeviceCharacteristics methods. The fourth parameter to +** [sqlite3_file_control()] for this opcode should be a pointer to an integer. +** That integer is 0 to disable zero-damage mode or 1 to enable zero-damage +** mode. If the integer is -1, then it is overwritten with the current +** zero-damage mode setting. +** +** <li>[[SQLITE_FCNTL_OVERWRITE]] +** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening +** a write transaction to indicate that, unless it is rolled back for some +** reason, the entire database file will be overwritten by the current +** transaction. This is used by VACUUM operations. +** +** <li>[[SQLITE_FCNTL_VFSNAME]] +** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of +** all [VFSes] in the VFS stack. The names are of all VFS shims and the +** final bottom-level VFS are written into memory obtained from +** [sqlite3_malloc()] and the result is stored in the char* variable +** that the fourth parameter of [sqlite3_file_control()] points to. +** The caller is responsible for freeing the memory when done. As with +** all file-control actions, there is no guarantee that this will actually +** do anything. Callers should initialize the char* variable to a NULL +** pointer in case this file-control is not implemented. This file-control +** is intended for diagnostic use only. +** +** <li>[[SQLITE_FCNTL_VFS_POINTER]] +** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level +** [VFSes] currently in use. ^(The argument X in +** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be +** of type "[sqlite3_vfs] **". This opcodes will set *X +** to a pointer to the top-level VFS.)^ +** ^When there are multiple VFS shims in the stack, this opcode finds the +** upper-most shim only. +** +** <li>[[SQLITE_FCNTL_PRAGMA]] +** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] +** file control is sent to the open [sqlite3_file] object corresponding +** to the database file to which the pragma statement refers. ^The argument +** to the [SQLITE_FCNTL_PRAGMA] file control is an array of +** pointers to strings (char**) in which the second element of the array +** is the name of the pragma and the third element is the argument to the +** pragma or NULL if the pragma has no argument. ^The handler for an +** [SQLITE_FCNTL_PRAGMA] file control can optionally make the first element +** of the char** argument point to a string obtained from [sqlite3_mprintf()] +** or the equivalent and that string will become the result of the pragma or +** the error message if the pragma fails. ^If the +** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal +** [PRAGMA] processing continues. ^If the [SQLITE_FCNTL_PRAGMA] +** file control returns [SQLITE_OK], then the parser assumes that the +** VFS has handled the PRAGMA itself and the parser generates a no-op +** prepared statement if result string is NULL, or that returns a copy +** of the result string if the string is non-NULL. +** ^If the [SQLITE_FCNTL_PRAGMA] file control returns +** any result code other than [SQLITE_OK] or [SQLITE_NOTFOUND], that means +** that the VFS encountered an error while handling the [PRAGMA] and the +** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] +** file control occurs at the beginning of pragma statement analysis and so +** it is able to override built-in [PRAGMA] statements. +** +** <li>[[SQLITE_FCNTL_BUSYHANDLER]] +** ^The [SQLITE_FCNTL_BUSYHANDLER] +** file-control may be invoked by SQLite on the database file handle +** shortly after it is opened in order to provide a custom VFS with access +** to the connection's busy-handler callback. The argument is of type (void**) +** - an array of two (void *) values. The first (void *) actually points +** to a function of type (int (*)(void *)). In order to invoke the connection's +** busy-handler, this function should be invoked with the second (void *) in +** the array as the only argument. If it returns non-zero, then the operation +** should be retried. If it returns zero, the custom VFS should abandon the +** current operation. +** +** <li>[[SQLITE_FCNTL_TEMPFILENAME]] +** ^Applications can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control +** to have SQLite generate a +** temporary filename using the same algorithm that is followed to generate +** temporary filenames for TEMP tables and other internal uses. The +** argument should be a char** which will be filled with the filename +** written into memory obtained from [sqlite3_malloc()]. The caller should +** invoke [sqlite3_free()] on the result to avoid a memory leak. +** +** <li>[[SQLITE_FCNTL_MMAP_SIZE]] +** The [SQLITE_FCNTL_MMAP_SIZE] file control is used to query or set the +** maximum number of bytes that will be used for memory-mapped I/O. +** The argument is a pointer to a value of type sqlite3_int64 that +** is an advisory maximum number of bytes in the file to memory map. The +** pointer is overwritten with the old value. The limit is not changed if +** the value originally pointed to is negative, and so the current limit +** can be queried by passing in a pointer to a negative number. This +** file-control is used internally to implement [PRAGMA mmap_size]. +** +** <li>[[SQLITE_FCNTL_TRACE]] +** The [SQLITE_FCNTL_TRACE] file control provides advisory information +** to the VFS about what the higher layers of the SQLite stack are doing. +** This file control is used by some VFS activity tracing [shims]. +** The argument is a zero-terminated string. Higher layers in the +** SQLite stack may generate instances of this file control if +** the [SQLITE_USE_FCNTL_TRACE] compile-time option is enabled. +** +** <li>[[SQLITE_FCNTL_HAS_MOVED]] +** The [SQLITE_FCNTL_HAS_MOVED] file control interprets its argument as a +** pointer to an integer and it writes a boolean into that integer depending +** on whether or not the file has been renamed, moved, or deleted since it +** was first opened. +** +** <li>[[SQLITE_FCNTL_WIN32_GET_HANDLE]] +** The [SQLITE_FCNTL_WIN32_GET_HANDLE] opcode can be used to obtain the +** underlying native file handle associated with a file handle. This file +** control interprets its argument as a pointer to a native file handle and +** writes the resulting value there. +** +** <li>[[SQLITE_FCNTL_WIN32_SET_HANDLE]] +** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This +** opcode causes the xFileControl method to swap the file handle with the one +** pointed to by the pArg argument. This capability is used during testing +** and only needs to be supported when SQLITE_TEST is defined. +** +** <li>[[SQLITE_FCNTL_WAL_BLOCK]] +** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might +** be advantageous to block on the next WAL lock if the lock is not immediately +** available. The WAL subsystem issues this signal during rare +** circumstances in order to fix a problem with priority inversion. +** Applications should <em>not</em> use this file-control. +** +** <li>[[SQLITE_FCNTL_ZIPVFS]] +** The [SQLITE_FCNTL_ZIPVFS] opcode is implemented by zipvfs only. All other +** VFS should return SQLITE_NOTFOUND for this opcode. +** +** <li>[[SQLITE_FCNTL_RBU]] +** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by +** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for +** this opcode. +** +** <li>[[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]] +** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then +** the file descriptor is placed in "batch write mode", which +** means all subsequent write operations will be deferred and done +** atomically at the next [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. Systems +** that do not support batch atomic writes will return SQLITE_NOTFOUND. +** ^Following a successful SQLITE_FCNTL_BEGIN_ATOMIC_WRITE and prior to +** the closing [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] or +** [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE], SQLite will make +** no VFS interface calls on the same [sqlite3_file] file descriptor +** except for calls to the xWrite method and the xFileControl method +** with [SQLITE_FCNTL_SIZE_HINT]. +** +** <li>[[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]] +** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write +** operations since the previous successful call to +** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically. +** This file control returns [SQLITE_OK] if and only if the writes were +** all performed successfully and have been committed to persistent storage. +** ^Regardless of whether or not it is successful, this file control takes +** the file descriptor out of batch write mode so that all subsequent +** write operations are independent. +** ^SQLite will never invoke SQLITE_FCNTL_COMMIT_ATOMIC_WRITE without +** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]. +** +** <li>[[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]] +** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write +** operations since the previous successful call to +** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back. +** ^This file control takes the file descriptor out of batch write mode +** so that all subsequent write operations are independent. +** ^SQLite will never invoke SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE without +** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]. +** +** <li>[[SQLITE_FCNTL_LOCK_TIMEOUT]] +** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode is used to configure a VFS +** to block for up to M milliseconds before failing when attempting to +** obtain a file lock using the xLock or xShmLock methods of the VFS. +** The parameter is a pointer to a 32-bit signed integer that contains +** the value that M is to be set to. Before returning, the 32-bit signed +** integer is overwritten with the previous value of M. +** +** <li>[[SQLITE_FCNTL_DATA_VERSION]] +** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to +** a database file. The argument is a pointer to a 32-bit unsigned integer. +** The "data version" for the pager is written into the pointer. The +** "data version" changes whenever any change occurs to the corresponding +** database file, either through SQL statements on the same database +** connection or through transactions committed by separate database +** connections possibly in other processes. The [sqlite3_total_changes()] +** interface can be used to find if any database on the connection has changed, +** but that interface responds to changes on TEMP as well as MAIN and does +** not provide a mechanism to detect changes to MAIN only. Also, the +** [sqlite3_total_changes()] interface responds to internal changes only and +** omits changes made by other database connections. The +** [PRAGMA data_version] command provides a mechanism to detect changes to +** a single attached database that occur due to other database connections, +** but omits changes implemented by the database connection on which it is +** called. This file control is the only mechanism to detect changes that +** happen either internally or externally and that are associated with +** a particular attached database. +** +** <li>[[SQLITE_FCNTL_CKPT_START]] +** The [SQLITE_FCNTL_CKPT_START] opcode is invoked from within a checkpoint +** in wal mode before the client starts to copy pages from the wal +** file to the database file. +** +** <li>[[SQLITE_FCNTL_CKPT_DONE]] +** The [SQLITE_FCNTL_CKPT_DONE] opcode is invoked from within a checkpoint +** in wal mode after the client has finished copying pages from the wal +** file to the database file, but before the *-shm file is updated to +** record the fact that the pages have been checkpointed. +** +** <li>[[SQLITE_FCNTL_EXTERNAL_READER]] +** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect +** whether or not there is a database client in another process with a wal-mode +** transaction open on the database or not. It is only available on unix.The +** (void*) argument passed with this file-control should be a pointer to a +** value of type (int). The integer value is set to 1 if the database is a wal +** mode database and there exists at least one client in another process that +** currently has an SQL transaction open on the database. It is set to 0 if +** the database is not a wal-mode db, or if there is no such connection in any +** other process. This opcode cannot be used to detect transactions opened +** by clients within the current process, only within other processes. +** +** <li>[[SQLITE_FCNTL_CKSM_FILE]] +** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use internally by the +** [checksum VFS shim] only. +** +** <li>[[SQLITE_FCNTL_RESET_CACHE]] +** If there is currently no transaction open on the database, and the +** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control +** purges the contents of the in-memory page cache. If there is an open +** transaction, or if the db is a temp-db, this opcode is a no-op, not an error. +** </ul> +*/ +#define SQLITE_FCNTL_LOCKSTATE 1 +#define SQLITE_FCNTL_GET_LOCKPROXYFILE 2 +#define SQLITE_FCNTL_SET_LOCKPROXYFILE 3 +#define SQLITE_FCNTL_LAST_ERRNO 4 +#define SQLITE_FCNTL_SIZE_HINT 5 +#define SQLITE_FCNTL_CHUNK_SIZE 6 +#define SQLITE_FCNTL_FILE_POINTER 7 +#define SQLITE_FCNTL_SYNC_OMITTED 8 +#define SQLITE_FCNTL_WIN32_AV_RETRY 9 +#define SQLITE_FCNTL_PERSIST_WAL 10 +#define SQLITE_FCNTL_OVERWRITE 11 +#define SQLITE_FCNTL_VFSNAME 12 +#define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 +#define SQLITE_FCNTL_PRAGMA 14 +#define SQLITE_FCNTL_BUSYHANDLER 15 +#define SQLITE_FCNTL_TEMPFILENAME 16 +#define SQLITE_FCNTL_MMAP_SIZE 18 +#define SQLITE_FCNTL_TRACE 19 +#define SQLITE_FCNTL_HAS_MOVED 20 +#define SQLITE_FCNTL_SYNC 21 +#define SQLITE_FCNTL_COMMIT_PHASETWO 22 +#define SQLITE_FCNTL_WIN32_SET_HANDLE 23 +#define SQLITE_FCNTL_WAL_BLOCK 24 +#define SQLITE_FCNTL_ZIPVFS 25 +#define SQLITE_FCNTL_RBU 26 +#define SQLITE_FCNTL_VFS_POINTER 27 +#define SQLITE_FCNTL_JOURNAL_POINTER 28 +#define SQLITE_FCNTL_WIN32_GET_HANDLE 29 +#define SQLITE_FCNTL_PDB 30 +#define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31 +#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32 +#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33 +#define SQLITE_FCNTL_LOCK_TIMEOUT 34 +#define SQLITE_FCNTL_DATA_VERSION 35 +#define SQLITE_FCNTL_SIZE_LIMIT 36 +#define SQLITE_FCNTL_CKPT_DONE 37 +#define SQLITE_FCNTL_RESERVE_BYTES 38 +#define SQLITE_FCNTL_CKPT_START 39 +#define SQLITE_FCNTL_EXTERNAL_READER 40 +#define SQLITE_FCNTL_CKSM_FILE 41 +#define SQLITE_FCNTL_RESET_CACHE 42 + +/* deprecated names */ +#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE +#define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE +#define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO + + +/* +** CAPI3REF: Mutex Handle +** +** The mutex module within SQLite defines [sqlite3_mutex] to be an +** abstract type for a mutex object. The SQLite core never looks +** at the internal representation of an [sqlite3_mutex]. It only +** deals with pointers to the [sqlite3_mutex] object. +** +** Mutexes are created using [sqlite3_mutex_alloc()]. +*/ +typedef struct sqlite3_mutex sqlite3_mutex; + +/* +** CAPI3REF: Loadable Extension Thunk +** +** A pointer to the opaque sqlite3_api_routines structure is passed as +** the third parameter to entry points of [loadable extensions]. This +** structure must be typedefed in order to work around compiler warnings +** on some platforms. +*/ +typedef struct sqlite3_api_routines sqlite3_api_routines; + +/* +** CAPI3REF: File Name +** +** Type [sqlite3_filename] is used by SQLite to pass filenames to the +** xOpen method of a [VFS]. It may be cast to (const char*) and treated +** as a normal, nul-terminated, UTF-8 buffer containing the filename, but +** may also be passed to special APIs such as: +** +** <ul> +** <li> sqlite3_filename_database() +** <li> sqlite3_filename_journal() +** <li> sqlite3_filename_wal() +** <li> sqlite3_uri_parameter() +** <li> sqlite3_uri_boolean() +** <li> sqlite3_uri_int64() +** <li> sqlite3_uri_key() +** </ul> +*/ +typedef const char *sqlite3_filename; + +/* +** CAPI3REF: OS Interface Object +** +** An instance of the sqlite3_vfs object defines the interface between +** the SQLite core and the underlying operating system. The "vfs" +** in the name of the object stands for "virtual file system". See +** the [VFS | VFS documentation] for further information. +** +** The VFS interface is sometimes extended by adding new methods onto +** the end. Each time such an extension occurs, the iVersion field +** is incremented. The iVersion value started out as 1 in +** SQLite [version 3.5.0] on [dateof:3.5.0], then increased to 2 +** with SQLite [version 3.7.0] on [dateof:3.7.0], and then increased +** to 3 with SQLite [version 3.7.6] on [dateof:3.7.6]. Additional fields +** may be appended to the sqlite3_vfs object and the iVersion value +** may increase again in future versions of SQLite. +** Note that due to an oversight, the structure +** of the sqlite3_vfs object changed in the transition from +** SQLite [version 3.5.9] to [version 3.6.0] on [dateof:3.6.0] +** and yet the iVersion field was not increased. +** +** The szOsFile field is the size of the subclassed [sqlite3_file] +** structure used by this VFS. mxPathname is the maximum length of +** a pathname in this VFS. +** +** Registered sqlite3_vfs objects are kept on a linked list formed by +** the pNext pointer. The [sqlite3_vfs_register()] +** and [sqlite3_vfs_unregister()] interfaces manage this list +** in a thread-safe way. The [sqlite3_vfs_find()] interface +** searches the list. Neither the application code nor the VFS +** implementation should use the pNext pointer. +** +** The pNext field is the only field in the sqlite3_vfs +** structure that SQLite will ever modify. SQLite will only access +** or modify this field while holding a particular static mutex. +** The application should never modify anything within the sqlite3_vfs +** object once the object has been registered. +** +** The zName field holds the name of the VFS module. The name must +** be unique across all VFS modules. +** +** [[sqlite3_vfs.xOpen]] +** ^SQLite guarantees that the zFilename parameter to xOpen +** is either a NULL pointer or string obtained +** from xFullPathname() with an optional suffix added. +** ^If a suffix is added to the zFilename parameter, it will +** consist of a single "-" character followed by no more than +** 11 alphanumeric and/or "-" characters. +** ^SQLite further guarantees that +** the string will be valid and unchanged until xClose() is +** called. Because of the previous sentence, +** the [sqlite3_file] can safely store a pointer to the +** filename if it needs to remember the filename for some reason. +** If the zFilename parameter to xOpen is a NULL pointer then xOpen +** must invent its own temporary name for the file. ^Whenever the +** xFilename parameter is NULL it will also be the case that the +** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE]. +** +** The flags argument to xOpen() includes all bits set in +** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()] +** or [sqlite3_open16()] is used, then flags includes at least +** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. +** If xOpen() opens a file read-only then it sets *pOutFlags to +** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set. +** +** ^(SQLite will also add one of the following flags to the xOpen() +** call, depending on the object being opened: +** +** <ul> +** <li> [SQLITE_OPEN_MAIN_DB] +** <li> [SQLITE_OPEN_MAIN_JOURNAL] +** <li> [SQLITE_OPEN_TEMP_DB] +** <li> [SQLITE_OPEN_TEMP_JOURNAL] +** <li> [SQLITE_OPEN_TRANSIENT_DB] +** <li> [SQLITE_OPEN_SUBJOURNAL] +** <li> [SQLITE_OPEN_SUPER_JOURNAL] +** <li> [SQLITE_OPEN_WAL] +** </ul>)^ +** +** The file I/O implementation can use the object type flags to +** change the way it deals with files. For example, an application +** that does not care about crash recovery or rollback might make +** the open of a journal file a no-op. Writes to this journal would +** also be no-ops, and any attempt to read the journal would return +** SQLITE_IOERR. Or the implementation might recognize that a database +** file will be doing page-aligned sector reads and writes in a random +** order and set up its I/O subsystem accordingly. +** +** SQLite might also add one of the following flags to the xOpen method: +** +** <ul> +** <li> [SQLITE_OPEN_DELETEONCLOSE] +** <li> [SQLITE_OPEN_EXCLUSIVE] +** </ul> +** +** The [SQLITE_OPEN_DELETEONCLOSE] flag means the file should be +** deleted when it is closed. ^The [SQLITE_OPEN_DELETEONCLOSE] +** will be set for TEMP databases and their journals, transient +** databases, and subjournals. +** +** ^The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction +** with the [SQLITE_OPEN_CREATE] flag, which are both directly +** analogous to the O_EXCL and O_CREAT flags of the POSIX open() +** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the +** SQLITE_OPEN_CREATE, is used to indicate that file should always +** be created, and that it is an error if it already exists. +** It is <i>not</i> used to indicate the file should be opened +** for exclusive access. +** +** ^At least szOsFile bytes of memory are allocated by SQLite +** to hold the [sqlite3_file] structure passed as the third +** argument to xOpen. The xOpen method does not have to +** allocate the structure; it should just fill it in. Note that +** the xOpen method must set the sqlite3_file.pMethods to either +** a valid [sqlite3_io_methods] object or to NULL. xOpen must do +** this even if the open fails. SQLite expects that the sqlite3_file.pMethods +** element will be valid after xOpen returns regardless of the success +** or failure of the xOpen call. +** +** [[sqlite3_vfs.xAccess]] +** ^The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS] +** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to +** test whether a file is readable and writable, or [SQLITE_ACCESS_READ] +** to test whether a file is at least readable. The SQLITE_ACCESS_READ +** flag is never actually used and is not implemented in the built-in +** VFSes of SQLite. The file is named by the second argument and can be a +** directory. The xAccess method returns [SQLITE_OK] on success or some +** non-zero error code if there is an I/O error or if the name of +** the file given in the second argument is illegal. If SQLITE_OK +** is returned, then non-zero or zero is written into *pResOut to indicate +** whether or not the file is accessible. +** +** ^SQLite will always allocate at least mxPathname+1 bytes for the +** output buffer xFullPathname. The exact size of the output buffer +** is also passed as a parameter to both methods. If the output buffer +** is not large enough, [SQLITE_CANTOPEN] should be returned. Since this is +** handled as a fatal error by SQLite, vfs implementations should endeavor +** to prevent this by setting mxPathname to a sufficiently large value. +** +** The xRandomness(), xSleep(), xCurrentTime(), and xCurrentTimeInt64() +** interfaces are not strictly a part of the filesystem, but they are +** included in the VFS structure for completeness. +** The xRandomness() function attempts to return nBytes bytes +** of good-quality randomness into zOut. The return value is +** the actual number of bytes of randomness obtained. +** The xSleep() method causes the calling thread to sleep for at +** least the number of microseconds given. ^The xCurrentTime() +** method returns a Julian Day Number for the current date and time as +** a floating point value. +** ^The xCurrentTimeInt64() method returns, as an integer, the Julian +** Day Number multiplied by 86400000 (the number of milliseconds in +** a 24-hour day). +** ^SQLite will use the xCurrentTimeInt64() method to get the current +** date and time if that method is available (if iVersion is 2 or +** greater and the function pointer is not NULL) and will fall back +** to xCurrentTime() if xCurrentTimeInt64() is unavailable. +** +** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces +** are not used by the SQLite core. These optional interfaces are provided +** by some VFSes to facilitate testing of the VFS code. By overriding +** system calls with functions under its control, a test program can +** simulate faults and error conditions that would otherwise be difficult +** or impossible to induce. The set of system calls that can be overridden +** varies from one VFS to another, and from one version of the same VFS to the +** next. Applications that use these interfaces must be prepared for any +** or all of these interfaces to be NULL or for their behavior to change +** from one release to the next. Applications must not attempt to access +** any of these methods if the iVersion of the VFS is less than 3. +*/ +typedef struct sqlite3_vfs sqlite3_vfs; +typedef void (*sqlite3_syscall_ptr)(void); +struct sqlite3_vfs { + int iVersion; /* Structure version number (currently 3) */ + int szOsFile; /* Size of subclassed sqlite3_file */ + int mxPathname; /* Maximum file pathname length */ + sqlite3_vfs *pNext; /* Next registered VFS */ + const char *zName; /* Name of this virtual file system */ + void *pAppData; /* Pointer to application-specific data */ + int (*xOpen)(sqlite3_vfs*, sqlite3_filename zName, sqlite3_file*, + int flags, int *pOutFlags); + int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir); + int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut); + int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut); + void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename); + void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg); + void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void); + void (*xDlClose)(sqlite3_vfs*, void*); + int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut); + int (*xSleep)(sqlite3_vfs*, int microseconds); + int (*xCurrentTime)(sqlite3_vfs*, double*); + int (*xGetLastError)(sqlite3_vfs*, int, char *); + /* + ** The methods above are in version 1 of the sqlite_vfs object + ** definition. Those that follow are added in version 2 or later + */ + int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*); + /* + ** The methods above are in versions 1 and 2 of the sqlite_vfs object. + ** Those below are for version 3 and greater. + */ + int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr); + sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName); + const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName); + /* + ** The methods above are in versions 1 through 3 of the sqlite_vfs object. + ** New fields may be appended in future versions. The iVersion + ** value will increment whenever this happens. + */ +}; + +/* +** CAPI3REF: Flags for the xAccess VFS method +** +** These integer constants can be used as the third parameter to +** the xAccess method of an [sqlite3_vfs] object. They determine +** what kind of permissions the xAccess method is looking for. +** With SQLITE_ACCESS_EXISTS, the xAccess method +** simply checks whether the file exists. +** With SQLITE_ACCESS_READWRITE, the xAccess method +** checks whether the named directory is both readable and writable +** (in other words, if files can be added, removed, and renamed within +** the directory). +** The SQLITE_ACCESS_READWRITE constant is currently used only by the +** [temp_store_directory pragma], though this could change in a future +** release of SQLite. +** With SQLITE_ACCESS_READ, the xAccess method +** checks whether the file is readable. The SQLITE_ACCESS_READ constant is +** currently unused, though it might be used in a future release of +** SQLite. +*/ +#define SQLITE_ACCESS_EXISTS 0 +#define SQLITE_ACCESS_READWRITE 1 /* Used by PRAGMA temp_store_directory */ +#define SQLITE_ACCESS_READ 2 /* Unused */ + +/* +** CAPI3REF: Flags for the xShmLock VFS method +** +** These integer constants define the various locking operations +** allowed by the xShmLock method of [sqlite3_io_methods]. The +** following are the only legal combinations of flags to the +** xShmLock method: +** +** <ul> +** <li> SQLITE_SHM_LOCK | SQLITE_SHM_SHARED +** <li> SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE +** <li> SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED +** <li> SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE +** </ul> +** +** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as +** was given on the corresponding lock. +** +** The xShmLock method can transition between unlocked and SHARED or +** between unlocked and EXCLUSIVE. It cannot transition between SHARED +** and EXCLUSIVE. +*/ +#define SQLITE_SHM_UNLOCK 1 +#define SQLITE_SHM_LOCK 2 +#define SQLITE_SHM_SHARED 4 +#define SQLITE_SHM_EXCLUSIVE 8 + +/* +** CAPI3REF: Maximum xShmLock index +** +** The xShmLock method on [sqlite3_io_methods] may use values +** between 0 and this upper bound as its "offset" argument. +** The SQLite core will never attempt to acquire or release a +** lock outside of this range +*/ +#define SQLITE_SHM_NLOCK 8 + + +/* +** CAPI3REF: Initialize The SQLite Library +** +** ^The sqlite3_initialize() routine initializes the +** SQLite library. ^The sqlite3_shutdown() routine +** deallocates any resources that were allocated by sqlite3_initialize(). +** These routines are designed to aid in process initialization and +** shutdown on embedded systems. Workstation applications using +** SQLite normally do not need to invoke either of these routines. +** +** A call to sqlite3_initialize() is an "effective" call if it is +** the first time sqlite3_initialize() is invoked during the lifetime of +** the process, or if it is the first time sqlite3_initialize() is invoked +** following a call to sqlite3_shutdown(). ^(Only an effective call +** of sqlite3_initialize() does any initialization. All other calls +** are harmless no-ops.)^ +** +** A call to sqlite3_shutdown() is an "effective" call if it is the first +** call to sqlite3_shutdown() since the last sqlite3_initialize(). ^(Only +** an effective call to sqlite3_shutdown() does any deinitialization. +** All other valid calls to sqlite3_shutdown() are harmless no-ops.)^ +** +** The sqlite3_initialize() interface is threadsafe, but sqlite3_shutdown() +** is not. The sqlite3_shutdown() interface must only be called from a +** single thread. All open [database connections] must be closed and all +** other SQLite resources must be deallocated prior to invoking +** sqlite3_shutdown(). +** +** Among other things, ^sqlite3_initialize() will invoke +** sqlite3_os_init(). Similarly, ^sqlite3_shutdown() +** will invoke sqlite3_os_end(). +** +** ^The sqlite3_initialize() routine returns [SQLITE_OK] on success. +** ^If for some reason, sqlite3_initialize() is unable to initialize +** the library (perhaps it is unable to allocate a needed resource such +** as a mutex) it returns an [error code] other than [SQLITE_OK]. +** +** ^The sqlite3_initialize() routine is called internally by many other +** SQLite interfaces so that an application usually does not need to +** invoke sqlite3_initialize() directly. For example, [sqlite3_open()] +** calls sqlite3_initialize() so the SQLite library will be automatically +** initialized when [sqlite3_open()] is called if it has not be initialized +** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT] +** compile-time option, then the automatic calls to sqlite3_initialize() +** are omitted and the application must call sqlite3_initialize() directly +** prior to using any other SQLite interface. For maximum portability, +** it is recommended that applications always invoke sqlite3_initialize() +** directly prior to using any other SQLite interface. Future releases +** of SQLite may require this. In other words, the behavior exhibited +** when SQLite is compiled with [SQLITE_OMIT_AUTOINIT] might become the +** default behavior in some future release of SQLite. +** +** The sqlite3_os_init() routine does operating-system specific +** initialization of the SQLite library. The sqlite3_os_end() +** routine undoes the effect of sqlite3_os_init(). Typical tasks +** performed by these routines include allocation or deallocation +** of static resources, initialization of global variables, +** setting up a default [sqlite3_vfs] module, or setting up +** a default configuration using [sqlite3_config()]. +** +** The application should never invoke either sqlite3_os_init() +** or sqlite3_os_end() directly. The application should only invoke +** sqlite3_initialize() and sqlite3_shutdown(). The sqlite3_os_init() +** interface is called automatically by sqlite3_initialize() and +** sqlite3_os_end() is called by sqlite3_shutdown(). Appropriate +** implementations for sqlite3_os_init() and sqlite3_os_end() +** are built into SQLite when it is compiled for Unix, Windows, or OS/2. +** When [custom builds | built for other platforms] +** (using the [SQLITE_OS_OTHER=1] compile-time +** option) the application must supply a suitable implementation for +** sqlite3_os_init() and sqlite3_os_end(). An application-supplied +** implementation of sqlite3_os_init() or sqlite3_os_end() +** must return [SQLITE_OK] on success and some other [error code] upon +** failure. +*/ +SQLITE_API int sqlite3_initialize(void); +SQLITE_API int sqlite3_shutdown(void); +SQLITE_API int sqlite3_os_init(void); +SQLITE_API int sqlite3_os_end(void); + +/* +** CAPI3REF: Configuring The SQLite Library +** +** The sqlite3_config() interface is used to make global configuration +** changes to SQLite in order to tune SQLite to the specific needs of +** the application. The default configuration is recommended for most +** applications and so this routine is usually not necessary. It is +** provided to support rare applications with unusual needs. +** +** <b>The sqlite3_config() interface is not threadsafe. The application +** must ensure that no other SQLite interfaces are invoked by other +** threads while sqlite3_config() is running.</b> +** +** The first argument to sqlite3_config() is an integer +** [configuration option] that determines +** what property of SQLite is to be configured. Subsequent arguments +** vary depending on the [configuration option] +** in the first argument. +** +** For most configuration options, the sqlite3_config() interface +** may only be invoked prior to library initialization using +** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. +** The exceptional configuration options that may be invoked at any time +** are called "anytime configuration options". +** ^If sqlite3_config() is called after [sqlite3_initialize()] and before +** [sqlite3_shutdown()] with a first argument that is not an anytime +** configuration option, then the sqlite3_config() call will return SQLITE_MISUSE. +** Note, however, that ^sqlite3_config() can be called as part of the +** implementation of an application-defined [sqlite3_os_init()]. +** +** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. +** ^If the option is unknown or SQLite is unable to set the option +** then this routine returns a non-zero [error code]. +*/ +SQLITE_API int sqlite3_config(int, ...); + +/* +** CAPI3REF: Configure database connections +** METHOD: sqlite3 +** +** The sqlite3_db_config() interface is used to make configuration +** changes to a [database connection]. The interface is similar to +** [sqlite3_config()] except that the changes apply to a single +** [database connection] (specified in the first argument). +** +** The second argument to sqlite3_db_config(D,V,...) is the +** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code +** that indicates what aspect of the [database connection] is being configured. +** Subsequent arguments vary depending on the configuration verb. +** +** ^Calls to sqlite3_db_config() return SQLITE_OK if and only if +** the call is considered successful. +*/ +SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...); + +/* +** CAPI3REF: Memory Allocation Routines +** +** An instance of this object defines the interface between SQLite +** and low-level memory allocation routines. +** +** This object is used in only one place in the SQLite interface. +** A pointer to an instance of this object is the argument to +** [sqlite3_config()] when the configuration option is +** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. +** By creating an instance of this object +** and passing it to [sqlite3_config]([SQLITE_CONFIG_MALLOC]) +** during configuration, an application can specify an alternative +** memory allocation subsystem for SQLite to use for all of its +** dynamic memory needs. +** +** Note that SQLite comes with several [built-in memory allocators] +** that are perfectly adequate for the overwhelming majority of applications +** and that this object is only useful to a tiny minority of applications +** with specialized memory allocation requirements. This object is +** also used during testing of SQLite in order to specify an alternative +** memory allocator that simulates memory out-of-memory conditions in +** order to verify that SQLite recovers gracefully from such +** conditions. +** +** The xMalloc, xRealloc, and xFree methods must work like the +** malloc(), realloc() and free() functions from the standard C library. +** ^SQLite guarantees that the second argument to +** xRealloc is always a value returned by a prior call to xRoundup. +** +** xSize should return the allocated size of a memory allocation +** previously obtained from xMalloc or xRealloc. The allocated size +** is always at least as big as the requested size but may be larger. +** +** The xRoundup method returns what would be the allocated size of +** a memory allocation given a particular requested size. Most memory +** allocators round up memory allocations at least to the next multiple +** of 8. Some allocators round up to a larger multiple or to a power of 2. +** Every memory allocation request coming in through [sqlite3_malloc()] +** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, +** that causes the corresponding memory allocation to fail. +** +** The xInit method initializes the memory allocator. For example, +** it might allocate any required mutexes or initialize internal data +** structures. The xShutdown method is invoked (indirectly) by +** [sqlite3_shutdown()] and should deallocate any resources acquired +** by xInit. The pAppData pointer is used as the only parameter to +** xInit and xShutdown. +** +** SQLite holds the [SQLITE_MUTEX_STATIC_MAIN] mutex when it invokes +** the xInit method, so the xInit method need not be threadsafe. The +** xShutdown method is only called from [sqlite3_shutdown()] so it does +** not need to be threadsafe either. For all other methods, SQLite +** holds the [SQLITE_MUTEX_STATIC_MEM] mutex as long as the +** [SQLITE_CONFIG_MEMSTATUS] configuration option is turned on (which +** it is by default) and so the methods are automatically serialized. +** However, if [SQLITE_CONFIG_MEMSTATUS] is disabled, then the other +** methods must be threadsafe or else make their own arrangements for +** serialization. +** +** SQLite will never invoke xInit() more than once without an intervening +** call to xShutdown(). +*/ +typedef struct sqlite3_mem_methods sqlite3_mem_methods; +struct sqlite3_mem_methods { + void *(*xMalloc)(int); /* Memory allocation function */ + void (*xFree)(void*); /* Free a prior allocation */ + void *(*xRealloc)(void*,int); /* Resize an allocation */ + int (*xSize)(void*); /* Return the size of an allocation */ + int (*xRoundup)(int); /* Round up request size to allocation size */ + int (*xInit)(void*); /* Initialize the memory allocator */ + void (*xShutdown)(void*); /* Deinitialize the memory allocator */ + void *pAppData; /* Argument to xInit() and xShutdown() */ +}; + +/* +** CAPI3REF: Configuration Options +** KEYWORDS: {configuration option} +** +** These constants are the available integer configuration options that +** can be passed as the first argument to the [sqlite3_config()] interface. +** +** Most of the configuration options for sqlite3_config() +** will only work if invoked prior to [sqlite3_initialize()] or after +** [sqlite3_shutdown()]. The few exceptions to this rule are called +** "anytime configuration options". +** ^Calling [sqlite3_config()] with a first argument that is not an +** anytime configuration option in between calls to [sqlite3_initialize()] and +** [sqlite3_shutdown()] is a no-op that returns SQLITE_MISUSE. +** +** The set of anytime configuration options can change (by insertions +** and/or deletions) from one release of SQLite to the next. +** As of SQLite version 3.42.0, the complete set of anytime configuration +** options is: +** <ul> +** <li> SQLITE_CONFIG_LOG +** <li> SQLITE_CONFIG_PCACHE_HDRSZ +** </ul> +** +** New configuration options may be added in future releases of SQLite. +** Existing configuration options might be discontinued. Applications +** should check the return code from [sqlite3_config()] to make sure that +** the call worked. The [sqlite3_config()] interface will return a +** non-zero [error code] if a discontinued or unsupported configuration option +** is invoked. +** +** <dl> +** [[SQLITE_CONFIG_SINGLETHREAD]] <dt>SQLITE_CONFIG_SINGLETHREAD</dt> +** <dd>There are no arguments to this option. ^This option sets the +** [threading mode] to Single-thread. In other words, it disables +** all mutexing and puts SQLite into a mode where it can only be used +** by a single thread. ^If SQLite is compiled with +** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then +** it is not possible to change the [threading mode] from its default +** value of Single-thread and so [sqlite3_config()] will return +** [SQLITE_ERROR] if called with the SQLITE_CONFIG_SINGLETHREAD +** configuration option.</dd> +** +** [[SQLITE_CONFIG_MULTITHREAD]] <dt>SQLITE_CONFIG_MULTITHREAD</dt> +** <dd>There are no arguments to this option. ^This option sets the +** [threading mode] to Multi-thread. In other words, it disables +** mutexing on [database connection] and [prepared statement] objects. +** The application is responsible for serializing access to +** [database connections] and [prepared statements]. But other mutexes +** are enabled so that SQLite will be safe to use in a multi-threaded +** environment as long as no two threads attempt to use the same +** [database connection] at the same time. ^If SQLite is compiled with +** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then +** it is not possible to set the Multi-thread [threading mode] and +** [sqlite3_config()] will return [SQLITE_ERROR] if called with the +** SQLITE_CONFIG_MULTITHREAD configuration option.</dd> +** +** [[SQLITE_CONFIG_SERIALIZED]] <dt>SQLITE_CONFIG_SERIALIZED</dt> +** <dd>There are no arguments to this option. ^This option sets the +** [threading mode] to Serialized. In other words, this option enables +** all mutexes including the recursive +** mutexes on [database connection] and [prepared statement] objects. +** In this mode (which is the default when SQLite is compiled with +** [SQLITE_THREADSAFE=1]) the SQLite library will itself serialize access +** to [database connections] and [prepared statements] so that the +** application is free to use the same [database connection] or the +** same [prepared statement] in different threads at the same time. +** ^If SQLite is compiled with +** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then +** it is not possible to set the Serialized [threading mode] and +** [sqlite3_config()] will return [SQLITE_ERROR] if called with the +** SQLITE_CONFIG_SERIALIZED configuration option.</dd> +** +** [[SQLITE_CONFIG_MALLOC]] <dt>SQLITE_CONFIG_MALLOC</dt> +** <dd> ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is +** a pointer to an instance of the [sqlite3_mem_methods] structure. +** The argument specifies +** alternative low-level memory allocation routines to be used in place of +** the memory allocation routines built into SQLite.)^ ^SQLite makes +** its own private copy of the content of the [sqlite3_mem_methods] structure +** before the [sqlite3_config()] call returns.</dd> +** +** [[SQLITE_CONFIG_GETMALLOC]] <dt>SQLITE_CONFIG_GETMALLOC</dt> +** <dd> ^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which +** is a pointer to an instance of the [sqlite3_mem_methods] structure. +** The [sqlite3_mem_methods] +** structure is filled with the currently defined memory allocation routines.)^ +** This option can be used to overload the default memory allocation +** routines with a wrapper that simulations memory allocation failure or +** tracks memory usage, for example. </dd> +** +** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt> +** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of +** type int, interpreted as a boolean, which if true provides a hint to +** SQLite that it should avoid large memory allocations if possible. +** SQLite will run faster if it is free to make large memory allocations, +** but some application might prefer to run slower in exchange for +** guarantees about memory fragmentation that are possible if large +** allocations are avoided. This hint is normally off. +** </dd> +** +** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt> +** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, +** interpreted as a boolean, which enables or disables the collection of +** memory allocation statistics. ^(When memory allocation statistics are +** disabled, the following SQLite interfaces become non-operational: +** <ul> +** <li> [sqlite3_hard_heap_limit64()] +** <li> [sqlite3_memory_used()] +** <li> [sqlite3_memory_highwater()] +** <li> [sqlite3_soft_heap_limit64()] +** <li> [sqlite3_status64()] +** </ul>)^ +** ^Memory allocation statistics are enabled by default unless SQLite is +** compiled with [SQLITE_DEFAULT_MEMSTATUS]=0 in which case memory +** allocation statistics are disabled by default. +** </dd> +** +** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt> +** <dd> The SQLITE_CONFIG_SCRATCH option is no longer used. +** </dd> +** +** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt> +** <dd> ^The SQLITE_CONFIG_PAGECACHE option specifies a memory pool +** that SQLite can use for the database page cache with the default page +** cache implementation. +** This configuration option is a no-op if an application-defined page +** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2]. +** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to +** 8-byte aligned memory (pMem), the size of each page cache line (sz), +** and the number of cache lines (N). +** The sz argument should be the size of the largest database page +** (a power of two between 512 and 65536) plus some extra bytes for each +** page header. ^The number of extra bytes needed by the page header +** can be determined using [SQLITE_CONFIG_PCACHE_HDRSZ]. +** ^It is harmless, apart from the wasted memory, +** for the sz parameter to be larger than necessary. The pMem +** argument must be either a NULL pointer or a pointer to an 8-byte +** aligned block of memory of at least sz*N bytes, otherwise +** subsequent behavior is undefined. +** ^When pMem is not NULL, SQLite will strive to use the memory provided +** to satisfy page cache needs, falling back to [sqlite3_malloc()] if +** a page cache line is larger than sz bytes or if all of the pMem buffer +** is exhausted. +** ^If pMem is NULL and N is non-zero, then each database connection +** does an initial bulk allocation for page cache memory +** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or +** of -1024*N bytes if N is negative, . ^If additional +** page cache memory is needed beyond what is provided by the initial +** allocation, then SQLite goes to [sqlite3_malloc()] separately for each +** additional cache line. </dd> +** +** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt> +** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer +** that SQLite will use for all of its dynamic memory allocation needs +** beyond those provided for by [SQLITE_CONFIG_PAGECACHE]. +** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled +** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns +** [SQLITE_ERROR] if invoked otherwise. +** ^There are three arguments to SQLITE_CONFIG_HEAP: +** An 8-byte aligned pointer to the memory, +** the number of bytes in the memory buffer, and the minimum allocation size. +** ^If the first pointer (the memory pointer) is NULL, then SQLite reverts +** to using its default memory allocator (the system malloc() implementation), +** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the +** memory pointer is not NULL then the alternative memory +** allocator is engaged to handle all of SQLites memory allocation needs. +** The first pointer (the memory pointer) must be aligned to an 8-byte +** boundary or subsequent behavior of SQLite will be undefined. +** The minimum allocation size is capped at 2**12. Reasonable values +** for the minimum allocation size are 2**5 through 2**8.</dd> +** +** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt> +** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a +** pointer to an instance of the [sqlite3_mutex_methods] structure. +** The argument specifies alternative low-level mutex routines to be used +** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of +** the content of the [sqlite3_mutex_methods] structure before the call to +** [sqlite3_config()] returns. ^If SQLite is compiled with +** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then +** the entire mutexing subsystem is omitted from the build and hence calls to +** [sqlite3_config()] with the SQLITE_CONFIG_MUTEX configuration option will +** return [SQLITE_ERROR].</dd> +** +** [[SQLITE_CONFIG_GETMUTEX]] <dt>SQLITE_CONFIG_GETMUTEX</dt> +** <dd> ^(The SQLITE_CONFIG_GETMUTEX option takes a single argument which +** is a pointer to an instance of the [sqlite3_mutex_methods] structure. The +** [sqlite3_mutex_methods] +** structure is filled with the currently defined mutex routines.)^ +** This option can be used to overload the default mutex allocation +** routines with a wrapper used to track mutex usage for performance +** profiling or testing, for example. ^If SQLite is compiled with +** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then +** the entire mutexing subsystem is omitted from the build and hence calls to +** [sqlite3_config()] with the SQLITE_CONFIG_GETMUTEX configuration option will +** return [SQLITE_ERROR].</dd> +** +** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt> +** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine +** the default size of lookaside memory on each [database connection]. +** The first argument is the +** size of each lookaside buffer slot and the second is the number of +** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE +** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] +** option to [sqlite3_db_config()] can be used to change the lookaside +** configuration on individual connections.)^ </dd> +** +** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> +** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is +** a pointer to an [sqlite3_pcache_methods2] object. This object specifies +** the interface to a custom page cache implementation.)^ +** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.</dd> +** +** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt> +** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which +** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of +** the current page cache implementation into that object.)^ </dd> +** +** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt> +** <dd> The SQLITE_CONFIG_LOG option is used to configure the SQLite +** global [error log]. +** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a +** function with a call signature of void(*)(void*,int,const char*), +** and a pointer to void. ^If the function pointer is not NULL, it is +** invoked by [sqlite3_log()] to process each logging event. ^If the +** function pointer is NULL, the [sqlite3_log()] interface becomes a no-op. +** ^The void pointer that is the second argument to SQLITE_CONFIG_LOG is +** passed through as the first parameter to the application-defined logger +** function whenever that function is invoked. ^The second parameter to +** the logger function is a copy of the first parameter to the corresponding +** [sqlite3_log()] call and is intended to be a [result code] or an +** [extended result code]. ^The third parameter passed to the logger is +** log message after formatting via [sqlite3_snprintf()]. +** The SQLite logging interface is not reentrant; the logger function +** supplied by the application must not invoke any SQLite interface. +** In a multi-threaded application, the application-defined logger +** function must be threadsafe. </dd> +** +** [[SQLITE_CONFIG_URI]] <dt>SQLITE_CONFIG_URI +** <dd>^(The SQLITE_CONFIG_URI option takes a single argument of type int. +** If non-zero, then URI handling is globally enabled. If the parameter is zero, +** then URI handling is globally disabled.)^ ^If URI handling is globally +** enabled, all filenames passed to [sqlite3_open()], [sqlite3_open_v2()], +** [sqlite3_open16()] or +** specified as part of [ATTACH] commands are interpreted as URIs, regardless +** of whether or not the [SQLITE_OPEN_URI] flag is set when the database +** connection is opened. ^If it is globally disabled, filenames are +** only interpreted as URIs if the SQLITE_OPEN_URI flag is set when the +** database connection is opened. ^(By default, URI handling is globally +** disabled. The default value may be changed by compiling with the +** [SQLITE_USE_URI] symbol defined.)^ +** +** [[SQLITE_CONFIG_COVERING_INDEX_SCAN]] <dt>SQLITE_CONFIG_COVERING_INDEX_SCAN +** <dd>^The SQLITE_CONFIG_COVERING_INDEX_SCAN option takes a single integer +** argument which is interpreted as a boolean in order to enable or disable +** the use of covering indices for full table scans in the query optimizer. +** ^The default setting is determined +** by the [SQLITE_ALLOW_COVERING_INDEX_SCAN] compile-time option, or is "on" +** if that compile-time option is omitted. +** The ability to disable the use of covering indices for full table scans +** is because some incorrectly coded legacy applications might malfunction +** when the optimization is enabled. Providing the ability to +** disable the optimization allows the older, buggy application code to work +** without change even with newer versions of SQLite. +** +** [[SQLITE_CONFIG_PCACHE]] [[SQLITE_CONFIG_GETPCACHE]] +** <dt>SQLITE_CONFIG_PCACHE and SQLITE_CONFIG_GETPCACHE +** <dd> These options are obsolete and should not be used by new code. +** They are retained for backwards compatibility but are now no-ops. +** </dd> +** +** [[SQLITE_CONFIG_SQLLOG]] +** <dt>SQLITE_CONFIG_SQLLOG +** <dd>This option is only available if sqlite is compiled with the +** [SQLITE_ENABLE_SQLLOG] pre-processor macro defined. The first argument should +** be a pointer to a function of type void(*)(void*,sqlite3*,const char*, int). +** The second should be of type (void*). The callback is invoked by the library +** in three separate circumstances, identified by the value passed as the +** fourth parameter. If the fourth parameter is 0, then the database connection +** passed as the second argument has just been opened. The third argument +** points to a buffer containing the name of the main database file. If the +** fourth parameter is 1, then the SQL statement that the third parameter +** points to has just been executed. Or, if the fourth parameter is 2, then +** the connection being passed as the second parameter is being closed. The +** third parameter is passed NULL In this case. An example of using this +** configuration option can be seen in the "test_sqllog.c" source file in +** the canonical SQLite source tree.</dd> +** +** [[SQLITE_CONFIG_MMAP_SIZE]] +** <dt>SQLITE_CONFIG_MMAP_SIZE +** <dd>^SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values +** that are the default mmap size limit (the default setting for +** [PRAGMA mmap_size]) and the maximum allowed mmap size limit. +** ^The default setting can be overridden by each database connection using +** either the [PRAGMA mmap_size] command, or by using the +** [SQLITE_FCNTL_MMAP_SIZE] file control. ^(The maximum allowed mmap size +** will be silently truncated if necessary so that it does not exceed the +** compile-time maximum mmap size set by the +** [SQLITE_MAX_MMAP_SIZE] compile-time option.)^ +** ^If either argument to this option is negative, then that argument is +** changed to its compile-time default. +** +** [[SQLITE_CONFIG_WIN32_HEAPSIZE]] +** <dt>SQLITE_CONFIG_WIN32_HEAPSIZE +** <dd>^The SQLITE_CONFIG_WIN32_HEAPSIZE option is only available if SQLite is +** compiled for Windows with the [SQLITE_WIN32_MALLOC] pre-processor macro +** defined. ^SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value +** that specifies the maximum size of the created heap. +** +** [[SQLITE_CONFIG_PCACHE_HDRSZ]] +** <dt>SQLITE_CONFIG_PCACHE_HDRSZ +** <dd>^The SQLITE_CONFIG_PCACHE_HDRSZ option takes a single parameter which +** is a pointer to an integer and writes into that integer the number of extra +** bytes per page required for each page in [SQLITE_CONFIG_PAGECACHE]. +** The amount of extra space required can change depending on the compiler, +** target platform, and SQLite version. +** +** [[SQLITE_CONFIG_PMASZ]] +** <dt>SQLITE_CONFIG_PMASZ +** <dd>^The SQLITE_CONFIG_PMASZ option takes a single parameter which +** is an unsigned integer and sets the "Minimum PMA Size" for the multithreaded +** sorter to that integer. The default minimum PMA Size is set by the +** [SQLITE_SORTER_PMASZ] compile-time option. New threads are launched +** to help with sort operations when multithreaded sorting +** is enabled (using the [PRAGMA threads] command) and the amount of content +** to be sorted exceeds the page size times the minimum of the +** [PRAGMA cache_size] setting and this value. +** +** [[SQLITE_CONFIG_STMTJRNL_SPILL]] +** <dt>SQLITE_CONFIG_STMTJRNL_SPILL +** <dd>^The SQLITE_CONFIG_STMTJRNL_SPILL option takes a single parameter which +** becomes the [statement journal] spill-to-disk threshold. +** [Statement journals] are held in memory until their size (in bytes) +** exceeds this threshold, at which point they are written to disk. +** Or if the threshold is -1, statement journals are always held +** exclusively in memory. +** Since many statement journals never become large, setting the spill +** threshold to a value such as 64KiB can greatly reduce the amount of +** I/O required to support statement rollback. +** The default value for this setting is controlled by the +** [SQLITE_STMTJRNL_SPILL] compile-time option. +** +** [[SQLITE_CONFIG_SORTERREF_SIZE]] +** <dt>SQLITE_CONFIG_SORTERREF_SIZE +** <dd>The SQLITE_CONFIG_SORTERREF_SIZE option accepts a single parameter +** of type (int) - the new value of the sorter-reference size threshold. +** Usually, when SQLite uses an external sort to order records according +** to an ORDER BY clause, all fields required by the caller are present in the +** sorted records. However, if SQLite determines based on the declared type +** of a table column that its values are likely to be very large - larger +** than the configured sorter-reference size threshold - then a reference +** is stored in each sorted record and the required column values loaded +** from the database as records are returned in sorted order. The default +** value for this option is to never use this optimization. Specifying a +** negative value for this option restores the default behaviour. +** This option is only available if SQLite is compiled with the +** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option. +** +** [[SQLITE_CONFIG_MEMDB_MAXSIZE]] +** <dt>SQLITE_CONFIG_MEMDB_MAXSIZE +** <dd>The SQLITE_CONFIG_MEMDB_MAXSIZE option accepts a single parameter +** [sqlite3_int64] parameter which is the default maximum size for an in-memory +** database created using [sqlite3_deserialize()]. This default maximum +** size can be adjusted up or down for individual databases using the +** [SQLITE_FCNTL_SIZE_LIMIT] [sqlite3_file_control|file-control]. If this +** configuration setting is never used, then the default maximum is determined +** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that +** compile-time option is not set, then the default maximum is 1073741824. +** </dl> +*/ +#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ +#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ +#define SQLITE_CONFIG_SERIALIZED 3 /* nil */ +#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */ +#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ +#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ +#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ +#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ +#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ +#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ +#define SQLITE_CONFIG_PCACHE 14 /* no-op */ +#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ +#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ +#define SQLITE_CONFIG_URI 17 /* int */ +#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ +#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ +#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */ +#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ +#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ +#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */ +#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */ +#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ +#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */ +#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ +#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ +#define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ + +/* +** CAPI3REF: Database Connection Configuration Options +** +** These constants are the available integer configuration options that +** can be passed as the second argument to the [sqlite3_db_config()] interface. +** +** New configuration options may be added in future releases of SQLite. +** Existing configuration options might be discontinued. Applications +** should check the return code from [sqlite3_db_config()] to make sure that +** the call worked. ^The [sqlite3_db_config()] interface will return a +** non-zero [error code] if a discontinued or unsupported configuration option +** is invoked. +** +** <dl> +** [[SQLITE_DBCONFIG_LOOKASIDE]] +** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt> +** <dd> ^This option takes three additional arguments that determine the +** [lookaside memory allocator] configuration for the [database connection]. +** ^The first argument (the third parameter to [sqlite3_db_config()] is a +** pointer to a memory buffer to use for lookaside memory. +** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb +** may be NULL in which case SQLite will allocate the +** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the +** size of each lookaside buffer slot. ^The third argument is the number of +** slots. The size of the buffer in the first argument must be greater than +** or equal to the product of the second and third arguments. The buffer +** must be aligned to an 8-byte boundary. ^If the second argument to +** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally +** rounded down to the next smaller multiple of 8. ^(The lookaside memory +** configuration for a database connection can only be changed when that +** connection is not currently using lookaside memory, or in other words +** when the "current value" returned by +** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero. +** Any attempt to change the lookaside memory configuration when lookaside +** memory is in use leaves the configuration unchanged and returns +** [SQLITE_BUSY].)^</dd> +** +** [[SQLITE_DBCONFIG_ENABLE_FKEY]] +** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt> +** <dd> ^This option is used to enable or disable the enforcement of +** [foreign key constraints]. There should be two additional arguments. +** The first argument is an integer which is 0 to disable FK enforcement, +** positive to enable FK enforcement or negative to leave FK enforcement +** unchanged. The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether FK enforcement is off or on +** following this call. The second parameter may be a NULL pointer, in +** which case the FK enforcement setting is not reported back. </dd> +** +** [[SQLITE_DBCONFIG_ENABLE_TRIGGER]] +** <dt>SQLITE_DBCONFIG_ENABLE_TRIGGER</dt> +** <dd> ^This option is used to enable or disable [CREATE TRIGGER | triggers]. +** There should be two additional arguments. +** The first argument is an integer which is 0 to disable triggers, +** positive to enable triggers or negative to leave the setting unchanged. +** The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether triggers are disabled or enabled +** following this call. The second parameter may be a NULL pointer, in +** which case the trigger setting is not reported back. +** +** <p>Originally this option disabled all triggers. ^(However, since +** SQLite version 3.35.0, TEMP triggers are still allowed even if +** this option is off. So, in other words, this option now only disables +** triggers in the main database schema or in the schemas of ATTACH-ed +** databases.)^ </dd> +** +** [[SQLITE_DBCONFIG_ENABLE_VIEW]] +** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt> +** <dd> ^This option is used to enable or disable [CREATE VIEW | views]. +** There should be two additional arguments. +** The first argument is an integer which is 0 to disable views, +** positive to enable views or negative to leave the setting unchanged. +** The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether views are disabled or enabled +** following this call. The second parameter may be a NULL pointer, in +** which case the view setting is not reported back. +** +** <p>Originally this option disabled all views. ^(However, since +** SQLite version 3.35.0, TEMP views are still allowed even if +** this option is off. So, in other words, this option now only disables +** views in the main database schema or in the schemas of ATTACH-ed +** databases.)^ </dd> +** +** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] +** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> +** <dd> ^This option is used to enable or disable the +** [fts3_tokenizer()] function which is part of the +** [FTS3] full-text search engine extension. +** There should be two additional arguments. +** The first argument is an integer which is 0 to disable fts3_tokenizer() or +** positive to enable fts3_tokenizer() or negative to leave the setting +** unchanged. +** The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled +** following this call. The second parameter may be a NULL pointer, in +** which case the new setting is not reported back. </dd> +** +** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] +** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> +** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()] +** interface independently of the [load_extension()] SQL function. +** The [sqlite3_enable_load_extension()] API enables or disables both the +** C-API [sqlite3_load_extension()] and the SQL function [load_extension()]. +** There should be two additional arguments. +** When the first argument to this interface is 1, then only the C-API is +** enabled and the SQL function remains disabled. If the first argument to +** this interface is 0, then both the C-API and the SQL function are disabled. +** If the first argument is -1, then no changes are made to state of either the +** C-API or the SQL function. +** The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface +** is disabled or enabled following this call. The second parameter may +** be a NULL pointer, in which case the new setting is not reported back. +** </dd> +** +** [[SQLITE_DBCONFIG_MAINDBNAME]] <dt>SQLITE_DBCONFIG_MAINDBNAME</dt> +** <dd> ^This option is used to change the name of the "main" database +** schema. ^The sole argument is a pointer to a constant UTF8 string +** which will become the new schema name in place of "main". ^SQLite +** does not make a copy of the new main schema name string, so the application +** must ensure that the argument passed into this DBCONFIG option is unchanged +** until after the database connection closes. +** </dd> +** +** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]] +** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt> +** <dd> Usually, when a database in wal mode is closed or detached from a +** database handle, SQLite checks if this will mean that there are now no +** connections at all to the database. If so, it performs a checkpoint +** operation before closing the connection. This option may be used to +** override this behaviour. The first parameter passed to this operation +** is an integer - positive to disable checkpoints-on-close, or zero (the +** default) to enable them, and negative to leave the setting unchanged. +** The second parameter is a pointer to an integer +** into which is written 0 or 1 to indicate whether checkpoints-on-close +** have been disabled - 0 if they are not disabled, 1 if they are. +** </dd> +** +** [[SQLITE_DBCONFIG_ENABLE_QPSG]] <dt>SQLITE_DBCONFIG_ENABLE_QPSG</dt> +** <dd>^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates +** the [query planner stability guarantee] (QPSG). When the QPSG is active, +** a single SQL query statement will always use the same algorithm regardless +** of values of [bound parameters].)^ The QPSG disables some query optimizations +** that look at the values of bound parameters, which can make some queries +** slower. But the QPSG has the advantage of more predictable behavior. With +** the QPSG active, SQLite will always use the same query plan in the field as +** was used during testing in the lab. +** The first argument to this setting is an integer which is 0 to disable +** the QPSG, positive to enable QPSG, or negative to leave the setting +** unchanged. The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether the QPSG is disabled or enabled +** following this call. +** </dd> +** +** [[SQLITE_DBCONFIG_TRIGGER_EQP]] <dt>SQLITE_DBCONFIG_TRIGGER_EQP</dt> +** <dd> By default, the output of EXPLAIN QUERY PLAN commands does not +** include output for any operations performed by trigger programs. This +** option is used to set or clear (the default) a flag that governs this +** behavior. The first parameter passed to this operation is an integer - +** positive to enable output for trigger programs, or zero to disable it, +** or negative to leave the setting unchanged. +** The second parameter is a pointer to an integer into which is written +** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if +** it is not disabled, 1 if it is. +** </dd> +** +** [[SQLITE_DBCONFIG_RESET_DATABASE]] <dt>SQLITE_DBCONFIG_RESET_DATABASE</dt> +** <dd> Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run +** [VACUUM] in order to reset a database back to an empty database +** with no schema and no content. The following process works even for +** a badly corrupted database file: +** <ol> +** <li> If the database connection is newly opened, make sure it has read the +** database schema by preparing then discarding some query against the +** database, or calling sqlite3_table_column_metadata(), ignoring any +** errors. This step is only necessary if the application desires to keep +** the database in WAL mode after the reset if it was in WAL mode before +** the reset. +** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); +** <li> [sqlite3_exec](db, "[VACUUM]", 0, 0, 0); +** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); +** </ol> +** Because resetting a database is destructive and irreversible, the +** process requires the use of this obscure API and multiple steps to +** help ensure that it does not happen by accident. Because this +** feature must be capable of resetting corrupt databases, and +** shutting down virtual tables may require access to that corrupt +** storage, the library must abandon any installed virtual tables +** without calling their xDestroy() methods. +** +** [[SQLITE_DBCONFIG_DEFENSIVE]] <dt>SQLITE_DBCONFIG_DEFENSIVE</dt> +** <dd>The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the +** "defensive" flag for a database connection. When the defensive +** flag is enabled, language features that allow ordinary SQL to +** deliberately corrupt the database file are disabled. The disabled +** features include but are not limited to the following: +** <ul> +** <li> The [PRAGMA writable_schema=ON] statement. +** <li> The [PRAGMA journal_mode=OFF] statement. +** <li> The [PRAGMA schema_version=N] statement. +** <li> Writes to the [sqlite_dbpage] virtual table. +** <li> Direct writes to [shadow tables]. +** </ul> +** </dd> +** +** [[SQLITE_DBCONFIG_WRITABLE_SCHEMA]] <dt>SQLITE_DBCONFIG_WRITABLE_SCHEMA</dt> +** <dd>The SQLITE_DBCONFIG_WRITABLE_SCHEMA option activates or deactivates the +** "writable_schema" flag. This has the same effect and is logically equivalent +** to setting [PRAGMA writable_schema=ON] or [PRAGMA writable_schema=OFF]. +** The first argument to this setting is an integer which is 0 to disable +** the writable_schema, positive to enable writable_schema, or negative to +** leave the setting unchanged. The second parameter is a pointer to an +** integer into which is written 0 or 1 to indicate whether the writable_schema +** is enabled or disabled following this call. +** </dd> +** +** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]] +** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt> +** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates +** the legacy behavior of the [ALTER TABLE RENAME] command such it +** behaves as it did prior to [version 3.24.0] (2018-06-04). See the +** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for +** additional information. This feature can also be turned on and off +** using the [PRAGMA legacy_alter_table] statement. +** </dd> +** +** [[SQLITE_DBCONFIG_DQS_DML]] +** <dt>SQLITE_DBCONFIG_DQS_DML</dt> +** <dd>The SQLITE_DBCONFIG_DQS_DML option activates or deactivates +** the legacy [double-quoted string literal] misfeature for DML statements +** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The +** default value of this setting is determined by the [-DSQLITE_DQS] +** compile-time option. +** </dd> +** +** [[SQLITE_DBCONFIG_DQS_DDL]] +** <dt>SQLITE_DBCONFIG_DQS_DDL</dt> +** <dd>The SQLITE_DBCONFIG_DQS option activates or deactivates +** the legacy [double-quoted string literal] misfeature for DDL statements, +** such as CREATE TABLE and CREATE INDEX. The +** default value of this setting is determined by the [-DSQLITE_DQS] +** compile-time option. +** </dd> +** +** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]] +** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</dt> +** <dd>The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to +** assume that database schemas are untainted by malicious content. +** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite +** takes additional defensive steps to protect the application from harm +** including: +** <ul> +** <li> Prohibit the use of SQL functions inside triggers, views, +** CHECK constraints, DEFAULT clauses, expression indexes, +** partial indexes, or generated columns +** unless those functions are tagged with [SQLITE_INNOCUOUS]. +** <li> Prohibit the use of virtual tables inside of triggers or views +** unless those virtual tables are tagged with [SQLITE_VTAB_INNOCUOUS]. +** </ul> +** This setting defaults to "on" for legacy compatibility, however +** all applications are advised to turn it off if possible. This setting +** can also be controlled using the [PRAGMA trusted_schema] statement. +** </dd> +** +** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]] +** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt> +** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates +** the legacy file format flag. When activated, this flag causes all newly +** created database file to have a schema format version number (the 4-byte +** integer found at offset 44 into the database header) of 1. This in turn +** means that the resulting database file will be readable and writable by +** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting, +** newly created databases are generally not understandable by SQLite versions +** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there +** is now scarcely any need to generate database files that are compatible +** all the way back to version 3.0.0, and so this setting is of little +** practical use, but is provided so that SQLite can continue to claim the +** ability to generate new database files that are compatible with version +** 3.0.0. +** <p>Note that when the SQLITE_DBCONFIG_LEGACY_FILE_FORMAT setting is on, +** the [VACUUM] command will fail with an obscure error when attempting to +** process a table with generated columns and a descending index. This is +** not considered a bug since SQLite versions 3.3.0 and earlier do not support +** either generated columns or descending indexes. +** </dd> +** +** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] +** <dt>SQLITE_DBCONFIG_STMT_SCANSTATUS</dt> +** <dd>The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in +** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears +** a flag that enables collection of the sqlite3_stmt_scanstatus_v2() +** statistics. For statistics to be collected, the flag must be set on +** the database handle both when the SQL statement is prepared and when it +** is stepped. The flag is set (collection of statistics is enabled) +** by default. This option takes two arguments: an integer and a pointer to +** an integer.. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the statement scanstatus option. If the second argument +** is not NULL, then the value of the statement scanstatus setting after +** processing the first argument is written into the integer that the second +** argument points to. +** </dd> +** +** [[SQLITE_DBCONFIG_REVERSE_SCANORDER]] +** <dt>SQLITE_DBCONFIG_REVERSE_SCANORDER</dt> +** <dd>The SQLITE_DBCONFIG_REVERSE_SCANORDER option changes the default order +** in which tables and indexes are scanned so that the scans start at the end +** and work toward the beginning rather than starting at the beginning and +** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the +** same as setting [PRAGMA reverse_unordered_selects]. This option takes +** two arguments which are an integer and a pointer to an integer. The first +** argument is 1, 0, or -1 to enable, disable, or leave unchanged the +** reverse scan order flag, respectively. If the second argument is not NULL, +** then 0 or 1 is written into the integer that the second argument points to +** depending on if the reverse scan order flag is set after processing the +** first argument. +** </dd> +** +** </dl> +*/ +#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ +#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ +#define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */ +#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */ +#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */ +#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */ +#define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */ +#define SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011 /* int int* */ +#define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012 /* int int* */ +#define SQLITE_DBCONFIG_DQS_DML 1013 /* int int* */ +#define SQLITE_DBCONFIG_DQS_DDL 1014 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */ +#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */ +#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */ +#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */ +#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */ +#define SQLITE_DBCONFIG_MAX 1019 /* Largest DBCONFIG */ + +/* +** CAPI3REF: Enable Or Disable Extended Result Codes +** METHOD: sqlite3 +** +** ^The sqlite3_extended_result_codes() routine enables or disables the +** [extended result codes] feature of SQLite. ^The extended result +** codes are disabled by default for historical compatibility. +*/ +SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); + +/* +** CAPI3REF: Last Insert Rowid +** METHOD: sqlite3 +** +** ^Each entry in most SQLite tables (except for [WITHOUT ROWID] tables) +** has a unique 64-bit signed +** integer key called the [ROWID | "rowid"]. ^The rowid is always available +** as an undeclared column named ROWID, OID, or _ROWID_ as long as those +** names are not also used by explicitly declared columns. ^If +** the table has a column of type [INTEGER PRIMARY KEY] then that column +** is another alias for the rowid. +** +** ^The sqlite3_last_insert_rowid(D) interface usually returns the [rowid] of +** the most recent successful [INSERT] into a rowid table or [virtual table] +** on database connection D. ^Inserts into [WITHOUT ROWID] tables are not +** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred +** on the database connection D, then sqlite3_last_insert_rowid(D) returns +** zero. +** +** As well as being set automatically as rows are inserted into database +** tables, the value returned by this function may be set explicitly by +** [sqlite3_set_last_insert_rowid()] +** +** Some virtual table implementations may INSERT rows into rowid tables as +** part of committing a transaction (e.g. to flush data accumulated in memory +** to disk). In this case subsequent calls to this function return the rowid +** associated with these internal INSERT operations, which leads to +** unintuitive results. Virtual table implementations that do write to rowid +** tables in this way can avoid this problem by restoring the original +** rowid value using [sqlite3_set_last_insert_rowid()] before returning +** control to the user. +** +** ^(If an [INSERT] occurs within a trigger then this routine will +** return the [rowid] of the inserted row as long as the trigger is +** running. Once the trigger program ends, the value returned +** by this routine reverts to what it was before the trigger was fired.)^ +** +** ^An [INSERT] that fails due to a constraint violation is not a +** successful [INSERT] and does not change the value returned by this +** routine. ^Thus INSERT OR FAIL, INSERT OR IGNORE, INSERT OR ROLLBACK, +** and INSERT OR ABORT make no changes to the return value of this +** routine when their insertion fails. ^(When INSERT OR REPLACE +** encounters a constraint violation, it does not fail. The +** INSERT continues to completion after deleting rows that caused +** the constraint problem so INSERT OR REPLACE will always change +** the return value of this interface.)^ +** +** ^For the purposes of this routine, an [INSERT] is considered to +** be successful even if it is subsequently rolled back. +** +** This function is accessible to SQL statements via the +** [last_insert_rowid() SQL function]. +** +** If a separate thread performs a new [INSERT] on the same +** database connection while the [sqlite3_last_insert_rowid()] +** function is running and thus changes the last insert [rowid], +** then the value returned by [sqlite3_last_insert_rowid()] is +** unpredictable and might not equal either the old or the new +** last insert [rowid]. +*/ +SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); + +/* +** CAPI3REF: Set the Last Insert Rowid value. +** METHOD: sqlite3 +** +** The sqlite3_set_last_insert_rowid(D, R) method allows the application to +** set the value returned by calling sqlite3_last_insert_rowid(D) to R +** without inserting a row into the database. +*/ +SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); + +/* +** CAPI3REF: Count The Number Of Rows Modified +** METHOD: sqlite3 +** +** ^These functions return the number of rows modified, inserted or +** deleted by the most recently completed INSERT, UPDATE or DELETE +** statement on the database connection specified by the only parameter. +** The two functions are identical except for the type of the return value +** and that if the number of rows modified by the most recent INSERT, UPDATE +** or DELETE is greater than the maximum value supported by type "int", then +** the return value of sqlite3_changes() is undefined. ^Executing any other +** type of SQL statement does not modify the value returned by these functions. +** +** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are +** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], +** [foreign key actions] or [REPLACE] constraint resolution are not counted. +** +** Changes to a view that are intercepted by +** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value +** returned by sqlite3_changes() immediately after an INSERT, UPDATE or +** DELETE statement run on a view is always zero. Only changes made to real +** tables are counted. +** +** Things are more complicated if the sqlite3_changes() function is +** executed while a trigger program is running. This may happen if the +** program uses the [changes() SQL function], or if some other callback +** function invokes sqlite3_changes() directly. Essentially: +** +** <ul> +** <li> ^(Before entering a trigger program the value returned by +** sqlite3_changes() function is saved. After the trigger program +** has finished, the original value is restored.)^ +** +** <li> ^(Within a trigger program each INSERT, UPDATE and DELETE +** statement sets the value returned by sqlite3_changes() +** upon completion as normal. Of course, this value will not include +** any changes performed by sub-triggers, as the sqlite3_changes() +** value will be saved and restored after each sub-trigger has run.)^ +** </ul> +** +** ^This means that if the changes() SQL function (or similar) is used +** by the first INSERT, UPDATE or DELETE statement within a trigger, it +** returns the value as set when the calling statement began executing. +** ^If it is used by the second or subsequent such statement within a trigger +** program, the value returned reflects the number of rows modified by the +** previous INSERT, UPDATE or DELETE statement within the same trigger. +** +** If a separate thread makes changes on the same database connection +** while [sqlite3_changes()] is running then the value returned +** is unpredictable and not meaningful. +** +** See also: +** <ul> +** <li> the [sqlite3_total_changes()] interface +** <li> the [count_changes pragma] +** <li> the [changes() SQL function] +** <li> the [data_version pragma] +** </ul> +*/ +SQLITE_API int sqlite3_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3*); + +/* +** CAPI3REF: Total Number Of Rows Modified +** METHOD: sqlite3 +** +** ^These functions return the total number of rows inserted, modified or +** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed +** since the database connection was opened, including those executed as +** part of trigger programs. The two functions are identical except for the +** type of the return value and that if the number of rows modified by the +** connection exceeds the maximum value supported by type "int", then +** the return value of sqlite3_total_changes() is undefined. ^Executing +** any other type of SQL statement does not affect the value returned by +** sqlite3_total_changes(). +** +** ^Changes made as part of [foreign key actions] are included in the +** count, but those made as part of REPLACE constraint resolution are +** not. ^Changes to a view that are intercepted by INSTEAD OF triggers +** are not counted. +** +** The [sqlite3_total_changes(D)] interface only reports the number +** of rows that changed due to SQL statement run against database +** connection D. Any changes by other database connections are ignored. +** To detect changes against a database file from other database +** connections use the [PRAGMA data_version] command or the +** [SQLITE_FCNTL_DATA_VERSION] [file control]. +** +** If a separate thread makes changes on the same database connection +** while [sqlite3_total_changes()] is running then the value +** returned is unpredictable and not meaningful. +** +** See also: +** <ul> +** <li> the [sqlite3_changes()] interface +** <li> the [count_changes pragma] +** <li> the [changes() SQL function] +** <li> the [data_version pragma] +** <li> the [SQLITE_FCNTL_DATA_VERSION] [file control] +** </ul> +*/ +SQLITE_API int sqlite3_total_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*); + +/* +** CAPI3REF: Interrupt A Long-Running Query +** METHOD: sqlite3 +** +** ^This function causes any pending database operation to abort and +** return at its earliest opportunity. This routine is typically +** called in response to a user action such as pressing "Cancel" +** or Ctrl-C where the user wants a long query operation to halt +** immediately. +** +** ^It is safe to call this routine from a thread different from the +** thread that is currently running the database operation. But it +** is not safe to call this routine with a [database connection] that +** is closed or might close before sqlite3_interrupt() returns. +** +** ^If an SQL operation is very nearly finished at the time when +** sqlite3_interrupt() is called, then it might not have an opportunity +** to be interrupted and might continue to completion. +** +** ^An SQL operation that is interrupted will return [SQLITE_INTERRUPT]. +** ^If the interrupted SQL operation is an INSERT, UPDATE, or DELETE +** that is inside an explicit transaction, then the entire transaction +** will be rolled back automatically. +** +** ^The sqlite3_interrupt(D) call is in effect until all currently running +** SQL statements on [database connection] D complete. ^Any new SQL statements +** that are started after the sqlite3_interrupt() call and before the +** running statement count reaches zero are interrupted as if they had been +** running prior to the sqlite3_interrupt() call. ^New SQL statements +** that are started after the running statement count reaches zero are +** not effected by the sqlite3_interrupt(). +** ^A call to sqlite3_interrupt(D) that occurs when there are no running +** SQL statements is a no-op and has no effect on SQL statements +** that are started after the sqlite3_interrupt() call returns. +** +** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether +** or not an interrupt is currently in effect for [database connection] D. +** It returns 1 if an interrupt is currently in effect, or 0 otherwise. +*/ +SQLITE_API void sqlite3_interrupt(sqlite3*); +SQLITE_API int sqlite3_is_interrupted(sqlite3*); + +/* +** CAPI3REF: Determine If An SQL Statement Is Complete +** +** These routines are useful during command-line input to determine if the +** currently entered text seems to form a complete SQL statement or +** if additional input is needed before sending the text into +** SQLite for parsing. ^These routines return 1 if the input string +** appears to be a complete SQL statement. ^A statement is judged to be +** complete if it ends with a semicolon token and is not a prefix of a +** well-formed CREATE TRIGGER statement. ^Semicolons that are embedded within +** string literals or quoted identifier names or comments are not +** independent tokens (they are part of the token in which they are +** embedded) and thus do not count as a statement terminator. ^Whitespace +** and comments that follow the final semicolon are ignored. +** +** ^These routines return 0 if the statement is incomplete. ^If a +** memory allocation fails, then SQLITE_NOMEM is returned. +** +** ^These routines do not parse the SQL statements thus +** will not detect syntactically incorrect SQL. +** +** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior +** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked +** automatically by sqlite3_complete16(). If that initialization fails, +** then the return value from sqlite3_complete16() will be non-zero +** regardless of whether or not the input SQL is complete.)^ +** +** The input to [sqlite3_complete()] must be a zero-terminated +** UTF-8 string. +** +** The input to [sqlite3_complete16()] must be a zero-terminated +** UTF-16 string in native byte order. +*/ +SQLITE_API int sqlite3_complete(const char *sql); +SQLITE_API int sqlite3_complete16(const void *sql); + +/* +** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors +** KEYWORDS: {busy-handler callback} {busy handler} +** METHOD: sqlite3 +** +** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X +** that might be invoked with argument P whenever +** an attempt is made to access a database table associated with +** [database connection] D when another thread +** or process has the table locked. +** The sqlite3_busy_handler() interface is used to implement +** [sqlite3_busy_timeout()] and [PRAGMA busy_timeout]. +** +** ^If the busy callback is NULL, then [SQLITE_BUSY] +** is returned immediately upon encountering the lock. ^If the busy callback +** is not NULL, then the callback might be invoked with two arguments. +** +** ^The first argument to the busy handler is a copy of the void* pointer which +** is the third argument to sqlite3_busy_handler(). ^The second argument to +** the busy handler callback is the number of times that the busy handler has +** been invoked previously for the same locking event. ^If the +** busy callback returns 0, then no additional attempts are made to +** access the database and [SQLITE_BUSY] is returned +** to the application. +** ^If the callback returns non-zero, then another attempt +** is made to access the database and the cycle repeats. +** +** The presence of a busy handler does not guarantee that it will be invoked +** when there is lock contention. ^If SQLite determines that invoking the busy +** handler could result in a deadlock, it will go ahead and return [SQLITE_BUSY] +** to the application instead of invoking the +** busy handler. +** Consider a scenario where one process is holding a read lock that +** it is trying to promote to a reserved lock and +** a second process is holding a reserved lock that it is trying +** to promote to an exclusive lock. The first process cannot proceed +** because it is blocked by the second and the second process cannot +** proceed because it is blocked by the first. If both processes +** invoke the busy handlers, neither will make any progress. Therefore, +** SQLite returns [SQLITE_BUSY] for the first process, hoping that this +** will induce the first process to release its read lock and allow +** the second process to proceed. +** +** ^The default busy callback is NULL. +** +** ^(There can only be a single busy handler defined for each +** [database connection]. Setting a new busy handler clears any +** previously set handler.)^ ^Note that calling [sqlite3_busy_timeout()] +** or evaluating [PRAGMA busy_timeout=N] will change the +** busy handler and thus clear any previously set busy handler. +** +** The busy callback should not take any actions which modify the +** database connection that invoked the busy handler. In other words, +** the busy handler is not reentrant. Any such actions +** result in undefined behavior. +** +** A busy handler must not close the database connection +** or [prepared statement] that invoked the busy handler. +*/ +SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*); + +/* +** CAPI3REF: Set A Busy Timeout +** METHOD: sqlite3 +** +** ^This routine sets a [sqlite3_busy_handler | busy handler] that sleeps +** for a specified amount of time when a table is locked. ^The handler +** will sleep multiple times until at least "ms" milliseconds of sleeping +** have accumulated. ^After at least "ms" milliseconds of sleeping, +** the handler returns 0 which causes [sqlite3_step()] to return +** [SQLITE_BUSY]. +** +** ^Calling this routine with an argument less than or equal to zero +** turns off all busy handlers. +** +** ^(There can only be a single busy handler for a particular +** [database connection] at any given moment. If another busy handler +** was defined (using [sqlite3_busy_handler()]) prior to calling +** this routine, that other busy handler is cleared.)^ +** +** See also: [PRAGMA busy_timeout] +*/ +SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); + +/* +** CAPI3REF: Convenience Routines For Running Queries +** METHOD: sqlite3 +** +** This is a legacy interface that is preserved for backwards compatibility. +** Use of this interface is not recommended. +** +** Definition: A <b>result table</b> is memory data structure created by the +** [sqlite3_get_table()] interface. A result table records the +** complete query results from one or more queries. +** +** The table conceptually has a number of rows and columns. But +** these numbers are not part of the result table itself. These +** numbers are obtained separately. Let N be the number of rows +** and M be the number of columns. +** +** A result table is an array of pointers to zero-terminated UTF-8 strings. +** There are (N+1)*M elements in the array. The first M pointers point +** to zero-terminated strings that contain the names of the columns. +** The remaining entries all point to query results. NULL values result +** in NULL pointers. All other values are in their UTF-8 zero-terminated +** string representation as returned by [sqlite3_column_text()]. +** +** A result table might consist of one or more memory allocations. +** It is not safe to pass a result table directly to [sqlite3_free()]. +** A result table should be deallocated using [sqlite3_free_table()]. +** +** ^(As an example of the result table format, suppose a query result +** is as follows: +** +** <blockquote><pre> +** Name | Age +** ----------------------- +** Alice | 43 +** Bob | 28 +** Cindy | 21 +** </pre></blockquote> +** +** There are two columns (M==2) and three rows (N==3). Thus the +** result table has 8 entries. Suppose the result table is stored +** in an array named azResult. Then azResult holds this content: +** +** <blockquote><pre> +** azResult&#91;0] = "Name"; +** azResult&#91;1] = "Age"; +** azResult&#91;2] = "Alice"; +** azResult&#91;3] = "43"; +** azResult&#91;4] = "Bob"; +** azResult&#91;5] = "28"; +** azResult&#91;6] = "Cindy"; +** azResult&#91;7] = "21"; +** </pre></blockquote>)^ +** +** ^The sqlite3_get_table() function evaluates one or more +** semicolon-separated SQL statements in the zero-terminated UTF-8 +** string of its 2nd parameter and returns a result table to the +** pointer given in its 3rd parameter. +** +** After the application has finished with the result from sqlite3_get_table(), +** it must pass the result table pointer to sqlite3_free_table() in order to +** release the memory that was malloced. Because of the way the +** [sqlite3_malloc()] happens within sqlite3_get_table(), the calling +** function must not try to call [sqlite3_free()] directly. Only +** [sqlite3_free_table()] is able to release the memory properly and safely. +** +** The sqlite3_get_table() interface is implemented as a wrapper around +** [sqlite3_exec()]. The sqlite3_get_table() routine does not have access +** to any internal data structures of SQLite. It uses only the public +** interface defined here. As a consequence, errors that occur in the +** wrapper layer outside of the internal [sqlite3_exec()] call are not +** reflected in subsequent calls to [sqlite3_errcode()] or +** [sqlite3_errmsg()]. +*/ +SQLITE_API int sqlite3_get_table( + sqlite3 *db, /* An open database */ + const char *zSql, /* SQL to be evaluated */ + char ***pazResult, /* Results of the query */ + int *pnRow, /* Number of result rows written here */ + int *pnColumn, /* Number of result columns written here */ + char **pzErrmsg /* Error msg written here */ +); +SQLITE_API void sqlite3_free_table(char **result); + +/* +** CAPI3REF: Formatted String Printing Functions +** +** These routines are work-alikes of the "printf()" family of functions +** from the standard C library. +** These routines understand most of the common formatting options from +** the standard library printf() +** plus some additional non-standard formats ([%q], [%Q], [%w], and [%z]). +** See the [built-in printf()] documentation for details. +** +** ^The sqlite3_mprintf() and sqlite3_vmprintf() routines write their +** results into memory obtained from [sqlite3_malloc64()]. +** The strings returned by these two routines should be +** released by [sqlite3_free()]. ^Both routines return a +** NULL pointer if [sqlite3_malloc64()] is unable to allocate enough +** memory to hold the resulting string. +** +** ^(The sqlite3_snprintf() routine is similar to "snprintf()" from +** the standard C library. The result is written into the +** buffer supplied as the second parameter whose size is given by +** the first parameter. Note that the order of the +** first two parameters is reversed from snprintf().)^ This is an +** historical accident that cannot be fixed without breaking +** backwards compatibility. ^(Note also that sqlite3_snprintf() +** returns a pointer to its buffer instead of the number of +** characters actually written into the buffer.)^ We admit that +** the number of characters written would be a more useful return +** value but we cannot change the implementation of sqlite3_snprintf() +** now without breaking compatibility. +** +** ^As long as the buffer size is greater than zero, sqlite3_snprintf() +** guarantees that the buffer is always zero-terminated. ^The first +** parameter "n" is the total size of the buffer, including space for +** the zero terminator. So the longest string that can be completely +** written will be n-1 characters. +** +** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf(). +** +** See also: [built-in printf()], [printf() SQL function] +*/ +SQLITE_API char *sqlite3_mprintf(const char*,...); +SQLITE_API char *sqlite3_vmprintf(const char*, va_list); +SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...); +SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); + +/* +** CAPI3REF: Memory Allocation Subsystem +** +** The SQLite core uses these three routines for all of its own +** internal memory allocation needs. "Core" in the previous sentence +** does not include operating-system specific [VFS] implementation. The +** Windows VFS uses native malloc() and free() for some operations. +** +** ^The sqlite3_malloc() routine returns a pointer to a block +** of memory at least N bytes in length, where N is the parameter. +** ^If sqlite3_malloc() is unable to obtain sufficient free +** memory, it returns a NULL pointer. ^If the parameter N to +** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns +** a NULL pointer. +** +** ^The sqlite3_malloc64(N) routine works just like +** sqlite3_malloc(N) except that N is an unsigned 64-bit integer instead +** of a signed 32-bit integer. +** +** ^Calling sqlite3_free() with a pointer previously returned +** by sqlite3_malloc() or sqlite3_realloc() releases that memory so +** that it might be reused. ^The sqlite3_free() routine is +** a no-op if is called with a NULL pointer. Passing a NULL pointer +** to sqlite3_free() is harmless. After being freed, memory +** should neither be read nor written. Even reading previously freed +** memory might result in a segmentation fault or other severe error. +** Memory corruption, a segmentation fault, or other severe error +** might result if sqlite3_free() is called with a non-NULL pointer that +** was not obtained from sqlite3_malloc() or sqlite3_realloc(). +** +** ^The sqlite3_realloc(X,N) interface attempts to resize a +** prior memory allocation X to be at least N bytes. +** ^If the X parameter to sqlite3_realloc(X,N) +** is a NULL pointer then its behavior is identical to calling +** sqlite3_malloc(N). +** ^If the N parameter to sqlite3_realloc(X,N) is zero or +** negative then the behavior is exactly the same as calling +** sqlite3_free(X). +** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation +** of at least N bytes in size or NULL if insufficient memory is available. +** ^If M is the size of the prior allocation, then min(N,M) bytes +** of the prior allocation are copied into the beginning of buffer returned +** by sqlite3_realloc(X,N) and the prior allocation is freed. +** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the +** prior allocation is not freed. +** +** ^The sqlite3_realloc64(X,N) interfaces works the same as +** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead +** of a 32-bit signed integer. +** +** ^If X is a memory allocation previously obtained from sqlite3_malloc(), +** sqlite3_malloc64(), sqlite3_realloc(), or sqlite3_realloc64(), then +** sqlite3_msize(X) returns the size of that memory allocation in bytes. +** ^The value returned by sqlite3_msize(X) might be larger than the number +** of bytes requested when X was allocated. ^If X is a NULL pointer then +** sqlite3_msize(X) returns zero. If X points to something that is not +** the beginning of memory allocation, or if it points to a formerly +** valid memory allocation that has now been freed, then the behavior +** of sqlite3_msize(X) is undefined and possibly harmful. +** +** ^The memory returned by sqlite3_malloc(), sqlite3_realloc(), +** sqlite3_malloc64(), and sqlite3_realloc64() +** is always aligned to at least an 8 byte boundary, or to a +** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time +** option is used. +** +** The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()] +** must be either NULL or else pointers obtained from a prior +** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that have +** not yet been released. +** +** The application must not read or write any part of +** a block of memory after it has been released using +** [sqlite3_free()] or [sqlite3_realloc()]. +*/ +SQLITE_API void *sqlite3_malloc(int); +SQLITE_API void *sqlite3_malloc64(sqlite3_uint64); +SQLITE_API void *sqlite3_realloc(void*, int); +SQLITE_API void *sqlite3_realloc64(void*, sqlite3_uint64); +SQLITE_API void sqlite3_free(void*); +SQLITE_API sqlite3_uint64 sqlite3_msize(void*); + +/* +** CAPI3REF: Memory Allocator Statistics +** +** SQLite provides these two interfaces for reporting on the status +** of the [sqlite3_malloc()], [sqlite3_free()], and [sqlite3_realloc()] +** routines, which form the built-in memory allocation subsystem. +** +** ^The [sqlite3_memory_used()] routine returns the number of bytes +** of memory currently outstanding (malloced but not freed). +** ^The [sqlite3_memory_highwater()] routine returns the maximum +** value of [sqlite3_memory_used()] since the high-water mark +** was last reset. ^The values returned by [sqlite3_memory_used()] and +** [sqlite3_memory_highwater()] include any overhead +** added by SQLite in its implementation of [sqlite3_malloc()], +** but not overhead added by the any underlying system library +** routines that [sqlite3_malloc()] may call. +** +** ^The memory high-water mark is reset to the current value of +** [sqlite3_memory_used()] if and only if the parameter to +** [sqlite3_memory_highwater()] is true. ^The value returned +** by [sqlite3_memory_highwater(1)] is the high-water mark +** prior to the reset. +*/ +SQLITE_API sqlite3_int64 sqlite3_memory_used(void); +SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag); + +/* +** CAPI3REF: Pseudo-Random Number Generator +** +** SQLite contains a high-quality pseudo-random number generator (PRNG) used to +** select random [ROWID | ROWIDs] when inserting new records into a table that +** already uses the largest possible [ROWID]. The PRNG is also used for +** the built-in random() and randomblob() SQL functions. This interface allows +** applications to access the same PRNG for other purposes. +** +** ^A call to this routine stores N bytes of randomness into buffer P. +** ^The P parameter can be a NULL pointer. +** +** ^If this routine has not been previously called or if the previous +** call had N less than one or a NULL pointer for P, then the PRNG is +** seeded using randomness obtained from the xRandomness method of +** the default [sqlite3_vfs] object. +** ^If the previous call to this routine had an N of 1 or more and a +** non-NULL P then the pseudo-randomness is generated +** internally and without recourse to the [sqlite3_vfs] xRandomness +** method. +*/ +SQLITE_API void sqlite3_randomness(int N, void *P); + +/* +** CAPI3REF: Compile-Time Authorization Callbacks +** METHOD: sqlite3 +** KEYWORDS: {authorizer callback} +** +** ^This routine registers an authorizer callback with a particular +** [database connection], supplied in the first argument. +** ^The authorizer callback is invoked as SQL statements are being compiled +** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()], +** [sqlite3_prepare_v3()], [sqlite3_prepare16()], [sqlite3_prepare16_v2()], +** and [sqlite3_prepare16_v3()]. ^At various +** points during the compilation process, as logic is being created +** to perform various actions, the authorizer callback is invoked to +** see if those actions are allowed. ^The authorizer callback should +** return [SQLITE_OK] to allow the action, [SQLITE_IGNORE] to disallow the +** specific action but allow the SQL statement to continue to be +** compiled, or [SQLITE_DENY] to cause the entire SQL statement to be +** rejected with an error. ^If the authorizer callback returns +** any value other than [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY] +** then the [sqlite3_prepare_v2()] or equivalent call that triggered +** the authorizer will fail with an error message. +** +** When the callback returns [SQLITE_OK], that means the operation +** requested is ok. ^When the callback returns [SQLITE_DENY], the +** [sqlite3_prepare_v2()] or equivalent call that triggered the +** authorizer will fail with an error message explaining that +** access is denied. +** +** ^The first parameter to the authorizer callback is a copy of the third +** parameter to the sqlite3_set_authorizer() interface. ^The second parameter +** to the callback is an integer [SQLITE_COPY | action code] that specifies +** the particular action to be authorized. ^The third through sixth parameters +** to the callback are either NULL pointers or zero-terminated strings +** that contain additional details about the action to be authorized. +** Applications must always be prepared to encounter a NULL pointer in any +** of the third through the sixth parameters of the authorization callback. +** +** ^If the action code is [SQLITE_READ] +** and the callback returns [SQLITE_IGNORE] then the +** [prepared statement] statement is constructed to substitute +** a NULL value in place of the table column that would have +** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE] +** return can be used to deny an untrusted user access to individual +** columns of a table. +** ^When a table is referenced by a [SELECT] but no column values are +** extracted from that table (for example in a query like +** "SELECT count(*) FROM tab") then the [SQLITE_READ] authorizer callback +** is invoked once for that table with a column name that is an empty string. +** ^If the action code is [SQLITE_DELETE] and the callback returns +** [SQLITE_IGNORE] then the [DELETE] operation proceeds but the +** [truncate optimization] is disabled and all rows are deleted individually. +** +** An authorizer is used when [sqlite3_prepare | preparing] +** SQL statements from an untrusted source, to ensure that the SQL statements +** do not try to access data they are not allowed to see, or that they do not +** try to execute malicious statements that damage the database. For +** example, an application may allow a user to enter arbitrary +** SQL queries for evaluation by a database. But the application does +** not want the user to be able to make arbitrary changes to the +** database. An authorizer could then be put in place while the +** user-entered SQL is being [sqlite3_prepare | prepared] that +** disallows everything except [SELECT] statements. +** +** Applications that need to process SQL from untrusted sources +** might also consider lowering resource limits using [sqlite3_limit()] +** and limiting database size using the [max_page_count] [PRAGMA] +** in addition to using an authorizer. +** +** ^(Only a single authorizer can be in place on a database connection +** at a time. Each call to sqlite3_set_authorizer overrides the +** previous call.)^ ^Disable the authorizer by installing a NULL callback. +** The authorizer is disabled by default. +** +** The authorizer callback must not do anything that will modify +** the database connection that invoked the authorizer callback. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** ^When [sqlite3_prepare_v2()] is used to prepare a statement, the +** statement might be re-prepared during [sqlite3_step()] due to a +** schema change. Hence, the application should ensure that the +** correct authorizer callback remains in place during the [sqlite3_step()]. +** +** ^Note that the authorizer callback is invoked only during +** [sqlite3_prepare()] or its variants. Authorization is not +** performed during statement evaluation in [sqlite3_step()], unless +** as stated in the previous paragraph, sqlite3_step() invokes +** sqlite3_prepare_v2() to reprepare a statement after a schema change. +*/ +SQLITE_API int sqlite3_set_authorizer( + sqlite3*, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pUserData +); + +/* +** CAPI3REF: Authorizer Return Codes +** +** The [sqlite3_set_authorizer | authorizer callback function] must +** return either [SQLITE_OK] or one of these two constants in order +** to signal SQLite whether or not the action is permitted. See the +** [sqlite3_set_authorizer | authorizer documentation] for additional +** information. +** +** Note that SQLITE_IGNORE is also used as a [conflict resolution mode] +** returned from the [sqlite3_vtab_on_conflict()] interface. +*/ +#define SQLITE_DENY 1 /* Abort the SQL statement with an error */ +#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ + +/* +** CAPI3REF: Authorizer Action Codes +** +** The [sqlite3_set_authorizer()] interface registers a callback function +** that is invoked to authorize certain SQL statement actions. The +** second parameter to the callback is an integer code that specifies +** what action is being authorized. These are the integer action codes that +** the authorizer callback may be passed. +** +** These action code values signify what kind of operation is to be +** authorized. The 3rd and 4th parameters to the authorization +** callback function will be parameters or NULL depending on which of these +** codes is used as the second parameter. ^(The 5th parameter to the +** authorizer callback is the name of the database ("main", "temp", +** etc.) if applicable.)^ ^The 6th parameter to the authorizer callback +** is the name of the inner-most trigger or view that is responsible for +** the access attempt or NULL if this access attempt is directly from +** top-level SQL code. +*/ +/******************************************* 3rd ************ 4th ***********/ +#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */ +#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */ +#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */ +#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */ +#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */ +#define SQLITE_CREATE_VIEW 8 /* View Name NULL */ +#define SQLITE_DELETE 9 /* Table Name NULL */ +#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */ +#define SQLITE_DROP_TABLE 11 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */ +#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */ +#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */ +#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */ +#define SQLITE_DROP_VIEW 17 /* View Name NULL */ +#define SQLITE_INSERT 18 /* Table Name NULL */ +#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */ +#define SQLITE_READ 20 /* Table Name Column Name */ +#define SQLITE_SELECT 21 /* NULL NULL */ +#define SQLITE_TRANSACTION 22 /* Operation NULL */ +#define SQLITE_UPDATE 23 /* Table Name Column Name */ +#define SQLITE_ATTACH 24 /* Filename NULL */ +#define SQLITE_DETACH 25 /* Database Name NULL */ +#define SQLITE_ALTER_TABLE 26 /* Database Name Table Name */ +#define SQLITE_REINDEX 27 /* Index Name NULL */ +#define SQLITE_ANALYZE 28 /* Table Name NULL */ +#define SQLITE_CREATE_VTABLE 29 /* Table Name Module Name */ +#define SQLITE_DROP_VTABLE 30 /* Table Name Module Name */ +#define SQLITE_FUNCTION 31 /* NULL Function Name */ +#define SQLITE_SAVEPOINT 32 /* Operation Savepoint Name */ +#define SQLITE_COPY 0 /* No longer used */ +#define SQLITE_RECURSIVE 33 /* NULL NULL */ + +/* +** CAPI3REF: Tracing And Profiling Functions +** METHOD: sqlite3 +** +** These routines are deprecated. Use the [sqlite3_trace_v2()] interface +** instead of the routines described here. +** +** These routines register callback functions that can be used for +** tracing and profiling the execution of SQL statements. +** +** ^The callback function registered by sqlite3_trace() is invoked at +** various times when an SQL statement is being run by [sqlite3_step()]. +** ^The sqlite3_trace() callback is invoked with a UTF-8 rendering of the +** SQL statement text as the statement first begins executing. +** ^(Additional sqlite3_trace() callbacks might occur +** as each triggered subprogram is entered. The callbacks for triggers +** contain a UTF-8 SQL comment that identifies the trigger.)^ +** +** The [SQLITE_TRACE_SIZE_LIMIT] compile-time option can be used to limit +** the length of [bound parameter] expansion in the output of sqlite3_trace(). +** +** ^The callback function registered by sqlite3_profile() is invoked +** as each SQL statement finishes. ^The profile callback contains +** the original statement text and an estimate of wall-clock time +** of how long that statement took to run. ^The profile callback +** time is in units of nanoseconds, however the current implementation +** is only capable of millisecond resolution so the six least significant +** digits in the time are meaningless. Future versions of SQLite +** might provide greater resolution on the profiler callback. Invoking +** either [sqlite3_trace()] or [sqlite3_trace_v2()] will cancel the +** profile callback. +*/ +SQLITE_API SQLITE_DEPRECATED void *sqlite3_trace(sqlite3*, + void(*xTrace)(void*,const char*), void*); +SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, + void(*xProfile)(void*,const char*,sqlite3_uint64), void*); + +/* +** CAPI3REF: SQL Trace Event Codes +** KEYWORDS: SQLITE_TRACE +** +** These constants identify classes of events that can be monitored +** using the [sqlite3_trace_v2()] tracing logic. The M argument +** to [sqlite3_trace_v2(D,M,X,P)] is an OR-ed combination of one or more of +** the following constants. ^The first argument to the trace callback +** is one of the following constants. +** +** New tracing constants may be added in future releases. +** +** ^A trace callback has four arguments: xCallback(T,C,P,X). +** ^The T argument is one of the integer type codes above. +** ^The C argument is a copy of the context pointer passed in as the +** fourth argument to [sqlite3_trace_v2()]. +** The P and X arguments are pointers whose meanings depend on T. +** +** <dl> +** [[SQLITE_TRACE_STMT]] <dt>SQLITE_TRACE_STMT</dt> +** <dd>^An SQLITE_TRACE_STMT callback is invoked when a prepared statement +** first begins running and possibly at other times during the +** execution of the prepared statement, such as at the start of each +** trigger subprogram. ^The P argument is a pointer to the +** [prepared statement]. ^The X argument is a pointer to a string which +** is the unexpanded SQL text of the prepared statement or an SQL comment +** that indicates the invocation of a trigger. ^The callback can compute +** the same text that would have been returned by the legacy [sqlite3_trace()] +** interface by using the X argument when X begins with "--" and invoking +** [sqlite3_expanded_sql(P)] otherwise. +** +** [[SQLITE_TRACE_PROFILE]] <dt>SQLITE_TRACE_PROFILE</dt> +** <dd>^An SQLITE_TRACE_PROFILE callback provides approximately the same +** information as is provided by the [sqlite3_profile()] callback. +** ^The P argument is a pointer to the [prepared statement] and the +** X argument points to a 64-bit integer which is approximately +** the number of nanoseconds that the prepared statement took to run. +** ^The SQLITE_TRACE_PROFILE callback is invoked when the statement finishes. +** +** [[SQLITE_TRACE_ROW]] <dt>SQLITE_TRACE_ROW</dt> +** <dd>^An SQLITE_TRACE_ROW callback is invoked whenever a prepared +** statement generates a single row of result. +** ^The P argument is a pointer to the [prepared statement] and the +** X argument is unused. +** +** [[SQLITE_TRACE_CLOSE]] <dt>SQLITE_TRACE_CLOSE</dt> +** <dd>^An SQLITE_TRACE_CLOSE callback is invoked when a database +** connection closes. +** ^The P argument is a pointer to the [database connection] object +** and the X argument is unused. +** </dl> +*/ +#define SQLITE_TRACE_STMT 0x01 +#define SQLITE_TRACE_PROFILE 0x02 +#define SQLITE_TRACE_ROW 0x04 +#define SQLITE_TRACE_CLOSE 0x08 + +/* +** CAPI3REF: SQL Trace Hook +** METHOD: sqlite3 +** +** ^The sqlite3_trace_v2(D,M,X,P) interface registers a trace callback +** function X against [database connection] D, using property mask M +** and context pointer P. ^If the X callback is +** NULL or if the M mask is zero, then tracing is disabled. The +** M argument should be the bitwise OR-ed combination of +** zero or more [SQLITE_TRACE] constants. +** +** ^Each call to either sqlite3_trace(D,X,P) or sqlite3_trace_v2(D,M,X,P) +** overrides (cancels) all prior calls to sqlite3_trace(D,X,P) or +** sqlite3_trace_v2(D,M,X,P) for the [database connection] D. Each +** database connection may have at most one trace callback. +** +** ^The X callback is invoked whenever any of the events identified by +** mask M occur. ^The integer return value from the callback is currently +** ignored, though this may change in future releases. Callback +** implementations should return zero to ensure future compatibility. +** +** ^A trace callback is invoked with four arguments: callback(T,C,P,X). +** ^The T argument is one of the [SQLITE_TRACE] +** constants to indicate why the callback was invoked. +** ^The C argument is a copy of the context pointer. +** The P and X arguments are pointers whose meanings depend on T. +** +** The sqlite3_trace_v2() interface is intended to replace the legacy +** interfaces [sqlite3_trace()] and [sqlite3_profile()], both of which +** are deprecated. +*/ +SQLITE_API int sqlite3_trace_v2( + sqlite3*, + unsigned uMask, + int(*xCallback)(unsigned,void*,void*,void*), + void *pCtx +); + +/* +** CAPI3REF: Query Progress Callbacks +** METHOD: sqlite3 +** +** ^The sqlite3_progress_handler(D,N,X,P) interface causes the callback +** function X to be invoked periodically during long running calls to +** [sqlite3_step()] and [sqlite3_prepare()] and similar for +** database connection D. An example use for this +** interface is to keep a GUI updated during a large query. +** +** ^The parameter P is passed through as the only parameter to the +** callback function X. ^The parameter N is the approximate number of +** [virtual machine instructions] that are evaluated between successive +** invocations of the callback X. ^If N is less than one then the progress +** handler is disabled. +** +** ^Only a single progress handler may be defined at one time per +** [database connection]; setting a new progress handler cancels the +** old one. ^Setting parameter X to NULL disables the progress handler. +** ^The progress handler is also disabled by setting N to a value less +** than 1. +** +** ^If the progress callback returns non-zero, the operation is +** interrupted. This feature can be used to implement a +** "Cancel" button on a GUI progress dialog box. +** +** The progress handler callback must not do anything that will modify +** the database connection that invoked the progress handler. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** The progress handler callback would originally only be invoked from the +** bytecode engine. It still might be invoked during [sqlite3_prepare()] +** and similar because those routines might force a reparse of the schema +** which involves running the bytecode engine. However, beginning with +** SQLite version 3.41.0, the progress handler callback might also be +** invoked directly from [sqlite3_prepare()] while analyzing and generating +** code for complex queries. +*/ +SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); + +/* +** CAPI3REF: Opening A New Database Connection +** CONSTRUCTOR: sqlite3 +** +** ^These routines open an SQLite database file as specified by the +** filename argument. ^The filename argument is interpreted as UTF-8 for +** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte +** order for sqlite3_open16(). ^(A [database connection] handle is usually +** returned in *ppDb, even if an error occurs. The only exception is that +** if SQLite is unable to allocate memory to hold the [sqlite3] object, +** a NULL will be written into *ppDb instead of a pointer to the [sqlite3] +** object.)^ ^(If the database is opened (and/or created) successfully, then +** [SQLITE_OK] is returned. Otherwise an [error code] is returned.)^ ^The +** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain +** an English language description of the error following a failure of any +** of the sqlite3_open() routines. +** +** ^The default encoding will be UTF-8 for databases created using +** sqlite3_open() or sqlite3_open_v2(). ^The default encoding for databases +** created using sqlite3_open16() will be UTF-16 in the native byte order. +** +** Whether or not an error occurs when it is opened, resources +** associated with the [database connection] handle should be released by +** passing it to [sqlite3_close()] when it is no longer required. +** +** The sqlite3_open_v2() interface works like sqlite3_open() +** except that it accepts two additional parameters for additional control +** over the new database connection. ^(The flags parameter to +** sqlite3_open_v2() must include, at a minimum, one of the following +** three flag combinations:)^ +** +** <dl> +** ^(<dt>[SQLITE_OPEN_READONLY]</dt> +** <dd>The database is opened in read-only mode. If the database does +** not already exist, an error is returned.</dd>)^ +** +** ^(<dt>[SQLITE_OPEN_READWRITE]</dt> +** <dd>The database is opened for reading and writing if possible, or +** reading only if the file is write protected by the operating +** system. In either case the database must already exist, otherwise +** an error is returned. For historical reasons, if opening in +** read-write mode fails due to OS-level permissions, an attempt is +** made to open it in read-only mode. [sqlite3_db_readonly()] can be +** used to determine whether the database is actually +** read-write.</dd>)^ +** +** ^(<dt>[SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]</dt> +** <dd>The database is opened for reading and writing, and is created if +** it does not already exist. This is the behavior that is always used for +** sqlite3_open() and sqlite3_open16().</dd>)^ +** </dl> +** +** In addition to the required flags, the following optional flags are +** also supported: +** +** <dl> +** ^(<dt>[SQLITE_OPEN_URI]</dt> +** <dd>The filename can be interpreted as a URI if this flag is set.</dd>)^ +** +** ^(<dt>[SQLITE_OPEN_MEMORY]</dt> +** <dd>The database will be opened as an in-memory database. The database +** is named by the "filename" argument for the purposes of cache-sharing, +** if shared cache mode is enabled, but the "filename" is otherwise ignored. +** </dd>)^ +** +** ^(<dt>[SQLITE_OPEN_NOMUTEX]</dt> +** <dd>The new database connection will use the "multi-thread" +** [threading mode].)^ This means that separate threads are allowed +** to use SQLite at the same time, as long as each thread is using +** a different [database connection]. +** +** ^(<dt>[SQLITE_OPEN_FULLMUTEX]</dt> +** <dd>The new database connection will use the "serialized" +** [threading mode].)^ This means the multiple threads can safely +** attempt to use the same database connection at the same time. +** (Mutexes will block any actual concurrency, but in this mode +** there is no harm in trying.) +** +** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt> +** <dd>The database is opened [shared cache] enabled, overriding +** the default shared cache setting provided by +** [sqlite3_enable_shared_cache()].)^ +** The [use of shared cache mode is discouraged] and hence shared cache +** capabilities may be omitted from many builds of SQLite. In such cases, +** this option is a no-op. +** +** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt> +** <dd>The database is opened [shared cache] disabled, overriding +** the default shared cache setting provided by +** [sqlite3_enable_shared_cache()].)^ +** +** [[OPEN_EXRESCODE]] ^(<dt>[SQLITE_OPEN_EXRESCODE]</dt> +** <dd>The database connection comes up in "extended result code mode". +** In other words, the database behaves has if +** [sqlite3_extended_result_codes(db,1)] where called on the database +** connection as soon as the connection is created. In addition to setting +** the extended result code mode, this flag also causes [sqlite3_open_v2()] +** to return an extended result code.</dd> +** +** [[OPEN_NOFOLLOW]] ^(<dt>[SQLITE_OPEN_NOFOLLOW]</dt> +** <dd>The database filename is not allowed to contain a symbolic link</dd> +** </dl>)^ +** +** If the 3rd parameter to sqlite3_open_v2() is not one of the +** required combinations shown above optionally combined with other +** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits] +** then the behavior is undefined. Historic versions of SQLite +** have silently ignored surplus bits in the flags parameter to +** sqlite3_open_v2(), however that behavior might not be carried through +** into future versions of SQLite and so applications should not rely +** upon it. Note in particular that the SQLITE_OPEN_EXCLUSIVE flag is a no-op +** for sqlite3_open_v2(). The SQLITE_OPEN_EXCLUSIVE does *not* cause +** the open to fail if the database already exists. The SQLITE_OPEN_EXCLUSIVE +** flag is intended for use by the [sqlite3_vfs|VFS interface] only, and not +** by sqlite3_open_v2(). +** +** ^The fourth parameter to sqlite3_open_v2() is the name of the +** [sqlite3_vfs] object that defines the operating system interface that +** the new database connection should use. ^If the fourth parameter is +** a NULL pointer then the default [sqlite3_vfs] object is used. +** +** ^If the filename is ":memory:", then a private, temporary in-memory database +** is created for the connection. ^This in-memory database will vanish when +** the database connection is closed. Future versions of SQLite might +** make use of additional special filenames that begin with the ":" character. +** It is recommended that when a database filename actually does begin with +** a ":" character you should prefix the filename with a pathname such as +** "./" to avoid ambiguity. +** +** ^If the filename is an empty string, then a private, temporary +** on-disk database will be created. ^This private database will be +** automatically deleted as soon as the database connection is closed. +** +** [[URI filenames in sqlite3_open()]] <h3>URI Filenames</h3> +** +** ^If [URI filename] interpretation is enabled, and the filename argument +** begins with "file:", then the filename is interpreted as a URI. ^URI +** filename interpretation is enabled if the [SQLITE_OPEN_URI] flag is +** set in the third argument to sqlite3_open_v2(), or if it has +** been enabled globally using the [SQLITE_CONFIG_URI] option with the +** [sqlite3_config()] method or by the [SQLITE_USE_URI] compile-time option. +** URI filename interpretation is turned off +** by default, but future releases of SQLite might enable URI filename +** interpretation by default. See "[URI filenames]" for additional +** information. +** +** URI filenames are parsed according to RFC 3986. ^If the URI contains an +** authority, then it must be either an empty string or the string +** "localhost". ^If the authority is not an empty string or "localhost", an +** error is returned to the caller. ^The fragment component of a URI, if +** present, is ignored. +** +** ^SQLite uses the path component of the URI as the name of the disk file +** which contains the database. ^If the path begins with a '/' character, +** then it is interpreted as an absolute path. ^If the path does not begin +** with a '/' (meaning that the authority section is omitted from the URI) +** then the path is interpreted as a relative path. +** ^(On windows, the first component of an absolute path +** is a drive specification (e.g. "C:").)^ +** +** [[core URI query parameters]] +** The query component of a URI may contain parameters that are interpreted +** either by SQLite itself, or by a [VFS | custom VFS implementation]. +** SQLite and its built-in [VFSes] interpret the +** following query parameters: +** +** <ul> +** <li> <b>vfs</b>: ^The "vfs" parameter may be used to specify the name of +** a VFS object that provides the operating system interface that should +** be used to access the database file on disk. ^If this option is set to +** an empty string the default VFS object is used. ^Specifying an unknown +** VFS is an error. ^If sqlite3_open_v2() is used and the vfs option is +** present, then the VFS specified by the option takes precedence over +** the value passed as the fourth parameter to sqlite3_open_v2(). +** +** <li> <b>mode</b>: ^(The mode parameter may be set to either "ro", "rw", +** "rwc", or "memory". Attempting to set it to any other value is +** an error)^. +** ^If "ro" is specified, then the database is opened for read-only +** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the +** third argument to sqlite3_open_v2(). ^If the mode option is set to +** "rw", then the database is opened for read-write (but not create) +** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had +** been set. ^Value "rwc" is equivalent to setting both +** SQLITE_OPEN_READWRITE and SQLITE_OPEN_CREATE. ^If the mode option is +** set to "memory" then a pure [in-memory database] that never reads +** or writes from disk is used. ^It is an error to specify a value for +** the mode parameter that is less restrictive than that specified by +** the flags passed in the third parameter to sqlite3_open_v2(). +** +** <li> <b>cache</b>: ^The cache parameter may be set to either "shared" or +** "private". ^Setting it to "shared" is equivalent to setting the +** SQLITE_OPEN_SHAREDCACHE bit in the flags argument passed to +** sqlite3_open_v2(). ^Setting the cache parameter to "private" is +** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit. +** ^If sqlite3_open_v2() is used and the "cache" parameter is present in +** a URI filename, its value overrides any behavior requested by setting +** SQLITE_OPEN_PRIVATECACHE or SQLITE_OPEN_SHAREDCACHE flag. +** +** <li> <b>psow</b>: ^The psow parameter indicates whether or not the +** [powersafe overwrite] property does or does not apply to the +** storage media on which the database file resides. +** +** <li> <b>nolock</b>: ^The nolock parameter is a boolean query parameter +** which if set disables file locking in rollback journal modes. This +** is useful for accessing a database on a filesystem that does not +** support locking. Caution: Database corruption might result if two +** or more processes write to the same database and any one of those +** processes uses nolock=1. +** +** <li> <b>immutable</b>: ^The immutable parameter is a boolean query +** parameter that indicates that the database file is stored on +** read-only media. ^When immutable is set, SQLite assumes that the +** database file cannot be changed, even by a process with higher +** privilege, and so the database is opened read-only and all locking +** and change detection is disabled. Caution: Setting the immutable +** property on a database file that does in fact change can result +** in incorrect query results and/or [SQLITE_CORRUPT] errors. +** See also: [SQLITE_IOCAP_IMMUTABLE]. +** +** </ul> +** +** ^Specifying an unknown parameter in the query component of a URI is not an +** error. Future versions of SQLite might understand additional query +** parameters. See "[query parameters with special meaning to SQLite]" for +** additional information. +** +** [[URI filename examples]] <h3>URI filename examples</h3> +** +** <table border="1" align=center cellpadding=5> +** <tr><th> URI filenames <th> Results +** <tr><td> file:data.db <td> +** Open the file "data.db" in the current directory. +** <tr><td> file:/home/fred/data.db<br> +** file:///home/fred/data.db <br> +** file://localhost/home/fred/data.db <br> <td> +** Open the database file "/home/fred/data.db". +** <tr><td> file://darkstar/home/fred/data.db <td> +** An error. "darkstar" is not a recognized authority. +** <tr><td style="white-space:nowrap"> +** file:///C:/Documents%20and%20Settings/fred/Desktop/data.db +** <td> Windows only: Open the file "data.db" on fred's desktop on drive +** C:. Note that the %20 escaping in this example is not strictly +** necessary - space characters can be used literally +** in URI filenames. +** <tr><td> file:data.db?mode=ro&cache=private <td> +** Open file "data.db" in the current directory for read-only access. +** Regardless of whether or not shared-cache mode is enabled by +** default, use a private cache. +** <tr><td> file:/home/fred/data.db?vfs=unix-dotfile <td> +** Open file "/home/fred/data.db". Use the special VFS "unix-dotfile" +** that uses dot-files in place of posix advisory locking. +** <tr><td> file:data.db?mode=readonly <td> +** An error. "readonly" is not a valid option for the "mode" parameter. +** Use "ro" instead: "file:data.db?mode=ro". +** </table> +** +** ^URI hexadecimal escape sequences (%HH) are supported within the path and +** query components of a URI. A hexadecimal escape sequence consists of a +** percent sign - "%" - followed by exactly two hexadecimal digits +** specifying an octet value. ^Before the path or query components of a +** URI filename are interpreted, they are encoded using UTF-8 and all +** hexadecimal escape sequences replaced by a single byte containing the +** corresponding octet. If this process generates an invalid UTF-8 encoding, +** the results are undefined. +** +** <b>Note to Windows users:</b> The encoding used for the filename argument +** of sqlite3_open() and sqlite3_open_v2() must be UTF-8, not whatever +** codepage is currently defined. Filenames containing international +** characters must be converted to UTF-8 prior to passing them into +** sqlite3_open() or sqlite3_open_v2(). +** +** <b>Note to Windows Runtime users:</b> The temporary directory must be set +** prior to calling sqlite3_open() or sqlite3_open_v2(). Otherwise, various +** features that require the use of temporary files may fail. +** +** See also: [sqlite3_temp_directory] +*/ +SQLITE_API int sqlite3_open( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +SQLITE_API int sqlite3_open16( + const void *filename, /* Database filename (UTF-16) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +SQLITE_API int sqlite3_open_v2( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb, /* OUT: SQLite db handle */ + int flags, /* Flags */ + const char *zVfs /* Name of VFS module to use */ +); + +/* +** CAPI3REF: Obtain Values For URI Parameters +** +** These are utility routines, useful to [VFS|custom VFS implementations], +** that check if a database file was a URI that contained a specific query +** parameter, and if so obtains the value of that query parameter. +** +** The first parameter to these interfaces (hereafter referred to +** as F) must be one of: +** <ul> +** <li> A database filename pointer created by the SQLite core and +** passed into the xOpen() method of a VFS implementation, or +** <li> A filename obtained from [sqlite3_db_filename()], or +** <li> A new filename constructed using [sqlite3_create_filename()]. +** </ul> +** If the F parameter is not one of the above, then the behavior is +** undefined and probably undesirable. Older versions of SQLite were +** more tolerant of invalid F parameters than newer versions. +** +** If F is a suitable filename (as described in the previous paragraph) +** and if P is the name of the query parameter, then +** sqlite3_uri_parameter(F,P) returns the value of the P +** parameter if it exists or a NULL pointer if P does not appear as a +** query parameter on F. If P is a query parameter of F and it +** has no explicit value, then sqlite3_uri_parameter(F,P) returns +** a pointer to an empty string. +** +** The sqlite3_uri_boolean(F,P,B) routine assumes that P is a boolean +** parameter and returns true (1) or false (0) according to the value +** of P. The sqlite3_uri_boolean(F,P,B) routine returns true (1) if the +** value of query parameter P is one of "yes", "true", or "on" in any +** case or if the value begins with a non-zero number. The +** sqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of +** query parameter P is one of "no", "false", or "off" in any case or +** if the value begins with a numeric zero. If P is not a query +** parameter on F or if the value of P does not match any of the +** above, then sqlite3_uri_boolean(F,P,B) returns (B!=0). +** +** The sqlite3_uri_int64(F,P,D) routine converts the value of P into a +** 64-bit signed integer and returns that integer, or D if P does not +** exist. If the value of P is something other than an integer, then +** zero is returned. +** +** The sqlite3_uri_key(F,N) returns a pointer to the name (not +** the value) of the N-th query parameter for filename F, or a NULL +** pointer if N is less than zero or greater than the number of query +** parameters minus 1. The N value is zero-based so N should be 0 to obtain +** the name of the first query parameter, 1 for the second parameter, and +** so forth. +** +** If F is a NULL pointer, then sqlite3_uri_parameter(F,P) returns NULL and +** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and +** is not a database file pathname pointer that the SQLite core passed +** into the xOpen VFS method, then the behavior of this routine is undefined +** and probably undesirable. +** +** Beginning with SQLite [version 3.31.0] ([dateof:3.31.0]) the input F +** parameter can also be the name of a rollback journal file or WAL file +** in addition to the main database file. Prior to version 3.31.0, these +** routines would only work if F was the name of the main database file. +** When the F parameter is the name of the rollback journal or WAL file, +** it has access to all the same query parameters as were found on the +** main database file. +** +** See the [URI filename] documentation for additional information. +*/ +SQLITE_API const char *sqlite3_uri_parameter(sqlite3_filename z, const char *zParam); +SQLITE_API int sqlite3_uri_boolean(sqlite3_filename z, const char *zParam, int bDefault); +SQLITE_API sqlite3_int64 sqlite3_uri_int64(sqlite3_filename, const char*, sqlite3_int64); +SQLITE_API const char *sqlite3_uri_key(sqlite3_filename z, int N); + +/* +** CAPI3REF: Translate filenames +** +** These routines are available to [VFS|custom VFS implementations] for +** translating filenames between the main database file, the journal file, +** and the WAL file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** passed by the SQLite core into the VFS, then sqlite3_filename_database(F) +** returns the name of the corresponding database file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** passed by the SQLite core into the VFS, or if F is a database filename +** obtained from [sqlite3_db_filename()], then sqlite3_filename_journal(F) +** returns the name of the corresponding rollback journal file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** that was passed by the SQLite core into the VFS, or if F is a database +** filename obtained from [sqlite3_db_filename()], then +** sqlite3_filename_wal(F) returns the name of the corresponding +** WAL file. +** +** In all of the above, if F is not the name of a database, journal or WAL +** filename passed into the VFS from the SQLite core and F is not the +** return value from [sqlite3_db_filename()], then the result is +** undefined and is likely a memory access violation. +*/ +SQLITE_API const char *sqlite3_filename_database(sqlite3_filename); +SQLITE_API const char *sqlite3_filename_journal(sqlite3_filename); +SQLITE_API const char *sqlite3_filename_wal(sqlite3_filename); + +/* +** CAPI3REF: Database File Corresponding To A Journal +** +** ^If X is the name of a rollback or WAL-mode journal file that is +** passed into the xOpen method of [sqlite3_vfs], then +** sqlite3_database_file_object(X) returns a pointer to the [sqlite3_file] +** object that represents the main database file. +** +** This routine is intended for use in custom [VFS] implementations +** only. It is not a general-purpose interface. +** The argument sqlite3_file_object(X) must be a filename pointer that +** has been passed into [sqlite3_vfs].xOpen method where the +** flags parameter to xOpen contains one of the bits +** [SQLITE_OPEN_MAIN_JOURNAL] or [SQLITE_OPEN_WAL]. Any other use +** of this routine results in undefined and probably undesirable +** behavior. +*/ +SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*); + +/* +** CAPI3REF: Create and Destroy VFS Filenames +** +** These interfaces are provided for use by [VFS shim] implementations and +** are not useful outside of that context. +** +** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of +** database filename D with corresponding journal file J and WAL file W and +** with N URI parameters key/values pairs in the array P. The result from +** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that +** is safe to pass to routines like: +** <ul> +** <li> [sqlite3_uri_parameter()], +** <li> [sqlite3_uri_boolean()], +** <li> [sqlite3_uri_int64()], +** <li> [sqlite3_uri_key()], +** <li> [sqlite3_filename_database()], +** <li> [sqlite3_filename_journal()], or +** <li> [sqlite3_filename_wal()]. +** </ul> +** If a memory allocation error occurs, sqlite3_create_filename() might +** return a NULL pointer. The memory obtained from sqlite3_create_filename(X) +** must be released by a corresponding call to sqlite3_free_filename(Y). +** +** The P parameter in sqlite3_create_filename(D,J,W,N,P) should be an array +** of 2*N pointers to strings. Each pair of pointers in this array corresponds +** to a key and value for a query parameter. The P parameter may be a NULL +** pointer if N is zero. None of the 2*N pointers in the P array may be +** NULL pointers and key pointers should not be empty strings. +** None of the D, J, or W parameters to sqlite3_create_filename(D,J,W,N,P) may +** be NULL pointers, though they can be empty strings. +** +** The sqlite3_free_filename(Y) routine releases a memory allocation +** previously obtained from sqlite3_create_filename(). Invoking +** sqlite3_free_filename(Y) where Y is a NULL pointer is a harmless no-op. +** +** If the Y parameter to sqlite3_free_filename(Y) is anything other +** than a NULL pointer or a pointer previously acquired from +** sqlite3_create_filename(), then bad things such as heap +** corruption or segfaults may occur. The value Y should not be +** used again after sqlite3_free_filename(Y) has been called. This means +** that if the [sqlite3_vfs.xOpen()] method of a VFS has been called using Y, +** then the corresponding [sqlite3_module.xClose() method should also be +** invoked prior to calling sqlite3_free_filename(Y). +*/ +SQLITE_API sqlite3_filename sqlite3_create_filename( + const char *zDatabase, + const char *zJournal, + const char *zWal, + int nParam, + const char **azParam +); +SQLITE_API void sqlite3_free_filename(sqlite3_filename); + +/* +** CAPI3REF: Error Codes And Messages +** METHOD: sqlite3 +** +** ^If the most recent sqlite3_* API call associated with +** [database connection] D failed, then the sqlite3_errcode(D) interface +** returns the numeric [result code] or [extended result code] for that +** API call. +** ^The sqlite3_extended_errcode() +** interface is the same except that it always returns the +** [extended result code] even when extended result codes are +** disabled. +** +** The values returned by sqlite3_errcode() and/or +** sqlite3_extended_errcode() might change with each API call. +** Except, there are some interfaces that are guaranteed to never +** change the value of the error code. The error-code preserving +** interfaces include the following: +** +** <ul> +** <li> sqlite3_errcode() +** <li> sqlite3_extended_errcode() +** <li> sqlite3_errmsg() +** <li> sqlite3_errmsg16() +** <li> sqlite3_error_offset() +** </ul> +** +** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language +** text that describes the error, as either UTF-8 or UTF-16 respectively. +** ^(Memory to hold the error message string is managed internally. +** The application does not need to worry about freeing the result. +** However, the error string might be overwritten or deallocated by +** subsequent calls to other SQLite interface functions.)^ +** +** ^The sqlite3_errstr() interface returns the English-language text +** that describes the [result code], as UTF-8. +** ^(Memory to hold the error message string is managed internally +** and must not be freed by the application)^. +** +** ^If the most recent error references a specific token in the input +** SQL, the sqlite3_error_offset() interface returns the byte offset +** of the start of that token. ^The byte offset returned by +** sqlite3_error_offset() assumes that the input SQL is UTF8. +** ^If the most recent error does not reference a specific token in the input +** SQL, then the sqlite3_error_offset() function returns -1. +** +** When the serialized [threading mode] is in use, it might be the +** case that a second error occurs on a separate thread in between +** the time of the first error and the call to these interfaces. +** When that happens, the second error will be reported since these +** interfaces always report the most recent result. To avoid +** this, each thread can obtain exclusive use of the [database connection] D +** by invoking [sqlite3_mutex_enter]([sqlite3_db_mutex](D)) before beginning +** to use D and invoking [sqlite3_mutex_leave]([sqlite3_db_mutex](D)) after +** all calls to the interfaces listed here are completed. +** +** If an interface fails with SQLITE_MISUSE, that means the interface +** was invoked incorrectly by the application. In that case, the +** error code and message may or may not be set. +*/ +SQLITE_API int sqlite3_errcode(sqlite3 *db); +SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); +SQLITE_API const char *sqlite3_errmsg(sqlite3*); +SQLITE_API const void *sqlite3_errmsg16(sqlite3*); +SQLITE_API const char *sqlite3_errstr(int); +SQLITE_API int sqlite3_error_offset(sqlite3 *db); + +/* +** CAPI3REF: Prepared Statement Object +** KEYWORDS: {prepared statement} {prepared statements} +** +** An instance of this object represents a single SQL statement that +** has been compiled into binary form and is ready to be evaluated. +** +** Think of each SQL statement as a separate computer program. The +** original SQL text is source code. A prepared statement object +** is the compiled object code. All SQL must be converted into a +** prepared statement before it can be run. +** +** The life-cycle of a prepared statement object usually goes like this: +** +** <ol> +** <li> Create the prepared statement object using [sqlite3_prepare_v2()]. +** <li> Bind values to [parameters] using the sqlite3_bind_*() +** interfaces. +** <li> Run the SQL by calling [sqlite3_step()] one or more times. +** <li> Reset the prepared statement using [sqlite3_reset()] then go back +** to step 2. Do this zero or more times. +** <li> Destroy the object using [sqlite3_finalize()]. +** </ol> +*/ +typedef struct sqlite3_stmt sqlite3_stmt; + +/* +** CAPI3REF: Run-time Limits +** METHOD: sqlite3 +** +** ^(This interface allows the size of various constructs to be limited +** on a connection by connection basis. The first parameter is the +** [database connection] whose limit is to be set or queried. The +** second parameter is one of the [limit categories] that define a +** class of constructs to be size limited. The third parameter is the +** new limit for that construct.)^ +** +** ^If the new limit is a negative number, the limit is unchanged. +** ^(For each limit category SQLITE_LIMIT_<i>NAME</i> there is a +** [limits | hard upper bound] +** set at compile-time by a C preprocessor macro called +** [limits | SQLITE_MAX_<i>NAME</i>]. +** (The "_LIMIT_" in the name is changed to "_MAX_".))^ +** ^Attempts to increase a limit above its hard upper bound are +** silently truncated to the hard upper bound. +** +** ^Regardless of whether or not the limit was changed, the +** [sqlite3_limit()] interface returns the prior value of the limit. +** ^Hence, to find the current value of a limit without changing it, +** simply invoke this interface with the third parameter set to -1. +** +** Run-time limits are intended for use in applications that manage +** both their own internal database and also databases that are controlled +** by untrusted external sources. An example application might be a +** web browser that has its own databases for storing history and +** separate databases controlled by JavaScript applications downloaded +** off the Internet. The internal databases can be given the +** large, default limits. Databases managed by external sources can +** be given much smaller limits designed to prevent a denial of service +** attack. Developers might also want to use the [sqlite3_set_authorizer()] +** interface to further control untrusted SQL. The size of the database +** created by an untrusted script can be contained using the +** [max_page_count] [PRAGMA]. +** +** New run-time limit categories may be added in future releases. +*/ +SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); + +/* +** CAPI3REF: Run-Time Limit Categories +** KEYWORDS: {limit category} {*limit categories} +** +** These constants define various performance limits +** that can be lowered at run-time using [sqlite3_limit()]. +** The synopsis of the meanings of the various limits is shown below. +** Additional information is available at [limits | Limits in SQLite]. +** +** <dl> +** [[SQLITE_LIMIT_LENGTH]] ^(<dt>SQLITE_LIMIT_LENGTH</dt> +** <dd>The maximum size of any string or BLOB or table row, in bytes.<dd>)^ +** +** [[SQLITE_LIMIT_SQL_LENGTH]] ^(<dt>SQLITE_LIMIT_SQL_LENGTH</dt> +** <dd>The maximum length of an SQL statement, in bytes.</dd>)^ +** +** [[SQLITE_LIMIT_COLUMN]] ^(<dt>SQLITE_LIMIT_COLUMN</dt> +** <dd>The maximum number of columns in a table definition or in the +** result set of a [SELECT] or the maximum number of columns in an index +** or in an ORDER BY or GROUP BY clause.</dd>)^ +** +** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(<dt>SQLITE_LIMIT_EXPR_DEPTH</dt> +** <dd>The maximum depth of the parse tree on any expression.</dd>)^ +** +** [[SQLITE_LIMIT_COMPOUND_SELECT]] ^(<dt>SQLITE_LIMIT_COMPOUND_SELECT</dt> +** <dd>The maximum number of terms in a compound SELECT statement.</dd>)^ +** +** [[SQLITE_LIMIT_VDBE_OP]] ^(<dt>SQLITE_LIMIT_VDBE_OP</dt> +** <dd>The maximum number of instructions in a virtual machine program +** used to implement an SQL statement. If [sqlite3_prepare_v2()] or +** the equivalent tries to allocate space for more than this many opcodes +** in a single prepared statement, an SQLITE_NOMEM error is returned.</dd>)^ +** +** [[SQLITE_LIMIT_FUNCTION_ARG]] ^(<dt>SQLITE_LIMIT_FUNCTION_ARG</dt> +** <dd>The maximum number of arguments on a function.</dd>)^ +** +** [[SQLITE_LIMIT_ATTACHED]] ^(<dt>SQLITE_LIMIT_ATTACHED</dt> +** <dd>The maximum number of [ATTACH | attached databases].)^</dd> +** +** [[SQLITE_LIMIT_LIKE_PATTERN_LENGTH]] +** ^(<dt>SQLITE_LIMIT_LIKE_PATTERN_LENGTH</dt> +** <dd>The maximum length of the pattern argument to the [LIKE] or +** [GLOB] operators.</dd>)^ +** +** [[SQLITE_LIMIT_VARIABLE_NUMBER]] +** ^(<dt>SQLITE_LIMIT_VARIABLE_NUMBER</dt> +** <dd>The maximum index number of any [parameter] in an SQL statement.)^ +** +** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(<dt>SQLITE_LIMIT_TRIGGER_DEPTH</dt> +** <dd>The maximum depth of recursion for triggers.</dd>)^ +** +** [[SQLITE_LIMIT_WORKER_THREADS]] ^(<dt>SQLITE_LIMIT_WORKER_THREADS</dt> +** <dd>The maximum number of auxiliary worker threads that a single +** [prepared statement] may start.</dd>)^ +** </dl> +*/ +#define SQLITE_LIMIT_LENGTH 0 +#define SQLITE_LIMIT_SQL_LENGTH 1 +#define SQLITE_LIMIT_COLUMN 2 +#define SQLITE_LIMIT_EXPR_DEPTH 3 +#define SQLITE_LIMIT_COMPOUND_SELECT 4 +#define SQLITE_LIMIT_VDBE_OP 5 +#define SQLITE_LIMIT_FUNCTION_ARG 6 +#define SQLITE_LIMIT_ATTACHED 7 +#define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 +#define SQLITE_LIMIT_VARIABLE_NUMBER 9 +#define SQLITE_LIMIT_TRIGGER_DEPTH 10 +#define SQLITE_LIMIT_WORKER_THREADS 11 + +/* +** CAPI3REF: Prepare Flags +** +** These constants define various flags that can be passed into +** "prepFlags" parameter of the [sqlite3_prepare_v3()] and +** [sqlite3_prepare16_v3()] interfaces. +** +** New flags may be added in future releases of SQLite. +** +** <dl> +** [[SQLITE_PREPARE_PERSISTENT]] ^(<dt>SQLITE_PREPARE_PERSISTENT</dt> +** <dd>The SQLITE_PREPARE_PERSISTENT flag is a hint to the query planner +** that the prepared statement will be retained for a long time and +** probably reused many times.)^ ^Without this flag, [sqlite3_prepare_v3()] +** and [sqlite3_prepare16_v3()] assume that the prepared statement will +** be used just once or at most a few times and then destroyed using +** [sqlite3_finalize()] relatively soon. The current implementation acts +** on this hint by avoiding the use of [lookaside memory] so as not to +** deplete the limited store of lookaside memory. Future versions of +** SQLite may act on this hint differently. +** +** [[SQLITE_PREPARE_NORMALIZE]] <dt>SQLITE_PREPARE_NORMALIZE</dt> +** <dd>The SQLITE_PREPARE_NORMALIZE flag is a no-op. This flag used +** to be required for any prepared statement that wanted to use the +** [sqlite3_normalized_sql()] interface. However, the +** [sqlite3_normalized_sql()] interface is now available to all +** prepared statements, regardless of whether or not they use this +** flag. +** +** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt> +** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler +** to return an error (error code SQLITE_ERROR) if the statement uses +** any virtual tables. +** </dl> +*/ +#define SQLITE_PREPARE_PERSISTENT 0x01 +#define SQLITE_PREPARE_NORMALIZE 0x02 +#define SQLITE_PREPARE_NO_VTAB 0x04 + +/* +** CAPI3REF: Compiling An SQL Statement +** KEYWORDS: {SQL statement compiler} +** METHOD: sqlite3 +** CONSTRUCTOR: sqlite3_stmt +** +** To execute an SQL statement, it must first be compiled into a byte-code +** program using one of these routines. Or, in other words, these routines +** are constructors for the [prepared statement] object. +** +** The preferred routine to use is [sqlite3_prepare_v2()]. The +** [sqlite3_prepare()] interface is legacy and should be avoided. +** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used +** for special purposes. +** +** The use of the UTF-8 interfaces is preferred, as SQLite currently +** does all parsing using UTF-8. The UTF-16 interfaces are provided +** as a convenience. The UTF-16 interfaces work by converting the +** input text into UTF-8, then invoking the corresponding UTF-8 interface. +** +** The first argument, "db", is a [database connection] obtained from a +** prior successful call to [sqlite3_open()], [sqlite3_open_v2()] or +** [sqlite3_open16()]. The database connection must not have been closed. +** +** The second argument, "zSql", is the statement to be compiled, encoded +** as either UTF-8 or UTF-16. The sqlite3_prepare(), sqlite3_prepare_v2(), +** and sqlite3_prepare_v3() +** interfaces use UTF-8, and sqlite3_prepare16(), sqlite3_prepare16_v2(), +** and sqlite3_prepare16_v3() use UTF-16. +** +** ^If the nByte argument is negative, then zSql is read up to the +** first zero terminator. ^If nByte is positive, then it is the +** number of bytes read from zSql. ^If nByte is zero, then no prepared +** statement is generated. +** If the caller knows that the supplied string is nul-terminated, then +** there is a small performance advantage to passing an nByte parameter that +** is the number of bytes in the input string <i>including</i> +** the nul-terminator. +** +** ^If pzTail is not NULL then *pzTail is made to point to the first byte +** past the end of the first SQL statement in zSql. These routines only +** compile the first statement in zSql, so *pzTail is left pointing to +** what remains uncompiled. +** +** ^*ppStmt is left pointing to a compiled [prepared statement] that can be +** executed using [sqlite3_step()]. ^If there is an error, *ppStmt is set +** to NULL. ^If the input text contains no SQL (if the input is an empty +** string or a comment) then *ppStmt is set to NULL. +** The calling procedure is responsible for deleting the compiled +** SQL statement using [sqlite3_finalize()] after it has finished with it. +** ppStmt may not be NULL. +** +** ^On success, the sqlite3_prepare() family of routines return [SQLITE_OK]; +** otherwise an [error code] is returned. +** +** The sqlite3_prepare_v2(), sqlite3_prepare_v3(), sqlite3_prepare16_v2(), +** and sqlite3_prepare16_v3() interfaces are recommended for all new programs. +** The older interfaces (sqlite3_prepare() and sqlite3_prepare16()) +** are retained for backwards compatibility, but their use is discouraged. +** ^In the "vX" interfaces, the prepared statement +** that is returned (the [sqlite3_stmt] object) contains a copy of the +** original SQL text. This causes the [sqlite3_step()] interface to +** behave differently in three ways: +** +** <ol> +** <li> +** ^If the database schema changes, instead of returning [SQLITE_SCHEMA] as it +** always used to do, [sqlite3_step()] will automatically recompile the SQL +** statement and try to run it again. As many as [SQLITE_MAX_SCHEMA_RETRY] +** retries will occur before sqlite3_step() gives up and returns an error. +** </li> +** +** <li> +** ^When an error occurs, [sqlite3_step()] will return one of the detailed +** [error codes] or [extended error codes]. ^The legacy behavior was that +** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code +** and the application would have to make a second call to [sqlite3_reset()] +** in order to find the underlying cause of the problem. With the "v2" prepare +** interfaces, the underlying reason for the error is returned immediately. +** </li> +** +** <li> +** ^If the specific value bound to a [parameter | host parameter] in the +** WHERE clause might influence the choice of query plan for a statement, +** then the statement will be automatically recompiled, as if there had been +** a schema change, on the first [sqlite3_step()] call following any change +** to the [sqlite3_bind_text | bindings] of that [parameter]. +** ^The specific value of a WHERE-clause [parameter] might influence the +** choice of query plan if the parameter is the left-hand side of a [LIKE] +** or [GLOB] operator or if the parameter is compared to an indexed column +** and the [SQLITE_ENABLE_STAT4] compile-time option is enabled. +** </li> +** </ol> +** +** <p>^sqlite3_prepare_v3() differs from sqlite3_prepare_v2() only in having +** the extra prepFlags parameter, which is a bit array consisting of zero or +** more of the [SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_*] flags. ^The +** sqlite3_prepare_v2() interface works exactly the same as +** sqlite3_prepare_v3() with a zero prepFlags parameter. +*/ +SQLITE_API int sqlite3_prepare( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare_v2( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare_v3( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16_v2( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16_v3( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); + +/* +** CAPI3REF: Retrieving Statement SQL +** METHOD: sqlite3_stmt +** +** ^The sqlite3_sql(P) interface returns a pointer to a copy of the UTF-8 +** SQL text used to create [prepared statement] P if P was +** created by [sqlite3_prepare_v2()], [sqlite3_prepare_v3()], +** [sqlite3_prepare16_v2()], or [sqlite3_prepare16_v3()]. +** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8 +** string containing the SQL text of prepared statement P with +** [bound parameters] expanded. +** ^The sqlite3_normalized_sql(P) interface returns a pointer to a UTF-8 +** string containing the normalized SQL text of prepared statement P. The +** semantics used to normalize a SQL statement are unspecified and subject +** to change. At a minimum, literal values will be replaced with suitable +** placeholders. +** +** ^(For example, if a prepared statement is created using the SQL +** text "SELECT $abc,:xyz" and if parameter $abc is bound to integer 2345 +** and parameter :xyz is unbound, then sqlite3_sql() will return +** the original string, "SELECT $abc,:xyz" but sqlite3_expanded_sql() +** will return "SELECT 2345,NULL".)^ +** +** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory +** is available to hold the result, or if the result would exceed the +** the maximum string length determined by the [SQLITE_LIMIT_LENGTH]. +** +** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of +** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time +** option causes sqlite3_expanded_sql() to always return NULL. +** +** ^The strings returned by sqlite3_sql(P) and sqlite3_normalized_sql(P) +** are managed by SQLite and are automatically freed when the prepared +** statement is finalized. +** ^The string returned by sqlite3_expanded_sql(P), on the other hand, +** is obtained from [sqlite3_malloc()] and must be freed by the application +** by passing it to [sqlite3_free()]. +** +** ^The sqlite3_normalized_sql() interface is only available if +** the [SQLITE_ENABLE_NORMALIZE] compile-time option is defined. +*/ +SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); +SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); +#ifdef SQLITE_ENABLE_NORMALIZE +SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); +#endif + +/* +** CAPI3REF: Determine If An SQL Statement Writes The Database +** METHOD: sqlite3_stmt +** +** ^The sqlite3_stmt_readonly(X) interface returns true (non-zero) if +** and only if the [prepared statement] X makes no direct changes to +** the content of the database file. +** +** Note that [application-defined SQL functions] or +** [virtual tables] might change the database indirectly as a side effect. +** ^(For example, if an application defines a function "eval()" that +** calls [sqlite3_exec()], then the following SQL statement would +** change the database file through side-effects: +** +** <blockquote><pre> +** SELECT eval('DELETE FROM t1') FROM t2; +** </pre></blockquote> +** +** But because the [SELECT] statement does not change the database file +** directly, sqlite3_stmt_readonly() would still return true.)^ +** +** ^Transaction control statements such as [BEGIN], [COMMIT], [ROLLBACK], +** [SAVEPOINT], and [RELEASE] cause sqlite3_stmt_readonly() to return true, +** since the statements themselves do not actually modify the database but +** rather they control the timing of when other statements modify the +** database. ^The [ATTACH] and [DETACH] statements also cause +** sqlite3_stmt_readonly() to return true since, while those statements +** change the configuration of a database connection, they do not make +** changes to the content of the database files on disk. +** ^The sqlite3_stmt_readonly() interface returns true for [BEGIN] since +** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and +** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so +** sqlite3_stmt_readonly() returns false for those commands. +** +** ^This routine returns false if there is any possibility that the +** statement might change the database file. ^A false return does +** not guarantee that the statement will change the database file. +** ^For example, an UPDATE statement might have a WHERE clause that +** makes it a no-op, but the sqlite3_stmt_readonly() result would still +** be false. ^Similarly, a CREATE TABLE IF NOT EXISTS statement is a +** read-only no-op if the table already exists, but +** sqlite3_stmt_readonly() still returns false for such a statement. +** +** ^If prepared statement X is an [EXPLAIN] or [EXPLAIN QUERY PLAN] +** statement, then sqlite3_stmt_readonly(X) returns the same value as +** if the EXPLAIN or EXPLAIN QUERY PLAN prefix were omitted. +*/ +SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Query The EXPLAIN Setting For A Prepared Statement +** METHOD: sqlite3_stmt +** +** ^The sqlite3_stmt_isexplain(S) interface returns 1 if the +** prepared statement S is an EXPLAIN statement, or 2 if the +** statement S is an EXPLAIN QUERY PLAN. +** ^The sqlite3_stmt_isexplain(S) interface returns 0 if S is +** an ordinary statement or a NULL pointer. +*/ +SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement +** METHOD: sqlite3_stmt +** +** The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN +** setting for [prepared statement] S. If E is zero, then S becomes +** a normal prepared statement. If E is 1, then S behaves as if +** its SQL text began with "[EXPLAIN]". If E is 2, then S behaves as if +** its SQL text began with "[EXPLAIN QUERY PLAN]". +** +** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared. +** SQLite tries to avoid a reprepare, but a reprepare might be necessary +** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode. +** +** Because of the potential need to reprepare, a call to +** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be +** reprepared because it was created using [sqlite3_prepare()] instead of +** the newer [sqlite3_prepare_v2()] or [sqlite3_prepare_v3()] interfaces and +** hence has no saved SQL text with which to reprepare. +** +** Changing the explain setting for a prepared statement does not change +** the original SQL text for the statement. Hence, if the SQL text originally +** began with EXPLAIN or EXPLAIN QUERY PLAN, but sqlite3_stmt_explain(S,0) +** is called to convert the statement into an ordinary statement, the EXPLAIN +** or EXPLAIN QUERY PLAN keywords will still appear in the sqlite3_sql(S) +** output, even though the statement now acts like a normal SQL statement. +** +** This routine returns SQLITE_OK if the explain mode is successfully +** changed, or an error code if the explain mode could not be changed. +** The explain mode cannot be changed while a statement is active. +** Hence, it is good practice to call [sqlite3_reset(S)] +** immediately prior to calling sqlite3_stmt_explain(S,E). +*/ +SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode); + +/* +** CAPI3REF: Determine If A Prepared Statement Has Been Reset +** METHOD: sqlite3_stmt +** +** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the +** [prepared statement] S has been stepped at least once using +** [sqlite3_step(S)] but has neither run to completion (returned +** [SQLITE_DONE] from [sqlite3_step(S)]) nor +** been reset using [sqlite3_reset(S)]. ^The sqlite3_stmt_busy(S) +** interface returns false if S is a NULL pointer. If S is not a +** NULL pointer and is not a pointer to a valid [prepared statement] +** object, then the behavior is undefined and probably undesirable. +** +** This interface can be used in combination [sqlite3_next_stmt()] +** to locate all prepared statements associated with a database +** connection that are in need of being reset. This can be used, +** for example, in diagnostic routines to search for prepared +** statements that are holding a transaction open. +*/ +SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); + +/* +** CAPI3REF: Dynamically Typed Value Object +** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value} +** +** SQLite uses the sqlite3_value object to represent all values +** that can be stored in a database table. SQLite uses dynamic typing +** for the values it stores. ^Values stored in sqlite3_value objects +** can be integers, floating point values, strings, BLOBs, or NULL. +** +** An sqlite3_value object may be either "protected" or "unprotected". +** Some interfaces require a protected sqlite3_value. Other interfaces +** will accept either a protected or an unprotected sqlite3_value. +** Every interface that accepts sqlite3_value arguments specifies +** whether or not it requires a protected sqlite3_value. The +** [sqlite3_value_dup()] interface can be used to construct a new +** protected sqlite3_value from an unprotected sqlite3_value. +** +** The terms "protected" and "unprotected" refer to whether or not +** a mutex is held. An internal mutex is held for a protected +** sqlite3_value object but no mutex is held for an unprotected +** sqlite3_value object. If SQLite is compiled to be single-threaded +** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0) +** or if SQLite is run in one of reduced mutex modes +** [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD] +** then there is no distinction between protected and unprotected +** sqlite3_value objects and they can be used interchangeably. However, +** for maximum code portability it is recommended that applications +** still make the distinction between protected and unprotected +** sqlite3_value objects even when not strictly required. +** +** ^The sqlite3_value objects that are passed as parameters into the +** implementation of [application-defined SQL functions] are protected. +** ^The sqlite3_value objects returned by [sqlite3_vtab_rhs_value()] +** are protected. +** ^The sqlite3_value object returned by +** [sqlite3_column_value()] is unprotected. +** Unprotected sqlite3_value objects may only be used as arguments +** to [sqlite3_result_value()], [sqlite3_bind_value()], and +** [sqlite3_value_dup()]. +** The [sqlite3_value_blob | sqlite3_value_type()] family of +** interfaces require protected sqlite3_value objects. +*/ +typedef struct sqlite3_value sqlite3_value; + +/* +** CAPI3REF: SQL Function Context Object +** +** The context in which an SQL function executes is stored in an +** sqlite3_context object. ^A pointer to an sqlite3_context object +** is always first parameter to [application-defined SQL functions]. +** The application-defined SQL function implementation will pass this +** pointer through into calls to [sqlite3_result_int | sqlite3_result()], +** [sqlite3_aggregate_context()], [sqlite3_user_data()], +** [sqlite3_context_db_handle()], [sqlite3_get_auxdata()], +** and/or [sqlite3_set_auxdata()]. +*/ +typedef struct sqlite3_context sqlite3_context; + +/* +** CAPI3REF: Binding Values To Prepared Statements +** KEYWORDS: {host parameter} {host parameters} {host parameter name} +** KEYWORDS: {SQL parameter} {SQL parameters} {parameter binding} +** METHOD: sqlite3_stmt +** +** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants, +** literals may be replaced by a [parameter] that matches one of following +** templates: +** +** <ul> +** <li> ? +** <li> ?NNN +** <li> :VVV +** <li> @VVV +** <li> $VVV +** </ul> +** +** In the templates above, NNN represents an integer literal, +** and VVV represents an alphanumeric identifier.)^ ^The values of these +** parameters (also called "host parameter names" or "SQL parameters") +** can be set using the sqlite3_bind_*() routines defined here. +** +** ^The first argument to the sqlite3_bind_*() routines is always +** a pointer to the [sqlite3_stmt] object returned from +** [sqlite3_prepare_v2()] or its variants. +** +** ^The second argument is the index of the SQL parameter to be set. +** ^The leftmost SQL parameter has an index of 1. ^When the same named +** SQL parameter is used more than once, second and subsequent +** occurrences have the same index as the first occurrence. +** ^The index for named parameters can be looked up using the +** [sqlite3_bind_parameter_index()] API if desired. ^The index +** for "?NNN" parameters is the value of NNN. +** ^The NNN value must be between 1 and the [sqlite3_limit()] +** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 32766). +** +** ^The third argument is the value to bind to the parameter. +** ^If the third parameter to sqlite3_bind_text() or sqlite3_bind_text16() +** or sqlite3_bind_blob() is a NULL pointer then the fourth parameter +** is ignored and the end result is the same as sqlite3_bind_null(). +** ^If the third parameter to sqlite3_bind_text() is not NULL, then +** it should be a pointer to well-formed UTF8 text. +** ^If the third parameter to sqlite3_bind_text16() is not NULL, then +** it should be a pointer to well-formed UTF16 text. +** ^If the third parameter to sqlite3_bind_text64() is not NULL, then +** it should be a pointer to a well-formed unicode string that is +** either UTF8 if the sixth parameter is SQLITE_UTF8, or UTF16 +** otherwise. +** +** [[byte-order determination rules]] ^The byte-order of +** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) +** found in first character, which is removed, or in the absence of a BOM +** the byte order is the native byte order of the host +** machine for sqlite3_bind_text16() or the byte order specified in +** the 6th parameter for sqlite3_bind_text64().)^ +** ^If UTF16 input text contains invalid unicode +** characters, then SQLite might change those invalid characters +** into the unicode replacement character: U+FFFD. +** +** ^(In those routines that have a fourth argument, its value is the +** number of bytes in the parameter. To be clear: the value is the +** number of <u>bytes</u> in the value, not the number of characters.)^ +** ^If the fourth parameter to sqlite3_bind_text() or sqlite3_bind_text16() +** is negative, then the length of the string is +** the number of bytes up to the first zero terminator. +** If the fourth parameter to sqlite3_bind_blob() is negative, then +** the behavior is undefined. +** If a non-negative fourth parameter is provided to sqlite3_bind_text() +** or sqlite3_bind_text16() or sqlite3_bind_text64() then +** that parameter must be the byte offset +** where the NUL terminator would occur assuming the string were NUL +** terminated. If any NUL characters occurs at byte offsets less than +** the value of the fourth parameter then the resulting string value will +** contain embedded NULs. The result of expressions involving strings +** with embedded NULs is undefined. +** +** ^The fifth argument to the BLOB and string binding interfaces controls +** or indicates the lifetime of the object referenced by the third parameter. +** These three options exist: +** ^ (1) A destructor to dispose of the BLOB or string after SQLite has finished +** with it may be passed. ^It is called to dispose of the BLOB or string even +** if the call to the bind API fails, except the destructor is not called if +** the third parameter is a NULL pointer or the fourth parameter is negative. +** ^ (2) The special constant, [SQLITE_STATIC], may be passed to indicate that +** the application remains responsible for disposing of the object. ^In this +** case, the object and the provided pointer to it must remain valid until +** either the prepared statement is finalized or the same SQL parameter is +** bound to something else, whichever occurs sooner. +** ^ (3) The constant, [SQLITE_TRANSIENT], may be passed to indicate that the +** object is to be copied prior to the return from sqlite3_bind_*(). ^The +** object and pointer to it must remain valid until then. ^SQLite will then +** manage the lifetime of its private copy. +** +** ^The sixth argument to sqlite3_bind_text64() must be one of +** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] +** to specify the encoding of the text in the third parameter. If +** the sixth argument to sqlite3_bind_text64() is not one of the +** allowed values shown above, or if the text encoding is different +** from the encoding specified by the sixth parameter, then the behavior +** is undefined. +** +** ^The sqlite3_bind_zeroblob() routine binds a BLOB of length N that +** is filled with zeroes. ^A zeroblob uses a fixed amount of memory +** (just an integer to hold its size) while it is being processed. +** Zeroblobs are intended to serve as placeholders for BLOBs whose +** content is later written using +** [sqlite3_blob_open | incremental BLOB I/O] routines. +** ^A negative value for the zeroblob results in a zero-length BLOB. +** +** ^The sqlite3_bind_pointer(S,I,P,T,D) routine causes the I-th parameter in +** [prepared statement] S to have an SQL value of NULL, but to also be +** associated with the pointer P of type T. ^D is either a NULL pointer or +** a pointer to a destructor function for P. ^SQLite will invoke the +** destructor D with a single argument of P when it is finished using +** P. The T parameter should be a static string, preferably a string +** literal. The sqlite3_bind_pointer() routine is part of the +** [pointer passing interface] added for SQLite 3.20.0. +** +** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer +** for the [prepared statement] or with a prepared statement for which +** [sqlite3_step()] has been called more recently than [sqlite3_reset()], +** then the call will return [SQLITE_MISUSE]. If any sqlite3_bind_() +** routine is passed a [prepared statement] that has been finalized, the +** result is undefined and probably harmful. +** +** ^Bindings are not cleared by the [sqlite3_reset()] routine. +** ^Unbound parameters are interpreted as NULL. +** +** ^The sqlite3_bind_* routines return [SQLITE_OK] on success or an +** [error code] if anything goes wrong. +** ^[SQLITE_TOOBIG] might be returned if the size of a string or BLOB +** exceeds limits imposed by [sqlite3_limit]([SQLITE_LIMIT_LENGTH]) or +** [SQLITE_MAX_LENGTH]. +** ^[SQLITE_RANGE] is returned if the parameter +** index is out of range. ^[SQLITE_NOMEM] is returned if malloc() fails. +** +** See also: [sqlite3_bind_parameter_count()], +** [sqlite3_bind_parameter_name()], and [sqlite3_bind_parameter_index()]. +*/ +SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); +SQLITE_API int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64, + void(*)(void*)); +SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double); +SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int); +SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); +SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int); +SQLITE_API int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*)); +SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); +SQLITE_API int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64, + void(*)(void*), unsigned char encoding); +SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); +SQLITE_API int sqlite3_bind_pointer(sqlite3_stmt*, int, void*, const char*,void(*)(void*)); +SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); +SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64); + +/* +** CAPI3REF: Number Of SQL Parameters +** METHOD: sqlite3_stmt +** +** ^This routine can be used to find the number of [SQL parameters] +** in a [prepared statement]. SQL parameters are tokens of the +** form "?", "?NNN", ":AAA", "$AAA", or "@AAA" that serve as +** placeholders for values that are [sqlite3_bind_blob | bound] +** to the parameters at a later time. +** +** ^(This routine actually returns the index of the largest (rightmost) +** parameter. For all forms except ?NNN, this will correspond to the +** number of unique parameters. If parameters of the ?NNN form are used, +** there may be gaps in the list.)^ +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_name()], and +** [sqlite3_bind_parameter_index()]. +*/ +SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); + +/* +** CAPI3REF: Name Of A Host Parameter +** METHOD: sqlite3_stmt +** +** ^The sqlite3_bind_parameter_name(P,N) interface returns +** the name of the N-th [SQL parameter] in the [prepared statement] P. +** ^(SQL parameters of the form "?NNN" or ":AAA" or "@AAA" or "$AAA" +** have a name which is the string "?NNN" or ":AAA" or "@AAA" or "$AAA" +** respectively. +** In other words, the initial ":" or "$" or "@" or "?" +** is included as part of the name.)^ +** ^Parameters of the form "?" without a following integer have no name +** and are referred to as "nameless" or "anonymous parameters". +** +** ^The first host parameter has an index of 1, not 0. +** +** ^If the value N is out of range or if the N-th parameter is +** nameless, then NULL is returned. ^The returned string is +** always in UTF-8 encoding even if the named parameter was +** originally specified as UTF-16 in [sqlite3_prepare16()], +** [sqlite3_prepare16_v2()], or [sqlite3_prepare16_v3()]. +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_count()], and +** [sqlite3_bind_parameter_index()]. +*/ +SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); + +/* +** CAPI3REF: Index Of A Parameter With A Given Name +** METHOD: sqlite3_stmt +** +** ^Return the index of an SQL parameter given its name. ^The +** index value returned is suitable for use as the second +** parameter to [sqlite3_bind_blob|sqlite3_bind()]. ^A zero +** is returned if no matching parameter is found. ^The parameter +** name must be given in UTF-8 even if the original statement +** was prepared from UTF-16 text using [sqlite3_prepare16_v2()] or +** [sqlite3_prepare16_v3()]. +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_count()], and +** [sqlite3_bind_parameter_name()]. +*/ +SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); + +/* +** CAPI3REF: Reset All Bindings On A Prepared Statement +** METHOD: sqlite3_stmt +** +** ^Contrary to the intuition of many, [sqlite3_reset()] does not reset +** the [sqlite3_bind_blob | bindings] on a [prepared statement]. +** ^Use this routine to reset all host parameters to NULL. +*/ +SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); + +/* +** CAPI3REF: Number Of Columns In A Result Set +** METHOD: sqlite3_stmt +** +** ^Return the number of columns in the result set returned by the +** [prepared statement]. ^If this routine returns 0, that means the +** [prepared statement] returns no data (for example an [UPDATE]). +** ^However, just because this routine returns a positive number does not +** mean that one or more rows of data will be returned. ^A SELECT statement +** will always have a positive sqlite3_column_count() but depending on the +** WHERE clause constraints and the table content, it might return no rows. +** +** See also: [sqlite3_data_count()] +*/ +SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Column Names In A Result Set +** METHOD: sqlite3_stmt +** +** ^These routines return the name assigned to a particular column +** in the result set of a [SELECT] statement. ^The sqlite3_column_name() +** interface returns a pointer to a zero-terminated UTF-8 string +** and sqlite3_column_name16() returns a pointer to a zero-terminated +** UTF-16 string. ^The first parameter is the [prepared statement] +** that implements the [SELECT] statement. ^The second parameter is the +** column number. ^The leftmost column is number 0. +** +** ^The returned string pointer is valid until either the [prepared statement] +** is destroyed by [sqlite3_finalize()] or until the statement is automatically +** reprepared by the first call to [sqlite3_step()] for a particular run +** or until the next call to +** sqlite3_column_name() or sqlite3_column_name16() on the same column. +** +** ^If sqlite3_malloc() fails during the processing of either routine +** (for example during a conversion from UTF-8 to UTF-16) then a +** NULL pointer is returned. +** +** ^The name of a result column is the value of the "AS" clause for +** that column, if there is an AS clause. If there is no AS clause +** then the name of the column is unspecified and may change from +** one release of SQLite to the next. +*/ +SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N); +SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); + +/* +** CAPI3REF: Source Of Data In A Query Result +** METHOD: sqlite3_stmt +** +** ^These routines provide a means to determine the database, table, and +** table column that is the origin of a particular result column in +** [SELECT] statement. +** ^The name of the database or table or column can be returned as +** either a UTF-8 or UTF-16 string. ^The _database_ routines return +** the database name, the _table_ routines return the table name, and +** the origin_ routines return the column name. +** ^The returned string is valid until the [prepared statement] is destroyed +** using [sqlite3_finalize()] or until the statement is automatically +** reprepared by the first call to [sqlite3_step()] for a particular run +** or until the same information is requested +** again in a different encoding. +** +** ^The names returned are the original un-aliased names of the +** database, table, and column. +** +** ^The first argument to these interfaces is a [prepared statement]. +** ^These functions return information about the Nth result column returned by +** the statement, where N is the second function argument. +** ^The left-most column is column 0 for these routines. +** +** ^If the Nth column returned by the statement is an expression or +** subquery and is not a column value, then all of these functions return +** NULL. ^These routines might also return NULL if a memory allocation error +** occurs. ^Otherwise, they return the name of the attached database, table, +** or column that query result column was extracted from. +** +** ^As with all other SQLite APIs, those whose names end with "16" return +** UTF-16 encoded strings and the other functions return UTF-8. +** +** ^These APIs are only available if the library was compiled with the +** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol. +** +** If two or more threads call one or more +** [sqlite3_column_database_name | column metadata interfaces] +** for the same [prepared statement] and result column +** at the same time then the results are undefined. +*/ +SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int); +SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int); +SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int); + +/* +** CAPI3REF: Declared Datatype Of A Query Result +** METHOD: sqlite3_stmt +** +** ^(The first parameter is a [prepared statement]. +** If this statement is a [SELECT] statement and the Nth column of the +** returned result set of that [SELECT] is a table column (not an +** expression or subquery) then the declared type of the table +** column is returned.)^ ^If the Nth column of the result set is an +** expression or subquery, then a NULL pointer is returned. +** ^The returned string is always UTF-8 encoded. +** +** ^(For example, given the database schema: +** +** CREATE TABLE t1(c1 VARIANT); +** +** and the following statement to be compiled: +** +** SELECT c1 + 1, c1 FROM t1; +** +** this routine would return the string "VARIANT" for the second result +** column (i==1), and a NULL pointer for the first result column (i==0).)^ +** +** ^SQLite uses dynamic run-time typing. ^So just because a column +** is declared to contain a particular type does not mean that the +** data stored in that column is of the declared type. SQLite is +** strongly typed, but the typing is dynamic not static. ^Type +** is associated with individual values, not with the containers +** used to hold those values. +*/ +SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); + +/* +** CAPI3REF: Evaluate An SQL Statement +** METHOD: sqlite3_stmt +** +** After a [prepared statement] has been prepared using any of +** [sqlite3_prepare_v2()], [sqlite3_prepare_v3()], [sqlite3_prepare16_v2()], +** or [sqlite3_prepare16_v3()] or one of the legacy +** interfaces [sqlite3_prepare()] or [sqlite3_prepare16()], this function +** must be called one or more times to evaluate the statement. +** +** The details of the behavior of the sqlite3_step() interface depend +** on whether the statement was prepared using the newer "vX" interfaces +** [sqlite3_prepare_v3()], [sqlite3_prepare_v2()], [sqlite3_prepare16_v3()], +** [sqlite3_prepare16_v2()] or the older legacy +** interfaces [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the +** new "vX" interface is recommended for new applications but the legacy +** interface will continue to be supported. +** +** ^In the legacy interface, the return value will be either [SQLITE_BUSY], +** [SQLITE_DONE], [SQLITE_ROW], [SQLITE_ERROR], or [SQLITE_MISUSE]. +** ^With the "v2" interface, any of the other [result codes] or +** [extended result codes] might be returned as well. +** +** ^[SQLITE_BUSY] means that the database engine was unable to acquire the +** database locks it needs to do its job. ^If the statement is a [COMMIT] +** or occurs outside of an explicit transaction, then you can retry the +** statement. If the statement is not a [COMMIT] and occurs within an +** explicit transaction then you should rollback the transaction before +** continuing. +** +** ^[SQLITE_DONE] means that the statement has finished executing +** successfully. sqlite3_step() should not be called again on this virtual +** machine without first calling [sqlite3_reset()] to reset the virtual +** machine back to its initial state. +** +** ^If the SQL statement being executed returns any data, then [SQLITE_ROW] +** is returned each time a new row of data is ready for processing by the +** caller. The values may be accessed using the [column access functions]. +** sqlite3_step() is called again to retrieve the next row of data. +** +** ^[SQLITE_ERROR] means that a run-time error (such as a constraint +** violation) has occurred. sqlite3_step() should not be called again on +** the VM. More information may be found by calling [sqlite3_errmsg()]. +** ^With the legacy interface, a more specific error code (for example, +** [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth) +** can be obtained by calling [sqlite3_reset()] on the +** [prepared statement]. ^In the "v2" interface, +** the more specific error code is returned directly by sqlite3_step(). +** +** [SQLITE_MISUSE] means that the this routine was called inappropriately. +** Perhaps it was called on a [prepared statement] that has +** already been [sqlite3_finalize | finalized] or on one that had +** previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could +** be the case that the same database connection is being used by two or +** more threads at the same moment in time. +** +** For all versions of SQLite up to and including 3.6.23.1, a call to +** [sqlite3_reset()] was required after sqlite3_step() returned anything +** other than [SQLITE_ROW] before any subsequent invocation of +** sqlite3_step(). Failure to reset the prepared statement using +** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from +** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], +** sqlite3_step() began +** calling [sqlite3_reset()] automatically in this circumstance rather +** than returning [SQLITE_MISUSE]. This is not considered a compatibility +** break because any application that ever receives an SQLITE_MISUSE error +** is broken by definition. The [SQLITE_OMIT_AUTORESET] compile-time option +** can be used to restore the legacy behavior. +** +** <b>Goofy Interface Alert:</b> In the legacy interface, the sqlite3_step() +** API always returns a generic error code, [SQLITE_ERROR], following any +** error other than [SQLITE_BUSY] and [SQLITE_MISUSE]. You must call +** [sqlite3_reset()] or [sqlite3_finalize()] in order to find one of the +** specific [error codes] that better describes the error. +** We admit that this is a goofy design. The problem has been fixed +** with the "v2" interface. If you prepare all of your SQL statements +** using [sqlite3_prepare_v3()] or [sqlite3_prepare_v2()] +** or [sqlite3_prepare16_v2()] or [sqlite3_prepare16_v3()] instead +** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces, +** then the more specific [error codes] are returned directly +** by sqlite3_step(). The use of the "vX" interfaces is recommended. +*/ +SQLITE_API int sqlite3_step(sqlite3_stmt*); + +/* +** CAPI3REF: Number of columns in a result set +** METHOD: sqlite3_stmt +** +** ^The sqlite3_data_count(P) interface returns the number of columns in the +** current row of the result set of [prepared statement] P. +** ^If prepared statement P does not have results ready to return +** (via calls to the [sqlite3_column_int | sqlite3_column()] family of +** interfaces) then sqlite3_data_count(P) returns 0. +** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. +** ^The sqlite3_data_count(P) routine returns 0 if the previous call to +** [sqlite3_step](P) returned [SQLITE_DONE]. ^The sqlite3_data_count(P) +** will return non-zero if previous call to [sqlite3_step](P) returned +** [SQLITE_ROW], except in the case of the [PRAGMA incremental_vacuum] +** where it always returns zero since each step of that multi-step +** pragma returns 0 columns of data. +** +** See also: [sqlite3_column_count()] +*/ +SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Fundamental Datatypes +** KEYWORDS: SQLITE_TEXT +** +** ^(Every value in SQLite has one of five fundamental datatypes: +** +** <ul> +** <li> 64-bit signed integer +** <li> 64-bit IEEE floating point number +** <li> string +** <li> BLOB +** <li> NULL +** </ul>)^ +** +** These constants are codes for each of those types. +** +** Note that the SQLITE_TEXT constant was also used in SQLite version 2 +** for a completely different meaning. Software that links against both +** SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT, not +** SQLITE_TEXT. +*/ +#define SQLITE_INTEGER 1 +#define SQLITE_FLOAT 2 +#define SQLITE_BLOB 4 +#define SQLITE_NULL 5 +#ifdef SQLITE_TEXT +# undef SQLITE_TEXT +#else +# define SQLITE_TEXT 3 +#endif +#define SQLITE3_TEXT 3 + +/* +** CAPI3REF: Result Values From A Query +** KEYWORDS: {column access functions} +** METHOD: sqlite3_stmt +** +** <b>Summary:</b> +** <blockquote><table border=0 cellpadding=0 cellspacing=0> +** <tr><td><b>sqlite3_column_blob</b><td>&rarr;<td>BLOB result +** <tr><td><b>sqlite3_column_double</b><td>&rarr;<td>REAL result +** <tr><td><b>sqlite3_column_int</b><td>&rarr;<td>32-bit INTEGER result +** <tr><td><b>sqlite3_column_int64</b><td>&rarr;<td>64-bit INTEGER result +** <tr><td><b>sqlite3_column_text</b><td>&rarr;<td>UTF-8 TEXT result +** <tr><td><b>sqlite3_column_text16</b><td>&rarr;<td>UTF-16 TEXT result +** <tr><td><b>sqlite3_column_value</b><td>&rarr;<td>The result as an +** [sqlite3_value|unprotected sqlite3_value] object. +** <tr><td>&nbsp;<td>&nbsp;<td>&nbsp; +** <tr><td><b>sqlite3_column_bytes</b><td>&rarr;<td>Size of a BLOB +** or a UTF-8 TEXT result in bytes +** <tr><td><b>sqlite3_column_bytes16&nbsp;&nbsp;</b> +** <td>&rarr;&nbsp;&nbsp;<td>Size of UTF-16 +** TEXT in bytes +** <tr><td><b>sqlite3_column_type</b><td>&rarr;<td>Default +** datatype of the result +** </table></blockquote> +** +** <b>Details:</b> +** +** ^These routines return information about a single column of the current +** result row of a query. ^In every case the first argument is a pointer +** to the [prepared statement] that is being evaluated (the [sqlite3_stmt*] +** that was returned from [sqlite3_prepare_v2()] or one of its variants) +** and the second argument is the index of the column for which information +** should be returned. ^The leftmost column of the result set has the index 0. +** ^The number of columns in the result can be determined using +** [sqlite3_column_count()]. +** +** If the SQL statement does not currently point to a valid row, or if the +** column index is out of range, the result is undefined. +** These routines may only be called when the most recent call to +** [sqlite3_step()] has returned [SQLITE_ROW] and neither +** [sqlite3_reset()] nor [sqlite3_finalize()] have been called subsequently. +** If any of these routines are called after [sqlite3_reset()] or +** [sqlite3_finalize()] or after [sqlite3_step()] has returned +** something other than [SQLITE_ROW], the results are undefined. +** If [sqlite3_step()] or [sqlite3_reset()] or [sqlite3_finalize()] +** are called from a different thread while any of these routines +** are pending, then the results are undefined. +** +** The first six interfaces (_blob, _double, _int, _int64, _text, and _text16) +** each return the value of a result column in a specific data format. If +** the result column is not initially in the requested format (for example, +** if the query returns an integer but the sqlite3_column_text() interface +** is used to extract the value) then an automatic type conversion is performed. +** +** ^The sqlite3_column_type() routine returns the +** [SQLITE_INTEGER | datatype code] for the initial data type +** of the result column. ^The returned value is one of [SQLITE_INTEGER], +** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. +** The return value of sqlite3_column_type() can be used to decide which +** of the first six interface should be used to extract the column value. +** The value returned by sqlite3_column_type() is only meaningful if no +** automatic type conversions have occurred for the value in question. +** After a type conversion, the result of calling sqlite3_column_type() +** is undefined, though harmless. Future +** versions of SQLite may change the behavior of sqlite3_column_type() +** following a type conversion. +** +** If the result is a BLOB or a TEXT string, then the sqlite3_column_bytes() +** or sqlite3_column_bytes16() interfaces can be used to determine the size +** of that BLOB or string. +** +** ^If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes() +** routine returns the number of bytes in that BLOB or string. +** ^If the result is a UTF-16 string, then sqlite3_column_bytes() converts +** the string to UTF-8 and then returns the number of bytes. +** ^If the result is a numeric value then sqlite3_column_bytes() uses +** [sqlite3_snprintf()] to convert that value to a UTF-8 string and returns +** the number of bytes in that string. +** ^If the result is NULL, then sqlite3_column_bytes() returns zero. +** +** ^If the result is a BLOB or UTF-16 string then the sqlite3_column_bytes16() +** routine returns the number of bytes in that BLOB or string. +** ^If the result is a UTF-8 string, then sqlite3_column_bytes16() converts +** the string to UTF-16 and then returns the number of bytes. +** ^If the result is a numeric value then sqlite3_column_bytes16() uses +** [sqlite3_snprintf()] to convert that value to a UTF-16 string and returns +** the number of bytes in that string. +** ^If the result is NULL, then sqlite3_column_bytes16() returns zero. +** +** ^The values returned by [sqlite3_column_bytes()] and +** [sqlite3_column_bytes16()] do not include the zero terminators at the end +** of the string. ^For clarity: the values returned by +** [sqlite3_column_bytes()] and [sqlite3_column_bytes16()] are the number of +** bytes in the string, not the number of characters. +** +** ^Strings returned by sqlite3_column_text() and sqlite3_column_text16(), +** even empty strings, are always zero-terminated. ^The return +** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer. +** +** ^Strings returned by sqlite3_column_text16() always have the endianness +** which is native to the platform, regardless of the text encoding set +** for the database. +** +** <b>Warning:</b> ^The object returned by [sqlite3_column_value()] is an +** [unprotected sqlite3_value] object. In a multithreaded environment, +** an unprotected sqlite3_value object may only be used safely with +** [sqlite3_bind_value()] and [sqlite3_result_value()]. +** If the [unprotected sqlite3_value] object returned by +** [sqlite3_column_value()] is used in any other way, including calls +** to routines like [sqlite3_value_int()], [sqlite3_value_text()], +** or [sqlite3_value_bytes()], the behavior is not threadsafe. +** Hence, the sqlite3_column_value() interface +** is normally only useful within the implementation of +** [application-defined SQL functions] or [virtual tables], not within +** top-level application code. +** +** These routines may attempt to convert the datatype of the result. +** ^For example, if the internal representation is FLOAT and a text result +** is requested, [sqlite3_snprintf()] is used internally to perform the +** conversion automatically. ^(The following table details the conversions +** that are applied: +** +** <blockquote> +** <table border="1"> +** <tr><th> Internal<br>Type <th> Requested<br>Type <th> Conversion +** +** <tr><td> NULL <td> INTEGER <td> Result is 0 +** <tr><td> NULL <td> FLOAT <td> Result is 0.0 +** <tr><td> NULL <td> TEXT <td> Result is a NULL pointer +** <tr><td> NULL <td> BLOB <td> Result is a NULL pointer +** <tr><td> INTEGER <td> FLOAT <td> Convert from integer to float +** <tr><td> INTEGER <td> TEXT <td> ASCII rendering of the integer +** <tr><td> INTEGER <td> BLOB <td> Same as INTEGER->TEXT +** <tr><td> FLOAT <td> INTEGER <td> [CAST] to INTEGER +** <tr><td> FLOAT <td> TEXT <td> ASCII rendering of the float +** <tr><td> FLOAT <td> BLOB <td> [CAST] to BLOB +** <tr><td> TEXT <td> INTEGER <td> [CAST] to INTEGER +** <tr><td> TEXT <td> FLOAT <td> [CAST] to REAL +** <tr><td> TEXT <td> BLOB <td> No change +** <tr><td> BLOB <td> INTEGER <td> [CAST] to INTEGER +** <tr><td> BLOB <td> FLOAT <td> [CAST] to REAL +** <tr><td> BLOB <td> TEXT <td> [CAST] to TEXT, ensure zero terminator +** </table> +** </blockquote>)^ +** +** Note that when type conversions occur, pointers returned by prior +** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or +** sqlite3_column_text16() may be invalidated. +** Type conversions and pointer invalidations might occur +** in the following cases: +** +** <ul> +** <li> The initial content is a BLOB and sqlite3_column_text() or +** sqlite3_column_text16() is called. A zero-terminator might +** need to be added to the string.</li> +** <li> The initial content is UTF-8 text and sqlite3_column_bytes16() or +** sqlite3_column_text16() is called. The content must be converted +** to UTF-16.</li> +** <li> The initial content is UTF-16 text and sqlite3_column_bytes() or +** sqlite3_column_text() is called. The content must be converted +** to UTF-8.</li> +** </ul> +** +** ^Conversions between UTF-16be and UTF-16le are always done in place and do +** not invalidate a prior pointer, though of course the content of the buffer +** that the prior pointer references will have been modified. Other kinds +** of conversion are done in place when it is possible, but sometimes they +** are not possible and in those cases prior pointers are invalidated. +** +** The safest policy is to invoke these routines +** in one of the following ways: +** +** <ul> +** <li>sqlite3_column_text() followed by sqlite3_column_bytes()</li> +** <li>sqlite3_column_blob() followed by sqlite3_column_bytes()</li> +** <li>sqlite3_column_text16() followed by sqlite3_column_bytes16()</li> +** </ul> +** +** In other words, you should call sqlite3_column_text(), +** sqlite3_column_blob(), or sqlite3_column_text16() first to force the result +** into the desired format, then invoke sqlite3_column_bytes() or +** sqlite3_column_bytes16() to find the size of the result. Do not mix calls +** to sqlite3_column_text() or sqlite3_column_blob() with calls to +** sqlite3_column_bytes16(), and do not mix calls to sqlite3_column_text16() +** with calls to sqlite3_column_bytes(). +** +** ^The pointers returned are valid until a type conversion occurs as +** described above, or until [sqlite3_step()] or [sqlite3_reset()] or +** [sqlite3_finalize()] is called. ^The memory space used to hold strings +** and BLOBs is freed automatically. Do not pass the pointers returned +** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into +** [sqlite3_free()]. +** +** As long as the input parameters are correct, these routines will only +** fail if an out-of-memory error occurs during a format conversion. +** Only the following subset of interfaces are subject to out-of-memory +** errors: +** +** <ul> +** <li> sqlite3_column_blob() +** <li> sqlite3_column_text() +** <li> sqlite3_column_text16() +** <li> sqlite3_column_bytes() +** <li> sqlite3_column_bytes16() +** </ul> +** +** If an out-of-memory error occurs, then the return value from these +** routines is the same as if the column had contained an SQL NULL value. +** Valid SQL NULL returns can be distinguished from out-of-memory errors +** by invoking the [sqlite3_errcode()] immediately after the suspect +** return value is obtained and before any +** other SQLite interface is called on the same [database connection]. +*/ +SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); +SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); +SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); +SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); + +/* +** CAPI3REF: Destroy A Prepared Statement Object +** DESTRUCTOR: sqlite3_stmt +** +** ^The sqlite3_finalize() function is called to delete a [prepared statement]. +** ^If the most recent evaluation of the statement encountered no errors +** or if the statement is never been evaluated, then sqlite3_finalize() returns +** SQLITE_OK. ^If the most recent evaluation of statement S failed, then +** sqlite3_finalize(S) returns the appropriate [error code] or +** [extended error code]. +** +** ^The sqlite3_finalize(S) routine can be called at any point during +** the life cycle of [prepared statement] S: +** before statement S is ever evaluated, after +** one or more calls to [sqlite3_reset()], or after any call +** to [sqlite3_step()] regardless of whether or not the statement has +** completed execution. +** +** ^Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op. +** +** The application must finalize every [prepared statement] in order to avoid +** resource leaks. It is a grievous error for the application to try to use +** a prepared statement after it has been finalized. Any use of a prepared +** statement after it has been finalized can result in undefined and +** undesirable behavior such as segfaults and heap corruption. +*/ +SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Reset A Prepared Statement Object +** METHOD: sqlite3_stmt +** +** The sqlite3_reset() function is called to reset a [prepared statement] +** object back to its initial state, ready to be re-executed. +** ^Any SQL statement variables that had values bound to them using +** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values. +** Use [sqlite3_clear_bindings()] to reset the bindings. +** +** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S +** back to the beginning of its program. +** +** ^The return code from [sqlite3_reset(S)] indicates whether or not +** the previous evaluation of prepared statement S completed successfully. +** ^If [sqlite3_step(S)] has never before been called on S or if +** [sqlite3_step(S)] has not been called since the previous call +** to [sqlite3_reset(S)], then [sqlite3_reset(S)] will return +** [SQLITE_OK]. +** +** ^If the most recent call to [sqlite3_step(S)] for the +** [prepared statement] S indicated an error, then +** [sqlite3_reset(S)] returns an appropriate [error code]. +** ^The [sqlite3_reset(S)] interface might also return an [error code] +** if there were no prior errors but the process of resetting +** the prepared statement caused a new error. ^For example, if an +** [INSERT] statement with a [RETURNING] clause is only stepped one time, +** that one call to [sqlite3_step(S)] might return SQLITE_ROW but +** the overall statement might still fail and the [sqlite3_reset(S)] call +** might return SQLITE_BUSY if locking constraints prevent the +** database change from committing. Therefore, it is important that +** applications check the return code from [sqlite3_reset(S)] even if +** no prior call to [sqlite3_step(S)] indicated a problem. +** +** ^The [sqlite3_reset(S)] interface does not change the values +** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. +*/ +SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); + + +/* +** CAPI3REF: Create Or Redefine SQL Functions +** KEYWORDS: {function creation routines} +** METHOD: sqlite3 +** +** ^These functions (collectively known as "function creation routines") +** are used to add SQL functions or aggregates or to redefine the behavior +** of existing SQL functions or aggregates. The only differences between +** the three "sqlite3_create_function*" routines are the text encoding +** expected for the second parameter (the name of the function being +** created) and the presence or absence of a destructor callback for +** the application data pointer. Function sqlite3_create_window_function() +** is similar, but allows the user to supply the extra callback functions +** needed by [aggregate window functions]. +** +** ^The first parameter is the [database connection] to which the SQL +** function is to be added. ^If an application uses more than one database +** connection then application-defined SQL functions must be added +** to each database connection separately. +** +** ^The second parameter is the name of the SQL function to be created or +** redefined. ^The length of the name is limited to 255 bytes in a UTF-8 +** representation, exclusive of the zero-terminator. ^Note that the name +** length limit is in UTF-8 bytes, not characters nor UTF-16 bytes. +** ^Any attempt to create a function with a longer name +** will result in [SQLITE_MISUSE] being returned. +** +** ^The third parameter (nArg) +** is the number of arguments that the SQL function or +** aggregate takes. ^If this parameter is -1, then the SQL function or +** aggregate may take any number of arguments between 0 and the limit +** set by [sqlite3_limit]([SQLITE_LIMIT_FUNCTION_ARG]). If the third +** parameter is less than -1 or greater than 127 then the behavior is +** undefined. +** +** ^The fourth parameter, eTextRep, specifies what +** [SQLITE_UTF8 | text encoding] this SQL function prefers for +** its parameters. The application should set this parameter to +** [SQLITE_UTF16LE] if the function implementation invokes +** [sqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the +** implementation invokes [sqlite3_value_text16be()] on an input, or +** [SQLITE_UTF16] if [sqlite3_value_text16()] is used, or [SQLITE_UTF8] +** otherwise. ^The same SQL function may be registered multiple times using +** different preferred text encodings, with different implementations for +** each encoding. +** ^When multiple implementations of the same function are available, SQLite +** will pick the one that involves the least amount of data conversion. +** +** ^The fourth parameter may optionally be ORed with [SQLITE_DETERMINISTIC] +** to signal that the function will always return the same result given +** the same inputs within a single SQL statement. Most SQL functions are +** deterministic. The built-in [random()] SQL function is an example of a +** function that is not deterministic. The SQLite query planner is able to +** perform additional optimizations on deterministic functions, so use +** of the [SQLITE_DETERMINISTIC] flag is recommended where possible. +** +** ^The fourth parameter may also optionally include the [SQLITE_DIRECTONLY] +** flag, which if present prevents the function from being invoked from +** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions, +** index expressions, or the WHERE clause of partial indexes. +** +** For best security, the [SQLITE_DIRECTONLY] flag is recommended for +** all application-defined SQL functions that do not need to be +** used inside of triggers, view, CHECK constraints, or other elements of +** the database schema. This flags is especially recommended for SQL +** functions that have side effects or reveal internal application state. +** Without this flag, an attacker might be able to modify the schema of +** a database file to include invocations of the function with parameters +** chosen by the attacker, which the application will then execute when +** the database file is opened and read. +** +** ^(The fifth parameter is an arbitrary pointer. The implementation of the +** function can gain access to this pointer using [sqlite3_user_data()].)^ +** +** ^The sixth, seventh and eighth parameters passed to the three +** "sqlite3_create_function*" functions, xFunc, xStep and xFinal, are +** pointers to C-language functions that implement the SQL function or +** aggregate. ^A scalar SQL function requires an implementation of the xFunc +** callback only; NULL pointers must be passed as the xStep and xFinal +** parameters. ^An aggregate SQL function requires an implementation of xStep +** and xFinal and NULL pointer must be passed for xFunc. ^To delete an existing +** SQL function or aggregate, pass NULL pointers for all three function +** callbacks. +** +** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue +** and xInverse) passed to sqlite3_create_window_function are pointers to +** C-language callbacks that implement the new function. xStep and xFinal +** must both be non-NULL. xValue and xInverse may either both be NULL, in +** which case a regular aggregate function is created, or must both be +** non-NULL, in which case the new function may be used as either an aggregate +** or aggregate window function. More details regarding the implementation +** of aggregate window functions are +** [user-defined window functions|available here]. +** +** ^(If the final parameter to sqlite3_create_function_v2() or +** sqlite3_create_window_function() is not NULL, then it is destructor for +** the application data pointer. The destructor is invoked when the function +** is deleted, either by being overloaded or when the database connection +** closes.)^ ^The destructor is also invoked if the call to +** sqlite3_create_function_v2() fails. ^When the destructor callback is +** invoked, it is passed a single argument which is a copy of the application +** data pointer which was the fifth parameter to sqlite3_create_function_v2(). +** +** ^It is permitted to register multiple implementations of the same +** functions with the same name but with either differing numbers of +** arguments or differing preferred text encodings. ^SQLite will use +** the implementation that most closely matches the way in which the +** SQL function is used. ^A function implementation with a non-negative +** nArg parameter is a better match than a function implementation with +** a negative nArg. ^A function where the preferred text encoding +** matches the database encoding is a better +** match than a function where the encoding is different. +** ^A function where the encoding difference is between UTF16le and UTF16be +** is a closer match than a function where the encoding difference is +** between UTF8 and UTF16. +** +** ^Built-in functions may be overloaded by new application-defined functions. +** +** ^An application-defined function is permitted to call other +** SQLite interfaces. However, such calls must not +** close the database connection nor finalize or reset the prepared +** statement in which the function is running. +*/ +SQLITE_API int sqlite3_create_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); +SQLITE_API int sqlite3_create_function16( + sqlite3 *db, + const void *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); +SQLITE_API int sqlite3_create_function_v2( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void(*xDestroy)(void*) +); +SQLITE_API int sqlite3_create_window_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void (*xValue)(sqlite3_context*), + void (*xInverse)(sqlite3_context*,int,sqlite3_value**), + void(*xDestroy)(void*) +); + +/* +** CAPI3REF: Text Encodings +** +** These constant define integer codes that represent the various +** text encodings supported by SQLite. +*/ +#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ +#define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ +#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */ +#define SQLITE_UTF16 4 /* Use native byte order */ +#define SQLITE_ANY 5 /* Deprecated */ +#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ + +/* +** CAPI3REF: Function Flags +** +** These constants may be ORed together with the +** [SQLITE_UTF8 | preferred text encoding] as the fourth argument +** to [sqlite3_create_function()], [sqlite3_create_function16()], or +** [sqlite3_create_function_v2()]. +** +** <dl> +** [[SQLITE_DETERMINISTIC]] <dt>SQLITE_DETERMINISTIC</dt><dd> +** The SQLITE_DETERMINISTIC flag means that the new function always gives +** the same output when the input parameters are the same. +** The [abs|abs() function] is deterministic, for example, but +** [randomblob|randomblob()] is not. Functions must +** be deterministic in order to be used in certain contexts such as +** with the WHERE clause of [partial indexes] or in [generated columns]. +** SQLite might also optimize deterministic functions by factoring them +** out of inner loops. +** </dd> +** +** [[SQLITE_DIRECTONLY]] <dt>SQLITE_DIRECTONLY</dt><dd> +** The SQLITE_DIRECTONLY flag means that the function may only be invoked +** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in +** schema structures such as [CHECK constraints], [DEFAULT clauses], +** [expression indexes], [partial indexes], or [generated columns]. +** <p> +** The SQLITE_DIRECTONLY flag is recommended for any +** [application-defined SQL function] +** that has side-effects or that could potentially leak sensitive information. +** This will prevent attacks in which an application is tricked +** into using a database file that has had its schema surreptitiously +** modified to invoke the application-defined function in ways that are +** harmful. +** <p> +** Some people say it is good practice to set SQLITE_DIRECTONLY on all +** [application-defined SQL functions], regardless of whether or not they +** are security sensitive, as doing so prevents those functions from being used +** inside of the database schema, and thus ensures that the database +** can be inspected and modified using generic tools (such as the [CLI]) +** that do not have access to the application-defined functions. +** </dd> +** +** [[SQLITE_INNOCUOUS]] <dt>SQLITE_INNOCUOUS</dt><dd> +** The SQLITE_INNOCUOUS flag means that the function is unlikely +** to cause problems even if misused. An innocuous function should have +** no side effects and should not depend on any values other than its +** input parameters. The [abs|abs() function] is an example of an +** innocuous function. +** The [load_extension() SQL function] is not innocuous because of its +** side effects. +** <p> SQLITE_INNOCUOUS is similar to SQLITE_DETERMINISTIC, but is not +** exactly the same. The [random|random() function] is an example of a +** function that is innocuous but not deterministic. +** <p>Some heightened security settings +** ([SQLITE_DBCONFIG_TRUSTED_SCHEMA] and [PRAGMA trusted_schema=OFF]) +** disable the use of SQL functions inside views and triggers and in +** schema structures such as [CHECK constraints], [DEFAULT clauses], +** [expression indexes], [partial indexes], and [generated columns] unless +** the function is tagged with SQLITE_INNOCUOUS. Most built-in functions +** are innocuous. Developers are advised to avoid using the +** SQLITE_INNOCUOUS flag for application-defined functions unless the +** function has been carefully audited and found to be free of potentially +** security-adverse side-effects and information-leaks. +** </dd> +** +** [[SQLITE_SUBTYPE]] <dt>SQLITE_SUBTYPE</dt><dd> +** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call +** [sqlite3_value_subtype()] to inspect the sub-types of its arguments. +** Specifying this flag makes no difference for scalar or aggregate user +** functions. However, if it is not specified for a user-defined window +** function, then any sub-types belonging to arguments passed to the window +** function may be discarded before the window function is called (i.e. +** sqlite3_value_subtype() will always return 0). +** </dd> +** </dl> +*/ +#define SQLITE_DETERMINISTIC 0x000000800 +#define SQLITE_DIRECTONLY 0x000080000 +#define SQLITE_SUBTYPE 0x000100000 +#define SQLITE_INNOCUOUS 0x000200000 + +/* +** CAPI3REF: Deprecated Functions +** DEPRECATED +** +** These functions are [deprecated]. In order to maintain +** backwards compatibility with older code, these functions continue +** to be supported. However, new applications should avoid +** the use of these functions. To encourage programmers to avoid +** these functions, we will not explain what they do. +*/ +#ifndef SQLITE_OMIT_DEPRECATED +SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void); +SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void); +SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), + void*,sqlite3_int64); +#endif + +/* +** CAPI3REF: Obtaining SQL Values +** METHOD: sqlite3_value +** +** <b>Summary:</b> +** <blockquote><table border=0 cellpadding=0 cellspacing=0> +** <tr><td><b>sqlite3_value_blob</b><td>&rarr;<td>BLOB value +** <tr><td><b>sqlite3_value_double</b><td>&rarr;<td>REAL value +** <tr><td><b>sqlite3_value_int</b><td>&rarr;<td>32-bit INTEGER value +** <tr><td><b>sqlite3_value_int64</b><td>&rarr;<td>64-bit INTEGER value +** <tr><td><b>sqlite3_value_pointer</b><td>&rarr;<td>Pointer value +** <tr><td><b>sqlite3_value_text</b><td>&rarr;<td>UTF-8 TEXT value +** <tr><td><b>sqlite3_value_text16</b><td>&rarr;<td>UTF-16 TEXT value in +** the native byteorder +** <tr><td><b>sqlite3_value_text16be</b><td>&rarr;<td>UTF-16be TEXT value +** <tr><td><b>sqlite3_value_text16le</b><td>&rarr;<td>UTF-16le TEXT value +** <tr><td>&nbsp;<td>&nbsp;<td>&nbsp; +** <tr><td><b>sqlite3_value_bytes</b><td>&rarr;<td>Size of a BLOB +** or a UTF-8 TEXT in bytes +** <tr><td><b>sqlite3_value_bytes16&nbsp;&nbsp;</b> +** <td>&rarr;&nbsp;&nbsp;<td>Size of UTF-16 +** TEXT in bytes +** <tr><td><b>sqlite3_value_type</b><td>&rarr;<td>Default +** datatype of the value +** <tr><td><b>sqlite3_value_numeric_type&nbsp;&nbsp;</b> +** <td>&rarr;&nbsp;&nbsp;<td>Best numeric datatype of the value +** <tr><td><b>sqlite3_value_nochange&nbsp;&nbsp;</b> +** <td>&rarr;&nbsp;&nbsp;<td>True if the column is unchanged in an UPDATE +** against a virtual table. +** <tr><td><b>sqlite3_value_frombind&nbsp;&nbsp;</b> +** <td>&rarr;&nbsp;&nbsp;<td>True if value originated from a [bound parameter] +** </table></blockquote> +** +** <b>Details:</b> +** +** These routines extract type, size, and content information from +** [protected sqlite3_value] objects. Protected sqlite3_value objects +** are used to pass parameter information into the functions that +** implement [application-defined SQL functions] and [virtual tables]. +** +** These routines work only with [protected sqlite3_value] objects. +** Any attempt to use these routines on an [unprotected sqlite3_value] +** is not threadsafe. +** +** ^These routines work just like the corresponding [column access functions] +** except that these routines take a single [protected sqlite3_value] object +** pointer instead of a [sqlite3_stmt*] pointer and an integer column number. +** +** ^The sqlite3_value_text16() interface extracts a UTF-16 string +** in the native byte-order of the host machine. ^The +** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces +** extract UTF-16 strings as big-endian and little-endian respectively. +** +** ^If [sqlite3_value] object V was initialized +** using [sqlite3_bind_pointer(S,I,P,X,D)] or [sqlite3_result_pointer(C,P,X,D)] +** and if X and Y are strings that compare equal according to strcmp(X,Y), +** then sqlite3_value_pointer(V,Y) will return the pointer P. ^Otherwise, +** sqlite3_value_pointer(V,Y) returns a NULL. The sqlite3_bind_pointer() +** routine is part of the [pointer passing interface] added for SQLite 3.20.0. +** +** ^(The sqlite3_value_type(V) interface returns the +** [SQLITE_INTEGER | datatype code] for the initial datatype of the +** [sqlite3_value] object V. The returned value is one of [SQLITE_INTEGER], +** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL].)^ +** Other interfaces might change the datatype for an sqlite3_value object. +** For example, if the datatype is initially SQLITE_INTEGER and +** sqlite3_value_text(V) is called to extract a text value for that +** integer, then subsequent calls to sqlite3_value_type(V) might return +** SQLITE_TEXT. Whether or not a persistent internal datatype conversion +** occurs is undefined and may change from one release of SQLite to the next. +** +** ^(The sqlite3_value_numeric_type() interface attempts to apply +** numeric affinity to the value. This means that an attempt is +** made to convert the value to an integer or floating point. If +** such a conversion is possible without loss of information (in other +** words, if the value is a string that looks like a number) +** then the conversion is performed. Otherwise no conversion occurs. +** The [SQLITE_INTEGER | datatype] after conversion is returned.)^ +** +** ^Within the [xUpdate] method of a [virtual table], the +** sqlite3_value_nochange(X) interface returns true if and only if +** the column corresponding to X is unchanged by the UPDATE operation +** that the xUpdate method call was invoked to implement and if +** and the prior [xColumn] method call that was invoked to extracted +** the value for that column returned without setting a result (probably +** because it queried [sqlite3_vtab_nochange()] and found that the column +** was unchanging). ^Within an [xUpdate] method, any value for which +** sqlite3_value_nochange(X) is true will in all other respects appear +** to be a NULL value. If sqlite3_value_nochange(X) is invoked anywhere other +** than within an [xUpdate] method call for an UPDATE statement, then +** the return value is arbitrary and meaningless. +** +** ^The sqlite3_value_frombind(X) interface returns non-zero if the +** value X originated from one of the [sqlite3_bind_int|sqlite3_bind()] +** interfaces. ^If X comes from an SQL literal value, or a table column, +** or an expression, then sqlite3_value_frombind(X) returns zero. +** +** Please pay particular attention to the fact that the pointer returned +** from [sqlite3_value_blob()], [sqlite3_value_text()], or +** [sqlite3_value_text16()] can be invalidated by a subsequent call to +** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()], +** or [sqlite3_value_text16()]. +** +** These routines must be called from the same thread as +** the SQL function that supplied the [sqlite3_value*] parameters. +** +** As long as the input parameter is correct, these routines can only +** fail if an out-of-memory error occurs during a format conversion. +** Only the following subset of interfaces are subject to out-of-memory +** errors: +** +** <ul> +** <li> sqlite3_value_blob() +** <li> sqlite3_value_text() +** <li> sqlite3_value_text16() +** <li> sqlite3_value_text16le() +** <li> sqlite3_value_text16be() +** <li> sqlite3_value_bytes() +** <li> sqlite3_value_bytes16() +** </ul> +** +** If an out-of-memory error occurs, then the return value from these +** routines is the same as if the column had contained an SQL NULL value. +** Valid SQL NULL returns can be distinguished from out-of-memory errors +** by invoking the [sqlite3_errcode()] immediately after the suspect +** return value is obtained and before any +** other SQLite interface is called on the same [database connection]. +*/ +SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); +SQLITE_API double sqlite3_value_double(sqlite3_value*); +SQLITE_API int sqlite3_value_int(sqlite3_value*); +SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); +SQLITE_API void *sqlite3_value_pointer(sqlite3_value*, const char*); +SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); +SQLITE_API int sqlite3_value_type(sqlite3_value*); +SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); +SQLITE_API int sqlite3_value_nochange(sqlite3_value*); +SQLITE_API int sqlite3_value_frombind(sqlite3_value*); + +/* +** CAPI3REF: Report the internal text encoding state of an sqlite3_value object +** METHOD: sqlite3_value +** +** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8], +** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current text encoding +** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X) +** returns something other than SQLITE_TEXT, then the return value from +** sqlite3_value_encoding(X) is meaningless. ^Calls to +** [sqlite3_value_text(X)], [sqlite3_value_text16(X)], [sqlite3_value_text16be(X)], +** [sqlite3_value_text16le(X)], [sqlite3_value_bytes(X)], or +** [sqlite3_value_bytes16(X)] might change the encoding of the value X and +** thus change the return from subsequent calls to sqlite3_value_encoding(X). +** +** This routine is intended for used by applications that test and validate +** the SQLite implementation. This routine is inquiring about the opaque +** internal state of an [sqlite3_value] object. Ordinary applications should +** not need to know what the internal state of an sqlite3_value object is and +** hence should not need to use this interface. +*/ +SQLITE_API int sqlite3_value_encoding(sqlite3_value*); + +/* +** CAPI3REF: Finding The Subtype Of SQL Values +** METHOD: sqlite3_value +** +** The sqlite3_value_subtype(V) function returns the subtype for +** an [application-defined SQL function] argument V. The subtype +** information can be used to pass a limited amount of context from +** one SQL function to another. Use the [sqlite3_result_subtype()] +** routine to set the subtype for the return value of an SQL function. +*/ +SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); + +/* +** CAPI3REF: Copy And Free SQL Values +** METHOD: sqlite3_value +** +** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value] +** object D and returns a pointer to that copy. ^The [sqlite3_value] returned +** is a [protected sqlite3_value] object even if the input is not. +** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a +** memory allocation fails. ^If V is a [pointer value], then the result +** of sqlite3_value_dup(V) is a NULL value. +** +** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object +** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer +** then sqlite3_value_free(V) is a harmless no-op. +*/ +SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value*); +SQLITE_API void sqlite3_value_free(sqlite3_value*); + +/* +** CAPI3REF: Obtain Aggregate Function Context +** METHOD: sqlite3_context +** +** Implementations of aggregate SQL functions use this +** routine to allocate memory for storing their state. +** +** ^The first time the sqlite3_aggregate_context(C,N) routine is called +** for a particular aggregate function, SQLite allocates +** N bytes of memory, zeroes out that memory, and returns a pointer +** to the new memory. ^On second and subsequent calls to +** sqlite3_aggregate_context() for the same aggregate function instance, +** the same buffer is returned. Sqlite3_aggregate_context() is normally +** called once for each invocation of the xStep callback and then one +** last time when the xFinal callback is invoked. ^(When no rows match +** an aggregate query, the xStep() callback of the aggregate function +** implementation is never called and xFinal() is called exactly once. +** In those cases, sqlite3_aggregate_context() might be called for the +** first time from within xFinal().)^ +** +** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer +** when first called if N is less than or equal to zero or if a memory +** allocation error occurs. +** +** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is +** determined by the N parameter on first successful call. Changing the +** value of N in any subsequent call to sqlite3_aggregate_context() within +** the same aggregate function instance will not resize the memory +** allocation.)^ Within the xFinal callback, it is customary to set +** N=0 in calls to sqlite3_aggregate_context(C,N) so that no +** pointless memory allocations occur. +** +** ^SQLite automatically frees the memory allocated by +** sqlite3_aggregate_context() when the aggregate query concludes. +** +** The first parameter must be a copy of the +** [sqlite3_context | SQL function context] that is the first parameter +** to the xStep or xFinal callback routine that implements the aggregate +** function. +** +** This routine must be called from the same thread in which +** the aggregate SQL function is running. +*/ +SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); + +/* +** CAPI3REF: User Data For Functions +** METHOD: sqlite3_context +** +** ^The sqlite3_user_data() interface returns a copy of +** the pointer that was the pUserData parameter (the 5th parameter) +** of the [sqlite3_create_function()] +** and [sqlite3_create_function16()] routines that originally +** registered the application defined function. +** +** This routine must be called from the same thread in which +** the application-defined function is running. +*/ +SQLITE_API void *sqlite3_user_data(sqlite3_context*); + +/* +** CAPI3REF: Database Connection For Functions +** METHOD: sqlite3_context +** +** ^The sqlite3_context_db_handle() interface returns a copy of +** the pointer to the [database connection] (the 1st parameter) +** of the [sqlite3_create_function()] +** and [sqlite3_create_function16()] routines that originally +** registered the application defined function. +*/ +SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); + +/* +** CAPI3REF: Function Auxiliary Data +** METHOD: sqlite3_context +** +** These functions may be used by (non-aggregate) SQL functions to +** associate auxiliary data with argument values. If the same argument +** value is passed to multiple invocations of the same SQL function during +** query execution, under some circumstances the associated auxiliary data +** might be preserved. An example of where this might be useful is in a +** regular-expression matching function. The compiled version of the regular +** expression can be stored as auxiliary data associated with the pattern string. +** Then as long as the pattern string remains the same, +** the compiled regular expression can be reused on multiple +** invocations of the same function. +** +** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the auxiliary data +** associated by the sqlite3_set_auxdata(C,N,P,X) function with the Nth argument +** value to the application-defined function. ^N is zero for the left-most +** function argument. ^If there is no auxiliary data +** associated with the function argument, the sqlite3_get_auxdata(C,N) interface +** returns a NULL pointer. +** +** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as auxiliary data for the +** N-th argument of the application-defined function. ^Subsequent +** calls to sqlite3_get_auxdata(C,N) return P from the most recent +** sqlite3_set_auxdata(C,N,P,X) call if the auxiliary data is still valid or +** NULL if the auxiliary data has been discarded. +** ^After each call to sqlite3_set_auxdata(C,N,P,X) where X is not NULL, +** SQLite will invoke the destructor function X with parameter P exactly +** once, when the auxiliary data is discarded. +** SQLite is free to discard the auxiliary data at any time, including: <ul> +** <li> ^(when the corresponding function parameter changes)^, or +** <li> ^(when [sqlite3_reset()] or [sqlite3_finalize()] is called for the +** SQL statement)^, or +** <li> ^(when sqlite3_set_auxdata() is invoked again on the same +** parameter)^, or +** <li> ^(during the original sqlite3_set_auxdata() call when a memory +** allocation error occurs.)^ </ul> +** +** Note the last bullet in particular. The destructor X in +** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the +** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata() +** should be called near the end of the function implementation and the +** function implementation should not make any use of P after +** sqlite3_set_auxdata() has been called. +** +** ^(In practice, auxiliary data is preserved between function calls for +** function parameters that are compile-time constants, including literal +** values and [parameters] and expressions composed from the same.)^ +** +** The value of the N parameter to these interfaces should be non-negative. +** Future enhancements may make use of negative N values to define new +** kinds of function caching behavior. +** +** These routines must be called from the same thread in which +** the SQL function is running. +** +** See also: [sqlite3_get_clientdata()] and [sqlite3_set_clientdata()]. +*/ +SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N); +SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); + +/* +** CAPI3REF: Database Connection Client Data +** METHOD: sqlite3 +** +** These functions are used to associate one or more named pointers +** with a [database connection]. +** A call to sqlite3_set_clientdata(D,N,P,X) causes the pointer P +** to be attached to [database connection] D using name N. Subsequent +** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P +** or a NULL pointer if there were no prior calls to +** sqlite3_set_clientdata() with the same values of D and N. +** Names are compared using strcmp() and are thus case sensitive. +** +** If P and X are both non-NULL, then the destructor X is invoked with +** argument P on the first of the following occurrences: +** <ul> +** <li> An out-of-memory error occurs during the call to +** sqlite3_set_clientdata() which attempts to register pointer P. +** <li> A subsequent call to sqlite3_set_clientdata(D,N,P,X) is made +** with the same D and N parameters. +** <li> The database connection closes. SQLite does not make any guarantees +** about the order in which destructors are called, only that all +** destructors will be called exactly once at some point during the +** database connection closingi process. +** </ul> +** +** SQLite does not do anything with client data other than invoke +** destructors on the client data at the appropriate time. The intended +** use for client data is to provide a mechanism for wrapper libraries +** to store additional information about an SQLite database connection. +** +** There is no limit (other than available memory) on the number of different +** client data pointers (with different names) that can be attached to a +** single database connection. However, the implementation is optimized +** for the case of having only one or two different client data names. +** Applications and wrapper libraries are discouraged from using more than +** one client data name each. +** +** There is no way to enumerate the client data pointers +** associated with a database connection. The N parameter can be thought +** of as a secret key such that only code that knows the secret key is able +** to access the associated data. +** +** Security Warning: These interfaces should not be exposed in scripting +** languages or in other circumstances where it might be possible for an +** an attacker to invoke them. Any agent that can invoke these interfaces +** can probably also take control of the process. +** +** Database connection client data is only available for SQLite +** version 3.44.0 ([dateof:3.44.0]) and later. +** +** See also: [sqlite3_set_auxdata()] and [sqlite3_get_auxdata()]. +*/ +SQLITE_API void *sqlite3_get_clientdata(sqlite3*,const char*); +SQLITE_API int sqlite3_set_clientdata(sqlite3*, const char*, void*, void(*)(void*)); + +/* +** CAPI3REF: Constants Defining Special Destructor Behavior +** +** These are special values for the destructor that is passed in as the +** final argument to routines like [sqlite3_result_blob()]. ^If the destructor +** argument is SQLITE_STATIC, it means that the content pointer is constant +** and will never change. It does not need to be destroyed. ^The +** SQLITE_TRANSIENT value means that the content will likely change in +** the near future and that SQLite should make its own private copy of +** the content before returning. +** +** The typedef is necessary to work around problems in certain +** C++ compilers. +*/ +typedef void (*sqlite3_destructor_type)(void*); +#define SQLITE_STATIC ((sqlite3_destructor_type)0) +#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) + +/* +** CAPI3REF: Setting The Result Of An SQL Function +** METHOD: sqlite3_context +** +** These routines are used by the xFunc or xFinal callbacks that +** implement SQL functions and aggregates. See +** [sqlite3_create_function()] and [sqlite3_create_function16()] +** for additional information. +** +** These functions work very much like the [parameter binding] family of +** functions used to bind values to host parameters in prepared statements. +** Refer to the [SQL parameter] documentation for additional information. +** +** ^The sqlite3_result_blob() interface sets the result from +** an application-defined function to be the BLOB whose content is pointed +** to by the second parameter and which is N bytes long where N is the +** third parameter. +** +** ^The sqlite3_result_zeroblob(C,N) and sqlite3_result_zeroblob64(C,N) +** interfaces set the result of the application-defined function to be +** a BLOB containing all zero bytes and N bytes in size. +** +** ^The sqlite3_result_double() interface sets the result from +** an application-defined function to be a floating point value specified +** by its 2nd argument. +** +** ^The sqlite3_result_error() and sqlite3_result_error16() functions +** cause the implemented SQL function to throw an exception. +** ^SQLite uses the string pointed to by the +** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() +** as the text of an error message. ^SQLite interprets the error +** message string from sqlite3_result_error() as UTF-8. ^SQLite +** interprets the string from sqlite3_result_error16() as UTF-16 using +** the same [byte-order determination rules] as [sqlite3_bind_text16()]. +** ^If the third parameter to sqlite3_result_error() +** or sqlite3_result_error16() is negative then SQLite takes as the error +** message all text up through the first zero character. +** ^If the third parameter to sqlite3_result_error() or +** sqlite3_result_error16() is non-negative then SQLite takes that many +** bytes (not characters) from the 2nd parameter as the error message. +** ^The sqlite3_result_error() and sqlite3_result_error16() +** routines make a private copy of the error message text before +** they return. Hence, the calling function can deallocate or +** modify the text after they return without harm. +** ^The sqlite3_result_error_code() function changes the error code +** returned by SQLite as a result of an error in a function. ^By default, +** the error code is SQLITE_ERROR. ^A subsequent call to sqlite3_result_error() +** or sqlite3_result_error16() resets the error code to SQLITE_ERROR. +** +** ^The sqlite3_result_error_toobig() interface causes SQLite to throw an +** error indicating that a string or BLOB is too long to represent. +** +** ^The sqlite3_result_error_nomem() interface causes SQLite to throw an +** error indicating that a memory allocation failed. +** +** ^The sqlite3_result_int() interface sets the return value +** of the application-defined function to be the 32-bit signed integer +** value given in the 2nd argument. +** ^The sqlite3_result_int64() interface sets the return value +** of the application-defined function to be the 64-bit signed integer +** value given in the 2nd argument. +** +** ^The sqlite3_result_null() interface sets the return value +** of the application-defined function to be NULL. +** +** ^The sqlite3_result_text(), sqlite3_result_text16(), +** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces +** set the return value of the application-defined function to be +** a text string which is represented as UTF-8, UTF-16 native byte order, +** UTF-16 little endian, or UTF-16 big endian, respectively. +** ^The sqlite3_result_text64() interface sets the return value of an +** application-defined function to be a text string in an encoding +** specified by the fifth (and last) parameter, which must be one +** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. +** ^SQLite takes the text result from the application from +** the 2nd parameter of the sqlite3_result_text* interfaces. +** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces +** other than sqlite3_result_text64() is negative, then SQLite computes +** the string length itself by searching the 2nd parameter for the first +** zero character. +** ^If the 3rd parameter to the sqlite3_result_text* interfaces +** is non-negative, then as many bytes (not characters) of the text +** pointed to by the 2nd parameter are taken as the application-defined +** function result. If the 3rd parameter is non-negative, then it +** must be the byte offset into the string where the NUL terminator would +** appear if the string where NUL terminated. If any NUL characters occur +** in the string at a byte offset that is less than the value of the 3rd +** parameter, then the resulting string will contain embedded NULs and the +** result of expressions operating on strings with embedded NULs is undefined. +** ^If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that +** function as the destructor on the text or BLOB result when it has +** finished using that result. +** ^If the 4th parameter to the sqlite3_result_text* interfaces or to +** sqlite3_result_blob is the special constant SQLITE_STATIC, then SQLite +** assumes that the text or BLOB result is in constant space and does not +** copy the content of the parameter nor call a destructor on the content +** when it has finished using that result. +** ^If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT +** then SQLite makes a copy of the result into space obtained +** from [sqlite3_malloc()] before it returns. +** +** ^For the sqlite3_result_text16(), sqlite3_result_text16le(), and +** sqlite3_result_text16be() routines, and for sqlite3_result_text64() +** when the encoding is not UTF8, if the input UTF16 begins with a +** byte-order mark (BOM, U+FEFF) then the BOM is removed from the +** string and the rest of the string is interpreted according to the +** byte-order specified by the BOM. ^The byte-order specified by +** the BOM at the beginning of the text overrides the byte-order +** specified by the interface procedure. ^So, for example, if +** sqlite3_result_text16le() is invoked with text that begins +** with bytes 0xfe, 0xff (a big-endian byte-order mark) then the +** first two bytes of input are skipped and the remaining input +** is interpreted as UTF16BE text. +** +** ^For UTF16 input text to the sqlite3_result_text16(), +** sqlite3_result_text16be(), sqlite3_result_text16le(), and +** sqlite3_result_text64() routines, if the text contains invalid +** UTF16 characters, the invalid characters might be converted +** into the unicode replacement character, U+FFFD. +** +** ^The sqlite3_result_value() interface sets the result of +** the application-defined function to be a copy of the +** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The +** sqlite3_result_value() interface makes a copy of the [sqlite3_value] +** so that the [sqlite3_value] specified in the parameter may change or +** be deallocated after sqlite3_result_value() returns without harm. +** ^A [protected sqlite3_value] object may always be used where an +** [unprotected sqlite3_value] object is required, so either +** kind of [sqlite3_value] object can be used with this interface. +** +** ^The sqlite3_result_pointer(C,P,T,D) interface sets the result to an +** SQL NULL value, just like [sqlite3_result_null(C)], except that it +** also associates the host-language pointer P or type T with that +** NULL value such that the pointer can be retrieved within an +** [application-defined SQL function] using [sqlite3_value_pointer()]. +** ^If the D parameter is not NULL, then it is a pointer to a destructor +** for the P parameter. ^SQLite invokes D with P as its only argument +** when SQLite is finished with P. The T parameter should be a static +** string and preferably a string literal. The sqlite3_result_pointer() +** routine is part of the [pointer passing interface] added for SQLite 3.20.0. +** +** If these routines are called from within the different thread +** than the one containing the application-defined function that received +** the [sqlite3_context] pointer, the results are undefined. +*/ +SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*, + sqlite3_uint64,void(*)(void*)); +SQLITE_API void sqlite3_result_double(sqlite3_context*, double); +SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); +SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); +SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*); +SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*); +SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); +SQLITE_API void sqlite3_result_null(sqlite3_context*); +SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64, + void(*)(void*), unsigned char encoding); +SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); +SQLITE_API void sqlite3_result_pointer(sqlite3_context*, void*,const char*,void(*)(void*)); +SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); +SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); + + +/* +** CAPI3REF: Setting The Subtype Of An SQL Function +** METHOD: sqlite3_context +** +** The sqlite3_result_subtype(C,T) function causes the subtype of +** the result from the [application-defined SQL function] with +** [sqlite3_context] C to be the value T. Only the lower 8 bits +** of the subtype T are preserved in current versions of SQLite; +** higher order bits are discarded. +** The number of subtype bytes preserved by SQLite might increase +** in future releases of SQLite. +*/ +SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); + +/* +** CAPI3REF: Define New Collating Sequences +** METHOD: sqlite3 +** +** ^These functions add, remove, or modify a [collation] associated +** with the [database connection] specified as the first argument. +** +** ^The name of the collation is a UTF-8 string +** for sqlite3_create_collation() and sqlite3_create_collation_v2() +** and a UTF-16 string in native byte order for sqlite3_create_collation16(). +** ^Collation names that compare equal according to [sqlite3_strnicmp()] are +** considered to be the same name. +** +** ^(The third argument (eTextRep) must be one of the constants: +** <ul> +** <li> [SQLITE_UTF8], +** <li> [SQLITE_UTF16LE], +** <li> [SQLITE_UTF16BE], +** <li> [SQLITE_UTF16], or +** <li> [SQLITE_UTF16_ALIGNED]. +** </ul>)^ +** ^The eTextRep argument determines the encoding of strings passed +** to the collating function callback, xCompare. +** ^The [SQLITE_UTF16] and [SQLITE_UTF16_ALIGNED] values for eTextRep +** force strings to be UTF16 with native byte order. +** ^The [SQLITE_UTF16_ALIGNED] value for eTextRep forces strings to begin +** on an even byte address. +** +** ^The fourth argument, pArg, is an application data pointer that is passed +** through as the first argument to the collating function callback. +** +** ^The fifth argument, xCompare, is a pointer to the collating function. +** ^Multiple collating functions can be registered using the same name but +** with different eTextRep parameters and SQLite will use whichever +** function requires the least amount of data transformation. +** ^If the xCompare argument is NULL then the collating function is +** deleted. ^When all collating functions having the same name are deleted, +** that collation is no longer usable. +** +** ^The collating function callback is invoked with a copy of the pArg +** application data pointer and with two strings in the encoding specified +** by the eTextRep argument. The two integer parameters to the collating +** function callback are the length of the two strings, in bytes. The collating +** function must return an integer that is negative, zero, or positive +** if the first string is less than, equal to, or greater than the second, +** respectively. A collating function must always return the same answer +** given the same inputs. If two or more collating functions are registered +** to the same collation name (using different eTextRep values) then all +** must give an equivalent answer when invoked with equivalent strings. +** The collating function must obey the following properties for all +** strings A, B, and C: +** +** <ol> +** <li> If A==B then B==A. +** <li> If A==B and B==C then A==C. +** <li> If A&lt;B THEN B&gt;A. +** <li> If A&lt;B and B&lt;C then A&lt;C. +** </ol> +** +** If a collating function fails any of the above constraints and that +** collating function is registered and used, then the behavior of SQLite +** is undefined. +** +** ^The sqlite3_create_collation_v2() works like sqlite3_create_collation() +** with the addition that the xDestroy callback is invoked on pArg when +** the collating function is deleted. +** ^Collating functions are deleted when they are overridden by later +** calls to the collation creation functions or when the +** [database connection] is closed using [sqlite3_close()]. +** +** ^The xDestroy callback is <u>not</u> called if the +** sqlite3_create_collation_v2() function fails. Applications that invoke +** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should +** check the return code and dispose of the application data pointer +** themselves rather than expecting SQLite to deal with it for them. +** This is different from every other SQLite interface. The inconsistency +** is unfortunate but cannot be changed without breaking backwards +** compatibility. +** +** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. +*/ +SQLITE_API int sqlite3_create_collation( + sqlite3*, + const char *zName, + int eTextRep, + void *pArg, + int(*xCompare)(void*,int,const void*,int,const void*) +); +SQLITE_API int sqlite3_create_collation_v2( + sqlite3*, + const char *zName, + int eTextRep, + void *pArg, + int(*xCompare)(void*,int,const void*,int,const void*), + void(*xDestroy)(void*) +); +SQLITE_API int sqlite3_create_collation16( + sqlite3*, + const void *zName, + int eTextRep, + void *pArg, + int(*xCompare)(void*,int,const void*,int,const void*) +); + +/* +** CAPI3REF: Collation Needed Callbacks +** METHOD: sqlite3 +** +** ^To avoid having to register all collation sequences before a database +** can be used, a single callback function may be registered with the +** [database connection] to be invoked whenever an undefined collation +** sequence is required. +** +** ^If the function is registered using the sqlite3_collation_needed() API, +** then it is passed the names of undefined collation sequences as strings +** encoded in UTF-8. ^If sqlite3_collation_needed16() is used, +** the names are passed as UTF-16 in machine native byte order. +** ^A call to either function replaces the existing collation-needed callback. +** +** ^(When the callback is invoked, the first argument passed is a copy +** of the second argument to sqlite3_collation_needed() or +** sqlite3_collation_needed16(). The second argument is the database +** connection. The third argument is one of [SQLITE_UTF8], [SQLITE_UTF16BE], +** or [SQLITE_UTF16LE], indicating the most desirable form of the collation +** sequence function required. The fourth parameter is the name of the +** required collation sequence.)^ +** +** The callback function should register the desired collation using +** [sqlite3_create_collation()], [sqlite3_create_collation16()], or +** [sqlite3_create_collation_v2()]. +*/ +SQLITE_API int sqlite3_collation_needed( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const char*) +); +SQLITE_API int sqlite3_collation_needed16( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const void*) +); + +#ifdef SQLITE_ENABLE_CEROD +/* +** Specify the activation key for a CEROD database. Unless +** activated, none of the CEROD routines will work. +*/ +SQLITE_API void sqlite3_activate_cerod( + const char *zPassPhrase /* Activation phrase */ +); +#endif + +/* +** CAPI3REF: Suspend Execution For A Short Time +** +** The sqlite3_sleep() function causes the current thread to suspend execution +** for at least a number of milliseconds specified in its parameter. +** +** If the operating system does not support sleep requests with +** millisecond time resolution, then the time will be rounded up to +** the nearest second. The number of milliseconds of sleep actually +** requested from the operating system is returned. +** +** ^SQLite implements this interface by calling the xSleep() +** method of the default [sqlite3_vfs] object. If the xSleep() method +** of the default VFS is not implemented correctly, or not implemented at +** all, then the behavior of sqlite3_sleep() may deviate from the description +** in the previous paragraphs. +** +** If a negative argument is passed to sqlite3_sleep() the results vary by +** VFS and operating system. Some system treat a negative argument as an +** instruction to sleep forever. Others understand it to mean do not sleep +** at all. ^In SQLite version 3.42.0 and later, a negative +** argument passed into sqlite3_sleep() is changed to zero before it is relayed +** down into the xSleep method of the VFS. +*/ +SQLITE_API int sqlite3_sleep(int); + +/* +** CAPI3REF: Name Of The Folder Holding Temporary Files +** +** ^(If this global variable is made to point to a string which is +** the name of a folder (a.k.a. directory), then all temporary files +** created by SQLite when using a built-in [sqlite3_vfs | VFS] +** will be placed in that directory.)^ ^If this variable +** is a NULL pointer, then SQLite performs a search for an appropriate +** temporary file directory. +** +** Applications are strongly discouraged from using this global variable. +** It is required to set a temporary folder on Windows Runtime (WinRT). +** But for all other platforms, it is highly recommended that applications +** neither read nor write this variable. This global variable is a relic +** that exists for backwards compatibility of legacy applications and should +** be avoided in new projects. +** +** It is not safe to read or modify this variable in more than one +** thread at a time. It is not safe to read or modify this variable +** if a [database connection] is being used at the same time in a separate +** thread. +** It is intended that this variable be set once +** as part of process initialization and before any SQLite interface +** routines have been called and that this variable remain unchanged +** thereafter. +** +** ^The [temp_store_directory pragma] may modify this variable and cause +** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, +** the [temp_store_directory pragma] always assumes that any string +** that this variable points to is held in memory obtained from +** [sqlite3_malloc] and the pragma may attempt to free that memory +** using [sqlite3_free]. +** Hence, if this variable is modified directly, either it should be +** made NULL or made to point to memory obtained from [sqlite3_malloc] +** or else the use of the [temp_store_directory pragma] should be avoided. +** Except when requested by the [temp_store_directory pragma], SQLite +** does not free the memory that sqlite3_temp_directory points to. If +** the application wants that memory to be freed, it must do +** so itself, taking care to only do so after all [database connection] +** objects have been destroyed. +** +** <b>Note to Windows Runtime users:</b> The temporary directory must be set +** prior to calling [sqlite3_open] or [sqlite3_open_v2]. Otherwise, various +** features that require the use of temporary files may fail. Here is an +** example of how to do this using C++ with the Windows Runtime: +** +** <blockquote><pre> +** LPCWSTR zPath = Windows::Storage::ApplicationData::Current-> +** &nbsp; TemporaryFolder->Path->Data(); +** char zPathBuf&#91;MAX_PATH + 1&#93;; +** memset(zPathBuf, 0, sizeof(zPathBuf)); +** WideCharToMultiByte(CP_UTF8, 0, zPath, -1, zPathBuf, sizeof(zPathBuf), +** &nbsp; NULL, NULL); +** sqlite3_temp_directory = sqlite3_mprintf("%s", zPathBuf); +** </pre></blockquote> +*/ +SQLITE_API SQLITE_EXTERN char *sqlite3_temp_directory; + +/* +** CAPI3REF: Name Of The Folder Holding Database Files +** +** ^(If this global variable is made to point to a string which is +** the name of a folder (a.k.a. directory), then all database files +** specified with a relative pathname and created or accessed by +** SQLite when using a built-in windows [sqlite3_vfs | VFS] will be assumed +** to be relative to that directory.)^ ^If this variable is a NULL +** pointer, then SQLite assumes that all database files specified +** with a relative pathname are relative to the current directory +** for the process. Only the windows VFS makes use of this global +** variable; it is ignored by the unix VFS. +** +** Changing the value of this variable while a database connection is +** open can result in a corrupt database. +** +** It is not safe to read or modify this variable in more than one +** thread at a time. It is not safe to read or modify this variable +** if a [database connection] is being used at the same time in a separate +** thread. +** It is intended that this variable be set once +** as part of process initialization and before any SQLite interface +** routines have been called and that this variable remain unchanged +** thereafter. +** +** ^The [data_store_directory pragma] may modify this variable and cause +** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, +** the [data_store_directory pragma] always assumes that any string +** that this variable points to is held in memory obtained from +** [sqlite3_malloc] and the pragma may attempt to free that memory +** using [sqlite3_free]. +** Hence, if this variable is modified directly, either it should be +** made NULL or made to point to memory obtained from [sqlite3_malloc] +** or else the use of the [data_store_directory pragma] should be avoided. +*/ +SQLITE_API SQLITE_EXTERN char *sqlite3_data_directory; + +/* +** CAPI3REF: Win32 Specific Interface +** +** These interfaces are available only on Windows. The +** [sqlite3_win32_set_directory] interface is used to set the value associated +** with the [sqlite3_temp_directory] or [sqlite3_data_directory] variable, to +** zValue, depending on the value of the type parameter. The zValue parameter +** should be NULL to cause the previous value to be freed via [sqlite3_free]; +** a non-NULL value will be copied into memory obtained from [sqlite3_malloc] +** prior to being used. The [sqlite3_win32_set_directory] interface returns +** [SQLITE_OK] to indicate success, [SQLITE_ERROR] if the type is unsupported, +** or [SQLITE_NOMEM] if memory could not be allocated. The value of the +** [sqlite3_data_directory] variable is intended to act as a replacement for +** the current directory on the sub-platforms of Win32 where that concept is +** not present, e.g. WinRT and UWP. The [sqlite3_win32_set_directory8] and +** [sqlite3_win32_set_directory16] interfaces behave exactly the same as the +** sqlite3_win32_set_directory interface except the string parameter must be +** UTF-8 or UTF-16, respectively. +*/ +SQLITE_API int sqlite3_win32_set_directory( + unsigned long type, /* Identifier for directory being set or reset */ + void *zValue /* New value for directory being set or reset */ +); +SQLITE_API int sqlite3_win32_set_directory8(unsigned long type, const char *zValue); +SQLITE_API int sqlite3_win32_set_directory16(unsigned long type, const void *zValue); + +/* +** CAPI3REF: Win32 Directory Types +** +** These macros are only available on Windows. They define the allowed values +** for the type argument to the [sqlite3_win32_set_directory] interface. +*/ +#define SQLITE_WIN32_DATA_DIRECTORY_TYPE 1 +#define SQLITE_WIN32_TEMP_DIRECTORY_TYPE 2 + +/* +** CAPI3REF: Test For Auto-Commit Mode +** KEYWORDS: {autocommit mode} +** METHOD: sqlite3 +** +** ^The sqlite3_get_autocommit() interface returns non-zero or +** zero if the given database connection is or is not in autocommit mode, +** respectively. ^Autocommit mode is on by default. +** ^Autocommit mode is disabled by a [BEGIN] statement. +** ^Autocommit mode is re-enabled by a [COMMIT] or [ROLLBACK]. +** +** If certain kinds of errors occur on a statement within a multi-statement +** transaction (errors including [SQLITE_FULL], [SQLITE_IOERR], +** [SQLITE_NOMEM], [SQLITE_BUSY], and [SQLITE_INTERRUPT]) then the +** transaction might be rolled back automatically. The only way to +** find out whether SQLite automatically rolled back the transaction after +** an error is to use this function. +** +** If another thread changes the autocommit status of the database +** connection while this routine is running, then the return value +** is undefined. +*/ +SQLITE_API int sqlite3_get_autocommit(sqlite3*); + +/* +** CAPI3REF: Find The Database Handle Of A Prepared Statement +** METHOD: sqlite3_stmt +** +** ^The sqlite3_db_handle interface returns the [database connection] handle +** to which a [prepared statement] belongs. ^The [database connection] +** returned by sqlite3_db_handle is the same [database connection] +** that was the first argument +** to the [sqlite3_prepare_v2()] call (or its variants) that was used to +** create the statement in the first place. +*/ +SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); + +/* +** CAPI3REF: Return The Schema Name For A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name +** for the N-th database on database connection D, or a NULL pointer of N is +** out of range. An N value of 0 means the main database file. An N of 1 is +** the "temp" schema. Larger values of N correspond to various ATTACH-ed +** databases. +** +** Space to hold the string that is returned by sqlite3_db_name() is managed +** by SQLite itself. The string might be deallocated by any operation that +** changes the schema, including [ATTACH] or [DETACH] or calls to +** [sqlite3_serialize()] or [sqlite3_deserialize()], even operations that +** occur on a different thread. Applications that need to +** remember the string long-term should make their own copy. Applications that +** are accessing the same database connection simultaneously on multiple +** threads should mutex-protect calls to this API and should make their own +** private copy of the result prior to releasing the mutex. +*/ +SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N); + +/* +** CAPI3REF: Return The Filename For A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_filename(D,N) interface returns a pointer to the filename +** associated with database N of connection D. +** ^If there is no attached database N on the database +** connection D, or if database N is a temporary or in-memory database, then +** this function will return either a NULL pointer or an empty string. +** +** ^The string value returned by this routine is owned and managed by +** the database connection. ^The value will be valid until the database N +** is [DETACH]-ed or until the database connection closes. +** +** ^The filename returned by this function is the output of the +** xFullPathname method of the [VFS]. ^In other words, the filename +** will be an absolute pathname, even if the filename used +** to open the database originally was a URI or relative pathname. +** +** If the filename pointer returned by this routine is not NULL, then it +** can be used as the filename input parameter to these routines: +** <ul> +** <li> [sqlite3_uri_parameter()] +** <li> [sqlite3_uri_boolean()] +** <li> [sqlite3_uri_int64()] +** <li> [sqlite3_filename_database()] +** <li> [sqlite3_filename_journal()] +** <li> [sqlite3_filename_wal()] +** </ul> +*/ +SQLITE_API sqlite3_filename sqlite3_db_filename(sqlite3 *db, const char *zDbName); + +/* +** CAPI3REF: Determine if a database is read-only +** METHOD: sqlite3 +** +** ^The sqlite3_db_readonly(D,N) interface returns 1 if the database N +** of connection D is read-only, 0 if it is read/write, or -1 if N is not +** the name of a database on connection D. +*/ +SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName); + +/* +** CAPI3REF: Determine the transaction state of a database +** METHOD: sqlite3 +** +** ^The sqlite3_txn_state(D,S) interface returns the current +** [transaction state] of schema S in database connection D. ^If S is NULL, +** then the highest transaction state of any schema on database connection D +** is returned. Transaction states are (in order of lowest to highest): +** <ol> +** <li value="0"> SQLITE_TXN_NONE +** <li value="1"> SQLITE_TXN_READ +** <li value="2"> SQLITE_TXN_WRITE +** </ol> +** ^If the S argument to sqlite3_txn_state(D,S) is not the name of +** a valid schema, then -1 is returned. +*/ +SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); + +/* +** CAPI3REF: Allowed return values from [sqlite3_txn_state()] +** KEYWORDS: {transaction state} +** +** These constants define the current transaction state of a database file. +** ^The [sqlite3_txn_state(D,S)] interface returns one of these +** constants in order to describe the transaction state of schema S +** in [database connection] D. +** +** <dl> +** [[SQLITE_TXN_NONE]] <dt>SQLITE_TXN_NONE</dt> +** <dd>The SQLITE_TXN_NONE state means that no transaction is currently +** pending.</dd> +** +** [[SQLITE_TXN_READ]] <dt>SQLITE_TXN_READ</dt> +** <dd>The SQLITE_TXN_READ state means that the database is currently +** in a read transaction. Content has been read from the database file +** but nothing in the database file has changed. The transaction state +** will advanced to SQLITE_TXN_WRITE if any changes occur and there are +** no other conflicting concurrent write transactions. The transaction +** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or +** [COMMIT].</dd> +** +** [[SQLITE_TXN_WRITE]] <dt>SQLITE_TXN_WRITE</dt> +** <dd>The SQLITE_TXN_WRITE state means that the database is currently +** in a write transaction. Content has been written to the database file +** but has not yet committed. The transaction state will change to +** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd> +*/ +#define SQLITE_TXN_NONE 0 +#define SQLITE_TXN_READ 1 +#define SQLITE_TXN_WRITE 2 + +/* +** CAPI3REF: Find the next prepared statement +** METHOD: sqlite3 +** +** ^This interface returns a pointer to the next [prepared statement] after +** pStmt associated with the [database connection] pDb. ^If pStmt is NULL +** then this interface returns a pointer to the first prepared statement +** associated with the database connection pDb. ^If no prepared statement +** satisfies the conditions of this routine, it returns NULL. +** +** The [database connection] pointer D in a call to +** [sqlite3_next_stmt(D,S)] must refer to an open database +** connection and in particular must not be a NULL pointer. +*/ +SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Commit And Rollback Notification Callbacks +** METHOD: sqlite3 +** +** ^The sqlite3_commit_hook() interface registers a callback +** function to be invoked whenever a transaction is [COMMIT | committed]. +** ^Any callback set by a previous call to sqlite3_commit_hook() +** for the same database connection is overridden. +** ^The sqlite3_rollback_hook() interface registers a callback +** function to be invoked whenever a transaction is [ROLLBACK | rolled back]. +** ^Any callback set by a previous call to sqlite3_rollback_hook() +** for the same database connection is overridden. +** ^The pArg argument is passed through to the callback. +** ^If the callback on a commit hook function returns non-zero, +** then the commit is converted into a rollback. +** +** ^The sqlite3_commit_hook(D,C,P) and sqlite3_rollback_hook(D,C,P) functions +** return the P argument from the previous call of the same function +** on the same [database connection] D, or NULL for +** the first call for each function on D. +** +** The commit and rollback hook callbacks are not reentrant. +** The callback implementation must not do anything that will modify +** the database connection that invoked the callback. Any actions +** to modify the database connection must be deferred until after the +** completion of the [sqlite3_step()] call that triggered the commit +** or rollback hook in the first place. +** Note that running any other SQL statements, including SELECT statements, +** or merely calling [sqlite3_prepare_v2()] and [sqlite3_step()] will modify +** the database connections for the meaning of "modify" in this paragraph. +** +** ^Registering a NULL function disables the callback. +** +** ^When the commit hook callback routine returns zero, the [COMMIT] +** operation is allowed to continue normally. ^If the commit hook +** returns non-zero, then the [COMMIT] is converted into a [ROLLBACK]. +** ^The rollback hook is invoked on a rollback that results from a commit +** hook returning non-zero, just as it would be with any other rollback. +** +** ^For the purposes of this API, a transaction is said to have been +** rolled back if an explicit "ROLLBACK" statement is executed, or +** an error or constraint causes an implicit rollback to occur. +** ^The rollback callback is not invoked if a transaction is +** automatically rolled back because the database connection is closed. +** +** See also the [sqlite3_update_hook()] interface. +*/ +SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); +SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); + +/* +** CAPI3REF: Autovacuum Compaction Amount Callback +** METHOD: sqlite3 +** +** ^The sqlite3_autovacuum_pages(D,C,P,X) interface registers a callback +** function C that is invoked prior to each autovacuum of the database +** file. ^The callback is passed a copy of the generic data pointer (P), +** the schema-name of the attached database that is being autovacuumed, +** the size of the database file in pages, the number of free pages, +** and the number of bytes per page, respectively. The callback should +** return the number of free pages that should be removed by the +** autovacuum. ^If the callback returns zero, then no autovacuum happens. +** ^If the value returned is greater than or equal to the number of +** free pages, then a complete autovacuum happens. +** +** <p>^If there are multiple ATTACH-ed database files that are being +** modified as part of a transaction commit, then the autovacuum pages +** callback is invoked separately for each file. +** +** <p><b>The callback is not reentrant.</b> The callback function should +** not attempt to invoke any other SQLite interface. If it does, bad +** things may happen, including segmentation faults and corrupt database +** files. The callback function should be a simple function that +** does some arithmetic on its input parameters and returns a result. +** +** ^The X parameter to sqlite3_autovacuum_pages(D,C,P,X) is an optional +** destructor for the P parameter. ^If X is not NULL, then X(P) is +** invoked whenever the database connection closes or when the callback +** is overwritten by another invocation of sqlite3_autovacuum_pages(). +** +** <p>^There is only one autovacuum pages callback per database connection. +** ^Each call to the sqlite3_autovacuum_pages() interface overrides all +** previous invocations for that database connection. ^If the callback +** argument (C) to sqlite3_autovacuum_pages(D,C,P,X) is a NULL pointer, +** then the autovacuum steps callback is cancelled. The return value +** from sqlite3_autovacuum_pages() is normally SQLITE_OK, but might +** be some other error code if something goes wrong. The current +** implementation will only return SQLITE_OK or SQLITE_MISUSE, but other +** return codes might be added in future releases. +** +** <p>If no autovacuum pages callback is specified (the usual case) or +** a NULL pointer is provided for the callback, +** then the default behavior is to vacuum all free pages. So, in other +** words, the default behavior is the same as if the callback function +** were something like this: +** +** <blockquote><pre> +** &nbsp; unsigned int demonstration_autovac_pages_callback( +** &nbsp; void *pClientData, +** &nbsp; const char *zSchema, +** &nbsp; unsigned int nDbPage, +** &nbsp; unsigned int nFreePage, +** &nbsp; unsigned int nBytePerPage +** &nbsp; ){ +** &nbsp; return nFreePage; +** &nbsp; } +** </pre></blockquote> +*/ +SQLITE_API int sqlite3_autovacuum_pages( + sqlite3 *db, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, + void(*)(void*) +); + + +/* +** CAPI3REF: Data Change Notification Callbacks +** METHOD: sqlite3 +** +** ^The sqlite3_update_hook() interface registers a callback function +** with the [database connection] identified by the first argument +** to be invoked whenever a row is updated, inserted or deleted in +** a [rowid table]. +** ^Any callback set by a previous call to this function +** for the same database connection is overridden. +** +** ^The second argument is a pointer to the function to invoke when a +** row is updated, inserted or deleted in a rowid table. +** ^The first argument to the callback is a copy of the third argument +** to sqlite3_update_hook(). +** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], +** or [SQLITE_UPDATE], depending on the operation that caused the callback +** to be invoked. +** ^The third and fourth arguments to the callback contain pointers to the +** database and table name containing the affected row. +** ^The final callback parameter is the [rowid] of the row. +** ^In the case of an update, this is the [rowid] after the update takes place. +** +** ^(The update hook is not invoked when internal system tables are +** modified (i.e. sqlite_sequence).)^ +** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified. +** +** ^In the current implementation, the update hook +** is not invoked when conflicting rows are deleted because of an +** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook +** invoked when rows are deleted using the [truncate optimization]. +** The exceptions defined in this paragraph might change in a future +** release of SQLite. +** +** The update hook implementation must not do anything that will modify +** the database connection that invoked the update hook. Any actions +** to modify the database connection must be deferred until after the +** completion of the [sqlite3_step()] call that triggered the update hook. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** ^The sqlite3_update_hook(D,C,P) function +** returns the P argument from the previous call +** on the same [database connection] D, or NULL for +** the first call on D. +** +** See also the [sqlite3_commit_hook()], [sqlite3_rollback_hook()], +** and [sqlite3_preupdate_hook()] interfaces. +*/ +SQLITE_API void *sqlite3_update_hook( + sqlite3*, + void(*)(void *,int ,char const *,char const *,sqlite3_int64), + void* +); + +/* +** CAPI3REF: Enable Or Disable Shared Pager Cache +** +** ^(This routine enables or disables the sharing of the database cache +** and schema data structures between [database connection | connections] +** to the same database. Sharing is enabled if the argument is true +** and disabled if the argument is false.)^ +** +** This interface is omitted if SQLite is compiled with +** [-DSQLITE_OMIT_SHARED_CACHE]. The [-DSQLITE_OMIT_SHARED_CACHE] +** compile-time option is recommended because the +** [use of shared cache mode is discouraged]. +** +** ^Cache sharing is enabled and disabled for an entire process. +** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). +** In prior versions of SQLite, +** sharing was enabled or disabled for each thread separately. +** +** ^(The cache sharing mode set by this interface effects all subsequent +** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()]. +** Existing database connections continue to use the sharing mode +** that was in effect at the time they were opened.)^ +** +** ^(This routine returns [SQLITE_OK] if shared cache was enabled or disabled +** successfully. An [error code] is returned otherwise.)^ +** +** ^Shared cache is disabled by default. It is recommended that it stay +** that way. In other words, do not use this routine. This interface +** continues to be provided for historical compatibility, but its use is +** discouraged. Any use of shared cache is discouraged. If shared cache +** must be used, it is recommended that shared cache only be enabled for +** individual database connections using the [sqlite3_open_v2()] interface +** with the [SQLITE_OPEN_SHAREDCACHE] flag. +** +** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0 +** and will always return SQLITE_MISUSE. On those systems, +** shared cache mode should be enabled per-database connection via +** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE]. +** +** This interface is threadsafe on processors where writing a +** 32-bit integer is atomic. +** +** See Also: [SQLite Shared-Cache Mode] +*/ +SQLITE_API int sqlite3_enable_shared_cache(int); + +/* +** CAPI3REF: Attempt To Free Heap Memory +** +** ^The sqlite3_release_memory() interface attempts to free N bytes +** of heap memory by deallocating non-essential memory allocations +** held by the database library. Memory used to cache database +** pages to improve performance is an example of non-essential memory. +** ^sqlite3_release_memory() returns the number of bytes actually freed, +** which might be more or less than the amount requested. +** ^The sqlite3_release_memory() routine is a no-op returning zero +** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT]. +** +** See also: [sqlite3_db_release_memory()] +*/ +SQLITE_API int sqlite3_release_memory(int); + +/* +** CAPI3REF: Free Memory Used By A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_release_memory(D) interface attempts to free as much heap +** memory as possible from database connection D. Unlike the +** [sqlite3_release_memory()] interface, this interface is in effect even +** when the [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is +** omitted. +** +** See also: [sqlite3_release_memory()] +*/ +SQLITE_API int sqlite3_db_release_memory(sqlite3*); + +/* +** CAPI3REF: Impose A Limit On Heap Size +** +** These interfaces impose limits on the amount of heap memory that will be +** by all database connections within a single process. +** +** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the +** soft limit on the amount of heap memory that may be allocated by SQLite. +** ^SQLite strives to keep heap memory utilization below the soft heap +** limit by reducing the number of pages held in the page cache +** as heap memory usages approaches the limit. +** ^The soft heap limit is "soft" because even though SQLite strives to stay +** below the limit, it will exceed the limit rather than generate +** an [SQLITE_NOMEM] error. In other words, the soft heap limit +** is advisory only. +** +** ^The sqlite3_hard_heap_limit64(N) interface sets a hard upper bound of +** N bytes on the amount of memory that will be allocated. ^The +** sqlite3_hard_heap_limit64(N) interface is similar to +** sqlite3_soft_heap_limit64(N) except that memory allocations will fail +** when the hard heap limit is reached. +** +** ^The return value from both sqlite3_soft_heap_limit64() and +** sqlite3_hard_heap_limit64() is the size of +** the heap limit prior to the call, or negative in the case of an +** error. ^If the argument N is negative +** then no change is made to the heap limit. Hence, the current +** size of heap limits can be determined by invoking +** sqlite3_soft_heap_limit64(-1) or sqlite3_hard_heap_limit(-1). +** +** ^Setting the heap limits to zero disables the heap limiter mechanism. +** +** ^The soft heap limit may not be greater than the hard heap limit. +** ^If the hard heap limit is enabled and if sqlite3_soft_heap_limit(N) +** is invoked with a value of N that is greater than the hard heap limit, +** the soft heap limit is set to the value of the hard heap limit. +** ^The soft heap limit is automatically enabled whenever the hard heap +** limit is enabled. ^When sqlite3_hard_heap_limit64(N) is invoked and +** the soft heap limit is outside the range of 1..N, then the soft heap +** limit is set to N. ^Invoking sqlite3_soft_heap_limit64(0) when the +** hard heap limit is enabled makes the soft heap limit equal to the +** hard heap limit. +** +** The memory allocation limits can also be adjusted using +** [PRAGMA soft_heap_limit] and [PRAGMA hard_heap_limit]. +** +** ^(The heap limits are not enforced in the current implementation +** if one or more of following conditions are true: +** +** <ul> +** <li> The limit value is set to zero. +** <li> Memory accounting is disabled using a combination of the +** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and +** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option. +** <li> An alternative page cache implementation is specified using +** [sqlite3_config]([SQLITE_CONFIG_PCACHE2],...). +** <li> The page cache allocates from its own memory pool supplied +** by [sqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than +** from the heap. +** </ul>)^ +** +** The circumstances under which SQLite will enforce the heap limits may +** changes in future releases of SQLite. +*/ +SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); +SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N); + +/* +** CAPI3REF: Deprecated Soft Heap Limit Interface +** DEPRECATED +** +** This is a deprecated version of the [sqlite3_soft_heap_limit64()] +** interface. This routine is provided for historical compatibility +** only. All new applications should use the +** [sqlite3_soft_heap_limit64()] interface rather than this one. +*/ +SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); + + +/* +** CAPI3REF: Extract Metadata About A Column Of A Table +** METHOD: sqlite3 +** +** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns +** information about column C of table T in database D +** on [database connection] X.)^ ^The sqlite3_table_column_metadata() +** interface returns SQLITE_OK and fills in the non-NULL pointers in +** the final five arguments with appropriate values if the specified +** column exists. ^The sqlite3_table_column_metadata() interface returns +** SQLITE_ERROR if the specified column does not exist. +** ^If the column-name parameter to sqlite3_table_column_metadata() is a +** NULL pointer, then this routine simply checks for the existence of the +** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it +** does not. If the table name parameter T in a call to +** sqlite3_table_column_metadata(X,D,T,C,...) is NULL then the result is +** undefined behavior. +** +** ^The column is identified by the second, third and fourth parameters to +** this function. ^(The second parameter is either the name of the database +** (i.e. "main", "temp", or an attached database) containing the specified +** table or NULL.)^ ^If it is NULL, then all attached databases are searched +** for the table using the same algorithm used by the database engine to +** resolve unqualified table references. +** +** ^The third and fourth parameters to this function are the table and column +** name of the desired column, respectively. +** +** ^Metadata is returned by writing to the memory locations passed as the 5th +** and subsequent parameters to this function. ^Any of these arguments may be +** NULL, in which case the corresponding element of metadata is omitted. +** +** ^(<blockquote> +** <table border="1"> +** <tr><th> Parameter <th> Output<br>Type <th> Description +** +** <tr><td> 5th <td> const char* <td> Data type +** <tr><td> 6th <td> const char* <td> Name of default collation sequence +** <tr><td> 7th <td> int <td> True if column has a NOT NULL constraint +** <tr><td> 8th <td> int <td> True if column is part of the PRIMARY KEY +** <tr><td> 9th <td> int <td> True if column is [AUTOINCREMENT] +** </table> +** </blockquote>)^ +** +** ^The memory pointed to by the character pointers returned for the +** declaration type and collation sequence is valid until the next +** call to any SQLite API function. +** +** ^If the specified table is actually a view, an [error code] is returned. +** +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** is not a [WITHOUT ROWID] table and an +** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output +** parameters are set for the explicitly declared column. ^(If there is no +** [INTEGER PRIMARY KEY] column, then the outputs +** for the [rowid] are set as follows: +** +** <pre> +** data type: "INTEGER" +** collation sequence: "BINARY" +** not null: 0 +** primary key: 1 +** auto increment: 0 +** </pre>)^ +** +** ^This function causes all database schemas to be read from disk and +** parsed, if that has not already been done, and returns an error if +** any errors are encountered while loading the schema. +*/ +SQLITE_API int sqlite3_table_column_metadata( + sqlite3 *db, /* Connection handle */ + const char *zDbName, /* Database name or NULL */ + const char *zTableName, /* Table name */ + const char *zColumnName, /* Column name */ + char const **pzDataType, /* OUTPUT: Declared data type */ + char const **pzCollSeq, /* OUTPUT: Collation sequence name */ + int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */ + int *pPrimaryKey, /* OUTPUT: True if column part of PK */ + int *pAutoinc /* OUTPUT: True if column is auto-increment */ +); + +/* +** CAPI3REF: Load An Extension +** METHOD: sqlite3 +** +** ^This interface loads an SQLite extension library from the named file. +** +** ^The sqlite3_load_extension() interface attempts to load an +** [SQLite extension] library contained in the file zFile. If +** the file cannot be loaded directly, attempts are made to load +** with various operating-system specific extensions added. +** So for example, if "samplelib" cannot be loaded, then names like +** "samplelib.so" or "samplelib.dylib" or "samplelib.dll" might +** be tried also. +** +** ^The entry point is zProc. +** ^(zProc may be 0, in which case SQLite will try to come up with an +** entry point name on its own. It first tries "sqlite3_extension_init". +** If that does not work, it constructs a name "sqlite3_X_init" where the +** X is consists of the lower-case equivalent of all ASCII alphabetic +** characters in the filename from the last "/" to the first following +** "." and omitting any initial "lib".)^ +** ^The sqlite3_load_extension() interface returns +** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. +** ^If an error occurs and pzErrMsg is not 0, then the +** [sqlite3_load_extension()] interface shall attempt to +** fill *pzErrMsg with error message text stored in memory +** obtained from [sqlite3_malloc()]. The calling function +** should free this memory by calling [sqlite3_free()]. +** +** ^Extension loading must be enabled using +** [sqlite3_enable_load_extension()] or +** [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],1,NULL) +** prior to calling this API, +** otherwise an error will be returned. +** +** <b>Security warning:</b> It is recommended that the +** [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method be used to enable only this +** interface. The use of the [sqlite3_enable_load_extension()] interface +** should be avoided. This will keep the SQL function [load_extension()] +** disabled and prevent SQL injections from giving attackers +** access to extension loading capabilities. +** +** See also the [load_extension() SQL function]. +*/ +SQLITE_API int sqlite3_load_extension( + sqlite3 *db, /* Load the extension into this database connection */ + const char *zFile, /* Name of the shared library containing extension */ + const char *zProc, /* Entry point. Derived from zFile if 0 */ + char **pzErrMsg /* Put error message here if not 0 */ +); + +/* +** CAPI3REF: Enable Or Disable Extension Loading +** METHOD: sqlite3 +** +** ^So as not to open security holes in older applications that are +** unprepared to deal with [extension loading], and as a means of disabling +** [extension loading] while evaluating user-entered SQL, the following API +** is provided to turn the [sqlite3_load_extension()] mechanism on and off. +** +** ^Extension loading is off by default. +** ^Call the sqlite3_enable_load_extension() routine with onoff==1 +** to turn extension loading on and call it with onoff==0 to turn +** it back off again. +** +** ^This interface enables or disables both the C-API +** [sqlite3_load_extension()] and the SQL function [load_extension()]. +** ^(Use [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],..) +** to enable or disable only the C-API.)^ +** +** <b>Security warning:</b> It is recommended that extension loading +** be enabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method +** rather than this interface, so the [load_extension()] SQL function +** remains disabled. This will prevent SQL injections from giving attackers +** access to extension loading capabilities. +*/ +SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); + +/* +** CAPI3REF: Automatically Load Statically Linked Extensions +** +** ^This interface causes the xEntryPoint() function to be invoked for +** each new [database connection] that is created. The idea here is that +** xEntryPoint() is the entry point for a statically linked [SQLite extension] +** that is to be automatically loaded into all new database connections. +** +** ^(Even though the function prototype shows that xEntryPoint() takes +** no arguments and returns void, SQLite invokes xEntryPoint() with three +** arguments and expects an integer result as if the signature of the +** entry point where as follows: +** +** <blockquote><pre> +** &nbsp; int xEntryPoint( +** &nbsp; sqlite3 *db, +** &nbsp; const char **pzErrMsg, +** &nbsp; const struct sqlite3_api_routines *pThunk +** &nbsp; ); +** </pre></blockquote>)^ +** +** If the xEntryPoint routine encounters an error, it should make *pzErrMsg +** point to an appropriate error message (obtained from [sqlite3_mprintf()]) +** and return an appropriate [error code]. ^SQLite ensures that *pzErrMsg +** is NULL before calling the xEntryPoint(). ^SQLite will invoke +** [sqlite3_free()] on *pzErrMsg after xEntryPoint() returns. ^If any +** xEntryPoint() returns an error, the [sqlite3_open()], [sqlite3_open16()], +** or [sqlite3_open_v2()] call that provoked the xEntryPoint() will fail. +** +** ^Calling sqlite3_auto_extension(X) with an entry point X that is already +** on the list of automatic extensions is a harmless no-op. ^No entry point +** will be called more than once for each database connection that is opened. +** +** See also: [sqlite3_reset_auto_extension()] +** and [sqlite3_cancel_auto_extension()] +*/ +SQLITE_API int sqlite3_auto_extension(void(*xEntryPoint)(void)); + +/* +** CAPI3REF: Cancel Automatic Extension Loading +** +** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the +** initialization routine X that was registered using a prior call to +** [sqlite3_auto_extension(X)]. ^The [sqlite3_cancel_auto_extension(X)] +** routine returns 1 if initialization routine X was successfully +** unregistered and it returns 0 if X was not on the list of initialization +** routines. +*/ +SQLITE_API int sqlite3_cancel_auto_extension(void(*xEntryPoint)(void)); + +/* +** CAPI3REF: Reset Automatic Extension Loading +** +** ^This interface disables all automatic extensions previously +** registered using [sqlite3_auto_extension()]. +*/ +SQLITE_API void sqlite3_reset_auto_extension(void); + +/* +** Structures used by the virtual table interface +*/ +typedef struct sqlite3_vtab sqlite3_vtab; +typedef struct sqlite3_index_info sqlite3_index_info; +typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; +typedef struct sqlite3_module sqlite3_module; + +/* +** CAPI3REF: Virtual Table Object +** KEYWORDS: sqlite3_module {virtual table module} +** +** This structure, sometimes called a "virtual table module", +** defines the implementation of a [virtual table]. +** This structure consists mostly of methods for the module. +** +** ^A virtual table module is created by filling in a persistent +** instance of this structure and passing a pointer to that instance +** to [sqlite3_create_module()] or [sqlite3_create_module_v2()]. +** ^The registration remains valid until it is replaced by a different +** module or until the [database connection] closes. The content +** of this structure must not change while it is registered with +** any database connection. +*/ +struct sqlite3_module { + int iVersion; + int (*xCreate)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xConnect)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*); + int (*xDisconnect)(sqlite3_vtab *pVTab); + int (*xDestroy)(sqlite3_vtab *pVTab); + int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor); + int (*xClose)(sqlite3_vtab_cursor*); + int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, + int argc, sqlite3_value **argv); + int (*xNext)(sqlite3_vtab_cursor*); + int (*xEof)(sqlite3_vtab_cursor*); + int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int); + int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid); + int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *); + int (*xBegin)(sqlite3_vtab *pVTab); + int (*xSync)(sqlite3_vtab *pVTab); + int (*xCommit)(sqlite3_vtab *pVTab); + int (*xRollback)(sqlite3_vtab *pVTab); + int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg); + int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); + /* The methods above are in version 1 of the sqlite_module object. Those + ** below are for version 2 and greater. */ + int (*xSavepoint)(sqlite3_vtab *pVTab, int); + int (*xRelease)(sqlite3_vtab *pVTab, int); + int (*xRollbackTo)(sqlite3_vtab *pVTab, int); + /* The methods above are in versions 1 and 2 of the sqlite_module object. + ** Those below are for version 3 and greater. */ + int (*xShadowName)(const char*); + /* The methods above are in versions 1 through 3 of the sqlite_module object. + ** Those below are for version 4 and greater. */ + int (*xIntegrity)(sqlite3_vtab *pVTab, char**); +}; + +/* +** CAPI3REF: Virtual Table Indexing Information +** KEYWORDS: sqlite3_index_info +** +** The sqlite3_index_info structure and its substructures is used as part +** of the [virtual table] interface to +** pass information into and receive the reply from the [xBestIndex] +** method of a [virtual table module]. The fields under **Inputs** are the +** inputs to xBestIndex and are read-only. xBestIndex inserts its +** results into the **Outputs** fields. +** +** ^(The aConstraint[] array records WHERE clause constraints of the form: +** +** <blockquote>column OP expr</blockquote> +** +** where OP is =, &lt;, &lt;=, &gt;, or &gt;=.)^ ^(The particular operator is +** stored in aConstraint[].op using one of the +** [SQLITE_INDEX_CONSTRAINT_EQ | SQLITE_INDEX_CONSTRAINT_ values].)^ +** ^(The index of the column is stored in +** aConstraint[].iColumn.)^ ^(aConstraint[].usable is TRUE if the +** expr on the right-hand side can be evaluated (and thus the constraint +** is usable) and false if it cannot.)^ +** +** ^The optimizer automatically inverts terms of the form "expr OP column" +** and makes other simplifications to the WHERE clause in an attempt to +** get as many WHERE clause terms into the form shown above as possible. +** ^The aConstraint[] array only reports WHERE clause terms that are +** relevant to the particular virtual table being queried. +** +** ^Information about the ORDER BY clause is stored in aOrderBy[]. +** ^Each term of aOrderBy records a column of the ORDER BY clause. +** +** The colUsed field indicates which columns of the virtual table may be +** required by the current scan. Virtual table columns are numbered from +** zero in the order in which they appear within the CREATE TABLE statement +** passed to sqlite3_declare_vtab(). For the first 63 columns (columns 0-62), +** the corresponding bit is set within the colUsed mask if the column may be +** required by SQLite. If the table has at least 64 columns and any column +** to the right of the first 63 is required, then bit 63 of colUsed is also +** set. In other words, column iCol may be required if the expression +** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to +** non-zero. +** +** The [xBestIndex] method must fill aConstraintUsage[] with information +** about what parameters to pass to xFilter. ^If argvIndex>0 then +** the right-hand side of the corresponding aConstraint[] is evaluated +** and becomes the argvIndex-th entry in argv. ^(If aConstraintUsage[].omit +** is true, then the constraint is assumed to be fully handled by the +** virtual table and might not be checked again by the byte code.)^ ^(The +** aConstraintUsage[].omit flag is an optimization hint. When the omit flag +** is left in its default setting of false, the constraint will always be +** checked separately in byte code. If the omit flag is change to true, then +** the constraint may or may not be checked in byte code. In other words, +** when the omit flag is true there is no guarantee that the constraint will +** not be checked again using byte code.)^ +** +** ^The idxNum and idxStr values are recorded and passed into the +** [xFilter] method. +** ^[sqlite3_free()] is used to free idxStr if and only if +** needToFreeIdxStr is true. +** +** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in +** the correct order to satisfy the ORDER BY clause so that no separate +** sorting step is required. +** +** ^The estimatedCost value is an estimate of the cost of a particular +** strategy. A cost of N indicates that the cost of the strategy is similar +** to a linear scan of an SQLite table with N rows. A cost of log(N) +** indicates that the expense of the operation is similar to that of a +** binary search on a unique indexed field of an SQLite table with N rows. +** +** ^The estimatedRows value is an estimate of the number of rows that +** will be returned by the strategy. +** +** The xBestIndex method may optionally populate the idxFlags field with a +** mask of SQLITE_INDEX_SCAN_* flags. Currently there is only one such flag - +** SQLITE_INDEX_SCAN_UNIQUE. If the xBestIndex method sets this flag, SQLite +** assumes that the strategy may visit at most one row. +** +** Additionally, if xBestIndex sets the SQLITE_INDEX_SCAN_UNIQUE flag, then +** SQLite also assumes that if a call to the xUpdate() method is made as +** part of the same statement to delete or update a virtual table row and the +** implementation returns SQLITE_CONSTRAINT, then there is no need to rollback +** any database changes. In other words, if the xUpdate() returns +** SQLITE_CONSTRAINT, the database contents must be exactly as they were +** before xUpdate was called. By contrast, if SQLITE_INDEX_SCAN_UNIQUE is not +** set and xUpdate returns SQLITE_CONSTRAINT, any database changes made by +** the xUpdate method are automatically rolled back by SQLite. +** +** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info +** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). +** If a virtual table extension is +** used with an SQLite version earlier than 3.8.2, the results of attempting +** to read or write the estimatedRows field are undefined (but are likely +** to include crashing the application). The estimatedRows field should +** therefore only be used if [sqlite3_libversion_number()] returns a +** value greater than or equal to 3008002. Similarly, the idxFlags field +** was added for [version 3.9.0] ([dateof:3.9.0]). +** It may therefore only be used if +** sqlite3_libversion_number() returns a value greater than or equal to +** 3009000. +*/ +struct sqlite3_index_info { + /* Inputs */ + int nConstraint; /* Number of entries in aConstraint */ + struct sqlite3_index_constraint { + int iColumn; /* Column constrained. -1 for ROWID */ + unsigned char op; /* Constraint operator */ + unsigned char usable; /* True if this constraint is usable */ + int iTermOffset; /* Used internally - xBestIndex should ignore */ + } *aConstraint; /* Table of WHERE clause constraints */ + int nOrderBy; /* Number of terms in the ORDER BY clause */ + struct sqlite3_index_orderby { + int iColumn; /* Column number */ + unsigned char desc; /* True for DESC. False for ASC. */ + } *aOrderBy; /* The ORDER BY clause */ + /* Outputs */ + struct sqlite3_index_constraint_usage { + int argvIndex; /* if >0, constraint is part of argv to xFilter */ + unsigned char omit; /* Do not code a test for this constraint */ + } *aConstraintUsage; + int idxNum; /* Number used to identify the index */ + char *idxStr; /* String, possibly obtained from sqlite3_malloc */ + int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ + int orderByConsumed; /* True if output is already ordered */ + double estimatedCost; /* Estimated cost of using this index */ + /* Fields below are only available in SQLite 3.8.2 and later */ + sqlite3_int64 estimatedRows; /* Estimated number of rows returned */ + /* Fields below are only available in SQLite 3.9.0 and later */ + int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */ + /* Fields below are only available in SQLite 3.10.0 and later */ + sqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */ +}; + +/* +** CAPI3REF: Virtual Table Scan Flags +** +** Virtual table implementations are allowed to set the +** [sqlite3_index_info].idxFlags field to some combination of +** these bits. +*/ +#define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */ + +/* +** CAPI3REF: Virtual Table Constraint Operator Codes +** +** These macros define the allowed values for the +** [sqlite3_index_info].aConstraint[].op field. Each value represents +** an operator that is part of a constraint term in the WHERE clause of +** a query that uses a [virtual table]. +** +** ^The left-hand operand of the operator is given by the corresponding +** aConstraint[].iColumn field. ^An iColumn of -1 indicates the left-hand +** operand is the rowid. +** The SQLITE_INDEX_CONSTRAINT_LIMIT and SQLITE_INDEX_CONSTRAINT_OFFSET +** operators have no left-hand operand, and so for those operators the +** corresponding aConstraint[].iColumn is meaningless and should not be +** used. +** +** All operator values from SQLITE_INDEX_CONSTRAINT_FUNCTION through +** value 255 are reserved to represent functions that are overloaded +** by the [xFindFunction|xFindFunction method] of the virtual table +** implementation. +** +** The right-hand operands for each constraint might be accessible using +** the [sqlite3_vtab_rhs_value()] interface. Usually the right-hand +** operand is only available if it appears as a single constant literal +** in the input SQL. If the right-hand operand is another column or an +** expression (even a constant expression) or a parameter, then the +** sqlite3_vtab_rhs_value() probably will not be able to extract it. +** ^The SQLITE_INDEX_CONSTRAINT_ISNULL and +** SQLITE_INDEX_CONSTRAINT_ISNOTNULL operators have no right-hand operand +** and hence calls to sqlite3_vtab_rhs_value() for those operators will +** always return SQLITE_NOTFOUND. +** +** The collating sequence to be used for comparison can be found using +** the [sqlite3_vtab_collation()] interface. For most real-world virtual +** tables, the collating sequence of constraints does not matter (for example +** because the constraints are numeric) and so the sqlite3_vtab_collation() +** interface is not commonly needed. +*/ +#define SQLITE_INDEX_CONSTRAINT_EQ 2 +#define SQLITE_INDEX_CONSTRAINT_GT 4 +#define SQLITE_INDEX_CONSTRAINT_LE 8 +#define SQLITE_INDEX_CONSTRAINT_LT 16 +#define SQLITE_INDEX_CONSTRAINT_GE 32 +#define SQLITE_INDEX_CONSTRAINT_MATCH 64 +#define SQLITE_INDEX_CONSTRAINT_LIKE 65 +#define SQLITE_INDEX_CONSTRAINT_GLOB 66 +#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 +#define SQLITE_INDEX_CONSTRAINT_NE 68 +#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 +#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 +#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 +#define SQLITE_INDEX_CONSTRAINT_IS 72 +#define SQLITE_INDEX_CONSTRAINT_LIMIT 73 +#define SQLITE_INDEX_CONSTRAINT_OFFSET 74 +#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 + +/* +** CAPI3REF: Register A Virtual Table Implementation +** METHOD: sqlite3 +** +** ^These routines are used to register a new [virtual table module] name. +** ^Module names must be registered before +** creating a new [virtual table] using the module and before using a +** preexisting [virtual table] for the module. +** +** ^The module name is registered on the [database connection] specified +** by the first parameter. ^The name of the module is given by the +** second parameter. ^The third parameter is a pointer to +** the implementation of the [virtual table module]. ^The fourth +** parameter is an arbitrary client data pointer that is passed through +** into the [xCreate] and [xConnect] methods of the virtual table module +** when a new virtual table is be being created or reinitialized. +** +** ^The sqlite3_create_module_v2() interface has a fifth parameter which +** is a pointer to a destructor for the pClientData. ^SQLite will +** invoke the destructor function (if it is not NULL) when SQLite +** no longer needs the pClientData pointer. ^The destructor will also +** be invoked if the call to sqlite3_create_module_v2() fails. +** ^The sqlite3_create_module() +** interface is equivalent to sqlite3_create_module_v2() with a NULL +** destructor. +** +** ^If the third parameter (the pointer to the sqlite3_module object) is +** NULL then no new module is created and any existing modules with the +** same name are dropped. +** +** See also: [sqlite3_drop_modules()] +*/ +SQLITE_API int sqlite3_create_module( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *p, /* Methods for the module */ + void *pClientData /* Client data for xCreate/xConnect */ +); +SQLITE_API int sqlite3_create_module_v2( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *p, /* Methods for the module */ + void *pClientData, /* Client data for xCreate/xConnect */ + void(*xDestroy)(void*) /* Module destructor function */ +); + +/* +** CAPI3REF: Remove Unnecessary Virtual Table Implementations +** METHOD: sqlite3 +** +** ^The sqlite3_drop_modules(D,L) interface removes all virtual +** table modules from database connection D except those named on list L. +** The L parameter must be either NULL or a pointer to an array of pointers +** to strings where the array is terminated by a single NULL pointer. +** ^If the L parameter is NULL, then all virtual table modules are removed. +** +** See also: [sqlite3_create_module()] +*/ +SQLITE_API int sqlite3_drop_modules( + sqlite3 *db, /* Remove modules from this connection */ + const char **azKeep /* Except, do not remove the ones named here */ +); + +/* +** CAPI3REF: Virtual Table Instance Object +** KEYWORDS: sqlite3_vtab +** +** Every [virtual table module] implementation uses a subclass +** of this object to describe a particular instance +** of the [virtual table]. Each subclass will +** be tailored to the specific needs of the module implementation. +** The purpose of this superclass is to define certain fields that are +** common to all module implementations. +** +** ^Virtual tables methods can set an error message by assigning a +** string obtained from [sqlite3_mprintf()] to zErrMsg. The method should +** take care that any prior string is freed by a call to [sqlite3_free()] +** prior to assigning a new string to zErrMsg. ^After the error message +** is delivered up to the client application, the string will be automatically +** freed by sqlite3_free() and the zErrMsg field will be zeroed. +*/ +struct sqlite3_vtab { + const sqlite3_module *pModule; /* The module for this virtual table */ + int nRef; /* Number of open cursors */ + char *zErrMsg; /* Error message from sqlite3_mprintf() */ + /* Virtual table implementations will typically add additional fields */ +}; + +/* +** CAPI3REF: Virtual Table Cursor Object +** KEYWORDS: sqlite3_vtab_cursor {virtual table cursor} +** +** Every [virtual table module] implementation uses a subclass of the +** following structure to describe cursors that point into the +** [virtual table] and are used +** to loop through the virtual table. Cursors are created using the +** [sqlite3_module.xOpen | xOpen] method of the module and are destroyed +** by the [sqlite3_module.xClose | xClose] method. Cursors are used +** by the [xFilter], [xNext], [xEof], [xColumn], and [xRowid] methods +** of the module. Each module implementation will define +** the content of a cursor structure to suit its own needs. +** +** This superclass exists in order to define fields of the cursor that +** are common to all implementations. +*/ +struct sqlite3_vtab_cursor { + sqlite3_vtab *pVtab; /* Virtual table of this cursor */ + /* Virtual table implementations will typically add additional fields */ +}; + +/* +** CAPI3REF: Declare The Schema Of A Virtual Table +** +** ^The [xCreate] and [xConnect] methods of a +** [virtual table module] call this interface +** to declare the format (the names and datatypes of the columns) of +** the virtual tables they implement. +*/ +SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL); + +/* +** CAPI3REF: Overload A Function For A Virtual Table +** METHOD: sqlite3 +** +** ^(Virtual tables can provide alternative implementations of functions +** using the [xFindFunction] method of the [virtual table module]. +** But global versions of those functions +** must exist in order to be overloaded.)^ +** +** ^(This API makes sure a global version of a function with a particular +** name and number of parameters exists. If no such function exists +** before this API is called, a new function is created.)^ ^The implementation +** of the new function always causes an exception to be thrown. So +** the new function is not good for anything by itself. Its only +** purpose is to be a placeholder function that can be overloaded +** by a [virtual table]. +*/ +SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); + +/* +** CAPI3REF: A Handle To An Open BLOB +** KEYWORDS: {BLOB handle} {BLOB handles} +** +** An instance of this object represents an open BLOB on which +** [sqlite3_blob_open | incremental BLOB I/O] can be performed. +** ^Objects of this type are created by [sqlite3_blob_open()] +** and destroyed by [sqlite3_blob_close()]. +** ^The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces +** can be used to read or write small subsections of the BLOB. +** ^The [sqlite3_blob_bytes()] interface returns the size of the BLOB in bytes. +*/ +typedef struct sqlite3_blob sqlite3_blob; + +/* +** CAPI3REF: Open A BLOB For Incremental I/O +** METHOD: sqlite3 +** CONSTRUCTOR: sqlite3_blob +** +** ^(This interfaces opens a [BLOB handle | handle] to the BLOB located +** in row iRow, column zColumn, table zTable in database zDb; +** in other words, the same BLOB that would be selected by: +** +** <pre> +** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow; +** </pre>)^ +** +** ^(Parameter zDb is not the filename that contains the database, but +** rather the symbolic name of the database. For attached databases, this is +** the name that appears after the AS keyword in the [ATTACH] statement. +** For the main database file, the database name is "main". For TEMP +** tables, the database name is "temp".)^ +** +** ^If the flags parameter is non-zero, then the BLOB is opened for read +** and write access. ^If the flags parameter is zero, the BLOB is opened for +** read-only access. +** +** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored +** in *ppBlob. Otherwise an [error code] is returned and, unless the error +** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided +** the API is not misused, it is always safe to call [sqlite3_blob_close()] +** on *ppBlob after this function it returns. +** +** This function fails with SQLITE_ERROR if any of the following are true: +** <ul> +** <li> ^(Database zDb does not exist)^, +** <li> ^(Table zTable does not exist within database zDb)^, +** <li> ^(Table zTable is a WITHOUT ROWID table)^, +** <li> ^(Column zColumn does not exist)^, +** <li> ^(Row iRow is not present in the table)^, +** <li> ^(The specified column of row iRow contains a value that is not +** a TEXT or BLOB value)^, +** <li> ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE +** constraint and the blob is being opened for read/write access)^, +** <li> ^([foreign key constraints | Foreign key constraints] are enabled, +** column zColumn is part of a [child key] definition and the blob is +** being opened for read/write access)^. +** </ul> +** +** ^Unless it returns SQLITE_MISUSE, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** +** A BLOB referenced by sqlite3_blob_open() may be read using the +** [sqlite3_blob_read()] interface and modified by using +** [sqlite3_blob_write()]. The [BLOB handle] can be moved to a +** different row of the same table using the [sqlite3_blob_reopen()] +** interface. However, the column, table, or database of a [BLOB handle] +** cannot be changed after the [BLOB handle] is opened. +** +** ^(If the row that a BLOB handle points to is modified by an +** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects +** then the BLOB handle is marked as "expired". +** This is true if any column of the row is changed, even a column +** other than the one the BLOB handle is open on.)^ +** ^Calls to [sqlite3_blob_read()] and [sqlite3_blob_write()] for +** an expired BLOB handle fail with a return code of [SQLITE_ABORT]. +** ^(Changes written into a BLOB prior to the BLOB expiring are not +** rolled back by the expiration of the BLOB. Such changes will eventually +** commit if the transaction continues to completion.)^ +** +** ^Use the [sqlite3_blob_bytes()] interface to determine the size of +** the opened blob. ^The size of a blob may not be changed by this +** interface. Use the [UPDATE] SQL command to change the size of a +** blob. +** +** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces +** and the built-in [zeroblob] SQL function may be used to create a +** zero-filled blob to read or write using the incremental-blob interface. +** +** To avoid a resource leak, every open [BLOB handle] should eventually +** be released by a call to [sqlite3_blob_close()]. +** +** See also: [sqlite3_blob_close()], +** [sqlite3_blob_reopen()], [sqlite3_blob_read()], +** [sqlite3_blob_bytes()], [sqlite3_blob_write()]. +*/ +SQLITE_API int sqlite3_blob_open( + sqlite3*, + const char *zDb, + const char *zTable, + const char *zColumn, + sqlite3_int64 iRow, + int flags, + sqlite3_blob **ppBlob +); + +/* +** CAPI3REF: Move a BLOB Handle to a New Row +** METHOD: sqlite3_blob +** +** ^This function is used to move an existing [BLOB handle] so that it points +** to a different row of the same database table. ^The new row is identified +** by the rowid value passed as the second argument. Only the row can be +** changed. ^The database, table and column on which the blob handle is open +** remain the same. Moving an existing [BLOB handle] to a new row is +** faster than closing the existing handle and opening a new one. +** +** ^(The new row must meet the same criteria as for [sqlite3_blob_open()] - +** it must exist and there must be either a blob or text value stored in +** the nominated column.)^ ^If the new row is not present in the table, or if +** it does not contain a blob or text value, or if another error occurs, an +** SQLite error code is returned and the blob handle is considered aborted. +** ^All subsequent calls to [sqlite3_blob_read()], [sqlite3_blob_write()] or +** [sqlite3_blob_reopen()] on an aborted blob handle immediately return +** SQLITE_ABORT. ^Calling [sqlite3_blob_bytes()] on an aborted blob handle +** always returns zero. +** +** ^This function sets the database handle error code and message. +*/ +SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); + +/* +** CAPI3REF: Close A BLOB Handle +** DESTRUCTOR: sqlite3_blob +** +** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed +** unconditionally. Even if this routine returns an error code, the +** handle is still closed.)^ +** +** ^If the blob handle being closed was opened for read-write access, and if +** the database is in auto-commit mode and there are no other open read-write +** blob handles or active write statements, the current transaction is +** committed. ^If an error occurs while committing the transaction, an error +** code is returned and the transaction rolled back. +** +** Calling this function with an argument that is not a NULL pointer or an +** open blob handle results in undefined behaviour. ^Calling this routine +** with a null pointer (such as would be returned by a failed call to +** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function +** is passed a valid open blob handle, the values returned by the +** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning. +*/ +SQLITE_API int sqlite3_blob_close(sqlite3_blob *); + +/* +** CAPI3REF: Return The Size Of An Open BLOB +** METHOD: sqlite3_blob +** +** ^Returns the size in bytes of the BLOB accessible via the +** successfully opened [BLOB handle] in its only argument. ^The +** incremental blob I/O routines can only read or overwriting existing +** blob content; they cannot change the size of a blob. +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +*/ +SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *); + +/* +** CAPI3REF: Read Data From A BLOB Incrementally +** METHOD: sqlite3_blob +** +** ^(This function is used to read data from an open [BLOB handle] into a +** caller-supplied buffer. N bytes of data are copied into buffer Z +** from the open BLOB, starting at offset iOffset.)^ +** +** ^If offset iOffset is less than N bytes from the end of the BLOB, +** [SQLITE_ERROR] is returned and no data is read. ^If N or iOffset is +** less than zero, [SQLITE_ERROR] is returned and no data is read. +** ^The size of the blob (and hence the maximum value of N+iOffset) +** can be determined using the [sqlite3_blob_bytes()] interface. +** +** ^An attempt to read from an expired [BLOB handle] fails with an +** error code of [SQLITE_ABORT]. +** +** ^(On success, sqlite3_blob_read() returns SQLITE_OK. +** Otherwise, an [error code] or an [extended error code] is returned.)^ +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +** +** See also: [sqlite3_blob_write()]. +*/ +SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); + +/* +** CAPI3REF: Write Data Into A BLOB Incrementally +** METHOD: sqlite3_blob +** +** ^(This function is used to write data into an open [BLOB handle] from a +** caller-supplied buffer. N bytes of data are copied from the buffer Z +** into the open BLOB, starting at offset iOffset.)^ +** +** ^(On success, sqlite3_blob_write() returns SQLITE_OK. +** Otherwise, an [error code] or an [extended error code] is returned.)^ +** ^Unless SQLITE_MISUSE is returned, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** +** ^If the [BLOB handle] passed as the first argument was not opened for +** writing (the flags parameter to [sqlite3_blob_open()] was zero), +** this function returns [SQLITE_READONLY]. +** +** This function may only modify the contents of the BLOB; it is +** not possible to increase the size of a BLOB using this API. +** ^If offset iOffset is less than N bytes from the end of the BLOB, +** [SQLITE_ERROR] is returned and no data is written. The size of the +** BLOB (and hence the maximum value of N+iOffset) can be determined +** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less +** than zero [SQLITE_ERROR] is returned and no data is written. +** +** ^An attempt to write to an expired [BLOB handle] fails with an +** error code of [SQLITE_ABORT]. ^Writes to the BLOB that occurred +** before the [BLOB handle] expired are not rolled back by the +** expiration of the handle, though of course those changes might +** have been overwritten by the statement that expired the BLOB handle +** or by other independent statements. +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +** +** See also: [sqlite3_blob_read()]. +*/ +SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); + +/* +** CAPI3REF: Virtual File System Objects +** +** A virtual filesystem (VFS) is an [sqlite3_vfs] object +** that SQLite uses to interact +** with the underlying operating system. Most SQLite builds come with a +** single default VFS that is appropriate for the host computer. +** New VFSes can be registered and existing VFSes can be unregistered. +** The following interfaces are provided. +** +** ^The sqlite3_vfs_find() interface returns a pointer to a VFS given its name. +** ^Names are case sensitive. +** ^Names are zero-terminated UTF-8 strings. +** ^If there is no match, a NULL pointer is returned. +** ^If zVfsName is NULL then the default VFS is returned. +** +** ^New VFSes are registered with sqlite3_vfs_register(). +** ^Each new VFS becomes the default VFS if the makeDflt flag is set. +** ^The same VFS can be registered multiple times without injury. +** ^To make an existing VFS into the default VFS, register it again +** with the makeDflt flag set. If two different VFSes with the +** same name are registered, the behavior is undefined. If a +** VFS is registered with a name that is NULL or an empty string, +** then the behavior is undefined. +** +** ^Unregister a VFS with the sqlite3_vfs_unregister() interface. +** ^(If the default VFS is unregistered, another VFS is chosen as +** the default. The choice for the new VFS is arbitrary.)^ +*/ +SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName); +SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); +SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); + +/* +** CAPI3REF: Mutexes +** +** The SQLite core uses these routines for thread +** synchronization. Though they are intended for internal +** use by SQLite, code that links against SQLite is +** permitted to use any of these routines. +** +** The SQLite source code contains multiple implementations +** of these mutex routines. An appropriate implementation +** is selected automatically at compile-time. The following +** implementations are available in the SQLite core: +** +** <ul> +** <li> SQLITE_MUTEX_PTHREADS +** <li> SQLITE_MUTEX_W32 +** <li> SQLITE_MUTEX_NOOP +** </ul> +** +** The SQLITE_MUTEX_NOOP implementation is a set of routines +** that does no real locking and is appropriate for use in +** a single-threaded application. The SQLITE_MUTEX_PTHREADS and +** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix +** and Windows. +** +** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor +** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex +** implementation is included with the library. In this case the +** application must supply a custom mutex implementation using the +** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function +** before calling sqlite3_initialize() or any other public sqlite3_ +** function that calls sqlite3_initialize(). +** +** ^The sqlite3_mutex_alloc() routine allocates a new +** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() +** routine returns NULL if it is unable to allocate the requested +** mutex. The argument to sqlite3_mutex_alloc() must one of these +** integer constants: +** +** <ul> +** <li> SQLITE_MUTEX_FAST +** <li> SQLITE_MUTEX_RECURSIVE +** <li> SQLITE_MUTEX_STATIC_MAIN +** <li> SQLITE_MUTEX_STATIC_MEM +** <li> SQLITE_MUTEX_STATIC_OPEN +** <li> SQLITE_MUTEX_STATIC_PRNG +** <li> SQLITE_MUTEX_STATIC_LRU +** <li> SQLITE_MUTEX_STATIC_PMEM +** <li> SQLITE_MUTEX_STATIC_APP1 +** <li> SQLITE_MUTEX_STATIC_APP2 +** <li> SQLITE_MUTEX_STATIC_APP3 +** <li> SQLITE_MUTEX_STATIC_VFS1 +** <li> SQLITE_MUTEX_STATIC_VFS2 +** <li> SQLITE_MUTEX_STATIC_VFS3 +** </ul> +** +** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) +** cause sqlite3_mutex_alloc() to create +** a new mutex. ^The new mutex is recursive when SQLITE_MUTEX_RECURSIVE +** is used but not necessarily so when SQLITE_MUTEX_FAST is used. +** The mutex implementation does not need to make a distinction +** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does +** not want to. SQLite will only request a recursive mutex in +** cases where it really needs one. If a faster non-recursive mutex +** implementation is available on the host platform, the mutex subsystem +** might return such a mutex in response to SQLITE_MUTEX_FAST. +** +** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other +** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return +** a pointer to a static preexisting mutex. ^Nine static mutexes are +** used by the current version of SQLite. Future versions of SQLite +** may add additional static mutexes. Static mutexes are for internal +** use by SQLite only. Applications that use SQLite mutexes should +** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or +** SQLITE_MUTEX_RECURSIVE. +** +** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST +** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() +** returns a different mutex on every call. ^For the static +** mutex types, the same mutex is returned on every call that has +** the same type number. +** +** ^The sqlite3_mutex_free() routine deallocates a previously +** allocated dynamic mutex. Attempting to deallocate a static +** mutex results in undefined behavior. +** +** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt +** to enter a mutex. ^If another thread is already within the mutex, +** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return +** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK] +** upon successful entry. ^(Mutexes created using +** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread. +** In such cases, the +** mutex must be exited an equal number of times before another thread +** can enter.)^ If the same thread tries to enter any mutex other +** than an SQLITE_MUTEX_RECURSIVE more than once, the behavior is undefined. +** +** ^(Some systems (for example, Windows 95) do not support the operation +** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() +** will always return SQLITE_BUSY. The SQLite core only ever uses +** sqlite3_mutex_try() as an optimization so this is acceptable +** behavior.)^ +** +** ^The sqlite3_mutex_leave() routine exits a mutex that was +** previously entered by the same thread. The behavior +** is undefined if the mutex is not currently entered by the +** calling thread or is not currently allocated. +** +** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), +** sqlite3_mutex_leave(), or sqlite3_mutex_free() is a NULL pointer, +** then any of the four routines behaves as a no-op. +** +** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. +*/ +SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int); +SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); + +/* +** CAPI3REF: Mutex Methods Object +** +** An instance of this structure defines the low-level routines +** used to allocate and use mutexes. +** +** Usually, the default mutex implementations provided by SQLite are +** sufficient, however the application has the option of substituting a custom +** implementation for specialized deployments or systems for which SQLite +** does not provide a suitable implementation. In this case, the application +** creates and populates an instance of this structure to pass +** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option. +** Additionally, an instance of this structure can be used as an +** output variable when querying the system for the current mutex +** implementation, using the [SQLITE_CONFIG_GETMUTEX] option. +** +** ^The xMutexInit method defined by this structure is invoked as +** part of system initialization by the sqlite3_initialize() function. +** ^The xMutexInit routine is called by SQLite exactly once for each +** effective call to [sqlite3_initialize()]. +** +** ^The xMutexEnd method defined by this structure is invoked as +** part of system shutdown by the sqlite3_shutdown() function. The +** implementation of this method is expected to release all outstanding +** resources obtained by the mutex methods implementation, especially +** those obtained by the xMutexInit method. ^The xMutexEnd() +** interface is invoked exactly once for each call to [sqlite3_shutdown()]. +** +** ^(The remaining seven methods defined by this structure (xMutexAlloc, +** xMutexFree, xMutexEnter, xMutexTry, xMutexLeave, xMutexHeld and +** xMutexNotheld) implement the following interfaces (respectively): +** +** <ul> +** <li> [sqlite3_mutex_alloc()] </li> +** <li> [sqlite3_mutex_free()] </li> +** <li> [sqlite3_mutex_enter()] </li> +** <li> [sqlite3_mutex_try()] </li> +** <li> [sqlite3_mutex_leave()] </li> +** <li> [sqlite3_mutex_held()] </li> +** <li> [sqlite3_mutex_notheld()] </li> +** </ul>)^ +** +** The only difference is that the public sqlite3_XXX functions enumerated +** above silently ignore any invocations that pass a NULL pointer instead +** of a valid mutex handle. The implementations of the methods defined +** by this structure are not required to handle this case. The results +** of passing a NULL pointer instead of a valid mutex handle are undefined +** (i.e. it is acceptable to provide an implementation that segfaults if +** it is passed a NULL pointer). +** +** The xMutexInit() method must be threadsafe. It must be harmless to +** invoke xMutexInit() multiple times within the same process and without +** intervening calls to xMutexEnd(). Second and subsequent calls to +** xMutexInit() must be no-ops. +** +** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] +** and its associates). Similarly, xMutexAlloc() must not use SQLite memory +** allocation for a static mutex. ^However xMutexAlloc() may use SQLite +** memory allocation for a fast or recursive mutex. +** +** ^SQLite will invoke the xMutexEnd() method when [sqlite3_shutdown()] is +** called, but only if the prior call to xMutexInit returned SQLITE_OK. +** If xMutexInit fails in any way, it is expected to clean up after itself +** prior to returning. +*/ +typedef struct sqlite3_mutex_methods sqlite3_mutex_methods; +struct sqlite3_mutex_methods { + int (*xMutexInit)(void); + int (*xMutexEnd)(void); + sqlite3_mutex *(*xMutexAlloc)(int); + void (*xMutexFree)(sqlite3_mutex *); + void (*xMutexEnter)(sqlite3_mutex *); + int (*xMutexTry)(sqlite3_mutex *); + void (*xMutexLeave)(sqlite3_mutex *); + int (*xMutexHeld)(sqlite3_mutex *); + int (*xMutexNotheld)(sqlite3_mutex *); +}; + +/* +** CAPI3REF: Mutex Verification Routines +** +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines +** are intended for use inside assert() statements. The SQLite core +** never uses these routines except inside an assert() and applications +** are advised to follow the lead of the core. The SQLite core only +** provides implementations for these routines when it is compiled +** with the SQLITE_DEBUG flag. External mutex implementations +** are only required to provide these routines if SQLITE_DEBUG is +** defined and if NDEBUG is not defined. +** +** These routines should return true if the mutex in their argument +** is held or not held, respectively, by the calling thread. +** +** The implementation is not required to provide versions of these +** routines that actually work. If the implementation does not provide working +** versions of these routines, it should at least provide stubs that always +** return true so that one does not get spurious assertion failures. +** +** If the argument to sqlite3_mutex_held() is a NULL pointer then +** the routine should return 1. This seems counter-intuitive since +** clearly the mutex cannot be held if it does not exist. But +** the reason the mutex does not exist is because the build is not +** using mutexes. And we do not want the assert() containing the +** call to sqlite3_mutex_held() to fail, so a non-zero return is +** the appropriate thing to do. The sqlite3_mutex_notheld() +** interface should also return 1 when given a NULL pointer. +*/ +#ifndef NDEBUG +SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); +#endif + +/* +** CAPI3REF: Mutex Types +** +** The [sqlite3_mutex_alloc()] interface takes a single argument +** which is one of these integer constants. +** +** The set of static mutexes may change from one SQLite release to the +** next. Applications that override the built-in mutex logic must be +** prepared to accommodate additional static mutexes. +*/ +#define SQLITE_MUTEX_FAST 0 +#define SQLITE_MUTEX_RECURSIVE 1 +#define SQLITE_MUTEX_STATIC_MAIN 2 +#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ +#define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ +#define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ +#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_randomness() */ +#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ +#define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ +#define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ +#define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */ +#define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */ +#define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */ +#define SQLITE_MUTEX_STATIC_VFS1 11 /* For use by built-in VFS */ +#define SQLITE_MUTEX_STATIC_VFS2 12 /* For use by extension VFS */ +#define SQLITE_MUTEX_STATIC_VFS3 13 /* For use by application VFS */ + +/* Legacy compatibility: */ +#define SQLITE_MUTEX_STATIC_MASTER 2 + + +/* +** CAPI3REF: Retrieve the mutex for a database connection +** METHOD: sqlite3 +** +** ^This interface returns a pointer the [sqlite3_mutex] object that +** serializes access to the [database connection] given in the argument +** when the [threading mode] is Serialized. +** ^If the [threading mode] is Single-thread or Multi-thread then this +** routine returns a NULL pointer. +*/ +SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*); + +/* +** CAPI3REF: Low-Level Control Of Database Files +** METHOD: sqlite3 +** KEYWORDS: {file control} +** +** ^The [sqlite3_file_control()] interface makes a direct call to the +** xFileControl method for the [sqlite3_io_methods] object associated +** with a particular database identified by the second argument. ^The +** name of the database is "main" for the main database or "temp" for the +** TEMP database, or the name that appears after the AS keyword for +** databases that are added using the [ATTACH] SQL command. +** ^A NULL pointer can be used in place of "main" to refer to the +** main database file. +** ^The third and fourth parameters to this routine +** are passed directly through to the second and third parameters of +** the xFileControl method. ^The return value of the xFileControl +** method becomes the return value of this routine. +** +** A few opcodes for [sqlite3_file_control()] are handled directly +** by the SQLite core and never invoke the +** sqlite3_io_methods.xFileControl method. +** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes +** a pointer to the underlying [sqlite3_file] object to be written into +** the space pointed to by the 4th parameter. The +** [SQLITE_FCNTL_JOURNAL_POINTER] works similarly except that it returns +** the [sqlite3_file] object associated with the journal file instead of +** the main database. The [SQLITE_FCNTL_VFS_POINTER] opcode returns +** a pointer to the underlying [sqlite3_vfs] object for the file. +** The [SQLITE_FCNTL_DATA_VERSION] returns the data version counter +** from the pager. +** +** ^If the second parameter (zDbName) does not match the name of any +** open database file, then SQLITE_ERROR is returned. ^This error +** code is not remembered and will not be recalled by [sqlite3_errcode()] +** or [sqlite3_errmsg()]. The underlying xFileControl method might +** also return SQLITE_ERROR. There is no way to distinguish between +** an incorrect zDbName and an SQLITE_ERROR return from the underlying +** xFileControl method. +** +** See also: [file control opcodes] +*/ +SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); + +/* +** CAPI3REF: Testing Interface +** +** ^The sqlite3_test_control() interface is used to read out internal +** state of SQLite and to inject faults into SQLite for testing +** purposes. ^The first parameter is an operation code that determines +** the number, meaning, and operation of all subsequent parameters. +** +** This interface is not for use by applications. It exists solely +** for verifying the correct operation of the SQLite library. Depending +** on how the SQLite library is compiled, this interface might not exist. +** +** The details of the operation codes, their meanings, the parameters +** they take, and what they do are all subject to change without notice. +** Unlike most of the SQLite API, this function is not guaranteed to +** operate consistently from one release to the next. +*/ +SQLITE_API int sqlite3_test_control(int op, ...); + +/* +** CAPI3REF: Testing Interface Operation Codes +** +** These constants are the valid operation code parameters used +** as the first argument to [sqlite3_test_control()]. +** +** These parameters and their meanings are subject to change +** without notice. These values are for testing purposes only. +** Applications should not use any of these parameters or the +** [sqlite3_test_control()] interface. +*/ +#define SQLITE_TESTCTRL_FIRST 5 +#define SQLITE_TESTCTRL_PRNG_SAVE 5 +#define SQLITE_TESTCTRL_PRNG_RESTORE 6 +#define SQLITE_TESTCTRL_PRNG_RESET 7 /* NOT USED */ +#define SQLITE_TESTCTRL_BITVEC_TEST 8 +#define SQLITE_TESTCTRL_FAULT_INSTALL 9 +#define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 +#define SQLITE_TESTCTRL_PENDING_BYTE 11 +#define SQLITE_TESTCTRL_ASSERT 12 +#define SQLITE_TESTCTRL_ALWAYS 13 +#define SQLITE_TESTCTRL_RESERVE 14 /* NOT USED */ +#define SQLITE_TESTCTRL_OPTIMIZATIONS 15 +#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */ +#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */ +#define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17 +#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 +#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ +#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19 +#define SQLITE_TESTCTRL_NEVER_CORRUPT 20 +#define SQLITE_TESTCTRL_VDBE_COVERAGE 21 +#define SQLITE_TESTCTRL_BYTEORDER 22 +#define SQLITE_TESTCTRL_ISINIT 23 +#define SQLITE_TESTCTRL_SORTER_MMAP 24 +#define SQLITE_TESTCTRL_IMPOSTER 25 +#define SQLITE_TESTCTRL_PARSER_COVERAGE 26 +#define SQLITE_TESTCTRL_RESULT_INTREAL 27 +#define SQLITE_TESTCTRL_PRNG_SEED 28 +#define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29 +#define SQLITE_TESTCTRL_SEEK_COUNT 30 +#define SQLITE_TESTCTRL_TRACEFLAGS 31 +#define SQLITE_TESTCTRL_TUNE 32 +#define SQLITE_TESTCTRL_LOGEST 33 +#define SQLITE_TESTCTRL_USELONGDOUBLE 34 +#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */ + +/* +** CAPI3REF: SQL Keyword Checking +** +** These routines provide access to the set of SQL language keywords +** recognized by SQLite. Applications can uses these routines to determine +** whether or not a specific identifier needs to be escaped (for example, +** by enclosing in double-quotes) so as not to confuse the parser. +** +** The sqlite3_keyword_count() interface returns the number of distinct +** keywords understood by SQLite. +** +** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and +** makes *Z point to that keyword expressed as UTF8 and writes the number +** of bytes in the keyword into *L. The string that *Z points to is not +** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns +** SQLITE_OK if N is within bounds and SQLITE_ERROR if not. If either Z +** or L are NULL or invalid pointers then calls to +** sqlite3_keyword_name(N,Z,L) result in undefined behavior. +** +** The sqlite3_keyword_check(Z,L) interface checks to see whether or not +** the L-byte UTF8 identifier that Z points to is a keyword, returning non-zero +** if it is and zero if not. +** +** The parser used by SQLite is forgiving. It is often possible to use +** a keyword as an identifier as long as such use does not result in a +** parsing ambiguity. For example, the statement +** "CREATE TABLE BEGIN(REPLACE,PRAGMA,END);" is accepted by SQLite, and +** creates a new table named "BEGIN" with three columns named +** "REPLACE", "PRAGMA", and "END". Nevertheless, best practice is to avoid +** using keywords as identifiers. Common techniques used to avoid keyword +** name collisions include: +** <ul> +** <li> Put all identifier names inside double-quotes. This is the official +** SQL way to escape identifier names. +** <li> Put identifier names inside &#91;...&#93;. This is not standard SQL, +** but it is what SQL Server does and so lots of programmers use this +** technique. +** <li> Begin every identifier with the letter "Z" as no SQL keywords start +** with "Z". +** <li> Include a digit somewhere in every identifier name. +** </ul> +** +** Note that the number of keywords understood by SQLite can depend on +** compile-time options. For example, "VACUUM" is not a keyword if +** SQLite is compiled with the [-DSQLITE_OMIT_VACUUM] option. Also, +** new keywords may be added to future releases of SQLite. +*/ +SQLITE_API int sqlite3_keyword_count(void); +SQLITE_API int sqlite3_keyword_name(int,const char**,int*); +SQLITE_API int sqlite3_keyword_check(const char*,int); + +/* +** CAPI3REF: Dynamic String Object +** KEYWORDS: {dynamic string} +** +** An instance of the sqlite3_str object contains a dynamically-sized +** string under construction. +** +** The lifecycle of an sqlite3_str object is as follows: +** <ol> +** <li> ^The sqlite3_str object is created using [sqlite3_str_new()]. +** <li> ^Text is appended to the sqlite3_str object using various +** methods, such as [sqlite3_str_appendf()]. +** <li> ^The sqlite3_str object is destroyed and the string it created +** is returned using the [sqlite3_str_finish()] interface. +** </ol> +*/ +typedef struct sqlite3_str sqlite3_str; + +/* +** CAPI3REF: Create A New Dynamic String Object +** CONSTRUCTOR: sqlite3_str +** +** ^The [sqlite3_str_new(D)] interface allocates and initializes +** a new [sqlite3_str] object. To avoid memory leaks, the object returned by +** [sqlite3_str_new()] must be freed by a subsequent call to +** [sqlite3_str_finish(X)]. +** +** ^The [sqlite3_str_new(D)] interface always returns a pointer to a +** valid [sqlite3_str] object, though in the event of an out-of-memory +** error the returned object might be a special singleton that will +** silently reject new text, always return SQLITE_NOMEM from +** [sqlite3_str_errcode()], always return 0 for +** [sqlite3_str_length()], and always return NULL from +** [sqlite3_str_finish(X)]. It is always safe to use the value +** returned by [sqlite3_str_new(D)] as the sqlite3_str parameter +** to any of the other [sqlite3_str] methods. +** +** The D parameter to [sqlite3_str_new(D)] may be NULL. If the +** D parameter in [sqlite3_str_new(D)] is not NULL, then the maximum +** length of the string contained in the [sqlite3_str] object will be +** the value set for [sqlite3_limit](D,[SQLITE_LIMIT_LENGTH]) instead +** of [SQLITE_MAX_LENGTH]. +*/ +SQLITE_API sqlite3_str *sqlite3_str_new(sqlite3*); + +/* +** CAPI3REF: Finalize A Dynamic String +** DESTRUCTOR: sqlite3_str +** +** ^The [sqlite3_str_finish(X)] interface destroys the sqlite3_str object X +** and returns a pointer to a memory buffer obtained from [sqlite3_malloc64()] +** that contains the constructed string. The calling application should +** pass the returned value to [sqlite3_free()] to avoid a memory leak. +** ^The [sqlite3_str_finish(X)] interface may return a NULL pointer if any +** errors were encountered during construction of the string. ^The +** [sqlite3_str_finish(X)] interface will also return a NULL pointer if the +** string in [sqlite3_str] object X is zero bytes long. +*/ +SQLITE_API char *sqlite3_str_finish(sqlite3_str*); + +/* +** CAPI3REF: Add Content To A Dynamic String +** METHOD: sqlite3_str +** +** These interfaces add content to an sqlite3_str object previously obtained +** from [sqlite3_str_new()]. +** +** ^The [sqlite3_str_appendf(X,F,...)] and +** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf] +** functionality of SQLite to append formatted text onto the end of +** [sqlite3_str] object X. +** +** ^The [sqlite3_str_append(X,S,N)] method appends exactly N bytes from string S +** onto the end of the [sqlite3_str] object X. N must be non-negative. +** S must contain at least N non-zero bytes of content. To append a +** zero-terminated string in its entirety, use the [sqlite3_str_appendall()] +** method instead. +** +** ^The [sqlite3_str_appendall(X,S)] method appends the complete content of +** zero-terminated string S onto the end of [sqlite3_str] object X. +** +** ^The [sqlite3_str_appendchar(X,N,C)] method appends N copies of the +** single-byte character C onto the end of [sqlite3_str] object X. +** ^This method can be used, for example, to add whitespace indentation. +** +** ^The [sqlite3_str_reset(X)] method resets the string under construction +** inside [sqlite3_str] object X back to zero bytes in length. +** +** These methods do not return a result code. ^If an error occurs, that fact +** is recorded in the [sqlite3_str] object and can be recovered by a +** subsequent call to [sqlite3_str_errcode(X)]. +*/ +SQLITE_API void sqlite3_str_appendf(sqlite3_str*, const char *zFormat, ...); +SQLITE_API void sqlite3_str_vappendf(sqlite3_str*, const char *zFormat, va_list); +SQLITE_API void sqlite3_str_append(sqlite3_str*, const char *zIn, int N); +SQLITE_API void sqlite3_str_appendall(sqlite3_str*, const char *zIn); +SQLITE_API void sqlite3_str_appendchar(sqlite3_str*, int N, char C); +SQLITE_API void sqlite3_str_reset(sqlite3_str*); + +/* +** CAPI3REF: Status Of A Dynamic String +** METHOD: sqlite3_str +** +** These interfaces return the current status of an [sqlite3_str] object. +** +** ^If any prior errors have occurred while constructing the dynamic string +** in sqlite3_str X, then the [sqlite3_str_errcode(X)] method will return +** an appropriate error code. ^The [sqlite3_str_errcode(X)] method returns +** [SQLITE_NOMEM] following any out-of-memory error, or +** [SQLITE_TOOBIG] if the size of the dynamic string exceeds +** [SQLITE_MAX_LENGTH], or [SQLITE_OK] if there have been no errors. +** +** ^The [sqlite3_str_length(X)] method returns the current length, in bytes, +** of the dynamic string under construction in [sqlite3_str] object X. +** ^The length returned by [sqlite3_str_length(X)] does not include the +** zero-termination byte. +** +** ^The [sqlite3_str_value(X)] method returns a pointer to the current +** content of the dynamic string under construction in X. The value +** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X +** and might be freed or altered by any subsequent method on the same +** [sqlite3_str] object. Applications must not used the pointer returned +** [sqlite3_str_value(X)] after any subsequent method call on the same +** object. ^Applications may change the content of the string returned +** by [sqlite3_str_value(X)] as long as they do not write into any bytes +** outside the range of 0 to [sqlite3_str_length(X)] and do not read or +** write any byte after any subsequent sqlite3_str method call. +*/ +SQLITE_API int sqlite3_str_errcode(sqlite3_str*); +SQLITE_API int sqlite3_str_length(sqlite3_str*); +SQLITE_API char *sqlite3_str_value(sqlite3_str*); + +/* +** CAPI3REF: SQLite Runtime Status +** +** ^These interfaces are used to retrieve runtime status information +** about the performance of SQLite, and optionally to reset various +** highwater marks. ^The first argument is an integer code for +** the specific parameter to measure. ^(Recognized integer codes +** are of the form [status parameters | SQLITE_STATUS_...].)^ +** ^The current value of the parameter is returned into *pCurrent. +** ^The highest recorded value is returned in *pHighwater. ^If the +** resetFlag is true, then the highest record value is reset after +** *pHighwater is written. ^(Some parameters do not record the highest +** value. For those parameters +** nothing is written into *pHighwater and the resetFlag is ignored.)^ +** ^(Other parameters record only the highwater mark and not the current +** value. For these latter parameters nothing is written into *pCurrent.)^ +** +** ^The sqlite3_status() and sqlite3_status64() routines return +** SQLITE_OK on success and a non-zero [error code] on failure. +** +** If either the current value or the highwater mark is too large to +** be represented by a 32-bit integer, then the values returned by +** sqlite3_status() are undefined. +** +** See also: [sqlite3_db_status()] +*/ +SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); +SQLITE_API int sqlite3_status64( + int op, + sqlite3_int64 *pCurrent, + sqlite3_int64 *pHighwater, + int resetFlag +); + + +/* +** CAPI3REF: Status Parameters +** KEYWORDS: {status parameters} +** +** These integer constants designate various run-time status parameters +** that can be returned by [sqlite3_status()]. +** +** <dl> +** [[SQLITE_STATUS_MEMORY_USED]] ^(<dt>SQLITE_STATUS_MEMORY_USED</dt> +** <dd>This parameter is the current amount of memory checked out +** using [sqlite3_malloc()], either directly or indirectly. The +** figure includes calls made to [sqlite3_malloc()] by the application +** and internal memory usage by the SQLite library. Auxiliary page-cache +** memory controlled by [SQLITE_CONFIG_PAGECACHE] is not included in +** this parameter. The amount returned is the sum of the allocation +** sizes as reported by the xSize method in [sqlite3_mem_methods].</dd>)^ +** +** [[SQLITE_STATUS_MALLOC_SIZE]] ^(<dt>SQLITE_STATUS_MALLOC_SIZE</dt> +** <dd>This parameter records the largest memory allocation request +** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their +** internal equivalents). Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. +** The value written into the *pCurrent parameter is undefined.</dd>)^ +** +** [[SQLITE_STATUS_MALLOC_COUNT]] ^(<dt>SQLITE_STATUS_MALLOC_COUNT</dt> +** <dd>This parameter records the number of separate memory allocations +** currently checked out.</dd>)^ +** +** [[SQLITE_STATUS_PAGECACHE_USED]] ^(<dt>SQLITE_STATUS_PAGECACHE_USED</dt> +** <dd>This parameter returns the number of pages used out of the +** [pagecache memory allocator] that was configured using +** [SQLITE_CONFIG_PAGECACHE]. The +** value returned is in pages, not in bytes.</dd>)^ +** +** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] +** ^(<dt>SQLITE_STATUS_PAGECACHE_OVERFLOW</dt> +** <dd>This parameter returns the number of bytes of page cache +** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] +** buffer and where forced to overflow to [sqlite3_malloc()]. The +** returned value includes allocations that overflowed because they +** where too large (they were larger than the "sz" parameter to +** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because +** no space was left in the page cache.</dd>)^ +** +** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(<dt>SQLITE_STATUS_PAGECACHE_SIZE</dt> +** <dd>This parameter records the largest memory allocation request +** handed to the [pagecache memory allocator]. Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. +** The value written into the *pCurrent parameter is undefined.</dd>)^ +** +** [[SQLITE_STATUS_SCRATCH_USED]] <dt>SQLITE_STATUS_SCRATCH_USED</dt> +** <dd>No longer used.</dd> +** +** [[SQLITE_STATUS_SCRATCH_OVERFLOW]] ^(<dt>SQLITE_STATUS_SCRATCH_OVERFLOW</dt> +** <dd>No longer used.</dd> +** +** [[SQLITE_STATUS_SCRATCH_SIZE]] <dt>SQLITE_STATUS_SCRATCH_SIZE</dt> +** <dd>No longer used.</dd> +** +** [[SQLITE_STATUS_PARSER_STACK]] ^(<dt>SQLITE_STATUS_PARSER_STACK</dt> +** <dd>The *pHighwater parameter records the deepest parser stack. +** The *pCurrent value is undefined. The *pHighwater value is only +** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].</dd>)^ +** </dl> +** +** New status parameters may be added from time to time. +*/ +#define SQLITE_STATUS_MEMORY_USED 0 +#define SQLITE_STATUS_PAGECACHE_USED 1 +#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2 +#define SQLITE_STATUS_SCRATCH_USED 3 /* NOT USED */ +#define SQLITE_STATUS_SCRATCH_OVERFLOW 4 /* NOT USED */ +#define SQLITE_STATUS_MALLOC_SIZE 5 +#define SQLITE_STATUS_PARSER_STACK 6 +#define SQLITE_STATUS_PAGECACHE_SIZE 7 +#define SQLITE_STATUS_SCRATCH_SIZE 8 /* NOT USED */ +#define SQLITE_STATUS_MALLOC_COUNT 9 + +/* +** CAPI3REF: Database Connection Status +** METHOD: sqlite3 +** +** ^This interface is used to retrieve runtime status information +** about a single [database connection]. ^The first argument is the +** database connection object to be interrogated. ^The second argument +** is an integer constant, taken from the set of +** [SQLITE_DBSTATUS options], that +** determines the parameter to interrogate. The set of +** [SQLITE_DBSTATUS options] is likely +** to grow in future releases of SQLite. +** +** ^The current value of the requested parameter is written into *pCur +** and the highest instantaneous value is written into *pHiwtr. ^If +** the resetFlg is true, then the highest instantaneous value is +** reset back down to the current value. +** +** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a +** non-zero [error code] on failure. +** +** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. +*/ +SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); + +/* +** CAPI3REF: Status Parameters for database connections +** KEYWORDS: {SQLITE_DBSTATUS options} +** +** These constants are the available integer "verbs" that can be passed as +** the second argument to the [sqlite3_db_status()] interface. +** +** New verbs may be added in future releases of SQLite. Existing verbs +** might be discontinued. Applications should check the return code from +** [sqlite3_db_status()] to make sure that the call worked. +** The [sqlite3_db_status()] interface will return a non-zero error code +** if a discontinued or unsupported verb is invoked. +** +** <dl> +** [[SQLITE_DBSTATUS_LOOKASIDE_USED]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_USED</dt> +** <dd>This parameter returns the number of lookaside memory slots currently +** checked out.</dd>)^ +** +** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt> +** <dd>This parameter returns the number of malloc attempts that were +** satisfied using lookaside memory. Only the high-water value is meaningful; +** the current value is always zero.)^ +** +** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]] +** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt> +** <dd>This parameter returns the number malloc attempts that might have +** been satisfied using lookaside memory but failed due to the amount of +** memory requested being larger than the lookaside slot size. +** Only the high-water value is meaningful; +** the current value is always zero.)^ +** +** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]] +** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt> +** <dd>This parameter returns the number malloc attempts that might have +** been satisfied using lookaside memory but failed due to all lookaside +** memory already being in use. +** Only the high-water value is meaningful; +** the current value is always zero.)^ +** +** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt> +** <dd>This parameter returns the approximate number of bytes of heap +** memory used by all pager caches associated with the database connection.)^ +** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. +** +** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] +** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt> +** <dd>This parameter is similar to DBSTATUS_CACHE_USED, except that if a +** pager cache is shared between two or more connections the bytes of heap +** memory used by that pager cache is divided evenly between the attached +** connections.)^ In other words, if none of the pager caches associated +** with the database connection are shared, this request returns the same +** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are +** shared, the value returned by this call will be smaller than that returned +** by DBSTATUS_CACHE_USED. ^The highwater mark associated with +** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0. +** +** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt> +** <dd>This parameter returns the approximate number of bytes of heap +** memory used to store the schema for all databases associated +** with the connection - main, temp, and any [ATTACH]-ed databases.)^ +** ^The full amount of memory used by the schemas is reported, even if the +** schema memory is shared with other database connections due to +** [shared cache mode] being enabled. +** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0. +** +** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt> +** <dd>This parameter returns the approximate number of bytes of heap +** and lookaside memory used by all prepared statements associated with +** the database connection.)^ +** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0. +** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(<dt>SQLITE_DBSTATUS_CACHE_HIT</dt> +** <dd>This parameter returns the number of pager cache hits that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** is always 0. +** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(<dt>SQLITE_DBSTATUS_CACHE_MISS</dt> +** <dd>This parameter returns the number of pager cache misses that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** is always 0. +** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_WRITE]] ^(<dt>SQLITE_DBSTATUS_CACHE_WRITE</dt> +** <dd>This parameter returns the number of dirty cache entries that have +** been written to disk. Specifically, the number of pages written to the +** wal file in wal mode databases, or the number of pages written to the +** database file in rollback mode databases. Any pages written as part of +** transaction rollback or database recovery operations are not included. +** If an IO or other error occurs while writing a page to disk, the effect +** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The +** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. +** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> +** <dd>This parameter returns the number of dirty cache entries that have +** been written to disk in the middle of a transaction due to the page +** cache overflowing. Transactions are more efficient if they are written +** to disk all at once. When pages spill mid-transaction, that introduces +** additional overhead. This parameter can be used help identify +** inefficiencies that can be resolved by increasing the cache size. +** </dd> +** +** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt> +** <dd>This parameter returns zero for the current value if and only if +** all foreign key constraints (deferred or immediate) have been +** resolved.)^ ^The highwater mark is always 0. +** </dd> +** </dl> +*/ +#define SQLITE_DBSTATUS_LOOKASIDE_USED 0 +#define SQLITE_DBSTATUS_CACHE_USED 1 +#define SQLITE_DBSTATUS_SCHEMA_USED 2 +#define SQLITE_DBSTATUS_STMT_USED 3 +#define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 +#define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 +#define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 +#define SQLITE_DBSTATUS_CACHE_HIT 7 +#define SQLITE_DBSTATUS_CACHE_MISS 8 +#define SQLITE_DBSTATUS_CACHE_WRITE 9 +#define SQLITE_DBSTATUS_DEFERRED_FKS 10 +#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 +#define SQLITE_DBSTATUS_CACHE_SPILL 12 +#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */ + + +/* +** CAPI3REF: Prepared Statement Status +** METHOD: sqlite3_stmt +** +** ^(Each prepared statement maintains various +** [SQLITE_STMTSTATUS counters] that measure the number +** of times it has performed specific operations.)^ These counters can +** be used to monitor the performance characteristics of the prepared +** statements. For example, if the number of table steps greatly exceeds +** the number of table searches or result rows, that would tend to indicate +** that the prepared statement is using a full table scan rather than +** an index. +** +** ^(This interface is used to retrieve and reset counter values from +** a [prepared statement]. The first argument is the prepared statement +** object to be interrogated. The second argument +** is an integer code for a specific [SQLITE_STMTSTATUS counter] +** to be interrogated.)^ +** ^The current value of the requested counter is returned. +** ^If the resetFlg is true, then the counter is reset to zero after this +** interface call returns. +** +** See also: [sqlite3_status()] and [sqlite3_db_status()]. +*/ +SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); + +/* +** CAPI3REF: Status Parameters for prepared statements +** KEYWORDS: {SQLITE_STMTSTATUS counter} {SQLITE_STMTSTATUS counters} +** +** These preprocessor macros define integer codes that name counter +** values associated with the [sqlite3_stmt_status()] interface. +** The meanings of the various counters are as follows: +** +** <dl> +** [[SQLITE_STMTSTATUS_FULLSCAN_STEP]] <dt>SQLITE_STMTSTATUS_FULLSCAN_STEP</dt> +** <dd>^This is the number of times that SQLite has stepped forward in +** a table as part of a full table scan. Large numbers for this counter +** may indicate opportunities for performance improvement through +** careful use of indices.</dd> +** +** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt> +** <dd>^This is the number of sort operations that have occurred. +** A non-zero value in this counter may indicate an opportunity to +** improvement performance through careful use of indices.</dd> +** +** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt> +** <dd>^This is the number of rows inserted into transient indices that +** were created automatically in order to help joins run faster. +** A non-zero value in this counter may indicate an opportunity to +** improvement performance by adding permanent indices that do not +** need to be reinitialized each time the statement is run.</dd> +** +** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt> +** <dd>^This is the number of virtual machine operations executed +** by the prepared statement if that number is less than or equal +** to 2147483647. The number of virtual machine operations can be +** used as a proxy for the total work done by the prepared statement. +** If the number of virtual machine operations exceeds 2147483647 +** then the value returned by this statement status code is undefined. +** +** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt> +** <dd>^This is the number of times that the prepare statement has been +** automatically regenerated due to schema changes or changes to +** [bound parameters] that might affect the query plan. +** +** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt> +** <dd>^This is the number of times that the prepared statement has +** been run. A single "run" for the purposes of this counter is one +** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()]. +** The counter is incremented on the first [sqlite3_step()] call of each +** cycle. +** +** [[SQLITE_STMTSTATUS_FILTER_MISS]] +** [[SQLITE_STMTSTATUS_FILTER HIT]] +** <dt>SQLITE_STMTSTATUS_FILTER_HIT<br> +** SQLITE_STMTSTATUS_FILTER_MISS</dt> +** <dd>^SQLITE_STMTSTATUS_FILTER_HIT is the number of times that a join +** step was bypassed because a Bloom filter returned not-found. The +** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of +** times that the Bloom filter returned a find, and thus the join step +** had to be processed as normal. +** +** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt> +** <dd>^This is the approximate number of bytes of heap memory +** used to store the prepared statement. ^This value is not actually +** a counter, and so the resetFlg parameter to sqlite3_stmt_status() +** is ignored when the opcode is SQLITE_STMTSTATUS_MEMUSED. +** </dd> +** </dl> +*/ +#define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 +#define SQLITE_STMTSTATUS_SORT 2 +#define SQLITE_STMTSTATUS_AUTOINDEX 3 +#define SQLITE_STMTSTATUS_VM_STEP 4 +#define SQLITE_STMTSTATUS_REPREPARE 5 +#define SQLITE_STMTSTATUS_RUN 6 +#define SQLITE_STMTSTATUS_FILTER_MISS 7 +#define SQLITE_STMTSTATUS_FILTER_HIT 8 +#define SQLITE_STMTSTATUS_MEMUSED 99 + +/* +** CAPI3REF: Custom Page Cache Object +** +** The sqlite3_pcache type is opaque. It is implemented by +** the pluggable module. The SQLite core has no knowledge of +** its size or internal structure and never deals with the +** sqlite3_pcache object except by holding and passing pointers +** to the object. +** +** See [sqlite3_pcache_methods2] for additional information. +*/ +typedef struct sqlite3_pcache sqlite3_pcache; + +/* +** CAPI3REF: Custom Page Cache Object +** +** The sqlite3_pcache_page object represents a single page in the +** page cache. The page cache will allocate instances of this +** object. Various methods of the page cache use pointers to instances +** of this object as parameters or as their return value. +** +** See [sqlite3_pcache_methods2] for additional information. +*/ +typedef struct sqlite3_pcache_page sqlite3_pcache_page; +struct sqlite3_pcache_page { + void *pBuf; /* The content of the page */ + void *pExtra; /* Extra information associated with the page */ +}; + +/* +** CAPI3REF: Application Defined Page Cache. +** KEYWORDS: {page cache} +** +** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can +** register an alternative page cache implementation by passing in an +** instance of the sqlite3_pcache_methods2 structure.)^ +** In many applications, most of the heap memory allocated by +** SQLite is used for the page cache. +** By implementing a +** custom page cache using this API, an application can better control +** the amount of memory consumed by SQLite, the way in which +** that memory is allocated and released, and the policies used to +** determine exactly which parts of a database file are cached and for +** how long. +** +** The alternative page cache mechanism is an +** extreme measure that is only needed by the most demanding applications. +** The built-in page cache is recommended for most uses. +** +** ^(The contents of the sqlite3_pcache_methods2 structure are copied to an +** internal buffer by SQLite within the call to [sqlite3_config]. Hence +** the application may discard the parameter after the call to +** [sqlite3_config()] returns.)^ +** +** [[the xInit() page cache method]] +** ^(The xInit() method is called once for each effective +** call to [sqlite3_initialize()])^ +** (usually only once during the lifetime of the process). ^(The xInit() +** method is passed a copy of the sqlite3_pcache_methods2.pArg value.)^ +** The intent of the xInit() method is to set up global data structures +** required by the custom page cache implementation. +** ^(If the xInit() method is NULL, then the +** built-in default page cache is used instead of the application defined +** page cache.)^ +** +** [[the xShutdown() page cache method]] +** ^The xShutdown() method is called by [sqlite3_shutdown()]. +** It can be used to clean up +** any outstanding resources before process shutdown, if required. +** ^The xShutdown() method may be NULL. +** +** ^SQLite automatically serializes calls to the xInit method, +** so the xInit method need not be threadsafe. ^The +** xShutdown method is only called from [sqlite3_shutdown()] so it does +** not need to be threadsafe either. All other methods must be threadsafe +** in multithreaded applications. +** +** ^SQLite will never invoke xInit() more than once without an intervening +** call to xShutdown(). +** +** [[the xCreate() page cache methods]] +** ^SQLite invokes the xCreate() method to construct a new cache instance. +** SQLite will typically create one cache instance for each open database file, +** though this is not guaranteed. ^The +** first parameter, szPage, is the size in bytes of the pages that must +** be allocated by the cache. ^szPage will always a power of two. ^The +** second parameter szExtra is a number of bytes of extra storage +** associated with each page cache entry. ^The szExtra parameter will +** a number less than 250. SQLite will use the +** extra szExtra bytes on each page to store metadata about the underlying +** database page on disk. The value passed into szExtra depends +** on the SQLite version, the target platform, and how SQLite was compiled. +** ^The third argument to xCreate(), bPurgeable, is true if the cache being +** created will be used to cache database pages of a file stored on disk, or +** false if it is used for an in-memory database. The cache implementation +** does not have to do anything special based with the value of bPurgeable; +** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will +** never invoke xUnpin() except to deliberately delete a page. +** ^In other words, calls to xUnpin() on a cache with bPurgeable set to +** false will always have the "discard" flag set to true. +** ^Hence, a cache created with bPurgeable false will +** never contain any unpinned pages. +** +** [[the xCachesize() page cache method]] +** ^(The xCachesize() method may be called at any time by SQLite to set the +** suggested maximum cache-size (number of pages stored by) the cache +** instance passed as the first argument. This is the value configured using +** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable +** parameter, the implementation is not required to do anything with this +** value; it is advisory only. +** +** [[the xPagecount() page cache methods]] +** The xPagecount() method must return the number of pages currently +** stored in the cache, both pinned and unpinned. +** +** [[the xFetch() page cache methods]] +** The xFetch() method locates a page in the cache and returns a pointer to +** an sqlite3_pcache_page object associated with that page, or a NULL pointer. +** The pBuf element of the returned sqlite3_pcache_page object will be a +** pointer to a buffer of szPage bytes used to store the content of a +** single database page. The pExtra element of sqlite3_pcache_page will be +** a pointer to the szExtra bytes of extra storage that SQLite has requested +** for each entry in the page cache. +** +** The page to be fetched is determined by the key. ^The minimum key value +** is 1. After it has been retrieved using xFetch, the page is considered +** to be "pinned". +** +** If the requested page is already in the page cache, then the page cache +** implementation must return a pointer to the page buffer with its content +** intact. If the requested page is not already in the cache, then the +** cache implementation should use the value of the createFlag +** parameter to help it determined what action to take: +** +** <table border=1 width=85% align=center> +** <tr><th> createFlag <th> Behavior when page is not already in cache +** <tr><td> 0 <td> Do not allocate a new page. Return NULL. +** <tr><td> 1 <td> Allocate a new page if it easy and convenient to do so. +** Otherwise return NULL. +** <tr><td> 2 <td> Make every effort to allocate a new page. Only return +** NULL if allocating a new page is effectively impossible. +** </table> +** +** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1. SQLite +** will only use a createFlag of 2 after a prior call with a createFlag of 1 +** failed.)^ In between the xFetch() calls, SQLite may +** attempt to unpin one or more cache pages by spilling the content of +** pinned pages to disk and synching the operating system disk cache. +** +** [[the xUnpin() page cache method]] +** ^xUnpin() is called by SQLite with a pointer to a currently pinned page +** as its second argument. If the third parameter, discard, is non-zero, +** then the page must be evicted from the cache. +** ^If the discard parameter is +** zero, then the page may be discarded or retained at the discretion of +** page cache implementation. ^The page cache implementation +** may choose to evict unpinned pages at any time. +** +** The cache must not perform any reference counting. A single +** call to xUnpin() unpins the page regardless of the number of prior calls +** to xFetch(). +** +** [[the xRekey() page cache methods]] +** The xRekey() method is used to change the key value associated with the +** page passed as the second argument. If the cache +** previously contains an entry associated with newKey, it must be +** discarded. ^Any prior cache entry associated with newKey is guaranteed not +** to be pinned. +** +** When SQLite calls the xTruncate() method, the cache must discard all +** existing cache entries with page numbers (keys) greater than or equal +** to the value of the iLimit parameter passed to xTruncate(). If any +** of these pages are pinned, they are implicitly unpinned, meaning that +** they can be safely discarded. +** +** [[the xDestroy() page cache method]] +** ^The xDestroy() method is used to delete a cache allocated by xCreate(). +** All resources associated with the specified cache should be freed. ^After +** calling the xDestroy() method, SQLite considers the [sqlite3_pcache*] +** handle invalid, and will not use it with any other sqlite3_pcache_methods2 +** functions. +** +** [[the xShrink() page cache method]] +** ^SQLite invokes the xShrink() method when it wants the page cache to +** free up as much of heap memory as possible. The page cache implementation +** is not obligated to free any memory, but well-behaved implementations should +** do their best. +*/ +typedef struct sqlite3_pcache_methods2 sqlite3_pcache_methods2; +struct sqlite3_pcache_methods2 { + int iVersion; + void *pArg; + int (*xInit)(void*); + void (*xShutdown)(void*); + sqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable); + void (*xCachesize)(sqlite3_pcache*, int nCachesize); + int (*xPagecount)(sqlite3_pcache*); + sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); + void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard); + void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, + unsigned oldKey, unsigned newKey); + void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); + void (*xDestroy)(sqlite3_pcache*); + void (*xShrink)(sqlite3_pcache*); +}; + +/* +** This is the obsolete pcache_methods object that has now been replaced +** by sqlite3_pcache_methods2. This object is not used by SQLite. It is +** retained in the header file for backwards compatibility only. +*/ +typedef struct sqlite3_pcache_methods sqlite3_pcache_methods; +struct sqlite3_pcache_methods { + void *pArg; + int (*xInit)(void*); + void (*xShutdown)(void*); + sqlite3_pcache *(*xCreate)(int szPage, int bPurgeable); + void (*xCachesize)(sqlite3_pcache*, int nCachesize); + int (*xPagecount)(sqlite3_pcache*); + void *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); + void (*xUnpin)(sqlite3_pcache*, void*, int discard); + void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey); + void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); + void (*xDestroy)(sqlite3_pcache*); +}; + + +/* +** CAPI3REF: Online Backup Object +** +** The sqlite3_backup object records state information about an ongoing +** online backup operation. ^The sqlite3_backup object is created by +** a call to [sqlite3_backup_init()] and is destroyed by a call to +** [sqlite3_backup_finish()]. +** +** See Also: [Using the SQLite Online Backup API] +*/ +typedef struct sqlite3_backup sqlite3_backup; + +/* +** CAPI3REF: Online Backup API. +** +** The backup API copies the content of one database into another. +** It is useful either for creating backups of databases or +** for copying in-memory databases to or from persistent files. +** +** See Also: [Using the SQLite Online Backup API] +** +** ^SQLite holds a write transaction open on the destination database file +** for the duration of the backup operation. +** ^The source database is read-locked only while it is being read; +** it is not locked continuously for the entire backup operation. +** ^Thus, the backup may be performed on a live source database without +** preventing other database connections from +** reading or writing to the source database while the backup is underway. +** +** ^(To perform a backup operation: +** <ol> +** <li><b>sqlite3_backup_init()</b> is called once to initialize the +** backup, +** <li><b>sqlite3_backup_step()</b> is called one or more times to transfer +** the data between the two databases, and finally +** <li><b>sqlite3_backup_finish()</b> is called to release all resources +** associated with the backup operation. +** </ol>)^ +** There should be exactly one call to sqlite3_backup_finish() for each +** successful call to sqlite3_backup_init(). +** +** [[sqlite3_backup_init()]] <b>sqlite3_backup_init()</b> +** +** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the +** [database connection] associated with the destination database +** and the database name, respectively. +** ^The database name is "main" for the main database, "temp" for the +** temporary database, or the name specified after the AS keyword in +** an [ATTACH] statement for an attached database. +** ^The S and M arguments passed to +** sqlite3_backup_init(D,N,S,M) identify the [database connection] +** and database name of the source database, respectively. +** ^The source and destination [database connections] (parameters S and D) +** must be different or else sqlite3_backup_init(D,N,S,M) will fail with +** an error. +** +** ^A call to sqlite3_backup_init() will fail, returning NULL, if +** there is already a read or read-write transaction open on the +** destination database. +** +** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is +** returned and an error code and error message are stored in the +** destination [database connection] D. +** ^The error code and message for the failed call to sqlite3_backup_init() +** can be retrieved using the [sqlite3_errcode()], [sqlite3_errmsg()], and/or +** [sqlite3_errmsg16()] functions. +** ^A successful call to sqlite3_backup_init() returns a pointer to an +** [sqlite3_backup] object. +** ^The [sqlite3_backup] object may be used with the sqlite3_backup_step() and +** sqlite3_backup_finish() functions to perform the specified backup +** operation. +** +** [[sqlite3_backup_step()]] <b>sqlite3_backup_step()</b> +** +** ^Function sqlite3_backup_step(B,N) will copy up to N pages between +** the source and destination databases specified by [sqlite3_backup] object B. +** ^If N is negative, all remaining source pages are copied. +** ^If sqlite3_backup_step(B,N) successfully copies N pages and there +** are still more pages to be copied, then the function returns [SQLITE_OK]. +** ^If sqlite3_backup_step(B,N) successfully finishes copying all pages +** from source to destination, then it returns [SQLITE_DONE]. +** ^If an error occurs while running sqlite3_backup_step(B,N), +** then an [error code] is returned. ^As well as [SQLITE_OK] and +** [SQLITE_DONE], a call to sqlite3_backup_step() may return [SQLITE_READONLY], +** [SQLITE_NOMEM], [SQLITE_BUSY], [SQLITE_LOCKED], or an +** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX] extended error code. +** +** ^(The sqlite3_backup_step() might return [SQLITE_READONLY] if +** <ol> +** <li> the destination database was opened read-only, or +** <li> the destination database is using write-ahead-log journaling +** and the destination and source page sizes differ, or +** <li> the destination database is an in-memory database and the +** destination and source page sizes differ. +** </ol>)^ +** +** ^If sqlite3_backup_step() cannot obtain a required file-system lock, then +** the [sqlite3_busy_handler | busy-handler function] +** is invoked (if one is specified). ^If the +** busy-handler returns non-zero before the lock is available, then +** [SQLITE_BUSY] is returned to the caller. ^In this case the call to +** sqlite3_backup_step() can be retried later. ^If the source +** [database connection] +** is being used to write to the source database when sqlite3_backup_step() +** is called, then [SQLITE_LOCKED] is returned immediately. ^Again, in this +** case the call to sqlite3_backup_step() can be retried later on. ^(If +** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or +** [SQLITE_READONLY] is returned, then +** there is no point in retrying the call to sqlite3_backup_step(). These +** errors are considered fatal.)^ The application must accept +** that the backup operation has failed and pass the backup operation handle +** to the sqlite3_backup_finish() to release associated resources. +** +** ^The first call to sqlite3_backup_step() obtains an exclusive lock +** on the destination file. ^The exclusive lock is not released until either +** sqlite3_backup_finish() is called or the backup operation is complete +** and sqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to +** sqlite3_backup_step() obtains a [shared lock] on the source database that +** lasts for the duration of the sqlite3_backup_step() call. +** ^Because the source database is not locked between calls to +** sqlite3_backup_step(), the source database may be modified mid-way +** through the backup process. ^If the source database is modified by an +** external process or via a database connection other than the one being +** used by the backup operation, then the backup will be automatically +** restarted by the next call to sqlite3_backup_step(). ^If the source +** database is modified by the using the same database connection as is used +** by the backup operation, then the backup database is automatically +** updated at the same time. +** +** [[sqlite3_backup_finish()]] <b>sqlite3_backup_finish()</b> +** +** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the +** application wishes to abandon the backup operation, the application +** should destroy the [sqlite3_backup] by passing it to sqlite3_backup_finish(). +** ^The sqlite3_backup_finish() interfaces releases all +** resources associated with the [sqlite3_backup] object. +** ^If sqlite3_backup_step() has not yet returned [SQLITE_DONE], then any +** active write-transaction on the destination database is rolled back. +** The [sqlite3_backup] object is invalid +** and may not be used following a call to sqlite3_backup_finish(). +** +** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no +** sqlite3_backup_step() errors occurred, regardless or whether or not +** sqlite3_backup_step() completed. +** ^If an out-of-memory condition or IO error occurred during any prior +** sqlite3_backup_step() call on the same [sqlite3_backup] object, then +** sqlite3_backup_finish() returns the corresponding [error code]. +** +** ^A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from sqlite3_backup_step() +** is not a permanent error and does not affect the return value of +** sqlite3_backup_finish(). +** +** [[sqlite3_backup_remaining()]] [[sqlite3_backup_pagecount()]] +** <b>sqlite3_backup_remaining() and sqlite3_backup_pagecount()</b> +** +** ^The sqlite3_backup_remaining() routine returns the number of pages still +** to be backed up at the conclusion of the most recent sqlite3_backup_step(). +** ^The sqlite3_backup_pagecount() routine returns the total number of pages +** in the source database at the conclusion of the most recent +** sqlite3_backup_step(). +** ^(The values returned by these functions are only updated by +** sqlite3_backup_step(). If the source database is modified in a way that +** changes the size of the source database or the number of pages remaining, +** those changes are not reflected in the output of sqlite3_backup_pagecount() +** and sqlite3_backup_remaining() until after the next +** sqlite3_backup_step().)^ +** +** <b>Concurrent Usage of Database Handles</b> +** +** ^The source [database connection] may be used by the application for other +** purposes while a backup operation is underway or being initialized. +** ^If SQLite is compiled and configured to support threadsafe database +** connections, then the source database connection may be used concurrently +** from within other threads. +** +** However, the application must guarantee that the destination +** [database connection] is not passed to any other API (by any thread) after +** sqlite3_backup_init() is called and before the corresponding call to +** sqlite3_backup_finish(). SQLite does not currently check to see +** if the application incorrectly accesses the destination [database connection] +** and so no error code is reported, but the operations may malfunction +** nevertheless. Use of the destination database connection while a +** backup is in progress might also cause a mutex deadlock. +** +** If running in [shared cache mode], the application must +** guarantee that the shared cache used by the destination database +** is not accessed while the backup is running. In practice this means +** that the application must guarantee that the disk file being +** backed up to is not accessed by any connection within the process, +** not just the specific connection that was passed to sqlite3_backup_init(). +** +** The [sqlite3_backup] object itself is partially threadsafe. Multiple +** threads may safely make multiple concurrent calls to sqlite3_backup_step(). +** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() +** APIs are not strictly speaking threadsafe. If they are invoked at the +** same time as another thread is invoking sqlite3_backup_step() it is +** possible that they return invalid values. +*/ +SQLITE_API sqlite3_backup *sqlite3_backup_init( + sqlite3 *pDest, /* Destination database handle */ + const char *zDestName, /* Destination database name */ + sqlite3 *pSource, /* Source database handle */ + const char *zSourceName /* Source database name */ +); +SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage); +SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p); +SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p); +SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); + +/* +** CAPI3REF: Unlock Notification +** METHOD: sqlite3 +** +** ^When running in shared-cache mode, a database operation may fail with +** an [SQLITE_LOCKED] error if the required locks on the shared-cache or +** individual tables within the shared-cache cannot be obtained. See +** [SQLite Shared-Cache Mode] for a description of shared-cache locking. +** ^This API may be used to register a callback that SQLite will invoke +** when the connection currently holding the required lock relinquishes it. +** ^This API is only available if the library was compiled with the +** [SQLITE_ENABLE_UNLOCK_NOTIFY] C-preprocessor symbol defined. +** +** See Also: [Using the SQLite Unlock Notification Feature]. +** +** ^Shared-cache locks are released when a database connection concludes +** its current transaction, either by committing it or rolling it back. +** +** ^When a connection (known as the blocked connection) fails to obtain a +** shared-cache lock and SQLITE_LOCKED is returned to the caller, the +** identity of the database connection (the blocking connection) that +** has locked the required resource is stored internally. ^After an +** application receives an SQLITE_LOCKED error, it may call the +** sqlite3_unlock_notify() method with the blocked connection handle as +** the first argument to register for a callback that will be invoked +** when the blocking connections current transaction is concluded. ^The +** callback is invoked from within the [sqlite3_step] or [sqlite3_close] +** call that concludes the blocking connection's transaction. +** +** ^(If sqlite3_unlock_notify() is called in a multi-threaded application, +** there is a chance that the blocking connection will have already +** concluded its transaction by the time sqlite3_unlock_notify() is invoked. +** If this happens, then the specified callback is invoked immediately, +** from within the call to sqlite3_unlock_notify().)^ +** +** ^If the blocked connection is attempting to obtain a write-lock on a +** shared-cache table, and more than one other connection currently holds +** a read-lock on the same table, then SQLite arbitrarily selects one of +** the other connections to use as the blocking connection. +** +** ^(There may be at most one unlock-notify callback registered by a +** blocked connection. If sqlite3_unlock_notify() is called when the +** blocked connection already has a registered unlock-notify callback, +** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is +** called with a NULL pointer as its second argument, then any existing +** unlock-notify callback is cancelled. ^The blocked connections +** unlock-notify callback may also be cancelled by closing the blocked +** connection using [sqlite3_close()]. +** +** The unlock-notify callback is not reentrant. If an application invokes +** any sqlite3_xxx API functions from within an unlock-notify callback, a +** crash or deadlock may be the result. +** +** ^Unless deadlock is detected (see below), sqlite3_unlock_notify() always +** returns SQLITE_OK. +** +** <b>Callback Invocation Details</b> +** +** When an unlock-notify callback is registered, the application provides a +** single void* pointer that is passed to the callback when it is invoked. +** However, the signature of the callback function allows SQLite to pass +** it an array of void* context pointers. The first argument passed to +** an unlock-notify callback is a pointer to an array of void* pointers, +** and the second is the number of entries in the array. +** +** When a blocking connection's transaction is concluded, there may be +** more than one blocked connection that has registered for an unlock-notify +** callback. ^If two or more such blocked connections have specified the +** same callback function, then instead of invoking the callback function +** multiple times, it is invoked once with the set of void* context pointers +** specified by the blocked connections bundled together into an array. +** This gives the application an opportunity to prioritize any actions +** related to the set of unblocked database connections. +** +** <b>Deadlock Detection</b> +** +** Assuming that after registering for an unlock-notify callback a +** database waits for the callback to be issued before taking any further +** action (a reasonable assumption), then using this API may cause the +** application to deadlock. For example, if connection X is waiting for +** connection Y's transaction to be concluded, and similarly connection +** Y is waiting on connection X's transaction, then neither connection +** will proceed and the system may remain deadlocked indefinitely. +** +** To avoid this scenario, the sqlite3_unlock_notify() performs deadlock +** detection. ^If a given call to sqlite3_unlock_notify() would put the +** system in a deadlocked state, then SQLITE_LOCKED is returned and no +** unlock-notify callback is registered. The system is said to be in +** a deadlocked state if connection A has registered for an unlock-notify +** callback on the conclusion of connection B's transaction, and connection +** B has itself registered for an unlock-notify callback when connection +** A's transaction is concluded. ^Indirect deadlock is also detected, so +** the system is also considered to be deadlocked if connection B has +** registered for an unlock-notify callback on the conclusion of connection +** C's transaction, where connection C is waiting on connection A. ^Any +** number of levels of indirection are allowed. +** +** <b>The "DROP TABLE" Exception</b> +** +** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost +** always appropriate to call sqlite3_unlock_notify(). There is however, +** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement, +** SQLite checks if there are any currently executing SELECT statements +** that belong to the same connection. If there are, SQLITE_LOCKED is +** returned. In this case there is no "blocking connection", so invoking +** sqlite3_unlock_notify() results in the unlock-notify callback being +** invoked immediately. If the application then re-attempts the "DROP TABLE" +** or "DROP INDEX" query, an infinite loop might be the result. +** +** One way around this problem is to check the extended error code returned +** by an sqlite3_step() call. ^(If there is a blocking connection, then the +** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in +** the special "DROP TABLE/INDEX" case, the extended error code is just +** SQLITE_LOCKED.)^ +*/ +SQLITE_API int sqlite3_unlock_notify( + sqlite3 *pBlocked, /* Waiting connection */ + void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */ + void *pNotifyArg /* Argument to pass to xNotify */ +); + + +/* +** CAPI3REF: String Comparison +** +** ^The [sqlite3_stricmp()] and [sqlite3_strnicmp()] APIs allow applications +** and extensions to compare the contents of two buffers containing UTF-8 +** strings in a case-independent fashion, using the same definition of "case +** independence" that SQLite uses internally when comparing identifiers. +*/ +SQLITE_API int sqlite3_stricmp(const char *, const char *); +SQLITE_API int sqlite3_strnicmp(const char *, const char *, int); + +/* +** CAPI3REF: String Globbing +* +** ^The [sqlite3_strglob(P,X)] interface returns zero if and only if +** string X matches the [GLOB] pattern P. +** ^The definition of [GLOB] pattern matching used in +** [sqlite3_strglob(P,X)] is the same as for the "X GLOB P" operator in the +** SQL dialect understood by SQLite. ^The [sqlite3_strglob(P,X)] function +** is case sensitive. +** +** Note that this routine returns zero on a match and non-zero if the strings +** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()]. +** +** See also: [sqlite3_strlike()]. +*/ +SQLITE_API int sqlite3_strglob(const char *zGlob, const char *zStr); + +/* +** CAPI3REF: String LIKE Matching +* +** ^The [sqlite3_strlike(P,X,E)] interface returns zero if and only if +** string X matches the [LIKE] pattern P with escape character E. +** ^The definition of [LIKE] pattern matching used in +** [sqlite3_strlike(P,X,E)] is the same as for the "X LIKE P ESCAPE E" +** operator in the SQL dialect understood by SQLite. ^For "X LIKE P" without +** the ESCAPE clause, set the E parameter of [sqlite3_strlike(P,X,E)] to 0. +** ^As with the LIKE operator, the [sqlite3_strlike(P,X,E)] function is case +** insensitive - equivalent upper and lower case ASCII characters match +** one another. +** +** ^The [sqlite3_strlike(P,X,E)] function matches Unicode characters, though +** only ASCII characters are case folded. +** +** Note that this routine returns zero on a match and non-zero if the strings +** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()]. +** +** See also: [sqlite3_strglob()]. +*/ +SQLITE_API int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc); + +/* +** CAPI3REF: Error Logging Interface +** +** ^The [sqlite3_log()] interface writes a message into the [error log] +** established by the [SQLITE_CONFIG_LOG] option to [sqlite3_config()]. +** ^If logging is enabled, the zFormat string and subsequent arguments are +** used with [sqlite3_snprintf()] to generate the final output string. +** +** The sqlite3_log() interface is intended for use by extensions such as +** virtual tables, collating functions, and SQL functions. While there is +** nothing to prevent an application from calling sqlite3_log(), doing so +** is considered bad form. +** +** The zFormat string must not be NULL. +** +** To avoid deadlocks and other threading problems, the sqlite3_log() routine +** will not use dynamically allocated memory. The log message is stored in +** a fixed-length buffer on the stack. If the log message is longer than +** a few hundred characters, it will be truncated to the length of the +** buffer. +*/ +SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); + +/* +** CAPI3REF: Write-Ahead Log Commit Hook +** METHOD: sqlite3 +** +** ^The [sqlite3_wal_hook()] function is used to register a callback that +** is invoked each time data is committed to a database in wal mode. +** +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation +** may read, write or [checkpoint] the database as required. +** +** ^The first parameter passed to the callback function when it is invoked +** is a copy of the third parameter passed to sqlite3_wal_hook() when +** registering the callback. ^The second is a copy of the database handle. +** ^The third parameter is the name of the database that was written to - +** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter +** is the number of pages currently in the write-ahead log file, +** including those that were just committed. +** +** The callback function should normally return [SQLITE_OK]. ^If an error +** code is returned, that error will propagate back up through the +** SQLite code base to cause the statement that provoked the callback +** to report an error, though the commit will have still occurred. If the +** callback returns [SQLITE_ROW] or [SQLITE_DONE], or if it returns a value +** that does not correspond to any valid SQLite error code, the results +** are undefined. +** +** A single database handle may have at most a single write-ahead log callback +** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any +** previously registered write-ahead log callback. ^The return value is +** a copy of the third parameter from the previous call, if any, or 0. +** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the +** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will +** overwrite any prior [sqlite3_wal_hook()] settings. +*/ +SQLITE_API void *sqlite3_wal_hook( + sqlite3*, + int(*)(void *,sqlite3*,const char*,int), + void* +); + +/* +** CAPI3REF: Configure an auto-checkpoint +** METHOD: sqlite3 +** +** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around +** [sqlite3_wal_hook()] that causes any database on [database connection] D +** to automatically [checkpoint] +** after committing a transaction if there are N or +** more frames in the [write-ahead log] file. ^Passing zero or +** a negative value as the nFrame parameter disables automatic +** checkpoints entirely. +** +** ^The callback registered by this function replaces any existing callback +** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback +** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism +** configured by this function. +** +** ^The [wal_autocheckpoint pragma] can be used to invoke this interface +** from SQL. +** +** ^Checkpoints initiated by this mechanism are +** [sqlite3_wal_checkpoint_v2|PASSIVE]. +** +** ^Every new [database connection] defaults to having the auto-checkpoint +** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] +** pages. The use of this interface +** is only necessary if the default setting is found to be suboptimal +** for a particular application. +*/ +SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); + +/* +** CAPI3REF: Checkpoint a database +** METHOD: sqlite3 +** +** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to +** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^ +** +** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the +** [write-ahead log] for database X on [database connection] D to be +** transferred into the database file and for the write-ahead log to +** be reset. See the [checkpointing] documentation for addition +** information. +** +** This interface used to be the only way to cause a checkpoint to +** occur. But then the newer and more powerful [sqlite3_wal_checkpoint_v2()] +** interface was added. This interface is retained for backwards +** compatibility and as a convenience for applications that need to manually +** start a callback but which do not need the full power (and corresponding +** complication) of [sqlite3_wal_checkpoint_v2()]. +*/ +SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); + +/* +** CAPI3REF: Checkpoint a database +** METHOD: sqlite3 +** +** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint +** operation on database X of [database connection] D in mode M. Status +** information is written back into integers pointed to by L and C.)^ +** ^(The M parameter must be a valid [checkpoint mode]:)^ +** +** <dl> +** <dt>SQLITE_CHECKPOINT_PASSIVE<dd> +** ^Checkpoint as many frames as possible without waiting for any database +** readers or writers to finish, then sync the database file if all frames +** in the log were checkpointed. ^The [busy-handler callback] +** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. +** ^On the other hand, passive mode might leave the checkpoint unfinished +** if there are concurrent readers or writers. +** +** <dt>SQLITE_CHECKPOINT_FULL<dd> +** ^This mode blocks (it invokes the +** [sqlite3_busy_handler|busy-handler callback]) until there is no +** database writer and all readers are reading from the most recent database +** snapshot. ^It then checkpoints all frames in the log file and syncs the +** database file. ^This mode blocks new database writers while it is pending, +** but new database readers are allowed to continue unimpeded. +** +** <dt>SQLITE_CHECKPOINT_RESTART<dd> +** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition +** that after checkpointing the log file it blocks (calls the +** [busy-handler callback]) +** until all readers are reading from the database file only. ^This ensures +** that the next writer will restart the log file from the beginning. +** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new +** database writer attempts while it is pending, but does not impede readers. +** +** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd> +** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the +** addition that it also truncates the log file to zero bytes just prior +** to a successful return. +** </dl> +** +** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in +** the log file or to -1 if the checkpoint could not run because +** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not +** NULL,then *pnCkpt is set to the total number of checkpointed frames in the +** log file (including any that were already checkpointed before the function +** was called) or to -1 if the checkpoint could not run due to an error or +** because the database is not in WAL mode. ^Note that upon successful +** completion of an SQLITE_CHECKPOINT_TRUNCATE, the log file will have been +** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero. +** +** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If +** any other process is running a checkpoint operation at the same time, the +** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a +** busy-handler configured, it will not be invoked in this case. +** +** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the +** exclusive "writer" lock on the database file. ^If the writer lock cannot be +** obtained immediately, and a busy-handler is configured, it is invoked and +** the writer lock retried until either the busy-handler returns 0 or the lock +** is successfully obtained. ^The busy-handler is also invoked while waiting for +** database readers as described above. ^If the busy-handler returns 0 before +** the writer lock is obtained or while waiting for database readers, the +** checkpoint operation proceeds from that point in the same way as +** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible +** without blocking any further. ^SQLITE_BUSY is returned in this case. +** +** ^If parameter zDb is NULL or points to a zero length string, then the +** specified operation is attempted on all WAL databases [attached] to +** [database connection] db. In this case the +** values written to output parameters *pnLog and *pnCkpt are undefined. ^If +** an SQLITE_BUSY error is encountered when processing one or more of the +** attached WAL databases, the operation is still attempted on any remaining +** attached databases and SQLITE_BUSY is returned at the end. ^If any other +** error occurs while processing an attached database, processing is abandoned +** and the error code is returned to the caller immediately. ^If no error +** (SQLITE_BUSY or otherwise) is encountered while processing the attached +** databases, SQLITE_OK is returned. +** +** ^If database zDb is the name of an attached database that is not in WAL +** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. ^If +** zDb is not NULL (or a zero length string) and is not the name of any +** attached database, SQLITE_ERROR is returned to the caller. +** +** ^Unless it returns SQLITE_MISUSE, +** the sqlite3_wal_checkpoint_v2() interface +** sets the error information that is queried by +** [sqlite3_errcode()] and [sqlite3_errmsg()]. +** +** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface +** from SQL. +*/ +SQLITE_API int sqlite3_wal_checkpoint_v2( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of attached database (or NULL) */ + int eMode, /* SQLITE_CHECKPOINT_* value */ + int *pnLog, /* OUT: Size of WAL log in frames */ + int *pnCkpt /* OUT: Total number of frames checkpointed */ +); + +/* +** CAPI3REF: Checkpoint Mode Values +** KEYWORDS: {checkpoint mode} +** +** These constants define all valid values for the "checkpoint mode" passed +** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. +** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the +** meaning of each of these checkpoint modes. +*/ +#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ +#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ +#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ +#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ + +/* +** CAPI3REF: Virtual Table Interface Configuration +** +** This function may be called by either the [xConnect] or [xCreate] method +** of a [virtual table] implementation to configure +** various facets of the virtual table interface. +** +** If this interface is invoked outside the context of an xConnect or +** xCreate virtual table method then the behavior is undefined. +** +** In the call sqlite3_vtab_config(D,C,...) the D parameter is the +** [database connection] in which the virtual table is being created and +** which is passed in as the first argument to the [xConnect] or [xCreate] +** method that is invoking sqlite3_vtab_config(). The C parameter is one +** of the [virtual table configuration options]. The presence and meaning +** of parameters after C depend on which [virtual table configuration option] +** is used. +*/ +SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); + +/* +** CAPI3REF: Virtual Table Configuration Options +** KEYWORDS: {virtual table configuration options} +** KEYWORDS: {virtual table configuration option} +** +** These macros define the various options to the +** [sqlite3_vtab_config()] interface that [virtual table] implementations +** can use to customize and optimize their behavior. +** +** <dl> +** [[SQLITE_VTAB_CONSTRAINT_SUPPORT]] +** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT</dt> +** <dd>Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported, +** where X is an integer. If X is zero, then the [virtual table] whose +** [xCreate] or [xConnect] method invoked [sqlite3_vtab_config()] does not +** support constraints. In this configuration (which is the default) if +** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire +** statement is rolled back as if [ON CONFLICT | OR ABORT] had been +** specified as part of the users SQL statement, regardless of the actual +** ON CONFLICT mode specified. +** +** If X is non-zero, then the virtual table implementation guarantees +** that if [xUpdate] returns [SQLITE_CONSTRAINT], it will do so before +** any modifications to internal or persistent data structures have been made. +** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite +** is able to roll back a statement or database transaction, and abandon +** or continue processing the current SQL statement as appropriate. +** If the ON CONFLICT mode is REPLACE and the [xUpdate] method returns +** [SQLITE_CONSTRAINT], SQLite handles this as if the ON CONFLICT mode +** had been ABORT. +** +** Virtual table implementations that are required to handle OR REPLACE +** must do so within the [xUpdate] method. If a call to the +** [sqlite3_vtab_on_conflict()] function indicates that the current ON +** CONFLICT policy is REPLACE, the virtual table implementation should +** silently replace the appropriate rows within the xUpdate callback and +** return SQLITE_OK. Or, if this is not possible, it may return +** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT +** constraint handling. +** </dd> +** +** [[SQLITE_VTAB_DIRECTONLY]]<dt>SQLITE_VTAB_DIRECTONLY</dt> +** <dd>Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** prohibits that virtual table from being used from within triggers and +** views. +** </dd> +** +** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt> +** <dd>Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** identify that virtual table as being safe to use from within triggers +** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the +** virtual table can do no serious harm even if it is controlled by a +** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS +** flag unless absolutely necessary. +** </dd> +** +** [[SQLITE_VTAB_USES_ALL_SCHEMAS]]<dt>SQLITE_VTAB_USES_ALL_SCHEMAS</dt> +** <dd>Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_USES_ALL_SCHEMA) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** instruct the query planner to begin at least a read transaction on +** all schemas ("main", "temp", and any ATTACH-ed databases) whenever the +** virtual table is used. +** </dd> +** </dl> +*/ +#define SQLITE_VTAB_CONSTRAINT_SUPPORT 1 +#define SQLITE_VTAB_INNOCUOUS 2 +#define SQLITE_VTAB_DIRECTONLY 3 +#define SQLITE_VTAB_USES_ALL_SCHEMAS 4 + +/* +** CAPI3REF: Determine The Virtual Table Conflict Policy +** +** This function may only be called from within a call to the [xUpdate] method +** of a [virtual table] implementation for an INSERT or UPDATE operation. ^The +** value returned is one of [SQLITE_ROLLBACK], [SQLITE_IGNORE], [SQLITE_FAIL], +** [SQLITE_ABORT], or [SQLITE_REPLACE], according to the [ON CONFLICT] mode +** of the SQL statement that triggered the call to the [xUpdate] method of the +** [virtual table]. +*/ +SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); + +/* +** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE +** +** If the sqlite3_vtab_nochange(X) routine is called within the [xColumn] +** method of a [virtual table], then it might return true if the +** column is being fetched as part of an UPDATE operation during which the +** column value will not change. The virtual table implementation can use +** this hint as permission to substitute a return value that is less +** expensive to compute and that the corresponding +** [xUpdate] method understands as a "no-change" value. +** +** If the [xColumn] method calls sqlite3_vtab_nochange() and finds that +** the column is not changed by the UPDATE statement, then the xColumn +** method can optionally return without setting a result, without calling +** any of the [sqlite3_result_int|sqlite3_result_xxxxx() interfaces]. +** In that case, [sqlite3_value_nochange(X)] will return true for the +** same column in the [xUpdate] method. +** +** The sqlite3_vtab_nochange() routine is an optimization. Virtual table +** implementations should continue to give a correct answer even if the +** sqlite3_vtab_nochange() interface were to always return false. In the +** current implementation, the sqlite3_vtab_nochange() interface does always +** returns false for the enhanced [UPDATE FROM] statement. +*/ +SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*); + +/* +** CAPI3REF: Determine The Collation For a Virtual Table Constraint +** METHOD: sqlite3_index_info +** +** This function may only be called from within a call to the [xBestIndex] +** method of a [virtual table]. This function returns a pointer to a string +** that is the name of the appropriate collation sequence to use for text +** comparisons on the constraint identified by its arguments. +** +** The first argument must be the pointer to the [sqlite3_index_info] object +** that is the first parameter to the xBestIndex() method. The second argument +** must be an index into the aConstraint[] array belonging to the +** sqlite3_index_info structure passed to xBestIndex. +** +** Important: +** The first parameter must be the same pointer that is passed into the +** xBestMethod() method. The first parameter may not be a pointer to a +** different [sqlite3_index_info] object, even an exact copy. +** +** The return value is computed as follows: +** +** <ol> +** <li><p> If the constraint comes from a WHERE clause expression that contains +** a [COLLATE operator], then the name of the collation specified by +** that COLLATE operator is returned. +** <li><p> If there is no COLLATE operator, but the column that is the subject +** of the constraint specifies an alternative collating sequence via +** a [COLLATE clause] on the column definition within the CREATE TABLE +** statement that was passed into [sqlite3_declare_vtab()], then the +** name of that alternative collating sequence is returned. +** <li><p> Otherwise, "BINARY" is returned. +** </ol> +*/ +SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int); + +/* +** CAPI3REF: Determine if a virtual table query is DISTINCT +** METHOD: sqlite3_index_info +** +** This API may only be used from within an [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this +** interface from outside of xBestIndex() is undefined and probably harmful. +** +** ^The sqlite3_vtab_distinct() interface returns an integer between 0 and +** 3. The integer returned by sqlite3_vtab_distinct() +** gives the virtual table additional information about how the query +** planner wants the output to be ordered. As long as the virtual table +** can meet the ordering requirements of the query planner, it may set +** the "orderByConsumed" flag. +** +** <ol><li value="0"><p> +** ^If the sqlite3_vtab_distinct() interface returns 0, that means +** that the query planner needs the virtual table to return all rows in the +** sort order defined by the "nOrderBy" and "aOrderBy" fields of the +** [sqlite3_index_info] object. This is the default expectation. If the +** virtual table outputs all rows in sorted order, then it is always safe for +** the xBestIndex method to set the "orderByConsumed" flag, regardless of +** the return value from sqlite3_vtab_distinct(). +** <li value="1"><p> +** ^(If the sqlite3_vtab_distinct() interface returns 1, that means +** that the query planner does not need the rows to be returned in sorted order +** as long as all rows with the same values in all columns identified by the +** "aOrderBy" field are adjacent.)^ This mode is used when the query planner +** is doing a GROUP BY. +** <li value="2"><p> +** ^(If the sqlite3_vtab_distinct() interface returns 2, that means +** that the query planner does not need the rows returned in any particular +** order, as long as rows with the same values in all "aOrderBy" columns +** are adjacent.)^ ^(Furthermore, only a single row for each particular +** combination of values in the columns identified by the "aOrderBy" field +** needs to be returned.)^ ^It is always ok for two or more rows with the same +** values in all "aOrderBy" columns to be returned, as long as all such rows +** are adjacent. ^The virtual table may, if it chooses, omit extra rows +** that have the same value for all columns identified by "aOrderBy". +** ^However omitting the extra rows is optional. +** This mode is used for a DISTINCT query. +** <li value="3"><p> +** ^(If the sqlite3_vtab_distinct() interface returns 3, that means +** that the query planner needs only distinct rows but it does need the +** rows to be sorted.)^ ^The virtual table implementation is free to omit +** rows that are identical in all aOrderBy columns, if it wants to, but +** it is not required to omit any rows. This mode is used for queries +** that have both DISTINCT and ORDER BY clauses. +** </ol> +** +** ^For the purposes of comparing virtual table output values to see if the +** values are same value for sorting purposes, two NULL values are considered +** to be the same. In other words, the comparison operator is "IS" +** (or "IS NOT DISTINCT FROM") and not "==". +** +** If a virtual table implementation is unable to meet the requirements +** specified above, then it must not set the "orderByConsumed" flag in the +** [sqlite3_index_info] object or an incorrect answer may result. +** +** ^A virtual table implementation is always free to return rows in any order +** it wants, as long as the "orderByConsumed" flag is not set. ^When the +** the "orderByConsumed" flag is unset, the query planner will add extra +** [bytecode] to ensure that the final results returned by the SQL query are +** ordered correctly. The use of the "orderByConsumed" flag and the +** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful +** use of the sqlite3_vtab_distinct() interface and the "orderByConsumed" +** flag might help queries against a virtual table to run faster. Being +** overly aggressive and setting the "orderByConsumed" flag when it is not +** valid to do so, on the other hand, might cause SQLite to return incorrect +** results. +*/ +SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*); + +/* +** CAPI3REF: Identify and handle IN constraints in xBestIndex +** +** This interface may only be used from within an +** [xBestIndex|xBestIndex() method] of a [virtual table] implementation. +** The result of invoking this interface from any other context is +** undefined and probably harmful. +** +** ^(A constraint on a virtual table of the form +** "[IN operator|column IN (...)]" is +** communicated to the xBestIndex method as a +** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use +** this constraint, it must set the corresponding +** aConstraintUsage[].argvIndex to a positive integer. ^(Then, under +** the usual mode of handling IN operators, SQLite generates [bytecode] +** that invokes the [xFilter|xFilter() method] once for each value +** on the right-hand side of the IN operator.)^ Thus the virtual table +** only sees a single value from the right-hand side of the IN operator +** at a time. +** +** In some cases, however, it would be advantageous for the virtual +** table to see all values on the right-hand of the IN operator all at +** once. The sqlite3_vtab_in() interfaces facilitates this in two ways: +** +** <ol> +** <li><p> +** ^A call to sqlite3_vtab_in(P,N,-1) will return true (non-zero) +** if and only if the [sqlite3_index_info|P->aConstraint][N] constraint +** is an [IN operator] that can be processed all at once. ^In other words, +** sqlite3_vtab_in() with -1 in the third argument is a mechanism +** by which the virtual table can ask SQLite if all-at-once processing +** of the IN operator is even possible. +** +** <li><p> +** ^A call to sqlite3_vtab_in(P,N,F) with F==1 or F==0 indicates +** to SQLite that the virtual table does or does not want to process +** the IN operator all-at-once, respectively. ^Thus when the third +** parameter (F) is non-negative, this interface is the mechanism by +** which the virtual table tells SQLite how it wants to process the +** IN operator. +** </ol> +** +** ^The sqlite3_vtab_in(P,N,F) interface can be invoked multiple times +** within the same xBestIndex method call. ^For any given P,N pair, +** the return value from sqlite3_vtab_in(P,N,F) will always be the same +** within the same xBestIndex call. ^If the interface returns true +** (non-zero), that means that the constraint is an IN operator +** that can be processed all-at-once. ^If the constraint is not an IN +** operator or cannot be processed all-at-once, then the interface returns +** false. +** +** ^(All-at-once processing of the IN operator is selected if both of the +** following conditions are met: +** +** <ol> +** <li><p> The P->aConstraintUsage[N].argvIndex value is set to a positive +** integer. This is how the virtual table tells SQLite that it wants to +** use the N-th constraint. +** +** <li><p> The last call to sqlite3_vtab_in(P,N,F) for which F was +** non-negative had F>=1. +** </ol>)^ +** +** ^If either or both of the conditions above are false, then SQLite uses +** the traditional one-at-a-time processing strategy for the IN constraint. +** ^If both conditions are true, then the argvIndex-th parameter to the +** xFilter method will be an [sqlite3_value] that appears to be NULL, +** but which can be passed to [sqlite3_vtab_in_first()] and +** [sqlite3_vtab_in_next()] to find all values on the right-hand side +** of the IN constraint. +*/ +SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); + +/* +** CAPI3REF: Find all elements on the right-hand side of an IN constraint. +** +** These interfaces are only useful from within the +** [xFilter|xFilter() method] of a [virtual table] implementation. +** The result of invoking these interfaces from any other context +** is undefined and probably harmful. +** +** The X parameter in a call to sqlite3_vtab_in_first(X,P) or +** sqlite3_vtab_in_next(X,P) should be one of the parameters to the +** xFilter method which invokes these routines, and specifically +** a parameter that was previously selected for all-at-once IN constraint +** processing use the [sqlite3_vtab_in()] interface in the +** [xBestIndex|xBestIndex method]. ^(If the X parameter is not +** an xFilter argument that was selected for all-at-once IN constraint +** processing, then these routines return [SQLITE_ERROR].)^ +** +** ^(Use these routines to access all values on the right-hand side +** of the IN constraint using code like the following: +** +** <blockquote><pre> +** &nbsp; for(rc=sqlite3_vtab_in_first(pList, &pVal); +** &nbsp; rc==SQLITE_OK && pVal; +** &nbsp; rc=sqlite3_vtab_in_next(pList, &pVal) +** &nbsp; ){ +** &nbsp; // do something with pVal +** &nbsp; } +** &nbsp; if( rc!=SQLITE_OK ){ +** &nbsp; // an error has occurred +** &nbsp; } +** </pre></blockquote>)^ +** +** ^On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) +** routines return SQLITE_OK and set *P to point to the first or next value +** on the RHS of the IN constraint. ^If there are no more values on the +** right hand side of the IN constraint, then *P is set to NULL and these +** routines return [SQLITE_DONE]. ^The return value might be +** some other value, such as SQLITE_NOMEM, in the event of a malfunction. +** +** The *ppOut values returned by these routines are only valid until the +** next call to either of these routines or until the end of the xFilter +** method from which these routines were called. If the virtual table +** implementation needs to retain the *ppOut values for longer, it must make +** copies. The *ppOut values are [protected sqlite3_value|protected]. +*/ +SQLITE_API int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); +SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); + +/* +** CAPI3REF: Constraint values in xBestIndex() +** METHOD: sqlite3_index_info +** +** This API may only be used from within the [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this interface +** from outside of an xBestIndex method are undefined and probably harmful. +** +** ^When the sqlite3_vtab_rhs_value(P,J,V) interface is invoked from within +** the [xBestIndex] method of a [virtual table] implementation, with P being +** a copy of the [sqlite3_index_info] object pointer passed into xBestIndex and +** J being a 0-based index into P->aConstraint[], then this routine +** attempts to set *V to the value of the right-hand operand of +** that constraint if the right-hand operand is known. ^If the +** right-hand operand is not known, then *V is set to a NULL pointer. +** ^The sqlite3_vtab_rhs_value(P,J,V) interface returns SQLITE_OK if +** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V) +** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th +** constraint is not available. ^The sqlite3_vtab_rhs_value() interface +** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if +** something goes wrong. +** +** The sqlite3_vtab_rhs_value() interface is usually only successful if +** the right-hand operand of a constraint is a literal value in the original +** SQL statement. If the right-hand operand is an expression or a reference +** to some other column or a [host parameter], then sqlite3_vtab_rhs_value() +** will probably return [SQLITE_NOTFOUND]. +** +** ^(Some constraints, such as [SQLITE_INDEX_CONSTRAINT_ISNULL] and +** [SQLITE_INDEX_CONSTRAINT_ISNOTNULL], have no right-hand operand. For such +** constraints, sqlite3_vtab_rhs_value() always returns SQLITE_NOTFOUND.)^ +** +** ^The [sqlite3_value] object returned in *V is a protected sqlite3_value +** and remains valid for the duration of the xBestIndex method call. +** ^When xBestIndex returns, the sqlite3_value object returned by +** sqlite3_vtab_rhs_value() is automatically deallocated. +** +** The "_rhs_" in the name of this routine is an abbreviation for +** "Right-Hand Side". +*/ +SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); + +/* +** CAPI3REF: Conflict resolution modes +** KEYWORDS: {conflict resolution mode} +** +** These constants are returned by [sqlite3_vtab_on_conflict()] to +** inform a [virtual table] implementation what the [ON CONFLICT] mode +** is for the SQL statement being evaluated. +** +** Note that the [SQLITE_IGNORE] constant is also used as a potential +** return value from the [sqlite3_set_authorizer()] callback and that +** [SQLITE_ABORT] is also a [result code]. +*/ +#define SQLITE_ROLLBACK 1 +/* #define SQLITE_IGNORE 2 // Also used by sqlite3_authorizer() callback */ +#define SQLITE_FAIL 3 +/* #define SQLITE_ABORT 4 // Also an error code */ +#define SQLITE_REPLACE 5 + +/* +** CAPI3REF: Prepared Statement Scan Status Opcodes +** KEYWORDS: {scanstatus options} +** +** The following constants can be used for the T parameter to the +** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a +** different metric for sqlite3_stmt_scanstatus() to return. +** +** When the value returned to V is a string, space to hold that string is +** managed by the prepared statement S and will be automatically freed when +** S is finalized. +** +** Not all values are available for all query elements. When a value is +** not available, the output variable is set to -1 if the value is numeric, +** or to NULL if it is a string (SQLITE_SCANSTAT_NAME). +** +** <dl> +** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt> +** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be +** set to the total number of times that the X-th loop has run.</dd> +** +** [[SQLITE_SCANSTAT_NVISIT]] <dt>SQLITE_SCANSTAT_NVISIT</dt> +** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be set +** to the total number of rows examined by all iterations of the X-th loop.</dd> +** +** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt> +** <dd>^The "double" variable pointed to by the V parameter will be set to the +** query planner's estimate for the average number of rows output from each +** iteration of the X-th loop. If the query planner's estimates was accurate, +** then this value will approximate the quotient NVISIT/NLOOP and the +** product of this value for all prior loops with the same SELECTID will +** be the NLOOP value for the current loop. +** +** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt> +** <dd>^The "const char *" variable pointed to by the V parameter will be set +** to a zero-terminated UTF-8 string containing the name of the index or table +** used for the X-th loop. +** +** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt> +** <dd>^The "const char *" variable pointed to by the V parameter will be set +** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] +** description for the X-th loop. +** +** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt> +** <dd>^The "int" variable pointed to by the V parameter will be set to the +** id for the X-th query plan element. The id value is unique within the +** statement. The select-id is the same value as is output in the first +** column of an [EXPLAIN QUERY PLAN] query. +** +** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt> +** <dd>The "int" variable pointed to by the V parameter will be set to the +** the id of the parent of the current query element, if applicable, or +** to zero if the query element has no parent. This is the same value as +** returned in the second column of an [EXPLAIN QUERY PLAN] query. +** +** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt> +** <dd>The sqlite3_int64 output value is set to the number of cycles, +** according to the processor time-stamp counter, that elapsed while the +** query element was being processed. This value is not available for +** all query elements - if it is unavailable the output variable is +** set to -1. +** </dl> +*/ +#define SQLITE_SCANSTAT_NLOOP 0 +#define SQLITE_SCANSTAT_NVISIT 1 +#define SQLITE_SCANSTAT_EST 2 +#define SQLITE_SCANSTAT_NAME 3 +#define SQLITE_SCANSTAT_EXPLAIN 4 +#define SQLITE_SCANSTAT_SELECTID 5 +#define SQLITE_SCANSTAT_PARENTID 6 +#define SQLITE_SCANSTAT_NCYCLE 7 + +/* +** CAPI3REF: Prepared Statement Scan Status +** METHOD: sqlite3_stmt +** +** These interfaces return information about the predicted and measured +** performance for pStmt. Advanced applications can use this +** interface to compare the predicted and the measured performance and +** issue warnings and/or rerun [ANALYZE] if discrepancies are found. +** +** Since this interface is expected to be rarely used, it is only +** available if SQLite is compiled using the [SQLITE_ENABLE_STMT_SCANSTATUS] +** compile-time option. +** +** The "iScanStatusOp" parameter determines which status information to return. +** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior +** of this interface is undefined. ^The requested measurement is written into +** a variable pointed to by the "pOut" parameter. +** +** The "flags" parameter must be passed a mask of flags. At present only +** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX +** is specified, then status information is available for all elements +** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If +** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements +** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of +** the EXPLAIN QUERY PLAN output) are available. Invoking API +** sqlite3_stmt_scanstatus() is equivalent to calling +** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter. +** +** Parameter "idx" identifies the specific query element to retrieve statistics +** for. Query elements are numbered starting from zero. A value of -1 may be +** to query for statistics regarding the entire query. ^If idx is out of range +** - less than -1 or greater than or equal to the total number of query +** elements used to implement the statement - a non-zero value is returned and +** the variable that pOut points to is unchanged. +** +** See also: [sqlite3_stmt_scanstatus_reset()] +*/ +SQLITE_API int sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + void *pOut /* Result written here */ +); +SQLITE_API int sqlite3_stmt_scanstatus_v2( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + int flags, /* Mask of flags defined below */ + void *pOut /* Result written here */ +); + +/* +** CAPI3REF: Prepared Statement Scan Status +** KEYWORDS: {scan status flags} +*/ +#define SQLITE_SCANSTAT_COMPLEX 0x0001 + +/* +** CAPI3REF: Zero Scan-Status Counters +** METHOD: sqlite3_stmt +** +** ^Zero all [sqlite3_stmt_scanstatus()] related event counters. +** +** This API is only available if the library is built with pre-processor +** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined. +*/ +SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); + +/* +** CAPI3REF: Flush caches to disk mid-transaction +** METHOD: sqlite3 +** +** ^If a write-transaction is open on [database connection] D when the +** [sqlite3_db_cacheflush(D)] interface invoked, any dirty +** pages in the pager-cache that are not currently in use are written out +** to disk. A dirty page may be in use if a database cursor created by an +** active SQL statement is reading from it, or if it is page 1 of a database +** file (page 1 is always "in use"). ^The [sqlite3_db_cacheflush(D)] +** interface flushes caches for all schemas - "main", "temp", and +** any [attached] databases. +** +** ^If this function needs to obtain extra database locks before dirty pages +** can be flushed to disk, it does so. ^If those locks cannot be obtained +** immediately and there is a busy-handler callback configured, it is invoked +** in the usual manner. ^If the required lock still cannot be obtained, then +** the database is skipped and an attempt made to flush any dirty pages +** belonging to the next (if any) database. ^If any databases are skipped +** because locks cannot be obtained, but no other error occurs, this +** function returns SQLITE_BUSY. +** +** ^If any other error occurs while flushing dirty pages to disk (for +** example an IO error or out-of-memory condition), then processing is +** abandoned and an SQLite [error code] is returned to the caller immediately. +** +** ^Otherwise, if no error occurs, [sqlite3_db_cacheflush()] returns SQLITE_OK. +** +** ^This function does not set the database handle error code or message +** returned by the [sqlite3_errcode()] and [sqlite3_errmsg()] functions. +*/ +SQLITE_API int sqlite3_db_cacheflush(sqlite3*); + +/* +** CAPI3REF: The pre-update hook. +** METHOD: sqlite3 +** +** ^These interfaces are only available if SQLite is compiled using the +** [SQLITE_ENABLE_PREUPDATE_HOOK] compile-time option. +** +** ^The [sqlite3_preupdate_hook()] interface registers a callback function +** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation +** on a database table. +** ^At most one preupdate hook may be registered at a time on a single +** [database connection]; each call to [sqlite3_preupdate_hook()] overrides +** the previous setting. +** ^The preupdate hook is disabled by invoking [sqlite3_preupdate_hook()] +** with a NULL pointer as the second parameter. +** ^The third parameter to [sqlite3_preupdate_hook()] is passed through as +** the first parameter to callbacks. +** +** ^The preupdate hook only fires for changes to real database tables; the +** preupdate hook is not invoked for changes to [virtual tables] or to +** system tables like sqlite_sequence or sqlite_stat1. +** +** ^The second parameter to the preupdate callback is a pointer to +** the [database connection] that registered the preupdate hook. +** ^The third parameter to the preupdate callback is one of the constants +** [SQLITE_INSERT], [SQLITE_DELETE], or [SQLITE_UPDATE] to identify the +** kind of update operation that is about to occur. +** ^(The fourth parameter to the preupdate callback is the name of the +** database within the database connection that is being modified. This +** will be "main" for the main database or "temp" for TEMP tables or +** the name given after the AS keyword in the [ATTACH] statement for attached +** databases.)^ +** ^The fifth parameter to the preupdate callback is the name of the +** table that is being modified. +** +** For an UPDATE or DELETE operation on a [rowid table], the sixth +** parameter passed to the preupdate callback is the initial [rowid] of the +** row being modified or deleted. For an INSERT operation on a rowid table, +** or any operation on a WITHOUT ROWID table, the value of the sixth +** parameter is undefined. For an INSERT or UPDATE on a rowid table the +** seventh parameter is the final rowid value of the row being inserted +** or updated. The value of the seventh parameter passed to the callback +** function is not defined for operations on WITHOUT ROWID tables, or for +** DELETE operations on rowid tables. +** +** ^The sqlite3_preupdate_hook(D,C,P) function returns the P argument from +** the previous call on the same [database connection] D, or NULL for +** the first call on D. +** +** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()], +** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces +** provide additional information about a preupdate event. These routines +** may only be called from within a preupdate callback. Invoking any of +** these routines from outside of a preupdate callback or with a +** [database connection] pointer that is different from the one supplied +** to the preupdate callback results in undefined and probably undesirable +** behavior. +** +** ^The [sqlite3_preupdate_count(D)] interface returns the number of columns +** in the row that is being inserted, updated, or deleted. +** +** ^The [sqlite3_preupdate_old(D,N,P)] interface writes into P a pointer to +** a [protected sqlite3_value] that contains the value of the Nth column of +** the table row before it is updated. The N parameter must be between 0 +** and one less than the number of columns or the behavior will be +** undefined. This must only be used within SQLITE_UPDATE and SQLITE_DELETE +** preupdate callbacks; if it is used by an SQLITE_INSERT callback then the +** behavior is undefined. The [sqlite3_value] that P points to +** will be destroyed when the preupdate callback returns. +** +** ^The [sqlite3_preupdate_new(D,N,P)] interface writes into P a pointer to +** a [protected sqlite3_value] that contains the value of the Nth column of +** the table row after it is updated. The N parameter must be between 0 +** and one less than the number of columns or the behavior will be +** undefined. This must only be used within SQLITE_INSERT and SQLITE_UPDATE +** preupdate callbacks; if it is used by an SQLITE_DELETE callback then the +** behavior is undefined. The [sqlite3_value] that P points to +** will be destroyed when the preupdate callback returns. +** +** ^The [sqlite3_preupdate_depth(D)] interface returns 0 if the preupdate +** callback was invoked as a result of a direct insert, update, or delete +** operation; or 1 for inserts, updates, or deletes invoked by top-level +** triggers; or 2 for changes resulting from triggers called by top-level +** triggers; and so forth. +** +** When the [sqlite3_blob_write()] API is used to update a blob column, +** the pre-update hook is invoked with SQLITE_DELETE. This is because the +** in this case the new values are not available. In this case, when a +** callback made with op==SQLITE_DELETE is actually a write using the +** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns +** the index of the column being written. In other cases, where the +** pre-update hook is being invoked for some other reason, including a +** regular DELETE, sqlite3_preupdate_blobwrite() returns -1. +** +** See also: [sqlite3_update_hook()] +*/ +#if defined(SQLITE_ENABLE_PREUPDATE_HOOK) +SQLITE_API void *sqlite3_preupdate_hook( + sqlite3 *db, + void(*xPreUpdate)( + void *pCtx, /* Copy of third arg to preupdate_hook() */ + sqlite3 *db, /* Database handle */ + int op, /* SQLITE_UPDATE, DELETE or INSERT */ + char const *zDb, /* Database name */ + char const *zName, /* Table name */ + sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */ + sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */ + ), + void* +); +SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_count(sqlite3 *); +SQLITE_API int sqlite3_preupdate_depth(sqlite3 *); +SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); +#endif + +/* +** CAPI3REF: Low-level system error code +** METHOD: sqlite3 +** +** ^Attempt to return the underlying operating system error code or error +** number that caused the most recent I/O error or failure to open a file. +** The return value is OS-dependent. For example, on unix systems, after +** [sqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be +** called to get back the underlying "errno" that caused the problem, such +** as ENOSPC, EAUTH, EISDIR, and so forth. +*/ +SQLITE_API int sqlite3_system_errno(sqlite3*); + +/* +** CAPI3REF: Database Snapshot +** KEYWORDS: {snapshot} {sqlite3_snapshot} +** +** An instance of the snapshot object records the state of a [WAL mode] +** database for some specific point in history. +** +** In [WAL mode], multiple [database connections] that are open on the +** same database file can each be reading a different historical version +** of the database file. When a [database connection] begins a read +** transaction, that connection sees an unchanging copy of the database +** as it existed for the point in time when the transaction first started. +** Subsequent changes to the database from other connections are not seen +** by the reader until a new read transaction is started. +** +** The sqlite3_snapshot object records state information about an historical +** version of the database file so that it is possible to later open a new read +** transaction that sees that historical version of the database rather than +** the most recent version. +*/ +typedef struct sqlite3_snapshot { + unsigned char hidden[48]; +} sqlite3_snapshot; + +/* +** CAPI3REF: Record A Database Snapshot +** CONSTRUCTOR: sqlite3_snapshot +** +** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a +** new [sqlite3_snapshot] object that records the current state of +** schema S in database connection D. ^On success, the +** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly +** created [sqlite3_snapshot] object into *P and returns SQLITE_OK. +** If there is not already a read-transaction open on schema S when +** this function is called, one is opened automatically. +** +** The following must be true for this function to succeed. If any of +** the following statements are false when sqlite3_snapshot_get() is +** called, SQLITE_ERROR is returned. The final value of *P is undefined +** in this case. +** +** <ul> +** <li> The database handle must not be in [autocommit mode]. +** +** <li> Schema S of [database connection] D must be a [WAL mode] database. +** +** <li> There must not be a write transaction open on schema S of database +** connection D. +** +** <li> One or more transactions must have been written to the current wal +** file since it was created on disk (by any connection). This means +** that a snapshot cannot be taken on a wal mode database with no wal +** file immediately after it is first opened. At least one transaction +** must be written to it first. +** </ul> +** +** This function may also return SQLITE_NOMEM. If it is called with the +** database handle in autocommit mode but fails for some other reason, +** whether or not a read transaction is opened on schema S is undefined. +** +** The [sqlite3_snapshot] object returned from a successful call to +** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()] +** to avoid a memory leak. +** +** The [sqlite3_snapshot_get()] interface is only available when the +** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( + sqlite3 *db, + const char *zSchema, + sqlite3_snapshot **ppSnapshot +); + +/* +** CAPI3REF: Start a read transaction on an historical snapshot +** METHOD: sqlite3_snapshot +** +** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read +** transaction or upgrades an existing one for schema S of +** [database connection] D such that the read transaction refers to +** historical [snapshot] P, rather than the most recent change to the +** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK +** on success or an appropriate [error code] if it fails. +** +** ^In order to succeed, the database connection must not be in +** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there +** is already a read transaction open on schema S, then the database handle +** must have no active statements (SELECT statements that have been passed +** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). +** SQLITE_ERROR is returned if either of these conditions is violated, or +** if schema S does not exist, or if the snapshot object is invalid. +** +** ^A call to sqlite3_snapshot_open() will fail to open if the specified +** snapshot has been overwritten by a [checkpoint]. In this case +** SQLITE_ERROR_SNAPSHOT is returned. +** +** If there is already a read transaction open when this function is +** invoked, then the same read transaction remains open (on the same +** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_ERROR_SNAPSHOT +** is returned. If another error code - for example SQLITE_PROTOCOL or an +** SQLITE_IOERR error code - is returned, then the final state of the +** read transaction is undefined. If SQLITE_OK is returned, then the +** read transaction is now open on database snapshot P. +** +** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the +** database connection D does not know that the database file for +** schema S is in [WAL mode]. A database connection might not know +** that the database file is in [WAL mode] if there has been no prior +** I/O on that database connection, or if the database entered [WAL mode] +** after the most recent I/O on the database connection.)^ +** (Hint: Run "[PRAGMA application_id]" against a newly opened +** database connection in order to make it ready to use snapshots.) +** +** The [sqlite3_snapshot_open()] interface is only available when the +** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( + sqlite3 *db, + const char *zSchema, + sqlite3_snapshot *pSnapshot +); + +/* +** CAPI3REF: Destroy a snapshot +** DESTRUCTOR: sqlite3_snapshot +** +** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P. +** The application must eventually free every [sqlite3_snapshot] object +** using this routine to avoid a memory leak. +** +** The [sqlite3_snapshot_free()] interface is only available when the +** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. +*/ +SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); + +/* +** CAPI3REF: Compare the ages of two snapshot handles. +** METHOD: sqlite3_snapshot +** +** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages +** of two valid snapshot handles. +** +** If the two snapshot handles are not associated with the same database +** file, the result of the comparison is undefined. +** +** Additionally, the result of the comparison is only valid if both of the +** snapshot handles were obtained by calling sqlite3_snapshot_get() since the +** last time the wal file was deleted. The wal file is deleted when the +** database is changed back to rollback mode or when the number of database +** clients drops to zero. If either snapshot handle was obtained before the +** wal file was last deleted, the value returned by this function +** is undefined. +** +** Otherwise, this API returns a negative value if P1 refers to an older +** snapshot than P2, zero if the two handles refer to the same database +** snapshot, and a positive value if P1 is a newer snapshot than P2. +** +** This interface is only available if SQLite is compiled with the +** [SQLITE_ENABLE_SNAPSHOT] option. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( + sqlite3_snapshot *p1, + sqlite3_snapshot *p2 +); + +/* +** CAPI3REF: Recover snapshots from a wal file +** METHOD: sqlite3_snapshot +** +** If a [WAL file] remains on disk after all database connections close +** (either through the use of the [SQLITE_FCNTL_PERSIST_WAL] [file control] +** or because the last process to have the database opened exited without +** calling [sqlite3_close()]) and a new connection is subsequently opened +** on that database and [WAL file], the [sqlite3_snapshot_open()] interface +** will only be able to open the last transaction added to the WAL file +** even though the WAL file contains other valid transactions. +** +** This function attempts to scan the WAL file associated with database zDb +** of database handle db and make all valid snapshots available to +** sqlite3_snapshot_open(). It is an error if there is already a read +** transaction open on the database, or if the database is not a WAL mode +** database. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +** +** This interface is only available if SQLite is compiled with the +** [SQLITE_ENABLE_SNAPSHOT] option. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); + +/* +** CAPI3REF: Serialize a database +** +** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory +** that is a serialization of the S database on [database connection] D. +** If P is not a NULL pointer, then the size of the database in bytes +** is written into *P. +** +** For an ordinary on-disk database file, the serialization is just a +** copy of the disk file. For an in-memory database or a "TEMP" database, +** the serialization is the same sequence of bytes which would be written +** to disk if that database where backed up to disk. +** +** The usual case is that sqlite3_serialize() copies the serialization of +** the database into memory obtained from [sqlite3_malloc64()] and returns +** a pointer to that memory. The caller is responsible for freeing the +** returned value to avoid a memory leak. However, if the F argument +** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations +** are made, and the sqlite3_serialize() function will return a pointer +** to the contiguous memory representation of the database that SQLite +** is currently using for that database, or NULL if the no such contiguous +** memory representation of the database exists. A contiguous memory +** representation of the database will usually only exist if there has +** been a prior call to [sqlite3_deserialize(D,S,...)] with the same +** values of D and S. +** The size of the database is written into *P even if the +** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy +** of the database exists. +** +** A call to sqlite3_serialize(D,S,P,F) might return NULL even if the +** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory +** allocation error occurs. +** +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. +*/ +SQLITE_API unsigned char *sqlite3_serialize( + sqlite3 *db, /* The database connection */ + const char *zSchema, /* Which DB to serialize. ex: "main", "temp", ... */ + sqlite3_int64 *piSize, /* Write size of the DB here, if not NULL */ + unsigned int mFlags /* Zero or more SQLITE_SERIALIZE_* flags */ +); + +/* +** CAPI3REF: Flags for sqlite3_serialize +** +** Zero or more of the following constants can be OR-ed together for +** the F argument to [sqlite3_serialize(D,S,P,F)]. +** +** SQLITE_SERIALIZE_NOCOPY means that [sqlite3_serialize()] will return +** a pointer to contiguous in-memory database that it is currently using, +** without making a copy of the database. If SQLite is not currently using +** a contiguous in-memory database, then this option causes +** [sqlite3_serialize()] to return a NULL pointer. SQLite will only be +** using a contiguous in-memory database if it has been initialized by a +** prior call to [sqlite3_deserialize()]. +*/ +#define SQLITE_SERIALIZE_NOCOPY 0x001 /* Do no memory allocations */ + +/* +** CAPI3REF: Deserialize a database +** +** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the +** [database connection] D to disconnect from database S and then +** reopen S as an in-memory database based on the serialization contained +** in P. The serialized database P is N bytes in size. M is the size of +** the buffer P, which might be larger than N. If M is larger than N, and +** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is +** permitted to add content to the in-memory database as long as the total +** size does not exceed M bytes. +** +** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will +** invoke sqlite3_free() on the serialization buffer when the database +** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then +** SQLite will try to increase the buffer size using sqlite3_realloc64() +** if writes on the database cause it to grow larger than M bytes. +** +** The sqlite3_deserialize() interface will fail with SQLITE_BUSY if the +** database is currently in a read transaction or is involved in a backup +** operation. +** +** It is not possible to deserialized into the TEMP database. If the +** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the +** function returns SQLITE_ERROR. +** +** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the +** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then +** [sqlite3_free()] is invoked on argument P prior to returning. +** +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. +*/ +SQLITE_API int sqlite3_deserialize( + sqlite3 *db, /* The database connection */ + const char *zSchema, /* Which DB to reopen with the deserialization */ + unsigned char *pData, /* The serialized database content */ + sqlite3_int64 szDb, /* Number bytes in the deserialization */ + sqlite3_int64 szBuf, /* Total size of buffer pData[] */ + unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */ +); + +/* +** CAPI3REF: Flags for sqlite3_deserialize() +** +** The following are allowed values for 6th argument (the F argument) to +** the [sqlite3_deserialize(D,S,P,N,M,F)] interface. +** +** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization +** in the P argument is held in memory obtained from [sqlite3_malloc64()] +** and that SQLite should take ownership of this memory and automatically +** free it when it has finished using it. Without this flag, the caller +** is responsible for freeing any dynamically allocated memory. +** +** The SQLITE_DESERIALIZE_RESIZEABLE flag means that SQLite is allowed to +** grow the size of the database using calls to [sqlite3_realloc64()]. This +** flag should only be used if SQLITE_DESERIALIZE_FREEONCLOSE is also used. +** Without this flag, the deserialized database cannot increase in size beyond +** the number of bytes specified by the M parameter. +** +** The SQLITE_DESERIALIZE_READONLY flag means that the deserialized database +** should be treated as read-only. +*/ +#define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */ +#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ +#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ + +/* +** Undo the hack that converts floating point types to integer for +** builds on processors without floating point support. +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# undef double +#endif + +#if defined(__wasi__) +# undef SQLITE_WASI +# define SQLITE_WASI 1 +# undef SQLITE_OMIT_WAL +# define SQLITE_OMIT_WAL 1/* because it requires shared memory APIs */ +# ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION +# endif +# ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +# endif +#endif + +#ifdef __cplusplus +} /* End of the 'extern "C"' block */ +#endif +#endif /* SQLITE3_H */ + +/******** Begin file sqlite3rtree.h *********/ +/* +** 2010 August 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +*/ + +#ifndef _SQLITE3RTREE_H_ +#define _SQLITE3RTREE_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry; +typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info; + +/* The double-precision datatype used by RTree depends on the +** SQLITE_RTREE_INT_ONLY compile-time option. +*/ +#ifdef SQLITE_RTREE_INT_ONLY + typedef sqlite3_int64 sqlite3_rtree_dbl; +#else + typedef double sqlite3_rtree_dbl; +#endif + +/* +** Register a geometry callback named zGeom that can be used as part of an +** R-Tree geometry query as follows: +** +** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zGeom(... params ...) +*/ +SQLITE_API int sqlite3_rtree_geometry_callback( + sqlite3 *db, + const char *zGeom, + int (*xGeom)(sqlite3_rtree_geometry*, int, sqlite3_rtree_dbl*,int*), + void *pContext +); + + +/* +** A pointer to a structure of the following type is passed as the first +** argument to callbacks registered using rtree_geometry_callback(). +*/ +struct sqlite3_rtree_geometry { + void *pContext; /* Copy of pContext passed to s_r_g_c() */ + int nParam; /* Size of array aParam[] */ + sqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */ + void *pUser; /* Callback implementation user data */ + void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */ +}; + +/* +** Register a 2nd-generation geometry callback named zScore that can be +** used as part of an R-Tree geometry query as follows: +** +** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zQueryFunc(... params ...) +*/ +SQLITE_API int sqlite3_rtree_query_callback( + sqlite3 *db, + const char *zQueryFunc, + int (*xQueryFunc)(sqlite3_rtree_query_info*), + void *pContext, + void (*xDestructor)(void*) +); + + +/* +** A pointer to a structure of the following type is passed as the +** argument to scored geometry callback registered using +** sqlite3_rtree_query_callback(). +** +** Note that the first 5 fields of this structure are identical to +** sqlite3_rtree_geometry. This structure is a subclass of +** sqlite3_rtree_geometry. +*/ +struct sqlite3_rtree_query_info { + void *pContext; /* pContext from when function registered */ + int nParam; /* Number of function parameters */ + sqlite3_rtree_dbl *aParam; /* value of function parameters */ + void *pUser; /* callback can use this, if desired */ + void (*xDelUser)(void*); /* function to free pUser */ + sqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */ + unsigned int *anQueue; /* Number of pending entries in the queue */ + int nCoord; /* Number of coordinates */ + int iLevel; /* Level of current node or entry */ + int mxLevel; /* The largest iLevel value in the tree */ + sqlite3_int64 iRowid; /* Rowid for current entry */ + sqlite3_rtree_dbl rParentScore; /* Score of parent node */ + int eParentWithin; /* Visibility of parent node */ + int eWithin; /* OUT: Visibility */ + sqlite3_rtree_dbl rScore; /* OUT: Write the score here */ + /* The following fields are only available in 3.8.11 and later */ + sqlite3_value **apSqlParam; /* Original SQL values of parameters */ +}; + +/* +** Allowed values for sqlite3_rtree_query.eWithin and .eParentWithin. +*/ +#define NOT_WITHIN 0 /* Object completely outside of query region */ +#define PARTLY_WITHIN 1 /* Object partially overlaps query region */ +#define FULLY_WITHIN 2 /* Object fully contained within query region */ + + +#ifdef __cplusplus +} /* end of the 'extern "C"' block */ +#endif + +#endif /* ifndef _SQLITE3RTREE_H_ */ + +/******** End of sqlite3rtree.h *********/ +/******** Begin file sqlite3session.h *********/ + +#if !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) +#define __SQLITESESSION_H_ 1 + +/* +** Make sure we can call this stuff from C++. +*/ +#ifdef __cplusplus +extern "C" { +#endif + + +/* +** CAPI3REF: Session Object Handle +** +** An instance of this object is a [session] that can be used to +** record changes to a database. +*/ +typedef struct sqlite3_session sqlite3_session; + +/* +** CAPI3REF: Changeset Iterator Handle +** +** An instance of this object acts as a cursor for iterating +** over the elements of a [changeset] or [patchset]. +*/ +typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; + +/* +** CAPI3REF: Create A New Session Object +** CONSTRUCTOR: sqlite3_session +** +** Create a new session object attached to database handle db. If successful, +** a pointer to the new object is written to *ppSession and SQLITE_OK is +** returned. If an error occurs, *ppSession is set to NULL and an SQLite +** error code (e.g. SQLITE_NOMEM) is returned. +** +** It is possible to create multiple session objects attached to a single +** database handle. +** +** Session objects created using this function should be deleted using the +** [sqlite3session_delete()] function before the database handle that they +** are attached to is itself closed. If the database handle is closed before +** the session object is deleted, then the results of calling any session +** module function, including [sqlite3session_delete()] on the session object +** are undefined. +** +** Because the session module uses the [sqlite3_preupdate_hook()] API, it +** is not possible for an application to register a pre-update hook on a +** database handle that has one or more session objects attached. Nor is +** it possible to create a session object attached to a database handle for +** which a pre-update hook is already defined. The results of attempting +** either of these things are undefined. +** +** The session object will be used to create changesets for tables in +** database zDb, where zDb is either "main", or "temp", or the name of an +** attached database. It is not an error if database zDb is not attached +** to the database when the session object is created. +*/ +SQLITE_API int sqlite3session_create( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of db (e.g. "main") */ + sqlite3_session **ppSession /* OUT: New session object */ +); + +/* +** CAPI3REF: Delete A Session Object +** DESTRUCTOR: sqlite3_session +** +** Delete a session object previously allocated using +** [sqlite3session_create()]. Once a session object has been deleted, the +** results of attempting to use pSession with any other session module +** function are undefined. +** +** Session objects must be deleted before the database handle to which they +** are attached is closed. Refer to the documentation for +** [sqlite3session_create()] for details. +*/ +SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); + +/* +** CAPI3REF: Configure a Session Object +** METHOD: sqlite3_session +** +** This method is used to configure a session object after it has been +** created. At present the only valid values for the second parameter are +** [SQLITE_SESSION_OBJCONFIG_SIZE] and [SQLITE_SESSION_OBJCONFIG_ROWID]. +** +*/ +SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg); + +/* +** CAPI3REF: Options for sqlite3session_object_config +** +** The following values may passed as the the 2nd parameter to +** sqlite3session_object_config(). +** +** <dt>SQLITE_SESSION_OBJCONFIG_SIZE <dd> +** This option is used to set, clear or query the flag that enables +** the [sqlite3session_changeset_size()] API. Because it imposes some +** computational overhead, this API is disabled by default. Argument +** pArg must point to a value of type (int). If the value is initially +** 0, then the sqlite3session_changeset_size() API is disabled. If it +** is greater than 0, then the same API is enabled. Or, if the initial +** value is less than zero, no change is made. In all cases the (int) +** variable is set to 1 if the sqlite3session_changeset_size() API is +** enabled following the current call, or 0 otherwise. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +** +** <dt>SQLITE_SESSION_OBJCONFIG_ROWID <dd> +** This option is used to set, clear or query the flag that enables +** collection of data for tables with no explicit PRIMARY KEY. +** +** Normally, tables with no explicit PRIMARY KEY are simply ignored +** by the sessions module. However, if this flag is set, it behaves +** as if such tables have a column "_rowid_ INTEGER PRIMARY KEY" inserted +** as their leftmost columns. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +*/ +#define SQLITE_SESSION_OBJCONFIG_SIZE 1 +#define SQLITE_SESSION_OBJCONFIG_ROWID 2 + +/* +** CAPI3REF: Enable Or Disable A Session Object +** METHOD: sqlite3_session +** +** Enable or disable the recording of changes by a session object. When +** enabled, a session object records changes made to the database. When +** disabled - it does not. A newly created session object is enabled. +** Refer to the documentation for [sqlite3session_changeset()] for further +** details regarding how enabling and disabling a session object affects +** the eventual changesets. +** +** Passing zero to this function disables the session. Passing a value +** greater than zero enables it. Passing a value less than zero is a +** no-op, and may be used to query the current state of the session. +** +** The return value indicates the final state of the session object: 0 if +** the session is disabled, or 1 if it is enabled. +*/ +SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); + +/* +** CAPI3REF: Set Or Clear the Indirect Change Flag +** METHOD: sqlite3_session +** +** Each change recorded by a session object is marked as either direct or +** indirect. A change is marked as indirect if either: +** +** <ul> +** <li> The session object "indirect" flag is set when the change is +** made, or +** <li> The change is made by an SQL trigger or foreign key action +** instead of directly as a result of a users SQL statement. +** </ul> +** +** If a single row is affected by more than one operation within a session, +** then the change is considered indirect if all operations meet the criteria +** for an indirect change above, or direct otherwise. +** +** This function is used to set, clear or query the session object indirect +** flag. If the second argument passed to this function is zero, then the +** indirect flag is cleared. If it is greater than zero, the indirect flag +** is set. Passing a value less than zero does not modify the current value +** of the indirect flag, and may be used to query the current state of the +** indirect flag for the specified session object. +** +** The return value indicates the final state of the indirect flag: 0 if +** it is clear, or 1 if it is set. +*/ +SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); + +/* +** CAPI3REF: Attach A Table To A Session Object +** METHOD: sqlite3_session +** +** If argument zTab is not NULL, then it is the name of a table to attach +** to the session object passed as the first argument. All subsequent changes +** made to the table while the session object is enabled will be recorded. See +** documentation for [sqlite3session_changeset()] for further details. +** +** Or, if argument zTab is NULL, then changes are recorded for all tables +** in the database. If additional tables are added to the database (by +** executing "CREATE TABLE" statements) after this call is made, changes for +** the new tables are also recorded. +** +** Changes can only be recorded for tables that have a PRIMARY KEY explicitly +** defined as part of their CREATE TABLE statement. It does not matter if the +** PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) or not. The PRIMARY +** KEY may consist of a single column, or may be a composite key. +** +** It is not an error if the named table does not exist in the database. Nor +** is it an error if the named table does not have a PRIMARY KEY. However, +** no changes will be recorded in either of these scenarios. +** +** Changes are not recorded for individual rows that have NULL values stored +** in one or more of their PRIMARY KEY columns. +** +** SQLITE_OK is returned if the call completes without error. Or, if an error +** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. +** +** <h3>Special sqlite_stat1 Handling</h3> +** +** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to +** some of the rules above. In SQLite, the schema of sqlite_stat1 is: +** <pre> +** &nbsp; CREATE TABLE sqlite_stat1(tbl,idx,stat) +** </pre> +** +** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are +** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes +** are recorded for rows for which (idx IS NULL) is true. However, for such +** rows a zero-length blob (SQL value X'') is stored in the changeset or +** patchset instead of a NULL value. This allows such changesets to be +** manipulated by legacy implementations of sqlite3changeset_invert(), +** concat() and similar. +** +** The sqlite3changeset_apply() function automatically converts the +** zero-length blob back to a NULL value when updating the sqlite_stat1 +** table. However, if the application calls sqlite3changeset_new(), +** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset +** iterator directly (including on a changeset iterator passed to a +** conflict-handler callback) then the X'' value is returned. The application +** must translate X'' to NULL itself if required. +** +** Legacy (older than 3.22.0) versions of the sessions module cannot capture +** changes made to the sqlite_stat1 table. Legacy versions of the +** sqlite3changeset_apply() function silently ignore any modifications to the +** sqlite_stat1 table that are part of a changeset or patchset. +*/ +SQLITE_API int sqlite3session_attach( + sqlite3_session *pSession, /* Session object */ + const char *zTab /* Table name */ +); + +/* +** CAPI3REF: Set a table filter on a Session Object. +** METHOD: sqlite3_session +** +** The second argument (xFilter) is the "filter callback". For changes to rows +** in tables that are not attached to the Session object, the filter is called +** to determine whether changes to the table's rows should be tracked or not. +** If xFilter returns 0, changes are not tracked. Note that once a table is +** attached, xFilter will not be called again. +*/ +SQLITE_API void sqlite3session_table_filter( + sqlite3_session *pSession, /* Session object */ + int(*xFilter)( + void *pCtx, /* Copy of third arg to _filter_table() */ + const char *zTab /* Table name */ + ), + void *pCtx /* First argument passed to xFilter */ +); + +/* +** CAPI3REF: Generate A Changeset From A Session Object +** METHOD: sqlite3_session +** +** Obtain a changeset containing changes to the tables attached to the +** session object passed as the first argument. If successful, +** set *ppChangeset to point to a buffer containing the changeset +** and *pnChangeset to the size of the changeset in bytes before returning +** SQLITE_OK. If an error occurs, set both *ppChangeset and *pnChangeset to +** zero and return an SQLite error code. +** +** A changeset consists of zero or more INSERT, UPDATE and/or DELETE changes, +** each representing a change to a single row of an attached table. An INSERT +** change contains the values of each field of a new database row. A DELETE +** contains the original values of each field of a deleted database row. An +** UPDATE change contains the original values of each field of an updated +** database row along with the updated values for each updated non-primary-key +** column. It is not possible for an UPDATE change to represent a change that +** modifies the values of primary key columns. If such a change is made, it +** is represented in a changeset as a DELETE followed by an INSERT. +** +** Changes are not recorded for rows that have NULL values stored in one or +** more of their PRIMARY KEY columns. If such a row is inserted or deleted, +** no corresponding change is present in the changesets returned by this +** function. If an existing row with one or more NULL values stored in +** PRIMARY KEY columns is updated so that all PRIMARY KEY columns are non-NULL, +** only an INSERT is appears in the changeset. Similarly, if an existing row +** with non-NULL PRIMARY KEY values is updated so that one or more of its +** PRIMARY KEY columns are set to NULL, the resulting changeset contains a +** DELETE change only. +** +** The contents of a changeset may be traversed using an iterator created +** using the [sqlite3changeset_start()] API. A changeset may be applied to +** a database with a compatible schema using the [sqlite3changeset_apply()] +** API. +** +** Within a changeset generated by this function, all changes related to a +** single table are grouped together. In other words, when iterating through +** a changeset or when applying a changeset to a database, all changes related +** to a single table are processed before moving on to the next table. Tables +** are sorted in the same order in which they were attached (or auto-attached) +** to the sqlite3_session object. The order in which the changes related to +** a single table are stored is undefined. +** +** Following a successful call to this function, it is the responsibility of +** the caller to eventually free the buffer that *ppChangeset points to using +** [sqlite3_free()]. +** +** <h3>Changeset Generation</h3> +** +** Once a table has been attached to a session object, the session object +** records the primary key values of all new rows inserted into the table. +** It also records the original primary key and other column values of any +** deleted or updated rows. For each unique primary key value, data is only +** recorded once - the first time a row with said primary key is inserted, +** updated or deleted in the lifetime of the session. +** +** There is one exception to the previous paragraph: when a row is inserted, +** updated or deleted, if one or more of its primary key columns contain a +** NULL value, no record of the change is made. +** +** The session object therefore accumulates two types of records - those +** that consist of primary key values only (created when the user inserts +** a new record) and those that consist of the primary key values and the +** original values of other table columns (created when the users deletes +** or updates a record). +** +** When this function is called, the requested changeset is created using +** both the accumulated records and the current contents of the database +** file. Specifically: +** +** <ul> +** <li> For each record generated by an insert, the database is queried +** for a row with a matching primary key. If one is found, an INSERT +** change is added to the changeset. If no such row is found, no change +** is added to the changeset. +** +** <li> For each record generated by an update or delete, the database is +** queried for a row with a matching primary key. If such a row is +** found and one or more of the non-primary key fields have been +** modified from their original values, an UPDATE change is added to +** the changeset. Or, if no such row is found in the table, a DELETE +** change is added to the changeset. If there is a row with a matching +** primary key in the database, but all fields contain their original +** values, no change is added to the changeset. +** </ul> +** +** This means, amongst other things, that if a row is inserted and then later +** deleted while a session object is active, neither the insert nor the delete +** will be present in the changeset. Or if a row is deleted and then later a +** row with the same primary key values inserted while a session object is +** active, the resulting changeset will contain an UPDATE change instead of +** a DELETE and an INSERT. +** +** When a session object is disabled (see the [sqlite3session_enable()] API), +** it does not accumulate records when rows are inserted, updated or deleted. +** This may appear to have some counter-intuitive effects if a single row +** is written to more than once during a session. For example, if a row +** is inserted while a session object is enabled, then later deleted while +** the same session object is disabled, no INSERT record will appear in the +** changeset, even though the delete took place while the session was disabled. +** Or, if one field of a row is updated while a session is disabled, and +** another field of the same row is updated while the session is enabled, the +** resulting changeset will contain an UPDATE change that updates both fields. +*/ +SQLITE_API int sqlite3session_changeset( + sqlite3_session *pSession, /* Session object */ + int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ + void **ppChangeset /* OUT: Buffer containing changeset */ +); + +/* +** CAPI3REF: Return An Upper-limit For The Size Of The Changeset +** METHOD: sqlite3_session +** +** By default, this function always returns 0. For it to return +** a useful result, the sqlite3_session object must have been configured +** to enable this API using sqlite3session_object_config() with the +** SQLITE_SESSION_OBJCONFIG_SIZE verb. +** +** When enabled, this function returns an upper limit, in bytes, for the size +** of the changeset that might be produced if sqlite3session_changeset() were +** called. The final changeset size might be equal to or smaller than the +** size in bytes returned by this function. +*/ +SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession); + +/* +** CAPI3REF: Load The Difference Between Tables Into A Session +** METHOD: sqlite3_session +** +** If it is not already attached to the session object passed as the first +** argument, this function attaches table zTbl in the same manner as the +** [sqlite3session_attach()] function. If zTbl does not exist, or if it +** does not have a primary key, this function is a no-op (but does not return +** an error). +** +** Argument zFromDb must be the name of a database ("main", "temp" etc.) +** attached to the same database handle as the session object that contains +** a table compatible with the table attached to the session by this function. +** A table is considered compatible if it: +** +** <ul> +** <li> Has the same name, +** <li> Has the same set of columns declared in the same order, and +** <li> Has the same PRIMARY KEY definition. +** </ul> +** +** If the tables are not compatible, SQLITE_SCHEMA is returned. If the tables +** are compatible but do not have any PRIMARY KEY columns, it is not an error +** but no changes are added to the session object. As with other session +** APIs, tables without PRIMARY KEYs are simply ignored. +** +** This function adds a set of changes to the session object that could be +** used to update the table in database zFrom (call this the "from-table") +** so that its content is the same as the table attached to the session +** object (call this the "to-table"). Specifically: +** +** <ul> +** <li> For each row (primary key) that exists in the to-table but not in +** the from-table, an INSERT record is added to the session object. +** +** <li> For each row (primary key) that exists in the to-table but not in +** the from-table, a DELETE record is added to the session object. +** +** <li> For each row (primary key) that exists in both tables, but features +** different non-PK values in each, an UPDATE record is added to the +** session. +** </ul> +** +** To clarify, if this function is called and then a changeset constructed +** using [sqlite3session_changeset()], then after applying that changeset to +** database zFrom the contents of the two compatible tables would be +** identical. +** +** It an error if database zFrom does not exist or does not contain the +** required compatible table. +** +** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite +** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg +** may be set to point to a buffer containing an English language error +** message. It is the responsibility of the caller to free this buffer using +** sqlite3_free(). +*/ +SQLITE_API int sqlite3session_diff( + sqlite3_session *pSession, + const char *zFromDb, + const char *zTbl, + char **pzErrMsg +); + + +/* +** CAPI3REF: Generate A Patchset From A Session Object +** METHOD: sqlite3_session +** +** The differences between a patchset and a changeset are that: +** +** <ul> +** <li> DELETE records consist of the primary key fields only. The +** original values of other fields are omitted. +** <li> The original values of any modified fields are omitted from +** UPDATE records. +** </ul> +** +** A patchset blob may be used with up to date versions of all +** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(), +** which returns SQLITE_CORRUPT if it is passed a patchset. Similarly, +** attempting to use a patchset blob with old versions of the +** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error. +** +** Because the non-primary key "old.*" fields are omitted, no +** SQLITE_CHANGESET_DATA conflicts can be detected or reported if a patchset +** is passed to the sqlite3changeset_apply() API. Other conflict types work +** in the same way as for changesets. +** +** Changes within a patchset are ordered in the same way as for changesets +** generated by the sqlite3session_changeset() function (i.e. all changes for +** a single table are grouped together, tables appear in the order in which +** they were attached to the session object). +*/ +SQLITE_API int sqlite3session_patchset( + sqlite3_session *pSession, /* Session object */ + int *pnPatchset, /* OUT: Size of buffer at *ppPatchset */ + void **ppPatchset /* OUT: Buffer containing patchset */ +); + +/* +** CAPI3REF: Test if a changeset has recorded any changes. +** +** Return non-zero if no changes to attached tables have been recorded by +** the session object passed as the first argument. Otherwise, if one or +** more changes have been recorded, return zero. +** +** Even if this function returns zero, it is possible that calling +** [sqlite3session_changeset()] on the session handle may still return a +** changeset that contains no changes. This can happen when a row in +** an attached table is modified and then later on the original values +** are restored. However, if this function returns non-zero, then it is +** guaranteed that a call to sqlite3session_changeset() will return a +** changeset containing zero changes. +*/ +SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); + +/* +** CAPI3REF: Query for the amount of heap memory used by a session object. +** +** This API returns the total amount of heap memory in bytes currently +** used by the session object passed as the only argument. +*/ +SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession); + +/* +** CAPI3REF: Create An Iterator To Traverse A Changeset +** CONSTRUCTOR: sqlite3_changeset_iter +** +** Create an iterator used to iterate through the contents of a changeset. +** If successful, *pp is set to point to the iterator handle and SQLITE_OK +** is returned. Otherwise, if an error occurs, *pp is set to zero and an +** SQLite error code is returned. +** +** The following functions can be used to advance and query a changeset +** iterator created by this function: +** +** <ul> +** <li> [sqlite3changeset_next()] +** <li> [sqlite3changeset_op()] +** <li> [sqlite3changeset_new()] +** <li> [sqlite3changeset_old()] +** </ul> +** +** It is the responsibility of the caller to eventually destroy the iterator +** by passing it to [sqlite3changeset_finalize()]. The buffer containing the +** changeset (pChangeset) must remain valid until after the iterator is +** destroyed. +** +** Assuming the changeset blob was created by one of the +** [sqlite3session_changeset()], [sqlite3changeset_concat()] or +** [sqlite3changeset_invert()] functions, all changes within the changeset +** that apply to a single table are grouped together. This means that when +** an application iterates through a changeset using an iterator created by +** this function, all changes that relate to a single table are visited +** consecutively. There is no chance that the iterator will visit a change +** the applies to table X, then one for table Y, and then later on visit +** another change for table X. +** +** The behavior of sqlite3changeset_start_v2() and its streaming equivalent +** may be modified by passing a combination of +** [SQLITE_CHANGESETSTART_INVERT | supported flags] as the 4th parameter. +** +** Note that the sqlite3changeset_start_v2() API is still <b>experimental</b> +** and therefore subject to change. +*/ +SQLITE_API int sqlite3changeset_start( + sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ + int nChangeset, /* Size of changeset blob in bytes */ + void *pChangeset /* Pointer to blob containing changeset */ +); +SQLITE_API int sqlite3changeset_start_v2( + sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ + int nChangeset, /* Size of changeset blob in bytes */ + void *pChangeset, /* Pointer to blob containing changeset */ + int flags /* SESSION_CHANGESETSTART_* flags */ +); + +/* +** CAPI3REF: Flags for sqlite3changeset_start_v2 +** +** The following flags may passed via the 4th parameter to +** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]: +** +** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd> +** Invert the changeset while iterating through it. This is equivalent to +** inverting a changeset using sqlite3changeset_invert() before applying it. +** It is an error to specify this flag with a patchset. +*/ +#define SQLITE_CHANGESETSTART_INVERT 0x0002 + + +/* +** CAPI3REF: Advance A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** This function may only be used with iterators created by the function +** [sqlite3changeset_start()]. If it is called on an iterator passed to +** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE +** is returned and the call has no effect. +** +** Immediately after an iterator is created by sqlite3changeset_start(), it +** does not point to any change in the changeset. Assuming the changeset +** is not empty, the first call to this function advances the iterator to +** point to the first change in the changeset. Each subsequent call advances +** the iterator to point to the next change in the changeset (if any). If +** no error occurs and the iterator points to a valid change after a call +** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned. +** Otherwise, if all changes in the changeset have already been visited, +** SQLITE_DONE is returned. +** +** If an error occurs, an SQLite error code is returned. Possible error +** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or +** SQLITE_NOMEM. +*/ +SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter); + +/* +** CAPI3REF: Obtain The Current Operation From A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** The pIter argument passed to this function may either be an iterator +** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator +** created by [sqlite3changeset_start()]. In the latter case, the most recent +** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this +** is not the case, this function returns [SQLITE_MISUSE]. +** +** Arguments pOp, pnCol and pzTab may not be NULL. Upon return, three +** outputs are set through these pointers: +** +** *pOp is set to one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], +** depending on the type of change that the iterator currently points to; +** +** *pnCol is set to the number of columns in the table affected by the change; and +** +** *pzTab is set to point to a nul-terminated utf-8 encoded string containing +** the name of the table affected by the current change. The buffer remains +** valid until either sqlite3changeset_next() is called on the iterator +** or until the conflict-handler function returns. +** +** If pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change +** is an indirect change, or false (0) otherwise. See the documentation for +** [sqlite3session_indirect()] for a description of direct and indirect +** changes. +** +** If no error occurs, SQLITE_OK is returned. If an error does occur, an +** SQLite error code is returned. The values of the output variables may not +** be trusted in this case. +*/ +SQLITE_API int sqlite3changeset_op( + sqlite3_changeset_iter *pIter, /* Iterator object */ + const char **pzTab, /* OUT: Pointer to table name */ + int *pnCol, /* OUT: Number of columns in table */ + int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ + int *pbIndirect /* OUT: True for an 'indirect' change */ +); + +/* +** CAPI3REF: Obtain The Primary Key Definition Of A Table +** METHOD: sqlite3_changeset_iter +** +** For each modified table, a changeset includes the following: +** +** <ul> +** <li> The number of columns in the table, and +** <li> Which of those columns make up the tables PRIMARY KEY. +** </ul> +** +** This function is used to find which columns comprise the PRIMARY KEY of +** the table modified by the change that iterator pIter currently points to. +** If successful, *pabPK is set to point to an array of nCol entries, where +** nCol is the number of columns in the table. Elements of *pabPK are set to +** 0x01 if the corresponding column is part of the tables primary key, or +** 0x00 if it is not. +** +** If argument pnCol is not NULL, then *pnCol is set to the number of columns +** in the table. +** +** If this function is called when the iterator does not point to a valid +** entry, SQLITE_MISUSE is returned and the output variables zeroed. Otherwise, +** SQLITE_OK is returned and the output variables populated as described +** above. +*/ +SQLITE_API int sqlite3changeset_pk( + sqlite3_changeset_iter *pIter, /* Iterator object */ + unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */ + int *pnCol /* OUT: Number of entries in output array */ +); + +/* +** CAPI3REF: Obtain old.* Values From A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** The pIter argument passed to this function may either be an iterator +** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator +** created by [sqlite3changeset_start()]. In the latter case, the most recent +** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. +** Furthermore, it may only be called if the type of change that the iterator +** currently points to is either [SQLITE_DELETE] or [SQLITE_UPDATE]. Otherwise, +** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [SQLITE_RANGE] is returned and *ppValue is set to NULL. +** +** If successful, this function sets *ppValue to point to a protected +** sqlite3_value object containing the iVal'th value from the vector of +** original row values stored as part of the UPDATE or DELETE change and +** returns SQLITE_OK. The name of the function comes from the fact that this +** is similar to the "old.*" columns available to update or delete triggers. +** +** If some other error occurs (e.g. an OOM condition), an SQLite error code +** is returned and *ppValue is set to NULL. +*/ +SQLITE_API int sqlite3changeset_old( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */ +); + +/* +** CAPI3REF: Obtain new.* Values From A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** The pIter argument passed to this function may either be an iterator +** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator +** created by [sqlite3changeset_start()]. In the latter case, the most recent +** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. +** Furthermore, it may only be called if the type of change that the iterator +** currently points to is either [SQLITE_UPDATE] or [SQLITE_INSERT]. Otherwise, +** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [SQLITE_RANGE] is returned and *ppValue is set to NULL. +** +** If successful, this function sets *ppValue to point to a protected +** sqlite3_value object containing the iVal'th value from the vector of +** new row values stored as part of the UPDATE or INSERT change and +** returns SQLITE_OK. If the change is an UPDATE and does not include +** a new value for the requested column, *ppValue is set to NULL and +** SQLITE_OK returned. The name of the function comes from the fact that +** this is similar to the "new.*" columns available to update or delete +** triggers. +** +** If some other error occurs (e.g. an OOM condition), an SQLite error code +** is returned and *ppValue is set to NULL. +*/ +SQLITE_API int sqlite3changeset_new( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */ +); + +/* +** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** This function should only be used with iterator objects passed to a +** conflict-handler callback by [sqlite3changeset_apply()] with either +** [SQLITE_CHANGESET_DATA] or [SQLITE_CHANGESET_CONFLICT]. If this function +** is called on any other iterator, [SQLITE_MISUSE] is returned and *ppValue +** is set to NULL. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [SQLITE_RANGE] is returned and *ppValue is set to NULL. +** +** If successful, this function sets *ppValue to point to a protected +** sqlite3_value object containing the iVal'th value from the +** "conflicting row" associated with the current conflict-handler callback +** and returns SQLITE_OK. +** +** If some other error occurs (e.g. an OOM condition), an SQLite error code +** is returned and *ppValue is set to NULL. +*/ +SQLITE_API int sqlite3changeset_conflict( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: Value from conflicting row */ +); + +/* +** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations +** METHOD: sqlite3_changeset_iter +** +** This function may only be called with an iterator passed to an +** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case +** it sets the output variable to the total number of known foreign key +** violations in the destination database and returns SQLITE_OK. +** +** In all other cases this function returns SQLITE_MISUSE. +*/ +SQLITE_API int sqlite3changeset_fk_conflicts( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int *pnOut /* OUT: Number of FK violations */ +); + + +/* +** CAPI3REF: Finalize A Changeset Iterator +** METHOD: sqlite3_changeset_iter +** +** This function is used to finalize an iterator allocated with +** [sqlite3changeset_start()]. +** +** This function should only be called on iterators created using the +** [sqlite3changeset_start()] function. If an application calls this +** function with an iterator passed to a conflict-handler by +** [sqlite3changeset_apply()], [SQLITE_MISUSE] is immediately returned and the +** call has no effect. +** +** If an error was encountered within a call to an sqlite3changeset_xxx() +** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an +** [SQLITE_NOMEM] in [sqlite3changeset_new()]) then an error code corresponding +** to that error is returned by this function. Otherwise, SQLITE_OK is +** returned. This is to allow the following pattern (pseudo-code): +** +** <pre> +** sqlite3changeset_start(); +** while( SQLITE_ROW==sqlite3changeset_next() ){ +** // Do something with change. +** } +** rc = sqlite3changeset_finalize(); +** if( rc!=SQLITE_OK ){ +** // An error has occurred +** } +** </pre> +*/ +SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); + +/* +** CAPI3REF: Invert A Changeset +** +** This function is used to "invert" a changeset object. Applying an inverted +** changeset to a database reverses the effects of applying the uninverted +** changeset. Specifically: +** +** <ul> +** <li> Each DELETE change is changed to an INSERT, and +** <li> Each INSERT change is changed to a DELETE, and +** <li> For each UPDATE change, the old.* and new.* values are exchanged. +** </ul> +** +** This function does not change the order in which changes appear within +** the changeset. It merely reverses the sense of each individual change. +** +** If successful, a pointer to a buffer containing the inverted changeset +** is stored in *ppOut, the size of the same buffer is stored in *pnOut, and +** SQLITE_OK is returned. If an error occurs, both *pnOut and *ppOut are +** zeroed and an SQLite error code returned. +** +** It is the responsibility of the caller to eventually call sqlite3_free() +** on the *ppOut pointer to free the buffer allocation following a successful +** call to this function. +** +** WARNING/TODO: This function currently assumes that the input is a valid +** changeset. If it is not, the results are undefined. +*/ +SQLITE_API int sqlite3changeset_invert( + int nIn, const void *pIn, /* Input changeset */ + int *pnOut, void **ppOut /* OUT: Inverse of input */ +); + +/* +** CAPI3REF: Concatenate Two Changeset Objects +** +** This function is used to concatenate two changesets, A and B, into a +** single changeset. The result is a changeset equivalent to applying +** changeset A followed by changeset B. +** +** This function combines the two input changesets using an +** sqlite3_changegroup object. Calling it produces similar results as the +** following code fragment: +** +** <pre> +** sqlite3_changegroup *pGrp; +** rc = sqlite3_changegroup_new(&pGrp); +** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nA, pA); +** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nB, pB); +** if( rc==SQLITE_OK ){ +** rc = sqlite3changegroup_output(pGrp, pnOut, ppOut); +** }else{ +** *ppOut = 0; +** *pnOut = 0; +** } +** </pre> +** +** Refer to the sqlite3_changegroup documentation below for details. +*/ +SQLITE_API int sqlite3changeset_concat( + int nA, /* Number of bytes in buffer pA */ + void *pA, /* Pointer to buffer containing changeset A */ + int nB, /* Number of bytes in buffer pB */ + void *pB, /* Pointer to buffer containing changeset B */ + int *pnOut, /* OUT: Number of bytes in output changeset */ + void **ppOut /* OUT: Buffer containing output changeset */ +); + + +/* +** CAPI3REF: Changegroup Handle +** +** A changegroup is an object used to combine two or more +** [changesets] or [patchsets] +*/ +typedef struct sqlite3_changegroup sqlite3_changegroup; + +/* +** CAPI3REF: Create A New Changegroup Object +** CONSTRUCTOR: sqlite3_changegroup +** +** An sqlite3_changegroup object is used to combine two or more changesets +** (or patchsets) into a single changeset (or patchset). A single changegroup +** object may combine changesets or patchsets, but not both. The output is +** always in the same format as the input. +** +** If successful, this function returns SQLITE_OK and populates (*pp) with +** a pointer to a new sqlite3_changegroup object before returning. The caller +** should eventually free the returned object using a call to +** sqlite3changegroup_delete(). If an error occurs, an SQLite error code +** (i.e. SQLITE_NOMEM) is returned and *pp is set to NULL. +** +** The usual usage pattern for an sqlite3_changegroup object is as follows: +** +** <ul> +** <li> It is created using a call to sqlite3changegroup_new(). +** +** <li> Zero or more changesets (or patchsets) are added to the object +** by calling sqlite3changegroup_add(). +** +** <li> The result of combining all input changesets together is obtained +** by the application via a call to sqlite3changegroup_output(). +** +** <li> The object is deleted using a call to sqlite3changegroup_delete(). +** </ul> +** +** Any number of calls to add() and output() may be made between the calls to +** new() and delete(), and in any order. +** +** As well as the regular sqlite3changegroup_add() and +** sqlite3changegroup_output() functions, also available are the streaming +** versions sqlite3changegroup_add_strm() and sqlite3changegroup_output_strm(). +*/ +SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); + +/* +** CAPI3REF: Add A Changeset To A Changegroup +** METHOD: sqlite3_changegroup +** +** Add all changes within the changeset (or patchset) in buffer pData (size +** nData bytes) to the changegroup. +** +** If the buffer contains a patchset, then all prior calls to this function +** on the same changegroup object must also have specified patchsets. Or, if +** the buffer contains a changeset, so must have the earlier calls to this +** function. Otherwise, SQLITE_ERROR is returned and no changes are added +** to the changegroup. +** +** Rows within the changeset and changegroup are identified by the values in +** their PRIMARY KEY columns. A change in the changeset is considered to +** apply to the same row as a change already present in the changegroup if +** the two rows have the same primary key. +** +** Changes to rows that do not already appear in the changegroup are +** simply copied into it. Or, if both the new changeset and the changegroup +** contain changes that apply to a single row, the final contents of the +** changegroup depends on the type of each change, as follows: +** +** <table border=1 style="margin-left:8ex;margin-right:8ex"> +** <tr><th style="white-space:pre">Existing Change </th> +** <th style="white-space:pre">New Change </th> +** <th>Output Change +** <tr><td>INSERT <td>INSERT <td> +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +** <tr><td>INSERT <td>UPDATE <td> +** The INSERT change remains in the changegroup. The values in the +** INSERT change are modified as if the row was inserted by the +** existing change and then updated according to the new change. +** <tr><td>INSERT <td>DELETE <td> +** The existing INSERT is removed from the changegroup. The DELETE is +** not added. +** <tr><td>UPDATE <td>INSERT <td> +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +** <tr><td>UPDATE <td>UPDATE <td> +** The existing UPDATE remains within the changegroup. It is amended +** so that the accompanying values are as if the row was updated once +** by the existing change and then again by the new change. +** <tr><td>UPDATE <td>DELETE <td> +** The existing UPDATE is replaced by the new DELETE within the +** changegroup. +** <tr><td>DELETE <td>INSERT <td> +** If one or more of the column values in the row inserted by the +** new change differ from those in the row deleted by the existing +** change, the existing DELETE is replaced by an UPDATE within the +** changegroup. Otherwise, if the inserted row is exactly the same +** as the deleted row, the existing DELETE is simply discarded. +** <tr><td>DELETE <td>UPDATE <td> +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +** <tr><td>DELETE <td>DELETE <td> +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +** </table> +** +** If the new changeset contains changes to a table that is already present +** in the changegroup, then the number of columns and the position of the +** primary key columns for the table must be consistent. If this is not the +** case, this function fails with SQLITE_SCHEMA. If the input changeset +** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is +** returned. Or, if an out-of-memory condition occurs during processing, this +** function returns SQLITE_NOMEM. In all cases, if an error occurs the state +** of the final contents of the changegroup is undefined. +** +** If no error occurs, SQLITE_OK is returned. +*/ +SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); + +/* +** CAPI3REF: Obtain A Composite Changeset From A Changegroup +** METHOD: sqlite3_changegroup +** +** Obtain a buffer containing a changeset (or patchset) representing the +** current contents of the changegroup. If the inputs to the changegroup +** were themselves changesets, the output is a changeset. Or, if the +** inputs were patchsets, the output is also a patchset. +** +** As with the output of the sqlite3session_changeset() and +** sqlite3session_patchset() functions, all changes related to a single +** table are grouped together in the output of this function. Tables appear +** in the same order as for the very first changeset added to the changegroup. +** If the second or subsequent changesets added to the changegroup contain +** changes for tables that do not appear in the first changeset, they are +** appended onto the end of the output changeset, again in the order in +** which they are first encountered. +** +** If an error occurs, an SQLite error code is returned and the output +** variables (*pnData) and (*ppData) are set to 0. Otherwise, SQLITE_OK +** is returned and the output variables are set to the size of and a +** pointer to the output buffer, respectively. In this case it is the +** responsibility of the caller to eventually free the buffer using a +** call to sqlite3_free(). +*/ +SQLITE_API int sqlite3changegroup_output( + sqlite3_changegroup*, + int *pnData, /* OUT: Size of output buffer in bytes */ + void **ppData /* OUT: Pointer to output buffer */ +); + +/* +** CAPI3REF: Delete A Changegroup Object +** DESTRUCTOR: sqlite3_changegroup +*/ +SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); + +/* +** CAPI3REF: Apply A Changeset To A Database +** +** Apply a changeset or patchset to a database. These functions attempt to +** update the "main" database attached to handle db with the changes found in +** the changeset passed via the second and third arguments. +** +** The fourth argument (xFilter) passed to these functions is the "filter +** callback". If it is not NULL, then for each table affected by at least one +** change in the changeset, the filter callback is invoked with +** the table name as the second argument, and a copy of the context pointer +** passed as the sixth argument as the first. If the "filter callback" +** returns zero, then no attempt is made to apply any changes to the table. +** Otherwise, if the return value is non-zero or the xFilter argument to +** is NULL, all changes related to the table are attempted. +** +** For each table that is not excluded by the filter callback, this function +** tests that the target database contains a compatible table. A table is +** considered compatible if all of the following are true: +** +** <ul> +** <li> The table has the same name as the name recorded in the +** changeset, and +** <li> The table has at least as many columns as recorded in the +** changeset, and +** <li> The table has primary key columns in the same position as +** recorded in the changeset. +** </ul> +** +** If there is no compatible table, it is not an error, but none of the +** changes associated with the table are applied. A warning message is issued +** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most +** one such warning is issued for each table in the changeset. +** +** For each change for which there is a compatible table, an attempt is made +** to modify the table contents according to the UPDATE, INSERT or DELETE +** change. If a change cannot be applied cleanly, the conflict handler +** function passed as the fifth argument to sqlite3changeset_apply() may be +** invoked. A description of exactly when the conflict handler is invoked for +** each type of change is below. +** +** Unlike the xFilter argument, xConflict may not be passed NULL. The results +** of passing anything other than a valid function pointer as the xConflict +** argument are undefined. +** +** Each time the conflict handler function is invoked, it must return one +** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or +** [SQLITE_CHANGESET_REPLACE]. SQLITE_CHANGESET_REPLACE may only be returned +** if the second argument passed to the conflict handler is either +** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler +** returns an illegal value, any changes already made are rolled back and +** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different +** actions are taken by sqlite3changeset_apply() depending on the value +** returned by each invocation of the conflict-handler function. Refer to +** the documentation for the three +** [SQLITE_CHANGESET_OMIT|available return values] for details. +** +** <dl> +** <dt>DELETE Changes<dd> +** For each DELETE change, the function checks if the target database +** contains a row with the same primary key value (or values) as the +** original row values stored in the changeset. If it does, and the values +** stored in all non-primary key columns also match the values stored in +** the changeset the row is deleted from the target database. +** +** If a row with matching primary key values is found, but one or more of +** the non-primary key fields contains a value different from the original +** row value stored in the changeset, the conflict-handler function is +** invoked with [SQLITE_CHANGESET_DATA] as the second argument. If the +** database table has more columns than are recorded in the changeset, +** only the values of those non-primary key fields are compared against +** the current database contents - any trailing database table columns +** are ignored. +** +** If no row with matching primary key values is found in the database, +** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] +** passed as the second argument. +** +** If the DELETE operation is attempted, but SQLite returns SQLITE_CONSTRAINT +** (which can only happen if a foreign key constraint is violated), the +** conflict-handler function is invoked with [SQLITE_CHANGESET_CONSTRAINT] +** passed as the second argument. This includes the case where the DELETE +** operation is attempted because an earlier call to the conflict handler +** function returned [SQLITE_CHANGESET_REPLACE]. +** +** <dt>INSERT Changes<dd> +** For each INSERT change, an attempt is made to insert the new row into +** the database. If the changeset row contains fewer fields than the +** database table, the trailing fields are populated with their default +** values. +** +** If the attempt to insert the row fails because the database already +** contains a row with the same primary key values, the conflict handler +** function is invoked with the second argument set to +** [SQLITE_CHANGESET_CONFLICT]. +** +** If the attempt to insert the row fails because of some other constraint +** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is +** invoked with the second argument set to [SQLITE_CHANGESET_CONSTRAINT]. +** This includes the case where the INSERT operation is re-attempted because +** an earlier call to the conflict handler function returned +** [SQLITE_CHANGESET_REPLACE]. +** +** <dt>UPDATE Changes<dd> +** For each UPDATE change, the function checks if the target database +** contains a row with the same primary key value (or values) as the +** original row values stored in the changeset. If it does, and the values +** stored in all modified non-primary key columns also match the values +** stored in the changeset the row is updated within the target database. +** +** If a row with matching primary key values is found, but one or more of +** the modified non-primary key fields contains a value different from an +** original row value stored in the changeset, the conflict-handler function +** is invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since +** UPDATE changes only contain values for non-primary key fields that are +** to be modified, only those fields need to match the original values to +** avoid the SQLITE_CHANGESET_DATA conflict-handler callback. +** +** If no row with matching primary key values is found in the database, +** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] +** passed as the second argument. +** +** If the UPDATE operation is attempted, but SQLite returns +** SQLITE_CONSTRAINT, the conflict-handler function is invoked with +** [SQLITE_CHANGESET_CONSTRAINT] passed as the second argument. +** This includes the case where the UPDATE operation is attempted after +** an earlier call to the conflict handler function returned +** [SQLITE_CHANGESET_REPLACE]. +** </dl> +** +** It is safe to execute SQL statements, including those that write to the +** table that the callback related to, from within the xConflict callback. +** This can be used to further customize the application's conflict +** resolution strategy. +** +** All changes made by these functions are enclosed in a savepoint transaction. +** If any other error (aside from a constraint failure when attempting to +** write to the target database) occurs, then the savepoint transaction is +** rolled back, restoring the target database to its original state, and an +** SQLite error code returned. +** +** If the output parameters (ppRebase) and (pnRebase) are non-NULL and +** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() +** may set (*ppRebase) to point to a "rebase" that may be used with the +** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) +** is set to the size of the buffer in bytes. It is the responsibility of the +** caller to eventually free any such buffer using sqlite3_free(). The buffer +** is only allocated and populated if one or more conflicts were encountered +** while applying the patchset. See comments surrounding the sqlite3_rebaser +** APIs for further details. +** +** The behavior of sqlite3changeset_apply_v2() and its streaming equivalent +** may be modified by passing a combination of +** [SQLITE_CHANGESETAPPLY_NOSAVEPOINT | supported flags] as the 9th parameter. +** +** Note that the sqlite3changeset_apply_v2() API is still <b>experimental</b> +** and therefore subject to change. +*/ +SQLITE_API int sqlite3changeset_apply( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +); +SQLITE_API int sqlite3changeset_apply_v2( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, /* OUT: Rebase data */ + int flags /* SESSION_CHANGESETAPPLY_* flags */ +); + +/* +** CAPI3REF: Flags for sqlite3changeset_apply_v2 +** +** The following flags may passed via the 9th parameter to +** [sqlite3changeset_apply_v2] and [sqlite3changeset_apply_v2_strm]: +** +** <dl> +** <dt>SQLITE_CHANGESETAPPLY_NOSAVEPOINT <dd> +** Usually, the sessions module encloses all operations performed by +** a single call to apply_v2() or apply_v2_strm() in a [SAVEPOINT]. The +** SAVEPOINT is committed if the changeset or patchset is successfully +** applied, or rolled back if an error occurs. Specifying this flag +** causes the sessions module to omit this savepoint. In this case, if the +** caller has an open transaction or savepoint when apply_v2() is called, +** it may revert the partially applied changeset by rolling it back. +** +** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd> +** Invert the changeset before applying it. This is equivalent to inverting +** a changeset using sqlite3changeset_invert() before applying it. It is +** an error to specify this flag with a patchset. +** +** <dt>SQLITE_CHANGESETAPPLY_IGNORENOOP <dd> +** Do not invoke the conflict handler callback for any changes that +** would not actually modify the database even if they were applied. +** Specifically, this means that the conflict handler is not invoked +** for: +** <ul> +** <li>a delete change if the row being deleted cannot be found, +** <li>an update change if the modified fields are already set to +** their new values in the conflicting row, or +** <li>an insert change if all fields of the conflicting row match +** the row being inserted. +** </ul> +*/ +#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 +#define SQLITE_CHANGESETAPPLY_INVERT 0x0002 +#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 + +/* +** CAPI3REF: Constants Passed To The Conflict Handler +** +** Values that may be passed as the second argument to a conflict-handler. +** +** <dl> +** <dt>SQLITE_CHANGESET_DATA<dd> +** The conflict handler is invoked with CHANGESET_DATA as the second argument +** when processing a DELETE or UPDATE change if a row with the required +** PRIMARY KEY fields is present in the database, but one or more other +** (non primary-key) fields modified by the update do not contain the +** expected "before" values. +** +** The conflicting row, in this case, is the database row with the matching +** primary key. +** +** <dt>SQLITE_CHANGESET_NOTFOUND<dd> +** The conflict handler is invoked with CHANGESET_NOTFOUND as the second +** argument when processing a DELETE or UPDATE change if a row with the +** required PRIMARY KEY fields is not present in the database. +** +** There is no conflicting row in this case. The results of invoking the +** sqlite3changeset_conflict() API are undefined. +** +** <dt>SQLITE_CHANGESET_CONFLICT<dd> +** CHANGESET_CONFLICT is passed as the second argument to the conflict +** handler while processing an INSERT change if the operation would result +** in duplicate primary key values. +** +** The conflicting row in this case is the database row with the matching +** primary key. +** +** <dt>SQLITE_CHANGESET_FOREIGN_KEY<dd> +** If foreign key handling is enabled, and applying a changeset leaves the +** database in a state containing foreign key violations, the conflict +** handler is invoked with CHANGESET_FOREIGN_KEY as the second argument +** exactly once before the changeset is committed. If the conflict handler +** returns CHANGESET_OMIT, the changes, including those that caused the +** foreign key constraint violation, are committed. Or, if it returns +** CHANGESET_ABORT, the changeset is rolled back. +** +** No current or conflicting row information is provided. The only function +** it is possible to call on the supplied sqlite3_changeset_iter handle +** is sqlite3changeset_fk_conflicts(). +** +** <dt>SQLITE_CHANGESET_CONSTRAINT<dd> +** If any other constraint violation occurs while applying a change (i.e. +** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is +** invoked with CHANGESET_CONSTRAINT as the second argument. +** +** There is no conflicting row in this case. The results of invoking the +** sqlite3changeset_conflict() API are undefined. +** +** </dl> +*/ +#define SQLITE_CHANGESET_DATA 1 +#define SQLITE_CHANGESET_NOTFOUND 2 +#define SQLITE_CHANGESET_CONFLICT 3 +#define SQLITE_CHANGESET_CONSTRAINT 4 +#define SQLITE_CHANGESET_FOREIGN_KEY 5 + +/* +** CAPI3REF: Constants Returned By The Conflict Handler +** +** A conflict handler callback must return one of the following three values. +** +** <dl> +** <dt>SQLITE_CHANGESET_OMIT<dd> +** If a conflict handler returns this value no special action is taken. The +** change that caused the conflict is not applied. The session module +** continues to the next change in the changeset. +** +** <dt>SQLITE_CHANGESET_REPLACE<dd> +** This value may only be returned if the second argument to the conflict +** handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this +** is not the case, any changes applied so far are rolled back and the +** call to sqlite3changeset_apply() returns SQLITE_MISUSE. +** +** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict +** handler, then the conflicting row is either updated or deleted, depending +** on the type of change. +** +** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_CONFLICT conflict +** handler, then the conflicting row is removed from the database and a +** second attempt to apply the change is made. If this second attempt fails, +** the original row is restored to the database before continuing. +** +** <dt>SQLITE_CHANGESET_ABORT<dd> +** If this value is returned, any changes applied so far are rolled back +** and the call to sqlite3changeset_apply() returns SQLITE_ABORT. +** </dl> +*/ +#define SQLITE_CHANGESET_OMIT 0 +#define SQLITE_CHANGESET_REPLACE 1 +#define SQLITE_CHANGESET_ABORT 2 + +/* +** CAPI3REF: Rebasing changesets +** EXPERIMENTAL +** +** Suppose there is a site hosting a database in state S0. And that +** modifications are made that move that database to state S1 and a +** changeset recorded (the "local" changeset). Then, a changeset based +** on S0 is received from another site (the "remote" changeset) and +** applied to the database. The database is then in state +** (S1+"remote"), where the exact state depends on any conflict +** resolution decisions (OMIT or REPLACE) made while applying "remote". +** Rebasing a changeset is to update it to take those conflict +** resolution decisions into account, so that the same conflicts +** do not have to be resolved elsewhere in the network. +** +** For example, if both the local and remote changesets contain an +** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)": +** +** local: INSERT INTO t1 VALUES(1, 'v1'); +** remote: INSERT INTO t1 VALUES(1, 'v2'); +** +** and the conflict resolution is REPLACE, then the INSERT change is +** removed from the local changeset (it was overridden). Or, if the +** conflict resolution was "OMIT", then the local changeset is modified +** to instead contain: +** +** UPDATE t1 SET b = 'v2' WHERE a=1; +** +** Changes within the local changeset are rebased as follows: +** +** <dl> +** <dt>Local INSERT<dd> +** This may only conflict with a remote INSERT. If the conflict +** resolution was OMIT, then add an UPDATE change to the rebased +** changeset. Or, if the conflict resolution was REPLACE, add +** nothing to the rebased changeset. +** +** <dt>Local DELETE<dd> +** This may conflict with a remote UPDATE or DELETE. In both cases the +** only possible resolution is OMIT. If the remote operation was a +** DELETE, then add no change to the rebased changeset. If the remote +** operation was an UPDATE, then the old.* fields of change are updated +** to reflect the new.* values in the UPDATE. +** +** <dt>Local UPDATE<dd> +** This may conflict with a remote UPDATE or DELETE. If it conflicts +** with a DELETE, and the conflict resolution was OMIT, then the update +** is changed into an INSERT. Any undefined values in the new.* record +** from the update change are filled in using the old.* values from +** the conflicting DELETE. Or, if the conflict resolution was REPLACE, +** the UPDATE change is simply omitted from the rebased changeset. +** +** If conflict is with a remote UPDATE and the resolution is OMIT, then +** the old.* values are rebased using the new.* values in the remote +** change. Or, if the resolution is REPLACE, then the change is copied +** into the rebased changeset with updates to columns also updated by +** the conflicting remote UPDATE removed. If this means no columns would +** be updated, the change is omitted. +** </dl> +** +** A local change may be rebased against multiple remote changes +** simultaneously. If a single key is modified by multiple remote +** changesets, they are combined as follows before the local changeset +** is rebased: +** +** <ul> +** <li> If there has been one or more REPLACE resolutions on a +** key, it is rebased according to a REPLACE. +** +** <li> If there have been no REPLACE resolutions on a key, then +** the local changeset is rebased according to the most recent +** of the OMIT resolutions. +** </ul> +** +** Note that conflict resolutions from multiple remote changesets are +** combined on a per-field basis, not per-row. This means that in the +** case of multiple remote UPDATE operations, some fields of a single +** local change may be rebased for REPLACE while others are rebased for +** OMIT. +** +** In order to rebase a local changeset, the remote changeset must first +** be applied to the local database using sqlite3changeset_apply_v2() and +** the buffer of rebase information captured. Then: +** +** <ol> +** <li> An sqlite3_rebaser object is created by calling +** sqlite3rebaser_create(). +** <li> The new object is configured with the rebase buffer obtained from +** sqlite3changeset_apply_v2() by calling sqlite3rebaser_configure(). +** If the local changeset is to be rebased against multiple remote +** changesets, then sqlite3rebaser_configure() should be called +** multiple times, in the same order that the multiple +** sqlite3changeset_apply_v2() calls were made. +** <li> Each local changeset is rebased by calling sqlite3rebaser_rebase(). +** <li> The sqlite3_rebaser object is deleted by calling +** sqlite3rebaser_delete(). +** </ol> +*/ +typedef struct sqlite3_rebaser sqlite3_rebaser; + +/* +** CAPI3REF: Create a changeset rebaser object. +** EXPERIMENTAL +** +** Allocate a new changeset rebaser object. If successful, set (*ppNew) to +** point to the new object and return SQLITE_OK. Otherwise, if an error +** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew) +** to NULL. +*/ +SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser **ppNew); + +/* +** CAPI3REF: Configure a changeset rebaser object. +** EXPERIMENTAL +** +** Configure the changeset rebaser object to rebase changesets according +** to the conflict resolutions described by buffer pRebase (size nRebase +** bytes), which must have been obtained from a previous call to +** sqlite3changeset_apply_v2(). +*/ +SQLITE_API int sqlite3rebaser_configure( + sqlite3_rebaser*, + int nRebase, const void *pRebase +); + +/* +** CAPI3REF: Rebase a changeset +** EXPERIMENTAL +** +** Argument pIn must point to a buffer containing a changeset nIn bytes +** in size. This function allocates and populates a buffer with a copy +** of the changeset rebased according to the configuration of the +** rebaser object passed as the first argument. If successful, (*ppOut) +** is set to point to the new buffer containing the rebased changeset and +** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the +** responsibility of the caller to eventually free the new buffer using +** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut) +** are set to zero and an SQLite error code returned. +*/ +SQLITE_API int sqlite3rebaser_rebase( + sqlite3_rebaser*, + int nIn, const void *pIn, + int *pnOut, void **ppOut +); + +/* +** CAPI3REF: Delete a changeset rebaser object. +** EXPERIMENTAL +** +** Delete the changeset rebaser object and all associated resources. There +** should be one call to this function for each successful invocation +** of sqlite3rebaser_create(). +*/ +SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); + +/* +** CAPI3REF: Streaming Versions of API functions. +** +** The six streaming API xxx_strm() functions serve similar purposes to the +** corresponding non-streaming API functions: +** +** <table border=1 style="margin-left:8ex;margin-right:8ex"> +** <tr><th>Streaming function<th>Non-streaming equivalent</th> +** <tr><td>sqlite3changeset_apply_strm<td>[sqlite3changeset_apply] +** <tr><td>sqlite3changeset_apply_strm_v2<td>[sqlite3changeset_apply_v2] +** <tr><td>sqlite3changeset_concat_strm<td>[sqlite3changeset_concat] +** <tr><td>sqlite3changeset_invert_strm<td>[sqlite3changeset_invert] +** <tr><td>sqlite3changeset_start_strm<td>[sqlite3changeset_start] +** <tr><td>sqlite3session_changeset_strm<td>[sqlite3session_changeset] +** <tr><td>sqlite3session_patchset_strm<td>[sqlite3session_patchset] +** </table> +** +** Non-streaming functions that accept changesets (or patchsets) as input +** require that the entire changeset be stored in a single buffer in memory. +** Similarly, those that return a changeset or patchset do so by returning +** a pointer to a single large buffer allocated using sqlite3_malloc(). +** Normally this is convenient. However, if an application running in a +** low-memory environment is required to handle very large changesets, the +** large contiguous memory allocations required can become onerous. +** +** In order to avoid this problem, instead of a single large buffer, input +** is passed to a streaming API functions by way of a callback function that +** the sessions module invokes to incrementally request input data as it is +** required. In all cases, a pair of API function parameters such as +** +** <pre> +** &nbsp; int nChangeset, +** &nbsp; void *pChangeset, +** </pre> +** +** Is replaced by: +** +** <pre> +** &nbsp; int (*xInput)(void *pIn, void *pData, int *pnData), +** &nbsp; void *pIn, +** </pre> +** +** Each time the xInput callback is invoked by the sessions module, the first +** argument passed is a copy of the supplied pIn context pointer. The second +** argument, pData, points to a buffer (*pnData) bytes in size. Assuming no +** error occurs the xInput method should copy up to (*pnData) bytes of data +** into the buffer and set (*pnData) to the actual number of bytes copied +** before returning SQLITE_OK. If the input is completely exhausted, (*pnData) +** should be set to zero to indicate this. Or, if an error occurs, an SQLite +** error code should be returned. In all cases, if an xInput callback returns +** an error, all processing is abandoned and the streaming API function +** returns a copy of the error code to the caller. +** +** In the case of sqlite3changeset_start_strm(), the xInput callback may be +** invoked by the sessions module at any point during the lifetime of the +** iterator. If such an xInput callback returns an error, the iterator enters +** an error state, whereby all subsequent calls to iterator functions +** immediately fail with the same error code as returned by xInput. +** +** Similarly, streaming API functions that return changesets (or patchsets) +** return them in chunks by way of a callback function instead of via a +** pointer to a single large buffer. In this case, a pair of parameters such +** as: +** +** <pre> +** &nbsp; int *pnChangeset, +** &nbsp; void **ppChangeset, +** </pre> +** +** Is replaced by: +** +** <pre> +** &nbsp; int (*xOutput)(void *pOut, const void *pData, int nData), +** &nbsp; void *pOut +** </pre> +** +** The xOutput callback is invoked zero or more times to return data to +** the application. The first parameter passed to each call is a copy of the +** pOut pointer supplied by the application. The second parameter, pData, +** points to a buffer nData bytes in size containing the chunk of output +** data being returned. If the xOutput callback successfully processes the +** supplied data, it should return SQLITE_OK to indicate success. Otherwise, +** it should return some other SQLite error code. In this case processing +** is immediately abandoned and the streaming API function returns a copy +** of the xOutput error code to the application. +** +** The sessions module never invokes an xOutput callback with the third +** parameter set to a value less than or equal to zero. Other than this, +** no guarantees are made as to the size of the chunks of data returned. +*/ +SQLITE_API int sqlite3changeset_apply_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +); +SQLITE_API int sqlite3changeset_apply_v2_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +); +SQLITE_API int sqlite3changeset_concat_strm( + int (*xInputA)(void *pIn, void *pData, int *pnData), + void *pInA, + int (*xInputB)(void *pIn, void *pData, int *pnData), + void *pInB, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3changeset_invert_strm( + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3changeset_start_strm( + sqlite3_changeset_iter **pp, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +); +SQLITE_API int sqlite3changeset_start_v2_strm( + sqlite3_changeset_iter **pp, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int flags +); +SQLITE_API int sqlite3session_changeset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3session_patchset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3changegroup_add_strm(sqlite3_changegroup*, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +); +SQLITE_API int sqlite3changegroup_output_strm(sqlite3_changegroup*, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3rebaser_rebase_strm( + sqlite3_rebaser *pRebaser, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); + +/* +** CAPI3REF: Configure global parameters +** +** The sqlite3session_config() interface is used to make global configuration +** changes to the sessions module in order to tune it to the specific needs +** of the application. +** +** The sqlite3session_config() interface is not threadsafe. If it is invoked +** while any other thread is inside any other sessions method then the +** results are undefined. Furthermore, if it is invoked after any sessions +** related objects have been created, the results are also undefined. +** +** The first argument to the sqlite3session_config() function must be one +** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The +** interpretation of the (void*) value passed as the second parameter and +** the effect of calling this function depends on the value of the first +** parameter. +** +** <dl> +** <dt>SQLITE_SESSION_CONFIG_STRMSIZE<dd> +** By default, the sessions module streaming interfaces attempt to input +** and output data in approximately 1 KiB chunks. This operand may be used +** to set and query the value of this configuration setting. The pointer +** passed as the second argument must point to a value of type (int). +** If this value is greater than 0, it is used as the new streaming data +** chunk size for both input and output. Before returning, the (int) value +** pointed to by pArg is set to the final value of the streaming interface +** chunk size. +** </dl> +** +** This function returns SQLITE_OK if successful, or an SQLite error code +** otherwise. +*/ +SQLITE_API int sqlite3session_config(int op, void *pArg); + +/* +** CAPI3REF: Values for sqlite3session_config(). +*/ +#define SQLITE_SESSION_CONFIG_STRMSIZE 1 + +/* +** Make sure we can call this stuff from C++. +*/ +#ifdef __cplusplus +} +#endif + +#endif /* !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) */ + +/******** End of sqlite3session.h *********/ +/******** Begin file fts5.h *********/ +/* +** 2014 May 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** Interfaces to extend FTS5. Using the interfaces defined in this file, +** FTS5 may be extended with: +** +** * custom tokenizers, and +** * custom auxiliary functions. +*/ + + +#ifndef _FTS5_H +#define _FTS5_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/************************************************************************* +** CUSTOM AUXILIARY FUNCTIONS +** +** Virtual table implementations may overload SQL functions by implementing +** the sqlite3_module.xFindFunction() method. +*/ + +typedef struct Fts5ExtensionApi Fts5ExtensionApi; +typedef struct Fts5Context Fts5Context; +typedef struct Fts5PhraseIter Fts5PhraseIter; + +typedef void (*fts5_extension_function)( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + sqlite3_context *pCtx, /* Context for returning result/error */ + int nVal, /* Number of values in apVal[] array */ + sqlite3_value **apVal /* Array of trailing arguments */ +); + +struct Fts5PhraseIter { + const unsigned char *a; + const unsigned char *b; +}; + +/* +** EXTENSION API FUNCTIONS +** +** xUserData(pFts): +** Return a copy of the context pointer the extension function was +** registered with. +** +** xColumnTotalSize(pFts, iCol, pnToken): +** If parameter iCol is less than zero, set output variable *pnToken +** to the total number of tokens in the FTS5 table. Or, if iCol is +** non-negative but less than the number of columns in the table, return +** the total number of tokens in column iCol, considering all rows in +** the FTS5 table. +** +** If parameter iCol is greater than or equal to the number of columns +** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. +** an OOM condition or IO error), an appropriate SQLite error code is +** returned. +** +** xColumnCount(pFts): +** Return the number of columns in the table. +** +** xColumnSize(pFts, iCol, pnToken): +** If parameter iCol is less than zero, set output variable *pnToken +** to the total number of tokens in the current row. Or, if iCol is +** non-negative but less than the number of columns in the table, set +** *pnToken to the number of tokens in column iCol of the current row. +** +** If parameter iCol is greater than or equal to the number of columns +** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. +** an OOM condition or IO error), an appropriate SQLite error code is +** returned. +** +** This function may be quite inefficient if used with an FTS5 table +** created with the "columnsize=0" option. +** +** xColumnText: +** This function attempts to retrieve the text of column iCol of the +** current document. If successful, (*pz) is set to point to a buffer +** containing the text in utf-8 encoding, (*pn) is set to the size in bytes +** (not characters) of the buffer and SQLITE_OK is returned. Otherwise, +** if an error occurs, an SQLite error code is returned and the final values +** of (*pz) and (*pn) are undefined. +** +** xPhraseCount: +** Returns the number of phrases in the current query expression. +** +** xPhraseSize: +** Returns the number of tokens in phrase iPhrase of the query. Phrases +** are numbered starting from zero. +** +** xInstCount: +** Set *pnInst to the total number of occurrences of all phrases within +** the query within the current row. Return SQLITE_OK if successful, or +** an error code (i.e. SQLITE_NOMEM) if an error occurs. +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always returns 0. +** +** xInst: +** Query for the details of phrase match iIdx within the current row. +** Phrase matches are numbered starting from zero, so the iIdx argument +** should be greater than or equal to zero and smaller than the value +** output by xInstCount(). +** +** Usually, output parameter *piPhrase is set to the phrase number, *piCol +** to the column in which it occurs and *piOff the token offset of the +** first token of the phrase. Returns SQLITE_OK if successful, or an error +** code (i.e. SQLITE_NOMEM) if an error occurs. +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. +** +** xRowid: +** Returns the rowid of the current row. +** +** xTokenize: +** Tokenize text using the tokenizer belonging to the FTS5 table. +** +** xQueryPhrase(pFts5, iPhrase, pUserData, xCallback): +** This API function is used to query the FTS table for phrase iPhrase +** of the current query. Specifically, a query equivalent to: +** +** ... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid +** +** with $p set to a phrase equivalent to the phrase iPhrase of the +** current query is executed. Any column filter that applies to +** phrase iPhrase of the current query is included in $p. For each +** row visited, the callback function passed as the fourth argument +** is invoked. The context and API objects passed to the callback +** function may be used to access the properties of each matched row. +** Invoking Api.xUserData() returns a copy of the pointer passed as +** the third argument to pUserData. +** +** If the callback function returns any value other than SQLITE_OK, the +** query is abandoned and the xQueryPhrase function returns immediately. +** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK. +** Otherwise, the error code is propagated upwards. +** +** If the query runs to completion without incident, SQLITE_OK is returned. +** Or, if some error occurs before the query completes or is aborted by +** the callback, an SQLite error code is returned. +** +** +** xSetAuxdata(pFts5, pAux, xDelete) +** +** Save the pointer passed as the second argument as the extension function's +** "auxiliary data". The pointer may then be retrieved by the current or any +** future invocation of the same fts5 extension function made as part of +** the same MATCH query using the xGetAuxdata() API. +** +** Each extension function is allocated a single auxiliary data slot for +** each FTS query (MATCH expression). If the extension function is invoked +** more than once for a single FTS query, then all invocations share a +** single auxiliary data context. +** +** If there is already an auxiliary data pointer when this function is +** invoked, then it is replaced by the new pointer. If an xDelete callback +** was specified along with the original pointer, it is invoked at this +** point. +** +** The xDelete callback, if one is specified, is also invoked on the +** auxiliary data pointer after the FTS5 query has finished. +** +** If an error (e.g. an OOM condition) occurs within this function, +** the auxiliary data is set to NULL and an error code returned. If the +** xDelete parameter was not NULL, it is invoked on the auxiliary data +** pointer before returning. +** +** +** xGetAuxdata(pFts5, bClear) +** +** Returns the current auxiliary data pointer for the fts5 extension +** function. See the xSetAuxdata() method for details. +** +** If the bClear argument is non-zero, then the auxiliary data is cleared +** (set to NULL) before this function returns. In this case the xDelete, +** if any, is not invoked. +** +** +** xRowCount(pFts5, pnRow) +** +** This function is used to retrieve the total number of rows in the table. +** In other words, the same value that would be returned by: +** +** SELECT count(*) FROM ftstable; +** +** xPhraseFirst() +** This function is used, along with type Fts5PhraseIter and the xPhraseNext +** method, to iterate through all instances of a single query phrase within +** the current row. This is the same information as is accessible via the +** xInstCount/xInst APIs. While the xInstCount/xInst APIs are more convenient +** to use, this API may be faster under some circumstances. To iterate +** through instances of phrase iPhrase, use the following code: +** +** Fts5PhraseIter iter; +** int iCol, iOff; +** for(pApi->xPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff); +** iCol>=0; +** pApi->xPhraseNext(pFts, &iter, &iCol, &iOff) +** ){ +** // An instance of phrase iPhrase at offset iOff of column iCol +** } +** +** The Fts5PhraseIter structure is defined above. Applications should not +** modify this structure directly - it should only be used as shown above +** with the xPhraseFirst() and xPhraseNext() API methods (and by +** xPhraseFirstColumn() and xPhraseNextColumn() as illustrated below). +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always iterates +** through an empty set (all calls to xPhraseFirst() set iCol to -1). +** +** xPhraseNext() +** See xPhraseFirst above. +** +** xPhraseFirstColumn() +** This function and xPhraseNextColumn() are similar to the xPhraseFirst() +** and xPhraseNext() APIs described above. The difference is that instead +** of iterating through all instances of a phrase in the current row, these +** APIs are used to iterate through the set of columns in the current row +** that contain one or more instances of a specified phrase. For example: +** +** Fts5PhraseIter iter; +** int iCol; +** for(pApi->xPhraseFirstColumn(pFts, iPhrase, &iter, &iCol); +** iCol>=0; +** pApi->xPhraseNextColumn(pFts, &iter, &iCol) +** ){ +** // Column iCol contains at least one instance of phrase iPhrase +** } +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" option. If the FTS5 table is created with either +** "detail=none" "content=" option (i.e. if it is a contentless table), +** then this API always iterates through an empty set (all calls to +** xPhraseFirstColumn() set iCol to -1). +** +** The information accessed using this API and its companion +** xPhraseFirstColumn() may also be obtained using xPhraseFirst/xPhraseNext +** (or xInst/xInstCount). The chief advantage of this API is that it is +** significantly more efficient than those alternatives when used with +** "detail=column" tables. +** +** xPhraseNextColumn() +** See xPhraseFirstColumn above. +*/ +struct Fts5ExtensionApi { + int iVersion; /* Currently always set to 2 */ + + void *(*xUserData)(Fts5Context*); + + int (*xColumnCount)(Fts5Context*); + int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow); + int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken); + + int (*xTokenize)(Fts5Context*, + const char *pText, int nText, /* Text to tokenize */ + void *pCtx, /* Context passed to xToken() */ + int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ + ); + + int (*xPhraseCount)(Fts5Context*); + int (*xPhraseSize)(Fts5Context*, int iPhrase); + + int (*xInstCount)(Fts5Context*, int *pnInst); + int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff); + + sqlite3_int64 (*xRowid)(Fts5Context*); + int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn); + int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken); + + int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData, + int(*)(const Fts5ExtensionApi*,Fts5Context*,void*) + ); + int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*)); + void *(*xGetAuxdata)(Fts5Context*, int bClear); + + int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); + void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); + + int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); + void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); +}; + +/* +** CUSTOM AUXILIARY FUNCTIONS +*************************************************************************/ + +/************************************************************************* +** CUSTOM TOKENIZERS +** +** Applications may also register custom tokenizer types. A tokenizer +** is registered by providing fts5 with a populated instance of the +** following structure. All structure methods must be defined, setting +** any member of the fts5_tokenizer struct to NULL leads to undefined +** behaviour. The structure methods are expected to function as follows: +** +** xCreate: +** This function is used to allocate and initialize a tokenizer instance. +** A tokenizer instance is required to actually tokenize text. +** +** The first argument passed to this function is a copy of the (void*) +** pointer provided by the application when the fts5_tokenizer object +** was registered with FTS5 (the third argument to xCreateTokenizer()). +** The second and third arguments are an array of nul-terminated strings +** containing the tokenizer arguments, if any, specified following the +** tokenizer name as part of the CREATE VIRTUAL TABLE statement used +** to create the FTS5 table. +** +** The final argument is an output variable. If successful, (*ppOut) +** should be set to point to the new tokenizer handle and SQLITE_OK +** returned. If an error occurs, some value other than SQLITE_OK should +** be returned. In this case, fts5 assumes that the final value of *ppOut +** is undefined. +** +** xDelete: +** This function is invoked to delete a tokenizer handle previously +** allocated using xCreate(). Fts5 guarantees that this function will +** be invoked exactly once for each successful call to xCreate(). +** +** xTokenize: +** This function is expected to tokenize the nText byte string indicated +** by argument pText. pText may or may not be nul-terminated. The first +** argument passed to this function is a pointer to an Fts5Tokenizer object +** returned by an earlier call to xCreate(). +** +** The second argument indicates the reason that FTS5 is requesting +** tokenization of the supplied text. This is always one of the following +** four values: +** +** <ul><li> <b>FTS5_TOKENIZE_DOCUMENT</b> - A document is being inserted into +** or removed from the FTS table. The tokenizer is being invoked to +** determine the set of tokens to add to (or delete from) the +** FTS index. +** +** <li> <b>FTS5_TOKENIZE_QUERY</b> - A MATCH query is being executed +** against the FTS index. The tokenizer is being called to tokenize +** a bareword or quoted string specified as part of the query. +** +** <li> <b>(FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX)</b> - Same as +** FTS5_TOKENIZE_QUERY, except that the bareword or quoted string is +** followed by a "*" character, indicating that the last token +** returned by the tokenizer will be treated as a token prefix. +** +** <li> <b>FTS5_TOKENIZE_AUX</b> - The tokenizer is being invoked to +** satisfy an fts5_api.xTokenize() request made by an auxiliary +** function. Or an fts5_api.xColumnSize() request made by the same +** on a columnsize=0 database. +** </ul> +** +** For each token in the input string, the supplied callback xToken() must +** be invoked. The first argument to it should be a copy of the pointer +** passed as the second argument to xTokenize(). The third and fourth +** arguments are a pointer to a buffer containing the token text, and the +** size of the token in bytes. The 4th and 5th arguments are the byte offsets +** of the first byte of and first byte immediately following the text from +** which the token is derived within the input. +** +** The second argument passed to the xToken() callback ("tflags") should +** normally be set to 0. The exception is if the tokenizer supports +** synonyms. In this case see the discussion below for details. +** +** FTS5 assumes the xToken() callback is invoked for each token in the +** order that they occur within the input text. +** +** If an xToken() callback returns any value other than SQLITE_OK, then +** the tokenization should be abandoned and the xTokenize() method should +** immediately return a copy of the xToken() return value. Or, if the +** input buffer is exhausted, xTokenize() should return SQLITE_OK. Finally, +** if an error occurs with the xTokenize() implementation itself, it +** may abandon the tokenization and return any error code other than +** SQLITE_OK or SQLITE_DONE. +** +** SYNONYM SUPPORT +** +** Custom tokenizers may also support synonyms. Consider a case in which a +** user wishes to query for a phrase such as "first place". Using the +** built-in tokenizers, the FTS5 query 'first + place' will match instances +** of "first place" within the document set, but not alternative forms +** such as "1st place". In some applications, it would be better to match +** all instances of "first place" or "1st place" regardless of which form +** the user specified in the MATCH query text. +** +** There are several ways to approach this in FTS5: +** +** <ol><li> By mapping all synonyms to a single token. In this case, using +** the above example, this means that the tokenizer returns the +** same token for inputs "first" and "1st". Say that token is in +** fact "first", so that when the user inserts the document "I won +** 1st place" entries are added to the index for tokens "i", "won", +** "first" and "place". If the user then queries for '1st + place', +** the tokenizer substitutes "first" for "1st" and the query works +** as expected. +** +** <li> By querying the index for all synonyms of each query term +** separately. In this case, when tokenizing query text, the +** tokenizer may provide multiple synonyms for a single term +** within the document. FTS5 then queries the index for each +** synonym individually. For example, faced with the query: +** +** <codeblock> +** ... MATCH 'first place'</codeblock> +** +** the tokenizer offers both "1st" and "first" as synonyms for the +** first token in the MATCH query and FTS5 effectively runs a query +** similar to: +** +** <codeblock> +** ... MATCH '(first OR 1st) place'</codeblock> +** +** except that, for the purposes of auxiliary functions, the query +** still appears to contain just two phrases - "(first OR 1st)" +** being treated as a single phrase. +** +** <li> By adding multiple synonyms for a single term to the FTS index. +** Using this method, when tokenizing document text, the tokenizer +** provides multiple synonyms for each token. So that when a +** document such as "I won first place" is tokenized, entries are +** added to the FTS index for "i", "won", "first", "1st" and +** "place". +** +** This way, even if the tokenizer does not provide synonyms +** when tokenizing query text (it should not - to do so would be +** inefficient), it doesn't matter if the user queries for +** 'first + place' or '1st + place', as there are entries in the +** FTS index corresponding to both forms of the first token. +** </ol> +** +** Whether it is parsing document or query text, any call to xToken that +** specifies a <i>tflags</i> argument with the FTS5_TOKEN_COLOCATED bit +** is considered to supply a synonym for the previous token. For example, +** when parsing the document "I won first place", a tokenizer that supports +** synonyms would call xToken() 5 times, as follows: +** +** <codeblock> +** xToken(pCtx, 0, "i", 1, 0, 1); +** xToken(pCtx, 0, "won", 3, 2, 5); +** xToken(pCtx, 0, "first", 5, 6, 11); +** xToken(pCtx, FTS5_TOKEN_COLOCATED, "1st", 3, 6, 11); +** xToken(pCtx, 0, "place", 5, 12, 17); +**</codeblock> +** +** It is an error to specify the FTS5_TOKEN_COLOCATED flag the first time +** xToken() is called. Multiple synonyms may be specified for a single token +** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. +** There is no limit to the number of synonyms that may be provided for a +** single token. +** +** In many cases, method (1) above is the best approach. It does not add +** extra data to the FTS index or require FTS5 to query for multiple terms, +** so it is efficient in terms of disk space and query speed. However, it +** does not support prefix queries very well. If, as suggested above, the +** token "first" is substituted for "1st" by the tokenizer, then the query: +** +** <codeblock> +** ... MATCH '1s*'</codeblock> +** +** will not match documents that contain the token "1st" (as the tokenizer +** will probably not map "1s" to any prefix of "first"). +** +** For full prefix support, method (3) may be preferred. In this case, +** because the index contains entries for both "first" and "1st", prefix +** queries such as 'fi*' or '1s*' will match correctly. However, because +** extra entries are added to the FTS index, this method uses more space +** within the database. +** +** Method (2) offers a midpoint between (1) and (3). Using this method, +** a query such as '1s*' will match documents that contain the literal +** token "1st", but not "first" (assuming the tokenizer is not able to +** provide synonyms for prefixes). However, a non-prefix query like '1st' +** will match against "1st" and "first". This method does not require +** extra disk space, as no extra entries are added to the FTS index. +** On the other hand, it may require more CPU cycles to run MATCH queries, +** as separate queries of the FTS index are required for each synonym. +** +** When using methods (2) or (3), it is important that the tokenizer only +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is +** inefficient. +*/ +typedef struct Fts5Tokenizer Fts5Tokenizer; +typedef struct fts5_tokenizer fts5_tokenizer; +struct fts5_tokenizer { + int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + void (*xDelete)(Fts5Tokenizer*); + int (*xTokenize)(Fts5Tokenizer*, + void *pCtx, + int flags, /* Mask of FTS5_TOKENIZE_* flags */ + const char *pText, int nText, + int (*xToken)( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStart, /* Byte offset of token within input text */ + int iEnd /* Byte offset of end of token within input text */ + ) + ); +}; + +/* Flags that may be passed as the third argument to xTokenize() */ +#define FTS5_TOKENIZE_QUERY 0x0001 +#define FTS5_TOKENIZE_PREFIX 0x0002 +#define FTS5_TOKENIZE_DOCUMENT 0x0004 +#define FTS5_TOKENIZE_AUX 0x0008 + +/* Flags that may be passed by the tokenizer implementation back to FTS5 +** as the third argument to the supplied xToken callback. */ +#define FTS5_TOKEN_COLOCATED 0x0001 /* Same position as prev. token */ + +/* +** END OF CUSTOM TOKENIZERS +*************************************************************************/ + +/************************************************************************* +** FTS5 EXTENSION REGISTRATION API +*/ +typedef struct fts5_api fts5_api; +struct fts5_api { + int iVersion; /* Currently always set to 2 */ + + /* Create a new tokenizer */ + int (*xCreateTokenizer)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_tokenizer *pTokenizer, + void (*xDestroy)(void*) + ); + + /* Find an existing tokenizer */ + int (*xFindTokenizer)( + fts5_api *pApi, + const char *zName, + void **ppUserData, + fts5_tokenizer *pTokenizer + ); + + /* Create a new auxiliary function */ + int (*xCreateFunction)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_extension_function xFunction, + void (*xDestroy)(void*) + ); +}; + +/* +** END OF REGISTRATION API +*************************************************************************/ + +#ifdef __cplusplus +} /* end of the 'extern "C"' block */ +#endif + +#endif /* _FTS5_H */ + +/******** End of fts5.h *********/ diff --git a/SQLITE/sqlite3.pc.in b/SQLITE/sqlite3.pc.in new file mode 100644 index 0000000..3799671 --- /dev/null +++ b/SQLITE/sqlite3.pc.in @@ -0,0 +1,13 @@ +# Package Information for pkg-config + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: SQLite +Description: SQL database engine +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lsqlite3 +Libs.private: @LIBS@ +Cflags: -I${includedir} diff --git a/SQLITE/sqlite3.rc b/SQLITE/sqlite3.rc new file mode 100644 index 0000000..5a85649 --- /dev/null +++ b/SQLITE/sqlite3.rc @@ -0,0 +1,83 @@ +/* +** 2012 September 2 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code and resources that are specific to Windows. +*/ + +#if !defined(_WIN32_WCE) +#include "winresrc.h" +#else +#include "windows.h" +#endif /* !defined(_WIN32_WCE) */ + +#if !defined(VS_FF_NONE) +# define VS_FF_NONE 0x00000000L +#endif /* !defined(VS_FF_NONE) */ + +#include "sqlite3.h" +#include "sqlite3rc.h" + +/* + * English (U.S.) resources + */ + +#if defined(_WIN32) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif /* defined(_WIN32) */ + +/* + * Icon + */ + +#if !defined(RC_VERONLY) +#define IDI_SQLITE 101 + +IDI_SQLITE ICON "..\\art\\sqlite370.ico" +#endif /* !defined(RC_VERONLY) */ + +/* + * Version + */ + +VS_VERSION_INFO VERSIONINFO + FILEVERSION SQLITE_RESOURCE_VERSION + PRODUCTVERSION SQLITE_RESOURCE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#if defined(_DEBUG) + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS VS_FF_NONE +#endif /* defined(_DEBUG) */ + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "SQLite Development Team" + VALUE "FileDescription", "SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine." + VALUE "FileVersion", SQLITE_VERSION + VALUE "InternalName", "sqlite3" + VALUE "LegalCopyright", "http://www.sqlite.org/copyright.html" + VALUE "ProductName", "SQLite" + VALUE "ProductVersion", SQLITE_VERSION + VALUE "SourceId", SQLITE_SOURCE_ID + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END +END diff --git a/SQLITE/sqlite3ext.h b/SQLITE/sqlite3ext.h new file mode 100644 index 0000000..7116380 --- /dev/null +++ b/SQLITE/sqlite3ext.h @@ -0,0 +1,713 @@ +/* +** 2006 June 7 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the SQLite interface for use by +** shared libraries that want to be imported as extensions into +** an SQLite instance. Shared libraries that intend to be loaded +** as extensions by SQLite should #include this file instead of +** sqlite3.h. +*/ +#ifndef SQLITE3EXT_H +#define SQLITE3EXT_H +#include "sqlite3.h" + +/* +** The following structure holds pointers to all of the SQLite API +** routines. +** +** WARNING: In order to maintain backwards compatibility, add new +** interfaces to the end of this structure only. If you insert new +** interfaces in the middle of this structure, then older different +** versions of SQLite will not be able to load each other's shared +** libraries! +*/ +struct sqlite3_api_routines { + void * (*aggregate_context)(sqlite3_context*,int nBytes); + int (*aggregate_count)(sqlite3_context*); + int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*)); + int (*bind_double)(sqlite3_stmt*,int,double); + int (*bind_int)(sqlite3_stmt*,int,int); + int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64); + int (*bind_null)(sqlite3_stmt*,int); + int (*bind_parameter_count)(sqlite3_stmt*); + int (*bind_parameter_index)(sqlite3_stmt*,const char*zName); + const char * (*bind_parameter_name)(sqlite3_stmt*,int); + int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*)); + int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*)); + int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*); + int (*busy_handler)(sqlite3*,int(*)(void*,int),void*); + int (*busy_timeout)(sqlite3*,int ms); + int (*changes)(sqlite3*); + int (*close)(sqlite3*); + int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*, + int eTextRep,const char*)); + int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*, + int eTextRep,const void*)); + const void * (*column_blob)(sqlite3_stmt*,int iCol); + int (*column_bytes)(sqlite3_stmt*,int iCol); + int (*column_bytes16)(sqlite3_stmt*,int iCol); + int (*column_count)(sqlite3_stmt*pStmt); + const char * (*column_database_name)(sqlite3_stmt*,int); + const void * (*column_database_name16)(sqlite3_stmt*,int); + const char * (*column_decltype)(sqlite3_stmt*,int i); + const void * (*column_decltype16)(sqlite3_stmt*,int); + double (*column_double)(sqlite3_stmt*,int iCol); + int (*column_int)(sqlite3_stmt*,int iCol); + sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol); + const char * (*column_name)(sqlite3_stmt*,int); + const void * (*column_name16)(sqlite3_stmt*,int); + const char * (*column_origin_name)(sqlite3_stmt*,int); + const void * (*column_origin_name16)(sqlite3_stmt*,int); + const char * (*column_table_name)(sqlite3_stmt*,int); + const void * (*column_table_name16)(sqlite3_stmt*,int); + const unsigned char * (*column_text)(sqlite3_stmt*,int iCol); + const void * (*column_text16)(sqlite3_stmt*,int iCol); + int (*column_type)(sqlite3_stmt*,int iCol); + sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol); + void * (*commit_hook)(sqlite3*,int(*)(void*),void*); + int (*complete)(const char*sql); + int (*complete16)(const void*sql); + int (*create_collation)(sqlite3*,const char*,int,void*, + int(*)(void*,int,const void*,int,const void*)); + int (*create_collation16)(sqlite3*,const void*,int,void*, + int(*)(void*,int,const void*,int,const void*)); + int (*create_function)(sqlite3*,const char*,int,int,void*, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*)); + int (*create_function16)(sqlite3*,const void*,int,int,void*, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*)); + int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*); + int (*data_count)(sqlite3_stmt*pStmt); + sqlite3 * (*db_handle)(sqlite3_stmt*); + int (*declare_vtab)(sqlite3*,const char*); + int (*enable_shared_cache)(int); + int (*errcode)(sqlite3*db); + const char * (*errmsg)(sqlite3*); + const void * (*errmsg16)(sqlite3*); + int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**); + int (*expired)(sqlite3_stmt*); + int (*finalize)(sqlite3_stmt*pStmt); + void (*free)(void*); + void (*free_table)(char**result); + int (*get_autocommit)(sqlite3*); + void * (*get_auxdata)(sqlite3_context*,int); + int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**); + int (*global_recover)(void); + void (*interruptx)(sqlite3*); + sqlite_int64 (*last_insert_rowid)(sqlite3*); + const char * (*libversion)(void); + int (*libversion_number)(void); + void *(*malloc)(int); + char * (*mprintf)(const char*,...); + int (*open)(const char*,sqlite3**); + int (*open16)(const void*,sqlite3**); + int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**); + int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**); + void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*); + void (*progress_handler)(sqlite3*,int,int(*)(void*),void*); + void *(*realloc)(void*,int); + int (*reset)(sqlite3_stmt*pStmt); + void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_double)(sqlite3_context*,double); + void (*result_error)(sqlite3_context*,const char*,int); + void (*result_error16)(sqlite3_context*,const void*,int); + void (*result_int)(sqlite3_context*,int); + void (*result_int64)(sqlite3_context*,sqlite_int64); + void (*result_null)(sqlite3_context*); + void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*)); + void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_value)(sqlite3_context*,sqlite3_value*); + void * (*rollback_hook)(sqlite3*,void(*)(void*),void*); + int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*, + const char*,const char*),void*); + void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*)); + char * (*xsnprintf)(int,char*,const char*,...); + int (*step)(sqlite3_stmt*); + int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*, + char const**,char const**,int*,int*,int*); + void (*thread_cleanup)(void); + int (*total_changes)(sqlite3*); + void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*); + int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*); + void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*, + sqlite_int64),void*); + void * (*user_data)(sqlite3_context*); + const void * (*value_blob)(sqlite3_value*); + int (*value_bytes)(sqlite3_value*); + int (*value_bytes16)(sqlite3_value*); + double (*value_double)(sqlite3_value*); + int (*value_int)(sqlite3_value*); + sqlite_int64 (*value_int64)(sqlite3_value*); + int (*value_numeric_type)(sqlite3_value*); + const unsigned char * (*value_text)(sqlite3_value*); + const void * (*value_text16)(sqlite3_value*); + const void * (*value_text16be)(sqlite3_value*); + const void * (*value_text16le)(sqlite3_value*); + int (*value_type)(sqlite3_value*); + char *(*vmprintf)(const char*,va_list); + /* Added ??? */ + int (*overload_function)(sqlite3*, const char *zFuncName, int nArg); + /* Added by 3.3.13 */ + int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**); + int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**); + int (*clear_bindings)(sqlite3_stmt*); + /* Added by 3.4.1 */ + int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*, + void (*xDestroy)(void *)); + /* Added by 3.5.0 */ + int (*bind_zeroblob)(sqlite3_stmt*,int,int); + int (*blob_bytes)(sqlite3_blob*); + int (*blob_close)(sqlite3_blob*); + int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64, + int,sqlite3_blob**); + int (*blob_read)(sqlite3_blob*,void*,int,int); + int (*blob_write)(sqlite3_blob*,const void*,int,int); + int (*create_collation_v2)(sqlite3*,const char*,int,void*, + int(*)(void*,int,const void*,int,const void*), + void(*)(void*)); + int (*file_control)(sqlite3*,const char*,int,void*); + sqlite3_int64 (*memory_highwater)(int); + sqlite3_int64 (*memory_used)(void); + sqlite3_mutex *(*mutex_alloc)(int); + void (*mutex_enter)(sqlite3_mutex*); + void (*mutex_free)(sqlite3_mutex*); + void (*mutex_leave)(sqlite3_mutex*); + int (*mutex_try)(sqlite3_mutex*); + int (*open_v2)(const char*,sqlite3**,int,const char*); + int (*release_memory)(int); + void (*result_error_nomem)(sqlite3_context*); + void (*result_error_toobig)(sqlite3_context*); + int (*sleep)(int); + void (*soft_heap_limit)(int); + sqlite3_vfs *(*vfs_find)(const char*); + int (*vfs_register)(sqlite3_vfs*,int); + int (*vfs_unregister)(sqlite3_vfs*); + int (*xthreadsafe)(void); + void (*result_zeroblob)(sqlite3_context*,int); + void (*result_error_code)(sqlite3_context*,int); + int (*test_control)(int, ...); + void (*randomness)(int,void*); + sqlite3 *(*context_db_handle)(sqlite3_context*); + int (*extended_result_codes)(sqlite3*,int); + int (*limit)(sqlite3*,int,int); + sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*); + const char *(*sql)(sqlite3_stmt*); + int (*status)(int,int*,int*,int); + int (*backup_finish)(sqlite3_backup*); + sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*); + int (*backup_pagecount)(sqlite3_backup*); + int (*backup_remaining)(sqlite3_backup*); + int (*backup_step)(sqlite3_backup*,int); + const char *(*compileoption_get)(int); + int (*compileoption_used)(const char*); + int (*create_function_v2)(sqlite3*,const char*,int,int,void*, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void(*xDestroy)(void*)); + int (*db_config)(sqlite3*,int,...); + sqlite3_mutex *(*db_mutex)(sqlite3*); + int (*db_status)(sqlite3*,int,int*,int*,int); + int (*extended_errcode)(sqlite3*); + void (*log)(int,const char*,...); + sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64); + const char *(*sourceid)(void); + int (*stmt_status)(sqlite3_stmt*,int,int); + int (*strnicmp)(const char*,const char*,int); + int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*); + int (*wal_autocheckpoint)(sqlite3*,int); + int (*wal_checkpoint)(sqlite3*,const char*); + void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*); + int (*blob_reopen)(sqlite3_blob*,sqlite3_int64); + int (*vtab_config)(sqlite3*,int op,...); + int (*vtab_on_conflict)(sqlite3*); + /* Version 3.7.16 and later */ + int (*close_v2)(sqlite3*); + const char *(*db_filename)(sqlite3*,const char*); + int (*db_readonly)(sqlite3*,const char*); + int (*db_release_memory)(sqlite3*); + const char *(*errstr)(int); + int (*stmt_busy)(sqlite3_stmt*); + int (*stmt_readonly)(sqlite3_stmt*); + int (*stricmp)(const char*,const char*); + int (*uri_boolean)(const char*,const char*,int); + sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64); + const char *(*uri_parameter)(const char*,const char*); + char *(*xvsnprintf)(int,char*,const char*,va_list); + int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*); + /* Version 3.8.7 and later */ + int (*auto_extension)(void(*)(void)); + int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64, + void(*)(void*)); + int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64, + void(*)(void*),unsigned char); + int (*cancel_auto_extension)(void(*)(void)); + int (*load_extension)(sqlite3*,const char*,const char*,char**); + void *(*malloc64)(sqlite3_uint64); + sqlite3_uint64 (*msize)(void*); + void *(*realloc64)(void*,sqlite3_uint64); + void (*reset_auto_extension)(void); + void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64, + void(*)(void*)); + void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64, + void(*)(void*), unsigned char); + int (*strglob)(const char*,const char*); + /* Version 3.8.11 and later */ + sqlite3_value *(*value_dup)(const sqlite3_value*); + void (*value_free)(sqlite3_value*); + int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64); + int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64); + /* Version 3.9.0 and later */ + unsigned int (*value_subtype)(sqlite3_value*); + void (*result_subtype)(sqlite3_context*,unsigned int); + /* Version 3.10.0 and later */ + int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int); + int (*strlike)(const char*,const char*,unsigned int); + int (*db_cacheflush)(sqlite3*); + /* Version 3.12.0 and later */ + int (*system_errno)(sqlite3*); + /* Version 3.14.0 and later */ + int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*); + char *(*expanded_sql)(sqlite3_stmt*); + /* Version 3.18.0 and later */ + void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64); + /* Version 3.20.0 and later */ + int (*prepare_v3)(sqlite3*,const char*,int,unsigned int, + sqlite3_stmt**,const char**); + int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int, + sqlite3_stmt**,const void**); + int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*)); + void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*)); + void *(*value_pointer)(sqlite3_value*,const char*); + int (*vtab_nochange)(sqlite3_context*); + int (*value_nochange)(sqlite3_value*); + const char *(*vtab_collation)(sqlite3_index_info*,int); + /* Version 3.24.0 and later */ + int (*keyword_count)(void); + int (*keyword_name)(int,const char**,int*); + int (*keyword_check)(const char*,int); + sqlite3_str *(*str_new)(sqlite3*); + char *(*str_finish)(sqlite3_str*); + void (*str_appendf)(sqlite3_str*, const char *zFormat, ...); + void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list); + void (*str_append)(sqlite3_str*, const char *zIn, int N); + void (*str_appendall)(sqlite3_str*, const char *zIn); + void (*str_appendchar)(sqlite3_str*, int N, char C); + void (*str_reset)(sqlite3_str*); + int (*str_errcode)(sqlite3_str*); + int (*str_length)(sqlite3_str*); + char *(*str_value)(sqlite3_str*); + /* Version 3.25.0 and later */ + int (*create_window_function)(sqlite3*,const char*,int,int,void*, + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void (*xValue)(sqlite3_context*), + void (*xInv)(sqlite3_context*,int,sqlite3_value**), + void(*xDestroy)(void*)); + /* Version 3.26.0 and later */ + const char *(*normalized_sql)(sqlite3_stmt*); + /* Version 3.28.0 and later */ + int (*stmt_isexplain)(sqlite3_stmt*); + int (*value_frombind)(sqlite3_value*); + /* Version 3.30.0 and later */ + int (*drop_modules)(sqlite3*,const char**); + /* Version 3.31.0 and later */ + sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64); + const char *(*uri_key)(const char*,int); + const char *(*filename_database)(const char*); + const char *(*filename_journal)(const char*); + const char *(*filename_wal)(const char*); + /* Version 3.32.0 and later */ + const char *(*create_filename)(const char*,const char*,const char*, + int,const char**); + void (*free_filename)(const char*); + sqlite3_file *(*database_file_object)(const char*); + /* Version 3.34.0 and later */ + int (*txn_state)(sqlite3*,const char*); + /* Version 3.36.1 and later */ + sqlite3_int64 (*changes64)(sqlite3*); + sqlite3_int64 (*total_changes64)(sqlite3*); + /* Version 3.37.0 and later */ + int (*autovacuum_pages)(sqlite3*, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, void(*)(void*)); + /* Version 3.38.0 and later */ + int (*error_offset)(sqlite3*); + int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**); + int (*vtab_distinct)(sqlite3_index_info*); + int (*vtab_in)(sqlite3_index_info*,int,int); + int (*vtab_in_first)(sqlite3_value*,sqlite3_value**); + int (*vtab_in_next)(sqlite3_value*,sqlite3_value**); + /* Version 3.39.0 and later */ + int (*deserialize)(sqlite3*,const char*,unsigned char*, + sqlite3_int64,sqlite3_int64,unsigned); + unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*, + unsigned int); + const char *(*db_name)(sqlite3*,int); + /* Version 3.40.0 and later */ + int (*value_encoding)(sqlite3_value*); + /* Version 3.41.0 and later */ + int (*is_interrupted)(sqlite3*); + /* Version 3.43.0 and later */ + int (*stmt_explain)(sqlite3_stmt*,int); +}; + +/* +** This is the function signature used for all extension entry points. It +** is also defined in the file "loadext.c". +*/ +typedef int (*sqlite3_loadext_entry)( + sqlite3 *db, /* Handle to the database. */ + char **pzErrMsg, /* Used to set error string on failure. */ + const sqlite3_api_routines *pThunk /* Extension API function pointers. */ +); + +/* +** The following macros redefine the API routines so that they are +** redirected through the global sqlite3_api structure. +** +** This header file is also used by the loadext.c source file +** (part of the main SQLite library - not an extension) so that +** it can get access to the sqlite3_api_routines structure +** definition. But the main library does not want to redefine +** the API. So the redefinition macros are only valid if the +** SQLITE_CORE macros is undefined. +*/ +#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) +#define sqlite3_aggregate_context sqlite3_api->aggregate_context +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_aggregate_count sqlite3_api->aggregate_count +#endif +#define sqlite3_bind_blob sqlite3_api->bind_blob +#define sqlite3_bind_double sqlite3_api->bind_double +#define sqlite3_bind_int sqlite3_api->bind_int +#define sqlite3_bind_int64 sqlite3_api->bind_int64 +#define sqlite3_bind_null sqlite3_api->bind_null +#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count +#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index +#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name +#define sqlite3_bind_text sqlite3_api->bind_text +#define sqlite3_bind_text16 sqlite3_api->bind_text16 +#define sqlite3_bind_value sqlite3_api->bind_value +#define sqlite3_busy_handler sqlite3_api->busy_handler +#define sqlite3_busy_timeout sqlite3_api->busy_timeout +#define sqlite3_changes sqlite3_api->changes +#define sqlite3_close sqlite3_api->close +#define sqlite3_collation_needed sqlite3_api->collation_needed +#define sqlite3_collation_needed16 sqlite3_api->collation_needed16 +#define sqlite3_column_blob sqlite3_api->column_blob +#define sqlite3_column_bytes sqlite3_api->column_bytes +#define sqlite3_column_bytes16 sqlite3_api->column_bytes16 +#define sqlite3_column_count sqlite3_api->column_count +#define sqlite3_column_database_name sqlite3_api->column_database_name +#define sqlite3_column_database_name16 sqlite3_api->column_database_name16 +#define sqlite3_column_decltype sqlite3_api->column_decltype +#define sqlite3_column_decltype16 sqlite3_api->column_decltype16 +#define sqlite3_column_double sqlite3_api->column_double +#define sqlite3_column_int sqlite3_api->column_int +#define sqlite3_column_int64 sqlite3_api->column_int64 +#define sqlite3_column_name sqlite3_api->column_name +#define sqlite3_column_name16 sqlite3_api->column_name16 +#define sqlite3_column_origin_name sqlite3_api->column_origin_name +#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16 +#define sqlite3_column_table_name sqlite3_api->column_table_name +#define sqlite3_column_table_name16 sqlite3_api->column_table_name16 +#define sqlite3_column_text sqlite3_api->column_text +#define sqlite3_column_text16 sqlite3_api->column_text16 +#define sqlite3_column_type sqlite3_api->column_type +#define sqlite3_column_value sqlite3_api->column_value +#define sqlite3_commit_hook sqlite3_api->commit_hook +#define sqlite3_complete sqlite3_api->complete +#define sqlite3_complete16 sqlite3_api->complete16 +#define sqlite3_create_collation sqlite3_api->create_collation +#define sqlite3_create_collation16 sqlite3_api->create_collation16 +#define sqlite3_create_function sqlite3_api->create_function +#define sqlite3_create_function16 sqlite3_api->create_function16 +#define sqlite3_create_module sqlite3_api->create_module +#define sqlite3_create_module_v2 sqlite3_api->create_module_v2 +#define sqlite3_data_count sqlite3_api->data_count +#define sqlite3_db_handle sqlite3_api->db_handle +#define sqlite3_declare_vtab sqlite3_api->declare_vtab +#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache +#define sqlite3_errcode sqlite3_api->errcode +#define sqlite3_errmsg sqlite3_api->errmsg +#define sqlite3_errmsg16 sqlite3_api->errmsg16 +#define sqlite3_exec sqlite3_api->exec +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_expired sqlite3_api->expired +#endif +#define sqlite3_finalize sqlite3_api->finalize +#define sqlite3_free sqlite3_api->free +#define sqlite3_free_table sqlite3_api->free_table +#define sqlite3_get_autocommit sqlite3_api->get_autocommit +#define sqlite3_get_auxdata sqlite3_api->get_auxdata +#define sqlite3_get_table sqlite3_api->get_table +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_global_recover sqlite3_api->global_recover +#endif +#define sqlite3_interrupt sqlite3_api->interruptx +#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid +#define sqlite3_libversion sqlite3_api->libversion +#define sqlite3_libversion_number sqlite3_api->libversion_number +#define sqlite3_malloc sqlite3_api->malloc +#define sqlite3_mprintf sqlite3_api->mprintf +#define sqlite3_open sqlite3_api->open +#define sqlite3_open16 sqlite3_api->open16 +#define sqlite3_prepare sqlite3_api->prepare +#define sqlite3_prepare16 sqlite3_api->prepare16 +#define sqlite3_prepare_v2 sqlite3_api->prepare_v2 +#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2 +#define sqlite3_profile sqlite3_api->profile +#define sqlite3_progress_handler sqlite3_api->progress_handler +#define sqlite3_realloc sqlite3_api->realloc +#define sqlite3_reset sqlite3_api->reset +#define sqlite3_result_blob sqlite3_api->result_blob +#define sqlite3_result_double sqlite3_api->result_double +#define sqlite3_result_error sqlite3_api->result_error +#define sqlite3_result_error16 sqlite3_api->result_error16 +#define sqlite3_result_int sqlite3_api->result_int +#define sqlite3_result_int64 sqlite3_api->result_int64 +#define sqlite3_result_null sqlite3_api->result_null +#define sqlite3_result_text sqlite3_api->result_text +#define sqlite3_result_text16 sqlite3_api->result_text16 +#define sqlite3_result_text16be sqlite3_api->result_text16be +#define sqlite3_result_text16le sqlite3_api->result_text16le +#define sqlite3_result_value sqlite3_api->result_value +#define sqlite3_rollback_hook sqlite3_api->rollback_hook +#define sqlite3_set_authorizer sqlite3_api->set_authorizer +#define sqlite3_set_auxdata sqlite3_api->set_auxdata +#define sqlite3_snprintf sqlite3_api->xsnprintf +#define sqlite3_step sqlite3_api->step +#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata +#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup +#define sqlite3_total_changes sqlite3_api->total_changes +#define sqlite3_trace sqlite3_api->trace +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings +#endif +#define sqlite3_update_hook sqlite3_api->update_hook +#define sqlite3_user_data sqlite3_api->user_data +#define sqlite3_value_blob sqlite3_api->value_blob +#define sqlite3_value_bytes sqlite3_api->value_bytes +#define sqlite3_value_bytes16 sqlite3_api->value_bytes16 +#define sqlite3_value_double sqlite3_api->value_double +#define sqlite3_value_int sqlite3_api->value_int +#define sqlite3_value_int64 sqlite3_api->value_int64 +#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type +#define sqlite3_value_text sqlite3_api->value_text +#define sqlite3_value_text16 sqlite3_api->value_text16 +#define sqlite3_value_text16be sqlite3_api->value_text16be +#define sqlite3_value_text16le sqlite3_api->value_text16le +#define sqlite3_value_type sqlite3_api->value_type +#define sqlite3_vmprintf sqlite3_api->vmprintf +#define sqlite3_vsnprintf sqlite3_api->xvsnprintf +#define sqlite3_overload_function sqlite3_api->overload_function +#define sqlite3_prepare_v2 sqlite3_api->prepare_v2 +#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2 +#define sqlite3_clear_bindings sqlite3_api->clear_bindings +#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob +#define sqlite3_blob_bytes sqlite3_api->blob_bytes +#define sqlite3_blob_close sqlite3_api->blob_close +#define sqlite3_blob_open sqlite3_api->blob_open +#define sqlite3_blob_read sqlite3_api->blob_read +#define sqlite3_blob_write sqlite3_api->blob_write +#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2 +#define sqlite3_file_control sqlite3_api->file_control +#define sqlite3_memory_highwater sqlite3_api->memory_highwater +#define sqlite3_memory_used sqlite3_api->memory_used +#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc +#define sqlite3_mutex_enter sqlite3_api->mutex_enter +#define sqlite3_mutex_free sqlite3_api->mutex_free +#define sqlite3_mutex_leave sqlite3_api->mutex_leave +#define sqlite3_mutex_try sqlite3_api->mutex_try +#define sqlite3_open_v2 sqlite3_api->open_v2 +#define sqlite3_release_memory sqlite3_api->release_memory +#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem +#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig +#define sqlite3_sleep sqlite3_api->sleep +#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit +#define sqlite3_vfs_find sqlite3_api->vfs_find +#define sqlite3_vfs_register sqlite3_api->vfs_register +#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister +#define sqlite3_threadsafe sqlite3_api->xthreadsafe +#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob +#define sqlite3_result_error_code sqlite3_api->result_error_code +#define sqlite3_test_control sqlite3_api->test_control +#define sqlite3_randomness sqlite3_api->randomness +#define sqlite3_context_db_handle sqlite3_api->context_db_handle +#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes +#define sqlite3_limit sqlite3_api->limit +#define sqlite3_next_stmt sqlite3_api->next_stmt +#define sqlite3_sql sqlite3_api->sql +#define sqlite3_status sqlite3_api->status +#define sqlite3_backup_finish sqlite3_api->backup_finish +#define sqlite3_backup_init sqlite3_api->backup_init +#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount +#define sqlite3_backup_remaining sqlite3_api->backup_remaining +#define sqlite3_backup_step sqlite3_api->backup_step +#define sqlite3_compileoption_get sqlite3_api->compileoption_get +#define sqlite3_compileoption_used sqlite3_api->compileoption_used +#define sqlite3_create_function_v2 sqlite3_api->create_function_v2 +#define sqlite3_db_config sqlite3_api->db_config +#define sqlite3_db_mutex sqlite3_api->db_mutex +#define sqlite3_db_status sqlite3_api->db_status +#define sqlite3_extended_errcode sqlite3_api->extended_errcode +#define sqlite3_log sqlite3_api->log +#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64 +#define sqlite3_sourceid sqlite3_api->sourceid +#define sqlite3_stmt_status sqlite3_api->stmt_status +#define sqlite3_strnicmp sqlite3_api->strnicmp +#define sqlite3_unlock_notify sqlite3_api->unlock_notify +#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint +#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint +#define sqlite3_wal_hook sqlite3_api->wal_hook +#define sqlite3_blob_reopen sqlite3_api->blob_reopen +#define sqlite3_vtab_config sqlite3_api->vtab_config +#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict +/* Version 3.7.16 and later */ +#define sqlite3_close_v2 sqlite3_api->close_v2 +#define sqlite3_db_filename sqlite3_api->db_filename +#define sqlite3_db_readonly sqlite3_api->db_readonly +#define sqlite3_db_release_memory sqlite3_api->db_release_memory +#define sqlite3_errstr sqlite3_api->errstr +#define sqlite3_stmt_busy sqlite3_api->stmt_busy +#define sqlite3_stmt_readonly sqlite3_api->stmt_readonly +#define sqlite3_stricmp sqlite3_api->stricmp +#define sqlite3_uri_boolean sqlite3_api->uri_boolean +#define sqlite3_uri_int64 sqlite3_api->uri_int64 +#define sqlite3_uri_parameter sqlite3_api->uri_parameter +#define sqlite3_uri_vsnprintf sqlite3_api->xvsnprintf +#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2 +/* Version 3.8.7 and later */ +#define sqlite3_auto_extension sqlite3_api->auto_extension +#define sqlite3_bind_blob64 sqlite3_api->bind_blob64 +#define sqlite3_bind_text64 sqlite3_api->bind_text64 +#define sqlite3_cancel_auto_extension sqlite3_api->cancel_auto_extension +#define sqlite3_load_extension sqlite3_api->load_extension +#define sqlite3_malloc64 sqlite3_api->malloc64 +#define sqlite3_msize sqlite3_api->msize +#define sqlite3_realloc64 sqlite3_api->realloc64 +#define sqlite3_reset_auto_extension sqlite3_api->reset_auto_extension +#define sqlite3_result_blob64 sqlite3_api->result_blob64 +#define sqlite3_result_text64 sqlite3_api->result_text64 +#define sqlite3_strglob sqlite3_api->strglob +/* Version 3.8.11 and later */ +#define sqlite3_value_dup sqlite3_api->value_dup +#define sqlite3_value_free sqlite3_api->value_free +#define sqlite3_result_zeroblob64 sqlite3_api->result_zeroblob64 +#define sqlite3_bind_zeroblob64 sqlite3_api->bind_zeroblob64 +/* Version 3.9.0 and later */ +#define sqlite3_value_subtype sqlite3_api->value_subtype +#define sqlite3_result_subtype sqlite3_api->result_subtype +/* Version 3.10.0 and later */ +#define sqlite3_status64 sqlite3_api->status64 +#define sqlite3_strlike sqlite3_api->strlike +#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush +/* Version 3.12.0 and later */ +#define sqlite3_system_errno sqlite3_api->system_errno +/* Version 3.14.0 and later */ +#define sqlite3_trace_v2 sqlite3_api->trace_v2 +#define sqlite3_expanded_sql sqlite3_api->expanded_sql +/* Version 3.18.0 and later */ +#define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid +/* Version 3.20.0 and later */ +#define sqlite3_prepare_v3 sqlite3_api->prepare_v3 +#define sqlite3_prepare16_v3 sqlite3_api->prepare16_v3 +#define sqlite3_bind_pointer sqlite3_api->bind_pointer +#define sqlite3_result_pointer sqlite3_api->result_pointer +#define sqlite3_value_pointer sqlite3_api->value_pointer +/* Version 3.22.0 and later */ +#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange +#define sqlite3_value_nochange sqlite3_api->value_nochange +#define sqlite3_vtab_collation sqlite3_api->vtab_collation +/* Version 3.24.0 and later */ +#define sqlite3_keyword_count sqlite3_api->keyword_count +#define sqlite3_keyword_name sqlite3_api->keyword_name +#define sqlite3_keyword_check sqlite3_api->keyword_check +#define sqlite3_str_new sqlite3_api->str_new +#define sqlite3_str_finish sqlite3_api->str_finish +#define sqlite3_str_appendf sqlite3_api->str_appendf +#define sqlite3_str_vappendf sqlite3_api->str_vappendf +#define sqlite3_str_append sqlite3_api->str_append +#define sqlite3_str_appendall sqlite3_api->str_appendall +#define sqlite3_str_appendchar sqlite3_api->str_appendchar +#define sqlite3_str_reset sqlite3_api->str_reset +#define sqlite3_str_errcode sqlite3_api->str_errcode +#define sqlite3_str_length sqlite3_api->str_length +#define sqlite3_str_value sqlite3_api->str_value +/* Version 3.25.0 and later */ +#define sqlite3_create_window_function sqlite3_api->create_window_function +/* Version 3.26.0 and later */ +#define sqlite3_normalized_sql sqlite3_api->normalized_sql +/* Version 3.28.0 and later */ +#define sqlite3_stmt_isexplain sqlite3_api->stmt_isexplain +#define sqlite3_value_frombind sqlite3_api->value_frombind +/* Version 3.30.0 and later */ +#define sqlite3_drop_modules sqlite3_api->drop_modules +/* Version 3.31.0 and later */ +#define sqlite3_hard_heap_limit64 sqlite3_api->hard_heap_limit64 +#define sqlite3_uri_key sqlite3_api->uri_key +#define sqlite3_filename_database sqlite3_api->filename_database +#define sqlite3_filename_journal sqlite3_api->filename_journal +#define sqlite3_filename_wal sqlite3_api->filename_wal +/* Version 3.32.0 and later */ +#define sqlite3_create_filename sqlite3_api->create_filename +#define sqlite3_free_filename sqlite3_api->free_filename +#define sqlite3_database_file_object sqlite3_api->database_file_object +/* Version 3.34.0 and later */ +#define sqlite3_txn_state sqlite3_api->txn_state +/* Version 3.36.1 and later */ +#define sqlite3_changes64 sqlite3_api->changes64 +#define sqlite3_total_changes64 sqlite3_api->total_changes64 +/* Version 3.37.0 and later */ +#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages +/* Version 3.38.0 and later */ +#define sqlite3_error_offset sqlite3_api->error_offset +#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value +#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct +#define sqlite3_vtab_in sqlite3_api->vtab_in +#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first +#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next +/* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE +#define sqlite3_deserialize sqlite3_api->deserialize +#define sqlite3_serialize sqlite3_api->serialize +#endif +#define sqlite3_db_name sqlite3_api->db_name +/* Version 3.40.0 and later */ +#define sqlite3_value_encoding sqlite3_api->value_encoding +/* Version 3.41.0 and later */ +#define sqlite3_is_interrupted sqlite3_api->is_interrupted +/* Version 3.43.0 and later */ +#define sqlite3_stmt_explain sqlite3_api->stmt_explain +#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ + +#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) + /* This case when the file really is being compiled as a loadable + ** extension */ +# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0; +# define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v; +# define SQLITE_EXTENSION_INIT3 \ + extern const sqlite3_api_routines *sqlite3_api; +#else + /* This case when the file is being statically linked into the + ** application */ +# define SQLITE_EXTENSION_INIT1 /*no-op*/ +# define SQLITE_EXTENSION_INIT2(v) (void)v; /* unused parameter */ +# define SQLITE_EXTENSION_INIT3 /*no-op*/ +#endif + +#endif /* SQLITE3EXT_H */ diff --git a/SQLITE/sqlite3rc.h b/SQLITE/sqlite3rc.h new file mode 100644 index 0000000..6fb3a02 --- /dev/null +++ b/SQLITE/sqlite3rc.h @@ -0,0 +1,3 @@ +#ifndef SQLITE_RESOURCE_VERSION +#define SQLITE_RESOURCE_VERSION 3,44,0 +#endif diff --git a/SQLITE/tea/Makefile.in b/SQLITE/tea/Makefile.in new file mode 100644 index 0000000..5264f89 --- /dev/null +++ b/SQLITE/tea/Makefile.in @@ -0,0 +1,475 @@ +# Makefile.in -- +# +# This file is a Makefile for Sample TEA Extension. If it has the name +# "Makefile.in" then it is a template for a Makefile; to generate the +# actual Makefile, run "./configure", which is a configuration script +# generated by the "autoconf" program (constructs like "@foo@" will get +# replaced in the actual Makefile. +# +# Copyright (c) 1999 Scriptics Corporation. +# Copyright (c) 2002-2005 ActiveState Corporation. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +#======================================================================== +# Add additional lines to handle any additional AC_SUBST cases that +# have been added in a customized configure script. +#======================================================================== + +#SAMPLE_NEW_VAR = @SAMPLE_NEW_VAR@ + +#======================================================================== +# Nothing of the variables below this line should need to be changed. +# Please check the TARGETS section below to make sure the make targets +# are correct. +#======================================================================== + +#======================================================================== +# The names of the source files is defined in the configure script. +# The object files are used for linking into the final library. +# This will be used when a dist target is added to the Makefile. +# It is not important to specify the directory, as long as it is the +# $(srcdir) or in the generic, win or unix subdirectory. +#======================================================================== + +PKG_SOURCES = @PKG_SOURCES@ +PKG_OBJECTS = @PKG_OBJECTS@ + +PKG_STUB_SOURCES = @PKG_STUB_SOURCES@ +PKG_STUB_OBJECTS = @PKG_STUB_OBJECTS@ + +#======================================================================== +# PKG_TCL_SOURCES identifies Tcl runtime files that are associated with +# this package that need to be installed, if any. +#======================================================================== + +PKG_TCL_SOURCES = @PKG_TCL_SOURCES@ + +#======================================================================== +# This is a list of public header files to be installed, if any. +#======================================================================== + +PKG_HEADERS = @PKG_HEADERS@ + +#======================================================================== +# "PKG_LIB_FILE" refers to the library (dynamic or static as per +# configuration options) composed of the named objects. +#======================================================================== + +PKG_LIB_FILE = @PKG_LIB_FILE@ +PKG_LIB_FILE8 = @PKG_LIB_FILE8@ +PKG_LIB_FILE9 = @PKG_LIB_FILE9@ +PKG_STUB_LIB_FILE = @PKG_STUB_LIB_FILE@ + +lib_BINARIES = $(PKG_LIB_FILE) +BINARIES = $(lib_BINARIES) + +SHELL = @SHELL@ + +srcdir = @srcdir@ +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +bindir = @bindir@ +libdir = @libdir@ +includedir = @includedir@ +datarootdir = @datarootdir@ +runstatedir = @runstatedir@ +datadir = @datadir@ +mandir = @mandir@ + +DESTDIR = + +PKG_DIR = $(PACKAGE_NAME)$(PACKAGE_VERSION) +pkgdatadir = $(datadir)/$(PKG_DIR) +pkglibdir = $(libdir)/$(PKG_DIR) +pkgincludedir = $(includedir)/$(PKG_DIR) + +top_builddir = @abs_top_builddir@ + +INSTALL_OPTIONS = +INSTALL = @INSTALL@ $(INSTALL_OPTIONS) +INSTALL_DATA_DIR = @INSTALL_DATA_DIR@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_LIBRARY = @INSTALL_LIBRARY@ + +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +CC = @CC@ +CCLD = @CCLD@ +CFLAGS_DEFAULT = @CFLAGS_DEFAULT@ +CFLAGS_WARNING = @CFLAGS_WARNING@ +EXEEXT = @EXEEXT@ +LDFLAGS_DEFAULT = @LDFLAGS_DEFAULT@ +MAKE_LIB = @MAKE_LIB@ +MAKE_STUB_LIB = @MAKE_STUB_LIB@ +OBJEXT = @OBJEXT@ +RANLIB = @RANLIB@ +RANLIB_STUB = @RANLIB_STUB@ +SHLIB_CFLAGS = @SHLIB_CFLAGS@ +SHLIB_LD = @SHLIB_LD@ +SHLIB_LD_LIBS = @SHLIB_LD_LIBS@ +STLIB_LD = @STLIB_LD@ +#TCL_DEFS = @TCL_DEFS@ +TCL_BIN_DIR = @TCL_BIN_DIR@ +TCL_SRC_DIR = @TCL_SRC_DIR@ +#TK_BIN_DIR = @TK_BIN_DIR@ +#TK_SRC_DIR = @TK_SRC_DIR@ + +# Not used, but retained for reference of what libs Tcl required +#TCL_LIBS = @TCL_LIBS@ + +#======================================================================== +# TCLLIBPATH seeds the auto_path in Tcl's init.tcl so we can test our +# package without installing. The other environment variables allow us +# to test against an uninstalled Tcl. Add special env vars that you +# require for testing here (like TCLX_LIBRARY). +#======================================================================== + +EXTRA_PATH = $(top_builddir):$(TCL_BIN_DIR) +#EXTRA_PATH = $(top_builddir):$(TCL_BIN_DIR):$(TK_BIN_DIR) +TCLLIBPATH = $(top_builddir) +TCLSH_ENV = TCL_LIBRARY=`@CYGPATH@ $(TCL_SRC_DIR)/library` +PKG_ENV = @LD_LIBRARY_PATH_VAR@="$(EXTRA_PATH):$(@LD_LIBRARY_PATH_VAR@)" \ + PATH="$(EXTRA_PATH):$(PATH)" \ + TCLLIBPATH="$(TCLLIBPATH)" + +TCLSH_PROG = @TCLSH_PROG@ +TCLSH = $(TCLSH_ENV) $(PKG_ENV) $(TCLSH_PROG) + +#WISH_ENV = TK_LIBRARY=`@CYGPATH@ $(TK_SRC_DIR)/library` +#WISH_PROG = @WISH_PROG@ +#WISH = $(TCLSH_ENV) $(WISH_ENV) $(PKG_ENV) $(WISH_PROG) + +SHARED_BUILD = @SHARED_BUILD@ + +INCLUDES = @PKG_INCLUDES@ @TCL_INCLUDES@ -I. -I$(srcdir)/.. +#INCLUDES = @PKG_INCLUDES@ @TCL_INCLUDES@ @TK_INCLUDES@ @TK_XINCLUDES@ + +PKG_CFLAGS = @PKG_CFLAGS@ + +# TCL_DEFS is not strictly need here, but if you remove it, then you +# must make sure that configure.ac checks for the necessary components +# that your library may use. TCL_DEFS can actually be a problem if +# you do not compile with a similar machine setup as the Tcl core was +# compiled with. +#DEFS = $(TCL_DEFS) @DEFS@ $(PKG_CFLAGS) +DEFS = @DEFS@ $(PKG_CFLAGS) + +# Move pkgIndex.tcl to 'BINARIES' var if it is generated in the Makefile +CONFIG_CLEAN_FILES = Makefile pkgIndex.tcl +CLEANFILES = @CLEANFILES@ + +CPPFLAGS = @CPPFLAGS@ +LIBS = @PKG_LIBS@ @LIBS@ +AR = @AR@ +CFLAGS = @CFLAGS@ +LDFLAGS = @LDFLAGS@ +LDFLAGS_DEFAULT = @LDFLAGS_DEFAULT@ +COMPILE = $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) \ + $(CFLAGS_DEFAULT) $(CFLAGS_WARNING) $(SHLIB_CFLAGS) $(CFLAGS) + +GDB = gdb +VALGRIND = valgrind +VALGRINDARGS = --tool=memcheck --num-callers=8 --leak-resolution=high \ + --leak-check=yes --show-reachable=yes -v + +.SUFFIXES: .c .$(OBJEXT) + +#======================================================================== +# Start of user-definable TARGETS section +#======================================================================== + +#======================================================================== +# TEA TARGETS. Please note that the "libraries:" target refers to platform +# independent files, and the "binaries:" target includes executable programs and +# platform-dependent libraries. Modify these targets so that they install +# the various pieces of your package. The make and install rules +# for the BINARIES that you specified above have already been done. +#======================================================================== + +all: binaries libraries doc + +#======================================================================== +# The binaries target builds executable programs, Windows .dll's, unix +# shared/static libraries, and any other platform-dependent files. +# The list of targets to build for "binaries:" is specified at the top +# of the Makefile, in the "BINARIES" variable. +#======================================================================== + +binaries: $(BINARIES) + +libraries: + +#======================================================================== +# Your doc target should differentiate from doc builds (by the developer) +# and doc installs (see install-doc), which just install the docs on the +# end user machine when building from source. +#======================================================================== + +doc: + @echo "If you have documentation to create, place the commands to" + @echo "build the docs in the 'doc:' target. For example:" + @echo " xml2nroff sample.xml > sample.n" + @echo " xml2html sample.xml > sample.html" + +install: all install-binaries install-libraries install-doc + +install-binaries: binaries install-lib-binaries install-bin-binaries + +#======================================================================== +# This rule installs platform-independent files, such as header files. +# The list=...; for p in $$list handles the empty list case x-platform. +#======================================================================== + +install-libraries: libraries + @$(INSTALL_DATA_DIR) "$(DESTDIR)$(includedir)" + @echo "Installing header files in $(DESTDIR)$(includedir)" + @list='$(PKG_HEADERS)'; for i in $$list; do \ + echo "Installing $(srcdir)/$$i" ; \ + $(INSTALL_DATA) $(srcdir)/$$i "$(DESTDIR)$(includedir)" ; \ + done; + +#======================================================================== +# Install documentation. Unix manpages should go in the $(mandir) +# directory. +#======================================================================== + +install-doc: doc + @$(INSTALL_DATA_DIR) "$(DESTDIR)$(mandir)/mann" + @echo "Installing documentation in $(DESTDIR)$(mandir)" + @list='$(srcdir)/doc/*.n'; for i in $$list; do \ + echo "Installing $$i"; \ + $(INSTALL_DATA) $$i "$(DESTDIR)$(mandir)/mann" ; \ + done + +test: binaries libraries + @echo "SQLite TEA distribution does not include tests" + +shell: binaries libraries + @$(TCLSH) $(SCRIPT) + +gdb: + $(TCLSH_ENV) $(PKG_ENV) $(GDB) $(TCLSH_PROG) $(SCRIPT) + +gdb-test: binaries libraries + $(TCLSH_ENV) $(PKG_ENV) $(GDB) \ + --args $(TCLSH_PROG) `@CYGPATH@ $(srcdir)/tests/all.tcl` \ + $(TESTFLAGS) -singleproc 1 \ + -load "package ifneeded $(PACKAGE_NAME) $(PACKAGE_VERSION) \ + [list load `@CYGPATH@ $(PKG_LIB_FILE)` [string totitle $(PACKAGE_NAME)]]" + +valgrind: binaries libraries + $(TCLSH_ENV) $(PKG_ENV) $(VALGRIND) $(VALGRINDARGS) $(TCLSH_PROG) \ + `@CYGPATH@ $(srcdir)/tests/all.tcl` $(TESTFLAGS) + +valgrindshell: binaries libraries + $(TCLSH_ENV) $(PKG_ENV) $(VALGRIND) $(VALGRINDARGS) $(TCLSH_PROG) $(SCRIPT) + +depend: + +#======================================================================== +# $(PKG_LIB_FILE) should be listed as part of the BINARIES variable +# mentioned above. That will ensure that this target is built when you +# run "make binaries". +# +# The $(PKG_OBJECTS) objects are created and linked into the final +# library. In most cases these object files will correspond to the +# source files above. +#======================================================================== + +$(PKG_LIB_FILE): $(PKG_OBJECTS) + -rm -f $(PKG_LIB_FILE) + ${MAKE_LIB} + $(RANLIB) $(PKG_LIB_FILE) + +$(PKG_STUB_LIB_FILE): $(PKG_STUB_OBJECTS) + -rm -f $(PKG_STUB_LIB_FILE) + ${MAKE_STUB_LIB} + $(RANLIB_STUB) $(PKG_STUB_LIB_FILE) + +#======================================================================== +# We need to enumerate the list of .c to .o lines here. +# +# In the following lines, $(srcdir) refers to the toplevel directory +# containing your extension. If your sources are in a subdirectory, +# you will have to modify the paths to reflect this: +# +# sample.$(OBJEXT): $(srcdir)/generic/sample.c +# $(COMPILE) -c `@CYGPATH@ $(srcdir)/generic/sample.c` -o $@ +# +# Setting the VPATH variable to a list of paths will cause the makefile +# to look into these paths when resolving .c to .obj dependencies. +# As necessary, add $(srcdir):$(srcdir)/compat:.... +#======================================================================== + +VPATH = $(srcdir):$(srcdir)/generic:$(srcdir)/unix:$(srcdir)/win:$(srcdir)/macosx + +.c.@OBJEXT@: + $(COMPILE) -c `@CYGPATH@ $<` -o $@ + +tclsample.@OBJEXT@: sampleUuid.h + +$(srcdir)/manifest.uuid: + printf "git-" >$(srcdir)/manifest.uuid + (cd $(srcdir); git rev-parse HEAD >>$(srcdir)/manifest.uuid || \ + (printf "svn-r" >$(srcdir)/manifest.uuid ; \ + svn info --show-item last-changed-revision >>$(srcdir)/manifest.uuid) || \ + printf "unknown" >$(srcdir)/manifest.uuid) + +sampleUuid.h: $(srcdir)/manifest.uuid + echo "#define SAMPLE_VERSION_UUID \\" >$@ + cat $(srcdir)/manifest.uuid >>$@ + echo "" >>$@ + +#======================================================================== +# Distribution creation +# You may need to tweak this target to make it work correctly. +#======================================================================== + +#COMPRESS = tar cvf $(PKG_DIR).tar $(PKG_DIR); compress $(PKG_DIR).tar +COMPRESS = tar zcvf $(PKG_DIR).tar.gz $(PKG_DIR) +DIST_ROOT = /tmp/dist +DIST_DIR = $(DIST_ROOT)/$(PKG_DIR) + +DIST_INSTALL_DATA = CPPROG='cp -p' $(INSTALL) -m 644 +DIST_INSTALL_SCRIPT = CPPROG='cp -p' $(INSTALL) -m 755 + +dist-clean: + rm -rf $(DIST_DIR) $(DIST_ROOT)/$(PKG_DIR).tar.* + +dist: dist-clean $(srcdir)/manifest.uuid + $(INSTALL_DATA_DIR) $(DIST_DIR) + + # TEA files + $(DIST_INSTALL_DATA) $(srcdir)/Makefile.in \ + $(srcdir)/aclocal.m4 $(srcdir)/configure.ac \ + $(DIST_DIR)/ + $(DIST_INSTALL_SCRIPT) $(srcdir)/configure $(DIST_DIR)/ + + $(INSTALL_DATA_DIR) $(DIST_DIR)/tclconfig + $(DIST_INSTALL_DATA) $(srcdir)/tclconfig/README.txt \ + $(srcdir)/manifest.uuid \ + $(srcdir)/tclconfig/tcl.m4 $(srcdir)/tclconfig/install-sh \ + $(DIST_DIR)/tclconfig/ + + # Extension files + $(DIST_INSTALL_DATA) \ + $(srcdir)/ChangeLog \ + $(srcdir)/README.sha \ + $(srcdir)/license.terms \ + $(srcdir)/README \ + $(srcdir)/pkgIndex.tcl.in \ + $(DIST_DIR)/ + + list='demos doc generic library macosx tests unix win'; \ + for p in $$list; do \ + if test -d $(srcdir)/$$p ; then \ + $(INSTALL_DATA_DIR) $(DIST_DIR)/$$p; \ + $(DIST_INSTALL_DATA) $(srcdir)/$$p/* $(DIST_DIR)/$$p/; \ + fi; \ + done + + (cd $(DIST_ROOT); $(COMPRESS);) + +#======================================================================== +# End of user-definable section +#======================================================================== + +#======================================================================== +# Don't modify the file to clean here. Instead, set the "CLEANFILES" +# variable in configure.ac +#======================================================================== + +clean: + -test -z "$(BINARIES)" || rm -f $(BINARIES) + -rm -f *.$(OBJEXT) core *.core + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean: clean + -rm -f *.tab.c + -rm -f $(CONFIG_CLEAN_FILES) + -rm -f config.cache config.log config.status + +#======================================================================== +# Install binary object libraries. On Windows this includes both .dll and +# .lib files. Because the .lib files are not explicitly listed anywhere, +# we need to deduce their existence from the .dll file of the same name. +# Library files go into the lib directory. +# In addition, this will generate the pkgIndex.tcl +# file in the install location (assuming it can find a usable tclsh shell) +# +# You should not have to modify this target. +#======================================================================== + +install-lib-binaries: binaries + @$(INSTALL_DATA_DIR) "$(DESTDIR)$(pkglibdir)" + @list='$(lib_BINARIES)'; for p in $$list; do \ + if test -f $$p; then \ + echo " $(INSTALL_LIBRARY) $$p $(DESTDIR)$(pkglibdir)/$$p"; \ + $(INSTALL_LIBRARY) $$p "$(DESTDIR)$(pkglibdir)/$$p"; \ + ext=`echo $$p|sed -e "s/.*\.//"`; \ + if test "x$$ext" = "xdll"; then \ + lib=`basename $$p|sed -e 's/.[^.]*$$//'`.lib; \ + if test -f $$lib; then \ + echo " $(INSTALL_DATA) $$lib $(DESTDIR)$(pkglibdir)/$$lib"; \ + $(INSTALL_DATA) $$lib "$(DESTDIR)$(pkglibdir)/$$lib"; \ + fi; \ + fi; \ + fi; \ + done + @list='$(PKG_TCL_SOURCES)'; for p in $$list; do \ + if test -f $(srcdir)/$$p; then \ + destp=`basename $$p`; \ + echo " Install $$destp $(DESTDIR)$(pkglibdir)/$$destp"; \ + $(INSTALL_DATA) $(srcdir)/$$p "$(DESTDIR)$(pkglibdir)/$$destp"; \ + fi; \ + done + @if test "x$(SHARED_BUILD)" = "x1"; then \ + echo " Install pkgIndex.tcl $(DESTDIR)$(pkglibdir)"; \ + $(INSTALL_DATA) pkgIndex.tcl "$(DESTDIR)$(pkglibdir)"; \ + fi + +#======================================================================== +# Install binary executables (e.g. .exe files and dependent .dll files) +# This is for files that must go in the bin directory (located next to +# wish and tclsh), like dependent .dll files on Windows. +# +# You should not have to modify this target, except to define bin_BINARIES +# above if necessary. +#======================================================================== + +install-bin-binaries: binaries + @$(INSTALL_DATA_DIR) "$(DESTDIR)$(bindir)" + @list='$(bin_BINARIES)'; for p in $$list; do \ + if test -f $$p; then \ + echo " $(INSTALL_PROGRAM) $$p $(DESTDIR)$(bindir)/$$p"; \ + $(INSTALL_PROGRAM) $$p "$(DESTDIR)$(bindir)/$$p"; \ + fi; \ + done + +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + cd $(top_builddir) \ + && CONFIG_FILES=$@ CONFIG_HEADERS= $(SHELL) ./config.status + +uninstall-binaries: + list='$(lib_BINARIES)'; for p in $$list; do \ + rm -f "$(DESTDIR)$(pkglibdir)/$$p"; \ + done + list='$(PKG_TCL_SOURCES)'; for p in $$list; do \ + p=`basename $$p`; \ + rm -f "$(DESTDIR)$(pkglibdir)/$$p"; \ + done + list='$(bin_BINARIES)'; for p in $$list; do \ + rm -f "$(DESTDIR)$(bindir)/$$p"; \ + done + +.PHONY: all binaries clean depend distclean doc install libraries test +.PHONY: gdb gdb-test valgrind valgrindshell + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/SQLITE/tea/README b/SQLITE/tea/README new file mode 100644 index 0000000..99dc8b8 --- /dev/null +++ b/SQLITE/tea/README @@ -0,0 +1,36 @@ +This is the SQLite extension for Tcl using the Tcl Extension +Architecture (TEA). For additional information on SQLite see + + http://www.sqlite.org/ + + +UNIX BUILD +========== + +Building under most UNIX systems is easy, just run the configure script +and then run make. For more information about the build process, see +the tcl/unix/README file in the Tcl src dist. The following minimal +example will install the extension in the /opt/tcl directory. + + $ cd sqlite-*-tea + $ ./configure --prefix=/opt/tcl + $ make + $ make install + +WINDOWS BUILD +============= + +The recommended method to build extensions under windows is to use the +Msys + Mingw build process. This provides a Unix-style build while +generating native Windows binaries. Using the Msys + Mingw build tools +means that you can use the same configure script as per the Unix build +to create a Makefile. See the tcl/win/README file for the URL of +the Msys + Mingw download. + +If you have VC++ then you may wish to use the files in the win +subdirectory and build the extension using just VC++. These files have +been designed to be as generic as possible but will require some +additional maintenance by the project developer to synchronise with +the TEA configure.in and Makefile.in files. Instructions for using the +VC++ makefile are written in the first part of the Makefile.vc +file. diff --git a/SQLITE/tea/aclocal.m4 b/SQLITE/tea/aclocal.m4 new file mode 100644 index 0000000..0b05739 --- /dev/null +++ b/SQLITE/tea/aclocal.m4 @@ -0,0 +1,9 @@ +# +# Include the TEA standard macro set +# + +builtin(include,tclconfig/tcl.m4) + +# +# Add here whatever m4 macros you want to define for your package +# diff --git a/SQLITE/tea/configure b/SQLITE/tea/configure new file mode 100755 index 0000000..e120e64 --- /dev/null +++ b/SQLITE/tea/configure @@ -0,0 +1,9447 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69 for sqlite 3.44.0. +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +test -n "$DJDIR" || exec 7<&0 </dev/null +exec 6>&1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='sqlite' +PACKAGE_TARNAME='sqlite' +PACKAGE_VERSION='3.44.0' +PACKAGE_STRING='sqlite 3.44.0' +PACKAGE_BUGREPORT='' +PACKAGE_URL='' + +# Factoring default headers for most tests. +ac_includes_default="\ +#include <stdio.h> +#ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif +#ifdef HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif +#ifdef STDC_HEADERS +# include <stdlib.h> +# include <stddef.h> +#else +# ifdef HAVE_STDLIB_H +# include <stdlib.h> +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include <memory.h> +# endif +# include <string.h> +#endif +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif +#ifdef HAVE_INTTYPES_H +# include <inttypes.h> +#endif +#ifdef HAVE_STDINT_H +# include <stdint.h> +#endif +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif" + +ac_subst_vars='LTLIBOBJS +TCLSH_PROG +VC_MANIFEST_EMBED_EXE +VC_MANIFEST_EMBED_DLL +RANLIB_STUB +MAKE_STUB_LIB +MAKE_STATIC_LIB +MAKE_SHARED_LIB +MAKE_LIB +LDFLAGS_DEFAULT +CFLAGS_DEFAULT +LD_LIBRARY_PATH_VAR +SHLIB_CFLAGS +SHLIB_LD_LIBS +SHLIB_LD +STLIB_LD +LDFLAGS_OPTIMIZE +LDFLAGS_DEBUG +CFLAGS_WARNING +CFLAGS_OPTIMIZE +CFLAGS_DEBUG +LIBOBJS +RC +AR +STUBS_BUILD +SHARED_BUILD +TCL_THREADS +TCL_INCLUDES +PKG_OBJECTS +PKG_SOURCES +EGREP +GREP +RANLIB +SET_MAKE +CPP +TCL_SHLIB_LD_LIBS +TCL_LD_FLAGS +TCL_EXTRA_CFLAGS +TCL_DEFS +TCL_LIBS +CLEANFILES +OBJEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +TCL_STUB_LIB_SPEC +TCL_STUB_LIB_FLAG +TCL_STUB_LIB_FILE +TCL_LIB_SPEC +TCL_LIB_FLAG +TCL_LIB_FILE +TCL_SRC_DIR +TCL_BIN_DIR +TCL_PATCH_LEVEL +TCL_VERSION +INSTALL_LIBRARY +INSTALL_SCRIPT +INSTALL_PROGRAM +INSTALL_DATA +INSTALL_DATA_DIR +INSTALL +PKG_CFLAGS +PKG_LIBS +PKG_INCLUDES +PKG_HEADERS +PKG_TCL_SOURCES +PKG_STUB_OBJECTS +PKG_STUB_SOURCES +PKG_STUB_LIB_FILE +PKG_LIB_FILE9 +PKG_LIB_FILE8 +PKG_LIB_FILE +EXEEXT +CYGPATH +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +runstatedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +with_tcl +with_system_sqlite +with_tclinclude +enable_threads +enable_shared +enable_stubs +enable_64bit +enable_64bit_vis +enable_rpath +enable_symbols +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir runstatedir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures sqlite 3.44.0 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/sqlite] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of sqlite 3.44.0:";; + esac + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-threads build with threads (default: on) + --enable-shared build and link with shared libraries (default: on) + --enable-stubs build and link with stub libraries. Always true for + shared builds (default: on) + --enable-64bit enable 64bit support (default: off) + --enable-64bit-vis enable 64bit Sparc VIS support (default: off) + --disable-rpath disable rpath support (default: on) + --enable-symbols build with debugging symbols (default: off) + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-tcl directory containing tcl configuration + (tclConfig.sh) + --with-system-sqlite use a system-supplied libsqlite3 instead of the + bundled one + --with-tclinclude directory containing the public Tcl header files + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a + nonstandard directory <lib dir> + LIBS libraries to pass to the linker, e.g. -l<library> + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if + you have headers in a nonstandard directory <include dir> + CPP C preprocessor + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +sqlite configure 3.44.0 +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_mongrel + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case <limits.h> declares $2. + For example, HP-UX 11i <limits.h> declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. + Prefer <limits.h> to <assert.h> if __STDC__ is defined, since + <limits.h> exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include <limits.h> +#else +# include <assert.h> +#endif + +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main () +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by sqlite $as_me 3.44.0, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +#-------------------------------------------------------------------- +# Call TEA_INIT as the first TEA_ macro to set up initial vars. +# This will define a ${TEA_PLATFORM} variable == "unix" or "windows" +# as well as PKG_LIB_FILE and PKG_STUB_LIB_FILE. +#-------------------------------------------------------------------- + + + TEA_VERSION="3.13" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking TEA configuration" >&5 +$as_echo_n "checking TEA configuration... " >&6; } + if test x"${PACKAGE_NAME}" = x ; then + as_fn_error $? " +The PACKAGE_NAME variable must be defined by your TEA configure.ac" "$LINENO" 5 + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok (TEA ${TEA_VERSION})" >&5 +$as_echo "ok (TEA ${TEA_VERSION})" >&6; } + + # If the user did not set CFLAGS, set it now to keep macros + # like AC_PROG_CC and AC_TRY_COMPILE from adding "-g -O2". + if test "${CFLAGS+set}" != "set" ; then + CFLAGS="" + fi + + case "`uname -s`" in + *win32*|*WIN32*|*MINGW32_*|*MINGW64_*|*MSYS_*) + # Extract the first word of "cygpath", so it can be a program name with args. +set dummy cygpath; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CYGPATH+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CYGPATH"; then + ac_cv_prog_CYGPATH="$CYGPATH" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CYGPATH="cygpath -m" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_CYGPATH" && ac_cv_prog_CYGPATH="echo" +fi +fi +CYGPATH=$ac_cv_prog_CYGPATH +if test -n "$CYGPATH"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CYGPATH" >&5 +$as_echo "$CYGPATH" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + EXEEXT=".exe" + TEA_PLATFORM="windows" + ;; + *CYGWIN_*) + EXEEXT=".exe" + # CYGPATH and TEA_PLATFORM are determined later in LOAD_TCLCONFIG + ;; + *) + CYGPATH=echo + # Maybe we are cross-compiling.... + case ${host_alias} in + *mingw32*) + EXEEXT=".exe" + TEA_PLATFORM="windows" + ;; + *) + EXEEXT="" + TEA_PLATFORM="unix" + ;; + esac + ;; + esac + + # Check if exec_prefix is set. If not use fall back to prefix. + # Note when adjusted, so that TEA_PREFIX can correct for this. + # This is needed for recursive configures, since autoconf propagates + # $prefix, but not $exec_prefix (doh!). + if test x$exec_prefix = xNONE ; then + exec_prefix_default=yes + exec_prefix=$prefix + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: configuring ${PACKAGE_NAME} ${PACKAGE_VERSION}" >&5 +$as_echo "$as_me: configuring ${PACKAGE_NAME} ${PACKAGE_VERSION}" >&6;} + + + + + # This package name must be replaced statically for AC_SUBST to work + + + + # Substitute STUB_LIB_FILE in case package creates a stub library too. + + + # We AC_SUBST these here to ensure they are subst'ed, + # in case the user doesn't call TEA_ADD_... + + + + + + + + + # Configure the installer. + + INSTALL='$(SHELL) $(srcdir)/tclconfig/install-sh -c' + INSTALL_DATA_DIR='${INSTALL} -d -m 755' + INSTALL_DATA='${INSTALL} -m 644' + INSTALL_PROGRAM='${INSTALL} -m 755' + INSTALL_SCRIPT='${INSTALL} -m 755' + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking system version" >&5 +$as_echo_n "checking system version... " >&6; } +if ${tcl_cv_sys_version+:} false; then : + $as_echo_n "(cached) " >&6 +else + + # TEA specific: + if test "${TEA_PLATFORM}" = "windows" ; then + tcl_cv_sys_version=windows + else + tcl_cv_sys_version=`uname -s`-`uname -r` + if test "$?" -ne 0 ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: can't find uname command" >&5 +$as_echo "$as_me: WARNING: can't find uname command" >&2;} + tcl_cv_sys_version=unknown + else + if test "`uname -s`" = "AIX" ; then + tcl_cv_sys_version=AIX-`uname -v`.`uname -r` + fi + if test "`uname -s`" = "NetBSD" -a -f /etc/debian_version ; then + tcl_cv_sys_version=NetBSD-Debian + fi + fi + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_sys_version" >&5 +$as_echo "$tcl_cv_sys_version" >&6; } + system=$tcl_cv_sys_version + + case $system in + HP-UX-*) INSTALL_LIBRARY='${INSTALL} -m 755' ;; + *) INSTALL_LIBRARY='${INSTALL} -m 644' ;; + esac + + + + + + + + + + +ac_aux_dir= +for ac_dir in tclconfig "$srcdir"/tclconfig; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in tclconfig \"$srcdir\"/tclconfig" "$LINENO" 5 +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + + +#-------------------------------------------------------------------- +# Load the tclConfig.sh file +#-------------------------------------------------------------------- + + + + # + # Ok, lets find the tcl configuration + # First, look for one uninstalled. + # the alternative search directory is invoked by --with-tcl + # + + if test x"${no_tcl}" = x ; then + # we reset no_tcl in case something fails here + no_tcl=true + +# Check whether --with-tcl was given. +if test "${with_tcl+set}" = set; then : + withval=$with_tcl; with_tclconfig="${withval}" +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Tcl configuration" >&5 +$as_echo_n "checking for Tcl configuration... " >&6; } + if ${ac_cv_c_tclconfig+:} false; then : + $as_echo_n "(cached) " >&6 +else + + + # First check to see if --with-tcl was specified. + if test x"${with_tclconfig}" != x ; then + case "${with_tclconfig}" in + */tclConfig.sh ) + if test -f "${with_tclconfig}"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with-tcl argument should refer to directory containing tclConfig.sh, not to tclConfig.sh itself" >&5 +$as_echo "$as_me: WARNING: --with-tcl argument should refer to directory containing tclConfig.sh, not to tclConfig.sh itself" >&2;} + with_tclconfig="`echo "${with_tclconfig}" | sed 's!/tclConfig\.sh$!!'`" + fi ;; + esac + if test -f "${with_tclconfig}/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd "${with_tclconfig}"; pwd)`" + else + as_fn_error $? "${with_tclconfig} directory doesn't contain tclConfig.sh" "$LINENO" 5 + fi + fi + + # then check for a private Tcl installation + if test x"${ac_cv_c_tclconfig}" = x ; then + for i in \ + ../tcl \ + `ls -dr ../tcl[8-9].[0-9].[0-9]* 2>/dev/null` \ + `ls -dr ../tcl[8-9].[0-9] 2>/dev/null` \ + `ls -dr ../tcl[8-9].[0-9]* 2>/dev/null` \ + ../../tcl \ + `ls -dr ../../tcl[8-9].[0-9].[0-9]* 2>/dev/null` \ + `ls -dr ../../tcl[8-9].[0-9] 2>/dev/null` \ + `ls -dr ../../tcl[8-9].[0-9]* 2>/dev/null` \ + ../../../tcl \ + `ls -dr ../../../tcl[8-9].[0-9].[0-9]* 2>/dev/null` \ + `ls -dr ../../../tcl[8-9].[0-9] 2>/dev/null` \ + `ls -dr ../../../tcl[8-9].[0-9]* 2>/dev/null` ; do + if test "${TEA_PLATFORM}" = "windows" \ + -a -f "$i/win/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/win; pwd)`" + break + fi + if test -f "$i/unix/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/unix; pwd)`" + break + fi + done + fi + + # on Darwin, check in Framework installation locations + if test "`uname -s`" = "Darwin" -a x"${ac_cv_c_tclconfig}" = x ; then + for i in `ls -d ~/Library/Frameworks 2>/dev/null` \ + `ls -d /Library/Frameworks 2>/dev/null` \ + `ls -d /Network/Library/Frameworks 2>/dev/null` \ + `ls -d /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/Library/Frameworks/Tcl.framework 2>/dev/null` \ + `ls -d /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/Network/Library/Frameworks/Tcl.framework 2>/dev/null` \ + `ls -d /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Tcl.framework 2>/dev/null` \ + ; do + if test -f "$i/Tcl.framework/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/Tcl.framework; pwd)`" + break + fi + done + fi + + # TEA specific: on Windows, check in common installation locations + if test "${TEA_PLATFORM}" = "windows" \ + -a x"${ac_cv_c_tclconfig}" = x ; then + for i in `ls -d C:/Tcl/lib 2>/dev/null` \ + `ls -d C:/Progra~1/Tcl/lib 2>/dev/null` \ + ; do + if test -f "$i/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i; pwd)`" + break + fi + done + fi + + # check in a few common install locations + if test x"${ac_cv_c_tclconfig}" = x ; then + for i in `ls -d ${libdir} 2>/dev/null` \ + `ls -d ${exec_prefix}/lib 2>/dev/null` \ + `ls -d ${prefix}/lib 2>/dev/null` \ + `ls -d /usr/local/lib 2>/dev/null` \ + `ls -d /usr/contrib/lib 2>/dev/null` \ + `ls -d /usr/pkg/lib 2>/dev/null` \ + `ls -d /usr/lib 2>/dev/null` \ + `ls -d /usr/lib64 2>/dev/null` \ + `ls -d /usr/lib/tcl8.6 2>/dev/null` \ + `ls -d /usr/lib/tcl8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl8.6 2>/dev/null` \ + `ls -d /usr/local/lib/tcl8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tcl8.6 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tcl8.5 2>/dev/null` \ + ; do + if test -f "$i/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i; pwd)`" + break + fi + done + fi + + # check in a few other private locations + if test x"${ac_cv_c_tclconfig}" = x ; then + for i in \ + ${srcdir}/../tcl \ + `ls -dr ${srcdir}/../tcl[8-9].[0-9].[0-9]* 2>/dev/null` \ + `ls -dr ${srcdir}/../tcl[8-9].[0-9] 2>/dev/null` \ + `ls -dr ${srcdir}/../tcl[8-9].[0-9]* 2>/dev/null` ; do + if test "${TEA_PLATFORM}" = "windows" \ + -a -f "$i/win/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/win; pwd)`" + break + fi + if test -f "$i/unix/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/unix; pwd)`" + break + fi + done + fi + +fi + + + if test x"${ac_cv_c_tclconfig}" = x ; then + TCL_BIN_DIR="# no Tcl configs found" + as_fn_error $? "Can't find Tcl configuration definitions. Use --with-tcl to specify a directory containing tclConfig.sh" "$LINENO" 5 + else + no_tcl= + TCL_BIN_DIR="${ac_cv_c_tclconfig}" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: found ${TCL_BIN_DIR}/tclConfig.sh" >&5 +$as_echo "found ${TCL_BIN_DIR}/tclConfig.sh" >&6; } + fi + fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdio.h> +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdarg.h> +#include <stdio.h> +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for existence of ${TCL_BIN_DIR}/tclConfig.sh" >&5 +$as_echo_n "checking for existence of ${TCL_BIN_DIR}/tclConfig.sh... " >&6; } + + if test -f "${TCL_BIN_DIR}/tclConfig.sh" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: loading" >&5 +$as_echo "loading" >&6; } + . "${TCL_BIN_DIR}/tclConfig.sh" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: could not find ${TCL_BIN_DIR}/tclConfig.sh" >&5 +$as_echo "could not find ${TCL_BIN_DIR}/tclConfig.sh" >&6; } + fi + + # If the TCL_BIN_DIR is the build directory (not the install directory), + # then set the common variable name to the value of the build variables. + # For example, the variable TCL_LIB_SPEC will be set to the value + # of TCL_BUILD_LIB_SPEC. An extension should make use of TCL_LIB_SPEC + # instead of TCL_BUILD_LIB_SPEC since it will work with both an + # installed and uninstalled version of Tcl. + if test -f "${TCL_BIN_DIR}/Makefile" ; then + TCL_LIB_SPEC="${TCL_BUILD_LIB_SPEC}" + TCL_STUB_LIB_SPEC="${TCL_BUILD_STUB_LIB_SPEC}" + TCL_STUB_LIB_PATH="${TCL_BUILD_STUB_LIB_PATH}" + elif test "`uname -s`" = "Darwin"; then + # If Tcl was built as a framework, attempt to use the libraries + # from the framework at the given location so that linking works + # against Tcl.framework installed in an arbitrary location. + case ${TCL_DEFS} in + *TCL_FRAMEWORK*) + if test -f "${TCL_BIN_DIR}/${TCL_LIB_FILE}"; then + for i in "`cd "${TCL_BIN_DIR}"; pwd`" \ + "`cd "${TCL_BIN_DIR}"/../..; pwd`"; do + if test "`basename "$i"`" = "${TCL_LIB_FILE}.framework"; then + TCL_LIB_SPEC="-F`dirname "$i" | sed -e 's/ /\\\\ /g'` -framework ${TCL_LIB_FILE}" + break + fi + done + fi + if test -f "${TCL_BIN_DIR}/${TCL_STUB_LIB_FILE}"; then + TCL_STUB_LIB_SPEC="-L`echo "${TCL_BIN_DIR}" | sed -e 's/ /\\\\ /g'` ${TCL_STUB_LIB_FLAG}" + TCL_STUB_LIB_PATH="${TCL_BIN_DIR}/${TCL_STUB_LIB_FILE}" + fi + ;; + esac + fi + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking platform" >&5 +$as_echo_n "checking platform... " >&6; } + hold_cc=$CC; CC="$TCL_CC" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + #ifdef _WIN32 + #error win32 + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + # first test we've already retrieved platform (cross-compile), fallback to unix otherwise: + TEA_PLATFORM="${TEA_PLATFORM-unix}" + CYGPATH=echo + +else + + TEA_PLATFORM="windows" + # Extract the first word of "cygpath", so it can be a program name with args. +set dummy cygpath; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CYGPATH+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CYGPATH"; then + ac_cv_prog_CYGPATH="$CYGPATH" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CYGPATH="cygpath -m" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_CYGPATH" && ac_cv_prog_CYGPATH="echo" +fi +fi +CYGPATH=$ac_cv_prog_CYGPATH +if test -n "$CYGPATH"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CYGPATH" >&5 +$as_echo "$CYGPATH" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CC=$hold_cc + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TEA_PLATFORM" >&5 +$as_echo "$TEA_PLATFORM" >&6; } + + # The BUILD_$pkg is to define the correct extern storage class + # handling when making this package + +cat >>confdefs.h <<_ACEOF +#define BUILD_${PACKAGE_NAME} /**/ +_ACEOF + + # Do this here as we have fully defined TEA_PLATFORM now + if test "${TEA_PLATFORM}" = "windows" ; then + EXEEXT=".exe" + CLEANFILES="$CLEANFILES *.lib *.dll *.pdb *.exp" + fi + + # TEA specific: + + + + + + + + +#-------------------------------------------------------------------- +# Load the tkConfig.sh file if necessary (Tk extension) +#-------------------------------------------------------------------- + +#TEA_PATH_TKCONFIG +#TEA_LOAD_TKCONFIG + +#----------------------------------------------------------------------- +# Handle the --prefix=... option by defaulting to what Tcl gave. +# Must be called after TEA_LOAD_TCLCONFIG and before TEA_SETUP_COMPILER. +#----------------------------------------------------------------------- + + + if test "${prefix}" = "NONE"; then + prefix_default=yes + if test x"${TCL_PREFIX}" != x; then + { $as_echo "$as_me:${as_lineno-$LINENO}: --prefix defaulting to TCL_PREFIX ${TCL_PREFIX}" >&5 +$as_echo "$as_me: --prefix defaulting to TCL_PREFIX ${TCL_PREFIX}" >&6;} + prefix=${TCL_PREFIX} + else + { $as_echo "$as_me:${as_lineno-$LINENO}: --prefix defaulting to /usr/local" >&5 +$as_echo "$as_me: --prefix defaulting to /usr/local" >&6;} + prefix=/usr/local + fi + fi + if test "${exec_prefix}" = "NONE" -a x"${prefix_default}" = x"yes" \ + -o x"${exec_prefix_default}" = x"yes" ; then + if test x"${TCL_EXEC_PREFIX}" != x; then + { $as_echo "$as_me:${as_lineno-$LINENO}: --exec-prefix defaulting to TCL_EXEC_PREFIX ${TCL_EXEC_PREFIX}" >&5 +$as_echo "$as_me: --exec-prefix defaulting to TCL_EXEC_PREFIX ${TCL_EXEC_PREFIX}" >&6;} + exec_prefix=${TCL_EXEC_PREFIX} + else + { $as_echo "$as_me:${as_lineno-$LINENO}: --exec-prefix defaulting to ${prefix}" >&5 +$as_echo "$as_me: --exec-prefix defaulting to ${prefix}" >&6;} + exec_prefix=$prefix + fi + fi + + +#----------------------------------------------------------------------- +# Standard compiler checks. +# This sets up CC by using the CC env var, or looks for gcc otherwise. +# This also calls AC_PROG_CC and a few others to create the basic setup +# necessary to compile executables. +#----------------------------------------------------------------------- + + + # Don't put any macros that use the compiler (e.g. AC_TRY_COMPILE) + # in this macro, they need to go into TEA_SETUP_COMPILER instead. + + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdarg.h> +#include <stdio.h> +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since + # <limits.h> exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include <limits.h> +#else +# include <assert.h> +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <ac_nonexistent.h> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since + # <limits.h> exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include <limits.h> +#else +# include <assert.h> +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <ac_nonexistent.h> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + #-------------------------------------------------------------------- + # Checks to see if the make program sets the $MAKE variable. + #-------------------------------------------------------------------- + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + + + #-------------------------------------------------------------------- + # Find ranlib + #-------------------------------------------------------------------- + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + + + #-------------------------------------------------------------------- + # Determines the correct binary file extension (.o, .obj, .exe etc.) + #-------------------------------------------------------------------- + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <float.h> + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <string.h> + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdlib.h> + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <ctype.h> +#include <stdlib.h> +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + + + # Any macros that use the compiler (e.g. AC_TRY_COMPILE) have to go here. + + + #------------------------------------------------------------------------ + # If we're using GCC, see if the compiler understands -pipe. If so, use it. + # It makes compiling go faster. (This is only a performance feature.) + #------------------------------------------------------------------------ + + if test -z "$no_pipe" -a -n "$GCC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if the compiler understands -pipe" >&5 +$as_echo_n "checking if the compiler understands -pipe... " >&6; } +if ${tcl_cv_cc_pipe+:} false; then : + $as_echo_n "(cached) " >&6 +else + + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -pipe" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_cc_pipe=yes +else + tcl_cv_cc_pipe=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CFLAGS=$hold_cflags +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_pipe" >&5 +$as_echo "$tcl_cv_cc_pipe" >&6; } + if test $tcl_cv_cc_pipe = yes; then + CFLAGS="$CFLAGS -pipe" + fi + fi + + #-------------------------------------------------------------------- + # Common compiler flag setup + #-------------------------------------------------------------------- + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5 +$as_echo_n "checking whether byte ordering is bigendian... " >&6; } +if ${ac_cv_c_bigendian+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_c_bigendian=unknown + # See if we're dealing with a universal compiler. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifndef __APPLE_CC__ + not a universal capable compiler + #endif + typedef int dummy; + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + # Check for potential -arch flags. It is not universal unless + # there are at least two -arch flags with different values. + ac_arch= + ac_prev= + for ac_word in $CC $CFLAGS $CPPFLAGS $LDFLAGS; do + if test -n "$ac_prev"; then + case $ac_word in + i?86 | x86_64 | ppc | ppc64) + if test -z "$ac_arch" || test "$ac_arch" = "$ac_word"; then + ac_arch=$ac_word + else + ac_cv_c_bigendian=universal + break + fi + ;; + esac + ac_prev= + elif test "x$ac_word" = "x-arch"; then + ac_prev=arch + fi + done +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + if test $ac_cv_c_bigendian = unknown; then + # See if sys/param.h defines the BYTE_ORDER macro. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/types.h> + #include <sys/param.h> + +int +main () +{ +#if ! (defined BYTE_ORDER && defined BIG_ENDIAN \ + && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \ + && LITTLE_ENDIAN) + bogus endian macros + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + # It does; now see whether it defined to BIG_ENDIAN or not. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/types.h> + #include <sys/param.h> + +int +main () +{ +#if BYTE_ORDER != BIG_ENDIAN + not big endian + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_c_bigendian=yes +else + ac_cv_c_bigendian=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + fi + if test $ac_cv_c_bigendian = unknown; then + # See if <limits.h> defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris). + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <limits.h> + +int +main () +{ +#if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN) + bogus endian macros + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + # It does; now see whether it defined to _BIG_ENDIAN or not. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <limits.h> + +int +main () +{ +#ifndef _BIG_ENDIAN + not big endian + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_c_bigendian=yes +else + ac_cv_c_bigendian=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + fi + if test $ac_cv_c_bigendian = unknown; then + # Compile a test program. + if test "$cross_compiling" = yes; then : + # Try to guess by grepping values from an object file. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +short int ascii_mm[] = + { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 }; + short int ascii_ii[] = + { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 }; + int use_ascii (int i) { + return ascii_mm[i] + ascii_ii[i]; + } + short int ebcdic_ii[] = + { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 }; + short int ebcdic_mm[] = + { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 }; + int use_ebcdic (int i) { + return ebcdic_mm[i] + ebcdic_ii[i]; + } + extern int foo; + +int +main () +{ +return use_ascii (foo) == use_ebcdic (foo); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + if grep BIGenDianSyS conftest.$ac_objext >/dev/null; then + ac_cv_c_bigendian=yes + fi + if grep LiTTleEnDian conftest.$ac_objext >/dev/null ; then + if test "$ac_cv_c_bigendian" = unknown; then + ac_cv_c_bigendian=no + else + # finding both strings is unlikely to happen, but who knows? + ac_cv_c_bigendian=unknown + fi + fi +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_includes_default +int +main () +{ + + /* Are we little or big endian? From Harbison&Steele. */ + union + { + long int l; + char c[sizeof (long int)]; + } u; + u.l = 1; + return u.c[sizeof (long int) - 1] == 1; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + ac_cv_c_bigendian=no +else + ac_cv_c_bigendian=yes +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5 +$as_echo "$ac_cv_c_bigendian" >&6; } + case $ac_cv_c_bigendian in #( + yes) + $as_echo "#define WORDS_BIGENDIAN 1" >>confdefs.h +;; #( + no) + ;; #( + universal) + +$as_echo "#define AC_APPLE_UNIVERSAL_BUILD 1" >>confdefs.h + + ;; #( + *) + as_fn_error $? "unknown endianness + presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5 ;; + esac + + + +#----------------------------------------------------------------------- +# __CHANGE__ +# Specify the C source files to compile in TEA_ADD_SOURCES, +# public headers that need to be installed in TEA_ADD_HEADERS, +# stub library C source files to compile in TEA_ADD_STUB_SOURCES, +# and runtime Tcl library files in TEA_ADD_TCL_SOURCES. +# This defines PKG(_STUB)_SOURCES, PKG(_STUB)_OBJECTS, PKG_HEADERS +# and PKG_TCL_SOURCES. +#----------------------------------------------------------------------- + + + vars="tclsqlite3.c" + for i in $vars; do + case $i in + \$*) + # allow $-var names + PKG_SOURCES="$PKG_SOURCES $i" + PKG_OBJECTS="$PKG_OBJECTS $i" + ;; + *) + # check for existence - allows for generic/win/unix VPATH + # To add more dirs here (like 'src'), you have to update VPATH + # in Makefile.in as well + if test ! -f "${srcdir}/$i" -a ! -f "${srcdir}/generic/$i" \ + -a ! -f "${srcdir}/win/$i" -a ! -f "${srcdir}/unix/$i" \ + -a ! -f "${srcdir}/macosx/$i" \ + ; then + as_fn_error $? "could not find source file '$i'" "$LINENO" 5 + fi + PKG_SOURCES="$PKG_SOURCES $i" + # this assumes it is in a VPATH dir + i=`basename $i` + # handle user calling this before or after TEA_SETUP_COMPILER + if test x"${OBJEXT}" != x ; then + j="`echo $i | sed -e 's/\.[^.]*$//'`.${OBJEXT}" + else + j="`echo $i | sed -e 's/\.[^.]*$//'`.\${OBJEXT}" + fi + PKG_OBJECTS="$PKG_OBJECTS $j" + ;; + esac + done + + + + + vars="" + for i in $vars; do + # check for existence, be strict because it is installed + if test ! -f "${srcdir}/$i" ; then + as_fn_error $? "could not find header file '${srcdir}/$i'" "$LINENO" 5 + fi + PKG_HEADERS="$PKG_HEADERS $i" + done + + + + vars="" + for i in $vars; do + PKG_INCLUDES="$PKG_INCLUDES $i" + done + + + + vars="" + for i in $vars; do + if test "${TEA_PLATFORM}" = "windows" -a "$GCC" = "yes" ; then + # Convert foo.lib to -lfoo for GCC. No-op if not *.lib + i=`echo "$i" | sed -e 's/^\([^-].*\)\.[lL][iI][bB]$/-l\1/'` + fi + PKG_LIBS="$PKG_LIBS $i" + done + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_ENABLE_FTS3=1" + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_ENABLE_FTS4=1" + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_ENABLE_FTS5=1" + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_3_SUFFIX_ONLY=1" + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_ENABLE_RTREE=1" + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_ENABLE_GEOPOLY=1" + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_ENABLE_MATH_FUNCTIONS=1" + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_ENABLE_DESERIALIZE=1" + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_ENABLE_DBPAGE_VTAB=1" + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_ENABLE_BYTECODE_VTAB=1" + + + + PKG_CFLAGS="$PKG_CFLAGS -DSQLITE_ENABLE_DBSTAT_VTAB=1" + + + + vars="" + for i in $vars; do + # check for existence - allows for generic/win/unix VPATH + if test ! -f "${srcdir}/$i" -a ! -f "${srcdir}/generic/$i" \ + -a ! -f "${srcdir}/win/$i" -a ! -f "${srcdir}/unix/$i" \ + -a ! -f "${srcdir}/macosx/$i" \ + ; then + as_fn_error $? "could not find stub source file '$i'" "$LINENO" 5 + fi + PKG_STUB_SOURCES="$PKG_STUB_SOURCES $i" + # this assumes it is in a VPATH dir + i=`basename $i` + # handle user calling this before or after TEA_SETUP_COMPILER + if test x"${OBJEXT}" != x ; then + j="`echo $i | sed -e 's/\.[^.]*$//'`.${OBJEXT}" + else + j="`echo $i | sed -e 's/\.[^.]*$//'`.\${OBJEXT}" + fi + PKG_STUB_OBJECTS="$PKG_STUB_OBJECTS $j" + done + + + + + vars="" + for i in $vars; do + # check for existence, be strict because it is installed + if test ! -f "${srcdir}/$i" ; then + as_fn_error $? "could not find tcl source file '${srcdir}/$i'" "$LINENO" 5 + fi + PKG_TCL_SOURCES="$PKG_TCL_SOURCES $i" + done + + + +#-------------------------------------------------------------------- +# The --with-system-sqlite causes the TCL bindings to SQLite to use +# the system shared library for SQLite rather than statically linking +# against its own private copy. This is dangerous and leads to +# undersirable dependences and is not recommended. +# Patchs from rmax. +#-------------------------------------------------------------------- + +# Check whether --with-system-sqlite was given. +if test "${with_system_sqlite+set}" = set; then : + withval=$with_system_sqlite; +else + with_system_sqlite=no +fi + +if test x$with_system_sqlite != xno; then + ac_fn_c_check_header_mongrel "$LINENO" "sqlite3.h" "ac_cv_header_sqlite3_h" "$ac_includes_default" +if test "x$ac_cv_header_sqlite3_h" = xyes; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_initialize in -lsqlite3" >&5 +$as_echo_n "checking for sqlite3_initialize in -lsqlite3... " >&6; } +if ${ac_cv_lib_sqlite3_sqlite3_initialize+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsqlite3 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char sqlite3_initialize (); +int +main () +{ +return sqlite3_initialize (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_sqlite3_sqlite3_initialize=yes +else + ac_cv_lib_sqlite3_sqlite3_initialize=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sqlite3_sqlite3_initialize" >&5 +$as_echo "$ac_cv_lib_sqlite3_sqlite3_initialize" >&6; } +if test "x$ac_cv_lib_sqlite3_sqlite3_initialize" = xyes; then : + $as_echo "#define USE_SYSTEM_SQLITE 1" >>confdefs.h + + LIBS="$LIBS -lsqlite3" +fi + +fi + + +fi + +#-------------------------------------------------------------------- +# __CHANGE__ +# +# You can add more files to clean if your extension creates any extra +# files by extending CLEANFILES. +# Add pkgIndex.tcl if it is generated in the Makefile instead of ./configure +# and change Makefile.in to move it from CONFIG_CLEAN_FILES to BINARIES var. +# +# A few miscellaneous platform-specific items: +# TEA_ADD_* any platform specific compiler/build info here. +#-------------------------------------------------------------------- + +#CLEANFILES="$CLEANFILES pkgIndex.tcl" +if test "${TEA_PLATFORM}" = "windows" ; then + # Ensure no empty if clauses + : + #TEA_ADD_SOURCES([win/winFile.c]) + #TEA_ADD_INCLUDES([-I\"$(${CYGPATH} ${srcdir}/win)\"]) +else + # Ensure no empty else clauses + : + #TEA_ADD_SOURCES([unix/unixFile.c]) + #TEA_ADD_LIBS([-lsuperfly]) +fi + +#-------------------------------------------------------------------- +# __CHANGE__ +# Choose which headers you need. Extension authors should try very +# hard to only rely on the Tcl public header files. Internal headers +# contain private data structures and are subject to change without +# notice. +# This MUST be called after TEA_LOAD_TCLCONFIG / TEA_LOAD_TKCONFIG +#-------------------------------------------------------------------- + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Tcl public headers" >&5 +$as_echo_n "checking for Tcl public headers... " >&6; } + + +# Check whether --with-tclinclude was given. +if test "${with_tclinclude+set}" = set; then : + withval=$with_tclinclude; with_tclinclude=${withval} +fi + + + if ${ac_cv_c_tclh+:} false; then : + $as_echo_n "(cached) " >&6 +else + + # Use the value from --with-tclinclude, if it was given + + if test x"${with_tclinclude}" != x ; then + if test -f "${with_tclinclude}/tcl.h" ; then + ac_cv_c_tclh=${with_tclinclude} + else + as_fn_error $? "${with_tclinclude} directory does not contain tcl.h" "$LINENO" 5 + fi + else + list="" + if test "`uname -s`" = "Darwin"; then + # If Tcl was built as a framework, attempt to use + # the framework's Headers directory + case ${TCL_DEFS} in + *TCL_FRAMEWORK*) + list="`ls -d ${TCL_BIN_DIR}/Headers 2>/dev/null`" + ;; + esac + fi + + # Look in the source dir only if Tcl is not installed, + # and in that situation, look there before installed locations. + if test -f "${TCL_BIN_DIR}/Makefile" ; then + list="$list `ls -d ${TCL_SRC_DIR}/generic 2>/dev/null`" + fi + + # Check order: pkg --prefix location, Tcl's --prefix location, + # relative to directory of tclConfig.sh. + + eval "temp_includedir=${includedir}" + list="$list \ + `ls -d ${temp_includedir} 2>/dev/null` \ + `ls -d ${TCL_PREFIX}/include 2>/dev/null` \ + `ls -d ${TCL_BIN_DIR}/../include 2>/dev/null`" + if test "${TEA_PLATFORM}" != "windows" -o "$GCC" = "yes"; then + list="$list /usr/local/include /usr/include" + if test x"${TCL_INCLUDE_SPEC}" != x ; then + d=`echo "${TCL_INCLUDE_SPEC}" | sed -e 's/^-I//'` + list="$list `ls -d ${d} 2>/dev/null`" + fi + fi + for i in $list ; do + if test -f "$i/tcl.h" ; then + ac_cv_c_tclh=$i + break + fi + done + fi + +fi + + + # Print a message based on how we determined the include path + + if test x"${ac_cv_c_tclh}" = x ; then + as_fn_error $? "tcl.h not found. Please specify its location with --with-tclinclude" "$LINENO" 5 + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${ac_cv_c_tclh}" >&5 +$as_echo "${ac_cv_c_tclh}" >&6; } + fi + + # Convert to a native path and substitute into the output files. + + INCLUDE_DIR_NATIVE=`${CYGPATH} ${ac_cv_c_tclh}` + + TCL_INCLUDES=-I\"${INCLUDE_DIR_NATIVE}\" + + + +#TEA_PRIVATE_TCL_HEADERS + +#TEA_PUBLIC_TK_HEADERS +#TEA_PRIVATE_TK_HEADERS +#TEA_PATH_X + +#-------------------------------------------------------------------- +# Check whether --enable-threads or --disable-threads was given. +# This auto-enables if Tcl was compiled threaded. +#-------------------------------------------------------------------- + + + # Check whether --enable-threads was given. +if test "${enable_threads+set}" = set; then : + enableval=$enable_threads; tcl_ok=$enableval +else + tcl_ok=yes +fi + + + if test "${enable_threads+set}" = set; then + enableval="$enable_threads" + tcl_ok=$enableval + else + tcl_ok=yes + fi + + if test "$tcl_ok" = "yes" -o "${TCL_THREADS}" = 1; then + TCL_THREADS=1 + + if test "${TEA_PLATFORM}" != "windows" ; then + # We are always OK on Windows, so check what this platform wants: + + # USE_THREAD_ALLOC tells us to try the special thread-based + # allocator that significantly reduces lock contention + +$as_echo "#define USE_THREAD_ALLOC 1" >>confdefs.h + + +$as_echo "#define _REENTRANT 1" >>confdefs.h + + if test "`uname -s`" = "SunOS" ; then + +$as_echo "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h + + fi + +$as_echo "#define _THREAD_SAFE 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lpthread" >&5 +$as_echo_n "checking for pthread_mutex_init in -lpthread... " >&6; } +if ${ac_cv_lib_pthread_pthread_mutex_init+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_mutex_init (); +int +main () +{ +return pthread_mutex_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread_pthread_mutex_init=yes +else + ac_cv_lib_pthread_pthread_mutex_init=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_mutex_init" >&5 +$as_echo "$ac_cv_lib_pthread_pthread_mutex_init" >&6; } +if test "x$ac_cv_lib_pthread_pthread_mutex_init" = xyes; then : + tcl_ok=yes +else + tcl_ok=no +fi + + if test "$tcl_ok" = "no"; then + # Check a little harder for __pthread_mutex_init in the same + # library, as some systems hide it there until pthread.h is + # defined. We could alternatively do an AC_TRY_COMPILE with + # pthread.h, but that will work with libpthread really doesn't + # exist, like AIX 4.2. [Bug: 4359] + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __pthread_mutex_init in -lpthread" >&5 +$as_echo_n "checking for __pthread_mutex_init in -lpthread... " >&6; } +if ${ac_cv_lib_pthread___pthread_mutex_init+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char __pthread_mutex_init (); +int +main () +{ +return __pthread_mutex_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread___pthread_mutex_init=yes +else + ac_cv_lib_pthread___pthread_mutex_init=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread___pthread_mutex_init" >&5 +$as_echo "$ac_cv_lib_pthread___pthread_mutex_init" >&6; } +if test "x$ac_cv_lib_pthread___pthread_mutex_init" = xyes; then : + tcl_ok=yes +else + tcl_ok=no +fi + + fi + + if test "$tcl_ok" = "yes"; then + # The space is needed + THREADS_LIBS=" -lpthread" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lpthreads" >&5 +$as_echo_n "checking for pthread_mutex_init in -lpthreads... " >&6; } +if ${ac_cv_lib_pthreads_pthread_mutex_init+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthreads $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_mutex_init (); +int +main () +{ +return pthread_mutex_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthreads_pthread_mutex_init=yes +else + ac_cv_lib_pthreads_pthread_mutex_init=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthreads_pthread_mutex_init" >&5 +$as_echo "$ac_cv_lib_pthreads_pthread_mutex_init" >&6; } +if test "x$ac_cv_lib_pthreads_pthread_mutex_init" = xyes; then : + tcl_ok=yes +else + tcl_ok=no +fi + + if test "$tcl_ok" = "yes"; then + # The space is needed + THREADS_LIBS=" -lpthreads" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lc" >&5 +$as_echo_n "checking for pthread_mutex_init in -lc... " >&6; } +if ${ac_cv_lib_c_pthread_mutex_init+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lc $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_mutex_init (); +int +main () +{ +return pthread_mutex_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_c_pthread_mutex_init=yes +else + ac_cv_lib_c_pthread_mutex_init=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_pthread_mutex_init" >&5 +$as_echo "$ac_cv_lib_c_pthread_mutex_init" >&6; } +if test "x$ac_cv_lib_c_pthread_mutex_init" = xyes; then : + tcl_ok=yes +else + tcl_ok=no +fi + + if test "$tcl_ok" = "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lc_r" >&5 +$as_echo_n "checking for pthread_mutex_init in -lc_r... " >&6; } +if ${ac_cv_lib_c_r_pthread_mutex_init+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lc_r $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_mutex_init (); +int +main () +{ +return pthread_mutex_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_c_r_pthread_mutex_init=yes +else + ac_cv_lib_c_r_pthread_mutex_init=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_r_pthread_mutex_init" >&5 +$as_echo "$ac_cv_lib_c_r_pthread_mutex_init" >&6; } +if test "x$ac_cv_lib_c_r_pthread_mutex_init" = xyes; then : + tcl_ok=yes +else + tcl_ok=no +fi + + if test "$tcl_ok" = "yes"; then + # The space is needed + THREADS_LIBS=" -pthread" + else + TCL_THREADS=0 + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Do not know how to find pthread lib on your system - thread support disabled" >&5 +$as_echo "$as_me: WARNING: Do not know how to find pthread lib on your system - thread support disabled" >&2;} + fi + fi + fi + fi + fi + else + TCL_THREADS=0 + fi + # Do checking message here to not mess up interleaved configure output + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for building with threads" >&5 +$as_echo_n "checking for building with threads... " >&6; } + if test "${TCL_THREADS}" = 1; then + +$as_echo "#define TCL_THREADS 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes (default)" >&5 +$as_echo "yes (default)" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + # TCL_THREADS sanity checking. See if our request for building with + # threads is the same as the way Tcl was built. If not, warn the user. + case ${TCL_DEFS} in + *THREADS=1*) + if test "${TCL_THREADS}" = "0"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: + Building ${PACKAGE_NAME} without threads enabled, but building against Tcl + that IS thread-enabled. It is recommended to use --enable-threads." >&5 +$as_echo "$as_me: WARNING: + Building ${PACKAGE_NAME} without threads enabled, but building against Tcl + that IS thread-enabled. It is recommended to use --enable-threads." >&2;} + fi + ;; + esac + + +if test "${TCL_THREADS}" = "1" ; then + +$as_echo "#define SQLITE_THREADSAFE 1" >>confdefs.h + + # Not automatically added by Tcl because its assumed Tcl links to them, + # but it may not if it isn't really a threaded build. + + vars="$THREADS_LIBS" + for i in $vars; do + if test "${TEA_PLATFORM}" = "windows" -a "$GCC" = "yes" ; then + # Convert foo.lib to -lfoo for GCC. No-op if not *.lib + i=`echo "$i" | sed -e 's/^\([^-].*\)\.[lL][iI][bB]$/-l\1/'` + fi + PKG_LIBS="$PKG_LIBS $i" + done + + +else + +$as_echo "#define SQLITE_THREADSAFE 0" >>confdefs.h + +fi + +#-------------------------------------------------------------------- +# The statement below defines a collection of symbols related to +# building as a shared library instead of a static library. +#-------------------------------------------------------------------- + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to build libraries" >&5 +$as_echo_n "checking how to build libraries... " >&6; } + # Check whether --enable-shared was given. +if test "${enable_shared+set}" = set; then : + enableval=$enable_shared; shared_ok=$enableval +else + shared_ok=yes +fi + + + if test "${enable_shared+set}" = set; then + enableval="$enable_shared" + shared_ok=$enableval + else + shared_ok=yes + fi + + # Check whether --enable-stubs was given. +if test "${enable_stubs+set}" = set; then : + enableval=$enable_stubs; stubs_ok=$enableval +else + stubs_ok=yes +fi + + + if test "${enable_stubs+set}" = set; then + enableval="$enable_stubs" + stubs_ok=$enableval + else + stubs_ok=yes + fi + + # Stubs are always enabled for shared builds + if test "$shared_ok" = "yes" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: shared" >&5 +$as_echo "shared" >&6; } + SHARED_BUILD=1 + STUBS_BUILD=1 + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: static" >&5 +$as_echo "static" >&6; } + SHARED_BUILD=0 + +$as_echo "#define STATIC_BUILD 1" >>confdefs.h + + if test "$stubs_ok" = "yes" ; then + STUBS_BUILD=1 + else + STUBS_BUILD=0 + fi + fi + if test "${STUBS_BUILD}" = "1" ; then + +$as_echo "#define USE_TCL_STUBS 1" >>confdefs.h + + +$as_echo "#define USE_TCLOO_STUBS 1" >>confdefs.h + + if test "${TEA_WINDOWINGSYSTEM}" != ""; then + +$as_echo "#define USE_TK_STUBS 1" >>confdefs.h + + fi + fi + + + + + +#-------------------------------------------------------------------- +# This macro figures out what flags to use with the compiler/linker +# when building shared/static debug/optimized objects. This information +# can be taken from the tclConfig.sh file, but this figures it all out. +#-------------------------------------------------------------------- + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + + + + + # Step 0.a: Enable 64 bit support? + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if 64bit support is requested" >&5 +$as_echo_n "checking if 64bit support is requested... " >&6; } + # Check whether --enable-64bit was given. +if test "${enable_64bit+set}" = set; then : + enableval=$enable_64bit; do64bit=$enableval +else + do64bit=no +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $do64bit" >&5 +$as_echo "$do64bit" >&6; } + + # Step 0.b: Enable Solaris 64 bit VIS support? + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if 64bit Sparc VIS support is requested" >&5 +$as_echo_n "checking if 64bit Sparc VIS support is requested... " >&6; } + # Check whether --enable-64bit-vis was given. +if test "${enable_64bit_vis+set}" = set; then : + enableval=$enable_64bit_vis; do64bitVIS=$enableval +else + do64bitVIS=no +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $do64bitVIS" >&5 +$as_echo "$do64bitVIS" >&6; } + # Force 64bit on with VIS + if test "$do64bitVIS" = "yes"; then : + do64bit=yes +fi + + # Step 0.c: Check if visibility support is available. Do this here so + # that platform specific alternatives can be used below if this fails. + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler supports visibility \"hidden\"" >&5 +$as_echo_n "checking if compiler supports visibility \"hidden\"... " >&6; } +if ${tcl_cv_cc_visibility_hidden+:} false; then : + $as_echo_n "(cached) " >&6 +else + + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + extern __attribute__((__visibility__("hidden"))) void f(void); + void f(void) {} +int +main () +{ +f(); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + tcl_cv_cc_visibility_hidden=yes +else + tcl_cv_cc_visibility_hidden=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_visibility_hidden" >&5 +$as_echo "$tcl_cv_cc_visibility_hidden" >&6; } + if test $tcl_cv_cc_visibility_hidden = yes; then : + + +$as_echo "#define MODULE_SCOPE extern __attribute__((__visibility__(\"hidden\")))" >>confdefs.h + + +$as_echo "#define HAVE_HIDDEN 1" >>confdefs.h + + +fi + + # Step 0.d: Disable -rpath support? + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if rpath support is requested" >&5 +$as_echo_n "checking if rpath support is requested... " >&6; } + # Check whether --enable-rpath was given. +if test "${enable_rpath+set}" = set; then : + enableval=$enable_rpath; doRpath=$enableval +else + doRpath=yes +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $doRpath" >&5 +$as_echo "$doRpath" >&6; } + + # Set the variable "system" to hold the name and version number + # for the system. + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking system version" >&5 +$as_echo_n "checking system version... " >&6; } +if ${tcl_cv_sys_version+:} false; then : + $as_echo_n "(cached) " >&6 +else + + # TEA specific: + if test "${TEA_PLATFORM}" = "windows" ; then + tcl_cv_sys_version=windows + else + tcl_cv_sys_version=`uname -s`-`uname -r` + if test "$?" -ne 0 ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: can't find uname command" >&5 +$as_echo "$as_me: WARNING: can't find uname command" >&2;} + tcl_cv_sys_version=unknown + else + if test "`uname -s`" = "AIX" ; then + tcl_cv_sys_version=AIX-`uname -v`.`uname -r` + fi + if test "`uname -s`" = "NetBSD" -a -f /etc/debian_version ; then + tcl_cv_sys_version=NetBSD-Debian + fi + fi + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_sys_version" >&5 +$as_echo "$tcl_cv_sys_version" >&6; } + system=$tcl_cv_sys_version + + + # Require ranlib early so we can override it in special cases below. + + + + # Set configuration options based on system name and version. + # This is similar to Tcl's unix/tcl.m4 except that we've added a + # "windows" case and removed some core-only vars. + + do64bit_ok=no + # default to '{$LIBS}' and set to "" on per-platform necessary basis + SHLIB_LD_LIBS='${LIBS}' + # When ld needs options to work in 64-bit mode, put them in + # LDFLAGS_ARCH so they eventually end up in LDFLAGS even if [load] + # is disabled by the user. [Bug 1016796] + LDFLAGS_ARCH="" + UNSHARED_LIB_SUFFIX="" + # TEA specific: use PACKAGE_VERSION instead of VERSION + TCL_TRIM_DOTS='`echo ${PACKAGE_VERSION} | tr -d .`' + ECHO_VERSION='`echo ${PACKAGE_VERSION}`' + TCL_LIB_VERSIONS_OK=ok + CFLAGS_DEBUG=-g + if test "$GCC" = yes; then : + + CFLAGS_OPTIMIZE=-O2 + CFLAGS_WARNING="-Wall" + +else + + CFLAGS_OPTIMIZE=-O + CFLAGS_WARNING="" + +fi + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ar", so it can be a program name with args. +set dummy ${ac_tool_prefix}ar; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AR"; then + ac_cv_prog_AR="$AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AR="${ac_tool_prefix}ar" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AR=$ac_cv_prog_AR +if test -n "$AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 +$as_echo "$AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_AR"; then + ac_ct_AR=$AR + # Extract the first word of "ar", so it can be a program name with args. +set dummy ar; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_AR"; then + ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_AR="ar" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_AR=$ac_cv_prog_ac_ct_AR +if test -n "$ac_ct_AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 +$as_echo "$ac_ct_AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_AR" = x; then + AR="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + AR=$ac_ct_AR + fi +else + AR="$ac_cv_prog_AR" +fi + + STLIB_LD='${AR} cr' + LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH" + if test "x$SHLIB_VERSION" = x; then : + SHLIB_VERSION="" +else + SHLIB_VERSION=".$SHLIB_VERSION" +fi + case $system in + # TEA specific: + windows) + MACHINE="X86" + if test "$do64bit" != "no" ; then + case "$do64bit" in + amd64|x64|yes) + MACHINE="AMD64" ; # default to AMD64 64-bit build + ;; + arm64|aarch64) + MACHINE="ARM64" + ;; + ia64) + MACHINE="IA64" + ;; + esac + fi + + if test "$GCC" != "yes" ; then + if test "${SHARED_BUILD}" = "0" ; then + runtime=-MT + else + runtime=-MD + fi + case "x`echo \${VisualStudioVersion}`" in + x1[4-9]*) + lflags="${lflags} -nodefaultlib:libucrt.lib" + + vars="ucrt.lib" + for i in $vars; do + if test "${TEA_PLATFORM}" = "windows" -a "$GCC" = "yes" ; then + # Convert foo.lib to -lfoo for GCC. No-op if not *.lib + i=`echo "$i" | sed -e 's/^\([^-].*\)\.[lL][iI][bB]$/-l\1/'` + fi + PKG_LIBS="$PKG_LIBS $i" + done + + + ;; + *) + ;; + esac + + if test "$do64bit" != "no" ; then + CC="cl.exe" + RC="rc.exe" + lflags="${lflags} -nologo -MACHINE:${MACHINE} " + LINKBIN="link.exe" + CFLAGS_DEBUG="-nologo -Zi -Od -W3 ${runtime}d" + CFLAGS_OPTIMIZE="-nologo -O2 -W2 ${runtime}" + # Avoid 'unresolved external symbol __security_cookie' + # errors, c.f. http://support.microsoft.com/?id=894573 + + vars="bufferoverflowU.lib" + for i in $vars; do + if test "${TEA_PLATFORM}" = "windows" -a "$GCC" = "yes" ; then + # Convert foo.lib to -lfoo for GCC. No-op if not *.lib + i=`echo "$i" | sed -e 's/^\([^-].*\)\.[lL][iI][bB]$/-l\1/'` + fi + PKG_LIBS="$PKG_LIBS $i" + done + + + else + RC="rc" + lflags="${lflags} -nologo" + LINKBIN="link" + CFLAGS_DEBUG="-nologo -Z7 -Od -W3 -WX ${runtime}d" + CFLAGS_OPTIMIZE="-nologo -O2 -W2 ${runtime}" + fi + fi + + if test "$GCC" = "yes"; then + # mingw gcc mode + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}windres", so it can be a program name with args. +set dummy ${ac_tool_prefix}windres; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RC"; then + ac_cv_prog_RC="$RC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RC="${ac_tool_prefix}windres" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RC=$ac_cv_prog_RC +if test -n "$RC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RC" >&5 +$as_echo "$RC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RC"; then + ac_ct_RC=$RC + # Extract the first word of "windres", so it can be a program name with args. +set dummy windres; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RC"; then + ac_cv_prog_ac_ct_RC="$ac_ct_RC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RC="windres" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RC=$ac_cv_prog_ac_ct_RC +if test -n "$ac_ct_RC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RC" >&5 +$as_echo "$ac_ct_RC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RC" = x; then + RC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RC=$ac_ct_RC + fi +else + RC="$ac_cv_prog_RC" +fi + + CFLAGS_DEBUG="-g" + CFLAGS_OPTIMIZE="-O2 -fomit-frame-pointer" + SHLIB_LD='${CC} -shared' + UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.a' + LDFLAGS_CONSOLE="-wl,--subsystem,console ${lflags}" + LDFLAGS_WINDOW="-wl,--subsystem,windows ${lflags}" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for cross-compile version of gcc" >&5 +$as_echo_n "checking for cross-compile version of gcc... " >&6; } +if ${ac_cv_cross+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #ifdef _WIN32 + #error cross-compiler + #endif + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_cross=yes +else + ac_cv_cross=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cross" >&5 +$as_echo "$ac_cv_cross" >&6; } + if test "$ac_cv_cross" = "yes"; then + case "$do64bit" in + amd64|x64|yes) + CC="x86_64-w64-mingw32-${CC}" + LD="x86_64-w64-mingw32-ld" + AR="x86_64-w64-mingw32-ar" + RANLIB="x86_64-w64-mingw32-ranlib" + RC="x86_64-w64-mingw32-windres" + ;; + arm64|aarch64) + CC="aarch64-w64-mingw32-clang" + LD="aarch64-w64-mingw32-ld" + AR="aarch64-w64-mingw32-ar" + RANLIB="aarch64-w64-mingw32-ranlib" + RC="aarch64-w64-mingw32-windres" + ;; + *) + CC="i686-w64-mingw32-${CC}" + LD="i686-w64-mingw32-ld" + AR="i686-w64-mingw32-ar" + RANLIB="i686-w64-mingw32-ranlib" + RC="i686-w64-mingw32-windres" + ;; + esac + fi + + else + SHLIB_LD="${LINKBIN} -dll ${lflags}" + # link -lib only works when -lib is the first arg + STLIB_LD="${LINKBIN} -lib ${lflags}" + UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.lib' + PATHTYPE=-w + # For information on what debugtype is most useful, see: + # http://msdn.microsoft.com/library/en-us/dnvc60/html/gendepdebug.asp + # and also + # http://msdn2.microsoft.com/en-us/library/y0zzbyt4%28VS.80%29.aspx + # This essentially turns it all on. + LDFLAGS_DEBUG="-debug -debugtype:cv" + LDFLAGS_OPTIMIZE="-release" + LDFLAGS_CONSOLE="-link -subsystem:console ${lflags}" + LDFLAGS_WINDOW="-link -subsystem:windows ${lflags}" + fi + + SHLIB_SUFFIX=".dll" + SHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.dll' + + TCL_LIB_VERSIONS_OK=nodots + ;; + AIX-*) + if test "$GCC" != "yes"; then : + + # AIX requires the _r compiler when gcc isn't being used + case "${CC}" in + *_r|*_r\ *) + # ok ... + ;; + *) + # Make sure only first arg gets _r + CC=`echo "$CC" | sed -e 's/^\([^ ]*\)/\1_r/'` + ;; + esac + { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using $CC for compiling with threads" >&5 +$as_echo "Using $CC for compiling with threads" >&6; } + +fi + LIBS="$LIBS -lc" + SHLIB_CFLAGS="" + SHLIB_SUFFIX=".so" + + LD_LIBRARY_PATH_VAR="LIBPATH" + + # Check to enable 64-bit flags for compiler/linker + if test "$do64bit" = yes; then : + + if test "$GCC" = yes; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC on $system" >&5 +$as_echo "$as_me: WARNING: 64bit mode not supported with GCC on $system" >&2;} + +else + + do64bit_ok=yes + CFLAGS="$CFLAGS -q64" + LDFLAGS_ARCH="-q64" + RANLIB="${RANLIB} -X64" + AR="${AR} -X64" + SHLIB_LD_FLAGS="-b64" + +fi + +fi + + if test "`uname -m`" = ia64; then : + + # AIX-5 uses ELF style dynamic libraries on IA-64, but not PPC + SHLIB_LD="/usr/ccs/bin/ld -G -z text" + if test "$GCC" = yes; then : + + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' + +else + + CC_SEARCH_FLAGS='"-R${LIB_RUNTIME_DIR}"' + +fi + LD_SEARCH_FLAGS='-R "${LIB_RUNTIME_DIR}"' + +else + + if test "$GCC" = yes; then : + + SHLIB_LD='${CC} -shared -Wl,-bexpall' + +else + + SHLIB_LD="/bin/ld -bhalt:4 -bM:SRE -bexpall -H512 -T512 -bnoentry" + LDFLAGS="$LDFLAGS -brtl" + +fi + SHLIB_LD="${SHLIB_LD} ${SHLIB_LD_FLAGS}" + CC_SEARCH_FLAGS='"-L${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + +fi + ;; + BeOS*) + SHLIB_CFLAGS="-fPIC" + SHLIB_LD='${CC} -nostart' + SHLIB_SUFFIX=".so" + + #----------------------------------------------------------- + # Check for inet_ntoa in -lbind, for BeOS (which also needs + # -lsocket, even if the network functions are in -lnet which + # is always linked to, for compatibility. + #----------------------------------------------------------- + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inet_ntoa in -lbind" >&5 +$as_echo_n "checking for inet_ntoa in -lbind... " >&6; } +if ${ac_cv_lib_bind_inet_ntoa+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lbind $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char inet_ntoa (); +int +main () +{ +return inet_ntoa (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_bind_inet_ntoa=yes +else + ac_cv_lib_bind_inet_ntoa=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bind_inet_ntoa" >&5 +$as_echo "$ac_cv_lib_bind_inet_ntoa" >&6; } +if test "x$ac_cv_lib_bind_inet_ntoa" = xyes; then : + LIBS="$LIBS -lbind -lsocket" +fi + + ;; + BSD/OS-2.1*|BSD/OS-3*) + SHLIB_CFLAGS="" + SHLIB_LD="shlicc -r" + SHLIB_SUFFIX=".so" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + BSD/OS-4.*) + SHLIB_CFLAGS="-export-dynamic -fPIC" + SHLIB_LD='${CC} -shared' + SHLIB_SUFFIX=".so" + LDFLAGS="$LDFLAGS -export-dynamic" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + CYGWIN_*) + SHLIB_CFLAGS="" + SHLIB_LD='${CC} -shared' + SHLIB_SUFFIX=".dll" + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} -Wl,--out-implib,\$@.a" + EXEEXT=".exe" + do64bit_ok=yes + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + dgux*) + SHLIB_CFLAGS="-K PIC" + SHLIB_LD='${CC} -G' + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + Haiku*) + LDFLAGS="$LDFLAGS -Wl,--export-dynamic" + SHLIB_CFLAGS="-fPIC" + SHLIB_SUFFIX=".so" + SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS} -shared' + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inet_ntoa in -lnetwork" >&5 +$as_echo_n "checking for inet_ntoa in -lnetwork... " >&6; } +if ${ac_cv_lib_network_inet_ntoa+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lnetwork $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char inet_ntoa (); +int +main () +{ +return inet_ntoa (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_network_inet_ntoa=yes +else + ac_cv_lib_network_inet_ntoa=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_network_inet_ntoa" >&5 +$as_echo "$ac_cv_lib_network_inet_ntoa" >&6; } +if test "x$ac_cv_lib_network_inet_ntoa" = xyes; then : + LIBS="$LIBS -lnetwork" +fi + + ;; + HP-UX-*.11.*) + # Use updated header definitions where possible + +$as_echo "#define _XOPEN_SOURCE_EXTENDED 1" >>confdefs.h + + # TEA specific: Needed by Tcl, but not most extensions + #AC_DEFINE(_XOPEN_SOURCE, 1, [Do we want to use the XOPEN network library?]) + #LIBS="$LIBS -lxnet" # Use the XOPEN network library + + if test "`uname -m`" = ia64; then : + + SHLIB_SUFFIX=".so" + +else + + SHLIB_SUFFIX=".sl" + +fi + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +$as_echo_n "checking for shl_load in -ldld... " >&6; } +if ${ac_cv_lib_dld_shl_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_shl_load=yes +else + ac_cv_lib_dld_shl_load=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +$as_echo "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes; then : + tcl_ok=yes +else + tcl_ok=no +fi + + if test "$tcl_ok" = yes; then : + + SHLIB_CFLAGS="+z" + SHLIB_LD="ld -b" + LDFLAGS="$LDFLAGS -Wl,-E" + CC_SEARCH_FLAGS='"-Wl,+s,+b,${LIB_RUNTIME_DIR}:."' + LD_SEARCH_FLAGS='+s +b "${LIB_RUNTIME_DIR}:."' + LD_LIBRARY_PATH_VAR="SHLIB_PATH" + +fi + if test "$GCC" = yes; then : + + SHLIB_LD='${CC} -shared' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + +else + + CFLAGS="$CFLAGS -z" + +fi + + # Check to enable 64-bit flags for compiler/linker + if test "$do64bit" = "yes"; then : + + if test "$GCC" = yes; then : + + case `${CC} -dumpmachine` in + hppa64*) + # 64-bit gcc in use. Fix flags for GNU ld. + do64bit_ok=yes + SHLIB_LD='${CC} -shared' + if test $doRpath = yes; then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' +fi + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + ;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC on $system" >&5 +$as_echo "$as_me: WARNING: 64bit mode not supported with GCC on $system" >&2;} + ;; + esac + +else + + do64bit_ok=yes + CFLAGS="$CFLAGS +DD64" + LDFLAGS_ARCH="+DD64" + +fi + +fi ;; + HP-UX-*.08.*|HP-UX-*.09.*|HP-UX-*.10.*) + SHLIB_SUFFIX=".sl" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +$as_echo_n "checking for shl_load in -ldld... " >&6; } +if ${ac_cv_lib_dld_shl_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_shl_load=yes +else + ac_cv_lib_dld_shl_load=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +$as_echo "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes; then : + tcl_ok=yes +else + tcl_ok=no +fi + + if test "$tcl_ok" = yes; then : + + SHLIB_CFLAGS="+z" + SHLIB_LD="ld -b" + SHLIB_LD_LIBS="" + LDFLAGS="$LDFLAGS -Wl,-E" + CC_SEARCH_FLAGS='"-Wl,+s,+b,${LIB_RUNTIME_DIR}:."' + LD_SEARCH_FLAGS='+s +b "${LIB_RUNTIME_DIR}:."' + LD_LIBRARY_PATH_VAR="SHLIB_PATH" + +fi ;; + IRIX-5.*) + SHLIB_CFLAGS="" + SHLIB_LD="ld -shared -rdata_shared" + SHLIB_SUFFIX=".so" + case " $LIBOBJS " in + *" mkstemp.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS mkstemp.$ac_objext" + ;; +esac + + if test $doRpath = yes; then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath "${LIB_RUNTIME_DIR}"' +fi + ;; + IRIX-6.*) + SHLIB_CFLAGS="" + SHLIB_LD="ld -n32 -shared -rdata_shared" + SHLIB_SUFFIX=".so" + if test $doRpath = yes; then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath "${LIB_RUNTIME_DIR}"' +fi + if test "$GCC" = yes; then : + + CFLAGS="$CFLAGS -mabi=n32" + LDFLAGS="$LDFLAGS -mabi=n32" + +else + + case $system in + IRIX-6.3) + # Use to build 6.2 compatible binaries on 6.3. + CFLAGS="$CFLAGS -n32 -D_OLD_TERMIOS" + ;; + *) + CFLAGS="$CFLAGS -n32" + ;; + esac + LDFLAGS="$LDFLAGS -n32" + +fi + ;; + IRIX64-6.*) + SHLIB_CFLAGS="" + SHLIB_LD="ld -n32 -shared -rdata_shared" + SHLIB_SUFFIX=".so" + if test $doRpath = yes; then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath "${LIB_RUNTIME_DIR}"' +fi + + # Check to enable 64-bit flags for compiler/linker + + if test "$do64bit" = yes; then : + + if test "$GCC" = yes; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported by gcc" >&5 +$as_echo "$as_me: WARNING: 64bit mode not supported by gcc" >&2;} + +else + + do64bit_ok=yes + SHLIB_LD="ld -64 -shared -rdata_shared" + CFLAGS="$CFLAGS -64" + LDFLAGS_ARCH="-64" + +fi + +fi + ;; + Linux*|GNU*|NetBSD-Debian|DragonFly-*|FreeBSD-*) + SHLIB_CFLAGS="-fPIC" + SHLIB_SUFFIX=".so" + + # TEA specific: + CFLAGS_OPTIMIZE="-O2 -fomit-frame-pointer" + + # TEA specific: use LDFLAGS_DEFAULT instead of LDFLAGS + SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS_DEFAULT} -shared' + LDFLAGS="$LDFLAGS -Wl,--export-dynamic" + + case $system in + DragonFly-*|FreeBSD-*) + if test "${TCL_THREADS}" = "1"; then : + + # The -pthread needs to go in the LDFLAGS, not LIBS + LIBS=`echo $LIBS | sed s/-pthread//` + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LDFLAGS="$LDFLAGS $PTHREAD_LIBS" +fi + ;; + esac + + if test $doRpath = yes; then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' +fi + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + if test "`uname -m`" = "alpha"; then : + CFLAGS="$CFLAGS -mieee" +fi + if test $do64bit = yes; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -m64 flag" >&5 +$as_echo_n "checking if compiler accepts -m64 flag... " >&6; } +if ${tcl_cv_cc_m64+:} false; then : + $as_echo_n "(cached) " >&6 +else + + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -m64" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + tcl_cv_cc_m64=yes +else + tcl_cv_cc_m64=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_m64" >&5 +$as_echo "$tcl_cv_cc_m64" >&6; } + if test $tcl_cv_cc_m64 = yes; then : + + CFLAGS="$CFLAGS -m64" + do64bit_ok=yes + +fi + +fi + + # The combo of gcc + glibc has a bug related to inlining of + # functions like strtod(). The -fno-builtin flag should address + # this problem but it does not work. The -fno-inline flag is kind + # of overkill but it works. Disable inlining only when one of the + # files in compat/*.c is being linked in. + + if test x"${USE_COMPAT}" != x; then : + CFLAGS="$CFLAGS -fno-inline" +fi + ;; + Lynx*) + SHLIB_CFLAGS="-fPIC" + SHLIB_SUFFIX=".so" + CFLAGS_OPTIMIZE=-02 + SHLIB_LD='${CC} -shared' + LD_FLAGS="-Wl,--export-dynamic" + if test $doRpath = yes; then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' +fi + ;; + OpenBSD-*) + arch=`arch -s` + case "$arch" in + alpha|sparc64) + SHLIB_CFLAGS="-fPIC" + ;; + *) + SHLIB_CFLAGS="-fpic" + ;; + esac + SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' + SHLIB_SUFFIX=".so" + if test $doRpath = yes; then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' +fi + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + SHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.so${SHLIB_VERSION}' + LDFLAGS="$LDFLAGS -Wl,-export-dynamic" + CFLAGS_OPTIMIZE="-O2" + # On OpenBSD: Compile with -pthread + # Don't link with -lpthread + LIBS=`echo $LIBS | sed s/-lpthread//` + CFLAGS="$CFLAGS -pthread" + # OpenBSD doesn't do version numbers with dots. + UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.a' + TCL_LIB_VERSIONS_OK=nodots + ;; + NetBSD-*) + # NetBSD has ELF and can use 'cc -shared' to build shared libs + SHLIB_CFLAGS="-fPIC" + SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' + SHLIB_SUFFIX=".so" + LDFLAGS="$LDFLAGS -export-dynamic" + if test $doRpath = yes; then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' +fi + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + # The -pthread needs to go in the CFLAGS, not LIBS + LIBS=`echo $LIBS | sed s/-pthread//` + CFLAGS="$CFLAGS -pthread" + LDFLAGS="$LDFLAGS -pthread" + ;; + Darwin-*) + CFLAGS_OPTIMIZE="-Os" + SHLIB_CFLAGS="-fno-common" + # To avoid discrepancies between what headers configure sees during + # preprocessing tests and compiling tests, move any -isysroot and + # -mmacosx-version-min flags from CFLAGS to CPPFLAGS: + CPPFLAGS="${CPPFLAGS} `echo " ${CFLAGS}" | \ + awk 'BEGIN {FS=" +-";ORS=" "}; {for (i=2;i<=NF;i++) \ + if ($i~/^(isysroot|mmacosx-version-min)/) print "-"$i}'`" + CFLAGS="`echo " ${CFLAGS}" | \ + awk 'BEGIN {FS=" +-";ORS=" "}; {for (i=2;i<=NF;i++) \ + if (!($i~/^(isysroot|mmacosx-version-min)/)) print "-"$i}'`" + if test $do64bit = yes; then : + + case `arch` in + ppc) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -arch ppc64 flag" >&5 +$as_echo_n "checking if compiler accepts -arch ppc64 flag... " >&6; } +if ${tcl_cv_cc_arch_ppc64+:} false; then : + $as_echo_n "(cached) " >&6 +else + + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + tcl_cv_cc_arch_ppc64=yes +else + tcl_cv_cc_arch_ppc64=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_arch_ppc64" >&5 +$as_echo "$tcl_cv_cc_arch_ppc64" >&6; } + if test $tcl_cv_cc_arch_ppc64 = yes; then : + + CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" + do64bit_ok=yes + +fi;; + i386) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -arch x86_64 flag" >&5 +$as_echo_n "checking if compiler accepts -arch x86_64 flag... " >&6; } +if ${tcl_cv_cc_arch_x86_64+:} false; then : + $as_echo_n "(cached) " >&6 +else + + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -arch x86_64" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + tcl_cv_cc_arch_x86_64=yes +else + tcl_cv_cc_arch_x86_64=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_arch_x86_64" >&5 +$as_echo "$tcl_cv_cc_arch_x86_64" >&6; } + if test $tcl_cv_cc_arch_x86_64 = yes; then : + + CFLAGS="$CFLAGS -arch x86_64" + do64bit_ok=yes + +fi;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Don't know how enable 64-bit on architecture \`arch\`" >&5 +$as_echo "$as_me: WARNING: Don't know how enable 64-bit on architecture \`arch\`" >&2;};; + esac + +else + + # Check for combined 32-bit and 64-bit fat build + if echo "$CFLAGS " |grep -E -q -- '-arch (ppc64|x86_64) ' \ + && echo "$CFLAGS " |grep -E -q -- '-arch (ppc|i386) '; then : + + fat_32_64=yes +fi + +fi + # TEA specific: use LDFLAGS_DEFAULT instead of LDFLAGS + SHLIB_LD='${CC} -dynamiclib ${CFLAGS} ${LDFLAGS_DEFAULT}' + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if ld accepts -single_module flag" >&5 +$as_echo_n "checking if ld accepts -single_module flag... " >&6; } +if ${tcl_cv_ld_single_module+:} false; then : + $as_echo_n "(cached) " >&6 +else + + hold_ldflags=$LDFLAGS + LDFLAGS="$LDFLAGS -dynamiclib -Wl,-single_module" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +int i; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + tcl_cv_ld_single_module=yes +else + tcl_cv_ld_single_module=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$hold_ldflags +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_ld_single_module" >&5 +$as_echo "$tcl_cv_ld_single_module" >&6; } + if test $tcl_cv_ld_single_module = yes; then : + + SHLIB_LD="${SHLIB_LD} -Wl,-single_module" + +fi + # TEA specific: link shlib with current and compatibility version flags + vers=`echo ${PACKAGE_VERSION} | sed -e 's/^\([0-9]\{1,5\}\)\(\(\.[0-9]\{1,3\}\)\{0,2\}\).*$/\1\2/p' -e d` + SHLIB_LD="${SHLIB_LD} -current_version ${vers:-0} -compatibility_version ${vers:-0}" + SHLIB_SUFFIX=".dylib" + LDFLAGS="$LDFLAGS -headerpad_max_install_names" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if ld accepts -search_paths_first flag" >&5 +$as_echo_n "checking if ld accepts -search_paths_first flag... " >&6; } +if ${tcl_cv_ld_search_paths_first+:} false; then : + $as_echo_n "(cached) " >&6 +else + + hold_ldflags=$LDFLAGS + LDFLAGS="$LDFLAGS -Wl,-search_paths_first" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +int i; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + tcl_cv_ld_search_paths_first=yes +else + tcl_cv_ld_search_paths_first=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$hold_ldflags +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_ld_search_paths_first" >&5 +$as_echo "$tcl_cv_ld_search_paths_first" >&6; } + if test $tcl_cv_ld_search_paths_first = yes; then : + + LDFLAGS="$LDFLAGS -Wl,-search_paths_first" + +fi + if test "$tcl_cv_cc_visibility_hidden" != yes; then : + + +$as_echo "#define MODULE_SCOPE __private_extern__" >>confdefs.h + + tcl_cv_cc_visibility_hidden=yes + +fi + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + LD_LIBRARY_PATH_VAR="DYLD_LIBRARY_PATH" + # TEA specific: for combined 32 & 64 bit fat builds of Tk + # extensions, verify that 64-bit build is possible. + if test "$fat_32_64" = yes && test -n "${TK_BIN_DIR}"; then : + + if test "${TEA_WINDOWINGSYSTEM}" = x11; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for 64-bit X11" >&5 +$as_echo_n "checking for 64-bit X11... " >&6; } +if ${tcl_cv_lib_x11_64+:} false; then : + $as_echo_n "(cached) " >&6 +else + + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval 'hold_'$v'="$'$v'";'$v'="`echo "$'$v' "|sed -e "s/-arch ppc / /g" -e "s/-arch i386 / /g"`"' + done + CPPFLAGS="$CPPFLAGS -I/usr/X11R6/include" + LDFLAGS="$LDFLAGS -L/usr/X11R6/lib -lX11" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <X11/Xlib.h> +int +main () +{ +XrmInitialize(); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + tcl_cv_lib_x11_64=yes +else + tcl_cv_lib_x11_64=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval $v'="$hold_'$v'"' + done +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_lib_x11_64" >&5 +$as_echo "$tcl_cv_lib_x11_64" >&6; } + +fi + if test "${TEA_WINDOWINGSYSTEM}" = aqua; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for 64-bit Tk" >&5 +$as_echo_n "checking for 64-bit Tk... " >&6; } +if ${tcl_cv_lib_tk_64+:} false; then : + $as_echo_n "(cached) " >&6 +else + + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval 'hold_'$v'="$'$v'";'$v'="`echo "$'$v' "|sed -e "s/-arch ppc / /g" -e "s/-arch i386 / /g"`"' + done + CPPFLAGS="$CPPFLAGS -DUSE_TCL_STUBS=1 -DUSE_TK_STUBS=1 ${TCL_INCLUDES} ${TK_INCLUDES}" + LDFLAGS="$LDFLAGS ${TCL_STUB_LIB_SPEC} ${TK_STUB_LIB_SPEC}" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <tk.h> +int +main () +{ +Tk_InitStubs(NULL, "", 0); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + tcl_cv_lib_tk_64=yes +else + tcl_cv_lib_tk_64=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval $v'="$hold_'$v'"' + done +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_lib_tk_64" >&5 +$as_echo "$tcl_cv_lib_tk_64" >&6; } + +fi + # remove 64-bit arch flags from CFLAGS et al. if configuration + # does not support 64-bit. + if test "$tcl_cv_lib_tk_64" = no -o "$tcl_cv_lib_x11_64" = no; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: Removing 64-bit architectures from compiler & linker flags" >&5 +$as_echo "$as_me: Removing 64-bit architectures from compiler & linker flags" >&6;} + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval $v'="`echo "$'$v' "|sed -e "s/-arch ppc64 / /g" -e "s/-arch x86_64 / /g"`"' + done +fi + +fi + ;; + OS/390-*) + CFLAGS_OPTIMIZE="" # Optimizer is buggy + +$as_echo "#define _OE_SOCKETS 1" >>confdefs.h + + ;; + OSF1-V*) + # Digital OSF/1 + SHLIB_CFLAGS="" + if test "$SHARED_BUILD" = 1; then : + + SHLIB_LD='ld -shared -expect_unresolved "*"' + +else + + SHLIB_LD='ld -non_shared -expect_unresolved "*"' + +fi + SHLIB_SUFFIX=".so" + if test $doRpath = yes; then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}' +fi + if test "$GCC" = yes; then : + CFLAGS="$CFLAGS -mieee" +else + + CFLAGS="$CFLAGS -DHAVE_TZSET -std1 -ieee" +fi + # see pthread_intro(3) for pthread support on osf1, k.furukawa + CFLAGS="$CFLAGS -DHAVE_PTHREAD_ATTR_SETSTACKSIZE" + CFLAGS="$CFLAGS -DTCL_THREAD_STACK_MIN=PTHREAD_STACK_MIN*64" + LIBS=`echo $LIBS | sed s/-lpthreads//` + if test "$GCC" = yes; then : + + LIBS="$LIBS -lpthread -lmach -lexc" + +else + + CFLAGS="$CFLAGS -pthread" + LDFLAGS="$LDFLAGS -pthread" + +fi + ;; + QNX-6*) + # QNX RTP + # This may work for all QNX, but it was only reported for v6. + SHLIB_CFLAGS="-fPIC" + SHLIB_LD="ld -Bshareable -x" + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + SCO_SV-3.2*) + if test "$GCC" = yes; then : + + SHLIB_CFLAGS="-fPIC -melf" + LDFLAGS="$LDFLAGS -melf -Wl,-Bexport" + +else + + SHLIB_CFLAGS="-Kpic -belf" + LDFLAGS="$LDFLAGS -belf -Wl,-Bexport" + +fi + SHLIB_LD="ld -G" + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + SunOS-5.[0-6]) + # Careful to not let 5.10+ fall into this case + + # Note: If _REENTRANT isn't defined, then Solaris + # won't define thread-safe library routines. + + +$as_echo "#define _REENTRANT 1" >>confdefs.h + + +$as_echo "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h + + + SHLIB_CFLAGS="-KPIC" + SHLIB_SUFFIX=".so" + if test "$GCC" = yes; then : + + SHLIB_LD='${CC} -shared' + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + +else + + SHLIB_LD="/usr/ccs/bin/ld -G -z text" + CC_SEARCH_FLAGS='-R "${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + +fi + ;; + SunOS-5*) + # Note: If _REENTRANT isn't defined, then Solaris + # won't define thread-safe library routines. + + +$as_echo "#define _REENTRANT 1" >>confdefs.h + + +$as_echo "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h + + + SHLIB_CFLAGS="-KPIC" + + # Check to enable 64-bit flags for compiler/linker + if test "$do64bit" = yes; then : + + arch=`isainfo` + if test "$arch" = "sparcv9 sparc"; then : + + if test "$GCC" = yes; then : + + if test "`${CC} -dumpversion | awk -F. '{print $1}'`" -lt 3; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC < 3.2 on $system" >&5 +$as_echo "$as_me: WARNING: 64bit mode not supported with GCC < 3.2 on $system" >&2;} + +else + + do64bit_ok=yes + CFLAGS="$CFLAGS -m64 -mcpu=v9" + LDFLAGS="$LDFLAGS -m64 -mcpu=v9" + SHLIB_CFLAGS="-fPIC" + +fi + +else + + do64bit_ok=yes + if test "$do64bitVIS" = yes; then : + + CFLAGS="$CFLAGS -xarch=v9a" + LDFLAGS_ARCH="-xarch=v9a" + +else + + CFLAGS="$CFLAGS -xarch=v9" + LDFLAGS_ARCH="-xarch=v9" + +fi + # Solaris 64 uses this as well + #LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH_64" + +fi + +else + if test "$arch" = "amd64 i386"; then : + + if test "$GCC" = yes; then : + + case $system in + SunOS-5.1[1-9]*|SunOS-5.[2-9][0-9]*) + do64bit_ok=yes + CFLAGS="$CFLAGS -m64" + LDFLAGS="$LDFLAGS -m64";; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC on $system" >&5 +$as_echo "$as_me: WARNING: 64bit mode not supported with GCC on $system" >&2;};; + esac + +else + + do64bit_ok=yes + case $system in + SunOS-5.1[1-9]*|SunOS-5.[2-9][0-9]*) + CFLAGS="$CFLAGS -m64" + LDFLAGS="$LDFLAGS -m64";; + *) + CFLAGS="$CFLAGS -xarch=amd64" + LDFLAGS="$LDFLAGS -xarch=amd64";; + esac + +fi + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported for $arch" >&5 +$as_echo "$as_me: WARNING: 64bit mode not supported for $arch" >&2;} +fi +fi + +fi + + SHLIB_SUFFIX=".so" + if test "$GCC" = yes; then : + + SHLIB_LD='${CC} -shared' + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + if test "$do64bit_ok" = yes; then : + + if test "$arch" = "sparcv9 sparc"; then : + + # We need to specify -static-libgcc or we need to + # add the path to the sparv9 libgcc. + # JH: static-libgcc is necessary for core Tcl, but may + # not be necessary for extensions. + SHLIB_LD="$SHLIB_LD -m64 -mcpu=v9 -static-libgcc" + # for finding sparcv9 libgcc, get the regular libgcc + # path, remove so name and append 'sparcv9' + #v9gcclibdir="`gcc -print-file-name=libgcc_s.so` | ..." + #CC_SEARCH_FLAGS="${CC_SEARCH_FLAGS},-R,$v9gcclibdir" + +else + if test "$arch" = "amd64 i386"; then : + + # JH: static-libgcc is necessary for core Tcl, but may + # not be necessary for extensions. + SHLIB_LD="$SHLIB_LD -m64 -static-libgcc" + +fi +fi + +fi + +else + + case $system in + SunOS-5.[1-9][0-9]*) + # TEA specific: use LDFLAGS_DEFAULT instead of LDFLAGS + SHLIB_LD='${CC} -G -z text ${LDFLAGS_DEFAULT}';; + *) + SHLIB_LD='/usr/ccs/bin/ld -G -z text';; + esac + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-R "${LIB_RUNTIME_DIR}"' + +fi + ;; + UNIX_SV* | UnixWare-5*) + SHLIB_CFLAGS="-KPIC" + SHLIB_LD='${CC} -G' + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + # Some UNIX_SV* systems (unixware 1.1.2 for example) have linkers + # that don't grok the -Bexport option. Test that it does. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld accepts -Bexport flag" >&5 +$as_echo_n "checking for ld accepts -Bexport flag... " >&6; } +if ${tcl_cv_ld_Bexport+:} false; then : + $as_echo_n "(cached) " >&6 +else + + hold_ldflags=$LDFLAGS + LDFLAGS="$LDFLAGS -Wl,-Bexport" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +int i; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + tcl_cv_ld_Bexport=yes +else + tcl_cv_ld_Bexport=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$hold_ldflags +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_ld_Bexport" >&5 +$as_echo "$tcl_cv_ld_Bexport" >&6; } + if test $tcl_cv_ld_Bexport = yes; then : + + LDFLAGS="$LDFLAGS -Wl,-Bexport" + +fi + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + esac + + if test "$do64bit" = yes -a "$do64bit_ok" = no; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 64bit support being disabled -- don't know magic for this platform" >&5 +$as_echo "$as_me: WARNING: 64bit support being disabled -- don't know magic for this platform" >&2;} + +fi + + + + # Add in the arch flags late to ensure it wasn't removed. + # Not necessary in TEA, but this is aligned with core + LDFLAGS="$LDFLAGS $LDFLAGS_ARCH" + + # If we're running gcc, then change the C flags for compiling shared + # libraries to the right flags for gcc, instead of those for the + # standard manufacturer compiler. + + if test "$GCC" = yes; then : + + case $system in + AIX-*) ;; + BSD/OS*) ;; + CYGWIN_*|MINGW32_*|MINGW64_*|MSYS_*) ;; + IRIX*) ;; + NetBSD-*|DragonFly-*|FreeBSD-*|OpenBSD-*) ;; + Darwin-*) ;; + SCO_SV-3.2*) ;; + windows) ;; + *) SHLIB_CFLAGS="-fPIC" ;; + esac +fi + + if test "$tcl_cv_cc_visibility_hidden" != yes; then : + + +$as_echo "#define MODULE_SCOPE extern" >>confdefs.h + + +fi + + if test "$SHARED_LIB_SUFFIX" = ""; then : + + # TEA specific: use PACKAGE_VERSION instead of VERSION + SHARED_LIB_SUFFIX='${PACKAGE_VERSION}${SHLIB_SUFFIX}' +fi + if test "$UNSHARED_LIB_SUFFIX" = ""; then : + + # TEA specific: use PACKAGE_VERSION instead of VERSION + UNSHARED_LIB_SUFFIX='${PACKAGE_VERSION}.a' +fi + + if test "${GCC}" = "yes" -a ${SHLIB_SUFFIX} = ".dll"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SEH support in compiler" >&5 +$as_echo_n "checking for SEH support in compiler... " >&6; } +if ${tcl_cv_seh+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then : + tcl_cv_seh=no +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#undef WIN32_LEAN_AND_MEAN + + int main(int argc, char** argv) { + int a, b = 0; + __try { + a = 666 / b; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + return 0; + } + return 1; + } + +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + tcl_cv_seh=yes +else + tcl_cv_seh=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_seh" >&5 +$as_echo "$tcl_cv_seh" >&6; } + if test "$tcl_cv_seh" = "no" ; then + +$as_echo "#define HAVE_NO_SEH 1" >>confdefs.h + + fi + + # + # Check to see if the excpt.h include file provided contains the + # definition for EXCEPTION_DISPOSITION; if not, which is the case + # with Cygwin's version as of 2002-04-10, define it to be int, + # sufficient for getting the current code to work. + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for EXCEPTION_DISPOSITION support in include files" >&5 +$as_echo_n "checking for EXCEPTION_DISPOSITION support in include files... " >&6; } +if ${tcl_cv_eh_disposition+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +# define WIN32_LEAN_AND_MEAN +# include <windows.h> +# undef WIN32_LEAN_AND_MEAN + +int +main () +{ + + EXCEPTION_DISPOSITION x; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_eh_disposition=yes +else + tcl_cv_eh_disposition=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_eh_disposition" >&5 +$as_echo "$tcl_cv_eh_disposition" >&6; } + if test "$tcl_cv_eh_disposition" = "no" ; then + +$as_echo "#define EXCEPTION_DISPOSITION int" >>confdefs.h + + fi + + # Check to see if winnt.h defines CHAR, SHORT, and LONG + # even if VOID has already been #defined. The win32api + # used by mingw and cygwin is known to do this. + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for winnt.h that ignores VOID define" >&5 +$as_echo_n "checking for winnt.h that ignores VOID define... " >&6; } +if ${tcl_cv_winnt_ignore_void+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#define VOID void +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#undef WIN32_LEAN_AND_MEAN + +int +main () +{ + + CHAR c; + SHORT s; + LONG l; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_winnt_ignore_void=yes +else + tcl_cv_winnt_ignore_void=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_winnt_ignore_void" >&5 +$as_echo "$tcl_cv_winnt_ignore_void" >&6; } + if test "$tcl_cv_winnt_ignore_void" = "yes" ; then + +$as_echo "#define HAVE_WINNT_IGNORE_VOID 1" >>confdefs.h + + fi + fi + + # See if the compiler supports casting to a union type. + # This is used to stop gcc from printing a compiler + # warning when initializing a union member. + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for cast to union support" >&5 +$as_echo_n "checking for cast to union support... " >&6; } +if ${tcl_cv_cast_to_union+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + union foo { int i; double d; }; + union foo f = (union foo) (int) 0; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_cast_to_union=yes +else + tcl_cv_cast_to_union=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cast_to_union" >&5 +$as_echo "$tcl_cv_cast_to_union" >&6; } + if test "$tcl_cv_cast_to_union" = "yes"; then + +$as_echo "#define HAVE_CAST_TO_UNION 1" >>confdefs.h + + fi + + ac_fn_c_check_header_mongrel "$LINENO" "stdbool.h" "ac_cv_header_stdbool_h" "$ac_includes_default" +if test "x$ac_cv_header_stdbool_h" = xyes; then : + +$as_echo "#define HAVE_STDBOOL_H 1" >>confdefs.h + +fi + + + + + + + + + + + + + + + + + + # These must be called after we do the basic CFLAGS checks and + # verify any possible 64-bit or similar switches are necessary + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for required early compiler flags" >&5 +$as_echo_n "checking for required early compiler flags... " >&6; } + tcl_flags="" + + if ${tcl_cv_flag__isoc99_source+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdlib.h> +int +main () +{ +char *p = (char *)strtoll; char *q = (char *)strtoull; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_flag__isoc99_source=no +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _ISOC99_SOURCE 1 +#include <stdlib.h> +int +main () +{ +char *p = (char *)strtoll; char *q = (char *)strtoull; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_flag__isoc99_source=yes +else + tcl_cv_flag__isoc99_source=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + + if test "x${tcl_cv_flag__isoc99_source}" = "xyes" ; then + +$as_echo "#define _ISOC99_SOURCE 1" >>confdefs.h + + tcl_flags="$tcl_flags _ISOC99_SOURCE" + fi + + + if ${tcl_cv_flag__largefile64_source+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/stat.h> +int +main () +{ +struct stat64 buf; int i = stat64("/", &buf); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_flag__largefile64_source=no +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _LARGEFILE64_SOURCE 1 +#include <sys/stat.h> +int +main () +{ +struct stat64 buf; int i = stat64("/", &buf); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_flag__largefile64_source=yes +else + tcl_cv_flag__largefile64_source=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + + if test "x${tcl_cv_flag__largefile64_source}" = "xyes" ; then + +$as_echo "#define _LARGEFILE64_SOURCE 1" >>confdefs.h + + tcl_flags="$tcl_flags _LARGEFILE64_SOURCE" + fi + + + if ${tcl_cv_flag__largefile_source64+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/stat.h> +int +main () +{ +char *p = (char *)open64; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_flag__largefile_source64=no +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _LARGEFILE_SOURCE64 1 +#include <sys/stat.h> +int +main () +{ +char *p = (char *)open64; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_flag__largefile_source64=yes +else + tcl_cv_flag__largefile_source64=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + + if test "x${tcl_cv_flag__largefile_source64}" = "xyes" ; then + +$as_echo "#define _LARGEFILE_SOURCE64 1" >>confdefs.h + + tcl_flags="$tcl_flags _LARGEFILE_SOURCE64" + fi + + if test "x${tcl_flags}" = "x" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 +$as_echo "none" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${tcl_flags}" >&5 +$as_echo "${tcl_flags}" >&6; } + fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for 64-bit integer type" >&5 +$as_echo_n "checking for 64-bit integer type... " >&6; } + if ${tcl_cv_type_64bit+:} false; then : + $as_echo_n "(cached) " >&6 +else + + tcl_cv_type_64bit=none + # See if the compiler knows natively about __int64 + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +__int64 value = (__int64) 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_type_64bit=__int64 +else + tcl_type_64bit="long long" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + # See if we could use long anyway Note that we substitute in the + # type that is our current guess for a 64-bit type inside this check + # program, so it should be modified only carefully... + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +switch (0) { + case 1: case (sizeof(${tcl_type_64bit})==sizeof(long)): ; + } + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_type_64bit=${tcl_type_64bit} +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + + if test "${tcl_cv_type_64bit}" = none ; then + +$as_echo "#define TCL_WIDE_INT_IS_LONG 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + elif test "${tcl_cv_type_64bit}" = "__int64" \ + -a "${TEA_PLATFORM}" = "windows" ; then + # TEA specific: We actually want to use the default tcl.h checks in + # this case to handle both TCL_WIDE_INT_TYPE and TCL_LL_MODIFIER* + { $as_echo "$as_me:${as_lineno-$LINENO}: result: using Tcl header defaults" >&5 +$as_echo "using Tcl header defaults" >&6; } + else + +cat >>confdefs.h <<_ACEOF +#define TCL_WIDE_INT_TYPE ${tcl_cv_type_64bit} +_ACEOF + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${tcl_cv_type_64bit}" >&5 +$as_echo "${tcl_cv_type_64bit}" >&6; } + + # Now check for auxiliary declarations + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for struct dirent64" >&5 +$as_echo_n "checking for struct dirent64... " >&6; } +if ${tcl_cv_struct_dirent64+:} false; then : + $as_echo_n "(cached) " >&6 +else + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/types.h> +#include <dirent.h> +int +main () +{ +struct dirent64 p; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_struct_dirent64=yes +else + tcl_cv_struct_dirent64=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_struct_dirent64" >&5 +$as_echo "$tcl_cv_struct_dirent64" >&6; } + if test "x${tcl_cv_struct_dirent64}" = "xyes" ; then + +$as_echo "#define HAVE_STRUCT_DIRENT64 1" >>confdefs.h + + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for DIR64" >&5 +$as_echo_n "checking for DIR64... " >&6; } +if ${tcl_cv_DIR64+:} false; then : + $as_echo_n "(cached) " >&6 +else + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/types.h> +#include <dirent.h> +int +main () +{ +struct dirent64 *p; DIR64 d = opendir64("."); + p = readdir64(d); rewinddir64(d); closedir64(d); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_DIR64=yes +else + tcl_cv_DIR64=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_DIR64" >&5 +$as_echo "$tcl_cv_DIR64" >&6; } + if test "x${tcl_cv_DIR64}" = "xyes" ; then + +$as_echo "#define HAVE_DIR64 1" >>confdefs.h + + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for struct stat64" >&5 +$as_echo_n "checking for struct stat64... " >&6; } +if ${tcl_cv_struct_stat64+:} false; then : + $as_echo_n "(cached) " >&6 +else + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/stat.h> +int +main () +{ +struct stat64 p; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_struct_stat64=yes +else + tcl_cv_struct_stat64=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_struct_stat64" >&5 +$as_echo "$tcl_cv_struct_stat64" >&6; } + if test "x${tcl_cv_struct_stat64}" = "xyes" ; then + +$as_echo "#define HAVE_STRUCT_STAT64 1" >>confdefs.h + + fi + + for ac_func in open64 lseek64 +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for off64_t" >&5 +$as_echo_n "checking for off64_t... " >&6; } + if ${tcl_cv_type_off64_t+:} false; then : + $as_echo_n "(cached) " >&6 +else + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <sys/types.h> +int +main () +{ +off64_t offset; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tcl_cv_type_off64_t=yes +else + tcl_cv_type_off64_t=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + + if test "x${tcl_cv_type_off64_t}" = "xyes" && \ + test "x${ac_cv_func_lseek64}" = "xyes" && \ + test "x${ac_cv_func_open64}" = "xyes" ; then + +$as_echo "#define HAVE_TYPE_OFF64_T 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + fi + + + +#-------------------------------------------------------------------- +# Set the default compiler switches based on the --enable-symbols option. +#-------------------------------------------------------------------- + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for build with symbols" >&5 +$as_echo_n "checking for build with symbols... " >&6; } + # Check whether --enable-symbols was given. +if test "${enable_symbols+set}" = set; then : + enableval=$enable_symbols; tcl_ok=$enableval +else + tcl_ok=no +fi + + if test "$tcl_ok" = "no"; then + CFLAGS_DEFAULT="${CFLAGS_OPTIMIZE} -DNDEBUG" + LDFLAGS_DEFAULT="${LDFLAGS_OPTIMIZE}" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TCL_CFG_OPTIMIZED 1" >>confdefs.h + + else + CFLAGS_DEFAULT="${CFLAGS_DEBUG}" + LDFLAGS_DEFAULT="${LDFLAGS_DEBUG}" + if test "$tcl_ok" = "yes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes (standard debugging)" >&5 +$as_echo "yes (standard debugging)" >&6; } + fi + fi + + + + if test "$tcl_ok" = "mem" -o "$tcl_ok" = "all"; then + +$as_echo "#define TCL_MEM_DEBUG 1" >>confdefs.h + + fi + + if test "$tcl_ok" != "yes" -a "$tcl_ok" != "no"; then + if test "$tcl_ok" = "all"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: enabled symbols mem debugging" >&5 +$as_echo "enabled symbols mem debugging" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: enabled $tcl_ok debugging" >&5 +$as_echo "enabled $tcl_ok debugging" >&6; } + fi + fi + + +#-------------------------------------------------------------------- +# This macro generates a line to use when building a library. It +# depends on values set by the TEA_ENABLE_SHARED, TEA_ENABLE_SYMBOLS, +# and TEA_LOAD_TCLCONFIG macros above. +#-------------------------------------------------------------------- + + + if test "${TEA_PLATFORM}" = "windows" -a "$GCC" != "yes"; then + MAKE_STATIC_LIB="\${STLIB_LD} -out:\$@ \$(PKG_OBJECTS)" + MAKE_SHARED_LIB="\${SHLIB_LD} \${LDFLAGS} \${LDFLAGS_DEFAULT} -out:\$@ \$(PKG_OBJECTS) \${SHLIB_LD_LIBS}" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#if defined(_MSC_VER) && _MSC_VER >= 1400 +print("manifest needed") +#endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "manifest needed" >/dev/null 2>&1; then : + + # Could do a CHECK_PROG for mt, but should always be with MSVC8+ + VC_MANIFEST_EMBED_DLL="if test -f \$@.manifest ; then mt.exe -nologo -manifest \$@.manifest -outputresource:\$@\;2 ; fi" + VC_MANIFEST_EMBED_EXE="if test -f \$@.manifest ; then mt.exe -nologo -manifest \$@.manifest -outputresource:\$@\;1 ; fi" + MAKE_SHARED_LIB="${MAKE_SHARED_LIB} ; ${VC_MANIFEST_EMBED_DLL}" + + CLEANFILES="$CLEANFILES *.manifest" + + +fi +rm -f conftest* + + MAKE_STUB_LIB="\${STLIB_LD} -nodefaultlib -out:\$@ \$(PKG_STUB_OBJECTS)" + else + MAKE_STATIC_LIB="\${STLIB_LD} \$@ \$(PKG_OBJECTS)" + MAKE_SHARED_LIB="\${SHLIB_LD} \${LDFLAGS} \${LDFLAGS_DEFAULT} -o \$@ \$(PKG_OBJECTS) \${SHLIB_LD_LIBS}" + MAKE_STUB_LIB="\${STLIB_LD} \$@ \$(PKG_STUB_OBJECTS)" + fi + + if test "${SHARED_BUILD}" = "1" ; then + MAKE_LIB="${MAKE_SHARED_LIB} " + else + MAKE_LIB="${MAKE_STATIC_LIB} " + fi + + #-------------------------------------------------------------------- + # Shared libraries and static libraries have different names. + # Use the double eval to make sure any variables in the suffix is + # substituted. (@@@ Might not be necessary anymore) + #-------------------------------------------------------------------- + + PACKAGE_LIB_PREFIX8="${PACKAGE_LIB_PREFIX}" + PACKAGE_LIB_PREFIX9="${PACKAGE_LIB_PREFIX}tcl9" + if test "${TCL_MAJOR_VERSION}" -gt 8 ; then + PACKAGE_LIB_PREFIX="${PACKAGE_LIB_PREFIX9}" + else + PACKAGE_LIB_PREFIX="${PACKAGE_LIB_PREFIX8}" + fi + if test "${TEA_PLATFORM}" = "windows" ; then + if test "${SHARED_BUILD}" = "1" ; then + # We force the unresolved linking of symbols that are really in + # the private libraries of Tcl and Tk. + if test x"${TK_BIN_DIR}" != x ; then + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} \"`${CYGPATH} ${TK_BIN_DIR}/${TK_STUB_LIB_FILE}`\"" + fi + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} \"`${CYGPATH} ${TCL_BIN_DIR}/${TCL_STUB_LIB_FILE}`\"" + if test "$GCC" = "yes"; then + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} -static-libgcc" + fi + eval eval "PKG_LIB_FILE8=${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE9=${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + else + if test "$GCC" = "yes"; then + PACKAGE_LIB_PREFIX=lib${PACKAGE_LIB_PREFIX} + fi + eval eval "PKG_LIB_FILE8=${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE9=${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + fi + # Some packages build their own stubs libraries + eval eval "PKG_STUB_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" + if test "$GCC" = "yes"; then + PKG_STUB_LIB_FILE=lib${PKG_STUB_LIB_FILE} + fi + # These aren't needed on Windows (either MSVC or gcc) + RANLIB=: + RANLIB_STUB=: + else + RANLIB_STUB="${RANLIB}" + if test "${SHARED_BUILD}" = "1" ; then + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} ${TCL_STUB_LIB_SPEC}" + if test x"${TK_BIN_DIR}" != x ; then + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} ${TK_STUB_LIB_SPEC}" + fi + eval eval "PKG_LIB_FILE8=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE9=lib${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + RANLIB=: + else + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + fi + # Some packages build their own stubs libraries + eval eval "PKG_STUB_LIB_FILE=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" + fi + + # These are escaped so that only CFLAGS is picked up at configure time. + # The other values will be substituted at make time. + CFLAGS="${CFLAGS} \${CFLAGS_DEFAULT} \${CFLAGS_WARNING}" + if test "${SHARED_BUILD}" = "1" ; then + CFLAGS="${CFLAGS} \${SHLIB_CFLAGS}" + fi + + + + + + + + + + +#-------------------------------------------------------------------- +# Determine the name of the tclsh and/or wish executables in the +# Tcl and Tk build directories or the location they were installed +# into. These paths are used to support running test cases only, +# the Makefile should not be making use of these paths to generate +# a pkgIndex.tcl file or anything else at extension build time. +#-------------------------------------------------------------------- + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for tclsh" >&5 +$as_echo_n "checking for tclsh... " >&6; } + if test -f "${TCL_BIN_DIR}/Makefile" ; then + # tclConfig.sh is in Tcl build directory + if test "${TEA_PLATFORM}" = "windows"; then + if test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}s${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}s${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}t${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}t${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}st${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}st${EXEEXT}" + fi + else + TCLSH_PROG="${TCL_BIN_DIR}/tclsh" + fi + else + # tclConfig.sh is in install location + if test "${TEA_PLATFORM}" = "windows"; then + TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" + else + TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}" + fi + list="`ls -d ${TCL_BIN_DIR}/../bin 2>/dev/null` \ + `ls -d ${TCL_BIN_DIR}/.. 2>/dev/null` \ + `ls -d ${TCL_PREFIX}/bin 2>/dev/null`" + for i in $list ; do + if test -f "$i/${TCLSH_PROG}" ; then + REAL_TCL_BIN_DIR="`cd "$i"; pwd`/" + break + fi + done + TCLSH_PROG="${REAL_TCL_BIN_DIR}${TCLSH_PROG}" + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${TCLSH_PROG}" >&5 +$as_echo "${TCLSH_PROG}" >&6; } + + +#TEA_PROG_WISH + +#-------------------------------------------------------------------- +# Setup a *Config.sh.in configuration file. +#-------------------------------------------------------------------- + +#TEA_EXPORT_CONFIG([sample]) +#AC_SUBST(SAMPLE_VAR) + +#-------------------------------------------------------------------- +# Specify files to substitute AC variables in. You may alternatively +# have a special pkgIndex.tcl.in or other files which require +# substituting the AC variables in. Include these here. +#-------------------------------------------------------------------- + +ac_config_files="$ac_config_files Makefile pkgIndex.tcl" + +#AC_CONFIG_FILES([sampleConfig.sh]) + +#-------------------------------------------------------------------- +# Finally, substitute all of the various values into the files +# specified with AC_CONFIG_FILES. +#-------------------------------------------------------------------- + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +CFLAGS="${CFLAGS} ${CPPFLAGS}"; CPPFLAGS="" + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by sqlite $as_me 3.44.0, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Report bugs to the package provider." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +sqlite config.status 3.44.0 +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "pkgIndex.tcl") CONFIG_FILES="$CONFIG_FILES pkgIndex.tcl" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' <conf$$subs.awk | sed ' +/^[^""]/{ + N + s/\n// +} +' >>$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + diff --git a/SQLITE/tea/configure.ac b/SQLITE/tea/configure.ac new file mode 100644 index 0000000..e34bda9 --- /dev/null +++ b/SQLITE/tea/configure.ac @@ -0,0 +1,227 @@ +#!/bin/bash -norc +dnl This file is an input file used by the GNU "autoconf" program to +dnl generate the file "configure", which is run during Tcl installation +dnl to configure the system for the local environment. + +#----------------------------------------------------------------------- +# Sample configure.ac for Tcl Extensions. The only places you should +# need to modify this file are marked by the string __CHANGE__ +#----------------------------------------------------------------------- + +#----------------------------------------------------------------------- +# __CHANGE__ +# Set your package name and version numbers here. +# +# This initializes the environment with PACKAGE_NAME and PACKAGE_VERSION +# set as provided. These will also be added as -D defs in your Makefile +# so you can encode the package version directly into the source files. +# This will also define a special symbol for Windows (BUILD_<PACKAGE_NAME> +# so that we create the export library with the dll. +#----------------------------------------------------------------------- + +AC_INIT([sqlite],[3.44.0]) + +#-------------------------------------------------------------------- +# Call TEA_INIT as the first TEA_ macro to set up initial vars. +# This will define a ${TEA_PLATFORM} variable == "unix" or "windows" +# as well as PKG_LIB_FILE and PKG_STUB_LIB_FILE. +#-------------------------------------------------------------------- + +TEA_INIT() + +AC_CONFIG_AUX_DIR(tclconfig) + +#-------------------------------------------------------------------- +# Load the tclConfig.sh file +#-------------------------------------------------------------------- + +TEA_PATH_TCLCONFIG +TEA_LOAD_TCLCONFIG + +#-------------------------------------------------------------------- +# Load the tkConfig.sh file if necessary (Tk extension) +#-------------------------------------------------------------------- + +#TEA_PATH_TKCONFIG +#TEA_LOAD_TKCONFIG + +#----------------------------------------------------------------------- +# Handle the --prefix=... option by defaulting to what Tcl gave. +# Must be called after TEA_LOAD_TCLCONFIG and before TEA_SETUP_COMPILER. +#----------------------------------------------------------------------- + +TEA_PREFIX + +#----------------------------------------------------------------------- +# Standard compiler checks. +# This sets up CC by using the CC env var, or looks for gcc otherwise. +# This also calls AC_PROG_CC and a few others to create the basic setup +# necessary to compile executables. +#----------------------------------------------------------------------- + +TEA_SETUP_COMPILER + +#----------------------------------------------------------------------- +# __CHANGE__ +# Specify the C source files to compile in TEA_ADD_SOURCES, +# public headers that need to be installed in TEA_ADD_HEADERS, +# stub library C source files to compile in TEA_ADD_STUB_SOURCES, +# and runtime Tcl library files in TEA_ADD_TCL_SOURCES. +# This defines PKG(_STUB)_SOURCES, PKG(_STUB)_OBJECTS, PKG_HEADERS +# and PKG_TCL_SOURCES. +#----------------------------------------------------------------------- + +TEA_ADD_SOURCES([tclsqlite3.c]) +TEA_ADD_HEADERS([]) +TEA_ADD_INCLUDES([]) +TEA_ADD_LIBS([]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_FTS3=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_FTS4=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_FTS5=1]) +TEA_ADD_CFLAGS([-DSQLITE_3_SUFFIX_ONLY=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_RTREE=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_GEOPOLY=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_MATH_FUNCTIONS=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_DESERIALIZE=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_DBPAGE_VTAB=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_BYTECODE_VTAB=1]) +TEA_ADD_CFLAGS([-DSQLITE_ENABLE_DBSTAT_VTAB=1]) +TEA_ADD_STUB_SOURCES([]) +TEA_ADD_TCL_SOURCES([]) + +#-------------------------------------------------------------------- +# The --with-system-sqlite causes the TCL bindings to SQLite to use +# the system shared library for SQLite rather than statically linking +# against its own private copy. This is dangerous and leads to +# undersirable dependences and is not recommended. +# Patchs from rmax. +#-------------------------------------------------------------------- +AC_ARG_WITH([system-sqlite], + [AC_HELP_STRING([--with-system-sqlite], + [use a system-supplied libsqlite3 instead of the bundled one])], + [], [with_system_sqlite=no]) +if test x$with_system_sqlite != xno; then + AC_CHECK_HEADER([sqlite3.h], + [AC_CHECK_LIB([sqlite3],[sqlite3_initialize], + [AC_DEFINE(USE_SYSTEM_SQLITE) + LIBS="$LIBS -lsqlite3"])]) +fi + +#-------------------------------------------------------------------- +# __CHANGE__ +# +# You can add more files to clean if your extension creates any extra +# files by extending CLEANFILES. +# Add pkgIndex.tcl if it is generated in the Makefile instead of ./configure +# and change Makefile.in to move it from CONFIG_CLEAN_FILES to BINARIES var. +# +# A few miscellaneous platform-specific items: +# TEA_ADD_* any platform specific compiler/build info here. +#-------------------------------------------------------------------- + +#CLEANFILES="$CLEANFILES pkgIndex.tcl" +if test "${TEA_PLATFORM}" = "windows" ; then + # Ensure no empty if clauses + : + #TEA_ADD_SOURCES([win/winFile.c]) + #TEA_ADD_INCLUDES([-I\"$(${CYGPATH} ${srcdir}/win)\"]) +else + # Ensure no empty else clauses + : + #TEA_ADD_SOURCES([unix/unixFile.c]) + #TEA_ADD_LIBS([-lsuperfly]) +fi + +#-------------------------------------------------------------------- +# __CHANGE__ +# Choose which headers you need. Extension authors should try very +# hard to only rely on the Tcl public header files. Internal headers +# contain private data structures and are subject to change without +# notice. +# This MUST be called after TEA_LOAD_TCLCONFIG / TEA_LOAD_TKCONFIG +#-------------------------------------------------------------------- + +TEA_PUBLIC_TCL_HEADERS +#TEA_PRIVATE_TCL_HEADERS + +#TEA_PUBLIC_TK_HEADERS +#TEA_PRIVATE_TK_HEADERS +#TEA_PATH_X + +#-------------------------------------------------------------------- +# Check whether --enable-threads or --disable-threads was given. +# This auto-enables if Tcl was compiled threaded. +#-------------------------------------------------------------------- + +TEA_ENABLE_THREADS +if test "${TCL_THREADS}" = "1" ; then + AC_DEFINE(SQLITE_THREADSAFE, 1, [Trigger sqlite threadsafe build]) + # Not automatically added by Tcl because its assumed Tcl links to them, + # but it may not if it isn't really a threaded build. + TEA_ADD_LIBS([$THREADS_LIBS]) +else + AC_DEFINE(SQLITE_THREADSAFE, 0, [Trigger sqlite non-threadsafe build]) +fi + +#-------------------------------------------------------------------- +# The statement below defines a collection of symbols related to +# building as a shared library instead of a static library. +#-------------------------------------------------------------------- + +TEA_ENABLE_SHARED + +#-------------------------------------------------------------------- +# This macro figures out what flags to use with the compiler/linker +# when building shared/static debug/optimized objects. This information +# can be taken from the tclConfig.sh file, but this figures it all out. +#-------------------------------------------------------------------- + +TEA_CONFIG_CFLAGS + +#-------------------------------------------------------------------- +# Set the default compiler switches based on the --enable-symbols option. +#-------------------------------------------------------------------- + +TEA_ENABLE_SYMBOLS + +#-------------------------------------------------------------------- +# This macro generates a line to use when building a library. It +# depends on values set by the TEA_ENABLE_SHARED, TEA_ENABLE_SYMBOLS, +# and TEA_LOAD_TCLCONFIG macros above. +#-------------------------------------------------------------------- + +TEA_MAKE_LIB + +#-------------------------------------------------------------------- +# Determine the name of the tclsh and/or wish executables in the +# Tcl and Tk build directories or the location they were installed +# into. These paths are used to support running test cases only, +# the Makefile should not be making use of these paths to generate +# a pkgIndex.tcl file or anything else at extension build time. +#-------------------------------------------------------------------- + +TEA_PROG_TCLSH +#TEA_PROG_WISH + +#-------------------------------------------------------------------- +# Setup a *Config.sh.in configuration file. +#-------------------------------------------------------------------- + +#TEA_EXPORT_CONFIG([sample]) +#AC_SUBST(SAMPLE_VAR) + +#-------------------------------------------------------------------- +# Specify files to substitute AC variables in. You may alternatively +# have a special pkgIndex.tcl.in or other files which require +# substituting the AC variables in. Include these here. +#-------------------------------------------------------------------- + +AC_CONFIG_FILES([Makefile pkgIndex.tcl]) +#AC_CONFIG_FILES([sampleConfig.sh]) + +#-------------------------------------------------------------------- +# Finally, substitute all of the various values into the files +# specified with AC_CONFIG_FILES. +#-------------------------------------------------------------------- + +AC_OUTPUT diff --git a/SQLITE/tea/doc/sqlite3.n b/SQLITE/tea/doc/sqlite3.n new file mode 100644 index 0000000..13913e5 --- /dev/null +++ b/SQLITE/tea/doc/sqlite3.n @@ -0,0 +1,15 @@ +.TH sqlite3 n 4.1 "Tcl-Extensions" +.HS sqlite3 tcl +.BS +.SH NAME +sqlite3 \- an interface to the SQLite3 database engine +.SH SYNOPSIS +\fBsqlite3\fI command_name ?filename?\fR +.br +.SH DESCRIPTION +SQLite3 is a self-contains, zero-configuration, transactional SQL database +engine. This extension provides an easy to use interface for accessing +SQLite database files from Tcl. +.PP +For full documentation see \fIhttp://www.sqlite.org/\fR and +in particular \fIhttp://www.sqlite.org/tclsqlite.html\fR. diff --git a/SQLITE/tea/generic/tclsqlite3.c b/SQLITE/tea/generic/tclsqlite3.c new file mode 100644 index 0000000..0810b07 --- /dev/null +++ b/SQLITE/tea/generic/tclsqlite3.c @@ -0,0 +1,4080 @@ +#ifdef USE_SYSTEM_SQLITE +# include <sqlite3.h> +#else +#include "sqlite3.c" +#endif +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** A TCL Interface to SQLite. Append this file to sqlite3.c and +** compile the whole thing to build a TCL-enabled version of SQLite. +** +** Compile-time options: +** +** -DTCLSH Add a "main()" routine that works as a tclsh. +** +** -DTCLSH_INIT_PROC=name +** +** Invoke name(interp) to initialize the Tcl interpreter. +** If name(interp) returns a non-NULL string, then run +** that string as a Tcl script to launch the application. +** If name(interp) returns NULL, then run the regular +** tclsh-emulator code. +*/ +#ifdef TCLSH_INIT_PROC +# define TCLSH 1 +#endif + +/* +** If requested, include the SQLite compiler options file for MSVC. +*/ +#if defined(INCLUDE_MSVC_H) +# include "msvc.h" +#endif + +#if defined(INCLUDE_SQLITE_TCL_H) +# include "sqlite_tcl.h" +#else +# include "tcl.h" +# ifndef SQLITE_TCLAPI +# define SQLITE_TCLAPI +# endif +#endif +#include <errno.h> + +/* +** Some additional include files are needed if this file is not +** appended to the amalgamation. +*/ +#ifndef SQLITE_AMALGAMATION +# include "sqlite3.h" +# include <stdlib.h> +# include <string.h> +# include <assert.h> + typedef unsigned char u8; +# ifndef SQLITE_PTRSIZE +# if defined(__SIZEOF_POINTER__) +# define SQLITE_PTRSIZE __SIZEOF_POINTER__ +# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(_M_ARM) || defined(__arm__) || defined(__x86) || \ + (defined(__APPLE__) && defined(__POWERPC__)) || \ + (defined(__TOS_AIX__) && !defined(__64BIT__)) +# define SQLITE_PTRSIZE 4 +# else +# define SQLITE_PTRSIZE 8 +# endif +# endif /* SQLITE_PTRSIZE */ +# if defined(HAVE_STDINT_H) + typedef uintptr_t uptr; +# elif SQLITE_PTRSIZE==4 + typedef unsigned int uptr; +# else + typedef sqlite3_uint64 uptr; +# endif +#endif +#include <ctype.h> + +/* Used to get the current process ID */ +#if !defined(_WIN32) +# include <signal.h> +# include <unistd.h> +# define GETPID getpid +#elif !defined(_WIN32_WCE) +# ifndef SQLITE_AMALGAMATION +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include <windows.h> +# endif +# include <io.h> +# define isatty(h) _isatty(h) +# define GETPID (int)GetCurrentProcessId +#endif + +/* + * Windows needs to know which symbols to export. Unix does not. + * BUILD_sqlite should be undefined for Unix. + */ +#ifdef BUILD_sqlite +#undef TCL_STORAGE_CLASS +#define TCL_STORAGE_CLASS DLLEXPORT +#endif /* BUILD_sqlite */ + +#define NUM_PREPARED_STMTS 10 +#define MAX_PREPARED_STMTS 100 + +/* Forward declaration */ +typedef struct SqliteDb SqliteDb; + +/* +** New SQL functions can be created as TCL scripts. Each such function +** is described by an instance of the following structure. +** +** Variable eType may be set to SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, +** SQLITE_BLOB or SQLITE_NULL. If it is SQLITE_NULL, then the implementation +** attempts to determine the type of the result based on the Tcl object. +** If it is SQLITE_TEXT or SQLITE_BLOB, then a text (sqlite3_result_text()) +** or blob (sqlite3_result_blob()) is returned. If it is SQLITE_INTEGER +** or SQLITE_FLOAT, then an attempt is made to return an integer or float +** value, falling back to float and then text if this is not possible. +*/ +typedef struct SqlFunc SqlFunc; +struct SqlFunc { + Tcl_Interp *interp; /* The TCL interpret to execute the function */ + Tcl_Obj *pScript; /* The Tcl_Obj representation of the script */ + SqliteDb *pDb; /* Database connection that owns this function */ + int useEvalObjv; /* True if it is safe to use Tcl_EvalObjv */ + int eType; /* Type of value to return */ + char *zName; /* Name of this function */ + SqlFunc *pNext; /* Next function on the list of them all */ +}; + +/* +** New collation sequences function can be created as TCL scripts. Each such +** function is described by an instance of the following structure. +*/ +typedef struct SqlCollate SqlCollate; +struct SqlCollate { + Tcl_Interp *interp; /* The TCL interpret to execute the function */ + char *zScript; /* The script to be run */ + SqlCollate *pNext; /* Next function on the list of them all */ +}; + +/* +** Prepared statements are cached for faster execution. Each prepared +** statement is described by an instance of the following structure. +*/ +typedef struct SqlPreparedStmt SqlPreparedStmt; +struct SqlPreparedStmt { + SqlPreparedStmt *pNext; /* Next in linked list */ + SqlPreparedStmt *pPrev; /* Previous on the list */ + sqlite3_stmt *pStmt; /* The prepared statement */ + int nSql; /* chars in zSql[] */ + const char *zSql; /* Text of the SQL statement */ + int nParm; /* Size of apParm array */ + Tcl_Obj **apParm; /* Array of referenced object pointers */ +}; + +typedef struct IncrblobChannel IncrblobChannel; + +/* +** There is one instance of this structure for each SQLite database +** that has been opened by the SQLite TCL interface. +** +** If this module is built with SQLITE_TEST defined (to create the SQLite +** testfixture executable), then it may be configured to use either +** sqlite3_prepare_v2() or sqlite3_prepare() to prepare SQL statements. +** If SqliteDb.bLegacyPrepare is true, sqlite3_prepare() is used. +*/ +struct SqliteDb { + sqlite3 *db; /* The "real" database structure. MUST BE FIRST */ + Tcl_Interp *interp; /* The interpreter used for this database */ + char *zBusy; /* The busy callback routine */ + char *zCommit; /* The commit hook callback routine */ + char *zTrace; /* The trace callback routine */ + char *zTraceV2; /* The trace_v2 callback routine */ + char *zProfile; /* The profile callback routine */ + char *zProgress; /* The progress callback routine */ + char *zBindFallback; /* Callback to invoke on a binding miss */ + char *zAuth; /* The authorization callback routine */ + int disableAuth; /* Disable the authorizer if it exists */ + char *zNull; /* Text to substitute for an SQL NULL value */ + SqlFunc *pFunc; /* List of SQL functions */ + Tcl_Obj *pUpdateHook; /* Update hook script (if any) */ + Tcl_Obj *pPreUpdateHook; /* Pre-update hook script (if any) */ + Tcl_Obj *pRollbackHook; /* Rollback hook script (if any) */ + Tcl_Obj *pWalHook; /* WAL hook script (if any) */ + Tcl_Obj *pUnlockNotify; /* Unlock notify script (if any) */ + SqlCollate *pCollate; /* List of SQL collation functions */ + int rc; /* Return code of most recent sqlite3_exec() */ + Tcl_Obj *pCollateNeeded; /* Collation needed script */ + SqlPreparedStmt *stmtList; /* List of prepared statements*/ + SqlPreparedStmt *stmtLast; /* Last statement in the list */ + int maxStmt; /* The next maximum number of stmtList */ + int nStmt; /* Number of statements in stmtList */ + IncrblobChannel *pIncrblob;/* Linked list of open incrblob channels */ + int nStep, nSort, nIndex; /* Statistics for most recent operation */ + int nVMStep; /* Another statistic for most recent operation */ + int nTransaction; /* Number of nested [transaction] methods */ + int openFlags; /* Flags used to open. (SQLITE_OPEN_URI) */ + int nRef; /* Delete object when this reaches 0 */ +#ifdef SQLITE_TEST + int bLegacyPrepare; /* True to use sqlite3_prepare() */ +#endif +}; + +struct IncrblobChannel { + sqlite3_blob *pBlob; /* sqlite3 blob handle */ + SqliteDb *pDb; /* Associated database connection */ + int iSeek; /* Current seek offset */ + Tcl_Channel channel; /* Channel identifier */ + IncrblobChannel *pNext; /* Linked list of all open incrblob channels */ + IncrblobChannel *pPrev; /* Linked list of all open incrblob channels */ +}; + +/* +** Compute a string length that is limited to what can be stored in +** lower 30 bits of a 32-bit signed integer. +*/ +static int strlen30(const char *z){ + const char *z2 = z; + while( *z2 ){ z2++; } + return 0x3fffffff & (int)(z2 - z); +} + + +#ifndef SQLITE_OMIT_INCRBLOB +/* +** Close all incrblob channels opened using database connection pDb. +** This is called when shutting down the database connection. +*/ +static void closeIncrblobChannels(SqliteDb *pDb){ + IncrblobChannel *p; + IncrblobChannel *pNext; + + for(p=pDb->pIncrblob; p; p=pNext){ + pNext = p->pNext; + + /* Note: Calling unregister here call Tcl_Close on the incrblob channel, + ** which deletes the IncrblobChannel structure at *p. So do not + ** call Tcl_Free() here. + */ + Tcl_UnregisterChannel(pDb->interp, p->channel); + } +} + +/* +** Close an incremental blob channel. +*/ +static int SQLITE_TCLAPI incrblobClose( + ClientData instanceData, + Tcl_Interp *interp +){ + IncrblobChannel *p = (IncrblobChannel *)instanceData; + int rc = sqlite3_blob_close(p->pBlob); + sqlite3 *db = p->pDb->db; + + /* Remove the channel from the SqliteDb.pIncrblob list. */ + if( p->pNext ){ + p->pNext->pPrev = p->pPrev; + } + if( p->pPrev ){ + p->pPrev->pNext = p->pNext; + } + if( p->pDb->pIncrblob==p ){ + p->pDb->pIncrblob = p->pNext; + } + + /* Free the IncrblobChannel structure */ + Tcl_Free((char *)p); + + if( rc!=SQLITE_OK ){ + Tcl_SetResult(interp, (char *)sqlite3_errmsg(db), TCL_VOLATILE); + return TCL_ERROR; + } + return TCL_OK; +} + +/* +** Read data from an incremental blob channel. +*/ +static int SQLITE_TCLAPI incrblobInput( + ClientData instanceData, + char *buf, + int bufSize, + int *errorCodePtr +){ + IncrblobChannel *p = (IncrblobChannel *)instanceData; + int nRead = bufSize; /* Number of bytes to read */ + int nBlob; /* Total size of the blob */ + int rc; /* sqlite error code */ + + nBlob = sqlite3_blob_bytes(p->pBlob); + if( (p->iSeek+nRead)>nBlob ){ + nRead = nBlob-p->iSeek; + } + if( nRead<=0 ){ + return 0; + } + + rc = sqlite3_blob_read(p->pBlob, (void *)buf, nRead, p->iSeek); + if( rc!=SQLITE_OK ){ + *errorCodePtr = rc; + return -1; + } + + p->iSeek += nRead; + return nRead; +} + +/* +** Write data to an incremental blob channel. +*/ +static int SQLITE_TCLAPI incrblobOutput( + ClientData instanceData, + CONST char *buf, + int toWrite, + int *errorCodePtr +){ + IncrblobChannel *p = (IncrblobChannel *)instanceData; + int nWrite = toWrite; /* Number of bytes to write */ + int nBlob; /* Total size of the blob */ + int rc; /* sqlite error code */ + + nBlob = sqlite3_blob_bytes(p->pBlob); + if( (p->iSeek+nWrite)>nBlob ){ + *errorCodePtr = EINVAL; + return -1; + } + if( nWrite<=0 ){ + return 0; + } + + rc = sqlite3_blob_write(p->pBlob, (void *)buf, nWrite, p->iSeek); + if( rc!=SQLITE_OK ){ + *errorCodePtr = EIO; + return -1; + } + + p->iSeek += nWrite; + return nWrite; +} + +/* +** Seek an incremental blob channel. +*/ +static int SQLITE_TCLAPI incrblobSeek( + ClientData instanceData, + long offset, + int seekMode, + int *errorCodePtr +){ + IncrblobChannel *p = (IncrblobChannel *)instanceData; + + switch( seekMode ){ + case SEEK_SET: + p->iSeek = offset; + break; + case SEEK_CUR: + p->iSeek += offset; + break; + case SEEK_END: + p->iSeek = sqlite3_blob_bytes(p->pBlob) + offset; + break; + + default: assert(!"Bad seekMode"); + } + + return p->iSeek; +} + + +static void SQLITE_TCLAPI incrblobWatch( + ClientData instanceData, + int mode +){ + /* NO-OP */ +} +static int SQLITE_TCLAPI incrblobHandle( + ClientData instanceData, + int dir, + ClientData *hPtr +){ + return TCL_ERROR; +} + +static Tcl_ChannelType IncrblobChannelType = { + "incrblob", /* typeName */ + TCL_CHANNEL_VERSION_2, /* version */ + incrblobClose, /* closeProc */ + incrblobInput, /* inputProc */ + incrblobOutput, /* outputProc */ + incrblobSeek, /* seekProc */ + 0, /* setOptionProc */ + 0, /* getOptionProc */ + incrblobWatch, /* watchProc (this is a no-op) */ + incrblobHandle, /* getHandleProc (always returns error) */ + 0, /* close2Proc */ + 0, /* blockModeProc */ + 0, /* flushProc */ + 0, /* handlerProc */ + 0, /* wideSeekProc */ +}; + +/* +** Create a new incrblob channel. +*/ +static int createIncrblobChannel( + Tcl_Interp *interp, + SqliteDb *pDb, + const char *zDb, + const char *zTable, + const char *zColumn, + sqlite_int64 iRow, + int isReadonly +){ + IncrblobChannel *p; + sqlite3 *db = pDb->db; + sqlite3_blob *pBlob; + int rc; + int flags = TCL_READABLE|(isReadonly ? 0 : TCL_WRITABLE); + + /* This variable is used to name the channels: "incrblob_[incr count]" */ + static int count = 0; + char zChannel[64]; + + rc = sqlite3_blob_open(db, zDb, zTable, zColumn, iRow, !isReadonly, &pBlob); + if( rc!=SQLITE_OK ){ + Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_VOLATILE); + return TCL_ERROR; + } + + p = (IncrblobChannel *)Tcl_Alloc(sizeof(IncrblobChannel)); + p->iSeek = 0; + p->pBlob = pBlob; + + sqlite3_snprintf(sizeof(zChannel), zChannel, "incrblob_%d", ++count); + p->channel = Tcl_CreateChannel(&IncrblobChannelType, zChannel, p, flags); + Tcl_RegisterChannel(interp, p->channel); + + /* Link the new channel into the SqliteDb.pIncrblob list. */ + p->pNext = pDb->pIncrblob; + p->pPrev = 0; + if( p->pNext ){ + p->pNext->pPrev = p; + } + pDb->pIncrblob = p; + p->pDb = pDb; + + Tcl_SetResult(interp, (char *)Tcl_GetChannelName(p->channel), TCL_VOLATILE); + return TCL_OK; +} +#else /* else clause for "#ifndef SQLITE_OMIT_INCRBLOB" */ + #define closeIncrblobChannels(pDb) +#endif + +/* +** Look at the script prefix in pCmd. We will be executing this script +** after first appending one or more arguments. This routine analyzes +** the script to see if it is safe to use Tcl_EvalObjv() on the script +** rather than the more general Tcl_EvalEx(). Tcl_EvalObjv() is much +** faster. +** +** Scripts that are safe to use with Tcl_EvalObjv() consists of a +** command name followed by zero or more arguments with no [...] or $ +** or {...} or ; to be seen anywhere. Most callback scripts consist +** of just a single procedure name and they meet this requirement. +*/ +static int safeToUseEvalObjv(Tcl_Interp *interp, Tcl_Obj *pCmd){ + /* We could try to do something with Tcl_Parse(). But we will instead + ** just do a search for forbidden characters. If any of the forbidden + ** characters appear in pCmd, we will report the string as unsafe. + */ + const char *z; + int n; + z = Tcl_GetStringFromObj(pCmd, &n); + while( n-- > 0 ){ + int c = *(z++); + if( c=='$' || c=='[' || c==';' ) return 0; + } + return 1; +} + +/* +** Find an SqlFunc structure with the given name. Or create a new +** one if an existing one cannot be found. Return a pointer to the +** structure. +*/ +static SqlFunc *findSqlFunc(SqliteDb *pDb, const char *zName){ + SqlFunc *p, *pNew; + int nName = strlen30(zName); + pNew = (SqlFunc*)Tcl_Alloc( sizeof(*pNew) + nName + 1 ); + pNew->zName = (char*)&pNew[1]; + memcpy(pNew->zName, zName, nName+1); + for(p=pDb->pFunc; p; p=p->pNext){ + if( sqlite3_stricmp(p->zName, pNew->zName)==0 ){ + Tcl_Free((char*)pNew); + return p; + } + } + pNew->interp = pDb->interp; + pNew->pDb = pDb; + pNew->pScript = 0; + pNew->pNext = pDb->pFunc; + pDb->pFunc = pNew; + return pNew; +} + +/* +** Free a single SqlPreparedStmt object. +*/ +static void dbFreeStmt(SqlPreparedStmt *pStmt){ +#ifdef SQLITE_TEST + if( sqlite3_sql(pStmt->pStmt)==0 ){ + Tcl_Free((char *)pStmt->zSql); + } +#endif + sqlite3_finalize(pStmt->pStmt); + Tcl_Free((char *)pStmt); +} + +/* +** Finalize and free a list of prepared statements +*/ +static void flushStmtCache(SqliteDb *pDb){ + SqlPreparedStmt *pPreStmt; + SqlPreparedStmt *pNext; + + for(pPreStmt = pDb->stmtList; pPreStmt; pPreStmt=pNext){ + pNext = pPreStmt->pNext; + dbFreeStmt(pPreStmt); + } + pDb->nStmt = 0; + pDb->stmtLast = 0; + pDb->stmtList = 0; +} + +/* +** Increment the reference counter on the SqliteDb object. The reference +** should be released by calling delDatabaseRef(). +*/ +static void addDatabaseRef(SqliteDb *pDb){ + pDb->nRef++; +} + +/* +** Decrement the reference counter associated with the SqliteDb object. +** If it reaches zero, delete the object. +*/ +static void delDatabaseRef(SqliteDb *pDb){ + assert( pDb->nRef>0 ); + pDb->nRef--; + if( pDb->nRef==0 ){ + flushStmtCache(pDb); + closeIncrblobChannels(pDb); + sqlite3_close(pDb->db); + while( pDb->pFunc ){ + SqlFunc *pFunc = pDb->pFunc; + pDb->pFunc = pFunc->pNext; + assert( pFunc->pDb==pDb ); + Tcl_DecrRefCount(pFunc->pScript); + Tcl_Free((char*)pFunc); + } + while( pDb->pCollate ){ + SqlCollate *pCollate = pDb->pCollate; + pDb->pCollate = pCollate->pNext; + Tcl_Free((char*)pCollate); + } + if( pDb->zBusy ){ + Tcl_Free(pDb->zBusy); + } + if( pDb->zTrace ){ + Tcl_Free(pDb->zTrace); + } + if( pDb->zTraceV2 ){ + Tcl_Free(pDb->zTraceV2); + } + if( pDb->zProfile ){ + Tcl_Free(pDb->zProfile); + } + if( pDb->zBindFallback ){ + Tcl_Free(pDb->zBindFallback); + } + if( pDb->zAuth ){ + Tcl_Free(pDb->zAuth); + } + if( pDb->zNull ){ + Tcl_Free(pDb->zNull); + } + if( pDb->pUpdateHook ){ + Tcl_DecrRefCount(pDb->pUpdateHook); + } + if( pDb->pPreUpdateHook ){ + Tcl_DecrRefCount(pDb->pPreUpdateHook); + } + if( pDb->pRollbackHook ){ + Tcl_DecrRefCount(pDb->pRollbackHook); + } + if( pDb->pWalHook ){ + Tcl_DecrRefCount(pDb->pWalHook); + } + if( pDb->pCollateNeeded ){ + Tcl_DecrRefCount(pDb->pCollateNeeded); + } + Tcl_Free((char*)pDb); + } +} + +/* +** TCL calls this procedure when an sqlite3 database command is +** deleted. +*/ +static void SQLITE_TCLAPI DbDeleteCmd(void *db){ + SqliteDb *pDb = (SqliteDb*)db; + delDatabaseRef(pDb); +} + +/* +** This routine is called when a database file is locked while trying +** to execute SQL. +*/ +static int DbBusyHandler(void *cd, int nTries){ + SqliteDb *pDb = (SqliteDb*)cd; + int rc; + char zVal[30]; + + sqlite3_snprintf(sizeof(zVal), zVal, "%d", nTries); + rc = Tcl_VarEval(pDb->interp, pDb->zBusy, " ", zVal, (char*)0); + if( rc!=TCL_OK || atoi(Tcl_GetStringResult(pDb->interp)) ){ + return 0; + } + return 1; +} + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK +/* +** This routine is invoked as the 'progress callback' for the database. +*/ +static int DbProgressHandler(void *cd){ + SqliteDb *pDb = (SqliteDb*)cd; + int rc; + + assert( pDb->zProgress ); + rc = Tcl_Eval(pDb->interp, pDb->zProgress); + if( rc!=TCL_OK || atoi(Tcl_GetStringResult(pDb->interp)) ){ + return 1; + } + return 0; +} +#endif + +#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) && \ + !defined(SQLITE_OMIT_DEPRECATED) +/* +** This routine is called by the SQLite trace handler whenever a new +** block of SQL is executed. The TCL script in pDb->zTrace is executed. +*/ +static void DbTraceHandler(void *cd, const char *zSql){ + SqliteDb *pDb = (SqliteDb*)cd; + Tcl_DString str; + + Tcl_DStringInit(&str); + Tcl_DStringAppend(&str, pDb->zTrace, -1); + Tcl_DStringAppendElement(&str, zSql); + Tcl_Eval(pDb->interp, Tcl_DStringValue(&str)); + Tcl_DStringFree(&str); + Tcl_ResetResult(pDb->interp); +} +#endif + +#ifndef SQLITE_OMIT_TRACE +/* +** This routine is called by the SQLite trace_v2 handler whenever a new +** supported event is generated. Unsupported event types are ignored. +** The TCL script in pDb->zTraceV2 is executed, with the arguments for +** the event appended to it (as list elements). +*/ +static int DbTraceV2Handler( + unsigned type, /* One of the SQLITE_TRACE_* event types. */ + void *cd, /* The original context data pointer. */ + void *pd, /* Primary event data, depends on event type. */ + void *xd /* Extra event data, depends on event type. */ +){ + SqliteDb *pDb = (SqliteDb*)cd; + Tcl_Obj *pCmd; + + switch( type ){ + case SQLITE_TRACE_STMT: { + sqlite3_stmt *pStmt = (sqlite3_stmt *)pd; + char *zSql = (char *)xd; + + pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1); + Tcl_IncrRefCount(pCmd); + Tcl_ListObjAppendElement(pDb->interp, pCmd, + Tcl_NewWideIntObj((Tcl_WideInt)(uptr)pStmt)); + Tcl_ListObjAppendElement(pDb->interp, pCmd, + Tcl_NewStringObj(zSql, -1)); + Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); + Tcl_ResetResult(pDb->interp); + break; + } + case SQLITE_TRACE_PROFILE: { + sqlite3_stmt *pStmt = (sqlite3_stmt *)pd; + sqlite3_int64 ns = *(sqlite3_int64*)xd; + + pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1); + Tcl_IncrRefCount(pCmd); + Tcl_ListObjAppendElement(pDb->interp, pCmd, + Tcl_NewWideIntObj((Tcl_WideInt)(uptr)pStmt)); + Tcl_ListObjAppendElement(pDb->interp, pCmd, + Tcl_NewWideIntObj((Tcl_WideInt)ns)); + Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); + Tcl_ResetResult(pDb->interp); + break; + } + case SQLITE_TRACE_ROW: { + sqlite3_stmt *pStmt = (sqlite3_stmt *)pd; + + pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1); + Tcl_IncrRefCount(pCmd); + Tcl_ListObjAppendElement(pDb->interp, pCmd, + Tcl_NewWideIntObj((Tcl_WideInt)(uptr)pStmt)); + Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); + Tcl_ResetResult(pDb->interp); + break; + } + case SQLITE_TRACE_CLOSE: { + sqlite3 *db = (sqlite3 *)pd; + + pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1); + Tcl_IncrRefCount(pCmd); + Tcl_ListObjAppendElement(pDb->interp, pCmd, + Tcl_NewWideIntObj((Tcl_WideInt)(uptr)db)); + Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); + Tcl_ResetResult(pDb->interp); + break; + } + } + return SQLITE_OK; +} +#endif + +#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) && \ + !defined(SQLITE_OMIT_DEPRECATED) +/* +** This routine is called by the SQLite profile handler after a statement +** SQL has executed. The TCL script in pDb->zProfile is evaluated. +*/ +static void DbProfileHandler(void *cd, const char *zSql, sqlite_uint64 tm){ + SqliteDb *pDb = (SqliteDb*)cd; + Tcl_DString str; + char zTm[100]; + + sqlite3_snprintf(sizeof(zTm)-1, zTm, "%lld", tm); + Tcl_DStringInit(&str); + Tcl_DStringAppend(&str, pDb->zProfile, -1); + Tcl_DStringAppendElement(&str, zSql); + Tcl_DStringAppendElement(&str, zTm); + Tcl_Eval(pDb->interp, Tcl_DStringValue(&str)); + Tcl_DStringFree(&str); + Tcl_ResetResult(pDb->interp); +} +#endif + +/* +** This routine is called when a transaction is committed. The +** TCL script in pDb->zCommit is executed. If it returns non-zero or +** if it throws an exception, the transaction is rolled back instead +** of being committed. +*/ +static int DbCommitHandler(void *cd){ + SqliteDb *pDb = (SqliteDb*)cd; + int rc; + + rc = Tcl_Eval(pDb->interp, pDb->zCommit); + if( rc!=TCL_OK || atoi(Tcl_GetStringResult(pDb->interp)) ){ + return 1; + } + return 0; +} + +static void DbRollbackHandler(void *clientData){ + SqliteDb *pDb = (SqliteDb*)clientData; + assert(pDb->pRollbackHook); + if( TCL_OK!=Tcl_EvalObjEx(pDb->interp, pDb->pRollbackHook, 0) ){ + Tcl_BackgroundError(pDb->interp); + } +} + +/* +** This procedure handles wal_hook callbacks. +*/ +static int DbWalHandler( + void *clientData, + sqlite3 *db, + const char *zDb, + int nEntry +){ + int ret = SQLITE_OK; + Tcl_Obj *p; + SqliteDb *pDb = (SqliteDb*)clientData; + Tcl_Interp *interp = pDb->interp; + assert(pDb->pWalHook); + + assert( db==pDb->db ); + p = Tcl_DuplicateObj(pDb->pWalHook); + Tcl_IncrRefCount(p); + Tcl_ListObjAppendElement(interp, p, Tcl_NewStringObj(zDb, -1)); + Tcl_ListObjAppendElement(interp, p, Tcl_NewIntObj(nEntry)); + if( TCL_OK!=Tcl_EvalObjEx(interp, p, 0) + || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &ret) + ){ + Tcl_BackgroundError(interp); + } + Tcl_DecrRefCount(p); + + return ret; +} + +#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) +static void setTestUnlockNotifyVars(Tcl_Interp *interp, int iArg, int nArg){ + char zBuf[64]; + sqlite3_snprintf(sizeof(zBuf), zBuf, "%d", iArg); + Tcl_SetVar(interp, "sqlite_unlock_notify_arg", zBuf, TCL_GLOBAL_ONLY); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%d", nArg); + Tcl_SetVar(interp, "sqlite_unlock_notify_argcount", zBuf, TCL_GLOBAL_ONLY); +} +#else +# define setTestUnlockNotifyVars(x,y,z) +#endif + +#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY +static void DbUnlockNotify(void **apArg, int nArg){ + int i; + for(i=0; i<nArg; i++){ + const int flags = (TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT); + SqliteDb *pDb = (SqliteDb *)apArg[i]; + setTestUnlockNotifyVars(pDb->interp, i, nArg); + assert( pDb->pUnlockNotify); + Tcl_EvalObjEx(pDb->interp, pDb->pUnlockNotify, flags); + Tcl_DecrRefCount(pDb->pUnlockNotify); + pDb->pUnlockNotify = 0; + } +} +#endif + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** Pre-update hook callback. +*/ +static void DbPreUpdateHandler( + void *p, + sqlite3 *db, + int op, + const char *zDb, + const char *zTbl, + sqlite_int64 iKey1, + sqlite_int64 iKey2 +){ + SqliteDb *pDb = (SqliteDb *)p; + Tcl_Obj *pCmd; + static const char *azStr[] = {"DELETE", "INSERT", "UPDATE"}; + + assert( (SQLITE_DELETE-1)/9 == 0 ); + assert( (SQLITE_INSERT-1)/9 == 1 ); + assert( (SQLITE_UPDATE-1)/9 == 2 ); + assert( pDb->pPreUpdateHook ); + assert( db==pDb->db ); + assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE ); + + pCmd = Tcl_DuplicateObj(pDb->pPreUpdateHook); + Tcl_IncrRefCount(pCmd); + Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(azStr[(op-1)/9], -1)); + Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zDb, -1)); + Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zTbl, -1)); + Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(iKey1)); + Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(iKey2)); + Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +static void DbUpdateHandler( + void *p, + int op, + const char *zDb, + const char *zTbl, + sqlite_int64 rowid +){ + SqliteDb *pDb = (SqliteDb *)p; + Tcl_Obj *pCmd; + static const char *azStr[] = {"DELETE", "INSERT", "UPDATE"}; + + assert( (SQLITE_DELETE-1)/9 == 0 ); + assert( (SQLITE_INSERT-1)/9 == 1 ); + assert( (SQLITE_UPDATE-1)/9 == 2 ); + + assert( pDb->pUpdateHook ); + assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE ); + + pCmd = Tcl_DuplicateObj(pDb->pUpdateHook); + Tcl_IncrRefCount(pCmd); + Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(azStr[(op-1)/9], -1)); + Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zDb, -1)); + Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zTbl, -1)); + Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(rowid)); + Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); +} + +static void tclCollateNeeded( + void *pCtx, + sqlite3 *db, + int enc, + const char *zName +){ + SqliteDb *pDb = (SqliteDb *)pCtx; + Tcl_Obj *pScript = Tcl_DuplicateObj(pDb->pCollateNeeded); + Tcl_IncrRefCount(pScript); + Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj(zName, -1)); + Tcl_EvalObjEx(pDb->interp, pScript, 0); + Tcl_DecrRefCount(pScript); +} + +/* +** This routine is called to evaluate an SQL collation function implemented +** using TCL script. +*/ +static int tclSqlCollate( + void *pCtx, + int nA, + const void *zA, + int nB, + const void *zB +){ + SqlCollate *p = (SqlCollate *)pCtx; + Tcl_Obj *pCmd; + + pCmd = Tcl_NewStringObj(p->zScript, -1); + Tcl_IncrRefCount(pCmd); + Tcl_ListObjAppendElement(p->interp, pCmd, Tcl_NewStringObj(zA, nA)); + Tcl_ListObjAppendElement(p->interp, pCmd, Tcl_NewStringObj(zB, nB)); + Tcl_EvalObjEx(p->interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); + return (atoi(Tcl_GetStringResult(p->interp))); +} + +/* +** This routine is called to evaluate an SQL function implemented +** using TCL script. +*/ +static void tclSqlFunc(sqlite3_context *context, int argc, sqlite3_value**argv){ + SqlFunc *p = sqlite3_user_data(context); + Tcl_Obj *pCmd; + int i; + int rc; + + if( argc==0 ){ + /* If there are no arguments to the function, call Tcl_EvalObjEx on the + ** script object directly. This allows the TCL compiler to generate + ** bytecode for the command on the first invocation and thus make + ** subsequent invocations much faster. */ + pCmd = p->pScript; + Tcl_IncrRefCount(pCmd); + rc = Tcl_EvalObjEx(p->interp, pCmd, 0); + Tcl_DecrRefCount(pCmd); + }else{ + /* If there are arguments to the function, make a shallow copy of the + ** script object, lappend the arguments, then evaluate the copy. + ** + ** By "shallow" copy, we mean only the outer list Tcl_Obj is duplicated. + ** The new Tcl_Obj contains pointers to the original list elements. + ** That way, when Tcl_EvalObjv() is run and shimmers the first element + ** of the list to tclCmdNameType, that alternate representation will + ** be preserved and reused on the next invocation. + */ + Tcl_Obj **aArg; + int nArg; + if( Tcl_ListObjGetElements(p->interp, p->pScript, &nArg, &aArg) ){ + sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1); + return; + } + pCmd = Tcl_NewListObj(nArg, aArg); + Tcl_IncrRefCount(pCmd); + for(i=0; i<argc; i++){ + sqlite3_value *pIn = argv[i]; + Tcl_Obj *pVal; + + /* Set pVal to contain the i'th column of this row. */ + switch( sqlite3_value_type(pIn) ){ + case SQLITE_BLOB: { + int bytes = sqlite3_value_bytes(pIn); + pVal = Tcl_NewByteArrayObj(sqlite3_value_blob(pIn), bytes); + break; + } + case SQLITE_INTEGER: { + sqlite_int64 v = sqlite3_value_int64(pIn); + if( v>=-2147483647 && v<=2147483647 ){ + pVal = Tcl_NewIntObj((int)v); + }else{ + pVal = Tcl_NewWideIntObj(v); + } + break; + } + case SQLITE_FLOAT: { + double r = sqlite3_value_double(pIn); + pVal = Tcl_NewDoubleObj(r); + break; + } + case SQLITE_NULL: { + pVal = Tcl_NewStringObj(p->pDb->zNull, -1); + break; + } + default: { + int bytes = sqlite3_value_bytes(pIn); + pVal = Tcl_NewStringObj((char *)sqlite3_value_text(pIn), bytes); + break; + } + } + rc = Tcl_ListObjAppendElement(p->interp, pCmd, pVal); + if( rc ){ + Tcl_DecrRefCount(pCmd); + sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1); + return; + } + } + if( !p->useEvalObjv ){ + /* Tcl_EvalObjEx() will automatically call Tcl_EvalObjv() if pCmd + ** is a list without a string representation. To prevent this from + ** happening, make sure pCmd has a valid string representation */ + Tcl_GetString(pCmd); + } + rc = Tcl_EvalObjEx(p->interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); + } + + if( rc && rc!=TCL_RETURN ){ + sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1); + }else{ + Tcl_Obj *pVar = Tcl_GetObjResult(p->interp); + int n; + u8 *data; + const char *zType = (pVar->typePtr ? pVar->typePtr->name : ""); + char c = zType[0]; + int eType = p->eType; + + if( eType==SQLITE_NULL ){ + if( c=='b' && strcmp(zType,"bytearray")==0 && pVar->bytes==0 ){ + /* Only return a BLOB type if the Tcl variable is a bytearray and + ** has no string representation. */ + eType = SQLITE_BLOB; + }else if( (c=='b' && strcmp(zType,"boolean")==0) + || (c=='w' && strcmp(zType,"wideInt")==0) + || (c=='i' && strcmp(zType,"int")==0) + ){ + eType = SQLITE_INTEGER; + }else if( c=='d' && strcmp(zType,"double")==0 ){ + eType = SQLITE_FLOAT; + }else{ + eType = SQLITE_TEXT; + } + } + + switch( eType ){ + case SQLITE_BLOB: { + data = Tcl_GetByteArrayFromObj(pVar, &n); + sqlite3_result_blob(context, data, n, SQLITE_TRANSIENT); + break; + } + case SQLITE_INTEGER: { + Tcl_WideInt v; + if( TCL_OK==Tcl_GetWideIntFromObj(0, pVar, &v) ){ + sqlite3_result_int64(context, v); + break; + } + /* fall-through */ + } + case SQLITE_FLOAT: { + double r; + if( TCL_OK==Tcl_GetDoubleFromObj(0, pVar, &r) ){ + sqlite3_result_double(context, r); + break; + } + /* fall-through */ + } + default: { + data = (unsigned char *)Tcl_GetStringFromObj(pVar, &n); + sqlite3_result_text(context, (char *)data, n, SQLITE_TRANSIENT); + break; + } + } + + } +} + +#ifndef SQLITE_OMIT_AUTHORIZATION +/* +** This is the authentication function. It appends the authentication +** type code and the two arguments to zCmd[] then invokes the result +** on the interpreter. The reply is examined to determine if the +** authentication fails or succeeds. +*/ +static int auth_callback( + void *pArg, + int code, + const char *zArg1, + const char *zArg2, + const char *zArg3, + const char *zArg4 +#ifdef SQLITE_USER_AUTHENTICATION + ,const char *zArg5 +#endif +){ + const char *zCode; + Tcl_DString str; + int rc; + const char *zReply; + /* EVIDENCE-OF: R-38590-62769 The first parameter to the authorizer + ** callback is a copy of the third parameter to the + ** sqlite3_set_authorizer() interface. + */ + SqliteDb *pDb = (SqliteDb*)pArg; + if( pDb->disableAuth ) return SQLITE_OK; + + /* EVIDENCE-OF: R-56518-44310 The second parameter to the callback is an + ** integer action code that specifies the particular action to be + ** authorized. */ + switch( code ){ + case SQLITE_COPY : zCode="SQLITE_COPY"; break; + case SQLITE_CREATE_INDEX : zCode="SQLITE_CREATE_INDEX"; break; + case SQLITE_CREATE_TABLE : zCode="SQLITE_CREATE_TABLE"; break; + case SQLITE_CREATE_TEMP_INDEX : zCode="SQLITE_CREATE_TEMP_INDEX"; break; + case SQLITE_CREATE_TEMP_TABLE : zCode="SQLITE_CREATE_TEMP_TABLE"; break; + case SQLITE_CREATE_TEMP_TRIGGER: zCode="SQLITE_CREATE_TEMP_TRIGGER"; break; + case SQLITE_CREATE_TEMP_VIEW : zCode="SQLITE_CREATE_TEMP_VIEW"; break; + case SQLITE_CREATE_TRIGGER : zCode="SQLITE_CREATE_TRIGGER"; break; + case SQLITE_CREATE_VIEW : zCode="SQLITE_CREATE_VIEW"; break; + case SQLITE_DELETE : zCode="SQLITE_DELETE"; break; + case SQLITE_DROP_INDEX : zCode="SQLITE_DROP_INDEX"; break; + case SQLITE_DROP_TABLE : zCode="SQLITE_DROP_TABLE"; break; + case SQLITE_DROP_TEMP_INDEX : zCode="SQLITE_DROP_TEMP_INDEX"; break; + case SQLITE_DROP_TEMP_TABLE : zCode="SQLITE_DROP_TEMP_TABLE"; break; + case SQLITE_DROP_TEMP_TRIGGER : zCode="SQLITE_DROP_TEMP_TRIGGER"; break; + case SQLITE_DROP_TEMP_VIEW : zCode="SQLITE_DROP_TEMP_VIEW"; break; + case SQLITE_DROP_TRIGGER : zCode="SQLITE_DROP_TRIGGER"; break; + case SQLITE_DROP_VIEW : zCode="SQLITE_DROP_VIEW"; break; + case SQLITE_INSERT : zCode="SQLITE_INSERT"; break; + case SQLITE_PRAGMA : zCode="SQLITE_PRAGMA"; break; + case SQLITE_READ : zCode="SQLITE_READ"; break; + case SQLITE_SELECT : zCode="SQLITE_SELECT"; break; + case SQLITE_TRANSACTION : zCode="SQLITE_TRANSACTION"; break; + case SQLITE_UPDATE : zCode="SQLITE_UPDATE"; break; + case SQLITE_ATTACH : zCode="SQLITE_ATTACH"; break; + case SQLITE_DETACH : zCode="SQLITE_DETACH"; break; + case SQLITE_ALTER_TABLE : zCode="SQLITE_ALTER_TABLE"; break; + case SQLITE_REINDEX : zCode="SQLITE_REINDEX"; break; + case SQLITE_ANALYZE : zCode="SQLITE_ANALYZE"; break; + case SQLITE_CREATE_VTABLE : zCode="SQLITE_CREATE_VTABLE"; break; + case SQLITE_DROP_VTABLE : zCode="SQLITE_DROP_VTABLE"; break; + case SQLITE_FUNCTION : zCode="SQLITE_FUNCTION"; break; + case SQLITE_SAVEPOINT : zCode="SQLITE_SAVEPOINT"; break; + case SQLITE_RECURSIVE : zCode="SQLITE_RECURSIVE"; break; + default : zCode="????"; break; + } + Tcl_DStringInit(&str); + Tcl_DStringAppend(&str, pDb->zAuth, -1); + Tcl_DStringAppendElement(&str, zCode); + Tcl_DStringAppendElement(&str, zArg1 ? zArg1 : ""); + Tcl_DStringAppendElement(&str, zArg2 ? zArg2 : ""); + Tcl_DStringAppendElement(&str, zArg3 ? zArg3 : ""); + Tcl_DStringAppendElement(&str, zArg4 ? zArg4 : ""); +#ifdef SQLITE_USER_AUTHENTICATION + Tcl_DStringAppendElement(&str, zArg5 ? zArg5 : ""); +#endif + rc = Tcl_GlobalEval(pDb->interp, Tcl_DStringValue(&str)); + Tcl_DStringFree(&str); + zReply = rc==TCL_OK ? Tcl_GetStringResult(pDb->interp) : "SQLITE_DENY"; + if( strcmp(zReply,"SQLITE_OK")==0 ){ + rc = SQLITE_OK; + }else if( strcmp(zReply,"SQLITE_DENY")==0 ){ + rc = SQLITE_DENY; + }else if( strcmp(zReply,"SQLITE_IGNORE")==0 ){ + rc = SQLITE_IGNORE; + }else{ + rc = 999; + } + return rc; +} +#endif /* SQLITE_OMIT_AUTHORIZATION */ + +/* +** This routine reads a line of text from FILE in, stores +** the text in memory obtained from malloc() and returns a pointer +** to the text. NULL is returned at end of file, or if malloc() +** fails. +** +** The interface is like "readline" but no command-line editing +** is done. +** +** copied from shell.c from '.import' command +*/ +static char *local_getline(char *zPrompt, FILE *in){ + char *zLine; + int nLine; + int n; + + nLine = 100; + zLine = malloc( nLine ); + if( zLine==0 ) return 0; + n = 0; + while( 1 ){ + if( n+100>nLine ){ + nLine = nLine*2 + 100; + zLine = realloc(zLine, nLine); + if( zLine==0 ) return 0; + } + if( fgets(&zLine[n], nLine - n, in)==0 ){ + if( n==0 ){ + free(zLine); + return 0; + } + zLine[n] = 0; + break; + } + while( zLine[n] ){ n++; } + if( n>0 && zLine[n-1]=='\n' ){ + n--; + zLine[n] = 0; + break; + } + } + zLine = realloc( zLine, n+1 ); + return zLine; +} + + +/* +** This function is part of the implementation of the command: +** +** $db transaction [-deferred|-immediate|-exclusive] SCRIPT +** +** It is invoked after evaluating the script SCRIPT to commit or rollback +** the transaction or savepoint opened by the [transaction] command. +*/ +static int SQLITE_TCLAPI DbTransPostCmd( + ClientData data[], /* data[0] is the Sqlite3Db* for $db */ + Tcl_Interp *interp, /* Tcl interpreter */ + int result /* Result of evaluating SCRIPT */ +){ + static const char *const azEnd[] = { + "RELEASE _tcl_transaction", /* rc==TCL_ERROR, nTransaction!=0 */ + "COMMIT", /* rc!=TCL_ERROR, nTransaction==0 */ + "ROLLBACK TO _tcl_transaction ; RELEASE _tcl_transaction", + "ROLLBACK" /* rc==TCL_ERROR, nTransaction==0 */ + }; + SqliteDb *pDb = (SqliteDb*)data[0]; + int rc = result; + const char *zEnd; + + pDb->nTransaction--; + zEnd = azEnd[(rc==TCL_ERROR)*2 + (pDb->nTransaction==0)]; + + pDb->disableAuth++; + if( sqlite3_exec(pDb->db, zEnd, 0, 0, 0) ){ + /* This is a tricky scenario to handle. The most likely cause of an + ** error is that the exec() above was an attempt to commit the + ** top-level transaction that returned SQLITE_BUSY. Or, less likely, + ** that an IO-error has occurred. In either case, throw a Tcl exception + ** and try to rollback the transaction. + ** + ** But it could also be that the user executed one or more BEGIN, + ** COMMIT, SAVEPOINT, RELEASE or ROLLBACK commands that are confusing + ** this method's logic. Not clear how this would be best handled. + */ + if( rc!=TCL_ERROR ){ + Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), (char*)0); + rc = TCL_ERROR; + } + sqlite3_exec(pDb->db, "ROLLBACK", 0, 0, 0); + } + pDb->disableAuth--; + + delDatabaseRef(pDb); + return rc; +} + +/* +** Unless SQLITE_TEST is defined, this function is a simple wrapper around +** sqlite3_prepare_v2(). If SQLITE_TEST is defined, then it uses either +** sqlite3_prepare_v2() or legacy interface sqlite3_prepare(), depending +** on whether or not the [db_use_legacy_prepare] command has been used to +** configure the connection. +*/ +static int dbPrepare( + SqliteDb *pDb, /* Database object */ + const char *zSql, /* SQL to compile */ + sqlite3_stmt **ppStmt, /* OUT: Prepared statement */ + const char **pzOut /* OUT: Pointer to next SQL statement */ +){ + unsigned int prepFlags = 0; +#ifdef SQLITE_TEST + if( pDb->bLegacyPrepare ){ + return sqlite3_prepare(pDb->db, zSql, -1, ppStmt, pzOut); + } +#endif + /* If the statement cache is large, use the SQLITE_PREPARE_PERSISTENT + ** flags, which uses less lookaside memory. But if the cache is small, + ** omit that flag to make full use of lookaside */ + if( pDb->maxStmt>5 ) prepFlags = SQLITE_PREPARE_PERSISTENT; + + return sqlite3_prepare_v3(pDb->db, zSql, -1, prepFlags, ppStmt, pzOut); +} + +/* +** Search the cache for a prepared-statement object that implements the +** first SQL statement in the buffer pointed to by parameter zIn. If +** no such prepared-statement can be found, allocate and prepare a new +** one. In either case, bind the current values of the relevant Tcl +** variables to any $var, :var or @var variables in the statement. Before +** returning, set *ppPreStmt to point to the prepared-statement object. +** +** Output parameter *pzOut is set to point to the next SQL statement in +** buffer zIn, or to the '\0' byte at the end of zIn if there is no +** next statement. +** +** If successful, TCL_OK is returned. Otherwise, TCL_ERROR is returned +** and an error message loaded into interpreter pDb->interp. +*/ +static int dbPrepareAndBind( + SqliteDb *pDb, /* Database object */ + char const *zIn, /* SQL to compile */ + char const **pzOut, /* OUT: Pointer to next SQL statement */ + SqlPreparedStmt **ppPreStmt /* OUT: Object used to cache statement */ +){ + const char *zSql = zIn; /* Pointer to first SQL statement in zIn */ + sqlite3_stmt *pStmt = 0; /* Prepared statement object */ + SqlPreparedStmt *pPreStmt; /* Pointer to cached statement */ + int nSql; /* Length of zSql in bytes */ + int nVar = 0; /* Number of variables in statement */ + int iParm = 0; /* Next free entry in apParm */ + char c; + int i; + int needResultReset = 0; /* Need to invoke Tcl_ResetResult() */ + int rc = SQLITE_OK; /* Value to return */ + Tcl_Interp *interp = pDb->interp; + + *ppPreStmt = 0; + + /* Trim spaces from the start of zSql and calculate the remaining length. */ + while( (c = zSql[0])==' ' || c=='\t' || c=='\r' || c=='\n' ){ zSql++; } + nSql = strlen30(zSql); + + for(pPreStmt = pDb->stmtList; pPreStmt; pPreStmt=pPreStmt->pNext){ + int n = pPreStmt->nSql; + if( nSql>=n + && memcmp(pPreStmt->zSql, zSql, n)==0 + && (zSql[n]==0 || zSql[n-1]==';') + ){ + pStmt = pPreStmt->pStmt; + *pzOut = &zSql[pPreStmt->nSql]; + + /* When a prepared statement is found, unlink it from the + ** cache list. It will later be added back to the beginning + ** of the cache list in order to implement LRU replacement. + */ + if( pPreStmt->pPrev ){ + pPreStmt->pPrev->pNext = pPreStmt->pNext; + }else{ + pDb->stmtList = pPreStmt->pNext; + } + if( pPreStmt->pNext ){ + pPreStmt->pNext->pPrev = pPreStmt->pPrev; + }else{ + pDb->stmtLast = pPreStmt->pPrev; + } + pDb->nStmt--; + nVar = sqlite3_bind_parameter_count(pStmt); + break; + } + } + + /* If no prepared statement was found. Compile the SQL text. Also allocate + ** a new SqlPreparedStmt structure. */ + if( pPreStmt==0 ){ + int nByte; + + if( SQLITE_OK!=dbPrepare(pDb, zSql, &pStmt, pzOut) ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3_errmsg(pDb->db), -1)); + return TCL_ERROR; + } + if( pStmt==0 ){ + if( SQLITE_OK!=sqlite3_errcode(pDb->db) ){ + /* A compile-time error in the statement. */ + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3_errmsg(pDb->db), -1)); + return TCL_ERROR; + }else{ + /* The statement was a no-op. Continue to the next statement + ** in the SQL string. + */ + return TCL_OK; + } + } + + assert( pPreStmt==0 ); + nVar = sqlite3_bind_parameter_count(pStmt); + nByte = sizeof(SqlPreparedStmt) + nVar*sizeof(Tcl_Obj *); + pPreStmt = (SqlPreparedStmt*)Tcl_Alloc(nByte); + memset(pPreStmt, 0, nByte); + + pPreStmt->pStmt = pStmt; + pPreStmt->nSql = (int)(*pzOut - zSql); + pPreStmt->zSql = sqlite3_sql(pStmt); + pPreStmt->apParm = (Tcl_Obj **)&pPreStmt[1]; +#ifdef SQLITE_TEST + if( pPreStmt->zSql==0 ){ + char *zCopy = Tcl_Alloc(pPreStmt->nSql + 1); + memcpy(zCopy, zSql, pPreStmt->nSql); + zCopy[pPreStmt->nSql] = '\0'; + pPreStmt->zSql = zCopy; + } +#endif + } + assert( pPreStmt ); + assert( strlen30(pPreStmt->zSql)==pPreStmt->nSql ); + assert( 0==memcmp(pPreStmt->zSql, zSql, pPreStmt->nSql) ); + + /* Bind values to parameters that begin with $ or : */ + for(i=1; i<=nVar; i++){ + const char *zVar = sqlite3_bind_parameter_name(pStmt, i); + if( zVar!=0 && (zVar[0]=='$' || zVar[0]==':' || zVar[0]=='@') ){ + Tcl_Obj *pVar = Tcl_GetVar2Ex(interp, &zVar[1], 0, 0); + if( pVar==0 && pDb->zBindFallback!=0 ){ + Tcl_Obj *pCmd; + int rx; + pCmd = Tcl_NewStringObj(pDb->zBindFallback, -1); + Tcl_IncrRefCount(pCmd); + Tcl_ListObjAppendElement(interp, pCmd, Tcl_NewStringObj(zVar,-1)); + if( needResultReset ) Tcl_ResetResult(interp); + needResultReset = 1; + rx = Tcl_EvalObjEx(interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); + if( rx==TCL_OK ){ + pVar = Tcl_GetObjResult(interp); + }else if( rx==TCL_ERROR ){ + rc = TCL_ERROR; + break; + }else{ + pVar = 0; + } + } + if( pVar ){ + int n; + u8 *data; + const char *zType = (pVar->typePtr ? pVar->typePtr->name : ""); + c = zType[0]; + if( zVar[0]=='@' || + (c=='b' && strcmp(zType,"bytearray")==0 && pVar->bytes==0) ){ + /* Load a BLOB type if the Tcl variable is a bytearray and + ** it has no string representation or the host + ** parameter name begins with "@". */ + data = Tcl_GetByteArrayFromObj(pVar, &n); + sqlite3_bind_blob(pStmt, i, data, n, SQLITE_STATIC); + Tcl_IncrRefCount(pVar); + pPreStmt->apParm[iParm++] = pVar; + }else if( c=='b' && strcmp(zType,"boolean")==0 ){ + Tcl_GetIntFromObj(interp, pVar, &n); + sqlite3_bind_int(pStmt, i, n); + }else if( c=='d' && strcmp(zType,"double")==0 ){ + double r; + Tcl_GetDoubleFromObj(interp, pVar, &r); + sqlite3_bind_double(pStmt, i, r); + }else if( (c=='w' && strcmp(zType,"wideInt")==0) || + (c=='i' && strcmp(zType,"int")==0) ){ + Tcl_WideInt v; + Tcl_GetWideIntFromObj(interp, pVar, &v); + sqlite3_bind_int64(pStmt, i, v); + }else{ + data = (unsigned char *)Tcl_GetStringFromObj(pVar, &n); + sqlite3_bind_text(pStmt, i, (char *)data, n, SQLITE_STATIC); + Tcl_IncrRefCount(pVar); + pPreStmt->apParm[iParm++] = pVar; + } + }else{ + sqlite3_bind_null(pStmt, i); + } + if( needResultReset ) Tcl_ResetResult(pDb->interp); + } + } + pPreStmt->nParm = iParm; + *ppPreStmt = pPreStmt; + if( needResultReset && rc==TCL_OK ) Tcl_ResetResult(pDb->interp); + + return rc; +} + +/* +** Release a statement reference obtained by calling dbPrepareAndBind(). +** There should be exactly one call to this function for each call to +** dbPrepareAndBind(). +** +** If the discard parameter is non-zero, then the statement is deleted +** immediately. Otherwise it is added to the LRU list and may be returned +** by a subsequent call to dbPrepareAndBind(). +*/ +static void dbReleaseStmt( + SqliteDb *pDb, /* Database handle */ + SqlPreparedStmt *pPreStmt, /* Prepared statement handle to release */ + int discard /* True to delete (not cache) the pPreStmt */ +){ + int i; + + /* Free the bound string and blob parameters */ + for(i=0; i<pPreStmt->nParm; i++){ + Tcl_DecrRefCount(pPreStmt->apParm[i]); + } + pPreStmt->nParm = 0; + + if( pDb->maxStmt<=0 || discard ){ + /* If the cache is turned off, deallocated the statement */ + dbFreeStmt(pPreStmt); + }else{ + /* Add the prepared statement to the beginning of the cache list. */ + pPreStmt->pNext = pDb->stmtList; + pPreStmt->pPrev = 0; + if( pDb->stmtList ){ + pDb->stmtList->pPrev = pPreStmt; + } + pDb->stmtList = pPreStmt; + if( pDb->stmtLast==0 ){ + assert( pDb->nStmt==0 ); + pDb->stmtLast = pPreStmt; + }else{ + assert( pDb->nStmt>0 ); + } + pDb->nStmt++; + + /* If we have too many statement in cache, remove the surplus from + ** the end of the cache list. */ + while( pDb->nStmt>pDb->maxStmt ){ + SqlPreparedStmt *pLast = pDb->stmtLast; + pDb->stmtLast = pLast->pPrev; + pDb->stmtLast->pNext = 0; + pDb->nStmt--; + dbFreeStmt(pLast); + } + } +} + +/* +** Structure used with dbEvalXXX() functions: +** +** dbEvalInit() +** dbEvalStep() +** dbEvalFinalize() +** dbEvalRowInfo() +** dbEvalColumnValue() +*/ +typedef struct DbEvalContext DbEvalContext; +struct DbEvalContext { + SqliteDb *pDb; /* Database handle */ + Tcl_Obj *pSql; /* Object holding string zSql */ + const char *zSql; /* Remaining SQL to execute */ + SqlPreparedStmt *pPreStmt; /* Current statement */ + int nCol; /* Number of columns returned by pStmt */ + int evalFlags; /* Flags used */ + Tcl_Obj *pArray; /* Name of array variable */ + Tcl_Obj **apColName; /* Array of column names */ +}; + +#define SQLITE_EVAL_WITHOUTNULLS 0x00001 /* Unset array(*) for NULL */ + +/* +** Release any cache of column names currently held as part of +** the DbEvalContext structure passed as the first argument. +*/ +static void dbReleaseColumnNames(DbEvalContext *p){ + if( p->apColName ){ + int i; + for(i=0; i<p->nCol; i++){ + Tcl_DecrRefCount(p->apColName[i]); + } + Tcl_Free((char *)p->apColName); + p->apColName = 0; + } + p->nCol = 0; +} + +/* +** Initialize a DbEvalContext structure. +** +** If pArray is not NULL, then it contains the name of a Tcl array +** variable. The "*" member of this array is set to a list containing +** the names of the columns returned by the statement as part of each +** call to dbEvalStep(), in order from left to right. e.g. if the names +** of the returned columns are a, b and c, it does the equivalent of the +** tcl command: +** +** set ${pArray}(*) {a b c} +*/ +static void dbEvalInit( + DbEvalContext *p, /* Pointer to structure to initialize */ + SqliteDb *pDb, /* Database handle */ + Tcl_Obj *pSql, /* Object containing SQL script */ + Tcl_Obj *pArray, /* Name of Tcl array to set (*) element of */ + int evalFlags /* Flags controlling evaluation */ +){ + memset(p, 0, sizeof(DbEvalContext)); + p->pDb = pDb; + p->zSql = Tcl_GetString(pSql); + p->pSql = pSql; + Tcl_IncrRefCount(pSql); + if( pArray ){ + p->pArray = pArray; + Tcl_IncrRefCount(pArray); + } + p->evalFlags = evalFlags; + addDatabaseRef(p->pDb); +} + +/* +** Obtain information about the row that the DbEvalContext passed as the +** first argument currently points to. +*/ +static void dbEvalRowInfo( + DbEvalContext *p, /* Evaluation context */ + int *pnCol, /* OUT: Number of column names */ + Tcl_Obj ***papColName /* OUT: Array of column names */ +){ + /* Compute column names */ + if( 0==p->apColName ){ + sqlite3_stmt *pStmt = p->pPreStmt->pStmt; + int i; /* Iterator variable */ + int nCol; /* Number of columns returned by pStmt */ + Tcl_Obj **apColName = 0; /* Array of column names */ + + p->nCol = nCol = sqlite3_column_count(pStmt); + if( nCol>0 && (papColName || p->pArray) ){ + apColName = (Tcl_Obj**)Tcl_Alloc( sizeof(Tcl_Obj*)*nCol ); + for(i=0; i<nCol; i++){ + apColName[i] = Tcl_NewStringObj(sqlite3_column_name(pStmt,i), -1); + Tcl_IncrRefCount(apColName[i]); + } + p->apColName = apColName; + } + + /* If results are being stored in an array variable, then create + ** the array(*) entry for that array + */ + if( p->pArray ){ + Tcl_Interp *interp = p->pDb->interp; + Tcl_Obj *pColList = Tcl_NewObj(); + Tcl_Obj *pStar = Tcl_NewStringObj("*", -1); + + for(i=0; i<nCol; i++){ + Tcl_ListObjAppendElement(interp, pColList, apColName[i]); + } + Tcl_IncrRefCount(pStar); + Tcl_ObjSetVar2(interp, p->pArray, pStar, pColList, 0); + Tcl_DecrRefCount(pStar); + } + } + + if( papColName ){ + *papColName = p->apColName; + } + if( pnCol ){ + *pnCol = p->nCol; + } +} + +/* +** Return one of TCL_OK, TCL_BREAK or TCL_ERROR. If TCL_ERROR is +** returned, then an error message is stored in the interpreter before +** returning. +** +** A return value of TCL_OK means there is a row of data available. The +** data may be accessed using dbEvalRowInfo() and dbEvalColumnValue(). This +** is analogous to a return of SQLITE_ROW from sqlite3_step(). If TCL_BREAK +** is returned, then the SQL script has finished executing and there are +** no further rows available. This is similar to SQLITE_DONE. +*/ +static int dbEvalStep(DbEvalContext *p){ + const char *zPrevSql = 0; /* Previous value of p->zSql */ + + while( p->zSql[0] || p->pPreStmt ){ + int rc; + if( p->pPreStmt==0 ){ + zPrevSql = (p->zSql==zPrevSql ? 0 : p->zSql); + rc = dbPrepareAndBind(p->pDb, p->zSql, &p->zSql, &p->pPreStmt); + if( rc!=TCL_OK ) return rc; + }else{ + int rcs; + SqliteDb *pDb = p->pDb; + SqlPreparedStmt *pPreStmt = p->pPreStmt; + sqlite3_stmt *pStmt = pPreStmt->pStmt; + + rcs = sqlite3_step(pStmt); + if( rcs==SQLITE_ROW ){ + return TCL_OK; + } + if( p->pArray ){ + dbEvalRowInfo(p, 0, 0); + } + rcs = sqlite3_reset(pStmt); + + pDb->nStep = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_FULLSCAN_STEP,1); + pDb->nSort = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_SORT,1); + pDb->nIndex = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_AUTOINDEX,1); + pDb->nVMStep = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_VM_STEP,1); + dbReleaseColumnNames(p); + p->pPreStmt = 0; + + if( rcs!=SQLITE_OK ){ + /* If a run-time error occurs, report the error and stop reading + ** the SQL. */ + dbReleaseStmt(pDb, pPreStmt, 1); +#if SQLITE_TEST + if( p->pDb->bLegacyPrepare && rcs==SQLITE_SCHEMA && zPrevSql ){ + /* If the runtime error was an SQLITE_SCHEMA, and the database + ** handle is configured to use the legacy sqlite3_prepare() + ** interface, retry prepare()/step() on the same SQL statement. + ** This only happens once. If there is a second SQLITE_SCHEMA + ** error, the error will be returned to the caller. */ + p->zSql = zPrevSql; + continue; + } +#endif + Tcl_SetObjResult(pDb->interp, + Tcl_NewStringObj(sqlite3_errmsg(pDb->db), -1)); + return TCL_ERROR; + }else{ + dbReleaseStmt(pDb, pPreStmt, 0); + } + } + } + + /* Finished */ + return TCL_BREAK; +} + +/* +** Free all resources currently held by the DbEvalContext structure passed +** as the first argument. There should be exactly one call to this function +** for each call to dbEvalInit(). +*/ +static void dbEvalFinalize(DbEvalContext *p){ + if( p->pPreStmt ){ + sqlite3_reset(p->pPreStmt->pStmt); + dbReleaseStmt(p->pDb, p->pPreStmt, 0); + p->pPreStmt = 0; + } + if( p->pArray ){ + Tcl_DecrRefCount(p->pArray); + p->pArray = 0; + } + Tcl_DecrRefCount(p->pSql); + dbReleaseColumnNames(p); + delDatabaseRef(p->pDb); +} + +/* +** Return a pointer to a Tcl_Obj structure with ref-count 0 that contains +** the value for the iCol'th column of the row currently pointed to by +** the DbEvalContext structure passed as the first argument. +*/ +static Tcl_Obj *dbEvalColumnValue(DbEvalContext *p, int iCol){ + sqlite3_stmt *pStmt = p->pPreStmt->pStmt; + switch( sqlite3_column_type(pStmt, iCol) ){ + case SQLITE_BLOB: { + int bytes = sqlite3_column_bytes(pStmt, iCol); + const char *zBlob = sqlite3_column_blob(pStmt, iCol); + if( !zBlob ) bytes = 0; + return Tcl_NewByteArrayObj((u8*)zBlob, bytes); + } + case SQLITE_INTEGER: { + sqlite_int64 v = sqlite3_column_int64(pStmt, iCol); + if( v>=-2147483647 && v<=2147483647 ){ + return Tcl_NewIntObj((int)v); + }else{ + return Tcl_NewWideIntObj(v); + } + } + case SQLITE_FLOAT: { + return Tcl_NewDoubleObj(sqlite3_column_double(pStmt, iCol)); + } + case SQLITE_NULL: { + return Tcl_NewStringObj(p->pDb->zNull, -1); + } + } + + return Tcl_NewStringObj((char*)sqlite3_column_text(pStmt, iCol), -1); +} + +/* +** If using Tcl version 8.6 or greater, use the NR functions to avoid +** recursive evaluation of scripts by the [db eval] and [db trans] +** commands. Even if the headers used while compiling the extension +** are 8.6 or newer, the code still tests the Tcl version at runtime. +** This allows stubs-enabled builds to be used with older Tcl libraries. +*/ +#if TCL_MAJOR_VERSION>8 || (TCL_MAJOR_VERSION==8 && TCL_MINOR_VERSION>=6) +# define SQLITE_TCL_NRE 1 +static int DbUseNre(void){ + int major, minor; + Tcl_GetVersion(&major, &minor, 0, 0); + return( (major==8 && minor>=6) || major>8 ); +} +#else +/* +** Compiling using headers earlier than 8.6. In this case NR cannot be +** used, so DbUseNre() to always return zero. Add #defines for the other +** Tcl_NRxxx() functions to prevent them from causing compilation errors, +** even though the only invocations of them are within conditional blocks +** of the form: +** +** if( DbUseNre() ) { ... } +*/ +# define SQLITE_TCL_NRE 0 +# define DbUseNre() 0 +# define Tcl_NRAddCallback(a,b,c,d,e,f) (void)0 +# define Tcl_NREvalObj(a,b,c) 0 +# define Tcl_NRCreateCommand(a,b,c,d,e,f) (void)0 +#endif + +/* +** This function is part of the implementation of the command: +** +** $db eval SQL ?ARRAYNAME? SCRIPT +*/ +static int SQLITE_TCLAPI DbEvalNextCmd( + ClientData data[], /* data[0] is the (DbEvalContext*) */ + Tcl_Interp *interp, /* Tcl interpreter */ + int result /* Result so far */ +){ + int rc = result; /* Return code */ + + /* The first element of the data[] array is a pointer to a DbEvalContext + ** structure allocated using Tcl_Alloc(). The second element of data[] + ** is a pointer to a Tcl_Obj containing the script to run for each row + ** returned by the queries encapsulated in data[0]. */ + DbEvalContext *p = (DbEvalContext *)data[0]; + Tcl_Obj *pScript = (Tcl_Obj *)data[1]; + Tcl_Obj *pArray = p->pArray; + + while( (rc==TCL_OK || rc==TCL_CONTINUE) && TCL_OK==(rc = dbEvalStep(p)) ){ + int i; + int nCol; + Tcl_Obj **apColName; + dbEvalRowInfo(p, &nCol, &apColName); + for(i=0; i<nCol; i++){ + if( pArray==0 ){ + Tcl_ObjSetVar2(interp, apColName[i], 0, dbEvalColumnValue(p,i), 0); + }else if( (p->evalFlags & SQLITE_EVAL_WITHOUTNULLS)!=0 + && sqlite3_column_type(p->pPreStmt->pStmt, i)==SQLITE_NULL + ){ + Tcl_UnsetVar2(interp, Tcl_GetString(pArray), + Tcl_GetString(apColName[i]), 0); + }else{ + Tcl_ObjSetVar2(interp, pArray, apColName[i], dbEvalColumnValue(p,i), 0); + } + } + + /* The required interpreter variables are now populated with the data + ** from the current row. If using NRE, schedule callbacks to evaluate + ** script pScript, then to invoke this function again to fetch the next + ** row (or clean up if there is no next row or the script throws an + ** exception). After scheduling the callbacks, return control to the + ** caller. + ** + ** If not using NRE, evaluate pScript directly and continue with the + ** next iteration of this while(...) loop. */ + if( DbUseNre() ){ + Tcl_NRAddCallback(interp, DbEvalNextCmd, (void*)p, (void*)pScript, 0, 0); + return Tcl_NREvalObj(interp, pScript, 0); + }else{ + rc = Tcl_EvalObjEx(interp, pScript, 0); + } + } + + Tcl_DecrRefCount(pScript); + dbEvalFinalize(p); + Tcl_Free((char *)p); + + if( rc==TCL_OK || rc==TCL_BREAK ){ + Tcl_ResetResult(interp); + rc = TCL_OK; + } + return rc; +} + +/* +** This function is used by the implementations of the following database +** handle sub-commands: +** +** $db update_hook ?SCRIPT? +** $db wal_hook ?SCRIPT? +** $db commit_hook ?SCRIPT? +** $db preupdate hook ?SCRIPT? +*/ +static void DbHookCmd( + Tcl_Interp *interp, /* Tcl interpreter */ + SqliteDb *pDb, /* Database handle */ + Tcl_Obj *pArg, /* SCRIPT argument (or NULL) */ + Tcl_Obj **ppHook /* Pointer to member of SqliteDb */ +){ + sqlite3 *db = pDb->db; + + if( *ppHook ){ + Tcl_SetObjResult(interp, *ppHook); + if( pArg ){ + Tcl_DecrRefCount(*ppHook); + *ppHook = 0; + } + } + if( pArg ){ + assert( !(*ppHook) ); + if( Tcl_GetCharLength(pArg)>0 ){ + *ppHook = pArg; + Tcl_IncrRefCount(*ppHook); + } + } + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + sqlite3_preupdate_hook(db, (pDb->pPreUpdateHook?DbPreUpdateHandler:0), pDb); +#endif + sqlite3_update_hook(db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb); + sqlite3_rollback_hook(db, (pDb->pRollbackHook?DbRollbackHandler:0), pDb); + sqlite3_wal_hook(db, (pDb->pWalHook?DbWalHandler:0), pDb); +} + +/* +** The "sqlite" command below creates a new Tcl command for each +** connection it opens to an SQLite database. This routine is invoked +** whenever one of those connection-specific commands is executed +** in Tcl. For example, if you run Tcl code like this: +** +** sqlite3 db1 "my_database" +** db1 close +** +** The first command opens a connection to the "my_database" database +** and calls that connection "db1". The second command causes this +** subroutine to be invoked. +*/ +static int SQLITE_TCLAPI DbObjCmd( + void *cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const*objv +){ + SqliteDb *pDb = (SqliteDb*)cd; + int choice; + int rc = TCL_OK; + static const char *DB_strs[] = { + "authorizer", "backup", "bind_fallback", + "busy", "cache", "changes", + "close", "collate", "collation_needed", + "commit_hook", "complete", "config", + "copy", "deserialize", "enable_load_extension", + "errorcode", "erroroffset", "eval", + "exists", "function", "incrblob", + "interrupt", "last_insert_rowid", "nullvalue", + "onecolumn", "preupdate", "profile", + "progress", "rekey", "restore", + "rollback_hook", "serialize", "status", + "timeout", "total_changes", "trace", + "trace_v2", "transaction", "unlock_notify", + "update_hook", "version", "wal_hook", + 0 + }; + enum DB_enum { + DB_AUTHORIZER, DB_BACKUP, DB_BIND_FALLBACK, + DB_BUSY, DB_CACHE, DB_CHANGES, + DB_CLOSE, DB_COLLATE, DB_COLLATION_NEEDED, + DB_COMMIT_HOOK, DB_COMPLETE, DB_CONFIG, + DB_COPY, DB_DESERIALIZE, DB_ENABLE_LOAD_EXTENSION, + DB_ERRORCODE, DB_ERROROFFSET, DB_EVAL, + DB_EXISTS, DB_FUNCTION, DB_INCRBLOB, + DB_INTERRUPT, DB_LAST_INSERT_ROWID, DB_NULLVALUE, + DB_ONECOLUMN, DB_PREUPDATE, DB_PROFILE, + DB_PROGRESS, DB_REKEY, DB_RESTORE, + DB_ROLLBACK_HOOK, DB_SERIALIZE, DB_STATUS, + DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE, + DB_TRACE_V2, DB_TRANSACTION, DB_UNLOCK_NOTIFY, + DB_UPDATE_HOOK, DB_VERSION, DB_WAL_HOOK, + }; + /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */ + + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); + return TCL_ERROR; + } + if( Tcl_GetIndexFromObj(interp, objv[1], DB_strs, "option", 0, &choice) ){ + return TCL_ERROR; + } + + switch( (enum DB_enum)choice ){ + + /* $db authorizer ?CALLBACK? + ** + ** Invoke the given callback to authorize each SQL operation as it is + ** compiled. 5 arguments are appended to the callback before it is + ** invoked: + ** + ** (1) The authorization type (ex: SQLITE_CREATE_TABLE, SQLITE_INSERT, ...) + ** (2) First descriptive name (depends on authorization type) + ** (3) Second descriptive name + ** (4) Name of the database (ex: "main", "temp") + ** (5) Name of trigger that is doing the access + ** + ** The callback should return on of the following strings: SQLITE_OK, + ** SQLITE_IGNORE, or SQLITE_DENY. Any other return value is an error. + ** + ** If this method is invoked with no arguments, the current authorization + ** callback string is returned. + */ + case DB_AUTHORIZER: { +#ifdef SQLITE_OMIT_AUTHORIZATION + Tcl_AppendResult(interp, "authorization not available in this build", + (char*)0); + return TCL_ERROR; +#else + if( objc>3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?"); + return TCL_ERROR; + }else if( objc==2 ){ + if( pDb->zAuth ){ + Tcl_AppendResult(interp, pDb->zAuth, (char*)0); + } + }else{ + char *zAuth; + int len; + if( pDb->zAuth ){ + Tcl_Free(pDb->zAuth); + } + zAuth = Tcl_GetStringFromObj(objv[2], &len); + if( zAuth && len>0 ){ + pDb->zAuth = Tcl_Alloc( len + 1 ); + memcpy(pDb->zAuth, zAuth, len+1); + }else{ + pDb->zAuth = 0; + } + if( pDb->zAuth ){ + typedef int (*sqlite3_auth_cb)( + void*,int,const char*,const char*, + const char*,const char*); + pDb->interp = interp; + sqlite3_set_authorizer(pDb->db,(sqlite3_auth_cb)auth_callback,pDb); + }else{ + sqlite3_set_authorizer(pDb->db, 0, 0); + } + } +#endif + break; + } + + /* $db backup ?DATABASE? FILENAME + ** + ** Open or create a database file named FILENAME. Transfer the + ** content of local database DATABASE (default: "main") into the + ** FILENAME database. + */ + case DB_BACKUP: { + const char *zDestFile; + const char *zSrcDb; + sqlite3 *pDest; + sqlite3_backup *pBackup; + + if( objc==3 ){ + zSrcDb = "main"; + zDestFile = Tcl_GetString(objv[2]); + }else if( objc==4 ){ + zSrcDb = Tcl_GetString(objv[2]); + zDestFile = Tcl_GetString(objv[3]); + }else{ + Tcl_WrongNumArgs(interp, 2, objv, "?DATABASE? FILENAME"); + return TCL_ERROR; + } + rc = sqlite3_open_v2(zDestFile, &pDest, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE| pDb->openFlags, 0); + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp, "cannot open target database: ", + sqlite3_errmsg(pDest), (char*)0); + sqlite3_close(pDest); + return TCL_ERROR; + } + pBackup = sqlite3_backup_init(pDest, "main", pDb->db, zSrcDb); + if( pBackup==0 ){ + Tcl_AppendResult(interp, "backup failed: ", + sqlite3_errmsg(pDest), (char*)0); + sqlite3_close(pDest); + return TCL_ERROR; + } + while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){} + sqlite3_backup_finish(pBackup); + if( rc==SQLITE_DONE ){ + rc = TCL_OK; + }else{ + Tcl_AppendResult(interp, "backup failed: ", + sqlite3_errmsg(pDest), (char*)0); + rc = TCL_ERROR; + } + sqlite3_close(pDest); + break; + } + + /* $db bind_fallback ?CALLBACK? + ** + ** When resolving bind parameters in an SQL statement, if the parameter + ** cannot be associated with a TCL variable then invoke CALLBACK with a + ** single argument that is the name of the parameter and use the return + ** value of the CALLBACK as the binding. If CALLBACK returns something + ** other than TCL_OK or TCL_ERROR then bind a NULL. + ** + ** If CALLBACK is an empty string, then revert to the default behavior + ** which is to set the binding to NULL. + ** + ** If CALLBACK returns an error, that causes the statement execution to + ** abort. Hence, to configure a connection so that it throws an error + ** on an attempt to bind an unknown variable, do something like this: + ** + ** proc bind_error {name} {error "no such variable: $name"} + ** db bind_fallback bind_error + */ + case DB_BIND_FALLBACK: { + if( objc>3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?"); + return TCL_ERROR; + }else if( objc==2 ){ + if( pDb->zBindFallback ){ + Tcl_AppendResult(interp, pDb->zBindFallback, (char*)0); + } + }else{ + char *zCallback; + int len; + if( pDb->zBindFallback ){ + Tcl_Free(pDb->zBindFallback); + } + zCallback = Tcl_GetStringFromObj(objv[2], &len); + if( zCallback && len>0 ){ + pDb->zBindFallback = Tcl_Alloc( len + 1 ); + memcpy(pDb->zBindFallback, zCallback, len+1); + }else{ + pDb->zBindFallback = 0; + } + } + break; + } + + /* $db busy ?CALLBACK? + ** + ** Invoke the given callback if an SQL statement attempts to open + ** a locked database file. + */ + case DB_BUSY: { + if( objc>3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "CALLBACK"); + return TCL_ERROR; + }else if( objc==2 ){ + if( pDb->zBusy ){ + Tcl_AppendResult(interp, pDb->zBusy, (char*)0); + } + }else{ + char *zBusy; + int len; + if( pDb->zBusy ){ + Tcl_Free(pDb->zBusy); + } + zBusy = Tcl_GetStringFromObj(objv[2], &len); + if( zBusy && len>0 ){ + pDb->zBusy = Tcl_Alloc( len + 1 ); + memcpy(pDb->zBusy, zBusy, len+1); + }else{ + pDb->zBusy = 0; + } + if( pDb->zBusy ){ + pDb->interp = interp; + sqlite3_busy_handler(pDb->db, DbBusyHandler, pDb); + }else{ + sqlite3_busy_handler(pDb->db, 0, 0); + } + } + break; + } + + /* $db cache flush + ** $db cache size n + ** + ** Flush the prepared statement cache, or set the maximum number of + ** cached statements. + */ + case DB_CACHE: { + char *subCmd; + int n; + + if( objc<=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "cache option ?arg?"); + return TCL_ERROR; + } + subCmd = Tcl_GetStringFromObj( objv[2], 0 ); + if( *subCmd=='f' && strcmp(subCmd,"flush")==0 ){ + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "flush"); + return TCL_ERROR; + }else{ + flushStmtCache( pDb ); + } + }else if( *subCmd=='s' && strcmp(subCmd,"size")==0 ){ + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "size n"); + return TCL_ERROR; + }else{ + if( TCL_ERROR==Tcl_GetIntFromObj(interp, objv[3], &n) ){ + Tcl_AppendResult( interp, "cannot convert \"", + Tcl_GetStringFromObj(objv[3],0), "\" to integer", (char*)0); + return TCL_ERROR; + }else{ + if( n<0 ){ + flushStmtCache( pDb ); + n = 0; + }else if( n>MAX_PREPARED_STMTS ){ + n = MAX_PREPARED_STMTS; + } + pDb->maxStmt = n; + } + } + }else{ + Tcl_AppendResult( interp, "bad option \"", + Tcl_GetStringFromObj(objv[2],0), "\": must be flush or size", + (char*)0); + return TCL_ERROR; + } + break; + } + + /* $db changes + ** + ** Return the number of rows that were modified, inserted, or deleted by + ** the most recent INSERT, UPDATE or DELETE statement, not including + ** any changes made by trigger programs. + */ + case DB_CHANGES: { + Tcl_Obj *pResult; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + pResult = Tcl_GetObjResult(interp); + Tcl_SetWideIntObj(pResult, sqlite3_changes64(pDb->db)); + break; + } + + /* $db close + ** + ** Shutdown the database + */ + case DB_CLOSE: { + Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0)); + break; + } + + /* + ** $db collate NAME SCRIPT + ** + ** Create a new SQL collation function called NAME. Whenever + ** that function is called, invoke SCRIPT to evaluate the function. + */ + case DB_COLLATE: { + SqlCollate *pCollate; + char *zName; + char *zScript; + int nScript; + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "NAME SCRIPT"); + return TCL_ERROR; + } + zName = Tcl_GetStringFromObj(objv[2], 0); + zScript = Tcl_GetStringFromObj(objv[3], &nScript); + pCollate = (SqlCollate*)Tcl_Alloc( sizeof(*pCollate) + nScript + 1 ); + if( pCollate==0 ) return TCL_ERROR; + pCollate->interp = interp; + pCollate->pNext = pDb->pCollate; + pCollate->zScript = (char*)&pCollate[1]; + pDb->pCollate = pCollate; + memcpy(pCollate->zScript, zScript, nScript+1); + if( sqlite3_create_collation(pDb->db, zName, SQLITE_UTF8, + pCollate, tclSqlCollate) ){ + Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_VOLATILE); + return TCL_ERROR; + } + break; + } + + /* + ** $db collation_needed SCRIPT + ** + ** Create a new SQL collation function called NAME. Whenever + ** that function is called, invoke SCRIPT to evaluate the function. + */ + case DB_COLLATION_NEEDED: { + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "SCRIPT"); + return TCL_ERROR; + } + if( pDb->pCollateNeeded ){ + Tcl_DecrRefCount(pDb->pCollateNeeded); + } + pDb->pCollateNeeded = Tcl_DuplicateObj(objv[2]); + Tcl_IncrRefCount(pDb->pCollateNeeded); + sqlite3_collation_needed(pDb->db, pDb, tclCollateNeeded); + break; + } + + /* $db commit_hook ?CALLBACK? + ** + ** Invoke the given callback just before committing every SQL transaction. + ** If the callback throws an exception or returns non-zero, then the + ** transaction is aborted. If CALLBACK is an empty string, the callback + ** is disabled. + */ + case DB_COMMIT_HOOK: { + if( objc>3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?"); + return TCL_ERROR; + }else if( objc==2 ){ + if( pDb->zCommit ){ + Tcl_AppendResult(interp, pDb->zCommit, (char*)0); + } + }else{ + const char *zCommit; + int len; + if( pDb->zCommit ){ + Tcl_Free(pDb->zCommit); + } + zCommit = Tcl_GetStringFromObj(objv[2], &len); + if( zCommit && len>0 ){ + pDb->zCommit = Tcl_Alloc( len + 1 ); + memcpy(pDb->zCommit, zCommit, len+1); + }else{ + pDb->zCommit = 0; + } + if( pDb->zCommit ){ + pDb->interp = interp; + sqlite3_commit_hook(pDb->db, DbCommitHandler, pDb); + }else{ + sqlite3_commit_hook(pDb->db, 0, 0); + } + } + break; + } + + /* $db complete SQL + ** + ** Return TRUE if SQL is a complete SQL statement. Return FALSE if + ** additional lines of input are needed. This is similar to the + ** built-in "info complete" command of Tcl. + */ + case DB_COMPLETE: { +#ifndef SQLITE_OMIT_COMPLETE + Tcl_Obj *pResult; + int isComplete; + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "SQL"); + return TCL_ERROR; + } + isComplete = sqlite3_complete( Tcl_GetStringFromObj(objv[2], 0) ); + pResult = Tcl_GetObjResult(interp); + Tcl_SetBooleanObj(pResult, isComplete); +#endif + break; + } + + /* $db config ?OPTION? ?BOOLEAN? + ** + ** Configure the database connection using the sqlite3_db_config() + ** interface. + */ + case DB_CONFIG: { + static const struct DbConfigChoices { + const char *zName; + int op; + } aDbConfig[] = { + { "defensive", SQLITE_DBCONFIG_DEFENSIVE }, + { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL }, + { "dqs_dml", SQLITE_DBCONFIG_DQS_DML }, + { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY }, + { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG }, + { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER }, + { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW }, + { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, + { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE }, + { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, + { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, + { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE }, + { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE }, + { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP }, + { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA }, + { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA }, + }; + Tcl_Obj *pResult; + int ii; + if( objc>4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?OPTION? ?BOOLEAN?"); + return TCL_ERROR; + } + if( objc==2 ){ + /* With no arguments, list all configuration options and with the + ** current value */ + pResult = Tcl_NewListObj(0,0); + for(ii=0; ii<sizeof(aDbConfig)/sizeof(aDbConfig[0]); ii++){ + int v = 0; + sqlite3_db_config(pDb->db, aDbConfig[ii].op, -1, &v); + Tcl_ListObjAppendElement(interp, pResult, + Tcl_NewStringObj(aDbConfig[ii].zName,-1)); + Tcl_ListObjAppendElement(interp, pResult, + Tcl_NewIntObj(v)); + } + }else{ + const char *zOpt = Tcl_GetString(objv[2]); + int onoff = -1; + int v = 0; + if( zOpt[0]=='-' ) zOpt++; + for(ii=0; ii<sizeof(aDbConfig)/sizeof(aDbConfig[0]); ii++){ + if( strcmp(aDbConfig[ii].zName, zOpt)==0 ) break; + } + if( ii>=sizeof(aDbConfig)/sizeof(aDbConfig[0]) ){ + Tcl_AppendResult(interp, "unknown config option: \"", zOpt, + "\"", (void*)0); + return TCL_ERROR; + } + if( objc==4 ){ + if( Tcl_GetBooleanFromObj(interp, objv[3], &onoff) ){ + return TCL_ERROR; + } + } + sqlite3_db_config(pDb->db, aDbConfig[ii].op, onoff, &v); + pResult = Tcl_NewIntObj(v); + } + Tcl_SetObjResult(interp, pResult); + break; + } + + /* $db copy conflict-algorithm table filename ?SEPARATOR? ?NULLINDICATOR? + ** + ** Copy data into table from filename, optionally using SEPARATOR + ** as column separators. If a column contains a null string, or the + ** value of NULLINDICATOR, a NULL is inserted for the column. + ** conflict-algorithm is one of the sqlite conflict algorithms: + ** rollback, abort, fail, ignore, replace + ** On success, return the number of lines processed, not necessarily same + ** as 'db changes' due to conflict-algorithm selected. + ** + ** This code is basically an implementation/enhancement of + ** the sqlite3 shell.c ".import" command. + ** + ** This command usage is equivalent to the sqlite2.x COPY statement, + ** which imports file data into a table using the PostgreSQL COPY file format: + ** $db copy $conflict_algorithm $table_name $filename \t \\N + */ + case DB_COPY: { + char *zTable; /* Insert data into this table */ + char *zFile; /* The file from which to extract data */ + char *zConflict; /* The conflict algorithm to use */ + sqlite3_stmt *pStmt; /* A statement */ + int nCol; /* Number of columns in the table */ + int nByte; /* Number of bytes in an SQL string */ + int i, j; /* Loop counters */ + int nSep; /* Number of bytes in zSep[] */ + int nNull; /* Number of bytes in zNull[] */ + char *zSql; /* An SQL statement */ + char *zLine; /* A single line of input from the file */ + char **azCol; /* zLine[] broken up into columns */ + const char *zCommit; /* How to commit changes */ + FILE *in; /* The input file */ + int lineno = 0; /* Line number of input file */ + char zLineNum[80]; /* Line number print buffer */ + Tcl_Obj *pResult; /* interp result */ + + const char *zSep; + const char *zNull; + if( objc<5 || objc>7 ){ + Tcl_WrongNumArgs(interp, 2, objv, + "CONFLICT-ALGORITHM TABLE FILENAME ?SEPARATOR? ?NULLINDICATOR?"); + return TCL_ERROR; + } + if( objc>=6 ){ + zSep = Tcl_GetStringFromObj(objv[5], 0); + }else{ + zSep = "\t"; + } + if( objc>=7 ){ + zNull = Tcl_GetStringFromObj(objv[6], 0); + }else{ + zNull = ""; + } + zConflict = Tcl_GetStringFromObj(objv[2], 0); + zTable = Tcl_GetStringFromObj(objv[3], 0); + zFile = Tcl_GetStringFromObj(objv[4], 0); + nSep = strlen30(zSep); + nNull = strlen30(zNull); + if( nSep==0 ){ + Tcl_AppendResult(interp,"Error: non-null separator required for copy", + (char*)0); + return TCL_ERROR; + } + if(strcmp(zConflict, "rollback") != 0 && + strcmp(zConflict, "abort" ) != 0 && + strcmp(zConflict, "fail" ) != 0 && + strcmp(zConflict, "ignore" ) != 0 && + strcmp(zConflict, "replace" ) != 0 ) { + Tcl_AppendResult(interp, "Error: \"", zConflict, + "\", conflict-algorithm must be one of: rollback, " + "abort, fail, ignore, or replace", (char*)0); + return TCL_ERROR; + } + zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable); + if( zSql==0 ){ + Tcl_AppendResult(interp, "Error: no such table: ", zTable, (char*)0); + return TCL_ERROR; + } + nByte = strlen30(zSql); + rc = sqlite3_prepare(pDb->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rc ){ + Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), (char*)0); + nCol = 0; + }else{ + nCol = sqlite3_column_count(pStmt); + } + sqlite3_finalize(pStmt); + if( nCol==0 ) { + return TCL_ERROR; + } + zSql = malloc( nByte + 50 + nCol*2 ); + if( zSql==0 ) { + Tcl_AppendResult(interp, "Error: can't malloc()", (char*)0); + return TCL_ERROR; + } + sqlite3_snprintf(nByte+50, zSql, "INSERT OR %q INTO '%q' VALUES(?", + zConflict, zTable); + j = strlen30(zSql); + for(i=1; i<nCol; i++){ + zSql[j++] = ','; + zSql[j++] = '?'; + } + zSql[j++] = ')'; + zSql[j] = 0; + rc = sqlite3_prepare(pDb->db, zSql, -1, &pStmt, 0); + free(zSql); + if( rc ){ + Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), (char*)0); + sqlite3_finalize(pStmt); + return TCL_ERROR; + } + in = fopen(zFile, "rb"); + if( in==0 ){ + Tcl_AppendResult(interp, "Error: cannot open file: ", zFile, (char*)0); + sqlite3_finalize(pStmt); + return TCL_ERROR; + } + azCol = malloc( sizeof(azCol[0])*(nCol+1) ); + if( azCol==0 ) { + Tcl_AppendResult(interp, "Error: can't malloc()", (char*)0); + fclose(in); + return TCL_ERROR; + } + (void)sqlite3_exec(pDb->db, "BEGIN", 0, 0, 0); + zCommit = "COMMIT"; + while( (zLine = local_getline(0, in))!=0 ){ + char *z; + lineno++; + azCol[0] = zLine; + for(i=0, z=zLine; *z; z++){ + if( *z==zSep[0] && strncmp(z, zSep, nSep)==0 ){ + *z = 0; + i++; + if( i<nCol ){ + azCol[i] = &z[nSep]; + z += nSep-1; + } + } + } + if( i+1!=nCol ){ + char *zErr; + int nErr = strlen30(zFile) + 200; + zErr = malloc(nErr); + if( zErr ){ + sqlite3_snprintf(nErr, zErr, + "Error: %s line %d: expected %d columns of data but found %d", + zFile, lineno, nCol, i+1); + Tcl_AppendResult(interp, zErr, (char*)0); + free(zErr); + } + zCommit = "ROLLBACK"; + break; + } + for(i=0; i<nCol; i++){ + /* check for null data, if so, bind as null */ + if( (nNull>0 && strcmp(azCol[i], zNull)==0) + || strlen30(azCol[i])==0 + ){ + sqlite3_bind_null(pStmt, i+1); + }else{ + sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC); + } + } + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + free(zLine); + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp,"Error: ", sqlite3_errmsg(pDb->db), (char*)0); + zCommit = "ROLLBACK"; + break; + } + } + free(azCol); + fclose(in); + sqlite3_finalize(pStmt); + (void)sqlite3_exec(pDb->db, zCommit, 0, 0, 0); + + if( zCommit[0] == 'C' ){ + /* success, set result as number of lines processed */ + pResult = Tcl_GetObjResult(interp); + Tcl_SetIntObj(pResult, lineno); + rc = TCL_OK; + }else{ + /* failure, append lineno where failed */ + sqlite3_snprintf(sizeof(zLineNum), zLineNum,"%d",lineno); + Tcl_AppendResult(interp,", failed while processing line: ",zLineNum, + (char*)0); + rc = TCL_ERROR; + } + break; + } + + /* + ** $db deserialize ?-maxsize N? ?-readonly BOOL? ?DATABASE? VALUE + ** + ** Reopen DATABASE (default "main") using the content in $VALUE + */ + case DB_DESERIALIZE: { +#ifdef SQLITE_OMIT_DESERIALIZE + Tcl_AppendResult(interp, "MEMDB not available in this build", + (char*)0); + rc = TCL_ERROR; +#else + const char *zSchema = 0; + Tcl_Obj *pValue = 0; + unsigned char *pBA; + unsigned char *pData; + int len, xrc; + sqlite3_int64 mxSize = 0; + int i; + int isReadonly = 0; + + + if( objc<3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?DATABASE? VALUE"); + rc = TCL_ERROR; + break; + } + for(i=2; i<objc-1; i++){ + const char *z = Tcl_GetString(objv[i]); + if( strcmp(z,"-maxsize")==0 && i<objc-2 ){ + Tcl_WideInt x; + rc = Tcl_GetWideIntFromObj(interp, objv[++i], &x); + if( rc ) goto deserialize_error; + mxSize = x; + continue; + } + if( strcmp(z,"-readonly")==0 && i<objc-2 ){ + rc = Tcl_GetBooleanFromObj(interp, objv[++i], &isReadonly); + if( rc ) goto deserialize_error; + continue; + } + if( zSchema==0 && i==objc-2 && z[0]!='-' ){ + zSchema = z; + continue; + } + Tcl_AppendResult(interp, "unknown option: ", z, (char*)0); + rc = TCL_ERROR; + goto deserialize_error; + } + pValue = objv[objc-1]; + pBA = Tcl_GetByteArrayFromObj(pValue, &len); + pData = sqlite3_malloc64( len ); + if( pData==0 && len>0 ){ + Tcl_AppendResult(interp, "out of memory", (char*)0); + rc = TCL_ERROR; + }else{ + int flags; + if( len>0 ) memcpy(pData, pBA, len); + if( isReadonly ){ + flags = SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_READONLY; + }else{ + flags = SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE; + } + xrc = sqlite3_deserialize(pDb->db, zSchema, pData, len, len, flags); + if( xrc ){ + Tcl_AppendResult(interp, "unable to set MEMDB content", (char*)0); + rc = TCL_ERROR; + } + if( mxSize>0 ){ + sqlite3_file_control(pDb->db, zSchema,SQLITE_FCNTL_SIZE_LIMIT,&mxSize); + } + } +deserialize_error: +#endif + break; + } + + /* + ** $db enable_load_extension BOOLEAN + ** + ** Turn the extension loading feature on or off. It if off by + ** default. + */ + case DB_ENABLE_LOAD_EXTENSION: { +#ifndef SQLITE_OMIT_LOAD_EXTENSION + int onoff; + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "BOOLEAN"); + return TCL_ERROR; + } + if( Tcl_GetBooleanFromObj(interp, objv[2], &onoff) ){ + return TCL_ERROR; + } + sqlite3_enable_load_extension(pDb->db, onoff); + break; +#else + Tcl_AppendResult(interp, "extension loading is turned off at compile-time", + (char*)0); + return TCL_ERROR; +#endif + } + + /* + ** $db errorcode + ** + ** Return the numeric error code that was returned by the most recent + ** call to sqlite3_exec(). + */ + case DB_ERRORCODE: { + Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_errcode(pDb->db))); + break; + } + + /* + ** $db erroroffset + ** + ** Return the numeric error code that was returned by the most recent + ** call to sqlite3_exec(). + */ + case DB_ERROROFFSET: { + Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_error_offset(pDb->db))); + break; + } + + /* + ** $db exists $sql + ** $db onecolumn $sql + ** + ** The onecolumn method is the equivalent of: + ** lindex [$db eval $sql] 0 + */ + case DB_EXISTS: + case DB_ONECOLUMN: { + Tcl_Obj *pResult = 0; + DbEvalContext sEval; + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "SQL"); + return TCL_ERROR; + } + + dbEvalInit(&sEval, pDb, objv[2], 0, 0); + rc = dbEvalStep(&sEval); + if( choice==DB_ONECOLUMN ){ + if( rc==TCL_OK ){ + pResult = dbEvalColumnValue(&sEval, 0); + }else if( rc==TCL_BREAK ){ + Tcl_ResetResult(interp); + } + }else if( rc==TCL_BREAK || rc==TCL_OK ){ + pResult = Tcl_NewBooleanObj(rc==TCL_OK); + } + dbEvalFinalize(&sEval); + if( pResult ) Tcl_SetObjResult(interp, pResult); + + if( rc==TCL_BREAK ){ + rc = TCL_OK; + } + break; + } + + /* + ** $db eval ?options? $sql ?array? ?{ ...code... }? + ** + ** The SQL statement in $sql is evaluated. For each row, the values are + ** placed in elements of the array named "array" and ...code... is executed. + ** If "array" and "code" are omitted, then no callback is every invoked. + ** If "array" is an empty string, then the values are placed in variables + ** that have the same name as the fields extracted by the query. + */ + case DB_EVAL: { + int evalFlags = 0; + const char *zOpt; + while( objc>3 && (zOpt = Tcl_GetString(objv[2]))!=0 && zOpt[0]=='-' ){ + if( strcmp(zOpt, "-withoutnulls")==0 ){ + evalFlags |= SQLITE_EVAL_WITHOUTNULLS; + } + else{ + Tcl_AppendResult(interp, "unknown option: \"", zOpt, "\"", (void*)0); + return TCL_ERROR; + } + objc--; + objv++; + } + if( objc<3 || objc>5 ){ + Tcl_WrongNumArgs(interp, 2, objv, + "?OPTIONS? SQL ?ARRAY-NAME? ?SCRIPT?"); + return TCL_ERROR; + } + + if( objc==3 ){ + DbEvalContext sEval; + Tcl_Obj *pRet = Tcl_NewObj(); + Tcl_IncrRefCount(pRet); + dbEvalInit(&sEval, pDb, objv[2], 0, 0); + while( TCL_OK==(rc = dbEvalStep(&sEval)) ){ + int i; + int nCol; + dbEvalRowInfo(&sEval, &nCol, 0); + for(i=0; i<nCol; i++){ + Tcl_ListObjAppendElement(interp, pRet, dbEvalColumnValue(&sEval, i)); + } + } + dbEvalFinalize(&sEval); + if( rc==TCL_BREAK ){ + Tcl_SetObjResult(interp, pRet); + rc = TCL_OK; + } + Tcl_DecrRefCount(pRet); + }else{ + ClientData cd2[2]; + DbEvalContext *p; + Tcl_Obj *pArray = 0; + Tcl_Obj *pScript; + + if( objc>=5 && *(char *)Tcl_GetString(objv[3]) ){ + pArray = objv[3]; + } + pScript = objv[objc-1]; + Tcl_IncrRefCount(pScript); + + p = (DbEvalContext *)Tcl_Alloc(sizeof(DbEvalContext)); + dbEvalInit(p, pDb, objv[2], pArray, evalFlags); + + cd2[0] = (void *)p; + cd2[1] = (void *)pScript; + rc = DbEvalNextCmd(cd2, interp, TCL_OK); + } + break; + } + + /* + ** $db function NAME [OPTIONS] SCRIPT + ** + ** Create a new SQL function called NAME. Whenever that function is + ** called, invoke SCRIPT to evaluate the function. + ** + ** Options: + ** --argcount N Function has exactly N arguments + ** --deterministic The function is pure + ** --directonly Prohibit use inside triggers and views + ** --innocuous Has no side effects or information leaks + ** --returntype TYPE Specify the return type of the function + */ + case DB_FUNCTION: { + int flags = SQLITE_UTF8; + SqlFunc *pFunc; + Tcl_Obj *pScript; + char *zName; + int nArg = -1; + int i; + int eType = SQLITE_NULL; + if( objc<4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "NAME ?SWITCHES? SCRIPT"); + return TCL_ERROR; + } + for(i=3; i<(objc-1); i++){ + const char *z = Tcl_GetString(objv[i]); + int n = strlen30(z); + if( n>1 && strncmp(z, "-argcount",n)==0 ){ + if( i==(objc-2) ){ + Tcl_AppendResult(interp, "option requires an argument: ", z,(char*)0); + return TCL_ERROR; + } + if( Tcl_GetIntFromObj(interp, objv[i+1], &nArg) ) return TCL_ERROR; + if( nArg<0 ){ + Tcl_AppendResult(interp, "number of arguments must be non-negative", + (char*)0); + return TCL_ERROR; + } + i++; + }else + if( n>1 && strncmp(z, "-deterministic",n)==0 ){ + flags |= SQLITE_DETERMINISTIC; + }else + if( n>1 && strncmp(z, "-directonly",n)==0 ){ + flags |= SQLITE_DIRECTONLY; + }else + if( n>1 && strncmp(z, "-innocuous",n)==0 ){ + flags |= SQLITE_INNOCUOUS; + }else + if( n>1 && strncmp(z, "-returntype", n)==0 ){ + const char *azType[] = {"integer", "real", "text", "blob", "any", 0}; + assert( SQLITE_INTEGER==1 && SQLITE_FLOAT==2 && SQLITE_TEXT==3 ); + assert( SQLITE_BLOB==4 && SQLITE_NULL==5 ); + if( i==(objc-2) ){ + Tcl_AppendResult(interp, "option requires an argument: ", z,(char*)0); + return TCL_ERROR; + } + i++; + if( Tcl_GetIndexFromObj(interp, objv[i], azType, "type", 0, &eType) ){ + return TCL_ERROR; + } + eType++; + }else{ + Tcl_AppendResult(interp, "bad option \"", z, + "\": must be -argcount, -deterministic, -directonly," + " -innocuous, or -returntype", (char*)0 + ); + return TCL_ERROR; + } + } + + pScript = objv[objc-1]; + zName = Tcl_GetStringFromObj(objv[2], 0); + pFunc = findSqlFunc(pDb, zName); + if( pFunc==0 ) return TCL_ERROR; + if( pFunc->pScript ){ + Tcl_DecrRefCount(pFunc->pScript); + } + pFunc->pScript = pScript; + Tcl_IncrRefCount(pScript); + pFunc->useEvalObjv = safeToUseEvalObjv(interp, pScript); + pFunc->eType = eType; + rc = sqlite3_create_function(pDb->db, zName, nArg, flags, + pFunc, tclSqlFunc, 0, 0); + if( rc!=SQLITE_OK ){ + rc = TCL_ERROR; + Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_VOLATILE); + } + break; + } + + /* + ** $db incrblob ?-readonly? ?DB? TABLE COLUMN ROWID + */ + case DB_INCRBLOB: { +#ifdef SQLITE_OMIT_INCRBLOB + Tcl_AppendResult(interp, "incrblob not available in this build", (char*)0); + return TCL_ERROR; +#else + int isReadonly = 0; + const char *zDb = "main"; + const char *zTable; + const char *zColumn; + Tcl_WideInt iRow; + + /* Check for the -readonly option */ + if( objc>3 && strcmp(Tcl_GetString(objv[2]), "-readonly")==0 ){ + isReadonly = 1; + } + + if( objc!=(5+isReadonly) && objc!=(6+isReadonly) ){ + Tcl_WrongNumArgs(interp, 2, objv, "?-readonly? ?DB? TABLE COLUMN ROWID"); + return TCL_ERROR; + } + + if( objc==(6+isReadonly) ){ + zDb = Tcl_GetString(objv[2+isReadonly]); + } + zTable = Tcl_GetString(objv[objc-3]); + zColumn = Tcl_GetString(objv[objc-2]); + rc = Tcl_GetWideIntFromObj(interp, objv[objc-1], &iRow); + + if( rc==TCL_OK ){ + rc = createIncrblobChannel( + interp, pDb, zDb, zTable, zColumn, (sqlite3_int64)iRow, isReadonly + ); + } +#endif + break; + } + + /* + ** $db interrupt + ** + ** Interrupt the execution of the inner-most SQL interpreter. This + ** causes the SQL statement to return an error of SQLITE_INTERRUPT. + */ + case DB_INTERRUPT: { + sqlite3_interrupt(pDb->db); + break; + } + + /* + ** $db nullvalue ?STRING? + ** + ** Change text used when a NULL comes back from the database. If ?STRING? + ** is not present, then the current string used for NULL is returned. + ** If STRING is present, then STRING is returned. + ** + */ + case DB_NULLVALUE: { + if( objc!=2 && objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "NULLVALUE"); + return TCL_ERROR; + } + if( objc==3 ){ + int len; + char *zNull = Tcl_GetStringFromObj(objv[2], &len); + if( pDb->zNull ){ + Tcl_Free(pDb->zNull); + } + if( zNull && len>0 ){ + pDb->zNull = Tcl_Alloc( len + 1 ); + memcpy(pDb->zNull, zNull, len); + pDb->zNull[len] = '\0'; + }else{ + pDb->zNull = 0; + } + } + Tcl_SetObjResult(interp, Tcl_NewStringObj(pDb->zNull, -1)); + break; + } + + /* + ** $db last_insert_rowid + ** + ** Return an integer which is the ROWID for the most recent insert. + */ + case DB_LAST_INSERT_ROWID: { + Tcl_Obj *pResult; + Tcl_WideInt rowid; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + rowid = sqlite3_last_insert_rowid(pDb->db); + pResult = Tcl_GetObjResult(interp); + Tcl_SetWideIntObj(pResult, rowid); + break; + } + + /* + ** The DB_ONECOLUMN method is implemented together with DB_EXISTS. + */ + + /* $db progress ?N CALLBACK? + ** + ** Invoke the given callback every N virtual machine opcodes while executing + ** queries. + */ + case DB_PROGRESS: { + if( objc==2 ){ + if( pDb->zProgress ){ + Tcl_AppendResult(interp, pDb->zProgress, (char*)0); + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + sqlite3_progress_handler(pDb->db, 0, 0, 0); +#endif + }else if( objc==4 ){ + char *zProgress; + int len; + int N; + if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &N) ){ + return TCL_ERROR; + }; + if( pDb->zProgress ){ + Tcl_Free(pDb->zProgress); + } + zProgress = Tcl_GetStringFromObj(objv[3], &len); + if( zProgress && len>0 ){ + pDb->zProgress = Tcl_Alloc( len + 1 ); + memcpy(pDb->zProgress, zProgress, len+1); + }else{ + pDb->zProgress = 0; + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( pDb->zProgress ){ + pDb->interp = interp; + sqlite3_progress_handler(pDb->db, N, DbProgressHandler, pDb); + }else{ + sqlite3_progress_handler(pDb->db, 0, 0, 0); + } +#endif + }else{ + Tcl_WrongNumArgs(interp, 2, objv, "N CALLBACK"); + return TCL_ERROR; + } + break; + } + + /* $db profile ?CALLBACK? + ** + ** Make arrangements to invoke the CALLBACK routine after each SQL statement + ** that has run. The text of the SQL and the amount of elapse time are + ** appended to CALLBACK before the script is run. + */ + case DB_PROFILE: { + if( objc>3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?"); + return TCL_ERROR; + }else if( objc==2 ){ + if( pDb->zProfile ){ + Tcl_AppendResult(interp, pDb->zProfile, (char*)0); + } + }else{ + char *zProfile; + int len; + if( pDb->zProfile ){ + Tcl_Free(pDb->zProfile); + } + zProfile = Tcl_GetStringFromObj(objv[2], &len); + if( zProfile && len>0 ){ + pDb->zProfile = Tcl_Alloc( len + 1 ); + memcpy(pDb->zProfile, zProfile, len+1); + }else{ + pDb->zProfile = 0; + } +#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) && \ + !defined(SQLITE_OMIT_DEPRECATED) + if( pDb->zProfile ){ + pDb->interp = interp; + sqlite3_profile(pDb->db, DbProfileHandler, pDb); + }else{ + sqlite3_profile(pDb->db, 0, 0); + } +#endif + } + break; + } + + /* + ** $db rekey KEY + ** + ** Change the encryption key on the currently open database. + */ + case DB_REKEY: { + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "KEY"); + return TCL_ERROR; + } + break; + } + + /* $db restore ?DATABASE? FILENAME + ** + ** Open a database file named FILENAME. Transfer the content + ** of FILENAME into the local database DATABASE (default: "main"). + */ + case DB_RESTORE: { + const char *zSrcFile; + const char *zDestDb; + sqlite3 *pSrc; + sqlite3_backup *pBackup; + int nTimeout = 0; + + if( objc==3 ){ + zDestDb = "main"; + zSrcFile = Tcl_GetString(objv[2]); + }else if( objc==4 ){ + zDestDb = Tcl_GetString(objv[2]); + zSrcFile = Tcl_GetString(objv[3]); + }else{ + Tcl_WrongNumArgs(interp, 2, objv, "?DATABASE? FILENAME"); + return TCL_ERROR; + } + rc = sqlite3_open_v2(zSrcFile, &pSrc, + SQLITE_OPEN_READONLY | pDb->openFlags, 0); + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp, "cannot open source database: ", + sqlite3_errmsg(pSrc), (char*)0); + sqlite3_close(pSrc); + return TCL_ERROR; + } + pBackup = sqlite3_backup_init(pDb->db, zDestDb, pSrc, "main"); + if( pBackup==0 ){ + Tcl_AppendResult(interp, "restore failed: ", + sqlite3_errmsg(pDb->db), (char*)0); + sqlite3_close(pSrc); + return TCL_ERROR; + } + while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK + || rc==SQLITE_BUSY ){ + if( rc==SQLITE_BUSY ){ + if( nTimeout++ >= 3 ) break; + sqlite3_sleep(100); + } + } + sqlite3_backup_finish(pBackup); + if( rc==SQLITE_DONE ){ + rc = TCL_OK; + }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){ + Tcl_AppendResult(interp, "restore failed: source database busy", + (char*)0); + rc = TCL_ERROR; + }else{ + Tcl_AppendResult(interp, "restore failed: ", + sqlite3_errmsg(pDb->db), (char*)0); + rc = TCL_ERROR; + } + sqlite3_close(pSrc); + break; + } + + /* + ** $db serialize ?DATABASE? + ** + ** Return a serialization of a database. + */ + case DB_SERIALIZE: { +#ifdef SQLITE_OMIT_DESERIALIZE + Tcl_AppendResult(interp, "MEMDB not available in this build", + (char*)0); + rc = TCL_ERROR; +#else + const char *zSchema = objc>=3 ? Tcl_GetString(objv[2]) : "main"; + sqlite3_int64 sz = 0; + unsigned char *pData; + if( objc!=2 && objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?DATABASE?"); + rc = TCL_ERROR; + }else{ + int needFree; + pData = sqlite3_serialize(pDb->db, zSchema, &sz, SQLITE_SERIALIZE_NOCOPY); + if( pData ){ + needFree = 0; + }else{ + pData = sqlite3_serialize(pDb->db, zSchema, &sz, 0); + needFree = 1; + } + Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pData,sz)); + if( needFree ) sqlite3_free(pData); + } +#endif + break; + } + + /* + ** $db status (step|sort|autoindex|vmstep) + ** + ** Display SQLITE_STMTSTATUS_FULLSCAN_STEP or + ** SQLITE_STMTSTATUS_SORT for the most recent eval. + */ + case DB_STATUS: { + int v; + const char *zOp; + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "(step|sort|autoindex)"); + return TCL_ERROR; + } + zOp = Tcl_GetString(objv[2]); + if( strcmp(zOp, "step")==0 ){ + v = pDb->nStep; + }else if( strcmp(zOp, "sort")==0 ){ + v = pDb->nSort; + }else if( strcmp(zOp, "autoindex")==0 ){ + v = pDb->nIndex; + }else if( strcmp(zOp, "vmstep")==0 ){ + v = pDb->nVMStep; + }else{ + Tcl_AppendResult(interp, + "bad argument: should be autoindex, step, sort or vmstep", + (char*)0); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewIntObj(v)); + break; + } + + /* + ** $db timeout MILLESECONDS + ** + ** Delay for the number of milliseconds specified when a file is locked. + */ + case DB_TIMEOUT: { + int ms; + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "MILLISECONDS"); + return TCL_ERROR; + } + if( Tcl_GetIntFromObj(interp, objv[2], &ms) ) return TCL_ERROR; + sqlite3_busy_timeout(pDb->db, ms); + break; + } + + /* + ** $db total_changes + ** + ** Return the number of rows that were modified, inserted, or deleted + ** since the database handle was created. + */ + case DB_TOTAL_CHANGES: { + Tcl_Obj *pResult; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + pResult = Tcl_GetObjResult(interp); + Tcl_SetWideIntObj(pResult, sqlite3_total_changes64(pDb->db)); + break; + } + + /* $db trace ?CALLBACK? + ** + ** Make arrangements to invoke the CALLBACK routine for each SQL statement + ** that is executed. The text of the SQL is appended to CALLBACK before + ** it is executed. + */ + case DB_TRACE: { + if( objc>3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?"); + return TCL_ERROR; + }else if( objc==2 ){ + if( pDb->zTrace ){ + Tcl_AppendResult(interp, pDb->zTrace, (char*)0); + } + }else{ + char *zTrace; + int len; + if( pDb->zTrace ){ + Tcl_Free(pDb->zTrace); + } + zTrace = Tcl_GetStringFromObj(objv[2], &len); + if( zTrace && len>0 ){ + pDb->zTrace = Tcl_Alloc( len + 1 ); + memcpy(pDb->zTrace, zTrace, len+1); + }else{ + pDb->zTrace = 0; + } +#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) && \ + !defined(SQLITE_OMIT_DEPRECATED) + if( pDb->zTrace ){ + pDb->interp = interp; + sqlite3_trace(pDb->db, DbTraceHandler, pDb); + }else{ + sqlite3_trace(pDb->db, 0, 0); + } +#endif + } + break; + } + + /* $db trace_v2 ?CALLBACK? ?MASK? + ** + ** Make arrangements to invoke the CALLBACK routine for each trace event + ** matching the mask that is generated. The parameters are appended to + ** CALLBACK before it is executed. + */ + case DB_TRACE_V2: { + if( objc>4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK? ?MASK?"); + return TCL_ERROR; + }else if( objc==2 ){ + if( pDb->zTraceV2 ){ + Tcl_AppendResult(interp, pDb->zTraceV2, (char*)0); + } + }else{ + char *zTraceV2; + int len; + Tcl_WideInt wMask = 0; + if( objc==4 ){ + static const char *TTYPE_strs[] = { + "statement", "profile", "row", "close", 0 + }; + enum TTYPE_enum { + TTYPE_STMT, TTYPE_PROFILE, TTYPE_ROW, TTYPE_CLOSE + }; + int i; + if( TCL_OK!=Tcl_ListObjLength(interp, objv[3], &len) ){ + return TCL_ERROR; + } + for(i=0; i<len; i++){ + Tcl_Obj *pObj; + int ttype; + if( TCL_OK!=Tcl_ListObjIndex(interp, objv[3], i, &pObj) ){ + return TCL_ERROR; + } + if( Tcl_GetIndexFromObj(interp, pObj, TTYPE_strs, "trace type", + 0, &ttype)!=TCL_OK ){ + Tcl_WideInt wType; + Tcl_Obj *pError = Tcl_DuplicateObj(Tcl_GetObjResult(interp)); + Tcl_IncrRefCount(pError); + if( TCL_OK==Tcl_GetWideIntFromObj(interp, pObj, &wType) ){ + Tcl_DecrRefCount(pError); + wMask |= wType; + }else{ + Tcl_SetObjResult(interp, pError); + Tcl_DecrRefCount(pError); + return TCL_ERROR; + } + }else{ + switch( (enum TTYPE_enum)ttype ){ + case TTYPE_STMT: wMask |= SQLITE_TRACE_STMT; break; + case TTYPE_PROFILE: wMask |= SQLITE_TRACE_PROFILE; break; + case TTYPE_ROW: wMask |= SQLITE_TRACE_ROW; break; + case TTYPE_CLOSE: wMask |= SQLITE_TRACE_CLOSE; break; + } + } + } + }else{ + wMask = SQLITE_TRACE_STMT; /* use the "legacy" default */ + } + if( pDb->zTraceV2 ){ + Tcl_Free(pDb->zTraceV2); + } + zTraceV2 = Tcl_GetStringFromObj(objv[2], &len); + if( zTraceV2 && len>0 ){ + pDb->zTraceV2 = Tcl_Alloc( len + 1 ); + memcpy(pDb->zTraceV2, zTraceV2, len+1); + }else{ + pDb->zTraceV2 = 0; + } +#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) + if( pDb->zTraceV2 ){ + pDb->interp = interp; + sqlite3_trace_v2(pDb->db, (unsigned)wMask, DbTraceV2Handler, pDb); + }else{ + sqlite3_trace_v2(pDb->db, 0, 0, 0); + } +#endif + } + break; + } + + /* $db transaction [-deferred|-immediate|-exclusive] SCRIPT + ** + ** Start a new transaction (if we are not already in the midst of a + ** transaction) and execute the TCL script SCRIPT. After SCRIPT + ** completes, either commit the transaction or roll it back if SCRIPT + ** throws an exception. Or if no new transaction was started, do nothing. + ** pass the exception on up the stack. + ** + ** This command was inspired by Dave Thomas's talk on Ruby at the + ** 2005 O'Reilly Open Source Convention (OSCON). + */ + case DB_TRANSACTION: { + Tcl_Obj *pScript; + const char *zBegin = "SAVEPOINT _tcl_transaction"; + if( objc!=3 && objc!=4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "[TYPE] SCRIPT"); + return TCL_ERROR; + } + + if( pDb->nTransaction==0 && objc==4 ){ + static const char *TTYPE_strs[] = { + "deferred", "exclusive", "immediate", 0 + }; + enum TTYPE_enum { + TTYPE_DEFERRED, TTYPE_EXCLUSIVE, TTYPE_IMMEDIATE + }; + int ttype; + if( Tcl_GetIndexFromObj(interp, objv[2], TTYPE_strs, "transaction type", + 0, &ttype) ){ + return TCL_ERROR; + } + switch( (enum TTYPE_enum)ttype ){ + case TTYPE_DEFERRED: /* no-op */; break; + case TTYPE_EXCLUSIVE: zBegin = "BEGIN EXCLUSIVE"; break; + case TTYPE_IMMEDIATE: zBegin = "BEGIN IMMEDIATE"; break; + } + } + pScript = objv[objc-1]; + + /* Run the SQLite BEGIN command to open a transaction or savepoint. */ + pDb->disableAuth++; + rc = sqlite3_exec(pDb->db, zBegin, 0, 0, 0); + pDb->disableAuth--; + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), (char*)0); + return TCL_ERROR; + } + pDb->nTransaction++; + + /* If using NRE, schedule a callback to invoke the script pScript, then + ** a second callback to commit (or rollback) the transaction or savepoint + ** opened above. If not using NRE, evaluate the script directly, then + ** call function DbTransPostCmd() to commit (or rollback) the transaction + ** or savepoint. */ + addDatabaseRef(pDb); /* DbTransPostCmd() calls delDatabaseRef() */ + if( DbUseNre() ){ + Tcl_NRAddCallback(interp, DbTransPostCmd, cd, 0, 0, 0); + (void)Tcl_NREvalObj(interp, pScript, 0); + }else{ + rc = DbTransPostCmd(&cd, interp, Tcl_EvalObjEx(interp, pScript, 0)); + } + break; + } + + /* + ** $db unlock_notify ?script? + */ + case DB_UNLOCK_NOTIFY: { +#ifndef SQLITE_ENABLE_UNLOCK_NOTIFY + Tcl_AppendResult(interp, "unlock_notify not available in this build", + (char*)0); + rc = TCL_ERROR; +#else + if( objc!=2 && objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?"); + rc = TCL_ERROR; + }else{ + void (*xNotify)(void **, int) = 0; + void *pNotifyArg = 0; + + if( pDb->pUnlockNotify ){ + Tcl_DecrRefCount(pDb->pUnlockNotify); + pDb->pUnlockNotify = 0; + } + + if( objc==3 ){ + xNotify = DbUnlockNotify; + pNotifyArg = (void *)pDb; + pDb->pUnlockNotify = objv[2]; + Tcl_IncrRefCount(pDb->pUnlockNotify); + } + + if( sqlite3_unlock_notify(pDb->db, xNotify, pNotifyArg) ){ + Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), (char*)0); + rc = TCL_ERROR; + } + } +#endif + break; + } + + /* + ** $db preupdate_hook count + ** $db preupdate_hook hook ?SCRIPT? + ** $db preupdate_hook new INDEX + ** $db preupdate_hook old INDEX + */ + case DB_PREUPDATE: { +#ifndef SQLITE_ENABLE_PREUPDATE_HOOK + Tcl_AppendResult(interp, "preupdate_hook was omitted at compile-time", + (char*)0); + rc = TCL_ERROR; +#else + static const char *azSub[] = {"count", "depth", "hook", "new", "old", 0}; + enum DbPreupdateSubCmd { + PRE_COUNT, PRE_DEPTH, PRE_HOOK, PRE_NEW, PRE_OLD + }; + int iSub; + + if( objc<3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "SUB-COMMAND ?ARGS?"); + } + if( Tcl_GetIndexFromObj(interp, objv[2], azSub, "sub-command", 0, &iSub) ){ + return TCL_ERROR; + } + + switch( (enum DbPreupdateSubCmd)iSub ){ + case PRE_COUNT: { + int nCol = sqlite3_preupdate_count(pDb->db); + Tcl_SetObjResult(interp, Tcl_NewIntObj(nCol)); + break; + } + + case PRE_HOOK: { + if( objc>4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "hook ?SCRIPT?"); + return TCL_ERROR; + } + DbHookCmd(interp, pDb, (objc==4 ? objv[3] : 0), &pDb->pPreUpdateHook); + break; + } + + case PRE_DEPTH: { + Tcl_Obj *pRet; + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 3, objv, ""); + return TCL_ERROR; + } + pRet = Tcl_NewIntObj(sqlite3_preupdate_depth(pDb->db)); + Tcl_SetObjResult(interp, pRet); + break; + } + + case PRE_NEW: + case PRE_OLD: { + int iIdx; + sqlite3_value *pValue; + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 3, objv, "INDEX"); + return TCL_ERROR; + } + if( Tcl_GetIntFromObj(interp, objv[3], &iIdx) ){ + return TCL_ERROR; + } + + if( iSub==PRE_OLD ){ + rc = sqlite3_preupdate_old(pDb->db, iIdx, &pValue); + }else{ + assert( iSub==PRE_NEW ); + rc = sqlite3_preupdate_new(pDb->db, iIdx, &pValue); + } + + if( rc==SQLITE_OK ){ + Tcl_Obj *pObj; + pObj = Tcl_NewStringObj((char*)sqlite3_value_text(pValue), -1); + Tcl_SetObjResult(interp, pObj); + }else{ + Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), (char*)0); + return TCL_ERROR; + } + } + } +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + break; + } + + /* + ** $db wal_hook ?script? + ** $db update_hook ?script? + ** $db rollback_hook ?script? + */ + case DB_WAL_HOOK: + case DB_UPDATE_HOOK: + case DB_ROLLBACK_HOOK: { + /* set ppHook to point at pUpdateHook or pRollbackHook, depending on + ** whether [$db update_hook] or [$db rollback_hook] was invoked. + */ + Tcl_Obj **ppHook = 0; + if( choice==DB_WAL_HOOK ) ppHook = &pDb->pWalHook; + if( choice==DB_UPDATE_HOOK ) ppHook = &pDb->pUpdateHook; + if( choice==DB_ROLLBACK_HOOK ) ppHook = &pDb->pRollbackHook; + if( objc>3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?"); + return TCL_ERROR; + } + + DbHookCmd(interp, pDb, (objc==3 ? objv[2] : 0), ppHook); + break; + } + + /* $db version + ** + ** Return the version string for this database. + */ + case DB_VERSION: { + int i; + for(i=2; i<objc; i++){ + const char *zArg = Tcl_GetString(objv[i]); + /* Optional arguments to $db version are used for testing purpose */ +#ifdef SQLITE_TEST + /* $db version -use-legacy-prepare BOOLEAN + ** + ** Turn the use of legacy sqlite3_prepare() on or off. + */ + if( strcmp(zArg, "-use-legacy-prepare")==0 && i+1<objc ){ + i++; + if( Tcl_GetBooleanFromObj(interp, objv[i], &pDb->bLegacyPrepare) ){ + return TCL_ERROR; + } + }else + + /* $db version -last-stmt-ptr + ** + ** Return a string which is a hex encoding of the pointer to the + ** most recent sqlite3_stmt in the statement cache. + */ + if( strcmp(zArg, "-last-stmt-ptr")==0 ){ + char zBuf[100]; + sqlite3_snprintf(sizeof(zBuf), zBuf, "%p", + pDb->stmtList ? pDb->stmtList->pStmt: 0); + Tcl_SetResult(interp, zBuf, TCL_VOLATILE); + }else +#endif /* SQLITE_TEST */ + { + Tcl_AppendResult(interp, "unknown argument: ", zArg, (char*)0); + return TCL_ERROR; + } + } + if( i==2 ){ + Tcl_SetResult(interp, (char *)sqlite3_libversion(), TCL_STATIC); + } + break; + } + + + } /* End of the SWITCH statement */ + return rc; +} + +#if SQLITE_TCL_NRE +/* +** Adaptor that provides an objCmd interface to the NRE-enabled +** interface implementation. +*/ +static int SQLITE_TCLAPI DbObjCmdAdaptor( + void *cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const*objv +){ + return Tcl_NRCallObjProc(interp, DbObjCmd, cd, objc, objv); +} +#endif /* SQLITE_TCL_NRE */ + +/* +** Issue the usage message when the "sqlite3" command arguments are +** incorrect. +*/ +static int sqliteCmdUsage( + Tcl_Interp *interp, + Tcl_Obj *const*objv +){ + Tcl_WrongNumArgs(interp, 1, objv, + "HANDLE ?FILENAME? ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN?" + " ?-nofollow BOOLEAN?" + " ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN?" + ); + return TCL_ERROR; +} + +/* +** sqlite3 DBNAME FILENAME ?-vfs VFSNAME? ?-key KEY? ?-readonly BOOLEAN? +** ?-create BOOLEAN? ?-nomutex BOOLEAN? +** ?-nofollow BOOLEAN? +** +** This is the main Tcl command. When the "sqlite" Tcl command is +** invoked, this routine runs to process that command. +** +** The first argument, DBNAME, is an arbitrary name for a new +** database connection. This command creates a new command named +** DBNAME that is used to control that connection. The database +** connection is deleted when the DBNAME command is deleted. +** +** The second argument is the name of the database file. +** +*/ +static int SQLITE_TCLAPI DbMain( + void *cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const*objv +){ + SqliteDb *p; + const char *zArg; + char *zErrMsg; + int i; + const char *zFile = 0; + const char *zVfs = 0; + int flags; + int bTranslateFileName = 1; + Tcl_DString translatedFilename; + int rc; + + /* In normal use, each TCL interpreter runs in a single thread. So + ** by default, we can turn off mutexing on SQLite database connections. + ** However, for testing purposes it is useful to have mutexes turned + ** on. So, by default, mutexes default off. But if compiled with + ** SQLITE_TCL_DEFAULT_FULLMUTEX then mutexes default on. + */ +#ifdef SQLITE_TCL_DEFAULT_FULLMUTEX + flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX; +#else + flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX; +#endif + + if( objc==1 ) return sqliteCmdUsage(interp, objv); + if( objc==2 ){ + zArg = Tcl_GetStringFromObj(objv[1], 0); + if( strcmp(zArg,"-version")==0 ){ + Tcl_AppendResult(interp,sqlite3_libversion(), (char*)0); + return TCL_OK; + } + if( strcmp(zArg,"-sourceid")==0 ){ + Tcl_AppendResult(interp,sqlite3_sourceid(), (char*)0); + return TCL_OK; + } + if( strcmp(zArg,"-has-codec")==0 ){ + Tcl_AppendResult(interp,"0",(char*)0); + return TCL_OK; + } + if( zArg[0]=='-' ) return sqliteCmdUsage(interp, objv); + } + for(i=2; i<objc; i++){ + zArg = Tcl_GetString(objv[i]); + if( zArg[0]!='-' ){ + if( zFile!=0 ) return sqliteCmdUsage(interp, objv); + zFile = zArg; + continue; + } + if( i==objc-1 ) return sqliteCmdUsage(interp, objv); + i++; + if( strcmp(zArg,"-key")==0 ){ + /* no-op */ + }else if( strcmp(zArg, "-vfs")==0 ){ + zVfs = Tcl_GetString(objv[i]); + }else if( strcmp(zArg, "-readonly")==0 ){ + int b; + if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR; + if( b ){ + flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); + flags |= SQLITE_OPEN_READONLY; + }else{ + flags &= ~SQLITE_OPEN_READONLY; + flags |= SQLITE_OPEN_READWRITE; + } + }else if( strcmp(zArg, "-create")==0 ){ + int b; + if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR; + if( b && (flags & SQLITE_OPEN_READONLY)==0 ){ + flags |= SQLITE_OPEN_CREATE; + }else{ + flags &= ~SQLITE_OPEN_CREATE; + } + }else if( strcmp(zArg, "-nofollow")==0 ){ + int b; + if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR; + if( b ){ + flags |= SQLITE_OPEN_NOFOLLOW; + }else{ + flags &= ~SQLITE_OPEN_NOFOLLOW; + } + }else if( strcmp(zArg, "-nomutex")==0 ){ + int b; + if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR; + if( b ){ + flags |= SQLITE_OPEN_NOMUTEX; + flags &= ~SQLITE_OPEN_FULLMUTEX; + }else{ + flags &= ~SQLITE_OPEN_NOMUTEX; + } + }else if( strcmp(zArg, "-fullmutex")==0 ){ + int b; + if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR; + if( b ){ + flags |= SQLITE_OPEN_FULLMUTEX; + flags &= ~SQLITE_OPEN_NOMUTEX; + }else{ + flags &= ~SQLITE_OPEN_FULLMUTEX; + } + }else if( strcmp(zArg, "-uri")==0 ){ + int b; + if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR; + if( b ){ + flags |= SQLITE_OPEN_URI; + }else{ + flags &= ~SQLITE_OPEN_URI; + } + }else if( strcmp(zArg, "-translatefilename")==0 ){ + if( Tcl_GetBooleanFromObj(interp, objv[i], &bTranslateFileName) ){ + return TCL_ERROR; + } + }else{ + Tcl_AppendResult(interp, "unknown option: ", zArg, (char*)0); + return TCL_ERROR; + } + } + zErrMsg = 0; + p = (SqliteDb*)Tcl_Alloc( sizeof(*p) ); + memset(p, 0, sizeof(*p)); + if( zFile==0 ) zFile = ""; + if( bTranslateFileName ){ + zFile = Tcl_TranslateFileName(interp, zFile, &translatedFilename); + } + rc = sqlite3_open_v2(zFile, &p->db, flags, zVfs); + if( bTranslateFileName ){ + Tcl_DStringFree(&translatedFilename); + } + if( p->db ){ + if( SQLITE_OK!=sqlite3_errcode(p->db) ){ + zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(p->db)); + sqlite3_close(p->db); + p->db = 0; + } + }else{ + zErrMsg = sqlite3_mprintf("%s", sqlite3_errstr(rc)); + } + if( p->db==0 ){ + Tcl_SetResult(interp, zErrMsg, TCL_VOLATILE); + Tcl_Free((char*)p); + sqlite3_free(zErrMsg); + return TCL_ERROR; + } + p->maxStmt = NUM_PREPARED_STMTS; + p->openFlags = flags & SQLITE_OPEN_URI; + p->interp = interp; + zArg = Tcl_GetStringFromObj(objv[1], 0); + if( DbUseNre() ){ + Tcl_NRCreateCommand(interp, zArg, DbObjCmdAdaptor, DbObjCmd, + (char*)p, DbDeleteCmd); + }else{ + Tcl_CreateObjCommand(interp, zArg, DbObjCmd, (char*)p, DbDeleteCmd); + } + p->nRef = 1; + return TCL_OK; +} + +/* +** Provide a dummy Tcl_InitStubs if we are using this as a static +** library. +*/ +#ifndef USE_TCL_STUBS +# undef Tcl_InitStubs +# define Tcl_InitStubs(a,b,c) TCL_VERSION +#endif + +/* +** Make sure we have a PACKAGE_VERSION macro defined. This will be +** defined automatically by the TEA makefile. But other makefiles +** do not define it. +*/ +#ifndef PACKAGE_VERSION +# define PACKAGE_VERSION SQLITE_VERSION +#endif + +/* +** Initialize this module. +** +** This Tcl module contains only a single new Tcl command named "sqlite". +** (Hence there is no namespace. There is no point in using a namespace +** if the extension only supplies one new name!) The "sqlite" command is +** used to open a new SQLite database. See the DbMain() routine above +** for additional information. +** +** The EXTERN macros are required by TCL in order to work on windows. +*/ +EXTERN int Sqlite3_Init(Tcl_Interp *interp){ + int rc = Tcl_InitStubs(interp, "8.4", 0) ? TCL_OK : TCL_ERROR; + if( rc==TCL_OK ){ + Tcl_CreateObjCommand(interp, "sqlite3", (Tcl_ObjCmdProc*)DbMain, 0, 0); +#ifndef SQLITE_3_SUFFIX_ONLY + /* The "sqlite" alias is undocumented. It is here only to support + ** legacy scripts. All new scripts should use only the "sqlite3" + ** command. */ + Tcl_CreateObjCommand(interp, "sqlite", (Tcl_ObjCmdProc*)DbMain, 0, 0); +#endif + rc = Tcl_PkgProvide(interp, "sqlite3", PACKAGE_VERSION); + } + return rc; +} +EXTERN int Tclsqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); } +EXTERN int Sqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; } +EXTERN int Tclsqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; } + +/* Because it accesses the file-system and uses persistent state, SQLite +** is not considered appropriate for safe interpreters. Hence, we cause +** the _SafeInit() interfaces return TCL_ERROR. +*/ +EXTERN int Sqlite3_SafeInit(Tcl_Interp *interp){ return TCL_ERROR; } +EXTERN int Sqlite3_SafeUnload(Tcl_Interp *interp, int flags){return TCL_ERROR;} + + + +#ifndef SQLITE_3_SUFFIX_ONLY +int Sqlite_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); } +int Tclsqlite_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); } +int Sqlite_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; } +int Tclsqlite_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; } +#endif + +/* +** If the TCLSH macro is defined, add code to make a stand-alone program. +*/ +#if defined(TCLSH) + +/* This is the main routine for an ordinary TCL shell. If there are +** are arguments, run the first argument as a script. Otherwise, +** read TCL commands from standard input +*/ +static const char *tclsh_main_loop(void){ + static const char zMainloop[] = + "if {[llength $argv]>=1} {\n" + "set argv0 [lindex $argv 0]\n" + "set argv [lrange $argv 1 end]\n" + "source $argv0\n" + "} else {\n" + "set line {}\n" + "while {![eof stdin]} {\n" + "if {$line!=\"\"} {\n" + "puts -nonewline \"> \"\n" + "} else {\n" + "puts -nonewline \"% \"\n" + "}\n" + "flush stdout\n" + "append line [gets stdin]\n" + "if {[info complete $line]} {\n" + "if {[catch {uplevel #0 $line} result]} {\n" + "puts stderr \"Error: $result\"\n" + "} elseif {$result!=\"\"} {\n" + "puts $result\n" + "}\n" + "set line {}\n" + "} else {\n" + "append line \\n\n" + "}\n" + "}\n" + "}\n" + ; + return zMainloop; +} + +#ifndef TCLSH_MAIN +# define TCLSH_MAIN main +#endif +int SQLITE_CDECL TCLSH_MAIN(int argc, char **argv){ + Tcl_Interp *interp; + int i; + const char *zScript = 0; + char zArgc[32]; +#if defined(TCLSH_INIT_PROC) + extern const char *TCLSH_INIT_PROC(Tcl_Interp*); +#endif + +#if !defined(_WIN32_WCE) + if( getenv("SQLITE_DEBUG_BREAK") ){ + if( isatty(0) && isatty(2) ){ + fprintf(stderr, + "attach debugger to process %d and press any key to continue.\n", + GETPID()); + fgetc(stdin); + }else{ +#if defined(_WIN32) || defined(WIN32) + DebugBreak(); +#elif defined(SIGTRAP) + raise(SIGTRAP); +#endif + } + } +#endif + + /* Call sqlite3_shutdown() once before doing anything else. This is to + ** test that sqlite3_shutdown() can be safely called by a process before + ** sqlite3_initialize() is. */ + sqlite3_shutdown(); + + Tcl_FindExecutable(argv[0]); + Tcl_SetSystemEncoding(NULL, "utf-8"); + interp = Tcl_CreateInterp(); + Sqlite3_Init(interp); + + sqlite3_snprintf(sizeof(zArgc), zArgc, "%d", argc-1); + Tcl_SetVar(interp,"argc", zArgc, TCL_GLOBAL_ONLY); + Tcl_SetVar(interp,"argv0",argv[0],TCL_GLOBAL_ONLY); + Tcl_SetVar(interp,"argv", "", TCL_GLOBAL_ONLY); + for(i=1; i<argc; i++){ + Tcl_SetVar(interp, "argv", argv[i], + TCL_GLOBAL_ONLY | TCL_LIST_ELEMENT | TCL_APPEND_VALUE); + } +#if defined(TCLSH_INIT_PROC) + zScript = TCLSH_INIT_PROC(interp); +#endif + if( zScript==0 ){ + zScript = tclsh_main_loop(); + } + if( Tcl_GlobalEval(interp, zScript)!=TCL_OK ){ + const char *zInfo = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY); + if( zInfo==0 ) zInfo = Tcl_GetStringResult(interp); + fprintf(stderr,"%s: %s\n", *argv, zInfo); + return 1; + } + return 0; +} +#endif /* TCLSH */ diff --git a/SQLITE/tea/license.terms b/SQLITE/tea/license.terms new file mode 100644 index 0000000..723c4cd --- /dev/null +++ b/SQLITE/tea/license.terms @@ -0,0 +1,6 @@ +The author disclaims copyright to this source code. In place of +a legal notice, here is a blessing: + + May you do good and not evil. + May you find forgiveness for yourself and forgive others. + May you share freely, never taking more than you give. diff --git a/SQLITE/tea/pkgIndex.tcl.in b/SQLITE/tea/pkgIndex.tcl.in new file mode 100644 index 0000000..f95f7d3 --- /dev/null +++ b/SQLITE/tea/pkgIndex.tcl.in @@ -0,0 +1,10 @@ +# -*- tcl -*- +# Tcl package index file, version 1.1 +# +if {[package vsatisfies [package provide Tcl] 9.0-]} { + package ifneeded sqlite3 @PACKAGE_VERSION@ \ + [list load [file join $dir @PKG_LIB_FILE9@] sqlite3] +} else { + package ifneeded sqlite3 @PACKAGE_VERSION@ \ + [list load [file join $dir @PKG_LIB_FILE8@] sqlite3] +} diff --git a/SQLITE/tea/tclconfig/install-sh b/SQLITE/tea/tclconfig/install-sh new file mode 100644 index 0000000..7c34c3f --- /dev/null +++ b/SQLITE/tea/tclconfig/install-sh @@ -0,0 +1,528 @@ +#!/bin/sh +# install - install a program, script, or datafile + +scriptversion=2011-04-20.01; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. + +nl=' +' +IFS=" "" $nl" + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit=${DOITPROG-} +if test -z "$doit"; then + doit_exec=exec +else + doit_exec=$doit +fi + +# Put in absolute file names if you don't have them in your path; +# or use environment vars. + +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} + +posix_glob='?' +initialize_posix_glob=' + test "$posix_glob" != "?" || { + if (set -f) 2>/dev/null; then + posix_glob= + else + posix_glob=: + fi + } +' + +posix_mkdir= + +# Desired mode of installed file. +mode=0755 + +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog +rmcmd="$rmprog -f" +stripcmd= + +src= +dst= +dir_arg= +dst_arg= + +copy_on_change=false +no_target_directory= + +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... + +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. + +Options: + --help display this help and exit. + --version display version info and exit. + + -c (ignored) + -C install only if different (preserve the last data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -s $stripprog installed files. + -S $stripprog installed files. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. + +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG +" + +while test $# -ne 0; do + case $1 in + -c) ;; + + -C) copy_on_change=true;; + + -d) dir_arg=true;; + + -g) chgrpcmd="$chgrpprog $2" + shift;; + + --help) echo "$usage"; exit $?;; + + -m) mode=$2 + case $mode in + *' '* | *' '* | *' +'* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; + + -o) chowncmd="$chownprog $2" + shift;; + + -s) stripcmd=$stripprog;; + + -S) stripcmd="$stripprog $2" + shift;; + + -t) dst_arg=$2 + shift;; + + -T) no_target_directory=true;; + + --version) echo "$0 $scriptversion"; exit $?;; + + --) shift + break;; + + -*) echo "$0: invalid option: $1" >&2 + exit 1;; + + *) break;; + esac + shift +done + +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + done +fi + +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call `install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi + +for src +do + # Protect names starting with `-'. + case $src in + -*) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + + dst=$dst_arg + # Protect names starting with `-'. + case $dst in + -*) dst=./$dst;; + esac + + # If destination is a directory, append the input filename; won't work + # if double slashes aren't ignored. + if test -d "$dst"; then + if test -n "$no_target_directory"; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dst=$dstdir/`basename "$src"` + dstdir_status=0 + else + # Prefer dirname, but fall back on a substitute if dirname fails. + dstdir=` + (dirname "$dst") 2>/dev/null || + expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$dst" : 'X\(//\)[^/]' \| \ + X"$dst" : 'X\(//\)$' \| \ + X"$dst" : 'X\(/\)' \| . 2>/dev/null || + echo X"$dst" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q' + ` + + test -d "$dstdir" + dstdir_status=$? + fi + fi + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # Create intermediate dirs using mode 755 as modified by the umask. + # This is like FreeBSD 'install' as of 1997-10-28. + umask=`umask` + case $stripcmd.$umask in + # Optimize common cases. + *[2367][2367]) mkdir_umask=$umask;; + .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; + + *[0-7]) + mkdir_umask=`expr $umask + 22 \ + - $umask % 100 % 40 + $umask % 20 \ + - $umask % 10 % 4 + $umask % 2 + `;; + *) mkdir_umask=$umask,go-w;; + esac + + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + case $umask in + *[123567][0-7][0-7]) + # POSIX mkdir -p sets u+wx bits regardless of umask, which + # is incompatible with FreeBSD 'install' when (umask & 300) != 0. + ;; + *) + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 + + if (umask $mkdir_umask && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writeable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + ls_ld_tmpdir=`ls -ld "$tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/d" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null + fi + trap '' 0;; + esac;; + esac + + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # The umask is ridiculous, or mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + -*) prefix='./';; + *) prefix='';; + esac + + eval "$initialize_posix_glob" + + oIFS=$IFS + IFS=/ + $posix_glob set -f + set fnord $dstdir + shift + $posix_glob set +f + IFS=$oIFS + + prefixes= + + for d + do + test -z "$d" && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask=$mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=$dstdir/_inst.$$_ + rmtmp=$dstdir/_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + + eval "$initialize_posix_glob" && + $posix_glob set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + $posix_glob set +f && + + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd -f "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/SQLITE/tea/tclconfig/tcl.m4 b/SQLITE/tea/tclconfig/tcl.m4 new file mode 100644 index 0000000..c83d660 --- /dev/null +++ b/SQLITE/tea/tclconfig/tcl.m4 @@ -0,0 +1,4067 @@ +# tcl.m4 -- +# +# This file provides a set of autoconf macros to help TEA-enable +# a Tcl extension. +# +# Copyright (c) 1999-2000 Ajuba Solutions. +# Copyright (c) 2002-2005 ActiveState Corporation. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +AC_PREREQ([2.69]) + +# Possible values for key variables defined: +# +# TEA_WINDOWINGSYSTEM - win32 aqua x11 (mirrors 'tk windowingsystem') +# TEA_PLATFORM - windows unix +# TEA_TK_EXTENSION - True if this is a Tk extension +# + +#------------------------------------------------------------------------ +# TEA_PATH_TCLCONFIG -- +# +# Locate the tclConfig.sh file and perform a sanity check on +# the Tcl compile flags +# +# Arguments: +# none +# +# Results: +# +# Adds the following arguments to configure: +# --with-tcl=... +# +# Defines the following vars: +# TCL_BIN_DIR Full path to the directory containing +# the tclConfig.sh file +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_PATH_TCLCONFIG], [ + dnl TEA specific: Make sure we are initialized + AC_REQUIRE([TEA_INIT]) + # + # Ok, lets find the tcl configuration + # First, look for one uninstalled. + # the alternative search directory is invoked by --with-tcl + # + + if test x"${no_tcl}" = x ; then + # we reset no_tcl in case something fails here + no_tcl=true + AC_ARG_WITH(tcl, + AS_HELP_STRING([--with-tcl], + [directory containing tcl configuration (tclConfig.sh)]), + [with_tclconfig="${withval}"]) + AC_MSG_CHECKING([for Tcl configuration]) + AC_CACHE_VAL(ac_cv_c_tclconfig,[ + + # First check to see if --with-tcl was specified. + if test x"${with_tclconfig}" != x ; then + case "${with_tclconfig}" in + */tclConfig.sh ) + if test -f "${with_tclconfig}"; then + AC_MSG_WARN([--with-tcl argument should refer to directory containing tclConfig.sh, not to tclConfig.sh itself]) + with_tclconfig="`echo "${with_tclconfig}" | sed 's!/tclConfig\.sh$!!'`" + fi ;; + esac + if test -f "${with_tclconfig}/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd "${with_tclconfig}"; pwd)`" + else + AC_MSG_ERROR([${with_tclconfig} directory doesn't contain tclConfig.sh]) + fi + fi + + # then check for a private Tcl installation + if test x"${ac_cv_c_tclconfig}" = x ; then + for i in \ + ../tcl \ + `ls -dr ../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../tcl[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../tcl[[8-9]].[[0-9]]* 2>/dev/null` \ + ../../tcl \ + `ls -dr ../../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../../tcl[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../../tcl[[8-9]].[[0-9]]* 2>/dev/null` \ + ../../../tcl \ + `ls -dr ../../../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../../../tcl[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../../../tcl[[8-9]].[[0-9]]* 2>/dev/null` ; do + if test "${TEA_PLATFORM}" = "windows" \ + -a -f "$i/win/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/win; pwd)`" + break + fi + if test -f "$i/unix/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/unix; pwd)`" + break + fi + done + fi + + # on Darwin, check in Framework installation locations + if test "`uname -s`" = "Darwin" -a x"${ac_cv_c_tclconfig}" = x ; then + for i in `ls -d ~/Library/Frameworks 2>/dev/null` \ + `ls -d /Library/Frameworks 2>/dev/null` \ + `ls -d /Network/Library/Frameworks 2>/dev/null` \ + `ls -d /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/Library/Frameworks/Tcl.framework 2>/dev/null` \ + `ls -d /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/Network/Library/Frameworks/Tcl.framework 2>/dev/null` \ + `ls -d /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Tcl.framework 2>/dev/null` \ + ; do + if test -f "$i/Tcl.framework/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/Tcl.framework; pwd)`" + break + fi + done + fi + + # TEA specific: on Windows, check in common installation locations + if test "${TEA_PLATFORM}" = "windows" \ + -a x"${ac_cv_c_tclconfig}" = x ; then + for i in `ls -d C:/Tcl/lib 2>/dev/null` \ + `ls -d C:/Progra~1/Tcl/lib 2>/dev/null` \ + ; do + if test -f "$i/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i; pwd)`" + break + fi + done + fi + + # check in a few common install locations + if test x"${ac_cv_c_tclconfig}" = x ; then + for i in `ls -d ${libdir} 2>/dev/null` \ + `ls -d ${exec_prefix}/lib 2>/dev/null` \ + `ls -d ${prefix}/lib 2>/dev/null` \ + `ls -d /usr/local/lib 2>/dev/null` \ + `ls -d /usr/contrib/lib 2>/dev/null` \ + `ls -d /usr/pkg/lib 2>/dev/null` \ + `ls -d /usr/lib 2>/dev/null` \ + `ls -d /usr/lib64 2>/dev/null` \ + `ls -d /usr/lib/tcl8.6 2>/dev/null` \ + `ls -d /usr/lib/tcl8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl8.6 2>/dev/null` \ + `ls -d /usr/local/lib/tcl8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tcl8.6 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tcl8.5 2>/dev/null` \ + ; do + if test -f "$i/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i; pwd)`" + break + fi + done + fi + + # check in a few other private locations + if test x"${ac_cv_c_tclconfig}" = x ; then + for i in \ + ${srcdir}/../tcl \ + `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]]* 2>/dev/null` ; do + if test "${TEA_PLATFORM}" = "windows" \ + -a -f "$i/win/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/win; pwd)`" + break + fi + if test -f "$i/unix/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/unix; pwd)`" + break + fi + done + fi + ]) + + if test x"${ac_cv_c_tclconfig}" = x ; then + TCL_BIN_DIR="# no Tcl configs found" + AC_MSG_ERROR([Can't find Tcl configuration definitions. Use --with-tcl to specify a directory containing tclConfig.sh]) + else + no_tcl= + TCL_BIN_DIR="${ac_cv_c_tclconfig}" + AC_MSG_RESULT([found ${TCL_BIN_DIR}/tclConfig.sh]) + fi + fi +]) + +#------------------------------------------------------------------------ +# TEA_PATH_TKCONFIG -- +# +# Locate the tkConfig.sh file +# +# Arguments: +# none +# +# Results: +# +# Adds the following arguments to configure: +# --with-tk=... +# +# Defines the following vars: +# TK_BIN_DIR Full path to the directory containing +# the tkConfig.sh file +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_PATH_TKCONFIG], [ + # + # Ok, lets find the tk configuration + # First, look for one uninstalled. + # the alternative search directory is invoked by --with-tk + # + + if test x"${no_tk}" = x ; then + # we reset no_tk in case something fails here + no_tk=true + AC_ARG_WITH(tk, + AS_HELP_STRING([--with-tk], + [directory containing tk configuration (tkConfig.sh)]), + [with_tkconfig="${withval}"]) + AC_MSG_CHECKING([for Tk configuration]) + AC_CACHE_VAL(ac_cv_c_tkconfig,[ + + # First check to see if --with-tkconfig was specified. + if test x"${with_tkconfig}" != x ; then + case "${with_tkconfig}" in + */tkConfig.sh ) + if test -f "${with_tkconfig}"; then + AC_MSG_WARN([--with-tk argument should refer to directory containing tkConfig.sh, not to tkConfig.sh itself]) + with_tkconfig="`echo "${with_tkconfig}" | sed 's!/tkConfig\.sh$!!'`" + fi ;; + esac + if test -f "${with_tkconfig}/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd "${with_tkconfig}"; pwd)`" + else + AC_MSG_ERROR([${with_tkconfig} directory doesn't contain tkConfig.sh]) + fi + fi + + # then check for a private Tk library + if test x"${ac_cv_c_tkconfig}" = x ; then + for i in \ + ../tk \ + `ls -dr ../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../tk[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../tk[[8-9]].[[0-9]]* 2>/dev/null` \ + ../../tk \ + `ls -dr ../../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../../tk[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../../tk[[8-9]].[[0-9]]* 2>/dev/null` \ + ../../../tk \ + `ls -dr ../../../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../../../tk[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../../../tk[[8-9]].[[0-9]]* 2>/dev/null` ; do + if test "${TEA_PLATFORM}" = "windows" \ + -a -f "$i/win/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd $i/win; pwd)`" + break + fi + if test -f "$i/unix/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd $i/unix; pwd)`" + break + fi + done + fi + + # on Darwin, check in Framework installation locations + if test "`uname -s`" = "Darwin" -a x"${ac_cv_c_tkconfig}" = x ; then + for i in `ls -d ~/Library/Frameworks 2>/dev/null` \ + `ls -d /Library/Frameworks 2>/dev/null` \ + `ls -d /Network/Library/Frameworks 2>/dev/null` \ + ; do + if test -f "$i/Tk.framework/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd $i/Tk.framework; pwd)`" + break + fi + done + fi + + # check in a few common install locations + if test x"${ac_cv_c_tkconfig}" = x ; then + for i in `ls -d ${libdir} 2>/dev/null` \ + `ls -d ${exec_prefix}/lib 2>/dev/null` \ + `ls -d ${prefix}/lib 2>/dev/null` \ + `ls -d /usr/local/lib 2>/dev/null` \ + `ls -d /usr/contrib/lib 2>/dev/null` \ + `ls -d /usr/pkg/lib 2>/dev/null` \ + `ls -d /usr/lib/tk8.6 2>/dev/null` \ + `ls -d /usr/lib/tk8.5 2>/dev/null` \ + `ls -d /usr/lib 2>/dev/null` \ + `ls -d /usr/lib64 2>/dev/null` \ + `ls -d /usr/local/lib/tk8.6 2>/dev/null` \ + `ls -d /usr/local/lib/tk8.5 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tk8.6 2>/dev/null` \ + `ls -d /usr/local/lib/tcl/tk8.5 2>/dev/null` \ + ; do + if test -f "$i/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd $i; pwd)`" + break + fi + done + fi + + # TEA specific: on Windows, check in common installation locations + if test "${TEA_PLATFORM}" = "windows" \ + -a x"${ac_cv_c_tkconfig}" = x ; then + for i in `ls -d C:/Tcl/lib 2>/dev/null` \ + `ls -d C:/Progra~1/Tcl/lib 2>/dev/null` \ + ; do + if test -f "$i/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd $i; pwd)`" + break + fi + done + fi + + # check in a few other private locations + if test x"${ac_cv_c_tkconfig}" = x ; then + for i in \ + ${srcdir}/../tk \ + `ls -dr ${srcdir}/../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ${srcdir}/../tk[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ${srcdir}/../tk[[8-9]].[[0-9]]* 2>/dev/null` ; do + if test "${TEA_PLATFORM}" = "windows" \ + -a -f "$i/win/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd $i/win; pwd)`" + break + fi + if test -f "$i/unix/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd $i/unix; pwd)`" + break + fi + done + fi + ]) + + if test x"${ac_cv_c_tkconfig}" = x ; then + TK_BIN_DIR="# no Tk configs found" + AC_MSG_ERROR([Can't find Tk configuration definitions. Use --with-tk to specify a directory containing tkConfig.sh]) + else + no_tk= + TK_BIN_DIR="${ac_cv_c_tkconfig}" + AC_MSG_RESULT([found ${TK_BIN_DIR}/tkConfig.sh]) + fi + fi +]) + +#------------------------------------------------------------------------ +# TEA_LOAD_TCLCONFIG -- +# +# Load the tclConfig.sh file +# +# Arguments: +# +# Requires the following vars to be set: +# TCL_BIN_DIR +# +# Results: +# +# Substitutes the following vars: +# TCL_BIN_DIR +# TCL_SRC_DIR +# TCL_LIB_FILE +# TCL_ZIP_FILE +# TCL_ZIPFS_SUPPORT +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_LOAD_TCLCONFIG], [ + AC_MSG_CHECKING([for existence of ${TCL_BIN_DIR}/tclConfig.sh]) + + if test -f "${TCL_BIN_DIR}/tclConfig.sh" ; then + AC_MSG_RESULT([loading]) + . "${TCL_BIN_DIR}/tclConfig.sh" + else + AC_MSG_RESULT([could not find ${TCL_BIN_DIR}/tclConfig.sh]) + fi + + # If the TCL_BIN_DIR is the build directory (not the install directory), + # then set the common variable name to the value of the build variables. + # For example, the variable TCL_LIB_SPEC will be set to the value + # of TCL_BUILD_LIB_SPEC. An extension should make use of TCL_LIB_SPEC + # instead of TCL_BUILD_LIB_SPEC since it will work with both an + # installed and uninstalled version of Tcl. + if test -f "${TCL_BIN_DIR}/Makefile" ; then + TCL_LIB_SPEC="${TCL_BUILD_LIB_SPEC}" + TCL_STUB_LIB_SPEC="${TCL_BUILD_STUB_LIB_SPEC}" + TCL_STUB_LIB_PATH="${TCL_BUILD_STUB_LIB_PATH}" + elif test "`uname -s`" = "Darwin"; then + # If Tcl was built as a framework, attempt to use the libraries + # from the framework at the given location so that linking works + # against Tcl.framework installed in an arbitrary location. + case ${TCL_DEFS} in + *TCL_FRAMEWORK*) + if test -f "${TCL_BIN_DIR}/${TCL_LIB_FILE}"; then + for i in "`cd "${TCL_BIN_DIR}"; pwd`" \ + "`cd "${TCL_BIN_DIR}"/../..; pwd`"; do + if test "`basename "$i"`" = "${TCL_LIB_FILE}.framework"; then + TCL_LIB_SPEC="-F`dirname "$i" | sed -e 's/ /\\\\ /g'` -framework ${TCL_LIB_FILE}" + break + fi + done + fi + if test -f "${TCL_BIN_DIR}/${TCL_STUB_LIB_FILE}"; then + TCL_STUB_LIB_SPEC="-L`echo "${TCL_BIN_DIR}" | sed -e 's/ /\\\\ /g'` ${TCL_STUB_LIB_FLAG}" + TCL_STUB_LIB_PATH="${TCL_BIN_DIR}/${TCL_STUB_LIB_FILE}" + fi + ;; + esac + fi + + AC_SUBST(TCL_VERSION) + AC_SUBST(TCL_PATCH_LEVEL) + AC_SUBST(TCL_BIN_DIR) + AC_SUBST(TCL_SRC_DIR) + + AC_SUBST(TCL_LIB_FILE) + AC_SUBST(TCL_LIB_FLAG) + AC_SUBST(TCL_LIB_SPEC) + + AC_SUBST(TCL_STUB_LIB_FILE) + AC_SUBST(TCL_STUB_LIB_FLAG) + AC_SUBST(TCL_STUB_LIB_SPEC) + + AC_MSG_CHECKING([platform]) + hold_cc=$CC; CC="$TCL_CC" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ + #ifdef _WIN32 + #error win32 + #endif + ]])],[ + # first test we've already retrieved platform (cross-compile), fallback to unix otherwise: + TEA_PLATFORM="${TEA_PLATFORM-unix}" + CYGPATH=echo + ],[ + TEA_PLATFORM="windows" + AC_CHECK_PROG(CYGPATH, cygpath, cygpath -m, echo) + ]) + CC=$hold_cc + AC_MSG_RESULT($TEA_PLATFORM) + + # The BUILD_$pkg is to define the correct extern storage class + # handling when making this package + AC_DEFINE_UNQUOTED(BUILD_${PACKAGE_NAME}, [], + [Building extension source?]) + # Do this here as we have fully defined TEA_PLATFORM now + if test "${TEA_PLATFORM}" = "windows" ; then + EXEEXT=".exe" + CLEANFILES="$CLEANFILES *.lib *.dll *.pdb *.exp" + fi + + # TEA specific: + AC_SUBST(CLEANFILES) + AC_SUBST(TCL_LIBS) + AC_SUBST(TCL_DEFS) + AC_SUBST(TCL_EXTRA_CFLAGS) + AC_SUBST(TCL_LD_FLAGS) + AC_SUBST(TCL_SHLIB_LD_LIBS) +]) + +#------------------------------------------------------------------------ +# TEA_LOAD_TKCONFIG -- +# +# Load the tkConfig.sh file +# +# Arguments: +# +# Requires the following vars to be set: +# TK_BIN_DIR +# +# Results: +# +# Sets the following vars that should be in tkConfig.sh: +# TK_BIN_DIR +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_LOAD_TKCONFIG], [ + AC_MSG_CHECKING([for existence of ${TK_BIN_DIR}/tkConfig.sh]) + + if test -f "${TK_BIN_DIR}/tkConfig.sh" ; then + AC_MSG_RESULT([loading]) + . "${TK_BIN_DIR}/tkConfig.sh" + else + AC_MSG_RESULT([could not find ${TK_BIN_DIR}/tkConfig.sh]) + fi + + # If the TK_BIN_DIR is the build directory (not the install directory), + # then set the common variable name to the value of the build variables. + # For example, the variable TK_LIB_SPEC will be set to the value + # of TK_BUILD_LIB_SPEC. An extension should make use of TK_LIB_SPEC + # instead of TK_BUILD_LIB_SPEC since it will work with both an + # installed and uninstalled version of Tcl. + if test -f "${TK_BIN_DIR}/Makefile" ; then + TK_LIB_SPEC="${TK_BUILD_LIB_SPEC}" + TK_STUB_LIB_SPEC="${TK_BUILD_STUB_LIB_SPEC}" + TK_STUB_LIB_PATH="${TK_BUILD_STUB_LIB_PATH}" + elif test "`uname -s`" = "Darwin"; then + # If Tk was built as a framework, attempt to use the libraries + # from the framework at the given location so that linking works + # against Tk.framework installed in an arbitrary location. + case ${TK_DEFS} in + *TK_FRAMEWORK*) + if test -f "${TK_BIN_DIR}/${TK_LIB_FILE}"; then + for i in "`cd "${TK_BIN_DIR}"; pwd`" \ + "`cd "${TK_BIN_DIR}"/../..; pwd`"; do + if test "`basename "$i"`" = "${TK_LIB_FILE}.framework"; then + TK_LIB_SPEC="-F`dirname "$i" | sed -e 's/ /\\\\ /g'` -framework ${TK_LIB_FILE}" + break + fi + done + fi + if test -f "${TK_BIN_DIR}/${TK_STUB_LIB_FILE}"; then + TK_STUB_LIB_SPEC="-L` echo "${TK_BIN_DIR}" | sed -e 's/ /\\\\ /g'` ${TK_STUB_LIB_FLAG}" + TK_STUB_LIB_PATH="${TK_BIN_DIR}/${TK_STUB_LIB_FILE}" + fi + ;; + esac + fi + + # TEA specific: Ensure windowingsystem is defined + if test "${TEA_PLATFORM}" = "unix" ; then + case ${TK_DEFS} in + *MAC_OSX_TK*) + AC_DEFINE(MAC_OSX_TK, 1, [Are we building against Mac OS X TkAqua?]) + TEA_WINDOWINGSYSTEM="aqua" + ;; + *) + TEA_WINDOWINGSYSTEM="x11" + ;; + esac + elif test "${TEA_PLATFORM}" = "windows" ; then + TEA_WINDOWINGSYSTEM="win32" + fi + + AC_SUBST(TK_VERSION) + AC_SUBST(TK_BIN_DIR) + AC_SUBST(TK_SRC_DIR) + + AC_SUBST(TK_LIB_FILE) + AC_SUBST(TK_LIB_FLAG) + AC_SUBST(TK_LIB_SPEC) + + AC_SUBST(TK_STUB_LIB_FILE) + AC_SUBST(TK_STUB_LIB_FLAG) + AC_SUBST(TK_STUB_LIB_SPEC) + + # TEA specific: + AC_SUBST(TK_LIBS) + AC_SUBST(TK_XINCLUDES) +]) + +#------------------------------------------------------------------------ +# TEA_PROG_TCLSH +# Determine the fully qualified path name of the tclsh executable +# in the Tcl build directory or the tclsh installed in a bin +# directory. This macro will correctly determine the name +# of the tclsh executable even if tclsh has not yet been +# built in the build directory. The tclsh found is always +# associated with a tclConfig.sh file. This tclsh should be used +# only for running extension test cases. It should never be +# or generation of files (like pkgIndex.tcl) at build time. +# +# Arguments: +# none +# +# Results: +# Substitutes the following vars: +# TCLSH_PROG +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_PROG_TCLSH], [ + AC_MSG_CHECKING([for tclsh]) + if test -f "${TCL_BIN_DIR}/Makefile" ; then + # tclConfig.sh is in Tcl build directory + if test "${TEA_PLATFORM}" = "windows"; then + if test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}s${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}s${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}t${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}t${EXEEXT}" + elif test -f "${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}st${EXEEXT}" ; then + TCLSH_PROG="${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}st${EXEEXT}" + fi + else + TCLSH_PROG="${TCL_BIN_DIR}/tclsh" + fi + else + # tclConfig.sh is in install location + if test "${TEA_PLATFORM}" = "windows"; then + TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}${EXEEXT}" + else + TCLSH_PROG="tclsh${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}" + fi + list="`ls -d ${TCL_BIN_DIR}/../bin 2>/dev/null` \ + `ls -d ${TCL_BIN_DIR}/.. 2>/dev/null` \ + `ls -d ${TCL_PREFIX}/bin 2>/dev/null`" + for i in $list ; do + if test -f "$i/${TCLSH_PROG}" ; then + REAL_TCL_BIN_DIR="`cd "$i"; pwd`/" + break + fi + done + TCLSH_PROG="${REAL_TCL_BIN_DIR}${TCLSH_PROG}" + fi + AC_MSG_RESULT([${TCLSH_PROG}]) + AC_SUBST(TCLSH_PROG) +]) + +#------------------------------------------------------------------------ +# TEA_PROG_WISH +# Determine the fully qualified path name of the wish executable +# in the Tk build directory or the wish installed in a bin +# directory. This macro will correctly determine the name +# of the wish executable even if wish has not yet been +# built in the build directory. The wish found is always +# associated with a tkConfig.sh file. This wish should be used +# only for running extension test cases. It should never be +# or generation of files (like pkgIndex.tcl) at build time. +# +# Arguments: +# none +# +# Results: +# Substitutes the following vars: +# WISH_PROG +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_PROG_WISH], [ + AC_MSG_CHECKING([for wish]) + if test -f "${TK_BIN_DIR}/Makefile" ; then + # tkConfig.sh is in Tk build directory + if test "${TEA_PLATFORM}" = "windows"; then + if test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" + elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}s${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}$s{EXEEXT}" + elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}t${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}t${EXEEXT}" + elif test -f "${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}st${EXEEXT}" ; then + WISH_PROG="${TK_BIN_DIR}/wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}st${EXEEXT}" + fi + else + WISH_PROG="${TK_BIN_DIR}/wish" + fi + else + # tkConfig.sh is in install location + if test "${TEA_PLATFORM}" = "windows"; then + WISH_PROG="wish${TK_MAJOR_VERSION}${TK_MINOR_VERSION}${EXEEXT}" + else + WISH_PROG="wish${TK_MAJOR_VERSION}.${TK_MINOR_VERSION}" + fi + list="`ls -d ${TK_BIN_DIR}/../bin 2>/dev/null` \ + `ls -d ${TK_BIN_DIR}/.. 2>/dev/null` \ + `ls -d ${TK_PREFIX}/bin 2>/dev/null`" + for i in $list ; do + if test -f "$i/${WISH_PROG}" ; then + REAL_TK_BIN_DIR="`cd "$i"; pwd`/" + break + fi + done + WISH_PROG="${REAL_TK_BIN_DIR}${WISH_PROG}" + fi + AC_MSG_RESULT([${WISH_PROG}]) + AC_SUBST(WISH_PROG) +]) + +#------------------------------------------------------------------------ +# TEA_ENABLE_SHARED -- +# +# Allows the building of shared libraries +# +# Arguments: +# none +# +# Results: +# +# Adds the following arguments to configure: +# --enable-shared=yes|no +# --enable-stubs=yes|no +# +# Defines the following vars: +# STATIC_BUILD Used for building import/export libraries +# on Windows. +# +# Sets the following vars: +# SHARED_BUILD Value of 1 or 0 +# STUBS_BUILD Value if 1 or 0 +# USE_TCL_STUBS Value true: if SHARED_BUILD or --enable-stubs +# USE_TCLOO_STUBS Value true: if SHARED_BUILD or --enable-stubs +# USE_TK_STUBS Value true: if SHARED_BUILD or --enable-stubs +# AND TEA_WINDOWING_SYSTEM != "" +#------------------------------------------------------------------------ +AC_DEFUN([TEA_ENABLE_SHARED], [ + AC_MSG_CHECKING([how to build libraries]) + AC_ARG_ENABLE(shared, + AS_HELP_STRING([--enable-shared], + [build and link with shared libraries (default: on)]), + [shared_ok=$enableval], [shared_ok=yes]) + + if test "${enable_shared+set}" = set; then + enableval="$enable_shared" + shared_ok=$enableval + else + shared_ok=yes + fi + + AC_ARG_ENABLE(stubs, + AS_HELP_STRING([--enable-stubs], + [build and link with stub libraries. Always true for shared builds (default: on)]), + [stubs_ok=$enableval], [stubs_ok=yes]) + + if test "${enable_stubs+set}" = set; then + enableval="$enable_stubs" + stubs_ok=$enableval + else + stubs_ok=yes + fi + + # Stubs are always enabled for shared builds + if test "$shared_ok" = "yes" ; then + AC_MSG_RESULT([shared]) + SHARED_BUILD=1 + STUBS_BUILD=1 + else + AC_MSG_RESULT([static]) + SHARED_BUILD=0 + AC_DEFINE(STATIC_BUILD, 1, [This a static build]) + if test "$stubs_ok" = "yes" ; then + STUBS_BUILD=1 + else + STUBS_BUILD=0 + fi + fi + if test "${STUBS_BUILD}" = "1" ; then + AC_DEFINE(USE_TCL_STUBS, 1, [Use Tcl stubs]) + AC_DEFINE(USE_TCLOO_STUBS, 1, [Use TclOO stubs]) + if test "${TEA_WINDOWINGSYSTEM}" != ""; then + AC_DEFINE(USE_TK_STUBS, 1, [Use Tk stubs]) + fi + fi + + AC_SUBST(SHARED_BUILD) + AC_SUBST(STUBS_BUILD) +]) + +#------------------------------------------------------------------------ +# TEA_ENABLE_THREADS -- +# +# Specify if thread support should be enabled. If "yes" is specified +# as an arg (optional), threads are enabled by default, "no" means +# threads are disabled. "yes" is the default. +# +# TCL_THREADS is checked so that if you are compiling an extension +# against a threaded core, your extension must be compiled threaded +# as well. +# +# Note that it is legal to have a thread enabled extension run in a +# threaded or non-threaded Tcl core, but a non-threaded extension may +# only run in a non-threaded Tcl core. +# +# Arguments: +# none +# +# Results: +# +# Adds the following arguments to configure: +# --enable-threads +# +# Sets the following vars: +# THREADS_LIBS Thread library(s) +# +# Defines the following vars: +# TCL_THREADS +# _REENTRANT +# _THREAD_SAFE +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_ENABLE_THREADS], [ + AC_ARG_ENABLE(threads, + AS_HELP_STRING([--enable-threads], + [build with threads (default: on)]), + [tcl_ok=$enableval], [tcl_ok=yes]) + + if test "${enable_threads+set}" = set; then + enableval="$enable_threads" + tcl_ok=$enableval + else + tcl_ok=yes + fi + + if test "$tcl_ok" = "yes" -o "${TCL_THREADS}" = 1; then + TCL_THREADS=1 + + if test "${TEA_PLATFORM}" != "windows" ; then + # We are always OK on Windows, so check what this platform wants: + + # USE_THREAD_ALLOC tells us to try the special thread-based + # allocator that significantly reduces lock contention + AC_DEFINE(USE_THREAD_ALLOC, 1, + [Do we want to use the threaded memory allocator?]) + AC_DEFINE(_REENTRANT, 1, [Do we want the reentrant OS API?]) + if test "`uname -s`" = "SunOS" ; then + AC_DEFINE(_POSIX_PTHREAD_SEMANTICS, 1, + [Do we really want to follow the standard? Yes we do!]) + fi + AC_DEFINE(_THREAD_SAFE, 1, [Do we want the thread-safe OS API?]) + AC_CHECK_LIB(pthread,pthread_mutex_init,tcl_ok=yes,tcl_ok=no) + if test "$tcl_ok" = "no"; then + # Check a little harder for __pthread_mutex_init in the same + # library, as some systems hide it there until pthread.h is + # defined. We could alternatively do an AC_TRY_COMPILE with + # pthread.h, but that will work with libpthread really doesn't + # exist, like AIX 4.2. [Bug: 4359] + AC_CHECK_LIB(pthread, __pthread_mutex_init, + tcl_ok=yes, tcl_ok=no) + fi + + if test "$tcl_ok" = "yes"; then + # The space is needed + THREADS_LIBS=" -lpthread" + else + AC_CHECK_LIB(pthreads, pthread_mutex_init, + tcl_ok=yes, tcl_ok=no) + if test "$tcl_ok" = "yes"; then + # The space is needed + THREADS_LIBS=" -lpthreads" + else + AC_CHECK_LIB(c, pthread_mutex_init, + tcl_ok=yes, tcl_ok=no) + if test "$tcl_ok" = "no"; then + AC_CHECK_LIB(c_r, pthread_mutex_init, + tcl_ok=yes, tcl_ok=no) + if test "$tcl_ok" = "yes"; then + # The space is needed + THREADS_LIBS=" -pthread" + else + TCL_THREADS=0 + AC_MSG_WARN([Do not know how to find pthread lib on your system - thread support disabled]) + fi + fi + fi + fi + fi + else + TCL_THREADS=0 + fi + # Do checking message here to not mess up interleaved configure output + AC_MSG_CHECKING([for building with threads]) + if test "${TCL_THREADS}" = 1; then + AC_DEFINE(TCL_THREADS, 1, [Are we building with threads enabled?]) + AC_MSG_RESULT([yes (default)]) + else + AC_MSG_RESULT([no]) + fi + # TCL_THREADS sanity checking. See if our request for building with + # threads is the same as the way Tcl was built. If not, warn the user. + case ${TCL_DEFS} in + *THREADS=1*) + if test "${TCL_THREADS}" = "0"; then + AC_MSG_WARN([ + Building ${PACKAGE_NAME} without threads enabled, but building against Tcl + that IS thread-enabled. It is recommended to use --enable-threads.]) + fi + ;; + esac + AC_SUBST(TCL_THREADS) +]) + +#------------------------------------------------------------------------ +# TEA_ENABLE_SYMBOLS -- +# +# Specify if debugging symbols should be used. +# Memory (TCL_MEM_DEBUG) debugging can also be enabled. +# +# Arguments: +# none +# +# TEA varies from core Tcl in that C|LDFLAGS_DEFAULT receives +# the value of C|LDFLAGS_OPTIMIZE|DEBUG already substituted. +# Requires the following vars to be set in the Makefile: +# CFLAGS_DEFAULT +# LDFLAGS_DEFAULT +# +# Results: +# +# Adds the following arguments to configure: +# --enable-symbols +# +# Defines the following vars: +# CFLAGS_DEFAULT Sets to $(CFLAGS_DEBUG) if true +# Sets to "$(CFLAGS_OPTIMIZE) -DNDEBUG" if false +# LDFLAGS_DEFAULT Sets to $(LDFLAGS_DEBUG) if true +# Sets to $(LDFLAGS_OPTIMIZE) if false +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_ENABLE_SYMBOLS], [ + dnl TEA specific: Make sure we are initialized + AC_REQUIRE([TEA_CONFIG_CFLAGS]) + AC_MSG_CHECKING([for build with symbols]) + AC_ARG_ENABLE(symbols, + AS_HELP_STRING([--enable-symbols], + [build with debugging symbols (default: off)]), + [tcl_ok=$enableval], [tcl_ok=no]) + if test "$tcl_ok" = "no"; then + CFLAGS_DEFAULT="${CFLAGS_OPTIMIZE} -DNDEBUG" + LDFLAGS_DEFAULT="${LDFLAGS_OPTIMIZE}" + AC_MSG_RESULT([no]) + AC_DEFINE(TCL_CFG_OPTIMIZED, 1, [Is this an optimized build?]) + else + CFLAGS_DEFAULT="${CFLAGS_DEBUG}" + LDFLAGS_DEFAULT="${LDFLAGS_DEBUG}" + if test "$tcl_ok" = "yes"; then + AC_MSG_RESULT([yes (standard debugging)]) + fi + fi + AC_SUBST(CFLAGS_DEFAULT) + AC_SUBST(LDFLAGS_DEFAULT) + + if test "$tcl_ok" = "mem" -o "$tcl_ok" = "all"; then + AC_DEFINE(TCL_MEM_DEBUG, 1, [Is memory debugging enabled?]) + fi + + if test "$tcl_ok" != "yes" -a "$tcl_ok" != "no"; then + if test "$tcl_ok" = "all"; then + AC_MSG_RESULT([enabled symbols mem debugging]) + else + AC_MSG_RESULT([enabled $tcl_ok debugging]) + fi + fi +]) + +#------------------------------------------------------------------------ +# TEA_ENABLE_LANGINFO -- +# +# Allows use of modern nl_langinfo check for better l10n. +# This is only relevant for Unix. +# +# Arguments: +# none +# +# Results: +# +# Adds the following arguments to configure: +# --enable-langinfo=yes|no (default is yes) +# +# Defines the following vars: +# HAVE_LANGINFO Triggers use of nl_langinfo if defined. +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_ENABLE_LANGINFO], [ + AC_ARG_ENABLE(langinfo, + AS_HELP_STRING([--enable-langinfo], + [use nl_langinfo if possible to determine encoding at startup, otherwise use old heuristic (default: on)]), + [langinfo_ok=$enableval], [langinfo_ok=yes]) + + HAVE_LANGINFO=0 + if test "$langinfo_ok" = "yes"; then + AC_CHECK_HEADER(langinfo.h,[langinfo_ok=yes],[langinfo_ok=no]) + fi + AC_MSG_CHECKING([whether to use nl_langinfo]) + if test "$langinfo_ok" = "yes"; then + AC_CACHE_VAL(tcl_cv_langinfo_h, [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <langinfo.h>]], [[nl_langinfo(CODESET);]])], + [tcl_cv_langinfo_h=yes],[tcl_cv_langinfo_h=no])]) + AC_MSG_RESULT([$tcl_cv_langinfo_h]) + if test $tcl_cv_langinfo_h = yes; then + AC_DEFINE(HAVE_LANGINFO, 1, [Do we have nl_langinfo()?]) + fi + else + AC_MSG_RESULT([$langinfo_ok]) + fi +]) + +#-------------------------------------------------------------------- +# TEA_CONFIG_SYSTEM +# +# Determine what the system is (some things cannot be easily checked +# on a feature-driven basis, alas). This can usually be done via the +# "uname" command. +# +# Arguments: +# none +# +# Results: +# Defines the following var: +# +# system - System/platform/version identification code. +# +#-------------------------------------------------------------------- + +AC_DEFUN([TEA_CONFIG_SYSTEM], [ + AC_CACHE_CHECK([system version], tcl_cv_sys_version, [ + # TEA specific: + if test "${TEA_PLATFORM}" = "windows" ; then + tcl_cv_sys_version=windows + else + tcl_cv_sys_version=`uname -s`-`uname -r` + if test "$?" -ne 0 ; then + AC_MSG_WARN([can't find uname command]) + tcl_cv_sys_version=unknown + else + if test "`uname -s`" = "AIX" ; then + tcl_cv_sys_version=AIX-`uname -v`.`uname -r` + fi + if test "`uname -s`" = "NetBSD" -a -f /etc/debian_version ; then + tcl_cv_sys_version=NetBSD-Debian + fi + fi + fi + ]) + system=$tcl_cv_sys_version +]) + +#-------------------------------------------------------------------- +# TEA_CONFIG_CFLAGS +# +# Try to determine the proper flags to pass to the compiler +# for building shared libraries and other such nonsense. +# +# Arguments: +# none +# +# Results: +# +# Defines and substitutes the following vars: +# +# DL_OBJS, DL_LIBS - removed for TEA, only needed by core. +# LDFLAGS - Flags to pass to the compiler when linking object +# files into an executable application binary such +# as tclsh. +# LD_SEARCH_FLAGS-Flags to pass to ld, such as "-R /usr/local/tcl/lib", +# that tell the run-time dynamic linker where to look +# for shared libraries such as libtcl.so. Depends on +# the variable LIB_RUNTIME_DIR in the Makefile. Could +# be the same as CC_SEARCH_FLAGS if ${CC} is used to link. +# CC_SEARCH_FLAGS-Flags to pass to ${CC}, such as "-Wl,-rpath,/usr/local/tcl/lib", +# that tell the run-time dynamic linker where to look +# for shared libraries such as libtcl.so. Depends on +# the variable LIB_RUNTIME_DIR in the Makefile. +# SHLIB_CFLAGS - Flags to pass to cc when compiling the components +# of a shared library (may request position-independent +# code, among other things). +# SHLIB_LD - Base command to use for combining object files +# into a shared library. +# SHLIB_LD_LIBS - Dependent libraries for the linker to scan when +# creating shared libraries. This symbol typically +# goes at the end of the "ld" commands that build +# shared libraries. The value of the symbol defaults to +# "${LIBS}" if all of the dependent libraries should +# be specified when creating a shared library. If +# dependent libraries should not be specified (as on +# SunOS 4.x, where they cause the link to fail, or in +# general if Tcl and Tk aren't themselves shared +# libraries), then this symbol has an empty string +# as its value. +# SHLIB_SUFFIX - Suffix to use for the names of dynamically loadable +# extensions. An empty string means we don't know how +# to use shared libraries on this platform. +# LIB_SUFFIX - Specifies everything that comes after the "libfoo" +# in a static or shared library name, using the $PACKAGE_VERSION variable +# to put the version in the right place. This is used +# by platforms that need non-standard library names. +# Examples: ${PACKAGE_VERSION}.so.1.1 on NetBSD, since it needs +# to have a version after the .so, and ${PACKAGE_VERSION}.a +# on AIX, since a shared library needs to have +# a .a extension whereas shared objects for loadable +# extensions have a .so extension. Defaults to +# ${PACKAGE_VERSION}${SHLIB_SUFFIX}. +# CFLAGS_DEBUG - +# Flags used when running the compiler in debug mode +# CFLAGS_OPTIMIZE - +# Flags used when running the compiler in optimize mode +# CFLAGS - Additional CFLAGS added as necessary (usually 64-bit) +#-------------------------------------------------------------------- + +AC_DEFUN([TEA_CONFIG_CFLAGS], [ + dnl TEA specific: Make sure we are initialized + AC_REQUIRE([TEA_INIT]) + + # Step 0.a: Enable 64 bit support? + + AC_MSG_CHECKING([if 64bit support is requested]) + AC_ARG_ENABLE(64bit, + AS_HELP_STRING([--enable-64bit], + [enable 64bit support (default: off)]), + [do64bit=$enableval], [do64bit=no]) + AC_MSG_RESULT([$do64bit]) + + # Step 0.b: Enable Solaris 64 bit VIS support? + + AC_MSG_CHECKING([if 64bit Sparc VIS support is requested]) + AC_ARG_ENABLE(64bit-vis, + AS_HELP_STRING([--enable-64bit-vis], + [enable 64bit Sparc VIS support (default: off)]), + [do64bitVIS=$enableval], [do64bitVIS=no]) + AC_MSG_RESULT([$do64bitVIS]) + # Force 64bit on with VIS + AS_IF([test "$do64bitVIS" = "yes"], [do64bit=yes]) + + # Step 0.c: Check if visibility support is available. Do this here so + # that platform specific alternatives can be used below if this fails. + + AC_CACHE_CHECK([if compiler supports visibility "hidden"], + tcl_cv_cc_visibility_hidden, [ + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + extern __attribute__((__visibility__("hidden"))) void f(void); + void f(void) {}]], [[f();]])],[tcl_cv_cc_visibility_hidden=yes], + [tcl_cv_cc_visibility_hidden=no]) + CFLAGS=$hold_cflags]) + AS_IF([test $tcl_cv_cc_visibility_hidden = yes], [ + AC_DEFINE(MODULE_SCOPE, + [extern __attribute__((__visibility__("hidden")))], + [Compiler support for module scope symbols]) + AC_DEFINE(HAVE_HIDDEN, [1], [Compiler support for module scope symbols]) + ]) + + # Step 0.d: Disable -rpath support? + + AC_MSG_CHECKING([if rpath support is requested]) + AC_ARG_ENABLE(rpath, + AS_HELP_STRING([--disable-rpath], + [disable rpath support (default: on)]), + [doRpath=$enableval], [doRpath=yes]) + AC_MSG_RESULT([$doRpath]) + + # Set the variable "system" to hold the name and version number + # for the system. + + TEA_CONFIG_SYSTEM + + # Require ranlib early so we can override it in special cases below. + + AC_REQUIRE([AC_PROG_RANLIB]) + + # Set configuration options based on system name and version. + # This is similar to Tcl's unix/tcl.m4 except that we've added a + # "windows" case and removed some core-only vars. + + do64bit_ok=no + # default to '{$LIBS}' and set to "" on per-platform necessary basis + SHLIB_LD_LIBS='${LIBS}' + # When ld needs options to work in 64-bit mode, put them in + # LDFLAGS_ARCH so they eventually end up in LDFLAGS even if [load] + # is disabled by the user. [Bug 1016796] + LDFLAGS_ARCH="" + UNSHARED_LIB_SUFFIX="" + # TEA specific: use PACKAGE_VERSION instead of VERSION + TCL_TRIM_DOTS='`echo ${PACKAGE_VERSION} | tr -d .`' + ECHO_VERSION='`echo ${PACKAGE_VERSION}`' + TCL_LIB_VERSIONS_OK=ok + CFLAGS_DEBUG=-g + AS_IF([test "$GCC" = yes], [ + CFLAGS_OPTIMIZE=-O2 + CFLAGS_WARNING="-Wall" + ], [ + CFLAGS_OPTIMIZE=-O + CFLAGS_WARNING="" + ]) + AC_CHECK_TOOL(AR, ar) + STLIB_LD='${AR} cr' + LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH" + AS_IF([test "x$SHLIB_VERSION" = x],[SHLIB_VERSION=""],[SHLIB_VERSION=".$SHLIB_VERSION"]) + case $system in + # TEA specific: + windows) + MACHINE="X86" + if test "$do64bit" != "no" ; then + case "$do64bit" in + amd64|x64|yes) + MACHINE="AMD64" ; # default to AMD64 64-bit build + ;; + arm64|aarch64) + MACHINE="ARM64" + ;; + ia64) + MACHINE="IA64" + ;; + esac + fi + + if test "$GCC" != "yes" ; then + if test "${SHARED_BUILD}" = "0" ; then + runtime=-MT + else + runtime=-MD + fi + case "x`echo \${VisualStudioVersion}`" in + x1[[4-9]]*) + lflags="${lflags} -nodefaultlib:libucrt.lib" + TEA_ADD_LIBS([ucrt.lib]) + ;; + *) + ;; + esac + + if test "$do64bit" != "no" ; then + CC="cl.exe" + RC="rc.exe" + lflags="${lflags} -nologo -MACHINE:${MACHINE} " + LINKBIN="link.exe" + CFLAGS_DEBUG="-nologo -Zi -Od -W3 ${runtime}d" + CFLAGS_OPTIMIZE="-nologo -O2 -W2 ${runtime}" + # Avoid 'unresolved external symbol __security_cookie' + # errors, c.f. http://support.microsoft.com/?id=894573 + TEA_ADD_LIBS([bufferoverflowU.lib]) + else + RC="rc" + lflags="${lflags} -nologo" + LINKBIN="link" + CFLAGS_DEBUG="-nologo -Z7 -Od -W3 -WX ${runtime}d" + CFLAGS_OPTIMIZE="-nologo -O2 -W2 ${runtime}" + fi + fi + + if test "$GCC" = "yes"; then + # mingw gcc mode + AC_CHECK_TOOL(RC, windres) + CFLAGS_DEBUG="-g" + CFLAGS_OPTIMIZE="-O2 -fomit-frame-pointer" + SHLIB_LD='${CC} -shared' + UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.a' + LDFLAGS_CONSOLE="-wl,--subsystem,console ${lflags}" + LDFLAGS_WINDOW="-wl,--subsystem,windows ${lflags}" + + AC_CACHE_CHECK(for cross-compile version of gcc, + ac_cv_cross, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #ifdef _WIN32 + #error cross-compiler + #endif + ]], [[]])], + [ac_cv_cross=yes], + [ac_cv_cross=no]) + ) + if test "$ac_cv_cross" = "yes"; then + case "$do64bit" in + amd64|x64|yes) + CC="x86_64-w64-mingw32-${CC}" + LD="x86_64-w64-mingw32-ld" + AR="x86_64-w64-mingw32-ar" + RANLIB="x86_64-w64-mingw32-ranlib" + RC="x86_64-w64-mingw32-windres" + ;; + arm64|aarch64) + CC="aarch64-w64-mingw32-clang" + LD="aarch64-w64-mingw32-ld" + AR="aarch64-w64-mingw32-ar" + RANLIB="aarch64-w64-mingw32-ranlib" + RC="aarch64-w64-mingw32-windres" + ;; + *) + CC="i686-w64-mingw32-${CC}" + LD="i686-w64-mingw32-ld" + AR="i686-w64-mingw32-ar" + RANLIB="i686-w64-mingw32-ranlib" + RC="i686-w64-mingw32-windres" + ;; + esac + fi + + else + SHLIB_LD="${LINKBIN} -dll ${lflags}" + # link -lib only works when -lib is the first arg + STLIB_LD="${LINKBIN} -lib ${lflags}" + UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.lib' + PATHTYPE=-w + # For information on what debugtype is most useful, see: + # http://msdn.microsoft.com/library/en-us/dnvc60/html/gendepdebug.asp + # and also + # http://msdn2.microsoft.com/en-us/library/y0zzbyt4%28VS.80%29.aspx + # This essentially turns it all on. + LDFLAGS_DEBUG="-debug -debugtype:cv" + LDFLAGS_OPTIMIZE="-release" + LDFLAGS_CONSOLE="-link -subsystem:console ${lflags}" + LDFLAGS_WINDOW="-link -subsystem:windows ${lflags}" + fi + + SHLIB_SUFFIX=".dll" + SHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.dll' + + TCL_LIB_VERSIONS_OK=nodots + ;; + AIX-*) + AS_IF([test "$GCC" != "yes"], [ + # AIX requires the _r compiler when gcc isn't being used + case "${CC}" in + *_r|*_r\ *) + # ok ... + ;; + *) + # Make sure only first arg gets _r + CC=`echo "$CC" | sed -e 's/^\([[^ ]]*\)/\1_r/'` + ;; + esac + AC_MSG_RESULT([Using $CC for compiling with threads]) + ]) + LIBS="$LIBS -lc" + SHLIB_CFLAGS="" + SHLIB_SUFFIX=".so" + + LD_LIBRARY_PATH_VAR="LIBPATH" + + # Check to enable 64-bit flags for compiler/linker + AS_IF([test "$do64bit" = yes], [ + AS_IF([test "$GCC" = yes], [ + AC_MSG_WARN([64bit mode not supported with GCC on $system]) + ], [ + do64bit_ok=yes + CFLAGS="$CFLAGS -q64" + LDFLAGS_ARCH="-q64" + RANLIB="${RANLIB} -X64" + AR="${AR} -X64" + SHLIB_LD_FLAGS="-b64" + ]) + ]) + + AS_IF([test "`uname -m`" = ia64], [ + # AIX-5 uses ELF style dynamic libraries on IA-64, but not PPC + SHLIB_LD="/usr/ccs/bin/ld -G -z text" + AS_IF([test "$GCC" = yes], [ + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' + ], [ + CC_SEARCH_FLAGS='"-R${LIB_RUNTIME_DIR}"' + ]) + LD_SEARCH_FLAGS='-R "${LIB_RUNTIME_DIR}"' + ], [ + AS_IF([test "$GCC" = yes], [ + SHLIB_LD='${CC} -shared -Wl,-bexpall' + ], [ + SHLIB_LD="/bin/ld -bhalt:4 -bM:SRE -bexpall -H512 -T512 -bnoentry" + LDFLAGS="$LDFLAGS -brtl" + ]) + SHLIB_LD="${SHLIB_LD} ${SHLIB_LD_FLAGS}" + CC_SEARCH_FLAGS='"-L${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + ]) + ;; + BeOS*) + SHLIB_CFLAGS="-fPIC" + SHLIB_LD='${CC} -nostart' + SHLIB_SUFFIX=".so" + + #----------------------------------------------------------- + # Check for inet_ntoa in -lbind, for BeOS (which also needs + # -lsocket, even if the network functions are in -lnet which + # is always linked to, for compatibility. + #----------------------------------------------------------- + AC_CHECK_LIB(bind, inet_ntoa, [LIBS="$LIBS -lbind -lsocket"]) + ;; + BSD/OS-2.1*|BSD/OS-3*) + SHLIB_CFLAGS="" + SHLIB_LD="shlicc -r" + SHLIB_SUFFIX=".so" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + BSD/OS-4.*) + SHLIB_CFLAGS="-export-dynamic -fPIC" + SHLIB_LD='${CC} -shared' + SHLIB_SUFFIX=".so" + LDFLAGS="$LDFLAGS -export-dynamic" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + CYGWIN_*) + SHLIB_CFLAGS="" + SHLIB_LD='${CC} -shared' + SHLIB_SUFFIX=".dll" + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} -Wl,--out-implib,\$[@].a" + EXEEXT=".exe" + do64bit_ok=yes + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + dgux*) + SHLIB_CFLAGS="-K PIC" + SHLIB_LD='${CC} -G' + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + Haiku*) + LDFLAGS="$LDFLAGS -Wl,--export-dynamic" + SHLIB_CFLAGS="-fPIC" + SHLIB_SUFFIX=".so" + SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS} -shared' + AC_CHECK_LIB(network, inet_ntoa, [LIBS="$LIBS -lnetwork"]) + ;; + HP-UX-*.11.*) + # Use updated header definitions where possible + AC_DEFINE(_XOPEN_SOURCE_EXTENDED, 1, [Do we want to use the XOPEN network library?]) + # TEA specific: Needed by Tcl, but not most extensions + #AC_DEFINE(_XOPEN_SOURCE, 1, [Do we want to use the XOPEN network library?]) + #LIBS="$LIBS -lxnet" # Use the XOPEN network library + + AS_IF([test "`uname -m`" = ia64], [ + SHLIB_SUFFIX=".so" + ], [ + SHLIB_SUFFIX=".sl" + ]) + AC_CHECK_LIB(dld, shl_load, tcl_ok=yes, tcl_ok=no) + AS_IF([test "$tcl_ok" = yes], [ + SHLIB_CFLAGS="+z" + SHLIB_LD="ld -b" + LDFLAGS="$LDFLAGS -Wl,-E" + CC_SEARCH_FLAGS='"-Wl,+s,+b,${LIB_RUNTIME_DIR}:."' + LD_SEARCH_FLAGS='+s +b "${LIB_RUNTIME_DIR}:."' + LD_LIBRARY_PATH_VAR="SHLIB_PATH" + ]) + AS_IF([test "$GCC" = yes], [ + SHLIB_LD='${CC} -shared' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + ], [ + CFLAGS="$CFLAGS -z" + ]) + + # Check to enable 64-bit flags for compiler/linker + AS_IF([test "$do64bit" = "yes"], [ + AS_IF([test "$GCC" = yes], [ + case `${CC} -dumpmachine` in + hppa64*) + # 64-bit gcc in use. Fix flags for GNU ld. + do64bit_ok=yes + SHLIB_LD='${CC} -shared' + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + ;; + *) + AC_MSG_WARN([64bit mode not supported with GCC on $system]) + ;; + esac + ], [ + do64bit_ok=yes + CFLAGS="$CFLAGS +DD64" + LDFLAGS_ARCH="+DD64" + ]) + ]) ;; + HP-UX-*.08.*|HP-UX-*.09.*|HP-UX-*.10.*) + SHLIB_SUFFIX=".sl" + AC_CHECK_LIB(dld, shl_load, tcl_ok=yes, tcl_ok=no) + AS_IF([test "$tcl_ok" = yes], [ + SHLIB_CFLAGS="+z" + SHLIB_LD="ld -b" + SHLIB_LD_LIBS="" + LDFLAGS="$LDFLAGS -Wl,-E" + CC_SEARCH_FLAGS='"-Wl,+s,+b,${LIB_RUNTIME_DIR}:."' + LD_SEARCH_FLAGS='+s +b "${LIB_RUNTIME_DIR}:."' + LD_LIBRARY_PATH_VAR="SHLIB_PATH" + ]) ;; + IRIX-5.*) + SHLIB_CFLAGS="" + SHLIB_LD="ld -shared -rdata_shared" + SHLIB_SUFFIX=".so" + AC_LIBOBJ(mkstemp) + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath "${LIB_RUNTIME_DIR}"']) + ;; + IRIX-6.*) + SHLIB_CFLAGS="" + SHLIB_LD="ld -n32 -shared -rdata_shared" + SHLIB_SUFFIX=".so" + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath "${LIB_RUNTIME_DIR}"']) + AS_IF([test "$GCC" = yes], [ + CFLAGS="$CFLAGS -mabi=n32" + LDFLAGS="$LDFLAGS -mabi=n32" + ], [ + case $system in + IRIX-6.3) + # Use to build 6.2 compatible binaries on 6.3. + CFLAGS="$CFLAGS -n32 -D_OLD_TERMIOS" + ;; + *) + CFLAGS="$CFLAGS -n32" + ;; + esac + LDFLAGS="$LDFLAGS -n32" + ]) + ;; + IRIX64-6.*) + SHLIB_CFLAGS="" + SHLIB_LD="ld -n32 -shared -rdata_shared" + SHLIB_SUFFIX=".so" + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath "${LIB_RUNTIME_DIR}"']) + + # Check to enable 64-bit flags for compiler/linker + + AS_IF([test "$do64bit" = yes], [ + AS_IF([test "$GCC" = yes], [ + AC_MSG_WARN([64bit mode not supported by gcc]) + ], [ + do64bit_ok=yes + SHLIB_LD="ld -64 -shared -rdata_shared" + CFLAGS="$CFLAGS -64" + LDFLAGS_ARCH="-64" + ]) + ]) + ;; + Linux*|GNU*|NetBSD-Debian|DragonFly-*|FreeBSD-*) + SHLIB_CFLAGS="-fPIC" + SHLIB_SUFFIX=".so" + + # TEA specific: + CFLAGS_OPTIMIZE="-O2 -fomit-frame-pointer" + + # TEA specific: use LDFLAGS_DEFAULT instead of LDFLAGS + SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS_DEFAULT} -shared' + LDFLAGS="$LDFLAGS -Wl,--export-dynamic" + + case $system in + DragonFly-*|FreeBSD-*) + AS_IF([test "${TCL_THREADS}" = "1"], [ + # The -pthread needs to go in the LDFLAGS, not LIBS + LIBS=`echo $LIBS | sed s/-pthread//` + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LDFLAGS="$LDFLAGS $PTHREAD_LIBS"]) + ;; + esac + + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + AS_IF([test "`uname -m`" = "alpha"], [CFLAGS="$CFLAGS -mieee"]) + AS_IF([test $do64bit = yes], [ + AC_CACHE_CHECK([if compiler accepts -m64 flag], tcl_cv_cc_m64, [ + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -m64" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], + [tcl_cv_cc_m64=yes],[tcl_cv_cc_m64=no]) + CFLAGS=$hold_cflags]) + AS_IF([test $tcl_cv_cc_m64 = yes], [ + CFLAGS="$CFLAGS -m64" + do64bit_ok=yes + ]) + ]) + + # The combo of gcc + glibc has a bug related to inlining of + # functions like strtod(). The -fno-builtin flag should address + # this problem but it does not work. The -fno-inline flag is kind + # of overkill but it works. Disable inlining only when one of the + # files in compat/*.c is being linked in. + + AS_IF([test x"${USE_COMPAT}" != x],[CFLAGS="$CFLAGS -fno-inline"]) + ;; + Lynx*) + SHLIB_CFLAGS="-fPIC" + SHLIB_SUFFIX=".so" + CFLAGS_OPTIMIZE=-02 + SHLIB_LD='${CC} -shared' + LD_FLAGS="-Wl,--export-dynamic" + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) + ;; + OpenBSD-*) + arch=`arch -s` + case "$arch" in + alpha|sparc64) + SHLIB_CFLAGS="-fPIC" + ;; + *) + SHLIB_CFLAGS="-fpic" + ;; + esac + SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' + SHLIB_SUFFIX=".so" + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + SHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.so${SHLIB_VERSION}' + LDFLAGS="$LDFLAGS -Wl,-export-dynamic" + CFLAGS_OPTIMIZE="-O2" + # On OpenBSD: Compile with -pthread + # Don't link with -lpthread + LIBS=`echo $LIBS | sed s/-lpthread//` + CFLAGS="$CFLAGS -pthread" + # OpenBSD doesn't do version numbers with dots. + UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.a' + TCL_LIB_VERSIONS_OK=nodots + ;; + NetBSD-*) + # NetBSD has ELF and can use 'cc -shared' to build shared libs + SHLIB_CFLAGS="-fPIC" + SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' + SHLIB_SUFFIX=".so" + LDFLAGS="$LDFLAGS -export-dynamic" + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + # The -pthread needs to go in the CFLAGS, not LIBS + LIBS=`echo $LIBS | sed s/-pthread//` + CFLAGS="$CFLAGS -pthread" + LDFLAGS="$LDFLAGS -pthread" + ;; + Darwin-*) + CFLAGS_OPTIMIZE="-Os" + SHLIB_CFLAGS="-fno-common" + # To avoid discrepancies between what headers configure sees during + # preprocessing tests and compiling tests, move any -isysroot and + # -mmacosx-version-min flags from CFLAGS to CPPFLAGS: + CPPFLAGS="${CPPFLAGS} `echo " ${CFLAGS}" | \ + awk 'BEGIN {FS=" +-";ORS=" "}; {for (i=2;i<=NF;i++) \ + if ([$]i~/^(isysroot|mmacosx-version-min)/) print "-"[$]i}'`" + CFLAGS="`echo " ${CFLAGS}" | \ + awk 'BEGIN {FS=" +-";ORS=" "}; {for (i=2;i<=NF;i++) \ + if (!([$]i~/^(isysroot|mmacosx-version-min)/)) print "-"[$]i}'`" + AS_IF([test $do64bit = yes], [ + case `arch` in + ppc) + AC_CACHE_CHECK([if compiler accepts -arch ppc64 flag], + tcl_cv_cc_arch_ppc64, [ + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], + [tcl_cv_cc_arch_ppc64=yes],[tcl_cv_cc_arch_ppc64=no]) + CFLAGS=$hold_cflags]) + AS_IF([test $tcl_cv_cc_arch_ppc64 = yes], [ + CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" + do64bit_ok=yes + ]);; + i386) + AC_CACHE_CHECK([if compiler accepts -arch x86_64 flag], + tcl_cv_cc_arch_x86_64, [ + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -arch x86_64" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], + [tcl_cv_cc_arch_x86_64=yes],[tcl_cv_cc_arch_x86_64=no]) + CFLAGS=$hold_cflags]) + AS_IF([test $tcl_cv_cc_arch_x86_64 = yes], [ + CFLAGS="$CFLAGS -arch x86_64" + do64bit_ok=yes + ]);; + *) + AC_MSG_WARN([Don't know how enable 64-bit on architecture `arch`]);; + esac + ], [ + # Check for combined 32-bit and 64-bit fat build + AS_IF([echo "$CFLAGS " |grep -E -q -- '-arch (ppc64|x86_64) ' \ + && echo "$CFLAGS " |grep -E -q -- '-arch (ppc|i386) '], [ + fat_32_64=yes]) + ]) + # TEA specific: use LDFLAGS_DEFAULT instead of LDFLAGS + SHLIB_LD='${CC} -dynamiclib ${CFLAGS} ${LDFLAGS_DEFAULT}' + AC_CACHE_CHECK([if ld accepts -single_module flag], tcl_cv_ld_single_module, [ + hold_ldflags=$LDFLAGS + LDFLAGS="$LDFLAGS -dynamiclib -Wl,-single_module" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i;]])], + [tcl_cv_ld_single_module=yes],[tcl_cv_ld_single_module=no]) + LDFLAGS=$hold_ldflags]) + AS_IF([test $tcl_cv_ld_single_module = yes], [ + SHLIB_LD="${SHLIB_LD} -Wl,-single_module" + ]) + # TEA specific: link shlib with current and compatibility version flags + vers=`echo ${PACKAGE_VERSION} | sed -e 's/^\([[0-9]]\{1,5\}\)\(\(\.[[0-9]]\{1,3\}\)\{0,2\}\).*$/\1\2/p' -e d` + SHLIB_LD="${SHLIB_LD} -current_version ${vers:-0} -compatibility_version ${vers:-0}" + SHLIB_SUFFIX=".dylib" + LDFLAGS="$LDFLAGS -headerpad_max_install_names" + AC_CACHE_CHECK([if ld accepts -search_paths_first flag], + tcl_cv_ld_search_paths_first, [ + hold_ldflags=$LDFLAGS + LDFLAGS="$LDFLAGS -Wl,-search_paths_first" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i;]])], + [tcl_cv_ld_search_paths_first=yes],[tcl_cv_ld_search_paths_first=no]) + LDFLAGS=$hold_ldflags]) + AS_IF([test $tcl_cv_ld_search_paths_first = yes], [ + LDFLAGS="$LDFLAGS -Wl,-search_paths_first" + ]) + AS_IF([test "$tcl_cv_cc_visibility_hidden" != yes], [ + AC_DEFINE(MODULE_SCOPE, [__private_extern__], + [Compiler support for module scope symbols]) + tcl_cv_cc_visibility_hidden=yes + ]) + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + LD_LIBRARY_PATH_VAR="DYLD_LIBRARY_PATH" + # TEA specific: for combined 32 & 64 bit fat builds of Tk + # extensions, verify that 64-bit build is possible. + AS_IF([test "$fat_32_64" = yes && test -n "${TK_BIN_DIR}"], [ + AS_IF([test "${TEA_WINDOWINGSYSTEM}" = x11], [ + AC_CACHE_CHECK([for 64-bit X11], tcl_cv_lib_x11_64, [ + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval 'hold_'$v'="$'$v'";'$v'="`echo "$'$v' "|sed -e "s/-arch ppc / /g" -e "s/-arch i386 / /g"`"' + done + CPPFLAGS="$CPPFLAGS -I/usr/X11R6/include" + LDFLAGS="$LDFLAGS -L/usr/X11R6/lib -lX11" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <X11/Xlib.h>]], [[XrmInitialize();]])], + [tcl_cv_lib_x11_64=yes],[tcl_cv_lib_x11_64=no]) + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval $v'="$hold_'$v'"' + done]) + ]) + AS_IF([test "${TEA_WINDOWINGSYSTEM}" = aqua], [ + AC_CACHE_CHECK([for 64-bit Tk], tcl_cv_lib_tk_64, [ + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval 'hold_'$v'="$'$v'";'$v'="`echo "$'$v' "|sed -e "s/-arch ppc / /g" -e "s/-arch i386 / /g"`"' + done + CPPFLAGS="$CPPFLAGS -DUSE_TCL_STUBS=1 -DUSE_TK_STUBS=1 ${TCL_INCLUDES} ${TK_INCLUDES}" + LDFLAGS="$LDFLAGS ${TCL_STUB_LIB_SPEC} ${TK_STUB_LIB_SPEC}" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <tk.h>]], [[Tk_InitStubs(NULL, "", 0);]])], + [tcl_cv_lib_tk_64=yes],[tcl_cv_lib_tk_64=no]) + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval $v'="$hold_'$v'"' + done]) + ]) + # remove 64-bit arch flags from CFLAGS et al. if configuration + # does not support 64-bit. + AS_IF([test "$tcl_cv_lib_tk_64" = no -o "$tcl_cv_lib_x11_64" = no], [ + AC_MSG_NOTICE([Removing 64-bit architectures from compiler & linker flags]) + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval $v'="`echo "$'$v' "|sed -e "s/-arch ppc64 / /g" -e "s/-arch x86_64 / /g"`"' + done]) + ]) + ;; + OS/390-*) + CFLAGS_OPTIMIZE="" # Optimizer is buggy + AC_DEFINE(_OE_SOCKETS, 1, # needed in sys/socket.h + [Should OS/390 do the right thing with sockets?]) + ;; + OSF1-V*) + # Digital OSF/1 + SHLIB_CFLAGS="" + AS_IF([test "$SHARED_BUILD" = 1], [ + SHLIB_LD='ld -shared -expect_unresolved "*"' + ], [ + SHLIB_LD='ld -non_shared -expect_unresolved "*"' + ]) + SHLIB_SUFFIX=".so" + AS_IF([test $doRpath = yes], [ + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}']) + AS_IF([test "$GCC" = yes], [CFLAGS="$CFLAGS -mieee"], [ + CFLAGS="$CFLAGS -DHAVE_TZSET -std1 -ieee"]) + # see pthread_intro(3) for pthread support on osf1, k.furukawa + CFLAGS="$CFLAGS -DHAVE_PTHREAD_ATTR_SETSTACKSIZE" + CFLAGS="$CFLAGS -DTCL_THREAD_STACK_MIN=PTHREAD_STACK_MIN*64" + LIBS=`echo $LIBS | sed s/-lpthreads//` + AS_IF([test "$GCC" = yes], [ + LIBS="$LIBS -lpthread -lmach -lexc" + ], [ + CFLAGS="$CFLAGS -pthread" + LDFLAGS="$LDFLAGS -pthread" + ]) + ;; + QNX-6*) + # QNX RTP + # This may work for all QNX, but it was only reported for v6. + SHLIB_CFLAGS="-fPIC" + SHLIB_LD="ld -Bshareable -x" + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + SCO_SV-3.2*) + AS_IF([test "$GCC" = yes], [ + SHLIB_CFLAGS="-fPIC -melf" + LDFLAGS="$LDFLAGS -melf -Wl,-Bexport" + ], [ + SHLIB_CFLAGS="-Kpic -belf" + LDFLAGS="$LDFLAGS -belf -Wl,-Bexport" + ]) + SHLIB_LD="ld -G" + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + SunOS-5.[[0-6]]) + # Careful to not let 5.10+ fall into this case + + # Note: If _REENTRANT isn't defined, then Solaris + # won't define thread-safe library routines. + + AC_DEFINE(_REENTRANT, 1, [Do we want the reentrant OS API?]) + AC_DEFINE(_POSIX_PTHREAD_SEMANTICS, 1, + [Do we really want to follow the standard? Yes we do!]) + + SHLIB_CFLAGS="-KPIC" + SHLIB_SUFFIX=".so" + AS_IF([test "$GCC" = yes], [ + SHLIB_LD='${CC} -shared' + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + ], [ + SHLIB_LD="/usr/ccs/bin/ld -G -z text" + CC_SEARCH_FLAGS='-R "${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + ]) + ;; + SunOS-5*) + # Note: If _REENTRANT isn't defined, then Solaris + # won't define thread-safe library routines. + + AC_DEFINE(_REENTRANT, 1, [Do we want the reentrant OS API?]) + AC_DEFINE(_POSIX_PTHREAD_SEMANTICS, 1, + [Do we really want to follow the standard? Yes we do!]) + + SHLIB_CFLAGS="-KPIC" + + # Check to enable 64-bit flags for compiler/linker + AS_IF([test "$do64bit" = yes], [ + arch=`isainfo` + AS_IF([test "$arch" = "sparcv9 sparc"], [ + AS_IF([test "$GCC" = yes], [ + AS_IF([test "`${CC} -dumpversion | awk -F. '{print [$]1}'`" -lt 3], [ + AC_MSG_WARN([64bit mode not supported with GCC < 3.2 on $system]) + ], [ + do64bit_ok=yes + CFLAGS="$CFLAGS -m64 -mcpu=v9" + LDFLAGS="$LDFLAGS -m64 -mcpu=v9" + SHLIB_CFLAGS="-fPIC" + ]) + ], [ + do64bit_ok=yes + AS_IF([test "$do64bitVIS" = yes], [ + CFLAGS="$CFLAGS -xarch=v9a" + LDFLAGS_ARCH="-xarch=v9a" + ], [ + CFLAGS="$CFLAGS -xarch=v9" + LDFLAGS_ARCH="-xarch=v9" + ]) + # Solaris 64 uses this as well + #LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH_64" + ]) + ], [AS_IF([test "$arch" = "amd64 i386"], [ + AS_IF([test "$GCC" = yes], [ + case $system in + SunOS-5.1[[1-9]]*|SunOS-5.[[2-9]][[0-9]]*) + do64bit_ok=yes + CFLAGS="$CFLAGS -m64" + LDFLAGS="$LDFLAGS -m64";; + *) + AC_MSG_WARN([64bit mode not supported with GCC on $system]);; + esac + ], [ + do64bit_ok=yes + case $system in + SunOS-5.1[[1-9]]*|SunOS-5.[[2-9]][[0-9]]*) + CFLAGS="$CFLAGS -m64" + LDFLAGS="$LDFLAGS -m64";; + *) + CFLAGS="$CFLAGS -xarch=amd64" + LDFLAGS="$LDFLAGS -xarch=amd64";; + esac + ]) + ], [AC_MSG_WARN([64bit mode not supported for $arch])])]) + ]) + + SHLIB_SUFFIX=".so" + AS_IF([test "$GCC" = yes], [ + SHLIB_LD='${CC} -shared' + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + AS_IF([test "$do64bit_ok" = yes], [ + AS_IF([test "$arch" = "sparcv9 sparc"], [ + # We need to specify -static-libgcc or we need to + # add the path to the sparv9 libgcc. + # JH: static-libgcc is necessary for core Tcl, but may + # not be necessary for extensions. + SHLIB_LD="$SHLIB_LD -m64 -mcpu=v9 -static-libgcc" + # for finding sparcv9 libgcc, get the regular libgcc + # path, remove so name and append 'sparcv9' + #v9gcclibdir="`gcc -print-file-name=libgcc_s.so` | ..." + #CC_SEARCH_FLAGS="${CC_SEARCH_FLAGS},-R,$v9gcclibdir" + ], [AS_IF([test "$arch" = "amd64 i386"], [ + # JH: static-libgcc is necessary for core Tcl, but may + # not be necessary for extensions. + SHLIB_LD="$SHLIB_LD -m64 -static-libgcc" + ])]) + ]) + ], [ + case $system in + SunOS-5.[[1-9]][[0-9]]*) + # TEA specific: use LDFLAGS_DEFAULT instead of LDFLAGS + SHLIB_LD='${CC} -G -z text ${LDFLAGS_DEFAULT}';; + *) + SHLIB_LD='/usr/ccs/bin/ld -G -z text';; + esac + CC_SEARCH_FLAGS='"-Wl,-R,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-R "${LIB_RUNTIME_DIR}"' + ]) + ;; + UNIX_SV* | UnixWare-5*) + SHLIB_CFLAGS="-KPIC" + SHLIB_LD='${CC} -G' + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + # Some UNIX_SV* systems (unixware 1.1.2 for example) have linkers + # that don't grok the -Bexport option. Test that it does. + AC_CACHE_CHECK([for ld accepts -Bexport flag], tcl_cv_ld_Bexport, [ + hold_ldflags=$LDFLAGS + LDFLAGS="$LDFLAGS -Wl,-Bexport" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i;]])], + [tcl_cv_ld_Bexport=yes],[tcl_cv_ld_Bexport=no]) + LDFLAGS=$hold_ldflags]) + AS_IF([test $tcl_cv_ld_Bexport = yes], [ + LDFLAGS="$LDFLAGS -Wl,-Bexport" + ]) + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + esac + + AS_IF([test "$do64bit" = yes -a "$do64bit_ok" = no], [ + AC_MSG_WARN([64bit support being disabled -- don't know magic for this platform]) + ]) + +dnl # Add any CPPFLAGS set in the environment to our CFLAGS, but delay doing so +dnl # until the end of configure, as configure's compile and link tests use +dnl # both CPPFLAGS and CFLAGS (unlike our compile and link) but configure's +dnl # preprocessing tests use only CPPFLAGS. + AC_CONFIG_COMMANDS_PRE([CFLAGS="${CFLAGS} ${CPPFLAGS}"; CPPFLAGS=""]) + + # Add in the arch flags late to ensure it wasn't removed. + # Not necessary in TEA, but this is aligned with core + LDFLAGS="$LDFLAGS $LDFLAGS_ARCH" + + # If we're running gcc, then change the C flags for compiling shared + # libraries to the right flags for gcc, instead of those for the + # standard manufacturer compiler. + + AS_IF([test "$GCC" = yes], [ + case $system in + AIX-*) ;; + BSD/OS*) ;; + CYGWIN_*|MINGW32_*|MINGW64_*|MSYS_*) ;; + IRIX*) ;; + NetBSD-*|DragonFly-*|FreeBSD-*|OpenBSD-*) ;; + Darwin-*) ;; + SCO_SV-3.2*) ;; + windows) ;; + *) SHLIB_CFLAGS="-fPIC" ;; + esac]) + + AS_IF([test "$tcl_cv_cc_visibility_hidden" != yes], [ + AC_DEFINE(MODULE_SCOPE, [extern], + [No Compiler support for module scope symbols]) + ]) + + AS_IF([test "$SHARED_LIB_SUFFIX" = ""], [ + # TEA specific: use PACKAGE_VERSION instead of VERSION + SHARED_LIB_SUFFIX='${PACKAGE_VERSION}${SHLIB_SUFFIX}']) + AS_IF([test "$UNSHARED_LIB_SUFFIX" = ""], [ + # TEA specific: use PACKAGE_VERSION instead of VERSION + UNSHARED_LIB_SUFFIX='${PACKAGE_VERSION}.a']) + + if test "${GCC}" = "yes" -a ${SHLIB_SUFFIX} = ".dll"; then + AC_CACHE_CHECK(for SEH support in compiler, + tcl_cv_seh, + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#undef WIN32_LEAN_AND_MEAN + + int main(int argc, char** argv) { + int a, b = 0; + __try { + a = 666 / b; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + return 0; + } + return 1; + } + ]])], + [tcl_cv_seh=yes], + [tcl_cv_seh=no], + [tcl_cv_seh=no]) + ) + if test "$tcl_cv_seh" = "no" ; then + AC_DEFINE(HAVE_NO_SEH, 1, + [Defined when mingw does not support SEH]) + fi + + # + # Check to see if the excpt.h include file provided contains the + # definition for EXCEPTION_DISPOSITION; if not, which is the case + # with Cygwin's version as of 2002-04-10, define it to be int, + # sufficient for getting the current code to work. + # + AC_CACHE_CHECK(for EXCEPTION_DISPOSITION support in include files, + tcl_cv_eh_disposition, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +# define WIN32_LEAN_AND_MEAN +# include <windows.h> +# undef WIN32_LEAN_AND_MEAN + ]], [[ + EXCEPTION_DISPOSITION x; + ]])], + [tcl_cv_eh_disposition=yes], + [tcl_cv_eh_disposition=no]) + ) + if test "$tcl_cv_eh_disposition" = "no" ; then + AC_DEFINE(EXCEPTION_DISPOSITION, int, + [Defined when cygwin/mingw does not support EXCEPTION DISPOSITION]) + fi + + # Check to see if winnt.h defines CHAR, SHORT, and LONG + # even if VOID has already been #defined. The win32api + # used by mingw and cygwin is known to do this. + + AC_CACHE_CHECK(for winnt.h that ignores VOID define, + tcl_cv_winnt_ignore_void, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#define VOID void +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#undef WIN32_LEAN_AND_MEAN + ]], [[ + CHAR c; + SHORT s; + LONG l; + ]])], + [tcl_cv_winnt_ignore_void=yes], + [tcl_cv_winnt_ignore_void=no]) + ) + if test "$tcl_cv_winnt_ignore_void" = "yes" ; then + AC_DEFINE(HAVE_WINNT_IGNORE_VOID, 1, + [Defined when cygwin/mingw ignores VOID define in winnt.h]) + fi + fi + + # See if the compiler supports casting to a union type. + # This is used to stop gcc from printing a compiler + # warning when initializing a union member. + + AC_CACHE_CHECK(for cast to union support, + tcl_cv_cast_to_union, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ + union foo { int i; double d; }; + union foo f = (union foo) (int) 0; + ]])], + [tcl_cv_cast_to_union=yes], + [tcl_cv_cast_to_union=no]) + ) + if test "$tcl_cv_cast_to_union" = "yes"; then + AC_DEFINE(HAVE_CAST_TO_UNION, 1, + [Defined when compiler supports casting to union type.]) + fi + + AC_CHECK_HEADER(stdbool.h, [AC_DEFINE(HAVE_STDBOOL_H, 1, [Do we have <stdbool.h>?])],) + + AC_SUBST(CFLAGS_DEBUG) + AC_SUBST(CFLAGS_OPTIMIZE) + AC_SUBST(CFLAGS_WARNING) + AC_SUBST(LDFLAGS_DEBUG) + AC_SUBST(LDFLAGS_OPTIMIZE) + + AC_SUBST(STLIB_LD) + AC_SUBST(SHLIB_LD) + + AC_SUBST(SHLIB_LD_LIBS) + AC_SUBST(SHLIB_CFLAGS) + + AC_SUBST(LD_LIBRARY_PATH_VAR) + + # These must be called after we do the basic CFLAGS checks and + # verify any possible 64-bit or similar switches are necessary + TEA_TCL_EARLY_FLAGS + TEA_TCL_64BIT_FLAGS +]) + +#-------------------------------------------------------------------- +# TEA_SERIAL_PORT +# +# Determine which interface to use to talk to the serial port. +# Note that #include lines must begin in leftmost column for +# some compilers to recognize them as preprocessor directives, +# and some build environments have stdin not pointing at a +# pseudo-terminal (usually /dev/null instead.) +# +# Arguments: +# none +# +# Results: +# +# Defines only one of the following vars: +# HAVE_SYS_MODEM_H +# USE_TERMIOS +# USE_TERMIO +# USE_SGTTY +#-------------------------------------------------------------------- + +AC_DEFUN([TEA_SERIAL_PORT], [ + AC_CHECK_HEADERS(sys/modem.h) + AC_CACHE_CHECK([termios vs. termio vs. sgtty], tcl_cv_api_serial, [ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include <termios.h> + +int main() { + struct termios t; + if (tcgetattr(0, &t) == 0) { + cfsetospeed(&t, 0); + t.c_cflag |= PARENB | PARODD | CSIZE | CSTOPB; + return 0; + } + return 1; +}]])],[tcl_cv_api_serial=termios],[tcl_cv_api_serial=no],[tcl_cv_api_serial=no]) + if test $tcl_cv_api_serial = no ; then + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include <termio.h> + +int main() { + struct termio t; + if (ioctl(0, TCGETA, &t) == 0) { + t.c_cflag |= CBAUD | PARENB | PARODD | CSIZE | CSTOPB; + return 0; + } + return 1; +}]])],[tcl_cv_api_serial=termio],[tcl_cv_api_serial=no],[tcl_cv_api_serial=no]) + fi + if test $tcl_cv_api_serial = no ; then + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include <sgtty.h> + +int main() { + struct sgttyb t; + if (ioctl(0, TIOCGETP, &t) == 0) { + t.sg_ospeed = 0; + t.sg_flags |= ODDP | EVENP | RAW; + return 0; + } + return 1; +}]])],[tcl_cv_api_serial=sgtty],[tcl_cv_api_serial=no],[tcl_cv_api_serial=no]) + fi + if test $tcl_cv_api_serial = no ; then + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include <termios.h> +#include <errno.h> + +int main() { + struct termios t; + if (tcgetattr(0, &t) == 0 + || errno == ENOTTY || errno == ENXIO || errno == EINVAL) { + cfsetospeed(&t, 0); + t.c_cflag |= PARENB | PARODD | CSIZE | CSTOPB; + return 0; + } + return 1; +}]])],[tcl_cv_api_serial=termios],[tcl_cv_api_serial=no],[tcl_cv_api_serial=no]) + fi + if test $tcl_cv_api_serial = no; then + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include <termio.h> +#include <errno.h> + +int main() { + struct termio t; + if (ioctl(0, TCGETA, &t) == 0 + || errno == ENOTTY || errno == ENXIO || errno == EINVAL) { + t.c_cflag |= CBAUD | PARENB | PARODD | CSIZE | CSTOPB; + return 0; + } + return 1; + }]])],[tcl_cv_api_serial=termio],[tcl_cv_api_serial=no],[tcl_cv_api_serial=no]) + fi + if test $tcl_cv_api_serial = no; then + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include <sgtty.h> +#include <errno.h> + +int main() { + struct sgttyb t; + if (ioctl(0, TIOCGETP, &t) == 0 + || errno == ENOTTY || errno == ENXIO || errno == EINVAL) { + t.sg_ospeed = 0; + t.sg_flags |= ODDP | EVENP | RAW; + return 0; + } + return 1; +}]])],[tcl_cv_api_serial=sgtty],[tcl_cv_api_serial=none],[tcl_cv_api_serial=none]) + fi]) + case $tcl_cv_api_serial in + termios) AC_DEFINE(USE_TERMIOS, 1, [Use the termios API for serial lines]);; + termio) AC_DEFINE(USE_TERMIO, 1, [Use the termio API for serial lines]);; + sgtty) AC_DEFINE(USE_SGTTY, 1, [Use the sgtty API for serial lines]);; + esac +]) + +#-------------------------------------------------------------------- +# TEA_PATH_X +# +# Locate the X11 header files and the X11 library archive. Try +# the ac_path_x macro first, but if it doesn't find the X stuff +# (e.g. because there's no xmkmf program) then check through +# a list of possible directories. Under some conditions the +# autoconf macro will return an include directory that contains +# no include files, so double-check its result just to be safe. +# +# This should be called after TEA_CONFIG_CFLAGS as setting the +# LIBS line can confuse some configure macro magic. +# +# Arguments: +# none +# +# Results: +# +# Sets the following vars: +# XINCLUDES +# XLIBSW +# PKG_LIBS (appends to) +#-------------------------------------------------------------------- + +AC_DEFUN([TEA_PATH_X], [ + if test "${TEA_WINDOWINGSYSTEM}" = "x11" ; then + TEA_PATH_UNIX_X + fi +]) + +AC_DEFUN([TEA_PATH_UNIX_X], [ + AC_PATH_X + not_really_there="" + if test "$no_x" = ""; then + if test "$x_includes" = ""; then + AC_PREPROC_IFELSE([AC_LANG_SOURCE([[#include <X11/Xlib.h>]])],[],[not_really_there="yes"]) + else + if test ! -r $x_includes/X11/Xlib.h; then + not_really_there="yes" + fi + fi + fi + if test "$no_x" = "yes" -o "$not_really_there" = "yes"; then + AC_MSG_CHECKING([for X11 header files]) + found_xincludes="no" + AC_PREPROC_IFELSE([AC_LANG_SOURCE([[#include <X11/Xlib.h>]])],[found_xincludes="yes"],[found_xincludes="no"]) + if test "$found_xincludes" = "no"; then + dirs="/usr/unsupported/include /usr/local/include /usr/X386/include /usr/X11R6/include /usr/X11R5/include /usr/include/X11R5 /usr/include/X11R4 /usr/openwin/include /usr/X11/include /usr/sww/include" + for i in $dirs ; do + if test -r $i/X11/Xlib.h; then + AC_MSG_RESULT([$i]) + XINCLUDES=" -I$i" + found_xincludes="yes" + break + fi + done + fi + else + if test "$x_includes" != ""; then + XINCLUDES="-I$x_includes" + found_xincludes="yes" + fi + fi + if test "$found_xincludes" = "no"; then + AC_MSG_RESULT([couldn't find any!]) + fi + + if test "$no_x" = yes; then + AC_MSG_CHECKING([for X11 libraries]) + XLIBSW=nope + dirs="/usr/unsupported/lib /usr/local/lib /usr/X386/lib /usr/X11R6/lib /usr/X11R5/lib /usr/lib/X11R5 /usr/lib/X11R4 /usr/openwin/lib /usr/X11/lib /usr/sww/X11/lib" + for i in $dirs ; do + if test -r $i/libX11.a -o -r $i/libX11.so -o -r $i/libX11.sl -o -r $i/libX11.dylib; then + AC_MSG_RESULT([$i]) + XLIBSW="-L$i -lX11" + x_libraries="$i" + break + fi + done + else + if test "$x_libraries" = ""; then + XLIBSW=-lX11 + else + XLIBSW="-L$x_libraries -lX11" + fi + fi + if test "$XLIBSW" = nope ; then + AC_CHECK_LIB(Xwindow, XCreateWindow, XLIBSW=-lXwindow) + fi + if test "$XLIBSW" = nope ; then + AC_MSG_RESULT([could not find any! Using -lX11.]) + XLIBSW=-lX11 + fi + # TEA specific: + if test x"${XLIBSW}" != x ; then + PKG_LIBS="${PKG_LIBS} ${XLIBSW}" + fi +]) + +#-------------------------------------------------------------------- +# TEA_BLOCKING_STYLE +# +# The statements below check for systems where POSIX-style +# non-blocking I/O (O_NONBLOCK) doesn't work or is unimplemented. +# On these systems (mostly older ones), use the old BSD-style +# FIONBIO approach instead. +# +# Arguments: +# none +# +# Results: +# +# Defines some of the following vars: +# HAVE_SYS_IOCTL_H +# HAVE_SYS_FILIO_H +# USE_FIONBIO +# O_NONBLOCK +#-------------------------------------------------------------------- + +AC_DEFUN([TEA_BLOCKING_STYLE], [ + AC_CHECK_HEADERS(sys/ioctl.h) + AC_CHECK_HEADERS(sys/filio.h) + TEA_CONFIG_SYSTEM + AC_MSG_CHECKING([FIONBIO vs. O_NONBLOCK for nonblocking I/O]) + case $system in + OSF*) + AC_DEFINE(USE_FIONBIO, 1, [Should we use FIONBIO?]) + AC_MSG_RESULT([FIONBIO]) + ;; + *) + AC_MSG_RESULT([O_NONBLOCK]) + ;; + esac +]) + +#-------------------------------------------------------------------- +# TEA_TIME_HANDLER +# +# Checks how the system deals with time.h, what time structures +# are used on the system, and what fields the structures have. +# +# Arguments: +# none +# +# Results: +# +# Defines some of the following vars: +# USE_DELTA_FOR_TZ +# HAVE_TM_GMTOFF +# HAVE_TM_TZADJ +# HAVE_TIMEZONE_VAR +# +#-------------------------------------------------------------------- + +AC_DEFUN([TEA_TIME_HANDLER], [ + AC_CHECK_HEADERS(sys/time.h) + AC_HEADER_TIME + AC_STRUCT_TIMEZONE + + AC_CHECK_FUNCS(gmtime_r localtime_r mktime) + + AC_CACHE_CHECK([tm_tzadj in struct tm], tcl_cv_member_tm_tzadj, [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <time.h>]], [[struct tm tm; (void)tm.tm_tzadj;]])], + [tcl_cv_member_tm_tzadj=yes], + [tcl_cv_member_tm_tzadj=no])]) + if test $tcl_cv_member_tm_tzadj = yes ; then + AC_DEFINE(HAVE_TM_TZADJ, 1, [Should we use the tm_tzadj field of struct tm?]) + fi + + AC_CACHE_CHECK([tm_gmtoff in struct tm], tcl_cv_member_tm_gmtoff, [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <time.h>]], [[struct tm tm; (void)tm.tm_gmtoff;]])], + [tcl_cv_member_tm_gmtoff=yes], + [tcl_cv_member_tm_gmtoff=no])]) + if test $tcl_cv_member_tm_gmtoff = yes ; then + AC_DEFINE(HAVE_TM_GMTOFF, 1, [Should we use the tm_gmtoff field of struct tm?]) + fi + + # + # Its important to include time.h in this check, as some systems + # (like convex) have timezone functions, etc. + # + AC_CACHE_CHECK([long timezone variable], tcl_cv_timezone_long, [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <time.h> +#include <stdlib.h>]], + [[extern long timezone; + timezone += 1; + exit (0);]])], + [tcl_cv_timezone_long=yes], [tcl_cv_timezone_long=no])]) + if test $tcl_cv_timezone_long = yes ; then + AC_DEFINE(HAVE_TIMEZONE_VAR, 1, [Should we use the global timezone variable?]) + else + # + # On some systems (eg IRIX 6.2), timezone is a time_t and not a long. + # + AC_CACHE_CHECK([time_t timezone variable], tcl_cv_timezone_time, [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <time.h> +#include <stdlib.h>]], + [[extern time_t timezone; + timezone += 1; + exit (0);]])], + [tcl_cv_timezone_time=yes], [tcl_cv_timezone_time=no])]) + if test $tcl_cv_timezone_time = yes ; then + AC_DEFINE(HAVE_TIMEZONE_VAR, 1, [Should we use the global timezone variable?]) + fi + fi +]) + +#-------------------------------------------------------------------- +# TEA_BUGGY_STRTOD +# +# Under Solaris 2.4, strtod returns the wrong value for the +# terminating character under some conditions. Check for this +# and if the problem exists use a substitute procedure +# "fixstrtod" (provided by Tcl) that corrects the error. +# Also, on Compaq's Tru64 Unix 5.0, +# strtod(" ") returns 0.0 instead of a failure to convert. +# +# Arguments: +# none +# +# Results: +# +# Might defines some of the following vars: +# strtod (=fixstrtod) +#-------------------------------------------------------------------- + +AC_DEFUN([TEA_BUGGY_STRTOD], [ + AC_CHECK_FUNC(strtod, tcl_strtod=1, tcl_strtod=0) + if test "$tcl_strtod" = 1; then + AC_CACHE_CHECK([for Solaris2.4/Tru64 strtod bugs], tcl_cv_strtod_buggy,[ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ + #include <stdlib.h> + extern double strtod(); + int main() { + char *infString="Inf", *nanString="NaN", *spaceString=" "; + char *term; + double value; + value = strtod(infString, &term); + if ((term != infString) && (term[-1] == 0)) { + exit(1); + } + value = strtod(nanString, &term); + if ((term != nanString) && (term[-1] == 0)) { + exit(1); + } + value = strtod(spaceString, &term); + if (term == (spaceString+1)) { + exit(1); + } + exit(0); + }]])], [tcl_cv_strtod_buggy=ok], [tcl_cv_strtod_buggy=buggy], + [tcl_cv_strtod_buggy=buggy])]) + if test "$tcl_cv_strtod_buggy" = buggy; then + AC_LIBOBJ([fixstrtod]) + USE_COMPAT=1 + AC_DEFINE(strtod, fixstrtod, [Do we want to use the strtod() in compat?]) + fi + fi +]) + +#-------------------------------------------------------------------- +# TEA_TCL_LINK_LIBS +# +# Search for the libraries needed to link the Tcl shell. +# Things like the math library (-lm), socket stuff (-lsocket vs. +# -lnsl), zlib (-lz) and libtommath (-ltommath) are dealt with here. +# +# Arguments: +# None. +# +# Results: +# +# Might append to the following vars: +# LIBS +# MATH_LIBS +# +# Might define the following vars: +# HAVE_NET_ERRNO_H +# +#-------------------------------------------------------------------- + +AC_DEFUN([TEA_TCL_LINK_LIBS], [ + #-------------------------------------------------------------------- + # On a few very rare systems, all of the libm.a stuff is + # already in libc.a. Set compiler flags accordingly. + #-------------------------------------------------------------------- + + AC_CHECK_FUNC(sin, MATH_LIBS="", MATH_LIBS="-lm") + + #-------------------------------------------------------------------- + # Interactive UNIX requires -linet instead of -lsocket, plus it + # needs net/errno.h to define the socket-related error codes. + #-------------------------------------------------------------------- + + AC_CHECK_LIB(inet, main, [LIBS="$LIBS -linet"]) + AC_CHECK_HEADER(net/errno.h, [ + AC_DEFINE(HAVE_NET_ERRNO_H, 1, [Do we have <net/errno.h>?])]) + + #-------------------------------------------------------------------- + # Check for the existence of the -lsocket and -lnsl libraries. + # The order here is important, so that they end up in the right + # order in the command line generated by make. Here are some + # special considerations: + # 1. Use "connect" and "accept" to check for -lsocket, and + # "gethostbyname" to check for -lnsl. + # 2. Use each function name only once: can't redo a check because + # autoconf caches the results of the last check and won't redo it. + # 3. Use -lnsl and -lsocket only if they supply procedures that + # aren't already present in the normal libraries. This is because + # IRIX 5.2 has libraries, but they aren't needed and they're + # bogus: they goof up name resolution if used. + # 4. On some SVR4 systems, can't use -lsocket without -lnsl too. + # To get around this problem, check for both libraries together + # if -lsocket doesn't work by itself. + #-------------------------------------------------------------------- + + tcl_checkBoth=0 + AC_CHECK_FUNC(connect, tcl_checkSocket=0, tcl_checkSocket=1) + if test "$tcl_checkSocket" = 1; then + AC_CHECK_FUNC(setsockopt, , [AC_CHECK_LIB(socket, setsockopt, + LIBS="$LIBS -lsocket", tcl_checkBoth=1)]) + fi + if test "$tcl_checkBoth" = 1; then + tk_oldLibs=$LIBS + LIBS="$LIBS -lsocket -lnsl" + AC_CHECK_FUNC(accept, tcl_checkNsl=0, [LIBS=$tk_oldLibs]) + fi + AC_CHECK_FUNC(gethostbyname, , [AC_CHECK_LIB(nsl, gethostbyname, + [LIBS="$LIBS -lnsl"])]) + AC_CHECK_FUNC(mp_log_u32, , [AC_CHECK_LIB(tommath, mp_log_u32, + [LIBS="$LIBS -ltommath"])]) + AC_CHECK_FUNC(deflateSetHeader, , [AC_CHECK_LIB(z, deflateSetHeader, + [LIBS="$LIBS -lz"])]) +]) + +#-------------------------------------------------------------------- +# TEA_TCL_EARLY_FLAGS +# +# Check for what flags are needed to be passed so the correct OS +# features are available. +# +# Arguments: +# None +# +# Results: +# +# Might define the following vars: +# _ISOC99_SOURCE +# _LARGEFILE64_SOURCE +# _LARGEFILE_SOURCE64 +# +#-------------------------------------------------------------------- + +AC_DEFUN([TEA_TCL_EARLY_FLAG],[ + AC_CACHE_VAL([tcl_cv_flag_]translit($1,[A-Z],[a-z]), + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[$2]], [[$3]])], + [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no,[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[[#define ]$1[ 1 +]$2]], [[$3]])], + [tcl_cv_flag_]translit($1,[A-Z],[a-z])=yes, + [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no)])) + if test ["x${tcl_cv_flag_]translit($1,[A-Z],[a-z])[}" = "xyes"] ; then + AC_DEFINE($1, 1, [Add the ]$1[ flag when building]) + tcl_flags="$tcl_flags $1" + fi +]) + +AC_DEFUN([TEA_TCL_EARLY_FLAGS],[ + AC_MSG_CHECKING([for required early compiler flags]) + tcl_flags="" + TEA_TCL_EARLY_FLAG(_ISOC99_SOURCE,[#include <stdlib.h>], + [char *p = (char *)strtoll; char *q = (char *)strtoull;]) + TEA_TCL_EARLY_FLAG(_LARGEFILE64_SOURCE,[#include <sys/stat.h>], + [struct stat64 buf; int i = stat64("/", &buf);]) + TEA_TCL_EARLY_FLAG(_LARGEFILE_SOURCE64,[#include <sys/stat.h>], + [char *p = (char *)open64;]) + if test "x${tcl_flags}" = "x" ; then + AC_MSG_RESULT([none]) + else + AC_MSG_RESULT([${tcl_flags}]) + fi +]) + +#-------------------------------------------------------------------- +# TEA_TCL_64BIT_FLAGS +# +# Check for what is defined in the way of 64-bit features. +# +# Arguments: +# None +# +# Results: +# +# Might define the following vars: +# TCL_WIDE_INT_IS_LONG +# TCL_WIDE_INT_TYPE +# HAVE_STRUCT_DIRENT64, HAVE_DIR64 +# HAVE_STRUCT_STAT64 +# HAVE_TYPE_OFF64_T +# +#-------------------------------------------------------------------- + +AC_DEFUN([TEA_TCL_64BIT_FLAGS], [ + AC_MSG_CHECKING([for 64-bit integer type]) + AC_CACHE_VAL(tcl_cv_type_64bit,[ + tcl_cv_type_64bit=none + # See if the compiler knows natively about __int64 + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[__int64 value = (__int64) 0;]])], + [tcl_type_64bit=__int64],[tcl_type_64bit="long long"]) + # See if we could use long anyway Note that we substitute in the + # type that is our current guess for a 64-bit type inside this check + # program, so it should be modified only carefully... + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[switch (0) { + case 1: case (sizeof(${tcl_type_64bit})==sizeof(long)): ; + }]])],[tcl_cv_type_64bit=${tcl_type_64bit}],[])]) + if test "${tcl_cv_type_64bit}" = none ; then + AC_DEFINE(TCL_WIDE_INT_IS_LONG, 1, [Do 'long' and 'long long' have the same size (64-bit)?]) + AC_MSG_RESULT([yes]) + elif test "${tcl_cv_type_64bit}" = "__int64" \ + -a "${TEA_PLATFORM}" = "windows" ; then + # TEA specific: We actually want to use the default tcl.h checks in + # this case to handle both TCL_WIDE_INT_TYPE and TCL_LL_MODIFIER* + AC_MSG_RESULT([using Tcl header defaults]) + else + AC_DEFINE_UNQUOTED(TCL_WIDE_INT_TYPE,${tcl_cv_type_64bit}, + [What type should be used to define wide integers?]) + AC_MSG_RESULT([${tcl_cv_type_64bit}]) + + # Now check for auxiliary declarations + AC_CACHE_CHECK([for struct dirent64], tcl_cv_struct_dirent64,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h> +#include <dirent.h>]], [[struct dirent64 p;]])], + [tcl_cv_struct_dirent64=yes],[tcl_cv_struct_dirent64=no])]) + if test "x${tcl_cv_struct_dirent64}" = "xyes" ; then + AC_DEFINE(HAVE_STRUCT_DIRENT64, 1, [Is 'struct dirent64' in <sys/types.h>?]) + fi + + AC_CACHE_CHECK([for DIR64], tcl_cv_DIR64,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h> +#include <dirent.h>]], [[struct dirent64 *p; DIR64 d = opendir64("."); + p = readdir64(d); rewinddir64(d); closedir64(d);]])], + [tcl_cv_DIR64=yes], [tcl_cv_DIR64=no])]) + if test "x${tcl_cv_DIR64}" = "xyes" ; then + AC_DEFINE(HAVE_DIR64, 1, [Is 'DIR64' in <sys/types.h>?]) + fi + + AC_CACHE_CHECK([for struct stat64], tcl_cv_struct_stat64,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <sys/stat.h>]], [[struct stat64 p; +]])], + [tcl_cv_struct_stat64=yes], [tcl_cv_struct_stat64=no])]) + if test "x${tcl_cv_struct_stat64}" = "xyes" ; then + AC_DEFINE(HAVE_STRUCT_STAT64, 1, [Is 'struct stat64' in <sys/stat.h>?]) + fi + + AC_CHECK_FUNCS(open64 lseek64) + AC_MSG_CHECKING([for off64_t]) + AC_CACHE_VAL(tcl_cv_type_off64_t,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h>]], [[off64_t offset; +]])], + [tcl_cv_type_off64_t=yes], [tcl_cv_type_off64_t=no])]) + dnl Define HAVE_TYPE_OFF64_T only when the off64_t type and the + dnl functions lseek64 and open64 are defined. + if test "x${tcl_cv_type_off64_t}" = "xyes" && \ + test "x${ac_cv_func_lseek64}" = "xyes" && \ + test "x${ac_cv_func_open64}" = "xyes" ; then + AC_DEFINE(HAVE_TYPE_OFF64_T, 1, [Is off64_t in <sys/types.h>?]) + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + fi +]) + +## +## Here ends the standard Tcl configuration bits and starts the +## TEA specific functions +## + +#------------------------------------------------------------------------ +# TEA_INIT -- +# +# Init various Tcl Extension Architecture (TEA) variables. +# This should be the first called TEA_* macro. +# +# Arguments: +# none +# +# Results: +# +# Defines and substs the following vars: +# CYGPATH +# EXEEXT +# Defines only: +# TEA_VERSION +# TEA_INITED +# TEA_PLATFORM (windows or unix) +# +# "cygpath" is used on windows to generate native path names for include +# files. These variables should only be used with the compiler and linker +# since they generate native path names. +# +# EXEEXT +# Select the executable extension based on the host type. This +# is a lightweight replacement for AC_EXEEXT that doesn't require +# a compiler. +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_INIT], [ + TEA_VERSION="3.13" + + AC_MSG_CHECKING([TEA configuration]) + if test x"${PACKAGE_NAME}" = x ; then + AC_MSG_ERROR([ +The PACKAGE_NAME variable must be defined by your TEA configure.ac]) + fi + AC_MSG_RESULT([ok (TEA ${TEA_VERSION})]) + + # If the user did not set CFLAGS, set it now to keep macros + # like AC_PROG_CC and AC_TRY_COMPILE from adding "-g -O2". + if test "${CFLAGS+set}" != "set" ; then + CFLAGS="" + fi + + case "`uname -s`" in + *win32*|*WIN32*|*MINGW32_*|*MINGW64_*|*MSYS_*) + AC_CHECK_PROG(CYGPATH, cygpath, cygpath -m, echo) + EXEEXT=".exe" + TEA_PLATFORM="windows" + ;; + *CYGWIN_*) + EXEEXT=".exe" + # CYGPATH and TEA_PLATFORM are determined later in LOAD_TCLCONFIG + ;; + *) + CYGPATH=echo + # Maybe we are cross-compiling.... + case ${host_alias} in + *mingw32*) + EXEEXT=".exe" + TEA_PLATFORM="windows" + ;; + *) + EXEEXT="" + TEA_PLATFORM="unix" + ;; + esac + ;; + esac + + # Check if exec_prefix is set. If not use fall back to prefix. + # Note when adjusted, so that TEA_PREFIX can correct for this. + # This is needed for recursive configures, since autoconf propagates + # $prefix, but not $exec_prefix (doh!). + if test x$exec_prefix = xNONE ; then + exec_prefix_default=yes + exec_prefix=$prefix + fi + + AC_MSG_NOTICE([configuring ${PACKAGE_NAME} ${PACKAGE_VERSION}]) + + AC_SUBST(EXEEXT) + AC_SUBST(CYGPATH) + + # This package name must be replaced statically for AC_SUBST to work + AC_SUBST(PKG_LIB_FILE) + AC_SUBST(PKG_LIB_FILE8) + AC_SUBST(PKG_LIB_FILE9) + # Substitute STUB_LIB_FILE in case package creates a stub library too. + AC_SUBST(PKG_STUB_LIB_FILE) + + # We AC_SUBST these here to ensure they are subst'ed, + # in case the user doesn't call TEA_ADD_... + AC_SUBST(PKG_STUB_SOURCES) + AC_SUBST(PKG_STUB_OBJECTS) + AC_SUBST(PKG_TCL_SOURCES) + AC_SUBST(PKG_HEADERS) + AC_SUBST(PKG_INCLUDES) + AC_SUBST(PKG_LIBS) + AC_SUBST(PKG_CFLAGS) + + # Configure the installer. + TEA_INSTALLER +]) + +#------------------------------------------------------------------------ +# TEA_ADD_SOURCES -- +# +# Specify one or more source files. Users should check for +# the right platform before adding to their list. +# It is not important to specify the directory, as long as it is +# in the generic, win or unix subdirectory of $(srcdir). +# +# Arguments: +# one or more file names +# +# Results: +# +# Defines and substs the following vars: +# PKG_SOURCES +# PKG_OBJECTS +#------------------------------------------------------------------------ +AC_DEFUN([TEA_ADD_SOURCES], [ + vars="$@" + for i in $vars; do + case $i in + [\$]*) + # allow $-var names + PKG_SOURCES="$PKG_SOURCES $i" + PKG_OBJECTS="$PKG_OBJECTS $i" + ;; + *) + # check for existence - allows for generic/win/unix VPATH + # To add more dirs here (like 'src'), you have to update VPATH + # in Makefile.in as well + if test ! -f "${srcdir}/$i" -a ! -f "${srcdir}/generic/$i" \ + -a ! -f "${srcdir}/win/$i" -a ! -f "${srcdir}/unix/$i" \ + -a ! -f "${srcdir}/macosx/$i" \ + ; then + AC_MSG_ERROR([could not find source file '$i']) + fi + PKG_SOURCES="$PKG_SOURCES $i" + # this assumes it is in a VPATH dir + i=`basename $i` + # handle user calling this before or after TEA_SETUP_COMPILER + if test x"${OBJEXT}" != x ; then + j="`echo $i | sed -e 's/\.[[^.]]*$//'`.${OBJEXT}" + else + j="`echo $i | sed -e 's/\.[[^.]]*$//'`.\${OBJEXT}" + fi + PKG_OBJECTS="$PKG_OBJECTS $j" + ;; + esac + done + AC_SUBST(PKG_SOURCES) + AC_SUBST(PKG_OBJECTS) +]) + +#------------------------------------------------------------------------ +# TEA_ADD_STUB_SOURCES -- +# +# Specify one or more source files. Users should check for +# the right platform before adding to their list. +# It is not important to specify the directory, as long as it is +# in the generic, win or unix subdirectory of $(srcdir). +# +# Arguments: +# one or more file names +# +# Results: +# +# Defines and substs the following vars: +# PKG_STUB_SOURCES +# PKG_STUB_OBJECTS +#------------------------------------------------------------------------ +AC_DEFUN([TEA_ADD_STUB_SOURCES], [ + vars="$@" + for i in $vars; do + # check for existence - allows for generic/win/unix VPATH + if test ! -f "${srcdir}/$i" -a ! -f "${srcdir}/generic/$i" \ + -a ! -f "${srcdir}/win/$i" -a ! -f "${srcdir}/unix/$i" \ + -a ! -f "${srcdir}/macosx/$i" \ + ; then + AC_MSG_ERROR([could not find stub source file '$i']) + fi + PKG_STUB_SOURCES="$PKG_STUB_SOURCES $i" + # this assumes it is in a VPATH dir + i=`basename $i` + # handle user calling this before or after TEA_SETUP_COMPILER + if test x"${OBJEXT}" != x ; then + j="`echo $i | sed -e 's/\.[[^.]]*$//'`.${OBJEXT}" + else + j="`echo $i | sed -e 's/\.[[^.]]*$//'`.\${OBJEXT}" + fi + PKG_STUB_OBJECTS="$PKG_STUB_OBJECTS $j" + done + AC_SUBST(PKG_STUB_SOURCES) + AC_SUBST(PKG_STUB_OBJECTS) +]) + +#------------------------------------------------------------------------ +# TEA_ADD_TCL_SOURCES -- +# +# Specify one or more Tcl source files. These should be platform +# independent runtime files. +# +# Arguments: +# one or more file names +# +# Results: +# +# Defines and substs the following vars: +# PKG_TCL_SOURCES +#------------------------------------------------------------------------ +AC_DEFUN([TEA_ADD_TCL_SOURCES], [ + vars="$@" + for i in $vars; do + # check for existence, be strict because it is installed + if test ! -f "${srcdir}/$i" ; then + AC_MSG_ERROR([could not find tcl source file '${srcdir}/$i']) + fi + PKG_TCL_SOURCES="$PKG_TCL_SOURCES $i" + done + AC_SUBST(PKG_TCL_SOURCES) +]) + +#------------------------------------------------------------------------ +# TEA_ADD_HEADERS -- +# +# Specify one or more source headers. Users should check for +# the right platform before adding to their list. +# +# Arguments: +# one or more file names +# +# Results: +# +# Defines and substs the following vars: +# PKG_HEADERS +#------------------------------------------------------------------------ +AC_DEFUN([TEA_ADD_HEADERS], [ + vars="$@" + for i in $vars; do + # check for existence, be strict because it is installed + if test ! -f "${srcdir}/$i" ; then + AC_MSG_ERROR([could not find header file '${srcdir}/$i']) + fi + PKG_HEADERS="$PKG_HEADERS $i" + done + AC_SUBST(PKG_HEADERS) +]) + +#------------------------------------------------------------------------ +# TEA_ADD_INCLUDES -- +# +# Specify one or more include dirs. Users should check for +# the right platform before adding to their list. +# +# Arguments: +# one or more file names +# +# Results: +# +# Defines and substs the following vars: +# PKG_INCLUDES +#------------------------------------------------------------------------ +AC_DEFUN([TEA_ADD_INCLUDES], [ + vars="$@" + for i in $vars; do + PKG_INCLUDES="$PKG_INCLUDES $i" + done + AC_SUBST(PKG_INCLUDES) +]) + +#------------------------------------------------------------------------ +# TEA_ADD_LIBS -- +# +# Specify one or more libraries. Users should check for +# the right platform before adding to their list. For Windows, +# libraries provided in "foo.lib" format will be converted to +# "-lfoo" when using GCC (mingw). +# +# Arguments: +# one or more file names +# +# Results: +# +# Defines and substs the following vars: +# PKG_LIBS +#------------------------------------------------------------------------ +AC_DEFUN([TEA_ADD_LIBS], [ + vars="$@" + for i in $vars; do + if test "${TEA_PLATFORM}" = "windows" -a "$GCC" = "yes" ; then + # Convert foo.lib to -lfoo for GCC. No-op if not *.lib + i=`echo "$i" | sed -e 's/^\([[^-]].*\)\.[[lL]][[iI]][[bB]][$]/-l\1/'` + fi + PKG_LIBS="$PKG_LIBS $i" + done + AC_SUBST(PKG_LIBS) +]) + +#------------------------------------------------------------------------ +# TEA_ADD_CFLAGS -- +# +# Specify one or more CFLAGS. Users should check for +# the right platform before adding to their list. +# +# Arguments: +# one or more file names +# +# Results: +# +# Defines and substs the following vars: +# PKG_CFLAGS +#------------------------------------------------------------------------ +AC_DEFUN([TEA_ADD_CFLAGS], [ + PKG_CFLAGS="$PKG_CFLAGS $@" + AC_SUBST(PKG_CFLAGS) +]) + +#------------------------------------------------------------------------ +# TEA_ADD_CLEANFILES -- +# +# Specify one or more CLEANFILES. +# +# Arguments: +# one or more file names to clean target +# +# Results: +# +# Appends to CLEANFILES, already defined for subst in LOAD_TCLCONFIG +#------------------------------------------------------------------------ +AC_DEFUN([TEA_ADD_CLEANFILES], [ + CLEANFILES="$CLEANFILES $@" +]) + +#------------------------------------------------------------------------ +# TEA_PREFIX -- +# +# Handle the --prefix=... option by defaulting to what Tcl gave +# +# Arguments: +# none +# +# Results: +# +# If --prefix or --exec-prefix was not specified, $prefix and +# $exec_prefix will be set to the values given to Tcl when it was +# configured. +#------------------------------------------------------------------------ +AC_DEFUN([TEA_PREFIX], [ + if test "${prefix}" = "NONE"; then + prefix_default=yes + if test x"${TCL_PREFIX}" != x; then + AC_MSG_NOTICE([--prefix defaulting to TCL_PREFIX ${TCL_PREFIX}]) + prefix=${TCL_PREFIX} + else + AC_MSG_NOTICE([--prefix defaulting to /usr/local]) + prefix=/usr/local + fi + fi + if test "${exec_prefix}" = "NONE" -a x"${prefix_default}" = x"yes" \ + -o x"${exec_prefix_default}" = x"yes" ; then + if test x"${TCL_EXEC_PREFIX}" != x; then + AC_MSG_NOTICE([--exec-prefix defaulting to TCL_EXEC_PREFIX ${TCL_EXEC_PREFIX}]) + exec_prefix=${TCL_EXEC_PREFIX} + else + AC_MSG_NOTICE([--exec-prefix defaulting to ${prefix}]) + exec_prefix=$prefix + fi + fi +]) + +#------------------------------------------------------------------------ +# TEA_SETUP_COMPILER_CC -- +# +# Do compiler checks the way we want. This is just a replacement +# for AC_PROG_CC in TEA configure.ac files to make them cleaner. +# +# Arguments: +# none +# +# Results: +# +# Sets up CC var and other standard bits we need to make executables. +#------------------------------------------------------------------------ +AC_DEFUN([TEA_SETUP_COMPILER_CC], [ + # Don't put any macros that use the compiler (e.g. AC_TRY_COMPILE) + # in this macro, they need to go into TEA_SETUP_COMPILER instead. + + AC_PROG_CC + AC_PROG_CPP + + #-------------------------------------------------------------------- + # Checks to see if the make program sets the $MAKE variable. + #-------------------------------------------------------------------- + + AC_PROG_MAKE_SET + + #-------------------------------------------------------------------- + # Find ranlib + #-------------------------------------------------------------------- + + AC_CHECK_TOOL(RANLIB, ranlib) + + #-------------------------------------------------------------------- + # Determines the correct binary file extension (.o, .obj, .exe etc.) + #-------------------------------------------------------------------- + + AC_OBJEXT + AC_EXEEXT +]) + +#------------------------------------------------------------------------ +# TEA_SETUP_COMPILER -- +# +# Do compiler checks that use the compiler. This must go after +# TEA_SETUP_COMPILER_CC, which does the actual compiler check. +# +# Arguments: +# none +# +# Results: +# +# Sets up CC var and other standard bits we need to make executables. +#------------------------------------------------------------------------ +AC_DEFUN([TEA_SETUP_COMPILER], [ + # Any macros that use the compiler (e.g. AC_TRY_COMPILE) have to go here. + AC_REQUIRE([TEA_SETUP_COMPILER_CC]) + + #------------------------------------------------------------------------ + # If we're using GCC, see if the compiler understands -pipe. If so, use it. + # It makes compiling go faster. (This is only a performance feature.) + #------------------------------------------------------------------------ + + if test -z "$no_pipe" -a -n "$GCC"; then + AC_CACHE_CHECK([if the compiler understands -pipe], + tcl_cv_cc_pipe, [ + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -pipe" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[tcl_cv_cc_pipe=yes],[tcl_cv_cc_pipe=no]) + CFLAGS=$hold_cflags]) + if test $tcl_cv_cc_pipe = yes; then + CFLAGS="$CFLAGS -pipe" + fi + fi + + #-------------------------------------------------------------------- + # Common compiler flag setup + #-------------------------------------------------------------------- + + AC_C_BIGENDIAN +]) + +#------------------------------------------------------------------------ +# TEA_MAKE_LIB -- +# +# Generate a line that can be used to build a shared/unshared library +# in a platform independent manner. +# +# Arguments: +# none +# +# Requires: +# +# Results: +# +# Defines the following vars: +# CFLAGS - Done late here to note disturb other AC macros +# MAKE_LIB - Command to execute to build the Tcl library; +# differs depending on whether or not Tcl is being +# compiled as a shared library. +# MAKE_SHARED_LIB Makefile rule for building a shared library +# MAKE_STATIC_LIB Makefile rule for building a static library +# MAKE_STUB_LIB Makefile rule for building a stub library +# VC_MANIFEST_EMBED_DLL Makefile rule for embedded VC manifest in DLL +# VC_MANIFEST_EMBED_EXE Makefile rule for embedded VC manifest in EXE +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_MAKE_LIB], [ + if test "${TEA_PLATFORM}" = "windows" -a "$GCC" != "yes"; then + MAKE_STATIC_LIB="\${STLIB_LD} -out:\[$]@ \$(PKG_OBJECTS)" + MAKE_SHARED_LIB="\${SHLIB_LD} \${LDFLAGS} \${LDFLAGS_DEFAULT} -out:\[$]@ \$(PKG_OBJECTS) \${SHLIB_LD_LIBS}" + AC_EGREP_CPP([manifest needed], [ +#if defined(_MSC_VER) && _MSC_VER >= 1400 +print("manifest needed") +#endif + ], [ + # Could do a CHECK_PROG for mt, but should always be with MSVC8+ + VC_MANIFEST_EMBED_DLL="if test -f \[$]@.manifest ; then mt.exe -nologo -manifest \[$]@.manifest -outputresource:\[$]@\;2 ; fi" + VC_MANIFEST_EMBED_EXE="if test -f \[$]@.manifest ; then mt.exe -nologo -manifest \[$]@.manifest -outputresource:\[$]@\;1 ; fi" + MAKE_SHARED_LIB="${MAKE_SHARED_LIB} ; ${VC_MANIFEST_EMBED_DLL}" + TEA_ADD_CLEANFILES([*.manifest]) + ]) + MAKE_STUB_LIB="\${STLIB_LD} -nodefaultlib -out:\[$]@ \$(PKG_STUB_OBJECTS)" + else + MAKE_STATIC_LIB="\${STLIB_LD} \[$]@ \$(PKG_OBJECTS)" + MAKE_SHARED_LIB="\${SHLIB_LD} \${LDFLAGS} \${LDFLAGS_DEFAULT} -o \[$]@ \$(PKG_OBJECTS) \${SHLIB_LD_LIBS}" + MAKE_STUB_LIB="\${STLIB_LD} \[$]@ \$(PKG_STUB_OBJECTS)" + fi + + if test "${SHARED_BUILD}" = "1" ; then + MAKE_LIB="${MAKE_SHARED_LIB} " + else + MAKE_LIB="${MAKE_STATIC_LIB} " + fi + + #-------------------------------------------------------------------- + # Shared libraries and static libraries have different names. + # Use the double eval to make sure any variables in the suffix is + # substituted. (@@@ Might not be necessary anymore) + #-------------------------------------------------------------------- + + PACKAGE_LIB_PREFIX8="${PACKAGE_LIB_PREFIX}" + PACKAGE_LIB_PREFIX9="${PACKAGE_LIB_PREFIX}tcl9" + if test "${TCL_MAJOR_VERSION}" -gt 8 ; then + PACKAGE_LIB_PREFIX="${PACKAGE_LIB_PREFIX9}" + else + PACKAGE_LIB_PREFIX="${PACKAGE_LIB_PREFIX8}" + fi + if test "${TEA_PLATFORM}" = "windows" ; then + if test "${SHARED_BUILD}" = "1" ; then + # We force the unresolved linking of symbols that are really in + # the private libraries of Tcl and Tk. + if test x"${TK_BIN_DIR}" != x ; then + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} \"`${CYGPATH} ${TK_BIN_DIR}/${TK_STUB_LIB_FILE}`\"" + fi + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} \"`${CYGPATH} ${TCL_BIN_DIR}/${TCL_STUB_LIB_FILE}`\"" + if test "$GCC" = "yes"; then + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} -static-libgcc" + fi + eval eval "PKG_LIB_FILE8=${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE9=${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + else + if test "$GCC" = "yes"; then + PACKAGE_LIB_PREFIX=lib${PACKAGE_LIB_PREFIX} + fi + eval eval "PKG_LIB_FILE8=${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE9=${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + fi + # Some packages build their own stubs libraries + eval eval "PKG_STUB_LIB_FILE=${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" + if test "$GCC" = "yes"; then + PKG_STUB_LIB_FILE=lib${PKG_STUB_LIB_FILE} + fi + # These aren't needed on Windows (either MSVC or gcc) + RANLIB=: + RANLIB_STUB=: + else + RANLIB_STUB="${RANLIB}" + if test "${SHARED_BUILD}" = "1" ; then + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} ${TCL_STUB_LIB_SPEC}" + if test x"${TK_BIN_DIR}" != x ; then + SHLIB_LD_LIBS="${SHLIB_LD_LIBS} ${TK_STUB_LIB_SPEC}" + fi + eval eval "PKG_LIB_FILE8=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE9=lib${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${SHARED_LIB_SUFFIX}" + RANLIB=: + else + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX9}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + eval eval "PKG_LIB_FILE=lib${PACKAGE_LIB_PREFIX}${PACKAGE_NAME}${UNSHARED_LIB_SUFFIX}" + fi + # Some packages build their own stubs libraries + eval eval "PKG_STUB_LIB_FILE=lib${PACKAGE_LIB_PREFIX8}${PACKAGE_NAME}stub${UNSHARED_LIB_SUFFIX}" + fi + + # These are escaped so that only CFLAGS is picked up at configure time. + # The other values will be substituted at make time. + CFLAGS="${CFLAGS} \${CFLAGS_DEFAULT} \${CFLAGS_WARNING}" + if test "${SHARED_BUILD}" = "1" ; then + CFLAGS="${CFLAGS} \${SHLIB_CFLAGS}" + fi + + AC_SUBST(MAKE_LIB) + AC_SUBST(MAKE_SHARED_LIB) + AC_SUBST(MAKE_STATIC_LIB) + AC_SUBST(MAKE_STUB_LIB) + AC_SUBST(RANLIB_STUB) + AC_SUBST(VC_MANIFEST_EMBED_DLL) + AC_SUBST(VC_MANIFEST_EMBED_EXE) +]) + +#------------------------------------------------------------------------ +# TEA_LIB_SPEC -- +# +# Compute the name of an existing object library located in libdir +# from the given base name and produce the appropriate linker flags. +# +# Arguments: +# basename The base name of the library without version +# numbers, extensions, or "lib" prefixes. +# extra_dir Extra directory in which to search for the +# library. This location is used first, then +# $prefix/$exec-prefix, then some defaults. +# +# Requires: +# TEA_INIT and TEA_PREFIX must be called first. +# +# Results: +# +# Defines the following vars: +# ${basename}_LIB_NAME The computed library name. +# ${basename}_LIB_SPEC The computed linker flags. +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_LIB_SPEC], [ + AC_MSG_CHECKING([for $1 library]) + + # Look in exec-prefix for the library (defined by TEA_PREFIX). + + tea_lib_name_dir="${exec_prefix}/lib" + + # Or in a user-specified location. + + if test x"$2" != x ; then + tea_extra_lib_dir=$2 + else + tea_extra_lib_dir=NONE + fi + + for i in \ + `ls -dr ${tea_extra_lib_dir}/$1[[0-9]]*.lib 2>/dev/null ` \ + `ls -dr ${tea_extra_lib_dir}/lib$1[[0-9]]* 2>/dev/null ` \ + `ls -dr ${tea_lib_name_dir}/$1[[0-9]]*.lib 2>/dev/null ` \ + `ls -dr ${tea_lib_name_dir}/lib$1[[0-9]]* 2>/dev/null ` \ + `ls -dr /usr/lib/$1[[0-9]]*.lib 2>/dev/null ` \ + `ls -dr /usr/lib/lib$1[[0-9]]* 2>/dev/null ` \ + `ls -dr /usr/lib64/$1[[0-9]]*.lib 2>/dev/null ` \ + `ls -dr /usr/lib64/lib$1[[0-9]]* 2>/dev/null ` \ + `ls -dr /usr/local/lib/$1[[0-9]]*.lib 2>/dev/null ` \ + `ls -dr /usr/local/lib/lib$1[[0-9]]* 2>/dev/null ` ; do + if test -f "$i" ; then + tea_lib_name_dir=`dirname $i` + $1_LIB_NAME=`basename $i` + $1_LIB_PATH_NAME=$i + break + fi + done + + if test "${TEA_PLATFORM}" = "windows"; then + $1_LIB_SPEC=\"`${CYGPATH} ${$1_LIB_PATH_NAME} 2>/dev/null`\" + else + # Strip off the leading "lib" and trailing ".a" or ".so" + + tea_lib_name_lib=`echo ${$1_LIB_NAME}|sed -e 's/^lib//' -e 's/\.[[^.]]*$//' -e 's/\.so.*//'` + $1_LIB_SPEC="-L${tea_lib_name_dir} -l${tea_lib_name_lib}" + fi + + if test "x${$1_LIB_NAME}" = x ; then + AC_MSG_ERROR([not found]) + else + AC_MSG_RESULT([${$1_LIB_SPEC}]) + fi +]) + +#------------------------------------------------------------------------ +# TEA_PRIVATE_TCL_HEADERS -- +# +# Locate the private Tcl include files +# +# Arguments: +# +# Requires: +# TCL_SRC_DIR Assumes that TEA_LOAD_TCLCONFIG has +# already been called. +# +# Results: +# +# Substitutes the following vars: +# TCL_TOP_DIR_NATIVE +# TCL_INCLUDES +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_PRIVATE_TCL_HEADERS], [ + # Allow for --with-tclinclude to take effect and define ${ac_cv_c_tclh} + AC_REQUIRE([TEA_PUBLIC_TCL_HEADERS]) + AC_MSG_CHECKING([for Tcl private include files]) + + TCL_SRC_DIR_NATIVE=`${CYGPATH} ${TCL_SRC_DIR}` + TCL_TOP_DIR_NATIVE=\"${TCL_SRC_DIR_NATIVE}\" + + # Check to see if tcl<Plat>Port.h isn't already with the public headers + # Don't look for tclInt.h because that resides with tcl.h in the core + # sources, but the <plat>Port headers are in a different directory + if test "${TEA_PLATFORM}" = "windows" -a \ + -f "${ac_cv_c_tclh}/tclWinPort.h"; then + result="private headers found with public headers" + elif test "${TEA_PLATFORM}" = "unix" -a \ + -f "${ac_cv_c_tclh}/tclUnixPort.h"; then + result="private headers found with public headers" + else + TCL_GENERIC_DIR_NATIVE=\"${TCL_SRC_DIR_NATIVE}/generic\" + if test "${TEA_PLATFORM}" = "windows"; then + TCL_PLATFORM_DIR_NATIVE=\"${TCL_SRC_DIR_NATIVE}/win\" + else + TCL_PLATFORM_DIR_NATIVE=\"${TCL_SRC_DIR_NATIVE}/unix\" + fi + # Overwrite the previous TCL_INCLUDES as this should capture both + # public and private headers in the same set. + # We want to ensure these are substituted so as not to require + # any *_NATIVE vars be defined in the Makefile + TCL_INCLUDES="-I${TCL_GENERIC_DIR_NATIVE} -I${TCL_PLATFORM_DIR_NATIVE}" + if test "`uname -s`" = "Darwin"; then + # If Tcl was built as a framework, attempt to use + # the framework's Headers and PrivateHeaders directories + case ${TCL_DEFS} in + *TCL_FRAMEWORK*) + if test -d "${TCL_BIN_DIR}/Headers" -a \ + -d "${TCL_BIN_DIR}/PrivateHeaders"; then + TCL_INCLUDES="-I\"${TCL_BIN_DIR}/Headers\" -I\"${TCL_BIN_DIR}/PrivateHeaders\" ${TCL_INCLUDES}" + else + TCL_INCLUDES="${TCL_INCLUDES} ${TCL_INCLUDE_SPEC} `echo "${TCL_INCLUDE_SPEC}" | sed -e 's/Headers/PrivateHeaders/'`" + fi + ;; + esac + result="Using ${TCL_INCLUDES}" + else + if test ! -f "${TCL_SRC_DIR}/generic/tclInt.h" ; then + AC_MSG_ERROR([Cannot find private header tclInt.h in ${TCL_SRC_DIR}]) + fi + result="Using srcdir found in tclConfig.sh: ${TCL_SRC_DIR}" + fi + fi + + AC_SUBST(TCL_TOP_DIR_NATIVE) + + AC_SUBST(TCL_INCLUDES) + AC_MSG_RESULT([${result}]) +]) + +#------------------------------------------------------------------------ +# TEA_PUBLIC_TCL_HEADERS -- +# +# Locate the installed public Tcl header files +# +# Arguments: +# None. +# +# Requires: +# CYGPATH must be set +# +# Results: +# +# Adds a --with-tclinclude switch to configure. +# Result is cached. +# +# Substitutes the following vars: +# TCL_INCLUDES +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_PUBLIC_TCL_HEADERS], [ + AC_MSG_CHECKING([for Tcl public headers]) + + AC_ARG_WITH(tclinclude, [ --with-tclinclude directory containing the public Tcl header files], with_tclinclude=${withval}) + + AC_CACHE_VAL(ac_cv_c_tclh, [ + # Use the value from --with-tclinclude, if it was given + + if test x"${with_tclinclude}" != x ; then + if test -f "${with_tclinclude}/tcl.h" ; then + ac_cv_c_tclh=${with_tclinclude} + else + AC_MSG_ERROR([${with_tclinclude} directory does not contain tcl.h]) + fi + else + list="" + if test "`uname -s`" = "Darwin"; then + # If Tcl was built as a framework, attempt to use + # the framework's Headers directory + case ${TCL_DEFS} in + *TCL_FRAMEWORK*) + list="`ls -d ${TCL_BIN_DIR}/Headers 2>/dev/null`" + ;; + esac + fi + + # Look in the source dir only if Tcl is not installed, + # and in that situation, look there before installed locations. + if test -f "${TCL_BIN_DIR}/Makefile" ; then + list="$list `ls -d ${TCL_SRC_DIR}/generic 2>/dev/null`" + fi + + # Check order: pkg --prefix location, Tcl's --prefix location, + # relative to directory of tclConfig.sh. + + eval "temp_includedir=${includedir}" + list="$list \ + `ls -d ${temp_includedir} 2>/dev/null` \ + `ls -d ${TCL_PREFIX}/include 2>/dev/null` \ + `ls -d ${TCL_BIN_DIR}/../include 2>/dev/null`" + if test "${TEA_PLATFORM}" != "windows" -o "$GCC" = "yes"; then + list="$list /usr/local/include /usr/include" + if test x"${TCL_INCLUDE_SPEC}" != x ; then + d=`echo "${TCL_INCLUDE_SPEC}" | sed -e 's/^-I//'` + list="$list `ls -d ${d} 2>/dev/null`" + fi + fi + for i in $list ; do + if test -f "$i/tcl.h" ; then + ac_cv_c_tclh=$i + break + fi + done + fi + ]) + + # Print a message based on how we determined the include path + + if test x"${ac_cv_c_tclh}" = x ; then + AC_MSG_ERROR([tcl.h not found. Please specify its location with --with-tclinclude]) + else + AC_MSG_RESULT([${ac_cv_c_tclh}]) + fi + + # Convert to a native path and substitute into the output files. + + INCLUDE_DIR_NATIVE=`${CYGPATH} ${ac_cv_c_tclh}` + + TCL_INCLUDES=-I\"${INCLUDE_DIR_NATIVE}\" + + AC_SUBST(TCL_INCLUDES) +]) + +#------------------------------------------------------------------------ +# TEA_PRIVATE_TK_HEADERS -- +# +# Locate the private Tk include files +# +# Arguments: +# +# Requires: +# TK_SRC_DIR Assumes that TEA_LOAD_TKCONFIG has +# already been called. +# +# Results: +# +# Substitutes the following vars: +# TK_INCLUDES +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_PRIVATE_TK_HEADERS], [ + # Allow for --with-tkinclude to take effect and define ${ac_cv_c_tkh} + AC_REQUIRE([TEA_PUBLIC_TK_HEADERS]) + AC_MSG_CHECKING([for Tk private include files]) + + TK_SRC_DIR_NATIVE=`${CYGPATH} ${TK_SRC_DIR}` + TK_TOP_DIR_NATIVE=\"${TK_SRC_DIR_NATIVE}\" + + # Check to see if tk<Plat>Port.h isn't already with the public headers + # Don't look for tkInt.h because that resides with tk.h in the core + # sources, but the <plat>Port headers are in a different directory + if test "${TEA_PLATFORM}" = "windows" -a \ + -f "${ac_cv_c_tkh}/tkWinPort.h"; then + result="private headers found with public headers" + elif test "${TEA_PLATFORM}" = "unix" -a \ + -f "${ac_cv_c_tkh}/tkUnixPort.h"; then + result="private headers found with public headers" + else + TK_GENERIC_DIR_NATIVE=\"${TK_SRC_DIR_NATIVE}/generic\" + TK_XLIB_DIR_NATIVE=\"${TK_SRC_DIR_NATIVE}/xlib\" + if test "${TEA_PLATFORM}" = "windows"; then + TK_PLATFORM_DIR_NATIVE=\"${TK_SRC_DIR_NATIVE}/win\" + else + TK_PLATFORM_DIR_NATIVE=\"${TK_SRC_DIR_NATIVE}/unix\" + fi + # Overwrite the previous TK_INCLUDES as this should capture both + # public and private headers in the same set. + # We want to ensure these are substituted so as not to require + # any *_NATIVE vars be defined in the Makefile + TK_INCLUDES="-I${TK_GENERIC_DIR_NATIVE} -I${TK_PLATFORM_DIR_NATIVE}" + # Detect and add ttk subdir + if test -d "${TK_SRC_DIR}/generic/ttk"; then + TK_INCLUDES="${TK_INCLUDES} -I\"${TK_SRC_DIR_NATIVE}/generic/ttk\"" + fi + if test "${TEA_WINDOWINGSYSTEM}" != "x11"; then + TK_INCLUDES="${TK_INCLUDES} -I\"${TK_XLIB_DIR_NATIVE}\"" + fi + if test "${TEA_WINDOWINGSYSTEM}" = "aqua"; then + TK_INCLUDES="${TK_INCLUDES} -I\"${TK_SRC_DIR_NATIVE}/macosx\"" + fi + if test "`uname -s`" = "Darwin"; then + # If Tk was built as a framework, attempt to use + # the framework's Headers and PrivateHeaders directories + case ${TK_DEFS} in + *TK_FRAMEWORK*) + if test -d "${TK_BIN_DIR}/Headers" -a \ + -d "${TK_BIN_DIR}/PrivateHeaders"; then + TK_INCLUDES="-I\"${TK_BIN_DIR}/Headers\" -I\"${TK_BIN_DIR}/PrivateHeaders\" ${TK_INCLUDES}" + else + TK_INCLUDES="${TK_INCLUDES} ${TK_INCLUDE_SPEC} `echo "${TK_INCLUDE_SPEC}" | sed -e 's/Headers/PrivateHeaders/'`" + fi + ;; + esac + result="Using ${TK_INCLUDES}" + else + if test ! -f "${TK_SRC_DIR}/generic/tkInt.h" ; then + AC_MSG_ERROR([Cannot find private header tkInt.h in ${TK_SRC_DIR}]) + fi + result="Using srcdir found in tkConfig.sh: ${TK_SRC_DIR}" + fi + fi + + AC_SUBST(TK_TOP_DIR_NATIVE) + AC_SUBST(TK_XLIB_DIR_NATIVE) + + AC_SUBST(TK_INCLUDES) + AC_MSG_RESULT([${result}]) +]) + +#------------------------------------------------------------------------ +# TEA_PUBLIC_TK_HEADERS -- +# +# Locate the installed public Tk header files +# +# Arguments: +# None. +# +# Requires: +# CYGPATH must be set +# +# Results: +# +# Adds a --with-tkinclude switch to configure. +# Result is cached. +# +# Substitutes the following vars: +# TK_INCLUDES +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_PUBLIC_TK_HEADERS], [ + AC_MSG_CHECKING([for Tk public headers]) + + AC_ARG_WITH(tkinclude, [ --with-tkinclude directory containing the public Tk header files], with_tkinclude=${withval}) + + AC_CACHE_VAL(ac_cv_c_tkh, [ + # Use the value from --with-tkinclude, if it was given + + if test x"${with_tkinclude}" != x ; then + if test -f "${with_tkinclude}/tk.h" ; then + ac_cv_c_tkh=${with_tkinclude} + else + AC_MSG_ERROR([${with_tkinclude} directory does not contain tk.h]) + fi + else + list="" + if test "`uname -s`" = "Darwin"; then + # If Tk was built as a framework, attempt to use + # the framework's Headers directory. + case ${TK_DEFS} in + *TK_FRAMEWORK*) + list="`ls -d ${TK_BIN_DIR}/Headers 2>/dev/null`" + ;; + esac + fi + + # Look in the source dir only if Tk is not installed, + # and in that situation, look there before installed locations. + if test -f "${TK_BIN_DIR}/Makefile" ; then + list="$list `ls -d ${TK_SRC_DIR}/generic 2>/dev/null`" + fi + + # Check order: pkg --prefix location, Tk's --prefix location, + # relative to directory of tkConfig.sh, Tcl's --prefix location, + # relative to directory of tclConfig.sh. + + eval "temp_includedir=${includedir}" + list="$list \ + `ls -d ${temp_includedir} 2>/dev/null` \ + `ls -d ${TK_PREFIX}/include 2>/dev/null` \ + `ls -d ${TK_BIN_DIR}/../include 2>/dev/null` \ + `ls -d ${TCL_PREFIX}/include 2>/dev/null` \ + `ls -d ${TCL_BIN_DIR}/../include 2>/dev/null`" + if test "${TEA_PLATFORM}" != "windows" -o "$GCC" = "yes"; then + list="$list /usr/local/include /usr/include" + if test x"${TK_INCLUDE_SPEC}" != x ; then + d=`echo "${TK_INCLUDE_SPEC}" | sed -e 's/^-I//'` + list="$list `ls -d ${d} 2>/dev/null`" + fi + fi + for i in $list ; do + if test -f "$i/tk.h" ; then + ac_cv_c_tkh=$i + break + fi + done + fi + ]) + + # Print a message based on how we determined the include path + + if test x"${ac_cv_c_tkh}" = x ; then + AC_MSG_ERROR([tk.h not found. Please specify its location with --with-tkinclude]) + else + AC_MSG_RESULT([${ac_cv_c_tkh}]) + fi + + # Convert to a native path and substitute into the output files. + + INCLUDE_DIR_NATIVE=`${CYGPATH} ${ac_cv_c_tkh}` + + TK_INCLUDES=-I\"${INCLUDE_DIR_NATIVE}\" + + AC_SUBST(TK_INCLUDES) + + if test "${TEA_WINDOWINGSYSTEM}" != "x11"; then + # On Windows and Aqua, we need the X compat headers + AC_MSG_CHECKING([for X11 header files]) + if test ! -r "${INCLUDE_DIR_NATIVE}/X11/Xlib.h"; then + INCLUDE_DIR_NATIVE="`${CYGPATH} ${TK_SRC_DIR}/xlib`" + TK_XINCLUDES=-I\"${INCLUDE_DIR_NATIVE}\" + AC_SUBST(TK_XINCLUDES) + fi + AC_MSG_RESULT([${INCLUDE_DIR_NATIVE}]) + fi +]) + +#------------------------------------------------------------------------ +# TEA_PATH_CONFIG -- +# +# Locate the ${1}Config.sh file and perform a sanity check on +# the ${1} compile flags. These are used by packages like +# [incr Tk] that load *Config.sh files from more than Tcl and Tk. +# +# Arguments: +# none +# +# Results: +# +# Adds the following arguments to configure: +# --with-$1=... +# +# Defines the following vars: +# $1_BIN_DIR Full path to the directory containing +# the $1Config.sh file +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_PATH_CONFIG], [ + # + # Ok, lets find the $1 configuration + # First, look for one uninstalled. + # the alternative search directory is invoked by --with-$1 + # + + if test x"${no_$1}" = x ; then + # we reset no_$1 in case something fails here + no_$1=true + AC_ARG_WITH($1, [ --with-$1 directory containing $1 configuration ($1Config.sh)], with_$1config=${withval}) + AC_MSG_CHECKING([for $1 configuration]) + AC_CACHE_VAL(ac_cv_c_$1config,[ + + # First check to see if --with-$1 was specified. + if test x"${with_$1config}" != x ; then + case ${with_$1config} in + */$1Config.sh ) + if test -f ${with_$1config}; then + AC_MSG_WARN([--with-$1 argument should refer to directory containing $1Config.sh, not to $1Config.sh itself]) + with_$1config=`echo ${with_$1config} | sed 's!/$1Config\.sh$!!'` + fi;; + esac + if test -f "${with_$1config}/$1Config.sh" ; then + ac_cv_c_$1config=`(cd ${with_$1config}; pwd)` + else + AC_MSG_ERROR([${with_$1config} directory doesn't contain $1Config.sh]) + fi + fi + + # then check for a private $1 installation + if test x"${ac_cv_c_$1config}" = x ; then + for i in \ + ../$1 \ + `ls -dr ../$1*[[0-9]].[[0-9]]*.[[0-9]]* 2>/dev/null` \ + `ls -dr ../$1*[[0-9]].[[0-9]][[0-9]] 2>/dev/null` \ + `ls -dr ../$1*[[0-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../$1*[[0-9]].[[0-9]]* 2>/dev/null` \ + ../../$1 \ + `ls -dr ../../$1*[[0-9]].[[0-9]]*.[[0-9]]* 2>/dev/null` \ + `ls -dr ../../$1*[[0-9]].[[0-9]][[0-9]] 2>/dev/null` \ + `ls -dr ../../$1*[[0-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../../$1*[[0-9]].[[0-9]]* 2>/dev/null` \ + ../../../$1 \ + `ls -dr ../../../$1*[[0-9]].[[0-9]]*.[[0-9]]* 2>/dev/null` \ + `ls -dr ../../../$1*[[0-9]].[[0-9]][[0-9]] 2>/dev/null` \ + `ls -dr ../../../$1*[[0-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../../../$1*[[0-9]].[[0-9]]* 2>/dev/null` \ + ${srcdir}/../$1 \ + `ls -dr ${srcdir}/../$1*[[0-9]].[[0-9]]*.[[0-9]]* 2>/dev/null` \ + `ls -dr ${srcdir}/../$1*[[0-9]].[[0-9]][[0-9]] 2>/dev/null` \ + `ls -dr ${srcdir}/../$1*[[0-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ${srcdir}/../$1*[[0-9]].[[0-9]]* 2>/dev/null` \ + ; do + if test -f "$i/$1Config.sh" ; then + ac_cv_c_$1config=`(cd $i; pwd)` + break + fi + if test -f "$i/unix/$1Config.sh" ; then + ac_cv_c_$1config=`(cd $i/unix; pwd)` + break + fi + done + fi + + # check in a few common install locations + if test x"${ac_cv_c_$1config}" = x ; then + for i in `ls -d ${libdir} 2>/dev/null` \ + `ls -d ${exec_prefix}/lib 2>/dev/null` \ + `ls -d ${prefix}/lib 2>/dev/null` \ + `ls -d /usr/local/lib 2>/dev/null` \ + `ls -d /usr/contrib/lib 2>/dev/null` \ + `ls -d /usr/pkg/lib 2>/dev/null` \ + `ls -d /usr/lib 2>/dev/null` \ + `ls -d /usr/lib64 2>/dev/null` \ + ; do + if test -f "$i/$1Config.sh" ; then + ac_cv_c_$1config=`(cd $i; pwd)` + break + fi + done + fi + ]) + + if test x"${ac_cv_c_$1config}" = x ; then + $1_BIN_DIR="# no $1 configs found" + AC_MSG_WARN([Cannot find $1 configuration definitions]) + exit 0 + else + no_$1= + $1_BIN_DIR=${ac_cv_c_$1config} + AC_MSG_RESULT([found $$1_BIN_DIR/$1Config.sh]) + fi + fi +]) + +#------------------------------------------------------------------------ +# TEA_LOAD_CONFIG -- +# +# Load the $1Config.sh file +# +# Arguments: +# +# Requires the following vars to be set: +# $1_BIN_DIR +# +# Results: +# +# Substitutes the following vars: +# $1_SRC_DIR +# $1_LIB_FILE +# $1_LIB_SPEC +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_LOAD_CONFIG], [ + AC_MSG_CHECKING([for existence of ${$1_BIN_DIR}/$1Config.sh]) + + if test -f "${$1_BIN_DIR}/$1Config.sh" ; then + AC_MSG_RESULT([loading]) + . "${$1_BIN_DIR}/$1Config.sh" + else + AC_MSG_RESULT([file not found]) + fi + + # + # If the $1_BIN_DIR is the build directory (not the install directory), + # then set the common variable name to the value of the build variables. + # For example, the variable $1_LIB_SPEC will be set to the value + # of $1_BUILD_LIB_SPEC. An extension should make use of $1_LIB_SPEC + # instead of $1_BUILD_LIB_SPEC since it will work with both an + # installed and uninstalled version of Tcl. + # + + if test -f "${$1_BIN_DIR}/Makefile" ; then + AC_MSG_WARN([Found Makefile - using build library specs for $1]) + $1_LIB_SPEC=${$1_BUILD_LIB_SPEC} + $1_STUB_LIB_SPEC=${$1_BUILD_STUB_LIB_SPEC} + $1_STUB_LIB_PATH=${$1_BUILD_STUB_LIB_PATH} + $1_INCLUDE_SPEC=${$1_BUILD_INCLUDE_SPEC} + $1_LIBRARY_PATH=${$1_LIBRARY_PATH} + fi + + AC_SUBST($1_VERSION) + AC_SUBST($1_BIN_DIR) + AC_SUBST($1_SRC_DIR) + + AC_SUBST($1_LIB_FILE) + AC_SUBST($1_LIB_SPEC) + + AC_SUBST($1_STUB_LIB_FILE) + AC_SUBST($1_STUB_LIB_SPEC) + AC_SUBST($1_STUB_LIB_PATH) + + # Allow the caller to prevent this auto-check by specifying any 2nd arg + AS_IF([test "x$2" = x], [ + # Check both upper and lower-case variants + # If a dev wanted non-stubs libs, this function could take an option + # to not use _STUB in the paths below + AS_IF([test "x${$1_STUB_LIB_SPEC}" = x], + [TEA_LOAD_CONFIG_LIB(translit($1,[a-z],[A-Z])_STUB)], + [TEA_LOAD_CONFIG_LIB($1_STUB)]) + ]) +]) + +#------------------------------------------------------------------------ +# TEA_LOAD_CONFIG_LIB -- +# +# Helper function to load correct library from another extension's +# ${PACKAGE}Config.sh. +# +# Results: +# Adds to LIBS the appropriate extension library +#------------------------------------------------------------------------ +AC_DEFUN([TEA_LOAD_CONFIG_LIB], [ + AC_MSG_CHECKING([For $1 library for LIBS]) + # This simplifies the use of stub libraries by automatically adding + # the stub lib to your path. Normally this would add to SHLIB_LD_LIBS, + # but this is called before CONFIG_CFLAGS. More importantly, this adds + # to PKG_LIBS, which becomes LIBS, and that is only used by SHLIB_LD. + if test "x${$1_LIB_SPEC}" != "x" ; then + if test "${TEA_PLATFORM}" = "windows" -a "$GCC" != "yes" ; then + TEA_ADD_LIBS([\"`${CYGPATH} ${$1_LIB_PATH}`\"]) + AC_MSG_RESULT([using $1_LIB_PATH ${$1_LIB_PATH}]) + else + TEA_ADD_LIBS([${$1_LIB_SPEC}]) + AC_MSG_RESULT([using $1_LIB_SPEC ${$1_LIB_SPEC}]) + fi + else + AC_MSG_RESULT([file not found]) + fi +]) + +#------------------------------------------------------------------------ +# TEA_EXPORT_CONFIG -- +# +# Define the data to insert into the ${PACKAGE}Config.sh file +# +# Arguments: +# +# Requires the following vars to be set: +# $1 +# +# Results: +# Substitutes the following vars: +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_EXPORT_CONFIG], [ + #-------------------------------------------------------------------- + # These are for $1Config.sh + #-------------------------------------------------------------------- + + # pkglibdir must be a fully qualified path and (not ${exec_prefix}/lib) + eval pkglibdir="[$]{libdir}/$1${PACKAGE_VERSION}" + if test "${TCL_LIB_VERSIONS_OK}" = "ok"; then + eval $1_LIB_FLAG="-l$1${PACKAGE_VERSION}" + eval $1_STUB_LIB_FLAG="-l$1stub${PACKAGE_VERSION}" + else + eval $1_LIB_FLAG="-l$1`echo ${PACKAGE_VERSION} | tr -d .`" + eval $1_STUB_LIB_FLAG="-l$1stub`echo ${PACKAGE_VERSION} | tr -d .`" + fi + $1_BUILD_LIB_SPEC="-L`$CYGPATH $(pwd)` ${$1_LIB_FLAG}" + $1_LIB_SPEC="-L`$CYGPATH ${pkglibdir}` ${$1_LIB_FLAG}" + $1_BUILD_STUB_LIB_SPEC="-L`$CYGPATH $(pwd)` [$]{$1_STUB_LIB_FLAG}" + $1_STUB_LIB_SPEC="-L`$CYGPATH ${pkglibdir}` [$]{$1_STUB_LIB_FLAG}" + $1_BUILD_STUB_LIB_PATH="`$CYGPATH $(pwd)`/[$]{PKG_STUB_LIB_FILE}" + $1_STUB_LIB_PATH="`$CYGPATH ${pkglibdir}`/[$]{PKG_STUB_LIB_FILE}" + + AC_SUBST($1_BUILD_LIB_SPEC) + AC_SUBST($1_LIB_SPEC) + AC_SUBST($1_BUILD_STUB_LIB_SPEC) + AC_SUBST($1_STUB_LIB_SPEC) + AC_SUBST($1_BUILD_STUB_LIB_PATH) + AC_SUBST($1_STUB_LIB_PATH) + + AC_SUBST(MAJOR_VERSION) + AC_SUBST(MINOR_VERSION) + AC_SUBST(PATCHLEVEL) +]) + + +#------------------------------------------------------------------------ +# TEA_INSTALLER -- +# +# Configure the installer. +# +# Arguments: +# none +# +# Results: +# Substitutes the following vars: +# INSTALL +# INSTALL_DATA_DIR +# INSTALL_DATA +# INSTALL_PROGRAM +# INSTALL_SCRIPT +# INSTALL_LIBRARY +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_INSTALLER], [ + INSTALL='$(SHELL) $(srcdir)/tclconfig/install-sh -c' + INSTALL_DATA_DIR='${INSTALL} -d -m 755' + INSTALL_DATA='${INSTALL} -m 644' + INSTALL_PROGRAM='${INSTALL} -m 755' + INSTALL_SCRIPT='${INSTALL} -m 755' + + TEA_CONFIG_SYSTEM + case $system in + HP-UX-*) INSTALL_LIBRARY='${INSTALL} -m 755' ;; + *) INSTALL_LIBRARY='${INSTALL} -m 644' ;; + esac + + AC_SUBST(INSTALL) + AC_SUBST(INSTALL_DATA_DIR) + AC_SUBST(INSTALL_DATA) + AC_SUBST(INSTALL_PROGRAM) + AC_SUBST(INSTALL_SCRIPT) + AC_SUBST(INSTALL_LIBRARY) +]) + +### +# Tip 430 - ZipFS Modifications +### +#------------------------------------------------------------------------ +# TEA_ZIPFS_SUPPORT +# Locate a zip encoder installed on the system path, or none. +# +# Arguments: +# none +# +# Results: +# Substitutes the following vars: +# MACHER_PROG +# ZIP_PROG +# ZIP_PROG_OPTIONS +# ZIP_PROG_VFSSEARCH +# ZIP_INSTALL_OBJS +#------------------------------------------------------------------------ + +AC_DEFUN([TEA_ZIPFS_SUPPORT], [ + MACHER_PROG="" + ZIP_PROG="" + ZIP_PROG_OPTIONS="" + ZIP_PROG_VFSSEARCH="" + ZIP_INSTALL_OBJS="" + + AC_MSG_CHECKING([for macher]) + AC_CACHE_VAL(ac_cv_path_macher, [ + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/macher 2> /dev/null` \ + `ls -r $dir/macher 2> /dev/null` ; do + if test x"$ac_cv_path_macher" = x ; then + if test -f "$j" ; then + ac_cv_path_macher=$j + break + fi + fi + done + done + ]) + if test -f "$ac_cv_path_macher" ; then + MACHER_PROG="$ac_cv_path_macher" + AC_MSG_RESULT([$MACHER_PROG]) + AC_MSG_RESULT([Found macher in environment]) + fi + AC_MSG_CHECKING([for zip]) + AC_CACHE_VAL(ac_cv_path_zip, [ + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/zip 2> /dev/null` \ + `ls -r $dir/zip 2> /dev/null` ; do + if test x"$ac_cv_path_zip" = x ; then + if test -f "$j" ; then + ac_cv_path_zip=$j + break + fi + fi + done + done + ]) + if test -f "$ac_cv_path_zip" ; then + ZIP_PROG="$ac_cv_path_zip" + AC_MSG_RESULT([$ZIP_PROG]) + ZIP_PROG_OPTIONS="-rq" + ZIP_PROG_VFSSEARCH="*" + AC_MSG_RESULT([Found INFO Zip in environment]) + # Use standard arguments for zip + else + # It is not an error if an installed version of Zip can't be located. + # We can use the locally distributed minizip instead + ZIP_PROG="./minizip${EXEEXT_FOR_BUILD}" + ZIP_PROG_OPTIONS="-o -r" + ZIP_PROG_VFSSEARCH="*" + ZIP_INSTALL_OBJS="minizip${EXEEXT_FOR_BUILD}" + AC_MSG_RESULT([No zip found on PATH. Building minizip]) + fi + AC_SUBST(MACHER_PROG) + AC_SUBST(ZIP_PROG) + AC_SUBST(ZIP_PROG_OPTIONS) + AC_SUBST(ZIP_PROG_VFSSEARCH) + AC_SUBST(ZIP_INSTALL_OBJS) +]) + +# Local Variables: +# mode: autoconf +# End: \ No newline at end of file diff --git a/SQLITE/tea/win/makefile.vc b/SQLITE/tea/win/makefile.vc new file mode 100644 index 0000000..da56e81 --- /dev/null +++ b/SQLITE/tea/win/makefile.vc @@ -0,0 +1,430 @@ +# makefile.vc -- -*- Makefile -*- +# +# Microsoft Visual C++ makefile for use with nmake.exe v1.62+ (VC++ 5.0+) +# +# This makefile is based upon the Tcl 8.4 Makefile.vc and modified to +# make it suitable as a general package makefile. Look for the word EDIT +# which marks sections that may need modification. As a minumum you will +# need to change the PROJECT, DOTVERSION and DLLOBJS variables to values +# relevant to your package. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# Copyright (c) 1995-1996 Sun Microsystems, Inc. +# Copyright (c) 1998-2000 Ajuba Solutions. +# Copyright (c) 2001 ActiveState Corporation. +# Copyright (c) 2001-2002 David Gravereaux. +# Copyright (c) 2003 Pat Thoyts +# +#------------------------------------------------------------------------- +# RCS: @(#)$Id: makefile.vc,v 1.4 2004/07/26 08:22:05 patthoyts Exp $ +#------------------------------------------------------------------------- + +!if !defined(MSDEVDIR) && !defined(MSVCDIR) && !defined(VCINSTALLDIR) && !defined(MSSDK) && !defined(WINDOWSSDKDIR) +MSG = ^ +You will need to run vcvars32.bat from Developer Studio, first, to setup^ +the environment. Jump to this line to read the new instructions. +!error $(MSG) +!endif + +#------------------------------------------------------------------------------ +# HOW TO USE this makefile: +# +# 1) It is now necessary to have %MSVCDir% set in the environment. This is +# used as a check to see if vcvars32.bat had been run prior to running +# nmake or during the installation of Microsoft Visual C++, MSVCDir had +# been set globally and the PATH adjusted. Either way is valid. +# +# You'll need to run vcvars32.bat contained in the MsDev's vc(98)/bin +# directory to setup the proper environment, if needed, for your current +# setup. This is a needed bootstrap requirement and allows the swapping of +# different environments to be easier. +# +# 2) To use the Platform SDK (not expressly needed), run setenv.bat after +# vcvars32.bat according to the instructions for it. This can also turn on +# the 64-bit compiler, if your SDK has it. +# +# 3) Targets are: +# all -- Builds everything. +# <project> -- Builds the project (eg: nmake sample) +# test -- Builds and runs the test suite. +# install -- Installs the built binaries and libraries to $(INSTALLDIR) +# in an appropriate subdirectory. +# clean/realclean/distclean -- varying levels of cleaning. +# +# 4) Macros usable on the commandline: +# INSTALLDIR=<path> +# Sets where to install Tcl from the built binaries. +# C:\Progra~1\Tcl is assumed when not specified. +# +# OPTS=static,msvcrt,staticpkg,threads,symbols,profile,loimpact,none +# Sets special options for the core. The default is for none. +# Any combination of the above may be used (comma separated). +# 'none' will over-ride everything to nothing. +# +# static = Builds a static library of the core instead of a +# dll. The shell will be static (and large), as well. +# msvcrt = Effects the static option only to switch it from +# using libcmt(d) as the C runtime [by default] to +# msvcrt(d). This is useful for static embedding +# support. +# staticpkg = Effects the static option only to switch +# tclshXX.exe to have the dde and reg extension linked +# inside it. +# threads = Turns on full multithreading support. +# thrdalloc = Use the thread allocator (shared global free pool). +# symbols = Adds symbols for step debugging. +# profile = Adds profiling hooks. Map file is assumed. +# loimpact = Adds a flag for how NT treats the heap to keep memory +# in use, low. This is said to impact alloc performance. +# +# STATS=memdbg,compdbg,none +# Sets optional memory and bytecode compiler debugging code added +# to the core. The default is for none. Any combination of the +# above may be used (comma separated). 'none' will over-ride +# everything to nothing. +# +# memdbg = Enables the debugging memory allocator. +# compdbg = Enables byte compilation logging. +# +# MACHINE=(IX86|IA64|ALPHA) +# Set the machine type used for the compiler, linker, and +# resource compiler. This hook is needed to tell the tools +# when alternate platforms are requested. IX86 is the default +# when not specified. +# +# TMP_DIR=<path> +# OUT_DIR=<path> +# Hooks to allow the intermediate and output directories to be +# changed. $(OUT_DIR) is assumed to be +# $(BINROOT)\(Release|Debug) based on if symbols are requested. +# $(TMP_DIR) will de $(OUT_DIR)\<buildtype> by default. +# +# TESTPAT=<file> +# Reads the tests requested to be run from this file. +# +# CFG_ENCODING=encoding +# name of encoding for configuration information. Defaults +# to cp1252 +# +# 5) Examples: +# +# Basic syntax of calling nmake looks like this: +# nmake [-nologo] -f makefile.vc [target|macrodef [target|macrodef] [...]] +# +# Standard (no frills) +# c:\tcl_src\win\>c:\progra~1\micros~1\vc98\bin\vcvars32.bat +# Setting environment for using Microsoft Visual C++ tools. +# c:\tcl_src\win\>nmake -f makefile.vc all +# c:\tcl_src\win\>nmake -f makefile.vc install INSTALLDIR=c:\progra~1\tcl +# +# Building for Win64 +# c:\tcl_src\win\>c:\progra~1\micros~1\vc98\bin\vcvars32.bat +# Setting environment for using Microsoft Visual C++ tools. +# c:\tcl_src\win\>c:\progra~1\platfo~1\setenv.bat /pre64 /RETAIL +# Targeting Windows pre64 RETAIL +# c:\tcl_src\win\>nmake -f makefile.vc MACHINE=IA64 +# +#------------------------------------------------------------------------------ +#============================================================================== +############################################################################### +#------------------------------------------------------------------------------ + +!if !exist("makefile.vc") +MSG = ^ +You must run this makefile only from the directory it is in.^ +Please `cd` to its location first. +!error $(MSG) +!endif + +#------------------------------------------------------------------------- +# Project specific information (EDIT) +# +# You should edit this with the name and version of your project. This +# information is used to generate the name of the package library and +# it's install location. +# +# For example, the sample extension is going to build sample04.dll and +# would install it into $(INSTALLDIR)\lib\sample04 +# +# You need to specify the object files that need to be linked into your +# binary here. +# +#------------------------------------------------------------------------- + +PROJECT = sqlite3 +!include "rules.vc" + +# nmakehelp -V <file> <tag> will search the file for tag, skips until a +# number and returns all character until a character not in [0-9.ab] +# is read. + +!if [echo REM = This file is generated from Makefile.vc > versions.vc] +!endif +# get project version from row "AC_INIT([sqlite], [3.x.y])" +!if [echo DOTVERSION = \>> versions.vc] \ + && [nmakehlp -V ..\configure.ac AC_INIT >> versions.vc] +!endif +!include "versions.vc" + +VERSION = $(DOTVERSION:.=) +STUBPREFIX = $(PROJECT)stub + +#------------------------------------------------------------------------- +# Target names and paths ( shouldn't need changing ) +#------------------------------------------------------------------------- + +BINROOT = . +ROOT = .. + +PRJIMPLIB = $(OUT_DIR)\$(PROJECT)$(VERSION)$(SUFX).lib +PRJLIBNAME = $(PROJECT).$(EXT) +PRJLIB = $(OUT_DIR)\$(PRJLIBNAME) + +PRJSTUBLIBNAME = $(STUBPREFIX)$(VERSION).lib +PRJSTUBLIB = $(OUT_DIR)\$(PRJSTUBLIBNAME) + +### Make sure we use backslash only. +PRJ_INSTALL_DIR = $(_INSTALLDIR)\$(PROJECT)$(DOTVERSION) +LIB_INSTALL_DIR = $(PRJ_INSTALL_DIR) +BIN_INSTALL_DIR = $(PRJ_INSTALL_DIR) +DOC_INSTALL_DIR = $(PRJ_INSTALL_DIR) +SCRIPT_INSTALL_DIR = $(PRJ_INSTALL_DIR) +INCLUDE_INSTALL_DIR = $(_TCLDIR)\include + +### The following paths CANNOT have spaces in them. +GENERICDIR = $(ROOT)\generic +WINDIR = $(ROOT)\win +LIBDIR = $(ROOT)\library +DOCDIR = $(ROOT)\doc +TOOLSDIR = $(ROOT)\tools +COMPATDIR = $(ROOT)\compat + +### Figure out where the primary source code file(s) is/are. +!if exist("$(ROOT)\..\..\sqlite3.c") && exist("$(ROOT)\..\..\src\tclsqlite.c") +SQL_INCLUDES = -I"$(ROOT)\..\.." +SQLITE_SRCDIR = $(ROOT)\..\.. +TCLSQLITE_SRCDIR = $(ROOT)\..\..\src +DLLOBJS = $(TMP_DIR)\sqlite3.obj $(TMP_DIR)\tclsqlite.obj +!else +TCLSQLITE_SRCDIR = $(ROOT)\generic +DLLOBJS = $(TMP_DIR)\tclsqlite3.obj +!endif + +#--------------------------------------------------------------------- +# Compile flags +#--------------------------------------------------------------------- + +!if !$(DEBUG) +!if $(OPTIMIZING) +### This cranks the optimization level to maximize speed +cdebug = -O2 -Op -Gs +!else +cdebug = +!endif +!else if "$(MACHINE)" == "IA64" +### Warnings are too many, can't support warnings into errors. +cdebug = -Z7 -Od -GZ +!else +cdebug = -Z7 -WX -Od -GZ +!endif + +### Declarations common to all compiler options +cflags = -nologo -c -W3 -D_CRT_SECURE_NO_WARNINGS -YX -Fp$(TMP_DIR)^\ + +!if $(MSVCRT) +!if $(DEBUG) +crt = -MDd +!else +crt = -MD +!endif +!else +!if $(DEBUG) +crt = -MTd +!else +crt = -MT +!endif +!endif + +INCLUDES = $(SQL_INCLUDES) $(TCL_INCLUDES) -I"$(WINDIR)" \ + -I"$(GENERICDIR)" -I"$(ROOT)\.." +BASE_CLFAGS = $(cflags) $(cdebug) $(crt) $(INCLUDES) \ + -DSQLITE_3_SUFFIX_ONLY=1 -DSQLITE_ENABLE_RTREE=1 \ + -DSQLITE_ENABLE_FTS3=1 -DSQLITE_OMIT_DEPRECATED=1 \ + -DSQLITE_ENABLE_FTS4=1 \ + -DSQLITE_ENABLE_FTS5=1 \ + -DSQLITE_3_SUFFIX_ONLY=1 \ + -DSQLITE_ENABLE_RTREE=1 \ + -DSQLITE_ENABLE_GEOPOLY=1 \ + -DSQLITE_ENABLE_MATH_FUNCTIONS=1 \ + -DSQLITE_ENABLE_DESERIALIZE=1 \ + -DSQLITE_ENABLE_DBPAGE_VTAB=1 \ + -DSQLITE_ENABLE_BYTECODE_VTAB=1 \ + -DSQLITE_ENABLE_DBSTAT_VTAB=1 + +CON_CFLAGS = $(cflags) $(cdebug) $(crt) -DCONSOLE -DSQLITE_ENABLE_FTS3=1 +TCL_CFLAGS = -DBUILD_sqlite -DUSE_TCL_STUBS \ + -DPACKAGE_VERSION="\"$(DOTVERSION)\"" $(BASE_CLFAGS) \ + $(OPTDEFINES) + +#--------------------------------------------------------------------- +# Link flags +#--------------------------------------------------------------------- + +!if $(DEBUG) +ldebug = -debug:full -debugtype:cv +!else +ldebug = -release -opt:ref -opt:icf,3 +!endif + +### Declarations common to all linker options +lflags = -nologo -machine:$(MACHINE) $(ldebug) + +!if $(PROFILE) +lflags = $(lflags) -profile +!endif + +!if $(ALIGN98_HACK) && !$(STATIC_BUILD) +### Align sections for PE size savings. +lflags = $(lflags) -opt:nowin98 +!else if !$(ALIGN98_HACK) && $(STATIC_BUILD) +### Align sections for speed in loading by choosing the virtual page size. +lflags = $(lflags) -align:4096 +!endif + +!if $(LOIMPACT) +lflags = $(lflags) -ws:aggressive +!endif + +dlllflags = $(lflags) -dll +conlflags = $(lflags) -subsystem:console +guilflags = $(lflags) -subsystem:windows +baselibs = $(TCLSTUBLIB) + +#--------------------------------------------------------------------- +# TclTest flags +#--------------------------------------------------------------------- + +!IF "$(TESTPAT)" != "" +TESTFLAGS = $(TESTFLAGS) -file $(TESTPAT) +!ENDIF + +#--------------------------------------------------------------------- +# Project specific targets (EDIT) +#--------------------------------------------------------------------- + +all: setup $(PROJECT) +$(PROJECT): setup $(PRJLIB) +install: install-binaries install-libraries install-docs + +# Tests need to ensure we load the right dll file we +# have to handle the output differently on Win9x. +# +!if "$(OS)" == "Windows_NT" || "$(MSVCDIR)" == "IDE" +test: setup $(PROJECT) + set TCL_LIBRARY=$(ROOT)/library + $(TCLSH) << +load $(PRJLIB:\=/) +cd "$(ROOT)/tests" +set argv "$(TESTFLAGS)" +source all.tcl +<< +!else +test: setup $(PROJECT) + echo Please wait while the test results are collected + set TCL_LIBRARY=$(ROOT)/library + $(TCLSH) << >tests.log +load $(PRJLIB:\=/) +cd "$(ROOT)/tests" +set argv "$(TESTFLAGS)" +source all.tcl +<< + type tests.log | more +!endif + +setup: + @if not exist $(OUT_DIR)\nul mkdir $(OUT_DIR) + @if not exist $(TMP_DIR)\nul mkdir $(TMP_DIR) + +$(PRJLIB): $(DLLOBJS) + $(link32) $(dlllflags) -out:$@ $(baselibs) @<< +$** +<< + -@del $*.exp + +$(PRJSTUBLIB): $(PRJSTUBOBJS) + $(lib32) -nologo -out:$@ $(PRJSTUBOBJS) + +#--------------------------------------------------------------------- +# Implicit rules +#--------------------------------------------------------------------- + +$(TMP_DIR)\sqlite3.obj: $(SQLITE_SRCDIR)\sqlite3.c + $(cc32) $(TCL_CFLAGS) -DBUILD_$(PROJECT) -Fo$(TMP_DIR)\ \ + -c $(SQLITE_SRCDIR)\sqlite3.c + +$(TMP_DIR)\tclsqlite.obj: $(TCLSQLITE_SRCDIR)\tclsqlite.c + $(cc32) $(TCL_CFLAGS) -DBUILD_$(PROJECT) -Fo$(TMP_DIR)\ \ + -c $(TCLSQLITE_SRCDIR)\tclsqlite.c + +$(TMP_DIR)\tclsqlite3.obj: $(TCLSQLITE_SRCDIR)\tclsqlite3.c + $(cc32) $(TCL_CFLAGS) -DBUILD_$(PROJECT) -Fo$(TMP_DIR)\ \ + -c $(TCLSQLITE_SRCDIR)\tclsqlite3.c + +{$(WINDIR)}.rc{$(TMP_DIR)}.res: + $(rc32) -fo $@ -r -i "$(GENERICDIR)" -D__WIN32__ \ +!if $(DEBUG) + -d DEBUG \ +!endif +!if $(TCL_THREADS) + -d TCL_THREADS \ +!endif +!if $(STATIC_BUILD) + -d STATIC_BUILD \ +!endif + $< + +.SUFFIXES: +.SUFFIXES:.c .rc + +#--------------------------------------------------------------------- +# Installation. (EDIT) +# +# You may need to modify this section to reflect the final distribution +# of your files and possibly to generate documentation. +# +#--------------------------------------------------------------------- + +install-binaries: + @echo Installing binaries to '$(SCRIPT_INSTALL_DIR)' + @if not exist "$(SCRIPT_INSTALL_DIR)" mkdir "$(SCRIPT_INSTALL_DIR)" + @$(CPY) $(PRJLIB) "$(SCRIPT_INSTALL_DIR)" >NUL + +install-libraries: + @echo Installing libraries to '$(SCRIPT_INSTALL_DIR)' + @if exist $(LIBDIR) $(CPY) $(LIBDIR)\*.tcl "$(SCRIPT_INSTALL_DIR)" + @echo Installing package index in '$(SCRIPT_INSTALL_DIR)' + @type << >"$(SCRIPT_INSTALL_DIR)\pkgIndex.tcl" +package ifneeded $(PROJECT) $(DOTVERSION) \ + [list load [file join $$dir $(PRJLIBNAME)] sqlite3] +<< + +install-docs: + @echo Installing documentation files to '$(DOC_INSTALL_DIR)' + @if exist $(DOCDIR) $(CPY) $(DOCDIR)\*.n "$(DOC_INSTALL_DIR)" + +#--------------------------------------------------------------------- +# Clean up +#--------------------------------------------------------------------- + +clean: + @if exist $(TMP_DIR)\nul $(RMDIR) $(TMP_DIR) + @if exist $(WINDIR)\version.vc del $(WINDIR)\version.vc + +realclean: clean + @if exist $(OUT_DIR)\nul $(RMDIR) $(OUT_DIR) + +distclean: realclean + @if exist $(WINDIR)\nmakehlp.exe del $(WINDIR)\nmakehlp.exe + @if exist $(WINDIR)\nmakehlp.obj del $(WINDIR)\nmakehlp.obj diff --git a/SQLITE/tea/win/nmakehlp.c b/SQLITE/tea/win/nmakehlp.c new file mode 100644 index 0000000..2dc33cc --- /dev/null +++ b/SQLITE/tea/win/nmakehlp.c @@ -0,0 +1,815 @@ +/* + * ---------------------------------------------------------------------------- + * nmakehlp.c -- + * + * This is used to fix limitations within nmake and the environment. + * + * Copyright (c) 2002 by David Gravereaux. + * Copyright (c) 2006 by Pat Thoyts + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + * ---------------------------------------------------------------------------- + */ + +#define _CRT_SECURE_NO_DEPRECATE +#include <windows.h> +#ifdef _MSC_VER +#pragma comment (lib, "user32.lib") +#pragma comment (lib, "kernel32.lib") +#endif +#include <stdio.h> +#include <math.h> + +/* + * This library is required for x64 builds with _some_ versions of MSVC + */ +#if defined(_M_IA64) || defined(_M_AMD64) +#if _MSC_VER >= 1400 && _MSC_VER < 1500 +#pragma comment(lib, "bufferoverflowU") +#endif +#endif + +/* ISO hack for dumb VC++ */ +#ifdef _MSC_VER +#define snprintf _snprintf +#endif + + +/* protos */ + +static int CheckForCompilerFeature(const char *option); +static int CheckForLinkerFeature(char **options, int count); +static int IsIn(const char *string, const char *substring); +static int SubstituteFile(const char *substs, const char *filename); +static int QualifyPath(const char *path); +static int LocateDependency(const char *keyfile); +static const char *GetVersionFromFile(const char *filename, const char *match, int numdots); +static DWORD WINAPI ReadFromPipe(LPVOID args); + +/* globals */ + +#define CHUNK 25 +#define STATICBUFFERSIZE 1000 +typedef struct { + HANDLE pipe; + char buffer[STATICBUFFERSIZE]; +} pipeinfo; + +pipeinfo Out = {INVALID_HANDLE_VALUE, ""}; +pipeinfo Err = {INVALID_HANDLE_VALUE, ""}; + +/* + * exitcodes: 0 == no, 1 == yes, 2 == error + */ + +int +main( + int argc, + char *argv[]) +{ + char msg[300]; + DWORD dwWritten; + int chars; + const char *s; + + /* + * Make sure children (cl.exe and link.exe) are kept quiet. + */ + + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); + + /* + * Make sure the compiler and linker aren't effected by the outside world. + */ + + SetEnvironmentVariable("CL", ""); + SetEnvironmentVariable("LINK", ""); + + if (argc > 1 && *argv[1] == '-') { + switch (*(argv[1]+1)) { + case 'c': + if (argc != 3) { + chars = snprintf(msg, sizeof(msg) - 1, + "usage: %s -c <compiler option>\n" + "Tests for whether cl.exe supports an option\n" + "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, + &dwWritten, NULL); + return 2; + } + return CheckForCompilerFeature(argv[2]); + case 'l': + if (argc < 3) { + chars = snprintf(msg, sizeof(msg) - 1, + "usage: %s -l <linker option> ?<mandatory option> ...?\n" + "Tests for whether link.exe supports an option\n" + "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, + &dwWritten, NULL); + return 2; + } + return CheckForLinkerFeature(&argv[2], argc-2); + case 'f': + if (argc == 2) { + chars = snprintf(msg, sizeof(msg) - 1, + "usage: %s -f <string> <substring>\n" + "Find a substring within another\n" + "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, + &dwWritten, NULL); + return 2; + } else if (argc == 3) { + /* + * If the string is blank, there is no match. + */ + + return 0; + } else { + return IsIn(argv[2], argv[3]); + } + case 's': + if (argc == 2) { + chars = snprintf(msg, sizeof(msg) - 1, + "usage: %s -s <substitutions file> <file>\n" + "Perform a set of string map type substutitions on a file\n" + "exitcodes: 0\n", + argv[0]); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, + &dwWritten, NULL); + return 2; + } + return SubstituteFile(argv[2], argv[3]); + case 'V': + if (argc != 4) { + chars = snprintf(msg, sizeof(msg) - 1, + "usage: %s -V filename matchstring\n" + "Extract a version from a file:\n" + "eg: pkgIndex.tcl \"package ifneeded http\"", + argv[0]); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, + &dwWritten, NULL); + return 0; + } + s = GetVersionFromFile(argv[2], argv[3], *(argv[1]+2) - '0'); + if (s && *s) { + printf("%s\n", s); + return 0; + } else + return 1; /* Version not found. Return non-0 exit code */ + + case 'Q': + if (argc != 3) { + chars = snprintf(msg, sizeof(msg) - 1, + "usage: %s -Q path\n" + "Emit the fully qualified path\n" + "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, + &dwWritten, NULL); + return 2; + } + return QualifyPath(argv[2]); + + case 'L': + if (argc != 3) { + chars = snprintf(msg, sizeof(msg) - 1, + "usage: %s -L keypath\n" + "Emit the fully qualified path of directory containing keypath\n" + "exitcodes: 0 == success, 1 == not found, 2 == error\n", argv[0]); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, + &dwWritten, NULL); + return 2; + } + return LocateDependency(argv[2]); + } + } + chars = snprintf(msg, sizeof(msg) - 1, + "usage: %s -c|-f|-l|-Q|-s|-V ...\n" + "This is a little helper app to equalize shell differences between WinNT and\n" + "Win9x and get nmake.exe to accomplish its job.\n", + argv[0]); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL); + return 2; +} + +static int +CheckForCompilerFeature( + const char *option) +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + SECURITY_ATTRIBUTES sa; + DWORD threadID; + char msg[300]; + BOOL ok; + HANDLE hProcess, h, pipeThreads[2]; + char cmdline[100]; + + hProcess = GetCurrentProcess(); + + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = INVALID_HANDLE_VALUE; + + ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = FALSE; + + /* + * Create a non-inheritible pipe. + */ + + CreatePipe(&Out.pipe, &h, &sa, 0); + + /* + * Dupe the write side, make it inheritible, and close the original. + */ + + DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE, + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); + + /* + * Same as above, but for the error side. + */ + + CreatePipe(&Err.pipe, &h, &sa, 0); + DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE, + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); + + /* + * Base command line. + */ + + lstrcpy(cmdline, "cl.exe -nologo -c -TC -Zs -X -Fp.\\_junk.pch "); + + /* + * Append our option for testing + */ + + lstrcat(cmdline, option); + + /* + * Filename to compile, which exists, but is nothing and empty. + */ + + lstrcat(cmdline, " .\\nul"); + + ok = CreateProcess( + NULL, /* Module name. */ + cmdline, /* Command line. */ + NULL, /* Process handle not inheritable. */ + NULL, /* Thread handle not inheritable. */ + TRUE, /* yes, inherit handles. */ + DETACHED_PROCESS, /* No console for you. */ + NULL, /* Use parent's environment block. */ + NULL, /* Use parent's starting directory. */ + &si, /* Pointer to STARTUPINFO structure. */ + &pi); /* Pointer to PROCESS_INFORMATION structure. */ + + if (!ok) { + DWORD err = GetLastError(); + int chars = snprintf(msg, sizeof(msg) - 1, + "Tried to launch: \"%s\", but got error [%u]: ", cmdline, err); + + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS| + FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPSTR)&msg[chars], + (300-chars), 0); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, lstrlen(msg), &err,NULL); + return 2; + } + + /* + * Close our references to the write handles that have now been inherited. + */ + + CloseHandle(si.hStdOutput); + CloseHandle(si.hStdError); + + WaitForInputIdle(pi.hProcess, 5000); + CloseHandle(pi.hThread); + + /* + * Start the pipe reader threads. + */ + + pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID); + pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID); + + /* + * Block waiting for the process to end. + */ + + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + + /* + * Wait for our pipe to get done reading, should it be a little slow. + */ + + WaitForMultipleObjects(2, pipeThreads, TRUE, 500); + CloseHandle(pipeThreads[0]); + CloseHandle(pipeThreads[1]); + + /* + * Look for the commandline warning code in both streams. + * - in MSVC 6 & 7 we get D4002, in MSVC 8 we get D9002. + */ + + return !(strstr(Out.buffer, "D4002") != NULL + || strstr(Err.buffer, "D4002") != NULL + || strstr(Out.buffer, "D9002") != NULL + || strstr(Err.buffer, "D9002") != NULL + || strstr(Out.buffer, "D2021") != NULL + || strstr(Err.buffer, "D2021") != NULL); +} + +static int +CheckForLinkerFeature( + char **options, + int count) +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + SECURITY_ATTRIBUTES sa; + DWORD threadID; + char msg[300]; + BOOL ok; + HANDLE hProcess, h, pipeThreads[2]; + int i; + char cmdline[255]; + + hProcess = GetCurrentProcess(); + + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = INVALID_HANDLE_VALUE; + + ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + /* + * Create a non-inheritible pipe. + */ + + CreatePipe(&Out.pipe, &h, &sa, 0); + + /* + * Dupe the write side, make it inheritible, and close the original. + */ + + DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE, + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); + + /* + * Same as above, but for the error side. + */ + + CreatePipe(&Err.pipe, &h, &sa, 0); + DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE, + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); + + /* + * Base command line. + */ + + lstrcpy(cmdline, "link.exe -nologo "); + + /* + * Append our option for testing. + */ + + for (i = 0; i < count; i++) { + lstrcat(cmdline, " \""); + lstrcat(cmdline, options[i]); + lstrcat(cmdline, "\""); + } + + ok = CreateProcess( + NULL, /* Module name. */ + cmdline, /* Command line. */ + NULL, /* Process handle not inheritable. */ + NULL, /* Thread handle not inheritable. */ + TRUE, /* yes, inherit handles. */ + DETACHED_PROCESS, /* No console for you. */ + NULL, /* Use parent's environment block. */ + NULL, /* Use parent's starting directory. */ + &si, /* Pointer to STARTUPINFO structure. */ + &pi); /* Pointer to PROCESS_INFORMATION structure. */ + + if (!ok) { + DWORD err = GetLastError(); + int chars = snprintf(msg, sizeof(msg) - 1, + "Tried to launch: \"%s\", but got error [%u]: ", cmdline, err); + + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS| + FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPSTR)&msg[chars], + (300-chars), 0); + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, lstrlen(msg), &err,NULL); + return 2; + } + + /* + * Close our references to the write handles that have now been inherited. + */ + + CloseHandle(si.hStdOutput); + CloseHandle(si.hStdError); + + WaitForInputIdle(pi.hProcess, 5000); + CloseHandle(pi.hThread); + + /* + * Start the pipe reader threads. + */ + + pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID); + pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID); + + /* + * Block waiting for the process to end. + */ + + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + + /* + * Wait for our pipe to get done reading, should it be a little slow. + */ + + WaitForMultipleObjects(2, pipeThreads, TRUE, 500); + CloseHandle(pipeThreads[0]); + CloseHandle(pipeThreads[1]); + + /* + * Look for the commandline warning code in the stderr stream. + */ + + return !(strstr(Out.buffer, "LNK1117") != NULL || + strstr(Err.buffer, "LNK1117") != NULL || + strstr(Out.buffer, "LNK4044") != NULL || + strstr(Err.buffer, "LNK4044") != NULL || + strstr(Out.buffer, "LNK4224") != NULL || + strstr(Err.buffer, "LNK4224") != NULL); +} + +static DWORD WINAPI +ReadFromPipe( + LPVOID args) +{ + pipeinfo *pi = (pipeinfo *) args; + char *lastBuf = pi->buffer; + DWORD dwRead; + BOOL ok; + + again: + if (lastBuf - pi->buffer + CHUNK > STATICBUFFERSIZE) { + CloseHandle(pi->pipe); + return (DWORD)-1; + } + ok = ReadFile(pi->pipe, lastBuf, CHUNK, &dwRead, 0L); + if (!ok || dwRead == 0) { + CloseHandle(pi->pipe); + return 0; + } + lastBuf += dwRead; + goto again; + + return 0; /* makes the compiler happy */ +} + +static int +IsIn( + const char *string, + const char *substring) +{ + return (strstr(string, substring) != NULL); +} + +/* + * GetVersionFromFile -- + * Looks for a match string in a file and then returns the version + * following the match where a version is anything acceptable to + * package provide or package ifneeded. + */ + +static const char * +GetVersionFromFile( + const char *filename, + const char *match, + int numdots) +{ + static char szBuffer[100]; + char *szResult = NULL; + FILE *fp = fopen(filename, "rt"); + + if (fp != NULL) { + /* + * Read data until we see our match string. + */ + + while (fgets(szBuffer, sizeof(szBuffer), fp) != NULL) { + LPSTR p, q; + + p = strstr(szBuffer, match); + if (p != NULL) { + /* + * Skip to first digit after the match. + */ + + p += strlen(match); + while (*p && !isdigit((unsigned char)*p)) { + ++p; + } + + /* + * Find ending whitespace. + */ + + q = p; + while (*q && (strchr("0123456789.ab", *q)) && (((!strchr(".ab", *q) + && !strchr("ab", q[-1])) || --numdots))) { + ++q; + } + + *q = 0; + szResult = p; + break; + } + } + fclose(fp); + } + return szResult; +} + +/* + * List helpers for the SubstituteFile function + */ + +typedef struct list_item_t { + struct list_item_t *nextPtr; + char * key; + char * value; +} list_item_t; + +/* insert a list item into the list (list may be null) */ +static list_item_t * +list_insert(list_item_t **listPtrPtr, const char *key, const char *value) +{ + list_item_t *itemPtr = (list_item_t *)malloc(sizeof(list_item_t)); + if (itemPtr) { + itemPtr->key = strdup(key); + itemPtr->value = strdup(value); + itemPtr->nextPtr = NULL; + + while(*listPtrPtr) { + listPtrPtr = &(*listPtrPtr)->nextPtr; + } + *listPtrPtr = itemPtr; + } + return itemPtr; +} + +static void +list_free(list_item_t **listPtrPtr) +{ + list_item_t *tmpPtr, *listPtr = *listPtrPtr; + while (listPtr) { + tmpPtr = listPtr; + listPtr = listPtr->nextPtr; + free(tmpPtr->key); + free(tmpPtr->value); + free(tmpPtr); + } +} + +/* + * SubstituteFile -- + * As windows doesn't provide anything useful like sed and it's unreliable + * to use the tclsh you are building against (consider x-platform builds - + * eg compiling AMD64 target from IX86) we provide a simple substitution + * option here to handle autoconf style substitutions. + * The substitution file is whitespace and line delimited. The file should + * consist of lines matching the regular expression: + * \s*\S+\s+\S*$ + * + * Usage is something like: + * nmakehlp -S << $** > $@ + * @PACKAGE_NAME@ $(PACKAGE_NAME) + * @PACKAGE_VERSION@ $(PACKAGE_VERSION) + * << + */ + +static int +SubstituteFile( + const char *substitutions, + const char *filename) +{ + static char szBuffer[1024], szCopy[1024]; + list_item_t *substPtr = NULL; + FILE *fp, *sp; + + fp = fopen(filename, "rt"); + if (fp != NULL) { + + /* + * Build a list of substutitions from the first filename + */ + + sp = fopen(substitutions, "rt"); + if (sp != NULL) { + while (fgets(szBuffer, sizeof(szBuffer), sp) != NULL) { + unsigned char *ks, *ke, *vs, *ve; + ks = (unsigned char*)szBuffer; + while (ks && *ks && isspace(*ks)) ++ks; + ke = ks; + while (ke && *ke && !isspace(*ke)) ++ke; + vs = ke; + while (vs && *vs && isspace(*vs)) ++vs; + ve = vs; + while (ve && *ve && !(*ve == '\r' || *ve == '\n')) ++ve; + *ke = 0, *ve = 0; + list_insert(&substPtr, (char*)ks, (char*)vs); + } + fclose(sp); + } + + /* debug: dump the list */ +#ifndef NDEBUG + { + int n = 0; + list_item_t *p = NULL; + for (p = substPtr; p != NULL; p = p->nextPtr, ++n) { + fprintf(stderr, "% 3d '%s' => '%s'\n", n, p->key, p->value); + } + } +#endif + + /* + * Run the substitutions over each line of the input + */ + + while (fgets(szBuffer, sizeof(szBuffer), fp) != NULL) { + list_item_t *p = NULL; + for (p = substPtr; p != NULL; p = p->nextPtr) { + char *m = strstr(szBuffer, p->key); + if (m) { + char *cp, *op, *sp; + cp = szCopy; + op = szBuffer; + while (op != m) *cp++ = *op++; + sp = p->value; + while (sp && *sp) *cp++ = *sp++; + op += strlen(p->key); + while (*op) *cp++ = *op++; + *cp = 0; + memcpy(szBuffer, szCopy, sizeof(szCopy)); + } + } + printf("%s", szBuffer); + } + + list_free(&substPtr); + } + fclose(fp); + return 0; +} + +BOOL FileExists(LPCTSTR szPath) +{ +#ifndef INVALID_FILE_ATTRIBUTES + #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif + DWORD pathAttr = GetFileAttributes(szPath); + return (pathAttr != INVALID_FILE_ATTRIBUTES && + !(pathAttr & FILE_ATTRIBUTE_DIRECTORY)); +} + + +/* + * QualifyPath -- + * + * This composes the current working directory with a provided path + * and returns the fully qualified and normalized path. + * Mostly needed to setup paths for testing. + */ + +static int +QualifyPath( + const char *szPath) +{ + char szCwd[MAX_PATH + 1]; + + GetFullPathName(szPath, sizeof(szCwd)-1, szCwd, NULL); + printf("%s\n", szCwd); + return 0; +} + +/* + * Implements LocateDependency for a single directory. See that command + * for an explanation. + * Returns 0 if found after printing the directory. + * Returns 1 if not found but no errors. + * Returns 2 on any kind of error + * Basically, these are used as exit codes for the process. + */ +static int LocateDependencyHelper(const char *dir, const char *keypath) +{ + HANDLE hSearch; + char path[MAX_PATH+1]; + size_t dirlen; + int keylen, ret; + WIN32_FIND_DATA finfo; + + if (dir == NULL || keypath == NULL) + return 2; /* Have no real error reporting mechanism into nmake */ + dirlen = strlen(dir); + if ((dirlen + 3) > sizeof(path)) + return 2; + strncpy(path, dir, dirlen); + strncpy(path+dirlen, "\\*", 3); /* Including terminating \0 */ + keylen = strlen(keypath); + +#if 0 /* This function is not available in Visual C++ 6 */ + /* + * Use numerics 0 -> FindExInfoStandard, + * 1 -> FindExSearchLimitToDirectories, + * as these are not defined in Visual C++ 6 + */ + hSearch = FindFirstFileEx(path, 0, &finfo, 1, NULL, 0); +#else + hSearch = FindFirstFile(path, &finfo); +#endif + if (hSearch == INVALID_HANDLE_VALUE) + return 1; /* Not found */ + + /* Loop through all subdirs checking if the keypath is under there */ + ret = 1; /* Assume not found */ + do { + int sublen; + /* + * We need to check it is a directory despite the + * FindExSearchLimitToDirectories in the above call. See SDK docs + */ + if ((finfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) + continue; + sublen = strlen(finfo.cFileName); + if ((dirlen+1+sublen+1+keylen+1) > sizeof(path)) + continue; /* Path does not fit, assume not matched */ + strncpy(path+dirlen+1, finfo.cFileName, sublen); + path[dirlen+1+sublen] = '\\'; + strncpy(path+dirlen+1+sublen+1, keypath, keylen+1); + if (FileExists(path)) { + /* Found a match, print to stdout */ + path[dirlen+1+sublen] = '\0'; + QualifyPath(path); + ret = 0; + break; + } + } while (FindNextFile(hSearch, &finfo)); + FindClose(hSearch); + return ret; +} + +/* + * LocateDependency -- + * + * Locates a dependency for a package. + * keypath - a relative path within the package directory + * that is used to confirm it is the correct directory. + * The search path for the package directory is currently only + * the parent and grandparent of the current working directory. + * If found, the command prints + * name_DIRPATH=<full path of located directory> + * and returns 0. If not found, does not print anything and returns 1. + */ +static int LocateDependency(const char *keypath) +{ + size_t i; + int ret; + static const char *paths[] = {"..", "..\\..", "..\\..\\.."}; + + for (i = 0; i < (sizeof(paths)/sizeof(paths[0])); ++i) { + ret = LocateDependencyHelper(paths[i], keypath); + if (ret == 0) + return ret; + } + return ret; +} + + +/* + * Local variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * indent-tabs-mode: t + * tab-width: 8 + * End: + */ diff --git a/SQLITE/tea/win/rules.vc b/SQLITE/tea/win/rules.vc new file mode 100644 index 0000000..9947105 --- /dev/null +++ b/SQLITE/tea/win/rules.vc @@ -0,0 +1,711 @@ +#------------------------------------------------------------------------------ +# rules.vc -- +# +# Microsoft Visual C++ makefile include for decoding the commandline +# macros. This file does not need editing to build Tcl. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# Copyright (c) 2001-2003 David Gravereaux. +# Copyright (c) 2003-2008 Patrick Thoyts +#------------------------------------------------------------------------------ + +!ifndef _RULES_VC +_RULES_VC = 1 + +cc32 = $(CC) # built-in default. +link32 = link +lib32 = lib +rc32 = $(RC) # built-in default. + +!ifndef INSTALLDIR +### Assume the normal default. +_INSTALLDIR = C:\Program Files\Tcl +!else +### Fix the path separators. +_INSTALLDIR = $(INSTALLDIR:/=\) +!endif + +#---------------------------------------------------------- +# Set the proper copy method to avoid overwrite questions +# to the user when copying files and selecting the right +# "delete all" method. +#---------------------------------------------------------- + +!if "$(OS)" == "Windows_NT" +RMDIR = rmdir /S /Q +ERRNULL = 2>NUL +!if ![ver | find "4.0" > nul] +CPY = echo y | xcopy /i >NUL +COPY = copy >NUL +!else +CPY = xcopy /i /y >NUL +COPY = copy /y >NUL +!endif +!else # "$(OS)" != "Windows_NT" +CPY = xcopy /i >_JUNK.OUT # On Win98 NUL does not work here. +COPY = copy >_JUNK.OUT # On Win98 NUL does not work here. +RMDIR = deltree /Y +NULL = \NUL # Used in testing directory existence +ERRNULL = >NUL # Win9x shell cannot redirect stderr +!endif +MKDIR = mkdir + +#------------------------------------------------------------------------------ +# Determine the host and target architectures and compiler version. +#------------------------------------------------------------------------------ + +_HASH=^# +_VC_MANIFEST_EMBED_EXE= +_VC_MANIFEST_EMBED_DLL= +VCVER=0 +!if ![echo VCVERSION=_MSC_VER > vercl.x] \ + && ![echo $(_HASH)if defined(_M_IX86) >> vercl.x] \ + && ![echo ARCH=IX86 >> vercl.x] \ + && ![echo $(_HASH)elif defined(_M_AMD64) >> vercl.x] \ + && ![echo ARCH=AMD64 >> vercl.x] \ + && ![echo $(_HASH)endif >> vercl.x] \ + && ![cl -nologo -TC -P vercl.x $(ERRNULL)] +!include vercl.i +!if ![echo VCVER= ^\> vercl.vc] \ + && ![set /a $(VCVERSION) / 100 - 6 >> vercl.vc] +!include vercl.vc +!endif +!endif +!if ![del $(ERRNUL) /q/f vercl.x vercl.i vercl.vc] +!endif + +!if ![reg query HKLM\Hardware\Description\System\CentralProcessor\0 /v Identifier | findstr /i x86] +NATIVE_ARCH=IX86 +!else +NATIVE_ARCH=AMD64 +!endif + +# Since MSVC8 we must deal with manifest resources. +!if $(VCVERSION) >= 1400 +_VC_MANIFEST_EMBED_EXE=if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;1 +_VC_MANIFEST_EMBED_DLL=if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;2 +!endif + +!ifndef MACHINE +MACHINE=$(ARCH) +!endif + +!ifndef CFG_ENCODING +CFG_ENCODING = \"cp1252\" +!endif + +!message =============================================================================== + +#---------------------------------------------------------- +# build the helper app we need to overcome nmake's limiting +# environment. +#---------------------------------------------------------- + +!if !exist(nmakehlp.exe) +!if [$(cc32) -nologo nmakehlp.c -link -subsystem:console > nul] +!endif +!endif + +#---------------------------------------------------------- +# Test for compiler features +#---------------------------------------------------------- + +### test for optimizations +!if [nmakehlp -c -Ot] +!message *** Compiler has 'Optimizations' +OPTIMIZING = 1 +!else +!message *** Compiler does not have 'Optimizations' +OPTIMIZING = 0 +!endif + +OPTIMIZATIONS = + +!if [nmakehlp -c -Ot] +OPTIMIZATIONS = $(OPTIMIZATIONS) -Ot +!endif + +!if [nmakehlp -c -Oi] +OPTIMIZATIONS = $(OPTIMIZATIONS) -Oi +!endif + +!if [nmakehlp -c -Op] +OPTIMIZATIONS = $(OPTIMIZATIONS) -Op +!endif + +!if [nmakehlp -c -fp:strict] +OPTIMIZATIONS = $(OPTIMIZATIONS) -fp:strict +!endif + +!if [nmakehlp -c -Gs] +OPTIMIZATIONS = $(OPTIMIZATIONS) -Gs +!endif + +!if [nmakehlp -c -GS] +OPTIMIZATIONS = $(OPTIMIZATIONS) -GS +!endif + +!if [nmakehlp -c -GL] +OPTIMIZATIONS = $(OPTIMIZATIONS) -GL +!endif + +DEBUGFLAGS = + +!if [nmakehlp -c -RTC1] +DEBUGFLAGS = $(DEBUGFLAGS) -RTC1 +!elseif [nmakehlp -c -GZ] +DEBUGFLAGS = $(DEBUGFLAGS) -GZ +!endif + +COMPILERFLAGS =-W3 -DUNICODE -D_UNICODE + +# In v13 -GL and -YX are incompatible. +!if [nmakehlp -c -YX] +!if ![nmakehlp -c -GL] +OPTIMIZATIONS = $(OPTIMIZATIONS) -YX +!endif +!endif + +!if "$(MACHINE)" == "IX86" +### test for pentium errata +!if [nmakehlp -c -QI0f] +!message *** Compiler has 'Pentium 0x0f fix' +COMPILERFLAGS = $(COMPILERFLAGS) -QI0f +!else +!message *** Compiler does not have 'Pentium 0x0f fix' +!endif +!endif + +!if "$(MACHINE)" == "IA64" +### test for Itanium errata +!if [nmakehlp -c -QIA64_Bx] +!message *** Compiler has 'B-stepping errata workarounds' +COMPILERFLAGS = $(COMPILERFLAGS) -QIA64_Bx +!else +!message *** Compiler does not have 'B-stepping errata workarounds' +!endif +!endif + +!if "$(MACHINE)" == "IX86" +### test for -align:4096, when align:512 will do. +!if [nmakehlp -l -opt:nowin98] +!message *** Linker has 'Win98 alignment problem' +ALIGN98_HACK = 1 +!else +!message *** Linker does not have 'Win98 alignment problem' +ALIGN98_HACK = 0 +!endif +!else +ALIGN98_HACK = 0 +!endif + +LINKERFLAGS = + +!if [nmakehlp -l -ltcg] +LINKERFLAGS =-ltcg +!endif + +#---------------------------------------------------------- +# Decode the options requested. +#---------------------------------------------------------- + +!if "$(OPTS)" == "" || [nmakehlp -f "$(OPTS)" "none"] +STATIC_BUILD = 0 +TCL_THREADS = 1 +DEBUG = 0 +SYMBOLS = 0 +PROFILE = 0 +PGO = 0 +MSVCRT = 0 +LOIMPACT = 0 +TCL_USE_STATIC_PACKAGES = 0 +USE_THREAD_ALLOC = 1 +UNCHECKED = 0 +!else +!if [nmakehlp -f $(OPTS) "static"] +!message *** Doing static +STATIC_BUILD = 1 +!else +STATIC_BUILD = 0 +!endif +!if [nmakehlp -f $(OPTS) "msvcrt"] +!message *** Doing msvcrt +MSVCRT = 1 +!else +MSVCRT = 0 +!endif +!if [nmakehlp -f $(OPTS) "staticpkg"] +!message *** Doing staticpkg +TCL_USE_STATIC_PACKAGES = 1 +!else +TCL_USE_STATIC_PACKAGES = 0 +!endif +!if [nmakehlp -f $(OPTS) "nothreads"] +!message *** Compile explicitly for non-threaded tcl +TCL_THREADS = 0 +!else +TCL_THREADS = 1 +USE_THREAD_ALLOC= 1 +!endif +!if [nmakehlp -f $(OPTS) "symbols"] +!message *** Doing symbols +DEBUG = 1 +!else +DEBUG = 0 +!endif +!if [nmakehlp -f $(OPTS) "pdbs"] +!message *** Doing pdbs +SYMBOLS = 1 +!else +SYMBOLS = 0 +!endif +!if [nmakehlp -f $(OPTS) "profile"] +!message *** Doing profile +PROFILE = 1 +!else +PROFILE = 0 +!endif +!if [nmakehlp -f $(OPTS) "pgi"] +!message *** Doing profile guided optimization instrumentation +PGO = 1 +!elseif [nmakehlp -f $(OPTS) "pgo"] +!message *** Doing profile guided optimization +PGO = 2 +!else +PGO = 0 +!endif +!if [nmakehlp -f $(OPTS) "loimpact"] +!message *** Doing loimpact +LOIMPACT = 1 +!else +LOIMPACT = 0 +!endif +!if [nmakehlp -f $(OPTS) "thrdalloc"] +!message *** Doing thrdalloc +USE_THREAD_ALLOC = 1 +!endif +!if [nmakehlp -f $(OPTS) "tclalloc"] +!message *** Doing tclalloc +USE_THREAD_ALLOC = 0 +!endif +!if [nmakehlp -f $(OPTS) "unchecked"] +!message *** Doing unchecked +UNCHECKED = 1 +!else +UNCHECKED = 0 +!endif +!endif + + +!if !$(STATIC_BUILD) +# Make sure we don't build overly fat DLLs. +MSVCRT = 1 +# We shouldn't statically put the extensions inside the shell when dynamic. +TCL_USE_STATIC_PACKAGES = 0 +!endif + + +#---------------------------------------------------------- +# Figure-out how to name our intermediate and output directories. +# We wouldn't want different builds to use the same .obj files +# by accident. +#---------------------------------------------------------- + +#---------------------------------------- +# Naming convention: +# t = full thread support. +# s = static library (as opposed to an +# import library) +# g = linked to the debug enabled C +# run-time. +# x = special static build when it +# links to the dynamic C run-time. +#---------------------------------------- +SUFX = tsgx + +!if $(DEBUG) +BUILDDIRTOP = Debug +!else +BUILDDIRTOP = Release +!endif + +!if "$(MACHINE)" != "IX86" +BUILDDIRTOP =$(BUILDDIRTOP)_$(MACHINE) +!endif +!if $(VCVER) > 6 +BUILDDIRTOP =$(BUILDDIRTOP)_VC$(VCVER) +!endif + +!if !$(DEBUG) || $(DEBUG) && $(UNCHECKED) +SUFX = $(SUFX:g=) +!endif + +TMP_DIRFULL = .\$(BUILDDIRTOP)\$(PROJECT)_ThreadedDynamicStaticX + +!if !$(STATIC_BUILD) +TMP_DIRFULL = $(TMP_DIRFULL:Static=) +SUFX = $(SUFX:s=) +EXT = dll +!if $(MSVCRT) +TMP_DIRFULL = $(TMP_DIRFULL:X=) +SUFX = $(SUFX:x=) +!endif +!else +TMP_DIRFULL = $(TMP_DIRFULL:Dynamic=) +EXT = lib +!if !$(MSVCRT) +TMP_DIRFULL = $(TMP_DIRFULL:X=) +SUFX = $(SUFX:x=) +!endif +!endif + +!if !$(TCL_THREADS) +TMP_DIRFULL = $(TMP_DIRFULL:Threaded=) +SUFX = $(SUFX:t=) +!endif + +!ifndef TMP_DIR +TMP_DIR = $(TMP_DIRFULL) +!ifndef OUT_DIR +OUT_DIR = .\$(BUILDDIRTOP) +!endif +!else +!ifndef OUT_DIR +OUT_DIR = $(TMP_DIR) +!endif +!endif + + +#---------------------------------------------------------- +# Decode the statistics requested. +#---------------------------------------------------------- + +!if "$(STATS)" == "" || [nmakehlp -f "$(STATS)" "none"] +TCL_MEM_DEBUG = 0 +TCL_COMPILE_DEBUG = 0 +!else +!if [nmakehlp -f $(STATS) "memdbg"] +!message *** Doing memdbg +TCL_MEM_DEBUG = 1 +!else +TCL_MEM_DEBUG = 0 +!endif +!if [nmakehlp -f $(STATS) "compdbg"] +!message *** Doing compdbg +TCL_COMPILE_DEBUG = 1 +!else +TCL_COMPILE_DEBUG = 0 +!endif +!endif + + +#---------------------------------------------------------- +# Decode the checks requested. +#---------------------------------------------------------- + +!if "$(CHECKS)" == "" || [nmakehlp -f "$(CHECKS)" "none"] +TCL_NO_DEPRECATED = 0 +WARNINGS = -W3 +!else +!if [nmakehlp -f $(CHECKS) "nodep"] +!message *** Doing nodep check +TCL_NO_DEPRECATED = 1 +!else +TCL_NO_DEPRECATED = 0 +!endif +!if [nmakehlp -f $(CHECKS) "fullwarn"] +!message *** Doing full warnings check +WARNINGS = -W4 +!if [nmakehlp -l -warn:3] +LINKERFLAGS = $(LINKERFLAGS) -warn:3 +!endif +!else +WARNINGS = -W3 +!endif +!if [nmakehlp -f $(CHECKS) "64bit"] && [nmakehlp -c -Wp64] +!message *** Doing 64bit portability warnings +WARNINGS = $(WARNINGS) -Wp64 +!endif +!endif + +!if $(PGO) > 1 +!if [nmakehlp -l -ltcg:pgoptimize] +LINKERFLAGS = $(LINKERFLAGS:-ltcg=) -ltcg:pgoptimize +!else +MSG=^ +This compiler does not support profile guided optimization. +!error $(MSG) +!endif +!elseif $(PGO) > 0 +!if [nmakehlp -l -ltcg:pginstrument] +LINKERFLAGS = $(LINKERFLAGS:-ltcg=) -ltcg:pginstrument +!else +MSG=^ +This compiler does not support profile guided optimization. +!error $(MSG) +!endif +!endif + +#---------------------------------------------------------- +# Set our defines now armed with our options. +#---------------------------------------------------------- + +OPTDEFINES = -DTCL_CFGVAL_ENCODING=$(CFG_ENCODING) -DSTDC_HEADERS + +!if $(TCL_MEM_DEBUG) +OPTDEFINES = $(OPTDEFINES) -DTCL_MEM_DEBUG +!endif +!if $(TCL_COMPILE_DEBUG) +OPTDEFINES = $(OPTDEFINES) -DTCL_COMPILE_DEBUG -DTCL_COMPILE_STATS +!endif +!if $(TCL_THREADS) +OPTDEFINES = $(OPTDEFINES) -DTCL_THREADS=1 +!if $(USE_THREAD_ALLOC) +OPTDEFINES = $(OPTDEFINES) -DUSE_THREAD_ALLOC=1 +!endif +!endif +!if $(STATIC_BUILD) +OPTDEFINES = $(OPTDEFINES) -DSTATIC_BUILD +!endif +!if $(TCL_NO_DEPRECATED) +OPTDEFINES = $(OPTDEFINES) -DTCL_NO_DEPRECATED +!endif + +!if !$(DEBUG) +OPTDEFINES = $(OPTDEFINES) -DNDEBUG +!if $(OPTIMIZING) +OPTDEFINES = $(OPTDEFINES) -DTCL_CFG_OPTIMIZED +!endif +!endif +!if $(PROFILE) +OPTDEFINES = $(OPTDEFINES) -DTCL_CFG_PROFILED +!endif +!if "$(MACHINE)" == "IA64" || "$(MACHINE)" == "AMD64" +OPTDEFINES = $(OPTDEFINES) -DTCL_CFG_DO64BIT +!endif +!if $(VCVERSION) < 1300 +OPTDEFINES = $(OPTDEFINES) -DNO_STRTOI64 +!endif + +#---------------------------------------------------------- +# Locate the Tcl headers to build against +#---------------------------------------------------------- + +!if "$(PROJECT)" == "tcl" + +_TCL_H = ..\generic\tcl.h + +!else + +# If INSTALLDIR set to tcl root dir then reset to the lib dir. +!if exist("$(_INSTALLDIR)\include\tcl.h") +_INSTALLDIR=$(_INSTALLDIR)\lib +!endif + +!if !defined(TCLDIR) +!if exist("$(_INSTALLDIR)\..\include\tcl.h") +TCLINSTALL = 1 +_TCLDIR = $(_INSTALLDIR)\.. +_TCL_H = $(_INSTALLDIR)\..\include\tcl.h +TCLDIR = $(_INSTALLDIR)\.. +!else +MSG=^ +Failed to find tcl.h. Set the TCLDIR macro. +!error $(MSG) +!endif +!else +_TCLDIR = $(TCLDIR:/=\) +!if exist("$(_TCLDIR)\include\tcl.h") +TCLINSTALL = 1 +_TCL_H = $(_TCLDIR)\include\tcl.h +!elseif exist("$(_TCLDIR)\generic\tcl.h") +TCLINSTALL = 0 +_TCL_H = $(_TCLDIR)\generic\tcl.h +!else +MSG =^ +Failed to find tcl.h. The TCLDIR macro does not appear correct. +!error $(MSG) +!endif +!endif +!endif + +#-------------------------------------------------------------- +# Extract various version numbers from tcl headers +# The generated file is then included in the makefile. +#-------------------------------------------------------------- + +!if [echo REM = This file is generated from rules.vc > versions.vc] +!endif +!if [echo TCL_MAJOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" TCL_MAJOR_VERSION >> versions.vc] +!endif +!if [echo TCL_MINOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" TCL_MINOR_VERSION >> versions.vc] +!endif +!if [echo TCL_PATCH_LEVEL = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" TCL_PATCH_LEVEL >> versions.vc] +!endif + +# If building the tcl core then we need additional package versions +!if "$(PROJECT)" == "tcl" +!if [echo PKG_HTTP_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\http\pkgIndex.tcl http >> versions.vc] +!endif +!if [echo PKG_TCLTEST_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\tcltest\pkgIndex.tcl tcltest >> versions.vc] +!endif +!if [echo PKG_MSGCAT_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\msgcat\pkgIndex.tcl msgcat >> versions.vc] +!endif +!if [echo PKG_PLATFORM_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\platform\pkgIndex.tcl "platform " >> versions.vc] +!endif +!if [echo PKG_SHELL_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\platform\pkgIndex.tcl "platform::shell" >> versions.vc] +!endif +!if [echo PKG_DDE_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\dde\pkgIndex.tcl "dde " >> versions.vc] +!endif +!if [echo PKG_REG_VER =\>> versions.vc] \ + && [nmakehlp -V ..\library\reg\pkgIndex.tcl registry >> versions.vc] +!endif +!endif + +!include versions.vc + +#-------------------------------------------------------------- +# Setup tcl version dependent stuff headers +#-------------------------------------------------------------- + +!if "$(PROJECT)" != "tcl" + +TCL_VERSION = $(TCL_MAJOR_VERSION)$(TCL_MINOR_VERSION) + +!if $(TCL_VERSION) < 81 +TCL_DOES_STUBS = 0 +!else +TCL_DOES_STUBS = 1 +!endif + +!if $(TCLINSTALL) +TCLSH = "$(_TCLDIR)\bin\tclsh$(TCL_VERSION)$(SUFX).exe" +!if !exist($(TCLSH)) && $(TCL_THREADS) +TCLSH = "$(_TCLDIR)\bin\tclsh$(TCL_VERSION)t$(SUFX).exe" +!endif +TCLSTUBLIB = "$(_TCLDIR)\lib\tclstub$(TCL_VERSION).lib" +TCLIMPLIB = "$(_TCLDIR)\lib\tcl$(TCL_VERSION)$(SUFX).lib" +TCL_LIBRARY = $(_TCLDIR)\lib +TCLREGLIB = "$(_TCLDIR)\lib\tclreg13$(SUFX:t=).lib" +TCLDDELIB = "$(_TCLDIR)\lib\tcldde14$(SUFX:t=).lib" +COFFBASE = \must\have\tcl\sources\to\build\this\target +TCLTOOLSDIR = \must\have\tcl\sources\to\build\this\target +TCL_INCLUDES = -I"$(_TCLDIR)\include" +!else +TCLSH = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tclsh$(TCL_VERSION)$(SUFX).exe" +!if !exist($(TCLSH)) && $(TCL_THREADS) +TCLSH = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tclsh$(TCL_VERSION)t$(SUFX).exe" +!endif +TCLSTUBLIB = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tclstub$(TCL_VERSION).lib" +TCLIMPLIB = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tcl$(TCL_VERSION)$(SUFX).lib" +TCL_LIBRARY = $(_TCLDIR)\library +TCLREGLIB = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tclreg13$(SUFX:t=).lib" +TCLDDELIB = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tcldde14$(SUFX:t=).lib" +COFFBASE = "$(_TCLDIR)\win\coffbase.txt" +TCLTOOLSDIR = $(_TCLDIR)\tools +TCL_INCLUDES = -I"$(_TCLDIR)\generic" -I"$(_TCLDIR)\win" +!endif + +!endif + +#------------------------------------------------------------------------- +# Locate the Tk headers to build against +#------------------------------------------------------------------------- + +!if "$(PROJECT)" == "tk" +_TK_H = ..\generic\tk.h +_INSTALLDIR = $(_INSTALLDIR)\.. +!endif + +!ifdef PROJECT_REQUIRES_TK +!if !defined(TKDIR) +!if exist("$(_INSTALLDIR)\..\include\tk.h") +TKINSTALL = 1 +_TKDIR = $(_INSTALLDIR)\.. +_TK_H = $(_TKDIR)\include\tk.h +TKDIR = $(_TKDIR) +!elseif exist("$(_TCLDIR)\include\tk.h") +TKINSTALL = 1 +_TKDIR = $(_TCLDIR) +_TK_H = $(_TKDIR)\include\tk.h +TKDIR = $(_TKDIR) +!endif +!else +_TKDIR = $(TKDIR:/=\) +!if exist("$(_TKDIR)\include\tk.h") +TKINSTALL = 1 +_TK_H = $(_TKDIR)\include\tk.h +!elseif exist("$(_TKDIR)\generic\tk.h") +TKINSTALL = 0 +_TK_H = $(_TKDIR)\generic\tk.h +!else +MSG =^ +Failed to find tk.h. The TKDIR macro does not appear correct. +!error $(MSG) +!endif +!endif +!endif + +#------------------------------------------------------------------------- +# Extract Tk version numbers +#------------------------------------------------------------------------- + +!if defined(PROJECT_REQUIRES_TK) || "$(PROJECT)" == "tk" + +!if [echo TK_MAJOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V $(_TK_H) TK_MAJOR_VERSION >> versions.vc] +!endif +!if [echo TK_MINOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V $(_TK_H) TK_MINOR_VERSION >> versions.vc] +!endif +!if [echo TK_PATCH_LEVEL = \>> versions.vc] \ + && [nmakehlp -V $(_TK_H) TK_PATCH_LEVEL >> versions.vc] +!endif + +!include versions.vc + +TK_DOTVERSION = $(TK_MAJOR_VERSION).$(TK_MINOR_VERSION) +TK_VERSION = $(TK_MAJOR_VERSION)$(TK_MINOR_VERSION) + +!if "$(PROJECT)" != "tk" +!if $(TKINSTALL) +WISH = "$(_TKDIR)\bin\wish$(TK_VERSION)$(SUFX).exe" +TKSTUBLIB = "$(_TKDIR)\lib\tkstub$(TK_VERSION).lib" +TKIMPLIB = "$(_TKDIR)\lib\tk$(TK_VERSION)$(SUFX).lib" +TK_INCLUDES = -I"$(_TKDIR)\include" +!else +WISH = "$(_TKDIR)\win\$(BUILDDIRTOP)\wish$(TCL_VERSION)$(SUFX).exe" +TKSTUBLIB = "$(_TKDIR)\win\$(BUILDDIRTOP)\tkstub$(TCL_VERSION).lib" +TKIMPLIB = "$(_TKDIR)\win\$(BUILDDIRTOP)\tk$(TCL_VERSION)$(SUFX).lib" +TK_INCLUDES = -I"$(_TKDIR)\generic" -I"$(_TKDIR)\win" -I"$(_TKDIR)\xlib" +!endif +!endif + +!endif + +#---------------------------------------------------------- +# Display stats being used. +#---------------------------------------------------------- + +!message *** Intermediate directory will be '$(TMP_DIR)' +!message *** Output directory will be '$(OUT_DIR)' +!message *** Suffix for binaries will be '$(SUFX)' +!message *** Optional defines are '$(OPTDEFINES)' +!message *** Compiler version $(VCVER). Target machine is $(MACHINE) +!message *** Host architecture is $(NATIVE_ARCH) +!message *** Compiler options '$(COMPILERFLAGS) $(OPTIMIZATIONS) $(DEBUGFLAGS) $(WARNINGS)' +!message *** Link options '$(LINKERFLAGS)' + +!endif + diff --git a/photo_edf1.JPG b/photo_edf1.JPG new file mode 100644 index 0000000000000000000000000000000000000000..36c10e1d2651287dd1acbf7b96845556100366a6 GIT binary patch literal 177811 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%0A8P&3wKQ!Zu+$n<+2ulpKbQE4 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 C-~18) literal 0 HcmV?d00001 diff --git a/photo_edf2.JPG b/photo_edf2.JPG new file mode 100644 index 0000000000000000000000000000000000000000..fb8acb01201380379dcadb72a8ebd54355941b13 GIT binary patch literal 1957142 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%0A%6_rBe_Gck!A+C`o^nb2G+w4VBFCqE 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%0D_;3N@4e{&>7l3O!j+ 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%0D>Ji1k#| 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%0ALP(Gof}J{v(@6l!^c~^ZRuV<f!|1lVrfv% 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%0D#(`z+#h)PDtA_gHm&ic-FT4-Hc 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%0Ap@b&PPg({#5vCg9DD^Wur2XvrPb_MzSC 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 HAkhB;DRZVp literal 0 HcmV?d00001 diff --git a/photo_edf3.JPG b/photo_edf3.JPG new file mode 100644 index 0000000000000000000000000000000000000000..c23f82879fededfc122a17bbd8a823c95a42232a GIT binary patch literal 38339 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 V)(E^T?d5U6S(5OXfLdiI|JfM~Pa^;T literal 0 HcmV?d00001